@askalf/dario 4.8.73 → 4.8.75

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.
@@ -1456,7 +1456,21 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1456
1456
  ccRequest.tools = [...CC_TOOL_DEFINITIONS, ...appended];
1457
1457
  }
1458
1458
  else {
1459
- ccRequest.tools = CC_TOOL_DEFINITIONS;
1459
+ // Advertise only the CC-native tools the client actually declared.
1460
+ // Substituting the FULL CC template here makes the model emit a tool_use
1461
+ // for a tool the client never sent — e.g. AskUserQuestion when a headless
1462
+ // or SDK session has it disabled — and the client harness then rejects it
1463
+ // with "<Tool> exists but is not enabled in this context" (reported via a
1464
+ // dario-routed CC session). A real CC client with a reduced tool set sends
1465
+ // exactly this reduced array (every --disallowedTools / MCP delta does
1466
+ // this), so filtering tracks CC's wire shape rather than diverging from it.
1467
+ // If the client declared no CC-native tool at all it isn't really CC; keep
1468
+ // the full template as the safer fingerprint default in that case.
1469
+ const clientToolNames = new Set(clientTools
1470
+ .map((t) => t.name?.toLowerCase())
1471
+ .filter((n) => Boolean(n)));
1472
+ const availableCC = CC_TOOL_DEFINITIONS.filter((t) => clientToolNames.has(t.name.toLowerCase()));
1473
+ ccRequest.tools = availableCC.length > 0 ? availableCC : CC_TOOL_DEFINITIONS;
1460
1474
  }
1461
1475
  }
1462
1476
  else if (effectiveMergeTools) {
@@ -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
- const dead = s.status === 'broken' || s.status === 'none' ||
1114
- (s.status === 'expired' && s.canRefresh === false);
1115
- const httpStatus = dead ? 503 : 200;
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.73",
3
+ "version": "4.8.75",
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": {