@askalf/dario 4.8.72 → 4.8.74
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/cc-template.d.ts +7 -4
- package/dist/cc-template.js +23 -19
- package/dist/health-response.d.ts +28 -0
- package/dist/health-response.js +34 -0
- package/dist/proxy.js +6 -11
- package/package.json +1 -1
package/dist/cc-template.d.ts
CHANGED
|
@@ -39,10 +39,13 @@ export declare const CC_TOOL_DEFINITIONS: {
|
|
|
39
39
|
description: string;
|
|
40
40
|
input_schema: Record<string, unknown>;
|
|
41
41
|
}[];
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
42
|
+
/** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
|
|
43
|
+
* tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
|
|
44
|
+
* cross-client aliases ('read' → {path}/{filePath}) would otherwise mistranslate
|
|
45
|
+
* a CC tool (Read's file_path → path). Exact case is the discriminator: CC sends
|
|
46
|
+
* PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
|
|
47
|
+
* still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
|
|
48
|
+
export declare const CC_NATIVE_NAMES: Set<string>;
|
|
46
49
|
/** CC's static system prompt (~25KB). */
|
|
47
50
|
export declare const CC_SYSTEM_PROMPT: string;
|
|
48
51
|
/** CC's agent identity string. */
|
package/dist/cc-template.js
CHANGED
|
@@ -45,10 +45,13 @@ export function filterToolsForPlatform(tools, platform) {
|
|
|
45
45
|
}
|
|
46
46
|
/** CC's exact tool definitions for the current platform — filtered from the bundled union. */
|
|
47
47
|
export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools, process.platform);
|
|
48
|
-
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
|
|
48
|
+
/** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
|
|
49
|
+
* tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
|
|
50
|
+
* cross-client aliases ('read' → {path}/{filePath}) would otherwise mistranslate
|
|
51
|
+
* a CC tool (Read's file_path → path). Exact case is the discriminator: CC sends
|
|
52
|
+
* PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
|
|
53
|
+
* still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
|
|
54
|
+
export const CC_NATIVE_NAMES = new Set(CC_TOOL_DEFINITIONS.map((t) => String(t.name)));
|
|
52
55
|
/** CC's static system prompt (~25KB). */
|
|
53
56
|
export const CC_SYSTEM_PROMPT = TEMPLATE.system_prompt;
|
|
54
57
|
/** CC's agent identity string. */
|
|
@@ -1234,19 +1237,20 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
1234
1237
|
const claimedCC = new Set();
|
|
1235
1238
|
for (const tool of clientTools) {
|
|
1236
1239
|
const name = (tool.name || '').toLowerCase();
|
|
1237
|
-
// A CC client's OWN tools map to THEMSELVES (identity)
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
//
|
|
1241
|
-
//
|
|
1242
|
-
//
|
|
1243
|
-
//
|
|
1244
|
-
//
|
|
1245
|
-
//
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1240
|
+
// A CC client's OWN tools map to THEMSELVES (identity), and this OVERRIDES
|
|
1241
|
+
// TOOL_MAP. Two failure modes it fixes, both seen via the dock:
|
|
1242
|
+
// 1. TOOL_MAP's lowercase cross-client aliases mistranslate a CC tool —
|
|
1243
|
+
// `Read` → TOOL_MAP['read'] whose translateBack emits {path, filePath}
|
|
1244
|
+
// instead of {file_path}, so every Read failed validation client-side.
|
|
1245
|
+
// 2. CC's newer built-ins (Agent, AskUserQuestion, Cron*, Task*, Workflow,
|
|
1246
|
+
// NotebookEdit, Enter/ExitPlanMode, …) aren't in TOOL_MAP at all, so
|
|
1247
|
+
// they were round-robined onto Read/Bash/etc. and collided.
|
|
1248
|
+
// Exact case is the discriminator (CC sends PascalCase; {path}-style clients
|
|
1249
|
+
// send lowercase/snake) so a genuine non-CC `read` still routes via TOOL_MAP.
|
|
1250
|
+
// Tracks the live bundle, so future CC tools are covered after the next bake.
|
|
1251
|
+
const mapping = CC_NATIVE_NAMES.has(tool.name)
|
|
1252
|
+
? { ccTool: tool.name, translateArgs: (a) => a, translateBack: (a) => a }
|
|
1253
|
+
: TOOL_MAP[name];
|
|
1250
1254
|
if (mapping) {
|
|
1251
1255
|
// In hybrid mode, clone the shared mapping and attach the
|
|
1252
1256
|
// client-declared top-level field names from input_schema.
|
|
@@ -1282,8 +1286,8 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
1282
1286
|
const CC_FALLBACK_TOOLS = ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'];
|
|
1283
1287
|
for (const tool of clientTools) {
|
|
1284
1288
|
const name = (tool.name || '').toLowerCase();
|
|
1285
|
-
if (
|
|
1286
|
-
continue; //
|
|
1289
|
+
if (CC_NATIVE_NAMES.has(tool.name) || TOOL_MAP[name])
|
|
1290
|
+
continue; // CC-native (identity in pass 1) or mapped
|
|
1287
1291
|
unmappedTools.push(tool.name);
|
|
1288
1292
|
if (opts.hybridTools)
|
|
1289
1293
|
continue; // dropped — see comment above
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /health response builder — extracted so the public-vs-internal disclosure rule
|
|
3
|
+
* is unit-testable without spinning a proxy.
|
|
4
|
+
*
|
|
5
|
+
* dario's /health is auth-free (docker healthchecks + `depends_on: service_healthy`
|
|
6
|
+
* need it before any secret is configured). When dario sits behind a Cloudflare
|
|
7
|
+
* tunnel with a public /health bypass (uptime monitoring), that endpoint is
|
|
8
|
+
* world-readable — so it must not leak OAuth internals (token countdown, request
|
|
9
|
+
* volume, refresh errors). The Cloudflare edge stamps `cf-ray` on every request it
|
|
10
|
+
* proxies, so its presence marks a request as having come from the public internet.
|
|
11
|
+
* Internal callers (the docker healthcheck, `dario doctor`, the self-probe) hit
|
|
12
|
+
* dario directly on loopback with no CF headers and still get the full detail.
|
|
13
|
+
*
|
|
14
|
+
* The HTTP status (200 healthy / 503 degraded) is identical either way, so external
|
|
15
|
+
* uptime monitoring that keys on the status code is unaffected.
|
|
16
|
+
*/
|
|
17
|
+
export interface HealthStatusLike {
|
|
18
|
+
status: string;
|
|
19
|
+
canRefresh?: boolean;
|
|
20
|
+
expiresIn?: string;
|
|
21
|
+
refreshFailures?: number;
|
|
22
|
+
lastRefreshError?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface HealthResponse {
|
|
25
|
+
httpStatus: number;
|
|
26
|
+
body: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export declare function buildHealthResponse(s: HealthStatusLike, requestCount: number, viaPublicTunnel: boolean): HealthResponse;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /health response builder — extracted so the public-vs-internal disclosure rule
|
|
3
|
+
* is unit-testable without spinning a proxy.
|
|
4
|
+
*
|
|
5
|
+
* dario's /health is auth-free (docker healthchecks + `depends_on: service_healthy`
|
|
6
|
+
* need it before any secret is configured). When dario sits behind a Cloudflare
|
|
7
|
+
* tunnel with a public /health bypass (uptime monitoring), that endpoint is
|
|
8
|
+
* world-readable — so it must not leak OAuth internals (token countdown, request
|
|
9
|
+
* volume, refresh errors). The Cloudflare edge stamps `cf-ray` on every request it
|
|
10
|
+
* proxies, so its presence marks a request as having come from the public internet.
|
|
11
|
+
* Internal callers (the docker healthcheck, `dario doctor`, the self-probe) hit
|
|
12
|
+
* dario directly on loopback with no CF headers and still get the full detail.
|
|
13
|
+
*
|
|
14
|
+
* The HTTP status (200 healthy / 503 degraded) is identical either way, so external
|
|
15
|
+
* uptime monitoring that keys on the status code is unaffected.
|
|
16
|
+
*/
|
|
17
|
+
export function buildHealthResponse(s, requestCount, viaPublicTunnel) {
|
|
18
|
+
const dead = s.status === 'broken' ||
|
|
19
|
+
s.status === 'none' ||
|
|
20
|
+
(s.status === 'expired' && s.canRefresh === false);
|
|
21
|
+
const httpStatus = dead ? 503 : 200;
|
|
22
|
+
const liveness = { status: dead ? 'degraded' : 'ok' };
|
|
23
|
+
const body = viaPublicTunnel
|
|
24
|
+
? liveness
|
|
25
|
+
: {
|
|
26
|
+
...liveness,
|
|
27
|
+
oauth: s.status,
|
|
28
|
+
expiresIn: s.expiresIn,
|
|
29
|
+
requests: requestCount,
|
|
30
|
+
...(s.refreshFailures ? { refreshFailures: s.refreshFailures } : {}),
|
|
31
|
+
...(s.lastRefreshError ? { lastRefreshError: s.lastRefreshError } : {}),
|
|
32
|
+
};
|
|
33
|
+
return { httpStatus, body };
|
|
34
|
+
}
|
package/dist/proxy.js
CHANGED
|
@@ -7,6 +7,7 @@ import { homedir } from 'node:os';
|
|
|
7
7
|
import { setDefaultResultOrder } from 'node:dns';
|
|
8
8
|
import { arch, platform } from 'node:process';
|
|
9
9
|
import { getAccessToken, getStatus } from './oauth.js';
|
|
10
|
+
import { buildHealthResponse } from './health-response.js';
|
|
10
11
|
import { buildCCRequest, applyCcPromptCaching, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, CC_TEMPLATE } from './cc-template.js';
|
|
11
12
|
import { describeTemplate, detectDrift, checkCCCompat } from './live-fingerprint.js';
|
|
12
13
|
import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs } from './pool.js';
|
|
@@ -1110,18 +1111,12 @@ export async function startProxy(opts = {}) {
|
|
|
1110
1111
|
// react instead of cheerfully passing while every /v1/messages 401s.
|
|
1111
1112
|
if (urlPath === '/health' || urlPath === '/') {
|
|
1112
1113
|
const s = await getStatus();
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1114
|
+
// Public requests arrive through the Cloudflare tunnel (the edge stamps
|
|
1115
|
+
// `cf-ray`); they get only the liveness verdict, never the OAuth internals.
|
|
1116
|
+
// See buildHealthResponse for the full rationale.
|
|
1117
|
+
const { httpStatus, body } = buildHealthResponse(s, requestCount, req.headers['cf-ray'] !== undefined);
|
|
1116
1118
|
res.writeHead(httpStatus, JSON_HEADERS);
|
|
1117
|
-
res.end(JSON.stringify(
|
|
1118
|
-
status: dead ? 'degraded' : 'ok',
|
|
1119
|
-
oauth: s.status,
|
|
1120
|
-
expiresIn: s.expiresIn,
|
|
1121
|
-
requests: requestCount,
|
|
1122
|
-
...(s.refreshFailures ? { refreshFailures: s.refreshFailures } : {}),
|
|
1123
|
-
...(s.lastRefreshError ? { lastRefreshError: s.lastRefreshError } : {}),
|
|
1124
|
-
}));
|
|
1119
|
+
res.end(JSON.stringify(body));
|
|
1125
1120
|
return;
|
|
1126
1121
|
}
|
|
1127
1122
|
if (!checkAuth(req)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.74",
|
|
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": {
|