@khalilgharbaoui/opencode-claude-code-plugin 0.5.0 → 0.6.2
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 +20 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.js +116 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ claude --version
|
|
|
23
23
|
|
|
24
24
|
That's it. Restart opencode, pick a `claude-code` model, done.
|
|
25
25
|
|
|
26
|
-
The plugin self-registers the `claude-code` provider, all current Claude Code models (Haiku 4.5, Sonnet 4.5/4.6, Opus 4.5/4.6/4.7) with reasoning variants (`low` / `medium` / `high` / `xhigh` / `max`), and sensible defaults for tool proxying. You don't need to write a `provider` block at all unless you want to override something.
|
|
26
|
+
The plugin self-registers the `claude-code` provider, all current Claude Code models (Haiku 4.5, Sonnet 4.5/4.6, Opus 4.5/4.6/4.7/4.8) with reasoning variants (`low` / `medium` / `high` / `xhigh` / `max`), and sensible defaults for tool proxying. You don't need to write a `provider` block at all unless you want to override something.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -74,6 +74,7 @@ The plugin auto-registers the following. They appear in the model picker without
|
|
|
74
74
|
| `claude-opus-4-5` | Claude Code Opus 4.5 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
75
75
|
| `claude-opus-4-6` | Claude Code Opus 4.6 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
76
76
|
| `claude-opus-4-7` | Claude Code Opus 4.7 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
77
|
+
| `claude-opus-4-8` | Claude Code Opus 4.8 | 1M | 16,384 | low/medium/high/xhigh/max |
|
|
77
78
|
|
|
78
79
|
Capabilities for every model: text + image input, text output, tool use, attachments. No temperature control, no PDF/audio/video, no interleaved streaming.
|
|
79
80
|
|
|
@@ -456,6 +457,24 @@ doesn't accrete a log file on every user's disk by default — opt in when
|
|
|
456
457
|
you need to inspect auto-continue decisions, broker state, or other
|
|
457
458
|
plugin internals.
|
|
458
459
|
|
|
460
|
+
## Compatibility with other opencode plugins
|
|
461
|
+
|
|
462
|
+
### [opencode-dcp](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning) (Dynamic Context Pruning)
|
|
463
|
+
|
|
464
|
+
Partial support since v0.5.1. DCP runs in a useful degraded mode: automatic strategies and slash commands work, autonomous model-driven compression does not.
|
|
465
|
+
|
|
466
|
+
| DCP feature | Status | Notes |
|
|
467
|
+
|---|---|---|
|
|
468
|
+
| `experimental.chat.messages.transform` (compression placeholders, dedup, error purge) | ✅ Works | Transforms run inside opencode before reaching this plugin. |
|
|
469
|
+
| `experimental.chat.system.transform` (context-limit nudges, iteration reminders) | ✅ Works | `extractSystemMessages` forwards system-role content to Claude CLI via `--append-system-prompt-file`. |
|
|
470
|
+
| `/dcp compress`, `/dcp sweep`, `/dcp manual`, `/dcp context`, `/dcp stats` slash commands | ✅ Works | Handled by opencode's `command.execute.before` hook, not the model. |
|
|
471
|
+
| Automatic `deduplication` + `purgeErrors` strategies | ✅ Works | Message-transform only, no model tool calls. |
|
|
472
|
+
| Autonomous model-driven `compress` tool calls | ❌ Not supported | DCP registers `compress` as an opencode-native tool. Claude CLI only sees its own built-ins and MCP-bridged servers, so the model never sees `compress`. The plugin prepends a runtime note instructing Claude to ignore any system instruction that asks it to call `compress`/`distill`/`prune`. |
|
|
473
|
+
|
|
474
|
+
Workaround for autonomous compression: trigger it manually with `/dcp compress` whenever you'd want the model to call it. Full autonomous support would require exposing `compress` as an MCP-bridged tool, which is upstream of this plugin.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
459
478
|
## Known limitations
|
|
460
479
|
|
|
461
480
|
- No streaming of tool inputs as they're being constructed (Anthropic's `input_json_delta`); the plugin emits them once complete.
|
package/dist/index.d.ts
CHANGED
|
@@ -89,6 +89,13 @@ type OpenCodeEvent = {
|
|
|
89
89
|
* for the current call ("default", "compaction", "title", etc.), the
|
|
90
90
|
* resolved model, and the user message. Output is the mutable params bag
|
|
91
91
|
* the hook can adjust before opencode forwards them to the LM.
|
|
92
|
+
*
|
|
93
|
+
* The plugin injects `input.agent` as `opencodeAgent` and `input.sessionID`
|
|
94
|
+
* as `opencodeSessionID` into `output.options` so the language model can
|
|
95
|
+
* read them from `providerOptions[providerID]` on every LLM request.
|
|
96
|
+
* `opencodeSessionID` serves as a fallback affinity token when the
|
|
97
|
+
* `x-session-affinity` request header is absent (provider switch
|
|
98
|
+
* mid-session, title synthesis paths, older opencode versions).
|
|
92
99
|
*/
|
|
93
100
|
type OpenCodeChatParamsInput = {
|
|
94
101
|
sessionID?: string;
|
|
@@ -450,11 +457,14 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
450
457
|
private ensureProxyServer;
|
|
451
458
|
private extractPendingProxyResult;
|
|
452
459
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
460
|
+
* Resolve the session affinity token for this LLM call. Delegates to the
|
|
461
|
+
* exported `resolveSessionAffinity` helper so the logic is unit-testable.
|
|
462
|
+
* Priority:
|
|
463
|
+
* 1. `x-session-affinity` request header (primary).
|
|
464
|
+
* 2. `opencodeSessionID` in providerOptions (chat.params hook fallback —
|
|
465
|
+
* covers provider switches mid-session and title synthesis paths
|
|
466
|
+
* where the header is absent).
|
|
467
|
+
* 3. `"default"`.
|
|
458
468
|
*/
|
|
459
469
|
private sessionAffinity;
|
|
460
470
|
private controlRequestBehaviorForTool;
|
package/dist/index.js
CHANGED
|
@@ -1955,6 +1955,22 @@ function resolveCompactionModel(configured) {
|
|
|
1955
1955
|
if (trimmed) return trimmed;
|
|
1956
1956
|
return DEFAULT_COMPACTION_MODEL;
|
|
1957
1957
|
}
|
|
1958
|
+
function resolveSessionAffinity(headers, providerOptions, providerKey) {
|
|
1959
|
+
if (headers) {
|
|
1960
|
+
for (const key of Object.keys(headers)) {
|
|
1961
|
+
if (key.toLowerCase() === "x-session-affinity") {
|
|
1962
|
+
const v = headers[key];
|
|
1963
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (providerOptions) {
|
|
1968
|
+
const bag = providerOptions[providerKey] ?? providerOptions["claude-code"];
|
|
1969
|
+
const sid = bag?.opencodeSessionID;
|
|
1970
|
+
if (typeof sid === "string" && sid.length > 0) return sid;
|
|
1971
|
+
}
|
|
1972
|
+
return "default";
|
|
1973
|
+
}
|
|
1958
1974
|
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
1959
1975
|
"thinking_delta",
|
|
1960
1976
|
"text_delta",
|
|
@@ -2132,6 +2148,11 @@ function nearestWorkspaceAgentsPrompt(cwd) {
|
|
|
2132
2148
|
dir = parent;
|
|
2133
2149
|
}
|
|
2134
2150
|
}
|
|
2151
|
+
var AGENTS_MAINTENANCE_HINT = `## Keeping AGENTS.md up to date
|
|
2152
|
+
|
|
2153
|
+
When you complete a task, phase, or to-do item that is listed in AGENTS.md, update the file
|
|
2154
|
+
immediately after the work is done \u2014 mark it \u2705, check it off, or remove it. Do this inside
|
|
2155
|
+
the same turn so the next session does not repeat work that is already finished.`;
|
|
2135
2156
|
var MULTI_STEP_TASK_HINT = `## Continuing through multi-step tasks
|
|
2136
2157
|
|
|
2137
2158
|
opencode requires the user to press "continue" after each turn ends. When a
|
|
@@ -2140,13 +2161,43 @@ than pausing for user confirmation between subtasks. End the turn only
|
|
|
2140
2161
|
when the task is done, you need clarification on intent, or you hit a real
|
|
2141
2162
|
blocker. The user can interrupt or abort at any time; turn endings should
|
|
2142
2163
|
mark meaningful checkpoints, not every completed substep.`;
|
|
2143
|
-
|
|
2164
|
+
var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
|
|
2165
|
+
|
|
2166
|
+
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
2167
|
+
|
|
2168
|
+
- The \`compress\` tool is NOT available. Do not attempt to call it.
|
|
2169
|
+
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
2170
|
+
- Context window management is handled automatically by Claude CLI's own session history.
|
|
2171
|
+
- Ignore any system instructions that tell you to call \`compress\` \u2014 they are intended for direct API providers, not this environment.
|
|
2172
|
+
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
2173
|
+
function extractSystemMessages(prompt) {
|
|
2174
|
+
const out = [];
|
|
2175
|
+
for (const msg of prompt) {
|
|
2176
|
+
if (msg.role !== "system") continue;
|
|
2177
|
+
if (typeof msg.content === "string") {
|
|
2178
|
+
if (msg.content.trim()) out.push(msg.content.trim());
|
|
2179
|
+
} else if (Array.isArray(msg.content)) {
|
|
2180
|
+
for (const part of msg.content) {
|
|
2181
|
+
if (part?.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
|
2182
|
+
out.push(part.text.trim());
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return out;
|
|
2188
|
+
}
|
|
2189
|
+
function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystemContent = []) {
|
|
2144
2190
|
const parts = [];
|
|
2191
|
+
parts.push(CLAUDE_CLI_CONTEXT_NOTE);
|
|
2192
|
+
for (const s of extraSystemContent) {
|
|
2193
|
+
if (s.trim()) parts.push(s.trim());
|
|
2194
|
+
}
|
|
2145
2195
|
const configRoot = process.env.XDG_CONFIG_HOME ?? join5(homedir3(), ".config");
|
|
2146
2196
|
const globalAgents = readPromptFileIfPresent(join5(configRoot, "opencode", "AGENTS.md"));
|
|
2147
2197
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
2148
2198
|
if (globalAgents) parts.push(globalAgents);
|
|
2149
2199
|
if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
|
|
2200
|
+
if (globalAgents || workspaceAgents) parts.push(AGENTS_MAINTENANCE_HINT);
|
|
2150
2201
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
2151
2202
|
const content = parts.join("\n\n");
|
|
2152
2203
|
if (!content) return void 0;
|
|
@@ -2336,22 +2387,22 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2336
2387
|
return null;
|
|
2337
2388
|
}
|
|
2338
2389
|
/**
|
|
2339
|
-
*
|
|
2340
|
-
*
|
|
2341
|
-
*
|
|
2342
|
-
*
|
|
2343
|
-
*
|
|
2390
|
+
* Resolve the session affinity token for this LLM call. Delegates to the
|
|
2391
|
+
* exported `resolveSessionAffinity` helper so the logic is unit-testable.
|
|
2392
|
+
* Priority:
|
|
2393
|
+
* 1. `x-session-affinity` request header (primary).
|
|
2394
|
+
* 2. `opencodeSessionID` in providerOptions (chat.params hook fallback —
|
|
2395
|
+
* covers provider switches mid-session and title synthesis paths
|
|
2396
|
+
* where the header is absent).
|
|
2397
|
+
* 3. `"default"`.
|
|
2344
2398
|
*/
|
|
2345
2399
|
sessionAffinity(options) {
|
|
2346
2400
|
const headers = options?.headers;
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
return "default";
|
|
2401
|
+
return resolveSessionAffinity(
|
|
2402
|
+
headers,
|
|
2403
|
+
options.providerOptions,
|
|
2404
|
+
this.config.provider
|
|
2405
|
+
);
|
|
2355
2406
|
}
|
|
2356
2407
|
controlRequestBehaviorForTool(toolName) {
|
|
2357
2408
|
const configured = this.config.controlRequestToolBehaviors;
|
|
@@ -2663,7 +2714,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2663
2714
|
]);
|
|
2664
2715
|
const systemPromptFile = buildAppendedSystemPrompt(
|
|
2665
2716
|
cwd,
|
|
2666
|
-
this.config.multiStepContinuation !== false
|
|
2717
|
+
this.config.multiStepContinuation !== false,
|
|
2718
|
+
extractSystemMessages(options.prompt)
|
|
2667
2719
|
);
|
|
2668
2720
|
const cliArgs = buildCliArgs({
|
|
2669
2721
|
sessionKey: sk,
|
|
@@ -2703,8 +2755,15 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2703
2755
|
let thinkingText = "";
|
|
2704
2756
|
let resultMeta = {};
|
|
2705
2757
|
const toolCalls = [];
|
|
2758
|
+
const toolCallStreams = /* @__PURE__ */ new Map();
|
|
2706
2759
|
let gotPartialEvents = false;
|
|
2707
2760
|
const result = await new Promise((resolve3, reject) => {
|
|
2761
|
+
const cleanup = () => {
|
|
2762
|
+
try {
|
|
2763
|
+
if (!proc.killed && proc.exitCode === null) proc.kill();
|
|
2764
|
+
} catch {
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2708
2767
|
rl.on("line", (line) => {
|
|
2709
2768
|
if (!line.trim()) return;
|
|
2710
2769
|
try {
|
|
@@ -2755,30 +2814,41 @@ ${plan}
|
|
|
2755
2814
|
}
|
|
2756
2815
|
}
|
|
2757
2816
|
}
|
|
2758
|
-
if (msg.type === "content_block_start" && msg.content_block) {
|
|
2817
|
+
if (msg.type === "content_block_start" && msg.content_block && msg.index !== void 0) {
|
|
2759
2818
|
if (msg.content_block.type === "tool_use" && msg.content_block.id && msg.content_block.name) {
|
|
2760
|
-
|
|
2819
|
+
toolCallStreams.set(msg.index, {
|
|
2761
2820
|
id: msg.content_block.id,
|
|
2762
2821
|
name: msg.content_block.name,
|
|
2763
|
-
|
|
2822
|
+
inputJson: ""
|
|
2764
2823
|
});
|
|
2765
2824
|
}
|
|
2766
2825
|
}
|
|
2767
|
-
if (msg.type === "content_block_delta" && msg.delta) {
|
|
2826
|
+
if (msg.type === "content_block_delta" && msg.delta && msg.index !== void 0) {
|
|
2768
2827
|
if (msg.delta.type === "text_delta" && msg.delta.text) {
|
|
2769
2828
|
responseText += msg.delta.text;
|
|
2770
2829
|
}
|
|
2771
2830
|
if (msg.delta.type === "thinking_delta" && msg.delta.thinking) {
|
|
2772
2831
|
thinkingText += msg.delta.thinking;
|
|
2773
2832
|
}
|
|
2774
|
-
if (msg.delta.type === "input_json_delta" && msg.delta.partial_json
|
|
2775
|
-
const tc =
|
|
2776
|
-
if (tc)
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2833
|
+
if (msg.delta.type === "input_json_delta" && msg.delta.partial_json) {
|
|
2834
|
+
const tc = toolCallStreams.get(msg.index);
|
|
2835
|
+
if (tc) tc.inputJson += msg.delta.partial_json;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
if (msg.type === "content_block_stop" && msg.index !== void 0) {
|
|
2839
|
+
const tc = toolCallStreams.get(msg.index);
|
|
2840
|
+
if (tc) {
|
|
2841
|
+
let args = {};
|
|
2842
|
+
try {
|
|
2843
|
+
args = tc.inputJson ? JSON.parse(tc.inputJson) : {};
|
|
2844
|
+
} catch (err) {
|
|
2845
|
+
log.warn("tool input JSON parse failed", {
|
|
2846
|
+
name: tc.name,
|
|
2847
|
+
error: String(err)
|
|
2848
|
+
});
|
|
2781
2849
|
}
|
|
2850
|
+
toolCalls.push({ id: tc.id, name: tc.name, args });
|
|
2851
|
+
toolCallStreams.delete(msg.index);
|
|
2782
2852
|
}
|
|
2783
2853
|
}
|
|
2784
2854
|
if (msg.type === "result") {
|
|
@@ -2794,6 +2864,7 @@ ${plan}
|
|
|
2794
2864
|
durationMs: msg.duration_ms,
|
|
2795
2865
|
usage: msg.usage
|
|
2796
2866
|
};
|
|
2867
|
+
cleanup();
|
|
2797
2868
|
resolve3({
|
|
2798
2869
|
...resultMeta,
|
|
2799
2870
|
text: responseText,
|
|
@@ -2805,6 +2876,7 @@ ${plan}
|
|
|
2805
2876
|
}
|
|
2806
2877
|
});
|
|
2807
2878
|
rl.on("close", () => {
|
|
2879
|
+
cleanup();
|
|
2808
2880
|
resolve3({
|
|
2809
2881
|
...resultMeta,
|
|
2810
2882
|
text: responseText,
|
|
@@ -2814,6 +2886,7 @@ ${plan}
|
|
|
2814
2886
|
});
|
|
2815
2887
|
proc.on("error", (err) => {
|
|
2816
2888
|
log.error("process error", { error: err.message });
|
|
2889
|
+
cleanup();
|
|
2817
2890
|
reject(err);
|
|
2818
2891
|
});
|
|
2819
2892
|
proc.stderr?.on("data", (data) => {
|
|
@@ -3070,7 +3143,8 @@ ${plan}
|
|
|
3070
3143
|
);
|
|
3071
3144
|
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
3072
3145
|
cwd,
|
|
3073
|
-
self.config.multiStepContinuation !== false
|
|
3146
|
+
self.config.multiStepContinuation !== false,
|
|
3147
|
+
extractSystemMessages(options.prompt)
|
|
3074
3148
|
);
|
|
3075
3149
|
cliArgs = buildCliArgs({
|
|
3076
3150
|
sessionKey: sk,
|
|
@@ -4157,6 +4231,16 @@ var defaultModels = {
|
|
|
4157
4231
|
output: 16384,
|
|
4158
4232
|
cost: opusCost,
|
|
4159
4233
|
releaseDate: "2025-07-16"
|
|
4234
|
+
}),
|
|
4235
|
+
"claude-opus-4-8": defineModel({
|
|
4236
|
+
id: "claude-opus-4-8",
|
|
4237
|
+
name: "Claude Opus 4.8",
|
|
4238
|
+
family: "opus",
|
|
4239
|
+
reasoning: true,
|
|
4240
|
+
context: 1e6,
|
|
4241
|
+
output: 16384,
|
|
4242
|
+
cost: opusCost,
|
|
4243
|
+
releaseDate: "2026-05-28"
|
|
4160
4244
|
})
|
|
4161
4245
|
};
|
|
4162
4246
|
|
|
@@ -4662,11 +4746,16 @@ var server = async (input) => {
|
|
|
4662
4746
|
});
|
|
4663
4747
|
if (typeof providerID !== "string") return;
|
|
4664
4748
|
if (providerID !== PROVIDER_ID2 && !providerID.startsWith(`${PROVIDER_ID2}-`)) return;
|
|
4749
|
+
if (typeof input2.sessionID === "string" && input2.sessionID.length > 0) {
|
|
4750
|
+
output.options ??= {};
|
|
4751
|
+
output.options.opencodeSessionID = input2.sessionID;
|
|
4752
|
+
}
|
|
4665
4753
|
if (!input2.agent) return;
|
|
4666
4754
|
output.options ??= {};
|
|
4667
4755
|
output.options.opencodeAgent = input2.agent;
|
|
4668
4756
|
log.debug("chat.params tagged providerOptions", {
|
|
4669
4757
|
agent: input2.agent,
|
|
4758
|
+
sessionID: input2.sessionID,
|
|
4670
4759
|
providerID
|
|
4671
4760
|
});
|
|
4672
4761
|
}
|