instar 1.3.64 → 1.3.65
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/commands/reflect.d.ts.map +1 -1
- package/dist/commands/reflect.js +2 -1
- package/dist/commands/reflect.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +16 -4
- package/dist/commands/server.js.map +1 -1
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts +40 -0
- package/dist/core/CircuitBreakingIntelligenceProvider.d.ts.map +1 -0
- package/dist/core/CircuitBreakingIntelligenceProvider.js +72 -0
- package/dist/core/CircuitBreakingIntelligenceProvider.js.map +1 -0
- package/dist/core/ClaudeCliIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/ClaudeCliIntelligenceProvider.js +5 -2
- package/dist/core/ClaudeCliIntelligenceProvider.js.map +1 -1
- package/dist/core/CodexCliIntelligenceProvider.d.ts.map +1 -1
- package/dist/core/CodexCliIntelligenceProvider.js +3 -1
- package/dist/core/CodexCliIntelligenceProvider.js.map +1 -1
- package/dist/core/LlmCircuitBreaker.d.ts +137 -0
- package/dist/core/LlmCircuitBreaker.d.ts.map +1 -0
- package/dist/core/LlmCircuitBreaker.js +236 -0
- package/dist/core/LlmCircuitBreaker.js.map +1 -0
- package/dist/core/intelligenceProviderFactory.d.ts.map +1 -1
- package/dist/core/intelligenceProviderFactory.js +8 -3
- package/dist/core/intelligenceProviderFactory.js.map +1 -1
- package/dist/core/types.d.ts +22 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.65.md +67 -0
- package/upgrades/side-effects/llm-ratelimit-circuit-breaker.md +75 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CircuitBreakingIntelligenceProvider — wraps any IntelligenceProvider with the
|
|
3
|
+
* account-global LlmCircuitBreaker.
|
|
4
|
+
*
|
|
5
|
+
* This is the structural chokepoint that makes the breaker universal: every
|
|
6
|
+
* IntelligenceProvider built by the factory (and at the few direct construction
|
|
7
|
+
* sites) is wrapped once, so EVERY consumer — current and future — is protected
|
|
8
|
+
* without having to remember to consult the breaker (Structure > Willpower).
|
|
9
|
+
*
|
|
10
|
+
* Behaviour:
|
|
11
|
+
* - Before delegating, ask the breaker. If it's open, throw
|
|
12
|
+
* LlmCircuitOpenError WITHOUT spawning the underlying provider — this is
|
|
13
|
+
* what actually stops the bleeding: no `claude -p` subprocess, no tokens.
|
|
14
|
+
* - On success, tell the breaker the provider responded (closes it).
|
|
15
|
+
* - On a rate-limit error, trip the breaker and re-throw a typed
|
|
16
|
+
* RateLimitError so callers/telemetry can distinguish it.
|
|
17
|
+
* - On any other error, close the breaker (the limit isn't the cause) and
|
|
18
|
+
* re-throw the original error unchanged so existing fallback paths behave
|
|
19
|
+
* exactly as before.
|
|
20
|
+
*
|
|
21
|
+
* Callers that already swallow provider errors (e.g. PromptGate's silent
|
|
22
|
+
* fallback) will swallow LlmCircuitOpenError too — which is precisely the
|
|
23
|
+
* desired behaviour: skip the LLM step while the breaker is open and fall back
|
|
24
|
+
* to heuristics, at zero subprocess cost.
|
|
25
|
+
*/
|
|
26
|
+
import type { IntelligenceProvider, IntelligenceOptions } from './types.js';
|
|
27
|
+
import { LlmCircuitBreaker } from './LlmCircuitBreaker.js';
|
|
28
|
+
export declare class CircuitBreakingIntelligenceProvider implements IntelligenceProvider {
|
|
29
|
+
private readonly inner;
|
|
30
|
+
private readonly breaker;
|
|
31
|
+
constructor(inner: IntelligenceProvider, breaker?: LlmCircuitBreaker);
|
|
32
|
+
evaluate(prompt: string, options?: IntelligenceOptions): Promise<string>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Wrap a provider with the shared circuit breaker. No-ops on null (so callers
|
|
36
|
+
* can pass a possibly-null factory result through unchanged) and is idempotent
|
|
37
|
+
* (never double-wraps).
|
|
38
|
+
*/
|
|
39
|
+
export declare function wrapIntelligenceWithCircuitBreaker(provider: IntelligenceProvider | null | undefined): IntelligenceProvider | null;
|
|
40
|
+
//# sourceMappingURL=CircuitBreakingIntelligenceProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CircuitBreakingIntelligenceProvider.d.ts","sourceRoot":"","sources":["../../src/core/CircuitBreakingIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,iBAAiB,EAKlB,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,mCAAoC,YAAW,oBAAoB;IAE5E,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,oBAAoB,EAC3B,OAAO,GAAE,iBAA0C;IAGhE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;CAwB/E;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GAAG,SAAS,GAChD,oBAAoB,GAAG,IAAI,CAI7B"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CircuitBreakingIntelligenceProvider — wraps any IntelligenceProvider with the
|
|
3
|
+
* account-global LlmCircuitBreaker.
|
|
4
|
+
*
|
|
5
|
+
* This is the structural chokepoint that makes the breaker universal: every
|
|
6
|
+
* IntelligenceProvider built by the factory (and at the few direct construction
|
|
7
|
+
* sites) is wrapped once, so EVERY consumer — current and future — is protected
|
|
8
|
+
* without having to remember to consult the breaker (Structure > Willpower).
|
|
9
|
+
*
|
|
10
|
+
* Behaviour:
|
|
11
|
+
* - Before delegating, ask the breaker. If it's open, throw
|
|
12
|
+
* LlmCircuitOpenError WITHOUT spawning the underlying provider — this is
|
|
13
|
+
* what actually stops the bleeding: no `claude -p` subprocess, no tokens.
|
|
14
|
+
* - On success, tell the breaker the provider responded (closes it).
|
|
15
|
+
* - On a rate-limit error, trip the breaker and re-throw a typed
|
|
16
|
+
* RateLimitError so callers/telemetry can distinguish it.
|
|
17
|
+
* - On any other error, close the breaker (the limit isn't the cause) and
|
|
18
|
+
* re-throw the original error unchanged so existing fallback paths behave
|
|
19
|
+
* exactly as before.
|
|
20
|
+
*
|
|
21
|
+
* Callers that already swallow provider errors (e.g. PromptGate's silent
|
|
22
|
+
* fallback) will swallow LlmCircuitOpenError too — which is precisely the
|
|
23
|
+
* desired behaviour: skip the LLM step while the breaker is open and fall back
|
|
24
|
+
* to heuristics, at zero subprocess cost.
|
|
25
|
+
*/
|
|
26
|
+
import { LlmCircuitOpenError, RateLimitError, getLlmCircuitBreaker, isRateLimitError, } from './LlmCircuitBreaker.js';
|
|
27
|
+
export class CircuitBreakingIntelligenceProvider {
|
|
28
|
+
inner;
|
|
29
|
+
breaker;
|
|
30
|
+
constructor(inner, breaker = getLlmCircuitBreaker()) {
|
|
31
|
+
this.inner = inner;
|
|
32
|
+
this.breaker = breaker;
|
|
33
|
+
}
|
|
34
|
+
async evaluate(prompt, options) {
|
|
35
|
+
const gate = this.breaker.acquire();
|
|
36
|
+
if (!gate.allow) {
|
|
37
|
+
throw new LlmCircuitOpenError(gate.retryAfterMs);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const result = await this.inner.evaluate(prompt, options);
|
|
41
|
+
this.breaker.onResolved();
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
46
|
+
if (err instanceof RateLimitError || isRateLimitError(message)) {
|
|
47
|
+
this.breaker.onRateLimited(message);
|
|
48
|
+
if (err instanceof RateLimitError)
|
|
49
|
+
throw err;
|
|
50
|
+
throw new RateLimitError(message, err);
|
|
51
|
+
}
|
|
52
|
+
// Non-rate-limit failure (timeout, parse error, network blip, …): not the
|
|
53
|
+
// breaker's concern. Close it so we don't keep all LLM features down on an
|
|
54
|
+
// unrelated error, and surface the original error to the caller's fallback.
|
|
55
|
+
this.breaker.onResolved();
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Wrap a provider with the shared circuit breaker. No-ops on null (so callers
|
|
62
|
+
* can pass a possibly-null factory result through unchanged) and is idempotent
|
|
63
|
+
* (never double-wraps).
|
|
64
|
+
*/
|
|
65
|
+
export function wrapIntelligenceWithCircuitBreaker(provider) {
|
|
66
|
+
if (!provider)
|
|
67
|
+
return null;
|
|
68
|
+
if (provider instanceof CircuitBreakingIntelligenceProvider)
|
|
69
|
+
return provider;
|
|
70
|
+
return new CircuitBreakingIntelligenceProvider(provider);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=CircuitBreakingIntelligenceProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CircuitBreakingIntelligenceProvider.js","sourceRoot":"","sources":["../../src/core/CircuitBreakingIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAEL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,OAAO,mCAAmC;IAE3B;IACA;IAFnB,YACmB,KAA2B,EAC3B,UAA6B,oBAAoB,EAAE;QADnD,UAAK,GAAL,KAAK,CAAsB;QAC3B,YAAO,GAAP,OAAO,CAA4C;IACnE,CAAC;IAEJ,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,OAA6B;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,mBAAmB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC1D,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,IAAI,GAAG,YAAY,cAAc,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/D,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;gBACpC,IAAI,GAAG,YAAY,cAAc;oBAAE,MAAM,GAAG,CAAC;gBAC7C,MAAM,IAAI,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACzC,CAAC;YACD,0EAA0E;YAC1E,2EAA2E;YAC3E,4EAA4E;YAC5E,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;YAC1B,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAChD,QAAiD;IAEjD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,QAAQ,YAAY,mCAAmC;QAAE,OAAO,QAAQ,CAAC;IAC7E,OAAO,IAAI,mCAAmC,CAAC,QAAQ,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClaudeCliIntelligenceProvider.d.ts","sourceRoot":"","sources":["../../src/core/ClaudeCliIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAO5E,qBAAa,6BAA8B,YAAW,oBAAoB;IACxE,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM;IAYxB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ClaudeCliIntelligenceProvider.d.ts","sourceRoot":"","sources":["../../src/core/ClaudeCliIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAO5E,qBAAa,6BAA8B,YAAW,oBAAoB;IACxE,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM;IAYxB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;CA+C/E"}
|
|
@@ -55,8 +55,11 @@ export class ClaudeCliIntelligenceProvider {
|
|
|
55
55
|
env: childEnv,
|
|
56
56
|
}, (error, stdout, stderr) => {
|
|
57
57
|
if (error) {
|
|
58
|
-
// Timeout or other error —
|
|
59
|
-
|
|
58
|
+
// Timeout or other error — reject so caller can fall back. Include a
|
|
59
|
+
// generous stderr slice so the circuit breaker's rate-limit
|
|
60
|
+
// classifier (isRateLimitError) can see usage/limit language that
|
|
61
|
+
// often appears past the first 200 chars.
|
|
62
|
+
reject(new Error(`Claude CLI error: ${error.message}${stderr ? ` — ${stderr.slice(0, 600)}` : ''}`));
|
|
60
63
|
return;
|
|
61
64
|
}
|
|
62
65
|
resolve(stdout.trim());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClaudeCliIntelligenceProvider.js","sourceRoot":"","sources":["../../src/core/ClaudeCliIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,OAAO,6BAA6B;IAChC,UAAU,CAAS;IAE3B,YAAY,UAAkB;QAC5B,wEAAwE;QACxE,mEAAmE;QACnE,oEAAoE;QACpE,qEAAqE;QACrE,mEAAmE;QACnE,wEAAwE;QACxE,yDAAyD;QACzD,mBAAmB,CAAC,+BAA+B,CAAC,CAAC;QACrD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,OAA6B;QAC1D,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,IAAI,aAAa,CAAC,CAAC;QAE9D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,IAAI,GAAG;gBACX,IAAI,EAAE,MAAM;gBACZ,SAAS,EAAE,KAAK;gBAChB,aAAa,EAAE,GAAG;gBAClB,iBAAiB,EAAE,MAAM;gBACzB,8DAA8D;gBAC9D,4DAA4D;gBAC5D,mBAAmB,EAAE,MAAM;aAC5B,CAAC;YAEF,uEAAuE;YACvE,4EAA4E;YAC5E,0EAA0E;YAC1E,yEAAyE;YACzE,MAAM,QAAQ,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YACpC,OAAO,QAAQ,CAAC,UAAU,CAAC;YAC3B,OAAO,QAAQ,CAAC,iBAAiB,CAAC;YAElC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE;gBAC5C,2EAA2E;gBAC3E,2EAA2E;gBAC3E,qEAAqE;gBACrE,8DAA8D;gBAC9D,OAAO,EAAE,OAAO,EAAE,SAAS,IAAI,kBAAkB;gBACjD,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM;gBAC9B,GAAG,EAAE,QAAQ;aACd,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;gBAC3B,IAAI,KAAK,EAAE,CAAC;oBACV,
|
|
1
|
+
{"version":3,"file":"ClaudeCliIntelligenceProvider.js","sourceRoot":"","sources":["../../src/core/ClaudeCliIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,OAAO,6BAA6B;IAChC,UAAU,CAAS;IAE3B,YAAY,UAAkB;QAC5B,wEAAwE;QACxE,mEAAmE;QACnE,oEAAoE;QACpE,qEAAqE;QACrE,mEAAmE;QACnE,wEAAwE;QACxE,yDAAyD;QACzD,mBAAmB,CAAC,+BAA+B,CAAC,CAAC;QACrD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,OAA6B;QAC1D,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,IAAI,aAAa,CAAC,CAAC;QAE9D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,IAAI,GAAG;gBACX,IAAI,EAAE,MAAM;gBACZ,SAAS,EAAE,KAAK;gBAChB,aAAa,EAAE,GAAG;gBAClB,iBAAiB,EAAE,MAAM;gBACzB,8DAA8D;gBAC9D,4DAA4D;gBAC5D,mBAAmB,EAAE,MAAM;aAC5B,CAAC;YAEF,uEAAuE;YACvE,4EAA4E;YAC5E,0EAA0E;YAC1E,yEAAyE;YACzE,MAAM,QAAQ,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YACpC,OAAO,QAAQ,CAAC,UAAU,CAAC;YAC3B,OAAO,QAAQ,CAAC,iBAAiB,CAAC;YAElC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE;gBAC5C,2EAA2E;gBAC3E,2EAA2E;gBAC3E,qEAAqE;gBACrE,8DAA8D;gBAC9D,OAAO,EAAE,OAAO,EAAE,SAAS,IAAI,kBAAkB;gBACjD,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM;gBAC9B,GAAG,EAAE,QAAQ;aACd,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;gBAC3B,IAAI,KAAK,EAAE,CAAC;oBACV,qEAAqE;oBACrE,4DAA4D;oBAC5D,kEAAkE;oBAClE,0CAA0C;oBAC1C,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;oBACrG,OAAO;gBACT,CAAC;gBAED,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YAEH,qEAAqE;YACrE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodexCliIntelligenceProvider.d.ts","sourceRoot":"","sources":["../../src/core/CodexCliIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA6C5E,MAAM,WAAW,mCAAmC;IAClD,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;IACrE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,4BAA6B,YAAW,oBAAoB;IACvE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyD;gBAEzE,OAAO,EAAE,mCAAmC;IASlD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"CodexCliIntelligenceProvider.d.ts","sourceRoot":"","sources":["../../src/core/CodexCliIntelligenceProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA6C5E,MAAM,WAAW,mCAAmC;IAClD,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;IACrE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,4BAA6B,YAAW,oBAAoB;IACvE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyD;gBAEzE,OAAO,EAAE,mCAAmC;IASlD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;CAiF/E"}
|
|
@@ -119,8 +119,10 @@ export class CodexCliIntelligenceProvider {
|
|
|
119
119
|
env: childEnv,
|
|
120
120
|
}, (error, stdout, stderr) => {
|
|
121
121
|
if (error) {
|
|
122
|
+
// Generous stderr slice so the circuit breaker's rate-limit
|
|
123
|
+
// classifier can see usage/limit language past the first 200 chars.
|
|
122
124
|
reject(new Error(`Codex CLI error: ${error.message}` +
|
|
123
|
-
(stderr ? ` — ${stderr.slice(0,
|
|
125
|
+
(stderr ? ` — ${stderr.slice(0, 600)}` : '')));
|
|
124
126
|
return;
|
|
125
127
|
}
|
|
126
128
|
resolve(stdout.trim());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodexCliIntelligenceProvider.js","sourceRoot":"","sources":["../../src/core/CodexCliIntelligenceProvider.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CodexCliIntelligenceProvider.js","sourceRoot":"","sources":["../../src/core/CodexCliIntelligenceProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LlmCircuitBreaker — account-global reactive circuit breaker for LLM calls.
|
|
3
|
+
*
|
|
4
|
+
* Motivating incident (2026-05-28): a wild agent burned $452/$455 in usage
|
|
5
|
+
* credits because PromptGate's per-tick Haiku detection loop kept calling
|
|
6
|
+
* `claude -p` while the account was over its weekly spend limit. The loop's
|
|
7
|
+
* error handler swallowed the rate-limit error with no backoff, so every
|
|
8
|
+
* monitor tick spawned another doomed (or, with auto-reload, freshly-billed)
|
|
9
|
+
* subprocess. There is NO legitimate reason to keep calling the LLM once the
|
|
10
|
+
* provider has told us we are over our limit — every further call is either
|
|
11
|
+
* wasted (rejected) or actively harmful (auto-reload refuels the burn).
|
|
12
|
+
*
|
|
13
|
+
* This breaker reacts to the provider's OWN rate-limit signal in milliseconds,
|
|
14
|
+
* which is distinct from (and complementary to) the volume-based
|
|
15
|
+
* token-burn-detection system (BurnDetector + LlmRateGate), which reacts to
|
|
16
|
+
* statistical token-share over a ~30-minute window. Where the burn system
|
|
17
|
+
* answers "this path is spending too much," the breaker answers "the provider
|
|
18
|
+
* just said we're rate-limited — stop."
|
|
19
|
+
*
|
|
20
|
+
* Scope: ACCOUNT-GLOBAL. A usage/rate limit applies to the whole subscription,
|
|
21
|
+
* not one session or one component, so a single shared breaker pauses every
|
|
22
|
+
* LLM-backed feature (PromptGate, PresenceProxy, PromiseBeacon, sentinels,
|
|
23
|
+
* reviewers) at once. It is wired structurally at the IntelligenceProvider
|
|
24
|
+
* construction chokepoint via CircuitBreakingIntelligenceProvider, so no
|
|
25
|
+
* consumer has to remember to consult it (Structure > Willpower).
|
|
26
|
+
*
|
|
27
|
+
* State: in-memory, process-local. A restart resets it — which is correct: the
|
|
28
|
+
* first call after restart probes the provider, and if still limited the
|
|
29
|
+
* breaker re-trips immediately.
|
|
30
|
+
*/
|
|
31
|
+
/** Thrown by a provider (or re-thrown by the decorator) when the underlying LLM call was rejected for a usage/rate limit. */
|
|
32
|
+
export declare class RateLimitError extends Error {
|
|
33
|
+
readonly providerError?: unknown | undefined;
|
|
34
|
+
readonly isRateLimit: true;
|
|
35
|
+
constructor(message: string, providerError?: unknown | undefined);
|
|
36
|
+
}
|
|
37
|
+
/** Thrown by the decorator when the breaker is open — the call was refused WITHOUT spawning the underlying provider. */
|
|
38
|
+
export declare class LlmCircuitOpenError extends Error {
|
|
39
|
+
readonly retryAfterMs: number;
|
|
40
|
+
readonly circuitOpen: true;
|
|
41
|
+
constructor(retryAfterMs: number);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Classify whether an error message indicates a provider usage/rate/spend
|
|
45
|
+
* limit (as opposed to a timeout, a parse error, a network blip, etc.).
|
|
46
|
+
*
|
|
47
|
+
* Pure + exported so it is unit-testable against representative CLI error
|
|
48
|
+
* strings from both `claude -p` and `codex exec`. Deliberately conservative:
|
|
49
|
+
* a false positive trips the breaker and degrades all LLM-backed features to
|
|
50
|
+
* heuristic-only for one open window, so we match only language that is
|
|
51
|
+
* unambiguously about hitting a usage/spend/quota ceiling — never generic
|
|
52
|
+
* "error"/"failed"/timeout text.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isRateLimitError(message: string | null | undefined): boolean;
|
|
55
|
+
type CircuitState = 'closed' | 'open' | 'half-open';
|
|
56
|
+
export interface LlmCircuitBreakerOptions {
|
|
57
|
+
/** How long to stay fully open before allowing a single probe. Default 15 min. */
|
|
58
|
+
openMs?: number;
|
|
59
|
+
/** When false, the breaker is a passthrough — acquire() always allows, records are no-ops. Default true. */
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
/** Injectable clock for tests. */
|
|
62
|
+
now?: () => number;
|
|
63
|
+
/** Injectable logger (defaults to console.warn → server log). */
|
|
64
|
+
log?: (line: string) => void;
|
|
65
|
+
}
|
|
66
|
+
export interface AcquireDecision {
|
|
67
|
+
/** Whether the caller may invoke the underlying provider. */
|
|
68
|
+
allow: boolean;
|
|
69
|
+
/** True when this acquisition is the single half-open probe. */
|
|
70
|
+
probe: boolean;
|
|
71
|
+
/** When blocked, how long until the next probe window (ms). */
|
|
72
|
+
retryAfterMs: number;
|
|
73
|
+
}
|
|
74
|
+
export interface LlmCircuitBreakerStatus {
|
|
75
|
+
state: CircuitState;
|
|
76
|
+
enabled: boolean;
|
|
77
|
+
openUntil: number | null;
|
|
78
|
+
retryAfterMs: number;
|
|
79
|
+
tripCount: number;
|
|
80
|
+
lastReason: string | null;
|
|
81
|
+
lastTrippedAt: number | null;
|
|
82
|
+
}
|
|
83
|
+
export declare class LlmCircuitBreaker {
|
|
84
|
+
private state;
|
|
85
|
+
private openUntil;
|
|
86
|
+
private firstOpenedAt;
|
|
87
|
+
private probeInFlight;
|
|
88
|
+
private tripCount;
|
|
89
|
+
private lastReason;
|
|
90
|
+
private lastTrippedAt;
|
|
91
|
+
private openMs;
|
|
92
|
+
private enabled;
|
|
93
|
+
private readonly now;
|
|
94
|
+
private readonly log;
|
|
95
|
+
constructor(opts?: LlmCircuitBreakerOptions);
|
|
96
|
+
/** Runtime reconfiguration (called once at server startup from config). */
|
|
97
|
+
configure(opts: {
|
|
98
|
+
openMs?: number;
|
|
99
|
+
enabled?: boolean;
|
|
100
|
+
}): void;
|
|
101
|
+
/**
|
|
102
|
+
* Gate a call. Call BEFORE invoking the underlying provider. When it returns
|
|
103
|
+
* { allow: false }, the caller MUST NOT call the provider — throw
|
|
104
|
+
* LlmCircuitOpenError instead (the whole point is to avoid the spend).
|
|
105
|
+
*
|
|
106
|
+
* Exactly one probe is admitted when transitioning out of the open window;
|
|
107
|
+
* concurrent callers are blocked until that probe resolves.
|
|
108
|
+
*/
|
|
109
|
+
acquire(): AcquireDecision;
|
|
110
|
+
/**
|
|
111
|
+
* Record that the underlying call returned (or threw a non-rate-limit
|
|
112
|
+
* error). Either way the rate-limit condition is not currently present, so
|
|
113
|
+
* the breaker closes. A non-rate-limit error is handled by the caller's own
|
|
114
|
+
* fallback — it is not the breaker's concern, and staying open on unrelated
|
|
115
|
+
* errors would needlessly keep all LLM features down.
|
|
116
|
+
*/
|
|
117
|
+
onResolved(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Record that the underlying call was rejected for a usage/rate limit. Opens
|
|
120
|
+
* (or re-extends) the breaker for a full window.
|
|
121
|
+
*/
|
|
122
|
+
onRateLimited(reason: string): void;
|
|
123
|
+
status(): LlmCircuitBreakerStatus;
|
|
124
|
+
/** Reset to a clean closed state. For tests + an explicit operator override. */
|
|
125
|
+
reset(): void;
|
|
126
|
+
}
|
|
127
|
+
/** Process-wide account-global breaker. All wrapped providers share this instance. */
|
|
128
|
+
export declare function getLlmCircuitBreaker(): LlmCircuitBreaker;
|
|
129
|
+
/** Configure the singleton at startup. Safe to call before first use. */
|
|
130
|
+
export declare function configureLlmCircuitBreaker(opts: {
|
|
131
|
+
openMs?: number;
|
|
132
|
+
enabled?: boolean;
|
|
133
|
+
}): void;
|
|
134
|
+
/** Test-only: drop the singleton so a fresh one is built next access. */
|
|
135
|
+
export declare function __resetLlmCircuitBreakerSingleton(): void;
|
|
136
|
+
export {};
|
|
137
|
+
//# sourceMappingURL=LlmCircuitBreaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LlmCircuitBreaker.d.ts","sourceRoot":"","sources":["../../src/core/LlmCircuitBreaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,6HAA6H;AAC7H,qBAAa,cAAe,SAAQ,KAAK;IAEV,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO;IAD7D,QAAQ,CAAC,WAAW,EAAG,IAAI,CAAU;gBACzB,OAAO,EAAE,MAAM,EAAW,aAAa,CAAC,EAAE,OAAO,YAAA;CAI9D;AAED,wHAAwH;AACxH,qBAAa,mBAAoB,SAAQ,KAAK;IAEhC,QAAQ,CAAC,YAAY,EAAE,MAAM;IADzC,QAAQ,CAAC,WAAW,EAAG,IAAI,CAAU;gBAChB,YAAY,EAAE,MAAM;CAM1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAyC5E;AAED,KAAK,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAEpD,MAAM,WAAW,wBAAwB;IACvC,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4GAA4G;IAC5G,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,iEAAiE;IACjE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,KAAK,EAAE,OAAO,CAAC;IACf,gEAAgE;IAChE,KAAK,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAID,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAyB;gBAEjC,IAAI,GAAE,wBAA6B;IAO/C,2EAA2E;IAC3E,SAAS,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAK7D;;;;;;;OAOG;IACH,OAAO,IAAI,eAAe;IA+B1B;;;;;;OAMG;IACH,UAAU,IAAI,IAAI;IAUlB;;;OAGG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAmBnC,MAAM,IAAI,uBAAuB;IAajC,gFAAgF;IAChF,KAAK,IAAI,IAAI;CAMd;AAID,sFAAsF;AACtF,wBAAgB,oBAAoB,IAAI,iBAAiB,CAGxD;AAED,yEAAyE;AACzE,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAE7F;AAED,yEAAyE;AACzE,wBAAgB,iCAAiC,IAAI,IAAI,CAExD"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LlmCircuitBreaker — account-global reactive circuit breaker for LLM calls.
|
|
3
|
+
*
|
|
4
|
+
* Motivating incident (2026-05-28): a wild agent burned $452/$455 in usage
|
|
5
|
+
* credits because PromptGate's per-tick Haiku detection loop kept calling
|
|
6
|
+
* `claude -p` while the account was over its weekly spend limit. The loop's
|
|
7
|
+
* error handler swallowed the rate-limit error with no backoff, so every
|
|
8
|
+
* monitor tick spawned another doomed (or, with auto-reload, freshly-billed)
|
|
9
|
+
* subprocess. There is NO legitimate reason to keep calling the LLM once the
|
|
10
|
+
* provider has told us we are over our limit — every further call is either
|
|
11
|
+
* wasted (rejected) or actively harmful (auto-reload refuels the burn).
|
|
12
|
+
*
|
|
13
|
+
* This breaker reacts to the provider's OWN rate-limit signal in milliseconds,
|
|
14
|
+
* which is distinct from (and complementary to) the volume-based
|
|
15
|
+
* token-burn-detection system (BurnDetector + LlmRateGate), which reacts to
|
|
16
|
+
* statistical token-share over a ~30-minute window. Where the burn system
|
|
17
|
+
* answers "this path is spending too much," the breaker answers "the provider
|
|
18
|
+
* just said we're rate-limited — stop."
|
|
19
|
+
*
|
|
20
|
+
* Scope: ACCOUNT-GLOBAL. A usage/rate limit applies to the whole subscription,
|
|
21
|
+
* not one session or one component, so a single shared breaker pauses every
|
|
22
|
+
* LLM-backed feature (PromptGate, PresenceProxy, PromiseBeacon, sentinels,
|
|
23
|
+
* reviewers) at once. It is wired structurally at the IntelligenceProvider
|
|
24
|
+
* construction chokepoint via CircuitBreakingIntelligenceProvider, so no
|
|
25
|
+
* consumer has to remember to consult it (Structure > Willpower).
|
|
26
|
+
*
|
|
27
|
+
* State: in-memory, process-local. A restart resets it — which is correct: the
|
|
28
|
+
* first call after restart probes the provider, and if still limited the
|
|
29
|
+
* breaker re-trips immediately.
|
|
30
|
+
*/
|
|
31
|
+
/** Thrown by a provider (or re-thrown by the decorator) when the underlying LLM call was rejected for a usage/rate limit. */
|
|
32
|
+
export class RateLimitError extends Error {
|
|
33
|
+
providerError;
|
|
34
|
+
isRateLimit = true;
|
|
35
|
+
constructor(message, providerError) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.providerError = providerError;
|
|
38
|
+
this.name = 'RateLimitError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Thrown by the decorator when the breaker is open — the call was refused WITHOUT spawning the underlying provider. */
|
|
42
|
+
export class LlmCircuitOpenError extends Error {
|
|
43
|
+
retryAfterMs;
|
|
44
|
+
circuitOpen = true;
|
|
45
|
+
constructor(retryAfterMs) {
|
|
46
|
+
super(`LLM circuit breaker open — provider rate-limited; pausing LLM-backed work, retry in ~${Math.ceil(retryAfterMs / 1000)}s`);
|
|
47
|
+
this.retryAfterMs = retryAfterMs;
|
|
48
|
+
this.name = 'LlmCircuitOpenError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Classify whether an error message indicates a provider usage/rate/spend
|
|
53
|
+
* limit (as opposed to a timeout, a parse error, a network blip, etc.).
|
|
54
|
+
*
|
|
55
|
+
* Pure + exported so it is unit-testable against representative CLI error
|
|
56
|
+
* strings from both `claude -p` and `codex exec`. Deliberately conservative:
|
|
57
|
+
* a false positive trips the breaker and degrades all LLM-backed features to
|
|
58
|
+
* heuristic-only for one open window, so we match only language that is
|
|
59
|
+
* unambiguously about hitting a usage/spend/quota ceiling — never generic
|
|
60
|
+
* "error"/"failed"/timeout text.
|
|
61
|
+
*/
|
|
62
|
+
export function isRateLimitError(message) {
|
|
63
|
+
if (!message)
|
|
64
|
+
return false;
|
|
65
|
+
const m = message.toLowerCase();
|
|
66
|
+
// Explicit HTTP status codes for limit / billing rejections.
|
|
67
|
+
if (/\b429\b/.test(m))
|
|
68
|
+
return true; // Too Many Requests
|
|
69
|
+
if (/\b402\b/.test(m))
|
|
70
|
+
return true; // Payment Required
|
|
71
|
+
const phrases = [
|
|
72
|
+
'rate limit',
|
|
73
|
+
'rate-limit',
|
|
74
|
+
'rate_limit',
|
|
75
|
+
'ratelimit',
|
|
76
|
+
'too many requests',
|
|
77
|
+
'usage limit',
|
|
78
|
+
'usage_limit',
|
|
79
|
+
'usage-limit',
|
|
80
|
+
'limit reached',
|
|
81
|
+
'limit will reset',
|
|
82
|
+
'limit resets',
|
|
83
|
+
'reached your limit',
|
|
84
|
+
'payment required',
|
|
85
|
+
'out of credit',
|
|
86
|
+
'credit balance is too low',
|
|
87
|
+
'insufficient credit',
|
|
88
|
+
'insufficient quota',
|
|
89
|
+
'quota exceeded',
|
|
90
|
+
'exceeded your',
|
|
91
|
+
'spend limit',
|
|
92
|
+
'spending limit',
|
|
93
|
+
'billing',
|
|
94
|
+
];
|
|
95
|
+
if (phrases.some((p) => m.includes(p)))
|
|
96
|
+
return true;
|
|
97
|
+
// "exceeded ... (limit|quota|usage)" with words in between.
|
|
98
|
+
if (/exceed(?:ed|s)?\b[^.\n]{0,40}(?:limit|quota|usage|credit)/.test(m))
|
|
99
|
+
return true;
|
|
100
|
+
// Bare "quota" is a strong-enough signal on its own (Anthropic/OpenAI both use it).
|
|
101
|
+
if (/\bquota\b/.test(m))
|
|
102
|
+
return true;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const DEFAULT_OPEN_MS = 15 * 60_000; // 15 minutes
|
|
106
|
+
export class LlmCircuitBreaker {
|
|
107
|
+
state = 'closed';
|
|
108
|
+
openUntil = 0;
|
|
109
|
+
firstOpenedAt = 0;
|
|
110
|
+
probeInFlight = false;
|
|
111
|
+
tripCount = 0;
|
|
112
|
+
lastReason = null;
|
|
113
|
+
lastTrippedAt = null;
|
|
114
|
+
openMs;
|
|
115
|
+
enabled;
|
|
116
|
+
now;
|
|
117
|
+
log;
|
|
118
|
+
constructor(opts = {}) {
|
|
119
|
+
this.openMs = opts.openMs && opts.openMs > 0 ? opts.openMs : DEFAULT_OPEN_MS;
|
|
120
|
+
this.enabled = opts.enabled ?? true;
|
|
121
|
+
this.now = opts.now ?? (() => Date.now());
|
|
122
|
+
this.log = opts.log ?? ((line) => console.warn(line));
|
|
123
|
+
}
|
|
124
|
+
/** Runtime reconfiguration (called once at server startup from config). */
|
|
125
|
+
configure(opts) {
|
|
126
|
+
if (typeof opts.openMs === 'number' && opts.openMs > 0)
|
|
127
|
+
this.openMs = opts.openMs;
|
|
128
|
+
if (typeof opts.enabled === 'boolean')
|
|
129
|
+
this.enabled = opts.enabled;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Gate a call. Call BEFORE invoking the underlying provider. When it returns
|
|
133
|
+
* { allow: false }, the caller MUST NOT call the provider — throw
|
|
134
|
+
* LlmCircuitOpenError instead (the whole point is to avoid the spend).
|
|
135
|
+
*
|
|
136
|
+
* Exactly one probe is admitted when transitioning out of the open window;
|
|
137
|
+
* concurrent callers are blocked until that probe resolves.
|
|
138
|
+
*/
|
|
139
|
+
acquire() {
|
|
140
|
+
if (!this.enabled)
|
|
141
|
+
return { allow: true, probe: false, retryAfterMs: 0 };
|
|
142
|
+
const now = this.now();
|
|
143
|
+
if (this.state === 'closed') {
|
|
144
|
+
return { allow: true, probe: false, retryAfterMs: 0 };
|
|
145
|
+
}
|
|
146
|
+
if (this.state === 'open') {
|
|
147
|
+
if (now >= this.openUntil) {
|
|
148
|
+
// Window elapsed → admit exactly one probe.
|
|
149
|
+
this.state = 'half-open';
|
|
150
|
+
this.probeInFlight = true;
|
|
151
|
+
this.log(`[llm-circuit] half-open: admitting one probe call after ~${Math.round((now - this.firstOpenedAt) / 1000)}s open`);
|
|
152
|
+
return { allow: true, probe: true, retryAfterMs: 0 };
|
|
153
|
+
}
|
|
154
|
+
return { allow: false, probe: false, retryAfterMs: this.openUntil - now };
|
|
155
|
+
}
|
|
156
|
+
// half-open
|
|
157
|
+
if (this.probeInFlight) {
|
|
158
|
+
// A probe is already out — block everyone else until it resolves.
|
|
159
|
+
return { allow: false, probe: false, retryAfterMs: Math.max(0, this.openUntil - now) };
|
|
160
|
+
}
|
|
161
|
+
// No probe in flight (defensive) — admit one.
|
|
162
|
+
this.probeInFlight = true;
|
|
163
|
+
return { allow: true, probe: true, retryAfterMs: 0 };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Record that the underlying call returned (or threw a non-rate-limit
|
|
167
|
+
* error). Either way the rate-limit condition is not currently present, so
|
|
168
|
+
* the breaker closes. A non-rate-limit error is handled by the caller's own
|
|
169
|
+
* fallback — it is not the breaker's concern, and staying open on unrelated
|
|
170
|
+
* errors would needlessly keep all LLM features down.
|
|
171
|
+
*/
|
|
172
|
+
onResolved() {
|
|
173
|
+
if (!this.enabled)
|
|
174
|
+
return;
|
|
175
|
+
if (this.state !== 'closed') {
|
|
176
|
+
this.log(`[llm-circuit] closing: provider responded (was ${this.state})`);
|
|
177
|
+
}
|
|
178
|
+
this.state = 'closed';
|
|
179
|
+
this.probeInFlight = false;
|
|
180
|
+
this.openUntil = 0;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Record that the underlying call was rejected for a usage/rate limit. Opens
|
|
184
|
+
* (or re-extends) the breaker for a full window.
|
|
185
|
+
*/
|
|
186
|
+
onRateLimited(reason) {
|
|
187
|
+
if (!this.enabled)
|
|
188
|
+
return;
|
|
189
|
+
const now = this.now();
|
|
190
|
+
if (this.state === 'closed') {
|
|
191
|
+
this.firstOpenedAt = now;
|
|
192
|
+
}
|
|
193
|
+
this.tripCount += 1;
|
|
194
|
+
this.lastReason = reason.slice(0, 200);
|
|
195
|
+
this.lastTrippedAt = now;
|
|
196
|
+
this.state = 'open';
|
|
197
|
+
this.openUntil = now + this.openMs;
|
|
198
|
+
this.probeInFlight = false;
|
|
199
|
+
this.log(`[llm-circuit] OPEN: provider rate-limited — pausing ALL LLM-backed work for ~${Math.round(this.openMs / 1000)}s (trip #${this.tripCount}); reason: ${this.lastReason}`);
|
|
200
|
+
}
|
|
201
|
+
status() {
|
|
202
|
+
const now = this.now();
|
|
203
|
+
return {
|
|
204
|
+
state: this.state,
|
|
205
|
+
enabled: this.enabled,
|
|
206
|
+
openUntil: this.state === 'closed' ? null : this.openUntil,
|
|
207
|
+
retryAfterMs: this.state === 'open' ? Math.max(0, this.openUntil - now) : 0,
|
|
208
|
+
tripCount: this.tripCount,
|
|
209
|
+
lastReason: this.lastReason,
|
|
210
|
+
lastTrippedAt: this.lastTrippedAt,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Reset to a clean closed state. For tests + an explicit operator override. */
|
|
214
|
+
reset() {
|
|
215
|
+
this.state = 'closed';
|
|
216
|
+
this.openUntil = 0;
|
|
217
|
+
this.firstOpenedAt = 0;
|
|
218
|
+
this.probeInFlight = false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
let _singleton = null;
|
|
222
|
+
/** Process-wide account-global breaker. All wrapped providers share this instance. */
|
|
223
|
+
export function getLlmCircuitBreaker() {
|
|
224
|
+
if (!_singleton)
|
|
225
|
+
_singleton = new LlmCircuitBreaker();
|
|
226
|
+
return _singleton;
|
|
227
|
+
}
|
|
228
|
+
/** Configure the singleton at startup. Safe to call before first use. */
|
|
229
|
+
export function configureLlmCircuitBreaker(opts) {
|
|
230
|
+
getLlmCircuitBreaker().configure(opts);
|
|
231
|
+
}
|
|
232
|
+
/** Test-only: drop the singleton so a fresh one is built next access. */
|
|
233
|
+
export function __resetLlmCircuitBreakerSingleton() {
|
|
234
|
+
_singleton = null;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=LlmCircuitBreaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LlmCircuitBreaker.js","sourceRoot":"","sources":["../../src/core/LlmCircuitBreaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligenceProviderFactory.d.ts","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"intelligenceProviderFactory.d.ts","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAMvD;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,WAAW,CAAC;AAEhE,MAAM,WAAW,gCAAgC;IAC/C;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,oBAAoB,GAAG,IAAI,CA+B7B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,qBAAqB,GAAG,IAAI,CAMnG"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { detectClaudePath, detectCodexPath } from './Config.js';
|
|
17
17
|
import { ClaudeCliIntelligenceProvider } from './ClaudeCliIntelligenceProvider.js';
|
|
18
18
|
import { CodexCliIntelligenceProvider } from './CodexCliIntelligenceProvider.js';
|
|
19
|
+
import { wrapIntelligenceWithCircuitBreaker } from './CircuitBreakingIntelligenceProvider.js';
|
|
19
20
|
/**
|
|
20
21
|
* Build an IntelligenceProvider for the given framework. Returns null
|
|
21
22
|
* if the required binary can't be located. The caller decides what
|
|
@@ -30,21 +31,25 @@ import { CodexCliIntelligenceProvider } from './CodexCliIntelligenceProvider.js'
|
|
|
30
31
|
*/
|
|
31
32
|
export function buildIntelligenceProvider(options = {}) {
|
|
32
33
|
const framework = options.framework ?? 'claude-code';
|
|
34
|
+
// Every provider the factory hands out is wrapped with the account-global
|
|
35
|
+
// LLM circuit breaker (CircuitBreakingIntelligenceProvider). A closed breaker
|
|
36
|
+
// is a transparent passthrough, so this is always safe; it only acts when the
|
|
37
|
+
// provider reports a usage/rate limit. See LlmCircuitBreaker for the why.
|
|
33
38
|
switch (framework) {
|
|
34
39
|
case 'claude-code': {
|
|
35
40
|
const path = options.binaryPath ?? detectClaudePath();
|
|
36
41
|
if (!path)
|
|
37
42
|
return null;
|
|
38
|
-
return new ClaudeCliIntelligenceProvider(path);
|
|
43
|
+
return wrapIntelligenceWithCircuitBreaker(new ClaudeCliIntelligenceProvider(path));
|
|
39
44
|
}
|
|
40
45
|
case 'codex-cli': {
|
|
41
46
|
const path = options.binaryPath ?? detectCodexPath();
|
|
42
47
|
if (!path)
|
|
43
48
|
return null;
|
|
44
|
-
return new CodexCliIntelligenceProvider({
|
|
49
|
+
return wrapIntelligenceWithCircuitBreaker(new CodexCliIntelligenceProvider({
|
|
45
50
|
codexPath: path,
|
|
46
51
|
...(options.workingDirectory ? { workingDirectory: options.workingDirectory } : {}),
|
|
47
|
-
});
|
|
52
|
+
}));
|
|
48
53
|
}
|
|
49
54
|
default: {
|
|
50
55
|
// Exhaustiveness check — extending IntelligenceFramework without
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligenceProviderFactory.js","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"intelligenceProviderFactory.js","sourceRoot":"","sources":["../../src/core/intelligenceProviderFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,EAAE,kCAAkC,EAAE,MAAM,0CAA0C,CAAC;AA0B9F;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAA4C,EAAE;IAE9C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IAErD,0EAA0E;IAC1E,8EAA8E;IAC9E,8EAA8E;IAC9E,0EAA0E;IAC1E,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,IAAI,gBAAgB,EAAE,CAAC;YACtD,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,OAAO,kCAAkC,CAAC,IAAI,6BAA6B,CAAC,IAAI,CAAC,CAAC,CAAC;QACrF,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,IAAI,eAAe,EAAE,CAAC;YACrD,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,OAAO,kCAAkC,CACvC,IAAI,4BAA4B,CAAC;gBAC/B,SAAS,EAAE,IAAI;gBACf,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpF,CAAC,CACH,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,CAAC;YACR,iEAAiE;YACjE,sCAAsC;YACtC,MAAM,WAAW,GAAU,SAAS,CAAC;YACrC,KAAK,WAAW,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACnE,MAAM,GAAG,GAAG,GAAG,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1D,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC;IACpE,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,WAAW,CAAC;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC"}
|