Flow (v3-final corrected):
1. Model call succeeds.
2. Server passes the AI reply through unchanged as 'markdown' (the
system prompt allows ```language fences + a brief explanation, so
the response is real markdown that marked can render into code
blocks, headings, etc.).
3. Client OpenCodeCellActions._handleResponse calls prompt.setOutput(
resp.markdown); the inline prompt widget renders it with
marked.parse and shows it in a new output area (hidden until a
response arrives). The cell source is NOT replaced.
4. User can close the output or cancel the whole prompt.
Server:
- New unified system prompt: '你是代码助手 ... 按指令修改代码,可附简
短说明' (allows ```fences``` + explanation; no more 'no markdown
fences' restriction).
- EditHandler returns {ok, markdown, sessionId, notebookPath} (raw
text, fences intact). _strip_code_fence kept as a helper for any
future apply-to-cell path; no longer called.
- finalSource field dropped (the cell-apply path is gone).
Client:
- OpenCodeSuccess: markdown: string (finalSource removed).
- OpenCodeInlinePrompt: new output area, setOutput(md) renders via
marked.parse, hideOutput() closes it.
- OpenCodeCellActions._handleResponse: setOutput(markdown) instead of
sharedModel.setSource + auto-hide. The prompt stays open so the
user can read the output.
- Uses marked@17 (already in node_modules via JupyterLab; no new dep).
- CSS: output area styling (border, max-height 320px scroll, code/pre
styling, close button).
Tests: pytest 34/34, jest 29/29. marked is mocked in jest.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The inline prompt now renders two <select> boxes:
- Provider: lists every provider from the cached /config/providers
response. Default = first provider.
- Model: lists that provider's model keys (Object.keys of the Record).
Default = default[providerID] (from the same response) when that
modelID is present in the provider's models; otherwise the first
model. Changing the provider select rebuilds the model select with
the new provider's models and its own default[provider].
On submit the two select values are read separately and passed as
providerId / modelId to the OpenCodeRequest (no more '|' delimiter).
The /opencode-bridge/edit body shape is unchanged (providerId, modelId).
Also fix the index.ts startup console log (p.models is a Record, use
Object.values), and drop the unused _cell field on the prompt widget
(now an unused param).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the OpenCode server docs (GET /config/providers), each Provider's
'models' is { [modelID: string]: Model } (a Record), so model IDs can be
referenced as strings (matching the 'default' map of providerID ->
modelID). Our code treated it as Model[], which:
1. crashed with 'p.models is not iterable' on real responses, and
2. after the previous Array.isArray guard, skipped EVERY provider
(a Record is never an array), so the inline picker showed nothing.
Update types.ts (OpenCodeProvider.models -> Record, add OpenCodeModel,
add optional 'default' map to the response) and rewrite
flattenProviders to iterate Object.keys(p.models). Update test fixture
to the Record shape.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Some OpenCode Serve /config/providers responses include providers
without a 'models' field (or with a non-array value). The un-guarded
for-of on p.models then throws 'p.models is not iterable' and crashes
the inline prompt construction. Skip such providers instead.
Also re-assert the button label as 'AI' (matches the df2f3df label
simplification, which the 02cedac revert had bundled away).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously the inline prompt was Widget.attach'd to cell.node, which
in JupyterLab places it at the very bottom of the cell (after the
output area and In/Out prompt) — so the user couldn't see it, especially
on taller cells. Attaching to cell.inputArea.node puts the prompt
directly under the editor, which is the natural inline position.
Falls back to cell.node if inputArea is unexpectedly null.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tornado rejects fetch(request, **kwargs) when the first arg is already an
HTTPRequest and the kwargs overlap request construction (auth_username /
auth_password). Move auth onto the HTTPRequest. Add OpenCodeClient.close()
to release its AsyncHTTPClient; module-level atexit closes the tornado
singleton so 'jupyter lab' can exit cleanly after SIGINT instead of
hanging on 'received signal 2, stopping'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removes the pnpm node-linker so 'jlpm test' / 'jlpm build' run directly
without the YARN_NODE_LINKER=node-modules env override. Re-run 'jlpm
install' to regenerate node_modules in the flat layout (the in-flight
yarn.lock migration in package.json already targets node-modules).
A JupyterLab extension that bridges the cell UI to a local OpenCode Serve
process. The extension is a dual package: a Python server extension
exposed under /opencode-bridge/*, plus a TypeScript frontend that
registers per-cell toolbars.
Backend (Python, tornado)
- Slice 1: config + auth + OpenCode HTTP client (tornado.httpclient,
no aiohttp). 4 settings in schema/plugin.json (url, user, password,
request timeout).
- Slice 2: handlers for /hello, /health, /providers, /edit.
- Slice 2.1 (correction): SessionManager with 1 notebook = 1 session
mapping, async-safe via per-path locks, 404 recovery via invalidate().
Two new endpoints: GET /sessions, DELETE /session?notebook=<path>.
- 32 pytest tests pass.
Frontend (TypeScript, JupyterLab 4.6)
- src/types.ts: CellContext, OpenCodeRequest/Response, OpenCodeSettings.
- src/context/cell_context.ts: extract CellContext from a CodeCell +
its parent NotebookPanel, structured error collection.
- src/api/opencode_client.ts: callOpenCodeEdit, callOpenCodeProviders.
- src/components/opencode_cell_footer.ts: OpenCodeCellFooter Widget
implementing ICellFooter with 3 buttons (optimize / fix / edit),
resolved via this.parent instanceof CodeCell. NOT cellToolbar
(does not exist in JL 4.6) and NOT Widget.findParent (removed in
@lumino/widgets 2.x).
- src/components/opencode_cell_factory.ts: Cell.ContentFactory
subclass returning the OpenCodeCellFooter.
- src/components/opencode_installer.ts: installOpenCodeEverywhere
patches every notebook (existing + new) to use the custom factory.
- src/index.ts: registers the factory, loads settings, fetches
/providers on activation and logs the list to the console.
- 23 jest tests pass (mocked JupyterLab boundary, pnpm path safe).
Settings
- 6 fields: 3 auth (url/user/password) + 1 timeout + 2 model selection
(provider/model). Provider list is fetched at startup from
/opencode-bridge/providers and printed to the browser console so
users can copy values into Settings Editor.
Docs
- design.md: 6 sections covering architecture, UI flow, API contract,
TS skeletons, session management (v0.2.1 correction), and
provider/model selection (v0.2.2 addition).
- CLAUDE.md: agent guidance for working in this repo.
- TODO.md: remaining work for Slices 4-7 + v0.4+ backlog.
CI
- Gitea release workflow at .github/workflows/build.yml.
- Bark notification helper (non-fatal on failure).
Generated artefacts ignored: opencode_bridge/labextension/, _version.py,
*.tsbuildinfo, junit.xml, test.ipynb scratch notebook.