@axlsdk/axl 0.21.1 → 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 +2791 -168
- 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 +2833 -223
- 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;
|
|
@@ -1195,6 +1277,38 @@ function unsafeTransportError(surface, remediation, exposedData) {
|
|
|
1195
1277
|
}
|
|
1196
1278
|
|
|
1197
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
|
+
}
|
|
1198
1312
|
function resolvePerModel(value, model, fallback) {
|
|
1199
1313
|
if (value === void 0) return fallback;
|
|
1200
1314
|
return typeof value === "function" ? value(model) : value;
|
|
@@ -1310,6 +1424,41 @@ function normalizeMessageContent(content) {
|
|
|
1310
1424
|
}
|
|
1311
1425
|
var OpenAICompatibleProvider = class {
|
|
1312
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
|
+
}
|
|
1313
1462
|
/** `json_schema` is honored natively when the profile's `supportsJsonSchema`
|
|
1314
1463
|
* capability is true for this model (the default); otherwise the engine
|
|
1315
1464
|
* downgrades the request to plain `json_object` (see `buildRequestBody`),
|
|
@@ -1483,7 +1632,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1483
1632
|
formatMessage(msg, model) {
|
|
1484
1633
|
const out = {
|
|
1485
1634
|
role: this.profile.roleFor ? this.profile.roleFor(msg.role, model) : msg.role,
|
|
1486
|
-
content: msg.content
|
|
1635
|
+
content: Array.isArray(msg.content) && this.name === "openrouter" && model === OPENROUTER_IMAGE_MODEL ? openRouterImageParts(msg.content) : msg.content
|
|
1487
1636
|
};
|
|
1488
1637
|
if (msg.name && (this.profile.capabilities?.emitsMessageName ?? true)) out.name = msg.name;
|
|
1489
1638
|
if (msg.tool_calls) out.tool_calls = msg.tool_calls;
|
|
@@ -2230,8 +2379,88 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
|
|
|
2230
2379
|
};
|
|
2231
2380
|
|
|
2232
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
|
+
}
|
|
2233
2424
|
var OpenAIResponsesProvider = class {
|
|
2234
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
|
+
}
|
|
2235
2464
|
/** The Responses API honors `json_schema` (structured outputs) natively. */
|
|
2236
2465
|
nativeStructuredOutputSupport() {
|
|
2237
2466
|
return "schema";
|
|
@@ -2396,11 +2625,12 @@ var OpenAIResponsesProvider = class {
|
|
|
2396
2625
|
buildInput(messages) {
|
|
2397
2626
|
const input = [];
|
|
2398
2627
|
for (const msg of messages) {
|
|
2628
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
2399
2629
|
if (msg.role === "tool") {
|
|
2400
2630
|
input.push({
|
|
2401
2631
|
type: "function_call_output",
|
|
2402
2632
|
call_id: msg.tool_call_id ?? "",
|
|
2403
|
-
output:
|
|
2633
|
+
output: text
|
|
2404
2634
|
});
|
|
2405
2635
|
} else if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
2406
2636
|
const reasoningItems = msg.providerMetadata?.openaiReasoningItems;
|
|
@@ -2409,8 +2639,8 @@ var OpenAIResponsesProvider = class {
|
|
|
2409
2639
|
input.push(item);
|
|
2410
2640
|
}
|
|
2411
2641
|
}
|
|
2412
|
-
if (
|
|
2413
|
-
input.push({ type: "message", role: "assistant", content:
|
|
2642
|
+
if (text) {
|
|
2643
|
+
input.push({ type: "message", role: "assistant", content: text });
|
|
2414
2644
|
}
|
|
2415
2645
|
for (const tc of msg.tool_calls) {
|
|
2416
2646
|
input.push({
|
|
@@ -2430,7 +2660,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2430
2660
|
input.push({
|
|
2431
2661
|
type: "message",
|
|
2432
2662
|
role: msg.role,
|
|
2433
|
-
content: msg.content
|
|
2663
|
+
content: typeof msg.content === "string" ? msg.content : responseImageParts(msg.content)
|
|
2434
2664
|
});
|
|
2435
2665
|
}
|
|
2436
2666
|
}
|
|
@@ -2497,7 +2727,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2497
2727
|
cached_tokens: json.usage.input_tokens_details?.cached_tokens,
|
|
2498
2728
|
cache_write_tokens: json.usage.input_tokens_details?.cache_write_tokens
|
|
2499
2729
|
} : void 0;
|
|
2500
|
-
const cost = usage ? estimateDirectOpenAICost(json.model ?? model, usage, {
|
|
2730
|
+
const cost = usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(json.model ?? model, usage, {
|
|
2501
2731
|
baseUrl: this.baseUrl,
|
|
2502
2732
|
request,
|
|
2503
2733
|
response: json
|
|
@@ -2512,6 +2742,16 @@ var OpenAIResponsesProvider = class {
|
|
|
2512
2742
|
providerMetadata
|
|
2513
2743
|
};
|
|
2514
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
|
+
}
|
|
2515
2755
|
// ---------------------------------------------------------------------------
|
|
2516
2756
|
// Internal: parse SSE stream
|
|
2517
2757
|
// ---------------------------------------------------------------------------
|
|
@@ -2599,7 +2839,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2599
2839
|
return {
|
|
2600
2840
|
type: "done",
|
|
2601
2841
|
usage,
|
|
2602
|
-
cost: usage ? estimateDirectOpenAICost(response?.model ?? model, usage, {
|
|
2842
|
+
cost: usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(response?.model ?? model, usage, {
|
|
2603
2843
|
baseUrl: this.baseUrl,
|
|
2604
2844
|
request,
|
|
2605
2845
|
response
|
|
@@ -2636,7 +2876,51 @@ var OpenAIResponsesProvider = class {
|
|
|
2636
2876
|
};
|
|
2637
2877
|
|
|
2638
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
|
+
}
|
|
2639
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
|
+
}
|
|
2640
2924
|
var CLAUDE_CAPABILITIES = {
|
|
2641
2925
|
"claude-fable-5": {
|
|
2642
2926
|
thinking: "adaptive-always-on",
|
|
@@ -2897,6 +3181,11 @@ function isEffortAtOrBelow(effort, ceiling) {
|
|
|
2897
3181
|
function pricingContextFromBody(body) {
|
|
2898
3182
|
return {
|
|
2899
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
|
+
),
|
|
2900
3189
|
unpricedModifier: isAnthropicModifier("inference_geo", body.inference_geo) || isAnthropicModifier("speed", body.speed) || hasAnthropicServerTool(body.tools) || body.fallbacks !== void 0
|
|
2901
3190
|
};
|
|
2902
3191
|
}
|
|
@@ -2991,6 +3280,44 @@ function mergeAnthropicUsage(initial, update) {
|
|
|
2991
3280
|
}
|
|
2992
3281
|
var AnthropicProvider = class {
|
|
2993
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
|
+
}
|
|
2994
3321
|
/** Anthropic ignores native `json_schema` structurally — Axl uses a system
|
|
2995
3322
|
* prompt JSON instruction + client-side Zod validation (see buildRequest). */
|
|
2996
3323
|
nativeStructuredOutputSupport() {
|
|
@@ -3028,7 +3355,10 @@ var AnthropicProvider = class {
|
|
|
3028
3355
|
// chat - non-streaming completion
|
|
3029
3356
|
// ---------------------------------------------------------------------------
|
|
3030
3357
|
async chat(messages, options) {
|
|
3031
|
-
const headers = this.buildHeaders(
|
|
3358
|
+
const headers = this.buildHeaders(
|
|
3359
|
+
await this.resolveKey(),
|
|
3360
|
+
hasAnthropicProviderFileImage(messages)
|
|
3361
|
+
);
|
|
3032
3362
|
const body = this.buildRequestBody(messages, options, false);
|
|
3033
3363
|
const pricingContext = pricingContextFromBody(body);
|
|
3034
3364
|
const res = await fetchWithRetry(
|
|
@@ -3059,7 +3389,10 @@ var AnthropicProvider = class {
|
|
|
3059
3389
|
// stream - SSE streaming completion
|
|
3060
3390
|
// ---------------------------------------------------------------------------
|
|
3061
3391
|
async *stream(messages, options) {
|
|
3062
|
-
const headers = this.buildHeaders(
|
|
3392
|
+
const headers = this.buildHeaders(
|
|
3393
|
+
await this.resolveKey(),
|
|
3394
|
+
hasAnthropicProviderFileImage(messages)
|
|
3395
|
+
);
|
|
3063
3396
|
const body = this.buildRequestBody(messages, options, true);
|
|
3064
3397
|
const pricingContext = pricingContextFromBody(body);
|
|
3065
3398
|
const res = await fetchWithRetry(
|
|
@@ -3091,11 +3424,12 @@ var AnthropicProvider = class {
|
|
|
3091
3424
|
// ---------------------------------------------------------------------------
|
|
3092
3425
|
// Internal: request building
|
|
3093
3426
|
// ---------------------------------------------------------------------------
|
|
3094
|
-
buildHeaders(apiKey) {
|
|
3427
|
+
buildHeaders(apiKey, requiresFilesBeta = false) {
|
|
3095
3428
|
return {
|
|
3096
3429
|
"Content-Type": "application/json",
|
|
3097
3430
|
"x-api-key": apiKey,
|
|
3098
|
-
"anthropic-version": ANTHROPIC_API_VERSION
|
|
3431
|
+
"anthropic-version": ANTHROPIC_API_VERSION,
|
|
3432
|
+
...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
|
|
3099
3433
|
};
|
|
3100
3434
|
}
|
|
3101
3435
|
/** Extract a human-readable message from an API error response body. */
|
|
@@ -3165,12 +3499,13 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3165
3499
|
mapMessages(messages) {
|
|
3166
3500
|
const result = [];
|
|
3167
3501
|
for (const msg of messages) {
|
|
3502
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
3168
3503
|
if (msg.role === "assistant") {
|
|
3169
3504
|
const replayedThinking = this.getReplayedThinkingBlocks(msg);
|
|
3170
3505
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
3171
3506
|
const content = [...replayedThinking];
|
|
3172
|
-
if (
|
|
3173
|
-
content.push({ type: "text", text
|
|
3507
|
+
if (text) {
|
|
3508
|
+
content.push({ type: "text", text });
|
|
3174
3509
|
}
|
|
3175
3510
|
for (const tc of msg.tool_calls) {
|
|
3176
3511
|
let parsedArgs;
|
|
@@ -3189,10 +3524,10 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3189
3524
|
result.push({ role: "assistant", content });
|
|
3190
3525
|
} else if (replayedThinking.length > 0) {
|
|
3191
3526
|
const content = [...replayedThinking];
|
|
3192
|
-
if (
|
|
3527
|
+
if (text) content.push({ type: "text", text });
|
|
3193
3528
|
result.push({ role: "assistant", content });
|
|
3194
3529
|
} else {
|
|
3195
|
-
result.push({ role: "assistant", content:
|
|
3530
|
+
result.push({ role: "assistant", content: text });
|
|
3196
3531
|
}
|
|
3197
3532
|
} else if (msg.role === "tool") {
|
|
3198
3533
|
result.push({
|
|
@@ -3201,12 +3536,15 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3201
3536
|
{
|
|
3202
3537
|
type: "tool_result",
|
|
3203
3538
|
tool_use_id: msg.tool_call_id,
|
|
3204
|
-
content:
|
|
3539
|
+
content: text
|
|
3205
3540
|
}
|
|
3206
3541
|
]
|
|
3207
3542
|
});
|
|
3208
3543
|
} else if (msg.role === "user") {
|
|
3209
|
-
result.push({
|
|
3544
|
+
result.push({
|
|
3545
|
+
role: "user",
|
|
3546
|
+
content: typeof msg.content === "string" ? msg.content : anthropicImageBlocks(msg.content)
|
|
3547
|
+
});
|
|
3210
3548
|
}
|
|
3211
3549
|
}
|
|
3212
3550
|
return this.mergeConsecutiveRoles(result);
|
|
@@ -3311,7 +3649,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3311
3649
|
"Anthropic fallback responses cannot be continued safely; retry without fallbacks."
|
|
3312
3650
|
);
|
|
3313
3651
|
}
|
|
3314
|
-
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;
|
|
3315
3653
|
return {
|
|
3316
3654
|
content,
|
|
3317
3655
|
thinking_content: thinkingContent || void 0,
|
|
@@ -3348,7 +3686,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3348
3686
|
const doneChunk = () => ({
|
|
3349
3687
|
type: "done",
|
|
3350
3688
|
usage,
|
|
3351
|
-
cost: pricingUsage && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
|
|
3689
|
+
cost: pricingUsage && !pricingContext.hasRichInput && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
|
|
3352
3690
|
providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
|
|
3353
3691
|
});
|
|
3354
3692
|
try {
|
|
@@ -3496,6 +3834,60 @@ function lastFallbackIndex(blocks) {
|
|
|
3496
3834
|
}
|
|
3497
3835
|
|
|
3498
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
|
+
}
|
|
3499
3891
|
var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
3500
3892
|
"additionalProperties",
|
|
3501
3893
|
"$schema",
|
|
@@ -3567,6 +3959,7 @@ var GEMINI_PRICING = {
|
|
|
3567
3959
|
"gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
3568
3960
|
"gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
|
|
3569
3961
|
};
|
|
3962
|
+
var GEMINI_INTERACTIONS_IMAGE_MODELS = /* @__PURE__ */ new Set(["gemini-3.7-flash"]);
|
|
3570
3963
|
function isValidTokenCount2(value) {
|
|
3571
3964
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3572
3965
|
}
|
|
@@ -3654,7 +4047,10 @@ var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
|
|
|
3654
4047
|
["gemini-3.1-flash-lite-preview", "minimal"],
|
|
3655
4048
|
["gemini-3.5-flash", "minimal"],
|
|
3656
4049
|
["gemini-3.5-flash-lite", "minimal"],
|
|
3657
|
-
["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"]
|
|
3658
4054
|
]);
|
|
3659
4055
|
var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
|
|
3660
4056
|
"gemini-3.5-flash-lite",
|
|
@@ -3681,6 +4077,61 @@ function warnGemini3xEffortNone(model) {
|
|
|
3681
4077
|
}
|
|
3682
4078
|
var GeminiProvider = class {
|
|
3683
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
|
+
}
|
|
3684
4135
|
/** Gemini accepts a `responseSchema` but `sanitizeSchemaForGemini` strips
|
|
3685
4136
|
* keywords it doesn't support (`$ref`/`$defs`/`additionalProperties`/…), so a
|
|
3686
4137
|
* derived schema can lose constraints — lossy, not faithful. */
|
|
@@ -3719,6 +4170,7 @@ var GeminiProvider = class {
|
|
|
3719
4170
|
// chat - non-streaming completion
|
|
3720
4171
|
// ---------------------------------------------------------------------------
|
|
3721
4172
|
async chat(messages, options) {
|
|
4173
|
+
if (hasRichGeminiMessages(messages)) return this.chatInteraction(messages, options);
|
|
3722
4174
|
const body = this.buildRequestBody(messages, options);
|
|
3723
4175
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3724
4176
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
@@ -3751,6 +4203,10 @@ var GeminiProvider = class {
|
|
|
3751
4203
|
// stream - SSE streaming completion
|
|
3752
4204
|
// ---------------------------------------------------------------------------
|
|
3753
4205
|
async *stream(messages, options) {
|
|
4206
|
+
if (hasRichGeminiMessages(messages)) {
|
|
4207
|
+
yield* this.streamInteraction(messages, options);
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
3754
4210
|
const body = this.buildRequestBody(messages, options);
|
|
3755
4211
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3756
4212
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
@@ -3782,6 +4238,339 @@ var GeminiProvider = class {
|
|
|
3782
4238
|
yield* this.parseSSEStream(res.body, pricingContext);
|
|
3783
4239
|
}
|
|
3784
4240
|
// ---------------------------------------------------------------------------
|
|
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.
|
|
4244
|
+
// ---------------------------------------------------------------------------
|
|
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
|
|
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;
|
|
4354
|
+
}
|
|
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
|
+
});
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
return steps;
|
|
4413
|
+
}
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
3785
4574
|
// Internal: request building
|
|
3786
4575
|
// ---------------------------------------------------------------------------
|
|
3787
4576
|
buildHeaders(apiKey) {
|
|
@@ -4036,14 +4825,15 @@ var GeminiProvider = class {
|
|
|
4036
4825
|
}
|
|
4037
4826
|
const result = [];
|
|
4038
4827
|
for (const msg of messages) {
|
|
4828
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
4039
4829
|
if (msg.role === "assistant") {
|
|
4040
4830
|
const rawParts = msg.providerMetadata?.geminiParts;
|
|
4041
4831
|
if (rawParts && rawParts.length > 0) {
|
|
4042
4832
|
result.push({ role: "model", parts: rawParts });
|
|
4043
4833
|
} else {
|
|
4044
4834
|
const parts = [];
|
|
4045
|
-
if (
|
|
4046
|
-
parts.push({ text
|
|
4835
|
+
if (text) {
|
|
4836
|
+
parts.push({ text });
|
|
4047
4837
|
}
|
|
4048
4838
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
4049
4839
|
for (const tc of msg.tool_calls) {
|
|
@@ -4069,7 +4859,7 @@ var GeminiProvider = class {
|
|
|
4069
4859
|
const functionName = (msg.tool_call_id ? geminiNativeNames.get(msg.tool_call_id) : void 0) ?? toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
|
|
4070
4860
|
const functionResponse = {
|
|
4071
4861
|
name: functionName,
|
|
4072
|
-
response: parseGeminiFunctionResponse(
|
|
4862
|
+
response: parseGeminiFunctionResponse(text)
|
|
4073
4863
|
};
|
|
4074
4864
|
if (msg.tool_call_id && geminiNativeIds.has(msg.tool_call_id)) {
|
|
4075
4865
|
functionResponse.id = msg.tool_call_id;
|
|
@@ -4079,7 +4869,7 @@ var GeminiProvider = class {
|
|
|
4079
4869
|
parts: [{ functionResponse }]
|
|
4080
4870
|
});
|
|
4081
4871
|
} else if (msg.role === "user") {
|
|
4082
|
-
result.push({ role: "user", parts: [{ text
|
|
4872
|
+
result.push({ role: "user", parts: [{ text }] });
|
|
4083
4873
|
}
|
|
4084
4874
|
}
|
|
4085
4875
|
return this.mergeConsecutiveRoles(result);
|
|
@@ -4267,6 +5057,56 @@ var GeminiProvider = class {
|
|
|
4267
5057
|
}
|
|
4268
5058
|
}
|
|
4269
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
|
+
}
|
|
4270
5110
|
|
|
4271
5111
|
// src/providers/profiles/openrouter.ts
|
|
4272
5112
|
var OPENROUTER_PROFILE = {
|
|
@@ -4698,68 +5538,1220 @@ var ProviderRegistry = class {
|
|
|
4698
5538
|
if (cached) return cached;
|
|
4699
5539
|
const factory = this.factories.get(name);
|
|
4700
5540
|
if (factory) {
|
|
4701
|
-
const instance = factory(config);
|
|
4702
|
-
this.instances.set(name, instance);
|
|
4703
|
-
return instance;
|
|
4704
|
-
}
|
|
4705
|
-
if (this.fallbackInstance) {
|
|
4706
|
-
return this.fallbackInstance;
|
|
4707
|
-
}
|
|
4708
|
-
throw new Error(`Unknown provider "${name}". Registered providers: ${this.list().join(", ")}`);
|
|
5541
|
+
const instance = factory(config);
|
|
5542
|
+
this.instances.set(name, instance);
|
|
5543
|
+
return instance;
|
|
5544
|
+
}
|
|
5545
|
+
if (this.fallbackInstance) {
|
|
5546
|
+
return this.fallbackInstance;
|
|
5547
|
+
}
|
|
5548
|
+
throw new Error(`Unknown provider "${name}". Registered providers: ${this.list().join(", ")}`);
|
|
5549
|
+
}
|
|
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);
|
|
6562
|
+
}
|
|
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
|
+
});
|
|
4709
6577
|
}
|
|
4710
|
-
/**
|
|
4711
|
-
* Resolve a "provider:model" URI string into a Provider instance and model name.
|
|
4712
|
-
*
|
|
4713
|
-
* Supported formats:
|
|
4714
|
-
* - "openai:gpt-4o" -> provider=openai, model=gpt-4o
|
|
4715
|
-
* - "anthropic:claude-3" -> provider=anthropic, model=claude-3
|
|
4716
|
-
* - "gpt-4o" -> uses defaultProvider from config, model=gpt-4o
|
|
4717
|
-
* - undefined / empty -> uses defaultProvider and defaultModel from config
|
|
4718
|
-
*
|
|
4719
|
-
* @param uri Provider:model string, or just a model name
|
|
4720
|
-
* @param config Axl configuration for provider options and defaults
|
|
4721
|
-
*/
|
|
4722
6578
|
resolve(uri, config = {}) {
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
const providerName2 = config.defaultProvider ?? "openai";
|
|
4731
|
-
return { provider: this.get(providerName2, config), model: uri };
|
|
4732
|
-
}
|
|
4733
|
-
const providerName = uri.slice(0, colonIndex);
|
|
4734
|
-
const model = uri.slice(colonIndex + 1);
|
|
4735
|
-
if (!providerName || !model) {
|
|
4736
|
-
throw new Error(
|
|
4737
|
-
`Invalid provider URI "${uri}". Expected format: "provider:model" (e.g. "openai:gpt-4o")`
|
|
4738
|
-
);
|
|
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
|
+
});
|
|
4739
6586
|
}
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
* Clear all cached provider instances. Useful for testing or reconfiguration.
|
|
4744
|
-
*/
|
|
4745
|
-
clearCache() {
|
|
4746
|
-
this.instances.clear();
|
|
4747
|
-
}
|
|
4748
|
-
/**
|
|
4749
|
-
* Clear all registered factories (including built-ins).
|
|
4750
|
-
* Useful for test runtimes where only explicitly registered instances should be used.
|
|
4751
|
-
*/
|
|
4752
|
-
clearFactories() {
|
|
4753
|
-
this.factories.clear();
|
|
6587
|
+
const providerName = uri.slice(0, colon);
|
|
6588
|
+
const model = uri.slice(colon + 1);
|
|
6589
|
+
return { provider: this.get(providerName, config), model, providerName };
|
|
4754
6590
|
}
|
|
4755
6591
|
};
|
|
4756
|
-
var defaultRegistry = new ProviderRegistry();
|
|
4757
6592
|
|
|
4758
6593
|
// src/context.ts
|
|
4759
6594
|
var import_node_async_hooks = require("async_hooks");
|
|
4760
6595
|
var import_node_crypto = require("crypto");
|
|
4761
6596
|
var import_zod2 = require("zod");
|
|
4762
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
|
+
|
|
4763
6755
|
// src/partial-json.ts
|
|
4764
6756
|
var MAX_DEPTH = 256;
|
|
4765
6757
|
function parsePartialJson(input) {
|
|
@@ -5331,7 +7323,8 @@ var COST_BEARING_LEAF_TYPES = [
|
|
|
5331
7323
|
"agent_call_end",
|
|
5332
7324
|
"tool_call_end",
|
|
5333
7325
|
"memory_remember",
|
|
5334
|
-
"memory_recall"
|
|
7326
|
+
"memory_recall",
|
|
7327
|
+
"transcription_end"
|
|
5335
7328
|
];
|
|
5336
7329
|
var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
5337
7330
|
function hasPositiveTokens(event) {
|
|
@@ -5339,6 +7332,12 @@ function hasPositiveTokens(event) {
|
|
|
5339
7332
|
if (!t) return false;
|
|
5340
7333
|
return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) + (t.cached ?? 0) + (t.cacheWrite ?? 0) > 0;
|
|
5341
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
|
+
}
|
|
5342
7341
|
function isUsableCost(cost) {
|
|
5343
7342
|
return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
|
|
5344
7343
|
}
|
|
@@ -5351,7 +7350,7 @@ function isCostBearingLeaf(event) {
|
|
|
5351
7350
|
return COST_LEAF_SET.has(event.type);
|
|
5352
7351
|
}
|
|
5353
7352
|
function isUnpricedLeaf(event) {
|
|
5354
|
-
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);
|
|
5355
7354
|
}
|
|
5356
7355
|
function isRootLevel(event) {
|
|
5357
7356
|
const d = event.depth;
|
|
@@ -5366,6 +7365,9 @@ var AXL_EVENT_TYPES = [
|
|
|
5366
7365
|
// Workflow lifecycle
|
|
5367
7366
|
"workflow_start",
|
|
5368
7367
|
"workflow_end",
|
|
7368
|
+
// Transcription operation lifecycle (separate from chat/agent turns)
|
|
7369
|
+
"transcription_start",
|
|
7370
|
+
"transcription_end",
|
|
5369
7371
|
// Ask boundary
|
|
5370
7372
|
"ask_start",
|
|
5371
7373
|
"ask_end",
|
|
@@ -5456,6 +7458,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
5456
7458
|
"schema_diagnostic",
|
|
5457
7459
|
"workflow_start",
|
|
5458
7460
|
"workflow_end",
|
|
7461
|
+
"transcription_start",
|
|
7462
|
+
"transcription_end",
|
|
5459
7463
|
"checkpoint_save",
|
|
5460
7464
|
"checkpoint_replay",
|
|
5461
7465
|
"await_human",
|
|
@@ -6271,6 +8275,21 @@ function redactGate(event) {
|
|
|
6271
8275
|
}
|
|
6272
8276
|
};
|
|
6273
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
|
+
}
|
|
6274
8293
|
function redactMemory(event) {
|
|
6275
8294
|
const d = event.data;
|
|
6276
8295
|
if (!d || typeof d !== "object") return event;
|
|
@@ -6294,7 +8313,11 @@ var LEGACY_REDACTION_RULES = {
|
|
|
6294
8313
|
}
|
|
6295
8314
|
};
|
|
6296
8315
|
},
|
|
6297
|
-
ask_start: (e) => ({
|
|
8316
|
+
ask_start: (e) => ({
|
|
8317
|
+
...e,
|
|
8318
|
+
prompt: REDACTED,
|
|
8319
|
+
...e.input ? { input: redactInputDescriptor(e.input) } : {}
|
|
8320
|
+
}),
|
|
6298
8321
|
ask_end: (e) => ({
|
|
6299
8322
|
...e,
|
|
6300
8323
|
outcome: e.outcome.ok ? { ok: true, result: REDACTED } : { ok: false, error: REDACTED }
|
|
@@ -6302,6 +8325,14 @@ var LEGACY_REDACTION_RULES = {
|
|
|
6302
8325
|
agent_call_start: (e) => {
|
|
6303
8326
|
const d = e.data;
|
|
6304
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
|
+
}));
|
|
6305
8336
|
if (d.system !== void 0) out.system = REDACTED;
|
|
6306
8337
|
if (Array.isArray(d.messages)) {
|
|
6307
8338
|
out.messages = [{ role: "system", content: `[${d.messages.length} messages redacted]` }];
|
|
@@ -6457,6 +8488,18 @@ function reuseLegacyRule(type) {
|
|
|
6457
8488
|
var COMMON_V2_REDACTION_RULES = {
|
|
6458
8489
|
workflow_start: reuseLegacyRule("workflow_start"),
|
|
6459
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
|
+
},
|
|
6460
8503
|
ask_start: reuseLegacyRule("ask_start"),
|
|
6461
8504
|
ask_end: reuseLegacyRule("ask_end"),
|
|
6462
8505
|
agent_call_start: reuseLegacyRule("agent_call_start"),
|
|
@@ -6651,7 +8694,7 @@ function resolveConfig(config) {
|
|
|
6651
8694
|
|
|
6652
8695
|
// src/tool-model-output.ts
|
|
6653
8696
|
var import_node_util = require("util");
|
|
6654
|
-
function
|
|
8697
|
+
function invalid2(path, reason) {
|
|
6655
8698
|
return new Error(`Invalid tool model output at ${path}: ${reason}`);
|
|
6656
8699
|
}
|
|
6657
8700
|
function propertyPath(parent, key) {
|
|
@@ -6669,14 +8712,14 @@ function propertyPath(parent, key) {
|
|
|
6669
8712
|
function normalize(value, path, stack) {
|
|
6670
8713
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
6671
8714
|
if (typeof value === "number") {
|
|
6672
|
-
if (!Number.isFinite(value)) throw
|
|
8715
|
+
if (!Number.isFinite(value)) throw invalid2(path, "number must be finite");
|
|
6673
8716
|
return value;
|
|
6674
8717
|
}
|
|
6675
|
-
if (value === void 0) throw
|
|
8718
|
+
if (value === void 0) throw invalid2(path, "undefined is only allowed on object properties");
|
|
6676
8719
|
if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
|
|
6677
|
-
throw
|
|
8720
|
+
throw invalid2(path, `unsupported ${typeof value}`);
|
|
6678
8721
|
}
|
|
6679
|
-
if (typeof value !== "object") throw
|
|
8722
|
+
if (typeof value !== "object") throw invalid2(path, `unsupported ${typeof value}`);
|
|
6680
8723
|
let prototype;
|
|
6681
8724
|
let descriptors;
|
|
6682
8725
|
try {
|
|
@@ -6684,38 +8727,38 @@ function normalize(value, path, stack) {
|
|
|
6684
8727
|
descriptors = Object.getOwnPropertyDescriptors(value);
|
|
6685
8728
|
Object.setPrototypeOf(descriptors, null);
|
|
6686
8729
|
} catch (cause) {
|
|
6687
|
-
throw
|
|
8730
|
+
throw invalid2(
|
|
6688
8731
|
path,
|
|
6689
8732
|
`object inspection failed (${cause instanceof Error ? cause.name : "error"})`
|
|
6690
8733
|
);
|
|
6691
8734
|
}
|
|
6692
|
-
if (stack.has(value)) throw
|
|
8735
|
+
if (stack.has(value)) throw invalid2(path, "cyclic reference");
|
|
6693
8736
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
6694
8737
|
const descriptor = descriptors[key];
|
|
6695
8738
|
if (descriptor && !("value" in descriptor)) {
|
|
6696
|
-
throw
|
|
8739
|
+
throw invalid2(
|
|
6697
8740
|
typeof key === "string" ? propertyPath(path, key) : `${path}[symbol]`,
|
|
6698
8741
|
"accessor properties are not supported"
|
|
6699
8742
|
);
|
|
6700
8743
|
}
|
|
6701
8744
|
if (typeof key === "symbol") {
|
|
6702
|
-
throw
|
|
8745
|
+
throw invalid2(`${path}[symbol]`, "symbol keys are not supported");
|
|
6703
8746
|
}
|
|
6704
8747
|
}
|
|
6705
8748
|
if (typeof descriptors.toJSON?.value === "function") {
|
|
6706
|
-
throw
|
|
8749
|
+
throw invalid2(propertyPath(path, "toJSON"), "custom toJSON is not supported");
|
|
6707
8750
|
}
|
|
6708
8751
|
const array = Array.isArray(value);
|
|
6709
8752
|
if (array && prototype !== Array.prototype && prototype !== null) {
|
|
6710
|
-
throw
|
|
8753
|
+
throw invalid2(path, "array subclasses are not supported");
|
|
6711
8754
|
}
|
|
6712
8755
|
const inheritedToJSON = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "toJSON") : void 0;
|
|
6713
8756
|
if (typeof inheritedToJSON?.value === "function") {
|
|
6714
|
-
throw
|
|
8757
|
+
throw invalid2(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
|
|
6715
8758
|
}
|
|
6716
8759
|
const inheritedThen = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "then") : void 0;
|
|
6717
8760
|
if (typeof descriptors.then?.value === "function" || inheritedThen !== void 0 && (!("value" in inheritedThen) || typeof inheritedThen.value === "function")) {
|
|
6718
|
-
throw
|
|
8761
|
+
throw invalid2(path, "thenable output is not supported");
|
|
6719
8762
|
}
|
|
6720
8763
|
stack.add(value);
|
|
6721
8764
|
try {
|
|
@@ -6723,15 +8766,15 @@ function normalize(value, path, stack) {
|
|
|
6723
8766
|
const normalized2 = [];
|
|
6724
8767
|
const length = descriptors.length?.value;
|
|
6725
8768
|
if (!Number.isSafeInteger(length) || length < 0) {
|
|
6726
|
-
throw
|
|
8769
|
+
throw invalid2(propertyPath(path, "length"), "array length is invalid");
|
|
6727
8770
|
}
|
|
6728
8771
|
for (let index = 0; index < length; index++) {
|
|
6729
8772
|
const descriptor = descriptors[String(index)];
|
|
6730
8773
|
if (!descriptor || !("value" in descriptor)) {
|
|
6731
|
-
throw
|
|
8774
|
+
throw invalid2(`${path}[${index}]`, "arrays must be dense data properties");
|
|
6732
8775
|
}
|
|
6733
8776
|
if (descriptor.value === void 0) {
|
|
6734
|
-
throw
|
|
8777
|
+
throw invalid2(`${path}[${index}]`, "array values cannot be undefined");
|
|
6735
8778
|
}
|
|
6736
8779
|
normalized2.push(normalize(descriptor.value, `${path}[${index}]`, stack));
|
|
6737
8780
|
}
|
|
@@ -6740,21 +8783,21 @@ function normalize(value, path, stack) {
|
|
|
6740
8783
|
if (!descriptor?.enumerable) continue;
|
|
6741
8784
|
const index = Number(key);
|
|
6742
8785
|
if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
|
|
6743
|
-
throw
|
|
8786
|
+
throw invalid2(propertyPath(path, key), "non-index array properties are not supported");
|
|
6744
8787
|
}
|
|
6745
8788
|
}
|
|
6746
8789
|
Object.setPrototypeOf(normalized2, null);
|
|
6747
8790
|
return normalized2;
|
|
6748
8791
|
}
|
|
6749
8792
|
if (prototype !== Object.prototype && prototype !== null) {
|
|
6750
|
-
throw
|
|
8793
|
+
throw invalid2(path, "only plain objects and null-prototype records are supported");
|
|
6751
8794
|
}
|
|
6752
8795
|
const normalized = /* @__PURE__ */ Object.create(null);
|
|
6753
8796
|
for (const key of Object.keys(descriptors)) {
|
|
6754
8797
|
const descriptor = descriptors[key];
|
|
6755
8798
|
if (!descriptor?.enumerable) continue;
|
|
6756
8799
|
if (!("value" in descriptor)) {
|
|
6757
|
-
throw
|
|
8800
|
+
throw invalid2(propertyPath(path, key), "accessor properties are not supported");
|
|
6758
8801
|
}
|
|
6759
8802
|
if (descriptor.value === void 0) continue;
|
|
6760
8803
|
normalized[key] = normalize(descriptor.value, propertyPath(path, key), stack);
|
|
@@ -7262,44 +9305,44 @@ function resolveBranchDrainTimeoutMs(value) {
|
|
|
7262
9305
|
return resolved;
|
|
7263
9306
|
}
|
|
7264
9307
|
function parseHumanDecision(value) {
|
|
7265
|
-
const
|
|
9308
|
+
const invalid3 = () => {
|
|
7266
9309
|
throw new AxlError(
|
|
7267
9310
|
"INVALID_HUMAN_DECISION",
|
|
7268
9311
|
"Human decision must be a plain object with a boolean approved field and only branch-appropriate string data"
|
|
7269
9312
|
);
|
|
7270
9313
|
};
|
|
7271
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
9314
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) invalid3();
|
|
7272
9315
|
let prototype;
|
|
7273
9316
|
try {
|
|
7274
9317
|
prototype = Object.getPrototypeOf(value);
|
|
7275
9318
|
} catch {
|
|
7276
|
-
return
|
|
9319
|
+
return invalid3();
|
|
7277
9320
|
}
|
|
7278
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
9321
|
+
if (prototype !== Object.prototype && prototype !== null) invalid3();
|
|
7279
9322
|
let ownKeys;
|
|
7280
9323
|
try {
|
|
7281
9324
|
ownKeys = Reflect.ownKeys(value);
|
|
7282
9325
|
} catch {
|
|
7283
|
-
return
|
|
9326
|
+
return invalid3();
|
|
7284
9327
|
}
|
|
7285
9328
|
if (ownKeys.some(
|
|
7286
9329
|
(key) => typeof key !== "string" || key !== "approved" && key !== "data" && key !== "reason"
|
|
7287
9330
|
)) {
|
|
7288
|
-
|
|
9331
|
+
invalid3();
|
|
7289
9332
|
}
|
|
7290
9333
|
const readDataProperty = (key) => {
|
|
7291
9334
|
let descriptor;
|
|
7292
9335
|
try {
|
|
7293
9336
|
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
7294
9337
|
} catch {
|
|
7295
|
-
return
|
|
9338
|
+
return invalid3();
|
|
7296
9339
|
}
|
|
7297
9340
|
if (!descriptor) {
|
|
7298
|
-
if (ownKeys.includes(key))
|
|
9341
|
+
if (ownKeys.includes(key)) invalid3();
|
|
7299
9342
|
return { present: false, value: void 0 };
|
|
7300
9343
|
}
|
|
7301
|
-
if (!ownKeys.includes(key)) return
|
|
7302
|
-
if (!("value" in descriptor)) return
|
|
9344
|
+
if (!ownKeys.includes(key)) return invalid3();
|
|
9345
|
+
if (!("value" in descriptor)) return invalid3();
|
|
7303
9346
|
return { present: true, value: descriptor.value };
|
|
7304
9347
|
};
|
|
7305
9348
|
const approved = readDataProperty("approved");
|
|
@@ -7318,7 +9361,7 @@ function parseHumanDecision(value) {
|
|
|
7318
9361
|
};
|
|
7319
9362
|
}
|
|
7320
9363
|
}
|
|
7321
|
-
return
|
|
9364
|
+
return invalid3();
|
|
7322
9365
|
}
|
|
7323
9366
|
var askStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
7324
9367
|
var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7426,8 +9469,12 @@ function appendRetryMessages(messages, content, feedbackMessage, providerMetadat
|
|
|
7426
9469
|
}
|
|
7427
9470
|
function estimateMessagesTokens(messages) {
|
|
7428
9471
|
let total = 0;
|
|
9472
|
+
let unmeasured = false;
|
|
7429
9473
|
for (const msg of messages) {
|
|
7430
|
-
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
|
+
}
|
|
7431
9478
|
if (msg.tool_calls) {
|
|
7432
9479
|
for (const tc of msg.tool_calls) {
|
|
7433
9480
|
total += estimateTokens(tc.function.name + tc.function.arguments);
|
|
@@ -7435,7 +9482,73 @@ function estimateMessagesTokens(messages) {
|
|
|
7435
9482
|
}
|
|
7436
9483
|
total += 4;
|
|
7437
9484
|
}
|
|
7438
|
-
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
|
+
};
|
|
7439
9552
|
}
|
|
7440
9553
|
var WorkflowContext = class _WorkflowContext {
|
|
7441
9554
|
input;
|
|
@@ -7451,6 +9564,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7451
9564
|
}
|
|
7452
9565
|
config;
|
|
7453
9566
|
providerRegistry;
|
|
9567
|
+
transcriptionProviderRegistry;
|
|
7454
9568
|
sessionHistory;
|
|
7455
9569
|
onTrace;
|
|
7456
9570
|
pendingDecisions;
|
|
@@ -7476,6 +9590,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7476
9590
|
*/
|
|
7477
9591
|
autoCheckpointCounters;
|
|
7478
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;
|
|
7479
9597
|
/** Root-shared idempotency guards for workflow lifecycle emission.
|
|
7480
9598
|
* Both `runtime.execute()` and `runtime.stream()` have paths where a
|
|
7481
9599
|
* post-emit side-effect (checkpoint deletion, state-store persistence)
|
|
@@ -7559,7 +9677,8 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7559
9677
|
get events() {
|
|
7560
9678
|
if (!this._busRef.current) {
|
|
7561
9679
|
this._busRef.current = new AxlEventBus(this._eventStreamOptions);
|
|
7562
|
-
|
|
9680
|
+
const signal = this.currentSignal;
|
|
9681
|
+
if (signal?.aborted) {
|
|
7563
9682
|
this._busRef.current._finish();
|
|
7564
9683
|
}
|
|
7565
9684
|
}
|
|
@@ -7597,6 +9716,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7597
9716
|
this.metadata = init.metadata ?? {};
|
|
7598
9717
|
this.config = init.config;
|
|
7599
9718
|
this.providerRegistry = init.providerRegistry;
|
|
9719
|
+
this.transcriptionProviderRegistry = init.transcriptionProviderRegistry;
|
|
7600
9720
|
this.sessionHistory = init.sessionHistory ?? [];
|
|
7601
9721
|
this.onTrace = init.onTrace;
|
|
7602
9722
|
this.pendingDecisions = init.pendingDecisions;
|
|
@@ -7630,6 +9750,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7630
9750
|
byAgent: /* @__PURE__ */ new Map(),
|
|
7631
9751
|
root: 0
|
|
7632
9752
|
};
|
|
9753
|
+
this.richFailureState = init.richFailureState ?? {
|
|
9754
|
+
associations: [],
|
|
9755
|
+
overflowed: false,
|
|
9756
|
+
indeterminate: false
|
|
9757
|
+
};
|
|
7633
9758
|
const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(init.branchDrainTimeoutMs);
|
|
7634
9759
|
this.branchDrainState = init.branchDrainState ?? {
|
|
7635
9760
|
pending: /* @__PURE__ */ new Set(),
|
|
@@ -7662,6 +9787,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7662
9787
|
executionId: this.executionId,
|
|
7663
9788
|
config: this.config,
|
|
7664
9789
|
providerRegistry: this.providerRegistry,
|
|
9790
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
7665
9791
|
metadata: { ...this.metadata },
|
|
7666
9792
|
// Shared infrastructure
|
|
7667
9793
|
budgetContext: this.budgetContext,
|
|
@@ -7684,6 +9810,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7684
9810
|
autoCheckpointCounters: this.autoCheckpointCounters,
|
|
7685
9811
|
branchDrainState: this.branchDrainState,
|
|
7686
9812
|
workflowLifecycleState: this.workflowLifecycleState,
|
|
9813
|
+
richFailureState: this.richFailureState,
|
|
7687
9814
|
// Share the parent's bus slot (mutable ref). Late allocation in
|
|
7688
9815
|
// either parent or child propagates because both read the same
|
|
7689
9816
|
// slot. Nested-ask events (agent-as-tool pattern) bubble up to
|
|
@@ -7695,6 +9822,52 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7695
9822
|
// Isolated: sessionHistory (empty)
|
|
7696
9823
|
});
|
|
7697
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
|
+
}
|
|
7698
9871
|
/**
|
|
7699
9872
|
* Resolve the current abort signal.
|
|
7700
9873
|
* Branch-scoped signals (from race/spawn/map/budget) in AsyncLocalStorage
|
|
@@ -7896,6 +10069,17 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7896
10069
|
}
|
|
7897
10070
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
7898
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);
|
|
7899
10083
|
const agentName = agent2._name;
|
|
7900
10084
|
return this._checkpoint(
|
|
7901
10085
|
this._autoCheckpointName("ask", agentName),
|
|
@@ -7915,7 +10099,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7915
10099
|
};
|
|
7916
10100
|
return askStorage.run(frame, async () => {
|
|
7917
10101
|
const askStart = Date.now();
|
|
7918
|
-
this.emitEvent({
|
|
10102
|
+
this.emitEvent({
|
|
10103
|
+
type: "ask_start",
|
|
10104
|
+
prompt: promptText,
|
|
10105
|
+
...inputDescriptor ? { input: inputDescriptor } : {}
|
|
10106
|
+
});
|
|
7919
10107
|
const costBefore = this.budgetContext?.totalCost ?? 0;
|
|
7920
10108
|
const unpricedCountBefore = this.budgetContext?.unpricedCount ?? 0;
|
|
7921
10109
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
@@ -7923,10 +10111,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7923
10111
|
const doCall = async () => {
|
|
7924
10112
|
const result2 = await this.executeAgentCall(
|
|
7925
10113
|
agent2,
|
|
7926
|
-
|
|
10114
|
+
normalizedInput,
|
|
7927
10115
|
options,
|
|
7928
10116
|
void 0,
|
|
7929
|
-
usageCapture
|
|
10117
|
+
usageCapture,
|
|
10118
|
+
normalizedHistory
|
|
7930
10119
|
);
|
|
7931
10120
|
return result2;
|
|
7932
10121
|
};
|
|
@@ -7943,6 +10132,9 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7943
10132
|
},
|
|
7944
10133
|
async (span) => {
|
|
7945
10134
|
const r = await doCall();
|
|
10135
|
+
if (usageCapture.modelUri) {
|
|
10136
|
+
span.setAttribute("axl.agent.model", usageCapture.modelUri);
|
|
10137
|
+
}
|
|
7946
10138
|
const costAfter2 = this.budgetContext?.totalCost ?? 0;
|
|
7947
10139
|
span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
|
|
7948
10140
|
span.setAttribute("axl.agent.duration", Date.now() - askStart);
|
|
@@ -7976,9 +10168,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7976
10168
|
try {
|
|
7977
10169
|
this.onAgentCallComplete({
|
|
7978
10170
|
agent: agent2._name,
|
|
7979
|
-
prompt,
|
|
10171
|
+
prompt: promptText,
|
|
10172
|
+
...inputDescriptor ? { input: inputDescriptor } : {},
|
|
7980
10173
|
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
7981
|
-
model: agent2.resolveModel(resolveCtx),
|
|
10174
|
+
model: usageCapture.modelUri ?? agent2.resolveModel(resolveCtx),
|
|
7982
10175
|
cost: costAfter - costBefore,
|
|
7983
10176
|
unpriced: frame.askUnpriced || unpricedCountAfter > unpricedCountBefore,
|
|
7984
10177
|
duration: Date.now() - askStart,
|
|
@@ -8002,9 +10195,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8002
10195
|
} catch (err) {
|
|
8003
10196
|
askFailed = true;
|
|
8004
10197
|
askFailure = err;
|
|
10198
|
+
if (richAsk) this.recordRichFailure(err);
|
|
8005
10199
|
outcome = {
|
|
8006
10200
|
ok: false,
|
|
8007
|
-
error: err
|
|
10201
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
|
|
8008
10202
|
};
|
|
8009
10203
|
}
|
|
8010
10204
|
let terminalFailure;
|
|
@@ -8032,7 +10226,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8032
10226
|
{ agent: agentName }
|
|
8033
10227
|
);
|
|
8034
10228
|
}
|
|
8035
|
-
async executeAgentCall(agent2,
|
|
10229
|
+
async executeAgentCall(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
|
|
8036
10230
|
if (this.budgetContext?.exceeded) {
|
|
8037
10231
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
8038
10232
|
if (policy === "warn") {
|
|
@@ -8049,7 +10243,82 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8049
10243
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
8050
10244
|
const modelUri = agent2.resolveModel(resolveCtx);
|
|
8051
10245
|
const systemPrompt = agent2.resolveSystem(resolveCtx);
|
|
8052
|
-
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);
|
|
8053
10322
|
let resolvedHandoffs;
|
|
8054
10323
|
if (typeof agent2._config.handoffs === "function") {
|
|
8055
10324
|
try {
|
|
@@ -8070,50 +10339,60 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8070
10339
|
messages.push({ role: "system", content: systemPrompt });
|
|
8071
10340
|
}
|
|
8072
10341
|
const maxContext = agent2._config.maxContext;
|
|
8073
|
-
if (maxContext &&
|
|
10342
|
+
if (maxContext && sessionHistory.length > 0) {
|
|
8074
10343
|
const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
|
|
8075
10344
|
const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;
|
|
8076
10345
|
const toolTokens = toolDefs.length > 0 ? estimateTokens(JSON.stringify(toolDefs)) : 0;
|
|
8077
10346
|
const overhead = systemTokens + toolTokens + reserveTokens;
|
|
8078
10347
|
const availableForHistory = maxContext - overhead;
|
|
8079
|
-
const
|
|
8080
|
-
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) {
|
|
8081
10358
|
const summarizedMessages = await this.summarizeHistory(
|
|
8082
10359
|
provider,
|
|
8083
10360
|
model,
|
|
8084
|
-
|
|
10361
|
+
sessionHistory,
|
|
8085
10362
|
availableForHistory
|
|
8086
10363
|
);
|
|
8087
10364
|
for (const msg of summarizedMessages) {
|
|
8088
10365
|
messages.push(msg);
|
|
8089
10366
|
}
|
|
8090
10367
|
} else {
|
|
8091
|
-
for (const msg of
|
|
10368
|
+
for (const msg of sessionHistory) {
|
|
8092
10369
|
messages.push(msg);
|
|
8093
10370
|
}
|
|
8094
10371
|
}
|
|
8095
10372
|
} else {
|
|
8096
|
-
for (const msg of
|
|
10373
|
+
for (const msg of sessionHistory) {
|
|
8097
10374
|
messages.push(msg);
|
|
8098
10375
|
}
|
|
8099
10376
|
}
|
|
8100
10377
|
const schemaPromptMode = options?.schemaPrompt ?? agent2._config.schemaPrompt ?? "json-schema";
|
|
8101
|
-
let userContent =
|
|
10378
|
+
let userContent = input;
|
|
8102
10379
|
let appendedSchemaText;
|
|
8103
10380
|
if (options?.schema) {
|
|
8104
10381
|
const schema = options.schema;
|
|
8105
10382
|
if (schemaPromptMode === "none") {
|
|
8106
10383
|
} else if (schemaPromptMode === "json-schema") {
|
|
8107
10384
|
appendedSchemaText = renderSchemaForPrompt(schema);
|
|
8108
|
-
|
|
10385
|
+
const guidance = `
|
|
8109
10386
|
|
|
8110
10387
|
Respond with valid JSON matching this schema:
|
|
8111
10388
|
${appendedSchemaText}`;
|
|
10389
|
+
userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
|
|
8112
10390
|
} else {
|
|
8113
10391
|
appendedSchemaText = typeof schemaPromptMode.render === "function" ? schemaPromptMode.render(schema) : schemaPromptMode.render;
|
|
8114
|
-
|
|
10392
|
+
const guidance = `
|
|
8115
10393
|
|
|
8116
10394
|
${appendedSchemaText}`;
|
|
10395
|
+
userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
|
|
8117
10396
|
}
|
|
8118
10397
|
}
|
|
8119
10398
|
messages.push({ role: "user", content: userContent });
|
|
@@ -8141,7 +10420,10 @@ ${appendedSchemaText}`;
|
|
|
8141
10420
|
}
|
|
8142
10421
|
const guardrails = agent2._config.guardrails;
|
|
8143
10422
|
if (guardrails?.input) {
|
|
8144
|
-
const inputResult = await guardrails.input(prompt, {
|
|
10423
|
+
const inputResult = await guardrails.input(prompt, {
|
|
10424
|
+
metadata: this.metadata,
|
|
10425
|
+
input: cloneModelInput(input)
|
|
10426
|
+
});
|
|
8145
10427
|
this.emitEvent({
|
|
8146
10428
|
type: "guardrail",
|
|
8147
10429
|
agent: agent2._name,
|
|
@@ -8232,7 +10514,7 @@ ${appendedSchemaText}`;
|
|
|
8232
10514
|
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
8233
10515
|
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
8234
10516
|
stop: options?.stop ?? agent2._config.stop,
|
|
8235
|
-
providerOptions
|
|
10517
|
+
providerOptions,
|
|
8236
10518
|
signal: this.currentSignal
|
|
8237
10519
|
};
|
|
8238
10520
|
if (options?.schema && toolDefs.length === 0) {
|
|
@@ -8258,15 +10540,26 @@ ${appendedSchemaText}`;
|
|
|
8258
10540
|
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
8259
10541
|
};
|
|
8260
10542
|
let messagesSnapshot;
|
|
10543
|
+
let messageInputs;
|
|
8261
10544
|
if (verboseTrace) {
|
|
8262
10545
|
try {
|
|
8263
|
-
|
|
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
|
+
}));
|
|
8264
10554
|
} catch (err) {
|
|
8265
10555
|
console.warn(
|
|
8266
10556
|
"[axl] verbose trace messages snapshot failed to clone; emitting shallow copy:",
|
|
8267
10557
|
err instanceof Error ? err.message : String(err)
|
|
8268
10558
|
);
|
|
8269
|
-
messagesSnapshot =
|
|
10559
|
+
messagesSnapshot = currentMessages.map((message) => ({
|
|
10560
|
+
...message,
|
|
10561
|
+
content: sanitizeModelInputForTrace(message.content)
|
|
10562
|
+
}));
|
|
8270
10563
|
}
|
|
8271
10564
|
}
|
|
8272
10565
|
const retryReason = pendingRetryReason;
|
|
@@ -8274,16 +10567,18 @@ ${appendedSchemaText}`;
|
|
|
8274
10567
|
this.emitEvent({
|
|
8275
10568
|
type: "agent_call_start",
|
|
8276
10569
|
agent: agent2._name,
|
|
8277
|
-
model:
|
|
10570
|
+
model: effectiveModelUri,
|
|
8278
10571
|
turn: turns,
|
|
8279
10572
|
data: {
|
|
8280
10573
|
prompt,
|
|
10574
|
+
...typeof input === "string" ? {} : { input: describeModelInput(input) },
|
|
8281
10575
|
...systemPrompt ? { system: systemPrompt } : {},
|
|
8282
10576
|
params: callParams,
|
|
8283
10577
|
turn: turns,
|
|
8284
10578
|
...retryReason ? { retryReason } : {},
|
|
8285
10579
|
...toolDefs.length > 0 ? { toolNames: toolDefs.map((t) => t.function.name) } : {},
|
|
8286
|
-
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
10580
|
+
...messagesSnapshot ? { messages: messagesSnapshot } : {},
|
|
10581
|
+
...messageInputs && messageInputs.length > 0 ? { messageInputs } : {}
|
|
8287
10582
|
}
|
|
8288
10583
|
});
|
|
8289
10584
|
let response;
|
|
@@ -8307,7 +10602,10 @@ ${appendedSchemaText}`;
|
|
|
8307
10602
|
});
|
|
8308
10603
|
}
|
|
8309
10604
|
}) : void 0;
|
|
8310
|
-
for await (const chunk of provider.stream(
|
|
10605
|
+
for await (const chunk of provider.stream(
|
|
10606
|
+
cloneMessagesForProvider(currentMessages),
|
|
10607
|
+
chatOptions
|
|
10608
|
+
)) {
|
|
8311
10609
|
if (chunk.type === "text_delta") {
|
|
8312
10610
|
content2 += chunk.content;
|
|
8313
10611
|
this.emitEvent({ type: "token", data: chunk.content });
|
|
@@ -8373,24 +10671,25 @@ ${appendedSchemaText}`;
|
|
|
8373
10671
|
response.thinking_content = thinkingContent;
|
|
8374
10672
|
}
|
|
8375
10673
|
} else {
|
|
8376
|
-
response = await provider.chat(currentMessages, chatOptions);
|
|
10674
|
+
response = await provider.chat(cloneMessagesForProvider(currentMessages), chatOptions);
|
|
8377
10675
|
}
|
|
8378
10676
|
} catch (err) {
|
|
10677
|
+
if (richRequest) this.recordRichFailure(err);
|
|
8379
10678
|
this.emitEvent({
|
|
8380
10679
|
type: "agent_call_end",
|
|
8381
10680
|
agent: agent2._name,
|
|
8382
|
-
model:
|
|
10681
|
+
model: effectiveModelUri,
|
|
8383
10682
|
promptVersion: agent2._config.version,
|
|
8384
10683
|
duration: Date.now() - turnStart,
|
|
8385
10684
|
data: {
|
|
8386
10685
|
response: "",
|
|
8387
10686
|
turn: turns,
|
|
8388
10687
|
...retryReason ? { retryReason } : {},
|
|
8389
|
-
error: err
|
|
10688
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err),
|
|
8390
10689
|
// Enrich with typed-error metadata when available. `body` is
|
|
8391
10690
|
// deliberately NOT emitted — it's redaction-eligible (see
|
|
8392
10691
|
// docs/security.md). `data.error` already carries the message.
|
|
8393
|
-
...err
|
|
10692
|
+
...providerErrorEventMetadata(err, richRequest)
|
|
8394
10693
|
}
|
|
8395
10694
|
});
|
|
8396
10695
|
throw err;
|
|
@@ -8404,7 +10703,7 @@ ${appendedSchemaText}`;
|
|
|
8404
10703
|
this.emitEvent({
|
|
8405
10704
|
type: "agent_call_end",
|
|
8406
10705
|
agent: agent2._name,
|
|
8407
|
-
model:
|
|
10706
|
+
model: effectiveModelUri,
|
|
8408
10707
|
promptVersion: agent2._config.version,
|
|
8409
10708
|
cost: response.cost,
|
|
8410
10709
|
tokens: response.usage ? {
|
|
@@ -8438,12 +10737,12 @@ ${appendedSchemaText}`;
|
|
|
8438
10737
|
if (descriptor) {
|
|
8439
10738
|
const mode = descriptor.mode ?? "oneway";
|
|
8440
10739
|
let handoffPrompt = prompt;
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
} 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;
|
|
8446
10744
|
}
|
|
10745
|
+
} catch {
|
|
8447
10746
|
}
|
|
8448
10747
|
const handoffStart = Date.now();
|
|
8449
10748
|
const sourceFrame = askStorage.getStore();
|
|
@@ -8470,15 +10769,27 @@ ${appendedSchemaText}`;
|
|
|
8470
10769
|
validateRetries: options.validateRetries
|
|
8471
10770
|
} : void 0;
|
|
8472
10771
|
const handoffFn = () => askStorage.run(targetFrame, async () => {
|
|
8473
|
-
|
|
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
|
+
});
|
|
8474
10784
|
const targetAskStart = Date.now();
|
|
8475
10785
|
try {
|
|
8476
10786
|
const result = await this.executeAgentCall(
|
|
8477
10787
|
descriptor.agent,
|
|
8478
|
-
|
|
10788
|
+
enrichedHandoffInput,
|
|
8479
10789
|
handoffOptions,
|
|
8480
10790
|
currentMessages,
|
|
8481
|
-
usageCapture
|
|
10791
|
+
usageCapture,
|
|
10792
|
+
sessionHistory
|
|
8482
10793
|
);
|
|
8483
10794
|
this.emitEvent({
|
|
8484
10795
|
type: "ask_end",
|
|
@@ -8489,11 +10800,13 @@ ${appendedSchemaText}`;
|
|
|
8489
10800
|
});
|
|
8490
10801
|
return result;
|
|
8491
10802
|
} catch (err) {
|
|
10803
|
+
const richHandoff = typeof enrichedHandoffInput !== "string" || hasRichMessage(sessionHistory);
|
|
10804
|
+
if (richHandoff) this.recordRichFailure(err);
|
|
8492
10805
|
this.emitEvent({
|
|
8493
10806
|
type: "ask_end",
|
|
8494
10807
|
outcome: {
|
|
8495
10808
|
ok: false,
|
|
8496
|
-
error: err
|
|
10809
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
|
|
8497
10810
|
},
|
|
8498
10811
|
cost: targetFrame.askCost.value,
|
|
8499
10812
|
...targetFrame.askUnpriced ? { unpriced: true } : {},
|
|
@@ -9002,12 +11315,12 @@ ${appendedSchemaText}`;
|
|
|
9002
11315
|
content: `Summary of earlier conversation:
|
|
9003
11316
|
${this.summaryCache}`
|
|
9004
11317
|
};
|
|
9005
|
-
const summaryTokens = estimateTokens(summaryMsg2.content) + 4;
|
|
11318
|
+
const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg2.content)) + 4;
|
|
9006
11319
|
const remaining = availableTokens - summaryTokens;
|
|
9007
11320
|
let recentTokens2 = 0;
|
|
9008
11321
|
let splitIdx2 = history.length;
|
|
9009
11322
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
9010
|
-
const msgTokens = estimateTokens(history[i].content) + 4;
|
|
11323
|
+
const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
|
|
9011
11324
|
if (recentTokens2 + msgTokens > remaining) break;
|
|
9012
11325
|
recentTokens2 += msgTokens;
|
|
9013
11326
|
splitIdx2 = i;
|
|
@@ -9020,7 +11333,7 @@ ${this.summaryCache}`
|
|
|
9020
11333
|
let splitIdx = history.length;
|
|
9021
11334
|
const targetRecent = Math.floor(availableTokens * 0.6);
|
|
9022
11335
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
9023
|
-
const msgTokens = estimateTokens(history[i].content) + 4;
|
|
11336
|
+
const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
|
|
9024
11337
|
if (recentTokens + msgTokens > targetRecent) break;
|
|
9025
11338
|
recentTokens += msgTokens;
|
|
9026
11339
|
splitIdx = i;
|
|
@@ -9038,7 +11351,7 @@ ${this.summaryCache}`
|
|
|
9038
11351
|
summaryProvider = provider;
|
|
9039
11352
|
summaryModel = model;
|
|
9040
11353
|
}
|
|
9041
|
-
const oldContent = oldMessages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
11354
|
+
const oldContent = oldMessages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n");
|
|
9042
11355
|
const summaryResponse = await summaryProvider.chat(
|
|
9043
11356
|
[
|
|
9044
11357
|
{
|
|
@@ -10272,7 +12585,9 @@ ${summaryResponse.content}`
|
|
|
10272
12585
|
reason: "routed"
|
|
10273
12586
|
}
|
|
10274
12587
|
});
|
|
10275
|
-
|
|
12588
|
+
const routerInput = options?.routerInput === "text" ? inputText(prompt) : prompt;
|
|
12589
|
+
routerAgent._delegateOriginalInput = prompt;
|
|
12590
|
+
return this.ask(routerAgent, routerInput, {
|
|
10276
12591
|
schema: options?.schema,
|
|
10277
12592
|
schemaPrompt: options?.schemaPrompt,
|
|
10278
12593
|
nativeStructuredOutput: options?.nativeStructuredOutput,
|
|
@@ -10282,6 +12597,278 @@ ${summaryResponse.content}`
|
|
|
10282
12597
|
validateRetries: options?.validateRetries
|
|
10283
12598
|
});
|
|
10284
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
|
+
}
|
|
10285
12872
|
// ── Private ───────────────────────────────────────────────────────────
|
|
10286
12873
|
/**
|
|
10287
12874
|
* Internal emitter input — intentionally loose so call sites don't need to
|
|
@@ -10297,7 +12884,7 @@ ${summaryResponse.content}`
|
|
|
10297
12884
|
const cost = partial.cost;
|
|
10298
12885
|
if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
10299
12886
|
const usable = isUsableCost(cost);
|
|
10300
|
-
const unpriced = !usable &&
|
|
12887
|
+
const unpriced = !usable && hasPositiveBillableWork(
|
|
10301
12888
|
partial
|
|
10302
12889
|
);
|
|
10303
12890
|
if (frame) {
|
|
@@ -10402,11 +12989,13 @@ function agent(config) {
|
|
|
10402
12989
|
_name: defaultName,
|
|
10403
12990
|
async ask(prompt, options) {
|
|
10404
12991
|
const registry = new ProviderRegistry();
|
|
12992
|
+
const transcriptionRegistry = new TranscriptionProviderRegistry();
|
|
10405
12993
|
const ctx = new WorkflowContext({
|
|
10406
12994
|
input: prompt,
|
|
10407
12995
|
executionId: (0, import_node_crypto2.randomUUID)(),
|
|
10408
12996
|
config: {},
|
|
10409
|
-
providerRegistry: registry
|
|
12997
|
+
providerRegistry: registry,
|
|
12998
|
+
transcriptionProviderRegistry: transcriptionRegistry
|
|
10410
12999
|
});
|
|
10411
13000
|
return ctx.ask(agentInstance, prompt, options);
|
|
10412
13001
|
},
|
|
@@ -10466,7 +13055,7 @@ var MemoryStore = class {
|
|
|
10466
13055
|
return data !== void 0 ? structuredClone(data) : null;
|
|
10467
13056
|
}
|
|
10468
13057
|
async saveSession(sessionId, history) {
|
|
10469
|
-
this.sessions.set(sessionId, structuredClone(history));
|
|
13058
|
+
this.sessions.set(sessionId, structuredClone(normalizePersistedSessionHistory(history)));
|
|
10470
13059
|
}
|
|
10471
13060
|
async getSession(sessionId) {
|
|
10472
13061
|
const history = this.sessions.get(sessionId);
|
|
@@ -10890,7 +13479,7 @@ var SQLiteStore = class {
|
|
|
10890
13479
|
const stmt = this.db.prepare(
|
|
10891
13480
|
"INSERT OR REPLACE INTO sessions (session_id, history) VALUES (?, ?)"
|
|
10892
13481
|
);
|
|
10893
|
-
stmt.run(sessionId, JSON.stringify(history));
|
|
13482
|
+
stmt.run(sessionId, JSON.stringify(normalizePersistedSessionHistory(history)));
|
|
10894
13483
|
}
|
|
10895
13484
|
async getSession(sessionId) {
|
|
10896
13485
|
const stmt = this.db.prepare("SELECT history FROM sessions WHERE session_id = ?");
|
|
@@ -11458,7 +14047,7 @@ var AxlStream = class extends import_node_stream.Readable {
|
|
|
11458
14047
|
/** Signal an error. `executionId` is required for the same reason as
|
|
11459
14048
|
* `_done`: terminal events must carry a real id even when the
|
|
11460
14049
|
* failure happens before any real trace event fires (review S4). */
|
|
11461
|
-
_error(error, executionId) {
|
|
14050
|
+
_error(error, executionId, observerMessage = error.message, observerName = error.name) {
|
|
11462
14051
|
if (this.events.isFinished) return;
|
|
11463
14052
|
const errorEvent = {
|
|
11464
14053
|
schemaVersion: 2,
|
|
@@ -11466,7 +14055,7 @@ var AxlStream = class extends import_node_stream.Readable {
|
|
|
11466
14055
|
executionId,
|
|
11467
14056
|
step: Number.MAX_SAFE_INTEGER,
|
|
11468
14057
|
timestamp: Date.now(),
|
|
11469
|
-
data: { message:
|
|
14058
|
+
data: { message: observerMessage, name: observerName }
|
|
11470
14059
|
};
|
|
11471
14060
|
this.events._push(errorEvent);
|
|
11472
14061
|
this.push(errorEvent);
|
|
@@ -12193,6 +14782,10 @@ var StreamingFlusher = class {
|
|
|
12193
14782
|
function isAbortError4(err) {
|
|
12194
14783
|
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
12195
14784
|
}
|
|
14785
|
+
function executionErrorProjection(ctx, error) {
|
|
14786
|
+
const safe = ctx?._observerErrorProjection(error);
|
|
14787
|
+
return safe ?? { message: error instanceof Error ? error.message : String(error) };
|
|
14788
|
+
}
|
|
12196
14789
|
function pushEventBounded(execInfo, event, cap) {
|
|
12197
14790
|
if (STREAMING_EXCLUDED_TYPES.has(event.type)) return;
|
|
12198
14791
|
if (cap === Infinity || execInfo.events.length < cap) {
|
|
@@ -12287,6 +14880,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12287
14880
|
tools = /* @__PURE__ */ new Map();
|
|
12288
14881
|
agents = /* @__PURE__ */ new Map();
|
|
12289
14882
|
providerRegistry;
|
|
14883
|
+
transcriptionProviderRegistry;
|
|
12290
14884
|
stateStore;
|
|
12291
14885
|
executions = /* @__PURE__ */ new Map();
|
|
12292
14886
|
pendingDecisionResolvers = /* @__PURE__ */ new Map();
|
|
@@ -12342,6 +14936,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12342
14936
|
super();
|
|
12343
14937
|
this.config = resolveConfig(config ?? {});
|
|
12344
14938
|
this.providerRegistry = new ProviderRegistry();
|
|
14939
|
+
this.transcriptionProviderRegistry = new TranscriptionProviderRegistry();
|
|
12345
14940
|
this.stateStore = this.createStateStore();
|
|
12346
14941
|
const requested = this.config.state?.maxEventsPerExecution;
|
|
12347
14942
|
if (requested === void 0) {
|
|
@@ -12490,7 +15085,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12490
15085
|
execInfo.status = "failed";
|
|
12491
15086
|
execInfo.completedAt = Date.now();
|
|
12492
15087
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
12493
|
-
execInfo.error =
|
|
15088
|
+
execInfo.error = executionErrorProjection(ctx, terminalError).message;
|
|
12494
15089
|
execInfo.observation = observation;
|
|
12495
15090
|
ctx._emitWorkflowEnd({
|
|
12496
15091
|
status: "failed",
|
|
@@ -12745,6 +15340,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12745
15340
|
metadata: options?.metadata,
|
|
12746
15341
|
config: this.config,
|
|
12747
15342
|
providerRegistry: this.providerRegistry,
|
|
15343
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12748
15344
|
stateStore: this.stateStore,
|
|
12749
15345
|
mcpManager: this.mcpManager,
|
|
12750
15346
|
spanManager: this.spanManager,
|
|
@@ -12773,6 +15369,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12773
15369
|
registerProvider(name, provider) {
|
|
12774
15370
|
this.providerRegistry.registerInstance(name, provider);
|
|
12775
15371
|
}
|
|
15372
|
+
/** Register a dedicated transcription adapter. It is never used for chat. */
|
|
15373
|
+
registerTranscriptionProvider(name, provider) {
|
|
15374
|
+
this.transcriptionProviderRegistry.registerInstance(name, provider);
|
|
15375
|
+
}
|
|
12776
15376
|
/** Resolve a provider:model URI to a Provider instance and model name. */
|
|
12777
15377
|
resolveProvider(uri) {
|
|
12778
15378
|
return this.providerRegistry.resolve(uri, this.config);
|
|
@@ -12816,6 +15416,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12816
15416
|
metadata: options?.metadata,
|
|
12817
15417
|
config: this.config,
|
|
12818
15418
|
providerRegistry: this.providerRegistry,
|
|
15419
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12819
15420
|
sessionHistory,
|
|
12820
15421
|
signal: controller.signal,
|
|
12821
15422
|
eventStreamOptions: options?.events,
|
|
@@ -12903,6 +15504,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12903
15504
|
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
12904
15505
|
this.abortControllers.set(executionId, controller);
|
|
12905
15506
|
let execInfo;
|
|
15507
|
+
let wfCtx;
|
|
12906
15508
|
const run = async () => {
|
|
12907
15509
|
const workflow2 = this.workflows.get(name);
|
|
12908
15510
|
if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
|
|
@@ -12926,12 +15528,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12926
15528
|
metadata: liftPersistedMetadata(options?.metadata)
|
|
12927
15529
|
};
|
|
12928
15530
|
this.executions.set(executionId, execInfo);
|
|
12929
|
-
const
|
|
15531
|
+
const context = new WorkflowContext({
|
|
12930
15532
|
input: validated,
|
|
12931
15533
|
executionId,
|
|
12932
15534
|
metadata: options?.metadata,
|
|
12933
15535
|
config: this.config,
|
|
12934
15536
|
providerRegistry: this.providerRegistry,
|
|
15537
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12935
15538
|
sessionHistory,
|
|
12936
15539
|
signal: controller.signal,
|
|
12937
15540
|
eventStreamOptions: options?.events,
|
|
@@ -12996,6 +15599,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12996
15599
|
unpricedWarned: false
|
|
12997
15600
|
}
|
|
12998
15601
|
});
|
|
15602
|
+
wfCtx = context;
|
|
12999
15603
|
return this.spanManager.withSpanAsync(
|
|
13000
15604
|
"axl.workflow.execute",
|
|
13001
15605
|
{
|
|
@@ -13005,7 +15609,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
13005
15609
|
},
|
|
13006
15610
|
(span) => this.runWorkflowBody({
|
|
13007
15611
|
workflow: workflow2,
|
|
13008
|
-
ctx:
|
|
15612
|
+
ctx: context,
|
|
13009
15613
|
execInfo,
|
|
13010
15614
|
validated,
|
|
13011
15615
|
span,
|
|
@@ -13018,12 +15622,14 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
13018
15622
|
execInfo.status = "failed";
|
|
13019
15623
|
execInfo.completedAt = Date.now();
|
|
13020
15624
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
13021
|
-
execInfo.error =
|
|
15625
|
+
execInfo.error = executionErrorProjection(wfCtx, err).message;
|
|
13022
15626
|
this.persistExecution(execInfo);
|
|
13023
15627
|
}
|
|
13024
15628
|
this.abortControllers.delete(executionId);
|
|
13025
15629
|
cleanupAbortForwarder();
|
|
13026
|
-
|
|
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);
|
|
13027
15633
|
});
|
|
13028
15634
|
return axlStream;
|
|
13029
15635
|
}
|
|
@@ -13434,7 +16040,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
13434
16040
|
},
|
|
13435
16041
|
{
|
|
13436
16042
|
role: "user",
|
|
13437
|
-
content: messages.map((m) => `${m.role}: ${m.content}`).join("\n")
|
|
16043
|
+
content: messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n")
|
|
13438
16044
|
}
|
|
13439
16045
|
],
|
|
13440
16046
|
{ model, maxTokens: 1024 }
|
|
@@ -13881,7 +16487,7 @@ async function registerConditions(conditions) {
|
|
|
13881
16487
|
].join("\n");
|
|
13882
16488
|
nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);
|
|
13883
16489
|
} catch {
|
|
13884
|
-
console.warn("Warning:
|
|
16490
|
+
console.warn("Warning: this Node.js runtime does not support --conditions");
|
|
13885
16491
|
}
|
|
13886
16492
|
}
|
|
13887
16493
|
|
|
@@ -14190,7 +16796,11 @@ var RedisStore = class _RedisStore {
|
|
|
14190
16796
|
async saveSession(sessionId, history) {
|
|
14191
16797
|
const ttl = this.ttlFor("session");
|
|
14192
16798
|
const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
|
|
14193
|
-
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();
|
|
14194
16804
|
}
|
|
14195
16805
|
async getSession(sessionId) {
|
|
14196
16806
|
const raw = await this.client.get(this.sessionKey(sessionId));
|
|
@@ -14679,10 +17289,15 @@ function cosineSimilarity2(a, b) {
|
|
|
14679
17289
|
EventStreamOverflowError,
|
|
14680
17290
|
GROQ_PROFILE,
|
|
14681
17291
|
GeminiProvider,
|
|
17292
|
+
GeminiTranscriptionProvider,
|
|
14682
17293
|
GuardrailError,
|
|
14683
17294
|
InMemoryVectorStore,
|
|
17295
|
+
InvalidModelInputError,
|
|
17296
|
+
InvalidTranscriptionInputError,
|
|
14684
17297
|
LLAMACPP_PROFILE,
|
|
14685
17298
|
LMSTUDIO_PROFILE,
|
|
17299
|
+
MAX_INLINE_MODEL_INPUT_BYTES,
|
|
17300
|
+
MAX_INLINE_TRANSCRIPTION_BYTES,
|
|
14686
17301
|
MISTRAL_PROFILE,
|
|
14687
17302
|
MaxTurnsError,
|
|
14688
17303
|
McpManager,
|
|
@@ -14698,6 +17313,8 @@ function cosineSimilarity2(a, b) {
|
|
|
14698
17313
|
OpenAIEmbedder,
|
|
14699
17314
|
OpenAIProvider,
|
|
14700
17315
|
OpenAIResponsesProvider,
|
|
17316
|
+
OpenAITranscriptionProvider,
|
|
17317
|
+
OpenRouterTranscriptionProvider,
|
|
14701
17318
|
ProviderError,
|
|
14702
17319
|
ProviderRegistry,
|
|
14703
17320
|
QuorumNotMet,
|
|
@@ -14713,7 +17330,11 @@ function cosineSimilarity2(a, b) {
|
|
|
14713
17330
|
TimeoutError,
|
|
14714
17331
|
ToolFailure,
|
|
14715
17332
|
ToolModelOutputError,
|
|
17333
|
+
TranscriptionOperationError,
|
|
17334
|
+
TranscriptionProviderRegistry,
|
|
14716
17335
|
UnsupportedEventSchemaVersionError,
|
|
17336
|
+
UnsupportedModelInputError,
|
|
17337
|
+
UnsupportedTranscriptionInputError,
|
|
14717
17338
|
VLLM_PROFILE,
|
|
14718
17339
|
ValidationError,
|
|
14719
17340
|
VerifyError,
|
|
@@ -14730,8 +17351,10 @@ function cosineSimilarity2(a, b) {
|
|
|
14730
17351
|
findConfig,
|
|
14731
17352
|
getEventSchemaVersion,
|
|
14732
17353
|
getExecutionEventSchemaVersion,
|
|
17354
|
+
hasPositiveBillableWork,
|
|
14733
17355
|
hasPositiveTokens,
|
|
14734
17356
|
importModule,
|
|
17357
|
+
inputText,
|
|
14735
17358
|
isCostBearingLeaf,
|
|
14736
17359
|
isRetryableStatus,
|
|
14737
17360
|
isRootLevel,
|