dsh-opencode-go 0.1.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/lib/client.js ADDED
@@ -0,0 +1,853 @@
1
+ window.__ModuleLoader__.load({ id: "dsh-opencode-go", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/client/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ OPENCODE_GO_NS: () => OPENCODE_GO_NS,
25
+ apply: () => apply,
26
+ inject: () => inject
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/client/Section.tsx
31
+ var import_react = require("react");
32
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
33
+
34
+ // plugin-css:/Users/tanglei/Desktop/dsh-opencode-go/src/client/Section.module.css
35
+ var id = "dsh-opencode-go/settings";
36
+ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id) + "]")) {
37
+ const style = document.createElement("style");
38
+ style.dataset.plugin = "dsh-opencode-go";
39
+ style.dataset.pluginCss = id;
40
+ style.textContent = ".BtKYva_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.BtKYva_field+.BtKYva_field{border-top:.5px solid var(--dsw-alias-border-l2)}.BtKYva_head{align-items:center;gap:8px;display:flex}.BtKYva_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.BtKYva_badges{align-items:center;gap:8px;display:inline-flex}.BtKYva_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.BtKYva_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.BtKYva_reset:disabled{cursor:default}.BtKYva_input{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.BtKYva_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.BtKYva_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.BtKYva_inputInvalid{border-color:var(--dsw-alias-label-error);}.BtKYva_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.BtKYva_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.BtKYva_intro{color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-size:13px;line-height:1.5}.BtKYva_disclosure{font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;margin:0;padding:0;display:flex}.BtKYva_disclosure:hover .BtKYva_label{color:var(--dsw-alias-brand-primary)}.BtKYva_chevron,.BtKYva_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none}.BtKYva_chevron{transform:rotate(-90deg)}.BtKYva_advanced{flex-direction:column;gap:4px;padding:4px 0 0 22px;display:flex}.BtKYva_advanced .BtKYva_field+.BtKYva_field{border-top:none}.BtKYva_chips{flex-wrap:wrap;gap:6px;display:flex}.BtKYva_actions{align-items:center;gap:8px;padding-top:12px;display:flex}.BtKYva_failedNote{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}";
41
+ document.head.appendChild(style);
42
+ }
43
+ var Section_default = { "reset": "BtKYva_reset", "chevronOpen": "BtKYva_chevronOpen", "advanced": "BtKYva_advanced", "head": "BtKYva_head", "hint": "BtKYva_hint", "chevron": "BtKYva_chevron", "actions": "BtKYva_actions", "label": "BtKYva_label", "badges": "BtKYva_badges", "input": "BtKYva_input", "invalid": "BtKYva_invalid", "disclosure": "BtKYva_disclosure", "inputInvalid": "BtKYva_inputInvalid", "failedNote": "BtKYva_failedNote", "chips": "BtKYva_chips", "field": "BtKYva_field", "intro": "BtKYva_intro" };
44
+
45
+ // src/client/Section.tsx
46
+ var import_jsx_runtime = require("react/jsx-runtime");
47
+ function ValueField(props) {
48
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.field, children: [
49
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.head, children: [
50
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: Section_default.label, htmlFor: props.id, children: props.label }),
51
+ props.field.overridden ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: Section_default.badges, children: [
52
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "neutral", children: props.overriddenLabel }),
53
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54
+ "button",
55
+ {
56
+ type: "button",
57
+ className: Section_default.reset,
58
+ disabled: props.disabled,
59
+ onClick: props.onReset,
60
+ children: props.resetLabel
61
+ }
62
+ )
63
+ ] }) : null
64
+ ] }),
65
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
66
+ "input",
67
+ {
68
+ id: props.id,
69
+ className: props.field.invalid ? Section_default.inputInvalid : Section_default.input,
70
+ type: "text",
71
+ ...props.numeric === true ? { inputMode: "numeric" } : {},
72
+ ...props.field.invalid ? { "aria-invalid": true } : {},
73
+ value: props.field.text,
74
+ disabled: props.disabled,
75
+ onChange: (event) => {
76
+ props.onEdit(event.target.value);
77
+ }
78
+ }
79
+ ),
80
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: props.field.invalid ? Section_default.invalid : Section_default.hint, children: props.field.invalid ? props.invalidLabel : props.hint })
81
+ ] });
82
+ }
83
+ function ModelsBody({ models, t }) {
84
+ if (models.status === "failed") {
85
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
86
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.failedNote, children: t("modelsFailed") }),
87
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: models.message })
88
+ ] });
89
+ }
90
+ if (models.status !== "ready") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("modelsLoading") });
91
+ if (models.count === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("modelsEmpty") });
92
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: Section_default.chips, children: models.preview.map((name) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "outline", children: name }, name)) });
93
+ }
94
+ function OpencodeGoSection(props) {
95
+ const { useOpencodeGo, edit, resetField, save, discard, loadModels, setEnabled, t } = props;
96
+ if (useOpencodeGo === void 0 || edit === void 0 || resetField === void 0 || save === void 0 || discard === void 0 || loadModels === void 0 || setEnabled === void 0 || t === void 0) return null;
97
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
98
+ Loaded,
99
+ {
100
+ state: useOpencodeGo((snapshot) => snapshot),
101
+ t,
102
+ edit,
103
+ resetField,
104
+ save,
105
+ discard,
106
+ loadModels,
107
+ setEnabled
108
+ }
109
+ );
110
+ }
111
+ function Loaded(props) {
112
+ const { t, state, loadModels } = props;
113
+ const [advanced, setAdvanced] = (0, import_react.useState)(false);
114
+ (0, import_react.useEffect)(() => {
115
+ if (state.available && state.models.status === "idle") loadModels();
116
+ }, [state.available, state.models.status, loadModels]);
117
+ if (!state.available) {
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.intro, children: t("unavailable") });
119
+ }
120
+ const disabled = !state.writable;
121
+ const fieldProps = {
122
+ invalidLabel: t("invalidValue"),
123
+ overriddenLabel: t("overridden"),
124
+ resetLabel: t("reset"),
125
+ disabled
126
+ };
127
+ const advancedOverridden = state.apiKeyEnv.overridden || state.baseURL.overridden || state.refreshMinutes.overridden || state.streamIdleTimeoutMs.overridden || state.maxRequestImageBytes.overridden || state.requestImagePixelBudget.overridden || state.requestImageMaxBytes.overridden;
128
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.intro, children: t("intro") }),
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.field, children: [
131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.head, children: [
132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.label, children: t("enabledLabel") }),
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
134
+ import_dsh_client_ui_primitives.Switch,
135
+ {
136
+ checked: state.enabled,
137
+ label: t("enabledLabel"),
138
+ disabled,
139
+ onChange: props.setEnabled
140
+ }
141
+ )
142
+ ] }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: state.enabled ? t("enabledHint") : t("enabledOff") })
144
+ ] }),
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.field, children: [
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.head, children: [
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: Section_default.label, htmlFor: "opencode-go-key", children: t("keyLabel") }),
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.badges, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: state.apiKeyConfigured ? "success" : "warning", children: state.apiKeyConfigured ? t("keyConfigured") : t("keyMissing") }) })
149
+ ] }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
151
+ "input",
152
+ {
153
+ id: "opencode-go-key",
154
+ className: Section_default.input,
155
+ type: "password",
156
+ autoComplete: "off",
157
+ value: state.apiKey.text,
158
+ disabled: !state.apiKeyWritable,
159
+ onChange: (event) => {
160
+ props.edit("apiKey", event.target.value);
161
+ }
162
+ }
163
+ ),
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: state.apiKeyWritable ? t("keyHint") : t("keyNotWritable") })
165
+ ] }),
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.field, children: [
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.head, children: [
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.label, children: t("modelsLabel") }),
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: Section_default.badges, children: [
170
+ state.models.status === "ready" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "neutral", children: t("modelsCount", { count: state.models.count }) }) : null,
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
172
+ "button",
173
+ {
174
+ type: "button",
175
+ className: Section_default.reset,
176
+ disabled: state.models.status === "loading",
177
+ onClick: loadModels,
178
+ children: t("modelsRefresh")
179
+ }
180
+ )
181
+ ] })
182
+ ] }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModelsBody, { models: state.models, t })
184
+ ] }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.field, children: [
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
187
+ "button",
188
+ {
189
+ type: "button",
190
+ className: Section_default.disclosure,
191
+ "aria-expanded": advanced,
192
+ "aria-controls": "opencode-go-advanced",
193
+ onClick: () => {
194
+ setAdvanced(!advanced);
195
+ },
196
+ children: [
197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutline14, { className: advanced ? Section_default.chevronOpen : Section_default.chevron }),
198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Section_default.label, children: t("advancedLabel") }),
199
+ advancedOverridden ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Tag, { tone: "neutral", children: t("overridden") }) : null
200
+ ]
201
+ }
202
+ ),
203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("advancedHint") }),
204
+ advanced ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { id: "opencode-go-advanced", className: Section_default.advanced, children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
206
+ ValueField,
207
+ {
208
+ id: "opencode-go-api-key-env",
209
+ label: t("apiKeyEnvLabel"),
210
+ hint: t("apiKeyEnvHint"),
211
+ field: state.apiKeyEnv,
212
+ ...fieldProps,
213
+ onEdit: (text) => {
214
+ props.edit("apiKeyEnv", text);
215
+ },
216
+ onReset: () => {
217
+ props.resetField("apiKeyEnv");
218
+ }
219
+ }
220
+ ),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
222
+ ValueField,
223
+ {
224
+ id: "opencode-go-base-url",
225
+ label: t("baseURLLabel"),
226
+ hint: t("baseURLHint"),
227
+ field: state.baseURL,
228
+ ...fieldProps,
229
+ onEdit: (text) => {
230
+ props.edit("baseURL", text);
231
+ },
232
+ onReset: () => {
233
+ props.resetField("baseURL");
234
+ }
235
+ }
236
+ ),
237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
238
+ ValueField,
239
+ {
240
+ id: "opencode-go-refresh-minutes",
241
+ label: t("refreshMinutesLabel"),
242
+ hint: t("refreshMinutesHint"),
243
+ field: state.refreshMinutes,
244
+ numeric: true,
245
+ ...fieldProps,
246
+ onEdit: (text) => {
247
+ props.edit("refreshMinutes", text);
248
+ },
249
+ onReset: () => {
250
+ props.resetField("refreshMinutes");
251
+ }
252
+ }
253
+ ),
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
255
+ ValueField,
256
+ {
257
+ id: "opencode-go-stream-idle",
258
+ label: t("streamIdleTimeoutMsLabel"),
259
+ hint: t("streamIdleTimeoutMsHint"),
260
+ field: state.streamIdleTimeoutMs,
261
+ numeric: true,
262
+ ...fieldProps,
263
+ onEdit: (text) => {
264
+ props.edit("streamIdleTimeoutMs", text);
265
+ },
266
+ onReset: () => {
267
+ props.resetField("streamIdleTimeoutMs");
268
+ }
269
+ }
270
+ ),
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
272
+ ValueField,
273
+ {
274
+ id: "opencode-go-max-request-image-bytes",
275
+ label: t("maxRequestImageBytesLabel"),
276
+ hint: t("maxRequestImageBytesHint"),
277
+ field: state.maxRequestImageBytes,
278
+ numeric: true,
279
+ ...fieldProps,
280
+ onEdit: (text) => {
281
+ props.edit("maxRequestImageBytes", text);
282
+ },
283
+ onReset: () => {
284
+ props.resetField("maxRequestImageBytes");
285
+ }
286
+ }
287
+ ),
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
289
+ ValueField,
290
+ {
291
+ id: "opencode-go-image-pixel-budget",
292
+ label: t("requestImagePixelBudgetLabel"),
293
+ hint: t("requestImagePixelBudgetHint"),
294
+ field: state.requestImagePixelBudget,
295
+ numeric: true,
296
+ ...fieldProps,
297
+ onEdit: (text) => {
298
+ props.edit("requestImagePixelBudget", text);
299
+ },
300
+ onReset: () => {
301
+ props.resetField("requestImagePixelBudget");
302
+ }
303
+ }
304
+ ),
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
306
+ ValueField,
307
+ {
308
+ id: "opencode-go-image-max-bytes",
309
+ label: t("requestImageMaxBytesLabel"),
310
+ hint: t("requestImageMaxBytesHint"),
311
+ field: state.requestImageMaxBytes,
312
+ numeric: true,
313
+ ...fieldProps,
314
+ onEdit: (text) => {
315
+ props.edit("requestImageMaxBytes", text);
316
+ },
317
+ onReset: () => {
318
+ props.resetField("requestImageMaxBytes");
319
+ }
320
+ }
321
+ )
322
+ ] }) : null
323
+ ] }),
324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: Section_default.actions, children: [
325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", size: "md", disabled: disabled || !state.dirty || state.invalid || state.saving, onClick: props.save, children: state.saving ? t("saving") : t("save") }),
326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "outline", size: "md", disabled: disabled || !state.dirty || state.saving, onClick: props.discard, children: t("discard") }),
327
+ state.failed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.failedNote, children: t("savedFailed") }) : null
328
+ ] }),
329
+ disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: Section_default.hint, children: t("readOnly") }) : null
330
+ ] });
331
+ }
332
+
333
+ // src/client/staged-form.ts
334
+ var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
335
+ function numberField(field) {
336
+ return {
337
+ field,
338
+ format: (value) => typeof value === "number" ? String(value) : "",
339
+ parse: (text) => {
340
+ const trimmed = text.trim();
341
+ if (trimmed === "") return { kind: "clear" };
342
+ const parsed = Number(trimmed);
343
+ return Number.isFinite(parsed) ? { kind: "set", value: parsed } : void 0;
344
+ }
345
+ };
346
+ }
347
+ function booleanField(field) {
348
+ return {
349
+ field,
350
+ format: (value) => typeof value === "boolean" ? String(value) : "",
351
+ parse: (text) => {
352
+ if (text === "true") return { kind: "set", value: true };
353
+ if (text === "false") return { kind: "set", value: false };
354
+ return void 0;
355
+ }
356
+ };
357
+ }
358
+ function textField(field) {
359
+ return {
360
+ field,
361
+ format: (value) => typeof value === "string" ? value : "",
362
+ parse: (text) => {
363
+ const trimmed = text.trim();
364
+ return trimmed === "" ? { kind: "clear" } : { kind: "set", value: trimmed };
365
+ }
366
+ };
367
+ }
368
+ var StagedForm = class {
369
+ /**
370
+ * @param scope - the bound settings scope for this page's namespace.
371
+ * @param specs - the section fields this page edits.
372
+ * @param secrets - the page's write-only controls, written outside the section.
373
+ */
374
+ constructor(scope, specs, secrets = []) {
375
+ this.scope = scope;
376
+ this.specs = new Map(specs.map((spec) => [spec.field, spec]));
377
+ this.secretSpecs = new Map(secrets.map((spec) => [spec.field, spec]));
378
+ scope.subscribe(() => {
379
+ this.publish();
380
+ });
381
+ }
382
+ specs;
383
+ secretSpecs;
384
+ staged = /* @__PURE__ */ new Map();
385
+ listeners = /* @__PURE__ */ new Set();
386
+ saving = false;
387
+ failed = false;
388
+ /**
389
+ * Publish a projection of this form, rebuilt whenever the scope or a draft changes.
390
+ * @param project - build the page state from the form's current reads.
391
+ * @returns the store the component reads through its bound selector.
392
+ */
393
+ bind(project) {
394
+ const store = (0, import_dsh_client_store.createSnapshotStore)(project());
395
+ this.listeners.add(() => {
396
+ store.set(project());
397
+ });
398
+ return store;
399
+ }
400
+ /**
401
+ * Read the page-level state: what the Host serves, and what a save would do.
402
+ * @returns the form state the page shell shares.
403
+ */
404
+ shell() {
405
+ const snapshot = this.scope.getSnapshot();
406
+ const plan = this.plan();
407
+ return {
408
+ available: snapshot.status === "ready",
409
+ writable: snapshot.writable,
410
+ dirty: plan.length > 0,
411
+ invalid: plan.some((item) => item.run === void 0),
412
+ saving: this.saving,
413
+ failed: this.failed
414
+ };
415
+ }
416
+ /**
417
+ * Read one control's state.
418
+ * @param field - field name of a section field or of a write-only control.
419
+ * @returns the draft text, whether a save would leave an override, and whether it is invalid.
420
+ */
421
+ field(field) {
422
+ const staged = this.staged.get(field);
423
+ if (this.secretSpecs.has(field)) {
424
+ return { text: staged?.text ?? "", overridden: false, invalid: false };
425
+ }
426
+ const spec = this.spec(field);
427
+ if (staged === void 0) {
428
+ return { text: spec.format(this.sectionValue(field)), overridden: this.stored(field), invalid: false };
429
+ }
430
+ const write = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
431
+ return {
432
+ text: staged.text,
433
+ overridden: write?.kind === "set",
434
+ invalid: write === void 0
435
+ };
436
+ }
437
+ /**
438
+ * Build the edit, reset, save, and discard actions bound to this form.
439
+ * @returns the actions the page's slot entry injects.
440
+ */
441
+ actions() {
442
+ return {
443
+ edit: (field, text) => {
444
+ this.stage(field, { text, clear: false });
445
+ },
446
+ resetField: (field) => {
447
+ this.stage(field, { text: this.spec(field).format(this.baseValue(field)), clear: true });
448
+ },
449
+ save: () => {
450
+ void this.save();
451
+ },
452
+ discard: () => {
453
+ if (this.staged.size === 0 && !this.failed) return;
454
+ this.staged.clear();
455
+ this.failed = false;
456
+ this.publish();
457
+ }
458
+ };
459
+ }
460
+ /**
461
+ * Write every staged edit, then re-seed from what the Host accepted.
462
+ *
463
+ * The Host is the only authority on whether a value was accepted — its
464
+ * validators own the constraints no schema can express — so the outcome is
465
+ * read back from the section rather than predicted here. A save that did not
466
+ * land keeps its drafts, so the user can correct them instead of retyping.
467
+ */
468
+ async save() {
469
+ const plan = this.plan();
470
+ const writes = plan.flatMap((item) => item.run === void 0 ? [] : [item.run]);
471
+ if (plan.length === 0 || this.saving || writes.length !== plan.length) return;
472
+ this.saving = true;
473
+ this.failed = false;
474
+ this.publish();
475
+ let landed = true;
476
+ for (const write of writes) {
477
+ landed = await write() && landed;
478
+ }
479
+ if (landed) this.staged.clear();
480
+ this.saving = false;
481
+ this.failed = !landed;
482
+ this.publish();
483
+ }
484
+ /**
485
+ * Every staged edit a save would write. An entry whose draft is not a value
486
+ * its field accepts carries no write: the form is still dirty, and the save
487
+ * refuses rather than dropping the edit.
488
+ * @returns the planned writes, in the order the fields were staged.
489
+ */
490
+ plan() {
491
+ const plan = [];
492
+ for (const [field, staged] of this.staged) {
493
+ const secret = this.secretSpecs.get(field);
494
+ if (secret !== void 0) {
495
+ const value = staged.text.trim();
496
+ if (value !== "") plan.push({ field, run: () => secret.write(value) });
497
+ continue;
498
+ }
499
+ const spec = this.spec(field);
500
+ if (staged.clear) {
501
+ if (this.stored(field)) plan.push({ field, run: () => this.clear(field) });
502
+ continue;
503
+ }
504
+ if (staged.text === spec.format(this.sectionValue(field))) continue;
505
+ const write = spec.parse(staged.text);
506
+ if (write === void 0) plan.push({ field, run: void 0 });
507
+ else if (write.kind === "clear") plan.push({ field, run: () => this.clear(field) });
508
+ else plan.push({ field, run: () => this.store(field, write.value) });
509
+ }
510
+ return plan;
511
+ }
512
+ async clear(field) {
513
+ await this.scope.unset(field);
514
+ return !this.stored(field);
515
+ }
516
+ async store(field, value) {
517
+ await this.scope.set(field, value);
518
+ return this.userLayer()?.[field] === value;
519
+ }
520
+ stage(field, edit) {
521
+ this.staged.set(field, edit);
522
+ this.failed = false;
523
+ this.publish();
524
+ }
525
+ spec(field) {
526
+ const spec = this.specs.get(field);
527
+ if (spec === void 0) throw new Error(`opencode-go settings page has no field ${field}`);
528
+ return spec;
529
+ }
530
+ snapshotOf() {
531
+ return this.scope.getSnapshot();
532
+ }
533
+ sectionValue(field) {
534
+ return this.snapshotOf().value?.[field];
535
+ }
536
+ baseValue(field) {
537
+ return this.snapshotOf().base?.[field];
538
+ }
539
+ userLayer() {
540
+ return this.snapshotOf().user;
541
+ }
542
+ stored(field) {
543
+ const user = this.userLayer();
544
+ return user !== void 0 && Object.hasOwn(user, field);
545
+ }
546
+ publish() {
547
+ for (const listener of this.listeners) listener();
548
+ }
549
+ };
550
+
551
+ // src/client/section-controller.ts
552
+ var OPENCODE_GO_NS = "llm-opencode-go";
553
+ var DEFAULT_API_KEY_REF = "OPENCODE_API_KEY";
554
+ var API_KEY_FIELD = "apiKey";
555
+ var OPENCODE_GO_PROVIDER = "opencode-go";
556
+ var MODEL_PREVIEW_LIMIT = 6;
557
+ var OpencodeGoSectionController = class {
558
+ /**
559
+ * @param scope - the bound settings scope for the `llm-opencode-go` namespace.
560
+ * @param ctx - the page plugin's context, whose `remote.credentials` namespace
561
+ * answers for the credential the section references.
562
+ */
563
+ constructor(scope, ctx) {
564
+ this.scope = scope;
565
+ this.ctx = ctx;
566
+ this.form = new StagedForm(
567
+ scope,
568
+ [
569
+ // Present so the shared override/reset machinery tracks the field; the
570
+ // page's switch writes it directly instead of staging it.
571
+ booleanField("enabled"),
572
+ textField("apiKeyEnv"),
573
+ textField("baseURL"),
574
+ numberField("refreshMinutes"),
575
+ numberField("streamIdleTimeoutMs"),
576
+ numberField("maxRequestImageBytes"),
577
+ numberField("requestImagePixelBudget"),
578
+ numberField("requestImageMaxBytes")
579
+ ],
580
+ [{ field: API_KEY_FIELD, write: (text) => this.writeKey(text) }]
581
+ );
582
+ this.store = this.form.bind(() => this.projection());
583
+ scope.subscribe(() => {
584
+ void this.readCredential();
585
+ });
586
+ void this.readCredential();
587
+ }
588
+ form;
589
+ store;
590
+ credential = { ref: "", configured: false, writable: true };
591
+ models = { status: "idle" };
592
+ modelsRequest = 0;
593
+ face;
594
+ projection() {
595
+ return {
596
+ ...this.form.shell(),
597
+ enabled: this.enabled(),
598
+ apiKeyEnv: this.form.field("apiKeyEnv"),
599
+ baseURL: this.form.field("baseURL"),
600
+ refreshMinutes: this.form.field("refreshMinutes"),
601
+ streamIdleTimeoutMs: this.form.field("streamIdleTimeoutMs"),
602
+ maxRequestImageBytes: this.form.field("maxRequestImageBytes"),
603
+ requestImagePixelBudget: this.form.field("requestImagePixelBudget"),
604
+ requestImageMaxBytes: this.form.field("requestImageMaxBytes"),
605
+ apiKey: this.form.field(API_KEY_FIELD),
606
+ apiKeyConfigured: this.credential.configured,
607
+ apiKeyWritable: this.credential.writable,
608
+ models: this.models
609
+ };
610
+ }
611
+ /**
612
+ * The adapter's effective switch state: the resolved section's value, over
613
+ * the Host's own default when the section carries none.
614
+ * @returns whether the route is currently served.
615
+ */
616
+ enabled() {
617
+ return this.scope.getSnapshot().value?.enabled ?? true;
618
+ }
619
+ /**
620
+ * Flip the switch by writing the field on the click itself.
621
+ *
622
+ * This is the one control on the page that does not wait for Save: the point
623
+ * of turning it off is to watch the models leave the pickers, and the point
624
+ * of turning it back on is to use the route again — staging either behind a
625
+ * second gesture would report a state the Host does not hold. The write is
626
+ * revision-fenced by the scope like every other, and a refusal surfaces as a
627
+ * failed save through the shared shell rather than a silent revert.
628
+ * @param next - the state the switch asks for.
629
+ */
630
+ setEnabled(next) {
631
+ void this.scope.set("enabled", next);
632
+ }
633
+ /**
634
+ * Read the gateway's model listing through the Host's discovery for this
635
+ * adapter. Called when the page mounts and again from its refresh control.
636
+ * A rejection settles as a failure too: the refresh control is disabled
637
+ * while loading and the next read starts only from `idle`, so leaving the
638
+ * state loading would strand the page with no way to ask the gateway again.
639
+ */
640
+ loadModels() {
641
+ const request = ++this.modelsRequest;
642
+ this.models = { status: "loading" };
643
+ this.store.set(this.projection());
644
+ void this.ctx.remote.llm.discoverModels(OPENCODE_GO_NS, { provider: OPENCODE_GO_PROVIDER }).then((response) => {
645
+ if (request !== this.modelsRequest) return;
646
+ this.models = response.ok ? {
647
+ status: "ready",
648
+ count: response.value.length,
649
+ preview: response.value.slice(0, MODEL_PREVIEW_LIMIT).map((model) => model.name ?? model.id)
650
+ } : { status: "failed", message: response.error.message };
651
+ this.store.set(this.projection());
652
+ }).catch((error) => {
653
+ if (request !== this.modelsRequest) return;
654
+ this.models = { status: "failed", message: error instanceof Error ? error.message : String(error) };
655
+ this.store.set(this.projection());
656
+ });
657
+ }
658
+ /**
659
+ * Ask the credentials domain about the reference the section currently names.
660
+ *
661
+ * The answer is stored with the reference it describes: `apiKeyEnv` can
662
+ * change between the request and its response, and two reads can settle out
663
+ * of order, so a response is published only while it still answers for the
664
+ * reference in force.
665
+ */
666
+ async readCredential() {
667
+ const ref = refOf(this.scope.getSnapshot());
668
+ if (ref !== this.credential.ref) {
669
+ this.credential = { ref, configured: false, writable: true };
670
+ this.store.set(this.projection());
671
+ }
672
+ const response = await this.ctx.remote.credentials.describe([ref]);
673
+ if (!response.ok || ref !== refOf(this.scope.getSnapshot())) return;
674
+ const view = response.value[ref];
675
+ const next = {
676
+ ref,
677
+ configured: view?.configured ?? false,
678
+ // An unknown reference is treated as writable: the control stays usable
679
+ // and the Host is what refuses, rather than the page guessing a refusal.
680
+ writable: view?.writable ?? true
681
+ };
682
+ if (next.configured === this.credential.configured && next.writable === this.credential.writable) return;
683
+ this.credential = next;
684
+ this.store.set(this.projection());
685
+ }
686
+ /**
687
+ * Re-read after the Host reports a change to the reference this page watches.
688
+ *
689
+ * A key can be written from somewhere else — the Models page addresses the
690
+ * same reference — and the settings section does not change when it is, so
691
+ * without this the badge keeps reporting a state the Host already replaced.
692
+ * @param ref - the reference the Host reports as changed.
693
+ */
694
+ refreshCredential(ref) {
695
+ if (ref !== this.credential.ref) return;
696
+ void this.readCredential();
697
+ }
698
+ /**
699
+ * Build the face the page's slot registration injects. Built once: the store
700
+ * is what changes, and the renderer binds the same callbacks across renders.
701
+ * @returns the page snapshot and its form actions.
702
+ */
703
+ inject() {
704
+ this.face ??= {
705
+ hooks: { opencodeGo: this.store },
706
+ loadModels: () => {
707
+ this.loadModels();
708
+ },
709
+ setEnabled: (next) => {
710
+ this.setEnabled(next);
711
+ },
712
+ ...this.form.actions()
713
+ };
714
+ return this.face;
715
+ }
716
+ /**
717
+ * Write the staged key, then re-read whether the Host now holds one.
718
+ * @param value - the staged credential literal.
719
+ * @returns whether the Host reports a configured credential afterwards.
720
+ */
721
+ async writeKey(value) {
722
+ await this.ctx.remote.credentials.set(refOf(this.scope.getSnapshot()), value);
723
+ await this.readCredential();
724
+ return this.credential.configured;
725
+ }
726
+ };
727
+ function refOf(snapshot) {
728
+ const declared = snapshot.value?.apiKeyEnv;
729
+ return declared !== void 0 && declared.length > 0 ? declared : DEFAULT_API_KEY_REF;
730
+ }
731
+
732
+ // src/client/locales.ts
733
+ var en = {
734
+ nav: "OpenCode Go",
735
+ title: "OpenCode Go",
736
+ intro: "Use your OpenCode Go subscription in the harness. Paste the API key below and save; the models the gateway currently serves are listed underneath.",
737
+ enabledLabel: "Enable OpenCode Go",
738
+ enabledHint: "While this is on, OpenCode Go appears in every model picker and can serve requests. Turning it off withdraws the provider and its models immediately; this page stays reachable so you can turn it back on.",
739
+ enabledOff: "OpenCode Go is off. Its models are withdrawn from every picker and no request reaches the gateway. Turn the switch back on to use it again.",
740
+ enabledUnavailable: "This deployment pins the switch on in its composition, so it cannot be changed here.",
741
+ keyLabel: "API key",
742
+ keyHint: "From your OpenCode Go subscription (opencode.ai). Leaving this blank keeps the key already saved, and a saved key applies to the next request without a restart.",
743
+ keyConfigured: "Key saved",
744
+ keyMissing: "No key yet",
745
+ keyNotWritable: "This key is supplied from outside the settings document (an environment variable, for example), so it cannot be changed here.",
746
+ modelsLabel: "Available models",
747
+ modelsRefresh: "Refresh",
748
+ modelsLoading: "Reading the gateway model list\u2026",
749
+ modelsFailed: "Could not read the gateway model list.",
750
+ modelsEmpty: "The gateway currently serves no model this adapter can route.",
751
+ modelsCount: "{count} models",
752
+ advancedLabel: "Advanced settings",
753
+ advancedHint: "Credential name, gateway URL, and adapter tuning. The inherited values work as they are; change them only if you know you need to.",
754
+ apiKeyEnvLabel: "Credential reference",
755
+ apiKeyEnvHint: "The name the key is resolved from, addressed through the credentials service.",
756
+ baseURLLabel: "Gateway URL",
757
+ baseURLHint: "The endpoint that serves requests and the live model list.",
758
+ refreshMinutesLabel: "Catalog refresh (minutes)",
759
+ refreshMinutesHint: "How long one live model-listing resolution stays authoritative before re-fetching.",
760
+ streamIdleTimeoutMsLabel: "Stream idle timeout (ms)",
761
+ streamIdleTimeoutMsHint: "Largest quiet gap between stream events before the request fails.",
762
+ maxRequestImageBytesLabel: "Request image payload cap (bytes)",
763
+ maxRequestImageBytesHint: "Accumulated base64 image payload bound for one request.",
764
+ requestImagePixelBudgetLabel: "Image pixel budget",
765
+ requestImagePixelBudgetHint: "Total-pixel budget for one converted request image.",
766
+ requestImageMaxBytesLabel: "Image byte target (bytes)",
767
+ requestImageMaxBytesHint: "Raw encoded-byte target for one request image before base64 expansion.",
768
+ overridden: "Customized",
769
+ reset: "Reset to default",
770
+ invalidValue: "Not a valid value",
771
+ save: "Save",
772
+ discard: "Discard",
773
+ saving: "Saving\u2026",
774
+ savedFailed: "The last save did not land as staged; correct the values or discard.",
775
+ readOnly: "The settings document is read-only in this deployment.",
776
+ unavailable: "The OpenCode Go settings are not served in this deployment."
777
+ };
778
+ var zh = {
779
+ nav: "OpenCode Go",
780
+ title: "OpenCode Go",
781
+ intro: "\u5728 Harness \u4E2D\u4F7F\u7528\u4F60\u7684 OpenCode Go \u8BA2\u9605\u3002\u586B\u5165\u4E0B\u65B9 API key \u5E76\u4FDD\u5B58\u5373\u53EF\uFF1B\u7F51\u5173\u5F53\u524D\u63D0\u4F9B\u7684\u6A21\u578B\u5217\u5728\u4E0B\u9762\u3002",
782
+ enabledLabel: "\u542F\u7528 OpenCode Go",
783
+ enabledHint: "\u5F00\u542F\u65F6\uFF0COpenCode Go \u4F1A\u51FA\u73B0\u5728\u6240\u6709\u6A21\u578B\u9009\u62E9\u5668\u4E2D\u5E76\u53EF\u5904\u7406\u8BF7\u6C42\u3002\u5173\u95ED\u540E\u5C06\u7ACB\u5373\u64A4\u4E0B\u8BE5\u63D0\u4F9B\u65B9\u53CA\u5176\u6A21\u578B\uFF1B\u672C\u9875\u4ECD\u53EF\u8BBF\u95EE\uFF0C\u65B9\u4FBF\u4F60\u968F\u65F6\u91CD\u65B0\u5F00\u542F\u3002",
784
+ enabledOff: "OpenCode Go \u5DF2\u5173\u95ED\u3002\u5176\u6A21\u578B\u5DF2\u4ECE\u6240\u6709\u9009\u62E9\u5668\u4E2D\u64A4\u4E0B\uFF0C\u4E5F\u4E0D\u4F1A\u6709\u8BF7\u6C42\u53D1\u5F80\u7F51\u5173\u3002\u9700\u8981\u65F6\u628A\u5F00\u5173\u91CD\u65B0\u6253\u5F00\u5373\u53EF\u3002",
785
+ enabledUnavailable: "\u6B64\u90E8\u7F72\u5728\u7EC4\u5408\u5C42\u56FA\u5B9A\u5F00\u542F\u4E86\u8BE5\u5F00\u5173\uFF0C\u65E0\u6CD5\u5728\u6B64\u4FEE\u6539\u3002",
786
+ keyLabel: "API key",
787
+ keyHint: "\u6765\u81EA\u4F60\u7684 OpenCode Go \u8BA2\u9605\uFF08opencode.ai\uFF09\u3002\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u5DF2\u4FDD\u5B58\u7684 key\uFF1B\u4FDD\u5B58\u540E\u4E0B\u4E00\u6B21\u8BF7\u6C42\u5373\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\u3002",
788
+ keyConfigured: "\u5DF2\u914D\u7F6E key",
789
+ keyMissing: "\u5C1A\u672A\u914D\u7F6E",
790
+ keyNotWritable: "\u8BE5 key \u7531\u8BBE\u7F6E\u6587\u6863\u4E4B\u5916\u7684\u6765\u6E90\u63D0\u4F9B\uFF08\u4F8B\u5982\u73AF\u5883\u53D8\u91CF\uFF09\uFF0C\u65E0\u6CD5\u5728\u6B64\u4FEE\u6539\u3002",
791
+ modelsLabel: "\u53EF\u7528\u6A21\u578B",
792
+ modelsRefresh: "\u5237\u65B0",
793
+ modelsLoading: "\u6B63\u5728\u8BFB\u53D6\u7F51\u5173\u6A21\u578B\u5217\u8868\u2026",
794
+ modelsFailed: "\u672A\u80FD\u8BFB\u53D6\u7F51\u5173\u6A21\u578B\u5217\u8868\u3002",
795
+ modelsEmpty: "\u7F51\u5173\u5F53\u524D\u6CA1\u6709\u672C\u9002\u914D\u5668\u53EF\u8DEF\u7531\u7684\u6A21\u578B\u3002",
796
+ modelsCount: "{count} \u4E2A\u6A21\u578B",
797
+ advancedLabel: "\u9AD8\u7EA7\u8BBE\u7F6E",
798
+ advancedHint: "\u51ED\u8BC1\u540D\u79F0\u3001\u7F51\u5173\u5730\u5740\u4E0E\u9002\u914D\u5668\u8C03\u4F18\u53C2\u6570\u3002\u7EE7\u627F\u6765\u7684\u53D6\u503C\u5373\u53EF\u6B63\u5E38\u4F7F\u7528\uFF0C\u9664\u975E\u4F60\u660E\u786E\u77E5\u9053\u9700\u8981\u4FEE\u6539\uFF0C\u5426\u5219\u4E0D\u5FC5\u52A8\u8FD9\u91CC\u3002",
799
+ apiKeyEnvLabel: "\u51ED\u8BC1\u5F15\u7528\u540D",
800
+ apiKeyEnvHint: "key \u89E3\u6790\u6240\u4F9D\u636E\u7684\u5F15\u7528\u540D\uFF0C\u7ECF\u51ED\u8BC1\u670D\u52A1\u5BFB\u5740\u3002",
801
+ baseURLLabel: "\u7F51\u5173\u5730\u5740",
802
+ baseURLHint: "\u540C\u65F6\u63D0\u4F9B\u6A21\u578B\u8BF7\u6C42\u4E0E\u5B9E\u65F6\u6A21\u578B\u5217\u8868\u7684\u7AEF\u70B9\u3002",
803
+ refreshMinutesLabel: "\u76EE\u5F55\u5237\u65B0\uFF08\u5206\u949F\uFF09",
804
+ refreshMinutesHint: "\u4E00\u6B21\u5B9E\u65F6\u6A21\u578B\u5217\u8868\u89E3\u6790\u4FDD\u6301\u6709\u6548\u7684\u65F6\u957F\uFF0C\u5230\u671F\u540E\u91CD\u65B0\u62C9\u53D6\u3002",
805
+ streamIdleTimeoutMsLabel: "\u6D41\u7A7A\u95F2\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
806
+ streamIdleTimeoutMsHint: "\u6D41\u4E8B\u4EF6\u95F4\u5141\u8BB8\u7684\u6700\u5927\u9759\u9ED8\u95F4\u9694\uFF0C\u8D85\u51FA\u540E\u8BF7\u6C42\u5931\u8D25\u3002",
807
+ maxRequestImageBytesLabel: "\u8BF7\u6C42\u56FE\u7247\u8F7D\u8377\u4E0A\u9650\uFF08\u5B57\u8282\uFF09",
808
+ maxRequestImageBytesHint: "\u5355\u4E2A\u8BF7\u6C42\u7D2F\u8BA1 base64 \u56FE\u7247\u8F7D\u8377\u7684\u4E0A\u9650\u3002",
809
+ requestImagePixelBudgetLabel: "\u56FE\u7247\u50CF\u7D20\u9884\u7B97",
810
+ requestImagePixelBudgetHint: "\u5355\u5F20\u8F6C\u6362\u540E\u8BF7\u6C42\u56FE\u7247\u7684\u603B\u50CF\u7D20\u9884\u7B97\u3002",
811
+ requestImageMaxBytesLabel: "\u56FE\u7247\u5B57\u8282\u76EE\u6807\uFF08\u5B57\u8282\uFF09",
812
+ requestImageMaxBytesHint: "\u5355\u5F20\u8BF7\u6C42\u56FE\u7247 base64 \u6269\u5C55\u524D\u7684\u539F\u59CB\u7F16\u7801\u5B57\u8282\u76EE\u6807\u3002",
813
+ overridden: "\u5DF2\u81EA\u5B9A\u4E49",
814
+ reset: "\u6062\u590D\u9ED8\u8BA4",
815
+ invalidValue: "\u4E0D\u662F\u6709\u6548\u503C",
816
+ save: "\u4FDD\u5B58",
817
+ discard: "\u653E\u5F03",
818
+ saving: "\u4FDD\u5B58\u4E2D\u2026",
819
+ savedFailed: "\u4E0A\u6B21\u4FDD\u5B58\u672A\u6309\u8349\u7A3F\u751F\u6548\uFF1B\u8BF7\u4FEE\u6B63\u6570\u503C\u6216\u653E\u5F03\u4FEE\u6539\u3002",
820
+ readOnly: "\u6B64\u90E8\u7F72\u4E2D\u8BBE\u7F6E\u6587\u6863\u4E3A\u53EA\u8BFB\u3002",
821
+ unavailable: "\u6B64\u90E8\u7F72\u672A\u63D0\u4F9B OpenCode Go \u8BBE\u7F6E\u3002"
822
+ };
823
+
824
+ // src/client/index.ts
825
+ var NS = "settings.opencode-go";
826
+ var inject = ["slots", "locale", "remote", "remote.credentials", "remote.llm", "settingsScope"];
827
+ function apply(ctx) {
828
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "llm-opencode-go: copy dictionaries");
829
+ const scope = ctx.settingsScope.bind({
830
+ namespace: "llm-opencode-go",
831
+ decode: (section) => typeof section === "object" && section !== null ? section : void 0
832
+ });
833
+ const controller = new OpencodeGoSectionController(scope, ctx);
834
+ const t = ctx.locale.bind(NS);
835
+ const injected = () => ({ ...controller.inject(), t });
836
+ ctx.effect(() => {
837
+ const refresh = (ref) => {
838
+ controller.refreshCredential(ref);
839
+ };
840
+ const dispose = ctx.remote.$on("credentials/reference-updated", refresh);
841
+ return () => {
842
+ dispose();
843
+ };
844
+ }, "llm-opencode-go: pushed credential invalidations");
845
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
846
+ name: "settings.section",
847
+ id: "opencode-go",
848
+ order: 20,
849
+ label: () => t("nav"),
850
+ inject: injected
851
+ }, OpencodeGoSection));
852
+ }
853
+ return module.exports; } });