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,40 @@
1
+ import { BaseSSETransform } from "./stream-transform-base.js";
2
+ /**
3
+ * Bridge transform: Chat Completions SSE → Responses API SSE.
4
+ *
5
+ * Used when the client sends a Responses API request but the upstream
6
+ * provider speaks Chat Completions. This is a lossy conversion —
7
+ * Responses API has richer event types than Chat delta chunks.
8
+ */
9
+ export declare class ChatToResponsesBridgeTransform extends BaseSSETransform {
10
+ private responseId;
11
+ private hasResponseCreated;
12
+ private outputIndex;
13
+ private contentIndex;
14
+ private sequenceNumber;
15
+ private hasMessageItemStarted;
16
+ private hasContentPartStarted;
17
+ private hasReasoningItemStarted;
18
+ private inputTokens;
19
+ private outputTokens;
20
+ private pendingCompletion;
21
+ private collectedOutput;
22
+ private currentMessageItemId;
23
+ private currentFunctionCallId;
24
+ private currentFunctionCallName;
25
+ private currentReasoningItemId;
26
+ private createdAt;
27
+ private nextSeq;
28
+ private ensureResponseCreated;
29
+ private closeCurrentMessageItem;
30
+ private closeCurrentReasoningItem;
31
+ private closeCurrentFunctionCall;
32
+ private closeAllOpenItems;
33
+ private emitCompleted;
34
+ protected processEvent(event: {
35
+ event?: string;
36
+ data?: string;
37
+ }): void;
38
+ protected flushPendingData(): void;
39
+ protected ensureTerminated(): void;
40
+ }
@@ -0,0 +1,382 @@
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
+ /**
9
+ * Bridge transform: Chat Completions SSE → Responses API SSE.
10
+ *
11
+ * Used when the client sends a Responses API request but the upstream
12
+ * provider speaks Chat Completions. This is a lossy conversion —
13
+ * Responses API has richer event types than Chat delta chunks.
14
+ */
15
+ export class ChatToResponsesBridgeTransform extends BaseSSETransform {
16
+ responseId = generateRespId();
17
+ hasResponseCreated = false;
18
+ outputIndex = 0;
19
+ contentIndex = 0;
20
+ sequenceNumber = 0;
21
+ hasMessageItemStarted = false;
22
+ hasContentPartStarted = false;
23
+ hasReasoningItemStarted = false;
24
+ inputTokens = 0;
25
+ outputTokens = 0;
26
+ pendingCompletion = false;
27
+ collectedOutput = [];
28
+ currentMessageItemId = "";
29
+ currentFunctionCallId = "";
30
+ currentFunctionCallName = "";
31
+ currentReasoningItemId = "";
32
+ createdAt = Math.floor(Date.now() / 1000);
33
+ nextSeq() {
34
+ return this.sequenceNumber++;
35
+ }
36
+ ensureResponseCreated() {
37
+ if (this.hasResponseCreated)
38
+ return;
39
+ this.hasResponseCreated = true;
40
+ const base = {
41
+ id: this.responseId,
42
+ object: "response",
43
+ model: this.model,
44
+ status: "in_progress",
45
+ output: [],
46
+ created_at: this.createdAt,
47
+ };
48
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.CREATED, {
49
+ type: RESPONSES_SSE_EVENTS.CREATED,
50
+ response: { ...base, status: "queued" },
51
+ sequence_number: this.nextSeq(),
52
+ });
53
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.IN_PROGRESS, {
54
+ type: RESPONSES_SSE_EVENTS.IN_PROGRESS,
55
+ response: base,
56
+ sequence_number: this.nextSeq(),
57
+ });
58
+ }
59
+ closeCurrentMessageItem() {
60
+ if (this.hasContentPartStarted) {
61
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DONE, {
62
+ type: RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DONE,
63
+ output_index: this.outputIndex,
64
+ content_index: this.contentIndex,
65
+ text: "",
66
+ sequence_number: this.nextSeq(),
67
+ });
68
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.CONTENT_PART_DONE, {
69
+ type: RESPONSES_SSE_EVENTS.CONTENT_PART_DONE,
70
+ output_index: this.outputIndex,
71
+ content_index: this.contentIndex,
72
+ part: { type: "output_text", text: "", annotations: [] },
73
+ sequence_number: this.nextSeq(),
74
+ });
75
+ this.hasContentPartStarted = false;
76
+ }
77
+ if (this.hasMessageItemStarted) {
78
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE, {
79
+ type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE,
80
+ output_index: this.outputIndex,
81
+ item: {
82
+ type: "message",
83
+ id: this.currentMessageItemId,
84
+ role: "assistant",
85
+ content: [{ type: "output_text", text: "", annotations: [] }],
86
+ status: "completed",
87
+ },
88
+ sequence_number: this.nextSeq(),
89
+ });
90
+ this.collectedOutput.push({
91
+ type: "message",
92
+ id: this.currentMessageItemId,
93
+ role: "assistant",
94
+ content: [{ type: "output_text", text: "", annotations: [] }],
95
+ });
96
+ this.hasMessageItemStarted = false;
97
+ this.outputIndex++;
98
+ }
99
+ }
100
+ closeCurrentReasoningItem() {
101
+ if (!this.hasReasoningItemStarted)
102
+ return;
103
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DONE, {
104
+ type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DONE,
105
+ output_index: this.outputIndex,
106
+ summary_index: 0,
107
+ text: "",
108
+ sequence_number: this.nextSeq(),
109
+ });
110
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_DONE, {
111
+ type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_DONE,
112
+ output_index: this.outputIndex,
113
+ summary_index: 0,
114
+ part: { type: "summary_text", text: "" },
115
+ sequence_number: this.nextSeq(),
116
+ });
117
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE, {
118
+ type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE,
119
+ output_index: this.outputIndex,
120
+ item: {
121
+ type: "reasoning",
122
+ id: this.currentReasoningItemId,
123
+ summary: [{ type: "summary_text", text: "" }],
124
+ },
125
+ sequence_number: this.nextSeq(),
126
+ });
127
+ this.collectedOutput.push({
128
+ type: "reasoning",
129
+ id: this.currentReasoningItemId,
130
+ summary: [{ type: "summary_text", text: "" }],
131
+ });
132
+ this.hasReasoningItemStarted = false;
133
+ this.outputIndex++;
134
+ }
135
+ closeCurrentFunctionCall() {
136
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DONE, {
137
+ type: RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DONE,
138
+ output_index: this.outputIndex,
139
+ item_id: this.currentFunctionCallId,
140
+ call_id: this.currentFunctionCallId,
141
+ arguments: "",
142
+ sequence_number: this.nextSeq(),
143
+ });
144
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE, {
145
+ type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_DONE,
146
+ output_index: this.outputIndex,
147
+ item: {
148
+ type: "function_call",
149
+ id: this.currentFunctionCallId,
150
+ call_id: this.currentFunctionCallId,
151
+ name: this.currentFunctionCallName,
152
+ arguments: "",
153
+ status: "completed",
154
+ },
155
+ sequence_number: this.nextSeq(),
156
+ });
157
+ this.collectedOutput.push({
158
+ type: "function_call",
159
+ id: this.currentFunctionCallId,
160
+ call_id: this.currentFunctionCallId,
161
+ name: this.currentFunctionCallName,
162
+ arguments: "",
163
+ });
164
+ this.outputIndex++;
165
+ this.currentFunctionCallId = "";
166
+ this.currentFunctionCallName = "";
167
+ }
168
+ closeAllOpenItems() {
169
+ this.closeCurrentReasoningItem();
170
+ this.closeCurrentMessageItem();
171
+ if (this.currentFunctionCallId) {
172
+ this.closeCurrentFunctionCall();
173
+ }
174
+ }
175
+ emitCompleted() {
176
+ const completedAt = Math.floor(Date.now() / 1000);
177
+ const response = {
178
+ id: this.responseId,
179
+ object: "response",
180
+ model: this.model,
181
+ status: "completed",
182
+ output: this.collectedOutput,
183
+ usage: {
184
+ input_tokens: this.inputTokens,
185
+ output_tokens: this.outputTokens,
186
+ total_tokens: this.inputTokens + this.outputTokens,
187
+ },
188
+ created_at: this.createdAt,
189
+ completed_at: completedAt,
190
+ };
191
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.COMPLETED, {
192
+ type: RESPONSES_SSE_EVENTS.COMPLETED,
193
+ response,
194
+ sequence_number: this.nextSeq(),
195
+ });
196
+ this.pushDone();
197
+ }
198
+ processEvent(event) {
199
+ let chunk;
200
+ try {
201
+ chunk = JSON.parse(event.data);
202
+ }
203
+ catch (err) {
204
+ this.emit("warning", err);
205
+ return;
206
+ }
207
+ // Extract usage when present (usage-only chunks or chunks with usage)
208
+ if (chunk.usage) {
209
+ const usage = chunk.usage;
210
+ this.inputTokens = usage.prompt_tokens ?? this.inputTokens;
211
+ this.outputTokens = usage.completion_tokens ?? this.outputTokens;
212
+ }
213
+ // Usage-only chunk (no choices) — may trigger completion
214
+ if (chunk.usage && !(Array.isArray(chunk.choices) && chunk.choices.length > 0)) {
215
+ if (this.pendingCompletion) {
216
+ this.closeAllOpenItems();
217
+ this.emitCompleted();
218
+ this.pendingCompletion = false;
219
+ }
220
+ return;
221
+ }
222
+ const choices = chunk.choices;
223
+ const choice = choices?.[0];
224
+ if (!choice)
225
+ return;
226
+ const delta = choice.delta;
227
+ // First chunk with role → emit response.created + response.in_progress
228
+ if (delta?.role === "assistant") {
229
+ this.ensureResponseCreated();
230
+ }
231
+ // Handle reasoning_content
232
+ if (delta?.reasoning_content != null && delta.reasoning_content !== "") {
233
+ this.ensureResponseCreated();
234
+ // Close message item if open (reasoning comes first, but could be interleaved)
235
+ this.closeCurrentMessageItem();
236
+ if (!this.hasReasoningItemStarted) {
237
+ this.hasReasoningItemStarted = true;
238
+ this.currentReasoningItemId = `rs_${randomHex(12)}`;
239
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED, {
240
+ type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED,
241
+ output_index: this.outputIndex,
242
+ item: { type: "reasoning", id: this.currentReasoningItemId, summary: [] },
243
+ sequence_number: this.nextSeq(),
244
+ });
245
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_ADDED, {
246
+ type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_PART_ADDED,
247
+ output_index: this.outputIndex,
248
+ summary_index: 0,
249
+ part: { type: "summary_text", text: "" },
250
+ sequence_number: this.nextSeq(),
251
+ });
252
+ }
253
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DELTA, {
254
+ type: RESPONSES_SSE_EVENTS.REASONING_SUMMARY_TEXT_DELTA,
255
+ output_index: this.outputIndex,
256
+ summary_index: 0,
257
+ delta: delta.reasoning_content,
258
+ sequence_number: this.nextSeq(),
259
+ });
260
+ }
261
+ // Handle text content
262
+ if (delta?.content != null && delta.content !== "") {
263
+ this.ensureResponseCreated();
264
+ // Close reasoning item if transitioning to text
265
+ this.closeCurrentReasoningItem();
266
+ if (!this.hasMessageItemStarted) {
267
+ this.hasMessageItemStarted = true;
268
+ this.contentIndex = 0;
269
+ this.currentMessageItemId = `msg_${randomHex(12)}`;
270
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED, {
271
+ type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED,
272
+ output_index: this.outputIndex,
273
+ item: {
274
+ type: "message",
275
+ id: this.currentMessageItemId,
276
+ role: "assistant",
277
+ content: [],
278
+ status: "in_progress",
279
+ },
280
+ sequence_number: this.nextSeq(),
281
+ });
282
+ }
283
+ if (!this.hasContentPartStarted) {
284
+ this.hasContentPartStarted = true;
285
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.CONTENT_PART_ADDED, {
286
+ type: RESPONSES_SSE_EVENTS.CONTENT_PART_ADDED,
287
+ output_index: this.outputIndex,
288
+ content_index: this.contentIndex,
289
+ part: { type: "output_text", text: "", annotations: [] },
290
+ sequence_number: this.nextSeq(),
291
+ });
292
+ }
293
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DELTA, {
294
+ type: RESPONSES_SSE_EVENTS.OUTPUT_TEXT_DELTA,
295
+ output_index: this.outputIndex,
296
+ content_index: this.contentIndex,
297
+ delta: delta.content,
298
+ sequence_number: this.nextSeq(),
299
+ });
300
+ }
301
+ // Handle tool_calls
302
+ const toolCalls = delta?.tool_calls;
303
+ if (toolCalls) {
304
+ this.ensureResponseCreated();
305
+ // Close any open items before starting a tool call
306
+ this.closeCurrentReasoningItem();
307
+ this.closeCurrentMessageItem();
308
+ for (const tc of toolCalls) {
309
+ const fn = tc.function;
310
+ const tcId = tc.id;
311
+ const tcName = fn?.name;
312
+ // New tool call (has id + name)
313
+ if (tcId && tcName) {
314
+ // Close previous function call if any
315
+ if (this.currentFunctionCallId) {
316
+ this.closeCurrentFunctionCall();
317
+ }
318
+ this.currentFunctionCallId = tcId;
319
+ this.currentFunctionCallName = tcName;
320
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED, {
321
+ type: RESPONSES_SSE_EVENTS.OUTPUT_ITEM_ADDED,
322
+ output_index: this.outputIndex,
323
+ item: {
324
+ type: "function_call",
325
+ id: tcId,
326
+ call_id: tcId,
327
+ name: tcName,
328
+ arguments: "",
329
+ status: "in_progress",
330
+ },
331
+ sequence_number: this.nextSeq(),
332
+ });
333
+ // Also emit any initial arguments
334
+ const args = fn?.arguments;
335
+ if (args && args !== "") {
336
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DELTA, {
337
+ type: RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DELTA,
338
+ output_index: this.outputIndex,
339
+ item_id: tcId,
340
+ call_id: tcId,
341
+ delta: args,
342
+ sequence_number: this.nextSeq(),
343
+ });
344
+ }
345
+ }
346
+ else if (fn?.arguments) {
347
+ // Arguments continuation for current tool call
348
+ this.pushResponsesSSE(RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DELTA, {
349
+ type: RESPONSES_SSE_EVENTS.FUNCTION_CALL_ARGUMENTS_DELTA,
350
+ output_index: this.outputIndex,
351
+ item_id: this.currentFunctionCallId,
352
+ call_id: this.currentFunctionCallId,
353
+ delta: fn.arguments,
354
+ sequence_number: this.nextSeq(),
355
+ });
356
+ }
357
+ }
358
+ }
359
+ // Handle finish_reason
360
+ const finishReason = choice.finish_reason;
361
+ if (finishReason) {
362
+ this.closeAllOpenItems();
363
+ this.pendingCompletion = true;
364
+ // If there's no usage-only chunk coming, emit completed now
365
+ // Usage chunk may come in a separate chunk or was already in this chunk
366
+ if (chunk.usage) {
367
+ this.emitCompleted();
368
+ this.pendingCompletion = false;
369
+ }
370
+ }
371
+ }
372
+ flushPendingData() {
373
+ // No buffered data to flush
374
+ }
375
+ ensureTerminated() {
376
+ if (!this.done) {
377
+ this.ensureResponseCreated();
378
+ this.closeAllOpenItems();
379
+ this.emitCompleted();
380
+ }
381
+ }
382
+ }
@@ -0,0 +1,24 @@
1
+ import { BaseSSETransform } from "./stream-transform-base.js";
2
+ /**
3
+ * Bridge transform: Responses API SSE → Chat Completions SSE.
4
+ *
5
+ * Used when the client sends a Chat Completions request but the upstream
6
+ * provider speaks Responses API. This is a lossy conversion —
7
+ * Chat Completions has fewer event types than Responses API.
8
+ */
9
+ export declare class ResponsesToChatBridgeTransform extends BaseSSETransform {
10
+ private chatcmplId;
11
+ private hasSentRole;
12
+ private currentToolCallIndex;
13
+ private inputTokens;
14
+ private outputTokens;
15
+ private finishReasonEmitted;
16
+ private hasFunctionCall;
17
+ private ensureRoleSent;
18
+ protected processEvent(event: {
19
+ event?: string;
20
+ data?: string;
21
+ }): void;
22
+ protected flushPendingData(): void;
23
+ protected ensureTerminated(): void;
24
+ }
@@ -0,0 +1,237 @@
1
+ import { BaseSSETransform } from "./stream-transform-base.js";
2
+ import { generateChatcmplId } from "./id-utils.js";
3
+ /**
4
+ * Bridge transform: Responses API SSE → Chat Completions SSE.
5
+ *
6
+ * Used when the client sends a Chat Completions request but the upstream
7
+ * provider speaks Responses API. This is a lossy conversion —
8
+ * Chat Completions has fewer event types than Responses API.
9
+ */
10
+ export class ResponsesToChatBridgeTransform extends BaseSSETransform {
11
+ chatcmplId = generateChatcmplId();
12
+ hasSentRole = false;
13
+ currentToolCallIndex = 0;
14
+ inputTokens = 0;
15
+ outputTokens = 0;
16
+ finishReasonEmitted = false;
17
+ hasFunctionCall = false;
18
+ ensureRoleSent() {
19
+ if (this.hasSentRole)
20
+ return;
21
+ this.hasSentRole = true;
22
+ this.pushOpenAISSE({
23
+ id: this.chatcmplId,
24
+ object: "chat.completion.chunk",
25
+ choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
26
+ });
27
+ }
28
+ processEvent(event) {
29
+ const eventType = event.event;
30
+ let payload;
31
+ try {
32
+ payload = JSON.parse(event.data);
33
+ }
34
+ catch (err) {
35
+ this.emit("warning", err);
36
+ return;
37
+ }
38
+ switch (eventType) {
39
+ case "response.created":
40
+ case "response.in_progress":
41
+ case "response.queued": {
42
+ // Extract usage from response object if present
43
+ const resp = payload.response;
44
+ if (resp?.usage) {
45
+ const usage = resp.usage;
46
+ this.inputTokens = usage.input_tokens ?? this.inputTokens;
47
+ this.outputTokens = usage.output_tokens ?? this.outputTokens;
48
+ }
49
+ break;
50
+ }
51
+ case "response.output_text.delta": {
52
+ this.ensureRoleSent();
53
+ const delta = payload.delta;
54
+ if (delta) {
55
+ this.pushOpenAISSE({
56
+ id: this.chatcmplId,
57
+ object: "chat.completion.chunk",
58
+ choices: [{ index: 0, delta: { content: delta }, finish_reason: null }],
59
+ });
60
+ }
61
+ break;
62
+ }
63
+ case "response.output_item.added": {
64
+ const item = payload.item;
65
+ const itemType = item?.type;
66
+ if (itemType === "function_call") {
67
+ this.ensureRoleSent();
68
+ this.hasFunctionCall = true;
69
+ const tcIndex = this.currentToolCallIndex++;
70
+ const callId = item.call_id ?? item.id;
71
+ const name = item.name ?? "";
72
+ this.pushOpenAISSE({
73
+ id: this.chatcmplId,
74
+ object: "chat.completion.chunk",
75
+ choices: [{
76
+ index: 0,
77
+ delta: {
78
+ tool_calls: [{
79
+ index: tcIndex,
80
+ id: callId,
81
+ type: "function",
82
+ function: { name, arguments: "" },
83
+ }],
84
+ },
85
+ finish_reason: null,
86
+ }],
87
+ });
88
+ }
89
+ // Other item types (message, reasoning) — skip, content comes via delta events
90
+ break;
91
+ }
92
+ case "response.function_call_arguments.delta": {
93
+ const delta = payload.delta;
94
+ if (delta) {
95
+ // Use currentToolCallIndex - 1 because the tool call was already registered
96
+ const tcIndex = this.currentToolCallIndex - 1;
97
+ this.pushOpenAISSE({
98
+ id: this.chatcmplId,
99
+ object: "chat.completion.chunk",
100
+ choices: [{
101
+ index: 0,
102
+ delta: {
103
+ tool_calls: [{ index: tcIndex, function: { arguments: delta } }],
104
+ },
105
+ finish_reason: null,
106
+ }],
107
+ });
108
+ }
109
+ break;
110
+ }
111
+ case "response.reasoning_summary_text.delta": {
112
+ this.ensureRoleSent();
113
+ const delta = payload.delta;
114
+ if (delta) {
115
+ this.pushOpenAISSE({
116
+ id: this.chatcmplId,
117
+ object: "chat.completion.chunk",
118
+ choices: [{ index: 0, delta: { reasoning_content: delta }, finish_reason: null }],
119
+ });
120
+ }
121
+ break;
122
+ }
123
+ case "response.completed": {
124
+ const resp = payload.response;
125
+ if (resp?.usage) {
126
+ const usage = resp.usage;
127
+ this.inputTokens = usage.input_tokens ?? this.inputTokens;
128
+ this.outputTokens = usage.output_tokens ?? this.outputTokens;
129
+ }
130
+ if (!this.finishReasonEmitted) {
131
+ this.finishReasonEmitted = true;
132
+ const finishReason = this.hasFunctionCall ? "tool_calls" : "stop";
133
+ this.pushOpenAISSE({
134
+ id: this.chatcmplId,
135
+ object: "chat.completion.chunk",
136
+ choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
137
+ });
138
+ }
139
+ // Emit usage chunk
140
+ this.pushOpenAISSE({
141
+ id: this.chatcmplId,
142
+ object: "chat.completion.chunk",
143
+ choices: [],
144
+ usage: {
145
+ prompt_tokens: this.inputTokens,
146
+ completion_tokens: this.outputTokens,
147
+ total_tokens: this.inputTokens + this.outputTokens,
148
+ },
149
+ });
150
+ this.pushDone();
151
+ break;
152
+ }
153
+ case "response.incomplete": {
154
+ if (!this.finishReasonEmitted) {
155
+ this.finishReasonEmitted = true;
156
+ this.pushOpenAISSE({
157
+ id: this.chatcmplId,
158
+ object: "chat.completion.chunk",
159
+ choices: [{ index: 0, delta: {}, finish_reason: "length" }],
160
+ });
161
+ }
162
+ this.pushOpenAISSE({
163
+ id: this.chatcmplId,
164
+ object: "chat.completion.chunk",
165
+ choices: [],
166
+ usage: {
167
+ prompt_tokens: this.inputTokens,
168
+ completion_tokens: this.outputTokens,
169
+ total_tokens: this.inputTokens + this.outputTokens,
170
+ },
171
+ });
172
+ this.pushDone();
173
+ break;
174
+ }
175
+ case "response.failed": {
176
+ const resp = payload.response;
177
+ const err = resp?.error;
178
+ this.pushOpenAISSE({
179
+ error: {
180
+ message: err?.message ?? "Upstream error",
181
+ type: err?.type ?? "api_error",
182
+ code: err?.code ?? "upstream_error",
183
+ },
184
+ });
185
+ this.pushDone();
186
+ break;
187
+ }
188
+ case "response.output_text.done":
189
+ case "response.content_part.added":
190
+ case "response.content_part.done":
191
+ case "response.output_item.done":
192
+ case "response.function_call_arguments.done":
193
+ case "response.reasoning_summary_part.added":
194
+ case "response.reasoning_summary_text.done":
195
+ case "response.reasoning_summary_part.done":
196
+ case "response.reasoning_text.delta":
197
+ case "response.reasoning_text.done":
198
+ case "response.refusal.delta":
199
+ case "response.refusal.done": {
200
+ // These events don't map to Chat SSE — skip
201
+ break;
202
+ }
203
+ case "error": {
204
+ this.pushOpenAISSE({
205
+ error: {
206
+ message: payload.message ?? "Stream error",
207
+ type: payload.type ?? "api_error",
208
+ code: "upstream_error",
209
+ },
210
+ });
211
+ this.pushDone();
212
+ break;
213
+ }
214
+ default: {
215
+ this.emit("warning", { event: "unknown_sse_event", eventType });
216
+ break;
217
+ }
218
+ }
219
+ }
220
+ flushPendingData() {
221
+ // No buffered data
222
+ }
223
+ ensureTerminated() {
224
+ if (!this.done) {
225
+ this.ensureRoleSent();
226
+ if (!this.finishReasonEmitted) {
227
+ this.finishReasonEmitted = true;
228
+ this.pushOpenAISSE({
229
+ id: this.chatcmplId,
230
+ object: "chat.completion.chunk",
231
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
232
+ });
233
+ }
234
+ this.pushDone();
235
+ }
236
+ }
237
+ }