@khalilgharbaoui/opencode-claude-code-plugin 0.26.1 → 0.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -514,6 +514,9 @@ function translateStreamForHost(stream, dialect = "v1") {
514
514
  );
515
515
  }
516
516
 
517
+ // src/account-failover.ts
518
+ import { homedir as homedir2 } from "os";
519
+
517
520
  // src/accounts.ts
518
521
  import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
519
522
  import path from "path";
@@ -907,8 +910,9 @@ var QUESTION_TOOL_NAME = "question";
907
910
  var APPROVED_EXIT_PLAN_MODE_MESSAGE = "User has approved your plan. You can now start coding. Start with updating your todo list if applicable.";
908
911
  var REJECTED_EXIT_PLAN_MODE_PREFIX = "The user doesn't want to proceed with this tool use. The tool use was rejected. To tell you how to proceed, the user said:";
909
912
  var PLAN_MODE_APPROVAL_QUESTION = "Do you want to proceed with this plan?";
910
- var OPENCODE_QUESTION_RESULT_PREFIX = `User has answered your questions: "${PLAN_MODE_APPROVAL_QUESTION}"="`;
911
- var OPENCODE_QUESTION_RESULT_SUFFIX = `". You can now continue with the user's answers in mind.`;
913
+ var OPENCODE_QUESTION_RESULT_PREFIX = "User has answered your questions: ";
914
+ var OPENCODE_QUESTION_RESULT_SUFFIX = ". You can now continue with the user's answers in mind.";
915
+ var OPENCODE_UNANSWERED = "Unanswered";
912
916
  var KEY_SEPARATOR = "\0";
913
917
  function isPlanModeQuestionActive(input) {
914
918
  if (input.compactionMode) return false;
@@ -1007,29 +1011,40 @@ function unwrapToolOutput(part) {
1007
1011
  return output;
1008
1012
  }
1009
1013
  }
1010
- function unwrapOpencodeQuestionResult(value) {
1011
- if (value.startsWith(OPENCODE_QUESTION_RESULT_PREFIX) && value.endsWith(OPENCODE_QUESTION_RESULT_SUFFIX)) {
1012
- return value.slice(
1013
- OPENCODE_QUESTION_RESULT_PREFIX.length,
1014
- -OPENCODE_QUESTION_RESULT_SUFFIX.length
1015
- );
1014
+ function unwrapOpencodeQuestionResult(value, question) {
1015
+ if (!value.startsWith(OPENCODE_QUESTION_RESULT_PREFIX) || !value.endsWith(OPENCODE_QUESTION_RESULT_SUFFIX)) {
1016
+ return value;
1016
1017
  }
1017
- return value;
1018
+ const body = value.slice(
1019
+ OPENCODE_QUESTION_RESULT_PREFIX.length,
1020
+ value.length - OPENCODE_QUESTION_RESULT_SUFFIX.length
1021
+ );
1022
+ if (!body.startsWith('"') || !body.endsWith('"')) return value;
1023
+ let answer;
1024
+ const head = question === void 0 ? void 0 : `"${question}"="`;
1025
+ if (head && body.startsWith(head) && body.length > head.length) {
1026
+ answer = body.slice(head.length, -1);
1027
+ } else {
1028
+ const split = body.lastIndexOf('"="');
1029
+ if (split > 0) answer = body.slice(split + 3, -1);
1030
+ }
1031
+ if (answer === void 0) return value;
1032
+ return answer === OPENCODE_UNANSWERED ? "" : answer;
1018
1033
  }
1019
- function collectAnswerStrings(value) {
1020
- if (typeof value === "string") return [unwrapOpencodeQuestionResult(value)];
1021
- if (Array.isArray(value)) return value.flatMap(collectAnswerStrings);
1034
+ function collectAnswerStrings(value, question) {
1035
+ if (typeof value === "string") return [unwrapOpencodeQuestionResult(value, question)];
1036
+ if (Array.isArray(value)) return value.flatMap((item) => collectAnswerStrings(item, question));
1022
1037
  if (!value || typeof value !== "object") return [];
1023
1038
  const obj = value;
1024
1039
  if (obj.denied === true) return [String(obj.reason ?? "question rejected")];
1025
1040
  for (const key of ["answers", "answer", "selected", "selection", "value"]) {
1026
- if (key in obj) return collectAnswerStrings(obj[key]);
1041
+ if (key in obj) return collectAnswerStrings(obj[key], question);
1027
1042
  }
1028
1043
  return [];
1029
1044
  }
1030
1045
  function classifyQuestionResult(part) {
1031
1046
  const output = unwrapToolOutput(part);
1032
- const answers = collectAnswerStrings(output).map((answer) => answer.trim()).filter(Boolean);
1047
+ const answers = collectAnswerStrings(output, PLAN_MODE_APPROVAL_QUESTION).map((answer) => answer.trim()).filter(Boolean);
1033
1048
  if (answers.length === 1 && answers[0].toLowerCase() === "yes") {
1034
1049
  return { approved: true, feedback: "" };
1035
1050
  }
@@ -1070,6 +1085,35 @@ var ACCOUNT_LIMIT_PATTERNS = [
1070
1085
  /third-party apps now draw from your extra usage/i,
1071
1086
  /you[’'`]?ve hit your individual spend limit/i
1072
1087
  ];
1088
+ var ACCOUNT_BLOCK_MARKER = "\u258C **claude account:**";
1089
+ var ACCOUNT_BLOCKS = {
1090
+ authentication_failed: { what: "is not logged in (its login expired or was revoked)", login: true },
1091
+ oauth_org_not_allowed: { what: "belongs to an organization that does not allow this login", login: false },
1092
+ account_on_hold: { what: "is on hold", login: false },
1093
+ verification_required: { what: "needs to be verified at claude.ai", login: false },
1094
+ billing_error: { what: "has a billing problem", login: false }
1095
+ };
1096
+ function accountBlockKind(msg) {
1097
+ if (msg.type !== "assistant" || typeof msg.error !== "string") return null;
1098
+ return Object.prototype.hasOwnProperty.call(ACCOUNT_BLOCKS, msg.error) ? msg.error : null;
1099
+ }
1100
+ function describeAccountBlock(kind) {
1101
+ return ACCOUNT_BLOCKS[kind].what;
1102
+ }
1103
+ function loginCommandFor(configDir, home = homedir2()) {
1104
+ if (!configDir) return "claude auth login";
1105
+ const shown = configDir === home || configDir.startsWith(`${home}/`) ? `~${configDir.slice(home.length)}` : configDir;
1106
+ return `CLAUDE_CONFIG_DIR=${shown} claude auth login`;
1107
+ }
1108
+ function formatAccountBlockNote(input) {
1109
+ const block = ACCOUNT_BLOCKS[input.kind];
1110
+ const account = normalizeAccountName(input.account || DEFAULT_ACCOUNT);
1111
+ const fix = block.login ? `Log in again with \`${loginCommandFor(input.configDir)}\`, then resend your message.` : "Check the account at claude.ai, then resend your message.";
1112
+ const then = input.offeringSwitch ? " Or pick another account below." : "";
1113
+ return `
1114
+ ${ACCOUNT_BLOCK_MARKER} the Claude account "${account}" ${block.what}. ${fix}${then}
1115
+ `;
1116
+ }
1073
1117
  function isAccountLimitError(input) {
1074
1118
  if (input.rateLimit && isRateLimitRejected(input.rateLimit)) return true;
1075
1119
  const text = input.resultText;
@@ -1175,17 +1219,18 @@ function createAccountFailoverQuestionCall(sessionKey2, input, toolCallId = `${A
1175
1219
  const candidates = input.candidates.map((c) => normalizeAccountName(c));
1176
1220
  const resets = describeReset(input.resetsAt);
1177
1221
  const until = resets ?? "opencode restarts";
1178
- pendingQuestions2.set(pendingKey2(sessionKey2, toolCallId), {
1179
- sourceAccount: source,
1180
- candidates: [...candidates],
1181
- resetsAt: input.resetsAt
1182
- });
1183
- const question = [
1222
+ const question = input.reason ? `The Claude account "${source}" ${input.reason}. Continue this task on another configured account? Leaving this unanswered waits, at no cost.` : [
1184
1223
  `The Claude account "${source}" is out of usage`,
1185
1224
  input.window ? ` in ${input.window}` : "",
1186
1225
  resets ? `, which resets at ${resets}` : "",
1187
1226
  ". Continue this task on another configured account? Leaving this unanswered waits, at no cost."
1188
1227
  ].join("");
1228
+ pendingQuestions2.set(pendingKey2(sessionKey2, toolCallId), {
1229
+ sourceAccount: source,
1230
+ candidates: [...candidates],
1231
+ resetsAt: input.resetsAt,
1232
+ question
1233
+ });
1189
1234
  return {
1190
1235
  toolCallId,
1191
1236
  toolName: QUESTION_TOOL_NAME,
@@ -1223,7 +1268,11 @@ function classify(pending, part) {
1223
1268
  reason: String(output.reason ?? "question rejected")
1224
1269
  };
1225
1270
  }
1226
- const answers = collectAnswerStrings(output).map((answer) => answer.trim()).filter(Boolean);
1271
+ const outputType = part?.output?.type;
1272
+ if (outputType === "error-text" || outputType === "error-json") {
1273
+ return { kind: "stop", reason: String(output ?? "the form was dismissed") };
1274
+ }
1275
+ const answers = collectAnswerStrings(output, pending.question).map((answer) => answer.trim()).filter(Boolean);
1227
1276
  if (answers.length === 0) return { kind: "stop", reason: "no answer" };
1228
1277
  const picked = normalizeAccountName(answers[0]);
1229
1278
  if (picked === STOP_ANSWER) {
@@ -1240,7 +1289,7 @@ function classify(pending, part) {
1240
1289
  resetsAt: pending.resetsAt
1241
1290
  };
1242
1291
  }
1243
- function consumeAccountFailoverAnswer(sessionKey2, prompt) {
1292
+ function consumeAccountFailoverAnswer(sessionKey2, prompt, fallback) {
1244
1293
  for (let i = prompt.length - 1; i >= 0; i--) {
1245
1294
  const msg = prompt[i];
1246
1295
  if (!Array.isArray(msg.content)) continue;
@@ -1255,6 +1304,21 @@ function consumeAccountFailoverAnswer(sessionKey2, prompt) {
1255
1304
  return classify(pending, part);
1256
1305
  }
1257
1306
  }
1307
+ const last = prompt[prompt.length - 1];
1308
+ if (fallback && fallback.candidates.length > 0 && Array.isArray(last?.content)) {
1309
+ const orphan = last.content.find(
1310
+ (part) => part?.type === "tool-result" && typeof part.toolCallId === "string" && part.toolCallId.startsWith(ACCOUNT_FAILOVER_TOOL_CALL_PREFIX)
1311
+ );
1312
+ if (orphan) {
1313
+ return classify(
1314
+ {
1315
+ sourceAccount: normalizeAccountName(fallback.sourceAccount || DEFAULT_ACCOUNT),
1316
+ candidates: fallback.candidates.map((c) => normalizeAccountName(c))
1317
+ },
1318
+ orphan
1319
+ );
1320
+ }
1321
+ }
1258
1322
  return null;
1259
1323
  }
1260
1324
  function formatFailoverNote(input) {
@@ -1515,6 +1579,53 @@ function resolveProxyClientCeilingMs(overrides) {
1515
1579
  }
1516
1580
  return Math.min(ms, MAX_PROXY_TIMEOUT_MS);
1517
1581
  }
1582
+ var proxyDeadlineGuard = null;
1583
+ function setProxyDeadlineGuard(guard) {
1584
+ proxyDeadlineGuard = guard;
1585
+ }
1586
+ var PROXY_DEADLINE_RECHECK_MS = 6e4;
1587
+ var deadlineRecheckMs = PROXY_DEADLINE_RECHECK_MS;
1588
+ function armProxyDeadline(opts) {
1589
+ let cancelled = false;
1590
+ let extended = false;
1591
+ let timer = null;
1592
+ const check = async () => {
1593
+ timer = null;
1594
+ if (cancelled) return;
1595
+ let keep = false;
1596
+ const guard = proxyDeadlineGuard;
1597
+ if (guard) {
1598
+ try {
1599
+ keep = await guard({ callId: opts.callId, toolName: opts.toolName });
1600
+ } catch {
1601
+ keep = false;
1602
+ }
1603
+ }
1604
+ if (cancelled) return;
1605
+ if (keep) {
1606
+ if (!extended && opts.logExtension) {
1607
+ log.warn("proxy call past its deadline, but opencode is still serving it; waiting", {
1608
+ callId: opts.callId,
1609
+ toolName: opts.toolName,
1610
+ deadlineMs: opts.deadlineMs,
1611
+ note: "usually a permission prompt in opencode; the call ends when it is answered, you abort, or the claude process goes"
1612
+ });
1613
+ }
1614
+ extended = true;
1615
+ timer = setTimeout(() => void check(), opts.recheckMs ?? deadlineRecheckMs);
1616
+ return;
1617
+ }
1618
+ opts.onExpire();
1619
+ };
1620
+ timer = setTimeout(() => void check(), opts.deadlineMs);
1621
+ return {
1622
+ cancel() {
1623
+ cancelled = true;
1624
+ if (timer) clearTimeout(timer);
1625
+ timer = null;
1626
+ }
1627
+ };
1628
+ }
1518
1629
  function buildProxyTimeoutError(toolName, ms) {
1519
1630
  const key = toolName.toLowerCase();
1520
1631
  const base = `Proxy tool '${toolName}' timed out after ${ms}ms waiting for opencode to resolve the call`;
@@ -2000,21 +2111,26 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverride
2000
2111
  timeoutOverrides
2001
2112
  );
2002
2113
  if (deadlineMs > PROXY_NO_DEADLINE_MS) {
2003
- timer = setTimeout(() => {
2004
- if (!pending.has(callId)) return;
2005
- pending.delete(callId);
2006
- log.notice("proxy-mcp tool call timed out", {
2007
- callId,
2008
- toolName,
2009
- deadlineMs
2010
- });
2011
- reject2(buildProxyTimeoutError(toolName, deadlineMs));
2012
- }, deadlineMs);
2114
+ timer = armProxyDeadline({
2115
+ callId,
2116
+ toolName,
2117
+ deadlineMs,
2118
+ onExpire: () => {
2119
+ if (!pending.has(callId)) return;
2120
+ pending.delete(callId);
2121
+ log.notice("proxy-mcp tool call timed out", {
2122
+ callId,
2123
+ toolName,
2124
+ deadlineMs
2125
+ });
2126
+ reject2(buildProxyTimeoutError(toolName, deadlineMs));
2127
+ }
2128
+ });
2013
2129
  }
2014
2130
  calls.emit("call", entry);
2015
2131
  }
2016
2132
  ).finally(() => {
2017
- if (timer) clearTimeout(timer);
2133
+ timer?.cancel();
2018
2134
  pending.delete(callId);
2019
2135
  });
2020
2136
  if (channel.closed) {
@@ -2381,7 +2497,7 @@ var PROXY_STALL_WARNING_MS = 5 * 6e4;
2381
2497
  var PROXY_DEADLINE_WARNING_FRACTION = 0.6;
2382
2498
  var PROXY_DEADLINE_WARNING_MIN_MS = 6e4;
2383
2499
  function clearPendingTimers(pending) {
2384
- if (pending.timer) clearTimeout(pending.timer);
2500
+ pending.timer?.cancel();
2385
2501
  if (pending.stallTimer) clearInterval(pending.stallTimer);
2386
2502
  if (pending.deadlineWarnTimer) clearTimeout(pending.deadlineWarnTimer);
2387
2503
  }
@@ -2425,20 +2541,26 @@ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides, stallWarning
2425
2541
  call.input,
2426
2542
  timeoutOverrides
2427
2543
  );
2428
- const timer = deadlineMs > PROXY_NO_DEADLINE_MS ? setTimeout(() => {
2429
- const current = pendingByCallId.get(call.id);
2430
- if (!current) return;
2431
- pendingByCallId.delete(call.id);
2432
- indexRemove(current.sessionKey, call.id);
2433
- clearPendingTimers(current);
2434
- current.reject(buildProxyTimeoutError(call.toolName, deadlineMs));
2435
- log.notice("timed out pending proxy call", {
2436
- sessionKey: current.sessionKey,
2437
- toolCallId: call.id,
2438
- toolName: call.toolName,
2439
- deadlineMs
2440
- });
2441
- }, deadlineMs) : null;
2544
+ const timer = deadlineMs > PROXY_NO_DEADLINE_MS ? armProxyDeadline({
2545
+ callId: call.id,
2546
+ toolName: call.toolName,
2547
+ deadlineMs,
2548
+ logExtension: true,
2549
+ onExpire: () => {
2550
+ const current = pendingByCallId.get(call.id);
2551
+ if (!current) return;
2552
+ pendingByCallId.delete(call.id);
2553
+ indexRemove(current.sessionKey, call.id);
2554
+ clearPendingTimers(current);
2555
+ current.reject(buildProxyTimeoutError(call.toolName, deadlineMs));
2556
+ log.notice("timed out pending proxy call", {
2557
+ sessionKey: current.sessionKey,
2558
+ toolCallId: call.id,
2559
+ toolName: call.toolName,
2560
+ deadlineMs
2561
+ });
2562
+ }
2563
+ }) : null;
2442
2564
  const stallTimer = deadlineMs === PROXY_NO_DEADLINE_MS && stallWarningMs > 0 ? setInterval(() => {
2443
2565
  const current = pendingByCallId.get(call.id);
2444
2566
  if (!current) return;
@@ -2467,7 +2589,7 @@ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides, stallWarning
2467
2589
  remainingMs: Math.max(0, deadlineMs - waitedMs),
2468
2590
  emitted: current.emitted === true,
2469
2591
  channelClosed: current.channel?.closed === true,
2470
- note: "it will be rejected when the deadline passes; raise this tool's proxyToolTimeoutMs if the work is legitimately this long"
2592
+ note: "it will be rejected when the deadline passes unless opencode is still serving it (a permission prompt is waiting); raise this tool's proxyToolTimeoutMs if the work is legitimately this long"
2471
2593
  });
2472
2594
  }, warnAtMs) : null;
2473
2595
  deadlineWarnTimer?.unref?.();
@@ -2502,6 +2624,9 @@ function markPendingProxyCallEmitted(toolCallId) {
2502
2624
  function isPendingProxyCallChannelClosed(call) {
2503
2625
  return call.channel?.closed === true;
2504
2626
  }
2627
+ function findPendingProxyCall(toolCallId) {
2628
+ return pendingByCallId.get(toolCallId);
2629
+ }
2505
2630
  function getPendingProxyCalls(sessionKey2) {
2506
2631
  const s = callIdsBySession.get(sessionKey2);
2507
2632
  if (!s || s.size === 0) return [];
@@ -4600,7 +4725,8 @@ var PLUGIN_NOTE_MARKERS = [
4600
4725
  RESULT_ERROR_MARKER,
4601
4726
  DOCTOR_MARKER,
4602
4727
  STREAM_TIMEOUT_MARKER,
4603
- FAILOVER_MARKER
4728
+ FAILOVER_MARKER,
4729
+ ACCOUNT_BLOCK_MARKER
4604
4730
  ];
4605
4731
  function isPluginNote(part) {
4606
4732
  if (!part || part.type !== "text" || typeof part.text !== "string") return false;
@@ -4854,6 +4980,7 @@ function getClaudeUserMessage(prompt, includeHistoryContext = false, opts = {})
4854
4980
  const compactionMode = opts.compactionMode === true;
4855
4981
  const cliToolCallIds = opts.cliToolCallIds;
4856
4982
  const content = [];
4983
+ prompt = stripAccountFailoverParts(prompt);
4857
4984
  if (opts.stripContextReminders) {
4858
4985
  const stripped = stripContextReminders(prompt);
4859
4986
  if (stripped.removed > 0) {
@@ -6195,9 +6322,16 @@ function spawnInteractiveProcess(opts) {
6195
6322
  // src/claude-code-language-model.ts
6196
6323
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
6197
6324
  import { unlink as unlink3 } from "fs/promises";
6198
- import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
6325
+ import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
6199
6326
  import { randomUUID as randomUUID5 } from "crypto";
6200
6327
  import { dirname as dirname5, join as join8 } from "path";
6328
+ async function isProxyCallStillServed(callId) {
6329
+ const pending = findPendingProxyCall(callId);
6330
+ if (!pending || pending.emitted !== true) return false;
6331
+ const session = describeSessionKey(pending.sessionKey).session;
6332
+ return await fetchSessionRunState(session) === "busy";
6333
+ }
6334
+ setProxyDeadlineGuard(({ callId }) => isProxyCallStillServed(callId));
6201
6335
  var DEFAULT_COMPACTION_MODEL = "claude-haiku-4-5";
6202
6336
  function resolveCompactionModel(configured) {
6203
6337
  const env = process.env.CLAUDE_CODE_COMPACTION_MODEL?.trim();
@@ -6552,7 +6686,7 @@ ${options.compressionSummary.trim()}`
6552
6686
  for (const s of extraSystemContent) {
6553
6687
  if (s.trim()) parts.push(s.trim());
6554
6688
  }
6555
- const configRoot = process.env.XDG_CONFIG_HOME ?? join8(homedir5(), ".config");
6689
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join8(homedir6(), ".config");
6556
6690
  const globalAgents = readPromptFileIfPresent(join8(configRoot, "opencode", "AGENTS.md"));
6557
6691
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
6558
6692
  const forwarded = extraSystemContent.join("\n\n");
@@ -7855,7 +7989,10 @@ ${plan}
7855
7989
  deleteActiveProcess(sk);
7856
7990
  clearCompression(sk);
7857
7991
  }
7858
- const failoverAnswer = compactionMode || useInteractive ? null : consumeAccountFailoverAnswer(sk, options.prompt);
7992
+ const failoverAnswer = compactionMode || useInteractive ? null : consumeAccountFailoverAnswer(sk, options.prompt, {
7993
+ sourceAccount,
7994
+ candidates: failoverCandidates(this.config.failoverAccounts, sourceAccount)
7995
+ });
7859
7996
  if (failoverAnswer?.kind === "stop") {
7860
7997
  log.warn("account failover declined; ending the turn", {
7861
7998
  sessionKey: sk,
@@ -8487,6 +8624,7 @@ ${plan}
8487
8624
  let resultMeta = {};
8488
8625
  let resultFailure;
8489
8626
  let accountLimitHit = null;
8627
+ let accountBlock = null;
8490
8628
  const drainBuffer = [];
8491
8629
  let drainTimer = null;
8492
8630
  const DRAIN_QUIET_MS = 100;
@@ -8648,20 +8786,21 @@ ${plan}
8648
8786
  });
8649
8787
  }
8650
8788
  activeProcess?.pendingProxyCompletions?.clear();
8651
- if (accountLimitHit && failoverAskActive && msg.is_error === true) {
8789
+ if ((accountLimitHit || accountBlock) && failoverAskActive && msg.is_error === true) {
8652
8790
  const call = createAccountFailoverQuestionCall(sk, {
8653
8791
  sourceAccount,
8654
8792
  candidates: failoverAccounts,
8655
- resetsAt: accountLimitHit.resetsAt,
8656
- window: accountLimitHit.window
8793
+ resetsAt: accountLimitHit?.resetsAt,
8794
+ window: accountLimitHit?.window,
8795
+ reason: accountLimitHit || !accountBlock ? void 0 : describeAccountBlock(accountBlock)
8657
8796
  });
8658
8797
  log.warn(
8659
- `Claude account "${sourceAccount}" is out of usage; asking which account to continue on.`,
8798
+ `Claude account "${sourceAccount}" ${accountLimitHit || !accountBlock ? "is out of usage" : `cannot serve (${accountBlock})`}; asking which account to continue on.`,
8660
8799
  {
8661
8800
  sessionKey: sk,
8662
8801
  candidates: failoverAccounts,
8663
8802
  toolCallId: call.toolCallId,
8664
- resetsAt: accountLimitHit.resetsAt ?? null
8803
+ resetsAt: accountLimitHit?.resetsAt ?? null
8665
8804
  }
8666
8805
  );
8667
8806
  finishWithQuestionCall(call);
@@ -8799,6 +8938,8 @@ ${plan}
8799
8938
  endTextBlock();
8800
8939
  }
8801
8940
  }
8941
+ const block = accountBlockKind(msg);
8942
+ if (block) accountBlock = block;
8802
8943
  if (msg.type === "rate_limit_event") {
8803
8944
  const info = parseRateLimitEvent(msg);
8804
8945
  if (info && isRateLimitRejected(info)) {
@@ -8815,58 +8956,58 @@ ${plan}
8815
8956
  return;
8816
8957
  }
8817
8958
  if (msg.type === "content_block_start" && msg.content_block && msg.index !== void 0) {
8818
- const block = msg.content_block;
8959
+ const block2 = msg.content_block;
8819
8960
  const idx = msg.index;
8820
- if (block.type === "thinking") {
8961
+ if (block2.type === "thinking") {
8821
8962
  noteReasoning();
8822
8963
  const reasoningId = generateId();
8823
8964
  reasoningIds.set(idx, reasoningId);
8824
8965
  }
8825
- if (block.type === "text") {
8966
+ if (block2.type === "text") {
8826
8967
  textBlockIndices.add(idx);
8827
8968
  resetLastVisibleTextBlock();
8828
- if (block.text) {
8969
+ if (block2.text) {
8829
8970
  if (!currentTextId) startTextBlock();
8830
8971
  controller.enqueue({
8831
8972
  type: "text-delta",
8832
8973
  id: currentTextId,
8833
- delta: block.text
8974
+ delta: block2.text
8834
8975
  });
8835
- noteVisibleText(block.text);
8976
+ noteVisibleText(block2.text);
8836
8977
  hasReceivedContent = true;
8837
8978
  }
8838
8979
  }
8839
- if (block.type === "tool_use" && block.id && block.name) {
8980
+ if (block2.type === "tool_use" && block2.id && block2.name) {
8840
8981
  noteToolActivity();
8841
8982
  const entry = {
8842
- id: block.id,
8843
- name: block.name,
8983
+ id: block2.id,
8984
+ name: block2.name,
8844
8985
  inputJson: "",
8845
8986
  started: false
8846
8987
  };
8847
8988
  toolCallMap.set(idx, entry);
8848
- if (block.name !== "AskUserQuestion" && block.name !== "ask_user_question" && block.name !== "ExitPlanMode" && !block.name.startsWith(PROXY_TOOL_PREFIX)) {
8989
+ if (block2.name !== "AskUserQuestion" && block2.name !== "ask_user_question" && block2.name !== "ExitPlanMode" && !block2.name.startsWith(PROXY_TOOL_PREFIX)) {
8849
8990
  const { name: mappedName, skip, executed } = mapTool(
8850
- block.name,
8991
+ block2.name,
8851
8992
  void 0,
8852
8993
  {
8853
8994
  webSearch: self.config.webSearch,
8854
8995
  sessionId: getClaudeSessionId(sk),
8855
- toolUseId: block.id
8996
+ toolUseId: block2.id
8856
8997
  }
8857
8998
  );
8858
8999
  if (!skip) {
8859
9000
  entry.started = true;
8860
9001
  controller.enqueue({
8861
9002
  type: "tool-input-start",
8862
- id: block.id,
9003
+ id: block2.id,
8863
9004
  toolName: mappedName,
8864
9005
  providerExecuted: executed
8865
9006
  });
8866
9007
  log.info("tool started", {
8867
- name: block.name,
9008
+ name: block2.name,
8868
9009
  mappedName,
8869
- id: block.id
9010
+ id: block2.id
8870
9011
  });
8871
9012
  }
8872
9013
  }
@@ -9057,8 +9198,8 @@ ${plan}
9057
9198
  hadStreamThinking: hadThinkingTextFromStream
9058
9199
  });
9059
9200
  if (!hadThinkingTextFromStream) {
9060
- for (const block of thinkingBlocks) {
9061
- if (block.thinking && block.thinking.length > 0) {
9201
+ for (const block2 of thinkingBlocks) {
9202
+ if (block2.thinking && block2.thinking.length > 0) {
9062
9203
  noteReasoning();
9063
9204
  hadThinkingTextFromStream = true;
9064
9205
  const thinkingId = generateId();
@@ -9069,7 +9210,7 @@ ${plan}
9069
9210
  controller.enqueue({
9070
9211
  type: "reasoning-delta",
9071
9212
  id: thinkingId,
9072
- delta: block.thinking
9213
+ delta: block2.thinking
9073
9214
  });
9074
9215
  controller.enqueue({
9075
9216
  type: "reasoning-end",
@@ -9096,20 +9237,20 @@ ${plan}
9096
9237
  if (hasToolUse) {
9097
9238
  clearFallbackTimer();
9098
9239
  }
9099
- for (const block of msg.message.content) {
9100
- if (block.type === "text" && block.text) {
9240
+ for (const block2 of msg.message.content) {
9241
+ if (block2.type === "text" && block2.text) {
9101
9242
  resetLastVisibleTextBlock();
9102
9243
  const blockId = startTextBlock();
9103
9244
  controller.enqueue({
9104
9245
  type: "text-delta",
9105
9246
  id: blockId,
9106
- delta: block.text
9247
+ delta: block2.text
9107
9248
  });
9108
9249
  endTextBlock();
9109
- noteVisibleText(block.text);
9250
+ noteVisibleText(block2.text);
9110
9251
  hasReceivedContent = true;
9111
9252
  }
9112
- if (block.type === "thinking" && block.thinking) {
9253
+ if (block2.type === "thinking" && block2.thinking) {
9113
9254
  noteReasoning();
9114
9255
  const thinkingId = generateId();
9115
9256
  controller.enqueue({
@@ -9119,17 +9260,17 @@ ${plan}
9119
9260
  controller.enqueue({
9120
9261
  type: "reasoning-delta",
9121
9262
  id: thinkingId,
9122
- delta: block.thinking
9263
+ delta: block2.thinking
9123
9264
  });
9124
9265
  controller.enqueue({
9125
9266
  type: "reasoning-end",
9126
9267
  id: thinkingId
9127
9268
  });
9128
9269
  }
9129
- if (block.type === "tool_use" && block.id && block.name) {
9270
+ if (block2.type === "tool_use" && block2.id && block2.name) {
9130
9271
  noteToolActivity();
9131
- const parsedInput = block.input ?? {};
9132
- if (isAskUserQuestionTool(block.name)) {
9272
+ const parsedInput = block2.input ?? {};
9273
+ if (isAskUserQuestionTool(block2.name)) {
9133
9274
  const askId = startTextBlock();
9134
9275
  controller.enqueue({
9135
9276
  type: "text-delta",
@@ -9137,12 +9278,12 @@ ${plan}
9137
9278
  delta: formatAskUserQuestion(parsedInput)
9138
9279
  });
9139
9280
  endTextBlock();
9140
- } else if (block.name === "ExitPlanMode") {
9281
+ } else if (block2.name === "ExitPlanMode") {
9141
9282
  const plan = parsedInput?.plan || "";
9142
9283
  if (planModeQuestionActive) {
9143
9284
  const questionCall = createExitPlanModeQuestionCall(
9144
9285
  sk,
9145
- block.id,
9286
+ block2.id,
9146
9287
  plan
9147
9288
  );
9148
9289
  const planId2 = startTextBlock();
@@ -9167,8 +9308,8 @@ ${plan}
9167
9308
  `
9168
9309
  });
9169
9310
  endTextBlock();
9170
- } else if (isWebSearchTool(block.name) && isWebSearchHandledByCli(self.config.webSearch)) {
9171
- toolCallsById.delete(block.id);
9311
+ } else if (isWebSearchTool(block2.name) && isWebSearchHandledByCli(self.config.webSearch)) {
9312
+ toolCallsById.delete(block2.id);
9172
9313
  const query = typeof parsedInput?.query === "string" ? parsedInput.query : JSON.stringify(parsedInput);
9173
9314
  const searchId = startTextBlock();
9174
9315
  controller.enqueue({
@@ -9179,11 +9320,11 @@ ${plan}
9179
9320
  `
9180
9321
  });
9181
9322
  endTextBlock();
9182
- } else if (block.name.startsWith(PROXY_TOOL_PREFIX)) {
9323
+ } else if (block2.name.startsWith(PROXY_TOOL_PREFIX)) {
9183
9324
  noteProxyActivity();
9184
9325
  log.debug("ignoring proxy tool_use from assistant message", {
9185
- name: block.name,
9186
- id: block.id
9326
+ name: block2.name,
9327
+ id: block2.id
9187
9328
  });
9188
9329
  } else {
9189
9330
  const {
@@ -9191,61 +9332,61 @@ ${plan}
9191
9332
  input: mappedInput,
9192
9333
  executed,
9193
9334
  skip
9194
- } = mapTool(block.name, parsedInput, {
9335
+ } = mapTool(block2.name, parsedInput, {
9195
9336
  webSearch: self.config.webSearch,
9196
9337
  sessionId: getClaudeSessionId(sk),
9197
- toolUseId: block.id
9338
+ toolUseId: block2.id
9198
9339
  });
9199
9340
  if (!skip) {
9200
- toolCallsById.set(block.id, {
9201
- id: block.id,
9202
- name: block.name,
9341
+ toolCallsById.set(block2.id, {
9342
+ id: block2.id,
9343
+ name: block2.name,
9203
9344
  input: parsedInput
9204
9345
  });
9205
- if (!executed) skipResultForIds.add(block.id);
9346
+ if (!executed) skipResultForIds.add(block2.id);
9206
9347
  controller.enqueue({
9207
9348
  type: "tool-input-start",
9208
- id: block.id,
9349
+ id: block2.id,
9209
9350
  toolName: mappedName,
9210
9351
  providerExecuted: executed
9211
9352
  });
9212
9353
  controller.enqueue({
9213
9354
  type: "tool-call",
9214
- toolCallId: block.id,
9355
+ toolCallId: block2.id,
9215
9356
  toolName: mappedName,
9216
9357
  input: JSON.stringify(mappedInput),
9217
9358
  providerExecuted: executed
9218
9359
  });
9219
9360
  }
9220
9361
  log.info("tool_use from assistant message", {
9221
- name: block.name,
9362
+ name: block2.name,
9222
9363
  mappedName,
9223
- id: block.id,
9364
+ id: block2.id,
9224
9365
  executed
9225
9366
  });
9226
9367
  }
9227
9368
  }
9228
- if (block.type === "tool_result") {
9369
+ if (block2.type === "tool_result") {
9229
9370
  log.debug("tool_result", {
9230
- toolUseId: block.tool_use_id
9371
+ toolUseId: block2.tool_use_id
9231
9372
  });
9232
9373
  }
9233
9374
  }
9234
9375
  }
9235
9376
  if (msg.type === "user" && msg.message?.content) {
9236
- for (const block of msg.message.content) {
9237
- if (block.type === "tool_result" && block.tool_use_id) {
9238
- if (skipResultForIds.has(block.tool_use_id)) {
9377
+ for (const block2 of msg.message.content) {
9378
+ if (block2.type === "tool_result" && block2.tool_use_id) {
9379
+ if (skipResultForIds.has(block2.tool_use_id)) {
9239
9380
  log.debug("skipping tool-result (opencode runs it)", {
9240
- toolUseId: block.tool_use_id
9381
+ toolUseId: block2.tool_use_id
9241
9382
  });
9242
9383
  continue;
9243
9384
  }
9244
9385
  let resultText = "";
9245
- if (typeof block.content === "string") {
9246
- resultText = block.content;
9247
- } else if (Array.isArray(block.content)) {
9248
- resultText = block.content.filter(
9386
+ if (typeof block2.content === "string") {
9387
+ resultText = block2.content;
9388
+ } else if (Array.isArray(block2.content)) {
9389
+ resultText = block2.content.filter(
9249
9390
  (c) => c.type === "text" && typeof c.text === "string"
9250
9391
  ).map((c) => c.text).join("\n");
9251
9392
  }
@@ -9253,11 +9394,11 @@ ${plan}
9253
9394
  if (claudeSessionId) {
9254
9395
  const list2 = applyTaskCreateToolResult(
9255
9396
  claudeSessionId,
9256
- block.tool_use_id,
9397
+ block2.tool_use_id,
9257
9398
  resultText
9258
9399
  );
9259
9400
  if (list2) {
9260
- const synthId = `todowrite_${block.tool_use_id}`;
9401
+ const synthId = `todowrite_${block2.tool_use_id}`;
9261
9402
  controller.enqueue({
9262
9403
  type: "tool-input-start",
9263
9404
  id: synthId,
@@ -9281,12 +9422,12 @@ ${plan}
9281
9422
  noteToolActivity();
9282
9423
  }
9283
9424
  }
9284
- const toolCall = toolCallsById.get(block.tool_use_id);
9425
+ const toolCall = toolCallsById.get(block2.tool_use_id);
9285
9426
  if (toolCall) {
9286
- const isError = block.is_error === true;
9427
+ const isError = block2.is_error === true;
9287
9428
  controller.enqueue({
9288
9429
  type: "tool-result",
9289
- toolCallId: block.tool_use_id,
9430
+ toolCallId: block2.tool_use_id,
9290
9431
  toolName: toolCall.name,
9291
9432
  result: {
9292
9433
  output: resultText,
@@ -9298,11 +9439,11 @@ ${plan}
9298
9439
  });
9299
9440
  noteToolActivity();
9300
9441
  log.info("tool result emitted", {
9301
- toolUseId: block.tool_use_id,
9442
+ toolUseId: block2.tool_use_id,
9302
9443
  name: toolCall.name,
9303
9444
  isError
9304
9445
  });
9305
- toolCallsById.delete(block.tool_use_id);
9446
+ toolCallsById.delete(block2.tool_use_id);
9306
9447
  }
9307
9448
  }
9308
9449
  }
@@ -9328,6 +9469,26 @@ ${plan}
9328
9469
  })) {
9329
9470
  accountLimitHit = {};
9330
9471
  }
9472
+ if (accountBlock && msg.is_error) {
9473
+ resultFailure ??= accountBlock;
9474
+ const offeringSwitch = failoverAskActive;
9475
+ controller.enqueue({
9476
+ type: "text-delta",
9477
+ id: startTextBlock(),
9478
+ delta: formatAccountBlockNote({
9479
+ kind: accountBlock,
9480
+ account: sourceAccount,
9481
+ configDir: self.config.configDir,
9482
+ offeringSwitch
9483
+ })
9484
+ });
9485
+ endTextBlock();
9486
+ log.warn(`Claude account "${sourceAccount}" cannot serve requests`, {
9487
+ sessionKey: sk,
9488
+ kind: accountBlock,
9489
+ offeringSwitch
9490
+ });
9491
+ }
9331
9492
  const failure = describeResultFailure(msg);
9332
9493
  if (failure) {
9333
9494
  resultFailure = msg.subtype;
@@ -9773,7 +9934,7 @@ import {
9773
9934
  rmSync as rmSync3,
9774
9935
  writeFileSync as writeFileSync5
9775
9936
  } from "fs";
9776
- import { homedir as homedir6 } from "os";
9937
+ import { homedir as homedir7 } from "os";
9777
9938
  import { join as join9, resolve as resolve4 } from "path";
9778
9939
  import { fileURLToPath as fileURLToPath3 } from "url";
9779
9940
  var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
@@ -9783,12 +9944,12 @@ function candidateCacheRoots() {
9783
9944
  const xdg = process.env.XDG_CACHE_HOME;
9784
9945
  return [
9785
9946
  xdg ? join9(xdg, "opencode") : null,
9786
- join9(homedir6(), ".cache", "opencode"),
9787
- join9(homedir6(), "Library", "Caches", "opencode")
9947
+ join9(homedir7(), ".cache", "opencode"),
9948
+ join9(homedir7(), "Library", "Caches", "opencode")
9788
9949
  ].filter((p) => Boolean(p));
9789
9950
  }
9790
9951
  function userOpencodeJsonPath() {
9791
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join9(homedir6(), ".config");
9952
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join9(homedir7(), ".config");
9792
9953
  return join9(xdgConfig, "opencode", "opencode.json");
9793
9954
  }
9794
9955
  function userIntendsToUseUnscoped() {