veryfront 0.1.283 → 0.1.285
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/esm/deno.d.ts +0 -8
- package/esm/deno.js +8 -11
- package/esm/src/agent/agent-service.d.ts +28 -6
- package/esm/src/agent/agent-service.d.ts.map +1 -1
- package/esm/src/agent/agent-service.js +23 -1
- package/esm/src/agent/index.d.ts +1 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/embedding/resolve.d.ts.map +1 -1
- package/esm/src/embedding/resolve.js +15 -2
- package/esm/src/embedding/veryfront-cloud/provider.d.ts.map +1 -1
- package/esm/src/embedding/veryfront-cloud/provider.js +2 -7
- package/esm/src/extensions/contracts.js +3 -1
- package/esm/src/extensions/interfaces/ai-provider.d.ts +50 -0
- package/esm/src/extensions/interfaces/ai-provider.d.ts.map +1 -0
- package/esm/src/extensions/interfaces/ai-provider.js +13 -0
- package/esm/src/extensions/interfaces/code-parser.d.ts +11 -0
- package/esm/src/extensions/interfaces/code-parser.d.ts.map +1 -1
- package/esm/src/extensions/interfaces/css-processor.d.ts +41 -22
- package/esm/src/extensions/interfaces/css-processor.d.ts.map +1 -1
- package/esm/src/extensions/interfaces/css-processor.js +10 -1
- package/esm/src/extensions/interfaces/index.d.ts +5 -4
- package/esm/src/extensions/interfaces/index.d.ts.map +1 -1
- package/esm/src/extensions/interfaces/index.js +1 -0
- package/esm/src/extensions/interfaces/schema-validator.d.ts +84 -5
- package/esm/src/extensions/interfaces/schema-validator.d.ts.map +1 -1
- package/esm/src/extensions/interfaces/schema-validator.js +5 -0
- package/esm/src/extensions/loader.d.ts +7 -0
- package/esm/src/extensions/loader.d.ts.map +1 -1
- package/esm/src/extensions/loader.js +12 -0
- package/esm/src/extensions/orchestrate.d.ts +2 -0
- package/esm/src/extensions/orchestrate.d.ts.map +1 -1
- package/esm/src/extensions/orchestrate.js +3 -0
- package/esm/src/extensions/recommendations.d.ts.map +1 -1
- package/esm/src/extensions/recommendations.js +4 -1
- package/esm/src/extensions/registries/ai-provider-registry.d.ts +11 -0
- package/esm/src/extensions/registries/ai-provider-registry.d.ts.map +1 -0
- package/esm/src/extensions/registries/ai-provider-registry.js +40 -0
- package/esm/src/html/styles-builder/plugin-loader.d.ts.map +1 -1
- package/esm/src/html/styles-builder/plugin-loader.js +4 -16
- package/esm/src/html/styles-builder/tailwind-compiler-cache.d.ts +8 -2
- package/esm/src/html/styles-builder/tailwind-compiler-cache.d.ts.map +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler-cache.js +20 -3
- package/esm/src/provider/model-registry.d.ts.map +1 -1
- package/esm/src/provider/model-registry.js +33 -6
- package/esm/src/provider/runtime-loader/provider-embedding-responses.d.ts +1 -0
- package/esm/src/provider/runtime-loader/provider-embedding-responses.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader/provider-embedding-responses.js +1 -1
- package/esm/src/provider/runtime-loader/provider-http.d.ts +9 -0
- package/esm/src/provider/runtime-loader/provider-http.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader/provider-http.js +2 -2
- package/esm/src/provider/runtime-loader.d.ts +120 -9
- package/esm/src/provider/runtime-loader.d.ts.map +1 -1
- package/esm/src/provider/runtime-loader.js +13 -943
- package/esm/src/provider/veryfront-cloud/provider.d.ts.map +1 -1
- package/esm/src/provider/veryfront-cloud/provider.js +30 -15
- package/esm/src/sandbox/index.d.ts +1 -1
- package/esm/src/sandbox/index.d.ts.map +1 -1
- package/esm/src/sandbox/index.js +1 -1
- package/esm/src/sandbox/lazy-sandbox.d.ts +3 -0
- package/esm/src/sandbox/lazy-sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/lazy-sandbox.js +22 -1
- package/esm/src/schemas/define.d.ts +31 -0
- package/esm/src/schemas/define.d.ts.map +1 -0
- package/esm/src/schemas/define.js +42 -0
- package/esm/src/schemas/index.d.ts +7 -2
- package/esm/src/schemas/index.d.ts.map +1 -1
- package/esm/src/schemas/index.js +10 -2
- package/esm/src/schemas/zod-adapter.d.ts +25 -0
- package/esm/src/schemas/zod-adapter.d.ts.map +1 -0
- package/esm/src/schemas/zod-adapter.js +120 -0
- package/esm/src/server/bootstrap.d.ts.map +1 -1
- package/esm/src/server/bootstrap.js +5 -0
- package/esm/src/transforms/plugins/babel-node-positions.d.ts +6 -7
- package/esm/src/transforms/plugins/babel-node-positions.d.ts.map +1 -1
- package/esm/src/transforms/plugins/babel-node-positions.js +10 -123
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -6
- package/src/deno.js +8 -11
- package/src/src/agent/agent-service.ts +91 -7
- package/src/src/agent/index.ts +4 -0
- package/src/src/embedding/resolve.ts +18 -7
- package/src/src/embedding/veryfront-cloud/provider.ts +4 -10
- package/src/src/extensions/contracts.ts +3 -3
- package/src/src/extensions/interfaces/ai-provider.ts +54 -0
- package/src/src/extensions/interfaces/code-parser.ts +12 -0
- package/src/src/extensions/interfaces/css-processor.ts +43 -22
- package/src/src/extensions/interfaces/index.ts +15 -11
- package/src/src/extensions/interfaces/schema-validator.ts +112 -5
- package/src/src/extensions/loader.ts +14 -0
- package/src/src/extensions/orchestrate.ts +5 -0
- package/src/src/extensions/recommendations.ts +4 -1
- package/src/src/extensions/registries/ai-provider-registry.ts +53 -0
- package/src/src/html/styles-builder/plugin-loader.ts +4 -16
- package/src/src/html/styles-builder/tailwind-compiler-cache.ts +27 -6
- package/src/src/provider/model-registry.ts +34 -15
- package/src/src/provider/runtime-loader/provider-embedding-responses.ts +1 -1
- package/src/src/provider/runtime-loader/provider-http.ts +2 -2
- package/src/src/provider/runtime-loader.ts +41 -1189
- package/src/src/provider/veryfront-cloud/provider.ts +35 -19
- package/src/src/sandbox/index.ts +5 -1
- package/src/src/sandbox/lazy-sandbox.ts +25 -1
- package/src/src/schemas/define.ts +48 -0
- package/src/src/schemas/index.ts +13 -2
- package/src/src/schemas/zod-adapter.ts +180 -0
- package/src/src/server/bootstrap.ts +5 -0
- package/src/src/transforms/plugins/babel-node-positions.ts +11 -173
- package/src/src/utils/version-constant.ts +1 -1
- package/esm/src/extensions/interfaces/ai-model-provider.d.ts +0 -94
- package/esm/src/extensions/interfaces/ai-model-provider.d.ts.map +0 -1
- package/esm/src/extensions/interfaces/ai-model-provider.js +0 -8
- package/src/src/extensions/interfaces/ai-model-provider.ts +0 -100
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { getAnthropicMessagesUrl, getGoogleEmbeddingUrl, getGoogleGenerateContentUrl, getGoogleStreamGenerateContentUrl,
|
|
2
|
-
import { extractGoogleEmbedding, extractGoogleUsageTokens,
|
|
3
|
-
import { normalizeAnthropicFinishReason, normalizeGoogleFinishReason,
|
|
4
|
-
import { createAnthropicRequestInit, createGoogleRequestInit,
|
|
1
|
+
import { getAnthropicMessagesUrl, getGoogleEmbeddingUrl, getGoogleGenerateContentUrl, getGoogleStreamGenerateContentUrl, } from "./runtime-loader/provider-endpoints.js";
|
|
2
|
+
import { extractGoogleEmbedding, extractGoogleUsageTokens, isNumberArray, } from "./runtime-loader/provider-embedding-responses.js";
|
|
3
|
+
import { normalizeAnthropicFinishReason, normalizeGoogleFinishReason, } from "./runtime-loader/provider-finish-reasons.js";
|
|
4
|
+
import { createAnthropicRequestInit, createGoogleRequestInit, } from "./runtime-loader/provider-request-init.js";
|
|
5
5
|
import { parseSseChunk } from "./runtime-loader/provider-sse.js";
|
|
6
|
-
import { extractAnthropicUsage, extractGoogleUsage,
|
|
7
|
-
import { requestJson, requestStream } from "./runtime-loader/provider-http.js";
|
|
6
|
+
import { extractAnthropicUsage, extractGoogleUsage, mergeUsage, } from "./runtime-loader/provider-usage.js";
|
|
7
|
+
import { buildProviderError, parseRetryAfterMs, requestJson, requestStream, } from "./runtime-loader/provider-http.js";
|
|
8
8
|
import { readRecord } from "./runtime-loader/provider-records.js";
|
|
9
9
|
import { TOOL_INPUT_PENDING_THRESHOLD_MS, withToolInputStatusTransitions, } from "./runtime-loader/tool-input-status.js";
|
|
10
10
|
export { ProviderError, ProviderOverloadedError, ProviderQuotaError, ProviderRateLimitError, ProviderRequestError, } from "./runtime-loader/provider-http.js";
|
|
11
11
|
export { TOOL_INPUT_PENDING_THRESHOLD_MS, withToolInputStatusTransitions };
|
|
12
|
-
|
|
12
|
+
export { buildProviderError, isNumberArray, mergeUsage, parseRetryAfterMs, readRecord, requestJson, requestStream, };
|
|
13
|
+
export function createWarningCollector() {
|
|
13
14
|
const list = [];
|
|
14
15
|
return {
|
|
15
16
|
push(warning) {
|
|
@@ -20,13 +21,13 @@ function createWarningCollector() {
|
|
|
20
21
|
},
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
|
-
function stringifyJsonValue(value) {
|
|
24
|
+
export function stringifyJsonValue(value) {
|
|
24
25
|
if (typeof value === "string") {
|
|
25
26
|
return value;
|
|
26
27
|
}
|
|
27
28
|
return JSON.stringify(value);
|
|
28
29
|
}
|
|
29
|
-
function readTextParts(parts) {
|
|
30
|
+
export function readTextParts(parts) {
|
|
30
31
|
let text = "";
|
|
31
32
|
for (const part of parts) {
|
|
32
33
|
if (part.type === "text" && typeof part.text === "string") {
|
|
@@ -35,7 +36,7 @@ function readTextParts(parts) {
|
|
|
35
36
|
}
|
|
36
37
|
return text;
|
|
37
38
|
}
|
|
38
|
-
function toOpenAICompatibleMessages(prompt) {
|
|
39
|
+
export function toOpenAICompatibleMessages(prompt) {
|
|
39
40
|
const messages = [];
|
|
40
41
|
for (const message of prompt) {
|
|
41
42
|
switch (message.role) {
|
|
@@ -87,7 +88,7 @@ function toOpenAICompatibleMessages(prompt) {
|
|
|
87
88
|
}
|
|
88
89
|
return messages;
|
|
89
90
|
}
|
|
90
|
-
function toOpenAICompatibleTools(tools) {
|
|
91
|
+
export function toOpenAICompatibleTools(tools) {
|
|
91
92
|
if (!tools) {
|
|
92
93
|
return undefined;
|
|
93
94
|
}
|
|
@@ -103,7 +104,7 @@ function toOpenAICompatibleTools(tools) {
|
|
|
103
104
|
: []);
|
|
104
105
|
return functions.length > 0 ? functions : undefined;
|
|
105
106
|
}
|
|
106
|
-
function readProviderOptions(providerOptions, ...providerNames) {
|
|
107
|
+
export function readProviderOptions(providerOptions, ...providerNames) {
|
|
107
108
|
if (!providerOptions) {
|
|
108
109
|
return {};
|
|
109
110
|
}
|
|
@@ -826,198 +827,6 @@ async function* streamAnthropicCompatibleParts(stream) {
|
|
|
826
827
|
...(usage ? { usage } : {}),
|
|
827
828
|
};
|
|
828
829
|
}
|
|
829
|
-
function extractOpenAIContentText(content) {
|
|
830
|
-
if (typeof content === "string") {
|
|
831
|
-
return content;
|
|
832
|
-
}
|
|
833
|
-
if (!Array.isArray(content)) {
|
|
834
|
-
return "";
|
|
835
|
-
}
|
|
836
|
-
let text = "";
|
|
837
|
-
for (const part of content) {
|
|
838
|
-
const record = readRecord(part);
|
|
839
|
-
const type = record?.type;
|
|
840
|
-
if (type === "text" && typeof record?.text === "string") {
|
|
841
|
-
text += record.text;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
return text;
|
|
845
|
-
}
|
|
846
|
-
function extractOpenAIToolCalls(message) {
|
|
847
|
-
const toolCalls = message.tool_calls;
|
|
848
|
-
if (!Array.isArray(toolCalls)) {
|
|
849
|
-
return [];
|
|
850
|
-
}
|
|
851
|
-
const normalized = [];
|
|
852
|
-
for (const entry of toolCalls) {
|
|
853
|
-
const record = readRecord(entry);
|
|
854
|
-
const id = typeof record?.id === "string" ? record.id : undefined;
|
|
855
|
-
const fn = readRecord(record?.function);
|
|
856
|
-
const name = typeof fn?.name === "string" ? fn.name : undefined;
|
|
857
|
-
const argumentsText = typeof fn?.arguments === "string" ? fn.arguments : undefined;
|
|
858
|
-
if (!id || !name || argumentsText === undefined) {
|
|
859
|
-
continue;
|
|
860
|
-
}
|
|
861
|
-
normalized.push({
|
|
862
|
-
toolCallId: id,
|
|
863
|
-
toolName: name,
|
|
864
|
-
input: argumentsText,
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
return normalized;
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* OpenAI reasoning models (o1 / o3 / o4 family) use the completion path but
|
|
871
|
-
* have different constraints than chat models: sampling params are rejected,
|
|
872
|
-
* and they accept a `reasoning_effort` field. We detect them by model id
|
|
873
|
-
* prefix so callers don't have to configure it per runtime.
|
|
874
|
-
*/
|
|
875
|
-
function isOpenAIReasoningModel(modelId) {
|
|
876
|
-
return /^o[134](-|$)/.test(modelId);
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Detect native OpenAI models (gpt-*, o-series, chatgpt-*) vs third-party
|
|
880
|
-
* OpenAI-compatible providers (Kimi, etc.). Native OpenAI models require
|
|
881
|
-
* `max_completion_tokens` (the old `max_tokens` is rejected by newer models
|
|
882
|
-
* like gpt-5.2), while third-party providers still expect `max_tokens`.
|
|
883
|
-
*/
|
|
884
|
-
function isNativeOpenAIModel(modelId) {
|
|
885
|
-
return /^(gpt-|o[134](-|$)|chatgpt-)/.test(modelId);
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Kimi K2.5 fixes sampling parameters (temperature, top_p, presence_penalty,
|
|
889
|
-
* frequency_penalty) to predetermined values and rejects any other values.
|
|
890
|
-
* See https://platform.moonshot.cn/docs/guide/kimi-k2-5-quickstart
|
|
891
|
-
*/
|
|
892
|
-
function isFixedSamplingModel(modelId) {
|
|
893
|
-
return /^kimi-k2\.5/.test(modelId);
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Map the unified reasoning effort to OpenAI's `reasoning_effort` enum.
|
|
897
|
-
* OpenAI doesn't accept "max" — we collapse it to "high".
|
|
898
|
-
*/
|
|
899
|
-
function resolveOpenAIReasoningEffort(option) {
|
|
900
|
-
if (!option || option.enabled !== true) {
|
|
901
|
-
return undefined;
|
|
902
|
-
}
|
|
903
|
-
switch (option.effort) {
|
|
904
|
-
case "low":
|
|
905
|
-
return "low";
|
|
906
|
-
case "high":
|
|
907
|
-
case "max":
|
|
908
|
-
return "high";
|
|
909
|
-
case "medium":
|
|
910
|
-
default:
|
|
911
|
-
return "medium";
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
function buildOpenAIChatRequest(modelId, providerName, options, stream, warnings) {
|
|
915
|
-
const isReasoningModel = isOpenAIReasoningModel(modelId);
|
|
916
|
-
const reasoningEffort = resolveOpenAIReasoningEffort(options.reasoning);
|
|
917
|
-
const reasoningEnabled = isReasoningModel || reasoningEffort !== undefined;
|
|
918
|
-
const fixedSampling = isFixedSamplingModel(modelId);
|
|
919
|
-
const dropSamplingParams = reasoningEnabled || fixedSampling;
|
|
920
|
-
// OpenAI Chat Completions has no top_k surface (it's exposed only on the
|
|
921
|
-
// Responses API for some reasoning models). Quietly accepting it would
|
|
922
|
-
// mislead callers into thinking it took effect.
|
|
923
|
-
if (options.topK !== undefined) {
|
|
924
|
-
warnings.push({
|
|
925
|
-
type: "unsupported-setting",
|
|
926
|
-
provider: "openai",
|
|
927
|
-
setting: "topK",
|
|
928
|
-
details: "OpenAI Chat Completions does not expose top_k; the value was dropped.",
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
// Reasoning models (o1 / o3 / o4) and models with fixed sampling params
|
|
932
|
-
// (Kimi K2.5) reject sampling params outright. Emit warnings at build time
|
|
933
|
-
// so callers see *why* the value didn't apply rather than a 400 from the API.
|
|
934
|
-
if (dropSamplingParams) {
|
|
935
|
-
const dropped = [
|
|
936
|
-
["temperature", "temperature"],
|
|
937
|
-
["topP", "top_p"],
|
|
938
|
-
["presencePenalty", "presence_penalty"],
|
|
939
|
-
["frequencyPenalty", "frequency_penalty"],
|
|
940
|
-
];
|
|
941
|
-
for (const [key, openaiName] of dropped) {
|
|
942
|
-
if (options[key] !== undefined) {
|
|
943
|
-
warnings.push({
|
|
944
|
-
type: "unsupported-setting",
|
|
945
|
-
provider: "openai",
|
|
946
|
-
setting: key,
|
|
947
|
-
details: fixedSampling
|
|
948
|
-
? `Dropped because this model uses fixed sampling parameters.`
|
|
949
|
-
: `Dropped because OpenAI reasoning models reject ${openaiName}. Reasoning was active for this request.`,
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
const body = {
|
|
955
|
-
model: modelId,
|
|
956
|
-
messages: toOpenAICompatibleMessages(options.prompt),
|
|
957
|
-
...(stream ? { stream: true, stream_options: { include_usage: true } } : {}),
|
|
958
|
-
...(options.maxOutputTokens !== undefined
|
|
959
|
-
? isNativeOpenAIModel(modelId)
|
|
960
|
-
? { max_completion_tokens: options.maxOutputTokens }
|
|
961
|
-
: { max_tokens: options.maxOutputTokens }
|
|
962
|
-
: {}),
|
|
963
|
-
// Reasoning models and fixed-sampling models reject temperature / top_p /
|
|
964
|
-
// frequency / presence. Drop them rather than letting the API bounce.
|
|
965
|
-
...(!dropSamplingParams && options.temperature !== undefined
|
|
966
|
-
? { temperature: options.temperature }
|
|
967
|
-
: {}),
|
|
968
|
-
...(!dropSamplingParams && options.topP !== undefined ? { top_p: options.topP } : {}),
|
|
969
|
-
...(options.stopSequences && options.stopSequences.length > 0
|
|
970
|
-
? { stop: options.stopSequences }
|
|
971
|
-
: {}),
|
|
972
|
-
...(toOpenAICompatibleTools(options.tools)
|
|
973
|
-
? { tools: toOpenAICompatibleTools(options.tools) }
|
|
974
|
-
: {}),
|
|
975
|
-
...(options.toolChoice !== undefined ? { tool_choice: options.toolChoice } : {}),
|
|
976
|
-
...(options.seed !== undefined ? { seed: options.seed } : {}),
|
|
977
|
-
...(!dropSamplingParams && options.presencePenalty !== undefined
|
|
978
|
-
? { presence_penalty: options.presencePenalty }
|
|
979
|
-
: {}),
|
|
980
|
-
...(!dropSamplingParams && options.frequencyPenalty !== undefined
|
|
981
|
-
? { frequency_penalty: options.frequencyPenalty }
|
|
982
|
-
: {}),
|
|
983
|
-
...(reasoningEffort !== undefined ? { reasoning_effort: reasoningEffort } : {}),
|
|
984
|
-
...(typeof options.userId === "string" && options.userId.length > 0
|
|
985
|
-
? { user: options.userId }
|
|
986
|
-
: {}),
|
|
987
|
-
...(options.serviceTier !== undefined ? { service_tier: options.serviceTier } : {}),
|
|
988
|
-
...(options.parallelToolCalls !== undefined
|
|
989
|
-
? { parallel_tool_calls: options.parallelToolCalls }
|
|
990
|
-
: {}),
|
|
991
|
-
...(options.responseFormat && options.responseFormat.type !== "text"
|
|
992
|
-
? {
|
|
993
|
-
response_format: options.responseFormat.type === "json" ? { type: "json_object" } : {
|
|
994
|
-
type: "json_schema",
|
|
995
|
-
json_schema: {
|
|
996
|
-
name: options.responseFormat.name,
|
|
997
|
-
...(typeof options.responseFormat.description === "string"
|
|
998
|
-
? { description: options.responseFormat.description }
|
|
999
|
-
: {}),
|
|
1000
|
-
schema: unwrapToolInputSchema(options.responseFormat.schema),
|
|
1001
|
-
...(options.responseFormat.strict !== undefined
|
|
1002
|
-
? { strict: options.responseFormat.strict }
|
|
1003
|
-
: {}),
|
|
1004
|
-
},
|
|
1005
|
-
},
|
|
1006
|
-
}
|
|
1007
|
-
: {}),
|
|
1008
|
-
};
|
|
1009
|
-
const providerOpts = readProviderOptions(options.providerOptions, "openai", providerName);
|
|
1010
|
-
// Normalize max_tokens → max_completion_tokens for native OpenAI models.
|
|
1011
|
-
// Provider options can re-introduce max_tokens which newer models reject.
|
|
1012
|
-
if (isNativeOpenAIModel(modelId) && "max_tokens" in providerOpts) {
|
|
1013
|
-
if (!("max_completion_tokens" in providerOpts)) {
|
|
1014
|
-
providerOpts.max_completion_tokens = providerOpts.max_tokens;
|
|
1015
|
-
}
|
|
1016
|
-
delete providerOpts.max_tokens;
|
|
1017
|
-
}
|
|
1018
|
-
Object.assign(body, providerOpts);
|
|
1019
|
-
return body;
|
|
1020
|
-
}
|
|
1021
830
|
function toGoogleContents(prompt) {
|
|
1022
831
|
const systemParts = [];
|
|
1023
832
|
const contents = [];
|
|
@@ -1433,706 +1242,6 @@ async function* streamGoogleCompatibleParts(stream) {
|
|
|
1433
1242
|
...(usage ? { usage } : {}),
|
|
1434
1243
|
};
|
|
1435
1244
|
}
|
|
1436
|
-
function extractFirstChoice(payload) {
|
|
1437
|
-
const record = readRecord(payload);
|
|
1438
|
-
const choices = record?.choices;
|
|
1439
|
-
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1440
|
-
return undefined;
|
|
1441
|
-
}
|
|
1442
|
-
const first = readRecord(choices[0]);
|
|
1443
|
-
if (!first) {
|
|
1444
|
-
return undefined;
|
|
1445
|
-
}
|
|
1446
|
-
return first;
|
|
1447
|
-
}
|
|
1448
|
-
function buildOpenAIGenerateResult(payload) {
|
|
1449
|
-
const choice = extractFirstChoice(payload);
|
|
1450
|
-
const message = readRecord(choice?.message);
|
|
1451
|
-
const text = extractOpenAIContentText(message?.content);
|
|
1452
|
-
const toolCalls = message ? extractOpenAIToolCalls(message) : [];
|
|
1453
|
-
return {
|
|
1454
|
-
content: [
|
|
1455
|
-
...(text.length > 0 ? [{ type: "text", text }] : []),
|
|
1456
|
-
...toolCalls.map((toolCall) => ({
|
|
1457
|
-
type: "tool-call",
|
|
1458
|
-
toolCallId: toolCall.toolCallId,
|
|
1459
|
-
toolName: toolCall.toolName,
|
|
1460
|
-
input: toolCall.input,
|
|
1461
|
-
})),
|
|
1462
|
-
],
|
|
1463
|
-
finishReason: normalizeOpenAIFinishReason(choice?.finish_reason),
|
|
1464
|
-
usage: extractOpenAIUsage(payload),
|
|
1465
|
-
};
|
|
1466
|
-
}
|
|
1467
|
-
async function* streamOpenAICompatibleParts(stream) {
|
|
1468
|
-
const decoder = new TextDecoder();
|
|
1469
|
-
let buffer = "";
|
|
1470
|
-
const toolCalls = new Map();
|
|
1471
|
-
let reasoningId = null;
|
|
1472
|
-
let reasoningIndex = 0;
|
|
1473
|
-
let finishReason = null;
|
|
1474
|
-
let usage;
|
|
1475
|
-
for await (const chunk of stream) {
|
|
1476
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
1477
|
-
const parsed = parseSseChunk(buffer);
|
|
1478
|
-
buffer = parsed.remainder;
|
|
1479
|
-
for (const event of parsed.events) {
|
|
1480
|
-
if (event === "[DONE]") {
|
|
1481
|
-
continue;
|
|
1482
|
-
}
|
|
1483
|
-
const record = readRecord(event);
|
|
1484
|
-
usage = extractOpenAIUsage(record) ?? usage;
|
|
1485
|
-
const choice = extractFirstChoice(record);
|
|
1486
|
-
if (!choice) {
|
|
1487
|
-
continue;
|
|
1488
|
-
}
|
|
1489
|
-
const delta = readRecord(choice.delta);
|
|
1490
|
-
if (typeof delta?.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
|
1491
|
-
if (!reasoningId) {
|
|
1492
|
-
reasoningId = `reasoning-${reasoningIndex++}`;
|
|
1493
|
-
yield {
|
|
1494
|
-
type: "reasoning-start",
|
|
1495
|
-
id: reasoningId,
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
yield {
|
|
1499
|
-
type: "reasoning-delta",
|
|
1500
|
-
id: reasoningId,
|
|
1501
|
-
delta: delta.reasoning_content,
|
|
1502
|
-
};
|
|
1503
|
-
}
|
|
1504
|
-
const textDelta = extractOpenAIContentText(delta?.content);
|
|
1505
|
-
if (textDelta.length > 0) {
|
|
1506
|
-
if (reasoningId) {
|
|
1507
|
-
yield {
|
|
1508
|
-
type: "reasoning-end",
|
|
1509
|
-
id: reasoningId,
|
|
1510
|
-
};
|
|
1511
|
-
reasoningId = null;
|
|
1512
|
-
}
|
|
1513
|
-
yield { type: "text-delta", delta: textDelta };
|
|
1514
|
-
}
|
|
1515
|
-
const rawToolCalls = Array.isArray(delta?.tool_calls) ? delta.tool_calls : [];
|
|
1516
|
-
for (const rawToolCall of rawToolCalls) {
|
|
1517
|
-
if (reasoningId) {
|
|
1518
|
-
yield {
|
|
1519
|
-
type: "reasoning-end",
|
|
1520
|
-
id: reasoningId,
|
|
1521
|
-
};
|
|
1522
|
-
reasoningId = null;
|
|
1523
|
-
}
|
|
1524
|
-
const toolCallRecord = readRecord(rawToolCall);
|
|
1525
|
-
const index = typeof toolCallRecord?.index === "number" ? toolCallRecord.index : 0;
|
|
1526
|
-
const current = toolCalls.get(index) ?? {
|
|
1527
|
-
id: typeof toolCallRecord?.id === "string" ? toolCallRecord.id : `tool-${index}`,
|
|
1528
|
-
name: "",
|
|
1529
|
-
arguments: "",
|
|
1530
|
-
started: false,
|
|
1531
|
-
};
|
|
1532
|
-
if (typeof toolCallRecord?.id === "string") {
|
|
1533
|
-
current.id = toolCallRecord.id;
|
|
1534
|
-
}
|
|
1535
|
-
const fn = readRecord(toolCallRecord?.function);
|
|
1536
|
-
if (typeof fn?.name === "string") {
|
|
1537
|
-
current.name = fn.name;
|
|
1538
|
-
}
|
|
1539
|
-
if (!current.started && current.name.length > 0) {
|
|
1540
|
-
current.started = true;
|
|
1541
|
-
yield {
|
|
1542
|
-
type: "tool-input-start",
|
|
1543
|
-
id: current.id,
|
|
1544
|
-
toolName: current.name,
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
if (typeof fn?.arguments === "string" && fn.arguments.length > 0) {
|
|
1548
|
-
current.arguments += fn.arguments;
|
|
1549
|
-
yield {
|
|
1550
|
-
type: "tool-input-delta",
|
|
1551
|
-
id: current.id,
|
|
1552
|
-
delta: fn.arguments,
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
toolCalls.set(index, current);
|
|
1556
|
-
}
|
|
1557
|
-
const normalizedFinishReason = normalizeOpenAIFinishReason(choice.finish_reason);
|
|
1558
|
-
if (normalizedFinishReason) {
|
|
1559
|
-
finishReason = normalizedFinishReason;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
if (buffer.trim().length > 0) {
|
|
1564
|
-
const parsed = parseSseChunk(`${buffer}\n\n`);
|
|
1565
|
-
for (const event of parsed.events) {
|
|
1566
|
-
if (event === "[DONE]") {
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
|
-
const record = readRecord(event);
|
|
1570
|
-
usage = extractOpenAIUsage(record) ?? usage;
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
if (reasoningId) {
|
|
1574
|
-
yield {
|
|
1575
|
-
type: "reasoning-end",
|
|
1576
|
-
id: reasoningId,
|
|
1577
|
-
};
|
|
1578
|
-
}
|
|
1579
|
-
if (finishReason &&
|
|
1580
|
-
typeof finishReason === "object" &&
|
|
1581
|
-
finishReason.unified === "tool-calls") {
|
|
1582
|
-
for (const toolCall of toolCalls.values()) {
|
|
1583
|
-
yield {
|
|
1584
|
-
type: "tool-call",
|
|
1585
|
-
toolCallId: toolCall.id,
|
|
1586
|
-
toolName: toolCall.name,
|
|
1587
|
-
input: toolCall.arguments,
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
yield {
|
|
1592
|
-
type: "finish",
|
|
1593
|
-
finishReason,
|
|
1594
|
-
...(usage ? { usage } : {}),
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
1597
|
-
export function createOpenAIModelRuntime(config, modelId) {
|
|
1598
|
-
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
1599
|
-
return {
|
|
1600
|
-
provider: config.name ?? "openai",
|
|
1601
|
-
modelId,
|
|
1602
|
-
specificationVersion: "v3",
|
|
1603
|
-
supportedUrls: {},
|
|
1604
|
-
doGenerate(optionsForRuntime) {
|
|
1605
|
-
const options = optionsForRuntime;
|
|
1606
|
-
const url = getOpenAIChatCompletionsUrl(config.baseURL);
|
|
1607
|
-
const warnings = createWarningCollector();
|
|
1608
|
-
const body = buildOpenAIChatRequest(modelId, config.name ?? "openai", options, false, warnings);
|
|
1609
|
-
return requestJson({
|
|
1610
|
-
url,
|
|
1611
|
-
fetchImpl,
|
|
1612
|
-
providerLabel: config.name ?? "openai",
|
|
1613
|
-
providerKind: "openai",
|
|
1614
|
-
init: createOpenAIRequestInit({
|
|
1615
|
-
apiKey: config.apiKey,
|
|
1616
|
-
extraHeaders: options.headers,
|
|
1617
|
-
body: JSON.stringify(body),
|
|
1618
|
-
signal: options.abortSignal,
|
|
1619
|
-
}),
|
|
1620
|
-
}).then((payload) => {
|
|
1621
|
-
const drained = warnings.drain();
|
|
1622
|
-
return {
|
|
1623
|
-
...buildOpenAIGenerateResult(payload),
|
|
1624
|
-
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
1625
|
-
};
|
|
1626
|
-
});
|
|
1627
|
-
},
|
|
1628
|
-
doStream(optionsForRuntime) {
|
|
1629
|
-
const options = optionsForRuntime;
|
|
1630
|
-
const url = getOpenAIChatCompletionsUrl(config.baseURL);
|
|
1631
|
-
const warnings = createWarningCollector();
|
|
1632
|
-
const body = buildOpenAIChatRequest(modelId, config.name ?? "openai", options, true, warnings);
|
|
1633
|
-
return requestStream({
|
|
1634
|
-
url,
|
|
1635
|
-
fetchImpl,
|
|
1636
|
-
providerLabel: config.name ?? "openai",
|
|
1637
|
-
providerKind: "openai",
|
|
1638
|
-
init: createOpenAIRequestInit({
|
|
1639
|
-
apiKey: config.apiKey,
|
|
1640
|
-
extraHeaders: options.headers,
|
|
1641
|
-
body: JSON.stringify(body),
|
|
1642
|
-
signal: options.abortSignal,
|
|
1643
|
-
}),
|
|
1644
|
-
}).then((responseStream) => {
|
|
1645
|
-
const drained = warnings.drain();
|
|
1646
|
-
return {
|
|
1647
|
-
stream: ReadableStream.from(withToolInputStatusTransitions(streamOpenAICompatibleParts(responseStream))),
|
|
1648
|
-
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
1649
|
-
};
|
|
1650
|
-
});
|
|
1651
|
-
},
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
|
-
/**
|
|
1655
|
-
* Convert the unified RuntimePromptMessage[] to the Responses API `input`
|
|
1656
|
-
* array shape. Differences from Chat Completions:
|
|
1657
|
-
* - System prompts go on the top-level `instructions` field, not inline.
|
|
1658
|
-
* - Content parts use `input_text` / `output_text` discriminants instead
|
|
1659
|
-
* of the Chat Completions plain-text shorthand.
|
|
1660
|
-
* - Assistant tool calls become standalone `function_call` items in the
|
|
1661
|
-
* input array, not nested `tool_calls` on a message.
|
|
1662
|
-
* - Tool results become standalone `function_call_output` items.
|
|
1663
|
-
* - Reasoning content parts roundtrip as `reasoning` items so callers can
|
|
1664
|
-
* replay multi-turn conversations with chain-of-thought intact.
|
|
1665
|
-
*/
|
|
1666
|
-
function toOpenAIResponsesInput(prompt) {
|
|
1667
|
-
const instructionsParts = [];
|
|
1668
|
-
const input = [];
|
|
1669
|
-
for (const message of prompt) {
|
|
1670
|
-
switch (message.role) {
|
|
1671
|
-
case "system":
|
|
1672
|
-
if (message.content.length > 0) {
|
|
1673
|
-
instructionsParts.push(message.content);
|
|
1674
|
-
}
|
|
1675
|
-
break;
|
|
1676
|
-
case "user":
|
|
1677
|
-
input.push({
|
|
1678
|
-
role: "user",
|
|
1679
|
-
content: [{ type: "input_text", text: readTextParts(message.content) }],
|
|
1680
|
-
});
|
|
1681
|
-
break;
|
|
1682
|
-
case "assistant": {
|
|
1683
|
-
const messageContent = [];
|
|
1684
|
-
for (const part of message.content) {
|
|
1685
|
-
if (part.type === "text") {
|
|
1686
|
-
messageContent.push({ type: "output_text", text: part.text });
|
|
1687
|
-
continue;
|
|
1688
|
-
}
|
|
1689
|
-
if (part.type === "reasoning") {
|
|
1690
|
-
// Reasoning items are top-level entries in the input array,
|
|
1691
|
-
// not nested inside the assistant message — flush whatever
|
|
1692
|
-
// text we've accumulated first, then push the reasoning item.
|
|
1693
|
-
if (messageContent.length > 0) {
|
|
1694
|
-
input.push({ role: "assistant", content: [...messageContent] });
|
|
1695
|
-
messageContent.length = 0;
|
|
1696
|
-
}
|
|
1697
|
-
const summary = [];
|
|
1698
|
-
if (typeof part.text === "string" && part.text.length > 0) {
|
|
1699
|
-
summary.push({ type: "summary_text", text: part.text });
|
|
1700
|
-
}
|
|
1701
|
-
input.push({
|
|
1702
|
-
type: "reasoning",
|
|
1703
|
-
...(typeof part.signature === "string" ? { encrypted_content: part.signature } : {}),
|
|
1704
|
-
summary,
|
|
1705
|
-
});
|
|
1706
|
-
continue;
|
|
1707
|
-
}
|
|
1708
|
-
// tool-call: flush message content, then push as standalone
|
|
1709
|
-
// function_call item per Responses API shape.
|
|
1710
|
-
if (messageContent.length > 0) {
|
|
1711
|
-
input.push({ role: "assistant", content: [...messageContent] });
|
|
1712
|
-
messageContent.length = 0;
|
|
1713
|
-
}
|
|
1714
|
-
input.push({
|
|
1715
|
-
type: "function_call",
|
|
1716
|
-
call_id: part.toolCallId,
|
|
1717
|
-
name: part.toolName,
|
|
1718
|
-
arguments: stringifyJsonValue(part.input),
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
if (messageContent.length > 0) {
|
|
1722
|
-
input.push({ role: "assistant", content: messageContent });
|
|
1723
|
-
}
|
|
1724
|
-
break;
|
|
1725
|
-
}
|
|
1726
|
-
case "tool":
|
|
1727
|
-
for (const part of message.content) {
|
|
1728
|
-
input.push({
|
|
1729
|
-
type: "function_call_output",
|
|
1730
|
-
call_id: part.toolCallId,
|
|
1731
|
-
output: stringifyJsonValue(part.output.value),
|
|
1732
|
-
});
|
|
1733
|
-
}
|
|
1734
|
-
break;
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
return {
|
|
1738
|
-
...(instructionsParts.length > 0 ? { instructions: instructionsParts.join("\n\n") } : {}),
|
|
1739
|
-
input,
|
|
1740
|
-
};
|
|
1741
|
-
}
|
|
1742
|
-
/**
|
|
1743
|
-
* Tools on the Responses API differ from Chat Completions: instead of
|
|
1744
|
-
* `{ type: "function", function: { name, parameters } }` the function
|
|
1745
|
-
* shape lifts the name/parameters/strict to the top of the entry. Native
|
|
1746
|
-
* tools (web_search, file_search, computer_use, code_interpreter) live
|
|
1747
|
-
* alongside function tools in the same array.
|
|
1748
|
-
*/
|
|
1749
|
-
function toOpenAIResponsesTools(tools) {
|
|
1750
|
-
if (!tools)
|
|
1751
|
-
return undefined;
|
|
1752
|
-
const normalized = [];
|
|
1753
|
-
for (const tool of tools) {
|
|
1754
|
-
if (tool.type === "function") {
|
|
1755
|
-
normalized.push({
|
|
1756
|
-
type: "function",
|
|
1757
|
-
name: tool.name,
|
|
1758
|
-
...(typeof tool.description === "string" ? { description: tool.description } : {}),
|
|
1759
|
-
parameters: unwrapToolInputSchema(tool.inputSchema),
|
|
1760
|
-
});
|
|
1761
|
-
continue;
|
|
1762
|
-
}
|
|
1763
|
-
if (!tool.id.startsWith("openai."))
|
|
1764
|
-
continue;
|
|
1765
|
-
const providerType = tool.id.slice("openai.".length);
|
|
1766
|
-
if (providerType.length === 0)
|
|
1767
|
-
continue;
|
|
1768
|
-
normalized.push({
|
|
1769
|
-
type: providerType,
|
|
1770
|
-
...toSnakeCaseRecord(tool.args),
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
1774
|
-
}
|
|
1775
|
-
function buildOpenAIResponsesRequest(modelId, providerName, options, stream, warnings) {
|
|
1776
|
-
const isReasoningModel = isOpenAIReasoningModel(modelId);
|
|
1777
|
-
const reasoningEffort = resolveOpenAIReasoningEffort(options.reasoning);
|
|
1778
|
-
const reasoningEnabled = isReasoningModel || reasoningEffort !== undefined;
|
|
1779
|
-
// Same param-sanitization rules as Chat Completions: reasoning models
|
|
1780
|
-
// reject sampling params. Drop with a warning.
|
|
1781
|
-
if (options.topK !== undefined) {
|
|
1782
|
-
warnings.push({
|
|
1783
|
-
type: "unsupported-setting",
|
|
1784
|
-
provider: "openai",
|
|
1785
|
-
setting: "topK",
|
|
1786
|
-
details: "OpenAI Responses API does not expose top_k; the value was dropped.",
|
|
1787
|
-
});
|
|
1788
|
-
}
|
|
1789
|
-
if (reasoningEnabled) {
|
|
1790
|
-
const dropped = [
|
|
1791
|
-
["temperature", "temperature"],
|
|
1792
|
-
["topP", "top_p"],
|
|
1793
|
-
["presencePenalty", "presence_penalty"],
|
|
1794
|
-
["frequencyPenalty", "frequency_penalty"],
|
|
1795
|
-
];
|
|
1796
|
-
for (const [key, openaiName] of dropped) {
|
|
1797
|
-
if (options[key] !== undefined) {
|
|
1798
|
-
warnings.push({
|
|
1799
|
-
type: "unsupported-setting",
|
|
1800
|
-
provider: "openai",
|
|
1801
|
-
setting: key,
|
|
1802
|
-
details: `Dropped because OpenAI reasoning models reject ${openaiName}. Reasoning was active for this request.`,
|
|
1803
|
-
});
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
const { instructions, input } = toOpenAIResponsesInput(options.prompt);
|
|
1808
|
-
const responsesTools = toOpenAIResponsesTools(options.tools);
|
|
1809
|
-
const body = {
|
|
1810
|
-
model: modelId,
|
|
1811
|
-
input,
|
|
1812
|
-
...(instructions !== undefined ? { instructions } : {}),
|
|
1813
|
-
...(stream ? { stream: true } : {}),
|
|
1814
|
-
...(options.maxOutputTokens !== undefined
|
|
1815
|
-
? { max_output_tokens: options.maxOutputTokens }
|
|
1816
|
-
: {}),
|
|
1817
|
-
...(!reasoningEnabled && options.temperature !== undefined
|
|
1818
|
-
? { temperature: options.temperature }
|
|
1819
|
-
: {}),
|
|
1820
|
-
...(!reasoningEnabled && options.topP !== undefined ? { top_p: options.topP } : {}),
|
|
1821
|
-
...(responsesTools ? { tools: responsesTools } : {}),
|
|
1822
|
-
...(options.toolChoice !== undefined ? { tool_choice: options.toolChoice } : {}),
|
|
1823
|
-
// The Responses API surfaces reasoning effort + summary verbosity
|
|
1824
|
-
// in a structured `reasoning` object instead of a flat field. We
|
|
1825
|
-
// request "auto" summary so callers see structured summary parts
|
|
1826
|
-
// without having to opt into them per request.
|
|
1827
|
-
...(reasoningEffort !== undefined
|
|
1828
|
-
? { reasoning: { effort: reasoningEffort, summary: "auto" } }
|
|
1829
|
-
: {}),
|
|
1830
|
-
...(typeof options.userId === "string" && options.userId.length > 0
|
|
1831
|
-
? { user: options.userId }
|
|
1832
|
-
: {}),
|
|
1833
|
-
...(options.serviceTier !== undefined ? { service_tier: options.serviceTier } : {}),
|
|
1834
|
-
...(options.parallelToolCalls !== undefined
|
|
1835
|
-
? { parallel_tool_calls: options.parallelToolCalls }
|
|
1836
|
-
: {}),
|
|
1837
|
-
// Responses API uses `text.format` instead of Chat Completions'
|
|
1838
|
-
// `response_format`. The shape is similar but nested under `text`.
|
|
1839
|
-
...(options.responseFormat && options.responseFormat.type !== "text"
|
|
1840
|
-
? {
|
|
1841
|
-
text: {
|
|
1842
|
-
format: options.responseFormat.type === "json" ? { type: "json_object" } : {
|
|
1843
|
-
type: "json_schema",
|
|
1844
|
-
name: options.responseFormat.name,
|
|
1845
|
-
...(typeof options.responseFormat.description === "string"
|
|
1846
|
-
? { description: options.responseFormat.description }
|
|
1847
|
-
: {}),
|
|
1848
|
-
schema: unwrapToolInputSchema(options.responseFormat.schema),
|
|
1849
|
-
...(options.responseFormat.strict !== undefined
|
|
1850
|
-
? { strict: options.responseFormat.strict }
|
|
1851
|
-
: {}),
|
|
1852
|
-
},
|
|
1853
|
-
},
|
|
1854
|
-
}
|
|
1855
|
-
: {}),
|
|
1856
|
-
};
|
|
1857
|
-
Object.assign(body, readProviderOptions(options.providerOptions, "openai", providerName));
|
|
1858
|
-
return body;
|
|
1859
|
-
}
|
|
1860
|
-
function buildOpenAIResponsesGenerateResult(payload) {
|
|
1861
|
-
const record = readRecord(payload);
|
|
1862
|
-
const output = Array.isArray(record?.output) ? record.output : [];
|
|
1863
|
-
const content = [];
|
|
1864
|
-
for (const item of output) {
|
|
1865
|
-
const itemRecord = readRecord(item);
|
|
1866
|
-
const itemType = typeof itemRecord?.type === "string" ? itemRecord.type : undefined;
|
|
1867
|
-
if (itemType === "message" && Array.isArray(itemRecord?.content)) {
|
|
1868
|
-
// A message item bundles one or more output_text parts. Concat
|
|
1869
|
-
// their texts into a single text content entry.
|
|
1870
|
-
let text = "";
|
|
1871
|
-
for (const part of itemRecord.content) {
|
|
1872
|
-
const p = readRecord(part);
|
|
1873
|
-
if (typeof p?.type === "string" && p.type === "output_text" && typeof p.text === "string") {
|
|
1874
|
-
text += p.text;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
if (text.length > 0) {
|
|
1878
|
-
content.push({ type: "text", text });
|
|
1879
|
-
}
|
|
1880
|
-
continue;
|
|
1881
|
-
}
|
|
1882
|
-
if (itemType === "function_call") {
|
|
1883
|
-
content.push({
|
|
1884
|
-
type: "tool-call",
|
|
1885
|
-
toolCallId: typeof itemRecord?.call_id === "string"
|
|
1886
|
-
? itemRecord.call_id
|
|
1887
|
-
: (typeof itemRecord?.id === "string" ? itemRecord.id : ""),
|
|
1888
|
-
toolName: typeof itemRecord?.name === "string" ? itemRecord.name : "",
|
|
1889
|
-
input: typeof itemRecord?.arguments === "string"
|
|
1890
|
-
? itemRecord.arguments
|
|
1891
|
-
: stringifyJsonValue(itemRecord?.arguments ?? {}),
|
|
1892
|
-
});
|
|
1893
|
-
continue;
|
|
1894
|
-
}
|
|
1895
|
-
if (itemType === "reasoning") {
|
|
1896
|
-
const summary = Array.isArray(itemRecord?.summary) ? itemRecord.summary : [];
|
|
1897
|
-
const summaries = [];
|
|
1898
|
-
for (const s of summary) {
|
|
1899
|
-
const sr = readRecord(s);
|
|
1900
|
-
if (typeof sr?.text === "string" && sr.text.length > 0) {
|
|
1901
|
-
summaries.push({
|
|
1902
|
-
...(typeof sr?.id === "string" ? { id: sr.id } : {}),
|
|
1903
|
-
text: sr.text,
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
content.push({
|
|
1908
|
-
type: "reasoning",
|
|
1909
|
-
...(summaries.length > 0 ? { summaries } : {}),
|
|
1910
|
-
...(typeof itemRecord?.encrypted_content === "string"
|
|
1911
|
-
? { signature: itemRecord.encrypted_content }
|
|
1912
|
-
: {}),
|
|
1913
|
-
});
|
|
1914
|
-
continue;
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
return {
|
|
1918
|
-
content,
|
|
1919
|
-
finishReason: normalizeOpenAIResponsesFinishReason(record?.status),
|
|
1920
|
-
usage: extractOpenAIResponsesUsage(payload),
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
/**
|
|
1924
|
-
* Parse the Responses API streaming event grammar into the same UI part
|
|
1925
|
-
* shapes the existing OpenAI / Anthropic / Google streams emit. The
|
|
1926
|
-
* Responses API uses a strict event-typed protocol — every event has a
|
|
1927
|
-
* `type` field naming the lifecycle phase — instead of the loose
|
|
1928
|
-
* `delta`-based shape Chat Completions uses.
|
|
1929
|
-
*/
|
|
1930
|
-
async function* streamOpenAIResponsesParts(stream) {
|
|
1931
|
-
const decoder = new TextDecoder();
|
|
1932
|
-
let buffer = "";
|
|
1933
|
-
const reasoningBlocks = new Map();
|
|
1934
|
-
const functionCalls = new Map();
|
|
1935
|
-
const startedToolCalls = new Set();
|
|
1936
|
-
let finishReason = null;
|
|
1937
|
-
let usage;
|
|
1938
|
-
let reasoningCounter = 0;
|
|
1939
|
-
for await (const chunk of stream) {
|
|
1940
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
1941
|
-
const parsed = parseSseChunk(buffer);
|
|
1942
|
-
buffer = parsed.remainder;
|
|
1943
|
-
for (const event of parsed.events) {
|
|
1944
|
-
if (event === "[DONE]")
|
|
1945
|
-
continue;
|
|
1946
|
-
const record = readRecord(event);
|
|
1947
|
-
const type = typeof record?.type === "string" ? record.type : undefined;
|
|
1948
|
-
if (!type)
|
|
1949
|
-
continue;
|
|
1950
|
-
// response.output_item.added: a new output item begins. Track
|
|
1951
|
-
// function_call items so their argument deltas can be attributed,
|
|
1952
|
-
// and reasoning items so summary deltas can group correctly.
|
|
1953
|
-
if (type === "response.output_item.added") {
|
|
1954
|
-
const item = readRecord(record?.item);
|
|
1955
|
-
const itemType = typeof item?.type === "string" ? item.type : undefined;
|
|
1956
|
-
const itemId = typeof item?.id === "string" ? item.id : undefined;
|
|
1957
|
-
if (itemType === "function_call" && itemId) {
|
|
1958
|
-
const callId = typeof item?.call_id === "string" ? item.call_id : itemId;
|
|
1959
|
-
const name = typeof item?.name === "string" ? item.name : "";
|
|
1960
|
-
functionCalls.set(itemId, {
|
|
1961
|
-
id: itemId,
|
|
1962
|
-
toolCallId: callId,
|
|
1963
|
-
name,
|
|
1964
|
-
arguments: "",
|
|
1965
|
-
});
|
|
1966
|
-
}
|
|
1967
|
-
if (itemType === "reasoning" && itemId) {
|
|
1968
|
-
reasoningBlocks.set(itemId, {
|
|
1969
|
-
id: `reasoning-${reasoningCounter++}`,
|
|
1970
|
-
emittedStart: false,
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
continue;
|
|
1974
|
-
}
|
|
1975
|
-
// response.output_text.delta: text chunk for a message item.
|
|
1976
|
-
if (type === "response.output_text.delta" && typeof record?.delta === "string") {
|
|
1977
|
-
if (record.delta.length > 0) {
|
|
1978
|
-
yield { type: "text-delta", delta: record.delta };
|
|
1979
|
-
}
|
|
1980
|
-
continue;
|
|
1981
|
-
}
|
|
1982
|
-
// response.reasoning_summary_text.delta: reasoning summary text
|
|
1983
|
-
// chunk. The first delta on an item lazily emits the
|
|
1984
|
-
// reasoning-start event so callers can group deltas into a part.
|
|
1985
|
-
if (type === "response.reasoning_summary_text.delta" && typeof record?.delta === "string") {
|
|
1986
|
-
const itemId = typeof record?.item_id === "string" ? record.item_id : undefined;
|
|
1987
|
-
const state = itemId ? reasoningBlocks.get(itemId) : undefined;
|
|
1988
|
-
if (state && record.delta.length > 0) {
|
|
1989
|
-
if (!state.emittedStart) {
|
|
1990
|
-
yield { type: "reasoning-start", id: state.id };
|
|
1991
|
-
state.emittedStart = true;
|
|
1992
|
-
}
|
|
1993
|
-
yield { type: "reasoning-delta", id: state.id, delta: record.delta };
|
|
1994
|
-
}
|
|
1995
|
-
continue;
|
|
1996
|
-
}
|
|
1997
|
-
// response.function_call_arguments.delta: tool call argument
|
|
1998
|
-
// chunk. The first delta lazily emits tool-input-start.
|
|
1999
|
-
if (type === "response.function_call_arguments.delta" && typeof record?.delta === "string") {
|
|
2000
|
-
const itemId = typeof record?.item_id === "string" ? record.item_id : undefined;
|
|
2001
|
-
const state = itemId ? functionCalls.get(itemId) : undefined;
|
|
2002
|
-
if (state && record.delta.length > 0) {
|
|
2003
|
-
if (!startedToolCalls.has(state.id)) {
|
|
2004
|
-
yield {
|
|
2005
|
-
type: "tool-input-start",
|
|
2006
|
-
id: state.toolCallId,
|
|
2007
|
-
toolName: state.name,
|
|
2008
|
-
};
|
|
2009
|
-
startedToolCalls.add(state.id);
|
|
2010
|
-
}
|
|
2011
|
-
state.arguments += record.delta;
|
|
2012
|
-
yield {
|
|
2013
|
-
type: "tool-input-delta",
|
|
2014
|
-
id: state.toolCallId,
|
|
2015
|
-
delta: record.delta,
|
|
2016
|
-
};
|
|
2017
|
-
}
|
|
2018
|
-
continue;
|
|
2019
|
-
}
|
|
2020
|
-
// response.output_item.done: an item has finished emitting deltas.
|
|
2021
|
-
// Close any reasoning or function-call streams that were open.
|
|
2022
|
-
if (type === "response.output_item.done") {
|
|
2023
|
-
const item = readRecord(record?.item);
|
|
2024
|
-
const itemType = typeof item?.type === "string" ? item.type : undefined;
|
|
2025
|
-
const itemId = typeof item?.id === "string" ? item.id : undefined;
|
|
2026
|
-
if (itemType === "reasoning" && itemId) {
|
|
2027
|
-
const state = reasoningBlocks.get(itemId);
|
|
2028
|
-
if (state?.emittedStart) {
|
|
2029
|
-
yield { type: "reasoning-end", id: state.id };
|
|
2030
|
-
}
|
|
2031
|
-
reasoningBlocks.delete(itemId);
|
|
2032
|
-
}
|
|
2033
|
-
if (itemType === "function_call" && itemId) {
|
|
2034
|
-
const state = functionCalls.get(itemId);
|
|
2035
|
-
if (state) {
|
|
2036
|
-
yield {
|
|
2037
|
-
type: "tool-call",
|
|
2038
|
-
toolCallId: state.toolCallId,
|
|
2039
|
-
toolName: state.name,
|
|
2040
|
-
input: state.arguments,
|
|
2041
|
-
};
|
|
2042
|
-
}
|
|
2043
|
-
functionCalls.delete(itemId);
|
|
2044
|
-
}
|
|
2045
|
-
continue;
|
|
2046
|
-
}
|
|
2047
|
-
// response.completed: terminal event with the final response object
|
|
2048
|
-
// (status + usage). Capture both for the final finish part.
|
|
2049
|
-
if (type === "response.completed") {
|
|
2050
|
-
usage = extractOpenAIResponsesUsage(record) ?? usage;
|
|
2051
|
-
const responseRecord = readRecord(record?.response);
|
|
2052
|
-
finishReason = normalizeOpenAIResponsesFinishReason(responseRecord?.status);
|
|
2053
|
-
continue;
|
|
2054
|
-
}
|
|
2055
|
-
if (type === "response.failed" || type === "response.incomplete") {
|
|
2056
|
-
const responseRecord = readRecord(record?.response);
|
|
2057
|
-
finishReason = normalizeOpenAIResponsesFinishReason(responseRecord?.status) ??
|
|
2058
|
-
(type === "response.failed"
|
|
2059
|
-
? { unified: "error", raw: "failed" }
|
|
2060
|
-
: { unified: "length", raw: "incomplete" });
|
|
2061
|
-
usage = extractOpenAIResponsesUsage(record) ?? usage;
|
|
2062
|
-
continue;
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
// Close any reasoning streams still open at end-of-stream (defensive
|
|
2067
|
-
// — a clean Responses API stream always closes them via output_item.done).
|
|
2068
|
-
for (const state of reasoningBlocks.values()) {
|
|
2069
|
-
if (state.emittedStart) {
|
|
2070
|
-
yield { type: "reasoning-end", id: state.id };
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
yield {
|
|
2074
|
-
type: "finish",
|
|
2075
|
-
finishReason,
|
|
2076
|
-
...(usage ? { usage } : {}),
|
|
2077
|
-
};
|
|
2078
|
-
}
|
|
2079
|
-
export function createOpenAIResponsesRuntime(config, modelId) {
|
|
2080
|
-
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
2081
|
-
return {
|
|
2082
|
-
provider: config.name ?? "openai",
|
|
2083
|
-
modelId,
|
|
2084
|
-
specificationVersion: "v3",
|
|
2085
|
-
supportedUrls: {},
|
|
2086
|
-
doGenerate(optionsForRuntime) {
|
|
2087
|
-
const options = optionsForRuntime;
|
|
2088
|
-
const url = getOpenAIResponsesUrl(config.baseURL);
|
|
2089
|
-
const warnings = createWarningCollector();
|
|
2090
|
-
const body = buildOpenAIResponsesRequest(modelId, config.name ?? "openai", options, false, warnings);
|
|
2091
|
-
return requestJson({
|
|
2092
|
-
url,
|
|
2093
|
-
fetchImpl,
|
|
2094
|
-
providerLabel: config.name ?? "openai",
|
|
2095
|
-
providerKind: "openai",
|
|
2096
|
-
init: createOpenAIRequestInit({
|
|
2097
|
-
apiKey: config.apiKey,
|
|
2098
|
-
extraHeaders: options.headers,
|
|
2099
|
-
body: JSON.stringify(body),
|
|
2100
|
-
signal: options.abortSignal,
|
|
2101
|
-
}),
|
|
2102
|
-
}).then((payload) => {
|
|
2103
|
-
const drained = warnings.drain();
|
|
2104
|
-
return {
|
|
2105
|
-
...buildOpenAIResponsesGenerateResult(payload),
|
|
2106
|
-
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
2107
|
-
};
|
|
2108
|
-
});
|
|
2109
|
-
},
|
|
2110
|
-
doStream(optionsForRuntime) {
|
|
2111
|
-
const options = optionsForRuntime;
|
|
2112
|
-
const url = getOpenAIResponsesUrl(config.baseURL);
|
|
2113
|
-
const warnings = createWarningCollector();
|
|
2114
|
-
const body = buildOpenAIResponsesRequest(modelId, config.name ?? "openai", options, true, warnings);
|
|
2115
|
-
return requestStream({
|
|
2116
|
-
url,
|
|
2117
|
-
fetchImpl,
|
|
2118
|
-
providerLabel: config.name ?? "openai",
|
|
2119
|
-
providerKind: "openai",
|
|
2120
|
-
init: createOpenAIRequestInit({
|
|
2121
|
-
apiKey: config.apiKey,
|
|
2122
|
-
extraHeaders: options.headers,
|
|
2123
|
-
body: JSON.stringify(body),
|
|
2124
|
-
signal: options.abortSignal,
|
|
2125
|
-
}),
|
|
2126
|
-
}).then((responseStream) => {
|
|
2127
|
-
const drained = warnings.drain();
|
|
2128
|
-
return {
|
|
2129
|
-
stream: ReadableStream.from(withToolInputStatusTransitions(streamOpenAIResponsesParts(responseStream))),
|
|
2130
|
-
...(drained.length > 0 ? { warnings: drained } : {}),
|
|
2131
|
-
};
|
|
2132
|
-
});
|
|
2133
|
-
},
|
|
2134
|
-
};
|
|
2135
|
-
}
|
|
2136
1245
|
export function createAnthropicModelRuntime(config, modelId) {
|
|
2137
1246
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
2138
1247
|
return {
|
|
@@ -2250,45 +1359,6 @@ export function createGoogleModelRuntime(config, modelId) {
|
|
|
2250
1359
|
},
|
|
2251
1360
|
};
|
|
2252
1361
|
}
|
|
2253
|
-
export function createOpenAIEmbeddingRuntime(config, modelId) {
|
|
2254
|
-
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
2255
|
-
return {
|
|
2256
|
-
provider: config.name ?? "openai",
|
|
2257
|
-
modelId,
|
|
2258
|
-
supportsParallelCalls: true,
|
|
2259
|
-
doEmbed({ values, abortSignal }) {
|
|
2260
|
-
if (values.length === 0) {
|
|
2261
|
-
return Promise.resolve({
|
|
2262
|
-
embeddings: [],
|
|
2263
|
-
warnings: [],
|
|
2264
|
-
rawResponse: { data: [] },
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2267
|
-
const url = getOpenAIEmbeddingUrl(config.baseURL);
|
|
2268
|
-
return requestJson({
|
|
2269
|
-
url,
|
|
2270
|
-
fetchImpl,
|
|
2271
|
-
providerLabel: config.name ?? "openai",
|
|
2272
|
-
providerKind: "openai",
|
|
2273
|
-
init: createOpenAIRequestInit({
|
|
2274
|
-
apiKey: config.apiKey,
|
|
2275
|
-
body: JSON.stringify({
|
|
2276
|
-
model: modelId,
|
|
2277
|
-
input: values,
|
|
2278
|
-
}),
|
|
2279
|
-
signal: abortSignal,
|
|
2280
|
-
}),
|
|
2281
|
-
}).then((payload) => ({
|
|
2282
|
-
embeddings: extractOpenAIEmbeddings(payload),
|
|
2283
|
-
usage: {
|
|
2284
|
-
tokens: extractOpenAIUsageTokens(payload),
|
|
2285
|
-
},
|
|
2286
|
-
rawResponse: payload,
|
|
2287
|
-
warnings: [],
|
|
2288
|
-
}));
|
|
2289
|
-
},
|
|
2290
|
-
};
|
|
2291
|
-
}
|
|
2292
1362
|
export function createGoogleEmbeddingRuntime(config, modelId) {
|
|
2293
1363
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
2294
1364
|
return {
|