@khalilgharbaoui/opencode-claude-code-plugin 0.25.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/index.d.ts +226 -1
- package/dist/index.js +743 -46
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +14 -0
package/README.md
CHANGED
|
@@ -80,6 +80,32 @@ In your `opencode.json`, point at the local build with a `file://` URL:
|
|
|
80
80
|
|
|
81
81
|
CI installs and builds on **Node 24** (`.github/workflows/publish.yml`), which is the only version this package is built against. `package.json` declares no `engines` range, so older Node versions are untested rather than deliberately unsupported. opencode itself may run under Bun; the [interactive transport](#interactive-transport-experimental) requires that.
|
|
82
82
|
|
|
83
|
+
### opencode 2
|
|
84
|
+
|
|
85
|
+
The same package runs on opencode 1.x and 2.x, and nothing changes for 1.x. The same config works too: opencode 2's native key is `plugins`, but it still reads the 1.x `plugin` key shown above, so an existing install needs no edit. A config used only by opencode 2 can spell it natively:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"plugins": ["@khalilgharbaoui/opencode-claude-code-plugin"]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Your existing `provider.claude-code.options` block keeps working, because opencode 2 still reads 1.x config files. Its native spelling is `provider.claude-code.settings`, and plugin-level settings such as `accounts` may also go in the plugin entry itself: `{"package": "@khalilgharbaoui/opencode-claude-code-plugin", "options": {"accounts": ["work"]}}`.
|
|
94
|
+
|
|
95
|
+
For a local checkout, point opencode 2 at the **`dist` directory**, not the repository root. It loads `<dir>/server` or `<dir>/index` from a directory and never reads `package.json#main`:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"plugins": ["/absolute/path/to/opencode-claude-code-plugin/dist"]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Verified live on opencode **2.0.11** with Claude Code 2.1.280: chat turns, proxied tools running through opencode 2's own `shell`, `edit`, `write`, `webfetch` and `subagent` tools and their permission rules, Claude's own tools rendered in the transcript, subagent dispatch with the agent list, reasoning variants, compaction, account providers, `/claude-code-doctor`, `/btw`, and the bundled configuration skill. Differences from 1.x:
|
|
104
|
+
|
|
105
|
+
- **`/btw` is answered after the running turn**, not inside it. opencode 2's plugin API has no session-status route, which is what 1.x uses to write the answer into a turn that is still running. The aside is queued, so it can never swallow the turn's own continuation.
|
|
106
|
+
- **No todo panel.** opencode 2 has no `todowrite` tool, so Claude's task list is not mirrored into one.
|
|
107
|
+
- **Account failover and the plan-mode form** use opencode 2's `question` tool, which takes the same input as 1.x. Both are covered by offline tests only on 2.x, since neither can be triggered on demand.
|
|
108
|
+
|
|
83
109
|
---
|
|
84
110
|
|
|
85
111
|
## Models
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,188 @@
|
|
|
1
1
|
import { LanguageModelV3, LanguageModelV3CallOptions } from '@ai-sdk/provider';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Hand-written structural mirror of the slice of opencode 2.x's plugin API
|
|
5
|
+
* this package touches. Same approach as `opencode-types.ts` for V1: no
|
|
6
|
+
* runtime or type dependency on `@opencode/plugin`, whose promise API pulls in
|
|
7
|
+
* an Effect release candidate we have no other use for.
|
|
8
|
+
*
|
|
9
|
+
* Read off the shipped `.d.ts` of `@opencode/plugin@2.0.11`, `@opencode/schema`
|
|
10
|
+
* and `@opencode/client` at the same version, not off the website, whose
|
|
11
|
+
* provider docs describe a metadata-only surface and omit the `aisdk` domain
|
|
12
|
+
* entirely. Every field here is one we read or write; everything else is left
|
|
13
|
+
* out on purpose so drift in an unused corner cannot break the build.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface V2Registration {
|
|
17
|
+
readonly dispose: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/** `@opencode/schema/provider` `Provider.Info`, the fields we set. */
|
|
20
|
+
interface V2ProviderInfo {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
activation: "auto" | "enabled" | "disabled";
|
|
24
|
+
package: string;
|
|
25
|
+
settings?: Record<string, unknown>;
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
body?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
interface V2ModelVariant {
|
|
30
|
+
id: string;
|
|
31
|
+
settings?: Record<string, unknown>;
|
|
32
|
+
headers?: Record<string, string>;
|
|
33
|
+
body?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
interface V2ModelCost {
|
|
36
|
+
input: number;
|
|
37
|
+
output: number;
|
|
38
|
+
cache: {
|
|
39
|
+
read: number;
|
|
40
|
+
write: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** `@opencode/schema/model` `Model.Info`, every required field plus the optional ones we fill. */
|
|
44
|
+
interface V2ModelInfo {
|
|
45
|
+
id: string;
|
|
46
|
+
modelID: string;
|
|
47
|
+
providerID: string;
|
|
48
|
+
name: string;
|
|
49
|
+
family?: string;
|
|
50
|
+
capabilities: {
|
|
51
|
+
tools: boolean;
|
|
52
|
+
input: string[];
|
|
53
|
+
output: string[];
|
|
54
|
+
};
|
|
55
|
+
variants: V2ModelVariant[];
|
|
56
|
+
time: {
|
|
57
|
+
released: number;
|
|
58
|
+
};
|
|
59
|
+
cost: V2ModelCost[];
|
|
60
|
+
status: "alpha" | "beta" | "deprecated" | "active";
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
limit: {
|
|
63
|
+
context: number;
|
|
64
|
+
input?: number;
|
|
65
|
+
output: number;
|
|
66
|
+
};
|
|
67
|
+
settings?: Record<string, unknown>;
|
|
68
|
+
headers?: Record<string, string>;
|
|
69
|
+
package?: string;
|
|
70
|
+
}
|
|
71
|
+
interface V2ProviderRecord {
|
|
72
|
+
readonly provider: V2ProviderInfo;
|
|
73
|
+
readonly models: ReadonlyMap<string, V2ModelInfo>;
|
|
74
|
+
}
|
|
75
|
+
interface V2ProviderEditor {
|
|
76
|
+
list(): readonly V2ProviderRecord[];
|
|
77
|
+
get(providerID: string): V2ProviderRecord | undefined;
|
|
78
|
+
add(input: {
|
|
79
|
+
info: V2ProviderInfo;
|
|
80
|
+
models: readonly V2ModelInfo[];
|
|
81
|
+
}): void;
|
|
82
|
+
update(providerID: string, update: (provider: V2ProviderInfo) => void): void;
|
|
83
|
+
remove(providerID: string): void;
|
|
84
|
+
readonly models: {
|
|
85
|
+
set(providerID: string, models: readonly V2ModelInfo[]): void;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
interface V2AISDKSdkEvent {
|
|
89
|
+
readonly model: V2ModelInfo;
|
|
90
|
+
readonly package: string;
|
|
91
|
+
readonly options: Record<string, unknown>;
|
|
92
|
+
sdk?: unknown;
|
|
93
|
+
}
|
|
94
|
+
interface V2AISDKLanguageEvent {
|
|
95
|
+
readonly model: V2ModelInfo;
|
|
96
|
+
readonly sdk: unknown;
|
|
97
|
+
readonly options: Record<string, unknown>;
|
|
98
|
+
language?: LanguageModelV3;
|
|
99
|
+
}
|
|
100
|
+
type V2RequestKind = "primary" | "compaction" | "title" | "generate";
|
|
101
|
+
interface V2ModelRequestEvent {
|
|
102
|
+
readonly sessionID: string;
|
|
103
|
+
readonly agent: string;
|
|
104
|
+
readonly model: {
|
|
105
|
+
id: string;
|
|
106
|
+
providerID: string;
|
|
107
|
+
variant?: string;
|
|
108
|
+
};
|
|
109
|
+
readonly kind: V2RequestKind;
|
|
110
|
+
baseURL?: string;
|
|
111
|
+
headers: Record<string, string>;
|
|
112
|
+
}
|
|
113
|
+
interface V2ModelHookOptions {
|
|
114
|
+
readonly providerID?: string;
|
|
115
|
+
}
|
|
116
|
+
/** `@opencode/schema/prompt-input` `Prompt`: we only rewrite `text`. */
|
|
117
|
+
type V2Prompt = {
|
|
118
|
+
text?: string;
|
|
119
|
+
} & Record<string, unknown>;
|
|
120
|
+
type V2Delivery = "steer" | "queue";
|
|
121
|
+
interface V2CommandInvocation {
|
|
122
|
+
readonly sessionID: string;
|
|
123
|
+
readonly prompt: V2Prompt;
|
|
124
|
+
readonly delivery: V2Delivery;
|
|
125
|
+
}
|
|
126
|
+
interface V2CommandDefinition {
|
|
127
|
+
readonly name: string;
|
|
128
|
+
readonly description?: string;
|
|
129
|
+
readonly execute: (input: V2CommandInvocation) => Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
/** `session.deleted` is the one event this plugin acts on. */
|
|
132
|
+
interface V2Event {
|
|
133
|
+
readonly type?: string;
|
|
134
|
+
readonly data?: {
|
|
135
|
+
readonly sessionID?: string;
|
|
136
|
+
} & Record<string, unknown>;
|
|
137
|
+
}
|
|
138
|
+
interface V2Context {
|
|
139
|
+
readonly app: {
|
|
140
|
+
readonly version: string;
|
|
141
|
+
};
|
|
142
|
+
readonly location: {
|
|
143
|
+
readonly directory: string;
|
|
144
|
+
};
|
|
145
|
+
readonly options: Record<string, unknown>;
|
|
146
|
+
readonly provider: {
|
|
147
|
+
transform(callback: (editor: V2ProviderEditor) => void): Promise<V2Registration>;
|
|
148
|
+
};
|
|
149
|
+
readonly aisdk: {
|
|
150
|
+
hook(name: "sdk", callback: (event: V2AISDKSdkEvent) => Promise<void> | void, options?: V2ModelHookOptions): Promise<V2Registration>;
|
|
151
|
+
hook(name: "language", callback: (event: V2AISDKLanguageEvent) => Promise<void> | void, options?: V2ModelHookOptions): Promise<V2Registration>;
|
|
152
|
+
};
|
|
153
|
+
readonly session: {
|
|
154
|
+
hook(name: "model.request", callback: (event: V2ModelRequestEvent) => Promise<void> | void, options?: V2ModelHookOptions): Promise<V2Registration>;
|
|
155
|
+
prompt?(input: V2Prompt & {
|
|
156
|
+
sessionID: string;
|
|
157
|
+
delivery?: V2Delivery;
|
|
158
|
+
}): Promise<unknown>;
|
|
159
|
+
};
|
|
160
|
+
readonly command?: {
|
|
161
|
+
transform(callback: (editor: {
|
|
162
|
+
add(definition: V2CommandDefinition): void;
|
|
163
|
+
}) => void): Promise<V2Registration>;
|
|
164
|
+
};
|
|
165
|
+
readonly event?: {
|
|
166
|
+
subscribe(options?: {
|
|
167
|
+
signal?: AbortSignal;
|
|
168
|
+
}): AsyncIterable<V2Event>;
|
|
169
|
+
};
|
|
170
|
+
readonly skill?: {
|
|
171
|
+
transform(callback: (editor: {
|
|
172
|
+
get(id: string): unknown;
|
|
173
|
+
add(skill: {
|
|
174
|
+
id: string;
|
|
175
|
+
name: string;
|
|
176
|
+
description?: string;
|
|
177
|
+
path: string;
|
|
178
|
+
content: string;
|
|
179
|
+
}): void;
|
|
180
|
+
}) => void): Promise<V2Registration>;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** What `setup` may return: a cleanup run when opencode unloads the plugin. */
|
|
184
|
+
type V2Cleanup = () => void | Promise<void>;
|
|
185
|
+
|
|
3
186
|
type ModelID = string;
|
|
4
187
|
type ProviderID = string;
|
|
5
188
|
type OpenCodeModel = {
|
|
@@ -158,6 +341,14 @@ type LogMode = "silent" | "debug";
|
|
|
158
341
|
interface ClaudeCodeConfig {
|
|
159
342
|
provider: string;
|
|
160
343
|
cliPath: string;
|
|
344
|
+
/**
|
|
345
|
+
* Which opencode major this model was created by, which decides the tool
|
|
346
|
+
* vocabulary its stream uses (src/host-tools.ts). Set only by the V2
|
|
347
|
+
* entrypoint's `sdk` hook; absent means opencode 1.x. Per model rather than
|
|
348
|
+
* per process on purpose: opencode 1.18 also calls a dual export's `setup`,
|
|
349
|
+
* so nothing process-wide may decide it.
|
|
350
|
+
*/
|
|
351
|
+
hostApi?: "v1" | "v2";
|
|
161
352
|
/** Drive interactive claude (subscription) instead of headless --print. */
|
|
162
353
|
interactive?: boolean;
|
|
163
354
|
/** Deprecated/no-op with interactive: Claude Code's TUI requires manual confirmation for bypassPermissions. */
|
|
@@ -258,6 +449,8 @@ type WebSearchRouting = "claude" | "disabled" | (string & {});
|
|
|
258
449
|
type AccountFailoverMode = "ask" | "off";
|
|
259
450
|
interface ClaudeCodeProviderSettings {
|
|
260
451
|
cliPath?: string;
|
|
452
|
+
/** Internal: set by the opencode 2 entrypoint. See `ClaudeCodeConfig.hostApi`. */
|
|
453
|
+
hostApi?: "v1" | "v2";
|
|
261
454
|
/** Drive interactive claude (subscription) instead of headless --print. */
|
|
262
455
|
interactive?: boolean;
|
|
263
456
|
/** Deprecated/no-op with interactive: Claude Code's TUI requires manual confirmation for bypassPermissions. */
|
|
@@ -724,6 +917,16 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
724
917
|
get provider(): string;
|
|
725
918
|
private toUsage;
|
|
726
919
|
private toFinishReason;
|
|
920
|
+
/**
|
|
921
|
+
* Whether this call only names the session, which gets the synthetic stub
|
|
922
|
+
* rather than a `claude` spawn. opencode 1.x sends a title request with no
|
|
923
|
+
* tools, and that is the whole test there. opencode 2 sends its tool set
|
|
924
|
+
* along with it (measured on 2.0.11: `scope: "tools"`, agent `title`), so
|
|
925
|
+
* every new V2 session paid for a second `claude` process just to title
|
|
926
|
+
* itself; for a V2 model the request kind, carried as the `title` agent,
|
|
927
|
+
* decides instead.
|
|
928
|
+
*/
|
|
929
|
+
private isTitleRequest;
|
|
727
930
|
private requestScope;
|
|
728
931
|
/**
|
|
729
932
|
* Build the combined `--mcp-config` list and return both the list and the
|
|
@@ -842,7 +1045,14 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
842
1045
|
private synthesizeTitle;
|
|
843
1046
|
private doGenerateViaStream;
|
|
844
1047
|
doGenerate(options: LanguageModelV3CallOptions): Promise<Awaited<ReturnType<LanguageModelV3["doGenerate"]>>>;
|
|
1048
|
+
private doGenerateForHost;
|
|
1049
|
+
/**
|
|
1050
|
+
* Tool parts leave in opencode 1.x's vocabulary; on opencode 2.x they are
|
|
1051
|
+
* renamed at this one edge (src/host-tools.ts). On V1 the stream is
|
|
1052
|
+
* returned untouched.
|
|
1053
|
+
*/
|
|
845
1054
|
doStream(options: LanguageModelV3CallOptions): Promise<Awaited<ReturnType<LanguageModelV3["doStream"]>>>;
|
|
1055
|
+
private doStreamForHost;
|
|
846
1056
|
}
|
|
847
1057
|
|
|
848
1058
|
interface BridgedMcp {
|
|
@@ -959,6 +1169,20 @@ declare function configModelsForProvider(providerModels: OpenCodeProvider["model
|
|
|
959
1169
|
* diagnostics never report another provider's options.
|
|
960
1170
|
*/
|
|
961
1171
|
declare function claudeCodeProviders(providers: Record<string, DiagnosticsProviderEntry> | undefined): Record<string, DiagnosticsProviderEntry>;
|
|
1172
|
+
/**
|
|
1173
|
+
* Record what every known agent asked for, so `resolveAgentModel` and
|
|
1174
|
+
* `resolveAgentEffort` can answer at spawn time without the language model
|
|
1175
|
+
* needing to see opencode's config.
|
|
1176
|
+
*
|
|
1177
|
+
* Runs BEFORE `expandAccountProviders`, which deletes the seed provider entry
|
|
1178
|
+
* once it has expanded it: `defaultSubagentModel` has to be read while it is
|
|
1179
|
+
* still there.
|
|
1180
|
+
*
|
|
1181
|
+
* Purely observational. It defines no agents and changes no agent's config;
|
|
1182
|
+
* an agent this plugin never heard of is simply absent from the registry,
|
|
1183
|
+
* which is what keeps opencode's built-ins out of the override path.
|
|
1184
|
+
*/
|
|
1185
|
+
declare function buildAgentRegistry(config: OpenCodeConfig): Promise<void>;
|
|
962
1186
|
/**
|
|
963
1187
|
* The opencode session id a `session.deleted` bus event names, or undefined
|
|
964
1188
|
* for any other event. opencode publishes `{ type, properties: { info } }`
|
|
@@ -968,6 +1192,7 @@ declare function extractDeletedSessionId(event: OpenCodeEvent | undefined): stri
|
|
|
968
1192
|
declare const _default: {
|
|
969
1193
|
id: string;
|
|
970
1194
|
server: OpenCodePlugin;
|
|
1195
|
+
setup: (ctx: V2Context) => Promise<V2Cleanup>;
|
|
971
1196
|
};
|
|
972
1197
|
|
|
973
|
-
export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, _resetPlanModeWarningForTests, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, extractDeletedSessionId, getAgentRegistry, getDefaultSubagentModel, registerDoctorCommand, registerSideQuestionCommand, resolveAgentModel, warnIfPlanModeCannotExit };
|
|
1198
|
+
export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, _resetPlanModeWarningForTests, bridgeOpencodeMcp, buildAgentRegistry, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, extractDeletedSessionId, getAgentRegistry, getDefaultSubagentModel, registerDoctorCommand, registerSideQuestionCommand, resolveAgentModel, warnIfPlanModeCannotExit };
|