Files
notebook-ai-extension/src/types.ts
T
tao.chen 310d50759a fix: SSE filter no longer drops content events for a different session
Bug: after switching to a new session via the SessionSelector, the
next /edit would not get a reply. Root cause: applyEvent dropped
ANY event whose sessionID didn't match the prompt's _sessionId,
including text deltas / reasoning / tool / idle. OpenCode's sessionID
extraction from event payloads is not perfectly consistent across
event types, so the prompt could end up with a stale _sessionId
that doesn't match the events coming in, and EVERY event got
filtered out -> no reply.

Fix: the cross-session filter is now strict ONLY for
permission.asked / question.asked (so the user can't accidentally
reply to another session's prompt). Content events always pass
through. The server's /events handler is the single source of
truth for session filtering (it has the URL ?session= param); the
client's filter would only ever mask the user's interaction with
their own active session.

Also: drop the cell-context auto-injection. The OpenCodeRequest
now carries only {notebookPath}. The user explicitly attaches
whatever they want via the new '📋 插入单元格内容' button
(inserts the cell source as a markdown code block into the input).
This makes the LLM context match user intent and stops the
OpenCode prompt from being polluted with stale previousCode /
traceback snapshots.

Backend
-------
- _build_request_body: now takes only the prompt, returns
  parts=[{text: prompt}]. No more <previous_cell>/<traceback>/<cell>
  tag wrapping.

Frontend
-------
- types.ts: CellContext collapsed to {notebookPath}. ErrorOutput /
  cellId / source / previousCode / error / cellIndex / totalCells
  / language all removed.
- opencode_cell_actions.ts: extractCellContextFromCell() replaced
  with extractNotebookPathFromCell(). _context field renamed to
  _notebookPath. NotebookPanel / extractCellContext / context/
  cell_context imports all removed.
- src/context/cell_context.ts + src/__tests__/cell_context.spec.ts:
  deleted.

New feature: '📋 插入单元格内容' button
----------------------------------------
The button sits at the start of the actions row (visually
left-aligned via margin-right:auto) and appends the current cell's
source as a markdown code fence to the textarea. Caret is moved
to the end so the user can keep typing. The fence info string is
the cell's model type (falls back to a plain fence if the type
isn't a clean language identifier). No-op for empty cells.

Tests
-----
- 72 jest (was 78: -8 cell-context tests + 1 SSE filter test +
  3 insert-cell tests + rewrites). 73 pytest (unchanged). Build
  green.
2026-07-28 18:02:21 +08:00

150 lines
4.3 KiB
TypeScript

/**
* Shared types for the opencode-bridge frontend.
*
* Mirrors the Python server's response shapes in `opencode_bridge/routes.py`
* and the OpenCode Serve SSE event shape documented in
* `/Users/taochen/temp/demo.html` (`handleGlobalEvent` / `processSessionEvent`).
*
* v4: POST /edit is async (returns immediately with sessionId). The LLM
* reply is consumed via the GET /events SSE stream. Each SSE event is
* a JSON object with a `type` string; consumers route on `type`.
*/
/**
* Minimal context carried with each edit request. Only the
* notebookPath is needed (so the server can pick the right OpenCode
* session). The cell source, previous-cell, and traceback are NO
* LONGER auto-injected into the LLM prompt — the user inserts them
* manually via the "📋 插入单元格内容" button (or pastes anything
* else they want) so the LLM only sees what they explicitly chose
* to send. v0.1.x used to pack a lot more here; that auto-wrap
* made the LLM context hard to reason about and made the user's
* intent ambiguous.
*/
export interface CellContext {
notebookPath: string;
}
export interface OpenCodeRequest {
prompt: string;
context: CellContext;
providerId?: string;
modelId?: string;
}
/**
* Response from POST /opencode-bridge/edit (async).
* The actual assistant reply arrives via the /events SSE stream and is
* NOT embedded in this response.
*/
export interface OpenCodeEditSuccess {
ok: true;
sessionId: string;
notebookPath: string;
}
export interface OpenCodeEditFailure {
ok: false;
error: string;
}
export type OpenCodeEditResponse = OpenCodeEditSuccess | OpenCodeEditFailure;
/**
* One SSE event from GET /opencode-bridge/events (proxied from OpenCode
* Serve's /global/event). The shape is loosely typed because OpenCode's
* payload envelope is not strictly documented; we follow demo.html's
* defensive property lookup pattern (try top-level then nested under
* `payload`).
*/
export interface OpenCodeEvent {
type: string;
payload?: {
type?: string;
properties?: { [k: string]: any };
syncEvent?: { aggregateID?: string };
};
properties?: { [k: string]: any };
syncEvent?: { aggregateID?: string };
}
/** Response from GET /opencode-bridge/session-messages?notebook=... .
* Projected from OpenCode's {info, parts}[] by the server into a
* frontend-friendly {role, content}[]. */
export interface OpenCodeMessage {
role: "user" | "assistant";
content: string;
}
export interface OpenCodeMessagesResponse {
messages: OpenCodeMessage[];
}
// ---------------------------------------------------------------------------
// Multi-session management types (1 notebook can bind N sessions, one active)
// ---------------------------------------------------------------------------
/** One session on the OpenCode Serve side (returned by GET /session). */
export interface OpenCodeSessionMeta {
id: string;
title?: string;
createdAt?: number;
updatedAt?: number;
[k: string]: unknown;
}
/** Response from GET /opencode-bridge/sessions/all. */
export interface OpenCodeAllSessionsResponse {
ok: boolean;
sessions: OpenCodeSessionMeta[];
error?: string;
}
/** One session bound to a notebook (returned by GET /sessions/notebook). */
export interface OpenCodeNotebookSession {
sessionId: string;
title?: string;
createdAt?: number;
updatedAt?: number;
isActive: boolean;
}
/** Response from GET /opencode-bridge/sessions/notebook?notebook=... */
export interface OpenCodeNotebookSessionsResponse {
ok: boolean;
notebookPath: string;
activeSessionId: string | null;
sessions: OpenCodeNotebookSession[];
error?: string;
}
/** Response from POST/PUT /sessions/notebook and PUT /sessions/active. */
export interface OpenCodeSessionOpResponse {
ok: boolean;
notebookPath: string;
sessionId?: string;
activeSessionId?: string | null;
deleted?: boolean;
error?: string;
}
/** Response from GET /opencode-bridge/providers (proxies OpenCode /config/providers).
* Each Provider.models is a Record keyed by modelID (NOT an array). */
export interface OpenCodeModel {
id: string;
name?: string;
[k: string]: unknown;
}
export interface OpenCodeProvider {
id: string;
name?: string;
source?: string;
models: { [modelID: string]: OpenCodeModel };
}
export interface OpenCodeProvidersResponse {
providers: OpenCodeProvider[];
default?: { [providerID: string]: string };
}