Replace the synchronous /edit (wait for full markdown response) with an
async + SSE flow per demo.html so the user sees text stream in real-time
and can interact with permission/question events the agent raises.
Backend
-------
- EditHandler now calls OpenCode /session/:id/prompt_async and returns
immediately with {ok, sessionId, notebookPath}; the LLM reply is no
longer embedded in this response.
- New GlobalEventHandler proxies OpenCode /global/event as
text/event-stream. Server forwards ALL events; the client filters.
A too-eager server-side ?session= filter was silently dropping events
the client would have accepted, so it was removed.
- New PermissionReplyHandler + QuestionReplyHandler forward user
replies (once/always/reject and freeform answer) back to OpenCode
Serve at /session/:sid/permissions/:permId and
/session/:sid/question/:qId/reply.
- OpenCodeClient gains send_message_async(), stream_global_events()
(async generator over the SSE feed via tornado streaming_callback),
reply_permission() and reply_question(). Legacy send_message_sync
removed.
Frontend
--------
- subscribeOpenCodeEvents() opens a fetch+reader SSE client (XSRF
token injected from serverSettings); AbortController-backed close()
is idempotent.
- OpenCodeInlinePrompt.applyEvent() routes events into the streaming
UI: text delta -> assistant message (re-rendered as markdown on
every delta so the user sees formatted <pre><code> blocks in
real-time, not raw fence source); reasoning/tool/permission/question
get collapsible details blocks. session.idle resets stream pointers
and fires onStreamEnd.
- Permission/question blocks render real interactive UI: three
buttons (once/always/reject) for permission, a text input + submit
for question. Click handlers post through the new API routes and
show success/failure status in place.
- Centralized idle detection (4 shapes: top-level + payload-nested
x {session.idle, session.status/idle}) inside the prompt; cell
action reacts via onStreamEnd callback rather than re-parsing
event types.
- System / workspace / pty / lsp / mcp / installation events AND
session-level control events (agent.switched, model.switched,
file.edited) are not rendered in the frontend.
Tests
-----
- 52 backend pytest (was 43)
- 58 frontend jest (was 46)
design.md section 3 updated for the new async /edit response shape
and the new GET /events SSE endpoint.
Co-Authored-By: Claude <noreply@anthropic.com>
104 lines
2.7 KiB
TypeScript
104 lines
2.7 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`.
|
|
*/
|
|
|
|
export interface ErrorOutput {
|
|
ename: string;
|
|
evalue: string;
|
|
traceback: string[];
|
|
}
|
|
|
|
export interface CellContext {
|
|
notebookPath: string;
|
|
cellId: string;
|
|
language: string;
|
|
cellIndex: number;
|
|
totalCells: number;
|
|
source: string;
|
|
previousCode: string | null;
|
|
error: ErrorOutput | null;
|
|
}
|
|
|
|
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[];
|
|
}
|
|
|
|
/** 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 };
|
|
}
|