@askalf/dario 5.5.27 → 5.5.29
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/admin-api.d.ts +11 -0
- package/dist/cc-template.js +10 -1
- package/dist/pool.d.ts +25 -0
- package/dist/pool.js +24 -0
- package/dist/proxy.js +62 -2
- package/package.json +1 -1
package/dist/admin-api.d.ts
CHANGED
|
@@ -77,6 +77,17 @@ export interface AdminAccountRecord {
|
|
|
77
77
|
export interface AdminAccountLive {
|
|
78
78
|
util5h: number;
|
|
79
79
|
util7d: number;
|
|
80
|
+
/**
|
|
81
|
+
* When `util5h` / `util7d` were last observed, epoch ms — or `null` if this
|
|
82
|
+
* account has never served a response. The utilisation figures are a
|
|
83
|
+
* snapshot of the last response the account served, not a live gauge: while
|
|
84
|
+
* an account is parked nothing refreshes them, so without a timestamp a
|
|
85
|
+
* consumer cannot tell a current reading from one frozen minutes ago
|
|
86
|
+
* (dario#1032).
|
|
87
|
+
*/
|
|
88
|
+
lastObservedAt: number | null;
|
|
89
|
+
/** Age of that reading in ms, or `null` when never observed. */
|
|
90
|
+
utilAgeMs: number | null;
|
|
80
91
|
claim: string;
|
|
81
92
|
status: string;
|
|
82
93
|
requestCount: number;
|
package/dist/cc-template.js
CHANGED
|
@@ -1672,10 +1672,19 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
1672
1672
|
// appended its assistant reply locally, dario stripped it from the next
|
|
1673
1673
|
// request, the model regenerated the same reply, dario stripped that, and
|
|
1674
1674
|
// the loop never terminated (133 POSTs from a single user prompt).
|
|
1675
|
+
//
|
|
1676
|
+
// Restricted to ASSISTANT turns, which is the only shape this loop was
|
|
1677
|
+
// written for (a thinking-only assistant turn emptied by the strip above).
|
|
1678
|
+
// Popping an empty *user* turn is what the loop must not do: it exposes the
|
|
1679
|
+
// assistant turn behind it and produces the very prefill rejection the loop
|
|
1680
|
+
// exists to prevent (dario#1033). An empty user turn is a malformed client
|
|
1681
|
+
// request either way — leaving it in place lets the upstream name it
|
|
1682
|
+
// accurately ("messages.N: content must contain at least one block")
|
|
1683
|
+
// instead of dario converting it into a misleading prefill error.
|
|
1675
1684
|
while (messages.length > 0) {
|
|
1676
1685
|
const last = messages[messages.length - 1];
|
|
1677
1686
|
const contentEmpty = Array.isArray(last.content) && last.content.length === 0;
|
|
1678
|
-
if (contentEmpty) {
|
|
1687
|
+
if (contentEmpty && last.role === 'assistant') {
|
|
1679
1688
|
messages.pop();
|
|
1680
1689
|
continue;
|
|
1681
1690
|
}
|
package/dist/pool.d.ts
CHANGED
|
@@ -40,6 +40,31 @@ export interface RateLimitSnapshot {
|
|
|
40
40
|
updatedAt: number;
|
|
41
41
|
}
|
|
42
42
|
export declare const EMPTY_SNAPSHOT: RateLimitSnapshot;
|
|
43
|
+
/** Freshness of an account's utilisation reading — see `utilFreshness`. */
|
|
44
|
+
export interface UtilFreshness {
|
|
45
|
+
/** When util5h/util7d were last observed, epoch ms; null if never. */
|
|
46
|
+
lastObservedAt: number | null;
|
|
47
|
+
/** Age of that reading in ms; null if never observed. */
|
|
48
|
+
utilAgeMs: number | null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Derive how old an account's utilisation reading is (dario#1032).
|
|
52
|
+
*
|
|
53
|
+
* `util5h` / `util7d` are a SNAPSHOT of the last response the account served.
|
|
54
|
+
* They do not tick on their own, and nothing refreshes them while an account is
|
|
55
|
+
* parked (rejected, or in auth cooldown) — so they stay frozen at whatever they
|
|
56
|
+
* read at the moment it was parked. The pool does return parked accounts to
|
|
57
|
+
* service on its own and the value corrects itself when it does, which is what
|
|
58
|
+
* makes this a REPORTING problem rather than a routing one: the payload carried
|
|
59
|
+
* no timestamp, so no consumer could tell a current reading from one frozen
|
|
60
|
+
* minutes ago, and a dashboard rendered "5-hour window full" for an account
|
|
61
|
+
* that had since reset and was free.
|
|
62
|
+
*
|
|
63
|
+
* `updatedAt` was already on the snapshot; it was simply never surfaced. An
|
|
64
|
+
* `updatedAt` of 0 is EMPTY_SNAPSHOT's "never observed", which must report as
|
|
65
|
+
* null rather than as an age of ~56 years since epoch.
|
|
66
|
+
*/
|
|
67
|
+
export declare function utilFreshness(rl: RateLimitSnapshot, now: number): UtilFreshness;
|
|
43
68
|
export interface PoolAccount {
|
|
44
69
|
alias: string;
|
|
45
70
|
accessToken: string;
|
package/dist/pool.js
CHANGED
|
@@ -34,6 +34,30 @@ export const EMPTY_SNAPSHOT = {
|
|
|
34
34
|
fallbackPct: 0,
|
|
35
35
|
updatedAt: 0,
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Derive how old an account's utilisation reading is (dario#1032).
|
|
39
|
+
*
|
|
40
|
+
* `util5h` / `util7d` are a SNAPSHOT of the last response the account served.
|
|
41
|
+
* They do not tick on their own, and nothing refreshes them while an account is
|
|
42
|
+
* parked (rejected, or in auth cooldown) — so they stay frozen at whatever they
|
|
43
|
+
* read at the moment it was parked. The pool does return parked accounts to
|
|
44
|
+
* service on its own and the value corrects itself when it does, which is what
|
|
45
|
+
* makes this a REPORTING problem rather than a routing one: the payload carried
|
|
46
|
+
* no timestamp, so no consumer could tell a current reading from one frozen
|
|
47
|
+
* minutes ago, and a dashboard rendered "5-hour window full" for an account
|
|
48
|
+
* that had since reset and was free.
|
|
49
|
+
*
|
|
50
|
+
* `updatedAt` was already on the snapshot; it was simply never surfaced. An
|
|
51
|
+
* `updatedAt` of 0 is EMPTY_SNAPSHOT's "never observed", which must report as
|
|
52
|
+
* null rather than as an age of ~56 years since epoch.
|
|
53
|
+
*/
|
|
54
|
+
export function utilFreshness(rl, now) {
|
|
55
|
+
const lastObservedAt = rl.updatedAt || null;
|
|
56
|
+
return {
|
|
57
|
+
lastObservedAt,
|
|
58
|
+
utilAgeMs: lastObservedAt === null ? null : Math.max(0, now - lastObservedAt),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
37
61
|
/**
|
|
38
62
|
* Cool-down schedule after auth failures. First failure: 60s. Each
|
|
39
63
|
* consecutive failure doubles the window up to 30 minutes. Cleared
|
package/dist/proxy.js
CHANGED
|
@@ -12,7 +12,7 @@ import { darioVersion } from './version.js';
|
|
|
12
12
|
import { buildCCRequest, applyCcPromptCaching, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, overlayTemplateHeaderValues, forwardClientCCIdentityHeaders, isMcpToolName, CC_TEMPLATE, effectiveCacheControl, withForced1hBeta } from './cc-template.js';
|
|
13
13
|
import { stampCch, hasCchSeed } from './cch.js';
|
|
14
14
|
import { describeTemplate, detectDrift, checkCCCompat, probeInstalledCCVersion } from './live-fingerprint.js';
|
|
15
|
-
import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, reconcilePoolAccounts, resolvePoolStrategy } from './pool.js';
|
|
15
|
+
import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, reconcilePoolAccounts, resolvePoolStrategy, utilFreshness } from './pool.js';
|
|
16
16
|
import { Analytics, billingBucketFromClaim, formatUsageLogLine, SUBSCRIPTION_CLAIMS } from './analytics.js';
|
|
17
17
|
import { OverageGuard, buildHaltErrorBody } from './overage-guard.js';
|
|
18
18
|
import { notify as osNotify } from './notify.js';
|
|
@@ -658,6 +658,12 @@ export function sanitizeMessages(body, preserveTags) {
|
|
|
658
658
|
if (!messages)
|
|
659
659
|
return;
|
|
660
660
|
const patterns = orchestrationPatternsFor(preserveTags);
|
|
661
|
+
// Snapshot the tail turn before scrubbing. If the scrub empties it and the
|
|
662
|
+
// drop below would expose an assistant turn, we put the original content
|
|
663
|
+
// back rather than ship a prefill (dario#1033) — see the guard after the
|
|
664
|
+
// filter for the reasoning.
|
|
665
|
+
const tail = messages.length > 0 ? messages[messages.length - 1] : undefined;
|
|
666
|
+
const tailContentBeforeScrub = tail ? tail.content : undefined;
|
|
661
667
|
for (const msg of messages) {
|
|
662
668
|
if (typeof msg.content === 'string') {
|
|
663
669
|
msg.content = sanitizeContent(msg.content, patterns);
|
|
@@ -691,13 +697,48 @@ export function sanitizeMessages(body, preserveTags) {
|
|
|
691
697
|
// The message carried nothing for the model, so removing it is the same
|
|
692
698
|
// decision the block filter already made, applied one level up. String
|
|
693
699
|
// content scrubbed to '' is the same case in its other shape.
|
|
694
|
-
|
|
700
|
+
const kept = messages.filter((m) => {
|
|
695
701
|
if (Array.isArray(m.content))
|
|
696
702
|
return m.content.length > 0;
|
|
697
703
|
if (typeof m.content === 'string')
|
|
698
704
|
return m.content !== '';
|
|
699
705
|
return true;
|
|
700
706
|
});
|
|
707
|
+
// Invariant: the scrub must never turn a request that ended on a USER turn
|
|
708
|
+
// into one that ends on an ASSISTANT turn. Anthropic reads a trailing
|
|
709
|
+
// assistant turn as a prefill ("continue from this text") and Opus 4.6 under
|
|
710
|
+
// adaptive thinking + the claude-code beta rejects it outright:
|
|
711
|
+
// 400 "This model does not support assistant message prefill.
|
|
712
|
+
// The conversation must end with a user message."
|
|
713
|
+
//
|
|
714
|
+
// CC emits standalone `<system-reminder>` / `<task_metadata>` user turns —
|
|
715
|
+
// notably right after a Task (sub-agent) result is folded back into the
|
|
716
|
+
// parent transcript. Both tags are in ORCHESTRATION_TAG_NAMES, so that turn
|
|
717
|
+
// scrubs to empty, the filter above drops it, and a valid CC request leaves
|
|
718
|
+
// as a prefill (dario#1033).
|
|
719
|
+
//
|
|
720
|
+
// The fix restores the turn's PRE-SCRUB content instead of dropping it. The
|
|
721
|
+
// orchestration tag survives in this one position only, which is the right
|
|
722
|
+
// trade in both directions: for a CC client the tag is CC's own injection,
|
|
723
|
+
// so keeping it is *more* wire-faithful, not less; for a non-CC client a
|
|
724
|
+
// lone tag as the final turn is the actual prompt, and forwarding it beats
|
|
725
|
+
// a hard 400. Every other position still scrubs exactly as before.
|
|
726
|
+
const tailWasDropped = tail !== undefined && kept[kept.length - 1] !== tail;
|
|
727
|
+
const tailHadContent = Array.isArray(tailContentBeforeScrub)
|
|
728
|
+
? tailContentBeforeScrub.length > 0
|
|
729
|
+
: typeof tailContentBeforeScrub === 'string'
|
|
730
|
+
? tailContentBeforeScrub !== ''
|
|
731
|
+
: tailContentBeforeScrub != null;
|
|
732
|
+
if (tail !== undefined &&
|
|
733
|
+
tailWasDropped &&
|
|
734
|
+
tail.role === 'user' &&
|
|
735
|
+
tailHadContent &&
|
|
736
|
+
kept.length > 0 &&
|
|
737
|
+
kept[kept.length - 1].role === 'assistant') {
|
|
738
|
+
tail.content = tailContentBeforeScrub;
|
|
739
|
+
kept.push(tail);
|
|
740
|
+
}
|
|
741
|
+
body.messages = kept;
|
|
701
742
|
}
|
|
702
743
|
/**
|
|
703
744
|
* Scrub non-Claude-Code fields and normalize field ordering.
|
|
@@ -1793,6 +1834,10 @@ export async function startProxy(opts = {}) {
|
|
|
1793
1834
|
snap.set(a.alias, {
|
|
1794
1835
|
util5h: a.rateLimit.util5h,
|
|
1795
1836
|
util7d: a.rateLimit.util7d,
|
|
1837
|
+
// Same freshness fields GET /accounts exposes (#1032) — this
|
|
1838
|
+
// surface documents itself as reporting the same snapshot, so it
|
|
1839
|
+
// must not be the one place a stale reading still looks current.
|
|
1840
|
+
...utilFreshness(a.rateLimit, snapNow),
|
|
1796
1841
|
claim: a.rateLimit.claim,
|
|
1797
1842
|
status: isInAuthCooldown(a, snapNow) ? 'auth-cooldown' : a.rateLimit.status,
|
|
1798
1843
|
requestCount: a.requestCount,
|
|
@@ -1872,10 +1917,25 @@ export async function startProxy(opts = {}) {
|
|
|
1872
1917
|
const cooldownMs = inCooldown && a.lastAuthFailureAt
|
|
1873
1918
|
? Math.max(0, authCooldownMs(a.consecutiveAuthFailures) - (now - a.lastAuthFailureAt))
|
|
1874
1919
|
: 0;
|
|
1920
|
+
// Freshness of the utilisation reading (#1032). util5h/util7d are a
|
|
1921
|
+
// SNAPSHOT of the last response this account served — they do not tick
|
|
1922
|
+
// on their own. While an account is parked (rejected, or in auth
|
|
1923
|
+
// cooldown) nothing refreshes them, so they stay frozen at whatever
|
|
1924
|
+
// they read at the moment it was parked, and a consumer sees "5-hour
|
|
1925
|
+
// window full" for an account that has since reset and is free.
|
|
1926
|
+
//
|
|
1927
|
+
// The pool does return parked accounts to service on its own and the
|
|
1928
|
+
// value corrects itself the moment it does, so this is a reporting
|
|
1929
|
+
// problem, not a routing one: nothing downstream could tell a current
|
|
1930
|
+
// reading from one frozen minutes ago, because the payload carried no
|
|
1931
|
+
// timestamp at all. `updatedAt` was already on the snapshot; it was
|
|
1932
|
+
// simply never surfaced. null means "never observed" (no response has
|
|
1933
|
+
// been served on this account yet) rather than "observed at epoch 0".
|
|
1875
1934
|
return {
|
|
1876
1935
|
alias: a.alias,
|
|
1877
1936
|
util5h: a.rateLimit.util5h,
|
|
1878
1937
|
util7d: a.rateLimit.util7d,
|
|
1938
|
+
...utilFreshness(a.rateLimit, now),
|
|
1879
1939
|
claim: a.rateLimit.claim,
|
|
1880
1940
|
status: inCooldown ? 'auth-cooldown' : a.rateLimit.status,
|
|
1881
1941
|
requestCount: a.requestCount,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.29",
|
|
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": {
|