@codeworksh/aikit 0.2.1 → 0.3.1

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/index.mjs CHANGED
@@ -1,19 +1,38 @@
1
1
  import { Filesystem, NamedError, lazy } from "@codeworksh/utils";
2
- import { Type } from "@sinclair/typebox";
2
+ import { Type, TypeGuard } from "@sinclair/typebox";
3
+ import { capitalize, keys, mapValues, pick, pipe } from "remeda";
4
+ import Ajv from "ajv";
5
+ import addFormats from "ajv-formats";
3
6
  import { uuidv7 } from "uuidv7";
4
- import { keys, mapValues, pick, pipe } from "remeda";
5
7
  import Anthropic from "@anthropic-ai/sdk";
6
8
  import { parse } from "partial-json";
7
- import Ajv from "ajv";
8
- import addFormats from "ajv-formats";
9
+ import OpenAI from "openai";
10
+ import { transform } from "esbuild";
9
11
  //#region src/provider/provider.ts
10
12
  let Provider;
11
13
  (function(_Provider) {
12
14
  const KnownProviderEnum = _Provider.KnownProviderEnum = {
13
15
  anthropic: "anthropic",
14
- openai: "openai"
16
+ openai: "openai",
17
+ githubCopilot: "github-copilot",
18
+ openrouter: "openrouter",
19
+ groq: "groq",
20
+ xai: "xai",
21
+ cerebras: "cerebras",
22
+ zai: "zai",
23
+ opencode: "opencode"
15
24
  };
16
- const KnownProviderSchema = _Provider.KnownProviderSchema = Type.Union([Type.Literal(KnownProviderEnum.anthropic), Type.Literal(KnownProviderEnum.openai)]);
25
+ const KnownProviderSchema = _Provider.KnownProviderSchema = Type.Union([
26
+ Type.Literal(KnownProviderEnum.anthropic),
27
+ Type.Literal(KnownProviderEnum.openai),
28
+ Type.Literal(KnownProviderEnum.githubCopilot),
29
+ Type.Literal(KnownProviderEnum.openrouter),
30
+ Type.Literal(KnownProviderEnum.groq),
31
+ Type.Literal(KnownProviderEnum.xai),
32
+ Type.Literal(KnownProviderEnum.cerebras),
33
+ Type.Literal(KnownProviderEnum.zai),
34
+ Type.Literal(KnownProviderEnum.opencode)
35
+ ]);
17
36
  _Provider.Info = Type.Object({
18
37
  id: KnownProviderSchema,
19
38
  name: Type.String(),
@@ -75,6 +94,46 @@ let ModelCatalog;
75
94
  }, 60 * 1e3 * 60).unref();
76
95
  })(ModelCatalog || (ModelCatalog = {}));
77
96
  //#endregion
97
+ //#region src/model/transform.ts
98
+ function defaultBaseUrl(providerId) {
99
+ switch (providerId) {
100
+ case Provider.KnownProviderEnum.openai: return "https://api.openai.com/v1";
101
+ default: return;
102
+ }
103
+ }
104
+ function resolveDefaultProtocol(providerId) {
105
+ switch (providerId) {
106
+ case Provider.KnownProviderEnum.anthropic: return Model.KnownProtocolEnum.anthropicMessages;
107
+ case Provider.KnownProviderEnum.openai: return Model.KnownProtocolEnum.openaiResponses;
108
+ default: return Model.KnownProtocolEnum.openaiCompletions;
109
+ }
110
+ }
111
+ function applyModification(providerId, provider, model) {
112
+ const baseUrl = model.baseUrl ?? provider.baseUrl ?? provider.api ?? defaultBaseUrl(providerId);
113
+ const normalized = {
114
+ id: model.id,
115
+ name: model.name,
116
+ provider: Model.toProviderInfo(providerId, provider),
117
+ baseUrl,
118
+ reasoning: Boolean(model.reasoning),
119
+ input: Model.normalizeInput(model.modalities.input),
120
+ cost: {
121
+ input: model.cost?.input ?? 0,
122
+ output: model.cost?.output ?? 0,
123
+ cacheRead: model.cost?.cache_read ?? 0,
124
+ cacheWrite: model.cost?.cache_write ?? 0
125
+ },
126
+ contextWindow: model.limit?.context ?? 0,
127
+ maxTokens: model.limit?.output ?? 0,
128
+ headers: model.headers ?? provider.headers,
129
+ protocol: resolveDefaultProtocol(providerId)
130
+ };
131
+ if (providerId === Provider.KnownProviderEnum.openai) {
132
+ normalized.supportedProtocols = { openaiCompletions: Model.KnownProtocolEnum.openaiCompletions };
133
+ return normalized;
134
+ } else return normalized;
135
+ }
136
+ //#endregion
78
137
  //#region src/model/model.ts
79
138
  let Model;
80
139
  (function(_Model) {
@@ -96,6 +155,11 @@ let Model;
96
155
  cacheWrite: Type.Number()
97
156
  });
98
157
  const HeadersSchema = Type.Optional(Type.Record(Type.String(), Type.String()));
158
+ const SupportedProtocolsSchema = _Model.SupportedProtocolsSchema = Type.Object({
159
+ anthropicMessages: Type.Optional(Type.Literal(KnownProtocolEnum.anthropicMessages)),
160
+ openaiCompletions: Type.Optional(Type.Literal(KnownProtocolEnum.openaiCompletions)),
161
+ openaiResponses: Type.Optional(Type.Literal(KnownProtocolEnum.openaiResponses))
162
+ });
99
163
  const BaseSchema = _Model.BaseSchema = Type.Object({
100
164
  id: Type.String(),
101
165
  name: Type.String(),
@@ -106,7 +170,8 @@ let Model;
106
170
  cost: CostSchema,
107
171
  contextWindow: Type.Number(),
108
172
  maxTokens: Type.Number(),
109
- headers: HeadersSchema
173
+ headers: HeadersSchema,
174
+ supportedProtocols: Type.Optional(Type.Partial(SupportedProtocolsSchema))
110
175
  });
111
176
  const OpenAICompletionsCompatSchema = _Model.OpenAICompletionsCompatSchema = Type.Object({
112
177
  supportsStore: Type.Optional(Type.Boolean()),
@@ -127,6 +192,7 @@ let Model;
127
192
  ])),
128
193
  openRouterRouting: Type.Optional(Provider.OpenRouterRoutingSchema),
129
194
  vercelGatewayRouting: Type.Optional(Provider.VercelGatewayRoutingSchema),
195
+ zaiToolStream: Type.Optional(Type.Boolean()),
130
196
  supportsStrictMode: Type.Optional(Type.Boolean())
131
197
  });
132
198
  const OpenAIResponsesCompatSchema = _Model.OpenAIResponsesCompatSchema = Type.Object({});
@@ -140,7 +206,7 @@ let Model;
140
206
  protocol: Type.Literal(KnownProtocolEnum.openaiResponses),
141
207
  compat: Type.Optional(OpenAIResponsesCompatSchema)
142
208
  })]);
143
- _Model.Schema = Type.Union([
209
+ _Model.Info = Type.Union([
144
210
  AnthropicSchema,
145
211
  OpenAICompletionsSchema,
146
212
  OpenAIResponsesSchema
@@ -154,18 +220,13 @@ let Model;
154
220
  usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
155
221
  }
156
222
  _Model.calculateCost = calculateCost;
157
- function resolveProtocol(providerId) {
158
- switch (providerId) {
159
- case Provider.KnownProviderEnum.anthropic: return KnownProtocolEnum.anthropicMessages;
160
- case Provider.KnownProviderEnum.openai: return KnownProtocolEnum.openaiResponses;
161
- }
162
- }
163
223
  function normalizeInput(input) {
164
224
  const normalized = /* @__PURE__ */ new Set();
165
225
  for (const modality of input ?? ["text"]) if (modality === "text" || modality === "image") normalized.add(modality);
166
226
  if (normalized.size === 0) normalized.add("text");
167
227
  return [...normalized];
168
228
  }
229
+ _Model.normalizeInput = normalizeInput;
169
230
  function toProviderInfo(providerId, provider) {
170
231
  return {
171
232
  id: providerId,
@@ -175,26 +236,9 @@ let Model;
175
236
  options: provider.headers
176
237
  };
177
238
  }
239
+ _Model.toProviderInfo = toProviderInfo;
178
240
  function toModelValue(providerId, provider, model) {
179
- const baseUrl = model.baseUrl ?? provider.baseUrl ?? provider.api;
180
- return {
181
- id: model.id,
182
- name: model.name,
183
- provider: toProviderInfo(providerId, provider),
184
- baseUrl,
185
- reasoning: Boolean(model.reasoning),
186
- input: normalizeInput(model.modalities.input),
187
- cost: {
188
- input: model.cost?.input ?? 0,
189
- output: model.cost?.output ?? 0,
190
- cacheRead: model.cost?.cache_read ?? 0,
191
- cacheWrite: model.cost?.cache_write ?? 0
192
- },
193
- contextWindow: model.limit?.context ?? 0,
194
- maxTokens: model.limit?.output ?? 0,
195
- headers: model.headers ?? provider.headers,
196
- protocol: resolveProtocol(providerId)
197
- };
241
+ return applyModification(providerId, provider, model);
198
242
  }
199
243
  async function getBuiltInModels() {
200
244
  return pipe(await ModelCatalog.get(), pick(BUILTINS), mapValues((provider, providerId) => mapValues(provider.models, (model) => toModelValue(providerId, provider, model))));
@@ -210,8 +254,13 @@ let Model;
210
254
  }
211
255
  return registry;
212
256
  });
213
- async function getModel(provider, model) {
214
- return (await registry()).get(provider)?.get(model);
257
+ async function getModel(provider, model, overrides) {
258
+ const result = (await registry()).get(provider)?.get(model);
259
+ if (result && overrides) return {
260
+ ...result,
261
+ ...overrides
262
+ };
263
+ return result;
215
264
  }
216
265
  _Model.getModel = getModel;
217
266
  async function getProviders() {
@@ -240,237 +289,171 @@ let Model;
240
289
  _Model.modelsAreEqual = modelsAreEqual;
241
290
  })(Model || (Model = {}));
242
291
  //#endregion
243
- //#region src/message/message.ts
244
- let Message;
245
- (function(_Message) {
246
- const TextContentSchema = _Message.TextContentSchema = Type.Object({
247
- type: Type.Literal("text"),
248
- text: Type.String(),
249
- textSignature: Type.Optional(Type.String())
250
- });
251
- const ImageContentSchema = _Message.ImageContentSchema = Type.Object({
252
- type: Type.Literal("image"),
253
- data: Type.String(),
254
- mimeType: Type.String()
255
- });
256
- const ThinkingContentSchema = _Message.ThinkingContentSchema = Type.Object({
257
- type: Type.Literal("thinking"),
258
- thinking: Type.String(),
259
- thinkingSignature: Type.Optional(Type.String()),
260
- redacted: Type.Optional(Type.Boolean())
261
- });
262
- const ToolCallBaseSchema = _Message.ToolCallBaseSchema = Type.Object({
263
- type: Type.Literal("toolCall"),
264
- callID: Type.String(),
265
- name: Type.String(),
266
- arguments: Type.Record(Type.String(), Type.Any()),
267
- thoughtSignature: Type.Optional(Type.String()),
268
- time: Type.Object({
269
- start: Type.Number(),
270
- end: Type.Number()
271
- })
272
- });
273
- const ToolSuccessResult = _Message.ToolSuccessResult = Type.Object({
274
- content: Type.Array(Type.Union([TextContentSchema, ImageContentSchema])),
275
- details: Type.Optional(Type.Any()),
276
- isError: Type.Literal(false)
277
- });
278
- const ToolErrorResult = _Message.ToolErrorResult = Type.Object({
279
- content: Type.Array(Type.Union([TextContentSchema, ImageContentSchema])),
280
- details: Type.Optional(Type.Any()),
281
- isError: Type.Literal(true)
282
- });
283
- const ToolRunningPartial = _Message.ToolRunningPartial = Type.Object({
284
- content: Type.Optional(Type.Array(Type.Union([TextContentSchema, ImageContentSchema]))),
285
- details: Type.Optional(Type.Any())
286
- });
287
- const ToolStatusEnum = _Message.ToolStatusEnum = {
288
- pending: "pending",
289
- running: "running",
290
- completed: "completed",
291
- error: "error",
292
- skipped: "skipped",
293
- aborted: "aborted"
292
+ //#region src/provider/stream.ts
293
+ let Stream;
294
+ (function(_Stream) {
295
+ const ProtocolProviderNotFoundError = _Stream.ProtocolProviderNotFoundError = NamedError.create("ProtocolProviderNotFoundError", Type.Object({ protocol: Model.KnownProtocolSchema }));
296
+ const ProtocolMismatchError = _Stream.ProtocolMismatchError = NamedError.create("ProtocolMismatchError", Type.Object({
297
+ actual: Model.KnownProtocolSchema,
298
+ expected: Model.KnownProtocolSchema
299
+ }));
300
+ const ThinkingLevelEnum = _Stream.ThinkingLevelEnum = {
301
+ off: "off",
302
+ minimal: "minimal",
303
+ low: "low",
304
+ medium: "medium",
305
+ high: "high",
306
+ xhigh: "xhigh"
294
307
  };
295
- const ToolRunningSchema = _Message.ToolRunningSchema = Type.Object({
296
- status: Type.Literal(ToolStatusEnum.running),
297
- partial: Type.Optional(ToolRunningPartial)
298
- });
299
- const ToolCompletedSchema = _Message.ToolCompletedSchema = Type.Object({
300
- status: Type.Literal(ToolStatusEnum.completed),
301
- result: ToolSuccessResult
302
- });
303
- const ToolErrorSchema = _Message.ToolErrorSchema = Type.Object({
304
- status: Type.Literal(ToolStatusEnum.error),
305
- result: ToolErrorResult
306
- });
307
- const ToolSkippedSchema = _Message.ToolSkippedSchema = Type.Object({
308
- status: Type.Literal(ToolStatusEnum.skipped),
309
- result: ToolErrorResult
310
- });
311
- const ToolAbortedSchema = _Message.ToolAbortedSchema = Type.Object({
312
- status: Type.Literal(ToolStatusEnum.aborted),
313
- result: ToolErrorResult
314
- });
315
- const ToolCallPendingPart = Type.Intersect([ToolCallBaseSchema, Type.Object({ status: Type.Literal(ToolStatusEnum.pending) })]);
316
- const ToolCallRunningPart = Type.Composite([ToolCallBaseSchema, ToolRunningSchema]);
317
- const ToolCallCompletedPart = Type.Composite([ToolCallBaseSchema, ToolCompletedSchema]);
318
- const ToolCallErrorPart = Type.Composite([ToolCallBaseSchema, ToolErrorSchema]);
319
- const ToolCallSkippedPart = Type.Composite([ToolCallBaseSchema, ToolSkippedSchema]);
320
- const ToolCallAbortedPart = Type.Composite([ToolCallBaseSchema, ToolAbortedSchema]);
321
- const ToolCallSchema = _Message.ToolCallSchema = Type.Union([
322
- ToolCallPendingPart,
323
- ToolCallRunningPart,
324
- ToolCallCompletedPart,
325
- ToolCallErrorPart,
326
- ToolCallSkippedPart,
327
- ToolCallAbortedPart
308
+ const ThinkingLevelSchema = _Stream.ThinkingLevelSchema = Type.Union([
309
+ Type.Literal(ThinkingLevelEnum.off),
310
+ Type.Literal(ThinkingLevelEnum.minimal),
311
+ Type.Literal(ThinkingLevelEnum.low),
312
+ Type.Literal(ThinkingLevelEnum.medium),
313
+ Type.Literal(ThinkingLevelEnum.high),
314
+ Type.Literal(ThinkingLevelEnum.xhigh)
328
315
  ]);
329
- const UsageSchema = _Message.UsageSchema = Type.Object({
330
- input: Type.Number(),
331
- output: Type.Number(),
332
- cacheRead: Type.Number(),
333
- cacheWrite: Type.Number(),
334
- totalTokens: Type.Number(),
335
- cost: Type.Object({
336
- input: Type.Number(),
337
- output: Type.Number(),
338
- cacheRead: Type.Number(),
339
- cacheWrite: Type.Number(),
340
- total: Type.Number()
341
- })
316
+ const ThinkingBudgetsSchema = _Stream.ThinkingBudgetsSchema = Type.Object({
317
+ off: Type.Optional(Type.Number()),
318
+ minimal: Type.Optional(Type.Number()),
319
+ low: Type.Optional(Type.Number()),
320
+ medium: Type.Optional(Type.Number()),
321
+ high: Type.Optional(Type.Number())
342
322
  });
343
- const StopReasonSchema = _Message.StopReasonSchema = Type.Union([
344
- Type.Literal("stop"),
345
- Type.Literal("length"),
346
- Type.Literal("toolUse"),
347
- Type.Literal("error"),
348
- Type.Literal("aborted")
323
+ const CacheRetentionSchema = _Stream.CacheRetentionSchema = Type.Union([
324
+ Type.Literal("none"),
325
+ Type.Literal("short"),
326
+ Type.Literal("long")
349
327
  ]);
350
- const UserMessageSchema = _Message.UserMessageSchema = Type.Object({
351
- messageId: Type.String(),
352
- role: Type.Literal("user"),
353
- time: Type.Object({ created: Type.Number() }),
354
- parts: Type.Array(Type.Union([TextContentSchema, ImageContentSchema]))
355
- });
356
- const AssistantMessageSchema = _Message.AssistantMessageSchema = Type.Object({
357
- role: Type.Literal("assistant"),
358
- protocol: Model.KnownProtocolSchema,
359
- provider: Provider.Info,
360
- model: Type.String(),
361
- usage: UsageSchema,
362
- stopReason: StopReasonSchema,
363
- errorMessage: Type.Optional(Type.String()),
364
- time: Type.Object({
365
- created: Type.Number(),
366
- completed: Type.Number()
367
- }),
368
- parts: Type.Array(Type.Union([
369
- TextContentSchema,
370
- ImageContentSchema,
371
- ThinkingContentSchema,
372
- ToolCallSchema
373
- ])),
374
- responseId: Type.Optional(Type.String()),
375
- messageId: Type.String()
328
+ const TransportSchema = _Stream.TransportSchema = Type.Union([
329
+ Type.Literal("sse"),
330
+ Type.Literal("websocket"),
331
+ Type.Literal("auto")
332
+ ]);
333
+ const OptionsSchema = _Stream.OptionsSchema = Type.Object({
334
+ temperature: Type.Optional(Type.Number()),
335
+ maxTokens: Type.Optional(Type.Number()),
336
+ signal: Type.Optional(Type.Unsafe()),
337
+ apiKey: Type.Optional(Type.String()),
338
+ transport: Type.Optional(TransportSchema),
339
+ cacheRetention: Type.Optional(CacheRetentionSchema),
340
+ sessionId: Type.Optional(Type.String()),
341
+ onPayload: Type.Optional(Type.Unsafe()),
342
+ headers: Type.Optional(Type.Record(Type.String(), Type.String())),
343
+ maxRetryDelayMs: Type.Optional(Type.Number()),
344
+ metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
376
345
  });
377
- const MessageSchema = _Message.MessageSchema = Type.Union([UserMessageSchema, AssistantMessageSchema]);
378
- function createMessageId() {
379
- return uuidv7();
380
- }
381
- _Message.createMessageId = createMessageId;
382
- function createUserMessage(message) {
383
- const { messageId = createMessageId(), ...rest } = message;
346
+ _Stream.SimpleOptionsSchema = Type.Composite([OptionsSchema, Type.Object({
347
+ reasoning: Type.Optional(ThinkingLevelSchema),
348
+ thinkingBudgets: Type.Optional(ThinkingBudgetsSchema)
349
+ })]);
350
+ function buildBaseOptions(model, options, apiKey) {
384
351
  return {
385
- messageId,
386
- ...rest
352
+ temperature: options?.temperature,
353
+ maxTokens: options?.maxTokens || Math.min(model.maxTokens, 32e3),
354
+ signal: options?.signal,
355
+ apiKey: apiKey || options?.apiKey,
356
+ transport: options?.transport,
357
+ cacheRetention: options?.cacheRetention,
358
+ sessionId: options?.sessionId,
359
+ headers: options?.headers,
360
+ onPayload: options?.onPayload,
361
+ maxRetryDelayMs: options?.maxRetryDelayMs,
362
+ metadata: options?.metadata
387
363
  };
388
364
  }
389
- _Message.createUserMessage = createUserMessage;
390
- function createAssistantMessage(message) {
391
- const { messageId = createMessageId(), ...rest } = message;
365
+ _Stream.buildBaseOptions = buildBaseOptions;
366
+ function clampReasoning(effort) {
367
+ return effort === ThinkingLevelEnum.xhigh ? ThinkingLevelEnum.high : effort;
368
+ }
369
+ _Stream.clampReasoning = clampReasoning;
370
+ function adjustMaxTokensForThinking(baseMaxTokens, modelMaxTokens, reasoningLevel, customBudgets) {
371
+ const budgets = {
372
+ minimal: 1024,
373
+ low: 2048,
374
+ medium: 8192,
375
+ high: 16384,
376
+ ...customBudgets
377
+ };
378
+ const minOutputTokens = 1024;
379
+ let thinkingBudget = budgets[clampReasoning(reasoningLevel)];
380
+ const maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
381
+ if (maxTokens <= thinkingBudget) thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
392
382
  return {
393
- messageId,
394
- ...rest
383
+ maxTokens,
384
+ thinkingBudget
395
385
  };
396
386
  }
397
- _Message.createAssistantMessage = createAssistantMessage;
398
- const ToolSchema = _Message.ToolSchema = Type.Object({
399
- name: Type.String(),
400
- description: Type.String(),
401
- parameters: Type.Unsafe()
402
- });
403
- function defineTool(tool) {
404
- return tool;
405
- }
406
- _Message.defineTool = defineTool;
407
- _Message.ContextSchema = Type.Object({
408
- systemPrompt: Type.Optional(Type.String()),
409
- messages: Type.Array(MessageSchema),
410
- tools: Type.Optional(Type.Array(ToolSchema))
411
- });
412
- function transformMessages(messages, model, normalizeToolCallId) {
413
- const toolCallIDMap = /* @__PURE__ */ new Map();
414
- const transformed = [];
415
- for (const msg of messages) {
416
- if (msg.role === "user") {
417
- transformed.push(msg);
418
- continue;
419
- }
420
- const assistantMsg = msg;
421
- const isSameModel = assistantMsg.provider.id === model.provider.id && assistantMsg.protocol === model.protocol && assistantMsg.model === model.id;
422
- const parts = assistantMsg.parts.flatMap((block) => {
423
- if (block.type === "thinking") {
424
- if (isSameModel && block.thinkingSignature) return block;
425
- if (!block.thinking || block.thinking.trim() === "") return [];
426
- if (isSameModel) return block;
427
- return {
428
- type: "text",
429
- text: block.thinking
430
- };
431
- }
432
- if (block.type === "text") {
433
- if (isSameModel) return block;
434
- return {
435
- type: "text",
436
- text: block.text
437
- };
438
- }
439
- if (block.type === "toolCall") {
440
- const toolCall = block;
441
- let normalizedToolCall = toolCall;
442
- if (!isSameModel && toolCall.thoughtSignature) {
443
- normalizedToolCall = { ...toolCall };
444
- delete normalizedToolCall.thoughtSignature;
445
- }
446
- const normalizedID = toolCallIDMap.get(block.callID);
447
- if (normalizedID && normalizedID !== block.callID) normalizedToolCall = {
448
- ...normalizedToolCall,
449
- callID: normalizedID
450
- };
451
- else if (!isSameModel && normalizeToolCallId) {
452
- const nextID = normalizeToolCallId(toolCall.callID, model, assistantMsg);
453
- if (nextID !== toolCall.callID) {
454
- toolCallIDMap.set(toolCall.callID, nextID);
455
- normalizedToolCall = {
456
- ...normalizedToolCall,
457
- callID: nextID
458
- };
459
- }
460
- }
461
- return normalizedToolCall;
462
- }
463
- return block;
387
+ _Stream.adjustMaxTokensForThinking = adjustMaxTokensForThinking;
388
+ const protocolProviderRegistry = /* @__PURE__ */ new Map();
389
+ function wrapStream(protocol, stream) {
390
+ return (model, context, options) => {
391
+ if (model.protocol !== protocol) throw new ProtocolMismatchError({
392
+ actual: model.protocol,
393
+ expected: protocol
464
394
  });
465
- transformed.push({
466
- ...assistantMsg,
467
- parts
395
+ return stream(model, context, options);
396
+ };
397
+ }
398
+ function wrapStreamSimple(protocol, streamSimple) {
399
+ return (model, context, options) => {
400
+ if (model.protocol !== protocol) throw new ProtocolMismatchError({
401
+ actual: model.protocol,
402
+ expected: protocol
468
403
  });
469
- }
470
- return transformed;
404
+ return streamSimple(model, context, options);
405
+ };
471
406
  }
472
- _Message.transformMessages = transformMessages;
473
- })(Message || (Message = {}));
407
+ function registerProtocolProvider(provider, sourceId) {
408
+ protocolProviderRegistry.set(provider.protocol, {
409
+ provider: {
410
+ protocol: provider.protocol,
411
+ stream: wrapStream(provider.protocol, provider.stream),
412
+ streamSimple: wrapStreamSimple(provider.protocol, provider.streamSimple)
413
+ },
414
+ sourceId
415
+ });
416
+ }
417
+ _Stream.registerProtocolProvider = registerProtocolProvider;
418
+ function getProtocolProvider(protocol) {
419
+ return protocolProviderRegistry.get(protocol)?.provider;
420
+ }
421
+ _Stream.getProtocolProvider = getProtocolProvider;
422
+ function resolveProtocolProvider(model) {
423
+ const provider = getProtocolProvider(model.protocol);
424
+ if (!provider) throw new ProtocolProviderNotFoundError({ protocol: model.protocol });
425
+ return provider;
426
+ }
427
+ _Stream.resolveProtocolProvider = resolveProtocolProvider;
428
+ function stream(model, context, options) {
429
+ return resolveProtocolProvider(model).stream(model, context, options);
430
+ }
431
+ _Stream.stream = stream;
432
+ async function complete(model, context, options) {
433
+ return stream(model, context, options).result();
434
+ }
435
+ _Stream.complete = complete;
436
+ function streamSimple(model, context, options) {
437
+ return resolveProtocolProvider(model).streamSimple(model, context, options);
438
+ }
439
+ _Stream.streamSimple = streamSimple;
440
+ async function completeSimple(model, context, options) {
441
+ return streamSimple(model, context, options).result();
442
+ }
443
+ _Stream.completeSimple = completeSimple;
444
+ function getApiProviders() {
445
+ return Array.from(protocolProviderRegistry.values(), (entry) => entry.provider);
446
+ }
447
+ _Stream.getApiProviders = getApiProviders;
448
+ function unregisterProtocolProviders(sourceId) {
449
+ for (const [api, entry] of protocolProviderRegistry.entries()) if (entry.sourceId === sourceId) protocolProviderRegistry.delete(api);
450
+ }
451
+ _Stream.unregisterProtocolProviders = unregisterProtocolProviders;
452
+ function clearProtocolProviders() {
453
+ protocolProviderRegistry.clear();
454
+ }
455
+ _Stream.clearProtocolProviders = clearProtocolProviders;
456
+ })(Stream || (Stream = {}));
474
457
  //#endregion
475
458
  //#region src/utils/eventstream.ts
476
459
  var EventStream = class {
@@ -530,1306 +513,2020 @@ var AssistantMessageEventStream = class extends EventStream {
530
513
  }
531
514
  };
532
515
  //#endregion
533
- //#region src/utils/jsonparse.ts
534
- /**
535
- * @description Helper function to parse partial jsons during LLM streaming.
536
- */
516
+ //#region src/utils/validation.ts
517
+ const ajv = new Ajv({
518
+ allErrors: true,
519
+ strict: false,
520
+ coerceTypes: true
521
+ });
522
+ addFormats(ajv);
537
523
  /**
538
- * Attempts to parse potentially incomplete JSON during streaming.
539
- * Always returns a valid object, even if the JSON is incomplete.
540
- *
541
- * @param partialJson The partial JSON string from streaming
542
- * @returns Parsed object or empty object if parsing fails
524
+ * Validates an arbitrary value against a TypeBox schema and returns the coerced value.
543
525
  */
544
- function parseStreamingJson(partialJson) {
545
- if (!partialJson || partialJson.trim() === "") return {};
546
- try {
547
- return JSON.parse(partialJson);
548
- } catch {
549
- try {
550
- return parse(partialJson) ?? {};
551
- } catch {
552
- return {};
553
- }
554
- }
526
+ function validateSchema(schema, value, label) {
527
+ const validate = ajv.compile(schema);
528
+ const input = structuredClone(value);
529
+ if (validate(input)) return input;
530
+ const errors = validate.errors?.map((err) => {
531
+ return ` - ${err.instancePath ? err.instancePath.substring(1) : err.params?.missingProperty || "root"}: ${err.message}`;
532
+ }).join("\n") || "Unknown Validation Error";
533
+ throw new Error([
534
+ `Validation Failed For ${label}`,
535
+ `${errors}\n`,
536
+ "Received Value:",
537
+ `${JSON.stringify(value, null, 2)}\n`
538
+ ].join("\n"));
555
539
  }
556
- //#endregion
557
- //#region src/utils/sanitize.ts
558
540
  /**
559
- * Removes unpaired Unicode surrogate characters from a string.
560
- *
561
- * Unpaired surrogates (high surrogates 0xD800-0xDBFF without matching low surrogates 0xDC00-0xDFFF,
562
- * or vice versa) cause JSON serialization errors in many API providers.
563
- *
564
- * Valid emoji and other characters outside the Basic Multilingual Plane use properly paired
565
- * surrogates and will NOT be affected by this function.
566
- *
567
- * @param text - The text to sanitize
568
- * @returns The sanitized text with unpaired surrogates removed
569
- *
570
- * @example
571
- * // Valid emoji (properly paired surrogates) are preserved
572
- * sanitizeSurrogates("Hello 🙈 World") // => "Hello 🙈 World"
573
- *
574
- * // Unpaired high surrogate is removed
575
- * const unpaired = String.fromCharCode(0xD83D); // high surrogate without low
576
- * sanitizeSurrogates(`Text ${unpaired} here`) // => "Text here"
541
+ * Validates tool call arguments against the tool's TypeBox schema
577
542
  */
578
- function sanitizeSurrogates(text) {
579
- return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
543
+ function validateToolArguments(tool, toolExecution) {
544
+ return validateSchema(tool.parameters, toolExecution.rawArgs, `Tool "${toolExecution.name}"`);
580
545
  }
581
546
  //#endregion
582
- //#region src/provider/stream.ts
583
- let Stream;
584
- (function(_Stream) {
585
- const ProtocolProviderNotFoundError = _Stream.ProtocolProviderNotFoundError = NamedError.create("ProtocolProviderNotFoundError", Type.Object({ protocol: Model.KnownProtocolSchema }));
586
- const ProtocolMismatchError = _Stream.ProtocolMismatchError = NamedError.create("ProtocolMismatchError", Type.Object({
587
- actual: Model.KnownProtocolSchema,
588
- expected: Model.KnownProtocolSchema
589
- }));
590
- const ThinkingLevelEnum = _Stream.ThinkingLevelEnum = {
591
- off: "off",
592
- minimal: "minimal",
593
- low: "low",
594
- medium: "medium",
595
- high: "high",
596
- xhigh: "xhigh"
597
- };
598
- const ThinkingLevelSchema = _Stream.ThinkingLevelSchema = Type.Union([
599
- Type.Literal(ThinkingLevelEnum.off),
600
- Type.Literal(ThinkingLevelEnum.minimal),
601
- Type.Literal(ThinkingLevelEnum.low),
602
- Type.Literal(ThinkingLevelEnum.medium),
603
- Type.Literal(ThinkingLevelEnum.high),
604
- Type.Literal(ThinkingLevelEnum.xhigh)
605
- ]);
606
- const ThinkingBudgetsSchema = _Stream.ThinkingBudgetsSchema = Type.Object({
607
- off: Type.Optional(Type.Number()),
608
- minimal: Type.Optional(Type.Number()),
609
- low: Type.Optional(Type.Number()),
610
- medium: Type.Optional(Type.Number()),
611
- high: Type.Optional(Type.Number())
612
- });
613
- const CacheRetentionSchema = _Stream.CacheRetentionSchema = Type.Union([
614
- Type.Literal("none"),
615
- Type.Literal("short"),
616
- Type.Literal("long")
617
- ]);
618
- const TransportSchema = _Stream.TransportSchema = Type.Union([
619
- Type.Literal("sse"),
620
- Type.Literal("websocket"),
621
- Type.Literal("auto")
622
- ]);
623
- const OptionsSchema = _Stream.OptionsSchema = Type.Object({
624
- temperature: Type.Optional(Type.Number()),
625
- maxTokens: Type.Optional(Type.Number()),
626
- signal: Type.Optional(Type.Unsafe()),
627
- apiKey: Type.Optional(Type.String()),
628
- transport: Type.Optional(TransportSchema),
629
- cacheRetention: Type.Optional(CacheRetentionSchema),
630
- sessionId: Type.Optional(Type.String()),
631
- onPayload: Type.Optional(Type.Unsafe()),
632
- headers: Type.Optional(Type.Record(Type.String(), Type.String())),
633
- maxRetryDelayMs: Type.Optional(Type.Number()),
634
- metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
635
- });
636
- _Stream.SimpleOptionsSchema = Type.Composite([OptionsSchema, Type.Object({
637
- reasoning: Type.Optional(ThinkingLevelSchema),
638
- thinkingBudgets: Type.Optional(ThinkingBudgetsSchema)
639
- })]);
640
- function buildBaseOptions(model, options, apiKey) {
641
- return {
642
- temperature: options?.temperature,
643
- maxTokens: options?.maxTokens || Math.min(model.maxTokens, 32e3),
644
- signal: options?.signal,
645
- apiKey: apiKey || options?.apiKey,
646
- transport: options?.transport,
647
- cacheRetention: options?.cacheRetention,
648
- sessionId: options?.sessionId,
649
- headers: options?.headers,
650
- onPayload: options?.onPayload,
651
- maxRetryDelayMs: options?.maxRetryDelayMs,
652
- metadata: options?.metadata
653
- };
547
+ //#region src/agent/loop.ts
548
+ let Loop;
549
+ (function(_Loop) {
550
+ function createAgentStream() {
551
+ return new EventStream((event) => event.type === "agent.end", (event) => event.type === "agent.end" ? event.messages : []);
654
552
  }
655
- _Stream.buildBaseOptions = buildBaseOptions;
656
- function clampReasoning(effort) {
657
- return effort === ThinkingLevelEnum.xhigh ? ThinkingLevelEnum.high : effort;
553
+ const AgentContextError = _Loop.AgentContextError = NamedError.create("AgentContextError", Type.Object({ message: Type.String() }));
554
+ function snapshotAssistantMessage(message) {
555
+ return structuredClone(message);
658
556
  }
659
- _Stream.clampReasoning = clampReasoning;
660
- function adjustMaxTokensForThinking(baseMaxTokens, modelMaxTokens, reasoningLevel, customBudgets) {
661
- const budgets = {
662
- minimal: 1024,
663
- low: 2048,
664
- medium: 8192,
665
- high: 16384,
666
- ...customBudgets
667
- };
668
- const minOutputTokens = 1024;
669
- let thinkingBudget = budgets[clampReasoning(reasoningLevel)];
670
- const maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
671
- if (maxTokens <= thinkingBudget) thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
557
+ function snapshotAssistantPartEvent(message, partIndex) {
558
+ const snapshot = snapshotAssistantMessage(message);
559
+ const part = snapshot.parts[partIndex];
560
+ if (!part) throw new Error(`Assistant message part at index ${partIndex} was not found`);
672
561
  return {
673
- maxTokens,
674
- thinkingBudget
562
+ message: snapshot,
563
+ part
675
564
  };
676
565
  }
677
- _Stream.adjustMaxTokensForThinking = adjustMaxTokensForThinking;
678
- const protocolProviderRegistry = /* @__PURE__ */ new Map();
679
- function wrapStream(protocol, stream) {
680
- return (model, context, options) => {
681
- if (model.protocol !== protocol) throw new ProtocolMismatchError({
682
- actual: model.protocol,
683
- expected: protocol
566
+ async function emitEvent(emit, event) {
567
+ await emit(event);
568
+ }
569
+ async function streamAssistantResponse(config, context, emit, streamFn, signal) {
570
+ let messages = context.messages;
571
+ if (config.transformContext) messages = await config.transformContext(messages, signal);
572
+ const llmMessages = await config.convertToLlm(messages);
573
+ const llmContext = {
574
+ systemPrompt: context.systemPrompt,
575
+ messages: llmMessages,
576
+ tools: context.tools
577
+ };
578
+ const streamFunction = streamFn || Stream.streamSimple;
579
+ const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : void 0) || config.apiKey;
580
+ const events = await streamFunction(config.model, llmContext, {
581
+ ...config,
582
+ apiKey: resolvedApiKey,
583
+ signal
584
+ });
585
+ let partialMessage = null;
586
+ let started = false;
587
+ const ensureMessageStarted = async (message) => {
588
+ if (started) return;
589
+ started = true;
590
+ await emitEvent(emit, {
591
+ type: "message.start",
592
+ message: snapshotAssistantMessage(message)
684
593
  });
685
- return stream(model, context, options);
686
594
  };
687
- }
688
- function wrapStreamSimple(protocol, streamSimple) {
689
- return (model, context, options) => {
690
- if (model.protocol !== protocol) throw new ProtocolMismatchError({
691
- actual: model.protocol,
692
- expected: protocol
595
+ const emitMessageUpdate = async (message) => {
596
+ await emitEvent(emit, {
597
+ type: "message.update",
598
+ message: snapshotAssistantMessage(message)
693
599
  });
694
- return streamSimple(model, context, options);
695
600
  };
696
- }
697
- function registerProtocolProvider(provider, sourceId) {
698
- protocolProviderRegistry.set(provider.protocol, {
699
- provider: {
700
- protocol: provider.protocol,
701
- stream: wrapStream(provider.protocol, provider.stream),
702
- streamSimple: wrapStreamSimple(provider.protocol, provider.streamSimple)
703
- },
704
- sourceId
601
+ const emitPartStart = async (message, partIndex) => {
602
+ await emitEvent(emit, {
603
+ type: "message.part.start",
604
+ partIndex,
605
+ ...snapshotAssistantPartEvent(message, partIndex)
606
+ });
607
+ };
608
+ const emitPartUpdate = async (message, partIndex) => {
609
+ await emitEvent(emit, {
610
+ type: "message.part.update",
611
+ partIndex,
612
+ ...snapshotAssistantPartEvent(message, partIndex),
613
+ source: "llm"
614
+ });
615
+ };
616
+ const emitPartEnd = async (message, partIndex) => {
617
+ await emitEvent(emit, {
618
+ type: "message.part.end",
619
+ partIndex,
620
+ ...snapshotAssistantPartEvent(message, partIndex)
621
+ });
622
+ };
623
+ for await (const event of events) switch (event.type) {
624
+ case "start":
625
+ partialMessage = event.partial;
626
+ await ensureMessageStarted(partialMessage);
627
+ break;
628
+ case "text.start":
629
+ case "thinking.start":
630
+ case "toolcall.start":
631
+ partialMessage = event.partial;
632
+ await ensureMessageStarted(partialMessage);
633
+ await emitPartStart(partialMessage, event.partIndex);
634
+ break;
635
+ case "text.delta":
636
+ case "thinking.delta":
637
+ case "toolcall.delta":
638
+ partialMessage = event.partial;
639
+ await ensureMessageStarted(partialMessage);
640
+ await emitPartUpdate(partialMessage, event.partIndex);
641
+ break;
642
+ case "text.end":
643
+ case "thinking.end":
644
+ case "toolcall.end":
645
+ partialMessage = event.partial;
646
+ await ensureMessageStarted(partialMessage);
647
+ await emitPartEnd(partialMessage, event.partIndex);
648
+ await emitMessageUpdate(partialMessage);
649
+ break;
650
+ case "done":
651
+ await ensureMessageStarted(event.message);
652
+ await emitEvent(emit, {
653
+ type: "message.end",
654
+ message: snapshotAssistantMessage(event.message)
655
+ });
656
+ return event.message;
657
+ case "error":
658
+ await ensureMessageStarted(event.error);
659
+ await emitEvent(emit, {
660
+ type: "message.end",
661
+ message: snapshotAssistantMessage(event.error)
662
+ });
663
+ return event.error;
664
+ }
665
+ const finalMessage = await events.result();
666
+ await ensureMessageStarted(finalMessage);
667
+ await emitEvent(emit, {
668
+ type: "message.end",
669
+ message: snapshotAssistantMessage(finalMessage)
705
670
  });
671
+ return finalMessage;
706
672
  }
707
- _Stream.registerProtocolProvider = registerProtocolProvider;
708
- function getProtocolProvider(protocol) {
709
- return protocolProviderRegistry.get(protocol)?.provider;
710
- }
711
- _Stream.getProtocolProvider = getProtocolProvider;
712
- function resolveProtocolProvider(model) {
713
- const provider = getProtocolProvider(model.protocol);
714
- if (!provider) throw new ProtocolProviderNotFoundError({ protocol: model.protocol });
715
- return provider;
716
- }
717
- _Stream.resolveProtocolProvider = resolveProtocolProvider;
718
- function stream(model, context, options) {
719
- return resolveProtocolProvider(model).stream(model, context, options);
720
- }
721
- _Stream.stream = stream;
722
- async function complete(model, context, options) {
723
- return stream(model, context, options).result();
724
- }
725
- _Stream.complete = complete;
726
- function streamSimple(model, context, options) {
727
- return resolveProtocolProvider(model).streamSimple(model, context, options);
728
- }
729
- _Stream.streamSimple = streamSimple;
730
- async function completeSimple(model, context, options) {
731
- return streamSimple(model, context, options).result();
732
- }
733
- _Stream.completeSimple = completeSimple;
734
- function getApiProviders() {
735
- return Array.from(protocolProviderRegistry.values(), (entry) => entry.provider);
736
- }
737
- _Stream.getApiProviders = getApiProviders;
738
- function unregisterProtocolProviders(sourceId) {
739
- for (const [api, entry] of protocolProviderRegistry.entries()) if (entry.sourceId === sourceId) protocolProviderRegistry.delete(api);
673
+ async function executeToolCalls(config, currentContext, message, toolCalls, emit, signal) {
674
+ if (config.toolExecution === "sequential") return executeToolCallsSequential(config, currentContext, message, toolCalls, emit, signal);
675
+ return executeToolCallsParallel(config, currentContext, message, toolCalls, emit, signal);
740
676
  }
741
- _Stream.unregisterProtocolProviders = unregisterProtocolProviders;
742
- function clearProtocolProviders() {
743
- protocolProviderRegistry.clear();
677
+ async function executeToolCallsSequential(config, currentContext, message, toolCalls, emit, signal) {
678
+ for (const toolCall of toolCalls) {
679
+ const toolCallInFlight = {
680
+ callID: toolCall.callID,
681
+ name: toolCall.name,
682
+ rawArgs: toolCall.arguments
683
+ };
684
+ await emitEvent(emit, {
685
+ type: "tool.execution.start",
686
+ ...toolCallInFlight
687
+ });
688
+ const prepared = await prepareToolCall(config, currentContext, message, toolCallInFlight, signal);
689
+ if (prepared.error) {
690
+ const error = {
691
+ status: "error",
692
+ result: {
693
+ content: [{
694
+ type: "text",
695
+ text: prepared.error.message
696
+ }],
697
+ details: prepared.error.details,
698
+ isError: true
699
+ }
700
+ };
701
+ await emitToolCallTerminalResult(toolCall, prepared.runnable, message, emit, error);
702
+ } else {
703
+ const runnable = {
704
+ toolCall,
705
+ runnable: prepared.runnable,
706
+ tool: prepared.tool
707
+ };
708
+ const executed = await executeRunnableToolCall(runnable, message, emit, signal);
709
+ if (executed.error) {
710
+ const error = {
711
+ status: "error",
712
+ result: {
713
+ content: [{
714
+ type: "text",
715
+ text: executed.error.message
716
+ }],
717
+ details: executed.error.details,
718
+ isError: true
719
+ }
720
+ };
721
+ await emitToolCallTerminalResult(runnable.toolCall, runnable.runnable, message, emit, error);
722
+ } else await finalizeExecutedToolCall(config, currentContext, runnable.toolCall, runnable.runnable, message, emit, executed.result);
723
+ }
724
+ }
744
725
  }
745
- _Stream.clearProtocolProviders = clearProtocolProviders;
746
- })(Stream || (Stream = {}));
747
- //#endregion
748
- //#region src/provider/providers/anthropic/index.ts
749
- function getEnvApiKey(_provider) {}
750
- function resolveCacheRetention(cacheRetention) {
751
- if (cacheRetention) return cacheRetention;
752
- if (typeof process !== "undefined" && process.env.CODEWORK_CACHE_RETENTION === "long") return "long";
753
- return "short";
754
- }
755
- function getCacheControl(baseUrl, cacheRetention) {
756
- const retention = resolveCacheRetention(cacheRetention);
757
- if (retention === "none") return { retention };
758
- const ttl = retention === "long" && baseUrl.includes("api.anthropic.com") ? "1h" : void 0;
759
- return {
760
- retention,
761
- cacheControl: {
762
- type: "ephemeral",
763
- ...ttl && { ttl }
726
+ async function executeToolCallsParallel(config, currentContext, message, toolCalls, emit, signal) {
727
+ const runnables = [];
728
+ for (const toolCall of toolCalls) {
729
+ const toolCallInFlight = {
730
+ callID: toolCall.callID,
731
+ name: toolCall.name,
732
+ rawArgs: toolCall.arguments
733
+ };
734
+ await emitEvent(emit, {
735
+ type: "tool.execution.start",
736
+ ...toolCallInFlight
737
+ });
738
+ const prepared = await prepareToolCall(config, currentContext, message, toolCallInFlight, signal);
739
+ if (prepared.error) {
740
+ const error = {
741
+ status: "error",
742
+ result: {
743
+ content: [{
744
+ type: "text",
745
+ text: prepared.error.message
746
+ }],
747
+ details: prepared.error.details,
748
+ isError: true
749
+ }
750
+ };
751
+ await emitToolCallTerminalResult(toolCall, prepared.runnable, message, emit, error);
752
+ } else runnables.push({
753
+ toolCall,
754
+ runnable: prepared.runnable,
755
+ tool: prepared.tool
756
+ });
757
+ }
758
+ const runningExecutions = runnables.map((runnable) => ({
759
+ runnable,
760
+ execution: executeRunnableToolCall(runnable, message, emit, signal)
761
+ }));
762
+ for (const running of runningExecutions) {
763
+ const executed = await running.execution;
764
+ const { runnable } = running;
765
+ if (executed.error) {
766
+ const error = {
767
+ status: "error",
768
+ result: {
769
+ content: [{
770
+ type: "text",
771
+ text: executed.error.message
772
+ }],
773
+ details: executed.error.details,
774
+ isError: true
775
+ }
776
+ };
777
+ await emitToolCallTerminalResult(runnable.toolCall, runnable.runnable, message, emit, error);
778
+ } else await finalizeExecutedToolCall(config, currentContext, runnable.toolCall, runnable.runnable, message, emit, executed.result);
764
779
  }
765
- };
766
- }
767
- const claudeCodeVersion = "2.1.2";
768
- const ccToolLookup = new Map([
769
- "Read",
770
- "Write",
771
- "Edit",
772
- "Bash",
773
- "Grep",
774
- "Glob",
775
- "AskUserQuestion",
776
- "EnterPlanMode",
777
- "ExitPlanMode",
778
- "KillShell",
779
- "NotebookEdit",
780
- "Skill",
781
- "Task",
782
- "TaskOutput",
783
- "TodoWrite",
784
- "WebFetch",
785
- "WebSearch"
786
- ].map((t) => [t.toLowerCase(), t]));
787
- const toClaudeCodeName = (name) => ccToolLookup.get(name.toLowerCase()) ?? name;
788
- const fromClaudeCodeName = (name, tools) => {
789
- if (tools && tools.length > 0) {
790
- const lowerName = name.toLowerCase();
791
- const matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);
792
- if (matchedTool) return matchedTool.name;
793
780
  }
794
- return name;
795
- };
796
- function convertContentBlocks(content) {
797
- if (!content.some((c) => c.type === "image")) return sanitizeSurrogates(content.map((c) => c.text).join("\n"));
798
- const blocks = content.map((block) => {
799
- if (block.type === "text") return {
800
- type: "text",
801
- text: sanitizeSurrogates(block.text)
781
+ async function prepareToolCall(config, currentContext, assistantMessage, toolCallInFlight, signal) {
782
+ const tool = currentContext.tools?.find((t) => t.name === toolCallInFlight.name);
783
+ if (!tool) return {
784
+ error: {
785
+ kind: "error",
786
+ message: `Tool ${toolCallInFlight.name} Not Found`
787
+ },
788
+ runnable: toolCallInFlight
802
789
  };
803
- return {
804
- type: "image",
805
- source: {
806
- type: "base64",
807
- media_type: block.mimeType,
808
- data: block.data
790
+ try {
791
+ toolCallInFlight.args = validateToolArguments(tool, toolCallInFlight);
792
+ if (config.beforeToolExecution) {
793
+ const beforeResult = await config.beforeToolExecution({
794
+ context: currentContext,
795
+ assistantMessage,
796
+ toolCall: toolCallInFlight
797
+ }, signal);
798
+ if (beforeResult?.block) return {
799
+ error: {
800
+ kind: "blocked",
801
+ message: beforeResult?.reason || "Tool Execution was Blocked",
802
+ details: beforeResult?.details
803
+ },
804
+ runnable: toolCallInFlight
805
+ };
809
806
  }
810
- };
811
- });
812
- if (!blocks.some((b) => b.type === "text")) blocks.unshift({
813
- type: "text",
814
- text: "(see attached image)"
815
- });
816
- return blocks;
817
- }
818
- function mergeHeaders(...headerSources) {
819
- const merged = {};
820
- for (const headers of headerSources) if (headers) Object.assign(merged, headers);
821
- return merged;
822
- }
823
- function isOAuthToken(apiKey) {
824
- return apiKey.includes("sk-ant-oat");
825
- }
826
- function createClient(model, apiKey, interleavedThinking, optionsHeaders) {
827
- const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
828
- if (interleavedThinking) betaFeatures.push("interleaved-thinking-2025-05-14");
829
- if (isOAuthToken(apiKey)) {
830
- const defaultHeaders = mergeHeaders({
831
- accept: "application/json",
832
- "anthropic-dangerous-direct-browser-access": "true",
833
- "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}`,
834
- "user-agent": `claude-cli/${claudeCodeVersion} (external, cli)`,
835
- "x-app": "cli"
836
- }, model.headers, optionsHeaders);
837
- return {
838
- client: new Anthropic({
839
- apiKey: null,
840
- authToken: apiKey,
841
- baseURL: model.baseUrl,
842
- defaultHeaders,
843
- dangerouslyAllowBrowser: true
844
- }),
845
- isOAuthToken: true
846
- };
847
- }
848
- const defaultHeaders = mergeHeaders({
849
- accept: "application/json",
850
- "anthropic-dangerous-direct-browser-access": "true",
851
- "anthropic-beta": betaFeatures.join(",")
852
- }, model.headers, optionsHeaders);
853
- return {
854
- client: new Anthropic({
855
- apiKey,
856
- baseURL: model.baseUrl,
857
- dangerouslyAllowBrowser: true,
858
- defaultHeaders
859
- }),
860
- isOAuthToken: false
861
- };
862
- }
863
- function normalizeToolCallId(id) {
864
- return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
865
- }
866
- function assertReplayableAssistant(message) {
867
- let sawToolCall = false;
868
- let sawNonTerminalToolCall = false;
869
- let sawTerminalToolCall = false;
870
- for (const part of message.parts) {
871
- if (part.type === "image") throw new Error("Anthropic replay does not support assistant image parts");
872
- if (part.type === "toolCall") {
873
- sawToolCall = true;
874
- if (part.status === "pending" || part.status === "running") sawNonTerminalToolCall = true;
875
- else sawTerminalToolCall = true;
876
- continue;
807
+ return {
808
+ runnable: toolCallInFlight,
809
+ tool
810
+ };
811
+ } catch (err) {
812
+ return {
813
+ error: {
814
+ kind: "error",
815
+ message: err instanceof Error ? err.message : String(err)
816
+ },
817
+ runnable: toolCallInFlight
818
+ };
877
819
  }
878
- if (sawToolCall) throw new Error("Assistant parts after a toolCall are not replayable for Anthropic");
879
820
  }
880
- if (sawToolCall && message.stopReason !== "toolUse") throw new Error("Assistant message with tool calls must have stopReason 'toolUse'");
881
- if (!sawToolCall && message.stopReason === "toolUse") throw new Error("Assistant message with stopReason 'toolUse' must contain at least one toolCall");
882
- if (sawTerminalToolCall && sawNonTerminalToolCall) throw new Error("Assistant message cannot mix terminal and non-terminal toolCall states");
883
- }
884
- function buildSyntheticToolResult(block) {
885
- return {
886
- type: "tool_result",
887
- tool_use_id: block.callID,
888
- content: convertContentBlocks([{
889
- type: "text",
890
- text: [
891
- "<result>",
892
- "No Result Provided",
893
- "</result>"
894
- ].join("\n")
895
- }]),
896
- is_error: true
897
- };
898
- }
899
- function convertMessages(messages, model, isOAuthToken, cacheControl) {
900
- const params = [];
901
- const transformedMessages = Message.transformMessages(messages, model, normalizeToolCallId);
902
- for (const msg of transformedMessages) {
903
- if (msg.role === "user") {
904
- const blocks = msg.parts.map((item) => {
905
- if (item.type === "text") return {
906
- type: "text",
907
- text: sanitizeSurrogates(item.text)
908
- };
909
- return {
910
- type: "image",
911
- source: {
912
- type: "base64",
913
- media_type: item.mimeType,
914
- data: item.data
915
- }
821
+ async function executeRunnableToolCall(toolCallRunnable, message, emit, signal) {
822
+ const { toolCall, runnable, tool } = toolCallRunnable;
823
+ const { partIndex, ...pendingPart } = toolCall;
824
+ try {
825
+ return { result: await tool.execute(runnable.callID, runnable.args, signal, async (result) => {
826
+ const runningResult = {
827
+ status: "running",
828
+ partial: result.partial
916
829
  };
917
- });
918
- let filteredBlocks = !model.input.includes("image") ? blocks.filter((b) => b.type !== "image") : blocks;
919
- filteredBlocks = filteredBlocks.filter((b) => b.type === "text" ? b.text.trim().length > 0 : true);
920
- if (filteredBlocks.length === 0) continue;
921
- params.push({
922
- role: "user",
923
- content: filteredBlocks
924
- });
925
- continue;
926
- }
927
- if (msg.stopReason === "error" || msg.stopReason === "aborted") continue;
928
- assertReplayableAssistant(msg);
929
- const assistantBlocks = [];
930
- const toolResults = [];
931
- for (const block of msg.parts) if (block.type === "text") {
932
- if (block.text.trim().length === 0) continue;
933
- assistantBlocks.push({
934
- type: "text",
935
- text: sanitizeSurrogates(block.text)
936
- });
937
- } else if (block.type === "thinking") {
938
- if (block.thinking.trim().length === 0) continue;
939
- if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) assistantBlocks.push({
940
- type: "text",
941
- text: sanitizeSurrogates(block.thinking)
942
- });
943
- else assistantBlocks.push({
944
- type: "thinking",
945
- thinking: sanitizeSurrogates(block.thinking),
946
- signature: block.thinkingSignature
947
- });
948
- } else if (block.type === "toolCall") {
949
- assistantBlocks.push({
950
- type: "tool_use",
951
- id: block.callID,
952
- name: isOAuthToken ? toClaudeCodeName(block.name) : block.name,
953
- input: block.arguments ?? {}
954
- });
955
- if (block.status === "pending" || block.status === "running") toolResults.push(buildSyntheticToolResult(block));
956
- else toolResults.push({
957
- type: "tool_result",
958
- tool_use_id: block.callID,
959
- content: convertContentBlocks(block.result.content),
960
- is_error: block.result.isError
961
- });
830
+ await emitEvent(emit, {
831
+ type: "tool.execution.update",
832
+ ...runnable,
833
+ ...runningResult
834
+ });
835
+ const runningPart = {
836
+ ...pendingPart,
837
+ ...runningResult
838
+ };
839
+ message.parts[partIndex] = runningPart;
840
+ await emitEvent(emit, {
841
+ type: "message.part.update",
842
+ message,
843
+ partIndex,
844
+ part: runningPart,
845
+ source: "tool"
846
+ });
847
+ }) };
848
+ } catch (err) {
849
+ return { error: { message: err instanceof Error ? err.message : String(err) } };
962
850
  }
963
- if (assistantBlocks.length > 0) params.push({
964
- role: "assistant",
965
- content: assistantBlocks
851
+ }
852
+ async function emitToolCallTerminalResult(toolCall, runnable, message, emit, result) {
853
+ const { partIndex, ...pendingPart } = toolCall;
854
+ const { time } = pendingPart;
855
+ await emitEvent(emit, {
856
+ type: "tool.execution.end",
857
+ ...runnable,
858
+ ...result
966
859
  });
967
- if (toolResults.length > 0) params.push({
968
- role: "user",
969
- content: toolResults
860
+ const part = {
861
+ ...pendingPart,
862
+ ...result,
863
+ time: {
864
+ start: time.start,
865
+ end: Date.now()
866
+ }
867
+ };
868
+ message.parts[partIndex] = part;
869
+ await emitEvent(emit, {
870
+ type: "message.part.start",
871
+ message,
872
+ partIndex,
873
+ part
874
+ });
875
+ await emitEvent(emit, {
876
+ type: "message.part.end",
877
+ message,
878
+ partIndex,
879
+ part
880
+ });
881
+ await emitEvent(emit, {
882
+ type: "message.update",
883
+ message
970
884
  });
971
885
  }
972
- if (cacheControl && params.length > 0) {
973
- const lastMessage = params[params.length - 1];
974
- if (lastMessage.role === "user") {
975
- if (Array.isArray(lastMessage.content)) {
976
- const lastBlock = lastMessage.content[lastMessage.content.length - 1];
977
- if (lastBlock && (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")) lastBlock.cache_control = cacheControl;
978
- } else if (typeof lastMessage.content === "string") lastMessage.content = [{
979
- type: "text",
980
- text: lastMessage.content,
981
- cache_control: cacheControl
982
- }];
886
+ async function finalizeExecutedToolCall(config, currentContext, toolCall, runnable, message, emit, result, signal) {
887
+ if (config.afterToolExecution) {
888
+ const afterResult = await config.afterToolExecution({
889
+ context: currentContext,
890
+ assistantMessage: message,
891
+ toolCall: runnable,
892
+ result
893
+ }, signal);
894
+ if (afterResult) {
895
+ await emitToolCallTerminalResult(toolCall, runnable, message, emit, afterResult);
896
+ return;
897
+ }
983
898
  }
899
+ await emitToolCallTerminalResult(toolCall, runnable, message, emit, result);
984
900
  }
985
- return params;
986
- }
987
- function convertTools(tools, isOAuthToken) {
988
- if (!tools) return [];
989
- return tools.map((tool) => {
990
- const jsonSchema = tool.parameters;
991
- return {
992
- name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
993
- description: tool.description,
994
- input_schema: {
995
- type: "object",
996
- properties: jsonSchema.properties || {},
997
- required: jsonSchema.required || []
901
+ /**
902
+ * Main core loop shared by run and runContinue.
903
+ */
904
+ async function runLoop(config, currentContext, newMessages, emit, streamFn, signal) {
905
+ let firstTurn = true;
906
+ let pendingMessages = await config.getSteeringMessages?.() || [];
907
+ while (true) {
908
+ let hasMoreToolCalls = true;
909
+ while (hasMoreToolCalls || pendingMessages.length > 0) {
910
+ if (!firstTurn) await emitEvent(emit, { type: "turn.start" });
911
+ else firstTurn = false;
912
+ if (pendingMessages.length > 0) {
913
+ for (const message of pendingMessages) {
914
+ await emitEvent(emit, {
915
+ type: "message.start",
916
+ message
917
+ });
918
+ for (const [partIndex, part] of message.parts.entries()) {
919
+ await emitEvent(emit, {
920
+ type: "message.part.start",
921
+ message,
922
+ partIndex,
923
+ part
924
+ });
925
+ await emitEvent(emit, {
926
+ type: "message.part.end",
927
+ message,
928
+ partIndex,
929
+ part
930
+ });
931
+ await emitEvent(emit, {
932
+ type: "message.update",
933
+ message
934
+ });
935
+ }
936
+ await emitEvent(emit, {
937
+ type: "message.end",
938
+ message
939
+ });
940
+ currentContext.messages.push(message);
941
+ newMessages.push(message);
942
+ }
943
+ pendingMessages = [];
944
+ }
945
+ const message = await streamAssistantResponse(config, currentContext, emit, streamFn, signal);
946
+ if (message.stopReason === "error" || message.stopReason === "aborted") {
947
+ currentContext.messages.push(message);
948
+ newMessages.push(message);
949
+ await emitEvent(emit, {
950
+ type: "turn.end",
951
+ message
952
+ });
953
+ await emitEvent(emit, {
954
+ type: "agent.end",
955
+ messages: newMessages
956
+ });
957
+ return;
958
+ }
959
+ const toolCalls = message.parts.map((part, index) => ({
960
+ ...part,
961
+ partIndex: index
962
+ })).filter((part) => part.type === "toolCall" && part.status === "pending");
963
+ hasMoreToolCalls = toolCalls.length > 0;
964
+ if (hasMoreToolCalls) await executeToolCalls(config, currentContext, message, toolCalls, emit, signal);
965
+ currentContext.messages.push(message);
966
+ newMessages.push(message);
967
+ await emitEvent(emit, {
968
+ type: "turn.end",
969
+ message
970
+ });
971
+ pendingMessages = await config.getSteeringMessages?.() || [];
998
972
  }
999
- };
1000
- });
1001
- }
1002
- function mapStopReason(reason) {
1003
- switch (reason) {
1004
- case "end_turn": return "stop";
1005
- case "max_tokens": return "length";
1006
- case "tool_use": return "toolUse";
1007
- case "pause_turn":
1008
- case "stop_sequence": return "stop";
1009
- case "refusal":
1010
- case "sensitive": return "error";
1011
- default: throw new Error("Unhandled Anthropic stop reason");
1012
- }
1013
- }
1014
- function buildParams(model, context, isOAuthToken, options) {
1015
- const { cacheControl } = getCacheControl(model.baseUrl, options?.cacheRetention);
1016
- const params = {
1017
- model: model.id,
1018
- messages: convertMessages(context.messages, model, isOAuthToken, cacheControl),
1019
- max_tokens: options?.maxTokens || model.maxTokens / 3 | 0,
1020
- stream: true
1021
- };
1022
- if (isOAuthToken) {
1023
- params.system = [{
1024
- type: "text",
1025
- text: "You are Claude Code, Anthropic's official CLI for Claude.",
1026
- ...cacheControl ? { cache_control: cacheControl } : {}
1027
- }];
1028
- if (context.systemPrompt) params.system.push({
1029
- type: "text",
1030
- text: sanitizeSurrogates(context.systemPrompt),
1031
- ...cacheControl ? { cache_control: cacheControl } : {}
973
+ const followUpMessages = await config.getFollowUpMessages?.() || [];
974
+ if (followUpMessages.length > 0) {
975
+ pendingMessages = followUpMessages;
976
+ continue;
977
+ }
978
+ break;
979
+ }
980
+ await emitEvent(emit, {
981
+ type: "agent.end",
982
+ messages: newMessages
1032
983
  });
1033
- } else if (context.systemPrompt) params.system = [{
1034
- type: "text",
1035
- text: sanitizeSurrogates(context.systemPrompt),
1036
- ...cacheControl ? { cache_control: cacheControl } : {}
1037
- }];
1038
- if (options?.temperature !== void 0 && !options?.thinkingEnabled) params.temperature = options.temperature;
1039
- if (context.tools) params.tools = convertTools(context.tools, isOAuthToken);
1040
- if (options?.thinkingEnabled && model.reasoning) params.thinking = {
1041
- type: "enabled",
1042
- budget_tokens: options.thinkingBudgetTokens || 1024
1043
- };
1044
- if (options?.metadata) {
1045
- const userId = options.metadata.user_id;
1046
- if (typeof userId === "string") params.metadata = { user_id: userId };
1047
984
  }
1048
- if (options?.toolChoice) params.tool_choice = typeof options.toolChoice === "string" ? { type: options.toolChoice } : options.toolChoice;
1049
- return params;
1050
- }
1051
- const streamAnthropic = (model, context, options) => {
1052
- const stream = new AssistantMessageEventStream();
1053
- (async () => {
1054
- const output = Message.createAssistantMessage({
1055
- role: "assistant",
1056
- parts: [],
1057
- protocol: model.protocol,
1058
- provider: model.provider,
1059
- model: model.id,
1060
- usage: {
1061
- input: 0,
1062
- output: 0,
1063
- cacheRead: 0,
1064
- cacheWrite: 0,
1065
- totalTokens: 0,
1066
- cost: {
1067
- input: 0,
1068
- output: 0,
1069
- cacheRead: 0,
1070
- cacheWrite: 0,
1071
- total: 0
1072
- }
1073
- },
1074
- stopReason: "stop",
1075
- time: {
1076
- created: Date.now(),
1077
- completed: Date.now()
1078
- }
1079
- });
1080
- try {
1081
- let client;
1082
- let isOAuthToken;
1083
- if (options?.client) {
1084
- client = options.client;
1085
- isOAuthToken = false;
1086
- } else {
1087
- const apiKey = options?.apiKey ?? getEnvApiKey(model.provider);
1088
- if (!apiKey) throw new Error(`No API key for provider: ${model.provider.id}`);
1089
- const created = createClient(model, apiKey, options?.interleavedThinking ?? true, options?.headers);
1090
- client = created.client;
1091
- isOAuthToken = created.isOAuthToken;
1092
- }
1093
- let params = buildParams(model, context, isOAuthToken, options);
1094
- const nextParams = await options?.onPayload?.(params, model);
1095
- if (nextParams !== void 0) params = nextParams;
1096
- const anthropicStream = client.messages.stream({
1097
- ...params,
1098
- stream: true
1099
- }, { signal: options?.signal });
1100
- stream.push({
1101
- type: "start",
1102
- partial: output
985
+ async function runAgentLoop(config, context, prompts, emit, streamFn, signal) {
986
+ /**
987
+ * maintain 2 arrays to represent state, `context` the actual full state sent to the LLM,
988
+ * `newMessages` for intermediate current loop state.
989
+ * think of newMessages as subset of context.messages for the running current loop.
990
+ * they do move at the same speed staring with initial user prompts.
991
+ * */
992
+ const newMessages = [...prompts];
993
+ const currentContext = {
994
+ ...context,
995
+ messages: [...context.messages, ...prompts]
996
+ };
997
+ await emitEvent(emit, { type: "agent.start" });
998
+ await emitEvent(emit, { type: "turn.start" });
999
+ for (const prompt of prompts) {
1000
+ await emitEvent(emit, {
1001
+ type: "message.start",
1002
+ message: prompt
1103
1003
  });
1104
- const blocks = output.parts;
1105
- for await (const event of anthropicStream) if (event.type === "message_start") {
1106
- output.responseId = event.message.id;
1107
- output.usage.input = event.message.usage.input_tokens || 0;
1108
- output.usage.output = event.message.usage.output_tokens || 0;
1109
- output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
1110
- output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
1111
- output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
1112
- Model.calculateCost(model, output.usage);
1113
- } else if (event.type === "content_block_start") {
1114
- if (event.content_block.type === "text") {
1115
- const block = {
1116
- type: "text",
1117
- text: "",
1118
- index: event.index
1119
- };
1120
- output.parts.push(block);
1121
- stream.push({
1122
- type: "text.start",
1123
- partIndex: output.parts.length - 1,
1124
- partial: output
1125
- });
1126
- } else if (event.content_block.type === "thinking") {
1127
- const block = {
1128
- type: "thinking",
1129
- thinking: "",
1130
- thinkingSignature: "",
1131
- index: event.index
1132
- };
1133
- output.parts.push(block);
1134
- stream.push({
1135
- type: "thinking.start",
1136
- partIndex: output.parts.length - 1,
1137
- partial: output
1138
- });
1139
- } else if (event.content_block.type === "redacted_thinking") {
1140
- const block = {
1141
- type: "thinking",
1142
- thinking: "[Reasoning redacted]",
1143
- thinkingSignature: event.content_block.data,
1144
- redacted: true,
1145
- index: event.index
1146
- };
1147
- output.parts.push(block);
1148
- stream.push({
1149
- type: "thinking.start",
1150
- partIndex: output.parts.length - 1,
1151
- partial: output
1152
- });
1153
- } else if (event.content_block.type === "tool_use") {
1154
- const block = {
1155
- type: "toolCall",
1156
- callID: event.content_block.id,
1157
- name: isOAuthToken ? fromClaudeCodeName(event.content_block.name, context.tools) : event.content_block.name,
1158
- arguments: event.content_block.input ?? {},
1159
- partialJson: "",
1160
- status: "pending",
1161
- time: {
1162
- start: Date.now(),
1163
- end: Date.now()
1164
- },
1165
- index: event.index
1166
- };
1167
- output.parts.push(block);
1168
- stream.push({
1169
- type: "toolcall.start",
1170
- partIndex: output.parts.length - 1,
1171
- partial: output
1172
- });
1173
- }
1174
- } else if (event.type === "content_block_delta") {
1175
- const partIndex = blocks.findIndex((b) => b.index === event.index);
1176
- const block = blocks[partIndex];
1177
- if (!block) continue;
1178
- if (event.delta.type === "text_delta" && block.type === "text") {
1179
- block.text += event.delta.text;
1180
- stream.push({
1181
- type: "text.delta",
1182
- partIndex,
1183
- delta: event.delta.text,
1184
- partial: output
1185
- });
1186
- } else if (event.delta.type === "thinking_delta" && block.type === "thinking") {
1187
- block.thinking += event.delta.thinking;
1188
- stream.push({
1189
- type: "thinking.delta",
1190
- partIndex,
1191
- delta: event.delta.thinking,
1192
- partial: output
1193
- });
1194
- } else if (event.delta.type === "input_json_delta" && block.type === "toolCall") {
1195
- block.partialJson += event.delta.partial_json;
1196
- block.arguments = parseStreamingJson(block.partialJson);
1197
- stream.push({
1198
- type: "toolcall.delta",
1199
- partIndex,
1200
- delta: event.delta.partial_json,
1201
- partial: output
1202
- });
1203
- } else if (event.delta.type === "signature_delta" && block.type === "thinking") {
1204
- block.thinkingSignature = block.thinkingSignature || "";
1205
- block.thinkingSignature += event.delta.signature;
1206
- }
1207
- } else if (event.type === "content_block_stop") {
1208
- const partIndex = blocks.findIndex((b) => b.index === event.index);
1209
- const block = blocks[partIndex];
1210
- if (block) {
1211
- delete block.index;
1212
- if (block.type === "text") stream.push({
1213
- type: "text.end",
1214
- partIndex,
1215
- content: block.text,
1216
- partial: output
1217
- });
1218
- else if (block.type === "thinking") stream.push({
1219
- type: "thinking.end",
1220
- partIndex,
1221
- content: block.thinking,
1222
- partial: output
1223
- });
1224
- else if (block.type === "toolCall") {
1225
- block.arguments = parseStreamingJson(block.partialJson);
1226
- delete block.partialJson;
1227
- stream.push({
1228
- type: "toolcall.end",
1229
- partIndex,
1230
- toolCall: block,
1231
- partial: output
1232
- });
1233
- }
1234
- }
1235
- } else if (event.type === "message_delta") {
1236
- if (event.delta.stop_reason) output.stopReason = mapStopReason(event.delta.stop_reason);
1237
- if (event.usage.input_tokens != null) output.usage.input = event.usage.input_tokens;
1238
- if (event.usage.output_tokens != null) output.usage.output = event.usage.output_tokens;
1239
- if (event.usage.cache_read_input_tokens != null) output.usage.cacheRead = event.usage.cache_read_input_tokens;
1240
- if (event.usage.cache_creation_input_tokens != null) output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
1241
- output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
1242
- Model.calculateCost(model, output.usage);
1004
+ for (const [partIndex, part] of prompt.parts.entries()) {
1005
+ await emitEvent(emit, {
1006
+ type: "message.part.start",
1007
+ message: prompt,
1008
+ partIndex,
1009
+ part
1010
+ });
1011
+ await emitEvent(emit, {
1012
+ type: "message.part.end",
1013
+ message: prompt,
1014
+ partIndex,
1015
+ part
1016
+ });
1243
1017
  }
1244
- if (options?.signal?.aborted) throw new Error("Request was aborted");
1245
- if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error("An unknown error occurred");
1246
- output.time.completed = Date.now();
1247
- stream.push({
1248
- type: "done",
1249
- reason: output.stopReason,
1250
- message: output
1251
- });
1252
- stream.end();
1253
- } catch (error) {
1254
- for (const block of output.parts) delete block.index;
1255
- output.time.completed = Date.now();
1256
- output.stopReason = options?.signal?.aborted ? "aborted" : "error";
1257
- output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
1258
- stream.push({
1259
- type: "error",
1260
- reason: output.stopReason,
1261
- error: output
1018
+ await emitEvent(emit, {
1019
+ type: "message.end",
1020
+ message: prompt
1262
1021
  });
1263
- stream.end();
1264
1022
  }
1265
- })();
1266
- return stream;
1267
- };
1268
- const streamSimpleAnthropic = (model, context, options) => {
1269
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
1270
- if (!apiKey) throw new Error(`No API key for provider: ${model.provider.id}`);
1271
- const base = Stream.buildBaseOptions(model, options, apiKey);
1272
- if (!options?.reasoning) return streamAnthropic(model, context, {
1273
- ...base,
1274
- thinkingEnabled: false
1275
- });
1276
- const adjusted = Stream.adjustMaxTokensForThinking(base.maxTokens || 0, model.maxTokens, options.reasoning, options.thinkingBudgets);
1277
- return streamAnthropic(model, context, {
1278
- ...base,
1279
- maxTokens: adjusted.maxTokens,
1280
- thinkingEnabled: true,
1281
- thinkingBudgetTokens: adjusted.thinkingBudget
1282
- });
1283
- };
1284
- //#endregion
1285
- //#region src/provider/register.ts
1286
- const providerModules = [["providers/anthropic/index.ts", {
1287
- protocol: Model.KnownProtocolEnum.anthropicMessages,
1288
- stream: streamAnthropic,
1289
- streamSimple: streamSimpleAnthropic
1290
- }]];
1291
- for (const [path, provider] of providerModules) Stream.registerProtocolProvider(provider, path);
1023
+ await runLoop(config, currentContext, newMessages, emit, streamFn, signal);
1024
+ return newMessages;
1025
+ }
1026
+ _Loop.runAgentLoop = runAgentLoop;
1027
+ async function runAgentLoopContinue(config, context, emit, streamFn, signal) {
1028
+ if (context.messages.length === 0) throw new AgentContextError({ message: "connot continue: no messages in context" });
1029
+ if (context.messages[context.messages.length - 1]?.role === "assistant") throw new AgentContextError({ message: "cannot continue from message role: assistant" });
1030
+ const newMessages = [];
1031
+ const currentContext = { ...context };
1032
+ await emitEvent(emit, { type: "agent.start" });
1033
+ await emitEvent(emit, { type: "turn.start" });
1034
+ await runLoop(config, currentContext, newMessages, emit, streamFn, signal);
1035
+ return newMessages;
1036
+ }
1037
+ _Loop.runAgentLoopContinue = runAgentLoopContinue;
1038
+ function run(config, context, prompts, streamFn, signal) {
1039
+ const stream = createAgentStream();
1040
+ runAgentLoop(config, context, prompts, async (event) => {
1041
+ stream.push(event);
1042
+ }, streamFn, signal).then((messages) => {
1043
+ stream.end(messages);
1044
+ });
1045
+ return stream;
1046
+ }
1047
+ _Loop.run = run;
1048
+ function runContinue(config, context, streamFn, signal) {
1049
+ const stream = createAgentStream();
1050
+ runAgentLoopContinue(config, context, async (event) => {
1051
+ stream.push(event);
1052
+ }, streamFn, signal).then((messages) => {
1053
+ stream.end(messages);
1054
+ });
1055
+ return stream;
1056
+ }
1057
+ _Loop.runContinue = runContinue;
1058
+ })(Loop || (Loop = {}));
1292
1059
  //#endregion
1293
- //#region src/utils/validation.ts
1294
- /**
1295
- * @description Validation powered by ajv, TypeBox natively doesn't provide data validation.
1296
- */
1297
- const ajv = new Ajv({
1298
- allErrors: true,
1299
- strict: false,
1300
- coerceTypes: true
1301
- });
1302
- addFormats(ajv);
1303
- /**
1304
- * Validates tool call arguments against the tool's TypeBox schema
1305
- */
1306
- function validateToolArguments(tool, toolExecution) {
1307
- const validate = ajv.compile(tool.parameters);
1308
- const args = structuredClone(toolExecution.rawArgs);
1309
- if (validate(args)) return args;
1310
- const errors = validate.errors?.map((err) => {
1311
- return ` - ${err.instancePath ? err.instancePath.substring(1) : err.params?.missingProperty || "root"}: ${err.message}`;
1312
- }).join("\n") || "Unknown Validation Error";
1313
- throw new Error([
1314
- `Validation Failed For Tool "${toolExecution.name}"`,
1315
- `${errors}\n`,
1316
- "Received Arguments:",
1317
- `${JSON.stringify(toolExecution.rawArgs, null, 2)}\n`
1318
- ].join("\n"));
1319
- }
1060
+ //#region src/agent.ts
1061
+ const agent = {
1062
+ loop: Loop.run,
1063
+ loopContinue: Loop.runContinue
1064
+ };
1320
1065
  //#endregion
1321
- //#region src/agent/loop.ts
1322
- let Loop;
1323
- (function(_Loop) {
1324
- function createAgentStream() {
1325
- return new EventStream((event) => event.type === "agent.end", (event) => event.type === "agent.end" ? event.messages : []);
1066
+ //#region src/message/message.ts
1067
+ let Message;
1068
+ (function(_Message) {
1069
+ const TextContentSchema = _Message.TextContentSchema = Type.Object({
1070
+ type: Type.Literal("text"),
1071
+ text: Type.String(),
1072
+ textSignature: Type.Optional(Type.String())
1073
+ });
1074
+ const ImageContentSchema = _Message.ImageContentSchema = Type.Object({
1075
+ type: Type.Literal("image"),
1076
+ data: Type.String(),
1077
+ mimeType: Type.String()
1078
+ });
1079
+ const ThinkingContentSchema = _Message.ThinkingContentSchema = Type.Object({
1080
+ type: Type.Literal("thinking"),
1081
+ thinking: Type.String(),
1082
+ thinkingSignature: Type.Optional(Type.String()),
1083
+ redacted: Type.Optional(Type.Boolean())
1084
+ });
1085
+ const ToolCallBaseSchema = _Message.ToolCallBaseSchema = Type.Object({
1086
+ type: Type.Literal("toolCall"),
1087
+ callID: Type.String(),
1088
+ name: Type.String(),
1089
+ arguments: Type.Record(Type.String(), Type.Any()),
1090
+ thoughtSignature: Type.Optional(Type.String()),
1091
+ time: Type.Object({
1092
+ start: Type.Number(),
1093
+ end: Type.Number()
1094
+ })
1095
+ });
1096
+ const ToolSuccessResult = _Message.ToolSuccessResult = Type.Object({
1097
+ content: Type.Array(Type.Union([TextContentSchema, ImageContentSchema])),
1098
+ details: Type.Optional(Type.Any()),
1099
+ isError: Type.Literal(false)
1100
+ });
1101
+ const ToolErrorResult = _Message.ToolErrorResult = Type.Object({
1102
+ content: Type.Array(Type.Union([TextContentSchema, ImageContentSchema])),
1103
+ details: Type.Optional(Type.Any()),
1104
+ isError: Type.Literal(true)
1105
+ });
1106
+ const ToolRunningPartial = _Message.ToolRunningPartial = Type.Object({
1107
+ content: Type.Optional(Type.Array(Type.Union([TextContentSchema, ImageContentSchema]))),
1108
+ details: Type.Optional(Type.Any())
1109
+ });
1110
+ const ToolStatusEnum = _Message.ToolStatusEnum = {
1111
+ pending: "pending",
1112
+ running: "running",
1113
+ completed: "completed",
1114
+ error: "error",
1115
+ skipped: "skipped",
1116
+ aborted: "aborted"
1117
+ };
1118
+ const ToolRunningSchema = _Message.ToolRunningSchema = Type.Object({
1119
+ status: Type.Literal(ToolStatusEnum.running),
1120
+ partial: Type.Optional(ToolRunningPartial)
1121
+ });
1122
+ const ToolCompletedSchema = _Message.ToolCompletedSchema = Type.Object({
1123
+ status: Type.Literal(ToolStatusEnum.completed),
1124
+ result: ToolSuccessResult
1125
+ });
1126
+ const ToolErrorSchema = _Message.ToolErrorSchema = Type.Object({
1127
+ status: Type.Literal(ToolStatusEnum.error),
1128
+ result: ToolErrorResult
1129
+ });
1130
+ const ToolSkippedSchema = _Message.ToolSkippedSchema = Type.Object({
1131
+ status: Type.Literal(ToolStatusEnum.skipped),
1132
+ result: ToolErrorResult
1133
+ });
1134
+ const ToolAbortedSchema = _Message.ToolAbortedSchema = Type.Object({
1135
+ status: Type.Literal(ToolStatusEnum.aborted),
1136
+ result: ToolErrorResult
1137
+ });
1138
+ const ToolCallPendingPart = Type.Intersect([ToolCallBaseSchema, Type.Object({ status: Type.Literal(ToolStatusEnum.pending) })]);
1139
+ const ToolCallRunningPart = Type.Composite([ToolCallBaseSchema, ToolRunningSchema]);
1140
+ const ToolCallCompletedPart = Type.Composite([ToolCallBaseSchema, ToolCompletedSchema]);
1141
+ const ToolCallErrorPart = Type.Composite([ToolCallBaseSchema, ToolErrorSchema]);
1142
+ const ToolCallSkippedPart = Type.Composite([ToolCallBaseSchema, ToolSkippedSchema]);
1143
+ const ToolCallAbortedPart = Type.Composite([ToolCallBaseSchema, ToolAbortedSchema]);
1144
+ const ToolCallSchema = _Message.ToolCallSchema = Type.Union([
1145
+ ToolCallPendingPart,
1146
+ ToolCallRunningPart,
1147
+ ToolCallCompletedPart,
1148
+ ToolCallErrorPart,
1149
+ ToolCallSkippedPart,
1150
+ ToolCallAbortedPart
1151
+ ]);
1152
+ const UsageSchema = _Message.UsageSchema = Type.Object({
1153
+ input: Type.Number(),
1154
+ output: Type.Number(),
1155
+ cacheRead: Type.Number(),
1156
+ cacheWrite: Type.Number(),
1157
+ totalTokens: Type.Number(),
1158
+ cost: Type.Object({
1159
+ input: Type.Number(),
1160
+ output: Type.Number(),
1161
+ cacheRead: Type.Number(),
1162
+ cacheWrite: Type.Number(),
1163
+ total: Type.Number()
1164
+ })
1165
+ });
1166
+ const StopReasonSchema = _Message.StopReasonSchema = Type.Union([
1167
+ Type.Literal("stop"),
1168
+ Type.Literal("length"),
1169
+ Type.Literal("toolUse"),
1170
+ Type.Literal("error"),
1171
+ Type.Literal("aborted")
1172
+ ]);
1173
+ const UserMessageSchema = _Message.UserMessageSchema = Type.Object({
1174
+ messageId: Type.String(),
1175
+ role: Type.Literal("user"),
1176
+ time: Type.Object({ created: Type.Number() }),
1177
+ parts: Type.Array(Type.Union([TextContentSchema, ImageContentSchema]))
1178
+ });
1179
+ const AssistantMessageSchema = _Message.AssistantMessageSchema = Type.Object({
1180
+ role: Type.Literal("assistant"),
1181
+ protocol: Model.KnownProtocolSchema,
1182
+ provider: Provider.Info,
1183
+ model: Type.String(),
1184
+ usage: UsageSchema,
1185
+ stopReason: StopReasonSchema,
1186
+ errorMessage: Type.Optional(Type.String()),
1187
+ time: Type.Object({
1188
+ created: Type.Number(),
1189
+ completed: Type.Number()
1190
+ }),
1191
+ parts: Type.Array(Type.Union([
1192
+ TextContentSchema,
1193
+ ImageContentSchema,
1194
+ ThinkingContentSchema,
1195
+ ToolCallSchema
1196
+ ])),
1197
+ responseId: Type.Optional(Type.String()),
1198
+ messageId: Type.String()
1199
+ });
1200
+ const MessageSchema = _Message.MessageSchema = Type.Union([UserMessageSchema, AssistantMessageSchema]);
1201
+ function createMessageId() {
1202
+ return uuidv7();
1326
1203
  }
1327
- const AgentContextError = _Loop.AgentContextError = NamedError.create("AgentContextError", Type.Object({ message: Type.String() }));
1328
- function snapshotAssistantMessage(message) {
1329
- return structuredClone(message);
1204
+ _Message.createMessageId = createMessageId;
1205
+ function createUserMessage(message) {
1206
+ const { messageId = createMessageId(), ...rest } = message;
1207
+ return {
1208
+ messageId,
1209
+ ...rest
1210
+ };
1330
1211
  }
1331
- function snapshotAssistantPartEvent(message, partIndex) {
1332
- const snapshot = snapshotAssistantMessage(message);
1333
- const part = snapshot.parts[partIndex];
1334
- if (!part) throw new Error(`Assistant message part at index ${partIndex} was not found`);
1212
+ _Message.createUserMessage = createUserMessage;
1213
+ function createAssistantMessage(message) {
1214
+ const { messageId = createMessageId(), ...rest } = message;
1335
1215
  return {
1336
- message: snapshot,
1337
- part
1216
+ messageId,
1217
+ ...rest
1338
1218
  };
1339
1219
  }
1340
- async function emitEvent(emit, event) {
1341
- await emit(event);
1220
+ _Message.createAssistantMessage = createAssistantMessage;
1221
+ const ToolSchema = _Message.ToolSchema = Type.Object({
1222
+ name: Type.String(),
1223
+ description: Type.String(),
1224
+ parameters: Type.Unsafe()
1225
+ });
1226
+ function defineTool(tool) {
1227
+ return tool;
1342
1228
  }
1343
- async function streamAssistantResponse(config, context, emit, streamFn, signal) {
1344
- let messages = context.messages;
1345
- if (config.transformContext) messages = await config.transformContext(messages, signal);
1346
- const llmMessages = await config.convertToLlm(messages);
1347
- const llmContext = {
1348
- systemPrompt: context.systemPrompt,
1349
- messages: llmMessages,
1350
- tools: context.tools
1351
- };
1352
- const streamFunction = streamFn || Stream.streamSimple;
1353
- const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : void 0) || config.apiKey;
1354
- const events = await streamFunction(config.model, llmContext, {
1355
- ...config,
1356
- apiKey: resolvedApiKey,
1357
- signal
1358
- });
1359
- let partialMessage = null;
1360
- let started = false;
1361
- const ensureMessageStarted = async (message) => {
1362
- if (started) return;
1363
- started = true;
1364
- await emitEvent(emit, {
1365
- type: "message.start",
1366
- message: snapshotAssistantMessage(message)
1367
- });
1368
- };
1369
- const emitMessageUpdate = async (message) => {
1370
- await emitEvent(emit, {
1371
- type: "message.update",
1372
- message: snapshotAssistantMessage(message)
1373
- });
1374
- };
1375
- const emitPartStart = async (message, partIndex) => {
1376
- await emitEvent(emit, {
1377
- type: "message.part.start",
1378
- partIndex,
1379
- ...snapshotAssistantPartEvent(message, partIndex)
1380
- });
1381
- };
1382
- const emitPartUpdate = async (message, partIndex) => {
1383
- await emitEvent(emit, {
1384
- type: "message.part.update",
1385
- partIndex,
1386
- ...snapshotAssistantPartEvent(message, partIndex),
1387
- source: "llm"
1229
+ _Message.defineTool = defineTool;
1230
+ _Message.ContextSchema = Type.Object({
1231
+ systemPrompt: Type.Optional(Type.String()),
1232
+ messages: Type.Array(MessageSchema),
1233
+ tools: Type.Optional(Type.Array(ToolSchema))
1234
+ });
1235
+ function transformMessages(messages, model, normalizeToolCallId) {
1236
+ const toolCallIDMap = /* @__PURE__ */ new Map();
1237
+ const transformed = [];
1238
+ for (const msg of messages) {
1239
+ if (msg.role === "user") {
1240
+ transformed.push(msg);
1241
+ continue;
1242
+ }
1243
+ const assistantMsg = msg;
1244
+ const isSameModel = assistantMsg.provider.id === model.provider.id && assistantMsg.protocol === model.protocol && assistantMsg.model === model.id;
1245
+ const parts = assistantMsg.parts.flatMap((block) => {
1246
+ if (block.type === "thinking") {
1247
+ if (isSameModel && block.thinkingSignature) return block;
1248
+ if (!block.thinking || block.thinking.trim() === "") return [];
1249
+ if (isSameModel) return block;
1250
+ return {
1251
+ type: "text",
1252
+ text: block.thinking
1253
+ };
1254
+ }
1255
+ if (block.type === "text") {
1256
+ if (isSameModel) return block;
1257
+ return {
1258
+ type: "text",
1259
+ text: block.text
1260
+ };
1261
+ }
1262
+ if (block.type === "toolCall") {
1263
+ const toolCall = block;
1264
+ let normalizedToolCall = toolCall;
1265
+ if (!isSameModel && toolCall.thoughtSignature) {
1266
+ normalizedToolCall = { ...toolCall };
1267
+ delete normalizedToolCall.thoughtSignature;
1268
+ }
1269
+ const normalizedID = toolCallIDMap.get(block.callID);
1270
+ if (normalizedID && normalizedID !== block.callID) normalizedToolCall = {
1271
+ ...normalizedToolCall,
1272
+ callID: normalizedID
1273
+ };
1274
+ else if (!isSameModel && normalizeToolCallId) {
1275
+ const nextID = normalizeToolCallId(toolCall.callID, model, assistantMsg);
1276
+ if (nextID !== toolCall.callID) {
1277
+ toolCallIDMap.set(toolCall.callID, nextID);
1278
+ normalizedToolCall = {
1279
+ ...normalizedToolCall,
1280
+ callID: nextID
1281
+ };
1282
+ }
1283
+ }
1284
+ return normalizedToolCall;
1285
+ }
1286
+ return block;
1388
1287
  });
1389
- };
1390
- const emitPartEnd = async (message, partIndex) => {
1391
- await emitEvent(emit, {
1392
- type: "message.part.end",
1393
- partIndex,
1394
- ...snapshotAssistantPartEvent(message, partIndex)
1288
+ transformed.push({
1289
+ ...assistantMsg,
1290
+ parts
1395
1291
  });
1396
- };
1397
- for await (const event of events) switch (event.type) {
1398
- case "start":
1399
- partialMessage = event.partial;
1400
- await ensureMessageStarted(partialMessage);
1401
- break;
1402
- case "text.start":
1403
- case "thinking.start":
1404
- case "toolcall.start":
1405
- partialMessage = event.partial;
1406
- await ensureMessageStarted(partialMessage);
1407
- await emitPartStart(partialMessage, event.partIndex);
1408
- break;
1409
- case "text.delta":
1410
- case "thinking.delta":
1411
- case "toolcall.delta":
1412
- partialMessage = event.partial;
1413
- await ensureMessageStarted(partialMessage);
1414
- await emitPartUpdate(partialMessage, event.partIndex);
1415
- break;
1416
- case "text.end":
1417
- case "thinking.end":
1418
- case "toolcall.end":
1419
- partialMessage = event.partial;
1420
- await ensureMessageStarted(partialMessage);
1421
- await emitPartEnd(partialMessage, event.partIndex);
1422
- await emitMessageUpdate(partialMessage);
1423
- break;
1424
- case "done":
1425
- await ensureMessageStarted(event.message);
1426
- await emitEvent(emit, {
1427
- type: "message.end",
1428
- message: snapshotAssistantMessage(event.message)
1429
- });
1430
- return event.message;
1431
- case "error":
1432
- await ensureMessageStarted(event.error);
1433
- await emitEvent(emit, {
1434
- type: "message.end",
1435
- message: snapshotAssistantMessage(event.error)
1436
- });
1437
- return event.error;
1438
1292
  }
1439
- const finalMessage = await events.result();
1440
- await ensureMessageStarted(finalMessage);
1441
- await emitEvent(emit, {
1442
- type: "message.end",
1443
- message: snapshotAssistantMessage(finalMessage)
1444
- });
1445
- return finalMessage;
1446
- }
1447
- async function executeToolCalls(config, currentContext, message, toolCalls, emit, signal) {
1448
- if (config.toolExecution === "sequential") return executeToolCallsSequential(config, currentContext, message, toolCalls, emit, signal);
1449
- return executeToolCallsParallel(config, currentContext, message, toolCalls, emit, signal);
1293
+ return transformed;
1450
1294
  }
1451
- async function executeToolCallsSequential(config, currentContext, message, toolCalls, emit, signal) {
1452
- for (const toolCall of toolCalls) {
1453
- const toolCallInFlight = {
1454
- callID: toolCall.callID,
1455
- name: toolCall.name,
1456
- rawArgs: toolCall.arguments
1457
- };
1458
- await emitEvent(emit, {
1459
- type: "tool.execution.start",
1460
- ...toolCallInFlight
1461
- });
1462
- const prepared = await prepareToolCall(config, currentContext, message, toolCallInFlight, signal);
1463
- if (prepared.error) {
1464
- const error = {
1465
- status: "error",
1466
- result: {
1467
- content: [{
1468
- type: "text",
1469
- text: prepared.error.message
1470
- }],
1471
- details: prepared.error.details,
1472
- isError: true
1473
- }
1474
- };
1475
- await emitToolCallTerminalResult(toolCall, prepared.runnable, message, emit, error);
1476
- } else {
1477
- const runnable = {
1478
- toolCall,
1479
- runnable: prepared.runnable,
1480
- tool: prepared.tool
1481
- };
1482
- const executed = await executeRunnableToolCall(runnable, message, emit, signal);
1483
- if (executed.error) {
1484
- const error = {
1485
- status: "error",
1486
- result: {
1487
- content: [{
1488
- type: "text",
1489
- text: executed.error.message
1490
- }],
1491
- details: executed.error.details,
1492
- isError: true
1493
- }
1494
- };
1495
- await emitToolCallTerminalResult(runnable.toolCall, runnable.runnable, message, emit, error);
1496
- } else await finalizeExecutedToolCall(config, currentContext, runnable.toolCall, runnable.runnable, message, emit, executed.result);
1497
- }
1295
+ _Message.transformMessages = transformMessages;
1296
+ })(Message || (Message = {}));
1297
+ //#endregion
1298
+ //#region src/utils/jsonparse.ts
1299
+ /**
1300
+ * @description Helper function to parse partial jsons during LLM streaming.
1301
+ */
1302
+ /**
1303
+ * Attempts to parse potentially incomplete JSON during streaming.
1304
+ * Always returns a valid object, even if the JSON is incomplete.
1305
+ *
1306
+ * @param partialJson The partial JSON string from streaming
1307
+ * @returns Parsed object or empty object if parsing fails
1308
+ */
1309
+ function parseStreamingJson(partialJson) {
1310
+ if (!partialJson || partialJson.trim() === "") return {};
1311
+ try {
1312
+ return JSON.parse(partialJson);
1313
+ } catch {
1314
+ try {
1315
+ return parse(partialJson) ?? {};
1316
+ } catch {
1317
+ return {};
1498
1318
  }
1499
1319
  }
1500
- async function executeToolCallsParallel(config, currentContext, message, toolCalls, emit, signal) {
1501
- const runnables = [];
1502
- for (const toolCall of toolCalls) {
1503
- const toolCallInFlight = {
1504
- callID: toolCall.callID,
1505
- name: toolCall.name,
1506
- rawArgs: toolCall.arguments
1507
- };
1508
- await emitEvent(emit, {
1509
- type: "tool.execution.start",
1510
- ...toolCallInFlight
1511
- });
1512
- const prepared = await prepareToolCall(config, currentContext, message, toolCallInFlight, signal);
1513
- if (prepared.error) {
1514
- const error = {
1515
- status: "error",
1516
- result: {
1517
- content: [{
1518
- type: "text",
1519
- text: prepared.error.message
1520
- }],
1521
- details: prepared.error.details,
1522
- isError: true
1523
- }
1524
- };
1525
- await emitToolCallTerminalResult(toolCall, prepared.runnable, message, emit, error);
1526
- } else runnables.push({
1527
- toolCall,
1528
- runnable: prepared.runnable,
1529
- tool: prepared.tool
1530
- });
1531
- }
1532
- const runningExecutions = runnables.map((runnable) => ({
1533
- runnable,
1534
- execution: executeRunnableToolCall(runnable, message, emit, signal)
1535
- }));
1536
- for (const running of runningExecutions) {
1537
- const executed = await running.execution;
1538
- const { runnable } = running;
1539
- if (executed.error) {
1540
- const error = {
1541
- status: "error",
1542
- result: {
1543
- content: [{
1544
- type: "text",
1545
- text: executed.error.message
1546
- }],
1547
- details: executed.error.details,
1548
- isError: true
1549
- }
1550
- };
1551
- await emitToolCallTerminalResult(runnable.toolCall, runnable.runnable, message, emit, error);
1552
- } else await finalizeExecutedToolCall(config, currentContext, runnable.toolCall, runnable.runnable, message, emit, executed.result);
1320
+ }
1321
+ //#endregion
1322
+ //#region src/utils/sanitize.ts
1323
+ /**
1324
+ * Removes unpaired Unicode surrogate characters from a string.
1325
+ *
1326
+ * Unpaired surrogates (high surrogates 0xD800-0xDBFF without matching low surrogates 0xDC00-0xDFFF,
1327
+ * or vice versa) cause JSON serialization errors in many API providers.
1328
+ *
1329
+ * Valid emoji and other characters outside the Basic Multilingual Plane use properly paired
1330
+ * surrogates and will NOT be affected by this function.
1331
+ *
1332
+ * @param text - The text to sanitize
1333
+ * @returns The sanitized text with unpaired surrogates removed
1334
+ *
1335
+ * @example
1336
+ * // Valid emoji (properly paired surrogates) are preserved
1337
+ * sanitizeSurrogates("Hello 🙈 World") // => "Hello 🙈 World"
1338
+ *
1339
+ * // Unpaired high surrogate is removed
1340
+ * const unpaired = String.fromCharCode(0xD83D); // high surrogate without low
1341
+ * sanitizeSurrogates(`Text ${unpaired} here`) // => "Text here"
1342
+ */
1343
+ function sanitizeSurrogates(text) {
1344
+ return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
1345
+ }
1346
+ //#endregion
1347
+ //#region src/provider/providers/utils.ts
1348
+ function getEnvApiKey(_provider) {}
1349
+ //#endregion
1350
+ //#region src/provider/providers/anthropic/index.ts
1351
+ function resolveCacheRetention(cacheRetention) {
1352
+ if (cacheRetention) return cacheRetention;
1353
+ if (typeof process !== "undefined" && process.env.CODEWORK_CACHE_RETENTION === "long") return "long";
1354
+ return "short";
1355
+ }
1356
+ function getCacheControl(baseUrl, cacheRetention) {
1357
+ const retention = resolveCacheRetention(cacheRetention);
1358
+ if (retention === "none") return { retention };
1359
+ const ttl = retention === "long" && baseUrl.includes("api.anthropic.com") ? "1h" : void 0;
1360
+ return {
1361
+ retention,
1362
+ cacheControl: {
1363
+ type: "ephemeral",
1364
+ ...ttl && { ttl }
1553
1365
  }
1366
+ };
1367
+ }
1368
+ const claudeCodeVersion = "2.1.2";
1369
+ const ccToolLookup = new Map([
1370
+ "Read",
1371
+ "Write",
1372
+ "Edit",
1373
+ "Bash",
1374
+ "Grep",
1375
+ "Glob",
1376
+ "AskUserQuestion",
1377
+ "EnterPlanMode",
1378
+ "ExitPlanMode",
1379
+ "KillShell",
1380
+ "NotebookEdit",
1381
+ "Skill",
1382
+ "Task",
1383
+ "TaskOutput",
1384
+ "TodoWrite",
1385
+ "WebFetch",
1386
+ "WebSearch"
1387
+ ].map((t) => [t.toLowerCase(), t]));
1388
+ const toClaudeCodeName = (name) => ccToolLookup.get(name.toLowerCase()) ?? name;
1389
+ const fromClaudeCodeName = (name, tools) => {
1390
+ if (tools && tools.length > 0) {
1391
+ const lowerName = name.toLowerCase();
1392
+ const matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);
1393
+ if (matchedTool) return matchedTool.name;
1554
1394
  }
1555
- async function prepareToolCall(config, currentContext, assistantMessage, toolCallInFlight, signal) {
1556
- const tool = currentContext.tools?.find((t) => t.name === toolCallInFlight.name);
1557
- if (!tool) return {
1558
- error: {
1559
- kind: "error",
1560
- message: `Tool ${toolCallInFlight.name} Not Found`
1561
- },
1562
- runnable: toolCallInFlight
1395
+ return name;
1396
+ };
1397
+ function convertContentBlocks(content) {
1398
+ if (!content.some((c) => c.type === "image")) return sanitizeSurrogates(content.map((c) => c.text).join("\n"));
1399
+ const blocks = content.map((block) => {
1400
+ if (block.type === "text") return {
1401
+ type: "text",
1402
+ text: sanitizeSurrogates(block.text)
1563
1403
  };
1564
- try {
1565
- toolCallInFlight.args = validateToolArguments(tool, toolCallInFlight);
1566
- if (config.beforeToolExecution) {
1567
- const beforeResult = await config.beforeToolExecution({
1568
- context: currentContext,
1569
- assistantMessage,
1570
- toolCall: toolCallInFlight
1571
- }, signal);
1572
- if (beforeResult?.block) return {
1573
- error: {
1574
- kind: "blocked",
1575
- message: beforeResult?.reason || "Tool Execution was Blocked",
1576
- details: beforeResult?.details
1577
- },
1578
- runnable: toolCallInFlight
1579
- };
1404
+ return {
1405
+ type: "image",
1406
+ source: {
1407
+ type: "base64",
1408
+ media_type: block.mimeType,
1409
+ data: block.data
1580
1410
  }
1581
- return {
1582
- runnable: toolCallInFlight,
1583
- tool
1584
- };
1585
- } catch (err) {
1586
- return {
1587
- error: {
1588
- kind: "error",
1589
- message: err instanceof Error ? err.message : String(err)
1590
- },
1591
- runnable: toolCallInFlight
1592
- };
1411
+ };
1412
+ });
1413
+ if (!blocks.some((b) => b.type === "text")) blocks.unshift({
1414
+ type: "text",
1415
+ text: "(see attached image)"
1416
+ });
1417
+ return blocks;
1418
+ }
1419
+ function mergeHeaders(...headerSources) {
1420
+ const merged = {};
1421
+ for (const headers of headerSources) if (headers) Object.assign(merged, headers);
1422
+ return merged;
1423
+ }
1424
+ function isOAuthToken(apiKey) {
1425
+ return apiKey.includes("sk-ant-oat");
1426
+ }
1427
+ function createClient$1(model, apiKey, interleavedThinking, optionsHeaders) {
1428
+ const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
1429
+ if (interleavedThinking) betaFeatures.push("interleaved-thinking-2025-05-14");
1430
+ if (isOAuthToken(apiKey)) {
1431
+ const defaultHeaders = mergeHeaders({
1432
+ accept: "application/json",
1433
+ "anthropic-dangerous-direct-browser-access": "true",
1434
+ "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}`,
1435
+ "user-agent": `claude-cli/${claudeCodeVersion} (external, cli)`,
1436
+ "x-app": "cli"
1437
+ }, model.headers, optionsHeaders);
1438
+ return {
1439
+ client: new Anthropic({
1440
+ apiKey: null,
1441
+ authToken: apiKey,
1442
+ baseURL: model.baseUrl,
1443
+ defaultHeaders,
1444
+ dangerouslyAllowBrowser: true
1445
+ }),
1446
+ isOAuthToken: true
1447
+ };
1448
+ }
1449
+ const defaultHeaders = mergeHeaders({
1450
+ accept: "application/json",
1451
+ "anthropic-dangerous-direct-browser-access": "true",
1452
+ "anthropic-beta": betaFeatures.join(",")
1453
+ }, model.headers, optionsHeaders);
1454
+ return {
1455
+ client: new Anthropic({
1456
+ apiKey,
1457
+ baseURL: model.baseUrl,
1458
+ dangerouslyAllowBrowser: true,
1459
+ defaultHeaders
1460
+ }),
1461
+ isOAuthToken: false
1462
+ };
1463
+ }
1464
+ function normalizeToolCallId(id) {
1465
+ return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
1466
+ }
1467
+ function assertReplayableAssistant(message) {
1468
+ let sawToolCall = false;
1469
+ let sawNonTerminalToolCall = false;
1470
+ let sawTerminalToolCall = false;
1471
+ for (const part of message.parts) {
1472
+ if (part.type === "image") throw new Error("Anthropic replay does not support assistant image parts");
1473
+ if (part.type === "toolCall") {
1474
+ sawToolCall = true;
1475
+ if (part.status === "pending" || part.status === "running") sawNonTerminalToolCall = true;
1476
+ else sawTerminalToolCall = true;
1477
+ continue;
1593
1478
  }
1479
+ if (sawToolCall) throw new Error("Assistant parts after a toolCall are not replayable for Anthropic");
1594
1480
  }
1595
- async function executeRunnableToolCall(toolCallRunnable, message, emit, signal) {
1596
- const { toolCall, runnable, tool } = toolCallRunnable;
1597
- const { partIndex, ...pendingPart } = toolCall;
1598
- try {
1599
- return { result: await tool.execute(runnable.callID, runnable.args, signal, async (result) => {
1600
- const runningResult = {
1601
- status: "running",
1602
- partial: result.partial
1603
- };
1604
- await emitEvent(emit, {
1605
- type: "tool.execution.update",
1606
- ...runnable,
1607
- ...runningResult
1608
- });
1609
- const runningPart = {
1610
- ...pendingPart,
1611
- ...runningResult
1481
+ if (sawToolCall && message.stopReason !== "toolUse") throw new Error("Assistant message with tool calls must have stopReason 'toolUse'");
1482
+ if (!sawToolCall && message.stopReason === "toolUse") throw new Error("Assistant message with stopReason 'toolUse' must contain at least one toolCall");
1483
+ if (sawTerminalToolCall && sawNonTerminalToolCall) throw new Error("Assistant message cannot mix terminal and non-terminal toolCall states");
1484
+ }
1485
+ function buildSyntheticToolResult$1(block) {
1486
+ return {
1487
+ type: "tool_result",
1488
+ tool_use_id: block.callID,
1489
+ content: convertContentBlocks([{
1490
+ type: "text",
1491
+ text: [
1492
+ "<result>",
1493
+ "No Result Provided",
1494
+ "</result>"
1495
+ ].join("\n")
1496
+ }]),
1497
+ is_error: true
1498
+ };
1499
+ }
1500
+ function convertMessages$1(messages, model, isOAuthToken, cacheControl) {
1501
+ const params = [];
1502
+ const transformedMessages = Message.transformMessages(messages, model, normalizeToolCallId);
1503
+ for (const msg of transformedMessages) {
1504
+ if (msg.role === "user") {
1505
+ const blocks = msg.parts.map((item) => {
1506
+ if (item.type === "text") return {
1507
+ type: "text",
1508
+ text: sanitizeSurrogates(item.text)
1612
1509
  };
1613
- message.parts[partIndex] = runningPart;
1614
- await emitEvent(emit, {
1615
- type: "message.part.update",
1616
- message,
1617
- partIndex,
1618
- part: runningPart,
1619
- source: "tool"
1620
- });
1621
- }) };
1622
- } catch (err) {
1623
- return { error: { message: err instanceof Error ? err.message : String(err) } };
1510
+ return {
1511
+ type: "image",
1512
+ source: {
1513
+ type: "base64",
1514
+ media_type: item.mimeType,
1515
+ data: item.data
1516
+ }
1517
+ };
1518
+ });
1519
+ let filteredBlocks = !model.input.includes("image") ? blocks.filter((b) => b.type !== "image") : blocks;
1520
+ filteredBlocks = filteredBlocks.filter((b) => b.type === "text" ? b.text.trim().length > 0 : true);
1521
+ if (filteredBlocks.length === 0) continue;
1522
+ params.push({
1523
+ role: "user",
1524
+ content: filteredBlocks
1525
+ });
1526
+ continue;
1527
+ }
1528
+ if (msg.stopReason === "error" || msg.stopReason === "aborted") continue;
1529
+ assertReplayableAssistant(msg);
1530
+ const assistantBlocks = [];
1531
+ const toolResults = [];
1532
+ for (const block of msg.parts) if (block.type === "text") {
1533
+ if (block.text.trim().length === 0) continue;
1534
+ assistantBlocks.push({
1535
+ type: "text",
1536
+ text: sanitizeSurrogates(block.text)
1537
+ });
1538
+ } else if (block.type === "thinking") {
1539
+ if (block.thinking.trim().length === 0) continue;
1540
+ if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) assistantBlocks.push({
1541
+ type: "text",
1542
+ text: sanitizeSurrogates(block.thinking)
1543
+ });
1544
+ else assistantBlocks.push({
1545
+ type: "thinking",
1546
+ thinking: sanitizeSurrogates(block.thinking),
1547
+ signature: block.thinkingSignature
1548
+ });
1549
+ } else if (block.type === "toolCall") {
1550
+ assistantBlocks.push({
1551
+ type: "tool_use",
1552
+ id: block.callID,
1553
+ name: isOAuthToken ? toClaudeCodeName(block.name) : block.name,
1554
+ input: block.arguments ?? {}
1555
+ });
1556
+ if (block.status === "pending" || block.status === "running") toolResults.push(buildSyntheticToolResult$1(block));
1557
+ else toolResults.push({
1558
+ type: "tool_result",
1559
+ tool_use_id: block.callID,
1560
+ content: convertContentBlocks(block.result.content),
1561
+ is_error: block.result.isError
1562
+ });
1563
+ }
1564
+ if (assistantBlocks.length > 0) params.push({
1565
+ role: "assistant",
1566
+ content: assistantBlocks
1567
+ });
1568
+ if (toolResults.length > 0) params.push({
1569
+ role: "user",
1570
+ content: toolResults
1571
+ });
1572
+ }
1573
+ if (cacheControl && params.length > 0) {
1574
+ const lastMessage = params[params.length - 1];
1575
+ if (lastMessage.role === "user") {
1576
+ if (Array.isArray(lastMessage.content)) {
1577
+ const lastBlock = lastMessage.content[lastMessage.content.length - 1];
1578
+ if (lastBlock && (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")) lastBlock.cache_control = cacheControl;
1579
+ } else if (typeof lastMessage.content === "string") lastMessage.content = [{
1580
+ type: "text",
1581
+ text: lastMessage.content,
1582
+ cache_control: cacheControl
1583
+ }];
1584
+ }
1585
+ }
1586
+ return params;
1587
+ }
1588
+ function convertTools$1(tools, isOAuthToken) {
1589
+ if (!tools) return [];
1590
+ return tools.map((tool) => {
1591
+ const jsonSchema = tool.parameters;
1592
+ return {
1593
+ name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
1594
+ description: tool.description,
1595
+ input_schema: {
1596
+ type: "object",
1597
+ properties: jsonSchema.properties || {},
1598
+ required: jsonSchema.required || []
1599
+ }
1600
+ };
1601
+ });
1602
+ }
1603
+ function mapStopReason$1(reason) {
1604
+ switch (reason) {
1605
+ case "end_turn": return "stop";
1606
+ case "max_tokens": return "length";
1607
+ case "tool_use": return "toolUse";
1608
+ case "pause_turn":
1609
+ case "stop_sequence": return "stop";
1610
+ case "refusal":
1611
+ case "sensitive": return "error";
1612
+ default: throw new Error("Unhandled Anthropic stop reason");
1613
+ }
1614
+ }
1615
+ function buildParams$1(model, context, isOAuthToken, options) {
1616
+ const { cacheControl } = getCacheControl(model.baseUrl, options?.cacheRetention);
1617
+ const params = {
1618
+ model: model.id,
1619
+ messages: convertMessages$1(context.messages, model, isOAuthToken, cacheControl),
1620
+ max_tokens: options?.maxTokens || model.maxTokens / 3 | 0,
1621
+ stream: true
1622
+ };
1623
+ if (isOAuthToken) {
1624
+ params.system = [{
1625
+ type: "text",
1626
+ text: "You are Claude Code, Anthropic's official CLI for Claude.",
1627
+ ...cacheControl ? { cache_control: cacheControl } : {}
1628
+ }];
1629
+ if (context.systemPrompt) params.system.push({
1630
+ type: "text",
1631
+ text: sanitizeSurrogates(context.systemPrompt),
1632
+ ...cacheControl ? { cache_control: cacheControl } : {}
1633
+ });
1634
+ } else if (context.systemPrompt) params.system = [{
1635
+ type: "text",
1636
+ text: sanitizeSurrogates(context.systemPrompt),
1637
+ ...cacheControl ? { cache_control: cacheControl } : {}
1638
+ }];
1639
+ if (options?.temperature !== void 0 && !options?.thinkingEnabled) params.temperature = options.temperature;
1640
+ if (context.tools) params.tools = convertTools$1(context.tools, isOAuthToken);
1641
+ if (options?.thinkingEnabled && model.reasoning) params.thinking = {
1642
+ type: "enabled",
1643
+ budget_tokens: options.thinkingBudgetTokens || 1024
1644
+ };
1645
+ if (options?.metadata) {
1646
+ const userId = options.metadata.user_id;
1647
+ if (typeof userId === "string") params.metadata = { user_id: userId };
1648
+ }
1649
+ if (options?.toolChoice) params.tool_choice = typeof options.toolChoice === "string" ? { type: options.toolChoice } : options.toolChoice;
1650
+ return params;
1651
+ }
1652
+ const streamAnthropic = (model, context, options) => {
1653
+ const stream = new AssistantMessageEventStream();
1654
+ (async () => {
1655
+ const output = Message.createAssistantMessage({
1656
+ role: "assistant",
1657
+ parts: [],
1658
+ protocol: model.protocol,
1659
+ provider: model.provider,
1660
+ model: model.id,
1661
+ usage: {
1662
+ input: 0,
1663
+ output: 0,
1664
+ cacheRead: 0,
1665
+ cacheWrite: 0,
1666
+ totalTokens: 0,
1667
+ cost: {
1668
+ input: 0,
1669
+ output: 0,
1670
+ cacheRead: 0,
1671
+ cacheWrite: 0,
1672
+ total: 0
1673
+ }
1674
+ },
1675
+ stopReason: "stop",
1676
+ time: {
1677
+ created: Date.now(),
1678
+ completed: Date.now()
1679
+ }
1680
+ });
1681
+ try {
1682
+ let client;
1683
+ let isOAuthToken;
1684
+ if (options?.client) {
1685
+ client = options.client;
1686
+ isOAuthToken = false;
1687
+ } else {
1688
+ const apiKey = options?.apiKey ?? /* @__PURE__ */ getEnvApiKey(model.provider);
1689
+ if (!apiKey) throw new Error(`No API key for provider: ${model.provider.id}`);
1690
+ const created = createClient$1(model, apiKey, options?.interleavedThinking ?? true, options?.headers);
1691
+ client = created.client;
1692
+ isOAuthToken = created.isOAuthToken;
1693
+ }
1694
+ let params = buildParams$1(model, context, isOAuthToken, options);
1695
+ const nextParams = await options?.onPayload?.(params, model);
1696
+ if (nextParams !== void 0) params = nextParams;
1697
+ const anthropicStream = client.messages.stream({
1698
+ ...params,
1699
+ stream: true
1700
+ }, { signal: options?.signal });
1701
+ stream.push({
1702
+ type: "start",
1703
+ partial: output
1704
+ });
1705
+ const blocks = output.parts;
1706
+ for await (const event of anthropicStream) if (event.type === "message_start") {
1707
+ output.responseId = event.message.id;
1708
+ output.usage.input = event.message.usage.input_tokens || 0;
1709
+ output.usage.output = event.message.usage.output_tokens || 0;
1710
+ output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
1711
+ output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
1712
+ output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
1713
+ Model.calculateCost(model, output.usage);
1714
+ } else if (event.type === "content_block_start") {
1715
+ if (event.content_block.type === "text") {
1716
+ const block = {
1717
+ type: "text",
1718
+ text: "",
1719
+ index: event.index
1720
+ };
1721
+ output.parts.push(block);
1722
+ stream.push({
1723
+ type: "text.start",
1724
+ partIndex: output.parts.length - 1,
1725
+ partial: output
1726
+ });
1727
+ } else if (event.content_block.type === "thinking") {
1728
+ const block = {
1729
+ type: "thinking",
1730
+ thinking: "",
1731
+ thinkingSignature: "",
1732
+ index: event.index
1733
+ };
1734
+ output.parts.push(block);
1735
+ stream.push({
1736
+ type: "thinking.start",
1737
+ partIndex: output.parts.length - 1,
1738
+ partial: output
1739
+ });
1740
+ } else if (event.content_block.type === "redacted_thinking") {
1741
+ const block = {
1742
+ type: "thinking",
1743
+ thinking: "[Reasoning redacted]",
1744
+ thinkingSignature: event.content_block.data,
1745
+ redacted: true,
1746
+ index: event.index
1747
+ };
1748
+ output.parts.push(block);
1749
+ stream.push({
1750
+ type: "thinking.start",
1751
+ partIndex: output.parts.length - 1,
1752
+ partial: output
1753
+ });
1754
+ } else if (event.content_block.type === "tool_use") {
1755
+ const block = {
1756
+ type: "toolCall",
1757
+ callID: event.content_block.id,
1758
+ name: isOAuthToken ? fromClaudeCodeName(event.content_block.name, context.tools) : event.content_block.name,
1759
+ arguments: event.content_block.input ?? {},
1760
+ partialJson: "",
1761
+ status: "pending",
1762
+ time: {
1763
+ start: Date.now(),
1764
+ end: Date.now()
1765
+ },
1766
+ index: event.index
1767
+ };
1768
+ output.parts.push(block);
1769
+ stream.push({
1770
+ type: "toolcall.start",
1771
+ partIndex: output.parts.length - 1,
1772
+ partial: output
1773
+ });
1774
+ }
1775
+ } else if (event.type === "content_block_delta") {
1776
+ const partIndex = blocks.findIndex((b) => b.index === event.index);
1777
+ const block = blocks[partIndex];
1778
+ if (!block) continue;
1779
+ if (event.delta.type === "text_delta" && block.type === "text") {
1780
+ block.text += event.delta.text;
1781
+ stream.push({
1782
+ type: "text.delta",
1783
+ partIndex,
1784
+ delta: event.delta.text,
1785
+ partial: output
1786
+ });
1787
+ } else if (event.delta.type === "thinking_delta" && block.type === "thinking") {
1788
+ block.thinking += event.delta.thinking;
1789
+ stream.push({
1790
+ type: "thinking.delta",
1791
+ partIndex,
1792
+ delta: event.delta.thinking,
1793
+ partial: output
1794
+ });
1795
+ } else if (event.delta.type === "input_json_delta" && block.type === "toolCall") {
1796
+ block.partialJson += event.delta.partial_json;
1797
+ block.arguments = parseStreamingJson(block.partialJson);
1798
+ stream.push({
1799
+ type: "toolcall.delta",
1800
+ partIndex,
1801
+ delta: event.delta.partial_json,
1802
+ partial: output
1803
+ });
1804
+ } else if (event.delta.type === "signature_delta" && block.type === "thinking") {
1805
+ block.thinkingSignature = block.thinkingSignature || "";
1806
+ block.thinkingSignature += event.delta.signature;
1807
+ }
1808
+ } else if (event.type === "content_block_stop") {
1809
+ const partIndex = blocks.findIndex((b) => b.index === event.index);
1810
+ const block = blocks[partIndex];
1811
+ if (block) {
1812
+ delete block.index;
1813
+ if (block.type === "text") stream.push({
1814
+ type: "text.end",
1815
+ partIndex,
1816
+ content: block.text,
1817
+ partial: output
1818
+ });
1819
+ else if (block.type === "thinking") stream.push({
1820
+ type: "thinking.end",
1821
+ partIndex,
1822
+ content: block.thinking,
1823
+ partial: output
1824
+ });
1825
+ else if (block.type === "toolCall") {
1826
+ block.arguments = parseStreamingJson(block.partialJson);
1827
+ delete block.partialJson;
1828
+ stream.push({
1829
+ type: "toolcall.end",
1830
+ partIndex,
1831
+ toolCall: block,
1832
+ partial: output
1833
+ });
1834
+ }
1835
+ }
1836
+ } else if (event.type === "message_delta") {
1837
+ if (event.delta.stop_reason) output.stopReason = mapStopReason$1(event.delta.stop_reason);
1838
+ if (event.usage.input_tokens != null) output.usage.input = event.usage.input_tokens;
1839
+ if (event.usage.output_tokens != null) output.usage.output = event.usage.output_tokens;
1840
+ if (event.usage.cache_read_input_tokens != null) output.usage.cacheRead = event.usage.cache_read_input_tokens;
1841
+ if (event.usage.cache_creation_input_tokens != null) output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
1842
+ output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
1843
+ Model.calculateCost(model, output.usage);
1844
+ }
1845
+ if (options?.signal?.aborted) throw new Error("Request was aborted");
1846
+ if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error("An unknown error occurred");
1847
+ output.time.completed = Date.now();
1848
+ stream.push({
1849
+ type: "done",
1850
+ reason: output.stopReason,
1851
+ message: output
1852
+ });
1853
+ stream.end();
1854
+ } catch (error) {
1855
+ for (const block of output.parts) delete block.index;
1856
+ output.time.completed = Date.now();
1857
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
1858
+ output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
1859
+ stream.push({
1860
+ type: "error",
1861
+ reason: output.stopReason,
1862
+ error: output
1863
+ });
1864
+ stream.end();
1624
1865
  }
1866
+ })();
1867
+ return stream;
1868
+ };
1869
+ const streamSimpleAnthropic = (model, context, options) => {
1870
+ const apiKey = options?.apiKey || /* @__PURE__ */ getEnvApiKey(model.provider);
1871
+ if (!apiKey) throw new Error(`No API key for provider: ${model.provider.id}`);
1872
+ const base = Stream.buildBaseOptions(model, options, apiKey);
1873
+ if (!options?.reasoning) return streamAnthropic(model, context, {
1874
+ ...base,
1875
+ thinkingEnabled: false
1876
+ });
1877
+ const adjusted = Stream.adjustMaxTokensForThinking(base.maxTokens || 0, model.maxTokens, options.reasoning, options.thinkingBudgets);
1878
+ return streamAnthropic(model, context, {
1879
+ ...base,
1880
+ maxTokens: adjusted.maxTokens,
1881
+ thinkingEnabled: true,
1882
+ thinkingBudgetTokens: adjusted.thinkingBudget
1883
+ });
1884
+ };
1885
+ const anthropicProtocolProvider = {
1886
+ protocol: Model.KnownProtocolEnum.anthropicMessages,
1887
+ stream: streamAnthropic,
1888
+ streamSimple: streamSimpleAnthropic
1889
+ };
1890
+ //#endregion
1891
+ //#region src/provider/providers/github/github.ts
1892
+ function inferCopilotInitiator(messages) {
1893
+ const last = messages[messages.length - 1];
1894
+ return last && last.role !== "user" ? "agent" : "user";
1895
+ }
1896
+ function hasCopilotVisionInput(messages) {
1897
+ return messages.some((msg) => {
1898
+ if (msg.role === "user") return msg.parts.some((c) => c.type === "image");
1899
+ return false;
1900
+ });
1901
+ }
1902
+ function buildCopilotDynamicHeaders(params) {
1903
+ const headers = {
1904
+ "X-Initiator": inferCopilotInitiator(params.messages),
1905
+ "Openai-Intent": "conversation-edits"
1906
+ };
1907
+ if (params.hasImages) headers["Copilot-Vision-Request"] = "true";
1908
+ return headers;
1909
+ }
1910
+ //#endregion
1911
+ //#region src/provider/providers/openai/completions.ts
1912
+ function hasToolHistory(messages) {
1913
+ for (const msg of messages) {
1914
+ if (msg.role !== "assistant") continue;
1915
+ if (msg.parts.some((block) => block.type === "toolCall")) return true;
1625
1916
  }
1626
- async function emitToolCallTerminalResult(toolCall, runnable, message, emit, result) {
1627
- const { partIndex, ...pendingPart } = toolCall;
1628
- const { time } = pendingPart;
1629
- await emitEvent(emit, {
1630
- type: "tool.execution.end",
1631
- ...runnable,
1632
- ...result
1633
- });
1634
- const part = {
1635
- ...pendingPart,
1636
- ...result,
1917
+ return false;
1918
+ }
1919
+ function buildSyntheticToolResult(block, compat) {
1920
+ const message = {
1921
+ role: "tool",
1922
+ content: [
1923
+ "<result>",
1924
+ "No Result Provided",
1925
+ "</result>"
1926
+ ].join("\n"),
1927
+ tool_call_id: block.callID
1928
+ };
1929
+ if (compat.requiresToolResultName) message.name = block.name;
1930
+ return message;
1931
+ }
1932
+ function resolveBaseUrl(model) {
1933
+ return model.baseUrl || (model.provider.id === Provider.KnownProviderEnum.openai ? "https://api.openai.com/v1" : "");
1934
+ }
1935
+ const streamOpenAICompletions = (model, context, options) => {
1936
+ const stream = new AssistantMessageEventStream();
1937
+ (async () => {
1938
+ const output = Message.createAssistantMessage({
1939
+ role: "assistant",
1940
+ parts: [],
1941
+ protocol: model.protocol,
1942
+ provider: model.provider,
1943
+ model: model.id,
1944
+ usage: {
1945
+ input: 0,
1946
+ output: 0,
1947
+ cacheRead: 0,
1948
+ cacheWrite: 0,
1949
+ totalTokens: 0,
1950
+ cost: {
1951
+ input: 0,
1952
+ output: 0,
1953
+ cacheRead: 0,
1954
+ cacheWrite: 0,
1955
+ total: 0
1956
+ }
1957
+ },
1958
+ stopReason: "stop",
1637
1959
  time: {
1638
- start: time.start,
1639
- end: Date.now()
1960
+ created: Date.now(),
1961
+ completed: Date.now()
1640
1962
  }
1641
- };
1642
- message.parts[partIndex] = part;
1643
- await emitEvent(emit, {
1644
- type: "message.part.start",
1645
- message,
1646
- partIndex,
1647
- part
1648
- });
1649
- await emitEvent(emit, {
1650
- type: "message.part.end",
1651
- message,
1652
- partIndex,
1653
- part
1654
- });
1655
- await emitEvent(emit, {
1656
- type: "message.update",
1657
- message
1658
1963
  });
1659
- }
1660
- async function finalizeExecutedToolCall(config, currentContext, toolCall, runnable, message, emit, result, signal) {
1661
- if (config.afterToolExecution) {
1662
- const afterResult = await config.afterToolExecution({
1663
- context: currentContext,
1664
- assistantMessage: message,
1665
- toolCall: runnable,
1666
- result
1667
- }, signal);
1668
- if (afterResult) {
1669
- await emitToolCallTerminalResult(toolCall, runnable, message, emit, afterResult);
1670
- return;
1671
- }
1672
- }
1673
- await emitToolCallTerminalResult(toolCall, runnable, message, emit, result);
1674
- }
1675
- /**
1676
- * Main core loop shared by run and runContinue.
1677
- */
1678
- async function runLoop(config, currentContext, newMessages, emit, streamFn, signal) {
1679
- let firstTurn = true;
1680
- let pendingMessages = await config.getSteeringMessages?.() || [];
1681
- while (true) {
1682
- let hasMoreToolCalls = true;
1683
- while (hasMoreToolCalls || pendingMessages.length > 0) {
1684
- if (!firstTurn) await emitEvent(emit, { type: "turn.start" });
1685
- else firstTurn = false;
1686
- if (pendingMessages.length > 0) {
1687
- for (const message of pendingMessages) {
1688
- await emitEvent(emit, {
1689
- type: "message.start",
1690
- message
1964
+ try {
1965
+ const client = createClient(model, context, options?.apiKey || /* @__PURE__ */ getEnvApiKey(model.provider) || "", options?.headers);
1966
+ let params = buildParams(model, context, options);
1967
+ const nextParams = await options?.onPayload?.(params, model);
1968
+ if (nextParams !== void 0) params = nextParams;
1969
+ const openaiStream = await client.chat.completions.create(params, { signal: options?.signal });
1970
+ stream.push({
1971
+ type: "start",
1972
+ partial: output
1973
+ });
1974
+ let currentBlock = null;
1975
+ const blocks = output.parts;
1976
+ const activeToolCalls = /* @__PURE__ */ new Map();
1977
+ const finishCurrentBlock = (block) => {
1978
+ if (block) {
1979
+ const partIndex = block.index ?? blocks.indexOf(block);
1980
+ delete block.index;
1981
+ if (block.type === "text") stream.push({
1982
+ type: "text.end",
1983
+ partIndex,
1984
+ content: block.text,
1985
+ partial: output
1986
+ });
1987
+ else if (block.type === "thinking") stream.push({
1988
+ type: "thinking.end",
1989
+ partIndex,
1990
+ content: block.thinking,
1991
+ partial: output
1992
+ });
1993
+ else if (block.type === "toolCall") {
1994
+ if (block.streamIndex !== void 0) activeToolCalls.delete(block.streamIndex);
1995
+ block.arguments = parseStreamingJson(block.partialArgs);
1996
+ delete block.partialArgs;
1997
+ delete block.streamIndex;
1998
+ stream.push({
1999
+ type: "toolcall.end",
2000
+ partIndex,
2001
+ toolCall: block,
2002
+ partial: output
1691
2003
  });
1692
- for (const [partIndex, part] of message.parts.entries()) {
1693
- await emitEvent(emit, {
1694
- type: "message.part.start",
1695
- message,
1696
- partIndex,
1697
- part
1698
- });
1699
- await emitEvent(emit, {
1700
- type: "message.part.end",
1701
- message,
1702
- partIndex,
1703
- part
2004
+ }
2005
+ }
2006
+ };
2007
+ const flushActiveToolCalls = () => {
2008
+ for (const toolBlock of [...activeToolCalls.values()].sort((a, b) => (a.index ?? 0) - (b.index ?? 0))) finishCurrentBlock(toolBlock);
2009
+ };
2010
+ for await (const chunk of openaiStream) {
2011
+ if (!chunk || typeof chunk !== "object") continue;
2012
+ output.responseId ||= chunk.id;
2013
+ if (chunk.usage) output.usage = parseChunkUsage(chunk.usage, model);
2014
+ const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : void 0;
2015
+ if (!choice) continue;
2016
+ const choiceWithUsage = choice;
2017
+ if (!chunk.usage && choiceWithUsage.usage) output.usage = parseChunkUsage(choiceWithUsage.usage, model);
2018
+ if (choice.finish_reason) {
2019
+ const finishReasonResult = mapStopReason(choice.finish_reason);
2020
+ output.stopReason = finishReasonResult.stopReason;
2021
+ if (finishReasonResult.errorMessage) output.errorMessage = finishReasonResult.errorMessage;
2022
+ }
2023
+ if (!choice.delta) continue;
2024
+ if (choice.delta.content !== null && choice.delta.content !== void 0 && choice.delta.content.length > 0) {
2025
+ if (activeToolCalls.size > 0) flushActiveToolCalls();
2026
+ if (!currentBlock || currentBlock.type !== "text") {
2027
+ finishCurrentBlock(currentBlock);
2028
+ currentBlock = {
2029
+ type: "text",
2030
+ text: "",
2031
+ index: output.parts.length
2032
+ };
2033
+ output.parts.push(currentBlock);
2034
+ stream.push({
2035
+ type: "text.start",
2036
+ partIndex: currentBlock.index,
2037
+ partial: output
2038
+ });
2039
+ }
2040
+ if (currentBlock.type === "text") {
2041
+ currentBlock.text += choice.delta.content;
2042
+ stream.push({
2043
+ type: "text.delta",
2044
+ partIndex: currentBlock.index,
2045
+ delta: choice.delta.content,
2046
+ partial: output
2047
+ });
2048
+ }
2049
+ }
2050
+ const reasoningFields = [
2051
+ "reasoning_content",
2052
+ "reasoning",
2053
+ "reasoning_text"
2054
+ ];
2055
+ let foundReasoningField = null;
2056
+ for (const field of reasoningFields) {
2057
+ const value = choice.delta[field];
2058
+ if (typeof value === "string" && value.length > 0) {
2059
+ foundReasoningField = field;
2060
+ break;
2061
+ }
2062
+ }
2063
+ if (foundReasoningField) {
2064
+ if (activeToolCalls.size > 0) flushActiveToolCalls();
2065
+ if (!currentBlock || currentBlock.type !== "thinking") {
2066
+ finishCurrentBlock(currentBlock);
2067
+ currentBlock = {
2068
+ type: "thinking",
2069
+ thinking: "",
2070
+ thinkingSignature: foundReasoningField,
2071
+ index: output.parts.length
2072
+ };
2073
+ output.parts.push(currentBlock);
2074
+ stream.push({
2075
+ type: "thinking.start",
2076
+ partIndex: currentBlock.index,
2077
+ partial: output
2078
+ });
2079
+ }
2080
+ if (currentBlock.type === "thinking") {
2081
+ const delta = choice.delta[foundReasoningField];
2082
+ if (typeof delta === "string") {
2083
+ currentBlock.thinking += delta;
2084
+ stream.push({
2085
+ type: "thinking.delta",
2086
+ partIndex: currentBlock.index,
2087
+ delta,
2088
+ partial: output
1704
2089
  });
1705
- await emitEvent(emit, {
1706
- type: "message.update",
1707
- message
2090
+ }
2091
+ }
2092
+ }
2093
+ if (choice.delta.tool_calls) {
2094
+ if (currentBlock) {
2095
+ finishCurrentBlock(currentBlock);
2096
+ currentBlock = null;
2097
+ }
2098
+ for (const toolCall of choice.delta.tool_calls) {
2099
+ const streamIndex = toolCall.index ?? activeToolCalls.size;
2100
+ let toolBlock = activeToolCalls.get(streamIndex);
2101
+ if (!toolBlock) {
2102
+ toolBlock = {
2103
+ type: "toolCall",
2104
+ callID: toolCall.id || "",
2105
+ name: toolCall.function?.name || "",
2106
+ arguments: {},
2107
+ partialArgs: "",
2108
+ index: output.parts.length,
2109
+ streamIndex,
2110
+ status: "pending",
2111
+ time: {
2112
+ start: Date.now(),
2113
+ end: Date.now()
2114
+ }
2115
+ };
2116
+ activeToolCalls.set(streamIndex, toolBlock);
2117
+ output.parts.push(toolBlock);
2118
+ stream.push({
2119
+ type: "toolcall.start",
2120
+ partIndex: toolBlock.index,
2121
+ partial: output
1708
2122
  });
1709
2123
  }
1710
- await emitEvent(emit, {
1711
- type: "message.end",
1712
- message
1713
- });
1714
- currentContext.messages.push(message);
1715
- newMessages.push(message);
2124
+ if (toolCall.id) toolBlock.callID = toolCall.id;
2125
+ if (toolCall.function?.name) toolBlock.name = toolCall.function.name;
2126
+ let delta = "";
2127
+ if (toolCall.function?.arguments) {
2128
+ delta = toolCall.function.arguments;
2129
+ toolBlock.partialArgs = (toolBlock.partialArgs || "") + delta;
2130
+ toolBlock.arguments = parseStreamingJson(toolBlock.partialArgs);
2131
+ }
2132
+ toolBlock.time.end = Date.now();
2133
+ stream.push({
2134
+ type: "toolcall.delta",
2135
+ partIndex: toolBlock.index,
2136
+ delta,
2137
+ partial: output
2138
+ });
1716
2139
  }
1717
- pendingMessages = [];
1718
2140
  }
1719
- const message = await streamAssistantResponse(config, currentContext, emit, streamFn, signal);
1720
- if (message.stopReason === "error" || message.stopReason === "aborted") {
1721
- currentContext.messages.push(message);
1722
- newMessages.push(message);
1723
- await emitEvent(emit, {
1724
- type: "turn.end",
1725
- message
1726
- });
1727
- await emitEvent(emit, {
1728
- type: "agent.end",
1729
- messages: newMessages
1730
- });
1731
- return;
2141
+ const reasoningDetails = choice.delta.reasoning_details;
2142
+ if (reasoningDetails && Array.isArray(reasoningDetails)) {
2143
+ for (const detail of reasoningDetails) if (detail.type === "reasoning.encrypted" && typeof detail.id === "string" && detail.data !== void 0) {
2144
+ const matchingToolCall = output.parts.find((block) => block.type === "toolCall" && block.callID === detail.id);
2145
+ if (matchingToolCall) matchingToolCall.thoughtSignature = JSON.stringify(detail);
2146
+ }
1732
2147
  }
1733
- const toolCalls = message.parts.map((part, index) => ({
1734
- ...part,
1735
- partIndex: index
1736
- })).filter((part) => part.type === "toolCall" && part.status === "pending");
1737
- hasMoreToolCalls = toolCalls.length > 0;
1738
- if (hasMoreToolCalls) await executeToolCalls(config, currentContext, message, toolCalls, emit, signal);
1739
- currentContext.messages.push(message);
1740
- newMessages.push(message);
1741
- await emitEvent(emit, {
1742
- type: "turn.end",
1743
- message
1744
- });
1745
- pendingMessages = await config.getSteeringMessages?.() || [];
1746
2148
  }
1747
- const followUpMessages = await config.getFollowUpMessages?.() || [];
1748
- if (followUpMessages.length > 0) {
1749
- pendingMessages = followUpMessages;
1750
- continue;
2149
+ finishCurrentBlock(currentBlock);
2150
+ flushActiveToolCalls();
2151
+ if (options?.signal?.aborted) throw new Error("Request was aborted");
2152
+ if (output.stopReason === "aborted") throw new Error("Request was aborted");
2153
+ if (output.stopReason === "error") throw new Error(output.errorMessage || "Provider returned an error stop reason");
2154
+ output.time.completed = Date.now();
2155
+ stream.push({
2156
+ type: "done",
2157
+ reason: output.stopReason,
2158
+ message: output
2159
+ });
2160
+ stream.end();
2161
+ } catch (error) {
2162
+ for (const block of output.parts) {
2163
+ delete block.index;
2164
+ delete block.partialArgs;
2165
+ delete block.streamIndex;
1751
2166
  }
1752
- break;
2167
+ output.time.completed = Date.now();
2168
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
2169
+ output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
2170
+ const rawMetadata = error?.error?.metadata?.raw;
2171
+ if (rawMetadata) output.errorMessage += `\n${rawMetadata}`;
2172
+ stream.push({
2173
+ type: "error",
2174
+ reason: output.stopReason,
2175
+ error: output
2176
+ });
2177
+ stream.end();
1753
2178
  }
1754
- await emitEvent(emit, {
1755
- type: "agent.end",
1756
- messages: newMessages
2179
+ })();
2180
+ return stream;
2181
+ };
2182
+ const streamSimpleOpenAICompletions = (model, context, options) => {
2183
+ const apiKey = options?.apiKey || /* @__PURE__ */ getEnvApiKey(model.provider);
2184
+ if (!apiKey) throw new Error(`No API key for provider: ${model.provider.id}`);
2185
+ const base = Stream.buildBaseOptions(model, options, apiKey);
2186
+ const simpleReasoning = Model.supportsXhigh(model) ? options?.reasoning : Stream.clampReasoning(options?.reasoning);
2187
+ const reasoningEffort = simpleReasoning && simpleReasoning !== Stream.ThinkingLevelEnum.off ? simpleReasoning : void 0;
2188
+ const toolChoice = options?.toolChoice;
2189
+ return streamOpenAICompletions(model, context, {
2190
+ ...base,
2191
+ reasoningEffort,
2192
+ toolChoice
2193
+ });
2194
+ };
2195
+ function buildParams(model, context, options) {
2196
+ const compat = getCompat(model);
2197
+ const messages = convertMessages(model, context, compat);
2198
+ maybeAddOpenRouterAnthropicCacheControl(model, messages);
2199
+ const baseUrl = resolveBaseUrl(model);
2200
+ const params = {
2201
+ model: model.id,
2202
+ messages,
2203
+ stream: true
2204
+ };
2205
+ if (compat.supportsUsageInStreaming !== false) params.stream_options = { include_usage: true };
2206
+ if (compat.supportsStore) params.store = false;
2207
+ if (options?.maxTokens) if (compat.maxTokensField === "max_tokens") params.max_tokens = options.maxTokens;
2208
+ else params.max_completion_tokens = options.maxTokens;
2209
+ if (options?.temperature !== void 0) params.temperature = options.temperature;
2210
+ if (context.tools) {
2211
+ params.tools = convertTools(context.tools, compat);
2212
+ if (compat.zaiToolStream) params.tool_stream = true;
2213
+ } else if (hasToolHistory(context.messages)) params.tools = [];
2214
+ if (options?.toolChoice) params.tool_choice = options.toolChoice;
2215
+ if ((compat.thinkingFormat === "zai" || compat.thinkingFormat === "qwen") && model.reasoning) params.enable_thinking = !!options?.reasoningEffort;
2216
+ else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) params.chat_template_kwargs = { enable_thinking: !!options?.reasoningEffort };
2217
+ else if (compat.thinkingFormat === "openrouter" && model.reasoning) {
2218
+ const openRouterParams = params;
2219
+ if (options?.reasoningEffort) openRouterParams.reasoning = { effort: mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap) };
2220
+ else openRouterParams.reasoning = { effort: "none" };
2221
+ } else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) params.reasoning_effort = mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap);
2222
+ if (baseUrl.includes("openrouter.ai") && model.compat?.openRouterRouting) params.provider = model.compat.openRouterRouting;
2223
+ if (baseUrl.includes("ai-gateway.vercel.sh") && model.compat?.vercelGatewayRouting) {
2224
+ const routing = model.compat.vercelGatewayRouting;
2225
+ if (routing.only || routing.order) {
2226
+ const gatewayOptions = {};
2227
+ if (routing.only) gatewayOptions.only = routing.only;
2228
+ if (routing.order) gatewayOptions.order = routing.order;
2229
+ params.providerOptions = { gateway: gatewayOptions };
2230
+ }
2231
+ }
2232
+ return params;
2233
+ }
2234
+ function createClient(model, context, apiKey, optsHeaders) {
2235
+ const baseUrl = resolveBaseUrl(model);
2236
+ if (!apiKey) {
2237
+ if (!process.env.OPENAI_API_KEY) throw new Error("OpenAI API key is required. set OPENAI_API_KEY environment variable or pass it as an argument");
2238
+ apiKey = process.env.OPENAI_API_KEY;
2239
+ }
2240
+ const headers = { ...model.headers };
2241
+ if (model.provider.id === Provider.KnownProviderEnum.githubCopilot) {
2242
+ const hasImages = hasCopilotVisionInput(context.messages);
2243
+ const copilotHeaders = buildCopilotDynamicHeaders({
2244
+ messages: context.messages,
2245
+ hasImages
1757
2246
  });
2247
+ Object.assign(headers, copilotHeaders);
2248
+ }
2249
+ if (optsHeaders) Object.assign(headers, optsHeaders);
2250
+ return new OpenAI({
2251
+ apiKey,
2252
+ baseURL: baseUrl,
2253
+ dangerouslyAllowBrowser: true,
2254
+ defaultHeaders: headers
2255
+ });
2256
+ }
2257
+ function mapReasoningEffort(effort, reasoningEffortMap) {
2258
+ return reasoningEffortMap[effort] ?? effort;
2259
+ }
2260
+ function maybeAddOpenRouterAnthropicCacheControl(model, messages) {
2261
+ if (model.provider.id !== Provider.KnownProviderEnum.openrouter || !model.id.startsWith("anthropic/")) return;
2262
+ for (let i = messages.length - 1; i >= 0; i--) {
2263
+ const msg = messages[i];
2264
+ if (!msg) continue;
2265
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
2266
+ const content = msg.content;
2267
+ if (typeof content === "string") {
2268
+ msg.content = [Object.assign({
2269
+ type: "text",
2270
+ text: content
2271
+ }, { cache_control: { type: "ephemeral" } })];
2272
+ return;
2273
+ }
2274
+ if (!Array.isArray(content)) continue;
2275
+ for (let j = content.length - 1; j >= 0; j--) {
2276
+ const part = content[j];
2277
+ if (part?.type === "text") {
2278
+ Object.assign(part, { cache_control: { type: "ephemeral" } });
2279
+ return;
2280
+ }
2281
+ }
1758
2282
  }
1759
- async function runAgentLoop(config, context, prompts, emit, streamFn, signal) {
1760
- /**
1761
- * maintain 2 arrays to represent state, `context` the actual full state sent to the LLM,
1762
- * `newMessages` for intermediate current loop state.
1763
- * think of newMessages as subset of context.messages for the running current loop.
1764
- * they do move at the same speed staring with initial user prompts.
1765
- * */
1766
- const newMessages = [...prompts];
1767
- const currentContext = {
1768
- ...context,
1769
- messages: [...context.messages, ...prompts]
1770
- };
1771
- await emitEvent(emit, { type: "agent.start" });
1772
- await emitEvent(emit, { type: "turn.start" });
1773
- for (const prompt of prompts) {
1774
- await emitEvent(emit, {
1775
- type: "message.start",
1776
- message: prompt
2283
+ }
2284
+ function convertMessages(model, context, compat) {
2285
+ const params = [];
2286
+ const normalizeToolCallId = (id) => {
2287
+ if (id.includes("|")) return (id.split("|")[0] ?? id).replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
2288
+ if (model.provider.id === Provider.KnownProviderEnum.openai) return id.length > 40 ? id.slice(0, 40) : id;
2289
+ return id;
2290
+ };
2291
+ const transformedMessages = Message.transformMessages(context.messages, model, normalizeToolCallId);
2292
+ if (context.systemPrompt) {
2293
+ const role = model.reasoning && compat.supportsDeveloperRole ? "developer" : "system";
2294
+ params.push({
2295
+ role,
2296
+ content: sanitizeSurrogates(context.systemPrompt)
2297
+ });
2298
+ }
2299
+ let lastRole = null;
2300
+ for (const msg of transformedMessages) {
2301
+ if (compat.requiresAssistantAfterToolResult && lastRole === "tool" && msg.role === "user") {
2302
+ params.push({
2303
+ role: "assistant",
2304
+ content: "I have processed the tool results."
1777
2305
  });
1778
- for (const [partIndex, part] of prompt.parts.entries()) {
1779
- await emitEvent(emit, {
1780
- type: "message.part.start",
1781
- message: prompt,
1782
- partIndex,
1783
- part
1784
- });
1785
- await emitEvent(emit, {
1786
- type: "message.part.end",
1787
- message: prompt,
1788
- partIndex,
1789
- part
2306
+ lastRole = "assistant";
2307
+ }
2308
+ if (msg.role === "user") {
2309
+ const content = msg.parts.flatMap((item) => {
2310
+ if (item.type === "text") {
2311
+ const text = sanitizeSurrogates(item.text);
2312
+ if (text.trim().length === 0) return [];
2313
+ return [{
2314
+ type: "text",
2315
+ text
2316
+ }];
2317
+ }
2318
+ if (!model.input.includes("image")) return [];
2319
+ return [{
2320
+ type: "image_url",
2321
+ image_url: { url: `data:${item.mimeType};base64,${item.data}` }
2322
+ }];
2323
+ });
2324
+ if (content.length === 0) continue;
2325
+ params.push({
2326
+ role: "user",
2327
+ content
2328
+ });
2329
+ lastRole = "user";
2330
+ continue;
2331
+ }
2332
+ const assistantMsg = {
2333
+ role: "assistant",
2334
+ content: compat.requiresAssistantAfterToolResult ? "" : null
2335
+ };
2336
+ const textSegments = msg.parts.filter((block) => block.type === "text").map((block) => sanitizeSurrogates(block.text)).filter((text) => text.trim().length > 0);
2337
+ const nonEmptyThinkingBlocks = msg.parts.filter((block) => block.type === "thinking").filter((block) => block.thinking.trim().length > 0);
2338
+ if (compat.requiresThinkingAsText && nonEmptyThinkingBlocks.length > 0) textSegments.unshift(...nonEmptyThinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)));
2339
+ else if (nonEmptyThinkingBlocks.length > 0) {
2340
+ const signature = nonEmptyThinkingBlocks[0]?.thinkingSignature;
2341
+ if (signature && signature.length > 0) assistantMsg[signature] = nonEmptyThinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)).join("\n");
2342
+ }
2343
+ if (textSegments.length > 0) assistantMsg.content = textSegments.join("");
2344
+ const toolCalls = msg.parts.filter((block) => block.type === "toolCall");
2345
+ if (toolCalls.length > 0) {
2346
+ assistantMsg.tool_calls = toolCalls.map((toolCall) => ({
2347
+ id: toolCall.callID,
2348
+ type: "function",
2349
+ function: {
2350
+ name: toolCall.name,
2351
+ arguments: JSON.stringify(toolCall.arguments)
2352
+ }
2353
+ }));
2354
+ const reasoningDetails = toolCalls.filter((toolCall) => toolCall.thoughtSignature).map((toolCall) => {
2355
+ try {
2356
+ return JSON.parse(toolCall.thoughtSignature);
2357
+ } catch {
2358
+ return null;
2359
+ }
2360
+ }).filter(Boolean);
2361
+ if (reasoningDetails.length > 0) assistantMsg.reasoning_details = reasoningDetails;
2362
+ }
2363
+ const assistantContent = assistantMsg.content;
2364
+ if (!(assistantContent !== null && assistantContent !== void 0 && (typeof assistantContent === "string" ? assistantContent.length > 0 : assistantContent.length > 0)) && !assistantMsg.tool_calls) continue;
2365
+ params.push(assistantMsg);
2366
+ lastRole = "assistant";
2367
+ const imageBlocks = [];
2368
+ for (const toolCall of toolCalls) {
2369
+ if (toolCall.status === "pending" || toolCall.status === "running") {
2370
+ params.push(buildSyntheticToolResult(toolCall, compat));
2371
+ lastRole = "tool";
2372
+ continue;
2373
+ }
2374
+ const textResult = toolCall.result.content.filter((content) => content.type === "text").map((content) => content.text).join("\n");
2375
+ const hasImages = toolCall.result.content.some((content) => content.type === "image");
2376
+ const toolResultMsg = {
2377
+ role: "tool",
2378
+ content: sanitizeSurrogates(textResult.length > 0 ? textResult : "(see attached image)"),
2379
+ tool_call_id: toolCall.callID
2380
+ };
2381
+ if (compat.requiresToolResultName) toolResultMsg.name = toolCall.name;
2382
+ params.push(toolResultMsg);
2383
+ lastRole = "tool";
2384
+ if (hasImages && model.input.includes("image")) {
2385
+ for (const block of toolCall.result.content) if (block.type === "image") imageBlocks.push({
2386
+ type: "image_url",
2387
+ image_url: { url: `data:${block.mimeType};base64,${block.data}` }
1790
2388
  });
1791
2389
  }
1792
- await emitEvent(emit, {
1793
- type: "message.end",
1794
- message: prompt
2390
+ }
2391
+ if (imageBlocks.length > 0) {
2392
+ if (compat.requiresAssistantAfterToolResult) params.push({
2393
+ role: "assistant",
2394
+ content: "I have processed the tool results."
2395
+ });
2396
+ params.push({
2397
+ role: "user",
2398
+ content: [{
2399
+ type: "text",
2400
+ text: "Attached image(s) from tool result:"
2401
+ }, ...imageBlocks]
1795
2402
  });
2403
+ lastRole = "user";
1796
2404
  }
1797
- await runLoop(config, currentContext, newMessages, emit, streamFn, signal);
1798
- return newMessages;
1799
- }
1800
- _Loop.runAgentLoop = runAgentLoop;
1801
- async function runAgentLoopContinue(config, context, emit, streamFn, signal) {
1802
- if (context.messages.length === 0) throw new AgentContextError({ message: "connot continue: no messages in context" });
1803
- if (context.messages[context.messages.length - 1]?.role === "assistant") throw new AgentContextError({ message: "cannot continue from message role: assistant" });
1804
- const newMessages = [];
1805
- const currentContext = { ...context };
1806
- await emitEvent(emit, { type: "agent.start" });
1807
- await emitEvent(emit, { type: "turn.start" });
1808
- await runLoop(config, currentContext, newMessages, emit, streamFn, signal);
1809
- return newMessages;
1810
- }
1811
- _Loop.runAgentLoopContinue = runAgentLoopContinue;
1812
- function run(config, context, prompts, streamFn, signal) {
1813
- const stream = createAgentStream();
1814
- runAgentLoop(config, context, prompts, async (event) => {
1815
- stream.push(event);
1816
- }, streamFn, signal).then((messages) => {
1817
- stream.end(messages);
1818
- });
1819
- return stream;
1820
2405
  }
1821
- _Loop.run = run;
1822
- function runContinue(config, context, streamFn, signal) {
1823
- const stream = createAgentStream();
1824
- runAgentLoopContinue(config, context, async (event) => {
1825
- stream.push(event);
1826
- }, streamFn, signal).then((messages) => {
1827
- stream.end(messages);
1828
- });
1829
- return stream;
2406
+ return params;
2407
+ }
2408
+ function convertTools(tools, compat) {
2409
+ return tools.map((tool) => ({
2410
+ type: "function",
2411
+ function: {
2412
+ name: tool.name,
2413
+ description: tool.description,
2414
+ parameters: tool.parameters,
2415
+ ...compat.supportsStrictMode !== false && { strict: false }
2416
+ }
2417
+ }));
2418
+ }
2419
+ function parseChunkUsage(rawUsage, model) {
2420
+ const promptTokens = rawUsage.prompt_tokens || 0;
2421
+ const reportedCachedTokens = rawUsage.prompt_tokens_details?.cached_tokens || 0;
2422
+ const cacheWriteTokens = rawUsage.prompt_tokens_details?.cache_write_tokens || 0;
2423
+ const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens || 0;
2424
+ const cacheReadTokens = cacheWriteTokens > 0 ? Math.max(0, reportedCachedTokens - cacheWriteTokens) : reportedCachedTokens;
2425
+ const input = Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens);
2426
+ const outputTokens = (rawUsage.completion_tokens || 0) + reasoningTokens;
2427
+ const usage = {
2428
+ input,
2429
+ output: outputTokens,
2430
+ cacheRead: cacheReadTokens,
2431
+ cacheWrite: cacheWriteTokens,
2432
+ totalTokens: input + outputTokens + cacheReadTokens + cacheWriteTokens,
2433
+ cost: {
2434
+ input: 0,
2435
+ output: 0,
2436
+ cacheRead: 0,
2437
+ cacheWrite: 0,
2438
+ total: 0
2439
+ }
2440
+ };
2441
+ Model.calculateCost(model, usage);
2442
+ return usage;
2443
+ }
2444
+ function mapStopReason(reason) {
2445
+ if (reason === null) return { stopReason: "stop" };
2446
+ switch (reason) {
2447
+ case "stop":
2448
+ case "end": return { stopReason: "stop" };
2449
+ case "length": return { stopReason: "length" };
2450
+ case "function_call":
2451
+ case "tool_calls": return { stopReason: "toolUse" };
2452
+ case "content_filter": return {
2453
+ stopReason: "error",
2454
+ errorMessage: "Provider finish_reason: content_filter"
2455
+ };
2456
+ case "network_error": return {
2457
+ stopReason: "error",
2458
+ errorMessage: "Provider finish_reason: network_error"
2459
+ };
2460
+ default: return {
2461
+ stopReason: "error",
2462
+ errorMessage: `Provider finish_reason: ${reason}`
2463
+ };
1830
2464
  }
1831
- _Loop.runContinue = runContinue;
1832
- })(Loop || (Loop = {}));
2465
+ }
2466
+ function detectCompat(model) {
2467
+ const provider = model.provider.id;
2468
+ const baseUrl = resolveBaseUrl(model);
2469
+ const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
2470
+ const isNonStandard = provider === "cerebras" || baseUrl.includes("cerebras.ai") || provider === "xai" || baseUrl.includes("api.x.ai") || baseUrl.includes("chutes.ai") || baseUrl.includes("deepseek.com") || isZai || provider === "opencode" || baseUrl.includes("opencode.ai");
2471
+ const useMaxTokens = baseUrl.includes("chutes.ai");
2472
+ const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
2473
+ const reasoningEffortMap = (provider === "groq" || baseUrl.includes("groq.com")) && model.id === "qwen/qwen3-32b" ? {
2474
+ minimal: "default",
2475
+ low: "default",
2476
+ medium: "default",
2477
+ high: "default",
2478
+ xhigh: "default"
2479
+ } : {};
2480
+ return {
2481
+ supportsStore: !isNonStandard,
2482
+ supportsDeveloperRole: !isNonStandard,
2483
+ supportsReasoningEffort: !isGrok && !isZai,
2484
+ reasoningEffortMap,
2485
+ supportsUsageInStreaming: true,
2486
+ maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
2487
+ requiresToolResultName: false,
2488
+ requiresAssistantAfterToolResult: false,
2489
+ requiresThinkingAsText: false,
2490
+ thinkingFormat: isZai ? "zai" : provider === "openrouter" || baseUrl.includes("openrouter.ai") ? "openrouter" : "openai",
2491
+ openRouterRouting: {},
2492
+ vercelGatewayRouting: {},
2493
+ zaiToolStream: false,
2494
+ supportsStrictMode: true
2495
+ };
2496
+ }
2497
+ /**
2498
+ * Get resolved compatibility settings for a model.
2499
+ * Uses explicit model.compat if provided, otherwise auto-detects from provider/URL.
2500
+ */
2501
+ function getCompat(model) {
2502
+ const detected = detectCompat(model);
2503
+ if (!model.compat) return detected;
2504
+ return {
2505
+ supportsStore: model.compat.supportsStore ?? detected.supportsStore,
2506
+ supportsDeveloperRole: model.compat.supportsDeveloperRole ?? detected.supportsDeveloperRole,
2507
+ supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
2508
+ reasoningEffortMap: model.compat.reasoningEffortMap ?? detected.reasoningEffortMap,
2509
+ supportsUsageInStreaming: model.compat.supportsUsageInStreaming ?? detected.supportsUsageInStreaming,
2510
+ maxTokensField: model.compat.maxTokensField ?? detected.maxTokensField,
2511
+ requiresToolResultName: model.compat.requiresToolResultName ?? detected.requiresToolResultName,
2512
+ requiresAssistantAfterToolResult: model.compat.requiresAssistantAfterToolResult ?? detected.requiresAssistantAfterToolResult,
2513
+ requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
2514
+ thinkingFormat: model.compat.thinkingFormat ?? detected.thinkingFormat,
2515
+ openRouterRouting: model.compat.openRouterRouting ?? {},
2516
+ vercelGatewayRouting: model.compat.vercelGatewayRouting ?? detected.vercelGatewayRouting,
2517
+ zaiToolStream: model.compat.zaiToolStream ?? detected.zaiToolStream,
2518
+ supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode
2519
+ };
2520
+ }
2521
+ const openAICompletionsProtocolProvider = {
2522
+ protocol: Model.KnownProtocolEnum.openaiCompletions,
2523
+ stream: streamOpenAICompletions,
2524
+ streamSimple: streamSimpleOpenAICompletions
2525
+ };
2526
+ //#endregion
2527
+ //#region src/provider/register.ts
2528
+ Stream.registerProtocolProvider(anthropicProtocolProvider, "providers/anthropic/index.ts");
2529
+ Stream.registerProtocolProvider(openAICompletionsProtocolProvider, "providers/openai/completions.ts");
1833
2530
  //#endregion
1834
2531
  //#region src/agent/agent.ts
1835
2532
  let Agent;
@@ -2297,14 +2994,356 @@ let Agent;
2297
2994
  _Agent.create = create;
2298
2995
  })(Agent || (Agent = {}));
2299
2996
  //#endregion
2300
- //#region src/agent.ts
2301
- const agent = {
2302
- loop: Loop.run,
2303
- loopContinue: Loop.runContinue
2304
- };
2997
+ //#region src/agent/codemode/codemode.ts
2998
+ let CodeMode;
2999
+ (function(_CodeMode) {
3000
+ const ToolDefinitionErr = _CodeMode.ToolDefinitionErr = NamedError.create("ToolDefinitionErr", Type.Object({ message: Type.String() }));
3001
+ const toolFnPrefix = process.env.CODEWORK_CODEMODE_FN_PREFIX ?? "external_";
3002
+ const SandboxExecutionInputSchema = _CodeMode.SandboxExecutionInputSchema = Type.Object({ typescriptCode: Type.String({ description: "TypeScript code to execute in the sandbox." }) });
3003
+ const SandboxExecutionOutputSchema = _CodeMode.SandboxExecutionOutputSchema = Type.Object({
3004
+ result: Type.Optional(Type.Unknown()),
3005
+ logs: Type.Optional(Type.Array(Type.String()))
3006
+ });
3007
+ const SandboxExecutionErrorSchema = _CodeMode.SandboxExecutionErrorSchema = Type.Object({
3008
+ message: Type.String(),
3009
+ name: Type.Optional(Type.String()),
3010
+ stack: Type.Optional(Type.String()),
3011
+ line: Type.Optional(Type.Number()),
3012
+ logs: Type.Optional(Type.Array(Type.String()))
3013
+ });
3014
+ function withMessageInStack(message, stack) {
3015
+ if (!stack) return void 0;
3016
+ return stack.includes(message) ? stack : `${message}\n${stack}`;
3017
+ }
3018
+ function normalizeExecutionError(error) {
3019
+ if (error instanceof Error) return {
3020
+ name: error.name,
3021
+ message: error.message,
3022
+ stack: withMessageInStack(error.message, error.stack),
3023
+ line: parseErrorLine(error.stack)
3024
+ };
3025
+ if (typeof error === "object" && error !== null) {
3026
+ const candidate = error;
3027
+ return {
3028
+ name: typeof candidate.name === "string" ? candidate.name : void 0,
3029
+ message: typeof candidate.message === "string" ? candidate.message : JSON.stringify(candidate) || "Unknown execution error",
3030
+ stack: typeof candidate.stack === "string" ? withMessageInStack(typeof candidate.message === "string" ? candidate.message : JSON.stringify(candidate) || "Unknown execution error", candidate.stack) : void 0,
3031
+ line: typeof candidate.line === "number" ? candidate.line : parseErrorLine(candidate.stack)
3032
+ };
3033
+ }
3034
+ return {
3035
+ name: "Error",
3036
+ message: String(error)
3037
+ };
3038
+ }
3039
+ function parseErrorLine(stack) {
3040
+ if (typeof stack !== "string") return void 0;
3041
+ const lineMatch = stack.match(/:(\d+)(?::\d+)?\)?$/m);
3042
+ return lineMatch ? Number(lineMatch[1]) : void 0;
3043
+ }
3044
+ function serializeForContent(value) {
3045
+ return JSON.stringify(value, (_key, currentValue) => typeof currentValue === "bigint" ? currentValue.toString() : currentValue, 2);
3046
+ }
3047
+ function toolContentToText(result) {
3048
+ return result.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n").trim();
3049
+ }
3050
+ function unwrapToolResult(tool, result) {
3051
+ if (result.status === "completed") {
3052
+ if (tool.outputSchema) return validateSchema(tool.outputSchema, result.result.details, `Tool "${tool.name}" output`);
3053
+ return result.result.details;
3054
+ }
3055
+ if (tool.errorSchema && result.result.details !== void 0) validateSchema(tool.errorSchema, result.result.details, `Tool "${tool.name}" error`);
3056
+ const message = toolContentToText(result.result) || `Tool "${tool.name}" failed`;
3057
+ const error = new Error(message);
3058
+ if (result.result.details !== void 0) error.cause = result.result.details;
3059
+ throw error;
3060
+ }
3061
+ function toolToBinding(tool, prefix = "") {
3062
+ const inputSchema = TypeGuard.IsSchema(tool.parameters) ? tool.parameters : Type.Object({});
3063
+ const outputSchema = TypeGuard.IsSchema(tool.outputSchema) ? tool.outputSchema : void 0;
3064
+ const errorSchema = TypeGuard.IsSchema(tool.errorSchema) ? tool.errorSchema : void 0;
3065
+ if (!("execute" in tool) || typeof tool.execute !== "function") throw new ToolDefinitionErr({ message: `tool "${tool.name}" does not have an execute function. code mode requires tools with implementation` });
3066
+ return {
3067
+ name: `${prefix}${tool.name}`,
3068
+ description: tool.description,
3069
+ inputSchema,
3070
+ outputSchema,
3071
+ errorSchema,
3072
+ execute: async (callID, params, signal) => {
3073
+ const validatedParams = validateSchema(inputSchema, params, `Tool "${tool.name}" input`);
3074
+ return unwrapToolResult(tool, await tool.execute(callID, validatedParams, signal));
3075
+ }
3076
+ };
3077
+ }
3078
+ function toolsForBinding(tools, prefix) {
3079
+ const bindings = {};
3080
+ for (const tool of tools) {
3081
+ const name = `${prefix}${tool.name}`;
3082
+ bindings[name] = toolToBinding(tool, prefix);
3083
+ }
3084
+ return bindings;
3085
+ }
3086
+ function indent(value, depth = 1) {
3087
+ const prefix = " ".repeat(depth);
3088
+ return value.split("\n").map((line) => `${prefix}${line}`).join("\n");
3089
+ }
3090
+ function toPropertyKey(name) {
3091
+ return /^[$A-Z_a-z][$\w]*$/i.test(name) ? name : JSON.stringify(name);
3092
+ }
3093
+ function schemaToTypeScript(schema) {
3094
+ if (TypeGuard.IsAny(schema)) return "any";
3095
+ if (TypeGuard.IsUnknown(schema)) return "unknown";
3096
+ if (TypeGuard.IsString(schema)) return "string";
3097
+ if (TypeGuard.IsNumber(schema) || TypeGuard.IsInteger(schema)) return "number";
3098
+ if (TypeGuard.IsBoolean(schema)) return "boolean";
3099
+ if (TypeGuard.IsNull(schema)) return "null";
3100
+ if (TypeGuard.IsUndefined(schema) || TypeGuard.IsVoid(schema)) return "undefined";
3101
+ if (TypeGuard.IsLiteral(schema)) return JSON.stringify(schema.const);
3102
+ if (TypeGuard.IsArray(schema)) return `Array<${schemaToTypeScript(schema.items)}>`;
3103
+ if (TypeGuard.IsTuple(schema)) return `[${(schema.items ?? []).map((item) => schemaToTypeScript(item)).join(", ")}]`;
3104
+ if (TypeGuard.IsUnion(schema)) return schema.anyOf.map((item) => schemaToTypeScript(item)).join(" | ");
3105
+ if (TypeGuard.IsIntersect(schema)) return schema.allOf.map((item) => schemaToTypeScript(item)).join(" & ");
3106
+ if (TypeGuard.IsRecord(schema)) return `Record<string, ${schemaToTypeScript(Object.values(schema.patternProperties ?? {}).find(TypeGuard.IsSchema) ?? (schema.additionalProperties && TypeGuard.IsSchema(schema.additionalProperties) ? schema.additionalProperties : Type.Unknown()))}>`;
3107
+ if (TypeGuard.IsObject(schema)) {
3108
+ const required = new Set(schema.required ?? []);
3109
+ const properties = Object.entries(schema.properties ?? {}).map(([key, value]) => {
3110
+ const optional = required.has(key) ? "" : "?";
3111
+ return `${toPropertyKey(key)}${optional}: ${schemaToTypeScript(value)};`;
3112
+ });
3113
+ if (schema.additionalProperties === true) properties.push("[key: string]: unknown;");
3114
+ else if (schema.additionalProperties && TypeGuard.IsSchema(schema.additionalProperties)) properties.push(`[key: string]: ${schemaToTypeScript(schema.additionalProperties)};`);
3115
+ if (properties.length === 0) return "{}";
3116
+ return `{\n${indent(properties.join("\n"))}\n}`;
3117
+ }
3118
+ return "unknown";
3119
+ }
3120
+ function generateTypeStubs(bindings, options = {}) {
3121
+ const { includeDescriptions = true } = options;
3122
+ const declarations = [];
3123
+ for (const [name, binding] of Object.entries(bindings)) {
3124
+ const inputTypeName = `${capitalize(name)}Input`;
3125
+ const outputTypeName = `${capitalize(name)}Output`;
3126
+ declarations.push(`type ${inputTypeName} = ${schemaToTypeScript(binding.inputSchema)};`);
3127
+ let outputTypeRef = "unknown";
3128
+ if (binding.outputSchema !== void 0) {
3129
+ declarations.push(`type ${outputTypeName} = ${schemaToTypeScript(binding.outputSchema)};`);
3130
+ outputTypeRef = outputTypeName;
3131
+ }
3132
+ const description = includeDescriptions && binding.description ? `/** ${binding.description} */\n` : "";
3133
+ declarations.push(`${description}declare function ${name}(input: ${inputTypeName}): Promise<${outputTypeRef}>;`);
3134
+ }
3135
+ return declarations.join("\n\n");
3136
+ }
3137
+ _CodeMode.generateTypeStubs = generateTypeStubs;
3138
+ function buildExampleSnippet() {
3139
+ return [
3140
+ "```typescript",
3141
+ "const numbers = [12, 24, 36];",
3142
+ "const total = numbers.reduce((sum, value) => sum + value, 0);",
3143
+ "",
3144
+ "return {",
3145
+ " count: numbers.length,",
3146
+ " average: total / numbers.length,",
3147
+ "};",
3148
+ "```"
3149
+ ];
3150
+ }
3151
+ function generateSystemPrompt(config) {
3152
+ const fnPrefix = config.fnPrefix ?? toolFnPrefix;
3153
+ const bindings = toolsForBinding(config.tools, fnPrefix);
3154
+ const typeStubs = generateTypeStubs(bindings);
3155
+ return [
3156
+ "## Code Execution Tool",
3157
+ "",
3158
+ "You have access to `sandbox_execute_typescript` which runs TypeScript code in a sandboxed environment.",
3159
+ "",
3160
+ "### When to Use",
3161
+ "",
3162
+ "Use `sandbox_execute_typescript` when you need to:",
3163
+ "- Process data with loops, conditionals, or complex logic",
3164
+ "- Make multiple tool calls in parallel with `Promise.all(...)`",
3165
+ "- Transform, filter, or aggregate data",
3166
+ "- Perform calculations or data analysis",
3167
+ "",
3168
+ "For simple operations, prefer calling tools directly.",
3169
+ "",
3170
+ "### Available External APIs",
3171
+ "",
3172
+ "Inside your TypeScript code, you can call these async functions:",
3173
+ "",
3174
+ Object.entries(bindings).map(([name, binding]) => `- \`${name}(input)\`: ${binding.description}`).join("\n") || "- No external APIs available for this run.",
3175
+ "",
3176
+ "### Type Definitions",
3177
+ "",
3178
+ "```typescript",
3179
+ typeStubs,
3180
+ "```",
3181
+ "",
3182
+ "### Example",
3183
+ "",
3184
+ ...buildExampleSnippet(),
3185
+ "",
3186
+ "### Important Notes",
3187
+ "",
3188
+ `- All\`${fnPrefix}\`*calls are async, so always use \`await\``,
3189
+ "- Return a value to pass results back to you",
3190
+ "- Use `console.log()` for debugging (logs are captured)",
3191
+ "- The sandbox is isolated - no network access or file system",
3192
+ "- Each execution is isolated and does not share state with previous runs",
3193
+ ""
3194
+ ].join("\n");
3195
+ }
3196
+ _CodeMode.generateSystemPrompt = generateSystemPrompt;
3197
+ async function transpileTypeScript(typescriptCode) {
3198
+ return (await transform([
3199
+ "(async () => {",
3200
+ " const __codemodeNormalizeError = (error: unknown) => {",
3201
+ " if (error instanceof Error) return { name: error.name, message: error.message, stack: error.stack };",
3202
+ " if (typeof error === 'object' && error !== null) {",
3203
+ " const candidate = error as { name?: unknown; message?: unknown; stack?: unknown };",
3204
+ " return {",
3205
+ " name: typeof candidate.name === 'string' ? candidate.name : 'Error',",
3206
+ " message:",
3207
+ " typeof candidate.message === 'string'",
3208
+ " ? candidate.message",
3209
+ " : JSON.stringify(candidate) || 'Unknown execution error',",
3210
+ " stack: typeof candidate.stack === 'string' ? candidate.stack : undefined,",
3211
+ " };",
3212
+ " }",
3213
+ " return { name: 'Error', message: String(error) };",
3214
+ " };",
3215
+ " try {",
3216
+ typescriptCode,
3217
+ " } catch (error) {",
3218
+ " throw __codemodeNormalizeError(error);",
3219
+ " }",
3220
+ "})()"
3221
+ ].join("\n"), {
3222
+ loader: "ts",
3223
+ target: "es2022",
3224
+ sourcefile: "script.ts",
3225
+ minify: false,
3226
+ keepNames: false
3227
+ })).code;
3228
+ }
3229
+ function buildToolDescription(tools, fnPrefix = toolFnPrefix) {
3230
+ return [
3231
+ "Execute TypeScript code in a secure sandbox environment. ",
3232
+ "The code can use these external API functions:",
3233
+ "<functions>",
3234
+ ` ${tools.map((tool) => `${fnPrefix}${tool.name}`).join("\n")}`,
3235
+ "</functions>",
3236
+ `All ${fnPrefix}* calls are async and must be awaited. `,
3237
+ "Return a value to pass results back. Use console.log() for debugging."
3238
+ ].join("\n");
3239
+ }
3240
+ function createCodeModeTool(config) {
3241
+ if (config.tools.length === 0) throw new ToolDefinitionErr({ message: "at least one tool must be provided to `createCodeModeTool`" });
3242
+ return Agent.defineTool({
3243
+ name: "sandbox_execute_typescript",
3244
+ label: "Sandbox TypeScript",
3245
+ description: buildToolDescription(config.tools, config.fnPrefix),
3246
+ parameters: SandboxExecutionInputSchema,
3247
+ outputSchema: SandboxExecutionOutputSchema,
3248
+ errorSchema: SandboxExecutionErrorSchema,
3249
+ async execute(_callID, params, signal, onUpdate) {
3250
+ const bindings = toolsForBinding(config.tools, config.fnPrefix ?? toolFnPrefix);
3251
+ let context;
3252
+ try {
3253
+ await onUpdate?.({
3254
+ status: "running",
3255
+ partial: { content: [{
3256
+ type: "text",
3257
+ text: "Transpiling TypeScript for sandbox execution"
3258
+ }] }
3259
+ });
3260
+ const javascriptCode = await transpileTypeScript(params.typescriptCode);
3261
+ await onUpdate?.({
3262
+ status: "running",
3263
+ partial: { content: [{
3264
+ type: "text",
3265
+ text: "Executing code in sandbox runtime"
3266
+ }] }
3267
+ });
3268
+ context = await config.driver.createContext({
3269
+ bindings,
3270
+ timeout: config.timeout,
3271
+ memoryLimit: config.memoryLimit,
3272
+ signal
3273
+ });
3274
+ const executionResult = await context.execute(javascriptCode);
3275
+ if (executionResult.success) {
3276
+ const details = {
3277
+ result: executionResult.value,
3278
+ ...executionResult.logs && executionResult.logs.length > 0 ? { logs: executionResult.logs } : {}
3279
+ };
3280
+ return {
3281
+ status: "completed",
3282
+ result: {
3283
+ content: [{
3284
+ type: "text",
3285
+ text: serializeForContent(details)
3286
+ }],
3287
+ details,
3288
+ isError: false
3289
+ }
3290
+ };
3291
+ }
3292
+ const details = {
3293
+ message: executionResult.error?.message || "sandbox execution failed",
3294
+ ...executionResult.error?.name ? { name: executionResult.error.name } : {},
3295
+ ...executionResult.error?.stack ? { stack: executionResult.error.stack } : {},
3296
+ ...executionResult.error?.line != null ? { line: executionResult.error.line } : {},
3297
+ ...executionResult.logs && executionResult.logs.length > 0 ? { logs: executionResult.logs } : {}
3298
+ };
3299
+ return {
3300
+ status: "error",
3301
+ result: {
3302
+ content: [{
3303
+ type: "text",
3304
+ text: serializeForContent(details)
3305
+ }],
3306
+ details,
3307
+ isError: true
3308
+ }
3309
+ };
3310
+ } catch (error) {
3311
+ const normalizedError = normalizeExecutionError(error);
3312
+ const details = {
3313
+ message: normalizedError.message,
3314
+ ...normalizedError.name ? { name: normalizedError.name } : {},
3315
+ ...normalizedError.stack ? { stack: normalizedError.stack } : {},
3316
+ ...normalizedError.line != null ? { line: normalizedError.line } : {}
3317
+ };
3318
+ return {
3319
+ status: "error",
3320
+ result: {
3321
+ content: [{
3322
+ type: "text",
3323
+ text: serializeForContent(details)
3324
+ }],
3325
+ details,
3326
+ isError: true
3327
+ }
3328
+ };
3329
+ } finally {
3330
+ await context?.dispose();
3331
+ }
3332
+ }
3333
+ });
3334
+ }
3335
+ _CodeMode.createCodeModeTool = createCodeModeTool;
3336
+ async function create(config) {
3337
+ return {
3338
+ tool: createCodeModeTool(config),
3339
+ systemPrompt: generateSystemPrompt(config)
3340
+ };
3341
+ }
3342
+ _CodeMode.create = create;
3343
+ })(CodeMode || (CodeMode = {}));
2305
3344
  //#endregion
2306
3345
  //#region src/llm.ts
2307
- const llmImpl = async (provider, model) => Model.getModel(provider, model);
3346
+ const llmImpl = async (provider, model, overrides) => Model.getModel(provider, model, overrides);
2308
3347
  const llm = Object.assign(llmImpl, {
2309
3348
  model: Model.getModel,
2310
3349
  models: Model.getModels,
@@ -2337,6 +3376,6 @@ Stream.complete;
2337
3376
  Stream.streamSimple;
2338
3377
  Stream.completeSimple;
2339
3378
  //#endregion
2340
- export { Agent, Message, agent, llm, stream };
3379
+ export { Agent, CodeMode, Message, agent, llm, stream };
2341
3380
 
2342
3381
  //# sourceMappingURL=index.mjs.map