opencode-kiro 0.5.0-beta.2 → 0.5.0-beta.4

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.
Files changed (3) hide show
  1. package/README.md +98 -46
  2. package/dist/server.js +116 -39
  3. package/package.json +8 -5
package/README.md CHANGED
@@ -1,12 +1,11 @@
1
1
  # opencode-kiro
2
2
 
3
- > ⚠️ **Experimental prerelease** — `0.5.0-beta.2` targets the **unreleased OpenCode v2**
3
+ > ⚠️ **Experimental prerelease** — `0.5.0-beta.4` targets the **unreleased OpenCode v2**
4
4
  > plugin contract at a pinned snapshot. It does **not** work with OpenCode v1.
5
5
  > v1 users: stay on **`opencode-kiro@0.4.0`** (the `main` branch / npm `latest` line,
6
- > which remains the supported stable release). See
7
- > [RELEASE_NOTES_0.5.0-beta.2.md](./RELEASE_NOTES_0.5.0-beta.2.md) and
8
- > [PINNED_VERSIONS.md](./PINNED_VERSIONS.md) for exact pins and the tested OpenCode SHA.
9
- > No OpenCode v2 release date is known or claimed here.
6
+ > which remains the supported stable release). See [CHANGELOG.md](./CHANGELOG.md) and
7
+ > [docs/COMPATIBILITY.md](./docs/COMPATIBILITY.md) for exact pins and the tested
8
+ > OpenCode SHA. No OpenCode v2 release date is known or claimed here.
10
9
 
11
10
  The ACP-compliant [Kiro](https://kiro.dev) plugin for [opencode](https://opencode.ai).
12
11
 
@@ -36,11 +35,11 @@ This prerelease is built and tested against **one pinned OpenCode v2 snapshot**:
36
35
 
37
36
  | Item | Value |
38
37
  |---|---|
39
- | Tested OpenCode commit (`upstream/v2`) | `1cf61593b5ec204619b3f679fe418fec10ca5934` |
40
- | `@opencode-ai/plugin` | `0.0.0-dev-17968` (exact) |
41
- | Package version | `0.5.0-beta.2` |
38
+ | Tested OpenCode commit (`upstream/v2` head, 2026-08-29) | `8ba434b5973856b2f32b8cd3543e154b25c413e6` |
39
+ | `@opencode-ai/plugin` | `0.0.0-dev-18686` (exact) |
40
+ | Package version | `0.5.0-beta.4` |
42
41
 
43
- Full pin table and verification evidence: [PINNED_VERSIONS.md](./PINNED_VERSIONS.md).
42
+ Full pin table and verification steps: [docs/COMPATIBILITY.md](./docs/COMPATIBILITY.md).
44
43
  There is no `engines.opencode` constraint — the v2 host has no stable semver yet; the
45
44
  tested SHA above is the compatibility target. Other v2 snapshots may or may not work.
46
45
 
@@ -55,21 +54,29 @@ tested SHA above is the compatibility target. Other v2 snapshots may or may not
55
54
  ## Install and configure
56
55
 
57
56
  **One config entry — that's the whole setup.** Add the package to the **`plugins`**
58
- array (plural) of your OpenCode config (`opencode.json`, project or global):
57
+ array (plural) of your OpenCode config. That is any `opencode.json` or `opencode.jsonc`
58
+ (or `.opencode/opencode.json`) found walking up from the project directory, or the
59
+ global `~/.config/opencode/opencode.json`:
59
60
 
60
61
  ```json
61
62
  {
62
- "plugins": ["opencode-kiro@0.5.0-beta.2"]
63
+ "plugins": ["opencode-kiro@0.5.0-beta.4"]
63
64
  }
64
65
  ```
65
66
 
66
- The object form pins the same version and leaves room for future options:
67
+ > ⚠️ **Always pin the exact version, as above.** The host background-auto-refreshes
68
+ > unpinned npm plugin packages to whatever the registry serves, so a bare `"opencode-kiro"`
69
+ > spec can silently move you off the tested build. Use the exact
70
+ > `opencode-kiro@0.5.0-beta.4` spec.
71
+
72
+ The object form pins the same version and takes the plugin options described in
73
+ [Plugin options](#plugin-options):
67
74
 
68
75
  ```json
69
76
  {
70
77
  "plugins": [
71
78
  {
72
- "package": "opencode-kiro@0.5.0-beta.2",
79
+ "package": "opencode-kiro@0.5.0-beta.4",
73
80
  "options": {}
74
81
  }
75
82
  ]
@@ -78,10 +85,49 @@ The object form pins the same version and leaves room for future options:
78
85
 
79
86
  This loads the server entry (`./server` export): auth, model discovery, and provider
80
87
  ownership. The server plugin declares **`tui: true`**, so the host TUI **auto-loads the
81
- package's `./tui` entrypoint by itself** — the sidebar credits box and the composer
88
+ package's `./tui` entrypoint by itself** — the sidebar credits box and the prompt footer
82
89
  credits chip appear with no further configuration. **No `cli.json` TUI entry is
83
- needed.** (The beta.1 two-file setup is obsolete: if you added `"opencode-kiro"` to
84
- your global `cli.json` `plugins` array for beta.1, remove it.)
90
+ needed.**
91
+
92
+ ### Plugin options
93
+
94
+ All options are optional; omit the `options` object entirely to get the defaults.
95
+
96
+ | Option | Type | Default | Meaning |
97
+ |---|---|---|---|
98
+ | `agent` | string | `"opencode"` | kiro-cli agent name the provider runs under |
99
+ | `mcpTimeout` | number | `45` | MCP tool-call timeout, in **minutes** |
100
+ | `discover` | boolean | `true` | Set `false` to skip the setup-time model discovery kick-off when Kiro is already connected. Discovery triggered by login/credential events still runs. |
101
+
102
+ ```json
103
+ {
104
+ "plugins": [
105
+ {
106
+ "package": "opencode-kiro@0.5.0-beta.4",
107
+ "options": {
108
+ "agent": "opencode",
109
+ "mcpTimeout": 45,
110
+ "discover": true
111
+ }
112
+ }
113
+ ]
114
+ }
115
+ ```
116
+
117
+ A value of the wrong type falls back to its default; unknown keys are ignored. The
118
+ contract per option: `mcpTimeout` must be a positive number of minutes; zero, negative,
119
+ or non-numeric values fall back to the default (`45`); an empty `agent` string falls
120
+ back to `"opencode"`.
121
+
122
+ > ⚠️ **Options work on the npm channel only.** The host passes `options` to plugins it
123
+ > installed from npm (the `"package": "opencode-kiro@<version>"` form above). Local
124
+ > `name@file:` tarball installs count as the npm channel, so options work there too.
125
+ > Bundled or built-in plugin loads receive an empty options object, so **the defaults
126
+ > always apply there** regardless of what you write in `options`.
127
+
128
+ There is **no `cwd` option**. The working directory handed to kiro-cli is derived
129
+ automatically from the OpenCode location the plugin is set up for, once per location;
130
+ a user-supplied path could only be less accurate.
85
131
 
86
132
  ### Disabling
87
133
 
@@ -91,7 +137,7 @@ activates either:
91
137
 
92
138
  ```json
93
139
  {
94
- "plugins": ["opencode-kiro@0.5.0-beta.2", "-kiro"]
140
+ "plugins": ["opencode-kiro@0.5.0-beta.4", "-kiro"]
95
141
  }
96
142
  ```
97
143
 
@@ -113,14 +159,14 @@ npm install && npm run build && npm pack
113
159
  ```
114
160
 
115
161
  ```json
116
- { "plugins": ["opencode-kiro@file:/absolute/path/to/opencode-kiro-0.5.0-beta.2.tgz"] }
162
+ { "plugins": ["opencode-kiro@file:/absolute/path/to/opencode-kiro-0.5.0-beta.4.tgz"] }
117
163
  ```
118
164
 
119
165
  A bare path or bare `file:` spec is rejected at the tested SHA — the `name@file:` form
120
166
  is required. **Caveat (local `file:` installs only)**: the colon in the resulting
121
167
  install dirname defeats the host's OpenTUI loader shim, so the TUI surfaces render a
122
168
  contained per-slot error notice instead of the credits views (the rest of the TUI keeps
123
- working). Registry installs (`opencode-kiro@0.5.0-beta.2`) use colon-free paths and are
169
+ working). Registry installs (`opencode-kiro@0.5.0-beta.4`) use colon-free paths and are
124
170
  fully green — this affects local tarball validation only.
125
171
 
126
172
  The host resolves entrypoints from the package `exports` (`./server` for the server
@@ -143,10 +189,8 @@ Select the **Kiro** integration, then the **Kiro CLI Login** method:
143
189
  Complete the login there; the plugin polls for up to 120 seconds and stores a minimal
144
190
  credential record when kiro-cli reports success.
145
191
 
146
- There is no configuration prompt during login anymore: the beta.1
147
- "Enable the Kiro credits sidebar?" consent select was removed (upstream deleted the
148
- prompts API in favor of forms — and the sidebar no longer needs consent-driven config,
149
- since `tui: true` auto-loads it).
192
+ There is no configuration prompt during login; the sidebar and footer credits surfaces
193
+ auto-load from the single `plugins` entry.
150
194
 
151
195
  If the flow times out, authenticate directly with kiro-cli (`kiro-cli login`) and run
152
196
  `opencode auth login` again; the fast path then completes immediately.
@@ -171,16 +215,11 @@ opencode models
171
215
  opencode run -m kiro/<exact-model-id> "hello"
172
216
  ```
173
217
 
174
- Effort-capable models expose their variants through OpenCode's model-variant selection.
175
- The chosen level reaches the SDK through the provider-settings path: OpenCode overlays
176
- the selected variant's `settings` onto the model's `settings`, hands them to the
177
- plugin's `aisdk` hook as `event.options`, and the plugin passes them verbatim to
178
- `createKiroAcp({ effort })`. That is why the plugin emits the SDK's own `effort` key
179
- (not `reasoningEffort`) — and why the SDK factory setting, not per-call provider
180
- options, is the working carrier: OpenCode builds per-call `providerOptions` only for
181
- the first-party `@ai-sdk/*` provider families, so `providerOptions.kiro.*` is never
182
- populated for an `aisdk:` package provider like this one. Kiro cannot disable thinking,
183
- so even the lowest level still produces a reasoning trail.
218
+ Effort-capable models expose their variants through OpenCode's model-variant selection:
219
+ pick the variant to choose a reasoning-effort level. The selected level is applied per
220
+ request, and one provider instance (one kiro-cli process) serves every effort level, so
221
+ switching effort does not start additional kiro-cli processes. Kiro cannot disable
222
+ thinking, so even the lowest level still produces a reasoning trail.
184
223
 
185
224
  ## Credits in the TUI
186
225
 
@@ -191,7 +230,7 @@ plugin renders two surfaces:
191
230
  - a Kiro credits box in the sidebar (`sidebar.content` claim), showing the session's
192
231
  live credits total and unit
193
232
  - a compact credits chip in the prompt footer row beside the host cost/context
194
- display (`prompt.footer.status` claim, v1 placement restored) with the same total
233
+ display (`prompt.footer.status` claim) with the same total
195
234
 
196
235
  Both are additive `append` claims — they compose with the host's built-in content and
197
236
  never replace it — and both pick up the active theme's text tokens (feature-detected;
@@ -203,11 +242,11 @@ The only configuration needed is the single `plugins` entry from
203
242
  [Install](#install-and-configure) — the TUI half auto-loads via `tui: true`.
204
243
 
205
244
  Durable credits are read straight from host message state (the host persists provider
206
- state on text end — fixed upstream since beta.1). While a turn is still streaming,
245
+ state on text end). While a turn is still streaming,
207
246
  credits for just-ended text are picked up live through a transient overlay that works
208
247
  around a host reducer bug at the pinned snapshot (the live event path still drops
209
248
  provider state); once durable state arrives it is authoritative and nothing is
210
- double-counted. See the release notes for details.
249
+ double-counted. See [CHANGELOG.md](./CHANGELOG.md) for details.
211
250
 
212
251
  ## Known limitations (prerelease)
213
252
 
@@ -215,7 +254,7 @@ double-counted. See the release notes for details.
215
254
  upstream, but the live `session.text.ended` reducer still drops provider state at
216
255
  the tested SHA, so in-turn updates come from the plugin's transient overlay
217
256
  (durable state always wins on reconcile). See
218
- [Credits in the TUI](#credits-in-the-tui) and the release notes.
257
+ [Credits in the TUI](#credits-in-the-tui) and [CHANGELOG.md](./CHANGELOG.md).
219
258
  - **Credits render in the TUI only.** Every other cost surface (ACP clients, web,
220
259
  desktop, share pages, CLI cost output) shows $0.00 for Kiro sessions because the
221
260
  catalog declares Kiro's per-token `cost` as 0 (subscription-metered, no per-token
@@ -234,17 +273,30 @@ double-counted. See the release notes for details.
234
273
  ## How it works
235
274
 
236
275
  - **Auth (Integration + Credential)**: the plugin upserts the `kiro` integration with a
237
- "Kiro CLI Login" OAuth method. `verifyAuth` from `kiro-acp-ai-provider` is the auth
238
- authority (it delegates to kiro-cli); success is stored as a minimal
239
- `Credential.OAuth` presence record.
276
+ "Kiro CLI Login" OAuth method. `verifyAuthAsync` from `kiro-acp-ai-provider` is the
277
+ auth authority (it delegates to kiro-cli without blocking the host's event loop);
278
+ success is stored as a minimal `Credential.OAuth` presence record. Starting a new
279
+ login while one is pending cancels the earlier attempt.
240
280
  - **Model discovery (catalog transform)**: after login (and on later login events) the
241
281
  plugin runs `listModels()` outside the transform, then applies the validated capture
242
282
  via a catalog transform and reload — exact ID matching, catalog metadata preserved,
243
283
  effort variants projected, fail-open on any discovery error.
244
- - **Provider ownership (AISDK hook)**: the plugin's SDK hook always constructs the
245
- provider from `kiro-acp-ai-provider` with the plugin-supplied options and sets it as
246
- the event's SDK, so the Kiro provider is always plugin-owned. The options relay each
247
- model's context window into the SDK's `contextWindows` map keyed by model ID.
284
+ - **Provider ownership (AISDK hooks)**: the plugin's `sdk` hook constructs the provider
285
+ from `kiro-acp-ai-provider` with the plugin-supplied settings (identifying itself to
286
+ kiro-cli as `opencode-kiro`) and sets it as the event's SDK, so the Kiro provider is
287
+ always plugin-owned; one instance is cached per distinct settings and shared across
288
+ effort variants. The `language` hook applies the selected effort per request. The
289
+ settings relay each model's context window into the SDK's `contextWindows` map keyed
290
+ by model ID.
291
+ - **Effort carrier (why SDK settings, not `providerOptions`)**: OpenCode overlays the
292
+ selected variant's `settings` onto the model's `settings` and hands them to the
293
+ plugin's `aisdk` hooks as `event.options`; the `language` hook reads
294
+ `event.options.effort` and passes it to the shared provider as a per-model override
295
+ (`languageModel(id, { effort })`). The plugin emits the SDK's own `effort` key (not
296
+ `reasoningEffort`), and the settings path is the working carrier because OpenCode
297
+ builds per-call `providerOptions` only for the first-party `@ai-sdk/*` provider
298
+ families - `providerOptions.kiro.*` is never populated for an `aisdk:` package
299
+ provider like this one.
248
300
  - **Session affinity & reset (in-SDK)**: the SDK keys kiro-cli sessions off OpenCode's
249
301
  session affinity, isolates tool-less utility calls on an ephemeral session, detects
250
302
  prompt-history divergence, and starts a fresh kiro session when needed.
@@ -264,7 +316,7 @@ double-counted. See the release notes for details.
264
316
  | `kiro` provider not showing in `opencode models` | Run `opencode auth login` first: models are discovered after auth. If the loaded catalog lacks a `kiro` entry, the plugin self-registers a minimal fallback during discovery. |
265
317
  | Path install rejected (`must export id`) | Use the `name@file:<absolute tarball path>` form after `npm run build && npm pack` in your checkout (both entry modules export ids). |
266
318
  | Provider visible but runs fail | The provider can be selectable before any credential exists. Run `opencode auth login` first. |
267
- | Worked yesterday, broken today | This prerelease targets one pinned OpenCode snapshot (see [Compatibility](#compatibility)). If your OpenCode build moved past the tested SHA, the v2 plugin surface may have changed underneath it. |
319
+ | Worked yesterday, broken today | This prerelease targets one pinned OpenCode snapshot (see [Compatibility](#compatibility)). If your OpenCode build moved past the tested SHA, the v2 plugin surface may have changed underneath it. The other cause is an **unpinned** `plugins` entry (a bare `"opencode-kiro"`): the host background-auto-refreshes unpinned npm plugin packages, so the plugin itself can move off the tested build without you changing anything — pin the exact `opencode-kiro@0.5.0-beta.4` spec. |
268
320
 
269
321
  ## Legacy: v1 / OpenCode v1 users (`0.4.0`)
270
322
 
@@ -273,7 +325,7 @@ v1 (`opencode >= 1.16.0`). It uses the v1 contract throughout: singular `plugin`
273
325
  arrays in `opencode.json` and `tui.json`, the `opencode plugin opencode-kiro`
274
326
  installer, and `part.metadata.kiro` credits. Its full documentation is the README at
275
327
  the [`v0.4.0` tag](https://github.com/NachoFLizaur/opencode-kiro/tree/v0.4.0)
276
- (equivalently, `main`). Do not install `0.5.0-beta.2` into an OpenCode v1 setup.
328
+ (equivalently, `main`). Do not install `0.5.0-beta.4` into an OpenCode v1 setup.
277
329
 
278
330
  ## Development
279
331
 
package/dist/server.js CHANGED
@@ -1,3 +1,6 @@
1
+ // package.json
2
+ var version = "0.5.0-beta.4";
3
+
1
4
  // src/server/auth.ts
2
5
  var KIRO_INTEGRATION_ID = "kiro";
3
6
  var KIRO_INTEGRATION_NAME = "Kiro";
@@ -8,7 +11,9 @@ var POLL_INTERVAL_MS = 2e3;
8
11
  var MAX_WAIT_MS = 12e4;
9
12
  var NOT_INSTALLED_MESSAGE = "kiro-cli is not installed. Install it from https://kiro.dev/docs/cli/";
10
13
  var TIMEOUT_MESSAGE = "Kiro authentication timed out. Run `kiro-cli login` manually, then re-run `opencode auth login`.";
11
- var LOGIN_INSTRUCTIONS = "Complete Kiro authentication in the browser window that just opened. Waiting for login...";
14
+ var CANCELLED_MESSAGE = "Kiro authentication was cancelled.";
15
+ var SUPERSEDED_MESSAGE = "Kiro CLI login superseded by a new login attempt.";
16
+ var LOGIN_INSTRUCTIONS = "Complete Kiro authentication in the browser window that just opened (if no browser opened, run `kiro-cli login` in another terminal). Waiting for login...";
12
17
  var ALREADY_AUTHENTICATED_INSTRUCTIONS = "Already authenticated with Kiro CLI.";
13
18
  function createAuthResources() {
14
19
  return {
@@ -18,7 +23,7 @@ function createAuthResources() {
18
23
  disposeRegistration: void 0
19
24
  };
20
25
  }
21
- function releaseLoginResources(resources) {
26
+ function releaseLoginResources(resources, reason) {
22
27
  if (resources.pollTimer !== void 0) {
23
28
  clearTimeout(resources.pollTimer);
24
29
  resources.pollTimer = void 0;
@@ -30,7 +35,7 @@ function releaseLoginResources(resources) {
30
35
  if (resources.cancelPoll !== void 0) {
31
36
  const cancel = resources.cancelPoll;
32
37
  resources.cancelPoll = void 0;
33
- cancel();
38
+ cancel(reason);
34
39
  }
35
40
  }
36
41
  function kiroCredential() {
@@ -45,12 +50,15 @@ function kiroCredential() {
45
50
  function pollForLogin(verifyAuth, resources) {
46
51
  return new Promise((resolve, reject) => {
47
52
  const start = Date.now();
48
- resources.cancelPoll = () => {
49
- reject(new Error("Kiro authentication was cancelled."));
53
+ const cancel = (reason) => {
54
+ reject(reason ?? new Error(CANCELLED_MESSAGE));
50
55
  };
51
- const tick = () => {
56
+ resources.cancelPoll = cancel;
57
+ const tick = async () => {
52
58
  resources.pollTimer = void 0;
53
- if (verifyAuth().authenticated) {
59
+ const status = await verifyAuth();
60
+ if (resources.cancelPoll !== cancel) return;
61
+ if (status.authenticated) {
54
62
  resources.cancelPoll = void 0;
55
63
  releaseLoginResources(resources);
56
64
  resolve(kiroCredential());
@@ -62,14 +70,19 @@ function pollForLogin(verifyAuth, resources) {
62
70
  reject(new Error(TIMEOUT_MESSAGE));
63
71
  return;
64
72
  }
65
- resources.pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
73
+ resources.pollTimer = setTimeout(() => {
74
+ void tick();
75
+ }, POLL_INTERVAL_MS);
66
76
  };
67
- resources.pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
77
+ resources.pollTimer = setTimeout(() => {
78
+ void tick();
79
+ }, POLL_INTERVAL_MS);
68
80
  });
69
81
  }
70
82
  async function authorize(resources) {
71
- const { verifyAuth } = await import("kiro-acp-ai-provider");
72
- const status = verifyAuth();
83
+ const { verifyAuthAsync } = await import("kiro-acp-ai-provider");
84
+ const { execFile } = await import("child_process");
85
+ const status = await verifyAuthAsync();
73
86
  if (!status.installed) throw new Error(NOT_INSTALLED_MESSAGE);
74
87
  if (status.authenticated) {
75
88
  return {
@@ -79,16 +92,16 @@ async function authorize(resources) {
79
92
  callback: Promise.resolve(kiroCredential())
80
93
  };
81
94
  }
82
- const { execFile } = await import("child_process");
95
+ if (resources.cancelPoll !== void 0) {
96
+ releaseLoginResources(resources, new Error(SUPERSEDED_MESSAGE));
97
+ }
83
98
  resources.child = execFile("kiro-cli", ["login"], {
84
99
  shell: process.platform === "win32"
85
100
  });
86
- return {
87
- url: KIRO_DOCS_URL,
88
- instructions: LOGIN_INSTRUCTIONS,
89
- mode: "auto",
90
- callback: pollForLogin(verifyAuth, resources)
91
- };
101
+ const callback = pollForLogin(verifyAuthAsync, resources);
102
+ callback.catch(() => {
103
+ });
104
+ return { url: KIRO_DOCS_URL, instructions: LOGIN_INSTRUCTIONS, mode: "auto", callback };
92
105
  }
93
106
  async function registerAuth(context, resources) {
94
107
  const registration = await context.integration.transform((draft) => {
@@ -184,9 +197,10 @@ function applyCatalogSnapshot(draft, state) {
184
197
  provider.settings = {
185
198
  ...provider.settings,
186
199
  cwd: state.cwd,
187
- agent: "opencode",
200
+ agent: state.options.agent,
188
201
  trustAllTools: true,
189
- mcpTimeout: 45,
202
+ // intentionally not exposed as a plugin option
203
+ mcpTimeout: state.options.mcpTimeout,
190
204
  contextWindows
191
205
  };
192
206
  });
@@ -221,15 +235,26 @@ function createDiscover(context, state) {
221
235
  return run;
222
236
  };
223
237
  }
238
+ function isKiroCredentialEvent(event) {
239
+ const type = event.type;
240
+ if (type === "integration.connection.updated") {
241
+ const data = event.data;
242
+ return data?.integrationID === KIRO_INTEGRATION_ID;
243
+ }
244
+ if (event.type === "credential.switched") {
245
+ return asString(event.data.integrationID) === KIRO_INTEGRATION_ID;
246
+ }
247
+ return type === "credential.updated";
248
+ }
224
249
  async function consumeEvents(iterator, discover) {
225
250
  try {
226
251
  while (true) {
227
252
  const result = await iterator.next();
228
253
  if (result.done) return;
229
254
  const event = result.value;
230
- if (event.type === "integration.connection.updated" && event.data.integrationID === KIRO_INTEGRATION_ID) {
255
+ if (isKiroCredentialEvent(event)) {
231
256
  try {
232
- await discover("connection-updated");
257
+ await discover(event.type);
233
258
  } catch {
234
259
  }
235
260
  }
@@ -237,10 +262,11 @@ async function consumeEvents(iterator, discover) {
237
262
  } catch {
238
263
  }
239
264
  }
240
- async function registerDiscovery(context, resources) {
265
+ async function registerDiscovery(context, resources, options) {
241
266
  const { location } = await context.integration.list();
242
267
  const state = {
243
268
  cwd: location.directory,
269
+ options,
244
270
  snapshot: void 0,
245
271
  generation: 0,
246
272
  inflight: void 0
@@ -254,7 +280,7 @@ async function registerDiscovery(context, resources) {
254
280
  const iterator = context.event.subscribe()[Symbol.asyncIterator]();
255
281
  resources.eventIterator = iterator;
256
282
  resources.eventTask = consumeEvents(iterator, discover);
257
- if (await context.integration.connection.active(KIRO_INTEGRATION_ID)) {
283
+ if (options.discover && await context.integration.connection.active(KIRO_INTEGRATION_ID)) {
258
284
  void discover("setup").catch(() => {
259
285
  });
260
286
  }
@@ -288,16 +314,38 @@ async function registerDiscovery(context, resources) {
288
314
 
289
315
  // src/server/aisdk.ts
290
316
  var KIRO_SDK_PACKAGE = "kiro-acp-ai-provider";
317
+ var CLIENT_INFO = { name: "opencode-kiro", version };
291
318
  function createAisdkResources() {
292
319
  return {
293
320
  ownedSdks: /* @__PURE__ */ new Set(),
294
321
  cache: /* @__PURE__ */ new Map(),
295
- disposeHook: void 0
322
+ disposeHooks: []
296
323
  };
297
324
  }
298
325
  function isKiroPackage(pkg) {
299
326
  return pkg === KIRO_SDK_PACKAGE || pkg === `aisdk:${KIRO_SDK_PACKAGE}`;
300
327
  }
328
+ var SETTINGS_ALLOWLIST = [
329
+ "cwd",
330
+ "model",
331
+ "agent",
332
+ "trustAllTools",
333
+ "agentPrompt",
334
+ "env",
335
+ "clientInfo",
336
+ "sessionId",
337
+ "contextWindow",
338
+ "contextWindows",
339
+ "mcpTimeout"
340
+ ];
341
+ function sanitizeSettings(options) {
342
+ const settings = {};
343
+ for (const key of SETTINGS_ALLOWLIST) {
344
+ const value = options[key];
345
+ if (value !== void 0) settings[key] = value;
346
+ }
347
+ return settings;
348
+ }
301
349
  function isJsonSafe(value) {
302
350
  if (value === null) return true;
303
351
  const kind = typeof value;
@@ -318,19 +366,20 @@ function stableStringify(value) {
318
366
  }
319
367
  return JSON.stringify(value);
320
368
  }
321
- function stableOptionsKey(options) {
322
- return isJsonSafe(options) ? stableStringify(options) : void 0;
369
+ function stableSettingsKey(settings) {
370
+ return isJsonSafe(settings) ? stableStringify(settings) : void 0;
323
371
  }
324
372
  async function registerAisdkHook(context, resources) {
325
- const registration = await context.aisdk.hook(
373
+ const sdkRegistration = await context.aisdk.hook(
326
374
  "sdk",
327
375
  async (event) => {
328
376
  if (!isKiroPackage(event.package)) return;
329
- const { createKiroAcp } = await import("kiro-acp-ai-provider");
330
- const key = stableOptionsKey(event.options);
377
+ const settings = { ...sanitizeSettings(event.options), clientInfo: CLIENT_INFO };
378
+ const key = stableSettingsKey(settings);
331
379
  let sdk = key === void 0 ? void 0 : resources.cache.get(key);
332
380
  if (sdk === void 0) {
333
- sdk = createKiroAcp(event.options);
381
+ const { createKiroAcp } = await import("kiro-acp-ai-provider");
382
+ sdk = createKiroAcp(settings);
334
383
  resources.ownedSdks.add(sdk);
335
384
  if (key !== void 0) resources.cache.set(key, sdk);
336
385
  }
@@ -338,14 +387,34 @@ async function registerAisdkHook(context, resources) {
338
387
  },
339
388
  { providerID: KIRO_PROVIDER_ID }
340
389
  );
341
- resources.disposeHook = registration.dispose;
390
+ resources.disposeHooks.push(sdkRegistration.dispose);
391
+ let languageRegistration;
392
+ try {
393
+ languageRegistration = await context.aisdk.hook(
394
+ "language",
395
+ async (event) => {
396
+ const effort = typeof event.options.effort === "string" ? event.options.effort : void 0;
397
+ const overrides = effort === void 0 ? void 0 : { effort };
398
+ event.language = event.sdk.languageModel(String(event.model.modelID), overrides);
399
+ },
400
+ { providerID: KIRO_PROVIDER_ID }
401
+ );
402
+ } catch (error) {
403
+ await sdkRegistration.dispose().catch(() => {
404
+ });
405
+ resources.disposeHooks.length = 0;
406
+ throw error;
407
+ }
408
+ resources.disposeHooks.push(languageRegistration.dispose);
342
409
  return async () => {
343
410
  const errors = [];
344
- resources.disposeHook = void 0;
345
- try {
346
- await registration.dispose();
347
- } catch (error) {
348
- errors.push(error);
411
+ const disposeHooks = resources.disposeHooks.splice(0);
412
+ for (const dispose of disposeHooks) {
413
+ try {
414
+ await dispose();
415
+ } catch (error) {
416
+ errors.push(error);
417
+ }
349
418
  }
350
419
  const owned = Array.from(resources.ownedSdks);
351
420
  resources.ownedSdks.clear();
@@ -394,17 +463,25 @@ function buildCleanup(state) {
394
463
  }
395
464
 
396
465
  // src/server.ts
466
+ function resolveOptions(raw) {
467
+ return {
468
+ agent: typeof raw.agent === "string" && raw.agent !== "" ? raw.agent : "opencode",
469
+ mcpTimeout: typeof raw.mcpTimeout === "number" && Number.isFinite(raw.mcpTimeout) && raw.mcpTimeout > 0 ? raw.mcpTimeout : 45,
470
+ discover: typeof raw.discover === "boolean" ? raw.discover : true
471
+ };
472
+ }
397
473
  var plugin = {
398
474
  id: "kiro",
399
- // tui: true (dist/promise/plugin.d.ts:40) — the host auto-loads this
475
+ // tui: true (dist/promise/plugin.d.ts) — the host auto-loads this
400
476
  // package's `./tui` entrypoint for npm-channel installs (single config entry)
401
477
  tui: true,
402
478
  async setup(context) {
479
+ const options = resolveOptions(context.options ?? {});
403
480
  const state = createServerState();
404
481
  const cleanup = buildCleanup(state);
405
482
  try {
406
483
  state.disposers.push(await registerAuth(context, state.auth));
407
- state.disposers.push(await registerDiscovery(context, state.discovery));
484
+ state.disposers.push(await registerDiscovery(context, state.discovery, options));
408
485
  state.disposers.push(await registerAisdkHook(context, state.aisdk));
409
486
  } catch (error) {
410
487
  await Promise.resolve(cleanup()).catch(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-kiro",
3
- "version": "0.5.0-beta.2",
3
+ "version": "0.5.0-beta.4",
4
4
  "description": "The ACP-compliant Kiro plugin for opencode: auth via the official kiro-cli login, the live Kiro model lineup through the Agent Client Protocol, and TUI credits display",
5
5
  "license": "MIT",
6
6
  "author": "Nacho F. Lizaur (https://github.com/NachoFLizaur)",
@@ -38,16 +38,19 @@
38
38
  "engines": {
39
39
  "node": ">=20"
40
40
  },
41
+ "publishConfig": {
42
+ "tag": "beta"
43
+ },
41
44
  "peerDependencies": {
42
- "@opencode-ai/plugin": "0.0.0-dev-17968"
45
+ "@opencode-ai/plugin": "0.0.0-dev-18686"
43
46
  },
44
47
  "dependencies": {
45
- "@opentui/solid": "0.5.7",
46
- "kiro-acp-ai-provider": "3.0.0",
48
+ "@opentui/solid": "0.5.9",
49
+ "kiro-acp-ai-provider": "3.1.0",
47
50
  "solid-js": "1.9.12"
48
51
  },
49
52
  "devDependencies": {
50
- "@opencode-ai/plugin": "0.0.0-dev-17968",
53
+ "@opencode-ai/plugin": "0.0.0-dev-18686",
51
54
  "@types/node": "^20.19.42",
52
55
  "tsup": "^8.0.0",
53
56
  "typescript": "^5.7.0",