claudish 9.2.0 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1904 -498
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -715,7 +715,7 @@ var init_onepassword_config = __esm(() => {
|
|
|
715
715
|
});
|
|
716
716
|
|
|
717
717
|
// src/version.ts
|
|
718
|
-
var VERSION = "9.
|
|
718
|
+
var VERSION = "9.3.0";
|
|
719
719
|
|
|
720
720
|
// src/logger.ts
|
|
721
721
|
import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -20992,6 +20992,11 @@ function messageStartUsage(priorInputTokens) {
|
|
|
20992
20992
|
}
|
|
20993
20993
|
|
|
20994
20994
|
// src/handlers/shared/stream-parsers/openai-sse.ts
|
|
20995
|
+
function formatRawSseLogPayload(dataStr) {
|
|
20996
|
+
if (dataStr.length <= SSE_LOG_MAX_CHARS)
|
|
20997
|
+
return dataStr;
|
|
20998
|
+
return `${dataStr.substring(0, SSE_LOG_MAX_CHARS)} ${SSE_LOG_TRUNCATION_MARKER} original_chars=${dataStr.length}`;
|
|
20999
|
+
}
|
|
20995
21000
|
function validateToolArguments(toolName, argsStr, toolSchemas, textContent) {
|
|
20996
21001
|
const result = validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent);
|
|
20997
21002
|
if (result.repaired) {
|
|
@@ -21253,7 +21258,7 @@ data: ${JSON.stringify(d)}
|
|
|
21253
21258
|
if (!line.trim() || !line.startsWith("data: "))
|
|
21254
21259
|
continue;
|
|
21255
21260
|
const dataStr = line.slice(6);
|
|
21256
|
-
log(`[SSE:openai] ${dataStr
|
|
21261
|
+
log(`[SSE:openai] ${formatRawSseLogPayload(dataStr)}`);
|
|
21257
21262
|
if (dataStr === "[DONE]") {
|
|
21258
21263
|
await finalize("done");
|
|
21259
21264
|
return;
|
|
@@ -21539,6 +21544,7 @@ data: ${JSON.stringify(d)}
|
|
|
21539
21544
|
}
|
|
21540
21545
|
});
|
|
21541
21546
|
}
|
|
21547
|
+
var SSE_LOG_MAX_CHARS = 1e6, SSE_LOG_TRUNCATION_MARKER = "<<<CLAUDISH_SSE_TRUNCATED>>>";
|
|
21542
21548
|
var init_openai_sse = __esm(() => {
|
|
21543
21549
|
init_logger();
|
|
21544
21550
|
init_tool_call_recovery();
|
|
@@ -33599,6 +33605,26 @@ data: ${JSON.stringify(data)}
|
|
|
33599
33605
|
}
|
|
33600
33606
|
openToolBlocks.clear();
|
|
33601
33607
|
};
|
|
33608
|
+
const endTurnWithText = (text) => {
|
|
33609
|
+
const idx = curIdx++;
|
|
33610
|
+
send("content_block_start", {
|
|
33611
|
+
type: "content_block_start",
|
|
33612
|
+
index: idx,
|
|
33613
|
+
content_block: { type: "text", text: "" }
|
|
33614
|
+
});
|
|
33615
|
+
send("content_block_delta", {
|
|
33616
|
+
type: "content_block_delta",
|
|
33617
|
+
index: idx,
|
|
33618
|
+
delta: { type: "text_delta", text }
|
|
33619
|
+
});
|
|
33620
|
+
send("content_block_stop", { type: "content_block_stop", index: idx });
|
|
33621
|
+
send("message_delta", {
|
|
33622
|
+
type: "message_delta",
|
|
33623
|
+
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
33624
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens }
|
|
33625
|
+
});
|
|
33626
|
+
send("message_stop", { type: "message_stop" });
|
|
33627
|
+
};
|
|
33602
33628
|
send("message_start", {
|
|
33603
33629
|
type: "message_start",
|
|
33604
33630
|
message: {
|
|
@@ -33637,7 +33663,7 @@ data: ${JSON.stringify(data)}
|
|
|
33637
33663
|
if (data === "[DONE]")
|
|
33638
33664
|
continue;
|
|
33639
33665
|
if (getLogLevel() === "debug") {
|
|
33640
|
-
log(`[SSE:responses] ${data
|
|
33666
|
+
log(`[SSE:responses] ${formatRawSseLogPayload(data)}`);
|
|
33641
33667
|
}
|
|
33642
33668
|
try {
|
|
33643
33669
|
const event = JSON.parse(data);
|
|
@@ -33809,24 +33835,7 @@ data: ${JSON.stringify(data)}
|
|
|
33809
33835
|
|
|
33810
33836
|
[Context limit reached] This model's backend enforces a smaller context window${cap} than its API spec. ` + "Run /clear to start fresh (/compact will also fail \u2014 it re-sends the full conversation), " + `or route the model via \`oai@${opts.modelName}\` to use the full-size window.`;
|
|
33811
33837
|
}
|
|
33812
|
-
|
|
33813
|
-
send("content_block_start", {
|
|
33814
|
-
type: "content_block_start",
|
|
33815
|
-
index: errorIdx,
|
|
33816
|
-
content_block: { type: "text", text: "" }
|
|
33817
|
-
});
|
|
33818
|
-
send("content_block_delta", {
|
|
33819
|
-
type: "content_block_delta",
|
|
33820
|
-
index: errorIdx,
|
|
33821
|
-
delta: { type: "text_delta", text: errorText }
|
|
33822
|
-
});
|
|
33823
|
-
send("content_block_stop", { type: "content_block_stop", index: errorIdx });
|
|
33824
|
-
send("message_delta", {
|
|
33825
|
-
type: "message_delta",
|
|
33826
|
-
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
33827
|
-
usage: { input_tokens: inputTokens, output_tokens: outputTokens }
|
|
33828
|
-
});
|
|
33829
|
-
send("message_stop", { type: "message_stop" });
|
|
33838
|
+
endTurnWithText(errorText);
|
|
33830
33839
|
isClosed = true;
|
|
33831
33840
|
if (pingInterval) {
|
|
33832
33841
|
clearInterval(pingInterval);
|
|
@@ -33877,27 +33886,19 @@ data: ${JSON.stringify(data)}
|
|
|
33877
33886
|
try {
|
|
33878
33887
|
closeReasoning();
|
|
33879
33888
|
closeText();
|
|
33889
|
+
const toolCallCutOff = openToolBlocks.size > 0;
|
|
33880
33890
|
closeTools();
|
|
33881
|
-
|
|
33882
|
-
|
|
33883
|
-
|
|
33884
|
-
|
|
33885
|
-
|
|
33886
|
-
|
|
33887
|
-
|
|
33888
|
-
|
|
33889
|
-
index: errorIdx,
|
|
33890
|
-
delta: { type: "text_delta", text: `
|
|
33891
|
+
if (toolCallCutOff) {
|
|
33892
|
+
log("[ResponsesSSE] tool arguments cut off mid-stream \u2192 error event");
|
|
33893
|
+
send("error", {
|
|
33894
|
+
type: "error",
|
|
33895
|
+
error: { type: "api_error", message: `Stream error: ${error}` }
|
|
33896
|
+
});
|
|
33897
|
+
} else {
|
|
33898
|
+
endTurnWithText(`
|
|
33891
33899
|
|
|
33892
|
-
[Stream error: ${error}]`
|
|
33893
|
-
}
|
|
33894
|
-
send("content_block_stop", { type: "content_block_stop", index: errorIdx });
|
|
33895
|
-
send("message_delta", {
|
|
33896
|
-
type: "message_delta",
|
|
33897
|
-
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
33898
|
-
usage: { input_tokens: inputTokens, output_tokens: outputTokens }
|
|
33899
|
-
});
|
|
33900
|
-
send("message_stop", { type: "message_stop" });
|
|
33900
|
+
[Stream error: ${error}]`);
|
|
33901
|
+
}
|
|
33901
33902
|
} catch {}
|
|
33902
33903
|
isClosed = true;
|
|
33903
33904
|
if (opts.onTokenUpdate)
|
|
@@ -33926,6 +33927,7 @@ var init_openai_responses_sse = __esm(() => {
|
|
|
33926
33927
|
init_reasoning_cache();
|
|
33927
33928
|
init_logger();
|
|
33928
33929
|
init_anthropic_error();
|
|
33930
|
+
init_openai_sse();
|
|
33929
33931
|
});
|
|
33930
33932
|
|
|
33931
33933
|
// src/handlers/shared/token-tracker.ts
|
|
@@ -44817,166 +44819,6 @@ ${text}`;
|
|
|
44817
44819
|
};
|
|
44818
44820
|
});
|
|
44819
44821
|
|
|
44820
|
-
// src/handlers/fallback-handler.ts
|
|
44821
|
-
class FallbackHandler {
|
|
44822
|
-
candidates;
|
|
44823
|
-
lastSuccessIndex = 0;
|
|
44824
|
-
constructor(candidates) {
|
|
44825
|
-
this.candidates = candidates;
|
|
44826
|
-
}
|
|
44827
|
-
async handle(c, payload) {
|
|
44828
|
-
const errors = [];
|
|
44829
|
-
const startIndex = this.lastSuccessIndex;
|
|
44830
|
-
for (let attempt = 0;attempt < this.candidates.length; attempt++) {
|
|
44831
|
-
const idx = (startIndex + attempt) % this.candidates.length;
|
|
44832
|
-
const { name, handler } = this.candidates[idx];
|
|
44833
|
-
const isLast = attempt === this.candidates.length - 1;
|
|
44834
|
-
try {
|
|
44835
|
-
if (errors.length > 0 && handler instanceof ComposedHandler) {
|
|
44836
|
-
try {
|
|
44837
|
-
handler.setFallbackMeta(this.candidates.map((c) => c.name), errors.length);
|
|
44838
|
-
} catch {}
|
|
44839
|
-
}
|
|
44840
|
-
const response = await handler.handle(c, payload);
|
|
44841
|
-
if (response.ok) {
|
|
44842
|
-
this.lastSuccessIndex = idx;
|
|
44843
|
-
if (errors.length > 0) {
|
|
44844
|
-
logStderr(`[Fallback] ${name} succeeded after ${errors.length} failed attempt(s)`);
|
|
44845
|
-
if (handler instanceof ComposedHandler) {
|
|
44846
|
-
handler.getTokenTracker()?.setProviderDisplayName(name);
|
|
44847
|
-
}
|
|
44848
|
-
}
|
|
44849
|
-
return response;
|
|
44850
|
-
}
|
|
44851
|
-
const errorBody = await response.clone().text();
|
|
44852
|
-
if (!isRetryableError(response.status, errorBody, name)) {
|
|
44853
|
-
if (errors.length > 0) {
|
|
44854
|
-
errors.push({ provider: name, status: response.status, message: errorBody });
|
|
44855
|
-
return this.formatCombinedError(c, errors, payload.model);
|
|
44856
|
-
}
|
|
44857
|
-
return response;
|
|
44858
|
-
}
|
|
44859
|
-
errors.push({ provider: name, status: response.status, message: errorBody });
|
|
44860
|
-
if (!isLast) {
|
|
44861
|
-
if (hasQuotaExhaustionWording(errorBody)) {
|
|
44862
|
-
logStderr(`[Fallback] ${name} subscription allowance is spent \u2014 falling through to the next provider, which is billed PER TOKEN. Use a provider prefix (e.g. \`zgo@model\`) to fail instead of switching.`);
|
|
44863
|
-
} else {
|
|
44864
|
-
logStderr(`[Fallback] ${name} failed (HTTP ${response.status}), trying next provider...`);
|
|
44865
|
-
}
|
|
44866
|
-
}
|
|
44867
|
-
} catch (err) {
|
|
44868
|
-
errors.push({ provider: name, status: 0, message: err.message });
|
|
44869
|
-
if (!isLast) {
|
|
44870
|
-
logStderr(`[Fallback] ${name} error: ${err.message}, trying next provider...`);
|
|
44871
|
-
}
|
|
44872
|
-
}
|
|
44873
|
-
}
|
|
44874
|
-
return this.formatCombinedError(c, errors, payload.model);
|
|
44875
|
-
}
|
|
44876
|
-
formatCombinedError(c, errors, modelName) {
|
|
44877
|
-
const summary = errors.map((e) => ` ${e.provider}: HTTP ${e.status || "ERR"} \u2014 ${truncate(parseErrorMessage(e.message), 150)}`).join(`
|
|
44878
|
-
`);
|
|
44879
|
-
logStderr(`[Fallback] All ${errors.length} provider(s) failed for ${modelName || "model"}:
|
|
44880
|
-
${summary}`);
|
|
44881
|
-
return c.json({
|
|
44882
|
-
error: {
|
|
44883
|
-
type: "all_providers_failed",
|
|
44884
|
-
message: `All ${errors.length} providers failed for model '${modelName || "unknown"}'`,
|
|
44885
|
-
attempts: errors.map((e) => ({
|
|
44886
|
-
provider: e.provider,
|
|
44887
|
-
status: e.status,
|
|
44888
|
-
error: truncate(parseErrorMessage(e.message), 200)
|
|
44889
|
-
}))
|
|
44890
|
-
}
|
|
44891
|
-
}, exhaustedChainStatus(errors));
|
|
44892
|
-
}
|
|
44893
|
-
async shutdown() {
|
|
44894
|
-
for (const { handler } of this.candidates) {
|
|
44895
|
-
if (typeof handler.shutdown === "function") {
|
|
44896
|
-
await handler.shutdown();
|
|
44897
|
-
}
|
|
44898
|
-
}
|
|
44899
|
-
}
|
|
44900
|
-
}
|
|
44901
|
-
function isRetryableError(status, errorBody, provider) {
|
|
44902
|
-
if (hasQuotaExhaustionWording(errorBody))
|
|
44903
|
-
return true;
|
|
44904
|
-
const upstream = status === 400 ? extractUpstreamStatus(errorBody) : undefined;
|
|
44905
|
-
if (upstream === 401 || upstream === 403 || upstream === 402 || upstream === 429) {
|
|
44906
|
-
return true;
|
|
44907
|
-
}
|
|
44908
|
-
if (status === 401 || status === 403)
|
|
44909
|
-
return true;
|
|
44910
|
-
if (status === 402)
|
|
44911
|
-
return true;
|
|
44912
|
-
if (status === 404)
|
|
44913
|
-
return true;
|
|
44914
|
-
if (status === 429)
|
|
44915
|
-
return true;
|
|
44916
|
-
const lower = errorBody.toLowerCase();
|
|
44917
|
-
if (status === 422) {
|
|
44918
|
-
if (lower.includes("not available") || lower.includes("model not found") || lower.includes("not supported")) {
|
|
44919
|
-
return true;
|
|
44920
|
-
}
|
|
44921
|
-
}
|
|
44922
|
-
if (status === 400) {
|
|
44923
|
-
if (lower.includes("model not found") || lower.includes("not registered") || lower.includes("does not exist") || lower.includes("unknown model") || lower.includes("unsupported model") || lower.includes("no healthy deployment") || lower.includes("requires a google cloud project") || lower.includes("unsupported_client")) {
|
|
44924
|
-
return true;
|
|
44925
|
-
}
|
|
44926
|
-
if (provider?.toLowerCase().includes("antigravity") && lower.includes("invalid argument")) {
|
|
44927
|
-
return true;
|
|
44928
|
-
}
|
|
44929
|
-
if (isProvider(provider, "opencodezen") && (lower.includes("upstream request failed") || lower.includes("error from provider ("))) {
|
|
44930
|
-
return true;
|
|
44931
|
-
}
|
|
44932
|
-
}
|
|
44933
|
-
if (status === 500) {
|
|
44934
|
-
if (lower.includes("insufficient balance") || lower.includes("insufficient credit") || lower.includes("quota exceeded") || lower.includes("billing")) {
|
|
44935
|
-
return true;
|
|
44936
|
-
}
|
|
44937
|
-
}
|
|
44938
|
-
return false;
|
|
44939
|
-
}
|
|
44940
|
-
function exhaustedChainStatus(errors) {
|
|
44941
|
-
if (errors.length === 0)
|
|
44942
|
-
return 400;
|
|
44943
|
-
const isTransient = (e) => {
|
|
44944
|
-
if (e.status === 429 || e.status === 503)
|
|
44945
|
-
return true;
|
|
44946
|
-
if (hasQuotaExhaustionWording(e.message))
|
|
44947
|
-
return true;
|
|
44948
|
-
const upstream = e.status === 400 ? extractUpstreamStatus(e.message) : undefined;
|
|
44949
|
-
return upstream === 429 || upstream === 503;
|
|
44950
|
-
};
|
|
44951
|
-
return errors.every(isTransient) ? 503 : 400;
|
|
44952
|
-
}
|
|
44953
|
-
function isProvider(provider, needle) {
|
|
44954
|
-
if (!provider)
|
|
44955
|
-
return false;
|
|
44956
|
-
return provider.toLowerCase().replace(/[^a-z0-9]/g, "").includes(needle);
|
|
44957
|
-
}
|
|
44958
|
-
function parseErrorMessage(body) {
|
|
44959
|
-
try {
|
|
44960
|
-
const parsed = JSON.parse(body);
|
|
44961
|
-
if (typeof parsed.error === "string")
|
|
44962
|
-
return parsed.error;
|
|
44963
|
-
if (typeof parsed.error?.message === "string")
|
|
44964
|
-
return parsed.error.message;
|
|
44965
|
-
if (typeof parsed.message === "string")
|
|
44966
|
-
return parsed.message;
|
|
44967
|
-
} catch {}
|
|
44968
|
-
return body;
|
|
44969
|
-
}
|
|
44970
|
-
function truncate(s, max) {
|
|
44971
|
-
return s.length > max ? `${s.slice(0, max)}...` : s;
|
|
44972
|
-
}
|
|
44973
|
-
var init_fallback_handler = __esm(() => {
|
|
44974
|
-
init_logger();
|
|
44975
|
-
init_composed_handler();
|
|
44976
|
-
init_anthropic_error();
|
|
44977
|
-
init_quota_exhaustion();
|
|
44978
|
-
});
|
|
44979
|
-
|
|
44980
44822
|
// src/handlers/native-handler-advisor.ts
|
|
44981
44823
|
import { appendFileSync as appendFileSync7 } from "fs";
|
|
44982
44824
|
function loadAdvisorSwapConfig(cliModels, cliCollector) {
|
|
@@ -45027,17 +44869,65 @@ function stripAdvisorBeta(betaHeader) {
|
|
|
45027
44869
|
changed: true
|
|
45028
44870
|
};
|
|
45029
44871
|
}
|
|
44872
|
+
function scrubSecrets(text) {
|
|
44873
|
+
return text.replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/gi, `Bearer ${REDACTED}`).replace(/\bsk-[A-Za-z0-9_-]{8,}/g, REDACTED).replace(/\bAIza[0-9A-Za-z_-]{20,}/g, REDACTED).replace(/\bxai-[A-Za-z0-9_-]{16,}/g, REDACTED);
|
|
44874
|
+
}
|
|
44875
|
+
function sanitizeAdvisorReason(text, secretValues = []) {
|
|
44876
|
+
let out = text;
|
|
44877
|
+
for (const secret of secretValues) {
|
|
44878
|
+
const value = secret?.trim();
|
|
44879
|
+
if (!value || value.length < MIN_REDACTABLE_SECRET_LENGTH)
|
|
44880
|
+
continue;
|
|
44881
|
+
out = out.split(value).join(REDACTED);
|
|
44882
|
+
}
|
|
44883
|
+
return scrubSecrets(out);
|
|
44884
|
+
}
|
|
44885
|
+
function credentialValuesInHeaders(headers) {
|
|
44886
|
+
const values = [];
|
|
44887
|
+
const bearer = headers.Authorization ?? headers.authorization;
|
|
44888
|
+
if (bearer)
|
|
44889
|
+
values.push(bearer.replace(/^Bearer\s+/i, "").trim());
|
|
44890
|
+
const apiKey = headers["x-api-key"];
|
|
44891
|
+
if (apiKey)
|
|
44892
|
+
values.push(apiKey.trim());
|
|
44893
|
+
return values;
|
|
44894
|
+
}
|
|
44895
|
+
function credentialValuesOf(apiKeys) {
|
|
44896
|
+
return Object.values(apiKeys).filter((v) => typeof v === "string");
|
|
44897
|
+
}
|
|
45030
44898
|
function logAdvisorEvent(cfg, event) {
|
|
44899
|
+
const ts = new Date().toISOString();
|
|
44900
|
+
const record = { ts, ...event };
|
|
44901
|
+
let line;
|
|
44902
|
+
try {
|
|
44903
|
+
line = scrubSecrets(JSON.stringify(record));
|
|
44904
|
+
} catch {
|
|
44905
|
+
return;
|
|
44906
|
+
}
|
|
44907
|
+
if (typeof event.kind === "string" && ORIGIN_RECORD_KINDS.has(event.kind)) {
|
|
44908
|
+
if (getLogFilePath() !== null)
|
|
44909
|
+
log(`${ADVISOR_ORIGIN_LOG_PREFIX} ${line}`);
|
|
44910
|
+
appendLine(getAlwaysOnLogPath(), `[${ts}] ${ADVISOR_ORIGIN_LOG_PREFIX} ${line}`);
|
|
44911
|
+
}
|
|
44912
|
+
appendLine(cfg.logPath, line);
|
|
44913
|
+
}
|
|
44914
|
+
function appendLine(path, line) {
|
|
44915
|
+
if (!path)
|
|
44916
|
+
return;
|
|
44917
|
+
try {
|
|
44918
|
+
appendFileSync7(path, `${line}
|
|
44919
|
+
`);
|
|
44920
|
+
} catch {}
|
|
44921
|
+
}
|
|
44922
|
+
function recordAdvisorEventsFromResponseBody(cfg, body, sessionId) {
|
|
44923
|
+
collectAdvisorIdsFromValue(body, 0, sessionId);
|
|
45031
44924
|
if (!cfg.logPath)
|
|
45032
44925
|
return;
|
|
45033
|
-
const line = `${JSON.stringify({ ts: new Date().toISOString(), ...event })}
|
|
45034
|
-
`;
|
|
45035
44926
|
try {
|
|
45036
|
-
|
|
44927
|
+
logAdvisorMarkers(cfg, JSON.stringify(body));
|
|
45037
44928
|
} catch {}
|
|
45038
44929
|
}
|
|
45039
|
-
function
|
|
45040
|
-
extractAdvisorToolUseIds(chunkText);
|
|
44930
|
+
function logAdvisorMarkers(cfg, text) {
|
|
45041
44931
|
if (!cfg.logPath)
|
|
45042
44932
|
return;
|
|
45043
44933
|
const markers = [
|
|
@@ -45051,37 +44941,275 @@ function recordAdvisorEventsFromChunk(cfg, chunkText) {
|
|
|
45051
44941
|
for (const [needle, kind] of markers) {
|
|
45052
44942
|
let i = 0;
|
|
45053
44943
|
while (true) {
|
|
45054
|
-
i =
|
|
44944
|
+
i = text.indexOf(needle, i);
|
|
45055
44945
|
if (i < 0)
|
|
45056
44946
|
break;
|
|
45057
|
-
const ctx =
|
|
44947
|
+
const ctx = text.slice(Math.max(0, i - 40), i + 160);
|
|
45058
44948
|
logAdvisorEvent(cfg, { kind, needle, ctx });
|
|
45059
44949
|
i += needle.length;
|
|
45060
44950
|
}
|
|
45061
44951
|
}
|
|
45062
44952
|
}
|
|
45063
|
-
function
|
|
45064
|
-
|
|
45065
|
-
|
|
45066
|
-
|
|
45067
|
-
|
|
44953
|
+
function warnAdvisor(message) {
|
|
44954
|
+
logStderr(`[advisor] ${message}`);
|
|
44955
|
+
}
|
|
44956
|
+
function sessionKeyFor(sessionId) {
|
|
44957
|
+
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : NO_SESSION_BUCKET;
|
|
44958
|
+
}
|
|
44959
|
+
function isCallInFlight(toolUseId) {
|
|
44960
|
+
return (inFlightByToolUseId.get(toolUseId) ?? 0) > 0;
|
|
44961
|
+
}
|
|
44962
|
+
function bucketHasInFlightCall(bucket) {
|
|
44963
|
+
for (const id of bucket.keys())
|
|
44964
|
+
if (isCallInFlight(id))
|
|
44965
|
+
return true;
|
|
44966
|
+
return false;
|
|
44967
|
+
}
|
|
44968
|
+
function isExpired(call, now) {
|
|
44969
|
+
if (isCallInFlight(call.toolUseId))
|
|
44970
|
+
return false;
|
|
44971
|
+
return now - call.lastSeenAt > ADVISOR_PENDING_LIMITS.ttlMs;
|
|
44972
|
+
}
|
|
44973
|
+
function sweepExpiredIfDue() {
|
|
44974
|
+
const now = clock();
|
|
44975
|
+
if (now - lastSweepAt < SWEEP_INTERVAL_MS)
|
|
44976
|
+
return;
|
|
44977
|
+
lastSweepAt = now;
|
|
44978
|
+
for (const [key, bucket] of pendingBySession) {
|
|
44979
|
+
for (const [id, call] of bucket) {
|
|
44980
|
+
if (isExpired(call, now))
|
|
44981
|
+
bucket.delete(id);
|
|
44982
|
+
}
|
|
44983
|
+
if (bucket.size === 0)
|
|
44984
|
+
pendingBySession.delete(key);
|
|
44985
|
+
}
|
|
44986
|
+
}
|
|
44987
|
+
function touchBucket(key) {
|
|
44988
|
+
const bucket = pendingBySession.get(key);
|
|
44989
|
+
if (!bucket)
|
|
44990
|
+
return;
|
|
44991
|
+
pendingBySession.delete(key);
|
|
44992
|
+
pendingBySession.set(key, bucket);
|
|
44993
|
+
return bucket;
|
|
44994
|
+
}
|
|
44995
|
+
function bucketForWrite(key) {
|
|
44996
|
+
const existing = touchBucket(key);
|
|
44997
|
+
if (existing)
|
|
44998
|
+
return existing;
|
|
44999
|
+
const bucket = new Map;
|
|
45000
|
+
pendingBySession.set(key, bucket);
|
|
45001
|
+
while (pendingBySession.size > ADVISOR_PENDING_LIMITS.maxSessions) {
|
|
45002
|
+
const evictable = [...pendingBySession].find(([k, b]) => k !== key && !bucketHasInFlightCall(b));
|
|
45003
|
+
if (!evictable)
|
|
45004
|
+
break;
|
|
45005
|
+
pendingBySession.delete(evictable[0]);
|
|
45006
|
+
}
|
|
45007
|
+
return bucket;
|
|
45008
|
+
}
|
|
45009
|
+
function putCall(bucket, call) {
|
|
45010
|
+
bucket.delete(call.toolUseId);
|
|
45011
|
+
if (bucket.size >= ADVISOR_PENDING_LIMITS.maxCallsPerSession) {
|
|
45012
|
+
const oldest = [...bucket.keys()].find((id) => !isCallInFlight(id));
|
|
45013
|
+
if (oldest !== undefined)
|
|
45014
|
+
bucket.delete(oldest);
|
|
45015
|
+
}
|
|
45016
|
+
bucket.set(call.toolUseId, call);
|
|
45017
|
+
}
|
|
45018
|
+
function rememberAdvisorToolUseId(id, sessionId) {
|
|
45019
|
+
sweepExpiredIfDue();
|
|
45020
|
+
const key = sessionKeyFor(sessionId);
|
|
45021
|
+
const bucket = bucketForWrite(key);
|
|
45022
|
+
const now = clock();
|
|
45023
|
+
const existing = bucket.get(id);
|
|
45024
|
+
if (existing) {
|
|
45025
|
+
existing.lastSeenAt = now;
|
|
45026
|
+
putCall(bucket, existing);
|
|
45027
|
+
return;
|
|
45028
|
+
}
|
|
45029
|
+
putCall(bucket, { toolUseId: id, sessionKey: key, recordedAt: now, lastSeenAt: now });
|
|
45030
|
+
log(`[advisor] recorded advisor tool_use ${id} (session=${key})`);
|
|
45031
|
+
}
|
|
45032
|
+
function lookupAdvisorCall(toolUseId, sessionId) {
|
|
45033
|
+
sweepExpiredIfDue();
|
|
45034
|
+
const now = clock();
|
|
45035
|
+
const own = sessionKeyFor(sessionId);
|
|
45036
|
+
const keys = own === NO_SESSION_BUCKET ? [NO_SESSION_BUCKET] : [own, NO_SESSION_BUCKET];
|
|
45037
|
+
for (const key of keys) {
|
|
45038
|
+
const bucket = pendingBySession.get(key);
|
|
45039
|
+
const call = bucket?.get(toolUseId);
|
|
45040
|
+
if (!bucket || !call)
|
|
45041
|
+
continue;
|
|
45042
|
+
if (isExpired(call, now)) {
|
|
45043
|
+
bucket.delete(toolUseId);
|
|
45044
|
+
continue;
|
|
45045
|
+
}
|
|
45046
|
+
call.lastSeenAt = now;
|
|
45047
|
+
putCall(bucket, call);
|
|
45048
|
+
touchBucket(key);
|
|
45049
|
+
return call;
|
|
45050
|
+
}
|
|
45051
|
+
return;
|
|
45052
|
+
}
|
|
45053
|
+
function getAdvisorCall(toolUseId, sessionId) {
|
|
45054
|
+
return lookupAdvisorCall(toolUseId, sessionId);
|
|
45055
|
+
}
|
|
45056
|
+
function markAdvisorCallConsumed(toolUseId, result, sessionId) {
|
|
45057
|
+
const call = lookupAdvisorCall(toolUseId, sessionId);
|
|
45058
|
+
if (!call)
|
|
45059
|
+
return false;
|
|
45060
|
+
const own = sessionKeyFor(sessionId);
|
|
45061
|
+
if (call.sessionKey === NO_SESSION_BUCKET && own !== NO_SESSION_BUCKET) {
|
|
45062
|
+
pendingBySession.get(NO_SESSION_BUCKET)?.delete(toolUseId);
|
|
45063
|
+
call.sessionKey = own;
|
|
45064
|
+
putCall(bucketForWrite(own), call);
|
|
45065
|
+
}
|
|
45066
|
+
call.consumedAt ??= clock();
|
|
45067
|
+
call.result = result;
|
|
45068
|
+
return true;
|
|
45069
|
+
}
|
|
45070
|
+
function joinOrStartAdvisorCall(toolUseId, sessionId, start) {
|
|
45071
|
+
const key = `${sessionKeyFor(sessionId)}\x00${toolUseId}`;
|
|
45072
|
+
const existing = inFlightAdvisorCalls.get(key);
|
|
45073
|
+
if (existing)
|
|
45074
|
+
return { promise: existing, joined: true };
|
|
45075
|
+
while (inFlightAdvisorCalls.size >= MAX_IN_FLIGHT_ADVISOR_CALLS) {
|
|
45076
|
+
const oldest = inFlightAdvisorCalls.keys().next().value;
|
|
45077
|
+
if (oldest === undefined)
|
|
45078
|
+
break;
|
|
45079
|
+
inFlightAdvisorCalls.delete(oldest);
|
|
45080
|
+
}
|
|
45081
|
+
inFlightByToolUseId.set(toolUseId, (inFlightByToolUseId.get(toolUseId) ?? 0) + 1);
|
|
45082
|
+
const release = () => {
|
|
45083
|
+
const left = (inFlightByToolUseId.get(toolUseId) ?? 1) - 1;
|
|
45084
|
+
if (left > 0)
|
|
45085
|
+
inFlightByToolUseId.set(toolUseId, left);
|
|
45086
|
+
else
|
|
45087
|
+
inFlightByToolUseId.delete(toolUseId);
|
|
45088
|
+
};
|
|
45089
|
+
let started;
|
|
45090
|
+
try {
|
|
45091
|
+
started = start();
|
|
45092
|
+
} catch (err) {
|
|
45093
|
+
release();
|
|
45094
|
+
throw err;
|
|
45095
|
+
}
|
|
45096
|
+
const promise = started.finally(() => {
|
|
45097
|
+
release();
|
|
45098
|
+
if (inFlightAdvisorCalls.get(key) === promise)
|
|
45099
|
+
inFlightAdvisorCalls.delete(key);
|
|
45100
|
+
});
|
|
45101
|
+
inFlightAdvisorCalls.set(key, promise);
|
|
45102
|
+
return { promise, joined: false };
|
|
45103
|
+
}
|
|
45104
|
+
|
|
45105
|
+
class SseFrameBuffer {
|
|
45106
|
+
buf = "";
|
|
45107
|
+
take(chunkText) {
|
|
45108
|
+
this.buf += chunkText;
|
|
45109
|
+
const events = [];
|
|
45110
|
+
while (true) {
|
|
45111
|
+
const lf = this.buf.indexOf(`
|
|
45112
|
+
|
|
45113
|
+
`);
|
|
45114
|
+
const crlf = this.buf.indexOf(`\r
|
|
45115
|
+
\r
|
|
45116
|
+
`);
|
|
45117
|
+
let idx = -1;
|
|
45118
|
+
let width = 2;
|
|
45119
|
+
if (crlf >= 0 && (lf < 0 || crlf < lf)) {
|
|
45120
|
+
idx = crlf;
|
|
45121
|
+
width = 4;
|
|
45122
|
+
} else if (lf >= 0) {
|
|
45123
|
+
idx = lf;
|
|
45124
|
+
}
|
|
45125
|
+
if (idx < 0)
|
|
45126
|
+
break;
|
|
45127
|
+
events.push(this.buf.slice(0, idx));
|
|
45128
|
+
this.buf = this.buf.slice(idx + width);
|
|
45129
|
+
}
|
|
45130
|
+
if (this.buf.length > MAX_SSE_BUFFER_CHARS) {
|
|
45131
|
+
this.buf = this.buf.slice(-MAX_SSE_BUFFER_CHARS);
|
|
45132
|
+
}
|
|
45133
|
+
return events;
|
|
45068
45134
|
}
|
|
45069
|
-
|
|
45070
|
-
|
|
45071
|
-
rememberAdvisorToolUseId(m[1]);
|
|
45135
|
+
reset() {
|
|
45136
|
+
this.buf = "";
|
|
45072
45137
|
}
|
|
45073
45138
|
}
|
|
45074
|
-
function
|
|
45075
|
-
|
|
45139
|
+
function createAdvisorStreamScanner(cfg, sessionId) {
|
|
45140
|
+
const frames = new SseFrameBuffer;
|
|
45141
|
+
return {
|
|
45142
|
+
push(chunkText) {
|
|
45143
|
+
extractAdvisorToolUseIds(chunkText, frames, sessionId);
|
|
45144
|
+
logAdvisorMarkers(cfg, chunkText);
|
|
45145
|
+
}
|
|
45146
|
+
};
|
|
45147
|
+
}
|
|
45148
|
+
function extractAdvisorToolUseIds(chunkText, frames, sessionId) {
|
|
45149
|
+
if (parseAdvisorIdsFromJsonText(chunkText, sessionId))
|
|
45076
45150
|
return;
|
|
45077
|
-
|
|
45078
|
-
|
|
45079
|
-
|
|
45080
|
-
|
|
45151
|
+
let sawCompleteFrame = false;
|
|
45152
|
+
for (const event of frames.take(chunkText)) {
|
|
45153
|
+
sawCompleteFrame = true;
|
|
45154
|
+
if (!scanSseEventForAdvisorIds(event, sessionId))
|
|
45155
|
+
matchAdvisorIdsByRegex(event, sessionId);
|
|
45156
|
+
}
|
|
45157
|
+
if (!sawCompleteFrame)
|
|
45158
|
+
matchAdvisorIdsByRegex(chunkText, sessionId);
|
|
45159
|
+
}
|
|
45160
|
+
function scanSseEventForAdvisorIds(rawEvent, sessionId) {
|
|
45161
|
+
const dataLines = [];
|
|
45162
|
+
for (const line of rawEvent.split(/\r?\n/)) {
|
|
45163
|
+
if (!line.startsWith("data:"))
|
|
45164
|
+
continue;
|
|
45165
|
+
dataLines.push(line.slice(5).trimStart());
|
|
45166
|
+
}
|
|
45167
|
+
if (dataLines.length === 0)
|
|
45168
|
+
return false;
|
|
45169
|
+
const payload = dataLines.join(`
|
|
45170
|
+
`).trim();
|
|
45171
|
+
if (!payload || payload === "[DONE]")
|
|
45172
|
+
return true;
|
|
45173
|
+
return parseAdvisorIdsFromJsonText(payload, sessionId);
|
|
45174
|
+
}
|
|
45175
|
+
function parseAdvisorIdsFromJsonText(text, sessionId) {
|
|
45176
|
+
const trimmed = text.trimStart();
|
|
45177
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
|
|
45178
|
+
return false;
|
|
45179
|
+
let parsed;
|
|
45180
|
+
try {
|
|
45181
|
+
parsed = JSON.parse(trimmed);
|
|
45182
|
+
} catch {
|
|
45183
|
+
return false;
|
|
45184
|
+
}
|
|
45185
|
+
collectAdvisorIdsFromValue(parsed, 0, sessionId);
|
|
45186
|
+
return true;
|
|
45187
|
+
}
|
|
45188
|
+
function collectAdvisorIdsFromValue(value, depth, sessionId) {
|
|
45189
|
+
if (value === null || typeof value !== "object" || depth > MAX_WALK_DEPTH)
|
|
45190
|
+
return;
|
|
45191
|
+
if (Array.isArray(value)) {
|
|
45192
|
+
for (const item of value)
|
|
45193
|
+
collectAdvisorIdsFromValue(item, depth + 1, sessionId);
|
|
45194
|
+
return;
|
|
45195
|
+
}
|
|
45196
|
+
const obj = value;
|
|
45197
|
+
if (obj.type === "tool_use" && obj.name === ADVISOR_TOOL_NAME && typeof obj.id === "string" && obj.id.length > 0) {
|
|
45198
|
+
rememberAdvisorToolUseId(obj.id, sessionId);
|
|
45199
|
+
}
|
|
45200
|
+
for (const nested of Object.values(obj))
|
|
45201
|
+
collectAdvisorIdsFromValue(nested, depth + 1, sessionId);
|
|
45202
|
+
}
|
|
45203
|
+
function matchAdvisorIdsByRegex(text, sessionId) {
|
|
45204
|
+
for (const re of ADVISOR_ID_PATTERNS) {
|
|
45205
|
+
re.lastIndex = 0;
|
|
45206
|
+
let m;
|
|
45207
|
+
while ((m = re.exec(text)) !== null) {
|
|
45208
|
+
rememberAdvisorToolUseId(m[1], sessionId);
|
|
45209
|
+
}
|
|
45081
45210
|
}
|
|
45082
|
-
advisorToolUseIds.add(id);
|
|
45083
45211
|
}
|
|
45084
|
-
function rewriteAdvisorToolResults(payload, getAdviceFor) {
|
|
45212
|
+
function rewriteAdvisorToolResults(payload, getAdviceFor, sessionId) {
|
|
45085
45213
|
const messages = payload.messages;
|
|
45086
45214
|
if (!Array.isArray(messages))
|
|
45087
45215
|
return [];
|
|
@@ -45102,12 +45230,18 @@ function rewriteAdvisorToolResults(payload, getAdviceFor) {
|
|
|
45102
45230
|
const toolUseId = block.tool_use_id;
|
|
45103
45231
|
if (typeof toolUseId !== "string")
|
|
45104
45232
|
continue;
|
|
45105
|
-
if (!
|
|
45233
|
+
if (!lookupAdvisorCall(toolUseId, sessionId))
|
|
45106
45234
|
continue;
|
|
45107
|
-
const
|
|
45108
|
-
|
|
45109
|
-
|
|
45235
|
+
const supplied = getAdviceFor(toolUseId);
|
|
45236
|
+
if (supplied === undefined)
|
|
45237
|
+
continue;
|
|
45238
|
+
const result = typeof supplied === "string" ? { text: supplied, isError: false } : supplied;
|
|
45239
|
+
block.content = [{ type: "text", text: result.text }];
|
|
45240
|
+
if (result.isError) {
|
|
45241
|
+
block.is_error = true;
|
|
45242
|
+
} else if (block.is_error) {
|
|
45110
45243
|
block.is_error = false;
|
|
45244
|
+
}
|
|
45111
45245
|
rewritten.push(toolUseId);
|
|
45112
45246
|
}
|
|
45113
45247
|
}
|
|
@@ -45116,7 +45250,34 @@ function rewriteAdvisorToolResults(payload, getAdviceFor) {
|
|
|
45116
45250
|
function stubAdvisorAdvice(toolUseId) {
|
|
45117
45251
|
return `CLAUDISH_ADVISOR_STUB_${toolUseId}: Evaluation mode \u2014 this advice was supplied by a claudish proxy stub. For the rate-limiter design, consider a hybrid: local token bucket per node for burst tolerance plus a central quota coordinator for cross-region fairness. Use the CAP tradeoff as your framing; expose availability vs accuracy knobs per tenant. The single most important decision is your failure mode: fail-open vs fail-closed.`;
|
|
45118
45252
|
}
|
|
45119
|
-
function
|
|
45253
|
+
function prepareLegacyStubResult(cfg, toolUseId, sessionId) {
|
|
45254
|
+
const result = { text: stubAdvisorAdvice(toolUseId), isError: false };
|
|
45255
|
+
markAdvisorCallConsumed(toolUseId, result, sessionId);
|
|
45256
|
+
logAdvisorEvent(cfg, {
|
|
45257
|
+
kind: "advisor_rewrite",
|
|
45258
|
+
event: "advisor_rewrite",
|
|
45259
|
+
toolUseId,
|
|
45260
|
+
sessionId: sessionId ?? null,
|
|
45261
|
+
panel: [],
|
|
45262
|
+
originsByModel: {},
|
|
45263
|
+
failedModels: [],
|
|
45264
|
+
collector: null,
|
|
45265
|
+
collectorOrigin: null,
|
|
45266
|
+
resultOrigin: "stub",
|
|
45267
|
+
stubPath: ADVISOR_STUB_PATHS.LEGACY_STUB,
|
|
45268
|
+
isError: false
|
|
45269
|
+
});
|
|
45270
|
+
return result;
|
|
45271
|
+
}
|
|
45272
|
+
function missingAdvisorResult(toolUseId) {
|
|
45273
|
+
log(`[advisor] no prepared result for recorded call ${toolUseId} (stub path S2)`);
|
|
45274
|
+
warnAdvisor(`advisor call ${toolUseId}: claudish had no prepared result (internal error)`);
|
|
45275
|
+
return {
|
|
45276
|
+
text: `${ADVISOR_ERROR_PREFIX} claudish recorded advisor call ${toolUseId} but had no result prepared for it (internal error). ${ADVISOR_ERROR_SUFFIX}`,
|
|
45277
|
+
isError: true
|
|
45278
|
+
};
|
|
45279
|
+
}
|
|
45280
|
+
function findPendingAdvisorToolResults(payload, sessionId) {
|
|
45120
45281
|
const messages = payload.messages;
|
|
45121
45282
|
if (!Array.isArray(messages))
|
|
45122
45283
|
return [];
|
|
@@ -45135,13 +45296,101 @@ function findPendingAdvisorToolResults(payload) {
|
|
|
45135
45296
|
if (block.type !== "tool_result")
|
|
45136
45297
|
continue;
|
|
45137
45298
|
const toolUseId = block.tool_use_id;
|
|
45138
|
-
if (typeof toolUseId === "string" &&
|
|
45299
|
+
if (typeof toolUseId === "string" && lookupAdvisorCall(toolUseId, sessionId)) {
|
|
45139
45300
|
found.push(toolUseId);
|
|
45140
45301
|
}
|
|
45141
45302
|
}
|
|
45142
45303
|
}
|
|
45143
45304
|
return found;
|
|
45144
45305
|
}
|
|
45306
|
+
function toolResultText(content) {
|
|
45307
|
+
if (typeof content === "string")
|
|
45308
|
+
return content;
|
|
45309
|
+
if (!Array.isArray(content))
|
|
45310
|
+
return "";
|
|
45311
|
+
return content.map((b) => b && typeof b.text === "string" ? b.text : "").filter(Boolean).join(`
|
|
45312
|
+
`);
|
|
45313
|
+
}
|
|
45314
|
+
function advisorToolUseIdsInPayload(payload) {
|
|
45315
|
+
const ids = new Set;
|
|
45316
|
+
const messages = payload.messages;
|
|
45317
|
+
if (!Array.isArray(messages))
|
|
45318
|
+
return ids;
|
|
45319
|
+
for (const msg of messages) {
|
|
45320
|
+
if (!msg || typeof msg !== "object")
|
|
45321
|
+
continue;
|
|
45322
|
+
const content = msg.content;
|
|
45323
|
+
if (!Array.isArray(content))
|
|
45324
|
+
continue;
|
|
45325
|
+
for (const block of content) {
|
|
45326
|
+
if (!block || typeof block !== "object")
|
|
45327
|
+
continue;
|
|
45328
|
+
const b = block;
|
|
45329
|
+
if (b.type !== "tool_use" || b.name !== ADVISOR_TOOL_NAME)
|
|
45330
|
+
continue;
|
|
45331
|
+
if (typeof b.id === "string" && b.id.length > 0)
|
|
45332
|
+
ids.add(b.id);
|
|
45333
|
+
}
|
|
45334
|
+
}
|
|
45335
|
+
return ids;
|
|
45336
|
+
}
|
|
45337
|
+
function reportUnrecordedAdvisorCalls(cfg, payload, sessionId) {
|
|
45338
|
+
const messages = payload.messages;
|
|
45339
|
+
if (!Array.isArray(messages))
|
|
45340
|
+
return [];
|
|
45341
|
+
const advisorIds = advisorToolUseIdsInPayload(payload);
|
|
45342
|
+
if (advisorIds.size === 0)
|
|
45343
|
+
return [];
|
|
45344
|
+
const reported = [];
|
|
45345
|
+
for (const msg of messages) {
|
|
45346
|
+
if (!msg || typeof msg !== "object" || msg.role !== "user")
|
|
45347
|
+
continue;
|
|
45348
|
+
const content = msg.content;
|
|
45349
|
+
if (!Array.isArray(content))
|
|
45350
|
+
continue;
|
|
45351
|
+
for (const block of content) {
|
|
45352
|
+
if (!block || typeof block !== "object" || block.type !== "tool_result")
|
|
45353
|
+
continue;
|
|
45354
|
+
const toolUseId = block.tool_use_id;
|
|
45355
|
+
if (typeof toolUseId !== "string")
|
|
45356
|
+
continue;
|
|
45357
|
+
if (!advisorIds.has(toolUseId))
|
|
45358
|
+
continue;
|
|
45359
|
+
if (!NO_SUCH_ADVISOR_TOOL.test(toolResultText(block.content)))
|
|
45360
|
+
continue;
|
|
45361
|
+
if (lookupAdvisorCall(toolUseId, sessionId))
|
|
45362
|
+
continue;
|
|
45363
|
+
const memo = `${sessionKeyFor(sessionId)}\x00${toolUseId}`;
|
|
45364
|
+
if (reportedUnrecorded.has(memo))
|
|
45365
|
+
continue;
|
|
45366
|
+
if (reportedUnrecorded.size >= MAX_REPORTED_UNRECORDED) {
|
|
45367
|
+
const oldest = reportedUnrecorded.values().next().value;
|
|
45368
|
+
if (oldest !== undefined)
|
|
45369
|
+
reportedUnrecorded.delete(oldest);
|
|
45370
|
+
}
|
|
45371
|
+
reportedUnrecorded.add(memo);
|
|
45372
|
+
reported.push(toolUseId);
|
|
45373
|
+
const panel = cfg.models ?? [];
|
|
45374
|
+
logAdvisorEvent(cfg, {
|
|
45375
|
+
kind: "advisor_rewrite",
|
|
45376
|
+
event: "advisor_rewrite",
|
|
45377
|
+
toolUseId,
|
|
45378
|
+
sessionId: sessionId ?? null,
|
|
45379
|
+
panel,
|
|
45380
|
+
originsByModel: Object.fromEntries(panel.map((m) => [m, "absent"])),
|
|
45381
|
+
failedModels: [],
|
|
45382
|
+
collector: cfg.collector ?? null,
|
|
45383
|
+
collectorOrigin: cfg.collector ? "absent" : null,
|
|
45384
|
+
resultOrigin: "absent",
|
|
45385
|
+
stubPath: ADVISOR_STUB_PATHS.NOT_REWRITTEN,
|
|
45386
|
+
isError: true
|
|
45387
|
+
});
|
|
45388
|
+
log(`[advisor] call ${toolUseId} was never rewritten (stub path S10)`);
|
|
45389
|
+
warnAdvisor(`advisor call ${toolUseId} was not answered: claudish never saw the advisor tool call, so the model got "No such tool available: advisor"`);
|
|
45390
|
+
}
|
|
45391
|
+
}
|
|
45392
|
+
return reported;
|
|
45393
|
+
}
|
|
45145
45394
|
function convertToOpenAIMessages(anthropicMessages) {
|
|
45146
45395
|
return anthropicMessages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({
|
|
45147
45396
|
role: m.role,
|
|
@@ -45169,168 +45418,598 @@ function extractBlocksAsText(content) {
|
|
|
45169
45418
|
}).filter(Boolean).join(`
|
|
45170
45419
|
`);
|
|
45171
45420
|
}
|
|
45172
|
-
function
|
|
45173
|
-
const
|
|
45174
|
-
|
|
45175
|
-
|
|
45176
|
-
|
|
45177
|
-
|
|
45178
|
-
|
|
45179
|
-
|
|
45180
|
-
|
|
45181
|
-
|
|
45182
|
-
|
|
45183
|
-
model: parsed.model,
|
|
45184
|
-
max_tokens: 2048,
|
|
45185
|
-
messages: [{ role: "system", content: systemPrompt }, ...openaiMessages]
|
|
45186
|
-
}
|
|
45187
|
-
};
|
|
45421
|
+
function describeAdvisorQuestion(input) {
|
|
45422
|
+
const clip = (s) => s.length > MAX_ADVISOR_QUESTION_CHARS ? `${s.slice(0, MAX_ADVISOR_QUESTION_CHARS)}\u2026` : s || null;
|
|
45423
|
+
if (typeof input === "string")
|
|
45424
|
+
return clip(input.trim());
|
|
45425
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
45426
|
+
return null;
|
|
45427
|
+
const obj = input;
|
|
45428
|
+
for (const field of ADVISOR_QUESTION_FIELDS) {
|
|
45429
|
+
const value = obj[field];
|
|
45430
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
45431
|
+
return clip(value.trim());
|
|
45188
45432
|
}
|
|
45189
|
-
if (
|
|
45190
|
-
return
|
|
45191
|
-
|
|
45192
|
-
|
|
45193
|
-
|
|
45194
|
-
|
|
45195
|
-
|
|
45196
|
-
|
|
45197
|
-
|
|
45198
|
-
|
|
45199
|
-
|
|
45433
|
+
if (Object.keys(obj).length === 0)
|
|
45434
|
+
return null;
|
|
45435
|
+
try {
|
|
45436
|
+
return clip(JSON.stringify(obj));
|
|
45437
|
+
} catch {
|
|
45438
|
+
return null;
|
|
45439
|
+
}
|
|
45440
|
+
}
|
|
45441
|
+
function advisorQuestionMessage(question) {
|
|
45442
|
+
const body = question === null ? "The assistant named no explicit question, so its question is the conversation itself: " + "advise it on the decision it now faces, the risks in the approach it has taken, and what " + "it should do next." : `The assistant's question is:
|
|
45443
|
+
|
|
45444
|
+
${question}`;
|
|
45445
|
+
return "[claudish] The coding assistant paused the work above and consulted its advisor. " + `You are the advisor.
|
|
45446
|
+
|
|
45447
|
+
` + `${body}
|
|
45448
|
+
|
|
45449
|
+
` + "Answer that question, using the conversation above as context. The advisor tool is provided " + "by the claudish proxy and has no implementation inside the assistant's harness, so any tool " + "error about it is plumbing: do not treat it as the subject, and do not comment on how the " + "call was made.";
|
|
45450
|
+
}
|
|
45451
|
+
function prepareAdvisorPanelMessages(messages, toolUseId) {
|
|
45452
|
+
if (!Array.isArray(messages))
|
|
45453
|
+
return [];
|
|
45454
|
+
let question = null;
|
|
45455
|
+
const prepared = messages.map((msg) => {
|
|
45456
|
+
if (!msg || typeof msg !== "object" || !Array.isArray(msg.content))
|
|
45457
|
+
return msg;
|
|
45458
|
+
let changed = false;
|
|
45459
|
+
const content = msg.content.map((block) => {
|
|
45460
|
+
if (!block || typeof block !== "object")
|
|
45461
|
+
return block;
|
|
45462
|
+
if (block.type === "tool_use" && block.name === ADVISOR_TOOL_NAME && block.id === toolUseId) {
|
|
45463
|
+
question ??= describeAdvisorQuestion(block.input);
|
|
45464
|
+
return block;
|
|
45200
45465
|
}
|
|
45201
|
-
|
|
45466
|
+
if (block.type === "tool_result" && block.tool_use_id === toolUseId) {
|
|
45467
|
+
changed = true;
|
|
45468
|
+
return {
|
|
45469
|
+
...block,
|
|
45470
|
+
content: [{ type: "text", text: ADVISOR_PLUMBING_MARKER }],
|
|
45471
|
+
is_error: false
|
|
45472
|
+
};
|
|
45473
|
+
}
|
|
45474
|
+
return block;
|
|
45475
|
+
});
|
|
45476
|
+
return changed ? { ...msg, content } : msg;
|
|
45477
|
+
});
|
|
45478
|
+
prepared.push({
|
|
45479
|
+
role: "user",
|
|
45480
|
+
content: [{ type: "text", text: advisorQuestionMessage(question) }]
|
|
45481
|
+
});
|
|
45482
|
+
return prepared;
|
|
45483
|
+
}
|
|
45484
|
+
function routeOf(kind, wireModel) {
|
|
45485
|
+
const url = ADVISOR_ENDPOINTS[kind];
|
|
45486
|
+
return { kind, host: new URL(url).host, url, credential: kind, wireModel };
|
|
45487
|
+
}
|
|
45488
|
+
function advisorRouteFor(modelSpec, role) {
|
|
45489
|
+
const parsed = parseModelSpec(modelSpec);
|
|
45490
|
+
if (role === "collector" && isAnthropicModel(parsed)) {
|
|
45491
|
+
const model = parsed.model;
|
|
45492
|
+
const tier = anthropicTierAlias(model);
|
|
45493
|
+
if (!tier)
|
|
45494
|
+
return routeOf("anthropic", model);
|
|
45495
|
+
const resolved = findEntryByAlias(model)?.modelId ?? latestAnthropicTierModelId(tier);
|
|
45496
|
+
if (!resolved)
|
|
45497
|
+
return { ...routeOf("anthropic", model), unresolvedAlias: model };
|
|
45498
|
+
return routeOf("anthropic", resolved);
|
|
45202
45499
|
}
|
|
45203
|
-
const
|
|
45204
|
-
|
|
45205
|
-
|
|
45206
|
-
|
|
45207
|
-
|
|
45208
|
-
|
|
45209
|
-
|
|
45210
|
-
|
|
45211
|
-
|
|
45212
|
-
|
|
45213
|
-
|
|
45214
|
-
|
|
45215
|
-
|
|
45216
|
-
|
|
45500
|
+
const provider = parsed.provider;
|
|
45501
|
+
if (provider === "google" || provider === "gemini")
|
|
45502
|
+
return routeOf("google", parsed.model);
|
|
45503
|
+
if (provider === "openai" || provider === "oai")
|
|
45504
|
+
return routeOf("openai", parsed.model);
|
|
45505
|
+
if (!parsed.isExplicitProvider || provider === "openrouter") {
|
|
45506
|
+
return routeOf("openrouter", openRouterWireModelFor(null, parsed.model));
|
|
45507
|
+
}
|
|
45508
|
+
return routeOf("openrouter", openRouterWireModelFor(provider, parsed.model));
|
|
45509
|
+
}
|
|
45510
|
+
function openRouterIdOf(entry) {
|
|
45511
|
+
const fromAggregator = entry.aggregators?.find((a) => a.provider === "openrouter")?.externalId;
|
|
45512
|
+
if (fromAggregator)
|
|
45513
|
+
return fromAggregator;
|
|
45514
|
+
return entry.sources["openrouter-api"]?.externalId ?? null;
|
|
45515
|
+
}
|
|
45516
|
+
function lookupOpenRouterId(name) {
|
|
45517
|
+
const entries = getCatalogEntries();
|
|
45518
|
+
if (!entries)
|
|
45519
|
+
return { kind: "unknown" };
|
|
45520
|
+
const lower = name.toLowerCase();
|
|
45521
|
+
if (name.includes("/")) {
|
|
45522
|
+
const match = entries.find((e) => openRouterIdOf(e)?.toLowerCase() === lower);
|
|
45523
|
+
return { kind: "id", id: match ? openRouterIdOf(match) : name };
|
|
45524
|
+
}
|
|
45525
|
+
const byModelId = entries.find((e) => e.modelId.toLowerCase() === lower);
|
|
45526
|
+
if (byModelId) {
|
|
45527
|
+
const id = openRouterIdOf(byModelId);
|
|
45528
|
+
return id ? { kind: "id", id } : { kind: "not-served" };
|
|
45529
|
+
}
|
|
45530
|
+
const byAlias = entries.find((e) => e.aliases.some((a) => a.toLowerCase() === lower));
|
|
45531
|
+
if (byAlias) {
|
|
45532
|
+
const id = openRouterIdOf(byAlias);
|
|
45533
|
+
return id ? { kind: "id", id } : { kind: "not-served" };
|
|
45534
|
+
}
|
|
45535
|
+
const suffix = `/${lower}`;
|
|
45536
|
+
for (const entry of entries) {
|
|
45537
|
+
const id = openRouterIdOf(entry);
|
|
45538
|
+
if (id && (id.toLowerCase() === lower || id.toLowerCase().endsWith(suffix))) {
|
|
45539
|
+
return { kind: "id", id };
|
|
45540
|
+
}
|
|
45541
|
+
}
|
|
45542
|
+
return { kind: "unknown" };
|
|
45543
|
+
}
|
|
45544
|
+
function isOpenRouterVendorNamespace(vendor) {
|
|
45545
|
+
const entries = getCatalogEntries();
|
|
45546
|
+
if (!entries)
|
|
45547
|
+
return false;
|
|
45548
|
+
const prefix = `${vendor.toLowerCase()}/`;
|
|
45549
|
+
return entries.some((e) => openRouterIdOf(e)?.toLowerCase().startsWith(prefix));
|
|
45550
|
+
}
|
|
45551
|
+
function nativeVendorsForProvider(providerUid) {
|
|
45552
|
+
const vendors = new Set;
|
|
45553
|
+
try {
|
|
45554
|
+
for (const plan of readAllModelsCache()?.plans ?? []) {
|
|
45555
|
+
if (plan.routing?.providerUid !== providerUid)
|
|
45556
|
+
continue;
|
|
45557
|
+
for (const native of plan.routing.nativeModelProviders ?? [])
|
|
45558
|
+
vendors.add(native);
|
|
45217
45559
|
}
|
|
45560
|
+
} catch {}
|
|
45561
|
+
return [...vendors];
|
|
45562
|
+
}
|
|
45563
|
+
function openRouterWireModelFor(provider, model) {
|
|
45564
|
+
const lookup = lookupOpenRouterId(model);
|
|
45565
|
+
if (provider === null || provider === "openrouter") {
|
|
45566
|
+
if (lookup.kind === "id")
|
|
45567
|
+
return lookup.id;
|
|
45568
|
+
if (lookup.kind === "unknown")
|
|
45569
|
+
return model;
|
|
45570
|
+
throw new Error(`${model} cannot be used as an advisor model: the catalog lists no OpenRouter id for it ` + "(OpenRouter is where every unprefixed advisor model is called), so claudish has no id " + "OpenRouter would accept. Name a model OpenRouter serves, or give the id in full " + '("openrouter@vendor/model").');
|
|
45571
|
+
}
|
|
45572
|
+
const vendorPrefixed = `${provider}/${model}`;
|
|
45573
|
+
if (isOpenRouterVendorNamespace(provider))
|
|
45574
|
+
return vendorPrefixed;
|
|
45575
|
+
if (lookup.kind === "id")
|
|
45576
|
+
return lookup.id;
|
|
45577
|
+
for (const vendor of nativeVendorsForProvider(provider)) {
|
|
45578
|
+
if (isOpenRouterVendorNamespace(vendor))
|
|
45579
|
+
return `${vendor}/${model}`;
|
|
45580
|
+
}
|
|
45581
|
+
if (getCatalogEntries() === null)
|
|
45582
|
+
return vendorPrefixed;
|
|
45583
|
+
throw new Error(`${provider}@${model} cannot be resolved to a model OpenRouter serves: "${provider}" is not an ` + "OpenRouter vendor namespace, and the catalog lists no OpenRouter id for " + `"${model}". Advisor panel models are always called metered, so a subscription prefix ` + `buys nothing here \u2014 use the bare model name ("${model}"), or name the OpenRouter id ` + 'in full ("openrouter@vendor/model").');
|
|
45584
|
+
}
|
|
45585
|
+
function advisorCredentialsFor(models, collector) {
|
|
45586
|
+
const needed = new Set;
|
|
45587
|
+
const add = (spec, role) => {
|
|
45588
|
+
try {
|
|
45589
|
+
needed.add(advisorRouteFor(spec, role).credential);
|
|
45590
|
+
} catch {}
|
|
45218
45591
|
};
|
|
45592
|
+
for (const m of models)
|
|
45593
|
+
add(m, "panel");
|
|
45594
|
+
if (collector && models.length > 1)
|
|
45595
|
+
add(collector, "collector");
|
|
45596
|
+
return needed;
|
|
45219
45597
|
}
|
|
45220
|
-
|
|
45221
|
-
|
|
45222
|
-
|
|
45223
|
-
|
|
45224
|
-
const
|
|
45598
|
+
function isPlaceholderAnthropicKey(key) {
|
|
45599
|
+
return /placeholder/i.test(key);
|
|
45600
|
+
}
|
|
45601
|
+
async function resolveAdvisorCredential(credential) {
|
|
45602
|
+
const fromAuthority = async (provider, header) => {
|
|
45603
|
+
try {
|
|
45604
|
+
const auth = await credentials.getRequestAuth(provider, { model: "" });
|
|
45605
|
+
const bearer = auth.headers.Authorization?.replace(/^Bearer\s+/i, "").trim();
|
|
45606
|
+
const apiKey = auth.headers["x-api-key"]?.trim();
|
|
45607
|
+
return (header === "x-api-key" ? apiKey : bearer || apiKey) || undefined;
|
|
45608
|
+
} catch {
|
|
45609
|
+
return;
|
|
45610
|
+
}
|
|
45611
|
+
};
|
|
45612
|
+
if (credential === "anthropic") {
|
|
45613
|
+
const key = await fromAuthority(ADVISOR_AUTHORITY_PROVIDER.anthropic, "x-api-key");
|
|
45614
|
+
if (!key || key === process.env.ANTHROPIC_AUTH_TOKEN || isPlaceholderAnthropicKey(key)) {
|
|
45615
|
+
return;
|
|
45616
|
+
}
|
|
45617
|
+
return key;
|
|
45618
|
+
}
|
|
45619
|
+
if (credential === "google") {
|
|
45620
|
+
return await fromAuthority(ADVISOR_AUTHORITY_PROVIDER.google, "any") || process.env.GOOGLE_API_KEY?.trim() || undefined;
|
|
45621
|
+
}
|
|
45622
|
+
return fromAuthority(ADVISOR_AUTHORITY_PROVIDER[credential], "any");
|
|
45623
|
+
}
|
|
45624
|
+
function advisorTokenParamFor(route) {
|
|
45625
|
+
if (route.kind !== "openai")
|
|
45626
|
+
return "max_tokens";
|
|
45627
|
+
let fromCatalog;
|
|
45225
45628
|
try {
|
|
45226
|
-
|
|
45227
|
-
|
|
45228
|
-
|
|
45229
|
-
|
|
45230
|
-
|
|
45231
|
-
|
|
45232
|
-
|
|
45233
|
-
|
|
45234
|
-
|
|
45629
|
+
fromCatalog = lookupModelTokenParam(route.wireModel);
|
|
45630
|
+
} catch {}
|
|
45631
|
+
if (fromCatalog === "max_tokens" || fromCatalog === "max_completion_tokens")
|
|
45632
|
+
return fromCatalog;
|
|
45633
|
+
return "max_completion_tokens";
|
|
45634
|
+
}
|
|
45635
|
+
function buildAdvisorRequest(route, messages, apiKeys, systemPrompt = ADVISOR_SYSTEM_PROMPT) {
|
|
45636
|
+
if (route.kind === "anthropic") {
|
|
45637
|
+
throw new Error(`${route.wireModel}: the Anthropic route is collector-only`);
|
|
45638
|
+
}
|
|
45639
|
+
const headers = {
|
|
45640
|
+
"Content-Type": "application/json",
|
|
45641
|
+
Authorization: `Bearer ${apiKeys[route.credential] ?? ""}`
|
|
45642
|
+
};
|
|
45643
|
+
if (route.kind === "openrouter") {
|
|
45644
|
+
headers["HTTP-Referer"] = "https://claudish.com";
|
|
45645
|
+
headers["X-Title"] = "Claudish Advisor";
|
|
45646
|
+
}
|
|
45647
|
+
const body = {
|
|
45648
|
+
model: route.wireModel,
|
|
45649
|
+
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAIMessages(messages)]
|
|
45650
|
+
};
|
|
45651
|
+
body[advisorTokenParamFor(route)] = ADVISOR_MAX_OUTPUT_TOKENS;
|
|
45652
|
+
return { headers, body };
|
|
45653
|
+
}
|
|
45654
|
+
function stubOutcome(base, stubPath, reason, latencyMs, secrets, observed) {
|
|
45655
|
+
return {
|
|
45656
|
+
...base,
|
|
45657
|
+
upstreamStatus: observed?.status ?? null,
|
|
45658
|
+
upstreamStatusSource: observed?.source ?? null,
|
|
45659
|
+
responseBytes: observed?.bytes ?? 0,
|
|
45660
|
+
latencyMs,
|
|
45661
|
+
origin: "stub",
|
|
45662
|
+
stubPath,
|
|
45663
|
+
reason: sanitizeAdvisorReason(reason, secrets)
|
|
45664
|
+
};
|
|
45665
|
+
}
|
|
45666
|
+
function summarizeErrorBody(bodyText) {
|
|
45667
|
+
let message = "";
|
|
45668
|
+
try {
|
|
45669
|
+
message = extractProviderMessage(JSON.parse(bodyText));
|
|
45670
|
+
} catch {
|
|
45671
|
+
message = bodyText;
|
|
45672
|
+
}
|
|
45673
|
+
const oneLine = String(message ?? "").replace(/\s+/g, " ").trim();
|
|
45674
|
+
return oneLine ? oneLine.slice(0, 200) : "(empty body)";
|
|
45675
|
+
}
|
|
45676
|
+
function describeFetchError(err, timeoutMs) {
|
|
45677
|
+
const e = err;
|
|
45678
|
+
if (e?.name === "AbortError" && timeoutMs) {
|
|
45679
|
+
return `timed out after ${Math.round(timeoutMs / 1000)}s`;
|
|
45680
|
+
}
|
|
45681
|
+
return `request failed: ${e?.message ?? String(err)}`;
|
|
45682
|
+
}
|
|
45683
|
+
async function executeAdvisorFetch(plan, fetchImpl) {
|
|
45684
|
+
const base = { role: plan.role, requestedModel: plan.requestedModel, route: plan.route };
|
|
45685
|
+
const secrets = credentialValuesInHeaders(plan.headers);
|
|
45686
|
+
const started = performance.now();
|
|
45687
|
+
const elapsed = () => Math.round(performance.now() - started);
|
|
45688
|
+
const controller = plan.timeoutMs ? new AbortController : undefined;
|
|
45689
|
+
const timer = controller ? setTimeout(() => controller.abort(), plan.timeoutMs) : undefined;
|
|
45690
|
+
const doFetch = fetchImpl ?? fetch;
|
|
45691
|
+
try {
|
|
45692
|
+
let resp;
|
|
45693
|
+
try {
|
|
45694
|
+
resp = await doFetch(plan.route.url, {
|
|
45695
|
+
method: "POST",
|
|
45696
|
+
headers: plan.headers,
|
|
45697
|
+
body: JSON.stringify(plan.body),
|
|
45698
|
+
signal: controller?.signal
|
|
45699
|
+
});
|
|
45700
|
+
} catch (err) {
|
|
45701
|
+
return stubOutcome(base, plan.errorStubPath, describeFetchError(err, plan.timeoutMs), elapsed(), secrets);
|
|
45235
45702
|
}
|
|
45236
|
-
|
|
45237
|
-
|
|
45703
|
+
let bodyText;
|
|
45704
|
+
try {
|
|
45705
|
+
bodyText = await resp.text();
|
|
45706
|
+
} catch (err) {
|
|
45707
|
+
return stubOutcome(base, plan.errorStubPath, `HTTP ${resp.status} but the body could not be read: ${describeFetchError(err, plan.timeoutMs)}`, elapsed(), secrets, { status: resp.status, source: "http_status", bytes: 0 });
|
|
45708
|
+
}
|
|
45709
|
+
const bodyStatus = extractUpstreamStatus(bodyText);
|
|
45710
|
+
const observed = {
|
|
45711
|
+
status: bodyStatus ?? resp.status,
|
|
45712
|
+
source: bodyStatus !== undefined ? "error.upstream_status" : "http_status",
|
|
45713
|
+
bytes: new TextEncoder().encode(bodyText).byteLength
|
|
45714
|
+
};
|
|
45715
|
+
if (!resp.ok || bodyStatus !== undefined && bodyStatus >= 400) {
|
|
45716
|
+
return stubOutcome(base, plan.errorStubPath, `HTTP ${observed.status}: ${summarizeErrorBody(bodyText)}`, elapsed(), secrets, observed);
|
|
45717
|
+
}
|
|
45718
|
+
let data;
|
|
45719
|
+
try {
|
|
45720
|
+
data = JSON.parse(bodyText);
|
|
45721
|
+
} catch {
|
|
45722
|
+
return stubOutcome(base, plan.emptyStubPath, `HTTP ${observed.status} but the response body is not JSON`, elapsed(), secrets, observed);
|
|
45723
|
+
}
|
|
45724
|
+
const text = plan.extractText(data);
|
|
45725
|
+
if (typeof text !== "string" || text.trim().length === 0) {
|
|
45726
|
+
return stubOutcome(base, plan.emptyStubPath, `HTTP ${observed.status} but the response carried no advice text`, elapsed(), secrets, observed);
|
|
45727
|
+
}
|
|
45728
|
+
return {
|
|
45729
|
+
...base,
|
|
45730
|
+
upstreamStatus: observed.status,
|
|
45731
|
+
upstreamStatusSource: observed.source,
|
|
45732
|
+
responseBytes: observed.bytes,
|
|
45733
|
+
latencyMs: elapsed(),
|
|
45734
|
+
origin: "upstream",
|
|
45735
|
+
stubPath: null,
|
|
45736
|
+
text
|
|
45737
|
+
};
|
|
45238
45738
|
} finally {
|
|
45239
|
-
|
|
45739
|
+
if (timer)
|
|
45740
|
+
clearTimeout(timer);
|
|
45741
|
+
}
|
|
45742
|
+
}
|
|
45743
|
+
function extractChatCompletionText(data) {
|
|
45744
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
45745
|
+
if (typeof content === "string")
|
|
45746
|
+
return content;
|
|
45747
|
+
if (Array.isArray(content)) {
|
|
45748
|
+
const joined = content.map((p) => typeof p?.text === "string" ? p.text : "").join("");
|
|
45749
|
+
return joined || undefined;
|
|
45750
|
+
}
|
|
45751
|
+
return;
|
|
45752
|
+
}
|
|
45753
|
+
function extractAnthropicText(data) {
|
|
45754
|
+
const blocks = data?.content;
|
|
45755
|
+
if (!Array.isArray(blocks))
|
|
45756
|
+
return;
|
|
45757
|
+
const text = blocks.find((b) => b?.type === "text")?.text;
|
|
45758
|
+
return typeof text === "string" ? text : undefined;
|
|
45759
|
+
}
|
|
45760
|
+
function errorMessageOf(err) {
|
|
45761
|
+
return err?.message ?? String(err);
|
|
45762
|
+
}
|
|
45763
|
+
async function callAdvisorModel(modelSpec, messages, apiKeys, fetchImpl) {
|
|
45764
|
+
let plan;
|
|
45765
|
+
try {
|
|
45766
|
+
const route = advisorRouteFor(modelSpec, "panel");
|
|
45767
|
+
const { headers, body } = buildAdvisorRequest(route, messages, apiKeys);
|
|
45768
|
+
plan = {
|
|
45769
|
+
role: "panel",
|
|
45770
|
+
requestedModel: modelSpec,
|
|
45771
|
+
route,
|
|
45772
|
+
headers,
|
|
45773
|
+
body,
|
|
45774
|
+
timeoutMs: ADVISOR_PANEL_TIMEOUT_MS,
|
|
45775
|
+
extractText: extractChatCompletionText,
|
|
45776
|
+
emptyStubPath: ADVISOR_STUB_PATHS.PANEL_EMPTY,
|
|
45777
|
+
errorStubPath: ADVISOR_STUB_PATHS.PANEL_ERROR
|
|
45778
|
+
};
|
|
45779
|
+
} catch (err) {
|
|
45780
|
+
const base = { role: "panel", requestedModel: modelSpec, route: null };
|
|
45781
|
+
return stubOutcome(base, ADVISOR_STUB_PATHS.PANEL_ERROR, `could not build the request: ${errorMessageOf(err)}`, 0, credentialValuesOf(apiKeys));
|
|
45240
45782
|
}
|
|
45783
|
+
return executeAdvisorFetch(plan, fetchImpl);
|
|
45784
|
+
}
|
|
45785
|
+
function anthropicTierAlias(model) {
|
|
45786
|
+
const m = model.toLowerCase();
|
|
45787
|
+
return m === "opus" || m === "sonnet" || m === "haiku" ? m : null;
|
|
45241
45788
|
}
|
|
45242
45789
|
function isAnthropicModel(parsed) {
|
|
45243
45790
|
const m = parsed.model.toLowerCase();
|
|
45244
45791
|
return parsed.provider === "anthropic" || m.startsWith("claude-") || m === "haiku" || m === "sonnet" || m === "opus";
|
|
45245
45792
|
}
|
|
45246
|
-
|
|
45247
|
-
|
|
45248
|
-
|
|
45249
|
-
|
|
45250
|
-
|
|
45793
|
+
function planAnthropicCollector(requestedModel, route, adviceText, apiKey) {
|
|
45794
|
+
return {
|
|
45795
|
+
role: "collector",
|
|
45796
|
+
requestedModel,
|
|
45797
|
+
route,
|
|
45251
45798
|
headers: {
|
|
45252
45799
|
"Content-Type": "application/json",
|
|
45253
45800
|
"x-api-key": apiKey ?? "",
|
|
45254
45801
|
"anthropic-version": "2023-06-01"
|
|
45255
45802
|
},
|
|
45256
|
-
body:
|
|
45257
|
-
model:
|
|
45803
|
+
body: {
|
|
45804
|
+
model: route.wireModel,
|
|
45258
45805
|
max_tokens: 1024,
|
|
45259
45806
|
system: COLLECTOR_SYSTEM_PROMPT,
|
|
45260
45807
|
messages: [{ role: "user", content: adviceText }]
|
|
45261
|
-
}
|
|
45262
|
-
|
|
45263
|
-
|
|
45264
|
-
|
|
45265
|
-
|
|
45266
|
-
|
|
45808
|
+
},
|
|
45809
|
+
timeoutMs: ADVISOR_COLLECTOR_TIMEOUT_MS,
|
|
45810
|
+
extractText: extractAnthropicText,
|
|
45811
|
+
emptyStubPath: ADVISOR_STUB_PATHS.ANTHROPIC_COLLECTOR_EMPTY,
|
|
45812
|
+
errorStubPath: ADVISOR_STUB_PATHS.COLLECTOR_FAILED
|
|
45813
|
+
};
|
|
45267
45814
|
}
|
|
45268
|
-
async function callCollectorModel(collectorSpec, advice, apiKeys) {
|
|
45269
|
-
|
|
45815
|
+
async function callCollectorModel(collectorSpec, advice, apiKeys, fetchImpl) {
|
|
45816
|
+
let plan;
|
|
45817
|
+
try {
|
|
45818
|
+
const adviceText = advice.map((a, i) => `### Advisor ${i + 1} (${a.model})
|
|
45270
45819
|
${a.text}`).join(`
|
|
45271
45820
|
|
|
45272
45821
|
`);
|
|
45273
|
-
|
|
45274
|
-
|
|
45275
|
-
|
|
45276
|
-
|
|
45277
|
-
|
|
45278
|
-
|
|
45279
|
-
body.messages = [
|
|
45280
|
-
{ role: "system", content: COLLECTOR_SYSTEM_PROMPT },
|
|
45281
|
-
{ role: "user", content: adviceText }
|
|
45282
|
-
];
|
|
45283
|
-
const controller = new AbortController;
|
|
45284
|
-
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
45285
|
-
try {
|
|
45286
|
-
const resp = await fetch(url, {
|
|
45287
|
-
method: "POST",
|
|
45288
|
-
headers,
|
|
45289
|
-
body: JSON.stringify(body),
|
|
45290
|
-
signal: controller.signal
|
|
45291
|
-
});
|
|
45292
|
-
if (!resp.ok)
|
|
45293
|
-
throw new Error(`collector ${resp.status}`);
|
|
45294
|
-
const data = await resp.json();
|
|
45295
|
-
return data.choices?.[0]?.message?.content ?? "(collector returned empty)";
|
|
45296
|
-
} finally {
|
|
45297
|
-
clearTimeout(timeout);
|
|
45298
|
-
}
|
|
45299
|
-
}
|
|
45300
|
-
async function fetchMultiModelAdvice(_toolUseId, messages, models, collector, apiKeys) {
|
|
45301
|
-
const results = await Promise.allSettled(models.map((model) => callAdvisorModel(model, messages, apiKeys)));
|
|
45302
|
-
const sections = [];
|
|
45303
|
-
const successfulAdvice = [];
|
|
45304
|
-
for (let i = 0;i < models.length; i++) {
|
|
45305
|
-
const result = results[i];
|
|
45306
|
-
if (result.status === "fulfilled") {
|
|
45307
|
-
sections.push(`## ${models[i]}
|
|
45308
|
-
${result.value}`);
|
|
45309
|
-
successfulAdvice.push({ model: models[i], text: result.value });
|
|
45822
|
+
const route = advisorRouteFor(collectorSpec, "collector");
|
|
45823
|
+
if (route.unresolvedAlias) {
|
|
45824
|
+
throw new Error(`"${route.unresolvedAlias}" is a claudish alias, not a model id ${route.host} accepts, and ` + "the model catalog holds no id for it, so claudish refused to send it. Name the " + 'collector by its full model id (for example "claude-haiku-4-5"), or refresh the ' + "catalog (`claudish --models-refresh`).");
|
|
45825
|
+
}
|
|
45826
|
+
if (route.kind === "anthropic") {
|
|
45827
|
+
plan = planAnthropicCollector(collectorSpec, route, adviceText, apiKeys.anthropic);
|
|
45310
45828
|
} else {
|
|
45311
|
-
|
|
45312
|
-
|
|
45829
|
+
const { headers, body } = buildAdvisorRequest(route, [], apiKeys, COLLECTOR_SYSTEM_PROMPT);
|
|
45830
|
+
body.messages = [
|
|
45831
|
+
{ role: "system", content: COLLECTOR_SYSTEM_PROMPT },
|
|
45832
|
+
{ role: "user", content: adviceText }
|
|
45833
|
+
];
|
|
45834
|
+
plan = {
|
|
45835
|
+
role: "collector",
|
|
45836
|
+
requestedModel: collectorSpec,
|
|
45837
|
+
route,
|
|
45838
|
+
headers,
|
|
45839
|
+
body,
|
|
45840
|
+
timeoutMs: ADVISOR_COLLECTOR_TIMEOUT_MS,
|
|
45841
|
+
extractText: extractChatCompletionText,
|
|
45842
|
+
emptyStubPath: ADVISOR_STUB_PATHS.COLLECTOR_EMPTY,
|
|
45843
|
+
errorStubPath: ADVISOR_STUB_PATHS.COLLECTOR_FAILED
|
|
45844
|
+
};
|
|
45313
45845
|
}
|
|
45846
|
+
} catch (err) {
|
|
45847
|
+
const base = {
|
|
45848
|
+
role: "collector",
|
|
45849
|
+
requestedModel: collectorSpec,
|
|
45850
|
+
route: null
|
|
45851
|
+
};
|
|
45852
|
+
return stubOutcome(base, ADVISOR_STUB_PATHS.COLLECTOR_FAILED, `could not build the request: ${errorMessageOf(err)}`, 0, credentialValuesOf(apiKeys));
|
|
45314
45853
|
}
|
|
45315
|
-
|
|
45316
|
-
|
|
45854
|
+
return executeAdvisorFetch(plan, fetchImpl);
|
|
45855
|
+
}
|
|
45856
|
+
function panelFailureSection(o) {
|
|
45857
|
+
return `## ${o.requestedModel}
|
|
45858
|
+
[Error: ${o.requestedModel} returned no advice \u2014 ${o.reason}]`;
|
|
45859
|
+
}
|
|
45860
|
+
function allPanelFailedText(panel) {
|
|
45861
|
+
if (panel.length === 0) {
|
|
45862
|
+
return `${ADVISOR_ERROR_PREFIX} No advisor model is configured, so no advice was produced. ${ADVISOR_ERROR_SUFFIX}`;
|
|
45317
45863
|
}
|
|
45318
|
-
|
|
45319
|
-
|
|
45864
|
+
const head = panel.length === 1 ? `The advisor model ${panel[0].requestedModel} did not return advice.` : `None of the ${panel.length} advisor models returned advice.`;
|
|
45865
|
+
const lines = panel.map((o) => `- ${o.requestedModel}: ${o.reason}`);
|
|
45866
|
+
return `${ADVISOR_ERROR_PREFIX} ${head}
|
|
45867
|
+
${lines.join(`
|
|
45868
|
+
`)}
|
|
45869
|
+
${ADVISOR_ERROR_SUFFIX}`;
|
|
45870
|
+
}
|
|
45871
|
+
function collectorFailedText(collector, sections) {
|
|
45872
|
+
return `${ADVISOR_ERROR_PREFIX} The collector model ${collector.requestedModel} failed to synthesize ` + `the advice: ${collector.reason}. The panel's unsynthesized answers follow.
|
|
45320
45873
|
|
|
45321
|
-
`
|
|
45322
|
-
}
|
|
45323
|
-
try {
|
|
45324
|
-
const synthesized = await callCollectorModel(collector, successfulAdvice, apiKeys);
|
|
45325
|
-
return synthesized;
|
|
45326
|
-
} catch (err) {
|
|
45327
|
-
log(`[advisor] collector ${collector} failed: ${err.message}, falling back to concat`);
|
|
45328
|
-
return sections.join(`
|
|
45874
|
+
` + sections.join(`
|
|
45329
45875
|
|
|
45330
45876
|
`);
|
|
45877
|
+
}
|
|
45878
|
+
function modelCallRecord(kind, outcome, call) {
|
|
45879
|
+
return {
|
|
45880
|
+
kind,
|
|
45881
|
+
event: kind,
|
|
45882
|
+
toolUseId: call.toolUseId,
|
|
45883
|
+
sessionId: call.sessionId,
|
|
45884
|
+
role: outcome.role,
|
|
45885
|
+
requestedModel: outcome.requestedModel,
|
|
45886
|
+
provider: outcome.route?.kind ?? null,
|
|
45887
|
+
route: outcome.route ? { kind: outcome.route.kind, host: outcome.route.host } : null,
|
|
45888
|
+
routedModel: outcome.route?.wireModel ?? null,
|
|
45889
|
+
upstreamStatus: outcome.upstreamStatus,
|
|
45890
|
+
upstreamStatusSource: outcome.upstreamStatusSource,
|
|
45891
|
+
responseBytes: outcome.responseBytes,
|
|
45892
|
+
latencyMs: outcome.latencyMs,
|
|
45893
|
+
origin: outcome.origin,
|
|
45894
|
+
stubPath: outcome.stubPath,
|
|
45895
|
+
reason: outcome.reason ?? null
|
|
45896
|
+
};
|
|
45897
|
+
}
|
|
45898
|
+
function logAdvisorCallOutcome(cfg, o) {
|
|
45899
|
+
for (const p of o.panel)
|
|
45900
|
+
logAdvisorEvent(cfg, modelCallRecord("advisor_call", p, o));
|
|
45901
|
+
if (o.collectorOutcome) {
|
|
45902
|
+
logAdvisorEvent(cfg, modelCallRecord("advisor_collector_call", o.collectorOutcome, o));
|
|
45903
|
+
}
|
|
45904
|
+
const failedModels = [...o.panel, ...o.collectorOutcome ? [o.collectorOutcome] : []].filter((m) => m.origin !== "upstream").map((m) => m.requestedModel);
|
|
45905
|
+
logAdvisorEvent(cfg, {
|
|
45906
|
+
kind: "advisor_rewrite",
|
|
45907
|
+
event: "advisor_rewrite",
|
|
45908
|
+
toolUseId: o.toolUseId,
|
|
45909
|
+
sessionId: o.sessionId,
|
|
45910
|
+
panel: o.panel.map((p) => p.requestedModel),
|
|
45911
|
+
originsByModel: Object.fromEntries(o.panel.map((p) => [p.requestedModel, p.origin])),
|
|
45912
|
+
failedModels,
|
|
45913
|
+
collector: o.collector,
|
|
45914
|
+
collectorOrigin: o.collectorOutcome ? o.collectorOutcome.origin : o.collector ? "absent" : null,
|
|
45915
|
+
resultOrigin: o.resultOrigin,
|
|
45916
|
+
stubPath: o.stubPath,
|
|
45917
|
+
isError: o.result.isError
|
|
45918
|
+
});
|
|
45919
|
+
}
|
|
45920
|
+
function warnOnAdvisorFailures(o, warn = warnAdvisor) {
|
|
45921
|
+
const failed = [...o.panel, ...o.collectorOutcome ? [o.collectorOutcome] : []].filter((m) => m.origin !== "upstream");
|
|
45922
|
+
if (failed.length === 0 && o.resultOrigin === "upstream")
|
|
45923
|
+
return;
|
|
45924
|
+
const detail = failed.length > 0 ? failed.map((m) => `${m.role === "collector" ? "collector " : ""}${m.requestedModel}: ${m.reason}`).join("; ") : "no advisor model is configured";
|
|
45925
|
+
const verdict = o.resultOrigin === "upstream" ? "advice from the other models was still delivered" : "the model received an error report instead of advice";
|
|
45926
|
+
warn(`advisor call ${o.toolUseId} \u2014 ${detail} (${verdict})`);
|
|
45927
|
+
}
|
|
45928
|
+
function recoverUnassociatedAdvisorResult(cfg, outcome, warn = warnAdvisor) {
|
|
45929
|
+
const lost = [
|
|
45930
|
+
...outcome.panel.map((p) => p.requestedModel),
|
|
45931
|
+
...outcome.collectorOutcome ? [outcome.collectorOutcome.requestedModel] : []
|
|
45932
|
+
];
|
|
45933
|
+
const result = {
|
|
45934
|
+
text: `${ADVISOR_ERROR_PREFIX} claudish could not associate the advisor answer with call ` + `${outcome.toolUseId}: its record was gone by the time the answer arrived, so the advice ` + `from ${lost.length > 0 ? lost.join(", ") : "the panel"} could not be delivered. ` + ADVISOR_ERROR_SUFFIX,
|
|
45935
|
+
isError: true
|
|
45936
|
+
};
|
|
45937
|
+
const sessionId = outcome.sessionId ?? undefined;
|
|
45938
|
+
rememberAdvisorToolUseId(outcome.toolUseId, sessionId);
|
|
45939
|
+
markAdvisorCallConsumed(outcome.toolUseId, result, sessionId);
|
|
45940
|
+
log(`[advisor] call ${outcome.toolUseId}: the result could not be associated with the tracked call (stub path ${ADVISOR_STUB_PATHS.PREPARED_RESULT_MISSING})`);
|
|
45941
|
+
if (cfg) {
|
|
45942
|
+
logAdvisorEvent(cfg, {
|
|
45943
|
+
kind: "advisor_rewrite",
|
|
45944
|
+
event: "advisor_rewrite",
|
|
45945
|
+
toolUseId: outcome.toolUseId,
|
|
45946
|
+
sessionId: outcome.sessionId,
|
|
45947
|
+
corrects: "advisor_rewrite",
|
|
45948
|
+
panel: outcome.panel.map((p) => p.requestedModel),
|
|
45949
|
+
originsByModel: Object.fromEntries(outcome.panel.map((p) => [p.requestedModel, "stub"])),
|
|
45950
|
+
failedModels: lost,
|
|
45951
|
+
collector: outcome.collector,
|
|
45952
|
+
collectorOrigin: outcome.collectorOutcome ? "stub" : null,
|
|
45953
|
+
resultOrigin: "stub",
|
|
45954
|
+
stubPath: ADVISOR_STUB_PATHS.PREPARED_RESULT_MISSING,
|
|
45955
|
+
isError: true
|
|
45956
|
+
});
|
|
45331
45957
|
}
|
|
45958
|
+
warn(`advisor call ${outcome.toolUseId}: the panel answered, but claudish no longer had a record of the call, so the model received an error report instead of the advice`);
|
|
45959
|
+
return result;
|
|
45332
45960
|
}
|
|
45333
|
-
|
|
45961
|
+
async function runAdvisorCall(params) {
|
|
45962
|
+
const { toolUseId, messages, models, collector, apiKeys, fetchImpl } = params;
|
|
45963
|
+
const panelMessages = prepareAdvisorPanelMessages(messages, toolUseId);
|
|
45964
|
+
const panel = await Promise.all(models.map((m) => callAdvisorModel(m, panelMessages, apiKeys, fetchImpl)));
|
|
45965
|
+
const successful = panel.filter((o) => o.origin === "upstream");
|
|
45966
|
+
const sections = panel.map((o) => o.origin === "upstream" ? `## ${o.requestedModel}
|
|
45967
|
+
${o.text}` : panelFailureSection(o));
|
|
45968
|
+
let result;
|
|
45969
|
+
let collectorOutcome = null;
|
|
45970
|
+
let resultOrigin;
|
|
45971
|
+
let stubPath = null;
|
|
45972
|
+
if (models.length === 1 && successful.length === 1) {
|
|
45973
|
+
result = { text: successful[0].text, isError: false };
|
|
45974
|
+
resultOrigin = "upstream";
|
|
45975
|
+
} else if (successful.length === 0) {
|
|
45976
|
+
result = { text: allPanelFailedText(panel), isError: true };
|
|
45977
|
+
resultOrigin = "stub";
|
|
45978
|
+
stubPath = ADVISOR_STUB_PATHS.ALL_PANEL_FAILED;
|
|
45979
|
+
} else if (!collector) {
|
|
45980
|
+
result = { text: sections.join(`
|
|
45981
|
+
|
|
45982
|
+
`), isError: false };
|
|
45983
|
+
resultOrigin = "upstream";
|
|
45984
|
+
} else {
|
|
45985
|
+
collectorOutcome = await callCollectorModel(collector, successful.map((o) => ({ model: o.requestedModel, text: o.text })), apiKeys, fetchImpl);
|
|
45986
|
+
if (collectorOutcome.origin === "upstream" && collectorOutcome.text !== undefined) {
|
|
45987
|
+
result = { text: collectorOutcome.text, isError: false };
|
|
45988
|
+
resultOrigin = "upstream";
|
|
45989
|
+
} else {
|
|
45990
|
+
log(`[advisor] collector ${collector} failed: ${collectorOutcome.reason}, falling back to concat`);
|
|
45991
|
+
result = { text: collectorFailedText(collectorOutcome, sections), isError: true };
|
|
45992
|
+
resultOrigin = "stub";
|
|
45993
|
+
stubPath = collectorOutcome.stubPath;
|
|
45994
|
+
}
|
|
45995
|
+
}
|
|
45996
|
+
const outcome = {
|
|
45997
|
+
toolUseId,
|
|
45998
|
+
sessionId: params.sessionId ?? null,
|
|
45999
|
+
panel,
|
|
46000
|
+
collector,
|
|
46001
|
+
collectorOutcome,
|
|
46002
|
+
resultOrigin,
|
|
46003
|
+
stubPath,
|
|
46004
|
+
result
|
|
46005
|
+
};
|
|
46006
|
+
log(`[advisor] call ${toolUseId}: resultOrigin=${resultOrigin}${stubPath ? ` stubPath=${stubPath}` : ""} ` + `origins=${panel.map((p) => `${p.requestedModel}:${p.origin}`).join(",")}` + (collectorOutcome ? ` collector=${collectorOutcome.requestedModel}:${collectorOutcome.origin}` : ""));
|
|
46007
|
+
if (params.cfg)
|
|
46008
|
+
logAdvisorCallOutcome(params.cfg, outcome);
|
|
46009
|
+
warnOnAdvisorFailures(outcome, params.warn);
|
|
46010
|
+
return outcome;
|
|
46011
|
+
}
|
|
46012
|
+
var ADVISOR_SERVER_TOOL_TYPE = "advisor_20260301", ADVISOR_BETA_FLAG = "advisor-tool-2026-03-01", ADVISOR_ORIGIN_LOG_PREFIX = "[advisor-origin]", ORIGIN_RECORD_KINDS, REDACTED = "[redacted]", MIN_REDACTABLE_SECRET_LENGTH = 8, ADVISOR_STUB_PATHS, ADVISOR_ERROR_PREFIX = "[claudish advisor error]", ADVISOR_ERROR_SUFFIX = "This tool result is an error report from the claudish proxy, not advice.", NO_SESSION_BUCKET = "__no_session__", ADVISOR_PENDING_LIMITS, SWEEP_INTERVAL_MS = 60000, pendingBySession, clock, lastSweepAt, ADVISOR_TOOL_NAME = "advisor", MAX_WALK_DEPTH = 32, MAX_SSE_BUFFER_CHARS, inFlightByToolUseId, inFlightAdvisorCalls, MAX_IN_FLIGHT_ADVISOR_CALLS, streamFrameBuffer, ADVISOR_ID_PATTERNS, NO_SUCH_ADVISOR_TOOL, reportedUnrecorded, MAX_REPORTED_UNRECORDED = 1024, ADVISOR_PLUMBING_MARKER = "(handled by the claudish proxy \u2014 this advisor call is what you are being asked to answer; there is no local tool output)", ADVISOR_QUESTION_FIELDS, MAX_ADVISOR_QUESTION_CHARS = 4000, ADVISOR_SYSTEM_PROMPT = `You are a strategic advisor to a coding agent. You have been given the full conversation history between a user and a Claude Code coding assistant. The assistant has paused to consult you for guidance.
|
|
45334
46013
|
|
|
45335
46014
|
Review the conversation and provide concise, actionable advice. Focus on:
|
|
45336
46015
|
- Architectural decisions and trade-offs
|
|
@@ -45342,13 +46021,523 @@ Be direct. Limit your response to 300-500 words.`, COLLECTOR_SYSTEM_PROMPT = `Yo
|
|
|
45342
46021
|
- Identifies consensus points (where advisors agree)
|
|
45343
46022
|
- Highlights disagreements and explains which perspective is stronger
|
|
45344
46023
|
- Produces a clear, actionable recommendation
|
|
45345
|
-
Be concise. Do not attribute advice to specific models
|
|
46024
|
+
Be concise. Do not attribute advice to specific models.`, ADVISOR_ENDPOINTS, ADVISOR_AUTHORITY_PROVIDER, ADVISOR_MAX_OUTPUT_TOKENS = 2048, ADVISOR_PANEL_TIMEOUT_MS = 60000, ADVISOR_COLLECTOR_TIMEOUT_MS = 30000;
|
|
45346
46025
|
var init_native_handler_advisor = __esm(() => {
|
|
46026
|
+
init_model_catalog();
|
|
46027
|
+
init_authority();
|
|
45347
46028
|
init_logger();
|
|
46029
|
+
init_all_models_cache();
|
|
45348
46030
|
init_catalog_client();
|
|
45349
46031
|
init_catalog_query();
|
|
45350
46032
|
init_model_parser();
|
|
45351
|
-
|
|
46033
|
+
init_anthropic_error();
|
|
46034
|
+
ORIGIN_RECORD_KINDS = new Set([
|
|
46035
|
+
"advisor_call",
|
|
46036
|
+
"advisor_collector_call",
|
|
46037
|
+
"advisor_rewrite"
|
|
46038
|
+
]);
|
|
46039
|
+
ADVISOR_STUB_PATHS = Object.freeze({
|
|
46040
|
+
LEGACY_STUB: "S1",
|
|
46041
|
+
PREPARED_RESULT_MISSING: "S2",
|
|
46042
|
+
DISABLED_STUB: "S3",
|
|
46043
|
+
PANEL_EMPTY: "S4",
|
|
46044
|
+
ANTHROPIC_COLLECTOR_EMPTY: "S5",
|
|
46045
|
+
COLLECTOR_EMPTY: "S6",
|
|
46046
|
+
PANEL_ERROR: "S7",
|
|
46047
|
+
ALL_PANEL_FAILED: "S8",
|
|
46048
|
+
COLLECTOR_FAILED: "S9",
|
|
46049
|
+
NOT_REWRITTEN: "S10"
|
|
46050
|
+
});
|
|
46051
|
+
ADVISOR_PENDING_LIMITS = Object.freeze({
|
|
46052
|
+
maxCallsPerSession: 256,
|
|
46053
|
+
maxSessions: 64,
|
|
46054
|
+
ttlMs: 24 * 60 * 60 * 1000
|
|
46055
|
+
});
|
|
46056
|
+
pendingBySession = new Map;
|
|
46057
|
+
clock = Date.now;
|
|
46058
|
+
lastSweepAt = Number.NEGATIVE_INFINITY;
|
|
46059
|
+
MAX_SSE_BUFFER_CHARS = 256 * 1024;
|
|
46060
|
+
inFlightByToolUseId = new Map;
|
|
46061
|
+
inFlightAdvisorCalls = new Map;
|
|
46062
|
+
MAX_IN_FLIGHT_ADVISOR_CALLS = ADVISOR_PENDING_LIMITS.maxSessions * ADVISOR_PENDING_LIMITS.maxCallsPerSession;
|
|
46063
|
+
streamFrameBuffer = new SseFrameBuffer;
|
|
46064
|
+
ADVISOR_ID_PATTERNS = [
|
|
46065
|
+
/"type"\s*:\s*"tool_use"[^}]*?"id"\s*:\s*"([^"]+)"[^}]*?"name"\s*:\s*"advisor"/g,
|
|
46066
|
+
/"name"\s*:\s*"advisor"[^}]*?"id"\s*:\s*"([^"]+)"/g,
|
|
46067
|
+
/"id"\s*:\s*"([^"]+)"\s*,\s*"name"\s*:\s*"advisor"/g
|
|
46068
|
+
];
|
|
46069
|
+
NO_SUCH_ADVISOR_TOOL = /No such tool available:\s*advisor\b/;
|
|
46070
|
+
reportedUnrecorded = new Set;
|
|
46071
|
+
ADVISOR_QUESTION_FIELDS = [
|
|
46072
|
+
"question",
|
|
46073
|
+
"prompt",
|
|
46074
|
+
"query",
|
|
46075
|
+
"request",
|
|
46076
|
+
"task",
|
|
46077
|
+
"topic",
|
|
46078
|
+
"context",
|
|
46079
|
+
"input",
|
|
46080
|
+
"text"
|
|
46081
|
+
];
|
|
46082
|
+
ADVISOR_ENDPOINTS = Object.freeze({
|
|
46083
|
+
google: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
|
|
46084
|
+
openai: "https://api.openai.com/v1/chat/completions",
|
|
46085
|
+
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
|
46086
|
+
anthropic: "https://api.anthropic.com/v1/messages"
|
|
46087
|
+
});
|
|
46088
|
+
ADVISOR_AUTHORITY_PROVIDER = Object.freeze({
|
|
46089
|
+
google: "google",
|
|
46090
|
+
openai: "openai",
|
|
46091
|
+
openrouter: "openrouter",
|
|
46092
|
+
anthropic: "native-anthropic"
|
|
46093
|
+
});
|
|
46094
|
+
});
|
|
46095
|
+
|
|
46096
|
+
// src/handlers/advisor-decorator.ts
|
|
46097
|
+
async function resolveAdvisorKeys(needed, inboundApiKey) {
|
|
46098
|
+
const anthropicKey = async () => {
|
|
46099
|
+
if (inboundApiKey && !isPlaceholderAnthropicKey(inboundApiKey))
|
|
46100
|
+
return inboundApiKey;
|
|
46101
|
+
return resolveAdvisorCredential("anthropic");
|
|
46102
|
+
};
|
|
46103
|
+
const [openrouter, google, openai, anthropic] = await Promise.all([
|
|
46104
|
+
needed.has("openrouter") ? resolveAdvisorCredential("openrouter") : undefined,
|
|
46105
|
+
needed.has("google") ? resolveAdvisorCredential("google") : undefined,
|
|
46106
|
+
needed.has("openai") ? resolveAdvisorCredential("openai") : undefined,
|
|
46107
|
+
needed.has("anthropic") ? anthropicKey() : undefined
|
|
46108
|
+
]);
|
|
46109
|
+
return { openrouter, google, openai, anthropic };
|
|
46110
|
+
}
|
|
46111
|
+
function toolsOfferAdvisor(tools) {
|
|
46112
|
+
if (!Array.isArray(tools))
|
|
46113
|
+
return false;
|
|
46114
|
+
return tools.some((t) => {
|
|
46115
|
+
if (!t || typeof t !== "object")
|
|
46116
|
+
return false;
|
|
46117
|
+
const tool = t;
|
|
46118
|
+
return tool.type === ADVISOR_SERVER_TOOL_TYPE2 || tool.name === ADVISOR_TOOL_NAME2;
|
|
46119
|
+
});
|
|
46120
|
+
}
|
|
46121
|
+
function createAdvisorPresenceMonitor(warn = logStderr) {
|
|
46122
|
+
let consecutiveAbsent = 0;
|
|
46123
|
+
let warned = false;
|
|
46124
|
+
return {
|
|
46125
|
+
observe(payload) {
|
|
46126
|
+
if (warned)
|
|
46127
|
+
return;
|
|
46128
|
+
const tools = payload?.tools;
|
|
46129
|
+
if (!Array.isArray(tools) || tools.length === 0)
|
|
46130
|
+
return;
|
|
46131
|
+
if (toolsOfferAdvisor(tools)) {
|
|
46132
|
+
consecutiveAbsent = 0;
|
|
46133
|
+
return;
|
|
46134
|
+
}
|
|
46135
|
+
consecutiveAbsent++;
|
|
46136
|
+
log(`[advisor-swap] request offers ${tools.length} tool(s) but no advisor (${consecutiveAbsent} in a row)`);
|
|
46137
|
+
if (consecutiveAbsent < ADVISOR_ABSENT_WARN_AFTER)
|
|
46138
|
+
return;
|
|
46139
|
+
warned = true;
|
|
46140
|
+
warn(`[advisor] Claude Code sent ${consecutiveAbsent} requests in a row that offer tools but no advisor tool, so --advisor is having no effect. Claude Code is not offering the advisor in this session (for example, the experimental advisor flag was withdrawn or disabled). This warning is shown once.`);
|
|
46141
|
+
}
|
|
46142
|
+
};
|
|
46143
|
+
}
|
|
46144
|
+
function withAdvisorSwap(inner, cfg, deps = {}) {
|
|
46145
|
+
if (!cfg.enabled)
|
|
46146
|
+
return inner;
|
|
46147
|
+
if (inner instanceof AdvisorSwapHandler)
|
|
46148
|
+
return inner;
|
|
46149
|
+
return new AdvisorSwapHandler(inner, cfg, deps);
|
|
46150
|
+
}
|
|
46151
|
+
|
|
46152
|
+
class AdvisorSwapHandler {
|
|
46153
|
+
inner;
|
|
46154
|
+
cfg;
|
|
46155
|
+
deps;
|
|
46156
|
+
constructor(inner, cfg, deps = {}) {
|
|
46157
|
+
this.inner = inner;
|
|
46158
|
+
this.cfg = cfg;
|
|
46159
|
+
this.deps = deps;
|
|
46160
|
+
}
|
|
46161
|
+
async handle(c, payload) {
|
|
46162
|
+
if (c.get("advisorHandled") === true)
|
|
46163
|
+
return this.inner.handle(c, payload);
|
|
46164
|
+
c.set("advisorHandled", true);
|
|
46165
|
+
const sessionId = extractSessionId(payload);
|
|
46166
|
+
try {
|
|
46167
|
+
this.deps.presence?.observe(payload);
|
|
46168
|
+
} catch {}
|
|
46169
|
+
await applyAdvisorRequestSide(c, payload, this.cfg, sessionId, this.deps.resolveKeys ?? resolveAdvisorKeys);
|
|
46170
|
+
const response = await this.inner.handle(c, payload);
|
|
46171
|
+
return tapAdvisorResponse(response, this.cfg, sessionId);
|
|
46172
|
+
}
|
|
46173
|
+
async shutdown() {
|
|
46174
|
+
await this.inner.shutdown();
|
|
46175
|
+
}
|
|
46176
|
+
}
|
|
46177
|
+
async function applyAdvisorRequestSide(c, payload, cfg, sessionId, resolveKeys) {
|
|
46178
|
+
const target = payload.model;
|
|
46179
|
+
const sessionLabel = sessionId ?? NO_SESSION_BUCKET;
|
|
46180
|
+
const swapped = swapAdvisorToolInBody(payload);
|
|
46181
|
+
if (swapped) {
|
|
46182
|
+
c.set(ADVISOR_SWAPPED_CONTEXT_KEY, true);
|
|
46183
|
+
log(`[advisor-swap] replaced advisor_20260301 with function tool 'advisor' (model=${target}, session=${sessionLabel})`);
|
|
46184
|
+
logAdvisorEvent(cfg, {
|
|
46185
|
+
kind: "swap_applied",
|
|
46186
|
+
model: target,
|
|
46187
|
+
originalTool: swapped.originalTool,
|
|
46188
|
+
regularTool: swapped.regularTool
|
|
46189
|
+
});
|
|
46190
|
+
}
|
|
46191
|
+
const cachedResult = (id) => getAdvisorCall(id, sessionId)?.result;
|
|
46192
|
+
let rewrittenIds = [];
|
|
46193
|
+
if (cfg.models && cfg.models.length > 0) {
|
|
46194
|
+
const models = cfg.models;
|
|
46195
|
+
rewriteAdvisorToolResults(payload, cachedResult, sessionId);
|
|
46196
|
+
const pendingIds = findPendingAdvisorToolResults(payload, sessionId);
|
|
46197
|
+
if (pendingIds.length > 0) {
|
|
46198
|
+
const freshIds = [];
|
|
46199
|
+
const delivered = new Map;
|
|
46200
|
+
let apiKeys;
|
|
46201
|
+
for (const id of pendingIds) {
|
|
46202
|
+
if (cachedResult(id))
|
|
46203
|
+
continue;
|
|
46204
|
+
const runPanel = async () => {
|
|
46205
|
+
apiKeys ??= await resolveKeys(advisorCredentialsFor(models, cfg.collector), c.req.header("x-api-key"));
|
|
46206
|
+
const outcome = await runAdvisorCall({
|
|
46207
|
+
toolUseId: id,
|
|
46208
|
+
sessionId,
|
|
46209
|
+
messages: payload.messages,
|
|
46210
|
+
models,
|
|
46211
|
+
collector: cfg.collector ?? null,
|
|
46212
|
+
apiKeys,
|
|
46213
|
+
cfg
|
|
46214
|
+
});
|
|
46215
|
+
if (!markAdvisorCallConsumed(id, outcome.result, sessionId)) {
|
|
46216
|
+
return recoverUnassociatedAdvisorResult(cfg, outcome);
|
|
46217
|
+
}
|
|
46218
|
+
return outcome.result;
|
|
46219
|
+
};
|
|
46220
|
+
let { promise, joined } = joinOrStartAdvisorCall(id, sessionId, runPanel);
|
|
46221
|
+
if (joined) {
|
|
46222
|
+
log(`[advisor-swap] advisor call ${id} is already running for this session; joined it instead of running the panel again (session=${sessionLabel})`);
|
|
46223
|
+
try {
|
|
46224
|
+
delivered.set(id, await promise);
|
|
46225
|
+
continue;
|
|
46226
|
+
} catch (err) {
|
|
46227
|
+
log(`[advisor-swap] the in-flight advisor call ${id} failed (${errorMessage(err)}); running our own`);
|
|
46228
|
+
({ promise, joined } = joinOrStartAdvisorCall(id, sessionId, runPanel));
|
|
46229
|
+
}
|
|
46230
|
+
}
|
|
46231
|
+
delivered.set(id, await promise);
|
|
46232
|
+
if (!joined)
|
|
46233
|
+
freshIds.push(id);
|
|
46234
|
+
}
|
|
46235
|
+
rewrittenIds = rewriteAdvisorToolResults(payload, (id) => cachedResult(id) ?? delivered.get(id) ?? missingAdvisorResult(id), sessionId);
|
|
46236
|
+
if (rewrittenIds.length > 0) {
|
|
46237
|
+
const replayed = rewrittenIds.filter((id) => !freshIds.includes(id));
|
|
46238
|
+
log(`[advisor-swap] rewrote ${rewrittenIds.length} advisor tool_result(s) (session=${sessionLabel}) fresh=[${freshIds.join(", ")}] replayed=[${replayed.join(", ")}] panel=[${cfg.models.join(", ")}] collector=${cfg.collector ?? "none"}`);
|
|
46239
|
+
logAdvisorEvent(cfg, {
|
|
46240
|
+
kind: "multi_model_rewrite",
|
|
46241
|
+
ids: rewrittenIds,
|
|
46242
|
+
freshIds,
|
|
46243
|
+
models: cfg.models,
|
|
46244
|
+
collector: cfg.collector,
|
|
46245
|
+
model: target
|
|
46246
|
+
});
|
|
46247
|
+
}
|
|
46248
|
+
}
|
|
46249
|
+
} else {
|
|
46250
|
+
for (const id of findPendingAdvisorToolResults(payload, sessionId)) {
|
|
46251
|
+
if (!cachedResult(id))
|
|
46252
|
+
prepareLegacyStubResult(cfg, id, sessionId);
|
|
46253
|
+
}
|
|
46254
|
+
rewrittenIds = rewriteAdvisorToolResults(payload, (id) => cachedResult(id) ?? stubAdvisorAdvice(id), sessionId);
|
|
46255
|
+
if (rewrittenIds.length > 0) {
|
|
46256
|
+
log(`[advisor-swap] rewrote ${rewrittenIds.length} advisor tool_result(s) with stub advice (session=${sessionLabel}): ${rewrittenIds.join(", ")}`);
|
|
46257
|
+
logAdvisorEvent(cfg, {
|
|
46258
|
+
kind: "tool_result_rewritten",
|
|
46259
|
+
ids: rewrittenIds,
|
|
46260
|
+
model: target
|
|
46261
|
+
});
|
|
46262
|
+
}
|
|
46263
|
+
}
|
|
46264
|
+
reportUnrecordedAdvisorCalls(cfg, payload, sessionId);
|
|
46265
|
+
if (cfg.dumpBodies) {
|
|
46266
|
+
logAdvisorEvent(cfg, {
|
|
46267
|
+
kind: "request_body",
|
|
46268
|
+
swapApplied: !!swapped,
|
|
46269
|
+
rewrittenIds,
|
|
46270
|
+
model: target,
|
|
46271
|
+
body: trimForLog(payload)
|
|
46272
|
+
});
|
|
46273
|
+
}
|
|
46274
|
+
}
|
|
46275
|
+
function tapAdvisorResponse(response, cfg, sessionId) {
|
|
46276
|
+
const body = response.body;
|
|
46277
|
+
if (!body)
|
|
46278
|
+
return response;
|
|
46279
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
46280
|
+
const kind = contentType.includes("text/event-stream") ? "sse" : contentType.includes("json") ? "json" : null;
|
|
46281
|
+
if (!kind)
|
|
46282
|
+
return response;
|
|
46283
|
+
let toClient;
|
|
46284
|
+
let toScan;
|
|
46285
|
+
try {
|
|
46286
|
+
[toClient, toScan] = body.tee();
|
|
46287
|
+
} catch (err) {
|
|
46288
|
+
log(`[advisor-swap] response not scanned (${errorMessage(err)}); passed through untouched`);
|
|
46289
|
+
return response;
|
|
46290
|
+
}
|
|
46291
|
+
const clientReader = toClient.getReader();
|
|
46292
|
+
const scanReader = toScan.getReader();
|
|
46293
|
+
drainScanBranch(scanReader, kind, cfg, sessionId);
|
|
46294
|
+
const passthrough = new ReadableStream({
|
|
46295
|
+
async pull(controller) {
|
|
46296
|
+
try {
|
|
46297
|
+
const { done, value } = await clientReader.read();
|
|
46298
|
+
if (done)
|
|
46299
|
+
controller.close();
|
|
46300
|
+
else
|
|
46301
|
+
controller.enqueue(value);
|
|
46302
|
+
} catch (err) {
|
|
46303
|
+
controller.error(err);
|
|
46304
|
+
}
|
|
46305
|
+
},
|
|
46306
|
+
cancel(reason) {
|
|
46307
|
+
scanReader.cancel(reason).catch(() => {});
|
|
46308
|
+
return clientReader.cancel(reason);
|
|
46309
|
+
}
|
|
46310
|
+
}, { highWaterMark: 0 });
|
|
46311
|
+
return new Response(passthrough, {
|
|
46312
|
+
status: response.status,
|
|
46313
|
+
statusText: response.statusText,
|
|
46314
|
+
headers: response.headers
|
|
46315
|
+
});
|
|
46316
|
+
}
|
|
46317
|
+
async function drainScanBranch(reader, kind, cfg, sessionId) {
|
|
46318
|
+
const decoder = new TextDecoder;
|
|
46319
|
+
const scanner = kind === "sse" ? createAdvisorStreamScanner(cfg, sessionId) : null;
|
|
46320
|
+
let jsonText = "";
|
|
46321
|
+
let jsonTooLarge = false;
|
|
46322
|
+
const take = (text) => {
|
|
46323
|
+
if (!text)
|
|
46324
|
+
return;
|
|
46325
|
+
if (scanner) {
|
|
46326
|
+
scanner.push(text);
|
|
46327
|
+
} else if (!jsonTooLarge) {
|
|
46328
|
+
jsonText += text;
|
|
46329
|
+
if (jsonText.length > MAX_JSON_SCAN_CHARS) {
|
|
46330
|
+
jsonTooLarge = true;
|
|
46331
|
+
jsonText = "";
|
|
46332
|
+
}
|
|
46333
|
+
}
|
|
46334
|
+
};
|
|
46335
|
+
try {
|
|
46336
|
+
while (true) {
|
|
46337
|
+
const { done, value } = await reader.read();
|
|
46338
|
+
if (done)
|
|
46339
|
+
break;
|
|
46340
|
+
take(typeof value === "string" ? value : decoder.decode(value, { stream: true }));
|
|
46341
|
+
}
|
|
46342
|
+
take(decoder.decode());
|
|
46343
|
+
if (jsonTooLarge) {
|
|
46344
|
+
log(`[advisor-swap] non-stream response over ${MAX_JSON_SCAN_CHARS} chars was not scanned for advisor calls`);
|
|
46345
|
+
} else if (!scanner && jsonText.trim()) {
|
|
46346
|
+
recordAdvisorEventsFromResponseBody(cfg, JSON.parse(jsonText), sessionId);
|
|
46347
|
+
}
|
|
46348
|
+
} catch (err) {
|
|
46349
|
+
log(`[advisor-swap] response scan stopped: ${errorMessage(err)} (client stream unaffected)`);
|
|
46350
|
+
reader.cancel(err).catch(() => {});
|
|
46351
|
+
}
|
|
46352
|
+
}
|
|
46353
|
+
function errorMessage(err) {
|
|
46354
|
+
return err instanceof Error ? err.message : String(err);
|
|
46355
|
+
}
|
|
46356
|
+
function trimForLog(payload) {
|
|
46357
|
+
const TEXT_TRUNC = 400;
|
|
46358
|
+
const clone = structuredClone(payload);
|
|
46359
|
+
const trimStr = (s) => typeof s === "string" && s.length > TEXT_TRUNC ? `${s.slice(0, TEXT_TRUNC)}\u2026 [+${s.length - TEXT_TRUNC} chars]` : s;
|
|
46360
|
+
const walk = (v) => {
|
|
46361
|
+
if (typeof v === "string")
|
|
46362
|
+
return trimStr(v);
|
|
46363
|
+
if (Array.isArray(v))
|
|
46364
|
+
return v.map(walk);
|
|
46365
|
+
if (v && typeof v === "object") {
|
|
46366
|
+
const out = {};
|
|
46367
|
+
for (const [k, val] of Object.entries(v))
|
|
46368
|
+
out[k] = walk(val);
|
|
46369
|
+
return out;
|
|
46370
|
+
}
|
|
46371
|
+
return v;
|
|
46372
|
+
};
|
|
46373
|
+
return walk(clone);
|
|
46374
|
+
}
|
|
46375
|
+
var ADVISOR_SWAPPED_CONTEXT_KEY = "advisorSwapped", ADVISOR_SERVER_TOOL_TYPE2 = "advisor_20260301", ADVISOR_TOOL_NAME2 = "advisor", MAX_JSON_SCAN_CHARS, ADVISOR_ABSENT_WARN_AFTER = 3;
|
|
46376
|
+
var init_advisor_decorator = __esm(() => {
|
|
46377
|
+
init_harness();
|
|
46378
|
+
init_logger();
|
|
46379
|
+
init_native_handler_advisor();
|
|
46380
|
+
MAX_JSON_SCAN_CHARS = 16 * 1024 * 1024;
|
|
46381
|
+
});
|
|
46382
|
+
|
|
46383
|
+
// src/handlers/fallback-handler.ts
|
|
46384
|
+
class FallbackHandler {
|
|
46385
|
+
candidates;
|
|
46386
|
+
lastSuccessIndex = 0;
|
|
46387
|
+
constructor(candidates) {
|
|
46388
|
+
this.candidates = candidates;
|
|
46389
|
+
}
|
|
46390
|
+
async handle(c, payload) {
|
|
46391
|
+
const errors = [];
|
|
46392
|
+
const startIndex = this.lastSuccessIndex;
|
|
46393
|
+
for (let attempt = 0;attempt < this.candidates.length; attempt++) {
|
|
46394
|
+
const idx = (startIndex + attempt) % this.candidates.length;
|
|
46395
|
+
const { name, handler } = this.candidates[idx];
|
|
46396
|
+
const isLast = attempt === this.candidates.length - 1;
|
|
46397
|
+
try {
|
|
46398
|
+
if (errors.length > 0 && handler instanceof ComposedHandler) {
|
|
46399
|
+
try {
|
|
46400
|
+
handler.setFallbackMeta(this.candidates.map((c) => c.name), errors.length);
|
|
46401
|
+
} catch {}
|
|
46402
|
+
}
|
|
46403
|
+
const response = await handler.handle(c, payload);
|
|
46404
|
+
if (response.ok) {
|
|
46405
|
+
this.lastSuccessIndex = idx;
|
|
46406
|
+
if (errors.length > 0) {
|
|
46407
|
+
logStderr(`[Fallback] ${name} succeeded after ${errors.length} failed attempt(s)`);
|
|
46408
|
+
if (handler instanceof ComposedHandler) {
|
|
46409
|
+
handler.getTokenTracker()?.setProviderDisplayName(name);
|
|
46410
|
+
}
|
|
46411
|
+
}
|
|
46412
|
+
return response;
|
|
46413
|
+
}
|
|
46414
|
+
const errorBody = await response.clone().text();
|
|
46415
|
+
if (!isRetryableError(response.status, errorBody, name)) {
|
|
46416
|
+
if (errors.length > 0) {
|
|
46417
|
+
errors.push({ provider: name, status: response.status, message: errorBody });
|
|
46418
|
+
return this.formatCombinedError(c, errors, payload.model);
|
|
46419
|
+
}
|
|
46420
|
+
return response;
|
|
46421
|
+
}
|
|
46422
|
+
errors.push({ provider: name, status: response.status, message: errorBody });
|
|
46423
|
+
if (!isLast) {
|
|
46424
|
+
if (hasQuotaExhaustionWording(errorBody)) {
|
|
46425
|
+
logStderr(`[Fallback] ${name} subscription allowance is spent \u2014 falling through to the next provider, which is billed PER TOKEN. Use a provider prefix (e.g. \`zgo@model\`) to fail instead of switching.`);
|
|
46426
|
+
} else {
|
|
46427
|
+
logStderr(`[Fallback] ${name} failed (HTTP ${response.status}), trying next provider...`);
|
|
46428
|
+
}
|
|
46429
|
+
}
|
|
46430
|
+
} catch (err) {
|
|
46431
|
+
errors.push({ provider: name, status: 0, message: err.message });
|
|
46432
|
+
if (!isLast) {
|
|
46433
|
+
logStderr(`[Fallback] ${name} error: ${err.message}, trying next provider...`);
|
|
46434
|
+
}
|
|
46435
|
+
}
|
|
46436
|
+
}
|
|
46437
|
+
return this.formatCombinedError(c, errors, payload.model);
|
|
46438
|
+
}
|
|
46439
|
+
formatCombinedError(c, errors, modelName) {
|
|
46440
|
+
const summary = errors.map((e) => ` ${e.provider}: HTTP ${e.status || "ERR"} \u2014 ${truncate(parseErrorMessage(e.message), 150)}`).join(`
|
|
46441
|
+
`);
|
|
46442
|
+
logStderr(`[Fallback] All ${errors.length} provider(s) failed for ${modelName || "model"}:
|
|
46443
|
+
${summary}`);
|
|
46444
|
+
return c.json({
|
|
46445
|
+
error: {
|
|
46446
|
+
type: "all_providers_failed",
|
|
46447
|
+
message: `All ${errors.length} providers failed for model '${modelName || "unknown"}'`,
|
|
46448
|
+
attempts: errors.map((e) => ({
|
|
46449
|
+
provider: e.provider,
|
|
46450
|
+
status: e.status,
|
|
46451
|
+
error: truncate(parseErrorMessage(e.message), 200)
|
|
46452
|
+
}))
|
|
46453
|
+
}
|
|
46454
|
+
}, exhaustedChainStatus(errors));
|
|
46455
|
+
}
|
|
46456
|
+
async shutdown() {
|
|
46457
|
+
for (const { handler } of this.candidates) {
|
|
46458
|
+
if (typeof handler.shutdown === "function") {
|
|
46459
|
+
await handler.shutdown();
|
|
46460
|
+
}
|
|
46461
|
+
}
|
|
46462
|
+
}
|
|
46463
|
+
}
|
|
46464
|
+
function isRetryableError(status, errorBody, provider) {
|
|
46465
|
+
if (hasQuotaExhaustionWording(errorBody))
|
|
46466
|
+
return true;
|
|
46467
|
+
const upstream = status === 400 ? extractUpstreamStatus(errorBody) : undefined;
|
|
46468
|
+
if (upstream === 401 || upstream === 403 || upstream === 402 || upstream === 429) {
|
|
46469
|
+
return true;
|
|
46470
|
+
}
|
|
46471
|
+
if (status === 401 || status === 403)
|
|
46472
|
+
return true;
|
|
46473
|
+
if (status === 402)
|
|
46474
|
+
return true;
|
|
46475
|
+
if (status === 404)
|
|
46476
|
+
return true;
|
|
46477
|
+
if (status === 429)
|
|
46478
|
+
return true;
|
|
46479
|
+
const lower = errorBody.toLowerCase();
|
|
46480
|
+
if (status === 422) {
|
|
46481
|
+
if (lower.includes("not available") || lower.includes("model not found") || lower.includes("not supported")) {
|
|
46482
|
+
return true;
|
|
46483
|
+
}
|
|
46484
|
+
}
|
|
46485
|
+
if (status === 400) {
|
|
46486
|
+
if (lower.includes("model not found") || lower.includes("not registered") || lower.includes("does not exist") || lower.includes("unknown model") || lower.includes("unsupported model") || lower.includes("no healthy deployment") || lower.includes("requires a google cloud project") || lower.includes("unsupported_client")) {
|
|
46487
|
+
return true;
|
|
46488
|
+
}
|
|
46489
|
+
if (provider?.toLowerCase().includes("antigravity") && lower.includes("invalid argument")) {
|
|
46490
|
+
return true;
|
|
46491
|
+
}
|
|
46492
|
+
if (isProvider(provider, "opencodezen") && (lower.includes("upstream request failed") || lower.includes("error from provider ("))) {
|
|
46493
|
+
return true;
|
|
46494
|
+
}
|
|
46495
|
+
}
|
|
46496
|
+
if (status === 500) {
|
|
46497
|
+
if (lower.includes("insufficient balance") || lower.includes("insufficient credit") || lower.includes("quota exceeded") || lower.includes("billing")) {
|
|
46498
|
+
return true;
|
|
46499
|
+
}
|
|
46500
|
+
}
|
|
46501
|
+
return false;
|
|
46502
|
+
}
|
|
46503
|
+
function exhaustedChainStatus(errors) {
|
|
46504
|
+
if (errors.length === 0)
|
|
46505
|
+
return 400;
|
|
46506
|
+
const isTransient = (e) => {
|
|
46507
|
+
if (e.status === 429 || e.status === 503)
|
|
46508
|
+
return true;
|
|
46509
|
+
if (hasQuotaExhaustionWording(e.message))
|
|
46510
|
+
return true;
|
|
46511
|
+
const upstream = e.status === 400 ? extractUpstreamStatus(e.message) : undefined;
|
|
46512
|
+
return upstream === 429 || upstream === 503;
|
|
46513
|
+
};
|
|
46514
|
+
return errors.every(isTransient) ? 503 : 400;
|
|
46515
|
+
}
|
|
46516
|
+
function isProvider(provider, needle) {
|
|
46517
|
+
if (!provider)
|
|
46518
|
+
return false;
|
|
46519
|
+
return provider.toLowerCase().replace(/[^a-z0-9]/g, "").includes(needle);
|
|
46520
|
+
}
|
|
46521
|
+
function parseErrorMessage(body) {
|
|
46522
|
+
try {
|
|
46523
|
+
const parsed = JSON.parse(body);
|
|
46524
|
+
if (typeof parsed.error === "string")
|
|
46525
|
+
return parsed.error;
|
|
46526
|
+
if (typeof parsed.error?.message === "string")
|
|
46527
|
+
return parsed.error.message;
|
|
46528
|
+
if (typeof parsed.message === "string")
|
|
46529
|
+
return parsed.message;
|
|
46530
|
+
} catch {}
|
|
46531
|
+
return body;
|
|
46532
|
+
}
|
|
46533
|
+
function truncate(s, max) {
|
|
46534
|
+
return s.length > max ? `${s.slice(0, max)}...` : s;
|
|
46535
|
+
}
|
|
46536
|
+
var init_fallback_handler = __esm(() => {
|
|
46537
|
+
init_logger();
|
|
46538
|
+
init_composed_handler();
|
|
46539
|
+
init_anthropic_error();
|
|
46540
|
+
init_quota_exhaustion();
|
|
45352
46541
|
});
|
|
45353
46542
|
|
|
45354
46543
|
// src/handlers/shared/thinking-signature.ts
|
|
@@ -45380,36 +46569,6 @@ function stripUnsignedThinkingBlocks(messages) {
|
|
|
45380
46569
|
}
|
|
45381
46570
|
|
|
45382
46571
|
// src/handlers/native-handler.ts
|
|
45383
|
-
async function resolveAdvisorKeys() {
|
|
45384
|
-
const keyFromAuthority = async (name) => {
|
|
45385
|
-
try {
|
|
45386
|
-
const auth = await credentials.getRequestAuth(name, { model: "" });
|
|
45387
|
-
const k = auth.headers.Authorization?.replace(/^Bearer\s+/i, "") || auth.headers["x-api-key"];
|
|
45388
|
-
return k || undefined;
|
|
45389
|
-
} catch {
|
|
45390
|
-
return;
|
|
45391
|
-
}
|
|
45392
|
-
};
|
|
45393
|
-
const geminiKey = async () => {
|
|
45394
|
-
const local = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || getApiKey("GEMINI_API_KEY");
|
|
45395
|
-
if (local)
|
|
45396
|
-
return local;
|
|
45397
|
-
if (hasOpSources()) {
|
|
45398
|
-
const r = await resolveOpKeyForEnvVars(new Set(["GEMINI_API_KEY", "GOOGLE_API_KEY"]), {
|
|
45399
|
-
onAuthFailure: "skip"
|
|
45400
|
-
});
|
|
45401
|
-
return r.GEMINI_API_KEY || r.GOOGLE_API_KEY || undefined;
|
|
45402
|
-
}
|
|
45403
|
-
return;
|
|
45404
|
-
};
|
|
45405
|
-
const [openrouter, google, openai] = await Promise.all([
|
|
45406
|
-
keyFromAuthority("openrouter"),
|
|
45407
|
-
geminiKey(),
|
|
45408
|
-
keyFromAuthority("openai")
|
|
45409
|
-
]);
|
|
45410
|
-
return { openrouter, google, openai };
|
|
45411
|
-
}
|
|
45412
|
-
|
|
45413
46572
|
class NativeHandler {
|
|
45414
46573
|
apiKey;
|
|
45415
46574
|
baseUrl;
|
|
@@ -45428,65 +46587,7 @@ class NativeHandler {
|
|
|
45428
46587
|
if (strippedThinking > 0) {
|
|
45429
46588
|
log(`[Native] stripped ${strippedThinking} unsigned thinking block(s) from history for ${target} (foreign-provider origin)`);
|
|
45430
46589
|
}
|
|
45431
|
-
const
|
|
45432
|
-
let advisorSwapped = null;
|
|
45433
|
-
let advisorRewrittenIds = [];
|
|
45434
|
-
if (advisorCfg.enabled) {
|
|
45435
|
-
advisorSwapped = swapAdvisorToolInBody(payload);
|
|
45436
|
-
if (advisorSwapped) {
|
|
45437
|
-
log("[Native][advisor-swap] replaced advisor_20260301 with regular tool 'advisor'");
|
|
45438
|
-
logAdvisorEvent(advisorCfg, {
|
|
45439
|
-
kind: "swap_applied",
|
|
45440
|
-
model: target,
|
|
45441
|
-
originalTool: advisorSwapped.originalTool,
|
|
45442
|
-
regularTool: advisorSwapped.regularTool
|
|
45443
|
-
});
|
|
45444
|
-
}
|
|
45445
|
-
if (advisorCfg.models && advisorCfg.models.length > 0) {
|
|
45446
|
-
const pendingIds = findPendingAdvisorToolResults(payload);
|
|
45447
|
-
if (pendingIds.length > 0) {
|
|
45448
|
-
const adviceMap = new Map;
|
|
45449
|
-
for (const id of pendingIds) {
|
|
45450
|
-
const advisorKeys = await resolveAdvisorKeys();
|
|
45451
|
-
const advice = await fetchMultiModelAdvice(id, payload.messages, advisorCfg.models, advisorCfg.collector ?? null, {
|
|
45452
|
-
...advisorKeys,
|
|
45453
|
-
anthropic: originalHeaders["x-api-key"]
|
|
45454
|
-
});
|
|
45455
|
-
adviceMap.set(id, advice);
|
|
45456
|
-
}
|
|
45457
|
-
advisorRewrittenIds = rewriteAdvisorToolResults(payload, (id) => adviceMap.get(id) ?? stubAdvisorAdvice(id));
|
|
45458
|
-
if (advisorRewrittenIds.length > 0) {
|
|
45459
|
-
log(`[Native][advisor] rewrote ${advisorRewrittenIds.length} tool_result(s) with multi-model advice from [${advisorCfg.models.join(", ")}]${advisorCfg.collector ? ` (collector: ${advisorCfg.collector})` : " (no collector)"}`);
|
|
45460
|
-
logAdvisorEvent(advisorCfg, {
|
|
45461
|
-
kind: "multi_model_rewrite",
|
|
45462
|
-
ids: advisorRewrittenIds,
|
|
45463
|
-
models: advisorCfg.models,
|
|
45464
|
-
collector: advisorCfg.collector,
|
|
45465
|
-
model: target
|
|
45466
|
-
});
|
|
45467
|
-
}
|
|
45468
|
-
}
|
|
45469
|
-
} else {
|
|
45470
|
-
advisorRewrittenIds = rewriteAdvisorToolResults(payload, stubAdvisorAdvice);
|
|
45471
|
-
if (advisorRewrittenIds.length > 0) {
|
|
45472
|
-
log(`[Native][advisor-swap] rewrote ${advisorRewrittenIds.length} error tool_result(s) with stub advice: ${advisorRewrittenIds.join(", ")}`);
|
|
45473
|
-
logAdvisorEvent(advisorCfg, {
|
|
45474
|
-
kind: "tool_result_rewritten",
|
|
45475
|
-
ids: advisorRewrittenIds,
|
|
45476
|
-
model: target
|
|
45477
|
-
});
|
|
45478
|
-
}
|
|
45479
|
-
}
|
|
45480
|
-
if (advisorCfg.dumpBodies) {
|
|
45481
|
-
logAdvisorEvent(advisorCfg, {
|
|
45482
|
-
kind: "request_body",
|
|
45483
|
-
swapApplied: !!advisorSwapped,
|
|
45484
|
-
rewrittenIds: advisorRewrittenIds,
|
|
45485
|
-
model: target,
|
|
45486
|
-
body: trimForLog(payload)
|
|
45487
|
-
});
|
|
45488
|
-
}
|
|
45489
|
-
}
|
|
46590
|
+
const advisorSwapped = c.get(ADVISOR_SWAPPED_CONTEXT_KEY) === true;
|
|
45490
46591
|
log(`
|
|
45491
46592
|
=== [NATIVE] Claude Code \u2192 Anthropic API Request ===`);
|
|
45492
46593
|
log(`[Native] x-api-key: ${originalHeaders["x-api-key"] ? maskCredential(originalHeaders["x-api-key"]) : "(not set)"}`);
|
|
@@ -45520,7 +46621,7 @@ class NativeHandler {
|
|
|
45520
46621
|
const { stripped, changed } = stripAdvisorBeta(incomingBeta);
|
|
45521
46622
|
if (changed) {
|
|
45522
46623
|
log(`[Native][advisor-swap] stripped advisor-tool beta; before=${incomingBeta} after=${stripped ?? "(empty)"}`);
|
|
45523
|
-
logAdvisorEvent(
|
|
46624
|
+
logAdvisorEvent(loadAdvisorSwapConfig(this.advisorModels, this.advisorCollector), {
|
|
45524
46625
|
kind: "beta_stripped",
|
|
45525
46626
|
before: incomingBeta,
|
|
45526
46627
|
after: stripped ?? ""
|
|
@@ -45557,7 +46658,6 @@ class NativeHandler {
|
|
|
45557
46658
|
controller.enqueue(value);
|
|
45558
46659
|
const chunkText = decoder.decode(value, { stream: true });
|
|
45559
46660
|
buffer += chunkText;
|
|
45560
|
-
recordAdvisorEventsFromChunk(advisorCfg, chunkText);
|
|
45561
46661
|
const lines = buffer.split(`
|
|
45562
46662
|
`);
|
|
45563
46663
|
buffer = lines.pop() || "";
|
|
@@ -45587,11 +46687,6 @@ class NativeHandler {
|
|
|
45587
46687
|
log(`
|
|
45588
46688
|
=== [NATIVE] Response ===`);
|
|
45589
46689
|
log(JSON.stringify(data, null, 2));
|
|
45590
|
-
if (advisorCfg.enabled) {
|
|
45591
|
-
try {
|
|
45592
|
-
recordAdvisorEventsFromChunk(advisorCfg, JSON.stringify(data));
|
|
45593
|
-
} catch {}
|
|
45594
|
-
}
|
|
45595
46690
|
const responseHeaders = { "Content-Type": "application/json" };
|
|
45596
46691
|
if (anthropicResponse.headers.has("anthropic-version")) {
|
|
45597
46692
|
responseHeaders["anthropic-version"] = anthropicResponse.headers.get("anthropic-version");
|
|
@@ -45604,30 +46699,10 @@ class NativeHandler {
|
|
|
45604
46699
|
}
|
|
45605
46700
|
async shutdown() {}
|
|
45606
46701
|
}
|
|
45607
|
-
function trimForLog(payload) {
|
|
45608
|
-
const TEXT_TRUNC = 400;
|
|
45609
|
-
const clone = structuredClone(payload);
|
|
45610
|
-
const trimStr = (s) => typeof s === "string" && s.length > TEXT_TRUNC ? `${s.slice(0, TEXT_TRUNC)}\u2026 [+${s.length - TEXT_TRUNC} chars]` : s;
|
|
45611
|
-
const walk = (v) => {
|
|
45612
|
-
if (typeof v === "string")
|
|
45613
|
-
return trimStr(v);
|
|
45614
|
-
if (Array.isArray(v))
|
|
45615
|
-
return v.map(walk);
|
|
45616
|
-
if (v && typeof v === "object") {
|
|
45617
|
-
const out = {};
|
|
45618
|
-
for (const [k, val] of Object.entries(v))
|
|
45619
|
-
out[k] = walk(val);
|
|
45620
|
-
return out;
|
|
45621
|
-
}
|
|
45622
|
-
return v;
|
|
45623
|
-
};
|
|
45624
|
-
return walk(clone);
|
|
45625
|
-
}
|
|
45626
46702
|
var init_native_handler = __esm(() => {
|
|
45627
46703
|
init_authority();
|
|
45628
|
-
init_op_source();
|
|
45629
46704
|
init_logger();
|
|
45630
|
-
|
|
46705
|
+
init_advisor_decorator();
|
|
45631
46706
|
init_native_handler_advisor();
|
|
45632
46707
|
init_anthropic_error();
|
|
45633
46708
|
});
|
|
@@ -46468,6 +47543,10 @@ async function createProxyServer(port, _openrouterApiKey, model, monitorMode = f
|
|
|
46468
47543
|
log(`[Proxy] behavior hooks load skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
46469
47544
|
}
|
|
46470
47545
|
const nativeHandler = new NativeHandler(anthropicApiKey, options.advisorModels, options.advisorCollector);
|
|
47546
|
+
const advisorPresence = createAdvisorPresenceMonitor();
|
|
47547
|
+
const withAdvisor = (handler, presence) => withAdvisorSwap(handler, loadAdvisorSwapConfig(options.advisorModels, options.advisorCollector), {
|
|
47548
|
+
presence
|
|
47549
|
+
});
|
|
46471
47550
|
const requestShapingOpts = {
|
|
46472
47551
|
effortOverride: isEffortLevel(options.effortOverride) ? options.effortOverride : undefined,
|
|
46473
47552
|
modelParams: options.modelParams,
|
|
@@ -46877,9 +47956,9 @@ ${plan.hint}` : `[Route] ${plan.reason}`;
|
|
|
46877
47956
|
if (!monitorMode && options.classifier?.enabled && isAutoModeClassifierRequest(body)) {
|
|
46878
47957
|
log(`[Classifier] auto-mode permission classifier \u2192 native Anthropic (model ${body.model} \u2192 ${options.classifier.model})`);
|
|
46879
47958
|
rewriteClassifierForNative(body, options.classifier.model);
|
|
46880
|
-
return nativeHandler.handle(c, body);
|
|
47959
|
+
return await withAdvisor(nativeHandler).handle(c, body);
|
|
46881
47960
|
}
|
|
46882
|
-
const handler = await getHandlerForRequest(body.model);
|
|
47961
|
+
const handler = withAdvisor(await getHandlerForRequest(body.model), advisorPresence);
|
|
46883
47962
|
return await handler.handle(c, body);
|
|
46884
47963
|
} catch (e) {
|
|
46885
47964
|
log(`[Proxy] Error: ${e}`);
|
|
@@ -46935,8 +48014,10 @@ var init_proxy_server = __esm(() => {
|
|
|
46935
48014
|
init_authority();
|
|
46936
48015
|
init_hooks();
|
|
46937
48016
|
init_behavior();
|
|
48017
|
+
init_advisor_decorator();
|
|
46938
48018
|
init_composed_handler();
|
|
46939
48019
|
init_fallback_handler();
|
|
48020
|
+
init_native_handler_advisor();
|
|
46940
48021
|
init_native_handler();
|
|
46941
48022
|
init_anthropic_error();
|
|
46942
48023
|
init_logger();
|
|
@@ -56292,7 +57373,11 @@ function parseAdvisorFlag(value) {
|
|
|
56292
57373
|
} else {
|
|
56293
57374
|
collector = collectorPart;
|
|
56294
57375
|
}
|
|
56295
|
-
return {
|
|
57376
|
+
return {
|
|
57377
|
+
models,
|
|
57378
|
+
collector,
|
|
57379
|
+
collectorDefaulted: models.length > 1 && collectorPart === undefined
|
|
57380
|
+
};
|
|
56296
57381
|
}
|
|
56297
57382
|
async function parseArgs(args) {
|
|
56298
57383
|
const config = {
|
|
@@ -56440,7 +57525,8 @@ async function parseArgs(args) {
|
|
|
56440
57525
|
const parsed = parseAdvisorFlag(modelsArg);
|
|
56441
57526
|
config.advisorModels = parsed.models;
|
|
56442
57527
|
config.advisorCollector = parsed.collector;
|
|
56443
|
-
config.
|
|
57528
|
+
config.advisorCollectorDefaulted = parsed.collectorDefaulted;
|
|
57529
|
+
config.advisor = true;
|
|
56444
57530
|
} else if (arg === "--stdin") {
|
|
56445
57531
|
config.stdin = true;
|
|
56446
57532
|
} else if (arg === "--free") {
|
|
@@ -56674,10 +57760,10 @@ Usage: claudish --models --provider <slug>`);
|
|
|
56674
57760
|
if (config._sawVerbose && !config.interactive && !config.claudeArgs.includes("--verbose") && !config.claudeArgs.includes("-v")) {
|
|
56675
57761
|
config.claudeArgs.push("--verbose");
|
|
56676
57762
|
}
|
|
57763
|
+
if ((config.monitor || config.advisor) && process.env.ANTHROPIC_API_KEY?.includes("placeholder")) {
|
|
57764
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
57765
|
+
}
|
|
56677
57766
|
if (config.monitor) {
|
|
56678
|
-
if (process.env.ANTHROPIC_API_KEY?.includes("placeholder")) {
|
|
56679
|
-
delete process.env.ANTHROPIC_API_KEY;
|
|
56680
|
-
}
|
|
56681
57767
|
if (!config.quiet) {
|
|
56682
57768
|
console.log("[claudish] Monitor mode enabled - proxying to real Anthropic API");
|
|
56683
57769
|
console.log("[claudish] Using Claude Code's native authentication");
|
|
@@ -57644,7 +58730,7 @@ ${h("OPTIONS")}
|
|
|
57644
58730
|
${green("--stdin")} Read prompt from stdin (large prompts / piping)
|
|
57645
58731
|
${green("--free")} Show only FREE models in the interactive selector
|
|
57646
58732
|
${green("--monitor")} Monitor mode - proxy to REAL Anthropic API and log traffic
|
|
57647
|
-
${green("--advisor")} ${yellow('"m1,m2[:collector]"')} Multi-model advisor replacement (
|
|
58733
|
+
${green("--advisor")} ${yellow('"m1,m2[:collector]"')} Multi-model advisor replacement (works with any --model)
|
|
57648
58734
|
${green("--model-params")} ${yellow('"k=v,..."')} Extra request params merged into the payload (e.g. reasoning.mode=pro)
|
|
57649
58735
|
${green("--effort-override")} ${yellow("<level>")} Pin reasoning effort verbatim, skipping the per-model clamp
|
|
57650
58736
|
${green("--pro-on-ultracode")} Apply the model's catalog preset while in ultracode (opt-in)
|
|
@@ -65606,12 +66692,34 @@ function shouldHideIncidentalAnthropicKey(config, env = process.env) {
|
|
|
65606
66692
|
return false;
|
|
65607
66693
|
return !wantsAnthropicApiBilling(config, env);
|
|
65608
66694
|
}
|
|
66695
|
+
function scrubInheritedClaudishPlaceholders(env) {
|
|
66696
|
+
const removed = [];
|
|
66697
|
+
for (const name of ["ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY"]) {
|
|
66698
|
+
if (isClaudishPlaceholderCredential(name, env[name])) {
|
|
66699
|
+
delete env[name];
|
|
66700
|
+
removed.push(name);
|
|
66701
|
+
}
|
|
66702
|
+
}
|
|
66703
|
+
return { removed };
|
|
66704
|
+
}
|
|
66705
|
+
function isClaudishPlaceholderCredential(name, value) {
|
|
66706
|
+
if (name === "ANTHROPIC_API_KEY")
|
|
66707
|
+
return value === CLAUDISH_PLACEHOLDER_API_KEY;
|
|
66708
|
+
if (name === "ANTHROPIC_AUTH_TOKEN")
|
|
66709
|
+
return value === CLAUDISH_PLACEHOLDER_AUTH_TOKEN;
|
|
66710
|
+
return false;
|
|
66711
|
+
}
|
|
66712
|
+
function isRealAnthropicEnvCredential(env, name) {
|
|
66713
|
+
const value = env[name];
|
|
66714
|
+
return Boolean(value) && !isClaudishPlaceholderCredential(name, value);
|
|
66715
|
+
}
|
|
65609
66716
|
function hasResolvableAnthropicAuth(deps = {}) {
|
|
65610
66717
|
const env = deps.env ?? process.env;
|
|
65611
66718
|
const fileExists = deps.fileExists ?? existsSync31;
|
|
65612
66719
|
const keychainProbe = deps.keychainProbe ?? defaultKeychainAnthropicProbe;
|
|
65613
|
-
if (env
|
|
66720
|
+
if (isRealAnthropicEnvCredential(env, "ANTHROPIC_API_KEY") || isRealAnthropicEnvCredential(env, "ANTHROPIC_AUTH_TOKEN")) {
|
|
65614
66721
|
return true;
|
|
66722
|
+
}
|
|
65615
66723
|
if (fileExists(join41(homedir36(), ".claude", ".credentials.json")))
|
|
65616
66724
|
return true;
|
|
65617
66725
|
return keychainProbe();
|
|
@@ -65619,8 +66727,11 @@ function hasResolvableAnthropicAuth(deps = {}) {
|
|
|
65619
66727
|
function shouldPreserveNativeAuth(config) {
|
|
65620
66728
|
return hasNativeAnthropicMapping(config) || classifierPassthroughEnabled(config) && hasResolvableAnthropicAuth();
|
|
65621
66729
|
}
|
|
66730
|
+
function isAdvisorNativeSession(config) {
|
|
66731
|
+
return Boolean(config.advisor) && !config.model && !config.modelChain;
|
|
66732
|
+
}
|
|
65622
66733
|
function isProxyAuthMode(config) {
|
|
65623
|
-
return !config.monitor && !shouldPreserveNativeAuth(config);
|
|
66734
|
+
return !config.monitor && !isAdvisorNativeSession(config) && !shouldPreserveNativeAuth(config);
|
|
65624
66735
|
}
|
|
65625
66736
|
function managedSettingsPath() {
|
|
65626
66737
|
if (isWindows2()) {
|
|
@@ -66017,9 +67128,48 @@ function resolveContextWindowEnv(realWindow, processEnv = process.env) {
|
|
|
66017
67128
|
notice: `[claudish] Model's real context window (${realWindow.toLocaleString()}) is below ` + `Claude Code's ${MIN_AUTO_COMPACT_WINDOW.toLocaleString()}-token auto-compact floor \u2014 ` + "leaving CLAUDE_CODE_AUTO_COMPACT_WINDOW unset so native auto-compaction stays on."
|
|
66018
67129
|
};
|
|
66019
67130
|
}
|
|
67131
|
+
function resolveAdvisorToolEnv(config, processEnv = process.env) {
|
|
67132
|
+
if (!config.advisor)
|
|
67133
|
+
return { vars: {}, source: "off" };
|
|
67134
|
+
if (processEnv[ADVISOR_TOOL_ENV_VAR] !== undefined)
|
|
67135
|
+
return { vars: {}, source: "inherited" };
|
|
67136
|
+
return { vars: { [ADVISOR_TOOL_ENV_VAR]: "1" }, source: "claudish" };
|
|
67137
|
+
}
|
|
67138
|
+
function discoverUserAdvisorModel(claudeArgs = [], cwd = process.cwd()) {
|
|
67139
|
+
const sources = userSettingsFileCandidates(cwd).filter((file) => existsSync31(file));
|
|
67140
|
+
const idx = claudeArgs.indexOf("--settings");
|
|
67141
|
+
const settingsArg = idx === -1 ? undefined : claudeArgs[idx + 1];
|
|
67142
|
+
if (settingsArg)
|
|
67143
|
+
sources.push(settingsArg);
|
|
67144
|
+
const managed = managedSettingsPath();
|
|
67145
|
+
if (existsSync31(managed))
|
|
67146
|
+
sources.push(managed);
|
|
67147
|
+
let effective;
|
|
67148
|
+
for (const source of sources) {
|
|
67149
|
+
const layer = parseSettingsArgSafe(source);
|
|
67150
|
+
if (!layer || !("advisorModel" in layer))
|
|
67151
|
+
continue;
|
|
67152
|
+
const value = layer.advisorModel;
|
|
67153
|
+
effective = typeof value === "string" && value !== "" ? value : undefined;
|
|
67154
|
+
}
|
|
67155
|
+
return effective;
|
|
67156
|
+
}
|
|
67157
|
+
function resolveAdvisorModelArg(config, cwd = process.cwd()) {
|
|
67158
|
+
if (!config.advisor)
|
|
67159
|
+
return { args: [], source: "off" };
|
|
67160
|
+
const userChoice = discoverUserAdvisorModel(config.claudeArgs, cwd);
|
|
67161
|
+
if (userChoice)
|
|
67162
|
+
return { args: [], model: userChoice, source: "inherited" };
|
|
67163
|
+
return {
|
|
67164
|
+
args: ["--advisor", CLAUDISH_CHILD_ADVISOR_MODEL],
|
|
67165
|
+
model: CLAUDISH_CHILD_ADVISOR_MODEL,
|
|
67166
|
+
source: "claudish"
|
|
67167
|
+
};
|
|
67168
|
+
}
|
|
66020
67169
|
async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
|
|
66021
67170
|
const hasProfileMappings = config.modelOpus || config.modelSonnet || config.modelHaiku || config.modelSubagent;
|
|
66022
|
-
const
|
|
67171
|
+
const advisorNativeSession = isAdvisorNativeSession(config);
|
|
67172
|
+
const modelId = config.model || (hasProfileMappings || config.monitor || advisorNativeSession ? undefined : "unknown");
|
|
66023
67173
|
const portMatch = proxyUrl.match(/:(\d+)/);
|
|
66024
67174
|
const port = portMatch ? portMatch[1] : "unknown";
|
|
66025
67175
|
const proxyAuthMode = isProxyAuthMode(config);
|
|
@@ -66031,6 +67181,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
|
|
|
66031
67181
|
return 1;
|
|
66032
67182
|
}
|
|
66033
67183
|
const userStatusLineCommand = discoverUserStatusLineCommand(config.claudeArgs);
|
|
67184
|
+
const advisorModelArg = resolveAdvisorModelArg(config);
|
|
66034
67185
|
const {
|
|
66035
67186
|
path: tempSettingsPath,
|
|
66036
67187
|
statusLine,
|
|
@@ -66039,6 +67190,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
|
|
|
66039
67190
|
mergeUserSettingsIfPresent(config, tempSettingsPath, statusLine, proxyAuthMode);
|
|
66040
67191
|
const claudeArgs = [];
|
|
66041
67192
|
claudeArgs.push("--settings", tempSettingsPath);
|
|
67193
|
+
claudeArgs.push(...advisorModelArg.args);
|
|
66042
67194
|
if (config.interactive) {
|
|
66043
67195
|
if (config.autoApprove) {
|
|
66044
67196
|
claudeArgs.push("--dangerously-skip-permissions");
|
|
@@ -66064,12 +67216,14 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
|
|
|
66064
67216
|
}
|
|
66065
67217
|
const isLocalModel = modelId ? modelId.startsWith("ollama/") || modelId.startsWith("ollama:") || modelId.startsWith("lmstudio/") || modelId.startsWith("lmstudio:") || modelId.startsWith("vllm/") || modelId.startsWith("vllm:") || modelId.startsWith("mlx/") || modelId.startsWith("mlx:") || modelId.startsWith("http://") || modelId.startsWith("https://") : false;
|
|
66066
67218
|
const modelDisplayName = modelId || config.profile || "default";
|
|
67219
|
+
const advisorToolEnv = resolveAdvisorToolEnv(config);
|
|
66067
67220
|
const env = {
|
|
66068
67221
|
...process.env,
|
|
66069
67222
|
ANTHROPIC_BASE_URL: proxyUrl,
|
|
66070
67223
|
[ENV.CLAUDISH_ACTIVE_MODEL_NAME]: modelDisplayName,
|
|
66071
67224
|
CLAUDISH_IS_LOCAL: isLocalModel ? "true" : "false",
|
|
66072
|
-
[ENV.CLAUDISH_TOKEN_FILE]: tokenFilePath
|
|
67225
|
+
[ENV.CLAUDISH_TOKEN_FILE]: tokenFilePath,
|
|
67226
|
+
...advisorToolEnv.vars
|
|
66073
67227
|
};
|
|
66074
67228
|
if (modelId) {
|
|
66075
67229
|
const parsedSpec = parseModelSpec(modelId);
|
|
@@ -66078,9 +67232,15 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
|
|
|
66078
67232
|
env[ENV.CLAUDISH_PROVIDER_NAME] = providerDisplayName;
|
|
66079
67233
|
}
|
|
66080
67234
|
}
|
|
67235
|
+
if (advisorToolEnv.source !== "off") {
|
|
67236
|
+
log(`[claude-runner] ${ADVISOR_TOOL_ENV_VAR}=${env[ADVISOR_TOOL_ENV_VAR]} (${advisorToolEnv.source})`);
|
|
67237
|
+
}
|
|
67238
|
+
if (advisorModelArg.source !== "off") {
|
|
67239
|
+
log(`[claude-runner] child advisor model=${advisorModelArg.model} (${advisorModelArg.source}` + `${advisorModelArg.source === "inherited" ? "; user setting kept, no --advisor passed" : " via --advisor"})`);
|
|
67240
|
+
}
|
|
66081
67241
|
let hidAnthropicApiKey = false;
|
|
66082
67242
|
delete env.CLAUDECODE;
|
|
66083
|
-
if (config.monitor) {
|
|
67243
|
+
if (config.monitor || advisorNativeSession) {
|
|
66084
67244
|
delete env.ANTHROPIC_API_KEY;
|
|
66085
67245
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
66086
67246
|
if (modelId) {
|
|
@@ -66093,7 +67253,11 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
|
|
|
66093
67253
|
env[ENV.ANTHROPIC_SMALL_FAST_MODEL] = modelId;
|
|
66094
67254
|
}
|
|
66095
67255
|
if (shouldPreserveNativeAuth(config)) {
|
|
66096
|
-
|
|
67256
|
+
const scrubbed = scrubInheritedClaudishPlaceholders(env);
|
|
67257
|
+
if (scrubbed.removed.length > 0) {
|
|
67258
|
+
log(`[claude-runner] Removed inherited claudish placeholder credentials: ${scrubbed.removed.join(", ")}`);
|
|
67259
|
+
}
|
|
67260
|
+
if (shouldHideIncidentalAnthropicKey(config, env)) {
|
|
66097
67261
|
delete env.ANTHROPIC_API_KEY;
|
|
66098
67262
|
hidAnthropicApiKey = true;
|
|
66099
67263
|
}
|
|
@@ -66101,8 +67265,8 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
|
|
|
66101
67265
|
if (classifierPassthroughEnabled(config)) {
|
|
66102
67266
|
console.error("[claudish] classifier passthrough enabled but no Anthropic credentials detected \u2014 " + "classifier requests will fail to authenticate against api.anthropic.com. " + "Map a role to a native Claude model (e.g. --model-sonnet claude-sonnet-5) or set ANTHROPIC_API_KEY.");
|
|
66103
67267
|
}
|
|
66104
|
-
env.ANTHROPIC_API_KEY =
|
|
66105
|
-
env.ANTHROPIC_AUTH_TOKEN =
|
|
67268
|
+
env.ANTHROPIC_API_KEY = CLAUDISH_PLACEHOLDER_API_KEY;
|
|
67269
|
+
env.ANTHROPIC_AUTH_TOKEN = CLAUDISH_PLACEHOLDER_AUTH_TOKEN;
|
|
66106
67270
|
const realWindow = await computeMainThreadContextWindow(config);
|
|
66107
67271
|
const contextEnv = resolveContextWindowEnv(realWindow, process.env);
|
|
66108
67272
|
Object.assign(env, contextEnv.vars);
|
|
@@ -66316,7 +67480,7 @@ var restoreTerminal = null, macosKeychainAnthropicResult, defaultKeychainAnthrop
|
|
|
66316
67480
|
macosKeychainAnthropicResult = false;
|
|
66317
67481
|
}
|
|
66318
67482
|
return macosKeychainAnthropicResult;
|
|
66319
|
-
}, STALE_TOKEN_FILE_MS, MAX_TOKEN_FILES_SCANNED = 4000, CLAUDE_CODE_DEFAULT_MAX_CONTEXT = 200000, USER_STATUS_LINE_TIMEOUT_SECONDS = 3, MIN_AUTO_COMPACT_WINDOW = 200000, SIGNAL_EXIT_NUMBERS;
|
|
67483
|
+
}, CLAUDISH_PLACEHOLDER_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx", CLAUDISH_PLACEHOLDER_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth", STALE_TOKEN_FILE_MS, MAX_TOKEN_FILES_SCANNED = 4000, CLAUDE_CODE_DEFAULT_MAX_CONTEXT = 200000, USER_STATUS_LINE_TIMEOUT_SECONDS = 3, MIN_AUTO_COMPACT_WINDOW = 200000, ADVISOR_TOOL_ENV_VAR = "CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL", CLAUDISH_CHILD_ADVISOR_MODEL = "sonnet", SIGNAL_EXIT_NUMBERS;
|
|
66320
67484
|
var init_claude_runner = __esm(() => {
|
|
66321
67485
|
init_model_catalog();
|
|
66322
67486
|
init_config2();
|
|
@@ -66601,6 +67765,226 @@ var init_catalog_warm = __esm(() => {
|
|
|
66601
67765
|
];
|
|
66602
67766
|
});
|
|
66603
67767
|
|
|
67768
|
+
// src/advisor-startup.ts
|
|
67769
|
+
function isClaudeCodeBoolTrue(value) {
|
|
67770
|
+
if (value === undefined)
|
|
67771
|
+
return false;
|
|
67772
|
+
const v = value.trim().toLowerCase();
|
|
67773
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
67774
|
+
}
|
|
67775
|
+
function routeAdvisorModel(modelSpec, role) {
|
|
67776
|
+
return advisorRouteFor(modelSpec, role);
|
|
67777
|
+
}
|
|
67778
|
+
function advisorCredentialEnvName(credential) {
|
|
67779
|
+
if (credential === "anthropic")
|
|
67780
|
+
return "ANTHROPIC_API_KEY";
|
|
67781
|
+
return getProviderByName(ADVISOR_AUTHORITY_PROVIDER[credential])?.apiKeyEnvVar || `a ${credential} API key`;
|
|
67782
|
+
}
|
|
67783
|
+
function alsoAcceptedEnvName(credential) {
|
|
67784
|
+
return credential === "google" ? "GOOGLE_API_KEY" : null;
|
|
67785
|
+
}
|
|
67786
|
+
async function resolveAdvisorCredentials(needed) {
|
|
67787
|
+
const present = async (credential) => Boolean(await resolveAdvisorCredential(credential));
|
|
67788
|
+
const list = [...needed];
|
|
67789
|
+
const results = await Promise.all(list.map(present));
|
|
67790
|
+
const out = {};
|
|
67791
|
+
list.forEach((c, i) => {
|
|
67792
|
+
out[c] = results[i];
|
|
67793
|
+
});
|
|
67794
|
+
return out;
|
|
67795
|
+
}
|
|
67796
|
+
function advisorModelStatus(model, route, presence) {
|
|
67797
|
+
const credentialName = advisorCredentialEnvName(route.credential);
|
|
67798
|
+
if (route.unresolvedAlias) {
|
|
67799
|
+
return {
|
|
67800
|
+
model,
|
|
67801
|
+
route,
|
|
67802
|
+
callable: false,
|
|
67803
|
+
credentialName,
|
|
67804
|
+
unresolved: `${model} is a claudish alias, not a model id ${route.host} accepts, and the model ` + "catalog holds no id for it (it is cold or has never been fetched), so claudish would " + `have to POST "${route.unresolvedAlias}" verbatim`
|
|
67805
|
+
};
|
|
67806
|
+
}
|
|
67807
|
+
return {
|
|
67808
|
+
model,
|
|
67809
|
+
route,
|
|
67810
|
+
callable: presence[route.credential] === true,
|
|
67811
|
+
credentialName
|
|
67812
|
+
};
|
|
67813
|
+
}
|
|
67814
|
+
function describeMissingCredential2(s) {
|
|
67815
|
+
const also = alsoAcceptedEnvName(s.route.credential);
|
|
67816
|
+
return `${s.model} calls ${s.route.host} and needs ${s.credentialName}${also ? ` (or ${also})` : ""}; ` + "none found in env, config, keychain or 1Password";
|
|
67817
|
+
}
|
|
67818
|
+
function describeUncallable(s) {
|
|
67819
|
+
return s.unresolved ?? describeMissingCredential2(s);
|
|
67820
|
+
}
|
|
67821
|
+
function formatCarriesTools(format) {
|
|
67822
|
+
try {
|
|
67823
|
+
const out = format.convertTools({
|
|
67824
|
+
tools: [
|
|
67825
|
+
{
|
|
67826
|
+
name: "advisor",
|
|
67827
|
+
description: "startup probe",
|
|
67828
|
+
input_schema: { type: "object", properties: {}, additionalProperties: false }
|
|
67829
|
+
}
|
|
67830
|
+
]
|
|
67831
|
+
}, false);
|
|
67832
|
+
return Array.isArray(out) && out.length > 0;
|
|
67833
|
+
} catch {
|
|
67834
|
+
return true;
|
|
67835
|
+
}
|
|
67836
|
+
}
|
|
67837
|
+
function pinnedFormatForTransport(transport, modelName) {
|
|
67838
|
+
switch (transport) {
|
|
67839
|
+
case "ollamacloud":
|
|
67840
|
+
return new OllamaAPIFormat(modelName);
|
|
67841
|
+
default:
|
|
67842
|
+
return null;
|
|
67843
|
+
}
|
|
67844
|
+
}
|
|
67845
|
+
function resolveMainModelFact(model) {
|
|
67846
|
+
const native = nativeRouteFor(model);
|
|
67847
|
+
if (native)
|
|
67848
|
+
return { model, providerName: native.displayName, carriesTools: true };
|
|
67849
|
+
const resolution = resolveModelProvider(model);
|
|
67850
|
+
const definitionName = resolution.catalogName === "gemini" ? "google" : resolution.catalogName;
|
|
67851
|
+
const definition = definitionName ? getProviderByName(definitionName) : undefined;
|
|
67852
|
+
const format = definition ? pinnedFormatForTransport(definition.transport, resolution.modelName) : null;
|
|
67853
|
+
const providerName = resolution.category === "unknown" ? "unresolved (routed on the first request)" : definition?.displayName || resolution.providerName;
|
|
67854
|
+
return {
|
|
67855
|
+
model,
|
|
67856
|
+
providerName,
|
|
67857
|
+
carriesTools: format ? formatCarriesTools(format) : true,
|
|
67858
|
+
wireFormat: format?.getName()
|
|
67859
|
+
};
|
|
67860
|
+
}
|
|
67861
|
+
function mainModelSpecs(config) {
|
|
67862
|
+
if (config.modelChain && config.modelChain.length > 0)
|
|
67863
|
+
return [...config.modelChain];
|
|
67864
|
+
if (config.model)
|
|
67865
|
+
return [config.model];
|
|
67866
|
+
const tiers = [config.modelOpus, config.modelSonnet, config.modelHaiku, config.modelSubagent];
|
|
67867
|
+
return [...new Set(tiers.filter((m) => typeof m === "string" && m.length > 0))];
|
|
67868
|
+
}
|
|
67869
|
+
function modelIdentity(spec) {
|
|
67870
|
+
const model = parseModelSpec(spec).model.toLowerCase();
|
|
67871
|
+
const slash = model.lastIndexOf("/");
|
|
67872
|
+
return slash >= 0 ? model.slice(slash + 1) : model;
|
|
67873
|
+
}
|
|
67874
|
+
function panelMembersMatchingMain(panel, mainModels) {
|
|
67875
|
+
const main = new Set(mainModels.map(modelIdentity));
|
|
67876
|
+
return panel.filter((p) => main.has(modelIdentity(p)));
|
|
67877
|
+
}
|
|
67878
|
+
function refusalBeforeCredentials(facts) {
|
|
67879
|
+
const disable = facts.childEnv[ADVISOR_DISABLE_ENV_VAR];
|
|
67880
|
+
if (isClaudeCodeBoolTrue(disable)) {
|
|
67881
|
+
return `${ADVISOR_DISABLE_ENV_VAR}=${JSON.stringify(disable)} is set in your environment. ` + `Claude Code then never offers the advisor tool, and ${ADVISOR_TOOL_ENV_VAR} does not ` + `override it. Unset ${ADVISOR_DISABLE_ENV_VAR} to use --advisor.`;
|
|
67882
|
+
}
|
|
67883
|
+
const toolless = facts.mainModels.filter((m) => !m.carriesTools);
|
|
67884
|
+
if (toolless.length > 0) {
|
|
67885
|
+
const named = toolless.map((m) => `${m.model} (${m.providerName}${m.wireFormat ? `, ${m.wireFormat}` : ""})`).join(", ");
|
|
67886
|
+
return `the main model path cannot carry tools: ${named} drops every tool from the request, ` + "so the advisor tool can never reach the model. Choose a main model on a provider " + "that supports tool calls, or drop --advisor.";
|
|
67887
|
+
}
|
|
67888
|
+
if (facts.panel.length === 0) {
|
|
67889
|
+
return 'no advisor panel models were given. Use --advisor "model1[,model2][:collector]".';
|
|
67890
|
+
}
|
|
67891
|
+
return null;
|
|
67892
|
+
}
|
|
67893
|
+
function describeRoute(s) {
|
|
67894
|
+
return `${s.model} -> ${s.route.host} (${s.credentialName}, billed per token)`;
|
|
67895
|
+
}
|
|
67896
|
+
function decideAdvisorStartup(facts) {
|
|
67897
|
+
const early = refusalBeforeCredentials(facts);
|
|
67898
|
+
if (early)
|
|
67899
|
+
return { kind: "refuse", reason: early };
|
|
67900
|
+
const missingPanel = facts.panelStatus.filter((s) => !s.callable);
|
|
67901
|
+
if (missingPanel.length > 0) {
|
|
67902
|
+
return {
|
|
67903
|
+
kind: "refuse",
|
|
67904
|
+
reason: `advisor panel model${missingPanel.length > 1 ? "s" : ""} cannot be called: ` + `${missingPanel.map(describeUncallable).join("; ")}. ` + "Set the key, or remove the model from --advisor."
|
|
67905
|
+
};
|
|
67906
|
+
}
|
|
67907
|
+
let effectiveCollector = facts.collector;
|
|
67908
|
+
let collectorLine = "none";
|
|
67909
|
+
const cs = facts.collectorStatus;
|
|
67910
|
+
if (cs && !cs.callable) {
|
|
67911
|
+
if (!facts.collectorDefaulted) {
|
|
67912
|
+
return {
|
|
67913
|
+
kind: "refuse",
|
|
67914
|
+
reason: `collector ${describeUncallable(cs)}. ` + 'Set the key, name another collector ("a,b:collector"), or end the value with ":" for no collector.'
|
|
67915
|
+
};
|
|
67916
|
+
}
|
|
67917
|
+
effectiveCollector = null;
|
|
67918
|
+
collectorLine = cs.unresolved ? `none \u2014 ${cs.unresolved}; panel answers will be concatenated` : `none \u2014 the default collector ${cs.model} needs ${cs.credentialName}, which was not ` + "found; panel answers will be concatenated";
|
|
67919
|
+
} else if (cs) {
|
|
67920
|
+
collectorLine = describeRoute(cs);
|
|
67921
|
+
}
|
|
67922
|
+
const notice = ["[claudish] --advisor is on for this launch"];
|
|
67923
|
+
notice.push(" panel (panel calls never use a subscription):");
|
|
67924
|
+
for (const s of facts.panelStatus)
|
|
67925
|
+
notice.push(` ${describeRoute(s)}`);
|
|
67926
|
+
notice.push(` collector: ${collectorLine}`);
|
|
67927
|
+
const main = facts.mainModels.length === 0 ? "your Claude Code session" : facts.mainModels.map((m) => `${m.model} via ${m.providerName}`).join(" -> ");
|
|
67928
|
+
notice.push(` main model: ${main}`);
|
|
67929
|
+
const envValue = facts.childEnv[ADVISOR_TOOL_ENV_VAR];
|
|
67930
|
+
if (facts.toolEnv.source === "inherited") {
|
|
67931
|
+
notice.push(` ${ADVISOR_TOOL_ENV_VAR}=${JSON.stringify(envValue ?? "")} inherited from your environment` + (isClaudeCodeBoolTrue(envValue) ? "" : " \u2014 Claude Code reads this value as FALSE, so the advisor tool appears only if your main model qualifies for it without the override"));
|
|
67932
|
+
} else {
|
|
67933
|
+
notice.push(` ${ADVISOR_TOOL_ENV_VAR}=1 set by claudish`);
|
|
67934
|
+
}
|
|
67935
|
+
const includesMain = panelMembersMatchingMain(facts.panel, facts.mainModels.map((m) => m.model));
|
|
67936
|
+
if (includesMain.length > 0) {
|
|
67937
|
+
notice.push(` note: the panel includes the main model (${includesMain.join(", ")})`);
|
|
67938
|
+
}
|
|
67939
|
+
notice.push(" cost: each advisor call sends the FULL conversation to every panel model, uncached; " + "cost grows with panel size and transcript length");
|
|
67940
|
+
return { kind: "proceed", notice, effectiveCollector };
|
|
67941
|
+
}
|
|
67942
|
+
async function evaluateAdvisorStartup(config, toolEnv, childEnv = process.env, deps = { resolveCredentials: resolveAdvisorCredentials }) {
|
|
67943
|
+
if (!config.advisor)
|
|
67944
|
+
return null;
|
|
67945
|
+
const panel = config.advisorModels ?? [];
|
|
67946
|
+
const collector = config.advisorCollector ?? null;
|
|
67947
|
+
const mainModels = mainModelSpecs(config).map(resolveMainModelFact);
|
|
67948
|
+
const early = refusalBeforeCredentials({ childEnv, mainModels, panel });
|
|
67949
|
+
if (early)
|
|
67950
|
+
return { kind: "refuse", reason: early };
|
|
67951
|
+
let panelRoutes;
|
|
67952
|
+
let collectorRoute;
|
|
67953
|
+
try {
|
|
67954
|
+
panelRoutes = panel.map((m) => routeAdvisorModel(m, "panel"));
|
|
67955
|
+
collectorRoute = collector ? routeAdvisorModel(collector, "collector") : null;
|
|
67956
|
+
} catch (err) {
|
|
67957
|
+
return {
|
|
67958
|
+
kind: "refuse",
|
|
67959
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
67960
|
+
};
|
|
67961
|
+
}
|
|
67962
|
+
const needed = new Set(panelRoutes.map((r) => r.credential));
|
|
67963
|
+
if (collectorRoute)
|
|
67964
|
+
needed.add(collectorRoute.credential);
|
|
67965
|
+
const presence = await deps.resolveCredentials(needed);
|
|
67966
|
+
return decideAdvisorStartup({
|
|
67967
|
+
panel,
|
|
67968
|
+
collector,
|
|
67969
|
+
collectorDefaulted: config.advisorCollectorDefaulted === true,
|
|
67970
|
+
mainModels,
|
|
67971
|
+
childEnv,
|
|
67972
|
+
toolEnv,
|
|
67973
|
+
panelStatus: panel.map((m, i) => advisorModelStatus(m, panelRoutes[i], presence)),
|
|
67974
|
+
collectorStatus: collector && collectorRoute ? advisorModelStatus(collector, collectorRoute, presence) : null
|
|
67975
|
+
});
|
|
67976
|
+
}
|
|
67977
|
+
var ADVISOR_DISABLE_ENV_VAR = "CLAUDE_CODE_DISABLE_ADVISOR_TOOL";
|
|
67978
|
+
var init_advisor_startup = __esm(() => {
|
|
67979
|
+
init_ollama_api_format();
|
|
67980
|
+
init_claude_runner();
|
|
67981
|
+
init_native_handler_advisor();
|
|
67982
|
+
init_model_parser();
|
|
67983
|
+
init_native_route();
|
|
67984
|
+
init_provider_definitions();
|
|
67985
|
+
init_provider_resolver();
|
|
67986
|
+
});
|
|
67987
|
+
|
|
66604
67988
|
// src/tui/viz/text.ts
|
|
66605
67989
|
function columns(n, fn, arg = "width") {
|
|
66606
67990
|
if (!Number.isFinite(n))
|
|
@@ -69417,7 +70801,8 @@ Team Status`);
|
|
|
69417
70801
|
}
|
|
69418
70802
|
const rawArgs = process.argv.slice(2);
|
|
69419
70803
|
const explicitNoAutoApprove = rawArgs.includes("--no-auto-approve");
|
|
69420
|
-
|
|
70804
|
+
const explicitAutoApprove = rawArgs.includes("-y") || rawArgs.includes("--auto-approve");
|
|
70805
|
+
if (cliConfig.autoApprove && !explicitNoAutoApprove && !explicitAutoApprove && !cliConfig.stdin && cliConfig.interactive) {
|
|
69421
70806
|
await Promise.resolve().then(() => init_profile_config());
|
|
69422
70807
|
try {
|
|
69423
70808
|
const cfg = loadConfig();
|
|
@@ -69486,11 +70871,12 @@ Team Status`);
|
|
|
69486
70871
|
process.exit(1);
|
|
69487
70872
|
}
|
|
69488
70873
|
const hasProfileTiers = cliConfig.modelOpus || cliConfig.modelSonnet || cliConfig.modelHaiku || cliConfig.modelSubagent;
|
|
69489
|
-
|
|
70874
|
+
const advisorNativeSession = isAdvisorNativeSession(cliConfig);
|
|
70875
|
+
if (cliConfig.interactive && !cliConfig.monitor && !advisorNativeSession && !cliConfig.model && !hasProfileTiers) {
|
|
69490
70876
|
cliConfig.model = await traceSpan("startup:model-select", () => selectModel({ freeOnly: cliConfig.freeOnly }).catch(handlePromptExit), { mayIncludeUserPrompt: true });
|
|
69491
70877
|
console.log("");
|
|
69492
70878
|
}
|
|
69493
|
-
if (!cliConfig.interactive && !cliConfig.monitor && !cliConfig.model && !hasProfileTiers) {
|
|
70879
|
+
if (!cliConfig.interactive && !cliConfig.monitor && !advisorNativeSession && !cliConfig.model && !hasProfileTiers) {
|
|
69494
70880
|
console.error("Error: Model must be specified in non-interactive mode");
|
|
69495
70881
|
console.error("Use --model <model> flag, set CLAUDISH_MODEL env var, or use --profile");
|
|
69496
70882
|
console.error("Try: claudish --models");
|
|
@@ -69550,16 +70936,36 @@ Team Status`);
|
|
|
69550
70936
|
}
|
|
69551
70937
|
}
|
|
69552
70938
|
}
|
|
70939
|
+
const warmOutcome = await traceSpan("startup:catalog-warm", () => warmCatalogIfNeeded(cliConfig));
|
|
70940
|
+
if (warmOutcome === "hard_fail") {
|
|
70941
|
+
process.exit(1);
|
|
70942
|
+
}
|
|
70943
|
+
if (cliConfig.advisor) {
|
|
70944
|
+
await Promise.resolve().then(() => init_advisor_startup());
|
|
70945
|
+
const decision = await traceSpan("startup:advisor-check", () => evaluateAdvisorStartup(cliConfig, resolveAdvisorToolEnv(cliConfig)));
|
|
70946
|
+
if (decision?.kind === "refuse") {
|
|
70947
|
+
process.stderr.write(`[claudish] Error: --advisor cannot work in this launch: ${decision.reason}
|
|
70948
|
+
`);
|
|
70949
|
+
process.exit(1);
|
|
70950
|
+
}
|
|
70951
|
+
if (decision?.kind === "proceed") {
|
|
70952
|
+
cliConfig.advisorCollector = decision.effectiveCollector;
|
|
70953
|
+
await Promise.resolve().then(() => init_catalog_client());
|
|
70954
|
+
if (getCatalogEntries() === null) {
|
|
70955
|
+
const panel = cliConfig.advisorModels ?? [];
|
|
70956
|
+
decision.notice.push(` WARNING: the model catalog is not loaded (catalog warm: ${warmOutcome}), so claudish could not verify that ${panel.length === 1 ? "this panel model is" : "these panel models are"} routable: ${panel.join(", ")}. An unroutable one fails on its first advisor call instead of being refused here. Run \`claudish --models-refresh\` to check them at launch.`);
|
|
70957
|
+
}
|
|
70958
|
+
process.stderr.write(`${decision.notice.join(`
|
|
70959
|
+
`)}
|
|
70960
|
+
`);
|
|
70961
|
+
}
|
|
70962
|
+
}
|
|
69553
70963
|
if (cliConfig.stdin) {
|
|
69554
70964
|
const stdinInput = await traceSpan("startup:stdin-read", () => readStdin());
|
|
69555
70965
|
if (stdinInput.trim()) {
|
|
69556
70966
|
cliConfig.claudeArgs = [stdinInput, ...cliConfig.claudeArgs];
|
|
69557
70967
|
}
|
|
69558
70968
|
}
|
|
69559
|
-
const warmOutcome = await traceSpan("startup:catalog-warm", () => warmCatalogIfNeeded(cliConfig));
|
|
69560
|
-
if (warmOutcome === "hard_fail") {
|
|
69561
|
-
process.exit(1);
|
|
69562
|
-
}
|
|
69563
70969
|
const port = cliConfig.port || await traceSpan("startup:find-port", () => findAvailablePort(DEFAULT_PORT_RANGE.start, DEFAULT_PORT_RANGE.end));
|
|
69564
70970
|
const explicitModel = typeof cliConfig.model === "string" ? cliConfig.model : undefined;
|
|
69565
70971
|
const modelMap = {
|