@velum-labs/routekit-gateway 0.9.0 → 0.9.2

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.
@@ -1022,25 +1022,42 @@ export class CodexResponsesBackend extends HttpProviderBackend {
1022
1022
  ...decoder.feed(await response.text()),
1023
1023
  ...decoder.flush()
1024
1024
  ];
1025
- for (let index = events.length - 1; index >= 0; index -= 1) {
1026
- const event = events[index];
1027
- if (event?.event !== "response.completed")
1028
- continue;
1029
- let completed;
1025
+ const completedOutput = [];
1026
+ let completedResponse;
1027
+ for (const event of events) {
1028
+ let payload;
1030
1029
  try {
1031
- completed = JSON.parse(event.data);
1030
+ payload = JSON.parse(event.data);
1032
1031
  }
1033
1032
  catch {
1033
+ if (event.event !== "response.output_item.done" &&
1034
+ event.event !== "response.completed") {
1035
+ continue;
1036
+ }
1034
1037
  throw new SseParseError("provider SSE event contained malformed JSON", event.data.slice(0, 200));
1035
1038
  }
1036
- if (typeof completed === "object" &&
1037
- completed !== null &&
1038
- typeof completed.response === "object" &&
1039
- completed.response !== null) {
1040
- const payload = completed
1041
- .response;
1042
- return jsonResponse(chatCompletion(model, responsesOutput(payload), normalizedOpenAiUsage(payload.usage)));
1039
+ if (typeof payload !== "object" || payload === null)
1040
+ continue;
1041
+ const record = payload;
1042
+ const eventType = event.event ?? record.type;
1043
+ if (eventType === "response.output_item.done" &&
1044
+ typeof record.item === "object" &&
1045
+ record.item !== null) {
1046
+ completedOutput.push(record.item);
1043
1047
  }
1048
+ if (eventType === "response.completed" &&
1049
+ typeof record.response === "object" &&
1050
+ record.response !== null) {
1051
+ completedResponse = record.response;
1052
+ }
1053
+ }
1054
+ if (completedResponse !== undefined) {
1055
+ const terminalOutput = completedResponse.output;
1056
+ const payload = (!Array.isArray(terminalOutput) || terminalOutput.length === 0) &&
1057
+ completedOutput.length > 0
1058
+ ? { ...completedResponse, output: completedOutput }
1059
+ : completedResponse;
1060
+ return jsonResponse(chatCompletion(model, responsesOutput(payload), normalizedOpenAiUsage(payload.usage)));
1044
1061
  }
1045
1062
  throw new SseParseError("provider SSE stream ended without response.completed");
1046
1063
  }
@@ -393,6 +393,50 @@ test("Codex subscription egress forces SSE and omits unsupported sampling", asyn
393
393
  globalThis.fetch = original;
394
394
  }
395
395
  });
396
+ test("Codex subscription egress recovers output from completed stream items", async () => {
397
+ const original = globalThis.fetch;
398
+ globalThis.fetch = async () => sse([
399
+ {
400
+ data: {
401
+ type: "response.output_item.done",
402
+ item: {
403
+ type: "message",
404
+ role: "assistant",
405
+ content: [{ type: "output_text", text: "ok" }]
406
+ },
407
+ output_index: 0
408
+ }
409
+ },
410
+ {
411
+ data: {
412
+ type: "response.completed",
413
+ response: {
414
+ output: [],
415
+ usage: { input_tokens: 8, output_tokens: 5, total_tokens: 13 }
416
+ }
417
+ }
418
+ }
419
+ ]);
420
+ try {
421
+ const backend = new CodexResponsesBackend({
422
+ baseUrl: "https://chatgpt.test/backend-api/codex",
423
+ apiKey: "oauth",
424
+ defaultModel: "gpt-5.4-mini",
425
+ forceStream: true,
426
+ omitSampling: true
427
+ });
428
+ const response = await backend.chat({
429
+ stream: false,
430
+ messages: [{ role: "user", content: "Say ok" }]
431
+ });
432
+ const body = (await response.json());
433
+ assert.equal(body.choices[0]?.message.content, "ok");
434
+ assert.equal(body.usage.completion_tokens, 5);
435
+ }
436
+ finally {
437
+ globalThis.fetch = original;
438
+ }
439
+ });
396
440
  test("Anthropic streaming egress preserves tool calls and terminal usage", async () => {
397
441
  const original = globalThis.fetch;
398
442
  globalThis.fetch = async () => sse([
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velum-labs/routekit-gateway",
3
3
  "private": false,
4
- "version": "0.9.0",
4
+ "version": "0.9.2",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/velum-labs/handoffkit.git",
@@ -27,10 +27,10 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "zod": "4.4.3",
30
- "@velum-labs/routekit-contracts": "0.9.0",
31
- "@velum-labs/routekit-tracing": "0.9.0",
32
- "@velum-labs/routekit-runtime": "0.9.0",
33
- "@velum-labs/routekit-registry": "0.9.0"
30
+ "@velum-labs/routekit-registry": "0.9.2",
31
+ "@velum-labs/routekit-contracts": "0.9.2",
32
+ "@velum-labs/routekit-tracing": "0.9.2",
33
+ "@velum-labs/routekit-runtime": "0.9.2"
34
34
  },
35
35
  "keywords": [
36
36
  "routekit",