@askalf/dario 3.33.0 → 3.34.1

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.
@@ -24,6 +24,35 @@ export declare const CC_TOOL_DEFINITIONS: {
24
24
  export declare const CC_SYSTEM_PROMPT: string;
25
25
  /** CC's agent identity string. */
26
26
  export declare const CC_AGENT_IDENTITY: string;
27
+ /**
28
+ * Resolve the system prompt for outbound CC-shaped requests.
29
+ *
30
+ * Empirically validated against Anthropic's billing classifier in
31
+ * docs/research/system-prompt.md (and reproducible from
32
+ * scripts/test-system-prompt-mods.mjs + scripts/test-constraint-removal.mjs):
33
+ * system prompt content, length, and block count are not classifier
34
+ * inputs — every variant tested routed to `five_hour` (subscription).
35
+ *
36
+ * Modes:
37
+ * - undefined / 'verbatim' — CC's prompt unchanged (default; existing
38
+ * setups don't regress).
39
+ * - 'partial' — strip purely behavioral constraints (Tone-and-style +
40
+ * Text-output sections, scope-discipline / verbosity / commenting
41
+ * bullets in Doing-tasks). Recovers most of the 1.2-2.8x output
42
+ * capability seen in the constraint-removal test while leaving
43
+ * every IMPORTANT: refusal reminder and every tool description
44
+ * intact.
45
+ * - 'aggressive' — partial + remove prompt-level RLHF reminders (the
46
+ * IMPORTANT: lines that re-state refusal categories) and the
47
+ * Executing-actions-with-care overcaution language. Adds <3%
48
+ * practical difference vs partial because alignment is RLHF-trained,
49
+ * not prompt-trained — RLHF refusals on harmful content survive
50
+ * prompt removal.
51
+ * - any other string — used as the literal system prompt text. The
52
+ * CLI resolves file paths to file contents up-front so this layer
53
+ * stays filesystem-pure.
54
+ */
55
+ export declare function resolveSystemPrompt(arg: string | undefined): string;
27
56
  /**
28
57
  * Apply the live template's captured header_order to an outbound header
29
58
  * record. Returns a HeadersInit in one of two forms:
@@ -246,6 +275,7 @@ export declare function buildCCRequest(clientBody: Record<string, unknown>, bill
246
275
  noAutoDetect?: boolean;
247
276
  effort?: EffortValue;
248
277
  maxTokens?: number | 'client';
278
+ systemPrompt?: string;
249
279
  }): {
250
280
  body: Record<string, unknown>;
251
281
  toolMap: Map<string, ToolMapping>;
@@ -43,6 +43,72 @@ export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools, proces
43
43
  export const CC_SYSTEM_PROMPT = TEMPLATE.system_prompt;
44
44
  /** CC's agent identity string. */
45
45
  export const CC_AGENT_IDENTITY = TEMPLATE.agent_identity;
46
+ /**
47
+ * Resolve the system prompt for outbound CC-shaped requests.
48
+ *
49
+ * Empirically validated against Anthropic's billing classifier in
50
+ * docs/research/system-prompt.md (and reproducible from
51
+ * scripts/test-system-prompt-mods.mjs + scripts/test-constraint-removal.mjs):
52
+ * system prompt content, length, and block count are not classifier
53
+ * inputs — every variant tested routed to `five_hour` (subscription).
54
+ *
55
+ * Modes:
56
+ * - undefined / 'verbatim' — CC's prompt unchanged (default; existing
57
+ * setups don't regress).
58
+ * - 'partial' — strip purely behavioral constraints (Tone-and-style +
59
+ * Text-output sections, scope-discipline / verbosity / commenting
60
+ * bullets in Doing-tasks). Recovers most of the 1.2-2.8x output
61
+ * capability seen in the constraint-removal test while leaving
62
+ * every IMPORTANT: refusal reminder and every tool description
63
+ * intact.
64
+ * - 'aggressive' — partial + remove prompt-level RLHF reminders (the
65
+ * IMPORTANT: lines that re-state refusal categories) and the
66
+ * Executing-actions-with-care overcaution language. Adds <3%
67
+ * practical difference vs partial because alignment is RLHF-trained,
68
+ * not prompt-trained — RLHF refusals on harmful content survive
69
+ * prompt removal.
70
+ * - any other string — used as the literal system prompt text. The
71
+ * CLI resolves file paths to file contents up-front so this layer
72
+ * stays filesystem-pure.
73
+ */
74
+ export function resolveSystemPrompt(arg) {
75
+ if (!arg || arg === 'verbatim')
76
+ return CC_SYSTEM_PROMPT;
77
+ if (arg === 'partial')
78
+ return stripBehavioralConstraints(CC_SYSTEM_PROMPT, 'partial');
79
+ if (arg === 'aggressive')
80
+ return stripBehavioralConstraints(CC_SYSTEM_PROMPT, 'aggressive');
81
+ return arg;
82
+ }
83
+ /**
84
+ * Port of scripts/test-constraint-removal.mjs:stripConstraints. Pure over
85
+ * its input; returns the input unchanged if section headers don't match
86
+ * (so a future CC bump that renames sections degrades to verbatim rather
87
+ * than producing an unpredictable strip).
88
+ */
89
+ function stripBehavioralConstraints(input, level) {
90
+ let s = input;
91
+ s = s.replace(/# Tone and style[\s\S]*?(?=\n# |\n$|$)/m, '');
92
+ s = s.replace(/# Text output[^\n]*\n[\s\S]*?(?=\n# |\n$|$)/m, '');
93
+ const doingTasksConstraints = [
94
+ /^ - Don't add features, refactor, or introduce abstractions[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n/m,
95
+ /^ - Don't add error handling, fallbacks, or validation[^\n]*\n[^\n]*\n/m,
96
+ /^ - Default to writing no comments\.[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n/m,
97
+ /^ - Don't explain WHAT the code does[^\n]*\n[^\n]*\n/m,
98
+ /^ - For exploratory questions[^\n]*\n[^\n]*\n/m,
99
+ /^ - Avoid backwards-compatibility hacks[^\n]*\n[^\n]*\n/m,
100
+ ];
101
+ for (const re of doingTasksConstraints) {
102
+ s = s.replace(re, '');
103
+ }
104
+ 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');
105
+ if (level === 'aggressive') {
106
+ s = s.replace(/^IMPORTANT: Assist with authorized security testing[^\n]*\n/m, '');
107
+ s = s.replace(/^IMPORTANT: You must NEVER generate or guess URLs[^\n]*\n/m, '');
108
+ s = s.replace(/# Executing actions with care[\s\S]*?(?=\n# |\n$|$)/m, '');
109
+ }
110
+ return s;
111
+ }
46
112
  /**
47
113
  * Apply the live template's captured header_order to an outbound header
48
114
  * record. Returns a HeadersInit in one of two forms:
@@ -1089,9 +1155,14 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1089
1155
  // [0] billing tag (no cache)
1090
1156
  // [1] agent identity (1h cache)
1091
1157
  // [2] CC's full 25KB system prompt + client's custom prompt appended (1h cache)
1158
+ // resolveSystemPrompt is the seam for --system-prompt=verbatim|partial|
1159
+ // aggressive|<file>. Default (undefined) returns CC_SYSTEM_PROMPT
1160
+ // unchanged. See docs/research/system-prompt.md for the empirical
1161
+ // validation that this slot is unfingerprinted by the billing classifier.
1162
+ const baseSystemPrompt = resolveSystemPrompt(opts.systemPrompt);
1092
1163
  const fullSystemPrompt = systemText
1093
- ? `${CC_SYSTEM_PROMPT}\n\n${systemText}`
1094
- : CC_SYSTEM_PROMPT;
1164
+ ? `${baseSystemPrompt}\n\n${systemText}`
1165
+ : baseSystemPrompt;
1095
1166
  const ccRequest = {
1096
1167
  model,
1097
1168
  messages,
package/dist/cli.d.ts CHANGED
@@ -10,6 +10,24 @@
10
10
  * dario logout — Remove saved credentials
11
11
  */
12
12
  import { type EffortValue } from './cc-template.js';
13
+ /**
14
+ * Parse `--system-prompt=<verbatim|partial|aggressive|filepath>` (or the
15
+ * `DARIO_SYSTEM_PROMPT` env-var fallback) into the value passed through
16
+ * to startProxy. The CLI flag wins over the env var when both are set —
17
+ * convention every other dario flag uses.
18
+ *
19
+ * Returns:
20
+ * - undefined for missing / 'verbatim' (proxy default — CC unchanged)
21
+ * - 'partial' / 'aggressive' as keyword strings (stripping happens
22
+ * in cc-template.ts:resolveSystemPrompt at request build time)
23
+ * - the literal text contents of the file at <filepath> for the
24
+ * custom-prompt escape hatch. Fails fast (process.exit(1)) on a
25
+ * value that is neither a recognized keyword nor a readable file.
26
+ *
27
+ * Exported for the test suite — same shape as resolveEffortFlag /
28
+ * resolveMaxTokensFlag.
29
+ */
30
+ export declare function resolveSystemPromptFlag(args: string[], envVar: string | undefined): string | undefined;
13
31
  /**
14
32
  * Parse `--passthrough-betas=<csv>` (or the env-var fallback) into a
15
33
  * deduped, trimmed list. The CLI flag wins over the env var when both
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@
17
17
  // just want `parsePositiveIntEnv`) doesn't trigger a Bun relaunch or any
18
18
  // other startup side effect.
19
19
  import { unlink } from 'node:fs/promises';
20
- import { realpathSync } from 'node:fs';
20
+ import { realpathSync, readFileSync } from 'node:fs';
21
21
  import { join } from 'node:path';
22
22
  import { homedir } from 'node:os';
23
23
  import { pathToFileURL } from 'node:url';
@@ -284,6 +284,20 @@ async function proxy() {
284
284
  // Falls back to DARIO_LOG_FILE; off by default. Path is opened with
285
285
  // append mode so multiple proxy restarts share a rolling history.
286
286
  const logFile = parseLogFileFlag(args) ?? process.env['DARIO_LOG_FILE'] ?? undefined;
287
+ // --system-prompt=<verbatim|partial|aggressive|filepath> — system-prompt
288
+ // mode for outbound CC-shaped requests (v3.34.0). The classifier is
289
+ // empirically not reading this slot (docs/research/system-prompt.md),
290
+ // so users can strip CC's behavioral constraints — Tone-and-style,
291
+ // Text-output, scope/verbosity bullets — and recover 1.2-2.8x output
292
+ // capability without flipping subscription billing. Default 'verbatim'
293
+ // preserves existing setups.
294
+ //
295
+ // The CLI resolves the value here so the runtime path stays
296
+ // filesystem-pure: 'verbatim'/'partial'/'aggressive' pass through as
297
+ // keywords; anything else is treated as a file path and read at
298
+ // startup. A bad path fails fast rather than silently degrading to
299
+ // verbatim — same fail-loud philosophy as --strict-tls / --strict-template.
300
+ const systemPrompt = resolveSystemPromptFlag(args, process.env['DARIO_SYSTEM_PROMPT']);
287
301
  // --passthrough-betas=name1,name2 — operator-pinned beta allow-list.
288
302
  // Names listed here are always forwarded to Anthropic regardless of
289
303
  // CC's captured set or the client's own beta header; bypasses the
@@ -309,7 +323,47 @@ async function proxy() {
309
323
  console.error(`[dario] Override (not recommended): pass --unsafe-no-auth if you have out-of-band network controls and accept the risk.`);
310
324
  process.exit(1);
311
325
  }
312
- await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools, mergeTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs, drainOnClose, sessionIdleRotateMs, sessionRotateJitterMs, sessionMaxAgeMs, sessionPerClient, preserveOrchestrationTags, noLiveCapture, strictTemplate, maxConcurrent, maxQueued, queueTimeoutMs, effort, maxTokens, logFile, passthroughBetas });
326
+ await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools, mergeTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs, drainOnClose, sessionIdleRotateMs, sessionRotateJitterMs, sessionMaxAgeMs, sessionPerClient, preserveOrchestrationTags, noLiveCapture, strictTemplate, maxConcurrent, maxQueued, queueTimeoutMs, effort, maxTokens, logFile, passthroughBetas, systemPrompt });
327
+ }
328
+ /**
329
+ * Parse `--system-prompt=<verbatim|partial|aggressive|filepath>` (or the
330
+ * `DARIO_SYSTEM_PROMPT` env-var fallback) into the value passed through
331
+ * to startProxy. The CLI flag wins over the env var when both are set —
332
+ * convention every other dario flag uses.
333
+ *
334
+ * Returns:
335
+ * - undefined for missing / 'verbatim' (proxy default — CC unchanged)
336
+ * - 'partial' / 'aggressive' as keyword strings (stripping happens
337
+ * in cc-template.ts:resolveSystemPrompt at request build time)
338
+ * - the literal text contents of the file at <filepath> for the
339
+ * custom-prompt escape hatch. Fails fast (process.exit(1)) on a
340
+ * value that is neither a recognized keyword nor a readable file.
341
+ *
342
+ * Exported for the test suite — same shape as resolveEffortFlag /
343
+ * resolveMaxTokensFlag.
344
+ */
345
+ export function resolveSystemPromptFlag(args, envVar) {
346
+ const eqArg = args.find((a) => a.startsWith('--system-prompt='));
347
+ const raw = eqArg !== undefined ? eqArg.split('=').slice(1).join('=') : envVar;
348
+ if (raw === undefined || raw === '' || raw === 'verbatim')
349
+ return undefined;
350
+ if (raw === 'partial' || raw === 'aggressive')
351
+ return raw;
352
+ // File-path mode. Read the file at startup so the runtime path stays
353
+ // pure. Fail loud on missing/unreadable paths.
354
+ try {
355
+ const text = readFileSync(raw, 'utf-8');
356
+ if (text.length === 0) {
357
+ console.error(`[dario] --system-prompt=${raw}: file is empty. Refusing to start.`);
358
+ console.error(`[dario] Use --system-prompt=verbatim to disable, or write a non-empty file.`);
359
+ process.exit(1);
360
+ }
361
+ return text;
362
+ }
363
+ catch (err) {
364
+ console.error(`[dario] --system-prompt=${raw}: not 'verbatim'/'partial'/'aggressive' and not a readable file (${err.message}). Refusing to start.`);
365
+ process.exit(1);
366
+ }
313
367
  }
314
368
  /**
315
369
  * Parse `--passthrough-betas=<csv>` (or the env-var fallback) into a
@@ -914,6 +968,28 @@ async function help() {
914
968
  on your account but isn't in the captured
915
969
  template. Env: DARIO_PASSTHROUGH_BETAS.
916
970
 
971
+ --system-prompt=<MODE> System-prompt mode for outbound CC-shaped
972
+ requests (v3.34.0). One of:
973
+ verbatim — CC unchanged (default)
974
+ partial — strip behavioral constraints
975
+ (Tone-and-style, Text-output,
976
+ verbosity / comment / scope
977
+ bullets in Doing-tasks).
978
+ Recovers ~1.2-2.8x output on
979
+ open-ended work.
980
+ aggressive — partial + remove prompt-level
981
+ RLHF restatements + Executing-
982
+ actions-with-care section.
983
+ Adds <3% over partial; RLHF
984
+ refusals on harmful content
985
+ unaffected (alignment is in
986
+ the weights, not the prompt).
987
+ <filepath> — replace the slot entirely
988
+ with the file's contents.
989
+ Empirically validated as unfingerprinted by
990
+ the billing classifier — see docs/research/
991
+ system-prompt.md. Env: DARIO_SYSTEM_PROMPT.
992
+
917
993
  Quick start:
918
994
  dario login # auto-detects Claude Code credentials
919
995
  dario proxy --model=opus # or: dario proxy --passthrough
package/dist/doctor.js CHANGED
@@ -16,7 +16,7 @@ import { fileURLToPath } from 'node:url';
16
16
  import { homedir, platform, arch, release } from 'node:os';
17
17
  import { execFileSync } from 'node:child_process';
18
18
  import { createServer } from 'node:http';
19
- import { CC_TEMPLATE, } from './cc-template.js';
19
+ import { CC_TEMPLATE, resolveSystemPrompt, } from './cc-template.js';
20
20
  import { describeTemplate, detectDrift, checkCCCompat, findInstalledCC, SUPPORTED_CC_RANGE, CURRENT_SCHEMA_VERSION, compareVersions, } from './live-fingerprint.js';
21
21
  import { detectCCOAuthConfig } from './cc-oauth-detect.js';
22
22
  import { runAuthorizeProbe } from './cc-authorize-probe.js';
@@ -242,6 +242,32 @@ export async function runChecks(opts = {}) {
242
242
  }
243
243
  }
244
244
  catch { /* don't let overhead reporting break the doctor */ }
245
+ // ---- System-prompt mode (v3.34.0)
246
+ // Surfaces the configured `--system-prompt` mode + the resulting char
247
+ // count delta vs CC verbatim. Read-only — does not run a request.
248
+ // Env-only path here so doctor can be invoked without a live proxy.
249
+ try {
250
+ const rawMode = process.env['DARIO_SYSTEM_PROMPT'];
251
+ if (rawMode && rawMode !== 'verbatim') {
252
+ const cc = CC_TEMPLATE.system_prompt ?? '';
253
+ let resolved;
254
+ if (rawMode === 'partial' || rawMode === 'aggressive') {
255
+ resolved = resolveSystemPrompt(rawMode);
256
+ }
257
+ else {
258
+ // file-path mode — doctor doesn't read files (might leak path),
259
+ // just report that custom mode is active.
260
+ resolved = '';
261
+ }
262
+ const isCustom = rawMode !== 'partial' && rawMode !== 'aggressive';
263
+ const detail = isCustom
264
+ ? `DARIO_SYSTEM_PROMPT=${rawMode} (custom file). Runtime path replaces system[2].text with file contents.`
265
+ : `DARIO_SYSTEM_PROMPT=${rawMode}. Strips ${(cc.length - resolved.length).toLocaleString()} chars from CC's ${cc.length.toLocaleString()}-char prompt. ` +
266
+ `See docs/research/system-prompt.md for the empirical validation that this slot is unfingerprinted by the billing classifier.`;
267
+ checks.push({ status: 'info', label: 'System-prompt mode', detail });
268
+ }
269
+ }
270
+ catch { /* never let prompt-mode reporting break the doctor */ }
245
271
  // ---- Template drift
246
272
  try {
247
273
  const drift = detectDrift(CC_TEMPLATE);
@@ -282,7 +282,7 @@ export declare function _resetInstalledVersionProbeForTest(): void;
282
282
  */
283
283
  export declare const SUPPORTED_CC_RANGE: {
284
284
  readonly min: "1.0.0";
285
- readonly maxTested: "2.1.123";
285
+ readonly maxTested: "2.1.126";
286
286
  };
287
287
  /**
288
288
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
@@ -777,7 +777,7 @@ export function _resetInstalledVersionProbeForTest() {
777
777
  */
778
778
  export const SUPPORTED_CC_RANGE = {
779
779
  min: '1.0.0',
780
- maxTested: '2.1.123',
780
+ maxTested: '2.1.126',
781
781
  };
782
782
  /**
783
783
  * Compare two dotted-numeric version strings. Returns negative if `a<b`,
package/dist/proxy.d.ts CHANGED
@@ -124,6 +124,25 @@ interface ProxyOptions {
124
124
  * pinned flags has been rejected and is therefore being dropped.
125
125
  */
126
126
  passthroughBetas?: string[];
127
+ /**
128
+ * System-prompt mode for the Claude backend. Empirically validated as
129
+ * unfingerprinted by the billing classifier in docs/research/system-prompt.md.
130
+ *
131
+ * - undefined / 'verbatim' — CC's prompt unchanged (default).
132
+ * - 'partial' — strip behavioral constraints (Tone-and-style, Text-output,
133
+ * scope/verbosity/comment bullets in Doing-tasks). Recovers ~1.2-2.8x
134
+ * output capability on open-ended work; leaves IMPORTANT: refusal
135
+ * reminders and tool descriptions intact.
136
+ * - 'aggressive' — partial + remove prompt-level RLHF restatements and
137
+ * the Executing-actions-with-care section. Adds <3% over partial; RLHF
138
+ * refusals on harmful content are unaffected (alignment is in the weights).
139
+ * - any other string — used as the literal system prompt text. The CLI
140
+ * resolves --system-prompt=<file path> to the file's contents up front
141
+ * so the runtime path stays filesystem-pure.
142
+ *
143
+ * Sourced from `--system-prompt=<value>` or DARIO_SYSTEM_PROMPT.
144
+ */
145
+ systemPrompt?: string;
127
146
  }
128
147
  /**
129
148
  * One JSON-ND record per completed request. Field set kept narrow to
package/dist/proxy.js CHANGED
@@ -1105,6 +1105,7 @@ export async function startProxy(opts = {}) {
1105
1105
  noAutoDetect: opts.noAutoDetect ?? false,
1106
1106
  effort: opts.effort,
1107
1107
  maxTokens: opts.maxTokens,
1108
+ systemPrompt: opts.systemPrompt,
1108
1109
  });
1109
1110
  detectedClientForLog = detectedClient;
1110
1111
  preserveToolsEffective = Boolean(opts.preserveTools)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "3.33.0",
3
+ "version": "3.34.1",
4
4
  "description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
5
5
  "type": "module",
6
6
  "bin": {