@cognipeer/agent-sdk 0.4.9 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -5
- package/dist/index.cjs +2682 -528
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +503 -25
- package/dist/index.d.ts +503 -25
- package/dist/index.mjs +2669 -531
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
348
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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;
|
|
@@ -1727,235 +2080,134 @@ function createAgentCoreNode(opts) {
|
|
|
1727
2080
|
|
|
1728
2081
|
// src/smart/runtimeConfig.ts
|
|
1729
2082
|
var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
|
|
2083
|
+
var BASE_DEFAULTS = {
|
|
2084
|
+
limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
|
|
2085
|
+
summarization: {
|
|
2086
|
+
enable: true,
|
|
2087
|
+
maxTokens: 24e3,
|
|
2088
|
+
summaryTriggerTokens: 17e3,
|
|
2089
|
+
summaryPromptMaxTokens: 7e3,
|
|
2090
|
+
summaryCompressionRatioTarget: 0.35,
|
|
2091
|
+
summaryMode: "incremental",
|
|
2092
|
+
promptTemplate: "",
|
|
2093
|
+
toolFreeCall: true,
|
|
2094
|
+
integrityCheck: true
|
|
2095
|
+
},
|
|
2096
|
+
context: {
|
|
2097
|
+
policy: "hybrid",
|
|
2098
|
+
lastTurnsToKeep: 8,
|
|
2099
|
+
toolResponsePolicy: "summarize_archive",
|
|
2100
|
+
archiveLargeToolResponses: true,
|
|
2101
|
+
retrieveArchivedToolResponseOnDemand: true,
|
|
2102
|
+
budget: {
|
|
2103
|
+
systemReserveTokens: 1200,
|
|
2104
|
+
goalsReserveTokens: 1200,
|
|
2105
|
+
recentTurnsReserveTokens: 5200,
|
|
2106
|
+
toolResponseReserveTokens: 2800
|
|
2107
|
+
}
|
|
2108
|
+
},
|
|
2109
|
+
planning: {
|
|
2110
|
+
mode: "off",
|
|
2111
|
+
replanPolicy: "on_failure",
|
|
2112
|
+
everyNSteps: 0
|
|
2113
|
+
},
|
|
2114
|
+
memory: {
|
|
2115
|
+
provider: "inMemory",
|
|
2116
|
+
scope: "session",
|
|
2117
|
+
writePolicy: "auto_important",
|
|
2118
|
+
readPolicy: "hybrid"
|
|
2119
|
+
},
|
|
2120
|
+
delegation: {
|
|
2121
|
+
mode: "role_based",
|
|
2122
|
+
maxDelegationDepth: 2,
|
|
2123
|
+
maxChildCalls: 4,
|
|
2124
|
+
maxParallelChild: 2,
|
|
2125
|
+
childContextPolicy: "scoped",
|
|
2126
|
+
requireJsonOutputContract: true
|
|
2127
|
+
},
|
|
2128
|
+
toolResponses: {
|
|
2129
|
+
maxToolResponseChars: 3600,
|
|
2130
|
+
maxToolResponseTokens: 1200,
|
|
2131
|
+
defaultPolicy: "keep_structured",
|
|
2132
|
+
largeResponsePolicy: "summarize_archive",
|
|
2133
|
+
toolResponseRetentionByTool: {},
|
|
2134
|
+
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
2135
|
+
schemaValidation: "strict",
|
|
2136
|
+
retryOnSchemaError: true,
|
|
2137
|
+
fallbackPolicy: "keep_structured"
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
function buildProfile(overrides) {
|
|
2141
|
+
return {
|
|
2142
|
+
limits: { ...BASE_DEFAULTS.limits, ...overrides.limits },
|
|
2143
|
+
summarization: { ...BASE_DEFAULTS.summarization, ...overrides.summarization },
|
|
2144
|
+
context: {
|
|
2145
|
+
...BASE_DEFAULTS.context,
|
|
2146
|
+
...overrides.context,
|
|
2147
|
+
budget: { ...BASE_DEFAULTS.context.budget, ...overrides.context?.budget }
|
|
2148
|
+
},
|
|
2149
|
+
planning: { ...BASE_DEFAULTS.planning, ...overrides.planning },
|
|
2150
|
+
memory: { ...BASE_DEFAULTS.memory, ...overrides.memory },
|
|
2151
|
+
delegation: { ...BASE_DEFAULTS.delegation, ...overrides.delegation },
|
|
2152
|
+
toolResponses: {
|
|
2153
|
+
...BASE_DEFAULTS.toolResponses,
|
|
2154
|
+
...overrides.toolResponses,
|
|
2155
|
+
criticalTools: overrides.toolResponses?.criticalTools ?? [...DEFAULT_CRITICAL_TOOLS],
|
|
2156
|
+
toolResponseRetentionByTool: overrides.toolResponses?.toolResponseRetentionByTool ?? {}
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
1730
2160
|
var DEFAULT_PROFILE_CONFIGS = {
|
|
1731
|
-
fast: {
|
|
2161
|
+
fast: buildProfile({
|
|
1732
2162
|
limits: { maxToolCalls: 4, maxParallelTools: 1, maxContextTokens: 12e3 },
|
|
1733
2163
|
summarization: {
|
|
1734
|
-
enable: true,
|
|
1735
2164
|
maxTokens: 12e3,
|
|
1736
2165
|
summaryTriggerTokens: 9e3,
|
|
1737
2166
|
summaryPromptMaxTokens: 5e3,
|
|
1738
|
-
summaryCompressionRatioTarget: 0.4
|
|
1739
|
-
summaryMode: "incremental",
|
|
1740
|
-
promptTemplate: "",
|
|
1741
|
-
toolFreeCall: true,
|
|
1742
|
-
integrityCheck: true
|
|
2167
|
+
summaryCompressionRatioTarget: 0.4
|
|
1743
2168
|
},
|
|
1744
2169
|
context: {
|
|
1745
|
-
policy: "hybrid",
|
|
1746
2170
|
lastTurnsToKeep: 6,
|
|
1747
2171
|
toolResponsePolicy: "keep_structured",
|
|
1748
|
-
|
|
1749
|
-
retrieveArchivedToolResponseOnDemand: true,
|
|
1750
|
-
budget: {
|
|
1751
|
-
systemReserveTokens: 1e3,
|
|
1752
|
-
goalsReserveTokens: 900,
|
|
1753
|
-
recentTurnsReserveTokens: 3200,
|
|
1754
|
-
toolResponseReserveTokens: 1800
|
|
1755
|
-
}
|
|
1756
|
-
},
|
|
1757
|
-
planning: {
|
|
1758
|
-
mode: "off",
|
|
1759
|
-
replanPolicy: "on_failure",
|
|
1760
|
-
everyNSteps: 0
|
|
1761
|
-
},
|
|
1762
|
-
memory: {
|
|
1763
|
-
provider: "inMemory",
|
|
1764
|
-
scope: "session",
|
|
1765
|
-
writePolicy: "auto_important",
|
|
1766
|
-
readPolicy: "recent_only"
|
|
1767
|
-
},
|
|
1768
|
-
delegation: {
|
|
1769
|
-
mode: "off",
|
|
1770
|
-
maxDelegationDepth: 1,
|
|
1771
|
-
maxChildCalls: 2,
|
|
1772
|
-
maxParallelChild: 1,
|
|
1773
|
-
childContextPolicy: "minimal",
|
|
1774
|
-
requireJsonOutputContract: true
|
|
1775
|
-
},
|
|
1776
|
-
toolResponses: {
|
|
1777
|
-
maxToolResponseChars: 2400,
|
|
1778
|
-
maxToolResponseTokens: 800,
|
|
1779
|
-
defaultPolicy: "keep_structured",
|
|
1780
|
-
largeResponsePolicy: "summarize_archive",
|
|
1781
|
-
toolResponseRetentionByTool: {},
|
|
1782
|
-
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1783
|
-
schemaValidation: "strict",
|
|
1784
|
-
retryOnSchemaError: true,
|
|
1785
|
-
fallbackPolicy: "keep_structured"
|
|
1786
|
-
}
|
|
1787
|
-
},
|
|
1788
|
-
balanced: {
|
|
1789
|
-
limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
|
|
1790
|
-
summarization: {
|
|
1791
|
-
enable: true,
|
|
1792
|
-
maxTokens: 24e3,
|
|
1793
|
-
summaryTriggerTokens: 17e3,
|
|
1794
|
-
summaryPromptMaxTokens: 7e3,
|
|
1795
|
-
summaryCompressionRatioTarget: 0.35,
|
|
1796
|
-
summaryMode: "incremental",
|
|
1797
|
-
promptTemplate: "",
|
|
1798
|
-
toolFreeCall: true,
|
|
1799
|
-
integrityCheck: true
|
|
1800
|
-
},
|
|
1801
|
-
context: {
|
|
1802
|
-
policy: "hybrid",
|
|
1803
|
-
lastTurnsToKeep: 8,
|
|
1804
|
-
toolResponsePolicy: "summarize_archive",
|
|
1805
|
-
archiveLargeToolResponses: true,
|
|
1806
|
-
retrieveArchivedToolResponseOnDemand: true,
|
|
1807
|
-
budget: {
|
|
1808
|
-
systemReserveTokens: 1200,
|
|
1809
|
-
goalsReserveTokens: 1200,
|
|
1810
|
-
recentTurnsReserveTokens: 5200,
|
|
1811
|
-
toolResponseReserveTokens: 2800
|
|
1812
|
-
}
|
|
1813
|
-
},
|
|
1814
|
-
planning: {
|
|
1815
|
-
mode: "off",
|
|
1816
|
-
replanPolicy: "on_failure",
|
|
1817
|
-
everyNSteps: 0
|
|
2172
|
+
budget: { systemReserveTokens: 1e3, goalsReserveTokens: 900, recentTurnsReserveTokens: 3200, toolResponseReserveTokens: 1800 }
|
|
1818
2173
|
},
|
|
1819
|
-
memory: {
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
delegation: {
|
|
1826
|
-
mode: "role_based",
|
|
1827
|
-
maxDelegationDepth: 2,
|
|
1828
|
-
maxChildCalls: 4,
|
|
1829
|
-
maxParallelChild: 2,
|
|
1830
|
-
childContextPolicy: "scoped",
|
|
1831
|
-
requireJsonOutputContract: true
|
|
1832
|
-
},
|
|
1833
|
-
toolResponses: {
|
|
1834
|
-
maxToolResponseChars: 3600,
|
|
1835
|
-
maxToolResponseTokens: 1200,
|
|
1836
|
-
defaultPolicy: "keep_structured",
|
|
1837
|
-
largeResponsePolicy: "summarize_archive",
|
|
1838
|
-
toolResponseRetentionByTool: {},
|
|
1839
|
-
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1840
|
-
schemaValidation: "strict",
|
|
1841
|
-
retryOnSchemaError: true,
|
|
1842
|
-
fallbackPolicy: "keep_structured"
|
|
1843
|
-
}
|
|
1844
|
-
},
|
|
1845
|
-
deep: {
|
|
2174
|
+
memory: { readPolicy: "recent_only" },
|
|
2175
|
+
delegation: { mode: "off", maxDelegationDepth: 1, maxChildCalls: 2, maxParallelChild: 1, childContextPolicy: "minimal" },
|
|
2176
|
+
toolResponses: { maxToolResponseChars: 2400, maxToolResponseTokens: 800 }
|
|
2177
|
+
}),
|
|
2178
|
+
balanced: buildProfile({}),
|
|
2179
|
+
deep: buildProfile({
|
|
1846
2180
|
limits: { maxToolCalls: 14, maxParallelTools: 3, maxContextTokens: 42e3 },
|
|
1847
2181
|
summarization: {
|
|
1848
|
-
enable: true,
|
|
1849
2182
|
maxTokens: 42e3,
|
|
1850
2183
|
summaryTriggerTokens: 3e4,
|
|
1851
2184
|
summaryPromptMaxTokens: 9e3,
|
|
1852
|
-
summaryCompressionRatioTarget: 0.3
|
|
1853
|
-
summaryMode: "incremental",
|
|
1854
|
-
promptTemplate: "",
|
|
1855
|
-
toolFreeCall: true,
|
|
1856
|
-
integrityCheck: true
|
|
2185
|
+
summaryCompressionRatioTarget: 0.3
|
|
1857
2186
|
},
|
|
1858
2187
|
context: {
|
|
1859
|
-
policy: "hybrid",
|
|
1860
2188
|
lastTurnsToKeep: 12,
|
|
1861
|
-
|
|
1862
|
-
archiveLargeToolResponses: true,
|
|
1863
|
-
retrieveArchivedToolResponseOnDemand: true,
|
|
1864
|
-
budget: {
|
|
1865
|
-
systemReserveTokens: 1400,
|
|
1866
|
-
goalsReserveTokens: 1600,
|
|
1867
|
-
recentTurnsReserveTokens: 7e3,
|
|
1868
|
-
toolResponseReserveTokens: 3600
|
|
1869
|
-
}
|
|
1870
|
-
},
|
|
1871
|
-
planning: {
|
|
1872
|
-
mode: "off",
|
|
1873
|
-
replanPolicy: "on_failure",
|
|
1874
|
-
everyNSteps: 0
|
|
1875
|
-
},
|
|
1876
|
-
memory: {
|
|
1877
|
-
provider: "inMemory",
|
|
1878
|
-
scope: "workspace",
|
|
1879
|
-
writePolicy: "auto_important",
|
|
1880
|
-
readPolicy: "hybrid"
|
|
1881
|
-
},
|
|
1882
|
-
delegation: {
|
|
1883
|
-
mode: "role_based",
|
|
1884
|
-
maxDelegationDepth: 3,
|
|
1885
|
-
maxChildCalls: 6,
|
|
1886
|
-
maxParallelChild: 2,
|
|
1887
|
-
childContextPolicy: "scoped",
|
|
1888
|
-
requireJsonOutputContract: true
|
|
2189
|
+
budget: { systemReserveTokens: 1400, goalsReserveTokens: 1600, recentTurnsReserveTokens: 7e3, toolResponseReserveTokens: 3600 }
|
|
1889
2190
|
},
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
toolResponseRetentionByTool: {},
|
|
1896
|
-
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1897
|
-
schemaValidation: "strict",
|
|
1898
|
-
retryOnSchemaError: true,
|
|
1899
|
-
fallbackPolicy: "keep_structured"
|
|
1900
|
-
}
|
|
1901
|
-
},
|
|
1902
|
-
research: {
|
|
2191
|
+
memory: { scope: "workspace" },
|
|
2192
|
+
delegation: { maxDelegationDepth: 3, maxChildCalls: 6 },
|
|
2193
|
+
toolResponses: { maxToolResponseChars: 5400, maxToolResponseTokens: 1800 }
|
|
2194
|
+
}),
|
|
2195
|
+
research: buildProfile({
|
|
1903
2196
|
limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
|
|
1904
2197
|
summarization: {
|
|
1905
|
-
enable: true,
|
|
1906
2198
|
maxTokens: 56e3,
|
|
1907
2199
|
summaryTriggerTokens: 42e3,
|
|
1908
2200
|
summaryPromptMaxTokens: 1e4,
|
|
1909
|
-
summaryCompressionRatioTarget: 0.28
|
|
1910
|
-
summaryMode: "incremental",
|
|
1911
|
-
promptTemplate: "",
|
|
1912
|
-
toolFreeCall: true,
|
|
1913
|
-
integrityCheck: true
|
|
2201
|
+
summaryCompressionRatioTarget: 0.28
|
|
1914
2202
|
},
|
|
1915
2203
|
context: {
|
|
1916
|
-
policy: "hybrid",
|
|
1917
2204
|
lastTurnsToKeep: 20,
|
|
1918
|
-
|
|
1919
|
-
archiveLargeToolResponses: true,
|
|
1920
|
-
retrieveArchivedToolResponseOnDemand: true,
|
|
1921
|
-
budget: {
|
|
1922
|
-
systemReserveTokens: 1600,
|
|
1923
|
-
goalsReserveTokens: 1800,
|
|
1924
|
-
recentTurnsReserveTokens: 9e3,
|
|
1925
|
-
toolResponseReserveTokens: 4800
|
|
1926
|
-
}
|
|
1927
|
-
},
|
|
1928
|
-
planning: {
|
|
1929
|
-
mode: "off",
|
|
1930
|
-
replanPolicy: "on_failure",
|
|
1931
|
-
everyNSteps: 0
|
|
1932
|
-
},
|
|
1933
|
-
memory: {
|
|
1934
|
-
provider: "inMemory",
|
|
1935
|
-
scope: "workspace",
|
|
1936
|
-
writePolicy: "auto_important",
|
|
1937
|
-
readPolicy: "hybrid"
|
|
2205
|
+
budget: { systemReserveTokens: 1600, goalsReserveTokens: 1800, recentTurnsReserveTokens: 9e3, toolResponseReserveTokens: 4800 }
|
|
1938
2206
|
},
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
maxParallelChild: 3,
|
|
1944
|
-
childContextPolicy: "full",
|
|
1945
|
-
requireJsonOutputContract: true
|
|
1946
|
-
},
|
|
1947
|
-
toolResponses: {
|
|
1948
|
-
maxToolResponseChars: 7200,
|
|
1949
|
-
maxToolResponseTokens: 2200,
|
|
1950
|
-
defaultPolicy: "keep_structured",
|
|
1951
|
-
largeResponsePolicy: "summarize_archive",
|
|
1952
|
-
toolResponseRetentionByTool: {},
|
|
1953
|
-
criticalTools: [...DEFAULT_CRITICAL_TOOLS],
|
|
1954
|
-
schemaValidation: "strict",
|
|
1955
|
-
retryOnSchemaError: true,
|
|
1956
|
-
fallbackPolicy: "keep_structured"
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
2207
|
+
memory: { scope: "workspace" },
|
|
2208
|
+
delegation: { mode: "automatic", maxDelegationDepth: 4, maxChildCalls: 8, maxParallelChild: 3, childContextPolicy: "full" },
|
|
2209
|
+
toolResponses: { maxToolResponseChars: 7200, maxToolResponseTokens: 2200 }
|
|
2210
|
+
})
|
|
1959
2211
|
};
|
|
1960
2212
|
function mergeLimits(base, override) {
|
|
1961
2213
|
return {
|
|
@@ -2098,19 +2350,55 @@ function countMessagesTokens(messages) {
|
|
|
2098
2350
|
return countApproxTokens(allText) + messages.length * 4;
|
|
2099
2351
|
}
|
|
2100
2352
|
|
|
2101
|
-
// src/
|
|
2102
|
-
function
|
|
2103
|
-
if (typeof value === "string") return value;
|
|
2353
|
+
// src/utils/content.ts
|
|
2354
|
+
function contentToString(content) {
|
|
2104
2355
|
try {
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2356
|
+
if (content == null) return "";
|
|
2357
|
+
if (typeof content === "string") return content;
|
|
2358
|
+
if (Array.isArray(content)) {
|
|
2359
|
+
return content.map((p) => {
|
|
2360
|
+
if (!p || typeof p !== "object") return String(p);
|
|
2361
|
+
if (p.type === "text") return String(p.text ?? "");
|
|
2362
|
+
if (p.type === "image_url") {
|
|
2363
|
+
const img = p.image_url;
|
|
2364
|
+
const url = typeof img === "string" ? img : img?.url;
|
|
2365
|
+
const detail = typeof img === "string" ? void 0 : img?.detail;
|
|
2366
|
+
return `[image:${url || "unknown"}${detail ? ` detail=${detail}` : ""}]`;
|
|
2367
|
+
}
|
|
2368
|
+
return JSON.stringify(p);
|
|
2369
|
+
}).join("\n");
|
|
2370
|
+
}
|
|
2371
|
+
return JSON.stringify(content);
|
|
2372
|
+
} catch {
|
|
2373
|
+
return String(content);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function mergeContentsToString(contents) {
|
|
2377
|
+
return contents.map((c) => contentToString(c)).join("\n");
|
|
2378
|
+
}
|
|
2379
|
+
function safeStringify(value, pretty = false) {
|
|
2380
|
+
if (value == null) return "";
|
|
2381
|
+
if (typeof value === "string") return value;
|
|
2382
|
+
try {
|
|
2383
|
+
return JSON.stringify(value, null, pretty ? 2 : void 0) ?? String(value);
|
|
2384
|
+
} catch {
|
|
2385
|
+
return String(value);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
function extractMessageText2(message) {
|
|
2389
|
+
if (!message) return "";
|
|
2390
|
+
const content = message.content;
|
|
2391
|
+
if (typeof content === "string") return content;
|
|
2392
|
+
if (Array.isArray(content)) {
|
|
2393
|
+
return content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join("");
|
|
2394
|
+
}
|
|
2395
|
+
return "";
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
// src/smart/toolResponses.ts
|
|
2399
|
+
function summarizeObject(value) {
|
|
2400
|
+
if (value == null) return "null";
|
|
2401
|
+
if (typeof value === "string") {
|
|
2114
2402
|
return value.length > 240 ? `${value.slice(0, 240)}...` : value;
|
|
2115
2403
|
}
|
|
2116
2404
|
if (Array.isArray(value)) {
|
|
@@ -2204,6 +2492,16 @@ function countSuccessfulToolExecutions(toolHistory, toolName) {
|
|
|
2204
2492
|
if (!Array.isArray(toolHistory)) return 0;
|
|
2205
2493
|
return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
|
|
2206
2494
|
}
|
|
2495
|
+
function normalizeToolCall(call) {
|
|
2496
|
+
if (!call || typeof call !== "object") return null;
|
|
2497
|
+
const name = call.name ?? call.tool ?? call.function?.name ?? call.function_call?.name;
|
|
2498
|
+
if (typeof name !== "string" || name.length === 0) return null;
|
|
2499
|
+
return {
|
|
2500
|
+
id: call.id,
|
|
2501
|
+
name,
|
|
2502
|
+
args: call.args ?? call.arguments ?? call.input ?? call.function?.arguments ?? call.function_call?.arguments
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2207
2505
|
function createToolsNode(initialTools, opts) {
|
|
2208
2506
|
return async (state) => {
|
|
2209
2507
|
const runtime = state.agent || {
|
|
@@ -2238,12 +2536,14 @@ function createToolsNode(initialTools, opts) {
|
|
|
2238
2536
|
if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
|
|
2239
2537
|
anyTool._stateRef.toolHistory = state.toolHistory;
|
|
2240
2538
|
anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
2539
|
+
anyTool._stateRef.pendingApprovals = pendingApprovals;
|
|
2540
|
+
anyTool._stateRef.ctx = state.ctx || (state.ctx = {});
|
|
2241
2541
|
anyTool._stateRef.__onEvent = onEvent;
|
|
2242
2542
|
}
|
|
2243
2543
|
}
|
|
2244
2544
|
const last = state.messages[state.messages.length - 1];
|
|
2245
2545
|
let toolCount = state.toolCallCount || 0;
|
|
2246
|
-
const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
|
|
2546
|
+
const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls.map((toolCall) => normalizeToolCall(toolCall)).filter((toolCall) => toolCall !== null) : [];
|
|
2247
2547
|
const toolHistory = state.toolHistory || [];
|
|
2248
2548
|
const toolHistoryArchived = state.toolHistoryArchived || [];
|
|
2249
2549
|
const remaining = Math.max(0, limits.maxToolCalls - toolCount);
|
|
@@ -2302,6 +2602,13 @@ function createToolsNode(initialTools, opts) {
|
|
|
2302
2602
|
args = validatedArgs.value;
|
|
2303
2603
|
}
|
|
2304
2604
|
const toolName = tool.name || tc.name;
|
|
2605
|
+
const toolStateRef = tool._stateRef && typeof tool._stateRef === "object" ? tool._stateRef : null;
|
|
2606
|
+
if (toolStateRef) {
|
|
2607
|
+
toolStateRef.pendingApprovals = pendingApprovals;
|
|
2608
|
+
toolStateRef.ctx = state.ctx || (state.ctx = {});
|
|
2609
|
+
toolStateRef.__currentToolCallId = toolCallId;
|
|
2610
|
+
delete toolStateRef.__awaitingApproval;
|
|
2611
|
+
}
|
|
2305
2612
|
const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
|
|
2306
2613
|
if (maxExecutionsPerRun !== null) {
|
|
2307
2614
|
const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
|
|
@@ -2414,6 +2721,22 @@ function createToolsNode(initialTools, opts) {
|
|
|
2414
2721
|
toolCount += 1;
|
|
2415
2722
|
return { status: "success", approval: approvalEntry };
|
|
2416
2723
|
}
|
|
2724
|
+
if (output && typeof output === "object" && output.__awaitingToolApproval) {
|
|
2725
|
+
if (toolStateRef?.ctx && typeof toolStateRef.ctx === "object") {
|
|
2726
|
+
state.ctx = toolStateRef.ctx;
|
|
2727
|
+
}
|
|
2728
|
+
const pendingApproval = pendingApprovals.find(
|
|
2729
|
+
(entry) => entry.toolCallId === output.toolCallId || entry.id === output.approvalId
|
|
2730
|
+
);
|
|
2731
|
+
if (!pendingApproval) {
|
|
2732
|
+
throw new Error(`Tool ${toolName} requested approval but no pending approval entry was recorded.`);
|
|
2733
|
+
}
|
|
2734
|
+
awaitingApproval = true;
|
|
2735
|
+
return {
|
|
2736
|
+
status: "awaiting_approval",
|
|
2737
|
+
approval: pendingApproval
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2417
2740
|
if (output && typeof output === "object" && output.__finalStructuredOutput) {
|
|
2418
2741
|
if (!state.ctx) state.ctx = {};
|
|
2419
2742
|
state.ctx.__structuredOutputParsed = output.data;
|
|
@@ -2836,7 +3159,7 @@ function resolveToolApprovalState(state, resolution) {
|
|
|
2836
3159
|
};
|
|
2837
3160
|
}
|
|
2838
3161
|
|
|
2839
|
-
// src/
|
|
3162
|
+
// src/utils/syntheticMessages.ts
|
|
2840
3163
|
function isSyntheticSummaryMessage(message) {
|
|
2841
3164
|
if (!message) return false;
|
|
2842
3165
|
if (message.role === "tool" && message.name === "summarize_context") {
|
|
@@ -2850,13 +3173,155 @@ function isSyntheticSummaryMessage(message) {
|
|
|
2850
3173
|
}
|
|
2851
3174
|
return false;
|
|
2852
3175
|
}
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
3176
|
+
|
|
3177
|
+
// src/structuredOutput/manager.ts
|
|
3178
|
+
var StructuredOutputManager = class {
|
|
3179
|
+
constructor(schema, strategy, config) {
|
|
3180
|
+
this.schema = schema;
|
|
3181
|
+
this.strategy = strategy;
|
|
3182
|
+
this.maxRetries = config?.maxRetries ?? 2;
|
|
3183
|
+
}
|
|
3184
|
+
// --- Setup Phase ---
|
|
3185
|
+
/**
|
|
3186
|
+
* Get the `response` tool for tool-based strategy.
|
|
3187
|
+
* Returns undefined for native strategy (no tool needed).
|
|
3188
|
+
*/
|
|
3189
|
+
getResponseTool() {
|
|
3190
|
+
if (this.strategy.kind !== "tool_based") return void 0;
|
|
3191
|
+
return this.strategy.createResponseTool(this.schema);
|
|
3192
|
+
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Get response_format config for native strategy.
|
|
3195
|
+
* Returns undefined for tool-based strategy.
|
|
3196
|
+
*/
|
|
3197
|
+
getResponseFormat() {
|
|
3198
|
+
if (this.strategy.kind !== "native") return void 0;
|
|
3199
|
+
return this.strategy.buildResponseFormat(this.schema);
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Build the system prompt hint that tells the model how to produce structured output.
|
|
3203
|
+
*/
|
|
3204
|
+
buildSystemPromptHint() {
|
|
3205
|
+
if (this.strategy.kind === "native") {
|
|
3206
|
+
return [
|
|
3207
|
+
"A structured output schema is active.",
|
|
3208
|
+
"Your response MUST be valid JSON matching the output schema.",
|
|
3209
|
+
"Do NOT wrap the JSON in code fences or add any text before/after it."
|
|
3210
|
+
].join("\n");
|
|
3211
|
+
}
|
|
3212
|
+
return [
|
|
3213
|
+
"A structured output schema is active.",
|
|
3214
|
+
"Do NOT output the final JSON directly as an assistant message.",
|
|
3215
|
+
"When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
|
|
3216
|
+
"Call it exactly once then STOP producing further assistant messages."
|
|
3217
|
+
].join("\n");
|
|
3218
|
+
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Build a nudge message for when the model didn't produce structured output.
|
|
3221
|
+
* Used by the agent loop when it needs to retry.
|
|
3222
|
+
*/
|
|
3223
|
+
buildNudgeMessage(isLastAttempt) {
|
|
3224
|
+
if (this.strategy.kind === "native") {
|
|
3225
|
+
return {
|
|
3226
|
+
role: "system",
|
|
3227
|
+
content: [
|
|
3228
|
+
"A structured output schema is active.",
|
|
3229
|
+
"You MUST respond with valid JSON matching the schema.",
|
|
3230
|
+
"Do NOT include any text outside the JSON object.",
|
|
3231
|
+
isLastAttempt ? "This is your FINAL attempt. Produce the JSON NOW." : ""
|
|
3232
|
+
].filter(Boolean).join("\n")
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
return {
|
|
3236
|
+
role: "system",
|
|
3237
|
+
content: [
|
|
3238
|
+
"A structured output schema is active.",
|
|
3239
|
+
"You MUST now call tool `response` with the final JSON object that matches the schema.",
|
|
3240
|
+
"Do not write the JSON in the assistant message.",
|
|
3241
|
+
"Call `response` exactly once, then stop.",
|
|
3242
|
+
isLastAttempt ? "Do NOT call any other tools. Produce the response NOW based on whatever information you have gathered so far." : ""
|
|
3243
|
+
].filter(Boolean).join("\n")
|
|
3244
|
+
};
|
|
2857
3245
|
}
|
|
2858
|
-
|
|
2859
|
-
|
|
3246
|
+
/**
|
|
3247
|
+
* Build a correction message when schema validation failed.
|
|
3248
|
+
*/
|
|
3249
|
+
buildCorrectionMessage(error, _previousAttempt) {
|
|
3250
|
+
if (error.fieldErrors && error.fieldErrors.length > 0) {
|
|
3251
|
+
const fieldIssues = error.fieldErrors.map((f) => `- "${f.path}": ${f.message} (received: ${JSON.stringify(f.received)})`).join("\n");
|
|
3252
|
+
const action = this.strategy.kind === "native" ? "Please output corrected JSON matching the schema." : "Please call `response` again with corrected values matching the schema.";
|
|
3253
|
+
return {
|
|
3254
|
+
role: "system",
|
|
3255
|
+
content: [
|
|
3256
|
+
"Your previous structured output had validation errors:",
|
|
3257
|
+
fieldIssues,
|
|
3258
|
+
"",
|
|
3259
|
+
action
|
|
3260
|
+
].join("\n")
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
return this.buildNudgeMessage(false);
|
|
3264
|
+
}
|
|
3265
|
+
// --- Parse Phase ---
|
|
3266
|
+
/**
|
|
3267
|
+
* Try to parse structured output from the tool-based finalization result.
|
|
3268
|
+
* Called when __structuredOutputParsed is available in ctx.
|
|
3269
|
+
*/
|
|
3270
|
+
parseFromToolResult(data) {
|
|
3271
|
+
return { success: true, data, attempts: 1 };
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Try to parse structured output from assistant message content.
|
|
3275
|
+
* For native strategy: content should be valid JSON.
|
|
3276
|
+
* For tool-based strategy: last-resort fallback extraction from text.
|
|
3277
|
+
*/
|
|
3278
|
+
parseFromContent(content) {
|
|
3279
|
+
if (this.strategy.kind === "native") {
|
|
3280
|
+
const result2 = this.strategy.extractOutput(content, this.schema);
|
|
3281
|
+
if ("data" in result2) {
|
|
3282
|
+
return { success: true, data: result2.data, attempts: 1 };
|
|
3283
|
+
}
|
|
3284
|
+
return { success: false, error: result2.error, attempts: 1, rawContent: content };
|
|
3285
|
+
}
|
|
3286
|
+
const result = this.strategy.extractFromText(content, this.schema);
|
|
3287
|
+
if ("data" in result) {
|
|
3288
|
+
return { success: true, data: result.data, attempts: 1 };
|
|
3289
|
+
}
|
|
3290
|
+
return { success: false, error: result.error, attempts: 1, rawContent: content };
|
|
3291
|
+
}
|
|
3292
|
+
/**
|
|
3293
|
+
* Create a "no output" error result.
|
|
3294
|
+
*/
|
|
3295
|
+
noOutputResult(attempts, rawContent) {
|
|
3296
|
+
return {
|
|
3297
|
+
success: false,
|
|
3298
|
+
error: {
|
|
3299
|
+
type: "no_output",
|
|
3300
|
+
message: "Model did not produce structured output after all retry attempts.",
|
|
3301
|
+
rawContent
|
|
3302
|
+
},
|
|
3303
|
+
attempts,
|
|
3304
|
+
rawContent
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* Create a "max retries" error result.
|
|
3309
|
+
*/
|
|
3310
|
+
maxRetriesResult(lastError, attempts, rawContent) {
|
|
3311
|
+
return {
|
|
3312
|
+
success: false,
|
|
3313
|
+
error: {
|
|
3314
|
+
...lastError,
|
|
3315
|
+
type: "max_retries",
|
|
3316
|
+
message: `Structured output failed after ${attempts} attempts: ${lastError.message}`
|
|
3317
|
+
},
|
|
3318
|
+
attempts,
|
|
3319
|
+
rawContent
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
|
|
3324
|
+
// src/agent.ts
|
|
2860
3325
|
function getLastAssistantMessage(messages) {
|
|
2861
3326
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
2862
3327
|
if (messages[index]?.role === "assistant") {
|
|
@@ -2887,14 +3352,13 @@ function createAgent(opts) {
|
|
|
2887
3352
|
const resolver = createResolverNode();
|
|
2888
3353
|
const agentCore = createAgentCoreNode(opts);
|
|
2889
3354
|
const toolsBase = [...opts.tools ?? []];
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
}
|
|
2897
|
-
toolsBase.push(responseTool);
|
|
3355
|
+
const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
|
|
3356
|
+
if (soManager) {
|
|
3357
|
+
const modelCapabilities = getModelCapabilities(opts.model);
|
|
3358
|
+
const responseTool = soManager.getResponseTool();
|
|
3359
|
+
if (responseTool && modelCapabilities.structuredOutput !== "native") {
|
|
3360
|
+
toolsBase.push(responseTool);
|
|
3361
|
+
}
|
|
2898
3362
|
}
|
|
2899
3363
|
const toolsNode = createToolsNode(toolsBase, opts);
|
|
2900
3364
|
const finalizeNode = createToolLimitFinalizeNode();
|
|
@@ -2929,6 +3393,7 @@ function createAgent(opts) {
|
|
|
2929
3393
|
limits: opts.limits,
|
|
2930
3394
|
useTodoList: void 0,
|
|
2931
3395
|
outputSchema: opts.outputSchema,
|
|
3396
|
+
responseFormat: soManager?.getResponseFormat(),
|
|
2932
3397
|
tracing: opts.tracing
|
|
2933
3398
|
};
|
|
2934
3399
|
const summarizationThreshold = getActiveSummarizationThreshold(opts);
|
|
@@ -3067,10 +3532,11 @@ function createAgent(opts) {
|
|
|
3067
3532
|
});
|
|
3068
3533
|
const tokenCount = countMessagesTokens(tokCountMessages);
|
|
3069
3534
|
if (tokenCount > summarizationThreshold) {
|
|
3535
|
+
const summarizationExhausted = !!state.ctx?.__summarizationExhausted;
|
|
3070
3536
|
const hasFreshSummary = (state.messages || []).some(
|
|
3071
3537
|
(m) => m.role === "tool" && typeof m.content === "string" && (m.content === "SUMMARIZED" || m.content.startsWith("SUMMARIZED_TOOL_RESPONSE"))
|
|
3072
3538
|
);
|
|
3073
|
-
if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else {
|
|
3539
|
+
if (hasFreshSummary && tokenCount <= summarizationThreshold * 1.15) ; else if (summarizationExhausted) ; else {
|
|
3074
3540
|
const ctx = { ...state.ctx || {}, __needsSummarization: true };
|
|
3075
3541
|
state = { ...state, ctx };
|
|
3076
3542
|
break;
|
|
@@ -3148,18 +3614,10 @@ function createAgent(opts) {
|
|
|
3148
3614
|
continue;
|
|
3149
3615
|
}
|
|
3150
3616
|
if (toolCalls.length === 0) {
|
|
3151
|
-
if (
|
|
3152
|
-
const
|
|
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
|
-
};
|
|
3617
|
+
if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
|
|
3618
|
+
const nudge = soManager.buildNudgeMessage(false);
|
|
3161
3619
|
const ctx = { ...state.ctx || {}, __structuredOutputForceFinalize: true };
|
|
3162
|
-
state = { ...state, messages: [...state.messages,
|
|
3620
|
+
state = { ...state, messages: [...state.messages, nudge], ctx };
|
|
3163
3621
|
continue;
|
|
3164
3622
|
}
|
|
3165
3623
|
break;
|
|
@@ -3172,23 +3630,16 @@ function createAgent(opts) {
|
|
|
3172
3630
|
if (checkpointIfRequested("after_tools")) break;
|
|
3173
3631
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3174
3632
|
}
|
|
3175
|
-
if (
|
|
3176
|
-
const
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
const
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
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] };
|
|
3633
|
+
if (soManager && !state.ctx?.__finalizedDueToStructuredOutput) {
|
|
3634
|
+
const maxPostLoopRetries = soManager.maxRetries;
|
|
3635
|
+
for (let postRetry = 0; postRetry < maxPostLoopRetries; postRetry++) {
|
|
3636
|
+
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3637
|
+
const last = state.messages[state.messages.length - 1];
|
|
3638
|
+
const lastHasToolCalls = Array.isArray(last?.tool_calls) && last.tool_calls.length > 0;
|
|
3639
|
+
if (!lastHasToolCalls) {
|
|
3640
|
+
const isLastAttempt = postRetry === maxPostLoopRetries - 1;
|
|
3641
|
+
const nudge = soManager.buildNudgeMessage(isLastAttempt);
|
|
3642
|
+
state = { ...state, messages: [...state.messages, nudge] };
|
|
3192
3643
|
}
|
|
3193
3644
|
try {
|
|
3194
3645
|
state = { ...state, ...await agentCore(state) };
|
|
@@ -3288,25 +3739,24 @@ function createAgent(opts) {
|
|
|
3288
3739
|
status: "success"
|
|
3289
3740
|
});
|
|
3290
3741
|
const finalAssistantMsg = getLastAssistantMessage(res.messages);
|
|
3291
|
-
const content =
|
|
3742
|
+
const content = extractMessageText2(finalAssistantMsg);
|
|
3292
3743
|
let parsed = void 0;
|
|
3293
|
-
|
|
3294
|
-
if (
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
} catch {
|
|
3744
|
+
let outputError = void 0;
|
|
3745
|
+
if (soManager) {
|
|
3746
|
+
if (res.ctx?.__structuredOutputParsed) {
|
|
3747
|
+
parsed = res.ctx.__structuredOutputParsed;
|
|
3748
|
+
} else if (content) {
|
|
3749
|
+
const fallbackResult = soManager.parseFromContent(content);
|
|
3750
|
+
if (fallbackResult.success) {
|
|
3751
|
+
parsed = fallbackResult.data;
|
|
3752
|
+
} else {
|
|
3753
|
+
outputError = fallbackResult.error;
|
|
3754
|
+
}
|
|
3755
|
+
} else {
|
|
3756
|
+
const noResult = soManager.noOutputResult(1);
|
|
3757
|
+
if (!noResult.success) {
|
|
3758
|
+
outputError = noResult.error;
|
|
3759
|
+
}
|
|
3310
3760
|
}
|
|
3311
3761
|
}
|
|
3312
3762
|
emit({ type: "finalAnswer", content });
|
|
@@ -3317,6 +3767,7 @@ function createAgent(opts) {
|
|
|
3317
3767
|
return {
|
|
3318
3768
|
content,
|
|
3319
3769
|
output: parsed,
|
|
3770
|
+
outputError,
|
|
3320
3771
|
metadata: { usage: res.usage },
|
|
3321
3772
|
messages: res.messages,
|
|
3322
3773
|
state: res
|
|
@@ -3440,7 +3891,7 @@ function createAgentNode(opts) {
|
|
|
3440
3891
|
...messages,
|
|
3441
3892
|
response
|
|
3442
3893
|
];
|
|
3443
|
-
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.usage;
|
|
3894
|
+
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
3895
|
const normalized = normalizeUsage(rawUsage);
|
|
3445
3896
|
const modelName = getModelName(runtime.model || opts.model) || "unknown_model";
|
|
3446
3897
|
const providerName = getProviderName(runtime.model || opts.model);
|
|
@@ -3488,25 +3939,27 @@ function createAgentNode(opts) {
|
|
|
3488
3939
|
}
|
|
3489
3940
|
|
|
3490
3941
|
// src/smart/contextPolicy.ts
|
|
3491
|
-
function messageText(message) {
|
|
3492
|
-
if (typeof message.content === "string") return message.content;
|
|
3493
|
-
if (Array.isArray(message.content)) {
|
|
3494
|
-
return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
|
|
3495
|
-
}
|
|
3496
|
-
return "";
|
|
3497
|
-
}
|
|
3498
3942
|
function collectRecentTurns(messages, lastTurnsToKeep) {
|
|
3499
3943
|
if (lastTurnsToKeep <= 0) return [];
|
|
3500
3944
|
const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
|
|
3501
3945
|
const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
|
|
3502
|
-
|
|
3946
|
+
const totalUserTurns = body.filter((m) => m.role === "user").length;
|
|
3947
|
+
const countAssistantTurns = totalUserTurns < lastTurnsToKeep;
|
|
3948
|
+
let turnCount = 0;
|
|
3503
3949
|
const collected = [];
|
|
3504
3950
|
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
3505
3951
|
const message = body[index];
|
|
3506
3952
|
collected.unshift(message);
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3953
|
+
const isTurnBoundary = countAssistantTurns ? message.role === "assistant" : message.role === "user";
|
|
3954
|
+
if (isTurnBoundary) {
|
|
3955
|
+
turnCount += 1;
|
|
3956
|
+
if (turnCount >= lastTurnsToKeep) break;
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
if (countAssistantTurns && collected.length > 0 && collected[0].role !== "user") {
|
|
3960
|
+
const firstUserMsg = body.find((m) => m.role === "user");
|
|
3961
|
+
if (firstUserMsg && !collected.includes(firstUserMsg)) {
|
|
3962
|
+
collected.unshift(firstUserMsg);
|
|
3510
3963
|
}
|
|
3511
3964
|
}
|
|
3512
3965
|
return [...systemPrefix, ...collected];
|
|
@@ -3526,10 +3979,6 @@ function renderStructuredSummary(summary) {
|
|
|
3526
3979
|
lines.push("Open questions:");
|
|
3527
3980
|
lines.push(...summary.open_questions.map((question) => `- ${question}`));
|
|
3528
3981
|
}
|
|
3529
|
-
if (summary.discarded_obsolete.length > 0) {
|
|
3530
|
-
lines.push("Discarded obsolete:");
|
|
3531
|
-
lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
|
|
3532
|
-
}
|
|
3533
3982
|
return lines.join("\n");
|
|
3534
3983
|
}
|
|
3535
3984
|
function renderMemoryBlock(facts) {
|
|
@@ -3542,11 +3991,48 @@ function renderMemoryBlock(facts) {
|
|
|
3542
3991
|
function clampToBudget(messages, maxContextTokens) {
|
|
3543
3992
|
let working = [...messages];
|
|
3544
3993
|
while (working.length > 2) {
|
|
3545
|
-
const tokenCount = countApproxTokens(working.map(
|
|
3994
|
+
const tokenCount = countApproxTokens(working.map(extractMessageText2).join("\n"));
|
|
3546
3995
|
if (tokenCount <= maxContextTokens) return working;
|
|
3547
3996
|
const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
|
|
3548
3997
|
if (firstNonSystem < 0) break;
|
|
3549
|
-
working
|
|
3998
|
+
const target = working[firstNonSystem];
|
|
3999
|
+
if (target.role === "assistant" && Array.isArray(target.tool_calls) && target.tool_calls.length > 0) {
|
|
4000
|
+
const toolCallIds = new Set(
|
|
4001
|
+
target.tool_calls.map((tc) => tc.id).filter(Boolean)
|
|
4002
|
+
);
|
|
4003
|
+
working = working.filter((m, idx) => {
|
|
4004
|
+
if (idx === firstNonSystem) return false;
|
|
4005
|
+
if (m.role === "tool" && m.tool_call_id && toolCallIds.has(m.tool_call_id)) return false;
|
|
4006
|
+
return true;
|
|
4007
|
+
});
|
|
4008
|
+
} else if (target.role === "tool") {
|
|
4009
|
+
const toolCallId = target.tool_call_id;
|
|
4010
|
+
let parentIdx = -1;
|
|
4011
|
+
if (toolCallId) {
|
|
4012
|
+
for (let i = firstNonSystem - 1; i >= 0; i--) {
|
|
4013
|
+
const m = working[i];
|
|
4014
|
+
if (m.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.some((tc) => tc.id === toolCallId)) {
|
|
4015
|
+
parentIdx = i;
|
|
4016
|
+
break;
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
if (parentIdx >= 0) {
|
|
4021
|
+
const parent = working[parentIdx];
|
|
4022
|
+
const parentToolCallIds = new Set(
|
|
4023
|
+
(parent.tool_calls || []).map((tc) => tc.id).filter(Boolean)
|
|
4024
|
+
);
|
|
4025
|
+
working = working.filter((m, idx) => {
|
|
4026
|
+
if (idx === parentIdx) return false;
|
|
4027
|
+
if (m.role === "tool" && m.tool_call_id && parentToolCallIds.has(m.tool_call_id)) return false;
|
|
4028
|
+
return true;
|
|
4029
|
+
});
|
|
4030
|
+
} else {
|
|
4031
|
+
working.splice(firstNonSystem, 1);
|
|
4032
|
+
}
|
|
4033
|
+
} else {
|
|
4034
|
+
working.splice(firstNonSystem, 1);
|
|
4035
|
+
}
|
|
3550
4036
|
}
|
|
3551
4037
|
return working;
|
|
3552
4038
|
}
|
|
@@ -3579,18 +4065,6 @@ function buildModelMessages(state, config) {
|
|
|
3579
4065
|
// src/nodes/contextSummarize.ts
|
|
3580
4066
|
var systemMessage = (content) => ({ role: "system", content });
|
|
3581
4067
|
var humanMessage = (content) => ({ role: "user", content });
|
|
3582
|
-
function isSyntheticSummaryToolMessage(message) {
|
|
3583
|
-
return message.role === "tool" && message.name === "summarize_context";
|
|
3584
|
-
}
|
|
3585
|
-
function isSyntheticSummaryAssistantMessage(message) {
|
|
3586
|
-
if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
|
|
3587
|
-
return false;
|
|
3588
|
-
}
|
|
3589
|
-
return message.tool_calls.some((toolCall) => {
|
|
3590
|
-
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
3591
|
-
return toolName === "summarize_context";
|
|
3592
|
-
});
|
|
3593
|
-
}
|
|
3594
4068
|
function validateMessageSequence(messages) {
|
|
3595
4069
|
const validated = [];
|
|
3596
4070
|
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
@@ -3815,7 +4289,7 @@ function createContextSummarizeNode(opts) {
|
|
|
3815
4289
|
(m) => {
|
|
3816
4290
|
if (m.role !== "tool") return false;
|
|
3817
4291
|
if (isSummarizedToolPlaceholder(m.content)) return false;
|
|
3818
|
-
if (
|
|
4292
|
+
if (isSyntheticSummaryMessage(m)) return false;
|
|
3819
4293
|
const histEntry = findToolHistoryEntry(state, m.tool_call_id, m.name);
|
|
3820
4294
|
if (histEntry?.retentionPolicy === "keep_full") return false;
|
|
3821
4295
|
if (!histEntry?.retentionPolicy) {
|
|
@@ -3828,15 +4302,14 @@ function createContextSummarizeNode(opts) {
|
|
|
3828
4302
|
if (compressableMessages.length === 0) {
|
|
3829
4303
|
return {};
|
|
3830
4304
|
}
|
|
3831
|
-
const
|
|
3832
|
-
const tokenCountBefore = countApproxTokens(allTextBefore);
|
|
4305
|
+
const tokenCountBefore = countMessagesTokens(messages);
|
|
3833
4306
|
const summaryPromptMaxTokens = config.summaryPromptMaxTokens;
|
|
3834
4307
|
const availableTokensForConversation = summaryPromptMaxTokens - 500;
|
|
3835
4308
|
const messageTexts = [];
|
|
3836
4309
|
let accumulatedTokens = 0;
|
|
3837
4310
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3838
4311
|
const m = messages[i];
|
|
3839
|
-
if (
|
|
4312
|
+
if (isSyntheticSummaryMessage(m) || isSyntheticSummaryMessage(m)) {
|
|
3840
4313
|
continue;
|
|
3841
4314
|
}
|
|
3842
4315
|
let content = "";
|
|
@@ -3857,13 +4330,13 @@ function createContextSummarizeNode(opts) {
|
|
|
3857
4330
|
}
|
|
3858
4331
|
let prefix = `${m.role.toUpperCase()}`;
|
|
3859
4332
|
if (m.name) prefix += ` (${m.name})`;
|
|
3860
|
-
const
|
|
3861
|
-
const messageTokens = countApproxTokens(
|
|
4333
|
+
const messageText = `${prefix}: ${content}`;
|
|
4334
|
+
const messageTokens = countApproxTokens(messageText);
|
|
3862
4335
|
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
3863
4336
|
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
3864
4337
|
break;
|
|
3865
4338
|
}
|
|
3866
|
-
messageTexts.unshift(
|
|
4339
|
+
messageTexts.unshift(messageText);
|
|
3867
4340
|
accumulatedTokens += messageTokens;
|
|
3868
4341
|
}
|
|
3869
4342
|
const conversationText = messageTexts.join("\n\n");
|
|
@@ -4005,7 +4478,7 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
|
|
|
4005
4478
|
const toolResponsesConfig = resolved.toolResponses;
|
|
4006
4479
|
const newMessages = messages.map((m) => {
|
|
4007
4480
|
if (m.role === "tool") {
|
|
4008
|
-
if (
|
|
4481
|
+
if (isSyntheticSummaryMessage(m)) {
|
|
4009
4482
|
return m;
|
|
4010
4483
|
}
|
|
4011
4484
|
const toolCallId = m.tool_call_id;
|
|
@@ -4063,8 +4536,7 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
|
|
|
4063
4536
|
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
4064
4537
|
const summaryRecords = Array.isArray(state.summaryRecords) ? [...state.summaryRecords, { ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }] : [{ ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
4065
4538
|
const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
|
|
4066
|
-
const
|
|
4067
|
-
const tokenCountAfter = countApproxTokens(allTextAfter);
|
|
4539
|
+
const tokenCountAfter = countMessagesTokens(finalMessages);
|
|
4068
4540
|
const summarizationEvent = {
|
|
4069
4541
|
type: "summarization",
|
|
4070
4542
|
summary: summaryText,
|
|
@@ -4115,25 +4587,6 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
|
|
|
4115
4587
|
};
|
|
4116
4588
|
};
|
|
4117
4589
|
}
|
|
4118
|
-
|
|
4119
|
-
// src/utils/tokenManager.ts
|
|
4120
|
-
async function applyTokenLimits({
|
|
4121
|
-
state,
|
|
4122
|
-
limits
|
|
4123
|
-
}) {
|
|
4124
|
-
const messages = state.messages || [];
|
|
4125
|
-
const budget = limits.contextTokenLimit;
|
|
4126
|
-
let total = messages.reduce((acc, m) => acc + countApproxTokens(String(m.content || "")), 0);
|
|
4127
|
-
if (total <= budget) return state;
|
|
4128
|
-
const newMessages = [...messages];
|
|
4129
|
-
while (total > budget && newMessages.length > 1) {
|
|
4130
|
-
const idx = newMessages.findIndex((m) => m.role !== "system");
|
|
4131
|
-
if (idx === -1) break;
|
|
4132
|
-
const removed = newMessages.splice(idx, 1)[0];
|
|
4133
|
-
total -= countApproxTokens(String(removed.content || ""));
|
|
4134
|
-
}
|
|
4135
|
-
return { ...state, messages: newMessages };
|
|
4136
|
-
}
|
|
4137
4590
|
var todoStatusSchema = z.enum(["not-started", "in-progress", "completed", "blocked"]);
|
|
4138
4591
|
var todoWriteItemSchema = z.object({
|
|
4139
4592
|
id: z.number().int().min(1).describe("Sequential id starting from 1"),
|
|
@@ -4196,6 +4649,12 @@ function calculateAdherenceScore(todoList) {
|
|
|
4196
4649
|
const completedCount = todoList.filter((item) => item.status === "completed").length;
|
|
4197
4650
|
return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
|
|
4198
4651
|
}
|
|
4652
|
+
function formatTodoListSummary(todoList) {
|
|
4653
|
+
if (!Array.isArray(todoList) || todoList.length === 0) {
|
|
4654
|
+
return "No plan items.";
|
|
4655
|
+
}
|
|
4656
|
+
return todoList.map((item) => `${item.id}. ${item.title || item.step || item.description || `Step ${item.id}`} [${item.status}]`).join("\n");
|
|
4657
|
+
}
|
|
4199
4658
|
function createContextTools(stateRef, opts) {
|
|
4200
4659
|
const tools = [];
|
|
4201
4660
|
if (opts?.planningEnabled) {
|
|
@@ -4208,11 +4667,43 @@ function createContextTools(stateRef, opts) {
|
|
|
4208
4667
|
todoList: z.array(z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
|
|
4209
4668
|
}),
|
|
4210
4669
|
func: async ({ operation, expectedVersion, todoList }) => {
|
|
4211
|
-
const
|
|
4670
|
+
const toolStateRef = manageTodo._stateRef;
|
|
4671
|
+
const onEvent = toolStateRef?.__onEvent;
|
|
4672
|
+
const emitPlanEvent = (list) => {
|
|
4673
|
+
const version = stateRef.planVersion || 1;
|
|
4674
|
+
const adherenceScore = stateRef.adherenceScore || 0;
|
|
4675
|
+
const planData = {
|
|
4676
|
+
source: "manage_todo_list",
|
|
4677
|
+
operation,
|
|
4678
|
+
version,
|
|
4679
|
+
adherenceScore,
|
|
4680
|
+
count: Array.isArray(list) ? list.length : 0
|
|
4681
|
+
};
|
|
4682
|
+
onEvent?.({ type: "plan", todoList: list, ...planData });
|
|
4683
|
+
const traceSession = toolStateRef?.ctx?.__traceSession;
|
|
4684
|
+
recordTraceEvent(traceSession, {
|
|
4685
|
+
type: "plan",
|
|
4686
|
+
label: `Plan ${operation}`,
|
|
4687
|
+
actor: { scope: "agent", name: "manage_todo_list", role: "planner" },
|
|
4688
|
+
sections: [
|
|
4689
|
+
{
|
|
4690
|
+
kind: "summary",
|
|
4691
|
+
label: "Todo List",
|
|
4692
|
+
content: formatTodoListSummary(list)
|
|
4693
|
+
},
|
|
4694
|
+
{
|
|
4695
|
+
kind: "metadata",
|
|
4696
|
+
label: "Plan Metadata",
|
|
4697
|
+
data: planData
|
|
4698
|
+
}
|
|
4699
|
+
],
|
|
4700
|
+
debug: planData
|
|
4701
|
+
});
|
|
4702
|
+
};
|
|
4212
4703
|
const currentVersion = stateRef.planVersion || 0;
|
|
4213
4704
|
if (operation === "read") {
|
|
4214
4705
|
const list = stateRef.todoList || [];
|
|
4215
|
-
|
|
4706
|
+
emitPlanEvent(list);
|
|
4216
4707
|
return list;
|
|
4217
4708
|
}
|
|
4218
4709
|
if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
|
|
@@ -4277,9 +4768,10 @@ function createContextTools(stateRef, opts) {
|
|
|
4277
4768
|
adherenceScore: stateRef.adherenceScore || 0
|
|
4278
4769
|
};
|
|
4279
4770
|
}
|
|
4280
|
-
const
|
|
4771
|
+
const currentMap = new Map(currentList.map((item) => [item.id, item]));
|
|
4772
|
+
const patchMap = /* @__PURE__ */ new Map();
|
|
4281
4773
|
for (const item of parsed.data) {
|
|
4282
|
-
if (
|
|
4774
|
+
if (patchMap.has(item.id)) {
|
|
4283
4775
|
return {
|
|
4284
4776
|
status: "error",
|
|
4285
4777
|
operation,
|
|
@@ -4288,10 +4780,7 @@ function createContextTools(stateRef, opts) {
|
|
|
4288
4780
|
adherenceScore: stateRef.adherenceScore || 0
|
|
4289
4781
|
};
|
|
4290
4782
|
}
|
|
4291
|
-
|
|
4292
|
-
}
|
|
4293
|
-
const currentMap = new Map(currentList.map((item) => [item.id, item]));
|
|
4294
|
-
for (const item of parsed.data) {
|
|
4783
|
+
patchMap.set(item.id, item);
|
|
4295
4784
|
if (!currentMap.has(item.id)) {
|
|
4296
4785
|
return {
|
|
4297
4786
|
status: "error",
|
|
@@ -4303,7 +4792,7 @@ function createContextTools(stateRef, opts) {
|
|
|
4303
4792
|
}
|
|
4304
4793
|
}
|
|
4305
4794
|
const mergedList = currentList.map((item) => {
|
|
4306
|
-
const patch =
|
|
4795
|
+
const patch = patchMap.get(item.id);
|
|
4307
4796
|
return patch ? normalizeTodoItem(patch, item) : item;
|
|
4308
4797
|
});
|
|
4309
4798
|
const validation = validatePlanInvariants(mergedList, false);
|
|
@@ -4327,7 +4816,7 @@ function createContextTools(stateRef, opts) {
|
|
|
4327
4816
|
version: stateRef.planVersion || 1,
|
|
4328
4817
|
adherenceScore: stateRef.adherenceScore || 0
|
|
4329
4818
|
};
|
|
4330
|
-
|
|
4819
|
+
emitPlanEvent(stateRef.todoList || []);
|
|
4331
4820
|
return payload;
|
|
4332
4821
|
}
|
|
4333
4822
|
});
|
|
@@ -4352,40 +4841,10 @@ function createContextTools(stateRef, opts) {
|
|
|
4352
4841
|
});
|
|
4353
4842
|
getTool._stateRef = stateRef;
|
|
4354
4843
|
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
4844
|
return tools;
|
|
4373
4845
|
}
|
|
4374
4846
|
|
|
4375
4847
|
// src/graph/decisions.ts
|
|
4376
|
-
function isSyntheticSummaryMessage2(message) {
|
|
4377
|
-
if (!message) return false;
|
|
4378
|
-
if (message.role === "tool" && message.name === "summarize_context") {
|
|
4379
|
-
return true;
|
|
4380
|
-
}
|
|
4381
|
-
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
4382
|
-
return message.tool_calls.some((toolCall) => {
|
|
4383
|
-
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
4384
|
-
return toolName === "summarize_context";
|
|
4385
|
-
});
|
|
4386
|
-
}
|
|
4387
|
-
return false;
|
|
4388
|
-
}
|
|
4389
4848
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
4390
4849
|
if (!summarizationEnabled) {
|
|
4391
4850
|
return false;
|
|
@@ -4399,30 +4858,9 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
4399
4858
|
}
|
|
4400
4859
|
const maxTok = resolved.summarization.summaryTriggerTokens;
|
|
4401
4860
|
try {
|
|
4402
|
-
const
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
4406
|
-
}
|
|
4407
|
-
if (m.content && typeof m.content === "object") {
|
|
4408
|
-
return JSON.stringify(m.content);
|
|
4409
|
-
}
|
|
4410
|
-
return "";
|
|
4411
|
-
}).join("\n");
|
|
4412
|
-
const tokenCount = countApproxTokens(allText);
|
|
4413
|
-
const needsSum = tokenCount > maxTok;
|
|
4414
|
-
if (process.env.DEBUG_SUMMARIZATION) {
|
|
4415
|
-
const msgCount = (state.messages || []).length;
|
|
4416
|
-
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
4417
|
-
const toolDetails = toolMsgs.map((m, i) => {
|
|
4418
|
-
const contentStr = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
4419
|
-
return `[${i}] id:${m.tool_call_id?.slice(-8) || "none"} len:${contentStr.length} preview:"${contentStr.slice(0, 30)}..."`;
|
|
4420
|
-
});
|
|
4421
|
-
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
4422
|
-
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
4423
|
-
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
|
|
4424
|
-
}
|
|
4425
|
-
return needsSum;
|
|
4861
|
+
const filtered = (state.messages || []).filter((message) => !isSyntheticSummaryMessage(message));
|
|
4862
|
+
const tokenCount = countMessagesTokens(filtered);
|
|
4863
|
+
return tokenCount > maxTok;
|
|
4426
4864
|
} catch {
|
|
4427
4865
|
return false;
|
|
4428
4866
|
}
|
|
@@ -4551,13 +4989,6 @@ async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
|
|
|
4551
4989
|
}
|
|
4552
4990
|
return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
|
|
4553
4991
|
}
|
|
4554
|
-
function renderMemoryFacts(facts) {
|
|
4555
|
-
if (facts.length === 0) return "";
|
|
4556
|
-
return [
|
|
4557
|
-
"Memory facts:",
|
|
4558
|
-
...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
|
|
4559
|
-
].join("\n");
|
|
4560
|
-
}
|
|
4561
4992
|
|
|
4562
4993
|
// src/smart/index.ts
|
|
4563
4994
|
function createSmartAgent(opts) {
|
|
@@ -4577,7 +5008,7 @@ function createSmartAgent(opts) {
|
|
|
4577
5008
|
useTodoList: planningEnabled
|
|
4578
5009
|
};
|
|
4579
5010
|
const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
|
|
4580
|
-
const contextTools = createContextTools(stateRef, { planningEnabled
|
|
5011
|
+
const contextTools = createContextTools(stateRef, { planningEnabled });
|
|
4581
5012
|
const mergedTools = [...opts.tools ?? [], ...contextTools];
|
|
4582
5013
|
const base = createAgent({ ...runtimeOpts, tools: mergedTools });
|
|
4583
5014
|
base.__runtime.runtimeProfile = resolved.runtimeProfile;
|
|
@@ -4586,12 +5017,8 @@ function createSmartAgent(opts) {
|
|
|
4586
5017
|
const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
|
|
4587
5018
|
const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
4588
5019
|
const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
4589
|
-
const
|
|
4590
|
-
|
|
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") : "";
|
|
5020
|
+
const soManager = opts.outputSchema ? new StructuredOutputManager(opts.outputSchema, resolveStrategy(opts.model)) : void 0;
|
|
5021
|
+
const structuredOutputHint = soManager ? soManager.buildSystemPromptHint() : "";
|
|
4595
5022
|
const runtimeHint = [
|
|
4596
5023
|
resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
|
|
4597
5024
|
`Context policy: ${resolved.context.policy}.`,
|
|
@@ -4601,8 +5028,7 @@ function createSmartAgent(opts) {
|
|
|
4601
5028
|
].join("\n");
|
|
4602
5029
|
function latestUserPrompt(messages) {
|
|
4603
5030
|
const latest = [...messages].reverse().find((message) => message.role === "user");
|
|
4604
|
-
|
|
4605
|
-
return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
|
|
5031
|
+
return latest ? extractMessageText2(latest) : "";
|
|
4606
5032
|
}
|
|
4607
5033
|
async function syncMemory(state) {
|
|
4608
5034
|
const query = latestUserPrompt(state.messages || []);
|
|
@@ -4642,6 +5068,18 @@ function createSmartAgent(opts) {
|
|
|
4642
5068
|
);
|
|
4643
5069
|
return { role: "system", content: sys };
|
|
4644
5070
|
}
|
|
5071
|
+
async function trySummarize(currentState, currentRawMessages) {
|
|
5072
|
+
if (!summarizer) return { state: currentState, rawMessages: currentRawMessages, compressed: false };
|
|
5073
|
+
const delta = await summarizer(currentState);
|
|
5074
|
+
if (!delta || Object.keys(delta).length === 0) {
|
|
5075
|
+
const ctx2 = { ...currentState.ctx || {}, __summarizationExhausted: true };
|
|
5076
|
+
return { state: { ...currentState, ctx: ctx2 }, rawMessages: currentRawMessages, compressed: false };
|
|
5077
|
+
}
|
|
5078
|
+
const ctx = { ...currentState.ctx || {} };
|
|
5079
|
+
delete ctx.__summarizationExhausted;
|
|
5080
|
+
const updated = await persistLatestSummary(syncPlanState({ ...currentState, ...delta, ctx }));
|
|
5081
|
+
return { state: updated, rawMessages: [...updated.messages || currentRawMessages], compressed: true };
|
|
5082
|
+
}
|
|
4645
5083
|
const instance = {
|
|
4646
5084
|
invoke: async (input, config) => {
|
|
4647
5085
|
stateRef.toolHistory = input.toolHistory;
|
|
@@ -4660,12 +5098,10 @@ function createSmartAgent(opts) {
|
|
|
4660
5098
|
state = { ...state, messages: rawMessages };
|
|
4661
5099
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
4662
5100
|
if (next === "contextSummarize" && summarizer) {
|
|
4663
|
-
const
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
continue;
|
|
4668
|
-
}
|
|
5101
|
+
const result = await trySummarize(state, rawMessages);
|
|
5102
|
+
state = result.state;
|
|
5103
|
+
rawMessages = result.rawMessages;
|
|
5104
|
+
if (result.compressed) continue;
|
|
4669
5105
|
}
|
|
4670
5106
|
const modelMessages = buildModelMessages(state, resolved);
|
|
4671
5107
|
const res = await base.invoke({ ...state, messages: modelMessages }, config);
|
|
@@ -4693,23 +5129,20 @@ function createSmartAgent(opts) {
|
|
|
4693
5129
|
const ctx = { ...state.ctx || {} };
|
|
4694
5130
|
delete ctx.__needsSummarization;
|
|
4695
5131
|
state = { ...state, ctx };
|
|
4696
|
-
const
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
continue;
|
|
4701
|
-
}
|
|
5132
|
+
const result = await trySummarize(state, rawMessages);
|
|
5133
|
+
state = result.state;
|
|
5134
|
+
rawMessages = result.rawMessages;
|
|
5135
|
+
continue;
|
|
4702
5136
|
}
|
|
4703
5137
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
5138
|
+
if (opts.outputSchema && lastResult?.output != null) break;
|
|
4704
5139
|
if (summarizationEnabled) {
|
|
4705
5140
|
const after = decideAfter(state);
|
|
4706
5141
|
if (after === "contextSummarize" && summarizer) {
|
|
4707
|
-
const
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
continue;
|
|
4712
|
-
}
|
|
5142
|
+
const result = await trySummarize(state, rawMessages);
|
|
5143
|
+
state = result.state;
|
|
5144
|
+
rawMessages = result.rawMessages;
|
|
5145
|
+
if (result.compressed) continue;
|
|
4713
5146
|
}
|
|
4714
5147
|
}
|
|
4715
5148
|
break;
|
|
@@ -4873,35 +5306,6 @@ async function runSmartAgentEvalHarness(params) {
|
|
|
4873
5306
|
}
|
|
4874
5307
|
return results;
|
|
4875
5308
|
}
|
|
4876
|
-
|
|
4877
|
-
// src/utils/content.ts
|
|
4878
|
-
function contentToString(content) {
|
|
4879
|
-
try {
|
|
4880
|
-
if (content == null) return "";
|
|
4881
|
-
if (typeof content === "string") return content;
|
|
4882
|
-
if (Array.isArray(content)) {
|
|
4883
|
-
return content.map((p) => {
|
|
4884
|
-
if (!p || typeof p !== "object") return String(p);
|
|
4885
|
-
if (p.type === "text") return String(p.text ?? "");
|
|
4886
|
-
if (p.type === "image_url") {
|
|
4887
|
-
const img = p.image_url;
|
|
4888
|
-
const url = typeof img === "string" ? img : img?.url;
|
|
4889
|
-
const detail = typeof img === "string" ? void 0 : img?.detail;
|
|
4890
|
-
return `[image:${url || "unknown"}${detail ? ` detail=${detail}` : ""}]`;
|
|
4891
|
-
}
|
|
4892
|
-
return JSON.stringify(p);
|
|
4893
|
-
}).join("\n");
|
|
4894
|
-
}
|
|
4895
|
-
return JSON.stringify(content);
|
|
4896
|
-
} catch {
|
|
4897
|
-
return String(content);
|
|
4898
|
-
}
|
|
4899
|
-
}
|
|
4900
|
-
function mergeContentsToString(contents) {
|
|
4901
|
-
return contents.map((c) => contentToString(c)).join("\n");
|
|
4902
|
-
}
|
|
4903
|
-
|
|
4904
|
-
// src/guardrails/checks.ts
|
|
4905
5309
|
var Ajv = AjvModule?.default ?? AjvModule;
|
|
4906
5310
|
var defaultAjv = new Ajv({ allErrors: true });
|
|
4907
5311
|
defaultAjv.opts.strict = false;
|
|
@@ -5151,6 +5555,1740 @@ function createCodeGuardrail(options) {
|
|
|
5151
5555
|
});
|
|
5152
5556
|
}
|
|
5153
5557
|
|
|
5154
|
-
|
|
5558
|
+
// src/providers/types.ts
|
|
5559
|
+
function emptyUsage() {
|
|
5560
|
+
return {
|
|
5561
|
+
inputTokens: 0,
|
|
5562
|
+
outputTokens: 0,
|
|
5563
|
+
totalTokens: 0,
|
|
5564
|
+
cachedInputTokens: 0,
|
|
5565
|
+
cachedWriteTokens: 0,
|
|
5566
|
+
cachedOutputTokens: 0,
|
|
5567
|
+
reasoningTokens: 0
|
|
5568
|
+
};
|
|
5569
|
+
}
|
|
5570
|
+
var ProviderError = class extends Error {
|
|
5571
|
+
constructor(message, provider, statusCode, responseBody) {
|
|
5572
|
+
super(message);
|
|
5573
|
+
this.provider = provider;
|
|
5574
|
+
this.statusCode = statusCode;
|
|
5575
|
+
this.responseBody = responseBody;
|
|
5576
|
+
this.name = "ProviderError";
|
|
5577
|
+
}
|
|
5578
|
+
};
|
|
5579
|
+
|
|
5580
|
+
// src/providers/base.ts
|
|
5581
|
+
var BaseProvider = class {
|
|
5582
|
+
};
|
|
5583
|
+
|
|
5584
|
+
// src/providers/utils/sse.ts
|
|
5585
|
+
async function* parseSSEStream(body, signal) {
|
|
5586
|
+
const reader = body.getReader();
|
|
5587
|
+
const decoder = new TextDecoder();
|
|
5588
|
+
let buffer = "";
|
|
5589
|
+
try {
|
|
5590
|
+
while (true) {
|
|
5591
|
+
if (signal?.aborted) ;
|
|
5592
|
+
const { done, value } = await reader.read();
|
|
5593
|
+
if (done) break;
|
|
5594
|
+
buffer += decoder.decode(value, { stream: true });
|
|
5595
|
+
const parts = buffer.split(/\r?\n\r?\n/);
|
|
5596
|
+
buffer = parts.pop() ?? "";
|
|
5597
|
+
for (const part of parts) {
|
|
5598
|
+
if (!part.trim()) continue;
|
|
5599
|
+
const event = parseSSEBlock(part);
|
|
5600
|
+
if (event) yield event;
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
if (buffer.trim()) {
|
|
5604
|
+
const event = parseSSEBlock(buffer);
|
|
5605
|
+
if (event) yield event;
|
|
5606
|
+
}
|
|
5607
|
+
} finally {
|
|
5608
|
+
reader.releaseLock();
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
function parseSSEBlock(block) {
|
|
5612
|
+
let event;
|
|
5613
|
+
let id;
|
|
5614
|
+
let retry;
|
|
5615
|
+
const dataLines = [];
|
|
5616
|
+
for (const line of block.split(/\r?\n/)) {
|
|
5617
|
+
if (line.startsWith(":")) continue;
|
|
5618
|
+
const colonIdx = line.indexOf(":");
|
|
5619
|
+
let field;
|
|
5620
|
+
let value;
|
|
5621
|
+
if (colonIdx === -1) {
|
|
5622
|
+
field = line;
|
|
5623
|
+
value = "";
|
|
5624
|
+
} else {
|
|
5625
|
+
field = line.slice(0, colonIdx);
|
|
5626
|
+
value = line.slice(colonIdx + 1);
|
|
5627
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
5628
|
+
}
|
|
5629
|
+
switch (field) {
|
|
5630
|
+
case "event":
|
|
5631
|
+
event = value;
|
|
5632
|
+
break;
|
|
5633
|
+
case "data":
|
|
5634
|
+
dataLines.push(value);
|
|
5635
|
+
break;
|
|
5636
|
+
case "id":
|
|
5637
|
+
id = value;
|
|
5638
|
+
break;
|
|
5639
|
+
case "retry": {
|
|
5640
|
+
const n = parseInt(value, 10);
|
|
5641
|
+
if (!isNaN(n)) retry = n;
|
|
5642
|
+
break;
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
if (dataLines.length === 0) return null;
|
|
5647
|
+
return { event, data: dataLines.join("\n"), id, retry };
|
|
5648
|
+
}
|
|
5649
|
+
|
|
5650
|
+
// src/providers/openai.ts
|
|
5651
|
+
var OpenAIProvider = class extends BaseProvider {
|
|
5652
|
+
constructor(config) {
|
|
5653
|
+
super();
|
|
5654
|
+
this.providerName = "openai";
|
|
5655
|
+
this.apiKey = config.apiKey;
|
|
5656
|
+
this.baseURL = (config.baseURL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
5657
|
+
this.defaultModel = config.defaultModel ?? "gpt-4o";
|
|
5658
|
+
this.defaultHeaders = {
|
|
5659
|
+
...config.defaultHeaders,
|
|
5660
|
+
...config.organization ? { "OpenAI-Organization": config.organization } : {}
|
|
5661
|
+
};
|
|
5662
|
+
}
|
|
5663
|
+
async complete(request) {
|
|
5664
|
+
const body = this.buildRequestBody(request, false);
|
|
5665
|
+
const res = await this.doFetch(body);
|
|
5666
|
+
const json = await res.json();
|
|
5667
|
+
return this.parseResponse(json);
|
|
5668
|
+
}
|
|
5669
|
+
async *completeStream(request) {
|
|
5670
|
+
const body = this.buildRequestBody(request, true);
|
|
5671
|
+
const res = await this.doFetch(body);
|
|
5672
|
+
if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
|
|
5673
|
+
let id = "";
|
|
5674
|
+
let model = "";
|
|
5675
|
+
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
5676
|
+
let usage;
|
|
5677
|
+
for await (const event of parseSSEStream(res.body)) {
|
|
5678
|
+
if (event.data === "[DONE]") break;
|
|
5679
|
+
let chunk;
|
|
5680
|
+
try {
|
|
5681
|
+
chunk = JSON.parse(event.data);
|
|
5682
|
+
} catch {
|
|
5683
|
+
continue;
|
|
5684
|
+
}
|
|
5685
|
+
id = chunk.id ?? id;
|
|
5686
|
+
model = chunk.model ?? model;
|
|
5687
|
+
const choice = chunk.choices?.[0];
|
|
5688
|
+
if (chunk.usage) {
|
|
5689
|
+
usage = this.extractUsage(chunk.usage);
|
|
5690
|
+
}
|
|
5691
|
+
if (!choice) {
|
|
5692
|
+
if (usage) yield { id, model, delta: {}, usage };
|
|
5693
|
+
continue;
|
|
5694
|
+
}
|
|
5695
|
+
const delta = choice.delta ?? {};
|
|
5696
|
+
const out = {
|
|
5697
|
+
id,
|
|
5698
|
+
model,
|
|
5699
|
+
delta: {},
|
|
5700
|
+
finishReason: mapFinishReason(choice.finish_reason)
|
|
5701
|
+
};
|
|
5702
|
+
if (delta.content) {
|
|
5703
|
+
out.delta.content = delta.content;
|
|
5704
|
+
}
|
|
5705
|
+
if (delta.tool_calls) {
|
|
5706
|
+
const partials = [];
|
|
5707
|
+
for (const tc of delta.tool_calls) {
|
|
5708
|
+
const idx = tc.index ?? 0;
|
|
5709
|
+
if (!toolCallBuffers.has(idx)) {
|
|
5710
|
+
toolCallBuffers.set(idx, { id: tc.id ?? "", name: "", arguments: "" });
|
|
5711
|
+
}
|
|
5712
|
+
const buf = toolCallBuffers.get(idx);
|
|
5713
|
+
if (tc.id) buf.id = tc.id;
|
|
5714
|
+
if (tc.function?.name) buf.name = tc.function.name;
|
|
5715
|
+
if (tc.function?.arguments) buf.arguments += tc.function.arguments;
|
|
5716
|
+
partials.push({ id: buf.id, name: buf.name || void 0, arguments: tc.function?.arguments });
|
|
5717
|
+
}
|
|
5718
|
+
out.delta.toolCalls = partials;
|
|
5719
|
+
}
|
|
5720
|
+
if (usage) out.usage = usage;
|
|
5721
|
+
yield out;
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
// ─── Internals ───────────────────────────────────────────────────────────
|
|
5725
|
+
buildRequestBody(request, stream) {
|
|
5726
|
+
const body = {
|
|
5727
|
+
model: request.model || this.defaultModel,
|
|
5728
|
+
messages: request.messages.map((m) => this.toOpenAIMessage(m)),
|
|
5729
|
+
stream
|
|
5730
|
+
};
|
|
5731
|
+
if (stream) {
|
|
5732
|
+
body.stream_options = { include_usage: true };
|
|
5733
|
+
}
|
|
5734
|
+
if (request.temperature != null) body.temperature = request.temperature;
|
|
5735
|
+
if (request.maxTokens != null) body.max_tokens = request.maxTokens;
|
|
5736
|
+
if (request.topP != null) body.top_p = request.topP;
|
|
5737
|
+
if (request.stop) body.stop = request.stop;
|
|
5738
|
+
if (request.tools?.length) {
|
|
5739
|
+
body.tools = request.tools.map((t) => this.toOpenAITool(t));
|
|
5740
|
+
}
|
|
5741
|
+
if (request.toolChoice != null) {
|
|
5742
|
+
if (typeof request.toolChoice === "string") {
|
|
5743
|
+
body.tool_choice = request.toolChoice;
|
|
5744
|
+
} else {
|
|
5745
|
+
body.tool_choice = { type: "function", function: { name: request.toolChoice.name } };
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
if (request.responseFormat) {
|
|
5749
|
+
if (request.responseFormat.type === "json_schema") {
|
|
5750
|
+
body.response_format = {
|
|
5751
|
+
type: "json_schema",
|
|
5752
|
+
json_schema: {
|
|
5753
|
+
name: request.responseFormat.name ?? "response",
|
|
5754
|
+
schema: request.responseFormat.schema,
|
|
5755
|
+
strict: true
|
|
5756
|
+
}
|
|
5757
|
+
};
|
|
5758
|
+
} else if (request.responseFormat.type === "json_object") {
|
|
5759
|
+
body.response_format = { type: "json_object" };
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
if (request.extra) {
|
|
5763
|
+
Object.assign(body, request.extra);
|
|
5764
|
+
}
|
|
5765
|
+
return body;
|
|
5766
|
+
}
|
|
5767
|
+
toOpenAIMessage(m) {
|
|
5768
|
+
const msg = { role: m.role };
|
|
5769
|
+
if (typeof m.content === "string") {
|
|
5770
|
+
msg.content = m.content;
|
|
5771
|
+
} else if (Array.isArray(m.content)) {
|
|
5772
|
+
msg.content = m.content.map((part) => {
|
|
5773
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
5774
|
+
if (part.type === "image") {
|
|
5775
|
+
const src = part.source;
|
|
5776
|
+
const url = src.type === "url" ? src.url : `data:${src.mediaType};base64,${src.data}`;
|
|
5777
|
+
return { type: "image_url", image_url: { url } };
|
|
5778
|
+
}
|
|
5779
|
+
return part;
|
|
5780
|
+
});
|
|
5781
|
+
}
|
|
5782
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
5783
|
+
msg.tool_calls = m.toolCalls.map((tc) => ({
|
|
5784
|
+
id: tc.id,
|
|
5785
|
+
type: "function",
|
|
5786
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
5787
|
+
}));
|
|
5788
|
+
}
|
|
5789
|
+
if (m.role === "tool" && m.toolCallId) {
|
|
5790
|
+
msg.tool_call_id = m.toolCallId;
|
|
5791
|
+
}
|
|
5792
|
+
if (m.name) msg.name = m.name;
|
|
5793
|
+
return msg;
|
|
5794
|
+
}
|
|
5795
|
+
toOpenAITool(t) {
|
|
5796
|
+
const fn = {
|
|
5797
|
+
name: t.name,
|
|
5798
|
+
description: t.description,
|
|
5799
|
+
parameters: t.parameters
|
|
5800
|
+
};
|
|
5801
|
+
if (t.strict) {
|
|
5802
|
+
fn.strict = true;
|
|
5803
|
+
}
|
|
5804
|
+
return { type: "function", function: fn };
|
|
5805
|
+
}
|
|
5806
|
+
async doFetch(body) {
|
|
5807
|
+
const url = `${this.baseURL}/chat/completions`;
|
|
5808
|
+
const res = await fetch(url, {
|
|
5809
|
+
method: "POST",
|
|
5810
|
+
headers: {
|
|
5811
|
+
"Content-Type": "application/json",
|
|
5812
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
5813
|
+
...this.defaultHeaders
|
|
5814
|
+
},
|
|
5815
|
+
body: JSON.stringify(body)
|
|
5816
|
+
});
|
|
5817
|
+
if (!res.ok) {
|
|
5818
|
+
const text = await res.text().catch(() => "");
|
|
5819
|
+
throw new ProviderError(
|
|
5820
|
+
`OpenAI API error ${res.status}: ${text}`,
|
|
5821
|
+
this.providerName,
|
|
5822
|
+
res.status,
|
|
5823
|
+
text
|
|
5824
|
+
);
|
|
5825
|
+
}
|
|
5826
|
+
return res;
|
|
5827
|
+
}
|
|
5828
|
+
parseResponse(json) {
|
|
5829
|
+
const choice = json.choices?.[0];
|
|
5830
|
+
const message = choice?.message ?? {};
|
|
5831
|
+
const toolCalls = (message.tool_calls ?? []).map((tc) => ({
|
|
5832
|
+
id: tc.id,
|
|
5833
|
+
name: tc.function?.name ?? "",
|
|
5834
|
+
arguments: tc.function?.arguments ?? "{}"
|
|
5835
|
+
}));
|
|
5836
|
+
return {
|
|
5837
|
+
id: json.id ?? "",
|
|
5838
|
+
model: json.model ?? "",
|
|
5839
|
+
content: message.content ?? null,
|
|
5840
|
+
toolCalls,
|
|
5841
|
+
usage: this.extractUsage(json.usage),
|
|
5842
|
+
finishReason: mapFinishReason(choice?.finish_reason),
|
|
5843
|
+
raw: json
|
|
5844
|
+
};
|
|
5845
|
+
}
|
|
5846
|
+
extractUsage(usage) {
|
|
5847
|
+
if (!usage) return emptyUsage();
|
|
5848
|
+
const promptDetails = usage.prompt_tokens_details ?? {};
|
|
5849
|
+
const completionDetails = usage.completion_tokens_details ?? {};
|
|
5850
|
+
return {
|
|
5851
|
+
inputTokens: usage.prompt_tokens ?? 0,
|
|
5852
|
+
outputTokens: usage.completion_tokens ?? 0,
|
|
5853
|
+
totalTokens: usage.total_tokens ?? (usage.prompt_tokens ?? 0) + (usage.completion_tokens ?? 0),
|
|
5854
|
+
cachedInputTokens: promptDetails.cached_tokens ?? 0,
|
|
5855
|
+
cachedWriteTokens: 0,
|
|
5856
|
+
cachedOutputTokens: 0,
|
|
5857
|
+
reasoningTokens: completionDetails.reasoning_tokens ?? 0
|
|
5858
|
+
};
|
|
5859
|
+
}
|
|
5860
|
+
};
|
|
5861
|
+
function mapFinishReason(reason) {
|
|
5862
|
+
switch (reason) {
|
|
5863
|
+
case "stop":
|
|
5864
|
+
return "stop";
|
|
5865
|
+
case "tool_calls":
|
|
5866
|
+
return "tool_calls";
|
|
5867
|
+
case "length":
|
|
5868
|
+
return "length";
|
|
5869
|
+
case "content_filter":
|
|
5870
|
+
return "content_filter";
|
|
5871
|
+
default:
|
|
5872
|
+
return "stop";
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
|
|
5876
|
+
// src/providers/anthropic.ts
|
|
5877
|
+
var AnthropicProvider = class extends BaseProvider {
|
|
5878
|
+
constructor(config) {
|
|
5879
|
+
super();
|
|
5880
|
+
this.providerName = "anthropic";
|
|
5881
|
+
this.apiKey = config.apiKey;
|
|
5882
|
+
this.baseURL = (config.baseURL ?? "https://api.anthropic.com").replace(/\/$/, "");
|
|
5883
|
+
this.defaultModel = config.defaultModel ?? "claude-sonnet-4-20250514";
|
|
5884
|
+
this.defaultHeaders = config.defaultHeaders ?? {};
|
|
5885
|
+
this.anthropicVersion = config.anthropicVersion ?? "2023-06-01";
|
|
5886
|
+
}
|
|
5887
|
+
async complete(request) {
|
|
5888
|
+
const body = this.buildRequestBody(request, false);
|
|
5889
|
+
const res = await this.doFetch(body);
|
|
5890
|
+
const json = await res.json();
|
|
5891
|
+
return this.parseResponse(json);
|
|
5892
|
+
}
|
|
5893
|
+
async *completeStream(request) {
|
|
5894
|
+
const body = this.buildRequestBody(request, true);
|
|
5895
|
+
const res = await this.doFetch(body);
|
|
5896
|
+
if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
|
|
5897
|
+
let id = "";
|
|
5898
|
+
let model = "";
|
|
5899
|
+
let inputTokens = 0;
|
|
5900
|
+
let outputTokens = 0;
|
|
5901
|
+
let cacheReadInputTokens = 0;
|
|
5902
|
+
let cacheCreationInputTokens = 0;
|
|
5903
|
+
const currentToolCalls = /* @__PURE__ */ new Map();
|
|
5904
|
+
for await (const event of parseSSEStream(res.body)) {
|
|
5905
|
+
let data;
|
|
5906
|
+
try {
|
|
5907
|
+
data = JSON.parse(event.data);
|
|
5908
|
+
} catch {
|
|
5909
|
+
continue;
|
|
5910
|
+
}
|
|
5911
|
+
const eventType = event.event ?? data.type;
|
|
5912
|
+
switch (eventType) {
|
|
5913
|
+
case "message_start": {
|
|
5914
|
+
const msg = data.message;
|
|
5915
|
+
id = msg?.id ?? "";
|
|
5916
|
+
model = msg?.model ?? "";
|
|
5917
|
+
if (msg?.usage) {
|
|
5918
|
+
inputTokens = msg.usage.input_tokens ?? 0;
|
|
5919
|
+
cacheReadInputTokens = msg.usage.cache_read_input_tokens ?? 0;
|
|
5920
|
+
cacheCreationInputTokens = msg.usage.cache_creation_input_tokens ?? 0;
|
|
5921
|
+
}
|
|
5922
|
+
break;
|
|
5923
|
+
}
|
|
5924
|
+
case "content_block_start": {
|
|
5925
|
+
const idx = data.index ?? 0;
|
|
5926
|
+
const block = data.content_block;
|
|
5927
|
+
if (block?.type === "tool_use") {
|
|
5928
|
+
currentToolCalls.set(idx, {
|
|
5929
|
+
id: block.id ?? "",
|
|
5930
|
+
name: block.name ?? "",
|
|
5931
|
+
arguments: ""
|
|
5932
|
+
});
|
|
5933
|
+
}
|
|
5934
|
+
break;
|
|
5935
|
+
}
|
|
5936
|
+
case "content_block_delta": {
|
|
5937
|
+
const idx = data.index ?? 0;
|
|
5938
|
+
const delta = data.delta;
|
|
5939
|
+
if (delta?.type === "text_delta" && delta.text) {
|
|
5940
|
+
yield {
|
|
5941
|
+
id,
|
|
5942
|
+
model,
|
|
5943
|
+
delta: { content: delta.text }
|
|
5944
|
+
};
|
|
5945
|
+
} else if (delta?.type === "input_json_delta" && delta.partial_json) {
|
|
5946
|
+
const tc = currentToolCalls.get(idx);
|
|
5947
|
+
if (tc) {
|
|
5948
|
+
tc.arguments += delta.partial_json;
|
|
5949
|
+
yield {
|
|
5950
|
+
id,
|
|
5951
|
+
model,
|
|
5952
|
+
delta: {
|
|
5953
|
+
toolCalls: [{ id: tc.id, name: tc.name, arguments: delta.partial_json }]
|
|
5954
|
+
}
|
|
5955
|
+
};
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
break;
|
|
5959
|
+
}
|
|
5960
|
+
case "content_block_stop":
|
|
5961
|
+
break;
|
|
5962
|
+
case "message_delta": {
|
|
5963
|
+
const delta = data.delta;
|
|
5964
|
+
if (data.usage) {
|
|
5965
|
+
outputTokens = data.usage.output_tokens ?? outputTokens;
|
|
5966
|
+
}
|
|
5967
|
+
const finishReason = mapAnthropicStopReason(delta?.stop_reason);
|
|
5968
|
+
const usage = {
|
|
5969
|
+
inputTokens,
|
|
5970
|
+
outputTokens,
|
|
5971
|
+
totalTokens: inputTokens + outputTokens,
|
|
5972
|
+
cachedInputTokens: cacheReadInputTokens,
|
|
5973
|
+
cachedWriteTokens: cacheCreationInputTokens,
|
|
5974
|
+
cachedOutputTokens: 0,
|
|
5975
|
+
reasoningTokens: 0
|
|
5976
|
+
};
|
|
5977
|
+
yield {
|
|
5978
|
+
id,
|
|
5979
|
+
model,
|
|
5980
|
+
delta: {},
|
|
5981
|
+
finishReason,
|
|
5982
|
+
usage
|
|
5983
|
+
};
|
|
5984
|
+
break;
|
|
5985
|
+
}
|
|
5986
|
+
case "message_stop":
|
|
5987
|
+
break;
|
|
5988
|
+
case "error": {
|
|
5989
|
+
const errMsg = data.error?.message ?? "Unknown Anthropic streaming error";
|
|
5990
|
+
throw new ProviderError(errMsg, this.providerName);
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
// ─── Internals ───────────────────────────────────────────────────────────
|
|
5996
|
+
buildRequestBody(request, stream) {
|
|
5997
|
+
const { system, messages } = this.splitSystemMessages(request.messages);
|
|
5998
|
+
const body = {
|
|
5999
|
+
model: request.model || this.defaultModel,
|
|
6000
|
+
messages: messages.map((m) => this.toAnthropicMessage(m)),
|
|
6001
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
6002
|
+
stream
|
|
6003
|
+
};
|
|
6004
|
+
if (system) body.system = system;
|
|
6005
|
+
if (request.temperature != null) body.temperature = request.temperature;
|
|
6006
|
+
if (request.topP != null) body.top_p = request.topP;
|
|
6007
|
+
if (request.stop) body.stop_sequences = request.stop;
|
|
6008
|
+
if (request.tools?.length) {
|
|
6009
|
+
body.tools = request.tools.map((t) => this.toAnthropicTool(t));
|
|
6010
|
+
}
|
|
6011
|
+
if (request.toolChoice != null) {
|
|
6012
|
+
if (request.toolChoice === "auto") {
|
|
6013
|
+
body.tool_choice = { type: "auto" };
|
|
6014
|
+
} else if (request.toolChoice === "required") {
|
|
6015
|
+
body.tool_choice = { type: "any" };
|
|
6016
|
+
} else if (request.toolChoice === "none") ; else {
|
|
6017
|
+
body.tool_choice = { type: "tool", name: request.toolChoice.name };
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
if (request.extra) {
|
|
6021
|
+
Object.assign(body, request.extra);
|
|
6022
|
+
}
|
|
6023
|
+
return body;
|
|
6024
|
+
}
|
|
6025
|
+
splitSystemMessages(messages) {
|
|
6026
|
+
const systemParts = [];
|
|
6027
|
+
const rest = [];
|
|
6028
|
+
for (const m of messages) {
|
|
6029
|
+
if (m.role === "system") {
|
|
6030
|
+
if (typeof m.content === "string") {
|
|
6031
|
+
systemParts.push(m.content);
|
|
6032
|
+
} else {
|
|
6033
|
+
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
6034
|
+
if (text) systemParts.push(text);
|
|
6035
|
+
}
|
|
6036
|
+
} else {
|
|
6037
|
+
rest.push(m);
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
return {
|
|
6041
|
+
system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
|
|
6042
|
+
messages: rest
|
|
6043
|
+
};
|
|
6044
|
+
}
|
|
6045
|
+
toAnthropicMessage(m) {
|
|
6046
|
+
if (m.role === "tool" && m.toolCallId) {
|
|
6047
|
+
const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
6048
|
+
return {
|
|
6049
|
+
role: "user",
|
|
6050
|
+
content: [
|
|
6051
|
+
{
|
|
6052
|
+
type: "tool_result",
|
|
6053
|
+
tool_use_id: m.toolCallId,
|
|
6054
|
+
content
|
|
6055
|
+
}
|
|
6056
|
+
]
|
|
6057
|
+
};
|
|
6058
|
+
}
|
|
6059
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
6060
|
+
const content = [];
|
|
6061
|
+
if (typeof m.content === "string" && m.content) {
|
|
6062
|
+
content.push({ type: "text", text: m.content });
|
|
6063
|
+
}
|
|
6064
|
+
for (const tc of m.toolCalls) {
|
|
6065
|
+
content.push({
|
|
6066
|
+
type: "tool_use",
|
|
6067
|
+
id: tc.id,
|
|
6068
|
+
name: tc.name,
|
|
6069
|
+
input: safeJsonParse(tc.arguments)
|
|
6070
|
+
});
|
|
6071
|
+
}
|
|
6072
|
+
return { role: "assistant", content };
|
|
6073
|
+
}
|
|
6074
|
+
const msg = { role: m.role };
|
|
6075
|
+
if (typeof m.content === "string") {
|
|
6076
|
+
msg.content = m.content;
|
|
6077
|
+
} else if (Array.isArray(m.content)) {
|
|
6078
|
+
msg.content = m.content.map((part) => {
|
|
6079
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
6080
|
+
if (part.type === "image") {
|
|
6081
|
+
const src = part.source;
|
|
6082
|
+
if (src.type === "base64") {
|
|
6083
|
+
return {
|
|
6084
|
+
type: "image",
|
|
6085
|
+
source: { type: "base64", media_type: src.mediaType, data: src.data }
|
|
6086
|
+
};
|
|
6087
|
+
}
|
|
6088
|
+
return {
|
|
6089
|
+
type: "image",
|
|
6090
|
+
source: { type: "url", url: src.url }
|
|
6091
|
+
};
|
|
6092
|
+
}
|
|
6093
|
+
return part;
|
|
6094
|
+
});
|
|
6095
|
+
}
|
|
6096
|
+
return msg;
|
|
6097
|
+
}
|
|
6098
|
+
toAnthropicTool(t) {
|
|
6099
|
+
return {
|
|
6100
|
+
name: t.name,
|
|
6101
|
+
description: t.description,
|
|
6102
|
+
input_schema: t.parameters
|
|
6103
|
+
};
|
|
6104
|
+
}
|
|
6105
|
+
async doFetch(body) {
|
|
6106
|
+
const url = `${this.baseURL}/v1/messages`;
|
|
6107
|
+
const res = await fetch(url, {
|
|
6108
|
+
method: "POST",
|
|
6109
|
+
headers: {
|
|
6110
|
+
"Content-Type": "application/json",
|
|
6111
|
+
"x-api-key": this.apiKey,
|
|
6112
|
+
"anthropic-version": this.anthropicVersion,
|
|
6113
|
+
...this.defaultHeaders
|
|
6114
|
+
},
|
|
6115
|
+
body: JSON.stringify(body)
|
|
6116
|
+
});
|
|
6117
|
+
if (!res.ok) {
|
|
6118
|
+
const text = await res.text().catch(() => "");
|
|
6119
|
+
throw new ProviderError(
|
|
6120
|
+
`Anthropic API error ${res.status}: ${text}`,
|
|
6121
|
+
this.providerName,
|
|
6122
|
+
res.status,
|
|
6123
|
+
text
|
|
6124
|
+
);
|
|
6125
|
+
}
|
|
6126
|
+
return res;
|
|
6127
|
+
}
|
|
6128
|
+
parseResponse(json) {
|
|
6129
|
+
const textParts = [];
|
|
6130
|
+
const toolCalls = [];
|
|
6131
|
+
for (const block of json.content ?? []) {
|
|
6132
|
+
if (block.type === "text") {
|
|
6133
|
+
textParts.push(block.text);
|
|
6134
|
+
} else if (block.type === "tool_use") {
|
|
6135
|
+
toolCalls.push({
|
|
6136
|
+
id: block.id,
|
|
6137
|
+
name: block.name,
|
|
6138
|
+
arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input)
|
|
6139
|
+
});
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
const usage = json.usage ?? {};
|
|
6143
|
+
const tokenUsage = {
|
|
6144
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
6145
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
6146
|
+
totalTokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
|
|
6147
|
+
cachedInputTokens: usage.cache_read_input_tokens ?? 0,
|
|
6148
|
+
cachedWriteTokens: usage.cache_creation_input_tokens ?? 0,
|
|
6149
|
+
cachedOutputTokens: 0,
|
|
6150
|
+
reasoningTokens: 0
|
|
6151
|
+
};
|
|
6152
|
+
return {
|
|
6153
|
+
id: json.id ?? "",
|
|
6154
|
+
model: json.model ?? "",
|
|
6155
|
+
content: textParts.join("") || null,
|
|
6156
|
+
toolCalls,
|
|
6157
|
+
usage: tokenUsage,
|
|
6158
|
+
finishReason: mapAnthropicStopReason(json.stop_reason),
|
|
6159
|
+
raw: json
|
|
6160
|
+
};
|
|
6161
|
+
}
|
|
6162
|
+
};
|
|
6163
|
+
function mapAnthropicStopReason(reason) {
|
|
6164
|
+
switch (reason) {
|
|
6165
|
+
case "end_turn":
|
|
6166
|
+
case "stop_sequence":
|
|
6167
|
+
return "stop";
|
|
6168
|
+
case "tool_use":
|
|
6169
|
+
return "tool_calls";
|
|
6170
|
+
case "max_tokens":
|
|
6171
|
+
return "length";
|
|
6172
|
+
default:
|
|
6173
|
+
return "stop";
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
function safeJsonParse(str) {
|
|
6177
|
+
try {
|
|
6178
|
+
return JSON.parse(str);
|
|
6179
|
+
} catch {
|
|
6180
|
+
return {};
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
|
|
6184
|
+
// src/providers/azure.ts
|
|
6185
|
+
var AzureProvider = class extends OpenAIProvider {
|
|
6186
|
+
constructor(config) {
|
|
6187
|
+
super({
|
|
6188
|
+
provider: "openai",
|
|
6189
|
+
apiKey: config.apiKey,
|
|
6190
|
+
defaultModel: config.defaultModel ?? config.deploymentName ?? "gpt-4o",
|
|
6191
|
+
defaultHeaders: config.defaultHeaders
|
|
6192
|
+
});
|
|
6193
|
+
this.providerName = "azure";
|
|
6194
|
+
this.endpoint = config.endpoint.replace(/\/$/, "");
|
|
6195
|
+
this.apiVersion = config.apiVersion ?? "2024-10-21";
|
|
6196
|
+
this.deploymentName = config.deploymentName;
|
|
6197
|
+
this.azureApiKey = config.apiKey;
|
|
6198
|
+
}
|
|
6199
|
+
async doFetch(body) {
|
|
6200
|
+
const deployment = this.deploymentName ?? body.model;
|
|
6201
|
+
if (!deployment) {
|
|
6202
|
+
throw new ProviderError("Azure requires a deploymentName or model", this.providerName);
|
|
6203
|
+
}
|
|
6204
|
+
const url = `${this.endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${this.apiVersion}`;
|
|
6205
|
+
const { model: _model, ...bodyWithoutModel } = body;
|
|
6206
|
+
const res = await fetch(url, {
|
|
6207
|
+
method: "POST",
|
|
6208
|
+
headers: {
|
|
6209
|
+
"Content-Type": "application/json",
|
|
6210
|
+
"api-key": this.azureApiKey,
|
|
6211
|
+
...this.defaultHeaders
|
|
6212
|
+
},
|
|
6213
|
+
body: JSON.stringify(bodyWithoutModel)
|
|
6214
|
+
});
|
|
6215
|
+
if (!res.ok) {
|
|
6216
|
+
const text = await res.text().catch(() => "");
|
|
6217
|
+
throw new ProviderError(
|
|
6218
|
+
`Azure OpenAI API error ${res.status}: ${text}`,
|
|
6219
|
+
this.providerName,
|
|
6220
|
+
res.status,
|
|
6221
|
+
text
|
|
6222
|
+
);
|
|
6223
|
+
}
|
|
6224
|
+
return res;
|
|
6225
|
+
}
|
|
6226
|
+
};
|
|
6227
|
+
|
|
6228
|
+
// src/providers/openaiCompatible.ts
|
|
6229
|
+
var OpenAICompatibleProvider = class extends OpenAIProvider {
|
|
6230
|
+
constructor(config) {
|
|
6231
|
+
super({
|
|
6232
|
+
provider: "openai",
|
|
6233
|
+
apiKey: config.apiKey,
|
|
6234
|
+
baseURL: config.baseURL,
|
|
6235
|
+
defaultModel: config.defaultModel,
|
|
6236
|
+
defaultHeaders: config.defaultHeaders
|
|
6237
|
+
});
|
|
6238
|
+
this.providerName = "openai-compatible";
|
|
6239
|
+
}
|
|
6240
|
+
};
|
|
6241
|
+
function signRequest(req) {
|
|
6242
|
+
const now = /* @__PURE__ */ new Date();
|
|
6243
|
+
const dateStamp = toDateStamp(now);
|
|
6244
|
+
const amzDate = toAmzDate(now);
|
|
6245
|
+
const parsed = new URL(req.url);
|
|
6246
|
+
const host = parsed.host;
|
|
6247
|
+
const canonicalUri = parsed.pathname || "/";
|
|
6248
|
+
const canonicalQuerystring = sortQueryParams(parsed.searchParams);
|
|
6249
|
+
const headers = {
|
|
6250
|
+
...req.headers,
|
|
6251
|
+
host,
|
|
6252
|
+
"x-amz-date": amzDate
|
|
6253
|
+
};
|
|
6254
|
+
if (req.credentials.sessionToken) {
|
|
6255
|
+
headers["x-amz-security-token"] = req.credentials.sessionToken;
|
|
6256
|
+
}
|
|
6257
|
+
const signedHeaderKeys = Object.keys(headers).map((k) => k.toLowerCase()).sort();
|
|
6258
|
+
const canonicalHeaders = signedHeaderKeys.map((k) => `${k}:${headers[Object.keys(headers).find((h) => h.toLowerCase() === k)]?.trim()}`).join("\n") + "\n";
|
|
6259
|
+
const signedHeaders = signedHeaderKeys.join(";");
|
|
6260
|
+
const payloadHash = sha256(req.body);
|
|
6261
|
+
const canonicalRequest = [
|
|
6262
|
+
req.method,
|
|
6263
|
+
canonicalUri,
|
|
6264
|
+
canonicalQuerystring,
|
|
6265
|
+
canonicalHeaders,
|
|
6266
|
+
signedHeaders,
|
|
6267
|
+
payloadHash
|
|
6268
|
+
].join("\n");
|
|
6269
|
+
const credentialScope = `${dateStamp}/${req.region}/${req.service}/aws4_request`;
|
|
6270
|
+
const stringToSign = [
|
|
6271
|
+
"AWS4-HMAC-SHA256",
|
|
6272
|
+
amzDate,
|
|
6273
|
+
credentialScope,
|
|
6274
|
+
sha256(canonicalRequest)
|
|
6275
|
+
].join("\n");
|
|
6276
|
+
const signingKey = getSignatureKey(
|
|
6277
|
+
req.credentials.secretAccessKey,
|
|
6278
|
+
dateStamp,
|
|
6279
|
+
req.region,
|
|
6280
|
+
req.service
|
|
6281
|
+
);
|
|
6282
|
+
const signature = hmacHex(signingKey, stringToSign);
|
|
6283
|
+
const authorization = `AWS4-HMAC-SHA256 Credential=${req.credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
6284
|
+
const result = {
|
|
6285
|
+
...req.headers,
|
|
6286
|
+
"x-amz-date": amzDate,
|
|
6287
|
+
Authorization: authorization
|
|
6288
|
+
};
|
|
6289
|
+
if (req.credentials.sessionToken) {
|
|
6290
|
+
result["x-amz-security-token"] = req.credentials.sessionToken;
|
|
6291
|
+
}
|
|
6292
|
+
return result;
|
|
6293
|
+
}
|
|
6294
|
+
function toDateStamp(d) {
|
|
6295
|
+
return d.toISOString().replace(/[-:T]/g, "").slice(0, 8);
|
|
6296
|
+
}
|
|
6297
|
+
function toAmzDate(d) {
|
|
6298
|
+
return d.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
6299
|
+
}
|
|
6300
|
+
function sha256(data) {
|
|
6301
|
+
return createHash("sha256").update(data, "utf8").digest("hex");
|
|
6302
|
+
}
|
|
6303
|
+
function hmac(key, data) {
|
|
6304
|
+
return createHmac("sha256", key).update(data, "utf8").digest();
|
|
6305
|
+
}
|
|
6306
|
+
function hmacHex(key, data) {
|
|
6307
|
+
return createHmac("sha256", key).update(data, "utf8").digest("hex");
|
|
6308
|
+
}
|
|
6309
|
+
function getSignatureKey(secret, dateStamp, region, service) {
|
|
6310
|
+
const kDate = hmac(`AWS4${secret}`, dateStamp);
|
|
6311
|
+
const kRegion = hmac(kDate, region);
|
|
6312
|
+
const kService = hmac(kRegion, service);
|
|
6313
|
+
return hmac(kService, "aws4_request");
|
|
6314
|
+
}
|
|
6315
|
+
function sortQueryParams(params) {
|
|
6316
|
+
const sorted = [...params.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
6317
|
+
return sorted.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
6318
|
+
}
|
|
6319
|
+
|
|
6320
|
+
// src/providers/bedrock.ts
|
|
6321
|
+
var BedrockProvider = class extends BaseProvider {
|
|
6322
|
+
constructor(config) {
|
|
6323
|
+
super();
|
|
6324
|
+
this.providerName = "bedrock";
|
|
6325
|
+
this.region = config.region;
|
|
6326
|
+
this.defaultModel = config.defaultModel ?? "anthropic.claude-sonnet-4-20250514-v1:0";
|
|
6327
|
+
this.defaultHeaders = config.defaultHeaders ?? {};
|
|
6328
|
+
this.credentials = {
|
|
6329
|
+
accessKeyId: config.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? "",
|
|
6330
|
+
secretAccessKey: config.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? "",
|
|
6331
|
+
sessionToken: config.sessionToken ?? process.env.AWS_SESSION_TOKEN
|
|
6332
|
+
};
|
|
6333
|
+
if (!this.credentials.accessKeyId || !this.credentials.secretAccessKey) {
|
|
6334
|
+
throw new ProviderError(
|
|
6335
|
+
"AWS credentials required: provide accessKeyId/secretAccessKey or set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars",
|
|
6336
|
+
"bedrock"
|
|
6337
|
+
);
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
async complete(request) {
|
|
6341
|
+
const modelId = request.model || this.defaultModel;
|
|
6342
|
+
const body = this.buildConverseBody(request);
|
|
6343
|
+
const json = await this.doFetch(modelId, body);
|
|
6344
|
+
return this.parseResponse(json, modelId);
|
|
6345
|
+
}
|
|
6346
|
+
async *completeStream(request) {
|
|
6347
|
+
const response = await this.complete(request);
|
|
6348
|
+
yield {
|
|
6349
|
+
id: response.id,
|
|
6350
|
+
model: response.model,
|
|
6351
|
+
delta: {
|
|
6352
|
+
content: response.content ?? void 0,
|
|
6353
|
+
toolCalls: response.toolCalls.length > 0 ? response.toolCalls : void 0
|
|
6354
|
+
},
|
|
6355
|
+
usage: response.usage,
|
|
6356
|
+
finishReason: response.finishReason
|
|
6357
|
+
};
|
|
6358
|
+
}
|
|
6359
|
+
// ─── Internals ───────────────────────────────────────────────────────────
|
|
6360
|
+
buildConverseBody(request) {
|
|
6361
|
+
const { system, messages } = this.splitSystemMessages(request.messages);
|
|
6362
|
+
const body = {
|
|
6363
|
+
messages: messages.map((m) => this.toBedrockMessage(m))
|
|
6364
|
+
};
|
|
6365
|
+
if (system.length > 0) {
|
|
6366
|
+
body.system = system.map((s) => ({ text: s }));
|
|
6367
|
+
}
|
|
6368
|
+
const inferenceConfig = {};
|
|
6369
|
+
if (request.maxTokens != null) inferenceConfig.maxTokens = request.maxTokens;
|
|
6370
|
+
if (request.temperature != null) inferenceConfig.temperature = request.temperature;
|
|
6371
|
+
if (request.topP != null) inferenceConfig.topP = request.topP;
|
|
6372
|
+
if (request.stop) inferenceConfig.stopSequences = request.stop;
|
|
6373
|
+
if (Object.keys(inferenceConfig).length > 0) body.inferenceConfig = inferenceConfig;
|
|
6374
|
+
if (request.tools?.length) {
|
|
6375
|
+
body.toolConfig = {
|
|
6376
|
+
tools: request.tools.map((t) => this.toBedrockTool(t))
|
|
6377
|
+
};
|
|
6378
|
+
if (request.toolChoice != null) {
|
|
6379
|
+
if (request.toolChoice === "auto") {
|
|
6380
|
+
body.toolConfig.toolChoice = { auto: {} };
|
|
6381
|
+
} else if (request.toolChoice === "required") {
|
|
6382
|
+
body.toolConfig.toolChoice = { any: {} };
|
|
6383
|
+
} else if (typeof request.toolChoice === "object") {
|
|
6384
|
+
body.toolConfig.toolChoice = { tool: { name: request.toolChoice.name } };
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
if (request.extra) {
|
|
6389
|
+
Object.assign(body, request.extra);
|
|
6390
|
+
}
|
|
6391
|
+
return body;
|
|
6392
|
+
}
|
|
6393
|
+
splitSystemMessages(messages) {
|
|
6394
|
+
const system = [];
|
|
6395
|
+
const rest = [];
|
|
6396
|
+
for (const m of messages) {
|
|
6397
|
+
if (m.role === "system") {
|
|
6398
|
+
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
6399
|
+
if (text) system.push(text);
|
|
6400
|
+
} else {
|
|
6401
|
+
rest.push(m);
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
return { system, messages: rest };
|
|
6405
|
+
}
|
|
6406
|
+
toBedrockMessage(m) {
|
|
6407
|
+
if (m.role === "tool" && m.toolCallId) {
|
|
6408
|
+
const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
6409
|
+
return {
|
|
6410
|
+
role: "user",
|
|
6411
|
+
content: [
|
|
6412
|
+
{
|
|
6413
|
+
toolResult: {
|
|
6414
|
+
toolUseId: m.toolCallId,
|
|
6415
|
+
content: [{ text }],
|
|
6416
|
+
status: "success"
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
]
|
|
6420
|
+
};
|
|
6421
|
+
}
|
|
6422
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
6423
|
+
const content2 = [];
|
|
6424
|
+
if (typeof m.content === "string" && m.content) {
|
|
6425
|
+
content2.push({ text: m.content });
|
|
6426
|
+
}
|
|
6427
|
+
for (const tc of m.toolCalls) {
|
|
6428
|
+
content2.push({
|
|
6429
|
+
toolUse: {
|
|
6430
|
+
toolUseId: tc.id,
|
|
6431
|
+
name: tc.name,
|
|
6432
|
+
input: safeJsonParse2(tc.arguments)
|
|
6433
|
+
}
|
|
6434
|
+
});
|
|
6435
|
+
}
|
|
6436
|
+
return { role: "assistant", content: content2 };
|
|
6437
|
+
}
|
|
6438
|
+
const content = [];
|
|
6439
|
+
if (typeof m.content === "string") {
|
|
6440
|
+
content.push({ text: m.content });
|
|
6441
|
+
} else if (Array.isArray(m.content)) {
|
|
6442
|
+
for (const part of m.content) {
|
|
6443
|
+
if (part.type === "text") {
|
|
6444
|
+
content.push({ text: part.text });
|
|
6445
|
+
} else if (part.type === "image") {
|
|
6446
|
+
const src = part.source;
|
|
6447
|
+
if (src.type === "base64") {
|
|
6448
|
+
content.push({
|
|
6449
|
+
image: {
|
|
6450
|
+
format: mediaTypeToFormat(src.mediaType),
|
|
6451
|
+
source: { bytes: src.data }
|
|
6452
|
+
}
|
|
6453
|
+
});
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
return { role: m.role === "user" ? "user" : "assistant", content };
|
|
6459
|
+
}
|
|
6460
|
+
toBedrockTool(t) {
|
|
6461
|
+
return {
|
|
6462
|
+
toolSpec: {
|
|
6463
|
+
name: t.name,
|
|
6464
|
+
description: t.description,
|
|
6465
|
+
inputSchema: { json: t.parameters }
|
|
6466
|
+
}
|
|
6467
|
+
};
|
|
6468
|
+
}
|
|
6469
|
+
async doFetch(modelId, body) {
|
|
6470
|
+
const host = `bedrock-runtime.${this.region}.amazonaws.com`;
|
|
6471
|
+
const url = `https://${host}/model/${encodeURIComponent(modelId)}/converse`;
|
|
6472
|
+
const bodyStr = JSON.stringify(body);
|
|
6473
|
+
const headers = signRequest({
|
|
6474
|
+
method: "POST",
|
|
6475
|
+
url,
|
|
6476
|
+
headers: {
|
|
6477
|
+
"Content-Type": "application/json",
|
|
6478
|
+
Accept: "application/json",
|
|
6479
|
+
...this.defaultHeaders
|
|
6480
|
+
},
|
|
6481
|
+
body: bodyStr,
|
|
6482
|
+
region: this.region,
|
|
6483
|
+
service: "bedrock",
|
|
6484
|
+
credentials: this.credentials
|
|
6485
|
+
});
|
|
6486
|
+
const res = await fetch(url, {
|
|
6487
|
+
method: "POST",
|
|
6488
|
+
headers,
|
|
6489
|
+
body: bodyStr
|
|
6490
|
+
});
|
|
6491
|
+
if (!res.ok) {
|
|
6492
|
+
const text = await res.text().catch(() => "");
|
|
6493
|
+
throw new ProviderError(
|
|
6494
|
+
`Bedrock API error ${res.status}: ${text}`,
|
|
6495
|
+
this.providerName,
|
|
6496
|
+
res.status,
|
|
6497
|
+
text
|
|
6498
|
+
);
|
|
6499
|
+
}
|
|
6500
|
+
return res.json();
|
|
6501
|
+
}
|
|
6502
|
+
parseResponse(json, modelId) {
|
|
6503
|
+
const output = json.output?.message;
|
|
6504
|
+
const textParts = [];
|
|
6505
|
+
const toolCalls = [];
|
|
6506
|
+
for (const block of output?.content ?? []) {
|
|
6507
|
+
if (block.text) {
|
|
6508
|
+
textParts.push(block.text);
|
|
6509
|
+
} else if (block.toolUse) {
|
|
6510
|
+
toolCalls.push({
|
|
6511
|
+
id: block.toolUse.toolUseId,
|
|
6512
|
+
name: block.toolUse.name,
|
|
6513
|
+
arguments: typeof block.toolUse.input === "string" ? block.toolUse.input : JSON.stringify(block.toolUse.input ?? {})
|
|
6514
|
+
});
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
const usage = json.usage ?? {};
|
|
6518
|
+
const tokenUsage = {
|
|
6519
|
+
inputTokens: usage.inputTokens ?? 0,
|
|
6520
|
+
outputTokens: usage.outputTokens ?? 0,
|
|
6521
|
+
totalTokens: usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0),
|
|
6522
|
+
cachedInputTokens: usage.cacheReadInputTokenCount ?? 0,
|
|
6523
|
+
cachedWriteTokens: usage.cacheWriteInputTokenCount ?? 0,
|
|
6524
|
+
cachedOutputTokens: 0,
|
|
6525
|
+
reasoningTokens: 0
|
|
6526
|
+
};
|
|
6527
|
+
return {
|
|
6528
|
+
id: json.requestId ?? json.$metadata?.requestId ?? "",
|
|
6529
|
+
model: modelId,
|
|
6530
|
+
content: textParts.join("") || null,
|
|
6531
|
+
toolCalls,
|
|
6532
|
+
usage: tokenUsage,
|
|
6533
|
+
finishReason: mapBedrockStopReason(json.stopReason),
|
|
6534
|
+
raw: json
|
|
6535
|
+
};
|
|
6536
|
+
}
|
|
6537
|
+
};
|
|
6538
|
+
function mapBedrockStopReason(reason) {
|
|
6539
|
+
switch (reason) {
|
|
6540
|
+
case "end_turn":
|
|
6541
|
+
case "stop_sequence":
|
|
6542
|
+
return "stop";
|
|
6543
|
+
case "tool_use":
|
|
6544
|
+
return "tool_calls";
|
|
6545
|
+
case "max_tokens":
|
|
6546
|
+
return "length";
|
|
6547
|
+
case "content_filtered":
|
|
6548
|
+
case "guardrail":
|
|
6549
|
+
return "content_filter";
|
|
6550
|
+
default:
|
|
6551
|
+
return "stop";
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
function mediaTypeToFormat(mediaType) {
|
|
6555
|
+
const map = {
|
|
6556
|
+
"image/jpeg": "jpeg",
|
|
6557
|
+
"image/png": "png",
|
|
6558
|
+
"image/gif": "gif",
|
|
6559
|
+
"image/webp": "webp"
|
|
6560
|
+
};
|
|
6561
|
+
return map[mediaType] ?? "png";
|
|
6562
|
+
}
|
|
6563
|
+
function safeJsonParse2(str) {
|
|
6564
|
+
try {
|
|
6565
|
+
return JSON.parse(str);
|
|
6566
|
+
} catch {
|
|
6567
|
+
return {};
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6571
|
+
// src/providers/vertex.ts
|
|
6572
|
+
var VertexProvider = class extends BaseProvider {
|
|
6573
|
+
constructor(config) {
|
|
6574
|
+
super();
|
|
6575
|
+
this.providerName = "vertex";
|
|
6576
|
+
this.tokenExpiry = 0;
|
|
6577
|
+
this.projectId = config.projectId;
|
|
6578
|
+
this.location = config.location ?? "us-central1";
|
|
6579
|
+
this.defaultModel = config.defaultModel ?? "gemini-2.0-flash";
|
|
6580
|
+
this.defaultHeaders = config.defaultHeaders ?? {};
|
|
6581
|
+
if (config.accessToken) {
|
|
6582
|
+
this.accessToken = config.accessToken;
|
|
6583
|
+
this.tokenExpiry = Date.now() + 36e5;
|
|
6584
|
+
}
|
|
6585
|
+
if (config.serviceAccountJson) {
|
|
6586
|
+
this.serviceAccountJson = typeof config.serviceAccountJson === "string" ? JSON.parse(config.serviceAccountJson) : config.serviceAccountJson;
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
async complete(request) {
|
|
6590
|
+
const modelId = request.model || this.defaultModel;
|
|
6591
|
+
const body = this.buildGeminiBody(request);
|
|
6592
|
+
const url = this.buildUrl(modelId, false);
|
|
6593
|
+
const res = await this.doFetch(url, body);
|
|
6594
|
+
const json = await res.json();
|
|
6595
|
+
return this.parseResponse(json, modelId);
|
|
6596
|
+
}
|
|
6597
|
+
async *completeStream(request) {
|
|
6598
|
+
const modelId = request.model || this.defaultModel;
|
|
6599
|
+
const body = this.buildGeminiBody(request);
|
|
6600
|
+
const url = this.buildUrl(modelId, true);
|
|
6601
|
+
const res = await this.doFetch(url, body);
|
|
6602
|
+
if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
|
|
6603
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
6604
|
+
if (contentType.includes("text/event-stream")) {
|
|
6605
|
+
for await (const event of parseSSEStream(res.body)) {
|
|
6606
|
+
if (event.data === "[DONE]") break;
|
|
6607
|
+
let chunk;
|
|
6608
|
+
try {
|
|
6609
|
+
chunk = JSON.parse(event.data);
|
|
6610
|
+
} catch {
|
|
6611
|
+
continue;
|
|
6612
|
+
}
|
|
6613
|
+
yield this.parseStreamChunk(chunk, modelId);
|
|
6614
|
+
}
|
|
6615
|
+
} else {
|
|
6616
|
+
const reader = res.body.getReader();
|
|
6617
|
+
const decoder = new TextDecoder();
|
|
6618
|
+
let buffer = "";
|
|
6619
|
+
try {
|
|
6620
|
+
while (true) {
|
|
6621
|
+
const { done, value } = await reader.read();
|
|
6622
|
+
if (done) break;
|
|
6623
|
+
buffer += decoder.decode(value, { stream: true });
|
|
6624
|
+
const lines = buffer.split("\n");
|
|
6625
|
+
buffer = lines.pop() ?? "";
|
|
6626
|
+
for (const line of lines) {
|
|
6627
|
+
const trimmed = line.trim().replace(/^,/, "").replace(/^\[/, "").replace(/\]$/, "");
|
|
6628
|
+
if (!trimmed || trimmed === "[" || trimmed === "]") continue;
|
|
6629
|
+
try {
|
|
6630
|
+
const chunk = JSON.parse(trimmed);
|
|
6631
|
+
yield this.parseStreamChunk(chunk, modelId);
|
|
6632
|
+
} catch {
|
|
6633
|
+
buffer = line + "\n" + buffer;
|
|
6634
|
+
break;
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
const remaining = buffer.trim().replace(/^,/, "").replace(/^\[/, "").replace(/\]$/, "");
|
|
6639
|
+
if (remaining && remaining !== "[" && remaining !== "]") {
|
|
6640
|
+
try {
|
|
6641
|
+
const chunk = JSON.parse(remaining);
|
|
6642
|
+
yield this.parseStreamChunk(chunk, modelId);
|
|
6643
|
+
} catch {
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
} finally {
|
|
6647
|
+
reader.releaseLock();
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6651
|
+
// ─── Internals ───────────────────────────────────────────────────────────
|
|
6652
|
+
buildUrl(modelId, stream) {
|
|
6653
|
+
const base = `https://${this.location}-aiplatform.googleapis.com/v1`;
|
|
6654
|
+
const path2 = `projects/${this.projectId}/locations/${this.location}/publishers/google/models/${modelId}`;
|
|
6655
|
+
const action = stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
6656
|
+
return `${base}/${path2}:${action}`;
|
|
6657
|
+
}
|
|
6658
|
+
buildGeminiBody(request) {
|
|
6659
|
+
const { system, contents } = this.buildContents(request.messages);
|
|
6660
|
+
const body = { contents };
|
|
6661
|
+
if (system) {
|
|
6662
|
+
body.systemInstruction = { parts: [{ text: system }] };
|
|
6663
|
+
}
|
|
6664
|
+
const generationConfig = {};
|
|
6665
|
+
if (request.maxTokens != null) generationConfig.maxOutputTokens = request.maxTokens;
|
|
6666
|
+
if (request.temperature != null) generationConfig.temperature = request.temperature;
|
|
6667
|
+
if (request.topP != null) generationConfig.topP = request.topP;
|
|
6668
|
+
if (request.stop) generationConfig.stopSequences = request.stop;
|
|
6669
|
+
if (request.responseFormat) {
|
|
6670
|
+
if (request.responseFormat.type === "json_schema" || request.responseFormat.type === "json_object") {
|
|
6671
|
+
generationConfig.responseMimeType = "application/json";
|
|
6672
|
+
if (request.responseFormat.schema) {
|
|
6673
|
+
generationConfig.responseSchema = request.responseFormat.schema;
|
|
6674
|
+
}
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
6678
|
+
if (request.tools?.length) {
|
|
6679
|
+
body.tools = [
|
|
6680
|
+
{
|
|
6681
|
+
functionDeclarations: request.tools.map((t) => this.toGeminiTool(t))
|
|
6682
|
+
}
|
|
6683
|
+
];
|
|
6684
|
+
if (request.toolChoice != null) {
|
|
6685
|
+
const mode = request.toolChoice === "auto" ? "AUTO" : request.toolChoice === "required" ? "ANY" : request.toolChoice === "none" ? "NONE" : "AUTO";
|
|
6686
|
+
body.toolConfig = { functionCallingConfig: { mode } };
|
|
6687
|
+
}
|
|
6688
|
+
}
|
|
6689
|
+
if (request.extra) {
|
|
6690
|
+
Object.assign(body, request.extra);
|
|
6691
|
+
}
|
|
6692
|
+
return body;
|
|
6693
|
+
}
|
|
6694
|
+
buildContents(messages) {
|
|
6695
|
+
const systemParts = [];
|
|
6696
|
+
const contents = [];
|
|
6697
|
+
for (const m of messages) {
|
|
6698
|
+
if (m.role === "system") {
|
|
6699
|
+
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
6700
|
+
if (text) systemParts.push(text);
|
|
6701
|
+
continue;
|
|
6702
|
+
}
|
|
6703
|
+
contents.push(this.toGeminiContent(m));
|
|
6704
|
+
}
|
|
6705
|
+
return {
|
|
6706
|
+
system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
|
|
6707
|
+
contents
|
|
6708
|
+
};
|
|
6709
|
+
}
|
|
6710
|
+
toGeminiContent(m) {
|
|
6711
|
+
if (m.role === "tool" && m.toolCallId) {
|
|
6712
|
+
const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
6713
|
+
let response;
|
|
6714
|
+
try {
|
|
6715
|
+
response = JSON.parse(text);
|
|
6716
|
+
} catch {
|
|
6717
|
+
response = { result: text };
|
|
6718
|
+
}
|
|
6719
|
+
return {
|
|
6720
|
+
role: "user",
|
|
6721
|
+
parts: [
|
|
6722
|
+
{
|
|
6723
|
+
functionResponse: {
|
|
6724
|
+
name: m.name ?? m.toolCallId,
|
|
6725
|
+
response
|
|
6726
|
+
}
|
|
6727
|
+
}
|
|
6728
|
+
]
|
|
6729
|
+
};
|
|
6730
|
+
}
|
|
6731
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
6732
|
+
const parts2 = [];
|
|
6733
|
+
if (typeof m.content === "string" && m.content) {
|
|
6734
|
+
parts2.push({ text: m.content });
|
|
6735
|
+
}
|
|
6736
|
+
for (const tc of m.toolCalls) {
|
|
6737
|
+
parts2.push({
|
|
6738
|
+
functionCall: {
|
|
6739
|
+
name: tc.name,
|
|
6740
|
+
args: safeJsonParse3(tc.arguments)
|
|
6741
|
+
}
|
|
6742
|
+
});
|
|
6743
|
+
}
|
|
6744
|
+
return { role: "model", parts: parts2 };
|
|
6745
|
+
}
|
|
6746
|
+
const role = m.role === "assistant" ? "model" : "user";
|
|
6747
|
+
const parts = [];
|
|
6748
|
+
if (typeof m.content === "string") {
|
|
6749
|
+
parts.push({ text: m.content });
|
|
6750
|
+
} else if (Array.isArray(m.content)) {
|
|
6751
|
+
for (const part of m.content) {
|
|
6752
|
+
if (part.type === "text") {
|
|
6753
|
+
parts.push({ text: part.text });
|
|
6754
|
+
} else if (part.type === "image") {
|
|
6755
|
+
const src = part.source;
|
|
6756
|
+
if (src.type === "base64") {
|
|
6757
|
+
parts.push({
|
|
6758
|
+
inlineData: { mimeType: src.mediaType, data: src.data }
|
|
6759
|
+
});
|
|
6760
|
+
} else {
|
|
6761
|
+
parts.push({
|
|
6762
|
+
fileData: { mimeType: "image/jpeg", fileUri: src.url }
|
|
6763
|
+
});
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
return { role, parts };
|
|
6769
|
+
}
|
|
6770
|
+
toGeminiTool(t) {
|
|
6771
|
+
return {
|
|
6772
|
+
name: t.name,
|
|
6773
|
+
description: t.description,
|
|
6774
|
+
parameters: t.parameters
|
|
6775
|
+
};
|
|
6776
|
+
}
|
|
6777
|
+
async getAccessToken() {
|
|
6778
|
+
if (this.accessToken && Date.now() < this.tokenExpiry) {
|
|
6779
|
+
return this.accessToken;
|
|
6780
|
+
}
|
|
6781
|
+
if (this.serviceAccountJson) {
|
|
6782
|
+
const token = await this.getTokenFromServiceAccount(this.serviceAccountJson);
|
|
6783
|
+
this.accessToken = token;
|
|
6784
|
+
this.tokenExpiry = Date.now() + 35e5;
|
|
6785
|
+
return token;
|
|
6786
|
+
}
|
|
6787
|
+
try {
|
|
6788
|
+
const { execSync } = await import('child_process');
|
|
6789
|
+
const token = execSync("gcloud auth print-access-token", { encoding: "utf8" }).trim();
|
|
6790
|
+
this.accessToken = token;
|
|
6791
|
+
this.tokenExpiry = Date.now() + 35e5;
|
|
6792
|
+
return token;
|
|
6793
|
+
} catch {
|
|
6794
|
+
throw new ProviderError(
|
|
6795
|
+
"No access token available. Provide accessToken, serviceAccountJson, or ensure gcloud CLI is authenticated.",
|
|
6796
|
+
this.providerName
|
|
6797
|
+
);
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
async getTokenFromServiceAccount(sa) {
|
|
6801
|
+
const { createSign } = await import('crypto');
|
|
6802
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
6803
|
+
const header = { alg: "RS256", typ: "JWT" };
|
|
6804
|
+
const claims = {
|
|
6805
|
+
iss: sa.client_email,
|
|
6806
|
+
scope: "https://www.googleapis.com/auth/cloud-platform",
|
|
6807
|
+
aud: "https://oauth2.googleapis.com/token",
|
|
6808
|
+
iat: now,
|
|
6809
|
+
exp: now + 3600
|
|
6810
|
+
};
|
|
6811
|
+
const segments = [
|
|
6812
|
+
base64url(JSON.stringify(header)),
|
|
6813
|
+
base64url(JSON.stringify(claims))
|
|
6814
|
+
];
|
|
6815
|
+
const signingInput = segments.join(".");
|
|
6816
|
+
const sign = createSign("RSA-SHA256");
|
|
6817
|
+
sign.update(signingInput);
|
|
6818
|
+
const signature = sign.sign(sa.private_key, "base64url");
|
|
6819
|
+
const jwt = `${signingInput}.${signature}`;
|
|
6820
|
+
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
6821
|
+
method: "POST",
|
|
6822
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
6823
|
+
body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`
|
|
6824
|
+
});
|
|
6825
|
+
if (!tokenRes.ok) {
|
|
6826
|
+
const text = await tokenRes.text().catch(() => "");
|
|
6827
|
+
throw new ProviderError(`Failed to get Vertex access token: ${text}`, this.providerName);
|
|
6828
|
+
}
|
|
6829
|
+
const tokenJson = await tokenRes.json();
|
|
6830
|
+
return tokenJson.access_token;
|
|
6831
|
+
}
|
|
6832
|
+
async doFetch(url, body) {
|
|
6833
|
+
const token = await this.getAccessToken();
|
|
6834
|
+
const res = await fetch(url, {
|
|
6835
|
+
method: "POST",
|
|
6836
|
+
headers: {
|
|
6837
|
+
"Content-Type": "application/json",
|
|
6838
|
+
Authorization: `Bearer ${token}`,
|
|
6839
|
+
...this.defaultHeaders
|
|
6840
|
+
},
|
|
6841
|
+
body: JSON.stringify(body)
|
|
6842
|
+
});
|
|
6843
|
+
if (!res.ok) {
|
|
6844
|
+
const text = await res.text().catch(() => "");
|
|
6845
|
+
throw new ProviderError(
|
|
6846
|
+
`Vertex AI API error ${res.status}: ${text}`,
|
|
6847
|
+
this.providerName,
|
|
6848
|
+
res.status,
|
|
6849
|
+
text
|
|
6850
|
+
);
|
|
6851
|
+
}
|
|
6852
|
+
return res;
|
|
6853
|
+
}
|
|
6854
|
+
parseResponse(json, modelId) {
|
|
6855
|
+
const candidate = json.candidates?.[0];
|
|
6856
|
+
const parts = candidate?.content?.parts ?? [];
|
|
6857
|
+
const textParts = [];
|
|
6858
|
+
const toolCalls = [];
|
|
6859
|
+
let toolCallIdx = 0;
|
|
6860
|
+
for (const part of parts) {
|
|
6861
|
+
if (part.text) {
|
|
6862
|
+
textParts.push(part.text);
|
|
6863
|
+
} else if (part.functionCall) {
|
|
6864
|
+
toolCalls.push({
|
|
6865
|
+
id: `call_${toolCallIdx++}`,
|
|
6866
|
+
name: part.functionCall.name,
|
|
6867
|
+
arguments: JSON.stringify(part.functionCall.args ?? {})
|
|
6868
|
+
});
|
|
6869
|
+
}
|
|
6870
|
+
}
|
|
6871
|
+
const usageMeta = json.usageMetadata ?? {};
|
|
6872
|
+
const tokenUsage = {
|
|
6873
|
+
inputTokens: usageMeta.promptTokenCount ?? 0,
|
|
6874
|
+
outputTokens: usageMeta.candidatesTokenCount ?? 0,
|
|
6875
|
+
totalTokens: usageMeta.totalTokenCount ?? 0,
|
|
6876
|
+
cachedInputTokens: usageMeta.cachedContentTokenCount ?? 0,
|
|
6877
|
+
cachedWriteTokens: 0,
|
|
6878
|
+
cachedOutputTokens: 0,
|
|
6879
|
+
reasoningTokens: usageMeta.thoughtsTokenCount ?? 0
|
|
6880
|
+
};
|
|
6881
|
+
return {
|
|
6882
|
+
id: json.responseId ?? "",
|
|
6883
|
+
model: modelId,
|
|
6884
|
+
content: textParts.join("") || null,
|
|
6885
|
+
toolCalls,
|
|
6886
|
+
usage: tokenUsage,
|
|
6887
|
+
finishReason: mapGeminiFinishReason(candidate?.finishReason),
|
|
6888
|
+
raw: json
|
|
6889
|
+
};
|
|
6890
|
+
}
|
|
6891
|
+
parseStreamChunk(chunk, modelId) {
|
|
6892
|
+
const candidate = chunk.candidates?.[0];
|
|
6893
|
+
const parts = candidate?.content?.parts ?? [];
|
|
6894
|
+
const delta = {};
|
|
6895
|
+
for (const part of parts) {
|
|
6896
|
+
if (part.text) {
|
|
6897
|
+
delta.content = (delta.content ?? "") + part.text;
|
|
6898
|
+
} else if (part.functionCall) {
|
|
6899
|
+
if (!delta.toolCalls) delta.toolCalls = [];
|
|
6900
|
+
delta.toolCalls.push({
|
|
6901
|
+
id: `call_${delta.toolCalls.length}`,
|
|
6902
|
+
name: part.functionCall.name,
|
|
6903
|
+
arguments: JSON.stringify(part.functionCall.args ?? {})
|
|
6904
|
+
});
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
let usage;
|
|
6908
|
+
if (chunk.usageMetadata) {
|
|
6909
|
+
const u = chunk.usageMetadata;
|
|
6910
|
+
usage = {
|
|
6911
|
+
inputTokens: u.promptTokenCount ?? 0,
|
|
6912
|
+
outputTokens: u.candidatesTokenCount ?? 0,
|
|
6913
|
+
totalTokens: u.totalTokenCount ?? 0,
|
|
6914
|
+
cachedInputTokens: u.cachedContentTokenCount ?? 0,
|
|
6915
|
+
cachedWriteTokens: 0,
|
|
6916
|
+
cachedOutputTokens: 0,
|
|
6917
|
+
reasoningTokens: u.thoughtsTokenCount ?? 0
|
|
6918
|
+
};
|
|
6919
|
+
}
|
|
6920
|
+
return {
|
|
6921
|
+
id: chunk.responseId ?? "",
|
|
6922
|
+
model: modelId,
|
|
6923
|
+
delta,
|
|
6924
|
+
usage,
|
|
6925
|
+
finishReason: mapGeminiFinishReason(candidate?.finishReason)
|
|
6926
|
+
};
|
|
6927
|
+
}
|
|
6928
|
+
};
|
|
6929
|
+
function mapGeminiFinishReason(reason) {
|
|
6930
|
+
switch (reason) {
|
|
6931
|
+
case "STOP":
|
|
6932
|
+
return "stop";
|
|
6933
|
+
case "MAX_TOKENS":
|
|
6934
|
+
return "length";
|
|
6935
|
+
case "SAFETY":
|
|
6936
|
+
case "RECITATION":
|
|
6937
|
+
return "content_filter";
|
|
6938
|
+
case "TOOL_CALLS":
|
|
6939
|
+
return "tool_calls";
|
|
6940
|
+
default:
|
|
6941
|
+
return "stop";
|
|
6942
|
+
}
|
|
6943
|
+
}
|
|
6944
|
+
function base64url(str) {
|
|
6945
|
+
return Buffer.from(str).toString("base64url");
|
|
6946
|
+
}
|
|
6947
|
+
function safeJsonParse3(str) {
|
|
6948
|
+
try {
|
|
6949
|
+
return JSON.parse(str);
|
|
6950
|
+
} catch {
|
|
6951
|
+
return {};
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
function fromNativeProvider(provider, options) {
|
|
6955
|
+
let boundTools = options?.__tools;
|
|
6956
|
+
const modelName = options?.model ?? provider.defaultModel ?? provider.providerName;
|
|
6957
|
+
const adapted = {
|
|
6958
|
+
modelName,
|
|
6959
|
+
capabilities: resolveCapabilities(provider.providerName),
|
|
6960
|
+
invoke: async (messages, _invokeOptions) => {
|
|
6961
|
+
const request = buildRequest(messages, boundTools, options, _invokeOptions);
|
|
6962
|
+
const response = await provider.complete(request);
|
|
6963
|
+
return toBaseChatMessage(response);
|
|
6964
|
+
},
|
|
6965
|
+
stream: async function* (messages, _invokeOptions) {
|
|
6966
|
+
const request = buildRequest(messages, boundTools, options, _invokeOptions);
|
|
6967
|
+
request.stream = true;
|
|
6968
|
+
let fullContent = "";
|
|
6969
|
+
let allToolCalls = [];
|
|
6970
|
+
let lastUsage;
|
|
6971
|
+
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
6972
|
+
for await (const chunk of provider.completeStream(request)) {
|
|
6973
|
+
if (chunk.delta.content) {
|
|
6974
|
+
fullContent += chunk.delta.content;
|
|
6975
|
+
yield chunk.delta.content;
|
|
6976
|
+
}
|
|
6977
|
+
if (chunk.delta.toolCalls) {
|
|
6978
|
+
for (const tc of chunk.delta.toolCalls) {
|
|
6979
|
+
if (tc.id) {
|
|
6980
|
+
const existing = toolCallBuffers.get(tc.id);
|
|
6981
|
+
if (existing) {
|
|
6982
|
+
if (tc.arguments) existing.arguments += tc.arguments;
|
|
6983
|
+
} else {
|
|
6984
|
+
toolCallBuffers.set(tc.id, {
|
|
6985
|
+
id: tc.id,
|
|
6986
|
+
name: tc.name ?? "",
|
|
6987
|
+
arguments: tc.arguments ?? ""
|
|
6988
|
+
});
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
if (chunk.usage) lastUsage = chunk.usage;
|
|
6994
|
+
}
|
|
6995
|
+
allToolCalls = [...toolCallBuffers.values()];
|
|
6996
|
+
const finalMessage = assembleMessage(fullContent, allToolCalls, lastUsage);
|
|
6997
|
+
yield finalMessage;
|
|
6998
|
+
},
|
|
6999
|
+
bindTools: (tools, _bindOptions) => {
|
|
7000
|
+
const strict = _bindOptions?.strict ?? false;
|
|
7001
|
+
const toolDefs = tools.map((t) => toToolDefinition(t, strict));
|
|
7002
|
+
return fromNativeProvider(provider, { ...options, __tools: toolDefs });
|
|
7003
|
+
}
|
|
7004
|
+
};
|
|
7005
|
+
return adapted;
|
|
7006
|
+
function buildRequest(messages, tools, opts, invokeOptions) {
|
|
7007
|
+
const req = {
|
|
7008
|
+
model: opts?.model ?? modelName,
|
|
7009
|
+
messages: messages.map(toUnifiedMessage)
|
|
7010
|
+
};
|
|
7011
|
+
if (opts?.temperature != null) req.temperature = opts.temperature;
|
|
7012
|
+
if (opts?.maxTokens != null) req.maxTokens = opts.maxTokens;
|
|
7013
|
+
if (tools?.length) req.tools = tools;
|
|
7014
|
+
if (opts?.extra) req.extra = opts.extra;
|
|
7015
|
+
const rf = invokeOptions?.response_format;
|
|
7016
|
+
if (rf && typeof rf === "object" && rf.type) {
|
|
7017
|
+
if (rf.type === "json_schema" && rf.json_schema) {
|
|
7018
|
+
req.responseFormat = {
|
|
7019
|
+
type: "json_schema",
|
|
7020
|
+
schema: rf.json_schema.schema,
|
|
7021
|
+
name: rf.json_schema.name
|
|
7022
|
+
};
|
|
7023
|
+
} else if (rf.type === "json_object") {
|
|
7024
|
+
req.responseFormat = { type: "json_object" };
|
|
7025
|
+
} else if (rf.type === "text") {
|
|
7026
|
+
req.responseFormat = { type: "text" };
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
if (!req.responseFormat) {
|
|
7030
|
+
const lastMsg = messages[messages.length - 1];
|
|
7031
|
+
if (lastMsg && lastMsg.__responseFormat) {
|
|
7032
|
+
req.responseFormat = lastMsg.__responseFormat;
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
7035
|
+
return req;
|
|
7036
|
+
}
|
|
7037
|
+
}
|
|
7038
|
+
function toUnifiedMessage(msg) {
|
|
7039
|
+
const unified = {
|
|
7040
|
+
role: msg.role,
|
|
7041
|
+
content: convertContent(msg.content)
|
|
7042
|
+
};
|
|
7043
|
+
if (msg.name) unified.name = msg.name;
|
|
7044
|
+
if (msg.tool_call_id) unified.toolCallId = msg.tool_call_id;
|
|
7045
|
+
if (msg.tool_calls?.length) {
|
|
7046
|
+
unified.toolCalls = msg.tool_calls.map((tc) => ({
|
|
7047
|
+
id: tc.id,
|
|
7048
|
+
name: tc.function?.name ?? tc.name ?? "",
|
|
7049
|
+
arguments: tc.function?.arguments ?? tc.arguments ?? "{}"
|
|
7050
|
+
}));
|
|
7051
|
+
}
|
|
7052
|
+
return unified;
|
|
7053
|
+
}
|
|
7054
|
+
function convertContent(content) {
|
|
7055
|
+
if (typeof content === "string") return content;
|
|
7056
|
+
if (!Array.isArray(content)) return String(content ?? "");
|
|
7057
|
+
return content.map((part) => {
|
|
7058
|
+
if (part.type === "text" || !part.type && part.text) {
|
|
7059
|
+
return { type: "text", text: part.text ?? part.content ?? "" };
|
|
7060
|
+
}
|
|
7061
|
+
if (part.type === "image_url") {
|
|
7062
|
+
const img = part.image_url;
|
|
7063
|
+
if (typeof img === "string" || img?.url?.startsWith("http")) {
|
|
7064
|
+
return {
|
|
7065
|
+
type: "image",
|
|
7066
|
+
source: { type: "url", url: typeof img === "string" ? img : img.url }
|
|
7067
|
+
};
|
|
7068
|
+
}
|
|
7069
|
+
if (img?.url?.startsWith("data:")) {
|
|
7070
|
+
const match = img.url.match(/^data:([^;]+);base64,(.+)$/);
|
|
7071
|
+
if (match) {
|
|
7072
|
+
return {
|
|
7073
|
+
type: "image",
|
|
7074
|
+
source: { type: "base64", mediaType: match[1], data: match[2] }
|
|
7075
|
+
};
|
|
7076
|
+
}
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
return { type: "text", text: part.text ?? part.content ?? JSON.stringify(part) };
|
|
7080
|
+
});
|
|
7081
|
+
}
|
|
7082
|
+
function isZodSchema(obj) {
|
|
7083
|
+
return obj != null && typeof obj === "object" && typeof obj.safeParse === "function" && "_def" in obj;
|
|
7084
|
+
}
|
|
7085
|
+
function convertToJsonSchema(schema, strict = false) {
|
|
7086
|
+
if (!schema || typeof schema !== "object") {
|
|
7087
|
+
return { type: "object", properties: {}, required: [], additionalProperties: false };
|
|
7088
|
+
}
|
|
7089
|
+
if (isZodSchema(schema)) {
|
|
7090
|
+
const converted = zodToJsonSchema(schema, {
|
|
7091
|
+
$refStrategy: "none",
|
|
7092
|
+
...strict ? { openaiStrictMode: true } : {}
|
|
7093
|
+
});
|
|
7094
|
+
delete converted["$schema"];
|
|
7095
|
+
if (!converted.type) converted.type = "object";
|
|
7096
|
+
return converted;
|
|
7097
|
+
}
|
|
7098
|
+
if (!schema.type && schema.properties) {
|
|
7099
|
+
return { type: "object", ...schema };
|
|
7100
|
+
}
|
|
7101
|
+
return schema;
|
|
7102
|
+
}
|
|
7103
|
+
function normalizeStrictSchema(schema) {
|
|
7104
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
7105
|
+
const clone2 = { ...schema };
|
|
7106
|
+
if (clone2.type === "object" || clone2.properties) {
|
|
7107
|
+
if (!clone2.type) clone2.type = "object";
|
|
7108
|
+
if (clone2.properties && typeof clone2.properties === "object") {
|
|
7109
|
+
const normalized = {};
|
|
7110
|
+
for (const [k, v] of Object.entries(clone2.properties)) {
|
|
7111
|
+
normalized[k] = normalizeStrictSchema(v);
|
|
7112
|
+
}
|
|
7113
|
+
clone2.properties = normalized;
|
|
7114
|
+
clone2.required = Object.keys(normalized);
|
|
7115
|
+
} else {
|
|
7116
|
+
clone2.properties = {};
|
|
7117
|
+
clone2.required = [];
|
|
7118
|
+
}
|
|
7119
|
+
clone2.additionalProperties = false;
|
|
7120
|
+
}
|
|
7121
|
+
if (clone2.items) clone2.items = normalizeStrictSchema(clone2.items);
|
|
7122
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
7123
|
+
if (Array.isArray(clone2[key])) {
|
|
7124
|
+
clone2[key] = clone2[key].map((s) => normalizeStrictSchema(s));
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
if (clone2.definitions && typeof clone2.definitions === "object") {
|
|
7128
|
+
const nd = {};
|
|
7129
|
+
for (const [k, v] of Object.entries(clone2.definitions)) {
|
|
7130
|
+
nd[k] = normalizeStrictSchema(v);
|
|
7131
|
+
}
|
|
7132
|
+
clone2.definitions = nd;
|
|
7133
|
+
}
|
|
7134
|
+
if (clone2.$defs && typeof clone2.$defs === "object") {
|
|
7135
|
+
const nd = {};
|
|
7136
|
+
for (const [k, v] of Object.entries(clone2.$defs)) {
|
|
7137
|
+
nd[k] = normalizeStrictSchema(v);
|
|
7138
|
+
}
|
|
7139
|
+
clone2.$defs = nd;
|
|
7140
|
+
}
|
|
7141
|
+
return clone2;
|
|
7142
|
+
}
|
|
7143
|
+
function toToolDefinition(tool, strict) {
|
|
7144
|
+
let name;
|
|
7145
|
+
let description;
|
|
7146
|
+
let parameters;
|
|
7147
|
+
if (tool.type === "function" && tool.function) {
|
|
7148
|
+
name = tool.function.name;
|
|
7149
|
+
description = tool.function.description ?? "";
|
|
7150
|
+
parameters = tool.function.parameters ?? { type: "object", properties: {}, additionalProperties: false };
|
|
7151
|
+
} else {
|
|
7152
|
+
name = tool.name ?? "unknown";
|
|
7153
|
+
description = tool.description ?? "";
|
|
7154
|
+
const rawSchema = tool.schema ?? tool.parameters;
|
|
7155
|
+
parameters = convertToJsonSchema(rawSchema, strict);
|
|
7156
|
+
}
|
|
7157
|
+
if (strict) {
|
|
7158
|
+
parameters = normalizeStrictSchema(parameters);
|
|
7159
|
+
}
|
|
7160
|
+
return { name, description, parameters, ...strict ? { strict: true } : {} };
|
|
7161
|
+
}
|
|
7162
|
+
function toBaseChatMessage(response) {
|
|
7163
|
+
const msg = {
|
|
7164
|
+
role: "assistant",
|
|
7165
|
+
content: response.content ?? "",
|
|
7166
|
+
usage: {
|
|
7167
|
+
prompt_tokens: response.usage.inputTokens,
|
|
7168
|
+
completion_tokens: response.usage.outputTokens,
|
|
7169
|
+
total_tokens: response.usage.totalTokens,
|
|
7170
|
+
prompt_tokens_details: {
|
|
7171
|
+
cached_tokens: response.usage.cachedInputTokens
|
|
7172
|
+
},
|
|
7173
|
+
completion_tokens_details: {
|
|
7174
|
+
reasoning_tokens: response.usage.reasoningTokens
|
|
7175
|
+
}
|
|
7176
|
+
},
|
|
7177
|
+
response_metadata: {
|
|
7178
|
+
token_usage: {
|
|
7179
|
+
prompt_tokens: response.usage.inputTokens,
|
|
7180
|
+
completion_tokens: response.usage.outputTokens,
|
|
7181
|
+
total_tokens: response.usage.totalTokens,
|
|
7182
|
+
cached_tokens: response.usage.cachedInputTokens,
|
|
7183
|
+
cache_write_tokens: response.usage.cachedWriteTokens
|
|
7184
|
+
},
|
|
7185
|
+
finish_reason: response.finishReason,
|
|
7186
|
+
model_name: response.model
|
|
7187
|
+
}
|
|
7188
|
+
};
|
|
7189
|
+
if (response.toolCalls.length > 0) {
|
|
7190
|
+
msg.tool_calls = response.toolCalls.map((tc) => ({
|
|
7191
|
+
id: tc.id,
|
|
7192
|
+
type: "function",
|
|
7193
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
7194
|
+
}));
|
|
7195
|
+
}
|
|
7196
|
+
return msg;
|
|
7197
|
+
}
|
|
7198
|
+
function assembleMessage(content, toolCalls, usage) {
|
|
7199
|
+
const msg = {
|
|
7200
|
+
role: "assistant",
|
|
7201
|
+
content
|
|
7202
|
+
};
|
|
7203
|
+
if (usage) {
|
|
7204
|
+
msg.usage = {
|
|
7205
|
+
prompt_tokens: usage.inputTokens,
|
|
7206
|
+
completion_tokens: usage.outputTokens,
|
|
7207
|
+
total_tokens: usage.totalTokens,
|
|
7208
|
+
prompt_tokens_details: {
|
|
7209
|
+
cached_tokens: usage.cachedInputTokens
|
|
7210
|
+
},
|
|
7211
|
+
completion_tokens_details: {
|
|
7212
|
+
reasoning_tokens: usage.reasoningTokens
|
|
7213
|
+
}
|
|
7214
|
+
};
|
|
7215
|
+
msg.response_metadata = {
|
|
7216
|
+
token_usage: {
|
|
7217
|
+
prompt_tokens: usage.inputTokens,
|
|
7218
|
+
completion_tokens: usage.outputTokens,
|
|
7219
|
+
total_tokens: usage.totalTokens,
|
|
7220
|
+
cached_tokens: usage.cachedInputTokens,
|
|
7221
|
+
cache_write_tokens: usage.cachedWriteTokens
|
|
7222
|
+
}
|
|
7223
|
+
};
|
|
7224
|
+
}
|
|
7225
|
+
if (toolCalls.length > 0) {
|
|
7226
|
+
msg.tool_calls = toolCalls.map((tc) => ({
|
|
7227
|
+
id: tc.id,
|
|
7228
|
+
type: "function",
|
|
7229
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
7230
|
+
}));
|
|
7231
|
+
}
|
|
7232
|
+
return msg;
|
|
7233
|
+
}
|
|
7234
|
+
function resolveCapabilities(provider) {
|
|
7235
|
+
switch (provider) {
|
|
7236
|
+
case "openai":
|
|
7237
|
+
case "azure":
|
|
7238
|
+
case "openai-compatible":
|
|
7239
|
+
return {
|
|
7240
|
+
structuredOutput: "native",
|
|
7241
|
+
strictToolCalling: true,
|
|
7242
|
+
streaming: true,
|
|
7243
|
+
provider
|
|
7244
|
+
};
|
|
7245
|
+
case "anthropic":
|
|
7246
|
+
return {
|
|
7247
|
+
structuredOutput: "tool_based",
|
|
7248
|
+
strictToolCalling: false,
|
|
7249
|
+
streaming: true,
|
|
7250
|
+
provider: "anthropic"
|
|
7251
|
+
};
|
|
7252
|
+
case "bedrock":
|
|
7253
|
+
return {
|
|
7254
|
+
structuredOutput: "tool_based",
|
|
7255
|
+
strictToolCalling: false,
|
|
7256
|
+
streaming: false,
|
|
7257
|
+
// streaming uses binary protocol
|
|
7258
|
+
provider: "bedrock"
|
|
7259
|
+
};
|
|
7260
|
+
case "vertex":
|
|
7261
|
+
return {
|
|
7262
|
+
structuredOutput: "native",
|
|
7263
|
+
strictToolCalling: false,
|
|
7264
|
+
streaming: true,
|
|
7265
|
+
provider: "google"
|
|
7266
|
+
};
|
|
7267
|
+
default:
|
|
7268
|
+
return { provider };
|
|
7269
|
+
}
|
|
7270
|
+
}
|
|
7271
|
+
|
|
7272
|
+
// src/providers/index.ts
|
|
7273
|
+
function createProvider(config) {
|
|
7274
|
+
switch (config.provider) {
|
|
7275
|
+
case "openai":
|
|
7276
|
+
return new OpenAIProvider(config);
|
|
7277
|
+
case "anthropic":
|
|
7278
|
+
return new AnthropicProvider(config);
|
|
7279
|
+
case "azure":
|
|
7280
|
+
return new AzureProvider(config);
|
|
7281
|
+
case "openai-compatible":
|
|
7282
|
+
return new OpenAICompatibleProvider(config);
|
|
7283
|
+
case "bedrock":
|
|
7284
|
+
return new BedrockProvider(config);
|
|
7285
|
+
case "vertex":
|
|
7286
|
+
return new VertexProvider(config);
|
|
7287
|
+
default:
|
|
7288
|
+
throw new Error(`Unknown provider: ${config.provider}`);
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
|
|
7292
|
+
export { AnthropicProvider, AzureProvider, BaseProvider, BedrockProvider, DEFAULT_PROFILE_CONFIGS, GuardrailPhase, InMemoryMemoryStore, NativeJsonSchemaStrategy, OpenAICompatibleProvider, OpenAIProvider, ProviderError, StructuredOutputManager, ToolBasedStrategy, VertexProvider, agentVerdictRule, 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, resolveMemoryStore, resolveStrategy, resolveToolApprovalState, restoreSnapshot, runSmartAgentEvalHarness, startStreamingSession, traceSessionToOtlp, withTools, writeSummaryFactsToMemory };
|
|
5155
7293
|
//# sourceMappingURL=index.mjs.map
|
|
5156
7294
|
//# sourceMappingURL=index.mjs.map
|