@tanstack/ai-opencode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/esm/adapters/projection.d.ts +14 -0
- package/dist/esm/adapters/projection.js +83 -0
- package/dist/esm/adapters/projection.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +47 -0
- package/dist/esm/adapters/text.js +231 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/messages/prompt.d.ts +16 -0
- package/dist/esm/messages/prompt.js +37 -0
- package/dist/esm/messages/prompt.js.map +1 -0
- package/dist/esm/model-meta.d.ts +14 -0
- package/dist/esm/model-meta.js +13 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/process/permissions.d.ts +50 -0
- package/dist/esm/process/permissions.js +51 -0
- package/dist/esm/process/permissions.js.map +1 -0
- package/dist/esm/process/sandbox-server.d.ts +23 -0
- package/dist/esm/process/sandbox-server.js +72 -0
- package/dist/esm/process/sandbox-server.js.map +1 -0
- package/dist/esm/process/server.d.ts +77 -0
- package/dist/esm/process/server.js +160 -0
- package/dist/esm/process/server.js.map +1 -0
- package/dist/esm/provider-options.d.ts +18 -0
- package/dist/esm/stream/queue.d.ts +18 -0
- package/dist/esm/stream/queue.js +56 -0
- package/dist/esm/stream/queue.js.map +1 -0
- package/dist/esm/stream/sdk-types.d.ts +134 -0
- package/dist/esm/stream/translate.d.ts +51 -0
- package/dist/esm/stream/translate.js +305 -0
- package/dist/esm/stream/translate.js.map +1 -0
- package/package.json +62 -0
- package/src/adapters/projection.ts +220 -0
- package/src/adapters/text.ts +368 -0
- package/src/index.ts +40 -0
- package/src/messages/prompt.ts +67 -0
- package/src/model-meta.ts +24 -0
- package/src/process/permissions.ts +119 -0
- package/src/process/sandbox-server.ts +130 -0
- package/src/process/server.ts +270 -0
- package/src/provider-options.ts +19 -0
- package/src/stream/queue.ts +64 -0
- package/src/stream/sdk-types.ts +104 -0
- package/src/stream/translate.ts +419 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural subset of the `@opencode-ai/sdk` event types that the stream
|
|
3
|
+
* translator consumes.
|
|
4
|
+
*
|
|
5
|
+
* These are intentionally defined structurally (rather than imported from the
|
|
6
|
+
* OpenCode SDK) so the translator stays a pure, fixture-testable state machine
|
|
7
|
+
* and the package's public types don't depend on the SDK's generated schema
|
|
8
|
+
* types. Unknown part or event types fall through every branch at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export interface OpencodeTokens {
|
|
11
|
+
input?: number;
|
|
12
|
+
output?: number;
|
|
13
|
+
reasoning?: number;
|
|
14
|
+
cache?: {
|
|
15
|
+
read?: number;
|
|
16
|
+
write?: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Error payload attached to a failed assistant message. */
|
|
20
|
+
export interface OpencodeMessageError {
|
|
21
|
+
name: string;
|
|
22
|
+
data?: {
|
|
23
|
+
message?: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The final assistant message of a turn, returned by the blocking prompt
|
|
28
|
+
* call. Carries the finish reason, token usage, and any fatal error.
|
|
29
|
+
*/
|
|
30
|
+
export interface OpencodeAssistantMessage {
|
|
31
|
+
id: string;
|
|
32
|
+
role: 'assistant';
|
|
33
|
+
finish?: string;
|
|
34
|
+
error?: OpencodeMessageError;
|
|
35
|
+
tokens?: OpencodeTokens;
|
|
36
|
+
cost?: number;
|
|
37
|
+
}
|
|
38
|
+
export type OpencodeToolState = {
|
|
39
|
+
status: 'pending';
|
|
40
|
+
input?: Record<string, unknown>;
|
|
41
|
+
} | {
|
|
42
|
+
status: 'running';
|
|
43
|
+
input?: Record<string, unknown>;
|
|
44
|
+
title?: string;
|
|
45
|
+
} | {
|
|
46
|
+
status: 'completed';
|
|
47
|
+
input?: Record<string, unknown>;
|
|
48
|
+
output: string;
|
|
49
|
+
title?: string;
|
|
50
|
+
} | {
|
|
51
|
+
status: 'error';
|
|
52
|
+
input?: Record<string, unknown>;
|
|
53
|
+
error: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The OpenCode message-part kinds the translator understands. The trailing
|
|
57
|
+
* catch-all member keeps the union open to other kinds (file, step-start,
|
|
58
|
+
* step-finish, snapshot, patch, agent, ...); the translator dispatches via
|
|
59
|
+
* the `is*Part` type guards, so those kinds simply match no guard.
|
|
60
|
+
*/
|
|
61
|
+
export type OpencodePart = {
|
|
62
|
+
id: string;
|
|
63
|
+
sessionID?: string;
|
|
64
|
+
type: 'text';
|
|
65
|
+
text: string;
|
|
66
|
+
} | {
|
|
67
|
+
id: string;
|
|
68
|
+
sessionID?: string;
|
|
69
|
+
type: 'reasoning';
|
|
70
|
+
text: string;
|
|
71
|
+
} | {
|
|
72
|
+
id: string;
|
|
73
|
+
sessionID?: string;
|
|
74
|
+
type: 'tool';
|
|
75
|
+
callID: string;
|
|
76
|
+
tool: string;
|
|
77
|
+
state: OpencodeToolState;
|
|
78
|
+
} | {
|
|
79
|
+
id: string;
|
|
80
|
+
sessionID?: string;
|
|
81
|
+
type: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* The OpenCode events the translator understands. This is a closed
|
|
85
|
+
* discriminated union (so `event.type` narrows cleanly); the server forwards
|
|
86
|
+
* raw SDK events cast to this type, and any event whose `type` isn't listed
|
|
87
|
+
* here simply matches no branch and is ignored at runtime.
|
|
88
|
+
*/
|
|
89
|
+
export type OpencodeEvent = {
|
|
90
|
+
type: 'message.part.updated';
|
|
91
|
+
properties: {
|
|
92
|
+
part: OpencodePart;
|
|
93
|
+
delta?: string;
|
|
94
|
+
};
|
|
95
|
+
} | {
|
|
96
|
+
type: 'message.updated';
|
|
97
|
+
properties: {
|
|
98
|
+
info: {
|
|
99
|
+
sessionID?: string;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
} | {
|
|
103
|
+
type: 'session.idle';
|
|
104
|
+
properties: {
|
|
105
|
+
sessionID: string;
|
|
106
|
+
};
|
|
107
|
+
} | {
|
|
108
|
+
type: 'session.error';
|
|
109
|
+
properties: {
|
|
110
|
+
sessionID?: string;
|
|
111
|
+
error?: OpencodeMessageError;
|
|
112
|
+
};
|
|
113
|
+
} | {
|
|
114
|
+
type: 'todo.updated';
|
|
115
|
+
properties: {
|
|
116
|
+
sessionID: string;
|
|
117
|
+
todos: Array<unknown>;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Events fed to the translator: the session id once established, every
|
|
122
|
+
* session-scoped OpenCode event, and a terminal `done` carrying the final
|
|
123
|
+
* assistant message (the adapter's async queue produces these).
|
|
124
|
+
*/
|
|
125
|
+
export type OpencodeStreamEvent = {
|
|
126
|
+
kind: 'session';
|
|
127
|
+
sessionId: string;
|
|
128
|
+
} | {
|
|
129
|
+
kind: 'event';
|
|
130
|
+
event: OpencodeEvent;
|
|
131
|
+
} | {
|
|
132
|
+
kind: 'done';
|
|
133
|
+
message: OpencodeAssistantMessage;
|
|
134
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { StreamChunk } from '@tanstack/ai';
|
|
2
|
+
import { OpencodeStreamEvent } from './sdk-types.js';
|
|
3
|
+
/** Name of the CUSTOM event carrying the OpenCode session id. */
|
|
4
|
+
export declare const SESSION_ID_EVENT = "opencode.session-id";
|
|
5
|
+
/** Name of the CUSTOM event carrying the harness's todo list updates. */
|
|
6
|
+
export declare const TODO_EVENT = "opencode.todo";
|
|
7
|
+
/** Server name used for bridged TanStack tools. */
|
|
8
|
+
export declare const BRIDGED_MCP_SERVER_NAME = "tanstack";
|
|
9
|
+
export interface TranslateContext {
|
|
10
|
+
model: string;
|
|
11
|
+
runId: string;
|
|
12
|
+
threadId: string;
|
|
13
|
+
parentRunId?: string;
|
|
14
|
+
genId: () => string;
|
|
15
|
+
/**
|
|
16
|
+
* Names of bridged TanStack tools, used to surface the harness's MCP tool
|
|
17
|
+
* calls under the names the application registered.
|
|
18
|
+
*/
|
|
19
|
+
bridgedToolNames?: ReadonlySet<string>;
|
|
20
|
+
/** Called for each raw stream event, for logging. */
|
|
21
|
+
onStreamEvent?: (event: OpencodeStreamEvent) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the AG-UI tool-call name for an OpenCode tool part. OpenCode names
|
|
25
|
+
* MCP tools `<server>_<tool>`, so bridged TanStack tools arrive as
|
|
26
|
+
* `tanstack_<tool>` and are surfaced under the names the application
|
|
27
|
+
* registered; everything else (built-in `read`, `edit`, `bash`, ... and
|
|
28
|
+
* foreign MCP tools) uses the harness tool name verbatim.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveToolName(tool: string, bridgedToolNames: ReadonlySet<string> | undefined): string;
|
|
31
|
+
/**
|
|
32
|
+
* Translate an OpenCode event stream into AG-UI StreamChunk events.
|
|
33
|
+
*
|
|
34
|
+
* The harness runs its own agent loop and executes its own tools, so the
|
|
35
|
+
* translation always ends with `finishReason: 'stop'` (or `'length'` /
|
|
36
|
+
* RUN_ERROR) — never `'tool_calls'`. Harness tool activity is emitted as
|
|
37
|
+
* already-resolved TOOL_CALL_START/ARGS/END + TOOL_CALL_RESULT sequences so
|
|
38
|
+
* UIs can render it, while the TanStack engine never tries to execute them.
|
|
39
|
+
*
|
|
40
|
+
* OpenCode delivers true token-level deltas for both assistant text and
|
|
41
|
+
* reasoning via `message.part.updated` events (a `delta` string when
|
|
42
|
+
* incremental, otherwise the full part text, from which the delta is
|
|
43
|
+
* derived). The final assistant message — finish reason, token usage, and any
|
|
44
|
+
* fatal error — arrives as the terminal `done` event.
|
|
45
|
+
*
|
|
46
|
+
* Invariant: every TOOL_CALL_START is eventually paired with a
|
|
47
|
+
* TOOL_CALL_RESULT (synthesized as `{"status":"interrupted"}` when the run
|
|
48
|
+
* ends or aborts before the harness reported one) so the engine's
|
|
49
|
+
* pending-tool-call scan on the next request never force-executes them.
|
|
50
|
+
*/
|
|
51
|
+
export declare function translateOpencodeStream(events: AsyncIterable<OpencodeStreamEvent>, ctx: TranslateContext): AsyncIterable<StreamChunk>;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { EventType, buildBaseUsage } from "@tanstack/ai";
|
|
2
|
+
const SESSION_ID_EVENT = "opencode.session-id";
|
|
3
|
+
const TODO_EVENT = "opencode.todo";
|
|
4
|
+
const BRIDGED_MCP_SERVER_NAME = "tanstack";
|
|
5
|
+
function resolveToolName(tool, bridgedToolNames) {
|
|
6
|
+
if (!bridgedToolNames || bridgedToolNames.size === 0) return tool;
|
|
7
|
+
if (bridgedToolNames.has(tool)) return tool;
|
|
8
|
+
if (tool.startsWith("tanstack_") && bridgedToolNames.has(tool.slice(9))) {
|
|
9
|
+
return tool.slice(9);
|
|
10
|
+
}
|
|
11
|
+
return tool;
|
|
12
|
+
}
|
|
13
|
+
function buildUsage(tokens) {
|
|
14
|
+
if (!tokens) return void 0;
|
|
15
|
+
const promptTokens = tokens.input ?? 0;
|
|
16
|
+
const completionTokens = tokens.output ?? 0;
|
|
17
|
+
const result = buildBaseUsage({
|
|
18
|
+
promptTokens,
|
|
19
|
+
completionTokens,
|
|
20
|
+
totalTokens: promptTokens + completionTokens
|
|
21
|
+
});
|
|
22
|
+
if (tokens.cache?.read) {
|
|
23
|
+
result.promptTokensDetails = { cachedTokens: tokens.cache.read };
|
|
24
|
+
}
|
|
25
|
+
if (tokens.reasoning) {
|
|
26
|
+
result.completionTokensDetails = { reasoningTokens: tokens.reasoning };
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
const isTextPart = (part) => part.type === "text";
|
|
31
|
+
const isReasoningPart = (part) => part.type === "reasoning";
|
|
32
|
+
const isToolPart = (part) => part.type === "tool";
|
|
33
|
+
function messageError(message) {
|
|
34
|
+
if (!message.error) return void 0;
|
|
35
|
+
return { message: message.error.data?.message ?? message.error.name };
|
|
36
|
+
}
|
|
37
|
+
async function* translateOpencodeStream(events, ctx) {
|
|
38
|
+
const { model, runId, threadId, genId } = ctx;
|
|
39
|
+
const now = () => Date.now();
|
|
40
|
+
let runStarted = false;
|
|
41
|
+
const unresolvedToolCalls = /* @__PURE__ */ new Set();
|
|
42
|
+
const openedToolCalls = /* @__PURE__ */ new Set();
|
|
43
|
+
const resolvedToolCalls = /* @__PURE__ */ new Set();
|
|
44
|
+
const textAccumulators = /* @__PURE__ */ new Map();
|
|
45
|
+
let openTextId = null;
|
|
46
|
+
let openReasoningId = null;
|
|
47
|
+
function* startRun() {
|
|
48
|
+
if (runStarted) return;
|
|
49
|
+
runStarted = true;
|
|
50
|
+
yield {
|
|
51
|
+
type: EventType.RUN_STARTED,
|
|
52
|
+
runId,
|
|
53
|
+
threadId,
|
|
54
|
+
model,
|
|
55
|
+
timestamp: now(),
|
|
56
|
+
...ctx.parentRunId !== void 0 && { parentRunId: ctx.parentRunId }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function* closeText() {
|
|
60
|
+
if (openTextId !== null) {
|
|
61
|
+
yield {
|
|
62
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
63
|
+
messageId: openTextId,
|
|
64
|
+
model,
|
|
65
|
+
timestamp: now()
|
|
66
|
+
};
|
|
67
|
+
openTextId = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function* closeReasoning() {
|
|
71
|
+
if (openReasoningId !== null) {
|
|
72
|
+
yield {
|
|
73
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
74
|
+
messageId: openReasoningId,
|
|
75
|
+
model,
|
|
76
|
+
timestamp: now()
|
|
77
|
+
};
|
|
78
|
+
yield {
|
|
79
|
+
type: EventType.REASONING_END,
|
|
80
|
+
messageId: openReasoningId,
|
|
81
|
+
model,
|
|
82
|
+
timestamp: now()
|
|
83
|
+
};
|
|
84
|
+
openReasoningId = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function* synthesizeUnresolvedResults() {
|
|
88
|
+
for (const toolCallId of unresolvedToolCalls) {
|
|
89
|
+
yield {
|
|
90
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
91
|
+
toolCallId,
|
|
92
|
+
messageId: genId(),
|
|
93
|
+
model,
|
|
94
|
+
timestamp: now(),
|
|
95
|
+
content: JSON.stringify({ status: "interrupted" })
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
unresolvedToolCalls.clear();
|
|
99
|
+
}
|
|
100
|
+
function* handleTextPart(part, delta) {
|
|
101
|
+
yield* closeReasoning();
|
|
102
|
+
const prev = textAccumulators.get(part.id) ?? "";
|
|
103
|
+
let deltaText;
|
|
104
|
+
if (typeof delta === "string" && delta !== "") {
|
|
105
|
+
deltaText = delta;
|
|
106
|
+
textAccumulators.set(part.id, prev + delta);
|
|
107
|
+
} else {
|
|
108
|
+
const full = part.text;
|
|
109
|
+
deltaText = full.startsWith(prev) ? full.slice(prev.length) : full;
|
|
110
|
+
textAccumulators.set(part.id, full);
|
|
111
|
+
}
|
|
112
|
+
if (deltaText === "") return;
|
|
113
|
+
if (openTextId !== part.id) {
|
|
114
|
+
yield* closeText();
|
|
115
|
+
openTextId = part.id;
|
|
116
|
+
yield {
|
|
117
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
118
|
+
messageId: part.id,
|
|
119
|
+
model,
|
|
120
|
+
timestamp: now(),
|
|
121
|
+
role: "assistant"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
yield {
|
|
125
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
126
|
+
messageId: part.id,
|
|
127
|
+
model,
|
|
128
|
+
timestamp: now(),
|
|
129
|
+
delta: deltaText,
|
|
130
|
+
content: textAccumulators.get(part.id) ?? deltaText
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function* handleReasoningPart(part, delta) {
|
|
134
|
+
yield* closeText();
|
|
135
|
+
const prev = textAccumulators.get(part.id) ?? "";
|
|
136
|
+
let deltaText;
|
|
137
|
+
if (typeof delta === "string" && delta !== "") {
|
|
138
|
+
deltaText = delta;
|
|
139
|
+
textAccumulators.set(part.id, prev + delta);
|
|
140
|
+
} else {
|
|
141
|
+
const full = part.text;
|
|
142
|
+
deltaText = full.startsWith(prev) ? full.slice(prev.length) : full;
|
|
143
|
+
textAccumulators.set(part.id, full);
|
|
144
|
+
}
|
|
145
|
+
if (deltaText === "") return;
|
|
146
|
+
if (openReasoningId !== part.id) {
|
|
147
|
+
yield* closeReasoning();
|
|
148
|
+
openReasoningId = part.id;
|
|
149
|
+
yield {
|
|
150
|
+
type: EventType.REASONING_START,
|
|
151
|
+
messageId: part.id,
|
|
152
|
+
model,
|
|
153
|
+
timestamp: now()
|
|
154
|
+
};
|
|
155
|
+
yield {
|
|
156
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
157
|
+
messageId: part.id,
|
|
158
|
+
role: "reasoning",
|
|
159
|
+
model,
|
|
160
|
+
timestamp: now()
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
yield {
|
|
164
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
165
|
+
messageId: part.id,
|
|
166
|
+
delta: deltaText,
|
|
167
|
+
model,
|
|
168
|
+
timestamp: now()
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function* openToolCall(part) {
|
|
172
|
+
if (openedToolCalls.has(part.callID)) return;
|
|
173
|
+
openedToolCalls.add(part.callID);
|
|
174
|
+
const toolCallName = resolveToolName(part.tool, ctx.bridgedToolNames);
|
|
175
|
+
const input = part.state.input ?? {};
|
|
176
|
+
const args = JSON.stringify(input);
|
|
177
|
+
yield {
|
|
178
|
+
type: EventType.TOOL_CALL_START,
|
|
179
|
+
toolCallId: part.callID,
|
|
180
|
+
toolCallName,
|
|
181
|
+
toolName: toolCallName,
|
|
182
|
+
model,
|
|
183
|
+
timestamp: now()
|
|
184
|
+
};
|
|
185
|
+
yield {
|
|
186
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
187
|
+
toolCallId: part.callID,
|
|
188
|
+
model,
|
|
189
|
+
timestamp: now(),
|
|
190
|
+
delta: args,
|
|
191
|
+
args
|
|
192
|
+
};
|
|
193
|
+
yield {
|
|
194
|
+
type: EventType.TOOL_CALL_END,
|
|
195
|
+
toolCallId: part.callID,
|
|
196
|
+
toolCallName,
|
|
197
|
+
toolName: toolCallName,
|
|
198
|
+
model,
|
|
199
|
+
timestamp: now(),
|
|
200
|
+
input
|
|
201
|
+
};
|
|
202
|
+
unresolvedToolCalls.add(part.callID);
|
|
203
|
+
}
|
|
204
|
+
function* handleToolPart(part) {
|
|
205
|
+
yield* closeText();
|
|
206
|
+
yield* closeReasoning();
|
|
207
|
+
yield* openToolCall(part);
|
|
208
|
+
const state = part.state;
|
|
209
|
+
if (state.status !== "completed" && state.status !== "error") return;
|
|
210
|
+
if (resolvedToolCalls.has(part.callID)) return;
|
|
211
|
+
resolvedToolCalls.add(part.callID);
|
|
212
|
+
unresolvedToolCalls.delete(part.callID);
|
|
213
|
+
const isError = state.status === "error";
|
|
214
|
+
yield {
|
|
215
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
216
|
+
toolCallId: part.callID,
|
|
217
|
+
messageId: genId(),
|
|
218
|
+
model,
|
|
219
|
+
timestamp: now(),
|
|
220
|
+
content: isError ? state.error : state.output,
|
|
221
|
+
...isError && { state: "output-error" }
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function* handleEvent(event) {
|
|
225
|
+
if (event.type === "message.part.updated") {
|
|
226
|
+
const { part, delta } = event.properties;
|
|
227
|
+
if (isTextPart(part)) {
|
|
228
|
+
yield* handleTextPart(part, delta);
|
|
229
|
+
} else if (isReasoningPart(part)) {
|
|
230
|
+
yield* handleReasoningPart(part, delta);
|
|
231
|
+
} else if (isToolPart(part)) {
|
|
232
|
+
yield* handleToolPart(part);
|
|
233
|
+
}
|
|
234
|
+
} else if (event.type === "todo.updated") {
|
|
235
|
+
yield {
|
|
236
|
+
type: EventType.CUSTOM,
|
|
237
|
+
model,
|
|
238
|
+
timestamp: now(),
|
|
239
|
+
name: TODO_EVENT,
|
|
240
|
+
value: { todos: event.properties.todos }
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function* finish(message) {
|
|
245
|
+
yield* startRun();
|
|
246
|
+
yield* closeText();
|
|
247
|
+
yield* closeReasoning();
|
|
248
|
+
yield* synthesizeUnresolvedResults();
|
|
249
|
+
const error = messageError(message);
|
|
250
|
+
if (error) {
|
|
251
|
+
yield {
|
|
252
|
+
type: EventType.RUN_ERROR,
|
|
253
|
+
model,
|
|
254
|
+
timestamp: now(),
|
|
255
|
+
message: error.message,
|
|
256
|
+
error
|
|
257
|
+
};
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const usage = buildUsage(message.tokens);
|
|
261
|
+
const finishReason = message.finish === "length" ? "length" : "stop";
|
|
262
|
+
yield {
|
|
263
|
+
type: EventType.RUN_FINISHED,
|
|
264
|
+
runId,
|
|
265
|
+
threadId,
|
|
266
|
+
model,
|
|
267
|
+
timestamp: now(),
|
|
268
|
+
finishReason,
|
|
269
|
+
...usage !== void 0 && { usage }
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
for await (const streamEvent of events) {
|
|
274
|
+
ctx.onStreamEvent?.(streamEvent);
|
|
275
|
+
if (streamEvent.kind === "session") {
|
|
276
|
+
yield* startRun();
|
|
277
|
+
yield {
|
|
278
|
+
type: EventType.CUSTOM,
|
|
279
|
+
model,
|
|
280
|
+
timestamp: now(),
|
|
281
|
+
name: SESSION_ID_EVENT,
|
|
282
|
+
value: { sessionId: streamEvent.sessionId }
|
|
283
|
+
};
|
|
284
|
+
} else if (streamEvent.kind === "event") {
|
|
285
|
+
yield* startRun();
|
|
286
|
+
yield* handleEvent(streamEvent.event);
|
|
287
|
+
} else {
|
|
288
|
+
yield* finish(streamEvent.message);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
yield* closeText();
|
|
293
|
+
yield* closeReasoning();
|
|
294
|
+
yield* synthesizeUnresolvedResults();
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export {
|
|
299
|
+
BRIDGED_MCP_SERVER_NAME,
|
|
300
|
+
SESSION_ID_EVENT,
|
|
301
|
+
TODO_EVENT,
|
|
302
|
+
resolveToolName,
|
|
303
|
+
translateOpencodeStream
|
|
304
|
+
};
|
|
305
|
+
//# sourceMappingURL=translate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.js","sources":["../../../src/stream/translate.ts"],"sourcesContent":["import { EventType, buildBaseUsage } from '@tanstack/ai'\nimport type { StreamChunk, TokenUsage } from '@tanstack/ai'\nimport type {\n OpencodeAssistantMessage,\n OpencodeEvent,\n OpencodePart,\n OpencodeStreamEvent,\n OpencodeTokens,\n} from './sdk-types'\n\n/** Name of the CUSTOM event carrying the OpenCode session id. */\nexport const SESSION_ID_EVENT = 'opencode.session-id'\n\n/** Name of the CUSTOM event carrying the harness's todo list updates. */\nexport const TODO_EVENT = 'opencode.todo'\n\n/** Server name used for bridged TanStack tools. */\nexport const BRIDGED_MCP_SERVER_NAME = 'tanstack'\n\nexport interface TranslateContext {\n model: string\n runId: string\n threadId: string\n parentRunId?: string\n genId: () => string\n /**\n * Names of bridged TanStack tools, used to surface the harness's MCP tool\n * calls under the names the application registered.\n */\n bridgedToolNames?: ReadonlySet<string>\n /** Called for each raw stream event, for logging. */\n onStreamEvent?: (event: OpencodeStreamEvent) => void\n}\n\n/**\n * Resolve the AG-UI tool-call name for an OpenCode tool part. OpenCode names\n * MCP tools `<server>_<tool>`, so bridged TanStack tools arrive as\n * `tanstack_<tool>` and are surfaced under the names the application\n * registered; everything else (built-in `read`, `edit`, `bash`, ... and\n * foreign MCP tools) uses the harness tool name verbatim.\n */\nexport function resolveToolName(\n tool: string,\n bridgedToolNames: ReadonlySet<string> | undefined,\n): string {\n if (!bridgedToolNames || bridgedToolNames.size === 0) return tool\n if (bridgedToolNames.has(tool)) return tool\n if (tool.startsWith('tanstack_') && bridgedToolNames.has(tool.slice(9))) {\n return tool.slice(9)\n }\n return tool\n}\n\nfunction buildUsage(\n tokens: OpencodeTokens | undefined,\n): TokenUsage | undefined {\n if (!tokens) return undefined\n const promptTokens = tokens.input ?? 0\n const completionTokens = tokens.output ?? 0\n const result = buildBaseUsage({\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n })\n if (tokens.cache?.read) {\n result.promptTokensDetails = { cachedTokens: tokens.cache.read }\n }\n if (tokens.reasoning) {\n result.completionTokensDetails = { reasoningTokens: tokens.reasoning }\n }\n return result\n}\n\ntype TextPart = Extract<OpencodePart, { type: 'text' }>\ntype ReasoningPart = Extract<OpencodePart, { type: 'reasoning' }>\ntype ToolPart = Extract<OpencodePart, { type: 'tool' }>\n\nconst isTextPart = (part: OpencodePart): part is TextPart =>\n part.type === 'text'\nconst isReasoningPart = (part: OpencodePart): part is ReasoningPart =>\n part.type === 'reasoning'\nconst isToolPart = (part: OpencodePart): part is ToolPart =>\n part.type === 'tool'\n\nfunction messageError(\n message: OpencodeAssistantMessage,\n): { message: string } | undefined {\n if (!message.error) return undefined\n return { message: message.error.data?.message ?? message.error.name }\n}\n\n/**\n * Translate an OpenCode event stream into AG-UI StreamChunk events.\n *\n * The harness runs its own agent loop and executes its own tools, so the\n * translation always ends with `finishReason: 'stop'` (or `'length'` /\n * RUN_ERROR) — never `'tool_calls'`. Harness tool activity is emitted as\n * already-resolved TOOL_CALL_START/ARGS/END + TOOL_CALL_RESULT sequences so\n * UIs can render it, while the TanStack engine never tries to execute them.\n *\n * OpenCode delivers true token-level deltas for both assistant text and\n * reasoning via `message.part.updated` events (a `delta` string when\n * incremental, otherwise the full part text, from which the delta is\n * derived). The final assistant message — finish reason, token usage, and any\n * fatal error — arrives as the terminal `done` event.\n *\n * Invariant: every TOOL_CALL_START is eventually paired with a\n * TOOL_CALL_RESULT (synthesized as `{\"status\":\"interrupted\"}` when the run\n * ends or aborts before the harness reported one) so the engine's\n * pending-tool-call scan on the next request never force-executes them.\n */\nexport async function* translateOpencodeStream(\n events: AsyncIterable<OpencodeStreamEvent>,\n ctx: TranslateContext,\n): AsyncIterable<StreamChunk> {\n const { model, runId, threadId, genId } = ctx\n const now = () => Date.now()\n\n let runStarted = false\n /** Tool calls started but with no result yet, keyed by callID. */\n const unresolvedToolCalls = new Set<string>()\n /** Tool call ids that already emitted TOOL_CALL_START/ARGS/END. */\n const openedToolCalls = new Set<string>()\n /** Tool call ids that already emitted a TOOL_CALL_RESULT. */\n const resolvedToolCalls = new Set<string>()\n\n /** Accumulated text per text-part id, for delta derivation. */\n const textAccumulators = new Map<string, string>()\n let openTextId: string | null = null\n let openReasoningId: string | null = null\n\n function* startRun(): Generator<StreamChunk> {\n if (runStarted) return\n runStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp: now(),\n ...(ctx.parentRunId !== undefined && { parentRunId: ctx.parentRunId }),\n }\n }\n\n function* closeText(): Generator<StreamChunk> {\n if (openTextId !== null) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: openTextId,\n model,\n timestamp: now(),\n }\n openTextId = null\n }\n }\n\n function* closeReasoning(): Generator<StreamChunk> {\n if (openReasoningId !== null) {\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: openReasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: openReasoningId,\n model,\n timestamp: now(),\n }\n openReasoningId = null\n }\n }\n\n function* synthesizeUnresolvedResults(): Generator<StreamChunk> {\n for (const toolCallId of unresolvedToolCalls) {\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId,\n messageId: genId(),\n model,\n timestamp: now(),\n content: JSON.stringify({ status: 'interrupted' }),\n }\n }\n unresolvedToolCalls.clear()\n }\n\n function* handleTextPart(\n part: Extract<OpencodePart, { type: 'text' }>,\n delta: string | undefined,\n ): Generator<StreamChunk> {\n yield* closeReasoning()\n\n const prev = textAccumulators.get(part.id) ?? ''\n let deltaText: string\n if (typeof delta === 'string' && delta !== '') {\n deltaText = delta\n textAccumulators.set(part.id, prev + delta)\n } else {\n const full = part.text\n deltaText = full.startsWith(prev) ? full.slice(prev.length) : full\n textAccumulators.set(part.id, full)\n }\n if (deltaText === '') return\n\n if (openTextId !== part.id) {\n yield* closeText()\n openTextId = part.id\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: part.id,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n }\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: part.id,\n model,\n timestamp: now(),\n delta: deltaText,\n content: textAccumulators.get(part.id) ?? deltaText,\n }\n }\n\n function* handleReasoningPart(\n part: Extract<OpencodePart, { type: 'reasoning' }>,\n delta: string | undefined,\n ): Generator<StreamChunk> {\n yield* closeText()\n\n const prev = textAccumulators.get(part.id) ?? ''\n let deltaText: string\n if (typeof delta === 'string' && delta !== '') {\n deltaText = delta\n textAccumulators.set(part.id, prev + delta)\n } else {\n const full = part.text\n deltaText = full.startsWith(prev) ? full.slice(prev.length) : full\n textAccumulators.set(part.id, full)\n }\n if (deltaText === '') return\n\n if (openReasoningId !== part.id) {\n yield* closeReasoning()\n openReasoningId = part.id\n yield {\n type: EventType.REASONING_START,\n messageId: part.id,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: part.id,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: part.id,\n delta: deltaText,\n model,\n timestamp: now(),\n }\n }\n\n function* openToolCall(\n part: Extract<OpencodePart, { type: 'tool' }>,\n ): Generator<StreamChunk> {\n if (openedToolCalls.has(part.callID)) return\n openedToolCalls.add(part.callID)\n const toolCallName = resolveToolName(part.tool, ctx.bridgedToolNames)\n const input = part.state.input ?? {}\n const args = JSON.stringify(input)\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: part.callID,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: part.callID,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: part.callID,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n input,\n }\n unresolvedToolCalls.add(part.callID)\n }\n\n function* handleToolPart(\n part: Extract<OpencodePart, { type: 'tool' }>,\n ): Generator<StreamChunk> {\n yield* closeText()\n yield* closeReasoning()\n yield* openToolCall(part)\n\n const state = part.state\n if (state.status !== 'completed' && state.status !== 'error') return\n if (resolvedToolCalls.has(part.callID)) return\n resolvedToolCalls.add(part.callID)\n unresolvedToolCalls.delete(part.callID)\n\n const isError = state.status === 'error'\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: part.callID,\n messageId: genId(),\n model,\n timestamp: now(),\n content: isError ? state.error : state.output,\n ...(isError && { state: 'output-error' as const }),\n }\n }\n\n function* handleEvent(event: OpencodeEvent): Generator<StreamChunk> {\n if (event.type === 'message.part.updated') {\n const { part, delta } = event.properties\n if (isTextPart(part)) {\n yield* handleTextPart(part, delta)\n } else if (isReasoningPart(part)) {\n yield* handleReasoningPart(part, delta)\n } else if (isToolPart(part)) {\n yield* handleToolPart(part)\n }\n // Other part kinds (file, step-start/finish, snapshot, ...) carry no\n // state the chunk stream needs.\n } else if (event.type === 'todo.updated') {\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: TODO_EVENT,\n value: { todos: event.properties.todos },\n }\n }\n // session.idle / session.status / message.updated are redundant with the\n // terminal `done` event and are ignored.\n }\n\n function* finish(message: OpencodeAssistantMessage): Generator<StreamChunk> {\n yield* startRun()\n yield* closeText()\n yield* closeReasoning()\n yield* synthesizeUnresolvedResults()\n\n const error = messageError(message)\n if (error) {\n yield {\n type: EventType.RUN_ERROR,\n model,\n timestamp: now(),\n message: error.message,\n error,\n }\n return\n }\n\n const usage = buildUsage(message.tokens)\n const finishReason = message.finish === 'length' ? 'length' : 'stop'\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason,\n ...(usage !== undefined && { usage }),\n }\n }\n\n try {\n for await (const streamEvent of events) {\n ctx.onStreamEvent?.(streamEvent)\n\n if (streamEvent.kind === 'session') {\n yield* startRun()\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: SESSION_ID_EVENT,\n value: { sessionId: streamEvent.sessionId },\n }\n } else if (streamEvent.kind === 'event') {\n yield* startRun()\n yield* handleEvent(streamEvent.event)\n } else {\n yield* finish(streamEvent.message)\n }\n }\n } catch (error) {\n // The run is dying (abort, server exit, or connection failure). Close any\n // open message and pair started tool calls with a synthetic result first\n // so the next request's pending-tool-call scan doesn't try to execute\n // them, then let the adapter surface the error as RUN_ERROR.\n yield* closeText()\n yield* closeReasoning()\n yield* synthesizeUnresolvedResults()\n throw error\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/ai-opencode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode harness adapter for TanStack AI — run OpenCode as a chat backend with local tool execution and stateful sessions.",
|
|
5
|
+
"author": "",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/ai.git",
|
|
10
|
+
"directory": "packages/ai-opencode"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"ai-sdk",
|
|
15
|
+
"typescript",
|
|
16
|
+
"tanstack",
|
|
17
|
+
"opencode",
|
|
18
|
+
"harness",
|
|
19
|
+
"agent",
|
|
20
|
+
"adapter",
|
|
21
|
+
"chat",
|
|
22
|
+
"tool-calling"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"module": "./dist/esm/index.js",
|
|
26
|
+
"types": "./dist/esm/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/esm/index.d.ts",
|
|
30
|
+
"import": "./dist/esm/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "vite build",
|
|
39
|
+
"clean": "premove ./build ./dist",
|
|
40
|
+
"lint:fix": "eslint ./src --fix",
|
|
41
|
+
"test:build": "publint --strict",
|
|
42
|
+
"test:eslint": "eslint ./src",
|
|
43
|
+
"test:lib": "vitest",
|
|
44
|
+
"test:lib:dev": "pnpm test:lib --watch",
|
|
45
|
+
"test:types": "tsc"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@opencode-ai/sdk": "^1.17.4"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@tanstack/ai": "workspace:^",
|
|
52
|
+
"@tanstack/ai-sandbox": "workspace:^"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@tanstack/ai": "workspace:*",
|
|
56
|
+
"@tanstack/ai-sandbox": "workspace:*",
|
|
57
|
+
"@vitest/coverage-v8": "4.0.14"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|