@askalf/dario 5.5.84 → 5.5.86
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.
- package/dist/cli.js +2 -0
- package/dist/codex-accounts.d.ts +3 -0
- package/dist/codex-accounts.js +33 -0
- package/dist/codex-backend.js +25 -5
- package/dist/proxy.js +18 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1138,6 +1138,8 @@ async function codex() {
|
|
|
1138
1138
|
await completeAddCodexAccount(alias, code, codeVerifier);
|
|
1139
1139
|
console.log('');
|
|
1140
1140
|
console.log(` Added Codex account "${alias}".`);
|
|
1141
|
+
console.log(' A proxy that is already running picks it up within ~30s —');
|
|
1142
|
+
console.log(' no restart needed (dario#1138).');
|
|
1141
1143
|
console.log('');
|
|
1142
1144
|
}
|
|
1143
1145
|
catch (err) {
|
package/dist/codex-accounts.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export interface CodexAccountCredentials {
|
|
|
6
6
|
idToken?: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function listCodexAccountAliases(): Promise<string[]>;
|
|
9
|
+
export declare function hasAnyCodexAccount(nowMs?: number): Promise<boolean>;
|
|
10
|
+
/** Drop the negative cache so a test doesn't have to sleep out its TTL. */
|
|
11
|
+
export declare function _resetCodexPresenceCacheForTest(): void;
|
|
9
12
|
export declare function loadCodexAccount(alias: string): Promise<CodexAccountCredentials | null>;
|
|
10
13
|
export declare function loadAllCodexAccounts(): Promise<CodexAccountCredentials[]>;
|
|
11
14
|
export declare function saveCodexAccount(creds: CodexAccountCredentials): Promise<void>;
|
package/dist/codex-accounts.js
CHANGED
|
@@ -43,6 +43,35 @@ export async function listCodexAccountAliases() {
|
|
|
43
43
|
return [];
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* "Is the codex route available at all" — the routing question, asked per
|
|
48
|
+
* request rather than once at startup (dario#1138).
|
|
49
|
+
*
|
|
50
|
+
* The proxy used to resolve this a single time while booting, so an account
|
|
51
|
+
* stored by `dario codex add` against an ALREADY-RUNNING proxy stayed
|
|
52
|
+
* invisible until a restart: /v1/models advertised no gpt slugs and a listed
|
|
53
|
+
* slug fell through to the Claude path as an unknown model. `dario login`
|
|
54
|
+
* restarts the proxy by convention, `dario codex add` does not.
|
|
55
|
+
*
|
|
56
|
+
* Cheap enough to ask often — a readdir of a directory holding at most a
|
|
57
|
+
* handful of files — but an idle proxy still shouldn't hit the filesystem on
|
|
58
|
+
* every request, so the NEGATIVE answer is cached briefly. The positive one
|
|
59
|
+
* isn't cached: the caller goes on to read the credentials anyway, and a
|
|
60
|
+
* `codex remove` has to take effect immediately.
|
|
61
|
+
*/
|
|
62
|
+
const CODEX_PRESENCE_NEGATIVE_TTL_MS = 30_000;
|
|
63
|
+
let codexAbsentUntil = 0;
|
|
64
|
+
export async function hasAnyCodexAccount(nowMs = Date.now()) {
|
|
65
|
+
if (nowMs < codexAbsentUntil)
|
|
66
|
+
return false;
|
|
67
|
+
const present = (await listCodexAccountAliases()).length > 0;
|
|
68
|
+
codexAbsentUntil = present ? 0 : nowMs + CODEX_PRESENCE_NEGATIVE_TTL_MS;
|
|
69
|
+
return present;
|
|
70
|
+
}
|
|
71
|
+
/** Drop the negative cache so a test doesn't have to sleep out its TTL. */
|
|
72
|
+
export function _resetCodexPresenceCacheForTest() {
|
|
73
|
+
codexAbsentUntil = 0;
|
|
74
|
+
}
|
|
46
75
|
export async function loadCodexAccount(alias) {
|
|
47
76
|
const path = safeAliasPath(alias);
|
|
48
77
|
if (!path)
|
|
@@ -66,6 +95,10 @@ export async function saveCodexAccount(creds) {
|
|
|
66
95
|
throw new Error(`invalid codex account alias: ${creds.alias}`);
|
|
67
96
|
await ensureDir();
|
|
68
97
|
await durableWriteFile(path, JSON.stringify(creds, null, 2), 0o600);
|
|
98
|
+
// An account stored in THIS process (`dario codex add` in a running admin
|
|
99
|
+
// path, or a test) must not be hidden by a negative cache entry taken
|
|
100
|
+
// moments earlier.
|
|
101
|
+
codexAbsentUntil = 0;
|
|
69
102
|
}
|
|
70
103
|
export async function removeCodexAccount(alias) {
|
|
71
104
|
const path = safeAliasPath(alias);
|
package/dist/codex-backend.js
CHANGED
|
@@ -228,6 +228,7 @@ export function createResponsesTranslator(model) {
|
|
|
228
228
|
let usage = null;
|
|
229
229
|
const toolCalls = new Map();
|
|
230
230
|
let nextToolIndex = 0;
|
|
231
|
+
let roleSent = false;
|
|
231
232
|
const frame = (delta, finish) => `data: ${JSON.stringify({
|
|
232
233
|
id,
|
|
233
234
|
object: 'chat.completion.chunk',
|
|
@@ -235,6 +236,25 @@ export function createResponsesTranslator(model) {
|
|
|
235
236
|
model,
|
|
236
237
|
choices: [{ index: 0, delta, finish_reason: finish }],
|
|
237
238
|
})}\n\n`;
|
|
239
|
+
/**
|
|
240
|
+
* Prefix the role-only opening frame the first time we emit anything.
|
|
241
|
+
*
|
|
242
|
+
* The reference OpenAI stream always opens with
|
|
243
|
+
* `delta: {"role":"assistant","content":""}` before any content, and SDK
|
|
244
|
+
* accumulators (openai-node's ChatCompletionStream, and every harness built
|
|
245
|
+
* on it — Cursor, Continue, Aider) use that frame to OPEN the assistant
|
|
246
|
+
* message. Without it the assembled message has no role, which reads fine in
|
|
247
|
+
* a terminal and then fails when the harness sends that history back.
|
|
248
|
+
* Emitted at `response.created` in the normal case; the flag makes every
|
|
249
|
+
* other branch safe too, so a stream that somehow starts with a text delta
|
|
250
|
+
* still cannot put content on the wire before the role.
|
|
251
|
+
*/
|
|
252
|
+
const opened = (rest) => {
|
|
253
|
+
if (roleSent)
|
|
254
|
+
return rest;
|
|
255
|
+
roleSent = true;
|
|
256
|
+
return frame({ role: 'assistant', content: '' }, null) + rest;
|
|
257
|
+
};
|
|
238
258
|
return {
|
|
239
259
|
/** Feed one raw SSE line. Returns the line to forward, or null. */
|
|
240
260
|
chunk(line) {
|
|
@@ -255,14 +275,14 @@ export function createResponsesTranslator(model) {
|
|
|
255
275
|
const r = e.response;
|
|
256
276
|
if (r?.id)
|
|
257
277
|
id = `chatcmpl-${r.id.replace(/^resp_/, '')}`;
|
|
258
|
-
return
|
|
278
|
+
return opened('');
|
|
259
279
|
}
|
|
260
280
|
if (type === 'response.output_text.delta') {
|
|
261
281
|
const d = typeof e.delta === 'string' ? e.delta : '';
|
|
262
282
|
if (!d)
|
|
263
283
|
return null;
|
|
264
284
|
text += d;
|
|
265
|
-
return frame({ content: d }, null);
|
|
285
|
+
return opened(frame({ content: d }, null));
|
|
266
286
|
}
|
|
267
287
|
if (type === 'response.output_item.added') {
|
|
268
288
|
const item = e.item;
|
|
@@ -276,7 +296,7 @@ export function createResponsesTranslator(model) {
|
|
|
276
296
|
args: '',
|
|
277
297
|
};
|
|
278
298
|
toolCalls.set(key, acc);
|
|
279
|
-
return frame({ tool_calls: [{ index: acc.index, id: acc.id, type: 'function', function: { name: acc.name, arguments: '' } }] }, null);
|
|
299
|
+
return opened(frame({ tool_calls: [{ index: acc.index, id: acc.id, type: 'function', function: { name: acc.name, arguments: '' } }] }, null));
|
|
280
300
|
}
|
|
281
301
|
if (type === 'response.function_call_arguments.delta') {
|
|
282
302
|
const key = String(e.item_id ?? '');
|
|
@@ -285,7 +305,7 @@ export function createResponsesTranslator(model) {
|
|
|
285
305
|
if (!acc || !d)
|
|
286
306
|
return null;
|
|
287
307
|
acc.args += d;
|
|
288
|
-
return frame({ tool_calls: [{ index: acc.index, function: { arguments: d } }] }, null);
|
|
308
|
+
return opened(frame({ tool_calls: [{ index: acc.index, function: { arguments: d } }] }, null));
|
|
289
309
|
}
|
|
290
310
|
if (type === 'response.completed' || type === 'response.incomplete') {
|
|
291
311
|
const r = e.response;
|
|
@@ -297,7 +317,7 @@ export function createResponsesTranslator(model) {
|
|
|
297
317
|
total_tokens: (u.input_tokens ?? 0) + (u.output_tokens ?? 0),
|
|
298
318
|
};
|
|
299
319
|
}
|
|
300
|
-
return `${frame({}, toolCalls.size > 0 ? 'tool_calls' : 'stop')}data: [DONE]\n\n
|
|
320
|
+
return opened(`${frame({}, toolCalls.size > 0 ? 'tool_calls' : 'stop')}data: [DONE]\n\n`);
|
|
301
321
|
}
|
|
302
322
|
return null;
|
|
303
323
|
},
|
package/dist/proxy.js
CHANGED
|
@@ -21,7 +21,7 @@ import { handleAdminRequest } from './admin-api.js';
|
|
|
21
21
|
import { createTokenBucket } from './rate-limit.js';
|
|
22
22
|
import { getOpenAIBackend, isOpenAIModel, forwardToOpenAI } from './openai-backend.js';
|
|
23
23
|
import { forwardToCodex, getCodexModelSlugs, CODEX_BACKEND_BASE_URL } from './codex-backend.js';
|
|
24
|
-
import { listCodexAccountAliases, selectCodexAccount, getFreshCodexAccount } from './codex-accounts.js';
|
|
24
|
+
import { listCodexAccountAliases, hasAnyCodexAccount, selectCodexAccount, getFreshCodexAccount } from './codex-accounts.js';
|
|
25
25
|
import { route as routeProvider } from './provider-adapter.js';
|
|
26
26
|
import { RequestQueue, QueueFullError, QueueTimeoutError, DEFAULT_MAX_CONCURRENT, DEFAULT_MAX_QUEUED, DEFAULT_QUEUE_TIMEOUT_MS } from './request-queue.js';
|
|
27
27
|
import { redactSecrets } from './redact.js';
|
|
@@ -1222,12 +1222,16 @@ export async function startProxy(opts = {}) {
|
|
|
1222
1222
|
console.log(` OpenAI-compat backend: ${openaiBackend.name} → ${openaiBackend.baseUrl}`);
|
|
1223
1223
|
}
|
|
1224
1224
|
// Codex/ChatGPT-subscription accounts — the "altman" engine (dario#1009).
|
|
1225
|
-
//
|
|
1226
|
-
//
|
|
1227
|
-
//
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1225
|
+
// This startup probe only decides what to PRINT and whether a Claude login
|
|
1226
|
+
// is required to boot; routing re-asks hasAnyCodexAccount() per request
|
|
1227
|
+
// (dario#1138), so an account stored by `dario codex add` against an
|
|
1228
|
+
// already-running proxy is picked up without a restart — `dario login`
|
|
1229
|
+
// restarts the proxy by convention, `dario codex add` does not. The
|
|
1230
|
+
// credentials themselves are re-read per request too (they rotate on
|
|
1231
|
+
// refresh).
|
|
1232
|
+
const startupCodexAliases = await listCodexAccountAliases();
|
|
1233
|
+
if (startupCodexAliases.length > 0) {
|
|
1234
|
+
console.log(` Codex accounts: ${startupCodexAliases.join(', ')} → ${CODEX_BACKEND_BASE_URL}`);
|
|
1231
1235
|
}
|
|
1232
1236
|
// Pool-exhausted fallback (strictly opt-in). When the Claude pool can't
|
|
1233
1237
|
// serve — every seat rate-limited or in auth cool-down — OpenAI-shape
|
|
@@ -1421,7 +1425,7 @@ export async function startProxy(opts = {}) {
|
|
|
1421
1425
|
// dead-but-refreshable token (a container restarted right after a normal
|
|
1422
1426
|
// expiry, gap #1) is refreshed, then back-filled so the recovered login
|
|
1423
1427
|
// becomes the pool-of-one it should be — rather than crash-looping on exit(1).
|
|
1424
|
-
if (requiresClaudeLogin(pool.size, adminEnabled, !!upstreamApiKey, opts.noClaudeAuth ?? false,
|
|
1428
|
+
if (requiresClaudeLogin(pool.size, adminEnabled, !!upstreamApiKey, opts.noClaudeAuth ?? false, startupCodexAliases.length > 0)) {
|
|
1425
1429
|
const single = await resolveSingleAccountStartupStatus();
|
|
1426
1430
|
if (!single.authenticated) {
|
|
1427
1431
|
console.error('[dario] Not authenticated. Run `dario login` first.');
|
|
@@ -2168,7 +2172,7 @@ export async function startProxy(opts = {}) {
|
|
|
2168
2172
|
// models that account may actually use. Discovery is cached and never
|
|
2169
2173
|
// throws, so /v1/models keeps its "always answers" property.
|
|
2170
2174
|
let codexNames = [];
|
|
2171
|
-
if (
|
|
2175
|
+
if (await hasAnyCodexAccount()) {
|
|
2172
2176
|
const stored = await selectCodexAccount();
|
|
2173
2177
|
if (stored) {
|
|
2174
2178
|
const creds = await getFreshCodexAccount(stored).catch(() => stored);
|
|
@@ -2490,17 +2494,16 @@ export async function startProxy(opts = {}) {
|
|
|
2490
2494
|
// case is a map lookup, not a request.
|
|
2491
2495
|
let codexCreds = null;
|
|
2492
2496
|
let codexModels = [];
|
|
2493
|
-
|
|
2497
|
+
// Presence is re-asked here rather than read from a startup flag so
|
|
2498
|
+
// an account added while the proxy runs routes on the very next
|
|
2499
|
+
// request; the absent answer is cached ~30s, so an idle proxy with
|
|
2500
|
+
// no codex account is not stat-ing the filesystem per request.
|
|
2501
|
+
if (isOpenAI && await hasAnyCodexAccount()) {
|
|
2494
2502
|
const stored = await selectCodexAccount();
|
|
2495
2503
|
if (stored) {
|
|
2496
2504
|
codexCreds = await getFreshCodexAccount(stored);
|
|
2497
2505
|
codexModels = await getCodexModelSlugs(codexCreds);
|
|
2498
2506
|
}
|
|
2499
|
-
else {
|
|
2500
|
-
// Accounts disappeared since startup — re-arm the routing flag so
|
|
2501
|
-
// later requests skip the codex path entirely.
|
|
2502
|
-
hasCodexAccount = false;
|
|
2503
|
-
}
|
|
2504
2507
|
}
|
|
2505
2508
|
const decision = routeProvider({
|
|
2506
2509
|
isOpenAIPath: isOpenAI,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.86",
|
|
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": {
|