dsh-opencode 0.1.1 → 0.1.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 +26 -75
- package/lib/commands.js +17 -43
- package/lib/index.js +0 -5
- package/lib/transport.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,108 +1,59 @@
|
|
|
1
|
-
# dsh-opencode
|
|
1
|
+
# dsh-opencode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
OpenCode Zen / Go を動的カタログ付きのライブ LLM ルートとして DSH に追加するプラグイン。
|
|
4
4
|
|
|
5
|
-
- `opencode-zen-live` —
|
|
6
|
-
- `opencode-go-live` —
|
|
5
|
+
- `opencode-zen-live` — OpenCode Zen (Live)
|
|
6
|
+
- `opencode-go-live` — OpenCode Go (Live)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Compatibility
|
|
11
|
-
|
|
12
|
-
- DSH packages: `0.1.2-rc.1` (published release; peer range `^0.1.2-rc.1`)
|
|
13
|
-
- pi-ai: pinned to the installed DSH adapter's range (`>=0.84.2 <0.86`, verified on `0.84.2`)
|
|
14
|
-
- Cordis: `4.0.2`
|
|
15
|
-
- Node: `^22.19.0 || >=24.0.0`
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
8
|
+
## インストール
|
|
18
9
|
|
|
19
10
|
```sh
|
|
20
11
|
pnpm dsh plugin --profile web add github:askdkc/dsh-opencode
|
|
21
|
-
pnpm dsh --profile web --dump-config
|
|
22
12
|
```
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
To remove it:
|
|
14
|
+
削除:
|
|
27
15
|
|
|
28
16
|
```sh
|
|
29
17
|
pnpm dsh plugin --profile web remove dsh-opencode
|
|
30
18
|
```
|
|
31
19
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
Both routes resolve the credential reference `apiKeyEnv` per request through the DSH credentials service (or the launch environment when no credential service is mounted). The default reference is `OPENCODE_API_KEY` for both products; set a different one per route in settings, e.g. `DSH_OPENCODE_ZEN_API_KEY` / `DSH_OPENCODE_GO_API_KEY`, and store the value through the standard credentials UI (the web Models page) or export it in the launch environment.
|
|
35
|
-
|
|
36
|
-
- A missing or unusable key fails the request with `MISSING_CREDENTIAL`; the plugin never falls back to an unrelated ambient key (`OPENAI_API_KEY`, `GEMINI_API_KEY`, …).
|
|
37
|
-
- Catalog fetches are unauthenticated and share no client with inference; keys never appear in URLs, logs, diagnostics, command output, or the cache.
|
|
38
|
-
- Go requests carry an honest `x-opencode-client: opencode-live/<version>` header and an opaque, stable `x-opencode-session` value per DSH session; the plugin never impersonates OpenCode itself.
|
|
39
|
-
|
|
40
|
-
## How the catalog works
|
|
41
|
-
|
|
42
|
-
Three public sources feed the catalog: the official Zen list, the official Go list, and the Models.dev `api.json` metadata for the `opencode` / `opencode-go` providers. The plugin joins them by exact model id and keeps every candidate visible:
|
|
43
|
-
|
|
44
|
-
| State | Meaning | Selector | Diagnostics |
|
|
45
|
-
|---|---|---|---|
|
|
46
|
-
| `ready` | officially listed with complete metadata and a known wire API | shown | — |
|
|
47
|
-
| `metadata-pending` | officially listed; missing name/context/output/input/api | hidden | shown with the missing fields |
|
|
48
|
-
| `unsupported-protocol` | metadata names an unknown SDK | hidden | shown with the SDK id |
|
|
49
|
-
| `catalog-only` | in Models.dev but absent from the official list | hidden | shown |
|
|
50
|
-
| `removed` | was officially listed before; no longer present | hidden (history keeps the name) | shown |
|
|
20
|
+
## 使い方
|
|
51
21
|
|
|
52
|
-
|
|
22
|
+
1. DSH を再起動すると両ルートが登録される(以降のカタログ更新に再起動は不要)
|
|
23
|
+
2. Web の 設定 > モデル > OpenCode Zen (Live) の「編集」を開き、マスク済み API キー入力欄にキーを貼り付けて保存
|
|
24
|
+
3. モデルセレクターから OpenCode のモデルを選んで使う
|
|
53
25
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
## Commands
|
|
57
|
-
|
|
58
|
-
| Command | Effect |
|
|
26
|
+
| コマンド | 効果 |
|
|
59
27
|
|---|---|
|
|
60
|
-
| `/dsh-opencode
|
|
61
|
-
| `/opencode-refresh [all\|zen\|go]` |
|
|
62
|
-
| `/opencode-status` |
|
|
63
|
-
| `/opencode-models <zen\|go> [--all]` |
|
|
28
|
+
| `/dsh-opencode` | ステータス表示とキー設定場所の案内 |
|
|
29
|
+
| `/opencode-refresh [all\|zen\|go]` | カタログを強制更新 |
|
|
30
|
+
| `/opencode-status` | 更新時刻・エラー・モデル数・キー設定状況 |
|
|
31
|
+
| `/opencode-models <zen\|go> [--all]` | モデル一覧(`--all` で非対応含む) |
|
|
64
32
|
|
|
65
|
-
|
|
33
|
+
## 設定
|
|
66
34
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Namespace `opencode-live` (composition defaults in `cordis.patch.yml`):
|
|
35
|
+
名前空間 `opencode-live`(デフォルトは `cordis.patch.yml` 参照):
|
|
70
36
|
|
|
71
37
|
```yaml
|
|
72
38
|
providers:
|
|
73
39
|
opencode-zen-live:
|
|
74
|
-
product: zen
|
|
75
|
-
apiKeyEnv: OPENCODE_API_KEY
|
|
40
|
+
product: zen
|
|
41
|
+
apiKeyEnv: OPENCODE_API_KEY # 両ルート既定は同一参照
|
|
76
42
|
opencode-go-live:
|
|
77
43
|
product: go
|
|
78
44
|
apiKeyEnv: OPENCODE_API_KEY
|
|
79
45
|
catalog:
|
|
80
|
-
refreshIntervalMs: 900000
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
maxStaleMs: 604800000 # 7 days; staleness display bound
|
|
84
|
-
requireFresh: false # true refuses requests while the official list is stale
|
|
46
|
+
refreshIntervalMs: 900000
|
|
47
|
+
timeoutMs: 15000
|
|
48
|
+
requireFresh: false
|
|
85
49
|
```
|
|
86
50
|
|
|
87
|
-
|
|
51
|
+
- API キーは設定 UI から保存(コマンド引数では受け付けない)。環境変数 `OPENCODE_API_KEY` でも可
|
|
52
|
+
- `cachePath` でキャッシュ位置を変更可能(既定は DSH ホーム下 `cache/opencode-live/catalog.json`)
|
|
88
53
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
## Development
|
|
54
|
+
## 開発
|
|
92
55
|
|
|
93
56
|
```sh
|
|
94
57
|
pnpm install
|
|
95
|
-
pnpm typecheck
|
|
96
|
-
pnpm test # vitest run (offline only; live tests skip)
|
|
97
|
-
pnpm test:live # opt-in: hits the public GET endpoints, no key, no inference
|
|
98
|
-
pnpm build # tsdown -> lib/
|
|
99
|
-
pnpm pack:check # npm pack --dry-run
|
|
58
|
+
pnpm typecheck && pnpm test && pnpm build
|
|
100
59
|
```
|
|
101
|
-
|
|
102
|
-
Tests mount a real Cordis context with the published DSH packages, stub the three catalog URLs, and cover the candidate states, refresh failure classes, wire-API selection, session headers, credential semantics, registry replacement, unload safety, and packaging.
|
|
103
|
-
|
|
104
|
-
## Scope notes
|
|
105
|
-
|
|
106
|
-
- Catalog refreshes never generate inference traffic, and a Go auth/limit error never switches the request to Zen.
|
|
107
|
-
- Model `limit.context` / `limit.output` come only from a validated source; unknown capacities never become fabricated numbers. Source pricing maps into pi-ai's descriptor; absent pricing is the absence of a fact, not a $0 claim.
|
|
108
|
-
- The repository commits the built `lib/` so installation needs no runtime TypeScript compilation.
|
package/lib/commands.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PRODUCT_BY_ROUTE, ROUTE_BY_PRODUCT, describeNonReadyState } from "./normalize.js";
|
|
2
|
-
import { assertUsableApiKey } from "@deepseek-ai/dsh-llm";
|
|
3
2
|
//#region src/commands.ts
|
|
4
3
|
const USAGE_REFRESH = "Usage: /opencode-refresh [all|zen|go]";
|
|
5
4
|
const USAGE_MODELS = "Usage: /opencode-models <zen|go> [--all]";
|
|
5
|
+
const USAGE_ENABLE = "Usage: /dsh-opencode — report status; store the API key through the web Models page";
|
|
6
6
|
/** Whether one date stamp renders as a short local time. */
|
|
7
7
|
function renderTime(timestamp) {
|
|
8
8
|
if (timestamp === void 0) return "never";
|
|
@@ -37,10 +37,6 @@ function parseProduct(raw) {
|
|
|
37
37
|
if (raw === "zen") return "zen";
|
|
38
38
|
if (raw === "go") return "go";
|
|
39
39
|
}
|
|
40
|
-
/** Distinct credential references across the configured routes. */
|
|
41
|
-
function configuredRefs(services) {
|
|
42
|
-
return [...new Set([...services.config().providers.values()].map((provider) => provider.apiKeyEnv))];
|
|
43
|
-
}
|
|
44
40
|
/** Build the three command definitions. */
|
|
45
41
|
function commandDefinitions(ctx, services) {
|
|
46
42
|
return [
|
|
@@ -134,46 +130,24 @@ function commandDefinitions(ctx, services) {
|
|
|
134
130
|
},
|
|
135
131
|
{
|
|
136
132
|
name: "dsh-opencode",
|
|
137
|
-
description: "
|
|
133
|
+
description: "Show OpenCode status and where to store the API key",
|
|
138
134
|
recordInput: false,
|
|
139
135
|
handler: async (invocation) => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
assertUsableApiKey(raw, "opencode-live", "input");
|
|
145
|
-
} catch (error) {
|
|
146
|
-
return {
|
|
147
|
-
kind: "error",
|
|
148
|
-
text: error.message
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
const failures = [];
|
|
152
|
-
for (const ref of configuredRefs(services)) try {
|
|
153
|
-
await services.storeCredential(ref, raw);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
failures.push(`${ref}: ${error.message}`);
|
|
156
|
-
}
|
|
157
|
-
if (failures.length > 0) return {
|
|
158
|
-
kind: "error",
|
|
159
|
-
text: ["Could not store the OpenCode API key:", ...failures].join("\n")
|
|
160
|
-
};
|
|
161
|
-
stored = true;
|
|
162
|
-
}
|
|
136
|
+
if (invocation.rawInput.trim().length > 0) return {
|
|
137
|
+
kind: "error",
|
|
138
|
+
text: [USAGE_ENABLE, "This command never accepts the key as text; paste it into the masked API key input on the web Models page instead."].join("\n")
|
|
139
|
+
};
|
|
163
140
|
const routes = [ROUTE_BY_PRODUCT.zen, ROUTE_BY_PRODUCT.go];
|
|
164
|
-
if (!
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
].join("\n")
|
|
175
|
-
};
|
|
176
|
-
}
|
|
141
|
+
if (!(await Promise.all(routes.map(async (route) => {
|
|
142
|
+
return (await services.describeCredential(route))?.configured === true;
|
|
143
|
+
}))).some((configured) => configured)) return {
|
|
144
|
+
kind: "success",
|
|
145
|
+
text: [
|
|
146
|
+
"No OpenCode API key is configured yet.",
|
|
147
|
+
"Open Settings > Models, edit the OpenCode Zen (Live) provider, and paste the key into its masked API key input.",
|
|
148
|
+
"This command never displays key values."
|
|
149
|
+
].join("\n")
|
|
150
|
+
};
|
|
177
151
|
if (invocation.signal.aborted) return {
|
|
178
152
|
kind: "success",
|
|
179
153
|
text: "Refresh cancelled."
|
|
@@ -184,7 +158,7 @@ function commandDefinitions(ctx, services) {
|
|
|
184
158
|
signal: invocation.signal
|
|
185
159
|
});
|
|
186
160
|
} catch {}
|
|
187
|
-
const lines = [
|
|
161
|
+
const lines = ["OpenCode API key configured. Zen and Go are enabled:"];
|
|
188
162
|
for (const route of routes) lines.push(await productStatus(ctx, services, PRODUCT_BY_ROUTE[route]));
|
|
189
163
|
return {
|
|
190
164
|
kind: "success",
|
package/lib/index.js
CHANGED
|
@@ -154,11 +154,6 @@ function apply(ctx, config = {}) {
|
|
|
154
154
|
const provider = currentConfig.providers.get(route);
|
|
155
155
|
if (provider === void 0) return void 0;
|
|
156
156
|
return describeCredential(ctx, provider.apiKeyEnv);
|
|
157
|
-
},
|
|
158
|
-
storeCredential: async (ref, value) => {
|
|
159
|
-
const credentials = ctx.get("credentials");
|
|
160
|
-
if (credentials === void 0) throw new Error("opencode-live: no credentials service is mounted; export the API key in the launch environment instead");
|
|
161
|
-
await credentials.set(ref, value);
|
|
162
157
|
}
|
|
163
158
|
});
|
|
164
159
|
});
|
package/lib/transport.js
CHANGED
|
@@ -27,7 +27,7 @@ import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.l
|
|
|
27
27
|
*/
|
|
28
28
|
/** The plugin's honest client identification value. */
|
|
29
29
|
const PLUGIN_ID = "opencode-live";
|
|
30
|
-
const PLUGIN_VERSION = "0.1.
|
|
30
|
+
const PLUGIN_VERSION = "0.1.1";
|
|
31
31
|
/** Header OpenCode Go documents for coding-agent session identification. */
|
|
32
32
|
const SESSION_HEADER = "x-opencode-session";
|
|
33
33
|
/** Header carrying this plugin's honest client identity alongside DSH attribution. */
|