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

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
@@ -386,6 +386,134 @@ function mapTool(name, input, opts) {
386
386
  return { name, input, executed: true };
387
387
  }
388
388
 
389
+ // src/host-tools.ts
390
+ function defined(input) {
391
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
392
+ }
393
+ var V2_TOOLS = {
394
+ bash: {
395
+ name: "shell",
396
+ // V2's shell takes no `description`; `workdir`, `timeout` and
397
+ // `background` keep their names.
398
+ input: (input) => defined({
399
+ command: input.command,
400
+ workdir: input.workdir,
401
+ timeout: input.timeout,
402
+ background: input.background
403
+ })
404
+ },
405
+ edit: {
406
+ name: "edit",
407
+ input: (input) => defined({
408
+ path: input.filePath ?? input.path,
409
+ oldString: input.oldString,
410
+ newString: input.newString,
411
+ replaceAll: input.replaceAll
412
+ })
413
+ },
414
+ write: {
415
+ name: "write",
416
+ input: (input) => defined({ path: input.filePath ?? input.path, content: input.content })
417
+ },
418
+ read: {
419
+ name: "read",
420
+ input: (input) => defined({ path: input.filePath ?? input.path, offset: input.offset, limit: input.limit })
421
+ },
422
+ glob: { name: "glob" },
423
+ grep: { name: "grep" },
424
+ webfetch: { name: "webfetch" },
425
+ websearch: { name: "websearch" },
426
+ question: { name: "question" },
427
+ task: {
428
+ name: "subagent",
429
+ input: (input) => defined({
430
+ agent: input.subagent_type ?? input.agent,
431
+ description: input.description,
432
+ prompt: input.prompt,
433
+ sessionID: input.task_id ?? input.sessionID,
434
+ background: input.background
435
+ })
436
+ },
437
+ todowrite: null,
438
+ plan_enter: null,
439
+ plan_exit: null,
440
+ notebookedit: null
441
+ };
442
+ function parseInput(raw) {
443
+ if (typeof raw !== "string") return raw ?? {};
444
+ try {
445
+ const parsed = JSON.parse(raw);
446
+ return parsed && typeof parsed === "object" ? parsed : {};
447
+ } catch {
448
+ return {};
449
+ }
450
+ }
451
+ function createHostToolPartTranslator(dialect = "v1") {
452
+ if (dialect !== "v2") return (part) => part;
453
+ const dropped = /* @__PURE__ */ new Set();
454
+ const translated = /* @__PURE__ */ new Map();
455
+ const classify2 = (id, name) => {
456
+ if (dropped.has(id)) return null;
457
+ const known = translated.get(id);
458
+ if (known) return known;
459
+ if (typeof name !== "string") return void 0;
460
+ const tool = V2_TOOLS[name];
461
+ if (tool === null) {
462
+ dropped.add(id);
463
+ return null;
464
+ }
465
+ if (tool) translated.set(id, tool);
466
+ return tool;
467
+ };
468
+ return (part) => {
469
+ switch (part.type) {
470
+ case "tool-input-start": {
471
+ const tool = classify2(String(part.id), part.toolName);
472
+ if (tool === null) return null;
473
+ return tool ? { ...part, toolName: tool.name } : part;
474
+ }
475
+ case "tool-input-delta": {
476
+ const tool = classify2(String(part.id), void 0);
477
+ if (tool === null) return null;
478
+ return tool?.input ? null : part;
479
+ }
480
+ case "tool-input-end": {
481
+ return classify2(String(part.id), void 0) === null ? null : part;
482
+ }
483
+ case "tool-call": {
484
+ const tool = classify2(String(part.toolCallId), part.toolName);
485
+ if (tool === null) return null;
486
+ if (!tool) return part;
487
+ const input = tool.input ? tool.input(parseInput(part.input)) : parseInput(part.input);
488
+ return {
489
+ ...part,
490
+ toolName: tool.name,
491
+ input: typeof part.input === "string" ? JSON.stringify(input) : input
492
+ };
493
+ }
494
+ case "tool-result": {
495
+ const tool = classify2(String(part.toolCallId), part.toolName);
496
+ if (tool === null) return null;
497
+ return tool ? { ...part, toolName: tool.name } : part;
498
+ }
499
+ default:
500
+ return part;
501
+ }
502
+ };
503
+ }
504
+ function translateStreamForHost(stream, dialect = "v1") {
505
+ if (dialect !== "v2") return stream;
506
+ const translate = createHostToolPartTranslator(dialect);
507
+ return stream.pipeThrough(
508
+ new TransformStream({
509
+ transform(part, controller) {
510
+ const next = translate(part);
511
+ if (next) controller.enqueue(next);
512
+ }
513
+ })
514
+ );
515
+ }
516
+
389
517
  // src/accounts.ts
390
518
  import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
391
519
  import path from "path";
@@ -596,7 +724,9 @@ function rateLimitKey(info) {
596
724
  ].join("|");
597
725
  }
598
726
  function isRateLimitRejected(info) {
599
- return info.status === "rejected" || info.overageStatus === "rejected";
727
+ if (info.status === "rejected") return true;
728
+ if (info.status === "allowed" || info.status === "allowed_warning") return false;
729
+ return info.overageStatus === "rejected";
600
730
  }
601
731
  function describeRateLimit(info) {
602
732
  if (!info.status && !info.overageStatus) return null;
@@ -3254,6 +3384,7 @@ var BtwHandledError = class extends Error {
3254
3384
  super(message);
3255
3385
  }
3256
3386
  };
3387
+ var BTW_COMMAND_DESCRIPTION = "Ask a side question in the live Claude Code session without changing its context";
3257
3388
  var BTW_NO_SESSION_MESSAGE = "/btw needs a live Claude Code session in this conversation. Send a normal message with a Claude Code model first, then ask again.";
3258
3389
  var BTW_INLINE_HANDLED_MESSAGE = "/btw was answered inside the running turn; nothing to add to this conversation.";
3259
3390
  var BTW_TURN_TOO_LONG_MESSAGE = "/btw gave up waiting for this turn to end. Ask again once it is over.";
@@ -3781,14 +3912,14 @@ function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers) {
3781
3912
  excludeServers
3782
3913
  });
3783
3914
  }
3784
- function mergeOpencodeMcp(cwd, runtimeStatus) {
3915
+ function opencodeConfigLayers(cwd) {
3785
3916
  const worktree = detectWorktree(cwd);
3786
- let merged = {};
3787
- merged = mergeMcp(merged, extractMcpBlock(loadGlobalConfig()));
3917
+ const layers = [];
3918
+ layers.push(loadGlobalConfig());
3788
3919
  const explicitConfig = process.env.OPENCODE_CONFIG;
3789
3920
  if (explicitConfig && fileExists(explicitConfig)) {
3790
3921
  const parsed = readAndParse(explicitConfig);
3791
- if (parsed) merged = mergeMcp(merged, extractMcpBlock(parsed));
3922
+ if (parsed) layers.push(parsed);
3792
3923
  }
3793
3924
  const projectFiles = walkUp({
3794
3925
  start: cwd,
@@ -3806,10 +3937,22 @@ function mergeOpencodeMcp(cwd, runtimeStatus) {
3806
3937
  }
3807
3938
  }
3808
3939
  for (const dir of projectDirs.slice().reverse()) {
3809
- merged = mergeMcp(merged, extractMcpBlock(loadProjectFilesInDir(dir)));
3940
+ layers.push(loadProjectFilesInDir(dir));
3810
3941
  }
3811
3942
  for (const dir of dotOpencodeDirs(cwd, worktree)) {
3812
- merged = mergeMcp(merged, extractMcpBlock(loadProjectFilesInDir(dir)));
3943
+ layers.push(loadProjectFilesInDir(dir));
3944
+ }
3945
+ return layers;
3946
+ }
3947
+ function loadMergedOpencodeConfig(cwd) {
3948
+ let merged = {};
3949
+ for (const layer of opencodeConfigLayers(cwd)) merged = deepMerge(merged, layer);
3950
+ return merged;
3951
+ }
3952
+ function mergeOpencodeMcp(cwd, runtimeStatus) {
3953
+ let merged = {};
3954
+ for (const layer of opencodeConfigLayers(cwd)) {
3955
+ merged = mergeMcp(merged, extractMcpBlock(layer));
3813
3956
  }
3814
3957
  if (runtimeStatus) {
3815
3958
  for (const name of Object.keys(merged)) {
@@ -3945,6 +4088,39 @@ async function fetchSessionParentId(sessionID) {
3945
4088
  return void 0;
3946
4089
  }
3947
4090
  }
4091
+ async function fetchSessionRunState(sessionID) {
4092
+ if (!sessionID || sessionID === "default") return "unknown";
4093
+ const status = opencodeClient?.session?.status;
4094
+ if (!status) return "unknown";
4095
+ try {
4096
+ const res = await status.call(opencodeClient.session);
4097
+ const data = res.data;
4098
+ if (!data || typeof data !== "object") return "unknown";
4099
+ const entry = data[sessionID];
4100
+ if (!entry || typeof entry !== "object") return "idle";
4101
+ const type = entry.type;
4102
+ return type === "idle" ? "idle" : "busy";
4103
+ } catch (err) {
4104
+ log.debug("failed to read opencode session status", {
4105
+ sessionID,
4106
+ error: err instanceof Error ? err.message : String(err)
4107
+ });
4108
+ return "unknown";
4109
+ }
4110
+ }
4111
+ async function settleSessionRunState(sessionID, options = {}) {
4112
+ const pollMs = options.pollMs ?? 150;
4113
+ const windowMs = options.windowMs ?? 1500;
4114
+ const started = Date.now();
4115
+ let last = "unknown";
4116
+ for (; ; ) {
4117
+ const state = await fetchSessionRunState(sessionID);
4118
+ if (state === "busy") return "busy";
4119
+ if (state !== "unknown") last = state;
4120
+ if (Date.now() - started >= windowMs) return last;
4121
+ await new Promise((resolve5) => setTimeout(resolve5, pollMs));
4122
+ }
4123
+ }
3948
4124
  async function getRuntimeMcpStatus() {
3949
4125
  const client = opencodeClient;
3950
4126
  if (!client?.mcp?.status) return void 0;
@@ -4129,6 +4305,7 @@ function logStartupDiagnostics(providers, opencodeVersion) {
4129
4305
 
4130
4306
  // src/doctor.ts
4131
4307
  var DOCTOR_COMMAND = "claude-code-doctor";
4308
+ var DOCTOR_COMMAND_DESCRIPTION = "Report what the Claude Code plugin sees: versions, cwd, live processes, pending proxy calls";
4132
4309
  var DOCTOR_MARKER = "\u258C **claude-code doctor**";
4133
4310
  function isRecord3(value) {
4134
4311
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -5340,6 +5517,33 @@ function registerBundledSkillPath(config) {
5340
5517
  paths.push(dir);
5341
5518
  return true;
5342
5519
  }
5520
+ function readBundledSkillInfos() {
5521
+ return discoverBundledSkills().flatMap((skill) => {
5522
+ const file = path7.join(skill.dir, "SKILL.md");
5523
+ let text;
5524
+ try {
5525
+ text = fs5.readFileSync(file, "utf8");
5526
+ } catch {
5527
+ return [];
5528
+ }
5529
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
5530
+ const frontmatter = match?.[1] ?? "";
5531
+ const field = (key) => {
5532
+ const line = new RegExp(`^${key}:\\s*(.+)$`, "m").exec(frontmatter)?.[1]?.trim();
5533
+ return line ? line.replace(/^(["'])(.*)\1$/, "$2") : void 0;
5534
+ };
5535
+ const description = field("description");
5536
+ return [
5537
+ {
5538
+ id: skill.name,
5539
+ name: field("name") ?? skill.name,
5540
+ ...description ? { description } : {},
5541
+ path: file,
5542
+ content: match ? text.slice(match[0].length) : text
5543
+ }
5544
+ ];
5545
+ });
5546
+ }
5343
5547
  function linkSkill(source, target) {
5344
5548
  try {
5345
5549
  fs5.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
@@ -5989,7 +6193,7 @@ function spawnInteractiveProcess(opts) {
5989
6193
  }
5990
6194
 
5991
6195
  // src/claude-code-language-model.ts
5992
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
6196
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
5993
6197
  import { unlink as unlink3 } from "fs/promises";
5994
6198
  import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
5995
6199
  import { randomUUID as randomUUID5 } from "crypto";
@@ -6018,6 +6222,32 @@ function resolveSessionAffinity(headers, providerOptions, providerKey) {
6018
6222
  }
6019
6223
  return "default";
6020
6224
  }
6225
+ function resolveOpencodeAgent(headers, providerOptions, providerKey) {
6226
+ if (providerOptions) {
6227
+ const bag = providerOptions[providerKey] ?? providerOptions["claude-code"];
6228
+ const agent = bag?.opencodeAgent;
6229
+ if (typeof agent === "string") return agent;
6230
+ }
6231
+ if (headers) {
6232
+ for (const key of Object.keys(headers)) {
6233
+ if (key.toLowerCase() === "x-opencode-agent") {
6234
+ const value = headers[key];
6235
+ if (typeof value === "string" && value.length > 0) return value;
6236
+ }
6237
+ }
6238
+ }
6239
+ return void 0;
6240
+ }
6241
+ function describeAbortReason(reason) {
6242
+ if (reason === void 0) return "undefined";
6243
+ if (reason instanceof Error) return `${reason.name}: ${reason.message}`;
6244
+ if (typeof reason === "string") return reason;
6245
+ try {
6246
+ return JSON.stringify(reason) ?? typeof reason;
6247
+ } catch {
6248
+ return typeof reason;
6249
+ }
6250
+ }
6021
6251
  var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
6022
6252
  "thinking_delta",
6023
6253
  "text_delta",
@@ -6217,9 +6447,9 @@ ${body}`;
6217
6447
  }
6218
6448
  });
6219
6449
  }
6220
- function readPromptFileIfPresent(path9) {
6450
+ function readPromptFileIfPresent(path10) {
6221
6451
  try {
6222
- const content = readFileSync4(path9, "utf8").trim();
6452
+ const content = readFileSync5(path10, "utf8").trim();
6223
6453
  return content || void 0;
6224
6454
  } catch {
6225
6455
  return void 0;
@@ -6334,10 +6564,10 @@ ${options.compressionSummary.trim()}`
6334
6564
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
6335
6565
  const content = parts.join("\n\n");
6336
6566
  if (!content) return void 0;
6337
- const path9 = join8(tmpdir2(), `opencode-cc-sys-${randomUUID5()}.md`);
6567
+ const path10 = join8(tmpdir2(), `opencode-cc-sys-${randomUUID5()}.md`);
6338
6568
  try {
6339
- writeFileSync4(path9, content, "utf8");
6340
- return path9;
6569
+ writeFileSync4(path10, content, "utf8");
6570
+ return path10;
6341
6571
  } catch (err) {
6342
6572
  log.warn("failed to write system prompt file", { error: String(err) });
6343
6573
  return void 0;
@@ -6423,6 +6653,19 @@ var ClaudeCodeLanguageModel = class {
6423
6653
  raw: reason
6424
6654
  };
6425
6655
  }
6656
+ /**
6657
+ * Whether this call only names the session, which gets the synthetic stub
6658
+ * rather than a `claude` spawn. opencode 1.x sends a title request with no
6659
+ * tools, and that is the whole test there. opencode 2 sends its tool set
6660
+ * along with it (measured on 2.0.11: `scope: "tools"`, agent `title`), so
6661
+ * every new V2 session paid for a second `claude` process just to title
6662
+ * itself; for a V2 model the request kind, carried as the `title` agent,
6663
+ * decides instead.
6664
+ */
6665
+ isTitleRequest(scope, options) {
6666
+ if (scope === "no-tools") return true;
6667
+ return this.config.hostApi === "v2" && this.getOpencodeAgent(options) === "title";
6668
+ }
6426
6669
  requestScope(options) {
6427
6670
  const tools = options?.tools;
6428
6671
  if (Array.isArray(tools)) return "tools";
@@ -6834,15 +7077,15 @@ var ClaudeCodeLanguageModel = class {
6834
7077
  ];
6835
7078
  return valid.includes(effort) ? effort : void 0;
6836
7079
  }
6837
- getOpencodeAgent(providerOptions) {
6838
- if (!providerOptions) return void 0;
6839
- const ownKey = this.config.provider;
6840
- const bag = providerOptions[ownKey] ?? providerOptions["claude-code"];
6841
- const agent = bag?.opencodeAgent;
6842
- return typeof agent === "string" ? agent : void 0;
7080
+ getOpencodeAgent(options) {
7081
+ return resolveOpencodeAgent(
7082
+ options?.headers,
7083
+ options.providerOptions,
7084
+ this.config.provider
7085
+ );
6843
7086
  }
6844
7087
  isCompactionCall(options) {
6845
- return this.getOpencodeAgent(options.providerOptions) === "compaction";
7088
+ return this.getOpencodeAgent(options) === "compaction";
6846
7089
  }
6847
7090
  /**
6848
7091
  * Pick the model used to handle /compact. Precedence:
@@ -6978,6 +7221,18 @@ var ClaudeCodeLanguageModel = class {
6978
7221
  };
6979
7222
  }
6980
7223
  async doGenerate(options) {
7224
+ const result = await this.doGenerateForHost(options);
7225
+ if (this.config.hostApi !== "v2") return result;
7226
+ const translate = createHostToolPartTranslator("v2");
7227
+ return {
7228
+ ...result,
7229
+ content: result.content.flatMap((part) => {
7230
+ const next = translate(part);
7231
+ return next ? [next] : [];
7232
+ })
7233
+ };
7234
+ }
7235
+ async doGenerateForHost(options) {
6981
7236
  if (!this.isCompactionCall(options) && this.requestScope(options) !== "no-tools" && parseSideQuestion(options.prompt)) {
6982
7237
  return this.doGenerateViaStream(options);
6983
7238
  }
@@ -6986,16 +7241,16 @@ var ClaudeCodeLanguageModel = class {
6986
7241
  const affinity = this.sessionAffinity(options);
6987
7242
  const cwd = await resolveSpawnCwdForSession(this.config.cwd, affinity);
6988
7243
  const effectiveModelId = resolveAgentModel(
6989
- this.getOpencodeAgent(options.providerOptions),
7244
+ this.getOpencodeAgent(options),
6990
7245
  this.modelId
6991
7246
  );
6992
7247
  const reasoningEffort = resolveAgentEffort(
6993
- this.getOpencodeAgent(options.providerOptions),
7248
+ this.getOpencodeAgent(options),
6994
7249
  this.getReasoningEffort(options.providerOptions)
6995
7250
  );
6996
7251
  const baseKey = sessionKey(
6997
7252
  cwd,
6998
- `${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options.providerOptions) ?? null])}`
7253
+ `${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options) ?? null])}`
6999
7254
  );
7000
7255
  const sk = effortSessionKey(baseKey, reasoningEffort);
7001
7256
  const compactionMode = this.isCompactionCall(options);
@@ -7005,10 +7260,10 @@ var ClaudeCodeLanguageModel = class {
7005
7260
  if (compactionMode) {
7006
7261
  return this.doGenerateViaStream(options);
7007
7262
  }
7008
- if (scope === "no-tools") {
7263
+ if (this.isTitleRequest(scope, options)) {
7009
7264
  log.info("doGenerate no-tools title stub", {
7010
7265
  compactionMode,
7011
- opencodeAgent: this.getOpencodeAgent(options.providerOptions),
7266
+ opencodeAgent: this.getOpencodeAgent(options),
7012
7267
  providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
7013
7268
  });
7014
7269
  const text = this.synthesizeTitle(options.prompt);
@@ -7408,7 +7663,19 @@ ${plan}
7408
7663
  warnings
7409
7664
  };
7410
7665
  }
7666
+ /**
7667
+ * Tool parts leave in opencode 1.x's vocabulary; on opencode 2.x they are
7668
+ * renamed at this one edge (src/host-tools.ts). On V1 the stream is
7669
+ * returned untouched.
7670
+ */
7411
7671
  async doStream(options) {
7672
+ const result = await this.doStreamForHost(options);
7673
+ return {
7674
+ ...result,
7675
+ stream: translateStreamForHost(result.stream, this.config.hostApi ?? "v1")
7676
+ };
7677
+ }
7678
+ async doStreamForHost(options) {
7412
7679
  const warnings = [];
7413
7680
  const skipPermissions = this.config.skipPermissions !== false;
7414
7681
  const scope = this.requestScope(options);
@@ -7416,16 +7683,16 @@ ${plan}
7416
7683
  const cwd = await resolveSpawnCwdForSession(this.config.cwd, affinity);
7417
7684
  const compactionMode = this.isCompactionCall(options);
7418
7685
  const effectiveModelId = compactionMode ? this.resolveCompactionModel() : resolveAgentModel(
7419
- this.getOpencodeAgent(options.providerOptions),
7686
+ this.getOpencodeAgent(options),
7420
7687
  this.modelId
7421
7688
  );
7422
7689
  const reasoningEffort = compactionMode ? void 0 : resolveAgentEffort(
7423
- this.getOpencodeAgent(options.providerOptions),
7690
+ this.getOpencodeAgent(options),
7424
7691
  this.getReasoningEffort(options.providerOptions)
7425
7692
  );
7426
7693
  const baseKey = sessionKey(
7427
7694
  cwd,
7428
- `${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options.providerOptions) ?? null])}`
7695
+ `${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options) ?? null])}`
7429
7696
  );
7430
7697
  const sk = compactionMode ? sessionKey(cwd, `${effectiveModelId}::compaction::${affinity}`) : effortSessionKey(baseKey, reasoningEffort);
7431
7698
  const toUsage = this.toUsage.bind(this);
@@ -7526,10 +7793,10 @@ ${plan}
7526
7793
  });
7527
7794
  return { stream: stream2, request: { body: { text: aside.question } } };
7528
7795
  }
7529
- if (scope === "no-tools" && !compactionMode) {
7796
+ if (this.isTitleRequest(scope, options) && !compactionMode) {
7530
7797
  log.info("doStream no-tools title stub", {
7531
7798
  compactionMode,
7532
- opencodeAgent: this.getOpencodeAgent(options.providerOptions),
7799
+ opencodeAgent: this.getOpencodeAgent(options),
7533
7800
  providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
7534
7801
  });
7535
7802
  const text = this.synthesizeTitle(options.prompt);
@@ -7719,7 +7986,7 @@ ${plan}
7719
7986
  proxyTools: resolvedProxy?.map((t) => t.name) ?? null,
7720
7987
  compactionMode,
7721
7988
  scope,
7722
- opencodeAgent: this.getOpencodeAgent(options.providerOptions),
7989
+ opencodeAgent: this.getOpencodeAgent(options),
7723
7990
  providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
7724
7991
  });
7725
7992
  const stream = new ReadableStream({
@@ -8381,7 +8648,7 @@ ${plan}
8381
8648
  });
8382
8649
  }
8383
8650
  activeProcess?.pendingProxyCompletions?.clear();
8384
- if (accountLimitHit && failoverAskActive) {
8651
+ if (accountLimitHit && failoverAskActive && msg.is_error === true) {
8385
8652
  const call = createAccountFailoverQuestionCall(sk, {
8386
8653
  sourceAccount,
8387
8654
  candidates: failoverAccounts,
@@ -9354,14 +9621,28 @@ ${plan}
9354
9621
  options.abortSignal.addEventListener("abort", () => {
9355
9622
  autoContinueState.aborted = true;
9356
9623
  if (turnCompleted || controllerClosed) {
9357
- if (activeProcess && activeProcess.lineEmitter.listenerCount("line") === 0 && getPendingProxyCalls(sk).length > 0) {
9358
- log.info("abort between proxy tool boundaries; releasing pending calls", { sk });
9359
- void interruptTurn(activeProcess).then((idle) => {
9360
- log.info("interrupt sent for aborted turn", { sk, idle });
9624
+ const stoppedProcess = activeProcess;
9625
+ if (stoppedProcess && stoppedProcess.lineEmitter.listenerCount("line") === 0 && getPendingProxyCalls(sk).length > 0) {
9626
+ const reason = describeAbortReason(options.abortSignal?.reason);
9627
+ void settleSessionRunState(affinity).then((stopped) => {
9628
+ const stillParked = stoppedProcess.lineEmitter.listenerCount("line") === 0 && getPendingProxyCalls(sk).length > 0;
9629
+ if (stopped === "busy" || !stillParked) {
9630
+ log.debug("abort at a tool boundary while opencode is still running the turn; keeping pending calls", {
9631
+ sk,
9632
+ session: stopped,
9633
+ stillParked,
9634
+ reason
9635
+ });
9636
+ return;
9637
+ }
9638
+ log.info("abort between proxy tool boundaries; releasing pending calls", { sk, reason });
9639
+ void interruptTurn(stoppedProcess).then((idle) => {
9640
+ log.info("interrupt sent for aborted turn", { sk, idle });
9641
+ });
9642
+ releaseAbandonedProxyCalls(
9643
+ "Provider stream was aborted while opencode was running its proxy tool calls"
9644
+ );
9361
9645
  });
9362
- releaseAbandonedProxyCalls(
9363
- "Provider stream was aborted while opencode was running its proxy tool calls"
9364
- );
9365
9646
  }
9366
9647
  return;
9367
9648
  }
@@ -9487,7 +9768,7 @@ ${plan}
9487
9768
  // src/cleanup-stale.ts
9488
9769
  import {
9489
9770
  existsSync as existsSync4,
9490
- readFileSync as readFileSync5,
9771
+ readFileSync as readFileSync6,
9491
9772
  realpathSync,
9492
9773
  rmSync as rmSync3,
9493
9774
  writeFileSync as writeFileSync5
@@ -9514,7 +9795,7 @@ function userIntendsToUseUnscoped() {
9514
9795
  const cfg = userOpencodeJsonPath();
9515
9796
  if (!existsSync4(cfg)) return false;
9516
9797
  try {
9517
- const json = JSON.parse(readFileSync5(cfg, "utf8"));
9798
+ const json = JSON.parse(readFileSync6(cfg, "utf8"));
9518
9799
  const plugins = json.plugin;
9519
9800
  if (!Array.isArray(plugins)) return false;
9520
9801
  return plugins.some(
@@ -9563,7 +9844,7 @@ function cleanupOne(cacheRoot, ourDir) {
9563
9844
  if (!existsSync4(pkgJsonPath)) return;
9564
9845
  let pkg = {};
9565
9846
  try {
9566
- pkg = JSON.parse(readFileSync5(pkgJsonPath, "utf8"));
9847
+ pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
9567
9848
  } catch {
9568
9849
  return;
9569
9850
  }
@@ -9582,7 +9863,7 @@ function cleanupOne(cacheRoot, ourDir) {
9582
9863
  const cachePkgJson = join9(cacheRoot, "package.json");
9583
9864
  if (!existsSync4(cachePkgJson)) return;
9584
9865
  try {
9585
- const cfg = JSON.parse(readFileSync5(cachePkgJson, "utf8"));
9866
+ const cfg = JSON.parse(readFileSync6(cachePkgJson, "utf8"));
9586
9867
  if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
9587
9868
  delete cfg.dependencies[STALE_PACKAGE_NAME];
9588
9869
  writeFileSync5(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
@@ -9595,6 +9876,414 @@ function cleanupOne(cacheRoot, ourDir) {
9595
9876
  }
9596
9877
  }
9597
9878
 
9879
+ // src/v2-client.ts
9880
+ import path9 from "path";
9881
+ function formatAgentTypeList(agents) {
9882
+ const lines = agents.flatMap((agent) => {
9883
+ if (agent.hidden === true || agent.mode === "primary") return [];
9884
+ const name = typeof agent.id === "string" ? agent.id : agent.name;
9885
+ if (typeof name !== "string" || name.length === 0) return [];
9886
+ const blurb = typeof agent.description === "string" ? agent.description.trim() : "";
9887
+ return [`- ${name}: ${blurb || "(no description)"}`];
9888
+ });
9889
+ if (lines.length === 0) return void 0;
9890
+ return `Available agent types and the tools they have access to:
9891
+ ${lines.join("\n")}`;
9892
+ }
9893
+ function describe(value) {
9894
+ if (typeof value === "string") return value;
9895
+ if (typeof value === "function") {
9896
+ try {
9897
+ const result = value();
9898
+ return typeof result === "string" ? result : "";
9899
+ } catch {
9900
+ return "";
9901
+ }
9902
+ }
9903
+ return "";
9904
+ }
9905
+ function toV1Session(info) {
9906
+ if (!info || typeof info !== "object") return void 0;
9907
+ const session = info;
9908
+ const root = session.location?.directory;
9909
+ const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path9.resolve(root, session.subpath) : root : void 0;
9910
+ return { ...info, directory, parentID: session.parentID };
9911
+ }
9912
+ function createV1ClientShim(ctx) {
9913
+ const client = {};
9914
+ const mcpList = ctx.mcp?.list;
9915
+ if (typeof mcpList === "function") {
9916
+ client.mcp = {
9917
+ // V1: `{ data: { [server]: { status } } }`. V2: `{ data: [{ name, status: { status } }] }`.
9918
+ status: async () => {
9919
+ const result = await mcpList.call(ctx.mcp);
9920
+ const data = {};
9921
+ for (const server2 of result?.data ?? []) {
9922
+ const status = server2?.status?.status;
9923
+ if (typeof server2?.name === "string" && typeof status === "string") {
9924
+ data[server2.name] = { status };
9925
+ }
9926
+ }
9927
+ return { data };
9928
+ }
9929
+ };
9930
+ }
9931
+ const toolList = ctx.tool?.list;
9932
+ if (typeof toolList === "function") {
9933
+ client.tool = {
9934
+ // V1: `{ data: [{ id, description, parameters }] }`. V2 returns the tools
9935
+ // themselves, with an Effect schema for input rather than JSON Schema, so
9936
+ // `parameters` is empty; only `proxyOpencodeTools` reads it.
9937
+ list: async () => {
9938
+ const tools = await toolList.call(ctx.tool);
9939
+ const data = (tools ?? []).flatMap((tool) => {
9940
+ const id = typeof tool?.id === "string" ? tool.id : tool?.name;
9941
+ return typeof id === "string" ? [{ id, description: describe(tool.description), parameters: {} }] : [];
9942
+ });
9943
+ const subagent = data.find((tool) => tool.id === "subagent");
9944
+ if (subagent && !data.some((tool) => tool.id === "task")) {
9945
+ let agentList;
9946
+ const agentApi = ctx.agent?.list;
9947
+ if (typeof agentApi === "function") {
9948
+ try {
9949
+ agentList = formatAgentTypeList((await agentApi.call(ctx.agent))?.data ?? []);
9950
+ } catch {
9951
+ agentList = void 0;
9952
+ }
9953
+ }
9954
+ const description = agentList ? `${subagent.description}
9955
+
9956
+ ${agentList}` : subagent.description;
9957
+ data.push({ ...subagent, id: "task", description });
9958
+ }
9959
+ return { data };
9960
+ }
9961
+ };
9962
+ }
9963
+ const sessionGet = ctx.session?.get;
9964
+ if (typeof sessionGet === "function") {
9965
+ client.session = {
9966
+ get: async (options) => ({
9967
+ data: toV1Session(await sessionGet.call(ctx.session, { sessionID: options.path.id }))
9968
+ })
9969
+ };
9970
+ }
9971
+ return client;
9972
+ }
9973
+
9974
+ // src/v2.ts
9975
+ var V2_PLUGIN_PACKAGE = "aisdk:@khalilgharbaoui/opencode-claude-code-plugin";
9976
+ var SESSION_AFFINITY_HEADER = "x-session-affinity";
9977
+ var OPENCODE_AGENT_HEADER = "x-opencode-agent";
9978
+ var PLUGIN_ONLY_SETTINGS = ["accounts", "defaultSubagentModel"];
9979
+ var OPENCODE_SDK_OPTION_KEYS = ["fetch", "headers", "body", "name"];
9980
+ function isClaudeCodeProviderId(providerID) {
9981
+ if (typeof providerID !== "string") return false;
9982
+ return providerID === BASE_PROVIDER_ID || providerID.startsWith(`${BASE_PROVIDER_ID}-`);
9983
+ }
9984
+ function agentForRequest(kind, agent) {
9985
+ if (kind === "compaction") return "compaction";
9986
+ if (kind === "title") return "title";
9987
+ return agent;
9988
+ }
9989
+ function releasedAt(date) {
9990
+ const ms = date ? Date.parse(date) : Number.NaN;
9991
+ return Number.isFinite(ms) ? ms : 0;
9992
+ }
9993
+ function modalities(flags) {
9994
+ return Object.entries(flags).filter(([, enabled]) => enabled === true).map(([name]) => name);
9995
+ }
9996
+ function toV2Model(model, providerID, modelId) {
9997
+ return {
9998
+ id: modelId,
9999
+ modelID: modelId,
10000
+ providerID,
10001
+ name: model.name,
10002
+ family: model.family,
10003
+ capabilities: {
10004
+ tools: model.capabilities.toolcall,
10005
+ input: modalities(model.capabilities.input),
10006
+ output: modalities(model.capabilities.output)
10007
+ },
10008
+ variants: Object.entries(model.variants ?? {}).map(([id, settings]) => ({
10009
+ id,
10010
+ settings: { ...settings }
10011
+ })),
10012
+ time: { released: releasedAt(model.release_date) },
10013
+ cost: [
10014
+ {
10015
+ input: model.cost.input,
10016
+ output: model.cost.output,
10017
+ cache: { read: model.cost.cache.read, write: model.cost.cache.write }
10018
+ }
10019
+ ],
10020
+ status: model.status ?? "active",
10021
+ enabled: true,
10022
+ limit: { context: model.limit.context, output: model.limit.output },
10023
+ package: V2_PLUGIN_PACKAGE
10024
+ };
10025
+ }
10026
+ function v2ModelsForProvider(providerID, modelSuffix) {
10027
+ return Object.entries(defaultModels).map(
10028
+ ([id, model]) => toV2Model(model, providerID, modelSuffix ? `${id}@${modelSuffix}` : id)
10029
+ );
10030
+ }
10031
+ function stripPluginOnly(settings) {
10032
+ const result = { ...settings ?? {} };
10033
+ for (const key of PLUGIN_ONLY_SETTINGS) delete result[key];
10034
+ return result;
10035
+ }
10036
+ function planV2Providers(seedSettings, defaultProxyTools) {
10037
+ const base = {
10038
+ cliPath: "claude",
10039
+ proxyTools: [...defaultProxyTools],
10040
+ ...stripPluginOnly(seedSettings)
10041
+ };
10042
+ const accounts = resolveAccounts(seedSettings?.accounts);
10043
+ if (!accounts) {
10044
+ return [
10045
+ {
10046
+ info: {
10047
+ id: BASE_PROVIDER_ID,
10048
+ name: "Claude Code",
10049
+ activation: "enabled",
10050
+ package: V2_PLUGIN_PACKAGE,
10051
+ settings: { ...base, providerID: BASE_PROVIDER_ID }
10052
+ },
10053
+ models: v2ModelsForProvider(BASE_PROVIDER_ID)
10054
+ }
10055
+ ];
10056
+ }
10057
+ return accounts.map((account) => {
10058
+ const providerID = accountProviderId(account);
10059
+ return {
10060
+ info: {
10061
+ id: providerID,
10062
+ name: accountDisplayName(account),
10063
+ activation: "enabled",
10064
+ package: V2_PLUGIN_PACKAGE,
10065
+ settings: {
10066
+ ...base,
10067
+ account,
10068
+ // The resolved list, so this account's language model can offer the
10069
+ // others when it runs out of usage (src/account-failover.ts).
10070
+ failoverAccounts: accounts,
10071
+ providerID,
10072
+ // Kept separately because the wrapper replaces `cliPath`, and a
10073
+ // failover builds another account's wrapper on the same base.
10074
+ baseCliPath: String(base.cliPath ?? "claude")
10075
+ }
10076
+ },
10077
+ models: v2ModelsForProvider(providerID, accountModelSuffix(account))
10078
+ };
10079
+ });
10080
+ }
10081
+ async function resolveSdkSettings(providerID, planned, eventOptions) {
10082
+ const fromEvent = { ...eventOptions };
10083
+ for (const key of OPENCODE_SDK_OPTION_KEYS) delete fromEvent[key];
10084
+ const merged = {
10085
+ ...stripPluginOnly({ ...planned ?? {}, ...fromEvent }),
10086
+ providerID,
10087
+ // The only place a model is told it serves opencode 2.
10088
+ hostApi: "v2"
10089
+ };
10090
+ const account = typeof merged.account === "string" ? merged.account : void 0;
10091
+ if (account) {
10092
+ const base = String(merged.baseCliPath ?? merged.cliPath ?? "claude");
10093
+ const runtime = await ensureAccountRuntime(account, base);
10094
+ Object.assign(merged, runtime, { baseCliPath: base });
10095
+ }
10096
+ return merged;
10097
+ }
10098
+ function plainObject(value) {
10099
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
10100
+ }
10101
+ function configuredSeedSettings(config, pluginOptions) {
10102
+ const provider = plainObject(plainObject(config.provider)?.[BASE_PROVIDER_ID]);
10103
+ return {
10104
+ ...plainObject(provider?.options) ?? {},
10105
+ ...plainObject(provider?.settings) ?? {},
10106
+ ...pluginOptions ?? {}
10107
+ };
10108
+ }
10109
+ function isOpencodeV2Context(ctx) {
10110
+ const context = plainObject(ctx);
10111
+ if (!context) return false;
10112
+ const version = plainObject(context.app)?.version;
10113
+ if (typeof version === "string") {
10114
+ const major = Number.parseInt(version.replace(/^v/, ""), 10);
10115
+ if (Number.isFinite(major) && major < 2) return false;
10116
+ }
10117
+ const hasHook = (domain, name) => typeof plainObject(domain)?.[name] === "function";
10118
+ return hasHook(context.provider, "transform") && hasHook(context.aisdk, "hook") && hasHook(context.session, "hook");
10119
+ }
10120
+ function commandPromptText(command, args) {
10121
+ const rest = (args ?? "").trim();
10122
+ return rest ? `/${command} ${rest}` : `/${command}`;
10123
+ }
10124
+ function deletedSessionIdV2(event) {
10125
+ if (!event || event.type !== "session.deleted") return void 0;
10126
+ const id = event.data?.sessionID;
10127
+ return typeof id === "string" && id.length > 0 ? id : void 0;
10128
+ }
10129
+ async function releaseDeletedSessions(subscribe, signal) {
10130
+ try {
10131
+ for await (const event of subscribe({ signal })) {
10132
+ if (signal.aborted) break;
10133
+ const sessionID = deletedSessionIdV2(event);
10134
+ if (!sessionID) continue;
10135
+ const released = deleteActiveProcessesForSession(sessionID);
10136
+ if (released.length > 0) {
10137
+ log.info("released claude state for deleted session", { sessionID, released });
10138
+ }
10139
+ }
10140
+ } catch (err) {
10141
+ if (!signal.aborted) {
10142
+ log.warn("opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction", {
10143
+ error: err instanceof Error ? err.message : String(err)
10144
+ });
10145
+ }
10146
+ }
10147
+ }
10148
+ function createV2Setup(deps) {
10149
+ return async (ctx) => {
10150
+ if (!isOpencodeV2Context(ctx)) {
10151
+ log.debug("setup called outside opencode 2; leaving it to server()", {
10152
+ version: ctx?.app?.version
10153
+ });
10154
+ return () => void 0;
10155
+ }
10156
+ ensureProcessExitCleanup();
10157
+ setOpencodeClient(createV1ClientShim(ctx));
10158
+ const directory = ctx.location?.directory;
10159
+ setOpencodeProjectDirectory(isUsableDirectory(directory) ? directory : void 0);
10160
+ if (deps.buildAgentRegistry) {
10161
+ try {
10162
+ const config = deps.loadConfig(directory ?? process.cwd());
10163
+ await deps.buildAgentRegistry({
10164
+ ...config,
10165
+ provider: {
10166
+ ...plainObject(config.provider) ?? {},
10167
+ [BASE_PROVIDER_ID]: {
10168
+ options: configuredSeedSettings(config, plainObject(ctx.options))
10169
+ }
10170
+ }
10171
+ });
10172
+ } catch (err) {
10173
+ log.warn("failed to build the agent registry for opencode 2", {
10174
+ error: err instanceof Error ? err.message : String(err)
10175
+ });
10176
+ }
10177
+ }
10178
+ const planned = /* @__PURE__ */ new Map();
10179
+ const registrations = [];
10180
+ registrations.push(
10181
+ await ctx.provider.transform((editor) => {
10182
+ const seed = editor.get(BASE_PROVIDER_ID);
10183
+ const seedSettings = {
10184
+ ...seed?.provider.settings ?? {},
10185
+ ...configuredSeedSettings(
10186
+ deps.loadConfig(directory ?? process.cwd()),
10187
+ plainObject(ctx.options)
10188
+ )
10189
+ };
10190
+ const plans = planV2Providers(seedSettings, deps.defaultProxyTools);
10191
+ planned.clear();
10192
+ for (const plan of plans) {
10193
+ planned.set(plan.info.id, plan.info.settings ?? {});
10194
+ if (editor.get(plan.info.id)) {
10195
+ editor.update(plan.info.id, (provider) => {
10196
+ provider.name = plan.info.name;
10197
+ provider.activation = plan.info.activation;
10198
+ provider.package = plan.info.package;
10199
+ provider.settings = plan.info.settings;
10200
+ });
10201
+ editor.models.set(plan.info.id, plan.models);
10202
+ } else {
10203
+ editor.add({ info: plan.info, models: plan.models });
10204
+ }
10205
+ }
10206
+ if (seed && !plans.some((plan) => plan.info.id === BASE_PROVIDER_ID)) {
10207
+ editor.remove(BASE_PROVIDER_ID);
10208
+ }
10209
+ logStartupDiagnostics(
10210
+ Object.fromEntries(
10211
+ plans.map((plan) => [plan.info.id, { name: plan.info.name, options: plan.info.settings }])
10212
+ ),
10213
+ ctx.app?.version
10214
+ );
10215
+ })
10216
+ );
10217
+ registrations.push(
10218
+ await ctx.aisdk.hook("sdk", async (event) => {
10219
+ const providerID = event.model.providerID;
10220
+ if (!isClaudeCodeProviderId(providerID)) return;
10221
+ const settings = await resolveSdkSettings(providerID, planned.get(providerID), event.options);
10222
+ event.sdk = deps.createProvider(settings);
10223
+ log.debug("v2 sdk created", { providerID, cliPath: settings.cliPath });
10224
+ })
10225
+ );
10226
+ registrations.push(
10227
+ await ctx.session.hook("model.request", (event) => {
10228
+ if (!isClaudeCodeProviderId(event.model.providerID)) return;
10229
+ event.headers[SESSION_AFFINITY_HEADER] = event.sessionID;
10230
+ event.headers[OPENCODE_AGENT_HEADER] = agentForRequest(event.kind, event.agent);
10231
+ })
10232
+ );
10233
+ const prompt = ctx.session.prompt;
10234
+ if (ctx.command?.transform && typeof prompt === "function") {
10235
+ const send = (input, command, delivery) => prompt.call(ctx.session, {
10236
+ ...input.prompt,
10237
+ sessionID: input.sessionID,
10238
+ text: commandPromptText(command, input.prompt.text),
10239
+ delivery
10240
+ }).then(() => void 0);
10241
+ registrations.push(
10242
+ await ctx.command.transform((editor) => {
10243
+ editor.add({
10244
+ name: DOCTOR_COMMAND,
10245
+ description: DOCTOR_COMMAND_DESCRIPTION,
10246
+ execute: (input) => send(input, DOCTOR_COMMAND, input.delivery)
10247
+ });
10248
+ editor.add({
10249
+ name: "btw",
10250
+ description: BTW_COMMAND_DESCRIPTION,
10251
+ execute: (input) => send(input, "btw", "queue")
10252
+ });
10253
+ })
10254
+ );
10255
+ }
10256
+ const bundledSkills = readBundledSkillInfos();
10257
+ if (typeof ctx.skill?.transform === "function" && bundledSkills.length > 0) {
10258
+ registrations.push(
10259
+ await ctx.skill.transform((editor) => {
10260
+ const added = [];
10261
+ for (const skill of bundledSkills) {
10262
+ if (editor.get(skill.id)) continue;
10263
+ editor.add(skill);
10264
+ added.push(skill.id);
10265
+ }
10266
+ log.debug("v2 bundled skills registered", { added });
10267
+ })
10268
+ );
10269
+ }
10270
+ const events = new AbortController();
10271
+ if (typeof ctx.event?.subscribe === "function") {
10272
+ void releaseDeletedSessions(ctx.event.subscribe.bind(ctx.event), events.signal);
10273
+ }
10274
+ log.info("claude-code plugin set up for opencode 2", {
10275
+ opencode: ctx.app?.version,
10276
+ directory
10277
+ });
10278
+ return async () => {
10279
+ events.abort();
10280
+ for (const registration of registrations.splice(0)) {
10281
+ await registration.dispose().catch(() => void 0);
10282
+ }
10283
+ };
10284
+ };
10285
+ }
10286
+
9598
10287
  // src/index.ts
9599
10288
  function pickOpencodeDirectory(input) {
9600
10289
  if (!input || typeof input !== "object") return void 0;
@@ -9617,7 +10306,7 @@ function registerSideQuestionCommand(config) {
9617
10306
  if (config.command.btw) return false;
9618
10307
  config.command.btw = {
9619
10308
  template: "/btw $ARGUMENTS",
9620
- description: "Ask a side question in the live Claude Code session without changing its context"
10309
+ description: BTW_COMMAND_DESCRIPTION
9621
10310
  };
9622
10311
  return true;
9623
10312
  }
@@ -9626,7 +10315,7 @@ function registerDoctorCommand(config) {
9626
10315
  if (config.command[DOCTOR_COMMAND]) return false;
9627
10316
  config.command[DOCTOR_COMMAND] = {
9628
10317
  template: `/${DOCTOR_COMMAND} $ARGUMENTS`,
9629
- description: "Report what the Claude Code plugin sees: versions, cwd, live processes, pending proxy calls"
10318
+ description: DOCTOR_COMMAND_DESCRIPTION
9630
10319
  };
9631
10320
  return true;
9632
10321
  }
@@ -9675,6 +10364,7 @@ function createClaudeCode(settings = {}) {
9675
10364
  return new ClaudeCodeLanguageModel(modelId, {
9676
10365
  provider: providerName,
9677
10366
  cliPath,
10367
+ hostApi: settings.hostApi,
9678
10368
  cwd: settings.cwd,
9679
10369
  account: settings.account,
9680
10370
  configDir: settings.configDir,
@@ -10004,13 +10694,20 @@ var server = async (input) => {
10004
10694
  };
10005
10695
  var index_default = {
10006
10696
  id: "@khalilgharbaoui/opencode-claude-code-plugin",
10007
- server
10697
+ server,
10698
+ setup: createV2Setup({
10699
+ createProvider: createClaudeCode,
10700
+ defaultProxyTools: DEFAULT_PROXY_TOOL_NAMES,
10701
+ loadConfig: loadMergedOpencodeConfig,
10702
+ buildAgentRegistry: (config) => buildAgentRegistry(config)
10703
+ })
10008
10704
  };
10009
10705
  export {
10010
10706
  ClaudeCodeLanguageModel,
10011
10707
  DEFAULT_PROXY_TOOL_NAMES,
10012
10708
  _resetPlanModeWarningForTests,
10013
10709
  bridgeOpencodeMcp,
10710
+ buildAgentRegistry,
10014
10711
  claudeCodeProviders,
10015
10712
  configModelsForProvider,
10016
10713
  createClaudeCode,