@tanstack/ai-claude-code 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/policy-map.d.ts +8 -0
- package/dist/esm/adapters/policy-map.js +51 -0
- package/dist/esm/adapters/policy-map.js.map +1 -0
- package/dist/esm/adapters/projection.d.ts +14 -0
- package/dist/esm/adapters/projection.js +101 -0
- package/dist/esm/adapters/projection.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +75 -0
- package/dist/esm/adapters/text.js +306 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/messages/prompt.d.ts +17 -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 +10 -0
- package/dist/esm/model-meta.js +14 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/provider-options.d.ts +30 -0
- package/dist/esm/stream/sdk-types.d.ts +124 -0
- package/dist/esm/stream/translate.d.ts +44 -0
- package/dist/esm/stream/translate.js +368 -0
- package/dist/esm/stream/translate.js.map +1 -0
- package/package.json +62 -0
- package/src/adapters/policy-map.ts +83 -0
- package/src/adapters/projection.ts +226 -0
- package/src/adapters/text.ts +539 -0
- package/src/index.ts +21 -0
- package/src/messages/prompt.ts +68 -0
- package/src/model-meta.ts +21 -0
- package/src/provider-options.ts +30 -0
- package/src/stream/sdk-types.ts +135 -0
- package/src/stream/translate.ts +483 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { EventType, buildBaseUsage } from "@tanstack/ai";
|
|
2
|
+
const SESSION_ID_EVENT = "claude-code.session-id";
|
|
3
|
+
const BRIDGED_MCP_SERVER_NAME = "tanstack";
|
|
4
|
+
const BRIDGED_MCP_PREFIX = `mcp__${BRIDGED_MCP_SERVER_NAME}__`;
|
|
5
|
+
function stripMcpPrefix(name) {
|
|
6
|
+
return name.startsWith(BRIDGED_MCP_PREFIX) ? name.slice(BRIDGED_MCP_PREFIX.length) : name;
|
|
7
|
+
}
|
|
8
|
+
function stringifyToolResultContent(content) {
|
|
9
|
+
if (content === void 0) return "";
|
|
10
|
+
if (typeof content === "string") return content;
|
|
11
|
+
return content.map((block) => typeof block.text === "string" ? block.text : "").join("");
|
|
12
|
+
}
|
|
13
|
+
function buildUsage(usage, totalCostUsd) {
|
|
14
|
+
if (!usage) return void 0;
|
|
15
|
+
const promptTokens = usage.input_tokens ?? 0;
|
|
16
|
+
const completionTokens = usage.output_tokens ?? 0;
|
|
17
|
+
const result = buildBaseUsage({
|
|
18
|
+
promptTokens,
|
|
19
|
+
completionTokens,
|
|
20
|
+
totalTokens: promptTokens + completionTokens
|
|
21
|
+
});
|
|
22
|
+
const cacheWrite = usage.cache_creation_input_tokens;
|
|
23
|
+
const cacheRead = usage.cache_read_input_tokens;
|
|
24
|
+
const promptTokensDetails = {
|
|
25
|
+
...cacheWrite ? { cacheWriteTokens: cacheWrite } : {},
|
|
26
|
+
...cacheRead ? { cachedTokens: cacheRead } : {}
|
|
27
|
+
};
|
|
28
|
+
if (Object.keys(promptTokensDetails).length > 0) {
|
|
29
|
+
result.promptTokensDetails = promptTokensDetails;
|
|
30
|
+
}
|
|
31
|
+
if (totalCostUsd !== void 0) {
|
|
32
|
+
result.providerUsageDetails = { totalCostUsd };
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
async function* translateSdkStream(sdkMessages, ctx) {
|
|
37
|
+
const { model, runId, threadId, genId } = ctx;
|
|
38
|
+
const now = () => Date.now();
|
|
39
|
+
let runStarted = false;
|
|
40
|
+
const unresolvedToolCalls = /* @__PURE__ */ new Set();
|
|
41
|
+
const streamedMessageIds = /* @__PURE__ */ new Set();
|
|
42
|
+
let partialMessageId = null;
|
|
43
|
+
let partialBlockType = null;
|
|
44
|
+
let partialTextMessageId = null;
|
|
45
|
+
let partialTextContent = "";
|
|
46
|
+
let partialTextStarted = false;
|
|
47
|
+
let partialReasoningId = null;
|
|
48
|
+
function* startRun() {
|
|
49
|
+
if (runStarted) return;
|
|
50
|
+
runStarted = true;
|
|
51
|
+
yield {
|
|
52
|
+
type: EventType.RUN_STARTED,
|
|
53
|
+
runId,
|
|
54
|
+
threadId,
|
|
55
|
+
model,
|
|
56
|
+
timestamp: now(),
|
|
57
|
+
...ctx.parentRunId !== void 0 && { parentRunId: ctx.parentRunId }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function* synthesizeUnresolvedResults() {
|
|
61
|
+
for (const toolCallId of unresolvedToolCalls) {
|
|
62
|
+
yield {
|
|
63
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
64
|
+
toolCallId,
|
|
65
|
+
messageId: genId(),
|
|
66
|
+
model,
|
|
67
|
+
timestamp: now(),
|
|
68
|
+
content: JSON.stringify({ status: "interrupted" })
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
unresolvedToolCalls.clear();
|
|
72
|
+
}
|
|
73
|
+
function* closePartialText() {
|
|
74
|
+
if (partialTextStarted && partialTextMessageId) {
|
|
75
|
+
yield {
|
|
76
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
77
|
+
messageId: partialTextMessageId,
|
|
78
|
+
model,
|
|
79
|
+
timestamp: now()
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
partialTextStarted = false;
|
|
83
|
+
partialTextMessageId = null;
|
|
84
|
+
partialTextContent = "";
|
|
85
|
+
}
|
|
86
|
+
function* closePartialReasoning() {
|
|
87
|
+
if (partialReasoningId) {
|
|
88
|
+
yield {
|
|
89
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
90
|
+
messageId: partialReasoningId,
|
|
91
|
+
model,
|
|
92
|
+
timestamp: now()
|
|
93
|
+
};
|
|
94
|
+
yield {
|
|
95
|
+
type: EventType.REASONING_END,
|
|
96
|
+
messageId: partialReasoningId,
|
|
97
|
+
model,
|
|
98
|
+
timestamp: now()
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
partialReasoningId = null;
|
|
102
|
+
}
|
|
103
|
+
function* emitToolUse(block) {
|
|
104
|
+
const toolCallName = stripMcpPrefix(block.name);
|
|
105
|
+
const args = JSON.stringify(block.input ?? {});
|
|
106
|
+
yield {
|
|
107
|
+
type: EventType.TOOL_CALL_START,
|
|
108
|
+
toolCallId: block.id,
|
|
109
|
+
toolCallName,
|
|
110
|
+
toolName: toolCallName,
|
|
111
|
+
model,
|
|
112
|
+
timestamp: now()
|
|
113
|
+
};
|
|
114
|
+
yield {
|
|
115
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
116
|
+
toolCallId: block.id,
|
|
117
|
+
model,
|
|
118
|
+
timestamp: now(),
|
|
119
|
+
delta: args,
|
|
120
|
+
args
|
|
121
|
+
};
|
|
122
|
+
yield {
|
|
123
|
+
type: EventType.TOOL_CALL_END,
|
|
124
|
+
toolCallId: block.id,
|
|
125
|
+
toolCallName,
|
|
126
|
+
toolName: toolCallName,
|
|
127
|
+
model,
|
|
128
|
+
timestamp: now(),
|
|
129
|
+
input: block.input ?? {}
|
|
130
|
+
};
|
|
131
|
+
unresolvedToolCalls.add(block.id);
|
|
132
|
+
}
|
|
133
|
+
function* handleAssistant(message) {
|
|
134
|
+
const alreadyStreamed = message.message.id !== void 0 && streamedMessageIds.has(message.message.id);
|
|
135
|
+
for (const block of message.message.content) {
|
|
136
|
+
if (block.type === "text") {
|
|
137
|
+
if (alreadyStreamed) continue;
|
|
138
|
+
const messageId = message.message.id ?? genId();
|
|
139
|
+
const text = block.text;
|
|
140
|
+
yield {
|
|
141
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
142
|
+
messageId,
|
|
143
|
+
model,
|
|
144
|
+
timestamp: now(),
|
|
145
|
+
role: "assistant"
|
|
146
|
+
};
|
|
147
|
+
yield {
|
|
148
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
149
|
+
messageId,
|
|
150
|
+
model,
|
|
151
|
+
timestamp: now(),
|
|
152
|
+
delta: text,
|
|
153
|
+
content: text
|
|
154
|
+
};
|
|
155
|
+
yield {
|
|
156
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
157
|
+
messageId,
|
|
158
|
+
model,
|
|
159
|
+
timestamp: now()
|
|
160
|
+
};
|
|
161
|
+
} else if (block.type === "thinking") {
|
|
162
|
+
if (alreadyStreamed) continue;
|
|
163
|
+
const reasoningId = genId();
|
|
164
|
+
const thinking = block.thinking;
|
|
165
|
+
yield {
|
|
166
|
+
type: EventType.REASONING_START,
|
|
167
|
+
messageId: reasoningId,
|
|
168
|
+
model,
|
|
169
|
+
timestamp: now()
|
|
170
|
+
};
|
|
171
|
+
yield {
|
|
172
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
173
|
+
messageId: reasoningId,
|
|
174
|
+
role: "reasoning",
|
|
175
|
+
model,
|
|
176
|
+
timestamp: now()
|
|
177
|
+
};
|
|
178
|
+
yield {
|
|
179
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
180
|
+
messageId: reasoningId,
|
|
181
|
+
delta: thinking,
|
|
182
|
+
model,
|
|
183
|
+
timestamp: now()
|
|
184
|
+
};
|
|
185
|
+
yield {
|
|
186
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
187
|
+
messageId: reasoningId,
|
|
188
|
+
model,
|
|
189
|
+
timestamp: now()
|
|
190
|
+
};
|
|
191
|
+
yield {
|
|
192
|
+
type: EventType.REASONING_END,
|
|
193
|
+
messageId: reasoningId,
|
|
194
|
+
model,
|
|
195
|
+
timestamp: now()
|
|
196
|
+
};
|
|
197
|
+
} else if (block.type === "tool_use") {
|
|
198
|
+
yield* emitToolUse(
|
|
199
|
+
block
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function* handleUser(message) {
|
|
205
|
+
const content = message.message.content;
|
|
206
|
+
if (typeof content === "string") return;
|
|
207
|
+
for (const block of content) {
|
|
208
|
+
if (block.type !== "tool_result") continue;
|
|
209
|
+
const toolResult = block;
|
|
210
|
+
unresolvedToolCalls.delete(toolResult.tool_use_id);
|
|
211
|
+
yield {
|
|
212
|
+
type: EventType.TOOL_CALL_RESULT,
|
|
213
|
+
toolCallId: toolResult.tool_use_id,
|
|
214
|
+
messageId: genId(),
|
|
215
|
+
model,
|
|
216
|
+
timestamp: now(),
|
|
217
|
+
content: stringifyToolResultContent(toolResult.content),
|
|
218
|
+
...toolResult.is_error === true && { state: "output-error" }
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function* handleResult(message) {
|
|
223
|
+
yield* closePartialText();
|
|
224
|
+
yield* closePartialReasoning();
|
|
225
|
+
yield* synthesizeUnresolvedResults();
|
|
226
|
+
const usage = buildUsage(message.usage, message.total_cost_usd);
|
|
227
|
+
if (message.subtype === "success") {
|
|
228
|
+
yield {
|
|
229
|
+
type: EventType.RUN_FINISHED,
|
|
230
|
+
runId,
|
|
231
|
+
threadId,
|
|
232
|
+
model,
|
|
233
|
+
timestamp: now(),
|
|
234
|
+
finishReason: "stop",
|
|
235
|
+
...usage !== void 0 && { usage }
|
|
236
|
+
};
|
|
237
|
+
} else if (message.subtype === "error_max_turns") {
|
|
238
|
+
yield {
|
|
239
|
+
type: EventType.RUN_FINISHED,
|
|
240
|
+
runId,
|
|
241
|
+
threadId,
|
|
242
|
+
model,
|
|
243
|
+
timestamp: now(),
|
|
244
|
+
finishReason: "length",
|
|
245
|
+
...usage !== void 0 && { usage }
|
|
246
|
+
};
|
|
247
|
+
} else {
|
|
248
|
+
const errorMessage = message.errors && message.errors.length > 0 ? message.errors.join("; ") : `Claude Code run failed: ${message.subtype}`;
|
|
249
|
+
yield {
|
|
250
|
+
type: EventType.RUN_ERROR,
|
|
251
|
+
model,
|
|
252
|
+
timestamp: now(),
|
|
253
|
+
message: errorMessage,
|
|
254
|
+
code: message.subtype,
|
|
255
|
+
error: { message: errorMessage, code: message.subtype }
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function* handleStreamEvent(message) {
|
|
260
|
+
const event = message.event;
|
|
261
|
+
if (event.type === "message_start") {
|
|
262
|
+
partialMessageId = event.message.id ?? genId();
|
|
263
|
+
streamedMessageIds.add(partialMessageId);
|
|
264
|
+
} else if (event.type === "content_block_start") {
|
|
265
|
+
partialBlockType = event.content_block.type;
|
|
266
|
+
if (partialBlockType === "text") {
|
|
267
|
+
partialTextMessageId = partialMessageId ?? genId();
|
|
268
|
+
partialTextContent = "";
|
|
269
|
+
if (!partialTextStarted) {
|
|
270
|
+
partialTextStarted = true;
|
|
271
|
+
yield {
|
|
272
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
273
|
+
messageId: partialTextMessageId,
|
|
274
|
+
model,
|
|
275
|
+
timestamp: now(),
|
|
276
|
+
role: "assistant"
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
} else if (partialBlockType === "thinking") {
|
|
280
|
+
partialReasoningId = genId();
|
|
281
|
+
yield {
|
|
282
|
+
type: EventType.REASONING_START,
|
|
283
|
+
messageId: partialReasoningId,
|
|
284
|
+
model,
|
|
285
|
+
timestamp: now()
|
|
286
|
+
};
|
|
287
|
+
yield {
|
|
288
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
289
|
+
messageId: partialReasoningId,
|
|
290
|
+
role: "reasoning",
|
|
291
|
+
model,
|
|
292
|
+
timestamp: now()
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
} else if (event.type === "content_block_delta") {
|
|
296
|
+
if (event.delta.type === "text_delta" && partialTextStarted && partialTextMessageId && typeof event.delta.text === "string") {
|
|
297
|
+
partialTextContent += event.delta.text;
|
|
298
|
+
yield {
|
|
299
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
300
|
+
messageId: partialTextMessageId,
|
|
301
|
+
model,
|
|
302
|
+
timestamp: now(),
|
|
303
|
+
delta: event.delta.text,
|
|
304
|
+
content: partialTextContent
|
|
305
|
+
};
|
|
306
|
+
} else if (event.delta.type === "thinking_delta" && partialReasoningId && typeof event.delta.thinking === "string") {
|
|
307
|
+
yield {
|
|
308
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
309
|
+
messageId: partialReasoningId,
|
|
310
|
+
delta: event.delta.thinking,
|
|
311
|
+
model,
|
|
312
|
+
timestamp: now()
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
} else if (event.type === "content_block_stop") {
|
|
316
|
+
if (partialBlockType === "text") {
|
|
317
|
+
yield* closePartialText();
|
|
318
|
+
} else if (partialBlockType === "thinking") {
|
|
319
|
+
yield* closePartialReasoning();
|
|
320
|
+
}
|
|
321
|
+
partialBlockType = null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
for await (const sdkMessage of sdkMessages) {
|
|
326
|
+
ctx.onSdkMessage?.(sdkMessage);
|
|
327
|
+
if (sdkMessage.type === "system" && sdkMessage.subtype === "init") {
|
|
328
|
+
yield* startRun();
|
|
329
|
+
ctx.onSessionId?.(sdkMessage.session_id);
|
|
330
|
+
yield {
|
|
331
|
+
type: EventType.CUSTOM,
|
|
332
|
+
model,
|
|
333
|
+
timestamp: now(),
|
|
334
|
+
name: SESSION_ID_EVENT,
|
|
335
|
+
value: {
|
|
336
|
+
sessionId: sdkMessage.session_id,
|
|
337
|
+
model: sdkMessage.model,
|
|
338
|
+
tools: sdkMessage.tools
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
yield* startRun();
|
|
344
|
+
if (sdkMessage.type === "stream_event") {
|
|
345
|
+
if (sdkMessage.parent_tool_use_id !== null) continue;
|
|
346
|
+
yield* handleStreamEvent(sdkMessage);
|
|
347
|
+
} else if (sdkMessage.type === "assistant") {
|
|
348
|
+
if (sdkMessage.parent_tool_use_id !== null) continue;
|
|
349
|
+
yield* handleAssistant(sdkMessage);
|
|
350
|
+
} else if (sdkMessage.type === "user") {
|
|
351
|
+
if (sdkMessage.parent_tool_use_id !== null) continue;
|
|
352
|
+
yield* handleUser(sdkMessage);
|
|
353
|
+
} else if (sdkMessage.type === "result") {
|
|
354
|
+
yield* handleResult(sdkMessage);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} catch (error) {
|
|
358
|
+
yield* synthesizeUnresolvedResults();
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
export {
|
|
363
|
+
BRIDGED_MCP_SERVER_NAME,
|
|
364
|
+
SESSION_ID_EVENT,
|
|
365
|
+
stripMcpPrefix,
|
|
366
|
+
translateSdkStream
|
|
367
|
+
};
|
|
368
|
+
//# 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 AgentSdkMessage,\n SdkAssistantMessage,\n SdkPartialAssistantMessage,\n SdkResultMessage,\n SdkToolResultContent,\n SdkUsage,\n SdkUserMessage,\n} from './sdk-types'\n\n/** Name of the CUSTOM event carrying the Claude Code session id. */\nexport const SESSION_ID_EVENT = 'claude-code.session-id'\n\n/** Server name used for bridged TanStack tools (model sees `mcp__tanstack__<name>`). */\nexport const BRIDGED_MCP_SERVER_NAME = 'tanstack'\n\nconst BRIDGED_MCP_PREFIX = `mcp__${BRIDGED_MCP_SERVER_NAME}__`\n\n/** Claude Code-specific usage details attached to RUN_FINISHED usage. */\nexport type ClaudeCodeProviderUsageDetails = {\n /** Total cost of the harness run in USD, as reported by Claude Code. */\n totalCostUsd?: number\n}\n\nexport interface TranslateContext {\n model: string\n runId: string\n threadId: string\n parentRunId?: string\n genId: () => string\n /** Called as soon as the harness reports its session id. */\n onSessionId?: (sessionId: string) => void\n /** Called for each raw SDK message, for logging. */\n onSdkMessage?: (message: AgentSdkMessage) => void\n}\n\n/**\n * Strip the bridged MCP server prefix so tool-call events match the TanStack\n * tool names the application registered. Built-in harness tools (Bash, Read,\n * Edit, ...) and foreign MCP tools pass through verbatim.\n */\nexport function stripMcpPrefix(name: string): string {\n return name.startsWith(BRIDGED_MCP_PREFIX)\n ? name.slice(BRIDGED_MCP_PREFIX.length)\n : name\n}\n\nfunction stringifyToolResultContent(\n content: SdkToolResultContent | undefined,\n): string {\n if (content === undefined) return ''\n if (typeof content === 'string') return content\n return content\n .map((block) => (typeof block.text === 'string' ? block.text : ''))\n .join('')\n}\n\nfunction buildUsage(\n usage: SdkUsage | undefined,\n totalCostUsd: number | undefined,\n): TokenUsage<ClaudeCodeProviderUsageDetails> | undefined {\n if (!usage) return undefined\n const promptTokens = usage.input_tokens ?? 0\n const completionTokens = usage.output_tokens ?? 0\n const result = buildBaseUsage<ClaudeCodeProviderUsageDetails>({\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n })\n const cacheWrite = usage.cache_creation_input_tokens\n const cacheRead = usage.cache_read_input_tokens\n const promptTokensDetails = {\n ...(cacheWrite ? { cacheWriteTokens: cacheWrite } : {}),\n ...(cacheRead ? { cachedTokens: cacheRead } : {}),\n }\n if (Object.keys(promptTokensDetails).length > 0) {\n result.promptTokensDetails = promptTokensDetails\n }\n if (totalCostUsd !== undefined) {\n result.providerUsageDetails = { totalCostUsd }\n }\n return result\n}\n\n/**\n * Translate a Claude Code Agent SDK message stream into AG-UI StreamChunk\n * 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 * 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* translateSdkStream(\n sdkMessages: AsyncIterable<AgentSdkMessage>,\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. */\n const unresolvedToolCalls = new Set<string>()\n /** Anthropic message ids whose text/thinking already streamed via partials. */\n const streamedMessageIds = new Set<string>()\n\n // Partial-stream state\n let partialMessageId: string | null = null\n let partialBlockType: string | null = null\n let partialTextMessageId: string | null = null\n let partialTextContent = ''\n let partialTextStarted = false\n let partialReasoningId: 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* 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* closePartialText(): Generator<StreamChunk> {\n if (partialTextStarted && partialTextMessageId) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n }\n }\n partialTextStarted = false\n partialTextMessageId = null\n partialTextContent = ''\n }\n\n function* closePartialReasoning(): Generator<StreamChunk> {\n if (partialReasoningId) {\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n }\n partialReasoningId = null\n }\n\n function* emitToolUse(block: {\n id: string\n name: string\n input: unknown\n }): Generator<StreamChunk> {\n const toolCallName = stripMcpPrefix(block.name)\n const args = JSON.stringify(block.input ?? {})\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: block.id,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: block.id,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: block.id,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n input: block.input ?? {},\n }\n unresolvedToolCalls.add(block.id)\n }\n\n function* handleAssistant(\n message: SdkAssistantMessage,\n ): Generator<StreamChunk> {\n const alreadyStreamed =\n message.message.id !== undefined &&\n streamedMessageIds.has(message.message.id)\n\n for (const block of message.message.content) {\n if (block.type === 'text') {\n if (alreadyStreamed) continue\n const messageId = message.message.id ?? genId()\n const text = (block as { text: string }).text\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp: now(),\n delta: text,\n content: text,\n }\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model,\n timestamp: now(),\n }\n } else if (block.type === 'thinking') {\n if (alreadyStreamed) continue\n const reasoningId = genId()\n const thinking = (block as { thinking: string }).thinking\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningId,\n delta: thinking,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n } else if (block.type === 'tool_use') {\n yield* emitToolUse(\n block as { id: string; name: string; input: unknown },\n )\n }\n }\n }\n\n function* handleUser(message: SdkUserMessage): Generator<StreamChunk> {\n const content = message.message.content\n if (typeof content === 'string') return\n for (const block of content) {\n if (block.type !== 'tool_result') continue\n const toolResult = block as {\n tool_use_id: string\n content?: SdkToolResultContent\n is_error?: boolean\n }\n unresolvedToolCalls.delete(toolResult.tool_use_id)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: toolResult.tool_use_id,\n messageId: genId(),\n model,\n timestamp: now(),\n content: stringifyToolResultContent(toolResult.content),\n ...(toolResult.is_error === true && { state: 'output-error' as const }),\n }\n }\n }\n\n function* handleResult(message: SdkResultMessage): Generator<StreamChunk> {\n yield* closePartialText()\n yield* closePartialReasoning()\n yield* synthesizeUnresolvedResults()\n\n const usage = buildUsage(message.usage, message.total_cost_usd)\n if (message.subtype === 'success') {\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason: 'stop',\n ...(usage !== undefined && { usage }),\n }\n } else if (message.subtype === 'error_max_turns') {\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason: 'length',\n ...(usage !== undefined && { usage }),\n }\n } else {\n const errorMessage =\n message.errors && message.errors.length > 0\n ? message.errors.join('; ')\n : `Claude Code run failed: ${message.subtype}`\n yield {\n type: EventType.RUN_ERROR,\n model,\n timestamp: now(),\n message: errorMessage,\n code: message.subtype,\n error: { message: errorMessage, code: message.subtype },\n }\n }\n }\n\n function* handleStreamEvent(\n message: SdkPartialAssistantMessage,\n ): Generator<StreamChunk> {\n const event = message.event\n if (event.type === 'message_start') {\n partialMessageId = event.message.id ?? genId()\n streamedMessageIds.add(partialMessageId)\n } else if (event.type === 'content_block_start') {\n partialBlockType = event.content_block.type\n if (partialBlockType === 'text') {\n partialTextMessageId = partialMessageId ?? genId()\n partialTextContent = ''\n if (!partialTextStarted) {\n partialTextStarted = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n }\n } else if (partialBlockType === 'thinking') {\n partialReasoningId = genId()\n yield {\n type: EventType.REASONING_START,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: partialReasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n }\n } else if (event.type === 'content_block_delta') {\n if (\n event.delta.type === 'text_delta' &&\n partialTextStarted &&\n partialTextMessageId &&\n typeof event.delta.text === 'string'\n ) {\n partialTextContent += event.delta.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n delta: event.delta.text,\n content: partialTextContent,\n }\n } else if (\n event.delta.type === 'thinking_delta' &&\n partialReasoningId &&\n typeof event.delta.thinking === 'string'\n ) {\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: partialReasoningId,\n delta: event.delta.thinking,\n model,\n timestamp: now(),\n }\n }\n } else if (event.type === 'content_block_stop') {\n if (partialBlockType === 'text') {\n yield* closePartialText()\n } else if (partialBlockType === 'thinking') {\n yield* closePartialReasoning()\n }\n partialBlockType = null\n }\n }\n\n try {\n for await (const sdkMessage of sdkMessages) {\n ctx.onSdkMessage?.(sdkMessage)\n\n if (sdkMessage.type === 'system' && sdkMessage.subtype === 'init') {\n yield* startRun()\n ctx.onSessionId?.(sdkMessage.session_id)\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: SESSION_ID_EVENT,\n value: {\n sessionId: sdkMessage.session_id,\n model: sdkMessage.model,\n tools: sdkMessage.tools,\n },\n }\n continue\n }\n\n // Anything before init still needs RUN_STARTED first.\n yield* startRun()\n\n if (sdkMessage.type === 'stream_event') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleStreamEvent(sdkMessage)\n } else if (sdkMessage.type === 'assistant') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleAssistant(sdkMessage)\n } else if (sdkMessage.type === 'user') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleUser(sdkMessage)\n } else if (sdkMessage.type === 'result') {\n yield* handleResult(sdkMessage)\n }\n // All other SDK message types (status, hooks, notifications, ...) are\n // harness-internal and intentionally ignored.\n }\n } catch (error) {\n // The run is dying (abort or SDK failure). Pair any started tool calls\n // with a synthetic result first so the next request's pending-tool-call\n // scan doesn't try to execute them, then let the adapter surface the\n // error as RUN_ERROR.\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-claude-code",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Claude Code harness adapter for TanStack AI — run Claude Code 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-claude-code"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"ai-sdk",
|
|
15
|
+
"typescript",
|
|
16
|
+
"tanstack",
|
|
17
|
+
"anthropic",
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"harness",
|
|
21
|
+
"agent",
|
|
22
|
+
"adapter",
|
|
23
|
+
"chat",
|
|
24
|
+
"tool-calling"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"module": "./dist/esm/index.js",
|
|
28
|
+
"types": "./dist/esm/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/esm/index.d.ts",
|
|
32
|
+
"import": "./dist/esm/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "vite build",
|
|
41
|
+
"clean": "premove ./build ./dist",
|
|
42
|
+
"lint:fix": "eslint ./src --fix",
|
|
43
|
+
"test:build": "publint --strict",
|
|
44
|
+
"test:eslint": "eslint ./src",
|
|
45
|
+
"test:lib": "vitest",
|
|
46
|
+
"test:lib:dev": "pnpm test:lib --watch",
|
|
47
|
+
"test:types": "tsc"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@tanstack/ai": "workspace:^",
|
|
51
|
+
"@tanstack/ai-sandbox": "workspace:^"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@tanstack/ai": "workspace:*",
|
|
55
|
+
"@tanstack/ai-sandbox": "workspace:*",
|
|
56
|
+
"@tanstack/ai-sandbox-local-process": "workspace:*",
|
|
57
|
+
"@vitest/coverage-v8": "4.0.14"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a portable {@link SandboxPolicy} onto Claude Code CLI permission flags.
|
|
3
|
+
*
|
|
4
|
+
* This is a best-effort, coarse mapping (the CLI's permission model is
|
|
5
|
+
* tool-level + a permission mode, not arbitrary command globs):
|
|
6
|
+
*
|
|
7
|
+
* - `default` decision → `--permission-mode`:
|
|
8
|
+
* `'allow'` → `bypassPermissions`, `'acceptEdits'`-ish `'ask'` → `acceptEdits`,
|
|
9
|
+
* `'deny'` → `default` (in `-p` mode, prompts are auto-denied).
|
|
10
|
+
* - `capabilities.fileWrite === 'deny'` → disallow `Write`,`Edit`,`MultiEdit`.
|
|
11
|
+
* - `capabilities.network === 'deny'` → disallow `WebFetch`,`WebSearch`.
|
|
12
|
+
* - `commands.deny` that name a bare built-in tool (e.g. `Bash`) are added to
|
|
13
|
+
* `--disallowedTools`; fine-grained command-glob enforcement is left to the
|
|
14
|
+
* MCP permission-prompt tool (interactive approvals).
|
|
15
|
+
*
|
|
16
|
+
* Returns the permission mode plus tool allow/deny additions; the adapter
|
|
17
|
+
* merges these with its own config.
|
|
18
|
+
*/
|
|
19
|
+
import type { PolicyDecision, SandboxPolicy } from '@tanstack/ai-sandbox'
|
|
20
|
+
import type { ClaudeCodePermissionMode } from './text'
|
|
21
|
+
|
|
22
|
+
export interface ClaudePolicyFlags {
|
|
23
|
+
permissionMode?: ClaudeCodePermissionMode
|
|
24
|
+
allowedTools: Array<string>
|
|
25
|
+
disallowedTools: Array<string>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const WRITE_TOOLS = ['Write', 'Edit', 'MultiEdit']
|
|
29
|
+
const NETWORK_TOOLS = ['WebFetch', 'WebSearch']
|
|
30
|
+
const BUILTIN_TOOL_NAMES = new Set([
|
|
31
|
+
'Bash',
|
|
32
|
+
'Read',
|
|
33
|
+
'Write',
|
|
34
|
+
'Edit',
|
|
35
|
+
'MultiEdit',
|
|
36
|
+
'Glob',
|
|
37
|
+
'Grep',
|
|
38
|
+
'WebFetch',
|
|
39
|
+
'WebSearch',
|
|
40
|
+
'NotebookEdit',
|
|
41
|
+
'Task',
|
|
42
|
+
])
|
|
43
|
+
|
|
44
|
+
function modeFor(decision: PolicyDecision): ClaudeCodePermissionMode {
|
|
45
|
+
switch (decision) {
|
|
46
|
+
case 'allow':
|
|
47
|
+
return 'bypassPermissions'
|
|
48
|
+
case 'ask':
|
|
49
|
+
return 'acceptEdits'
|
|
50
|
+
case 'deny':
|
|
51
|
+
return 'default'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function mapPolicyToClaudeFlags(
|
|
56
|
+
policy: SandboxPolicy | undefined,
|
|
57
|
+
): ClaudePolicyFlags {
|
|
58
|
+
const allowedTools: Array<string> = []
|
|
59
|
+
const disallowedTools: Array<string> = []
|
|
60
|
+
if (!policy) return { allowedTools, disallowedTools }
|
|
61
|
+
|
|
62
|
+
if (policy.capabilities?.fileWrite === 'deny')
|
|
63
|
+
disallowedTools.push(...WRITE_TOOLS)
|
|
64
|
+
if (policy.capabilities?.network === 'deny')
|
|
65
|
+
disallowedTools.push(...NETWORK_TOOLS)
|
|
66
|
+
|
|
67
|
+
// Tool-name-level command rules map directly; everything else is left to the
|
|
68
|
+
// permission-prompt tool.
|
|
69
|
+
for (const pattern of policy.commands?.deny ?? []) {
|
|
70
|
+
if (BUILTIN_TOOL_NAMES.has(pattern)) disallowedTools.push(pattern)
|
|
71
|
+
}
|
|
72
|
+
for (const pattern of policy.commands?.allow ?? []) {
|
|
73
|
+
if (BUILTIN_TOOL_NAMES.has(pattern)) allowedTools.push(pattern)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result: ClaudePolicyFlags = {
|
|
77
|
+
allowedTools: [...new Set(allowedTools)],
|
|
78
|
+
disallowedTools: [...new Set(disallowedTools)],
|
|
79
|
+
}
|
|
80
|
+
if (policy.default !== undefined)
|
|
81
|
+
result.permissionMode = modeFor(policy.default)
|
|
82
|
+
return result
|
|
83
|
+
}
|