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.
Files changed (222) hide show
  1. package/README.en.md +319 -0
  2. package/README.md +2 -0
  3. package/config/recommended-providers.json +33 -9
  4. package/config/recommended-retry-rules.json +9 -8
  5. package/dist/admin/providers.js +11 -9
  6. package/dist/admin/quick-setup.d.ts +13 -0
  7. package/dist/admin/quick-setup.js +169 -0
  8. package/dist/admin/recommended.js +5 -1
  9. package/dist/admin/routes.js +2 -0
  10. package/dist/config/model-context.d.ts +8 -2
  11. package/dist/config/model-context.js +17 -5
  12. package/dist/config/recommended.d.ts +2 -1
  13. package/dist/config/recommended.js +5 -9
  14. package/dist/core/constants.js +2 -0
  15. package/dist/db/index.js +5 -0
  16. package/dist/db/migrations/033_add_adaptive_concurrency.sql +3 -0
  17. package/dist/db/migrations/036_add_openai_responses_api_type.sql +68 -0
  18. package/dist/db/migrations/037_fix_035_data_corruption.sql +54 -0
  19. package/dist/db/providers.d.ts +3 -3
  20. package/dist/index.js +7 -3
  21. package/dist/metrics/metrics-extractor.d.ts +3 -2
  22. package/dist/metrics/metrics-extractor.js +45 -0
  23. package/dist/metrics/sse-metrics-transform.d.ts +1 -1
  24. package/dist/metrics/sse-metrics-transform.js +10 -0
  25. package/dist/monitor/request-tracker.d.ts +1 -1
  26. package/dist/monitor/stream-content-accumulator.d.ts +1 -1
  27. package/dist/monitor/stream-extractor.d.ts +1 -1
  28. package/dist/monitor/stream-extractor.js +21 -0
  29. package/dist/monitor/types.d.ts +1 -1
  30. package/dist/proxy/handler/proxy-handler-utils.d.ts +1 -1
  31. package/dist/proxy/handler/proxy-handler.d.ts +1 -1
  32. package/dist/proxy/handler/proxy-handler.js +8 -2
  33. package/dist/proxy/handler/responses.d.ts +7 -0
  34. package/dist/proxy/handler/responses.js +48 -0
  35. package/dist/proxy/loop-prevention/tool-loop-guard.d.ts +1 -1
  36. package/dist/proxy/loop-prevention/tool-loop-guard.js +10 -0
  37. package/dist/proxy/orchestration/orchestrator.d.ts +1 -1
  38. package/dist/proxy/orchestration/semaphore.js +6 -0
  39. package/dist/proxy/patch/deepseek/index.d.ts +1 -1
  40. package/dist/proxy/patch/deepseek/patch-thinking-param.d.ts +1 -1
  41. package/dist/proxy/patch/index.d.ts +3 -0
  42. package/dist/proxy/patch/index.js +28 -0
  43. package/dist/proxy/patch/tool-round-limiter.d.ts +1 -1
  44. package/dist/proxy/patch/tool-round-limiter.js +16 -0
  45. package/dist/proxy/proxy-core.d.ts +1 -1
  46. package/dist/proxy/proxy-logging.d.ts +3 -3
  47. package/dist/proxy/response-transform.js +13 -0
  48. package/dist/proxy/transform/id-utils.d.ts +1 -0
  49. package/dist/proxy/transform/id-utils.js +3 -0
  50. package/dist/proxy/transform/plugin-types.d.ts +5 -5
  51. package/dist/proxy/transform/request-bridge-responses.d.ts +19 -0
  52. package/dist/proxy/transform/request-bridge-responses.js +311 -0
  53. package/dist/proxy/transform/request-transform-responses.d.ts +2 -0
  54. package/dist/proxy/transform/request-transform-responses.js +350 -0
  55. package/dist/proxy/transform/response-bridge-responses.d.ts +23 -0
  56. package/dist/proxy/transform/response-bridge-responses.js +173 -0
  57. package/dist/proxy/transform/response-transform-responses.d.ts +2 -0
  58. package/dist/proxy/transform/response-transform-responses.js +137 -0
  59. package/dist/proxy/transform/stream-ant2resp.d.ts +26 -0
  60. package/dist/proxy/transform/stream-ant2resp.js +322 -0
  61. package/dist/proxy/transform/stream-bridge-chat2resp.d.ts +40 -0
  62. package/dist/proxy/transform/stream-bridge-chat2resp.js +382 -0
  63. package/dist/proxy/transform/stream-bridge-resp2chat.d.ts +24 -0
  64. package/dist/proxy/transform/stream-bridge-resp2chat.js +237 -0
  65. package/dist/proxy/transform/stream-resp2ant.d.ts +21 -0
  66. package/dist/proxy/transform/stream-resp2ant.js +238 -0
  67. package/dist/proxy/transform/stream-transform-base.d.ts +1 -0
  68. package/dist/proxy/transform/stream-transform-base.js +3 -0
  69. package/dist/proxy/transform/transform-coordinator.d.ts +1 -0
  70. package/dist/proxy/transform/transform-coordinator.js +127 -8
  71. package/dist/proxy/transform/types-responses.d.ts +177 -0
  72. package/dist/proxy/transform/types-responses.js +27 -0
  73. package/dist/proxy/transform/types.d.ts +3 -1
  74. package/dist/proxy/transport/transport-fn.d.ts +1 -1
  75. package/frontend-dist/assets/CardContent-BhMXx-JD.js +1 -0
  76. package/frontend-dist/assets/CardTitle-DQDjTee3.js +1 -0
  77. package/frontend-dist/assets/CascadingModelSelect-JBQq3JJt.js +1 -0
  78. package/frontend-dist/assets/Checkbox-ByxbKP_C.js +1 -0
  79. package/frontend-dist/assets/CollapsibleContent-GecW2Jk_.js +1 -0
  80. package/frontend-dist/assets/CollapsibleTrigger-Cib3-OsK.js +1 -0
  81. package/frontend-dist/assets/Collection-Dbvdpa0m.js +1 -0
  82. package/frontend-dist/assets/Dashboard-3MJPLflT.js +3 -0
  83. package/frontend-dist/assets/DialogTitle-Ej_rtfV1.js +1 -0
  84. package/frontend-dist/assets/{Input-CAnKUBBK.js → Input-tcnrMp1v.js} +1 -1
  85. package/frontend-dist/assets/Label-BwzPFyL-.js +1 -0
  86. package/frontend-dist/assets/Login-Cdsw2pWC.js +1 -0
  87. package/frontend-dist/assets/Logs-5_CWiws5.js +1 -0
  88. package/frontend-dist/assets/MappingList-D8HRph05.js +1 -0
  89. package/frontend-dist/assets/ModelCard-CZbQcYNn.js +1 -0
  90. package/frontend-dist/assets/ModelMappings-CJqgl7O8.js +1 -0
  91. package/frontend-dist/assets/Monitor-B8v5a8fB.js +1 -0
  92. package/frontend-dist/assets/PopoverTrigger-C88SpJNZ.js +1 -0
  93. package/frontend-dist/assets/PopperContent-6BXua_FZ.js +1 -0
  94. package/frontend-dist/assets/Providers-DH0nvlGn.js +1 -0
  95. package/frontend-dist/assets/ProxyEnhancement-CAH-44W-.js +5 -0
  96. package/frontend-dist/assets/QuickSetup-CsDO-ZGP.js +1 -0
  97. package/frontend-dist/assets/RetryRules-8iT9fLsH.js +1 -0
  98. package/frontend-dist/assets/RouterKeys-BFoEmWgj.js +1 -0
  99. package/frontend-dist/assets/RovingFocusItem-DdPUFQHC.js +1 -0
  100. package/frontend-dist/assets/Schedules-B8Se31u4.js +1 -0
  101. package/frontend-dist/assets/SelectValue-CT2z_-6j.js +1 -0
  102. package/frontend-dist/assets/Settings-BHvtsJKD.js +6 -0
  103. package/frontend-dist/assets/Setup-k-l9KDC0.js +1 -0
  104. package/frontend-dist/assets/Switch-D1NdA4ax.js +1 -0
  105. package/frontend-dist/assets/TableHeader-CcMyOsUB.js +1 -0
  106. package/frontend-dist/assets/Teleport-Bmeh33lB.js +3 -0
  107. package/frontend-dist/assets/TooltipTrigger-LegC_Uvp.js +1 -0
  108. package/frontend-dist/assets/UnifiedRequestDialog-BVw6W2pk.js +3 -0
  109. package/frontend-dist/assets/UnifiedRequestDialog-C4MTxb25.css +1 -0
  110. package/frontend-dist/assets/VisuallyHidden-ogESfc9X.js +1 -0
  111. package/frontend-dist/assets/VisuallyHiddenInput-BQemVGau.js +1 -0
  112. package/frontend-dist/assets/alert-dialog-DzKCAoYJ.js +1 -0
  113. package/frontend-dist/assets/badge-C-9zPTgw.js +1 -0
  114. package/frontend-dist/assets/button-D27ClX8J.js +14 -0
  115. package/frontend-dist/assets/check-yTAivq1h.js +1 -0
  116. package/frontend-dist/assets/common-CWCbKHOK.js +1 -0
  117. package/frontend-dist/assets/common-D4xnnaqi.js +1 -0
  118. package/frontend-dist/assets/constants-B-VELBjk.js +1 -0
  119. package/frontend-dist/assets/copy-DWG9cQPR.js +1 -0
  120. package/frontend-dist/assets/dashboard-B8eI-t8c.js +1 -0
  121. package/frontend-dist/assets/dashboard-Dbe6A2lu.js +1 -0
  122. package/frontend-dist/assets/dialog-BnYR6_dh.js +1 -0
  123. package/frontend-dist/assets/file-text-D33FJAPX.js +1 -0
  124. package/frontend-dist/assets/format-BhxQSgt6.js +1 -0
  125. package/frontend-dist/assets/i18n-CwUfS0tE.js +1 -0
  126. package/frontend-dist/assets/index-B348nt-T.css +1 -0
  127. package/frontend-dist/assets/index-DPRxBo3N.js +1 -0
  128. package/frontend-dist/assets/lib-D0Ek2pPZ.js +1 -0
  129. package/frontend-dist/assets/loader-circle-EpKC006I.js +1 -0
  130. package/frontend-dist/assets/login-BTolYxVI.js +1 -0
  131. package/frontend-dist/assets/login-w_ICpiU5.js +1 -0
  132. package/frontend-dist/assets/logs-7dT2uyMa.js +1 -0
  133. package/frontend-dist/assets/logs-_3w8tDQa.js +1 -0
  134. package/frontend-dist/assets/mappings-Bbn3r2uJ.js +1 -0
  135. package/frontend-dist/assets/mappings-CTZ-zb1x.js +1 -0
  136. package/frontend-dist/assets/monitor-DN5m5n_x.js +1 -0
  137. package/frontend-dist/assets/monitor-DysWEOtt.js +1 -0
  138. package/frontend-dist/assets/providers-C1gQGzwa.js +1 -0
  139. package/frontend-dist/assets/providers-CCfko___.js +1 -0
  140. package/frontend-dist/assets/proxyEnhancement-BItabyLo.js +1 -0
  141. package/frontend-dist/assets/proxyEnhancement-DeMb7wIE.js +1 -0
  142. package/frontend-dist/assets/quickSetup-C75HMC_z.js +1 -0
  143. package/frontend-dist/assets/quickSetup-DStZWiuf.js +1 -0
  144. package/frontend-dist/assets/requestDetail-BoaPEQs-.js +1 -0
  145. package/frontend-dist/assets/requestDetail-CM5kFgy6.js +1 -0
  146. package/frontend-dist/assets/retryRules-CIF37gOl.js +1 -0
  147. package/frontend-dist/assets/retryRules-o_D8S5gy.js +1 -0
  148. package/frontend-dist/assets/routerKeys-BAvjW0V8.js +1 -0
  149. package/frontend-dist/assets/routerKeys-mQt2YPuE.js +1 -0
  150. package/frontend-dist/assets/schedules-BCV2rxK-.js +1 -0
  151. package/frontend-dist/assets/schedules-Qte9b7b_.js +1 -0
  152. package/frontend-dist/assets/settings-Bgu2lJfy.js +1 -0
  153. package/frontend-dist/assets/settings-UCmMSq_F.js +1 -0
  154. package/frontend-dist/assets/setup-B_fAfMoV.js +1 -0
  155. package/frontend-dist/assets/setup-Chc246Zi.js +1 -0
  156. package/frontend-dist/assets/sidebar-B7rejnZA.js +1 -0
  157. package/frontend-dist/assets/sidebar-CBMItLst.js +1 -0
  158. package/frontend-dist/assets/sun-BylRZIWt.js +1 -0
  159. package/frontend-dist/assets/trash-2-QNFff7V4.js +1 -0
  160. package/frontend-dist/assets/{useClipboard-BmmsNSGV.js → useClipboard-BFt5f-_-.js} +1 -1
  161. package/frontend-dist/assets/{useFocusGuards-A-9V2Y-b.js → useFocusGuards-DQBZKWnu.js} +1 -1
  162. package/frontend-dist/assets/useFormControl-T2RQNBqs.js +1 -0
  163. package/frontend-dist/assets/useLogRetention-NrrZrpPE.js +1 -0
  164. package/frontend-dist/assets/useNonce-DR38uny5.js +1 -0
  165. package/frontend-dist/assets/useTheme-CpTI547G.js +1 -0
  166. package/frontend-dist/assets/x-DSgLgKC_.js +1 -0
  167. package/frontend-dist/index.html +25 -22
  168. package/package.json +1 -1
  169. package/dist/db/migrations/033_add_pipeline_snapshot.sql +0 -1
  170. package/frontend-dist/assets/CardContent-BVMQ2_pg.js +0 -1
  171. package/frontend-dist/assets/CardTitle-GLv7QyIY.js +0 -1
  172. package/frontend-dist/assets/CascadingModelSelect-CBhqKFDX.js +0 -1
  173. package/frontend-dist/assets/Checkbox-HPVDmEdV.js +0 -1
  174. package/frontend-dist/assets/CollapsibleTrigger-DhxD9tpM.js +0 -1
  175. package/frontend-dist/assets/Collection-BRt7YxN8.js +0 -1
  176. package/frontend-dist/assets/Dashboard-D1Ys8Zog.js +0 -3
  177. package/frontend-dist/assets/DialogTitle-23q73lwF.js +0 -1
  178. package/frontend-dist/assets/Label-DWdYtVMI.js +0 -1
  179. package/frontend-dist/assets/Login-w5WFOinP.js +0 -1
  180. package/frontend-dist/assets/Logs-C1F1ZmWF.js +0 -1
  181. package/frontend-dist/assets/ModelMappings-BzmecWEH.js +0 -1
  182. package/frontend-dist/assets/Monitor-DrAZFTKR.js +0 -1
  183. package/frontend-dist/assets/PopoverTrigger-Bj65uUbv.js +0 -1
  184. package/frontend-dist/assets/PopperContent-gzzf1XHe.js +0 -1
  185. package/frontend-dist/assets/Providers-DSgf4mb6.js +0 -1
  186. package/frontend-dist/assets/ProxyEnhancement-Bb1cCP6d.js +0 -5
  187. package/frontend-dist/assets/RetryRules-BwPfEZtm.js +0 -1
  188. package/frontend-dist/assets/RouterKeys-CzTSq1Mx.js +0 -1
  189. package/frontend-dist/assets/RovingFocusItem-CXM_Yfkm.js +0 -1
  190. package/frontend-dist/assets/Schedules-DVilCXrC.js +0 -1
  191. package/frontend-dist/assets/SelectValue-C0-LzGQY.js +0 -1
  192. package/frontend-dist/assets/Settings-Bpk53zVX.js +0 -6
  193. package/frontend-dist/assets/Setup-Dn7EgC49.js +0 -1
  194. package/frontend-dist/assets/Switch-BO8Ooae6.js +0 -1
  195. package/frontend-dist/assets/TableHeader-Bded9VTC.js +0 -1
  196. package/frontend-dist/assets/TabsTrigger-BzKMi9AF.js +0 -1
  197. package/frontend-dist/assets/Teleport-DizRK5O3.js +0 -3
  198. package/frontend-dist/assets/TooltipTrigger-EiIy2zn8.js +0 -1
  199. package/frontend-dist/assets/UnifiedRequestDialog-BABsTaGb.js +0 -3
  200. package/frontend-dist/assets/UnifiedRequestDialog-BjEigSaR.css +0 -1
  201. package/frontend-dist/assets/VisuallyHidden-5AozJQza.js +0 -1
  202. package/frontend-dist/assets/VisuallyHiddenInput-DdiZrV2i.js +0 -1
  203. package/frontend-dist/assets/alert-dialog-DlKUuTPe.js +0 -1
  204. package/frontend-dist/assets/arrow-down-CxWKmZ2I.js +0 -1
  205. package/frontend-dist/assets/badge-9KJEMa53.js +0 -1
  206. package/frontend-dist/assets/button-Ul8WlrM5.js +0 -12
  207. package/frontend-dist/assets/check-7ahK--N4.js +0 -1
  208. package/frontend-dist/assets/constants-D_0jiLjw.js +0 -1
  209. package/frontend-dist/assets/copy-DzU2pAMG.js +0 -1
  210. package/frontend-dist/assets/dialog-B9j-FMrd.js +0 -1
  211. package/frontend-dist/assets/file-text-Bj3ZIo-E.js +0 -1
  212. package/frontend-dist/assets/format-Dln15Luw.js +0 -1
  213. package/frontend-dist/assets/index-Bz_ZaXNn.css +0 -1
  214. package/frontend-dist/assets/index-MedWZMHB.js +0 -1
  215. package/frontend-dist/assets/lib-Hhs3NqfD.js +0 -1
  216. package/frontend-dist/assets/loader-circle-5TJUukEe.js +0 -1
  217. package/frontend-dist/assets/useFormControl-DEO19lRe.js +0 -1
  218. package/frontend-dist/assets/useLogRetention-BfnBFZ5K.js +0 -1
  219. package/frontend-dist/assets/useNonce-BfwUJ1Ci.js +0 -1
  220. package/frontend-dist/assets/x-Cfopt3QL.js +0 -1
  221. /package/dist/db/migrations/{034_drop_redundant_log_columns.sql → 035_drop_redundant_log_columns.sql} +0 -0
  222. /package/frontend-dist/assets/{ohash.D__AXeF1-D5e5Wyzx.js → ohash.D__AXeF1-CTo5WcIm.js} +0 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Bridge (lossy) request 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 `previous_response_id`, built-in tools, or structured
8
+ * reasoning items.
9
+ */
10
+ // ---------- Responses → Chat Completions ----------
11
+ /**
12
+ * Convert an OpenAI Responses API request body to an OpenAI Chat Completions
13
+ * request body.
14
+ */
15
+ export function responsesToChatRequest(body) {
16
+ const result = {};
17
+ result.model = body.model;
18
+ // instructions → system message
19
+ const messages = [];
20
+ if (body.instructions != null && body.instructions !== "") {
21
+ messages.push({ role: "system", content: String(body.instructions) });
22
+ }
23
+ // input → messages
24
+ convertResponsesInputToChatMessages(body.input, messages);
25
+ result.messages = messages;
26
+ // max_output_tokens → max_completion_tokens
27
+ if (body.max_output_tokens != null) {
28
+ result.max_completion_tokens = body.max_output_tokens;
29
+ }
30
+ // Pass-through fields
31
+ if (body.temperature != null)
32
+ result.temperature = body.temperature;
33
+ if (body.top_p != null)
34
+ result.top_p = body.top_p;
35
+ if (body.stream != null)
36
+ result.stream = body.stream;
37
+ // tools: Responses format → Chat Completions format
38
+ const tools = body.tools;
39
+ if (tools) {
40
+ const chatTools = [];
41
+ for (const t of tools) {
42
+ if (t.type === "function") {
43
+ // Responses tools are flat: {type:"function", name, parameters, description}
44
+ // Chat tools need function wrapper: {type:"function", function:{name, parameters}}
45
+ const fn = { name: String(t.name) };
46
+ if (t.description != null)
47
+ fn.description = String(t.description);
48
+ if (t.parameters != null)
49
+ fn.parameters = t.parameters;
50
+ chatTools.push({ type: "function", function: fn });
51
+ }
52
+ // Non-function tools (web_search_preview, file_search, etc.) → skip
53
+ }
54
+ if (chatTools.length > 0) {
55
+ result.tools = chatTools;
56
+ }
57
+ }
58
+ // tool_choice — compatible between Chat and Responses
59
+ if (body.tool_choice != null) {
60
+ result.tool_choice = body.tool_choice;
61
+ }
62
+ // reasoning — pass through (both use {effort?, max_tokens?})
63
+ if (body.reasoning != null) {
64
+ result.reasoning = body.reasoning;
65
+ }
66
+ // text.format → response_format
67
+ const text = body.text;
68
+ if (text?.format != null) {
69
+ result.response_format = text.format;
70
+ }
71
+ // stream_options
72
+ if (body.stream_options != null) {
73
+ result.stream_options = body.stream_options;
74
+ }
75
+ return result;
76
+ }
77
+ /**
78
+ * Convert Responses `input` (string | ResponseInputItem[]) into Chat
79
+ * Completions `messages[]`, appending to the provided array.
80
+ */
81
+ function convertResponsesInputToChatMessages(input, messages) {
82
+ if (input == null)
83
+ return;
84
+ // String shorthand → single user message
85
+ if (typeof input === "string") {
86
+ messages.push({ role: "user", content: input });
87
+ return;
88
+ }
89
+ if (!Array.isArray(input))
90
+ return;
91
+ // Track pending function_calls to merge into a single assistant message
92
+ const pendingFnCalls = [];
93
+ for (const item of input) {
94
+ const type = item.type;
95
+ // Flush any pending function_calls before processing non-function_call items
96
+ if (type !== "function_call" && pendingFnCalls.length > 0) {
97
+ flushFunctionCalls(messages, pendingFnCalls);
98
+ }
99
+ if (type === "message") {
100
+ // ResponseInputMessage → Chat message
101
+ const role = item.role;
102
+ const content = extractMessageTextContent(item);
103
+ messages.push({ role, content });
104
+ }
105
+ else if (type === "input_text") {
106
+ messages.push({ role: "user", content: String(item.text ?? "") });
107
+ }
108
+ else if (type === "function_call") {
109
+ // Collect; will be flushed when next non-function_call item appears
110
+ // or at end of loop
111
+ const fn = {
112
+ name: String(item.name ?? ""),
113
+ arguments: String(item.arguments ?? "{}"),
114
+ };
115
+ pendingFnCalls.push({
116
+ id: String(item.id ?? ""),
117
+ type: "function",
118
+ function: fn,
119
+ });
120
+ }
121
+ else if (type === "function_call_output") {
122
+ messages.push({
123
+ role: "tool",
124
+ tool_call_id: String(item.call_id ?? ""),
125
+ content: String(item.output ?? ""),
126
+ });
127
+ }
128
+ else if (type === "reasoning") {
129
+ // No Chat Completions equivalent — skip
130
+ }
131
+ // Unknown item types → skip
132
+ }
133
+ // Flush any remaining pending function_calls
134
+ if (pendingFnCalls.length > 0) {
135
+ flushFunctionCalls(messages, pendingFnCalls);
136
+ }
137
+ }
138
+ /**
139
+ * Flush accumulated function_call tool_calls into a single assistant message.
140
+ */
141
+ function flushFunctionCalls(messages, pending) {
142
+ messages.push({
143
+ role: "assistant",
144
+ content: null,
145
+ tool_calls: [...pending],
146
+ });
147
+ pending.length = 0;
148
+ }
149
+ /**
150
+ * Extract text content from a ResponseInputMessage.
151
+ */
152
+ function extractMessageTextContent(msg) {
153
+ const content = msg.content;
154
+ if (content == null)
155
+ return "";
156
+ if (typeof content === "string")
157
+ return content;
158
+ if (Array.isArray(content)) {
159
+ return content
160
+ .filter((p) => p.type === "input_text" && p.text != null)
161
+ .map((p) => String(p.text))
162
+ .join("");
163
+ }
164
+ return "";
165
+ }
166
+ // ---------- Chat Completions → Responses ----------
167
+ /**
168
+ * Convert an OpenAI Chat Completions request body to an OpenAI Responses API
169
+ * request body.
170
+ */
171
+ export function chatToResponsesRequest(body) {
172
+ const result = {};
173
+ result.model = body.model;
174
+ // Extract instructions from system/developer messages
175
+ const messages = body.messages;
176
+ const { instructions, nonSystemMsgs } = extractChatInstructions(messages ?? []);
177
+ if (instructions) {
178
+ result.instructions = instructions;
179
+ }
180
+ // Convert non-system messages → input items
181
+ result.input = convertChatMessagesToResponsesInput(nonSystemMsgs);
182
+ // max_completion_tokens / max_tokens → max_output_tokens
183
+ if (body.max_completion_tokens != null) {
184
+ result.max_output_tokens = body.max_completion_tokens;
185
+ }
186
+ else if (body.max_tokens != null) {
187
+ result.max_output_tokens = body.max_tokens;
188
+ }
189
+ // Pass-through fields
190
+ if (body.temperature != null)
191
+ result.temperature = body.temperature;
192
+ if (body.top_p != null)
193
+ result.top_p = body.top_p;
194
+ if (body.stream != null)
195
+ result.stream = body.stream;
196
+ // tools: Chat format → Responses format
197
+ const tools = body.tools;
198
+ if (tools) {
199
+ const respTools = [];
200
+ for (const t of tools) {
201
+ if (t.type === "function" && t.function) {
202
+ // Chat: {type:"function", function:{name, parameters, description}}
203
+ // Responses: {type:"function", name, parameters, description}
204
+ const fn = t.function;
205
+ const mapped = {
206
+ type: "function",
207
+ name: String(fn.name),
208
+ };
209
+ if (fn.description != null)
210
+ mapped.description = String(fn.description);
211
+ if (fn.parameters != null)
212
+ mapped.parameters = fn.parameters;
213
+ respTools.push(mapped);
214
+ }
215
+ // Non-function tools → skip
216
+ }
217
+ if (respTools.length > 0) {
218
+ result.tools = respTools;
219
+ }
220
+ }
221
+ // tool_choice — compatible
222
+ if (body.tool_choice != null) {
223
+ result.tool_choice = body.tool_choice;
224
+ }
225
+ // reasoning — pass through
226
+ if (body.reasoning != null) {
227
+ result.reasoning = body.reasoning;
228
+ }
229
+ // response_format → text.format
230
+ if (body.response_format != null) {
231
+ result.text = { format: body.response_format };
232
+ }
233
+ // stream_options
234
+ if (body.stream_options != null) {
235
+ result.stream_options = body.stream_options;
236
+ }
237
+ return result;
238
+ }
239
+ /**
240
+ * Extract system/developer messages from Chat messages as instructions.
241
+ */
242
+ function extractChatInstructions(messages) {
243
+ const parts = [];
244
+ const nonSystemMsgs = [];
245
+ for (const msg of messages) {
246
+ const role = msg.role;
247
+ if (role === "system" || role === "developer") {
248
+ parts.push(String(msg.content ?? ""));
249
+ }
250
+ else {
251
+ nonSystemMsgs.push(msg);
252
+ }
253
+ }
254
+ return {
255
+ instructions: parts.length > 0 ? parts.join("\n") : "",
256
+ nonSystemMsgs,
257
+ };
258
+ }
259
+ /**
260
+ * Convert Chat Completions non-system messages → Responses input items.
261
+ */
262
+ function convertChatMessagesToResponsesInput(messages) {
263
+ const items = [];
264
+ for (const msg of messages) {
265
+ const role = msg.role;
266
+ if (role === "user") {
267
+ const content = msg.content;
268
+ const text = typeof content === "string" ? content : String(content ?? "");
269
+ items.push({
270
+ type: "message",
271
+ role: "user",
272
+ content: [{ type: "input_text", text }],
273
+ });
274
+ }
275
+ else if (role === "assistant") {
276
+ // Text content → assistant message with output_text
277
+ const content = msg.content;
278
+ if (content != null && content !== "" && content !== null) {
279
+ const text = typeof content === "string" ? content : String(content);
280
+ items.push({
281
+ type: "message",
282
+ role: "assistant",
283
+ content: [{ type: "output_text", text }],
284
+ });
285
+ }
286
+ // tool_calls → function_call items
287
+ const toolCalls = msg.tool_calls;
288
+ if (toolCalls) {
289
+ for (const tc of toolCalls) {
290
+ const fn = tc.function;
291
+ items.push({
292
+ type: "function_call",
293
+ id: String(tc.id ?? ""),
294
+ call_id: String(tc.id ?? ""),
295
+ name: String(fn?.name ?? ""),
296
+ arguments: String(fn?.arguments ?? "{}"),
297
+ });
298
+ }
299
+ }
300
+ }
301
+ else if (role === "tool") {
302
+ items.push({
303
+ type: "function_call_output",
304
+ call_id: String(msg.tool_call_id ?? ""),
305
+ output: String(msg.content ?? ""),
306
+ });
307
+ }
308
+ // reasoning_content in messages → skip (can't create reasoning items)
309
+ }
310
+ return items;
311
+ }
@@ -0,0 +1,2 @@
1
+ export declare function responsesToAnthropicRequest(body: Record<string, unknown>): Record<string, unknown>;
2
+ export declare function anthropicToResponsesRequest(body: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,350 @@
1
+ import { sanitizeToolUseId, parseToolArguments } from "./sanitize.js";
2
+ // ---------- Effort → budget mapping (shared with thinking-mapper) ----------
3
+ const EFFORT_BUDGET = { low: 1024, medium: 8192, high: 32768 };
4
+ const DEFAULT_BUDGET = 8192;
5
+ // ---------- Helpers ----------
6
+ /** Strip "toolu_" prefix from a tool_use_id to recover the original call_id. */
7
+ function stripTooluPrefix(id) {
8
+ return id.startsWith("toolu_") ? id.slice(6) : id;
9
+ }
10
+ /** Merge consecutive same-role messages to satisfy Anthropic strict alternation. */
11
+ function mergeConsecutiveMessages(msgs) {
12
+ const merged = [];
13
+ for (const msg of msgs) {
14
+ const prev = merged[merged.length - 1];
15
+ if (prev && prev.role === msg.role) {
16
+ prev.content = [...prev.content, ...msg.content];
17
+ }
18
+ else {
19
+ merged.push({ ...msg, content: [...msg.content] });
20
+ }
21
+ }
22
+ return merged;
23
+ }
24
+ /** Ensure first message has role "user" (prepend empty user if needed). */
25
+ function ensureFirstIsUser(msgs) {
26
+ if (msgs.length > 0 && msgs[0].role !== "user") {
27
+ msgs.unshift({ role: "user", content: [{ type: "text", text: "" }] });
28
+ }
29
+ return msgs;
30
+ }
31
+ // ---------- Responses → Anthropic ----------
32
+ export function responsesToAnthropicRequest(body) {
33
+ const result = {};
34
+ result.model = body.model;
35
+ // instructions → system
36
+ if (body.instructions != null) {
37
+ result.system = String(body.instructions);
38
+ }
39
+ // input → messages
40
+ result.messages = convertResponsesInputToAntMessages(body.input);
41
+ // max_output_tokens → max_tokens
42
+ if (body.max_output_tokens != null) {
43
+ result.max_tokens = body.max_output_tokens;
44
+ }
45
+ // temperature, top_p, stream — pass through
46
+ if (body.temperature != null)
47
+ result.temperature = body.temperature;
48
+ if (body.top_p != null)
49
+ result.top_p = body.top_p;
50
+ if (body.stream != null)
51
+ result.stream = body.stream;
52
+ // tools: only function-type tools are forwarded
53
+ const tools = body.tools;
54
+ if (tools) {
55
+ const fnTools = tools.filter(t => t.type === "function");
56
+ if (fnTools.length > 0 && body.tool_choice !== "none") {
57
+ result.tools = fnTools.map(t => {
58
+ const mapped = { name: String(t.name) };
59
+ if (t.description != null)
60
+ mapped.description = String(t.description);
61
+ if (t.parameters != null)
62
+ mapped.input_schema = t.parameters;
63
+ return mapped;
64
+ });
65
+ // tool_choice mapping
66
+ if (body.tool_choice != null && body.tool_choice !== "none") {
67
+ const tc = mapToolChoiceResponses2Ant(body.tool_choice);
68
+ if (tc != null) {
69
+ result.tool_choice = body.parallel_tool_calls === false
70
+ ? { ...tc, disable_parallel_tool_use: true }
71
+ : tc;
72
+ }
73
+ }
74
+ else if (body.parallel_tool_calls === false) {
75
+ result.tool_choice = { type: "auto", disable_parallel_tool_use: true };
76
+ }
77
+ }
78
+ }
79
+ // reasoning → thinking
80
+ if (body.reasoning) {
81
+ const reasoning = body.reasoning;
82
+ const effort = reasoning.effort;
83
+ const maxTokens = reasoning.max_tokens;
84
+ const budget = maxTokens ?? EFFORT_BUDGET[effort ?? ""] ?? DEFAULT_BUDGET;
85
+ result.thinking = { type: "enabled", budget_tokens: budget };
86
+ // Ensure max_tokens >= budget_tokens
87
+ if (result.max_tokens != null && result.max_tokens < budget) {
88
+ result.max_tokens = budget;
89
+ }
90
+ }
91
+ // metadata.user_id
92
+ const meta = body.metadata;
93
+ if (meta?.user_id) {
94
+ result.metadata = { user_id: meta.user_id };
95
+ }
96
+ return result;
97
+ }
98
+ /** Convert Responses input (string | ResponseInputItem[]) → Anthropic messages. */
99
+ function convertResponsesInputToAntMessages(input) {
100
+ if (input == null)
101
+ return [];
102
+ // String shorthand → single user message
103
+ if (typeof input === "string") {
104
+ return [{ role: "user", content: [{ type: "text", text: input }] }];
105
+ }
106
+ if (!Array.isArray(input))
107
+ return [];
108
+ const raw = [];
109
+ for (const item of input) {
110
+ const type = item.type;
111
+ if (type === "message") {
112
+ // ResponseInputMessage: extract content as AnthropicContentBlock[]
113
+ const role = item.role;
114
+ const content = extractMessageContent(item);
115
+ raw.push({ role, content });
116
+ }
117
+ else if (type === "function_call") {
118
+ // → assistant tool_use (Anthropic requires "toolu_" prefix)
119
+ const rawId = String(item.call_id ?? item.id ?? "");
120
+ const antId = rawId.startsWith("toolu_") ? rawId : `toolu_${rawId}`;
121
+ raw.push({
122
+ role: "assistant",
123
+ content: [{
124
+ type: "tool_use",
125
+ id: sanitizeToolUseId(antId),
126
+ name: String(item.name ?? ""),
127
+ input: parseToolArguments(item.arguments),
128
+ }],
129
+ });
130
+ }
131
+ else if (type === "function_call_output") {
132
+ // → user tool_result (Anthropic requires "toolu_" prefix)
133
+ const rawCallId = String(item.call_id ?? "");
134
+ const antCallId = rawCallId.startsWith("toolu_") ? rawCallId : `toolu_${rawCallId}`;
135
+ raw.push({
136
+ role: "user",
137
+ content: [{
138
+ type: "tool_result",
139
+ tool_use_id: sanitizeToolUseId(antCallId),
140
+ content: String(item.output ?? ""),
141
+ }],
142
+ });
143
+ }
144
+ else if (type === "reasoning") {
145
+ // → assistant thinking
146
+ const summary = item.summary;
147
+ const thinkingText = summary
148
+ ? summary.map(s => String(s.text ?? "")).join("\n")
149
+ : "";
150
+ raw.push({
151
+ role: "assistant",
152
+ content: [{ type: "thinking", thinking: thinkingText }],
153
+ });
154
+ }
155
+ else if (type === "input_text") {
156
+ // → user text
157
+ raw.push({
158
+ role: "user",
159
+ content: [{ type: "text", text: String(item.text ?? "") }],
160
+ });
161
+ }
162
+ }
163
+ const merged = mergeConsecutiveMessages(raw);
164
+ ensureFirstIsUser(merged);
165
+ return merged;
166
+ }
167
+ /** Extract content blocks from a ResponseInputMessage. */
168
+ function extractMessageContent(msg) {
169
+ const content = msg.content;
170
+ if (content == null)
171
+ return [];
172
+ if (typeof content === "string") {
173
+ return [{ type: "text", text: content }];
174
+ }
175
+ if (Array.isArray(content)) {
176
+ return content.flatMap((part) => {
177
+ if (part.type === "input_text" && part.text != null) {
178
+ return [{ type: "text", text: String(part.text) }];
179
+ }
180
+ return [];
181
+ });
182
+ }
183
+ return [];
184
+ }
185
+ /** Map Responses tool_choice → Anthropic tool_choice. */
186
+ function mapToolChoiceResponses2Ant(tc) {
187
+ if (tc === "auto")
188
+ return { type: "auto" };
189
+ if (tc === "required")
190
+ return { type: "any" };
191
+ if (tc === "none")
192
+ return undefined;
193
+ if (typeof tc === "object" && tc !== null) {
194
+ const obj = tc;
195
+ if (obj.type === "function" && obj.name) {
196
+ return { type: "tool", name: String(obj.name) };
197
+ }
198
+ }
199
+ return { type: "auto" };
200
+ }
201
+ // ---------- Anthropic → Responses ----------
202
+ export function anthropicToResponsesRequest(body) {
203
+ const result = {};
204
+ result.model = body.model;
205
+ // system → instructions
206
+ if (body.system != null) {
207
+ if (typeof body.system === "string") {
208
+ result.instructions = body.system;
209
+ }
210
+ else if (Array.isArray(body.system)) {
211
+ result.instructions = body.system
212
+ .map(b => String(b.text ?? ""))
213
+ .join("\n");
214
+ }
215
+ else {
216
+ result.instructions = String(body.system);
217
+ }
218
+ }
219
+ // messages → input items
220
+ const antMessages = body.messages;
221
+ result.input = antMessages ? convertAntMessagesToResponsesInput(antMessages) : [];
222
+ // max_tokens → max_output_tokens
223
+ if (body.max_tokens != null)
224
+ result.max_output_tokens = body.max_tokens;
225
+ // temperature, top_p, stream — pass through
226
+ if (body.temperature != null)
227
+ result.temperature = body.temperature;
228
+ if (body.top_p != null)
229
+ result.top_p = body.top_p;
230
+ if (body.stream != null)
231
+ result.stream = body.stream;
232
+ // tools
233
+ const tools = body.tools;
234
+ if (tools && tools.length > 0) {
235
+ result.tools = tools.map(t => {
236
+ const mapped = {
237
+ type: "function",
238
+ name: String(t.name),
239
+ };
240
+ if (t.description != null)
241
+ mapped.description = String(t.description);
242
+ if (t.input_schema != null)
243
+ mapped.parameters = t.input_schema;
244
+ return mapped;
245
+ });
246
+ }
247
+ // tool_choice
248
+ if (body.tool_choice != null) {
249
+ const tc = mapToolChoiceAnt2Responses(body.tool_choice);
250
+ if (tc != null)
251
+ result.tool_choice = tc;
252
+ }
253
+ // thinking → reasoning
254
+ if (body.thinking) {
255
+ const thinking = body.thinking;
256
+ if (thinking.type === "enabled" && thinking.budget_tokens != null) {
257
+ result.reasoning = { max_tokens: thinking.budget_tokens };
258
+ }
259
+ }
260
+ // metadata.user_id
261
+ const meta = body.metadata;
262
+ if (meta?.user_id) {
263
+ result.metadata = { user_id: meta.user_id };
264
+ }
265
+ return result;
266
+ }
267
+ /** Convert Anthropic messages → Responses input items. */
268
+ function convertAntMessagesToResponsesInput(messages) {
269
+ const items = [];
270
+ for (const msg of messages) {
271
+ const role = msg.role;
272
+ const content = msg.content;
273
+ if (!content || !Array.isArray(content))
274
+ continue;
275
+ if (role === "user") {
276
+ // Separate text blocks and tool_result blocks
277
+ const textBlocks = content.filter(b => b.type === "text");
278
+ const toolResultBlocks = content.filter(b => b.type === "tool_result");
279
+ if (textBlocks.length > 0) {
280
+ const text = textBlocks.map(b => String(b.text ?? "")).join("");
281
+ items.push({
282
+ type: "message",
283
+ role: "user",
284
+ content: [{ type: "input_text", text }],
285
+ });
286
+ }
287
+ for (const tr of toolResultBlocks) {
288
+ items.push({
289
+ type: "function_call_output",
290
+ call_id: stripTooluPrefix(String(tr.tool_use_id ?? "")),
291
+ output: String(tr.content ?? ""),
292
+ });
293
+ }
294
+ }
295
+ else if (role === "assistant") {
296
+ const textBlocks = content.filter(b => b.type === "text");
297
+ const toolUseBlocks = content.filter(b => b.type === "tool_use");
298
+ const thinkingBlocks = content.filter(b => b.type === "thinking");
299
+ // thinking → reasoning items
300
+ for (const tb of thinkingBlocks) {
301
+ items.push({
302
+ type: "reasoning",
303
+ id: `rs_${Date.now()}_${items.length}`,
304
+ summary: [{ type: "summary_text", text: String(tb.thinking ?? "") }],
305
+ });
306
+ }
307
+ // text → assistant message
308
+ if (textBlocks.length > 0) {
309
+ const text = textBlocks.map(b => String(b.text ?? "")).join("");
310
+ items.push({
311
+ type: "message",
312
+ role: "assistant",
313
+ content: [{ type: "output_text", text }],
314
+ });
315
+ }
316
+ // tool_use → function_call
317
+ for (const tu of toolUseBlocks) {
318
+ items.push({
319
+ type: "function_call",
320
+ id: String(tu.id ?? ""),
321
+ call_id: stripTooluPrefix(String(tu.id ?? "")),
322
+ name: String(tu.name ?? ""),
323
+ arguments: JSON.stringify(tu.input ?? {}),
324
+ });
325
+ }
326
+ }
327
+ }
328
+ return items;
329
+ }
330
+ /** Map Anthropic tool_choice → Responses tool_choice. */
331
+ function mapToolChoiceAnt2Responses(tc) {
332
+ if (typeof tc === "string") {
333
+ if (tc === "auto")
334
+ return "auto";
335
+ if (tc === "any")
336
+ return "required";
337
+ return "auto";
338
+ }
339
+ if (typeof tc === "object" && tc !== null) {
340
+ const obj = tc;
341
+ if (obj.type === "auto")
342
+ return "auto";
343
+ if (obj.type === "any")
344
+ return "required";
345
+ if (obj.type === "tool" && obj.name) {
346
+ return { type: "function", name: String(obj.name) };
347
+ }
348
+ }
349
+ return "auto";
350
+ }
@@ -0,0 +1,23 @@
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
+ /**
11
+ * Convert a Responses API response body to a Chat Completions response body.
12
+ *
13
+ * Lossy: structured reasoning summaries are flattened to a single string;
14
+ * built-in tool output items (web_search_call, etc.) are skipped.
15
+ */
16
+ export declare function responsesToChatResponse(bodyStr: string): string;
17
+ /**
18
+ * Convert a Chat Completions response body to a Responses API response body.
19
+ *
20
+ * Lossy: Chat Completions has no equivalent for built-in tool output items
21
+ * or structured reasoning summaries.
22
+ */
23
+ export declare function chatToResponsesResponse(bodyStr: string): string;