@clinebot/llms 0.0.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.
Files changed (219) hide show
  1. package/README.md +198 -0
  2. package/dist/config-browser.d.ts +3 -0
  3. package/dist/config.d.ts +3 -0
  4. package/dist/index.browser.d.ts +4 -0
  5. package/dist/index.browser.js +1 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +7 -0
  8. package/dist/models/generated-access.d.ts +4 -0
  9. package/dist/models/generated-provider-loaders.d.ts +13 -0
  10. package/dist/models/generated.d.ts +14 -0
  11. package/dist/models/index.d.ts +43 -0
  12. package/dist/models/models-dev-catalog.d.ts +32 -0
  13. package/dist/models/providers/aihubmix.d.ts +5 -0
  14. package/dist/models/providers/anthropic.d.ts +53 -0
  15. package/dist/models/providers/asksage.d.ts +5 -0
  16. package/dist/models/providers/baseten.d.ts +5 -0
  17. package/dist/models/providers/bedrock.d.ts +7 -0
  18. package/dist/models/providers/cerebras.d.ts +7 -0
  19. package/dist/models/providers/claude-code.d.ts +4 -0
  20. package/dist/models/providers/cline.d.ts +34 -0
  21. package/dist/models/providers/deepseek.d.ts +8 -0
  22. package/dist/models/providers/dify.d.ts +5 -0
  23. package/dist/models/providers/doubao.d.ts +7 -0
  24. package/dist/models/providers/fireworks.d.ts +8 -0
  25. package/dist/models/providers/gemini.d.ts +9 -0
  26. package/dist/models/providers/groq.d.ts +8 -0
  27. package/dist/models/providers/hicap.d.ts +5 -0
  28. package/dist/models/providers/huawei-cloud-maas.d.ts +5 -0
  29. package/dist/models/providers/huggingface.d.ts +6 -0
  30. package/dist/models/providers/index.d.ts +45 -0
  31. package/dist/models/providers/litellm.d.ts +5 -0
  32. package/dist/models/providers/lmstudio.d.ts +5 -0
  33. package/dist/models/providers/minimax.d.ts +7 -0
  34. package/dist/models/providers/mistral.d.ts +5 -0
  35. package/dist/models/providers/moonshot.d.ts +7 -0
  36. package/dist/models/providers/nebius.d.ts +7 -0
  37. package/dist/models/providers/nous-research.d.ts +7 -0
  38. package/dist/models/providers/oca.d.ts +9 -0
  39. package/dist/models/providers/ollama.d.ts +5 -0
  40. package/dist/models/providers/openai-codex.d.ts +10 -0
  41. package/dist/models/providers/openai.d.ts +9 -0
  42. package/dist/models/providers/opencode.d.ts +10 -0
  43. package/dist/models/providers/openrouter.d.ts +7 -0
  44. package/dist/models/providers/qwen-code.d.ts +7 -0
  45. package/dist/models/providers/qwen.d.ts +7 -0
  46. package/dist/models/providers/requesty.d.ts +6 -0
  47. package/dist/models/providers/sambanova.d.ts +7 -0
  48. package/dist/models/providers/sapaicore.d.ts +7 -0
  49. package/dist/models/providers/together.d.ts +8 -0
  50. package/dist/models/providers/vercel-ai-gateway.d.ts +5 -0
  51. package/dist/models/providers/vertex.d.ts +7 -0
  52. package/dist/models/providers/xai.d.ts +8 -0
  53. package/dist/models/providers/zai.d.ts +7 -0
  54. package/dist/models/query.d.ts +181 -0
  55. package/dist/models/registry.d.ts +123 -0
  56. package/dist/models/schemas/index.d.ts +7 -0
  57. package/dist/models/schemas/model.d.ts +340 -0
  58. package/dist/models/schemas/query.d.ts +191 -0
  59. package/dist/providers/handlers/ai-sdk-community.d.ts +46 -0
  60. package/dist/providers/handlers/ai-sdk-provider-base.d.ts +32 -0
  61. package/dist/providers/handlers/anthropic-base.d.ts +26 -0
  62. package/dist/providers/handlers/asksage.d.ts +12 -0
  63. package/dist/providers/handlers/auth.d.ts +5 -0
  64. package/dist/providers/handlers/base.d.ts +55 -0
  65. package/dist/providers/handlers/bedrock-base.d.ts +23 -0
  66. package/dist/providers/handlers/bedrock-client.d.ts +4 -0
  67. package/dist/providers/handlers/community-sdk.d.ts +97 -0
  68. package/dist/providers/handlers/fetch-base.d.ts +18 -0
  69. package/dist/providers/handlers/gemini-base.d.ts +25 -0
  70. package/dist/providers/handlers/index.d.ts +19 -0
  71. package/dist/providers/handlers/openai-base.d.ts +54 -0
  72. package/dist/providers/handlers/openai-responses.d.ts +64 -0
  73. package/dist/providers/handlers/providers.d.ts +43 -0
  74. package/dist/providers/handlers/r1-base.d.ts +62 -0
  75. package/dist/providers/handlers/registry.d.ts +106 -0
  76. package/dist/providers/handlers/vertex.d.ts +32 -0
  77. package/dist/providers/index.d.ts +100 -0
  78. package/dist/providers/public.browser.d.ts +2 -0
  79. package/dist/providers/public.d.ts +3 -0
  80. package/dist/providers/shared/openai-compatible.d.ts +10 -0
  81. package/dist/providers/transform/ai-sdk-community-format.d.ts +9 -0
  82. package/dist/providers/transform/anthropic-format.d.ts +24 -0
  83. package/dist/providers/transform/content-format.d.ts +3 -0
  84. package/dist/providers/transform/gemini-format.d.ts +19 -0
  85. package/dist/providers/transform/index.d.ts +10 -0
  86. package/dist/providers/transform/openai-format.d.ts +36 -0
  87. package/dist/providers/transform/r1-format.d.ts +26 -0
  88. package/dist/providers/types/config.d.ts +261 -0
  89. package/dist/providers/types/handler.d.ts +71 -0
  90. package/dist/providers/types/index.d.ts +11 -0
  91. package/dist/providers/types/messages.d.ts +139 -0
  92. package/dist/providers/types/model-info.d.ts +32 -0
  93. package/dist/providers/types/provider-ids.d.ts +63 -0
  94. package/dist/providers/types/settings.d.ts +308 -0
  95. package/dist/providers/types/stream.d.ts +106 -0
  96. package/dist/providers/utils/index.d.ts +7 -0
  97. package/dist/providers/utils/retry.d.ts +38 -0
  98. package/dist/providers/utils/stream-processor.d.ts +110 -0
  99. package/dist/providers/utils/tool-processor.d.ts +34 -0
  100. package/dist/sdk.d.ts +18 -0
  101. package/dist/types.d.ts +60 -0
  102. package/package.json +66 -0
  103. package/src/catalog.ts +20 -0
  104. package/src/config-browser.ts +11 -0
  105. package/src/config.ts +49 -0
  106. package/src/index.browser.ts +9 -0
  107. package/src/index.ts +10 -0
  108. package/src/live-providers.test.ts +137 -0
  109. package/src/models/generated-access.ts +41 -0
  110. package/src/models/generated-provider-loaders.ts +166 -0
  111. package/src/models/generated.ts +11997 -0
  112. package/src/models/index.ts +271 -0
  113. package/src/models/models-dev-catalog.test.ts +161 -0
  114. package/src/models/models-dev-catalog.ts +161 -0
  115. package/src/models/providers/aihubmix.ts +19 -0
  116. package/src/models/providers/anthropic.ts +60 -0
  117. package/src/models/providers/asksage.ts +19 -0
  118. package/src/models/providers/baseten.ts +21 -0
  119. package/src/models/providers/bedrock.ts +30 -0
  120. package/src/models/providers/cerebras.ts +24 -0
  121. package/src/models/providers/claude-code.ts +51 -0
  122. package/src/models/providers/cline.ts +25 -0
  123. package/src/models/providers/deepseek.ts +33 -0
  124. package/src/models/providers/dify.ts +17 -0
  125. package/src/models/providers/doubao.ts +33 -0
  126. package/src/models/providers/fireworks.ts +34 -0
  127. package/src/models/providers/gemini.ts +43 -0
  128. package/src/models/providers/groq.ts +33 -0
  129. package/src/models/providers/hicap.ts +18 -0
  130. package/src/models/providers/huawei-cloud-maas.ts +18 -0
  131. package/src/models/providers/huggingface.ts +22 -0
  132. package/src/models/providers/index.ts +162 -0
  133. package/src/models/providers/litellm.ts +19 -0
  134. package/src/models/providers/lmstudio.ts +22 -0
  135. package/src/models/providers/minimax.ts +34 -0
  136. package/src/models/providers/mistral.ts +19 -0
  137. package/src/models/providers/moonshot.ts +34 -0
  138. package/src/models/providers/nebius.ts +24 -0
  139. package/src/models/providers/nous-research.ts +21 -0
  140. package/src/models/providers/oca.ts +30 -0
  141. package/src/models/providers/ollama.ts +18 -0
  142. package/src/models/providers/openai-codex.ts +30 -0
  143. package/src/models/providers/openai.ts +43 -0
  144. package/src/models/providers/opencode.ts +28 -0
  145. package/src/models/providers/openrouter.ts +24 -0
  146. package/src/models/providers/qwen-code.ts +33 -0
  147. package/src/models/providers/qwen.ts +34 -0
  148. package/src/models/providers/requesty.ts +23 -0
  149. package/src/models/providers/sambanova.ts +23 -0
  150. package/src/models/providers/sapaicore.ts +34 -0
  151. package/src/models/providers/together.ts +35 -0
  152. package/src/models/providers/vercel-ai-gateway.ts +23 -0
  153. package/src/models/providers/vertex.ts +36 -0
  154. package/src/models/providers/xai.ts +34 -0
  155. package/src/models/providers/zai.ts +25 -0
  156. package/src/models/query.ts +407 -0
  157. package/src/models/registry.ts +511 -0
  158. package/src/models/schemas/index.ts +62 -0
  159. package/src/models/schemas/model.ts +308 -0
  160. package/src/models/schemas/query.ts +336 -0
  161. package/src/providers/browser.ts +4 -0
  162. package/src/providers/handlers/ai-sdk-community.ts +226 -0
  163. package/src/providers/handlers/ai-sdk-provider-base.ts +193 -0
  164. package/src/providers/handlers/anthropic-base.ts +372 -0
  165. package/src/providers/handlers/asksage.test.ts +103 -0
  166. package/src/providers/handlers/asksage.ts +138 -0
  167. package/src/providers/handlers/auth.test.ts +19 -0
  168. package/src/providers/handlers/auth.ts +121 -0
  169. package/src/providers/handlers/base.test.ts +46 -0
  170. package/src/providers/handlers/base.ts +160 -0
  171. package/src/providers/handlers/bedrock-base.ts +390 -0
  172. package/src/providers/handlers/bedrock-client.ts +100 -0
  173. package/src/providers/handlers/codex.test.ts +123 -0
  174. package/src/providers/handlers/community-sdk.test.ts +288 -0
  175. package/src/providers/handlers/community-sdk.ts +392 -0
  176. package/src/providers/handlers/fetch-base.ts +68 -0
  177. package/src/providers/handlers/gemini-base.ts +302 -0
  178. package/src/providers/handlers/index.ts +67 -0
  179. package/src/providers/handlers/openai-base.ts +277 -0
  180. package/src/providers/handlers/openai-responses.ts +598 -0
  181. package/src/providers/handlers/providers.test.ts +120 -0
  182. package/src/providers/handlers/providers.ts +563 -0
  183. package/src/providers/handlers/r1-base.ts +280 -0
  184. package/src/providers/handlers/registry.ts +185 -0
  185. package/src/providers/handlers/vertex.test.ts +124 -0
  186. package/src/providers/handlers/vertex.ts +292 -0
  187. package/src/providers/index.ts +534 -0
  188. package/src/providers/public.browser.ts +20 -0
  189. package/src/providers/public.ts +51 -0
  190. package/src/providers/shared/openai-compatible.ts +63 -0
  191. package/src/providers/transform/ai-sdk-community-format.test.ts +73 -0
  192. package/src/providers/transform/ai-sdk-community-format.ts +115 -0
  193. package/src/providers/transform/anthropic-format.ts +218 -0
  194. package/src/providers/transform/content-format.ts +34 -0
  195. package/src/providers/transform/format-conversion.test.ts +310 -0
  196. package/src/providers/transform/gemini-format.ts +167 -0
  197. package/src/providers/transform/index.ts +22 -0
  198. package/src/providers/transform/openai-format.ts +247 -0
  199. package/src/providers/transform/r1-format.ts +287 -0
  200. package/src/providers/types/config.ts +388 -0
  201. package/src/providers/types/handler.ts +87 -0
  202. package/src/providers/types/index.ts +120 -0
  203. package/src/providers/types/messages.ts +158 -0
  204. package/src/providers/types/model-info.test.ts +57 -0
  205. package/src/providers/types/model-info.ts +65 -0
  206. package/src/providers/types/provider-ids.test.ts +12 -0
  207. package/src/providers/types/provider-ids.ts +89 -0
  208. package/src/providers/types/settings.test.ts +49 -0
  209. package/src/providers/types/settings.ts +533 -0
  210. package/src/providers/types/stream.ts +117 -0
  211. package/src/providers/utils/index.ts +27 -0
  212. package/src/providers/utils/retry.test.ts +140 -0
  213. package/src/providers/utils/retry.ts +188 -0
  214. package/src/providers/utils/stream-processor.test.ts +232 -0
  215. package/src/providers/utils/stream-processor.ts +472 -0
  216. package/src/providers/utils/tool-processor.test.ts +34 -0
  217. package/src/providers/utils/tool-processor.ts +111 -0
  218. package/src/sdk.ts +264 -0
  219. package/src/types.ts +79 -0
@@ -0,0 +1,472 @@
1
+ /**
2
+ * Stream Response Processor
3
+ *
4
+ * Processes ApiStreamChunks and assembles them into Cline's message content format.
5
+ * This provides a clean interface between the streaming API and Cline's storage format.
6
+ */
7
+
8
+ import { parseJsonStream } from "@clinebot/shared";
9
+ import { JSONParser } from "@streamparser/json";
10
+ import type {
11
+ ApiStreamChunk,
12
+ ApiStreamReasoningChunk,
13
+ ApiStreamTextChunk,
14
+ ApiStreamToolCallsChunk,
15
+ ApiStreamUsageChunk,
16
+ } from "../types/stream";
17
+
18
+ // ============================================================================
19
+ // Output Types (Cline Storage Format)
20
+ // ============================================================================
21
+
22
+ export interface ReasoningDetailParam {
23
+ type: "reasoning.text" | string;
24
+ text: string;
25
+ signature: string;
26
+ format: "anthropic-claude-v1" | string;
27
+ index: number;
28
+ }
29
+
30
+ export interface AssistantTextBlock {
31
+ type: "text";
32
+ text: string;
33
+ call_id?: string;
34
+ reasoning_details?: ReasoningDetailParam[];
35
+ signature?: string;
36
+ }
37
+
38
+ export interface AssistantToolUseBlock {
39
+ type: "tool_use";
40
+ id: string;
41
+ name: string;
42
+ input: Record<string, unknown>;
43
+ call_id?: string;
44
+ reasoning_details?: ReasoningDetailParam[];
45
+ signature?: string;
46
+ }
47
+
48
+ export interface AssistantThinkingBlock {
49
+ type: "thinking";
50
+ thinking: string;
51
+ signature: string;
52
+ call_id?: string;
53
+ summary?: ReasoningDetailParam[];
54
+ }
55
+
56
+ export interface AssistantRedactedThinkingBlock {
57
+ type: "redacted_thinking";
58
+ data: string;
59
+ call_id?: string;
60
+ }
61
+
62
+ export type AssistantContentBlock =
63
+ | AssistantTextBlock
64
+ | AssistantToolUseBlock
65
+ | AssistantThinkingBlock
66
+ | AssistantRedactedThinkingBlock;
67
+
68
+ export interface UsageInfo {
69
+ inputTokens: number;
70
+ outputTokens: number;
71
+ cacheWriteTokens?: number;
72
+ cacheReadTokens?: number;
73
+ thoughtsTokenCount?: number;
74
+ totalCost?: number;
75
+ }
76
+
77
+ export interface ProcessedResponse {
78
+ content: AssistantContentBlock[];
79
+ usage?: UsageInfo;
80
+ responseId?: string;
81
+ incompleteReason?: string;
82
+ }
83
+
84
+ // ============================================================================
85
+ // Internal State
86
+ // ============================================================================
87
+
88
+ interface PendingToolUse {
89
+ id: string;
90
+ name: string;
91
+ rawInput: string;
92
+ parsedInput?: Record<string, unknown>;
93
+ signature?: string;
94
+ callId: string;
95
+ parser: JSONParser;
96
+ }
97
+
98
+ interface PendingReasoning {
99
+ thinking: string;
100
+ signature: string;
101
+ details: ReasoningDetailParam[];
102
+ redactedBlocks: AssistantRedactedThinkingBlock[];
103
+ callId?: string;
104
+ }
105
+
106
+ // ============================================================================
107
+ // Stream Response Processor
108
+ // ============================================================================
109
+
110
+ /**
111
+ * Processes streaming API responses and assembles content blocks.
112
+ *
113
+ * Usage:
114
+ * ```ts
115
+ * const processor = new StreamResponseProcessor()
116
+ * for await (const chunk of apiStream) {
117
+ * const partial = processor.process(chunk)
118
+ * // Use partial for live updates
119
+ * }
120
+ * const final = processor.finalize()
121
+ * ```
122
+ */
123
+ export class StreamResponseProcessor {
124
+ private text = "";
125
+ private textSignature?: string;
126
+ private toolUses = new Map<string, PendingToolUse>();
127
+ private reasoning: PendingReasoning | null = null;
128
+ private usage: UsageInfo | null = null;
129
+ private responseId?: string;
130
+ private incompleteReason?: string;
131
+
132
+ /**
133
+ * Process a single stream chunk and return current partial state.
134
+ * Call this for each chunk to get live updates.
135
+ */
136
+ process(chunk: ApiStreamChunk): ProcessedResponse {
137
+ switch (chunk.type) {
138
+ case "text":
139
+ this.processText(chunk);
140
+ break;
141
+ case "reasoning":
142
+ this.processReasoning(chunk);
143
+ break;
144
+ case "tool_calls":
145
+ this.processToolCall(chunk);
146
+ break;
147
+ case "usage":
148
+ this.processUsage(chunk);
149
+ break;
150
+ case "done":
151
+ this.incompleteReason = chunk.incompleteReason;
152
+ if (chunk.id) this.responseId = chunk.id;
153
+ break;
154
+ }
155
+
156
+ return this.getPartialResponse();
157
+ }
158
+
159
+ /**
160
+ * Finalize and return the complete response.
161
+ * Call this after all chunks have been processed.
162
+ */
163
+ finalize(): ProcessedResponse {
164
+ const content: AssistantContentBlock[] = [];
165
+
166
+ // Add thinking block if present
167
+ if (
168
+ this.reasoning &&
169
+ (this.reasoning.thinking || this.reasoning.details.length)
170
+ ) {
171
+ content.push({
172
+ type: "thinking",
173
+ thinking: this.reasoning.thinking,
174
+ signature: this.reasoning.signature,
175
+ call_id: this.reasoning.callId,
176
+ summary: this.reasoning.details.length
177
+ ? this.reasoning.details
178
+ : undefined,
179
+ });
180
+
181
+ // Add any redacted thinking blocks
182
+ content.push(...this.reasoning.redactedBlocks);
183
+ }
184
+
185
+ // Add text if present
186
+ if (this.text) {
187
+ content.push({
188
+ type: "text",
189
+ text: this.text,
190
+ signature: this.textSignature,
191
+ reasoning_details: this.reasoning?.details.length
192
+ ? this.reasoning.details
193
+ : undefined,
194
+ });
195
+ }
196
+
197
+ // Add finalized tool uses
198
+ for (const pending of this.toolUses.values()) {
199
+ if (!pending.name) continue;
200
+
201
+ const input = this.finalizeToolInput(pending);
202
+ content.push({
203
+ type: "tool_use",
204
+ id: pending.id,
205
+ name: pending.name,
206
+ input,
207
+ call_id: pending.callId,
208
+ signature: pending.signature,
209
+ reasoning_details: this.reasoning?.details.length
210
+ ? this.reasoning.details
211
+ : undefined,
212
+ });
213
+ }
214
+
215
+ return {
216
+ content,
217
+ usage: this.usage ?? undefined,
218
+ responseId: this.responseId,
219
+ incompleteReason: this.incompleteReason,
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Get current partial response for live streaming updates.
225
+ */
226
+ getPartialResponse(): ProcessedResponse {
227
+ const content: AssistantContentBlock[] = [];
228
+
229
+ // Add partial thinking
230
+ if (
231
+ this.reasoning &&
232
+ (this.reasoning.thinking || this.reasoning.details.length)
233
+ ) {
234
+ content.push({
235
+ type: "thinking",
236
+ thinking: this.reasoning.thinking,
237
+ signature: this.reasoning.signature,
238
+ call_id: this.reasoning.callId,
239
+ summary: this.reasoning.details.length
240
+ ? this.reasoning.details
241
+ : undefined,
242
+ });
243
+ }
244
+
245
+ // Add partial text
246
+ if (this.text) {
247
+ content.push({
248
+ type: "text",
249
+ text: this.text,
250
+ signature: this.textSignature,
251
+ });
252
+ }
253
+
254
+ // Add partial tool uses
255
+ for (const pending of this.toolUses.values()) {
256
+ if (!pending.name) continue;
257
+
258
+ const input =
259
+ pending.parsedInput ?? this.extractPartialJson(pending.rawInput);
260
+ content.push({
261
+ type: "tool_use",
262
+ id: pending.id,
263
+ name: pending.name,
264
+ input,
265
+ call_id: pending.callId,
266
+ signature: pending.signature,
267
+ });
268
+ }
269
+
270
+ return {
271
+ content,
272
+ usage: this.usage ?? undefined,
273
+ responseId: this.responseId,
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Reset processor state for reuse.
279
+ */
280
+ reset(): void {
281
+ this.text = "";
282
+ this.textSignature = undefined;
283
+ this.toolUses.clear();
284
+ this.reasoning = null;
285
+ this.usage = null;
286
+ this.responseId = undefined;
287
+ this.incompleteReason = undefined;
288
+ }
289
+
290
+ // ============================================================================
291
+ // Private Methods
292
+ // ============================================================================
293
+
294
+ private processText(chunk: ApiStreamTextChunk): void {
295
+ this.text += chunk.text;
296
+ if (chunk.signature) this.textSignature = chunk.signature;
297
+ if (chunk.id) this.responseId = chunk.id;
298
+ }
299
+
300
+ private processReasoning(chunk: ApiStreamReasoningChunk): void {
301
+ if (!this.reasoning) {
302
+ this.reasoning = {
303
+ thinking: "",
304
+ signature: "",
305
+ details: [],
306
+ redactedBlocks: [],
307
+ callId: chunk.id,
308
+ };
309
+ }
310
+
311
+ if (chunk.reasoning) {
312
+ this.reasoning.thinking += chunk.reasoning;
313
+ }
314
+
315
+ if (chunk.signature) {
316
+ this.reasoning.signature = chunk.signature;
317
+ }
318
+
319
+ if (chunk.details) {
320
+ const details = Array.isArray(chunk.details)
321
+ ? chunk.details
322
+ : [chunk.details];
323
+ for (const detail of details) {
324
+ if (this.isReasoningDetail(detail)) {
325
+ this.reasoning.details.push(detail);
326
+ // Extract signature from details if not set at top level
327
+ if (!this.reasoning.signature && detail.signature) {
328
+ this.reasoning.signature = detail.signature;
329
+ }
330
+ }
331
+ }
332
+ }
333
+
334
+ if (chunk.redacted_data) {
335
+ this.reasoning.redactedBlocks.push({
336
+ type: "redacted_thinking",
337
+ data: chunk.redacted_data,
338
+ call_id: chunk.id ?? this.reasoning.callId,
339
+ });
340
+ }
341
+
342
+ if (chunk.id) this.responseId = chunk.id;
343
+ }
344
+
345
+ private processToolCall(chunk: ApiStreamToolCallsChunk): void {
346
+ const tc = chunk.tool_call;
347
+ const fn = tc.function;
348
+ const id = fn.id ?? tc.call_id ?? "";
349
+
350
+ if (!id) return;
351
+
352
+ let pending = this.toolUses.get(id);
353
+ if (!pending) {
354
+ pending = this.createPendingToolUse(id, tc.call_id ?? id);
355
+ this.toolUses.set(id, pending);
356
+ }
357
+
358
+ if (fn.name) {
359
+ pending.name = fn.name;
360
+ }
361
+
362
+ if (chunk.signature) {
363
+ pending.signature = chunk.signature;
364
+ }
365
+
366
+ if (fn.arguments) {
367
+ const args =
368
+ typeof fn.arguments === "string"
369
+ ? fn.arguments
370
+ : JSON.stringify(fn.arguments);
371
+ pending.rawInput += args;
372
+ try {
373
+ pending.parser.write(args);
374
+ } catch {
375
+ // Expected during streaming - parser may not have complete JSON
376
+ }
377
+ }
378
+
379
+ if (chunk.id) this.responseId = chunk.id;
380
+ }
381
+
382
+ private processUsage(chunk: ApiStreamUsageChunk): void {
383
+ this.usage = {
384
+ inputTokens: chunk.inputTokens,
385
+ outputTokens: chunk.outputTokens,
386
+ cacheWriteTokens: chunk.cacheWriteTokens,
387
+ cacheReadTokens: chunk.cacheReadTokens,
388
+ thoughtsTokenCount: chunk.thoughtsTokenCount,
389
+ totalCost: chunk.totalCost,
390
+ };
391
+ if (chunk.id) this.responseId = chunk.id;
392
+ }
393
+
394
+ private createPendingToolUse(id: string, callId: string): PendingToolUse {
395
+ const pending: PendingToolUse = {
396
+ id,
397
+ name: "",
398
+ rawInput: "",
399
+ parsedInput: undefined,
400
+ signature: undefined,
401
+ callId,
402
+ parser: new JSONParser(),
403
+ };
404
+
405
+ pending.parser.onValue = (info: { stack: unknown[]; value?: unknown }) => {
406
+ if (
407
+ info.stack.length === 0 &&
408
+ info.value &&
409
+ typeof info.value === "object"
410
+ ) {
411
+ pending.parsedInput = info.value as Record<string, unknown>;
412
+ }
413
+ };
414
+ pending.parser.onError = () => {};
415
+
416
+ return pending;
417
+ }
418
+
419
+ private finalizeToolInput(pending: PendingToolUse): Record<string, unknown> {
420
+ if (pending.parsedInput) {
421
+ return pending.parsedInput;
422
+ }
423
+
424
+ if (pending.rawInput) {
425
+ const parsed = parseJsonStream(pending.rawInput);
426
+ if (this.isRecord(parsed)) {
427
+ return parsed;
428
+ }
429
+
430
+ return this.extractPartialJson(pending.rawInput);
431
+ }
432
+
433
+ return {};
434
+ }
435
+
436
+ /**
437
+ * Extract fields from incomplete JSON during streaming.
438
+ */
439
+ private extractPartialJson(partial: string): Record<string, unknown> {
440
+ const result: Record<string, unknown> = {};
441
+ const pattern = /"(\w+)":\s*"((?:[^"\\]|\\.)*)(?:")?/g;
442
+
443
+ for (const match of partial.matchAll(pattern)) {
444
+ result[match[1]] = this.unescapeString(match[2]);
445
+ }
446
+
447
+ return result;
448
+ }
449
+
450
+ private unescapeString(str: string): string {
451
+ return str
452
+ .replace(/\\n/g, "\n")
453
+ .replace(/\\t/g, "\t")
454
+ .replace(/\\r/g, "\r")
455
+ .replace(/\\"/g, '"')
456
+ .replace(/\\\\/g, "\\");
457
+ }
458
+
459
+ private isReasoningDetail(value: unknown): value is ReasoningDetailParam {
460
+ return (
461
+ typeof value === "object" &&
462
+ value !== null &&
463
+ "type" in value &&
464
+ "text" in value &&
465
+ typeof (value as ReasoningDetailParam).text === "string"
466
+ );
467
+ }
468
+
469
+ private isRecord(value: unknown): value is Record<string, unknown> {
470
+ return typeof value === "object" && value !== null && !Array.isArray(value);
471
+ }
472
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ToolCallProcessor } from "./tool-processor";
3
+
4
+ describe("ToolCallProcessor", () => {
5
+ it("emits delta arguments (not cumulative) so downstream can accumulate once", () => {
6
+ const processor = new ToolCallProcessor();
7
+
8
+ const first = processor.processToolCallDeltas(
9
+ [
10
+ {
11
+ index: 0,
12
+ id: "call_1",
13
+ function: { name: "run_commands", arguments: '{"commands":["ls' },
14
+ },
15
+ ],
16
+ "resp_1",
17
+ );
18
+
19
+ const second = processor.processToolCallDeltas(
20
+ [
21
+ {
22
+ index: 0,
23
+ function: { arguments: ' -la"]}' },
24
+ },
25
+ ],
26
+ "resp_1",
27
+ );
28
+
29
+ expect(first).toHaveLength(1);
30
+ expect(second).toHaveLength(1);
31
+ expect(first[0].tool_call.function.arguments).toBe('{"commands":["ls');
32
+ expect(second[0].tool_call.function.arguments).toBe(' -la"]}');
33
+ });
34
+ });
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Tool Call Processor
3
+ *
4
+ * Handles incremental assembly of tool calls from streaming responses.
5
+ * Tool calls can arrive in chunks that need to be accumulated.
6
+ */
7
+
8
+ import type {
9
+ ApiStreamToolCall,
10
+ ApiStreamToolCallsChunk,
11
+ } from "../types/stream";
12
+
13
+ // Re-export to satisfy linter (type is used in generator return type)
14
+ export type { ApiStreamToolCallsChunk };
15
+
16
+ interface ToolCallDelta {
17
+ index: number;
18
+ id?: string;
19
+ function?: {
20
+ name?: string;
21
+ arguments?: string;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Processes tool call deltas from streaming API responses
27
+ */
28
+ export class ToolCallProcessor {
29
+ private toolCalls: Map<
30
+ number,
31
+ {
32
+ id: string;
33
+ name: string;
34
+ arguments: string;
35
+ }
36
+ > = new Map();
37
+
38
+ /**
39
+ * Process tool call deltas and yield complete tool calls
40
+ */
41
+ processToolCallDeltas(
42
+ deltas: ToolCallDelta[],
43
+ responseId: string,
44
+ ): ApiStreamToolCallsChunk[] {
45
+ const results: ApiStreamToolCallsChunk[] = [];
46
+ for (const delta of deltas) {
47
+ const { index, id } = delta;
48
+ const fn = delta.function;
49
+
50
+ // Get or create tool call state
51
+ let toolCall = this.toolCalls.get(index);
52
+ if (!toolCall) {
53
+ toolCall = { id: "", name: "", arguments: "" };
54
+ this.toolCalls.set(index, toolCall);
55
+ }
56
+
57
+ // Update with delta values
58
+ if (id) {
59
+ toolCall.id = id;
60
+ }
61
+ if (fn?.name) {
62
+ toolCall.name = fn.name;
63
+ }
64
+ const deltaArguments = fn?.arguments ?? "";
65
+ if (deltaArguments) {
66
+ toolCall.arguments += deltaArguments;
67
+ }
68
+
69
+ // Add the current state as a chunk
70
+ // This allows streaming of partial tool calls
71
+ if (toolCall.id && toolCall.name) {
72
+ results.push({
73
+ type: "tool_calls",
74
+ id: responseId,
75
+ tool_call: {
76
+ call_id: toolCall.id,
77
+ function: {
78
+ id: toolCall.id,
79
+ name: toolCall.name,
80
+ // Emit only the current delta arguments; the agent layer already
81
+ // performs accumulation for streaming tool arguments.
82
+ arguments: deltaArguments,
83
+ },
84
+ },
85
+ });
86
+ }
87
+ }
88
+ return results;
89
+ }
90
+
91
+ /**
92
+ * Get all accumulated tool calls
93
+ */
94
+ getToolCalls(): ApiStreamToolCall[] {
95
+ return Array.from(this.toolCalls.values()).map((tc) => ({
96
+ call_id: tc.id,
97
+ function: {
98
+ id: tc.id,
99
+ name: tc.name,
100
+ arguments: tc.arguments,
101
+ },
102
+ }));
103
+ }
104
+
105
+ /**
106
+ * Reset the processor state
107
+ */
108
+ reset(): void {
109
+ this.toolCalls.clear();
110
+ }
111
+ }