@vanillagreen/pi-claude-bridge 1.9.0 → 3.2.2
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 +43 -125
- package/bundle/connector-inventory.js +16 -3
- package/bundle/index.js +3743 -1810
- package/package.json +14 -23
- package/src/account-host.ts +112 -0
- package/src/account-router.ts +272 -0
- package/src/agents-md.ts +54 -10
- package/src/assistant-stream.ts +472 -66
- package/src/auth-presence.ts +6 -50
- package/src/bridge-commands.ts +86 -0
- package/src/bridge-state.ts +200 -15
- package/src/config.ts +170 -20
- package/src/connector-audit.ts +203 -0
- package/src/connector-cache.ts +148 -0
- package/src/connector-inventory.ts +66 -7
- package/src/connector-runtime.ts +158 -0
- package/src/connectors.ts +406 -19
- package/src/consume-query.ts +312 -0
- package/src/convert.ts +20 -10
- package/src/debug.ts +64 -6
- package/src/index.ts +901 -676
- package/src/models.ts +0 -7
- package/src/native-provider.ts +94 -0
- package/src/prompt-context.ts +5 -1
- package/src/query-options.ts +183 -0
- package/src/query-state.ts +490 -25
- package/src/query-teardown.ts +45 -0
- package/src/rate-limit.ts +48 -13
- package/src/request-lane.ts +36 -0
- package/src/sdk-query.ts +16 -0
- package/src/session-persistence.ts +370 -50
- package/src/tool-pairing-audit.ts +117 -0
- package/src/typebox-to-zod.ts +9 -3
package/src/rate-limit.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
+
import { USAGE_LIMIT_ERROR_PREFIXES } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
|
|
1
3
|
export const RATE_LIMIT_AUTO_RESUME_EVENT = "vstack:rate-limit";
|
|
2
4
|
export const RATE_LIMIT_TOKEN = "\x1b[31m[rate-limit]\x1b[39m";
|
|
3
5
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
// The SDK export is @alpha — degrade to "no match" (pre-0.3.220 behavior) if a
|
|
7
|
+
// future release drops it, instead of crashing message classification.
|
|
8
|
+
const USAGE_LIMIT_PREFIXES: readonly string[] = Array.isArray(USAGE_LIMIT_ERROR_PREFIXES as unknown)
|
|
9
|
+
? USAGE_LIMIT_ERROR_PREFIXES
|
|
10
|
+
: [];
|
|
11
|
+
|
|
12
|
+
function coerceMessageText(value: unknown): string {
|
|
13
|
+
if (typeof value === "string") return value;
|
|
14
|
+
if (value instanceof Error) return value.message;
|
|
15
|
+
try { return JSON.stringify(value ?? ""); }
|
|
16
|
+
catch { return String(value); }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Broad test: any "a usage limit was genuinely reached" message, matched
|
|
20
|
+
* against the CLI's own copy (SDK `USAGE_LIMIT_ERROR_PREFIXES`, e.g. "You've
|
|
21
|
+
* hit your weekly limit…"). Substring rather than prefix match because the
|
|
22
|
+
* text usually arrives embedded in a result payload's errors array. */
|
|
23
|
+
export function isUsageLimitMessage(value: unknown): boolean {
|
|
24
|
+
const text = coerceMessageText(value);
|
|
25
|
+
return USAGE_LIMIT_PREFIXES.some((prefix) => text.includes(prefix));
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
export function uniqueNonEmptyLines(values: unknown[]): string[] {
|
|
@@ -24,9 +37,27 @@ export function uniqueNonEmptyLines(values: unknown[]): string[] {
|
|
|
24
37
|
return out;
|
|
25
38
|
}
|
|
26
39
|
|
|
40
|
+
/** Epoch milliseconds from an SDK reset timestamp, or undefined.
|
|
41
|
+
* `SDKRateLimitInfo.resetsAt` is a bare number in epoch SECONDS (measured:
|
|
42
|
+
* treating it as ms rendered "resets Jan 21, 1970" for a Jul 2026 reset).
|
|
43
|
+
* The unit is undocumented, so detect by magnitude — epoch seconds stay below
|
|
44
|
+
* 1e12 until the year 33658, epoch ms passed 1e12 in 2001. A numeric STRING
|
|
45
|
+
* gets the same magnitude treatment (rate_limit_event payloads have carried
|
|
46
|
+
* both), and anything else falls back to Date.parse for ISO strings. */
|
|
47
|
+
export function resetTimestampMs(value: unknown): number | undefined {
|
|
48
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
49
|
+
return Math.abs(value) < 1e12 ? value * 1000 : value;
|
|
50
|
+
}
|
|
51
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
52
|
+
const numeric = Number(value);
|
|
53
|
+
if (Number.isFinite(numeric)) return Math.abs(numeric) < 1e12 ? numeric * 1000 : numeric;
|
|
54
|
+
const parsed = Date.parse(value);
|
|
55
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
27
58
|
export function formatResetTimestamp(value: unknown): string {
|
|
28
|
-
const parsed =
|
|
29
|
-
if (
|
|
59
|
+
const parsed = resetTimestampMs(value);
|
|
60
|
+
if (parsed === undefined) return "unknown";
|
|
30
61
|
return new Date(parsed).toLocaleString(undefined, {
|
|
31
62
|
day: "numeric",
|
|
32
63
|
hour: "numeric",
|
|
@@ -44,8 +75,12 @@ export function normalizeRateLimitUtilization(value: unknown): number | undefine
|
|
|
44
75
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
|
|
45
76
|
if (value === 0) return 0;
|
|
46
77
|
// Claude SDK payloads have appeared as both fractions and percentages.
|
|
47
|
-
// Exact 1 is unit-ambiguous (1% vs 100%)
|
|
48
|
-
|
|
78
|
+
// Exact 1 is unit-ambiguous (1% vs 100%); read it as the fractional form
|
|
79
|
+
// (100%) because that is the fail-closed direction — under the fraction
|
|
80
|
+
// convention 1 is the fully-consumed case the warning exists to surface,
|
|
81
|
+
// while under the percent convention 1% sits below the threshold anyway,
|
|
82
|
+
// so nothing is lost by warning (VST-16).
|
|
83
|
+
if (value > 0 && value <= 1) return value * 100;
|
|
49
84
|
if (value > 1 && value <= 100) return value;
|
|
50
85
|
return undefined;
|
|
51
86
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Selects the Pi agent session whose provider request is currently executing.
|
|
5
|
+
*
|
|
6
|
+
* Pi forwards a stable `SimpleStreamOptions.sessionId` on every model request,
|
|
7
|
+
* including tool-result continuations. The bridge may serve the parent agent
|
|
8
|
+
* and several in-process subagents concurrently, so process-global query state
|
|
9
|
+
* cannot identify the request that a callback belongs to. AsyncLocalStorage
|
|
10
|
+
* keeps that identity attached to every promise, SDK iterator, and timer born
|
|
11
|
+
* during the provider call without threading the id through every helper.
|
|
12
|
+
*/
|
|
13
|
+
const REQUEST_LANE_SYMBOL = Symbol.for("vstack.pi.claude-bridge.request-lane.v1");
|
|
14
|
+
|
|
15
|
+
function requestLaneStorage(): AsyncLocalStorage<string> {
|
|
16
|
+
const host = globalThis as Record<symbol, unknown>;
|
|
17
|
+
let storage = host[REQUEST_LANE_SYMBOL] as AsyncLocalStorage<string> | undefined;
|
|
18
|
+
if (!storage) {
|
|
19
|
+
storage = new AsyncLocalStorage<string>();
|
|
20
|
+
host[REQUEST_LANE_SYMBOL] = storage;
|
|
21
|
+
}
|
|
22
|
+
return storage;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Run `callback` in the lane for `sessionId`. `undefined` selects the default
|
|
26
|
+
* (direct-host) lane even when a named lane is active — a listener that fires
|
|
27
|
+
* inside another request's context must not inherit that request's lane. */
|
|
28
|
+
export function runInRequestLane<T>(sessionId: string | undefined, callback: () => T): T {
|
|
29
|
+
const storage = requestLaneStorage();
|
|
30
|
+
if (sessionId !== undefined) return storage.run(sessionId, callback);
|
|
31
|
+
return storage.getStore() === undefined ? callback() : storage.exit(callback);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function currentRequestLaneId(): string | undefined {
|
|
35
|
+
return requestLaneStorage().getStore();
|
|
36
|
+
}
|
package/src/sdk-query.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Shared mutable SDK query factory + its test seam. In its own module so both
|
|
2
|
+
// the provider entry (index.ts) and the account host spawn children through
|
|
3
|
+
// the same seam — tests swap the factory once and every spawn path honors it.
|
|
4
|
+
|
|
5
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
6
|
+
|
|
7
|
+
export type SdkQueryFactory = typeof query;
|
|
8
|
+
|
|
9
|
+
// ESM live binding: importers read the CURRENT factory at call time.
|
|
10
|
+
export let sdkQueryFactory: SdkQueryFactory = query;
|
|
11
|
+
|
|
12
|
+
/** Test seam for exercising the real bridge retry/session orchestration without
|
|
13
|
+
* spending Claude usage. Production never calls this. */
|
|
14
|
+
export function __testSetSdkQueryFactory(factory?: SdkQueryFactory): void {
|
|
15
|
+
sdkQueryFactory = factory ?? query;
|
|
16
|
+
}
|