@askalf/dario 5.5.88 → 5.5.89
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.
|
@@ -516,6 +516,22 @@ export declare function parseResponsesSSEEvent(eventLine: string | undefined, da
|
|
|
516
516
|
* Call `flush()` at end-of-stream to parse any trailing record that was
|
|
517
517
|
* not blank-line terminated. Pure and offline-testable.
|
|
518
518
|
*/
|
|
519
|
+
/**
|
|
520
|
+
* Fold the Anthropic stream this module produces back into ONE Message.
|
|
521
|
+
*
|
|
522
|
+
* Needed because the ChatGPT Codex backend's terminal `response.completed`
|
|
523
|
+
* carries `output: []` — the content only ever exists in the delta events, so
|
|
524
|
+
* `responsesToAnthropicResponse(terminal)` yields an empty message. (The
|
|
525
|
+
* standard Responses API does populate `output`, which is why this was not
|
|
526
|
+
* obvious.) The chat path has always avoided the trap by accumulating from
|
|
527
|
+
* deltas; this is the same discipline for the Anthropic shape, and it means
|
|
528
|
+
* the streaming and non-streaming bodies are assembled from ONE translation
|
|
529
|
+
* rather than two that can disagree. dario#1143.
|
|
530
|
+
*/
|
|
531
|
+
export declare function createAnthropicMessageAssembler(): {
|
|
532
|
+
push(events: readonly ResponsesAnthropicStreamEvent[]): void;
|
|
533
|
+
message(fallbackModel: string): AnthropicResponseWithThinking;
|
|
534
|
+
};
|
|
519
535
|
export declare function createResponsesSSEParser(): {
|
|
520
536
|
push(chunk: string): ResponsesStreamEvent[];
|
|
521
537
|
flush(): ResponsesStreamEvent[];
|
|
@@ -791,6 +791,75 @@ export function parseResponsesSSEEvent(eventLine, dataLine) {
|
|
|
791
791
|
* Call `flush()` at end-of-stream to parse any trailing record that was
|
|
792
792
|
* not blank-line terminated. Pure and offline-testable.
|
|
793
793
|
*/
|
|
794
|
+
/**
|
|
795
|
+
* Fold the Anthropic stream this module produces back into ONE Message.
|
|
796
|
+
*
|
|
797
|
+
* Needed because the ChatGPT Codex backend's terminal `response.completed`
|
|
798
|
+
* carries `output: []` — the content only ever exists in the delta events, so
|
|
799
|
+
* `responsesToAnthropicResponse(terminal)` yields an empty message. (The
|
|
800
|
+
* standard Responses API does populate `output`, which is why this was not
|
|
801
|
+
* obvious.) The chat path has always avoided the trap by accumulating from
|
|
802
|
+
* deltas; this is the same discipline for the Anthropic shape, and it means
|
|
803
|
+
* the streaming and non-streaming bodies are assembled from ONE translation
|
|
804
|
+
* rather than two that can disagree. dario#1143.
|
|
805
|
+
*/
|
|
806
|
+
export function createAnthropicMessageAssembler() {
|
|
807
|
+
let msg = null;
|
|
808
|
+
const blocks = [];
|
|
809
|
+
const partialJson = new Map();
|
|
810
|
+
return {
|
|
811
|
+
push(events) {
|
|
812
|
+
for (const e of events) {
|
|
813
|
+
if (e.type === 'message_start') {
|
|
814
|
+
msg = { ...e.message, content: [] };
|
|
815
|
+
}
|
|
816
|
+
else if (e.type === 'content_block_start') {
|
|
817
|
+
blocks[e.index] = { ...e.content_block };
|
|
818
|
+
if (e.content_block.type === 'tool_use')
|
|
819
|
+
partialJson.set(e.index, '');
|
|
820
|
+
}
|
|
821
|
+
else if (e.type === 'content_block_delta') {
|
|
822
|
+
const b = blocks[e.index] ?? (blocks[e.index] = {});
|
|
823
|
+
const d = e.delta;
|
|
824
|
+
if (d.type === 'text_delta')
|
|
825
|
+
b.text = String(b.text ?? '') + d.text;
|
|
826
|
+
else if (d.type === 'thinking_delta')
|
|
827
|
+
b.thinking = String(b.thinking ?? '') + d.thinking;
|
|
828
|
+
else if (d.type === 'input_json_delta')
|
|
829
|
+
partialJson.set(e.index, (partialJson.get(e.index) ?? '') + d.partial_json);
|
|
830
|
+
}
|
|
831
|
+
else if (e.type === 'message_delta') {
|
|
832
|
+
if (msg) {
|
|
833
|
+
msg.stop_reason = e.delta.stop_reason;
|
|
834
|
+
const u = msg.usage ?? { input_tokens: 0, output_tokens: 0 };
|
|
835
|
+
msg.usage = {
|
|
836
|
+
...u,
|
|
837
|
+
output_tokens: e.usage.output_tokens,
|
|
838
|
+
input_tokens: e.usage.input_tokens ?? u.input_tokens,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
message(fallbackModel) {
|
|
845
|
+
for (const [i, raw] of partialJson) {
|
|
846
|
+
const b = blocks[i];
|
|
847
|
+
if (!b || b.type !== 'tool_use')
|
|
848
|
+
continue;
|
|
849
|
+
b.input = safeParseArguments(raw);
|
|
850
|
+
}
|
|
851
|
+
const content = blocks.filter(Boolean);
|
|
852
|
+
if (!msg) {
|
|
853
|
+
return {
|
|
854
|
+
id: 'msg_dario', type: 'message', role: 'assistant', model: fallbackModel,
|
|
855
|
+
content, stop_reason: 'end_turn', stop_sequence: null,
|
|
856
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
return { ...msg, content };
|
|
860
|
+
},
|
|
861
|
+
};
|
|
862
|
+
}
|
|
794
863
|
export function createResponsesSSEParser() {
|
|
795
864
|
let buffer = '';
|
|
796
865
|
const boundary = /\r?\n\r?\n/;
|
package/dist/codex-backend.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { anthropicToResponsesRequest, createResponsesSSEParser, formatResponsesAnthropicSSE,
|
|
1
|
+
import { anthropicToResponsesRequest, createResponsesSSEParser, formatResponsesAnthropicSSE, createAnthropicMessageAssembler, responsesStreamToAnthropicSSE, } from './anthropic-responses-translate.js';
|
|
2
2
|
export const CODEX_BACKEND_BASE_URL = process.env.DARIO_CODEX_BASE_URL || 'https://chatgpt.com/backend-api/codex';
|
|
3
3
|
/** Originator string the codex CLI identifies itself with. */
|
|
4
4
|
const CODEX_ORIGINATOR = 'codex_cli_rs';
|
|
@@ -471,6 +471,10 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
471
471
|
const translator = isAnthropic ? null : createResponsesTranslator(model);
|
|
472
472
|
const sseParser = isAnthropic ? createResponsesSSEParser() : null;
|
|
473
473
|
const antTranslator = isAnthropic ? responsesStreamToAnthropicSSE({ requestModel: model }) : null;
|
|
474
|
+
// The non-streaming body is FOLDED FROM THE STREAM, not read off the
|
|
475
|
+
// terminal event: this backend sends response.completed with output: [],
|
|
476
|
+
// so the content exists only in the deltas (dario#1143).
|
|
477
|
+
const antAssembler = isAnthropic ? createAnthropicMessageAssembler() : null;
|
|
474
478
|
let terminalResponse = null;
|
|
475
479
|
let anthropicFailed = false;
|
|
476
480
|
const emitAnthropic = (events) => {
|
|
@@ -483,7 +487,10 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
483
487
|
if (t === 'response.failed')
|
|
484
488
|
anthropicFailed = true;
|
|
485
489
|
}
|
|
486
|
-
|
|
490
|
+
const produced = antTranslator.push(ev);
|
|
491
|
+
if (!clientWantsStream)
|
|
492
|
+
antAssembler.push(produced);
|
|
493
|
+
for (const out of produced) {
|
|
487
494
|
if (clientWantsStream)
|
|
488
495
|
res.write(formatResponsesAnthropicSSE(out));
|
|
489
496
|
}
|
|
@@ -563,7 +570,8 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
563
570
|
'Access-Control-Allow-Origin': corsOrigin,
|
|
564
571
|
...securityHeaders,
|
|
565
572
|
});
|
|
566
|
-
|
|
573
|
+
antAssembler.push(antTranslator.end());
|
|
574
|
+
res.end(JSON.stringify(antAssembler.message(model)));
|
|
567
575
|
}
|
|
568
576
|
}
|
|
569
577
|
else if (clientWantsStream) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.89",
|
|
4
4
|
"description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|