@cognipeer/agent-sdk 0.4.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  import { z, ZodError } from 'zod';
2
2
  import { createRequire } from 'module';
3
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
4
  import fs from 'fs';
4
5
  import path from 'path';
5
- import { Buffer } from 'buffer';
6
+ import { Buffer as Buffer$1 } from 'buffer';
6
7
  import { nanoid } from 'nanoid';
7
- import crypto from 'crypto';
8
+ import crypto, { createHash, createHmac } from 'crypto';
8
9
  import AjvModule from 'ajv';
9
10
 
10
11
  // src/adapters/langchain.ts
@@ -124,12 +125,377 @@ function createSimpleTool({
124
125
  };
125
126
  }
126
127
 
128
+ // src/structuredOutput/types.ts
129
+ function getNestedValue(obj, path2) {
130
+ let current = obj;
131
+ for (const key of path2) {
132
+ if (current == null || typeof current !== "object") return void 0;
133
+ current = current[key];
134
+ }
135
+ return current;
136
+ }
137
+ function formatZodError(error, rawContent) {
138
+ const fieldErrors = error.issues.map((issue) => ({
139
+ path: issue.path.join(".") || "root",
140
+ expected: issue.expected ?? issue.code,
141
+ received: issue.received ?? "unknown",
142
+ message: issue.message
143
+ }));
144
+ return {
145
+ type: "validation_error",
146
+ message: `Schema validation failed: ${fieldErrors.map((f) => `${f.path}: ${f.message}`).join("; ")}`,
147
+ fieldErrors,
148
+ rawContent
149
+ };
150
+ }
151
+
152
+ // src/structuredOutput/nativeStrategy.ts
153
+ var NativeJsonSchemaStrategy = class {
154
+ constructor() {
155
+ this.kind = "native";
156
+ }
157
+ resolveJsonPointer(document, pointer) {
158
+ if (!pointer.startsWith("#/")) {
159
+ return void 0;
160
+ }
161
+ const segments = pointer.slice(2).split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
162
+ let current = document;
163
+ for (const segment of segments) {
164
+ if (!current || typeof current !== "object" || !(segment in current)) {
165
+ return void 0;
166
+ }
167
+ current = current[segment];
168
+ }
169
+ return current;
170
+ }
171
+ unwrapRootObjectSchema(schema) {
172
+ if (schema.type === "object") {
173
+ return schema;
174
+ }
175
+ const ref = typeof schema.$ref === "string" ? schema.$ref : null;
176
+ if (!ref) {
177
+ return schema;
178
+ }
179
+ const resolved = this.resolveJsonPointer(schema, ref);
180
+ if (!resolved || typeof resolved !== "object" || Array.isArray(resolved)) {
181
+ return schema;
182
+ }
183
+ const resolvedSchema = { ...resolved };
184
+ if (resolvedSchema.type !== "object") {
185
+ return schema;
186
+ }
187
+ if (schema.definitions && resolvedSchema.definitions === void 0) {
188
+ resolvedSchema.definitions = schema.definitions;
189
+ }
190
+ if (schema.$defs && resolvedSchema.$defs === void 0) {
191
+ resolvedSchema.$defs = schema.$defs;
192
+ }
193
+ return resolvedSchema;
194
+ }
195
+ normalizeOpenAIStrictSchema(schema) {
196
+ const visit = (node) => {
197
+ if (!node || typeof node !== "object") return node;
198
+ if (Array.isArray(node)) {
199
+ return node.map(visit);
200
+ }
201
+ const clone2 = { ...node };
202
+ if (clone2.properties && typeof clone2.properties === "object") {
203
+ const normalizedProperties = {};
204
+ for (const [key, value] of Object.entries(clone2.properties)) {
205
+ normalizedProperties[key] = visit(value);
206
+ }
207
+ clone2.properties = normalizedProperties;
208
+ clone2.required = Object.keys(normalizedProperties);
209
+ if (clone2.additionalProperties === void 0) {
210
+ clone2.additionalProperties = false;
211
+ }
212
+ }
213
+ if (clone2.items) {
214
+ clone2.items = visit(clone2.items);
215
+ }
216
+ if (clone2.definitions && typeof clone2.definitions === "object") {
217
+ const normalizedDefinitions = {};
218
+ for (const [key, value] of Object.entries(clone2.definitions)) {
219
+ normalizedDefinitions[key] = visit(value);
220
+ }
221
+ clone2.definitions = normalizedDefinitions;
222
+ }
223
+ if (clone2.$defs && typeof clone2.$defs === "object") {
224
+ const normalizedDefs = {};
225
+ for (const [key, value] of Object.entries(clone2.$defs)) {
226
+ normalizedDefs[key] = visit(value);
227
+ }
228
+ clone2.$defs = normalizedDefs;
229
+ }
230
+ for (const compositeKey of ["anyOf", "oneOf", "allOf"]) {
231
+ if (Array.isArray(clone2[compositeKey])) {
232
+ clone2[compositeKey] = clone2[compositeKey].map(visit);
233
+ }
234
+ }
235
+ return clone2;
236
+ };
237
+ return visit(schema);
238
+ }
239
+ /**
240
+ * Convert a Zod schema to the JSON Schema object used in response_format.
241
+ */
242
+ toJsonSchema(schema, name = "structured_response") {
243
+ const result = zodToJsonSchema(schema, {
244
+ name,
245
+ openaiStrictMode: true,
246
+ nameStrategy: "duplicate-ref",
247
+ $refStrategy: "extract-to-root",
248
+ nullableStrategy: "property"
249
+ });
250
+ const normalized = this.normalizeOpenAIStrictSchema(result);
251
+ return this.unwrapRootObjectSchema(normalized);
252
+ }
253
+ /**
254
+ * Build response_format configuration for the model call.
255
+ * Returns the object that should be spread into model invocation options.
256
+ */
257
+ buildResponseFormat(schema, name) {
258
+ const schemaName = name || "structured_response";
259
+ const jsonSchema = this.toJsonSchema(schema, schemaName);
260
+ delete jsonSchema["$schema"];
261
+ return {
262
+ response_format: {
263
+ type: "json_schema",
264
+ json_schema: {
265
+ name: schemaName,
266
+ strict: true,
267
+ schema: jsonSchema
268
+ }
269
+ }
270
+ };
271
+ }
272
+ /**
273
+ * Parse and validate model response content.
274
+ * With native support, content is guaranteed JSON — but we still validate with Zod
275
+ * for type safety and field-level error reporting.
276
+ */
277
+ extractOutput(content, schema) {
278
+ try {
279
+ const raw = JSON.parse(content);
280
+ const validated = schema.parse(raw);
281
+ return { data: validated };
282
+ } catch (e) {
283
+ if (e?.issues) {
284
+ return { error: formatZodError(e, content) };
285
+ }
286
+ return {
287
+ error: {
288
+ type: "parse_error",
289
+ message: `Failed to parse native structured output: ${e?.message}`,
290
+ rawContent: content
291
+ }
292
+ };
293
+ }
294
+ }
295
+ buildCorrectionPrompt(error, previousAttempt) {
296
+ const fieldIssues = error.issues.map((issue) => {
297
+ const pathStr = issue.path.join(".") || "root";
298
+ const received = getNestedValue(previousAttempt, issue.path);
299
+ return `- "${pathStr}": ${issue.message} (received: ${JSON.stringify(received)})`;
300
+ }).join("\n");
301
+ return [
302
+ "Your previous JSON response had validation errors:",
303
+ fieldIssues,
304
+ "",
305
+ "Please output corrected JSON matching the schema."
306
+ ].join("\n");
307
+ }
308
+ };
309
+
310
+ // src/tool.ts
311
+ function createTool({
312
+ name,
313
+ description,
314
+ schema,
315
+ func,
316
+ needsApproval,
317
+ approvalPrompt,
318
+ approvalDefaults,
319
+ maxExecutionsPerRun
320
+ }) {
321
+ const execute = async (input) => func(input);
322
+ const toolRecord = {
323
+ name,
324
+ description,
325
+ schema,
326
+ // Our runtime prefers invoke but fallback helpers keep compatibility with LC-style tools.
327
+ invoke: execute,
328
+ call: execute,
329
+ run: execute,
330
+ func: execute
331
+ };
332
+ if (typeof needsApproval === "boolean") {
333
+ toolRecord.needsApproval = needsApproval;
334
+ }
335
+ if (approvalPrompt !== void 0) {
336
+ toolRecord.approvalPrompt = approvalPrompt;
337
+ }
338
+ if (approvalDefaults !== void 0) {
339
+ toolRecord.approvalDefaults = approvalDefaults;
340
+ }
341
+ if (maxExecutionsPerRun !== void 0) {
342
+ toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
343
+ }
344
+ toolRecord.__source = toolRecord.__source || "smart";
345
+ toolRecord.__impl = func;
346
+ return toolRecord;
347
+ }
348
+
349
+ // src/structuredOutput/toolStrategy.ts
350
+ var ToolBasedStrategy = class {
351
+ constructor() {
352
+ this.kind = "tool_based";
353
+ }
354
+ /**
355
+ * Create the `response` tool for a given Zod schema.
356
+ * The tool validates input against the schema and returns a sentinel object
357
+ * that the tools node picks up to finalize the structured output.
358
+ */
359
+ createResponseTool(schema) {
360
+ return createTool({
361
+ name: "response",
362
+ description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
363
+ schema,
364
+ func: async (data) => {
365
+ try {
366
+ const validated = schema.parse ? schema.parse(data) : data;
367
+ return { __finalStructuredOutput: true, data: validated };
368
+ } catch (e) {
369
+ const zodError = e;
370
+ if (zodError?.issues) {
371
+ const soError = formatZodError(zodError);
372
+ return {
373
+ error: "Schema validation failed",
374
+ details: soError.message,
375
+ fieldErrors: soError.fieldErrors
376
+ };
377
+ }
378
+ return { error: "Schema validation failed", details: e?.message };
379
+ }
380
+ }
381
+ });
382
+ }
383
+ /**
384
+ * Try to extract structured output from raw assistant text content.
385
+ * Used as a last resort when the model writes JSON directly instead of calling the tool.
386
+ */
387
+ extractFromText(content, schema) {
388
+ let jsonText = null;
389
+ const fenced = content.match(/```(?:json)?\n([\s\S]*?)```/i);
390
+ if (fenced && fenced[1]) {
391
+ jsonText = fenced[1].trim();
392
+ } else {
393
+ const braceIdx = content.indexOf("{");
394
+ const bracketIdx = content.indexOf("[");
395
+ const candidates = [braceIdx, bracketIdx].filter((i) => i >= 0).sort((a, b) => a - b);
396
+ if (candidates.length > 0) {
397
+ jsonText = content.slice(candidates[0]).trim();
398
+ }
399
+ }
400
+ try {
401
+ const raw = JSON.parse(jsonText ?? content);
402
+ const validated = schema.parse(raw);
403
+ return { data: validated };
404
+ } catch (e) {
405
+ if (e?.issues) {
406
+ return { error: formatZodError(e, content) };
407
+ }
408
+ return {
409
+ error: {
410
+ type: "parse_error",
411
+ message: `Failed to parse JSON from assistant message: ${e?.message}`,
412
+ rawContent: content
413
+ }
414
+ };
415
+ }
416
+ }
417
+ buildCorrectionPrompt(error, previousAttempt) {
418
+ const fieldIssues = error.issues.map((issue) => {
419
+ const pathStr = issue.path.join(".") || "root";
420
+ const received = getNestedValue(previousAttempt, issue.path);
421
+ return `- "${pathStr}": ${issue.message} (received: ${JSON.stringify(received)})`;
422
+ }).join("\n");
423
+ return [
424
+ "Your previous structured output had validation errors:",
425
+ fieldIssues,
426
+ "",
427
+ "Please call `response` again with corrected values matching the schema."
428
+ ].join("\n");
429
+ }
430
+ };
431
+
432
+ // src/structuredOutput/resolver.ts
433
+ function resolveStrategy(model) {
434
+ const capabilities = getModelCapabilities(model);
435
+ if (capabilities.structuredOutput === "native") {
436
+ return new NativeJsonSchemaStrategy();
437
+ }
438
+ return new ToolBasedStrategy();
439
+ }
440
+ function getModelCapabilities(model) {
441
+ if (model?.capabilities && typeof model.capabilities === "object") {
442
+ return model.capabilities;
443
+ }
444
+ const provider = detectProvider(model);
445
+ if (provider) {
446
+ return {
447
+ provider,
448
+ structuredOutput: supportsNativeStructuredOutput(provider) ? "native" : "tool_based",
449
+ strictToolCalling: requiresStrictToolCalling(provider),
450
+ streaming: true
451
+ };
452
+ }
453
+ return { structuredOutput: "tool_based" };
454
+ }
455
+ function detectProvider(model) {
456
+ if (!model || typeof model !== "object") return void 0;
457
+ if (typeof model.provider === "string") return model.provider;
458
+ const lcModel = model._lc;
459
+ if (lcModel) {
460
+ return detectProviderFromLCModel(lcModel);
461
+ }
462
+ const modelName = model.modelName || model.model || "";
463
+ if (modelName.startsWith("gpt-") || modelName.startsWith("o1") || modelName.startsWith("o3") || modelName.startsWith("o4")) return "openai";
464
+ if (modelName.startsWith("claude")) return "anthropic";
465
+ if (modelName.startsWith("gemini")) return "google";
466
+ if (modelName.startsWith("amazon.") || modelName.startsWith("anthropic.")) return "bedrock";
467
+ return void 0;
468
+ }
469
+ function detectProviderFromLCModel(lcModel) {
470
+ if (!lcModel) return void 0;
471
+ const className = lcModel.constructor?.name || "";
472
+ if (className.includes("ChatOpenAI") || className.includes("AzureChatOpenAI")) return "openai";
473
+ if (className.includes("ChatAnthropic")) return "anthropic";
474
+ if (className.includes("ChatGoogleGenerativeAI") || className.includes("ChatVertexAI")) return "google";
475
+ if (className.includes("ChatBedrock") || className.includes("BedrockChat")) return "bedrock";
476
+ if (className.includes("ChatGroq")) return "groq";
477
+ if (className.includes("ChatMistralAI")) return "mistral";
478
+ const llmType = typeof lcModel._llmType === "function" ? lcModel._llmType() : lcModel._llmType;
479
+ if (typeof llmType === "string") {
480
+ if (llmType.includes("openai")) return "openai";
481
+ if (llmType.includes("anthropic")) return "anthropic";
482
+ if (llmType.includes("google")) return "google";
483
+ }
484
+ return void 0;
485
+ }
486
+ function supportsNativeStructuredOutput(provider) {
487
+ return ["openai", "google", "groq", "mistral"].includes(provider);
488
+ }
489
+ function requiresStrictToolCalling(provider) {
490
+ return ["openai"].includes(provider);
491
+ }
492
+
127
493
  // src/model.ts
128
494
  function isSmartModel(m) {
129
495
  return !!m && typeof m === "object" && typeof m.invoke === "function";
130
496
  }
131
- function withTools(model, tools) {
132
- if (model?.bindTools) return model.bindTools(tools);
497
+ function withTools(model, tools, options) {
498
+ if (model?.bindTools) return model.bindTools(tools, options);
133
499
  return model;
134
500
  }
135
501
  function fromLangchainModel(lcModel) {
@@ -174,7 +540,8 @@ function fromLangchainModel(lcModel) {
174
540
  role: response.role || "assistant",
175
541
  content,
176
542
  tool_calls: response.tool_calls,
177
- usage: response.usage,
543
+ usage: response.usage ?? response.usage_metadata ?? response.response_metadata?.token_usage ?? response.response_metadata?.tokenUsage,
544
+ usage_metadata: response.usage_metadata,
178
545
  response_metadata: response.response_metadata,
179
546
  ...response
180
547
  };
@@ -190,14 +557,14 @@ function fromLangchainModel(lcModel) {
190
557
  yield chunk;
191
558
  }
192
559
  },
193
- bindTools: (tools) => {
560
+ bindTools: (tools, options) => {
194
561
  const lcReady = toLangchainTools(tools);
195
562
  if (typeof lcModel.bindTools === "function") {
196
- const bound = lcModel.bindTools(lcReady);
563
+ const bound = lcModel.bindTools(lcReady, options);
197
564
  return fromLangchainModel(bound);
198
565
  }
199
566
  if (typeof lcModel.bind === "function") {
200
- const bound = lcModel.bind({ tools: lcReady });
567
+ const bound = lcModel.bind({ tools: lcReady, ...options || {} });
201
568
  return fromLangchainModel(bound);
202
569
  }
203
570
  return adapted;
@@ -205,48 +572,10 @@ function fromLangchainModel(lcModel) {
205
572
  modelName: lcModel.modelName || lcModel._modelId || lcModel._llmType || lcModel.name,
206
573
  _lc: lcModel
207
574
  };
575
+ adapted.capabilities = getModelCapabilities(adapted);
208
576
  return adapted;
209
577
  }
210
578
 
211
- // src/tool.ts
212
- function createTool({
213
- name,
214
- description,
215
- schema,
216
- func,
217
- needsApproval,
218
- approvalPrompt,
219
- approvalDefaults,
220
- maxExecutionsPerRun
221
- }) {
222
- const execute = async (input) => func(input);
223
- const toolRecord = {
224
- name,
225
- description,
226
- schema,
227
- // Our runtime prefers invoke but fallback helpers keep compatibility with LC-style tools.
228
- invoke: execute,
229
- call: execute,
230
- run: execute,
231
- func: execute
232
- };
233
- if (typeof needsApproval === "boolean") {
234
- toolRecord.needsApproval = needsApproval;
235
- }
236
- if (approvalPrompt !== void 0) {
237
- toolRecord.approvalPrompt = approvalPrompt;
238
- }
239
- if (approvalDefaults !== void 0) {
240
- toolRecord.approvalDefaults = approvalDefaults;
241
- }
242
- if (maxExecutionsPerRun !== void 0) {
243
- toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
244
- }
245
- toolRecord.__source = toolRecord.__source || "smart";
246
- toolRecord.__impl = func;
247
- return toolRecord;
248
- }
249
-
250
579
  // src/prompts.ts
251
580
  var DEFAULT_TODO_LIST_PROMPT = `Planning tools are available when they materially improve execution.
252
581
 
@@ -329,23 +658,39 @@ function normalizeUsage(raw) {
329
658
  raw.totalTokens,
330
659
  (typeof prompt === "number" ? prompt : 0) + (typeof completion === "number" ? completion : 0)
331
660
  );
332
- const promptDetailsSrc = raw.prompt_tokens_details || raw.promptTokensDetails || raw.input_token_details || raw.inputTokenDetails || {};
333
- const completionDetailsSrc = raw.completion_tokens_details || raw.completionTokensDetails || raw.output_token_details || raw.outputTokenDetails || {};
661
+ const promptDetailsSrc = raw.prompt_tokens_details || raw.promptTokensDetails || raw.input_token_details || raw.inputTokenDetails || raw.input_tokens_details || raw.inputTokensDetails || {};
662
+ const completionDetailsSrc = raw.completion_tokens_details || raw.completionTokensDetails || raw.output_token_details || raw.outputTokenDetails || raw.output_tokens_details || raw.outputTokensDetails || {};
663
+ const cachedTokens = num(
664
+ promptDetailsSrc.cached_tokens,
665
+ promptDetailsSrc.cachedTokens,
666
+ promptDetailsSrc.cached,
667
+ promptDetailsSrc.cache_read,
668
+ promptDetailsSrc.cacheRead,
669
+ promptDetailsSrc.cache_read_tokens,
670
+ raw.cached_input_tokens,
671
+ raw.cachedInputTokens,
672
+ raw.cached_prompt_tokens,
673
+ raw.cachedPromptTokens,
674
+ raw.cache_read_input_tokens,
675
+ raw.cacheReadInputTokens
676
+ );
334
677
  const normalized = {
335
678
  prompt_tokens: safe(prompt),
336
679
  completion_tokens: safe(completion),
337
680
  total_tokens: safe(total || (prompt || 0) + (completion || 0)),
338
681
  prompt_tokens_details: {
339
- cached_tokens: safe(
340
- promptDetailsSrc.cached_tokens || promptDetailsSrc.cached || promptDetailsSrc.cache_read || raw.cached_input_tokens || raw.cached_prompt_tokens || raw.cache_read_input_tokens
341
- ),
342
- audio_tokens: safe(promptDetailsSrc.audio_tokens || promptDetailsSrc.audio)
682
+ cached_tokens: safe(cachedTokens),
683
+ audio_tokens: safe(num(promptDetailsSrc.audio_tokens, promptDetailsSrc.audioTokens, promptDetailsSrc.audio))
343
684
  },
344
685
  completion_tokens_details: {
345
- reasoning_tokens: safe(completionDetailsSrc.reasoning_tokens),
346
- audio_tokens: safe(completionDetailsSrc.audio_tokens),
347
- accepted_prediction_tokens: safe(completionDetailsSrc.accepted_prediction_tokens),
348
- rejected_prediction_tokens: safe(completionDetailsSrc.rejected_prediction_tokens)
686
+ reasoning_tokens: safe(num(completionDetailsSrc.reasoning_tokens, completionDetailsSrc.reasoningTokens)),
687
+ audio_tokens: safe(num(completionDetailsSrc.audio_tokens, completionDetailsSrc.audioTokens)),
688
+ accepted_prediction_tokens: safe(
689
+ num(completionDetailsSrc.accepted_prediction_tokens, completionDetailsSrc.acceptedPredictionTokens)
690
+ ),
691
+ rejected_prediction_tokens: safe(
692
+ num(completionDetailsSrc.rejected_prediction_tokens, completionDetailsSrc.rejectedPredictionTokens)
693
+ )
349
694
  },
350
695
  raw
351
696
  };
@@ -455,21 +800,21 @@ var METADATA_PATHS = {
455
800
  "lc_kwargs.provider"
456
801
  ]
457
802
  };
458
- function getNestedValue(obj, path2) {
803
+ function getNestedValue2(obj, path2) {
459
804
  if (!obj || typeof path2 !== "string") return void 0;
460
805
  return path2.split(".").reduce((current, key) => current?.[key], obj);
461
806
  }
462
807
  function extractMetadata(model, paths) {
463
808
  if (!model) return void 0;
464
809
  for (const path2 of paths) {
465
- const value = getNestedValue(model, path2);
810
+ const value = getNestedValue2(model, path2);
466
811
  const str = coerceToString(value);
467
812
  if (str) return str;
468
813
  }
469
814
  const maybeLc = model?._lc;
470
815
  if (maybeLc) {
471
816
  for (const path2 of paths) {
472
- const value = getNestedValue(maybeLc, path2);
817
+ const value = getNestedValue2(maybeLc, path2);
473
818
  const str = coerceToString(value);
474
819
  if (str) return str;
475
820
  }
@@ -477,7 +822,7 @@ function extractMetadata(model, paths) {
477
822
  const maybeClient = model?.client || model?.api || model?.service;
478
823
  if (maybeClient) {
479
824
  for (const path2 of paths) {
480
- const value = getNestedValue(maybeClient, path2);
825
+ const value = getNestedValue2(maybeClient, path2);
481
826
  const str = coerceToString(value);
482
827
  if (str) return str;
483
828
  }
@@ -509,7 +854,7 @@ function inferModelFromMessages(messageList) {
509
854
  const message = messageList[i];
510
855
  if (!message) continue;
511
856
  for (const path2 of messagePaths) {
512
- const value = getNestedValue(message, path2);
857
+ const value = getNestedValue2(message, path2);
513
858
  const str = coerceToString(value);
514
859
  if (str) return str;
515
860
  }
@@ -528,7 +873,7 @@ function inferProviderFromMessages(messageList) {
528
873
  const message = messageList[i];
529
874
  if (!message) continue;
530
875
  for (const path2 of messagePaths) {
531
- const value = getNestedValue(message, path2);
876
+ const value = getNestedValue2(message, path2);
532
877
  const str = coerceToString(value);
533
878
  if (str) return str;
534
879
  }
@@ -1514,10 +1859,10 @@ function sanitizeTracePayload(value) {
1514
1859
  function estimatePayloadBytes(value) {
1515
1860
  try {
1516
1861
  const json = JSON.stringify(value);
1517
- return Buffer.byteLength(json ?? "", "utf8");
1862
+ return Buffer$1.byteLength(json ?? "", "utf8");
1518
1863
  } catch {
1519
1864
  try {
1520
- return Buffer.byteLength(String(value ?? ""), "utf8");
1865
+ return Buffer$1.byteLength(String(value ?? ""), "utf8");
1521
1866
  } catch {
1522
1867
  return 0;
1523
1868
  }
@@ -1539,7 +1884,13 @@ function createAgentCoreNode(opts) {
1539
1884
  tracing: opts.tracing
1540
1885
  };
1541
1886
  const tools = runtime.tools ?? [];
1542
- const modelWithTools = runtime.model?.bindTools ? runtime.model.bindTools(tools) : runtime.model;
1887
+ const shouldUseStrictToolCalling = Boolean(
1888
+ runtime.responseFormat && runtime.model?.capabilities?.strictToolCalling
1889
+ );
1890
+ const modelWithTools = runtime.model?.bindTools ? runtime.model.bindTools(
1891
+ tools,
1892
+ shouldUseStrictToolCalling ? { strict: true } : void 0
1893
+ ) : runtime.model;
1543
1894
  const traceSession = state.ctx?.__traceSession;
1544
1895
  const actorName = runtime.name ?? opts.name ?? "agent";
1545
1896
  const actorVersion = runtime.version ?? opts.version;
@@ -1616,6 +1967,7 @@ function createAgentCoreNode(opts) {
1616
1967
  const streamingEnabled = Boolean(state.ctx?.__streaming);
1617
1968
  const cancellationToken = state.ctx?.__cancellationToken;
1618
1969
  const abortSignal = state.ctx?.__abortSignal;
1970
+ const responseFormat = runtime.responseFormat;
1619
1971
  const extractText = (chunk) => {
1620
1972
  if (chunk == null) return "";
1621
1973
  if (typeof chunk === "string") return chunk;
@@ -1634,7 +1986,8 @@ function createAgentCoreNode(opts) {
1634
1986
  if (streamingEnabled && typeof modelWithTools.stream === "function") {
1635
1987
  let streamedText = "";
1636
1988
  let streamedMessage;
1637
- for await (const chunk of modelWithTools.stream(normalizedMessages, { signal: abortSignal, cancellationToken })) {
1989
+ const streamOptions = { signal: abortSignal, cancellationToken, ...responseFormat };
1990
+ for await (const chunk of modelWithTools.stream(normalizedMessages, streamOptions)) {
1638
1991
  if (cancellationToken && cancellationToken.isCancellationRequested || abortSignal?.aborted) {
1639
1992
  break;
1640
1993
  }
@@ -1657,7 +2010,7 @@ function createAgentCoreNode(opts) {
1657
2010
  response = { role: "assistant", content: streamedText };
1658
2011
  }
1659
2012
  } else {
1660
- response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken });
2013
+ response = await modelWithTools.invoke(normalizedMessages, { signal: abortSignal, cancellationToken, ...responseFormat });
1661
2014
  }
1662
2015
  } catch (err) {
1663
2016
  const durationMs2 = Date.now() - start;
@@ -1679,7 +2032,7 @@ function createAgentCoreNode(opts) {
1679
2032
  ...state.messages,
1680
2033
  response
1681
2034
  ];
1682
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2035
+ 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;
1683
2036
  const normalized = normalizeUsage(rawUsage);
1684
2037
  const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
1685
2038
  const durationMs = Date.now() - start;
@@ -2204,6 +2557,16 @@ function countSuccessfulToolExecutions(toolHistory, toolName) {
2204
2557
  if (!Array.isArray(toolHistory)) return 0;
2205
2558
  return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
2206
2559
  }
2560
+ function normalizeToolCall(call) {
2561
+ if (!call || typeof call !== "object") return null;
2562
+ const name = call.name ?? call.tool ?? call.function?.name ?? call.function_call?.name;
2563
+ if (typeof name !== "string" || name.length === 0) return null;
2564
+ return {
2565
+ id: call.id,
2566
+ name,
2567
+ args: call.args ?? call.arguments ?? call.input ?? call.function?.arguments ?? call.function_call?.arguments
2568
+ };
2569
+ }
2207
2570
  function createToolsNode(initialTools, opts) {
2208
2571
  return async (state) => {
2209
2572
  const runtime = state.agent || {
@@ -2238,12 +2601,14 @@ function createToolsNode(initialTools, opts) {
2238
2601
  if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2239
2602
  anyTool._stateRef.toolHistory = state.toolHistory;
2240
2603
  anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2604
+ anyTool._stateRef.pendingApprovals = pendingApprovals;
2605
+ anyTool._stateRef.ctx = state.ctx || (state.ctx = {});
2241
2606
  anyTool._stateRef.__onEvent = onEvent;
2242
2607
  }
2243
2608
  }
2244
2609
  const last = state.messages[state.messages.length - 1];
2245
2610
  let toolCount = state.toolCallCount || 0;
2246
- const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
2611
+ const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls.map((toolCall) => normalizeToolCall(toolCall)).filter((toolCall) => toolCall !== null) : [];
2247
2612
  const toolHistory = state.toolHistory || [];
2248
2613
  const toolHistoryArchived = state.toolHistoryArchived || [];
2249
2614
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
@@ -2302,6 +2667,13 @@ function createToolsNode(initialTools, opts) {
2302
2667
  args = validatedArgs.value;
2303
2668
  }
2304
2669
  const toolName = tool.name || tc.name;
2670
+ const toolStateRef = tool._stateRef && typeof tool._stateRef === "object" ? tool._stateRef : null;
2671
+ if (toolStateRef) {
2672
+ toolStateRef.pendingApprovals = pendingApprovals;
2673
+ toolStateRef.ctx = state.ctx || (state.ctx = {});
2674
+ toolStateRef.__currentToolCallId = toolCallId;
2675
+ delete toolStateRef.__awaitingApproval;
2676
+ }
2305
2677
  const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
2306
2678
  if (maxExecutionsPerRun !== null) {
2307
2679
  const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
@@ -2414,6 +2786,22 @@ function createToolsNode(initialTools, opts) {
2414
2786
  toolCount += 1;
2415
2787
  return { status: "success", approval: approvalEntry };
2416
2788
  }
2789
+ if (output && typeof output === "object" && output.__awaitingToolApproval) {
2790
+ if (toolStateRef?.ctx && typeof toolStateRef.ctx === "object") {
2791
+ state.ctx = toolStateRef.ctx;
2792
+ }
2793
+ const pendingApproval = pendingApprovals.find(
2794
+ (entry) => entry.toolCallId === output.toolCallId || entry.id === output.approvalId
2795
+ );
2796
+ if (!pendingApproval) {
2797
+ throw new Error(`Tool ${toolName} requested approval but no pending approval entry was recorded.`);
2798
+ }
2799
+ awaitingApproval = true;
2800
+ return {
2801
+ status: "awaiting_approval",
2802
+ approval: pendingApproval
2803
+ };
2804
+ }
2417
2805
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
2418
2806
  if (!state.ctx) state.ctx = {};
2419
2807
  state.ctx.__structuredOutputParsed = output.data;
@@ -2836,6 +3224,153 @@ function resolveToolApprovalState(state, resolution) {
2836
3224
  };
2837
3225
  }
2838
3226
 
3227
+ // src/structuredOutput/manager.ts
3228
+ var StructuredOutputManager = class {
3229
+ constructor(schema, strategy, config) {
3230
+ this.schema = schema;
3231
+ this.strategy = strategy;
3232
+ this.maxRetries = config?.maxRetries ?? 2;
3233
+ }
3234
+ // --- Setup Phase ---
3235
+ /**
3236
+ * Get the `response` tool for tool-based strategy.
3237
+ * Returns undefined for native strategy (no tool needed).
3238
+ */
3239
+ getResponseTool() {
3240
+ if (this.strategy.kind !== "tool_based") return void 0;
3241
+ return this.strategy.createResponseTool(this.schema);
3242
+ }
3243
+ /**
3244
+ * Get response_format config for native strategy.
3245
+ * Returns undefined for tool-based strategy.
3246
+ */
3247
+ getResponseFormat() {
3248
+ if (this.strategy.kind !== "native") return void 0;
3249
+ return this.strategy.buildResponseFormat(this.schema);
3250
+ }
3251
+ /**
3252
+ * Build the system prompt hint that tells the model how to produce structured output.
3253
+ */
3254
+ buildSystemPromptHint() {
3255
+ if (this.strategy.kind === "native") {
3256
+ return [
3257
+ "A structured output schema is active.",
3258
+ "Your response MUST be valid JSON matching the output schema.",
3259
+ "Do NOT wrap the JSON in code fences or add any text before/after it."
3260
+ ].join("\n");
3261
+ }
3262
+ return [
3263
+ "A structured output schema is active.",
3264
+ "Do NOT output the final JSON directly as an assistant message.",
3265
+ "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
3266
+ "Call it exactly once then STOP producing further assistant messages."
3267
+ ].join("\n");
3268
+ }
3269
+ /**
3270
+ * Build a nudge message for when the model didn't produce structured output.
3271
+ * Used by the agent loop when it needs to retry.
3272
+ */
3273
+ buildNudgeMessage(isLastAttempt) {
3274
+ if (this.strategy.kind === "native") {
3275
+ return {
3276
+ role: "system",
3277
+ content: [
3278
+ "A structured output schema is active.",
3279
+ "You MUST respond with valid JSON matching the schema.",
3280
+ "Do NOT include any text outside the JSON object.",
3281
+ isLastAttempt ? "This is your FINAL attempt. Produce the JSON NOW." : ""
3282
+ ].filter(Boolean).join("\n")
3283
+ };
3284
+ }
3285
+ return {
3286
+ role: "system",
3287
+ content: [
3288
+ "A structured output schema is active.",
3289
+ "You MUST now call tool `response` with the final JSON object that matches the schema.",
3290
+ "Do not write the JSON in the assistant message.",
3291
+ "Call `response` exactly once, then stop.",
3292
+ isLastAttempt ? "Do NOT call any other tools. Produce the response NOW based on whatever information you have gathered so far." : ""
3293
+ ].filter(Boolean).join("\n")
3294
+ };
3295
+ }
3296
+ /**
3297
+ * Build a correction message when schema validation failed.
3298
+ */
3299
+ buildCorrectionMessage(error, _previousAttempt) {
3300
+ if (error.fieldErrors && error.fieldErrors.length > 0) {
3301
+ const fieldIssues = error.fieldErrors.map((f) => `- "${f.path}": ${f.message} (received: ${JSON.stringify(f.received)})`).join("\n");
3302
+ const action = this.strategy.kind === "native" ? "Please output corrected JSON matching the schema." : "Please call `response` again with corrected values matching the schema.";
3303
+ return {
3304
+ role: "system",
3305
+ content: [
3306
+ "Your previous structured output had validation errors:",
3307
+ fieldIssues,
3308
+ "",
3309
+ action
3310
+ ].join("\n")
3311
+ };
3312
+ }
3313
+ return this.buildNudgeMessage(false);
3314
+ }
3315
+ // --- Parse Phase ---
3316
+ /**
3317
+ * Try to parse structured output from the tool-based finalization result.
3318
+ * Called when __structuredOutputParsed is available in ctx.
3319
+ */
3320
+ parseFromToolResult(data) {
3321
+ return { success: true, data, attempts: 1 };
3322
+ }
3323
+ /**
3324
+ * Try to parse structured output from assistant message content.
3325
+ * For native strategy: content should be valid JSON.
3326
+ * For tool-based strategy: last-resort fallback extraction from text.
3327
+ */
3328
+ parseFromContent(content) {
3329
+ if (this.strategy.kind === "native") {
3330
+ const result2 = this.strategy.extractOutput(content, this.schema);
3331
+ if ("data" in result2) {
3332
+ return { success: true, data: result2.data, attempts: 1 };
3333
+ }
3334
+ return { success: false, error: result2.error, attempts: 1, rawContent: content };
3335
+ }
3336
+ const result = this.strategy.extractFromText(content, this.schema);
3337
+ if ("data" in result) {
3338
+ return { success: true, data: result.data, attempts: 1 };
3339
+ }
3340
+ return { success: false, error: result.error, attempts: 1, rawContent: content };
3341
+ }
3342
+ /**
3343
+ * Create a "no output" error result.
3344
+ */
3345
+ noOutputResult(attempts, rawContent) {
3346
+ return {
3347
+ success: false,
3348
+ error: {
3349
+ type: "no_output",
3350
+ message: "Model did not produce structured output after all retry attempts.",
3351
+ rawContent
3352
+ },
3353
+ attempts,
3354
+ rawContent
3355
+ };
3356
+ }
3357
+ /**
3358
+ * Create a "max retries" error result.
3359
+ */
3360
+ maxRetriesResult(lastError, attempts, rawContent) {
3361
+ return {
3362
+ success: false,
3363
+ error: {
3364
+ ...lastError,
3365
+ type: "max_retries",
3366
+ message: `Structured output failed after ${attempts} attempts: ${lastError.message}`
3367
+ },
3368
+ attempts,
3369
+ rawContent
3370
+ };
3371
+ }
3372
+ };
3373
+
2839
3374
  // src/agent.ts
2840
3375
  function isSyntheticSummaryMessage(message) {
2841
3376
  if (!message) return false;
@@ -2887,14 +3422,13 @@ function createAgent(opts) {
2887
3422
  const resolver = createResolverNode();
2888
3423
  const agentCore = createAgentCoreNode(opts);
2889
3424
  const toolsBase = [...opts.tools ?? []];
2890
- if (opts.outputSchema) {
2891
- const responseTool = createTool({
2892
- name: "response",
2893
- description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
2894
- schema: opts.outputSchema,
2895
- func: async (data) => ({ __finalStructuredOutput: true, data })
2896
- });
2897
- toolsBase.push(responseTool);
3425
+ const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
3426
+ if (soManager) {
3427
+ const modelCapabilities = getModelCapabilities(opts.model);
3428
+ const responseTool = soManager.getResponseTool();
3429
+ if (responseTool && modelCapabilities.structuredOutput !== "native") {
3430
+ toolsBase.push(responseTool);
3431
+ }
2898
3432
  }
2899
3433
  const toolsNode = createToolsNode(toolsBase, opts);
2900
3434
  const finalizeNode = createToolLimitFinalizeNode();
@@ -2929,6 +3463,7 @@ function createAgent(opts) {
2929
3463
  limits: opts.limits,
2930
3464
  useTodoList: void 0,
2931
3465
  outputSchema: opts.outputSchema,
3466
+ responseFormat: soManager?.getResponseFormat(),
2932
3467
  tracing: opts.tracing
2933
3468
  };
2934
3469
  const summarizationThreshold = getActiveSummarizationThreshold(opts);
@@ -3067,10 +3602,11 @@ function createAgent(opts) {
3067
3602
  });
3068
3603
  const tokenCount = countMessagesTokens(tokCountMessages);
3069
3604
  if (tokenCount > summarizationThreshold) {
3605
+ const summarizationExhausted = !!state.ctx?.__summarizationExhausted;
3070
3606
  const hasFreshSummary = (state.messages || []).some(
3071
3607
  (m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
3072
3608
  );
3073
- if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else {
3609
+ if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else if (summarizationExhausted) ; else {
3074
3610
  const ctx = { ...state.ctx || {}, __needsSummarization: true };
3075
3611
  state = { ...state, ctx };
3076
3612
  break;
@@ -3148,18 +3684,10 @@ function createAgent(opts) {
3148
3684
  continue;
3149
3685
  }
3150
3686
  if (toolCalls.length === 0) {
3151
- if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
3152
- const forceMsg = {
3153
- role: "system",
3154
- content: [
3155
- "A structured output schema is active.",
3156
- "You MUST now call tool `response` with the final JSON object that matches the schema.",
3157
- "Do not write the JSON in the assistant message.",
3158
- "Call `response` exactly once, then stop."
3159
- ].join("\n")
3160
- };
3687
+ if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
3688
+ const nudge = soManager.buildNudgeMessage(false);
3161
3689
  const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
3162
- state = { ...state, messages: [...state.messages, forceMsg], ctx };
3690
+ state = { ...state, messages: [...state.messages, nudge], ctx };
3163
3691
  continue;
3164
3692
  }
3165
3693
  break;
@@ -3172,23 +3700,16 @@ function createAgent(opts) {
3172
3700
  if (checkpointIfRequested("after_tools")) break;
3173
3701
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3174
3702
  }
3175
- if (opts.outputSchema && !state.ctx?.__finalizedDueToStructuredOutput) {
3176
- const last = state.messages[state.messages.length - 1];
3177
- const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
3178
- if (!lastHasToolCalls) {
3179
- const alreadyForced = Boolean(state.ctx?.__structuredOutputForceFinalize);
3180
- if (alreadyForced) {
3181
- const finalNudge = {
3182
- role: "system",
3183
- content: [
3184
- "A structured output schema is active.",
3185
- "You MUST now call tool `response` with the final JSON object that matches the schema.",
3186
- "Do not write the JSON in the assistant message.",
3187
- "Call `response` exactly once, then stop.",
3188
- "Do NOT call any other tools. Produce the response NOW based on whatever information you have gathered so far."
3189
- ].join("\n")
3190
- };
3191
- state = { ...state, messages: [...state.messages, finalNudge] };
3703
+ if (soManager && !state.ctx?.__finalizedDueToStructuredOutput) {
3704
+ const maxPostLoopRetries = soManager.maxRetries;
3705
+ for (let postRetry = 0; postRetry < maxPostLoopRetries; postRetry++) {
3706
+ if (state.ctx?.__finalizedDueToStructuredOutput) break;
3707
+ const last = state.messages[state.messages.length - 1];
3708
+ const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
3709
+ if (!lastHasToolCalls) {
3710
+ const isLastAttempt = postRetry === maxPostLoopRetries - 1;
3711
+ const nudge = soManager.buildNudgeMessage(isLastAttempt);
3712
+ state = { ...state, messages: [...state.messages, nudge] };
3192
3713
  }
3193
3714
  try {
3194
3715
  state = { ...state, ...await agentCore(state) };
@@ -3290,23 +3811,22 @@ function createAgent(opts) {
3290
3811
  const finalAssistantMsg = getLastAssistantMessage(res.messages);
3291
3812
  const content = getMessageText(finalAssistantMsg);
3292
3813
  let parsed = void 0;
3293
- const schema = opts.outputSchema;
3294
- if (schema && res.ctx?.__structuredOutputParsed) {
3295
- parsed = res.ctx.__structuredOutputParsed;
3296
- } else if (schema && content) {
3297
- let jsonText = null;
3298
- const fenced = content.match(/```(?:json)?\n([\s\S]*?)```/i);
3299
- if (fenced && fenced[1]) jsonText = fenced[1].trim();
3300
- else {
3301
- const braceIdx = content.indexOf("{");
3302
- const bracketIdx = content.indexOf("[");
3303
- const start = [braceIdx, bracketIdx].filter((i) => i >= 0).sort((a, b) => a - b)[0];
3304
- if (start !== void 0) jsonText = content.slice(start).trim();
3305
- }
3306
- try {
3307
- const raw = JSON.parse(jsonText ?? content);
3308
- parsed = schema.parse(raw);
3309
- } catch {
3814
+ let outputError = void 0;
3815
+ if (soManager) {
3816
+ if (res.ctx?.__structuredOutputParsed) {
3817
+ parsed = res.ctx.__structuredOutputParsed;
3818
+ } else if (content) {
3819
+ const fallbackResult = soManager.parseFromContent(content);
3820
+ if (fallbackResult.success) {
3821
+ parsed = fallbackResult.data;
3822
+ } else {
3823
+ outputError = fallbackResult.error;
3824
+ }
3825
+ } else {
3826
+ const noResult = soManager.noOutputResult(1);
3827
+ if (!noResult.success) {
3828
+ outputError = noResult.error;
3829
+ }
3310
3830
  }
3311
3831
  }
3312
3832
  emit({ type: "finalAnswer", content });
@@ -3317,6 +3837,7 @@ function createAgent(opts) {
3317
3837
  return {
3318
3838
  content,
3319
3839
  output: parsed,
3840
+ outputError,
3320
3841
  metadata: { usage: res.usage },
3321
3842
  messages: res.messages,
3322
3843
  state: res
@@ -3440,7 +3961,7 @@ function createAgentNode(opts) {
3440
3961
  ...messages,
3441
3962
  response
3442
3963
  ];
3443
- const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
3964
+ 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;
3444
3965
  const normalized = normalizeUsage(rawUsage);
3445
3966
  const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
3446
3967
  const providerName = getProviderName(runtime.model || opts.model);
@@ -3499,14 +4020,23 @@ function collectRecentTurns(messages, lastTurnsToKeep) {
3499
4020
  if (lastTurnsToKeep <= 0) return [];
3500
4021
  const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
3501
4022
  const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
3502
- let userTurnCount = 0;
4023
+ const totalUserTurns = body.filter((m) => m.role === "user").length;
4024
+ const countAssistantTurns = totalUserTurns < lastTurnsToKeep;
4025
+ let turnCount = 0;
3503
4026
  const collected = [];
3504
4027
  for (let index = body.length - 1; index >= 0; index -= 1) {
3505
4028
  const message = body[index];
3506
4029
  collected.unshift(message);
3507
- if (message.role === "user") {
3508
- userTurnCount += 1;
3509
- if (userTurnCount >= lastTurnsToKeep) break;
4030
+ const isTurnBoundary = countAssistantTurns ? message.role === "assistant" : message.role === "user";
4031
+ if (isTurnBoundary) {
4032
+ turnCount += 1;
4033
+ if (turnCount >= lastTurnsToKeep) break;
4034
+ }
4035
+ }
4036
+ if (countAssistantTurns && collected.length > 0 && collected[0].role !== "user") {
4037
+ const firstUserMsg = body.find((m) => m.role === "user");
4038
+ if (firstUserMsg && !collected.includes(firstUserMsg)) {
4039
+ collected.unshift(firstUserMsg);
3510
4040
  }
3511
4041
  }
3512
4042
  return [...systemPrefix, ...collected];
@@ -3546,7 +4076,44 @@ function clampToBudget(messages, maxContextTokens) {
3546
4076
  if (tokenCount <= maxContextTokens) return working;
3547
4077
  const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
3548
4078
  if (firstNonSystem < 0) break;
3549
- working.splice(firstNonSystem, 1);
4079
+ const target = working[firstNonSystem];
4080
+ if (target.role === "assistant" && Array.isArray(target.tool_calls) && target.tool_calls.length > 0) {
4081
+ const toolCallIds = new Set(
4082
+ target.tool_calls.map((tc) => tc.id).filter(Boolean)
4083
+ );
4084
+ working = working.filter((m, idx) => {
4085
+ if (idx === firstNonSystem) return false;
4086
+ if (m.role === "tool" && m.tool_call_id && toolCallIds.has(m.tool_call_id)) return false;
4087
+ return true;
4088
+ });
4089
+ } else if (target.role === "tool") {
4090
+ const toolCallId = target.tool_call_id;
4091
+ let parentIdx = -1;
4092
+ if (toolCallId) {
4093
+ for (let i = firstNonSystem - 1; i >= 0; i--) {
4094
+ const m = working[i];
4095
+ if (m.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.some((tc) => tc.id === toolCallId)) {
4096
+ parentIdx = i;
4097
+ break;
4098
+ }
4099
+ }
4100
+ }
4101
+ if (parentIdx >= 0) {
4102
+ const parent = working[parentIdx];
4103
+ const parentToolCallIds = new Set(
4104
+ (parent.tool_calls || []).map((tc) => tc.id).filter(Boolean)
4105
+ );
4106
+ working = working.filter((m, idx) => {
4107
+ if (idx === parentIdx) return false;
4108
+ if (m.role === "tool" && m.tool_call_id && parentToolCallIds.has(m.tool_call_id)) return false;
4109
+ return true;
4110
+ });
4111
+ } else {
4112
+ working.splice(firstNonSystem, 1);
4113
+ }
4114
+ } else {
4115
+ working.splice(firstNonSystem, 1);
4116
+ }
3550
4117
  }
3551
4118
  return working;
3552
4119
  }
@@ -4196,6 +4763,12 @@ function calculateAdherenceScore(todoList) {
4196
4763
  const completedCount = todoList.filter((item) => item.status === "completed").length;
4197
4764
  return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
4198
4765
  }
4766
+ function formatTodoListSummary(todoList) {
4767
+ if (!Array.isArray(todoList) || todoList.length === 0) {
4768
+ return "No plan items.";
4769
+ }
4770
+ return todoList.map((item) => `${item.id}. ${item.title || item.step || item.description || `Step ${item.id}`} [${item.status}]`).join("\n");
4771
+ }
4199
4772
  function createContextTools(stateRef, opts) {
4200
4773
  const tools = [];
4201
4774
  if (opts?.planningEnabled) {
@@ -4208,11 +4781,43 @@ function createContextTools(stateRef, opts) {
4208
4781
  todoList: z.array(z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
4209
4782
  }),
4210
4783
  func: async ({ operation, expectedVersion, todoList }) => {
4211
- const onEvent = manageTodo._stateRef?.__onEvent;
4212
- const currentVersion = stateRef.planVersion || 0;
4213
- if (operation === "read") {
4784
+ const toolStateRef = manageTodo._stateRef;
4785
+ const onEvent = toolStateRef?.__onEvent;
4786
+ const emitPlanEvent = (list) => {
4787
+ const version = stateRef.planVersion || 1;
4788
+ const adherenceScore = stateRef.adherenceScore || 0;
4789
+ const planData = {
4790
+ source: "manage_todo_list",
4791
+ operation,
4792
+ version,
4793
+ adherenceScore,
4794
+ count: Array.isArray(list) ? list.length : 0
4795
+ };
4796
+ onEvent?.({ type: "plan", todoList: list, ...planData });
4797
+ const traceSession = toolStateRef?.ctx?.__traceSession;
4798
+ recordTraceEvent(traceSession, {
4799
+ type: "plan",
4800
+ label: `Plan ${operation}`,
4801
+ actor: { scope: "agent", name: "manage_todo_list", role: "planner" },
4802
+ sections: [
4803
+ {
4804
+ kind: "summary",
4805
+ label: "Todo List",
4806
+ content: formatTodoListSummary(list)
4807
+ },
4808
+ {
4809
+ kind: "metadata",
4810
+ label: "Plan Metadata",
4811
+ data: planData
4812
+ }
4813
+ ],
4814
+ debug: planData
4815
+ });
4816
+ };
4817
+ const currentVersion = stateRef.planVersion || 0;
4818
+ if (operation === "read") {
4214
4819
  const list = stateRef.todoList || [];
4215
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
4820
+ emitPlanEvent(list);
4216
4821
  return list;
4217
4822
  }
4218
4823
  if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
@@ -4277,9 +4882,10 @@ function createContextTools(stateRef, opts) {
4277
4882
  adherenceScore: stateRef.adherenceScore || 0
4278
4883
  };
4279
4884
  }
4280
- const patchIds = /* @__PURE__ */ new Set();
4885
+ const currentMap = new Map(currentList.map((item) => [item.id, item]));
4886
+ const patchMap = /* @__PURE__ */ new Map();
4281
4887
  for (const item of parsed.data) {
4282
- if (patchIds.has(item.id)) {
4888
+ if (patchMap.has(item.id)) {
4283
4889
  return {
4284
4890
  status: "error",
4285
4891
  operation,
@@ -4288,10 +4894,7 @@ function createContextTools(stateRef, opts) {
4288
4894
  adherenceScore: stateRef.adherenceScore || 0
4289
4895
  };
4290
4896
  }
4291
- patchIds.add(item.id);
4292
- }
4293
- const currentMap = new Map(currentList.map((item) => [item.id, item]));
4294
- for (const item of parsed.data) {
4897
+ patchMap.set(item.id, item);
4295
4898
  if (!currentMap.has(item.id)) {
4296
4899
  return {
4297
4900
  status: "error",
@@ -4303,7 +4906,7 @@ function createContextTools(stateRef, opts) {
4303
4906
  }
4304
4907
  }
4305
4908
  const mergedList = currentList.map((item) => {
4306
- const patch = parsed.data.find((candidate) => candidate.id === item.id);
4909
+ const patch = patchMap.get(item.id);
4307
4910
  return patch ? normalizeTodoItem(patch, item) : item;
4308
4911
  });
4309
4912
  const validation = validatePlanInvariants(mergedList, false);
@@ -4327,7 +4930,7 @@ function createContextTools(stateRef, opts) {
4327
4930
  version: stateRef.planVersion || 1,
4328
4931
  adherenceScore: stateRef.adherenceScore || 0
4329
4932
  };
4330
- onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
4933
+ emitPlanEvent(stateRef.todoList || []);
4331
4934
  return payload;
4332
4935
  }
4333
4936
  });
@@ -4352,23 +4955,6 @@ function createContextTools(stateRef, opts) {
4352
4955
  });
4353
4956
  getTool._stateRef = stateRef;
4354
4957
  tools.push(getTool);
4355
- if (opts?.outputSchema) {
4356
- const responseTool = createTool({
4357
- name: "response",
4358
- description: "Finalize the answer by returning the final structured JSON matching the required schema. Call exactly once when you are fully done, then stop.",
4359
- schema: opts.outputSchema.shape ? opts.outputSchema : z.any(),
4360
- func: async (data) => {
4361
- try {
4362
- const validated = opts.outputSchema.parse ? opts.outputSchema.parse(data) : data;
4363
- return { __finalStructuredOutput: true, data: validated };
4364
- } catch (e) {
4365
- return { error: "Schema validation failed", details: e?.message };
4366
- }
4367
- }
4368
- });
4369
- responseTool._stateRef = stateRef;
4370
- tools.push(responseTool);
4371
- }
4372
4958
  return tools;
4373
4959
  }
4374
4960
 
@@ -4577,7 +5163,7 @@ function createSmartAgent(opts) {
4577
5163
  useTodoList: planningEnabled
4578
5164
  };
4579
5165
  const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
4580
- const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
5166
+ const contextTools = createContextTools(stateRef, { planningEnabled });
4581
5167
  const mergedTools = [...opts.tools ?? [], ...contextTools];
4582
5168
  const base = createAgent({ ...runtimeOpts, tools: mergedTools });
4583
5169
  base.__runtime.runtimeProfile = resolved.runtimeProfile;
@@ -4586,12 +5172,8 @@ function createSmartAgent(opts) {
4586
5172
  const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
4587
5173
  const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
4588
5174
  const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
4589
- const structuredOutputHint = opts.outputSchema ? [
4590
- "A structured output schema is active.",
4591
- "Do NOT output the final JSON directly as an assistant message.",
4592
- "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
4593
- "Call it exactly once then STOP producing further assistant messages."
4594
- ].join("\n") : "";
5175
+ const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
5176
+ const structuredOutputHint = soManager ? soManager.buildSystemPromptHint() : "";
4595
5177
  const runtimeHint = [
4596
5178
  resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
4597
5179
  `Context policy: ${resolved.context.policy}.`,
@@ -4661,8 +5243,13 @@ function createSmartAgent(opts) {
4661
5243
  const next = summarizationEnabled ? decideBefore(state) : "agent";
4662
5244
  if (next === "contextSummarize" && summarizer) {
4663
5245
  const delta = await summarizer(state);
4664
- if (!delta || Object.keys(delta).length === 0) ; else {
4665
- state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
5246
+ if (!delta || Object.keys(delta).length === 0) {
5247
+ const ctx = { ...state.ctx || {}, __summarizationExhausted: true };
5248
+ state = { ...state, ctx };
5249
+ } else {
5250
+ const ctx = { ...state.ctx || {} };
5251
+ delete ctx.__summarizationExhausted;
5252
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta, ctx }));
4666
5253
  rawMessages = [...state.messages || rawMessages];
4667
5254
  continue;
4668
5255
  }
@@ -4695,20 +5282,32 @@ function createSmartAgent(opts) {
4695
5282
  state = { ...state, ctx };
4696
5283
  const delta = await summarizer(state);
4697
5284
  if (delta && Object.keys(delta).length > 0) {
4698
- state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
5285
+ const successCtx = { ...state.ctx || {} };
5286
+ delete successCtx.__summarizationExhausted;
5287
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta, ctx: successCtx }));
4699
5288
  rawMessages = [...state.messages || rawMessages];
4700
5289
  continue;
5290
+ } else {
5291
+ const exhaustedCtx = { ...state.ctx || {}, __summarizationExhausted: true };
5292
+ state = { ...state, ctx: exhaustedCtx };
5293
+ continue;
4701
5294
  }
4702
5295
  }
4703
5296
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
5297
+ if (opts.outputSchema && lastResult?.output != null) break;
4704
5298
  if (summarizationEnabled) {
4705
5299
  const after = decideAfter(state);
4706
5300
  if (after === "contextSummarize" && summarizer) {
4707
5301
  const delta = await summarizer(state);
4708
5302
  if (delta && Object.keys(delta).length > 0) {
4709
- state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
5303
+ const successCtx = { ...state.ctx || {} };
5304
+ delete successCtx.__summarizationExhausted;
5305
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta, ctx: successCtx }));
4710
5306
  rawMessages = [...state.messages || rawMessages];
4711
5307
  continue;
5308
+ } else {
5309
+ const exhaustedCtx = { ...state.ctx || {}, __summarizationExhausted: true };
5310
+ state = { ...state, ctx: exhaustedCtx };
4712
5311
  }
4713
5312
  }
4714
5313
  }
@@ -5151,6 +5750,1740 @@ function createCodeGuardrail(options) {
5151
5750
  });
5152
5751
  }
5153
5752
 
5154
- export { DEFAULT_PROFILE_CONFIGS, GuardrailPhase, InMemoryMemoryStore, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, evaluateGuardrails, fileSink, fromLangchainModel, fromLangchainTools, generateSpanId, generateTraceId, getResolvedSmartConfig, httpSink, isSmartModel, jsonSchemaRule, normalizeSmartAgentOptions, otlpSink, readMemoryFacts, regexRule, renderMemoryFacts, resolveMemoryStore, resolveToolApprovalState, restoreSnapshot, runSmartAgentEvalHarness, startStreamingSession, traceSessionToOtlp, withTools, writeSummaryFactsToMemory };
5753
+ // src/providers/types.ts
5754
+ function emptyUsage() {
5755
+ return {
5756
+ inputTokens: 0,
5757
+ outputTokens: 0,
5758
+ totalTokens: 0,
5759
+ cachedInputTokens: 0,
5760
+ cachedWriteTokens: 0,
5761
+ cachedOutputTokens: 0,
5762
+ reasoningTokens: 0
5763
+ };
5764
+ }
5765
+ var ProviderError = class extends Error {
5766
+ constructor(message, provider, statusCode, responseBody) {
5767
+ super(message);
5768
+ this.provider = provider;
5769
+ this.statusCode = statusCode;
5770
+ this.responseBody = responseBody;
5771
+ this.name = "ProviderError";
5772
+ }
5773
+ };
5774
+
5775
+ // src/providers/base.ts
5776
+ var BaseProvider = class {
5777
+ };
5778
+
5779
+ // src/providers/utils/sse.ts
5780
+ async function* parseSSEStream(body, signal) {
5781
+ const reader = body.getReader();
5782
+ const decoder = new TextDecoder();
5783
+ let buffer = "";
5784
+ try {
5785
+ while (true) {
5786
+ if (signal?.aborted) ;
5787
+ const { done, value } = await reader.read();
5788
+ if (done) break;
5789
+ buffer += decoder.decode(value, { stream: true });
5790
+ const parts = buffer.split(/\r?\n\r?\n/);
5791
+ buffer = parts.pop() ?? "";
5792
+ for (const part of parts) {
5793
+ if (!part.trim()) continue;
5794
+ const event = parseSSEBlock(part);
5795
+ if (event) yield event;
5796
+ }
5797
+ }
5798
+ if (buffer.trim()) {
5799
+ const event = parseSSEBlock(buffer);
5800
+ if (event) yield event;
5801
+ }
5802
+ } finally {
5803
+ reader.releaseLock();
5804
+ }
5805
+ }
5806
+ function parseSSEBlock(block) {
5807
+ let event;
5808
+ let id;
5809
+ let retry;
5810
+ const dataLines = [];
5811
+ for (const line of block.split(/\r?\n/)) {
5812
+ if (line.startsWith(":")) continue;
5813
+ const colonIdx = line.indexOf(":");
5814
+ let field;
5815
+ let value;
5816
+ if (colonIdx === -1) {
5817
+ field = line;
5818
+ value = "";
5819
+ } else {
5820
+ field = line.slice(0, colonIdx);
5821
+ value = line.slice(colonIdx + 1);
5822
+ if (value.startsWith(" ")) value = value.slice(1);
5823
+ }
5824
+ switch (field) {
5825
+ case "event":
5826
+ event = value;
5827
+ break;
5828
+ case "data":
5829
+ dataLines.push(value);
5830
+ break;
5831
+ case "id":
5832
+ id = value;
5833
+ break;
5834
+ case "retry": {
5835
+ const n = parseInt(value, 10);
5836
+ if (!isNaN(n)) retry = n;
5837
+ break;
5838
+ }
5839
+ }
5840
+ }
5841
+ if (dataLines.length === 0) return null;
5842
+ return { event, data: dataLines.join("\n"), id, retry };
5843
+ }
5844
+
5845
+ // src/providers/openai.ts
5846
+ var OpenAIProvider = class extends BaseProvider {
5847
+ constructor(config) {
5848
+ super();
5849
+ this.providerName = "openai";
5850
+ this.apiKey = config.apiKey;
5851
+ this.baseURL = (config.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "");
5852
+ this.defaultModel = config.defaultModel ?? "gpt-4o";
5853
+ this.defaultHeaders = {
5854
+ ...config.defaultHeaders,
5855
+ ...config.organization ? { "OpenAI-Organization": config.organization } : {}
5856
+ };
5857
+ }
5858
+ async complete(request) {
5859
+ const body = this.buildRequestBody(request, false);
5860
+ const res = await this.doFetch(body);
5861
+ const json = await res.json();
5862
+ return this.parseResponse(json);
5863
+ }
5864
+ async *completeStream(request) {
5865
+ const body = this.buildRequestBody(request, true);
5866
+ const res = await this.doFetch(body);
5867
+ if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
5868
+ let id = "";
5869
+ let model = "";
5870
+ const toolCallBuffers = /* @__PURE__ */ new Map();
5871
+ let usage;
5872
+ for await (const event of parseSSEStream(res.body)) {
5873
+ if (event.data === "[DONE]") break;
5874
+ let chunk;
5875
+ try {
5876
+ chunk = JSON.parse(event.data);
5877
+ } catch {
5878
+ continue;
5879
+ }
5880
+ id = chunk.id ?? id;
5881
+ model = chunk.model ?? model;
5882
+ const choice = chunk.choices?.[0];
5883
+ if (chunk.usage) {
5884
+ usage = this.extractUsage(chunk.usage);
5885
+ }
5886
+ if (!choice) {
5887
+ if (usage) yield { id, model, delta: {}, usage };
5888
+ continue;
5889
+ }
5890
+ const delta = choice.delta ?? {};
5891
+ const out = {
5892
+ id,
5893
+ model,
5894
+ delta: {},
5895
+ finishReason: mapFinishReason(choice.finish_reason)
5896
+ };
5897
+ if (delta.content) {
5898
+ out.delta.content = delta.content;
5899
+ }
5900
+ if (delta.tool_calls) {
5901
+ const partials = [];
5902
+ for (const tc of delta.tool_calls) {
5903
+ const idx = tc.index ?? 0;
5904
+ if (!toolCallBuffers.has(idx)) {
5905
+ toolCallBuffers.set(idx, { id: tc.id ?? "", name: "", arguments: "" });
5906
+ }
5907
+ const buf = toolCallBuffers.get(idx);
5908
+ if (tc.id) buf.id = tc.id;
5909
+ if (tc.function?.name) buf.name = tc.function.name;
5910
+ if (tc.function?.arguments) buf.arguments += tc.function.arguments;
5911
+ partials.push({ id: buf.id, name: buf.name || void 0, arguments: tc.function?.arguments });
5912
+ }
5913
+ out.delta.toolCalls = partials;
5914
+ }
5915
+ if (usage) out.usage = usage;
5916
+ yield out;
5917
+ }
5918
+ }
5919
+ // ─── Internals ───────────────────────────────────────────────────────────
5920
+ buildRequestBody(request, stream) {
5921
+ const body = {
5922
+ model: request.model || this.defaultModel,
5923
+ messages: request.messages.map((m) => this.toOpenAIMessage(m)),
5924
+ stream
5925
+ };
5926
+ if (stream) {
5927
+ body.stream_options = { include_usage: true };
5928
+ }
5929
+ if (request.temperature != null) body.temperature = request.temperature;
5930
+ if (request.maxTokens != null) body.max_tokens = request.maxTokens;
5931
+ if (request.topP != null) body.top_p = request.topP;
5932
+ if (request.stop) body.stop = request.stop;
5933
+ if (request.tools?.length) {
5934
+ body.tools = request.tools.map((t) => this.toOpenAITool(t));
5935
+ }
5936
+ if (request.toolChoice != null) {
5937
+ if (typeof request.toolChoice === "string") {
5938
+ body.tool_choice = request.toolChoice;
5939
+ } else {
5940
+ body.tool_choice = { type: "function", function: { name: request.toolChoice.name } };
5941
+ }
5942
+ }
5943
+ if (request.responseFormat) {
5944
+ if (request.responseFormat.type === "json_schema") {
5945
+ body.response_format = {
5946
+ type: "json_schema",
5947
+ json_schema: {
5948
+ name: request.responseFormat.name ?? "response",
5949
+ schema: request.responseFormat.schema,
5950
+ strict: true
5951
+ }
5952
+ };
5953
+ } else if (request.responseFormat.type === "json_object") {
5954
+ body.response_format = { type: "json_object" };
5955
+ }
5956
+ }
5957
+ if (request.extra) {
5958
+ Object.assign(body, request.extra);
5959
+ }
5960
+ return body;
5961
+ }
5962
+ toOpenAIMessage(m) {
5963
+ const msg = { role: m.role };
5964
+ if (typeof m.content === "string") {
5965
+ msg.content = m.content;
5966
+ } else if (Array.isArray(m.content)) {
5967
+ msg.content = m.content.map((part) => {
5968
+ if (part.type === "text") return { type: "text", text: part.text };
5969
+ if (part.type === "image") {
5970
+ const src = part.source;
5971
+ const url = src.type === "url" ? src.url : `data:${src.mediaType};base64,${src.data}`;
5972
+ return { type: "image_url", image_url: { url } };
5973
+ }
5974
+ return part;
5975
+ });
5976
+ }
5977
+ if (m.role === "assistant" && m.toolCalls?.length) {
5978
+ msg.tool_calls = m.toolCalls.map((tc) => ({
5979
+ id: tc.id,
5980
+ type: "function",
5981
+ function: { name: tc.name, arguments: tc.arguments }
5982
+ }));
5983
+ }
5984
+ if (m.role === "tool" && m.toolCallId) {
5985
+ msg.tool_call_id = m.toolCallId;
5986
+ }
5987
+ if (m.name) msg.name = m.name;
5988
+ return msg;
5989
+ }
5990
+ toOpenAITool(t) {
5991
+ const fn = {
5992
+ name: t.name,
5993
+ description: t.description,
5994
+ parameters: t.parameters
5995
+ };
5996
+ if (t.strict) {
5997
+ fn.strict = true;
5998
+ }
5999
+ return { type: "function", function: fn };
6000
+ }
6001
+ async doFetch(body) {
6002
+ const url = `${this.baseURL}/chat/completions`;
6003
+ const res = await fetch(url, {
6004
+ method: "POST",
6005
+ headers: {
6006
+ "Content-Type": "application/json",
6007
+ Authorization: `Bearer ${this.apiKey}`,
6008
+ ...this.defaultHeaders
6009
+ },
6010
+ body: JSON.stringify(body)
6011
+ });
6012
+ if (!res.ok) {
6013
+ const text = await res.text().catch(() => "");
6014
+ throw new ProviderError(
6015
+ `OpenAI API error ${res.status}: ${text}`,
6016
+ this.providerName,
6017
+ res.status,
6018
+ text
6019
+ );
6020
+ }
6021
+ return res;
6022
+ }
6023
+ parseResponse(json) {
6024
+ const choice = json.choices?.[0];
6025
+ const message = choice?.message ?? {};
6026
+ const toolCalls = (message.tool_calls ?? []).map((tc) => ({
6027
+ id: tc.id,
6028
+ name: tc.function?.name ?? "",
6029
+ arguments: tc.function?.arguments ?? "{}"
6030
+ }));
6031
+ return {
6032
+ id: json.id ?? "",
6033
+ model: json.model ?? "",
6034
+ content: message.content ?? null,
6035
+ toolCalls,
6036
+ usage: this.extractUsage(json.usage),
6037
+ finishReason: mapFinishReason(choice?.finish_reason),
6038
+ raw: json
6039
+ };
6040
+ }
6041
+ extractUsage(usage) {
6042
+ if (!usage) return emptyUsage();
6043
+ const promptDetails = usage.prompt_tokens_details ?? {};
6044
+ const completionDetails = usage.completion_tokens_details ?? {};
6045
+ return {
6046
+ inputTokens: usage.prompt_tokens ?? 0,
6047
+ outputTokens: usage.completion_tokens ?? 0,
6048
+ totalTokens: usage.total_tokens ?? (usage.prompt_tokens ?? 0) + (usage.completion_tokens ?? 0),
6049
+ cachedInputTokens: promptDetails.cached_tokens ?? 0,
6050
+ cachedWriteTokens: 0,
6051
+ cachedOutputTokens: 0,
6052
+ reasoningTokens: completionDetails.reasoning_tokens ?? 0
6053
+ };
6054
+ }
6055
+ };
6056
+ function mapFinishReason(reason) {
6057
+ switch (reason) {
6058
+ case "stop":
6059
+ return "stop";
6060
+ case "tool_calls":
6061
+ return "tool_calls";
6062
+ case "length":
6063
+ return "length";
6064
+ case "content_filter":
6065
+ return "content_filter";
6066
+ default:
6067
+ return "stop";
6068
+ }
6069
+ }
6070
+
6071
+ // src/providers/anthropic.ts
6072
+ var AnthropicProvider = class extends BaseProvider {
6073
+ constructor(config) {
6074
+ super();
6075
+ this.providerName = "anthropic";
6076
+ this.apiKey = config.apiKey;
6077
+ this.baseURL = (config.baseURL ?? "https://api.anthropic.com").replace(/\/$/, "");
6078
+ this.defaultModel = config.defaultModel ?? "claude-sonnet-4-20250514";
6079
+ this.defaultHeaders = config.defaultHeaders ?? {};
6080
+ this.anthropicVersion = config.anthropicVersion ?? "2023-06-01";
6081
+ }
6082
+ async complete(request) {
6083
+ const body = this.buildRequestBody(request, false);
6084
+ const res = await this.doFetch(body);
6085
+ const json = await res.json();
6086
+ return this.parseResponse(json);
6087
+ }
6088
+ async *completeStream(request) {
6089
+ const body = this.buildRequestBody(request, true);
6090
+ const res = await this.doFetch(body);
6091
+ if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
6092
+ let id = "";
6093
+ let model = "";
6094
+ let inputTokens = 0;
6095
+ let outputTokens = 0;
6096
+ let cacheReadInputTokens = 0;
6097
+ let cacheCreationInputTokens = 0;
6098
+ const currentToolCalls = /* @__PURE__ */ new Map();
6099
+ for await (const event of parseSSEStream(res.body)) {
6100
+ let data;
6101
+ try {
6102
+ data = JSON.parse(event.data);
6103
+ } catch {
6104
+ continue;
6105
+ }
6106
+ const eventType = event.event ?? data.type;
6107
+ switch (eventType) {
6108
+ case "message_start": {
6109
+ const msg = data.message;
6110
+ id = msg?.id ?? "";
6111
+ model = msg?.model ?? "";
6112
+ if (msg?.usage) {
6113
+ inputTokens = msg.usage.input_tokens ?? 0;
6114
+ cacheReadInputTokens = msg.usage.cache_read_input_tokens ?? 0;
6115
+ cacheCreationInputTokens = msg.usage.cache_creation_input_tokens ?? 0;
6116
+ }
6117
+ break;
6118
+ }
6119
+ case "content_block_start": {
6120
+ const idx = data.index ?? 0;
6121
+ const block = data.content_block;
6122
+ if (block?.type === "tool_use") {
6123
+ currentToolCalls.set(idx, {
6124
+ id: block.id ?? "",
6125
+ name: block.name ?? "",
6126
+ arguments: ""
6127
+ });
6128
+ }
6129
+ break;
6130
+ }
6131
+ case "content_block_delta": {
6132
+ const idx = data.index ?? 0;
6133
+ const delta = data.delta;
6134
+ if (delta?.type === "text_delta" && delta.text) {
6135
+ yield {
6136
+ id,
6137
+ model,
6138
+ delta: { content: delta.text }
6139
+ };
6140
+ } else if (delta?.type === "input_json_delta" && delta.partial_json) {
6141
+ const tc = currentToolCalls.get(idx);
6142
+ if (tc) {
6143
+ tc.arguments += delta.partial_json;
6144
+ yield {
6145
+ id,
6146
+ model,
6147
+ delta: {
6148
+ toolCalls: [{ id: tc.id, name: tc.name, arguments: delta.partial_json }]
6149
+ }
6150
+ };
6151
+ }
6152
+ }
6153
+ break;
6154
+ }
6155
+ case "content_block_stop":
6156
+ break;
6157
+ case "message_delta": {
6158
+ const delta = data.delta;
6159
+ if (data.usage) {
6160
+ outputTokens = data.usage.output_tokens ?? outputTokens;
6161
+ }
6162
+ const finishReason = mapAnthropicStopReason(delta?.stop_reason);
6163
+ const usage = {
6164
+ inputTokens,
6165
+ outputTokens,
6166
+ totalTokens: inputTokens + outputTokens,
6167
+ cachedInputTokens: cacheReadInputTokens,
6168
+ cachedWriteTokens: cacheCreationInputTokens,
6169
+ cachedOutputTokens: 0,
6170
+ reasoningTokens: 0
6171
+ };
6172
+ yield {
6173
+ id,
6174
+ model,
6175
+ delta: {},
6176
+ finishReason,
6177
+ usage
6178
+ };
6179
+ break;
6180
+ }
6181
+ case "message_stop":
6182
+ break;
6183
+ case "error": {
6184
+ const errMsg = data.error?.message ?? "Unknown Anthropic streaming error";
6185
+ throw new ProviderError(errMsg, this.providerName);
6186
+ }
6187
+ }
6188
+ }
6189
+ }
6190
+ // ─── Internals ───────────────────────────────────────────────────────────
6191
+ buildRequestBody(request, stream) {
6192
+ const { system, messages } = this.splitSystemMessages(request.messages);
6193
+ const body = {
6194
+ model: request.model || this.defaultModel,
6195
+ messages: messages.map((m) => this.toAnthropicMessage(m)),
6196
+ max_tokens: request.maxTokens ?? 4096,
6197
+ stream
6198
+ };
6199
+ if (system) body.system = system;
6200
+ if (request.temperature != null) body.temperature = request.temperature;
6201
+ if (request.topP != null) body.top_p = request.topP;
6202
+ if (request.stop) body.stop_sequences = request.stop;
6203
+ if (request.tools?.length) {
6204
+ body.tools = request.tools.map((t) => this.toAnthropicTool(t));
6205
+ }
6206
+ if (request.toolChoice != null) {
6207
+ if (request.toolChoice === "auto") {
6208
+ body.tool_choice = { type: "auto" };
6209
+ } else if (request.toolChoice === "required") {
6210
+ body.tool_choice = { type: "any" };
6211
+ } else if (request.toolChoice === "none") ; else {
6212
+ body.tool_choice = { type: "tool", name: request.toolChoice.name };
6213
+ }
6214
+ }
6215
+ if (request.extra) {
6216
+ Object.assign(body, request.extra);
6217
+ }
6218
+ return body;
6219
+ }
6220
+ splitSystemMessages(messages) {
6221
+ const systemParts = [];
6222
+ const rest = [];
6223
+ for (const m of messages) {
6224
+ if (m.role === "system") {
6225
+ if (typeof m.content === "string") {
6226
+ systemParts.push(m.content);
6227
+ } else {
6228
+ const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
6229
+ if (text) systemParts.push(text);
6230
+ }
6231
+ } else {
6232
+ rest.push(m);
6233
+ }
6234
+ }
6235
+ return {
6236
+ system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
6237
+ messages: rest
6238
+ };
6239
+ }
6240
+ toAnthropicMessage(m) {
6241
+ if (m.role === "tool" && m.toolCallId) {
6242
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
6243
+ return {
6244
+ role: "user",
6245
+ content: [
6246
+ {
6247
+ type: "tool_result",
6248
+ tool_use_id: m.toolCallId,
6249
+ content
6250
+ }
6251
+ ]
6252
+ };
6253
+ }
6254
+ if (m.role === "assistant" && m.toolCalls?.length) {
6255
+ const content = [];
6256
+ if (typeof m.content === "string" && m.content) {
6257
+ content.push({ type: "text", text: m.content });
6258
+ }
6259
+ for (const tc of m.toolCalls) {
6260
+ content.push({
6261
+ type: "tool_use",
6262
+ id: tc.id,
6263
+ name: tc.name,
6264
+ input: safeJsonParse(tc.arguments)
6265
+ });
6266
+ }
6267
+ return { role: "assistant", content };
6268
+ }
6269
+ const msg = { role: m.role };
6270
+ if (typeof m.content === "string") {
6271
+ msg.content = m.content;
6272
+ } else if (Array.isArray(m.content)) {
6273
+ msg.content = m.content.map((part) => {
6274
+ if (part.type === "text") return { type: "text", text: part.text };
6275
+ if (part.type === "image") {
6276
+ const src = part.source;
6277
+ if (src.type === "base64") {
6278
+ return {
6279
+ type: "image",
6280
+ source: { type: "base64", media_type: src.mediaType, data: src.data }
6281
+ };
6282
+ }
6283
+ return {
6284
+ type: "image",
6285
+ source: { type: "url", url: src.url }
6286
+ };
6287
+ }
6288
+ return part;
6289
+ });
6290
+ }
6291
+ return msg;
6292
+ }
6293
+ toAnthropicTool(t) {
6294
+ return {
6295
+ name: t.name,
6296
+ description: t.description,
6297
+ input_schema: t.parameters
6298
+ };
6299
+ }
6300
+ async doFetch(body) {
6301
+ const url = `${this.baseURL}/v1/messages`;
6302
+ const res = await fetch(url, {
6303
+ method: "POST",
6304
+ headers: {
6305
+ "Content-Type": "application/json",
6306
+ "x-api-key": this.apiKey,
6307
+ "anthropic-version": this.anthropicVersion,
6308
+ ...this.defaultHeaders
6309
+ },
6310
+ body: JSON.stringify(body)
6311
+ });
6312
+ if (!res.ok) {
6313
+ const text = await res.text().catch(() => "");
6314
+ throw new ProviderError(
6315
+ `Anthropic API error ${res.status}: ${text}`,
6316
+ this.providerName,
6317
+ res.status,
6318
+ text
6319
+ );
6320
+ }
6321
+ return res;
6322
+ }
6323
+ parseResponse(json) {
6324
+ const textParts = [];
6325
+ const toolCalls = [];
6326
+ for (const block of json.content ?? []) {
6327
+ if (block.type === "text") {
6328
+ textParts.push(block.text);
6329
+ } else if (block.type === "tool_use") {
6330
+ toolCalls.push({
6331
+ id: block.id,
6332
+ name: block.name,
6333
+ arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input)
6334
+ });
6335
+ }
6336
+ }
6337
+ const usage = json.usage ?? {};
6338
+ const tokenUsage = {
6339
+ inputTokens: usage.input_tokens ?? 0,
6340
+ outputTokens: usage.output_tokens ?? 0,
6341
+ totalTokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
6342
+ cachedInputTokens: usage.cache_read_input_tokens ?? 0,
6343
+ cachedWriteTokens: usage.cache_creation_input_tokens ?? 0,
6344
+ cachedOutputTokens: 0,
6345
+ reasoningTokens: 0
6346
+ };
6347
+ return {
6348
+ id: json.id ?? "",
6349
+ model: json.model ?? "",
6350
+ content: textParts.join("") || null,
6351
+ toolCalls,
6352
+ usage: tokenUsage,
6353
+ finishReason: mapAnthropicStopReason(json.stop_reason),
6354
+ raw: json
6355
+ };
6356
+ }
6357
+ };
6358
+ function mapAnthropicStopReason(reason) {
6359
+ switch (reason) {
6360
+ case "end_turn":
6361
+ case "stop_sequence":
6362
+ return "stop";
6363
+ case "tool_use":
6364
+ return "tool_calls";
6365
+ case "max_tokens":
6366
+ return "length";
6367
+ default:
6368
+ return "stop";
6369
+ }
6370
+ }
6371
+ function safeJsonParse(str) {
6372
+ try {
6373
+ return JSON.parse(str);
6374
+ } catch {
6375
+ return {};
6376
+ }
6377
+ }
6378
+
6379
+ // src/providers/azure.ts
6380
+ var AzureProvider = class extends OpenAIProvider {
6381
+ constructor(config) {
6382
+ super({
6383
+ provider: "openai",
6384
+ apiKey: config.apiKey,
6385
+ defaultModel: config.defaultModel ?? config.deploymentName ?? "gpt-4o",
6386
+ defaultHeaders: config.defaultHeaders
6387
+ });
6388
+ this.providerName = "azure";
6389
+ this.endpoint = config.endpoint.replace(/\/$/, "");
6390
+ this.apiVersion = config.apiVersion ?? "2024-10-21";
6391
+ this.deploymentName = config.deploymentName;
6392
+ this.azureApiKey = config.apiKey;
6393
+ }
6394
+ async doFetch(body) {
6395
+ const deployment = this.deploymentName ?? body.model;
6396
+ if (!deployment) {
6397
+ throw new ProviderError("Azure requires a deploymentName or model", this.providerName);
6398
+ }
6399
+ const url = `${this.endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${this.apiVersion}`;
6400
+ const { model: _model, ...bodyWithoutModel } = body;
6401
+ const res = await fetch(url, {
6402
+ method: "POST",
6403
+ headers: {
6404
+ "Content-Type": "application/json",
6405
+ "api-key": this.azureApiKey,
6406
+ ...this.defaultHeaders
6407
+ },
6408
+ body: JSON.stringify(bodyWithoutModel)
6409
+ });
6410
+ if (!res.ok) {
6411
+ const text = await res.text().catch(() => "");
6412
+ throw new ProviderError(
6413
+ `Azure OpenAI API error ${res.status}: ${text}`,
6414
+ this.providerName,
6415
+ res.status,
6416
+ text
6417
+ );
6418
+ }
6419
+ return res;
6420
+ }
6421
+ };
6422
+
6423
+ // src/providers/openaiCompatible.ts
6424
+ var OpenAICompatibleProvider = class extends OpenAIProvider {
6425
+ constructor(config) {
6426
+ super({
6427
+ provider: "openai",
6428
+ apiKey: config.apiKey,
6429
+ baseURL: config.baseURL,
6430
+ defaultModel: config.defaultModel,
6431
+ defaultHeaders: config.defaultHeaders
6432
+ });
6433
+ this.providerName = "openai-compatible";
6434
+ }
6435
+ };
6436
+ function signRequest(req) {
6437
+ const now = /* @__PURE__ */ new Date();
6438
+ const dateStamp = toDateStamp(now);
6439
+ const amzDate = toAmzDate(now);
6440
+ const parsed = new URL(req.url);
6441
+ const host = parsed.host;
6442
+ const canonicalUri = parsed.pathname || "/";
6443
+ const canonicalQuerystring = sortQueryParams(parsed.searchParams);
6444
+ const headers = {
6445
+ ...req.headers,
6446
+ host,
6447
+ "x-amz-date": amzDate
6448
+ };
6449
+ if (req.credentials.sessionToken) {
6450
+ headers["x-amz-security-token"] = req.credentials.sessionToken;
6451
+ }
6452
+ const signedHeaderKeys = Object.keys(headers).map((k) => k.toLowerCase()).sort();
6453
+ const canonicalHeaders = signedHeaderKeys.map((k) => `${k}:${headers[Object.keys(headers).find((h) => h.toLowerCase() === k)]?.trim()}`).join("\n") + "\n";
6454
+ const signedHeaders = signedHeaderKeys.join(";");
6455
+ const payloadHash = sha256(req.body);
6456
+ const canonicalRequest = [
6457
+ req.method,
6458
+ canonicalUri,
6459
+ canonicalQuerystring,
6460
+ canonicalHeaders,
6461
+ signedHeaders,
6462
+ payloadHash
6463
+ ].join("\n");
6464
+ const credentialScope = `${dateStamp}/${req.region}/${req.service}/aws4_request`;
6465
+ const stringToSign = [
6466
+ "AWS4-HMAC-SHA256",
6467
+ amzDate,
6468
+ credentialScope,
6469
+ sha256(canonicalRequest)
6470
+ ].join("\n");
6471
+ const signingKey = getSignatureKey(
6472
+ req.credentials.secretAccessKey,
6473
+ dateStamp,
6474
+ req.region,
6475
+ req.service
6476
+ );
6477
+ const signature = hmacHex(signingKey, stringToSign);
6478
+ const authorization = `AWS4-HMAC-SHA256 Credential=${req.credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
6479
+ const result = {
6480
+ ...req.headers,
6481
+ "x-amz-date": amzDate,
6482
+ Authorization: authorization
6483
+ };
6484
+ if (req.credentials.sessionToken) {
6485
+ result["x-amz-security-token"] = req.credentials.sessionToken;
6486
+ }
6487
+ return result;
6488
+ }
6489
+ function toDateStamp(d) {
6490
+ return d.toISOString().replace(/[-:T]/g, "").slice(0, 8);
6491
+ }
6492
+ function toAmzDate(d) {
6493
+ return d.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
6494
+ }
6495
+ function sha256(data) {
6496
+ return createHash("sha256").update(data, "utf8").digest("hex");
6497
+ }
6498
+ function hmac(key, data) {
6499
+ return createHmac("sha256", key).update(data, "utf8").digest();
6500
+ }
6501
+ function hmacHex(key, data) {
6502
+ return createHmac("sha256", key).update(data, "utf8").digest("hex");
6503
+ }
6504
+ function getSignatureKey(secret, dateStamp, region, service) {
6505
+ const kDate = hmac(`AWS4${secret}`, dateStamp);
6506
+ const kRegion = hmac(kDate, region);
6507
+ const kService = hmac(kRegion, service);
6508
+ return hmac(kService, "aws4_request");
6509
+ }
6510
+ function sortQueryParams(params) {
6511
+ const sorted = [...params.entries()].sort(([a], [b]) => a.localeCompare(b));
6512
+ return sorted.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
6513
+ }
6514
+
6515
+ // src/providers/bedrock.ts
6516
+ var BedrockProvider = class extends BaseProvider {
6517
+ constructor(config) {
6518
+ super();
6519
+ this.providerName = "bedrock";
6520
+ this.region = config.region;
6521
+ this.defaultModel = config.defaultModel ?? "anthropic.claude-sonnet-4-20250514-v1:0";
6522
+ this.defaultHeaders = config.defaultHeaders ?? {};
6523
+ this.credentials = {
6524
+ accessKeyId: config.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? "",
6525
+ secretAccessKey: config.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? "",
6526
+ sessionToken: config.sessionToken ?? process.env.AWS_SESSION_TOKEN
6527
+ };
6528
+ if (!this.credentials.accessKeyId || !this.credentials.secretAccessKey) {
6529
+ throw new ProviderError(
6530
+ "AWS credentials required: provide accessKeyId/secretAccessKey or set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars",
6531
+ "bedrock"
6532
+ );
6533
+ }
6534
+ }
6535
+ async complete(request) {
6536
+ const modelId = request.model || this.defaultModel;
6537
+ const body = this.buildConverseBody(request);
6538
+ const json = await this.doFetch(modelId, body);
6539
+ return this.parseResponse(json, modelId);
6540
+ }
6541
+ async *completeStream(request) {
6542
+ const response = await this.complete(request);
6543
+ yield {
6544
+ id: response.id,
6545
+ model: response.model,
6546
+ delta: {
6547
+ content: response.content ?? void 0,
6548
+ toolCalls: response.toolCalls.length > 0 ? response.toolCalls : void 0
6549
+ },
6550
+ usage: response.usage,
6551
+ finishReason: response.finishReason
6552
+ };
6553
+ }
6554
+ // ─── Internals ───────────────────────────────────────────────────────────
6555
+ buildConverseBody(request) {
6556
+ const { system, messages } = this.splitSystemMessages(request.messages);
6557
+ const body = {
6558
+ messages: messages.map((m) => this.toBedrockMessage(m))
6559
+ };
6560
+ if (system.length > 0) {
6561
+ body.system = system.map((s) => ({ text: s }));
6562
+ }
6563
+ const inferenceConfig = {};
6564
+ if (request.maxTokens != null) inferenceConfig.maxTokens = request.maxTokens;
6565
+ if (request.temperature != null) inferenceConfig.temperature = request.temperature;
6566
+ if (request.topP != null) inferenceConfig.topP = request.topP;
6567
+ if (request.stop) inferenceConfig.stopSequences = request.stop;
6568
+ if (Object.keys(inferenceConfig).length > 0) body.inferenceConfig = inferenceConfig;
6569
+ if (request.tools?.length) {
6570
+ body.toolConfig = {
6571
+ tools: request.tools.map((t) => this.toBedrockTool(t))
6572
+ };
6573
+ if (request.toolChoice != null) {
6574
+ if (request.toolChoice === "auto") {
6575
+ body.toolConfig.toolChoice = { auto: {} };
6576
+ } else if (request.toolChoice === "required") {
6577
+ body.toolConfig.toolChoice = { any: {} };
6578
+ } else if (typeof request.toolChoice === "object") {
6579
+ body.toolConfig.toolChoice = { tool: { name: request.toolChoice.name } };
6580
+ }
6581
+ }
6582
+ }
6583
+ if (request.extra) {
6584
+ Object.assign(body, request.extra);
6585
+ }
6586
+ return body;
6587
+ }
6588
+ splitSystemMessages(messages) {
6589
+ const system = [];
6590
+ const rest = [];
6591
+ for (const m of messages) {
6592
+ if (m.role === "system") {
6593
+ const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
6594
+ if (text) system.push(text);
6595
+ } else {
6596
+ rest.push(m);
6597
+ }
6598
+ }
6599
+ return { system, messages: rest };
6600
+ }
6601
+ toBedrockMessage(m) {
6602
+ if (m.role === "tool" && m.toolCallId) {
6603
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
6604
+ return {
6605
+ role: "user",
6606
+ content: [
6607
+ {
6608
+ toolResult: {
6609
+ toolUseId: m.toolCallId,
6610
+ content: [{ text }],
6611
+ status: "success"
6612
+ }
6613
+ }
6614
+ ]
6615
+ };
6616
+ }
6617
+ if (m.role === "assistant" && m.toolCalls?.length) {
6618
+ const content2 = [];
6619
+ if (typeof m.content === "string" && m.content) {
6620
+ content2.push({ text: m.content });
6621
+ }
6622
+ for (const tc of m.toolCalls) {
6623
+ content2.push({
6624
+ toolUse: {
6625
+ toolUseId: tc.id,
6626
+ name: tc.name,
6627
+ input: safeJsonParse2(tc.arguments)
6628
+ }
6629
+ });
6630
+ }
6631
+ return { role: "assistant", content: content2 };
6632
+ }
6633
+ const content = [];
6634
+ if (typeof m.content === "string") {
6635
+ content.push({ text: m.content });
6636
+ } else if (Array.isArray(m.content)) {
6637
+ for (const part of m.content) {
6638
+ if (part.type === "text") {
6639
+ content.push({ text: part.text });
6640
+ } else if (part.type === "image") {
6641
+ const src = part.source;
6642
+ if (src.type === "base64") {
6643
+ content.push({
6644
+ image: {
6645
+ format: mediaTypeToFormat(src.mediaType),
6646
+ source: { bytes: src.data }
6647
+ }
6648
+ });
6649
+ }
6650
+ }
6651
+ }
6652
+ }
6653
+ return { role: m.role === "user" ? "user" : "assistant", content };
6654
+ }
6655
+ toBedrockTool(t) {
6656
+ return {
6657
+ toolSpec: {
6658
+ name: t.name,
6659
+ description: t.description,
6660
+ inputSchema: { json: t.parameters }
6661
+ }
6662
+ };
6663
+ }
6664
+ async doFetch(modelId, body) {
6665
+ const host = `bedrock-runtime.${this.region}.amazonaws.com`;
6666
+ const url = `https://${host}/model/${encodeURIComponent(modelId)}/converse`;
6667
+ const bodyStr = JSON.stringify(body);
6668
+ const headers = signRequest({
6669
+ method: "POST",
6670
+ url,
6671
+ headers: {
6672
+ "Content-Type": "application/json",
6673
+ Accept: "application/json",
6674
+ ...this.defaultHeaders
6675
+ },
6676
+ body: bodyStr,
6677
+ region: this.region,
6678
+ service: "bedrock",
6679
+ credentials: this.credentials
6680
+ });
6681
+ const res = await fetch(url, {
6682
+ method: "POST",
6683
+ headers,
6684
+ body: bodyStr
6685
+ });
6686
+ if (!res.ok) {
6687
+ const text = await res.text().catch(() => "");
6688
+ throw new ProviderError(
6689
+ `Bedrock API error ${res.status}: ${text}`,
6690
+ this.providerName,
6691
+ res.status,
6692
+ text
6693
+ );
6694
+ }
6695
+ return res.json();
6696
+ }
6697
+ parseResponse(json, modelId) {
6698
+ const output = json.output?.message;
6699
+ const textParts = [];
6700
+ const toolCalls = [];
6701
+ for (const block of output?.content ?? []) {
6702
+ if (block.text) {
6703
+ textParts.push(block.text);
6704
+ } else if (block.toolUse) {
6705
+ toolCalls.push({
6706
+ id: block.toolUse.toolUseId,
6707
+ name: block.toolUse.name,
6708
+ arguments: typeof block.toolUse.input === "string" ? block.toolUse.input : JSON.stringify(block.toolUse.input ?? {})
6709
+ });
6710
+ }
6711
+ }
6712
+ const usage = json.usage ?? {};
6713
+ const tokenUsage = {
6714
+ inputTokens: usage.inputTokens ?? 0,
6715
+ outputTokens: usage.outputTokens ?? 0,
6716
+ totalTokens: usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0),
6717
+ cachedInputTokens: usage.cacheReadInputTokenCount ?? 0,
6718
+ cachedWriteTokens: usage.cacheWriteInputTokenCount ?? 0,
6719
+ cachedOutputTokens: 0,
6720
+ reasoningTokens: 0
6721
+ };
6722
+ return {
6723
+ id: json.requestId ?? json.$metadata?.requestId ?? "",
6724
+ model: modelId,
6725
+ content: textParts.join("") || null,
6726
+ toolCalls,
6727
+ usage: tokenUsage,
6728
+ finishReason: mapBedrockStopReason(json.stopReason),
6729
+ raw: json
6730
+ };
6731
+ }
6732
+ };
6733
+ function mapBedrockStopReason(reason) {
6734
+ switch (reason) {
6735
+ case "end_turn":
6736
+ case "stop_sequence":
6737
+ return "stop";
6738
+ case "tool_use":
6739
+ return "tool_calls";
6740
+ case "max_tokens":
6741
+ return "length";
6742
+ case "content_filtered":
6743
+ case "guardrail":
6744
+ return "content_filter";
6745
+ default:
6746
+ return "stop";
6747
+ }
6748
+ }
6749
+ function mediaTypeToFormat(mediaType) {
6750
+ const map = {
6751
+ "image/jpeg": "jpeg",
6752
+ "image/png": "png",
6753
+ "image/gif": "gif",
6754
+ "image/webp": "webp"
6755
+ };
6756
+ return map[mediaType] ?? "png";
6757
+ }
6758
+ function safeJsonParse2(str) {
6759
+ try {
6760
+ return JSON.parse(str);
6761
+ } catch {
6762
+ return {};
6763
+ }
6764
+ }
6765
+
6766
+ // src/providers/vertex.ts
6767
+ var VertexProvider = class extends BaseProvider {
6768
+ constructor(config) {
6769
+ super();
6770
+ this.providerName = "vertex";
6771
+ this.tokenExpiry = 0;
6772
+ this.projectId = config.projectId;
6773
+ this.location = config.location ?? "us-central1";
6774
+ this.defaultModel = config.defaultModel ?? "gemini-2.0-flash";
6775
+ this.defaultHeaders = config.defaultHeaders ?? {};
6776
+ if (config.accessToken) {
6777
+ this.accessToken = config.accessToken;
6778
+ this.tokenExpiry = Date.now() + 36e5;
6779
+ }
6780
+ if (config.serviceAccountJson) {
6781
+ this.serviceAccountJson = typeof config.serviceAccountJson === "string" ? JSON.parse(config.serviceAccountJson) : config.serviceAccountJson;
6782
+ }
6783
+ }
6784
+ async complete(request) {
6785
+ const modelId = request.model || this.defaultModel;
6786
+ const body = this.buildGeminiBody(request);
6787
+ const url = this.buildUrl(modelId, false);
6788
+ const res = await this.doFetch(url, body);
6789
+ const json = await res.json();
6790
+ return this.parseResponse(json, modelId);
6791
+ }
6792
+ async *completeStream(request) {
6793
+ const modelId = request.model || this.defaultModel;
6794
+ const body = this.buildGeminiBody(request);
6795
+ const url = this.buildUrl(modelId, true);
6796
+ const res = await this.doFetch(url, body);
6797
+ if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
6798
+ const contentType = res.headers.get("content-type") ?? "";
6799
+ if (contentType.includes("text/event-stream")) {
6800
+ for await (const event of parseSSEStream(res.body)) {
6801
+ if (event.data === "[DONE]") break;
6802
+ let chunk;
6803
+ try {
6804
+ chunk = JSON.parse(event.data);
6805
+ } catch {
6806
+ continue;
6807
+ }
6808
+ yield this.parseStreamChunk(chunk, modelId);
6809
+ }
6810
+ } else {
6811
+ const reader = res.body.getReader();
6812
+ const decoder = new TextDecoder();
6813
+ let buffer = "";
6814
+ try {
6815
+ while (true) {
6816
+ const { done, value } = await reader.read();
6817
+ if (done) break;
6818
+ buffer += decoder.decode(value, { stream: true });
6819
+ const lines = buffer.split("\n");
6820
+ buffer = lines.pop() ?? "";
6821
+ for (const line of lines) {
6822
+ const trimmed = line.trim().replace(/^,/, "").replace(/^\[/, "").replace(/\]$/, "");
6823
+ if (!trimmed || trimmed === "[" || trimmed === "]") continue;
6824
+ try {
6825
+ const chunk = JSON.parse(trimmed);
6826
+ yield this.parseStreamChunk(chunk, modelId);
6827
+ } catch {
6828
+ buffer = line + "\n" + buffer;
6829
+ break;
6830
+ }
6831
+ }
6832
+ }
6833
+ const remaining = buffer.trim().replace(/^,/, "").replace(/^\[/, "").replace(/\]$/, "");
6834
+ if (remaining && remaining !== "[" && remaining !== "]") {
6835
+ try {
6836
+ const chunk = JSON.parse(remaining);
6837
+ yield this.parseStreamChunk(chunk, modelId);
6838
+ } catch {
6839
+ }
6840
+ }
6841
+ } finally {
6842
+ reader.releaseLock();
6843
+ }
6844
+ }
6845
+ }
6846
+ // ─── Internals ───────────────────────────────────────────────────────────
6847
+ buildUrl(modelId, stream) {
6848
+ const base = `https://${this.location}-aiplatform.googleapis.com/v1`;
6849
+ const path2 = `projects/${this.projectId}/locations/${this.location}/publishers/google/models/${modelId}`;
6850
+ const action = stream ? "streamGenerateContent?alt=sse" : "generateContent";
6851
+ return `${base}/${path2}:${action}`;
6852
+ }
6853
+ buildGeminiBody(request) {
6854
+ const { system, contents } = this.buildContents(request.messages);
6855
+ const body = { contents };
6856
+ if (system) {
6857
+ body.systemInstruction = { parts: [{ text: system }] };
6858
+ }
6859
+ const generationConfig = {};
6860
+ if (request.maxTokens != null) generationConfig.maxOutputTokens = request.maxTokens;
6861
+ if (request.temperature != null) generationConfig.temperature = request.temperature;
6862
+ if (request.topP != null) generationConfig.topP = request.topP;
6863
+ if (request.stop) generationConfig.stopSequences = request.stop;
6864
+ if (request.responseFormat) {
6865
+ if (request.responseFormat.type === "json_schema" || request.responseFormat.type === "json_object") {
6866
+ generationConfig.responseMimeType = "application/json";
6867
+ if (request.responseFormat.schema) {
6868
+ generationConfig.responseSchema = request.responseFormat.schema;
6869
+ }
6870
+ }
6871
+ }
6872
+ if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
6873
+ if (request.tools?.length) {
6874
+ body.tools = [
6875
+ {
6876
+ functionDeclarations: request.tools.map((t) => this.toGeminiTool(t))
6877
+ }
6878
+ ];
6879
+ if (request.toolChoice != null) {
6880
+ const mode = request.toolChoice === "auto" ? "AUTO" : request.toolChoice === "required" ? "ANY" : request.toolChoice === "none" ? "NONE" : "AUTO";
6881
+ body.toolConfig = { functionCallingConfig: { mode } };
6882
+ }
6883
+ }
6884
+ if (request.extra) {
6885
+ Object.assign(body, request.extra);
6886
+ }
6887
+ return body;
6888
+ }
6889
+ buildContents(messages) {
6890
+ const systemParts = [];
6891
+ const contents = [];
6892
+ for (const m of messages) {
6893
+ if (m.role === "system") {
6894
+ const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
6895
+ if (text) systemParts.push(text);
6896
+ continue;
6897
+ }
6898
+ contents.push(this.toGeminiContent(m));
6899
+ }
6900
+ return {
6901
+ system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
6902
+ contents
6903
+ };
6904
+ }
6905
+ toGeminiContent(m) {
6906
+ if (m.role === "tool" && m.toolCallId) {
6907
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
6908
+ let response;
6909
+ try {
6910
+ response = JSON.parse(text);
6911
+ } catch {
6912
+ response = { result: text };
6913
+ }
6914
+ return {
6915
+ role: "user",
6916
+ parts: [
6917
+ {
6918
+ functionResponse: {
6919
+ name: m.name ?? m.toolCallId,
6920
+ response
6921
+ }
6922
+ }
6923
+ ]
6924
+ };
6925
+ }
6926
+ if (m.role === "assistant" && m.toolCalls?.length) {
6927
+ const parts2 = [];
6928
+ if (typeof m.content === "string" && m.content) {
6929
+ parts2.push({ text: m.content });
6930
+ }
6931
+ for (const tc of m.toolCalls) {
6932
+ parts2.push({
6933
+ functionCall: {
6934
+ name: tc.name,
6935
+ args: safeJsonParse3(tc.arguments)
6936
+ }
6937
+ });
6938
+ }
6939
+ return { role: "model", parts: parts2 };
6940
+ }
6941
+ const role = m.role === "assistant" ? "model" : "user";
6942
+ const parts = [];
6943
+ if (typeof m.content === "string") {
6944
+ parts.push({ text: m.content });
6945
+ } else if (Array.isArray(m.content)) {
6946
+ for (const part of m.content) {
6947
+ if (part.type === "text") {
6948
+ parts.push({ text: part.text });
6949
+ } else if (part.type === "image") {
6950
+ const src = part.source;
6951
+ if (src.type === "base64") {
6952
+ parts.push({
6953
+ inlineData: { mimeType: src.mediaType, data: src.data }
6954
+ });
6955
+ } else {
6956
+ parts.push({
6957
+ fileData: { mimeType: "image/jpeg", fileUri: src.url }
6958
+ });
6959
+ }
6960
+ }
6961
+ }
6962
+ }
6963
+ return { role, parts };
6964
+ }
6965
+ toGeminiTool(t) {
6966
+ return {
6967
+ name: t.name,
6968
+ description: t.description,
6969
+ parameters: t.parameters
6970
+ };
6971
+ }
6972
+ async getAccessToken() {
6973
+ if (this.accessToken && Date.now() < this.tokenExpiry) {
6974
+ return this.accessToken;
6975
+ }
6976
+ if (this.serviceAccountJson) {
6977
+ const token = await this.getTokenFromServiceAccount(this.serviceAccountJson);
6978
+ this.accessToken = token;
6979
+ this.tokenExpiry = Date.now() + 35e5;
6980
+ return token;
6981
+ }
6982
+ try {
6983
+ const { execSync } = await import('child_process');
6984
+ const token = execSync("gcloud auth print-access-token", { encoding: "utf8" }).trim();
6985
+ this.accessToken = token;
6986
+ this.tokenExpiry = Date.now() + 35e5;
6987
+ return token;
6988
+ } catch {
6989
+ throw new ProviderError(
6990
+ "No access token available. Provide accessToken, serviceAccountJson, or ensure gcloud CLI is authenticated.",
6991
+ this.providerName
6992
+ );
6993
+ }
6994
+ }
6995
+ async getTokenFromServiceAccount(sa) {
6996
+ const { createSign } = await import('crypto');
6997
+ const now = Math.floor(Date.now() / 1e3);
6998
+ const header = { alg: "RS256", typ: "JWT" };
6999
+ const claims = {
7000
+ iss: sa.client_email,
7001
+ scope: "https://www.googleapis.com/auth/cloud-platform",
7002
+ aud: "https://oauth2.googleapis.com/token",
7003
+ iat: now,
7004
+ exp: now + 3600
7005
+ };
7006
+ const segments = [
7007
+ base64url(JSON.stringify(header)),
7008
+ base64url(JSON.stringify(claims))
7009
+ ];
7010
+ const signingInput = segments.join(".");
7011
+ const sign = createSign("RSA-SHA256");
7012
+ sign.update(signingInput);
7013
+ const signature = sign.sign(sa.private_key, "base64url");
7014
+ const jwt = `${signingInput}.${signature}`;
7015
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
7016
+ method: "POST",
7017
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
7018
+ body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`
7019
+ });
7020
+ if (!tokenRes.ok) {
7021
+ const text = await tokenRes.text().catch(() => "");
7022
+ throw new ProviderError(`Failed to get Vertex access token: ${text}`, this.providerName);
7023
+ }
7024
+ const tokenJson = await tokenRes.json();
7025
+ return tokenJson.access_token;
7026
+ }
7027
+ async doFetch(url, body) {
7028
+ const token = await this.getAccessToken();
7029
+ const res = await fetch(url, {
7030
+ method: "POST",
7031
+ headers: {
7032
+ "Content-Type": "application/json",
7033
+ Authorization: `Bearer ${token}`,
7034
+ ...this.defaultHeaders
7035
+ },
7036
+ body: JSON.stringify(body)
7037
+ });
7038
+ if (!res.ok) {
7039
+ const text = await res.text().catch(() => "");
7040
+ throw new ProviderError(
7041
+ `Vertex AI API error ${res.status}: ${text}`,
7042
+ this.providerName,
7043
+ res.status,
7044
+ text
7045
+ );
7046
+ }
7047
+ return res;
7048
+ }
7049
+ parseResponse(json, modelId) {
7050
+ const candidate = json.candidates?.[0];
7051
+ const parts = candidate?.content?.parts ?? [];
7052
+ const textParts = [];
7053
+ const toolCalls = [];
7054
+ let toolCallIdx = 0;
7055
+ for (const part of parts) {
7056
+ if (part.text) {
7057
+ textParts.push(part.text);
7058
+ } else if (part.functionCall) {
7059
+ toolCalls.push({
7060
+ id: `call_${toolCallIdx++}`,
7061
+ name: part.functionCall.name,
7062
+ arguments: JSON.stringify(part.functionCall.args ?? {})
7063
+ });
7064
+ }
7065
+ }
7066
+ const usageMeta = json.usageMetadata ?? {};
7067
+ const tokenUsage = {
7068
+ inputTokens: usageMeta.promptTokenCount ?? 0,
7069
+ outputTokens: usageMeta.candidatesTokenCount ?? 0,
7070
+ totalTokens: usageMeta.totalTokenCount ?? 0,
7071
+ cachedInputTokens: usageMeta.cachedContentTokenCount ?? 0,
7072
+ cachedWriteTokens: 0,
7073
+ cachedOutputTokens: 0,
7074
+ reasoningTokens: usageMeta.thoughtsTokenCount ?? 0
7075
+ };
7076
+ return {
7077
+ id: json.responseId ?? "",
7078
+ model: modelId,
7079
+ content: textParts.join("") || null,
7080
+ toolCalls,
7081
+ usage: tokenUsage,
7082
+ finishReason: mapGeminiFinishReason(candidate?.finishReason),
7083
+ raw: json
7084
+ };
7085
+ }
7086
+ parseStreamChunk(chunk, modelId) {
7087
+ const candidate = chunk.candidates?.[0];
7088
+ const parts = candidate?.content?.parts ?? [];
7089
+ const delta = {};
7090
+ for (const part of parts) {
7091
+ if (part.text) {
7092
+ delta.content = (delta.content ?? "") + part.text;
7093
+ } else if (part.functionCall) {
7094
+ if (!delta.toolCalls) delta.toolCalls = [];
7095
+ delta.toolCalls.push({
7096
+ id: `call_${delta.toolCalls.length}`,
7097
+ name: part.functionCall.name,
7098
+ arguments: JSON.stringify(part.functionCall.args ?? {})
7099
+ });
7100
+ }
7101
+ }
7102
+ let usage;
7103
+ if (chunk.usageMetadata) {
7104
+ const u = chunk.usageMetadata;
7105
+ usage = {
7106
+ inputTokens: u.promptTokenCount ?? 0,
7107
+ outputTokens: u.candidatesTokenCount ?? 0,
7108
+ totalTokens: u.totalTokenCount ?? 0,
7109
+ cachedInputTokens: u.cachedContentTokenCount ?? 0,
7110
+ cachedWriteTokens: 0,
7111
+ cachedOutputTokens: 0,
7112
+ reasoningTokens: u.thoughtsTokenCount ?? 0
7113
+ };
7114
+ }
7115
+ return {
7116
+ id: chunk.responseId ?? "",
7117
+ model: modelId,
7118
+ delta,
7119
+ usage,
7120
+ finishReason: mapGeminiFinishReason(candidate?.finishReason)
7121
+ };
7122
+ }
7123
+ };
7124
+ function mapGeminiFinishReason(reason) {
7125
+ switch (reason) {
7126
+ case "STOP":
7127
+ return "stop";
7128
+ case "MAX_TOKENS":
7129
+ return "length";
7130
+ case "SAFETY":
7131
+ case "RECITATION":
7132
+ return "content_filter";
7133
+ case "TOOL_CALLS":
7134
+ return "tool_calls";
7135
+ default:
7136
+ return "stop";
7137
+ }
7138
+ }
7139
+ function base64url(str) {
7140
+ return Buffer.from(str).toString("base64url");
7141
+ }
7142
+ function safeJsonParse3(str) {
7143
+ try {
7144
+ return JSON.parse(str);
7145
+ } catch {
7146
+ return {};
7147
+ }
7148
+ }
7149
+ function fromNativeProvider(provider, options) {
7150
+ let boundTools = options?.__tools;
7151
+ const modelName = options?.model ?? provider.defaultModel ?? provider.providerName;
7152
+ const adapted = {
7153
+ modelName,
7154
+ capabilities: resolveCapabilities(provider.providerName),
7155
+ invoke: async (messages, _invokeOptions) => {
7156
+ const request = buildRequest(messages, boundTools, options, _invokeOptions);
7157
+ const response = await provider.complete(request);
7158
+ return toBaseChatMessage(response);
7159
+ },
7160
+ stream: async function* (messages, _invokeOptions) {
7161
+ const request = buildRequest(messages, boundTools, options, _invokeOptions);
7162
+ request.stream = true;
7163
+ let fullContent = "";
7164
+ let allToolCalls = [];
7165
+ let lastUsage;
7166
+ const toolCallBuffers = /* @__PURE__ */ new Map();
7167
+ for await (const chunk of provider.completeStream(request)) {
7168
+ if (chunk.delta.content) {
7169
+ fullContent += chunk.delta.content;
7170
+ yield chunk.delta.content;
7171
+ }
7172
+ if (chunk.delta.toolCalls) {
7173
+ for (const tc of chunk.delta.toolCalls) {
7174
+ if (tc.id) {
7175
+ const existing = toolCallBuffers.get(tc.id);
7176
+ if (existing) {
7177
+ if (tc.arguments) existing.arguments += tc.arguments;
7178
+ } else {
7179
+ toolCallBuffers.set(tc.id, {
7180
+ id: tc.id,
7181
+ name: tc.name ?? "",
7182
+ arguments: tc.arguments ?? ""
7183
+ });
7184
+ }
7185
+ }
7186
+ }
7187
+ }
7188
+ if (chunk.usage) lastUsage = chunk.usage;
7189
+ }
7190
+ allToolCalls = [...toolCallBuffers.values()];
7191
+ const finalMessage = assembleMessage(fullContent, allToolCalls, lastUsage);
7192
+ yield finalMessage;
7193
+ },
7194
+ bindTools: (tools, _bindOptions) => {
7195
+ const strict = _bindOptions?.strict ?? false;
7196
+ const toolDefs = tools.map((t) => toToolDefinition(t, strict));
7197
+ return fromNativeProvider(provider, { ...options, __tools: toolDefs });
7198
+ }
7199
+ };
7200
+ return adapted;
7201
+ function buildRequest(messages, tools, opts, invokeOptions) {
7202
+ const req = {
7203
+ model: opts?.model ?? modelName,
7204
+ messages: messages.map(toUnifiedMessage)
7205
+ };
7206
+ if (opts?.temperature != null) req.temperature = opts.temperature;
7207
+ if (opts?.maxTokens != null) req.maxTokens = opts.maxTokens;
7208
+ if (tools?.length) req.tools = tools;
7209
+ if (opts?.extra) req.extra = opts.extra;
7210
+ const rf = invokeOptions?.response_format;
7211
+ if (rf && typeof rf === "object" && rf.type) {
7212
+ if (rf.type === "json_schema" && rf.json_schema) {
7213
+ req.responseFormat = {
7214
+ type: "json_schema",
7215
+ schema: rf.json_schema.schema,
7216
+ name: rf.json_schema.name
7217
+ };
7218
+ } else if (rf.type === "json_object") {
7219
+ req.responseFormat = { type: "json_object" };
7220
+ } else if (rf.type === "text") {
7221
+ req.responseFormat = { type: "text" };
7222
+ }
7223
+ }
7224
+ if (!req.responseFormat) {
7225
+ const lastMsg = messages[messages.length - 1];
7226
+ if (lastMsg && lastMsg.__responseFormat) {
7227
+ req.responseFormat = lastMsg.__responseFormat;
7228
+ }
7229
+ }
7230
+ return req;
7231
+ }
7232
+ }
7233
+ function toUnifiedMessage(msg) {
7234
+ const unified = {
7235
+ role: msg.role,
7236
+ content: convertContent(msg.content)
7237
+ };
7238
+ if (msg.name) unified.name = msg.name;
7239
+ if (msg.tool_call_id) unified.toolCallId = msg.tool_call_id;
7240
+ if (msg.tool_calls?.length) {
7241
+ unified.toolCalls = msg.tool_calls.map((tc) => ({
7242
+ id: tc.id,
7243
+ name: tc.function?.name ?? tc.name ?? "",
7244
+ arguments: tc.function?.arguments ?? tc.arguments ?? "{}"
7245
+ }));
7246
+ }
7247
+ return unified;
7248
+ }
7249
+ function convertContent(content) {
7250
+ if (typeof content === "string") return content;
7251
+ if (!Array.isArray(content)) return String(content ?? "");
7252
+ return content.map((part) => {
7253
+ if (part.type === "text" || !part.type && part.text) {
7254
+ return { type: "text", text: part.text ?? part.content ?? "" };
7255
+ }
7256
+ if (part.type === "image_url") {
7257
+ const img = part.image_url;
7258
+ if (typeof img === "string" || img?.url?.startsWith("http")) {
7259
+ return {
7260
+ type: "image",
7261
+ source: { type: "url", url: typeof img === "string" ? img : img.url }
7262
+ };
7263
+ }
7264
+ if (img?.url?.startsWith("data:")) {
7265
+ const match = img.url.match(/^data:([^;]+);base64,(.+)$/);
7266
+ if (match) {
7267
+ return {
7268
+ type: "image",
7269
+ source: { type: "base64", mediaType: match[1], data: match[2] }
7270
+ };
7271
+ }
7272
+ }
7273
+ }
7274
+ return { type: "text", text: part.text ?? part.content ?? JSON.stringify(part) };
7275
+ });
7276
+ }
7277
+ function isZodSchema(obj) {
7278
+ return obj != null && typeof obj === "object" && typeof obj.safeParse === "function" && "_def" in obj;
7279
+ }
7280
+ function convertToJsonSchema(schema, strict = false) {
7281
+ if (!schema || typeof schema !== "object") {
7282
+ return { type: "object", properties: {}, required: [], additionalProperties: false };
7283
+ }
7284
+ if (isZodSchema(schema)) {
7285
+ const converted = zodToJsonSchema(schema, {
7286
+ $refStrategy: "none",
7287
+ ...strict ? { openaiStrictMode: true } : {}
7288
+ });
7289
+ delete converted["$schema"];
7290
+ if (!converted.type) converted.type = "object";
7291
+ return converted;
7292
+ }
7293
+ if (!schema.type && schema.properties) {
7294
+ return { type: "object", ...schema };
7295
+ }
7296
+ return schema;
7297
+ }
7298
+ function normalizeStrictSchema(schema) {
7299
+ if (!schema || typeof schema !== "object") return schema;
7300
+ const clone2 = { ...schema };
7301
+ if (clone2.type === "object" || clone2.properties) {
7302
+ if (!clone2.type) clone2.type = "object";
7303
+ if (clone2.properties && typeof clone2.properties === "object") {
7304
+ const normalized = {};
7305
+ for (const [k, v] of Object.entries(clone2.properties)) {
7306
+ normalized[k] = normalizeStrictSchema(v);
7307
+ }
7308
+ clone2.properties = normalized;
7309
+ clone2.required = Object.keys(normalized);
7310
+ } else {
7311
+ clone2.properties = {};
7312
+ clone2.required = [];
7313
+ }
7314
+ clone2.additionalProperties = false;
7315
+ }
7316
+ if (clone2.items) clone2.items = normalizeStrictSchema(clone2.items);
7317
+ for (const key of ["anyOf", "oneOf", "allOf"]) {
7318
+ if (Array.isArray(clone2[key])) {
7319
+ clone2[key] = clone2[key].map((s) => normalizeStrictSchema(s));
7320
+ }
7321
+ }
7322
+ if (clone2.definitions && typeof clone2.definitions === "object") {
7323
+ const nd = {};
7324
+ for (const [k, v] of Object.entries(clone2.definitions)) {
7325
+ nd[k] = normalizeStrictSchema(v);
7326
+ }
7327
+ clone2.definitions = nd;
7328
+ }
7329
+ if (clone2.$defs && typeof clone2.$defs === "object") {
7330
+ const nd = {};
7331
+ for (const [k, v] of Object.entries(clone2.$defs)) {
7332
+ nd[k] = normalizeStrictSchema(v);
7333
+ }
7334
+ clone2.$defs = nd;
7335
+ }
7336
+ return clone2;
7337
+ }
7338
+ function toToolDefinition(tool, strict) {
7339
+ let name;
7340
+ let description;
7341
+ let parameters;
7342
+ if (tool.type === "function" && tool.function) {
7343
+ name = tool.function.name;
7344
+ description = tool.function.description ?? "";
7345
+ parameters = tool.function.parameters ?? { type: "object", properties: {}, additionalProperties: false };
7346
+ } else {
7347
+ name = tool.name ?? "unknown";
7348
+ description = tool.description ?? "";
7349
+ const rawSchema = tool.schema ?? tool.parameters;
7350
+ parameters = convertToJsonSchema(rawSchema, strict);
7351
+ }
7352
+ if (strict) {
7353
+ parameters = normalizeStrictSchema(parameters);
7354
+ }
7355
+ return { name, description, parameters, ...strict ? { strict: true } : {} };
7356
+ }
7357
+ function toBaseChatMessage(response) {
7358
+ const msg = {
7359
+ role: "assistant",
7360
+ content: response.content ?? "",
7361
+ usage: {
7362
+ prompt_tokens: response.usage.inputTokens,
7363
+ completion_tokens: response.usage.outputTokens,
7364
+ total_tokens: response.usage.totalTokens,
7365
+ prompt_tokens_details: {
7366
+ cached_tokens: response.usage.cachedInputTokens
7367
+ },
7368
+ completion_tokens_details: {
7369
+ reasoning_tokens: response.usage.reasoningTokens
7370
+ }
7371
+ },
7372
+ response_metadata: {
7373
+ token_usage: {
7374
+ prompt_tokens: response.usage.inputTokens,
7375
+ completion_tokens: response.usage.outputTokens,
7376
+ total_tokens: response.usage.totalTokens,
7377
+ cached_tokens: response.usage.cachedInputTokens,
7378
+ cache_write_tokens: response.usage.cachedWriteTokens
7379
+ },
7380
+ finish_reason: response.finishReason,
7381
+ model_name: response.model
7382
+ }
7383
+ };
7384
+ if (response.toolCalls.length > 0) {
7385
+ msg.tool_calls = response.toolCalls.map((tc) => ({
7386
+ id: tc.id,
7387
+ type: "function",
7388
+ function: { name: tc.name, arguments: tc.arguments }
7389
+ }));
7390
+ }
7391
+ return msg;
7392
+ }
7393
+ function assembleMessage(content, toolCalls, usage) {
7394
+ const msg = {
7395
+ role: "assistant",
7396
+ content
7397
+ };
7398
+ if (usage) {
7399
+ msg.usage = {
7400
+ prompt_tokens: usage.inputTokens,
7401
+ completion_tokens: usage.outputTokens,
7402
+ total_tokens: usage.totalTokens,
7403
+ prompt_tokens_details: {
7404
+ cached_tokens: usage.cachedInputTokens
7405
+ },
7406
+ completion_tokens_details: {
7407
+ reasoning_tokens: usage.reasoningTokens
7408
+ }
7409
+ };
7410
+ msg.response_metadata = {
7411
+ token_usage: {
7412
+ prompt_tokens: usage.inputTokens,
7413
+ completion_tokens: usage.outputTokens,
7414
+ total_tokens: usage.totalTokens,
7415
+ cached_tokens: usage.cachedInputTokens,
7416
+ cache_write_tokens: usage.cachedWriteTokens
7417
+ }
7418
+ };
7419
+ }
7420
+ if (toolCalls.length > 0) {
7421
+ msg.tool_calls = toolCalls.map((tc) => ({
7422
+ id: tc.id,
7423
+ type: "function",
7424
+ function: { name: tc.name, arguments: tc.arguments }
7425
+ }));
7426
+ }
7427
+ return msg;
7428
+ }
7429
+ function resolveCapabilities(provider) {
7430
+ switch (provider) {
7431
+ case "openai":
7432
+ case "azure":
7433
+ case "openai-compatible":
7434
+ return {
7435
+ structuredOutput: "native",
7436
+ strictToolCalling: true,
7437
+ streaming: true,
7438
+ provider
7439
+ };
7440
+ case "anthropic":
7441
+ return {
7442
+ structuredOutput: "tool_based",
7443
+ strictToolCalling: false,
7444
+ streaming: true,
7445
+ provider: "anthropic"
7446
+ };
7447
+ case "bedrock":
7448
+ return {
7449
+ structuredOutput: "tool_based",
7450
+ strictToolCalling: false,
7451
+ streaming: false,
7452
+ // streaming uses binary protocol
7453
+ provider: "bedrock"
7454
+ };
7455
+ case "vertex":
7456
+ return {
7457
+ structuredOutput: "native",
7458
+ strictToolCalling: false,
7459
+ streaming: true,
7460
+ provider: "google"
7461
+ };
7462
+ default:
7463
+ return { provider };
7464
+ }
7465
+ }
7466
+
7467
+ // src/providers/index.ts
7468
+ function createProvider(config) {
7469
+ switch (config.provider) {
7470
+ case "openai":
7471
+ return new OpenAIProvider(config);
7472
+ case "anthropic":
7473
+ return new AnthropicProvider(config);
7474
+ case "azure":
7475
+ return new AzureProvider(config);
7476
+ case "openai-compatible":
7477
+ return new OpenAICompatibleProvider(config);
7478
+ case "bedrock":
7479
+ return new BedrockProvider(config);
7480
+ case "vertex":
7481
+ return new VertexProvider(config);
7482
+ default:
7483
+ throw new Error(`Unknown provider: ${config.provider}`);
7484
+ }
7485
+ }
7486
+
7487
+ export { AnthropicProvider, AzureProvider, BaseProvider, BedrockProvider, DEFAULT_PROFILE_CONFIGS, GuardrailPhase, InMemoryMemoryStore, NativeJsonSchemaStrategy, OpenAICompatibleProvider, OpenAIProvider, ProviderError, StructuredOutputManager, ToolBasedStrategy, VertexProvider, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createProvider, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, emptyUsage, evaluateGuardrails, fileSink, formatZodError, fromLangchainModel, fromLangchainTools, fromNativeProvider, generateSpanId, generateTraceId, getModelCapabilities, getNestedValue, getResolvedSmartConfig, httpSink, isSmartModel, jsonSchemaRule, normalizeSmartAgentOptions, otlpSink, readMemoryFacts, regexRule, renderMemoryFacts, resolveMemoryStore, resolveStrategy, resolveToolApprovalState, restoreSnapshot, runSmartAgentEvalHarness, startStreamingSession, traceSessionToOtlp, withTools, writeSummaryFactsToMemory };
5155
7488
  //# sourceMappingURL=index.mjs.map
5156
7489
  //# sourceMappingURL=index.mjs.map