@yourgpt/llm-sdk 2.6.0-beta.3 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +46 -10
- package/dist/adapters/index.d.mts +11 -13
- package/dist/adapters/index.d.ts +11 -13
- package/dist/adapters/index.js +327 -100
- package/dist/adapters/index.mjs +327 -100
- package/dist/audio-K-Z3-Bce.d.mts +191 -0
- package/dist/audio-K-Z3-Bce.d.ts +191 -0
- package/dist/{base-BICaQaRS.d.mts → base-6LzczuCg.d.ts} +8 -1
- package/dist/{base-CpRPB8u8.d.ts → base-DySbTP8C.d.mts} +8 -1
- package/dist/decisions-CJzOt87t.d.mts +137 -0
- package/dist/decisions-CJzOt87t.d.ts +137 -0
- package/dist/fallback/index.d.mts +4 -4
- package/dist/fallback/index.d.ts +4 -4
- package/dist/http-BQxUT5A9.d.mts +9 -0
- package/dist/http-BQxUT5A9.d.ts +9 -0
- package/dist/index.d.mts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +599 -195
- package/dist/index.mjs +596 -196
- package/dist/providers/anthropic/index.d.mts +7 -4
- package/dist/providers/anthropic/index.d.ts +7 -4
- package/dist/providers/anthropic/index.js +383 -102
- package/dist/providers/anthropic/index.mjs +383 -102
- package/dist/providers/azure/index.d.mts +3 -3
- package/dist/providers/azure/index.d.ts +3 -3
- package/dist/providers/cloudflare/index.d.mts +11 -0
- package/dist/providers/cloudflare/index.d.ts +11 -0
- package/dist/providers/cloudflare/index.js +743 -0
- package/dist/providers/cloudflare/index.mjs +741 -0
- package/dist/providers/fireworks/index.d.mts +3 -3
- package/dist/providers/fireworks/index.d.ts +3 -3
- package/dist/providers/fireworks/index.js +108 -59
- package/dist/providers/fireworks/index.mjs +108 -59
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +108 -59
- package/dist/providers/google/index.mjs +108 -59
- package/dist/providers/ollama/index.d.mts +4 -4
- package/dist/providers/ollama/index.d.ts +4 -4
- package/dist/providers/openai/index.d.mts +38 -6
- package/dist/providers/openai/index.d.ts +38 -6
- package/dist/providers/openai/index.js +1378 -84
- package/dist/providers/openai/index.mjs +1376 -85
- package/dist/providers/openrouter/index.d.mts +21 -4
- package/dist/providers/openrouter/index.d.ts +21 -4
- package/dist/providers/openrouter/index.js +967 -130
- package/dist/providers/openrouter/index.mjs +967 -131
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +108 -59
- package/dist/providers/togetherai/index.mjs +108 -59
- package/dist/providers/typesafe/index.d.mts +8 -0
- package/dist/providers/typesafe/index.d.ts +8 -0
- package/dist/providers/typesafe/index.js +735 -0
- package/dist/providers/typesafe/index.mjs +733 -0
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +108 -59
- package/dist/providers/xai/index.mjs +108 -59
- package/dist/{types-DCFqkJAy.d.ts → types-B0y6VZSQ.d.ts} +1 -1
- package/dist/{types-DHQEMFHA.d.mts → types-BXQMtaCX.d.ts} +17 -3
- package/dist/{types-C3RgRbEC.d.mts → types-BY6BVhFg.d.mts} +1 -1
- package/dist/{types-Dikj3En4.d.ts → types-C7_qVSSd.d.mts} +17 -3
- package/dist/{types-Dg1Bcg1k.d.mts → types-D7lzMMLu.d.mts} +13 -1
- package/dist/{types-Dg1Bcg1k.d.ts → types-D7lzMMLu.d.ts} +13 -1
- package/dist/yourgpt/index.d.mts +1 -1
- package/dist/yourgpt/index.d.ts +1 -1
- package/docs/decisions.md +174 -0
- package/docs/media.md +201 -0
- package/docs/multimodal-and-audio.md +9 -2
- package/package.json +34 -12
- package/dist/audio-BBSwnjo8.d.mts +0 -92
- package/dist/audio-BBSwnjo8.d.ts +0 -92
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
|
|
1
3
|
// src/core/tool-output.ts
|
|
2
4
|
var imageTypes = /* @__PURE__ */ new Set([
|
|
3
5
|
"image/png",
|
|
@@ -111,6 +113,19 @@ function capabilities(model) {
|
|
|
111
113
|
const supportsNone = major === 5 && minor >= 1 && !chat || /^gpt-6-(?:sol|luna)(?:-\d{4}-\d{2}-\d{2})?$/i.test(model);
|
|
112
114
|
return { major, minor, reasoning, supportsNone };
|
|
113
115
|
}
|
|
116
|
+
function filterOpenAIResponseInclude(model, include, options = {}) {
|
|
117
|
+
const c = capabilities(model);
|
|
118
|
+
let result = include;
|
|
119
|
+
if (options.parameterPolicy !== "passthrough" && c.reasoning && !(options.reasoningEffort === "none" && c.supportsNone) && include.includes("message.output_text.logprobs")) {
|
|
120
|
+
result = include.filter(
|
|
121
|
+
(value) => value !== "message.output_text.logprobs"
|
|
122
|
+
);
|
|
123
|
+
(options.onWarning ?? console.warn)(
|
|
124
|
+
`[llm-sdk] ${model}: omitted message.output_text.logprobs because reasoning is enabled.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return result.length ? result : void 0;
|
|
128
|
+
}
|
|
114
129
|
function requiresOpenAIResponses(model, baseUrl, options = {}) {
|
|
115
130
|
if (options.parameterPolicy === "passthrough") return false;
|
|
116
131
|
const c = capabilities(model);
|
|
@@ -272,6 +287,7 @@ function toOpenAIResponseFormat(rf) {
|
|
|
272
287
|
};
|
|
273
288
|
}
|
|
274
289
|
function toOpenAIResponsesTextFormat(rf) {
|
|
290
|
+
if (rf?.type === "json_object") return { type: "json_object" };
|
|
275
291
|
if (!rf || rf.type !== "json_schema") return void 0;
|
|
276
292
|
return {
|
|
277
293
|
type: "json_schema",
|
|
@@ -387,6 +403,30 @@ var DEFAULT_MODEL_CONFIG = {
|
|
|
387
403
|
maxTokens: 128e3
|
|
388
404
|
};
|
|
389
405
|
function openrouter(modelId, options = {}) {
|
|
406
|
+
function reasoning(params) {
|
|
407
|
+
const configured = options.reasoning;
|
|
408
|
+
if (!configured && params.reasoningEffort === void 0) return {};
|
|
409
|
+
const effort = params.reasoningEffort ?? configured?.effort;
|
|
410
|
+
const budget = configured?.maxTokens;
|
|
411
|
+
if (effort !== void 0 && budget !== void 0) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
"OpenRouter reasoning accepts effort or maxTokens, not both"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (budget !== void 0 && (!Number.isSafeInteger(budget) || budget <= 0)) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
"OpenRouter reasoning maxTokens must be a positive integer"
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
reasoning: {
|
|
423
|
+
...effort !== void 0 ? { effort } : {},
|
|
424
|
+
...budget !== void 0 ? { max_tokens: budget } : {},
|
|
425
|
+
...configured?.enabled !== void 0 ? { enabled: configured.enabled } : {},
|
|
426
|
+
...configured?.exclude !== void 0 ? { exclude: configured.exclude } : {}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
390
430
|
const apiKey = options.apiKey ?? process.env.OPENROUTER_API_KEY;
|
|
391
431
|
const baseURL = options.baseURL ?? "https://openrouter.ai/api/v1";
|
|
392
432
|
const headers = {};
|
|
@@ -417,7 +457,7 @@ function openrouter(modelId, options = {}) {
|
|
|
417
457
|
supportsTools: modelConfig.tools,
|
|
418
458
|
supportsStreaming: true,
|
|
419
459
|
supportsJsonMode: modelConfig.jsonMode,
|
|
420
|
-
supportsThinking:
|
|
460
|
+
supportsThinking: options.reasoning !== void 0,
|
|
421
461
|
supportsPDF: false,
|
|
422
462
|
maxTokens: modelConfig.maxTokens,
|
|
423
463
|
supportedImageTypes: modelConfig.vision ? ["image/png", "image/jpeg", "image/gif", "image/webp"] : []
|
|
@@ -429,7 +469,8 @@ function openrouter(modelId, options = {}) {
|
|
|
429
469
|
model: modelId,
|
|
430
470
|
messages,
|
|
431
471
|
temperature: params.temperature,
|
|
432
|
-
max_tokens: params.maxTokens
|
|
472
|
+
max_tokens: params.maxTokens,
|
|
473
|
+
...reasoning(params)
|
|
433
474
|
};
|
|
434
475
|
if (params.tools) {
|
|
435
476
|
requestBody.tools = params.tools;
|
|
@@ -441,7 +482,9 @@ function openrouter(modelId, options = {}) {
|
|
|
441
482
|
if (responseFormat) {
|
|
442
483
|
requestBody.response_format = responseFormat;
|
|
443
484
|
}
|
|
444
|
-
const response = await client2.chat.completions.create(requestBody
|
|
485
|
+
const response = await client2.chat.completions.create(requestBody, {
|
|
486
|
+
signal: params.signal
|
|
487
|
+
});
|
|
445
488
|
const choice = response.choices[0];
|
|
446
489
|
const message = choice.message;
|
|
447
490
|
const toolCalls = (message.tool_calls ?? []).map(
|
|
@@ -453,6 +496,8 @@ function openrouter(modelId, options = {}) {
|
|
|
453
496
|
);
|
|
454
497
|
return {
|
|
455
498
|
text: message.content ?? "",
|
|
499
|
+
...reasoningTextFor(message) ? { reasoningText: reasoningTextFor(message) } : {},
|
|
500
|
+
...reasoningMetadata(message.reasoning_details),
|
|
456
501
|
toolCalls,
|
|
457
502
|
finishReason: mapFinishReason(choice.finish_reason),
|
|
458
503
|
usage: {
|
|
@@ -471,7 +516,9 @@ function openrouter(modelId, options = {}) {
|
|
|
471
516
|
messages,
|
|
472
517
|
temperature: params.temperature,
|
|
473
518
|
max_tokens: params.maxTokens,
|
|
474
|
-
|
|
519
|
+
...reasoning(params),
|
|
520
|
+
stream: true,
|
|
521
|
+
stream_options: { include_usage: true }
|
|
475
522
|
};
|
|
476
523
|
if (params.tools) {
|
|
477
524
|
requestBody.tools = params.tools;
|
|
@@ -483,70 +530,61 @@ function openrouter(modelId, options = {}) {
|
|
|
483
530
|
if (responseFormat) {
|
|
484
531
|
requestBody.response_format = responseFormat;
|
|
485
532
|
}
|
|
486
|
-
const stream = await client2.chat.completions.create(requestBody
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
533
|
+
const stream = await client2.chat.completions.create(requestBody, {
|
|
534
|
+
signal: params.signal
|
|
535
|
+
});
|
|
536
|
+
const calls = /* @__PURE__ */ new Map();
|
|
537
|
+
const details = [];
|
|
538
|
+
let finishReason = "unknown";
|
|
539
|
+
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
490
540
|
for await (const chunk of stream) {
|
|
491
|
-
if (params.signal?.aborted)
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
const choice = chunk.choices[0];
|
|
541
|
+
if (params.signal?.aborted)
|
|
542
|
+
throw params.signal.reason ?? new Error("Aborted");
|
|
543
|
+
const choice = chunk.choices?.[0];
|
|
496
544
|
const delta = choice?.delta;
|
|
497
|
-
if (
|
|
498
|
-
|
|
545
|
+
if (chunk.usage)
|
|
546
|
+
usage = {
|
|
547
|
+
promptTokens: chunk.usage.prompt_tokens ?? 0,
|
|
548
|
+
completionTokens: chunk.usage.completion_tokens ?? 0,
|
|
549
|
+
totalTokens: chunk.usage.total_tokens ?? (chunk.usage.prompt_tokens ?? 0) + (chunk.usage.completion_tokens ?? 0)
|
|
550
|
+
};
|
|
551
|
+
const reasoningText = reasoningTextFor(delta);
|
|
552
|
+
if (typeof reasoningText === "string" && reasoningText) {
|
|
553
|
+
yield { type: "reasoning-delta", text: reasoningText };
|
|
499
554
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
name: currentToolCall.name,
|
|
509
|
-
args: JSON.parse(currentToolCall.arguments || "{}")
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
currentToolCall = {
|
|
514
|
-
id: tc.id,
|
|
515
|
-
name: tc.function?.name ?? "",
|
|
516
|
-
arguments: tc.function?.arguments ?? ""
|
|
517
|
-
};
|
|
518
|
-
} else if (currentToolCall && tc.function?.arguments) {
|
|
519
|
-
currentToolCall.arguments += tc.function.arguments;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
555
|
+
for (const detail of delta?.reasoning_details ?? []) {
|
|
556
|
+
const last = details.at(-1);
|
|
557
|
+
const field = detail.type === "reasoning.text" ? "text" : detail.type === "reasoning.summary" ? "summary" : void 0;
|
|
558
|
+
if (field && last && last.type === detail.type && (!detail.id || !last.id || detail.id === last.id)) {
|
|
559
|
+
last[field] = (last[field] ?? "") + (detail[field] ?? "");
|
|
560
|
+
last.signature ?? (last.signature = detail.signature);
|
|
561
|
+
last.format ?? (last.format = detail.format);
|
|
562
|
+
} else details.push({ ...detail });
|
|
522
563
|
}
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
currentToolCall = null;
|
|
534
|
-
}
|
|
535
|
-
if (chunk.usage) {
|
|
536
|
-
totalPromptTokens = chunk.usage.prompt_tokens;
|
|
537
|
-
totalCompletionTokens = chunk.usage.completion_tokens;
|
|
538
|
-
}
|
|
539
|
-
yield {
|
|
540
|
-
type: "finish",
|
|
541
|
-
finishReason: mapFinishReason(choice.finish_reason),
|
|
542
|
-
usage: {
|
|
543
|
-
promptTokens: totalPromptTokens,
|
|
544
|
-
completionTokens: totalCompletionTokens,
|
|
545
|
-
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
546
|
-
}
|
|
547
|
-
};
|
|
564
|
+
if (delta?.content) yield { type: "text-delta", text: delta.content };
|
|
565
|
+
for (const tc of delta?.tool_calls ?? []) {
|
|
566
|
+
const index = tc.index ?? 0;
|
|
567
|
+
const call = calls.get(index) ?? { id: "", name: "", arguments: "" };
|
|
568
|
+
if (tc.id) call.id = tc.id;
|
|
569
|
+
if (tc.function?.name) call.name = tc.function.name;
|
|
570
|
+
if (tc.function?.arguments) call.arguments += tc.function.arguments;
|
|
571
|
+
calls.set(index, call);
|
|
548
572
|
}
|
|
573
|
+
if (choice?.finish_reason)
|
|
574
|
+
finishReason = mapFinishReason(choice.finish_reason);
|
|
549
575
|
}
|
|
576
|
+
const parsed = [...calls.values()].map((call) => ({
|
|
577
|
+
id: call.id,
|
|
578
|
+
name: call.name,
|
|
579
|
+
args: JSON.parse(call.arguments || "{}")
|
|
580
|
+
}));
|
|
581
|
+
for (const toolCall of parsed) yield { type: "tool-call", toolCall };
|
|
582
|
+
yield {
|
|
583
|
+
type: "finish",
|
|
584
|
+
finishReason,
|
|
585
|
+
usage,
|
|
586
|
+
...reasoningMetadata(details)
|
|
587
|
+
};
|
|
550
588
|
}
|
|
551
589
|
};
|
|
552
590
|
}
|
|
@@ -582,7 +620,7 @@ function formatMessagesForOpenRouter(messages) {
|
|
|
582
620
|
}
|
|
583
621
|
if (part.type === "image") {
|
|
584
622
|
const imageData = typeof part.image === "string" ? part.image : Buffer.from(part.image).toString("base64");
|
|
585
|
-
const url =
|
|
623
|
+
const url = /^(data:|https?:\/\/)/.test(imageData) ? imageData : `data:${part.mimeType ?? "image/png"};base64,${imageData}`;
|
|
586
624
|
return { type: "image_url", image_url: { url, detail: "auto" } };
|
|
587
625
|
}
|
|
588
626
|
return { type: "text", text: "" };
|
|
@@ -593,6 +631,9 @@ function formatMessagesForOpenRouter(messages) {
|
|
|
593
631
|
role: "assistant",
|
|
594
632
|
content: msg.content
|
|
595
633
|
};
|
|
634
|
+
const details = msg.providerMetadata?.openrouter?.reasoningDetails;
|
|
635
|
+
if (Array.isArray(details) && details.length)
|
|
636
|
+
assistantMsg.reasoning_details = details;
|
|
596
637
|
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
597
638
|
assistantMsg.tool_calls = msg.toolCalls.map((tc) => ({
|
|
598
639
|
id: tc.id,
|
|
@@ -638,6 +679,92 @@ async function searchOpenRouterModels(query, apiKey) {
|
|
|
638
679
|
(model) => model.id.toLowerCase().includes(lowerQuery) || model.name.toLowerCase().includes(lowerQuery)
|
|
639
680
|
);
|
|
640
681
|
}
|
|
682
|
+
function reasoningMetadata(details) {
|
|
683
|
+
if (!Array.isArray(details) || !details.length) return {};
|
|
684
|
+
const fields = [
|
|
685
|
+
"type",
|
|
686
|
+
"text",
|
|
687
|
+
"summary",
|
|
688
|
+
"data",
|
|
689
|
+
"signature",
|
|
690
|
+
"id",
|
|
691
|
+
"format",
|
|
692
|
+
"index"
|
|
693
|
+
];
|
|
694
|
+
const reasoningDetails = details.filter((detail) => detail && typeof detail === "object").map(
|
|
695
|
+
(detail) => Object.fromEntries(
|
|
696
|
+
fields.filter((key) => detail[key] !== void 0).map((key) => [key, structuredClone(detail[key])])
|
|
697
|
+
)
|
|
698
|
+
);
|
|
699
|
+
return { providerMetadata: { openrouter: { reasoningDetails } } };
|
|
700
|
+
}
|
|
701
|
+
function reasoningTextFor(message) {
|
|
702
|
+
const text = message?.reasoning ?? message?.reasoning_content;
|
|
703
|
+
if (typeof text === "string" && text) return text;
|
|
704
|
+
if (!Array.isArray(message?.reasoning_details)) return void 0;
|
|
705
|
+
const parts = message.reasoning_details.flatMap((detail) => {
|
|
706
|
+
const value = detail?.type === "reasoning.text" ? detail.text : detail?.type === "reasoning.summary" ? detail.summary : void 0;
|
|
707
|
+
return typeof value === "string" ? [value] : [];
|
|
708
|
+
});
|
|
709
|
+
return parts.length ? parts.join("") : void 0;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/core/provider-error.ts
|
|
713
|
+
function invalidProviderResponse(message) {
|
|
714
|
+
return Object.assign(new Error(message), {
|
|
715
|
+
name: "InvalidProviderResponseError",
|
|
716
|
+
status: 502,
|
|
717
|
+
code: "INVALID_PROVIDER_RESPONSE"
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
function providerErrorEvent(error, code) {
|
|
721
|
+
const details = error ?? {};
|
|
722
|
+
const event = {
|
|
723
|
+
type: "error",
|
|
724
|
+
message: error instanceof Error ? error.message : String(error),
|
|
725
|
+
code,
|
|
726
|
+
...typeof details.status === "number" ? { status: details.status } : {},
|
|
727
|
+
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
728
|
+
...typeof details.param === "string" ? { param: details.param } : {},
|
|
729
|
+
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
730
|
+
};
|
|
731
|
+
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
732
|
+
return event;
|
|
733
|
+
}
|
|
734
|
+
function throwIfAborted(signal) {
|
|
735
|
+
if (!signal?.aborted) return;
|
|
736
|
+
const error = new Error("The operation was aborted");
|
|
737
|
+
error.name = "AbortError";
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/core/tool-validation.ts
|
|
742
|
+
function parseToolArguments(value) {
|
|
743
|
+
let args = value;
|
|
744
|
+
if (typeof value === "string") {
|
|
745
|
+
try {
|
|
746
|
+
args = JSON.parse(value || "{}");
|
|
747
|
+
} catch {
|
|
748
|
+
throw invalidProviderResponse("Tool arguments are not valid JSON");
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (args === null || typeof args !== "object" || Array.isArray(args))
|
|
752
|
+
throw invalidProviderResponse("Tool arguments must be a JSON object");
|
|
753
|
+
return args;
|
|
754
|
+
}
|
|
755
|
+
function validateToolCalls(calls) {
|
|
756
|
+
const ids = /* @__PURE__ */ new Set();
|
|
757
|
+
for (const call of calls) {
|
|
758
|
+
if (!call || typeof call.id !== "string" || !call.id || typeof call.name !== "string" || !call.name || ids.has(call.id))
|
|
759
|
+
throw invalidProviderResponse(
|
|
760
|
+
"Tool calls have missing or duplicate identities"
|
|
761
|
+
);
|
|
762
|
+
ids.add(call.id);
|
|
763
|
+
if (typeof call.args === "string")
|
|
764
|
+
throw invalidProviderResponse("Parsed tool arguments must be an object");
|
|
765
|
+
parseToolArguments(call.args);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
641
768
|
|
|
642
769
|
// src/core/openai-responses.ts
|
|
643
770
|
function resolveOpenAITransport(defaults, overrides) {
|
|
@@ -745,35 +872,6 @@ function getResponseReplay(metadata) {
|
|
|
745
872
|
return responseReplayMetadata(metadata)?.openai?.response?.output;
|
|
746
873
|
}
|
|
747
874
|
|
|
748
|
-
// src/core/provider-error.ts
|
|
749
|
-
function invalidProviderResponse(message) {
|
|
750
|
-
return Object.assign(new Error(message), {
|
|
751
|
-
name: "InvalidProviderResponseError",
|
|
752
|
-
status: 502,
|
|
753
|
-
code: "INVALID_PROVIDER_RESPONSE"
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
function providerErrorEvent(error, code) {
|
|
757
|
-
const details = error ?? {};
|
|
758
|
-
const event = {
|
|
759
|
-
type: "error",
|
|
760
|
-
message: error instanceof Error ? error.message : String(error),
|
|
761
|
-
code,
|
|
762
|
-
...typeof details.status === "number" ? { status: details.status } : {},
|
|
763
|
-
...typeof details.code === "string" ? { providerCode: details.code } : {},
|
|
764
|
-
...typeof details.param === "string" ? { param: details.param } : {},
|
|
765
|
-
...typeof details.request_id === "string" ? { requestId: details.request_id } : {}
|
|
766
|
-
};
|
|
767
|
-
Object.defineProperty(event, "cause", { value: error, enumerable: false });
|
|
768
|
-
return event;
|
|
769
|
-
}
|
|
770
|
-
function throwIfAborted(signal) {
|
|
771
|
-
if (!signal?.aborted) return;
|
|
772
|
-
const error = new Error("The operation was aborted");
|
|
773
|
-
error.name = "AbortError";
|
|
774
|
-
throw error;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
875
|
// src/core/utils.ts
|
|
778
876
|
function generateId(prefix = "id") {
|
|
779
877
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -781,9 +879,6 @@ function generateId(prefix = "id") {
|
|
|
781
879
|
function generateMessageId() {
|
|
782
880
|
return generateId("msg");
|
|
783
881
|
}
|
|
784
|
-
function generateToolCallId() {
|
|
785
|
-
return generateId("call");
|
|
786
|
-
}
|
|
787
882
|
|
|
788
883
|
// src/adapters/openai.ts
|
|
789
884
|
var OpenAIAdapter = class _OpenAIAdapter {
|
|
@@ -807,6 +902,8 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
807
902
|
apiKey: this.config.apiKey,
|
|
808
903
|
baseURL: this.config.baseUrl,
|
|
809
904
|
organization: this.config.organization,
|
|
905
|
+
project: this.config.project,
|
|
906
|
+
timeout: this.config.timeout,
|
|
810
907
|
defaultHeaders: this.config.headers
|
|
811
908
|
});
|
|
812
909
|
}
|
|
@@ -871,11 +968,11 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
871
968
|
}
|
|
872
969
|
buildResponsesInput(request) {
|
|
873
970
|
const sourceMessages = request.rawMessages && request.rawMessages.length > 0 ? this.formatRawMessages(request.rawMessages, true) : formatMessagesForOpenAI(request.messages, void 0, true);
|
|
874
|
-
const
|
|
971
|
+
const input2 = [];
|
|
875
972
|
for (const message of sourceMessages) {
|
|
876
973
|
if (message.role === "system") {
|
|
877
974
|
if (message.content && message.content !== request.systemPrompt) {
|
|
878
|
-
|
|
975
|
+
input2.push({
|
|
879
976
|
type: "message",
|
|
880
977
|
role: "developer",
|
|
881
978
|
content: this.toResponsesContent(message.content, "developer")
|
|
@@ -886,12 +983,12 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
886
983
|
if (message.role === "assistant") {
|
|
887
984
|
const replay = getResponseReplay(message.providerMetadata);
|
|
888
985
|
if (replay) {
|
|
889
|
-
|
|
986
|
+
input2.push(...replay);
|
|
890
987
|
continue;
|
|
891
988
|
}
|
|
892
989
|
const content = this.toResponsesContent(message.content, "assistant");
|
|
893
990
|
if (typeof content === "string" ? content : Array.isArray(content) && content.length > 0) {
|
|
894
|
-
|
|
991
|
+
input2.push({
|
|
895
992
|
type: "message",
|
|
896
993
|
role: "assistant",
|
|
897
994
|
content
|
|
@@ -899,7 +996,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
899
996
|
}
|
|
900
997
|
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
901
998
|
for (const toolCall of toolCalls) {
|
|
902
|
-
|
|
999
|
+
input2.push({
|
|
903
1000
|
type: "function_call",
|
|
904
1001
|
call_id: toolCall.id,
|
|
905
1002
|
name: toolCall.function?.name,
|
|
@@ -909,7 +1006,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
909
1006
|
continue;
|
|
910
1007
|
}
|
|
911
1008
|
if (message.role === "tool") {
|
|
912
|
-
|
|
1009
|
+
input2.push({
|
|
913
1010
|
type: "function_call_output",
|
|
914
1011
|
call_id: message.tool_call_id,
|
|
915
1012
|
output: message.modelOutput ? toolOutputForProvider(message.modelOutput, "responses") : typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? null)
|
|
@@ -917,13 +1014,13 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
917
1014
|
continue;
|
|
918
1015
|
}
|
|
919
1016
|
const role = message.role === "developer" ? "developer" : "user";
|
|
920
|
-
|
|
1017
|
+
input2.push({
|
|
921
1018
|
type: "message",
|
|
922
1019
|
role,
|
|
923
1020
|
content: this.toResponsesContent(message.content, role)
|
|
924
1021
|
});
|
|
925
1022
|
}
|
|
926
|
-
return
|
|
1023
|
+
return input2;
|
|
927
1024
|
}
|
|
928
1025
|
/** Preserve Copilot screenshot attachments on every OpenAI transport. */
|
|
929
1026
|
formatRawMessages(messages, preserveProviderMetadata = false) {
|
|
@@ -1088,6 +1185,16 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1088
1185
|
request.config?.responseFormat
|
|
1089
1186
|
);
|
|
1090
1187
|
const modelIdForPayload = request.config?.model || this.model;
|
|
1188
|
+
const include = filterOpenAIResponseInclude(
|
|
1189
|
+
modelIdForPayload,
|
|
1190
|
+
[
|
|
1191
|
+
.../* @__PURE__ */ new Set([
|
|
1192
|
+
...options?.include ?? [],
|
|
1193
|
+
...options?.store === false ? ["reasoning.encrypted_content"] : []
|
|
1194
|
+
])
|
|
1195
|
+
],
|
|
1196
|
+
this.parameterOptions(request)
|
|
1197
|
+
);
|
|
1091
1198
|
const payload = {
|
|
1092
1199
|
model: modelIdForPayload,
|
|
1093
1200
|
instructions: request.systemPrompt,
|
|
@@ -1111,14 +1218,7 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1111
1218
|
...options?.promptCacheKey !== void 0 ? { prompt_cache_key: options.promptCacheKey } : {},
|
|
1112
1219
|
...options?.promptCacheOptions !== void 0 ? { prompt_cache_options: options.promptCacheOptions } : {},
|
|
1113
1220
|
...options?.promptCacheRetention !== void 0 ? { prompt_cache_retention: options.promptCacheRetention } : {},
|
|
1114
|
-
...
|
|
1115
|
-
include: [
|
|
1116
|
-
.../* @__PURE__ */ new Set([
|
|
1117
|
-
...options?.include ?? [],
|
|
1118
|
-
...options?.store === false ? ["reasoning.encrypted_content"] : []
|
|
1119
|
-
])
|
|
1120
|
-
]
|
|
1121
|
-
} : {},
|
|
1221
|
+
...include ? { include } : {},
|
|
1122
1222
|
...responsesTextFormat ? { text: { format: responsesTextFormat } } : {},
|
|
1123
1223
|
stream
|
|
1124
1224
|
};
|
|
@@ -1489,15 +1589,20 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1489
1589
|
}
|
|
1490
1590
|
throwIfAborted(request.signal);
|
|
1491
1591
|
if (!finishReason)
|
|
1492
|
-
throw
|
|
1592
|
+
throw invalidProviderResponse(
|
|
1593
|
+
"OpenAI stream ended without a finish reason"
|
|
1594
|
+
);
|
|
1493
1595
|
if (toolCalls.size && !["tool_calls", "function_call"].includes(finishReason)) {
|
|
1494
|
-
throw
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
if (!call.id || !call.name)
|
|
1498
|
-
throw new Error("OpenAI stream contains an incomplete tool call");
|
|
1499
|
-
JSON.parse(call.arguments || "{}");
|
|
1596
|
+
throw invalidProviderResponse(
|
|
1597
|
+
"OpenAI stream ended with incomplete tool calls"
|
|
1598
|
+
);
|
|
1500
1599
|
}
|
|
1600
|
+
validateToolCalls(
|
|
1601
|
+
[...toolCalls.values()].map((call) => ({
|
|
1602
|
+
...call,
|
|
1603
|
+
args: parseToolArguments(call.arguments)
|
|
1604
|
+
}))
|
|
1605
|
+
);
|
|
1501
1606
|
for (const call of toolCalls.values()) {
|
|
1502
1607
|
yield {
|
|
1503
1608
|
type: "action:start",
|
|
@@ -1581,20 +1686,20 @@ var OpenAIAdapter = class _OpenAIAdapter {
|
|
|
1581
1686
|
logProviderPayload("openai", "response payload", response, request.debug);
|
|
1582
1687
|
const choice = response.choices?.[0];
|
|
1583
1688
|
const message = choice?.message;
|
|
1689
|
+
const toolCalls = (message?.tool_calls ?? []).map((toolCall) => ({
|
|
1690
|
+
id: toolCall.id,
|
|
1691
|
+
name: toolCall.function?.name,
|
|
1692
|
+
args: parseToolArguments(toolCall.function?.arguments ?? "{}"),
|
|
1693
|
+
...toolCall.extra_content ? { extra_content: toolCall.extra_content } : {}
|
|
1694
|
+
}));
|
|
1695
|
+
validateToolCalls(toolCalls);
|
|
1696
|
+
if (toolCalls.length && choice?.finish_reason && !["tool_calls", "function_call"].includes(choice.finish_reason))
|
|
1697
|
+
throw invalidProviderResponse(
|
|
1698
|
+
"OpenAI response ended with incomplete tool calls"
|
|
1699
|
+
);
|
|
1584
1700
|
return {
|
|
1585
1701
|
content: message?.content ?? "",
|
|
1586
|
-
toolCalls
|
|
1587
|
-
id: toolCall.id ?? generateToolCallId(),
|
|
1588
|
-
name: toolCall.function?.name ?? "",
|
|
1589
|
-
args: (() => {
|
|
1590
|
-
try {
|
|
1591
|
-
return JSON.parse(toolCall.function?.arguments ?? "{}");
|
|
1592
|
-
} catch {
|
|
1593
|
-
return {};
|
|
1594
|
-
}
|
|
1595
|
-
})(),
|
|
1596
|
-
...toolCall.extra_content ? { extra_content: toolCall.extra_content } : {}
|
|
1597
|
-
})) ?? [],
|
|
1702
|
+
toolCalls,
|
|
1598
1703
|
usage: response.usage ? {
|
|
1599
1704
|
promptTokens: response.usage.prompt_tokens,
|
|
1600
1705
|
completionTokens: response.usage.completion_tokens,
|
|
@@ -1643,6 +1748,737 @@ function createCallableProvider(providerFn, properties) {
|
|
|
1643
1748
|
return providerFn;
|
|
1644
1749
|
}
|
|
1645
1750
|
|
|
1751
|
+
// src/core/decisions.ts
|
|
1752
|
+
var decisionErrorMarker = /* @__PURE__ */ Symbol.for("yourgpt.llm-sdk.DecisionAPIError");
|
|
1753
|
+
var _a, _b;
|
|
1754
|
+
var DecisionAPIError = class extends (_b = Error, _a = decisionErrorMarker, _b) {
|
|
1755
|
+
constructor(message, code, status, requestId) {
|
|
1756
|
+
super(message);
|
|
1757
|
+
this.code = code;
|
|
1758
|
+
this.status = status;
|
|
1759
|
+
this.requestId = requestId;
|
|
1760
|
+
this.name = "DecisionAPIError";
|
|
1761
|
+
this[_a] = true;
|
|
1762
|
+
}
|
|
1763
|
+
/** Works across CJS/ESM and separately bundled provider entry points. */
|
|
1764
|
+
static isInstance(error) {
|
|
1765
|
+
return typeof error === "object" && error !== null && /* @__PURE__ */ Symbol.for("yourgpt.llm-sdk.DecisionAPIError") in error;
|
|
1766
|
+
}
|
|
1767
|
+
/** Advisory only: this SDK does not retry Decisions automatically. */
|
|
1768
|
+
get retryable() {
|
|
1769
|
+
return this.code === "network_error" || this.code === "http_error" && (this.status === 408 || this.status === 429 || this.status != null && this.status >= 500);
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
// src/core/decision-validation.ts
|
|
1774
|
+
function decisionInputError(message) {
|
|
1775
|
+
throw new DecisionAPIError(message, "invalid_input");
|
|
1776
|
+
}
|
|
1777
|
+
function decisionResponseError(message) {
|
|
1778
|
+
throw new DecisionAPIError(message, "invalid_response");
|
|
1779
|
+
}
|
|
1780
|
+
function isDecisionRecord(value) {
|
|
1781
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
1782
|
+
return false;
|
|
1783
|
+
const proto = Object.getPrototypeOf(value);
|
|
1784
|
+
return proto === Object.prototype || proto === null;
|
|
1785
|
+
}
|
|
1786
|
+
function json(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
1787
|
+
if (depth > 64) return false;
|
|
1788
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
1789
|
+
return true;
|
|
1790
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1791
|
+
if (typeof value !== "object" || !Array.isArray(value) && !isDecisionRecord(value) || seen.has(value))
|
|
1792
|
+
return false;
|
|
1793
|
+
seen.add(value);
|
|
1794
|
+
const valid = Object.getOwnPropertySymbols(value).length === 0 && (Array.isArray(value) ? Array.from(value) : Object.values(value)).every(
|
|
1795
|
+
(item) => json(item, seen, depth + 1)
|
|
1796
|
+
);
|
|
1797
|
+
seen.delete(value);
|
|
1798
|
+
return valid;
|
|
1799
|
+
}
|
|
1800
|
+
function input(value) {
|
|
1801
|
+
return (typeof value === "string" || Array.isArray(value) || isDecisionRecord(value)) && json(value);
|
|
1802
|
+
}
|
|
1803
|
+
function assertDecisionKeys(value, allowed, label) {
|
|
1804
|
+
if (Object.keys(value).some((key) => !allowed.includes(key)))
|
|
1805
|
+
decisionInputError(`${label} contains an unsupported field`);
|
|
1806
|
+
}
|
|
1807
|
+
function validateDecisionInput(options) {
|
|
1808
|
+
if (!isDecisionRecord(options))
|
|
1809
|
+
decisionInputError("Decisions options must be an object");
|
|
1810
|
+
assertDecisionKeys(
|
|
1811
|
+
options,
|
|
1812
|
+
["state", "questions", "images", "signal", "timeoutMs", "providerOptions"],
|
|
1813
|
+
"Decisions options"
|
|
1814
|
+
);
|
|
1815
|
+
if (!input(options.state))
|
|
1816
|
+
decisionInputError(
|
|
1817
|
+
"state must be finite, acyclic JSON text, object, or array (maximum depth 64)"
|
|
1818
|
+
);
|
|
1819
|
+
if (!isDecisionRecord(options.questions) || Object.keys(options.questions).length === 0)
|
|
1820
|
+
decisionInputError("questions must be a nonempty map");
|
|
1821
|
+
for (const [id, question] of Object.entries(options.questions)) {
|
|
1822
|
+
if (!id.length || !isDecisionRecord(question) || !input(question.instructions))
|
|
1823
|
+
decisionInputError(
|
|
1824
|
+
"Every question needs a nonempty ID and JSON-compatible instructions"
|
|
1825
|
+
);
|
|
1826
|
+
assertDecisionKeys(
|
|
1827
|
+
question,
|
|
1828
|
+
["type", "instructions", "criteria"],
|
|
1829
|
+
"Question"
|
|
1830
|
+
);
|
|
1831
|
+
if (question.type === "boolean") {
|
|
1832
|
+
if (question.criteria !== void 0 && (!isDecisionRecord(question.criteria) || Object.entries(question.criteria).some(
|
|
1833
|
+
([key, value]) => !["true", "false"].includes(key) || !input(value)
|
|
1834
|
+
)))
|
|
1835
|
+
decisionInputError("Boolean criteria may describe only true and false");
|
|
1836
|
+
} else if (question.type === "choice") {
|
|
1837
|
+
if (!isDecisionRecord(question.criteria) || Object.keys(question.criteria).length < 2 || Object.keys(question.criteria).length > 255 || Object.entries(question.criteria).some(
|
|
1838
|
+
([key, value]) => !key.length || value !== null && !input(value)
|
|
1839
|
+
))
|
|
1840
|
+
decisionInputError(
|
|
1841
|
+
"Choice criteria require 2\u2013255 named options with JSON descriptions or null"
|
|
1842
|
+
);
|
|
1843
|
+
} else if (question.type === "score") {
|
|
1844
|
+
if (!Array.isArray(question.criteria) || question.criteria.length < 2 || !Array.from(question.criteria).every(input))
|
|
1845
|
+
decisionInputError(
|
|
1846
|
+
"Score criteria require at least two ordered JSON descriptions"
|
|
1847
|
+
);
|
|
1848
|
+
} else
|
|
1849
|
+
decisionInputError("Question type must be boolean, choice, or score");
|
|
1850
|
+
}
|
|
1851
|
+
if (options.timeoutMs !== void 0 && (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 2147483647))
|
|
1852
|
+
decisionInputError("timeoutMs must be a positive 32-bit integer");
|
|
1853
|
+
if (options.providerOptions !== void 0 && (!isDecisionRecord(options.providerOptions) || !json(options.providerOptions)))
|
|
1854
|
+
decisionInputError("providerOptions must be a JSON object");
|
|
1855
|
+
if (options.images !== void 0 && (!Array.isArray(options.images) || !Array.from(options.images).every(
|
|
1856
|
+
(image) => isDecisionRecord(image) && image.data instanceof Uint8Array
|
|
1857
|
+
)))
|
|
1858
|
+
decisionInputError(
|
|
1859
|
+
"Decision images must contain encoded bytes; hosted image URLs are unsupported"
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
function probability(value) {
|
|
1863
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
1864
|
+
}
|
|
1865
|
+
function exactKeys(value, keys) {
|
|
1866
|
+
return Object.keys(value).length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
1867
|
+
}
|
|
1868
|
+
function validateDecisionResult(questions, result) {
|
|
1869
|
+
if (!isDecisionRecord(result) || !isDecisionRecord(result.answers) || !exactKeys(result.answers, Object.keys(questions)))
|
|
1870
|
+
decisionResponseError(
|
|
1871
|
+
"Decisions must return exactly one answer per question"
|
|
1872
|
+
);
|
|
1873
|
+
const rounding = result.rounding;
|
|
1874
|
+
let pError = 0, sError = 0;
|
|
1875
|
+
if (rounding !== void 0) {
|
|
1876
|
+
if (!isDecisionRecord(rounding) || ![rounding.probabilityDecimals, rounding.scoreDecimals].every(
|
|
1877
|
+
(v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 15
|
|
1878
|
+
))
|
|
1879
|
+
decisionResponseError("Invalid decision rounding precision");
|
|
1880
|
+
pError = 0.5 * 10 ** -rounding.probabilityDecimals;
|
|
1881
|
+
sError = 0.5 * 10 ** -rounding.scoreDecimals;
|
|
1882
|
+
}
|
|
1883
|
+
const epsilon = 1e-6;
|
|
1884
|
+
for (const [id, question] of Object.entries(questions)) {
|
|
1885
|
+
const answer = result.answers[id];
|
|
1886
|
+
if (!isDecisionRecord(answer))
|
|
1887
|
+
decisionResponseError("Invalid decision answer");
|
|
1888
|
+
if (answer.type === "refusal") continue;
|
|
1889
|
+
if (answer.type !== question.type)
|
|
1890
|
+
decisionResponseError("Decision answer type does not match its question");
|
|
1891
|
+
if (answer.confidence !== void 0 && !probability(answer.confidence))
|
|
1892
|
+
decisionResponseError(
|
|
1893
|
+
"Confidence must be finite and between zero and one"
|
|
1894
|
+
);
|
|
1895
|
+
if (question.type === "boolean") {
|
|
1896
|
+
if (!probability(answer.probability))
|
|
1897
|
+
decisionResponseError(
|
|
1898
|
+
"Boolean probability must be between zero and one"
|
|
1899
|
+
);
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
const keys = question.type === "choice" ? Object.keys(question.criteria) : question.criteria.map((_, index) => String(index));
|
|
1903
|
+
if (question.type === "choice" && (typeof answer.choice !== "string" || !keys.includes(answer.choice)))
|
|
1904
|
+
decisionResponseError("Decision selected an unknown choice");
|
|
1905
|
+
if (question.type === "score" && (typeof answer.score !== "number" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > keys.length - 1))
|
|
1906
|
+
decisionResponseError("Decision score is outside its ordered levels");
|
|
1907
|
+
if (answer.probabilities === void 0) continue;
|
|
1908
|
+
const distribution2 = answer.probabilities;
|
|
1909
|
+
if (!isDecisionRecord(distribution2) || !exactKeys(distribution2, keys) || !Object.values(distribution2).every(probability))
|
|
1910
|
+
decisionResponseError(
|
|
1911
|
+
"Decision distribution must contain every option exactly once with finite probabilities"
|
|
1912
|
+
);
|
|
1913
|
+
const values = Object.values(distribution2);
|
|
1914
|
+
if (Math.abs(values.reduce((a, b) => a + b, 0) - 1) > epsilon + keys.length * pError)
|
|
1915
|
+
decisionResponseError("Decision probabilities do not sum to one");
|
|
1916
|
+
if (question.type === "choice") {
|
|
1917
|
+
if (values.some(
|
|
1918
|
+
(v) => v > distribution2[answer.choice] + epsilon + 2 * pError
|
|
1919
|
+
))
|
|
1920
|
+
decisionResponseError(
|
|
1921
|
+
"Decision choice is not a highest-probability option"
|
|
1922
|
+
);
|
|
1923
|
+
} else {
|
|
1924
|
+
const mean = keys.reduce(
|
|
1925
|
+
(sum, key) => sum + Number(key) * distribution2[key],
|
|
1926
|
+
0
|
|
1927
|
+
);
|
|
1928
|
+
const tolerance = epsilon + sError + pError * keys.reduce((sum, key) => sum + Number(key), 0);
|
|
1929
|
+
if (Math.abs(answer.score - mean) > tolerance)
|
|
1930
|
+
decisionResponseError(
|
|
1931
|
+
"Decision score does not match its probability-weighted levels"
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// src/providers/decisions/http.ts
|
|
1938
|
+
async function postDecision(url, headers, body, options, fetchFn, maxBytes) {
|
|
1939
|
+
const encoded = JSON.stringify(body);
|
|
1940
|
+
if (Buffer.byteLength(encoded, "utf8") > maxBytes)
|
|
1941
|
+
decisionInputError("Decision request exceeds the provider's byte limit");
|
|
1942
|
+
options.signal?.throwIfAborted();
|
|
1943
|
+
const controller = new AbortController();
|
|
1944
|
+
const relay = () => controller.abort(options.signal?.reason);
|
|
1945
|
+
options.signal?.addEventListener("abort", relay, { once: true });
|
|
1946
|
+
const timer = setTimeout(
|
|
1947
|
+
() => controller.abort(
|
|
1948
|
+
new DOMException("Decision request timed out", "TimeoutError")
|
|
1949
|
+
),
|
|
1950
|
+
options.timeoutMs ?? 6e4
|
|
1951
|
+
);
|
|
1952
|
+
let rejectAbort = () => {
|
|
1953
|
+
};
|
|
1954
|
+
const aborted = new Promise((_, reject) => {
|
|
1955
|
+
rejectAbort = () => reject(controller.signal.reason);
|
|
1956
|
+
controller.signal.addEventListener("abort", rejectAbort, { once: true });
|
|
1957
|
+
});
|
|
1958
|
+
const perform = async () => {
|
|
1959
|
+
let response;
|
|
1960
|
+
try {
|
|
1961
|
+
response = await fetchFn(url, {
|
|
1962
|
+
method: "POST",
|
|
1963
|
+
headers,
|
|
1964
|
+
body: encoded,
|
|
1965
|
+
signal: controller.signal,
|
|
1966
|
+
redirect: "error"
|
|
1967
|
+
});
|
|
1968
|
+
} catch {
|
|
1969
|
+
controller.signal.throwIfAborted();
|
|
1970
|
+
throw new DecisionAPIError(
|
|
1971
|
+
"Decision request failed before receiving a response",
|
|
1972
|
+
"network_error"
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("cf-ray") ?? void 0;
|
|
1976
|
+
if (controller.signal.aborted) {
|
|
1977
|
+
void response.body?.cancel().catch(() => {
|
|
1978
|
+
});
|
|
1979
|
+
controller.signal.throwIfAborted();
|
|
1980
|
+
}
|
|
1981
|
+
if (!response.ok) {
|
|
1982
|
+
void response.body?.cancel().catch(() => {
|
|
1983
|
+
});
|
|
1984
|
+
throw new DecisionAPIError(
|
|
1985
|
+
`Decision API returned HTTP ${response.status}`,
|
|
1986
|
+
"http_error",
|
|
1987
|
+
response.status,
|
|
1988
|
+
requestId
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
if (!response.body)
|
|
1992
|
+
decisionResponseError("Decision API returned an empty body");
|
|
1993
|
+
const reader = response.body.getReader();
|
|
1994
|
+
const cancel = () => {
|
|
1995
|
+
void reader.cancel().catch(() => {
|
|
1996
|
+
});
|
|
1997
|
+
};
|
|
1998
|
+
controller.signal.addEventListener("abort", cancel, { once: true });
|
|
1999
|
+
const chunks = [];
|
|
2000
|
+
let size = 0;
|
|
2001
|
+
try {
|
|
2002
|
+
while (true) {
|
|
2003
|
+
controller.signal.throwIfAborted();
|
|
2004
|
+
const { value: value2, done } = await reader.read();
|
|
2005
|
+
if (done) break;
|
|
2006
|
+
size += value2.byteLength;
|
|
2007
|
+
if (size > 8 * 1024 * 1024) {
|
|
2008
|
+
cancel();
|
|
2009
|
+
decisionResponseError("Decision API response exceeds 8 MiB");
|
|
2010
|
+
}
|
|
2011
|
+
chunks.push(value2);
|
|
2012
|
+
}
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
controller.signal.throwIfAborted();
|
|
2015
|
+
if (error instanceof DecisionAPIError) throw error;
|
|
2016
|
+
throw new DecisionAPIError(
|
|
2017
|
+
"Decision response could not be read",
|
|
2018
|
+
"network_error",
|
|
2019
|
+
response.status,
|
|
2020
|
+
requestId
|
|
2021
|
+
);
|
|
2022
|
+
} finally {
|
|
2023
|
+
controller.signal.removeEventListener("abort", cancel);
|
|
2024
|
+
reader.releaseLock();
|
|
2025
|
+
}
|
|
2026
|
+
controller.signal.throwIfAborted();
|
|
2027
|
+
let value;
|
|
2028
|
+
try {
|
|
2029
|
+
value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
2030
|
+
} catch {
|
|
2031
|
+
decisionResponseError("Decision API did not return valid JSON");
|
|
2032
|
+
}
|
|
2033
|
+
return { value, status: response.status, requestId };
|
|
2034
|
+
};
|
|
2035
|
+
try {
|
|
2036
|
+
return await Promise.race([perform(), aborted]);
|
|
2037
|
+
} finally {
|
|
2038
|
+
clearTimeout(timer);
|
|
2039
|
+
options.signal?.removeEventListener("abort", relay);
|
|
2040
|
+
controller.signal.removeEventListener("abort", rejectAbort);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
// src/providers/decisions/images.ts
|
|
2045
|
+
function unsupportedDecision(message) {
|
|
2046
|
+
throw new DecisionAPIError(message, "unsupported_option");
|
|
2047
|
+
}
|
|
2048
|
+
function prepareDecisionImages(images, provider) {
|
|
2049
|
+
if (provider === "openrouter" && images.length)
|
|
2050
|
+
unsupportedDecision(
|
|
2051
|
+
"OpenRouter's native Decisions schema does not support image inputs"
|
|
2052
|
+
);
|
|
2053
|
+
if (provider === "cloudflare" && images.length > 4)
|
|
2054
|
+
decisionInputError("Cloudflare Decisions supports at most four images");
|
|
2055
|
+
if (provider === "openai" && images.length > 128)
|
|
2056
|
+
decisionInputError("OpenAI Decisions supports at most 128 images");
|
|
2057
|
+
let total = 0;
|
|
2058
|
+
return images.map((image) => {
|
|
2059
|
+
assertDecisionKeys(
|
|
2060
|
+
image,
|
|
2061
|
+
["data", "mediaType", "detail"],
|
|
2062
|
+
"Decision image"
|
|
2063
|
+
);
|
|
2064
|
+
if (image.detail !== void 0) {
|
|
2065
|
+
if (provider !== "openai")
|
|
2066
|
+
unsupportedDecision(
|
|
2067
|
+
"Image detail is only supported by OpenAI Decisions"
|
|
2068
|
+
);
|
|
2069
|
+
if (!["auto", "low", "high", "original"].includes(image.detail))
|
|
2070
|
+
decisionInputError("Invalid image detail");
|
|
2071
|
+
}
|
|
2072
|
+
const data = Buffer.from(image.data);
|
|
2073
|
+
const signature = data.subarray(0, 12).toString("latin1");
|
|
2074
|
+
const mediaType = signature.startsWith("\x89PNG\r\n\n") ? "image/png" : data[0] === 255 && data[1] === 216 && data[2] === 255 ? "image/jpeg" : signature.startsWith("RIFF") && signature.slice(8) === "WEBP" ? "image/webp" : /^GIF8[79]a/.test(signature) ? "image/gif" : void 0;
|
|
2075
|
+
if (!mediaType || image.mediaType !== void 0 && image.mediaType !== mediaType)
|
|
2076
|
+
decisionInputError(
|
|
2077
|
+
"Decision image bytes must match a supported encoded image format"
|
|
2078
|
+
);
|
|
2079
|
+
if (provider === "typesafe" && !["image/png", "image/jpeg"].includes(mediaType))
|
|
2080
|
+
unsupportedDecision("Jev images must be PNG or JPEG");
|
|
2081
|
+
if (provider === "cloudflare" && mediaType === "image/gif")
|
|
2082
|
+
unsupportedDecision("Cloudflare Decisions does not accept GIF images");
|
|
2083
|
+
const limit = provider === "cloudflare" ? 4 : provider === "typesafe" ? 16 : 20;
|
|
2084
|
+
if (data.length > limit * 1024 * 1024)
|
|
2085
|
+
decisionInputError("Decision image exceeds this provider's file limit");
|
|
2086
|
+
total += data.length;
|
|
2087
|
+
if (provider === "cloudflare" && total > 8 * 1024 * 1024)
|
|
2088
|
+
decisionInputError("Cloudflare Decisions images exceed 8 MiB total");
|
|
2089
|
+
const base64 = data.toString("base64");
|
|
2090
|
+
return {
|
|
2091
|
+
url: `data:${mediaType};base64,${base64}`,
|
|
2092
|
+
base64,
|
|
2093
|
+
mediaType,
|
|
2094
|
+
detail: image.detail
|
|
2095
|
+
};
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// src/providers/decisions/response.ts
|
|
2100
|
+
function distribution(value, score) {
|
|
2101
|
+
if (!Array.isArray(value))
|
|
2102
|
+
decisionResponseError(
|
|
2103
|
+
"OpenAI Decisions omitted its probability distribution"
|
|
2104
|
+
);
|
|
2105
|
+
const entries = [];
|
|
2106
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2107
|
+
for (const item of value) {
|
|
2108
|
+
if (!isDecisionRecord(item) || (score ? typeof item.value !== "number" || !Number.isInteger(item.value) || item.value < 0 : typeof item.value !== "string"))
|
|
2109
|
+
decisionResponseError("Invalid decision probability option");
|
|
2110
|
+
const key = String(item.value);
|
|
2111
|
+
if (seen.has(key))
|
|
2112
|
+
decisionResponseError("Duplicate option in decision probabilities");
|
|
2113
|
+
seen.add(key);
|
|
2114
|
+
entries.push([key, item.probability]);
|
|
2115
|
+
}
|
|
2116
|
+
return Object.fromEntries(entries);
|
|
2117
|
+
}
|
|
2118
|
+
function parseDecisionResponse(raw, provider, questions, status, requestId) {
|
|
2119
|
+
if (!isDecisionRecord(raw))
|
|
2120
|
+
decisionResponseError("Decision response must be an object");
|
|
2121
|
+
let payload = raw;
|
|
2122
|
+
if (provider === "cloudflare") {
|
|
2123
|
+
if (raw.success === false)
|
|
2124
|
+
throw new DecisionAPIError(
|
|
2125
|
+
"Cloudflare reported an unsuccessful decision request",
|
|
2126
|
+
"http_error",
|
|
2127
|
+
status,
|
|
2128
|
+
requestId
|
|
2129
|
+
);
|
|
2130
|
+
if (raw.success !== true || !isDecisionRecord(raw.result))
|
|
2131
|
+
decisionResponseError(
|
|
2132
|
+
"Cloudflare Decisions returned an invalid result envelope"
|
|
2133
|
+
);
|
|
2134
|
+
payload = raw.result;
|
|
2135
|
+
}
|
|
2136
|
+
if (typeof payload.model !== "string" || !payload.model)
|
|
2137
|
+
decisionResponseError("Decision response omitted its model identity");
|
|
2138
|
+
const entries = [];
|
|
2139
|
+
if (provider === "openai") {
|
|
2140
|
+
if (!Array.isArray(payload.answers))
|
|
2141
|
+
decisionResponseError("OpenAI Decisions answers must be an array");
|
|
2142
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2143
|
+
for (const answer of payload.answers) {
|
|
2144
|
+
if (!isDecisionRecord(answer))
|
|
2145
|
+
decisionResponseError("Invalid OpenAI decision answer");
|
|
2146
|
+
if (answer.type === "refusal" && answer.name == null)
|
|
2147
|
+
throw new DecisionAPIError(
|
|
2148
|
+
"OpenAI refused an unnamed decision question",
|
|
2149
|
+
"refusal",
|
|
2150
|
+
status,
|
|
2151
|
+
requestId
|
|
2152
|
+
);
|
|
2153
|
+
if (typeof answer.name !== "string" || seen.has(answer.name))
|
|
2154
|
+
decisionResponseError(
|
|
2155
|
+
"OpenAI decision answers have missing or duplicate question identities"
|
|
2156
|
+
);
|
|
2157
|
+
seen.add(answer.name);
|
|
2158
|
+
entries.push([answer.name, answer]);
|
|
2159
|
+
}
|
|
2160
|
+
} else {
|
|
2161
|
+
if (!isDecisionRecord(payload.answers))
|
|
2162
|
+
decisionResponseError("Decisions answers must be a named map");
|
|
2163
|
+
entries.push(...Object.entries(payload.answers));
|
|
2164
|
+
}
|
|
2165
|
+
const answers = Object.fromEntries(
|
|
2166
|
+
entries.map(([id, value]) => {
|
|
2167
|
+
if (!isDecisionRecord(value))
|
|
2168
|
+
decisionResponseError("Invalid decision answer");
|
|
2169
|
+
if (value.type === "refusal" && provider === "openai")
|
|
2170
|
+
return [id, { type: "refusal" }];
|
|
2171
|
+
const booleanType = provider === "openai" ? "predicate" : "noul";
|
|
2172
|
+
if (value.type === booleanType)
|
|
2173
|
+
return [
|
|
2174
|
+
id,
|
|
2175
|
+
{
|
|
2176
|
+
type: "boolean",
|
|
2177
|
+
probability: provider === "openai" ? value.probability : value.noul
|
|
2178
|
+
}
|
|
2179
|
+
];
|
|
2180
|
+
if (value.type !== "choice" && value.type !== "score")
|
|
2181
|
+
decisionResponseError(
|
|
2182
|
+
"Provider returned an unsupported decision answer type"
|
|
2183
|
+
);
|
|
2184
|
+
if (provider !== "openrouter" && value.probabilities == null)
|
|
2185
|
+
decisionResponseError(
|
|
2186
|
+
"Provider omitted a required decision distribution"
|
|
2187
|
+
);
|
|
2188
|
+
const probabilities = provider === "openai" ? distribution(value.probabilities, value.type === "score") : value.probabilities;
|
|
2189
|
+
const fields = {
|
|
2190
|
+
...probabilities !== void 0 && {
|
|
2191
|
+
probabilities
|
|
2192
|
+
},
|
|
2193
|
+
...value.confidence != null && {
|
|
2194
|
+
confidence: value.confidence
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
return [
|
|
2198
|
+
id,
|
|
2199
|
+
value.type === "choice" ? { type: "choice", choice: value.choice, ...fields } : { type: "score", score: value.score, ...fields }
|
|
2200
|
+
];
|
|
2201
|
+
})
|
|
2202
|
+
);
|
|
2203
|
+
const result = {
|
|
2204
|
+
answers,
|
|
2205
|
+
response: {
|
|
2206
|
+
provider,
|
|
2207
|
+
modelId: payload.model,
|
|
2208
|
+
status,
|
|
2209
|
+
requestId: requestId ?? (typeof payload.id === "string" ? payload.id : void 0),
|
|
2210
|
+
body: raw
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
const typesafe = provider === "typesafe" || provider === "openrouter" && (payload.model.startsWith("typesafe/") || payload.provider === "TypeSafe");
|
|
2214
|
+
if (typesafe) result.rounding = { probabilityDecimals: 2, scoreDecimals: 2 };
|
|
2215
|
+
if (payload.usage != null) {
|
|
2216
|
+
if (!isDecisionRecord(payload.usage))
|
|
2217
|
+
decisionResponseError("Invalid decision usage");
|
|
2218
|
+
const usage = payload.usage;
|
|
2219
|
+
const details = isDecisionRecord(usage.input_tokens_details) ? usage.input_tokens_details : {};
|
|
2220
|
+
result.usage = {};
|
|
2221
|
+
const mapped = {
|
|
2222
|
+
inputTokens: usage.input_tokens,
|
|
2223
|
+
outputTokens: usage.output_tokens,
|
|
2224
|
+
totalTokens: usage.total_tokens,
|
|
2225
|
+
cachedInputTokens: details.cached_tokens,
|
|
2226
|
+
cacheWriteTokens: details.cache_write_tokens,
|
|
2227
|
+
costUsd: usage.cost
|
|
2228
|
+
};
|
|
2229
|
+
for (const [key, value] of Object.entries(mapped)) {
|
|
2230
|
+
if (value == null) continue;
|
|
2231
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || key !== "costUsd" && !Number.isInteger(value))
|
|
2232
|
+
decisionResponseError("Invalid decision usage count");
|
|
2233
|
+
result.usage[key] = value;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
validateDecisionResult(questions, result);
|
|
2237
|
+
return result;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// src/providers/decisions/model.ts
|
|
2241
|
+
function asText(value) {
|
|
2242
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
2243
|
+
}
|
|
2244
|
+
function requestOptions(options, provider) {
|
|
2245
|
+
for (const key of Object.keys(options.providerOptions ?? {})) {
|
|
2246
|
+
if (key !== provider)
|
|
2247
|
+
unsupportedDecision(
|
|
2248
|
+
"Decision providerOptions must match the selected provider"
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
const opts = options.providerOptions?.[provider] ?? {};
|
|
2252
|
+
if (!isDecisionRecord(opts))
|
|
2253
|
+
decisionInputError("Decision provider options must be an object");
|
|
2254
|
+
const allowed = provider === "openai" ? ["safetyIdentifier", "promptCacheKey"] : provider === "typesafe" ? ["longContext"] : provider === "openrouter" ? ["sessionId", "provider"] : [];
|
|
2255
|
+
assertDecisionKeys(opts, allowed, "Decision provider options");
|
|
2256
|
+
for (const [key, limit] of [
|
|
2257
|
+
["safetyIdentifier", 128],
|
|
2258
|
+
["promptCacheKey", 64],
|
|
2259
|
+
["sessionId", 256]
|
|
2260
|
+
]) {
|
|
2261
|
+
if (opts[key] !== void 0 && (typeof opts[key] !== "string" || opts[key].length > limit))
|
|
2262
|
+
decisionInputError("Invalid decision identifier or cache key");
|
|
2263
|
+
}
|
|
2264
|
+
if (opts.longContext !== void 0 && typeof opts.longContext !== "boolean")
|
|
2265
|
+
decisionInputError("longContext must be boolean");
|
|
2266
|
+
if (opts.provider !== void 0) {
|
|
2267
|
+
if (!isDecisionRecord(opts.provider))
|
|
2268
|
+
decisionInputError("OpenRouter routing preferences must be an object");
|
|
2269
|
+
assertDecisionKeys(
|
|
2270
|
+
opts.provider,
|
|
2271
|
+
["order", "allowFallbacks"],
|
|
2272
|
+
"OpenRouter routing preferences"
|
|
2273
|
+
);
|
|
2274
|
+
if (opts.provider.allowFallbacks !== void 0 && typeof opts.provider.allowFallbacks !== "boolean")
|
|
2275
|
+
decisionInputError("allowFallbacks must be boolean");
|
|
2276
|
+
if (opts.provider.order !== void 0 && (!Array.isArray(opts.provider.order) || !opts.provider.order.every(
|
|
2277
|
+
(value) => typeof value === "string" && value.length
|
|
2278
|
+
)))
|
|
2279
|
+
decisionInputError("Provider order must be an array of provider names");
|
|
2280
|
+
}
|
|
2281
|
+
return opts;
|
|
2282
|
+
}
|
|
2283
|
+
function nativeDecisionModel(modelId, config) {
|
|
2284
|
+
if (typeof modelId !== "string" || !modelId.trim())
|
|
2285
|
+
decisionInputError("Decision model ID is required");
|
|
2286
|
+
const provider = config.provider;
|
|
2287
|
+
return {
|
|
2288
|
+
provider,
|
|
2289
|
+
modelId,
|
|
2290
|
+
async doDecide(options) {
|
|
2291
|
+
validateDecisionInput(options);
|
|
2292
|
+
options.signal?.throwIfAborted();
|
|
2293
|
+
const opts = requestOptions(options, provider);
|
|
2294
|
+
const questions = Object.entries(options.questions);
|
|
2295
|
+
if (provider === "openai" && questions.length > 200)
|
|
2296
|
+
decisionInputError("OpenAI Decisions supports at most 200 questions");
|
|
2297
|
+
if (provider === "cloudflare" && (questions.length > 64 || questions.some(([id]) => !/^[A-Za-z0-9_.-]{1,100}$/.test(id))))
|
|
2298
|
+
decisionInputError(
|
|
2299
|
+
"Cloudflare supports 1\u201364 questions with IDs of 1\u2013100 letters, digits, underscores, dots, or hyphens"
|
|
2300
|
+
);
|
|
2301
|
+
for (const [, question] of questions) {
|
|
2302
|
+
if (provider !== "openai" && question.type === "score" && question.criteria.length > 10)
|
|
2303
|
+
decisionInputError(
|
|
2304
|
+
"System One adapters support at most ten score levels"
|
|
2305
|
+
);
|
|
2306
|
+
if (provider === "cloudflare" && typeof question.instructions === "string" && !question.instructions.trim())
|
|
2307
|
+
decisionInputError("Cloudflare requires nonempty instructions");
|
|
2308
|
+
if (provider === "openrouter" && question.type === "boolean" && question.criteria !== void 0 && (question.criteria.true === void 0 || question.criteria.false === void 0))
|
|
2309
|
+
decisionInputError(
|
|
2310
|
+
"OpenRouter boolean criteria must describe both true and false when supplied"
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
const images = prepareDecisionImages(options.images ?? [], provider);
|
|
2314
|
+
const headers = Object.fromEntries(
|
|
2315
|
+
Object.entries(config.headers ?? {}).filter(
|
|
2316
|
+
([name]) => !["authorization", "content-type"].includes(name.toLowerCase())
|
|
2317
|
+
)
|
|
2318
|
+
);
|
|
2319
|
+
headers["Content-Type"] = "application/json";
|
|
2320
|
+
const key = config.apiKey ?? process.env[config.keyEnv];
|
|
2321
|
+
if (!key?.trim())
|
|
2322
|
+
decisionInputError(`Missing ${config.keyEnv} for Decisions`);
|
|
2323
|
+
headers.Authorization = `Bearer ${key}`;
|
|
2324
|
+
let body;
|
|
2325
|
+
if (provider === "openai") {
|
|
2326
|
+
body = {
|
|
2327
|
+
model: modelId,
|
|
2328
|
+
input: [
|
|
2329
|
+
{
|
|
2330
|
+
role: "user",
|
|
2331
|
+
content: [
|
|
2332
|
+
{ type: "input_text", text: asText(options.state) },
|
|
2333
|
+
...images.map((image) => ({
|
|
2334
|
+
type: "input_image",
|
|
2335
|
+
image_url: image.url,
|
|
2336
|
+
...image.detail !== void 0 && { detail: image.detail }
|
|
2337
|
+
}))
|
|
2338
|
+
]
|
|
2339
|
+
}
|
|
2340
|
+
],
|
|
2341
|
+
questions: questions.map(([name, question]) => {
|
|
2342
|
+
const instructions = asText(question.instructions);
|
|
2343
|
+
if (question.type === "boolean")
|
|
2344
|
+
return {
|
|
2345
|
+
type: "predicate",
|
|
2346
|
+
name,
|
|
2347
|
+
instructions: [
|
|
2348
|
+
instructions,
|
|
2349
|
+
...Object.entries(question.criteria ?? {}).map(
|
|
2350
|
+
([key2, value]) => `Criteria for ${key2}:
|
|
2351
|
+
${asText(value)}`
|
|
2352
|
+
)
|
|
2353
|
+
].join("\n\n")
|
|
2354
|
+
};
|
|
2355
|
+
if (question.type === "choice")
|
|
2356
|
+
return {
|
|
2357
|
+
type: "choice",
|
|
2358
|
+
name,
|
|
2359
|
+
instructions,
|
|
2360
|
+
choices: Object.entries(question.criteria).map(
|
|
2361
|
+
([value, description]) => ({
|
|
2362
|
+
value,
|
|
2363
|
+
...description !== null && {
|
|
2364
|
+
description: asText(description)
|
|
2365
|
+
}
|
|
2366
|
+
})
|
|
2367
|
+
)
|
|
2368
|
+
};
|
|
2369
|
+
return {
|
|
2370
|
+
type: "score",
|
|
2371
|
+
name,
|
|
2372
|
+
instructions,
|
|
2373
|
+
levels: question.criteria.map((description, index) => ({
|
|
2374
|
+
label: String(index),
|
|
2375
|
+
description: asText(description)
|
|
2376
|
+
}))
|
|
2377
|
+
};
|
|
2378
|
+
}),
|
|
2379
|
+
...opts.safetyIdentifier !== void 0 && {
|
|
2380
|
+
safety_identifier: opts.safetyIdentifier
|
|
2381
|
+
},
|
|
2382
|
+
...opts.promptCacheKey !== void 0 && {
|
|
2383
|
+
prompt_cache_key: opts.promptCacheKey
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
} else {
|
|
2387
|
+
body = {
|
|
2388
|
+
model: config.bodyModel ?? modelId,
|
|
2389
|
+
state: options.state,
|
|
2390
|
+
questions: Object.fromEntries(
|
|
2391
|
+
questions.map(([id, question]) => [
|
|
2392
|
+
id,
|
|
2393
|
+
{
|
|
2394
|
+
...question,
|
|
2395
|
+
type: question.type === "boolean" ? "noul" : question.type
|
|
2396
|
+
}
|
|
2397
|
+
])
|
|
2398
|
+
)
|
|
2399
|
+
};
|
|
2400
|
+
if (provider === "typesafe") {
|
|
2401
|
+
if (opts.longContext)
|
|
2402
|
+
headers["TypeSafe-Danger-Zone-Long-Context"] = "true";
|
|
2403
|
+
if (images.length) {
|
|
2404
|
+
const refs = images.map(() => randomUUID());
|
|
2405
|
+
body.state = { state: options.state, images: refs };
|
|
2406
|
+
body.assets = Object.fromEntries(
|
|
2407
|
+
images.map((image, index) => [
|
|
2408
|
+
refs[index],
|
|
2409
|
+
{
|
|
2410
|
+
type: "image",
|
|
2411
|
+
data_base64: image.base64,
|
|
2412
|
+
format: image.mediaType === "image/png" ? "png" : "jpeg"
|
|
2413
|
+
}
|
|
2414
|
+
])
|
|
2415
|
+
);
|
|
2416
|
+
headers["TypeSafe-Danger-Zone-Images"] = "true";
|
|
2417
|
+
}
|
|
2418
|
+
} else if (provider === "cloudflare" && images.length) {
|
|
2419
|
+
body.images = images.map((image) => ({
|
|
2420
|
+
content_type: image.mediaType,
|
|
2421
|
+
base64: image.base64
|
|
2422
|
+
}));
|
|
2423
|
+
} else if (provider === "openrouter") {
|
|
2424
|
+
if (opts.sessionId !== void 0) body.session_id = opts.sessionId;
|
|
2425
|
+
if (isDecisionRecord(opts.provider))
|
|
2426
|
+
body.provider = {
|
|
2427
|
+
...opts.provider.order !== void 0 && {
|
|
2428
|
+
order: opts.provider.order
|
|
2429
|
+
},
|
|
2430
|
+
...opts.provider.allowFallbacks !== void 0 && {
|
|
2431
|
+
allow_fallbacks: opts.provider.allowFallbacks
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
const base = config.baseUrl ?? config.defaultBaseUrl;
|
|
2437
|
+
let url;
|
|
2438
|
+
try {
|
|
2439
|
+
url = new URL(`${base.replace(/\/+$/, "")}${config.path}`);
|
|
2440
|
+
} catch {
|
|
2441
|
+
decisionInputError("Invalid Decisions base URL");
|
|
2442
|
+
}
|
|
2443
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
|
|
2444
|
+
decisionInputError(
|
|
2445
|
+
"Decisions base URL must be an HTTP(S) API root without credentials, query, or fragment"
|
|
2446
|
+
);
|
|
2447
|
+
const response = await postDecision(
|
|
2448
|
+
url.href,
|
|
2449
|
+
headers,
|
|
2450
|
+
body,
|
|
2451
|
+
options,
|
|
2452
|
+
config.fetch ?? globalThis.fetch,
|
|
2453
|
+
(provider === "cloudflare" ? 13 : 32) * 1024 * 1024
|
|
2454
|
+
);
|
|
2455
|
+
return parseDecisionResponse(
|
|
2456
|
+
response.value,
|
|
2457
|
+
provider,
|
|
2458
|
+
options.questions,
|
|
2459
|
+
response.status,
|
|
2460
|
+
response.requestId
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// src/providers/openrouter/decisions.ts
|
|
2467
|
+
function openrouterDecision(modelId = "typesafe/jev-1.13", options = {}) {
|
|
2468
|
+
return nativeDecisionModel(modelId, {
|
|
2469
|
+
...options,
|
|
2470
|
+
headers: {
|
|
2471
|
+
...options.headers,
|
|
2472
|
+
...options.siteUrl && { "HTTP-Referer": options.siteUrl },
|
|
2473
|
+
...options.appName && { "X-Title": options.appName }
|
|
2474
|
+
},
|
|
2475
|
+
provider: "openrouter",
|
|
2476
|
+
keyEnv: "OPENROUTER_API_KEY",
|
|
2477
|
+
defaultBaseUrl: "https://openrouter.ai/api/alpha",
|
|
2478
|
+
path: "/decisions"
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
|
|
1646
2482
|
// src/providers/openrouter/index.ts
|
|
1647
2483
|
var DEFAULT_CAPABILITIES = {
|
|
1648
2484
|
vision: true,
|
|
@@ -1684,6 +2520,6 @@ function createOpenRouter(config = {}) {
|
|
|
1684
2520
|
}
|
|
1685
2521
|
var createOpenRouterProvider = createOpenRouter;
|
|
1686
2522
|
|
|
1687
|
-
export { createOpenRouter, openrouter as createOpenRouterModel, createOpenRouterProvider, fetchOpenRouterModels, openrouter, searchOpenRouterModels };
|
|
2523
|
+
export { createOpenRouter, openrouter as createOpenRouterModel, createOpenRouterProvider, fetchOpenRouterModels, openrouter, openrouterDecision, searchOpenRouterModels };
|
|
1688
2524
|
//# sourceMappingURL=index.mjs.map
|
|
1689
2525
|
//# sourceMappingURL=index.mjs.map
|