@cognipeer/agent-sdk 0.4.9 → 0.5.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/dist/index.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var zod = require('zod');
4
4
  var module$1 = require('module');
5
+ var zodToJsonSchema = require('zod-to-json-schema');
5
6
  var fs = require('fs');
6
7
  var path = require('path');
7
8
  var buffer = require('buffer');
@@ -134,12 +135,377 @@ function createSimpleTool({
134
135
  };
135
136
  }
136
137
 
138
+ // src/structuredOutput/types.ts
139
+ function getNestedValue(obj, path2) {
140
+ let current = obj;
141
+ for (const key of path2) {
142
+ if (current == null || typeof current !== "object") return void 0;
143
+ current = current[key];
144
+ }
145
+ return current;
146
+ }
147
+ function formatZodError(error, rawContent) {
148
+ const fieldErrors = error.issues.map((issue) => ({
149
+ path: issue.path.join(".") || "root",
150
+ expected: issue.expected ?? issue.code,
151
+ received: issue.received ?? "unknown",
152
+ message: issue.message
153
+ }));
154
+ return {
155
+ type: "validation_error",
156
+ message: `Schema validation failed: ${fieldErrors.map((f) => `${f.path}: ${f.message}`).join("; ")}`,
157
+ fieldErrors,
158
+ rawContent
159
+ };
160
+ }
161
+
162
+ // src/structuredOutput/nativeStrategy.ts
163
+ var NativeJsonSchemaStrategy = class {
164
+ constructor() {
165
+ this.kind = "native";
166
+ }
167
+ resolveJsonPointer(document, pointer) {
168
+ if (!pointer.startsWith("#/")) {
169
+ return void 0;
170
+ }
171
+ const segments = pointer.slice(2).split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
172
+ let current = document;
173
+ for (const segment of segments) {
174
+ if (!current || typeof current !== "object" || !(segment in current)) {
175
+ return void 0;
176
+ }
177
+ current = current[segment];
178
+ }
179
+ return current;
180
+ }
181
+ unwrapRootObjectSchema(schema) {
182
+ if (schema.type === "object") {
183
+ return schema;
184
+ }
185
+ const ref = typeof schema.$ref === "string" ? schema.$ref : null;
186
+ if (!ref) {
187
+ return schema;
188
+ }
189
+ const resolved = this.resolveJsonPointer(schema, ref);
190
+ if (!resolved || typeof resolved !== "object" || Array.isArray(resolved)) {
191
+ return schema;
192
+ }
193
+ const resolvedSchema = { ...resolved };
194
+ if (resolvedSchema.type !== "object") {
195
+ return schema;
196
+ }
197
+ if (schema.definitions && resolvedSchema.definitions === void 0) {
198
+ resolvedSchema.definitions = schema.definitions;
199
+ }
200
+ if (schema.$defs && resolvedSchema.$defs === void 0) {
201
+ resolvedSchema.$defs = schema.$defs;
202
+ }
203
+ return resolvedSchema;
204
+ }
205
+ normalizeOpenAIStrictSchema(schema) {
206
+ const visit = (node) => {
207
+ if (!node || typeof node !== "object") return node;
208
+ if (Array.isArray(node)) {
209
+ return node.map(visit);
210
+ }
211
+ const clone2 = { ...node };
212
+ if (clone2.properties && typeof clone2.properties === "object") {
213
+ const normalizedProperties = {};
214
+ for (const [key, value] of Object.entries(clone2.properties)) {
215
+ normalizedProperties[key] = visit(value);
216
+ }
217
+ clone2.properties = normalizedProperties;
218
+ clone2.required = Object.keys(normalizedProperties);
219
+ if (clone2.additionalProperties === void 0) {
220
+ clone2.additionalProperties = false;
221
+ }
222
+ }
223
+ if (clone2.items) {
224
+ clone2.items = visit(clone2.items);
225
+ }
226
+ if (clone2.definitions && typeof clone2.definitions === "object") {
227
+ const normalizedDefinitions = {};
228
+ for (const [key, value] of Object.entries(clone2.definitions)) {
229
+ normalizedDefinitions[key] = visit(value);
230
+ }
231
+ clone2.definitions = normalizedDefinitions;
232
+ }
233
+ if (clone2.$defs && typeof clone2.$defs === "object") {
234
+ const normalizedDefs = {};
235
+ for (const [key, value] of Object.entries(clone2.$defs)) {
236
+ normalizedDefs[key] = visit(value);
237
+ }
238
+ clone2.$defs = normalizedDefs;
239
+ }
240
+ for (const compositeKey of ["anyOf", "oneOf", "allOf"]) {
241
+ if (Array.isArray(clone2[compositeKey])) {
242
+ clone2[compositeKey] = clone2[compositeKey].map(visit);
243
+ }
244
+ }
245
+ return clone2;
246
+ };
247
+ return visit(schema);
248
+ }
249
+ /**
250
+ * Convert a Zod schema to the JSON Schema object used in response_format.
251
+ */
252
+ toJsonSchema(schema, name = "structured_response") {
253
+ const result = zodToJsonSchema.zodToJsonSchema(schema, {
254
+ name,
255
+ openaiStrictMode: true,
256
+ nameStrategy: "duplicate-ref",
257
+ $refStrategy: "extract-to-root",
258
+ nullableStrategy: "property"
259
+ });
260
+ const normalized = this.normalizeOpenAIStrictSchema(result);
261
+ return this.unwrapRootObjectSchema(normalized);
262
+ }
263
+ /**
264
+ * Build response_format configuration for the model call.
265
+ * Returns the object that should be spread into model invocation options.
266
+ */
267
+ buildResponseFormat(schema, name) {
268
+ const schemaName = name || "structured_response";
269
+ const jsonSchema = this.toJsonSchema(schema, schemaName);
270
+ delete jsonSchema["$schema"];
271
+ return {
272
+ response_format: {
273
+ type: "json_schema",
274
+ json_schema: {
275
+ name: schemaName,
276
+ strict: true,
277
+ schema: jsonSchema
278
+ }
279
+ }
280
+ };
281
+ }
282
+ /**
283
+ * Parse and validate model response content.
284
+ * With native support, content is guaranteed JSON — but we still validate with Zod
285
+ * for type safety and field-level error reporting.
286
+ */
287
+ extractOutput(content, schema) {
288
+ try {
289
+ const raw = JSON.parse(content);
290
+ const validated = schema.parse(raw);
291
+ return { data: validated };
292
+ } catch (e) {
293
+ if (e?.issues) {
294
+ return { error: formatZodError(e, content) };
295
+ }
296
+ return {
297
+ error: {
298
+ type: "parse_error",
299
+ message: `Failed to parse native structured output: ${e?.message}`,
300
+ rawContent: content
301
+ }
302
+ };
303
+ }
304
+ }
305
+ buildCorrectionPrompt(error, previousAttempt) {
306
+ const fieldIssues = error.issues.map((issue) => {
307
+ const pathStr = issue.path.join(".") || "root";
308
+ const received = getNestedValue(previousAttempt, issue.path);
309
+ return `- "${pathStr}": ${issue.message} (received: ${JSON.stringify(received)})`;
310
+ }).join("\n");
311
+ return [
312
+ "Your previous JSON response had validation errors:",
313
+ fieldIssues,
314
+ "",
315
+ "Please output corrected JSON matching the schema."
316
+ ].join("\n");
317
+ }
318
+ };
319
+
320
+ // src/tool.ts
321
+ function createTool({
322
+ name,
323
+ description,
324
+ schema,
325
+ func,
326
+ needsApproval,
327
+ approvalPrompt,
328
+ approvalDefaults,
329
+ maxExecutionsPerRun
330
+ }) {
331
+ const execute = async (input) => func(input);
332
+ const toolRecord = {
333
+ name,
334
+ description,
335
+ schema,
336
+ // Our runtime prefers invoke but fallback helpers keep compatibility with LC-style tools.
337
+ invoke: execute,
338
+ call: execute,
339
+ run: execute,
340
+ func: execute
341
+ };
342
+ if (typeof needsApproval === "boolean") {
343
+ toolRecord.needsApproval = needsApproval;
344
+ }
345
+ if (approvalPrompt !== void 0) {
346
+ toolRecord.approvalPrompt = approvalPrompt;
347
+ }
348
+ if (approvalDefaults !== void 0) {
349
+ toolRecord.approvalDefaults = approvalDefaults;
350
+ }
351
+ if (maxExecutionsPerRun !== void 0) {
352
+ toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
353
+ }
354
+ toolRecord.__source = toolRecord.__source || "smart";
355
+ toolRecord.__impl = func;
356
+ return toolRecord;
357
+ }
358
+
359
+ // src/structuredOutput/toolStrategy.ts
360
+ var ToolBasedStrategy = class {
361
+ constructor() {
362
+ this.kind = "tool_based";
363
+ }
364
+ /**
365
+ * Create the `response` tool for a given Zod schema.
366
+ * The tool validates input against the schema and returns a sentinel object
367
+ * that the tools node picks up to finalize the structured output.
368
+ */
369
+ createResponseTool(schema) {
370
+ return createTool({
371
+ name: "response",
372
+ description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
373
+ schema,
374
+ func: async (data) => {
375
+ try {
376
+ const validated = schema.parse ? schema.parse(data) : data;
377
+ return { __finalStructuredOutput: true, data: validated };
378
+ } catch (e) {
379
+ const zodError = e;
380
+ if (zodError?.issues) {
381
+ const soError = formatZodError(zodError);
382
+ return {
383
+ error: "Schema validation failed",
384
+ details: soError.message,
385
+ fieldErrors: soError.fieldErrors
386
+ };
387
+ }
388
+ return { error: "Schema validation failed", details: e?.message };
389
+ }
390
+ }
391
+ });
392
+ }
393
+ /**
394
+ * Try to extract structured output from raw assistant text content.
395
+ * Used as a last resort when the model writes JSON directly instead of calling the tool.
396
+ */
397
+ extractFromText(content, schema) {
398
+ let jsonText = null;
399
+ const fenced = content.match(/```(?:json)?\n([\s\S]*?)```/i);
400
+ if (fenced && fenced[1]) {
401
+ jsonText = fenced[1].trim();
402
+ } else {
403
+ const braceIdx = content.indexOf("{");
404
+ const bracketIdx = content.indexOf("[");
405
+ const candidates = [braceIdx, bracketIdx].filter((i) => i >= 0).sort((a, b) => a - b);
406
+ if (candidates.length > 0) {
407
+ jsonText = content.slice(candidates[0]).trim();
408
+ }
409
+ }
410
+ try {
411
+ const raw = JSON.parse(jsonText ?? content);
412
+ const validated = schema.parse(raw);
413
+ return { data: validated };
414
+ } catch (e) {
415
+ if (e?.issues) {
416
+ return { error: formatZodError(e, content) };
417
+ }
418
+ return {
419
+ error: {
420
+ type: "parse_error",
421
+ message: `Failed to parse JSON from assistant message: ${e?.message}`,
422
+ rawContent: content
423
+ }
424
+ };
425
+ }
426
+ }
427
+ buildCorrectionPrompt(error, previousAttempt) {
428
+ const fieldIssues = error.issues.map((issue) => {
429
+ const pathStr = issue.path.join(".") || "root";
430
+ const received = getNestedValue(previousAttempt, issue.path);
431
+ return `- "${pathStr}": ${issue.message} (received: ${JSON.stringify(received)})`;
432
+ }).join("\n");
433
+ return [
434
+ "Your previous structured output had validation errors:",
435
+ fieldIssues,
436
+ "",
437
+ "Please call `response` again with corrected values matching the schema."
438
+ ].join("\n");
439
+ }
440
+ };
441
+
442
+ // src/structuredOutput/resolver.ts
443
+ function resolveStrategy(model) {
444
+ const capabilities = getModelCapabilities(model);
445
+ if (capabilities.structuredOutput === "native") {
446
+ return new NativeJsonSchemaStrategy();
447
+ }
448
+ return new ToolBasedStrategy();
449
+ }
450
+ function getModelCapabilities(model) {
451
+ if (model?.capabilities && typeof model.capabilities === "object") {
452
+ return model.capabilities;
453
+ }
454
+ const provider = detectProvider(model);
455
+ if (provider) {
456
+ return {
457
+ provider,
458
+ structuredOutput: supportsNativeStructuredOutput(provider) ? "native" : "tool_based",
459
+ strictToolCalling: requiresStrictToolCalling(provider),
460
+ streaming: true
461
+ };
462
+ }
463
+ return { structuredOutput: "tool_based" };
464
+ }
465
+ function detectProvider(model) {
466
+ if (!model || typeof model !== "object") return void 0;
467
+ if (typeof model.provider === "string") return model.provider;
468
+ const lcModel = model._lc;
469
+ if (lcModel) {
470
+ return detectProviderFromLCModel(lcModel);
471
+ }
472
+ const modelName = model.modelName || model.model || "";
473
+ if (modelName.startsWith("gpt-") || modelName.startsWith("o1") || modelName.startsWith("o3") || modelName.startsWith("o4")) return "openai";
474
+ if (modelName.startsWith("claude")) return "anthropic";
475
+ if (modelName.startsWith("gemini")) return "google";
476
+ if (modelName.startsWith("amazon.") || modelName.startsWith("anthropic.")) return "bedrock";
477
+ return void 0;
478
+ }
479
+ function detectProviderFromLCModel(lcModel) {
480
+ if (!lcModel) return void 0;
481
+ const className = lcModel.constructor?.name || "";
482
+ if (className.includes("ChatOpenAI") || className.includes("AzureChatOpenAI")) return "openai";
483
+ if (className.includes("ChatAnthropic")) return "anthropic";
484
+ if (className.includes("ChatGoogleGenerativeAI") || className.includes("ChatVertexAI")) return "google";
485
+ if (className.includes("ChatBedrock") || className.includes("BedrockChat")) return "bedrock";
486
+ if (className.includes("ChatGroq")) return "groq";
487
+ if (className.includes("ChatMistralAI")) return "mistral";
488
+ const llmType = typeof lcModel._llmType === "function" ? lcModel._llmType() : lcModel._llmType;
489
+ if (typeof llmType === "string") {
490
+ if (llmType.includes("openai")) return "openai";
491
+ if (llmType.includes("anthropic")) return "anthropic";
492
+ if (llmType.includes("google")) return "google";
493
+ }
494
+ return void 0;
495
+ }
496
+ function supportsNativeStructuredOutput(provider) {
497
+ return ["openai", "google", "groq", "mistral"].includes(provider);
498
+ }
499
+ function requiresStrictToolCalling(provider) {
500
+ return ["openai"].includes(provider);
501
+ }
502
+
137
503
  // src/model.ts
138
504
  function isSmartModel(m) {
139
505
  return !!m && typeof m === "object" && typeof m.invoke === "function";
140
506
  }
141
- function withTools(model, tools) {
142
- if (model?.bindTools) return model.bindTools(tools);
507
+ function withTools(model, tools, options) {
508
+ if (model?.bindTools) return model.bindTools(tools, options);
143
509
  return model;
144
510
  }
145
511
  function fromLangchainModel(lcModel) {
@@ -184,7 +550,8 @@ function fromLangchainModel(lcModel) {
184
550
  role: response.role || "assistant",
185
551
  content,
186
552
  tool_calls: response.tool_calls,
187
- usage: response.usage,
553
+ usage: response.usage ?? response.usage_metadata ?? response.response_metadata?.token_usage ?? response.response_metadata?.tokenUsage,
554
+ usage_metadata: response.usage_metadata,
188
555
  response_metadata: response.response_metadata,
189
556
  ...response
190
557
  };
@@ -200,14 +567,14 @@ function fromLangchainModel(lcModel) {
200
567
  yield chunk;
201
568
  }
202
569
  },
203
- bindTools: (tools) => {
570
+ bindTools: (tools, options) => {
204
571
  const lcReady = toLangchainTools(tools);
205
572
  if (typeof lcModel.bindTools === "function") {
206
- const bound = lcModel.bindTools(lcReady);
573
+ const bound = lcModel.bindTools(lcReady, options);
207
574
  return fromLangchainModel(bound);
208
575
  }
209
576
  if (typeof lcModel.bind === "function") {
210
- const bound = lcModel.bind({ tools: lcReady });
577
+ const bound = lcModel.bind({ tools: lcReady, ...options || {} });
211
578
  return fromLangchainModel(bound);
212
579
  }
213
580
  return adapted;
@@ -215,48 +582,10 @@ function fromLangchainModel(lcModel) {
215
582
  modelName: lcModel.modelName || lcModel._modelId || lcModel._llmType || lcModel.name,
216
583
  _lc: lcModel
217
584
  };
585
+ adapted.capabilities = getModelCapabilities(adapted);
218
586
  return adapted;
219
587
  }
220
588
 
221
- // src/tool.ts
222
- function createTool({
223
- name,
224
- description,
225
- schema,
226
- func,
227
- needsApproval,
228
- approvalPrompt,
229
- approvalDefaults,
230
- maxExecutionsPerRun
231
- }) {
232
- const execute = async (input) => func(input);
233
- const toolRecord = {
234
- name,
235
- description,
236
- schema,
237
- // Our runtime prefers invoke but fallback helpers keep compatibility with LC-style tools.
238
- invoke: execute,
239
- call: execute,
240
- run: execute,
241
- func: execute
242
- };
243
- if (typeof needsApproval === "boolean") {
244
- toolRecord.needsApproval = needsApproval;
245
- }
246
- if (approvalPrompt !== void 0) {
247
- toolRecord.approvalPrompt = approvalPrompt;
248
- }
249
- if (approvalDefaults !== void 0) {
250
- toolRecord.approvalDefaults = approvalDefaults;
251
- }
252
- if (maxExecutionsPerRun !== void 0) {
253
- toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
254
- }
255
- toolRecord.__source = toolRecord.__source || "smart";
256
- toolRecord.__impl = func;
257
- return toolRecord;
258
- }
259
-
260
589
  // src/prompts.ts
261
590
  var DEFAULT_TODO_LIST_PROMPT = `Planning tools are available when they materially improve execution.
262
591
 
@@ -339,23 +668,39 @@ function normalizeUsage(raw) {
339
668
  raw.totalTokens,
340
669
  (typeof prompt === "number" ? prompt : 0) + (typeof completion === "number" ? completion : 0)
341
670
  );
342
- const promptDetailsSrc = raw.prompt_tokens_details || raw.promptTokensDetails || raw.input_token_details || raw.inputTokenDetails || {};
343
- const completionDetailsSrc = raw.completion_tokens_details || raw.completionTokensDetails || raw.output_token_details || raw.outputTokenDetails || {};
671
+ const promptDetailsSrc = raw.prompt_tokens_details || raw.promptTokensDetails || raw.input_token_details || raw.inputTokenDetails || raw.input_tokens_details || raw.inputTokensDetails || {};
672
+ const completionDetailsSrc = raw.completion_tokens_details || raw.completionTokensDetails || raw.output_token_details || raw.outputTokenDetails || raw.output_tokens_details || raw.outputTokensDetails || {};
673
+ const cachedTokens = num(
674
+ promptDetailsSrc.cached_tokens,
675
+ promptDetailsSrc.cachedTokens,
676
+ promptDetailsSrc.cached,
677
+ promptDetailsSrc.cache_read,
678
+ promptDetailsSrc.cacheRead,
679
+ promptDetailsSrc.cache_read_tokens,
680
+ raw.cached_input_tokens,
681
+ raw.cachedInputTokens,
682
+ raw.cached_prompt_tokens,
683
+ raw.cachedPromptTokens,
684
+ raw.cache_read_input_tokens,
685
+ raw.cacheReadInputTokens
686
+ );
344
687
  const normalized = {
345
688
  prompt_tokens: safe(prompt),
346
689
  completion_tokens: safe(completion),
347
690
  total_tokens: safe(total || (prompt || 0) + (completion || 0)),
348
691
  prompt_tokens_details: {
349
- cached_tokens: safe(
350
- promptDetailsSrc.cached_tokens || promptDetailsSrc.cached || promptDetailsSrc.cache_read || raw.cached_input_tokens || raw.cached_prompt_tokens || raw.cache_read_input_tokens
351
- ),
352
- audio_tokens: safe(promptDetailsSrc.audio_tokens || promptDetailsSrc.audio)
692
+ cached_tokens: safe(cachedTokens),
693
+ audio_tokens: safe(num(promptDetailsSrc.audio_tokens, promptDetailsSrc.audioTokens, promptDetailsSrc.audio))
353
694
  },
354
695
  completion_tokens_details: {
355
- reasoning_tokens: safe(completionDetailsSrc.reasoning_tokens),
356
- audio_tokens: safe(completionDetailsSrc.audio_tokens),
357
- accepted_prediction_tokens: safe(completionDetailsSrc.accepted_prediction_tokens),
358
- rejected_prediction_tokens: safe(completionDetailsSrc.rejected_prediction_tokens)
696
+ reasoning_tokens: safe(num(completionDetailsSrc.reasoning_tokens, completionDetailsSrc.reasoningTokens)),
697
+ audio_tokens: safe(num(completionDetailsSrc.audio_tokens, completionDetailsSrc.audioTokens)),
698
+ accepted_prediction_tokens: safe(
699
+ num(completionDetailsSrc.accepted_prediction_tokens, completionDetailsSrc.acceptedPredictionTokens)
700
+ ),
701
+ rejected_prediction_tokens: safe(
702
+ num(completionDetailsSrc.rejected_prediction_tokens, completionDetailsSrc.rejectedPredictionTokens)
703
+ )
359
704
  },
360
705
  raw
361
706
  };
@@ -465,21 +810,21 @@ var METADATA_PATHS = {
465
810
  "lc_kwargs.provider"
466
811
  ]
467
812
  };
468
- function getNestedValue(obj, path2) {
813
+ function getNestedValue2(obj, path2) {
469
814
  if (!obj || typeof path2 !== "string") return void 0;
470
815
  return path2.split(".").reduce((current, key) => current?.[key], obj);
471
816
  }
472
817
  function extractMetadata(model, paths) {
473
818
  if (!model) return void 0;
474
819
  for (const path2 of paths) {
475
- const value = getNestedValue(model, path2);
820
+ const value = getNestedValue2(model, path2);
476
821
  const str = coerceToString(value);
477
822
  if (str) return str;
478
823
  }
479
824
  const maybeLc = model?._lc;
480
825
  if (maybeLc) {
481
826
  for (const path2 of paths) {
482
- const value = getNestedValue(maybeLc, path2);
827
+ const value = getNestedValue2(maybeLc, path2);
483
828
  const str = coerceToString(value);
484
829
  if (str) return str;
485
830
  }
@@ -487,7 +832,7 @@ function extractMetadata(model, paths) {
487
832
  const maybeClient = model?.client || model?.api || model?.service;
488
833
  if (maybeClient) {
489
834
  for (const path2 of paths) {
490
- const value = getNestedValue(maybeClient, path2);
835
+ const value = getNestedValue2(maybeClient, path2);
491
836
  const str = coerceToString(value);
492
837
  if (str) return str;
493
838
  }
@@ -519,7 +864,7 @@ function inferModelFromMessages(messageList) {
519
864
  const message = messageList[i];
520
865
  if (!message) continue;
521
866
  for (const path2 of messagePaths) {
522
- const value = getNestedValue(message, path2);
867
+ const value = getNestedValue2(message, path2);
523
868
  const str = coerceToString(value);
524
869
  if (str) return str;
525
870
  }
@@ -538,7 +883,7 @@ function inferProviderFromMessages(messageList) {
538
883
  const message = messageList[i];
539
884
  if (!message) continue;
540
885
  for (const path2 of messagePaths) {
541
- const value = getNestedValue(message, path2);
886
+ const value = getNestedValue2(message, path2);
542
887
  const str = coerceToString(value);
543
888
  if (str) return str;
544
889
  }
@@ -1549,7 +1894,13 @@ function createAgentCoreNode(opts) {
1549
1894
  tracing: opts.tracing
1550
1895
  };
1551
1896
  const tools = runtime.tools ?? [];
1552
- const modelWithTools = runtime.model?.bindTools ? runtime.model.bindTools(tools) : runtime.model;
1897
+ const shouldUseStrictToolCalling = Boolean(
1898
+ runtime.responseFormat && runtime.model?.capabilities?.strictToolCalling
1899
+ );
1900
+ const modelWithTools = runtime.model?.bindTools ? runtime.model.bindTools(
1901
+ tools,
1902
+ shouldUseStrictToolCalling ? { strict: true } : void 0
1903
+ ) : runtime.model;
1553
1904
  const traceSession = state.ctx?.__traceSession;
1554
1905
  const actorName = runtime.name ?? opts.name ?? "agent";
1555
1906
  const actorVersion = runtime.version ?? opts.version;
@@ -1626,6 +1977,7 @@ function createAgentCoreNode(opts) {
1626
1977
  const streamingEnabled = Boolean(state.ctx?.__streaming);
1627
1978
  const cancellationToken = state.ctx?.__cancellationToken;
1628
1979
  const abortSignal = state.ctx?.__abortSignal;
1980
+ const responseFormat = runtime.responseFormat;
1629
1981
  const extractText = (chunk) => {
1630
1982
  if (chunk == null) return "";
1631
1983
  if (typeof chunk === "string") return chunk;
@@ -1644,7 +1996,8 @@ function createAgentCoreNode(opts) {
1644
1996
  if (streamingEnabled && typeof modelWithTools.stream === "function") {
1645
1997
  let streamedText = "";
1646
1998
  let streamedMessage;
1647
- for await (const chunk of modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken })) {
1999
+ const streamOptions = { signal: abortSignal, cancellationToken, ...responseFormat };
2000
+ for await (const chunk of modelWithTools.stream(normalizedMessages, streamOptions)) {
1648
2001
  if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
1649
2002
  break;
1650
2003
  }
@@ -1667,7 +2020,7 @@ function createAgentCoreNode(opts) {
1667
2020
  response = { role: "assistant", content: streamedText };
1668
2021
  }
1669
2022
  } else {
1670
- response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken });
2023
+ response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken, ...responseFormat });
1671
2024
  }
1672
2025
  } catch (err) {
1673
2026
  const durationMs2 = Date.now() - start;
@@ -1689,7 +2042,7 @@ function createAgentCoreNode(opts) {
1689
2042
  ...state.messages,
1690
2043
  response
1691
2044
  ];
1692
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2045
+ const rawUsage = response?.usage || response?.usage_metadata || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage || response?.response_metadata?.usage_metadata || response?.response_metadata?.usageMetadata;
1693
2046
  const normalized = normalizeUsage(rawUsage);
1694
2047
  const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
1695
2048
  const durationMs = Date.now() - start;
@@ -1737,235 +2090,134 @@ function createAgentCoreNode(opts) {
1737
2090
 
1738
2091
  // src/smart/runtimeConfig.ts
1739
2092
  var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
2093
+ var BASE_DEFAULTS = {
2094
+ limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
2095
+ summarization: {
2096
+ enable: true,
2097
+ maxTokens: 24e3,
2098
+ summaryTriggerTokens: 17e3,
2099
+ summaryPromptMaxTokens: 7e3,
2100
+ summaryCompressionRatioTarget: 0.35,
2101
+ summaryMode: "incremental",
2102
+ promptTemplate: "",
2103
+ toolFreeCall: true,
2104
+ integrityCheck: true
2105
+ },
2106
+ context: {
2107
+ policy: "hybrid",
2108
+ lastTurnsToKeep: 8,
2109
+ toolResponsePolicy: "summarize_archive",
2110
+ archiveLargeToolResponses: true,
2111
+ retrieveArchivedToolResponseOnDemand: true,
2112
+ budget: {
2113
+ systemReserveTokens: 1200,
2114
+ goalsReserveTokens: 1200,
2115
+ recentTurnsReserveTokens: 5200,
2116
+ toolResponseReserveTokens: 2800
2117
+ }
2118
+ },
2119
+ planning: {
2120
+ mode: "off",
2121
+ replanPolicy: "on_failure",
2122
+ everyNSteps: 0
2123
+ },
2124
+ memory: {
2125
+ provider: "inMemory",
2126
+ scope: "session",
2127
+ writePolicy: "auto_important",
2128
+ readPolicy: "hybrid"
2129
+ },
2130
+ delegation: {
2131
+ mode: "role_based",
2132
+ maxDelegationDepth: 2,
2133
+ maxChildCalls: 4,
2134
+ maxParallelChild: 2,
2135
+ childContextPolicy: "scoped",
2136
+ requireJsonOutputContract: true
2137
+ },
2138
+ toolResponses: {
2139
+ maxToolResponseChars: 3600,
2140
+ maxToolResponseTokens: 1200,
2141
+ defaultPolicy: "keep_structured",
2142
+ largeResponsePolicy: "summarize_archive",
2143
+ toolResponseRetentionByTool: {},
2144
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
2145
+ schemaValidation: "strict",
2146
+ retryOnSchemaError: true,
2147
+ fallbackPolicy: "keep_structured"
2148
+ }
2149
+ };
2150
+ function buildProfile(overrides) {
2151
+ return {
2152
+ limits: { ...BASE_DEFAULTS.limits, ...overrides.limits },
2153
+ summarization: { ...BASE_DEFAULTS.summarization, ...overrides.summarization },
2154
+ context: {
2155
+ ...BASE_DEFAULTS.context,
2156
+ ...overrides.context,
2157
+ budget: { ...BASE_DEFAULTS.context.budget, ...overrides.context?.budget }
2158
+ },
2159
+ planning: { ...BASE_DEFAULTS.planning, ...overrides.planning },
2160
+ memory: { ...BASE_DEFAULTS.memory, ...overrides.memory },
2161
+ delegation: { ...BASE_DEFAULTS.delegation, ...overrides.delegation },
2162
+ toolResponses: {
2163
+ ...BASE_DEFAULTS.toolResponses,
2164
+ ...overrides.toolResponses,
2165
+ criticalTools: overrides.toolResponses?.criticalTools ?? [...DEFAULT_CRITICAL_TOOLS],
2166
+ toolResponseRetentionByTool: overrides.toolResponses?.toolResponseRetentionByTool ?? {}
2167
+ }
2168
+ };
2169
+ }
1740
2170
  var DEFAULT_PROFILE_CONFIGS = {
1741
- fast: {
2171
+ fast: buildProfile({
1742
2172
  limits: { maxToolCalls: 4, maxParallelTools: 1, maxContextTokens: 12e3 },
1743
2173
  summarization: {
1744
- enable: true,
1745
2174
  maxTokens: 12e3,
1746
2175
  summaryTriggerTokens: 9e3,
1747
2176
  summaryPromptMaxTokens: 5e3,
1748
- summaryCompressionRatioTarget: 0.4,
1749
- summaryMode: "incremental",
1750
- promptTemplate: "",
1751
- toolFreeCall: true,
1752
- integrityCheck: true
2177
+ summaryCompressionRatioTarget: 0.4
1753
2178
  },
1754
2179
  context: {
1755
- policy: "hybrid",
1756
2180
  lastTurnsToKeep: 6,
1757
2181
  toolResponsePolicy: "keep_structured",
1758
- archiveLargeToolResponses: true,
1759
- retrieveArchivedToolResponseOnDemand: true,
1760
- budget: {
1761
- systemReserveTokens: 1e3,
1762
- goalsReserveTokens: 900,
1763
- recentTurnsReserveTokens: 3200,
1764
- toolResponseReserveTokens: 1800
1765
- }
1766
- },
1767
- planning: {
1768
- mode: "off",
1769
- replanPolicy: "on_failure",
1770
- everyNSteps: 0
1771
- },
1772
- memory: {
1773
- provider: "inMemory",
1774
- scope: "session",
1775
- writePolicy: "auto_important",
1776
- readPolicy: "recent_only"
1777
- },
1778
- delegation: {
1779
- mode: "off",
1780
- maxDelegationDepth: 1,
1781
- maxChildCalls: 2,
1782
- maxParallelChild: 1,
1783
- childContextPolicy: "minimal",
1784
- requireJsonOutputContract: true
1785
- },
1786
- toolResponses: {
1787
- maxToolResponseChars: 2400,
1788
- maxToolResponseTokens: 800,
1789
- defaultPolicy: "keep_structured",
1790
- largeResponsePolicy: "summarize_archive",
1791
- toolResponseRetentionByTool: {},
1792
- criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1793
- schemaValidation: "strict",
1794
- retryOnSchemaError: true,
1795
- fallbackPolicy: "keep_structured"
1796
- }
1797
- },
1798
- balanced: {
1799
- limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
1800
- summarization: {
1801
- enable: true,
1802
- maxTokens: 24e3,
1803
- summaryTriggerTokens: 17e3,
1804
- summaryPromptMaxTokens: 7e3,
1805
- summaryCompressionRatioTarget: 0.35,
1806
- summaryMode: "incremental",
1807
- promptTemplate: "",
1808
- toolFreeCall: true,
1809
- integrityCheck: true
1810
- },
1811
- context: {
1812
- policy: "hybrid",
1813
- lastTurnsToKeep: 8,
1814
- toolResponsePolicy: "summarize_archive",
1815
- archiveLargeToolResponses: true,
1816
- retrieveArchivedToolResponseOnDemand: true,
1817
- budget: {
1818
- systemReserveTokens: 1200,
1819
- goalsReserveTokens: 1200,
1820
- recentTurnsReserveTokens: 5200,
1821
- toolResponseReserveTokens: 2800
1822
- }
1823
- },
1824
- planning: {
1825
- mode: "off",
1826
- replanPolicy: "on_failure",
1827
- everyNSteps: 0
2182
+ budget: { systemReserveTokens: 1e3, goalsReserveTokens: 900, recentTurnsReserveTokens: 3200, toolResponseReserveTokens: 1800 }
1828
2183
  },
1829
- memory: {
1830
- provider: "inMemory",
1831
- scope: "session",
1832
- writePolicy: "auto_important",
1833
- readPolicy: "hybrid"
1834
- },
1835
- delegation: {
1836
- mode: "role_based",
1837
- maxDelegationDepth: 2,
1838
- maxChildCalls: 4,
1839
- maxParallelChild: 2,
1840
- childContextPolicy: "scoped",
1841
- requireJsonOutputContract: true
1842
- },
1843
- toolResponses: {
1844
- maxToolResponseChars: 3600,
1845
- maxToolResponseTokens: 1200,
1846
- defaultPolicy: "keep_structured",
1847
- largeResponsePolicy: "summarize_archive",
1848
- toolResponseRetentionByTool: {},
1849
- criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1850
- schemaValidation: "strict",
1851
- retryOnSchemaError: true,
1852
- fallbackPolicy: "keep_structured"
1853
- }
1854
- },
1855
- deep: {
2184
+ memory: { readPolicy: "recent_only" },
2185
+ delegation: { mode: "off", maxDelegationDepth: 1, maxChildCalls: 2, maxParallelChild: 1, childContextPolicy: "minimal" },
2186
+ toolResponses: { maxToolResponseChars: 2400, maxToolResponseTokens: 800 }
2187
+ }),
2188
+ balanced: buildProfile({}),
2189
+ deep: buildProfile({
1856
2190
  limits: { maxToolCalls: 14, maxParallelTools: 3, maxContextTokens: 42e3 },
1857
2191
  summarization: {
1858
- enable: true,
1859
2192
  maxTokens: 42e3,
1860
2193
  summaryTriggerTokens: 3e4,
1861
2194
  summaryPromptMaxTokens: 9e3,
1862
- summaryCompressionRatioTarget: 0.3,
1863
- summaryMode: "incremental",
1864
- promptTemplate: "",
1865
- toolFreeCall: true,
1866
- integrityCheck: true
2195
+ summaryCompressionRatioTarget: 0.3
1867
2196
  },
1868
2197
  context: {
1869
- policy: "hybrid",
1870
2198
  lastTurnsToKeep: 12,
1871
- toolResponsePolicy: "summarize_archive",
1872
- archiveLargeToolResponses: true,
1873
- retrieveArchivedToolResponseOnDemand: true,
1874
- budget: {
1875
- systemReserveTokens: 1400,
1876
- goalsReserveTokens: 1600,
1877
- recentTurnsReserveTokens: 7e3,
1878
- toolResponseReserveTokens: 3600
1879
- }
1880
- },
1881
- planning: {
1882
- mode: "off",
1883
- replanPolicy: "on_failure",
1884
- everyNSteps: 0
1885
- },
1886
- memory: {
1887
- provider: "inMemory",
1888
- scope: "workspace",
1889
- writePolicy: "auto_important",
1890
- readPolicy: "hybrid"
1891
- },
1892
- delegation: {
1893
- mode: "role_based",
1894
- maxDelegationDepth: 3,
1895
- maxChildCalls: 6,
1896
- maxParallelChild: 2,
1897
- childContextPolicy: "scoped",
1898
- requireJsonOutputContract: true
2199
+ budget: { systemReserveTokens: 1400, goalsReserveTokens: 1600, recentTurnsReserveTokens: 7e3, toolResponseReserveTokens: 3600 }
1899
2200
  },
1900
- toolResponses: {
1901
- maxToolResponseChars: 5400,
1902
- maxToolResponseTokens: 1800,
1903
- defaultPolicy: "keep_structured",
1904
- largeResponsePolicy: "summarize_archive",
1905
- toolResponseRetentionByTool: {},
1906
- criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1907
- schemaValidation: "strict",
1908
- retryOnSchemaError: true,
1909
- fallbackPolicy: "keep_structured"
1910
- }
1911
- },
1912
- research: {
2201
+ memory: { scope: "workspace" },
2202
+ delegation: { maxDelegationDepth: 3, maxChildCalls: 6 },
2203
+ toolResponses: { maxToolResponseChars: 5400, maxToolResponseTokens: 1800 }
2204
+ }),
2205
+ research: buildProfile({
1913
2206
  limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
1914
2207
  summarization: {
1915
- enable: true,
1916
2208
  maxTokens: 56e3,
1917
2209
  summaryTriggerTokens: 42e3,
1918
2210
  summaryPromptMaxTokens: 1e4,
1919
- summaryCompressionRatioTarget: 0.28,
1920
- summaryMode: "incremental",
1921
- promptTemplate: "",
1922
- toolFreeCall: true,
1923
- integrityCheck: true
2211
+ summaryCompressionRatioTarget: 0.28
1924
2212
  },
1925
2213
  context: {
1926
- policy: "hybrid",
1927
2214
  lastTurnsToKeep: 20,
1928
- toolResponsePolicy: "summarize_archive",
1929
- archiveLargeToolResponses: true,
1930
- retrieveArchivedToolResponseOnDemand: true,
1931
- budget: {
1932
- systemReserveTokens: 1600,
1933
- goalsReserveTokens: 1800,
1934
- recentTurnsReserveTokens: 9e3,
1935
- toolResponseReserveTokens: 4800
1936
- }
1937
- },
1938
- planning: {
1939
- mode: "off",
1940
- replanPolicy: "on_failure",
1941
- everyNSteps: 0
1942
- },
1943
- memory: {
1944
- provider: "inMemory",
1945
- scope: "workspace",
1946
- writePolicy: "auto_important",
1947
- readPolicy: "hybrid"
2215
+ budget: { systemReserveTokens: 1600, goalsReserveTokens: 1800, recentTurnsReserveTokens: 9e3, toolResponseReserveTokens: 4800 }
1948
2216
  },
1949
- delegation: {
1950
- mode: "automatic",
1951
- maxDelegationDepth: 4,
1952
- maxChildCalls: 8,
1953
- maxParallelChild: 3,
1954
- childContextPolicy: "full",
1955
- requireJsonOutputContract: true
1956
- },
1957
- toolResponses: {
1958
- maxToolResponseChars: 7200,
1959
- maxToolResponseTokens: 2200,
1960
- defaultPolicy: "keep_structured",
1961
- largeResponsePolicy: "summarize_archive",
1962
- toolResponseRetentionByTool: {},
1963
- criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1964
- schemaValidation: "strict",
1965
- retryOnSchemaError: true,
1966
- fallbackPolicy: "keep_structured"
1967
- }
1968
- }
2217
+ memory: { scope: "workspace" },
2218
+ delegation: { mode: "automatic", maxDelegationDepth: 4, maxChildCalls: 8, maxParallelChild: 3, childContextPolicy: "full" },
2219
+ toolResponses: { maxToolResponseChars: 7200, maxToolResponseTokens: 2200 }
2220
+ })
1969
2221
  };
1970
2222
  function mergeLimits(base, override) {
1971
2223
  return {
@@ -2108,19 +2360,55 @@ function countMessagesTokens(messages) {
2108
2360
  return countApproxTokens(allText) + messages.length * 4;
2109
2361
  }
2110
2362
 
2111
- // src/smart/toolResponses.ts
2112
- function safeStringify(value) {
2113
- if (typeof value === "string") return value;
2363
+ // src/utils/content.ts
2364
+ function contentToString(content) {
2114
2365
  try {
2115
- const serialized = JSON.stringify(value);
2116
- return typeof serialized === "string" ? serialized : String(value);
2117
- } catch {
2118
- return String(value);
2119
- }
2120
- }
2121
- function summarizeObject(value) {
2122
- if (value == null) return "null";
2123
- if (typeof value === "string") {
2366
+ if (content == null) return "";
2367
+ if (typeof content === "string") return content;
2368
+ if (Array.isArray(content)) {
2369
+ return content.map((p) => {
2370
+ if (!p || typeof p !== "object") return String(p);
2371
+ if (p.type === "text") return String(p.text ?? "");
2372
+ if (p.type === "image_url") {
2373
+ const img = p.image_url;
2374
+ const url = typeof img === "string" ? img : img?.url;
2375
+ const detail = typeof img === "string" ? void 0 : img?.detail;
2376
+ return `[image:${url || "unknown"}${detail ? ` detail=${detail}` : ""}]`;
2377
+ }
2378
+ return JSON.stringify(p);
2379
+ }).join("\n");
2380
+ }
2381
+ return JSON.stringify(content);
2382
+ } catch {
2383
+ return String(content);
2384
+ }
2385
+ }
2386
+ function mergeContentsToString(contents) {
2387
+ return contents.map((c) => contentToString(c)).join("\n");
2388
+ }
2389
+ function safeStringify(value, pretty = false) {
2390
+ if (value == null) return "";
2391
+ if (typeof value === "string") return value;
2392
+ try {
2393
+ return JSON.stringify(value, null, pretty ? 2 : void 0) ?? String(value);
2394
+ } catch {
2395
+ return String(value);
2396
+ }
2397
+ }
2398
+ function extractMessageText2(message) {
2399
+ if (!message) return "";
2400
+ const content = message.content;
2401
+ if (typeof content === "string") return content;
2402
+ if (Array.isArray(content)) {
2403
+ return content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join("");
2404
+ }
2405
+ return "";
2406
+ }
2407
+
2408
+ // src/smart/toolResponses.ts
2409
+ function summarizeObject(value) {
2410
+ if (value == null) return "null";
2411
+ if (typeof value === "string") {
2124
2412
  return value.length > 240 ? `${value.slice(0, 240)}...` : value;
2125
2413
  }
2126
2414
  if (Array.isArray(value)) {
@@ -2214,6 +2502,16 @@ function countSuccessfulToolExecutions(toolHistory, toolName) {
2214
2502
  if (!Array.isArray(toolHistory)) return 0;
2215
2503
  return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
2216
2504
  }
2505
+ function normalizeToolCall(call) {
2506
+ if (!call || typeof call !== "object") return null;
2507
+ const name = call.name ?? call.tool ?? call.function?.name ?? call.function_call?.name;
2508
+ if (typeof name !== "string" || name.length === 0) return null;
2509
+ return {
2510
+ id: call.id,
2511
+ name,
2512
+ args: call.args ?? call.arguments ?? call.input ?? call.function?.arguments ?? call.function_call?.arguments
2513
+ };
2514
+ }
2217
2515
  function createToolsNode(initialTools, opts) {
2218
2516
  return async (state) => {
2219
2517
  const runtime = state.agent || {
@@ -2248,12 +2546,14 @@ function createToolsNode(initialTools, opts) {
2248
2546
  if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2249
2547
  anyTool._stateRef.toolHistory = state.toolHistory;
2250
2548
  anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2549
+ anyTool._stateRef.pendingApprovals = pendingApprovals;
2550
+ anyTool._stateRef.ctx = state.ctx || (state.ctx = {});
2251
2551
  anyTool._stateRef.__onEvent = onEvent;
2252
2552
  }
2253
2553
  }
2254
2554
  const last = state.messages[state.messages.length - 1];
2255
2555
  let toolCount = state.toolCallCount || 0;
2256
- const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
2556
+ const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls.map((toolCall) => normalizeToolCall(toolCall)).filter((toolCall) => toolCall !== null) : [];
2257
2557
  const toolHistory = state.toolHistory || [];
2258
2558
  const toolHistoryArchived = state.toolHistoryArchived || [];
2259
2559
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
@@ -2312,6 +2612,13 @@ function createToolsNode(initialTools, opts) {
2312
2612
  args = validatedArgs.value;
2313
2613
  }
2314
2614
  const toolName = tool.name || tc.name;
2615
+ const toolStateRef = tool._stateRef && typeof tool._stateRef === "object" ? tool._stateRef : null;
2616
+ if (toolStateRef) {
2617
+ toolStateRef.pendingApprovals = pendingApprovals;
2618
+ toolStateRef.ctx = state.ctx || (state.ctx = {});
2619
+ toolStateRef.__currentToolCallId = toolCallId;
2620
+ delete toolStateRef.__awaitingApproval;
2621
+ }
2315
2622
  const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
2316
2623
  if (maxExecutionsPerRun !== null) {
2317
2624
  const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
@@ -2424,6 +2731,22 @@ function createToolsNode(initialTools, opts) {
2424
2731
  toolCount += 1;
2425
2732
  return { status: "success", approval: approvalEntry };
2426
2733
  }
2734
+ if (output && typeof output === "object" && output.__awaitingToolApproval) {
2735
+ if (toolStateRef?.ctx && typeof toolStateRef.ctx === "object") {
2736
+ state.ctx = toolStateRef.ctx;
2737
+ }
2738
+ const pendingApproval = pendingApprovals.find(
2739
+ (entry) => entry.toolCallId === output.toolCallId || entry.id === output.approvalId
2740
+ );
2741
+ if (!pendingApproval) {
2742
+ throw new Error(`Tool ${toolName} requested approval but no pending approval entry was recorded.`);
2743
+ }
2744
+ awaitingApproval = true;
2745
+ return {
2746
+ status: "awaiting_approval",
2747
+ approval: pendingApproval
2748
+ };
2749
+ }
2427
2750
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
2428
2751
  if (!state.ctx) state.ctx = {};
2429
2752
  state.ctx.__structuredOutputParsed = output.data;
@@ -2846,7 +3169,7 @@ function resolveToolApprovalState(state, resolution) {
2846
3169
  };
2847
3170
  }
2848
3171
 
2849
- // src/agent.ts
3172
+ // src/utils/syntheticMessages.ts
2850
3173
  function isSyntheticSummaryMessage(message) {
2851
3174
  if (!message) return false;
2852
3175
  if (message.role === "tool" && message.name === "summarize_context") {
@@ -2860,13 +3183,155 @@ function isSyntheticSummaryMessage(message) {
2860
3183
  }
2861
3184
  return false;
2862
3185
  }
2863
- function getMessageText(message) {
2864
- if (typeof message?.content === "string") return message.content;
2865
- if (Array.isArray(message?.content)) {
2866
- return message.content.map((chunk) => typeof chunk === "string" ? chunk : chunk?.text ?? chunk?.content ?? "").join("");
3186
+
3187
+ // src/structuredOutput/manager.ts
3188
+ var StructuredOutputManager = class {
3189
+ constructor(schema, strategy, config) {
3190
+ this.schema = schema;
3191
+ this.strategy = strategy;
3192
+ this.maxRetries = config?.maxRetries ?? 2;
3193
+ }
3194
+ // --- Setup Phase ---
3195
+ /**
3196
+ * Get the `response` tool for tool-based strategy.
3197
+ * Returns undefined for native strategy (no tool needed).
3198
+ */
3199
+ getResponseTool() {
3200
+ if (this.strategy.kind !== "tool_based") return void 0;
3201
+ return this.strategy.createResponseTool(this.schema);
3202
+ }
3203
+ /**
3204
+ * Get response_format config for native strategy.
3205
+ * Returns undefined for tool-based strategy.
3206
+ */
3207
+ getResponseFormat() {
3208
+ if (this.strategy.kind !== "native") return void 0;
3209
+ return this.strategy.buildResponseFormat(this.schema);
3210
+ }
3211
+ /**
3212
+ * Build the system prompt hint that tells the model how to produce structured output.
3213
+ */
3214
+ buildSystemPromptHint() {
3215
+ if (this.strategy.kind === "native") {
3216
+ return [
3217
+ "A structured output schema is active.",
3218
+ "Your response MUST be valid JSON matching the output schema.",
3219
+ "Do NOT wrap the JSON in code fences or add any text before/after it."
3220
+ ].join("\n");
3221
+ }
3222
+ return [
3223
+ "A structured output schema is active.",
3224
+ "Do NOT output the final JSON directly as an assistant message.",
3225
+ "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
3226
+ "Call it exactly once then STOP producing further assistant messages."
3227
+ ].join("\n");
3228
+ }
3229
+ /**
3230
+ * Build a nudge message for when the model didn't produce structured output.
3231
+ * Used by the agent loop when it needs to retry.
3232
+ */
3233
+ buildNudgeMessage(isLastAttempt) {
3234
+ if (this.strategy.kind === "native") {
3235
+ return {
3236
+ role: "system",
3237
+ content: [
3238
+ "A structured output schema is active.",
3239
+ "You MUST respond with valid JSON matching the schema.",
3240
+ "Do NOT include any text outside the JSON object.",
3241
+ isLastAttempt ? "This is your FINAL attempt. Produce the JSON NOW." : ""
3242
+ ].filter(Boolean).join("\n")
3243
+ };
3244
+ }
3245
+ return {
3246
+ role: "system",
3247
+ content: [
3248
+ "A structured output schema is active.",
3249
+ "You MUST now call tool `response` with the final JSON object that matches the schema.",
3250
+ "Do not write the JSON in the assistant message.",
3251
+ "Call `response` exactly once, then stop.",
3252
+ isLastAttempt ? "Do NOT call any other tools. Produce the response NOW based on whatever information you have gathered so far." : ""
3253
+ ].filter(Boolean).join("\n")
3254
+ };
2867
3255
  }
2868
- return "";
2869
- }
3256
+ /**
3257
+ * Build a correction message when schema validation failed.
3258
+ */
3259
+ buildCorrectionMessage(error, _previousAttempt) {
3260
+ if (error.fieldErrors && error.fieldErrors.length > 0) {
3261
+ const fieldIssues = error.fieldErrors.map((f) => `- "${f.path}": ${f.message} (received: ${JSON.stringify(f.received)})`).join("\n");
3262
+ const action = this.strategy.kind === "native" ? "Please output corrected JSON matching the schema." : "Please call `response` again with corrected values matching the schema.";
3263
+ return {
3264
+ role: "system",
3265
+ content: [
3266
+ "Your previous structured output had validation errors:",
3267
+ fieldIssues,
3268
+ "",
3269
+ action
3270
+ ].join("\n")
3271
+ };
3272
+ }
3273
+ return this.buildNudgeMessage(false);
3274
+ }
3275
+ // --- Parse Phase ---
3276
+ /**
3277
+ * Try to parse structured output from the tool-based finalization result.
3278
+ * Called when __structuredOutputParsed is available in ctx.
3279
+ */
3280
+ parseFromToolResult(data) {
3281
+ return { success: true, data, attempts: 1 };
3282
+ }
3283
+ /**
3284
+ * Try to parse structured output from assistant message content.
3285
+ * For native strategy: content should be valid JSON.
3286
+ * For tool-based strategy: last-resort fallback extraction from text.
3287
+ */
3288
+ parseFromContent(content) {
3289
+ if (this.strategy.kind === "native") {
3290
+ const result2 = this.strategy.extractOutput(content, this.schema);
3291
+ if ("data" in result2) {
3292
+ return { success: true, data: result2.data, attempts: 1 };
3293
+ }
3294
+ return { success: false, error: result2.error, attempts: 1, rawContent: content };
3295
+ }
3296
+ const result = this.strategy.extractFromText(content, this.schema);
3297
+ if ("data" in result) {
3298
+ return { success: true, data: result.data, attempts: 1 };
3299
+ }
3300
+ return { success: false, error: result.error, attempts: 1, rawContent: content };
3301
+ }
3302
+ /**
3303
+ * Create a "no output" error result.
3304
+ */
3305
+ noOutputResult(attempts, rawContent) {
3306
+ return {
3307
+ success: false,
3308
+ error: {
3309
+ type: "no_output",
3310
+ message: "Model did not produce structured output after all retry attempts.",
3311
+ rawContent
3312
+ },
3313
+ attempts,
3314
+ rawContent
3315
+ };
3316
+ }
3317
+ /**
3318
+ * Create a "max retries" error result.
3319
+ */
3320
+ maxRetriesResult(lastError, attempts, rawContent) {
3321
+ return {
3322
+ success: false,
3323
+ error: {
3324
+ ...lastError,
3325
+ type: "max_retries",
3326
+ message: `Structured output failed after ${attempts} attempts: ${lastError.message}`
3327
+ },
3328
+ attempts,
3329
+ rawContent
3330
+ };
3331
+ }
3332
+ };
3333
+
3334
+ // src/agent.ts
2870
3335
  function getLastAssistantMessage(messages) {
2871
3336
  for (let index = messages.length - 1; index >= 0; index -= 1) {
2872
3337
  if (messages[index]?.role === "assistant") {
@@ -2897,14 +3362,13 @@ function createAgent(opts) {
2897
3362
  const resolver = createResolverNode();
2898
3363
  const agentCore = createAgentCoreNode(opts);
2899
3364
  const toolsBase = [...opts.tools ?? []];
2900
- if (opts.outputSchema) {
2901
- const responseTool = createTool({
2902
- name: "response",
2903
- description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
2904
- schema: opts.outputSchema,
2905
- func: async (data) => ({ __finalStructuredOutput: true, data })
2906
- });
2907
- toolsBase.push(responseTool);
3365
+ const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
3366
+ if (soManager) {
3367
+ const modelCapabilities = getModelCapabilities(opts.model);
3368
+ const responseTool = soManager.getResponseTool();
3369
+ if (responseTool && modelCapabilities.structuredOutput !== "native") {
3370
+ toolsBase.push(responseTool);
3371
+ }
2908
3372
  }
2909
3373
  const toolsNode = createToolsNode(toolsBase, opts);
2910
3374
  const finalizeNode = createToolLimitFinalizeNode();
@@ -2939,6 +3403,7 @@ function createAgent(opts) {
2939
3403
  limits: opts.limits,
2940
3404
  useTodoList: void 0,
2941
3405
  outputSchema: opts.outputSchema,
3406
+ responseFormat: soManager?.getResponseFormat(),
2942
3407
  tracing: opts.tracing
2943
3408
  };
2944
3409
  const summarizationThreshold = getActiveSummarizationThreshold(opts);
@@ -3077,10 +3542,11 @@ function createAgent(opts) {
3077
3542
  });
3078
3543
  const tokenCount = countMessagesTokens(tokCountMessages);
3079
3544
  if (tokenCount > summarizationThreshold) {
3545
+ const summarizationExhausted = !!state.ctx?.__summarizationExhausted;
3080
3546
  const hasFreshSummary = (state.messages || []).some(
3081
3547
  (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
3082
3548
  );
3083
- if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else {
3549
+ if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else if (summarizationExhausted) ; else {
3084
3550
  const ctx = { ...state.ctx || {}, __needsSummarization: true };
3085
3551
  state = { ...state, ctx };
3086
3552
  break;
@@ -3158,18 +3624,10 @@ function createAgent(opts) {
3158
3624
  continue;
3159
3625
  }
3160
3626
  if (toolCalls.length === 0) {
3161
- if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
3162
- const forceMsg = {
3163
- role: "system",
3164
- content: [
3165
- "A structured output schema is active.",
3166
- "You MUST now call tool `response` with the final JSON object that matches the schema.",
3167
- "Do not write the JSON in the assistant message.",
3168
- "Call `response` exactly once, then stop."
3169
- ].join("\n")
3170
- };
3627
+ if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
3628
+ const nudge = soManager.buildNudgeMessage(false);
3171
3629
  const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
3172
- state = { ...state, messages: [...state.messages, forceMsg], ctx };
3630
+ state = { ...state, messages: [...state.messages, nudge], ctx };
3173
3631
  continue;
3174
3632
  }
3175
3633
  break;
@@ -3182,23 +3640,16 @@ function createAgent(opts) {
3182
3640
  if (checkpointIfRequested("after_tools")) break;
3183
3641
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3184
3642
  }
3185
- if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput) {
3186
- const last = state.messages[state.messages.length - 1];
3187
- const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
3188
- if (!lastHasToolCalls) {
3189
- const alreadyForced = Boolean(state.ctx?.__structuredOutputForceFinalize);
3190
- if (alreadyForced) {
3191
- const finalNudge = {
3192
- role: "system",
3193
- content: [
3194
- "A structured output schema is active.",
3195
- "You MUST now call tool `response` with the final JSON object that matches the schema.",
3196
- "Do not write the JSON in the assistant message.",
3197
- "Call `response` exactly once, then stop.",
3198
- "Do NOT call any other tools. Produce the response NOW based on whatever information you have gathered so far."
3199
- ].join("\n")
3200
- };
3201
- state = { ...state, messages: [...state.messages, finalNudge] };
3643
+ if (soManager && !state.ctx?.__finalizedDueToStructuredOutput) {
3644
+ const maxPostLoopRetries = soManager.maxRetries;
3645
+ for (let postRetry = 0; postRetry < maxPostLoopRetries; postRetry++) {
3646
+ if (state.ctx?.__finalizedDueToStructuredOutput) break;
3647
+ const last = state.messages[state.messages.length - 1];
3648
+ const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
3649
+ if (!lastHasToolCalls) {
3650
+ const isLastAttempt = postRetry === maxPostLoopRetries - 1;
3651
+ const nudge = soManager.buildNudgeMessage(isLastAttempt);
3652
+ state = { ...state, messages: [...state.messages, nudge] };
3202
3653
  }
3203
3654
  try {
3204
3655
  state = { ...state, ...await agentCore(state) };
@@ -3298,25 +3749,24 @@ function createAgent(opts) {
3298
3749
  status: "success"
3299
3750
  });
3300
3751
  const finalAssistantMsg = getLastAssistantMessage(res.messages);
3301
- const content = getMessageText(finalAssistantMsg);
3752
+ const content = extractMessageText2(finalAssistantMsg);
3302
3753
  let parsed = void 0;
3303
- const schema = opts.outputSchema;
3304
- if (schema && res.ctx?.__structuredOutputParsed) {
3305
- parsed = res.ctx.__structuredOutputParsed;
3306
- } else if (schema && content) {
3307
- let jsonText = null;
3308
- const fenced = content.match(/```(?:json)?\n([\s\S]*?)```/i);
3309
- if (fenced && fenced[1]) jsonText = fenced[1].trim();
3310
- else {
3311
- const braceIdx = content.indexOf("{");
3312
- const bracketIdx = content.indexOf("[");
3313
- const start = [braceIdx, bracketIdx].filter((i) => i >= 0).sort((a, b) => a - b)[0];
3314
- if (start !== void 0) jsonText = content.slice(start).trim();
3315
- }
3316
- try {
3317
- const raw = JSON.parse(jsonText ?? content);
3318
- parsed = schema.parse(raw);
3319
- } catch {
3754
+ let outputError = void 0;
3755
+ if (soManager) {
3756
+ if (res.ctx?.__structuredOutputParsed) {
3757
+ parsed = res.ctx.__structuredOutputParsed;
3758
+ } else if (content) {
3759
+ const fallbackResult = soManager.parseFromContent(content);
3760
+ if (fallbackResult.success) {
3761
+ parsed = fallbackResult.data;
3762
+ } else {
3763
+ outputError = fallbackResult.error;
3764
+ }
3765
+ } else {
3766
+ const noResult = soManager.noOutputResult(1);
3767
+ if (!noResult.success) {
3768
+ outputError = noResult.error;
3769
+ }
3320
3770
  }
3321
3771
  }
3322
3772
  emit({ type: "finalAnswer", content });
@@ -3327,6 +3777,7 @@ function createAgent(opts) {
3327
3777
  return {
3328
3778
  content,
3329
3779
  output: parsed,
3780
+ outputError,
3330
3781
  metadata: { usage: res.usage },
3331
3782
  messages: res.messages,
3332
3783
  state: res
@@ -3450,7 +3901,7 @@ function createAgentNode(opts) {
3450
3901
  ...messages,
3451
3902
  response
3452
3903
  ];
3453
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
3904
+ const rawUsage = response?.usage || response?.usage_metadata || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage || response?.response_metadata?.usage_metadata || response?.response_metadata?.usageMetadata;
3454
3905
  const normalized = normalizeUsage(rawUsage);
3455
3906
  const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
3456
3907
  const providerName = getProviderName(runtime.model || opts.model);
@@ -3498,25 +3949,27 @@ function createAgentNode(opts) {
3498
3949
  }
3499
3950
 
3500
3951
  // src/smart/contextPolicy.ts
3501
- function messageText(message) {
3502
- if (typeof message.content === "string") return message.content;
3503
- if (Array.isArray(message.content)) {
3504
- return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
3505
- }
3506
- return "";
3507
- }
3508
3952
  function collectRecentTurns(messages, lastTurnsToKeep) {
3509
3953
  if (lastTurnsToKeep <= 0) return [];
3510
3954
  const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
3511
3955
  const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
3512
- let userTurnCount = 0;
3956
+ const totalUserTurns = body.filter((m) => m.role === "user").length;
3957
+ const countAssistantTurns = totalUserTurns < lastTurnsToKeep;
3958
+ let turnCount = 0;
3513
3959
  const collected = [];
3514
3960
  for (let index = body.length - 1; index >= 0; index -= 1) {
3515
3961
  const message = body[index];
3516
3962
  collected.unshift(message);
3517
- if (message.role === "user") {
3518
- userTurnCount += 1;
3519
- if (userTurnCount >= lastTurnsToKeep) break;
3963
+ const isTurnBoundary = countAssistantTurns ? message.role === "assistant" : message.role === "user";
3964
+ if (isTurnBoundary) {
3965
+ turnCount += 1;
3966
+ if (turnCount >= lastTurnsToKeep) break;
3967
+ }
3968
+ }
3969
+ if (countAssistantTurns && collected.length > 0 && collected[0].role !== "user") {
3970
+ const firstUserMsg = body.find((m) => m.role === "user");
3971
+ if (firstUserMsg && !collected.includes(firstUserMsg)) {
3972
+ collected.unshift(firstUserMsg);
3520
3973
  }
3521
3974
  }
3522
3975
  return [...systemPrefix, ...collected];
@@ -3536,10 +3989,6 @@ function renderStructuredSummary(summary) {
3536
3989
  lines.push("Open questions:");
3537
3990
  lines.push(...summary.open_questions.map((question) => `- ${question}`));
3538
3991
  }
3539
- if (summary.discarded_obsolete.length > 0) {
3540
- lines.push("Discarded obsolete:");
3541
- lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
3542
- }
3543
3992
  return lines.join("\n");
3544
3993
  }
3545
3994
  function renderMemoryBlock(facts) {
@@ -3552,11 +4001,48 @@ function renderMemoryBlock(facts) {
3552
4001
  function clampToBudget(messages, maxContextTokens) {
3553
4002
  let working = [...messages];
3554
4003
  while (working.length > 2) {
3555
- const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
4004
+ const tokenCount = countApproxTokens(working.map(extractMessageText2).join("\n"));
3556
4005
  if (tokenCount <= maxContextTokens) return working;
3557
4006
  const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
3558
4007
  if (firstNonSystem < 0) break;
3559
- working.splice(firstNonSystem, 1);
4008
+ const target = working[firstNonSystem];
4009
+ if (target.role === "assistant" && Array.isArray(target.tool_calls) && target.tool_calls.length > 0) {
4010
+ const toolCallIds = new Set(
4011
+ target.tool_calls.map((tc) => tc.id).filter(Boolean)
4012
+ );
4013
+ working = working.filter((m, idx) => {
4014
+ if (idx === firstNonSystem) return false;
4015
+ if (m.role === "tool" && m.tool_call_id && toolCallIds.has(m.tool_call_id)) return false;
4016
+ return true;
4017
+ });
4018
+ } else if (target.role === "tool") {
4019
+ const toolCallId = target.tool_call_id;
4020
+ let parentIdx = -1;
4021
+ if (toolCallId) {
4022
+ for (let i = firstNonSystem - 1; i >= 0; i--) {
4023
+ const m = working[i];
4024
+ if (m.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.some((tc) => tc.id === toolCallId)) {
4025
+ parentIdx = i;
4026
+ break;
4027
+ }
4028
+ }
4029
+ }
4030
+ if (parentIdx >= 0) {
4031
+ const parent = working[parentIdx];
4032
+ const parentToolCallIds = new Set(
4033
+ (parent.tool_calls || []).map((tc) => tc.id).filter(Boolean)
4034
+ );
4035
+ working = working.filter((m, idx) => {
4036
+ if (idx === parentIdx) return false;
4037
+ if (m.role === "tool" && m.tool_call_id && parentToolCallIds.has(m.tool_call_id)) return false;
4038
+ return true;
4039
+ });
4040
+ } else {
4041
+ working.splice(firstNonSystem, 1);
4042
+ }
4043
+ } else {
4044
+ working.splice(firstNonSystem, 1);
4045
+ }
3560
4046
  }
3561
4047
  return working;
3562
4048
  }
@@ -3589,18 +4075,6 @@ function buildModelMessages(state, config) {
3589
4075
  // src/nodes/contextSummarize.ts
3590
4076
  var systemMessage = (content) => ({ role: "system", content });
3591
4077
  var humanMessage = (content) => ({ role: "user", content });
3592
- function isSyntheticSummaryToolMessage(message) {
3593
- return message.role === "tool" && message.name === "summarize_context";
3594
- }
3595
- function isSyntheticSummaryAssistantMessage(message) {
3596
- if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
3597
- return false;
3598
- }
3599
- return message.tool_calls.some((toolCall) => {
3600
- const toolName = toolCall?.function?.name || toolCall?.name;
3601
- return toolName === "summarize_context";
3602
- });
3603
- }
3604
4078
  function validateMessageSequence(messages) {
3605
4079
  const validated = [];
3606
4080
  const pendingToolCallIds = /* @__PURE__ */ new Set();
@@ -3825,7 +4299,7 @@ function createContextSummarizeNode(opts) {
3825
4299
  (m) => {
3826
4300
  if (m.role !== "tool") return false;
3827
4301
  if (isSummarizedToolPlaceholder(m.content)) return false;
3828
- if (isSyntheticSummaryToolMessage(m)) return false;
4302
+ if (isSyntheticSummaryMessage(m)) return false;
3829
4303
  const histEntry = findToolHistoryEntry(state, m.tool_call_id, m.name);
3830
4304
  if (histEntry?.retentionPolicy === "keep_full") return false;
3831
4305
  if (!histEntry?.retentionPolicy) {
@@ -3838,15 +4312,14 @@ function createContextSummarizeNode(opts) {
3838
4312
  if (compressableMessages.length === 0) {
3839
4313
  return {};
3840
4314
  }
3841
- const allTextBefore = messages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("") : "").join("\n");
3842
- const tokenCountBefore = countApproxTokens(allTextBefore);
4315
+ const tokenCountBefore = countMessagesTokens(messages);
3843
4316
  const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
3844
4317
  const availableTokensForConversation = summaryPromptMaxTokens - 500;
3845
4318
  const messageTexts = [];
3846
4319
  let accumulatedTokens = 0;
3847
4320
  for (let i = messages.length - 1; i >= 0; i--) {
3848
4321
  const m = messages[i];
3849
- if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
4322
+ if (isSyntheticSummaryMessage(m) || isSyntheticSummaryMessage(m)) {
3850
4323
  continue;
3851
4324
  }
3852
4325
  let content = "";
@@ -3867,13 +4340,13 @@ function createContextSummarizeNode(opts) {
3867
4340
  }
3868
4341
  let prefix = `${m.role.toUpperCase()}`;
3869
4342
  if (m.name) prefix += ` (${m.name})`;
3870
- const messageText2 = `${prefix}: ${content}`;
3871
- const messageTokens = countApproxTokens(messageText2);
4343
+ const messageText = `${prefix}: ${content}`;
4344
+ const messageTokens = countApproxTokens(messageText);
3872
4345
  if (accumulatedTokens + messageTokens > availableTokensForConversation) {
3873
4346
  messageTexts.unshift("[Earlier conversation history omitted for brevity]");
3874
4347
  break;
3875
4348
  }
3876
- messageTexts.unshift(messageText2);
4349
+ messageTexts.unshift(messageText);
3877
4350
  accumulatedTokens += messageTokens;
3878
4351
  }
3879
4352
  const conversationText = messageTexts.join("\n\n");
@@ -4015,7 +4488,7 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4015
4488
  const toolResponsesConfig = resolved.toolResponses;
4016
4489
  const newMessages = messages.map((m) => {
4017
4490
  if (m.role === "tool") {
4018
- if (isSyntheticSummaryToolMessage(m)) {
4491
+ if (isSyntheticSummaryMessage(m)) {
4019
4492
  return m;
4020
4493
  }
4021
4494
  const toolCallId = m.tool_call_id;
@@ -4073,8 +4546,7 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4073
4546
  const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
4074
4547
  const summaryRecords = Array.isArray(state.summaryRecords) ? [...state.summaryRecords, { ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }] : [{ ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }];
4075
4548
  const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
4076
- const allTextAfter = finalMessages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("") : "").join("\n");
4077
- const tokenCountAfter = countApproxTokens(allTextAfter);
4549
+ const tokenCountAfter = countMessagesTokens(finalMessages);
4078
4550
  const summarizationEvent = {
4079
4551
  type: "summarization",
4080
4552
  summary: summaryText,
@@ -4125,25 +4597,6 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4125
4597
  };
4126
4598
  };
4127
4599
  }
4128
-
4129
- // src/utils/tokenManager.ts
4130
- async function applyTokenLimits({
4131
- state,
4132
- limits
4133
- }) {
4134
- const messages = state.messages || [];
4135
- const budget = limits.contextTokenLimit;
4136
- let total = messages.reduce((acc, m) => acc + countApproxTokens(String(m.content || "")), 0);
4137
- if (total <= budget) return state;
4138
- const newMessages = [...messages];
4139
- while (total > budget && newMessages.length > 1) {
4140
- const idx = newMessages.findIndex((m) => m.role !== "system");
4141
- if (idx === -1) break;
4142
- const removed = newMessages.splice(idx, 1)[0];
4143
- total -= countApproxTokens(String(removed.content || ""));
4144
- }
4145
- return { ...state, messages: newMessages };
4146
- }
4147
4600
  var todoStatusSchema = zod.z.enum(["not-started", "in-progress", "completed", "blocked"]);
4148
4601
  var todoWriteItemSchema = zod.z.object({
4149
4602
  id: zod.z.number().int().min(1).describe("Sequential id starting from 1"),
@@ -4206,6 +4659,12 @@ function calculateAdherenceScore(todoList) {
4206
4659
  const completedCount = todoList.filter((item) => item.status === "completed").length;
4207
4660
  return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
4208
4661
  }
4662
+ function formatTodoListSummary(todoList) {
4663
+ if (!Array.isArray(todoList) || todoList.length === 0) {
4664
+ return "No plan items.";
4665
+ }
4666
+ return todoList.map((item) => `${item.id}. ${item.title || item.step || item.description || `Step ${item.id}`} [${item.status}]`).join("\n");
4667
+ }
4209
4668
  function createContextTools(stateRef, opts) {
4210
4669
  const tools = [];
4211
4670
  if (opts?.planningEnabled) {
@@ -4218,11 +4677,43 @@ function createContextTools(stateRef, opts) {
4218
4677
  todoList: zod.z.array(zod.z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
4219
4678
  }),
4220
4679
  func: async ({ operation, expectedVersion, todoList }) => {
4221
- const onEvent = manageTodo._stateRef?.__onEvent;
4680
+ const toolStateRef = manageTodo._stateRef;
4681
+ const onEvent = toolStateRef?.__onEvent;
4682
+ const emitPlanEvent = (list) => {
4683
+ const version = stateRef.planVersion || 1;
4684
+ const adherenceScore = stateRef.adherenceScore || 0;
4685
+ const planData = {
4686
+ source: "manage_todo_list",
4687
+ operation,
4688
+ version,
4689
+ adherenceScore,
4690
+ count: Array.isArray(list) ? list.length : 0
4691
+ };
4692
+ onEvent?.({ type: "plan", todoList: list, ...planData });
4693
+ const traceSession = toolStateRef?.ctx?.__traceSession;
4694
+ recordTraceEvent(traceSession, {
4695
+ type: "plan",
4696
+ label: `Plan ${operation}`,
4697
+ actor: { scope: "agent", name: "manage_todo_list", role: "planner" },
4698
+ sections: [
4699
+ {
4700
+ kind: "summary",
4701
+ label: "Todo List",
4702
+ content: formatTodoListSummary(list)
4703
+ },
4704
+ {
4705
+ kind: "metadata",
4706
+ label: "Plan Metadata",
4707
+ data: planData
4708
+ }
4709
+ ],
4710
+ debug: planData
4711
+ });
4712
+ };
4222
4713
  const currentVersion = stateRef.planVersion || 0;
4223
4714
  if (operation === "read") {
4224
4715
  const list = stateRef.todoList || [];
4225
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
4716
+ emitPlanEvent(list);
4226
4717
  return list;
4227
4718
  }
4228
4719
  if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
@@ -4287,9 +4778,10 @@ function createContextTools(stateRef, opts) {
4287
4778
  adherenceScore: stateRef.adherenceScore || 0
4288
4779
  };
4289
4780
  }
4290
- const patchIds = /* @__PURE__ */ new Set();
4781
+ const currentMap = new Map(currentList.map((item) => [item.id, item]));
4782
+ const patchMap = /* @__PURE__ */ new Map();
4291
4783
  for (const item of parsed.data) {
4292
- if (patchIds.has(item.id)) {
4784
+ if (patchMap.has(item.id)) {
4293
4785
  return {
4294
4786
  status: "error",
4295
4787
  operation,
@@ -4298,10 +4790,7 @@ function createContextTools(stateRef, opts) {
4298
4790
  adherenceScore: stateRef.adherenceScore || 0
4299
4791
  };
4300
4792
  }
4301
- patchIds.add(item.id);
4302
- }
4303
- const currentMap = new Map(currentList.map((item) => [item.id, item]));
4304
- for (const item of parsed.data) {
4793
+ patchMap.set(item.id, item);
4305
4794
  if (!currentMap.has(item.id)) {
4306
4795
  return {
4307
4796
  status: "error",
@@ -4313,7 +4802,7 @@ function createContextTools(stateRef, opts) {
4313
4802
  }
4314
4803
  }
4315
4804
  const mergedList = currentList.map((item) => {
4316
- const patch = parsed.data.find((candidate) => candidate.id === item.id);
4805
+ const patch = patchMap.get(item.id);
4317
4806
  return patch ? normalizeTodoItem(patch, item) : item;
4318
4807
  });
4319
4808
  const validation = validatePlanInvariants(mergedList, false);
@@ -4337,7 +4826,7 @@ function createContextTools(stateRef, opts) {
4337
4826
  version: stateRef.planVersion || 1,
4338
4827
  adherenceScore: stateRef.adherenceScore || 0
4339
4828
  };
4340
- onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
4829
+ emitPlanEvent(stateRef.todoList || []);
4341
4830
  return payload;
4342
4831
  }
4343
4832
  });
@@ -4362,40 +4851,10 @@ function createContextTools(stateRef, opts) {
4362
4851
  });
4363
4852
  getTool._stateRef = stateRef;
4364
4853
  tools.push(getTool);
4365
- if (opts?.outputSchema) {
4366
- const responseTool = createTool({
4367
- name: "response",
4368
- description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
4369
- schema: opts.outputSchema.shape ? opts.outputSchema : zod.z.any(),
4370
- func: async (data) => {
4371
- try {
4372
- const validated = opts.outputSchema.parse ? opts.outputSchema.parse(data) : data;
4373
- return { __finalStructuredOutput: true, data: validated };
4374
- } catch (e) {
4375
- return { error: "Schema validation failed", details: e?.message };
4376
- }
4377
- }
4378
- });
4379
- responseTool._stateRef = stateRef;
4380
- tools.push(responseTool);
4381
- }
4382
4854
  return tools;
4383
4855
  }
4384
4856
 
4385
4857
  // src/graph/decisions.ts
4386
- function isSyntheticSummaryMessage2(message) {
4387
- if (!message) return false;
4388
- if (message.role === "tool" && message.name === "summarize_context") {
4389
- return true;
4390
- }
4391
- if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
4392
- return message.tool_calls.some((toolCall) => {
4393
- const toolName = toolCall?.function?.name || toolCall?.name;
4394
- return toolName === "summarize_context";
4395
- });
4396
- }
4397
- return false;
4398
- }
4399
4858
  function needsSummarization(state, opts, summarizationEnabled) {
4400
4859
  if (!summarizationEnabled) {
4401
4860
  return false;
@@ -4409,30 +4868,9 @@ function needsSummarization(state, opts, summarizationEnabled) {
4409
4868
  }
4410
4869
  const maxTok = resolved.summarization.summaryTriggerTokens;
4411
4870
  try {
4412
- const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
4413
- if (typeof m.content === "string") return m.content;
4414
- if (Array.isArray(m.content)) {
4415
- return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
4416
- }
4417
- if (m.content && typeof m.content === "object") {
4418
- return JSON.stringify(m.content);
4419
- }
4420
- return "";
4421
- }).join("\n");
4422
- const tokenCount = countApproxTokens(allText);
4423
- const needsSum = tokenCount > maxTok;
4424
- if (process.env.DEBUG_SUMMARIZATION) {
4425
- const msgCount = (state.messages || []).length;
4426
- const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
4427
- const toolDetails = toolMsgs.map((m, i) => {
4428
- const contentStr = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
4429
- return `[${i}] id:${m.tool_call_id?.slice(-8) || "none"} len:${contentStr.length} preview:"${contentStr.slice(0, 30)}..."`;
4430
- });
4431
- console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
4432
- toolDetails.forEach((d) => console.log(` ${d}`));
4433
- console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
4434
- }
4435
- return needsSum;
4871
+ const filtered = (state.messages || []).filter((message) => !isSyntheticSummaryMessage(message));
4872
+ const tokenCount = countMessagesTokens(filtered);
4873
+ return tokenCount > maxTok;
4436
4874
  } catch {
4437
4875
  return false;
4438
4876
  }
@@ -4561,13 +4999,6 @@ async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
4561
4999
  }
4562
5000
  return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
4563
5001
  }
4564
- function renderMemoryFacts(facts) {
4565
- if (facts.length === 0) return "";
4566
- return [
4567
- "Memory facts:",
4568
- ...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
4569
- ].join("\n");
4570
- }
4571
5002
 
4572
5003
  // src/smart/index.ts
4573
5004
  function createSmartAgent(opts) {
@@ -4587,7 +5018,7 @@ function createSmartAgent(opts) {
4587
5018
  useTodoList: planningEnabled
4588
5019
  };
4589
5020
  const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
4590
- const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
5021
+ const contextTools = createContextTools(stateRef, { planningEnabled });
4591
5022
  const mergedTools = [...opts.tools ?? [], ...contextTools];
4592
5023
  const base = createAgent({ ...runtimeOpts, tools: mergedTools });
4593
5024
  base.__runtime.runtimeProfile = resolved.runtimeProfile;
@@ -4596,12 +5027,8 @@ function createSmartAgent(opts) {
4596
5027
  const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
4597
5028
  const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
4598
5029
  const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
4599
- const structuredOutputHint = opts.outputSchema ? [
4600
- "A structured output schema is active.",
4601
- "Do NOT output the final JSON directly as an assistant message.",
4602
- "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
4603
- "Call it exactly once then STOP producing further assistant messages."
4604
- ].join("\n") : "";
5030
+ const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
5031
+ const structuredOutputHint = soManager ? soManager.buildSystemPromptHint() : "";
4605
5032
  const runtimeHint = [
4606
5033
  resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
4607
5034
  `Context policy: ${resolved.context.policy}.`,
@@ -4611,8 +5038,7 @@ function createSmartAgent(opts) {
4611
5038
  ].join("\n");
4612
5039
  function latestUserPrompt(messages) {
4613
5040
  const latest = [...messages].reverse().find((message) => message.role === "user");
4614
- if (!latest) return "";
4615
- return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
5041
+ return latest ? extractMessageText2(latest) : "";
4616
5042
  }
4617
5043
  async function syncMemory(state) {
4618
5044
  const query = latestUserPrompt(state.messages || []);
@@ -4652,6 +5078,18 @@ function createSmartAgent(opts) {
4652
5078
  );
4653
5079
  return { role: "system", content: sys };
4654
5080
  }
5081
+ async function trySummarize(currentState, currentRawMessages) {
5082
+ if (!summarizer) return { state: currentState, rawMessages: currentRawMessages, compressed: false };
5083
+ const delta = await summarizer(currentState);
5084
+ if (!delta || Object.keys(delta).length === 0) {
5085
+ const ctx2 = { ...currentState.ctx || {}, __summarizationExhausted: true };
5086
+ return { state: { ...currentState, ctx: ctx2 }, rawMessages: currentRawMessages, compressed: false };
5087
+ }
5088
+ const ctx = { ...currentState.ctx || {} };
5089
+ delete ctx.__summarizationExhausted;
5090
+ const updated = await persistLatestSummary(syncPlanState({ ...currentState, ...delta, ctx }));
5091
+ return { state: updated, rawMessages: [...updated.messages || currentRawMessages], compressed: true };
5092
+ }
4655
5093
  const instance = {
4656
5094
  invoke: async (input, config) => {
4657
5095
  stateRef.toolHistory = input.toolHistory;
@@ -4670,12 +5108,10 @@ function createSmartAgent(opts) {
4670
5108
  state = { ...state, messages: rawMessages };
4671
5109
  const next = summarizationEnabled ? decideBefore(state) : "agent";
4672
5110
  if (next === "contextSummarize" && summarizer) {
4673
- const delta = await summarizer(state);
4674
- if (!delta || Object.keys(delta).length === 0) ; else {
4675
- state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4676
- rawMessages = [...state.messages || rawMessages];
4677
- continue;
4678
- }
5111
+ const result = await trySummarize(state, rawMessages);
5112
+ state = result.state;
5113
+ rawMessages = result.rawMessages;
5114
+ if (result.compressed) continue;
4679
5115
  }
4680
5116
  const modelMessages = buildModelMessages(state, resolved);
4681
5117
  const res = await base.invoke({ ...state, messages: modelMessages }, config);
@@ -4703,23 +5139,20 @@ function createSmartAgent(opts) {
4703
5139
  const ctx = { ...state.ctx || {} };
4704
5140
  delete ctx.__needsSummarization;
4705
5141
  state = { ...state, ctx };
4706
- const delta = await summarizer(state);
4707
- if (delta && Object.keys(delta).length > 0) {
4708
- state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4709
- rawMessages = [...state.messages || rawMessages];
4710
- continue;
4711
- }
5142
+ const result = await trySummarize(state, rawMessages);
5143
+ state = result.state;
5144
+ rawMessages = result.rawMessages;
5145
+ continue;
4712
5146
  }
4713
5147
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
5148
+ if (opts.outputSchema && lastResult?.output != null) break;
4714
5149
  if (summarizationEnabled) {
4715
5150
  const after = decideAfter(state);
4716
5151
  if (after === "contextSummarize" && summarizer) {
4717
- const delta = await summarizer(state);
4718
- if (delta && Object.keys(delta).length > 0) {
4719
- state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4720
- rawMessages = [...state.messages || rawMessages];
4721
- continue;
4722
- }
5152
+ const result = await trySummarize(state, rawMessages);
5153
+ state = result.state;
5154
+ rawMessages = result.rawMessages;
5155
+ if (result.compressed) continue;
4723
5156
  }
4724
5157
  }
4725
5158
  break;
@@ -4883,35 +5316,6 @@ async function runSmartAgentEvalHarness(params) {
4883
5316
  }
4884
5317
  return results;
4885
5318
  }
4886
-
4887
- // src/utils/content.ts
4888
- function contentToString(content) {
4889
- try {
4890
- if (content == null) return "";
4891
- if (typeof content === "string") return content;
4892
- if (Array.isArray(content)) {
4893
- return content.map((p) => {
4894
- if (!p || typeof p !== "object") return String(p);
4895
- if (p.type === "text") return String(p.text ?? "");
4896
- if (p.type === "image_url") {
4897
- const img = p.image_url;
4898
- const url = typeof img === "string" ? img : img?.url;
4899
- const detail = typeof img === "string" ? void 0 : img?.detail;
4900
- return `[image:${url || "unknown"}${detail ? ` detail=${detail}` : ""}]`;
4901
- }
4902
- return JSON.stringify(p);
4903
- }).join("\n");
4904
- }
4905
- return JSON.stringify(content);
4906
- } catch {
4907
- return String(content);
4908
- }
4909
- }
4910
- function mergeContentsToString(contents) {
4911
- return contents.map((c) => contentToString(c)).join("\n");
4912
- }
4913
-
4914
- // src/guardrails/checks.ts
4915
5319
  var Ajv = AjvModule__default.default?.default ?? AjvModule__default.default;
4916
5320
  var defaultAjv = new Ajv({ allErrors: true });
4917
5321
  defaultAjv.opts.strict = false;
@@ -5161,11 +5565,1755 @@ function createCodeGuardrail(options) {
5161
5565
  });
5162
5566
  }
5163
5567
 
5568
+ // src/providers/types.ts
5569
+ function emptyUsage() {
5570
+ return {
5571
+ inputTokens: 0,
5572
+ outputTokens: 0,
5573
+ totalTokens: 0,
5574
+ cachedInputTokens: 0,
5575
+ cachedWriteTokens: 0,
5576
+ cachedOutputTokens: 0,
5577
+ reasoningTokens: 0
5578
+ };
5579
+ }
5580
+ var ProviderError = class extends Error {
5581
+ constructor(message, provider, statusCode, responseBody) {
5582
+ super(message);
5583
+ this.provider = provider;
5584
+ this.statusCode = statusCode;
5585
+ this.responseBody = responseBody;
5586
+ this.name = "ProviderError";
5587
+ }
5588
+ };
5589
+
5590
+ // src/providers/base.ts
5591
+ var BaseProvider = class {
5592
+ };
5593
+
5594
+ // src/providers/utils/sse.ts
5595
+ async function* parseSSEStream(body, signal) {
5596
+ const reader = body.getReader();
5597
+ const decoder = new TextDecoder();
5598
+ let buffer = "";
5599
+ try {
5600
+ while (true) {
5601
+ if (signal?.aborted) ;
5602
+ const { done, value } = await reader.read();
5603
+ if (done) break;
5604
+ buffer += decoder.decode(value, { stream: true });
5605
+ const parts = buffer.split(/\r?\n\r?\n/);
5606
+ buffer = parts.pop() ?? "";
5607
+ for (const part of parts) {
5608
+ if (!part.trim()) continue;
5609
+ const event = parseSSEBlock(part);
5610
+ if (event) yield event;
5611
+ }
5612
+ }
5613
+ if (buffer.trim()) {
5614
+ const event = parseSSEBlock(buffer);
5615
+ if (event) yield event;
5616
+ }
5617
+ } finally {
5618
+ reader.releaseLock();
5619
+ }
5620
+ }
5621
+ function parseSSEBlock(block) {
5622
+ let event;
5623
+ let id;
5624
+ let retry;
5625
+ const dataLines = [];
5626
+ for (const line of block.split(/\r?\n/)) {
5627
+ if (line.startsWith(":")) continue;
5628
+ const colonIdx = line.indexOf(":");
5629
+ let field;
5630
+ let value;
5631
+ if (colonIdx === -1) {
5632
+ field = line;
5633
+ value = "";
5634
+ } else {
5635
+ field = line.slice(0, colonIdx);
5636
+ value = line.slice(colonIdx + 1);
5637
+ if (value.startsWith(" ")) value = value.slice(1);
5638
+ }
5639
+ switch (field) {
5640
+ case "event":
5641
+ event = value;
5642
+ break;
5643
+ case "data":
5644
+ dataLines.push(value);
5645
+ break;
5646
+ case "id":
5647
+ id = value;
5648
+ break;
5649
+ case "retry": {
5650
+ const n = parseInt(value, 10);
5651
+ if (!isNaN(n)) retry = n;
5652
+ break;
5653
+ }
5654
+ }
5655
+ }
5656
+ if (dataLines.length === 0) return null;
5657
+ return { event, data: dataLines.join("\n"), id, retry };
5658
+ }
5659
+
5660
+ // src/providers/openai.ts
5661
+ var OpenAIProvider = class extends BaseProvider {
5662
+ constructor(config) {
5663
+ super();
5664
+ this.providerName = "openai";
5665
+ this.apiKey = config.apiKey;
5666
+ this.baseURL = (config.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "");
5667
+ this.defaultModel = config.defaultModel ?? "gpt-4o";
5668
+ this.defaultHeaders = {
5669
+ ...config.defaultHeaders,
5670
+ ...config.organization ? { "OpenAI-Organization": config.organization } : {}
5671
+ };
5672
+ }
5673
+ async complete(request) {
5674
+ const body = this.buildRequestBody(request, false);
5675
+ const res = await this.doFetch(body);
5676
+ const json = await res.json();
5677
+ return this.parseResponse(json);
5678
+ }
5679
+ async *completeStream(request) {
5680
+ const body = this.buildRequestBody(request, true);
5681
+ const res = await this.doFetch(body);
5682
+ if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
5683
+ let id = "";
5684
+ let model = "";
5685
+ const toolCallBuffers = /* @__PURE__ */ new Map();
5686
+ let usage;
5687
+ for await (const event of parseSSEStream(res.body)) {
5688
+ if (event.data === "[DONE]") break;
5689
+ let chunk;
5690
+ try {
5691
+ chunk = JSON.parse(event.data);
5692
+ } catch {
5693
+ continue;
5694
+ }
5695
+ id = chunk.id ?? id;
5696
+ model = chunk.model ?? model;
5697
+ const choice = chunk.choices?.[0];
5698
+ if (chunk.usage) {
5699
+ usage = this.extractUsage(chunk.usage);
5700
+ }
5701
+ if (!choice) {
5702
+ if (usage) yield { id, model, delta: {}, usage };
5703
+ continue;
5704
+ }
5705
+ const delta = choice.delta ?? {};
5706
+ const out = {
5707
+ id,
5708
+ model,
5709
+ delta: {},
5710
+ finishReason: mapFinishReason(choice.finish_reason)
5711
+ };
5712
+ if (delta.content) {
5713
+ out.delta.content = delta.content;
5714
+ }
5715
+ if (delta.tool_calls) {
5716
+ const partials = [];
5717
+ for (const tc of delta.tool_calls) {
5718
+ const idx = tc.index ?? 0;
5719
+ if (!toolCallBuffers.has(idx)) {
5720
+ toolCallBuffers.set(idx, { id: tc.id ?? "", name: "", arguments: "" });
5721
+ }
5722
+ const buf = toolCallBuffers.get(idx);
5723
+ if (tc.id) buf.id = tc.id;
5724
+ if (tc.function?.name) buf.name = tc.function.name;
5725
+ if (tc.function?.arguments) buf.arguments += tc.function.arguments;
5726
+ partials.push({ id: buf.id, name: buf.name || void 0, arguments: tc.function?.arguments });
5727
+ }
5728
+ out.delta.toolCalls = partials;
5729
+ }
5730
+ if (usage) out.usage = usage;
5731
+ yield out;
5732
+ }
5733
+ }
5734
+ // ─── Internals ───────────────────────────────────────────────────────────
5735
+ buildRequestBody(request, stream) {
5736
+ const body = {
5737
+ model: request.model || this.defaultModel,
5738
+ messages: request.messages.map((m) => this.toOpenAIMessage(m)),
5739
+ stream
5740
+ };
5741
+ if (stream) {
5742
+ body.stream_options = { include_usage: true };
5743
+ }
5744
+ if (request.temperature != null) body.temperature = request.temperature;
5745
+ if (request.maxTokens != null) body.max_tokens = request.maxTokens;
5746
+ if (request.topP != null) body.top_p = request.topP;
5747
+ if (request.stop) body.stop = request.stop;
5748
+ if (request.tools?.length) {
5749
+ body.tools = request.tools.map((t) => this.toOpenAITool(t));
5750
+ }
5751
+ if (request.toolChoice != null) {
5752
+ if (typeof request.toolChoice === "string") {
5753
+ body.tool_choice = request.toolChoice;
5754
+ } else {
5755
+ body.tool_choice = { type: "function", function: { name: request.toolChoice.name } };
5756
+ }
5757
+ }
5758
+ if (request.responseFormat) {
5759
+ if (request.responseFormat.type === "json_schema") {
5760
+ body.response_format = {
5761
+ type: "json_schema",
5762
+ json_schema: {
5763
+ name: request.responseFormat.name ?? "response",
5764
+ schema: request.responseFormat.schema,
5765
+ strict: true
5766
+ }
5767
+ };
5768
+ } else if (request.responseFormat.type === "json_object") {
5769
+ body.response_format = { type: "json_object" };
5770
+ }
5771
+ }
5772
+ if (request.extra) {
5773
+ Object.assign(body, request.extra);
5774
+ }
5775
+ return body;
5776
+ }
5777
+ toOpenAIMessage(m) {
5778
+ const msg = { role: m.role };
5779
+ if (typeof m.content === "string") {
5780
+ msg.content = m.content;
5781
+ } else if (Array.isArray(m.content)) {
5782
+ msg.content = m.content.map((part) => {
5783
+ if (part.type === "text") return { type: "text", text: part.text };
5784
+ if (part.type === "image") {
5785
+ const src = part.source;
5786
+ const url = src.type === "url" ? src.url : `data:${src.mediaType};base64,${src.data}`;
5787
+ return { type: "image_url", image_url: { url } };
5788
+ }
5789
+ return part;
5790
+ });
5791
+ }
5792
+ if (m.role === "assistant" && m.toolCalls?.length) {
5793
+ msg.tool_calls = m.toolCalls.map((tc) => ({
5794
+ id: tc.id,
5795
+ type: "function",
5796
+ function: { name: tc.name, arguments: tc.arguments }
5797
+ }));
5798
+ }
5799
+ if (m.role === "tool" && m.toolCallId) {
5800
+ msg.tool_call_id = m.toolCallId;
5801
+ }
5802
+ if (m.name) msg.name = m.name;
5803
+ return msg;
5804
+ }
5805
+ toOpenAITool(t) {
5806
+ const fn = {
5807
+ name: t.name,
5808
+ description: t.description,
5809
+ parameters: t.parameters
5810
+ };
5811
+ if (t.strict) {
5812
+ fn.strict = true;
5813
+ }
5814
+ return { type: "function", function: fn };
5815
+ }
5816
+ async doFetch(body) {
5817
+ const url = `${this.baseURL}/chat/completions`;
5818
+ const res = await fetch(url, {
5819
+ method: "POST",
5820
+ headers: {
5821
+ "Content-Type": "application/json",
5822
+ Authorization: `Bearer ${this.apiKey}`,
5823
+ ...this.defaultHeaders
5824
+ },
5825
+ body: JSON.stringify(body)
5826
+ });
5827
+ if (!res.ok) {
5828
+ const text = await res.text().catch(() => "");
5829
+ throw new ProviderError(
5830
+ `OpenAI API error ${res.status}: ${text}`,
5831
+ this.providerName,
5832
+ res.status,
5833
+ text
5834
+ );
5835
+ }
5836
+ return res;
5837
+ }
5838
+ parseResponse(json) {
5839
+ const choice = json.choices?.[0];
5840
+ const message = choice?.message ?? {};
5841
+ const toolCalls = (message.tool_calls ?? []).map((tc) => ({
5842
+ id: tc.id,
5843
+ name: tc.function?.name ?? "",
5844
+ arguments: tc.function?.arguments ?? "{}"
5845
+ }));
5846
+ return {
5847
+ id: json.id ?? "",
5848
+ model: json.model ?? "",
5849
+ content: message.content ?? null,
5850
+ toolCalls,
5851
+ usage: this.extractUsage(json.usage),
5852
+ finishReason: mapFinishReason(choice?.finish_reason),
5853
+ raw: json
5854
+ };
5855
+ }
5856
+ extractUsage(usage) {
5857
+ if (!usage) return emptyUsage();
5858
+ const promptDetails = usage.prompt_tokens_details ?? {};
5859
+ const completionDetails = usage.completion_tokens_details ?? {};
5860
+ return {
5861
+ inputTokens: usage.prompt_tokens ?? 0,
5862
+ outputTokens: usage.completion_tokens ?? 0,
5863
+ totalTokens: usage.total_tokens ?? (usage.prompt_tokens ?? 0) + (usage.completion_tokens ?? 0),
5864
+ cachedInputTokens: promptDetails.cached_tokens ?? 0,
5865
+ cachedWriteTokens: 0,
5866
+ cachedOutputTokens: 0,
5867
+ reasoningTokens: completionDetails.reasoning_tokens ?? 0
5868
+ };
5869
+ }
5870
+ };
5871
+ function mapFinishReason(reason) {
5872
+ switch (reason) {
5873
+ case "stop":
5874
+ return "stop";
5875
+ case "tool_calls":
5876
+ return "tool_calls";
5877
+ case "length":
5878
+ return "length";
5879
+ case "content_filter":
5880
+ return "content_filter";
5881
+ default:
5882
+ return "stop";
5883
+ }
5884
+ }
5885
+
5886
+ // src/providers/anthropic.ts
5887
+ var AnthropicProvider = class extends BaseProvider {
5888
+ constructor(config) {
5889
+ super();
5890
+ this.providerName = "anthropic";
5891
+ this.apiKey = config.apiKey;
5892
+ this.baseURL = (config.baseURL ?? "https://api.anthropic.com").replace(/\/$/, "");
5893
+ this.defaultModel = config.defaultModel ?? "claude-sonnet-4-20250514";
5894
+ this.defaultHeaders = config.defaultHeaders ?? {};
5895
+ this.anthropicVersion = config.anthropicVersion ?? "2023-06-01";
5896
+ }
5897
+ async complete(request) {
5898
+ const body = this.buildRequestBody(request, false);
5899
+ const res = await this.doFetch(body);
5900
+ const json = await res.json();
5901
+ return this.parseResponse(json);
5902
+ }
5903
+ async *completeStream(request) {
5904
+ const body = this.buildRequestBody(request, true);
5905
+ const res = await this.doFetch(body);
5906
+ if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
5907
+ let id = "";
5908
+ let model = "";
5909
+ let inputTokens = 0;
5910
+ let outputTokens = 0;
5911
+ let cacheReadInputTokens = 0;
5912
+ let cacheCreationInputTokens = 0;
5913
+ const currentToolCalls = /* @__PURE__ */ new Map();
5914
+ for await (const event of parseSSEStream(res.body)) {
5915
+ let data;
5916
+ try {
5917
+ data = JSON.parse(event.data);
5918
+ } catch {
5919
+ continue;
5920
+ }
5921
+ const eventType = event.event ?? data.type;
5922
+ switch (eventType) {
5923
+ case "message_start": {
5924
+ const msg = data.message;
5925
+ id = msg?.id ?? "";
5926
+ model = msg?.model ?? "";
5927
+ if (msg?.usage) {
5928
+ inputTokens = msg.usage.input_tokens ?? 0;
5929
+ cacheReadInputTokens = msg.usage.cache_read_input_tokens ?? 0;
5930
+ cacheCreationInputTokens = msg.usage.cache_creation_input_tokens ?? 0;
5931
+ }
5932
+ break;
5933
+ }
5934
+ case "content_block_start": {
5935
+ const idx = data.index ?? 0;
5936
+ const block = data.content_block;
5937
+ if (block?.type === "tool_use") {
5938
+ currentToolCalls.set(idx, {
5939
+ id: block.id ?? "",
5940
+ name: block.name ?? "",
5941
+ arguments: ""
5942
+ });
5943
+ }
5944
+ break;
5945
+ }
5946
+ case "content_block_delta": {
5947
+ const idx = data.index ?? 0;
5948
+ const delta = data.delta;
5949
+ if (delta?.type === "text_delta" && delta.text) {
5950
+ yield {
5951
+ id,
5952
+ model,
5953
+ delta: { content: delta.text }
5954
+ };
5955
+ } else if (delta?.type === "input_json_delta" && delta.partial_json) {
5956
+ const tc = currentToolCalls.get(idx);
5957
+ if (tc) {
5958
+ tc.arguments += delta.partial_json;
5959
+ yield {
5960
+ id,
5961
+ model,
5962
+ delta: {
5963
+ toolCalls: [{ id: tc.id, name: tc.name, arguments: delta.partial_json }]
5964
+ }
5965
+ };
5966
+ }
5967
+ }
5968
+ break;
5969
+ }
5970
+ case "content_block_stop":
5971
+ break;
5972
+ case "message_delta": {
5973
+ const delta = data.delta;
5974
+ if (data.usage) {
5975
+ outputTokens = data.usage.output_tokens ?? outputTokens;
5976
+ }
5977
+ const finishReason = mapAnthropicStopReason(delta?.stop_reason);
5978
+ const usage = {
5979
+ inputTokens,
5980
+ outputTokens,
5981
+ totalTokens: inputTokens + outputTokens,
5982
+ cachedInputTokens: cacheReadInputTokens,
5983
+ cachedWriteTokens: cacheCreationInputTokens,
5984
+ cachedOutputTokens: 0,
5985
+ reasoningTokens: 0
5986
+ };
5987
+ yield {
5988
+ id,
5989
+ model,
5990
+ delta: {},
5991
+ finishReason,
5992
+ usage
5993
+ };
5994
+ break;
5995
+ }
5996
+ case "message_stop":
5997
+ break;
5998
+ case "error": {
5999
+ const errMsg = data.error?.message ?? "Unknown Anthropic streaming error";
6000
+ throw new ProviderError(errMsg, this.providerName);
6001
+ }
6002
+ }
6003
+ }
6004
+ }
6005
+ // ─── Internals ───────────────────────────────────────────────────────────
6006
+ buildRequestBody(request, stream) {
6007
+ const { system, messages } = this.splitSystemMessages(request.messages);
6008
+ const body = {
6009
+ model: request.model || this.defaultModel,
6010
+ messages: messages.map((m) => this.toAnthropicMessage(m)),
6011
+ max_tokens: request.maxTokens ?? 4096,
6012
+ stream
6013
+ };
6014
+ if (system) body.system = system;
6015
+ if (request.temperature != null) body.temperature = request.temperature;
6016
+ if (request.topP != null) body.top_p = request.topP;
6017
+ if (request.stop) body.stop_sequences = request.stop;
6018
+ if (request.tools?.length) {
6019
+ body.tools = request.tools.map((t) => this.toAnthropicTool(t));
6020
+ }
6021
+ if (request.toolChoice != null) {
6022
+ if (request.toolChoice === "auto") {
6023
+ body.tool_choice = { type: "auto" };
6024
+ } else if (request.toolChoice === "required") {
6025
+ body.tool_choice = { type: "any" };
6026
+ } else if (request.toolChoice === "none") ; else {
6027
+ body.tool_choice = { type: "tool", name: request.toolChoice.name };
6028
+ }
6029
+ }
6030
+ if (request.extra) {
6031
+ Object.assign(body, request.extra);
6032
+ }
6033
+ return body;
6034
+ }
6035
+ splitSystemMessages(messages) {
6036
+ const systemParts = [];
6037
+ const rest = [];
6038
+ for (const m of messages) {
6039
+ if (m.role === "system") {
6040
+ if (typeof m.content === "string") {
6041
+ systemParts.push(m.content);
6042
+ } else {
6043
+ const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
6044
+ if (text) systemParts.push(text);
6045
+ }
6046
+ } else {
6047
+ rest.push(m);
6048
+ }
6049
+ }
6050
+ return {
6051
+ system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
6052
+ messages: rest
6053
+ };
6054
+ }
6055
+ toAnthropicMessage(m) {
6056
+ if (m.role === "tool" && m.toolCallId) {
6057
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
6058
+ return {
6059
+ role: "user",
6060
+ content: [
6061
+ {
6062
+ type: "tool_result",
6063
+ tool_use_id: m.toolCallId,
6064
+ content
6065
+ }
6066
+ ]
6067
+ };
6068
+ }
6069
+ if (m.role === "assistant" && m.toolCalls?.length) {
6070
+ const content = [];
6071
+ if (typeof m.content === "string" && m.content) {
6072
+ content.push({ type: "text", text: m.content });
6073
+ }
6074
+ for (const tc of m.toolCalls) {
6075
+ content.push({
6076
+ type: "tool_use",
6077
+ id: tc.id,
6078
+ name: tc.name,
6079
+ input: safeJsonParse(tc.arguments)
6080
+ });
6081
+ }
6082
+ return { role: "assistant", content };
6083
+ }
6084
+ const msg = { role: m.role };
6085
+ if (typeof m.content === "string") {
6086
+ msg.content = m.content;
6087
+ } else if (Array.isArray(m.content)) {
6088
+ msg.content = m.content.map((part) => {
6089
+ if (part.type === "text") return { type: "text", text: part.text };
6090
+ if (part.type === "image") {
6091
+ const src = part.source;
6092
+ if (src.type === "base64") {
6093
+ return {
6094
+ type: "image",
6095
+ source: { type: "base64", media_type: src.mediaType, data: src.data }
6096
+ };
6097
+ }
6098
+ return {
6099
+ type: "image",
6100
+ source: { type: "url", url: src.url }
6101
+ };
6102
+ }
6103
+ return part;
6104
+ });
6105
+ }
6106
+ return msg;
6107
+ }
6108
+ toAnthropicTool(t) {
6109
+ return {
6110
+ name: t.name,
6111
+ description: t.description,
6112
+ input_schema: t.parameters
6113
+ };
6114
+ }
6115
+ async doFetch(body) {
6116
+ const url = `${this.baseURL}/v1/messages`;
6117
+ const res = await fetch(url, {
6118
+ method: "POST",
6119
+ headers: {
6120
+ "Content-Type": "application/json",
6121
+ "x-api-key": this.apiKey,
6122
+ "anthropic-version": this.anthropicVersion,
6123
+ ...this.defaultHeaders
6124
+ },
6125
+ body: JSON.stringify(body)
6126
+ });
6127
+ if (!res.ok) {
6128
+ const text = await res.text().catch(() => "");
6129
+ throw new ProviderError(
6130
+ `Anthropic API error ${res.status}: ${text}`,
6131
+ this.providerName,
6132
+ res.status,
6133
+ text
6134
+ );
6135
+ }
6136
+ return res;
6137
+ }
6138
+ parseResponse(json) {
6139
+ const textParts = [];
6140
+ const toolCalls = [];
6141
+ for (const block of json.content ?? []) {
6142
+ if (block.type === "text") {
6143
+ textParts.push(block.text);
6144
+ } else if (block.type === "tool_use") {
6145
+ toolCalls.push({
6146
+ id: block.id,
6147
+ name: block.name,
6148
+ arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input)
6149
+ });
6150
+ }
6151
+ }
6152
+ const usage = json.usage ?? {};
6153
+ const tokenUsage = {
6154
+ inputTokens: usage.input_tokens ?? 0,
6155
+ outputTokens: usage.output_tokens ?? 0,
6156
+ totalTokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
6157
+ cachedInputTokens: usage.cache_read_input_tokens ?? 0,
6158
+ cachedWriteTokens: usage.cache_creation_input_tokens ?? 0,
6159
+ cachedOutputTokens: 0,
6160
+ reasoningTokens: 0
6161
+ };
6162
+ return {
6163
+ id: json.id ?? "",
6164
+ model: json.model ?? "",
6165
+ content: textParts.join("") || null,
6166
+ toolCalls,
6167
+ usage: tokenUsage,
6168
+ finishReason: mapAnthropicStopReason(json.stop_reason),
6169
+ raw: json
6170
+ };
6171
+ }
6172
+ };
6173
+ function mapAnthropicStopReason(reason) {
6174
+ switch (reason) {
6175
+ case "end_turn":
6176
+ case "stop_sequence":
6177
+ return "stop";
6178
+ case "tool_use":
6179
+ return "tool_calls";
6180
+ case "max_tokens":
6181
+ return "length";
6182
+ default:
6183
+ return "stop";
6184
+ }
6185
+ }
6186
+ function safeJsonParse(str) {
6187
+ try {
6188
+ return JSON.parse(str);
6189
+ } catch {
6190
+ return {};
6191
+ }
6192
+ }
6193
+
6194
+ // src/providers/azure.ts
6195
+ var AzureProvider = class extends OpenAIProvider {
6196
+ constructor(config) {
6197
+ super({
6198
+ provider: "openai",
6199
+ apiKey: config.apiKey,
6200
+ defaultModel: config.defaultModel ?? config.deploymentName ?? "gpt-4o",
6201
+ defaultHeaders: config.defaultHeaders
6202
+ });
6203
+ this.providerName = "azure";
6204
+ this.endpoint = config.endpoint.replace(/\/$/, "");
6205
+ this.apiVersion = config.apiVersion ?? "2024-10-21";
6206
+ this.deploymentName = config.deploymentName;
6207
+ this.azureApiKey = config.apiKey;
6208
+ }
6209
+ async doFetch(body) {
6210
+ const deployment = this.deploymentName ?? body.model;
6211
+ if (!deployment) {
6212
+ throw new ProviderError("Azure requires a deploymentName or model", this.providerName);
6213
+ }
6214
+ const url = `${this.endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${this.apiVersion}`;
6215
+ const { model: _model, ...bodyWithoutModel } = body;
6216
+ const res = await fetch(url, {
6217
+ method: "POST",
6218
+ headers: {
6219
+ "Content-Type": "application/json",
6220
+ "api-key": this.azureApiKey,
6221
+ ...this.defaultHeaders
6222
+ },
6223
+ body: JSON.stringify(bodyWithoutModel)
6224
+ });
6225
+ if (!res.ok) {
6226
+ const text = await res.text().catch(() => "");
6227
+ throw new ProviderError(
6228
+ `Azure OpenAI API error ${res.status}: ${text}`,
6229
+ this.providerName,
6230
+ res.status,
6231
+ text
6232
+ );
6233
+ }
6234
+ return res;
6235
+ }
6236
+ };
6237
+
6238
+ // src/providers/openaiCompatible.ts
6239
+ var OpenAICompatibleProvider = class extends OpenAIProvider {
6240
+ constructor(config) {
6241
+ super({
6242
+ provider: "openai",
6243
+ apiKey: config.apiKey,
6244
+ baseURL: config.baseURL,
6245
+ defaultModel: config.defaultModel,
6246
+ defaultHeaders: config.defaultHeaders
6247
+ });
6248
+ this.providerName = "openai-compatible";
6249
+ }
6250
+ };
6251
+ function signRequest(req) {
6252
+ const now = /* @__PURE__ */ new Date();
6253
+ const dateStamp = toDateStamp(now);
6254
+ const amzDate = toAmzDate(now);
6255
+ const parsed = new URL(req.url);
6256
+ const host = parsed.host;
6257
+ const canonicalUri = parsed.pathname || "/";
6258
+ const canonicalQuerystring = sortQueryParams(parsed.searchParams);
6259
+ const headers = {
6260
+ ...req.headers,
6261
+ host,
6262
+ "x-amz-date": amzDate
6263
+ };
6264
+ if (req.credentials.sessionToken) {
6265
+ headers["x-amz-security-token"] = req.credentials.sessionToken;
6266
+ }
6267
+ const signedHeaderKeys = Object.keys(headers).map((k) => k.toLowerCase()).sort();
6268
+ const canonicalHeaders = signedHeaderKeys.map((k) => `${k}:${headers[Object.keys(headers).find((h) => h.toLowerCase() === k)]?.trim()}`).join("\n") + "\n";
6269
+ const signedHeaders = signedHeaderKeys.join(";");
6270
+ const payloadHash = sha256(req.body);
6271
+ const canonicalRequest = [
6272
+ req.method,
6273
+ canonicalUri,
6274
+ canonicalQuerystring,
6275
+ canonicalHeaders,
6276
+ signedHeaders,
6277
+ payloadHash
6278
+ ].join("\n");
6279
+ const credentialScope = `${dateStamp}/${req.region}/${req.service}/aws4_request`;
6280
+ const stringToSign = [
6281
+ "AWS4-HMAC-SHA256",
6282
+ amzDate,
6283
+ credentialScope,
6284
+ sha256(canonicalRequest)
6285
+ ].join("\n");
6286
+ const signingKey = getSignatureKey(
6287
+ req.credentials.secretAccessKey,
6288
+ dateStamp,
6289
+ req.region,
6290
+ req.service
6291
+ );
6292
+ const signature = hmacHex(signingKey, stringToSign);
6293
+ const authorization = `AWS4-HMAC-SHA256 Credential=${req.credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
6294
+ const result = {
6295
+ ...req.headers,
6296
+ "x-amz-date": amzDate,
6297
+ Authorization: authorization
6298
+ };
6299
+ if (req.credentials.sessionToken) {
6300
+ result["x-amz-security-token"] = req.credentials.sessionToken;
6301
+ }
6302
+ return result;
6303
+ }
6304
+ function toDateStamp(d) {
6305
+ return d.toISOString().replace(/[-:T]/g, "").slice(0, 8);
6306
+ }
6307
+ function toAmzDate(d) {
6308
+ return d.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
6309
+ }
6310
+ function sha256(data) {
6311
+ return crypto.createHash("sha256").update(data, "utf8").digest("hex");
6312
+ }
6313
+ function hmac(key, data) {
6314
+ return crypto.createHmac("sha256", key).update(data, "utf8").digest();
6315
+ }
6316
+ function hmacHex(key, data) {
6317
+ return crypto.createHmac("sha256", key).update(data, "utf8").digest("hex");
6318
+ }
6319
+ function getSignatureKey(secret, dateStamp, region, service) {
6320
+ const kDate = hmac(`AWS4${secret}`, dateStamp);
6321
+ const kRegion = hmac(kDate, region);
6322
+ const kService = hmac(kRegion, service);
6323
+ return hmac(kService, "aws4_request");
6324
+ }
6325
+ function sortQueryParams(params) {
6326
+ const sorted = [...params.entries()].sort(([a], [b]) => a.localeCompare(b));
6327
+ return sorted.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
6328
+ }
6329
+
6330
+ // src/providers/bedrock.ts
6331
+ var BedrockProvider = class extends BaseProvider {
6332
+ constructor(config) {
6333
+ super();
6334
+ this.providerName = "bedrock";
6335
+ this.region = config.region;
6336
+ this.defaultModel = config.defaultModel ?? "anthropic.claude-sonnet-4-20250514-v1:0";
6337
+ this.defaultHeaders = config.defaultHeaders ?? {};
6338
+ this.credentials = {
6339
+ accessKeyId: config.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? "",
6340
+ secretAccessKey: config.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? "",
6341
+ sessionToken: config.sessionToken ?? process.env.AWS_SESSION_TOKEN
6342
+ };
6343
+ if (!this.credentials.accessKeyId || !this.credentials.secretAccessKey) {
6344
+ throw new ProviderError(
6345
+ "AWS credentials required: provide accessKeyId/secretAccessKey or set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars",
6346
+ "bedrock"
6347
+ );
6348
+ }
6349
+ }
6350
+ async complete(request) {
6351
+ const modelId = request.model || this.defaultModel;
6352
+ const body = this.buildConverseBody(request);
6353
+ const json = await this.doFetch(modelId, body);
6354
+ return this.parseResponse(json, modelId);
6355
+ }
6356
+ async *completeStream(request) {
6357
+ const response = await this.complete(request);
6358
+ yield {
6359
+ id: response.id,
6360
+ model: response.model,
6361
+ delta: {
6362
+ content: response.content ?? void 0,
6363
+ toolCalls: response.toolCalls.length > 0 ? response.toolCalls : void 0
6364
+ },
6365
+ usage: response.usage,
6366
+ finishReason: response.finishReason
6367
+ };
6368
+ }
6369
+ // ─── Internals ───────────────────────────────────────────────────────────
6370
+ buildConverseBody(request) {
6371
+ const { system, messages } = this.splitSystemMessages(request.messages);
6372
+ const body = {
6373
+ messages: messages.map((m) => this.toBedrockMessage(m))
6374
+ };
6375
+ if (system.length > 0) {
6376
+ body.system = system.map((s) => ({ text: s }));
6377
+ }
6378
+ const inferenceConfig = {};
6379
+ if (request.maxTokens != null) inferenceConfig.maxTokens = request.maxTokens;
6380
+ if (request.temperature != null) inferenceConfig.temperature = request.temperature;
6381
+ if (request.topP != null) inferenceConfig.topP = request.topP;
6382
+ if (request.stop) inferenceConfig.stopSequences = request.stop;
6383
+ if (Object.keys(inferenceConfig).length > 0) body.inferenceConfig = inferenceConfig;
6384
+ if (request.tools?.length) {
6385
+ body.toolConfig = {
6386
+ tools: request.tools.map((t) => this.toBedrockTool(t))
6387
+ };
6388
+ if (request.toolChoice != null) {
6389
+ if (request.toolChoice === "auto") {
6390
+ body.toolConfig.toolChoice = { auto: {} };
6391
+ } else if (request.toolChoice === "required") {
6392
+ body.toolConfig.toolChoice = { any: {} };
6393
+ } else if (typeof request.toolChoice === "object") {
6394
+ body.toolConfig.toolChoice = { tool: { name: request.toolChoice.name } };
6395
+ }
6396
+ }
6397
+ }
6398
+ if (request.extra) {
6399
+ Object.assign(body, request.extra);
6400
+ }
6401
+ return body;
6402
+ }
6403
+ splitSystemMessages(messages) {
6404
+ const system = [];
6405
+ const rest = [];
6406
+ for (const m of messages) {
6407
+ if (m.role === "system") {
6408
+ const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
6409
+ if (text) system.push(text);
6410
+ } else {
6411
+ rest.push(m);
6412
+ }
6413
+ }
6414
+ return { system, messages: rest };
6415
+ }
6416
+ toBedrockMessage(m) {
6417
+ if (m.role === "tool" && m.toolCallId) {
6418
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
6419
+ return {
6420
+ role: "user",
6421
+ content: [
6422
+ {
6423
+ toolResult: {
6424
+ toolUseId: m.toolCallId,
6425
+ content: [{ text }],
6426
+ status: "success"
6427
+ }
6428
+ }
6429
+ ]
6430
+ };
6431
+ }
6432
+ if (m.role === "assistant" && m.toolCalls?.length) {
6433
+ const content2 = [];
6434
+ if (typeof m.content === "string" && m.content) {
6435
+ content2.push({ text: m.content });
6436
+ }
6437
+ for (const tc of m.toolCalls) {
6438
+ content2.push({
6439
+ toolUse: {
6440
+ toolUseId: tc.id,
6441
+ name: tc.name,
6442
+ input: safeJsonParse2(tc.arguments)
6443
+ }
6444
+ });
6445
+ }
6446
+ return { role: "assistant", content: content2 };
6447
+ }
6448
+ const content = [];
6449
+ if (typeof m.content === "string") {
6450
+ content.push({ text: m.content });
6451
+ } else if (Array.isArray(m.content)) {
6452
+ for (const part of m.content) {
6453
+ if (part.type === "text") {
6454
+ content.push({ text: part.text });
6455
+ } else if (part.type === "image") {
6456
+ const src = part.source;
6457
+ if (src.type === "base64") {
6458
+ content.push({
6459
+ image: {
6460
+ format: mediaTypeToFormat(src.mediaType),
6461
+ source: { bytes: src.data }
6462
+ }
6463
+ });
6464
+ }
6465
+ }
6466
+ }
6467
+ }
6468
+ return { role: m.role === "user" ? "user" : "assistant", content };
6469
+ }
6470
+ toBedrockTool(t) {
6471
+ return {
6472
+ toolSpec: {
6473
+ name: t.name,
6474
+ description: t.description,
6475
+ inputSchema: { json: t.parameters }
6476
+ }
6477
+ };
6478
+ }
6479
+ async doFetch(modelId, body) {
6480
+ const host = `bedrock-runtime.${this.region}.amazonaws.com`;
6481
+ const url = `https://${host}/model/${encodeURIComponent(modelId)}/converse`;
6482
+ const bodyStr = JSON.stringify(body);
6483
+ const headers = signRequest({
6484
+ method: "POST",
6485
+ url,
6486
+ headers: {
6487
+ "Content-Type": "application/json",
6488
+ Accept: "application/json",
6489
+ ...this.defaultHeaders
6490
+ },
6491
+ body: bodyStr,
6492
+ region: this.region,
6493
+ service: "bedrock",
6494
+ credentials: this.credentials
6495
+ });
6496
+ const res = await fetch(url, {
6497
+ method: "POST",
6498
+ headers,
6499
+ body: bodyStr
6500
+ });
6501
+ if (!res.ok) {
6502
+ const text = await res.text().catch(() => "");
6503
+ throw new ProviderError(
6504
+ `Bedrock API error ${res.status}: ${text}`,
6505
+ this.providerName,
6506
+ res.status,
6507
+ text
6508
+ );
6509
+ }
6510
+ return res.json();
6511
+ }
6512
+ parseResponse(json, modelId) {
6513
+ const output = json.output?.message;
6514
+ const textParts = [];
6515
+ const toolCalls = [];
6516
+ for (const block of output?.content ?? []) {
6517
+ if (block.text) {
6518
+ textParts.push(block.text);
6519
+ } else if (block.toolUse) {
6520
+ toolCalls.push({
6521
+ id: block.toolUse.toolUseId,
6522
+ name: block.toolUse.name,
6523
+ arguments: typeof block.toolUse.input === "string" ? block.toolUse.input : JSON.stringify(block.toolUse.input ?? {})
6524
+ });
6525
+ }
6526
+ }
6527
+ const usage = json.usage ?? {};
6528
+ const tokenUsage = {
6529
+ inputTokens: usage.inputTokens ?? 0,
6530
+ outputTokens: usage.outputTokens ?? 0,
6531
+ totalTokens: usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0),
6532
+ cachedInputTokens: usage.cacheReadInputTokenCount ?? 0,
6533
+ cachedWriteTokens: usage.cacheWriteInputTokenCount ?? 0,
6534
+ cachedOutputTokens: 0,
6535
+ reasoningTokens: 0
6536
+ };
6537
+ return {
6538
+ id: json.requestId ?? json.$metadata?.requestId ?? "",
6539
+ model: modelId,
6540
+ content: textParts.join("") || null,
6541
+ toolCalls,
6542
+ usage: tokenUsage,
6543
+ finishReason: mapBedrockStopReason(json.stopReason),
6544
+ raw: json
6545
+ };
6546
+ }
6547
+ };
6548
+ function mapBedrockStopReason(reason) {
6549
+ switch (reason) {
6550
+ case "end_turn":
6551
+ case "stop_sequence":
6552
+ return "stop";
6553
+ case "tool_use":
6554
+ return "tool_calls";
6555
+ case "max_tokens":
6556
+ return "length";
6557
+ case "content_filtered":
6558
+ case "guardrail":
6559
+ return "content_filter";
6560
+ default:
6561
+ return "stop";
6562
+ }
6563
+ }
6564
+ function mediaTypeToFormat(mediaType) {
6565
+ const map = {
6566
+ "image/jpeg": "jpeg",
6567
+ "image/png": "png",
6568
+ "image/gif": "gif",
6569
+ "image/webp": "webp"
6570
+ };
6571
+ return map[mediaType] ?? "png";
6572
+ }
6573
+ function safeJsonParse2(str) {
6574
+ try {
6575
+ return JSON.parse(str);
6576
+ } catch {
6577
+ return {};
6578
+ }
6579
+ }
6580
+
6581
+ // src/providers/vertex.ts
6582
+ var VertexProvider = class extends BaseProvider {
6583
+ constructor(config) {
6584
+ super();
6585
+ this.providerName = "vertex";
6586
+ this.tokenExpiry = 0;
6587
+ this.projectId = config.projectId;
6588
+ this.location = config.location ?? "us-central1";
6589
+ this.defaultModel = config.defaultModel ?? "gemini-2.0-flash";
6590
+ this.defaultHeaders = config.defaultHeaders ?? {};
6591
+ if (config.accessToken) {
6592
+ this.accessToken = config.accessToken;
6593
+ this.tokenExpiry = Date.now() + 36e5;
6594
+ }
6595
+ if (config.serviceAccountJson) {
6596
+ this.serviceAccountJson = typeof config.serviceAccountJson === "string" ? JSON.parse(config.serviceAccountJson) : config.serviceAccountJson;
6597
+ }
6598
+ }
6599
+ async complete(request) {
6600
+ const modelId = request.model || this.defaultModel;
6601
+ const body = this.buildGeminiBody(request);
6602
+ const url = this.buildUrl(modelId, false);
6603
+ const res = await this.doFetch(url, body);
6604
+ const json = await res.json();
6605
+ return this.parseResponse(json, modelId);
6606
+ }
6607
+ async *completeStream(request) {
6608
+ const modelId = request.model || this.defaultModel;
6609
+ const body = this.buildGeminiBody(request);
6610
+ const url = this.buildUrl(modelId, true);
6611
+ const res = await this.doFetch(url, body);
6612
+ if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
6613
+ const contentType = res.headers.get("content-type") ?? "";
6614
+ if (contentType.includes("text/event-stream")) {
6615
+ for await (const event of parseSSEStream(res.body)) {
6616
+ if (event.data === "[DONE]") break;
6617
+ let chunk;
6618
+ try {
6619
+ chunk = JSON.parse(event.data);
6620
+ } catch {
6621
+ continue;
6622
+ }
6623
+ yield this.parseStreamChunk(chunk, modelId);
6624
+ }
6625
+ } else {
6626
+ const reader = res.body.getReader();
6627
+ const decoder = new TextDecoder();
6628
+ let buffer = "";
6629
+ try {
6630
+ while (true) {
6631
+ const { done, value } = await reader.read();
6632
+ if (done) break;
6633
+ buffer += decoder.decode(value, { stream: true });
6634
+ const lines = buffer.split("\n");
6635
+ buffer = lines.pop() ?? "";
6636
+ for (const line of lines) {
6637
+ const trimmed = line.trim().replace(/^,/, "").replace(/^\[/, "").replace(/\]$/, "");
6638
+ if (!trimmed || trimmed === "[" || trimmed === "]") continue;
6639
+ try {
6640
+ const chunk = JSON.parse(trimmed);
6641
+ yield this.parseStreamChunk(chunk, modelId);
6642
+ } catch {
6643
+ buffer = line + "\n" + buffer;
6644
+ break;
6645
+ }
6646
+ }
6647
+ }
6648
+ const remaining = buffer.trim().replace(/^,/, "").replace(/^\[/, "").replace(/\]$/, "");
6649
+ if (remaining && remaining !== "[" && remaining !== "]") {
6650
+ try {
6651
+ const chunk = JSON.parse(remaining);
6652
+ yield this.parseStreamChunk(chunk, modelId);
6653
+ } catch {
6654
+ }
6655
+ }
6656
+ } finally {
6657
+ reader.releaseLock();
6658
+ }
6659
+ }
6660
+ }
6661
+ // ─── Internals ───────────────────────────────────────────────────────────
6662
+ buildUrl(modelId, stream) {
6663
+ const base = `https://${this.location}-aiplatform.googleapis.com/v1`;
6664
+ const path2 = `projects/${this.projectId}/locations/${this.location}/publishers/google/models/${modelId}`;
6665
+ const action = stream ? "streamGenerateContent?alt=sse" : "generateContent";
6666
+ return `${base}/${path2}:${action}`;
6667
+ }
6668
+ buildGeminiBody(request) {
6669
+ const { system, contents } = this.buildContents(request.messages);
6670
+ const body = { contents };
6671
+ if (system) {
6672
+ body.systemInstruction = { parts: [{ text: system }] };
6673
+ }
6674
+ const generationConfig = {};
6675
+ if (request.maxTokens != null) generationConfig.maxOutputTokens = request.maxTokens;
6676
+ if (request.temperature != null) generationConfig.temperature = request.temperature;
6677
+ if (request.topP != null) generationConfig.topP = request.topP;
6678
+ if (request.stop) generationConfig.stopSequences = request.stop;
6679
+ if (request.responseFormat) {
6680
+ if (request.responseFormat.type === "json_schema" || request.responseFormat.type === "json_object") {
6681
+ generationConfig.responseMimeType = "application/json";
6682
+ if (request.responseFormat.schema) {
6683
+ generationConfig.responseSchema = request.responseFormat.schema;
6684
+ }
6685
+ }
6686
+ }
6687
+ if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
6688
+ if (request.tools?.length) {
6689
+ body.tools = [
6690
+ {
6691
+ functionDeclarations: request.tools.map((t) => this.toGeminiTool(t))
6692
+ }
6693
+ ];
6694
+ if (request.toolChoice != null) {
6695
+ const mode = request.toolChoice === "auto" ? "AUTO" : request.toolChoice === "required" ? "ANY" : request.toolChoice === "none" ? "NONE" : "AUTO";
6696
+ body.toolConfig = { functionCallingConfig: { mode } };
6697
+ }
6698
+ }
6699
+ if (request.extra) {
6700
+ Object.assign(body, request.extra);
6701
+ }
6702
+ return body;
6703
+ }
6704
+ buildContents(messages) {
6705
+ const systemParts = [];
6706
+ const contents = [];
6707
+ for (const m of messages) {
6708
+ if (m.role === "system") {
6709
+ const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
6710
+ if (text) systemParts.push(text);
6711
+ continue;
6712
+ }
6713
+ contents.push(this.toGeminiContent(m));
6714
+ }
6715
+ return {
6716
+ system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
6717
+ contents
6718
+ };
6719
+ }
6720
+ toGeminiContent(m) {
6721
+ if (m.role === "tool" && m.toolCallId) {
6722
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
6723
+ let response;
6724
+ try {
6725
+ response = JSON.parse(text);
6726
+ } catch {
6727
+ response = { result: text };
6728
+ }
6729
+ return {
6730
+ role: "user",
6731
+ parts: [
6732
+ {
6733
+ functionResponse: {
6734
+ name: m.name ?? m.toolCallId,
6735
+ response
6736
+ }
6737
+ }
6738
+ ]
6739
+ };
6740
+ }
6741
+ if (m.role === "assistant" && m.toolCalls?.length) {
6742
+ const parts2 = [];
6743
+ if (typeof m.content === "string" && m.content) {
6744
+ parts2.push({ text: m.content });
6745
+ }
6746
+ for (const tc of m.toolCalls) {
6747
+ parts2.push({
6748
+ functionCall: {
6749
+ name: tc.name,
6750
+ args: safeJsonParse3(tc.arguments)
6751
+ }
6752
+ });
6753
+ }
6754
+ return { role: "model", parts: parts2 };
6755
+ }
6756
+ const role = m.role === "assistant" ? "model" : "user";
6757
+ const parts = [];
6758
+ if (typeof m.content === "string") {
6759
+ parts.push({ text: m.content });
6760
+ } else if (Array.isArray(m.content)) {
6761
+ for (const part of m.content) {
6762
+ if (part.type === "text") {
6763
+ parts.push({ text: part.text });
6764
+ } else if (part.type === "image") {
6765
+ const src = part.source;
6766
+ if (src.type === "base64") {
6767
+ parts.push({
6768
+ inlineData: { mimeType: src.mediaType, data: src.data }
6769
+ });
6770
+ } else {
6771
+ parts.push({
6772
+ fileData: { mimeType: "image/jpeg", fileUri: src.url }
6773
+ });
6774
+ }
6775
+ }
6776
+ }
6777
+ }
6778
+ return { role, parts };
6779
+ }
6780
+ toGeminiTool(t) {
6781
+ return {
6782
+ name: t.name,
6783
+ description: t.description,
6784
+ parameters: t.parameters
6785
+ };
6786
+ }
6787
+ async getAccessToken() {
6788
+ if (this.accessToken && Date.now() < this.tokenExpiry) {
6789
+ return this.accessToken;
6790
+ }
6791
+ if (this.serviceAccountJson) {
6792
+ const token = await this.getTokenFromServiceAccount(this.serviceAccountJson);
6793
+ this.accessToken = token;
6794
+ this.tokenExpiry = Date.now() + 35e5;
6795
+ return token;
6796
+ }
6797
+ try {
6798
+ const { execSync } = await import('child_process');
6799
+ const token = execSync("gcloud auth print-access-token", { encoding: "utf8" }).trim();
6800
+ this.accessToken = token;
6801
+ this.tokenExpiry = Date.now() + 35e5;
6802
+ return token;
6803
+ } catch {
6804
+ throw new ProviderError(
6805
+ "No access token available. Provide accessToken, serviceAccountJson, or ensure gcloud CLI is authenticated.",
6806
+ this.providerName
6807
+ );
6808
+ }
6809
+ }
6810
+ async getTokenFromServiceAccount(sa) {
6811
+ const { createSign } = await import('crypto');
6812
+ const now = Math.floor(Date.now() / 1e3);
6813
+ const header = { alg: "RS256", typ: "JWT" };
6814
+ const claims = {
6815
+ iss: sa.client_email,
6816
+ scope: "https://www.googleapis.com/auth/cloud-platform",
6817
+ aud: "https://oauth2.googleapis.com/token",
6818
+ iat: now,
6819
+ exp: now + 3600
6820
+ };
6821
+ const segments = [
6822
+ base64url(JSON.stringify(header)),
6823
+ base64url(JSON.stringify(claims))
6824
+ ];
6825
+ const signingInput = segments.join(".");
6826
+ const sign = createSign("RSA-SHA256");
6827
+ sign.update(signingInput);
6828
+ const signature = sign.sign(sa.private_key, "base64url");
6829
+ const jwt = `${signingInput}.${signature}`;
6830
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
6831
+ method: "POST",
6832
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6833
+ body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`
6834
+ });
6835
+ if (!tokenRes.ok) {
6836
+ const text = await tokenRes.text().catch(() => "");
6837
+ throw new ProviderError(`Failed to get Vertex access token: ${text}`, this.providerName);
6838
+ }
6839
+ const tokenJson = await tokenRes.json();
6840
+ return tokenJson.access_token;
6841
+ }
6842
+ async doFetch(url, body) {
6843
+ const token = await this.getAccessToken();
6844
+ const res = await fetch(url, {
6845
+ method: "POST",
6846
+ headers: {
6847
+ "Content-Type": "application/json",
6848
+ Authorization: `Bearer ${token}`,
6849
+ ...this.defaultHeaders
6850
+ },
6851
+ body: JSON.stringify(body)
6852
+ });
6853
+ if (!res.ok) {
6854
+ const text = await res.text().catch(() => "");
6855
+ throw new ProviderError(
6856
+ `Vertex AI API error ${res.status}: ${text}`,
6857
+ this.providerName,
6858
+ res.status,
6859
+ text
6860
+ );
6861
+ }
6862
+ return res;
6863
+ }
6864
+ parseResponse(json, modelId) {
6865
+ const candidate = json.candidates?.[0];
6866
+ const parts = candidate?.content?.parts ?? [];
6867
+ const textParts = [];
6868
+ const toolCalls = [];
6869
+ let toolCallIdx = 0;
6870
+ for (const part of parts) {
6871
+ if (part.text) {
6872
+ textParts.push(part.text);
6873
+ } else if (part.functionCall) {
6874
+ toolCalls.push({
6875
+ id: `call_${toolCallIdx++}`,
6876
+ name: part.functionCall.name,
6877
+ arguments: JSON.stringify(part.functionCall.args ?? {})
6878
+ });
6879
+ }
6880
+ }
6881
+ const usageMeta = json.usageMetadata ?? {};
6882
+ const tokenUsage = {
6883
+ inputTokens: usageMeta.promptTokenCount ?? 0,
6884
+ outputTokens: usageMeta.candidatesTokenCount ?? 0,
6885
+ totalTokens: usageMeta.totalTokenCount ?? 0,
6886
+ cachedInputTokens: usageMeta.cachedContentTokenCount ?? 0,
6887
+ cachedWriteTokens: 0,
6888
+ cachedOutputTokens: 0,
6889
+ reasoningTokens: usageMeta.thoughtsTokenCount ?? 0
6890
+ };
6891
+ return {
6892
+ id: json.responseId ?? "",
6893
+ model: modelId,
6894
+ content: textParts.join("") || null,
6895
+ toolCalls,
6896
+ usage: tokenUsage,
6897
+ finishReason: mapGeminiFinishReason(candidate?.finishReason),
6898
+ raw: json
6899
+ };
6900
+ }
6901
+ parseStreamChunk(chunk, modelId) {
6902
+ const candidate = chunk.candidates?.[0];
6903
+ const parts = candidate?.content?.parts ?? [];
6904
+ const delta = {};
6905
+ for (const part of parts) {
6906
+ if (part.text) {
6907
+ delta.content = (delta.content ?? "") + part.text;
6908
+ } else if (part.functionCall) {
6909
+ if (!delta.toolCalls) delta.toolCalls = [];
6910
+ delta.toolCalls.push({
6911
+ id: `call_${delta.toolCalls.length}`,
6912
+ name: part.functionCall.name,
6913
+ arguments: JSON.stringify(part.functionCall.args ?? {})
6914
+ });
6915
+ }
6916
+ }
6917
+ let usage;
6918
+ if (chunk.usageMetadata) {
6919
+ const u = chunk.usageMetadata;
6920
+ usage = {
6921
+ inputTokens: u.promptTokenCount ?? 0,
6922
+ outputTokens: u.candidatesTokenCount ?? 0,
6923
+ totalTokens: u.totalTokenCount ?? 0,
6924
+ cachedInputTokens: u.cachedContentTokenCount ?? 0,
6925
+ cachedWriteTokens: 0,
6926
+ cachedOutputTokens: 0,
6927
+ reasoningTokens: u.thoughtsTokenCount ?? 0
6928
+ };
6929
+ }
6930
+ return {
6931
+ id: chunk.responseId ?? "",
6932
+ model: modelId,
6933
+ delta,
6934
+ usage,
6935
+ finishReason: mapGeminiFinishReason(candidate?.finishReason)
6936
+ };
6937
+ }
6938
+ };
6939
+ function mapGeminiFinishReason(reason) {
6940
+ switch (reason) {
6941
+ case "STOP":
6942
+ return "stop";
6943
+ case "MAX_TOKENS":
6944
+ return "length";
6945
+ case "SAFETY":
6946
+ case "RECITATION":
6947
+ return "content_filter";
6948
+ case "TOOL_CALLS":
6949
+ return "tool_calls";
6950
+ default:
6951
+ return "stop";
6952
+ }
6953
+ }
6954
+ function base64url(str) {
6955
+ return Buffer.from(str).toString("base64url");
6956
+ }
6957
+ function safeJsonParse3(str) {
6958
+ try {
6959
+ return JSON.parse(str);
6960
+ } catch {
6961
+ return {};
6962
+ }
6963
+ }
6964
+ function fromNativeProvider(provider, options) {
6965
+ let boundTools = options?.__tools;
6966
+ const modelName = options?.model ?? provider.defaultModel ?? provider.providerName;
6967
+ const adapted = {
6968
+ modelName,
6969
+ capabilities: resolveCapabilities(provider.providerName),
6970
+ invoke: async (messages, _invokeOptions) => {
6971
+ const request = buildRequest(messages, boundTools, options, _invokeOptions);
6972
+ const response = await provider.complete(request);
6973
+ return toBaseChatMessage(response);
6974
+ },
6975
+ stream: async function* (messages, _invokeOptions) {
6976
+ const request = buildRequest(messages, boundTools, options, _invokeOptions);
6977
+ request.stream = true;
6978
+ let fullContent = "";
6979
+ let allToolCalls = [];
6980
+ let lastUsage;
6981
+ const toolCallBuffers = /* @__PURE__ */ new Map();
6982
+ for await (const chunk of provider.completeStream(request)) {
6983
+ if (chunk.delta.content) {
6984
+ fullContent += chunk.delta.content;
6985
+ yield chunk.delta.content;
6986
+ }
6987
+ if (chunk.delta.toolCalls) {
6988
+ for (const tc of chunk.delta.toolCalls) {
6989
+ if (tc.id) {
6990
+ const existing = toolCallBuffers.get(tc.id);
6991
+ if (existing) {
6992
+ if (tc.arguments) existing.arguments += tc.arguments;
6993
+ } else {
6994
+ toolCallBuffers.set(tc.id, {
6995
+ id: tc.id,
6996
+ name: tc.name ?? "",
6997
+ arguments: tc.arguments ?? ""
6998
+ });
6999
+ }
7000
+ }
7001
+ }
7002
+ }
7003
+ if (chunk.usage) lastUsage = chunk.usage;
7004
+ }
7005
+ allToolCalls = [...toolCallBuffers.values()];
7006
+ const finalMessage = assembleMessage(fullContent, allToolCalls, lastUsage);
7007
+ yield finalMessage;
7008
+ },
7009
+ bindTools: (tools, _bindOptions) => {
7010
+ const strict = _bindOptions?.strict ?? false;
7011
+ const toolDefs = tools.map((t) => toToolDefinition(t, strict));
7012
+ return fromNativeProvider(provider, { ...options, __tools: toolDefs });
7013
+ }
7014
+ };
7015
+ return adapted;
7016
+ function buildRequest(messages, tools, opts, invokeOptions) {
7017
+ const req = {
7018
+ model: opts?.model ?? modelName,
7019
+ messages: messages.map(toUnifiedMessage)
7020
+ };
7021
+ if (opts?.temperature != null) req.temperature = opts.temperature;
7022
+ if (opts?.maxTokens != null) req.maxTokens = opts.maxTokens;
7023
+ if (tools?.length) req.tools = tools;
7024
+ if (opts?.extra) req.extra = opts.extra;
7025
+ const rf = invokeOptions?.response_format;
7026
+ if (rf && typeof rf === "object" && rf.type) {
7027
+ if (rf.type === "json_schema" && rf.json_schema) {
7028
+ req.responseFormat = {
7029
+ type: "json_schema",
7030
+ schema: rf.json_schema.schema,
7031
+ name: rf.json_schema.name
7032
+ };
7033
+ } else if (rf.type === "json_object") {
7034
+ req.responseFormat = { type: "json_object" };
7035
+ } else if (rf.type === "text") {
7036
+ req.responseFormat = { type: "text" };
7037
+ }
7038
+ }
7039
+ if (!req.responseFormat) {
7040
+ const lastMsg = messages[messages.length - 1];
7041
+ if (lastMsg && lastMsg.__responseFormat) {
7042
+ req.responseFormat = lastMsg.__responseFormat;
7043
+ }
7044
+ }
7045
+ return req;
7046
+ }
7047
+ }
7048
+ function toUnifiedMessage(msg) {
7049
+ const unified = {
7050
+ role: msg.role,
7051
+ content: convertContent(msg.content)
7052
+ };
7053
+ if (msg.name) unified.name = msg.name;
7054
+ if (msg.tool_call_id) unified.toolCallId = msg.tool_call_id;
7055
+ if (msg.tool_calls?.length) {
7056
+ unified.toolCalls = msg.tool_calls.map((tc) => ({
7057
+ id: tc.id,
7058
+ name: tc.function?.name ?? tc.name ?? "",
7059
+ arguments: tc.function?.arguments ?? tc.arguments ?? "{}"
7060
+ }));
7061
+ }
7062
+ return unified;
7063
+ }
7064
+ function convertContent(content) {
7065
+ if (typeof content === "string") return content;
7066
+ if (!Array.isArray(content)) return String(content ?? "");
7067
+ return content.map((part) => {
7068
+ if (part.type === "text" || !part.type && part.text) {
7069
+ return { type: "text", text: part.text ?? part.content ?? "" };
7070
+ }
7071
+ if (part.type === "image_url") {
7072
+ const img = part.image_url;
7073
+ if (typeof img === "string" || img?.url?.startsWith("http")) {
7074
+ return {
7075
+ type: "image",
7076
+ source: { type: "url", url: typeof img === "string" ? img : img.url }
7077
+ };
7078
+ }
7079
+ if (img?.url?.startsWith("data:")) {
7080
+ const match = img.url.match(/^data:([^;]+);base64,(.+)$/);
7081
+ if (match) {
7082
+ return {
7083
+ type: "image",
7084
+ source: { type: "base64", mediaType: match[1], data: match[2] }
7085
+ };
7086
+ }
7087
+ }
7088
+ }
7089
+ return { type: "text", text: part.text ?? part.content ?? JSON.stringify(part) };
7090
+ });
7091
+ }
7092
+ function isZodSchema(obj) {
7093
+ return obj != null && typeof obj === "object" && typeof obj.safeParse === "function" && "_def" in obj;
7094
+ }
7095
+ function convertToJsonSchema(schema, strict = false) {
7096
+ if (!schema || typeof schema !== "object") {
7097
+ return { type: "object", properties: {}, required: [], additionalProperties: false };
7098
+ }
7099
+ if (isZodSchema(schema)) {
7100
+ const converted = zodToJsonSchema.zodToJsonSchema(schema, {
7101
+ $refStrategy: "none",
7102
+ ...strict ? { openaiStrictMode: true } : {}
7103
+ });
7104
+ delete converted["$schema"];
7105
+ if (!converted.type) converted.type = "object";
7106
+ return converted;
7107
+ }
7108
+ if (!schema.type && schema.properties) {
7109
+ return { type: "object", ...schema };
7110
+ }
7111
+ return schema;
7112
+ }
7113
+ function normalizeStrictSchema(schema) {
7114
+ if (!schema || typeof schema !== "object") return schema;
7115
+ const clone2 = { ...schema };
7116
+ if (clone2.type === "object" || clone2.properties) {
7117
+ if (!clone2.type) clone2.type = "object";
7118
+ if (clone2.properties && typeof clone2.properties === "object") {
7119
+ const normalized = {};
7120
+ for (const [k, v] of Object.entries(clone2.properties)) {
7121
+ normalized[k] = normalizeStrictSchema(v);
7122
+ }
7123
+ clone2.properties = normalized;
7124
+ clone2.required = Object.keys(normalized);
7125
+ } else {
7126
+ clone2.properties = {};
7127
+ clone2.required = [];
7128
+ }
7129
+ clone2.additionalProperties = false;
7130
+ }
7131
+ if (clone2.items) clone2.items = normalizeStrictSchema(clone2.items);
7132
+ for (const key of ["anyOf", "oneOf", "allOf"]) {
7133
+ if (Array.isArray(clone2[key])) {
7134
+ clone2[key] = clone2[key].map((s) => normalizeStrictSchema(s));
7135
+ }
7136
+ }
7137
+ if (clone2.definitions && typeof clone2.definitions === "object") {
7138
+ const nd = {};
7139
+ for (const [k, v] of Object.entries(clone2.definitions)) {
7140
+ nd[k] = normalizeStrictSchema(v);
7141
+ }
7142
+ clone2.definitions = nd;
7143
+ }
7144
+ if (clone2.$defs && typeof clone2.$defs === "object") {
7145
+ const nd = {};
7146
+ for (const [k, v] of Object.entries(clone2.$defs)) {
7147
+ nd[k] = normalizeStrictSchema(v);
7148
+ }
7149
+ clone2.$defs = nd;
7150
+ }
7151
+ return clone2;
7152
+ }
7153
+ function toToolDefinition(tool, strict) {
7154
+ let name;
7155
+ let description;
7156
+ let parameters;
7157
+ if (tool.type === "function" && tool.function) {
7158
+ name = tool.function.name;
7159
+ description = tool.function.description ?? "";
7160
+ parameters = tool.function.parameters ?? { type: "object", properties: {}, additionalProperties: false };
7161
+ } else {
7162
+ name = tool.name ?? "unknown";
7163
+ description = tool.description ?? "";
7164
+ const rawSchema = tool.schema ?? tool.parameters;
7165
+ parameters = convertToJsonSchema(rawSchema, strict);
7166
+ }
7167
+ if (strict) {
7168
+ parameters = normalizeStrictSchema(parameters);
7169
+ }
7170
+ return { name, description, parameters, ...strict ? { strict: true } : {} };
7171
+ }
7172
+ function toBaseChatMessage(response) {
7173
+ const msg = {
7174
+ role: "assistant",
7175
+ content: response.content ?? "",
7176
+ usage: {
7177
+ prompt_tokens: response.usage.inputTokens,
7178
+ completion_tokens: response.usage.outputTokens,
7179
+ total_tokens: response.usage.totalTokens,
7180
+ prompt_tokens_details: {
7181
+ cached_tokens: response.usage.cachedInputTokens
7182
+ },
7183
+ completion_tokens_details: {
7184
+ reasoning_tokens: response.usage.reasoningTokens
7185
+ }
7186
+ },
7187
+ response_metadata: {
7188
+ token_usage: {
7189
+ prompt_tokens: response.usage.inputTokens,
7190
+ completion_tokens: response.usage.outputTokens,
7191
+ total_tokens: response.usage.totalTokens,
7192
+ cached_tokens: response.usage.cachedInputTokens,
7193
+ cache_write_tokens: response.usage.cachedWriteTokens
7194
+ },
7195
+ finish_reason: response.finishReason,
7196
+ model_name: response.model
7197
+ }
7198
+ };
7199
+ if (response.toolCalls.length > 0) {
7200
+ msg.tool_calls = response.toolCalls.map((tc) => ({
7201
+ id: tc.id,
7202
+ type: "function",
7203
+ function: { name: tc.name, arguments: tc.arguments }
7204
+ }));
7205
+ }
7206
+ return msg;
7207
+ }
7208
+ function assembleMessage(content, toolCalls, usage) {
7209
+ const msg = {
7210
+ role: "assistant",
7211
+ content
7212
+ };
7213
+ if (usage) {
7214
+ msg.usage = {
7215
+ prompt_tokens: usage.inputTokens,
7216
+ completion_tokens: usage.outputTokens,
7217
+ total_tokens: usage.totalTokens,
7218
+ prompt_tokens_details: {
7219
+ cached_tokens: usage.cachedInputTokens
7220
+ },
7221
+ completion_tokens_details: {
7222
+ reasoning_tokens: usage.reasoningTokens
7223
+ }
7224
+ };
7225
+ msg.response_metadata = {
7226
+ token_usage: {
7227
+ prompt_tokens: usage.inputTokens,
7228
+ completion_tokens: usage.outputTokens,
7229
+ total_tokens: usage.totalTokens,
7230
+ cached_tokens: usage.cachedInputTokens,
7231
+ cache_write_tokens: usage.cachedWriteTokens
7232
+ }
7233
+ };
7234
+ }
7235
+ if (toolCalls.length > 0) {
7236
+ msg.tool_calls = toolCalls.map((tc) => ({
7237
+ id: tc.id,
7238
+ type: "function",
7239
+ function: { name: tc.name, arguments: tc.arguments }
7240
+ }));
7241
+ }
7242
+ return msg;
7243
+ }
7244
+ function resolveCapabilities(provider) {
7245
+ switch (provider) {
7246
+ case "openai":
7247
+ case "azure":
7248
+ case "openai-compatible":
7249
+ return {
7250
+ structuredOutput: "native",
7251
+ strictToolCalling: true,
7252
+ streaming: true,
7253
+ provider
7254
+ };
7255
+ case "anthropic":
7256
+ return {
7257
+ structuredOutput: "tool_based",
7258
+ strictToolCalling: false,
7259
+ streaming: true,
7260
+ provider: "anthropic"
7261
+ };
7262
+ case "bedrock":
7263
+ return {
7264
+ structuredOutput: "tool_based",
7265
+ strictToolCalling: false,
7266
+ streaming: false,
7267
+ // streaming uses binary protocol
7268
+ provider: "bedrock"
7269
+ };
7270
+ case "vertex":
7271
+ return {
7272
+ structuredOutput: "native",
7273
+ strictToolCalling: false,
7274
+ streaming: true,
7275
+ provider: "google"
7276
+ };
7277
+ default:
7278
+ return { provider };
7279
+ }
7280
+ }
7281
+
7282
+ // src/providers/index.ts
7283
+ function createProvider(config) {
7284
+ switch (config.provider) {
7285
+ case "openai":
7286
+ return new OpenAIProvider(config);
7287
+ case "anthropic":
7288
+ return new AnthropicProvider(config);
7289
+ case "azure":
7290
+ return new AzureProvider(config);
7291
+ case "openai-compatible":
7292
+ return new OpenAICompatibleProvider(config);
7293
+ case "bedrock":
7294
+ return new BedrockProvider(config);
7295
+ case "vertex":
7296
+ return new VertexProvider(config);
7297
+ default:
7298
+ throw new Error(`Unknown provider: ${config.provider}`);
7299
+ }
7300
+ }
7301
+
7302
+ exports.AnthropicProvider = AnthropicProvider;
7303
+ exports.AzureProvider = AzureProvider;
7304
+ exports.BaseProvider = BaseProvider;
7305
+ exports.BedrockProvider = BedrockProvider;
5164
7306
  exports.DEFAULT_PROFILE_CONFIGS = DEFAULT_PROFILE_CONFIGS;
5165
7307
  exports.GuardrailPhase = GuardrailPhase;
5166
7308
  exports.InMemoryMemoryStore = InMemoryMemoryStore;
7309
+ exports.NativeJsonSchemaStrategy = NativeJsonSchemaStrategy;
7310
+ exports.OpenAICompatibleProvider = OpenAICompatibleProvider;
7311
+ exports.OpenAIProvider = OpenAIProvider;
7312
+ exports.ProviderError = ProviderError;
7313
+ exports.StructuredOutputManager = StructuredOutputManager;
7314
+ exports.ToolBasedStrategy = ToolBasedStrategy;
7315
+ exports.VertexProvider = VertexProvider;
5167
7316
  exports.agentVerdictRule = agentVerdictRule;
5168
- exports.applyTokenLimits = applyTokenLimits;
5169
7317
  exports.buildSystemPrompt = buildSystemPrompt;
5170
7318
  exports.captureSnapshot = captureSnapshot;
5171
7319
  exports.codePresenceRule = codePresenceRule;
@@ -5179,6 +7327,7 @@ exports.createContextSummarizeNode = createContextSummarizeNode;
5179
7327
  exports.createContextTools = createContextTools;
5180
7328
  exports.createGuardrail = createGuardrail;
5181
7329
  exports.createJsonGuardrail = createJsonGuardrail;
7330
+ exports.createProvider = createProvider;
5182
7331
  exports.createRegexGuardrail = createRegexGuardrail;
5183
7332
  exports.createResolverNode = createResolverNode;
5184
7333
  exports.createSmartAgent = createSmartAgent;
@@ -5187,12 +7336,17 @@ exports.createToolLimitFinalizeNode = createToolLimitFinalizeNode;
5187
7336
  exports.createToolsNode = createToolsNode;
5188
7337
  exports.customCallbackRule = customCallbackRule;
5189
7338
  exports.customSink = customSink;
7339
+ exports.emptyUsage = emptyUsage;
5190
7340
  exports.evaluateGuardrails = evaluateGuardrails;
5191
7341
  exports.fileSink = fileSink;
7342
+ exports.formatZodError = formatZodError;
5192
7343
  exports.fromLangchainModel = fromLangchainModel;
5193
7344
  exports.fromLangchainTools = fromLangchainTools;
7345
+ exports.fromNativeProvider = fromNativeProvider;
5194
7346
  exports.generateSpanId = generateSpanId;
5195
7347
  exports.generateTraceId = generateTraceId;
7348
+ exports.getModelCapabilities = getModelCapabilities;
7349
+ exports.getNestedValue = getNestedValue;
5196
7350
  exports.getResolvedSmartConfig = getResolvedSmartConfig;
5197
7351
  exports.httpSink = httpSink;
5198
7352
  exports.isSmartModel = isSmartModel;
@@ -5201,8 +7355,8 @@ exports.normalizeSmartAgentOptions = normalizeSmartAgentOptions;
5201
7355
  exports.otlpSink = otlpSink;
5202
7356
  exports.readMemoryFacts = readMemoryFacts;
5203
7357
  exports.regexRule = regexRule;
5204
- exports.renderMemoryFacts = renderMemoryFacts;
5205
7358
  exports.resolveMemoryStore = resolveMemoryStore;
7359
+ exports.resolveStrategy = resolveStrategy;
5206
7360
  exports.resolveToolApprovalState = resolveToolApprovalState;
5207
7361
  exports.restoreSnapshot = restoreSnapshot;
5208
7362
  exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;