@askalf/dario 6.19.2 → 6.20.0
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/README.md +2 -1
- package/dist/admin-api.d.ts +1 -1
- package/dist/admin-api.js +40 -5
- package/dist/analytics.d.ts +16 -1
- package/dist/analytics.js +25 -2
- package/dist/anthropic-responses-translate.d.ts +2 -0
- package/dist/anthropic-responses-translate.js +1 -1
- package/dist/cc-template.d.ts +9 -8
- package/dist/cc-template.js +39 -25
- package/dist/cli.js +44 -10
- package/dist/doctor-core.d.ts +1 -1
- package/dist/doctor-core.js +2 -2
- package/dist/effort.d.ts +18 -0
- package/dist/effort.js +38 -0
- package/dist/keys.d.ts +20 -0
- package/dist/keys.js +27 -1
- package/dist/ledger.d.ts +17 -1
- package/dist/ledger.js +43 -9
- package/dist/live-fingerprint.js +2 -2
- package/dist/proxy.js +42 -11
- package/docs/admin-api.md +2 -1
- package/docs/configuration.md +10 -3
- package/docs/drift-monitor.md +4 -4
- package/docs/keys.md +36 -1
- package/docs/pool-keys-and-analytics.md +3 -0
- package/docs/system-prompt.md +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/v/@askalf/dario?color=6f42c1&label=npm&logo=npm" alt="npm version"></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/@askalf/dario"><img src="https://img.shields.io/npm/dm/@askalf/dario?color=6f42c1" alt="Downloads"></a>
|
|
12
12
|
<a href="https://github.com/askalf/dario/actions/workflows/ci.yml"><img src="https://github.com/askalf/dario/actions/workflows/ci.yml/badge.svg?branch=master" alt="CI"></a>
|
|
13
|
+
<a href="https://pitlane.askalf.org/"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fpitlane.askalf.org%2Fbadge.json" alt="CI on pitlane"></a>
|
|
13
14
|
<a href="https://scorecard.dev/viewer/?uri=github.com/askalf/dario"><img src="https://api.scorecard.dev/projects/github.com/askalf/dario/badge" alt="OpenSSF Scorecard"></a>
|
|
14
15
|
<a href="https://github.com/askalf/dario/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@askalf/dario?color=6f42c1" alt="License"></a>
|
|
15
16
|
</p>
|
|
@@ -117,7 +118,7 @@ dario runs entirely on your machine, authenticates as you with your own Claude l
|
|
|
117
118
|
| Dependencies | **0 runtime.** Verify: `npm ls --production` |
|
|
118
119
|
| Provenance | Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions + Sigstore, published with OIDC trusted publishing: no long-lived npm token exists to leak |
|
|
119
120
|
| Scanning | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) on every push to master, every PR and weekly · [ClusterFuzzLite](./.github/workflows/cflite.yml) fuzzes the SSE translator, the rejection parsers and the cch stamper weekly · the [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/askalf/dario) badge above and the [Best Practices](https://www.bestpractices.dev/projects/13638) entry are live |
|
|
120
|
-
| Tests | **
|
|
121
|
+
| Tests | **287 test files** run in parallel by `npm test` on Node 18, 20 and 22; the live e2e / compat / stealth suites have their own entry points. Green on every release |
|
|
121
122
|
| Credentials | Your own subscription tokens, never logged, redacted from errors, `0600` on disk in `0700` dirs |
|
|
122
123
|
| Network | Binds `127.0.0.1` by default; upstream only to configured backends over HTTPS; hardcoded SSRF allow-list; refuses a non-loopback bind without `DARIO_API_KEY`; refuses cross-origin and DNS-rebound browser requests |
|
|
123
124
|
| Telemetry | **None.** No analytics, no tracking, nothing phones home |
|
package/dist/admin-api.d.ts
CHANGED
|
@@ -168,7 +168,7 @@ export interface AdminCodexAccountRecord extends CodexSeatState {
|
|
|
168
168
|
usage: CodexUsageView | null;
|
|
169
169
|
}
|
|
170
170
|
export interface AdminAuditEvent {
|
|
171
|
-
action: 'login_start' | 'login_complete' | 'account_remove' | 'auth_reject' | 'rate_limited' | 'key_create' | 'key_revoke' | 'key_rotate' | 'key_budget';
|
|
171
|
+
action: 'login_start' | 'login_complete' | 'account_remove' | 'auth_reject' | 'rate_limited' | 'key_create' | 'key_revoke' | 'key_rotate' | 'key_budget' | 'key_effort';
|
|
172
172
|
/** Which engine's credentials the event touched; absent means Claude, the only engine before codex joined (dario#1009). */
|
|
173
173
|
engine?: 'codex';
|
|
174
174
|
ok: boolean;
|
package/dist/admin-api.js
CHANGED
|
@@ -5,7 +5,7 @@ import { startAddCodexAccount, completeAddCodexAccount, removeCodexAccount, load
|
|
|
5
5
|
import { codexUsageView } from './codex-usage.js';
|
|
6
6
|
import { parseManualPaste } from './oauth.js';
|
|
7
7
|
import { grantAge } from './refresh-grant.js';
|
|
8
|
-
import { createKey, revokeKey, rotateKey, parseExpiry, publicKey, setKeyBudget, normalizeBudget, KEY_NAME_RE } from './keys.js';
|
|
8
|
+
import { createKey, revokeKey, rotateKey, parseExpiry, publicKey, setKeyBudget, setKeyEffort, isKeyEffort, normalizeBudget, KEY_NAME_RE, KEY_EFFORT_VALUES } from './keys.js';
|
|
9
9
|
/**
|
|
10
10
|
* `{ budget_usd_per_day, budget_tokens_per_day }` (numbers, or numeric strings)
|
|
11
11
|
* → a KeyBudget; undefined when neither is present; throws with a 400-worthy
|
|
@@ -267,8 +267,10 @@ export async function handleAdminRequest(req, res, urlPath, deps) {
|
|
|
267
267
|
const isKeyRotate = keyTarget !== null && keyTarget.endsWith('/rotate');
|
|
268
268
|
// POST /admin/keys/<name>/budget — set or clear a key's daily caps (dario#1318 follow-up).
|
|
269
269
|
const isKeyBudget = keyTarget !== null && keyTarget.endsWith('/budget');
|
|
270
|
-
|
|
271
|
-
const
|
|
270
|
+
// POST /admin/keys/<name>/effort: set or clear a key's default effort.
|
|
271
|
+
const isKeyEffortRoute = keyTarget !== null && keyTarget.endsWith('/effort');
|
|
272
|
+
const keyName = keyTarget === null ? null : isKeyRotate ? keyTarget.slice(0, -'/rotate'.length) : isKeyBudget ? keyTarget.slice(0, -'/budget'.length) : isKeyEffortRoute ? keyTarget.slice(0, -'/effort'.length) : keyTarget;
|
|
273
|
+
const isKeyRevoke = keyTarget !== null && !isKeyRotate && !isKeyBudget && !isKeyEffortRoute && method === 'DELETE';
|
|
272
274
|
const known = urlPath === '/admin/login/start' ||
|
|
273
275
|
urlPath === '/admin/login/start-needed' ||
|
|
274
276
|
urlPath === '/admin/login/complete' ||
|
|
@@ -281,6 +283,7 @@ export async function handleAdminRequest(req, res, urlPath, deps) {
|
|
|
281
283
|
isAccountDelete ||
|
|
282
284
|
isKeyRotate ||
|
|
283
285
|
isKeyBudget ||
|
|
286
|
+
isKeyEffortRoute ||
|
|
284
287
|
isKeyRevoke;
|
|
285
288
|
if (!known)
|
|
286
289
|
return false;
|
|
@@ -313,7 +316,7 @@ export async function handleAdminRequest(req, res, urlPath, deps) {
|
|
|
313
316
|
// accounts' credentials for the price of one throttle token.
|
|
314
317
|
const isMutation = urlPath === '/admin/login/start' || isAccountDelete
|
|
315
318
|
|| urlPath === '/admin/codex/login/start' || isCodexAccountDelete
|
|
316
|
-
|| (urlPath === '/admin/keys' && method === 'POST') || isKeyRotate || isKeyBudget || isKeyRevoke;
|
|
319
|
+
|| (urlPath === '/admin/keys' && method === 'POST') || isKeyRotate || isKeyBudget || isKeyEffortRoute || isKeyRevoke;
|
|
317
320
|
if (isMutation) {
|
|
318
321
|
const wait = deps.rateLimit?.('mutation') ?? 0;
|
|
319
322
|
if (wait > 0) {
|
|
@@ -642,8 +645,16 @@ export async function handleAdminRequest(req, res, urlPath, deps) {
|
|
|
642
645
|
send(res, 400, { error: err.message });
|
|
643
646
|
return true;
|
|
644
647
|
}
|
|
648
|
+
let effort;
|
|
649
|
+
if (body.effort !== undefined && body.effort !== null) {
|
|
650
|
+
if (!isKeyEffort(body.effort)) {
|
|
651
|
+
send(res, 400, { error: `invalid "effort": one of ${KEY_EFFORT_VALUES.join(', ')}` });
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
effort = body.effort;
|
|
655
|
+
}
|
|
645
656
|
try {
|
|
646
|
-
const made = store.mutate((file) => createKey(file, name, { seat, models, expiresAt, budget, now }));
|
|
657
|
+
const made = store.mutate((file) => createKey(file, name, { seat, models, expiresAt, budget, effort, now }));
|
|
647
658
|
deps.audit?.({ action: 'key_create', ok: true, status: 201, key: made.record.name, remote, detail: seat ? `seat=${seat}` : undefined });
|
|
648
659
|
send(res, 201, { key: publicKey(made.record, now), secret: made.secret, note: 'the secret is shown once and is not stored' });
|
|
649
660
|
}
|
|
@@ -702,6 +713,30 @@ export async function handleAdminRequest(req, res, urlPath, deps) {
|
|
|
702
713
|
send(res, 200, { key: publicKey(updated, now) });
|
|
703
714
|
return true;
|
|
704
715
|
}
|
|
716
|
+
// POST /admin/keys/<name>/effort { effort: "low" | ... | "max" | null }; null or absent clears.
|
|
717
|
+
if (isKeyEffortRoute) {
|
|
718
|
+
if (method !== 'POST') {
|
|
719
|
+
send(res, 405, { error: 'Method not allowed (use POST)' });
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
const body = await readJsonBody(req);
|
|
723
|
+
let effort = null;
|
|
724
|
+
if (body.effort !== undefined && body.effort !== null) {
|
|
725
|
+
if (!isKeyEffort(body.effort)) {
|
|
726
|
+
send(res, 400, { error: `invalid "effort": one of ${KEY_EFFORT_VALUES.join(', ')}` });
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
effort = body.effort;
|
|
730
|
+
}
|
|
731
|
+
const updated = store.mutate((file) => setKeyEffort(file, keyName, effort));
|
|
732
|
+
deps.audit?.({ action: 'key_effort', ok: updated !== null, status: updated ? 200 : 404, key: keyName, remote, detail: effort ?? 'cleared' });
|
|
733
|
+
if (!updated) {
|
|
734
|
+
send(res, 404, { error: `no key named "${keyName}"` });
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
send(res, 200, { key: publicKey(updated, now) });
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
705
740
|
// DELETE /admin/keys/<name> — revoked, kept for the list.
|
|
706
741
|
if (isKeyRevoke) {
|
|
707
742
|
const revoked = store.mutate((file) => revokeKey(file, keyName));
|
package/dist/analytics.d.ts
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { EventEmitter } from 'node:events';
|
|
20
20
|
import { type RequestTiming, type TimingStats } from './timing.js';
|
|
21
|
+
/**
|
|
22
|
+
* The billed thinking count inside a usage block, or undefined when the block
|
|
23
|
+
* carries none: Anthropic's `output_tokens_details.thinking_tokens`. The
|
|
24
|
+
* number is a share of `output_tokens`, never an addition to it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function thinkingTokensFromUsage(usage: unknown): number | undefined;
|
|
21
27
|
export interface RequestRecord {
|
|
22
28
|
timestamp: number;
|
|
23
29
|
/**
|
|
@@ -341,7 +347,16 @@ export declare class Analytics extends EventEmitter {
|
|
|
341
347
|
* empty list.
|
|
342
348
|
*/
|
|
343
349
|
recent(n?: number): RequestRecord[];
|
|
344
|
-
/**
|
|
350
|
+
/**
|
|
351
|
+
* Parse usage from a non-streaming Anthropic response body.
|
|
352
|
+
*
|
|
353
|
+
* Thinking tokens come from `usage.output_tokens_details.thinking_tokens`
|
|
354
|
+
* when the response carries it. The older estimate, a quarter of the
|
|
355
|
+
* characters in the `thinking` blocks, stays as the fallback for responses
|
|
356
|
+
* without the field, but it reads ZERO on every current model, because
|
|
357
|
+
* they return thinking blocks with empty text unless the caller asked for a
|
|
358
|
+
* summary. The usage field counts what was billed.
|
|
359
|
+
*/
|
|
345
360
|
static parseUsage(body: Record<string, unknown>): {
|
|
346
361
|
inputTokens: number;
|
|
347
362
|
outputTokens: number;
|
package/dist/analytics.js
CHANGED
|
@@ -19,6 +19,20 @@
|
|
|
19
19
|
import { EventEmitter } from 'node:events';
|
|
20
20
|
import { createHash } from 'node:crypto';
|
|
21
21
|
import { timingStats } from './timing.js';
|
|
22
|
+
/**
|
|
23
|
+
* The billed thinking count inside a usage block, or undefined when the block
|
|
24
|
+
* carries none: Anthropic's `output_tokens_details.thinking_tokens`. The
|
|
25
|
+
* number is a share of `output_tokens`, never an addition to it.
|
|
26
|
+
*/
|
|
27
|
+
export function thinkingTokensFromUsage(usage) {
|
|
28
|
+
if (!usage || typeof usage !== 'object')
|
|
29
|
+
return undefined;
|
|
30
|
+
const details = usage.output_tokens_details;
|
|
31
|
+
if (!details || typeof details !== 'object')
|
|
32
|
+
return undefined;
|
|
33
|
+
const n = details.thinking_tokens;
|
|
34
|
+
return typeof n === 'number' && Number.isFinite(n) && n >= 0 ? Math.round(n) : undefined;
|
|
35
|
+
}
|
|
22
36
|
export function continuationStats(records) {
|
|
23
37
|
const out = { attempted: 0, finished: 0, unfinished: 0, failed: 0, noTarget: 0 };
|
|
24
38
|
for (const r of records) {
|
|
@@ -440,14 +454,23 @@ export class Analytics extends EventEmitter {
|
|
|
440
454
|
recent(n = 100) {
|
|
441
455
|
return this.records.slice(-n);
|
|
442
456
|
}
|
|
443
|
-
/**
|
|
457
|
+
/**
|
|
458
|
+
* Parse usage from a non-streaming Anthropic response body.
|
|
459
|
+
*
|
|
460
|
+
* Thinking tokens come from `usage.output_tokens_details.thinking_tokens`
|
|
461
|
+
* when the response carries it. The older estimate, a quarter of the
|
|
462
|
+
* characters in the `thinking` blocks, stays as the fallback for responses
|
|
463
|
+
* without the field, but it reads ZERO on every current model, because
|
|
464
|
+
* they return thinking blocks with empty text unless the caller asked for a
|
|
465
|
+
* summary. The usage field counts what was billed.
|
|
466
|
+
*/
|
|
444
467
|
static parseUsage(body) {
|
|
445
468
|
const u = body.usage;
|
|
446
469
|
const content = body.content;
|
|
447
470
|
const thinkingChars = content
|
|
448
471
|
?.filter(b => b.type === 'thinking')
|
|
449
472
|
.reduce((s, b) => s + (b.thinking?.length ?? 0), 0) ?? 0;
|
|
450
|
-
const thinkingTokens = Math.round(thinkingChars / 4);
|
|
473
|
+
const thinkingTokens = thinkingTokensFromUsage(u) ?? Math.round(thinkingChars / 4);
|
|
451
474
|
return {
|
|
452
475
|
inputTokens: u?.input_tokens ?? 0,
|
|
453
476
|
outputTokens: u?.output_tokens ?? 0,
|
|
@@ -387,6 +387,8 @@ export interface ResponsesResponse {
|
|
|
387
387
|
} | null;
|
|
388
388
|
usage?: ResponsesUsage | null;
|
|
389
389
|
}
|
|
390
|
+
/** thinking → reasoning effort, at the same cut points as the chat path. */
|
|
391
|
+
export declare function thinkingToReasoningEffort(thinking: AnthropicThinkingConfig | undefined): 'low' | 'medium' | 'high' | undefined;
|
|
390
392
|
/**
|
|
391
393
|
* Extra max_output_tokens reserved for reasoning tokens, by effort, so the
|
|
392
394
|
* client's intended visible-output budget survives on a reasoning model
|
|
@@ -111,7 +111,7 @@ export function anthropicUsageFromResponses(u) {
|
|
|
111
111
|
// Thresholds are IMPORTED so the two translators stay in lock-step.
|
|
112
112
|
// ─────────────────────────────────────────────────────────────────────
|
|
113
113
|
/** thinking → reasoning effort, at the same cut points as the chat path. */
|
|
114
|
-
function thinkingToReasoningEffort(thinking) {
|
|
114
|
+
export function thinkingToReasoningEffort(thinking) {
|
|
115
115
|
if (!thinking || thinking.type !== 'enabled')
|
|
116
116
|
return undefined;
|
|
117
117
|
const budget = thinking.budget_tokens;
|
package/dist/cc-template.d.ts
CHANGED
|
@@ -156,12 +156,13 @@ export declare const CC_AGENT_IDENTITY: string;
|
|
|
156
156
|
* - undefined / 'verbatim' — CC's prompt unchanged (default; existing
|
|
157
157
|
* setups don't regress).
|
|
158
158
|
* - 'partial' — strip purely behavioral constraints, leaving every
|
|
159
|
-
* refusal reminder and tool description intact.
|
|
160
|
-
* prompt
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* Tone-and-style
|
|
164
|
-
*
|
|
159
|
+
* refusal reminder and tool description intact. The strip runs on
|
|
160
|
+
* the prompt CC sends for `model`: a one-line comment rule (compact
|
|
161
|
+
* prompts, the fable variant) is swapped for a positive "be thorough"
|
|
162
|
+
* instruction; a verbose prompt (the sonnet-5 variant) also loses its
|
|
163
|
+
* Tone-and-style and Text-output sections and the scope, verbosity
|
|
164
|
+
* and comment bullets in Doing tasks. A prompt with none of these is
|
|
165
|
+
* sent unchanged. Recovers the output capability the
|
|
165
166
|
* constraint-removal research test measured.
|
|
166
167
|
* - 'aggressive' — partial + remove the prompt-level RLHF reminder (the
|
|
167
168
|
* IMPORTANT: line re-stating refusal categories) and the caution
|
|
@@ -177,7 +178,7 @@ export declare const CC_AGENT_IDENTITY: string;
|
|
|
177
178
|
* Precedence framing inserted between CC's persona prompt and the client's
|
|
178
179
|
* own system text in the merged block-3 system prompt.
|
|
179
180
|
*
|
|
180
|
-
* Why this exists (observed 2026-06-12,
|
|
181
|
+
* Why this exists (observed 2026-06-12, a downstream planner regression): a bare
|
|
181
182
|
* `\n\n` append silently stopped working on claude-sonnet-4-6 — the model
|
|
182
183
|
* followed the CC persona and treated the appended client instructions as
|
|
183
184
|
* ignorable boilerplate, deterministically (0/6 on a trivial "reply with
|
|
@@ -330,7 +331,7 @@ export declare function detectTextToolClient(systemText: string): string | null;
|
|
|
330
331
|
* own native tools (CC_NATIVE_NAMES), so it can never present a fully-foreign
|
|
331
332
|
* surface; only a genuinely non-CC tool set can. This catches the small
|
|
332
333
|
* in-house clients the 3-tool rule below
|
|
333
|
-
* misses, e.g.
|
|
334
|
+
* misses, e.g. in-house inspection agents dispatched with just their capability
|
|
334
335
|
* floor ([memory_store, db_query]). Before this, those 2-tool surfaces fell
|
|
335
336
|
* under a len<3 guard and got round-robined onto CC fallback slots, which
|
|
336
337
|
* silently corrupts every call (the model upstream never sees the real tool).
|
package/dist/cc-template.js
CHANGED
|
@@ -209,12 +209,13 @@ export const CC_AGENT_IDENTITY = TEMPLATE.agent_identity;
|
|
|
209
209
|
* - undefined / 'verbatim' — CC's prompt unchanged (default; existing
|
|
210
210
|
* setups don't regress).
|
|
211
211
|
* - 'partial' — strip purely behavioral constraints, leaving every
|
|
212
|
-
* refusal reminder and tool description intact.
|
|
213
|
-
* prompt
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* Tone-and-style
|
|
217
|
-
*
|
|
212
|
+
* refusal reminder and tool description intact. The strip runs on
|
|
213
|
+
* the prompt CC sends for `model`: a one-line comment rule (compact
|
|
214
|
+
* prompts, the fable variant) is swapped for a positive "be thorough"
|
|
215
|
+
* instruction; a verbose prompt (the sonnet-5 variant) also loses its
|
|
216
|
+
* Tone-and-style and Text-output sections and the scope, verbosity
|
|
217
|
+
* and comment bullets in Doing tasks. A prompt with none of these is
|
|
218
|
+
* sent unchanged. Recovers the output capability the
|
|
218
219
|
* constraint-removal research test measured.
|
|
219
220
|
* - 'aggressive' — partial + remove the prompt-level RLHF reminder (the
|
|
220
221
|
* IMPORTANT: line re-stating refusal categories) and the caution
|
|
@@ -230,7 +231,7 @@ export const CC_AGENT_IDENTITY = TEMPLATE.agent_identity;
|
|
|
230
231
|
* Precedence framing inserted between CC's persona prompt and the client's
|
|
231
232
|
* own system text in the merged block-3 system prompt.
|
|
232
233
|
*
|
|
233
|
-
* Why this exists (observed 2026-06-12,
|
|
234
|
+
* Why this exists (observed 2026-06-12, a downstream planner regression): a bare
|
|
234
235
|
* `\n\n` append silently stopped working on claude-sonnet-4-6 — the model
|
|
235
236
|
* followed the CC persona and treated the appended client instructions as
|
|
236
237
|
* ignorable boilerplate, deterministically (0/6 on a trivial "reply with
|
|
@@ -281,35 +282,48 @@ export function resolveSystemPrompt(arg, model) {
|
|
|
281
282
|
* Port of scripts/research/test-constraint-removal.mjs:stripConstraints. Pure over
|
|
282
283
|
* its input; returns the input unchanged if no target matches (so a CC
|
|
283
284
|
* bump that renames sections degrades to verbatim rather than producing
|
|
284
|
-
* an unpredictable strip).
|
|
285
|
-
*
|
|
286
|
-
*
|
|
285
|
+
* an unpredictable strip). CC sends a different prompt per model family
|
|
286
|
+
* and each words its constraints differently: the verbose shape (headed
|
|
287
|
+
* sections and Doing-tasks bullets, e.g. the sonnet-5 variant), the
|
|
288
|
+
* compact shape's comment-density line, and the fable variant's comment
|
|
289
|
+
* paragraph. Patterns for a shape not in play are no-ops, so a prompt
|
|
290
|
+
* carrying none of them (the 2.1.316 base and the opus-5 variant) comes
|
|
291
|
+
* back unchanged. test/system-prompt-modes.mjs lists which bundled prompt
|
|
292
|
+
* carries which constraint, so a rebake that rewords one fails there.
|
|
287
293
|
*/
|
|
288
294
|
function stripBehavioralConstraints(input, level) {
|
|
289
295
|
let s = input;
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
296
|
+
// Verbose shape: whole style sections, then the Doing-tasks bullets. A
|
|
297
|
+
// section runs to the next top-level heading or the end of the prompt.
|
|
298
|
+
// The end test is (?![\s\S]), not $: under the m flag $ matches at every
|
|
299
|
+
// line end, which cut the match at the heading and left the body in.
|
|
300
|
+
s = s.replace(/^# Tone and style[\s\S]*?(?:\n(?=# )|(?![\s\S]))/m, '');
|
|
301
|
+
s = s.replace(/^# Text output[\s\S]*?(?:\n(?=# )|(?![\s\S]))/m, '');
|
|
302
|
+
// Each pattern takes one bullet plus any wrapped or nested lines under
|
|
303
|
+
// it, and stops at the next top-level bullet, heading or blank line. CC
|
|
304
|
+
// writes each bullet on one line and the next bullet is often a neutral
|
|
305
|
+
// one ("Prefer editing existing files"), so a fixed line count would
|
|
306
|
+
// delete it.
|
|
293
307
|
const doingTasksConstraints = [
|
|
294
|
-
/^ - Don't add features, refactor, or introduce abstractions[^\n]*\n[^\n]
|
|
295
|
-
/^ - Don't add error handling, fallbacks, or validation[^\n]*\n[^\n]
|
|
296
|
-
/^ - Default to writing no comments\.[^\n]*\n[^\n]
|
|
297
|
-
/^ - Don't explain WHAT the code does[^\n]*\n[^\n]
|
|
298
|
-
/^ - For exploratory questions[^\n]*\n[^\n]
|
|
299
|
-
/^ - Avoid backwards-compatibility hacks[^\n]*\n[^\n]
|
|
308
|
+
/^ - Don't add features, refactor, or introduce abstractions[^\n]*\n(?:(?! - |#)[^\n]+\n)*/m,
|
|
309
|
+
/^ - Don't add error handling, fallbacks, or validation[^\n]*\n(?:(?! - |#)[^\n]+\n)*/m,
|
|
310
|
+
/^ - Default to writing no comments\.[^\n]*\n(?:(?! - |#)[^\n]+\n)*/m,
|
|
311
|
+
/^ - Don't explain WHAT the code does[^\n]*\n(?:(?! - |#)[^\n]+\n)*/m,
|
|
312
|
+
/^ - For exploratory questions[^\n]*\n(?:(?! - |#)[^\n]+\n)*/m,
|
|
313
|
+
/^ - Avoid backwards-compatibility hacks[^\n]*\n(?:(?! - |#)[^\n]+\n)*/m,
|
|
300
314
|
];
|
|
301
315
|
for (const re of doingTasksConstraints) {
|
|
302
316
|
s = s.replace(re, '');
|
|
303
317
|
}
|
|
304
318
|
s = s.replace(/^# Doing tasks\n/m, '# Doing tasks\n\nBe thorough. Show your reasoning. Provide the context and explanations the user is likely to find useful. Use as many tokens as the task warrants.\n\n');
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
s = s.replace(/^Write code that reads like the surrounding code
|
|
319
|
+
// Compact shape and the fable variant: the comment constraint is one
|
|
320
|
+
// unheaded line. Swap it for the same positive instruction the
|
|
321
|
+
// Doing-tasks rewrite inserts.
|
|
322
|
+
s = s.replace(/^(?:Write code that reads like the surrounding code:|Only write a code comment to state a constraint)[^\n]*\n/gm, 'Be thorough. Show your reasoning. Provide the context and explanations the user is likely to find useful. Use as many tokens as the task warrants.\n');
|
|
309
323
|
if (level === 'aggressive') {
|
|
310
324
|
s = s.replace(/^IMPORTANT: Assist with authorized security testing[^\n]*\n/m, '');
|
|
311
325
|
s = s.replace(/^IMPORTANT: You must NEVER generate or guess URLs[^\n]*\n/m, '');
|
|
312
|
-
s = s.replace(
|
|
326
|
+
s = s.replace(/^# Executing actions with care[\s\S]*?(?:\n(?=# )|(?![\s\S]))/m, '');
|
|
313
327
|
// Compact prompt: the caution guidance is a single unheaded paragraph.
|
|
314
328
|
s = s.replace(/^For actions that are hard to reverse or outward-facing,[^\n]*\n/m, '');
|
|
315
329
|
}
|
|
@@ -712,7 +726,7 @@ export function detectTextToolClient(systemText) {
|
|
|
712
726
|
* own native tools (CC_NATIVE_NAMES), so it can never present a fully-foreign
|
|
713
727
|
* surface; only a genuinely non-CC tool set can. This catches the small
|
|
714
728
|
* in-house clients the 3-tool rule below
|
|
715
|
-
* misses, e.g.
|
|
729
|
+
* misses, e.g. in-house inspection agents dispatched with just their capability
|
|
716
730
|
* floor ([memory_store, db_query]). Before this, those 2-tool surfaces fell
|
|
717
731
|
* under a len<3 guard and got round-robined onto CC fallback slots, which
|
|
718
732
|
* silently corrupts every call (the model upstream never sees the real tool).
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { unlink, writeFile } from 'node:fs/promises';
|
|
20
20
|
import { formatLedgerSummary, formatLedgerConsumers, formatUsd, renderLedgerCard, readLedgerFile, resolveLedgerPath, summarizeLedger } from './ledger.js';
|
|
21
21
|
import { renderSpendDonuts } from './donuts.js';
|
|
22
|
-
import { KeyStore, createKey, revokeKey, rotateKey, deleteKey, parseExpiry, publicKey, resolveKeysPath, KEY_NAME_RE, parseKeySocketSpec, setKeyBudget, parseUsdBudget, parseTokenBudget, formatBudget } from './keys.js';
|
|
22
|
+
import { KeyStore, createKey, revokeKey, rotateKey, deleteKey, parseExpiry, publicKey, resolveKeysPath, KEY_NAME_RE, parseKeySocketSpec, setKeyBudget, setKeyEffort, isKeyEffort, KEY_EFFORT_VALUES, parseUsdBudget, parseTokenBudget, formatBudget } from './keys.js';
|
|
23
23
|
/**
|
|
24
24
|
* `--budget=$5/day` / `--budget-tokens=2M/day` → a KeyBudget, or undefined when
|
|
25
25
|
* neither flag is present. A flag that does not parse exits 1 with the accepted
|
|
@@ -875,9 +875,9 @@ async function keys() {
|
|
|
875
875
|
return;
|
|
876
876
|
}
|
|
877
877
|
const w = Math.max(4, ...list.map((k) => k.name.length));
|
|
878
|
-
console.log(` ${'NAME'.padEnd(w)} ${'STATUS'.padEnd(7)} ${'SEAT'.padEnd(12)} ${'LAST USED'.padEnd(10)} ${'EXPIRES'.padEnd(10)} ${'BUDGET'.padEnd(20)} MODELS`);
|
|
878
|
+
console.log(` ${'NAME'.padEnd(w)} ${'STATUS'.padEnd(7)} ${'SEAT'.padEnd(12)} ${'EFFORT'.padEnd(6)} ${'LAST USED'.padEnd(10)} ${'EXPIRES'.padEnd(10)} ${'BUDGET'.padEnd(20)} MODELS`);
|
|
879
879
|
for (const k of list) {
|
|
880
|
-
console.log(` ${k.name.padEnd(w)} ${k.status.padEnd(7)} ${(k.seat ?? '-').padEnd(12)} ${fmtAgo(k.last_used).padEnd(10)} ${fmtDay(k.expires).padEnd(10)} ${formatBudget(k.budget ? { usdPerDay: k.budget.usd_per_day ?? undefined, tokensPerDay: k.budget.tokens_per_day ?? undefined } : null).padEnd(20)} ${k.models.length ? k.models.join(', ') : 'any'}`);
|
|
880
|
+
console.log(` ${k.name.padEnd(w)} ${k.status.padEnd(7)} ${(k.seat ?? '-').padEnd(12)} ${(k.effort ?? '-').padEnd(6)} ${fmtAgo(k.last_used).padEnd(10)} ${fmtDay(k.expires).padEnd(10)} ${formatBudget(k.budget ? { usdPerDay: k.budget.usd_per_day ?? undefined, tokensPerDay: k.budget.tokens_per_day ?? undefined } : null).padEnd(20)} ${k.models.length ? k.models.join(', ') : 'any'}`);
|
|
881
881
|
}
|
|
882
882
|
console.log('');
|
|
883
883
|
console.log(` ${list.length} key${list.length === 1 ? '' : 's'} in ${path}. Spend per key: dario usage --by-key`);
|
|
@@ -888,7 +888,7 @@ async function keys() {
|
|
|
888
888
|
const name = args[2];
|
|
889
889
|
if (!name || name.startsWith('--')) {
|
|
890
890
|
console.error('');
|
|
891
|
-
console.error(' Usage: dario keys create <name> [--seat=<alias>] [--models=a,b,prefix*] [--expires=30d|12h|2w|<ISO date>] [--budget=$5/day] [--budget-tokens=2M/day]');
|
|
891
|
+
console.error(' Usage: dario keys create <name> [--seat=<alias>] [--models=a,b,prefix*] [--expires=30d|12h|2w|<ISO date>] [--budget=$5/day] [--budget-tokens=2M/day] [--effort=low|medium|high|xhigh|max]');
|
|
892
892
|
console.error('');
|
|
893
893
|
process.exit(1);
|
|
894
894
|
}
|
|
@@ -907,8 +907,14 @@ async function keys() {
|
|
|
907
907
|
expiresAt = parsed;
|
|
908
908
|
}
|
|
909
909
|
const budget = readBudgetFlags(args);
|
|
910
|
+
const effortArg = args.find((a) => a.startsWith('--effort='));
|
|
911
|
+
const effort = effortArg ? effortArg.slice('--effort='.length).trim().toLowerCase() : undefined;
|
|
912
|
+
if (effort !== undefined && !isKeyEffort(effort)) {
|
|
913
|
+
console.error(`[dario] --effort: "${effort}" is not one of ${KEY_EFFORT_VALUES.join(', ')}.`);
|
|
914
|
+
process.exit(1);
|
|
915
|
+
}
|
|
910
916
|
try {
|
|
911
|
-
const made = store.mutate((file) => createKey(file, name, { seat, models, expiresAt, budget, now }));
|
|
917
|
+
const made = store.mutate((file) => createKey(file, name, { seat, models, expiresAt, budget, effort, now }));
|
|
912
918
|
printSecret('created', publicKey(made.record, now), made.secret);
|
|
913
919
|
}
|
|
914
920
|
catch (err) {
|
|
@@ -917,6 +923,34 @@ async function keys() {
|
|
|
917
923
|
}
|
|
918
924
|
return;
|
|
919
925
|
}
|
|
926
|
+
// dario keys effort <name> low|medium|high|xhigh|max | --clear
|
|
927
|
+
if (sub === 'effort') {
|
|
928
|
+
const name = args[2];
|
|
929
|
+
const raw = args.includes('--clear') ? null : (args[3] ?? '').trim().toLowerCase();
|
|
930
|
+
if (!name || !KEY_NAME_RE.test(name) || (raw !== null && !isKeyEffort(raw))) {
|
|
931
|
+
console.error('');
|
|
932
|
+
console.error(` Usage: dario keys effort <name> ${KEY_EFFORT_VALUES.join('|')} | --clear`);
|
|
933
|
+
console.error('');
|
|
934
|
+
console.error(' The effort this key\'s requests run at when they name none themselves (no model-name');
|
|
935
|
+
console.error(' suffix, no effort field in the body). --effort / DARIO_EFFORT still wins over it.');
|
|
936
|
+
console.error('');
|
|
937
|
+
process.exit(1);
|
|
938
|
+
}
|
|
939
|
+
const level = raw !== null && isKeyEffort(raw) ? raw : null;
|
|
940
|
+
try {
|
|
941
|
+
const updated = store.mutate((file) => setKeyEffort(file, name, level));
|
|
942
|
+
if (!updated) {
|
|
943
|
+
console.error(`[dario] No key named "${name}".`);
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
console.log(level ? `[dario] Key "${name}" default effort: ${level}` : `[dario] Key "${name}" default effort cleared.`);
|
|
947
|
+
}
|
|
948
|
+
catch (err) {
|
|
949
|
+
console.error(`[dario] ${err instanceof Error ? err.message : String(err)}`);
|
|
950
|
+
process.exit(1);
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
920
954
|
// dario keys budget <name> --budget=$5/day --budget-tokens=2M/day | --clear
|
|
921
955
|
if (sub === 'budget') {
|
|
922
956
|
const name = args[2];
|
|
@@ -985,7 +1019,7 @@ async function keys() {
|
|
|
985
1019
|
return;
|
|
986
1020
|
}
|
|
987
1021
|
console.error(`[dario] Unknown keys subcommand: ${sub}`);
|
|
988
|
-
console.error('Usage: dario keys [list|create <name> [--seat=..] [--models=..] [--expires=..] [--budget=..] [--budget-tokens=..]|budget <name> ..|revoke <name>|rotate <name>|remove <name>] [--json] [--keys-path=<file>]');
|
|
1022
|
+
console.error('Usage: dario keys [list|create <name> [--seat=..] [--models=..] [--expires=..] [--budget=..] [--budget-tokens=..] [--effort=..]|budget <name> ..|effort <name> <level>|--clear|revoke <name>|rotate <name>|remove <name>] [--json] [--keys-path=<file>]');
|
|
989
1023
|
process.exit(1);
|
|
990
1024
|
}
|
|
991
1025
|
/**
|
|
@@ -2025,8 +2059,8 @@ async function help() {
|
|
|
2025
2059
|
miss. Needs \`dario proxy\` running; costs
|
|
2026
2060
|
at most a few tiny subscription requests.
|
|
2027
2061
|
dario doctor --json Emit the check report as structured JSON
|
|
2028
|
-
for machine consumption (
|
|
2029
|
-
|
|
2062
|
+
for machine consumption (status bots,
|
|
2063
|
+
CI scripts, etc.) instead of the
|
|
2030
2064
|
human-readable table.
|
|
2031
2065
|
dario doctor --auth-check
|
|
2032
2066
|
One-shot listener on an ephemeral loopback
|
|
@@ -2759,8 +2793,8 @@ async function doctor() {
|
|
|
2759
2793
|
}
|
|
2760
2794
|
const checks = await runChecks({ probe, usage, obedience });
|
|
2761
2795
|
if (asJson) {
|
|
2762
|
-
// JSON mode is meant for machine consumption (
|
|
2763
|
-
//
|
|
2796
|
+
// JSON mode is meant for machine consumption (status bots, health
|
|
2797
|
+
// checks, CI scripts): no decorative header, no
|
|
2764
2798
|
// trailing prose, stable shape. Exit code is also surfaced in the
|
|
2765
2799
|
// JSON envelope for callers that can't read process exit codes.
|
|
2766
2800
|
process.stdout.write(formatChecksJson(checks) + '\n');
|
package/dist/doctor-core.d.ts
CHANGED
|
@@ -108,7 +108,7 @@ export declare function formatChecks(checks: Check[]): string;
|
|
|
108
108
|
export declare function exitCodeFor(checks: Check[]): number;
|
|
109
109
|
/**
|
|
110
110
|
* Serialize a check report as structured JSON. Lets other tools
|
|
111
|
-
* (
|
|
111
|
+
* (chat-bot /status commands, health probes, CI scripts) consume
|
|
112
112
|
* dario's health programmatically instead of scraping the formatted
|
|
113
113
|
* text. Emitted by `dario doctor --json`.
|
|
114
114
|
*/
|
package/dist/doctor-core.js
CHANGED
|
@@ -227,7 +227,7 @@ export function exitCodeFor(checks) {
|
|
|
227
227
|
}
|
|
228
228
|
/**
|
|
229
229
|
* Serialize a check report as structured JSON. Lets other tools
|
|
230
|
-
* (
|
|
230
|
+
* (chat-bot /status commands, health probes, CI scripts) consume
|
|
231
231
|
* dario's health programmatically instead of scraping the formatted
|
|
232
232
|
* text. Emitted by `dario doctor --json`.
|
|
233
233
|
*/
|
|
@@ -1019,7 +1019,7 @@ export async function runChecks(opts = {}) {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
}
|
|
1021
1021
|
// ---- Client-system obedience probe (opt-in, --obedience).
|
|
1022
|
-
// dario#509 / the 2026-06-12
|
|
1022
|
+
// dario#509 / the 2026-06-12 downstream planner outage: Anthropic's serving
|
|
1023
1023
|
// side changed how claude-sonnet-4-6 weighed client system text merged
|
|
1024
1024
|
// after the CC persona in block 3 — wire bytes identical, every existing
|
|
1025
1025
|
// check green — and the only symptom was a downstream consumer failing to
|
package/dist/effort.d.ts
CHANGED
|
@@ -32,6 +32,24 @@ export declare const VALID_EFFORT_VALUES: ReadonlyArray<EffortValue>;
|
|
|
32
32
|
* and the request's own thinking budget decides, as before.
|
|
33
33
|
*/
|
|
34
34
|
export declare function effortForCodex(effort: EffortValue | undefined): 'low' | 'medium' | 'high' | 'xhigh' | 'max' | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Did the request name an effort of its own? Three places a client can say it:
|
|
37
|
+
* `output_config.effort` (Messages shape), `reasoning_effort` (chat completions),
|
|
38
|
+
* `reasoning.effort` (Responses). A named key's default effort fills in only when
|
|
39
|
+
* this is false, so a client that chose is never overruled by a default. The
|
|
40
|
+
* model-name suffix is checked separately by the caller (it is parsed off the
|
|
41
|
+
* model before this runs).
|
|
42
|
+
*/
|
|
43
|
+
export declare function requestNamesEffort(body: Record<string, unknown> | null | undefined): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* A Responses body for the codex passthrough with `reasoning.effort` filled in.
|
|
46
|
+
* Returned as is when there is no effort to add or when the client already
|
|
47
|
+
* named one (a non-empty `reasoning.effort`); otherwise a copy carries the
|
|
48
|
+
* level and keeps every other `reasoning` field, since Codex CLI sends
|
|
49
|
+
* `summary` there. The passthrough forwards the client's body as written, so
|
|
50
|
+
* this is the only place a suffix or a key default can reach that route.
|
|
51
|
+
*/
|
|
52
|
+
export declare function withResponsesEffort(body: Record<string, unknown>, effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | undefined): Record<string, unknown>;
|
|
35
53
|
export declare function parseEffortSuffix(model: string): {
|
|
36
54
|
model: string;
|
|
37
55
|
effort?: EffortValue;
|
package/dist/effort.js
CHANGED
|
@@ -52,6 +52,44 @@ export function effortForCodex(effort) {
|
|
|
52
52
|
default: return undefined;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Did the request name an effort of its own? Three places a client can say it:
|
|
57
|
+
* `output_config.effort` (Messages shape), `reasoning_effort` (chat completions),
|
|
58
|
+
* `reasoning.effort` (Responses). A named key's default effort fills in only when
|
|
59
|
+
* this is false, so a client that chose is never overruled by a default. The
|
|
60
|
+
* model-name suffix is checked separately by the caller (it is parsed off the
|
|
61
|
+
* model before this runs).
|
|
62
|
+
*/
|
|
63
|
+
export function requestNamesEffort(body) {
|
|
64
|
+
if (!body || typeof body !== 'object')
|
|
65
|
+
return false;
|
|
66
|
+
const oc = body.output_config;
|
|
67
|
+
if (oc && typeof oc === 'object' && typeof oc.effort === 'string')
|
|
68
|
+
return true;
|
|
69
|
+
if (typeof body.reasoning_effort === 'string' && body.reasoning_effort.length > 0)
|
|
70
|
+
return true;
|
|
71
|
+
const reasoning = body.reasoning;
|
|
72
|
+
if (reasoning && typeof reasoning === 'object' && typeof reasoning.effort === 'string')
|
|
73
|
+
return true;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A Responses body for the codex passthrough with `reasoning.effort` filled in.
|
|
78
|
+
* Returned as is when there is no effort to add or when the client already
|
|
79
|
+
* named one (a non-empty `reasoning.effort`); otherwise a copy carries the
|
|
80
|
+
* level and keeps every other `reasoning` field, since Codex CLI sends
|
|
81
|
+
* `summary` there. The passthrough forwards the client's body as written, so
|
|
82
|
+
* this is the only place a suffix or a key default can reach that route.
|
|
83
|
+
*/
|
|
84
|
+
export function withResponsesEffort(body, effort) {
|
|
85
|
+
if (!effort)
|
|
86
|
+
return body;
|
|
87
|
+
const r = body.reasoning;
|
|
88
|
+
const existing = r !== null && typeof r === 'object' && !Array.isArray(r) ? r : {};
|
|
89
|
+
if (typeof existing.effort === 'string' && existing.effort.length > 0)
|
|
90
|
+
return body;
|
|
91
|
+
return { ...body, reasoning: { ...existing, effort } };
|
|
92
|
+
}
|
|
55
93
|
export function parseEffortSuffix(model) {
|
|
56
94
|
for (const e of SUFFIX_EFFORTS) {
|
|
57
95
|
for (const sep of [':', '-']) {
|
package/dist/keys.d.ts
CHANGED
|
@@ -3,6 +3,14 @@ export declare const KEY_PREFIX = "dk_";
|
|
|
3
3
|
/** Same charset as a pool alias — a key name is printed next to one. */
|
|
4
4
|
export declare const KEY_NAME_RE: RegExp;
|
|
5
5
|
export declare const KEYS_FLUSH_DELAY_MS = 3000;
|
|
6
|
+
/**
|
|
7
|
+
* The effort levels a named key may carry as its default: the wire-valid set,
|
|
8
|
+
* and nothing that means "ask the client" (`client`) or names a client mode
|
|
9
|
+
* (`ultracode`). Applied per request when the request named no effort itself.
|
|
10
|
+
*/
|
|
11
|
+
export type KeyEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
12
|
+
export declare const KEY_EFFORT_VALUES: ReadonlyArray<KeyEffort>;
|
|
13
|
+
export declare function isKeyEffort(v: unknown): v is KeyEffort;
|
|
6
14
|
export interface KeyRecord {
|
|
7
15
|
/** Stable short id (8 hex), for logs and rotation; never the secret. */
|
|
8
16
|
id: string;
|
|
@@ -23,6 +31,12 @@ export interface KeyRecord {
|
|
|
23
31
|
models?: string[];
|
|
24
32
|
/** Daily caps, UTC day, enforced from the ledger (dario#1318 follow-up). Absent = unlimited. */
|
|
25
33
|
budget?: KeyBudget;
|
|
34
|
+
/**
|
|
35
|
+
* Default effort for this key's requests when the request names none itself
|
|
36
|
+
* (no model-name suffix, no effort field in the body). Absent = the proxy's
|
|
37
|
+
* usual resolution. The operator's --effort pin still wins over it.
|
|
38
|
+
*/
|
|
39
|
+
effort?: KeyEffort;
|
|
26
40
|
}
|
|
27
41
|
/**
|
|
28
42
|
* A key's daily budget. Both caps are per UTC day and both are read from the
|
|
@@ -126,6 +140,8 @@ export declare function budgetVerdict(budget: KeyBudget, usage: KeyBudgetUsage,
|
|
|
126
140
|
export declare function budgetHeaders(v: KeyBudgetVerdict, keyName: string): Record<string, string>;
|
|
127
141
|
/** Replace (or clear, with null) a key's budget. Null result: no such key. */
|
|
128
142
|
export declare function setKeyBudget(file: KeysFile, name: string, budget: KeyBudget | null): KeyRecord | null;
|
|
143
|
+
/** Set or clear a key's default effort. Returns null when there is no such key; throws on a level outside KEY_EFFORT_VALUES. */
|
|
144
|
+
export declare function setKeyEffort(file: KeysFile, name: string, effort: KeyEffort | string | null): KeyRecord | null;
|
|
129
145
|
export interface KeysFile {
|
|
130
146
|
version: number;
|
|
131
147
|
keys: KeyRecord[];
|
|
@@ -161,6 +177,8 @@ export interface CreateKeyOptions {
|
|
|
161
177
|
budget?: KeyBudget;
|
|
162
178
|
/** Absolute expiry, epoch ms. */
|
|
163
179
|
expiresAt?: number;
|
|
180
|
+
/** Default effort; one of KEY_EFFORT_VALUES, validated. */
|
|
181
|
+
effort?: string;
|
|
164
182
|
now?: number;
|
|
165
183
|
}
|
|
166
184
|
/** Mint a key. Returns the record (stored) and the secret (shown once). Mutates `file`. */
|
|
@@ -203,6 +221,8 @@ export interface KeyPublic {
|
|
|
203
221
|
usd_per_day: number | null;
|
|
204
222
|
tokens_per_day: number | null;
|
|
205
223
|
} | null;
|
|
224
|
+
/** Default effort, or null when the key has none. */
|
|
225
|
+
effort: KeyEffort | null;
|
|
206
226
|
}
|
|
207
227
|
export declare function publicKey(k: KeyRecord, now?: number): KeyPublic;
|
|
208
228
|
/** `--expires=30d` / `12h` / `2026-12-31` → epoch ms, or null when unparseable. */
|