@khalilgharbaoui/opencode-claude-code-plugin 0.25.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 +26 -0
- package/dist/index.d.ts +226 -1
- package/dist/index.js +679 -39
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +14 -0
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);
|
|
@@ -5340,6 +5484,33 @@ function registerBundledSkillPath(config) {
|
|
|
5340
5484
|
paths.push(dir);
|
|
5341
5485
|
return true;
|
|
5342
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
|
+
}
|
|
5343
5514
|
function linkSkill(source, target) {
|
|
5344
5515
|
try {
|
|
5345
5516
|
fs5.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
|
|
@@ -5989,7 +6160,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
5989
6160
|
}
|
|
5990
6161
|
|
|
5991
6162
|
// src/claude-code-language-model.ts
|
|
5992
|
-
import { readFileSync as
|
|
6163
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5993
6164
|
import { unlink as unlink3 } from "fs/promises";
|
|
5994
6165
|
import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
|
|
5995
6166
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -6018,6 +6189,22 @@ function resolveSessionAffinity(headers, providerOptions, providerKey) {
|
|
|
6018
6189
|
}
|
|
6019
6190
|
return "default";
|
|
6020
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
|
+
}
|
|
6021
6208
|
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
6022
6209
|
"thinking_delta",
|
|
6023
6210
|
"text_delta",
|
|
@@ -6217,9 +6404,9 @@ ${body}`;
|
|
|
6217
6404
|
}
|
|
6218
6405
|
});
|
|
6219
6406
|
}
|
|
6220
|
-
function readPromptFileIfPresent(
|
|
6407
|
+
function readPromptFileIfPresent(path10) {
|
|
6221
6408
|
try {
|
|
6222
|
-
const content =
|
|
6409
|
+
const content = readFileSync5(path10, "utf8").trim();
|
|
6223
6410
|
return content || void 0;
|
|
6224
6411
|
} catch {
|
|
6225
6412
|
return void 0;
|
|
@@ -6334,10 +6521,10 @@ ${options.compressionSummary.trim()}`
|
|
|
6334
6521
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
6335
6522
|
const content = parts.join("\n\n");
|
|
6336
6523
|
if (!content) return void 0;
|
|
6337
|
-
const
|
|
6524
|
+
const path10 = join8(tmpdir2(), `opencode-cc-sys-${randomUUID5()}.md`);
|
|
6338
6525
|
try {
|
|
6339
|
-
writeFileSync4(
|
|
6340
|
-
return
|
|
6526
|
+
writeFileSync4(path10, content, "utf8");
|
|
6527
|
+
return path10;
|
|
6341
6528
|
} catch (err) {
|
|
6342
6529
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
6343
6530
|
return void 0;
|
|
@@ -6423,6 +6610,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6423
6610
|
raw: reason
|
|
6424
6611
|
};
|
|
6425
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
|
+
}
|
|
6426
6626
|
requestScope(options) {
|
|
6427
6627
|
const tools = options?.tools;
|
|
6428
6628
|
if (Array.isArray(tools)) return "tools";
|
|
@@ -6834,15 +7034,15 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6834
7034
|
];
|
|
6835
7035
|
return valid.includes(effort) ? effort : void 0;
|
|
6836
7036
|
}
|
|
6837
|
-
getOpencodeAgent(
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
7037
|
+
getOpencodeAgent(options) {
|
|
7038
|
+
return resolveOpencodeAgent(
|
|
7039
|
+
options?.headers,
|
|
7040
|
+
options.providerOptions,
|
|
7041
|
+
this.config.provider
|
|
7042
|
+
);
|
|
6843
7043
|
}
|
|
6844
7044
|
isCompactionCall(options) {
|
|
6845
|
-
return this.getOpencodeAgent(options
|
|
7045
|
+
return this.getOpencodeAgent(options) === "compaction";
|
|
6846
7046
|
}
|
|
6847
7047
|
/**
|
|
6848
7048
|
* Pick the model used to handle /compact. Precedence:
|
|
@@ -6978,6 +7178,18 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6978
7178
|
};
|
|
6979
7179
|
}
|
|
6980
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) {
|
|
6981
7193
|
if (!this.isCompactionCall(options) && this.requestScope(options) !== "no-tools" && parseSideQuestion(options.prompt)) {
|
|
6982
7194
|
return this.doGenerateViaStream(options);
|
|
6983
7195
|
}
|
|
@@ -6986,16 +7198,16 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6986
7198
|
const affinity = this.sessionAffinity(options);
|
|
6987
7199
|
const cwd = await resolveSpawnCwdForSession(this.config.cwd, affinity);
|
|
6988
7200
|
const effectiveModelId = resolveAgentModel(
|
|
6989
|
-
this.getOpencodeAgent(options
|
|
7201
|
+
this.getOpencodeAgent(options),
|
|
6990
7202
|
this.modelId
|
|
6991
7203
|
);
|
|
6992
7204
|
const reasoningEffort = resolveAgentEffort(
|
|
6993
|
-
this.getOpencodeAgent(options
|
|
7205
|
+
this.getOpencodeAgent(options),
|
|
6994
7206
|
this.getReasoningEffort(options.providerOptions)
|
|
6995
7207
|
);
|
|
6996
7208
|
const baseKey = sessionKey(
|
|
6997
7209
|
cwd,
|
|
6998
|
-
`${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])}`
|
|
6999
7211
|
);
|
|
7000
7212
|
const sk = effortSessionKey(baseKey, reasoningEffort);
|
|
7001
7213
|
const compactionMode = this.isCompactionCall(options);
|
|
@@ -7005,10 +7217,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
7005
7217
|
if (compactionMode) {
|
|
7006
7218
|
return this.doGenerateViaStream(options);
|
|
7007
7219
|
}
|
|
7008
|
-
if (scope
|
|
7220
|
+
if (this.isTitleRequest(scope, options)) {
|
|
7009
7221
|
log.info("doGenerate no-tools title stub", {
|
|
7010
7222
|
compactionMode,
|
|
7011
|
-
opencodeAgent: this.getOpencodeAgent(options
|
|
7223
|
+
opencodeAgent: this.getOpencodeAgent(options),
|
|
7012
7224
|
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
7013
7225
|
});
|
|
7014
7226
|
const text = this.synthesizeTitle(options.prompt);
|
|
@@ -7408,7 +7620,19 @@ ${plan}
|
|
|
7408
7620
|
warnings
|
|
7409
7621
|
};
|
|
7410
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
|
+
*/
|
|
7411
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) {
|
|
7412
7636
|
const warnings = [];
|
|
7413
7637
|
const skipPermissions = this.config.skipPermissions !== false;
|
|
7414
7638
|
const scope = this.requestScope(options);
|
|
@@ -7416,16 +7640,16 @@ ${plan}
|
|
|
7416
7640
|
const cwd = await resolveSpawnCwdForSession(this.config.cwd, affinity);
|
|
7417
7641
|
const compactionMode = this.isCompactionCall(options);
|
|
7418
7642
|
const effectiveModelId = compactionMode ? this.resolveCompactionModel() : resolveAgentModel(
|
|
7419
|
-
this.getOpencodeAgent(options
|
|
7643
|
+
this.getOpencodeAgent(options),
|
|
7420
7644
|
this.modelId
|
|
7421
7645
|
);
|
|
7422
7646
|
const reasoningEffort = compactionMode ? void 0 : resolveAgentEffort(
|
|
7423
|
-
this.getOpencodeAgent(options
|
|
7647
|
+
this.getOpencodeAgent(options),
|
|
7424
7648
|
this.getReasoningEffort(options.providerOptions)
|
|
7425
7649
|
);
|
|
7426
7650
|
const baseKey = sessionKey(
|
|
7427
7651
|
cwd,
|
|
7428
|
-
`${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])}`
|
|
7429
7653
|
);
|
|
7430
7654
|
const sk = compactionMode ? sessionKey(cwd, `${effectiveModelId}::compaction::${affinity}`) : effortSessionKey(baseKey, reasoningEffort);
|
|
7431
7655
|
const toUsage = this.toUsage.bind(this);
|
|
@@ -7526,10 +7750,10 @@ ${plan}
|
|
|
7526
7750
|
});
|
|
7527
7751
|
return { stream: stream2, request: { body: { text: aside.question } } };
|
|
7528
7752
|
}
|
|
7529
|
-
if (scope
|
|
7753
|
+
if (this.isTitleRequest(scope, options) && !compactionMode) {
|
|
7530
7754
|
log.info("doStream no-tools title stub", {
|
|
7531
7755
|
compactionMode,
|
|
7532
|
-
opencodeAgent: this.getOpencodeAgent(options
|
|
7756
|
+
opencodeAgent: this.getOpencodeAgent(options),
|
|
7533
7757
|
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
7534
7758
|
});
|
|
7535
7759
|
const text = this.synthesizeTitle(options.prompt);
|
|
@@ -7719,7 +7943,7 @@ ${plan}
|
|
|
7719
7943
|
proxyTools: resolvedProxy?.map((t) => t.name) ?? null,
|
|
7720
7944
|
compactionMode,
|
|
7721
7945
|
scope,
|
|
7722
|
-
opencodeAgent: this.getOpencodeAgent(options
|
|
7946
|
+
opencodeAgent: this.getOpencodeAgent(options),
|
|
7723
7947
|
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
7724
7948
|
});
|
|
7725
7949
|
const stream = new ReadableStream({
|
|
@@ -8381,7 +8605,7 @@ ${plan}
|
|
|
8381
8605
|
});
|
|
8382
8606
|
}
|
|
8383
8607
|
activeProcess?.pendingProxyCompletions?.clear();
|
|
8384
|
-
if (accountLimitHit && failoverAskActive) {
|
|
8608
|
+
if (accountLimitHit && failoverAskActive && msg.is_error === true) {
|
|
8385
8609
|
const call = createAccountFailoverQuestionCall(sk, {
|
|
8386
8610
|
sourceAccount,
|
|
8387
8611
|
candidates: failoverAccounts,
|
|
@@ -9487,7 +9711,7 @@ ${plan}
|
|
|
9487
9711
|
// src/cleanup-stale.ts
|
|
9488
9712
|
import {
|
|
9489
9713
|
existsSync as existsSync4,
|
|
9490
|
-
readFileSync as
|
|
9714
|
+
readFileSync as readFileSync6,
|
|
9491
9715
|
realpathSync,
|
|
9492
9716
|
rmSync as rmSync3,
|
|
9493
9717
|
writeFileSync as writeFileSync5
|
|
@@ -9514,7 +9738,7 @@ function userIntendsToUseUnscoped() {
|
|
|
9514
9738
|
const cfg = userOpencodeJsonPath();
|
|
9515
9739
|
if (!existsSync4(cfg)) return false;
|
|
9516
9740
|
try {
|
|
9517
|
-
const json = JSON.parse(
|
|
9741
|
+
const json = JSON.parse(readFileSync6(cfg, "utf8"));
|
|
9518
9742
|
const plugins = json.plugin;
|
|
9519
9743
|
if (!Array.isArray(plugins)) return false;
|
|
9520
9744
|
return plugins.some(
|
|
@@ -9563,7 +9787,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
9563
9787
|
if (!existsSync4(pkgJsonPath)) return;
|
|
9564
9788
|
let pkg = {};
|
|
9565
9789
|
try {
|
|
9566
|
-
pkg = JSON.parse(
|
|
9790
|
+
pkg = JSON.parse(readFileSync6(pkgJsonPath, "utf8"));
|
|
9567
9791
|
} catch {
|
|
9568
9792
|
return;
|
|
9569
9793
|
}
|
|
@@ -9582,7 +9806,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
9582
9806
|
const cachePkgJson = join9(cacheRoot, "package.json");
|
|
9583
9807
|
if (!existsSync4(cachePkgJson)) return;
|
|
9584
9808
|
try {
|
|
9585
|
-
const cfg = JSON.parse(
|
|
9809
|
+
const cfg = JSON.parse(readFileSync6(cachePkgJson, "utf8"));
|
|
9586
9810
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
9587
9811
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
9588
9812
|
writeFileSync5(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
@@ -9595,6 +9819,414 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
9595
9819
|
}
|
|
9596
9820
|
}
|
|
9597
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
|
+
|
|
9598
10230
|
// src/index.ts
|
|
9599
10231
|
function pickOpencodeDirectory(input) {
|
|
9600
10232
|
if (!input || typeof input !== "object") return void 0;
|
|
@@ -9617,7 +10249,7 @@ function registerSideQuestionCommand(config) {
|
|
|
9617
10249
|
if (config.command.btw) return false;
|
|
9618
10250
|
config.command.btw = {
|
|
9619
10251
|
template: "/btw $ARGUMENTS",
|
|
9620
|
-
description:
|
|
10252
|
+
description: BTW_COMMAND_DESCRIPTION
|
|
9621
10253
|
};
|
|
9622
10254
|
return true;
|
|
9623
10255
|
}
|
|
@@ -9626,7 +10258,7 @@ function registerDoctorCommand(config) {
|
|
|
9626
10258
|
if (config.command[DOCTOR_COMMAND]) return false;
|
|
9627
10259
|
config.command[DOCTOR_COMMAND] = {
|
|
9628
10260
|
template: `/${DOCTOR_COMMAND} $ARGUMENTS`,
|
|
9629
|
-
description:
|
|
10261
|
+
description: DOCTOR_COMMAND_DESCRIPTION
|
|
9630
10262
|
};
|
|
9631
10263
|
return true;
|
|
9632
10264
|
}
|
|
@@ -9675,6 +10307,7 @@ function createClaudeCode(settings = {}) {
|
|
|
9675
10307
|
return new ClaudeCodeLanguageModel(modelId, {
|
|
9676
10308
|
provider: providerName,
|
|
9677
10309
|
cliPath,
|
|
10310
|
+
hostApi: settings.hostApi,
|
|
9678
10311
|
cwd: settings.cwd,
|
|
9679
10312
|
account: settings.account,
|
|
9680
10313
|
configDir: settings.configDir,
|
|
@@ -10004,13 +10637,20 @@ var server = async (input) => {
|
|
|
10004
10637
|
};
|
|
10005
10638
|
var index_default = {
|
|
10006
10639
|
id: "@khalilgharbaoui/opencode-claude-code-plugin",
|
|
10007
|
-
server
|
|
10640
|
+
server,
|
|
10641
|
+
setup: createV2Setup({
|
|
10642
|
+
createProvider: createClaudeCode,
|
|
10643
|
+
defaultProxyTools: DEFAULT_PROXY_TOOL_NAMES,
|
|
10644
|
+
loadConfig: loadMergedOpencodeConfig,
|
|
10645
|
+
buildAgentRegistry: (config) => buildAgentRegistry(config)
|
|
10646
|
+
})
|
|
10008
10647
|
};
|
|
10009
10648
|
export {
|
|
10010
10649
|
ClaudeCodeLanguageModel,
|
|
10011
10650
|
DEFAULT_PROXY_TOOL_NAMES,
|
|
10012
10651
|
_resetPlanModeWarningForTests,
|
|
10013
10652
|
bridgeOpencodeMcp,
|
|
10653
|
+
buildAgentRegistry,
|
|
10014
10654
|
claudeCodeProviders,
|
|
10015
10655
|
configModelsForProvider,
|
|
10016
10656
|
createClaudeCode,
|