llm-simple-router 0.8.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +319 -0
- package/README.md +2 -0
- package/config/recommended-providers.json +33 -9
- package/config/recommended-retry-rules.json +9 -8
- package/dist/admin/providers.js +11 -9
- package/dist/admin/quick-setup.d.ts +13 -0
- package/dist/admin/quick-setup.js +169 -0
- package/dist/admin/recommended.js +5 -1
- package/dist/admin/routes.js +2 -0
- package/dist/config/model-context.d.ts +8 -2
- package/dist/config/model-context.js +17 -5
- package/dist/config/recommended.d.ts +2 -1
- package/dist/config/recommended.js +5 -9
- package/dist/core/constants.js +2 -0
- package/dist/db/index.js +5 -0
- package/dist/db/migrations/033_add_adaptive_concurrency.sql +3 -0
- package/dist/db/migrations/036_add_openai_responses_api_type.sql +68 -0
- package/dist/db/migrations/037_fix_035_data_corruption.sql +54 -0
- package/dist/db/providers.d.ts +3 -3
- package/dist/index.js +7 -3
- package/dist/metrics/metrics-extractor.d.ts +3 -2
- package/dist/metrics/metrics-extractor.js +45 -0
- package/dist/metrics/sse-metrics-transform.d.ts +1 -1
- package/dist/metrics/sse-metrics-transform.js +10 -0
- package/dist/monitor/request-tracker.d.ts +1 -1
- package/dist/monitor/stream-content-accumulator.d.ts +1 -1
- package/dist/monitor/stream-extractor.d.ts +1 -1
- package/dist/monitor/stream-extractor.js +21 -0
- package/dist/monitor/types.d.ts +1 -1
- package/dist/proxy/handler/proxy-handler-utils.d.ts +1 -1
- package/dist/proxy/handler/proxy-handler.d.ts +1 -1
- package/dist/proxy/handler/proxy-handler.js +8 -2
- package/dist/proxy/handler/responses.d.ts +7 -0
- package/dist/proxy/handler/responses.js +48 -0
- package/dist/proxy/loop-prevention/tool-loop-guard.d.ts +1 -1
- package/dist/proxy/loop-prevention/tool-loop-guard.js +10 -0
- package/dist/proxy/orchestration/orchestrator.d.ts +1 -1
- package/dist/proxy/orchestration/semaphore.js +6 -0
- package/dist/proxy/patch/deepseek/index.d.ts +1 -1
- package/dist/proxy/patch/deepseek/patch-thinking-param.d.ts +1 -1
- package/dist/proxy/patch/index.d.ts +3 -0
- package/dist/proxy/patch/index.js +28 -0
- package/dist/proxy/patch/tool-round-limiter.d.ts +1 -1
- package/dist/proxy/patch/tool-round-limiter.js +16 -0
- package/dist/proxy/proxy-core.d.ts +1 -1
- package/dist/proxy/proxy-logging.d.ts +3 -3
- package/dist/proxy/response-transform.js +13 -0
- package/dist/proxy/transform/id-utils.d.ts +1 -0
- package/dist/proxy/transform/id-utils.js +3 -0
- package/dist/proxy/transform/plugin-types.d.ts +5 -5
- package/dist/proxy/transform/request-bridge-responses.d.ts +19 -0
- package/dist/proxy/transform/request-bridge-responses.js +311 -0
- package/dist/proxy/transform/request-transform-responses.d.ts +2 -0
- package/dist/proxy/transform/request-transform-responses.js +350 -0
- package/dist/proxy/transform/response-bridge-responses.d.ts +23 -0
- package/dist/proxy/transform/response-bridge-responses.js +173 -0
- package/dist/proxy/transform/response-transform-responses.d.ts +2 -0
- package/dist/proxy/transform/response-transform-responses.js +137 -0
- package/dist/proxy/transform/stream-ant2resp.d.ts +26 -0
- package/dist/proxy/transform/stream-ant2resp.js +322 -0
- package/dist/proxy/transform/stream-bridge-chat2resp.d.ts +40 -0
- package/dist/proxy/transform/stream-bridge-chat2resp.js +382 -0
- package/dist/proxy/transform/stream-bridge-resp2chat.d.ts +24 -0
- package/dist/proxy/transform/stream-bridge-resp2chat.js +237 -0
- package/dist/proxy/transform/stream-resp2ant.d.ts +21 -0
- package/dist/proxy/transform/stream-resp2ant.js +238 -0
- package/dist/proxy/transform/stream-transform-base.d.ts +1 -0
- package/dist/proxy/transform/stream-transform-base.js +3 -0
- package/dist/proxy/transform/transform-coordinator.d.ts +1 -0
- package/dist/proxy/transform/transform-coordinator.js +127 -8
- package/dist/proxy/transform/types-responses.d.ts +177 -0
- package/dist/proxy/transform/types-responses.js +27 -0
- package/dist/proxy/transform/types.d.ts +3 -1
- package/dist/proxy/transport/transport-fn.d.ts +1 -1
- package/frontend-dist/assets/CardContent-BhMXx-JD.js +1 -0
- package/frontend-dist/assets/CardTitle-DQDjTee3.js +1 -0
- package/frontend-dist/assets/CascadingModelSelect-JBQq3JJt.js +1 -0
- package/frontend-dist/assets/Checkbox-ByxbKP_C.js +1 -0
- package/frontend-dist/assets/CollapsibleContent-GecW2Jk_.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-Cib3-OsK.js +1 -0
- package/frontend-dist/assets/Collection-Dbvdpa0m.js +1 -0
- package/frontend-dist/assets/Dashboard-3MJPLflT.js +3 -0
- package/frontend-dist/assets/DialogTitle-Ej_rtfV1.js +1 -0
- package/frontend-dist/assets/{Input-CAnKUBBK.js → Input-tcnrMp1v.js} +1 -1
- package/frontend-dist/assets/Label-BwzPFyL-.js +1 -0
- package/frontend-dist/assets/Login-Cdsw2pWC.js +1 -0
- package/frontend-dist/assets/Logs-5_CWiws5.js +1 -0
- package/frontend-dist/assets/MappingList-D8HRph05.js +1 -0
- package/frontend-dist/assets/ModelCard-CZbQcYNn.js +1 -0
- package/frontend-dist/assets/ModelMappings-CJqgl7O8.js +1 -0
- package/frontend-dist/assets/Monitor-B8v5a8fB.js +1 -0
- package/frontend-dist/assets/PopoverTrigger-C88SpJNZ.js +1 -0
- package/frontend-dist/assets/PopperContent-6BXua_FZ.js +1 -0
- package/frontend-dist/assets/Providers-DH0nvlGn.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-CAH-44W-.js +5 -0
- package/frontend-dist/assets/QuickSetup-CsDO-ZGP.js +1 -0
- package/frontend-dist/assets/RetryRules-8iT9fLsH.js +1 -0
- package/frontend-dist/assets/RouterKeys-BFoEmWgj.js +1 -0
- package/frontend-dist/assets/RovingFocusItem-DdPUFQHC.js +1 -0
- package/frontend-dist/assets/Schedules-B8Se31u4.js +1 -0
- package/frontend-dist/assets/SelectValue-CT2z_-6j.js +1 -0
- package/frontend-dist/assets/Settings-BHvtsJKD.js +6 -0
- package/frontend-dist/assets/Setup-k-l9KDC0.js +1 -0
- package/frontend-dist/assets/Switch-D1NdA4ax.js +1 -0
- package/frontend-dist/assets/TableHeader-CcMyOsUB.js +1 -0
- package/frontend-dist/assets/Teleport-Bmeh33lB.js +3 -0
- package/frontend-dist/assets/TooltipTrigger-LegC_Uvp.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-BVw6W2pk.js +3 -0
- package/frontend-dist/assets/UnifiedRequestDialog-C4MTxb25.css +1 -0
- package/frontend-dist/assets/VisuallyHidden-ogESfc9X.js +1 -0
- package/frontend-dist/assets/VisuallyHiddenInput-BQemVGau.js +1 -0
- package/frontend-dist/assets/alert-dialog-DzKCAoYJ.js +1 -0
- package/frontend-dist/assets/badge-C-9zPTgw.js +1 -0
- package/frontend-dist/assets/button-D27ClX8J.js +14 -0
- package/frontend-dist/assets/check-yTAivq1h.js +1 -0
- package/frontend-dist/assets/common-CWCbKHOK.js +1 -0
- package/frontend-dist/assets/common-D4xnnaqi.js +1 -0
- package/frontend-dist/assets/constants-B-VELBjk.js +1 -0
- package/frontend-dist/assets/copy-DWG9cQPR.js +1 -0
- package/frontend-dist/assets/dashboard-B8eI-t8c.js +1 -0
- package/frontend-dist/assets/dashboard-Dbe6A2lu.js +1 -0
- package/frontend-dist/assets/dialog-BnYR6_dh.js +1 -0
- package/frontend-dist/assets/file-text-D33FJAPX.js +1 -0
- package/frontend-dist/assets/format-BhxQSgt6.js +1 -0
- package/frontend-dist/assets/i18n-CwUfS0tE.js +1 -0
- package/frontend-dist/assets/index-B348nt-T.css +1 -0
- package/frontend-dist/assets/index-DPRxBo3N.js +1 -0
- package/frontend-dist/assets/lib-D0Ek2pPZ.js +1 -0
- package/frontend-dist/assets/loader-circle-EpKC006I.js +1 -0
- package/frontend-dist/assets/login-BTolYxVI.js +1 -0
- package/frontend-dist/assets/login-w_ICpiU5.js +1 -0
- package/frontend-dist/assets/logs-7dT2uyMa.js +1 -0
- package/frontend-dist/assets/logs-_3w8tDQa.js +1 -0
- package/frontend-dist/assets/mappings-Bbn3r2uJ.js +1 -0
- package/frontend-dist/assets/mappings-CTZ-zb1x.js +1 -0
- package/frontend-dist/assets/monitor-DN5m5n_x.js +1 -0
- package/frontend-dist/assets/monitor-DysWEOtt.js +1 -0
- package/frontend-dist/assets/providers-C1gQGzwa.js +1 -0
- package/frontend-dist/assets/providers-CCfko___.js +1 -0
- package/frontend-dist/assets/proxyEnhancement-BItabyLo.js +1 -0
- package/frontend-dist/assets/proxyEnhancement-DeMb7wIE.js +1 -0
- package/frontend-dist/assets/quickSetup-C75HMC_z.js +1 -0
- package/frontend-dist/assets/quickSetup-DStZWiuf.js +1 -0
- package/frontend-dist/assets/requestDetail-BoaPEQs-.js +1 -0
- package/frontend-dist/assets/requestDetail-CM5kFgy6.js +1 -0
- package/frontend-dist/assets/retryRules-CIF37gOl.js +1 -0
- package/frontend-dist/assets/retryRules-o_D8S5gy.js +1 -0
- package/frontend-dist/assets/routerKeys-BAvjW0V8.js +1 -0
- package/frontend-dist/assets/routerKeys-mQt2YPuE.js +1 -0
- package/frontend-dist/assets/schedules-BCV2rxK-.js +1 -0
- package/frontend-dist/assets/schedules-Qte9b7b_.js +1 -0
- package/frontend-dist/assets/settings-Bgu2lJfy.js +1 -0
- package/frontend-dist/assets/settings-UCmMSq_F.js +1 -0
- package/frontend-dist/assets/setup-B_fAfMoV.js +1 -0
- package/frontend-dist/assets/setup-Chc246Zi.js +1 -0
- package/frontend-dist/assets/sidebar-B7rejnZA.js +1 -0
- package/frontend-dist/assets/sidebar-CBMItLst.js +1 -0
- package/frontend-dist/assets/sun-BylRZIWt.js +1 -0
- package/frontend-dist/assets/trash-2-QNFff7V4.js +1 -0
- package/frontend-dist/assets/{useClipboard-BmmsNSGV.js → useClipboard-BFt5f-_-.js} +1 -1
- package/frontend-dist/assets/{useFocusGuards-A-9V2Y-b.js → useFocusGuards-DQBZKWnu.js} +1 -1
- package/frontend-dist/assets/useFormControl-T2RQNBqs.js +1 -0
- package/frontend-dist/assets/useLogRetention-NrrZrpPE.js +1 -0
- package/frontend-dist/assets/useNonce-DR38uny5.js +1 -0
- package/frontend-dist/assets/useTheme-CpTI547G.js +1 -0
- package/frontend-dist/assets/x-DSgLgKC_.js +1 -0
- package/frontend-dist/index.html +25 -22
- package/package.json +1 -1
- package/dist/db/migrations/033_add_pipeline_snapshot.sql +0 -1
- package/frontend-dist/assets/CardContent-BVMQ2_pg.js +0 -1
- package/frontend-dist/assets/CardTitle-GLv7QyIY.js +0 -1
- package/frontend-dist/assets/CascadingModelSelect-CBhqKFDX.js +0 -1
- package/frontend-dist/assets/Checkbox-HPVDmEdV.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-DhxD9tpM.js +0 -1
- package/frontend-dist/assets/Collection-BRt7YxN8.js +0 -1
- package/frontend-dist/assets/Dashboard-D1Ys8Zog.js +0 -3
- package/frontend-dist/assets/DialogTitle-23q73lwF.js +0 -1
- package/frontend-dist/assets/Label-DWdYtVMI.js +0 -1
- package/frontend-dist/assets/Login-w5WFOinP.js +0 -1
- package/frontend-dist/assets/Logs-C1F1ZmWF.js +0 -1
- package/frontend-dist/assets/ModelMappings-BzmecWEH.js +0 -1
- package/frontend-dist/assets/Monitor-DrAZFTKR.js +0 -1
- package/frontend-dist/assets/PopoverTrigger-Bj65uUbv.js +0 -1
- package/frontend-dist/assets/PopperContent-gzzf1XHe.js +0 -1
- package/frontend-dist/assets/Providers-DSgf4mb6.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-Bb1cCP6d.js +0 -5
- package/frontend-dist/assets/RetryRules-BwPfEZtm.js +0 -1
- package/frontend-dist/assets/RouterKeys-CzTSq1Mx.js +0 -1
- package/frontend-dist/assets/RovingFocusItem-CXM_Yfkm.js +0 -1
- package/frontend-dist/assets/Schedules-DVilCXrC.js +0 -1
- package/frontend-dist/assets/SelectValue-C0-LzGQY.js +0 -1
- package/frontend-dist/assets/Settings-Bpk53zVX.js +0 -6
- package/frontend-dist/assets/Setup-Dn7EgC49.js +0 -1
- package/frontend-dist/assets/Switch-BO8Ooae6.js +0 -1
- package/frontend-dist/assets/TableHeader-Bded9VTC.js +0 -1
- package/frontend-dist/assets/TabsTrigger-BzKMi9AF.js +0 -1
- package/frontend-dist/assets/Teleport-DizRK5O3.js +0 -3
- package/frontend-dist/assets/TooltipTrigger-EiIy2zn8.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-BABsTaGb.js +0 -3
- package/frontend-dist/assets/UnifiedRequestDialog-BjEigSaR.css +0 -1
- package/frontend-dist/assets/VisuallyHidden-5AozJQza.js +0 -1
- package/frontend-dist/assets/VisuallyHiddenInput-DdiZrV2i.js +0 -1
- package/frontend-dist/assets/alert-dialog-DlKUuTPe.js +0 -1
- package/frontend-dist/assets/arrow-down-CxWKmZ2I.js +0 -1
- package/frontend-dist/assets/badge-9KJEMa53.js +0 -1
- package/frontend-dist/assets/button-Ul8WlrM5.js +0 -12
- package/frontend-dist/assets/check-7ahK--N4.js +0 -1
- package/frontend-dist/assets/constants-D_0jiLjw.js +0 -1
- package/frontend-dist/assets/copy-DzU2pAMG.js +0 -1
- package/frontend-dist/assets/dialog-B9j-FMrd.js +0 -1
- package/frontend-dist/assets/file-text-Bj3ZIo-E.js +0 -1
- package/frontend-dist/assets/format-Dln15Luw.js +0 -1
- package/frontend-dist/assets/index-Bz_ZaXNn.css +0 -1
- package/frontend-dist/assets/index-MedWZMHB.js +0 -1
- package/frontend-dist/assets/lib-Hhs3NqfD.js +0 -1
- package/frontend-dist/assets/loader-circle-5TJUukEe.js +0 -1
- package/frontend-dist/assets/useFormControl-DEO19lRe.js +0 -1
- package/frontend-dist/assets/useLogRetention-BfnBFZ5K.js +0 -1
- package/frontend-dist/assets/useNonce-BfwUJ1Ci.js +0 -1
- package/frontend-dist/assets/x-Cfopt3QL.js +0 -1
- /package/dist/db/migrations/{034_drop_redundant_log_columns.sql → 035_drop_redundant_log_columns.sql} +0 -0
- /package/frontend-dist/assets/{ohash.D__AXeF1-D5e5Wyzx.js → ohash.D__AXeF1-CTo5WcIm.js} +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge (lossy) response transformation between OpenAI Responses API
|
|
3
|
+
* and OpenAI Chat Completions API.
|
|
4
|
+
*
|
|
5
|
+
* This is the SECONDARY conversion path used when the upstream provider
|
|
6
|
+
* only supports the opposite API format. It is lossy because Chat Completions
|
|
7
|
+
* cannot represent structured reasoning summaries, built-in tool outputs,
|
|
8
|
+
* or response-level metadata.
|
|
9
|
+
*/
|
|
10
|
+
import { generateChatcmplId, generateRespId, MS_PER_SECOND } from "./id-utils.js";
|
|
11
|
+
// ---------- Responses → Chat Completions ----------
|
|
12
|
+
/**
|
|
13
|
+
* Convert a Responses API response body to a Chat Completions response body.
|
|
14
|
+
*
|
|
15
|
+
* Lossy: structured reasoning summaries are flattened to a single string;
|
|
16
|
+
* built-in tool output items (web_search_call, etc.) are skipped.
|
|
17
|
+
*/
|
|
18
|
+
export function responsesToChatResponse(bodyStr) {
|
|
19
|
+
const resp = JSON.parse(bodyStr);
|
|
20
|
+
const output = resp.output ?? [];
|
|
21
|
+
const message = { role: "assistant" };
|
|
22
|
+
const toolCalls = [];
|
|
23
|
+
const textParts = [];
|
|
24
|
+
let hasFunctionCall = false;
|
|
25
|
+
for (const item of output) {
|
|
26
|
+
const type = item.type;
|
|
27
|
+
if (type === "message") {
|
|
28
|
+
// ResponseOutputMessage → extract text content
|
|
29
|
+
const msgContent = item.content ?? [];
|
|
30
|
+
for (const part of msgContent) {
|
|
31
|
+
if (part.type === "output_text" && part.text != null) {
|
|
32
|
+
textParts.push(String(part.text));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (type === "function_call") {
|
|
37
|
+
// → tool_calls
|
|
38
|
+
hasFunctionCall = true;
|
|
39
|
+
toolCalls.push({
|
|
40
|
+
id: String(item.id ?? ""),
|
|
41
|
+
type: "function",
|
|
42
|
+
function: {
|
|
43
|
+
name: String(item.name ?? ""),
|
|
44
|
+
arguments: String(item.arguments ?? "{}"),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else if (type === "reasoning") {
|
|
49
|
+
// → reasoning_content (joined summary text, LOSSY)
|
|
50
|
+
const summary = item.summary;
|
|
51
|
+
if (summary) {
|
|
52
|
+
const joined = summary.map(s => String(s.text ?? "")).join("");
|
|
53
|
+
if (joined) {
|
|
54
|
+
message.reasoning_content = joined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Other output types (web_search_call, file_search_call, etc.) → skip
|
|
59
|
+
}
|
|
60
|
+
// Text content
|
|
61
|
+
if (textParts.length > 0) {
|
|
62
|
+
message.content = textParts.join("");
|
|
63
|
+
}
|
|
64
|
+
// Tool calls
|
|
65
|
+
if (toolCalls.length > 0) {
|
|
66
|
+
message.tool_calls = toolCalls;
|
|
67
|
+
}
|
|
68
|
+
// finish_reason
|
|
69
|
+
let finishReason;
|
|
70
|
+
if (hasFunctionCall) {
|
|
71
|
+
finishReason = "tool_calls";
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
finishReason = mapStatusToFinishReason(String(resp.status ?? "completed"));
|
|
75
|
+
}
|
|
76
|
+
// Usage mapping
|
|
77
|
+
const usage = resp.usage;
|
|
78
|
+
const promptTokens = usage?.input_tokens ?? 0;
|
|
79
|
+
const completionTokens = usage?.output_tokens ?? 0;
|
|
80
|
+
return JSON.stringify({
|
|
81
|
+
id: generateChatcmplId(),
|
|
82
|
+
object: "chat.completion",
|
|
83
|
+
created: Math.floor(Date.now() / MS_PER_SECOND),
|
|
84
|
+
model: resp.model ?? "",
|
|
85
|
+
choices: [{
|
|
86
|
+
index: 0,
|
|
87
|
+
message,
|
|
88
|
+
finish_reason: finishReason,
|
|
89
|
+
}],
|
|
90
|
+
usage: {
|
|
91
|
+
prompt_tokens: promptTokens,
|
|
92
|
+
completion_tokens: completionTokens,
|
|
93
|
+
total_tokens: promptTokens + completionTokens,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** Responses API status → Chat Completions finish_reason */
|
|
98
|
+
function mapStatusToFinishReason(status) {
|
|
99
|
+
if (status === "incomplete")
|
|
100
|
+
return "length";
|
|
101
|
+
return "stop";
|
|
102
|
+
}
|
|
103
|
+
// ---------- Chat Completions → Responses ----------
|
|
104
|
+
/**
|
|
105
|
+
* Convert a Chat Completions response body to a Responses API response body.
|
|
106
|
+
*
|
|
107
|
+
* Lossy: Chat Completions has no equivalent for built-in tool output items
|
|
108
|
+
* or structured reasoning summaries.
|
|
109
|
+
*/
|
|
110
|
+
export function chatToResponsesResponse(bodyStr) {
|
|
111
|
+
const oai = JSON.parse(bodyStr);
|
|
112
|
+
const choices = (oai.choices ?? []);
|
|
113
|
+
const choice = choices[0];
|
|
114
|
+
const msg = choice?.message;
|
|
115
|
+
const output = [];
|
|
116
|
+
// reasoning_content → reasoning output
|
|
117
|
+
if (msg?.reasoning_content) {
|
|
118
|
+
output.push({
|
|
119
|
+
type: "reasoning",
|
|
120
|
+
id: `rs_${Date.now()}_0`,
|
|
121
|
+
summary: [{ type: "summary_text", text: String(msg.reasoning_content) }],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// text content → message output
|
|
125
|
+
if (msg?.content) {
|
|
126
|
+
output.push({
|
|
127
|
+
type: "message",
|
|
128
|
+
id: `msg_${Date.now()}_1`,
|
|
129
|
+
role: "assistant",
|
|
130
|
+
content: [{ type: "output_text", text: String(msg.content) }],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// tool_calls → function_call output items
|
|
134
|
+
const toolCalls = msg?.tool_calls;
|
|
135
|
+
if (toolCalls) {
|
|
136
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
137
|
+
const tc = toolCalls[i];
|
|
138
|
+
const fn = tc.function;
|
|
139
|
+
output.push({
|
|
140
|
+
type: "function_call",
|
|
141
|
+
id: String(tc.id ?? `fc_${i}`),
|
|
142
|
+
call_id: String(tc.id ?? `fc_${i}`),
|
|
143
|
+
name: String(fn?.name ?? ""),
|
|
144
|
+
arguments: String(fn?.arguments ?? "{}"),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Status mapping
|
|
149
|
+
const finishReason = String(choice?.finish_reason ?? "stop");
|
|
150
|
+
const status = mapFinishReasonToStatus(finishReason);
|
|
151
|
+
// Usage mapping
|
|
152
|
+
const oaiUsage = oai.usage;
|
|
153
|
+
const inputTokens = oaiUsage?.prompt_tokens ?? 0;
|
|
154
|
+
const outputTokens = oaiUsage?.completion_tokens ?? 0;
|
|
155
|
+
return JSON.stringify({
|
|
156
|
+
id: generateRespId(),
|
|
157
|
+
object: "response",
|
|
158
|
+
model: oai.model ?? "",
|
|
159
|
+
status,
|
|
160
|
+
output,
|
|
161
|
+
usage: {
|
|
162
|
+
input_tokens: inputTokens,
|
|
163
|
+
output_tokens: outputTokens,
|
|
164
|
+
total_tokens: inputTokens + outputTokens,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** Chat Completions finish_reason → Responses API status */
|
|
169
|
+
function mapFinishReasonToStatus(reason) {
|
|
170
|
+
if (reason === "length")
|
|
171
|
+
return "incomplete";
|
|
172
|
+
return "completed"; // "stop", "tool_calls", and unknown → completed
|
|
173
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { generateMsgId, generateRespId } from "./id-utils.js";
|
|
2
|
+
import { parseToolArguments } from "./sanitize.js";
|
|
3
|
+
// ---------- Status ↔ stop_reason mapping ----------
|
|
4
|
+
const RESP_STATUS_TO_STOP = {
|
|
5
|
+
completed: "end_turn",
|
|
6
|
+
incomplete: "max_tokens",
|
|
7
|
+
failed: "end_turn",
|
|
8
|
+
};
|
|
9
|
+
const ANT_STOP_TO_RESP_STATUS = {
|
|
10
|
+
end_turn: "completed",
|
|
11
|
+
stop_sequence: "completed",
|
|
12
|
+
tool_use: "completed",
|
|
13
|
+
max_tokens: "incomplete",
|
|
14
|
+
};
|
|
15
|
+
/** Responses API status → Anthropic stop_reason */
|
|
16
|
+
function mapStatusToStopReason(status) {
|
|
17
|
+
return RESP_STATUS_TO_STOP[status] ?? "end_turn";
|
|
18
|
+
}
|
|
19
|
+
/** Anthropic stop_reason → Responses API status */
|
|
20
|
+
function mapStopReasonToStatus(reason) {
|
|
21
|
+
return ANT_STOP_TO_RESP_STATUS[reason] ?? "completed";
|
|
22
|
+
}
|
|
23
|
+
// ---------- Responses → Anthropic ----------
|
|
24
|
+
export function responsesToAnthropicResponse(bodyStr) {
|
|
25
|
+
const resp = JSON.parse(bodyStr);
|
|
26
|
+
const output = resp.output ?? [];
|
|
27
|
+
const content = [];
|
|
28
|
+
for (const item of output) {
|
|
29
|
+
const type = item.type;
|
|
30
|
+
if (type === "message") {
|
|
31
|
+
// ResponseOutputMessage → text blocks
|
|
32
|
+
const msgContent = item.content ?? [];
|
|
33
|
+
for (const part of msgContent) {
|
|
34
|
+
if (part.type === "output_text" && part.text != null) {
|
|
35
|
+
content.push({ type: "text", text: String(part.text) });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (type === "function_call") {
|
|
40
|
+
// → tool_use block (Anthropic requires "toolu_" prefix)
|
|
41
|
+
const rawCallId = String(item.call_id ?? "");
|
|
42
|
+
const antId = rawCallId.startsWith("toolu_") ? rawCallId : `toolu_${rawCallId}`;
|
|
43
|
+
content.push({
|
|
44
|
+
type: "tool_use",
|
|
45
|
+
id: antId,
|
|
46
|
+
name: String(item.name ?? ""),
|
|
47
|
+
input: parseToolArguments(item.arguments),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else if (type === "reasoning") {
|
|
51
|
+
// → thinking block
|
|
52
|
+
const summary = item.summary;
|
|
53
|
+
const thinkingText = summary
|
|
54
|
+
? summary.map(s => String(s.text ?? "")).join("")
|
|
55
|
+
: "";
|
|
56
|
+
content.push({ type: "thinking", thinking: thinkingText });
|
|
57
|
+
}
|
|
58
|
+
// Other output types (web_search_call, etc.) → skip
|
|
59
|
+
}
|
|
60
|
+
if (content.length === 0) {
|
|
61
|
+
content.push({ type: "text", text: "" });
|
|
62
|
+
}
|
|
63
|
+
const usage = resp.usage;
|
|
64
|
+
return JSON.stringify({
|
|
65
|
+
id: generateMsgId(),
|
|
66
|
+
type: "message",
|
|
67
|
+
role: "assistant",
|
|
68
|
+
content,
|
|
69
|
+
model: resp.model ?? "",
|
|
70
|
+
stop_reason: mapStatusToStopReason(String(resp.status ?? "completed")),
|
|
71
|
+
stop_sequence: null,
|
|
72
|
+
usage: {
|
|
73
|
+
input_tokens: usage?.input_tokens ?? 0,
|
|
74
|
+
output_tokens: usage?.output_tokens ?? 0,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// ---------- Anthropic → Responses ----------
|
|
79
|
+
/** Strip "toolu_" prefix from a tool_use_id to recover the original call_id. */
|
|
80
|
+
function stripTooluPrefix(id) {
|
|
81
|
+
return id.startsWith("toolu_") ? id.slice(6) : id;
|
|
82
|
+
}
|
|
83
|
+
export function anthropicToResponsesResponse(bodyStr) {
|
|
84
|
+
const ant = JSON.parse(bodyStr);
|
|
85
|
+
const blocks = ant.content ?? [];
|
|
86
|
+
const output = [];
|
|
87
|
+
for (const block of blocks) {
|
|
88
|
+
const type = block.type;
|
|
89
|
+
if (type === "thinking") {
|
|
90
|
+
// → reasoning output
|
|
91
|
+
output.push({
|
|
92
|
+
type: "reasoning",
|
|
93
|
+
id: `rs_${Date.now()}_${output.length}`,
|
|
94
|
+
summary: [{ type: "summary_text", text: String(block.thinking ?? "") }],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else if (type === "text") {
|
|
98
|
+
// → message output
|
|
99
|
+
output.push({
|
|
100
|
+
type: "message",
|
|
101
|
+
id: generateMsgId(),
|
|
102
|
+
role: "assistant",
|
|
103
|
+
content: [{ type: "output_text", text: String(block.text ?? "") }],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (type === "tool_use") {
|
|
107
|
+
// → function_call output
|
|
108
|
+
const rawId = String(block.id ?? "");
|
|
109
|
+
const callId = stripTooluPrefix(rawId);
|
|
110
|
+
output.push({
|
|
111
|
+
type: "function_call",
|
|
112
|
+
id: `fc_${callId}`,
|
|
113
|
+
call_id: callId,
|
|
114
|
+
name: String(block.name ?? ""),
|
|
115
|
+
arguments: JSON.stringify(block.input ?? {}),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Usage mapping: Anthropic → Responses
|
|
120
|
+
const antUsage = ant.usage;
|
|
121
|
+
const inputTokens = (antUsage?.input_tokens ?? 0) +
|
|
122
|
+
(antUsage?.cache_read_input_tokens ?? 0) +
|
|
123
|
+
(antUsage?.cache_creation_input_tokens ?? 0);
|
|
124
|
+
const outputTokens = antUsage?.output_tokens ?? 0;
|
|
125
|
+
return JSON.stringify({
|
|
126
|
+
id: generateRespId(),
|
|
127
|
+
object: "response",
|
|
128
|
+
model: ant.model ?? "",
|
|
129
|
+
status: mapStopReasonToStatus(String(ant.stop_reason ?? "end_turn")),
|
|
130
|
+
output,
|
|
131
|
+
usage: {
|
|
132
|
+
input_tokens: inputTokens,
|
|
133
|
+
output_tokens: outputTokens,
|
|
134
|
+
total_tokens: inputTokens + outputTokens,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BaseSSETransform } from "./stream-transform-base.js";
|
|
2
|
+
export declare class AnthropicToResponsesTransform extends BaseSSETransform {
|
|
3
|
+
private state;
|
|
4
|
+
private responseId;
|
|
5
|
+
private outputIndex;
|
|
6
|
+
private sequenceNumber;
|
|
7
|
+
private hasResponseCreated;
|
|
8
|
+
private inputTokens;
|
|
9
|
+
private outputTokens;
|
|
10
|
+
private pendingStatus;
|
|
11
|
+
private activeToolCallId;
|
|
12
|
+
private collectedOutput;
|
|
13
|
+
private currentItemId;
|
|
14
|
+
private currentSummaryPartId;
|
|
15
|
+
private currentContentPartIndex;
|
|
16
|
+
private createdAt;
|
|
17
|
+
private nextSeq;
|
|
18
|
+
private emitResponseCreated;
|
|
19
|
+
protected processEvent(event: {
|
|
20
|
+
event?: string;
|
|
21
|
+
data?: string;
|
|
22
|
+
}): void;
|
|
23
|
+
private emitCompleted;
|
|
24
|
+
protected flushPendingData(): void;
|
|
25
|
+
protected ensureTerminated(): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
import { BaseSSETransform } from "./stream-transform-base.js";
|
|
3
|
+
import { generateRespId } from "./id-utils.js";
|
|
4
|
+
import { RESPONSES_SSE_EVENTS } from "./types-responses.js";
|
|
5
|
+
function randomHex(bytes) {
|
|
6
|
+
return randomBytes(bytes).toString("hex");
|
|
7
|
+
}
|
|
8
|
+
export class AnthropicToResponsesTransform extends BaseSSETransform {
|
|
9
|
+
state = "init";
|
|
10
|
+
responseId = generateRespId();
|
|
11
|
+
outputIndex = 0;
|
|
12
|
+
sequenceNumber = 0;
|
|
13
|
+
hasResponseCreated = false;
|
|
14
|
+
inputTokens = 0;
|
|
15
|
+
outputTokens = 0;
|
|
16
|
+
pendingStatus = null;
|
|
17
|
+
activeToolCallId = "";
|
|
18
|
+
collectedOutput = [];
|
|
19
|
+
currentItemId = "";
|
|
20
|
+
currentSummaryPartId = "";
|
|
21
|
+
currentContentPartIndex = 0;
|
|
22
|
+
createdAt = Math.floor(Date.now() / 1000);
|
|
23
|
+
nextSeq() {
|
|
24
|
+
return this.sequenceNumber++;
|
|
25
|
+
}
|
|
26
|
+
emitResponseCreated() {
|
|
27
|
+
if (this.hasResponseCreated)
|
|
28
|
+
return;
|
|
29
|
+
this.hasResponseCreated = true;
|
|
30
|
+
const base = {
|
|
31
|
+
id: this.responseId,
|
|
32
|
+
object: "response",
|
|
33
|
+
model: this.model,
|
|
34
|
+
status: "in_progress",
|
|
35
|
+
output: [],
|
|
36
|
+
created_at: this.createdAt,
|
|
37
|
+
};
|
|
38
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.CREATED, {
|
|
39
|
+
type: RESPONSES_SSE_EVENTS.CREATED,
|
|
40
|
+
response: { ...base, status: "queued" },
|
|
41
|
+
sequence_number: this.nextSeq(),
|
|
42
|
+
});
|
|
43
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.IN_PROGRESS, {
|
|
44
|
+
type: RESPONSES_SSE_EVENTS.IN_PROGRESS,
|
|
45
|
+
response: base,
|
|
46
|
+
sequence_number: this.nextSeq(),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
processEvent(event) {
|
|
50
|
+
let data;
|
|
51
|
+
try {
|
|
52
|
+
data = JSON.parse(event.data);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
this.emit("warning", err);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
switch (data.type) {
|
|
59
|
+
case "message_start": {
|
|
60
|
+
const msg = data.message;
|
|
61
|
+
const usage = msg?.usage;
|
|
62
|
+
this.inputTokens = usage?.input_tokens ?? 0;
|
|
63
|
+
this.emitResponseCreated();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "content_block_start": {
|
|
67
|
+
const block = data.content_block;
|
|
68
|
+
const blockType = block?.type;
|
|
69
|
+
if (blockType === "thinking") {
|
|
70
|
+
this.state = "thinking";
|
|
71
|
+
this.currentItemId = `rs_${randomHex(12)}`;
|
|
72
|
+
this.currentSummaryPartId = `sp_${randomHex(8)}`;
|
|
73
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED, {
|
|
74
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED,
|
|
75
|
+
output_index: this.outputIndex,
|
|
76
|
+
item: { type: "reasoning", id: this.currentItemId, summary: [] },
|
|
77
|
+
sequence_number: this.nextSeq(),
|
|
78
|
+
});
|
|
79
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_ADDED, {
|
|
80
|
+
type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_ADDED,
|
|
81
|
+
output_index: this.outputIndex,
|
|
82
|
+
summary_index: 0,
|
|
83
|
+
part: { type: "summary_text", text: "" },
|
|
84
|
+
sequence_number: this.nextSeq(),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (blockType === "text") {
|
|
88
|
+
this.state = "text";
|
|
89
|
+
this.currentItemId = `msg_${randomHex(12)}`;
|
|
90
|
+
this.currentContentPartIndex = 0;
|
|
91
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED, {
|
|
92
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED,
|
|
93
|
+
output_index: this.outputIndex,
|
|
94
|
+
item: { type: "message", id: this.currentItemId, role: "assistant", content: [], status: "in_progress" },
|
|
95
|
+
sequence_number: this.nextSeq(),
|
|
96
|
+
});
|
|
97
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.CONTENT_PART_ADDED, {
|
|
98
|
+
type: RESPONSES_SSE_EVENTS.CONTENT_PART_ADDED,
|
|
99
|
+
output_index: this.outputIndex,
|
|
100
|
+
content_index: 0,
|
|
101
|
+
part: { type: "output_text", text: "", annotations: [] },
|
|
102
|
+
sequence_number: this.nextSeq(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else if (blockType === "tool_use") {
|
|
106
|
+
this.state = "tool_use";
|
|
107
|
+
const toolId = block.id;
|
|
108
|
+
// Convert toolu_ prefix to fc_ prefix
|
|
109
|
+
this.activeToolCallId = toolId.startsWith("toolu_")
|
|
110
|
+
? `fc_${toolId.slice(6)}`
|
|
111
|
+
: `fc_${randomHex(12)}`;
|
|
112
|
+
const callId = this.activeToolCallId;
|
|
113
|
+
this.currentItemId = callId;
|
|
114
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED, {
|
|
115
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED,
|
|
116
|
+
output_index: this.outputIndex,
|
|
117
|
+
item: {
|
|
118
|
+
type: "function_call",
|
|
119
|
+
id: callId,
|
|
120
|
+
call_id: callId,
|
|
121
|
+
name: block.name,
|
|
122
|
+
arguments: "",
|
|
123
|
+
status: "in_progress",
|
|
124
|
+
},
|
|
125
|
+
sequence_number: this.nextSeq(),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "content_block_delta": {
|
|
131
|
+
const delta = data.delta;
|
|
132
|
+
const deltaType = delta?.type;
|
|
133
|
+
if (deltaType === "thinking_delta") {
|
|
134
|
+
const thinking = delta.thinking;
|
|
135
|
+
if (thinking) {
|
|
136
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DELTA, {
|
|
137
|
+
type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DELTA,
|
|
138
|
+
output_index: this.outputIndex,
|
|
139
|
+
summary_index: 0,
|
|
140
|
+
delta: thinking,
|
|
141
|
+
sequence_number: this.nextSeq(),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (deltaType === "text_delta") {
|
|
146
|
+
const text = delta.text;
|
|
147
|
+
if (text) {
|
|
148
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DELTA, {
|
|
149
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DELTA,
|
|
150
|
+
output_index: this.outputIndex,
|
|
151
|
+
content_index: 0,
|
|
152
|
+
delta: text,
|
|
153
|
+
sequence_number: this.nextSeq(),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else if (deltaType === "input_json_delta") {
|
|
158
|
+
const partialJson = delta.partial_json;
|
|
159
|
+
if (partialJson) {
|
|
160
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DELTA, {
|
|
161
|
+
type: RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DELTA,
|
|
162
|
+
output_index: this.outputIndex,
|
|
163
|
+
item_id: this.currentItemId,
|
|
164
|
+
call_id: this.activeToolCallId,
|
|
165
|
+
delta: partialJson,
|
|
166
|
+
sequence_number: this.nextSeq(),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "content_block_stop": {
|
|
173
|
+
if (this.state === "thinking") {
|
|
174
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DONE, {
|
|
175
|
+
type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DONE,
|
|
176
|
+
output_index: this.outputIndex,
|
|
177
|
+
summary_index: 0,
|
|
178
|
+
text: "",
|
|
179
|
+
sequence_number: this.nextSeq(),
|
|
180
|
+
});
|
|
181
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_DONE, {
|
|
182
|
+
type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_DONE,
|
|
183
|
+
output_index: this.outputIndex,
|
|
184
|
+
summary_index: 0,
|
|
185
|
+
part: { type: "summary_text", text: "" },
|
|
186
|
+
sequence_number: this.nextSeq(),
|
|
187
|
+
});
|
|
188
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE, {
|
|
189
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE,
|
|
190
|
+
output_index: this.outputIndex,
|
|
191
|
+
item: { type: "reasoning", id: this.currentItemId, summary: [{ type: "summary_text", text: "" }] },
|
|
192
|
+
sequence_number: this.nextSeq(),
|
|
193
|
+
});
|
|
194
|
+
this.collectedOutput.push({ type: "reasoning", id: this.currentItemId, summary: [{ type: "summary_text", text: "" }] });
|
|
195
|
+
}
|
|
196
|
+
else if (this.state === "text") {
|
|
197
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DONE, {
|
|
198
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DONE,
|
|
199
|
+
output_index: this.outputIndex,
|
|
200
|
+
content_index: 0,
|
|
201
|
+
text: "",
|
|
202
|
+
sequence_number: this.nextSeq(),
|
|
203
|
+
});
|
|
204
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.CONTENT_PART_DONE, {
|
|
205
|
+
type: RESPONSES_SSE_EVENTS.CONTENT_PART_DONE,
|
|
206
|
+
output_index: this.outputIndex,
|
|
207
|
+
content_index: 0,
|
|
208
|
+
part: { type: "output_text", text: "", annotations: [] },
|
|
209
|
+
sequence_number: this.nextSeq(),
|
|
210
|
+
});
|
|
211
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE, {
|
|
212
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE,
|
|
213
|
+
output_index: this.outputIndex,
|
|
214
|
+
item: { type: "message", id: this.currentItemId, role: "assistant", content: [{ type: "output_text", text: "", annotations: [] }], status: "completed" },
|
|
215
|
+
sequence_number: this.nextSeq(),
|
|
216
|
+
});
|
|
217
|
+
this.collectedOutput.push({
|
|
218
|
+
type: "message", id: this.currentItemId, role: "assistant",
|
|
219
|
+
content: [{ type: "output_text", text: "", annotations: [] }],
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
else if (this.state === "tool_use") {
|
|
223
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DONE, {
|
|
224
|
+
type: RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DONE,
|
|
225
|
+
output_index: this.outputIndex,
|
|
226
|
+
item_id: this.currentItemId,
|
|
227
|
+
call_id: this.activeToolCallId,
|
|
228
|
+
arguments: "",
|
|
229
|
+
sequence_number: this.nextSeq(),
|
|
230
|
+
});
|
|
231
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE, {
|
|
232
|
+
type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE,
|
|
233
|
+
output_index: this.outputIndex,
|
|
234
|
+
item: {
|
|
235
|
+
type: "function_call", id: this.activeToolCallId,
|
|
236
|
+
call_id: this.activeToolCallId,
|
|
237
|
+
name: "", arguments: "", status: "completed",
|
|
238
|
+
},
|
|
239
|
+
sequence_number: this.nextSeq(),
|
|
240
|
+
});
|
|
241
|
+
this.collectedOutput.push({
|
|
242
|
+
type: "function_call", id: this.activeToolCallId,
|
|
243
|
+
call_id: this.activeToolCallId, name: "", arguments: "",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
this.outputIndex++;
|
|
247
|
+
this.state = "init";
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "message_delta": {
|
|
251
|
+
const msgDelta = data.delta;
|
|
252
|
+
const usage = data.usage;
|
|
253
|
+
this.outputTokens = usage?.output_tokens ?? this.outputTokens;
|
|
254
|
+
const stopReason = msgDelta?.stop_reason;
|
|
255
|
+
if (stopReason === "tool_use") {
|
|
256
|
+
this.pendingStatus = "completed"; // will have function_calls → status remains completed
|
|
257
|
+
}
|
|
258
|
+
else if (stopReason === "max_tokens") {
|
|
259
|
+
this.pendingStatus = "incomplete";
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
this.pendingStatus = "completed";
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case "message_stop": {
|
|
267
|
+
this.emitCompleted();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case "error": {
|
|
271
|
+
const error = data.error;
|
|
272
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.ERROR, {
|
|
273
|
+
type: "error",
|
|
274
|
+
message: error?.message ?? "Stream error",
|
|
275
|
+
code: error?.type ?? "upstream_error",
|
|
276
|
+
});
|
|
277
|
+
this.pushDone();
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case "ping": {
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
default: {
|
|
284
|
+
this.emit("warning", { event: "unknown_event", type: data.type });
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
emitCompleted() {
|
|
290
|
+
const status = this.pendingStatus ?? "completed";
|
|
291
|
+
const completedAt = Math.floor(Date.now() / 1000);
|
|
292
|
+
const response = {
|
|
293
|
+
id: this.responseId,
|
|
294
|
+
object: "response",
|
|
295
|
+
model: this.model,
|
|
296
|
+
status: status,
|
|
297
|
+
output: this.collectedOutput,
|
|
298
|
+
usage: {
|
|
299
|
+
input_tokens: this.inputTokens,
|
|
300
|
+
output_tokens: this.outputTokens,
|
|
301
|
+
total_tokens: this.inputTokens + this.outputTokens,
|
|
302
|
+
},
|
|
303
|
+
created_at: this.createdAt,
|
|
304
|
+
completed_at: completedAt,
|
|
305
|
+
};
|
|
306
|
+
this.pushResponsesSSE(RESPONSES_SSE_EVENTS.COMPLETED, {
|
|
307
|
+
type: RESPONSES_SSE_EVENTS.COMPLETED,
|
|
308
|
+
response,
|
|
309
|
+
sequence_number: this.nextSeq(),
|
|
310
|
+
});
|
|
311
|
+
this.pushDone();
|
|
312
|
+
}
|
|
313
|
+
flushPendingData() {
|
|
314
|
+
// No buffered data to flush
|
|
315
|
+
}
|
|
316
|
+
ensureTerminated() {
|
|
317
|
+
if (!this.done) {
|
|
318
|
+
this.emitResponseCreated();
|
|
319
|
+
this.emitCompleted();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|