dsh-opencode 0.1.4 → 0.1.6

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/lib/commands.js CHANGED
@@ -1,8 +1,44 @@
1
- import { PRODUCT_BY_ROUTE, ROUTE_BY_PRODUCT, describeNonReadyState } from "./normalize.js";
1
+ import { ROUTE_BY_PRODUCT, describeNonReadyState } from "./normalize.js";
2
+ import { setupCancelled, setupHelp, setupSaved, setupUsage } from "./shared/language.js";
3
+ import { hostText } from "./language.js";
2
4
  //#region src/commands.ts
5
+ /**
6
+ * The host commands this plugin registers.
7
+ *
8
+ * `/opencode-refresh` forces a catalog refresh, `/opencode-status` reports
9
+ * source freshness and counts, and `/opencode-models` lists a product's
10
+ * models, including the non-ready ones with their reasons under `--all`.
11
+ *
12
+ * `/dsh-opencode` returns a short setup instruction on the Host. Secret input is
13
+ * owned by Settings > Models and never arrives through command rawInput.
14
+ *
15
+ * @module opencode-live/commands
16
+ */
3
17
  const USAGE_REFRESH = "Usage: /opencode-refresh [all|zen|go]";
4
18
  const USAGE_MODELS = "Usage: /opencode-models <zen|go> [--all]";
5
- const USAGE_ENABLE = "Usage: /dsh-opencode [status|help]";
19
+ /** User-facing setup guidance; detailed diagnostics belong to /opencode-status. */
20
+ async function setupGuidance(services) {
21
+ const labels = ["OpenCode Zen", "OpenCode Go"];
22
+ const results = await Promise.allSettled([services.describeCredential(ROUTE_BY_PRODUCT.zen), services.describeCredential(ROUTE_BY_PRODUCT.go)]);
23
+ const missing = [];
24
+ const unknown = [];
25
+ results.forEach((result, index) => {
26
+ if (result.status === "rejected" || result.value === void 0) unknown.push(labels[index]);
27
+ else if (!result.value.configured) missing.push(labels[index]);
28
+ });
29
+ if (unknown.length > 0) return {
30
+ kind: "error",
31
+ text: `${missing.length > 0 ? `${missing.join(" and ")} API key is not configured.\n` : ""}Could not check the API key settings for ${unknown.join(" and ")}.\n${setupHelp}`
32
+ };
33
+ if (missing.length > 0) return {
34
+ kind: "error",
35
+ text: `${missing.length === 2 ? "OpenCode" : missing[0]} API key is not configured.\n${setupHelp}`
36
+ };
37
+ return {
38
+ kind: "success",
39
+ text: setupSaved
40
+ };
41
+ }
6
42
  /** Whether one date stamp renders as a short local time. */
7
43
  function renderTime(timestamp) {
8
44
  if (timestamp === void 0) return "never";
@@ -130,24 +166,29 @@ function commandDefinitions(ctx, services) {
130
166
  },
131
167
  {
132
168
  name: "dsh-opencode",
133
- description: "Show OpenCode setup and credential status",
169
+ description: hostText(ctx, "OpenCode settings"),
134
170
  recordInput: false,
135
171
  handler: async (invocation) => {
136
172
  const input = invocation.rawInput.trim();
137
173
  if (input !== "" && input !== "status" && input !== "help") return {
138
174
  kind: "error",
139
- text: USAGE_ENABLE
175
+ text: hostText(ctx, setupUsage)
140
176
  };
141
- const routes = [ROUTE_BY_PRODUCT.zen, ROUTE_BY_PRODUCT.go];
142
177
  if (invocation.signal.aborted) return {
143
178
  kind: "success",
144
- text: "Refresh cancelled."
179
+ text: hostText(ctx, setupCancelled)
145
180
  };
146
- const lines = ["OpenCode setup status (use the Client setup form to save a key):"];
147
- for (const route of routes) lines.push(await productStatus(ctx, services, PRODUCT_BY_ROUTE[route]));
148
- return {
181
+ if (input === "help") return {
149
182
  kind: "success",
150
- text: lines.join("\n")
183
+ text: hostText(ctx, setupHelp)
184
+ };
185
+ const result = await setupGuidance(services);
186
+ return invocation.signal.aborted ? {
187
+ kind: "success",
188
+ text: hostText(ctx, setupCancelled)
189
+ } : {
190
+ ...result,
191
+ text: hostText(ctx, result.text ?? "")
151
192
  };
152
193
  }
153
194
  }
@@ -1,3 +1,4 @@
1
+ import { hostText } from "./language.js";
1
2
  import { LlmError, assertUsableApiKey } from "@deepseek-ai/dsh-llm";
2
3
  import "@deepseek-ai/dsh-credentials";
3
4
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
@@ -6,8 +7,8 @@ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
6
7
  * Resolve the API key for one inference call.
7
8
  *
8
9
  * Mirrors the fail-loud reference semantics of the DSH pi-ai adapter: a named
9
- * reference that misses throws `MISSING_CREDENTIAL` naming the route and the
10
- * reference, never a key fragment, and never falls back to an ambient key
10
+ * reference that misses throws `MISSING_CREDENTIAL` with a settings instruction,
11
+ * never a key fragment, and never falls back to an ambient key
11
12
  * another provider might have left in the environment.
12
13
  * @param ctx - the plugin context carrying the optional credential service.
13
14
  * @param route - the live route the credential is resolved for.
@@ -21,7 +22,7 @@ async function resolveApiKeyFor(ctx, route, profile) {
21
22
  const credentials = ctx.get("credentials");
22
23
  const hit = credentials !== void 0 ? (await credentials.resolve(ref))?.value : launchEnvironmentOf(ctx).get(ref)?.value;
23
24
  if (hit !== void 0 && hit.length > 0) return assertUsableApiKey(hit, "opencode-live", ref);
24
- throw new LlmError(`opencode-live: no credential for provider route "${route}"; its profile resolves ${ref}, which is not set store it through the credentials service (the web Models page writes it) or export it in the launching environment`, "MISSING_CREDENTIAL");
25
+ throw new LlmError(`${profile.displayName}: ${hostText(ctx, "The API key is not configured. Enter it in Settings > Models, click \"Save API key\", then send your message again.")}`, "MISSING_CREDENTIAL");
25
26
  }
26
27
  /**
27
28
  * Api-key auth for a route the plugin authenticates itself.
@@ -0,0 +1,8 @@
1
+ import { localePreference, resolveLanguage, translate } from "./shared/language.js";
2
+ //#region src/language.ts
3
+ /** Optional settings must never become a prerequisite for command registration. */
4
+ function hostText(ctx, text) {
5
+ return translate(text, resolveLanguage(localePreference(ctx.get?.("settings")?.get("locale")), process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG));
6
+ }
7
+ //#endregion
8
+ export { hostText };
@@ -0,0 +1,78 @@
1
+ //#region src/shared/language.ts
2
+ /** Explicit DSH preference wins; otherwise use the primary browser/OS locale. */
3
+ function resolveLanguage(preference, fallback) {
4
+ const locale = typeof preference === "string" && preference.trim() ? preference : fallback;
5
+ return /^ja(?:[-_.@]|$)/i.test(locale?.trim() ?? "") ? "ja" : "en";
6
+ }
7
+ function localePreference(value) {
8
+ return typeof value === "object" && value !== null && "preference" in value ? value.preference : void 0;
9
+ }
10
+ const setupHelp = [
11
+ "1. Open Settings > Models.",
12
+ "2. Enter your API key in the OpenCode provider you want to use.",
13
+ "3. Click \"Save API key\"."
14
+ ].join("\n");
15
+ const setupUsage = "Enter your API key in Settings > Models, not in chat.";
16
+ const setupSaved = "Your OpenCode API key is saved. Choose an OpenCode model from the model picker at the bottom right of the chat.";
17
+ const setupCancelled = "The check was cancelled.";
18
+ /** English source copy is the key. Only our known copy is translated. */
19
+ const japanese = {
20
+ "OpenCode settings": "OpenCode の設定",
21
+ "Close": "閉じる",
22
+ "OpenCode API key": "OpenCode APIキー",
23
+ "Key saved — enter a new key to replace it": "保存済みです。変更する場合は新しいキーを入力してください",
24
+ "Paste your OpenCode API key": "OpenCodeのAPIキーを貼り付けてください",
25
+ "This key is shared by OpenCode Zen and Go. Deleting it removes the key for both providers.": "このキーはOpenCode ZenとGoで共有しています。削除すると両方のキー設定が解除されます。",
26
+ "Checking credential status…": "APIキーの設定を確認しています…",
27
+ "Saving…": "保存中…",
28
+ "Save API key": "APIキーを保存",
29
+ "Deleting…": "削除中…",
30
+ "Delete API key": "APIキーを削除",
31
+ "Clear input": "入力を消去",
32
+ "The credential check was cancelled.": "APIキーの確認をキャンセルしました。",
33
+ "The configured credential reference is unavailable.": "APIキーの保存先を確認できませんでした。",
34
+ "Could not check whether an API key is saved.": "APIキーが保存されているか確認できませんでした。",
35
+ "Could not load settings or credential status.": "設定またはAPIキーの状態を読み込めませんでした。",
36
+ "The settings form was closed.": "設定画面が閉じられました。",
37
+ "The settings form was closed. Open it again and retry.": "設定画面が閉じられました。開き直して再試行してください。",
38
+ "The API key settings changed. Check which providers share the key and retry.": "APIキーの設定が変更されました。キーを共有するプロバイダーを確認して再試行してください。",
39
+ "This credential is read-only. Remove it from the environment that launches DSH.": "このAPIキーは読み取り専用です。DSHを起動する環境の設定から削除してください。",
40
+ "This credential is already being updated.": "このAPIキーは更新中です。完了後に再試行してください。",
41
+ "Could not delete the API key. Try again.": "APIキーを削除できませんでした。再試行してください。",
42
+ "The deletion request succeeded, but the key status could not be confirmed. Reload Settings > Models.": "削除リクエストは成功しましたが、キーの状態を確認できませんでした。Settings > Modelsを開き直してください。",
43
+ "The saved key was removed, but an API key is still configured. Check its source in Settings > Models.": "保存したキーは削除されましたが、別のAPIキー設定が残っています。Settings > Modelsで設定元を確認してください。",
44
+ "API key deleted for OpenCode Zen and Go.": "OpenCode ZenとGoの共有APIキーを削除しました。",
45
+ "API key deleted.": "APIキーを削除しました。",
46
+ "Paste the API key only, without quotes or an environment-variable assignment.": "引用符や環境変数の代入式を含めず、APIキーだけを貼り付けてください。",
47
+ "The credential reference changed. Reload its status and retry.": "APIキーの保存先が変更されました。状態を読み込み直して再試行してください。",
48
+ "This credential is read-only. Update it in the environment that launches DSH.": "このAPIキーは読み取り専用です。DSHを起動する環境の設定で変更してください。",
49
+ "Could not save the API key.": "APIキーを保存できませんでした。",
50
+ "The API key is not configured. Enter it in Settings > Models, click \"Save API key\", then send your message again.": "APIキーが未設定です。Settings > ModelsでAPIキーを入力して「APIキーを保存」を押してから、もう一度送信してください。",
51
+ "API key saved.": "APIキーを保存しました。",
52
+ "The key was saved, but its status could not be confirmed.": "APIキーを保存しましたが、保存後の状態を確認できませんでした。",
53
+ "1. Open Settings > Models.": "1. Settings > Modelsを開きます。",
54
+ "2. Enter your API key in the OpenCode provider you want to use.": "2. 使用するOpenCodeの欄にAPIキーを入力します。",
55
+ "3. Click \"Save API key\".": "3. 「APIキーを保存」を押してください。",
56
+ [setupUsage]: "APIキーはチャットに入力せず、Settings > Modelsから設定してください。",
57
+ [setupSaved]: "OpenCodeのAPIキーは保存されています。チャット右下のモデル選択から、使いたいOpenCodeのモデルを選んでください。",
58
+ [setupCancelled]: "確認をキャンセルしました。"
59
+ };
60
+ for (const label of [
61
+ "OpenCode",
62
+ "OpenCode Zen",
63
+ "OpenCode Go",
64
+ "OpenCode Zen and OpenCode Go"
65
+ ]) {
66
+ const jaLabel = label.replace(" and ", "・");
67
+ japanese[`${label} API key is not configured.`] = `${jaLabel}のAPIキーが未設定です。`;
68
+ japanese[`Could not check the API key settings for ${label}.`] = `${jaLabel}のAPIキー設定を確認できませんでした。`;
69
+ }
70
+ const english = new Map(Object.entries(japanese).map(([en, ja]) => [ja, en]));
71
+ function translate(text, language) {
72
+ return text.split("\n").map((line) => {
73
+ const source = english.get(line) ?? line;
74
+ return language === "ja" ? japanese[source] ?? line : source;
75
+ }).join("\n");
76
+ }
77
+ //#endregion
78
+ export { localePreference, resolveLanguage, setupCancelled, setupHelp, setupSaved, setupUsage, translate };
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.3";
30
+ const PLUGIN_VERSION = "0.1.5";
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Out-of-tree DSH bundle with live dynamic OpenCode Zen / Go model catalogs",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -45,16 +45,11 @@
45
45
  "platform": "web",
46
46
  "inject": [
47
47
  "@deepseek-ai/dsh-api-remotes",
48
- "@deepseek-ai/dsh-commands",
49
- "@deepseek-ai/dsh-client-locale",
50
- "@deepseek-ai/dsh-client-ui-layout",
51
- "@deepseek-ai/dsh-client-ui-commands",
52
48
  "@deepseek-ai/dsh-client-ui-settings",
53
49
  "@deepseek-ai/dsh-client-ui-settings-models",
54
- "@deepseek-ai/dsh-client-ui-primitives",
55
- "@deepseek-ai/dsh-client-ui-slots",
56
50
  "@deepseek-ai/dsh-client-ui-renderer",
57
- "@deepseek-ai/dsh-client-store"
51
+ "@deepseek-ai/dsh-client-ui-layout",
52
+ "@deepseek-ai/dsh-client-ui-commands"
58
53
  ]
59
54
  }
60
55
  },