@kill-switch/agent-guard 0.1.2 → 0.1.3
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/README.md +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/limits.d.ts +11 -0
- package/dist/limits.js +30 -2
- package/dist/proxy.js +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,10 @@ Tune the thresholds (0–1 utilization) if the defaults are too eager:
|
|
|
152
152
|
| `--5h-soft` / `--5h-danger` | 5-hour warn / danger utilization | 0.7 / 0.9 |
|
|
153
153
|
| `--burn-ratio` | pace multiplier that triggers a warning | 1.5 |
|
|
154
154
|
|
|
155
|
+
The first time the proxy sees the `unified-*` headers it writes the raw values once to
|
|
156
|
+
`~/.kill-switch/agent-guard/events.jsonl` (`kind: "unified-headers-observed"`) — so you can
|
|
157
|
+
confirm Anthropic's exact value formats with a single `cat`.
|
|
158
|
+
|
|
155
159
|
> Because subscription mode is alert-only, the "don't run both hook *and* proxy" caveat below
|
|
156
160
|
> doesn't bite here — running Claude Code through the proxy is exactly what feeds the limit
|
|
157
161
|
> headers, and dollars no longer gate anything.
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { dispatchAlert, type AlertEvent, type AlertLevel } from "./alert.js";
|
|
|
17
17
|
export { startProxy, resolveUpstream, type ProxyOptions } from "./proxy.js";
|
|
18
18
|
export { runHook } from "./hook.js";
|
|
19
19
|
export { buildStatusReport, formatLimitsLines, type StatusReport, type LimitsReport } from "./report.js";
|
|
20
|
-
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, WINDOW_MS, type LimitSnapshot, type WindowState, type LimitsState, type LimitWindow, type HeaderGetter, } from "./limits.js";
|
|
20
|
+
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, unifiedHeaderDump, logUnifiedHeaders, WINDOW_MS, type LimitSnapshot, type WindowState, type LimitsState, type LimitWindow, type HeaderGetter, } from "./limits.js";
|
|
21
21
|
export { assessWindow, assessSnapshot, worstLevel, type PacingAssessment, type PacingLevel, type PacingThresholds, } from "./pacing.js";
|
|
22
22
|
export { estimateSnapshot, isEstimated, TIER_BUDGETS, type PlanTier, type TierBudget, } from "./estimate.js";
|
|
23
23
|
export { installHook, setBudget, setLimits, resetLedger, type InstallOptions, type InstallResult, type BudgetPatch, type LimitsPatch, } from "./ops.js";
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export { dispatchAlert } from "./alert.js";
|
|
|
17
17
|
export { startProxy, resolveUpstream } from "./proxy.js";
|
|
18
18
|
export { runHook } from "./hook.js";
|
|
19
19
|
export { buildStatusReport, formatLimitsLines } from "./report.js";
|
|
20
|
-
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, WINDOW_MS, } from "./limits.js";
|
|
20
|
+
export { parseUnifiedHeaders, parseUtilization, parseReset, recordHeaders, loadLimitsState, saveLimitsState, emptyLimitsState, limitNotifyKey, unifiedHeaderDump, logUnifiedHeaders, WINDOW_MS, } from "./limits.js";
|
|
21
21
|
export { assessWindow, assessSnapshot, worstLevel, } from "./pacing.js";
|
|
22
22
|
export { estimateSnapshot, isEstimated, TIER_BUDGETS, } from "./estimate.js";
|
|
23
23
|
export { installHook, setBudget, setLimits, resetLedger, } from "./ops.js";
|
package/dist/limits.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface LimitsState {
|
|
|
49
49
|
snapshot: LimitSnapshot | null;
|
|
50
50
|
/** Dedup flags so a given window/level/reset only alerts once. */
|
|
51
51
|
notified: Record<string, boolean>;
|
|
52
|
+
/** Epoch ms we first logged the raw unified-* headers (write-once diagnostic). */
|
|
53
|
+
headersLoggedAt?: number;
|
|
52
54
|
}
|
|
53
55
|
/** Nominal window durations, used for pacing math when a reset time is unknown. */
|
|
54
56
|
export declare const WINDOW_MS: Record<LimitWindow, number>;
|
|
@@ -81,3 +83,12 @@ export declare function parseReset(raw: string | null | undefined, now: number):
|
|
|
81
83
|
export declare function parseUnifiedHeaders(h: HeaderGetter, now: number): LimitSnapshot | null;
|
|
82
84
|
/** Stable dedup key for a pacing alert: re-alerts when the window resets. */
|
|
83
85
|
export declare function limitNotifyKey(window: LimitWindow, level: string, resetAt: number | null): string;
|
|
86
|
+
/**
|
|
87
|
+
* Pull every `anthropic-ratelimit-unified-*` header out of a raw record, verbatim.
|
|
88
|
+
* Used for the write-once diagnostic — Anthropic's value *formats* (fraction vs.
|
|
89
|
+
* percent, ISO vs. epoch reset) aren't fully documented, so capturing the raw
|
|
90
|
+
* strings the first time we see them makes verification a single `cat` away.
|
|
91
|
+
*/
|
|
92
|
+
export declare function unifiedHeaderDump(rec: Record<string, string | string[] | undefined>): Record<string, string>;
|
|
93
|
+
/** Append a one-time raw-header diagnostic to events.jsonl. Best-effort, never throws. */
|
|
94
|
+
export declare function logUnifiedHeaders(dump: Record<string, string>, now: number): void;
|
package/dist/limits.js
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* either a 0–1 fraction or a 0–100 percent; reset is accepted as an ISO 8601
|
|
23
23
|
* timestamp, an epoch (s or ms), or a relative seconds-until-reset.
|
|
24
24
|
*/
|
|
25
|
-
import { readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
26
|
-
import { limitsPath, ensureGuardDir } from "./config.js";
|
|
25
|
+
import { readFileSync, writeFileSync, renameSync, appendFileSync } from "node:fs";
|
|
26
|
+
import { limitsPath, eventsPath, ensureGuardDir } from "./config.js";
|
|
27
27
|
/** Nominal window durations, used for pacing math when a reset time is unknown. */
|
|
28
28
|
export const WINDOW_MS = {
|
|
29
29
|
"5h": 5 * 60 * 60 * 1000,
|
|
@@ -41,6 +41,7 @@ export function loadLimitsState() {
|
|
|
41
41
|
subscriptionDetected: data.subscriptionDetected ?? false,
|
|
42
42
|
snapshot: data.snapshot ?? null,
|
|
43
43
|
notified: data.notified ?? {},
|
|
44
|
+
headersLoggedAt: data.headersLoggedAt,
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -131,3 +132,30 @@ export function parseUnifiedHeaders(h, now) {
|
|
|
131
132
|
export function limitNotifyKey(window, level, resetAt) {
|
|
132
133
|
return `${window}:${level}:${resetAt ?? 0}`;
|
|
133
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Pull every `anthropic-ratelimit-unified-*` header out of a raw record, verbatim.
|
|
137
|
+
* Used for the write-once diagnostic — Anthropic's value *formats* (fraction vs.
|
|
138
|
+
* percent, ISO vs. epoch reset) aren't fully documented, so capturing the raw
|
|
139
|
+
* strings the first time we see them makes verification a single `cat` away.
|
|
140
|
+
*/
|
|
141
|
+
export function unifiedHeaderDump(rec) {
|
|
142
|
+
const out = {};
|
|
143
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
144
|
+
if (v == null)
|
|
145
|
+
continue;
|
|
146
|
+
const key = k.toLowerCase();
|
|
147
|
+
if (key.startsWith("anthropic-ratelimit-unified"))
|
|
148
|
+
out[key] = Array.isArray(v) ? v.join(", ") : v;
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
/** Append a one-time raw-header diagnostic to events.jsonl. Best-effort, never throws. */
|
|
153
|
+
export function logUnifiedHeaders(dump, now) {
|
|
154
|
+
try {
|
|
155
|
+
ensureGuardDir();
|
|
156
|
+
appendFileSync(eventsPath(), JSON.stringify({ ts: now, kind: "unified-headers-observed", headers: dump }) + "\n");
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* diagnostic only */
|
|
160
|
+
}
|
|
161
|
+
}
|
package/dist/proxy.js
CHANGED
|
@@ -24,7 +24,7 @@ import { loadLedger, saveLedger, addSessionCost, rollingDailyCost, prune, } from
|
|
|
24
24
|
import { evaluate } from "./budget.js";
|
|
25
25
|
import { dispatchAlert } from "./alert.js";
|
|
26
26
|
import { assertSafeEndpoint, warnIfUnexpectedHost } from "./net.js";
|
|
27
|
-
import { parseUnifiedHeaders, loadLimitsState, saveLimitsState, limitNotifyKey, } from "./limits.js";
|
|
27
|
+
import { parseUnifiedHeaders, recordHeaders, unifiedHeaderDump, logUnifiedHeaders, loadLimitsState, saveLimitsState, limitNotifyKey, } from "./limits.js";
|
|
28
28
|
import { assessSnapshot, worstLevel } from "./pacing.js";
|
|
29
29
|
const UPSTREAMS = {
|
|
30
30
|
anthropic: "https://api.anthropic.com",
|
|
@@ -145,10 +145,20 @@ function meter(cfg, ledger, sessionId, parsed, now) {
|
|
|
145
145
|
* the real wall).
|
|
146
146
|
*/
|
|
147
147
|
function captureLimits(cfg, headers, sessionId, now) {
|
|
148
|
-
|
|
148
|
+
// Flatten to a lowercased record so we can both parse and dump the raw values.
|
|
149
|
+
const rec = {};
|
|
150
|
+
headers.forEach((v, k) => {
|
|
151
|
+
rec[k.toLowerCase()] = v;
|
|
152
|
+
});
|
|
153
|
+
const snap = parseUnifiedHeaders(recordHeaders(rec), now);
|
|
149
154
|
if (!snap)
|
|
150
155
|
return false;
|
|
151
156
|
const state = loadLimitsState();
|
|
157
|
+
// Write-once raw-header diagnostic for format verification (`cat events.jsonl`).
|
|
158
|
+
if (!state.headersLoggedAt) {
|
|
159
|
+
logUnifiedHeaders(unifiedHeaderDump(rec), now);
|
|
160
|
+
state.headersLoggedAt = now;
|
|
161
|
+
}
|
|
152
162
|
state.subscriptionDetected = true;
|
|
153
163
|
state.snapshot = snap;
|
|
154
164
|
const assessments = assessSnapshot(snap, cfg.limits, now);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kill-switch/agent-guard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Kill Switch for coding agents — stop runaway Claude Code / Cursor / Aider sessions from racking up an LLM bill. Native hook + token-metering proxy with per-session and daily-rolling budgets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|