@khanglvm/llm-router 1.2.0 → 1.3.1

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.
@@ -70,6 +70,120 @@ export async function buildFailureResponse(result) {
70
70
  }, fallbackStatus);
71
71
  }
72
72
 
73
+ async function adaptProviderResponse({
74
+ response,
75
+ stream,
76
+ translate,
77
+ sourceFormat,
78
+ targetFormat,
79
+ fallbackModel
80
+ }) {
81
+ if (stream) {
82
+ if (!translate) {
83
+ return {
84
+ ok: true,
85
+ status: 200,
86
+ retryable: false,
87
+ response: passthroughResponseWithCors(response, {
88
+ "Content-Type": "text/event-stream",
89
+ "Cache-Control": "no-cache",
90
+ Connection: "keep-alive"
91
+ })
92
+ };
93
+ }
94
+
95
+ if (sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.OPENAI) {
96
+ return {
97
+ ok: true,
98
+ status: 200,
99
+ retryable: false,
100
+ response: handleOpenAIStreamToClaude(response)
101
+ };
102
+ }
103
+
104
+ if (sourceFormat === FORMATS.OPENAI && targetFormat === FORMATS.CLAUDE) {
105
+ return {
106
+ ok: true,
107
+ status: 200,
108
+ retryable: false,
109
+ response: handleClaudeStreamToOpenAI(response)
110
+ };
111
+ }
112
+
113
+ return {
114
+ ok: false,
115
+ status: 501,
116
+ retryable: false,
117
+ errorKind: "not_supported_error",
118
+ response: jsonResponse({
119
+ type: "error",
120
+ error: {
121
+ type: "not_supported_error",
122
+ message: `Streaming translation from ${targetFormat} to ${sourceFormat} is not implemented.`
123
+ }
124
+ }, 501)
125
+ };
126
+ }
127
+
128
+ if (!translate) {
129
+ return {
130
+ ok: true,
131
+ status: 200,
132
+ retryable: false,
133
+ response: passthroughResponseWithCors(response)
134
+ };
135
+ }
136
+
137
+ const raw = await response.text();
138
+ const parsed = parseJsonSafely(raw);
139
+ if (!parsed) {
140
+ return {
141
+ ok: false,
142
+ status: 502,
143
+ retryable: true,
144
+ response: jsonResponse({
145
+ type: "error",
146
+ error: {
147
+ type: "api_error",
148
+ message: "Provider returned invalid JSON."
149
+ }
150
+ }, 502)
151
+ };
152
+ }
153
+
154
+ if (sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.OPENAI) {
155
+ return {
156
+ ok: true,
157
+ status: 200,
158
+ retryable: false,
159
+ response: jsonResponse(convertOpenAINonStreamToClaude(parsed, fallbackModel))
160
+ };
161
+ }
162
+
163
+ if (sourceFormat === FORMATS.OPENAI && targetFormat === FORMATS.CLAUDE) {
164
+ return {
165
+ ok: true,
166
+ status: 200,
167
+ retryable: false,
168
+ response: jsonResponse(claudeToOpenAINonStreamResponse(parsed))
169
+ };
170
+ }
171
+
172
+ return {
173
+ ok: false,
174
+ status: 501,
175
+ retryable: false,
176
+ errorKind: "not_supported_error",
177
+ response: jsonResponse({
178
+ type: "error",
179
+ error: {
180
+ type: "not_supported_error",
181
+ message: `Non-stream translation from ${targetFormat} to ${sourceFormat} is not implemented.`
182
+ }
183
+ }, 501)
184
+ };
185
+ }
186
+
73
187
  export async function makeProviderCall({
74
188
  body,
75
189
  sourceFormat,
@@ -120,11 +234,12 @@ export async function makeProviderCall({
120
234
  });
121
235
 
122
236
  if (isSubscriptionProvider(provider)) {
237
+ const subscriptionType = String(provider?.subscriptionType || provider?.subscription_type || "").trim().toLowerCase();
123
238
  const subscriptionResult = await makeSubscriptionProviderCall({
124
239
  provider,
125
240
  body: providerBody,
126
241
  // ChatGPT Codex backend expects stream=true; non-stream responses are reconstructed from SSE.
127
- stream: true,
242
+ stream: subscriptionType === "chatgpt-codex" ? true : Boolean(stream),
128
243
  env
129
244
  });
130
245
 
@@ -148,6 +263,17 @@ export async function makeProviderCall({
148
263
  }
149
264
 
150
265
  const fallbackModel = candidate?.backend || providerBody?.model || "unknown";
266
+ if (subscriptionType !== "chatgpt-codex") {
267
+ return adaptProviderResponse({
268
+ response: subscriptionResult.response,
269
+ stream,
270
+ translate,
271
+ sourceFormat,
272
+ targetFormat,
273
+ fallbackModel
274
+ });
275
+ }
276
+
151
277
  if (stream) {
152
278
  const openAIStreamResponse = handleCodexStreamToOpenAI(subscriptionResult.response, {
153
279
  fallbackModel
@@ -273,108 +399,12 @@ export async function makeProviderCall({
273
399
  };
274
400
  }
275
401
 
276
- if (stream) {
277
- if (!translate) {
278
- return {
279
- ok: true,
280
- status: 200,
281
- retryable: false,
282
- response: passthroughResponseWithCors(response, {
283
- "Content-Type": "text/event-stream",
284
- "Cache-Control": "no-cache",
285
- Connection: "keep-alive"
286
- })
287
- };
288
- }
289
-
290
- if (sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.OPENAI) {
291
- return {
292
- ok: true,
293
- status: 200,
294
- retryable: false,
295
- response: handleOpenAIStreamToClaude(response)
296
- };
297
- }
298
-
299
- if (sourceFormat === FORMATS.OPENAI && targetFormat === FORMATS.CLAUDE) {
300
- return {
301
- ok: true,
302
- status: 200,
303
- retryable: false,
304
- response: handleClaudeStreamToOpenAI(response)
305
- };
306
- }
307
-
308
- return {
309
- ok: false,
310
- status: 501,
311
- retryable: false,
312
- errorKind: "not_supported_error",
313
- response: jsonResponse({
314
- type: "error",
315
- error: {
316
- type: "not_supported_error",
317
- message: `Streaming translation from ${targetFormat} to ${sourceFormat} is not implemented.`
318
- }
319
- }, 501)
320
- };
321
- }
322
-
323
- if (!translate) {
324
- return {
325
- ok: true,
326
- status: 200,
327
- retryable: false,
328
- response: passthroughResponseWithCors(response)
329
- };
330
- }
331
-
332
- const raw = await response.text();
333
- const parsed = parseJsonSafely(raw);
334
- if (!parsed) {
335
- return {
336
- ok: false,
337
- status: 502,
338
- retryable: true,
339
- response: jsonResponse({
340
- type: "error",
341
- error: {
342
- type: "api_error",
343
- message: "Provider returned invalid JSON."
344
- }
345
- }, 502)
346
- };
347
- }
348
-
349
- if (sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.OPENAI) {
350
- return {
351
- ok: true,
352
- status: 200,
353
- retryable: false,
354
- response: jsonResponse(convertOpenAINonStreamToClaude(parsed, candidate.backend))
355
- };
356
- }
357
-
358
- if (sourceFormat === FORMATS.OPENAI && targetFormat === FORMATS.CLAUDE) {
359
- return {
360
- ok: true,
361
- status: 200,
362
- retryable: false,
363
- response: jsonResponse(claudeToOpenAINonStreamResponse(parsed))
364
- };
365
- }
366
-
367
- return {
368
- ok: false,
369
- status: 501,
370
- retryable: false,
371
- errorKind: "not_supported_error",
372
- response: jsonResponse({
373
- type: "error",
374
- error: {
375
- type: "not_supported_error",
376
- message: `Non-stream translation from ${targetFormat} to ${sourceFormat} is not implemented.`
377
- }
378
- }, 501)
379
- };
402
+ return adaptProviderResponse({
403
+ response,
404
+ stream,
405
+ translate,
406
+ sourceFormat,
407
+ targetFormat,
408
+ fallbackModel: candidate.backend
409
+ });
380
410
  }