@axlsdk/axl 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/index.cjs +3062 -183
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +580 -47
- package/dist/index.d.ts +580 -47
- package/dist/index.js +3073 -207
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -130,10 +130,15 @@ __export(index_exports, {
|
|
|
130
130
|
EventStreamOverflowError: () => EventStreamOverflowError,
|
|
131
131
|
GROQ_PROFILE: () => GROQ_PROFILE,
|
|
132
132
|
GeminiProvider: () => GeminiProvider,
|
|
133
|
+
GeminiTranscriptionProvider: () => GeminiTranscriptionProvider,
|
|
133
134
|
GuardrailError: () => GuardrailError,
|
|
134
135
|
InMemoryVectorStore: () => InMemoryVectorStore,
|
|
136
|
+
InvalidModelInputError: () => InvalidModelInputError,
|
|
137
|
+
InvalidTranscriptionInputError: () => InvalidTranscriptionInputError,
|
|
135
138
|
LLAMACPP_PROFILE: () => LLAMACPP_PROFILE,
|
|
136
139
|
LMSTUDIO_PROFILE: () => LMSTUDIO_PROFILE,
|
|
140
|
+
MAX_INLINE_MODEL_INPUT_BYTES: () => MAX_INLINE_MODEL_INPUT_BYTES,
|
|
141
|
+
MAX_INLINE_TRANSCRIPTION_BYTES: () => MAX_INLINE_TRANSCRIPTION_BYTES,
|
|
137
142
|
MISTRAL_PROFILE: () => MISTRAL_PROFILE,
|
|
138
143
|
MaxTurnsError: () => MaxTurnsError,
|
|
139
144
|
McpManager: () => McpManager,
|
|
@@ -149,6 +154,8 @@ __export(index_exports, {
|
|
|
149
154
|
OpenAIEmbedder: () => OpenAIEmbedder,
|
|
150
155
|
OpenAIProvider: () => OpenAIProvider,
|
|
151
156
|
OpenAIResponsesProvider: () => OpenAIResponsesProvider,
|
|
157
|
+
OpenAITranscriptionProvider: () => OpenAITranscriptionProvider,
|
|
158
|
+
OpenRouterTranscriptionProvider: () => OpenRouterTranscriptionProvider,
|
|
152
159
|
ProviderError: () => ProviderError,
|
|
153
160
|
ProviderRegistry: () => ProviderRegistry,
|
|
154
161
|
QuorumNotMet: () => QuorumNotMet,
|
|
@@ -164,7 +171,11 @@ __export(index_exports, {
|
|
|
164
171
|
TimeoutError: () => TimeoutError,
|
|
165
172
|
ToolFailure: () => ToolFailure,
|
|
166
173
|
ToolModelOutputError: () => ToolModelOutputError,
|
|
174
|
+
TranscriptionOperationError: () => TranscriptionOperationError,
|
|
175
|
+
TranscriptionProviderRegistry: () => TranscriptionProviderRegistry,
|
|
167
176
|
UnsupportedEventSchemaVersionError: () => UnsupportedEventSchemaVersionError,
|
|
177
|
+
UnsupportedModelInputError: () => UnsupportedModelInputError,
|
|
178
|
+
UnsupportedTranscriptionInputError: () => UnsupportedTranscriptionInputError,
|
|
168
179
|
VLLM_PROFILE: () => VLLM_PROFILE,
|
|
169
180
|
ValidationError: () => ValidationError,
|
|
170
181
|
VerifyError: () => VerifyError,
|
|
@@ -181,8 +192,10 @@ __export(index_exports, {
|
|
|
181
192
|
findConfig: () => findConfig,
|
|
182
193
|
getEventSchemaVersion: () => getEventSchemaVersion,
|
|
183
194
|
getExecutionEventSchemaVersion: () => getExecutionEventSchemaVersion,
|
|
195
|
+
hasPositiveBillableWork: () => hasPositiveBillableWork,
|
|
184
196
|
hasPositiveTokens: () => hasPositiveTokens,
|
|
185
197
|
importModule: () => importModule,
|
|
198
|
+
inputText: () => inputText,
|
|
186
199
|
isCostBearingLeaf: () => isCostBearingLeaf,
|
|
187
200
|
isRetryableStatus: () => isRetryableStatus,
|
|
188
201
|
isRootLevel: () => isRootLevel,
|
|
@@ -223,6 +236,75 @@ var AxlError = class extends Error {
|
|
|
223
236
|
this.code = code;
|
|
224
237
|
}
|
|
225
238
|
};
|
|
239
|
+
var InvalidModelInputError = class extends AxlError {
|
|
240
|
+
constructor(message) {
|
|
241
|
+
super("INVALID_MODEL_INPUT", message);
|
|
242
|
+
this.name = "InvalidModelInputError";
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
var UnsupportedModelInputError = class extends AxlError {
|
|
246
|
+
provider;
|
|
247
|
+
model;
|
|
248
|
+
modality;
|
|
249
|
+
source;
|
|
250
|
+
constructor(options) {
|
|
251
|
+
super(
|
|
252
|
+
"UNSUPPORTED_MODEL_INPUT",
|
|
253
|
+
`Provider '${options.provider}' model '${options.model}' does not support ${options.feature ?? options.modality}${options.source ? ` from ${options.source}` : ""}`
|
|
254
|
+
);
|
|
255
|
+
this.name = "UnsupportedModelInputError";
|
|
256
|
+
this.provider = options.provider;
|
|
257
|
+
this.model = options.model;
|
|
258
|
+
this.modality = options.modality;
|
|
259
|
+
this.source = options.source;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
var InvalidTranscriptionInputError = class extends AxlError {
|
|
263
|
+
constructor(message) {
|
|
264
|
+
super("INVALID_TRANSCRIPTION_INPUT", message);
|
|
265
|
+
this.name = "InvalidTranscriptionInputError";
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
var UnsupportedTranscriptionInputError = class extends AxlError {
|
|
269
|
+
provider;
|
|
270
|
+
model;
|
|
271
|
+
constructor(options) {
|
|
272
|
+
super(
|
|
273
|
+
"UNSUPPORTED_TRANSCRIPTION_INPUT",
|
|
274
|
+
`Transcription provider '${options.provider}'${options.model ? ` model '${options.model}'` : ""} does not support ${options.feature ?? "this request"}`
|
|
275
|
+
);
|
|
276
|
+
this.name = "UnsupportedTranscriptionInputError";
|
|
277
|
+
this.provider = options.provider;
|
|
278
|
+
this.model = options.model;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
var TranscriptionOperationError = class extends AxlError {
|
|
282
|
+
provider;
|
|
283
|
+
model;
|
|
284
|
+
usage;
|
|
285
|
+
pricingStatus;
|
|
286
|
+
cleanupStatus;
|
|
287
|
+
/** Safe HTTP diagnostics projected from ProviderError when available. */
|
|
288
|
+
status;
|
|
289
|
+
retryable;
|
|
290
|
+
retryAfterMs;
|
|
291
|
+
requestId;
|
|
292
|
+
constructor(options) {
|
|
293
|
+
super("TRANSCRIPTION_PROVIDER_ERROR", "Transcription provider operation failed");
|
|
294
|
+
this.name = "TranscriptionOperationError";
|
|
295
|
+
this.provider = options.provider;
|
|
296
|
+
this.model = options.model;
|
|
297
|
+
this.usage = options.usage;
|
|
298
|
+
this.pricingStatus = options.pricingStatus;
|
|
299
|
+
this.cleanupStatus = options.cleanupStatus;
|
|
300
|
+
this.status = options.status;
|
|
301
|
+
this.retryable = options.retryable;
|
|
302
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
303
|
+
this.requestId = options.requestId;
|
|
304
|
+
if (options.cause !== void 0)
|
|
305
|
+
Object.defineProperty(this, "cause", { enumerable: false, value: options.cause });
|
|
306
|
+
}
|
|
307
|
+
};
|
|
226
308
|
var EventStreamOverflowError = class extends Error {
|
|
227
309
|
maxQueued;
|
|
228
310
|
eventType;
|
|
@@ -380,6 +462,221 @@ var ValidationError = class extends AxlError {
|
|
|
380
462
|
|
|
381
463
|
// src/tool.ts
|
|
382
464
|
var DEFAULT_MAX_STRING_LENGTH = 1e4;
|
|
465
|
+
var MAX_MODEL_ARGUMENT_ISSUES = 8;
|
|
466
|
+
var MAX_MODEL_ARGUMENT_MESSAGE_LENGTH = 2e3;
|
|
467
|
+
var MAX_MODEL_ARGUMENT_LINE_LENGTH = 220;
|
|
468
|
+
var MAX_MODEL_ARGUMENT_PATH_LENGTH = 160;
|
|
469
|
+
var MAX_UNION_DEPTH = 3;
|
|
470
|
+
var MAX_UNION_BRANCHES = 4;
|
|
471
|
+
var MAX_LITERAL_ALTERNATIVES = 10;
|
|
472
|
+
var SAFE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
|
|
473
|
+
"string",
|
|
474
|
+
"number",
|
|
475
|
+
"boolean",
|
|
476
|
+
"null",
|
|
477
|
+
"array",
|
|
478
|
+
"object",
|
|
479
|
+
"integer"
|
|
480
|
+
]);
|
|
481
|
+
var SAFE_STRING_FORMATS = /* @__PURE__ */ new Set([
|
|
482
|
+
"email",
|
|
483
|
+
"url",
|
|
484
|
+
"emoji",
|
|
485
|
+
"uuid",
|
|
486
|
+
"guid",
|
|
487
|
+
"nanoid",
|
|
488
|
+
"cuid",
|
|
489
|
+
"cuid2",
|
|
490
|
+
"ulid",
|
|
491
|
+
"xid",
|
|
492
|
+
"ksuid",
|
|
493
|
+
"datetime",
|
|
494
|
+
"date",
|
|
495
|
+
"time",
|
|
496
|
+
"duration",
|
|
497
|
+
"ipv4",
|
|
498
|
+
"ipv6",
|
|
499
|
+
"cidrv4",
|
|
500
|
+
"cidrv6",
|
|
501
|
+
"base64",
|
|
502
|
+
"base64url",
|
|
503
|
+
"json_string",
|
|
504
|
+
"e164",
|
|
505
|
+
"lowercase",
|
|
506
|
+
"uppercase",
|
|
507
|
+
"jwt"
|
|
508
|
+
]);
|
|
509
|
+
function truncateModelFeedback(value, maximum) {
|
|
510
|
+
return value.length <= maximum ? value : `${value.slice(0, Math.max(0, maximum - 1))}\u2026`;
|
|
511
|
+
}
|
|
512
|
+
var DISPLAY_CONTROL_CHARACTERS = /[\u061C\u200B-\u200F\u202A-\u202E\u2066-\u2069\u2028\u2029]/g;
|
|
513
|
+
function isJsonSchema(value) {
|
|
514
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
515
|
+
}
|
|
516
|
+
function escapeDisplayControls(value) {
|
|
517
|
+
return value.replace(
|
|
518
|
+
DISPLAY_CONTROL_CHARACTERS,
|
|
519
|
+
(character) => `\\u${character.codePointAt(0).toString(16).padStart(4, "0")}`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
function quoteModelText(value) {
|
|
523
|
+
return escapeDisplayControls(JSON.stringify(value));
|
|
524
|
+
}
|
|
525
|
+
function schemaNumber(value) {
|
|
526
|
+
return typeof value === "number" && Number.isFinite(value) ? String(value) : void 0;
|
|
527
|
+
}
|
|
528
|
+
function schemaLiteral(value) {
|
|
529
|
+
if (typeof value === "string") {
|
|
530
|
+
const rendered = quoteModelText(value);
|
|
531
|
+
return rendered.length <= 120 ? rendered : '"<literal omitted>"';
|
|
532
|
+
}
|
|
533
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : void 0;
|
|
534
|
+
if (typeof value === "boolean") return String(value);
|
|
535
|
+
if (value === null) return "null";
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
function schemaType(schema) {
|
|
539
|
+
const type = schema.type;
|
|
540
|
+
return typeof type === "string" && SAFE_SCHEMA_TYPES.has(type) ? type : void 0;
|
|
541
|
+
}
|
|
542
|
+
function compositionBranches(schema) {
|
|
543
|
+
const branches = [];
|
|
544
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
545
|
+
const value = schema[key];
|
|
546
|
+
if (!Array.isArray(value)) continue;
|
|
547
|
+
for (const branch of value.slice(0, MAX_UNION_BRANCHES)) {
|
|
548
|
+
if (isJsonSchema(branch)) branches.push(branch);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return branches;
|
|
552
|
+
}
|
|
553
|
+
function unionBranches(schema) {
|
|
554
|
+
const branches = [];
|
|
555
|
+
for (const key of ["anyOf", "oneOf"]) {
|
|
556
|
+
const value = schema[key];
|
|
557
|
+
if (!Array.isArray(value)) continue;
|
|
558
|
+
for (const branch of value.slice(0, MAX_UNION_BRANCHES)) {
|
|
559
|
+
if (isJsonSchema(branch)) branches.push(branch);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return branches;
|
|
563
|
+
}
|
|
564
|
+
function resolvePathSegment(schema, segment, depth = 0) {
|
|
565
|
+
if (depth >= MAX_UNION_DEPTH) return void 0;
|
|
566
|
+
if (typeof segment === "string") {
|
|
567
|
+
const properties = schema.properties;
|
|
568
|
+
if (isJsonSchema(properties) && Object.hasOwn(properties, segment)) {
|
|
569
|
+
const child = properties[segment];
|
|
570
|
+
if (isJsonSchema(child)) return { node: child, renderedSegment: segment };
|
|
571
|
+
}
|
|
572
|
+
if (isJsonSchema(schema.additionalProperties)) {
|
|
573
|
+
return { node: schema.additionalProperties, renderedSegment: "<key>" };
|
|
574
|
+
}
|
|
575
|
+
} else if (typeof segment === "number" && Number.isSafeInteger(segment) && segment >= 0) {
|
|
576
|
+
const prefixItems = schema.prefixItems;
|
|
577
|
+
if (Array.isArray(prefixItems) && isJsonSchema(prefixItems[segment])) {
|
|
578
|
+
return { node: prefixItems[segment], renderedSegment: String(segment) };
|
|
579
|
+
}
|
|
580
|
+
if (isJsonSchema(schema.items)) {
|
|
581
|
+
return { node: schema.items, renderedSegment: "<index>" };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
for (const branch of compositionBranches(schema)) {
|
|
585
|
+
const resolved = resolvePathSegment(branch, segment, depth + 1);
|
|
586
|
+
if (resolved !== void 0) return resolved;
|
|
587
|
+
}
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
function resolveSchemaPath(providerVisibleSchema, issuePath) {
|
|
591
|
+
if (!isJsonSchema(providerVisibleSchema)) return void 0;
|
|
592
|
+
let node = providerVisibleSchema;
|
|
593
|
+
const segments = [];
|
|
594
|
+
for (const segment of issuePath) {
|
|
595
|
+
const resolved = resolvePathSegment(node, segment);
|
|
596
|
+
if (resolved === void 0) {
|
|
597
|
+
segments.push("<path omitted>");
|
|
598
|
+
return { node, segments };
|
|
599
|
+
}
|
|
600
|
+
node = resolved.node;
|
|
601
|
+
segments.push(resolved.renderedSegment);
|
|
602
|
+
}
|
|
603
|
+
return { node, segments };
|
|
604
|
+
}
|
|
605
|
+
function jsonPointerPath(segments) {
|
|
606
|
+
if (segments.length === 0) return "<root>";
|
|
607
|
+
const pointer = `/${segments.map((segment) => segment.replaceAll("~", "~0").replaceAll("/", "~1")).join("/")}`;
|
|
608
|
+
const rendered = quoteModelText(pointer);
|
|
609
|
+
return rendered.length <= MAX_MODEL_ARGUMENT_PATH_LENGTH ? rendered : '"<path omitted>"';
|
|
610
|
+
}
|
|
611
|
+
function schemaAlternatives(schema, depth = 0) {
|
|
612
|
+
if (depth >= MAX_UNION_DEPTH) return [];
|
|
613
|
+
const constant = "const" in schema ? schemaLiteral(schema.const) : void 0;
|
|
614
|
+
if (constant !== void 0) return [constant];
|
|
615
|
+
if (Array.isArray(schema.enum)) {
|
|
616
|
+
const values = schema.enum.slice(0, MAX_LITERAL_ALTERNATIVES).map(schemaLiteral);
|
|
617
|
+
if (values.every((value) => value !== void 0)) return values;
|
|
618
|
+
}
|
|
619
|
+
const type = schemaType(schema);
|
|
620
|
+
if (type !== void 0) return [type];
|
|
621
|
+
const alternatives = unionBranches(schema).flatMap(
|
|
622
|
+
(branch) => schemaAlternatives(branch, depth + 1)
|
|
623
|
+
);
|
|
624
|
+
return alternatives.slice(0, MAX_LITERAL_ALTERNATIVES);
|
|
625
|
+
}
|
|
626
|
+
function schemaEnumExpectation(schema) {
|
|
627
|
+
if ("const" in schema) {
|
|
628
|
+
const constant = schemaLiteral(schema.const);
|
|
629
|
+
return constant === void 0 ? void 0 : `expected ${constant}`;
|
|
630
|
+
}
|
|
631
|
+
if (!Array.isArray(schema.enum)) return void 0;
|
|
632
|
+
const values = schema.enum.slice(0, MAX_LITERAL_ALTERNATIVES).map(schemaLiteral);
|
|
633
|
+
if (values.some((value) => value === void 0)) return void 0;
|
|
634
|
+
const rendered = values;
|
|
635
|
+
if (rendered.length === 0) return void 0;
|
|
636
|
+
const omission = schema.enum.length > rendered.length ? ", \u2026" : "";
|
|
637
|
+
return `expected ${rendered.length === 1 ? rendered[0] : `one of ${rendered.join(", ")}`}${omission}`;
|
|
638
|
+
}
|
|
639
|
+
function schemaTypeExpectation(schema) {
|
|
640
|
+
const type = schemaType(schema);
|
|
641
|
+
if (type !== void 0) return `expected ${type}`;
|
|
642
|
+
const alternatives = schemaAlternatives(schema);
|
|
643
|
+
return alternatives.length === 0 ? void 0 : `expected one of: ${alternatives.join(", ")}`;
|
|
644
|
+
}
|
|
645
|
+
function schemaSizeExpectation(schema, direction) {
|
|
646
|
+
const type = schemaType(schema);
|
|
647
|
+
const keyword = direction === "small" ? type === "string" ? "minLength" : type === "array" ? "minItems" : "minimum" : type === "string" ? "maxLength" : type === "array" ? "maxItems" : "maximum";
|
|
648
|
+
const value = schemaNumber(schema[keyword]);
|
|
649
|
+
if (value === void 0) return void 0;
|
|
650
|
+
const label = type === "string" ? "string length" : type === "array" ? "array length" : type;
|
|
651
|
+
if (label === void 0) return void 0;
|
|
652
|
+
return `expected ${label} ${direction === "small" ? "at least" : "at most"} ${value}`;
|
|
653
|
+
}
|
|
654
|
+
function schemaExpectation(schema, issueCode) {
|
|
655
|
+
switch (issueCode) {
|
|
656
|
+
case "invalid_type":
|
|
657
|
+
return schemaTypeExpectation(schema) ?? "failed structural validation";
|
|
658
|
+
case "invalid_value":
|
|
659
|
+
return schemaEnumExpectation(schema) ?? schemaTypeExpectation(schema) ?? "failed structural validation";
|
|
660
|
+
case "too_small":
|
|
661
|
+
return schemaSizeExpectation(schema, "small") ?? schemaTypeExpectation(schema) ?? "failed structural validation";
|
|
662
|
+
case "too_big":
|
|
663
|
+
return schemaSizeExpectation(schema, "big") ?? schemaTypeExpectation(schema) ?? "failed structural validation";
|
|
664
|
+
case "not_multiple_of": {
|
|
665
|
+
const multipleOf = schemaNumber(schema.multipleOf);
|
|
666
|
+
return multipleOf === void 0 ? schemaTypeExpectation(schema) ?? "failed structural validation" : `expected a multiple of ${multipleOf}`;
|
|
667
|
+
}
|
|
668
|
+
case "invalid_format": {
|
|
669
|
+
const format = schema.format;
|
|
670
|
+
return typeof format === "string" && SAFE_STRING_FORMATS.has(format) ? `expected valid ${format} format` : schemaTypeExpectation(schema) ?? "failed structural validation";
|
|
671
|
+
}
|
|
672
|
+
case "invalid_union":
|
|
673
|
+
return schemaTypeExpectation(schema) ?? "failed structural validation";
|
|
674
|
+
default: {
|
|
675
|
+
const typeExpectation = schemaTypeExpectation(schema);
|
|
676
|
+
return typeExpectation === void 0 ? "failed structural validation" : `failed structural validation; ${typeExpectation}`;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
383
680
|
var ToolStringLengthError = class extends Error {
|
|
384
681
|
constructor(path, actual, maximum) {
|
|
385
682
|
const renderedPath = path.reduce(
|
|
@@ -395,6 +692,42 @@ var ToolStringLengthError = class extends Error {
|
|
|
395
692
|
this.name = "ToolStringLengthError";
|
|
396
693
|
}
|
|
397
694
|
};
|
|
695
|
+
function toolArgumentModelMessage(error, providerVisibleSchema) {
|
|
696
|
+
try {
|
|
697
|
+
let lines;
|
|
698
|
+
let omitted = false;
|
|
699
|
+
if (error instanceof import_zod.ZodError) {
|
|
700
|
+
if (error.issues.length === 0) return void 0;
|
|
701
|
+
lines = [];
|
|
702
|
+
for (const issue of error.issues.slice(0, MAX_MODEL_ARGUMENT_ISSUES)) {
|
|
703
|
+
const resolved = resolveSchemaPath(providerVisibleSchema, issue.path);
|
|
704
|
+
if (resolved === void 0) return void 0;
|
|
705
|
+
const path = jsonPointerPath(resolved.segments);
|
|
706
|
+
const expectation = schemaExpectation(resolved.node, issue.code);
|
|
707
|
+
lines.push(
|
|
708
|
+
truncateModelFeedback(`- ${path}: ${expectation}`, MAX_MODEL_ARGUMENT_LINE_LENGTH)
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
omitted = error.issues.length > lines.length;
|
|
712
|
+
} else if (error instanceof ToolStringLengthError) {
|
|
713
|
+
const resolved = resolveSchemaPath(providerVisibleSchema, error.path);
|
|
714
|
+
const maximum = schemaNumber(error.maximum);
|
|
715
|
+
if (resolved === void 0 || maximum === void 0) return void 0;
|
|
716
|
+
const path = jsonPointerPath(resolved.segments);
|
|
717
|
+
lines = [`- ${path}: maximum string length is ${maximum}`];
|
|
718
|
+
} else {
|
|
719
|
+
return void 0;
|
|
720
|
+
}
|
|
721
|
+
const header = "Error: Invalid tool arguments:";
|
|
722
|
+
const footer = "Correct the arguments and try again.";
|
|
723
|
+
const omission = omitted ? "Additional validation issues were omitted." : void 0;
|
|
724
|
+
const parts = [header, ...lines, ...omission ? [omission] : [], footer];
|
|
725
|
+
const message = parts.join("\n");
|
|
726
|
+
return message.length <= MAX_MODEL_ARGUMENT_MESSAGE_LENGTH ? message : void 0;
|
|
727
|
+
} catch {
|
|
728
|
+
return void 0;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
398
731
|
function validateStringLengths(value, maxLen, path = []) {
|
|
399
732
|
if (typeof value === "string") {
|
|
400
733
|
if (value.length > maxLen) {
|
|
@@ -944,6 +1277,38 @@ function unsafeTransportError(surface, remediation, exposedData) {
|
|
|
944
1277
|
}
|
|
945
1278
|
|
|
946
1279
|
// src/providers/openai-compatible.ts
|
|
1280
|
+
var OPENROUTER_IMAGE_MODEL = "openai/gpt-4o-mini";
|
|
1281
|
+
function compatibleImageBase64(source) {
|
|
1282
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
1283
|
+
"base64"
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
function openRouterImageParts(parts) {
|
|
1287
|
+
const content = [];
|
|
1288
|
+
for (const part of parts) {
|
|
1289
|
+
if (part.type === "text") {
|
|
1290
|
+
content.push({ type: "text", text: part.text });
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
const { source } = part;
|
|
1294
|
+
if (source.type === "provider-file") {
|
|
1295
|
+
throw new UnsupportedModelInputError({
|
|
1296
|
+
provider: "openrouter",
|
|
1297
|
+
model: OPENROUTER_IMAGE_MODEL,
|
|
1298
|
+
modality: "image",
|
|
1299
|
+
source: "provider-file"
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
content.push({
|
|
1303
|
+
type: "image_url",
|
|
1304
|
+
image_url: {
|
|
1305
|
+
url: source.type === "url" ? source.url : `data:${source.mediaType};base64,${compatibleImageBase64(source)}`
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
if (part.label) content.push({ type: "text", text: `[Image: ${part.label}]` });
|
|
1309
|
+
}
|
|
1310
|
+
return content;
|
|
1311
|
+
}
|
|
947
1312
|
function resolvePerModel(value, model, fallback) {
|
|
948
1313
|
if (value === void 0) return fallback;
|
|
949
1314
|
return typeof value === "function" ? value(model) : value;
|
|
@@ -1059,6 +1424,41 @@ function normalizeMessageContent(content) {
|
|
|
1059
1424
|
}
|
|
1060
1425
|
var OpenAICompatibleProvider = class {
|
|
1061
1426
|
name;
|
|
1427
|
+
inputCapabilities(model) {
|
|
1428
|
+
return this.name === "openrouter" && model === OPENROUTER_IMAGE_MODEL ? { image: { sources: ["url", "bytes", "base64"] } } : {};
|
|
1429
|
+
}
|
|
1430
|
+
validateInput(request) {
|
|
1431
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
1432
|
+
const fail = (source, feature) => {
|
|
1433
|
+
throw new UnsupportedModelInputError({
|
|
1434
|
+
provider: this.name,
|
|
1435
|
+
model: effectiveModel || request.model,
|
|
1436
|
+
modality: "image",
|
|
1437
|
+
...source ? { source } : {},
|
|
1438
|
+
...feature ? { feature } : {}
|
|
1439
|
+
});
|
|
1440
|
+
};
|
|
1441
|
+
if (this.name !== "openrouter" || effectiveModel !== OPENROUTER_IMAGE_MODEL) {
|
|
1442
|
+
fail(void 0, "image input for this model");
|
|
1443
|
+
}
|
|
1444
|
+
if (request.providerOptions && "messages" in request.providerOptions) {
|
|
1445
|
+
fail(void 0, "raw messages providerOptions");
|
|
1446
|
+
}
|
|
1447
|
+
if (request.hasTools) fail(void 0, "tools with image input");
|
|
1448
|
+
for (const message of request.history) {
|
|
1449
|
+
if (!Array.isArray(message.content)) continue;
|
|
1450
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
1451
|
+
if (message.content.some(
|
|
1452
|
+
(part) => part.type === "image" && part.source.type === "provider-file"
|
|
1453
|
+
)) {
|
|
1454
|
+
fail("provider-file");
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (Array.isArray(request.input) && request.input.some((part) => part.type === "image" && part.source.type === "provider-file")) {
|
|
1458
|
+
fail("provider-file");
|
|
1459
|
+
}
|
|
1460
|
+
return { effectiveModel };
|
|
1461
|
+
}
|
|
1062
1462
|
/** `json_schema` is honored natively when the profile's `supportsJsonSchema`
|
|
1063
1463
|
* capability is true for this model (the default); otherwise the engine
|
|
1064
1464
|
* downgrades the request to plain `json_object` (see `buildRequestBody`),
|
|
@@ -1232,7 +1632,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1232
1632
|
formatMessage(msg, model) {
|
|
1233
1633
|
const out = {
|
|
1234
1634
|
role: this.profile.roleFor ? this.profile.roleFor(msg.role, model) : msg.role,
|
|
1235
|
-
content: msg.content
|
|
1635
|
+
content: Array.isArray(msg.content) && this.name === "openrouter" && model === OPENROUTER_IMAGE_MODEL ? openRouterImageParts(msg.content) : msg.content
|
|
1236
1636
|
};
|
|
1237
1637
|
if (msg.name && (this.profile.capabilities?.emitsMessageName ?? true)) out.name = msg.name;
|
|
1238
1638
|
if (msg.tool_calls) out.tool_calls = msg.tool_calls;
|
|
@@ -1979,8 +2379,88 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
|
|
|
1979
2379
|
};
|
|
1980
2380
|
|
|
1981
2381
|
// src/providers/openai-responses.ts
|
|
2382
|
+
var OPENAI_RESPONSES_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
2383
|
+
"gpt-4o",
|
|
2384
|
+
"gpt-4o-2024-08-06",
|
|
2385
|
+
"gpt-4o-2024-11-20",
|
|
2386
|
+
"gpt-4o-mini",
|
|
2387
|
+
"gpt-4o-mini-2024-07-18"
|
|
2388
|
+
]);
|
|
2389
|
+
function base64FromSource(source) {
|
|
2390
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
2391
|
+
"base64"
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
function responseImageParts(parts) {
|
|
2395
|
+
const mapped = [];
|
|
2396
|
+
for (const part of parts) {
|
|
2397
|
+
if (part.type === "text") {
|
|
2398
|
+
mapped.push({ type: "input_text", text: part.text });
|
|
2399
|
+
continue;
|
|
2400
|
+
}
|
|
2401
|
+
const { source } = part;
|
|
2402
|
+
if (source.type === "provider-file") {
|
|
2403
|
+
if (source.provider !== "openai-responses") {
|
|
2404
|
+
throw new UnsupportedModelInputError({
|
|
2405
|
+
provider: "openai-responses",
|
|
2406
|
+
model: "unknown",
|
|
2407
|
+
modality: "image",
|
|
2408
|
+
source: "provider-file"
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
mapped.push({ type: "input_image", file_id: source.reference });
|
|
2412
|
+
} else if (source.type === "url") {
|
|
2413
|
+
mapped.push({ type: "input_image", image_url: source.url });
|
|
2414
|
+
} else {
|
|
2415
|
+
mapped.push({
|
|
2416
|
+
type: "input_image",
|
|
2417
|
+
image_url: `data:${source.mediaType};base64,${base64FromSource(source)}`
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
if (part.label) mapped.push({ type: "input_text", text: `[Image: ${part.label}]` });
|
|
2421
|
+
}
|
|
2422
|
+
return mapped;
|
|
2423
|
+
}
|
|
1982
2424
|
var OpenAIResponsesProvider = class {
|
|
1983
2425
|
name = "openai-responses";
|
|
2426
|
+
inputCapabilities(model) {
|
|
2427
|
+
return OPENAI_RESPONSES_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
2428
|
+
}
|
|
2429
|
+
validateInput(request) {
|
|
2430
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
2431
|
+
const fail = (source, feature) => {
|
|
2432
|
+
throw new UnsupportedModelInputError({
|
|
2433
|
+
provider: this.name,
|
|
2434
|
+
model: effectiveModel || request.model,
|
|
2435
|
+
modality: "image",
|
|
2436
|
+
...source ? { source } : {},
|
|
2437
|
+
...feature ? { feature } : {}
|
|
2438
|
+
});
|
|
2439
|
+
};
|
|
2440
|
+
if (!OPENAI_RESPONSES_IMAGE_MODELS.has(effectiveModel)) {
|
|
2441
|
+
fail(void 0, "image input for this model");
|
|
2442
|
+
}
|
|
2443
|
+
if (request.providerOptions && "input" in request.providerOptions) {
|
|
2444
|
+
fail(void 0, "raw input-container providerOptions");
|
|
2445
|
+
}
|
|
2446
|
+
for (const message of request.history) {
|
|
2447
|
+
if (!Array.isArray(message.content)) continue;
|
|
2448
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
2449
|
+
for (const part of message.content) {
|
|
2450
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
2451
|
+
fail("provider-file");
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (Array.isArray(request.input)) {
|
|
2456
|
+
for (const part of request.input) {
|
|
2457
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
2458
|
+
fail("provider-file");
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
return { effectiveModel };
|
|
2463
|
+
}
|
|
1984
2464
|
/** The Responses API honors `json_schema` (structured outputs) natively. */
|
|
1985
2465
|
nativeStructuredOutputSupport() {
|
|
1986
2466
|
return "schema";
|
|
@@ -2145,11 +2625,12 @@ var OpenAIResponsesProvider = class {
|
|
|
2145
2625
|
buildInput(messages) {
|
|
2146
2626
|
const input = [];
|
|
2147
2627
|
for (const msg of messages) {
|
|
2628
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
2148
2629
|
if (msg.role === "tool") {
|
|
2149
2630
|
input.push({
|
|
2150
2631
|
type: "function_call_output",
|
|
2151
2632
|
call_id: msg.tool_call_id ?? "",
|
|
2152
|
-
output:
|
|
2633
|
+
output: text
|
|
2153
2634
|
});
|
|
2154
2635
|
} else if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
2155
2636
|
const reasoningItems = msg.providerMetadata?.openaiReasoningItems;
|
|
@@ -2158,8 +2639,8 @@ var OpenAIResponsesProvider = class {
|
|
|
2158
2639
|
input.push(item);
|
|
2159
2640
|
}
|
|
2160
2641
|
}
|
|
2161
|
-
if (
|
|
2162
|
-
input.push({ type: "message", role: "assistant", content:
|
|
2642
|
+
if (text) {
|
|
2643
|
+
input.push({ type: "message", role: "assistant", content: text });
|
|
2163
2644
|
}
|
|
2164
2645
|
for (const tc of msg.tool_calls) {
|
|
2165
2646
|
input.push({
|
|
@@ -2179,7 +2660,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2179
2660
|
input.push({
|
|
2180
2661
|
type: "message",
|
|
2181
2662
|
role: msg.role,
|
|
2182
|
-
content: msg.content
|
|
2663
|
+
content: typeof msg.content === "string" ? msg.content : responseImageParts(msg.content)
|
|
2183
2664
|
});
|
|
2184
2665
|
}
|
|
2185
2666
|
}
|
|
@@ -2246,7 +2727,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2246
2727
|
cached_tokens: json.usage.input_tokens_details?.cached_tokens,
|
|
2247
2728
|
cache_write_tokens: json.usage.input_tokens_details?.cache_write_tokens
|
|
2248
2729
|
} : void 0;
|
|
2249
|
-
const cost = usage ? estimateDirectOpenAICost(json.model ?? model, usage, {
|
|
2730
|
+
const cost = usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(json.model ?? model, usage, {
|
|
2250
2731
|
baseUrl: this.baseUrl,
|
|
2251
2732
|
request,
|
|
2252
2733
|
response: json
|
|
@@ -2261,6 +2742,16 @@ var OpenAIResponsesProvider = class {
|
|
|
2261
2742
|
providerMetadata
|
|
2262
2743
|
};
|
|
2263
2744
|
}
|
|
2745
|
+
/** Responses usage does not report a media-inclusive price, so rich calls stay unpriced. */
|
|
2746
|
+
requestContainsImages(request) {
|
|
2747
|
+
return Array.isArray(request?.input) && request.input.some((item) => {
|
|
2748
|
+
if (!item || typeof item !== "object") return false;
|
|
2749
|
+
const content = item.content;
|
|
2750
|
+
return Array.isArray(content) && content.some(
|
|
2751
|
+
(part) => part !== null && typeof part === "object" && part.type === "input_image"
|
|
2752
|
+
);
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2264
2755
|
// ---------------------------------------------------------------------------
|
|
2265
2756
|
// Internal: parse SSE stream
|
|
2266
2757
|
// ---------------------------------------------------------------------------
|
|
@@ -2348,7 +2839,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2348
2839
|
return {
|
|
2349
2840
|
type: "done",
|
|
2350
2841
|
usage,
|
|
2351
|
-
cost: usage ? estimateDirectOpenAICost(response?.model ?? model, usage, {
|
|
2842
|
+
cost: usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(response?.model ?? model, usage, {
|
|
2352
2843
|
baseUrl: this.baseUrl,
|
|
2353
2844
|
request,
|
|
2354
2845
|
response
|
|
@@ -2385,7 +2876,51 @@ var OpenAIResponsesProvider = class {
|
|
|
2385
2876
|
};
|
|
2386
2877
|
|
|
2387
2878
|
// src/providers/anthropic.ts
|
|
2879
|
+
function anthropicBase64(source) {
|
|
2880
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
2881
|
+
"base64"
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
function anthropicImageBlocks(parts) {
|
|
2885
|
+
const blocks = [];
|
|
2886
|
+
for (const part of parts) {
|
|
2887
|
+
if (part.type === "text") {
|
|
2888
|
+
blocks.push({ type: "text", text: part.text });
|
|
2889
|
+
continue;
|
|
2890
|
+
}
|
|
2891
|
+
const { source } = part;
|
|
2892
|
+
if (source.type === "provider-file") {
|
|
2893
|
+
if (source.provider !== "anthropic") {
|
|
2894
|
+
throw new UnsupportedModelInputError({
|
|
2895
|
+
provider: "anthropic",
|
|
2896
|
+
model: "unknown",
|
|
2897
|
+
modality: "image",
|
|
2898
|
+
source: "provider-file"
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
blocks.push({ type: "image", source: { type: "file", file_id: source.reference } });
|
|
2902
|
+
} else if (source.type === "url") {
|
|
2903
|
+
blocks.push({ type: "image", source: { type: "url", url: source.url } });
|
|
2904
|
+
} else {
|
|
2905
|
+
blocks.push({
|
|
2906
|
+
type: "image",
|
|
2907
|
+
source: { type: "base64", media_type: source.mediaType, data: anthropicBase64(source) }
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
if (part.label) blocks.push({ type: "text", text: `[Image: ${part.label}]` });
|
|
2911
|
+
}
|
|
2912
|
+
return blocks;
|
|
2913
|
+
}
|
|
2388
2914
|
var ANTHROPIC_API_VERSION = "2023-06-01";
|
|
2915
|
+
var ANTHROPIC_FILES_BETA = "files-api-2025-04-14";
|
|
2916
|
+
var ANTHROPIC_IMAGE_MODELS = /* @__PURE__ */ new Set(["claude-sonnet-4-5", "claude-opus-4-8"]);
|
|
2917
|
+
function hasAnthropicProviderFileImage(messages) {
|
|
2918
|
+
return messages.some(
|
|
2919
|
+
(message) => Array.isArray(message.content) && message.content.some(
|
|
2920
|
+
(part) => part.type === "image" && part.source.type === "provider-file" && part.source.provider === "anthropic"
|
|
2921
|
+
)
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2389
2924
|
var CLAUDE_CAPABILITIES = {
|
|
2390
2925
|
"claude-fable-5": {
|
|
2391
2926
|
thinking: "adaptive-always-on",
|
|
@@ -2646,6 +3181,11 @@ function isEffortAtOrBelow(effort, ceiling) {
|
|
|
2646
3181
|
function pricingContextFromBody(body) {
|
|
2647
3182
|
return {
|
|
2648
3183
|
model: typeof body.model === "string" ? body.model : void 0,
|
|
3184
|
+
hasRichInput: Array.isArray(body.messages) && body.messages.some(
|
|
3185
|
+
(message) => message !== null && typeof message === "object" && Array.isArray(message.content) && message.content.some(
|
|
3186
|
+
(part) => part !== null && typeof part === "object" && part.type === "image"
|
|
3187
|
+
)
|
|
3188
|
+
),
|
|
2649
3189
|
unpricedModifier: isAnthropicModifier("inference_geo", body.inference_geo) || isAnthropicModifier("speed", body.speed) || hasAnthropicServerTool(body.tools) || body.fallbacks !== void 0
|
|
2650
3190
|
};
|
|
2651
3191
|
}
|
|
@@ -2740,6 +3280,44 @@ function mergeAnthropicUsage(initial, update) {
|
|
|
2740
3280
|
}
|
|
2741
3281
|
var AnthropicProvider = class {
|
|
2742
3282
|
name = "anthropic";
|
|
3283
|
+
inputCapabilities(model) {
|
|
3284
|
+
return ANTHROPIC_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
3285
|
+
}
|
|
3286
|
+
validateInput(request) {
|
|
3287
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
3288
|
+
const fail = (source, feature) => {
|
|
3289
|
+
throw new UnsupportedModelInputError({
|
|
3290
|
+
provider: this.name,
|
|
3291
|
+
model: effectiveModel || request.model,
|
|
3292
|
+
modality: "image",
|
|
3293
|
+
...source ? { source } : {},
|
|
3294
|
+
...feature ? { feature } : {}
|
|
3295
|
+
});
|
|
3296
|
+
};
|
|
3297
|
+
if (!ANTHROPIC_IMAGE_MODELS.has(effectiveModel)) {
|
|
3298
|
+
fail(void 0, "image input for this model");
|
|
3299
|
+
}
|
|
3300
|
+
if (request.providerOptions && "messages" in request.providerOptions) {
|
|
3301
|
+
fail(void 0, "raw messages providerOptions");
|
|
3302
|
+
}
|
|
3303
|
+
for (const message of request.history) {
|
|
3304
|
+
if (!Array.isArray(message.content)) continue;
|
|
3305
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
3306
|
+
for (const part of message.content) {
|
|
3307
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
3308
|
+
fail("provider-file");
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
if (Array.isArray(request.input)) {
|
|
3313
|
+
for (const part of request.input) {
|
|
3314
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
3315
|
+
fail("provider-file");
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
return { effectiveModel };
|
|
3320
|
+
}
|
|
2743
3321
|
/** Anthropic ignores native `json_schema` structurally — Axl uses a system
|
|
2744
3322
|
* prompt JSON instruction + client-side Zod validation (see buildRequest). */
|
|
2745
3323
|
nativeStructuredOutputSupport() {
|
|
@@ -2777,7 +3355,10 @@ var AnthropicProvider = class {
|
|
|
2777
3355
|
// chat - non-streaming completion
|
|
2778
3356
|
// ---------------------------------------------------------------------------
|
|
2779
3357
|
async chat(messages, options) {
|
|
2780
|
-
const headers = this.buildHeaders(
|
|
3358
|
+
const headers = this.buildHeaders(
|
|
3359
|
+
await this.resolveKey(),
|
|
3360
|
+
hasAnthropicProviderFileImage(messages)
|
|
3361
|
+
);
|
|
2781
3362
|
const body = this.buildRequestBody(messages, options, false);
|
|
2782
3363
|
const pricingContext = pricingContextFromBody(body);
|
|
2783
3364
|
const res = await fetchWithRetry(
|
|
@@ -2808,7 +3389,10 @@ var AnthropicProvider = class {
|
|
|
2808
3389
|
// stream - SSE streaming completion
|
|
2809
3390
|
// ---------------------------------------------------------------------------
|
|
2810
3391
|
async *stream(messages, options) {
|
|
2811
|
-
const headers = this.buildHeaders(
|
|
3392
|
+
const headers = this.buildHeaders(
|
|
3393
|
+
await this.resolveKey(),
|
|
3394
|
+
hasAnthropicProviderFileImage(messages)
|
|
3395
|
+
);
|
|
2812
3396
|
const body = this.buildRequestBody(messages, options, true);
|
|
2813
3397
|
const pricingContext = pricingContextFromBody(body);
|
|
2814
3398
|
const res = await fetchWithRetry(
|
|
@@ -2840,11 +3424,12 @@ var AnthropicProvider = class {
|
|
|
2840
3424
|
// ---------------------------------------------------------------------------
|
|
2841
3425
|
// Internal: request building
|
|
2842
3426
|
// ---------------------------------------------------------------------------
|
|
2843
|
-
buildHeaders(apiKey) {
|
|
3427
|
+
buildHeaders(apiKey, requiresFilesBeta = false) {
|
|
2844
3428
|
return {
|
|
2845
3429
|
"Content-Type": "application/json",
|
|
2846
3430
|
"x-api-key": apiKey,
|
|
2847
|
-
"anthropic-version": ANTHROPIC_API_VERSION
|
|
3431
|
+
"anthropic-version": ANTHROPIC_API_VERSION,
|
|
3432
|
+
...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
|
|
2848
3433
|
};
|
|
2849
3434
|
}
|
|
2850
3435
|
/** Extract a human-readable message from an API error response body. */
|
|
@@ -2914,12 +3499,13 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2914
3499
|
mapMessages(messages) {
|
|
2915
3500
|
const result = [];
|
|
2916
3501
|
for (const msg of messages) {
|
|
3502
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
2917
3503
|
if (msg.role === "assistant") {
|
|
2918
3504
|
const replayedThinking = this.getReplayedThinkingBlocks(msg);
|
|
2919
3505
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
2920
3506
|
const content = [...replayedThinking];
|
|
2921
|
-
if (
|
|
2922
|
-
content.push({ type: "text", text
|
|
3507
|
+
if (text) {
|
|
3508
|
+
content.push({ type: "text", text });
|
|
2923
3509
|
}
|
|
2924
3510
|
for (const tc of msg.tool_calls) {
|
|
2925
3511
|
let parsedArgs;
|
|
@@ -2938,10 +3524,10 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2938
3524
|
result.push({ role: "assistant", content });
|
|
2939
3525
|
} else if (replayedThinking.length > 0) {
|
|
2940
3526
|
const content = [...replayedThinking];
|
|
2941
|
-
if (
|
|
3527
|
+
if (text) content.push({ type: "text", text });
|
|
2942
3528
|
result.push({ role: "assistant", content });
|
|
2943
3529
|
} else {
|
|
2944
|
-
result.push({ role: "assistant", content:
|
|
3530
|
+
result.push({ role: "assistant", content: text });
|
|
2945
3531
|
}
|
|
2946
3532
|
} else if (msg.role === "tool") {
|
|
2947
3533
|
result.push({
|
|
@@ -2950,12 +3536,15 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2950
3536
|
{
|
|
2951
3537
|
type: "tool_result",
|
|
2952
3538
|
tool_use_id: msg.tool_call_id,
|
|
2953
|
-
content:
|
|
3539
|
+
content: text
|
|
2954
3540
|
}
|
|
2955
3541
|
]
|
|
2956
3542
|
});
|
|
2957
3543
|
} else if (msg.role === "user") {
|
|
2958
|
-
result.push({
|
|
3544
|
+
result.push({
|
|
3545
|
+
role: "user",
|
|
3546
|
+
content: typeof msg.content === "string" ? msg.content : anthropicImageBlocks(msg.content)
|
|
3547
|
+
});
|
|
2959
3548
|
}
|
|
2960
3549
|
}
|
|
2961
3550
|
return this.mergeConsecutiveRoles(result);
|
|
@@ -3060,7 +3649,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3060
3649
|
"Anthropic fallback responses cannot be continued safely; retry without fallbacks."
|
|
3061
3650
|
);
|
|
3062
3651
|
}
|
|
3063
|
-
const cost = normalized && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
|
|
3652
|
+
const cost = normalized && !pricingContext.hasRichInput && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
|
|
3064
3653
|
return {
|
|
3065
3654
|
content,
|
|
3066
3655
|
thinking_content: thinkingContent || void 0,
|
|
@@ -3097,7 +3686,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3097
3686
|
const doneChunk = () => ({
|
|
3098
3687
|
type: "done",
|
|
3099
3688
|
usage,
|
|
3100
|
-
cost: pricingUsage && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
|
|
3689
|
+
cost: pricingUsage && !pricingContext.hasRichInput && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
|
|
3101
3690
|
providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
|
|
3102
3691
|
});
|
|
3103
3692
|
try {
|
|
@@ -3245,6 +3834,60 @@ function lastFallbackIndex(blocks) {
|
|
|
3245
3834
|
}
|
|
3246
3835
|
|
|
3247
3836
|
// src/providers/gemini.ts
|
|
3837
|
+
function hasRichGeminiMessages(messages) {
|
|
3838
|
+
return messages.some((message) => Array.isArray(message.content));
|
|
3839
|
+
}
|
|
3840
|
+
function geminiImageBase64(source) {
|
|
3841
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
3842
|
+
"base64"
|
|
3843
|
+
);
|
|
3844
|
+
}
|
|
3845
|
+
function geminiInteractionContent(parts, model) {
|
|
3846
|
+
const content = [];
|
|
3847
|
+
for (const part of parts) {
|
|
3848
|
+
if (part.type === "text") {
|
|
3849
|
+
content.push({ type: "text", text: part.text });
|
|
3850
|
+
continue;
|
|
3851
|
+
}
|
|
3852
|
+
const { source } = part;
|
|
3853
|
+
if (source.type === "provider-file") {
|
|
3854
|
+
if (source.provider !== "google") {
|
|
3855
|
+
throw new UnsupportedModelInputError({
|
|
3856
|
+
provider: "google",
|
|
3857
|
+
model,
|
|
3858
|
+
modality: "image",
|
|
3859
|
+
source: "provider-file"
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3862
|
+
if (!source.mediaType) {
|
|
3863
|
+
throw new UnsupportedModelInputError({
|
|
3864
|
+
provider: "google",
|
|
3865
|
+
model,
|
|
3866
|
+
modality: "image",
|
|
3867
|
+
source: "provider-file",
|
|
3868
|
+
feature: "Interactions URI image mediaType"
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
content.push({
|
|
3872
|
+
type: "image",
|
|
3873
|
+
uri: source.reference,
|
|
3874
|
+
mime_type: source.mediaType
|
|
3875
|
+
});
|
|
3876
|
+
} else if (source.type === "url") {
|
|
3877
|
+
throw new UnsupportedModelInputError({
|
|
3878
|
+
provider: "google",
|
|
3879
|
+
model,
|
|
3880
|
+
modality: "image",
|
|
3881
|
+
source: "url",
|
|
3882
|
+
feature: "direct URL image input; pass bytes/base64 or a Gemini provider-file"
|
|
3883
|
+
});
|
|
3884
|
+
} else {
|
|
3885
|
+
content.push({ type: "image", data: geminiImageBase64(source), mime_type: source.mediaType });
|
|
3886
|
+
}
|
|
3887
|
+
if (part.label) content.push({ type: "text", text: `[Image: ${part.label}]` });
|
|
3888
|
+
}
|
|
3889
|
+
return content;
|
|
3890
|
+
}
|
|
3248
3891
|
var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
3249
3892
|
"additionalProperties",
|
|
3250
3893
|
"$schema",
|
|
@@ -3316,6 +3959,7 @@ var GEMINI_PRICING = {
|
|
|
3316
3959
|
"gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
3317
3960
|
"gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
|
|
3318
3961
|
};
|
|
3962
|
+
var GEMINI_INTERACTIONS_IMAGE_MODELS = /* @__PURE__ */ new Set(["gemini-3.7-flash"]);
|
|
3319
3963
|
function isValidTokenCount2(value) {
|
|
3320
3964
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3321
3965
|
}
|
|
@@ -3403,7 +4047,10 @@ var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
|
|
|
3403
4047
|
["gemini-3.1-flash-lite-preview", "minimal"],
|
|
3404
4048
|
["gemini-3.5-flash", "minimal"],
|
|
3405
4049
|
["gemini-3.5-flash-lite", "minimal"],
|
|
3406
|
-
["gemini-3.6-flash", "minimal"]
|
|
4050
|
+
["gemini-3.6-flash", "minimal"],
|
|
4051
|
+
// L4 live evidence: Interactions gemini-3.7-flash accepts low/medium/high,
|
|
4052
|
+
// but rejects minimal. Keep this exact entry isolated from legacy aliases.
|
|
4053
|
+
["gemini-3.7-flash", "low"]
|
|
3407
4054
|
]);
|
|
3408
4055
|
var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
|
|
3409
4056
|
"gemini-3.5-flash-lite",
|
|
@@ -3430,6 +4077,61 @@ function warnGemini3xEffortNone(model) {
|
|
|
3430
4077
|
}
|
|
3431
4078
|
var GeminiProvider = class {
|
|
3432
4079
|
name = "google";
|
|
4080
|
+
inputCapabilities(model) {
|
|
4081
|
+
return GEMINI_INTERACTIONS_IMAGE_MODELS.has(model) ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
|
|
4082
|
+
}
|
|
4083
|
+
validateInput(request) {
|
|
4084
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
4085
|
+
const fail = (source, feature) => {
|
|
4086
|
+
throw new UnsupportedModelInputError({
|
|
4087
|
+
provider: this.name,
|
|
4088
|
+
model: effectiveModel || request.model,
|
|
4089
|
+
modality: "image",
|
|
4090
|
+
...source ? { source } : {},
|
|
4091
|
+
...feature ? { feature } : {}
|
|
4092
|
+
});
|
|
4093
|
+
};
|
|
4094
|
+
if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(effectiveModel)) {
|
|
4095
|
+
fail(void 0, "image input for this model");
|
|
4096
|
+
}
|
|
4097
|
+
if (request.providerOptions) {
|
|
4098
|
+
const forbidden = [
|
|
4099
|
+
"input",
|
|
4100
|
+
"contents",
|
|
4101
|
+
"messages",
|
|
4102
|
+
"previous_interaction_id",
|
|
4103
|
+
"background",
|
|
4104
|
+
"stream",
|
|
4105
|
+
"store"
|
|
4106
|
+
].find((key) => key in request.providerOptions);
|
|
4107
|
+
if (forbidden) fail(void 0, `raw ${forbidden} providerOptions`);
|
|
4108
|
+
}
|
|
4109
|
+
for (const message of request.history) {
|
|
4110
|
+
if (!Array.isArray(message.content)) continue;
|
|
4111
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
4112
|
+
for (const part of message.content) {
|
|
4113
|
+
if (part.type !== "image") continue;
|
|
4114
|
+
if (part.source.type === "url")
|
|
4115
|
+
fail("url", "direct URL image input; pass bytes/base64 or a Gemini provider-file");
|
|
4116
|
+
if (part.source.type === "provider-file" && part.source.provider !== this.name)
|
|
4117
|
+
fail("provider-file");
|
|
4118
|
+
if (part.source.type === "provider-file" && !part.source.mediaType)
|
|
4119
|
+
fail("provider-file", "Interactions URI image mediaType");
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
if (Array.isArray(request.input)) {
|
|
4123
|
+
for (const part of request.input) {
|
|
4124
|
+
if (part.type !== "image") continue;
|
|
4125
|
+
if (part.source.type === "url")
|
|
4126
|
+
fail("url", "direct URL image input; pass bytes/base64 or a Gemini provider-file");
|
|
4127
|
+
if (part.source.type === "provider-file" && part.source.provider !== this.name)
|
|
4128
|
+
fail("provider-file");
|
|
4129
|
+
if (part.source.type === "provider-file" && !part.source.mediaType)
|
|
4130
|
+
fail("provider-file", "Interactions URI image mediaType");
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
return { effectiveModel };
|
|
4134
|
+
}
|
|
3433
4135
|
/** Gemini accepts a `responseSchema` but `sanitizeSchemaForGemini` strips
|
|
3434
4136
|
* keywords it doesn't support (`$ref`/`$defs`/`additionalProperties`/…), so a
|
|
3435
4137
|
* derived schema can lose constraints — lossy, not faithful. */
|
|
@@ -3468,6 +4170,7 @@ var GeminiProvider = class {
|
|
|
3468
4170
|
// chat - non-streaming completion
|
|
3469
4171
|
// ---------------------------------------------------------------------------
|
|
3470
4172
|
async chat(messages, options) {
|
|
4173
|
+
if (hasRichGeminiMessages(messages)) return this.chatInteraction(messages, options);
|
|
3471
4174
|
const body = this.buildRequestBody(messages, options);
|
|
3472
4175
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3473
4176
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
@@ -3500,6 +4203,10 @@ var GeminiProvider = class {
|
|
|
3500
4203
|
// stream - SSE streaming completion
|
|
3501
4204
|
// ---------------------------------------------------------------------------
|
|
3502
4205
|
async *stream(messages, options) {
|
|
4206
|
+
if (hasRichGeminiMessages(messages)) {
|
|
4207
|
+
yield* this.streamInteraction(messages, options);
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
3503
4210
|
const body = this.buildRequestBody(messages, options);
|
|
3504
4211
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3505
4212
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
@@ -3531,25 +4238,358 @@ var GeminiProvider = class {
|
|
|
3531
4238
|
yield* this.parseSSEStream(res.body, pricingContext);
|
|
3532
4239
|
}
|
|
3533
4240
|
// ---------------------------------------------------------------------------
|
|
3534
|
-
//
|
|
4241
|
+
// Rich image requests use Gemini Interactions. It is deliberately stateless:
|
|
4242
|
+
// Axl sends the complete application-owned history and never asks Google to
|
|
4243
|
+
// retain an interaction or follow a previous_interaction_id.
|
|
3535
4244
|
// ---------------------------------------------------------------------------
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
4245
|
+
async chatInteraction(messages, options) {
|
|
4246
|
+
this.assertSafeInteractionOptions(options);
|
|
4247
|
+
const body = this.buildInteractionRequestBody(messages, options, false);
|
|
4248
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
4249
|
+
const res = await fetchWithRetry(
|
|
4250
|
+
`${this.baseUrl}/interactions`,
|
|
4251
|
+
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4252
|
+
{ governor: this.governor, provider: this.name }
|
|
4253
|
+
);
|
|
4254
|
+
if (!res.ok) {
|
|
4255
|
+
const errorBody = await res.text();
|
|
4256
|
+
throw buildProviderError({
|
|
4257
|
+
provider: this.name,
|
|
4258
|
+
status: res.status,
|
|
4259
|
+
headers: res.headers,
|
|
4260
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
4261
|
+
body: errorBody
|
|
4262
|
+
});
|
|
4263
|
+
}
|
|
4264
|
+
return this.parseInteractionResponse(await res.json());
|
|
4265
|
+
}
|
|
4266
|
+
async *streamInteraction(messages, options) {
|
|
4267
|
+
this.assertSafeInteractionOptions(options);
|
|
4268
|
+
const body = this.buildInteractionRequestBody(messages, options, true);
|
|
4269
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
4270
|
+
const res = await fetchWithRetry(
|
|
4271
|
+
`${this.baseUrl}/interactions?alt=sse`,
|
|
4272
|
+
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4273
|
+
{ governor: this.governor, provider: this.name }
|
|
4274
|
+
);
|
|
4275
|
+
if (!res.ok) {
|
|
4276
|
+
const errorBody = await res.text();
|
|
4277
|
+
throw buildProviderError({
|
|
4278
|
+
provider: this.name,
|
|
4279
|
+
status: res.status,
|
|
4280
|
+
headers: res.headers,
|
|
4281
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
4282
|
+
body: errorBody
|
|
4283
|
+
});
|
|
4284
|
+
}
|
|
4285
|
+
if (!res.body) throw new Error("Gemini Interactions stream has no body");
|
|
4286
|
+
yield* this.parseInteractionSSEStream(res.body);
|
|
4287
|
+
}
|
|
4288
|
+
buildInteractionRequestBody(messages, options, stream) {
|
|
4289
|
+
const model = this.requestModel(options);
|
|
4290
|
+
const systemInstruction = messages.filter((message) => message.role === "system").map((message) => typeof message.content === "string" ? message.content : "").filter(Boolean).join("\n\n");
|
|
4291
|
+
const body = {
|
|
4292
|
+
model,
|
|
4293
|
+
input: this.mapInteractionInput(
|
|
4294
|
+
messages.filter((message) => message.role !== "system"),
|
|
4295
|
+
model
|
|
4296
|
+
),
|
|
4297
|
+
stream
|
|
3540
4298
|
};
|
|
4299
|
+
if (systemInstruction) body.system_instruction = systemInstruction;
|
|
4300
|
+
if (options.tools?.length) {
|
|
4301
|
+
body.tools = options.tools.map((tool2) => ({
|
|
4302
|
+
type: "function",
|
|
4303
|
+
name: tool2.function.name,
|
|
4304
|
+
description: tool2.function.description,
|
|
4305
|
+
parameters: sanitizeSchemaForGemini(tool2.function.parameters)
|
|
4306
|
+
}));
|
|
4307
|
+
}
|
|
4308
|
+
const generationConfig = {};
|
|
4309
|
+
if (options.maxTokens !== void 0) generationConfig.max_output_tokens = options.maxTokens;
|
|
4310
|
+
if (options.stop) generationConfig.stop_sequences = options.stop;
|
|
4311
|
+
if (options.toolChoice !== void 0) {
|
|
4312
|
+
generationConfig.tool_choice = typeof options.toolChoice === "string" ? options.toolChoice === "required" ? "any" : options.toolChoice : { allowed_tools: { mode: "any", tools: [options.toolChoice.function.name] } };
|
|
4313
|
+
}
|
|
4314
|
+
const thinking = resolveThinkingOptions(options);
|
|
4315
|
+
if (thinking.thinkingDisabled) {
|
|
4316
|
+
generationConfig.thinking_level = isGemini3x(model) ? minThinkingLevel(model) : "minimal";
|
|
4317
|
+
} else if (thinking.hasBudgetOverride) {
|
|
4318
|
+
generationConfig.thinking_level = budgetToThinkingLevel(thinking.thinkingBudget);
|
|
4319
|
+
} else if (thinking.activeEffort) {
|
|
4320
|
+
generationConfig.thinking_level = isGemini3x(model) ? THINKING_LEVELS[thinking.activeEffort] ?? "medium" : budgetToThinkingLevel(THINKING_BUDGETS2[thinking.activeEffort] ?? 5e3);
|
|
4321
|
+
}
|
|
4322
|
+
if (thinking.includeThoughts) generationConfig.thinking_summaries = "auto";
|
|
4323
|
+
if (Object.keys(generationConfig).length) body.generation_config = generationConfig;
|
|
4324
|
+
if (options.responseFormat?.type && options.responseFormat.type !== "text") {
|
|
4325
|
+
body.response_format = {
|
|
4326
|
+
type: "text",
|
|
4327
|
+
mime_type: "application/json",
|
|
4328
|
+
...options.responseFormat.type === "json_schema" ? { schema: sanitizeSchemaForGemini(options.responseFormat.json_schema.schema) } : {}
|
|
4329
|
+
};
|
|
4330
|
+
}
|
|
4331
|
+
if (options.providerOptions) {
|
|
4332
|
+
const overrides = { ...options.providerOptions };
|
|
4333
|
+
delete overrides.model;
|
|
4334
|
+
delete overrides.input;
|
|
4335
|
+
delete overrides.contents;
|
|
4336
|
+
delete overrides.messages;
|
|
4337
|
+
delete overrides.previous_interaction_id;
|
|
4338
|
+
delete overrides.background;
|
|
4339
|
+
delete overrides.stream;
|
|
4340
|
+
delete overrides.store;
|
|
4341
|
+
const rawGenerationConfig = overrides.generation_config;
|
|
4342
|
+
if (rawGenerationConfig && typeof rawGenerationConfig === "object" && !Array.isArray(rawGenerationConfig)) {
|
|
4343
|
+
const generationConfigOverrides = { ...rawGenerationConfig };
|
|
4344
|
+
delete generationConfigOverrides.temperature;
|
|
4345
|
+
if (Object.keys(generationConfigOverrides).length)
|
|
4346
|
+
overrides.generation_config = generationConfigOverrides;
|
|
4347
|
+
else delete overrides.generation_config;
|
|
4348
|
+
}
|
|
4349
|
+
Object.assign(body, overrides);
|
|
4350
|
+
}
|
|
4351
|
+
body.stream = stream;
|
|
4352
|
+
body.store = false;
|
|
4353
|
+
return body;
|
|
3541
4354
|
}
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
4355
|
+
assertSafeInteractionOptions(options) {
|
|
4356
|
+
const model = this.requestModel(options);
|
|
4357
|
+
const fail = (feature) => {
|
|
4358
|
+
throw new UnsupportedModelInputError({
|
|
4359
|
+
provider: this.name,
|
|
4360
|
+
model,
|
|
4361
|
+
modality: "image",
|
|
4362
|
+
feature
|
|
4363
|
+
});
|
|
4364
|
+
};
|
|
4365
|
+
if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(model)) fail("image input for this model");
|
|
4366
|
+
const forbidden = [
|
|
4367
|
+
"input",
|
|
4368
|
+
"contents",
|
|
4369
|
+
"messages",
|
|
4370
|
+
"previous_interaction_id",
|
|
4371
|
+
"background",
|
|
4372
|
+
"stream",
|
|
4373
|
+
"store"
|
|
4374
|
+
].find((key) => key in (options.providerOptions ?? {}));
|
|
4375
|
+
if (forbidden) fail(`raw ${forbidden} providerOptions`);
|
|
4376
|
+
}
|
|
4377
|
+
mapInteractionInput(messages, model) {
|
|
4378
|
+
const steps = [];
|
|
4379
|
+
for (const message of messages) {
|
|
4380
|
+
if (message.role === "user") {
|
|
4381
|
+
steps.push({
|
|
4382
|
+
type: "user_input",
|
|
4383
|
+
content: Array.isArray(message.content) ? geminiInteractionContent(message.content, model) : [{ type: "text", text: message.content }]
|
|
4384
|
+
});
|
|
4385
|
+
} else if (message.role === "assistant") {
|
|
4386
|
+
const nativeSteps = message.providerMetadata?.geminiInteractionSteps;
|
|
4387
|
+
if (Array.isArray(nativeSteps) && nativeSteps.every(isGeminiInteractionStep)) {
|
|
4388
|
+
steps.push(...nativeSteps);
|
|
4389
|
+
continue;
|
|
4390
|
+
}
|
|
4391
|
+
if (typeof message.content === "string" && message.content) {
|
|
4392
|
+
steps.push({ type: "model_output", content: [{ type: "text", text: message.content }] });
|
|
4393
|
+
}
|
|
4394
|
+
for (const call of message.tool_calls ?? []) {
|
|
4395
|
+
steps.push({
|
|
4396
|
+
type: "function_call",
|
|
4397
|
+
id: call.id,
|
|
4398
|
+
name: call.function.name,
|
|
4399
|
+
arguments: safeJsonObject(call.function.arguments)
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
} else if (message.role === "tool") {
|
|
4403
|
+
steps.push({
|
|
4404
|
+
type: "function_result",
|
|
4405
|
+
call_id: message.tool_call_id ?? "",
|
|
4406
|
+
result: [
|
|
4407
|
+
{ type: "text", text: typeof message.content === "string" ? message.content : "" }
|
|
4408
|
+
]
|
|
4409
|
+
});
|
|
3547
4410
|
}
|
|
3548
|
-
} catch {
|
|
3549
4411
|
}
|
|
3550
|
-
return
|
|
4412
|
+
return steps;
|
|
3551
4413
|
}
|
|
3552
|
-
|
|
4414
|
+
parseInteractionResponse(json) {
|
|
4415
|
+
let content = "";
|
|
4416
|
+
let thinkingContent = "";
|
|
4417
|
+
const toolCalls = [];
|
|
4418
|
+
for (const step of json.steps ?? []) {
|
|
4419
|
+
if (step.type === "model_output") content += interactionText(step.content);
|
|
4420
|
+
else if (step.type === "thought") thinkingContent += interactionText(step.summary);
|
|
4421
|
+
else if (step.type === "function_call" && step.id && step.name) {
|
|
4422
|
+
toolCalls.push({
|
|
4423
|
+
id: step.id,
|
|
4424
|
+
type: "function",
|
|
4425
|
+
function: { name: step.name, arguments: JSON.stringify(step.arguments ?? {}) }
|
|
4426
|
+
});
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
this.assertInteractionTerminalStatus(json.status, toolCalls.length);
|
|
4430
|
+
const usage = normalizeInteractionUsage(json.usage);
|
|
4431
|
+
return {
|
|
4432
|
+
content,
|
|
4433
|
+
thinking_content: thinkingContent || void 0,
|
|
4434
|
+
tool_calls: toolCalls.length ? toolCalls : void 0,
|
|
4435
|
+
usage,
|
|
4436
|
+
// Interactions returns modality usage but no authoritative monetary cost.
|
|
4437
|
+
cost: void 0,
|
|
4438
|
+
providerMetadata: json.steps?.length ? { geminiInteractionSteps: json.steps.filter(isGeminiInteractionStep) } : void 0
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4441
|
+
async *parseInteractionSSEStream(body) {
|
|
4442
|
+
const reader = body.getReader();
|
|
4443
|
+
const decoder = new TextDecoder();
|
|
4444
|
+
let buffer = "";
|
|
4445
|
+
const steps = /* @__PURE__ */ new Map();
|
|
4446
|
+
const argumentBuffers = /* @__PURE__ */ new Map();
|
|
4447
|
+
const stopped = /* @__PURE__ */ new Set();
|
|
4448
|
+
let completed = false;
|
|
4449
|
+
try {
|
|
4450
|
+
while (true) {
|
|
4451
|
+
const { done, value } = await reader.read();
|
|
4452
|
+
if (done) break;
|
|
4453
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4454
|
+
const lines = buffer.split("\n");
|
|
4455
|
+
buffer = lines.pop() ?? "";
|
|
4456
|
+
for (const line of lines) {
|
|
4457
|
+
const trimmed = line.trim();
|
|
4458
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
4459
|
+
if (trimmed.slice(6) === "[DONE]") continue;
|
|
4460
|
+
let event;
|
|
4461
|
+
try {
|
|
4462
|
+
event = JSON.parse(trimmed.slice(6));
|
|
4463
|
+
} catch {
|
|
4464
|
+
continue;
|
|
4465
|
+
}
|
|
4466
|
+
if (event.event_type === "error") {
|
|
4467
|
+
throw new Error(event.error?.message ?? "Gemini Interactions stream failed");
|
|
4468
|
+
}
|
|
4469
|
+
if (event.event_type === "step.start" && event.step) {
|
|
4470
|
+
const index = event.index;
|
|
4471
|
+
if (index === void 0 || steps.has(index)) {
|
|
4472
|
+
throw new Error("Gemini Interactions stream contained an invalid step.start index");
|
|
4473
|
+
}
|
|
4474
|
+
const step = cloneInteractionStep(event.step);
|
|
4475
|
+
steps.set(index, step);
|
|
4476
|
+
if (step.type === "model_output") {
|
|
4477
|
+
for (const text of interactionTextChunks(step.content)) {
|
|
4478
|
+
yield { type: "text_delta", content: text };
|
|
4479
|
+
}
|
|
4480
|
+
} else if (step.type === "thought") {
|
|
4481
|
+
for (const text of interactionTextChunks(step.summary)) {
|
|
4482
|
+
yield { type: "thinking_delta", content: text };
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
if (step.type === "function_call" && step.id && step.name) {
|
|
4486
|
+
argumentBuffers.set(index, "");
|
|
4487
|
+
yield { type: "tool_call_delta", id: step.id, name: step.name };
|
|
4488
|
+
}
|
|
4489
|
+
} else if (event.event_type === "step.delta" && event.delta) {
|
|
4490
|
+
const delta = event.delta;
|
|
4491
|
+
const index = event.index;
|
|
4492
|
+
if (index === void 0 || stopped.has(index)) {
|
|
4493
|
+
throw new Error("Gemini Interactions stream delta arrived outside an active step");
|
|
4494
|
+
}
|
|
4495
|
+
const step = steps.get(index);
|
|
4496
|
+
if (!step)
|
|
4497
|
+
throw new Error("Gemini Interactions stream delta arrived before step.start");
|
|
4498
|
+
if (delta.type === "text" && typeof delta.text === "string" && step.type === "model_output") {
|
|
4499
|
+
appendInteractionContent(step, "content", { type: "text", text: delta.text });
|
|
4500
|
+
yield { type: "text_delta", content: delta.text };
|
|
4501
|
+
} else if (delta.type === "thought_summary" && step.type === "thought" && delta.content) {
|
|
4502
|
+
appendInteractionContent(step, "summary", delta.content);
|
|
4503
|
+
for (const text of interactionTextChunks([delta.content])) {
|
|
4504
|
+
yield { type: "thinking_delta", content: text };
|
|
4505
|
+
}
|
|
4506
|
+
} else if (delta.type === "thought_signature" && step.type === "thought" && typeof delta.signature === "string") {
|
|
4507
|
+
step.signature = delta.signature;
|
|
4508
|
+
} else if (delta.type === "arguments_delta" && step.type === "function_call" && typeof delta.arguments === "string") {
|
|
4509
|
+
argumentBuffers.set(index, (argumentBuffers.get(index) ?? "") + delta.arguments);
|
|
4510
|
+
if (step.id)
|
|
4511
|
+
yield { type: "tool_call_delta", id: step.id, arguments: delta.arguments };
|
|
4512
|
+
}
|
|
4513
|
+
} else if (event.event_type === "step.stop") {
|
|
4514
|
+
const index = event.index;
|
|
4515
|
+
const step = index === void 0 ? void 0 : steps.get(index);
|
|
4516
|
+
if (step === void 0 || stopped.has(index)) {
|
|
4517
|
+
throw new Error("Gemini Interactions stream contained an invalid step.stop index");
|
|
4518
|
+
}
|
|
4519
|
+
if (step.type === "function_call") {
|
|
4520
|
+
const rawArguments = argumentBuffers.get(index) ?? "";
|
|
4521
|
+
if (rawArguments) {
|
|
4522
|
+
try {
|
|
4523
|
+
const parsed = JSON.parse(rawArguments);
|
|
4524
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4525
|
+
throw new Error("not an object");
|
|
4526
|
+
}
|
|
4527
|
+
step.arguments = parsed;
|
|
4528
|
+
} catch {
|
|
4529
|
+
throw new Error(
|
|
4530
|
+
"Gemini Interactions function_call arguments were not valid JSON"
|
|
4531
|
+
);
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
stopped.add(index);
|
|
4536
|
+
} else if (event.event_type === "interaction.completed") {
|
|
4537
|
+
if (steps.size !== stopped.size) {
|
|
4538
|
+
throw new Error("Gemini Interactions stream completed before all steps stopped");
|
|
4539
|
+
}
|
|
4540
|
+
const toolCallCount = [...steps.values()].filter(
|
|
4541
|
+
(step) => step.type === "function_call" && !!step.id && !!step.name
|
|
4542
|
+
).length;
|
|
4543
|
+
this.assertInteractionTerminalStatus(event.interaction?.status, toolCallCount);
|
|
4544
|
+
completed = true;
|
|
4545
|
+
yield {
|
|
4546
|
+
type: "done",
|
|
4547
|
+
usage: normalizeInteractionUsage(event.interaction?.usage),
|
|
4548
|
+
providerMetadata: steps.size ? {
|
|
4549
|
+
geminiInteractionSteps: [...steps.entries()].sort(([a], [b]) => a - b).map(([, step]) => step)
|
|
4550
|
+
} : void 0
|
|
4551
|
+
};
|
|
4552
|
+
return;
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
if (!completed)
|
|
4557
|
+
throw new Error("Gemini Interactions stream ended before interaction.completed");
|
|
4558
|
+
} finally {
|
|
4559
|
+
reader.releaseLock();
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
assertInteractionTerminalStatus(status, toolCallCount) {
|
|
4563
|
+
if (status === "completed") return;
|
|
4564
|
+
if (status === "requires_action" && toolCallCount > 0) return;
|
|
4565
|
+
const normalizedStatus = typeof status === "string" ? status : "unknown";
|
|
4566
|
+
throw new ProviderError({
|
|
4567
|
+
provider: this.name,
|
|
4568
|
+
status: 0,
|
|
4569
|
+
retryable: false,
|
|
4570
|
+
message: `Gemini Interactions ended with non-success status: ${normalizedStatus}`
|
|
4571
|
+
});
|
|
4572
|
+
}
|
|
4573
|
+
// ---------------------------------------------------------------------------
|
|
4574
|
+
// Internal: request building
|
|
4575
|
+
// ---------------------------------------------------------------------------
|
|
4576
|
+
buildHeaders(apiKey) {
|
|
4577
|
+
return {
|
|
4578
|
+
"Content-Type": "application/json",
|
|
4579
|
+
"x-goog-api-key": apiKey
|
|
4580
|
+
};
|
|
4581
|
+
}
|
|
4582
|
+
extractErrorMessage(body, status) {
|
|
4583
|
+
try {
|
|
4584
|
+
const json = JSON.parse(body);
|
|
4585
|
+
if (json.error?.message) {
|
|
4586
|
+
return `Gemini API error (${status}): ${json.error.message}`;
|
|
4587
|
+
}
|
|
4588
|
+
} catch {
|
|
4589
|
+
}
|
|
4590
|
+
return `Gemini API error (${status}): ${body}`;
|
|
4591
|
+
}
|
|
4592
|
+
pricingContext(fallbackModel, body) {
|
|
3553
4593
|
return {
|
|
3554
4594
|
// Gemini's model is normally selected in the URL. providerOptions is an
|
|
3555
4595
|
// explicit raw escape hatch, so honor a post-merge model override for both
|
|
@@ -3785,14 +4825,15 @@ var GeminiProvider = class {
|
|
|
3785
4825
|
}
|
|
3786
4826
|
const result = [];
|
|
3787
4827
|
for (const msg of messages) {
|
|
4828
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
3788
4829
|
if (msg.role === "assistant") {
|
|
3789
4830
|
const rawParts = msg.providerMetadata?.geminiParts;
|
|
3790
4831
|
if (rawParts && rawParts.length > 0) {
|
|
3791
4832
|
result.push({ role: "model", parts: rawParts });
|
|
3792
4833
|
} else {
|
|
3793
4834
|
const parts = [];
|
|
3794
|
-
if (
|
|
3795
|
-
parts.push({ text
|
|
4835
|
+
if (text) {
|
|
4836
|
+
parts.push({ text });
|
|
3796
4837
|
}
|
|
3797
4838
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
3798
4839
|
for (const tc of msg.tool_calls) {
|
|
@@ -3818,7 +4859,7 @@ var GeminiProvider = class {
|
|
|
3818
4859
|
const functionName = (msg.tool_call_id ? geminiNativeNames.get(msg.tool_call_id) : void 0) ?? toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
|
|
3819
4860
|
const functionResponse = {
|
|
3820
4861
|
name: functionName,
|
|
3821
|
-
response: parseGeminiFunctionResponse(
|
|
4862
|
+
response: parseGeminiFunctionResponse(text)
|
|
3822
4863
|
};
|
|
3823
4864
|
if (msg.tool_call_id && geminiNativeIds.has(msg.tool_call_id)) {
|
|
3824
4865
|
functionResponse.id = msg.tool_call_id;
|
|
@@ -3828,7 +4869,7 @@ var GeminiProvider = class {
|
|
|
3828
4869
|
parts: [{ functionResponse }]
|
|
3829
4870
|
});
|
|
3830
4871
|
} else if (msg.role === "user") {
|
|
3831
|
-
result.push({ role: "user", parts: [{ text
|
|
4872
|
+
result.push({ role: "user", parts: [{ text }] });
|
|
3832
4873
|
}
|
|
3833
4874
|
}
|
|
3834
4875
|
return this.mergeConsecutiveRoles(result);
|
|
@@ -4016,6 +5057,56 @@ var GeminiProvider = class {
|
|
|
4016
5057
|
}
|
|
4017
5058
|
}
|
|
4018
5059
|
};
|
|
5060
|
+
function isGeminiInteractionStep(value) {
|
|
5061
|
+
return value !== null && typeof value === "object" && ["user_input", "model_output", "thought", "function_call", "function_result"].includes(
|
|
5062
|
+
value.type
|
|
5063
|
+
);
|
|
5064
|
+
}
|
|
5065
|
+
function interactionText(content) {
|
|
5066
|
+
return content?.map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").join("") ?? "";
|
|
5067
|
+
}
|
|
5068
|
+
function interactionTextChunks(content) {
|
|
5069
|
+
return content?.flatMap(
|
|
5070
|
+
(part) => part.type === "text" && typeof part.text === "string" ? [part.text] : []
|
|
5071
|
+
) ?? [];
|
|
5072
|
+
}
|
|
5073
|
+
function cloneInteractionStep(step) {
|
|
5074
|
+
return JSON.parse(JSON.stringify(step));
|
|
5075
|
+
}
|
|
5076
|
+
function appendInteractionContent(step, field, content) {
|
|
5077
|
+
const existing = step[field] ?? [];
|
|
5078
|
+
const last = existing.at(-1);
|
|
5079
|
+
if (content.type === "text" && last?.type === "text" && typeof content.text === "string" && typeof last.text === "string") {
|
|
5080
|
+
last.text += content.text;
|
|
5081
|
+
} else {
|
|
5082
|
+
existing.push(content);
|
|
5083
|
+
}
|
|
5084
|
+
step[field] = existing;
|
|
5085
|
+
}
|
|
5086
|
+
function safeJsonObject(value) {
|
|
5087
|
+
try {
|
|
5088
|
+
const parsed = JSON.parse(value);
|
|
5089
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
5090
|
+
} catch {
|
|
5091
|
+
return {};
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
function normalizeInteractionUsage(usage) {
|
|
5095
|
+
if (!usage || !isValidTokenCount2(usage.total_input_tokens) || !isValidTokenCount2(usage.total_output_tokens) || !isValidTokenCount2(usage.total_tokens)) {
|
|
5096
|
+
return void 0;
|
|
5097
|
+
}
|
|
5098
|
+
const cached = usage.total_cached_tokens;
|
|
5099
|
+
const thinking = usage.total_thought_tokens;
|
|
5100
|
+
if (cached !== void 0 && (!isValidTokenCount2(cached) || cached > usage.total_input_tokens) || thinking !== void 0 && !isValidTokenCount2(thinking))
|
|
5101
|
+
return void 0;
|
|
5102
|
+
return {
|
|
5103
|
+
prompt_tokens: usage.total_input_tokens,
|
|
5104
|
+
completion_tokens: usage.total_output_tokens,
|
|
5105
|
+
total_tokens: usage.total_tokens,
|
|
5106
|
+
...cached ? { cached_tokens: cached } : {},
|
|
5107
|
+
...thinking ? { reasoning_tokens: thinking } : {}
|
|
5108
|
+
};
|
|
5109
|
+
}
|
|
4019
5110
|
|
|
4020
5111
|
// src/providers/profiles/openrouter.ts
|
|
4021
5112
|
var OPENROUTER_PROFILE = {
|
|
@@ -4456,59 +5547,1211 @@ var ProviderRegistry = class {
|
|
|
4456
5547
|
}
|
|
4457
5548
|
throw new Error(`Unknown provider "${name}". Registered providers: ${this.list().join(", ")}`);
|
|
4458
5549
|
}
|
|
4459
|
-
/**
|
|
4460
|
-
* Resolve a "provider:model" URI string into a Provider instance and model name.
|
|
4461
|
-
*
|
|
4462
|
-
* Supported formats:
|
|
4463
|
-
* - "openai:gpt-4o" -> provider=openai, model=gpt-4o
|
|
4464
|
-
* - "anthropic:claude-3" -> provider=anthropic, model=claude-3
|
|
4465
|
-
* - "gpt-4o" -> uses defaultProvider from config, model=gpt-4o
|
|
4466
|
-
* - undefined / empty -> uses defaultProvider and defaultModel from config
|
|
4467
|
-
*
|
|
4468
|
-
* @param uri Provider:model string, or just a model name
|
|
4469
|
-
* @param config Axl configuration for provider options and defaults
|
|
4470
|
-
*/
|
|
4471
|
-
resolve(uri, config = {}) {
|
|
4472
|
-
if (!uri) {
|
|
4473
|
-
const providerName2 = config.defaultProvider ?? "openai";
|
|
4474
|
-
const model2 = config.defaultModel ?? "gpt-4o";
|
|
4475
|
-
return { provider: this.get(providerName2, config), model: model2 };
|
|
4476
|
-
}
|
|
4477
|
-
const colonIndex = uri.indexOf(":");
|
|
4478
|
-
if (colonIndex === -1) {
|
|
4479
|
-
const providerName2 = config.defaultProvider ?? "openai";
|
|
4480
|
-
return { provider: this.get(providerName2, config), model: uri };
|
|
4481
|
-
}
|
|
4482
|
-
const providerName = uri.slice(0, colonIndex);
|
|
4483
|
-
const model = uri.slice(colonIndex + 1);
|
|
4484
|
-
if (!providerName || !model) {
|
|
4485
|
-
throw new Error(
|
|
4486
|
-
`Invalid provider URI "${uri}". Expected format: "provider:model" (e.g. "openai:gpt-4o")`
|
|
4487
|
-
);
|
|
4488
|
-
}
|
|
4489
|
-
return { provider: this.get(providerName, config), model };
|
|
5550
|
+
/**
|
|
5551
|
+
* Resolve a "provider:model" URI string into a Provider instance and model name.
|
|
5552
|
+
*
|
|
5553
|
+
* Supported formats:
|
|
5554
|
+
* - "openai:gpt-4o" -> provider=openai, model=gpt-4o
|
|
5555
|
+
* - "anthropic:claude-3" -> provider=anthropic, model=claude-3
|
|
5556
|
+
* - "gpt-4o" -> uses defaultProvider from config, model=gpt-4o
|
|
5557
|
+
* - undefined / empty -> uses defaultProvider and defaultModel from config
|
|
5558
|
+
*
|
|
5559
|
+
* @param uri Provider:model string, or just a model name
|
|
5560
|
+
* @param config Axl configuration for provider options and defaults
|
|
5561
|
+
*/
|
|
5562
|
+
resolve(uri, config = {}) {
|
|
5563
|
+
if (!uri) {
|
|
5564
|
+
const providerName2 = config.defaultProvider ?? "openai";
|
|
5565
|
+
const model2 = config.defaultModel ?? "gpt-4o";
|
|
5566
|
+
return { provider: this.get(providerName2, config), model: model2 };
|
|
5567
|
+
}
|
|
5568
|
+
const colonIndex = uri.indexOf(":");
|
|
5569
|
+
if (colonIndex === -1) {
|
|
5570
|
+
const providerName2 = config.defaultProvider ?? "openai";
|
|
5571
|
+
return { provider: this.get(providerName2, config), model: uri };
|
|
5572
|
+
}
|
|
5573
|
+
const providerName = uri.slice(0, colonIndex);
|
|
5574
|
+
const model = uri.slice(colonIndex + 1);
|
|
5575
|
+
if (!providerName || !model) {
|
|
5576
|
+
throw new Error(
|
|
5577
|
+
`Invalid provider URI "${uri}". Expected format: "provider:model" (e.g. "openai:gpt-4o")`
|
|
5578
|
+
);
|
|
5579
|
+
}
|
|
5580
|
+
return { provider: this.get(providerName, config), model };
|
|
5581
|
+
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Clear all cached provider instances. Useful for testing or reconfiguration.
|
|
5584
|
+
*/
|
|
5585
|
+
clearCache() {
|
|
5586
|
+
this.instances.clear();
|
|
5587
|
+
}
|
|
5588
|
+
/**
|
|
5589
|
+
* Clear all registered factories (including built-ins).
|
|
5590
|
+
* Useful for test runtimes where only explicitly registered instances should be used.
|
|
5591
|
+
*/
|
|
5592
|
+
clearFactories() {
|
|
5593
|
+
this.factories.clear();
|
|
5594
|
+
}
|
|
5595
|
+
};
|
|
5596
|
+
var defaultRegistry = new ProviderRegistry();
|
|
5597
|
+
|
|
5598
|
+
// src/transcription.ts
|
|
5599
|
+
var MAX_INLINE_TRANSCRIPTION_BYTES = 25 * 1024 * 1024;
|
|
5600
|
+
var RESERVED_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
5601
|
+
"model",
|
|
5602
|
+
"audio",
|
|
5603
|
+
"signal",
|
|
5604
|
+
"cleanup",
|
|
5605
|
+
"cleanupStatus",
|
|
5606
|
+
"usage",
|
|
5607
|
+
"cost",
|
|
5608
|
+
"pricingStatus",
|
|
5609
|
+
"accounting"
|
|
5610
|
+
]);
|
|
5611
|
+
function nonEmptyString(value) {
|
|
5612
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
5613
|
+
}
|
|
5614
|
+
function cloneValue(value) {
|
|
5615
|
+
try {
|
|
5616
|
+
return structuredClone(value);
|
|
5617
|
+
} catch {
|
|
5618
|
+
throw new InvalidTranscriptionInputError("Transcription options must be structured-cloneable");
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
function normalizeTranscriptionRequest(request) {
|
|
5622
|
+
if (!request || typeof request !== "object" || !nonEmptyString(request.model)) {
|
|
5623
|
+
throw new InvalidTranscriptionInputError("A transcription model URI is required");
|
|
5624
|
+
}
|
|
5625
|
+
const audio = request.audio;
|
|
5626
|
+
if (!audio || typeof audio !== "object") {
|
|
5627
|
+
throw new InvalidTranscriptionInputError("A transcription audio source is required");
|
|
5628
|
+
}
|
|
5629
|
+
let normalizedAudio;
|
|
5630
|
+
if (audio.type === "bytes") {
|
|
5631
|
+
if (!(audio.data instanceof Uint8Array) || audio.data.byteLength === 0 || !nonEmptyString(audio.mediaType)) {
|
|
5632
|
+
throw new InvalidTranscriptionInputError(
|
|
5633
|
+
"Inline transcription bytes require nonempty data and mediaType"
|
|
5634
|
+
);
|
|
5635
|
+
}
|
|
5636
|
+
if (audio.data.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES) {
|
|
5637
|
+
throw new InvalidTranscriptionInputError(
|
|
5638
|
+
"Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
|
|
5639
|
+
);
|
|
5640
|
+
}
|
|
5641
|
+
normalizedAudio = {
|
|
5642
|
+
type: "bytes",
|
|
5643
|
+
data: new Uint8Array(audio.data),
|
|
5644
|
+
mediaType: audio.mediaType
|
|
5645
|
+
};
|
|
5646
|
+
} else if (audio.type === "base64") {
|
|
5647
|
+
if (!nonEmptyString(audio.data) || !nonEmptyString(audio.mediaType)) {
|
|
5648
|
+
throw new InvalidTranscriptionInputError(
|
|
5649
|
+
"Inline base64 transcription audio requires data and mediaType"
|
|
5650
|
+
);
|
|
5651
|
+
}
|
|
5652
|
+
if (audio.data.length > 4 * Math.ceil(MAX_INLINE_TRANSCRIPTION_BYTES / 3)) {
|
|
5653
|
+
throw new InvalidTranscriptionInputError(
|
|
5654
|
+
"Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
|
|
5655
|
+
);
|
|
5656
|
+
}
|
|
5657
|
+
let decoded;
|
|
5658
|
+
try {
|
|
5659
|
+
decoded = Uint8Array.from(Buffer.from(audio.data, "base64"));
|
|
5660
|
+
} catch {
|
|
5661
|
+
throw new InvalidTranscriptionInputError(
|
|
5662
|
+
"Transcription base64 must be canonical and decode to audio"
|
|
5663
|
+
);
|
|
5664
|
+
}
|
|
5665
|
+
if (!decoded.byteLength || Buffer.from(decoded).toString("base64") !== audio.data) {
|
|
5666
|
+
throw new InvalidTranscriptionInputError(
|
|
5667
|
+
"Transcription base64 must be canonical and decode to nonempty audio"
|
|
5668
|
+
);
|
|
5669
|
+
}
|
|
5670
|
+
if (decoded.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES) {
|
|
5671
|
+
throw new InvalidTranscriptionInputError(
|
|
5672
|
+
"Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
|
|
5673
|
+
);
|
|
5674
|
+
}
|
|
5675
|
+
normalizedAudio = { type: "base64", data: audio.data, mediaType: audio.mediaType };
|
|
5676
|
+
} else if (audio.type === "provider-file") {
|
|
5677
|
+
if (!nonEmptyString(audio.provider) || !nonEmptyString(audio.reference)) {
|
|
5678
|
+
throw new InvalidTranscriptionInputError(
|
|
5679
|
+
"A provider-file transcription source requires provider and reference"
|
|
5680
|
+
);
|
|
5681
|
+
}
|
|
5682
|
+
if (audio.mediaType !== void 0 && !nonEmptyString(audio.mediaType)) {
|
|
5683
|
+
throw new InvalidTranscriptionInputError(
|
|
5684
|
+
"Transcription provider-file mediaType must be nonempty when supplied"
|
|
5685
|
+
);
|
|
5686
|
+
}
|
|
5687
|
+
normalizedAudio = {
|
|
5688
|
+
type: "provider-file",
|
|
5689
|
+
provider: audio.provider,
|
|
5690
|
+
reference: audio.reference,
|
|
5691
|
+
...audio.mediaType !== void 0 ? { mediaType: audio.mediaType } : {}
|
|
5692
|
+
};
|
|
5693
|
+
} else {
|
|
5694
|
+
throw new InvalidTranscriptionInputError(
|
|
5695
|
+
"Transcription audio must be bytes, base64, or provider-file"
|
|
5696
|
+
);
|
|
5697
|
+
}
|
|
5698
|
+
if (request.language !== void 0 && !nonEmptyString(request.language)) {
|
|
5699
|
+
throw new InvalidTranscriptionInputError(
|
|
5700
|
+
"Transcription language must be nonempty when supplied"
|
|
5701
|
+
);
|
|
5702
|
+
}
|
|
5703
|
+
if (request.timestamps !== void 0 && request.timestamps !== "segment" && request.timestamps !== "word") {
|
|
5704
|
+
throw new InvalidTranscriptionInputError("Transcription timestamps must be segment or word");
|
|
5705
|
+
}
|
|
5706
|
+
if (request.diarization !== void 0 && typeof request.diarization !== "boolean") {
|
|
5707
|
+
throw new InvalidTranscriptionInputError("Transcription diarization must be boolean");
|
|
5708
|
+
}
|
|
5709
|
+
if (request.providerOptions !== void 0) {
|
|
5710
|
+
if (!request.providerOptions || Array.isArray(request.providerOptions) || typeof request.providerOptions !== "object") {
|
|
5711
|
+
throw new InvalidTranscriptionInputError("Transcription providerOptions must be an object");
|
|
5712
|
+
}
|
|
5713
|
+
for (const key of Object.keys(request.providerOptions)) {
|
|
5714
|
+
if (RESERVED_PROVIDER_OPTION_KEYS.has(key)) {
|
|
5715
|
+
throw new InvalidTranscriptionInputError(
|
|
5716
|
+
"Transcription providerOptions cannot override reserved operation fields"
|
|
5717
|
+
);
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
return {
|
|
5722
|
+
model: request.model,
|
|
5723
|
+
audio: normalizedAudio,
|
|
5724
|
+
...request.language !== void 0 ? { language: request.language } : {},
|
|
5725
|
+
...request.timestamps !== void 0 ? { timestamps: request.timestamps } : {},
|
|
5726
|
+
...request.diarization !== void 0 ? { diarization: request.diarization } : {},
|
|
5727
|
+
...request.providerOptions !== void 0 ? { providerOptions: cloneValue(request.providerOptions) } : {}
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
function cloneTranscript(transcript) {
|
|
5731
|
+
if (!transcript || typeof transcript !== "object" || typeof transcript.text !== "string") {
|
|
5732
|
+
throw new InvalidTranscriptionInputError(
|
|
5733
|
+
"Transcription provider returned an invalid transcript"
|
|
5734
|
+
);
|
|
5735
|
+
}
|
|
5736
|
+
const cloned = cloneValue(transcript);
|
|
5737
|
+
const accounting = normalizeTranscriptionAccounting(cloned.usage, cloned.pricingStatus);
|
|
5738
|
+
return {
|
|
5739
|
+
text: cloned.text,
|
|
5740
|
+
...cloned.detectedLanguages ? { detectedLanguages: cloned.detectedLanguages } : {},
|
|
5741
|
+
...cloned.segments ? { segments: cloned.segments } : {},
|
|
5742
|
+
...cloned.words ? { words: cloned.words } : {},
|
|
5743
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
5744
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
5745
|
+
...cloned.providerMetadata ? { providerMetadata: cloned.providerMetadata } : {}
|
|
5746
|
+
};
|
|
5747
|
+
}
|
|
5748
|
+
function normalizeTranscriptionAccounting(usageValue, pricingValue) {
|
|
5749
|
+
const value = usageValue && typeof usageValue === "object" ? usageValue : void 0;
|
|
5750
|
+
const number = (key) => {
|
|
5751
|
+
const candidate = value?.[key];
|
|
5752
|
+
return typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0 ? candidate : void 0;
|
|
5753
|
+
};
|
|
5754
|
+
const audioSeconds = number("audioSeconds");
|
|
5755
|
+
const inputTokens = number("inputTokens");
|
|
5756
|
+
const outputTokens = number("outputTokens");
|
|
5757
|
+
const totalTokens = number("totalTokens");
|
|
5758
|
+
const reportedCost = number("cost");
|
|
5759
|
+
const usage = value && (audioSeconds !== void 0 || inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 || reportedCost !== void 0) ? {
|
|
5760
|
+
...audioSeconds !== void 0 ? { audioSeconds } : {},
|
|
5761
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
5762
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
5763
|
+
...totalTokens !== void 0 ? { totalTokens } : {},
|
|
5764
|
+
...reportedCost !== void 0 ? { cost: reportedCost } : {}
|
|
5765
|
+
} : void 0;
|
|
5766
|
+
const hasWork = (audioSeconds ?? 0) > 0 || (inputTokens ?? 0) > 0 || (outputTokens ?? 0) > 0 || (totalTokens ?? 0) > 0;
|
|
5767
|
+
const requested = pricingValue === "priced" || pricingValue === "unpriced" || pricingValue === "zero" ? pricingValue : void 0;
|
|
5768
|
+
const cost = requested === "zero" && (reportedCost === void 0 || reportedCost === 0) ? 0 : reportedCost;
|
|
5769
|
+
const pricingStatus = requested === "zero" && (reportedCost === void 0 || reportedCost === 0) ? "zero" : requested === "priced" && cost !== void 0 ? "priced" : requested === "unpriced" && cost === void 0 ? "unpriced" : cost !== void 0 ? "priced" : hasWork || requested === "unpriced" || requested === "priced" ? "unpriced" : void 0;
|
|
5770
|
+
const tokens = inputTokens !== void 0 || outputTokens !== void 0 ? {
|
|
5771
|
+
...inputTokens !== void 0 ? { input: inputTokens } : {},
|
|
5772
|
+
...outputTokens !== void 0 ? { output: outputTokens } : {}
|
|
5773
|
+
} : void 0;
|
|
5774
|
+
return {
|
|
5775
|
+
...usage ? { usage } : {},
|
|
5776
|
+
...pricingStatus ? { pricingStatus } : {},
|
|
5777
|
+
...cost !== void 0 ? { cost } : {},
|
|
5778
|
+
...tokens ? { tokens } : {},
|
|
5779
|
+
hasWork
|
|
5780
|
+
};
|
|
5781
|
+
}
|
|
5782
|
+
|
|
5783
|
+
// src/providers/transcription-utils.ts
|
|
5784
|
+
var OPENAI_TRANSCRIPTION_MODEL = "gpt-transcribe";
|
|
5785
|
+
var GEMINI_TRANSCRIPTION_MODEL = "gemini-3.5-transcribe";
|
|
5786
|
+
var OPENROUTER_TRANSCRIPTION_MODEL = "openai/whisper-1";
|
|
5787
|
+
function requireExactTranscriptionModel(provider, model, expected) {
|
|
5788
|
+
if (model !== expected) {
|
|
5789
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5790
|
+
provider,
|
|
5791
|
+
model,
|
|
5792
|
+
feature: "this transcription model"
|
|
5793
|
+
});
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
function inlineAudioBytes(audio) {
|
|
5797
|
+
if (audio.type === "bytes") return new Uint8Array(audio.data);
|
|
5798
|
+
if (audio.type === "base64") return Uint8Array.from(Buffer.from(audio.data, "base64"));
|
|
5799
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5800
|
+
provider: audio.provider,
|
|
5801
|
+
model: "",
|
|
5802
|
+
feature: "provider-file transcription input"
|
|
5803
|
+
});
|
|
5804
|
+
}
|
|
5805
|
+
function requireNoProviderFile(audio, provider, model) {
|
|
5806
|
+
if (audio.type === "provider-file") {
|
|
5807
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5808
|
+
provider,
|
|
5809
|
+
model,
|
|
5810
|
+
feature: "provider-file transcription input"
|
|
5811
|
+
});
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
function assertOnlyProviderOptions(request, provider, model, allowed) {
|
|
5815
|
+
const options = request.providerOptions ?? {};
|
|
5816
|
+
for (const key of Object.keys(options)) {
|
|
5817
|
+
if (!allowed.includes(key)) {
|
|
5818
|
+
throw new InvalidTranscriptionInputError(
|
|
5819
|
+
`Unsupported ${provider} transcription provider option for model '${model}'`
|
|
5820
|
+
);
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
return options;
|
|
5824
|
+
}
|
|
5825
|
+
function transcriptUsage(value) {
|
|
5826
|
+
const source = value && typeof value === "object" ? value : {};
|
|
5827
|
+
const number = (...keys) => {
|
|
5828
|
+
for (const key of keys) {
|
|
5829
|
+
const candidate = source[key];
|
|
5830
|
+
if (typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0)
|
|
5831
|
+
return candidate;
|
|
5832
|
+
}
|
|
5833
|
+
return void 0;
|
|
5834
|
+
};
|
|
5835
|
+
const inputTokens = number("total_input_tokens", "input_tokens", "prompt_tokens", "inputTokens");
|
|
5836
|
+
const outputTokens = number(
|
|
5837
|
+
"total_output_tokens",
|
|
5838
|
+
"output_tokens",
|
|
5839
|
+
"completion_tokens",
|
|
5840
|
+
"outputTokens"
|
|
5841
|
+
);
|
|
5842
|
+
const totalTokens = number("total_tokens", "totalTokens");
|
|
5843
|
+
const audioSeconds = number("seconds", "audio_seconds", "audioSeconds", "duration_seconds");
|
|
5844
|
+
const cost = number("cost");
|
|
5845
|
+
const usage = inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 || audioSeconds !== void 0 || cost !== void 0 ? {
|
|
5846
|
+
...audioSeconds !== void 0 ? { audioSeconds } : {},
|
|
5847
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
5848
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
5849
|
+
...totalTokens !== void 0 ? { totalTokens } : {},
|
|
5850
|
+
...cost !== void 0 ? { cost } : {}
|
|
5851
|
+
} : void 0;
|
|
5852
|
+
const hasWork = (audioSeconds ?? 0) > 0 || (inputTokens ?? 0) > 0 || (outputTokens ?? 0) > 0 || (totalTokens ?? 0) > 0;
|
|
5853
|
+
return {
|
|
5854
|
+
...usage ? { usage } : {},
|
|
5855
|
+
...cost !== void 0 ? { pricingStatus: "priced" } : hasWork ? { pricingStatus: "unpriced" } : {}
|
|
5856
|
+
};
|
|
5857
|
+
}
|
|
5858
|
+
function safeProviderMessage(provider, status, body) {
|
|
5859
|
+
try {
|
|
5860
|
+
const parsed = JSON.parse(body);
|
|
5861
|
+
const message = parsed.error?.message ?? parsed.message;
|
|
5862
|
+
if (typeof message === "string")
|
|
5863
|
+
return `${provider} transcription API error (${status}): ${message}`;
|
|
5864
|
+
} catch {
|
|
5865
|
+
}
|
|
5866
|
+
return `${provider} transcription API error (${status})`;
|
|
5867
|
+
}
|
|
5868
|
+
function attachTranscriptionFailureMetadata(error, metadata) {
|
|
5869
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
5870
|
+
if (value !== void 0) Object.defineProperty(error, key, { enumerable: true, value });
|
|
5871
|
+
}
|
|
5872
|
+
return error;
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
// src/providers/openai-transcription.ts
|
|
5876
|
+
var OPENAI_FILE_EXTENSIONS = {
|
|
5877
|
+
"audio/flac": "flac",
|
|
5878
|
+
"audio/m4a": "m4a",
|
|
5879
|
+
"audio/mp3": "mp3",
|
|
5880
|
+
"audio/mp4": "mp4",
|
|
5881
|
+
"audio/mpeg": "mp3",
|
|
5882
|
+
"audio/mpga": "mpga",
|
|
5883
|
+
"audio/ogg": "ogg",
|
|
5884
|
+
"audio/wav": "wav",
|
|
5885
|
+
"audio/webm": "webm"
|
|
5886
|
+
};
|
|
5887
|
+
var OpenAITranscriptionProvider = class {
|
|
5888
|
+
name = "openai-transcription";
|
|
5889
|
+
baseUrl;
|
|
5890
|
+
apiKeySource;
|
|
5891
|
+
governor;
|
|
5892
|
+
constructor(options = {}) {
|
|
5893
|
+
this.apiKeySource = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
5894
|
+
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
5895
|
+
assertSafeProviderBaseUrl(
|
|
5896
|
+
this.baseUrl,
|
|
5897
|
+
"OpenAI transcription provider",
|
|
5898
|
+
options.dangerouslyAllowInsecureHttp
|
|
5899
|
+
);
|
|
5900
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
5901
|
+
}
|
|
5902
|
+
capabilities(model) {
|
|
5903
|
+
return model === OPENAI_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64"] } : void 0;
|
|
5904
|
+
}
|
|
5905
|
+
async transcribe(request) {
|
|
5906
|
+
requireExactTranscriptionModel(this.name, request.model, OPENAI_TRANSCRIPTION_MODEL);
|
|
5907
|
+
requireNoProviderFile(request.audio, this.name, request.model);
|
|
5908
|
+
if (request.audio.type === "provider-file")
|
|
5909
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5910
|
+
provider: this.name,
|
|
5911
|
+
model: request.model,
|
|
5912
|
+
feature: "provider-file transcription input"
|
|
5913
|
+
});
|
|
5914
|
+
const options = assertOnlyProviderOptions(request, this.name, request.model, [
|
|
5915
|
+
"temperature",
|
|
5916
|
+
"prompt"
|
|
5917
|
+
]);
|
|
5918
|
+
if (options.temperature !== void 0 && (typeof options.temperature !== "number" || !Number.isFinite(options.temperature) || options.temperature < 0 || options.temperature > 1))
|
|
5919
|
+
throw new InvalidTranscriptionInputError(
|
|
5920
|
+
"OpenAI transcription temperature must be a number between 0 and 1"
|
|
5921
|
+
);
|
|
5922
|
+
if (options.prompt !== void 0 && typeof options.prompt !== "string")
|
|
5923
|
+
throw new InvalidTranscriptionInputError("OpenAI transcription prompt must be a string");
|
|
5924
|
+
const mediaType2 = request.audio.mediaType;
|
|
5925
|
+
const extension = OPENAI_FILE_EXTENSIONS[mediaType2];
|
|
5926
|
+
if (!extension)
|
|
5927
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5928
|
+
provider: this.name,
|
|
5929
|
+
model: request.model,
|
|
5930
|
+
feature: "this audio media type"
|
|
5931
|
+
});
|
|
5932
|
+
const bytes = inlineAudioBytes(request.audio);
|
|
5933
|
+
if (bytes.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES)
|
|
5934
|
+
throw new InvalidTranscriptionInputError("OpenAI transcription audio must not exceed 25 MiB");
|
|
5935
|
+
const form = new FormData();
|
|
5936
|
+
form.set("file", new Blob([Buffer.from(bytes)], { type: mediaType2 }), `audio.${extension}`);
|
|
5937
|
+
form.set("model", request.model);
|
|
5938
|
+
form.set("response_format", "json");
|
|
5939
|
+
if (request.language) form.set("language", request.language);
|
|
5940
|
+
if (options.temperature !== void 0) form.set("temperature", String(options.temperature));
|
|
5941
|
+
if (options.prompt !== void 0) form.set("prompt", options.prompt);
|
|
5942
|
+
const key = await this.resolveKey();
|
|
5943
|
+
const res = await fetchWithRetry(
|
|
5944
|
+
`${this.baseUrl}/audio/transcriptions`,
|
|
5945
|
+
{
|
|
5946
|
+
method: "POST",
|
|
5947
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
5948
|
+
body: form,
|
|
5949
|
+
signal: request.signal
|
|
5950
|
+
},
|
|
5951
|
+
{ governor: this.governor, provider: this.name }
|
|
5952
|
+
);
|
|
5953
|
+
if (!res.ok) {
|
|
5954
|
+
const body = await res.text();
|
|
5955
|
+
throw buildProviderError({
|
|
5956
|
+
provider: this.name,
|
|
5957
|
+
status: res.status,
|
|
5958
|
+
headers: res.headers,
|
|
5959
|
+
message: safeProviderMessage("OpenAI", res.status, body),
|
|
5960
|
+
body
|
|
5961
|
+
});
|
|
5962
|
+
}
|
|
5963
|
+
const json = await res.json();
|
|
5964
|
+
if (typeof json.text !== "string")
|
|
5965
|
+
throw new InvalidTranscriptionInputError(
|
|
5966
|
+
"OpenAI transcription response did not contain text"
|
|
5967
|
+
);
|
|
5968
|
+
return {
|
|
5969
|
+
transcript: { text: json.text, ...transcriptUsage(json.usage) },
|
|
5970
|
+
cleanupStatus: "not_required"
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5973
|
+
async resolveKey() {
|
|
5974
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
5975
|
+
if (!key)
|
|
5976
|
+
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
5977
|
+
return key;
|
|
5978
|
+
}
|
|
5979
|
+
};
|
|
5980
|
+
|
|
5981
|
+
// src/providers/gemini-transcription.ts
|
|
5982
|
+
var CLEANUP_TIMEOUT_MS = 3e3;
|
|
5983
|
+
var READY_TIMEOUT_MS = 3e4;
|
|
5984
|
+
var READY_POLL_MS = 250;
|
|
5985
|
+
var GEMINI_AUDIO_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
5986
|
+
"audio/wav",
|
|
5987
|
+
"audio/mp3",
|
|
5988
|
+
"audio/aiff",
|
|
5989
|
+
"audio/aac",
|
|
5990
|
+
"audio/ogg",
|
|
5991
|
+
"audio/flac",
|
|
5992
|
+
"audio/mpeg",
|
|
5993
|
+
"audio/m4a",
|
|
5994
|
+
"audio/l16",
|
|
5995
|
+
"audio/opus",
|
|
5996
|
+
"audio/alaw",
|
|
5997
|
+
"audio/mulaw",
|
|
5998
|
+
"audio/webm"
|
|
5999
|
+
]);
|
|
6000
|
+
function abortError2() {
|
|
6001
|
+
return new DOMException("The transcription operation was aborted", "AbortError");
|
|
6002
|
+
}
|
|
6003
|
+
function audioMime(audio) {
|
|
6004
|
+
if (!audio.mediaType)
|
|
6005
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6006
|
+
provider: "gemini-transcription",
|
|
6007
|
+
model: GEMINI_TRANSCRIPTION_MODEL,
|
|
6008
|
+
feature: "provider-file audio mediaType"
|
|
6009
|
+
});
|
|
6010
|
+
return audio.mediaType;
|
|
6011
|
+
}
|
|
6012
|
+
function assertGeminiAudioMime(mediaType2) {
|
|
6013
|
+
if (!mediaType2 || !GEMINI_AUDIO_MEDIA_TYPES.has(mediaType2)) {
|
|
6014
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6015
|
+
provider: "gemini-transcription",
|
|
6016
|
+
model: GEMINI_TRANSCRIPTION_MODEL,
|
|
6017
|
+
feature: "this audio media type"
|
|
6018
|
+
});
|
|
6019
|
+
}
|
|
6020
|
+
return mediaType2;
|
|
6021
|
+
}
|
|
6022
|
+
function secondsOffset(value) {
|
|
6023
|
+
if (typeof value !== "string") return void 0;
|
|
6024
|
+
const match = /^(\d+(?:\.\d+)?)s$/.exec(value);
|
|
6025
|
+
if (!match) return void 0;
|
|
6026
|
+
const seconds = Number(match[1]);
|
|
6027
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
|
|
6028
|
+
}
|
|
6029
|
+
function sleep3(ms, signal) {
|
|
6030
|
+
return new Promise((resolve2, reject) => {
|
|
6031
|
+
if (signal?.aborted) {
|
|
6032
|
+
reject(abortError2());
|
|
6033
|
+
return;
|
|
6034
|
+
}
|
|
6035
|
+
const timer = setTimeout(done, ms);
|
|
6036
|
+
const onAbort = () => {
|
|
6037
|
+
clearTimeout(timer);
|
|
6038
|
+
done(abortError2());
|
|
6039
|
+
};
|
|
6040
|
+
function done(error) {
|
|
6041
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6042
|
+
if (error) reject(error);
|
|
6043
|
+
else resolve2();
|
|
6044
|
+
}
|
|
6045
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
6046
|
+
});
|
|
6047
|
+
}
|
|
6048
|
+
var GeminiTranscriptionProvider = class {
|
|
6049
|
+
name = "gemini-transcription";
|
|
6050
|
+
baseUrl;
|
|
6051
|
+
apiKeySource;
|
|
6052
|
+
governor;
|
|
6053
|
+
constructor(options = {}) {
|
|
6054
|
+
this.apiKeySource = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "";
|
|
6055
|
+
this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(
|
|
6056
|
+
/\/$/,
|
|
6057
|
+
""
|
|
6058
|
+
);
|
|
6059
|
+
assertSafeProviderBaseUrl(
|
|
6060
|
+
this.baseUrl,
|
|
6061
|
+
"Gemini transcription provider",
|
|
6062
|
+
options.dangerouslyAllowInsecureHttp
|
|
6063
|
+
);
|
|
6064
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
6065
|
+
}
|
|
6066
|
+
capabilities(model) {
|
|
6067
|
+
return model === GEMINI_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64", "provider-file"], timestamps: ["word"], diarization: true } : void 0;
|
|
6068
|
+
}
|
|
6069
|
+
async transcribe(request) {
|
|
6070
|
+
requireExactTranscriptionModel(this.name, request.model, GEMINI_TRANSCRIPTION_MODEL);
|
|
6071
|
+
const options = assertOnlyProviderOptions(request, this.name, request.model, [
|
|
6072
|
+
"mode",
|
|
6073
|
+
"customVocabulary"
|
|
6074
|
+
]);
|
|
6075
|
+
const mode = options.mode ?? "verbatim";
|
|
6076
|
+
if (mode !== "smart" && mode !== "verbatim")
|
|
6077
|
+
throw new InvalidTranscriptionInputError(
|
|
6078
|
+
"Gemini transcription mode must be smart or verbatim"
|
|
6079
|
+
);
|
|
6080
|
+
if (options.customVocabulary !== void 0 && (!Array.isArray(options.customVocabulary) || options.customVocabulary.length > 1e3 || options.customVocabulary.some(
|
|
6081
|
+
(value) => typeof value !== "string" || !value.trim() || value.length > 256
|
|
6082
|
+
)))
|
|
6083
|
+
throw new InvalidTranscriptionInputError(
|
|
6084
|
+
"Gemini transcription customVocabulary must contain at most 1000 nonempty strings up to 256 characters"
|
|
6085
|
+
);
|
|
6086
|
+
if (mode === "smart" && (request.timestamps || request.diarization))
|
|
6087
|
+
throw new InvalidTranscriptionInputError(
|
|
6088
|
+
"Gemini smart transcription does not support timestamps or diarization"
|
|
6089
|
+
);
|
|
6090
|
+
if (request.timestamps && options.customVocabulary !== void 0)
|
|
6091
|
+
throw new InvalidTranscriptionInputError(
|
|
6092
|
+
"Gemini transcription customVocabulary cannot be combined with timestamps"
|
|
6093
|
+
);
|
|
6094
|
+
if (request.timestamps && request.timestamps !== "word")
|
|
6095
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6096
|
+
provider: this.name,
|
|
6097
|
+
model: request.model,
|
|
6098
|
+
feature: "segment timestamps"
|
|
6099
|
+
});
|
|
6100
|
+
if (request.diarization && request.timestamps !== "word")
|
|
6101
|
+
throw new InvalidTranscriptionInputError("Gemini diarization requires word timestamps");
|
|
6102
|
+
if (request.audio.type === "provider-file") {
|
|
6103
|
+
if (request.audio.provider !== this.name || typeof request.audio.reference !== "string" || !request.audio.reference.trim()) {
|
|
6104
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6105
|
+
provider: this.name,
|
|
6106
|
+
model: request.model,
|
|
6107
|
+
feature: "a matching nonempty provider-file reference"
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
assertGeminiAudioMime(request.audio.mediaType);
|
|
6111
|
+
} else {
|
|
6112
|
+
assertGeminiAudioMime(request.audio.mediaType);
|
|
6113
|
+
}
|
|
6114
|
+
const key = await this.resolveKey();
|
|
6115
|
+
if (request.audio.type === "provider-file")
|
|
6116
|
+
return this.transcribeReference(request, key, mode, options);
|
|
6117
|
+
return this.transcribeUpload(request, key, mode, options);
|
|
6118
|
+
}
|
|
6119
|
+
async transcribeReference(request, key, mode, options) {
|
|
6120
|
+
const audio = request.audio;
|
|
6121
|
+
if (audio.type !== "provider-file" || audio.provider !== this.name)
|
|
6122
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6123
|
+
provider: this.name,
|
|
6124
|
+
model: request.model,
|
|
6125
|
+
feature: "a provider-file owned by another provider"
|
|
6126
|
+
});
|
|
6127
|
+
const mimeType = audioMime(audio);
|
|
6128
|
+
const transcript = await this.infer(request, key, audio.reference, mimeType, mode, options);
|
|
6129
|
+
return { transcript, cleanupStatus: "not_required" };
|
|
6130
|
+
}
|
|
6131
|
+
async transcribeUpload(request, key, mode, options) {
|
|
6132
|
+
let created = false;
|
|
6133
|
+
let fileName;
|
|
6134
|
+
let primaryError;
|
|
6135
|
+
let transcript;
|
|
6136
|
+
let cleanupStatus = "not_required";
|
|
6137
|
+
try {
|
|
6138
|
+
const uploaded = await this.upload(request, key);
|
|
6139
|
+
created = true;
|
|
6140
|
+
fileName = uploaded.name;
|
|
6141
|
+
const ready = await this.waitForReady(
|
|
6142
|
+
this.fileFromJson(uploaded.file, uploaded.mimeType),
|
|
6143
|
+
key,
|
|
6144
|
+
request.signal
|
|
6145
|
+
);
|
|
6146
|
+
transcript = await this.infer(request, key, ready.uri, ready.mimeType, mode, options);
|
|
6147
|
+
} catch (error) {
|
|
6148
|
+
primaryError = error;
|
|
6149
|
+
} finally {
|
|
6150
|
+
if (created && fileName) cleanupStatus = await this.cleanup(fileName, key);
|
|
6151
|
+
}
|
|
6152
|
+
if (primaryError) {
|
|
6153
|
+
throw attachTranscriptionFailureMetadata(
|
|
6154
|
+
primaryError instanceof Error ? primaryError : new Error("Gemini transcription failed"),
|
|
6155
|
+
{ cleanupStatus }
|
|
6156
|
+
);
|
|
6157
|
+
}
|
|
6158
|
+
if (!transcript)
|
|
6159
|
+
throw new InvalidTranscriptionInputError(
|
|
6160
|
+
"Gemini transcription response did not contain text"
|
|
6161
|
+
);
|
|
6162
|
+
return { transcript, cleanupStatus };
|
|
6163
|
+
}
|
|
6164
|
+
/** A finalization response with a name creates a caller-owned cleanup obligation
|
|
6165
|
+
* even when its URI/state is malformed. `transcribeUpload` stores the name before
|
|
6166
|
+
* strictly parsing the rest of the file representation. */
|
|
6167
|
+
async upload(request, key) {
|
|
6168
|
+
if (request.audio.type === "provider-file")
|
|
6169
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6170
|
+
provider: this.name,
|
|
6171
|
+
model: request.model,
|
|
6172
|
+
feature: "provider-file upload"
|
|
6173
|
+
});
|
|
6174
|
+
const mimeType = assertGeminiAudioMime(request.audio.mediaType);
|
|
6175
|
+
const bytes = inlineAudioBytes(request.audio);
|
|
6176
|
+
const start = await fetchWithRetry(
|
|
6177
|
+
`${this.baseUrl.replace(/\/v1beta$/, "")}/upload/v1beta/files`,
|
|
6178
|
+
{
|
|
6179
|
+
method: "POST",
|
|
6180
|
+
headers: {
|
|
6181
|
+
"x-goog-api-key": key,
|
|
6182
|
+
"X-Goog-Upload-Protocol": "resumable",
|
|
6183
|
+
"X-Goog-Upload-Command": "start",
|
|
6184
|
+
"X-Goog-Upload-Header-Content-Length": String(bytes.byteLength),
|
|
6185
|
+
"X-Goog-Upload-Header-Content-Type": mimeType,
|
|
6186
|
+
"Content-Type": "application/json"
|
|
6187
|
+
},
|
|
6188
|
+
body: JSON.stringify({ file: { display_name: "axl-transcription" } }),
|
|
6189
|
+
signal: request.signal
|
|
6190
|
+
},
|
|
6191
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6192
|
+
);
|
|
6193
|
+
if (!start.ok) {
|
|
6194
|
+
const body = await start.text();
|
|
6195
|
+
throw buildProviderError({
|
|
6196
|
+
provider: this.name,
|
|
6197
|
+
status: start.status,
|
|
6198
|
+
headers: start.headers,
|
|
6199
|
+
message: safeProviderMessage("Gemini", start.status, body),
|
|
6200
|
+
body
|
|
6201
|
+
});
|
|
6202
|
+
}
|
|
6203
|
+
const uploadUrl = start.headers.get("x-goog-upload-url");
|
|
6204
|
+
if (!uploadUrl)
|
|
6205
|
+
throw new InvalidTranscriptionInputError(
|
|
6206
|
+
"Gemini transcription upload did not return an upload URL"
|
|
6207
|
+
);
|
|
6208
|
+
let parsedUploadUrl;
|
|
6209
|
+
try {
|
|
6210
|
+
parsedUploadUrl = new URL(uploadUrl);
|
|
6211
|
+
} catch {
|
|
6212
|
+
throw new InvalidTranscriptionInputError(
|
|
6213
|
+
"Gemini transcription upload returned an invalid upload URL"
|
|
6214
|
+
);
|
|
6215
|
+
}
|
|
6216
|
+
const baseOrigin = new URL(this.baseUrl).origin;
|
|
6217
|
+
if (parsedUploadUrl.username || parsedUploadUrl.password || parsedUploadUrl.origin !== baseOrigin) {
|
|
6218
|
+
throw new InvalidTranscriptionInputError(
|
|
6219
|
+
"Gemini transcription upload returned an untrusted upload URL"
|
|
6220
|
+
);
|
|
6221
|
+
}
|
|
6222
|
+
assertSafeProviderBaseUrl(
|
|
6223
|
+
parsedUploadUrl.toString(),
|
|
6224
|
+
"Gemini transcription upload endpoint",
|
|
6225
|
+
new URL(this.baseUrl).protocol === "http:"
|
|
6226
|
+
);
|
|
6227
|
+
const finalize = await fetchWithRetry(
|
|
6228
|
+
parsedUploadUrl.toString(),
|
|
6229
|
+
{
|
|
6230
|
+
method: "POST",
|
|
6231
|
+
headers: {
|
|
6232
|
+
"X-Goog-Upload-Command": "upload, finalize",
|
|
6233
|
+
"X-Goog-Upload-Offset": "0",
|
|
6234
|
+
"Content-Length": String(bytes.byteLength)
|
|
6235
|
+
},
|
|
6236
|
+
body: bytes,
|
|
6237
|
+
signal: request.signal
|
|
6238
|
+
},
|
|
6239
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6240
|
+
);
|
|
6241
|
+
if (!finalize.ok) {
|
|
6242
|
+
const body = await finalize.text();
|
|
6243
|
+
throw buildProviderError({
|
|
6244
|
+
provider: this.name,
|
|
6245
|
+
status: finalize.status,
|
|
6246
|
+
headers: finalize.headers,
|
|
6247
|
+
message: safeProviderMessage("Gemini", finalize.status, body),
|
|
6248
|
+
body
|
|
6249
|
+
});
|
|
6250
|
+
}
|
|
6251
|
+
const payload = await finalize.json();
|
|
6252
|
+
const file = payload.file;
|
|
6253
|
+
if (!file || typeof file.name !== "string") {
|
|
6254
|
+
throw new InvalidTranscriptionInputError(
|
|
6255
|
+
"Gemini transcription upload did not return a file name"
|
|
6256
|
+
);
|
|
6257
|
+
}
|
|
6258
|
+
return { name: file.name, file, mimeType };
|
|
6259
|
+
}
|
|
6260
|
+
async waitForReady(file, key, signal) {
|
|
6261
|
+
let current = file;
|
|
6262
|
+
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
6263
|
+
if (!current.state) {
|
|
6264
|
+
throw new InvalidTranscriptionInputError(
|
|
6265
|
+
"Gemini transcription file readiness state is missing"
|
|
6266
|
+
);
|
|
6267
|
+
}
|
|
6268
|
+
while (current.state && current.state !== "ACTIVE") {
|
|
6269
|
+
if (current.state === "FAILED")
|
|
6270
|
+
throw new InvalidTranscriptionInputError("Gemini transcription file processing failed");
|
|
6271
|
+
if (Date.now() >= deadline)
|
|
6272
|
+
throw new InvalidTranscriptionInputError("Gemini transcription file readiness timed out");
|
|
6273
|
+
await sleep3(Math.min(READY_POLL_MS, deadline - Date.now()), signal);
|
|
6274
|
+
const res = await fetchWithRetry(
|
|
6275
|
+
`${this.baseUrl}/${current.name}`,
|
|
6276
|
+
{ method: "GET", headers: { "x-goog-api-key": key }, signal },
|
|
6277
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6278
|
+
);
|
|
6279
|
+
if (!res.ok) {
|
|
6280
|
+
const body = await res.text();
|
|
6281
|
+
throw buildProviderError({
|
|
6282
|
+
provider: this.name,
|
|
6283
|
+
status: res.status,
|
|
6284
|
+
headers: res.headers,
|
|
6285
|
+
message: safeProviderMessage("Gemini", res.status, body),
|
|
6286
|
+
body
|
|
6287
|
+
});
|
|
6288
|
+
}
|
|
6289
|
+
current = this.fileFromJson(await res.json(), current.mimeType);
|
|
6290
|
+
}
|
|
6291
|
+
return current;
|
|
6292
|
+
}
|
|
6293
|
+
async infer(request, key, uri, mimeType, mode, options) {
|
|
6294
|
+
const transcriptionConfig = {
|
|
6295
|
+
mode: mode === "smart" ? "smart" : {
|
|
6296
|
+
type: "verbatim",
|
|
6297
|
+
...request.timestamps ? { timestamp_granularities: ["word"] } : {},
|
|
6298
|
+
...request.diarization ? { diarization_mode: "speaker" } : {}
|
|
6299
|
+
},
|
|
6300
|
+
...request.language ? { language_codes: [request.language] } : {},
|
|
6301
|
+
...options.customVocabulary ? { custom_vocabulary: options.customVocabulary } : {}
|
|
6302
|
+
};
|
|
6303
|
+
const body = {
|
|
6304
|
+
model: request.model,
|
|
6305
|
+
input: [{ type: "audio", uri, mime_type: mimeType }],
|
|
6306
|
+
store: false,
|
|
6307
|
+
generation_config: { transcription_config: transcriptionConfig }
|
|
6308
|
+
};
|
|
6309
|
+
const res = await fetchWithRetry(
|
|
6310
|
+
`${this.baseUrl}/interactions`,
|
|
6311
|
+
{
|
|
6312
|
+
method: "POST",
|
|
6313
|
+
headers: { "x-goog-api-key": key, "Content-Type": "application/json" },
|
|
6314
|
+
body: JSON.stringify(body),
|
|
6315
|
+
signal: request.signal
|
|
6316
|
+
},
|
|
6317
|
+
{ governor: this.governor, provider: this.name }
|
|
6318
|
+
);
|
|
6319
|
+
if (!res.ok) {
|
|
6320
|
+
const errorBody = await res.text();
|
|
6321
|
+
throw buildProviderError({
|
|
6322
|
+
provider: this.name,
|
|
6323
|
+
status: res.status,
|
|
6324
|
+
headers: res.headers,
|
|
6325
|
+
message: safeProviderMessage("Gemini", res.status, errorBody),
|
|
6326
|
+
body: errorBody
|
|
6327
|
+
});
|
|
6328
|
+
}
|
|
6329
|
+
const json = await res.json();
|
|
6330
|
+
if (json.status !== "completed") {
|
|
6331
|
+
throw new InvalidTranscriptionInputError("Gemini transcription interaction did not complete");
|
|
6332
|
+
}
|
|
6333
|
+
const words = [];
|
|
6334
|
+
let text = "";
|
|
6335
|
+
let hasModelOutputText = false;
|
|
6336
|
+
if (Array.isArray(json.steps)) {
|
|
6337
|
+
for (const step of json.steps) {
|
|
6338
|
+
if (!step || typeof step !== "object") continue;
|
|
6339
|
+
const modelOutput = step;
|
|
6340
|
+
if (modelOutput.type !== "model_output" || !Array.isArray(modelOutput.content)) continue;
|
|
6341
|
+
for (const block of modelOutput.content) {
|
|
6342
|
+
if (!block || typeof block !== "object") continue;
|
|
6343
|
+
const content = block;
|
|
6344
|
+
if (content.type !== "text" || typeof content.text !== "string") continue;
|
|
6345
|
+
hasModelOutputText = true;
|
|
6346
|
+
text += content.text;
|
|
6347
|
+
if (!Array.isArray(content.annotations)) continue;
|
|
6348
|
+
for (const raw of content.annotations) {
|
|
6349
|
+
if (!raw || typeof raw !== "object") continue;
|
|
6350
|
+
const annotation = raw;
|
|
6351
|
+
if (annotation.type !== "word_info") continue;
|
|
6352
|
+
if (typeof annotation.text !== "string") continue;
|
|
6353
|
+
const start = secondsOffset(annotation.start_offset);
|
|
6354
|
+
const end = secondsOffset(annotation.end_offset);
|
|
6355
|
+
if (start === void 0 || end === void 0 || end < start) continue;
|
|
6356
|
+
words.push({
|
|
6357
|
+
text: annotation.text,
|
|
6358
|
+
start,
|
|
6359
|
+
end,
|
|
6360
|
+
...typeof annotation.speaker === "string" ? { speaker: annotation.speaker } : {}
|
|
6361
|
+
});
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
if (!hasModelOutputText)
|
|
6367
|
+
throw new InvalidTranscriptionInputError(
|
|
6368
|
+
"Gemini transcription response did not contain model output text"
|
|
6369
|
+
);
|
|
6370
|
+
return { text, ...words.length ? { words } : {}, ...transcriptUsage(json.usage) };
|
|
6371
|
+
}
|
|
6372
|
+
fileFromJson(value, fallbackMimeType) {
|
|
6373
|
+
const file = "file" in value && value.file ? value.file : value;
|
|
6374
|
+
if (typeof file.name !== "string" || typeof file.uri !== "string")
|
|
6375
|
+
throw new InvalidTranscriptionInputError(
|
|
6376
|
+
"Gemini transcription upload did not return a file reference"
|
|
6377
|
+
);
|
|
6378
|
+
return {
|
|
6379
|
+
name: file.name,
|
|
6380
|
+
uri: file.uri,
|
|
6381
|
+
mimeType: typeof file.mimeType === "string" ? file.mimeType : typeof file.mime_type === "string" ? file.mime_type : fallbackMimeType,
|
|
6382
|
+
...typeof file.state === "string" ? { state: file.state } : {}
|
|
6383
|
+
};
|
|
6384
|
+
}
|
|
6385
|
+
async cleanup(fileName, key) {
|
|
6386
|
+
const controller = new AbortController();
|
|
6387
|
+
const timer = setTimeout(() => controller.abort(), CLEANUP_TIMEOUT_MS);
|
|
6388
|
+
try {
|
|
6389
|
+
const res = await fetchWithRetry(
|
|
6390
|
+
`${this.baseUrl}/${fileName}`,
|
|
6391
|
+
{ method: "DELETE", headers: { "x-goog-api-key": key }, signal: controller.signal },
|
|
6392
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6393
|
+
);
|
|
6394
|
+
return res.ok ? "deleted" : "failed";
|
|
6395
|
+
} catch {
|
|
6396
|
+
return controller.signal.aborted ? "timed_out" : "failed";
|
|
6397
|
+
} finally {
|
|
6398
|
+
clearTimeout(timer);
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
async resolveKey() {
|
|
6402
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
6403
|
+
if (!key)
|
|
6404
|
+
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
6405
|
+
return key;
|
|
6406
|
+
}
|
|
6407
|
+
};
|
|
6408
|
+
|
|
6409
|
+
// src/providers/openrouter-transcription.ts
|
|
6410
|
+
var FORMAT_BY_MIME = {
|
|
6411
|
+
"audio/aac": "aac",
|
|
6412
|
+
"audio/flac": "flac",
|
|
6413
|
+
"audio/m4a": "m4a",
|
|
6414
|
+
"audio/mp3": "mp3",
|
|
6415
|
+
"audio/mp4": "mp4",
|
|
6416
|
+
"audio/mpeg": "mp3",
|
|
6417
|
+
"audio/ogg": "ogg",
|
|
6418
|
+
"audio/wav": "wav",
|
|
6419
|
+
"audio/webm": "webm"
|
|
6420
|
+
};
|
|
6421
|
+
var OpenRouterTranscriptionProvider = class {
|
|
6422
|
+
name = "openrouter-transcription";
|
|
6423
|
+
baseUrl;
|
|
6424
|
+
apiKeySource;
|
|
6425
|
+
governor;
|
|
6426
|
+
constructor(options = {}) {
|
|
6427
|
+
this.apiKeySource = options.apiKey ?? process.env.OPENROUTER_API_KEY ?? "";
|
|
6428
|
+
this.baseUrl = (options.baseUrl ?? process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1").replace(/\/$/, "");
|
|
6429
|
+
assertSafeProviderBaseUrl(
|
|
6430
|
+
this.baseUrl,
|
|
6431
|
+
"OpenRouter transcription provider",
|
|
6432
|
+
options.dangerouslyAllowInsecureHttp
|
|
6433
|
+
);
|
|
6434
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
6435
|
+
}
|
|
6436
|
+
capabilities(model) {
|
|
6437
|
+
return model === OPENROUTER_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64"] } : void 0;
|
|
6438
|
+
}
|
|
6439
|
+
async transcribe(request) {
|
|
6440
|
+
requireExactTranscriptionModel(this.name, request.model, OPENROUTER_TRANSCRIPTION_MODEL);
|
|
6441
|
+
requireNoProviderFile(request.audio, this.name, request.model);
|
|
6442
|
+
if (request.audio.type === "provider-file")
|
|
6443
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6444
|
+
provider: this.name,
|
|
6445
|
+
model: request.model,
|
|
6446
|
+
feature: "provider-file transcription input"
|
|
6447
|
+
});
|
|
6448
|
+
const options = assertOnlyProviderOptions(request, this.name, request.model, [
|
|
6449
|
+
"temperature",
|
|
6450
|
+
"provider"
|
|
6451
|
+
]);
|
|
6452
|
+
if (options.temperature !== void 0 && (typeof options.temperature !== "number" || !Number.isFinite(options.temperature) || options.temperature < 0 || options.temperature > 1))
|
|
6453
|
+
throw new InvalidTranscriptionInputError(
|
|
6454
|
+
"OpenRouter transcription temperature must be a number between 0 and 1"
|
|
6455
|
+
);
|
|
6456
|
+
if (options.provider !== void 0 && (!options.provider || Array.isArray(options.provider) || typeof options.provider !== "object"))
|
|
6457
|
+
throw new InvalidTranscriptionInputError(
|
|
6458
|
+
"OpenRouter transcription provider routing must be an object"
|
|
6459
|
+
);
|
|
6460
|
+
const format = FORMAT_BY_MIME[request.audio.mediaType];
|
|
6461
|
+
if (!format)
|
|
6462
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6463
|
+
provider: this.name,
|
|
6464
|
+
model: request.model,
|
|
6465
|
+
feature: "this audio media type"
|
|
6466
|
+
});
|
|
6467
|
+
const bytes = inlineAudioBytes(request.audio);
|
|
6468
|
+
const body = {
|
|
6469
|
+
model: request.model,
|
|
6470
|
+
input_audio: { data: Buffer.from(bytes).toString("base64"), format },
|
|
6471
|
+
...request.language ? { language: request.language } : {},
|
|
6472
|
+
...options.temperature !== void 0 ? { temperature: options.temperature } : {},
|
|
6473
|
+
...options.provider !== void 0 ? { provider: options.provider } : {}
|
|
6474
|
+
};
|
|
6475
|
+
const key = await this.resolveKey();
|
|
6476
|
+
const res = await fetchWithRetry(
|
|
6477
|
+
`${this.baseUrl}/audio/transcriptions`,
|
|
6478
|
+
{
|
|
6479
|
+
method: "POST",
|
|
6480
|
+
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
6481
|
+
body: JSON.stringify(body),
|
|
6482
|
+
signal: request.signal
|
|
6483
|
+
},
|
|
6484
|
+
{ governor: this.governor, provider: this.name }
|
|
6485
|
+
);
|
|
6486
|
+
if (!res.ok) {
|
|
6487
|
+
const errorBody = await res.text();
|
|
6488
|
+
throw buildProviderError({
|
|
6489
|
+
provider: this.name,
|
|
6490
|
+
status: res.status,
|
|
6491
|
+
headers: res.headers,
|
|
6492
|
+
message: safeProviderMessage("OpenRouter", res.status, errorBody),
|
|
6493
|
+
body: errorBody
|
|
6494
|
+
});
|
|
6495
|
+
}
|
|
6496
|
+
const json = await res.json();
|
|
6497
|
+
if (typeof json.text !== "string")
|
|
6498
|
+
throw new InvalidTranscriptionInputError(
|
|
6499
|
+
"OpenRouter transcription response did not contain text"
|
|
6500
|
+
);
|
|
6501
|
+
return {
|
|
6502
|
+
transcript: { text: json.text, ...transcriptUsage(json.usage) },
|
|
6503
|
+
cleanupStatus: "not_required"
|
|
6504
|
+
};
|
|
6505
|
+
}
|
|
6506
|
+
async resolveKey() {
|
|
6507
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
6508
|
+
if (!key)
|
|
6509
|
+
throw new Error(
|
|
6510
|
+
"OpenRouter API key is required. Set OPENROUTER_API_KEY or pass apiKey in options."
|
|
6511
|
+
);
|
|
6512
|
+
return key;
|
|
6513
|
+
}
|
|
6514
|
+
};
|
|
6515
|
+
|
|
6516
|
+
// src/providers/transcription-registry.ts
|
|
6517
|
+
var builtinFactories2 = {
|
|
6518
|
+
"openai-transcription": (config) => {
|
|
6519
|
+
const opts = config.providers?.openai ?? {};
|
|
6520
|
+
return new OpenAITranscriptionProvider({
|
|
6521
|
+
apiKey: opts.apiKey,
|
|
6522
|
+
baseUrl: opts.baseUrl,
|
|
6523
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
6524
|
+
rateLimit: opts.rateLimit
|
|
6525
|
+
});
|
|
6526
|
+
},
|
|
6527
|
+
"gemini-transcription": (config) => {
|
|
6528
|
+
const opts = config.providers?.google ?? {};
|
|
6529
|
+
return new GeminiTranscriptionProvider({
|
|
6530
|
+
apiKey: opts.apiKey,
|
|
6531
|
+
baseUrl: opts.baseUrl,
|
|
6532
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
6533
|
+
rateLimit: opts.rateLimit
|
|
6534
|
+
});
|
|
6535
|
+
},
|
|
6536
|
+
"openrouter-transcription": (config) => {
|
|
6537
|
+
const opts = config.providers?.openrouter ?? {};
|
|
6538
|
+
return new OpenRouterTranscriptionProvider({
|
|
6539
|
+
apiKey: opts.apiKey,
|
|
6540
|
+
baseUrl: opts.baseUrl,
|
|
6541
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
6542
|
+
rateLimit: opts.rateLimit
|
|
6543
|
+
});
|
|
6544
|
+
}
|
|
6545
|
+
};
|
|
6546
|
+
var TranscriptionProviderRegistry = class {
|
|
6547
|
+
instances = /* @__PURE__ */ new Map();
|
|
6548
|
+
factories = /* @__PURE__ */ new Map();
|
|
6549
|
+
constructor() {
|
|
6550
|
+
for (const [name, factory] of Object.entries(builtinFactories2))
|
|
6551
|
+
this.factories.set(name, factory);
|
|
6552
|
+
}
|
|
6553
|
+
register(name, factory) {
|
|
6554
|
+
this.factories.set(name, factory);
|
|
6555
|
+
this.instances.delete(name);
|
|
6556
|
+
}
|
|
6557
|
+
registerInstance(name, provider) {
|
|
6558
|
+
this.instances.set(name, provider);
|
|
6559
|
+
}
|
|
6560
|
+
has(name) {
|
|
6561
|
+
return this.instances.has(name) || this.factories.has(name);
|
|
4490
6562
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
6563
|
+
get(name, config = {}) {
|
|
6564
|
+
const instance = this.instances.get(name);
|
|
6565
|
+
if (instance) return instance;
|
|
6566
|
+
const factory = this.factories.get(name);
|
|
6567
|
+
if (factory) {
|
|
6568
|
+
const created = factory(config);
|
|
6569
|
+
this.instances.set(name, created);
|
|
6570
|
+
return created;
|
|
6571
|
+
}
|
|
6572
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6573
|
+
provider: name,
|
|
6574
|
+
model: "",
|
|
6575
|
+
feature: "transcription"
|
|
6576
|
+
});
|
|
4496
6577
|
}
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
6578
|
+
resolve(uri, config = {}) {
|
|
6579
|
+
const colon = typeof uri === "string" ? uri.indexOf(":") : -1;
|
|
6580
|
+
if (colon <= 0 || colon === uri.length - 1 || uri.indexOf(":", colon + 1) !== -1) {
|
|
6581
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6582
|
+
provider: "unknown",
|
|
6583
|
+
model: "",
|
|
6584
|
+
feature: "an explicit transcription provider:model URI"
|
|
6585
|
+
});
|
|
6586
|
+
}
|
|
6587
|
+
const providerName = uri.slice(0, colon);
|
|
6588
|
+
const model = uri.slice(colon + 1);
|
|
6589
|
+
return { provider: this.get(providerName, config), model, providerName };
|
|
4503
6590
|
}
|
|
4504
6591
|
};
|
|
4505
|
-
var defaultRegistry = new ProviderRegistry();
|
|
4506
6592
|
|
|
4507
6593
|
// src/context.ts
|
|
4508
6594
|
var import_node_async_hooks = require("async_hooks");
|
|
4509
6595
|
var import_node_crypto = require("crypto");
|
|
4510
6596
|
var import_zod2 = require("zod");
|
|
4511
6597
|
|
|
6598
|
+
// src/input.ts
|
|
6599
|
+
var MAX_INLINE_MODEL_INPUT_BYTES = 25 * 1024 * 1024;
|
|
6600
|
+
function invalid(message) {
|
|
6601
|
+
throw new InvalidModelInputError(message);
|
|
6602
|
+
}
|
|
6603
|
+
function nonEmptyString2(value, field) {
|
|
6604
|
+
if (typeof value !== "string" || value.length === 0)
|
|
6605
|
+
invalid(`${field} must be a non-empty string`);
|
|
6606
|
+
return value;
|
|
6607
|
+
}
|
|
6608
|
+
function mediaType(value, field) {
|
|
6609
|
+
return nonEmptyString2(value, field);
|
|
6610
|
+
}
|
|
6611
|
+
function validBase64(value) {
|
|
6612
|
+
return value.length > 0 && value.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(value);
|
|
6613
|
+
}
|
|
6614
|
+
function decodedBase64Bytes(value) {
|
|
6615
|
+
return Math.floor(value.length * 3 / 4) - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0);
|
|
6616
|
+
}
|
|
6617
|
+
function normalizeModelInput(input) {
|
|
6618
|
+
if (typeof input === "string") return input;
|
|
6619
|
+
if (!Array.isArray(input) || input.length === 0)
|
|
6620
|
+
invalid("ModelInput parts must be a non-empty array");
|
|
6621
|
+
let inlineBytes = 0;
|
|
6622
|
+
const reserveInlineBytes = (bytes) => {
|
|
6623
|
+
if (bytes > MAX_INLINE_MODEL_INPUT_BYTES - inlineBytes) {
|
|
6624
|
+
invalid(
|
|
6625
|
+
"Inline image data must not exceed 25 MiB total; use a URL or provider-file source where supported"
|
|
6626
|
+
);
|
|
6627
|
+
}
|
|
6628
|
+
inlineBytes += bytes;
|
|
6629
|
+
};
|
|
6630
|
+
return input.map((part, index) => {
|
|
6631
|
+
if (!part || typeof part !== "object") invalid(`ModelInput part ${index} must be an object`);
|
|
6632
|
+
if (part.type === "text")
|
|
6633
|
+
return { type: "text", text: nonEmptyString2(part.text, `part ${index}.text`) };
|
|
6634
|
+
if (part.type !== "image") invalid(`part ${index}.type must be 'text' or 'image'`);
|
|
6635
|
+
const source = part.source;
|
|
6636
|
+
if (!source || typeof source !== "object") invalid(`part ${index}.source must be an object`);
|
|
6637
|
+
let clone;
|
|
6638
|
+
switch (source.type) {
|
|
6639
|
+
case "url": {
|
|
6640
|
+
const url = nonEmptyString2(source.url, `part ${index}.source.url`);
|
|
6641
|
+
let parsed;
|
|
6642
|
+
try {
|
|
6643
|
+
parsed = new URL(url);
|
|
6644
|
+
} catch {
|
|
6645
|
+
invalid(`part ${index}.source.url must be an http(s) URL`);
|
|
6646
|
+
}
|
|
6647
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
6648
|
+
invalid(`part ${index}.source.url must be an http(s) URL`);
|
|
6649
|
+
clone = {
|
|
6650
|
+
type: "url",
|
|
6651
|
+
url,
|
|
6652
|
+
...source.mediaType === void 0 ? {} : { mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`) }
|
|
6653
|
+
};
|
|
6654
|
+
break;
|
|
6655
|
+
}
|
|
6656
|
+
case "bytes":
|
|
6657
|
+
if (!(source.data instanceof Uint8Array) || source.data.byteLength === 0)
|
|
6658
|
+
invalid(`part ${index}.source.data must be a non-empty Uint8Array`);
|
|
6659
|
+
reserveInlineBytes(source.data.byteLength);
|
|
6660
|
+
clone = {
|
|
6661
|
+
type: "bytes",
|
|
6662
|
+
data: source.data.slice(),
|
|
6663
|
+
mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`)
|
|
6664
|
+
};
|
|
6665
|
+
break;
|
|
6666
|
+
case "base64": {
|
|
6667
|
+
const data = nonEmptyString2(source.data, `part ${index}.source.data`);
|
|
6668
|
+
if (data.length > 4 * Math.ceil(MAX_INLINE_MODEL_INPUT_BYTES / 3)) {
|
|
6669
|
+
invalid(
|
|
6670
|
+
"Inline image data must not exceed 25 MiB total; use a URL or provider-file source where supported"
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
if (!validBase64(data)) invalid(`part ${index}.source.data must be valid base64`);
|
|
6674
|
+
reserveInlineBytes(decodedBase64Bytes(data));
|
|
6675
|
+
clone = {
|
|
6676
|
+
type: "base64",
|
|
6677
|
+
data,
|
|
6678
|
+
mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`)
|
|
6679
|
+
};
|
|
6680
|
+
break;
|
|
6681
|
+
}
|
|
6682
|
+
case "provider-file":
|
|
6683
|
+
clone = {
|
|
6684
|
+
type: "provider-file",
|
|
6685
|
+
provider: nonEmptyString2(source.provider, `part ${index}.source.provider`),
|
|
6686
|
+
reference: nonEmptyString2(source.reference, `part ${index}.source.reference`),
|
|
6687
|
+
...source.mediaType === void 0 ? {} : { mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`) }
|
|
6688
|
+
};
|
|
6689
|
+
break;
|
|
6690
|
+
default:
|
|
6691
|
+
invalid(`part ${index}.source.type is unsupported`);
|
|
6692
|
+
}
|
|
6693
|
+
return {
|
|
6694
|
+
type: "image",
|
|
6695
|
+
source: clone,
|
|
6696
|
+
...part.label === void 0 ? {} : { label: nonEmptyString2(part.label, `part ${index}.label`) }
|
|
6697
|
+
};
|
|
6698
|
+
});
|
|
6699
|
+
}
|
|
6700
|
+
function inputText(input) {
|
|
6701
|
+
return typeof input === "string" ? input : input.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
6702
|
+
}
|
|
6703
|
+
function cloneModelInput(input) {
|
|
6704
|
+
return typeof input === "string" ? input : normalizeModelInput(input);
|
|
6705
|
+
}
|
|
6706
|
+
function describeModelInput(input) {
|
|
6707
|
+
if (typeof input === "string") return void 0;
|
|
6708
|
+
return {
|
|
6709
|
+
parts: input.map((part) => {
|
|
6710
|
+
if (part.type === "text") return { type: "text", characters: part.text.length };
|
|
6711
|
+
const { source } = part;
|
|
6712
|
+
return {
|
|
6713
|
+
type: "image",
|
|
6714
|
+
source: source.type,
|
|
6715
|
+
...source.mediaType ? { mediaType: source.mediaType } : {},
|
|
6716
|
+
...source.type === "bytes" ? { bytes: source.data.byteLength } : {},
|
|
6717
|
+
...source.type === "base64" ? {
|
|
6718
|
+
bytes: decodedBase64Bytes(source.data)
|
|
6719
|
+
} : {},
|
|
6720
|
+
...(source.type === "url" ? source.url : source.type === "provider-file" ? source.reference : void 0) ? {
|
|
6721
|
+
locator: source.type === "url" ? source.url : source.type === "provider-file" ? source.reference : void 0
|
|
6722
|
+
} : {},
|
|
6723
|
+
...part.label ? { label: part.label } : {}
|
|
6724
|
+
};
|
|
6725
|
+
})
|
|
6726
|
+
};
|
|
6727
|
+
}
|
|
6728
|
+
function summarizeModelInput(input) {
|
|
6729
|
+
if (typeof input === "string") return input;
|
|
6730
|
+
return input.map(
|
|
6731
|
+
(part) => part.type === "text" ? part.text : `[image ${part.source.mediaType ?? "media"}]`
|
|
6732
|
+
).join("\n");
|
|
6733
|
+
}
|
|
6734
|
+
function sanitizeModelInputForTrace(input) {
|
|
6735
|
+
return inputText(input);
|
|
6736
|
+
}
|
|
6737
|
+
function normalizePersistedSessionHistory(history) {
|
|
6738
|
+
return history.map((message, index) => {
|
|
6739
|
+
if (typeof message.content === "string") return { ...message };
|
|
6740
|
+
if (message.role !== "user") {
|
|
6741
|
+
throw new InvalidModelInputError(
|
|
6742
|
+
`History message ${index} has non-text content on a non-user role`
|
|
6743
|
+
);
|
|
6744
|
+
}
|
|
6745
|
+
const content = normalizeModelInput(message.content);
|
|
6746
|
+
if (typeof content !== "string" && content.some((part) => part.type === "image" && part.source.type === "bytes")) {
|
|
6747
|
+
throw new InvalidModelInputError(
|
|
6748
|
+
"Uint8Array image input cannot be persisted in session history"
|
|
6749
|
+
);
|
|
6750
|
+
}
|
|
6751
|
+
return { ...message, content };
|
|
6752
|
+
});
|
|
6753
|
+
}
|
|
6754
|
+
|
|
4512
6755
|
// src/partial-json.ts
|
|
4513
6756
|
var MAX_DEPTH = 256;
|
|
4514
6757
|
function parsePartialJson(input) {
|
|
@@ -5080,7 +7323,8 @@ var COST_BEARING_LEAF_TYPES = [
|
|
|
5080
7323
|
"agent_call_end",
|
|
5081
7324
|
"tool_call_end",
|
|
5082
7325
|
"memory_remember",
|
|
5083
|
-
"memory_recall"
|
|
7326
|
+
"memory_recall",
|
|
7327
|
+
"transcription_end"
|
|
5084
7328
|
];
|
|
5085
7329
|
var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
5086
7330
|
function hasPositiveTokens(event) {
|
|
@@ -5088,6 +7332,12 @@ function hasPositiveTokens(event) {
|
|
|
5088
7332
|
if (!t) return false;
|
|
5089
7333
|
return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) + (t.cached ?? 0) + (t.cacheWrite ?? 0) > 0;
|
|
5090
7334
|
}
|
|
7335
|
+
function hasPositiveBillableWork(event) {
|
|
7336
|
+
if (hasPositiveTokens(event)) return true;
|
|
7337
|
+
const seconds = event.data?.usage?.audioSeconds;
|
|
7338
|
+
const totalTokens = event.data?.usage?.totalTokens;
|
|
7339
|
+
return typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0 || typeof totalTokens === "number" && Number.isFinite(totalTokens) && totalTokens > 0;
|
|
7340
|
+
}
|
|
5091
7341
|
function isUsableCost(cost) {
|
|
5092
7342
|
return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
|
|
5093
7343
|
}
|
|
@@ -5100,7 +7350,7 @@ function isCostBearingLeaf(event) {
|
|
|
5100
7350
|
return COST_LEAF_SET.has(event.type);
|
|
5101
7351
|
}
|
|
5102
7352
|
function isUnpricedLeaf(event) {
|
|
5103
|
-
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) &&
|
|
7353
|
+
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveBillableWork(event);
|
|
5104
7354
|
}
|
|
5105
7355
|
function isRootLevel(event) {
|
|
5106
7356
|
const d = event.depth;
|
|
@@ -5115,6 +7365,9 @@ var AXL_EVENT_TYPES = [
|
|
|
5115
7365
|
// Workflow lifecycle
|
|
5116
7366
|
"workflow_start",
|
|
5117
7367
|
"workflow_end",
|
|
7368
|
+
// Transcription operation lifecycle (separate from chat/agent turns)
|
|
7369
|
+
"transcription_start",
|
|
7370
|
+
"transcription_end",
|
|
5118
7371
|
// Ask boundary
|
|
5119
7372
|
"ask_start",
|
|
5120
7373
|
"ask_end",
|
|
@@ -5205,6 +7458,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
5205
7458
|
"schema_diagnostic",
|
|
5206
7459
|
"workflow_start",
|
|
5207
7460
|
"workflow_end",
|
|
7461
|
+
"transcription_start",
|
|
7462
|
+
"transcription_end",
|
|
5208
7463
|
"checkpoint_save",
|
|
5209
7464
|
"checkpoint_replay",
|
|
5210
7465
|
"await_human",
|
|
@@ -6020,6 +8275,21 @@ function redactGate(event) {
|
|
|
6020
8275
|
}
|
|
6021
8276
|
};
|
|
6022
8277
|
}
|
|
8278
|
+
function redactInputDescriptor(input) {
|
|
8279
|
+
return {
|
|
8280
|
+
...input,
|
|
8281
|
+
parts: input.parts.map((part) => {
|
|
8282
|
+
if (!part || typeof part !== "object" || !("type" in part) || part.type === "text")
|
|
8283
|
+
return part;
|
|
8284
|
+
const value = part;
|
|
8285
|
+
return {
|
|
8286
|
+
...part,
|
|
8287
|
+
...value.locator !== void 0 ? { locator: REDACTED } : {},
|
|
8288
|
+
...value.label !== void 0 ? { label: REDACTED } : {}
|
|
8289
|
+
};
|
|
8290
|
+
})
|
|
8291
|
+
};
|
|
8292
|
+
}
|
|
6023
8293
|
function redactMemory(event) {
|
|
6024
8294
|
const d = event.data;
|
|
6025
8295
|
if (!d || typeof d !== "object") return event;
|
|
@@ -6043,7 +8313,11 @@ var LEGACY_REDACTION_RULES = {
|
|
|
6043
8313
|
}
|
|
6044
8314
|
};
|
|
6045
8315
|
},
|
|
6046
|
-
ask_start: (e) => ({
|
|
8316
|
+
ask_start: (e) => ({
|
|
8317
|
+
...e,
|
|
8318
|
+
prompt: REDACTED,
|
|
8319
|
+
...e.input ? { input: redactInputDescriptor(e.input) } : {}
|
|
8320
|
+
}),
|
|
6047
8321
|
ask_end: (e) => ({
|
|
6048
8322
|
...e,
|
|
6049
8323
|
outcome: e.outcome.ok ? { ok: true, result: REDACTED } : { ok: false, error: REDACTED }
|
|
@@ -6051,6 +8325,14 @@ var LEGACY_REDACTION_RULES = {
|
|
|
6051
8325
|
agent_call_start: (e) => {
|
|
6052
8326
|
const d = e.data;
|
|
6053
8327
|
const out = { ...d, prompt: REDACTED };
|
|
8328
|
+
if (d.input) {
|
|
8329
|
+
out.input = redactInputDescriptor(d.input);
|
|
8330
|
+
}
|
|
8331
|
+
if (d.messageInputs)
|
|
8332
|
+
out.messageInputs = d.messageInputs.map((entry) => ({
|
|
8333
|
+
...entry,
|
|
8334
|
+
input: redactInputDescriptor(entry.input)
|
|
8335
|
+
}));
|
|
6054
8336
|
if (d.system !== void 0) out.system = REDACTED;
|
|
6055
8337
|
if (Array.isArray(d.messages)) {
|
|
6056
8338
|
out.messages = [{ role: "system", content: `[${d.messages.length} messages redacted]` }];
|
|
@@ -6206,6 +8488,18 @@ function reuseLegacyRule(type) {
|
|
|
6206
8488
|
var COMMON_V2_REDACTION_RULES = {
|
|
6207
8489
|
workflow_start: reuseLegacyRule("workflow_start"),
|
|
6208
8490
|
workflow_end: reuseLegacyRule("workflow_end"),
|
|
8491
|
+
transcription_start: (e) => e,
|
|
8492
|
+
transcription_end: (e) => {
|
|
8493
|
+
const d = e.data;
|
|
8494
|
+
return {
|
|
8495
|
+
...e,
|
|
8496
|
+
data: {
|
|
8497
|
+
...d,
|
|
8498
|
+
...d.text !== void 0 ? { text: REDACTED } : {},
|
|
8499
|
+
...d.error !== void 0 ? { error: REDACTED } : {}
|
|
8500
|
+
}
|
|
8501
|
+
};
|
|
8502
|
+
},
|
|
6209
8503
|
ask_start: reuseLegacyRule("ask_start"),
|
|
6210
8504
|
ask_end: reuseLegacyRule("ask_end"),
|
|
6211
8505
|
agent_call_start: reuseLegacyRule("agent_call_start"),
|
|
@@ -6400,7 +8694,7 @@ function resolveConfig(config) {
|
|
|
6400
8694
|
|
|
6401
8695
|
// src/tool-model-output.ts
|
|
6402
8696
|
var import_node_util = require("util");
|
|
6403
|
-
function
|
|
8697
|
+
function invalid2(path, reason) {
|
|
6404
8698
|
return new Error(`Invalid tool model output at ${path}: ${reason}`);
|
|
6405
8699
|
}
|
|
6406
8700
|
function propertyPath(parent, key) {
|
|
@@ -6418,14 +8712,14 @@ function propertyPath(parent, key) {
|
|
|
6418
8712
|
function normalize(value, path, stack) {
|
|
6419
8713
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
6420
8714
|
if (typeof value === "number") {
|
|
6421
|
-
if (!Number.isFinite(value)) throw
|
|
8715
|
+
if (!Number.isFinite(value)) throw invalid2(path, "number must be finite");
|
|
6422
8716
|
return value;
|
|
6423
8717
|
}
|
|
6424
|
-
if (value === void 0) throw
|
|
8718
|
+
if (value === void 0) throw invalid2(path, "undefined is only allowed on object properties");
|
|
6425
8719
|
if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
|
|
6426
|
-
throw
|
|
8720
|
+
throw invalid2(path, `unsupported ${typeof value}`);
|
|
6427
8721
|
}
|
|
6428
|
-
if (typeof value !== "object") throw
|
|
8722
|
+
if (typeof value !== "object") throw invalid2(path, `unsupported ${typeof value}`);
|
|
6429
8723
|
let prototype;
|
|
6430
8724
|
let descriptors;
|
|
6431
8725
|
try {
|
|
@@ -6433,38 +8727,38 @@ function normalize(value, path, stack) {
|
|
|
6433
8727
|
descriptors = Object.getOwnPropertyDescriptors(value);
|
|
6434
8728
|
Object.setPrototypeOf(descriptors, null);
|
|
6435
8729
|
} catch (cause) {
|
|
6436
|
-
throw
|
|
8730
|
+
throw invalid2(
|
|
6437
8731
|
path,
|
|
6438
8732
|
`object inspection failed (${cause instanceof Error ? cause.name : "error"})`
|
|
6439
8733
|
);
|
|
6440
8734
|
}
|
|
6441
|
-
if (stack.has(value)) throw
|
|
8735
|
+
if (stack.has(value)) throw invalid2(path, "cyclic reference");
|
|
6442
8736
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
6443
8737
|
const descriptor = descriptors[key];
|
|
6444
8738
|
if (descriptor && !("value" in descriptor)) {
|
|
6445
|
-
throw
|
|
8739
|
+
throw invalid2(
|
|
6446
8740
|
typeof key === "string" ? propertyPath(path, key) : `${path}[symbol]`,
|
|
6447
8741
|
"accessor properties are not supported"
|
|
6448
8742
|
);
|
|
6449
8743
|
}
|
|
6450
8744
|
if (typeof key === "symbol") {
|
|
6451
|
-
throw
|
|
8745
|
+
throw invalid2(`${path}[symbol]`, "symbol keys are not supported");
|
|
6452
8746
|
}
|
|
6453
8747
|
}
|
|
6454
8748
|
if (typeof descriptors.toJSON?.value === "function") {
|
|
6455
|
-
throw
|
|
8749
|
+
throw invalid2(propertyPath(path, "toJSON"), "custom toJSON is not supported");
|
|
6456
8750
|
}
|
|
6457
8751
|
const array = Array.isArray(value);
|
|
6458
8752
|
if (array && prototype !== Array.prototype && prototype !== null) {
|
|
6459
|
-
throw
|
|
8753
|
+
throw invalid2(path, "array subclasses are not supported");
|
|
6460
8754
|
}
|
|
6461
8755
|
const inheritedToJSON = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "toJSON") : void 0;
|
|
6462
8756
|
if (typeof inheritedToJSON?.value === "function") {
|
|
6463
|
-
throw
|
|
8757
|
+
throw invalid2(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
|
|
6464
8758
|
}
|
|
6465
8759
|
const inheritedThen = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "then") : void 0;
|
|
6466
8760
|
if (typeof descriptors.then?.value === "function" || inheritedThen !== void 0 && (!("value" in inheritedThen) || typeof inheritedThen.value === "function")) {
|
|
6467
|
-
throw
|
|
8761
|
+
throw invalid2(path, "thenable output is not supported");
|
|
6468
8762
|
}
|
|
6469
8763
|
stack.add(value);
|
|
6470
8764
|
try {
|
|
@@ -6472,15 +8766,15 @@ function normalize(value, path, stack) {
|
|
|
6472
8766
|
const normalized2 = [];
|
|
6473
8767
|
const length = descriptors.length?.value;
|
|
6474
8768
|
if (!Number.isSafeInteger(length) || length < 0) {
|
|
6475
|
-
throw
|
|
8769
|
+
throw invalid2(propertyPath(path, "length"), "array length is invalid");
|
|
6476
8770
|
}
|
|
6477
8771
|
for (let index = 0; index < length; index++) {
|
|
6478
8772
|
const descriptor = descriptors[String(index)];
|
|
6479
8773
|
if (!descriptor || !("value" in descriptor)) {
|
|
6480
|
-
throw
|
|
8774
|
+
throw invalid2(`${path}[${index}]`, "arrays must be dense data properties");
|
|
6481
8775
|
}
|
|
6482
8776
|
if (descriptor.value === void 0) {
|
|
6483
|
-
throw
|
|
8777
|
+
throw invalid2(`${path}[${index}]`, "array values cannot be undefined");
|
|
6484
8778
|
}
|
|
6485
8779
|
normalized2.push(normalize(descriptor.value, `${path}[${index}]`, stack));
|
|
6486
8780
|
}
|
|
@@ -6489,21 +8783,21 @@ function normalize(value, path, stack) {
|
|
|
6489
8783
|
if (!descriptor?.enumerable) continue;
|
|
6490
8784
|
const index = Number(key);
|
|
6491
8785
|
if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
|
|
6492
|
-
throw
|
|
8786
|
+
throw invalid2(propertyPath(path, key), "non-index array properties are not supported");
|
|
6493
8787
|
}
|
|
6494
8788
|
}
|
|
6495
8789
|
Object.setPrototypeOf(normalized2, null);
|
|
6496
8790
|
return normalized2;
|
|
6497
8791
|
}
|
|
6498
8792
|
if (prototype !== Object.prototype && prototype !== null) {
|
|
6499
|
-
throw
|
|
8793
|
+
throw invalid2(path, "only plain objects and null-prototype records are supported");
|
|
6500
8794
|
}
|
|
6501
8795
|
const normalized = /* @__PURE__ */ Object.create(null);
|
|
6502
8796
|
for (const key of Object.keys(descriptors)) {
|
|
6503
8797
|
const descriptor = descriptors[key];
|
|
6504
8798
|
if (!descriptor?.enumerable) continue;
|
|
6505
8799
|
if (!("value" in descriptor)) {
|
|
6506
|
-
throw
|
|
8800
|
+
throw invalid2(propertyPath(path, key), "accessor properties are not supported");
|
|
6507
8801
|
}
|
|
6508
8802
|
if (descriptor.value === void 0) continue;
|
|
6509
8803
|
normalized[key] = normalize(descriptor.value, propertyPath(path, key), stack);
|
|
@@ -6539,7 +8833,15 @@ function cloneToolArguments(value) {
|
|
|
6539
8833
|
}
|
|
6540
8834
|
}
|
|
6541
8835
|
function parseToolInvocation(options) {
|
|
6542
|
-
const {
|
|
8836
|
+
const {
|
|
8837
|
+
toolCall,
|
|
8838
|
+
configuredTool,
|
|
8839
|
+
override,
|
|
8840
|
+
mcpCall,
|
|
8841
|
+
mcpTraceName,
|
|
8842
|
+
availableTools,
|
|
8843
|
+
providerVisibleSchema
|
|
8844
|
+
} = options;
|
|
6543
8845
|
const requestedTool = toolCall.function.name;
|
|
6544
8846
|
if (!override && !configuredTool && !mcpCall) {
|
|
6545
8847
|
return {
|
|
@@ -6599,7 +8901,7 @@ function parseToolInvocation(options) {
|
|
|
6599
8901
|
args: parsedArguments,
|
|
6600
8902
|
issues: toolArgumentIssues(error)
|
|
6601
8903
|
},
|
|
6602
|
-
modelMessage: INVALID_ARGUMENTS_MODEL_MESSAGE
|
|
8904
|
+
modelMessage: toolArgumentModelMessage(error, providerVisibleSchema) ?? INVALID_ARGUMENTS_MODEL_MESSAGE
|
|
6603
8905
|
};
|
|
6604
8906
|
}
|
|
6605
8907
|
}
|
|
@@ -7003,44 +9305,44 @@ function resolveBranchDrainTimeoutMs(value) {
|
|
|
7003
9305
|
return resolved;
|
|
7004
9306
|
}
|
|
7005
9307
|
function parseHumanDecision(value) {
|
|
7006
|
-
const
|
|
9308
|
+
const invalid3 = () => {
|
|
7007
9309
|
throw new AxlError(
|
|
7008
9310
|
"INVALID_HUMAN_DECISION",
|
|
7009
9311
|
"Human decision must be a plain object with a boolean approved field and only branch-appropriate string data"
|
|
7010
9312
|
);
|
|
7011
9313
|
};
|
|
7012
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
9314
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) invalid3();
|
|
7013
9315
|
let prototype;
|
|
7014
9316
|
try {
|
|
7015
9317
|
prototype = Object.getPrototypeOf(value);
|
|
7016
9318
|
} catch {
|
|
7017
|
-
return
|
|
9319
|
+
return invalid3();
|
|
7018
9320
|
}
|
|
7019
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
9321
|
+
if (prototype !== Object.prototype && prototype !== null) invalid3();
|
|
7020
9322
|
let ownKeys;
|
|
7021
9323
|
try {
|
|
7022
9324
|
ownKeys = Reflect.ownKeys(value);
|
|
7023
9325
|
} catch {
|
|
7024
|
-
return
|
|
9326
|
+
return invalid3();
|
|
7025
9327
|
}
|
|
7026
9328
|
if (ownKeys.some(
|
|
7027
9329
|
(key) => typeof key !== "string" || key !== "approved" && key !== "data" && key !== "reason"
|
|
7028
9330
|
)) {
|
|
7029
|
-
|
|
9331
|
+
invalid3();
|
|
7030
9332
|
}
|
|
7031
9333
|
const readDataProperty = (key) => {
|
|
7032
9334
|
let descriptor;
|
|
7033
9335
|
try {
|
|
7034
9336
|
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
7035
9337
|
} catch {
|
|
7036
|
-
return
|
|
9338
|
+
return invalid3();
|
|
7037
9339
|
}
|
|
7038
9340
|
if (!descriptor) {
|
|
7039
|
-
if (ownKeys.includes(key))
|
|
9341
|
+
if (ownKeys.includes(key)) invalid3();
|
|
7040
9342
|
return { present: false, value: void 0 };
|
|
7041
9343
|
}
|
|
7042
|
-
if (!ownKeys.includes(key)) return
|
|
7043
|
-
if (!("value" in descriptor)) return
|
|
9344
|
+
if (!ownKeys.includes(key)) return invalid3();
|
|
9345
|
+
if (!("value" in descriptor)) return invalid3();
|
|
7044
9346
|
return { present: true, value: descriptor.value };
|
|
7045
9347
|
};
|
|
7046
9348
|
const approved = readDataProperty("approved");
|
|
@@ -7059,7 +9361,7 @@ function parseHumanDecision(value) {
|
|
|
7059
9361
|
};
|
|
7060
9362
|
}
|
|
7061
9363
|
}
|
|
7062
|
-
return
|
|
9364
|
+
return invalid3();
|
|
7063
9365
|
}
|
|
7064
9366
|
var askStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
7065
9367
|
var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7167,8 +9469,12 @@ function appendRetryMessages(messages, content, feedbackMessage, providerMetadat
|
|
|
7167
9469
|
}
|
|
7168
9470
|
function estimateMessagesTokens(messages) {
|
|
7169
9471
|
let total = 0;
|
|
9472
|
+
let unmeasured = false;
|
|
7170
9473
|
for (const msg of messages) {
|
|
7171
|
-
total += estimateTokens(msg.content);
|
|
9474
|
+
total += estimateTokens(inputText(msg.content));
|
|
9475
|
+
if (typeof msg.content !== "string") {
|
|
9476
|
+
unmeasured ||= msg.content.some((part) => part.type === "image");
|
|
9477
|
+
}
|
|
7172
9478
|
if (msg.tool_calls) {
|
|
7173
9479
|
for (const tc of msg.tool_calls) {
|
|
7174
9480
|
total += estimateTokens(tc.function.name + tc.function.arguments);
|
|
@@ -7176,7 +9482,73 @@ function estimateMessagesTokens(messages) {
|
|
|
7176
9482
|
}
|
|
7177
9483
|
total += 4;
|
|
7178
9484
|
}
|
|
7179
|
-
return total;
|
|
9485
|
+
return { tokens: total, unmeasured };
|
|
9486
|
+
}
|
|
9487
|
+
function cloneMessagesForProvider(messages) {
|
|
9488
|
+
return messages.map((message) => ({
|
|
9489
|
+
...message,
|
|
9490
|
+
content: cloneModelInput(message.content),
|
|
9491
|
+
...message.tool_calls ? {
|
|
9492
|
+
tool_calls: message.tool_calls.map((call) => ({
|
|
9493
|
+
...call,
|
|
9494
|
+
function: { ...call.function }
|
|
9495
|
+
}))
|
|
9496
|
+
} : {}
|
|
9497
|
+
}));
|
|
9498
|
+
}
|
|
9499
|
+
function assertRichMessagesAreUserOnly(messages) {
|
|
9500
|
+
for (const message of messages) {
|
|
9501
|
+
if (typeof message.content !== "string" && message.role !== "user") {
|
|
9502
|
+
throw new InvalidModelInputError(
|
|
9503
|
+
"Non-text model input parts are only supported on user messages"
|
|
9504
|
+
);
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
}
|
|
9508
|
+
function normalizeSessionHistory(messages) {
|
|
9509
|
+
return messages.map((message) => {
|
|
9510
|
+
if (typeof message.content === "string") return { ...message };
|
|
9511
|
+
if (message.role !== "user") {
|
|
9512
|
+
throw new InvalidModelInputError(
|
|
9513
|
+
"Non-text model input parts are only supported on user messages"
|
|
9514
|
+
);
|
|
9515
|
+
}
|
|
9516
|
+
return { ...message, content: normalizeModelInput(message.content) };
|
|
9517
|
+
});
|
|
9518
|
+
}
|
|
9519
|
+
function hasRichMessage(messages) {
|
|
9520
|
+
return messages.some((message) => typeof message.content !== "string");
|
|
9521
|
+
}
|
|
9522
|
+
function firstImageSource(input, history) {
|
|
9523
|
+
const find = (value) => typeof value === "string" ? void 0 : value.find((part) => part.type === "image")?.source.type;
|
|
9524
|
+
return find(input) ?? history.map((message) => find(message.content)).find(Boolean);
|
|
9525
|
+
}
|
|
9526
|
+
function appendHandoffInstruction(input, originalText, instruction) {
|
|
9527
|
+
if (typeof input === "string" || instruction.length === 0 || instruction === originalText)
|
|
9528
|
+
return input;
|
|
9529
|
+
return [...input, { type: "text", text: instruction }];
|
|
9530
|
+
}
|
|
9531
|
+
var RICH_MODEL_INPUT_ERROR_MESSAGE = "Provider request failed while processing rich model input";
|
|
9532
|
+
var RICH_MODEL_INPUT_ERROR_NAME = "RichModelInputError";
|
|
9533
|
+
var MAX_RICH_FAILURE_ASSOCIATIONS = 32;
|
|
9534
|
+
var MAX_RICH_FAILURE_CAUSE_DEPTH = 4;
|
|
9535
|
+
function legacyErrorMessage(error) {
|
|
9536
|
+
return error instanceof Error ? error.message : String(error);
|
|
9537
|
+
}
|
|
9538
|
+
function providerErrorEventMetadata(error, richRequest) {
|
|
9539
|
+
if (!(error instanceof ProviderError)) {
|
|
9540
|
+
return error instanceof AxlError && richRequest ? { code: error.code } : {};
|
|
9541
|
+
}
|
|
9542
|
+
return {
|
|
9543
|
+
status: error.status,
|
|
9544
|
+
retryable: error.retryable,
|
|
9545
|
+
...richRequest ? {
|
|
9546
|
+
code: error.code,
|
|
9547
|
+
provider: error.provider,
|
|
9548
|
+
...error.requestId ? { requestId: error.requestId } : {},
|
|
9549
|
+
...error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
9550
|
+
} : {}
|
|
9551
|
+
};
|
|
7180
9552
|
}
|
|
7181
9553
|
var WorkflowContext = class _WorkflowContext {
|
|
7182
9554
|
input;
|
|
@@ -7192,6 +9564,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7192
9564
|
}
|
|
7193
9565
|
config;
|
|
7194
9566
|
providerRegistry;
|
|
9567
|
+
transcriptionProviderRegistry;
|
|
7195
9568
|
sessionHistory;
|
|
7196
9569
|
onTrace;
|
|
7197
9570
|
pendingDecisions;
|
|
@@ -7217,6 +9590,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7217
9590
|
*/
|
|
7218
9591
|
autoCheckpointCounters;
|
|
7219
9592
|
branchDrainState;
|
|
9593
|
+
/** Bounded exact-identity failure association for this execution tree.
|
|
9594
|
+
* Unlike a process-global marker, this cannot affect another context that
|
|
9595
|
+
* happens to reuse the same Error object. */
|
|
9596
|
+
richFailureState;
|
|
7220
9597
|
/** Root-shared idempotency guards for workflow lifecycle emission.
|
|
7221
9598
|
* Both `runtime.execute()` and `runtime.stream()` have paths where a
|
|
7222
9599
|
* post-emit side-effect (checkpoint deletion, state-store persistence)
|
|
@@ -7300,7 +9677,8 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7300
9677
|
get events() {
|
|
7301
9678
|
if (!this._busRef.current) {
|
|
7302
9679
|
this._busRef.current = new AxlEventBus(this._eventStreamOptions);
|
|
7303
|
-
|
|
9680
|
+
const signal = this.currentSignal;
|
|
9681
|
+
if (signal?.aborted) {
|
|
7304
9682
|
this._busRef.current._finish();
|
|
7305
9683
|
}
|
|
7306
9684
|
}
|
|
@@ -7338,6 +9716,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7338
9716
|
this.metadata = init.metadata ?? {};
|
|
7339
9717
|
this.config = init.config;
|
|
7340
9718
|
this.providerRegistry = init.providerRegistry;
|
|
9719
|
+
this.transcriptionProviderRegistry = init.transcriptionProviderRegistry;
|
|
7341
9720
|
this.sessionHistory = init.sessionHistory ?? [];
|
|
7342
9721
|
this.onTrace = init.onTrace;
|
|
7343
9722
|
this.pendingDecisions = init.pendingDecisions;
|
|
@@ -7371,6 +9750,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7371
9750
|
byAgent: /* @__PURE__ */ new Map(),
|
|
7372
9751
|
root: 0
|
|
7373
9752
|
};
|
|
9753
|
+
this.richFailureState = init.richFailureState ?? {
|
|
9754
|
+
associations: [],
|
|
9755
|
+
overflowed: false,
|
|
9756
|
+
indeterminate: false
|
|
9757
|
+
};
|
|
7374
9758
|
const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(init.branchDrainTimeoutMs);
|
|
7375
9759
|
this.branchDrainState = init.branchDrainState ?? {
|
|
7376
9760
|
pending: /* @__PURE__ */ new Set(),
|
|
@@ -7403,6 +9787,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7403
9787
|
executionId: this.executionId,
|
|
7404
9788
|
config: this.config,
|
|
7405
9789
|
providerRegistry: this.providerRegistry,
|
|
9790
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
7406
9791
|
metadata: { ...this.metadata },
|
|
7407
9792
|
// Shared infrastructure
|
|
7408
9793
|
budgetContext: this.budgetContext,
|
|
@@ -7425,6 +9810,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7425
9810
|
autoCheckpointCounters: this.autoCheckpointCounters,
|
|
7426
9811
|
branchDrainState: this.branchDrainState,
|
|
7427
9812
|
workflowLifecycleState: this.workflowLifecycleState,
|
|
9813
|
+
richFailureState: this.richFailureState,
|
|
7428
9814
|
// Share the parent's bus slot (mutable ref). Late allocation in
|
|
7429
9815
|
// either parent or child propagates because both read the same
|
|
7430
9816
|
// slot. Nested-ask events (agent-as-tool pattern) bubble up to
|
|
@@ -7436,6 +9822,52 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7436
9822
|
// Isolated: sessionHistory (empty)
|
|
7437
9823
|
});
|
|
7438
9824
|
}
|
|
9825
|
+
/** Associate an exact thrown value with rich input in this execution only. */
|
|
9826
|
+
recordRichFailure(error) {
|
|
9827
|
+
const state = this.richFailureState;
|
|
9828
|
+
if (state.associations.some((known) => Object.is(known, error))) return;
|
|
9829
|
+
if (state.associations.length >= MAX_RICH_FAILURE_ASSOCIATIONS) {
|
|
9830
|
+
state.associations.shift();
|
|
9831
|
+
state.overflowed = true;
|
|
9832
|
+
state.indeterminate = true;
|
|
9833
|
+
}
|
|
9834
|
+
state.associations.push(error);
|
|
9835
|
+
}
|
|
9836
|
+
/** @internal Safe observer projection for a failure associated with rich input. */
|
|
9837
|
+
_observerErrorProjection(error) {
|
|
9838
|
+
const state = this.richFailureState;
|
|
9839
|
+
const safe = () => ({
|
|
9840
|
+
message: RICH_MODEL_INPUT_ERROR_MESSAGE,
|
|
9841
|
+
name: RICH_MODEL_INPUT_ERROR_NAME
|
|
9842
|
+
});
|
|
9843
|
+
const indeterminate = () => {
|
|
9844
|
+
if (state.associations.length === 0) return void 0;
|
|
9845
|
+
state.indeterminate = true;
|
|
9846
|
+
return safe();
|
|
9847
|
+
};
|
|
9848
|
+
let current = error;
|
|
9849
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
9850
|
+
for (let depth = 0; depth < MAX_RICH_FAILURE_CAUSE_DEPTH; depth++) {
|
|
9851
|
+
if (state.associations.some((known) => Object.is(known, current))) {
|
|
9852
|
+
return safe();
|
|
9853
|
+
}
|
|
9854
|
+
if ((typeof current !== "object" || current === null) && typeof current !== "function") {
|
|
9855
|
+
return state.overflowed || state.indeterminate ? safe() : void 0;
|
|
9856
|
+
}
|
|
9857
|
+
if (seen.has(current)) return indeterminate();
|
|
9858
|
+
seen.add(current);
|
|
9859
|
+
let descriptor;
|
|
9860
|
+
try {
|
|
9861
|
+
descriptor = Object.getOwnPropertyDescriptor(current, "cause");
|
|
9862
|
+
} catch {
|
|
9863
|
+
return indeterminate();
|
|
9864
|
+
}
|
|
9865
|
+
if (!descriptor) return state.overflowed || state.indeterminate ? safe() : void 0;
|
|
9866
|
+
if (!("value" in descriptor)) return indeterminate();
|
|
9867
|
+
current = descriptor.value;
|
|
9868
|
+
}
|
|
9869
|
+
return indeterminate();
|
|
9870
|
+
}
|
|
7439
9871
|
/**
|
|
7440
9872
|
* Resolve the current abort signal.
|
|
7441
9873
|
* Branch-scoped signals (from race/spawn/map/budget) in AsyncLocalStorage
|
|
@@ -7502,7 +9934,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7502
9934
|
/** Resolve, execute, and observe one non-handoff tool request. Rejections
|
|
7503
9935
|
* never start. Every accepted request emits one terminal event before a
|
|
7504
9936
|
* continuation message or abort escapes this coordinator. */
|
|
7505
|
-
async observeToolInvocation(agent2, toolCall, currentMessages,
|
|
9937
|
+
async observeToolInvocation(agent2, toolCall, currentMessages, toolDefs) {
|
|
7506
9938
|
const signal = this.currentSignal;
|
|
7507
9939
|
signal?.throwIfAborted();
|
|
7508
9940
|
const requestedTool = toolCall.function.name;
|
|
@@ -7512,6 +9944,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7512
9944
|
const override = this.toolOverrides?.get(requestedTool);
|
|
7513
9945
|
const mcpManager = this.mcpManager;
|
|
7514
9946
|
const mcpMatch = !configuredTool ? mcpManager?.getToolsForAgent(agent2._config.mcp, agent2._config.mcpTools).find(({ tool: tool2 }) => tool2.name === requestedTool) : void 0;
|
|
9947
|
+
const providerVisibleSchema = toolDefs.find((tool2) => tool2.function.name === requestedTool)?.function.parameters;
|
|
7515
9948
|
const parsed = parseToolInvocation({
|
|
7516
9949
|
toolCall,
|
|
7517
9950
|
configuredTool,
|
|
@@ -7520,7 +9953,8 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7520
9953
|
mcpCall: (args) => mcpManager.callTool(requestedTool, args, mcpMatch.server),
|
|
7521
9954
|
mcpTraceName: `${mcpMatch.server}:${requestedTool}`
|
|
7522
9955
|
} : {},
|
|
7523
|
-
availableTools
|
|
9956
|
+
availableTools: toolDefs.map((tool2) => tool2.function.name),
|
|
9957
|
+
...configuredTool ? { providerVisibleSchema } : {}
|
|
7524
9958
|
});
|
|
7525
9959
|
signal?.throwIfAborted();
|
|
7526
9960
|
if ("kind" in parsed) {
|
|
@@ -7635,6 +10069,17 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7635
10069
|
}
|
|
7636
10070
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
7637
10071
|
async ask(agent2, prompt, options) {
|
|
10072
|
+
const normalizedInput = normalizeModelInput(prompt);
|
|
10073
|
+
const normalizedHistory = normalizeSessionHistory(this.sessionHistory);
|
|
10074
|
+
const delegateInputHolder = agent2;
|
|
10075
|
+
if (delegateInputHolder._delegateOriginalInput !== void 0) {
|
|
10076
|
+
delegateInputHolder._delegateOriginalInput = normalizeModelInput(
|
|
10077
|
+
delegateInputHolder._delegateOriginalInput
|
|
10078
|
+
);
|
|
10079
|
+
}
|
|
10080
|
+
const richAsk = typeof normalizedInput !== "string" || hasRichMessage(normalizedHistory) || delegateInputHolder._delegateOriginalInput !== void 0 && typeof delegateInputHolder._delegateOriginalInput !== "string";
|
|
10081
|
+
const promptText = inputText(normalizedInput);
|
|
10082
|
+
const inputDescriptor = describeModelInput(normalizedInput);
|
|
7638
10083
|
const agentName = agent2._name;
|
|
7639
10084
|
return this._checkpoint(
|
|
7640
10085
|
this._autoCheckpointName("ask", agentName),
|
|
@@ -7654,7 +10099,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7654
10099
|
};
|
|
7655
10100
|
return askStorage.run(frame, async () => {
|
|
7656
10101
|
const askStart = Date.now();
|
|
7657
|
-
this.emitEvent({
|
|
10102
|
+
this.emitEvent({
|
|
10103
|
+
type: "ask_start",
|
|
10104
|
+
prompt: promptText,
|
|
10105
|
+
...inputDescriptor ? { input: inputDescriptor } : {}
|
|
10106
|
+
});
|
|
7658
10107
|
const costBefore = this.budgetContext?.totalCost ?? 0;
|
|
7659
10108
|
const unpricedCountBefore = this.budgetContext?.unpricedCount ?? 0;
|
|
7660
10109
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
@@ -7662,10 +10111,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7662
10111
|
const doCall = async () => {
|
|
7663
10112
|
const result2 = await this.executeAgentCall(
|
|
7664
10113
|
agent2,
|
|
7665
|
-
|
|
10114
|
+
normalizedInput,
|
|
7666
10115
|
options,
|
|
7667
10116
|
void 0,
|
|
7668
|
-
usageCapture
|
|
10117
|
+
usageCapture,
|
|
10118
|
+
normalizedHistory
|
|
7669
10119
|
);
|
|
7670
10120
|
return result2;
|
|
7671
10121
|
};
|
|
@@ -7682,6 +10132,9 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7682
10132
|
},
|
|
7683
10133
|
async (span) => {
|
|
7684
10134
|
const r = await doCall();
|
|
10135
|
+
if (usageCapture.modelUri) {
|
|
10136
|
+
span.setAttribute("axl.agent.model", usageCapture.modelUri);
|
|
10137
|
+
}
|
|
7685
10138
|
const costAfter2 = this.budgetContext?.totalCost ?? 0;
|
|
7686
10139
|
span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
|
|
7687
10140
|
span.setAttribute("axl.agent.duration", Date.now() - askStart);
|
|
@@ -7715,9 +10168,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7715
10168
|
try {
|
|
7716
10169
|
this.onAgentCallComplete({
|
|
7717
10170
|
agent: agent2._name,
|
|
7718
|
-
prompt,
|
|
10171
|
+
prompt: promptText,
|
|
10172
|
+
...inputDescriptor ? { input: inputDescriptor } : {},
|
|
7719
10173
|
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
7720
|
-
model: agent2.resolveModel(resolveCtx),
|
|
10174
|
+
model: usageCapture.modelUri ?? agent2.resolveModel(resolveCtx),
|
|
7721
10175
|
cost: costAfter - costBefore,
|
|
7722
10176
|
unpriced: frame.askUnpriced || unpricedCountAfter > unpricedCountBefore,
|
|
7723
10177
|
duration: Date.now() - askStart,
|
|
@@ -7741,9 +10195,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7741
10195
|
} catch (err) {
|
|
7742
10196
|
askFailed = true;
|
|
7743
10197
|
askFailure = err;
|
|
10198
|
+
if (richAsk) this.recordRichFailure(err);
|
|
7744
10199
|
outcome = {
|
|
7745
10200
|
ok: false,
|
|
7746
|
-
error: err
|
|
10201
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
|
|
7747
10202
|
};
|
|
7748
10203
|
}
|
|
7749
10204
|
let terminalFailure;
|
|
@@ -7771,7 +10226,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7771
10226
|
{ agent: agentName }
|
|
7772
10227
|
);
|
|
7773
10228
|
}
|
|
7774
|
-
async executeAgentCall(agent2,
|
|
10229
|
+
async executeAgentCall(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
|
|
7775
10230
|
if (this.budgetContext?.exceeded) {
|
|
7776
10231
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
7777
10232
|
if (policy === "warn") {
|
|
@@ -7788,7 +10243,82 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7788
10243
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
7789
10244
|
const modelUri = agent2.resolveModel(resolveCtx);
|
|
7790
10245
|
const systemPrompt = agent2.resolveSystem(resolveCtx);
|
|
7791
|
-
const { provider, model } = this.providerRegistry.resolve(modelUri, this.config);
|
|
10246
|
+
const { provider, model: resolvedModel } = this.providerRegistry.resolve(modelUri, this.config);
|
|
10247
|
+
let model = resolvedModel;
|
|
10248
|
+
const prompt = inputText(input);
|
|
10249
|
+
const providerOptions = options?.providerOptions ?? agent2._config.providerOptions;
|
|
10250
|
+
const richRequest = typeof input !== "string" || hasRichMessage(sessionHistory);
|
|
10251
|
+
if (richRequest) {
|
|
10252
|
+
if (providerOptions && ("messages" in providerOptions || "input" in providerOptions)) {
|
|
10253
|
+
throw new UnsupportedModelInputError({
|
|
10254
|
+
provider: provider.name ?? modelUri.split(":", 1)[0],
|
|
10255
|
+
model,
|
|
10256
|
+
modality: "image",
|
|
10257
|
+
feature: "raw input-container providerOptions"
|
|
10258
|
+
});
|
|
10259
|
+
}
|
|
10260
|
+
const source = firstImageSource(input, sessionHistory);
|
|
10261
|
+
if (!provider.validateInput) {
|
|
10262
|
+
throw new UnsupportedModelInputError({
|
|
10263
|
+
provider: provider.name ?? modelUri.split(":", 1)[0],
|
|
10264
|
+
model,
|
|
10265
|
+
modality: "image",
|
|
10266
|
+
...source ? { source } : {}
|
|
10267
|
+
});
|
|
10268
|
+
}
|
|
10269
|
+
const validation = provider.validateInput({
|
|
10270
|
+
model,
|
|
10271
|
+
input: cloneModelInput(input),
|
|
10272
|
+
history: cloneMessagesForProvider(sessionHistory),
|
|
10273
|
+
stream: this._streamingEnabled,
|
|
10274
|
+
hasTools: Boolean(
|
|
10275
|
+
agent2._config.tools?.length || agent2._config.mcp?.length || agent2._config.mcpTools?.length || agent2._config.handoffs
|
|
10276
|
+
),
|
|
10277
|
+
responseMode: options?.schema ? "structured" : "text",
|
|
10278
|
+
providerOptions
|
|
10279
|
+
});
|
|
10280
|
+
if (!validation || typeof validation.effectiveModel !== "string" || validation.effectiveModel.length === 0) {
|
|
10281
|
+
throw new UnsupportedModelInputError({
|
|
10282
|
+
provider: provider.name ?? modelUri.split(":", 1)[0],
|
|
10283
|
+
model,
|
|
10284
|
+
modality: "image",
|
|
10285
|
+
...source ? { source } : {}
|
|
10286
|
+
});
|
|
10287
|
+
}
|
|
10288
|
+
model = validation.effectiveModel;
|
|
10289
|
+
}
|
|
10290
|
+
const effectiveModelUri = !richRequest ? modelUri : `${modelUri.slice(0, modelUri.indexOf(":") + 1)}${model}`;
|
|
10291
|
+
if (usageCapture) usageCapture.modelUri ??= effectiveModelUri;
|
|
10292
|
+
if (richRequest) {
|
|
10293
|
+
const descriptors = [
|
|
10294
|
+
describeModelInput(input),
|
|
10295
|
+
...sessionHistory.map((message) => describeModelInput(message.content))
|
|
10296
|
+
].filter(
|
|
10297
|
+
(descriptor) => descriptor !== void 0
|
|
10298
|
+
);
|
|
10299
|
+
const parts = descriptors.flatMap((descriptor) => descriptor.parts);
|
|
10300
|
+
this.spanManager?.addEventToActiveSpan("axl.model_input", {
|
|
10301
|
+
"axl.input.parts": parts.length,
|
|
10302
|
+
"axl.input.images": parts.filter((part) => part.type === "image").length,
|
|
10303
|
+
"axl.input.source.bytes": parts.filter(
|
|
10304
|
+
(part) => part.type === "image" && part.source === "bytes"
|
|
10305
|
+
).length,
|
|
10306
|
+
"axl.input.source.base64": parts.filter(
|
|
10307
|
+
(part) => part.type === "image" && part.source === "base64"
|
|
10308
|
+
).length,
|
|
10309
|
+
"axl.input.source.url": parts.filter(
|
|
10310
|
+
(part) => part.type === "image" && part.source === "url"
|
|
10311
|
+
).length,
|
|
10312
|
+
"axl.input.source.provider_file": parts.filter(
|
|
10313
|
+
(part) => part.type === "image" && part.source === "provider-file"
|
|
10314
|
+
).length,
|
|
10315
|
+
"axl.input.inline_bytes": parts.reduce(
|
|
10316
|
+
(sum, part) => sum + (part.type === "image" ? part.bytes ?? 0 : 0),
|
|
10317
|
+
0
|
|
10318
|
+
)
|
|
10319
|
+
});
|
|
10320
|
+
}
|
|
10321
|
+
assertRichMessagesAreUserOnly(sessionHistory);
|
|
7792
10322
|
let resolvedHandoffs;
|
|
7793
10323
|
if (typeof agent2._config.handoffs === "function") {
|
|
7794
10324
|
try {
|
|
@@ -7809,50 +10339,60 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7809
10339
|
messages.push({ role: "system", content: systemPrompt });
|
|
7810
10340
|
}
|
|
7811
10341
|
const maxContext = agent2._config.maxContext;
|
|
7812
|
-
if (maxContext &&
|
|
10342
|
+
if (maxContext && sessionHistory.length > 0) {
|
|
7813
10343
|
const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
|
|
7814
10344
|
const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;
|
|
7815
10345
|
const toolTokens = toolDefs.length > 0 ? estimateTokens(JSON.stringify(toolDefs)) : 0;
|
|
7816
10346
|
const overhead = systemTokens + toolTokens + reserveTokens;
|
|
7817
10347
|
const availableForHistory = maxContext - overhead;
|
|
7818
|
-
const
|
|
7819
|
-
if (
|
|
10348
|
+
const historyEstimate = estimateMessagesTokens(sessionHistory);
|
|
10349
|
+
if (historyEstimate.unmeasured) {
|
|
10350
|
+
this.emitEvent({
|
|
10351
|
+
type: "log",
|
|
10352
|
+
data: {
|
|
10353
|
+
warning: "Context media contribution is unmeasured; provider will enforce its context limit"
|
|
10354
|
+
}
|
|
10355
|
+
});
|
|
10356
|
+
}
|
|
10357
|
+
if (historyEstimate.tokens > availableForHistory) {
|
|
7820
10358
|
const summarizedMessages = await this.summarizeHistory(
|
|
7821
10359
|
provider,
|
|
7822
10360
|
model,
|
|
7823
|
-
|
|
10361
|
+
sessionHistory,
|
|
7824
10362
|
availableForHistory
|
|
7825
10363
|
);
|
|
7826
10364
|
for (const msg of summarizedMessages) {
|
|
7827
10365
|
messages.push(msg);
|
|
7828
10366
|
}
|
|
7829
10367
|
} else {
|
|
7830
|
-
for (const msg of
|
|
10368
|
+
for (const msg of sessionHistory) {
|
|
7831
10369
|
messages.push(msg);
|
|
7832
10370
|
}
|
|
7833
10371
|
}
|
|
7834
10372
|
} else {
|
|
7835
|
-
for (const msg of
|
|
10373
|
+
for (const msg of sessionHistory) {
|
|
7836
10374
|
messages.push(msg);
|
|
7837
10375
|
}
|
|
7838
10376
|
}
|
|
7839
10377
|
const schemaPromptMode = options?.schemaPrompt ?? agent2._config.schemaPrompt ?? "json-schema";
|
|
7840
|
-
let userContent =
|
|
10378
|
+
let userContent = input;
|
|
7841
10379
|
let appendedSchemaText;
|
|
7842
10380
|
if (options?.schema) {
|
|
7843
10381
|
const schema = options.schema;
|
|
7844
10382
|
if (schemaPromptMode === "none") {
|
|
7845
10383
|
} else if (schemaPromptMode === "json-schema") {
|
|
7846
10384
|
appendedSchemaText = renderSchemaForPrompt(schema);
|
|
7847
|
-
|
|
10385
|
+
const guidance = `
|
|
7848
10386
|
|
|
7849
10387
|
Respond with valid JSON matching this schema:
|
|
7850
10388
|
${appendedSchemaText}`;
|
|
10389
|
+
userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
|
|
7851
10390
|
} else {
|
|
7852
10391
|
appendedSchemaText = typeof schemaPromptMode.render === "function" ? schemaPromptMode.render(schema) : schemaPromptMode.render;
|
|
7853
|
-
|
|
10392
|
+
const guidance = `
|
|
7854
10393
|
|
|
7855
10394
|
${appendedSchemaText}`;
|
|
10395
|
+
userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
|
|
7856
10396
|
}
|
|
7857
10397
|
}
|
|
7858
10398
|
messages.push({ role: "user", content: userContent });
|
|
@@ -7880,7 +10420,10 @@ ${appendedSchemaText}`;
|
|
|
7880
10420
|
}
|
|
7881
10421
|
const guardrails = agent2._config.guardrails;
|
|
7882
10422
|
if (guardrails?.input) {
|
|
7883
|
-
const inputResult = await guardrails.input(prompt, {
|
|
10423
|
+
const inputResult = await guardrails.input(prompt, {
|
|
10424
|
+
metadata: this.metadata,
|
|
10425
|
+
input: cloneModelInput(input)
|
|
10426
|
+
});
|
|
7884
10427
|
this.emitEvent({
|
|
7885
10428
|
type: "guardrail",
|
|
7886
10429
|
agent: agent2._name,
|
|
@@ -7971,7 +10514,7 @@ ${appendedSchemaText}`;
|
|
|
7971
10514
|
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
7972
10515
|
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
7973
10516
|
stop: options?.stop ?? agent2._config.stop,
|
|
7974
|
-
providerOptions
|
|
10517
|
+
providerOptions,
|
|
7975
10518
|
signal: this.currentSignal
|
|
7976
10519
|
};
|
|
7977
10520
|
if (options?.schema && toolDefs.length === 0) {
|
|
@@ -7997,15 +10540,26 @@ ${appendedSchemaText}`;
|
|
|
7997
10540
|
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
7998
10541
|
};
|
|
7999
10542
|
let messagesSnapshot;
|
|
10543
|
+
let messageInputs;
|
|
8000
10544
|
if (verboseTrace) {
|
|
8001
10545
|
try {
|
|
8002
|
-
|
|
10546
|
+
messageInputs = currentMessages.flatMap((message, index) => {
|
|
10547
|
+
const descriptor = describeModelInput(message.content);
|
|
10548
|
+
return descriptor ? [{ index, input: descriptor }] : [];
|
|
10549
|
+
});
|
|
10550
|
+
messagesSnapshot = currentMessages.map((message) => ({
|
|
10551
|
+
...message,
|
|
10552
|
+
content: sanitizeModelInputForTrace(message.content)
|
|
10553
|
+
}));
|
|
8003
10554
|
} catch (err) {
|
|
8004
10555
|
console.warn(
|
|
8005
10556
|
"[axl] verbose trace messages snapshot failed to clone; emitting shallow copy:",
|
|
8006
10557
|
err instanceof Error ? err.message : String(err)
|
|
8007
10558
|
);
|
|
8008
|
-
messagesSnapshot =
|
|
10559
|
+
messagesSnapshot = currentMessages.map((message) => ({
|
|
10560
|
+
...message,
|
|
10561
|
+
content: sanitizeModelInputForTrace(message.content)
|
|
10562
|
+
}));
|
|
8009
10563
|
}
|
|
8010
10564
|
}
|
|
8011
10565
|
const retryReason = pendingRetryReason;
|
|
@@ -8013,16 +10567,18 @@ ${appendedSchemaText}`;
|
|
|
8013
10567
|
this.emitEvent({
|
|
8014
10568
|
type: "agent_call_start",
|
|
8015
10569
|
agent: agent2._name,
|
|
8016
|
-
model:
|
|
10570
|
+
model: effectiveModelUri,
|
|
8017
10571
|
turn: turns,
|
|
8018
10572
|
data: {
|
|
8019
10573
|
prompt,
|
|
10574
|
+
...typeof input === "string" ? {} : { input: describeModelInput(input) },
|
|
8020
10575
|
...systemPrompt ? { system: systemPrompt } : {},
|
|
8021
10576
|
params: callParams,
|
|
8022
10577
|
turn: turns,
|
|
8023
10578
|
...retryReason ? { retryReason } : {},
|
|
8024
10579
|
...toolDefs.length > 0 ? { toolNames: toolDefs.map((t) => t.function.name) } : {},
|
|
8025
|
-
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
10580
|
+
...messagesSnapshot ? { messages: messagesSnapshot } : {},
|
|
10581
|
+
...messageInputs && messageInputs.length > 0 ? { messageInputs } : {}
|
|
8026
10582
|
}
|
|
8027
10583
|
});
|
|
8028
10584
|
let response;
|
|
@@ -8046,7 +10602,10 @@ ${appendedSchemaText}`;
|
|
|
8046
10602
|
});
|
|
8047
10603
|
}
|
|
8048
10604
|
}) : void 0;
|
|
8049
|
-
for await (const chunk of provider.stream(
|
|
10605
|
+
for await (const chunk of provider.stream(
|
|
10606
|
+
cloneMessagesForProvider(currentMessages),
|
|
10607
|
+
chatOptions
|
|
10608
|
+
)) {
|
|
8050
10609
|
if (chunk.type === "text_delta") {
|
|
8051
10610
|
content2 += chunk.content;
|
|
8052
10611
|
this.emitEvent({ type: "token", data: chunk.content });
|
|
@@ -8112,24 +10671,25 @@ ${appendedSchemaText}`;
|
|
|
8112
10671
|
response.thinking_content = thinkingContent;
|
|
8113
10672
|
}
|
|
8114
10673
|
} else {
|
|
8115
|
-
response = await provider.chat(currentMessages, chatOptions);
|
|
10674
|
+
response = await provider.chat(cloneMessagesForProvider(currentMessages), chatOptions);
|
|
8116
10675
|
}
|
|
8117
10676
|
} catch (err) {
|
|
10677
|
+
if (richRequest) this.recordRichFailure(err);
|
|
8118
10678
|
this.emitEvent({
|
|
8119
10679
|
type: "agent_call_end",
|
|
8120
10680
|
agent: agent2._name,
|
|
8121
|
-
model:
|
|
10681
|
+
model: effectiveModelUri,
|
|
8122
10682
|
promptVersion: agent2._config.version,
|
|
8123
10683
|
duration: Date.now() - turnStart,
|
|
8124
10684
|
data: {
|
|
8125
10685
|
response: "",
|
|
8126
10686
|
turn: turns,
|
|
8127
10687
|
...retryReason ? { retryReason } : {},
|
|
8128
|
-
error: err
|
|
10688
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err),
|
|
8129
10689
|
// Enrich with typed-error metadata when available. `body` is
|
|
8130
10690
|
// deliberately NOT emitted — it's redaction-eligible (see
|
|
8131
10691
|
// docs/security.md). `data.error` already carries the message.
|
|
8132
|
-
...err
|
|
10692
|
+
...providerErrorEventMetadata(err, richRequest)
|
|
8133
10693
|
}
|
|
8134
10694
|
});
|
|
8135
10695
|
throw err;
|
|
@@ -8143,7 +10703,7 @@ ${appendedSchemaText}`;
|
|
|
8143
10703
|
this.emitEvent({
|
|
8144
10704
|
type: "agent_call_end",
|
|
8145
10705
|
agent: agent2._name,
|
|
8146
|
-
model:
|
|
10706
|
+
model: effectiveModelUri,
|
|
8147
10707
|
promptVersion: agent2._config.version,
|
|
8148
10708
|
cost: response.cost,
|
|
8149
10709
|
tokens: response.usage ? {
|
|
@@ -8177,12 +10737,12 @@ ${appendedSchemaText}`;
|
|
|
8177
10737
|
if (descriptor) {
|
|
8178
10738
|
const mode = descriptor.mode ?? "oneway";
|
|
8179
10739
|
let handoffPrompt = prompt;
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
} catch {
|
|
10740
|
+
try {
|
|
10741
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
10742
|
+
if (typeof args.message === "string" && args.message.length > 0) {
|
|
10743
|
+
handoffPrompt = args.message;
|
|
8185
10744
|
}
|
|
10745
|
+
} catch {
|
|
8186
10746
|
}
|
|
8187
10747
|
const handoffStart = Date.now();
|
|
8188
10748
|
const sourceFrame = askStorage.getStore();
|
|
@@ -8209,15 +10769,27 @@ ${appendedSchemaText}`;
|
|
|
8209
10769
|
validateRetries: options.validateRetries
|
|
8210
10770
|
} : void 0;
|
|
8211
10771
|
const handoffFn = () => askStorage.run(targetFrame, async () => {
|
|
8212
|
-
|
|
10772
|
+
const delegateOriginal = agent2._delegateOriginalInput;
|
|
10773
|
+
const handoffInput = delegateOriginal ?? (typeof input === "string" ? handoffPrompt : input);
|
|
10774
|
+
const enrichedHandoffInput = appendHandoffInstruction(
|
|
10775
|
+
handoffInput,
|
|
10776
|
+
inputText(input),
|
|
10777
|
+
handoffPrompt
|
|
10778
|
+
);
|
|
10779
|
+
this.emitEvent({
|
|
10780
|
+
type: "ask_start",
|
|
10781
|
+
prompt: inputText(enrichedHandoffInput),
|
|
10782
|
+
...typeof enrichedHandoffInput === "string" ? {} : { input: describeModelInput(enrichedHandoffInput) }
|
|
10783
|
+
});
|
|
8213
10784
|
const targetAskStart = Date.now();
|
|
8214
10785
|
try {
|
|
8215
10786
|
const result = await this.executeAgentCall(
|
|
8216
10787
|
descriptor.agent,
|
|
8217
|
-
|
|
10788
|
+
enrichedHandoffInput,
|
|
8218
10789
|
handoffOptions,
|
|
8219
10790
|
currentMessages,
|
|
8220
|
-
usageCapture
|
|
10791
|
+
usageCapture,
|
|
10792
|
+
sessionHistory
|
|
8221
10793
|
);
|
|
8222
10794
|
this.emitEvent({
|
|
8223
10795
|
type: "ask_end",
|
|
@@ -8228,11 +10800,13 @@ ${appendedSchemaText}`;
|
|
|
8228
10800
|
});
|
|
8229
10801
|
return result;
|
|
8230
10802
|
} catch (err) {
|
|
10803
|
+
const richHandoff = typeof enrichedHandoffInput !== "string" || hasRichMessage(sessionHistory);
|
|
10804
|
+
if (richHandoff) this.recordRichFailure(err);
|
|
8231
10805
|
this.emitEvent({
|
|
8232
10806
|
type: "ask_end",
|
|
8233
10807
|
outcome: {
|
|
8234
10808
|
ok: false,
|
|
8235
|
-
error: err
|
|
10809
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
|
|
8236
10810
|
},
|
|
8237
10811
|
cost: targetFrame.askCost.value,
|
|
8238
10812
|
...targetFrame.askUnpriced ? { unpriced: true } : {},
|
|
@@ -8331,12 +10905,7 @@ ${appendedSchemaText}`;
|
|
|
8331
10905
|
return await handoffFn();
|
|
8332
10906
|
}
|
|
8333
10907
|
}
|
|
8334
|
-
await this.observeToolInvocation(
|
|
8335
|
-
agent2,
|
|
8336
|
-
toolCall,
|
|
8337
|
-
currentMessages,
|
|
8338
|
-
toolDefs.map((tool2) => tool2.function.name)
|
|
8339
|
-
);
|
|
10908
|
+
await this.observeToolInvocation(agent2, toolCall, currentMessages, toolDefs);
|
|
8340
10909
|
}
|
|
8341
10910
|
continue;
|
|
8342
10911
|
}
|
|
@@ -8746,12 +11315,12 @@ ${appendedSchemaText}`;
|
|
|
8746
11315
|
content: `Summary of earlier conversation:
|
|
8747
11316
|
${this.summaryCache}`
|
|
8748
11317
|
};
|
|
8749
|
-
const summaryTokens = estimateTokens(summaryMsg2.content) + 4;
|
|
11318
|
+
const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg2.content)) + 4;
|
|
8750
11319
|
const remaining = availableTokens - summaryTokens;
|
|
8751
11320
|
let recentTokens2 = 0;
|
|
8752
11321
|
let splitIdx2 = history.length;
|
|
8753
11322
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
8754
|
-
const msgTokens = estimateTokens(history[i].content) + 4;
|
|
11323
|
+
const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
|
|
8755
11324
|
if (recentTokens2 + msgTokens > remaining) break;
|
|
8756
11325
|
recentTokens2 += msgTokens;
|
|
8757
11326
|
splitIdx2 = i;
|
|
@@ -8764,7 +11333,7 @@ ${this.summaryCache}`
|
|
|
8764
11333
|
let splitIdx = history.length;
|
|
8765
11334
|
const targetRecent = Math.floor(availableTokens * 0.6);
|
|
8766
11335
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
8767
|
-
const msgTokens = estimateTokens(history[i].content) + 4;
|
|
11336
|
+
const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
|
|
8768
11337
|
if (recentTokens + msgTokens > targetRecent) break;
|
|
8769
11338
|
recentTokens += msgTokens;
|
|
8770
11339
|
splitIdx = i;
|
|
@@ -8782,7 +11351,7 @@ ${this.summaryCache}`
|
|
|
8782
11351
|
summaryProvider = provider;
|
|
8783
11352
|
summaryModel = model;
|
|
8784
11353
|
}
|
|
8785
|
-
const oldContent = oldMessages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
11354
|
+
const oldContent = oldMessages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n");
|
|
8786
11355
|
const summaryResponse = await summaryProvider.chat(
|
|
8787
11356
|
[
|
|
8788
11357
|
{
|
|
@@ -10016,7 +12585,9 @@ ${summaryResponse.content}`
|
|
|
10016
12585
|
reason: "routed"
|
|
10017
12586
|
}
|
|
10018
12587
|
});
|
|
10019
|
-
|
|
12588
|
+
const routerInput = options?.routerInput === "text" ? inputText(prompt) : prompt;
|
|
12589
|
+
routerAgent._delegateOriginalInput = prompt;
|
|
12590
|
+
return this.ask(routerAgent, routerInput, {
|
|
10020
12591
|
schema: options?.schema,
|
|
10021
12592
|
schemaPrompt: options?.schemaPrompt,
|
|
10022
12593
|
nativeStructuredOutput: options?.nativeStructuredOutput,
|
|
@@ -10026,6 +12597,278 @@ ${summaryResponse.content}`
|
|
|
10026
12597
|
validateRetries: options?.validateRetries
|
|
10027
12598
|
});
|
|
10028
12599
|
}
|
|
12600
|
+
/** Transcribe recorded audio through the dedicated transcription registry.
|
|
12601
|
+
* This operation never enters the chat history or chat-provider path. */
|
|
12602
|
+
async transcribe(request) {
|
|
12603
|
+
const startedAt = Date.now();
|
|
12604
|
+
const transcriptionId = (0, import_node_crypto.randomUUID)();
|
|
12605
|
+
const signal = this.currentSignal;
|
|
12606
|
+
const rawAudio = request && typeof request === "object" && "audio" in request ? request.audio : void 0;
|
|
12607
|
+
const source = rawAudio?.type;
|
|
12608
|
+
const requestedUri = typeof request?.model === "string" ? request.model : void 0;
|
|
12609
|
+
const colon = requestedUri?.indexOf(":") ?? -1;
|
|
12610
|
+
const requestedProvider = colon > 0 ? requestedUri.slice(0, colon) : void 0;
|
|
12611
|
+
const requestedModel = colon > 0 && colon < requestedUri.length - 1 ? requestedUri.slice(colon + 1) : void 0;
|
|
12612
|
+
const startAudio = {
|
|
12613
|
+
source: typeof source === "string" ? source : "unknown",
|
|
12614
|
+
...rawAudio?.data instanceof Uint8Array ? { bytes: rawAudio.data.byteLength } : {},
|
|
12615
|
+
...typeof rawAudio?.mediaType === "string" ? { mediaType: rawAudio.mediaType } : {}
|
|
12616
|
+
};
|
|
12617
|
+
this.emitEvent({
|
|
12618
|
+
type: "transcription_start",
|
|
12619
|
+
transcriptionId,
|
|
12620
|
+
model: requestedModel,
|
|
12621
|
+
data: {
|
|
12622
|
+
...requestedProvider ? { provider: requestedProvider } : {},
|
|
12623
|
+
...requestedModel ? { model: requestedModel } : {},
|
|
12624
|
+
audio: startAudio
|
|
12625
|
+
}
|
|
12626
|
+
});
|
|
12627
|
+
let terminalEmitted = false;
|
|
12628
|
+
const emitTerminal = (partial) => {
|
|
12629
|
+
if (terminalEmitted) return;
|
|
12630
|
+
terminalEmitted = true;
|
|
12631
|
+
this.emitEvent({
|
|
12632
|
+
type: "transcription_end",
|
|
12633
|
+
transcriptionId,
|
|
12634
|
+
model: partial.model,
|
|
12635
|
+
duration: Date.now() - startedAt,
|
|
12636
|
+
...partial.cost !== void 0 ? { cost: partial.cost } : {},
|
|
12637
|
+
...partial.tokens ? { tokens: partial.tokens } : {},
|
|
12638
|
+
data: partial.data
|
|
12639
|
+
});
|
|
12640
|
+
};
|
|
12641
|
+
const abortError3 = () => new DOMException("The transcription operation was aborted", "AbortError");
|
|
12642
|
+
const cleanupStatus = (value) => value === "not_required" || value === "deleted" || value === "failed" || value === "timed_out" ? value : void 0;
|
|
12643
|
+
const safeProviderFailure = (error, provider, model) => {
|
|
12644
|
+
const details = error && typeof error === "object" ? error : void 0;
|
|
12645
|
+
const accounting = normalizeTranscriptionAccounting(details?.usage, details?.pricingStatus);
|
|
12646
|
+
const cleanup = cleanupStatus(details?.cleanupStatus);
|
|
12647
|
+
const providerError = error instanceof ProviderError ? error : void 0;
|
|
12648
|
+
const diagnostics = providerError ? {
|
|
12649
|
+
status: providerError.status,
|
|
12650
|
+
retryable: providerError.retryable,
|
|
12651
|
+
...providerError.retryAfterMs !== void 0 ? { retryAfterMs: providerError.retryAfterMs } : {},
|
|
12652
|
+
...providerError.requestId ? { requestId: providerError.requestId } : {}
|
|
12653
|
+
} : void 0;
|
|
12654
|
+
return {
|
|
12655
|
+
accounting,
|
|
12656
|
+
cleanup,
|
|
12657
|
+
diagnostics,
|
|
12658
|
+
error: new TranscriptionOperationError({
|
|
12659
|
+
provider,
|
|
12660
|
+
model,
|
|
12661
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
12662
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
12663
|
+
...cleanup ? { cleanupStatus: cleanup } : {},
|
|
12664
|
+
...diagnostics ?? {},
|
|
12665
|
+
cause: error
|
|
12666
|
+
})
|
|
12667
|
+
};
|
|
12668
|
+
};
|
|
12669
|
+
const terminalData = (status, provider, model, audio, accounting, extras = {}) => ({
|
|
12670
|
+
status,
|
|
12671
|
+
...provider ? { provider } : {},
|
|
12672
|
+
...model ? { model } : {},
|
|
12673
|
+
...audio ? { audio } : {},
|
|
12674
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
12675
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
12676
|
+
...extras
|
|
12677
|
+
});
|
|
12678
|
+
try {
|
|
12679
|
+
const normalized = normalizeTranscriptionRequest(request);
|
|
12680
|
+
const descriptor = {
|
|
12681
|
+
source: normalized.audio.type,
|
|
12682
|
+
...normalized.audio.type === "bytes" ? { bytes: normalized.audio.data.byteLength } : {},
|
|
12683
|
+
...normalized.audio.mediaType !== void 0 ? { mediaType: normalized.audio.mediaType } : {}
|
|
12684
|
+
};
|
|
12685
|
+
if (this.currentSignal?.aborted || signal?.aborted) {
|
|
12686
|
+
emitTerminal({
|
|
12687
|
+
model: requestedModel,
|
|
12688
|
+
data: terminalData("aborted", requestedProvider, requestedModel, descriptor, {
|
|
12689
|
+
hasWork: false
|
|
12690
|
+
})
|
|
12691
|
+
});
|
|
12692
|
+
throw abortError3();
|
|
12693
|
+
}
|
|
12694
|
+
if (this.budgetContext?.exceeded) {
|
|
12695
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
12696
|
+
if (policy === "warn") {
|
|
12697
|
+
this.emitEvent({
|
|
12698
|
+
type: "log",
|
|
12699
|
+
data: { warning: "Budget exceeded", limit, spent, policy }
|
|
12700
|
+
});
|
|
12701
|
+
} else {
|
|
12702
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
12703
|
+
}
|
|
12704
|
+
}
|
|
12705
|
+
if (!this.transcriptionProviderRegistry) {
|
|
12706
|
+
throw new InvalidTranscriptionInputError(
|
|
12707
|
+
"No transcription provider registry is configured"
|
|
12708
|
+
);
|
|
12709
|
+
}
|
|
12710
|
+
const resolved = this.transcriptionProviderRegistry.resolve(normalized.model, this.config);
|
|
12711
|
+
if (normalized.audio.type === "provider-file" && normalized.audio.provider !== resolved.providerName) {
|
|
12712
|
+
throw new InvalidTranscriptionInputError(
|
|
12713
|
+
"Transcription provider-file source does not match the requested provider"
|
|
12714
|
+
);
|
|
12715
|
+
}
|
|
12716
|
+
const capabilities = resolved.provider.capabilities(resolved.model);
|
|
12717
|
+
if (!capabilities || !capabilities.sources.includes(normalized.audio.type)) {
|
|
12718
|
+
throw new InvalidTranscriptionInputError(
|
|
12719
|
+
"The requested transcription model does not support this audio source"
|
|
12720
|
+
);
|
|
12721
|
+
}
|
|
12722
|
+
if (normalized.timestamps && !capabilities.timestamps?.includes(normalized.timestamps)) {
|
|
12723
|
+
throw new InvalidTranscriptionInputError(
|
|
12724
|
+
"The requested transcription model does not support requested timestamps"
|
|
12725
|
+
);
|
|
12726
|
+
}
|
|
12727
|
+
if (normalized.diarization && !capabilities.diarization) {
|
|
12728
|
+
throw new InvalidTranscriptionInputError(
|
|
12729
|
+
"The requested transcription model does not support diarization"
|
|
12730
|
+
);
|
|
12731
|
+
}
|
|
12732
|
+
const providerRequest = {
|
|
12733
|
+
...normalized,
|
|
12734
|
+
model: resolved.model,
|
|
12735
|
+
...signal ? { signal } : {}
|
|
12736
|
+
};
|
|
12737
|
+
let result;
|
|
12738
|
+
try {
|
|
12739
|
+
result = await resolved.provider.transcribe(providerRequest);
|
|
12740
|
+
} catch (error) {
|
|
12741
|
+
const failure = safeProviderFailure(error, resolved.providerName, resolved.model);
|
|
12742
|
+
if (this.currentSignal?.aborted || signal?.aborted) {
|
|
12743
|
+
if (failure.accounting.cost !== void 0)
|
|
12744
|
+
this._accumulateBudgetCost(failure.accounting.cost);
|
|
12745
|
+
emitTerminal({
|
|
12746
|
+
model: resolved.model,
|
|
12747
|
+
...failure.accounting.cost !== void 0 ? { cost: failure.accounting.cost } : {},
|
|
12748
|
+
...failure.accounting.tokens ? { tokens: failure.accounting.tokens } : {},
|
|
12749
|
+
data: terminalData(
|
|
12750
|
+
"aborted",
|
|
12751
|
+
resolved.providerName,
|
|
12752
|
+
resolved.model,
|
|
12753
|
+
descriptor,
|
|
12754
|
+
failure.accounting,
|
|
12755
|
+
...failure.cleanup ? [{ cleanupStatus: failure.cleanup }] : []
|
|
12756
|
+
)
|
|
12757
|
+
});
|
|
12758
|
+
throw abortError3();
|
|
12759
|
+
}
|
|
12760
|
+
if (failure.accounting.cost !== void 0)
|
|
12761
|
+
this._accumulateBudgetCost(failure.accounting.cost);
|
|
12762
|
+
emitTerminal({
|
|
12763
|
+
model: resolved.model,
|
|
12764
|
+
...failure.accounting.cost !== void 0 ? { cost: failure.accounting.cost } : {},
|
|
12765
|
+
...failure.accounting.tokens ? { tokens: failure.accounting.tokens } : {},
|
|
12766
|
+
data: terminalData(
|
|
12767
|
+
"failed",
|
|
12768
|
+
resolved.providerName,
|
|
12769
|
+
resolved.model,
|
|
12770
|
+
descriptor,
|
|
12771
|
+
failure.accounting,
|
|
12772
|
+
{
|
|
12773
|
+
error: "Transcription operation failed",
|
|
12774
|
+
errorCode: failure.error.code,
|
|
12775
|
+
...failure.diagnostics ? { providerError: failure.diagnostics } : {},
|
|
12776
|
+
...failure.cleanup ? { cleanupStatus: failure.cleanup } : {}
|
|
12777
|
+
}
|
|
12778
|
+
)
|
|
12779
|
+
});
|
|
12780
|
+
throw failure.error;
|
|
12781
|
+
}
|
|
12782
|
+
const accounting = normalizeTranscriptionAccounting(
|
|
12783
|
+
result.transcript?.usage,
|
|
12784
|
+
result.transcript?.pricingStatus
|
|
12785
|
+
);
|
|
12786
|
+
if (accounting.cost !== void 0) this._accumulateBudgetCost(accounting.cost);
|
|
12787
|
+
if (this.currentSignal?.aborted || signal?.aborted) {
|
|
12788
|
+
emitTerminal({
|
|
12789
|
+
model: resolved.model,
|
|
12790
|
+
...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
|
|
12791
|
+
...accounting.tokens ? { tokens: accounting.tokens } : {},
|
|
12792
|
+
data: terminalData(
|
|
12793
|
+
"aborted",
|
|
12794
|
+
resolved.providerName,
|
|
12795
|
+
resolved.model,
|
|
12796
|
+
descriptor,
|
|
12797
|
+
accounting,
|
|
12798
|
+
...cleanupStatus(result.cleanupStatus) ? [{ cleanupStatus: cleanupStatus(result.cleanupStatus) }] : []
|
|
12799
|
+
)
|
|
12800
|
+
});
|
|
12801
|
+
throw abortError3();
|
|
12802
|
+
}
|
|
12803
|
+
let transcript;
|
|
12804
|
+
try {
|
|
12805
|
+
transcript = cloneTranscript(result.transcript);
|
|
12806
|
+
} catch (error) {
|
|
12807
|
+
const cleanup = cleanupStatus(result.cleanupStatus);
|
|
12808
|
+
const failure = new TranscriptionOperationError({
|
|
12809
|
+
provider: resolved.providerName,
|
|
12810
|
+
model: resolved.model,
|
|
12811
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
12812
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
12813
|
+
...cleanup ? { cleanupStatus: cleanup } : {},
|
|
12814
|
+
cause: error
|
|
12815
|
+
});
|
|
12816
|
+
emitTerminal({
|
|
12817
|
+
model: resolved.model,
|
|
12818
|
+
...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
|
|
12819
|
+
...accounting.tokens ? { tokens: accounting.tokens } : {},
|
|
12820
|
+
data: terminalData(
|
|
12821
|
+
"failed",
|
|
12822
|
+
resolved.providerName,
|
|
12823
|
+
resolved.model,
|
|
12824
|
+
descriptor,
|
|
12825
|
+
accounting,
|
|
12826
|
+
{
|
|
12827
|
+
error: "Transcription operation failed",
|
|
12828
|
+
errorCode: "TRANSCRIPTION_PROVIDER_ERROR",
|
|
12829
|
+
...cleanup ? { cleanupStatus: cleanup } : {}
|
|
12830
|
+
}
|
|
12831
|
+
)
|
|
12832
|
+
});
|
|
12833
|
+
throw failure;
|
|
12834
|
+
}
|
|
12835
|
+
emitTerminal({
|
|
12836
|
+
model: resolved.model,
|
|
12837
|
+
...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
|
|
12838
|
+
...accounting.tokens ? { tokens: accounting.tokens } : {},
|
|
12839
|
+
data: terminalData(
|
|
12840
|
+
"completed",
|
|
12841
|
+
resolved.providerName,
|
|
12842
|
+
resolved.model,
|
|
12843
|
+
descriptor,
|
|
12844
|
+
accounting,
|
|
12845
|
+
{
|
|
12846
|
+
text: transcript.text,
|
|
12847
|
+
...cleanupStatus(result.cleanupStatus) ? { cleanupStatus: cleanupStatus(result.cleanupStatus) } : {}
|
|
12848
|
+
}
|
|
12849
|
+
)
|
|
12850
|
+
});
|
|
12851
|
+
return transcript;
|
|
12852
|
+
} catch (error) {
|
|
12853
|
+
if (!terminalEmitted) {
|
|
12854
|
+
const aborted = signal?.aborted || error instanceof Error && error.name === "AbortError";
|
|
12855
|
+
emitTerminal({
|
|
12856
|
+
model: requestedModel,
|
|
12857
|
+
data: terminalData(
|
|
12858
|
+
aborted ? "aborted" : "failed",
|
|
12859
|
+
requestedProvider,
|
|
12860
|
+
requestedModel,
|
|
12861
|
+
void 0,
|
|
12862
|
+
{ hasWork: false },
|
|
12863
|
+
{
|
|
12864
|
+
error: aborted ? "Transcription operation aborted" : "Transcription operation failed"
|
|
12865
|
+
}
|
|
12866
|
+
)
|
|
12867
|
+
});
|
|
12868
|
+
}
|
|
12869
|
+
throw error;
|
|
12870
|
+
}
|
|
12871
|
+
}
|
|
10029
12872
|
// ── Private ───────────────────────────────────────────────────────────
|
|
10030
12873
|
/**
|
|
10031
12874
|
* Internal emitter input — intentionally loose so call sites don't need to
|
|
@@ -10041,7 +12884,7 @@ ${summaryResponse.content}`
|
|
|
10041
12884
|
const cost = partial.cost;
|
|
10042
12885
|
if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
10043
12886
|
const usable = isUsableCost(cost);
|
|
10044
|
-
const unpriced = !usable &&
|
|
12887
|
+
const unpriced = !usable && hasPositiveBillableWork(
|
|
10045
12888
|
partial
|
|
10046
12889
|
);
|
|
10047
12890
|
if (frame) {
|
|
@@ -10146,11 +12989,13 @@ function agent(config) {
|
|
|
10146
12989
|
_name: defaultName,
|
|
10147
12990
|
async ask(prompt, options) {
|
|
10148
12991
|
const registry = new ProviderRegistry();
|
|
12992
|
+
const transcriptionRegistry = new TranscriptionProviderRegistry();
|
|
10149
12993
|
const ctx = new WorkflowContext({
|
|
10150
12994
|
input: prompt,
|
|
10151
12995
|
executionId: (0, import_node_crypto2.randomUUID)(),
|
|
10152
12996
|
config: {},
|
|
10153
|
-
providerRegistry: registry
|
|
12997
|
+
providerRegistry: registry,
|
|
12998
|
+
transcriptionProviderRegistry: transcriptionRegistry
|
|
10154
12999
|
});
|
|
10155
13000
|
return ctx.ask(agentInstance, prompt, options);
|
|
10156
13001
|
},
|
|
@@ -10210,7 +13055,7 @@ var MemoryStore = class {
|
|
|
10210
13055
|
return data !== void 0 ? structuredClone(data) : null;
|
|
10211
13056
|
}
|
|
10212
13057
|
async saveSession(sessionId, history) {
|
|
10213
|
-
this.sessions.set(sessionId, structuredClone(history));
|
|
13058
|
+
this.sessions.set(sessionId, structuredClone(normalizePersistedSessionHistory(history)));
|
|
10214
13059
|
}
|
|
10215
13060
|
async getSession(sessionId) {
|
|
10216
13061
|
const history = this.sessions.get(sessionId);
|
|
@@ -10634,7 +13479,7 @@ var SQLiteStore = class {
|
|
|
10634
13479
|
const stmt = this.db.prepare(
|
|
10635
13480
|
"INSERT OR REPLACE INTO sessions (session_id, history) VALUES (?, ?)"
|
|
10636
13481
|
);
|
|
10637
|
-
stmt.run(sessionId, JSON.stringify(history));
|
|
13482
|
+
stmt.run(sessionId, JSON.stringify(normalizePersistedSessionHistory(history)));
|
|
10638
13483
|
}
|
|
10639
13484
|
async getSession(sessionId) {
|
|
10640
13485
|
const stmt = this.db.prepare("SELECT history FROM sessions WHERE session_id = ?");
|
|
@@ -11202,7 +14047,7 @@ var AxlStream = class extends import_node_stream.Readable {
|
|
|
11202
14047
|
/** Signal an error. `executionId` is required for the same reason as
|
|
11203
14048
|
* `_done`: terminal events must carry a real id even when the
|
|
11204
14049
|
* failure happens before any real trace event fires (review S4). */
|
|
11205
|
-
_error(error, executionId) {
|
|
14050
|
+
_error(error, executionId, observerMessage = error.message, observerName = error.name) {
|
|
11206
14051
|
if (this.events.isFinished) return;
|
|
11207
14052
|
const errorEvent = {
|
|
11208
14053
|
schemaVersion: 2,
|
|
@@ -11210,7 +14055,7 @@ var AxlStream = class extends import_node_stream.Readable {
|
|
|
11210
14055
|
executionId,
|
|
11211
14056
|
step: Number.MAX_SAFE_INTEGER,
|
|
11212
14057
|
timestamp: Date.now(),
|
|
11213
|
-
data: { message:
|
|
14058
|
+
data: { message: observerMessage, name: observerName }
|
|
11214
14059
|
};
|
|
11215
14060
|
this.events._push(errorEvent);
|
|
11216
14061
|
this.push(errorEvent);
|
|
@@ -11937,6 +14782,10 @@ var StreamingFlusher = class {
|
|
|
11937
14782
|
function isAbortError4(err) {
|
|
11938
14783
|
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
11939
14784
|
}
|
|
14785
|
+
function executionErrorProjection(ctx, error) {
|
|
14786
|
+
const safe = ctx?._observerErrorProjection(error);
|
|
14787
|
+
return safe ?? { message: error instanceof Error ? error.message : String(error) };
|
|
14788
|
+
}
|
|
11940
14789
|
function pushEventBounded(execInfo, event, cap) {
|
|
11941
14790
|
if (STREAMING_EXCLUDED_TYPES.has(event.type)) return;
|
|
11942
14791
|
if (cap === Infinity || execInfo.events.length < cap) {
|
|
@@ -12031,6 +14880,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12031
14880
|
tools = /* @__PURE__ */ new Map();
|
|
12032
14881
|
agents = /* @__PURE__ */ new Map();
|
|
12033
14882
|
providerRegistry;
|
|
14883
|
+
transcriptionProviderRegistry;
|
|
12034
14884
|
stateStore;
|
|
12035
14885
|
executions = /* @__PURE__ */ new Map();
|
|
12036
14886
|
pendingDecisionResolvers = /* @__PURE__ */ new Map();
|
|
@@ -12086,6 +14936,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12086
14936
|
super();
|
|
12087
14937
|
this.config = resolveConfig(config ?? {});
|
|
12088
14938
|
this.providerRegistry = new ProviderRegistry();
|
|
14939
|
+
this.transcriptionProviderRegistry = new TranscriptionProviderRegistry();
|
|
12089
14940
|
this.stateStore = this.createStateStore();
|
|
12090
14941
|
const requested = this.config.state?.maxEventsPerExecution;
|
|
12091
14942
|
if (requested === void 0) {
|
|
@@ -12234,7 +15085,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12234
15085
|
execInfo.status = "failed";
|
|
12235
15086
|
execInfo.completedAt = Date.now();
|
|
12236
15087
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
12237
|
-
execInfo.error =
|
|
15088
|
+
execInfo.error = executionErrorProjection(ctx, terminalError).message;
|
|
12238
15089
|
execInfo.observation = observation;
|
|
12239
15090
|
ctx._emitWorkflowEnd({
|
|
12240
15091
|
status: "failed",
|
|
@@ -12489,6 +15340,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12489
15340
|
metadata: options?.metadata,
|
|
12490
15341
|
config: this.config,
|
|
12491
15342
|
providerRegistry: this.providerRegistry,
|
|
15343
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12492
15344
|
stateStore: this.stateStore,
|
|
12493
15345
|
mcpManager: this.mcpManager,
|
|
12494
15346
|
spanManager: this.spanManager,
|
|
@@ -12517,6 +15369,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12517
15369
|
registerProvider(name, provider) {
|
|
12518
15370
|
this.providerRegistry.registerInstance(name, provider);
|
|
12519
15371
|
}
|
|
15372
|
+
/** Register a dedicated transcription adapter. It is never used for chat. */
|
|
15373
|
+
registerTranscriptionProvider(name, provider) {
|
|
15374
|
+
this.transcriptionProviderRegistry.registerInstance(name, provider);
|
|
15375
|
+
}
|
|
12520
15376
|
/** Resolve a provider:model URI to a Provider instance and model name. */
|
|
12521
15377
|
resolveProvider(uri) {
|
|
12522
15378
|
return this.providerRegistry.resolve(uri, this.config);
|
|
@@ -12560,6 +15416,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12560
15416
|
metadata: options?.metadata,
|
|
12561
15417
|
config: this.config,
|
|
12562
15418
|
providerRegistry: this.providerRegistry,
|
|
15419
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12563
15420
|
sessionHistory,
|
|
12564
15421
|
signal: controller.signal,
|
|
12565
15422
|
eventStreamOptions: options?.events,
|
|
@@ -12647,6 +15504,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12647
15504
|
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
12648
15505
|
this.abortControllers.set(executionId, controller);
|
|
12649
15506
|
let execInfo;
|
|
15507
|
+
let wfCtx;
|
|
12650
15508
|
const run = async () => {
|
|
12651
15509
|
const workflow2 = this.workflows.get(name);
|
|
12652
15510
|
if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
|
|
@@ -12670,12 +15528,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12670
15528
|
metadata: liftPersistedMetadata(options?.metadata)
|
|
12671
15529
|
};
|
|
12672
15530
|
this.executions.set(executionId, execInfo);
|
|
12673
|
-
const
|
|
15531
|
+
const context = new WorkflowContext({
|
|
12674
15532
|
input: validated,
|
|
12675
15533
|
executionId,
|
|
12676
15534
|
metadata: options?.metadata,
|
|
12677
15535
|
config: this.config,
|
|
12678
15536
|
providerRegistry: this.providerRegistry,
|
|
15537
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12679
15538
|
sessionHistory,
|
|
12680
15539
|
signal: controller.signal,
|
|
12681
15540
|
eventStreamOptions: options?.events,
|
|
@@ -12740,6 +15599,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12740
15599
|
unpricedWarned: false
|
|
12741
15600
|
}
|
|
12742
15601
|
});
|
|
15602
|
+
wfCtx = context;
|
|
12743
15603
|
return this.spanManager.withSpanAsync(
|
|
12744
15604
|
"axl.workflow.execute",
|
|
12745
15605
|
{
|
|
@@ -12749,7 +15609,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12749
15609
|
},
|
|
12750
15610
|
(span) => this.runWorkflowBody({
|
|
12751
15611
|
workflow: workflow2,
|
|
12752
|
-
ctx:
|
|
15612
|
+
ctx: context,
|
|
12753
15613
|
execInfo,
|
|
12754
15614
|
validated,
|
|
12755
15615
|
span,
|
|
@@ -12762,12 +15622,14 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12762
15622
|
execInfo.status = "failed";
|
|
12763
15623
|
execInfo.completedAt = Date.now();
|
|
12764
15624
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
12765
|
-
execInfo.error =
|
|
15625
|
+
execInfo.error = executionErrorProjection(wfCtx, err).message;
|
|
12766
15626
|
this.persistExecution(execInfo);
|
|
12767
15627
|
}
|
|
12768
15628
|
this.abortControllers.delete(executionId);
|
|
12769
15629
|
cleanupAbortForwarder();
|
|
12770
|
-
|
|
15630
|
+
const originalError = err instanceof Error ? err : new Error(String(err));
|
|
15631
|
+
const projection = executionErrorProjection(wfCtx, err);
|
|
15632
|
+
axlStream._error(originalError, executionId, projection.message, projection.name);
|
|
12771
15633
|
});
|
|
12772
15634
|
return axlStream;
|
|
12773
15635
|
}
|
|
@@ -13178,7 +16040,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
13178
16040
|
},
|
|
13179
16041
|
{
|
|
13180
16042
|
role: "user",
|
|
13181
|
-
content: messages.map((m) => `${m.role}: ${m.content}`).join("\n")
|
|
16043
|
+
content: messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n")
|
|
13182
16044
|
}
|
|
13183
16045
|
],
|
|
13184
16046
|
{ model, maxTokens: 1024 }
|
|
@@ -13625,7 +16487,7 @@ async function registerConditions(conditions) {
|
|
|
13625
16487
|
].join("\n");
|
|
13626
16488
|
nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);
|
|
13627
16489
|
} catch {
|
|
13628
|
-
console.warn("Warning:
|
|
16490
|
+
console.warn("Warning: this Node.js runtime does not support --conditions");
|
|
13629
16491
|
}
|
|
13630
16492
|
}
|
|
13631
16493
|
|
|
@@ -13934,7 +16796,11 @@ var RedisStore = class _RedisStore {
|
|
|
13934
16796
|
async saveSession(sessionId, history) {
|
|
13935
16797
|
const ttl = this.ttlFor("session");
|
|
13936
16798
|
const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
|
|
13937
|
-
await this.client.multi().set(
|
|
16799
|
+
await this.client.multi().set(
|
|
16800
|
+
this.sessionKey(sessionId),
|
|
16801
|
+
JSON.stringify(normalizePersistedSessionHistory(history)),
|
|
16802
|
+
setOptions
|
|
16803
|
+
).sAdd(this.sessionIdsKey(), sessionId).exec();
|
|
13938
16804
|
}
|
|
13939
16805
|
async getSession(sessionId) {
|
|
13940
16806
|
const raw = await this.client.get(this.sessionKey(sessionId));
|
|
@@ -14423,10 +17289,15 @@ function cosineSimilarity2(a, b) {
|
|
|
14423
17289
|
EventStreamOverflowError,
|
|
14424
17290
|
GROQ_PROFILE,
|
|
14425
17291
|
GeminiProvider,
|
|
17292
|
+
GeminiTranscriptionProvider,
|
|
14426
17293
|
GuardrailError,
|
|
14427
17294
|
InMemoryVectorStore,
|
|
17295
|
+
InvalidModelInputError,
|
|
17296
|
+
InvalidTranscriptionInputError,
|
|
14428
17297
|
LLAMACPP_PROFILE,
|
|
14429
17298
|
LMSTUDIO_PROFILE,
|
|
17299
|
+
MAX_INLINE_MODEL_INPUT_BYTES,
|
|
17300
|
+
MAX_INLINE_TRANSCRIPTION_BYTES,
|
|
14430
17301
|
MISTRAL_PROFILE,
|
|
14431
17302
|
MaxTurnsError,
|
|
14432
17303
|
McpManager,
|
|
@@ -14442,6 +17313,8 @@ function cosineSimilarity2(a, b) {
|
|
|
14442
17313
|
OpenAIEmbedder,
|
|
14443
17314
|
OpenAIProvider,
|
|
14444
17315
|
OpenAIResponsesProvider,
|
|
17316
|
+
OpenAITranscriptionProvider,
|
|
17317
|
+
OpenRouterTranscriptionProvider,
|
|
14445
17318
|
ProviderError,
|
|
14446
17319
|
ProviderRegistry,
|
|
14447
17320
|
QuorumNotMet,
|
|
@@ -14457,7 +17330,11 @@ function cosineSimilarity2(a, b) {
|
|
|
14457
17330
|
TimeoutError,
|
|
14458
17331
|
ToolFailure,
|
|
14459
17332
|
ToolModelOutputError,
|
|
17333
|
+
TranscriptionOperationError,
|
|
17334
|
+
TranscriptionProviderRegistry,
|
|
14460
17335
|
UnsupportedEventSchemaVersionError,
|
|
17336
|
+
UnsupportedModelInputError,
|
|
17337
|
+
UnsupportedTranscriptionInputError,
|
|
14461
17338
|
VLLM_PROFILE,
|
|
14462
17339
|
ValidationError,
|
|
14463
17340
|
VerifyError,
|
|
@@ -14474,8 +17351,10 @@ function cosineSimilarity2(a, b) {
|
|
|
14474
17351
|
findConfig,
|
|
14475
17352
|
getEventSchemaVersion,
|
|
14476
17353
|
getExecutionEventSchemaVersion,
|
|
17354
|
+
hasPositiveBillableWork,
|
|
14477
17355
|
hasPositiveTokens,
|
|
14478
17356
|
importModule,
|
|
17357
|
+
inputText,
|
|
14479
17358
|
isCostBearingLeaf,
|
|
14480
17359
|
isRetryableStatus,
|
|
14481
17360
|
isRootLevel,
|