@askalf/dario 5.5.87 → 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';
|
|
@@ -434,6 +434,17 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
434
434
|
const upstreamBody = isAnthropic
|
|
435
435
|
? { ...anthropicToResponsesRequest(parsed, model), stream: true }
|
|
436
436
|
: chatCompletionsToResponses(parsed);
|
|
437
|
+
// The ChatGPT Codex backend REJECTS an output cap outright:
|
|
438
|
+
// 400 {"detail":"Unsupported parameter: max_output_tokens"}
|
|
439
|
+
// Both builders set it from the client's max_tokens / max_completion_tokens,
|
|
440
|
+
// so BOTH shapes 400 whenever a client asks for one. It stayed hidden because
|
|
441
|
+
// every smoke test so far happened to omit max_tokens; it then showed up as a
|
|
442
|
+
// 100% failure on the Anthropic path, where the Messages API REQUIRES
|
|
443
|
+
// max_tokens and so always produced it. Stripped here rather than in either
|
|
444
|
+
// translator because it is a property of THIS backend, not of either wire
|
|
445
|
+
// format — the same builders are correct against an API-key Responses
|
|
446
|
+
// endpoint, which does support the parameter.
|
|
447
|
+
delete upstreamBody.max_output_tokens;
|
|
437
448
|
const target = `${CODEX_BACKEND_BASE_URL.replace(/\/$/, '')}/responses`;
|
|
438
449
|
const abort = new AbortController();
|
|
439
450
|
const timeout = setTimeout(() => abort.abort(), upstreamTimeoutMs);
|
|
@@ -460,6 +471,10 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
460
471
|
const translator = isAnthropic ? null : createResponsesTranslator(model);
|
|
461
472
|
const sseParser = isAnthropic ? createResponsesSSEParser() : null;
|
|
462
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;
|
|
463
478
|
let terminalResponse = null;
|
|
464
479
|
let anthropicFailed = false;
|
|
465
480
|
const emitAnthropic = (events) => {
|
|
@@ -472,7 +487,10 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
472
487
|
if (t === 'response.failed')
|
|
473
488
|
anthropicFailed = true;
|
|
474
489
|
}
|
|
475
|
-
|
|
490
|
+
const produced = antTranslator.push(ev);
|
|
491
|
+
if (!clientWantsStream)
|
|
492
|
+
antAssembler.push(produced);
|
|
493
|
+
for (const out of produced) {
|
|
476
494
|
if (clientWantsStream)
|
|
477
495
|
res.write(formatResponsesAnthropicSSE(out));
|
|
478
496
|
}
|
|
@@ -552,7 +570,8 @@ export async function forwardToCodex(req, res, body, creds, corsOrigin, security
|
|
|
552
570
|
'Access-Control-Allow-Origin': corsOrigin,
|
|
553
571
|
...securityHeaders,
|
|
554
572
|
});
|
|
555
|
-
|
|
573
|
+
antAssembler.push(antTranslator.end());
|
|
574
|
+
res.end(JSON.stringify(antAssembler.message(model)));
|
|
556
575
|
}
|
|
557
576
|
}
|
|
558
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": {
|