@khalilgharbaoui/opencode-claude-code-plugin 0.24.0 → 0.26.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/README.md +35 -5
- package/dist/index.d.ts +226 -1
- package/dist/index.js +716 -45
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +21 -3
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
|
-
|
|
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
|
|
3915
|
+
function opencodeConfigLayers(cwd) {
|
|
3785
3916
|
const worktree = detectWorktree(cwd);
|
|
3786
|
-
|
|
3787
|
-
|
|
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)
|
|
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
|
-
|
|
3940
|
+
layers.push(loadProjectFilesInDir(dir));
|
|
3810
3941
|
}
|
|
3811
3942
|
for (const dir of dotOpencodeDirs(cwd, worktree)) {
|
|
3812
|
-
|
|
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)) {
|
|
@@ -4129,6 +4272,7 @@ function logStartupDiagnostics(providers, opencodeVersion) {
|
|
|
4129
4272
|
|
|
4130
4273
|
// src/doctor.ts
|
|
4131
4274
|
var DOCTOR_COMMAND = "claude-code-doctor";
|
|
4275
|
+
var DOCTOR_COMMAND_DESCRIPTION = "Report what the Claude Code plugin sees: versions, cwd, live processes, pending proxy calls";
|
|
4132
4276
|
var DOCTOR_MARKER = "\u258C **claude-code doctor**";
|
|
4133
4277
|
function isRecord3(value) {
|
|
4134
4278
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -4856,6 +5000,8 @@ var opusCost = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
|
4856
5000
|
var fableCost = { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 };
|
|
4857
5001
|
var fable51Cost = { input: 10, output: 50, cacheRead: 0.25, cacheWrite: 12.5 };
|
|
4858
5002
|
var opusFastCost = { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 };
|
|
5003
|
+
var opus55Cost = { input: 4, output: 20, cacheRead: 0.2, cacheWrite: 5 };
|
|
5004
|
+
var opus55FastCost = { input: 8, output: 40, cacheRead: 0.4, cacheWrite: 10 };
|
|
4859
5005
|
function toConfigModel(model) {
|
|
4860
5006
|
const inputMods = [];
|
|
4861
5007
|
const outputMods = [];
|
|
@@ -4982,10 +5128,12 @@ var defaultModels = {
|
|
|
4982
5128
|
// `--settings {"fastMode":true}` on the spawn. Retired `-fast` model strings
|
|
4983
5129
|
// (`claude-opus-4-6-fast`) are a different thing and are not registered here.
|
|
4984
5130
|
//
|
|
4985
|
-
// Only Opus 4.8 and Opus 5 qualify: the CLI gates fast mode on
|
|
4986
|
-
// model name containing `opus-4-8` or `opus-5
|
|
4987
|
-
//
|
|
4988
|
-
//
|
|
5131
|
+
// Only Opus 4.8, Opus 5 and Opus 5.5 qualify: the CLI gates fast mode on
|
|
5132
|
+
// the resolved model name containing `opus-4-8` or `opus-5` (which
|
|
5133
|
+
// `claude-opus-5-5` satisfies too, and 2.1.280's model catalog lists
|
|
5134
|
+
// `fast_mode` for it outright), so registering a fast entry for any other
|
|
5135
|
+
// model would produce a picker option that silently runs at standard speed
|
|
5136
|
+
// while displaying the fast price.
|
|
4989
5137
|
"claude-opus-4-8-fast": defineModel({
|
|
4990
5138
|
id: "claude-opus-4-8-fast",
|
|
4991
5139
|
name: "Claude Opus 4.8 Fast",
|
|
@@ -5019,6 +5167,33 @@ var defaultModels = {
|
|
|
5019
5167
|
multiplier: 10,
|
|
5020
5168
|
releaseDate: "2026-07-24"
|
|
5021
5169
|
}),
|
|
5170
|
+
// Opus 5.5 needs Claude Code 2.1.280+: the API rejects it from an older CLI
|
|
5171
|
+
// with a 400 naming that floor, which the plugin surfaces as a turn error.
|
|
5172
|
+
// Thinking cannot be switched off for it (the CLI's catalog marks it
|
|
5173
|
+
// `rejects_disabled_thinking`); the plugin never asks for that, so nothing
|
|
5174
|
+
// here has to special-case it.
|
|
5175
|
+
"claude-opus-5-5": defineModel({
|
|
5176
|
+
id: "claude-opus-5-5",
|
|
5177
|
+
name: "Claude Opus 5.5",
|
|
5178
|
+
family: "opus",
|
|
5179
|
+
reasoning: true,
|
|
5180
|
+
context: 1e6,
|
|
5181
|
+
output: 128e3,
|
|
5182
|
+
cost: opus55Cost,
|
|
5183
|
+
multiplier: 4,
|
|
5184
|
+
releaseDate: "2026-09-22"
|
|
5185
|
+
}),
|
|
5186
|
+
"claude-opus-5-5-fast": defineModel({
|
|
5187
|
+
id: "claude-opus-5-5-fast",
|
|
5188
|
+
name: "Claude Opus 5.5 Fast",
|
|
5189
|
+
family: "opus",
|
|
5190
|
+
reasoning: true,
|
|
5191
|
+
context: 1e6,
|
|
5192
|
+
output: 128e3,
|
|
5193
|
+
cost: opus55FastCost,
|
|
5194
|
+
multiplier: 8,
|
|
5195
|
+
releaseDate: "2026-09-22"
|
|
5196
|
+
}),
|
|
5022
5197
|
"claude-fable-5": defineModel({
|
|
5023
5198
|
id: "claude-fable-5",
|
|
5024
5199
|
name: "Claude Fable 5",
|
|
@@ -5309,6 +5484,33 @@ function registerBundledSkillPath(config) {
|
|
|
5309
5484
|
paths.push(dir);
|
|
5310
5485
|
return true;
|
|
5311
5486
|
}
|
|
5487
|
+
function readBundledSkillInfos() {
|
|
5488
|
+
return discoverBundledSkills().flatMap((skill) => {
|
|
5489
|
+
const file = path7.join(skill.dir, "SKILL.md");
|
|
5490
|
+
let text;
|
|
5491
|
+
try {
|
|
5492
|
+
text = fs5.readFileSync(file, "utf8");
|
|
5493
|
+
} catch {
|
|
5494
|
+
return [];
|
|
5495
|
+
}
|
|
5496
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
5497
|
+
const frontmatter = match?.[1] ?? "";
|
|
5498
|
+
const field = (key) => {
|
|
5499
|
+
const line = new RegExp(`^${key}:\\s*(.+)$`, "m").exec(frontmatter)?.[1]?.trim();
|
|
5500
|
+
return line ? line.replace(/^(["'])(.*)\1$/, "$2") : void 0;
|
|
5501
|
+
};
|
|
5502
|
+
const description = field("description");
|
|
5503
|
+
return [
|
|
5504
|
+
{
|
|
5505
|
+
id: skill.name,
|
|
5506
|
+
name: field("name") ?? skill.name,
|
|
5507
|
+
...description ? { description } : {},
|
|
5508
|
+
path: file,
|
|
5509
|
+
content: match ? text.slice(match[0].length) : text
|
|
5510
|
+
}
|
|
5511
|
+
];
|
|
5512
|
+
});
|
|
5513
|
+
}
|
|
5312
5514
|
function linkSkill(source, target) {
|
|
5313
5515
|
try {
|
|
5314
5516
|
fs5.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
|
|
@@ -5958,7 +6160,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
5958
6160
|
}
|
|
5959
6161
|
|
|
5960
6162
|
// src/claude-code-language-model.ts
|
|
5961
|
-
import { readFileSync as
|
|
6163
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5962
6164
|
import { unlink as unlink3 } from "fs/promises";
|
|
5963
6165
|
import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
|
|
5964
6166
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -5987,6 +6189,22 @@ function resolveSessionAffinity(headers, providerOptions, providerKey) {
|
|
|
5987
6189
|
}
|
|
5988
6190
|
return "default";
|
|
5989
6191
|
}
|
|
6192
|
+
function resolveOpencodeAgent(headers, providerOptions, providerKey) {
|
|
6193
|
+
if (providerOptions) {
|
|
6194
|
+
const bag = providerOptions[providerKey] ?? providerOptions["claude-code"];
|
|
6195
|
+
const agent = bag?.opencodeAgent;
|
|
6196
|
+
if (typeof agent === "string") return agent;
|
|
6197
|
+
}
|
|
6198
|
+
if (headers) {
|
|
6199
|
+
for (const key of Object.keys(headers)) {
|
|
6200
|
+
if (key.toLowerCase() === "x-opencode-agent") {
|
|
6201
|
+
const value = headers[key];
|
|
6202
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
return void 0;
|
|
6207
|
+
}
|
|
5990
6208
|
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
5991
6209
|
"thinking_delta",
|
|
5992
6210
|
"text_delta",
|
|
@@ -6186,9 +6404,9 @@ ${body}`;
|
|
|
6186
6404
|
}
|
|
6187
6405
|
});
|
|
6188
6406
|
}
|
|
6189
|
-
function readPromptFileIfPresent(
|
|
6407
|
+
function readPromptFileIfPresent(path10) {
|
|
6190
6408
|
try {
|
|
6191
|
-
const content =
|
|
6409
|
+
const content = readFileSync5(path10, "utf8").trim();
|
|
6192
6410
|
return content || void 0;
|
|
6193
6411
|
} catch {
|
|
6194
6412
|
return void 0;
|
|
@@ -6303,10 +6521,10 @@ ${options.compressionSummary.trim()}`
|
|
|
6303
6521
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
6304
6522
|
const content = parts.join("\n\n");
|
|
6305
6523
|
if (!content) return void 0;
|
|
6306
|
-
const
|
|
6524
|
+
const path10 = join8(tmpdir2(), `opencode-cc-sys-${randomUUID5()}.md`);
|
|
6307
6525
|
try {
|
|
6308
|
-
writeFileSync4(
|
|
6309
|
-
return
|
|
6526
|
+
writeFileSync4(path10, content, "utf8");
|
|
6527
|
+
return path10;
|
|
6310
6528
|
} catch (err) {
|
|
6311
6529
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
6312
6530
|
return void 0;
|
|
@@ -6338,14 +6556,14 @@ function reportFastModeState(msg, requested) {
|
|
|
6338
6556
|
const reason = msg.fast_mode_disabled_reason;
|
|
6339
6557
|
if (state === "cooldown") {
|
|
6340
6558
|
log.notice(
|
|
6341
|
-
"fast mode is in cooldown after a rate limit; this turn runs at standard speed and is billed at standard Opus rates, not the
|
|
6559
|
+
"fast mode is in cooldown after a rate limit; this turn runs at standard speed and is billed at standard Opus rates, not the fast price shown in the model picker.",
|
|
6342
6560
|
{ state, reason: reason ?? null }
|
|
6343
6561
|
);
|
|
6344
6562
|
return;
|
|
6345
6563
|
}
|
|
6346
6564
|
const key = reason ?? "unknown";
|
|
6347
6565
|
const explanation = reason ? FAST_MODE_REASONS[reason] : void 0;
|
|
6348
|
-
const message = `fast mode was requested but is off${explanation ? `: ${explanation}` : reason ? ` (${reason})` : ""}. Turns run at standard speed and are billed at standard Opus rates, not the
|
|
6566
|
+
const message = `fast mode was requested but is off${explanation ? `: ${explanation}` : reason ? ` (${reason})` : ""}. Turns run at standard speed and are billed at standard Opus rates, not the fast price shown in the model picker. Switch to the non-fast model id to make the picker's price accurate.`;
|
|
6349
6567
|
if (warnedFastModeReasons.has(key)) {
|
|
6350
6568
|
log.debug(message, { state, reason: reason ?? null });
|
|
6351
6569
|
return;
|
|
@@ -6392,6 +6610,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6392
6610
|
raw: reason
|
|
6393
6611
|
};
|
|
6394
6612
|
}
|
|
6613
|
+
/**
|
|
6614
|
+
* Whether this call only names the session, which gets the synthetic stub
|
|
6615
|
+
* rather than a `claude` spawn. opencode 1.x sends a title request with no
|
|
6616
|
+
* tools, and that is the whole test there. opencode 2 sends its tool set
|
|
6617
|
+
* along with it (measured on 2.0.11: `scope: "tools"`, agent `title`), so
|
|
6618
|
+
* every new V2 session paid for a second `claude` process just to title
|
|
6619
|
+
* itself; for a V2 model the request kind, carried as the `title` agent,
|
|
6620
|
+
* decides instead.
|
|
6621
|
+
*/
|
|
6622
|
+
isTitleRequest(scope, options) {
|
|
6623
|
+
if (scope === "no-tools") return true;
|
|
6624
|
+
return this.config.hostApi === "v2" && this.getOpencodeAgent(options) === "title";
|
|
6625
|
+
}
|
|
6395
6626
|
requestScope(options) {
|
|
6396
6627
|
const tools = options?.tools;
|
|
6397
6628
|
if (Array.isArray(tools)) return "tools";
|
|
@@ -6803,15 +7034,15 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6803
7034
|
];
|
|
6804
7035
|
return valid.includes(effort) ? effort : void 0;
|
|
6805
7036
|
}
|
|
6806
|
-
getOpencodeAgent(
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
7037
|
+
getOpencodeAgent(options) {
|
|
7038
|
+
return resolveOpencodeAgent(
|
|
7039
|
+
options?.headers,
|
|
7040
|
+
options.providerOptions,
|
|
7041
|
+
this.config.provider
|
|
7042
|
+
);
|
|
6812
7043
|
}
|
|
6813
7044
|
isCompactionCall(options) {
|
|
6814
|
-
return this.getOpencodeAgent(options
|
|
7045
|
+
return this.getOpencodeAgent(options) === "compaction";
|
|
6815
7046
|
}
|
|
6816
7047
|
/**
|
|
6817
7048
|
* Pick the model used to handle /compact. Precedence:
|
|
@@ -6947,6 +7178,18 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6947
7178
|
};
|
|
6948
7179
|
}
|
|
6949
7180
|
async doGenerate(options) {
|
|
7181
|
+
const result = await this.doGenerateForHost(options);
|
|
7182
|
+
if (this.config.hostApi !== "v2") return result;
|
|
7183
|
+
const translate = createHostToolPartTranslator("v2");
|
|
7184
|
+
return {
|
|
7185
|
+
...result,
|
|
7186
|
+
content: result.content.flatMap((part) => {
|
|
7187
|
+
const next = translate(part);
|
|
7188
|
+
return next ? [next] : [];
|
|
7189
|
+
})
|
|
7190
|
+
};
|
|
7191
|
+
}
|
|
7192
|
+
async doGenerateForHost(options) {
|
|
6950
7193
|
if (!this.isCompactionCall(options) && this.requestScope(options) !== "no-tools" && parseSideQuestion(options.prompt)) {
|
|
6951
7194
|
return this.doGenerateViaStream(options);
|
|
6952
7195
|
}
|
|
@@ -6955,16 +7198,16 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6955
7198
|
const affinity = this.sessionAffinity(options);
|
|
6956
7199
|
const cwd = await resolveSpawnCwdForSession(this.config.cwd, affinity);
|
|
6957
7200
|
const effectiveModelId = resolveAgentModel(
|
|
6958
|
-
this.getOpencodeAgent(options
|
|
7201
|
+
this.getOpencodeAgent(options),
|
|
6959
7202
|
this.modelId
|
|
6960
7203
|
);
|
|
6961
7204
|
const reasoningEffort = resolveAgentEffort(
|
|
6962
|
-
this.getOpencodeAgent(options
|
|
7205
|
+
this.getOpencodeAgent(options),
|
|
6963
7206
|
this.getReasoningEffort(options.providerOptions)
|
|
6964
7207
|
);
|
|
6965
7208
|
const baseKey = sessionKey(
|
|
6966
7209
|
cwd,
|
|
6967
|
-
`${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options
|
|
7210
|
+
`${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options) ?? null])}`
|
|
6968
7211
|
);
|
|
6969
7212
|
const sk = effortSessionKey(baseKey, reasoningEffort);
|
|
6970
7213
|
const compactionMode = this.isCompactionCall(options);
|
|
@@ -6974,10 +7217,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6974
7217
|
if (compactionMode) {
|
|
6975
7218
|
return this.doGenerateViaStream(options);
|
|
6976
7219
|
}
|
|
6977
|
-
if (scope
|
|
7220
|
+
if (this.isTitleRequest(scope, options)) {
|
|
6978
7221
|
log.info("doGenerate no-tools title stub", {
|
|
6979
7222
|
compactionMode,
|
|
6980
|
-
opencodeAgent: this.getOpencodeAgent(options
|
|
7223
|
+
opencodeAgent: this.getOpencodeAgent(options),
|
|
6981
7224
|
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
6982
7225
|
});
|
|
6983
7226
|
const text = this.synthesizeTitle(options.prompt);
|
|
@@ -7377,7 +7620,19 @@ ${plan}
|
|
|
7377
7620
|
warnings
|
|
7378
7621
|
};
|
|
7379
7622
|
}
|
|
7623
|
+
/**
|
|
7624
|
+
* Tool parts leave in opencode 1.x's vocabulary; on opencode 2.x they are
|
|
7625
|
+
* renamed at this one edge (src/host-tools.ts). On V1 the stream is
|
|
7626
|
+
* returned untouched.
|
|
7627
|
+
*/
|
|
7380
7628
|
async doStream(options) {
|
|
7629
|
+
const result = await this.doStreamForHost(options);
|
|
7630
|
+
return {
|
|
7631
|
+
...result,
|
|
7632
|
+
stream: translateStreamForHost(result.stream, this.config.hostApi ?? "v1")
|
|
7633
|
+
};
|
|
7634
|
+
}
|
|
7635
|
+
async doStreamForHost(options) {
|
|
7381
7636
|
const warnings = [];
|
|
7382
7637
|
const skipPermissions = this.config.skipPermissions !== false;
|
|
7383
7638
|
const scope = this.requestScope(options);
|
|
@@ -7385,16 +7640,16 @@ ${plan}
|
|
|
7385
7640
|
const cwd = await resolveSpawnCwdForSession(this.config.cwd, affinity);
|
|
7386
7641
|
const compactionMode = this.isCompactionCall(options);
|
|
7387
7642
|
const effectiveModelId = compactionMode ? this.resolveCompactionModel() : resolveAgentModel(
|
|
7388
|
-
this.getOpencodeAgent(options
|
|
7643
|
+
this.getOpencodeAgent(options),
|
|
7389
7644
|
this.modelId
|
|
7390
7645
|
);
|
|
7391
7646
|
const reasoningEffort = compactionMode ? void 0 : resolveAgentEffort(
|
|
7392
|
-
this.getOpencodeAgent(options
|
|
7647
|
+
this.getOpencodeAgent(options),
|
|
7393
7648
|
this.getReasoningEffort(options.providerOptions)
|
|
7394
7649
|
);
|
|
7395
7650
|
const baseKey = sessionKey(
|
|
7396
7651
|
cwd,
|
|
7397
|
-
`${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options
|
|
7652
|
+
`${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options) ?? null])}`
|
|
7398
7653
|
);
|
|
7399
7654
|
const sk = compactionMode ? sessionKey(cwd, `${effectiveModelId}::compaction::${affinity}`) : effortSessionKey(baseKey, reasoningEffort);
|
|
7400
7655
|
const toUsage = this.toUsage.bind(this);
|
|
@@ -7495,10 +7750,10 @@ ${plan}
|
|
|
7495
7750
|
});
|
|
7496
7751
|
return { stream: stream2, request: { body: { text: aside.question } } };
|
|
7497
7752
|
}
|
|
7498
|
-
if (scope
|
|
7753
|
+
if (this.isTitleRequest(scope, options) && !compactionMode) {
|
|
7499
7754
|
log.info("doStream no-tools title stub", {
|
|
7500
7755
|
compactionMode,
|
|
7501
|
-
opencodeAgent: this.getOpencodeAgent(options
|
|
7756
|
+
opencodeAgent: this.getOpencodeAgent(options),
|
|
7502
7757
|
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
7503
7758
|
});
|
|
7504
7759
|
const text = this.synthesizeTitle(options.prompt);
|
|
@@ -7688,7 +7943,7 @@ ${plan}
|
|
|
7688
7943
|
proxyTools: resolvedProxy?.map((t) => t.name) ?? null,
|
|
7689
7944
|
compactionMode,
|
|
7690
7945
|
scope,
|
|
7691
|
-
opencodeAgent: this.getOpencodeAgent(options
|
|
7946
|
+
opencodeAgent: this.getOpencodeAgent(options),
|
|
7692
7947
|
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
7693
7948
|
});
|
|
7694
7949
|
const stream = new ReadableStream({
|
|
@@ -8350,7 +8605,7 @@ ${plan}
|
|
|
8350
8605
|
});
|
|
8351
8606
|
}
|
|
8352
8607
|
activeProcess?.pendingProxyCompletions?.clear();
|
|
8353
|
-
if (accountLimitHit && failoverAskActive) {
|
|
8608
|
+
if (accountLimitHit && failoverAskActive && msg.is_error === true) {
|
|
8354
8609
|
const call = createAccountFailoverQuestionCall(sk, {
|
|
8355
8610
|
sourceAccount,
|
|
8356
8611
|
candidates: failoverAccounts,
|
|
@@ -9456,7 +9711,7 @@ ${plan}
|
|
|
9456
9711
|
// src/cleanup-stale.ts
|
|
9457
9712
|
import {
|
|
9458
9713
|
existsSync as existsSync4,
|
|
9459
|
-
readFileSync as
|
|
9714
|
+
readFileSync as readFileSync6,
|
|
9460
9715
|
realpathSync,
|
|
9461
9716
|
rmSync as rmSync3,
|
|
9462
9717
|
writeFileSync as writeFileSync5
|
|
@@ -9483,7 +9738,7 @@ function userIntendsToUseUnscoped() {
|
|
|
9483
9738
|
const cfg = userOpencodeJsonPath();
|
|
9484
9739
|
if (!existsSync4(cfg)) return false;
|
|
9485
9740
|
try {
|
|
9486
|
-
const json = JSON.parse(
|
|
9741
|
+
const json = JSON.parse(readFileSync6(cfg, "utf8"));
|
|
9487
9742
|
const plugins = json.plugin;
|
|
9488
9743
|
if (!Array.isArray(plugins)) return false;
|
|
9489
9744
|
return plugins.some(
|
|
@@ -9532,7 +9787,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
9532
9787
|
if (!existsSync4(pkgJsonPath)) return;
|
|
9533
9788
|
let pkg = {};
|
|
9534
9789
|
try {
|
|
9535
|
-
pkg = JSON.parse(
|
|
9790
|
+
pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
|
|
9536
9791
|
} catch {
|
|
9537
9792
|
return;
|
|
9538
9793
|
}
|
|
@@ -9551,7 +9806,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
9551
9806
|
const cachePkgJson = join9(cacheRoot, "package.json");
|
|
9552
9807
|
if (!existsSync4(cachePkgJson)) return;
|
|
9553
9808
|
try {
|
|
9554
|
-
const cfg = JSON.parse(
|
|
9809
|
+
const cfg = JSON.parse(readFileSync6(cachePkgJson, "utf8"));
|
|
9555
9810
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
9556
9811
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
9557
9812
|
writeFileSync5(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
@@ -9564,6 +9819,414 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
9564
9819
|
}
|
|
9565
9820
|
}
|
|
9566
9821
|
|
|
9822
|
+
// src/v2-client.ts
|
|
9823
|
+
import path9 from "path";
|
|
9824
|
+
function formatAgentTypeList(agents) {
|
|
9825
|
+
const lines = agents.flatMap((agent) => {
|
|
9826
|
+
if (agent.hidden === true || agent.mode === "primary") return [];
|
|
9827
|
+
const name = typeof agent.id === "string" ? agent.id : agent.name;
|
|
9828
|
+
if (typeof name !== "string" || name.length === 0) return [];
|
|
9829
|
+
const blurb = typeof agent.description === "string" ? agent.description.trim() : "";
|
|
9830
|
+
return [`- ${name}: ${blurb || "(no description)"}`];
|
|
9831
|
+
});
|
|
9832
|
+
if (lines.length === 0) return void 0;
|
|
9833
|
+
return `Available agent types and the tools they have access to:
|
|
9834
|
+
${lines.join("\n")}`;
|
|
9835
|
+
}
|
|
9836
|
+
function describe(value) {
|
|
9837
|
+
if (typeof value === "string") return value;
|
|
9838
|
+
if (typeof value === "function") {
|
|
9839
|
+
try {
|
|
9840
|
+
const result = value();
|
|
9841
|
+
return typeof result === "string" ? result : "";
|
|
9842
|
+
} catch {
|
|
9843
|
+
return "";
|
|
9844
|
+
}
|
|
9845
|
+
}
|
|
9846
|
+
return "";
|
|
9847
|
+
}
|
|
9848
|
+
function toV1Session(info) {
|
|
9849
|
+
if (!info || typeof info !== "object") return void 0;
|
|
9850
|
+
const session = info;
|
|
9851
|
+
const root = session.location?.directory;
|
|
9852
|
+
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path9.resolve(root, session.subpath) : root : void 0;
|
|
9853
|
+
return { ...info, directory, parentID: session.parentID };
|
|
9854
|
+
}
|
|
9855
|
+
function createV1ClientShim(ctx) {
|
|
9856
|
+
const client = {};
|
|
9857
|
+
const mcpList = ctx.mcp?.list;
|
|
9858
|
+
if (typeof mcpList === "function") {
|
|
9859
|
+
client.mcp = {
|
|
9860
|
+
// V1: `{ data: { [server]: { status } } }`. V2: `{ data: [{ name, status: { status } }] }`.
|
|
9861
|
+
status: async () => {
|
|
9862
|
+
const result = await mcpList.call(ctx.mcp);
|
|
9863
|
+
const data = {};
|
|
9864
|
+
for (const server2 of result?.data ?? []) {
|
|
9865
|
+
const status = server2?.status?.status;
|
|
9866
|
+
if (typeof server2?.name === "string" && typeof status === "string") {
|
|
9867
|
+
data[server2.name] = { status };
|
|
9868
|
+
}
|
|
9869
|
+
}
|
|
9870
|
+
return { data };
|
|
9871
|
+
}
|
|
9872
|
+
};
|
|
9873
|
+
}
|
|
9874
|
+
const toolList = ctx.tool?.list;
|
|
9875
|
+
if (typeof toolList === "function") {
|
|
9876
|
+
client.tool = {
|
|
9877
|
+
// V1: `{ data: [{ id, description, parameters }] }`. V2 returns the tools
|
|
9878
|
+
// themselves, with an Effect schema for input rather than JSON Schema, so
|
|
9879
|
+
// `parameters` is empty; only `proxyOpencodeTools` reads it.
|
|
9880
|
+
list: async () => {
|
|
9881
|
+
const tools = await toolList.call(ctx.tool);
|
|
9882
|
+
const data = (tools ?? []).flatMap((tool) => {
|
|
9883
|
+
const id = typeof tool?.id === "string" ? tool.id : tool?.name;
|
|
9884
|
+
return typeof id === "string" ? [{ id, description: describe(tool.description), parameters: {} }] : [];
|
|
9885
|
+
});
|
|
9886
|
+
const subagent = data.find((tool) => tool.id === "subagent");
|
|
9887
|
+
if (subagent && !data.some((tool) => tool.id === "task")) {
|
|
9888
|
+
let agentList;
|
|
9889
|
+
const agentApi = ctx.agent?.list;
|
|
9890
|
+
if (typeof agentApi === "function") {
|
|
9891
|
+
try {
|
|
9892
|
+
agentList = formatAgentTypeList((await agentApi.call(ctx.agent))?.data ?? []);
|
|
9893
|
+
} catch {
|
|
9894
|
+
agentList = void 0;
|
|
9895
|
+
}
|
|
9896
|
+
}
|
|
9897
|
+
const description = agentList ? `${subagent.description}
|
|
9898
|
+
|
|
9899
|
+
${agentList}` : subagent.description;
|
|
9900
|
+
data.push({ ...subagent, id: "task", description });
|
|
9901
|
+
}
|
|
9902
|
+
return { data };
|
|
9903
|
+
}
|
|
9904
|
+
};
|
|
9905
|
+
}
|
|
9906
|
+
const sessionGet = ctx.session?.get;
|
|
9907
|
+
if (typeof sessionGet === "function") {
|
|
9908
|
+
client.session = {
|
|
9909
|
+
get: async (options) => ({
|
|
9910
|
+
data: toV1Session(await sessionGet.call(ctx.session, { sessionID: options.path.id }))
|
|
9911
|
+
})
|
|
9912
|
+
};
|
|
9913
|
+
}
|
|
9914
|
+
return client;
|
|
9915
|
+
}
|
|
9916
|
+
|
|
9917
|
+
// src/v2.ts
|
|
9918
|
+
var V2_PLUGIN_PACKAGE = "aisdk:@khalilgharbaoui/opencode-claude-code-plugin";
|
|
9919
|
+
var SESSION_AFFINITY_HEADER = "x-session-affinity";
|
|
9920
|
+
var OPENCODE_AGENT_HEADER = "x-opencode-agent";
|
|
9921
|
+
var PLUGIN_ONLY_SETTINGS = ["accounts", "defaultSubagentModel"];
|
|
9922
|
+
var OPENCODE_SDK_OPTION_KEYS = ["fetch", "headers", "body", "name"];
|
|
9923
|
+
function isClaudeCodeProviderId(providerID) {
|
|
9924
|
+
if (typeof providerID !== "string") return false;
|
|
9925
|
+
return providerID === BASE_PROVIDER_ID || providerID.startsWith(`${BASE_PROVIDER_ID}-`);
|
|
9926
|
+
}
|
|
9927
|
+
function agentForRequest(kind, agent) {
|
|
9928
|
+
if (kind === "compaction") return "compaction";
|
|
9929
|
+
if (kind === "title") return "title";
|
|
9930
|
+
return agent;
|
|
9931
|
+
}
|
|
9932
|
+
function releasedAt(date) {
|
|
9933
|
+
const ms = date ? Date.parse(date) : Number.NaN;
|
|
9934
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
9935
|
+
}
|
|
9936
|
+
function modalities(flags) {
|
|
9937
|
+
return Object.entries(flags).filter(([, enabled]) => enabled === true).map(([name]) => name);
|
|
9938
|
+
}
|
|
9939
|
+
function toV2Model(model, providerID, modelId) {
|
|
9940
|
+
return {
|
|
9941
|
+
id: modelId,
|
|
9942
|
+
modelID: modelId,
|
|
9943
|
+
providerID,
|
|
9944
|
+
name: model.name,
|
|
9945
|
+
family: model.family,
|
|
9946
|
+
capabilities: {
|
|
9947
|
+
tools: model.capabilities.toolcall,
|
|
9948
|
+
input: modalities(model.capabilities.input),
|
|
9949
|
+
output: modalities(model.capabilities.output)
|
|
9950
|
+
},
|
|
9951
|
+
variants: Object.entries(model.variants ?? {}).map(([id, settings]) => ({
|
|
9952
|
+
id,
|
|
9953
|
+
settings: { ...settings }
|
|
9954
|
+
})),
|
|
9955
|
+
time: { released: releasedAt(model.release_date) },
|
|
9956
|
+
cost: [
|
|
9957
|
+
{
|
|
9958
|
+
input: model.cost.input,
|
|
9959
|
+
output: model.cost.output,
|
|
9960
|
+
cache: { read: model.cost.cache.read, write: model.cost.cache.write }
|
|
9961
|
+
}
|
|
9962
|
+
],
|
|
9963
|
+
status: model.status ?? "active",
|
|
9964
|
+
enabled: true,
|
|
9965
|
+
limit: { context: model.limit.context, output: model.limit.output },
|
|
9966
|
+
package: V2_PLUGIN_PACKAGE
|
|
9967
|
+
};
|
|
9968
|
+
}
|
|
9969
|
+
function v2ModelsForProvider(providerID, modelSuffix) {
|
|
9970
|
+
return Object.entries(defaultModels).map(
|
|
9971
|
+
([id, model]) => toV2Model(model, providerID, modelSuffix ? `${id}@${modelSuffix}` : id)
|
|
9972
|
+
);
|
|
9973
|
+
}
|
|
9974
|
+
function stripPluginOnly(settings) {
|
|
9975
|
+
const result = { ...settings ?? {} };
|
|
9976
|
+
for (const key of PLUGIN_ONLY_SETTINGS) delete result[key];
|
|
9977
|
+
return result;
|
|
9978
|
+
}
|
|
9979
|
+
function planV2Providers(seedSettings, defaultProxyTools) {
|
|
9980
|
+
const base = {
|
|
9981
|
+
cliPath: "claude",
|
|
9982
|
+
proxyTools: [...defaultProxyTools],
|
|
9983
|
+
...stripPluginOnly(seedSettings)
|
|
9984
|
+
};
|
|
9985
|
+
const accounts = resolveAccounts(seedSettings?.accounts);
|
|
9986
|
+
if (!accounts) {
|
|
9987
|
+
return [
|
|
9988
|
+
{
|
|
9989
|
+
info: {
|
|
9990
|
+
id: BASE_PROVIDER_ID,
|
|
9991
|
+
name: "Claude Code",
|
|
9992
|
+
activation: "enabled",
|
|
9993
|
+
package: V2_PLUGIN_PACKAGE,
|
|
9994
|
+
settings: { ...base, providerID: BASE_PROVIDER_ID }
|
|
9995
|
+
},
|
|
9996
|
+
models: v2ModelsForProvider(BASE_PROVIDER_ID)
|
|
9997
|
+
}
|
|
9998
|
+
];
|
|
9999
|
+
}
|
|
10000
|
+
return accounts.map((account) => {
|
|
10001
|
+
const providerID = accountProviderId(account);
|
|
10002
|
+
return {
|
|
10003
|
+
info: {
|
|
10004
|
+
id: providerID,
|
|
10005
|
+
name: accountDisplayName(account),
|
|
10006
|
+
activation: "enabled",
|
|
10007
|
+
package: V2_PLUGIN_PACKAGE,
|
|
10008
|
+
settings: {
|
|
10009
|
+
...base,
|
|
10010
|
+
account,
|
|
10011
|
+
// The resolved list, so this account's language model can offer the
|
|
10012
|
+
// others when it runs out of usage (src/account-failover.ts).
|
|
10013
|
+
failoverAccounts: accounts,
|
|
10014
|
+
providerID,
|
|
10015
|
+
// Kept separately because the wrapper replaces `cliPath`, and a
|
|
10016
|
+
// failover builds another account's wrapper on the same base.
|
|
10017
|
+
baseCliPath: String(base.cliPath ?? "claude")
|
|
10018
|
+
}
|
|
10019
|
+
},
|
|
10020
|
+
models: v2ModelsForProvider(providerID, accountModelSuffix(account))
|
|
10021
|
+
};
|
|
10022
|
+
});
|
|
10023
|
+
}
|
|
10024
|
+
async function resolveSdkSettings(providerID, planned, eventOptions) {
|
|
10025
|
+
const fromEvent = { ...eventOptions };
|
|
10026
|
+
for (const key of OPENCODE_SDK_OPTION_KEYS) delete fromEvent[key];
|
|
10027
|
+
const merged = {
|
|
10028
|
+
...stripPluginOnly({ ...planned ?? {}, ...fromEvent }),
|
|
10029
|
+
providerID,
|
|
10030
|
+
// The only place a model is told it serves opencode 2.
|
|
10031
|
+
hostApi: "v2"
|
|
10032
|
+
};
|
|
10033
|
+
const account = typeof merged.account === "string" ? merged.account : void 0;
|
|
10034
|
+
if (account) {
|
|
10035
|
+
const base = String(merged.baseCliPath ?? merged.cliPath ?? "claude");
|
|
10036
|
+
const runtime = await ensureAccountRuntime(account, base);
|
|
10037
|
+
Object.assign(merged, runtime, { baseCliPath: base });
|
|
10038
|
+
}
|
|
10039
|
+
return merged;
|
|
10040
|
+
}
|
|
10041
|
+
function plainObject(value) {
|
|
10042
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
10043
|
+
}
|
|
10044
|
+
function configuredSeedSettings(config, pluginOptions) {
|
|
10045
|
+
const provider = plainObject(plainObject(config.provider)?.[BASE_PROVIDER_ID]);
|
|
10046
|
+
return {
|
|
10047
|
+
...plainObject(provider?.options) ?? {},
|
|
10048
|
+
...plainObject(provider?.settings) ?? {},
|
|
10049
|
+
...pluginOptions ?? {}
|
|
10050
|
+
};
|
|
10051
|
+
}
|
|
10052
|
+
function isOpencodeV2Context(ctx) {
|
|
10053
|
+
const context = plainObject(ctx);
|
|
10054
|
+
if (!context) return false;
|
|
10055
|
+
const version = plainObject(context.app)?.version;
|
|
10056
|
+
if (typeof version === "string") {
|
|
10057
|
+
const major = Number.parseInt(version.replace(/^v/, ""), 10);
|
|
10058
|
+
if (Number.isFinite(major) && major < 2) return false;
|
|
10059
|
+
}
|
|
10060
|
+
const hasHook = (domain, name) => typeof plainObject(domain)?.[name] === "function";
|
|
10061
|
+
return hasHook(context.provider, "transform") && hasHook(context.aisdk, "hook") && hasHook(context.session, "hook");
|
|
10062
|
+
}
|
|
10063
|
+
function commandPromptText(command, args) {
|
|
10064
|
+
const rest = (args ?? "").trim();
|
|
10065
|
+
return rest ? `/${command} ${rest}` : `/${command}`;
|
|
10066
|
+
}
|
|
10067
|
+
function deletedSessionIdV2(event) {
|
|
10068
|
+
if (!event || event.type !== "session.deleted") return void 0;
|
|
10069
|
+
const id = event.data?.sessionID;
|
|
10070
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
10071
|
+
}
|
|
10072
|
+
async function releaseDeletedSessions(subscribe, signal) {
|
|
10073
|
+
try {
|
|
10074
|
+
for await (const event of subscribe({ signal })) {
|
|
10075
|
+
if (signal.aborted) break;
|
|
10076
|
+
const sessionID = deletedSessionIdV2(event);
|
|
10077
|
+
if (!sessionID) continue;
|
|
10078
|
+
const released = deleteActiveProcessesForSession(sessionID);
|
|
10079
|
+
if (released.length > 0) {
|
|
10080
|
+
log.info("released claude state for deleted session", { sessionID, released });
|
|
10081
|
+
}
|
|
10082
|
+
}
|
|
10083
|
+
} catch (err) {
|
|
10084
|
+
if (!signal.aborted) {
|
|
10085
|
+
log.warn("opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction", {
|
|
10086
|
+
error: err instanceof Error ? err.message : String(err)
|
|
10087
|
+
});
|
|
10088
|
+
}
|
|
10089
|
+
}
|
|
10090
|
+
}
|
|
10091
|
+
function createV2Setup(deps) {
|
|
10092
|
+
return async (ctx) => {
|
|
10093
|
+
if (!isOpencodeV2Context(ctx)) {
|
|
10094
|
+
log.debug("setup called outside opencode 2; leaving it to server()", {
|
|
10095
|
+
version: ctx?.app?.version
|
|
10096
|
+
});
|
|
10097
|
+
return () => void 0;
|
|
10098
|
+
}
|
|
10099
|
+
ensureProcessExitCleanup();
|
|
10100
|
+
setOpencodeClient(createV1ClientShim(ctx));
|
|
10101
|
+
const directory = ctx.location?.directory;
|
|
10102
|
+
setOpencodeProjectDirectory(isUsableDirectory(directory) ? directory : void 0);
|
|
10103
|
+
if (deps.buildAgentRegistry) {
|
|
10104
|
+
try {
|
|
10105
|
+
const config = deps.loadConfig(directory ?? process.cwd());
|
|
10106
|
+
await deps.buildAgentRegistry({
|
|
10107
|
+
...config,
|
|
10108
|
+
provider: {
|
|
10109
|
+
...plainObject(config.provider) ?? {},
|
|
10110
|
+
[BASE_PROVIDER_ID]: {
|
|
10111
|
+
options: configuredSeedSettings(config, plainObject(ctx.options))
|
|
10112
|
+
}
|
|
10113
|
+
}
|
|
10114
|
+
});
|
|
10115
|
+
} catch (err) {
|
|
10116
|
+
log.warn("failed to build the agent registry for opencode 2", {
|
|
10117
|
+
error: err instanceof Error ? err.message : String(err)
|
|
10118
|
+
});
|
|
10119
|
+
}
|
|
10120
|
+
}
|
|
10121
|
+
const planned = /* @__PURE__ */ new Map();
|
|
10122
|
+
const registrations = [];
|
|
10123
|
+
registrations.push(
|
|
10124
|
+
await ctx.provider.transform((editor) => {
|
|
10125
|
+
const seed = editor.get(BASE_PROVIDER_ID);
|
|
10126
|
+
const seedSettings = {
|
|
10127
|
+
...seed?.provider.settings ?? {},
|
|
10128
|
+
...configuredSeedSettings(
|
|
10129
|
+
deps.loadConfig(directory ?? process.cwd()),
|
|
10130
|
+
plainObject(ctx.options)
|
|
10131
|
+
)
|
|
10132
|
+
};
|
|
10133
|
+
const plans = planV2Providers(seedSettings, deps.defaultProxyTools);
|
|
10134
|
+
planned.clear();
|
|
10135
|
+
for (const plan of plans) {
|
|
10136
|
+
planned.set(plan.info.id, plan.info.settings ?? {});
|
|
10137
|
+
if (editor.get(plan.info.id)) {
|
|
10138
|
+
editor.update(plan.info.id, (provider) => {
|
|
10139
|
+
provider.name = plan.info.name;
|
|
10140
|
+
provider.activation = plan.info.activation;
|
|
10141
|
+
provider.package = plan.info.package;
|
|
10142
|
+
provider.settings = plan.info.settings;
|
|
10143
|
+
});
|
|
10144
|
+
editor.models.set(plan.info.id, plan.models);
|
|
10145
|
+
} else {
|
|
10146
|
+
editor.add({ info: plan.info, models: plan.models });
|
|
10147
|
+
}
|
|
10148
|
+
}
|
|
10149
|
+
if (seed && !plans.some((plan) => plan.info.id === BASE_PROVIDER_ID)) {
|
|
10150
|
+
editor.remove(BASE_PROVIDER_ID);
|
|
10151
|
+
}
|
|
10152
|
+
logStartupDiagnostics(
|
|
10153
|
+
Object.fromEntries(
|
|
10154
|
+
plans.map((plan) => [plan.info.id, { name: plan.info.name, options: plan.info.settings }])
|
|
10155
|
+
),
|
|
10156
|
+
ctx.app?.version
|
|
10157
|
+
);
|
|
10158
|
+
})
|
|
10159
|
+
);
|
|
10160
|
+
registrations.push(
|
|
10161
|
+
await ctx.aisdk.hook("sdk", async (event) => {
|
|
10162
|
+
const providerID = event.model.providerID;
|
|
10163
|
+
if (!isClaudeCodeProviderId(providerID)) return;
|
|
10164
|
+
const settings = await resolveSdkSettings(providerID, planned.get(providerID), event.options);
|
|
10165
|
+
event.sdk = deps.createProvider(settings);
|
|
10166
|
+
log.debug("v2 sdk created", { providerID, cliPath: settings.cliPath });
|
|
10167
|
+
})
|
|
10168
|
+
);
|
|
10169
|
+
registrations.push(
|
|
10170
|
+
await ctx.session.hook("model.request", (event) => {
|
|
10171
|
+
if (!isClaudeCodeProviderId(event.model.providerID)) return;
|
|
10172
|
+
event.headers[SESSION_AFFINITY_HEADER] = event.sessionID;
|
|
10173
|
+
event.headers[OPENCODE_AGENT_HEADER] = agentForRequest(event.kind, event.agent);
|
|
10174
|
+
})
|
|
10175
|
+
);
|
|
10176
|
+
const prompt = ctx.session.prompt;
|
|
10177
|
+
if (ctx.command?.transform && typeof prompt === "function") {
|
|
10178
|
+
const send = (input, command, delivery) => prompt.call(ctx.session, {
|
|
10179
|
+
...input.prompt,
|
|
10180
|
+
sessionID: input.sessionID,
|
|
10181
|
+
text: commandPromptText(command, input.prompt.text),
|
|
10182
|
+
delivery
|
|
10183
|
+
}).then(() => void 0);
|
|
10184
|
+
registrations.push(
|
|
10185
|
+
await ctx.command.transform((editor) => {
|
|
10186
|
+
editor.add({
|
|
10187
|
+
name: DOCTOR_COMMAND,
|
|
10188
|
+
description: DOCTOR_COMMAND_DESCRIPTION,
|
|
10189
|
+
execute: (input) => send(input, DOCTOR_COMMAND, input.delivery)
|
|
10190
|
+
});
|
|
10191
|
+
editor.add({
|
|
10192
|
+
name: "btw",
|
|
10193
|
+
description: BTW_COMMAND_DESCRIPTION,
|
|
10194
|
+
execute: (input) => send(input, "btw", "queue")
|
|
10195
|
+
});
|
|
10196
|
+
})
|
|
10197
|
+
);
|
|
10198
|
+
}
|
|
10199
|
+
const bundledSkills = readBundledSkillInfos();
|
|
10200
|
+
if (typeof ctx.skill?.transform === "function" && bundledSkills.length > 0) {
|
|
10201
|
+
registrations.push(
|
|
10202
|
+
await ctx.skill.transform((editor) => {
|
|
10203
|
+
const added = [];
|
|
10204
|
+
for (const skill of bundledSkills) {
|
|
10205
|
+
if (editor.get(skill.id)) continue;
|
|
10206
|
+
editor.add(skill);
|
|
10207
|
+
added.push(skill.id);
|
|
10208
|
+
}
|
|
10209
|
+
log.debug("v2 bundled skills registered", { added });
|
|
10210
|
+
})
|
|
10211
|
+
);
|
|
10212
|
+
}
|
|
10213
|
+
const events = new AbortController();
|
|
10214
|
+
if (typeof ctx.event?.subscribe === "function") {
|
|
10215
|
+
void releaseDeletedSessions(ctx.event.subscribe.bind(ctx.event), events.signal);
|
|
10216
|
+
}
|
|
10217
|
+
log.info("claude-code plugin set up for opencode 2", {
|
|
10218
|
+
opencode: ctx.app?.version,
|
|
10219
|
+
directory
|
|
10220
|
+
});
|
|
10221
|
+
return async () => {
|
|
10222
|
+
events.abort();
|
|
10223
|
+
for (const registration of registrations.splice(0)) {
|
|
10224
|
+
await registration.dispose().catch(() => void 0);
|
|
10225
|
+
}
|
|
10226
|
+
};
|
|
10227
|
+
};
|
|
10228
|
+
}
|
|
10229
|
+
|
|
9567
10230
|
// src/index.ts
|
|
9568
10231
|
function pickOpencodeDirectory(input) {
|
|
9569
10232
|
if (!input || typeof input !== "object") return void 0;
|
|
@@ -9586,7 +10249,7 @@ function registerSideQuestionCommand(config) {
|
|
|
9586
10249
|
if (config.command.btw) return false;
|
|
9587
10250
|
config.command.btw = {
|
|
9588
10251
|
template: "/btw $ARGUMENTS",
|
|
9589
|
-
description:
|
|
10252
|
+
description: BTW_COMMAND_DESCRIPTION
|
|
9590
10253
|
};
|
|
9591
10254
|
return true;
|
|
9592
10255
|
}
|
|
@@ -9595,7 +10258,7 @@ function registerDoctorCommand(config) {
|
|
|
9595
10258
|
if (config.command[DOCTOR_COMMAND]) return false;
|
|
9596
10259
|
config.command[DOCTOR_COMMAND] = {
|
|
9597
10260
|
template: `/${DOCTOR_COMMAND} $ARGUMENTS`,
|
|
9598
|
-
description:
|
|
10261
|
+
description: DOCTOR_COMMAND_DESCRIPTION
|
|
9599
10262
|
};
|
|
9600
10263
|
return true;
|
|
9601
10264
|
}
|
|
@@ -9644,6 +10307,7 @@ function createClaudeCode(settings = {}) {
|
|
|
9644
10307
|
return new ClaudeCodeLanguageModel(modelId, {
|
|
9645
10308
|
provider: providerName,
|
|
9646
10309
|
cliPath,
|
|
10310
|
+
hostApi: settings.hostApi,
|
|
9647
10311
|
cwd: settings.cwd,
|
|
9648
10312
|
account: settings.account,
|
|
9649
10313
|
configDir: settings.configDir,
|
|
@@ -9973,13 +10637,20 @@ var server = async (input) => {
|
|
|
9973
10637
|
};
|
|
9974
10638
|
var index_default = {
|
|
9975
10639
|
id: "@khalilgharbaoui/opencode-claude-code-plugin",
|
|
9976
|
-
server
|
|
10640
|
+
server,
|
|
10641
|
+
setup: createV2Setup({
|
|
10642
|
+
createProvider: createClaudeCode,
|
|
10643
|
+
defaultProxyTools: DEFAULT_PROXY_TOOL_NAMES,
|
|
10644
|
+
loadConfig: loadMergedOpencodeConfig,
|
|
10645
|
+
buildAgentRegistry: (config) => buildAgentRegistry(config)
|
|
10646
|
+
})
|
|
9977
10647
|
};
|
|
9978
10648
|
export {
|
|
9979
10649
|
ClaudeCodeLanguageModel,
|
|
9980
10650
|
DEFAULT_PROXY_TOOL_NAMES,
|
|
9981
10651
|
_resetPlanModeWarningForTests,
|
|
9982
10652
|
bridgeOpencodeMcp,
|
|
10653
|
+
buildAgentRegistry,
|
|
9983
10654
|
claudeCodeProviders,
|
|
9984
10655
|
configModelsForProvider,
|
|
9985
10656
|
createClaudeCode,
|