@axlsdk/axl 0.21.1 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/index.cjs +2859 -233
- 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 +2835 -222
- 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,37 @@ function unsafeTransportError(surface, remediation, exposedData) {
|
|
|
1195
1277
|
}
|
|
1196
1278
|
|
|
1197
1279
|
// src/providers/openai-compatible.ts
|
|
1280
|
+
function compatibleImageBase64(source) {
|
|
1281
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
1282
|
+
"base64"
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
function openRouterImageParts(parts, model) {
|
|
1286
|
+
const content = [];
|
|
1287
|
+
for (const part of parts) {
|
|
1288
|
+
if (part.type === "text") {
|
|
1289
|
+
content.push({ type: "text", text: part.text });
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
const { source } = part;
|
|
1293
|
+
if (source.type === "provider-file") {
|
|
1294
|
+
throw new UnsupportedModelInputError({
|
|
1295
|
+
provider: "openrouter",
|
|
1296
|
+
model,
|
|
1297
|
+
modality: "image",
|
|
1298
|
+
source: "provider-file"
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
content.push({
|
|
1302
|
+
type: "image_url",
|
|
1303
|
+
image_url: {
|
|
1304
|
+
url: source.type === "url" ? source.url : `data:${source.mediaType};base64,${compatibleImageBase64(source)}`
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
if (part.label) content.push({ type: "text", text: `[Image: ${part.label}]` });
|
|
1308
|
+
}
|
|
1309
|
+
return content;
|
|
1310
|
+
}
|
|
1198
1311
|
function resolvePerModel(value, model, fallback) {
|
|
1199
1312
|
if (value === void 0) return fallback;
|
|
1200
1313
|
return typeof value === "function" ? value(model) : value;
|
|
@@ -1310,6 +1423,40 @@ function normalizeMessageContent(content) {
|
|
|
1310
1423
|
}
|
|
1311
1424
|
var OpenAICompatibleProvider = class {
|
|
1312
1425
|
name;
|
|
1426
|
+
inputCapabilities(model) {
|
|
1427
|
+
return this.name === "openrouter" && model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64"] } } : {};
|
|
1428
|
+
}
|
|
1429
|
+
validateInput(request) {
|
|
1430
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
1431
|
+
const fail = (source, feature) => {
|
|
1432
|
+
throw new UnsupportedModelInputError({
|
|
1433
|
+
provider: this.name,
|
|
1434
|
+
model: effectiveModel || request.model,
|
|
1435
|
+
modality: "image",
|
|
1436
|
+
...source ? { source } : {},
|
|
1437
|
+
...feature ? { feature } : {}
|
|
1438
|
+
});
|
|
1439
|
+
};
|
|
1440
|
+
if (this.name !== "openrouter" || effectiveModel.trim().length === 0) {
|
|
1441
|
+
fail(void 0, "image input for this model");
|
|
1442
|
+
}
|
|
1443
|
+
if (request.providerOptions && "messages" in request.providerOptions) {
|
|
1444
|
+
fail(void 0, "raw messages providerOptions");
|
|
1445
|
+
}
|
|
1446
|
+
for (const message of request.history) {
|
|
1447
|
+
if (!Array.isArray(message.content)) continue;
|
|
1448
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
1449
|
+
if (message.content.some(
|
|
1450
|
+
(part) => part.type === "image" && part.source.type === "provider-file"
|
|
1451
|
+
)) {
|
|
1452
|
+
fail("provider-file");
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (Array.isArray(request.input) && request.input.some((part) => part.type === "image" && part.source.type === "provider-file")) {
|
|
1456
|
+
fail("provider-file");
|
|
1457
|
+
}
|
|
1458
|
+
return { effectiveModel };
|
|
1459
|
+
}
|
|
1313
1460
|
/** `json_schema` is honored natively when the profile's `supportsJsonSchema`
|
|
1314
1461
|
* capability is true for this model (the default); otherwise the engine
|
|
1315
1462
|
* downgrades the request to plain `json_object` (see `buildRequestBody`),
|
|
@@ -1483,7 +1630,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1483
1630
|
formatMessage(msg, model) {
|
|
1484
1631
|
const out = {
|
|
1485
1632
|
role: this.profile.roleFor ? this.profile.roleFor(msg.role, model) : msg.role,
|
|
1486
|
-
content: msg.content
|
|
1633
|
+
content: Array.isArray(msg.content) && this.name === "openrouter" ? openRouterImageParts(msg.content, model) : msg.content
|
|
1487
1634
|
};
|
|
1488
1635
|
if (msg.name && (this.profile.capabilities?.emitsMessageName ?? true)) out.name = msg.name;
|
|
1489
1636
|
if (msg.tool_calls) out.tool_calls = msg.tool_calls;
|
|
@@ -2230,8 +2377,88 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
|
|
|
2230
2377
|
};
|
|
2231
2378
|
|
|
2232
2379
|
// src/providers/openai-responses.ts
|
|
2380
|
+
var OPENAI_RESPONSES_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
2381
|
+
"gpt-4o",
|
|
2382
|
+
"gpt-4o-2024-08-06",
|
|
2383
|
+
"gpt-4o-2024-11-20",
|
|
2384
|
+
"gpt-4o-mini",
|
|
2385
|
+
"gpt-4o-mini-2024-07-18"
|
|
2386
|
+
]);
|
|
2387
|
+
function base64FromSource(source) {
|
|
2388
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
2389
|
+
"base64"
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
function responseImageParts(parts) {
|
|
2393
|
+
const mapped = [];
|
|
2394
|
+
for (const part of parts) {
|
|
2395
|
+
if (part.type === "text") {
|
|
2396
|
+
mapped.push({ type: "input_text", text: part.text });
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
const { source } = part;
|
|
2400
|
+
if (source.type === "provider-file") {
|
|
2401
|
+
if (source.provider !== "openai-responses") {
|
|
2402
|
+
throw new UnsupportedModelInputError({
|
|
2403
|
+
provider: "openai-responses",
|
|
2404
|
+
model: "unknown",
|
|
2405
|
+
modality: "image",
|
|
2406
|
+
source: "provider-file"
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
mapped.push({ type: "input_image", file_id: source.reference });
|
|
2410
|
+
} else if (source.type === "url") {
|
|
2411
|
+
mapped.push({ type: "input_image", image_url: source.url });
|
|
2412
|
+
} else {
|
|
2413
|
+
mapped.push({
|
|
2414
|
+
type: "input_image",
|
|
2415
|
+
image_url: `data:${source.mediaType};base64,${base64FromSource(source)}`
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
if (part.label) mapped.push({ type: "input_text", text: `[Image: ${part.label}]` });
|
|
2419
|
+
}
|
|
2420
|
+
return mapped;
|
|
2421
|
+
}
|
|
2233
2422
|
var OpenAIResponsesProvider = class {
|
|
2234
2423
|
name = "openai-responses";
|
|
2424
|
+
inputCapabilities(model) {
|
|
2425
|
+
return OPENAI_RESPONSES_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
2426
|
+
}
|
|
2427
|
+
validateInput(request) {
|
|
2428
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
2429
|
+
const fail = (source, feature) => {
|
|
2430
|
+
throw new UnsupportedModelInputError({
|
|
2431
|
+
provider: this.name,
|
|
2432
|
+
model: effectiveModel || request.model,
|
|
2433
|
+
modality: "image",
|
|
2434
|
+
...source ? { source } : {},
|
|
2435
|
+
...feature ? { feature } : {}
|
|
2436
|
+
});
|
|
2437
|
+
};
|
|
2438
|
+
if (!OPENAI_RESPONSES_IMAGE_MODELS.has(effectiveModel)) {
|
|
2439
|
+
fail(void 0, "image input for this model");
|
|
2440
|
+
}
|
|
2441
|
+
if (request.providerOptions && "input" in request.providerOptions) {
|
|
2442
|
+
fail(void 0, "raw input-container providerOptions");
|
|
2443
|
+
}
|
|
2444
|
+
for (const message of request.history) {
|
|
2445
|
+
if (!Array.isArray(message.content)) continue;
|
|
2446
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
2447
|
+
for (const part of message.content) {
|
|
2448
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
2449
|
+
fail("provider-file");
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
if (Array.isArray(request.input)) {
|
|
2454
|
+
for (const part of request.input) {
|
|
2455
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
2456
|
+
fail("provider-file");
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
return { effectiveModel };
|
|
2461
|
+
}
|
|
2235
2462
|
/** The Responses API honors `json_schema` (structured outputs) natively. */
|
|
2236
2463
|
nativeStructuredOutputSupport() {
|
|
2237
2464
|
return "schema";
|
|
@@ -2396,11 +2623,12 @@ var OpenAIResponsesProvider = class {
|
|
|
2396
2623
|
buildInput(messages) {
|
|
2397
2624
|
const input = [];
|
|
2398
2625
|
for (const msg of messages) {
|
|
2626
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
2399
2627
|
if (msg.role === "tool") {
|
|
2400
2628
|
input.push({
|
|
2401
2629
|
type: "function_call_output",
|
|
2402
2630
|
call_id: msg.tool_call_id ?? "",
|
|
2403
|
-
output:
|
|
2631
|
+
output: text
|
|
2404
2632
|
});
|
|
2405
2633
|
} else if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
2406
2634
|
const reasoningItems = msg.providerMetadata?.openaiReasoningItems;
|
|
@@ -2409,8 +2637,8 @@ var OpenAIResponsesProvider = class {
|
|
|
2409
2637
|
input.push(item);
|
|
2410
2638
|
}
|
|
2411
2639
|
}
|
|
2412
|
-
if (
|
|
2413
|
-
input.push({ type: "message", role: "assistant", content:
|
|
2640
|
+
if (text) {
|
|
2641
|
+
input.push({ type: "message", role: "assistant", content: text });
|
|
2414
2642
|
}
|
|
2415
2643
|
for (const tc of msg.tool_calls) {
|
|
2416
2644
|
input.push({
|
|
@@ -2430,7 +2658,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2430
2658
|
input.push({
|
|
2431
2659
|
type: "message",
|
|
2432
2660
|
role: msg.role,
|
|
2433
|
-
content: msg.content
|
|
2661
|
+
content: typeof msg.content === "string" ? msg.content : responseImageParts(msg.content)
|
|
2434
2662
|
});
|
|
2435
2663
|
}
|
|
2436
2664
|
}
|
|
@@ -2497,7 +2725,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2497
2725
|
cached_tokens: json.usage.input_tokens_details?.cached_tokens,
|
|
2498
2726
|
cache_write_tokens: json.usage.input_tokens_details?.cache_write_tokens
|
|
2499
2727
|
} : void 0;
|
|
2500
|
-
const cost = usage ? estimateDirectOpenAICost(json.model ?? model, usage, {
|
|
2728
|
+
const cost = usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(json.model ?? model, usage, {
|
|
2501
2729
|
baseUrl: this.baseUrl,
|
|
2502
2730
|
request,
|
|
2503
2731
|
response: json
|
|
@@ -2512,6 +2740,16 @@ var OpenAIResponsesProvider = class {
|
|
|
2512
2740
|
providerMetadata
|
|
2513
2741
|
};
|
|
2514
2742
|
}
|
|
2743
|
+
/** Responses usage does not report a media-inclusive price, so rich calls stay unpriced. */
|
|
2744
|
+
requestContainsImages(request) {
|
|
2745
|
+
return Array.isArray(request?.input) && request.input.some((item) => {
|
|
2746
|
+
if (!item || typeof item !== "object") return false;
|
|
2747
|
+
const content = item.content;
|
|
2748
|
+
return Array.isArray(content) && content.some(
|
|
2749
|
+
(part) => part !== null && typeof part === "object" && part.type === "input_image"
|
|
2750
|
+
);
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2515
2753
|
// ---------------------------------------------------------------------------
|
|
2516
2754
|
// Internal: parse SSE stream
|
|
2517
2755
|
// ---------------------------------------------------------------------------
|
|
@@ -2599,7 +2837,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2599
2837
|
return {
|
|
2600
2838
|
type: "done",
|
|
2601
2839
|
usage,
|
|
2602
|
-
cost: usage ? estimateDirectOpenAICost(response?.model ?? model, usage, {
|
|
2840
|
+
cost: usage && !this.requestContainsImages(request) ? estimateDirectOpenAICost(response?.model ?? model, usage, {
|
|
2603
2841
|
baseUrl: this.baseUrl,
|
|
2604
2842
|
request,
|
|
2605
2843
|
response
|
|
@@ -2636,7 +2874,51 @@ var OpenAIResponsesProvider = class {
|
|
|
2636
2874
|
};
|
|
2637
2875
|
|
|
2638
2876
|
// src/providers/anthropic.ts
|
|
2877
|
+
function anthropicBase64(source) {
|
|
2878
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
2879
|
+
"base64"
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
function anthropicImageBlocks(parts) {
|
|
2883
|
+
const blocks = [];
|
|
2884
|
+
for (const part of parts) {
|
|
2885
|
+
if (part.type === "text") {
|
|
2886
|
+
blocks.push({ type: "text", text: part.text });
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
const { source } = part;
|
|
2890
|
+
if (source.type === "provider-file") {
|
|
2891
|
+
if (source.provider !== "anthropic") {
|
|
2892
|
+
throw new UnsupportedModelInputError({
|
|
2893
|
+
provider: "anthropic",
|
|
2894
|
+
model: "unknown",
|
|
2895
|
+
modality: "image",
|
|
2896
|
+
source: "provider-file"
|
|
2897
|
+
});
|
|
2898
|
+
}
|
|
2899
|
+
blocks.push({ type: "image", source: { type: "file", file_id: source.reference } });
|
|
2900
|
+
} else if (source.type === "url") {
|
|
2901
|
+
blocks.push({ type: "image", source: { type: "url", url: source.url } });
|
|
2902
|
+
} else {
|
|
2903
|
+
blocks.push({
|
|
2904
|
+
type: "image",
|
|
2905
|
+
source: { type: "base64", media_type: source.mediaType, data: anthropicBase64(source) }
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
if (part.label) blocks.push({ type: "text", text: `[Image: ${part.label}]` });
|
|
2909
|
+
}
|
|
2910
|
+
return blocks;
|
|
2911
|
+
}
|
|
2639
2912
|
var ANTHROPIC_API_VERSION = "2023-06-01";
|
|
2913
|
+
var ANTHROPIC_FILES_BETA = "files-api-2025-04-14";
|
|
2914
|
+
var ANTHROPIC_IMAGE_MODELS = /* @__PURE__ */ new Set(["claude-sonnet-4-5", "claude-opus-4-8"]);
|
|
2915
|
+
function hasAnthropicProviderFileImage(messages) {
|
|
2916
|
+
return messages.some(
|
|
2917
|
+
(message) => Array.isArray(message.content) && message.content.some(
|
|
2918
|
+
(part) => part.type === "image" && part.source.type === "provider-file" && part.source.provider === "anthropic"
|
|
2919
|
+
)
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2640
2922
|
var CLAUDE_CAPABILITIES = {
|
|
2641
2923
|
"claude-fable-5": {
|
|
2642
2924
|
thinking: "adaptive-always-on",
|
|
@@ -2897,6 +3179,11 @@ function isEffortAtOrBelow(effort, ceiling) {
|
|
|
2897
3179
|
function pricingContextFromBody(body) {
|
|
2898
3180
|
return {
|
|
2899
3181
|
model: typeof body.model === "string" ? body.model : void 0,
|
|
3182
|
+
hasRichInput: Array.isArray(body.messages) && body.messages.some(
|
|
3183
|
+
(message) => message !== null && typeof message === "object" && Array.isArray(message.content) && message.content.some(
|
|
3184
|
+
(part) => part !== null && typeof part === "object" && part.type === "image"
|
|
3185
|
+
)
|
|
3186
|
+
),
|
|
2900
3187
|
unpricedModifier: isAnthropicModifier("inference_geo", body.inference_geo) || isAnthropicModifier("speed", body.speed) || hasAnthropicServerTool(body.tools) || body.fallbacks !== void 0
|
|
2901
3188
|
};
|
|
2902
3189
|
}
|
|
@@ -2991,6 +3278,44 @@ function mergeAnthropicUsage(initial, update) {
|
|
|
2991
3278
|
}
|
|
2992
3279
|
var AnthropicProvider = class {
|
|
2993
3280
|
name = "anthropic";
|
|
3281
|
+
inputCapabilities(model) {
|
|
3282
|
+
return ANTHROPIC_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
3283
|
+
}
|
|
3284
|
+
validateInput(request) {
|
|
3285
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
3286
|
+
const fail = (source, feature) => {
|
|
3287
|
+
throw new UnsupportedModelInputError({
|
|
3288
|
+
provider: this.name,
|
|
3289
|
+
model: effectiveModel || request.model,
|
|
3290
|
+
modality: "image",
|
|
3291
|
+
...source ? { source } : {},
|
|
3292
|
+
...feature ? { feature } : {}
|
|
3293
|
+
});
|
|
3294
|
+
};
|
|
3295
|
+
if (!ANTHROPIC_IMAGE_MODELS.has(effectiveModel)) {
|
|
3296
|
+
fail(void 0, "image input for this model");
|
|
3297
|
+
}
|
|
3298
|
+
if (request.providerOptions && "messages" in request.providerOptions) {
|
|
3299
|
+
fail(void 0, "raw messages providerOptions");
|
|
3300
|
+
}
|
|
3301
|
+
for (const message of request.history) {
|
|
3302
|
+
if (!Array.isArray(message.content)) continue;
|
|
3303
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
3304
|
+
for (const part of message.content) {
|
|
3305
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
3306
|
+
fail("provider-file");
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
if (Array.isArray(request.input)) {
|
|
3311
|
+
for (const part of request.input) {
|
|
3312
|
+
if (part.type === "image" && part.source.type === "provider-file" && part.source.provider !== this.name) {
|
|
3313
|
+
fail("provider-file");
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
return { effectiveModel };
|
|
3318
|
+
}
|
|
2994
3319
|
/** Anthropic ignores native `json_schema` structurally — Axl uses a system
|
|
2995
3320
|
* prompt JSON instruction + client-side Zod validation (see buildRequest). */
|
|
2996
3321
|
nativeStructuredOutputSupport() {
|
|
@@ -3028,7 +3353,10 @@ var AnthropicProvider = class {
|
|
|
3028
3353
|
// chat - non-streaming completion
|
|
3029
3354
|
// ---------------------------------------------------------------------------
|
|
3030
3355
|
async chat(messages, options) {
|
|
3031
|
-
const headers = this.buildHeaders(
|
|
3356
|
+
const headers = this.buildHeaders(
|
|
3357
|
+
await this.resolveKey(),
|
|
3358
|
+
hasAnthropicProviderFileImage(messages)
|
|
3359
|
+
);
|
|
3032
3360
|
const body = this.buildRequestBody(messages, options, false);
|
|
3033
3361
|
const pricingContext = pricingContextFromBody(body);
|
|
3034
3362
|
const res = await fetchWithRetry(
|
|
@@ -3059,7 +3387,10 @@ var AnthropicProvider = class {
|
|
|
3059
3387
|
// stream - SSE streaming completion
|
|
3060
3388
|
// ---------------------------------------------------------------------------
|
|
3061
3389
|
async *stream(messages, options) {
|
|
3062
|
-
const headers = this.buildHeaders(
|
|
3390
|
+
const headers = this.buildHeaders(
|
|
3391
|
+
await this.resolveKey(),
|
|
3392
|
+
hasAnthropicProviderFileImage(messages)
|
|
3393
|
+
);
|
|
3063
3394
|
const body = this.buildRequestBody(messages, options, true);
|
|
3064
3395
|
const pricingContext = pricingContextFromBody(body);
|
|
3065
3396
|
const res = await fetchWithRetry(
|
|
@@ -3091,11 +3422,12 @@ var AnthropicProvider = class {
|
|
|
3091
3422
|
// ---------------------------------------------------------------------------
|
|
3092
3423
|
// Internal: request building
|
|
3093
3424
|
// ---------------------------------------------------------------------------
|
|
3094
|
-
buildHeaders(apiKey) {
|
|
3425
|
+
buildHeaders(apiKey, requiresFilesBeta = false) {
|
|
3095
3426
|
return {
|
|
3096
3427
|
"Content-Type": "application/json",
|
|
3097
3428
|
"x-api-key": apiKey,
|
|
3098
|
-
"anthropic-version": ANTHROPIC_API_VERSION
|
|
3429
|
+
"anthropic-version": ANTHROPIC_API_VERSION,
|
|
3430
|
+
...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
|
|
3099
3431
|
};
|
|
3100
3432
|
}
|
|
3101
3433
|
/** Extract a human-readable message from an API error response body. */
|
|
@@ -3165,12 +3497,13 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3165
3497
|
mapMessages(messages) {
|
|
3166
3498
|
const result = [];
|
|
3167
3499
|
for (const msg of messages) {
|
|
3500
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
3168
3501
|
if (msg.role === "assistant") {
|
|
3169
3502
|
const replayedThinking = this.getReplayedThinkingBlocks(msg);
|
|
3170
3503
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
3171
3504
|
const content = [...replayedThinking];
|
|
3172
|
-
if (
|
|
3173
|
-
content.push({ type: "text", text
|
|
3505
|
+
if (text) {
|
|
3506
|
+
content.push({ type: "text", text });
|
|
3174
3507
|
}
|
|
3175
3508
|
for (const tc of msg.tool_calls) {
|
|
3176
3509
|
let parsedArgs;
|
|
@@ -3189,10 +3522,10 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3189
3522
|
result.push({ role: "assistant", content });
|
|
3190
3523
|
} else if (replayedThinking.length > 0) {
|
|
3191
3524
|
const content = [...replayedThinking];
|
|
3192
|
-
if (
|
|
3525
|
+
if (text) content.push({ type: "text", text });
|
|
3193
3526
|
result.push({ role: "assistant", content });
|
|
3194
3527
|
} else {
|
|
3195
|
-
result.push({ role: "assistant", content:
|
|
3528
|
+
result.push({ role: "assistant", content: text });
|
|
3196
3529
|
}
|
|
3197
3530
|
} else if (msg.role === "tool") {
|
|
3198
3531
|
result.push({
|
|
@@ -3201,12 +3534,15 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3201
3534
|
{
|
|
3202
3535
|
type: "tool_result",
|
|
3203
3536
|
tool_use_id: msg.tool_call_id,
|
|
3204
|
-
content:
|
|
3537
|
+
content: text
|
|
3205
3538
|
}
|
|
3206
3539
|
]
|
|
3207
3540
|
});
|
|
3208
3541
|
} else if (msg.role === "user") {
|
|
3209
|
-
result.push({
|
|
3542
|
+
result.push({
|
|
3543
|
+
role: "user",
|
|
3544
|
+
content: typeof msg.content === "string" ? msg.content : anthropicImageBlocks(msg.content)
|
|
3545
|
+
});
|
|
3210
3546
|
}
|
|
3211
3547
|
}
|
|
3212
3548
|
return this.mergeConsecutiveRoles(result);
|
|
@@ -3311,7 +3647,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3311
3647
|
"Anthropic fallback responses cannot be continued safely; retry without fallbacks."
|
|
3312
3648
|
);
|
|
3313
3649
|
}
|
|
3314
|
-
const cost = normalized && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
|
|
3650
|
+
const cost = normalized && !pricingContext.hasRichInput && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
|
|
3315
3651
|
return {
|
|
3316
3652
|
content,
|
|
3317
3653
|
thinking_content: thinkingContent || void 0,
|
|
@@ -3348,7 +3684,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3348
3684
|
const doneChunk = () => ({
|
|
3349
3685
|
type: "done",
|
|
3350
3686
|
usage,
|
|
3351
|
-
cost: pricingUsage && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
|
|
3687
|
+
cost: pricingUsage && !pricingContext.hasRichInput && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
|
|
3352
3688
|
providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
|
|
3353
3689
|
});
|
|
3354
3690
|
try {
|
|
@@ -3496,6 +3832,60 @@ function lastFallbackIndex(blocks) {
|
|
|
3496
3832
|
}
|
|
3497
3833
|
|
|
3498
3834
|
// src/providers/gemini.ts
|
|
3835
|
+
function hasRichGeminiMessages(messages) {
|
|
3836
|
+
return messages.some((message) => Array.isArray(message.content));
|
|
3837
|
+
}
|
|
3838
|
+
function geminiImageBase64(source) {
|
|
3839
|
+
return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
|
|
3840
|
+
"base64"
|
|
3841
|
+
);
|
|
3842
|
+
}
|
|
3843
|
+
function geminiInteractionContent(parts, model) {
|
|
3844
|
+
const content = [];
|
|
3845
|
+
for (const part of parts) {
|
|
3846
|
+
if (part.type === "text") {
|
|
3847
|
+
content.push({ type: "text", text: part.text });
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3850
|
+
const { source } = part;
|
|
3851
|
+
if (source.type === "provider-file") {
|
|
3852
|
+
if (source.provider !== "google") {
|
|
3853
|
+
throw new UnsupportedModelInputError({
|
|
3854
|
+
provider: "google",
|
|
3855
|
+
model,
|
|
3856
|
+
modality: "image",
|
|
3857
|
+
source: "provider-file"
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
if (!source.mediaType) {
|
|
3861
|
+
throw new UnsupportedModelInputError({
|
|
3862
|
+
provider: "google",
|
|
3863
|
+
model,
|
|
3864
|
+
modality: "image",
|
|
3865
|
+
source: "provider-file",
|
|
3866
|
+
feature: "Interactions URI image mediaType"
|
|
3867
|
+
});
|
|
3868
|
+
}
|
|
3869
|
+
content.push({
|
|
3870
|
+
type: "image",
|
|
3871
|
+
uri: source.reference,
|
|
3872
|
+
mime_type: source.mediaType
|
|
3873
|
+
});
|
|
3874
|
+
} else if (source.type === "url") {
|
|
3875
|
+
throw new UnsupportedModelInputError({
|
|
3876
|
+
provider: "google",
|
|
3877
|
+
model,
|
|
3878
|
+
modality: "image",
|
|
3879
|
+
source: "url",
|
|
3880
|
+
feature: "direct URL image input; pass bytes/base64 or a Gemini provider-file"
|
|
3881
|
+
});
|
|
3882
|
+
} else {
|
|
3883
|
+
content.push({ type: "image", data: geminiImageBase64(source), mime_type: source.mediaType });
|
|
3884
|
+
}
|
|
3885
|
+
if (part.label) content.push({ type: "text", text: `[Image: ${part.label}]` });
|
|
3886
|
+
}
|
|
3887
|
+
return content;
|
|
3888
|
+
}
|
|
3499
3889
|
var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
3500
3890
|
"additionalProperties",
|
|
3501
3891
|
"$schema",
|
|
@@ -3567,6 +3957,7 @@ var GEMINI_PRICING = {
|
|
|
3567
3957
|
"gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
3568
3958
|
"gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
|
|
3569
3959
|
};
|
|
3960
|
+
var GEMINI_INTERACTIONS_IMAGE_MODELS = /* @__PURE__ */ new Set(["gemini-3.7-flash"]);
|
|
3570
3961
|
function isValidTokenCount2(value) {
|
|
3571
3962
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3572
3963
|
}
|
|
@@ -3654,7 +4045,10 @@ var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
|
|
|
3654
4045
|
["gemini-3.1-flash-lite-preview", "minimal"],
|
|
3655
4046
|
["gemini-3.5-flash", "minimal"],
|
|
3656
4047
|
["gemini-3.5-flash-lite", "minimal"],
|
|
3657
|
-
["gemini-3.6-flash", "minimal"]
|
|
4048
|
+
["gemini-3.6-flash", "minimal"],
|
|
4049
|
+
// L4 live evidence: Interactions gemini-3.7-flash accepts low/medium/high,
|
|
4050
|
+
// but rejects minimal. Keep this exact entry isolated from legacy aliases.
|
|
4051
|
+
["gemini-3.7-flash", "low"]
|
|
3658
4052
|
]);
|
|
3659
4053
|
var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
|
|
3660
4054
|
"gemini-3.5-flash-lite",
|
|
@@ -3681,6 +4075,61 @@ function warnGemini3xEffortNone(model) {
|
|
|
3681
4075
|
}
|
|
3682
4076
|
var GeminiProvider = class {
|
|
3683
4077
|
name = "google";
|
|
4078
|
+
inputCapabilities(model) {
|
|
4079
|
+
return GEMINI_INTERACTIONS_IMAGE_MODELS.has(model) ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
|
|
4080
|
+
}
|
|
4081
|
+
validateInput(request) {
|
|
4082
|
+
const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
|
|
4083
|
+
const fail = (source, feature) => {
|
|
4084
|
+
throw new UnsupportedModelInputError({
|
|
4085
|
+
provider: this.name,
|
|
4086
|
+
model: effectiveModel || request.model,
|
|
4087
|
+
modality: "image",
|
|
4088
|
+
...source ? { source } : {},
|
|
4089
|
+
...feature ? { feature } : {}
|
|
4090
|
+
});
|
|
4091
|
+
};
|
|
4092
|
+
if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(effectiveModel)) {
|
|
4093
|
+
fail(void 0, "image input for this model");
|
|
4094
|
+
}
|
|
4095
|
+
if (request.providerOptions) {
|
|
4096
|
+
const forbidden = [
|
|
4097
|
+
"input",
|
|
4098
|
+
"contents",
|
|
4099
|
+
"messages",
|
|
4100
|
+
"previous_interaction_id",
|
|
4101
|
+
"background",
|
|
4102
|
+
"stream",
|
|
4103
|
+
"store"
|
|
4104
|
+
].find((key) => key in request.providerOptions);
|
|
4105
|
+
if (forbidden) fail(void 0, `raw ${forbidden} providerOptions`);
|
|
4106
|
+
}
|
|
4107
|
+
for (const message of request.history) {
|
|
4108
|
+
if (!Array.isArray(message.content)) continue;
|
|
4109
|
+
if (message.role !== "user") fail(void 0, "rich non-user history");
|
|
4110
|
+
for (const part of message.content) {
|
|
4111
|
+
if (part.type !== "image") continue;
|
|
4112
|
+
if (part.source.type === "url")
|
|
4113
|
+
fail("url", "direct URL image input; pass bytes/base64 or a Gemini provider-file");
|
|
4114
|
+
if (part.source.type === "provider-file" && part.source.provider !== this.name)
|
|
4115
|
+
fail("provider-file");
|
|
4116
|
+
if (part.source.type === "provider-file" && !part.source.mediaType)
|
|
4117
|
+
fail("provider-file", "Interactions URI image mediaType");
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
if (Array.isArray(request.input)) {
|
|
4121
|
+
for (const part of request.input) {
|
|
4122
|
+
if (part.type !== "image") continue;
|
|
4123
|
+
if (part.source.type === "url")
|
|
4124
|
+
fail("url", "direct URL image input; pass bytes/base64 or a Gemini provider-file");
|
|
4125
|
+
if (part.source.type === "provider-file" && part.source.provider !== this.name)
|
|
4126
|
+
fail("provider-file");
|
|
4127
|
+
if (part.source.type === "provider-file" && !part.source.mediaType)
|
|
4128
|
+
fail("provider-file", "Interactions URI image mediaType");
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
return { effectiveModel };
|
|
4132
|
+
}
|
|
3684
4133
|
/** Gemini accepts a `responseSchema` but `sanitizeSchemaForGemini` strips
|
|
3685
4134
|
* keywords it doesn't support (`$ref`/`$defs`/`additionalProperties`/…), so a
|
|
3686
4135
|
* derived schema can lose constraints — lossy, not faithful. */
|
|
@@ -3719,6 +4168,7 @@ var GeminiProvider = class {
|
|
|
3719
4168
|
// chat - non-streaming completion
|
|
3720
4169
|
// ---------------------------------------------------------------------------
|
|
3721
4170
|
async chat(messages, options) {
|
|
4171
|
+
if (hasRichGeminiMessages(messages)) return this.chatInteraction(messages, options);
|
|
3722
4172
|
const body = this.buildRequestBody(messages, options);
|
|
3723
4173
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3724
4174
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
@@ -3751,6 +4201,10 @@ var GeminiProvider = class {
|
|
|
3751
4201
|
// stream - SSE streaming completion
|
|
3752
4202
|
// ---------------------------------------------------------------------------
|
|
3753
4203
|
async *stream(messages, options) {
|
|
4204
|
+
if (hasRichGeminiMessages(messages)) {
|
|
4205
|
+
yield* this.streamInteraction(messages, options);
|
|
4206
|
+
return;
|
|
4207
|
+
}
|
|
3754
4208
|
const body = this.buildRequestBody(messages, options);
|
|
3755
4209
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3756
4210
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
@@ -3782,82 +4236,415 @@ var GeminiProvider = class {
|
|
|
3782
4236
|
yield* this.parseSSEStream(res.body, pricingContext);
|
|
3783
4237
|
}
|
|
3784
4238
|
// ---------------------------------------------------------------------------
|
|
3785
|
-
//
|
|
4239
|
+
// Rich image requests use Gemini Interactions. It is deliberately stateless:
|
|
4240
|
+
// Axl sends the complete application-owned history and never asks Google to
|
|
4241
|
+
// retain an interaction or follow a previous_interaction_id.
|
|
3786
4242
|
// ---------------------------------------------------------------------------
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
4243
|
+
async chatInteraction(messages, options) {
|
|
4244
|
+
this.assertSafeInteractionOptions(options);
|
|
4245
|
+
const body = this.buildInteractionRequestBody(messages, options, false);
|
|
4246
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
4247
|
+
const res = await fetchWithRetry(
|
|
4248
|
+
`${this.baseUrl}/interactions`,
|
|
4249
|
+
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4250
|
+
{ governor: this.governor, provider: this.name }
|
|
4251
|
+
);
|
|
4252
|
+
if (!res.ok) {
|
|
4253
|
+
const errorBody = await res.text();
|
|
4254
|
+
throw buildProviderError({
|
|
4255
|
+
provider: this.name,
|
|
4256
|
+
status: res.status,
|
|
4257
|
+
headers: res.headers,
|
|
4258
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
4259
|
+
body: errorBody
|
|
4260
|
+
});
|
|
4261
|
+
}
|
|
4262
|
+
return this.parseInteractionResponse(await res.json());
|
|
3792
4263
|
}
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
}
|
|
3799
|
-
|
|
4264
|
+
async *streamInteraction(messages, options) {
|
|
4265
|
+
this.assertSafeInteractionOptions(options);
|
|
4266
|
+
const body = this.buildInteractionRequestBody(messages, options, true);
|
|
4267
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
4268
|
+
const res = await fetchWithRetry(
|
|
4269
|
+
`${this.baseUrl}/interactions?alt=sse`,
|
|
4270
|
+
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4271
|
+
{ governor: this.governor, provider: this.name }
|
|
4272
|
+
);
|
|
4273
|
+
if (!res.ok) {
|
|
4274
|
+
const errorBody = await res.text();
|
|
4275
|
+
throw buildProviderError({
|
|
4276
|
+
provider: this.name,
|
|
4277
|
+
status: res.status,
|
|
4278
|
+
headers: res.headers,
|
|
4279
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
4280
|
+
body: errorBody
|
|
4281
|
+
});
|
|
3800
4282
|
}
|
|
3801
|
-
|
|
4283
|
+
if (!res.body) throw new Error("Gemini Interactions stream has no body");
|
|
4284
|
+
yield* this.parseInteractionSSEStream(res.body);
|
|
3802
4285
|
}
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
4286
|
+
buildInteractionRequestBody(messages, options, stream) {
|
|
4287
|
+
const model = this.requestModel(options);
|
|
4288
|
+
const systemInstruction = messages.filter((message) => message.role === "system").map((message) => typeof message.content === "string" ? message.content : "").filter(Boolean).join("\n\n");
|
|
4289
|
+
const body = {
|
|
4290
|
+
model,
|
|
4291
|
+
input: this.mapInteractionInput(
|
|
4292
|
+
messages.filter((message) => message.role !== "system"),
|
|
4293
|
+
model
|
|
4294
|
+
),
|
|
4295
|
+
stream
|
|
3811
4296
|
};
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
throw new Error(
|
|
3821
|
-
`${model} does not support a terminal assistant/model prefill; end the request with a user or tool message.`
|
|
3822
|
-
);
|
|
3823
|
-
}
|
|
4297
|
+
if (systemInstruction) body.system_instruction = systemInstruction;
|
|
4298
|
+
if (options.tools?.length) {
|
|
4299
|
+
body.tools = options.tools.map((tool2) => ({
|
|
4300
|
+
type: "function",
|
|
4301
|
+
name: tool2.function.name,
|
|
4302
|
+
description: tool2.function.description,
|
|
4303
|
+
parameters: sanitizeSchemaForGemini(tool2.function.parameters)
|
|
4304
|
+
}));
|
|
3824
4305
|
}
|
|
3825
|
-
|
|
3826
|
-
|
|
4306
|
+
const generationConfig = {};
|
|
4307
|
+
if (options.maxTokens !== void 0) generationConfig.max_output_tokens = options.maxTokens;
|
|
4308
|
+
if (options.stop) generationConfig.stop_sequences = options.stop;
|
|
4309
|
+
if (options.toolChoice !== void 0) {
|
|
4310
|
+
generationConfig.tool_choice = typeof options.toolChoice === "string" ? options.toolChoice === "required" ? "any" : options.toolChoice : { allowed_tools: { mode: "any", tools: [options.toolChoice.function.name] } };
|
|
4311
|
+
}
|
|
4312
|
+
const thinking = resolveThinkingOptions(options);
|
|
4313
|
+
if (thinking.thinkingDisabled) {
|
|
4314
|
+
generationConfig.thinking_level = isGemini3x(model) ? minThinkingLevel(model) : "minimal";
|
|
4315
|
+
} else if (thinking.hasBudgetOverride) {
|
|
4316
|
+
generationConfig.thinking_level = budgetToThinkingLevel(thinking.thinkingBudget);
|
|
4317
|
+
} else if (thinking.activeEffort) {
|
|
4318
|
+
generationConfig.thinking_level = isGemini3x(model) ? THINKING_LEVELS[thinking.activeEffort] ?? "medium" : budgetToThinkingLevel(THINKING_BUDGETS2[thinking.activeEffort] ?? 5e3);
|
|
4319
|
+
}
|
|
4320
|
+
if (thinking.includeThoughts) generationConfig.thinking_summaries = "auto";
|
|
4321
|
+
if (Object.keys(generationConfig).length) body.generation_config = generationConfig;
|
|
4322
|
+
if (options.responseFormat?.type && options.responseFormat.type !== "text") {
|
|
4323
|
+
body.response_format = {
|
|
4324
|
+
type: "text",
|
|
4325
|
+
mime_type: "application/json",
|
|
4326
|
+
...options.responseFormat.type === "json_schema" ? { schema: sanitizeSchemaForGemini(options.responseFormat.json_schema.schema) } : {}
|
|
4327
|
+
};
|
|
4328
|
+
}
|
|
4329
|
+
if (options.providerOptions) {
|
|
4330
|
+
const overrides = { ...options.providerOptions };
|
|
4331
|
+
delete overrides.model;
|
|
4332
|
+
delete overrides.input;
|
|
4333
|
+
delete overrides.contents;
|
|
4334
|
+
delete overrides.messages;
|
|
4335
|
+
delete overrides.previous_interaction_id;
|
|
4336
|
+
delete overrides.background;
|
|
4337
|
+
delete overrides.stream;
|
|
4338
|
+
delete overrides.store;
|
|
4339
|
+
const rawGenerationConfig = overrides.generation_config;
|
|
4340
|
+
if (rawGenerationConfig && typeof rawGenerationConfig === "object" && !Array.isArray(rawGenerationConfig)) {
|
|
4341
|
+
const generationConfigOverrides = { ...rawGenerationConfig };
|
|
4342
|
+
delete generationConfigOverrides.temperature;
|
|
4343
|
+
if (Object.keys(generationConfigOverrides).length)
|
|
4344
|
+
overrides.generation_config = generationConfigOverrides;
|
|
4345
|
+
else delete overrides.generation_config;
|
|
4346
|
+
}
|
|
4347
|
+
Object.assign(body, overrides);
|
|
4348
|
+
}
|
|
4349
|
+
body.stream = stream;
|
|
4350
|
+
body.store = false;
|
|
4351
|
+
return body;
|
|
4352
|
+
}
|
|
4353
|
+
assertSafeInteractionOptions(options) {
|
|
4354
|
+
const model = this.requestModel(options);
|
|
4355
|
+
const fail = (feature) => {
|
|
4356
|
+
throw new UnsupportedModelInputError({
|
|
4357
|
+
provider: this.name,
|
|
4358
|
+
model,
|
|
4359
|
+
modality: "image",
|
|
4360
|
+
feature
|
|
4361
|
+
});
|
|
4362
|
+
};
|
|
4363
|
+
if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(model)) fail("image input for this model");
|
|
4364
|
+
const forbidden = [
|
|
4365
|
+
"input",
|
|
4366
|
+
"contents",
|
|
4367
|
+
"messages",
|
|
4368
|
+
"previous_interaction_id",
|
|
4369
|
+
"background",
|
|
4370
|
+
"stream",
|
|
4371
|
+
"store"
|
|
4372
|
+
].find((key) => key in (options.providerOptions ?? {}));
|
|
4373
|
+
if (forbidden) fail(`raw ${forbidden} providerOptions`);
|
|
4374
|
+
}
|
|
4375
|
+
mapInteractionInput(messages, model) {
|
|
4376
|
+
const steps = [];
|
|
3827
4377
|
for (const message of messages) {
|
|
3828
|
-
if (message.role
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
4378
|
+
if (message.role === "user") {
|
|
4379
|
+
steps.push({
|
|
4380
|
+
type: "user_input",
|
|
4381
|
+
content: Array.isArray(message.content) ? geminiInteractionContent(message.content, model) : [{ type: "text", text: message.content }]
|
|
4382
|
+
});
|
|
4383
|
+
} else if (message.role === "assistant") {
|
|
4384
|
+
const nativeSteps = message.providerMetadata?.geminiInteractionSteps;
|
|
4385
|
+
if (Array.isArray(nativeSteps) && nativeSteps.every(isGeminiInteractionStep)) {
|
|
4386
|
+
steps.push(...nativeSteps);
|
|
4387
|
+
continue;
|
|
3838
4388
|
}
|
|
3839
|
-
|
|
4389
|
+
if (typeof message.content === "string" && message.content) {
|
|
4390
|
+
steps.push({ type: "model_output", content: [{ type: "text", text: message.content }] });
|
|
4391
|
+
}
|
|
4392
|
+
for (const call of message.tool_calls ?? []) {
|
|
4393
|
+
steps.push({
|
|
4394
|
+
type: "function_call",
|
|
4395
|
+
id: call.id,
|
|
4396
|
+
name: call.function.name,
|
|
4397
|
+
arguments: safeJsonObject(call.function.arguments)
|
|
4398
|
+
});
|
|
4399
|
+
}
|
|
4400
|
+
} else if (message.role === "tool") {
|
|
4401
|
+
steps.push({
|
|
4402
|
+
type: "function_result",
|
|
4403
|
+
call_id: message.tool_call_id ?? "",
|
|
4404
|
+
result: [
|
|
4405
|
+
{ type: "text", text: typeof message.content === "string" ? message.content : "" }
|
|
4406
|
+
]
|
|
4407
|
+
});
|
|
3840
4408
|
}
|
|
3841
4409
|
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
4410
|
+
return steps;
|
|
4411
|
+
}
|
|
4412
|
+
parseInteractionResponse(json) {
|
|
4413
|
+
let content = "";
|
|
4414
|
+
let thinkingContent = "";
|
|
4415
|
+
const toolCalls = [];
|
|
4416
|
+
for (const step of json.steps ?? []) {
|
|
4417
|
+
if (step.type === "model_output") content += interactionText(step.content);
|
|
4418
|
+
else if (step.type === "thought") thinkingContent += interactionText(step.summary);
|
|
4419
|
+
else if (step.type === "function_call" && step.id && step.name) {
|
|
4420
|
+
toolCalls.push({
|
|
4421
|
+
id: step.id,
|
|
4422
|
+
type: "function",
|
|
4423
|
+
function: { name: step.name, arguments: JSON.stringify(step.arguments ?? {}) }
|
|
4424
|
+
});
|
|
3850
4425
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
4426
|
+
}
|
|
4427
|
+
this.assertInteractionTerminalStatus(json.status, toolCalls.length);
|
|
4428
|
+
const usage = normalizeInteractionUsage(json.usage);
|
|
4429
|
+
return {
|
|
4430
|
+
content,
|
|
4431
|
+
thinking_content: thinkingContent || void 0,
|
|
4432
|
+
tool_calls: toolCalls.length ? toolCalls : void 0,
|
|
4433
|
+
usage,
|
|
4434
|
+
// Interactions returns modality usage but no authoritative monetary cost.
|
|
4435
|
+
cost: void 0,
|
|
4436
|
+
providerMetadata: json.steps?.length ? { geminiInteractionSteps: json.steps.filter(isGeminiInteractionStep) } : void 0
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4439
|
+
async *parseInteractionSSEStream(body) {
|
|
4440
|
+
const reader = body.getReader();
|
|
4441
|
+
const decoder = new TextDecoder();
|
|
4442
|
+
let buffer = "";
|
|
4443
|
+
const steps = /* @__PURE__ */ new Map();
|
|
4444
|
+
const argumentBuffers = /* @__PURE__ */ new Map();
|
|
4445
|
+
const stopped = /* @__PURE__ */ new Set();
|
|
4446
|
+
let completed = false;
|
|
4447
|
+
try {
|
|
4448
|
+
while (true) {
|
|
4449
|
+
const { done, value } = await reader.read();
|
|
4450
|
+
if (done) break;
|
|
4451
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4452
|
+
const lines = buffer.split("\n");
|
|
4453
|
+
buffer = lines.pop() ?? "";
|
|
4454
|
+
for (const line of lines) {
|
|
4455
|
+
const trimmed = line.trim();
|
|
4456
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
4457
|
+
if (trimmed.slice(6) === "[DONE]") continue;
|
|
4458
|
+
let event;
|
|
4459
|
+
try {
|
|
4460
|
+
event = JSON.parse(trimmed.slice(6));
|
|
4461
|
+
} catch {
|
|
4462
|
+
continue;
|
|
4463
|
+
}
|
|
4464
|
+
if (event.event_type === "error") {
|
|
4465
|
+
throw new Error(event.error?.message ?? "Gemini Interactions stream failed");
|
|
4466
|
+
}
|
|
4467
|
+
if (event.event_type === "step.start" && event.step) {
|
|
4468
|
+
const index = event.index;
|
|
4469
|
+
if (index === void 0 || steps.has(index)) {
|
|
4470
|
+
throw new Error("Gemini Interactions stream contained an invalid step.start index");
|
|
4471
|
+
}
|
|
4472
|
+
const step = cloneInteractionStep(event.step);
|
|
4473
|
+
steps.set(index, step);
|
|
4474
|
+
if (step.type === "model_output") {
|
|
4475
|
+
for (const text of interactionTextChunks(step.content)) {
|
|
4476
|
+
yield { type: "text_delta", content: text };
|
|
4477
|
+
}
|
|
4478
|
+
} else if (step.type === "thought") {
|
|
4479
|
+
for (const text of interactionTextChunks(step.summary)) {
|
|
4480
|
+
yield { type: "thinking_delta", content: text };
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
if (step.type === "function_call" && step.id && step.name) {
|
|
4484
|
+
argumentBuffers.set(index, "");
|
|
4485
|
+
yield { type: "tool_call_delta", id: step.id, name: step.name };
|
|
4486
|
+
}
|
|
4487
|
+
} else if (event.event_type === "step.delta" && event.delta) {
|
|
4488
|
+
const delta = event.delta;
|
|
4489
|
+
const index = event.index;
|
|
4490
|
+
if (index === void 0 || stopped.has(index)) {
|
|
4491
|
+
throw new Error("Gemini Interactions stream delta arrived outside an active step");
|
|
4492
|
+
}
|
|
4493
|
+
const step = steps.get(index);
|
|
4494
|
+
if (!step)
|
|
4495
|
+
throw new Error("Gemini Interactions stream delta arrived before step.start");
|
|
4496
|
+
if (delta.type === "text" && typeof delta.text === "string" && step.type === "model_output") {
|
|
4497
|
+
appendInteractionContent(step, "content", { type: "text", text: delta.text });
|
|
4498
|
+
yield { type: "text_delta", content: delta.text };
|
|
4499
|
+
} else if (delta.type === "thought_summary" && step.type === "thought" && delta.content) {
|
|
4500
|
+
appendInteractionContent(step, "summary", delta.content);
|
|
4501
|
+
for (const text of interactionTextChunks([delta.content])) {
|
|
4502
|
+
yield { type: "thinking_delta", content: text };
|
|
4503
|
+
}
|
|
4504
|
+
} else if (delta.type === "thought_signature" && step.type === "thought" && typeof delta.signature === "string") {
|
|
4505
|
+
step.signature = delta.signature;
|
|
4506
|
+
} else if (delta.type === "arguments_delta" && step.type === "function_call" && typeof delta.arguments === "string") {
|
|
4507
|
+
argumentBuffers.set(index, (argumentBuffers.get(index) ?? "") + delta.arguments);
|
|
4508
|
+
if (step.id)
|
|
4509
|
+
yield { type: "tool_call_delta", id: step.id, arguments: delta.arguments };
|
|
4510
|
+
}
|
|
4511
|
+
} else if (event.event_type === "step.stop") {
|
|
4512
|
+
const index = event.index;
|
|
4513
|
+
const step = index === void 0 ? void 0 : steps.get(index);
|
|
4514
|
+
if (step === void 0 || stopped.has(index)) {
|
|
4515
|
+
throw new Error("Gemini Interactions stream contained an invalid step.stop index");
|
|
4516
|
+
}
|
|
4517
|
+
if (step.type === "function_call") {
|
|
4518
|
+
const rawArguments = argumentBuffers.get(index) ?? "";
|
|
4519
|
+
if (rawArguments) {
|
|
4520
|
+
try {
|
|
4521
|
+
const parsed = JSON.parse(rawArguments);
|
|
4522
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4523
|
+
throw new Error("not an object");
|
|
4524
|
+
}
|
|
4525
|
+
step.arguments = parsed;
|
|
4526
|
+
} catch {
|
|
4527
|
+
throw new Error(
|
|
4528
|
+
"Gemini Interactions function_call arguments were not valid JSON"
|
|
4529
|
+
);
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
stopped.add(index);
|
|
4534
|
+
} else if (event.event_type === "interaction.completed") {
|
|
4535
|
+
if (steps.size !== stopped.size) {
|
|
4536
|
+
throw new Error("Gemini Interactions stream completed before all steps stopped");
|
|
4537
|
+
}
|
|
4538
|
+
const toolCallCount = [...steps.values()].filter(
|
|
4539
|
+
(step) => step.type === "function_call" && !!step.id && !!step.name
|
|
4540
|
+
).length;
|
|
4541
|
+
this.assertInteractionTerminalStatus(event.interaction?.status, toolCallCount);
|
|
4542
|
+
completed = true;
|
|
4543
|
+
yield {
|
|
4544
|
+
type: "done",
|
|
4545
|
+
usage: normalizeInteractionUsage(event.interaction?.usage),
|
|
4546
|
+
providerMetadata: steps.size ? {
|
|
4547
|
+
geminiInteractionSteps: [...steps.entries()].sort(([a], [b]) => a - b).map(([, step]) => step)
|
|
4548
|
+
} : void 0
|
|
4549
|
+
};
|
|
4550
|
+
return;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
if (!completed)
|
|
4555
|
+
throw new Error("Gemini Interactions stream ended before interaction.completed");
|
|
4556
|
+
} finally {
|
|
4557
|
+
reader.releaseLock();
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
assertInteractionTerminalStatus(status, toolCallCount) {
|
|
4561
|
+
if (status === "completed") return;
|
|
4562
|
+
if (status === "requires_action" && toolCallCount > 0) return;
|
|
4563
|
+
const normalizedStatus = typeof status === "string" ? status : "unknown";
|
|
4564
|
+
throw new ProviderError({
|
|
4565
|
+
provider: this.name,
|
|
4566
|
+
status: 0,
|
|
4567
|
+
retryable: false,
|
|
4568
|
+
message: `Gemini Interactions ended with non-success status: ${normalizedStatus}`
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4571
|
+
// ---------------------------------------------------------------------------
|
|
4572
|
+
// Internal: request building
|
|
4573
|
+
// ---------------------------------------------------------------------------
|
|
4574
|
+
buildHeaders(apiKey) {
|
|
4575
|
+
return {
|
|
4576
|
+
"Content-Type": "application/json",
|
|
4577
|
+
"x-goog-api-key": apiKey
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4580
|
+
extractErrorMessage(body, status) {
|
|
4581
|
+
try {
|
|
4582
|
+
const json = JSON.parse(body);
|
|
4583
|
+
if (json.error?.message) {
|
|
4584
|
+
return `Gemini API error (${status}): ${json.error.message}`;
|
|
4585
|
+
}
|
|
4586
|
+
} catch {
|
|
4587
|
+
}
|
|
4588
|
+
return `Gemini API error (${status}): ${body}`;
|
|
4589
|
+
}
|
|
4590
|
+
pricingContext(fallbackModel, body) {
|
|
4591
|
+
return {
|
|
4592
|
+
// Gemini's model is normally selected in the URL. providerOptions is an
|
|
4593
|
+
// explicit raw escape hatch, so honor a post-merge model override for both
|
|
4594
|
+
// the URL and pricing rather than pricing a different request.
|
|
4595
|
+
model: typeof body.model === "string" ? body.model : fallbackModel,
|
|
4596
|
+
serviceTier: body.serviceTier,
|
|
4597
|
+
eligibleRequest: this.baseUrl === "https://generativelanguage.googleapis.com/v1beta" && this.isTextOnlyClientRequest(body)
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
requestModel(options) {
|
|
4601
|
+
return typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
4602
|
+
}
|
|
4603
|
+
assertSafeGemini3Continuation(messages, model) {
|
|
4604
|
+
if (GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE.has(model)) {
|
|
4605
|
+
const mapped = this.mapMessages(messages.filter((message) => message.role !== "system"));
|
|
4606
|
+
if (mapped.at(-1)?.role === "model") {
|
|
4607
|
+
throw new Error(
|
|
4608
|
+
`${model} does not support a terminal assistant/model prefill; end the request with a user or tool message.`
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
if (!isGemini3x(model)) return;
|
|
4613
|
+
const replayedNativeCallIds = /* @__PURE__ */ new Set();
|
|
4614
|
+
for (const message of messages) {
|
|
4615
|
+
if (message.role !== "assistant" || !Array.isArray(message.providerMetadata?.geminiParts)) {
|
|
4616
|
+
continue;
|
|
4617
|
+
}
|
|
4618
|
+
for (const part of message.providerMetadata.geminiParts) {
|
|
4619
|
+
const id = part !== null && typeof part === "object" ? part.functionCall?.id : void 0;
|
|
4620
|
+
if (!id) continue;
|
|
4621
|
+
if (replayedNativeCallIds.has(id)) {
|
|
4622
|
+
throw new Error(
|
|
4623
|
+
`${model} native functionCall ids must be globally unique across a replay.`
|
|
4624
|
+
);
|
|
4625
|
+
}
|
|
4626
|
+
replayedNativeCallIds.add(id);
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
const consumedToolMessages = /* @__PURE__ */ new Set();
|
|
4630
|
+
for (const [assistantIndex, message] of messages.entries()) {
|
|
4631
|
+
if (message.role !== "assistant" || !message.tool_calls?.length) continue;
|
|
4632
|
+
const rawParts = message.providerMetadata?.geminiParts;
|
|
4633
|
+
if (!Array.isArray(rawParts)) {
|
|
4634
|
+
throw new Error(
|
|
4635
|
+
`${model} tool continuations require providerMetadata.geminiParts with thought signatures.`
|
|
4636
|
+
);
|
|
4637
|
+
}
|
|
4638
|
+
const nativeCalls = rawParts.flatMap((part) => {
|
|
4639
|
+
const functionCall = part !== null && typeof part === "object" ? part.functionCall : void 0;
|
|
4640
|
+
return functionCall?.id ? [{ id: functionCall.id, name: functionCall.name, part }] : [];
|
|
4641
|
+
});
|
|
4642
|
+
const expectedCalls = new Map(
|
|
4643
|
+
message.tool_calls.map((call) => [call.id, call.function.name])
|
|
4644
|
+
);
|
|
4645
|
+
if (expectedCalls.size !== message.tool_calls.length || nativeCalls.length !== message.tool_calls.length || new Set(nativeCalls.map((call) => call.id)).size !== nativeCalls.length || typeof nativeCalls[0]?.part.thoughtSignature !== "string" || nativeCalls[0].part.thoughtSignature.length === 0 || nativeCalls.some((call) => expectedCalls.get(call.id) !== call.name)) {
|
|
4646
|
+
throw new Error(
|
|
4647
|
+
`${model} tool continuations require exact native functionCall id/name and a thoughtSignature on the first function call.`
|
|
3861
4648
|
);
|
|
3862
4649
|
}
|
|
3863
4650
|
const seenResponses = /* @__PURE__ */ new Set();
|
|
@@ -4036,14 +4823,15 @@ var GeminiProvider = class {
|
|
|
4036
4823
|
}
|
|
4037
4824
|
const result = [];
|
|
4038
4825
|
for (const msg of messages) {
|
|
4826
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
4039
4827
|
if (msg.role === "assistant") {
|
|
4040
4828
|
const rawParts = msg.providerMetadata?.geminiParts;
|
|
4041
4829
|
if (rawParts && rawParts.length > 0) {
|
|
4042
4830
|
result.push({ role: "model", parts: rawParts });
|
|
4043
4831
|
} else {
|
|
4044
4832
|
const parts = [];
|
|
4045
|
-
if (
|
|
4046
|
-
parts.push({ text
|
|
4833
|
+
if (text) {
|
|
4834
|
+
parts.push({ text });
|
|
4047
4835
|
}
|
|
4048
4836
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
4049
4837
|
for (const tc of msg.tool_calls) {
|
|
@@ -4069,7 +4857,7 @@ var GeminiProvider = class {
|
|
|
4069
4857
|
const functionName = (msg.tool_call_id ? geminiNativeNames.get(msg.tool_call_id) : void 0) ?? toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
|
|
4070
4858
|
const functionResponse = {
|
|
4071
4859
|
name: functionName,
|
|
4072
|
-
response: parseGeminiFunctionResponse(
|
|
4860
|
+
response: parseGeminiFunctionResponse(text)
|
|
4073
4861
|
};
|
|
4074
4862
|
if (msg.tool_call_id && geminiNativeIds.has(msg.tool_call_id)) {
|
|
4075
4863
|
functionResponse.id = msg.tool_call_id;
|
|
@@ -4079,7 +4867,7 @@ var GeminiProvider = class {
|
|
|
4079
4867
|
parts: [{ functionResponse }]
|
|
4080
4868
|
});
|
|
4081
4869
|
} else if (msg.role === "user") {
|
|
4082
|
-
result.push({ role: "user", parts: [{ text
|
|
4870
|
+
result.push({ role: "user", parts: [{ text }] });
|
|
4083
4871
|
}
|
|
4084
4872
|
}
|
|
4085
4873
|
return this.mergeConsecutiveRoles(result);
|
|
@@ -4267,6 +5055,56 @@ var GeminiProvider = class {
|
|
|
4267
5055
|
}
|
|
4268
5056
|
}
|
|
4269
5057
|
};
|
|
5058
|
+
function isGeminiInteractionStep(value) {
|
|
5059
|
+
return value !== null && typeof value === "object" && ["user_input", "model_output", "thought", "function_call", "function_result"].includes(
|
|
5060
|
+
value.type
|
|
5061
|
+
);
|
|
5062
|
+
}
|
|
5063
|
+
function interactionText(content) {
|
|
5064
|
+
return content?.map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").join("") ?? "";
|
|
5065
|
+
}
|
|
5066
|
+
function interactionTextChunks(content) {
|
|
5067
|
+
return content?.flatMap(
|
|
5068
|
+
(part) => part.type === "text" && typeof part.text === "string" ? [part.text] : []
|
|
5069
|
+
) ?? [];
|
|
5070
|
+
}
|
|
5071
|
+
function cloneInteractionStep(step) {
|
|
5072
|
+
return JSON.parse(JSON.stringify(step));
|
|
5073
|
+
}
|
|
5074
|
+
function appendInteractionContent(step, field, content) {
|
|
5075
|
+
const existing = step[field] ?? [];
|
|
5076
|
+
const last = existing.at(-1);
|
|
5077
|
+
if (content.type === "text" && last?.type === "text" && typeof content.text === "string" && typeof last.text === "string") {
|
|
5078
|
+
last.text += content.text;
|
|
5079
|
+
} else {
|
|
5080
|
+
existing.push(content);
|
|
5081
|
+
}
|
|
5082
|
+
step[field] = existing;
|
|
5083
|
+
}
|
|
5084
|
+
function safeJsonObject(value) {
|
|
5085
|
+
try {
|
|
5086
|
+
const parsed = JSON.parse(value);
|
|
5087
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
5088
|
+
} catch {
|
|
5089
|
+
return {};
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
5092
|
+
function normalizeInteractionUsage(usage) {
|
|
5093
|
+
if (!usage || !isValidTokenCount2(usage.total_input_tokens) || !isValidTokenCount2(usage.total_output_tokens) || !isValidTokenCount2(usage.total_tokens)) {
|
|
5094
|
+
return void 0;
|
|
5095
|
+
}
|
|
5096
|
+
const cached = usage.total_cached_tokens;
|
|
5097
|
+
const thinking = usage.total_thought_tokens;
|
|
5098
|
+
if (cached !== void 0 && (!isValidTokenCount2(cached) || cached > usage.total_input_tokens) || thinking !== void 0 && !isValidTokenCount2(thinking))
|
|
5099
|
+
return void 0;
|
|
5100
|
+
return {
|
|
5101
|
+
prompt_tokens: usage.total_input_tokens,
|
|
5102
|
+
completion_tokens: usage.total_output_tokens,
|
|
5103
|
+
total_tokens: usage.total_tokens,
|
|
5104
|
+
...cached ? { cached_tokens: cached } : {},
|
|
5105
|
+
...thinking ? { reasoning_tokens: thinking } : {}
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
4270
5108
|
|
|
4271
5109
|
// src/providers/profiles/openrouter.ts
|
|
4272
5110
|
var OPENROUTER_PROFILE = {
|
|
@@ -4698,68 +5536,1225 @@ var ProviderRegistry = class {
|
|
|
4698
5536
|
if (cached) return cached;
|
|
4699
5537
|
const factory = this.factories.get(name);
|
|
4700
5538
|
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(", ")}`);
|
|
5539
|
+
const instance = factory(config);
|
|
5540
|
+
this.instances.set(name, instance);
|
|
5541
|
+
return instance;
|
|
5542
|
+
}
|
|
5543
|
+
if (this.fallbackInstance) {
|
|
5544
|
+
return this.fallbackInstance;
|
|
5545
|
+
}
|
|
5546
|
+
throw new Error(`Unknown provider "${name}". Registered providers: ${this.list().join(", ")}`);
|
|
5547
|
+
}
|
|
5548
|
+
/**
|
|
5549
|
+
* Resolve a "provider:model" URI string into a Provider instance and model name.
|
|
5550
|
+
*
|
|
5551
|
+
* Supported formats:
|
|
5552
|
+
* - "openai:gpt-4o" -> provider=openai, model=gpt-4o
|
|
5553
|
+
* - "anthropic:claude-3" -> provider=anthropic, model=claude-3
|
|
5554
|
+
* - "gpt-4o" -> uses defaultProvider from config, model=gpt-4o
|
|
5555
|
+
* - undefined / empty -> uses defaultProvider and defaultModel from config
|
|
5556
|
+
*
|
|
5557
|
+
* @param uri Provider:model string, or just a model name
|
|
5558
|
+
* @param config Axl configuration for provider options and defaults
|
|
5559
|
+
*/
|
|
5560
|
+
resolve(uri, config = {}) {
|
|
5561
|
+
if (!uri) {
|
|
5562
|
+
const providerName2 = config.defaultProvider ?? "openai";
|
|
5563
|
+
const model2 = config.defaultModel ?? "gpt-4o";
|
|
5564
|
+
return { provider: this.get(providerName2, config), model: model2 };
|
|
5565
|
+
}
|
|
5566
|
+
const colonIndex = uri.indexOf(":");
|
|
5567
|
+
if (colonIndex === -1) {
|
|
5568
|
+
const providerName2 = config.defaultProvider ?? "openai";
|
|
5569
|
+
return { provider: this.get(providerName2, config), model: uri };
|
|
5570
|
+
}
|
|
5571
|
+
const providerName = uri.slice(0, colonIndex);
|
|
5572
|
+
const model = uri.slice(colonIndex + 1);
|
|
5573
|
+
if (!providerName || !model) {
|
|
5574
|
+
throw new Error(
|
|
5575
|
+
`Invalid provider URI "${uri}". Expected format: "provider:model" (e.g. "openai:gpt-4o")`
|
|
5576
|
+
);
|
|
5577
|
+
}
|
|
5578
|
+
return { provider: this.get(providerName, config), model };
|
|
5579
|
+
}
|
|
5580
|
+
/**
|
|
5581
|
+
* Clear all cached provider instances. Useful for testing or reconfiguration.
|
|
5582
|
+
*/
|
|
5583
|
+
clearCache() {
|
|
5584
|
+
this.instances.clear();
|
|
5585
|
+
}
|
|
5586
|
+
/**
|
|
5587
|
+
* Clear all registered factories (including built-ins).
|
|
5588
|
+
* Useful for test runtimes where only explicitly registered instances should be used.
|
|
5589
|
+
*/
|
|
5590
|
+
clearFactories() {
|
|
5591
|
+
this.factories.clear();
|
|
5592
|
+
}
|
|
5593
|
+
};
|
|
5594
|
+
var defaultRegistry = new ProviderRegistry();
|
|
5595
|
+
|
|
5596
|
+
// src/transcription.ts
|
|
5597
|
+
var MAX_INLINE_TRANSCRIPTION_BYTES = 25 * 1024 * 1024;
|
|
5598
|
+
var RESERVED_PROVIDER_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
5599
|
+
"model",
|
|
5600
|
+
"audio",
|
|
5601
|
+
"signal",
|
|
5602
|
+
"cleanup",
|
|
5603
|
+
"cleanupStatus",
|
|
5604
|
+
"usage",
|
|
5605
|
+
"cost",
|
|
5606
|
+
"pricingStatus",
|
|
5607
|
+
"accounting"
|
|
5608
|
+
]);
|
|
5609
|
+
function nonEmptyString(value) {
|
|
5610
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
5611
|
+
}
|
|
5612
|
+
function cloneValue(value) {
|
|
5613
|
+
try {
|
|
5614
|
+
return structuredClone(value);
|
|
5615
|
+
} catch {
|
|
5616
|
+
throw new InvalidTranscriptionInputError("Transcription options must be structured-cloneable");
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
function normalizeTranscriptionRequest(request) {
|
|
5620
|
+
if (!request || typeof request !== "object" || !nonEmptyString(request.model)) {
|
|
5621
|
+
throw new InvalidTranscriptionInputError("A transcription model URI is required");
|
|
5622
|
+
}
|
|
5623
|
+
const audio = request.audio;
|
|
5624
|
+
if (!audio || typeof audio !== "object") {
|
|
5625
|
+
throw new InvalidTranscriptionInputError("A transcription audio source is required");
|
|
5626
|
+
}
|
|
5627
|
+
let normalizedAudio;
|
|
5628
|
+
if (audio.type === "bytes") {
|
|
5629
|
+
if (!(audio.data instanceof Uint8Array) || audio.data.byteLength === 0 || !nonEmptyString(audio.mediaType)) {
|
|
5630
|
+
throw new InvalidTranscriptionInputError(
|
|
5631
|
+
"Inline transcription bytes require nonempty data and mediaType"
|
|
5632
|
+
);
|
|
5633
|
+
}
|
|
5634
|
+
if (audio.data.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES) {
|
|
5635
|
+
throw new InvalidTranscriptionInputError(
|
|
5636
|
+
"Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
|
|
5637
|
+
);
|
|
5638
|
+
}
|
|
5639
|
+
normalizedAudio = {
|
|
5640
|
+
type: "bytes",
|
|
5641
|
+
data: new Uint8Array(audio.data),
|
|
5642
|
+
mediaType: audio.mediaType
|
|
5643
|
+
};
|
|
5644
|
+
} else if (audio.type === "base64") {
|
|
5645
|
+
if (!nonEmptyString(audio.data) || !nonEmptyString(audio.mediaType)) {
|
|
5646
|
+
throw new InvalidTranscriptionInputError(
|
|
5647
|
+
"Inline base64 transcription audio requires data and mediaType"
|
|
5648
|
+
);
|
|
5649
|
+
}
|
|
5650
|
+
if (audio.data.length > 4 * Math.ceil(MAX_INLINE_TRANSCRIPTION_BYTES / 3)) {
|
|
5651
|
+
throw new InvalidTranscriptionInputError(
|
|
5652
|
+
"Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
|
|
5653
|
+
);
|
|
5654
|
+
}
|
|
5655
|
+
let decoded;
|
|
5656
|
+
try {
|
|
5657
|
+
decoded = Uint8Array.from(Buffer.from(audio.data, "base64"));
|
|
5658
|
+
} catch {
|
|
5659
|
+
throw new InvalidTranscriptionInputError(
|
|
5660
|
+
"Transcription base64 must be canonical and decode to audio"
|
|
5661
|
+
);
|
|
5662
|
+
}
|
|
5663
|
+
if (!decoded.byteLength || Buffer.from(decoded).toString("base64") !== audio.data) {
|
|
5664
|
+
throw new InvalidTranscriptionInputError(
|
|
5665
|
+
"Transcription base64 must be canonical and decode to nonempty audio"
|
|
5666
|
+
);
|
|
5667
|
+
}
|
|
5668
|
+
if (decoded.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES) {
|
|
5669
|
+
throw new InvalidTranscriptionInputError(
|
|
5670
|
+
"Inline transcription audio must not exceed 25 MiB; chunk it or use a supported provider-file source"
|
|
5671
|
+
);
|
|
5672
|
+
}
|
|
5673
|
+
normalizedAudio = { type: "base64", data: audio.data, mediaType: audio.mediaType };
|
|
5674
|
+
} else if (audio.type === "provider-file") {
|
|
5675
|
+
if (!nonEmptyString(audio.provider) || !nonEmptyString(audio.reference)) {
|
|
5676
|
+
throw new InvalidTranscriptionInputError(
|
|
5677
|
+
"A provider-file transcription source requires provider and reference"
|
|
5678
|
+
);
|
|
5679
|
+
}
|
|
5680
|
+
if (audio.mediaType !== void 0 && !nonEmptyString(audio.mediaType)) {
|
|
5681
|
+
throw new InvalidTranscriptionInputError(
|
|
5682
|
+
"Transcription provider-file mediaType must be nonempty when supplied"
|
|
5683
|
+
);
|
|
5684
|
+
}
|
|
5685
|
+
normalizedAudio = {
|
|
5686
|
+
type: "provider-file",
|
|
5687
|
+
provider: audio.provider,
|
|
5688
|
+
reference: audio.reference,
|
|
5689
|
+
...audio.mediaType !== void 0 ? { mediaType: audio.mediaType } : {}
|
|
5690
|
+
};
|
|
5691
|
+
} else {
|
|
5692
|
+
throw new InvalidTranscriptionInputError(
|
|
5693
|
+
"Transcription audio must be bytes, base64, or provider-file"
|
|
5694
|
+
);
|
|
5695
|
+
}
|
|
5696
|
+
if (request.language !== void 0 && !nonEmptyString(request.language)) {
|
|
5697
|
+
throw new InvalidTranscriptionInputError(
|
|
5698
|
+
"Transcription language must be nonempty when supplied"
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5701
|
+
if (request.timestamps !== void 0 && request.timestamps !== "segment" && request.timestamps !== "word") {
|
|
5702
|
+
throw new InvalidTranscriptionInputError("Transcription timestamps must be segment or word");
|
|
5703
|
+
}
|
|
5704
|
+
if (request.diarization !== void 0 && typeof request.diarization !== "boolean") {
|
|
5705
|
+
throw new InvalidTranscriptionInputError("Transcription diarization must be boolean");
|
|
5706
|
+
}
|
|
5707
|
+
if (request.providerOptions !== void 0) {
|
|
5708
|
+
if (!request.providerOptions || Array.isArray(request.providerOptions) || typeof request.providerOptions !== "object") {
|
|
5709
|
+
throw new InvalidTranscriptionInputError("Transcription providerOptions must be an object");
|
|
5710
|
+
}
|
|
5711
|
+
for (const key of Object.keys(request.providerOptions)) {
|
|
5712
|
+
if (RESERVED_PROVIDER_OPTION_KEYS.has(key)) {
|
|
5713
|
+
throw new InvalidTranscriptionInputError(
|
|
5714
|
+
"Transcription providerOptions cannot override reserved operation fields"
|
|
5715
|
+
);
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
return {
|
|
5720
|
+
model: request.model,
|
|
5721
|
+
audio: normalizedAudio,
|
|
5722
|
+
...request.language !== void 0 ? { language: request.language } : {},
|
|
5723
|
+
...request.timestamps !== void 0 ? { timestamps: request.timestamps } : {},
|
|
5724
|
+
...request.diarization !== void 0 ? { diarization: request.diarization } : {},
|
|
5725
|
+
...request.providerOptions !== void 0 ? { providerOptions: cloneValue(request.providerOptions) } : {}
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
function cloneTranscript(transcript) {
|
|
5729
|
+
if (!transcript || typeof transcript !== "object" || typeof transcript.text !== "string") {
|
|
5730
|
+
throw new InvalidTranscriptionInputError(
|
|
5731
|
+
"Transcription provider returned an invalid transcript"
|
|
5732
|
+
);
|
|
5733
|
+
}
|
|
5734
|
+
const cloned = cloneValue(transcript);
|
|
5735
|
+
const accounting = normalizeTranscriptionAccounting(cloned.usage, cloned.pricingStatus);
|
|
5736
|
+
return {
|
|
5737
|
+
text: cloned.text,
|
|
5738
|
+
...cloned.detectedLanguages ? { detectedLanguages: cloned.detectedLanguages } : {},
|
|
5739
|
+
...cloned.segments ? { segments: cloned.segments } : {},
|
|
5740
|
+
...cloned.words ? { words: cloned.words } : {},
|
|
5741
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
5742
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
5743
|
+
...cloned.providerMetadata ? { providerMetadata: cloned.providerMetadata } : {}
|
|
5744
|
+
};
|
|
5745
|
+
}
|
|
5746
|
+
function normalizeTranscriptionAccounting(usageValue, pricingValue) {
|
|
5747
|
+
const value = usageValue && typeof usageValue === "object" ? usageValue : void 0;
|
|
5748
|
+
const number = (key) => {
|
|
5749
|
+
const candidate = value?.[key];
|
|
5750
|
+
return typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0 ? candidate : void 0;
|
|
5751
|
+
};
|
|
5752
|
+
const audioSeconds = number("audioSeconds");
|
|
5753
|
+
const inputTokens = number("inputTokens");
|
|
5754
|
+
const outputTokens = number("outputTokens");
|
|
5755
|
+
const totalTokens = number("totalTokens");
|
|
5756
|
+
const reportedCost = number("cost");
|
|
5757
|
+
const usage = value && (audioSeconds !== void 0 || inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 || reportedCost !== void 0) ? {
|
|
5758
|
+
...audioSeconds !== void 0 ? { audioSeconds } : {},
|
|
5759
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
5760
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
5761
|
+
...totalTokens !== void 0 ? { totalTokens } : {},
|
|
5762
|
+
...reportedCost !== void 0 ? { cost: reportedCost } : {}
|
|
5763
|
+
} : void 0;
|
|
5764
|
+
const hasWork = (audioSeconds ?? 0) > 0 || (inputTokens ?? 0) > 0 || (outputTokens ?? 0) > 0 || (totalTokens ?? 0) > 0;
|
|
5765
|
+
const requested = pricingValue === "priced" || pricingValue === "unpriced" || pricingValue === "zero" ? pricingValue : void 0;
|
|
5766
|
+
const cost = requested === "zero" && (reportedCost === void 0 || reportedCost === 0) ? 0 : reportedCost;
|
|
5767
|
+
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;
|
|
5768
|
+
const tokens = inputTokens !== void 0 || outputTokens !== void 0 ? {
|
|
5769
|
+
...inputTokens !== void 0 ? { input: inputTokens } : {},
|
|
5770
|
+
...outputTokens !== void 0 ? { output: outputTokens } : {}
|
|
5771
|
+
} : void 0;
|
|
5772
|
+
return {
|
|
5773
|
+
...usage ? { usage } : {},
|
|
5774
|
+
...pricingStatus ? { pricingStatus } : {},
|
|
5775
|
+
...cost !== void 0 ? { cost } : {},
|
|
5776
|
+
...tokens ? { tokens } : {},
|
|
5777
|
+
hasWork
|
|
5778
|
+
};
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5781
|
+
// src/providers/transcription-utils.ts
|
|
5782
|
+
var OPENAI_TRANSCRIPTION_MODEL = "gpt-transcribe";
|
|
5783
|
+
var GEMINI_TRANSCRIPTION_MODEL = "gemini-3.5-transcribe";
|
|
5784
|
+
function requireExactTranscriptionModel(provider, model, expected) {
|
|
5785
|
+
if (model !== expected) {
|
|
5786
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5787
|
+
provider,
|
|
5788
|
+
model,
|
|
5789
|
+
feature: "this transcription model"
|
|
5790
|
+
});
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
function inlineAudioBytes(audio) {
|
|
5794
|
+
if (audio.type === "bytes") return new Uint8Array(audio.data);
|
|
5795
|
+
if (audio.type === "base64") return Uint8Array.from(Buffer.from(audio.data, "base64"));
|
|
5796
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5797
|
+
provider: audio.provider,
|
|
5798
|
+
model: "",
|
|
5799
|
+
feature: "provider-file transcription input"
|
|
5800
|
+
});
|
|
5801
|
+
}
|
|
5802
|
+
function requireNoProviderFile(audio, provider, model) {
|
|
5803
|
+
if (audio.type === "provider-file") {
|
|
5804
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5805
|
+
provider,
|
|
5806
|
+
model,
|
|
5807
|
+
feature: "provider-file transcription input"
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
function assertOnlyProviderOptions(request, provider, model, allowed) {
|
|
5812
|
+
const options = request.providerOptions ?? {};
|
|
5813
|
+
for (const key of Object.keys(options)) {
|
|
5814
|
+
if (!allowed.includes(key)) {
|
|
5815
|
+
throw new InvalidTranscriptionInputError(
|
|
5816
|
+
`Unsupported ${provider} transcription provider option for model '${model}'`
|
|
5817
|
+
);
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
return options;
|
|
5821
|
+
}
|
|
5822
|
+
function transcriptUsage(value) {
|
|
5823
|
+
const source = value && typeof value === "object" ? value : {};
|
|
5824
|
+
const number = (...keys) => {
|
|
5825
|
+
for (const key of keys) {
|
|
5826
|
+
const candidate = source[key];
|
|
5827
|
+
if (typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0)
|
|
5828
|
+
return candidate;
|
|
5829
|
+
}
|
|
5830
|
+
return void 0;
|
|
5831
|
+
};
|
|
5832
|
+
const inputTokens = number("total_input_tokens", "input_tokens", "prompt_tokens", "inputTokens");
|
|
5833
|
+
const outputTokens = number(
|
|
5834
|
+
"total_output_tokens",
|
|
5835
|
+
"output_tokens",
|
|
5836
|
+
"completion_tokens",
|
|
5837
|
+
"outputTokens"
|
|
5838
|
+
);
|
|
5839
|
+
const totalTokens = number("total_tokens", "totalTokens");
|
|
5840
|
+
const audioSeconds = number("seconds", "audio_seconds", "audioSeconds", "duration_seconds");
|
|
5841
|
+
const cost = number("cost");
|
|
5842
|
+
const usage = inputTokens !== void 0 || outputTokens !== void 0 || totalTokens !== void 0 || audioSeconds !== void 0 || cost !== void 0 ? {
|
|
5843
|
+
...audioSeconds !== void 0 ? { audioSeconds } : {},
|
|
5844
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
5845
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
5846
|
+
...totalTokens !== void 0 ? { totalTokens } : {},
|
|
5847
|
+
...cost !== void 0 ? { cost } : {}
|
|
5848
|
+
} : void 0;
|
|
5849
|
+
const hasWork = (audioSeconds ?? 0) > 0 || (inputTokens ?? 0) > 0 || (outputTokens ?? 0) > 0 || (totalTokens ?? 0) > 0;
|
|
5850
|
+
return {
|
|
5851
|
+
...usage ? { usage } : {},
|
|
5852
|
+
...cost !== void 0 ? { pricingStatus: "priced" } : hasWork ? { pricingStatus: "unpriced" } : {}
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5855
|
+
function safeProviderMessage(provider, status, body) {
|
|
5856
|
+
try {
|
|
5857
|
+
const parsed = JSON.parse(body);
|
|
5858
|
+
const message = parsed.error?.message ?? parsed.message;
|
|
5859
|
+
if (typeof message === "string")
|
|
5860
|
+
return `${provider} transcription API error (${status}): ${message}`;
|
|
5861
|
+
} catch {
|
|
5862
|
+
}
|
|
5863
|
+
return `${provider} transcription API error (${status})`;
|
|
5864
|
+
}
|
|
5865
|
+
function attachTranscriptionFailureMetadata(error, metadata) {
|
|
5866
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
5867
|
+
if (value !== void 0) Object.defineProperty(error, key, { enumerable: true, value });
|
|
5868
|
+
}
|
|
5869
|
+
return error;
|
|
5870
|
+
}
|
|
5871
|
+
|
|
5872
|
+
// src/providers/openai-transcription.ts
|
|
5873
|
+
var OPENAI_FILE_EXTENSIONS = {
|
|
5874
|
+
"audio/flac": "flac",
|
|
5875
|
+
"audio/m4a": "m4a",
|
|
5876
|
+
"audio/mp3": "mp3",
|
|
5877
|
+
"audio/mp4": "mp4",
|
|
5878
|
+
"audio/mpeg": "mp3",
|
|
5879
|
+
"audio/mpga": "mpga",
|
|
5880
|
+
"audio/ogg": "ogg",
|
|
5881
|
+
"audio/wav": "wav",
|
|
5882
|
+
"audio/webm": "webm"
|
|
5883
|
+
};
|
|
5884
|
+
var OpenAITranscriptionProvider = class {
|
|
5885
|
+
name = "openai-transcription";
|
|
5886
|
+
baseUrl;
|
|
5887
|
+
apiKeySource;
|
|
5888
|
+
governor;
|
|
5889
|
+
constructor(options = {}) {
|
|
5890
|
+
this.apiKeySource = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
5891
|
+
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
5892
|
+
assertSafeProviderBaseUrl(
|
|
5893
|
+
this.baseUrl,
|
|
5894
|
+
"OpenAI transcription provider",
|
|
5895
|
+
options.dangerouslyAllowInsecureHttp
|
|
5896
|
+
);
|
|
5897
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
5898
|
+
}
|
|
5899
|
+
capabilities(model) {
|
|
5900
|
+
return model === OPENAI_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64"] } : void 0;
|
|
5901
|
+
}
|
|
5902
|
+
async transcribe(request) {
|
|
5903
|
+
requireExactTranscriptionModel(this.name, request.model, OPENAI_TRANSCRIPTION_MODEL);
|
|
5904
|
+
requireNoProviderFile(request.audio, this.name, request.model);
|
|
5905
|
+
if (request.audio.type === "provider-file")
|
|
5906
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5907
|
+
provider: this.name,
|
|
5908
|
+
model: request.model,
|
|
5909
|
+
feature: "provider-file transcription input"
|
|
5910
|
+
});
|
|
5911
|
+
const options = assertOnlyProviderOptions(request, this.name, request.model, [
|
|
5912
|
+
"temperature",
|
|
5913
|
+
"prompt"
|
|
5914
|
+
]);
|
|
5915
|
+
if (options.temperature !== void 0 && (typeof options.temperature !== "number" || !Number.isFinite(options.temperature) || options.temperature < 0 || options.temperature > 1))
|
|
5916
|
+
throw new InvalidTranscriptionInputError(
|
|
5917
|
+
"OpenAI transcription temperature must be a number between 0 and 1"
|
|
5918
|
+
);
|
|
5919
|
+
if (options.prompt !== void 0 && typeof options.prompt !== "string")
|
|
5920
|
+
throw new InvalidTranscriptionInputError("OpenAI transcription prompt must be a string");
|
|
5921
|
+
const mediaType2 = request.audio.mediaType;
|
|
5922
|
+
const extension = OPENAI_FILE_EXTENSIONS[mediaType2];
|
|
5923
|
+
if (!extension)
|
|
5924
|
+
throw new UnsupportedTranscriptionInputError({
|
|
5925
|
+
provider: this.name,
|
|
5926
|
+
model: request.model,
|
|
5927
|
+
feature: "this audio media type"
|
|
5928
|
+
});
|
|
5929
|
+
const bytes = inlineAudioBytes(request.audio);
|
|
5930
|
+
if (bytes.byteLength > MAX_INLINE_TRANSCRIPTION_BYTES)
|
|
5931
|
+
throw new InvalidTranscriptionInputError("OpenAI transcription audio must not exceed 25 MiB");
|
|
5932
|
+
const form = new FormData();
|
|
5933
|
+
form.set("file", new Blob([Buffer.from(bytes)], { type: mediaType2 }), `audio.${extension}`);
|
|
5934
|
+
form.set("model", request.model);
|
|
5935
|
+
form.set("response_format", "json");
|
|
5936
|
+
if (request.language) form.set("language", request.language);
|
|
5937
|
+
if (options.temperature !== void 0) form.set("temperature", String(options.temperature));
|
|
5938
|
+
if (options.prompt !== void 0) form.set("prompt", options.prompt);
|
|
5939
|
+
const key = await this.resolveKey();
|
|
5940
|
+
const res = await fetchWithRetry(
|
|
5941
|
+
`${this.baseUrl}/audio/transcriptions`,
|
|
5942
|
+
{
|
|
5943
|
+
method: "POST",
|
|
5944
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
5945
|
+
body: form,
|
|
5946
|
+
signal: request.signal
|
|
5947
|
+
},
|
|
5948
|
+
{ governor: this.governor, provider: this.name }
|
|
5949
|
+
);
|
|
5950
|
+
if (!res.ok) {
|
|
5951
|
+
const body = await res.text();
|
|
5952
|
+
throw buildProviderError({
|
|
5953
|
+
provider: this.name,
|
|
5954
|
+
status: res.status,
|
|
5955
|
+
headers: res.headers,
|
|
5956
|
+
message: safeProviderMessage("OpenAI", res.status, body),
|
|
5957
|
+
body
|
|
5958
|
+
});
|
|
5959
|
+
}
|
|
5960
|
+
const json = await res.json();
|
|
5961
|
+
if (typeof json.text !== "string")
|
|
5962
|
+
throw new InvalidTranscriptionInputError(
|
|
5963
|
+
"OpenAI transcription response did not contain text"
|
|
5964
|
+
);
|
|
5965
|
+
return {
|
|
5966
|
+
transcript: { text: json.text, ...transcriptUsage(json.usage) },
|
|
5967
|
+
cleanupStatus: "not_required"
|
|
5968
|
+
};
|
|
5969
|
+
}
|
|
5970
|
+
async resolveKey() {
|
|
5971
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
5972
|
+
if (!key)
|
|
5973
|
+
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
5974
|
+
return key;
|
|
5975
|
+
}
|
|
5976
|
+
};
|
|
5977
|
+
|
|
5978
|
+
// src/providers/gemini-transcription.ts
|
|
5979
|
+
var CLEANUP_TIMEOUT_MS = 3e3;
|
|
5980
|
+
var READY_TIMEOUT_MS = 3e4;
|
|
5981
|
+
var READY_POLL_MS = 250;
|
|
5982
|
+
var GEMINI_AUDIO_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
5983
|
+
"audio/wav",
|
|
5984
|
+
"audio/mp3",
|
|
5985
|
+
"audio/aiff",
|
|
5986
|
+
"audio/aac",
|
|
5987
|
+
"audio/ogg",
|
|
5988
|
+
"audio/flac",
|
|
5989
|
+
"audio/mpeg",
|
|
5990
|
+
"audio/m4a",
|
|
5991
|
+
"audio/l16",
|
|
5992
|
+
"audio/opus",
|
|
5993
|
+
"audio/alaw",
|
|
5994
|
+
"audio/mulaw",
|
|
5995
|
+
"audio/webm"
|
|
5996
|
+
]);
|
|
5997
|
+
function abortError2() {
|
|
5998
|
+
return new DOMException("The transcription operation was aborted", "AbortError");
|
|
5999
|
+
}
|
|
6000
|
+
function audioMime(audio) {
|
|
6001
|
+
if (!audio.mediaType)
|
|
6002
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6003
|
+
provider: "gemini-transcription",
|
|
6004
|
+
model: GEMINI_TRANSCRIPTION_MODEL,
|
|
6005
|
+
feature: "provider-file audio mediaType"
|
|
6006
|
+
});
|
|
6007
|
+
return audio.mediaType;
|
|
6008
|
+
}
|
|
6009
|
+
function assertGeminiAudioMime(mediaType2) {
|
|
6010
|
+
if (!mediaType2 || !GEMINI_AUDIO_MEDIA_TYPES.has(mediaType2)) {
|
|
6011
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6012
|
+
provider: "gemini-transcription",
|
|
6013
|
+
model: GEMINI_TRANSCRIPTION_MODEL,
|
|
6014
|
+
feature: "this audio media type"
|
|
6015
|
+
});
|
|
6016
|
+
}
|
|
6017
|
+
return mediaType2;
|
|
6018
|
+
}
|
|
6019
|
+
function secondsOffset(value) {
|
|
6020
|
+
if (typeof value !== "string") return void 0;
|
|
6021
|
+
const match = /^(\d+(?:\.\d+)?)s$/.exec(value);
|
|
6022
|
+
if (!match) return void 0;
|
|
6023
|
+
const seconds = Number(match[1]);
|
|
6024
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
|
|
6025
|
+
}
|
|
6026
|
+
function sleep3(ms, signal) {
|
|
6027
|
+
return new Promise((resolve2, reject) => {
|
|
6028
|
+
if (signal?.aborted) {
|
|
6029
|
+
reject(abortError2());
|
|
6030
|
+
return;
|
|
6031
|
+
}
|
|
6032
|
+
const timer = setTimeout(done, ms);
|
|
6033
|
+
const onAbort = () => {
|
|
6034
|
+
clearTimeout(timer);
|
|
6035
|
+
done(abortError2());
|
|
6036
|
+
};
|
|
6037
|
+
function done(error) {
|
|
6038
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6039
|
+
if (error) reject(error);
|
|
6040
|
+
else resolve2();
|
|
6041
|
+
}
|
|
6042
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
6043
|
+
});
|
|
6044
|
+
}
|
|
6045
|
+
var GeminiTranscriptionProvider = class {
|
|
6046
|
+
name = "gemini-transcription";
|
|
6047
|
+
baseUrl;
|
|
6048
|
+
apiKeySource;
|
|
6049
|
+
governor;
|
|
6050
|
+
constructor(options = {}) {
|
|
6051
|
+
this.apiKeySource = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "";
|
|
6052
|
+
this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(
|
|
6053
|
+
/\/$/,
|
|
6054
|
+
""
|
|
6055
|
+
);
|
|
6056
|
+
assertSafeProviderBaseUrl(
|
|
6057
|
+
this.baseUrl,
|
|
6058
|
+
"Gemini transcription provider",
|
|
6059
|
+
options.dangerouslyAllowInsecureHttp
|
|
6060
|
+
);
|
|
6061
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
6062
|
+
}
|
|
6063
|
+
capabilities(model) {
|
|
6064
|
+
return model === GEMINI_TRANSCRIPTION_MODEL ? { sources: ["bytes", "base64", "provider-file"], timestamps: ["word"], diarization: true } : void 0;
|
|
6065
|
+
}
|
|
6066
|
+
async transcribe(request) {
|
|
6067
|
+
requireExactTranscriptionModel(this.name, request.model, GEMINI_TRANSCRIPTION_MODEL);
|
|
6068
|
+
const options = assertOnlyProviderOptions(request, this.name, request.model, [
|
|
6069
|
+
"mode",
|
|
6070
|
+
"customVocabulary"
|
|
6071
|
+
]);
|
|
6072
|
+
const mode = options.mode ?? "verbatim";
|
|
6073
|
+
if (mode !== "smart" && mode !== "verbatim")
|
|
6074
|
+
throw new InvalidTranscriptionInputError(
|
|
6075
|
+
"Gemini transcription mode must be smart or verbatim"
|
|
6076
|
+
);
|
|
6077
|
+
if (options.customVocabulary !== void 0 && (!Array.isArray(options.customVocabulary) || options.customVocabulary.length > 1e3 || options.customVocabulary.some(
|
|
6078
|
+
(value) => typeof value !== "string" || !value.trim() || value.length > 256
|
|
6079
|
+
)))
|
|
6080
|
+
throw new InvalidTranscriptionInputError(
|
|
6081
|
+
"Gemini transcription customVocabulary must contain at most 1000 nonempty strings up to 256 characters"
|
|
6082
|
+
);
|
|
6083
|
+
if (mode === "smart" && (request.timestamps || request.diarization))
|
|
6084
|
+
throw new InvalidTranscriptionInputError(
|
|
6085
|
+
"Gemini smart transcription does not support timestamps or diarization"
|
|
6086
|
+
);
|
|
6087
|
+
if (request.timestamps && options.customVocabulary !== void 0)
|
|
6088
|
+
throw new InvalidTranscriptionInputError(
|
|
6089
|
+
"Gemini transcription customVocabulary cannot be combined with timestamps"
|
|
6090
|
+
);
|
|
6091
|
+
if (request.timestamps && request.timestamps !== "word")
|
|
6092
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6093
|
+
provider: this.name,
|
|
6094
|
+
model: request.model,
|
|
6095
|
+
feature: "segment timestamps"
|
|
6096
|
+
});
|
|
6097
|
+
if (request.diarization && request.timestamps !== "word")
|
|
6098
|
+
throw new InvalidTranscriptionInputError("Gemini diarization requires word timestamps");
|
|
6099
|
+
if (request.audio.type === "provider-file") {
|
|
6100
|
+
if (request.audio.provider !== this.name || typeof request.audio.reference !== "string" || !request.audio.reference.trim()) {
|
|
6101
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6102
|
+
provider: this.name,
|
|
6103
|
+
model: request.model,
|
|
6104
|
+
feature: "a matching nonempty provider-file reference"
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
assertGeminiAudioMime(request.audio.mediaType);
|
|
6108
|
+
} else {
|
|
6109
|
+
assertGeminiAudioMime(request.audio.mediaType);
|
|
6110
|
+
}
|
|
6111
|
+
const key = await this.resolveKey();
|
|
6112
|
+
if (request.audio.type === "provider-file")
|
|
6113
|
+
return this.transcribeReference(request, key, mode, options);
|
|
6114
|
+
return this.transcribeUpload(request, key, mode, options);
|
|
6115
|
+
}
|
|
6116
|
+
async transcribeReference(request, key, mode, options) {
|
|
6117
|
+
const audio = request.audio;
|
|
6118
|
+
if (audio.type !== "provider-file" || audio.provider !== this.name)
|
|
6119
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6120
|
+
provider: this.name,
|
|
6121
|
+
model: request.model,
|
|
6122
|
+
feature: "a provider-file owned by another provider"
|
|
6123
|
+
});
|
|
6124
|
+
const mimeType = audioMime(audio);
|
|
6125
|
+
const transcript = await this.infer(request, key, audio.reference, mimeType, mode, options);
|
|
6126
|
+
return { transcript, cleanupStatus: "not_required" };
|
|
6127
|
+
}
|
|
6128
|
+
async transcribeUpload(request, key, mode, options) {
|
|
6129
|
+
let created = false;
|
|
6130
|
+
let fileName;
|
|
6131
|
+
let primaryError;
|
|
6132
|
+
let transcript;
|
|
6133
|
+
let cleanupStatus = "not_required";
|
|
6134
|
+
try {
|
|
6135
|
+
const uploaded = await this.upload(request, key);
|
|
6136
|
+
created = true;
|
|
6137
|
+
fileName = uploaded.name;
|
|
6138
|
+
const ready = await this.waitForReady(
|
|
6139
|
+
this.fileFromJson(uploaded.file, uploaded.mimeType),
|
|
6140
|
+
key,
|
|
6141
|
+
request.signal
|
|
6142
|
+
);
|
|
6143
|
+
transcript = await this.infer(request, key, ready.uri, ready.mimeType, mode, options);
|
|
6144
|
+
} catch (error) {
|
|
6145
|
+
primaryError = error;
|
|
6146
|
+
} finally {
|
|
6147
|
+
if (created && fileName) cleanupStatus = await this.cleanup(fileName, key);
|
|
6148
|
+
}
|
|
6149
|
+
if (primaryError) {
|
|
6150
|
+
throw attachTranscriptionFailureMetadata(
|
|
6151
|
+
primaryError instanceof Error ? primaryError : new Error("Gemini transcription failed"),
|
|
6152
|
+
{ cleanupStatus }
|
|
6153
|
+
);
|
|
6154
|
+
}
|
|
6155
|
+
if (!transcript)
|
|
6156
|
+
throw new InvalidTranscriptionInputError(
|
|
6157
|
+
"Gemini transcription response did not contain text"
|
|
6158
|
+
);
|
|
6159
|
+
return { transcript, cleanupStatus };
|
|
6160
|
+
}
|
|
6161
|
+
/** A finalization response with a name creates a caller-owned cleanup obligation
|
|
6162
|
+
* even when its URI/state is malformed. `transcribeUpload` stores the name before
|
|
6163
|
+
* strictly parsing the rest of the file representation. */
|
|
6164
|
+
async upload(request, key) {
|
|
6165
|
+
if (request.audio.type === "provider-file")
|
|
6166
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6167
|
+
provider: this.name,
|
|
6168
|
+
model: request.model,
|
|
6169
|
+
feature: "provider-file upload"
|
|
6170
|
+
});
|
|
6171
|
+
const mimeType = assertGeminiAudioMime(request.audio.mediaType);
|
|
6172
|
+
const bytes = inlineAudioBytes(request.audio);
|
|
6173
|
+
const start = await fetchWithRetry(
|
|
6174
|
+
`${this.baseUrl.replace(/\/v1beta$/, "")}/upload/v1beta/files`,
|
|
6175
|
+
{
|
|
6176
|
+
method: "POST",
|
|
6177
|
+
headers: {
|
|
6178
|
+
"x-goog-api-key": key,
|
|
6179
|
+
"X-Goog-Upload-Protocol": "resumable",
|
|
6180
|
+
"X-Goog-Upload-Command": "start",
|
|
6181
|
+
"X-Goog-Upload-Header-Content-Length": String(bytes.byteLength),
|
|
6182
|
+
"X-Goog-Upload-Header-Content-Type": mimeType,
|
|
6183
|
+
"Content-Type": "application/json"
|
|
6184
|
+
},
|
|
6185
|
+
body: JSON.stringify({ file: { display_name: "axl-transcription" } }),
|
|
6186
|
+
signal: request.signal
|
|
6187
|
+
},
|
|
6188
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6189
|
+
);
|
|
6190
|
+
if (!start.ok) {
|
|
6191
|
+
const body = await start.text();
|
|
6192
|
+
throw buildProviderError({
|
|
6193
|
+
provider: this.name,
|
|
6194
|
+
status: start.status,
|
|
6195
|
+
headers: start.headers,
|
|
6196
|
+
message: safeProviderMessage("Gemini", start.status, body),
|
|
6197
|
+
body
|
|
6198
|
+
});
|
|
6199
|
+
}
|
|
6200
|
+
const uploadUrl = start.headers.get("x-goog-upload-url");
|
|
6201
|
+
if (!uploadUrl)
|
|
6202
|
+
throw new InvalidTranscriptionInputError(
|
|
6203
|
+
"Gemini transcription upload did not return an upload URL"
|
|
6204
|
+
);
|
|
6205
|
+
let parsedUploadUrl;
|
|
6206
|
+
try {
|
|
6207
|
+
parsedUploadUrl = new URL(uploadUrl);
|
|
6208
|
+
} catch {
|
|
6209
|
+
throw new InvalidTranscriptionInputError(
|
|
6210
|
+
"Gemini transcription upload returned an invalid upload URL"
|
|
6211
|
+
);
|
|
6212
|
+
}
|
|
6213
|
+
const baseOrigin = new URL(this.baseUrl).origin;
|
|
6214
|
+
if (parsedUploadUrl.username || parsedUploadUrl.password || parsedUploadUrl.origin !== baseOrigin) {
|
|
6215
|
+
throw new InvalidTranscriptionInputError(
|
|
6216
|
+
"Gemini transcription upload returned an untrusted upload URL"
|
|
6217
|
+
);
|
|
6218
|
+
}
|
|
6219
|
+
assertSafeProviderBaseUrl(
|
|
6220
|
+
parsedUploadUrl.toString(),
|
|
6221
|
+
"Gemini transcription upload endpoint",
|
|
6222
|
+
new URL(this.baseUrl).protocol === "http:"
|
|
6223
|
+
);
|
|
6224
|
+
const finalize = await fetchWithRetry(
|
|
6225
|
+
parsedUploadUrl.toString(),
|
|
6226
|
+
{
|
|
6227
|
+
method: "POST",
|
|
6228
|
+
headers: {
|
|
6229
|
+
"X-Goog-Upload-Command": "upload, finalize",
|
|
6230
|
+
"X-Goog-Upload-Offset": "0",
|
|
6231
|
+
"Content-Length": String(bytes.byteLength)
|
|
6232
|
+
},
|
|
6233
|
+
body: bytes,
|
|
6234
|
+
signal: request.signal
|
|
6235
|
+
},
|
|
6236
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6237
|
+
);
|
|
6238
|
+
if (!finalize.ok) {
|
|
6239
|
+
const body = await finalize.text();
|
|
6240
|
+
throw buildProviderError({
|
|
6241
|
+
provider: this.name,
|
|
6242
|
+
status: finalize.status,
|
|
6243
|
+
headers: finalize.headers,
|
|
6244
|
+
message: safeProviderMessage("Gemini", finalize.status, body),
|
|
6245
|
+
body
|
|
6246
|
+
});
|
|
6247
|
+
}
|
|
6248
|
+
const payload = await finalize.json();
|
|
6249
|
+
const file = payload.file;
|
|
6250
|
+
if (!file || typeof file.name !== "string") {
|
|
6251
|
+
throw new InvalidTranscriptionInputError(
|
|
6252
|
+
"Gemini transcription upload did not return a file name"
|
|
6253
|
+
);
|
|
6254
|
+
}
|
|
6255
|
+
return { name: file.name, file, mimeType };
|
|
6256
|
+
}
|
|
6257
|
+
async waitForReady(file, key, signal) {
|
|
6258
|
+
let current = file;
|
|
6259
|
+
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
6260
|
+
if (!current.state) {
|
|
6261
|
+
throw new InvalidTranscriptionInputError(
|
|
6262
|
+
"Gemini transcription file readiness state is missing"
|
|
6263
|
+
);
|
|
6264
|
+
}
|
|
6265
|
+
while (current.state && current.state !== "ACTIVE") {
|
|
6266
|
+
if (current.state === "FAILED")
|
|
6267
|
+
throw new InvalidTranscriptionInputError("Gemini transcription file processing failed");
|
|
6268
|
+
if (Date.now() >= deadline)
|
|
6269
|
+
throw new InvalidTranscriptionInputError("Gemini transcription file readiness timed out");
|
|
6270
|
+
await sleep3(Math.min(READY_POLL_MS, deadline - Date.now()), signal);
|
|
6271
|
+
const res = await fetchWithRetry(
|
|
6272
|
+
`${this.baseUrl}/${current.name}`,
|
|
6273
|
+
{ method: "GET", headers: { "x-goog-api-key": key }, signal },
|
|
6274
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6275
|
+
);
|
|
6276
|
+
if (!res.ok) {
|
|
6277
|
+
const body = await res.text();
|
|
6278
|
+
throw buildProviderError({
|
|
6279
|
+
provider: this.name,
|
|
6280
|
+
status: res.status,
|
|
6281
|
+
headers: res.headers,
|
|
6282
|
+
message: safeProviderMessage("Gemini", res.status, body),
|
|
6283
|
+
body
|
|
6284
|
+
});
|
|
6285
|
+
}
|
|
6286
|
+
current = this.fileFromJson(await res.json(), current.mimeType);
|
|
6287
|
+
}
|
|
6288
|
+
return current;
|
|
6289
|
+
}
|
|
6290
|
+
async infer(request, key, uri, mimeType, mode, options) {
|
|
6291
|
+
const transcriptionConfig = {
|
|
6292
|
+
mode: mode === "smart" ? "smart" : {
|
|
6293
|
+
type: "verbatim",
|
|
6294
|
+
...request.timestamps ? { timestamp_granularities: ["word"] } : {},
|
|
6295
|
+
...request.diarization ? { diarization_mode: "speaker" } : {}
|
|
6296
|
+
},
|
|
6297
|
+
...request.language ? { language_codes: [request.language] } : {},
|
|
6298
|
+
...options.customVocabulary ? { custom_vocabulary: options.customVocabulary } : {}
|
|
6299
|
+
};
|
|
6300
|
+
const body = {
|
|
6301
|
+
model: request.model,
|
|
6302
|
+
input: [{ type: "audio", uri, mime_type: mimeType }],
|
|
6303
|
+
store: false,
|
|
6304
|
+
generation_config: { transcription_config: transcriptionConfig }
|
|
6305
|
+
};
|
|
6306
|
+
const res = await fetchWithRetry(
|
|
6307
|
+
`${this.baseUrl}/interactions`,
|
|
6308
|
+
{
|
|
6309
|
+
method: "POST",
|
|
6310
|
+
headers: { "x-goog-api-key": key, "Content-Type": "application/json" },
|
|
6311
|
+
body: JSON.stringify(body),
|
|
6312
|
+
signal: request.signal
|
|
6313
|
+
},
|
|
6314
|
+
{ governor: this.governor, provider: this.name }
|
|
6315
|
+
);
|
|
6316
|
+
if (!res.ok) {
|
|
6317
|
+
const errorBody = await res.text();
|
|
6318
|
+
throw buildProviderError({
|
|
6319
|
+
provider: this.name,
|
|
6320
|
+
status: res.status,
|
|
6321
|
+
headers: res.headers,
|
|
6322
|
+
message: safeProviderMessage("Gemini", res.status, errorBody),
|
|
6323
|
+
body: errorBody
|
|
6324
|
+
});
|
|
6325
|
+
}
|
|
6326
|
+
const json = await res.json();
|
|
6327
|
+
if (json.status !== "completed") {
|
|
6328
|
+
throw new InvalidTranscriptionInputError("Gemini transcription interaction did not complete");
|
|
6329
|
+
}
|
|
6330
|
+
const words = [];
|
|
6331
|
+
let text = "";
|
|
6332
|
+
let hasModelOutputText = false;
|
|
6333
|
+
if (Array.isArray(json.steps)) {
|
|
6334
|
+
for (const step of json.steps) {
|
|
6335
|
+
if (!step || typeof step !== "object") continue;
|
|
6336
|
+
const modelOutput = step;
|
|
6337
|
+
if (modelOutput.type !== "model_output" || !Array.isArray(modelOutput.content)) continue;
|
|
6338
|
+
for (const block of modelOutput.content) {
|
|
6339
|
+
if (!block || typeof block !== "object") continue;
|
|
6340
|
+
const content = block;
|
|
6341
|
+
if (content.type !== "text" || typeof content.text !== "string") continue;
|
|
6342
|
+
hasModelOutputText = true;
|
|
6343
|
+
text += content.text;
|
|
6344
|
+
if (!Array.isArray(content.annotations)) continue;
|
|
6345
|
+
for (const raw of content.annotations) {
|
|
6346
|
+
if (!raw || typeof raw !== "object") continue;
|
|
6347
|
+
const annotation = raw;
|
|
6348
|
+
if (annotation.type !== "word_info") continue;
|
|
6349
|
+
if (typeof annotation.text !== "string") continue;
|
|
6350
|
+
const start = secondsOffset(annotation.start_offset);
|
|
6351
|
+
const end = secondsOffset(annotation.end_offset);
|
|
6352
|
+
if (start === void 0 || end === void 0 || end < start) continue;
|
|
6353
|
+
words.push({
|
|
6354
|
+
text: annotation.text,
|
|
6355
|
+
start,
|
|
6356
|
+
end,
|
|
6357
|
+
...typeof annotation.speaker === "string" ? { speaker: annotation.speaker } : {}
|
|
6358
|
+
});
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
if (!hasModelOutputText)
|
|
6364
|
+
throw new InvalidTranscriptionInputError(
|
|
6365
|
+
"Gemini transcription response did not contain model output text"
|
|
6366
|
+
);
|
|
6367
|
+
return { text, ...words.length ? { words } : {}, ...transcriptUsage(json.usage) };
|
|
6368
|
+
}
|
|
6369
|
+
fileFromJson(value, fallbackMimeType) {
|
|
6370
|
+
const file = "file" in value && value.file ? value.file : value;
|
|
6371
|
+
if (typeof file.name !== "string" || typeof file.uri !== "string")
|
|
6372
|
+
throw new InvalidTranscriptionInputError(
|
|
6373
|
+
"Gemini transcription upload did not return a file reference"
|
|
6374
|
+
);
|
|
6375
|
+
return {
|
|
6376
|
+
name: file.name,
|
|
6377
|
+
uri: file.uri,
|
|
6378
|
+
mimeType: typeof file.mimeType === "string" ? file.mimeType : typeof file.mime_type === "string" ? file.mime_type : fallbackMimeType,
|
|
6379
|
+
...typeof file.state === "string" ? { state: file.state } : {}
|
|
6380
|
+
};
|
|
6381
|
+
}
|
|
6382
|
+
async cleanup(fileName, key) {
|
|
6383
|
+
const controller = new AbortController();
|
|
6384
|
+
const timer = setTimeout(() => controller.abort(), CLEANUP_TIMEOUT_MS);
|
|
6385
|
+
try {
|
|
6386
|
+
const res = await fetchWithRetry(
|
|
6387
|
+
`${this.baseUrl}/${fileName}`,
|
|
6388
|
+
{ method: "DELETE", headers: { "x-goog-api-key": key }, signal: controller.signal },
|
|
6389
|
+
{ governor: this.governor, provider: this.name, maxRetries: 0 }
|
|
6390
|
+
);
|
|
6391
|
+
return res.ok ? "deleted" : "failed";
|
|
6392
|
+
} catch {
|
|
6393
|
+
return controller.signal.aborted ? "timed_out" : "failed";
|
|
6394
|
+
} finally {
|
|
6395
|
+
clearTimeout(timer);
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
async resolveKey() {
|
|
6399
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
6400
|
+
if (!key)
|
|
6401
|
+
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
6402
|
+
return key;
|
|
6403
|
+
}
|
|
6404
|
+
};
|
|
6405
|
+
|
|
6406
|
+
// src/providers/openrouter-transcription.ts
|
|
6407
|
+
var FORMAT_BY_MIME = {
|
|
6408
|
+
"audio/aac": "aac",
|
|
6409
|
+
"audio/flac": "flac",
|
|
6410
|
+
"audio/m4a": "m4a",
|
|
6411
|
+
"audio/mp3": "mp3",
|
|
6412
|
+
"audio/mp4": "mp4",
|
|
6413
|
+
"audio/mpeg": "mp3",
|
|
6414
|
+
"audio/ogg": "ogg",
|
|
6415
|
+
"audio/wav": "wav",
|
|
6416
|
+
"audio/webm": "webm"
|
|
6417
|
+
};
|
|
6418
|
+
var OpenRouterTranscriptionProvider = class {
|
|
6419
|
+
name = "openrouter-transcription";
|
|
6420
|
+
baseUrl;
|
|
6421
|
+
apiKeySource;
|
|
6422
|
+
governor;
|
|
6423
|
+
constructor(options = {}) {
|
|
6424
|
+
this.apiKeySource = options.apiKey ?? process.env.OPENROUTER_API_KEY ?? "";
|
|
6425
|
+
this.baseUrl = (options.baseUrl ?? process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1").replace(/\/$/, "");
|
|
6426
|
+
assertSafeProviderBaseUrl(
|
|
6427
|
+
this.baseUrl,
|
|
6428
|
+
"OpenRouter transcription provider",
|
|
6429
|
+
options.dangerouslyAllowInsecureHttp
|
|
6430
|
+
);
|
|
6431
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
6432
|
+
}
|
|
6433
|
+
capabilities(model) {
|
|
6434
|
+
return model.trim().length > 0 ? { sources: ["bytes", "base64"] } : void 0;
|
|
6435
|
+
}
|
|
6436
|
+
async transcribe(request) {
|
|
6437
|
+
if (request.model.trim().length === 0) {
|
|
6438
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6439
|
+
provider: this.name,
|
|
6440
|
+
model: request.model,
|
|
6441
|
+
feature: "this transcription model"
|
|
6442
|
+
});
|
|
6443
|
+
}
|
|
6444
|
+
requireNoProviderFile(request.audio, this.name, request.model);
|
|
6445
|
+
if (request.audio.type === "provider-file")
|
|
6446
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6447
|
+
provider: this.name,
|
|
6448
|
+
model: request.model,
|
|
6449
|
+
feature: "provider-file transcription input"
|
|
6450
|
+
});
|
|
6451
|
+
const options = assertOnlyProviderOptions(request, this.name, request.model, [
|
|
6452
|
+
"temperature",
|
|
6453
|
+
"provider"
|
|
6454
|
+
]);
|
|
6455
|
+
if (options.temperature !== void 0 && (typeof options.temperature !== "number" || !Number.isFinite(options.temperature) || options.temperature < 0 || options.temperature > 1))
|
|
6456
|
+
throw new InvalidTranscriptionInputError(
|
|
6457
|
+
"OpenRouter transcription temperature must be a number between 0 and 1"
|
|
6458
|
+
);
|
|
6459
|
+
if (options.provider !== void 0 && (!options.provider || Array.isArray(options.provider) || typeof options.provider !== "object"))
|
|
6460
|
+
throw new InvalidTranscriptionInputError(
|
|
6461
|
+
"OpenRouter transcription provider routing must be an object"
|
|
6462
|
+
);
|
|
6463
|
+
const format = FORMAT_BY_MIME[request.audio.mediaType];
|
|
6464
|
+
if (!format)
|
|
6465
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6466
|
+
provider: this.name,
|
|
6467
|
+
model: request.model,
|
|
6468
|
+
feature: "this audio media type"
|
|
6469
|
+
});
|
|
6470
|
+
const bytes = inlineAudioBytes(request.audio);
|
|
6471
|
+
const body = {
|
|
6472
|
+
model: request.model,
|
|
6473
|
+
input_audio: { data: Buffer.from(bytes).toString("base64"), format },
|
|
6474
|
+
...request.language ? { language: request.language } : {},
|
|
6475
|
+
...options.temperature !== void 0 ? { temperature: options.temperature } : {},
|
|
6476
|
+
...options.provider !== void 0 ? { provider: options.provider } : {}
|
|
6477
|
+
};
|
|
6478
|
+
const key = await this.resolveKey();
|
|
6479
|
+
const res = await fetchWithRetry(
|
|
6480
|
+
`${this.baseUrl}/audio/transcriptions`,
|
|
6481
|
+
{
|
|
6482
|
+
method: "POST",
|
|
6483
|
+
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
|
|
6484
|
+
body: JSON.stringify(body),
|
|
6485
|
+
signal: request.signal
|
|
6486
|
+
},
|
|
6487
|
+
{ governor: this.governor, provider: this.name }
|
|
6488
|
+
);
|
|
6489
|
+
if (!res.ok) {
|
|
6490
|
+
const errorBody = await res.text();
|
|
6491
|
+
throw buildProviderError({
|
|
6492
|
+
provider: this.name,
|
|
6493
|
+
status: res.status,
|
|
6494
|
+
headers: res.headers,
|
|
6495
|
+
message: safeProviderMessage("OpenRouter", res.status, errorBody),
|
|
6496
|
+
body: errorBody
|
|
6497
|
+
});
|
|
6498
|
+
}
|
|
6499
|
+
const json = await res.json();
|
|
6500
|
+
if (typeof json.text !== "string")
|
|
6501
|
+
throw new InvalidTranscriptionInputError(
|
|
6502
|
+
"OpenRouter transcription response did not contain text"
|
|
6503
|
+
);
|
|
6504
|
+
return {
|
|
6505
|
+
transcript: { text: json.text, ...transcriptUsage(json.usage) },
|
|
6506
|
+
cleanupStatus: "not_required"
|
|
6507
|
+
};
|
|
6508
|
+
}
|
|
6509
|
+
async resolveKey() {
|
|
6510
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
6511
|
+
if (!key)
|
|
6512
|
+
throw new Error(
|
|
6513
|
+
"OpenRouter API key is required. Set OPENROUTER_API_KEY or pass apiKey in options."
|
|
6514
|
+
);
|
|
6515
|
+
return key;
|
|
6516
|
+
}
|
|
6517
|
+
};
|
|
6518
|
+
|
|
6519
|
+
// src/providers/transcription-registry.ts
|
|
6520
|
+
var builtinFactories2 = {
|
|
6521
|
+
"openai-transcription": (config) => {
|
|
6522
|
+
const opts = config.providers?.openai ?? {};
|
|
6523
|
+
return new OpenAITranscriptionProvider({
|
|
6524
|
+
apiKey: opts.apiKey,
|
|
6525
|
+
baseUrl: opts.baseUrl,
|
|
6526
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
6527
|
+
rateLimit: opts.rateLimit
|
|
6528
|
+
});
|
|
6529
|
+
},
|
|
6530
|
+
"gemini-transcription": (config) => {
|
|
6531
|
+
const opts = config.providers?.google ?? {};
|
|
6532
|
+
return new GeminiTranscriptionProvider({
|
|
6533
|
+
apiKey: opts.apiKey,
|
|
6534
|
+
baseUrl: opts.baseUrl,
|
|
6535
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
6536
|
+
rateLimit: opts.rateLimit
|
|
6537
|
+
});
|
|
6538
|
+
},
|
|
6539
|
+
"openrouter-transcription": (config) => {
|
|
6540
|
+
const opts = config.providers?.openrouter ?? {};
|
|
6541
|
+
return new OpenRouterTranscriptionProvider({
|
|
6542
|
+
apiKey: opts.apiKey,
|
|
6543
|
+
baseUrl: opts.baseUrl,
|
|
6544
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
6545
|
+
rateLimit: opts.rateLimit
|
|
6546
|
+
});
|
|
6547
|
+
}
|
|
6548
|
+
};
|
|
6549
|
+
var TranscriptionProviderRegistry = class {
|
|
6550
|
+
instances = /* @__PURE__ */ new Map();
|
|
6551
|
+
factories = /* @__PURE__ */ new Map();
|
|
6552
|
+
constructor() {
|
|
6553
|
+
for (const [name, factory] of Object.entries(builtinFactories2))
|
|
6554
|
+
this.factories.set(name, factory);
|
|
6555
|
+
}
|
|
6556
|
+
register(name, factory) {
|
|
6557
|
+
this.factories.set(name, factory);
|
|
6558
|
+
this.instances.delete(name);
|
|
6559
|
+
}
|
|
6560
|
+
registerInstance(name, provider) {
|
|
6561
|
+
this.instances.set(name, provider);
|
|
6562
|
+
}
|
|
6563
|
+
has(name) {
|
|
6564
|
+
return this.instances.has(name) || this.factories.has(name);
|
|
6565
|
+
}
|
|
6566
|
+
get(name, config = {}) {
|
|
6567
|
+
const instance = this.instances.get(name);
|
|
6568
|
+
if (instance) return instance;
|
|
6569
|
+
const factory = this.factories.get(name);
|
|
6570
|
+
if (factory) {
|
|
6571
|
+
const created = factory(config);
|
|
6572
|
+
this.instances.set(name, created);
|
|
6573
|
+
return created;
|
|
6574
|
+
}
|
|
6575
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6576
|
+
provider: name,
|
|
6577
|
+
model: "",
|
|
6578
|
+
feature: "transcription"
|
|
6579
|
+
});
|
|
4709
6580
|
}
|
|
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
6581
|
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
|
-
);
|
|
6582
|
+
const colon = typeof uri === "string" ? uri.indexOf(":") : -1;
|
|
6583
|
+
if (colon <= 0 || colon === uri.length - 1) {
|
|
6584
|
+
throw new UnsupportedTranscriptionInputError({
|
|
6585
|
+
provider: "unknown",
|
|
6586
|
+
model: "",
|
|
6587
|
+
feature: "an explicit transcription provider:model URI"
|
|
6588
|
+
});
|
|
4739
6589
|
}
|
|
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();
|
|
6590
|
+
const providerName = uri.slice(0, colon);
|
|
6591
|
+
const model = uri.slice(colon + 1);
|
|
6592
|
+
return { provider: this.get(providerName, config), model, providerName };
|
|
4754
6593
|
}
|
|
4755
6594
|
};
|
|
4756
|
-
var defaultRegistry = new ProviderRegistry();
|
|
4757
6595
|
|
|
4758
6596
|
// src/context.ts
|
|
4759
6597
|
var import_node_async_hooks = require("async_hooks");
|
|
4760
6598
|
var import_node_crypto = require("crypto");
|
|
4761
6599
|
var import_zod2 = require("zod");
|
|
4762
6600
|
|
|
6601
|
+
// src/input.ts
|
|
6602
|
+
var MAX_INLINE_MODEL_INPUT_BYTES = 25 * 1024 * 1024;
|
|
6603
|
+
function invalid(message) {
|
|
6604
|
+
throw new InvalidModelInputError(message);
|
|
6605
|
+
}
|
|
6606
|
+
function nonEmptyString2(value, field) {
|
|
6607
|
+
if (typeof value !== "string" || value.length === 0)
|
|
6608
|
+
invalid(`${field} must be a non-empty string`);
|
|
6609
|
+
return value;
|
|
6610
|
+
}
|
|
6611
|
+
function mediaType(value, field) {
|
|
6612
|
+
return nonEmptyString2(value, field);
|
|
6613
|
+
}
|
|
6614
|
+
function validBase64(value) {
|
|
6615
|
+
return value.length > 0 && value.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(value);
|
|
6616
|
+
}
|
|
6617
|
+
function decodedBase64Bytes(value) {
|
|
6618
|
+
return Math.floor(value.length * 3 / 4) - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0);
|
|
6619
|
+
}
|
|
6620
|
+
function normalizeModelInput(input) {
|
|
6621
|
+
if (typeof input === "string") return input;
|
|
6622
|
+
if (!Array.isArray(input) || input.length === 0)
|
|
6623
|
+
invalid("ModelInput parts must be a non-empty array");
|
|
6624
|
+
let inlineBytes = 0;
|
|
6625
|
+
const reserveInlineBytes = (bytes) => {
|
|
6626
|
+
if (bytes > MAX_INLINE_MODEL_INPUT_BYTES - inlineBytes) {
|
|
6627
|
+
invalid(
|
|
6628
|
+
"Inline image data must not exceed 25 MiB total; use a URL or provider-file source where supported"
|
|
6629
|
+
);
|
|
6630
|
+
}
|
|
6631
|
+
inlineBytes += bytes;
|
|
6632
|
+
};
|
|
6633
|
+
return input.map((part, index) => {
|
|
6634
|
+
if (!part || typeof part !== "object") invalid(`ModelInput part ${index} must be an object`);
|
|
6635
|
+
if (part.type === "text")
|
|
6636
|
+
return { type: "text", text: nonEmptyString2(part.text, `part ${index}.text`) };
|
|
6637
|
+
if (part.type !== "image") invalid(`part ${index}.type must be 'text' or 'image'`);
|
|
6638
|
+
const source = part.source;
|
|
6639
|
+
if (!source || typeof source !== "object") invalid(`part ${index}.source must be an object`);
|
|
6640
|
+
let clone;
|
|
6641
|
+
switch (source.type) {
|
|
6642
|
+
case "url": {
|
|
6643
|
+
const url = nonEmptyString2(source.url, `part ${index}.source.url`);
|
|
6644
|
+
let parsed;
|
|
6645
|
+
try {
|
|
6646
|
+
parsed = new URL(url);
|
|
6647
|
+
} catch {
|
|
6648
|
+
invalid(`part ${index}.source.url must be an http(s) URL`);
|
|
6649
|
+
}
|
|
6650
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
6651
|
+
invalid(`part ${index}.source.url must be an http(s) URL`);
|
|
6652
|
+
clone = {
|
|
6653
|
+
type: "url",
|
|
6654
|
+
url,
|
|
6655
|
+
...source.mediaType === void 0 ? {} : { mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`) }
|
|
6656
|
+
};
|
|
6657
|
+
break;
|
|
6658
|
+
}
|
|
6659
|
+
case "bytes":
|
|
6660
|
+
if (!(source.data instanceof Uint8Array) || source.data.byteLength === 0)
|
|
6661
|
+
invalid(`part ${index}.source.data must be a non-empty Uint8Array`);
|
|
6662
|
+
reserveInlineBytes(source.data.byteLength);
|
|
6663
|
+
clone = {
|
|
6664
|
+
type: "bytes",
|
|
6665
|
+
data: source.data.slice(),
|
|
6666
|
+
mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`)
|
|
6667
|
+
};
|
|
6668
|
+
break;
|
|
6669
|
+
case "base64": {
|
|
6670
|
+
const data = nonEmptyString2(source.data, `part ${index}.source.data`);
|
|
6671
|
+
if (data.length > 4 * Math.ceil(MAX_INLINE_MODEL_INPUT_BYTES / 3)) {
|
|
6672
|
+
invalid(
|
|
6673
|
+
"Inline image data must not exceed 25 MiB total; use a URL or provider-file source where supported"
|
|
6674
|
+
);
|
|
6675
|
+
}
|
|
6676
|
+
if (!validBase64(data)) invalid(`part ${index}.source.data must be valid base64`);
|
|
6677
|
+
reserveInlineBytes(decodedBase64Bytes(data));
|
|
6678
|
+
clone = {
|
|
6679
|
+
type: "base64",
|
|
6680
|
+
data,
|
|
6681
|
+
mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`)
|
|
6682
|
+
};
|
|
6683
|
+
break;
|
|
6684
|
+
}
|
|
6685
|
+
case "provider-file":
|
|
6686
|
+
clone = {
|
|
6687
|
+
type: "provider-file",
|
|
6688
|
+
provider: nonEmptyString2(source.provider, `part ${index}.source.provider`),
|
|
6689
|
+
reference: nonEmptyString2(source.reference, `part ${index}.source.reference`),
|
|
6690
|
+
...source.mediaType === void 0 ? {} : { mediaType: mediaType(source.mediaType, `part ${index}.source.mediaType`) }
|
|
6691
|
+
};
|
|
6692
|
+
break;
|
|
6693
|
+
default:
|
|
6694
|
+
invalid(`part ${index}.source.type is unsupported`);
|
|
6695
|
+
}
|
|
6696
|
+
return {
|
|
6697
|
+
type: "image",
|
|
6698
|
+
source: clone,
|
|
6699
|
+
...part.label === void 0 ? {} : { label: nonEmptyString2(part.label, `part ${index}.label`) }
|
|
6700
|
+
};
|
|
6701
|
+
});
|
|
6702
|
+
}
|
|
6703
|
+
function inputText(input) {
|
|
6704
|
+
return typeof input === "string" ? input : input.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
6705
|
+
}
|
|
6706
|
+
function cloneModelInput(input) {
|
|
6707
|
+
return typeof input === "string" ? input : normalizeModelInput(input);
|
|
6708
|
+
}
|
|
6709
|
+
function describeModelInput(input) {
|
|
6710
|
+
if (typeof input === "string") return void 0;
|
|
6711
|
+
return {
|
|
6712
|
+
parts: input.map((part) => {
|
|
6713
|
+
if (part.type === "text") return { type: "text", characters: part.text.length };
|
|
6714
|
+
const { source } = part;
|
|
6715
|
+
return {
|
|
6716
|
+
type: "image",
|
|
6717
|
+
source: source.type,
|
|
6718
|
+
...source.mediaType ? { mediaType: source.mediaType } : {},
|
|
6719
|
+
...source.type === "bytes" ? { bytes: source.data.byteLength } : {},
|
|
6720
|
+
...source.type === "base64" ? {
|
|
6721
|
+
bytes: decodedBase64Bytes(source.data)
|
|
6722
|
+
} : {},
|
|
6723
|
+
...(source.type === "url" ? source.url : source.type === "provider-file" ? source.reference : void 0) ? {
|
|
6724
|
+
locator: source.type === "url" ? source.url : source.type === "provider-file" ? source.reference : void 0
|
|
6725
|
+
} : {},
|
|
6726
|
+
...part.label ? { label: part.label } : {}
|
|
6727
|
+
};
|
|
6728
|
+
})
|
|
6729
|
+
};
|
|
6730
|
+
}
|
|
6731
|
+
function summarizeModelInput(input) {
|
|
6732
|
+
if (typeof input === "string") return input;
|
|
6733
|
+
return input.map(
|
|
6734
|
+
(part) => part.type === "text" ? part.text : `[image ${part.source.mediaType ?? "media"}]`
|
|
6735
|
+
).join("\n");
|
|
6736
|
+
}
|
|
6737
|
+
function sanitizeModelInputForTrace(input) {
|
|
6738
|
+
return inputText(input);
|
|
6739
|
+
}
|
|
6740
|
+
function normalizePersistedSessionHistory(history) {
|
|
6741
|
+
return history.map((message, index) => {
|
|
6742
|
+
if (typeof message.content === "string") return { ...message };
|
|
6743
|
+
if (message.role !== "user") {
|
|
6744
|
+
throw new InvalidModelInputError(
|
|
6745
|
+
`History message ${index} has non-text content on a non-user role`
|
|
6746
|
+
);
|
|
6747
|
+
}
|
|
6748
|
+
const content = normalizeModelInput(message.content);
|
|
6749
|
+
if (typeof content !== "string" && content.some((part) => part.type === "image" && part.source.type === "bytes")) {
|
|
6750
|
+
throw new InvalidModelInputError(
|
|
6751
|
+
"Uint8Array image input cannot be persisted in session history"
|
|
6752
|
+
);
|
|
6753
|
+
}
|
|
6754
|
+
return { ...message, content };
|
|
6755
|
+
});
|
|
6756
|
+
}
|
|
6757
|
+
|
|
4763
6758
|
// src/partial-json.ts
|
|
4764
6759
|
var MAX_DEPTH = 256;
|
|
4765
6760
|
function parsePartialJson(input) {
|
|
@@ -5331,7 +7326,8 @@ var COST_BEARING_LEAF_TYPES = [
|
|
|
5331
7326
|
"agent_call_end",
|
|
5332
7327
|
"tool_call_end",
|
|
5333
7328
|
"memory_remember",
|
|
5334
|
-
"memory_recall"
|
|
7329
|
+
"memory_recall",
|
|
7330
|
+
"transcription_end"
|
|
5335
7331
|
];
|
|
5336
7332
|
var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
5337
7333
|
function hasPositiveTokens(event) {
|
|
@@ -5339,6 +7335,12 @@ function hasPositiveTokens(event) {
|
|
|
5339
7335
|
if (!t) return false;
|
|
5340
7336
|
return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) + (t.cached ?? 0) + (t.cacheWrite ?? 0) > 0;
|
|
5341
7337
|
}
|
|
7338
|
+
function hasPositiveBillableWork(event) {
|
|
7339
|
+
if (hasPositiveTokens(event)) return true;
|
|
7340
|
+
const seconds = event.data?.usage?.audioSeconds;
|
|
7341
|
+
const totalTokens = event.data?.usage?.totalTokens;
|
|
7342
|
+
return typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0 || typeof totalTokens === "number" && Number.isFinite(totalTokens) && totalTokens > 0;
|
|
7343
|
+
}
|
|
5342
7344
|
function isUsableCost(cost) {
|
|
5343
7345
|
return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
|
|
5344
7346
|
}
|
|
@@ -5351,7 +7353,7 @@ function isCostBearingLeaf(event) {
|
|
|
5351
7353
|
return COST_LEAF_SET.has(event.type);
|
|
5352
7354
|
}
|
|
5353
7355
|
function isUnpricedLeaf(event) {
|
|
5354
|
-
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) &&
|
|
7356
|
+
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveBillableWork(event);
|
|
5355
7357
|
}
|
|
5356
7358
|
function isRootLevel(event) {
|
|
5357
7359
|
const d = event.depth;
|
|
@@ -5366,6 +7368,9 @@ var AXL_EVENT_TYPES = [
|
|
|
5366
7368
|
// Workflow lifecycle
|
|
5367
7369
|
"workflow_start",
|
|
5368
7370
|
"workflow_end",
|
|
7371
|
+
// Transcription operation lifecycle (separate from chat/agent turns)
|
|
7372
|
+
"transcription_start",
|
|
7373
|
+
"transcription_end",
|
|
5369
7374
|
// Ask boundary
|
|
5370
7375
|
"ask_start",
|
|
5371
7376
|
"ask_end",
|
|
@@ -5456,6 +7461,8 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
5456
7461
|
"schema_diagnostic",
|
|
5457
7462
|
"workflow_start",
|
|
5458
7463
|
"workflow_end",
|
|
7464
|
+
"transcription_start",
|
|
7465
|
+
"transcription_end",
|
|
5459
7466
|
"checkpoint_save",
|
|
5460
7467
|
"checkpoint_replay",
|
|
5461
7468
|
"await_human",
|
|
@@ -6271,6 +8278,21 @@ function redactGate(event) {
|
|
|
6271
8278
|
}
|
|
6272
8279
|
};
|
|
6273
8280
|
}
|
|
8281
|
+
function redactInputDescriptor(input) {
|
|
8282
|
+
return {
|
|
8283
|
+
...input,
|
|
8284
|
+
parts: input.parts.map((part) => {
|
|
8285
|
+
if (!part || typeof part !== "object" || !("type" in part) || part.type === "text")
|
|
8286
|
+
return part;
|
|
8287
|
+
const value = part;
|
|
8288
|
+
return {
|
|
8289
|
+
...part,
|
|
8290
|
+
...value.locator !== void 0 ? { locator: REDACTED } : {},
|
|
8291
|
+
...value.label !== void 0 ? { label: REDACTED } : {}
|
|
8292
|
+
};
|
|
8293
|
+
})
|
|
8294
|
+
};
|
|
8295
|
+
}
|
|
6274
8296
|
function redactMemory(event) {
|
|
6275
8297
|
const d = event.data;
|
|
6276
8298
|
if (!d || typeof d !== "object") return event;
|
|
@@ -6294,7 +8316,11 @@ var LEGACY_REDACTION_RULES = {
|
|
|
6294
8316
|
}
|
|
6295
8317
|
};
|
|
6296
8318
|
},
|
|
6297
|
-
ask_start: (e) => ({
|
|
8319
|
+
ask_start: (e) => ({
|
|
8320
|
+
...e,
|
|
8321
|
+
prompt: REDACTED,
|
|
8322
|
+
...e.input ? { input: redactInputDescriptor(e.input) } : {}
|
|
8323
|
+
}),
|
|
6298
8324
|
ask_end: (e) => ({
|
|
6299
8325
|
...e,
|
|
6300
8326
|
outcome: e.outcome.ok ? { ok: true, result: REDACTED } : { ok: false, error: REDACTED }
|
|
@@ -6302,6 +8328,14 @@ var LEGACY_REDACTION_RULES = {
|
|
|
6302
8328
|
agent_call_start: (e) => {
|
|
6303
8329
|
const d = e.data;
|
|
6304
8330
|
const out = { ...d, prompt: REDACTED };
|
|
8331
|
+
if (d.input) {
|
|
8332
|
+
out.input = redactInputDescriptor(d.input);
|
|
8333
|
+
}
|
|
8334
|
+
if (d.messageInputs)
|
|
8335
|
+
out.messageInputs = d.messageInputs.map((entry) => ({
|
|
8336
|
+
...entry,
|
|
8337
|
+
input: redactInputDescriptor(entry.input)
|
|
8338
|
+
}));
|
|
6305
8339
|
if (d.system !== void 0) out.system = REDACTED;
|
|
6306
8340
|
if (Array.isArray(d.messages)) {
|
|
6307
8341
|
out.messages = [{ role: "system", content: `[${d.messages.length} messages redacted]` }];
|
|
@@ -6457,6 +8491,18 @@ function reuseLegacyRule(type) {
|
|
|
6457
8491
|
var COMMON_V2_REDACTION_RULES = {
|
|
6458
8492
|
workflow_start: reuseLegacyRule("workflow_start"),
|
|
6459
8493
|
workflow_end: reuseLegacyRule("workflow_end"),
|
|
8494
|
+
transcription_start: (e) => e,
|
|
8495
|
+
transcription_end: (e) => {
|
|
8496
|
+
const d = e.data;
|
|
8497
|
+
return {
|
|
8498
|
+
...e,
|
|
8499
|
+
data: {
|
|
8500
|
+
...d,
|
|
8501
|
+
...d.text !== void 0 ? { text: REDACTED } : {},
|
|
8502
|
+
...d.error !== void 0 ? { error: REDACTED } : {}
|
|
8503
|
+
}
|
|
8504
|
+
};
|
|
8505
|
+
},
|
|
6460
8506
|
ask_start: reuseLegacyRule("ask_start"),
|
|
6461
8507
|
ask_end: reuseLegacyRule("ask_end"),
|
|
6462
8508
|
agent_call_start: reuseLegacyRule("agent_call_start"),
|
|
@@ -6651,7 +8697,7 @@ function resolveConfig(config) {
|
|
|
6651
8697
|
|
|
6652
8698
|
// src/tool-model-output.ts
|
|
6653
8699
|
var import_node_util = require("util");
|
|
6654
|
-
function
|
|
8700
|
+
function invalid2(path, reason) {
|
|
6655
8701
|
return new Error(`Invalid tool model output at ${path}: ${reason}`);
|
|
6656
8702
|
}
|
|
6657
8703
|
function propertyPath(parent, key) {
|
|
@@ -6669,14 +8715,14 @@ function propertyPath(parent, key) {
|
|
|
6669
8715
|
function normalize(value, path, stack) {
|
|
6670
8716
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
6671
8717
|
if (typeof value === "number") {
|
|
6672
|
-
if (!Number.isFinite(value)) throw
|
|
8718
|
+
if (!Number.isFinite(value)) throw invalid2(path, "number must be finite");
|
|
6673
8719
|
return value;
|
|
6674
8720
|
}
|
|
6675
|
-
if (value === void 0) throw
|
|
8721
|
+
if (value === void 0) throw invalid2(path, "undefined is only allowed on object properties");
|
|
6676
8722
|
if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
|
|
6677
|
-
throw
|
|
8723
|
+
throw invalid2(path, `unsupported ${typeof value}`);
|
|
6678
8724
|
}
|
|
6679
|
-
if (typeof value !== "object") throw
|
|
8725
|
+
if (typeof value !== "object") throw invalid2(path, `unsupported ${typeof value}`);
|
|
6680
8726
|
let prototype;
|
|
6681
8727
|
let descriptors;
|
|
6682
8728
|
try {
|
|
@@ -6684,38 +8730,38 @@ function normalize(value, path, stack) {
|
|
|
6684
8730
|
descriptors = Object.getOwnPropertyDescriptors(value);
|
|
6685
8731
|
Object.setPrototypeOf(descriptors, null);
|
|
6686
8732
|
} catch (cause) {
|
|
6687
|
-
throw
|
|
8733
|
+
throw invalid2(
|
|
6688
8734
|
path,
|
|
6689
8735
|
`object inspection failed (${cause instanceof Error ? cause.name : "error"})`
|
|
6690
8736
|
);
|
|
6691
8737
|
}
|
|
6692
|
-
if (stack.has(value)) throw
|
|
8738
|
+
if (stack.has(value)) throw invalid2(path, "cyclic reference");
|
|
6693
8739
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
6694
8740
|
const descriptor = descriptors[key];
|
|
6695
8741
|
if (descriptor && !("value" in descriptor)) {
|
|
6696
|
-
throw
|
|
8742
|
+
throw invalid2(
|
|
6697
8743
|
typeof key === "string" ? propertyPath(path, key) : `${path}[symbol]`,
|
|
6698
8744
|
"accessor properties are not supported"
|
|
6699
8745
|
);
|
|
6700
8746
|
}
|
|
6701
8747
|
if (typeof key === "symbol") {
|
|
6702
|
-
throw
|
|
8748
|
+
throw invalid2(`${path}[symbol]`, "symbol keys are not supported");
|
|
6703
8749
|
}
|
|
6704
8750
|
}
|
|
6705
8751
|
if (typeof descriptors.toJSON?.value === "function") {
|
|
6706
|
-
throw
|
|
8752
|
+
throw invalid2(propertyPath(path, "toJSON"), "custom toJSON is not supported");
|
|
6707
8753
|
}
|
|
6708
8754
|
const array = Array.isArray(value);
|
|
6709
8755
|
if (array && prototype !== Array.prototype && prototype !== null) {
|
|
6710
|
-
throw
|
|
8756
|
+
throw invalid2(path, "array subclasses are not supported");
|
|
6711
8757
|
}
|
|
6712
8758
|
const inheritedToJSON = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "toJSON") : void 0;
|
|
6713
8759
|
if (typeof inheritedToJSON?.value === "function") {
|
|
6714
|
-
throw
|
|
8760
|
+
throw invalid2(propertyPath(path, "toJSON"), "inherited custom toJSON is not supported");
|
|
6715
8761
|
}
|
|
6716
8762
|
const inheritedThen = prototype === Array.prototype || prototype === Object.prototype ? Object.getOwnPropertyDescriptor(prototype, "then") : void 0;
|
|
6717
8763
|
if (typeof descriptors.then?.value === "function" || inheritedThen !== void 0 && (!("value" in inheritedThen) || typeof inheritedThen.value === "function")) {
|
|
6718
|
-
throw
|
|
8764
|
+
throw invalid2(path, "thenable output is not supported");
|
|
6719
8765
|
}
|
|
6720
8766
|
stack.add(value);
|
|
6721
8767
|
try {
|
|
@@ -6723,15 +8769,15 @@ function normalize(value, path, stack) {
|
|
|
6723
8769
|
const normalized2 = [];
|
|
6724
8770
|
const length = descriptors.length?.value;
|
|
6725
8771
|
if (!Number.isSafeInteger(length) || length < 0) {
|
|
6726
|
-
throw
|
|
8772
|
+
throw invalid2(propertyPath(path, "length"), "array length is invalid");
|
|
6727
8773
|
}
|
|
6728
8774
|
for (let index = 0; index < length; index++) {
|
|
6729
8775
|
const descriptor = descriptors[String(index)];
|
|
6730
8776
|
if (!descriptor || !("value" in descriptor)) {
|
|
6731
|
-
throw
|
|
8777
|
+
throw invalid2(`${path}[${index}]`, "arrays must be dense data properties");
|
|
6732
8778
|
}
|
|
6733
8779
|
if (descriptor.value === void 0) {
|
|
6734
|
-
throw
|
|
8780
|
+
throw invalid2(`${path}[${index}]`, "array values cannot be undefined");
|
|
6735
8781
|
}
|
|
6736
8782
|
normalized2.push(normalize(descriptor.value, `${path}[${index}]`, stack));
|
|
6737
8783
|
}
|
|
@@ -6740,21 +8786,21 @@ function normalize(value, path, stack) {
|
|
|
6740
8786
|
if (!descriptor?.enumerable) continue;
|
|
6741
8787
|
const index = Number(key);
|
|
6742
8788
|
if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== key) {
|
|
6743
|
-
throw
|
|
8789
|
+
throw invalid2(propertyPath(path, key), "non-index array properties are not supported");
|
|
6744
8790
|
}
|
|
6745
8791
|
}
|
|
6746
8792
|
Object.setPrototypeOf(normalized2, null);
|
|
6747
8793
|
return normalized2;
|
|
6748
8794
|
}
|
|
6749
8795
|
if (prototype !== Object.prototype && prototype !== null) {
|
|
6750
|
-
throw
|
|
8796
|
+
throw invalid2(path, "only plain objects and null-prototype records are supported");
|
|
6751
8797
|
}
|
|
6752
8798
|
const normalized = /* @__PURE__ */ Object.create(null);
|
|
6753
8799
|
for (const key of Object.keys(descriptors)) {
|
|
6754
8800
|
const descriptor = descriptors[key];
|
|
6755
8801
|
if (!descriptor?.enumerable) continue;
|
|
6756
8802
|
if (!("value" in descriptor)) {
|
|
6757
|
-
throw
|
|
8803
|
+
throw invalid2(propertyPath(path, key), "accessor properties are not supported");
|
|
6758
8804
|
}
|
|
6759
8805
|
if (descriptor.value === void 0) continue;
|
|
6760
8806
|
normalized[key] = normalize(descriptor.value, propertyPath(path, key), stack);
|
|
@@ -7262,44 +9308,44 @@ function resolveBranchDrainTimeoutMs(value) {
|
|
|
7262
9308
|
return resolved;
|
|
7263
9309
|
}
|
|
7264
9310
|
function parseHumanDecision(value) {
|
|
7265
|
-
const
|
|
9311
|
+
const invalid3 = () => {
|
|
7266
9312
|
throw new AxlError(
|
|
7267
9313
|
"INVALID_HUMAN_DECISION",
|
|
7268
9314
|
"Human decision must be a plain object with a boolean approved field and only branch-appropriate string data"
|
|
7269
9315
|
);
|
|
7270
9316
|
};
|
|
7271
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
9317
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) invalid3();
|
|
7272
9318
|
let prototype;
|
|
7273
9319
|
try {
|
|
7274
9320
|
prototype = Object.getPrototypeOf(value);
|
|
7275
9321
|
} catch {
|
|
7276
|
-
return
|
|
9322
|
+
return invalid3();
|
|
7277
9323
|
}
|
|
7278
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
9324
|
+
if (prototype !== Object.prototype && prototype !== null) invalid3();
|
|
7279
9325
|
let ownKeys;
|
|
7280
9326
|
try {
|
|
7281
9327
|
ownKeys = Reflect.ownKeys(value);
|
|
7282
9328
|
} catch {
|
|
7283
|
-
return
|
|
9329
|
+
return invalid3();
|
|
7284
9330
|
}
|
|
7285
9331
|
if (ownKeys.some(
|
|
7286
9332
|
(key) => typeof key !== "string" || key !== "approved" && key !== "data" && key !== "reason"
|
|
7287
9333
|
)) {
|
|
7288
|
-
|
|
9334
|
+
invalid3();
|
|
7289
9335
|
}
|
|
7290
9336
|
const readDataProperty = (key) => {
|
|
7291
9337
|
let descriptor;
|
|
7292
9338
|
try {
|
|
7293
9339
|
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
7294
9340
|
} catch {
|
|
7295
|
-
return
|
|
9341
|
+
return invalid3();
|
|
7296
9342
|
}
|
|
7297
9343
|
if (!descriptor) {
|
|
7298
|
-
if (ownKeys.includes(key))
|
|
9344
|
+
if (ownKeys.includes(key)) invalid3();
|
|
7299
9345
|
return { present: false, value: void 0 };
|
|
7300
9346
|
}
|
|
7301
|
-
if (!ownKeys.includes(key)) return
|
|
7302
|
-
if (!("value" in descriptor)) return
|
|
9347
|
+
if (!ownKeys.includes(key)) return invalid3();
|
|
9348
|
+
if (!("value" in descriptor)) return invalid3();
|
|
7303
9349
|
return { present: true, value: descriptor.value };
|
|
7304
9350
|
};
|
|
7305
9351
|
const approved = readDataProperty("approved");
|
|
@@ -7318,7 +9364,7 @@ function parseHumanDecision(value) {
|
|
|
7318
9364
|
};
|
|
7319
9365
|
}
|
|
7320
9366
|
}
|
|
7321
|
-
return
|
|
9367
|
+
return invalid3();
|
|
7322
9368
|
}
|
|
7323
9369
|
var askStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
7324
9370
|
var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7426,8 +9472,12 @@ function appendRetryMessages(messages, content, feedbackMessage, providerMetadat
|
|
|
7426
9472
|
}
|
|
7427
9473
|
function estimateMessagesTokens(messages) {
|
|
7428
9474
|
let total = 0;
|
|
9475
|
+
let unmeasured = false;
|
|
7429
9476
|
for (const msg of messages) {
|
|
7430
|
-
total += estimateTokens(msg.content);
|
|
9477
|
+
total += estimateTokens(inputText(msg.content));
|
|
9478
|
+
if (typeof msg.content !== "string") {
|
|
9479
|
+
unmeasured ||= msg.content.some((part) => part.type === "image");
|
|
9480
|
+
}
|
|
7431
9481
|
if (msg.tool_calls) {
|
|
7432
9482
|
for (const tc of msg.tool_calls) {
|
|
7433
9483
|
total += estimateTokens(tc.function.name + tc.function.arguments);
|
|
@@ -7435,7 +9485,73 @@ function estimateMessagesTokens(messages) {
|
|
|
7435
9485
|
}
|
|
7436
9486
|
total += 4;
|
|
7437
9487
|
}
|
|
7438
|
-
return total;
|
|
9488
|
+
return { tokens: total, unmeasured };
|
|
9489
|
+
}
|
|
9490
|
+
function cloneMessagesForProvider(messages) {
|
|
9491
|
+
return messages.map((message) => ({
|
|
9492
|
+
...message,
|
|
9493
|
+
content: cloneModelInput(message.content),
|
|
9494
|
+
...message.tool_calls ? {
|
|
9495
|
+
tool_calls: message.tool_calls.map((call) => ({
|
|
9496
|
+
...call,
|
|
9497
|
+
function: { ...call.function }
|
|
9498
|
+
}))
|
|
9499
|
+
} : {}
|
|
9500
|
+
}));
|
|
9501
|
+
}
|
|
9502
|
+
function assertRichMessagesAreUserOnly(messages) {
|
|
9503
|
+
for (const message of messages) {
|
|
9504
|
+
if (typeof message.content !== "string" && message.role !== "user") {
|
|
9505
|
+
throw new InvalidModelInputError(
|
|
9506
|
+
"Non-text model input parts are only supported on user messages"
|
|
9507
|
+
);
|
|
9508
|
+
}
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9511
|
+
function normalizeSessionHistory(messages) {
|
|
9512
|
+
return messages.map((message) => {
|
|
9513
|
+
if (typeof message.content === "string") return { ...message };
|
|
9514
|
+
if (message.role !== "user") {
|
|
9515
|
+
throw new InvalidModelInputError(
|
|
9516
|
+
"Non-text model input parts are only supported on user messages"
|
|
9517
|
+
);
|
|
9518
|
+
}
|
|
9519
|
+
return { ...message, content: normalizeModelInput(message.content) };
|
|
9520
|
+
});
|
|
9521
|
+
}
|
|
9522
|
+
function hasRichMessage(messages) {
|
|
9523
|
+
return messages.some((message) => typeof message.content !== "string");
|
|
9524
|
+
}
|
|
9525
|
+
function firstImageSource(input, history) {
|
|
9526
|
+
const find = (value) => typeof value === "string" ? void 0 : value.find((part) => part.type === "image")?.source.type;
|
|
9527
|
+
return find(input) ?? history.map((message) => find(message.content)).find(Boolean);
|
|
9528
|
+
}
|
|
9529
|
+
function appendHandoffInstruction(input, originalText, instruction) {
|
|
9530
|
+
if (typeof input === "string" || instruction.length === 0 || instruction === originalText)
|
|
9531
|
+
return input;
|
|
9532
|
+
return [...input, { type: "text", text: instruction }];
|
|
9533
|
+
}
|
|
9534
|
+
var RICH_MODEL_INPUT_ERROR_MESSAGE = "Provider request failed while processing rich model input";
|
|
9535
|
+
var RICH_MODEL_INPUT_ERROR_NAME = "RichModelInputError";
|
|
9536
|
+
var MAX_RICH_FAILURE_ASSOCIATIONS = 32;
|
|
9537
|
+
var MAX_RICH_FAILURE_CAUSE_DEPTH = 4;
|
|
9538
|
+
function legacyErrorMessage(error) {
|
|
9539
|
+
return error instanceof Error ? error.message : String(error);
|
|
9540
|
+
}
|
|
9541
|
+
function providerErrorEventMetadata(error, richRequest) {
|
|
9542
|
+
if (!(error instanceof ProviderError)) {
|
|
9543
|
+
return error instanceof AxlError && richRequest ? { code: error.code } : {};
|
|
9544
|
+
}
|
|
9545
|
+
return {
|
|
9546
|
+
status: error.status,
|
|
9547
|
+
retryable: error.retryable,
|
|
9548
|
+
...richRequest ? {
|
|
9549
|
+
code: error.code,
|
|
9550
|
+
provider: error.provider,
|
|
9551
|
+
...error.requestId ? { requestId: error.requestId } : {},
|
|
9552
|
+
...error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
9553
|
+
} : {}
|
|
9554
|
+
};
|
|
7439
9555
|
}
|
|
7440
9556
|
var WorkflowContext = class _WorkflowContext {
|
|
7441
9557
|
input;
|
|
@@ -7451,6 +9567,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7451
9567
|
}
|
|
7452
9568
|
config;
|
|
7453
9569
|
providerRegistry;
|
|
9570
|
+
transcriptionProviderRegistry;
|
|
7454
9571
|
sessionHistory;
|
|
7455
9572
|
onTrace;
|
|
7456
9573
|
pendingDecisions;
|
|
@@ -7476,6 +9593,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7476
9593
|
*/
|
|
7477
9594
|
autoCheckpointCounters;
|
|
7478
9595
|
branchDrainState;
|
|
9596
|
+
/** Bounded exact-identity failure association for this execution tree.
|
|
9597
|
+
* Unlike a process-global marker, this cannot affect another context that
|
|
9598
|
+
* happens to reuse the same Error object. */
|
|
9599
|
+
richFailureState;
|
|
7479
9600
|
/** Root-shared idempotency guards for workflow lifecycle emission.
|
|
7480
9601
|
* Both `runtime.execute()` and `runtime.stream()` have paths where a
|
|
7481
9602
|
* post-emit side-effect (checkpoint deletion, state-store persistence)
|
|
@@ -7559,7 +9680,8 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7559
9680
|
get events() {
|
|
7560
9681
|
if (!this._busRef.current) {
|
|
7561
9682
|
this._busRef.current = new AxlEventBus(this._eventStreamOptions);
|
|
7562
|
-
|
|
9683
|
+
const signal = this.currentSignal;
|
|
9684
|
+
if (signal?.aborted) {
|
|
7563
9685
|
this._busRef.current._finish();
|
|
7564
9686
|
}
|
|
7565
9687
|
}
|
|
@@ -7597,6 +9719,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7597
9719
|
this.metadata = init.metadata ?? {};
|
|
7598
9720
|
this.config = init.config;
|
|
7599
9721
|
this.providerRegistry = init.providerRegistry;
|
|
9722
|
+
this.transcriptionProviderRegistry = init.transcriptionProviderRegistry;
|
|
7600
9723
|
this.sessionHistory = init.sessionHistory ?? [];
|
|
7601
9724
|
this.onTrace = init.onTrace;
|
|
7602
9725
|
this.pendingDecisions = init.pendingDecisions;
|
|
@@ -7630,6 +9753,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7630
9753
|
byAgent: /* @__PURE__ */ new Map(),
|
|
7631
9754
|
root: 0
|
|
7632
9755
|
};
|
|
9756
|
+
this.richFailureState = init.richFailureState ?? {
|
|
9757
|
+
associations: [],
|
|
9758
|
+
overflowed: false,
|
|
9759
|
+
indeterminate: false
|
|
9760
|
+
};
|
|
7633
9761
|
const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(init.branchDrainTimeoutMs);
|
|
7634
9762
|
this.branchDrainState = init.branchDrainState ?? {
|
|
7635
9763
|
pending: /* @__PURE__ */ new Set(),
|
|
@@ -7662,6 +9790,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7662
9790
|
executionId: this.executionId,
|
|
7663
9791
|
config: this.config,
|
|
7664
9792
|
providerRegistry: this.providerRegistry,
|
|
9793
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
7665
9794
|
metadata: { ...this.metadata },
|
|
7666
9795
|
// Shared infrastructure
|
|
7667
9796
|
budgetContext: this.budgetContext,
|
|
@@ -7684,6 +9813,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7684
9813
|
autoCheckpointCounters: this.autoCheckpointCounters,
|
|
7685
9814
|
branchDrainState: this.branchDrainState,
|
|
7686
9815
|
workflowLifecycleState: this.workflowLifecycleState,
|
|
9816
|
+
richFailureState: this.richFailureState,
|
|
7687
9817
|
// Share the parent's bus slot (mutable ref). Late allocation in
|
|
7688
9818
|
// either parent or child propagates because both read the same
|
|
7689
9819
|
// slot. Nested-ask events (agent-as-tool pattern) bubble up to
|
|
@@ -7695,6 +9825,52 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7695
9825
|
// Isolated: sessionHistory (empty)
|
|
7696
9826
|
});
|
|
7697
9827
|
}
|
|
9828
|
+
/** Associate an exact thrown value with rich input in this execution only. */
|
|
9829
|
+
recordRichFailure(error) {
|
|
9830
|
+
const state = this.richFailureState;
|
|
9831
|
+
if (state.associations.some((known) => Object.is(known, error))) return;
|
|
9832
|
+
if (state.associations.length >= MAX_RICH_FAILURE_ASSOCIATIONS) {
|
|
9833
|
+
state.associations.shift();
|
|
9834
|
+
state.overflowed = true;
|
|
9835
|
+
state.indeterminate = true;
|
|
9836
|
+
}
|
|
9837
|
+
state.associations.push(error);
|
|
9838
|
+
}
|
|
9839
|
+
/** @internal Safe observer projection for a failure associated with rich input. */
|
|
9840
|
+
_observerErrorProjection(error) {
|
|
9841
|
+
const state = this.richFailureState;
|
|
9842
|
+
const safe = () => ({
|
|
9843
|
+
message: RICH_MODEL_INPUT_ERROR_MESSAGE,
|
|
9844
|
+
name: RICH_MODEL_INPUT_ERROR_NAME
|
|
9845
|
+
});
|
|
9846
|
+
const indeterminate = () => {
|
|
9847
|
+
if (state.associations.length === 0) return void 0;
|
|
9848
|
+
state.indeterminate = true;
|
|
9849
|
+
return safe();
|
|
9850
|
+
};
|
|
9851
|
+
let current = error;
|
|
9852
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
9853
|
+
for (let depth = 0; depth < MAX_RICH_FAILURE_CAUSE_DEPTH; depth++) {
|
|
9854
|
+
if (state.associations.some((known) => Object.is(known, current))) {
|
|
9855
|
+
return safe();
|
|
9856
|
+
}
|
|
9857
|
+
if ((typeof current !== "object" || current === null) && typeof current !== "function") {
|
|
9858
|
+
return state.overflowed || state.indeterminate ? safe() : void 0;
|
|
9859
|
+
}
|
|
9860
|
+
if (seen.has(current)) return indeterminate();
|
|
9861
|
+
seen.add(current);
|
|
9862
|
+
let descriptor;
|
|
9863
|
+
try {
|
|
9864
|
+
descriptor = Object.getOwnPropertyDescriptor(current, "cause");
|
|
9865
|
+
} catch {
|
|
9866
|
+
return indeterminate();
|
|
9867
|
+
}
|
|
9868
|
+
if (!descriptor) return state.overflowed || state.indeterminate ? safe() : void 0;
|
|
9869
|
+
if (!("value" in descriptor)) return indeterminate();
|
|
9870
|
+
current = descriptor.value;
|
|
9871
|
+
}
|
|
9872
|
+
return indeterminate();
|
|
9873
|
+
}
|
|
7698
9874
|
/**
|
|
7699
9875
|
* Resolve the current abort signal.
|
|
7700
9876
|
* Branch-scoped signals (from race/spawn/map/budget) in AsyncLocalStorage
|
|
@@ -7896,6 +10072,17 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7896
10072
|
}
|
|
7897
10073
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
7898
10074
|
async ask(agent2, prompt, options) {
|
|
10075
|
+
const normalizedInput = normalizeModelInput(prompt);
|
|
10076
|
+
const normalizedHistory = normalizeSessionHistory(this.sessionHistory);
|
|
10077
|
+
const delegateInputHolder = agent2;
|
|
10078
|
+
if (delegateInputHolder._delegateOriginalInput !== void 0) {
|
|
10079
|
+
delegateInputHolder._delegateOriginalInput = normalizeModelInput(
|
|
10080
|
+
delegateInputHolder._delegateOriginalInput
|
|
10081
|
+
);
|
|
10082
|
+
}
|
|
10083
|
+
const richAsk = typeof normalizedInput !== "string" || hasRichMessage(normalizedHistory) || delegateInputHolder._delegateOriginalInput !== void 0 && typeof delegateInputHolder._delegateOriginalInput !== "string";
|
|
10084
|
+
const promptText = inputText(normalizedInput);
|
|
10085
|
+
const inputDescriptor = describeModelInput(normalizedInput);
|
|
7899
10086
|
const agentName = agent2._name;
|
|
7900
10087
|
return this._checkpoint(
|
|
7901
10088
|
this._autoCheckpointName("ask", agentName),
|
|
@@ -7915,7 +10102,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7915
10102
|
};
|
|
7916
10103
|
return askStorage.run(frame, async () => {
|
|
7917
10104
|
const askStart = Date.now();
|
|
7918
|
-
this.emitEvent({
|
|
10105
|
+
this.emitEvent({
|
|
10106
|
+
type: "ask_start",
|
|
10107
|
+
prompt: promptText,
|
|
10108
|
+
...inputDescriptor ? { input: inputDescriptor } : {}
|
|
10109
|
+
});
|
|
7919
10110
|
const costBefore = this.budgetContext?.totalCost ?? 0;
|
|
7920
10111
|
const unpricedCountBefore = this.budgetContext?.unpricedCount ?? 0;
|
|
7921
10112
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
@@ -7923,10 +10114,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7923
10114
|
const doCall = async () => {
|
|
7924
10115
|
const result2 = await this.executeAgentCall(
|
|
7925
10116
|
agent2,
|
|
7926
|
-
|
|
10117
|
+
normalizedInput,
|
|
7927
10118
|
options,
|
|
7928
10119
|
void 0,
|
|
7929
|
-
usageCapture
|
|
10120
|
+
usageCapture,
|
|
10121
|
+
normalizedHistory
|
|
7930
10122
|
);
|
|
7931
10123
|
return result2;
|
|
7932
10124
|
};
|
|
@@ -7943,6 +10135,9 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7943
10135
|
},
|
|
7944
10136
|
async (span) => {
|
|
7945
10137
|
const r = await doCall();
|
|
10138
|
+
if (usageCapture.modelUri) {
|
|
10139
|
+
span.setAttribute("axl.agent.model", usageCapture.modelUri);
|
|
10140
|
+
}
|
|
7946
10141
|
const costAfter2 = this.budgetContext?.totalCost ?? 0;
|
|
7947
10142
|
span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
|
|
7948
10143
|
span.setAttribute("axl.agent.duration", Date.now() - askStart);
|
|
@@ -7976,9 +10171,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
7976
10171
|
try {
|
|
7977
10172
|
this.onAgentCallComplete({
|
|
7978
10173
|
agent: agent2._name,
|
|
7979
|
-
prompt,
|
|
10174
|
+
prompt: promptText,
|
|
10175
|
+
...inputDescriptor ? { input: inputDescriptor } : {},
|
|
7980
10176
|
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
7981
|
-
model: agent2.resolveModel(resolveCtx),
|
|
10177
|
+
model: usageCapture.modelUri ?? agent2.resolveModel(resolveCtx),
|
|
7982
10178
|
cost: costAfter - costBefore,
|
|
7983
10179
|
unpriced: frame.askUnpriced || unpricedCountAfter > unpricedCountBefore,
|
|
7984
10180
|
duration: Date.now() - askStart,
|
|
@@ -8002,9 +10198,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8002
10198
|
} catch (err) {
|
|
8003
10199
|
askFailed = true;
|
|
8004
10200
|
askFailure = err;
|
|
10201
|
+
if (richAsk) this.recordRichFailure(err);
|
|
8005
10202
|
outcome = {
|
|
8006
10203
|
ok: false,
|
|
8007
|
-
error: err
|
|
10204
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
|
|
8008
10205
|
};
|
|
8009
10206
|
}
|
|
8010
10207
|
let terminalFailure;
|
|
@@ -8032,7 +10229,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8032
10229
|
{ agent: agentName }
|
|
8033
10230
|
);
|
|
8034
10231
|
}
|
|
8035
|
-
async executeAgentCall(agent2,
|
|
10232
|
+
async executeAgentCall(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
|
|
8036
10233
|
if (this.budgetContext?.exceeded) {
|
|
8037
10234
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
8038
10235
|
if (policy === "warn") {
|
|
@@ -8049,7 +10246,82 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8049
10246
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
8050
10247
|
const modelUri = agent2.resolveModel(resolveCtx);
|
|
8051
10248
|
const systemPrompt = agent2.resolveSystem(resolveCtx);
|
|
8052
|
-
const { provider, model } = this.providerRegistry.resolve(modelUri, this.config);
|
|
10249
|
+
const { provider, model: resolvedModel } = this.providerRegistry.resolve(modelUri, this.config);
|
|
10250
|
+
let model = resolvedModel;
|
|
10251
|
+
const prompt = inputText(input);
|
|
10252
|
+
const providerOptions = options?.providerOptions ?? agent2._config.providerOptions;
|
|
10253
|
+
const richRequest = typeof input !== "string" || hasRichMessage(sessionHistory);
|
|
10254
|
+
if (richRequest) {
|
|
10255
|
+
if (providerOptions && ("messages" in providerOptions || "input" in providerOptions)) {
|
|
10256
|
+
throw new UnsupportedModelInputError({
|
|
10257
|
+
provider: provider.name ?? modelUri.split(":", 1)[0],
|
|
10258
|
+
model,
|
|
10259
|
+
modality: "image",
|
|
10260
|
+
feature: "raw input-container providerOptions"
|
|
10261
|
+
});
|
|
10262
|
+
}
|
|
10263
|
+
const source = firstImageSource(input, sessionHistory);
|
|
10264
|
+
if (!provider.validateInput) {
|
|
10265
|
+
throw new UnsupportedModelInputError({
|
|
10266
|
+
provider: provider.name ?? modelUri.split(":", 1)[0],
|
|
10267
|
+
model,
|
|
10268
|
+
modality: "image",
|
|
10269
|
+
...source ? { source } : {}
|
|
10270
|
+
});
|
|
10271
|
+
}
|
|
10272
|
+
const validation = provider.validateInput({
|
|
10273
|
+
model,
|
|
10274
|
+
input: cloneModelInput(input),
|
|
10275
|
+
history: cloneMessagesForProvider(sessionHistory),
|
|
10276
|
+
stream: this._streamingEnabled,
|
|
10277
|
+
hasTools: Boolean(
|
|
10278
|
+
agent2._config.tools?.length || agent2._config.mcp?.length || agent2._config.mcpTools?.length || agent2._config.handoffs
|
|
10279
|
+
),
|
|
10280
|
+
responseMode: options?.schema ? "structured" : "text",
|
|
10281
|
+
providerOptions
|
|
10282
|
+
});
|
|
10283
|
+
if (!validation || typeof validation.effectiveModel !== "string" || validation.effectiveModel.length === 0) {
|
|
10284
|
+
throw new UnsupportedModelInputError({
|
|
10285
|
+
provider: provider.name ?? modelUri.split(":", 1)[0],
|
|
10286
|
+
model,
|
|
10287
|
+
modality: "image",
|
|
10288
|
+
...source ? { source } : {}
|
|
10289
|
+
});
|
|
10290
|
+
}
|
|
10291
|
+
model = validation.effectiveModel;
|
|
10292
|
+
}
|
|
10293
|
+
const effectiveModelUri = !richRequest ? modelUri : `${modelUri.slice(0, modelUri.indexOf(":") + 1)}${model}`;
|
|
10294
|
+
if (usageCapture) usageCapture.modelUri ??= effectiveModelUri;
|
|
10295
|
+
if (richRequest) {
|
|
10296
|
+
const descriptors = [
|
|
10297
|
+
describeModelInput(input),
|
|
10298
|
+
...sessionHistory.map((message) => describeModelInput(message.content))
|
|
10299
|
+
].filter(
|
|
10300
|
+
(descriptor) => descriptor !== void 0
|
|
10301
|
+
);
|
|
10302
|
+
const parts = descriptors.flatMap((descriptor) => descriptor.parts);
|
|
10303
|
+
this.spanManager?.addEventToActiveSpan("axl.model_input", {
|
|
10304
|
+
"axl.input.parts": parts.length,
|
|
10305
|
+
"axl.input.images": parts.filter((part) => part.type === "image").length,
|
|
10306
|
+
"axl.input.source.bytes": parts.filter(
|
|
10307
|
+
(part) => part.type === "image" && part.source === "bytes"
|
|
10308
|
+
).length,
|
|
10309
|
+
"axl.input.source.base64": parts.filter(
|
|
10310
|
+
(part) => part.type === "image" && part.source === "base64"
|
|
10311
|
+
).length,
|
|
10312
|
+
"axl.input.source.url": parts.filter(
|
|
10313
|
+
(part) => part.type === "image" && part.source === "url"
|
|
10314
|
+
).length,
|
|
10315
|
+
"axl.input.source.provider_file": parts.filter(
|
|
10316
|
+
(part) => part.type === "image" && part.source === "provider-file"
|
|
10317
|
+
).length,
|
|
10318
|
+
"axl.input.inline_bytes": parts.reduce(
|
|
10319
|
+
(sum, part) => sum + (part.type === "image" ? part.bytes ?? 0 : 0),
|
|
10320
|
+
0
|
|
10321
|
+
)
|
|
10322
|
+
});
|
|
10323
|
+
}
|
|
10324
|
+
assertRichMessagesAreUserOnly(sessionHistory);
|
|
8053
10325
|
let resolvedHandoffs;
|
|
8054
10326
|
if (typeof agent2._config.handoffs === "function") {
|
|
8055
10327
|
try {
|
|
@@ -8070,50 +10342,60 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
8070
10342
|
messages.push({ role: "system", content: systemPrompt });
|
|
8071
10343
|
}
|
|
8072
10344
|
const maxContext = agent2._config.maxContext;
|
|
8073
|
-
if (maxContext &&
|
|
10345
|
+
if (maxContext && sessionHistory.length > 0) {
|
|
8074
10346
|
const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
|
|
8075
10347
|
const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;
|
|
8076
10348
|
const toolTokens = toolDefs.length > 0 ? estimateTokens(JSON.stringify(toolDefs)) : 0;
|
|
8077
10349
|
const overhead = systemTokens + toolTokens + reserveTokens;
|
|
8078
10350
|
const availableForHistory = maxContext - overhead;
|
|
8079
|
-
const
|
|
8080
|
-
if (
|
|
10351
|
+
const historyEstimate = estimateMessagesTokens(sessionHistory);
|
|
10352
|
+
if (historyEstimate.unmeasured) {
|
|
10353
|
+
this.emitEvent({
|
|
10354
|
+
type: "log",
|
|
10355
|
+
data: {
|
|
10356
|
+
warning: "Context media contribution is unmeasured; provider will enforce its context limit"
|
|
10357
|
+
}
|
|
10358
|
+
});
|
|
10359
|
+
}
|
|
10360
|
+
if (historyEstimate.tokens > availableForHistory) {
|
|
8081
10361
|
const summarizedMessages = await this.summarizeHistory(
|
|
8082
10362
|
provider,
|
|
8083
10363
|
model,
|
|
8084
|
-
|
|
10364
|
+
sessionHistory,
|
|
8085
10365
|
availableForHistory
|
|
8086
10366
|
);
|
|
8087
10367
|
for (const msg of summarizedMessages) {
|
|
8088
10368
|
messages.push(msg);
|
|
8089
10369
|
}
|
|
8090
10370
|
} else {
|
|
8091
|
-
for (const msg of
|
|
10371
|
+
for (const msg of sessionHistory) {
|
|
8092
10372
|
messages.push(msg);
|
|
8093
10373
|
}
|
|
8094
10374
|
}
|
|
8095
10375
|
} else {
|
|
8096
|
-
for (const msg of
|
|
10376
|
+
for (const msg of sessionHistory) {
|
|
8097
10377
|
messages.push(msg);
|
|
8098
10378
|
}
|
|
8099
10379
|
}
|
|
8100
10380
|
const schemaPromptMode = options?.schemaPrompt ?? agent2._config.schemaPrompt ?? "json-schema";
|
|
8101
|
-
let userContent =
|
|
10381
|
+
let userContent = input;
|
|
8102
10382
|
let appendedSchemaText;
|
|
8103
10383
|
if (options?.schema) {
|
|
8104
10384
|
const schema = options.schema;
|
|
8105
10385
|
if (schemaPromptMode === "none") {
|
|
8106
10386
|
} else if (schemaPromptMode === "json-schema") {
|
|
8107
10387
|
appendedSchemaText = renderSchemaForPrompt(schema);
|
|
8108
|
-
|
|
10388
|
+
const guidance = `
|
|
8109
10389
|
|
|
8110
10390
|
Respond with valid JSON matching this schema:
|
|
8111
10391
|
${appendedSchemaText}`;
|
|
10392
|
+
userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
|
|
8112
10393
|
} else {
|
|
8113
10394
|
appendedSchemaText = typeof schemaPromptMode.render === "function" ? schemaPromptMode.render(schema) : schemaPromptMode.render;
|
|
8114
|
-
|
|
10395
|
+
const guidance = `
|
|
8115
10396
|
|
|
8116
10397
|
${appendedSchemaText}`;
|
|
10398
|
+
userContent = typeof input === "string" ? input + guidance : [...input, { type: "text", text: guidance }];
|
|
8117
10399
|
}
|
|
8118
10400
|
}
|
|
8119
10401
|
messages.push({ role: "user", content: userContent });
|
|
@@ -8141,7 +10423,10 @@ ${appendedSchemaText}`;
|
|
|
8141
10423
|
}
|
|
8142
10424
|
const guardrails = agent2._config.guardrails;
|
|
8143
10425
|
if (guardrails?.input) {
|
|
8144
|
-
const inputResult = await guardrails.input(prompt, {
|
|
10426
|
+
const inputResult = await guardrails.input(prompt, {
|
|
10427
|
+
metadata: this.metadata,
|
|
10428
|
+
input: cloneModelInput(input)
|
|
10429
|
+
});
|
|
8145
10430
|
this.emitEvent({
|
|
8146
10431
|
type: "guardrail",
|
|
8147
10432
|
agent: agent2._name,
|
|
@@ -8232,7 +10517,7 @@ ${appendedSchemaText}`;
|
|
|
8232
10517
|
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
8233
10518
|
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
8234
10519
|
stop: options?.stop ?? agent2._config.stop,
|
|
8235
|
-
providerOptions
|
|
10520
|
+
providerOptions,
|
|
8236
10521
|
signal: this.currentSignal
|
|
8237
10522
|
};
|
|
8238
10523
|
if (options?.schema && toolDefs.length === 0) {
|
|
@@ -8258,15 +10543,26 @@ ${appendedSchemaText}`;
|
|
|
8258
10543
|
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
8259
10544
|
};
|
|
8260
10545
|
let messagesSnapshot;
|
|
10546
|
+
let messageInputs;
|
|
8261
10547
|
if (verboseTrace) {
|
|
8262
10548
|
try {
|
|
8263
|
-
|
|
10549
|
+
messageInputs = currentMessages.flatMap((message, index) => {
|
|
10550
|
+
const descriptor = describeModelInput(message.content);
|
|
10551
|
+
return descriptor ? [{ index, input: descriptor }] : [];
|
|
10552
|
+
});
|
|
10553
|
+
messagesSnapshot = currentMessages.map((message) => ({
|
|
10554
|
+
...message,
|
|
10555
|
+
content: sanitizeModelInputForTrace(message.content)
|
|
10556
|
+
}));
|
|
8264
10557
|
} catch (err) {
|
|
8265
10558
|
console.warn(
|
|
8266
10559
|
"[axl] verbose trace messages snapshot failed to clone; emitting shallow copy:",
|
|
8267
10560
|
err instanceof Error ? err.message : String(err)
|
|
8268
10561
|
);
|
|
8269
|
-
messagesSnapshot =
|
|
10562
|
+
messagesSnapshot = currentMessages.map((message) => ({
|
|
10563
|
+
...message,
|
|
10564
|
+
content: sanitizeModelInputForTrace(message.content)
|
|
10565
|
+
}));
|
|
8270
10566
|
}
|
|
8271
10567
|
}
|
|
8272
10568
|
const retryReason = pendingRetryReason;
|
|
@@ -8274,16 +10570,18 @@ ${appendedSchemaText}`;
|
|
|
8274
10570
|
this.emitEvent({
|
|
8275
10571
|
type: "agent_call_start",
|
|
8276
10572
|
agent: agent2._name,
|
|
8277
|
-
model:
|
|
10573
|
+
model: effectiveModelUri,
|
|
8278
10574
|
turn: turns,
|
|
8279
10575
|
data: {
|
|
8280
10576
|
prompt,
|
|
10577
|
+
...typeof input === "string" ? {} : { input: describeModelInput(input) },
|
|
8281
10578
|
...systemPrompt ? { system: systemPrompt } : {},
|
|
8282
10579
|
params: callParams,
|
|
8283
10580
|
turn: turns,
|
|
8284
10581
|
...retryReason ? { retryReason } : {},
|
|
8285
10582
|
...toolDefs.length > 0 ? { toolNames: toolDefs.map((t) => t.function.name) } : {},
|
|
8286
|
-
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
10583
|
+
...messagesSnapshot ? { messages: messagesSnapshot } : {},
|
|
10584
|
+
...messageInputs && messageInputs.length > 0 ? { messageInputs } : {}
|
|
8287
10585
|
}
|
|
8288
10586
|
});
|
|
8289
10587
|
let response;
|
|
@@ -8307,7 +10605,10 @@ ${appendedSchemaText}`;
|
|
|
8307
10605
|
});
|
|
8308
10606
|
}
|
|
8309
10607
|
}) : void 0;
|
|
8310
|
-
for await (const chunk of provider.stream(
|
|
10608
|
+
for await (const chunk of provider.stream(
|
|
10609
|
+
cloneMessagesForProvider(currentMessages),
|
|
10610
|
+
chatOptions
|
|
10611
|
+
)) {
|
|
8311
10612
|
if (chunk.type === "text_delta") {
|
|
8312
10613
|
content2 += chunk.content;
|
|
8313
10614
|
this.emitEvent({ type: "token", data: chunk.content });
|
|
@@ -8373,24 +10674,25 @@ ${appendedSchemaText}`;
|
|
|
8373
10674
|
response.thinking_content = thinkingContent;
|
|
8374
10675
|
}
|
|
8375
10676
|
} else {
|
|
8376
|
-
response = await provider.chat(currentMessages, chatOptions);
|
|
10677
|
+
response = await provider.chat(cloneMessagesForProvider(currentMessages), chatOptions);
|
|
8377
10678
|
}
|
|
8378
10679
|
} catch (err) {
|
|
10680
|
+
if (richRequest) this.recordRichFailure(err);
|
|
8379
10681
|
this.emitEvent({
|
|
8380
10682
|
type: "agent_call_end",
|
|
8381
10683
|
agent: agent2._name,
|
|
8382
|
-
model:
|
|
10684
|
+
model: effectiveModelUri,
|
|
8383
10685
|
promptVersion: agent2._config.version,
|
|
8384
10686
|
duration: Date.now() - turnStart,
|
|
8385
10687
|
data: {
|
|
8386
10688
|
response: "",
|
|
8387
10689
|
turn: turns,
|
|
8388
10690
|
...retryReason ? { retryReason } : {},
|
|
8389
|
-
error: err
|
|
10691
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err),
|
|
8390
10692
|
// Enrich with typed-error metadata when available. `body` is
|
|
8391
10693
|
// deliberately NOT emitted — it's redaction-eligible (see
|
|
8392
10694
|
// docs/security.md). `data.error` already carries the message.
|
|
8393
|
-
...err
|
|
10695
|
+
...providerErrorEventMetadata(err, richRequest)
|
|
8394
10696
|
}
|
|
8395
10697
|
});
|
|
8396
10698
|
throw err;
|
|
@@ -8404,7 +10706,7 @@ ${appendedSchemaText}`;
|
|
|
8404
10706
|
this.emitEvent({
|
|
8405
10707
|
type: "agent_call_end",
|
|
8406
10708
|
agent: agent2._name,
|
|
8407
|
-
model:
|
|
10709
|
+
model: effectiveModelUri,
|
|
8408
10710
|
promptVersion: agent2._config.version,
|
|
8409
10711
|
cost: response.cost,
|
|
8410
10712
|
tokens: response.usage ? {
|
|
@@ -8438,12 +10740,12 @@ ${appendedSchemaText}`;
|
|
|
8438
10740
|
if (descriptor) {
|
|
8439
10741
|
const mode = descriptor.mode ?? "oneway";
|
|
8440
10742
|
let handoffPrompt = prompt;
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
} catch {
|
|
10743
|
+
try {
|
|
10744
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
10745
|
+
if (typeof args.message === "string" && args.message.length > 0) {
|
|
10746
|
+
handoffPrompt = args.message;
|
|
8446
10747
|
}
|
|
10748
|
+
} catch {
|
|
8447
10749
|
}
|
|
8448
10750
|
const handoffStart = Date.now();
|
|
8449
10751
|
const sourceFrame = askStorage.getStore();
|
|
@@ -8470,15 +10772,27 @@ ${appendedSchemaText}`;
|
|
|
8470
10772
|
validateRetries: options.validateRetries
|
|
8471
10773
|
} : void 0;
|
|
8472
10774
|
const handoffFn = () => askStorage.run(targetFrame, async () => {
|
|
8473
|
-
|
|
10775
|
+
const delegateOriginal = agent2._delegateOriginalInput;
|
|
10776
|
+
const handoffInput = delegateOriginal ?? (typeof input === "string" ? handoffPrompt : input);
|
|
10777
|
+
const enrichedHandoffInput = appendHandoffInstruction(
|
|
10778
|
+
handoffInput,
|
|
10779
|
+
inputText(input),
|
|
10780
|
+
handoffPrompt
|
|
10781
|
+
);
|
|
10782
|
+
this.emitEvent({
|
|
10783
|
+
type: "ask_start",
|
|
10784
|
+
prompt: inputText(enrichedHandoffInput),
|
|
10785
|
+
...typeof enrichedHandoffInput === "string" ? {} : { input: describeModelInput(enrichedHandoffInput) }
|
|
10786
|
+
});
|
|
8474
10787
|
const targetAskStart = Date.now();
|
|
8475
10788
|
try {
|
|
8476
10789
|
const result = await this.executeAgentCall(
|
|
8477
10790
|
descriptor.agent,
|
|
8478
|
-
|
|
10791
|
+
enrichedHandoffInput,
|
|
8479
10792
|
handoffOptions,
|
|
8480
10793
|
currentMessages,
|
|
8481
|
-
usageCapture
|
|
10794
|
+
usageCapture,
|
|
10795
|
+
sessionHistory
|
|
8482
10796
|
);
|
|
8483
10797
|
this.emitEvent({
|
|
8484
10798
|
type: "ask_end",
|
|
@@ -8489,11 +10803,13 @@ ${appendedSchemaText}`;
|
|
|
8489
10803
|
});
|
|
8490
10804
|
return result;
|
|
8491
10805
|
} catch (err) {
|
|
10806
|
+
const richHandoff = typeof enrichedHandoffInput !== "string" || hasRichMessage(sessionHistory);
|
|
10807
|
+
if (richHandoff) this.recordRichFailure(err);
|
|
8492
10808
|
this.emitEvent({
|
|
8493
10809
|
type: "ask_end",
|
|
8494
10810
|
outcome: {
|
|
8495
10811
|
ok: false,
|
|
8496
|
-
error: err
|
|
10812
|
+
error: this._observerErrorProjection(err)?.message ?? legacyErrorMessage(err)
|
|
8497
10813
|
},
|
|
8498
10814
|
cost: targetFrame.askCost.value,
|
|
8499
10815
|
...targetFrame.askUnpriced ? { unpriced: true } : {},
|
|
@@ -9002,12 +11318,12 @@ ${appendedSchemaText}`;
|
|
|
9002
11318
|
content: `Summary of earlier conversation:
|
|
9003
11319
|
${this.summaryCache}`
|
|
9004
11320
|
};
|
|
9005
|
-
const summaryTokens = estimateTokens(summaryMsg2.content) + 4;
|
|
11321
|
+
const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg2.content)) + 4;
|
|
9006
11322
|
const remaining = availableTokens - summaryTokens;
|
|
9007
11323
|
let recentTokens2 = 0;
|
|
9008
11324
|
let splitIdx2 = history.length;
|
|
9009
11325
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
9010
|
-
const msgTokens = estimateTokens(history[i].content) + 4;
|
|
11326
|
+
const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
|
|
9011
11327
|
if (recentTokens2 + msgTokens > remaining) break;
|
|
9012
11328
|
recentTokens2 += msgTokens;
|
|
9013
11329
|
splitIdx2 = i;
|
|
@@ -9020,7 +11336,7 @@ ${this.summaryCache}`
|
|
|
9020
11336
|
let splitIdx = history.length;
|
|
9021
11337
|
const targetRecent = Math.floor(availableTokens * 0.6);
|
|
9022
11338
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
9023
|
-
const msgTokens = estimateTokens(history[i].content) + 4;
|
|
11339
|
+
const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
|
|
9024
11340
|
if (recentTokens + msgTokens > targetRecent) break;
|
|
9025
11341
|
recentTokens += msgTokens;
|
|
9026
11342
|
splitIdx = i;
|
|
@@ -9038,7 +11354,7 @@ ${this.summaryCache}`
|
|
|
9038
11354
|
summaryProvider = provider;
|
|
9039
11355
|
summaryModel = model;
|
|
9040
11356
|
}
|
|
9041
|
-
const oldContent = oldMessages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
11357
|
+
const oldContent = oldMessages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n");
|
|
9042
11358
|
const summaryResponse = await summaryProvider.chat(
|
|
9043
11359
|
[
|
|
9044
11360
|
{
|
|
@@ -10272,7 +12588,9 @@ ${summaryResponse.content}`
|
|
|
10272
12588
|
reason: "routed"
|
|
10273
12589
|
}
|
|
10274
12590
|
});
|
|
10275
|
-
|
|
12591
|
+
const routerInput = options?.routerInput === "text" ? inputText(prompt) : prompt;
|
|
12592
|
+
routerAgent._delegateOriginalInput = prompt;
|
|
12593
|
+
return this.ask(routerAgent, routerInput, {
|
|
10276
12594
|
schema: options?.schema,
|
|
10277
12595
|
schemaPrompt: options?.schemaPrompt,
|
|
10278
12596
|
nativeStructuredOutput: options?.nativeStructuredOutput,
|
|
@@ -10282,6 +12600,278 @@ ${summaryResponse.content}`
|
|
|
10282
12600
|
validateRetries: options?.validateRetries
|
|
10283
12601
|
});
|
|
10284
12602
|
}
|
|
12603
|
+
/** Transcribe recorded audio through the dedicated transcription registry.
|
|
12604
|
+
* This operation never enters the chat history or chat-provider path. */
|
|
12605
|
+
async transcribe(request) {
|
|
12606
|
+
const startedAt = Date.now();
|
|
12607
|
+
const transcriptionId = (0, import_node_crypto.randomUUID)();
|
|
12608
|
+
const signal = this.currentSignal;
|
|
12609
|
+
const rawAudio = request && typeof request === "object" && "audio" in request ? request.audio : void 0;
|
|
12610
|
+
const source = rawAudio?.type;
|
|
12611
|
+
const requestedUri = typeof request?.model === "string" ? request.model : void 0;
|
|
12612
|
+
const colon = requestedUri?.indexOf(":") ?? -1;
|
|
12613
|
+
const requestedProvider = colon > 0 ? requestedUri.slice(0, colon) : void 0;
|
|
12614
|
+
const requestedModel = colon > 0 && colon < requestedUri.length - 1 ? requestedUri.slice(colon + 1) : void 0;
|
|
12615
|
+
const startAudio = {
|
|
12616
|
+
source: typeof source === "string" ? source : "unknown",
|
|
12617
|
+
...rawAudio?.data instanceof Uint8Array ? { bytes: rawAudio.data.byteLength } : {},
|
|
12618
|
+
...typeof rawAudio?.mediaType === "string" ? { mediaType: rawAudio.mediaType } : {}
|
|
12619
|
+
};
|
|
12620
|
+
this.emitEvent({
|
|
12621
|
+
type: "transcription_start",
|
|
12622
|
+
transcriptionId,
|
|
12623
|
+
model: requestedModel,
|
|
12624
|
+
data: {
|
|
12625
|
+
...requestedProvider ? { provider: requestedProvider } : {},
|
|
12626
|
+
...requestedModel ? { model: requestedModel } : {},
|
|
12627
|
+
audio: startAudio
|
|
12628
|
+
}
|
|
12629
|
+
});
|
|
12630
|
+
let terminalEmitted = false;
|
|
12631
|
+
const emitTerminal = (partial) => {
|
|
12632
|
+
if (terminalEmitted) return;
|
|
12633
|
+
terminalEmitted = true;
|
|
12634
|
+
this.emitEvent({
|
|
12635
|
+
type: "transcription_end",
|
|
12636
|
+
transcriptionId,
|
|
12637
|
+
model: partial.model,
|
|
12638
|
+
duration: Date.now() - startedAt,
|
|
12639
|
+
...partial.cost !== void 0 ? { cost: partial.cost } : {},
|
|
12640
|
+
...partial.tokens ? { tokens: partial.tokens } : {},
|
|
12641
|
+
data: partial.data
|
|
12642
|
+
});
|
|
12643
|
+
};
|
|
12644
|
+
const abortError3 = () => new DOMException("The transcription operation was aborted", "AbortError");
|
|
12645
|
+
const cleanupStatus = (value) => value === "not_required" || value === "deleted" || value === "failed" || value === "timed_out" ? value : void 0;
|
|
12646
|
+
const safeProviderFailure = (error, provider, model) => {
|
|
12647
|
+
const details = error && typeof error === "object" ? error : void 0;
|
|
12648
|
+
const accounting = normalizeTranscriptionAccounting(details?.usage, details?.pricingStatus);
|
|
12649
|
+
const cleanup = cleanupStatus(details?.cleanupStatus);
|
|
12650
|
+
const providerError = error instanceof ProviderError ? error : void 0;
|
|
12651
|
+
const diagnostics = providerError ? {
|
|
12652
|
+
status: providerError.status,
|
|
12653
|
+
retryable: providerError.retryable,
|
|
12654
|
+
...providerError.retryAfterMs !== void 0 ? { retryAfterMs: providerError.retryAfterMs } : {},
|
|
12655
|
+
...providerError.requestId ? { requestId: providerError.requestId } : {}
|
|
12656
|
+
} : void 0;
|
|
12657
|
+
return {
|
|
12658
|
+
accounting,
|
|
12659
|
+
cleanup,
|
|
12660
|
+
diagnostics,
|
|
12661
|
+
error: new TranscriptionOperationError({
|
|
12662
|
+
provider,
|
|
12663
|
+
model,
|
|
12664
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
12665
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
12666
|
+
...cleanup ? { cleanupStatus: cleanup } : {},
|
|
12667
|
+
...diagnostics ?? {},
|
|
12668
|
+
cause: error
|
|
12669
|
+
})
|
|
12670
|
+
};
|
|
12671
|
+
};
|
|
12672
|
+
const terminalData = (status, provider, model, audio, accounting, extras = {}) => ({
|
|
12673
|
+
status,
|
|
12674
|
+
...provider ? { provider } : {},
|
|
12675
|
+
...model ? { model } : {},
|
|
12676
|
+
...audio ? { audio } : {},
|
|
12677
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
12678
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
12679
|
+
...extras
|
|
12680
|
+
});
|
|
12681
|
+
try {
|
|
12682
|
+
const normalized = normalizeTranscriptionRequest(request);
|
|
12683
|
+
const descriptor = {
|
|
12684
|
+
source: normalized.audio.type,
|
|
12685
|
+
...normalized.audio.type === "bytes" ? { bytes: normalized.audio.data.byteLength } : {},
|
|
12686
|
+
...normalized.audio.mediaType !== void 0 ? { mediaType: normalized.audio.mediaType } : {}
|
|
12687
|
+
};
|
|
12688
|
+
if (this.currentSignal?.aborted || signal?.aborted) {
|
|
12689
|
+
emitTerminal({
|
|
12690
|
+
model: requestedModel,
|
|
12691
|
+
data: terminalData("aborted", requestedProvider, requestedModel, descriptor, {
|
|
12692
|
+
hasWork: false
|
|
12693
|
+
})
|
|
12694
|
+
});
|
|
12695
|
+
throw abortError3();
|
|
12696
|
+
}
|
|
12697
|
+
if (this.budgetContext?.exceeded) {
|
|
12698
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
12699
|
+
if (policy === "warn") {
|
|
12700
|
+
this.emitEvent({
|
|
12701
|
+
type: "log",
|
|
12702
|
+
data: { warning: "Budget exceeded", limit, spent, policy }
|
|
12703
|
+
});
|
|
12704
|
+
} else {
|
|
12705
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
12706
|
+
}
|
|
12707
|
+
}
|
|
12708
|
+
if (!this.transcriptionProviderRegistry) {
|
|
12709
|
+
throw new InvalidTranscriptionInputError(
|
|
12710
|
+
"No transcription provider registry is configured"
|
|
12711
|
+
);
|
|
12712
|
+
}
|
|
12713
|
+
const resolved = this.transcriptionProviderRegistry.resolve(normalized.model, this.config);
|
|
12714
|
+
if (normalized.audio.type === "provider-file" && normalized.audio.provider !== resolved.providerName) {
|
|
12715
|
+
throw new InvalidTranscriptionInputError(
|
|
12716
|
+
"Transcription provider-file source does not match the requested provider"
|
|
12717
|
+
);
|
|
12718
|
+
}
|
|
12719
|
+
const capabilities = resolved.provider.capabilities(resolved.model);
|
|
12720
|
+
if (!capabilities || !capabilities.sources.includes(normalized.audio.type)) {
|
|
12721
|
+
throw new InvalidTranscriptionInputError(
|
|
12722
|
+
"The requested transcription model does not support this audio source"
|
|
12723
|
+
);
|
|
12724
|
+
}
|
|
12725
|
+
if (normalized.timestamps && !capabilities.timestamps?.includes(normalized.timestamps)) {
|
|
12726
|
+
throw new InvalidTranscriptionInputError(
|
|
12727
|
+
"The requested transcription model does not support requested timestamps"
|
|
12728
|
+
);
|
|
12729
|
+
}
|
|
12730
|
+
if (normalized.diarization && !capabilities.diarization) {
|
|
12731
|
+
throw new InvalidTranscriptionInputError(
|
|
12732
|
+
"The requested transcription model does not support diarization"
|
|
12733
|
+
);
|
|
12734
|
+
}
|
|
12735
|
+
const providerRequest = {
|
|
12736
|
+
...normalized,
|
|
12737
|
+
model: resolved.model,
|
|
12738
|
+
...signal ? { signal } : {}
|
|
12739
|
+
};
|
|
12740
|
+
let result;
|
|
12741
|
+
try {
|
|
12742
|
+
result = await resolved.provider.transcribe(providerRequest);
|
|
12743
|
+
} catch (error) {
|
|
12744
|
+
const failure = safeProviderFailure(error, resolved.providerName, resolved.model);
|
|
12745
|
+
if (this.currentSignal?.aborted || signal?.aborted) {
|
|
12746
|
+
if (failure.accounting.cost !== void 0)
|
|
12747
|
+
this._accumulateBudgetCost(failure.accounting.cost);
|
|
12748
|
+
emitTerminal({
|
|
12749
|
+
model: resolved.model,
|
|
12750
|
+
...failure.accounting.cost !== void 0 ? { cost: failure.accounting.cost } : {},
|
|
12751
|
+
...failure.accounting.tokens ? { tokens: failure.accounting.tokens } : {},
|
|
12752
|
+
data: terminalData(
|
|
12753
|
+
"aborted",
|
|
12754
|
+
resolved.providerName,
|
|
12755
|
+
resolved.model,
|
|
12756
|
+
descriptor,
|
|
12757
|
+
failure.accounting,
|
|
12758
|
+
...failure.cleanup ? [{ cleanupStatus: failure.cleanup }] : []
|
|
12759
|
+
)
|
|
12760
|
+
});
|
|
12761
|
+
throw abortError3();
|
|
12762
|
+
}
|
|
12763
|
+
if (failure.accounting.cost !== void 0)
|
|
12764
|
+
this._accumulateBudgetCost(failure.accounting.cost);
|
|
12765
|
+
emitTerminal({
|
|
12766
|
+
model: resolved.model,
|
|
12767
|
+
...failure.accounting.cost !== void 0 ? { cost: failure.accounting.cost } : {},
|
|
12768
|
+
...failure.accounting.tokens ? { tokens: failure.accounting.tokens } : {},
|
|
12769
|
+
data: terminalData(
|
|
12770
|
+
"failed",
|
|
12771
|
+
resolved.providerName,
|
|
12772
|
+
resolved.model,
|
|
12773
|
+
descriptor,
|
|
12774
|
+
failure.accounting,
|
|
12775
|
+
{
|
|
12776
|
+
error: "Transcription operation failed",
|
|
12777
|
+
errorCode: failure.error.code,
|
|
12778
|
+
...failure.diagnostics ? { providerError: failure.diagnostics } : {},
|
|
12779
|
+
...failure.cleanup ? { cleanupStatus: failure.cleanup } : {}
|
|
12780
|
+
}
|
|
12781
|
+
)
|
|
12782
|
+
});
|
|
12783
|
+
throw failure.error;
|
|
12784
|
+
}
|
|
12785
|
+
const accounting = normalizeTranscriptionAccounting(
|
|
12786
|
+
result.transcript?.usage,
|
|
12787
|
+
result.transcript?.pricingStatus
|
|
12788
|
+
);
|
|
12789
|
+
if (accounting.cost !== void 0) this._accumulateBudgetCost(accounting.cost);
|
|
12790
|
+
if (this.currentSignal?.aborted || signal?.aborted) {
|
|
12791
|
+
emitTerminal({
|
|
12792
|
+
model: resolved.model,
|
|
12793
|
+
...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
|
|
12794
|
+
...accounting.tokens ? { tokens: accounting.tokens } : {},
|
|
12795
|
+
data: terminalData(
|
|
12796
|
+
"aborted",
|
|
12797
|
+
resolved.providerName,
|
|
12798
|
+
resolved.model,
|
|
12799
|
+
descriptor,
|
|
12800
|
+
accounting,
|
|
12801
|
+
...cleanupStatus(result.cleanupStatus) ? [{ cleanupStatus: cleanupStatus(result.cleanupStatus) }] : []
|
|
12802
|
+
)
|
|
12803
|
+
});
|
|
12804
|
+
throw abortError3();
|
|
12805
|
+
}
|
|
12806
|
+
let transcript;
|
|
12807
|
+
try {
|
|
12808
|
+
transcript = cloneTranscript(result.transcript);
|
|
12809
|
+
} catch (error) {
|
|
12810
|
+
const cleanup = cleanupStatus(result.cleanupStatus);
|
|
12811
|
+
const failure = new TranscriptionOperationError({
|
|
12812
|
+
provider: resolved.providerName,
|
|
12813
|
+
model: resolved.model,
|
|
12814
|
+
...accounting.usage ? { usage: accounting.usage } : {},
|
|
12815
|
+
...accounting.pricingStatus ? { pricingStatus: accounting.pricingStatus } : {},
|
|
12816
|
+
...cleanup ? { cleanupStatus: cleanup } : {},
|
|
12817
|
+
cause: error
|
|
12818
|
+
});
|
|
12819
|
+
emitTerminal({
|
|
12820
|
+
model: resolved.model,
|
|
12821
|
+
...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
|
|
12822
|
+
...accounting.tokens ? { tokens: accounting.tokens } : {},
|
|
12823
|
+
data: terminalData(
|
|
12824
|
+
"failed",
|
|
12825
|
+
resolved.providerName,
|
|
12826
|
+
resolved.model,
|
|
12827
|
+
descriptor,
|
|
12828
|
+
accounting,
|
|
12829
|
+
{
|
|
12830
|
+
error: "Transcription operation failed",
|
|
12831
|
+
errorCode: "TRANSCRIPTION_PROVIDER_ERROR",
|
|
12832
|
+
...cleanup ? { cleanupStatus: cleanup } : {}
|
|
12833
|
+
}
|
|
12834
|
+
)
|
|
12835
|
+
});
|
|
12836
|
+
throw failure;
|
|
12837
|
+
}
|
|
12838
|
+
emitTerminal({
|
|
12839
|
+
model: resolved.model,
|
|
12840
|
+
...accounting.cost !== void 0 ? { cost: accounting.cost } : {},
|
|
12841
|
+
...accounting.tokens ? { tokens: accounting.tokens } : {},
|
|
12842
|
+
data: terminalData(
|
|
12843
|
+
"completed",
|
|
12844
|
+
resolved.providerName,
|
|
12845
|
+
resolved.model,
|
|
12846
|
+
descriptor,
|
|
12847
|
+
accounting,
|
|
12848
|
+
{
|
|
12849
|
+
text: transcript.text,
|
|
12850
|
+
...cleanupStatus(result.cleanupStatus) ? { cleanupStatus: cleanupStatus(result.cleanupStatus) } : {}
|
|
12851
|
+
}
|
|
12852
|
+
)
|
|
12853
|
+
});
|
|
12854
|
+
return transcript;
|
|
12855
|
+
} catch (error) {
|
|
12856
|
+
if (!terminalEmitted) {
|
|
12857
|
+
const aborted = signal?.aborted || error instanceof Error && error.name === "AbortError";
|
|
12858
|
+
emitTerminal({
|
|
12859
|
+
model: requestedModel,
|
|
12860
|
+
data: terminalData(
|
|
12861
|
+
aborted ? "aborted" : "failed",
|
|
12862
|
+
requestedProvider,
|
|
12863
|
+
requestedModel,
|
|
12864
|
+
void 0,
|
|
12865
|
+
{ hasWork: false },
|
|
12866
|
+
{
|
|
12867
|
+
error: aborted ? "Transcription operation aborted" : "Transcription operation failed"
|
|
12868
|
+
}
|
|
12869
|
+
)
|
|
12870
|
+
});
|
|
12871
|
+
}
|
|
12872
|
+
throw error;
|
|
12873
|
+
}
|
|
12874
|
+
}
|
|
10285
12875
|
// ── Private ───────────────────────────────────────────────────────────
|
|
10286
12876
|
/**
|
|
10287
12877
|
* Internal emitter input — intentionally loose so call sites don't need to
|
|
@@ -10297,7 +12887,7 @@ ${summaryResponse.content}`
|
|
|
10297
12887
|
const cost = partial.cost;
|
|
10298
12888
|
if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
10299
12889
|
const usable = isUsableCost(cost);
|
|
10300
|
-
const unpriced = !usable &&
|
|
12890
|
+
const unpriced = !usable && hasPositiveBillableWork(
|
|
10301
12891
|
partial
|
|
10302
12892
|
);
|
|
10303
12893
|
if (frame) {
|
|
@@ -10402,11 +12992,13 @@ function agent(config) {
|
|
|
10402
12992
|
_name: defaultName,
|
|
10403
12993
|
async ask(prompt, options) {
|
|
10404
12994
|
const registry = new ProviderRegistry();
|
|
12995
|
+
const transcriptionRegistry = new TranscriptionProviderRegistry();
|
|
10405
12996
|
const ctx = new WorkflowContext({
|
|
10406
12997
|
input: prompt,
|
|
10407
12998
|
executionId: (0, import_node_crypto2.randomUUID)(),
|
|
10408
12999
|
config: {},
|
|
10409
|
-
providerRegistry: registry
|
|
13000
|
+
providerRegistry: registry,
|
|
13001
|
+
transcriptionProviderRegistry: transcriptionRegistry
|
|
10410
13002
|
});
|
|
10411
13003
|
return ctx.ask(agentInstance, prompt, options);
|
|
10412
13004
|
},
|
|
@@ -10466,7 +13058,7 @@ var MemoryStore = class {
|
|
|
10466
13058
|
return data !== void 0 ? structuredClone(data) : null;
|
|
10467
13059
|
}
|
|
10468
13060
|
async saveSession(sessionId, history) {
|
|
10469
|
-
this.sessions.set(sessionId, structuredClone(history));
|
|
13061
|
+
this.sessions.set(sessionId, structuredClone(normalizePersistedSessionHistory(history)));
|
|
10470
13062
|
}
|
|
10471
13063
|
async getSession(sessionId) {
|
|
10472
13064
|
const history = this.sessions.get(sessionId);
|
|
@@ -10890,7 +13482,7 @@ var SQLiteStore = class {
|
|
|
10890
13482
|
const stmt = this.db.prepare(
|
|
10891
13483
|
"INSERT OR REPLACE INTO sessions (session_id, history) VALUES (?, ?)"
|
|
10892
13484
|
);
|
|
10893
|
-
stmt.run(sessionId, JSON.stringify(history));
|
|
13485
|
+
stmt.run(sessionId, JSON.stringify(normalizePersistedSessionHistory(history)));
|
|
10894
13486
|
}
|
|
10895
13487
|
async getSession(sessionId) {
|
|
10896
13488
|
const stmt = this.db.prepare("SELECT history FROM sessions WHERE session_id = ?");
|
|
@@ -11458,7 +14050,7 @@ var AxlStream = class extends import_node_stream.Readable {
|
|
|
11458
14050
|
/** Signal an error. `executionId` is required for the same reason as
|
|
11459
14051
|
* `_done`: terminal events must carry a real id even when the
|
|
11460
14052
|
* failure happens before any real trace event fires (review S4). */
|
|
11461
|
-
_error(error, executionId) {
|
|
14053
|
+
_error(error, executionId, observerMessage = error.message, observerName = error.name) {
|
|
11462
14054
|
if (this.events.isFinished) return;
|
|
11463
14055
|
const errorEvent = {
|
|
11464
14056
|
schemaVersion: 2,
|
|
@@ -11466,7 +14058,7 @@ var AxlStream = class extends import_node_stream.Readable {
|
|
|
11466
14058
|
executionId,
|
|
11467
14059
|
step: Number.MAX_SAFE_INTEGER,
|
|
11468
14060
|
timestamp: Date.now(),
|
|
11469
|
-
data: { message:
|
|
14061
|
+
data: { message: observerMessage, name: observerName }
|
|
11470
14062
|
};
|
|
11471
14063
|
this.events._push(errorEvent);
|
|
11472
14064
|
this.push(errorEvent);
|
|
@@ -12193,6 +14785,10 @@ var StreamingFlusher = class {
|
|
|
12193
14785
|
function isAbortError4(err) {
|
|
12194
14786
|
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
12195
14787
|
}
|
|
14788
|
+
function executionErrorProjection(ctx, error) {
|
|
14789
|
+
const safe = ctx?._observerErrorProjection(error);
|
|
14790
|
+
return safe ?? { message: error instanceof Error ? error.message : String(error) };
|
|
14791
|
+
}
|
|
12196
14792
|
function pushEventBounded(execInfo, event, cap) {
|
|
12197
14793
|
if (STREAMING_EXCLUDED_TYPES.has(event.type)) return;
|
|
12198
14794
|
if (cap === Infinity || execInfo.events.length < cap) {
|
|
@@ -12287,6 +14883,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12287
14883
|
tools = /* @__PURE__ */ new Map();
|
|
12288
14884
|
agents = /* @__PURE__ */ new Map();
|
|
12289
14885
|
providerRegistry;
|
|
14886
|
+
transcriptionProviderRegistry;
|
|
12290
14887
|
stateStore;
|
|
12291
14888
|
executions = /* @__PURE__ */ new Map();
|
|
12292
14889
|
pendingDecisionResolvers = /* @__PURE__ */ new Map();
|
|
@@ -12342,6 +14939,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12342
14939
|
super();
|
|
12343
14940
|
this.config = resolveConfig(config ?? {});
|
|
12344
14941
|
this.providerRegistry = new ProviderRegistry();
|
|
14942
|
+
this.transcriptionProviderRegistry = new TranscriptionProviderRegistry();
|
|
12345
14943
|
this.stateStore = this.createStateStore();
|
|
12346
14944
|
const requested = this.config.state?.maxEventsPerExecution;
|
|
12347
14945
|
if (requested === void 0) {
|
|
@@ -12490,7 +15088,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12490
15088
|
execInfo.status = "failed";
|
|
12491
15089
|
execInfo.completedAt = Date.now();
|
|
12492
15090
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
12493
|
-
execInfo.error =
|
|
15091
|
+
execInfo.error = executionErrorProjection(ctx, terminalError).message;
|
|
12494
15092
|
execInfo.observation = observation;
|
|
12495
15093
|
ctx._emitWorkflowEnd({
|
|
12496
15094
|
status: "failed",
|
|
@@ -12745,6 +15343,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12745
15343
|
metadata: options?.metadata,
|
|
12746
15344
|
config: this.config,
|
|
12747
15345
|
providerRegistry: this.providerRegistry,
|
|
15346
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12748
15347
|
stateStore: this.stateStore,
|
|
12749
15348
|
mcpManager: this.mcpManager,
|
|
12750
15349
|
spanManager: this.spanManager,
|
|
@@ -12773,6 +15372,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12773
15372
|
registerProvider(name, provider) {
|
|
12774
15373
|
this.providerRegistry.registerInstance(name, provider);
|
|
12775
15374
|
}
|
|
15375
|
+
/** Register a dedicated transcription adapter. It is never used for chat. */
|
|
15376
|
+
registerTranscriptionProvider(name, provider) {
|
|
15377
|
+
this.transcriptionProviderRegistry.registerInstance(name, provider);
|
|
15378
|
+
}
|
|
12776
15379
|
/** Resolve a provider:model URI to a Provider instance and model name. */
|
|
12777
15380
|
resolveProvider(uri) {
|
|
12778
15381
|
return this.providerRegistry.resolve(uri, this.config);
|
|
@@ -12816,6 +15419,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12816
15419
|
metadata: options?.metadata,
|
|
12817
15420
|
config: this.config,
|
|
12818
15421
|
providerRegistry: this.providerRegistry,
|
|
15422
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12819
15423
|
sessionHistory,
|
|
12820
15424
|
signal: controller.signal,
|
|
12821
15425
|
eventStreamOptions: options?.events,
|
|
@@ -12903,6 +15507,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12903
15507
|
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
12904
15508
|
this.abortControllers.set(executionId, controller);
|
|
12905
15509
|
let execInfo;
|
|
15510
|
+
let wfCtx;
|
|
12906
15511
|
const run = async () => {
|
|
12907
15512
|
const workflow2 = this.workflows.get(name);
|
|
12908
15513
|
if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
|
|
@@ -12926,12 +15531,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12926
15531
|
metadata: liftPersistedMetadata(options?.metadata)
|
|
12927
15532
|
};
|
|
12928
15533
|
this.executions.set(executionId, execInfo);
|
|
12929
|
-
const
|
|
15534
|
+
const context = new WorkflowContext({
|
|
12930
15535
|
input: validated,
|
|
12931
15536
|
executionId,
|
|
12932
15537
|
metadata: options?.metadata,
|
|
12933
15538
|
config: this.config,
|
|
12934
15539
|
providerRegistry: this.providerRegistry,
|
|
15540
|
+
transcriptionProviderRegistry: this.transcriptionProviderRegistry,
|
|
12935
15541
|
sessionHistory,
|
|
12936
15542
|
signal: controller.signal,
|
|
12937
15543
|
eventStreamOptions: options?.events,
|
|
@@ -12996,6 +15602,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
12996
15602
|
unpricedWarned: false
|
|
12997
15603
|
}
|
|
12998
15604
|
});
|
|
15605
|
+
wfCtx = context;
|
|
12999
15606
|
return this.spanManager.withSpanAsync(
|
|
13000
15607
|
"axl.workflow.execute",
|
|
13001
15608
|
{
|
|
@@ -13005,7 +15612,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
13005
15612
|
},
|
|
13006
15613
|
(span) => this.runWorkflowBody({
|
|
13007
15614
|
workflow: workflow2,
|
|
13008
|
-
ctx:
|
|
15615
|
+
ctx: context,
|
|
13009
15616
|
execInfo,
|
|
13010
15617
|
validated,
|
|
13011
15618
|
span,
|
|
@@ -13018,12 +15625,14 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
13018
15625
|
execInfo.status = "failed";
|
|
13019
15626
|
execInfo.completedAt = Date.now();
|
|
13020
15627
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
13021
|
-
execInfo.error =
|
|
15628
|
+
execInfo.error = executionErrorProjection(wfCtx, err).message;
|
|
13022
15629
|
this.persistExecution(execInfo);
|
|
13023
15630
|
}
|
|
13024
15631
|
this.abortControllers.delete(executionId);
|
|
13025
15632
|
cleanupAbortForwarder();
|
|
13026
|
-
|
|
15633
|
+
const originalError = err instanceof Error ? err : new Error(String(err));
|
|
15634
|
+
const projection = executionErrorProjection(wfCtx, err);
|
|
15635
|
+
axlStream._error(originalError, executionId, projection.message, projection.name);
|
|
13027
15636
|
});
|
|
13028
15637
|
return axlStream;
|
|
13029
15638
|
}
|
|
@@ -13434,7 +16043,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
13434
16043
|
},
|
|
13435
16044
|
{
|
|
13436
16045
|
role: "user",
|
|
13437
|
-
content: messages.map((m) => `${m.role}: ${m.content}`).join("\n")
|
|
16046
|
+
content: messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n")
|
|
13438
16047
|
}
|
|
13439
16048
|
],
|
|
13440
16049
|
{ model, maxTokens: 1024 }
|
|
@@ -13881,7 +16490,7 @@ async function registerConditions(conditions) {
|
|
|
13881
16490
|
].join("\n");
|
|
13882
16491
|
nodeModule.register(`data:text/javascript,${encodeURIComponent(hookCode)}`);
|
|
13883
16492
|
} catch {
|
|
13884
|
-
console.warn("Warning:
|
|
16493
|
+
console.warn("Warning: this Node.js runtime does not support --conditions");
|
|
13885
16494
|
}
|
|
13886
16495
|
}
|
|
13887
16496
|
|
|
@@ -14190,7 +16799,11 @@ var RedisStore = class _RedisStore {
|
|
|
14190
16799
|
async saveSession(sessionId, history) {
|
|
14191
16800
|
const ttl = this.ttlFor("session");
|
|
14192
16801
|
const setOptions = ttl !== void 0 ? { EX: ttl } : void 0;
|
|
14193
|
-
await this.client.multi().set(
|
|
16802
|
+
await this.client.multi().set(
|
|
16803
|
+
this.sessionKey(sessionId),
|
|
16804
|
+
JSON.stringify(normalizePersistedSessionHistory(history)),
|
|
16805
|
+
setOptions
|
|
16806
|
+
).sAdd(this.sessionIdsKey(), sessionId).exec();
|
|
14194
16807
|
}
|
|
14195
16808
|
async getSession(sessionId) {
|
|
14196
16809
|
const raw = await this.client.get(this.sessionKey(sessionId));
|
|
@@ -14679,10 +17292,15 @@ function cosineSimilarity2(a, b) {
|
|
|
14679
17292
|
EventStreamOverflowError,
|
|
14680
17293
|
GROQ_PROFILE,
|
|
14681
17294
|
GeminiProvider,
|
|
17295
|
+
GeminiTranscriptionProvider,
|
|
14682
17296
|
GuardrailError,
|
|
14683
17297
|
InMemoryVectorStore,
|
|
17298
|
+
InvalidModelInputError,
|
|
17299
|
+
InvalidTranscriptionInputError,
|
|
14684
17300
|
LLAMACPP_PROFILE,
|
|
14685
17301
|
LMSTUDIO_PROFILE,
|
|
17302
|
+
MAX_INLINE_MODEL_INPUT_BYTES,
|
|
17303
|
+
MAX_INLINE_TRANSCRIPTION_BYTES,
|
|
14686
17304
|
MISTRAL_PROFILE,
|
|
14687
17305
|
MaxTurnsError,
|
|
14688
17306
|
McpManager,
|
|
@@ -14698,6 +17316,8 @@ function cosineSimilarity2(a, b) {
|
|
|
14698
17316
|
OpenAIEmbedder,
|
|
14699
17317
|
OpenAIProvider,
|
|
14700
17318
|
OpenAIResponsesProvider,
|
|
17319
|
+
OpenAITranscriptionProvider,
|
|
17320
|
+
OpenRouterTranscriptionProvider,
|
|
14701
17321
|
ProviderError,
|
|
14702
17322
|
ProviderRegistry,
|
|
14703
17323
|
QuorumNotMet,
|
|
@@ -14713,7 +17333,11 @@ function cosineSimilarity2(a, b) {
|
|
|
14713
17333
|
TimeoutError,
|
|
14714
17334
|
ToolFailure,
|
|
14715
17335
|
ToolModelOutputError,
|
|
17336
|
+
TranscriptionOperationError,
|
|
17337
|
+
TranscriptionProviderRegistry,
|
|
14716
17338
|
UnsupportedEventSchemaVersionError,
|
|
17339
|
+
UnsupportedModelInputError,
|
|
17340
|
+
UnsupportedTranscriptionInputError,
|
|
14717
17341
|
VLLM_PROFILE,
|
|
14718
17342
|
ValidationError,
|
|
14719
17343
|
VerifyError,
|
|
@@ -14730,8 +17354,10 @@ function cosineSimilarity2(a, b) {
|
|
|
14730
17354
|
findConfig,
|
|
14731
17355
|
getEventSchemaVersion,
|
|
14732
17356
|
getExecutionEventSchemaVersion,
|
|
17357
|
+
hasPositiveBillableWork,
|
|
14733
17358
|
hasPositiveTokens,
|
|
14734
17359
|
importModule,
|
|
17360
|
+
inputText,
|
|
14735
17361
|
isCostBearingLeaf,
|
|
14736
17362
|
isRetryableStatus,
|
|
14737
17363
|
isRootLevel,
|