@vanillagreen/pi-claude-bridge 2.0.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 +40 -135
- package/bundle/connector-inventory.js +6 -3
- package/bundle/index.js +2432 -1123
- package/package.json +11 -20
- 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 +189 -50
- package/src/bridge-commands.ts +86 -0
- package/src/bridge-state.ts +157 -14
- package/src/config.ts +170 -20
- package/src/connector-cache.ts +43 -13
- package/src/connector-inventory.ts +14 -7
- package/src/connector-runtime.ts +158 -0
- package/src/connectors.ts +286 -40
- package/src/consume-query.ts +312 -0
- package/src/convert.ts +6 -10
- package/src/debug.ts +64 -6
- package/src/index.ts +769 -702
- package/src/models.ts +0 -7
- package/src/native-provider.ts +9 -4
- package/src/prompt-context.ts +5 -1
- package/src/query-options.ts +183 -0
- package/src/query-state.ts +296 -40
- package/src/rate-limit.ts +17 -14
- package/src/request-lane.ts +36 -0
- package/src/sdk-query.ts +16 -0
- package/src/session-persistence.ts +369 -54
- package/src/tool-pairing-audit.ts +69 -0
package/bundle/index.js
CHANGED
|
@@ -26941,12 +26941,116 @@ function splitPrefixSuffix(input, options = {}) {
|
|
|
26941
26941
|
// src/config.ts
|
|
26942
26942
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
26943
26943
|
import { homedir } from "os";
|
|
26944
|
-
import { dirname, join as
|
|
26944
|
+
import { dirname as dirname2, join as join3, resolve as resolve2, sep as sep2 } from "path";
|
|
26945
|
+
|
|
26946
|
+
// src/debug.ts
|
|
26947
|
+
import { appendFileSync as appendFileSync2, chmodSync, mkdirSync as mkdirSync2 } from "fs";
|
|
26948
|
+
import { dirname, join as join2 } from "path";
|
|
26949
|
+
var DEBUG = process.env.CLAUDE_BRIDGE_DEBUG === "1";
|
|
26950
|
+
var DEBUG_LOG_PATH = process.env.CLAUDE_BRIDGE_DEBUG_PATH || join2(piUserDir(), "claude-bridge.log");
|
|
26951
|
+
function diagLogPath() {
|
|
26952
|
+
return process.env.CLAUDE_BRIDGE_DIAG_PATH || join2(piUserDir(), "claude-bridge-diag.log");
|
|
26953
|
+
}
|
|
26954
|
+
function diagGuidance() {
|
|
26955
|
+
return DEBUG ? `see ${diagLogPath()}` : "re-run with CLAUDE_BRIDGE_DEBUG=1 to capture a diagnostic dump";
|
|
26956
|
+
}
|
|
26957
|
+
if (DEBUG) {
|
|
26958
|
+
try {
|
|
26959
|
+
mkdirSync2(dirname(DEBUG_LOG_PATH), { recursive: true, mode: 448 });
|
|
26960
|
+
mkdirSync2(dirname(diagLogPath()), { recursive: true, mode: 448 });
|
|
26961
|
+
chmodSync(dirname(DEBUG_LOG_PATH), 448);
|
|
26962
|
+
chmodSync(DEBUG_LOG_PATH, 384);
|
|
26963
|
+
} catch {
|
|
26964
|
+
}
|
|
26965
|
+
}
|
|
26966
|
+
var moduleInstanceId = Math.random().toString(36).slice(2, 8);
|
|
26967
|
+
function debug(...args) {
|
|
26968
|
+
if (!DEBUG) return;
|
|
26969
|
+
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
26970
|
+
const fmt = (a) => {
|
|
26971
|
+
if (typeof a === "string") return a;
|
|
26972
|
+
if (a instanceof Error) return `${a.name}: ${a.message}${a.stack ? "\n" + a.stack : ""}`;
|
|
26973
|
+
if (typeof a === "function") return fmt(a());
|
|
26974
|
+
return JSON.stringify(a);
|
|
26975
|
+
};
|
|
26976
|
+
const safeFmt = (a) => {
|
|
26977
|
+
let out;
|
|
26978
|
+
try {
|
|
26979
|
+
out = fmt(a);
|
|
26980
|
+
} catch (error51) {
|
|
26981
|
+
let reason = "formatting failed";
|
|
26982
|
+
try {
|
|
26983
|
+
reason = String(error51 instanceof Error ? error51.message : error51);
|
|
26984
|
+
} catch {
|
|
26985
|
+
}
|
|
26986
|
+
return `[unprintable: ${reason}]`;
|
|
26987
|
+
}
|
|
26988
|
+
if (out !== void 0) return out;
|
|
26989
|
+
if (typeof a === "function") return "undefined";
|
|
26990
|
+
try {
|
|
26991
|
+
return String(a);
|
|
26992
|
+
} catch {
|
|
26993
|
+
return "[unprintable: formatting failed]";
|
|
26994
|
+
}
|
|
26995
|
+
};
|
|
26996
|
+
const msg = args.map(safeFmt).join(" ");
|
|
26997
|
+
try {
|
|
26998
|
+
appendFileSync2(DEBUG_LOG_PATH, `[${ts2}] [${moduleInstanceId}] ${msg}
|
|
26999
|
+
`, { mode: 384 });
|
|
27000
|
+
} catch {
|
|
27001
|
+
}
|
|
27002
|
+
}
|
|
27003
|
+
var nextCliDebugSeq = 1;
|
|
27004
|
+
function makeCliDebugOptions(tag) {
|
|
27005
|
+
if (!DEBUG) return {};
|
|
27006
|
+
const seq = nextCliDebugSeq++;
|
|
27007
|
+
const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
27008
|
+
const logDir = join2(dirname(DEBUG_LOG_PATH), "cc-cli-logs");
|
|
27009
|
+
try {
|
|
27010
|
+
mkdirSync2(logDir, { recursive: true, mode: 448 });
|
|
27011
|
+
chmodSync(logDir, 448);
|
|
27012
|
+
} catch {
|
|
27013
|
+
}
|
|
27014
|
+
const debugFile = join2(logDir, `${ts2}-${tag}-${seq}.log`);
|
|
27015
|
+
debug(`cli-debug: ${tag} #${seq} \u2192 ${debugFile}`);
|
|
27016
|
+
return {
|
|
27017
|
+
debug: true,
|
|
27018
|
+
debugFile,
|
|
27019
|
+
stderr: (data) => {
|
|
27020
|
+
for (const line of data.split(/\r?\n/)) {
|
|
27021
|
+
if (line) debug(`[cli-stderr ${tag}#${seq}] ${line}`);
|
|
27022
|
+
}
|
|
27023
|
+
}
|
|
27024
|
+
};
|
|
27025
|
+
}
|
|
27026
|
+
function diagDump(label, data) {
|
|
27027
|
+
if (!DEBUG) return;
|
|
27028
|
+
try {
|
|
27029
|
+
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
27030
|
+
const entry = { ts: ts2, moduleInstanceId, label, ...data };
|
|
27031
|
+
const path = diagLogPath();
|
|
27032
|
+
try {
|
|
27033
|
+
mkdirSync2(dirname(path), { recursive: true, mode: 448 });
|
|
27034
|
+
} catch {
|
|
27035
|
+
}
|
|
27036
|
+
appendFileSync2(path, JSON.stringify(entry) + "\n", { mode: 384 });
|
|
27037
|
+
try {
|
|
27038
|
+
chmodSync(path, 384);
|
|
27039
|
+
} catch {
|
|
27040
|
+
}
|
|
27041
|
+
debug(`DIAG: ${label} (see ${path})`);
|
|
27042
|
+
} catch (error51) {
|
|
27043
|
+
debug(`DIAG FAILED: ${label}`, error51);
|
|
27044
|
+
}
|
|
27045
|
+
}
|
|
27046
|
+
|
|
27047
|
+
// src/config.ts
|
|
26945
27048
|
var PACKAGE_ID = "@vanillagreen/pi-claude-bridge";
|
|
27049
|
+
var EXTERNAL_CONFIG_RESOLVER_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.extension-config-resolver");
|
|
26946
27050
|
var VALID_EFFORT_LEVELS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
|
|
26947
27051
|
function expandHome(input) {
|
|
26948
27052
|
if (input === "~") return homedir();
|
|
26949
|
-
if (input.startsWith("~/")) return
|
|
27053
|
+
if (input.startsWith("~/")) return join3(homedir(), input.slice(2));
|
|
26950
27054
|
return input;
|
|
26951
27055
|
}
|
|
26952
27056
|
function piUserDir() {
|
|
@@ -26971,11 +27075,11 @@ function mergeDeep(target, source) {
|
|
|
26971
27075
|
function projectSettingsPath(cwd) {
|
|
26972
27076
|
let current = resolve2(cwd);
|
|
26973
27077
|
while (true) {
|
|
26974
|
-
const candidate =
|
|
27078
|
+
const candidate = join3(current, ".pi", "settings.json");
|
|
26975
27079
|
if (existsSync2(candidate)) return candidate;
|
|
26976
|
-
if (existsSync2(
|
|
26977
|
-
const parent =
|
|
26978
|
-
if (parent === current) return
|
|
27080
|
+
if (existsSync2(join3(current, ".pi")) || existsSync2(join3(current, ".git")) || existsSync2(join3(current, ".vstack-lock.json"))) return candidate;
|
|
27081
|
+
const parent = dirname2(current);
|
|
27082
|
+
if (parent === current) return join3(resolve2(cwd), ".pi", "settings.json");
|
|
26979
27083
|
current = parent;
|
|
26980
27084
|
}
|
|
26981
27085
|
}
|
|
@@ -27005,7 +27109,7 @@ function projectSettingsTrusted(settingsPath) {
|
|
|
27005
27109
|
return projectTrustRegistry().projectSettings?.get(settingsPath) === true;
|
|
27006
27110
|
}
|
|
27007
27111
|
function settingsPaths(cwd) {
|
|
27008
|
-
const user =
|
|
27112
|
+
const user = join3(piUserDir(), "settings.json");
|
|
27009
27113
|
if (isolatedFromEnv()) return [];
|
|
27010
27114
|
const project = projectSettingsPath(cwd);
|
|
27011
27115
|
return projectSettingsTrusted(project) ? [user, project] : [user];
|
|
@@ -27014,24 +27118,39 @@ function tryParseJson(path) {
|
|
|
27014
27118
|
if (!existsSync2(path)) return {};
|
|
27015
27119
|
try {
|
|
27016
27120
|
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
27017
|
-
} catch {
|
|
27121
|
+
} catch (error51) {
|
|
27122
|
+
debug(`config: ignoring malformed ${path}:`, error51 instanceof Error ? error51.message : String(error51));
|
|
27018
27123
|
return {};
|
|
27019
27124
|
}
|
|
27020
27125
|
}
|
|
27021
27126
|
function readManagerConfig(cwd) {
|
|
27022
27127
|
const merged = {};
|
|
27128
|
+
const userPath = join3(piUserDir(), "settings.json");
|
|
27023
27129
|
for (const path of settingsPaths(cwd)) {
|
|
27024
27130
|
if (!existsSync2(path)) continue;
|
|
27025
27131
|
try {
|
|
27026
27132
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
27027
27133
|
const configRoot = asRecord(asRecord(asRecord(parsed?.vstack)?.extensionManager)?.config);
|
|
27028
27134
|
const config2 = asRecord(configRoot?.[PACKAGE_ID]);
|
|
27029
|
-
if (config2) mergeDeep(merged, config2);
|
|
27030
|
-
} catch {
|
|
27135
|
+
if (config2) mergeDeep(merged, path === userPath ? config2 : withoutUserScopeOnlyKeys(config2));
|
|
27136
|
+
} catch (error51) {
|
|
27137
|
+
debug(`config: ignoring malformed manager config ${path}:`, error51 instanceof Error ? error51.message : String(error51));
|
|
27031
27138
|
}
|
|
27032
27139
|
}
|
|
27033
27140
|
return merged;
|
|
27034
27141
|
}
|
|
27142
|
+
var USER_SCOPE_ONLY_PROVIDER_KEYS = ["enableConnectors", "connectorWriteMode"];
|
|
27143
|
+
function withoutUserScopeOnlyKeys(raw) {
|
|
27144
|
+
const out = { ...raw };
|
|
27145
|
+
for (const key of USER_SCOPE_ONLY_PROVIDER_KEYS) delete out[key];
|
|
27146
|
+
return out;
|
|
27147
|
+
}
|
|
27148
|
+
function stripUserScopeOnlyProviderKeys(config2) {
|
|
27149
|
+
if (!config2.provider) return config2;
|
|
27150
|
+
const provider = { ...config2.provider };
|
|
27151
|
+
for (const key of USER_SCOPE_ONLY_PROVIDER_KEYS) delete provider[key];
|
|
27152
|
+
return { ...config2, provider };
|
|
27153
|
+
}
|
|
27035
27154
|
function boolFrom(raw, key) {
|
|
27036
27155
|
return typeof raw[key] === "boolean" ? raw[key] : void 0;
|
|
27037
27156
|
}
|
|
@@ -27095,8 +27214,6 @@ function managerToConfig(raw) {
|
|
|
27095
27214
|
const promptContext = {};
|
|
27096
27215
|
const appendSystemPrompt = boolFrom(raw, "appendSystemPrompt");
|
|
27097
27216
|
if (appendSystemPrompt !== void 0) provider.appendSystemPrompt = appendSystemPrompt;
|
|
27098
|
-
const allowExtraUsage = boolFrom(raw, "allowExtraUsage");
|
|
27099
|
-
if (allowExtraUsage !== void 0) provider.allowExtraUsage = allowExtraUsage;
|
|
27100
27217
|
const fastMode = boolFrom(raw, "fastMode");
|
|
27101
27218
|
if (fastMode !== void 0) provider.fastMode = fastMode;
|
|
27102
27219
|
if (hasOwn(raw, "forceEffort")) {
|
|
@@ -27127,19 +27244,84 @@ function managerToConfig(raw) {
|
|
|
27127
27244
|
...Object.keys(promptContext).length ? { promptContext } : {}
|
|
27128
27245
|
};
|
|
27129
27246
|
}
|
|
27247
|
+
function legacyFileConfig(path) {
|
|
27248
|
+
const raw = asRecord(tryParseJson(path)) ?? {};
|
|
27249
|
+
const flat = managerToConfig(raw);
|
|
27250
|
+
const provider = { ...flat.provider, ...asRecord(raw.provider) };
|
|
27251
|
+
const promptContext = { ...flat.promptContext, ...asRecord(raw.promptContext) };
|
|
27252
|
+
return {
|
|
27253
|
+
...flat.enabled !== void 0 ? { enabled: flat.enabled } : {},
|
|
27254
|
+
...Object.keys(provider ?? {}).length ? { provider } : {},
|
|
27255
|
+
...Object.keys(promptContext ?? {}).length ? { promptContext } : {}
|
|
27256
|
+
};
|
|
27257
|
+
}
|
|
27258
|
+
function legacyLayers(cwd) {
|
|
27259
|
+
const globalPath = join3(piUserDir(), "claude-bridge.json");
|
|
27260
|
+
const layers = [{ path: globalPath, config: legacyFileConfig(globalPath) }];
|
|
27261
|
+
if (isolatedFromEnv()) return layers;
|
|
27262
|
+
const projectSettings = projectSettingsPath(cwd);
|
|
27263
|
+
if (!projectSettingsTrusted(projectSettings)) return layers;
|
|
27264
|
+
const projectPath = join3(dirname2(projectSettings), "claude-bridge.json");
|
|
27265
|
+
return [...layers, { path: projectPath, config: stripUserScopeOnlyProviderKeys(legacyFileConfig(projectPath)) }];
|
|
27266
|
+
}
|
|
27267
|
+
function mergeLayers(layers) {
|
|
27268
|
+
const merged = { provider: {}, promptContext: {} };
|
|
27269
|
+
for (const layer of layers) {
|
|
27270
|
+
if (layer.config.enabled !== void 0) merged.enabled = layer.config.enabled;
|
|
27271
|
+
merged.provider = { ...merged.provider, ...layer.config.provider };
|
|
27272
|
+
merged.promptContext = { ...merged.promptContext, ...layer.config.promptContext };
|
|
27273
|
+
}
|
|
27274
|
+
return merged;
|
|
27275
|
+
}
|
|
27130
27276
|
function loadConfig(cwd) {
|
|
27131
|
-
const
|
|
27132
|
-
const
|
|
27133
|
-
const
|
|
27134
|
-
const trustedProject = projectSettings !== void 0 && projectSettingsTrusted(projectSettings);
|
|
27135
|
-
const project = trustedProject ? tryParseJson(join2(dirname(projectSettings), "claude-bridge.json")) : {};
|
|
27136
|
-
const manager = isolated ? {} : managerToConfig(readManagerConfig(cwd));
|
|
27137
|
-
const provider = normalizeProviderConfig({ ...global2.provider, ...project.provider, ...manager.provider });
|
|
27277
|
+
const legacy = mergeLayers(legacyLayers(cwd));
|
|
27278
|
+
const manager = isolatedFromEnv() ? {} : managerToConfig(readManagerConfig(cwd));
|
|
27279
|
+
const provider = normalizeProviderConfig({ ...legacy.provider, ...manager.provider });
|
|
27138
27280
|
return {
|
|
27139
|
-
enabled: manager.enabled ??
|
|
27281
|
+
enabled: manager.enabled ?? legacy.enabled ?? true,
|
|
27140
27282
|
provider,
|
|
27141
|
-
promptContext: { ...
|
|
27142
|
-
};
|
|
27283
|
+
promptContext: { ...legacy.promptContext, ...manager.promptContext }
|
|
27284
|
+
};
|
|
27285
|
+
}
|
|
27286
|
+
var PROVIDER_KEYS = /* @__PURE__ */ new Set([
|
|
27287
|
+
"appendSystemPrompt",
|
|
27288
|
+
"connectorWriteMode",
|
|
27289
|
+
"enableConnectors",
|
|
27290
|
+
"fastMode",
|
|
27291
|
+
"forceEffort",
|
|
27292
|
+
"modelEffortOverrides",
|
|
27293
|
+
"pathToClaudeCodeExecutable",
|
|
27294
|
+
"strictMcpConfig"
|
|
27295
|
+
]);
|
|
27296
|
+
var PROMPT_CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
27297
|
+
"includeAppendSystemPromptMd",
|
|
27298
|
+
"includeCavemanHook",
|
|
27299
|
+
"includeProjectAgentsHook",
|
|
27300
|
+
"includeTaskPanelHook"
|
|
27301
|
+
]);
|
|
27302
|
+
function configValueForKey(config2, key) {
|
|
27303
|
+
if (key === "enabled") return config2.enabled;
|
|
27304
|
+
if (PROVIDER_KEYS.has(key)) return normalizeProviderConfig(config2.provider)?.[key];
|
|
27305
|
+
if (PROMPT_CONTEXT_KEYS.has(key)) return config2.promptContext?.[key];
|
|
27306
|
+
return void 0;
|
|
27307
|
+
}
|
|
27308
|
+
function displayPath(path) {
|
|
27309
|
+
const home = homedir();
|
|
27310
|
+
return home && path.startsWith(home + sep2) ? `~${path.slice(home.length)}` : path;
|
|
27311
|
+
}
|
|
27312
|
+
function resolveExternalConfigValue(key, cwd) {
|
|
27313
|
+
const layers = legacyLayers(cwd);
|
|
27314
|
+
const value = configValueForKey(mergeLayers(layers), key);
|
|
27315
|
+
if (value === void 0) return { explicit: false, value: void 0 };
|
|
27316
|
+
const source = [...layers].reverse().find((layer) => configValueForKey(layer.config, key) !== void 0)?.path;
|
|
27317
|
+
return { explicit: true, value, ...source ? { source: displayPath(source) } : {} };
|
|
27318
|
+
}
|
|
27319
|
+
function registerExternalConfigResolver() {
|
|
27320
|
+
const host = globalThis;
|
|
27321
|
+
const existing = asRecord(host[EXTERNAL_CONFIG_RESOLVER_SYMBOL]);
|
|
27322
|
+
const registry2 = existing ?? {};
|
|
27323
|
+
if (!existing) host[EXTERNAL_CONFIG_RESOLVER_SYMBOL] = registry2;
|
|
27324
|
+
registry2[PACKAGE_ID] = (key, cwd) => resolveExternalConfigValue(key, cwd);
|
|
27143
27325
|
}
|
|
27144
27326
|
|
|
27145
27327
|
// src/skills.ts
|
|
@@ -27179,9 +27361,12 @@ function connectorServerNamespace(connectorName) {
|
|
|
27179
27361
|
function credentialCandidatePaths(env = process.env) {
|
|
27180
27362
|
const roots = [];
|
|
27181
27363
|
const configDir = env.CLAUDE_CONFIG_DIR?.trim();
|
|
27182
|
-
if (configDir)
|
|
27183
|
-
|
|
27184
|
-
|
|
27364
|
+
if (configDir) {
|
|
27365
|
+
roots.push(configDir);
|
|
27366
|
+
} else {
|
|
27367
|
+
const home = env.HOME?.trim();
|
|
27368
|
+
if (home) roots.push(`${home}/.claude`, home);
|
|
27369
|
+
}
|
|
27185
27370
|
const seen = /* @__PURE__ */ new Set();
|
|
27186
27371
|
const paths = [];
|
|
27187
27372
|
for (const root of roots) {
|
|
@@ -27348,6 +27533,10 @@ function connectorsEnabledFromEnv() {
|
|
|
27348
27533
|
function connectorsEnabledFor(config2) {
|
|
27349
27534
|
return connectorsEnabledFromEnv() || config2?.provider?.enableConnectors === true;
|
|
27350
27535
|
}
|
|
27536
|
+
function settingSourcesForQuery(connectorsEnabled, appendSystemPrompt, configured) {
|
|
27537
|
+
if (connectorsEnabled) return configured ?? ["user"];
|
|
27538
|
+
return appendSystemPrompt ? void 0 : configured ?? ["user", "project"];
|
|
27539
|
+
}
|
|
27351
27540
|
var CLAUDE_AI_CONNECTOR_TOOL_PATTERNS = [
|
|
27352
27541
|
"mcp__claude_ai_Gmail__*",
|
|
27353
27542
|
"mcp__claude_ai_Google_Calendar__*",
|
|
@@ -27355,7 +27544,16 @@ var CLAUDE_AI_CONNECTOR_TOOL_PATTERNS = [
|
|
|
27355
27544
|
"mcp__claude_ai_Slack__*",
|
|
27356
27545
|
"mcp__claude_ai_Atlassian__*"
|
|
27357
27546
|
];
|
|
27547
|
+
var SDK_TOOL_ALIASES = {
|
|
27548
|
+
ListMcpResources: "ListMcpResourcesTool",
|
|
27549
|
+
ReadMcpResource: "ReadMcpResourceTool"
|
|
27550
|
+
};
|
|
27551
|
+
function deliveredSpellings(name) {
|
|
27552
|
+
const canonical = SDK_TOOL_ALIASES[name];
|
|
27553
|
+
return canonical ? [name, canonical] : [name];
|
|
27554
|
+
}
|
|
27358
27555
|
var CONNECTOR_DISCOVERY_TOOLS = ["ToolSearch", "ListMcpResources", "ReadMcpResource"];
|
|
27556
|
+
var CONNECTOR_DISCOVERY_TOOL_NAMES = new Set(CONNECTOR_DISCOVERY_TOOLS.flatMap(deliveredSpellings));
|
|
27359
27557
|
var CONNECTOR_NS_PREFIX2 = "mcp__claude_ai_";
|
|
27360
27558
|
var CONNECTOR_NS_GMAIL = `${CONNECTOR_NS_PREFIX2}Gmail__`;
|
|
27361
27559
|
var CONNECTOR_NS_CALENDAR = `${CONNECTOR_NS_PREFIX2}Google_Calendar__`;
|
|
@@ -27512,15 +27710,22 @@ var CONNECTOR_WRITE_TOOLS = [
|
|
|
27512
27710
|
`${CONNECTOR_NS_ATLASSIAN}createCompassComponentRelationship`,
|
|
27513
27711
|
`${CONNECTOR_NS_ATLASSIAN}createCompassCustomFieldDefinition`
|
|
27514
27712
|
];
|
|
27515
|
-
function
|
|
27713
|
+
function isConnectorTool(name) {
|
|
27516
27714
|
return typeof name === "string" && name.startsWith(CONNECTOR_NS_PREFIX2);
|
|
27517
27715
|
}
|
|
27716
|
+
var CHILD_INTERNAL_TOOLS = /* @__PURE__ */ new Set(["ToolSearch", "ScheduleWakeup"]);
|
|
27717
|
+
function isChildInternalTool(name) {
|
|
27718
|
+
return typeof name === "string" && CHILD_INTERNAL_TOOLS.has(name);
|
|
27719
|
+
}
|
|
27720
|
+
function isChildExecutedTool(name) {
|
|
27721
|
+
return isConnectorTool(name) || isChildInternalTool(name);
|
|
27722
|
+
}
|
|
27518
27723
|
function isConnectorWriteTool(name) {
|
|
27519
|
-
if (!name
|
|
27520
|
-
const
|
|
27521
|
-
if (
|
|
27522
|
-
const server = name.slice(CONNECTOR_NS_PREFIX2.length,
|
|
27523
|
-
const words = connectorNameWords(name.slice(
|
|
27724
|
+
if (!isConnectorTool(name)) return false;
|
|
27725
|
+
const sep3 = name.indexOf("__", CONNECTOR_NS_PREFIX2.length);
|
|
27726
|
+
if (sep3 <= CONNECTOR_NS_PREFIX2.length) return true;
|
|
27727
|
+
const server = name.slice(CONNECTOR_NS_PREFIX2.length, sep3);
|
|
27728
|
+
const words = connectorNameWords(name.slice(sep3 + "__".length));
|
|
27524
27729
|
const serverWords = connectorNameWords(server);
|
|
27525
27730
|
let skipped = 0;
|
|
27526
27731
|
while (skipped < serverWords.length && words[skipped] === serverWords[skipped] && !CONNECTOR_MUTATION_WORDS.has(words[skipped])) skipped++;
|
|
@@ -27543,11 +27748,11 @@ function connectorWriteDenyHook() {
|
|
|
27543
27748
|
return async (input) => {
|
|
27544
27749
|
try {
|
|
27545
27750
|
if (input.hook_event_name !== "PreToolUse") return { continue: true };
|
|
27751
|
+
if (typeof input.tool_name !== "string") return connectorWriteDenyOutput("<unknown>");
|
|
27546
27752
|
if (!isConnectorWriteTool(input.tool_name)) return { continue: true };
|
|
27547
|
-
return connectorWriteDenyOutput(
|
|
27753
|
+
return connectorWriteDenyOutput(input.tool_name);
|
|
27548
27754
|
} catch {
|
|
27549
|
-
|
|
27550
|
-
return connectorWriteDenyOutput(toolName);
|
|
27755
|
+
return connectorWriteDenyOutput(safeToolNameFrom(input));
|
|
27551
27756
|
}
|
|
27552
27757
|
};
|
|
27553
27758
|
}
|
|
@@ -27560,14 +27765,66 @@ function connectorWriteDenyOutput(toolName) {
|
|
|
27560
27765
|
}
|
|
27561
27766
|
};
|
|
27562
27767
|
}
|
|
27768
|
+
function isAllowlistedConnectorSessionTool(name) {
|
|
27769
|
+
return name.startsWith(MCP_TOOL_PREFIX) || name.startsWith(CONNECTOR_NS_PREFIX2) || CONNECTOR_DISCOVERY_TOOL_NAMES.has(name);
|
|
27770
|
+
}
|
|
27771
|
+
function connectorBuiltinAllowlistHook() {
|
|
27772
|
+
return async (input) => {
|
|
27773
|
+
try {
|
|
27774
|
+
if (input.hook_event_name !== "PreToolUse") return { continue: true };
|
|
27775
|
+
if (typeof input.tool_name !== "string") return allowlistDenyOutput("<unknown>");
|
|
27776
|
+
if (isAllowlistedConnectorSessionTool(input.tool_name)) return { continue: true };
|
|
27777
|
+
return allowlistDenyOutput(input.tool_name);
|
|
27778
|
+
} catch {
|
|
27779
|
+
return allowlistDenyOutput(safeToolNameFrom(input));
|
|
27780
|
+
}
|
|
27781
|
+
};
|
|
27782
|
+
}
|
|
27783
|
+
function safeToolNameFrom(input) {
|
|
27784
|
+
try {
|
|
27785
|
+
const candidate = input?.tool_name;
|
|
27786
|
+
return typeof candidate === "string" ? candidate : "<unknown>";
|
|
27787
|
+
} catch {
|
|
27788
|
+
return "<unknown>";
|
|
27789
|
+
}
|
|
27790
|
+
}
|
|
27791
|
+
function allowlistDenyOutput(toolName) {
|
|
27792
|
+
return {
|
|
27793
|
+
hookSpecificOutput: {
|
|
27794
|
+
hookEventName: "PreToolUse",
|
|
27795
|
+
permissionDecision: "deny",
|
|
27796
|
+
permissionDecisionReason: `Tool "${toolName}" is not available in this connector session. Only bridged custom tools, claude.ai connector tools, and tool discovery are permitted here.`
|
|
27797
|
+
}
|
|
27798
|
+
};
|
|
27799
|
+
}
|
|
27800
|
+
function denyAllToolsHook() {
|
|
27801
|
+
return async (input) => {
|
|
27802
|
+
try {
|
|
27803
|
+
if (input.hook_event_name !== "PreToolUse") return { continue: true };
|
|
27804
|
+
return denyAllOutput(typeof input.tool_name === "string" ? input.tool_name : "<unknown>");
|
|
27805
|
+
} catch {
|
|
27806
|
+
return denyAllOutput("<unknown>");
|
|
27807
|
+
}
|
|
27808
|
+
};
|
|
27809
|
+
}
|
|
27810
|
+
function denyAllOutput(toolName) {
|
|
27811
|
+
return {
|
|
27812
|
+
hookSpecificOutput: {
|
|
27813
|
+
hookEventName: "PreToolUse",
|
|
27814
|
+
permissionDecision: "deny",
|
|
27815
|
+
permissionDecisionReason: `Tool "${toolName}" is not available: this session executes no tools.`
|
|
27816
|
+
}
|
|
27817
|
+
};
|
|
27818
|
+
}
|
|
27563
27819
|
function connectorQueryOptions(connectorsEnabled, writeMode = "deny") {
|
|
27564
27820
|
const isolation = toolIsolationForQuery(connectorsEnabled, writeMode);
|
|
27565
|
-
if (!connectorsEnabled
|
|
27566
|
-
|
|
27821
|
+
if (!connectorsEnabled) return isolation;
|
|
27822
|
+
const hooks = writeMode === "allow" ? [connectorBuiltinAllowlistHook()] : [connectorBuiltinAllowlistHook(), connectorWriteDenyHook()];
|
|
27823
|
+
return { ...isolation, hooks: { PreToolUse: [{ hooks }] } };
|
|
27567
27824
|
}
|
|
27568
27825
|
function toolIsolationForQuery(connectorsEnabled, writeMode = "deny") {
|
|
27569
27826
|
if (!connectorsEnabled) return CLAUDE_BRIDGE_TOOL_ISOLATION;
|
|
27570
|
-
const disallowedTools = DISALLOWED_BUILTIN_TOOLS.filter((t) => !
|
|
27827
|
+
const disallowedTools = DISALLOWED_BUILTIN_TOOLS.filter((t) => !CONNECTOR_DISCOVERY_TOOL_NAMES.has(t));
|
|
27571
27828
|
if (writeMode !== "allow") disallowedTools.push(...CONNECTOR_WRITE_TOOLS);
|
|
27572
27829
|
return {
|
|
27573
27830
|
disallowedTools,
|
|
@@ -27596,7 +27853,7 @@ function connectorDeclarationsDisabled(env = process.env) {
|
|
|
27596
27853
|
}
|
|
27597
27854
|
|
|
27598
27855
|
// src/convert.ts
|
|
27599
|
-
var PROVIDER_ID = "claude
|
|
27856
|
+
var PROVIDER_ID = "pi-claude";
|
|
27600
27857
|
var PI_TO_SDK_TOOL_NAME = {
|
|
27601
27858
|
read: "Read",
|
|
27602
27859
|
write: "Write",
|
|
@@ -27700,15 +27957,7 @@ function convertPiMessages(messages, customToolNameToSdk) {
|
|
|
27700
27957
|
if (toolMessages.length === 0) return;
|
|
27701
27958
|
anthropicMessages.push({
|
|
27702
27959
|
role: "user",
|
|
27703
|
-
content: toolMessages.map((toolMsg) =>
|
|
27704
|
-
const content = toolResultContentToAnthropic(toolMsg.content);
|
|
27705
|
-
return {
|
|
27706
|
-
type: "tool_result",
|
|
27707
|
-
tool_use_id: sanitizeToolId(toolMsg.toolCallId, sanitizedIds),
|
|
27708
|
-
content: content || "",
|
|
27709
|
-
is_error: toolMsg.isError
|
|
27710
|
-
};
|
|
27711
|
-
})
|
|
27960
|
+
content: toolMessages.map((toolMsg) => toolResultToAnthropicBlock(toolMsg, sanitizedIds))
|
|
27712
27961
|
});
|
|
27713
27962
|
};
|
|
27714
27963
|
for (let i = 0; i < messages.length; i++) {
|
|
@@ -27867,7 +28116,36 @@ function extractAllToolResults(messages) {
|
|
|
27867
28116
|
return { results, stopIdx };
|
|
27868
28117
|
}
|
|
27869
28118
|
|
|
28119
|
+
// src/request-lane.ts
|
|
28120
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
28121
|
+
var REQUEST_LANE_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.claude-bridge.request-lane.v1");
|
|
28122
|
+
function requestLaneStorage() {
|
|
28123
|
+
const host = globalThis;
|
|
28124
|
+
let storage = host[REQUEST_LANE_SYMBOL];
|
|
28125
|
+
if (!storage) {
|
|
28126
|
+
storage = new AsyncLocalStorage();
|
|
28127
|
+
host[REQUEST_LANE_SYMBOL] = storage;
|
|
28128
|
+
}
|
|
28129
|
+
return storage;
|
|
28130
|
+
}
|
|
28131
|
+
function runInRequestLane(sessionId, callback) {
|
|
28132
|
+
const storage = requestLaneStorage();
|
|
28133
|
+
if (sessionId !== void 0) return storage.run(sessionId, callback);
|
|
28134
|
+
return storage.getStore() === void 0 ? callback() : storage.exit(callback);
|
|
28135
|
+
}
|
|
28136
|
+
function currentRequestLaneId() {
|
|
28137
|
+
return requestLaneStorage().getStore();
|
|
28138
|
+
}
|
|
28139
|
+
|
|
27870
28140
|
// src/query-state.ts
|
|
28141
|
+
function summarizeDroppedUserMessages(site, dropped) {
|
|
28142
|
+
return {
|
|
28143
|
+
site,
|
|
28144
|
+
count: dropped.length,
|
|
28145
|
+
textLengths: dropped.map((message) => message.text.length),
|
|
28146
|
+
imageOnlyCount: dropped.filter((message) => !message.text && message.blocks?.length).length
|
|
28147
|
+
};
|
|
28148
|
+
}
|
|
27871
28149
|
var DRAIN_CAUSE_TEXT = {
|
|
27872
28150
|
"abort": "the turn was aborted",
|
|
27873
28151
|
"stream-idle-timeout": "the Claude Code stream went idle and the turn timed out",
|
|
@@ -27892,6 +28170,49 @@ function drainPendingToolCalls(queryCtx, cause) {
|
|
|
27892
28170
|
queryCtx.pendingToolCalls.clear();
|
|
27893
28171
|
return drained;
|
|
27894
28172
|
}
|
|
28173
|
+
function strandedToolCallResult() {
|
|
28174
|
+
return {
|
|
28175
|
+
content: [{ type: "text", text: "Claude bridge: this tool call was never forwarded to Pi before its turn ended, so it did not execute and no result can arrive. Re-run the tool." }],
|
|
28176
|
+
isError: true
|
|
28177
|
+
};
|
|
28178
|
+
}
|
|
28179
|
+
function failStrandedToolCall(queryCtx, id) {
|
|
28180
|
+
if (queryCtx.forwardedToolCallIds.has(id)) return false;
|
|
28181
|
+
const pending = queryCtx.pendingToolCalls.get(id);
|
|
28182
|
+
if (!pending) return false;
|
|
28183
|
+
queryCtx.pendingToolCalls.delete(id);
|
|
28184
|
+
queryCtx.deadToolCallIds.add(id);
|
|
28185
|
+
pending.resolve(strandedToolCallResult());
|
|
28186
|
+
return true;
|
|
28187
|
+
}
|
|
28188
|
+
function drainStrandedToolCalls(queryCtx) {
|
|
28189
|
+
const stranded = [];
|
|
28190
|
+
for (const [id, pending] of queryCtx.pendingToolCalls) {
|
|
28191
|
+
if (pending.generation >= queryCtx.callbackGeneration) continue;
|
|
28192
|
+
if (queryCtx.forwardedToolCallIds.has(id)) continue;
|
|
28193
|
+
stranded.push({ id, toolName: pending.toolName });
|
|
28194
|
+
}
|
|
28195
|
+
for (const { id } of stranded) {
|
|
28196
|
+
const pending = queryCtx.pendingToolCalls.get(id);
|
|
28197
|
+
queryCtx.pendingToolCalls.delete(id);
|
|
28198
|
+
queryCtx.deadToolCallIds.add(id);
|
|
28199
|
+
pending.resolve(strandedToolCallResult());
|
|
28200
|
+
}
|
|
28201
|
+
return stranded;
|
|
28202
|
+
}
|
|
28203
|
+
function takeQueuedOrParkedResult(queryCtx, id) {
|
|
28204
|
+
const queued = queryCtx.pendingResults.get(id);
|
|
28205
|
+
if (queued !== void 0) {
|
|
28206
|
+
queryCtx.pendingResults.delete(id);
|
|
28207
|
+
return queued;
|
|
28208
|
+
}
|
|
28209
|
+
const parked = queryCtx.reapedResults.get(id);
|
|
28210
|
+
if (parked !== void 0) {
|
|
28211
|
+
queryCtx.reapedResults.delete(id);
|
|
28212
|
+
return parked;
|
|
28213
|
+
}
|
|
28214
|
+
return void 0;
|
|
28215
|
+
}
|
|
27895
28216
|
function normalizeForCompare(value) {
|
|
27896
28217
|
if (Array.isArray(value)) return value.map(normalizeForCompare);
|
|
27897
28218
|
if (value && typeof value === "object") {
|
|
@@ -27930,6 +28251,33 @@ var QueryContext = class {
|
|
|
27930
28251
|
latestCursor = 0;
|
|
27931
28252
|
pendingToolCalls = /* @__PURE__ */ new Map();
|
|
27932
28253
|
pendingResults = /* @__PURE__ */ new Map();
|
|
28254
|
+
/** Results a message-boundary reap moved OUT of pendingResults so they stop
|
|
28255
|
+
* poisoning mismatch reports, kept CONSUMABLE for a handler that fires later.
|
|
28256
|
+
* The 2026-08-17 deadlock session showed the reap's "no consumer will ever
|
|
28257
|
+
* come" assumption failing routinely: Pi delivers a turn's results in one
|
|
28258
|
+
* callback while the SDK staggers handler invocations past the next message
|
|
28259
|
+
* boundary. Query-scoped, bounded by the query's tool-call count. */
|
|
28260
|
+
reapedResults = /* @__PURE__ */ new Map();
|
|
28261
|
+
/** Every tool-call id this query has handed to Pi inside an ENDED turn — the
|
|
28262
|
+
* set endToolUseTurn stamps from the turn's content. A forwarded id is one Pi
|
|
28263
|
+
* will execute and answer; it must never be emitted again (a lagging stream
|
|
28264
|
+
* replays the same tool_use into the NEXT turn, and per-message turnBlocks
|
|
28265
|
+
* dedup cannot see across turns — vstack#1469's duplicate executions), and a
|
|
28266
|
+
* handler waiting on it must be left waiting at the stranded-handler drains.
|
|
28267
|
+
* Query-scoped, never reset per message. */
|
|
28268
|
+
forwardedToolCallIds = /* @__PURE__ */ new Set();
|
|
28269
|
+
/** Ids whose waiting handler was resolved with strandedToolCallResult. The
|
|
28270
|
+
* model has been told these calls failed; forwarding one later would execute
|
|
28271
|
+
* it behind the model's back, so every forward path skips them. */
|
|
28272
|
+
deadToolCallIds = /* @__PURE__ */ new Set();
|
|
28273
|
+
/** Streamed block indexes suppressed as duplicate or dead tool_use blocks —
|
|
28274
|
+
* their deltas and stops must be ignored the same way child-executed indexes
|
|
28275
|
+
* are. Per message; reset by resetToolTracking. */
|
|
28276
|
+
suppressedStreamIndexes = /* @__PURE__ */ new Set();
|
|
28277
|
+
/** Bumped at every provider callback for this query. Stamped onto handlers at
|
|
28278
|
+
* registration so the stranded-handler drain can tell "registered before this
|
|
28279
|
+
* callback, provably settled" from "racing this callback's own stream". */
|
|
28280
|
+
callbackGeneration = 0;
|
|
27933
28281
|
turnToolCallIds = [];
|
|
27934
28282
|
turnToolCalls = [];
|
|
27935
28283
|
/**
|
|
@@ -27942,6 +28290,12 @@ var QueryContext = class {
|
|
|
27942
28290
|
* showed. Bounded by the number of tool calls in one query.
|
|
27943
28291
|
*/
|
|
27944
28292
|
queryToolNames = /* @__PURE__ */ new Map();
|
|
28293
|
+
/** id → last-known arguments, query-scoped like queryToolNames and for the
|
|
28294
|
+
* same reason: a late handler firing after resetToolTracking wiped the
|
|
28295
|
+
* per-message records must still be able to exact-match the parked/queued
|
|
28296
|
+
* result of ITS OWN call — without stored args the only fallback is
|
|
28297
|
+
* sole-same-name, which can hand it a LIVE sibling's id (vstack#1469). */
|
|
28298
|
+
queryToolArgs = /* @__PURE__ */ new Map();
|
|
27945
28299
|
claimedToolCallIds = /* @__PURE__ */ new Set();
|
|
27946
28300
|
deliveredToolResultIds = /* @__PURE__ */ new Set();
|
|
27947
28301
|
resolvedToolResultIds = /* @__PURE__ */ new Set();
|
|
@@ -27949,18 +28303,33 @@ var QueryContext = class {
|
|
|
27949
28303
|
reportedToolResultMismatch = false;
|
|
27950
28304
|
deferredUserMessages = [];
|
|
27951
28305
|
handledTerminalError = false;
|
|
28306
|
+
// Once visible text/thinking, a complete tool call, or a child-executed
|
|
28307
|
+
// CONNECTOR dispatch reaches Pi, the request must never be replayed on
|
|
28308
|
+
// another account (duplicate side effects). Query-scoped, not per-turn:
|
|
28309
|
+
// resetTurnState must not clear it.
|
|
28310
|
+
committedOutput = false;
|
|
28311
|
+
/** True when this query holds NO claim on the module-level shared session
|
|
28312
|
+
* record: a reentrant (subagent) query, or a foreign-conversation one-shot
|
|
28313
|
+
* (vstack#1001). Every shared-record mutation reachable from this context —
|
|
28314
|
+
* reportToolResultMismatch's needsRebuild/forceRotate mark, the cursor
|
|
28315
|
+
* advances on the tool-result-delivery and orphaned-result paths — must
|
|
28316
|
+
* no-op so the PARENT's record stays untouched. Assigned at fresh-query
|
|
28317
|
+
* setup; deliberately NOT cleared at query end, so a late orphaned tool
|
|
28318
|
+
* result arriving after this query settled is still attributed to it. */
|
|
28319
|
+
detachedFromSharedSession = false;
|
|
27952
28320
|
/** Armed grace timer for ending a tool_use turn whose terminal stream events
|
|
27953
28321
|
* (message_delta/message_stop) never arrive. The normal path ends the turn at
|
|
27954
28322
|
* message_stop, AFTER message_delta delivered the real output-token count;
|
|
27955
28323
|
* this is the deadlock backstop for streams that go silent instead. Managed
|
|
27956
28324
|
* by schedule/cancelToolUseTurnEnd in assistant-stream.ts. */
|
|
27957
28325
|
scheduledToolUseEnd = null;
|
|
27958
|
-
// Tool calls the CHILD executes itself (
|
|
27959
|
-
//
|
|
27960
|
-
//
|
|
27961
|
-
//
|
|
27962
|
-
// `user` message
|
|
27963
|
-
//
|
|
28326
|
+
// Tool calls the CHILD executes itself (see isChildExecutedTool).
|
|
28327
|
+
// Deliberately NOT in turnToolCalls/turnToolCallIds: those track calls Pi
|
|
28328
|
+
// owes a result for, and Pi owes nothing here. CONNECTORS ONLY — kept so the
|
|
28329
|
+
// child's real result can be recognized when it comes back on the SDK's
|
|
28330
|
+
// `user` message and audited. A child-internal built-in (ToolSearch et al.)
|
|
28331
|
+
// never enters this map: its result needs no recognition and no audit, only
|
|
28332
|
+
// its streamed deltas need skipping (childExecutedStreamIndexes below).
|
|
27964
28333
|
/** tool_use id → raw SDK tool name. */
|
|
27965
28334
|
childExecutedToolCalls = /* @__PURE__ */ new Map();
|
|
27966
28335
|
/**
|
|
@@ -28072,11 +28441,19 @@ var QueryContext = class {
|
|
|
28072
28441
|
this.reportedToolResultMismatch = false;
|
|
28073
28442
|
this.childExecutedToolCalls.clear();
|
|
28074
28443
|
this.childExecutedStreamIndexes.clear();
|
|
28444
|
+
this.suppressedStreamIndexes.clear();
|
|
28075
28445
|
}
|
|
28076
28446
|
/** Note a tool_use the child runs itself. `streamIndex` is present only on the
|
|
28077
|
-
* streamed path, where later deltas/stops for that block must be skipped
|
|
28447
|
+
* streamed path, where later deltas/stops for that block must be skipped —
|
|
28448
|
+
* that skip applies to every child-executed call. Result recognition and the
|
|
28449
|
+
* connector-call audit apply to CONNECTORS only: a child-internal built-in
|
|
28450
|
+
* (ToolSearch et al.) is tool plumbing, not account-data access, so nothing
|
|
28451
|
+
* about it belongs in the audit trail and no result needs matching. */
|
|
28078
28452
|
noteChildExecutedToolCall(id, rawName, streamIndex) {
|
|
28079
|
-
if (
|
|
28453
|
+
if (isConnectorTool(rawName)) {
|
|
28454
|
+
this.markOutputCommitted();
|
|
28455
|
+
}
|
|
28456
|
+
if (id && isConnectorTool(rawName)) {
|
|
28080
28457
|
this.childExecutedToolCalls.set(id, rawName);
|
|
28081
28458
|
if (!this.connectorCallAudit.has(id)) {
|
|
28082
28459
|
this.connectorCallAudit.set(id, {
|
|
@@ -28091,6 +28468,7 @@ var QueryContext = class {
|
|
|
28091
28468
|
recordToolCall(id, toolName, args = {}) {
|
|
28092
28469
|
if (!id) return;
|
|
28093
28470
|
this.queryToolNames.set(id, toolName);
|
|
28471
|
+
this.queryToolArgs.set(id, args);
|
|
28094
28472
|
if (!this.turnToolCallIds.includes(id)) this.turnToolCallIds.push(id);
|
|
28095
28473
|
const existing = this.turnToolCalls.find((call) => call.id === id);
|
|
28096
28474
|
if (existing) {
|
|
@@ -28102,16 +28480,32 @@ var QueryContext = class {
|
|
|
28102
28480
|
}
|
|
28103
28481
|
updateToolCallArgs(id, args) {
|
|
28104
28482
|
if (!id) return;
|
|
28483
|
+
this.queryToolArgs.set(id, args);
|
|
28105
28484
|
const existing = this.turnToolCalls.find((call) => call.id === id);
|
|
28106
28485
|
if (existing) existing.arguments = args;
|
|
28107
28486
|
}
|
|
28108
28487
|
hasRecordedToolCall(id) {
|
|
28109
28488
|
return Boolean(id && (this.turnToolCallIds.includes(id) || this.turnToolCalls.some((call) => call.id === id)));
|
|
28110
28489
|
}
|
|
28490
|
+
markOutputCommitted() {
|
|
28491
|
+
this.committedOutput = true;
|
|
28492
|
+
}
|
|
28111
28493
|
claimToolCall(toolName, args = {}) {
|
|
28112
28494
|
const unclaimed = this.turnToolCalls.filter((call) => !this.claimedToolCallIds.has(call.id));
|
|
28113
28495
|
const byName = unclaimed.filter((call) => call.toolName === toolName);
|
|
28114
28496
|
const exact = byName.filter((call) => sameArgs(call.arguments, args));
|
|
28497
|
+
const resultBacked = [.../* @__PURE__ */ new Set([...this.pendingResults.keys(), ...this.reapedResults.keys()])].filter((id) => !this.claimedToolCallIds.has(id) && this.queryToolNames.get(id) === toolName);
|
|
28498
|
+
const backedExact = resultBacked.filter((id) => sameArgs(this.queryToolArgs.get(id), args));
|
|
28499
|
+
const claimBacked = (id, viaExact) => {
|
|
28500
|
+
this.claimedToolCallIds.add(id);
|
|
28501
|
+
return {
|
|
28502
|
+
toolCallId: id,
|
|
28503
|
+
match: viaExact ? "tool-args" : "tool-name",
|
|
28504
|
+
ambiguous: viaExact && backedExact.length > 1,
|
|
28505
|
+
available: unclaimed.length,
|
|
28506
|
+
...!viaExact && hasRecordedArgs(this.queryToolArgs.get(id)) ? { argsMismatch: true } : {}
|
|
28507
|
+
};
|
|
28508
|
+
};
|
|
28115
28509
|
let chosen;
|
|
28116
28510
|
let match = "none";
|
|
28117
28511
|
let ambiguous = false;
|
|
@@ -28120,33 +28514,38 @@ var QueryContext = class {
|
|
|
28120
28514
|
chosen = exact[0];
|
|
28121
28515
|
match = "tool-args";
|
|
28122
28516
|
ambiguous = exact.length > 1;
|
|
28517
|
+
} else if (backedExact.length > 0) {
|
|
28518
|
+
return claimBacked(backedExact[0], true);
|
|
28123
28519
|
} else if (byName.length === 1) {
|
|
28124
28520
|
chosen = byName[0];
|
|
28125
28521
|
match = "tool-name";
|
|
28126
28522
|
argsMismatch = hasRecordedArgs(byName[0].arguments);
|
|
28127
28523
|
}
|
|
28524
|
+
if (!chosen && resultBacked.length === 1) return claimBacked(resultBacked[0], false);
|
|
28128
28525
|
if (!chosen) return { match: "none", ambiguous: false, available: unclaimed.length };
|
|
28129
28526
|
this.claimedToolCallIds.add(chosen.id);
|
|
28130
28527
|
return { toolCallId: chosen.id, match, ambiguous, available: unclaimed.length, ...argsMismatch ? { argsMismatch } : {} };
|
|
28131
28528
|
}
|
|
28132
28529
|
/**
|
|
28133
|
-
*
|
|
28530
|
+
* Move results still queued in `pendingResults` into the parked store and
|
|
28531
|
+
* report what moved.
|
|
28134
28532
|
*
|
|
28135
28533
|
* Called at a child MESSAGE boundary (message_start / the no-stream-events
|
|
28136
|
-
* assistant fallback)
|
|
28137
|
-
*
|
|
28138
|
-
*
|
|
28139
|
-
*
|
|
28140
|
-
*
|
|
28141
|
-
*
|
|
28142
|
-
*
|
|
28534
|
+
* assistant fallback). Left in pendingResults, each entry poisons every later
|
|
28535
|
+
* mismatch report for the whole query (queued>0 with 0/0 counters and no tool
|
|
28536
|
+
* names) and forces a session rebuild per turn. But the boundary does NOT
|
|
28537
|
+
* prove the handler gave up — the SDK staggers handler invocations, and the
|
|
28538
|
+
* 2026-08-17 deadlock session (vstack#1469) had three of five parallel
|
|
28539
|
+
* handlers fire after this reap destroyed their results. So the reap parks
|
|
28540
|
+
* instead of dropping: reports stay clean, and a late handler still gets its
|
|
28541
|
+
* real result through takeQueuedOrParkedResult.
|
|
28143
28542
|
*/
|
|
28144
28543
|
takeStaleQueuedResults() {
|
|
28145
28544
|
if (this.pendingResults.size === 0) return [];
|
|
28146
|
-
const stale = [...this.pendingResults.
|
|
28147
|
-
id,
|
|
28148
|
-
toolName: this.queryToolNames.get(id) ?? "unknown"
|
|
28149
|
-
})
|
|
28545
|
+
const stale = [...this.pendingResults.entries()].map(([id, result]) => {
|
|
28546
|
+
this.reapedResults.set(id, result);
|
|
28547
|
+
return { id, toolName: this.queryToolNames.get(id) ?? "unknown" };
|
|
28548
|
+
});
|
|
28150
28549
|
this.pendingResults.clear();
|
|
28151
28550
|
return stale;
|
|
28152
28551
|
}
|
|
@@ -28202,109 +28601,68 @@ var QueryContext = class {
|
|
|
28202
28601
|
};
|
|
28203
28602
|
}
|
|
28204
28603
|
};
|
|
28205
|
-
var
|
|
28206
|
-
|
|
28604
|
+
var QUERY_LANES_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.claude-bridge.query-lanes.v1");
|
|
28605
|
+
function queryLaneStore() {
|
|
28606
|
+
const host = globalThis;
|
|
28607
|
+
let store = host[QUERY_LANES_SYMBOL];
|
|
28608
|
+
if (!store) {
|
|
28609
|
+
store = {
|
|
28610
|
+
defaultLane: { current: new QueryContext(), stack: [] },
|
|
28611
|
+
sessionLanes: /* @__PURE__ */ new Map()
|
|
28612
|
+
};
|
|
28613
|
+
host[QUERY_LANES_SYMBOL] = store;
|
|
28614
|
+
}
|
|
28615
|
+
return store;
|
|
28616
|
+
}
|
|
28617
|
+
function lane() {
|
|
28618
|
+
const store = queryLaneStore();
|
|
28619
|
+
const sessionId = currentRequestLaneId();
|
|
28620
|
+
if (sessionId === void 0) return store.defaultLane;
|
|
28621
|
+
let state = store.sessionLanes.get(sessionId);
|
|
28622
|
+
if (!state) {
|
|
28623
|
+
state = { current: new QueryContext(), stack: [] };
|
|
28624
|
+
store.sessionLanes.set(sessionId, state);
|
|
28625
|
+
}
|
|
28626
|
+
return state;
|
|
28627
|
+
}
|
|
28207
28628
|
function ctx() {
|
|
28208
|
-
return
|
|
28629
|
+
return lane().current;
|
|
28209
28630
|
}
|
|
28210
28631
|
function stackDepth() {
|
|
28211
|
-
return
|
|
28632
|
+
return lane().stack.length;
|
|
28212
28633
|
}
|
|
28213
28634
|
function pushContext() {
|
|
28214
|
-
|
|
28215
|
-
|
|
28216
|
-
|
|
28635
|
+
const state = lane();
|
|
28636
|
+
if (!state.current.activeQuery) throw new Error("pushContext() called with no active query");
|
|
28637
|
+
state.stack.push(state.current);
|
|
28638
|
+
state.current = new QueryContext();
|
|
28217
28639
|
}
|
|
28218
28640
|
function popContext() {
|
|
28219
|
-
|
|
28220
|
-
|
|
28221
|
-
parent.
|
|
28222
|
-
|
|
28641
|
+
const state = lane();
|
|
28642
|
+
if (state.stack.length === 0) throw new Error("popContext() called with empty stack");
|
|
28643
|
+
const parent = state.stack[state.stack.length - 1];
|
|
28644
|
+
parent.deferredUserMessages.push(...state.current.deferredUserMessages);
|
|
28645
|
+
state.current = state.stack.pop();
|
|
28223
28646
|
}
|
|
28224
28647
|
function popContextFor(target) {
|
|
28225
|
-
|
|
28648
|
+
const state = lane();
|
|
28649
|
+
if (state.current === target) {
|
|
28226
28650
|
popContext();
|
|
28227
28651
|
return true;
|
|
28228
28652
|
}
|
|
28229
|
-
const idx =
|
|
28653
|
+
const idx = state.stack.indexOf(target);
|
|
28230
28654
|
if (idx < 0) return false;
|
|
28231
|
-
const parent = idx > 0 ?
|
|
28655
|
+
const parent = idx > 0 ? state.stack[idx - 1] : void 0;
|
|
28232
28656
|
parent?.deferredUserMessages.push(...target.deferredUserMessages);
|
|
28233
|
-
|
|
28657
|
+
state.stack.splice(idx, 1);
|
|
28234
28658
|
return true;
|
|
28235
28659
|
}
|
|
28236
|
-
|
|
28237
|
-
|
|
28238
|
-
|
|
28239
|
-
|
|
28240
|
-
|
|
28241
|
-
|
|
28242
|
-
function diagLogPath() {
|
|
28243
|
-
return process.env.CLAUDE_BRIDGE_DIAG_PATH || join3(piUserDir(), "claude-bridge-diag.log");
|
|
28244
|
-
}
|
|
28245
|
-
if (DEBUG) {
|
|
28246
|
-
try {
|
|
28247
|
-
mkdirSync2(dirname2(DEBUG_LOG_PATH), { recursive: true });
|
|
28248
|
-
mkdirSync2(dirname2(diagLogPath()), { recursive: true, mode: 448 });
|
|
28249
|
-
} catch {
|
|
28250
|
-
}
|
|
28251
|
-
}
|
|
28252
|
-
var moduleInstanceId = Math.random().toString(36).slice(2, 8);
|
|
28253
|
-
function debug(...args) {
|
|
28254
|
-
if (!DEBUG) return;
|
|
28255
|
-
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
28256
|
-
const fmt = (a) => {
|
|
28257
|
-
if (typeof a === "string") return a;
|
|
28258
|
-
if (a instanceof Error) return `${a.name}: ${a.message}${a.stack ? "\n" + a.stack : ""}`;
|
|
28259
|
-
return JSON.stringify(a);
|
|
28260
|
-
};
|
|
28261
|
-
const msg = args.map(fmt).join(" ");
|
|
28262
|
-
try {
|
|
28263
|
-
appendFileSync2(DEBUG_LOG_PATH, `[${ts2}] [${moduleInstanceId}] ${msg}
|
|
28264
|
-
`);
|
|
28265
|
-
} catch {
|
|
28266
|
-
}
|
|
28267
|
-
}
|
|
28268
|
-
var nextCliDebugSeq = 1;
|
|
28269
|
-
function makeCliDebugOptions(tag) {
|
|
28270
|
-
if (!DEBUG) return {};
|
|
28271
|
-
const seq = nextCliDebugSeq++;
|
|
28272
|
-
const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
28273
|
-
const logDir = join3(dirname2(DEBUG_LOG_PATH), "cc-cli-logs");
|
|
28274
|
-
try {
|
|
28275
|
-
mkdirSync2(logDir, { recursive: true });
|
|
28276
|
-
} catch {
|
|
28277
|
-
}
|
|
28278
|
-
const debugFile = join3(logDir, `${ts2}-${tag}-${seq}.log`);
|
|
28279
|
-
debug(`cli-debug: ${tag} #${seq} \u2192 ${debugFile}`);
|
|
28280
|
-
return {
|
|
28281
|
-
debug: true,
|
|
28282
|
-
debugFile,
|
|
28283
|
-
stderr: (data) => {
|
|
28284
|
-
for (const line of data.split(/\r?\n/)) {
|
|
28285
|
-
if (line) debug(`[cli-stderr ${tag}#${seq}] ${line}`);
|
|
28286
|
-
}
|
|
28287
|
-
}
|
|
28288
|
-
};
|
|
28289
|
-
}
|
|
28290
|
-
function diagDump(label, data) {
|
|
28291
|
-
try {
|
|
28292
|
-
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
28293
|
-
const entry = { ts: ts2, moduleInstanceId, label, ...data };
|
|
28294
|
-
const path = diagLogPath();
|
|
28295
|
-
try {
|
|
28296
|
-
mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
|
|
28297
|
-
} catch {
|
|
28298
|
-
}
|
|
28299
|
-
appendFileSync2(path, JSON.stringify(entry) + "\n", { mode: 384 });
|
|
28300
|
-
try {
|
|
28301
|
-
chmodSync(path, 384);
|
|
28302
|
-
} catch {
|
|
28303
|
-
}
|
|
28304
|
-
debug(`DIAG: ${label} (see ${path})`);
|
|
28305
|
-
} catch (error51) {
|
|
28306
|
-
debug(`DIAG FAILED: ${label}`, error51);
|
|
28307
|
-
}
|
|
28660
|
+
function deleteQueryLane(sessionId) {
|
|
28661
|
+
const store = queryLaneStore();
|
|
28662
|
+
if (sessionId === void 0) {
|
|
28663
|
+
store.defaultLane.current = new QueryContext();
|
|
28664
|
+
store.defaultLane.stack.length = 0;
|
|
28665
|
+
} else store.sessionLanes.delete(sessionId);
|
|
28308
28666
|
}
|
|
28309
28667
|
|
|
28310
28668
|
// src/tool-pairing-audit.ts
|
|
@@ -28321,6 +28679,44 @@ function toolResultIds(content) {
|
|
|
28321
28679
|
}
|
|
28322
28680
|
return ids;
|
|
28323
28681
|
}
|
|
28682
|
+
function recoverLaterToolResults(messages) {
|
|
28683
|
+
const recovered = [];
|
|
28684
|
+
for (let i = 0; i < messages.length; i++) {
|
|
28685
|
+
const assistant = messages[i];
|
|
28686
|
+
if (assistant?.role !== "assistant") continue;
|
|
28687
|
+
const uses = toolUses(assistant.content);
|
|
28688
|
+
if (uses.length === 0) continue;
|
|
28689
|
+
const target = messages[i + 1];
|
|
28690
|
+
if (target?.role !== "user") continue;
|
|
28691
|
+
const present = toolResultIds(target.content);
|
|
28692
|
+
const missing = uses.filter((use2) => !present.has(use2.id));
|
|
28693
|
+
if (missing.length === 0) continue;
|
|
28694
|
+
for (const use2 of missing) {
|
|
28695
|
+
let sourceBlock;
|
|
28696
|
+
let sourceUserIndex = -1;
|
|
28697
|
+
for (let j2 = i + 2; j2 < messages.length; j2++) {
|
|
28698
|
+
const candidate = messages[j2];
|
|
28699
|
+
if (candidate?.role !== "user") continue;
|
|
28700
|
+
sourceBlock = contentBlocks(candidate.content).find(
|
|
28701
|
+
(block) => block.type === "tool_result" && block.tool_use_id === use2.id
|
|
28702
|
+
);
|
|
28703
|
+
if (sourceBlock) {
|
|
28704
|
+
sourceUserIndex = j2;
|
|
28705
|
+
break;
|
|
28706
|
+
}
|
|
28707
|
+
}
|
|
28708
|
+
if (!sourceBlock) continue;
|
|
28709
|
+
const targetBlocks = Array.isArray(target.content) ? target.content : typeof target.content === "string" && target.content ? [{ type: "text", text: target.content }] : [];
|
|
28710
|
+
let insertAt = 0;
|
|
28711
|
+
while (insertAt < targetBlocks.length && targetBlocks[insertAt]?.type === "tool_result") insertAt++;
|
|
28712
|
+
targetBlocks.splice(insertAt, 0, { ...sourceBlock });
|
|
28713
|
+
target.content = targetBlocks;
|
|
28714
|
+
present.add(use2.id);
|
|
28715
|
+
recovered.push({ id: use2.id, assistantIndex: i, sourceUserIndex, targetUserIndex: i + 1 });
|
|
28716
|
+
}
|
|
28717
|
+
}
|
|
28718
|
+
return recovered;
|
|
28719
|
+
}
|
|
28324
28720
|
function findUnpairedToolUses(messages) {
|
|
28325
28721
|
const missing = [];
|
|
28326
28722
|
for (let i = 0; i < messages.length; i++) {
|
|
@@ -28368,11 +28764,62 @@ function summarizeMissingToolNames(missing) {
|
|
|
28368
28764
|
}
|
|
28369
28765
|
|
|
28370
28766
|
// src/bridge-state.ts
|
|
28371
|
-
var
|
|
28767
|
+
var SHARED_SESSION_LANES_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.claude-bridge.shared-session-lanes.v1");
|
|
28768
|
+
function sharedSessionLaneStore() {
|
|
28769
|
+
const host = globalThis;
|
|
28770
|
+
let store = host[SHARED_SESSION_LANES_SYMBOL];
|
|
28771
|
+
if (!store) {
|
|
28772
|
+
store = { defaultSession: null, sessions: /* @__PURE__ */ new Map() };
|
|
28773
|
+
host[SHARED_SESSION_LANES_SYMBOL] = store;
|
|
28774
|
+
}
|
|
28775
|
+
return store;
|
|
28776
|
+
}
|
|
28372
28777
|
var extensionApi;
|
|
28373
28778
|
var piUI;
|
|
28779
|
+
function getSharedSession() {
|
|
28780
|
+
const store = sharedSessionLaneStore();
|
|
28781
|
+
const sessionId = currentRequestLaneId();
|
|
28782
|
+
return sessionId === void 0 ? store.defaultSession : store.sessions.get(sessionId) ?? null;
|
|
28783
|
+
}
|
|
28374
28784
|
function setSharedSession(next) {
|
|
28375
|
-
|
|
28785
|
+
const store = sharedSessionLaneStore();
|
|
28786
|
+
const sessionId = currentRequestLaneId();
|
|
28787
|
+
if (sessionId === void 0) store.defaultSession = next;
|
|
28788
|
+
else store.sessions.set(sessionId, next);
|
|
28789
|
+
}
|
|
28790
|
+
function deleteSharedSessionLane(sessionId) {
|
|
28791
|
+
const store = sharedSessionLaneStore();
|
|
28792
|
+
if (sessionId === void 0) store.defaultSession = null;
|
|
28793
|
+
else store.sessions.delete(sessionId);
|
|
28794
|
+
}
|
|
28795
|
+
function clearSharedSessionLanes() {
|
|
28796
|
+
const store = sharedSessionLaneStore();
|
|
28797
|
+
store.sessions.clear();
|
|
28798
|
+
store.defaultSession = null;
|
|
28799
|
+
}
|
|
28800
|
+
var STARTED_LANES_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.claude-bridge.started-lanes.v1");
|
|
28801
|
+
function startedLaneStore() {
|
|
28802
|
+
const host = globalThis;
|
|
28803
|
+
let store = host[STARTED_LANES_SYMBOL];
|
|
28804
|
+
if (!store) {
|
|
28805
|
+
store = /* @__PURE__ */ new WeakMap();
|
|
28806
|
+
host[STARTED_LANES_SYMBOL] = store;
|
|
28807
|
+
}
|
|
28808
|
+
return store;
|
|
28809
|
+
}
|
|
28810
|
+
function recordStartedLane(sessionManager, sessionId) {
|
|
28811
|
+
startedLaneStore().set(sessionManager, sessionId);
|
|
28812
|
+
}
|
|
28813
|
+
function takeStartedLane(sessionManager) {
|
|
28814
|
+
const store = startedLaneStore();
|
|
28815
|
+
const sessionId = store.get(sessionManager);
|
|
28816
|
+
store.delete(sessionManager);
|
|
28817
|
+
return sessionId;
|
|
28818
|
+
}
|
|
28819
|
+
function markSessionForRebuild(opts = {}) {
|
|
28820
|
+
const sharedSession = getSharedSession();
|
|
28821
|
+
if (!sharedSession) return;
|
|
28822
|
+
setSharedSession({ ...sharedSession, needsRebuild: true, ...opts.forceRotate ? { forceRotate: true } : {} });
|
|
28376
28823
|
}
|
|
28377
28824
|
function setExtensionApi(next) {
|
|
28378
28825
|
extensionApi = next;
|
|
@@ -28428,7 +28875,7 @@ function reportSyntheticToolResultRepair(missing, context) {
|
|
|
28428
28875
|
sampledToolCallIds: sampledToolCallIds.slice(0, 12)
|
|
28429
28876
|
});
|
|
28430
28877
|
safeNotify(
|
|
28431
|
-
`Claude bridge: ${missing.length} missing tool result(s) repaired with an explicit error placeholder${toolNameSummary.length ? ` for ${toolNameSummary.join(", ")}` : ""}. Real tool output was lost before Claude session import;
|
|
28878
|
+
`Claude bridge: ${missing.length} missing tool result(s) repaired with an explicit error placeholder${toolNameSummary.length ? ` for ${toolNameSummary.join(", ")}` : ""}. Real tool output was lost before Claude session import; ${diagGuidance()}.`,
|
|
28432
28879
|
"error"
|
|
28433
28880
|
);
|
|
28434
28881
|
} catch (error51) {
|
|
@@ -28442,15 +28889,21 @@ function reportToolResultMismatch(queryCtx, reason, cwd, opts = {}) {
|
|
|
28442
28889
|
const hasMismatch = progress.expectedCount > 0 ? progress.unresolvedIds.length > 0 || progress.waitingCount > 0 || progress.queuedCount > 0 || progress.unmatchedResultCount > 0 : progress.waitingCount > 0 || progress.queuedCount > 0 || progress.unmatchedResultCount > 0;
|
|
28443
28890
|
if (!hasMismatch) return false;
|
|
28444
28891
|
queryCtx.reportedToolResultMismatch = true;
|
|
28445
|
-
if (
|
|
28446
|
-
|
|
28447
|
-
|
|
28892
|
+
if (!queryCtx.detachedFromSharedSession) markSessionForRebuild(opts);
|
|
28893
|
+
if (opts.expectedInterruption) {
|
|
28894
|
+
debug(
|
|
28895
|
+
`tool result delivery interrupted as expected during ${reason}; delivered=${progress.deliveredCount}/${progress.expectedCount} resolved=${progress.resolvedCount}/${progress.expectedCount} waiting=${progress.waitingCount} queued=${progress.queuedCount}`
|
|
28896
|
+
);
|
|
28897
|
+
return true;
|
|
28898
|
+
}
|
|
28448
28899
|
const toolNameSummary = compactToolNameSummary(progress.toolNames);
|
|
28900
|
+
const sharedSession = getSharedSession();
|
|
28449
28901
|
diagDump("tool_result_delivery_mismatch", {
|
|
28450
28902
|
reason,
|
|
28451
28903
|
cwd,
|
|
28452
28904
|
progress,
|
|
28453
28905
|
activeQueryExists: queryCtx.activeQuery !== null,
|
|
28906
|
+
detachedFromSharedSession: queryCtx.detachedFromSharedSession,
|
|
28454
28907
|
sharedSession: sharedSession ? {
|
|
28455
28908
|
sessionId: sharedSession.sessionId.slice(0, 8),
|
|
28456
28909
|
cursor: sharedSession.cursor,
|
|
@@ -28469,7 +28922,7 @@ function reportToolResultMismatch(queryCtx, reason, cwd, opts = {}) {
|
|
|
28469
28922
|
unmatchedResultIds: progress.unmatchedResultIds
|
|
28470
28923
|
});
|
|
28471
28924
|
safeNotify(
|
|
28472
|
-
`Claude bridge: tool result delivery interrupted during ${reason}; delivered ${progress.deliveredCount}/${progress.expectedCount}, resolved ${progress.resolvedCount}/${progress.expectedCount}, waiting=${progress.waitingCount}, queued=${progress.queuedCount}, unmatched=${progress.unmatchedResultCount}${toolNameSummary.length ? `, tools=${toolNameSummary.join(", ")}` : ""}. Claude session will rebuild before the next turn;
|
|
28925
|
+
`Claude bridge: tool result delivery interrupted during ${reason}; delivered ${progress.deliveredCount}/${progress.expectedCount}, resolved ${progress.resolvedCount}/${progress.expectedCount}, waiting=${progress.waitingCount}, queued=${progress.queuedCount}, unmatched=${progress.unmatchedResultCount}${toolNameSummary.length ? `, tools=${toolNameSummary.join(", ")}` : ""}. ` + (queryCtx.detachedFromSharedSession ? `Detached one-shot query \u2014 shared Claude session record left untouched; ${diagGuidance()}.` : `Claude session will rebuild before the next turn; ${diagGuidance()}.`),
|
|
28473
28926
|
"error"
|
|
28474
28927
|
);
|
|
28475
28928
|
return true;
|
|
@@ -28480,10 +28933,16 @@ function reportToolResultMismatch(queryCtx, reason, cwd, opts = {}) {
|
|
|
28480
28933
|
}
|
|
28481
28934
|
function __testSetBridgeIntegrityState(state) {
|
|
28482
28935
|
if ("ui" in state) piUI = state.ui;
|
|
28483
|
-
if ("sharedSession" in state)
|
|
28936
|
+
if ("sharedSession" in state) {
|
|
28937
|
+
if (currentRequestLaneId() !== void 0) setSharedSession(state.sharedSession ?? null);
|
|
28938
|
+
else {
|
|
28939
|
+
clearSharedSessionLanes();
|
|
28940
|
+
sharedSessionLaneStore().defaultSession = state.sharedSession ?? null;
|
|
28941
|
+
}
|
|
28942
|
+
}
|
|
28484
28943
|
}
|
|
28485
28944
|
function __testGetBridgeIntegrityState() {
|
|
28486
|
-
return { sharedSession };
|
|
28945
|
+
return { sharedSession: getSharedSession() };
|
|
28487
28946
|
}
|
|
28488
28947
|
|
|
28489
28948
|
// src/connector-audit.ts
|
|
@@ -28622,16 +29081,16 @@ function claudeAuthSourceLabel(env = process.env) {
|
|
|
28622
29081
|
if (env.ANTHROPIC_AUTH_TOKEN?.trim()) return "ANTHROPIC_AUTH_TOKEN";
|
|
28623
29082
|
return "Claude Code login";
|
|
28624
29083
|
}
|
|
28625
|
-
function buildNativeProvider(piAi2, models, streamSimple, env = process.env) {
|
|
29084
|
+
function buildNativeProvider(piAi2, models, streamSimple, env = process.env, hasCredentials = () => hasClaudeCredentials(env)) {
|
|
28626
29085
|
if (!supportsNativeProvider(piAi2)) throw new Error(NATIVE_PROVIDER_UNSUPPORTED_MESSAGE);
|
|
28627
|
-
const stamped = models.map((model) => ({ api: "claude-bridge", baseUrl: "claude-bridge", provider: PROVIDER_ID
|
|
29086
|
+
const stamped = models.map((model) => ({ ...model, api: "claude-bridge", baseUrl: "claude-bridge", provider: PROVIDER_ID }));
|
|
28628
29087
|
const streams = {
|
|
28629
29088
|
stream: streamSimple,
|
|
28630
29089
|
streamSimple
|
|
28631
29090
|
};
|
|
28632
29091
|
return piAi2.createProvider({
|
|
28633
29092
|
id: PROVIDER_ID,
|
|
28634
|
-
name: "
|
|
29093
|
+
name: "Pi Claude",
|
|
28635
29094
|
baseUrl: "claude-bridge",
|
|
28636
29095
|
auth: {
|
|
28637
29096
|
apiKey: {
|
|
@@ -28639,8 +29098,8 @@ function buildNativeProvider(piAi2, models, streamSimple, env = process.env) {
|
|
|
28639
29098
|
// check() exists so pi's availability pass never has to call
|
|
28640
29099
|
// resolve(): both are existence-only, but check is the documented
|
|
28641
29100
|
// side-effect-free probe.
|
|
28642
|
-
check: async () =>
|
|
28643
|
-
resolve: async () =>
|
|
29101
|
+
check: async () => hasCredentials() ? { type: "api_key", source: claudeAuthSourceLabel(env) } : void 0,
|
|
29102
|
+
resolve: async () => hasCredentials() ? { auth: { apiKey: "not-used" }, source: claudeAuthSourceLabel(env) } : void 0
|
|
28644
29103
|
}
|
|
28645
29104
|
},
|
|
28646
29105
|
models: stamped,
|
|
@@ -28648,177 +29107,6 @@ function buildNativeProvider(piAi2, models, streamSimple, env = process.env) {
|
|
|
28648
29107
|
});
|
|
28649
29108
|
}
|
|
28650
29109
|
|
|
28651
|
-
// src/agents-md.ts
|
|
28652
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
28653
|
-
import { dirname as dirname3, join as join5, resolve as resolve3 } from "path";
|
|
28654
|
-
function globalAgentsPath() {
|
|
28655
|
-
return join5(piUserDir(), "AGENTS.md");
|
|
28656
|
-
}
|
|
28657
|
-
function resolveAgentsMdPath() {
|
|
28658
|
-
if (isolatedFromEnv()) return void 0;
|
|
28659
|
-
const fromCwd = findAgentsMdInParents(process.cwd());
|
|
28660
|
-
if (fromCwd) return fromCwd;
|
|
28661
|
-
const globalPath = globalAgentsPath();
|
|
28662
|
-
if (existsSync4(globalPath)) return globalPath;
|
|
28663
|
-
return void 0;
|
|
28664
|
-
}
|
|
28665
|
-
function findAgentsMdInParents(startDir) {
|
|
28666
|
-
let current = resolve3(startDir);
|
|
28667
|
-
while (true) {
|
|
28668
|
-
const candidate = join5(current, "AGENTS.md");
|
|
28669
|
-
if (existsSync4(candidate)) return candidate;
|
|
28670
|
-
const parent = dirname3(current);
|
|
28671
|
-
if (parent === current) break;
|
|
28672
|
-
current = parent;
|
|
28673
|
-
}
|
|
28674
|
-
return void 0;
|
|
28675
|
-
}
|
|
28676
|
-
function extractAgentsAppend() {
|
|
28677
|
-
const agentsPath = resolveAgentsMdPath();
|
|
28678
|
-
if (!agentsPath) return void 0;
|
|
28679
|
-
try {
|
|
28680
|
-
const content = readFileSync4(agentsPath, "utf-8").trim();
|
|
28681
|
-
if (!content) return void 0;
|
|
28682
|
-
const sanitized = sanitizeAgentsContent(content);
|
|
28683
|
-
return sanitized.length > 0 ? `# CLAUDE.md
|
|
28684
|
-
|
|
28685
|
-
${sanitized}` : void 0;
|
|
28686
|
-
} catch {
|
|
28687
|
-
return void 0;
|
|
28688
|
-
}
|
|
28689
|
-
}
|
|
28690
|
-
function sanitizeAgentsContent(content) {
|
|
28691
|
-
let sanitized = content;
|
|
28692
|
-
sanitized = sanitized.replace(/~\/\.pi\b/gi, "~/.claude");
|
|
28693
|
-
sanitized = sanitized.replace(/(^|[\s'"`])\.pi\//g, "$1.claude/");
|
|
28694
|
-
sanitized = sanitized.replace(/\b\.pi\b/gi, ".claude");
|
|
28695
|
-
sanitized = sanitized.replace(/\bpi\b/gi, "environment");
|
|
28696
|
-
return sanitized;
|
|
28697
|
-
}
|
|
28698
|
-
|
|
28699
|
-
// src/prompt-context.ts
|
|
28700
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
28701
|
-
import { dirname as dirname4, join as join6, resolve as resolve4 } from "path";
|
|
28702
|
-
function readTrimmed(path) {
|
|
28703
|
-
try {
|
|
28704
|
-
if (!existsSync5(path)) return void 0;
|
|
28705
|
-
const content = readFileSync5(path, "utf8").trim();
|
|
28706
|
-
return content.length > 0 ? content : void 0;
|
|
28707
|
-
} catch {
|
|
28708
|
-
return void 0;
|
|
28709
|
-
}
|
|
28710
|
-
}
|
|
28711
|
-
function findProjectAppendSystem(startDir) {
|
|
28712
|
-
let current = resolve4(startDir);
|
|
28713
|
-
while (true) {
|
|
28714
|
-
const candidate = join6(current, ".pi", "APPEND_SYSTEM.md");
|
|
28715
|
-
if (existsSync5(candidate)) return candidate;
|
|
28716
|
-
const parent = dirname4(current);
|
|
28717
|
-
if (parent === current) break;
|
|
28718
|
-
current = parent;
|
|
28719
|
-
}
|
|
28720
|
-
return void 0;
|
|
28721
|
-
}
|
|
28722
|
-
function readAppendSystemPromptFiles(cwd) {
|
|
28723
|
-
const files = [
|
|
28724
|
-
{ label: "global APPEND_SYSTEM.md", path: join6(piUserDir(), "APPEND_SYSTEM.md") }
|
|
28725
|
-
];
|
|
28726
|
-
const projectPath = isolatedFromEnv() ? void 0 : findProjectAppendSystem(cwd);
|
|
28727
|
-
if (projectPath) files.push({ label: "project .pi/APPEND_SYSTEM.md", path: projectPath });
|
|
28728
|
-
const seen = /* @__PURE__ */ new Set();
|
|
28729
|
-
const output = [];
|
|
28730
|
-
for (const file2 of files) {
|
|
28731
|
-
if (seen.has(file2.path)) continue;
|
|
28732
|
-
seen.add(file2.path);
|
|
28733
|
-
const content = readTrimmed(file2.path);
|
|
28734
|
-
if (content) output.push({ label: file2.label, content });
|
|
28735
|
-
}
|
|
28736
|
-
return output;
|
|
28737
|
-
}
|
|
28738
|
-
function splitPromptBlocks(systemPrompt) {
|
|
28739
|
-
return (systemPrompt ?? "").split(/\n{2,}/).map((block) => block.trim()).filter(Boolean);
|
|
28740
|
-
}
|
|
28741
|
-
function extractHeadingSection(systemPrompt, headings) {
|
|
28742
|
-
if (!systemPrompt) return void 0;
|
|
28743
|
-
let start = -1;
|
|
28744
|
-
for (const heading of headings) {
|
|
28745
|
-
const index = systemPrompt.indexOf(heading);
|
|
28746
|
-
if (index >= 0 && (start < 0 || index < start)) start = index;
|
|
28747
|
-
}
|
|
28748
|
-
if (start < 0) return void 0;
|
|
28749
|
-
const rest = systemPrompt.slice(start).trim();
|
|
28750
|
-
const endCandidates = [
|
|
28751
|
-
rest.slice(1).search(/\n##\s+/),
|
|
28752
|
-
rest.search(/\n<\/project_instructions>/),
|
|
28753
|
-
rest.search(/\n<\/project_context>/)
|
|
28754
|
-
].map((index, offset) => index >= 0 && offset === 0 ? index + 1 : index).filter((index) => index >= 0);
|
|
28755
|
-
const end = endCandidates.length > 0 ? Math.min(...endCandidates) : -1;
|
|
28756
|
-
return (end >= 0 ? rest.slice(0, end) : rest).trim();
|
|
28757
|
-
}
|
|
28758
|
-
function extractBlockByMarkers(systemPrompt, markers) {
|
|
28759
|
-
for (const block of splitPromptBlocks(systemPrompt)) {
|
|
28760
|
-
if (markers.some((marker) => marker.test(block))) return block;
|
|
28761
|
-
}
|
|
28762
|
-
return void 0;
|
|
28763
|
-
}
|
|
28764
|
-
function buildPromptContextAppend(systemPrompt, cwd, settings) {
|
|
28765
|
-
const parts = [];
|
|
28766
|
-
const labels = [];
|
|
28767
|
-
if (settings.includeAppendSystemPromptMd) {
|
|
28768
|
-
for (const file2 of readAppendSystemPromptFiles(cwd)) {
|
|
28769
|
-
parts.push(xmlBlock("append_system_prompt", { label: file2.label }, file2.content));
|
|
28770
|
-
labels.push(file2.label);
|
|
28771
|
-
}
|
|
28772
|
-
}
|
|
28773
|
-
if (settings.includeProjectAgentsHook) {
|
|
28774
|
-
const projectAgents = extractHeadingSection(systemPrompt, ["## Project Agents", "## Project Subagents"]);
|
|
28775
|
-
if (projectAgents) {
|
|
28776
|
-
parts.push(xmlBlock("before_agent_start", { source: "project-agents" }, projectAgents));
|
|
28777
|
-
labels.push("project agents hook");
|
|
28778
|
-
}
|
|
28779
|
-
}
|
|
28780
|
-
if (settings.includeTaskPanelHook) {
|
|
28781
|
-
const taskReminder = extractBlockByMarkers(systemPrompt, [/^Task workflow reminder:/]);
|
|
28782
|
-
if (taskReminder) {
|
|
28783
|
-
parts.push(xmlBlock("before_agent_start", { source: "task-panel" }, taskReminder));
|
|
28784
|
-
labels.push("task panel hook");
|
|
28785
|
-
}
|
|
28786
|
-
}
|
|
28787
|
-
if (settings.includeCavemanHook) {
|
|
28788
|
-
const caveman = extractBlockByMarkers(systemPrompt, [/^You MUST respond in caveman /m]);
|
|
28789
|
-
if (caveman) {
|
|
28790
|
-
parts.push(xmlBlock("before_agent_start", { source: "caveman" }, caveman));
|
|
28791
|
-
labels.push("caveman hook");
|
|
28792
|
-
}
|
|
28793
|
-
}
|
|
28794
|
-
if (parts.length === 0) return { labels };
|
|
28795
|
-
return {
|
|
28796
|
-
labels,
|
|
28797
|
-
text: xmlBlock(
|
|
28798
|
-
"forwarded_pi_context",
|
|
28799
|
-
{},
|
|
28800
|
-
[
|
|
28801
|
-
"The following content was explicitly enabled in pi-claude-bridge settings and comes from Pi prompt files or before_agent_start prompt hooks.",
|
|
28802
|
-
...parts
|
|
28803
|
-
].join("\n\n"),
|
|
28804
|
-
false
|
|
28805
|
-
)
|
|
28806
|
-
};
|
|
28807
|
-
}
|
|
28808
|
-
function escapeXmlAttr(value) {
|
|
28809
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
28810
|
-
}
|
|
28811
|
-
function escapeXmlText(value) {
|
|
28812
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
28813
|
-
}
|
|
28814
|
-
function xmlBlock(tag, attrs, content, escapeContent = true) {
|
|
28815
|
-
const attrText = Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeXmlAttr(value)}"`).join("");
|
|
28816
|
-
const body = escapeContent ? escapeXmlText(content.trim()) : content.trim();
|
|
28817
|
-
return `<${tag}${attrText}>
|
|
28818
|
-
${body}
|
|
28819
|
-
</${tag}>`;
|
|
28820
|
-
}
|
|
28821
|
-
|
|
28822
29110
|
// node_modules/zod/v4/classic/external.js
|
|
28823
29111
|
var external_exports = {};
|
|
28824
29112
|
__export(external_exports, {
|
|
@@ -30254,8 +30542,8 @@ function prettifyError(error51) {
|
|
|
30254
30542
|
}
|
|
30255
30543
|
|
|
30256
30544
|
// node_modules/zod/v4/core/parse.js
|
|
30257
|
-
var _parse = (_Err) => (schema, value,
|
|
30258
|
-
const ctx2 =
|
|
30545
|
+
var _parse = (_Err) => (schema, value, _ctx, _params) => {
|
|
30546
|
+
const ctx2 = _ctx ? { ..._ctx, async: false } : { async: false };
|
|
30259
30547
|
const result = schema._zod.run({ value, issues: [] }, ctx2);
|
|
30260
30548
|
if (result instanceof Promise) {
|
|
30261
30549
|
throw new $ZodAsyncError();
|
|
@@ -30268,8 +30556,8 @@ var _parse = (_Err) => (schema, value, _ctx2, _params) => {
|
|
|
30268
30556
|
return result.value;
|
|
30269
30557
|
};
|
|
30270
30558
|
var parse2 = /* @__PURE__ */ _parse($ZodRealError);
|
|
30271
|
-
var _parseAsync = (_Err) => async (schema, value,
|
|
30272
|
-
const ctx2 =
|
|
30559
|
+
var _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
|
|
30560
|
+
const ctx2 = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
30273
30561
|
let result = schema._zod.run({ value, issues: [] }, ctx2);
|
|
30274
30562
|
if (result instanceof Promise)
|
|
30275
30563
|
result = await result;
|
|
@@ -30281,8 +30569,8 @@ var _parseAsync = (_Err) => async (schema, value, _ctx2, params) => {
|
|
|
30281
30569
|
return result.value;
|
|
30282
30570
|
};
|
|
30283
30571
|
var parseAsync = /* @__PURE__ */ _parseAsync($ZodRealError);
|
|
30284
|
-
var _safeParse = (_Err) => (schema, value,
|
|
30285
|
-
const ctx2 =
|
|
30572
|
+
var _safeParse = (_Err) => (schema, value, _ctx) => {
|
|
30573
|
+
const ctx2 = _ctx ? { ..._ctx, async: false } : { async: false };
|
|
30286
30574
|
const result = schema._zod.run({ value, issues: [] }, ctx2);
|
|
30287
30575
|
if (result instanceof Promise) {
|
|
30288
30576
|
throw new $ZodAsyncError();
|
|
@@ -30293,8 +30581,8 @@ var _safeParse = (_Err) => (schema, value, _ctx2) => {
|
|
|
30293
30581
|
} : { success: true, data: result.value };
|
|
30294
30582
|
};
|
|
30295
30583
|
var safeParse = /* @__PURE__ */ _safeParse($ZodRealError);
|
|
30296
|
-
var _safeParseAsync = (_Err) => async (schema, value,
|
|
30297
|
-
const ctx2 =
|
|
30584
|
+
var _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
|
|
30585
|
+
const ctx2 = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
30298
30586
|
let result = schema._zod.run({ value, issues: [] }, ctx2);
|
|
30299
30587
|
if (result instanceof Promise)
|
|
30300
30588
|
result = await result;
|
|
@@ -30304,40 +30592,40 @@ var _safeParseAsync = (_Err) => async (schema, value, _ctx2) => {
|
|
|
30304
30592
|
} : { success: true, data: result.value };
|
|
30305
30593
|
};
|
|
30306
30594
|
var safeParseAsync = /* @__PURE__ */ _safeParseAsync($ZodRealError);
|
|
30307
|
-
var _encode = (_Err) => (schema, value,
|
|
30308
|
-
const ctx2 =
|
|
30595
|
+
var _encode = (_Err) => (schema, value, _ctx) => {
|
|
30596
|
+
const ctx2 = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
30309
30597
|
return _parse(_Err)(schema, value, ctx2);
|
|
30310
30598
|
};
|
|
30311
30599
|
var encode = /* @__PURE__ */ _encode($ZodRealError);
|
|
30312
|
-
var _decode = (_Err) => (schema, value,
|
|
30313
|
-
return _parse(_Err)(schema, value,
|
|
30600
|
+
var _decode = (_Err) => (schema, value, _ctx) => {
|
|
30601
|
+
return _parse(_Err)(schema, value, _ctx);
|
|
30314
30602
|
};
|
|
30315
30603
|
var decode = /* @__PURE__ */ _decode($ZodRealError);
|
|
30316
|
-
var _encodeAsync = (_Err) => async (schema, value,
|
|
30317
|
-
const ctx2 =
|
|
30604
|
+
var _encodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
30605
|
+
const ctx2 = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
30318
30606
|
return _parseAsync(_Err)(schema, value, ctx2);
|
|
30319
30607
|
};
|
|
30320
30608
|
var encodeAsync = /* @__PURE__ */ _encodeAsync($ZodRealError);
|
|
30321
|
-
var _decodeAsync = (_Err) => async (schema, value,
|
|
30322
|
-
return _parseAsync(_Err)(schema, value,
|
|
30609
|
+
var _decodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
30610
|
+
return _parseAsync(_Err)(schema, value, _ctx);
|
|
30323
30611
|
};
|
|
30324
30612
|
var decodeAsync = /* @__PURE__ */ _decodeAsync($ZodRealError);
|
|
30325
|
-
var _safeEncode = (_Err) => (schema, value,
|
|
30326
|
-
const ctx2 =
|
|
30613
|
+
var _safeEncode = (_Err) => (schema, value, _ctx) => {
|
|
30614
|
+
const ctx2 = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
30327
30615
|
return _safeParse(_Err)(schema, value, ctx2);
|
|
30328
30616
|
};
|
|
30329
30617
|
var safeEncode = /* @__PURE__ */ _safeEncode($ZodRealError);
|
|
30330
|
-
var _safeDecode = (_Err) => (schema, value,
|
|
30331
|
-
return _safeParse(_Err)(schema, value,
|
|
30618
|
+
var _safeDecode = (_Err) => (schema, value, _ctx) => {
|
|
30619
|
+
return _safeParse(_Err)(schema, value, _ctx);
|
|
30332
30620
|
};
|
|
30333
30621
|
var safeDecode = /* @__PURE__ */ _safeDecode($ZodRealError);
|
|
30334
|
-
var _safeEncodeAsync = (_Err) => async (schema, value,
|
|
30335
|
-
const ctx2 =
|
|
30622
|
+
var _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
30623
|
+
const ctx2 = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
30336
30624
|
return _safeParseAsync(_Err)(schema, value, ctx2);
|
|
30337
30625
|
};
|
|
30338
30626
|
var safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync($ZodRealError);
|
|
30339
|
-
var _safeDecodeAsync = (_Err) => async (schema, value,
|
|
30340
|
-
return _safeParseAsync(_Err)(schema, value,
|
|
30627
|
+
var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
30628
|
+
return _safeParseAsync(_Err)(schema, value, _ctx);
|
|
30341
30629
|
};
|
|
30342
30630
|
var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync($ZodRealError);
|
|
30343
30631
|
|
|
@@ -31531,7 +31819,7 @@ var $ZodCustomStringFormat = /* @__PURE__ */ $constructor("$ZodCustomStringForma
|
|
|
31531
31819
|
var $ZodNumber = /* @__PURE__ */ $constructor("$ZodNumber", (inst, def) => {
|
|
31532
31820
|
$ZodType.init(inst, def);
|
|
31533
31821
|
inst._zod.pattern = inst._zod.bag.pattern ?? number;
|
|
31534
|
-
inst._zod.parse = (payload,
|
|
31822
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31535
31823
|
if (def.coerce)
|
|
31536
31824
|
try {
|
|
31537
31825
|
payload.value = Number(payload.value);
|
|
@@ -31559,7 +31847,7 @@ var $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst, d
|
|
|
31559
31847
|
var $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
31560
31848
|
$ZodType.init(inst, def);
|
|
31561
31849
|
inst._zod.pattern = boolean;
|
|
31562
|
-
inst._zod.parse = (payload,
|
|
31850
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31563
31851
|
if (def.coerce)
|
|
31564
31852
|
try {
|
|
31565
31853
|
payload.value = Boolean(payload.value);
|
|
@@ -31580,7 +31868,7 @@ var $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
|
31580
31868
|
var $ZodBigInt = /* @__PURE__ */ $constructor("$ZodBigInt", (inst, def) => {
|
|
31581
31869
|
$ZodType.init(inst, def);
|
|
31582
31870
|
inst._zod.pattern = bigint;
|
|
31583
|
-
inst._zod.parse = (payload,
|
|
31871
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31584
31872
|
if (def.coerce)
|
|
31585
31873
|
try {
|
|
31586
31874
|
payload.value = BigInt(payload.value);
|
|
@@ -31603,7 +31891,7 @@ var $ZodBigIntFormat = /* @__PURE__ */ $constructor("$ZodBigIntFormat", (inst, d
|
|
|
31603
31891
|
});
|
|
31604
31892
|
var $ZodSymbol = /* @__PURE__ */ $constructor("$ZodSymbol", (inst, def) => {
|
|
31605
31893
|
$ZodType.init(inst, def);
|
|
31606
|
-
inst._zod.parse = (payload,
|
|
31894
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31607
31895
|
const input = payload.value;
|
|
31608
31896
|
if (typeof input === "symbol")
|
|
31609
31897
|
return payload;
|
|
@@ -31620,7 +31908,7 @@ var $ZodUndefined = /* @__PURE__ */ $constructor("$ZodUndefined", (inst, def) =>
|
|
|
31620
31908
|
$ZodType.init(inst, def);
|
|
31621
31909
|
inst._zod.pattern = _undefined;
|
|
31622
31910
|
inst._zod.values = /* @__PURE__ */ new Set([void 0]);
|
|
31623
|
-
inst._zod.parse = (payload,
|
|
31911
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31624
31912
|
const input = payload.value;
|
|
31625
31913
|
if (typeof input === "undefined")
|
|
31626
31914
|
return payload;
|
|
@@ -31637,7 +31925,7 @@ var $ZodNull = /* @__PURE__ */ $constructor("$ZodNull", (inst, def) => {
|
|
|
31637
31925
|
$ZodType.init(inst, def);
|
|
31638
31926
|
inst._zod.pattern = _null;
|
|
31639
31927
|
inst._zod.values = /* @__PURE__ */ new Set([null]);
|
|
31640
|
-
inst._zod.parse = (payload,
|
|
31928
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31641
31929
|
const input = payload.value;
|
|
31642
31930
|
if (input === null)
|
|
31643
31931
|
return payload;
|
|
@@ -31660,7 +31948,7 @@ var $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
|
31660
31948
|
});
|
|
31661
31949
|
var $ZodNever = /* @__PURE__ */ $constructor("$ZodNever", (inst, def) => {
|
|
31662
31950
|
$ZodType.init(inst, def);
|
|
31663
|
-
inst._zod.parse = (payload,
|
|
31951
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31664
31952
|
payload.issues.push({
|
|
31665
31953
|
expected: "never",
|
|
31666
31954
|
code: "invalid_type",
|
|
@@ -31672,7 +31960,7 @@ var $ZodNever = /* @__PURE__ */ $constructor("$ZodNever", (inst, def) => {
|
|
|
31672
31960
|
});
|
|
31673
31961
|
var $ZodVoid = /* @__PURE__ */ $constructor("$ZodVoid", (inst, def) => {
|
|
31674
31962
|
$ZodType.init(inst, def);
|
|
31675
|
-
inst._zod.parse = (payload,
|
|
31963
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31676
31964
|
const input = payload.value;
|
|
31677
31965
|
if (typeof input === "undefined")
|
|
31678
31966
|
return payload;
|
|
@@ -31687,7 +31975,7 @@ var $ZodVoid = /* @__PURE__ */ $constructor("$ZodVoid", (inst, def) => {
|
|
|
31687
31975
|
});
|
|
31688
31976
|
var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
|
|
31689
31977
|
$ZodType.init(inst, def);
|
|
31690
|
-
inst._zod.parse = (payload,
|
|
31978
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
31691
31979
|
if (def.coerce) {
|
|
31692
31980
|
try {
|
|
31693
31981
|
payload.value = new Date(payload.value);
|
|
@@ -32634,7 +32922,7 @@ var $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
|
32634
32922
|
const valuesSet = new Set(values);
|
|
32635
32923
|
inst._zod.values = valuesSet;
|
|
32636
32924
|
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex(o) : o.toString()).join("|")})$`);
|
|
32637
|
-
inst._zod.parse = (payload,
|
|
32925
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
32638
32926
|
const input = payload.value;
|
|
32639
32927
|
if (valuesSet.has(input)) {
|
|
32640
32928
|
return payload;
|
|
@@ -32656,7 +32944,7 @@ var $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
|
32656
32944
|
const values = new Set(def.values);
|
|
32657
32945
|
inst._zod.values = values;
|
|
32658
32946
|
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
|
|
32659
|
-
inst._zod.parse = (payload,
|
|
32947
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
32660
32948
|
const input = payload.value;
|
|
32661
32949
|
if (values.has(input)) {
|
|
32662
32950
|
return payload;
|
|
@@ -32672,7 +32960,7 @@ var $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
|
32672
32960
|
});
|
|
32673
32961
|
var $ZodFile = /* @__PURE__ */ $constructor("$ZodFile", (inst, def) => {
|
|
32674
32962
|
$ZodType.init(inst, def);
|
|
32675
|
-
inst._zod.parse = (payload,
|
|
32963
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
32676
32964
|
const input = payload.value;
|
|
32677
32965
|
if (input instanceof File)
|
|
32678
32966
|
return payload;
|
|
@@ -32890,7 +33178,7 @@ var $ZodCatch = /* @__PURE__ */ $constructor("$ZodCatch", (inst, def) => {
|
|
|
32890
33178
|
});
|
|
32891
33179
|
var $ZodNaN = /* @__PURE__ */ $constructor("$ZodNaN", (inst, def) => {
|
|
32892
33180
|
$ZodType.init(inst, def);
|
|
32893
|
-
inst._zod.parse = (payload,
|
|
33181
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
32894
33182
|
if (typeof payload.value !== "number" || !Number.isNaN(payload.value)) {
|
|
32895
33183
|
payload.issues.push({
|
|
32896
33184
|
input: payload.value,
|
|
@@ -33026,7 +33314,7 @@ var $ZodTemplateLiteral = /* @__PURE__ */ $constructor("$ZodTemplateLiteral", (i
|
|
|
33026
33314
|
}
|
|
33027
33315
|
}
|
|
33028
33316
|
inst._zod.pattern = new RegExp(`^${regexParts.join("")}$`);
|
|
33029
|
-
inst._zod.parse = (payload,
|
|
33317
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
33030
33318
|
if (typeof payload.value !== "string") {
|
|
33031
33319
|
payload.issues.push({
|
|
33032
33320
|
input: payload.value,
|
|
@@ -33080,7 +33368,7 @@ var $ZodFunction = /* @__PURE__ */ $constructor("$ZodFunction", (inst, def) => {
|
|
|
33080
33368
|
return result;
|
|
33081
33369
|
};
|
|
33082
33370
|
};
|
|
33083
|
-
inst._zod.parse = (payload,
|
|
33371
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
33084
33372
|
if (typeof payload.value !== "function") {
|
|
33085
33373
|
payload.issues.push({
|
|
33086
33374
|
code: "invalid_type",
|
|
@@ -40520,8 +40808,8 @@ function finalize(ctx2, schema) {
|
|
|
40520
40808
|
throw new Error("Error converting schema to JSON.");
|
|
40521
40809
|
}
|
|
40522
40810
|
}
|
|
40523
|
-
function isTransforming(_schema,
|
|
40524
|
-
const ctx2 =
|
|
40811
|
+
function isTransforming(_schema, _ctx) {
|
|
40812
|
+
const ctx2 = _ctx ?? { seen: /* @__PURE__ */ new Set() };
|
|
40525
40813
|
if (ctx2.seen.has(_schema))
|
|
40526
40814
|
return false;
|
|
40527
40815
|
ctx2.seen.add(_schema);
|
|
@@ -40661,7 +40949,7 @@ var numberProcessor = (schema, ctx2, _json, _params) => {
|
|
|
40661
40949
|
if (typeof multipleOf === "number")
|
|
40662
40950
|
json2.multipleOf = multipleOf;
|
|
40663
40951
|
};
|
|
40664
|
-
var booleanProcessor = (_schema,
|
|
40952
|
+
var booleanProcessor = (_schema, _ctx, json2, _params) => {
|
|
40665
40953
|
json2.type = "boolean";
|
|
40666
40954
|
};
|
|
40667
40955
|
var bigintProcessor = (_schema, ctx2, _json, _params) => {
|
|
@@ -40693,19 +40981,19 @@ var voidProcessor = (_schema, ctx2, _json, _params) => {
|
|
|
40693
40981
|
throw new Error("Void cannot be represented in JSON Schema");
|
|
40694
40982
|
}
|
|
40695
40983
|
};
|
|
40696
|
-
var neverProcessor = (_schema,
|
|
40984
|
+
var neverProcessor = (_schema, _ctx, json2, _params) => {
|
|
40697
40985
|
json2.not = {};
|
|
40698
40986
|
};
|
|
40699
|
-
var anyProcessor = (_schema,
|
|
40987
|
+
var anyProcessor = (_schema, _ctx, _json, _params) => {
|
|
40700
40988
|
};
|
|
40701
|
-
var unknownProcessor = (_schema,
|
|
40989
|
+
var unknownProcessor = (_schema, _ctx, _json, _params) => {
|
|
40702
40990
|
};
|
|
40703
40991
|
var dateProcessor = (_schema, ctx2, _json, _params) => {
|
|
40704
40992
|
if (ctx2.unrepresentable === "throw") {
|
|
40705
40993
|
throw new Error("Date cannot be represented in JSON Schema");
|
|
40706
40994
|
}
|
|
40707
40995
|
};
|
|
40708
|
-
var enumProcessor = (schema,
|
|
40996
|
+
var enumProcessor = (schema, _ctx, json2, _params) => {
|
|
40709
40997
|
const def = schema._zod.def;
|
|
40710
40998
|
const values = getEnumValues(def.entries);
|
|
40711
40999
|
if (values.every((v2) => typeof v2 === "number"))
|
|
@@ -40759,7 +41047,7 @@ var nanProcessor = (_schema, ctx2, _json, _params) => {
|
|
|
40759
41047
|
throw new Error("NaN cannot be represented in JSON Schema");
|
|
40760
41048
|
}
|
|
40761
41049
|
};
|
|
40762
|
-
var templateLiteralProcessor = (schema,
|
|
41050
|
+
var templateLiteralProcessor = (schema, _ctx, json2, _params) => {
|
|
40763
41051
|
const _json = json2;
|
|
40764
41052
|
const pattern = schema._zod.pattern;
|
|
40765
41053
|
if (!pattern)
|
|
@@ -40767,7 +41055,7 @@ var templateLiteralProcessor = (schema, _ctx2, json2, _params) => {
|
|
|
40767
41055
|
_json.type = "string";
|
|
40768
41056
|
_json.pattern = pattern.source;
|
|
40769
41057
|
};
|
|
40770
|
-
var fileProcessor = (schema,
|
|
41058
|
+
var fileProcessor = (schema, _ctx, json2, _params) => {
|
|
40771
41059
|
const _json = json2;
|
|
40772
41060
|
const file2 = {
|
|
40773
41061
|
type: "string",
|
|
@@ -40791,7 +41079,7 @@ var fileProcessor = (schema, _ctx2, json2, _params) => {
|
|
|
40791
41079
|
Object.assign(_json, file2);
|
|
40792
41080
|
}
|
|
40793
41081
|
};
|
|
40794
|
-
var successProcessor = (_schema,
|
|
41082
|
+
var successProcessor = (_schema, _ctx, json2, _params) => {
|
|
40795
41083
|
json2.type = "boolean";
|
|
40796
41084
|
};
|
|
40797
41085
|
var customProcessor = (_schema, ctx2, _json, _params) => {
|
|
@@ -42479,8 +42767,8 @@ var ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
|
|
|
42479
42767
|
$ZodTransform.init(inst, def);
|
|
42480
42768
|
ZodType.init(inst, def);
|
|
42481
42769
|
inst._zod.processJSONSchema = (ctx2, json2, params) => transformProcessor(inst, ctx2, json2, params);
|
|
42482
|
-
inst._zod.parse = (payload,
|
|
42483
|
-
if (
|
|
42770
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
42771
|
+
if (_ctx.direction === "backward") {
|
|
42484
42772
|
throw new $ZodEncodeError(inst.constructor.name);
|
|
42485
42773
|
}
|
|
42486
42774
|
payload.addIssue = (issue2) => {
|
|
@@ -43382,9 +43670,6 @@ function jsonSchemaToZodShape(schema) {
|
|
|
43382
43670
|
return shape;
|
|
43383
43671
|
}
|
|
43384
43672
|
|
|
43385
|
-
// src/index.ts
|
|
43386
|
-
import { readFileSync as nodeReadFileSync } from "node:fs";
|
|
43387
|
-
|
|
43388
43673
|
// src/pi-ai-compat.ts
|
|
43389
43674
|
var dynamicImport = (specifier) => import(specifier);
|
|
43390
43675
|
async function resolveGetModels(root, loadCompat = () => dynamicImport("@earendil-works/pi-ai/compat")) {
|
|
@@ -43396,32 +43681,39 @@ async function resolveGetModels(root, loadCompat = () => dynamicImport("@earendi
|
|
|
43396
43681
|
|
|
43397
43682
|
// src/connector-cache.ts
|
|
43398
43683
|
import { createHash } from "node:crypto";
|
|
43399
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
43400
|
-
import { dirname as
|
|
43401
|
-
var CACHE_VERSION =
|
|
43684
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
43685
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
43686
|
+
var CACHE_VERSION = 2;
|
|
43402
43687
|
var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
43688
|
+
function scopeKeyFor(claudeConfigDir) {
|
|
43689
|
+
return claudeConfigDir?.trim() || "<default>";
|
|
43690
|
+
}
|
|
43403
43691
|
function connectorCacheScopeKey(env = process.env) {
|
|
43404
|
-
return env.CLAUDE_CONFIG_DIR
|
|
43692
|
+
return scopeKeyFor(env.CLAUDE_CONFIG_DIR);
|
|
43693
|
+
}
|
|
43694
|
+
function connectorCacheScopeDigest(scopeKey) {
|
|
43695
|
+
return createHash("sha256").update(scopeKey).digest("hex");
|
|
43405
43696
|
}
|
|
43406
43697
|
function connectorCachePath(scopeKey = connectorCacheScopeKey()) {
|
|
43407
|
-
const digest =
|
|
43408
|
-
return
|
|
43698
|
+
const digest = connectorCacheScopeDigest(scopeKey).slice(0, 16);
|
|
43699
|
+
return join5(piUserDir(), "connector-cache", `${digest}.json`);
|
|
43409
43700
|
}
|
|
43410
43701
|
function readCachedConnectors(scopeKey = connectorCacheScopeKey(), now = Date.now()) {
|
|
43411
43702
|
let raw;
|
|
43412
43703
|
try {
|
|
43413
|
-
raw =
|
|
43704
|
+
raw = readFileSync4(connectorCachePath(scopeKey), "utf8");
|
|
43414
43705
|
} catch {
|
|
43415
43706
|
return void 0;
|
|
43416
43707
|
}
|
|
43417
43708
|
let parsed;
|
|
43418
43709
|
try {
|
|
43419
43710
|
parsed = JSON.parse(raw);
|
|
43420
|
-
} catch {
|
|
43711
|
+
} catch (error51) {
|
|
43712
|
+
debug(`connector-cache: corrupt cache ${connectorCachePath(scopeKey)}:`, error51 instanceof Error ? error51.message : String(error51));
|
|
43421
43713
|
return void 0;
|
|
43422
43714
|
}
|
|
43423
43715
|
if (parsed?.version !== CACHE_VERSION) return void 0;
|
|
43424
|
-
if (parsed?.scope !== scopeKey) return void 0;
|
|
43716
|
+
if (parsed?.scope !== connectorCacheScopeDigest(scopeKey)) return void 0;
|
|
43425
43717
|
const savedAt = typeof parsed?.savedAt === "number" ? parsed.savedAt : 0;
|
|
43426
43718
|
if (!savedAt || now - savedAt > MAX_AGE_MS || savedAt > now) return void 0;
|
|
43427
43719
|
if (!Array.isArray(parsed?.connectors)) return void 0;
|
|
@@ -43434,26 +43726,112 @@ function writeCachedConnectors(connectors, scopeKey = connectorCacheScopeKey(),
|
|
|
43434
43726
|
if (!Array.isArray(connectors) || connectors.length === 0) return false;
|
|
43435
43727
|
const path = connectorCachePath(scopeKey);
|
|
43436
43728
|
try {
|
|
43437
|
-
mkdirSync3(
|
|
43729
|
+
mkdirSync3(dirname3(path), { recursive: true, mode: 448 });
|
|
43438
43730
|
writeFileSync(
|
|
43439
43731
|
path,
|
|
43440
|
-
JSON.stringify({ version: CACHE_VERSION, scope: scopeKey, savedAt: now, connectors }),
|
|
43732
|
+
JSON.stringify({ version: CACHE_VERSION, scope: connectorCacheScopeDigest(scopeKey), savedAt: now, connectors }),
|
|
43441
43733
|
{ mode: 384 }
|
|
43442
43734
|
);
|
|
43443
43735
|
return true;
|
|
43444
|
-
} catch {
|
|
43736
|
+
} catch (error51) {
|
|
43737
|
+
debug(`connector-cache: write failed ${path}:`, error51 instanceof Error ? error51.message : String(error51));
|
|
43445
43738
|
return false;
|
|
43446
43739
|
}
|
|
43447
43740
|
}
|
|
43448
43741
|
|
|
43742
|
+
// src/connector-runtime.ts
|
|
43743
|
+
import { readFileSync as nodeReadFileSync } from "node:fs";
|
|
43744
|
+
function readCredentialFile(path) {
|
|
43745
|
+
try {
|
|
43746
|
+
return nodeReadFileSync(path, "utf8");
|
|
43747
|
+
} catch {
|
|
43748
|
+
return void 0;
|
|
43749
|
+
}
|
|
43750
|
+
}
|
|
43751
|
+
var connectorServerCache = /* @__PURE__ */ new Map();
|
|
43752
|
+
var connectorServerPending = /* @__PURE__ */ new Set();
|
|
43753
|
+
var connectorServerFailureAt = /* @__PURE__ */ new Map();
|
|
43754
|
+
var CONNECTOR_PRIME_TIMEOUT_MS = 1e4;
|
|
43755
|
+
var CONNECTOR_PRIME_FAILURE_COOLDOWN_MS = 6e4;
|
|
43756
|
+
function connectorScopeKey(claudeConfigDir = process.env.CLAUDE_CONFIG_DIR) {
|
|
43757
|
+
return scopeKeyFor(claudeConfigDir);
|
|
43758
|
+
}
|
|
43759
|
+
function connectorCredentialEnv(claudeConfigDir = process.env.CLAUDE_CONFIG_DIR) {
|
|
43760
|
+
const env = { ...process.env };
|
|
43761
|
+
if (claudeConfigDir?.trim()) env.CLAUDE_CONFIG_DIR = claudeConfigDir.trim();
|
|
43762
|
+
else delete env.CLAUDE_CONFIG_DIR;
|
|
43763
|
+
return env;
|
|
43764
|
+
}
|
|
43765
|
+
function primeConnectorServers(claudeConfigDir, overrides = {}) {
|
|
43766
|
+
const key = connectorScopeKey(claudeConfigDir);
|
|
43767
|
+
if (connectorServerCache.has(key) || connectorServerPending.has(key)) return;
|
|
43768
|
+
const now = overrides.now ?? Date.now;
|
|
43769
|
+
const cooldownMs = overrides.failureCooldownMs ?? CONNECTOR_PRIME_FAILURE_COOLDOWN_MS;
|
|
43770
|
+
const failedAt = connectorServerFailureAt.get(key);
|
|
43771
|
+
if (failedAt !== void 0 && now() - failedAt < cooldownMs) {
|
|
43772
|
+
debug("connectors: prime cooling down after failure; declaring none until retry window opens");
|
|
43773
|
+
return;
|
|
43774
|
+
}
|
|
43775
|
+
connectorServerPending.add(key);
|
|
43776
|
+
void (async () => {
|
|
43777
|
+
let failed = false;
|
|
43778
|
+
try {
|
|
43779
|
+
const credentials = resolveClaudeOAuth(readCredentialFile, connectorCredentialEnv(claudeConfigDir));
|
|
43780
|
+
if (!credentials) {
|
|
43781
|
+
debug("connectors: no OAuth credentials; declaring none (will retry)");
|
|
43782
|
+
return;
|
|
43783
|
+
}
|
|
43784
|
+
const inventory = await listAccountConnectors({
|
|
43785
|
+
credentials,
|
|
43786
|
+
// A hung inventory request must not outlive the deadline: the abort
|
|
43787
|
+
// surfaces as `ok: false` and takes the cooldown path below.
|
|
43788
|
+
signal: AbortSignal.timeout(overrides.timeoutMs ?? CONNECTOR_PRIME_TIMEOUT_MS)
|
|
43789
|
+
});
|
|
43790
|
+
if (!inventory.ok) {
|
|
43791
|
+
failed = true;
|
|
43792
|
+
debug(`connectors: inventory failed (${inventory.reason}); declaring none (will retry after cooldown)`);
|
|
43793
|
+
return;
|
|
43794
|
+
}
|
|
43795
|
+
const servers = connectorMcpServers(inventory);
|
|
43796
|
+
debug(
|
|
43797
|
+
`connectors: declaring ${Object.keys(servers).length} of ${inventory.connectors.length} installed`,
|
|
43798
|
+
Object.keys(servers).join(", ") || "none"
|
|
43799
|
+
);
|
|
43800
|
+
connectorServerCache.set(key, servers);
|
|
43801
|
+
connectorServerFailureAt.delete(key);
|
|
43802
|
+
if (writeCachedConnectors(inventory.connectors, key)) {
|
|
43803
|
+
debug(`connectors: cached ${inventory.connectors.length} entries`);
|
|
43804
|
+
}
|
|
43805
|
+
} catch (error51) {
|
|
43806
|
+
failed = true;
|
|
43807
|
+
debug("connectors: declaration lookup threw; declaring none (will retry after cooldown)", error51);
|
|
43808
|
+
} finally {
|
|
43809
|
+
if (failed) connectorServerFailureAt.set(key, now());
|
|
43810
|
+
connectorServerPending.delete(key);
|
|
43811
|
+
}
|
|
43812
|
+
})();
|
|
43813
|
+
}
|
|
43814
|
+
function connectorServersSnapshot(claudeConfigDir) {
|
|
43815
|
+
const key = connectorScopeKey(claudeConfigDir);
|
|
43816
|
+
const ready = connectorServerCache.get(key);
|
|
43817
|
+
if (ready) return ready;
|
|
43818
|
+
primeConnectorServers(claudeConfigDir);
|
|
43819
|
+
const cached2 = readCachedConnectors(key);
|
|
43820
|
+
if (!cached2) return {};
|
|
43821
|
+
const servers = connectorMcpServers({ ok: true, complete: true, connectors: cached2 });
|
|
43822
|
+
if (Object.keys(servers).length === 0) return {};
|
|
43823
|
+
debug(`connectors: turn-1 declarations from cache \u2014 ${Object.keys(servers).join(", ")}`);
|
|
43824
|
+
return servers;
|
|
43825
|
+
}
|
|
43826
|
+
|
|
43449
43827
|
// src/claude-executable.ts
|
|
43450
43828
|
import { spawn as spawnProcess } from "child_process";
|
|
43451
|
-
import { accessSync, constants as fsConstants, readFileSync as
|
|
43452
|
-
import { delimiter, join as
|
|
43829
|
+
import { accessSync, constants as fsConstants, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync2 } from "fs";
|
|
43830
|
+
import { delimiter, join as join6 } from "path";
|
|
43453
43831
|
function executableFromPath(name) {
|
|
43454
43832
|
const paths = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
43455
43833
|
for (const dir of paths) {
|
|
43456
|
-
const candidate =
|
|
43834
|
+
const candidate = join6(dir, name);
|
|
43457
43835
|
try {
|
|
43458
43836
|
accessSync(candidate, fsConstants.X_OK);
|
|
43459
43837
|
return candidate;
|
|
@@ -43569,7 +43947,7 @@ function preflightClaudeExecutable(path, cwd) {
|
|
|
43569
43947
|
}
|
|
43570
43948
|
let fileType;
|
|
43571
43949
|
try {
|
|
43572
|
-
fileType = classifyClaudeExecutableBytes(
|
|
43950
|
+
fileType = classifyClaudeExecutableBytes(readFileSync5(realPath).subarray(0, 16));
|
|
43573
43951
|
} catch (err) {
|
|
43574
43952
|
throw makeClaudePreflightError("Claude Code executable preflight failed: cannot read executable header before spawning Claude Code.", {
|
|
43575
43953
|
code: codeValue(err, "EACCES"),
|
|
@@ -43662,19 +44040,19 @@ function spawnClaudeCodeWithDiagnostics(options) {
|
|
|
43662
44040
|
};
|
|
43663
44041
|
}
|
|
43664
44042
|
|
|
43665
|
-
// node_modules/cc-session-io/dist/chunk-
|
|
44043
|
+
// node_modules/cc-session-io/dist/chunk-7RWUSC7F.js
|
|
43666
44044
|
import { randomUUID } from "crypto";
|
|
43667
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync3, existsSync as
|
|
43668
|
-
import { dirname as
|
|
43669
|
-
import { readFileSync as
|
|
44045
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync3, existsSync as existsSync4, rmSync as rmSync2 } from "fs";
|
|
44046
|
+
import { dirname as dirname4 } from "path";
|
|
44047
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
43670
44048
|
import { realpathSync as realpathSync3 } from "fs";
|
|
43671
44049
|
import { homedir as homedir3 } from "os";
|
|
43672
|
-
import { join as
|
|
44050
|
+
import { join as join7 } from "path";
|
|
43673
44051
|
function parseJsonl(content) {
|
|
43674
44052
|
return content.split("\n").filter((line) => line.trim()).map(parseRecord);
|
|
43675
44053
|
}
|
|
43676
44054
|
function parseJsonlFile(path) {
|
|
43677
|
-
return parseJsonl(
|
|
44055
|
+
return parseJsonl(readFileSync6(path, "utf-8"));
|
|
43678
44056
|
}
|
|
43679
44057
|
function parseRecord(line) {
|
|
43680
44058
|
const raw = JSON.parse(line);
|
|
@@ -43687,7 +44065,7 @@ function serializeRecord(record2) {
|
|
|
43687
44065
|
}
|
|
43688
44066
|
var MAX_SANITIZED_LENGTH = 200;
|
|
43689
44067
|
function getClaudeDir(claudeDir) {
|
|
43690
|
-
return claudeDir ?? process.env.CLAUDE_CONFIG_DIR ??
|
|
44068
|
+
return claudeDir ?? process.env.CLAUDE_CONFIG_DIR ?? join7(homedir3(), ".claude");
|
|
43691
44069
|
}
|
|
43692
44070
|
function normalizeProjectPath(projectPath) {
|
|
43693
44071
|
try {
|
|
@@ -43705,10 +44083,10 @@ function projectPathToHash(projectPath) {
|
|
|
43705
44083
|
return `${sanitized.slice(0, MAX_SANITIZED_LENGTH)}-${Math.abs(h).toString(36)}`;
|
|
43706
44084
|
}
|
|
43707
44085
|
function getProjectDir(projectPath, claudeDir) {
|
|
43708
|
-
return
|
|
44086
|
+
return join7(getClaudeDir(claudeDir), "projects", projectPathToHash(normalizeProjectPath(projectPath)));
|
|
43709
44087
|
}
|
|
43710
44088
|
function getSessionPath(sessionId, projectPath, claudeDir) {
|
|
43711
|
-
return
|
|
44089
|
+
return join7(getProjectDir(projectPath, claudeDir), `${sessionId}.jsonl`);
|
|
43712
44090
|
}
|
|
43713
44091
|
function repairToolPairing(messages) {
|
|
43714
44092
|
const result = [];
|
|
@@ -43920,13 +44298,16 @@ var Session = class {
|
|
|
43920
44298
|
this._lastUuid = uuid3;
|
|
43921
44299
|
return record2;
|
|
43922
44300
|
}
|
|
43923
|
-
/** Add a user text
|
|
43924
|
-
addUserMessage(
|
|
44301
|
+
/** Add a user message, as plain text or content blocks. Returns its uuid. */
|
|
44302
|
+
addUserMessage(content) {
|
|
44303
|
+
if (Array.isArray(content) && content.length === 0) {
|
|
44304
|
+
throw new Error("addUserMessage: content array is empty; Anthropic rejects empty message content");
|
|
44305
|
+
}
|
|
43925
44306
|
const base = this.baseFields();
|
|
43926
44307
|
const record2 = {
|
|
43927
44308
|
type: "user",
|
|
43928
44309
|
...base,
|
|
43929
|
-
message: { role: "user", content
|
|
44310
|
+
message: { role: "user", content }
|
|
43930
44311
|
};
|
|
43931
44312
|
this._pendingRecords.push(record2);
|
|
43932
44313
|
return base.uuid;
|
|
@@ -44014,16 +44395,17 @@ var Session = class {
|
|
|
44014
44395
|
const toolResults = msg.content.filter(
|
|
44015
44396
|
(b) => b.type === "tool_result"
|
|
44016
44397
|
);
|
|
44398
|
+
const rest = msg.content.filter(
|
|
44399
|
+
(b) => b.type !== "tool_result"
|
|
44400
|
+
);
|
|
44017
44401
|
if (toolResults.length > 0) {
|
|
44018
44402
|
this.addToolResults(toolResults.map((r) => ({
|
|
44019
44403
|
toolUseId: r.tool_use_id,
|
|
44020
44404
|
content: r.content,
|
|
44021
44405
|
isError: r.is_error
|
|
44022
44406
|
})));
|
|
44023
|
-
} else {
|
|
44024
|
-
const text = msg.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
44025
|
-
this.addUserMessage(text || JSON.stringify(msg.content));
|
|
44026
44407
|
}
|
|
44408
|
+
if (rest.length > 0) this.addUserMessage(rest);
|
|
44027
44409
|
}
|
|
44028
44410
|
}
|
|
44029
44411
|
}
|
|
@@ -44043,8 +44425,8 @@ var Session = class {
|
|
|
44043
44425
|
/** Write pending records to disk. Creates the file/directory if needed. */
|
|
44044
44426
|
save() {
|
|
44045
44427
|
if (this._pendingRecords.length === 0) return;
|
|
44046
|
-
const dir =
|
|
44047
|
-
if (!
|
|
44428
|
+
const dir = dirname4(this.jsonlPath);
|
|
44429
|
+
if (!existsSync4(dir)) {
|
|
44048
44430
|
mkdirSync4(dir, { recursive: true });
|
|
44049
44431
|
}
|
|
44050
44432
|
const data = this._pendingRecords.map((r) => serializeRecord(r) + "\n").join("");
|
|
@@ -44183,25 +44565,286 @@ function verifyWrittenSession(jsonlPath, expectedSessionId, expectedRecordCount)
|
|
|
44183
44565
|
return warnings;
|
|
44184
44566
|
}
|
|
44185
44567
|
|
|
44186
|
-
// src/
|
|
44187
|
-
|
|
44188
|
-
|
|
44189
|
-
|
|
44190
|
-
|
|
44191
|
-
|
|
44192
|
-
|
|
44193
|
-
|
|
44194
|
-
|
|
44195
|
-
|
|
44196
|
-
|
|
44197
|
-
|
|
44198
|
-
return
|
|
44199
|
-
}
|
|
44200
|
-
|
|
44568
|
+
// src/account-router.ts
|
|
44569
|
+
import { homedir as homedir4 } from "node:os";
|
|
44570
|
+
import { join as join8 } from "node:path";
|
|
44571
|
+
|
|
44572
|
+
// src/rate-limit.ts
|
|
44573
|
+
var RATE_LIMIT_AUTO_RESUME_EVENT = "vstack:rate-limit";
|
|
44574
|
+
var RATE_LIMIT_TOKEN = "\x1B[31m[rate-limit]\x1B[39m";
|
|
44575
|
+
var USAGE_LIMIT_PREFIXES = Array.isArray(qO) ? qO : [];
|
|
44576
|
+
function coerceMessageText(value) {
|
|
44577
|
+
if (typeof value === "string") return value;
|
|
44578
|
+
if (value instanceof Error) return value.message;
|
|
44579
|
+
try {
|
|
44580
|
+
return JSON.stringify(value ?? "");
|
|
44581
|
+
} catch {
|
|
44582
|
+
return String(value);
|
|
44583
|
+
}
|
|
44201
44584
|
}
|
|
44202
|
-
function
|
|
44203
|
-
const
|
|
44204
|
-
return
|
|
44585
|
+
function isUsageLimitMessage(value) {
|
|
44586
|
+
const text = coerceMessageText(value);
|
|
44587
|
+
return USAGE_LIMIT_PREFIXES.some((prefix) => text.includes(prefix));
|
|
44588
|
+
}
|
|
44589
|
+
function uniqueNonEmptyLines(values) {
|
|
44590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44591
|
+
const out = [];
|
|
44592
|
+
for (const value of values) {
|
|
44593
|
+
const text = typeof value === "string" ? value.trim() : value == null ? "" : String(value).trim();
|
|
44594
|
+
if (!text || seen.has(text)) continue;
|
|
44595
|
+
seen.add(text);
|
|
44596
|
+
out.push(text);
|
|
44597
|
+
}
|
|
44598
|
+
return out;
|
|
44599
|
+
}
|
|
44600
|
+
function resetTimestampMs(value) {
|
|
44601
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
44602
|
+
return Math.abs(value) < 1e12 ? value * 1e3 : value;
|
|
44603
|
+
}
|
|
44604
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
44605
|
+
const numeric = Number(value);
|
|
44606
|
+
if (Number.isFinite(numeric)) return Math.abs(numeric) < 1e12 ? numeric * 1e3 : numeric;
|
|
44607
|
+
const parsed = Date.parse(value);
|
|
44608
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
44609
|
+
}
|
|
44610
|
+
function formatResetTimestamp(value) {
|
|
44611
|
+
const parsed = resetTimestampMs(value);
|
|
44612
|
+
if (parsed === void 0) return "unknown";
|
|
44613
|
+
return new Date(parsed).toLocaleString(void 0, {
|
|
44614
|
+
day: "numeric",
|
|
44615
|
+
hour: "numeric",
|
|
44616
|
+
minute: "2-digit",
|
|
44617
|
+
month: "short",
|
|
44618
|
+
second: "2-digit",
|
|
44619
|
+
timeZoneName: "short",
|
|
44620
|
+
year: "numeric"
|
|
44621
|
+
});
|
|
44622
|
+
}
|
|
44623
|
+
var ALLOWED_RATE_LIMIT_WARNING_UTILIZATION_THRESHOLD = 80;
|
|
44624
|
+
function normalizeRateLimitUtilization(value) {
|
|
44625
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
44626
|
+
if (value === 0) return 0;
|
|
44627
|
+
if (value > 0 && value <= 1) return value * 100;
|
|
44628
|
+
if (value > 1 && value <= 100) return value;
|
|
44629
|
+
return void 0;
|
|
44630
|
+
}
|
|
44631
|
+
function rateLimitTypeLabel(value) {
|
|
44632
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
44633
|
+
return text || "unknown";
|
|
44634
|
+
}
|
|
44635
|
+
function formatAllowedRateLimitWarning(info) {
|
|
44636
|
+
if (info?.status !== "allowed_warning") return void 0;
|
|
44637
|
+
const utilization = normalizeRateLimitUtilization(info.utilization);
|
|
44638
|
+
if (utilization === void 0 || utilization < ALLOWED_RATE_LIMIT_WARNING_UTILIZATION_THRESHOLD) return void 0;
|
|
44639
|
+
return `Claude rate limit warning: nearing ${rateLimitTypeLabel(info.rateLimitType)} limit; check Claude Code /usage for exact utilization.`;
|
|
44640
|
+
}
|
|
44641
|
+
|
|
44642
|
+
// src/account-router.ts
|
|
44643
|
+
var CLAUDE_ACCOUNT_ROUTER_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.claude-account-router.v1");
|
|
44644
|
+
var CLAUDE_BRIDGE_ACCOUNT_HOST_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.claude-bridge.account-host.v1");
|
|
44645
|
+
function resolveClaudeAccountRouter() {
|
|
44646
|
+
const host = globalThis;
|
|
44647
|
+
const candidate = host[CLAUDE_ACCOUNT_ROUTER_SYMBOL];
|
|
44648
|
+
return candidate?.version === 1 ? candidate : void 0;
|
|
44649
|
+
}
|
|
44650
|
+
function safeRouterCall(label, call) {
|
|
44651
|
+
try {
|
|
44652
|
+
call();
|
|
44653
|
+
} catch (error51) {
|
|
44654
|
+
debug(`router callback ${label} threw:`, error51);
|
|
44655
|
+
}
|
|
44656
|
+
}
|
|
44657
|
+
function subscriberProfileEnv(profile, base = process.env) {
|
|
44658
|
+
const env = { ...base };
|
|
44659
|
+
const directOverrides = /* @__PURE__ */ new Set([
|
|
44660
|
+
"ANTHROPIC_API_KEY",
|
|
44661
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
44662
|
+
"ANTHROPIC_OAUTH_TOKEN",
|
|
44663
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
44664
|
+
"ANTHROPIC_BASE_URL",
|
|
44665
|
+
"ANTHROPIC_CUSTOM_HEADERS",
|
|
44666
|
+
"ANTHROPIC_AWS_API_KEY",
|
|
44667
|
+
"ANTHROPIC_FOUNDRY_AUTH_TOKEN",
|
|
44668
|
+
"ANTHROPIC_BEDROCK_BASE_URL",
|
|
44669
|
+
"ANTHROPIC_VERTEX_BASE_URL",
|
|
44670
|
+
"ANTHROPIC_FOUNDRY_BASE_URL",
|
|
44671
|
+
"AWS_BEARER_TOKEN_BEDROCK"
|
|
44672
|
+
]);
|
|
44673
|
+
for (const key of Object.keys(env)) {
|
|
44674
|
+
if (directOverrides.has(key) || key.startsWith("CLAUDE_CODE_USE_")) delete env[key];
|
|
44675
|
+
}
|
|
44676
|
+
if (profile.configDir) env.CLAUDE_CONFIG_DIR = profile.configDir;
|
|
44677
|
+
else delete env.CLAUDE_CONFIG_DIR;
|
|
44678
|
+
return env;
|
|
44679
|
+
}
|
|
44680
|
+
function claudeDirForProfile(profile) {
|
|
44681
|
+
return profile.configDir?.trim() || join8(homedir4(), ".claude");
|
|
44682
|
+
}
|
|
44683
|
+
function accountSessionScope(profile) {
|
|
44684
|
+
return profile ? { accountProfileId: profile.profileId, claudeConfigDir: claudeDirForProfile(profile) } : {};
|
|
44685
|
+
}
|
|
44686
|
+
function commitsVisibleOutput(event) {
|
|
44687
|
+
if (event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta") {
|
|
44688
|
+
return event.delta.length > 0;
|
|
44689
|
+
}
|
|
44690
|
+
if (event.type === "text_end" || event.type === "thinking_end") return event.content.length > 0;
|
|
44691
|
+
return event.type === "toolcall_end";
|
|
44692
|
+
}
|
|
44693
|
+
var RetryEventBuffer = class {
|
|
44694
|
+
pending = [];
|
|
44695
|
+
committed = false;
|
|
44696
|
+
ended = false;
|
|
44697
|
+
discarded = false;
|
|
44698
|
+
target;
|
|
44699
|
+
onCommit;
|
|
44700
|
+
constructor(target, onCommit) {
|
|
44701
|
+
this.target = target;
|
|
44702
|
+
this.onCommit = onCommit;
|
|
44703
|
+
}
|
|
44704
|
+
push(event) {
|
|
44705
|
+
if (this.discarded) return;
|
|
44706
|
+
if (this.committed) {
|
|
44707
|
+
this.target.push(event);
|
|
44708
|
+
return;
|
|
44709
|
+
}
|
|
44710
|
+
this.pending.push(event);
|
|
44711
|
+
if (commitsVisibleOutput(event) || event.type === "done" || event.type === "error") this.commit();
|
|
44712
|
+
}
|
|
44713
|
+
end() {
|
|
44714
|
+
if (this.discarded) return;
|
|
44715
|
+
this.ended = true;
|
|
44716
|
+
if (this.committed) this.target.end();
|
|
44717
|
+
}
|
|
44718
|
+
commit() {
|
|
44719
|
+
if (this.discarded || this.committed) return;
|
|
44720
|
+
this.committed = true;
|
|
44721
|
+
this.onCommit?.();
|
|
44722
|
+
for (const event of this.pending) this.target.push(event);
|
|
44723
|
+
this.pending.length = 0;
|
|
44724
|
+
if (this.ended) this.target.end();
|
|
44725
|
+
}
|
|
44726
|
+
discard() {
|
|
44727
|
+
if (this.committed) return;
|
|
44728
|
+
this.discarded = true;
|
|
44729
|
+
this.pending.length = 0;
|
|
44730
|
+
}
|
|
44731
|
+
get hasCommittedOutput() {
|
|
44732
|
+
return this.committed;
|
|
44733
|
+
}
|
|
44734
|
+
};
|
|
44735
|
+
function rateLimitTypeFromInfo(info) {
|
|
44736
|
+
return info?.rateLimitType ?? info?.rate_limit_type ?? info?.type;
|
|
44737
|
+
}
|
|
44738
|
+
function rateLimitResetFromInfo(info) {
|
|
44739
|
+
return info?.resetsAt ?? info?.resets_at ?? info?.resetAt ?? info?.reset_at;
|
|
44740
|
+
}
|
|
44741
|
+
function rateLimitResetMs(info) {
|
|
44742
|
+
return resetTimestampMs(rateLimitResetFromInfo(info));
|
|
44743
|
+
}
|
|
44744
|
+
function httpStatusInText(normalized) {
|
|
44745
|
+
const match = /\b(?:http|https|status(?: code)?|error|code)\b[^a-z0-9]{0,4}([45]\d\d)\b/.exec(normalized);
|
|
44746
|
+
return match ? Number(match[1]) : void 0;
|
|
44747
|
+
}
|
|
44748
|
+
function classifyStatusCode(status) {
|
|
44749
|
+
if (status === 401) return "auth";
|
|
44750
|
+
if (status === 403) return "auth";
|
|
44751
|
+
if (status === 402) return "billing";
|
|
44752
|
+
if (status === 429) return "rate-limit";
|
|
44753
|
+
if (status === 529) return "overloaded";
|
|
44754
|
+
if (status >= 500 && status <= 599) return "server";
|
|
44755
|
+
return void 0;
|
|
44756
|
+
}
|
|
44757
|
+
function classifyClaudeFailure(value) {
|
|
44758
|
+
const details = [value];
|
|
44759
|
+
let numericStatus;
|
|
44760
|
+
if (value && typeof value === "object") {
|
|
44761
|
+
const record2 = value;
|
|
44762
|
+
details.push(record2.name, record2.type, record2.message, record2.code, record2.status, record2.statusCode, record2.body, record2.error);
|
|
44763
|
+
for (const field of [record2.status, record2.statusCode]) {
|
|
44764
|
+
if (typeof field === "number" && Number.isInteger(field)) {
|
|
44765
|
+
numericStatus = field;
|
|
44766
|
+
break;
|
|
44767
|
+
}
|
|
44768
|
+
}
|
|
44769
|
+
}
|
|
44770
|
+
const text = details.map((detail) => {
|
|
44771
|
+
if (typeof detail === "string" || typeof detail === "number") return String(detail);
|
|
44772
|
+
try {
|
|
44773
|
+
return JSON.stringify(detail ?? "");
|
|
44774
|
+
} catch {
|
|
44775
|
+
return String(detail);
|
|
44776
|
+
}
|
|
44777
|
+
}).join(" ");
|
|
44778
|
+
const normalized = text.toLowerCase().replace(/[_-]+/g, " ");
|
|
44779
|
+
const statusKind = numericStatus !== void 0 ? classifyStatusCode(numericStatus) : void 0;
|
|
44780
|
+
if (statusKind) return statusKind;
|
|
44781
|
+
if (/authentication (?:failed|error)|permission error|oauth org not allowed|oauth token.*expired|token.*expired|unauthorized|invalid token|login required|please run .*login|not logged in/.test(normalized) || httpStatusInText(normalized) === 401 || httpStatusInText(normalized) === 403) return "auth";
|
|
44782
|
+
if (/extra usage|overage/.test(normalized)) return "rate-limit";
|
|
44783
|
+
if (/billing error|payment|required.*billing|credit balance.*(?:low|insufficient|empty)|insufficient credits/.test(normalized)) return "billing";
|
|
44784
|
+
const quotaInUsageContext = /\bquota\b/.test(normalized) && /\b(?:rate|usage|limits?|requests?|tokens?|messages?|api)\b/.test(normalized);
|
|
44785
|
+
if (/\brate limit|usage limit|session limit|weekly limit|monthly limit|limit reached|you(?:'|’)ve hit your .* limit|too many requests|resets? (?:at )?\d/.test(normalized) || quotaInUsageContext || httpStatusInText(normalized) === 429) return "rate-limit";
|
|
44786
|
+
if (/overloaded|capacity/.test(normalized) || httpStatusInText(normalized) === 529) return "overloaded";
|
|
44787
|
+
const statusInText = httpStatusInText(normalized);
|
|
44788
|
+
if (/server error|internal server/.test(normalized) || statusInText !== void 0 && statusInText >= 500) return "server";
|
|
44789
|
+
if (/network|timeout|timed out|socket|econn|connection closed|fetch failed|unexpected end|\beof\b/.test(normalized)) return "network";
|
|
44790
|
+
return void 0;
|
|
44791
|
+
}
|
|
44792
|
+
|
|
44793
|
+
// src/session-persistence.ts
|
|
44794
|
+
var BRIDGE_SESSION_CUSTOM_TYPE = "claude-bridge-session";
|
|
44795
|
+
function normalizedMessageText(message) {
|
|
44796
|
+
const content = message.content;
|
|
44797
|
+
const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((block) => block.type === "text" ? block.text ?? "" : "").join("\n") : "";
|
|
44798
|
+
return text.trim();
|
|
44799
|
+
}
|
|
44800
|
+
function shortHash(text) {
|
|
44801
|
+
return createHash2("sha256").update(text).digest("hex").slice(0, 12);
|
|
44802
|
+
}
|
|
44803
|
+
function conversationFingerprint(messages) {
|
|
44804
|
+
const firstUser = messages.find((message) => message.role === "user");
|
|
44805
|
+
if (!firstUser) return void 0;
|
|
44806
|
+
const userText = normalizedMessageText(firstUser);
|
|
44807
|
+
if (!userText) return void 0;
|
|
44808
|
+
const firstAssistant = messages.find((message) => message.role === "assistant");
|
|
44809
|
+
const assistantText = firstAssistant ? normalizedMessageText(firstAssistant) : "";
|
|
44810
|
+
return assistantText ? `u:${shortHash(userText)}|a:${shortHash(assistantText)}` : `u:${shortHash(userText)}`;
|
|
44811
|
+
}
|
|
44812
|
+
function parseConversationFingerprint(fp2) {
|
|
44813
|
+
const match = /^u:([0-9a-f]+)(?:\|a:([0-9a-f]+))?$/.exec(fp2);
|
|
44814
|
+
if (!match) return void 0;
|
|
44815
|
+
return { user: match[1], ...match[2] ? { assistant: match[2] } : {} };
|
|
44816
|
+
}
|
|
44817
|
+
function conversationFingerprintsMatch(recorded, incoming) {
|
|
44818
|
+
const rec = parseConversationFingerprint(recorded);
|
|
44819
|
+
const inc = parseConversationFingerprint(incoming);
|
|
44820
|
+
if (!rec || !inc) return true;
|
|
44821
|
+
if (rec.user !== inc.user) return false;
|
|
44822
|
+
return !(rec.assistant && inc.assistant && rec.assistant !== inc.assistant);
|
|
44823
|
+
}
|
|
44824
|
+
function conversationFingerprintUpgrade(recorded, incoming) {
|
|
44825
|
+
if (!incoming) return void 0;
|
|
44826
|
+
if (!recorded) return incoming;
|
|
44827
|
+
const rec = parseConversationFingerprint(recorded);
|
|
44828
|
+
const inc = parseConversationFingerprint(incoming);
|
|
44829
|
+
return rec && inc && !rec.assistant && inc.assistant && rec.user === inc.user ? incoming : void 0;
|
|
44830
|
+
}
|
|
44831
|
+
function fingerprintMessages(messages) {
|
|
44832
|
+
const normalized = messages.map((message) => {
|
|
44833
|
+
if (message.role === "assistant") {
|
|
44834
|
+
return {
|
|
44835
|
+
role: message.role,
|
|
44836
|
+
provider: message.provider,
|
|
44837
|
+
model: message.model,
|
|
44838
|
+
content: message.content
|
|
44839
|
+
};
|
|
44840
|
+
}
|
|
44841
|
+
return message;
|
|
44842
|
+
});
|
|
44843
|
+
return createHash2("sha256").update(JSON.stringify(normalized)).digest("hex");
|
|
44844
|
+
}
|
|
44845
|
+
function readBuiltSessionContext(sessionManager) {
|
|
44846
|
+
const built = typeof sessionManager?.buildSessionContext === "function" ? sessionManager.buildSessionContext() : void 0;
|
|
44847
|
+
return Array.isArray(built?.messages) ? built : void 0;
|
|
44205
44848
|
}
|
|
44206
44849
|
function latestPersistedBridgeSession(sessionManager) {
|
|
44207
44850
|
const entries = typeof sessionManager?.getEntries === "function" ? sessionManager.getEntries() : [];
|
|
@@ -44215,9 +44858,9 @@ function latestPersistedBridgeSession(sessionManager) {
|
|
|
44215
44858
|
}
|
|
44216
44859
|
return void 0;
|
|
44217
44860
|
}
|
|
44218
|
-
function claudeSessionExists(sessionId, cwd) {
|
|
44861
|
+
function claudeSessionExists(sessionId, cwd, claudeDir) {
|
|
44219
44862
|
try {
|
|
44220
|
-
const session = openSession({ sessionId, projectPath: cwd, claudeDir
|
|
44863
|
+
const session = openSession({ sessionId, projectPath: cwd, claudeDir });
|
|
44221
44864
|
statSync4(session.jsonlPath);
|
|
44222
44865
|
return true;
|
|
44223
44866
|
} catch {
|
|
@@ -44260,34 +44903,72 @@ function restoreSharedSessionFromPi(ctx2) {
|
|
|
44260
44903
|
debug(`restoreSharedSession: fingerprint mismatch for ${persisted.sessionId.slice(0, 8)}`);
|
|
44261
44904
|
return;
|
|
44262
44905
|
}
|
|
44263
|
-
|
|
44906
|
+
const accountProfileId = typeof persisted.accountProfileId === "string" ? persisted.accountProfileId : void 0;
|
|
44907
|
+
const claudeConfigDir = accountProfileId ? claudeDirForProfile(resolveClaudeAccountRouter()?.resolveProfile?.(accountProfileId) ?? {}) : void 0;
|
|
44908
|
+
if (!claudeSessionExists(persisted.sessionId, persisted.cwd, claudeConfigDir ?? process.env.CLAUDE_CONFIG_DIR)) {
|
|
44264
44909
|
debug(`restoreSharedSession: Claude session missing for ${persisted.sessionId.slice(0, 8)}`);
|
|
44265
44910
|
return;
|
|
44266
44911
|
}
|
|
44267
|
-
setSharedSession({
|
|
44268
|
-
|
|
44912
|
+
setSharedSession({
|
|
44913
|
+
sessionId: persisted.sessionId,
|
|
44914
|
+
cursor,
|
|
44915
|
+
cwd: persisted.cwd,
|
|
44916
|
+
// Absent on pre-3.1.1 markers: restore as identity-unknown (the foreign
|
|
44917
|
+
// guard fails open) rather than rejecting the entry.
|
|
44918
|
+
...typeof persisted.conversationFingerprint === "string" ? { conversationFingerprint: persisted.conversationFingerprint } : {},
|
|
44919
|
+
...accountProfileId ? { accountProfileId, claudeConfigDir } : {}
|
|
44920
|
+
});
|
|
44921
|
+
debug(`restoreSharedSession: restored ${persisted.sessionId.slice(0, 8)}, cursor=${cursor}, account=${accountProfileId ?? "default"}`);
|
|
44922
|
+
}
|
|
44923
|
+
var SCHEDULED_PERSISTENCE_SYMBOL = /* @__PURE__ */ Symbol.for("vstack.pi.claude-bridge.scheduled-persistence.v1");
|
|
44924
|
+
function scheduledPersistenceTimers() {
|
|
44925
|
+
const host = globalThis;
|
|
44926
|
+
let store = host[SCHEDULED_PERSISTENCE_SYMBOL];
|
|
44927
|
+
if (!store) {
|
|
44928
|
+
store = /* @__PURE__ */ new Map();
|
|
44929
|
+
host[SCHEDULED_PERSISTENCE_SYMBOL] = store;
|
|
44930
|
+
}
|
|
44931
|
+
return store;
|
|
44932
|
+
}
|
|
44933
|
+
function cancelScheduledSessionPersistence(sessionManager) {
|
|
44934
|
+
const timers = scheduledPersistenceTimers();
|
|
44935
|
+
const timer = timers.get(sessionManager);
|
|
44936
|
+
if (timer === void 0) return;
|
|
44937
|
+
clearTimeout(timer);
|
|
44938
|
+
timers.delete(sessionManager);
|
|
44269
44939
|
}
|
|
44270
44940
|
function schedulePersistSharedSession(ctxLike) {
|
|
44941
|
+
const sharedSession = getSharedSession();
|
|
44271
44942
|
if (!extensionApi || !sharedSession || !ctxLike?.sessionManager) return;
|
|
44272
|
-
const
|
|
44943
|
+
const sessionManager = ctxLike.sessionManager;
|
|
44944
|
+
const { claudeConfigDir: _omitted, ...snapshot } = sharedSession;
|
|
44945
|
+
const timers = scheduledPersistenceTimers();
|
|
44946
|
+
const superseded = timers.get(sessionManager);
|
|
44947
|
+
if (superseded !== void 0) clearTimeout(superseded);
|
|
44273
44948
|
const timer = setTimeout(() => {
|
|
44949
|
+
if (timers.get(sessionManager) === timer) timers.delete(sessionManager);
|
|
44274
44950
|
try {
|
|
44275
|
-
const built = readBuiltSessionContext(
|
|
44951
|
+
const built = readBuiltSessionContext(sessionManager);
|
|
44276
44952
|
if (!built) return;
|
|
44277
44953
|
const cursor = Math.max(0, Math.min(snapshot.cursor, built.messages.length));
|
|
44278
44954
|
const data = {
|
|
44279
44955
|
...snapshot,
|
|
44280
44956
|
cursor,
|
|
44281
44957
|
fingerprint: fingerprintMessages(built.messages.slice(0, cursor)),
|
|
44282
|
-
piSessionId: typeof
|
|
44958
|
+
piSessionId: typeof sessionManager?.getSessionId === "function" ? sessionManager.getSessionId() : void 0,
|
|
44283
44959
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
44284
44960
|
};
|
|
44285
44961
|
extensionApi?.appendEntry(BRIDGE_SESSION_CUSTOM_TYPE, data);
|
|
44286
44962
|
debug(`persistSharedSession: saved ${data.sessionId.slice(0, 8)}, cursor=${data.cursor}`);
|
|
44287
44963
|
} catch (error51) {
|
|
44288
|
-
|
|
44964
|
+
diagDump("persist_shared_session_failed", {
|
|
44965
|
+
sessionId: snapshot.sessionId.slice(0, 8),
|
|
44966
|
+
cursor: snapshot.cursor,
|
|
44967
|
+
error: error51 instanceof Error ? `${error51.name}: ${error51.message}` : String(error51)
|
|
44968
|
+
});
|
|
44289
44969
|
}
|
|
44290
44970
|
}, 0);
|
|
44971
|
+
timers.set(sessionManager, timer);
|
|
44291
44972
|
timer.unref?.();
|
|
44292
44973
|
}
|
|
44293
44974
|
function convertAndImportMessages(session, messages, customToolNameToSdk, cwd) {
|
|
@@ -44305,6 +44986,13 @@ function convertAndImportMessages(session, messages, customToolNameToSdk, cwd) {
|
|
|
44305
44986
|
[...sanitizedIds.entries()].map(([orig, clean]) => orig === clean ? orig : `${orig}\u2192${clean}`).join(", ")
|
|
44306
44987
|
);
|
|
44307
44988
|
}
|
|
44989
|
+
const recoveredToolResults = recoverLaterToolResults(anthropicMessages);
|
|
44990
|
+
if (recoveredToolResults.length > 0) {
|
|
44991
|
+
debug(
|
|
44992
|
+
`convertAndImportMessages: recovered ${recoveredToolResults.length} later tool result(s) for original parallel batch`,
|
|
44993
|
+
recoveredToolResults.map((item) => item.id).join(", ")
|
|
44994
|
+
);
|
|
44995
|
+
}
|
|
44308
44996
|
const missingToolResults = findUnpairedToolUses(anthropicMessages);
|
|
44309
44997
|
if (missingToolResults.length > 0) insertLostToolResultPlaceholders(anthropicMessages, missingToolResults);
|
|
44310
44998
|
const repaired = repairToolPairing(anthropicMessages);
|
|
@@ -44322,17 +45010,37 @@ function convertAndImportMessages(session, messages, customToolNameToSdk, cwd) {
|
|
|
44322
45010
|
}
|
|
44323
45011
|
if (repaired.length) session.importMessages(repaired);
|
|
44324
45012
|
}
|
|
44325
|
-
function
|
|
45013
|
+
function planIncrementalPromptBatch(messages, cursor) {
|
|
45014
|
+
const lastIndex = messages.length - 1;
|
|
45015
|
+
if (lastIndex < 0 || messages[lastIndex].role !== "user") return void 0;
|
|
45016
|
+
if (cursor > lastIndex) {
|
|
45017
|
+
debug(`planIncrementalPromptBatch: rejected \u2014 cursor=${cursor} beyond last index ${lastIndex}; messages are not the conversation this cursor describes`);
|
|
45018
|
+
return void 0;
|
|
45019
|
+
}
|
|
45020
|
+
const boundedCursor = Math.max(0, cursor);
|
|
45021
|
+
let promptStart = boundedCursor;
|
|
45022
|
+
if (messages[promptStart]?.role === "assistant") promptStart++;
|
|
45023
|
+
const pendingPrompts = messages.slice(promptStart);
|
|
45024
|
+
if (pendingPrompts.length === 0 || pendingPrompts.some((message) => message.role !== "user")) {
|
|
45025
|
+
debug(`planIncrementalPromptBatch: rejected \u2014 cursor=${cursor} promptStart=${promptStart} tail roles=[${messages.slice(boundedCursor).map((m) => m.role).join(", ")}]`);
|
|
45026
|
+
return void 0;
|
|
45027
|
+
}
|
|
45028
|
+
return {
|
|
45029
|
+
promptStart,
|
|
45030
|
+
userMessageCount: pendingPrompts.length
|
|
45031
|
+
};
|
|
45032
|
+
}
|
|
45033
|
+
function verifyWrittenSession2(jsonlPath, expectedSessionId, expectedRecordCount, cwd, claudeDir) {
|
|
44326
45034
|
const warnings = verifyWrittenSession(jsonlPath, expectedSessionId, expectedRecordCount);
|
|
44327
45035
|
for (const msg of warnings) {
|
|
44328
45036
|
debug(`WARNING session verify: ${msg}`);
|
|
44329
|
-
|
|
45037
|
+
safeNotify(
|
|
44330
45038
|
`Session file issue: ${msg}
|
|
44331
|
-
cwd=${cwd} realpath=${safeRealpath(cwd)
|
|
44332
|
-
Please copy and paste this message into a new issue at https://github.com/
|
|
45039
|
+
cwd=${displayPath(cwd)} realpath=${displayPath(safeRealpath(cwd))}
|
|
45040
|
+
Please copy and paste this message into a new issue at https://github.com/vanillagreencom/vstack/issues/new` + (DEBUG ? ` and attach ${DEBUG_LOG_PATH}` : ` (rerun with CLAUDE_BRIDGE_DEBUG=1 to capture a debug log)`),
|
|
44333
45041
|
"warning"
|
|
44334
45042
|
);
|
|
44335
|
-
diagDump("session_verify_fail", { msg, jsonlPath, cwd, realpath: safeRealpath(cwd), claudeConfigDir:
|
|
45043
|
+
diagDump("session_verify_fail", { msg, jsonlPath, cwd, realpath: safeRealpath(cwd), claudeConfigDir: claudeDir ?? null });
|
|
44336
45044
|
}
|
|
44337
45045
|
}
|
|
44338
45046
|
function safeRealpath(p2) {
|
|
@@ -44342,7 +45050,7 @@ function safeRealpath(p2) {
|
|
|
44342
45050
|
return `<failed: ${e.message}>`;
|
|
44343
45051
|
}
|
|
44344
45052
|
}
|
|
44345
|
-
function debugSessionPaths(label, cwd, jsonlPath) {
|
|
45053
|
+
function debugSessionPaths(label, cwd, jsonlPath, claudeDir) {
|
|
44346
45054
|
const realCwd = safeRealpath(cwd);
|
|
44347
45055
|
let fileSize = null;
|
|
44348
45056
|
let fileExists = false;
|
|
@@ -44356,54 +45064,89 @@ function debugSessionPaths(label, cwd, jsonlPath) {
|
|
|
44356
45064
|
if (realCwd !== cwd) debug(`${label}: realpath(cwd)=${realCwd} (DIFFERS \u2014 symlink-resolved path is what CC SDK uses)`);
|
|
44357
45065
|
debug(`${label}: jsonlPath=${jsonlPath}`);
|
|
44358
45066
|
debug(`${label}: fileExists=${fileExists}${fileSize != null ? ` size=${fileSize}` : ""}`);
|
|
44359
|
-
debug(`${label}:
|
|
45067
|
+
debug(`${label}: selected.CLAUDE_CONFIG_DIR=${claudeDir ?? "(unset)"} HOME=${process.env.HOME ?? "(unset)"}`);
|
|
44360
45068
|
}
|
|
44361
|
-
function syncSharedSession(messages, cwd, customToolNameToSdk, modelId) {
|
|
45069
|
+
function syncSharedSession(messages, cwd, customToolNameToSdk, modelId, account) {
|
|
45070
|
+
const sharedSession = getSharedSession();
|
|
44362
45071
|
const priorMessages = messages.slice(0, -1);
|
|
44363
|
-
|
|
44364
|
-
|
|
44365
|
-
|
|
44366
|
-
|
|
44367
|
-
|
|
44368
|
-
|
|
44369
|
-
|
|
44370
|
-
|
|
44371
|
-
|
|
44372
|
-
|
|
45072
|
+
const accountProfileId = account?.accountProfileId;
|
|
45073
|
+
const scopeConfigDir = account?.claudeConfigDir;
|
|
45074
|
+
const claudeDir = scopeConfigDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
45075
|
+
const sameAccount = Boolean(
|
|
45076
|
+
sharedSession && sharedSession.accountProfileId === accountProfileId && sharedSession.claudeConfigDir === scopeConfigDir
|
|
45077
|
+
);
|
|
45078
|
+
const incomingFingerprint = conversationFingerprint(messages);
|
|
45079
|
+
if (sharedSession && !sharedSession.needsRebuild && sharedSession.conversationFingerprint && incomingFingerprint && !conversationFingerprintsMatch(sharedSession.conversationFingerprint, incomingFingerprint) && priorMessages.length <= sharedSession.cursor) {
|
|
45080
|
+
debug(
|
|
45081
|
+
`Case 6 foreign-conversation: fingerprint ${incomingFingerprint.slice(0, 8)} != record ${sharedSession.conversationFingerprint.slice(0, 8)} (cursor=${sharedSession.cursor}, priors=${priorMessages.length}) \u2014 clean one-shot, record untouched`
|
|
45082
|
+
);
|
|
45083
|
+
debug(`syncResult: path=foreign-one-shot`);
|
|
45084
|
+
return { sessionId: null, promptStart: messages.length - 1, foreignContext: true };
|
|
45085
|
+
}
|
|
45086
|
+
if (sharedSession && sameAccount && !sharedSession.needsRebuild) {
|
|
45087
|
+
const batch = planIncrementalPromptBatch(messages, sharedSession.cursor);
|
|
45088
|
+
if (batch) {
|
|
45089
|
+
const cursorBeforeUpdate = sharedSession.cursor;
|
|
45090
|
+
const upgradedFingerprint = conversationFingerprintUpgrade(sharedSession.conversationFingerprint, incomingFingerprint);
|
|
45091
|
+
setSharedSession({
|
|
45092
|
+
...sharedSession,
|
|
45093
|
+
cursor: batch.promptStart,
|
|
45094
|
+
cwd,
|
|
45095
|
+
...upgradedFingerprint ? { conversationFingerprint: upgradedFingerprint } : {}
|
|
45096
|
+
});
|
|
45097
|
+
const batching = batch.userMessageCount > 1 ? `batched ${batch.userMessageCount} consecutive user messages, ` : batch.promptStart > cursorBeforeUpdate ? "advanced cursor past trailing assistant, " : "";
|
|
45098
|
+
debug(`Case 3: ${batching}resuming session ${sharedSession.sessionId.slice(0, 8)}, cursor=${batch.promptStart}, account=${accountProfileId ?? "default"}`);
|
|
45099
|
+
debug(`syncResult: path=reuse sessionId=${sharedSession.sessionId} cursor=${batch.promptStart} promptUsers=${batch.userMessageCount}`);
|
|
45100
|
+
return {
|
|
45101
|
+
sessionId: sharedSession.sessionId,
|
|
45102
|
+
promptStart: batch.promptStart
|
|
45103
|
+
};
|
|
44373
45104
|
}
|
|
44374
45105
|
}
|
|
44375
45106
|
if (priorMessages.length === 0) {
|
|
44376
|
-
debug(`Case 1: clean start, ${messages.length} total messages`);
|
|
45107
|
+
debug(`Case 1: clean start, ${messages.length} total messages, account=${accountProfileId ?? "default"}`);
|
|
44377
45108
|
debug(`syncResult: path=clean-start`);
|
|
44378
|
-
return { sessionId: null };
|
|
45109
|
+
return { sessionId: null, promptStart: messages.length - 1 };
|
|
44379
45110
|
}
|
|
44380
|
-
const
|
|
44381
|
-
const
|
|
45111
|
+
const replacedSessionId = sharedSession?.sessionId;
|
|
45112
|
+
const previousSessionId = sameAccount ? sharedSession?.sessionId : void 0;
|
|
45113
|
+
const previousCursor = sameAccount ? sharedSession?.cursor ?? 0 : 0;
|
|
44382
45114
|
const preserveId = previousSessionId !== void 0 && !sharedSession?.forceRotate;
|
|
44383
45115
|
if (preserveId) {
|
|
44384
|
-
deleteSession(previousSessionId, cwd,
|
|
45116
|
+
deleteSession(previousSessionId, cwd, claudeDir);
|
|
44385
45117
|
}
|
|
44386
45118
|
const session = createSession({
|
|
44387
45119
|
projectPath: cwd,
|
|
44388
|
-
claudeDir
|
|
45120
|
+
claudeDir,
|
|
44389
45121
|
...preserveId ? { sessionId: previousSessionId } : {},
|
|
44390
45122
|
...modelId ? { model: modelId } : {}
|
|
44391
45123
|
});
|
|
44392
45124
|
convertAndImportMessages(session, priorMessages, customToolNameToSdk, cwd);
|
|
44393
45125
|
session.save();
|
|
44394
|
-
verifyWrittenSession2(session.jsonlPath, session.sessionId, session.messages.length, cwd);
|
|
44395
|
-
setSharedSession({
|
|
44396
|
-
|
|
45126
|
+
verifyWrittenSession2(session.jsonlPath, session.sessionId, session.messages.length, cwd, claudeDir);
|
|
45127
|
+
setSharedSession({
|
|
45128
|
+
sessionId: session.sessionId,
|
|
45129
|
+
cursor: priorMessages.length,
|
|
45130
|
+
cwd,
|
|
45131
|
+
// The rebuilt file's content IS this context, so its anchor is the
|
|
45132
|
+
// record's identity — including after a compact/tree-nav that moved it.
|
|
45133
|
+
...incomingFingerprint ? { conversationFingerprint: incomingFingerprint } : {},
|
|
45134
|
+
...accountProfileId ? { accountProfileId } : {},
|
|
45135
|
+
...scopeConfigDir ? { claudeConfigDir: scopeConfigDir } : {}
|
|
45136
|
+
});
|
|
45137
|
+
if (replacedSessionId === void 0) {
|
|
44397
45138
|
debug(`Case 2: first turn with ${priorMessages.length} prior messages \u2192 session ${session.sessionId.slice(0, 8)}, ${session.messages.length} records`);
|
|
45139
|
+
} else if (!sameAccount) {
|
|
45140
|
+
debug(`Case 5 account-rotation: ${priorMessages.length} prior messages \u2192 new session ${session.sessionId.slice(0, 8)} for account ${accountProfileId ?? "default"} (replaced ${replacedSessionId.slice(0, 8)})`);
|
|
44398
45141
|
} else if (preserveId) {
|
|
44399
45142
|
const missedCount = priorMessages.length - previousCursor;
|
|
44400
45143
|
debug(`Case 4: ${missedCount} missed messages, ${priorMessages.length} total \u2192 rewrote session ${session.sessionId.slice(0, 8)} (same id), ${session.messages.length} records`);
|
|
44401
45144
|
} else {
|
|
44402
45145
|
debug(`Case 4 post-abort: ${priorMessages.length} total \u2192 new session ${session.sessionId.slice(0, 8)} (was ${previousSessionId.slice(0, 8)}, rotated to avoid race with orphan writer), ${session.messages.length} records`);
|
|
44403
45146
|
}
|
|
44404
|
-
debugSessionPaths(`${session.sessionId.slice(0, 8)}`, cwd, session.jsonlPath);
|
|
44405
|
-
debug(`syncResult: path=rebuild sessionId=${session.sessionId} priors=${priorMessages.length} ${
|
|
44406
|
-
return { sessionId: session.sessionId };
|
|
45147
|
+
debugSessionPaths(`${session.sessionId.slice(0, 8)}`, cwd, session.jsonlPath, claudeDir);
|
|
45148
|
+
debug(`syncResult: path=rebuild sessionId=${session.sessionId} priors=${priorMessages.length} ${replacedSessionId === void 0 ? "first" : !sameAccount ? "account-rotated" : preserveId ? "preserved" : "rotated-post-abort"}`);
|
|
45149
|
+
return { sessionId: session.sessionId, promptStart: messages.length - 1 };
|
|
44407
45150
|
}
|
|
44408
45151
|
|
|
44409
45152
|
// src/stream-idle-watchdog.ts
|
|
@@ -44492,75 +45235,6 @@ function createStreamIdleWatchdog({
|
|
|
44492
45235
|
};
|
|
44493
45236
|
}
|
|
44494
45237
|
|
|
44495
|
-
// src/rate-limit.ts
|
|
44496
|
-
var RATE_LIMIT_AUTO_RESUME_EVENT = "vstack:rate-limit";
|
|
44497
|
-
var RATE_LIMIT_TOKEN = "\x1B[31m[rate-limit]\x1B[39m";
|
|
44498
|
-
var USAGE_LIMIT_PREFIXES = Array.isArray(qO) ? qO : [];
|
|
44499
|
-
function coerceMessageText(value) {
|
|
44500
|
-
if (typeof value === "string") return value;
|
|
44501
|
-
if (value instanceof Error) return value.message;
|
|
44502
|
-
try {
|
|
44503
|
-
return JSON.stringify(value ?? "");
|
|
44504
|
-
} catch {
|
|
44505
|
-
return String(value);
|
|
44506
|
-
}
|
|
44507
|
-
}
|
|
44508
|
-
function isExtraUsageRequiredMessage(value) {
|
|
44509
|
-
return /extra[-\s]?usage|overage|extra usage billing|extra usage credits|1M context/i.test(coerceMessageText(value));
|
|
44510
|
-
}
|
|
44511
|
-
function isUsageLimitMessage(value) {
|
|
44512
|
-
const text = coerceMessageText(value);
|
|
44513
|
-
return USAGE_LIMIT_PREFIXES.some((prefix) => text.includes(prefix));
|
|
44514
|
-
}
|
|
44515
|
-
function uniqueNonEmptyLines(values) {
|
|
44516
|
-
const seen = /* @__PURE__ */ new Set();
|
|
44517
|
-
const out = [];
|
|
44518
|
-
for (const value of values) {
|
|
44519
|
-
const text = typeof value === "string" ? value.trim() : value == null ? "" : String(value).trim();
|
|
44520
|
-
if (!text || seen.has(text)) continue;
|
|
44521
|
-
seen.add(text);
|
|
44522
|
-
out.push(text);
|
|
44523
|
-
}
|
|
44524
|
-
return out;
|
|
44525
|
-
}
|
|
44526
|
-
function resetTimestampMs(value) {
|
|
44527
|
-
let parsed = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
44528
|
-
if (!Number.isFinite(parsed)) return void 0;
|
|
44529
|
-
if (typeof value === "number" && Math.abs(parsed) < 1e12) parsed *= 1e3;
|
|
44530
|
-
return parsed;
|
|
44531
|
-
}
|
|
44532
|
-
function formatResetTimestamp(value) {
|
|
44533
|
-
const parsed = resetTimestampMs(value);
|
|
44534
|
-
if (parsed === void 0) return "unknown";
|
|
44535
|
-
return new Date(parsed).toLocaleString(void 0, {
|
|
44536
|
-
day: "numeric",
|
|
44537
|
-
hour: "numeric",
|
|
44538
|
-
minute: "2-digit",
|
|
44539
|
-
month: "short",
|
|
44540
|
-
second: "2-digit",
|
|
44541
|
-
timeZoneName: "short",
|
|
44542
|
-
year: "numeric"
|
|
44543
|
-
});
|
|
44544
|
-
}
|
|
44545
|
-
var ALLOWED_RATE_LIMIT_WARNING_UTILIZATION_THRESHOLD = 80;
|
|
44546
|
-
function normalizeRateLimitUtilization(value) {
|
|
44547
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
44548
|
-
if (value === 0) return 0;
|
|
44549
|
-
if (value > 0 && value < 1) return value * 100;
|
|
44550
|
-
if (value > 1 && value <= 100) return value;
|
|
44551
|
-
return void 0;
|
|
44552
|
-
}
|
|
44553
|
-
function rateLimitTypeLabel(value) {
|
|
44554
|
-
const text = typeof value === "string" ? value.trim() : "";
|
|
44555
|
-
return text || "unknown";
|
|
44556
|
-
}
|
|
44557
|
-
function formatAllowedRateLimitWarning(info) {
|
|
44558
|
-
if (info?.status !== "allowed_warning") return void 0;
|
|
44559
|
-
const utilization = normalizeRateLimitUtilization(info.utilization);
|
|
44560
|
-
if (utilization === void 0 || utilization < ALLOWED_RATE_LIMIT_WARNING_UTILIZATION_THRESHOLD) return void 0;
|
|
44561
|
-
return `Claude rate limit warning: nearing ${rateLimitTypeLabel(info.rateLimitType)} limit; check Claude Code /usage for exact utilization.`;
|
|
44562
|
-
}
|
|
44563
|
-
|
|
44564
45238
|
// src/tool-mapping.ts
|
|
44565
45239
|
var SDK_TO_PI_TOOL_NAME = {
|
|
44566
45240
|
read: "read",
|
|
@@ -44607,8 +45281,7 @@ function mapToolArgs(toolName, args) {
|
|
|
44607
45281
|
|
|
44608
45282
|
// src/assistant-stream.ts
|
|
44609
45283
|
import { calculateCost } from "@earendil-works/pi-ai";
|
|
44610
|
-
function updateUsage(output, usage, model) {
|
|
44611
|
-
const c = ctx();
|
|
45284
|
+
function updateUsage(output, usage, model, c) {
|
|
44612
45285
|
const current = c.currentMessageUsage;
|
|
44613
45286
|
const carry = c.turnUsageCarry;
|
|
44614
45287
|
if (usage.input_tokens != null) current.input = usage.input_tokens;
|
|
@@ -44663,6 +45336,17 @@ var TOOL_USE_END_GRACE_MS = 1500;
|
|
|
44663
45336
|
function endToolUseTurn(c) {
|
|
44664
45337
|
if (!c.currentPiStream || !c.turnOutput) return;
|
|
44665
45338
|
cancelScheduledToolUseEnd(c);
|
|
45339
|
+
const partial2 = c.turnOutput.content.filter((b) => b?.type === "toolCall" && "partialJson" in b);
|
|
45340
|
+
if (partial2.length > 0) {
|
|
45341
|
+
const calls = partial2.map((b) => ({ id: b.id, name: b.name }));
|
|
45342
|
+
debug(`endToolUseTurn: pruning ${partial2.length} still-partial tool call(s) \u2014 truncated arguments never execute:`, calls.map((entry) => `${entry.name} [${entry.id}]`).join(", "));
|
|
45343
|
+
diagDump("partial_tool_calls_pruned", { count: partial2.length, calls });
|
|
45344
|
+
appendIntegrityEntry("partial_tool_calls_pruned", { count: partial2.length, calls });
|
|
45345
|
+
c.turnOutput.content = c.turnOutput.content.filter((b) => !(b?.type === "toolCall" && "partialJson" in b));
|
|
45346
|
+
}
|
|
45347
|
+
for (const block of c.turnOutput.content) {
|
|
45348
|
+
if (block?.type === "toolCall" && typeof block.id === "string") c.forwardedToolCallIds.add(block.id);
|
|
45349
|
+
}
|
|
44666
45350
|
c.turnOutput.stopReason = "toolUse";
|
|
44667
45351
|
c.currentPiStream.push({ type: "done", reason: "toolUse", message: c.turnOutput });
|
|
44668
45352
|
c.currentPiStream.end();
|
|
@@ -44691,33 +45375,42 @@ function reapStaleQueuedResults(c) {
|
|
|
44691
45375
|
const stale = c.takeStaleQueuedResults();
|
|
44692
45376
|
if (stale.length === 0) return;
|
|
44693
45377
|
const names = stale.map((entry) => entry.toolName);
|
|
44694
|
-
debug(`reapStaleQueuedResults:
|
|
44695
|
-
diagDump("
|
|
44696
|
-
appendIntegrityEntry("
|
|
45378
|
+
debug(`reapStaleQueuedResults: parked ${stale.length} early tool result(s) awaiting a late handler:`, names.join(", "));
|
|
45379
|
+
diagDump("stale_queued_tool_results_parked", { count: stale.length, stale });
|
|
45380
|
+
appendIntegrityEntry("stale_queued_tool_results_parked", { count: stale.length, stale });
|
|
44697
45381
|
safeNotify(
|
|
44698
|
-
`Claude bridge:
|
|
45382
|
+
`Claude bridge: parked ${stale.length} early tool result(s) whose handler has not arrived (${names.slice(0, 6).join(", ")}${names.length > 6 ? ", \u2026" : ""}). A late handler can still consume them.`,
|
|
44699
45383
|
"warning"
|
|
44700
45384
|
);
|
|
44701
45385
|
}
|
|
44702
|
-
function updateTurnOutputModel(modelId) {
|
|
44703
|
-
const c = ctx();
|
|
45386
|
+
function updateTurnOutputModel(modelId, c = ctx()) {
|
|
44704
45387
|
if (typeof modelId !== "string" || !modelId || !c.turnOutput) return;
|
|
44705
45388
|
if (c.turnOutput.model === modelId) return;
|
|
44706
45389
|
debug(`provider: active Claude model changed ${c.turnOutput.model} -> ${modelId}`);
|
|
44707
45390
|
c.turnOutput.model = modelId;
|
|
44708
45391
|
}
|
|
44709
|
-
|
|
44710
|
-
|
|
45392
|
+
var FINALIZE_MAX_REARMS = 3;
|
|
45393
|
+
function finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, toolName, mappedArgs, rearmCount = 0) {
|
|
45394
|
+
if (!queryCtx.currentPiStream || !queryCtx.turnOutput) {
|
|
45395
|
+
if (failStrandedToolCall(queryCtx, toolCallId)) {
|
|
45396
|
+
debug(`mcp handler: ${toolName} [${toolCallId}] stranded \u2014 turn ended before its call reached Pi; resolved with error`);
|
|
45397
|
+
diagDump("tool_handler_stranded", { toolCallId, toolName, site: "finalize-no-stream" });
|
|
45398
|
+
appendIntegrityEntry("tool_handler_stranded", { toolCallId, toolName, site: "finalize-no-stream" });
|
|
45399
|
+
}
|
|
45400
|
+
return;
|
|
45401
|
+
}
|
|
44711
45402
|
let idx = queryCtx.turnBlocks.findIndex((b) => b.type === "toolCall" && b.id === toolCallId);
|
|
44712
45403
|
if (idx >= 0) {
|
|
44713
45404
|
const block = queryCtx.turnBlocks[idx];
|
|
44714
45405
|
if ("partialJson" in block) {
|
|
44715
|
-
block.arguments =
|
|
45406
|
+
block.arguments = mappedArgs;
|
|
44716
45407
|
queryCtx.updateToolCallArgs(block.id, block.arguments);
|
|
44717
45408
|
delete block.partialJson;
|
|
44718
45409
|
delete block.index;
|
|
44719
45410
|
queryCtx.currentPiStream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: queryCtx.turnOutput });
|
|
44720
45411
|
}
|
|
45412
|
+
} else if (queryCtx.forwardedToolCallIds.has(toolCallId) || queryCtx.deadToolCallIds.has(toolCallId)) {
|
|
45413
|
+
debug(`mcp handler: ${toolName} [${toolCallId}] already ${queryCtx.forwardedToolCallIds.has(toolCallId) ? "forwarded" : "dead"} \u2014 not re-emitting`);
|
|
44721
45414
|
} else {
|
|
44722
45415
|
queryCtx.turnBlocks.push({ type: "toolCall", id: toolCallId, name: toolName, arguments: mappedArgs });
|
|
44723
45416
|
idx = queryCtx.turnBlocks.length - 1;
|
|
@@ -44725,12 +45418,37 @@ function finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, toolName, ma
|
|
|
44725
45418
|
queryCtx.currentPiStream.push({ type: "toolcall_start", contentIndex: idx, partial: queryCtx.turnOutput });
|
|
44726
45419
|
queryCtx.currentPiStream.push({ type: "toolcall_end", contentIndex: idx, toolCall: block, partial: queryCtx.turnOutput });
|
|
44727
45420
|
}
|
|
45421
|
+
for (let i = 0; i < queryCtx.turnBlocks.length; i++) {
|
|
45422
|
+
const sibling = queryCtx.turnBlocks[i];
|
|
45423
|
+
if (sibling.type !== "toolCall" || !("partialJson" in sibling)) continue;
|
|
45424
|
+
const waiting = queryCtx.pendingToolCalls.get(sibling.id);
|
|
45425
|
+
if (!waiting) continue;
|
|
45426
|
+
sibling.arguments = waiting.args;
|
|
45427
|
+
queryCtx.updateToolCallArgs(sibling.id, sibling.arguments);
|
|
45428
|
+
delete sibling.partialJson;
|
|
45429
|
+
delete sibling.index;
|
|
45430
|
+
queryCtx.currentPiStream.push({ type: "toolcall_end", contentIndex: i, toolCall: sibling, partial: queryCtx.turnOutput });
|
|
45431
|
+
}
|
|
45432
|
+
const unsettled = queryCtx.turnBlocks.filter((b) => b.type === "toolCall" && "partialJson" in b);
|
|
45433
|
+
if (unsettled.length > 0 && rearmCount < FINALIZE_MAX_REARMS) {
|
|
45434
|
+
debug(`mcp handler: ${unsettled.length} sibling tool call(s) still streaming \u2014 re-arming grace (${rearmCount + 1}/${FINALIZE_MAX_REARMS})`);
|
|
45435
|
+
scheduleToolUseTurnEnd(
|
|
45436
|
+
queryCtx,
|
|
45437
|
+
() => finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, toolName, mappedArgs, rearmCount + 1),
|
|
45438
|
+
`finalize-rearm:${toolName}`
|
|
45439
|
+
);
|
|
45440
|
+
return;
|
|
45441
|
+
}
|
|
45442
|
+
const executable = queryCtx.turnBlocks.some((b) => b.type === "toolCall" && !("partialJson" in b));
|
|
45443
|
+
if (!executable) {
|
|
45444
|
+
debug(`mcp handler: nothing executable in this turn after suppression \u2014 leaving the stream to its own terminal events (${toolName} [${toolCallId}])`);
|
|
45445
|
+
return;
|
|
45446
|
+
}
|
|
44728
45447
|
queryCtx.turnSawToolCall = true;
|
|
44729
45448
|
debug(`mcp handler: finalizing tool_use turn from MCP invocation [${toolCallId}] (${toolName}) \u2014 terminal stream events never arrived`);
|
|
44730
45449
|
endToolUseTurn(queryCtx);
|
|
44731
45450
|
}
|
|
44732
|
-
function processStreamEvent(message, customToolNameToPi, model) {
|
|
44733
|
-
const c = ctx();
|
|
45451
|
+
function processStreamEvent(message, customToolNameToPi, model, c = ctx()) {
|
|
44734
45452
|
if (!c.currentPiStream || !c.turnOutput) return;
|
|
44735
45453
|
const event = message.event;
|
|
44736
45454
|
if (event?.type === "ping") return;
|
|
@@ -44742,14 +45460,15 @@ function processStreamEvent(message, customToolNameToPi, model) {
|
|
|
44742
45460
|
reapStaleQueuedResults(c);
|
|
44743
45461
|
c.resetToolTracking();
|
|
44744
45462
|
c.beginChildMessage(event.message?.id);
|
|
44745
|
-
updateTurnOutputModel(event.message?.model);
|
|
44746
|
-
if (event.message?.usage) updateUsage(c.turnOutput, event.message.usage, model);
|
|
45463
|
+
updateTurnOutputModel(event.message?.model, c);
|
|
45464
|
+
if (event.message?.usage) updateUsage(c.turnOutput, event.message.usage, model, c);
|
|
44747
45465
|
return;
|
|
44748
45466
|
}
|
|
44749
45467
|
if (event?.type === "content_block_start") {
|
|
44750
45468
|
c.turnSawStreamEvent = true;
|
|
44751
|
-
ensureTurnStarted();
|
|
45469
|
+
ensureTurnStarted(c);
|
|
44752
45470
|
c.childExecutedStreamIndexes.delete(event.index);
|
|
45471
|
+
c.suppressedStreamIndexes.delete(event.index);
|
|
44753
45472
|
if (event.content_block?.type === "tool_use" && isChildExecutedTool(event.content_block.name)) {
|
|
44754
45473
|
c.noteChildExecutedToolCall(event.content_block.id, event.content_block.name, event.index);
|
|
44755
45474
|
debug(`processStreamEvent: child-executed tool ${event.content_block.name} [${event.content_block.id}] \u2014 not mirrored as a Pi tool call`);
|
|
@@ -44762,6 +45481,17 @@ function processStreamEvent(message, customToolNameToPi, model) {
|
|
|
44762
45481
|
c.turnBlocks.push({ type: "thinking", thinking: "", thinkingSignature: "", index: event.index });
|
|
44763
45482
|
c.currentPiStream.push({ type: "thinking_start", contentIndex: c.turnBlocks.length - 1, partial: c.turnOutput });
|
|
44764
45483
|
} else if (event.content_block?.type === "tool_use") {
|
|
45484
|
+
const streamedId = event.content_block.id;
|
|
45485
|
+
if (typeof streamedId === "string" && (c.forwardedToolCallIds.has(streamedId) || c.deadToolCallIds.has(streamedId))) {
|
|
45486
|
+
c.suppressedStreamIndexes.add(event.index);
|
|
45487
|
+
debug(`processStreamEvent: tool_use ${streamedId} already ${c.forwardedToolCallIds.has(streamedId) ? "forwarded" : "dead"} \u2014 suppressing duplicate stream block`);
|
|
45488
|
+
return;
|
|
45489
|
+
}
|
|
45490
|
+
if (typeof streamedId === "string" && c.turnBlocks.some((b) => b.type === "toolCall" && b.id === streamedId)) {
|
|
45491
|
+
c.suppressedStreamIndexes.add(event.index);
|
|
45492
|
+
debug(`processStreamEvent: tool_use ${streamedId} already recorded in this turn \u2014 suppressing duplicate stream block`);
|
|
45493
|
+
return;
|
|
45494
|
+
}
|
|
44765
45495
|
c.turnSawToolCall = true;
|
|
44766
45496
|
const mappedName = mapToolName(event.content_block.name, customToolNameToPi);
|
|
44767
45497
|
c.recordToolCall(event.content_block.id, mappedName, {});
|
|
@@ -44780,7 +45510,7 @@ function processStreamEvent(message, customToolNameToPi, model) {
|
|
|
44780
45510
|
return;
|
|
44781
45511
|
}
|
|
44782
45512
|
if (event?.type === "content_block_delta") {
|
|
44783
|
-
if (c.childExecutedStreamIndexes.has(event.index)) {
|
|
45513
|
+
if (c.childExecutedStreamIndexes.has(event.index) || c.suppressedStreamIndexes.has(event.index)) {
|
|
44784
45514
|
c.turnSawStreamEvent = true;
|
|
44785
45515
|
return;
|
|
44786
45516
|
}
|
|
@@ -44809,7 +45539,7 @@ function processStreamEvent(message, customToolNameToPi, model) {
|
|
|
44809
45539
|
return;
|
|
44810
45540
|
}
|
|
44811
45541
|
if (event?.type === "content_block_stop") {
|
|
44812
|
-
if (c.childExecutedStreamIndexes.has(event.index)) {
|
|
45542
|
+
if (c.childExecutedStreamIndexes.has(event.index) || c.suppressedStreamIndexes.has(event.index)) {
|
|
44813
45543
|
c.turnSawStreamEvent = true;
|
|
44814
45544
|
return;
|
|
44815
45545
|
}
|
|
@@ -44839,7 +45569,7 @@ function processStreamEvent(message, customToolNameToPi, model) {
|
|
|
44839
45569
|
}
|
|
44840
45570
|
if (event?.type === "message_delta") {
|
|
44841
45571
|
c.turnOutput.stopReason = mapStopReason(event.delta?.stop_reason);
|
|
44842
|
-
if (event.usage) updateUsage(c.turnOutput, event.usage, model);
|
|
45572
|
+
if (event.usage) updateUsage(c.turnOutput, event.usage, model, c);
|
|
44843
45573
|
return;
|
|
44844
45574
|
}
|
|
44845
45575
|
if (event?.type === "message_stop" && c.turnSawToolCall) {
|
|
@@ -44850,9 +45580,9 @@ function processStreamEvent(message, customToolNameToPi, model) {
|
|
|
44850
45580
|
debug("processStreamEvent: unhandled event type", event?.type);
|
|
44851
45581
|
}
|
|
44852
45582
|
}
|
|
44853
|
-
function appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameToPi) {
|
|
44854
|
-
const c = ctx();
|
|
45583
|
+
function appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameToPi, c) {
|
|
44855
45584
|
if (!assistantMsg?.content) return false;
|
|
45585
|
+
const streamLive = Boolean(c.currentPiStream && c.turnOutput);
|
|
44856
45586
|
let sawToolUse = false;
|
|
44857
45587
|
for (const block of assistantMsg.content) {
|
|
44858
45588
|
if (block.type !== "tool_use") continue;
|
|
@@ -44861,11 +45591,16 @@ function appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameT
|
|
|
44861
45591
|
debug(`assistant message: child-executed tool ${block.name} [${block.id}] \u2014 not mirrored as a Pi tool call`);
|
|
44862
45592
|
continue;
|
|
44863
45593
|
}
|
|
44864
|
-
sawToolUse = true;
|
|
44865
45594
|
const existingIdx = c.turnBlocks.findIndex((b) => b.type === "toolCall" && b.id === block.id);
|
|
45595
|
+
if (existingIdx < 0 && (c.forwardedToolCallIds.has(block.id) || c.deadToolCallIds.has(block.id))) {
|
|
45596
|
+
debug(`assistant message: tool_use ${block.id} already ${c.forwardedToolCallIds.has(block.id) ? "forwarded" : "dead"} \u2014 skipping duplicate`);
|
|
45597
|
+
continue;
|
|
45598
|
+
}
|
|
45599
|
+
sawToolUse = true;
|
|
44866
45600
|
const name = mapToolName(block.name, customToolNameToPi);
|
|
44867
45601
|
const mappedArgs = mapToolArgs(name, block.input);
|
|
44868
45602
|
c.recordToolCall(block.id, name, mappedArgs);
|
|
45603
|
+
if (!streamLive) continue;
|
|
44869
45604
|
if (existingIdx >= 0) {
|
|
44870
45605
|
const existing = c.turnBlocks[existingIdx];
|
|
44871
45606
|
existing.name = name;
|
|
@@ -44878,7 +45613,7 @@ function appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameT
|
|
|
44878
45613
|
}
|
|
44879
45614
|
continue;
|
|
44880
45615
|
}
|
|
44881
|
-
ensureTurnStarted();
|
|
45616
|
+
ensureTurnStarted(c);
|
|
44882
45617
|
c.turnBlocks.push({
|
|
44883
45618
|
type: "toolCall",
|
|
44884
45619
|
id: block.id,
|
|
@@ -44890,11 +45625,10 @@ function appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameT
|
|
|
44890
45625
|
c.currentPiStream?.push({ type: "toolcall_start", contentIndex: idx, partial: c.turnOutput });
|
|
44891
45626
|
c.currentPiStream?.push({ type: "toolcall_end", contentIndex: idx, toolCall: toolBlock, partial: c.turnOutput });
|
|
44892
45627
|
}
|
|
44893
|
-
if (assistantMsg.usage && c.turnOutput && c.currentPiStream) updateUsage(c.turnOutput, assistantMsg.usage, model);
|
|
45628
|
+
if (assistantMsg.usage && c.turnOutput && c.currentPiStream) updateUsage(c.turnOutput, assistantMsg.usage, model, c);
|
|
44894
45629
|
return sawToolUse;
|
|
44895
45630
|
}
|
|
44896
|
-
function noteChildExecutedToolResults(message) {
|
|
44897
|
-
const c = ctx();
|
|
45631
|
+
function noteChildExecutedToolResults(message, c = ctx()) {
|
|
44898
45632
|
if (c.childExecutedToolCalls.size === 0) return;
|
|
44899
45633
|
const content = message.message?.content;
|
|
44900
45634
|
if (!Array.isArray(content)) return;
|
|
@@ -44908,13 +45642,12 @@ function noteChildExecutedToolResults(message) {
|
|
|
44908
45642
|
debug(`child-executed tool result: ${name} [${block.tool_use_id}] isError=${isError} byteSize=${byteSize ?? "unknown"} audited=${audited}`);
|
|
44909
45643
|
}
|
|
44910
45644
|
}
|
|
44911
|
-
function processAssistantMessage(message, model, customToolNameToPi) {
|
|
44912
|
-
const c = ctx();
|
|
45645
|
+
function processAssistantMessage(message, model, customToolNameToPi, c = ctx()) {
|
|
44913
45646
|
const assistantMsg = message.message;
|
|
44914
45647
|
if (!assistantMsg?.content) return;
|
|
44915
|
-
updateTurnOutputModel(assistantMsg.model);
|
|
45648
|
+
updateTurnOutputModel(assistantMsg.model, c);
|
|
44916
45649
|
if (c.turnSawStreamEvent) {
|
|
44917
|
-
if (appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameToPi)) {
|
|
45650
|
+
if (appendMissingToolUsesFromAssistant(assistantMsg, model, customToolNameToPi, c)) {
|
|
44918
45651
|
c.turnSawToolCall = true;
|
|
44919
45652
|
scheduleToolUseTurnEnd(c, () => endToolUseTurn(c), "assistant-boundary");
|
|
44920
45653
|
}
|
|
@@ -44931,7 +45664,7 @@ function processAssistantMessage(message, model, customToolNameToPi) {
|
|
|
44931
45664
|
for (const block of assistantMsg.content) {
|
|
44932
45665
|
if (block.type === "text" && block.text) {
|
|
44933
45666
|
if (alreadyRendered("text", block.text)) continue;
|
|
44934
|
-
ensureTurnStarted();
|
|
45667
|
+
ensureTurnStarted(c);
|
|
44935
45668
|
c.turnBlocks.push({ type: "text", text: block.text });
|
|
44936
45669
|
const idx = c.turnBlocks.length - 1;
|
|
44937
45670
|
c.currentPiStream?.push({ type: "text_start", contentIndex: idx, partial: c.turnOutput });
|
|
@@ -44939,7 +45672,7 @@ function processAssistantMessage(message, model, customToolNameToPi) {
|
|
|
44939
45672
|
c.currentPiStream?.push({ type: "text_end", contentIndex: idx, content: block.text, partial: c.turnOutput });
|
|
44940
45673
|
} else if (block.type === "thinking") {
|
|
44941
45674
|
if (alreadyRendered("thinking", block.thinking ?? "")) continue;
|
|
44942
|
-
ensureTurnStarted();
|
|
45675
|
+
ensureTurnStarted(c);
|
|
44943
45676
|
c.turnBlocks.push({ type: "thinking", thinking: block.thinking ?? "", thinkingSignature: block.signature ?? "" });
|
|
44944
45677
|
const idx = c.turnBlocks.length - 1;
|
|
44945
45678
|
c.currentPiStream?.push({ type: "thinking_start", contentIndex: idx, partial: c.turnOutput });
|
|
@@ -44951,7 +45684,11 @@ function processAssistantMessage(message, model, customToolNameToPi) {
|
|
|
44951
45684
|
debug(`processAssistantMessage fallback: child-executed tool ${block.name} [${block.id}] \u2014 not mirrored as a Pi tool call`);
|
|
44952
45685
|
continue;
|
|
44953
45686
|
}
|
|
44954
|
-
|
|
45687
|
+
if (!c.turnBlocks.some((b) => b.type === "toolCall" && b.id === block.id) && (c.forwardedToolCallIds.has(block.id) || c.deadToolCallIds.has(block.id))) {
|
|
45688
|
+
debug(`processAssistantMessage fallback: tool_use ${block.id} already ${c.forwardedToolCallIds.has(block.id) ? "forwarded" : "dead"} \u2014 skipping duplicate`);
|
|
45689
|
+
continue;
|
|
45690
|
+
}
|
|
45691
|
+
ensureTurnStarted(c);
|
|
44955
45692
|
c.turnSawToolCall = true;
|
|
44956
45693
|
const mappedName = mapToolName(block.name, customToolNameToPi);
|
|
44957
45694
|
const mappedArgs = mapToolArgs(mappedName, block.input);
|
|
@@ -44975,108 +45712,641 @@ function processAssistantMessage(message, model, customToolNameToPi) {
|
|
|
44975
45712
|
c.currentPiStream?.push({ type: "toolcall_start", contentIndex: idx, partial: c.turnOutput });
|
|
44976
45713
|
c.currentPiStream?.push({ type: "toolcall_end", contentIndex: idx, toolCall: toolBlock, partial: c.turnOutput });
|
|
44977
45714
|
} else if (block.type === "fallback") {
|
|
44978
|
-
updateTurnOutputModel(block.to?.model);
|
|
45715
|
+
updateTurnOutputModel(block.to?.model, c);
|
|
44979
45716
|
} else {
|
|
44980
45717
|
debug("processAssistantMessage: unhandled block type", block.type);
|
|
44981
45718
|
}
|
|
44982
45719
|
}
|
|
44983
|
-
if (assistantMsg.usage && c.turnOutput) updateUsage(c.turnOutput, assistantMsg.usage, model);
|
|
45720
|
+
if (assistantMsg.usage && c.turnOutput) updateUsage(c.turnOutput, assistantMsg.usage, model, c);
|
|
44984
45721
|
if (c.turnSawToolCall && c.currentPiStream && c.turnOutput) {
|
|
44985
45722
|
endToolUseTurn(c);
|
|
44986
45723
|
}
|
|
44987
45724
|
}
|
|
44988
45725
|
|
|
44989
|
-
// src/
|
|
44990
|
-
var
|
|
44991
|
-
|
|
44992
|
-
|
|
44993
|
-
var PRIMARY_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:primaryInstance");
|
|
44994
|
-
var ACTIVE_STREAM_SIMPLE_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:activeStreamSimple");
|
|
44995
|
-
var COMMANDS_REGISTERED_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:commandsRegistered");
|
|
44996
|
-
var MODELS = buildModels(getModels("anthropic"));
|
|
44997
|
-
var extraUsageHelperInFlight = null;
|
|
44998
|
-
function emitRateLimitEvent(payload) {
|
|
44999
|
-
try {
|
|
45000
|
-
extensionApi?.events?.emit?.(RATE_LIMIT_AUTO_RESUME_EVENT, payload);
|
|
45001
|
-
} catch {
|
|
45002
|
-
}
|
|
45003
|
-
}
|
|
45004
|
-
function extraUsageAllowed(config2) {
|
|
45005
|
-
return config2.provider?.allowExtraUsage === true;
|
|
45006
|
-
}
|
|
45007
|
-
var lastFastModeDisabledNoticeReason = null;
|
|
45008
|
-
var FAST_MODE_DISABLED_REASON_TEXT = {
|
|
45009
|
-
disabled_by_env: "disabled by an environment variable",
|
|
45010
|
-
extra_usage_disabled: "extra usage is disabled for this account",
|
|
45011
|
-
free: "not available on the free plan",
|
|
45012
|
-
model_not_allowed: "not available for this model",
|
|
45013
|
-
network_error: "the eligibility check hit a network error",
|
|
45014
|
-
not_first_party: "not available for this account type",
|
|
45015
|
-
preference: "disabled by a Claude Code preference",
|
|
45016
|
-
sdk_opt_in_required: "the SDK opt-in is missing",
|
|
45017
|
-
unknown: "unavailable for an unknown reason"
|
|
45018
|
-
};
|
|
45019
|
-
function noteFastModeDisabledReason(message, bridgeConfig) {
|
|
45020
|
-
if (bridgeConfig.provider?.fastMode !== true) return;
|
|
45021
|
-
const reason = message.fast_mode_disabled_reason;
|
|
45022
|
-
if (typeof reason !== "string" || reason === "pending") return;
|
|
45023
|
-
if (reason === lastFastModeDisabledNoticeReason) return;
|
|
45024
|
-
lastFastModeDisabledNoticeReason = reason;
|
|
45025
|
-
const text = FAST_MODE_DISABLED_REASON_TEXT[reason] ?? `unavailable (${reason})`;
|
|
45026
|
-
safeNotify(`Claude bridge: fast mode is enabled in settings but Claude Code declined it \u2014 ${text}.`, "warning");
|
|
45027
|
-
}
|
|
45028
|
-
function sdkTextFromMessage(message) {
|
|
45029
|
-
if (message.type === "result") return message.result;
|
|
45030
|
-
if (message.type === "assistant") {
|
|
45031
|
-
const content = message.message?.content;
|
|
45032
|
-
if (!Array.isArray(content)) return void 0;
|
|
45033
|
-
return content.map((block) => block?.type === "text" && typeof block.text === "string" ? block.text : "").filter(Boolean).join("\n");
|
|
45034
|
-
}
|
|
45035
|
-
return void 0;
|
|
45726
|
+
// src/sdk-query.ts
|
|
45727
|
+
var sdkQueryFactory = Okt;
|
|
45728
|
+
function __testSetSdkQueryFactory(factory) {
|
|
45729
|
+
sdkQueryFactory = factory ?? Okt;
|
|
45036
45730
|
}
|
|
45037
|
-
|
|
45038
|
-
|
|
45039
|
-
|
|
45040
|
-
|
|
45041
|
-
const
|
|
45042
|
-
|
|
45731
|
+
|
|
45732
|
+
// src/account-host.ts
|
|
45733
|
+
var ACCOUNT_PROBE_DEADLINE_MS = 1e4;
|
|
45734
|
+
async function probeClaudeAccountProfile(input) {
|
|
45735
|
+
const config2 = loadConfig(input.cwd);
|
|
45736
|
+
const claudeExecutable = resolveClaudeExecutable(config2.provider?.pathToClaudeCodeExecutable);
|
|
45737
|
+
if (claudeExecutable) preflightClaudeExecutable(claudeExecutable, input.cwd);
|
|
45738
|
+
const probe = sdkQueryFactory({
|
|
45739
|
+
prompt: "/usage",
|
|
45043
45740
|
options: {
|
|
45044
|
-
cwd,
|
|
45045
|
-
env: {
|
|
45741
|
+
cwd: input.cwd,
|
|
45742
|
+
env: {
|
|
45743
|
+
...subscriberProfileEnv(input.profile),
|
|
45744
|
+
ENABLE_CLAUDEAI_MCP_SERVERS: "0",
|
|
45745
|
+
DISABLE_AUTO_COMPACT: "1"
|
|
45746
|
+
},
|
|
45046
45747
|
maxTurns: 1,
|
|
45748
|
+
// bypassPermissions makes tool containment the only gate, so the probe
|
|
45749
|
+
// gets BOTH layers: the standard bridge isolation lists AND a deny-all
|
|
45750
|
+
// PreToolUse hook — a /usage probe has no business executing anything.
|
|
45751
|
+
permissionMode: "bypassPermissions",
|
|
45752
|
+
...CLAUDE_BRIDGE_TOOL_ISOLATION,
|
|
45753
|
+
hooks: { PreToolUse: [{ hooks: [denyAllToolsHook()] }] },
|
|
45047
45754
|
...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
|
|
45048
45755
|
spawnClaudeCodeProcess: spawnClaudeCodeWithDiagnostics,
|
|
45049
|
-
...makeCliDebugOptions("
|
|
45756
|
+
...makeCliDebugOptions("account-probe")
|
|
45050
45757
|
}
|
|
45051
45758
|
});
|
|
45052
|
-
const
|
|
45053
|
-
|
|
45054
|
-
|
|
45055
|
-
|
|
45056
|
-
|
|
45759
|
+
const onAbort = () => {
|
|
45760
|
+
void probe.interrupt().catch(() => {
|
|
45761
|
+
});
|
|
45762
|
+
try {
|
|
45763
|
+
probe.close();
|
|
45764
|
+
} catch {
|
|
45765
|
+
}
|
|
45766
|
+
};
|
|
45767
|
+
if (input.signal?.aborted) onAbort();
|
|
45768
|
+
else input.signal?.addEventListener("abort", onAbort, { once: true });
|
|
45769
|
+
let controls;
|
|
45770
|
+
let deadlineTimer;
|
|
45771
|
+
const deadline = new Promise((resolveDeadline) => {
|
|
45772
|
+
deadlineTimer = setTimeout(() => {
|
|
45773
|
+
debug(`account-probe: deadline expired for ${input.profile.label}; killing probe child`);
|
|
45774
|
+
onAbort();
|
|
45775
|
+
resolveDeadline("deadline");
|
|
45776
|
+
}, input.deadlineMs ?? ACCOUNT_PROBE_DEADLINE_MS);
|
|
45777
|
+
deadlineTimer.unref?.();
|
|
45778
|
+
});
|
|
45779
|
+
const consume = (async () => {
|
|
45780
|
+
for await (const message of probe) {
|
|
45781
|
+
if (message.type === "system" && message.subtype === "init" && !controls) {
|
|
45782
|
+
controls = Promise.allSettled([
|
|
45783
|
+
probe.accountInfo(),
|
|
45784
|
+
probe.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET()
|
|
45785
|
+
]).then(([identityResult, usageResult]) => ({
|
|
45786
|
+
...identityResult.status === "fulfilled" ? { identity: {
|
|
45787
|
+
email: identityResult.value.email,
|
|
45788
|
+
organization: identityResult.value.organization,
|
|
45789
|
+
subscriptionType: identityResult.value.subscriptionType
|
|
45790
|
+
} } : {},
|
|
45791
|
+
...usageResult.status === "fulfilled" ? { usage: usageResult.value } : {}
|
|
45792
|
+
}));
|
|
45793
|
+
}
|
|
45057
45794
|
}
|
|
45795
|
+
return controls ? await controls : {};
|
|
45796
|
+
})();
|
|
45797
|
+
try {
|
|
45798
|
+
const result = await Promise.race([consume, deadline]);
|
|
45799
|
+
return result === "deadline" ? {} : result;
|
|
45058
45800
|
} finally {
|
|
45059
|
-
|
|
45801
|
+
clearTimeout(deadlineTimer);
|
|
45802
|
+
input.signal?.removeEventListener("abort", onAbort);
|
|
45803
|
+
probe.close();
|
|
45804
|
+
void consume.catch(() => {
|
|
45805
|
+
});
|
|
45060
45806
|
}
|
|
45061
|
-
return outputs.join("\n").trim() || "Claude Code /extra-usage completed.";
|
|
45062
45807
|
}
|
|
45063
|
-
|
|
45064
|
-
|
|
45065
|
-
|
|
45066
|
-
|
|
45067
|
-
|
|
45068
|
-
|
|
45069
|
-
|
|
45070
|
-
|
|
45071
|
-
|
|
45072
|
-
|
|
45073
|
-
|
|
45074
|
-
|
|
45808
|
+
var BRIDGE_ACCOUNT_HOST = {
|
|
45809
|
+
version: 1,
|
|
45810
|
+
probeProfile: probeClaudeAccountProfile
|
|
45811
|
+
};
|
|
45812
|
+
|
|
45813
|
+
// src/bridge-commands.ts
|
|
45814
|
+
var COMMANDS_REGISTERED_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:commandsRegistered");
|
|
45815
|
+
function commandCwd(ctx2) {
|
|
45816
|
+
const value = ctx2?.cwd;
|
|
45817
|
+
return typeof value === "string" && value.length > 0 ? value : process.cwd();
|
|
45818
|
+
}
|
|
45819
|
+
async function tryOpenExtensionManagerSettings(ctx2) {
|
|
45820
|
+
const host = globalThis;
|
|
45821
|
+
const openQuickSettings = host[/* @__PURE__ */ Symbol.for("vstack.pi.extension-manager.open-quick-settings")];
|
|
45822
|
+
if (typeof openQuickSettings !== "function") return false;
|
|
45823
|
+
try {
|
|
45824
|
+
await openQuickSettings(ctx2, "@vanillagreen/pi-claude-bridge");
|
|
45825
|
+
return true;
|
|
45826
|
+
} catch {
|
|
45827
|
+
return false;
|
|
45828
|
+
}
|
|
45829
|
+
}
|
|
45830
|
+
function showBridgeStatus(ctx2) {
|
|
45831
|
+
const config2 = loadConfig(commandCwd(ctx2));
|
|
45832
|
+
ctx2.ui.notify([
|
|
45833
|
+
`Pi Claude: ${config2.enabled === false ? "disabled" : "enabled"}`,
|
|
45834
|
+
"Claude account billing settings (including Extra Usage) are managed in Claude."
|
|
45835
|
+
].join("\n"), "info");
|
|
45836
|
+
}
|
|
45837
|
+
async function reportConnectorInventory(ctx2) {
|
|
45838
|
+
const account = ctx2.model ? resolveClaudeAccountRouter()?.current(ctx2.model.id, ctx2.sessionManager?.getSessionId?.()) : void 0;
|
|
45839
|
+
const credentials = resolveClaudeOAuth(readCredentialFile, connectorCredentialEnv(account ? accountSessionScope(account).claudeConfigDir : void 0));
|
|
45840
|
+
if (!credentials) {
|
|
45841
|
+
ctx2.ui.notify("Pi Claude: no Claude OAuth credentials found \u2014 cannot enumerate connectors.", "error");
|
|
45842
|
+
return;
|
|
45843
|
+
}
|
|
45844
|
+
const inventory = await listAccountConnectors({ credentials });
|
|
45845
|
+
if (!inventory.ok) {
|
|
45846
|
+
ctx2.ui.notify(`Pi Claude: connector enumeration failed \u2014 ${inventory.reason}`, "error");
|
|
45847
|
+
return;
|
|
45848
|
+
}
|
|
45849
|
+
if (inventory.connectors.length === 0) {
|
|
45850
|
+
ctx2.ui.notify("Pi Claude: this account has no connectors installed.", "info");
|
|
45851
|
+
return;
|
|
45852
|
+
}
|
|
45853
|
+
const names = inventory.connectors.map((c) => c.name).join(", ");
|
|
45854
|
+
ctx2.ui.notify(`Pi Claude: ${inventory.connectors.length} connector(s) installed \u2014 ${names}`, "info");
|
|
45855
|
+
}
|
|
45856
|
+
function registerBridgeCommands(pi) {
|
|
45857
|
+
const guard = pi;
|
|
45858
|
+
if (guard[COMMANDS_REGISTERED_KEY]) return;
|
|
45859
|
+
guard[COMMANDS_REGISTERED_KEY] = true;
|
|
45860
|
+
pi.registerCommand("pi-claude", {
|
|
45861
|
+
description: "Open Pi Claude settings/status",
|
|
45862
|
+
handler: async (args, ctx2) => {
|
|
45863
|
+
if (args.trim()) ctx2.ui.notify("Unknown /pi-claude argument.", "warning");
|
|
45864
|
+
if (await tryOpenExtensionManagerSettings(ctx2)) return;
|
|
45865
|
+
showBridgeStatus(ctx2);
|
|
45866
|
+
}
|
|
45075
45867
|
});
|
|
45076
|
-
|
|
45868
|
+
pi.registerCommand("pi-claude:connectors", {
|
|
45869
|
+
description: "List the Claude account's installed claude.ai connectors",
|
|
45870
|
+
handler: async (_args, ctx2) => reportConnectorInventory(ctx2)
|
|
45077
45871
|
});
|
|
45078
|
-
return true;
|
|
45079
45872
|
}
|
|
45873
|
+
|
|
45874
|
+
// src/consume-query.ts
|
|
45875
|
+
function emitRateLimitEvent(payload) {
|
|
45876
|
+
try {
|
|
45877
|
+
extensionApi?.events?.emit?.(RATE_LIMIT_AUTO_RESUME_EVENT, payload);
|
|
45878
|
+
} catch {
|
|
45879
|
+
}
|
|
45880
|
+
}
|
|
45881
|
+
var lastFastModeDisabledNoticeReason = null;
|
|
45882
|
+
var FAST_MODE_DISABLED_REASON_TEXT = {
|
|
45883
|
+
disabled_by_env: "disabled by an environment variable",
|
|
45884
|
+
extra_usage_disabled: "extra usage is disabled for this account",
|
|
45885
|
+
free: "not available on the free plan",
|
|
45886
|
+
model_not_allowed: "not available for this model",
|
|
45887
|
+
network_error: "the eligibility check hit a network error",
|
|
45888
|
+
not_first_party: "not available for this account type",
|
|
45889
|
+
preference: "disabled by a Claude Code preference",
|
|
45890
|
+
sdk_opt_in_required: "the SDK opt-in is missing",
|
|
45891
|
+
unknown: "unavailable for an unknown reason"
|
|
45892
|
+
};
|
|
45893
|
+
function noteFastModeDisabledReason(message, bridgeConfig) {
|
|
45894
|
+
if (bridgeConfig.provider?.fastMode !== true) return;
|
|
45895
|
+
const reason = message.fast_mode_disabled_reason;
|
|
45896
|
+
if (typeof reason !== "string" || reason === "pending") return;
|
|
45897
|
+
if (reason === lastFastModeDisabledNoticeReason) return;
|
|
45898
|
+
lastFastModeDisabledNoticeReason = reason;
|
|
45899
|
+
const text = FAST_MODE_DISABLED_REASON_TEXT[reason] ?? `unavailable (${reason})`;
|
|
45900
|
+
safeNotify(`Pi Claude: fast mode is enabled in settings but Claude Code declined it \u2014 ${text}.`, "warning");
|
|
45901
|
+
}
|
|
45902
|
+
async function consumeQuery(sdkQuery, queryCtx, customToolNameToPi, model, bridgeConfig, wasAborted, account, router, attemptFailureBox) {
|
|
45903
|
+
let capturedSessionId;
|
|
45904
|
+
let failure;
|
|
45905
|
+
let accountProbe;
|
|
45906
|
+
const holdFailure = (next) => {
|
|
45907
|
+
failure = next;
|
|
45908
|
+
if (attemptFailureBox) attemptFailureBox.failure = next;
|
|
45909
|
+
};
|
|
45910
|
+
for await (const message of sdkQuery) {
|
|
45911
|
+
if (wasAborted()) break;
|
|
45912
|
+
activeStreamIdleWatchdogs.get(queryCtx)?.noteChunk();
|
|
45913
|
+
if (account) {
|
|
45914
|
+
debug("consumeQuery: managed message", () => JSON.stringify({
|
|
45915
|
+
type: message.type,
|
|
45916
|
+
subtype: message.subtype,
|
|
45917
|
+
error: message.error,
|
|
45918
|
+
eventType: message.event?.type,
|
|
45919
|
+
deltaType: message.event?.delta?.type,
|
|
45920
|
+
contentType: message.event?.content_block?.type
|
|
45921
|
+
}));
|
|
45922
|
+
}
|
|
45923
|
+
if (!queryCtx.turnOutput) continue;
|
|
45924
|
+
const streamLive = Boolean(queryCtx.currentPiStream);
|
|
45925
|
+
switch (message.type) {
|
|
45926
|
+
case "stream_event":
|
|
45927
|
+
if (!streamLive) break;
|
|
45928
|
+
processStreamEvent(message, customToolNameToPi, model, queryCtx);
|
|
45929
|
+
break;
|
|
45930
|
+
case "assistant": {
|
|
45931
|
+
const sdkError = message.error;
|
|
45932
|
+
if (sdkError && account) {
|
|
45933
|
+
if (!failure) holdFailure({ kind: classifyClaudeFailure(sdkError), message: String(sdkError) });
|
|
45934
|
+
break;
|
|
45935
|
+
}
|
|
45936
|
+
if (!streamLive && !queryCtx.turnSawToolCall) break;
|
|
45937
|
+
processAssistantMessage(message, model, customToolNameToPi, queryCtx);
|
|
45938
|
+
break;
|
|
45939
|
+
}
|
|
45940
|
+
case "result":
|
|
45941
|
+
if (failure && message.subtype === "success" && queryCtx.committedOutput) {
|
|
45942
|
+
debug(`consumeQuery: clearing informational ${failure.kind ?? "unclassified"} failure \u2014 query recovered with committed output`);
|
|
45943
|
+
holdFailure(void 0);
|
|
45944
|
+
}
|
|
45945
|
+
if (account && failure) break;
|
|
45946
|
+
if (!queryCtx.turnSawStreamEvent && message.subtype === "success") {
|
|
45947
|
+
if (!streamLive) break;
|
|
45948
|
+
const text = message.result || "";
|
|
45949
|
+
if (queryCtx.turnBlocks.some((b) => b.type === "text" && b.text === text)) {
|
|
45950
|
+
debug("consumeQuery: result text already rendered by assistant fallback; skipping duplicate");
|
|
45951
|
+
break;
|
|
45952
|
+
}
|
|
45953
|
+
ensureTurnStarted(queryCtx);
|
|
45954
|
+
queryCtx.turnBlocks.push({ type: "text", text });
|
|
45955
|
+
const idx = queryCtx.turnBlocks.length - 1;
|
|
45956
|
+
queryCtx.currentPiStream?.push({ type: "text_start", contentIndex: idx, partial: queryCtx.turnOutput });
|
|
45957
|
+
queryCtx.currentPiStream?.push({ type: "text_delta", contentIndex: idx, delta: text, partial: queryCtx.turnOutput });
|
|
45958
|
+
queryCtx.currentPiStream?.push({ type: "text_end", contentIndex: idx, content: text, partial: queryCtx.turnOutput });
|
|
45959
|
+
} else if (message.subtype !== "success") {
|
|
45960
|
+
const errorLines = Array.isArray(message.errors) ? uniqueNonEmptyLines(message.errors) : [];
|
|
45961
|
+
const errors = errorLines.length > 0 ? errorLines.join("\n") : String(message.result || message.subtype || "Claude Code request failed");
|
|
45962
|
+
const usageLimit = isUsageLimitMessage(message);
|
|
45963
|
+
if (!failure || !failure.rateLimitInfo) {
|
|
45964
|
+
holdFailure({ kind: usageLimit ? "rate-limit" : classifyClaudeFailure(errors), message: errors });
|
|
45965
|
+
}
|
|
45966
|
+
if (account) break;
|
|
45967
|
+
if (usageLimit) {
|
|
45968
|
+
queryCtx.handledTerminalError = true;
|
|
45969
|
+
queryCtx.turnOutput.stopReason = "error";
|
|
45970
|
+
queryCtx.turnOutput.errorMessage = errors;
|
|
45971
|
+
queryCtx.currentPiStream?.push({ type: "error", reason: "error", error: queryCtx.turnOutput });
|
|
45972
|
+
queryCtx.currentPiStream?.end();
|
|
45973
|
+
queryCtx.currentPiStream = null;
|
|
45974
|
+
}
|
|
45975
|
+
}
|
|
45976
|
+
break;
|
|
45977
|
+
case "system":
|
|
45978
|
+
if (!streamLive) break;
|
|
45979
|
+
if (message.subtype === "init" && message.session_id) {
|
|
45980
|
+
capturedSessionId = message.session_id;
|
|
45981
|
+
queryCtx.childSessionId = capturedSessionId;
|
|
45982
|
+
noteFastModeDisabledReason(message, bridgeConfig);
|
|
45983
|
+
if (account && router && !accountProbe) {
|
|
45984
|
+
accountProbe = Promise.allSettled([
|
|
45985
|
+
sdkQuery.accountInfo().then((info) => router.recordIdentity(account.profileId, {
|
|
45986
|
+
email: info.email,
|
|
45987
|
+
organization: info.organization,
|
|
45988
|
+
subscriptionType: info.subscriptionType
|
|
45989
|
+
})),
|
|
45990
|
+
sdkQuery.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET().then((usage) => router.recordUsage(account.profileId, usage))
|
|
45991
|
+
]).then((results) => {
|
|
45992
|
+
const labels = ["recordIdentity", "recordUsage"];
|
|
45993
|
+
results.forEach((result, i) => {
|
|
45994
|
+
if (result.status === "rejected") debug(`consumeQuery: account probe ${labels[i]} rejected:`, result.reason);
|
|
45995
|
+
});
|
|
45996
|
+
});
|
|
45997
|
+
}
|
|
45998
|
+
} else if (message.subtype === "model_refusal_fallback") {
|
|
45999
|
+
const originalModel = message.original_model;
|
|
46000
|
+
const fallbackModel = message.fallback_model;
|
|
46001
|
+
updateTurnOutputModel(fallbackModel, queryCtx);
|
|
46002
|
+
debug("consumeQuery: model_refusal_fallback", JSON.stringify({ originalModel, fallbackModel }));
|
|
46003
|
+
if (typeof fallbackModel === "string" && typeof originalModel === "string" && fallbackModelForPrimaryModel(originalModel) === fallbackModel) {
|
|
46004
|
+
safeNotify(
|
|
46005
|
+
`Pi Claude switched ${modelDisplayName(originalModel)} to ${modelDisplayName(fallbackModel)} after Claude Code safety fallback.`,
|
|
46006
|
+
"info"
|
|
46007
|
+
);
|
|
46008
|
+
}
|
|
46009
|
+
}
|
|
46010
|
+
break;
|
|
46011
|
+
case "user":
|
|
46012
|
+
noteChildExecutedToolResults(message, queryCtx);
|
|
46013
|
+
break;
|
|
46014
|
+
case "rate_limit_event": {
|
|
46015
|
+
if (!streamLive) break;
|
|
46016
|
+
const info = message.rate_limit_info;
|
|
46017
|
+
debug("consumeQuery: rate_limit_event", JSON.stringify(info).slice(0, 300));
|
|
46018
|
+
if (info?.status === "rejected") {
|
|
46019
|
+
const rateLimitType = rateLimitTypeFromInfo(info);
|
|
46020
|
+
const resetAt = rateLimitResetFromInfo(info);
|
|
46021
|
+
const resetAtMs = rateLimitResetMs(info);
|
|
46022
|
+
const reason = `${rateLimitType ?? "unknown"} rate limit`;
|
|
46023
|
+
if (account && router) {
|
|
46024
|
+
holdFailure({ kind: "rate-limit", message: reason, rateLimitInfo: info });
|
|
46025
|
+
safeRouterCall("recordRateLimit", () => router.recordRateLimit(account.profileId, info, model.id));
|
|
46026
|
+
} else {
|
|
46027
|
+
const resetsAt = formatResetTimestamp(resetAtMs ?? resetAt);
|
|
46028
|
+
emitRateLimitEvent({
|
|
46029
|
+
model: model.id,
|
|
46030
|
+
provider: model.provider,
|
|
46031
|
+
rateLimitType,
|
|
46032
|
+
reason,
|
|
46033
|
+
resetAt,
|
|
46034
|
+
...Number.isFinite(resetAtMs) ? { resetAtMs } : {},
|
|
46035
|
+
source: "claude-bridge",
|
|
46036
|
+
status: "rejected"
|
|
46037
|
+
});
|
|
46038
|
+
safeNotify(`${RATE_LIMIT_TOKEN} Claude ${reason} hit \u2014 resets ${resetsAt}`, "warning");
|
|
46039
|
+
}
|
|
46040
|
+
} else if (info?.status === "allowed_warning") {
|
|
46041
|
+
const warning = formatAllowedRateLimitWarning(info);
|
|
46042
|
+
if (warning) safeNotify(warning, "warning");
|
|
46043
|
+
else debug("consumeQuery: suppressed low/ambiguous allowed_warning rate_limit_event", JSON.stringify(info).slice(0, 300));
|
|
46044
|
+
}
|
|
46045
|
+
break;
|
|
46046
|
+
}
|
|
46047
|
+
default:
|
|
46048
|
+
debug("consumeQuery: unhandled SDK message type", message.type);
|
|
46049
|
+
break;
|
|
46050
|
+
}
|
|
46051
|
+
}
|
|
46052
|
+
if (accountProbe) {
|
|
46053
|
+
await Promise.race([
|
|
46054
|
+
accountProbe,
|
|
46055
|
+
new Promise((resolve5) => setTimeout(resolve5, 1500))
|
|
46056
|
+
]);
|
|
46057
|
+
}
|
|
46058
|
+
debug(`consumeQuery: for-await loop exited, wasAborted=${wasAborted()}, capturedSessionId=${capturedSessionId?.slice(0, 8) ?? "none"}, failure=${failure?.kind ?? "none"}`);
|
|
46059
|
+
return { capturedSessionId, failure };
|
|
46060
|
+
}
|
|
46061
|
+
|
|
46062
|
+
// src/agents-md.ts
|
|
46063
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
46064
|
+
import { dirname as dirname5, join as join9, resolve as resolve3 } from "path";
|
|
46065
|
+
var CONTEXT_FILE_CANDIDATES = ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD"];
|
|
46066
|
+
function contextFileInDir(dir) {
|
|
46067
|
+
for (const filename of CONTEXT_FILE_CANDIDATES) {
|
|
46068
|
+
const candidate = join9(dir, filename);
|
|
46069
|
+
try {
|
|
46070
|
+
if (statSync5(candidate).isFile()) return candidate;
|
|
46071
|
+
} catch (error51) {
|
|
46072
|
+
let lstatCode;
|
|
46073
|
+
let entryExists = false;
|
|
46074
|
+
try {
|
|
46075
|
+
lstatSync2(candidate);
|
|
46076
|
+
entryExists = true;
|
|
46077
|
+
} catch (lstatError) {
|
|
46078
|
+
lstatCode = lstatError.code;
|
|
46079
|
+
}
|
|
46080
|
+
if (entryExists || lstatCode !== "ENOENT") {
|
|
46081
|
+
const detail = error51.code ?? String(error51);
|
|
46082
|
+
const suffix = entryExists ? "" : ` (lstat: ${lstatCode ?? "unknown"})`;
|
|
46083
|
+
debug(`agents-md: skipping unusable ${candidate}: ${detail}${suffix}`);
|
|
46084
|
+
}
|
|
46085
|
+
}
|
|
46086
|
+
}
|
|
46087
|
+
return void 0;
|
|
46088
|
+
}
|
|
46089
|
+
function resolveAgentsMdPath() {
|
|
46090
|
+
if (isolatedFromEnv()) return void 0;
|
|
46091
|
+
const fromCwd = findAgentsMdInParents(process.cwd());
|
|
46092
|
+
if (fromCwd) return fromCwd;
|
|
46093
|
+
return contextFileInDir(piUserDir());
|
|
46094
|
+
}
|
|
46095
|
+
function findAgentsMdInParents(startDir) {
|
|
46096
|
+
let current = resolve3(startDir);
|
|
46097
|
+
while (true) {
|
|
46098
|
+
const candidate = contextFileInDir(current);
|
|
46099
|
+
if (candidate) return candidate;
|
|
46100
|
+
const parent = dirname5(current);
|
|
46101
|
+
if (parent === current) break;
|
|
46102
|
+
current = parent;
|
|
46103
|
+
}
|
|
46104
|
+
return void 0;
|
|
46105
|
+
}
|
|
46106
|
+
function extractAgentsAppend() {
|
|
46107
|
+
const agentsPath = resolveAgentsMdPath();
|
|
46108
|
+
if (!agentsPath) return void 0;
|
|
46109
|
+
try {
|
|
46110
|
+
const content = readFileSync7(agentsPath, "utf-8").trim();
|
|
46111
|
+
if (!content) return void 0;
|
|
46112
|
+
const sanitized = sanitizeAgentsContent(content);
|
|
46113
|
+
return sanitized.length > 0 ? `# CLAUDE.md
|
|
46114
|
+
|
|
46115
|
+
${sanitized}` : void 0;
|
|
46116
|
+
} catch (error51) {
|
|
46117
|
+
debug(`agents-md: failed to read ${agentsPath}:`, error51 instanceof Error ? error51.message : String(error51));
|
|
46118
|
+
return void 0;
|
|
46119
|
+
}
|
|
46120
|
+
}
|
|
46121
|
+
function sanitizeAgentsContent(content) {
|
|
46122
|
+
let sanitized = content;
|
|
46123
|
+
sanitized = sanitized.replace(/~\/\.pi\b/gi, "~/.claude");
|
|
46124
|
+
sanitized = sanitized.replace(/(^|[\s'"`])\.pi\//g, "$1.claude/");
|
|
46125
|
+
sanitized = sanitized.replace(/\b\.pi\b/gi, ".claude");
|
|
46126
|
+
sanitized = sanitized.replace(/\bpi\b/gi, "environment");
|
|
46127
|
+
return sanitized;
|
|
46128
|
+
}
|
|
46129
|
+
|
|
46130
|
+
// src/prompt-context.ts
|
|
46131
|
+
import { existsSync as existsSync5, readFileSync as readFileSync8 } from "fs";
|
|
46132
|
+
import { dirname as dirname6, join as join10, resolve as resolve4 } from "path";
|
|
46133
|
+
function readTrimmed(path) {
|
|
46134
|
+
try {
|
|
46135
|
+
if (!existsSync5(path)) return void 0;
|
|
46136
|
+
const content = readFileSync8(path, "utf8").trim();
|
|
46137
|
+
return content.length > 0 ? content : void 0;
|
|
46138
|
+
} catch (error51) {
|
|
46139
|
+
debug(`prompt-context: failed to read ${path}:`, error51 instanceof Error ? error51.message : String(error51));
|
|
46140
|
+
return void 0;
|
|
46141
|
+
}
|
|
46142
|
+
}
|
|
46143
|
+
function findProjectAppendSystem(startDir) {
|
|
46144
|
+
let current = resolve4(startDir);
|
|
46145
|
+
while (true) {
|
|
46146
|
+
const candidate = join10(current, ".pi", "APPEND_SYSTEM.md");
|
|
46147
|
+
if (existsSync5(candidate)) return candidate;
|
|
46148
|
+
const parent = dirname6(current);
|
|
46149
|
+
if (parent === current) break;
|
|
46150
|
+
current = parent;
|
|
46151
|
+
}
|
|
46152
|
+
return void 0;
|
|
46153
|
+
}
|
|
46154
|
+
function readAppendSystemPromptFiles(cwd) {
|
|
46155
|
+
const files = [
|
|
46156
|
+
{ label: "global APPEND_SYSTEM.md", path: join10(piUserDir(), "APPEND_SYSTEM.md") }
|
|
46157
|
+
];
|
|
46158
|
+
const projectPath = isolatedFromEnv() ? void 0 : findProjectAppendSystem(cwd);
|
|
46159
|
+
if (projectPath) files.push({ label: "project .pi/APPEND_SYSTEM.md", path: projectPath });
|
|
46160
|
+
const seen = /* @__PURE__ */ new Set();
|
|
46161
|
+
const output = [];
|
|
46162
|
+
for (const file2 of files) {
|
|
46163
|
+
if (seen.has(file2.path)) continue;
|
|
46164
|
+
seen.add(file2.path);
|
|
46165
|
+
const content = readTrimmed(file2.path);
|
|
46166
|
+
if (content) output.push({ label: file2.label, content });
|
|
46167
|
+
}
|
|
46168
|
+
return output;
|
|
46169
|
+
}
|
|
46170
|
+
function splitPromptBlocks(systemPrompt) {
|
|
46171
|
+
return (systemPrompt ?? "").split(/\n{2,}/).map((block) => block.trim()).filter(Boolean);
|
|
46172
|
+
}
|
|
46173
|
+
function extractHeadingSection(systemPrompt, headings) {
|
|
46174
|
+
if (!systemPrompt) return void 0;
|
|
46175
|
+
let start = -1;
|
|
46176
|
+
for (const heading of headings) {
|
|
46177
|
+
const index = systemPrompt.indexOf(heading);
|
|
46178
|
+
if (index >= 0 && (start < 0 || index < start)) start = index;
|
|
46179
|
+
}
|
|
46180
|
+
if (start < 0) return void 0;
|
|
46181
|
+
const rest = systemPrompt.slice(start).trim();
|
|
46182
|
+
const endCandidates = [
|
|
46183
|
+
rest.slice(1).search(/\n##\s+/),
|
|
46184
|
+
rest.search(/\n<\/project_instructions>/),
|
|
46185
|
+
rest.search(/\n<\/project_context>/)
|
|
46186
|
+
].map((index, offset) => index >= 0 && offset === 0 ? index + 1 : index).filter((index) => index >= 0);
|
|
46187
|
+
const end = endCandidates.length > 0 ? Math.min(...endCandidates) : -1;
|
|
46188
|
+
return (end >= 0 ? rest.slice(0, end) : rest).trim();
|
|
46189
|
+
}
|
|
46190
|
+
function extractBlockByMarkers(systemPrompt, markers) {
|
|
46191
|
+
for (const block of splitPromptBlocks(systemPrompt)) {
|
|
46192
|
+
if (markers.some((marker) => marker.test(block))) return block;
|
|
46193
|
+
}
|
|
46194
|
+
return void 0;
|
|
46195
|
+
}
|
|
46196
|
+
function buildPromptContextAppend(systemPrompt, cwd, settings) {
|
|
46197
|
+
const parts = [];
|
|
46198
|
+
const labels = [];
|
|
46199
|
+
if (settings.includeAppendSystemPromptMd) {
|
|
46200
|
+
for (const file2 of readAppendSystemPromptFiles(cwd)) {
|
|
46201
|
+
parts.push(xmlBlock("append_system_prompt", { label: file2.label }, file2.content));
|
|
46202
|
+
labels.push(file2.label);
|
|
46203
|
+
}
|
|
46204
|
+
}
|
|
46205
|
+
if (settings.includeProjectAgentsHook) {
|
|
46206
|
+
const projectAgents = extractHeadingSection(systemPrompt, ["## Project Agents", "## Project Subagents"]);
|
|
46207
|
+
if (projectAgents) {
|
|
46208
|
+
parts.push(xmlBlock("before_agent_start", { source: "project-agents" }, projectAgents));
|
|
46209
|
+
labels.push("project agents hook");
|
|
46210
|
+
}
|
|
46211
|
+
}
|
|
46212
|
+
if (settings.includeTaskPanelHook) {
|
|
46213
|
+
const taskReminder = extractBlockByMarkers(systemPrompt, [/^Task workflow reminder:/]);
|
|
46214
|
+
if (taskReminder) {
|
|
46215
|
+
parts.push(xmlBlock("before_agent_start", { source: "task-panel" }, taskReminder));
|
|
46216
|
+
labels.push("task panel hook");
|
|
46217
|
+
}
|
|
46218
|
+
}
|
|
46219
|
+
if (settings.includeCavemanHook) {
|
|
46220
|
+
const caveman = extractBlockByMarkers(systemPrompt, [/^You MUST respond in caveman /m]);
|
|
46221
|
+
if (caveman) {
|
|
46222
|
+
parts.push(xmlBlock("before_agent_start", { source: "caveman" }, caveman));
|
|
46223
|
+
labels.push("caveman hook");
|
|
46224
|
+
}
|
|
46225
|
+
}
|
|
46226
|
+
if (parts.length === 0) return { labels };
|
|
46227
|
+
return {
|
|
46228
|
+
labels,
|
|
46229
|
+
text: xmlBlock(
|
|
46230
|
+
"forwarded_pi_context",
|
|
46231
|
+
{},
|
|
46232
|
+
[
|
|
46233
|
+
"The following content was explicitly enabled in pi-claude-bridge settings and comes from Pi prompt files or before_agent_start prompt hooks.",
|
|
46234
|
+
...parts
|
|
46235
|
+
].join("\n\n"),
|
|
46236
|
+
false
|
|
46237
|
+
)
|
|
46238
|
+
};
|
|
46239
|
+
}
|
|
46240
|
+
function escapeXmlAttr(value) {
|
|
46241
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
46242
|
+
}
|
|
46243
|
+
function escapeXmlText(value) {
|
|
46244
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
46245
|
+
}
|
|
46246
|
+
function xmlBlock(tag, attrs, content, escapeContent = true) {
|
|
46247
|
+
const attrText = Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeXmlAttr(value)}"`).join("");
|
|
46248
|
+
const body = escapeContent ? escapeXmlText(content.trim()) : content.trim();
|
|
46249
|
+
return `<${tag}${attrText}>
|
|
46250
|
+
${body}
|
|
46251
|
+
</${tag}>`;
|
|
46252
|
+
}
|
|
46253
|
+
|
|
46254
|
+
// src/query-options.ts
|
|
46255
|
+
var REASONING_TO_EFFORT = {
|
|
46256
|
+
minimal: "low",
|
|
46257
|
+
low: "low",
|
|
46258
|
+
medium: "medium",
|
|
46259
|
+
high: "high",
|
|
46260
|
+
xhigh: "max",
|
|
46261
|
+
max: "max"
|
|
46262
|
+
};
|
|
46263
|
+
function normalizeEffortOverrideModelKey(value) {
|
|
46264
|
+
const key = value.trim().toLowerCase();
|
|
46265
|
+
return key.startsWith(`${PROVIDER_ID}/`) ? key.slice(PROVIDER_ID.length + 1) : key;
|
|
46266
|
+
}
|
|
46267
|
+
function resolveConfiguredEffort(modelId, reasoningEffort, providerConfig) {
|
|
46268
|
+
const target = normalizeEffortOverrideModelKey(modelId);
|
|
46269
|
+
for (const [key, rawEffort] of Object.entries(providerConfig?.modelEffortOverrides ?? {})) {
|
|
46270
|
+
const normalizedKey = normalizeEffortOverrideModelKey(key);
|
|
46271
|
+
if (normalizedKey !== "*" && normalizedKey !== target) continue;
|
|
46272
|
+
const effort = normalizeEffortLevel(rawEffort);
|
|
46273
|
+
if (effort) return effort;
|
|
46274
|
+
}
|
|
46275
|
+
return normalizeEffortLevel(providerConfig?.forceEffort) ?? reasoningEffort;
|
|
46276
|
+
}
|
|
46277
|
+
function buildClaudeQueryOptions(input) {
|
|
46278
|
+
const { cwd, requestedModel, queryModel, account, bridgeConfig, systemPrompt, reasoning, resumeSessionId, mcpServers, claudeExecutable } = input;
|
|
46279
|
+
const providerSettings = bridgeConfig.provider ?? {};
|
|
46280
|
+
const accountScope = accountSessionScope(account);
|
|
46281
|
+
const enableCloudMcp = connectorsEnabledFor(bridgeConfig);
|
|
46282
|
+
const connectorWriteMode = connectorWriteModeFor(bridgeConfig);
|
|
46283
|
+
const connectorServers = enableCloudMcp ? connectorServersSnapshot(accountScope.claudeConfigDir) : {};
|
|
46284
|
+
const appendSystemPrompt = providerSettings.appendSystemPrompt !== false;
|
|
46285
|
+
const agentsAppend = appendSystemPrompt ? extractAgentsAppend() : void 0;
|
|
46286
|
+
const skillsAppend = appendSystemPrompt ? extractSkillsBlock(systemPrompt) : void 0;
|
|
46287
|
+
const promptContextAppend = buildPromptContextAppend(systemPrompt, cwd, bridgeConfig.promptContext ?? {});
|
|
46288
|
+
const appendParts = [agentsAppend, skillsAppend, promptContextAppend.text].filter((part) => Boolean(part));
|
|
46289
|
+
const systemPromptAppend = appendParts.length > 0 ? appendParts.join("\n\n") : void 0;
|
|
46290
|
+
const settingSources = settingSourcesForQuery(
|
|
46291
|
+
enableCloudMcp,
|
|
46292
|
+
appendSystemPrompt,
|
|
46293
|
+
providerSettings.settingSources
|
|
46294
|
+
);
|
|
46295
|
+
const strictMcpConfigEnabled = !appendSystemPrompt && providerSettings.strictMcpConfig !== false;
|
|
46296
|
+
const requestedEffort = reasoning ? queryModel.thinkingLevelMap?.[reasoning] ?? REASONING_TO_EFFORT[reasoning] : void 0;
|
|
46297
|
+
const effort = resolveConfiguredEffort(queryModel.id, requestedEffort, providerSettings);
|
|
46298
|
+
const extraArgs = {};
|
|
46299
|
+
if (effort) extraArgs["thinking-display"] = "summarized";
|
|
46300
|
+
const fallbackModel = account && requestedModel.id === FABLE_MODEL_ID && queryModel.id === requestedModel.id ? void 0 : fallbackModelForPrimaryModel(queryModel.id);
|
|
46301
|
+
const childEnv = {
|
|
46302
|
+
...account ? subscriberProfileEnv(account) : process.env,
|
|
46303
|
+
ENABLE_CLAUDEAI_MCP_SERVERS: enableCloudMcp ? "1" : "0",
|
|
46304
|
+
DISABLE_AUTO_COMPACT: "1"
|
|
46305
|
+
};
|
|
46306
|
+
const queryOptions = {
|
|
46307
|
+
cwd,
|
|
46308
|
+
model: queryModel.id,
|
|
46309
|
+
env: childEnv,
|
|
46310
|
+
...connectorQueryOptions(enableCloudMcp, connectorWriteMode),
|
|
46311
|
+
permissionMode: "bypassPermissions",
|
|
46312
|
+
includePartialMessages: true,
|
|
46313
|
+
...fallbackModel ? { fallbackModel } : {},
|
|
46314
|
+
...providerSettings.fastMode ? { settings: { fastMode: true } } : {},
|
|
46315
|
+
systemPrompt: {
|
|
46316
|
+
type: "preset",
|
|
46317
|
+
preset: "claude_code",
|
|
46318
|
+
append: systemPromptAppend ? systemPromptAppend : void 0
|
|
46319
|
+
},
|
|
46320
|
+
extraArgs,
|
|
46321
|
+
...strictMcpConfigEnabled ? { strictMcpConfig: true } : {},
|
|
46322
|
+
...effort ? { effort } : {},
|
|
46323
|
+
...settingSources ? { settingSources } : {},
|
|
46324
|
+
...mcpServers || Object.keys(connectorServers).length > 0 ? { mcpServers: { ...mcpServers ?? {}, ...connectorServers } } : {},
|
|
46325
|
+
...resumeSessionId ? { resume: resumeSessionId } : {},
|
|
46326
|
+
...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
|
|
46327
|
+
spawnClaudeCodeProcess: spawnClaudeCodeWithDiagnostics,
|
|
46328
|
+
...makeCliDebugOptions("provider")
|
|
46329
|
+
};
|
|
46330
|
+
return {
|
|
46331
|
+
queryOptions,
|
|
46332
|
+
enableCloudMcp,
|
|
46333
|
+
appendSystemPrompt,
|
|
46334
|
+
promptContextLabels: promptContextAppend.labels,
|
|
46335
|
+
strictMcpConfigEnabled,
|
|
46336
|
+
...effort ? { effort } : {},
|
|
46337
|
+
...fallbackModel ? { fallbackModel } : {}
|
|
46338
|
+
};
|
|
46339
|
+
}
|
|
46340
|
+
|
|
46341
|
+
// src/index.ts
|
|
46342
|
+
var _piAi = piAi;
|
|
46343
|
+
var getModels = await resolveGetModels(_piAi);
|
|
46344
|
+
var newAssistantMessageEventStream = typeof _piAi.createAssistantMessageEventStream === "function" ? _piAi.createAssistantMessageEventStream : () => new _piAi.AssistantMessageEventStream();
|
|
46345
|
+
var PRIMARY_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:primaryInstance");
|
|
46346
|
+
var ACTIVE_STREAM_SIMPLE_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:activeStreamSimple");
|
|
46347
|
+
var ROTATION_STATE_KEY = /* @__PURE__ */ Symbol("claude-bridge:rotationState");
|
|
46348
|
+
var MAX_ROTATION_ATTEMPTS = 16;
|
|
46349
|
+
var MODELS = buildModels(getModels("anthropic"));
|
|
45080
46350
|
function extractAllToolResults2(context) {
|
|
45081
46351
|
const { results, stopIdx } = extractAllToolResults(context.messages);
|
|
45082
46352
|
debug(`extractAllToolResults: ${results.length} results from ${context.messages.length} msgs, stopped at index ${stopIdx}`);
|
|
@@ -45087,43 +46357,59 @@ function extractAllToolResults2(context) {
|
|
|
45087
46357
|
return results;
|
|
45088
46358
|
}
|
|
45089
46359
|
function extractUserPrompt(messages) {
|
|
45090
|
-
|
|
45091
|
-
|
|
45092
|
-
|
|
45093
|
-
|
|
46360
|
+
if (messages.length === 0 || messages.some((message) => message.role !== "user")) return null;
|
|
46361
|
+
return messages.map(
|
|
46362
|
+
(message) => typeof message.content === "string" ? message.content : messageContentToText(message.content) || ""
|
|
46363
|
+
).join("\n\n");
|
|
45094
46364
|
}
|
|
45095
46365
|
function extractUserPromptBlocks(messages) {
|
|
45096
|
-
|
|
45097
|
-
if (!last || last.role !== "user") return null;
|
|
45098
|
-
if (typeof last.content === "string") {
|
|
45099
|
-
debug(`extractUserPromptBlocks: content is string (length=${last.content.length})`);
|
|
45100
|
-
return null;
|
|
45101
|
-
}
|
|
45102
|
-
if (!Array.isArray(last.content)) {
|
|
45103
|
-
debug(`extractUserPromptBlocks: content is ${typeof last.content}`);
|
|
45104
|
-
return null;
|
|
45105
|
-
}
|
|
45106
|
-
debug(`extractUserPromptBlocks: ${last.content.length} blocks, types=${last.content.map((b) => b.type).join(",")}`);
|
|
46366
|
+
if (messages.length === 0 || messages.some((message) => message.role !== "user")) return null;
|
|
45107
46367
|
let hasImage = false;
|
|
45108
46368
|
const blocks = [];
|
|
45109
|
-
for (
|
|
45110
|
-
|
|
45111
|
-
|
|
45112
|
-
|
|
45113
|
-
|
|
45114
|
-
|
|
45115
|
-
|
|
45116
|
-
|
|
46369
|
+
for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
|
|
46370
|
+
const content = messages[messageIndex].content;
|
|
46371
|
+
if (messageIndex > 0) blocks.push({ type: "text", text: "\n\n" });
|
|
46372
|
+
if (typeof content === "string") {
|
|
46373
|
+
if (content) blocks.push({ type: "text", text: content });
|
|
46374
|
+
continue;
|
|
46375
|
+
}
|
|
46376
|
+
if (!Array.isArray(content)) {
|
|
46377
|
+
debug(`extractUserPromptBlocks: content is ${typeof content}`);
|
|
46378
|
+
continue;
|
|
46379
|
+
}
|
|
46380
|
+
debug(`extractUserPromptBlocks: ${content.length} blocks, types=${content.map((b) => b.type).join(",")}`);
|
|
46381
|
+
for (const block of content) {
|
|
46382
|
+
if (block.type === "text" && block.text) {
|
|
46383
|
+
blocks.push({ type: "text", text: block.text });
|
|
46384
|
+
} else if (block.type === "image") {
|
|
46385
|
+
debug(`image block: mimeType=${block.mimeType}, data length=${(block.data ?? "").length}, keys=${Object.keys(block).join(",")}`);
|
|
46386
|
+
if (!block.data || !block.mimeType) {
|
|
46387
|
+
debug(`image block missing data or mimeType, skipping`);
|
|
46388
|
+
continue;
|
|
46389
|
+
}
|
|
46390
|
+
hasImage = true;
|
|
46391
|
+
blocks.push({
|
|
46392
|
+
type: "image",
|
|
46393
|
+
source: { type: "base64", media_type: block.mimeType, data: block.data }
|
|
46394
|
+
});
|
|
45117
46395
|
}
|
|
45118
|
-
hasImage = true;
|
|
45119
|
-
blocks.push({
|
|
45120
|
-
type: "image",
|
|
45121
|
-
source: { type: "base64", media_type: block.mimeType, data: block.data }
|
|
45122
|
-
});
|
|
45123
46396
|
}
|
|
45124
46397
|
}
|
|
45125
46398
|
return hasImage ? blocks : null;
|
|
45126
46399
|
}
|
|
46400
|
+
function planDeferredUserReplay(messages, capturedThrough = 0) {
|
|
46401
|
+
let runStart = messages.length;
|
|
46402
|
+
while (runStart > capturedThrough && messages[runStart - 1]?.role === "user") runStart--;
|
|
46403
|
+
const trailingUsers = messages.slice(runStart);
|
|
46404
|
+
const prompt = trailingUsers.length > 0 ? extractUserPrompt(trailingUsers) : null;
|
|
46405
|
+
const blocks = trailingUsers.length > 0 ? extractUserPromptBlocks(trailingUsers) : null;
|
|
46406
|
+
return {
|
|
46407
|
+
runStart,
|
|
46408
|
+
userMessageCount: trailingUsers.length,
|
|
46409
|
+
prompt: prompt?.trim() ? prompt : null,
|
|
46410
|
+
blocks
|
|
46411
|
+
};
|
|
46412
|
+
}
|
|
45127
46413
|
async function* wrapPromptStream(blocks) {
|
|
45128
46414
|
yield {
|
|
45129
46415
|
type: "user",
|
|
@@ -45183,162 +46469,44 @@ function buildMcpServers(tools, queryCtx) {
|
|
|
45183
46469
|
});
|
|
45184
46470
|
return { content: [{ type: "text", text: `Claude bridge internal error: no matching tool_call id for ${tool.name}` }], isError: true };
|
|
45185
46471
|
}
|
|
45186
|
-
if (claim.argsMismatch) {
|
|
45187
|
-
debug(`mcp handler: ${tool.name} [${toolCallId}] claimed sole same-name call despite args mismatch`);
|
|
45188
|
-
diagDump("tool_claim_args_mismatch", {
|
|
45189
|
-
toolName: tool.name,
|
|
45190
|
-
toolCallId,
|
|
45191
|
-
handlerArgKeys: argKeys(mappedArgs),
|
|
45192
|
-
recordedArgKeys: argKeys(queryCtx.turnToolCalls.find((call) => call.id === toolCallId)?.arguments)
|
|
45193
|
-
});
|
|
45194
|
-
} else if (claim.match !== "tool-args" || claim.ambiguous) {
|
|
45195
|
-
debug(`mcp handler: ${tool.name} [${toolCallId}] claimed by ${claim.match}${claim.ambiguous ? " (ambiguous)" : ""}`);
|
|
45196
|
-
}
|
|
45197
|
-
|
|
45198
|
-
|
|
45199
|
-
queryCtx.
|
|
45200
|
-
queryCtx.
|
|
45201
|
-
|
|
45202
|
-
|
|
45203
|
-
}
|
|
45204
|
-
|
|
45205
|
-
|
|
45206
|
-
queryCtx,
|
|
45207
|
-
|
|
45208
|
-
|
|
45209
|
-
)
|
|
45210
|
-
|
|
45211
|
-
|
|
45212
|
-
|
|
45213
|
-
|
|
45214
|
-
|
|
45215
|
-
|
|
45216
|
-
|
|
45217
|
-
|
|
45218
|
-
|
|
45219
|
-
|
|
45220
|
-
}));
|
|
45221
|
-
const server = f0e({ name: MCP_SERVER_NAME, version: "1.0.0", tools: mcpTools });
|
|
45222
|
-
return { [MCP_SERVER_NAME]: server };
|
|
45223
|
-
}
|
|
45224
|
-
var REASONING_TO_EFFORT = {
|
|
45225
|
-
minimal: "low",
|
|
45226
|
-
low: "low",
|
|
45227
|
-
medium: "medium",
|
|
45228
|
-
high: "high",
|
|
45229
|
-
xhigh: "max",
|
|
45230
|
-
max: "max"
|
|
45231
|
-
};
|
|
45232
|
-
function normalizeEffortOverrideModelKey(value) {
|
|
45233
|
-
const key = value.trim().toLowerCase();
|
|
45234
|
-
return key.startsWith(`${PROVIDER_ID}/`) ? key.slice(PROVIDER_ID.length + 1) : key;
|
|
45235
|
-
}
|
|
45236
|
-
function resolveConfiguredEffort(modelId, reasoningEffort, providerConfig) {
|
|
45237
|
-
const target = normalizeEffortOverrideModelKey(modelId);
|
|
45238
|
-
for (const [key, rawEffort] of Object.entries(providerConfig?.modelEffortOverrides ?? {})) {
|
|
45239
|
-
const normalizedKey = normalizeEffortOverrideModelKey(key);
|
|
45240
|
-
if (normalizedKey !== "*" && normalizedKey !== target) continue;
|
|
45241
|
-
const effort = normalizeEffortLevel(rawEffort);
|
|
45242
|
-
if (effort) return effort;
|
|
45243
|
-
}
|
|
45244
|
-
return normalizeEffortLevel(providerConfig?.forceEffort) ?? reasoningEffort;
|
|
45245
|
-
}
|
|
45246
|
-
async function consumeQuery(sdkQuery, customToolNameToPi, model, cwd, bridgeConfig, wasAborted) {
|
|
45247
|
-
let capturedSessionId;
|
|
45248
|
-
for await (const message of sdkQuery) {
|
|
45249
|
-
if (wasAborted()) break;
|
|
45250
|
-
const queryCtx = ctx();
|
|
45251
|
-
activeStreamIdleWatchdogs.get(queryCtx)?.noteChunk();
|
|
45252
|
-
if (!queryCtx.turnOutput) continue;
|
|
45253
|
-
if (!queryCtx.currentPiStream && !(message.type === "assistant" && queryCtx.turnSawToolCall)) continue;
|
|
45254
|
-
switch (message.type) {
|
|
45255
|
-
case "stream_event":
|
|
45256
|
-
processStreamEvent(message, customToolNameToPi, model);
|
|
45257
|
-
break;
|
|
45258
|
-
case "assistant":
|
|
45259
|
-
processAssistantMessage(message, model, customToolNameToPi);
|
|
45260
|
-
break;
|
|
45261
|
-
case "result":
|
|
45262
|
-
if (!ctx().turnSawStreamEvent && message.subtype === "success") {
|
|
45263
|
-
const text = message.result || "";
|
|
45264
|
-
if (ctx().turnBlocks.some((b) => b.type === "text" && b.text === text)) {
|
|
45265
|
-
debug("consumeQuery: result text already rendered by assistant fallback; skipping duplicate");
|
|
45266
|
-
break;
|
|
45267
|
-
}
|
|
45268
|
-
ensureTurnStarted();
|
|
45269
|
-
ctx().turnBlocks.push({ type: "text", text });
|
|
45270
|
-
const idx = ctx().turnBlocks.length - 1;
|
|
45271
|
-
ctx().currentPiStream?.push({ type: "text_start", contentIndex: idx, partial: ctx().turnOutput });
|
|
45272
|
-
ctx().currentPiStream?.push({ type: "text_delta", contentIndex: idx, delta: text, partial: ctx().turnOutput });
|
|
45273
|
-
ctx().currentPiStream?.push({ type: "text_end", contentIndex: idx, content: text, partial: ctx().turnOutput });
|
|
45274
|
-
} else if (message.subtype !== "success" && (isExtraUsageRequiredMessage(message) || isUsageLimitMessage(message))) {
|
|
45275
|
-
const errorLines = Array.isArray(message.errors) ? uniqueNonEmptyLines(message.errors) : [];
|
|
45276
|
-
const errors = errorLines.length > 0 ? errorLines.join("\n") : String(message.subtype ?? "Claude Code rate limit");
|
|
45277
|
-
const extraUsage = isExtraUsageRequiredMessage(message);
|
|
45278
|
-
const openedExtraUsage = extraUsage && launchExtraUsageHelperIfAllowed(cwd, bridgeConfig, "result error");
|
|
45279
|
-
ctx().handledTerminalError = true;
|
|
45280
|
-
ctx().turnOutput.stopReason = "error";
|
|
45281
|
-
const extraUsageHint = openedExtraUsage ? "\n\nOpened Claude Code /extra-usage helper. Complete billing/admin flow in the browser, then retry the prompt." : extraUsage ? "\n\nRun /claude-bridge:extra, or enable Allow extra usage helper in settings." : "";
|
|
45282
|
-
ctx().turnOutput.errorMessage = `${errors}${extraUsageHint}`;
|
|
45283
|
-
ctx().currentPiStream?.push({ type: "error", reason: "error", error: ctx().turnOutput });
|
|
45284
|
-
ctx().currentPiStream?.end();
|
|
45285
|
-
ctx().currentPiStream = null;
|
|
45286
|
-
}
|
|
45287
|
-
break;
|
|
45288
|
-
case "system":
|
|
45289
|
-
if (message.subtype === "init" && message.session_id) {
|
|
45290
|
-
capturedSessionId = message.session_id;
|
|
45291
|
-
queryCtx.childSessionId = capturedSessionId;
|
|
45292
|
-
noteFastModeDisabledReason(message, bridgeConfig);
|
|
45293
|
-
} else if (message.subtype === "model_refusal_fallback") {
|
|
45294
|
-
const originalModel = message.original_model;
|
|
45295
|
-
const fallbackModel = message.fallback_model;
|
|
45296
|
-
updateTurnOutputModel(fallbackModel);
|
|
45297
|
-
debug("consumeQuery: model_refusal_fallback", JSON.stringify({ originalModel, fallbackModel }));
|
|
45298
|
-
if (typeof fallbackModel === "string" && typeof originalModel === "string" && fallbackModelForPrimaryModel(originalModel) === fallbackModel) {
|
|
45299
|
-
safeNotify(
|
|
45300
|
-
`Claude bridge switched ${modelDisplayName(originalModel)} to ${modelDisplayName(fallbackModel)} after Claude Code safety fallback.`,
|
|
45301
|
-
"info"
|
|
45302
|
-
);
|
|
45303
|
-
}
|
|
45304
|
-
}
|
|
45305
|
-
break;
|
|
45306
|
-
case "user":
|
|
45307
|
-
noteChildExecutedToolResults(message);
|
|
45308
|
-
break;
|
|
45309
|
-
case "rate_limit_event": {
|
|
45310
|
-
const info = message.rate_limit_info;
|
|
45311
|
-
debug("consumeQuery: rate_limit_event", JSON.stringify(info).slice(0, 300));
|
|
45312
|
-
if (info?.status === "rejected") {
|
|
45313
|
-
const resetsAt = formatResetTimestamp(info.resetsAt);
|
|
45314
|
-
const resetAtMs = resetTimestampMs(info.resetsAt);
|
|
45315
|
-
const reason = `${info.rateLimitType ?? "unknown"} rate limit`;
|
|
45316
|
-
const launchedExtraUsage = isExtraUsageRequiredMessage(info) && launchExtraUsageHelperIfAllowed(cwd, bridgeConfig, reason);
|
|
45317
|
-
emitRateLimitEvent({
|
|
45318
|
-
model: model.id,
|
|
45319
|
-
provider: PROVIDER_ID,
|
|
45320
|
-
rateLimitType: info.rateLimitType,
|
|
45321
|
-
reason,
|
|
45322
|
-
resetAt: info.resetsAt,
|
|
45323
|
-
...Number.isFinite(resetAtMs) ? { resetAtMs } : {},
|
|
45324
|
-
source: "claude-bridge",
|
|
45325
|
-
status: "rejected"
|
|
45326
|
-
});
|
|
45327
|
-
piUI?.notify(`${RATE_LIMIT_TOKEN} Claude ${reason} hit \u2014 resets ${resetsAt}${launchedExtraUsage ? "; opened /extra-usage helper" : ""}`, "warning");
|
|
45328
|
-
} else if (info?.status === "allowed_warning") {
|
|
45329
|
-
const warning = formatAllowedRateLimitWarning(info);
|
|
45330
|
-
if (warning) piUI?.notify(warning, "warning");
|
|
45331
|
-
else debug("consumeQuery: suppressed low/ambiguous allowed_warning rate_limit_event", JSON.stringify(info).slice(0, 300));
|
|
45332
|
-
}
|
|
45333
|
-
break;
|
|
45334
|
-
}
|
|
45335
|
-
default:
|
|
45336
|
-
debug("consumeQuery: unhandled SDK message type", message.type);
|
|
45337
|
-
break;
|
|
46472
|
+
if (claim.argsMismatch) {
|
|
46473
|
+
debug(`mcp handler: ${tool.name} [${toolCallId}] claimed sole same-name call despite args mismatch`);
|
|
46474
|
+
diagDump("tool_claim_args_mismatch", {
|
|
46475
|
+
toolName: tool.name,
|
|
46476
|
+
toolCallId,
|
|
46477
|
+
handlerArgKeys: argKeys(mappedArgs),
|
|
46478
|
+
recordedArgKeys: argKeys(queryCtx.turnToolCalls.find((call) => call.id === toolCallId)?.arguments)
|
|
46479
|
+
});
|
|
46480
|
+
} else if (claim.match !== "tool-args" || claim.ambiguous) {
|
|
46481
|
+
debug(`mcp handler: ${tool.name} [${toolCallId}] claimed by ${claim.match}${claim.ambiguous ? " (ambiguous)" : ""}`);
|
|
46482
|
+
}
|
|
46483
|
+
const earlyResult = toolCallId ? takeQueuedOrParkedResult(queryCtx, toolCallId) : void 0;
|
|
46484
|
+
if (earlyResult !== void 0) {
|
|
46485
|
+
queryCtx.markToolResultResolved(toolCallId);
|
|
46486
|
+
debug(`mcp handler: ${tool.name} [${toolCallId}] \u2192 resolved from queue/parked (${queryCtx.pendingResults.size} queued, ${queryCtx.reapedResults.size} parked remaining)`);
|
|
46487
|
+
return earlyResult;
|
|
46488
|
+
}
|
|
46489
|
+
debug(`mcp handler: ${tool.name} [${toolCallId}] \u2192 waiting`);
|
|
46490
|
+
scheduleToolUseTurnEnd(
|
|
46491
|
+
queryCtx,
|
|
46492
|
+
() => finalizeToolUseTurnFromMcpInvocation(queryCtx, toolCallId, tool.name, mappedArgs),
|
|
46493
|
+
`mcp-invocation:${tool.name}`
|
|
46494
|
+
);
|
|
46495
|
+
return new Promise((resolve5) => {
|
|
46496
|
+
queryCtx.pendingToolCalls.set(toolCallId, {
|
|
46497
|
+
toolName: tool.name,
|
|
46498
|
+
args: mappedArgs,
|
|
46499
|
+
generation: queryCtx.callbackGeneration,
|
|
46500
|
+
resolve: (result) => {
|
|
46501
|
+
queryCtx.markToolResultResolved(toolCallId);
|
|
46502
|
+
resolve5(result);
|
|
46503
|
+
}
|
|
46504
|
+
});
|
|
46505
|
+
});
|
|
45338
46506
|
}
|
|
45339
|
-
}
|
|
45340
|
-
|
|
45341
|
-
return {
|
|
46507
|
+
}));
|
|
46508
|
+
const server = f0e({ name: MCP_SERVER_NAME, version: "1.0.0", tools: mcpTools });
|
|
46509
|
+
return { [MCP_SERVER_NAME]: server };
|
|
45342
46510
|
}
|
|
45343
46511
|
function claimPrimaryInstance() {
|
|
45344
46512
|
const g = globalThis;
|
|
@@ -45347,6 +46515,9 @@ function claimPrimaryInstance() {
|
|
|
45347
46515
|
}
|
|
45348
46516
|
function releaseProviderTokens(event) {
|
|
45349
46517
|
const g = globalThis;
|
|
46518
|
+
if (g[CLAUDE_BRIDGE_ACCOUNT_HOST_SYMBOL] === BRIDGE_ACCOUNT_HOST) {
|
|
46519
|
+
g[CLAUDE_BRIDGE_ACCOUNT_HOST_SYMBOL] = void 0;
|
|
46520
|
+
}
|
|
45350
46521
|
if (g[ACTIVE_STREAM_SIMPLE_KEY] === streamClaudeAgentSdk) {
|
|
45351
46522
|
debug(`${event}: clearing ACTIVE_STREAM_SIMPLE_KEY`);
|
|
45352
46523
|
g[ACTIVE_STREAM_SIMPLE_KEY] = void 0;
|
|
@@ -45378,12 +46549,20 @@ function applyProviderRegistration(trigger) {
|
|
|
45378
46549
|
}
|
|
45379
46550
|
return;
|
|
45380
46551
|
}
|
|
45381
|
-
const credentialed = hasClaudeCredentials();
|
|
46552
|
+
const credentialed = hasClaudeCredentials() || Boolean(resolveClaudeAccountRouter());
|
|
45382
46553
|
debug(`${trigger}: native registration upsert, credentialed=${credentialed} (module=${moduleInstanceId})`);
|
|
45383
|
-
if (
|
|
46554
|
+
if (hasClaudeCredentials() && connectorsEnabledFor(loadConfig(process.cwd()))) primeConnectorServers();
|
|
45384
46555
|
g[ACTIVE_STREAM_SIMPLE_KEY] = streamClaudeAgentSdk;
|
|
45385
46556
|
try {
|
|
45386
|
-
nativeProviderInstance ??= buildNativeProvider(
|
|
46557
|
+
nativeProviderInstance ??= buildNativeProvider(
|
|
46558
|
+
_piAi,
|
|
46559
|
+
MODELS,
|
|
46560
|
+
streamClaudeAgentSdk,
|
|
46561
|
+
process.env,
|
|
46562
|
+
// Availability includes a companion account pool: the router owns
|
|
46563
|
+
// credentials the direct existence probes cannot see.
|
|
46564
|
+
() => hasClaudeCredentials() || Boolean(resolveClaudeAccountRouter())
|
|
46565
|
+
);
|
|
45387
46566
|
pi.registerProvider(nativeProviderInstance);
|
|
45388
46567
|
} catch (err) {
|
|
45389
46568
|
if (g[ACTIVE_STREAM_SIMPLE_KEY] === streamClaudeAgentSdk) g[ACTIVE_STREAM_SIMPLE_KEY] = void 0;
|
|
@@ -45391,7 +46570,17 @@ function applyProviderRegistration(trigger) {
|
|
|
45391
46570
|
}
|
|
45392
46571
|
}
|
|
45393
46572
|
function streamClaudeAgentSdk(model, context, options) {
|
|
46573
|
+
return runInRequestLane(options?.sessionId, () => streamClaudeAgentSdkInLane(model, context, options));
|
|
46574
|
+
}
|
|
46575
|
+
function streamClaudeAgentSdkInLane(model, context, options) {
|
|
45394
46576
|
const stream = newAssistantMessageEventStream();
|
|
46577
|
+
const laneId = currentRequestLaneId();
|
|
46578
|
+
const ephemeralLane = laneId !== void 0 && options?.cacheRetention === "none";
|
|
46579
|
+
const releaseEphemeralLane = () => {
|
|
46580
|
+
if (!ephemeralLane) return;
|
|
46581
|
+
deleteSharedSessionLane(laneId);
|
|
46582
|
+
deleteQueryLane(laneId);
|
|
46583
|
+
};
|
|
45395
46584
|
const lastMsgRole = context.messages[context.messages.length - 1]?.role;
|
|
45396
46585
|
const cwd = options?.cwd ?? process.cwd();
|
|
45397
46586
|
debug(`provider: streamClaudeAgentSdk called, activeQuery=${!!ctx().activeQuery}, lastMsgRole=${lastMsgRole}, isReentrant=${ctx().activeQuery !== null}`);
|
|
@@ -45399,13 +46588,14 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45399
46588
|
const queryCtx = ctx();
|
|
45400
46589
|
queryCtx.currentPiStream = stream;
|
|
45401
46590
|
queryCtx.resetTurnState(model);
|
|
46591
|
+
queryCtx.callbackGeneration += 1;
|
|
45402
46592
|
activeStreamIdleWatchdogs.get(queryCtx)?.refresh();
|
|
45403
46593
|
const allResults = extractAllToolResults2(context);
|
|
45404
46594
|
debug(`provider: tool results, ${allResults.length} results, ${queryCtx.pendingToolCalls.size} waiting handlers, ctx.msgs=${context.messages.length}`);
|
|
45405
46595
|
const unmatchedResultIds = [];
|
|
45406
46596
|
for (const result of allResults) {
|
|
45407
46597
|
const id = result.toolCallId;
|
|
45408
|
-
if (id && !queryCtx.hasRecordedToolCall(id)) {
|
|
46598
|
+
if (id && !queryCtx.hasRecordedToolCall(id) && !queryCtx.forwardedToolCallIds.has(id)) {
|
|
45409
46599
|
queryCtx.markToolResultUnmatched(id);
|
|
45410
46600
|
unmatchedResultIds.push(id);
|
|
45411
46601
|
debug(`ERROR: tool result [${id}] has no registered tool_call id; refusing to queue or deliver`);
|
|
@@ -45424,7 +46614,7 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45424
46614
|
debug(`WARNING: tool result without toolCallId, cannot match`);
|
|
45425
46615
|
}
|
|
45426
46616
|
if (queryCtx.pendingToolCalls.size > 0 && queryCtx.pendingResults.size > 0) {
|
|
45427
|
-
debug(`
|
|
46617
|
+
debug(`note: handlers and queued results coexist: handlers=${queryCtx.pendingToolCalls.size} results=${queryCtx.pendingResults.size}`);
|
|
45428
46618
|
}
|
|
45429
46619
|
}
|
|
45430
46620
|
if (unmatchedResultIds.length > 0) {
|
|
@@ -45432,38 +46622,65 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45432
46622
|
content: [{ type: "text", text: `Claude bridge internal error: ${unmatchedResultIds.length} tool result(s) did not match any registered tool_call id. The turn was stopped to avoid delivering tool output to the wrong call. Unmatched ids: ${unmatchedResultIds.slice(0, 8).join(", ")}${unmatchedResultIds.length > 8 ? ", ..." : ""}` }],
|
|
45433
46623
|
isError: true
|
|
45434
46624
|
};
|
|
45435
|
-
for (const pending of queryCtx.pendingToolCalls
|
|
46625
|
+
for (const [pendingId, pending] of queryCtx.pendingToolCalls) {
|
|
46626
|
+
if (!queryCtx.forwardedToolCallIds.has(pendingId)) queryCtx.deadToolCallIds.add(pendingId);
|
|
46627
|
+
pending.resolve(errorResult);
|
|
46628
|
+
}
|
|
45436
46629
|
queryCtx.pendingToolCalls.clear();
|
|
45437
46630
|
reportToolResultMismatch(queryCtx, "unmatched tool result", cwd);
|
|
45438
46631
|
}
|
|
45439
46632
|
if (queryCtx.pendingToolCalls.size > 0) {
|
|
45440
|
-
|
|
45441
|
-
|
|
46633
|
+
const stranded = drainStrandedToolCalls(queryCtx);
|
|
46634
|
+
if (stranded.length > 0) {
|
|
46635
|
+
const names = stranded.map((entry) => entry.toolName).join(", ");
|
|
46636
|
+
debug(`provider: failed ${stranded.length} stranded MCP handler(s) never forwarded to Pi: ${names}`);
|
|
46637
|
+
diagDump("tool_handlers_stranded", { count: stranded.length, stranded });
|
|
46638
|
+
appendIntegrityEntry("tool_handlers_stranded", { count: stranded.length, stranded });
|
|
46639
|
+
safeNotify(`Claude bridge: failed ${stranded.length} tool call(s) that never reached Pi before their turn ended (${names}). The model saw a retryable error.`, "warning");
|
|
46640
|
+
}
|
|
46641
|
+
if (queryCtx.pendingToolCalls.size > 0) {
|
|
46642
|
+
debug(`WARNING: ${queryCtx.pendingToolCalls.size} MCP handlers still waiting after delivering ${allResults.length} results`);
|
|
46643
|
+
safeNotify(`Claude bridge: ${queryCtx.pendingToolCalls.size} tool handler(s) still waiting \u2014 provider may be stuck`, "warning");
|
|
46644
|
+
}
|
|
45442
46645
|
}
|
|
46646
|
+
let capturedThrough = context.messages.length;
|
|
45443
46647
|
if (lastMsgRole === "user") {
|
|
45444
|
-
const
|
|
45445
|
-
if (
|
|
45446
|
-
ctx().deferredUserMessages.push(
|
|
45447
|
-
debug(`provider: deferred user message for replay after query: ${
|
|
46648
|
+
const replay = planDeferredUserReplay(context.messages, queryCtx.latestCursor);
|
|
46649
|
+
if (replay.prompt || replay.blocks) {
|
|
46650
|
+
ctx().deferredUserMessages.push({ text: replay.prompt ?? "", blocks: replay.blocks ?? void 0 });
|
|
46651
|
+
debug(`provider: deferred ${replay.userMessageCount} user message(s) for replay after query${replay.blocks ? ` (${replay.blocks.length} blocks incl. images)` : ""}: ${(replay.prompt ?? "[image-only]").slice(0, 60)}`);
|
|
46652
|
+
} else {
|
|
46653
|
+
capturedThrough = replay.runStart;
|
|
46654
|
+
diagDump("deferred_user_replay_skipped", {
|
|
46655
|
+
contextLength: context.messages.length,
|
|
46656
|
+
runStart: replay.runStart,
|
|
46657
|
+
userMessageCount: replay.userMessageCount,
|
|
46658
|
+
messageRoles: context.messages.map((m, i) => `[${i}]${m.role}`).join(" ")
|
|
46659
|
+
});
|
|
45448
46660
|
}
|
|
45449
46661
|
}
|
|
45450
|
-
|
|
45451
|
-
|
|
46662
|
+
const activeSession = getSharedSession();
|
|
46663
|
+
if (activeSession && stackDepth() === 0 && !queryCtx.detachedFromSharedSession) {
|
|
46664
|
+
setSharedSession({ ...activeSession, cursor: Math.max(activeSession.cursor, capturedThrough) });
|
|
46665
|
+
}
|
|
46666
|
+
queryCtx.latestCursor = Math.max(queryCtx.latestCursor, capturedThrough);
|
|
45452
46667
|
return stream;
|
|
45453
46668
|
}
|
|
45454
46669
|
const lastMsg = context.messages[context.messages.length - 1];
|
|
45455
46670
|
if (lastMsg?.role === "toolResult") {
|
|
45456
46671
|
debug(`provider: orphaned tool result after abort, emitting end_turn`);
|
|
45457
|
-
|
|
46672
|
+
const activeSession = getSharedSession();
|
|
46673
|
+
if (activeSession && stackDepth() === 0 && !ctx().detachedFromSharedSession) setSharedSession({ ...activeSession, cursor: context.messages.length });
|
|
45458
46674
|
const c = ctx();
|
|
45459
46675
|
queueMicrotask(() => {
|
|
45460
46676
|
c.resetTurnState(model);
|
|
45461
46677
|
stream.push({ type: "done", reason: "stop", message: c.turnOutput });
|
|
45462
46678
|
stream.end();
|
|
46679
|
+
releaseEphemeralLane();
|
|
45463
46680
|
});
|
|
45464
46681
|
return stream;
|
|
45465
46682
|
}
|
|
45466
|
-
if (!hasClaudeCredentials()) {
|
|
46683
|
+
if (!hasClaudeCredentials() && !resolveClaudeAccountRouter()) {
|
|
45467
46684
|
try {
|
|
45468
46685
|
applyProviderRegistration("pre-spawn");
|
|
45469
46686
|
} catch {
|
|
@@ -45491,6 +46708,7 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45491
46708
|
queueMicrotask(() => {
|
|
45492
46709
|
stream.push({ type: "error", reason: "error", error: errorOutput });
|
|
45493
46710
|
stream.end();
|
|
46711
|
+
releaseEphemeralLane();
|
|
45494
46712
|
});
|
|
45495
46713
|
return stream;
|
|
45496
46714
|
}
|
|
@@ -45500,87 +46718,167 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45500
46718
|
ctx().currentPiStream = stream;
|
|
45501
46719
|
ctx().pendingToolCalls.clear();
|
|
45502
46720
|
ctx().pendingResults.clear();
|
|
46721
|
+
ctx().reapedResults.clear();
|
|
46722
|
+
ctx().forwardedToolCallIds.clear();
|
|
46723
|
+
ctx().deadToolCallIds.clear();
|
|
46724
|
+
ctx().callbackGeneration = 0;
|
|
45503
46725
|
ctx().deferredUserMessages = [];
|
|
45504
46726
|
ctx().resetTurnState(model);
|
|
45505
46727
|
ctx().resetToolTracking();
|
|
45506
46728
|
ctx().latestCursor = 0;
|
|
46729
|
+
ctx().committedOutput = false;
|
|
46730
|
+
ctx().detachedFromSharedSession = isReentrant;
|
|
46731
|
+
const router = resolveClaudeAccountRouter();
|
|
46732
|
+
const rotationOptions = options;
|
|
46733
|
+
const rotationState = rotationOptions?.[ROTATION_STATE_KEY] ?? {
|
|
46734
|
+
excludedProfileIds: /* @__PURE__ */ new Set(),
|
|
46735
|
+
attempts: 0
|
|
46736
|
+
};
|
|
46737
|
+
let account;
|
|
46738
|
+
if (router) {
|
|
46739
|
+
try {
|
|
46740
|
+
account = router.acquire({
|
|
46741
|
+
modelId: model.id,
|
|
46742
|
+
sessionId: options?.sessionId,
|
|
46743
|
+
excludedProfileIds: [...rotationState.excludedProfileIds],
|
|
46744
|
+
forceRerank: rotationState.attempts > 0,
|
|
46745
|
+
reason: rotationState.attempts > 0 ? "automatic-failover" : void 0
|
|
46746
|
+
});
|
|
46747
|
+
rotationState.attempts += 1;
|
|
46748
|
+
} catch (error51) {
|
|
46749
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
46750
|
+
const resetAtMs = Number(error51?.resetAtMs);
|
|
46751
|
+
const rateLimitType = error51?.rateLimitType;
|
|
46752
|
+
if (ctx().turnOutput) {
|
|
46753
|
+
ctx().turnOutput.stopReason = "error";
|
|
46754
|
+
ctx().turnOutput.errorMessage = message;
|
|
46755
|
+
if (Number.isFinite(resetAtMs)) {
|
|
46756
|
+
Object.assign(ctx().turnOutput, { resetAtMs, rateLimitType });
|
|
46757
|
+
}
|
|
46758
|
+
}
|
|
46759
|
+
if (Number.isFinite(resetAtMs)) {
|
|
46760
|
+
emitRateLimitEvent({
|
|
46761
|
+
model: model.id,
|
|
46762
|
+
provider: model.provider,
|
|
46763
|
+
rateLimitType: rateLimitType ?? "all_accounts",
|
|
46764
|
+
reason: message,
|
|
46765
|
+
resetAt: new Date(resetAtMs).toISOString(),
|
|
46766
|
+
resetAtMs,
|
|
46767
|
+
source: "claude-bridge",
|
|
46768
|
+
status: "rejected"
|
|
46769
|
+
});
|
|
46770
|
+
}
|
|
46771
|
+
const errorOutput = ctx().turnOutput;
|
|
46772
|
+
if (isReentrant) popContext();
|
|
46773
|
+
queueMicrotask(() => {
|
|
46774
|
+
stream.push({ type: "error", reason: "error", error: errorOutput });
|
|
46775
|
+
stream.end();
|
|
46776
|
+
releaseEphemeralLane();
|
|
46777
|
+
});
|
|
46778
|
+
return stream;
|
|
46779
|
+
}
|
|
46780
|
+
}
|
|
46781
|
+
const queryModel = account?.modelId && account.modelId !== model.id ? { ...model, id: account.modelId, name: modelDisplayName(account.modelId) } : model;
|
|
46782
|
+
if (queryModel.id !== model.id) {
|
|
46783
|
+
updateTurnOutputModel(queryModel.id);
|
|
46784
|
+
if (rotationState.announcedModelId !== queryModel.id) {
|
|
46785
|
+
rotationState.announcedModelId = queryModel.id;
|
|
46786
|
+
safeNotify(
|
|
46787
|
+
account?.fallbackReason === "fable-quota" ? `Every ready account rejected Claude Fable; using ${modelDisplayName(queryModel.id)}.` : `Pi Claude switched to ${modelDisplayName(queryModel.id)}.`,
|
|
46788
|
+
"info"
|
|
46789
|
+
);
|
|
46790
|
+
}
|
|
46791
|
+
}
|
|
46792
|
+
const attemptCtx = ctx();
|
|
46793
|
+
const attemptBuffer = account ? new RetryEventBuffer(stream, () => attemptCtx.markOutputCommitted()) : void 0;
|
|
46794
|
+
if (attemptBuffer) attemptCtx.currentPiStream = attemptBuffer;
|
|
45507
46795
|
const { mcpTools, customToolNameToSdk, customToolNameToPi } = resolveMcpTools(context);
|
|
45508
|
-
const
|
|
45509
|
-
|
|
45510
|
-
|
|
46796
|
+
const bridgeConfig = loadConfig(cwd);
|
|
46797
|
+
const providerSettings = bridgeConfig.provider ?? {};
|
|
46798
|
+
const claudeExecutable = resolveClaudeExecutable(providerSettings.pathToClaudeCodeExecutable);
|
|
46799
|
+
const claudeExecutablePreflight = claudeExecutable ? preflightClaudeExecutable(claudeExecutable, cwd) : void 0;
|
|
46800
|
+
const accountScope = accountSessionScope(account);
|
|
46801
|
+
const cursorBeforeSync = getSharedSession()?.cursor ?? null;
|
|
46802
|
+
const syncResult = isReentrant ? { sessionId: null, promptStart: context.messages.length - 1 } : syncSharedSession(context.messages, cwd, customToolNameToSdk, queryModel.id, accountScope);
|
|
46803
|
+
const { sessionId: resumeSessionId, promptStart } = syncResult;
|
|
46804
|
+
const foreignContext = syncResult.foreignContext === true;
|
|
46805
|
+
if (foreignContext) ctx().detachedFromSharedSession = true;
|
|
46806
|
+
const conversationFp = isReentrant || foreignContext ? void 0 : conversationFingerprint(context.messages);
|
|
46807
|
+
const promptMessages = context.messages.slice(promptStart);
|
|
46808
|
+
const promptBlocks = extractUserPromptBlocks(promptMessages);
|
|
46809
|
+
let promptText = extractUserPrompt(promptMessages) ?? "";
|
|
46810
|
+
if (!promptText.trim() && !promptBlocks) {
|
|
45511
46811
|
diagDump("empty_prompt", {
|
|
45512
46812
|
contextLength: context.messages.length,
|
|
45513
46813
|
lastMsgRole: lastMsg?.role,
|
|
45514
46814
|
isReentrant,
|
|
45515
46815
|
stackDepth: stackDepth(),
|
|
45516
46816
|
activeQueryExists: ctx().activeQuery !== null,
|
|
45517
|
-
|
|
46817
|
+
cursorBeforeSync,
|
|
46818
|
+
promptStart,
|
|
46819
|
+
promptRoles: promptMessages.map((m) => m.role).join(" "),
|
|
46820
|
+
sharedSession: (() => {
|
|
46821
|
+
const activeSession = getSharedSession();
|
|
46822
|
+
return activeSession ? { sessionId: activeSession.sessionId.slice(0, 8), cursor: activeSession.cursor } : null;
|
|
46823
|
+
})(),
|
|
45518
46824
|
messageRoles: context.messages.map((m, i) => `[${i}]${m.role}`).join(" ")
|
|
45519
46825
|
});
|
|
45520
46826
|
promptText = "[continue]";
|
|
45521
46827
|
}
|
|
45522
46828
|
const prompt = promptBlocks ? wrapPromptStream(promptBlocks) : promptText;
|
|
45523
46829
|
const mcpServers = buildMcpServers(mcpTools, ctx());
|
|
45524
|
-
const
|
|
45525
|
-
const providerSettings = bridgeConfig.provider ?? {};
|
|
45526
|
-
const enableCloudMcp = connectorsEnabledFor(bridgeConfig);
|
|
45527
|
-
const connectorWriteMode = connectorWriteModeFor(bridgeConfig);
|
|
45528
|
-
const connectorServers = enableCloudMcp ? connectorServersSnapshot() : {};
|
|
45529
|
-
const appendSystemPrompt = providerSettings.appendSystemPrompt !== false;
|
|
45530
|
-
const agentsAppend = appendSystemPrompt ? extractAgentsAppend() : void 0;
|
|
45531
|
-
const skillsAppend = appendSystemPrompt ? extractSkillsBlock(context.systemPrompt) : void 0;
|
|
45532
|
-
const promptContextAppend = buildPromptContextAppend(context.systemPrompt, cwd, bridgeConfig.promptContext ?? {});
|
|
45533
|
-
const appendParts = [agentsAppend, skillsAppend, promptContextAppend.text].filter((part) => Boolean(part));
|
|
45534
|
-
const systemPromptAppend = appendParts.length > 0 ? appendParts.join("\n\n") : void 0;
|
|
45535
|
-
const settingSources = enableCloudMcp ? providerSettings.settingSources ?? ["user", "project", "local"] : appendSystemPrompt ? void 0 : providerSettings.settingSources ?? ["user", "project"];
|
|
45536
|
-
const strictMcpConfigEnabled = !appendSystemPrompt && providerSettings.strictMcpConfig !== false;
|
|
45537
|
-
const claudeExecutable = resolveClaudeExecutable(providerSettings.pathToClaudeCodeExecutable);
|
|
45538
|
-
const claudeExecutablePreflight = claudeExecutable ? preflightClaudeExecutable(claudeExecutable, cwd) : void 0;
|
|
45539
|
-
const { sessionId: resumeSessionId } = syncSharedSession(context.messages, cwd, customToolNameToSdk, model.id);
|
|
45540
|
-
const requestedEffort = options?.reasoning ? model.thinkingLevelMap?.[options.reasoning] ?? REASONING_TO_EFFORT[options.reasoning] : void 0;
|
|
45541
|
-
const effort = resolveConfiguredEffort(model.id, requestedEffort, providerSettings);
|
|
45542
|
-
const extraArgs = {};
|
|
45543
|
-
if (effort) extraArgs["thinking-display"] = "summarized";
|
|
45544
|
-
const fallbackModel = fallbackModelForPrimaryModel(model.id);
|
|
45545
|
-
const childEnv = { ...process.env, ENABLE_CLAUDEAI_MCP_SERVERS: enableCloudMcp ? "1" : "0", DISABLE_AUTO_COMPACT: "1" };
|
|
45546
|
-
const queryOptions = {
|
|
46830
|
+
const built = buildClaudeQueryOptions({
|
|
45547
46831
|
cwd,
|
|
45548
|
-
|
|
45549
|
-
|
|
45550
|
-
|
|
45551
|
-
|
|
45552
|
-
|
|
45553
|
-
|
|
45554
|
-
|
|
45555
|
-
|
|
45556
|
-
|
|
45557
|
-
|
|
45558
|
-
|
|
45559
|
-
},
|
|
45560
|
-
extraArgs,
|
|
45561
|
-
...strictMcpConfigEnabled ? { strictMcpConfig: true } : {},
|
|
45562
|
-
...effort ? { effort } : {},
|
|
45563
|
-
...settingSources ? { settingSources } : {},
|
|
45564
|
-
...mcpServers || Object.keys(connectorServers).length > 0 ? { mcpServers: { ...mcpServers ?? {}, ...connectorServers } } : {},
|
|
45565
|
-
...resumeSessionId ? { resume: resumeSessionId } : {},
|
|
45566
|
-
...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
|
|
45567
|
-
spawnClaudeCodeProcess: spawnClaudeCodeWithDiagnostics,
|
|
45568
|
-
...makeCliDebugOptions("provider")
|
|
45569
|
-
};
|
|
46832
|
+
requestedModel: model,
|
|
46833
|
+
queryModel,
|
|
46834
|
+
account,
|
|
46835
|
+
bridgeConfig,
|
|
46836
|
+
systemPrompt: context.systemPrompt,
|
|
46837
|
+
reasoning: options?.reasoning,
|
|
46838
|
+
resumeSessionId,
|
|
46839
|
+
mcpServers,
|
|
46840
|
+
claudeExecutable
|
|
46841
|
+
});
|
|
46842
|
+
const { queryOptions } = built;
|
|
45570
46843
|
debug(
|
|
45571
46844
|
"provider: fresh query",
|
|
45572
|
-
`model=${model.id} msgs=${context.messages.length} tools=${mcpTools.length}`,
|
|
45573
|
-
`resume=${resumeSessionId?.slice(0, 8) ?? "none"} effort=${effort ?? "default"}`,
|
|
45574
|
-
`fallback=${fallbackModel ?? "none"}`,
|
|
45575
|
-
`appendSys=${appendSystemPrompt} promptCtx=${
|
|
46845
|
+
`model=${queryModel.id} requested=${model.id} msgs=${context.messages.length} tools=${mcpTools.length}`,
|
|
46846
|
+
`resume=${resumeSessionId?.slice(0, 8) ?? "none"} effort=${built.effort ?? "default"} account=${account?.label ?? "legacy"}`,
|
|
46847
|
+
`fallback=${built.fallbackModel ?? "none"}`,
|
|
46848
|
+
`appendSys=${built.appendSystemPrompt} promptCtx=${built.promptContextLabels.join(",") || "none"} strictMcp=${built.strictMcpConfigEnabled} fastMode=${providerSettings.fastMode === true} connectors=${built.enableCloudMcp}`,
|
|
45576
46849
|
`claudeExec=${claudeExecutablePreflight ? `${claudeExecutablePreflight.fileType}:${claudeExecutablePreflight.path}` : "sdk-default"}`,
|
|
45577
46850
|
`prompt=${promptText.slice(0, 60)}${promptBlocks ? " [+images]" : ""}`
|
|
45578
46851
|
);
|
|
45579
46852
|
let wasAborted = false;
|
|
45580
46853
|
let streamIdleTimedOut = false;
|
|
45581
|
-
|
|
46854
|
+
let retryRequested = false;
|
|
46855
|
+
let retryFailure;
|
|
46856
|
+
const sdkQuery = sdkQueryFactory({ prompt, options: queryOptions });
|
|
45582
46857
|
ctx().activeQuery = sdkQuery;
|
|
45583
46858
|
const abortCtx = ctx();
|
|
46859
|
+
const attemptFailure = {};
|
|
46860
|
+
const persistSession = (next) => {
|
|
46861
|
+
if (isReentrant || foreignContext) return;
|
|
46862
|
+
setSharedSession(next && conversationFp ? { conversationFingerprint: conversationFp, ...next } : next);
|
|
46863
|
+
};
|
|
46864
|
+
const markRebuildForThisQuery = (opts = {}) => {
|
|
46865
|
+
if (isReentrant || foreignContext) return;
|
|
46866
|
+
markSessionForRebuild(opts);
|
|
46867
|
+
};
|
|
46868
|
+
const dropDeferredUserMessages = (site, undelivered) => {
|
|
46869
|
+
const dropped = [...undelivered !== void 0 ? [undelivered] : [], ...abortCtx.deferredUserMessages];
|
|
46870
|
+
abortCtx.deferredUserMessages = [];
|
|
46871
|
+
if (dropped.length > 0) {
|
|
46872
|
+
diagDump("deferred_user_messages_dropped", summarizeDroppedUserMessages(site, dropped));
|
|
46873
|
+
}
|
|
46874
|
+
return dropped;
|
|
46875
|
+
};
|
|
46876
|
+
let accountFailureRecorded = false;
|
|
46877
|
+
const recordAttemptFailure = (failure) => {
|
|
46878
|
+
if (accountFailureRecorded || !account || !router || !failure.kind || failure.rateLimitInfo || wasAborted || options?.signal?.aborted) return;
|
|
46879
|
+
safeRouterCall("recordFailure", () => router.recordFailure(account.profileId, failure.kind, queryModel.id));
|
|
46880
|
+
accountFailureRecorded = true;
|
|
46881
|
+
};
|
|
45584
46882
|
const requestAbort = () => {
|
|
45585
46883
|
void sdkQuery.interrupt().catch(() => {
|
|
45586
46884
|
});
|
|
@@ -45589,6 +46887,28 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45589
46887
|
} catch {
|
|
45590
46888
|
}
|
|
45591
46889
|
};
|
|
46890
|
+
const requestRotation = (failure) => {
|
|
46891
|
+
recordAttemptFailure(failure);
|
|
46892
|
+
const committed = abortCtx.committedOutput || attemptBuffer?.hasCommittedOutput === true;
|
|
46893
|
+
const eligible = Boolean(!isReentrant && account && router && failure.kind && !committed && !wasAborted && !options?.signal?.aborted && rotationState.attempts < MAX_ROTATION_ATTEMPTS);
|
|
46894
|
+
debug("provider: account rotation decision", JSON.stringify({
|
|
46895
|
+
eligible,
|
|
46896
|
+
account: account?.label,
|
|
46897
|
+
kind: failure.kind,
|
|
46898
|
+
committedOutput: committed,
|
|
46899
|
+
wasAborted,
|
|
46900
|
+
signalAborted: options?.signal?.aborted === true,
|
|
46901
|
+
attempts: rotationState.attempts
|
|
46902
|
+
}));
|
|
46903
|
+
if (!eligible || !account || !router || !failure.kind) return false;
|
|
46904
|
+
rotationState.excludedProfileIds.add(account.profileId);
|
|
46905
|
+
retryRequested = true;
|
|
46906
|
+
retryFailure = failure;
|
|
46907
|
+
attemptBuffer?.discard();
|
|
46908
|
+
abortCtx.currentPiStream = null;
|
|
46909
|
+
debug(`provider: rotating account after ${failure.kind}, from=${account.label}, attempt=${rotationState.attempts}`);
|
|
46910
|
+
return true;
|
|
46911
|
+
};
|
|
45592
46912
|
const streamIdleTimeoutMs = streamIdleTimeoutMsFromEnv();
|
|
45593
46913
|
const streamIdleWatchdog = streamIdleTimeoutMs > 0 ? createStreamIdleWatchdog({
|
|
45594
46914
|
getState: () => ({
|
|
@@ -45601,15 +46921,20 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45601
46921
|
onTimeout: ({ idleMs, timeoutMs }) => {
|
|
45602
46922
|
if (streamIdleTimedOut || wasAborted || options?.signal?.aborted || abortCtx.activeQuery !== sdkQuery) return;
|
|
45603
46923
|
streamIdleTimedOut = true;
|
|
45604
|
-
|
|
45605
|
-
|
|
45606
|
-
if (sharedSession) setSharedSession({ ...sharedSession, needsRebuild: true, forceRotate: true });
|
|
46924
|
+
dropDeferredUserMessages("stream-idle-timeout");
|
|
46925
|
+
markRebuildForThisQuery({ forceRotate: true });
|
|
45607
46926
|
const errorMessage = buildStreamIdleTimeoutErrorMessage(timeoutMs);
|
|
45608
|
-
debug("provider: stream idle timeout", `model=${
|
|
46927
|
+
debug("provider: stream idle timeout", `model=${queryModel.id}`, `timeout=${timeoutMs}`, `idle=${idleMs}`);
|
|
46928
|
+
const idleFailure = { kind: "network", message: errorMessage };
|
|
46929
|
+
if (requestRotation(idleFailure)) {
|
|
46930
|
+
requestAbort();
|
|
46931
|
+
return;
|
|
46932
|
+
}
|
|
46933
|
+
abortCtx.handledTerminalError = true;
|
|
45609
46934
|
emitRateLimitEvent({
|
|
45610
46935
|
idleMs,
|
|
45611
|
-
model:
|
|
45612
|
-
provider:
|
|
46936
|
+
model: queryModel.id,
|
|
46937
|
+
provider: queryModel.provider,
|
|
45613
46938
|
rateLimitType: "stream_idle",
|
|
45614
46939
|
reason: "Claude Code stream idle timeout",
|
|
45615
46940
|
retryAfterMs: STREAM_IDLE_BACKOFF_HINT_MS,
|
|
@@ -45617,7 +46942,7 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45617
46942
|
status: "rejected",
|
|
45618
46943
|
timeoutMs
|
|
45619
46944
|
});
|
|
45620
|
-
|
|
46945
|
+
safeNotify(`${RATE_LIMIT_TOKEN} Claude stream idle timeout after ${formatDurationShort(timeoutMs)} \u2014 retrying via rate-limit backoff`, "warning");
|
|
45621
46946
|
if (abortCtx.turnOutput) {
|
|
45622
46947
|
abortCtx.turnOutput.stopReason = "error";
|
|
45623
46948
|
abortCtx.turnOutput.errorMessage = errorMessage;
|
|
@@ -45638,68 +46963,126 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45638
46963
|
activeStreamIdleWatchdogs.set(abortCtx, streamIdleWatchdog);
|
|
45639
46964
|
streamIdleWatchdog.refresh();
|
|
45640
46965
|
}
|
|
45641
|
-
const onAbort = () => {
|
|
46966
|
+
const onAbort = () => runInRequestLane(laneId, () => {
|
|
45642
46967
|
wasAborted = true;
|
|
45643
|
-
|
|
45644
|
-
reportToolResultMismatch(abortCtx, "abort", cwd, {
|
|
46968
|
+
dropDeferredUserMessages("abort");
|
|
46969
|
+
reportToolResultMismatch(abortCtx, "abort", cwd, {
|
|
46970
|
+
expectedInterruption: true,
|
|
46971
|
+
forceRotate: true
|
|
46972
|
+
});
|
|
45645
46973
|
const drained = drainPendingToolCalls(abortCtx, "abort");
|
|
45646
46974
|
if (drained > 0) debug(`provider: abort drained ${drained} waiting MCP handler(s) as errors`);
|
|
45647
46975
|
abortCtx.pendingResults.clear();
|
|
45648
46976
|
requestAbort();
|
|
45649
|
-
};
|
|
46977
|
+
});
|
|
45650
46978
|
if (options?.signal) {
|
|
45651
46979
|
if (options.signal.aborted) onAbort();
|
|
45652
46980
|
else options.signal.addEventListener("abort", onAbort, { once: true });
|
|
45653
46981
|
}
|
|
45654
|
-
|
|
45655
|
-
|
|
46982
|
+
const surfaceFailure = (failure, aborted2 = false) => {
|
|
46983
|
+
attemptBuffer?.commit();
|
|
46984
|
+
if (failure.rateLimitInfo) {
|
|
46985
|
+
const info = failure.rateLimitInfo;
|
|
46986
|
+
const resetAt = rateLimitResetFromInfo(info);
|
|
46987
|
+
const resetAtMs = rateLimitResetMs(info);
|
|
46988
|
+
emitRateLimitEvent({
|
|
46989
|
+
model: queryModel.id,
|
|
46990
|
+
provider: queryModel.provider,
|
|
46991
|
+
rateLimitType: rateLimitTypeFromInfo(info),
|
|
46992
|
+
reason: failure.message,
|
|
46993
|
+
resetAt,
|
|
46994
|
+
...Number.isFinite(resetAtMs) ? { resetAtMs } : {},
|
|
46995
|
+
source: "claude-bridge",
|
|
46996
|
+
status: "rejected"
|
|
46997
|
+
});
|
|
46998
|
+
safeNotify(`${RATE_LIMIT_TOKEN} Claude ${failure.message} \u2014 resets ${formatResetTimestamp(resetAtMs ?? resetAt)}`, "warning");
|
|
46999
|
+
}
|
|
47000
|
+
if (abortCtx.turnOutput) {
|
|
47001
|
+
abortCtx.turnOutput.stopReason = aborted2 ? "aborted" : "error";
|
|
47002
|
+
abortCtx.turnOutput.errorMessage = failure.message;
|
|
47003
|
+
}
|
|
47004
|
+
abortCtx.currentPiStream?.push({ type: "error", reason: aborted2 ? "aborted" : "error", error: abortCtx.turnOutput });
|
|
47005
|
+
abortCtx.currentPiStream?.end();
|
|
47006
|
+
abortCtx.currentPiStream = null;
|
|
47007
|
+
};
|
|
47008
|
+
consumeQuery(sdkQuery, abortCtx, customToolNameToPi, queryModel, bridgeConfig, () => wasAborted, account, router, attemptFailure).then(async ({ capturedSessionId, failure }) => {
|
|
47009
|
+
debug(`provider: consumeQuery completed, stopReason=${abortCtx.turnOutput?.stopReason}, failure=${failure?.kind ?? "none"}, aborted=${wasAborted}`);
|
|
45656
47010
|
if (streamIdleTimedOut) {
|
|
45657
|
-
|
|
45658
|
-
debug(
|
|
47011
|
+
dropDeferredUserMessages("stream-idle-timeout-completion");
|
|
47012
|
+
debug(`provider: stream idle timeout ${retryRequested ? "queued account rotation" : "already surfaced"}; skipping normal completion`);
|
|
45659
47013
|
return;
|
|
45660
47014
|
}
|
|
45661
47015
|
if (wasAborted || options?.signal?.aborted) {
|
|
45662
|
-
|
|
45663
|
-
|
|
47016
|
+
markRebuildForThisQuery({ forceRotate: true });
|
|
47017
|
+
dropDeferredUserMessages("abort-completion");
|
|
45664
47018
|
debug(`provider: abort detected, marked sharedSession needsRebuild + forceRotate`);
|
|
45665
|
-
|
|
45666
|
-
|
|
45667
|
-
|
|
47019
|
+
surfaceFailure({ message: "Operation aborted" }, true);
|
|
47020
|
+
return;
|
|
47021
|
+
}
|
|
47022
|
+
if (failure) {
|
|
47023
|
+
if (requestRotation(failure)) return;
|
|
47024
|
+
if (!abortCtx.handledTerminalError) surfaceFailure(failure);
|
|
47025
|
+
const droppedSteers = dropDeferredUserMessages("terminal-failure");
|
|
47026
|
+
const activeSession2 = getSharedSession();
|
|
47027
|
+
const failedSessionId = capturedSessionId ?? activeSession2?.sessionId;
|
|
47028
|
+
if (failedSessionId) {
|
|
47029
|
+
const cursor = Math.max(context.messages.length, abortCtx.latestCursor, activeSession2?.cursor ?? 0);
|
|
47030
|
+
debug(`provider: terminal failure, persisting session=${failedSessionId.slice(0, 8)}, cursor=${cursor}, account=${account?.label ?? "legacy"}, droppedSteers=${droppedSteers.length}`);
|
|
47031
|
+
persistSession({ sessionId: failedSessionId, cursor, cwd, ...accountScope, ...droppedSteers.length > 0 ? { needsRebuild: true } : {} });
|
|
45668
47032
|
}
|
|
45669
|
-
abortCtx.currentPiStream?.push({ type: "error", reason: "aborted", error: abortCtx.turnOutput });
|
|
45670
|
-
abortCtx.currentPiStream?.end();
|
|
45671
|
-
abortCtx.currentPiStream = null;
|
|
45672
47033
|
return;
|
|
45673
47034
|
}
|
|
45674
|
-
const
|
|
47035
|
+
const activeSession = getSharedSession();
|
|
47036
|
+
const sessionId = capturedSessionId ?? activeSession?.sessionId;
|
|
45675
47037
|
if (sessionId) {
|
|
45676
|
-
const cursor = Math.max(context.messages.length, abortCtx.latestCursor,
|
|
45677
|
-
debug(`provider: query done, session=${sessionId.slice(0, 8)}, cursor=${cursor}`);
|
|
45678
|
-
|
|
47038
|
+
const cursor = Math.max(context.messages.length, abortCtx.latestCursor, activeSession?.cursor ?? 0);
|
|
47039
|
+
debug(`provider: query done, session=${sessionId.slice(0, 8)}, cursor=${cursor}, account=${account?.label ?? "legacy"}`);
|
|
47040
|
+
persistSession({ sessionId, cursor, cwd, ...accountScope });
|
|
45679
47041
|
}
|
|
47042
|
+
if (account && router) safeRouterCall("recordSuccess", () => router.recordSuccess(account.profileId, options?.sessionId));
|
|
45680
47043
|
try {
|
|
45681
47044
|
while (abortCtx.deferredUserMessages.length > 0 && !isReentrant && !wasAborted) {
|
|
45682
|
-
const
|
|
45683
|
-
|
|
45684
|
-
|
|
47045
|
+
const steer = abortCtx.deferredUserMessages.shift();
|
|
47046
|
+
const steerPreview = (steer.text || "[image-only]").slice(0, 60);
|
|
47047
|
+
debug(`provider: replaying deferred user message: ${steerPreview}`);
|
|
47048
|
+
abortCtx.resetTurnState(queryModel);
|
|
45685
47049
|
abortCtx.resetToolTracking();
|
|
45686
|
-
const resumeId =
|
|
47050
|
+
const resumeId = foreignContext ? capturedSessionId : getSharedSession()?.sessionId;
|
|
45687
47051
|
if (!resumeId) {
|
|
45688
47052
|
debug(`WARNING: no session to resume for deferred message, dropping`);
|
|
47053
|
+
dropDeferredUserMessages("continuation-no-resume-id", steer);
|
|
45689
47054
|
break;
|
|
45690
47055
|
}
|
|
45691
47056
|
const contOptions = { ...queryOptions, resume: resumeId, ...makeCliDebugOptions("continuation") };
|
|
45692
|
-
const contQuery =
|
|
47057
|
+
const contQuery = sdkQueryFactory({ prompt: steer.blocks ? wrapPromptStream(steer.blocks) : steer.text, options: contOptions });
|
|
45693
47058
|
abortCtx.activeQuery = contQuery;
|
|
45694
|
-
debug(`provider: continuation query, model=${
|
|
47059
|
+
debug(`provider: continuation query, model=${queryModel.id}, resume=${resumeId.slice(0, 8)}, account=${account?.label ?? "legacy"}, prompt=${steerPreview}`);
|
|
45695
47060
|
try {
|
|
45696
|
-
const
|
|
45697
|
-
|
|
47061
|
+
const continuation = await consumeQuery(contQuery, abortCtx, customToolNameToPi, queryModel, bridgeConfig, () => wasAborted, account, router);
|
|
47062
|
+
if (continuation.failure) {
|
|
47063
|
+
recordAttemptFailure(continuation.failure);
|
|
47064
|
+
if (!abortCtx.handledTerminalError) surfaceFailure(continuation.failure);
|
|
47065
|
+
if (dropDeferredUserMessages("continuation-failure", steer).length > 0) {
|
|
47066
|
+
markRebuildForThisQuery();
|
|
47067
|
+
}
|
|
47068
|
+
break;
|
|
47069
|
+
}
|
|
47070
|
+
const activeSession2 = getSharedSession();
|
|
47071
|
+
const sid = continuation.capturedSessionId ?? activeSession2?.sessionId;
|
|
45698
47072
|
if (sid) {
|
|
45699
|
-
|
|
47073
|
+
persistSession({ sessionId: sid, cursor: activeSession2?.cursor ?? 0, cwd, ...accountScope });
|
|
45700
47074
|
}
|
|
45701
47075
|
} catch (contError) {
|
|
45702
47076
|
debug(`provider: continuation query error:`, contError);
|
|
47077
|
+
const continuationFailure = {
|
|
47078
|
+
kind: classifyClaudeFailure(contError),
|
|
47079
|
+
message: contError instanceof Error ? contError.message : String(contError)
|
|
47080
|
+
};
|
|
47081
|
+
recordAttemptFailure(continuationFailure);
|
|
47082
|
+
if (!abortCtx.handledTerminalError) surfaceFailure(continuationFailure);
|
|
47083
|
+
if (dropDeferredUserMessages("continuation-error", steer).length > 0) {
|
|
47084
|
+
markRebuildForThisQuery();
|
|
47085
|
+
}
|
|
45703
47086
|
break;
|
|
45704
47087
|
} finally {
|
|
45705
47088
|
contQuery.close();
|
|
@@ -45710,26 +47093,25 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45710
47093
|
}
|
|
45711
47094
|
finalizeCurrentStream(abortCtx.turnOutput?.stopReason, abortCtx);
|
|
45712
47095
|
}).catch((error51) => {
|
|
45713
|
-
debug(`provider: query error, model=${
|
|
45714
|
-
const suppressDuplicateError = abortCtx.handledTerminalError || streamIdleTimedOut;
|
|
45715
|
-
|
|
45716
|
-
|
|
45717
|
-
setSharedSession({ ...sharedSession, needsRebuild: true, forceRotate: true });
|
|
45718
|
-
} else {
|
|
45719
|
-
setSharedSession(null);
|
|
47096
|
+
debug(`provider: query error, model=${queryModel.id}, aborted=${Boolean(options?.signal?.aborted)}, error=`, error51);
|
|
47097
|
+
const suppressDuplicateError = abortCtx.handledTerminalError || streamIdleTimedOut && !retryRequested;
|
|
47098
|
+
if (wasAborted || options?.signal?.aborted) {
|
|
47099
|
+
markRebuildForThisQuery({ forceRotate: true });
|
|
45720
47100
|
}
|
|
45721
|
-
|
|
45722
|
-
|
|
45723
|
-
debug("provider: suppressing duplicate query error after terminal error was already emitted");
|
|
45724
|
-
return;
|
|
47101
|
+
if (dropDeferredUserMessages("query-error").length > 0) {
|
|
47102
|
+
markRebuildForThisQuery();
|
|
45725
47103
|
}
|
|
45726
|
-
if (
|
|
45727
|
-
|
|
45728
|
-
|
|
47104
|
+
if (suppressDuplicateError || retryRequested) {
|
|
47105
|
+
debug("provider: suppressing duplicate query error after terminal handling");
|
|
47106
|
+
return;
|
|
45729
47107
|
}
|
|
45730
|
-
|
|
45731
|
-
|
|
45732
|
-
|
|
47108
|
+
const failure = attemptFailure.failure?.rateLimitInfo ? attemptFailure.failure : {
|
|
47109
|
+
kind: classifyClaudeFailure(error51),
|
|
47110
|
+
message: error51 instanceof Error ? error51.message : String(error51)
|
|
47111
|
+
};
|
|
47112
|
+
if (requestRotation(failure)) return;
|
|
47113
|
+
if (!wasAborted && !options?.signal?.aborted) persistSession(null);
|
|
47114
|
+
surfaceFailure(failure, Boolean(options?.signal?.aborted));
|
|
45733
47115
|
}).finally(() => {
|
|
45734
47116
|
streamIdleWatchdog?.dispose();
|
|
45735
47117
|
activeStreamIdleWatchdogs.delete(abortCtx);
|
|
@@ -45737,164 +47119,58 @@ function streamClaudeAgentSdk(model, context, options) {
|
|
|
45737
47119
|
const cause = toolCallDrainCause({ wasAborted, signalAborted: options?.signal?.aborted, streamIdleTimedOut });
|
|
45738
47120
|
teardownQuery(abortCtx, sdkQuery, cause, cwd, isReentrant);
|
|
45739
47121
|
sdkQuery.close();
|
|
45740
|
-
})
|
|
45741
|
-
|
|
45742
|
-
|
|
45743
|
-
|
|
45744
|
-
|
|
45745
|
-
|
|
45746
|
-
|
|
45747
|
-
async function tryOpenExtensionManagerSettings(ctx2) {
|
|
45748
|
-
const host = globalThis;
|
|
45749
|
-
const openQuickSettings = host[/* @__PURE__ */ Symbol.for("vstack.pi.extension-manager.open-quick-settings")];
|
|
45750
|
-
if (typeof openQuickSettings !== "function") return false;
|
|
45751
|
-
try {
|
|
45752
|
-
await openQuickSettings(ctx2, "@vanillagreen/pi-claude-bridge");
|
|
45753
|
-
return true;
|
|
45754
|
-
} catch {
|
|
45755
|
-
return false;
|
|
45756
|
-
}
|
|
45757
|
-
}
|
|
45758
|
-
function showBridgeStatus(ctx2) {
|
|
45759
|
-
const config2 = loadConfig(commandCwd(ctx2));
|
|
45760
|
-
ctx2.ui.notify([
|
|
45761
|
-
`Claude bridge: ${config2.enabled === false ? "disabled" : "enabled"}`,
|
|
45762
|
-
`Extra usage auto-helper: ${extraUsageAllowed(config2) ? "on" : "off"} (settings)`,
|
|
45763
|
-
`Use /claude-bridge:extra to run Claude Code /extra-usage now.`
|
|
45764
|
-
].join("\n"), "info");
|
|
45765
|
-
}
|
|
45766
|
-
function readCredentialFile(path) {
|
|
45767
|
-
try {
|
|
45768
|
-
return nodeReadFileSync(path, "utf8");
|
|
45769
|
-
} catch {
|
|
45770
|
-
return void 0;
|
|
45771
|
-
}
|
|
45772
|
-
}
|
|
45773
|
-
var connectorServerCache = /* @__PURE__ */ new Map();
|
|
45774
|
-
var connectorServerPending = /* @__PURE__ */ new Set();
|
|
45775
|
-
function connectorScopeKey() {
|
|
45776
|
-
return process.env.CLAUDE_CONFIG_DIR?.trim() || "<default>";
|
|
45777
|
-
}
|
|
45778
|
-
function primeConnectorServers() {
|
|
45779
|
-
const key = connectorScopeKey();
|
|
45780
|
-
if (connectorServerCache.has(key) || connectorServerPending.has(key)) return;
|
|
45781
|
-
connectorServerPending.add(key);
|
|
45782
|
-
void (async () => {
|
|
45783
|
-
try {
|
|
45784
|
-
const credentials = resolveClaudeOAuth(readCredentialFile);
|
|
45785
|
-
if (!credentials) {
|
|
45786
|
-
debug("connectors: no OAuth credentials; declaring none");
|
|
45787
|
-
connectorServerCache.set(key, {});
|
|
45788
|
-
return;
|
|
45789
|
-
}
|
|
45790
|
-
const inventory = await listAccountConnectors({ credentials });
|
|
45791
|
-
if (!inventory.ok) {
|
|
45792
|
-
debug(`connectors: inventory failed (${inventory.reason}); declaring none`);
|
|
45793
|
-
connectorServerCache.set(key, {});
|
|
45794
|
-
return;
|
|
45795
|
-
}
|
|
45796
|
-
const servers = connectorMcpServers(inventory);
|
|
45797
|
-
debug(
|
|
45798
|
-
`connectors: declaring ${Object.keys(servers).length} of ${inventory.connectors.length} installed`,
|
|
45799
|
-
Object.keys(servers).join(", ") || "none"
|
|
45800
|
-
);
|
|
45801
|
-
connectorServerCache.set(key, servers);
|
|
45802
|
-
if (writeCachedConnectors(inventory.connectors, key)) {
|
|
45803
|
-
debug(`connectors: cached ${inventory.connectors.length} entries`);
|
|
47122
|
+
}).then(async () => {
|
|
47123
|
+
if (!retryRequested) return;
|
|
47124
|
+
if (wasAborted || options?.signal?.aborted) {
|
|
47125
|
+
debug("provider: abort after queued account retry \u2014 terminating stream without retrying");
|
|
47126
|
+
if (abortCtx.turnOutput) {
|
|
47127
|
+
abortCtx.turnOutput.stopReason = "aborted";
|
|
47128
|
+
abortCtx.turnOutput.errorMessage = "Operation aborted";
|
|
45804
47129
|
}
|
|
45805
|
-
|
|
45806
|
-
|
|
45807
|
-
connectorServerCache.set(key, {});
|
|
45808
|
-
} finally {
|
|
45809
|
-
connectorServerPending.delete(key);
|
|
45810
|
-
}
|
|
45811
|
-
})();
|
|
45812
|
-
}
|
|
45813
|
-
function connectorServersSnapshot() {
|
|
45814
|
-
const key = connectorScopeKey();
|
|
45815
|
-
const ready = connectorServerCache.get(key);
|
|
45816
|
-
if (ready) return ready;
|
|
45817
|
-
primeConnectorServers();
|
|
45818
|
-
const cached2 = readCachedConnectors(key);
|
|
45819
|
-
if (!cached2) return {};
|
|
45820
|
-
const servers = connectorMcpServers({ ok: true, complete: true, connectors: cached2 });
|
|
45821
|
-
if (Object.keys(servers).length === 0) return {};
|
|
45822
|
-
debug(`connectors: turn-1 declarations from cache \u2014 ${Object.keys(servers).join(", ")}`);
|
|
45823
|
-
return servers;
|
|
45824
|
-
}
|
|
45825
|
-
async function reportConnectorInventory(ctx2) {
|
|
45826
|
-
const credentials = resolveClaudeOAuth(readCredentialFile);
|
|
45827
|
-
if (!credentials) {
|
|
45828
|
-
ctx2.ui.notify("Claude bridge: no Claude OAuth credentials found \u2014 cannot enumerate connectors.", "error");
|
|
45829
|
-
return;
|
|
45830
|
-
}
|
|
45831
|
-
const inventory = await listAccountConnectors({ credentials });
|
|
45832
|
-
if (!inventory.ok) {
|
|
45833
|
-
ctx2.ui.notify(`Claude bridge: connector enumeration failed \u2014 ${inventory.reason}`, "error");
|
|
45834
|
-
return;
|
|
45835
|
-
}
|
|
45836
|
-
if (inventory.connectors.length === 0) {
|
|
45837
|
-
ctx2.ui.notify("Claude bridge: this account has no connectors installed.", "info");
|
|
45838
|
-
return;
|
|
45839
|
-
}
|
|
45840
|
-
const names = inventory.connectors.map((c) => c.name).join(", ");
|
|
45841
|
-
ctx2.ui.notify(`Claude bridge: ${inventory.connectors.length} connector(s) installed \u2014 ${names}`, "info");
|
|
45842
|
-
}
|
|
45843
|
-
function registerBridgeCommands(pi) {
|
|
45844
|
-
const guard = pi;
|
|
45845
|
-
if (guard[COMMANDS_REGISTERED_KEY]) return;
|
|
45846
|
-
guard[COMMANDS_REGISTERED_KEY] = true;
|
|
45847
|
-
const runExtraUsage = async (ctx2) => {
|
|
45848
|
-
const cwd = commandCwd(ctx2);
|
|
45849
|
-
if (extraUsageHelperInFlight) {
|
|
45850
|
-
ctx2.ui.notify("Claude extra usage helper already running.", "info");
|
|
45851
|
-
await extraUsageHelperInFlight.catch(() => void 0);
|
|
47130
|
+
stream.push({ type: "error", reason: "aborted", error: abortCtx.turnOutput });
|
|
47131
|
+
stream.end();
|
|
45852
47132
|
return;
|
|
45853
47133
|
}
|
|
45854
|
-
|
|
45855
|
-
|
|
45856
|
-
|
|
45857
|
-
|
|
45858
|
-
|
|
45859
|
-
|
|
45860
|
-
|
|
45861
|
-
|
|
45862
|
-
|
|
45863
|
-
|
|
45864
|
-
|
|
45865
|
-
|
|
45866
|
-
pi.registerCommand("claude-bridge", {
|
|
45867
|
-
description: "Open Claude bridge settings/status",
|
|
45868
|
-
handler: async (args, ctx2) => {
|
|
45869
|
-
if (args.trim()) ctx2.ui.notify("Unknown /claude-bridge argument. Use /claude-bridge:extra to run Claude Code /extra-usage.", "warning");
|
|
45870
|
-
if (await tryOpenExtensionManagerSettings(ctx2)) return;
|
|
45871
|
-
showBridgeStatus(ctx2);
|
|
47134
|
+
debug(`provider: starting account retry after ${retryFailure?.kind ?? "failure"}; excluded=${[...rotationState.excludedProfileIds].join(",")}`);
|
|
47135
|
+
const retryStream = streamClaudeAgentSdk(model, context, {
|
|
47136
|
+
...options ?? {},
|
|
47137
|
+
[ROTATION_STATE_KEY]: rotationState
|
|
47138
|
+
});
|
|
47139
|
+
for await (const event of retryStream) stream.push(event);
|
|
47140
|
+
stream.end();
|
|
47141
|
+
}).catch((error51) => {
|
|
47142
|
+
debug("provider: account retry pipeline failed:", error51);
|
|
47143
|
+
if (abortCtx.turnOutput) {
|
|
47144
|
+
abortCtx.turnOutput.stopReason = "error";
|
|
47145
|
+
abortCtx.turnOutput.errorMessage = error51 instanceof Error ? error51.message : String(error51);
|
|
45872
47146
|
}
|
|
45873
|
-
|
|
45874
|
-
|
|
45875
|
-
|
|
45876
|
-
|
|
45877
|
-
});
|
|
45878
|
-
pi.registerCommand("claude-bridge:connectors", {
|
|
45879
|
-
description: "List the Claude account's installed claude.ai connectors",
|
|
45880
|
-
handler: async (_args, ctx2) => reportConnectorInventory(ctx2)
|
|
45881
|
-
});
|
|
47147
|
+
stream.push({ type: "error", reason: "error", error: abortCtx.turnOutput });
|
|
47148
|
+
stream.end();
|
|
47149
|
+
}).finally(releaseEphemeralLane);
|
|
47150
|
+
return stream;
|
|
45882
47151
|
}
|
|
45883
47152
|
function index_default(pi) {
|
|
45884
47153
|
setExtensionApi(pi);
|
|
45885
47154
|
process.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
|
|
45886
47155
|
const config2 = loadConfig(process.cwd());
|
|
45887
47156
|
debug("loadConfig:", JSON.stringify(config2));
|
|
47157
|
+
registerExternalConfigResolver();
|
|
45888
47158
|
registerBridgeCommands(pi);
|
|
45889
47159
|
if (config2.enabled === false) {
|
|
45890
47160
|
debug("provider: disabled by configuration");
|
|
45891
47161
|
return;
|
|
45892
47162
|
}
|
|
47163
|
+
if (claimPrimaryInstance()) {
|
|
47164
|
+
const host = globalThis;
|
|
47165
|
+
host[CLAUDE_BRIDGE_ACCOUNT_HOST_SYMBOL] = BRIDGE_ACCOUNT_HOST;
|
|
47166
|
+
}
|
|
45893
47167
|
const clearSession = (event) => {
|
|
45894
|
-
|
|
47168
|
+
const activeSession = getSharedSession();
|
|
47169
|
+
debug(`${event}: clearing session ${activeSession?.sessionId?.slice(0, 8) ?? "none"}`);
|
|
45895
47170
|
setSharedSession(null);
|
|
45896
47171
|
};
|
|
45897
|
-
pi.on("session_start", (event, ctx2) => {
|
|
47172
|
+
pi.on("session_start", (event, ctx2) => runInRequestLane(ctx2.sessionManager.getSessionId(), () => {
|
|
47173
|
+
recordStartedLane(ctx2.sessionManager, ctx2.sessionManager.getSessionId());
|
|
45898
47174
|
recordProjectTrust(ctx2);
|
|
45899
47175
|
setPiUI(ctx2.ui);
|
|
45900
47176
|
if (event.reason === "new" || event.reason === "resume" || event.reason === "fork") {
|
|
@@ -45902,31 +47178,40 @@ function index_default(pi) {
|
|
|
45902
47178
|
}
|
|
45903
47179
|
if (event.reason === "startup" || event.reason === "resume") restoreSharedSessionFromPi(ctx2);
|
|
45904
47180
|
applyProviderRegistration(`session_start:${event.reason}`);
|
|
47181
|
+
}));
|
|
47182
|
+
pi.on("session_shutdown", (_event, ctx2) => {
|
|
47183
|
+
const sessionId = takeStartedLane(ctx2.sessionManager) ?? ctx2.sessionManager.getSessionId();
|
|
47184
|
+
runInRequestLane(sessionId, () => {
|
|
47185
|
+
cancelScheduledSessionPersistence(ctx2.sessionManager);
|
|
47186
|
+
clearSession("session_shutdown");
|
|
47187
|
+
releaseProviderTokens("session_shutdown");
|
|
47188
|
+
});
|
|
47189
|
+
deleteSharedSessionLane(sessionId);
|
|
47190
|
+
deleteQueryLane(sessionId);
|
|
45905
47191
|
});
|
|
45906
|
-
pi.on("
|
|
45907
|
-
clearSession("session_shutdown");
|
|
45908
|
-
releaseProviderTokens("session_shutdown");
|
|
45909
|
-
});
|
|
45910
|
-
pi.on("message_end", (event, ctx2) => {
|
|
47192
|
+
pi.on("message_end", (event, ctx2) => runInRequestLane(ctx2.sessionManager.getSessionId(), () => {
|
|
45911
47193
|
const message = event.message;
|
|
45912
47194
|
if (message?.role === "assistant" && message.provider === PROVIDER_ID) schedulePersistSharedSession(ctx2);
|
|
45913
|
-
});
|
|
47195
|
+
}));
|
|
45914
47196
|
const markRebuild = (event) => {
|
|
47197
|
+
const activeSession = getSharedSession();
|
|
45915
47198
|
if (ctx().activeQuery) {
|
|
45916
|
-
reportToolResultMismatch(ctx(), event,
|
|
47199
|
+
reportToolResultMismatch(ctx(), event, activeSession?.cwd ?? process.cwd());
|
|
45917
47200
|
}
|
|
45918
|
-
if (
|
|
45919
|
-
debug(`${event}: marking needsRebuild on session ${
|
|
45920
|
-
|
|
47201
|
+
if (activeSession) {
|
|
47202
|
+
debug(`${event}: marking needsRebuild on session ${activeSession.sessionId.slice(0, 8)}`);
|
|
47203
|
+
markSessionForRebuild();
|
|
45921
47204
|
}
|
|
45922
47205
|
};
|
|
45923
|
-
pi.on("session_compact", () => markRebuild("session_compact"));
|
|
45924
|
-
pi.on("session_tree", () => markRebuild("session_tree"));
|
|
47206
|
+
pi.on("session_compact", (_event, ctx2) => runInRequestLane(ctx2.sessionManager.getSessionId(), () => markRebuild("session_compact")));
|
|
47207
|
+
pi.on("session_tree", (_event, ctx2) => runInRequestLane(ctx2.sessionManager.getSessionId(), () => markRebuild("session_tree")));
|
|
45925
47208
|
applyProviderRegistration("load");
|
|
45926
47209
|
}
|
|
45927
47210
|
export {
|
|
45928
47211
|
ALLOWED_RATE_LIMIT_WARNING_UTILIZATION_THRESHOLD,
|
|
47212
|
+
CLAUDE_ACCOUNT_ROUTER_SYMBOL,
|
|
45929
47213
|
CLAUDE_AI_CONNECTOR_TOOL_PATTERNS,
|
|
47214
|
+
CLAUDE_BRIDGE_ACCOUNT_HOST_SYMBOL,
|
|
45930
47215
|
CLAUDE_BRIDGE_TOOL_ISOLATION,
|
|
45931
47216
|
CONNECTOR_CALL_CUSTOM_TYPE,
|
|
45932
47217
|
CONNECTOR_DISCOVERY_TOOLS,
|
|
@@ -45935,16 +47220,24 @@ export {
|
|
|
45935
47220
|
DISALLOWED_BUILTIN_TOOLS,
|
|
45936
47221
|
INTEGRITY_CUSTOM_TYPE,
|
|
45937
47222
|
NATIVE_PROVIDER_UNSUPPORTED_MESSAGE,
|
|
47223
|
+
RetryEventBuffer,
|
|
45938
47224
|
STREAM_IDLE_BACKOFF_HINT_MS,
|
|
45939
47225
|
STREAM_IDLE_TIMEOUT_ENV,
|
|
45940
47226
|
__testGetBridgeIntegrityState,
|
|
45941
47227
|
__testSetBridgeIntegrityState,
|
|
47228
|
+
__testSetSdkQueryFactory,
|
|
47229
|
+
accountSessionScope,
|
|
45942
47230
|
appendIntegrityEntry,
|
|
45943
47231
|
buildNativeProvider,
|
|
45944
47232
|
buildStreamIdleTimeoutErrorMessage,
|
|
47233
|
+
cancelScheduledSessionPersistence,
|
|
45945
47234
|
cancelScheduledToolUseEnd,
|
|
45946
47235
|
classifyClaudeExecutableBytes,
|
|
47236
|
+
classifyClaudeFailure,
|
|
45947
47237
|
claudeAuthSourceLabel,
|
|
47238
|
+
claudeDirForProfile,
|
|
47239
|
+
commitsVisibleOutput,
|
|
47240
|
+
connectorBuiltinAllowlistHook,
|
|
45948
47241
|
connectorCachePath,
|
|
45949
47242
|
connectorCacheScopeKey,
|
|
45950
47243
|
connectorDeclarationsDisabled,
|
|
@@ -45954,32 +47247,44 @@ export {
|
|
|
45954
47247
|
connectorResultByteSize,
|
|
45955
47248
|
connectorServerName,
|
|
45956
47249
|
connectorServerNamespace,
|
|
47250
|
+
connectorServersSnapshot,
|
|
45957
47251
|
connectorWriteDenyHook,
|
|
45958
47252
|
connectorWriteModeFor,
|
|
45959
47253
|
connectorWriteModeFromEnv,
|
|
45960
47254
|
connectorsEnabledFor,
|
|
45961
47255
|
connectorsEnabledFromEnv,
|
|
45962
47256
|
connectorsListUrl,
|
|
47257
|
+
conversationFingerprint,
|
|
47258
|
+
conversationFingerprintsMatch,
|
|
45963
47259
|
createStreamIdleWatchdog,
|
|
45964
47260
|
credentialCandidatePaths,
|
|
45965
47261
|
index_default as default,
|
|
47262
|
+
denyAllToolsHook,
|
|
45966
47263
|
endToolUseTurn,
|
|
45967
47264
|
finalizeToolUseTurnFromMcpInvocation,
|
|
45968
47265
|
flushConnectorCallAudit,
|
|
45969
47266
|
formatAllowedRateLimitWarning,
|
|
45970
47267
|
formatResetTimestamp,
|
|
47268
|
+
isAllowlistedConnectorSessionTool,
|
|
45971
47269
|
isChildExecutedTool,
|
|
47270
|
+
isChildInternalTool,
|
|
47271
|
+
isConnectorTool,
|
|
45972
47272
|
isConnectorWriteTool,
|
|
45973
|
-
isExtraUsageRequiredMessage,
|
|
45974
47273
|
isUsageLimitMessage,
|
|
45975
47274
|
listAccountConnectors,
|
|
45976
47275
|
mapToolName,
|
|
45977
47276
|
normalizeRateLimitUtilization,
|
|
45978
47277
|
noteChildExecutedToolResults,
|
|
47278
|
+
planDeferredUserReplay,
|
|
47279
|
+
planIncrementalPromptBatch,
|
|
45979
47280
|
preflightClaudeExecutable,
|
|
45980
47281
|
primeConnectorServers,
|
|
47282
|
+
probeClaudeAccountProfile,
|
|
45981
47283
|
processAssistantMessage,
|
|
45982
47284
|
processStreamEvent,
|
|
47285
|
+
rateLimitResetFromInfo,
|
|
47286
|
+
rateLimitResetMs,
|
|
47287
|
+
rateLimitTypeFromInfo,
|
|
45983
47288
|
readCachedConnectors,
|
|
45984
47289
|
reapStaleQueuedResults,
|
|
45985
47290
|
recordConnectorCallResult,
|
|
@@ -45991,10 +47296,14 @@ export {
|
|
|
45991
47296
|
resolveMcpTools,
|
|
45992
47297
|
restoreSharedSessionFromPi,
|
|
45993
47298
|
scheduleToolUseTurnEnd,
|
|
47299
|
+
scopeKeyFor,
|
|
45994
47300
|
setConnectorCallAuditSink,
|
|
47301
|
+
settingSourcesForQuery,
|
|
45995
47302
|
shouldRestorePersistedBridgeEntry,
|
|
45996
47303
|
spawnClaudeCodeWithDiagnostics,
|
|
47304
|
+
streamClaudeAgentSdk,
|
|
45997
47305
|
streamIdleTimeoutMsFromEnv,
|
|
47306
|
+
subscriberProfileEnv,
|
|
45998
47307
|
supportsNativeProvider,
|
|
45999
47308
|
toolIsolationForQuery,
|
|
46000
47309
|
uniqueNonEmptyLines,
|