opencode-kiro 0.5.0-beta.3 → 0.5.0-beta.5
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 +163 -51
- package/dist/chunk-LRAGM3PR.js +103 -0
- package/dist/{credits-box-view-GLFJSV6M.js → credits-box-view-2OGZPIW6.js} +7 -2
- package/dist/{credits-chip-view-YP4RPX26.js → credits-chip-view-M7PLKDN4.js} +3 -3
- package/dist/server.js +204 -54
- package/dist/tui.js +26 -27
- package/package.json +5 -2
- package/dist/chunk-M7OUVABX.js +0 -40
package/README.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# opencode-kiro
|
|
2
2
|
|
|
3
|
-
> ⚠️ **Experimental prerelease** — `0.5.0-beta.
|
|
3
|
+
> ⚠️ **Experimental prerelease** — `0.5.0-beta.5` 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
|
-
> [
|
|
8
|
-
>
|
|
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
|
|
|
@@ -19,7 +18,7 @@ The plugin supplies:
|
|
|
19
18
|
minimal self-registration fallback when the catalog lacks a `kiro` entry
|
|
20
19
|
- **Provider ownership**: an AISDK hook constructs the provider from
|
|
21
20
|
[`kiro-acp-ai-provider`](https://www.npmjs.com/package/kiro-acp-ai-provider) with the
|
|
22
|
-
right options (`cwd`, `agent`, `trustAllTools`, `mcpTimeout`, `contextWindows`)
|
|
21
|
+
right options (`cwd`, `agent`, `trustAllTools`, `mcpTimeout`, `stall`, `contextWindows`)
|
|
23
22
|
- **TUI credits display**: a live Kiro credits box in the sidebar and a compact
|
|
24
23
|
credits chip in the prompt footer row beside the host cost/context display, both
|
|
25
24
|
styled with the host's active theme tokens — auto-loaded from the same single
|
|
@@ -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`) | `8ba434b5973856b2f32b8cd3543e154b25c413e6` |
|
|
38
|
+
| Tested OpenCode commit (`upstream/v2` head, 2026-08-29) | `8ba434b5973856b2f32b8cd3543e154b25c413e6` |
|
|
40
39
|
| `@opencode-ai/plugin` | `0.0.0-dev-18686` (exact) |
|
|
41
|
-
| Package version | `0.5.0-beta.
|
|
40
|
+
| Package version | `0.5.0-beta.5` |
|
|
42
41
|
|
|
43
|
-
Full pin table and verification
|
|
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,26 +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
|
|
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.
|
|
63
|
+
"plugins": ["opencode-kiro@0.5.0-beta.5"]
|
|
63
64
|
}
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
> ⚠️ **Always pin the exact version, as above.** The host
|
|
67
|
-
>
|
|
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"`
|
|
68
69
|
> spec can silently move you off the tested build. Use the exact
|
|
69
|
-
> `opencode-kiro@0.5.0-beta.
|
|
70
|
+
> `opencode-kiro@0.5.0-beta.5` spec.
|
|
70
71
|
|
|
71
|
-
The object form pins the same version and
|
|
72
|
+
The object form pins the same version and takes the plugin options described in
|
|
73
|
+
[Plugin options](#plugin-options):
|
|
72
74
|
|
|
73
75
|
```json
|
|
74
76
|
{
|
|
75
77
|
"plugins": [
|
|
76
78
|
{
|
|
77
|
-
"package": "opencode-kiro@0.5.0-beta.
|
|
79
|
+
"package": "opencode-kiro@0.5.0-beta.5",
|
|
78
80
|
"options": {}
|
|
79
81
|
}
|
|
80
82
|
]
|
|
@@ -85,8 +87,95 @@ This loads the server entry (`./server` export): auth, model discovery, and prov
|
|
|
85
87
|
ownership. The server plugin declares **`tui: true`**, so the host TUI **auto-loads the
|
|
86
88
|
package's `./tui` entrypoint by itself** — the sidebar credits box and the prompt footer
|
|
87
89
|
credits chip appear with no further configuration. **No `cli.json` TUI entry is
|
|
88
|
-
needed.**
|
|
89
|
-
|
|
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
|
+
| `stall` | object | `{ "afterMs": 10000, "live": "reasoning" }` | Stall notice for turns with no model output. `afterMs` is the silence threshold in milliseconds (`0` disables the notice entirely); `live` is `"reasoning"` (live notice in the transcript, visible only when thinking is shown) or `"off"` (summary line only). See [Slow responses](#slow-responses). |
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"plugins": [
|
|
106
|
+
{
|
|
107
|
+
"package": "opencode-kiro@0.5.0-beta.5",
|
|
108
|
+
"options": {
|
|
109
|
+
"agent": "opencode",
|
|
110
|
+
"mcpTimeout": 45,
|
|
111
|
+
"discover": true,
|
|
112
|
+
"stall": { "afterMs": 10000, "live": "reasoning" }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A value of the wrong type falls back to its default; unknown keys are ignored. The
|
|
120
|
+
contract per option: `mcpTimeout` must be a positive number of minutes; zero, negative,
|
|
121
|
+
or non-numeric values fall back to the default (`45`); an empty `agent` string falls
|
|
122
|
+
back to `"opencode"`. `stall` must be an object; both members are optional and each is
|
|
123
|
+
checked on its own - `afterMs` must be a number of milliseconds of `0` or more, `live`
|
|
124
|
+
must be exactly `"off"` or `"reasoning"`. An invalid member is dropped while the valid
|
|
125
|
+
one is kept, and an object with nothing valid left counts as omitted (defaults apply).
|
|
126
|
+
|
|
127
|
+
> ⚠️ **Options work on the npm channel only.** The host passes `options` to plugins it
|
|
128
|
+
> installed from npm (the `"package": "opencode-kiro@<version>"` form above). Local
|
|
129
|
+
> `name@file:` tarball installs count as the npm channel, so options work there too.
|
|
130
|
+
> Bundled or built-in plugin loads receive an empty options object, so **the defaults
|
|
131
|
+
> always apply there** regardless of what you write in `options`.
|
|
132
|
+
|
|
133
|
+
There is **no `cwd` option**. The working directory handed to kiro-cli is derived
|
|
134
|
+
automatically from the OpenCode location the plugin is set up for, once per location;
|
|
135
|
+
a user-supplied path could only be less accurate.
|
|
136
|
+
|
|
137
|
+
### Slow responses
|
|
138
|
+
|
|
139
|
+
When the Kiro backend is overloaded, kiro-cli retries the request on its own and the
|
|
140
|
+
turn produces no output for a while - a spinner with nothing behind it. The `stall`
|
|
141
|
+
option controls how the plugin surfaces that wait:
|
|
142
|
+
|
|
143
|
+
- **By default** (`afterMs: 10000`, `live: "reasoning"`): after 10 seconds without
|
|
144
|
+
model output, a short reasoning block appears in the transcript, along the lines of
|
|
145
|
+
*"Kiro: no output for 10s - the model may be overloaded and kiro-cli is retrying."*
|
|
146
|
+
It refreshes every further 10 seconds while the silence lasts and closes with
|
|
147
|
+
*"output resumed after Ns"* once real output arrives, or *"turn ended after Ns
|
|
148
|
+
without further output"* if the turn ends first. The block is separate from the
|
|
149
|
+
model's own reasoning and text; the answer is unaffected. Because the live notice is
|
|
150
|
+
rendered as a reasoning block, the TUI shows it **collapsed by default (click to
|
|
151
|
+
expand)**, and only when the TUI shows thinking (`session.thinking: "show"` in
|
|
152
|
+
`cli.json`); with reasoning hidden the notice is hidden too, while the
|
|
153
|
+
after-the-turn summary line described next still appears.
|
|
154
|
+
- **After the turn**, the credits box in the sidebar and the credits chip in the prompt
|
|
155
|
+
footer add one line, `last turn stalled Ns (Reason)`, for as long as the last
|
|
156
|
+
completed turn is the one that stalled. The parenthesized reason (for example
|
|
157
|
+
`ModelOverloaded`) is taken, best-effort, from the last error kiro-cli wrote to its
|
|
158
|
+
own log during that turn; when no such line is available the summary shows the
|
|
159
|
+
duration alone.
|
|
160
|
+
- **`live: "off"`** hides the live transcript block; the after-the-turn summary line
|
|
161
|
+
still appears.
|
|
162
|
+
- **`afterMs: 0`** disables the feature entirely: no transcript block and no summary.
|
|
163
|
+
Any other value changes the silence threshold (in milliseconds).
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"plugins": [
|
|
168
|
+
{
|
|
169
|
+
"package": "opencode-kiro@0.5.0-beta.5",
|
|
170
|
+
"options": { "stall": { "live": "off" } }
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The notice is informational only; the plugin never cancels or retries the turn itself.
|
|
177
|
+
Requires `kiro-acp-ai-provider` 3.2.0 (see [docs/COMPATIBILITY.md](./docs/COMPATIBILITY.md)
|
|
178
|
+
for the pinned SDK version).
|
|
90
179
|
|
|
91
180
|
### Disabling
|
|
92
181
|
|
|
@@ -96,7 +185,7 @@ activates either:
|
|
|
96
185
|
|
|
97
186
|
```json
|
|
98
187
|
{
|
|
99
|
-
"plugins": ["opencode-kiro@0.5.0-beta.
|
|
188
|
+
"plugins": ["opencode-kiro@0.5.0-beta.5", "-kiro"]
|
|
100
189
|
}
|
|
101
190
|
```
|
|
102
191
|
|
|
@@ -118,14 +207,14 @@ npm install && npm run build && npm pack
|
|
|
118
207
|
```
|
|
119
208
|
|
|
120
209
|
```json
|
|
121
|
-
{ "plugins": ["opencode-kiro@file:/absolute/path/to/opencode-kiro-0.5.0-beta.
|
|
210
|
+
{ "plugins": ["opencode-kiro@file:/absolute/path/to/opencode-kiro-0.5.0-beta.5.tgz"] }
|
|
122
211
|
```
|
|
123
212
|
|
|
124
213
|
A bare path or bare `file:` spec is rejected at the tested SHA — the `name@file:` form
|
|
125
214
|
is required. **Caveat (local `file:` installs only)**: the colon in the resulting
|
|
126
215
|
install dirname defeats the host's OpenTUI loader shim, so the TUI surfaces render a
|
|
127
216
|
contained per-slot error notice instead of the credits views (the rest of the TUI keeps
|
|
128
|
-
working). Registry installs (`opencode-kiro@0.5.0-beta.
|
|
217
|
+
working). Registry installs (`opencode-kiro@0.5.0-beta.5`) use colon-free paths and are
|
|
129
218
|
fully green — this affects local tarball validation only.
|
|
130
219
|
|
|
131
220
|
The host resolves entrypoints from the package `exports` (`./server` for the server
|
|
@@ -148,10 +237,8 @@ Select the **Kiro** integration, then the **Kiro CLI Login** method:
|
|
|
148
237
|
Complete the login there; the plugin polls for up to 120 seconds and stores a minimal
|
|
149
238
|
credential record when kiro-cli reports success.
|
|
150
239
|
|
|
151
|
-
There is no configuration prompt during login
|
|
152
|
-
|
|
153
|
-
prompts API in favor of forms — and the sidebar no longer needs consent-driven config,
|
|
154
|
-
since `tui: true` auto-loads it).
|
|
240
|
+
There is no configuration prompt during login; the sidebar and footer credits surfaces
|
|
241
|
+
auto-load from the single `plugins` entry.
|
|
155
242
|
|
|
156
243
|
If the flow times out, authenticate directly with kiro-cli (`kiro-cli login`) and run
|
|
157
244
|
`opencode auth login` again; the fast path then completes immediately.
|
|
@@ -169,6 +256,14 @@ sets the model's base effort. A discovery failure or duplicate runtime ID leaves
|
|
|
169
256
|
catalog unchanged (fail-open). If the loaded catalog has no `kiro` provider at all, the
|
|
170
257
|
plugin self-registers a minimal fallback entry so discovered models remain usable.
|
|
171
258
|
|
|
259
|
+
Discovery is bounded and self-healing: a probe that gets no answer from kiro-cli within
|
|
260
|
+
60 seconds is treated as failed, and a failed probe is retried while Kiro stays
|
|
261
|
+
connected (after 5, 20, and 60 seconds) before giving up until the next login or
|
|
262
|
+
credential change. Each failure is reported on the server's stderr with an
|
|
263
|
+
`[opencode-kiro]` prefix - visible in `opencode serve` output, or from the TUI with
|
|
264
|
+
`OPENCODE_PRINT_LOGS=1` - so a missing model list is never silent. A login triggers one
|
|
265
|
+
probe per project location, however many credential events the host emits for it.
|
|
266
|
+
|
|
172
267
|
List the resulting models with:
|
|
173
268
|
|
|
174
269
|
```bash
|
|
@@ -176,16 +271,11 @@ opencode models
|
|
|
176
271
|
opencode run -m kiro/<exact-model-id> "hello"
|
|
177
272
|
```
|
|
178
273
|
|
|
179
|
-
Effort-capable models expose their variants through OpenCode's model-variant selection
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
(not `reasoningEffort`) — and why the SDK factory setting, not per-call provider
|
|
185
|
-
options, is the working carrier: OpenCode builds per-call `providerOptions` only for
|
|
186
|
-
the first-party `@ai-sdk/*` provider families, so `providerOptions.kiro.*` is never
|
|
187
|
-
populated for an `aisdk:` package provider like this one. Kiro cannot disable thinking,
|
|
188
|
-
so even the lowest level still produces a reasoning trail.
|
|
274
|
+
Effort-capable models expose their variants through OpenCode's model-variant selection:
|
|
275
|
+
pick the variant to choose a reasoning-effort level. The selected level is applied per
|
|
276
|
+
request, and one provider instance (one kiro-cli process) serves every effort level, so
|
|
277
|
+
switching effort does not start additional kiro-cli processes. Kiro cannot disable
|
|
278
|
+
thinking, so even the lowest level still produces a reasoning trail.
|
|
189
279
|
|
|
190
280
|
## Credits in the TUI
|
|
191
281
|
|
|
@@ -196,7 +286,7 @@ plugin renders two surfaces:
|
|
|
196
286
|
- a Kiro credits box in the sidebar (`sidebar.content` claim), showing the session's
|
|
197
287
|
live credits total and unit
|
|
198
288
|
- a compact credits chip in the prompt footer row beside the host cost/context
|
|
199
|
-
display (`prompt.footer.status` claim
|
|
289
|
+
display (`prompt.footer.status` claim) with the same total
|
|
200
290
|
|
|
201
291
|
Both are additive `append` claims — they compose with the host's built-in content and
|
|
202
292
|
never replace it — and both pick up the active theme's text tokens (feature-detected;
|
|
@@ -208,11 +298,11 @@ The only configuration needed is the single `plugins` entry from
|
|
|
208
298
|
[Install](#install-and-configure) — the TUI half auto-loads via `tui: true`.
|
|
209
299
|
|
|
210
300
|
Durable credits are read straight from host message state (the host persists provider
|
|
211
|
-
state on text end
|
|
301
|
+
state on text end). While a turn is still streaming,
|
|
212
302
|
credits for just-ended text are picked up live through a transient overlay that works
|
|
213
303
|
around a host reducer bug at the pinned snapshot (the live event path still drops
|
|
214
304
|
provider state); once durable state arrives it is authoritative and nothing is
|
|
215
|
-
double-counted. See
|
|
305
|
+
double-counted. See [CHANGELOG.md](./CHANGELOG.md) for details.
|
|
216
306
|
|
|
217
307
|
## Known limitations (prerelease)
|
|
218
308
|
|
|
@@ -220,7 +310,7 @@ double-counted. See the release notes for details.
|
|
|
220
310
|
upstream, but the live `session.text.ended` reducer still drops provider state at
|
|
221
311
|
the tested SHA, so in-turn updates come from the plugin's transient overlay
|
|
222
312
|
(durable state always wins on reconcile). See
|
|
223
|
-
[Credits in the TUI](#credits-in-the-tui) and
|
|
313
|
+
[Credits in the TUI](#credits-in-the-tui) and [CHANGELOG.md](./CHANGELOG.md).
|
|
224
314
|
- **Credits render in the TUI only.** Every other cost surface (ACP clients, web,
|
|
225
315
|
desktop, share pages, CLI cost output) shows $0.00 for Kiro sessions because the
|
|
226
316
|
catalog declares Kiro's per-token `cost` as 0 (subscription-metered, no per-token
|
|
@@ -239,17 +329,33 @@ double-counted. See the release notes for details.
|
|
|
239
329
|
## How it works
|
|
240
330
|
|
|
241
331
|
- **Auth (Integration + Credential)**: the plugin upserts the `kiro` integration with a
|
|
242
|
-
"Kiro CLI Login" OAuth method. `
|
|
243
|
-
authority (it delegates to kiro-cli
|
|
244
|
-
`Credential.OAuth` presence record.
|
|
332
|
+
"Kiro CLI Login" OAuth method. `verifyAuthAsync` from `kiro-acp-ai-provider` is the
|
|
333
|
+
auth authority (it delegates to kiro-cli without blocking the host's event loop);
|
|
334
|
+
success is stored as a minimal `Credential.OAuth` presence record. Starting a new
|
|
335
|
+
login while one is pending cancels the earlier attempt.
|
|
245
336
|
- **Model discovery (catalog transform)**: after login (and on later login events) the
|
|
246
337
|
plugin runs `listModels()` outside the transform, then applies the validated capture
|
|
247
|
-
via a catalog transform and reload
|
|
248
|
-
effort variants projected, fail-open on any discovery error.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
338
|
+
via a catalog transform and reload - exact ID matching, catalog metadata preserved,
|
|
339
|
+
effort variants projected, fail-open on any discovery error. Each probe runs under a
|
|
340
|
+
generation token: a logout, a newer probe, or a retry bumps the generation, and a
|
|
341
|
+
result that arrives for an older generation is discarded instead of overwriting the
|
|
342
|
+
catalog.
|
|
343
|
+
- **Provider ownership (AISDK hooks)**: the plugin's `sdk` hook constructs the provider
|
|
344
|
+
from `kiro-acp-ai-provider` with the plugin-supplied settings (identifying itself to
|
|
345
|
+
kiro-cli as `opencode-kiro`) and sets it as the event's SDK, so the Kiro provider is
|
|
346
|
+
always plugin-owned; one instance is cached per distinct settings and shared across
|
|
347
|
+
effort variants. The `language` hook applies the selected effort per request. The
|
|
348
|
+
settings relay each model's context window into the SDK's `contextWindows` map keyed
|
|
349
|
+
by model ID.
|
|
350
|
+
- **Effort carrier (why SDK settings, not `providerOptions`)**: OpenCode overlays the
|
|
351
|
+
selected variant's `settings` onto the model's `settings` and hands them to the
|
|
352
|
+
plugin's `aisdk` hooks as `event.options`; the `language` hook reads
|
|
353
|
+
`event.options.effort` and passes it to the shared provider as a per-model override
|
|
354
|
+
(`languageModel(id, { effort })`). The plugin emits the SDK's own `effort` key (not
|
|
355
|
+
`reasoningEffort`), and the settings path is the working carrier because OpenCode
|
|
356
|
+
builds per-call `providerOptions` only for the first-party `@ai-sdk/*` provider
|
|
357
|
+
families - `providerOptions.kiro.*` is never populated for an `aisdk:` package
|
|
358
|
+
provider like this one.
|
|
253
359
|
- **Session affinity & reset (in-SDK)**: the SDK keys kiro-cli sessions off OpenCode's
|
|
254
360
|
session affinity, isolates tool-less utility calls on an ephemeral session, detects
|
|
255
361
|
prompt-history divergence, and starts a fresh kiro session when needed.
|
|
@@ -257,6 +363,11 @@ double-counted. See the release notes for details.
|
|
|
257
363
|
metadata; OpenCode persists them key-unwrapped on message part state
|
|
258
364
|
(`part.state.credits`, `part.state.creditsUnit`), and the TUI plugin sums them per
|
|
259
365
|
assistant message (deduped across parts).
|
|
366
|
+
- **Stall status**: when a turn stalled, the SDK attaches `status`
|
|
367
|
+
(`{ stalledMs, hint? }`) to the same provider metadata; the TUI plugin reads it from
|
|
368
|
+
`part.state.status` on the same path as credits and renders the one-line summary
|
|
369
|
+
described in [Slow responses](#slow-responses). The live transcript notice is a
|
|
370
|
+
reasoning fragment emitted by the SDK itself, so it needs no TUI support.
|
|
260
371
|
|
|
261
372
|
## Troubleshooting
|
|
262
373
|
|
|
@@ -266,10 +377,11 @@ double-counted. See the release notes for details.
|
|
|
266
377
|
| Auth times out after ~120s | Complete the browser login faster, or run `kiro-cli login` yourself, then re-run `opencode auth login` (fast path). |
|
|
267
378
|
| No credits line / credits stay 0 | Credits appear after the first **completed** kiro turn; cancelled turns and turns without usage state contribute nothing. Check the plugin is active (no stray `"-kiro"` directive — note that directive residue can persist on a reused data dir). |
|
|
268
379
|
| Credits surfaces never appear | The TUI half auto-loads from the server `plugins` entry via `tui: true` — no separate TUI config exists. If the box/chip are missing, the server plugin itself is not loading (check your `plugins` entry and restart opencode). For local `name@file:` tarball installs, a contained per-slot error notice instead of the credits views is the known colon-path caveat; use a registry install. |
|
|
269
|
-
| `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. |
|
|
380
|
+
| `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. If you are logged in and the list (or the effort variants) is still missing, the discovery probe may have failed or timed out: it is retried automatically for a few minutes, and each attempt is reported on the server's stderr with an `[opencode-kiro]` prefix (`opencode serve`, or `OPENCODE_PRINT_LOGS=1` with the TUI). Logging in again starts a fresh probe. |
|
|
381
|
+
| Spinner with no output for a long time | The Kiro backend is likely overloaded and kiro-cli is retrying the request. With the default `stall` option a collapsed reasoning block saying so appears in the transcript after 10 seconds (only when thinking is shown; see [Slow responses](#slow-responses)), and the credits box/chip show `last turn stalled Ns (Reason)` once the turn ends. If no such block appears and the model list or effort variants are also missing, discovery may be the problem instead: watch the server log (`OPENCODE_PRINT_LOGS=1`, or run under `opencode serve`) for `[opencode-kiro]` lines. kiro-cli's own errors are in its log under your temp directory (`kiro-log/kiro-chat.log`). |
|
|
270
382
|
| 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). |
|
|
271
383
|
| Provider visible but runs fail | The provider can be selectable before any credential exists. Run `opencode auth login` first. |
|
|
272
|
-
| 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. |
|
|
384
|
+
| 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.5` spec. |
|
|
273
385
|
|
|
274
386
|
## Legacy: v1 / OpenCode v1 users (`0.4.0`)
|
|
275
387
|
|
|
@@ -278,7 +390,7 @@ v1 (`opencode >= 1.16.0`). It uses the v1 contract throughout: singular `plugin`
|
|
|
278
390
|
arrays in `opencode.json` and `tui.json`, the `opencode plugin opencode-kiro`
|
|
279
391
|
installer, and `part.metadata.kiro` credits. Its full documentation is the README at
|
|
280
392
|
the [`v0.4.0` tag](https://github.com/NachoFLizaur/opencode-kiro/tree/v0.4.0)
|
|
281
|
-
(equivalently, `main`). Do not install `0.5.0-beta.
|
|
393
|
+
(equivalently, `main`). Do not install `0.5.0-beta.5` into an OpenCode v1 setup.
|
|
282
394
|
|
|
283
395
|
## Development
|
|
284
396
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// src/tui/credits.ts
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null;
|
|
4
|
+
}
|
|
5
|
+
function readCreditState(state) {
|
|
6
|
+
if (!isRecord(state)) return void 0;
|
|
7
|
+
if (typeof state.credits !== "number" || !Number.isFinite(state.credits)) return void 0;
|
|
8
|
+
return {
|
|
9
|
+
credits: state.credits,
|
|
10
|
+
unit: typeof state.creditsUnit === "string" && state.creditsUnit.length > 0 ? state.creditsUnit : void 0
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function readStallStatus(status) {
|
|
14
|
+
if (!isRecord(status)) return void 0;
|
|
15
|
+
const { stalledMs, hint } = status;
|
|
16
|
+
if (typeof stalledMs !== "number" || !Number.isFinite(stalledMs) || stalledMs <= 0) return void 0;
|
|
17
|
+
return typeof hint === "string" && hint.length > 0 ? { stalledMs, hint } : { stalledMs };
|
|
18
|
+
}
|
|
19
|
+
function readPartMetadata(state) {
|
|
20
|
+
if (!isRecord(state)) return void 0;
|
|
21
|
+
const credits = readCreditState(state);
|
|
22
|
+
const status = readStallStatus(state.status);
|
|
23
|
+
if (!credits && !status) return void 0;
|
|
24
|
+
return { ...credits, ...status ? { status } : {} };
|
|
25
|
+
}
|
|
26
|
+
function carrierState(part) {
|
|
27
|
+
if (!isRecord(part)) return void 0;
|
|
28
|
+
if (part.type !== "text" && part.type !== "reasoning") return void 0;
|
|
29
|
+
return part.state;
|
|
30
|
+
}
|
|
31
|
+
function readPartCredits(part) {
|
|
32
|
+
return readCreditState(carrierState(part));
|
|
33
|
+
}
|
|
34
|
+
function readPartStatus(part) {
|
|
35
|
+
const state = carrierState(part);
|
|
36
|
+
return isRecord(state) ? readStallStatus(state.status) : void 0;
|
|
37
|
+
}
|
|
38
|
+
function messageCredits(parts) {
|
|
39
|
+
const carriers = parts.map(readPartCredits).filter((value) => value !== void 0);
|
|
40
|
+
const last = carriers.at(-1);
|
|
41
|
+
if (!last) return void 0;
|
|
42
|
+
return {
|
|
43
|
+
credits: last.credits,
|
|
44
|
+
unit: last.unit ?? carriers.findLast((carrier) => carrier.unit !== void 0)?.unit
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function messageStatus(parts) {
|
|
48
|
+
return parts.map(readPartStatus).findLast((value) => value !== void 0);
|
|
49
|
+
}
|
|
50
|
+
function messageMetadata(parts) {
|
|
51
|
+
const credits = messageCredits(parts);
|
|
52
|
+
const status = messageStatus(parts);
|
|
53
|
+
if (!credits && !status) return void 0;
|
|
54
|
+
return { ...credits, ...status ? { status } : {} };
|
|
55
|
+
}
|
|
56
|
+
function addMessageMetadata(acc, hit) {
|
|
57
|
+
if (!hit) return acc;
|
|
58
|
+
return {
|
|
59
|
+
total: acc.total + (hit.credits ?? 0),
|
|
60
|
+
unit: hit.unit ?? acc.unit,
|
|
61
|
+
present: true,
|
|
62
|
+
...hit.status ? { status: hit.status } : {}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
var creditsAmount = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
|
|
66
|
+
function formatCredits(value, unit) {
|
|
67
|
+
const amount = creditsAmount.format(Number.isFinite(value) ? value : 0);
|
|
68
|
+
if (!unit) return amount;
|
|
69
|
+
const label = value === 1 || unit.endsWith("s") ? unit : `${unit}s`;
|
|
70
|
+
return `${amount} ${label}`;
|
|
71
|
+
}
|
|
72
|
+
var ERROR_SUFFIX = "Error";
|
|
73
|
+
function stallReason(hint) {
|
|
74
|
+
if (typeof hint !== "string" || hint.length === 0) return void 0;
|
|
75
|
+
const token = /\bkind:\s*([A-Z][A-Za-z0-9]*)/.exec(hint)?.[1] ?? /\b([A-Z][A-Za-z0-9]*Error)\b/.exec(hint)?.[1];
|
|
76
|
+
if (!token) return void 0;
|
|
77
|
+
const reason = token.endsWith(ERROR_SUFFIX) ? token.slice(0, -ERROR_SUFFIX.length) : token;
|
|
78
|
+
return reason.length > 0 ? reason : void 0;
|
|
79
|
+
}
|
|
80
|
+
function formatStallSummary(status) {
|
|
81
|
+
const valid = readStallStatus(status);
|
|
82
|
+
if (!valid) return void 0;
|
|
83
|
+
const seconds = Math.max(1, Math.round(valid.stalledMs / 1e3));
|
|
84
|
+
const reason = stallReason(valid.hint);
|
|
85
|
+
return reason ? `last turn stalled ${seconds}s (${reason})` : `last turn stalled ${seconds}s`;
|
|
86
|
+
}
|
|
87
|
+
var STALL_SEPARATOR = " \xB7 ";
|
|
88
|
+
function creditsChipText(credits) {
|
|
89
|
+
if (!credits.present) return "";
|
|
90
|
+
const total = formatCredits(credits.total, credits.unit);
|
|
91
|
+
const stall = formatStallSummary(credits.status);
|
|
92
|
+
return stall ? `${total}${STALL_SEPARATOR}${stall}` : total;
|
|
93
|
+
}
|
|
94
|
+
var money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
readPartMetadata,
|
|
98
|
+
messageMetadata,
|
|
99
|
+
addMessageMetadata,
|
|
100
|
+
formatCredits,
|
|
101
|
+
formatStallSummary,
|
|
102
|
+
creditsChipText
|
|
103
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
formatCredits
|
|
3
|
-
|
|
2
|
+
formatCredits,
|
|
3
|
+
formatStallSummary
|
|
4
|
+
} from "./chunk-LRAGM3PR.js";
|
|
4
5
|
|
|
5
6
|
// src/tui/credits-box-view.ts
|
|
6
7
|
import { createElement, insert, insertNode, setProp } from "@opentui/solid";
|
|
@@ -16,6 +17,10 @@ function createCreditsBoxView(credits, tokens) {
|
|
|
16
17
|
root,
|
|
17
18
|
plainLine(() => current().present ? formatCredits(current().total, current().unit) : "", tokens?.subdued)
|
|
18
19
|
);
|
|
20
|
+
insertNode(
|
|
21
|
+
root,
|
|
22
|
+
plainLine(() => current().present ? formatStallSummary(current().status) ?? "" : "", tokens?.subdued)
|
|
23
|
+
);
|
|
19
24
|
return root;
|
|
20
25
|
}
|
|
21
26
|
function headerLine(content, fg) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
creditsChipText
|
|
3
|
+
} from "./chunk-LRAGM3PR.js";
|
|
4
4
|
|
|
5
5
|
// src/tui/credits-chip-view.ts
|
|
6
6
|
import { createElement, insert, setProp } from "@opentui/solid";
|
|
@@ -12,7 +12,7 @@ function createCreditsChipView(credits, tokens) {
|
|
|
12
12
|
setProp(chip, "wrapMode", "none");
|
|
13
13
|
setProp(chip, "flexShrink", 0);
|
|
14
14
|
if (tokens?.subdued !== void 0) setProp(chip, "fg", tokens.subdued);
|
|
15
|
-
insert(chip, () =>
|
|
15
|
+
insert(chip, () => creditsChipText(current()));
|
|
16
16
|
return chip;
|
|
17
17
|
}
|
|
18
18
|
export {
|
package/dist/server.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var version = "0.5.0-beta.5";
|
|
3
|
+
|
|
1
4
|
// src/server/auth.ts
|
|
2
5
|
var KIRO_INTEGRATION_ID = "kiro";
|
|
3
6
|
var KIRO_INTEGRATION_NAME = "Kiro";
|
|
@@ -8,6 +11,8 @@ 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`.";
|
|
14
|
+
var CANCELLED_MESSAGE = "Kiro authentication was cancelled.";
|
|
15
|
+
var SUPERSEDED_MESSAGE = "Kiro CLI login superseded by a new login attempt.";
|
|
11
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() {
|
|
@@ -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
|
-
|
|
49
|
-
reject(new Error(
|
|
53
|
+
const cancel = (reason) => {
|
|
54
|
+
reject(reason ?? new Error(CANCELLED_MESSAGE));
|
|
50
55
|
};
|
|
51
|
-
|
|
56
|
+
resources.cancelPoll = cancel;
|
|
57
|
+
const tick = async () => {
|
|
52
58
|
resources.pollTimer = void 0;
|
|
53
|
-
|
|
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(
|
|
73
|
+
resources.pollTimer = setTimeout(() => {
|
|
74
|
+
void tick();
|
|
75
|
+
}, POLL_INTERVAL_MS);
|
|
66
76
|
};
|
|
67
|
-
resources.pollTimer = setTimeout(
|
|
77
|
+
resources.pollTimer = setTimeout(() => {
|
|
78
|
+
void tick();
|
|
79
|
+
}, POLL_INTERVAL_MS);
|
|
68
80
|
});
|
|
69
81
|
}
|
|
70
82
|
async function authorize(resources) {
|
|
71
|
-
const {
|
|
72
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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) => {
|
|
@@ -120,6 +133,25 @@ var KIRO_PROVIDER_PACKAGE = "aisdk:kiro-acp-ai-provider";
|
|
|
120
133
|
function asString(value) {
|
|
121
134
|
return value;
|
|
122
135
|
}
|
|
136
|
+
var DISCOVERY_TIMEOUT_MS = 6e4;
|
|
137
|
+
var RETRY_BACKOFF_MS = [5e3, 2e4, 6e4];
|
|
138
|
+
var LOG_PREFIX = "[opencode-kiro]";
|
|
139
|
+
function describeError(error) {
|
|
140
|
+
return error instanceof Error ? error.message : String(error);
|
|
141
|
+
}
|
|
142
|
+
function cancelRetry(state) {
|
|
143
|
+
if (state.retryTimer !== void 0) {
|
|
144
|
+
clearTimeout(state.retryTimer);
|
|
145
|
+
state.retryTimer = void 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function clearDiscoveryTimers(state) {
|
|
149
|
+
if (state.probeTimer !== void 0) {
|
|
150
|
+
clearTimeout(state.probeTimer);
|
|
151
|
+
state.probeTimer = void 0;
|
|
152
|
+
}
|
|
153
|
+
cancelRetry(state);
|
|
154
|
+
}
|
|
123
155
|
function createDiscoveryResources() {
|
|
124
156
|
return {
|
|
125
157
|
state: void 0,
|
|
@@ -184,41 +216,95 @@ function applyCatalogSnapshot(draft, state) {
|
|
|
184
216
|
provider.settings = {
|
|
185
217
|
...provider.settings,
|
|
186
218
|
cwd: state.cwd,
|
|
187
|
-
agent:
|
|
219
|
+
agent: state.options.agent,
|
|
188
220
|
trustAllTools: true,
|
|
189
|
-
|
|
221
|
+
// intentionally not exposed as a plugin option
|
|
222
|
+
mcpTimeout: state.options.mcpTimeout,
|
|
223
|
+
...state.options.stall !== void 0 ? { stall: state.options.stall } : {},
|
|
190
224
|
contextWindows
|
|
191
225
|
};
|
|
192
226
|
});
|
|
193
227
|
}
|
|
194
228
|
function createDiscover(context, state) {
|
|
195
|
-
async function
|
|
229
|
+
async function publish(gen, discovered) {
|
|
230
|
+
if (gen !== state.generation) return;
|
|
231
|
+
const uniqueIds = new Set(discovered.map((model) => model.modelId));
|
|
232
|
+
if (uniqueIds.size !== discovered.length) {
|
|
233
|
+
console.error(
|
|
234
|
+
`${LOG_PREFIX} model discovery for ${state.cwd} returned duplicate model ids; catalog left unchanged`
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
cancelRetry(state);
|
|
239
|
+
state.snapshot = discovered;
|
|
240
|
+
await context.catalog.reload();
|
|
241
|
+
}
|
|
242
|
+
function probeDeadline(gen, probe) {
|
|
243
|
+
return new Promise((_resolve, reject) => {
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
if (state.probeTimer === timer) state.probeTimer = void 0;
|
|
246
|
+
void probe.then((discovered) => publish(gen, discovered)).catch(() => {
|
|
247
|
+
});
|
|
248
|
+
reject(new Error(`no response after ${DISCOVERY_TIMEOUT_MS / 1e3}s`));
|
|
249
|
+
}, DISCOVERY_TIMEOUT_MS);
|
|
250
|
+
state.probeTimer = timer;
|
|
251
|
+
const settle = () => {
|
|
252
|
+
clearTimeout(timer);
|
|
253
|
+
if (state.probeTimer === timer) state.probeTimer = void 0;
|
|
254
|
+
};
|
|
255
|
+
probe.then(settle, settle);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async function runDiscovery(gen, attempt) {
|
|
196
259
|
try {
|
|
197
260
|
const { listModels } = await import("kiro-acp-ai-provider");
|
|
198
|
-
const
|
|
261
|
+
const probe = listModels({ cwd: state.cwd });
|
|
262
|
+
const discovered = await Promise.race([probe, probeDeadline(gen, probe)]);
|
|
263
|
+
await publish(gen, discovered);
|
|
264
|
+
} catch (error) {
|
|
199
265
|
if (gen !== state.generation) return;
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
266
|
+
const delay = RETRY_BACKOFF_MS[attempt];
|
|
267
|
+
const next = delay === void 0 ? "giving up until the next credential change" : `retrying in ${delay / 1e3}s`;
|
|
268
|
+
console.error(
|
|
269
|
+
`${LOG_PREFIX} model discovery failed for ${state.cwd} (attempt ${attempt + 1}/${RETRY_BACKOFF_MS.length + 1}): ${describeError(error)}; ${next}`
|
|
270
|
+
);
|
|
271
|
+
if (delay !== void 0) scheduleRetry(gen, attempt + 1, delay);
|
|
205
272
|
}
|
|
206
273
|
}
|
|
274
|
+
function start(attempt) {
|
|
275
|
+
const gen = ++state.generation;
|
|
276
|
+
const run = runDiscovery(gen, attempt).finally(() => {
|
|
277
|
+
if (state.inflight === run) state.inflight = void 0;
|
|
278
|
+
});
|
|
279
|
+
state.inflight = run;
|
|
280
|
+
return run;
|
|
281
|
+
}
|
|
282
|
+
function scheduleRetry(gen, attempt, delay) {
|
|
283
|
+
cancelRetry(state);
|
|
284
|
+
state.retryTimer = setTimeout(() => {
|
|
285
|
+
state.retryTimer = void 0;
|
|
286
|
+
void retry(gen, attempt).catch(() => {
|
|
287
|
+
});
|
|
288
|
+
}, delay);
|
|
289
|
+
}
|
|
290
|
+
async function retry(gen, attempt) {
|
|
291
|
+
if (gen !== state.generation) return;
|
|
292
|
+
const active = await context.integration.connection.active(KIRO_INTEGRATION_ID);
|
|
293
|
+
if (!active || gen !== state.generation) return;
|
|
294
|
+
await start(attempt);
|
|
295
|
+
}
|
|
207
296
|
return async function discover(_reason) {
|
|
208
297
|
const active = await context.integration.connection.active(KIRO_INTEGRATION_ID);
|
|
209
298
|
if (!active) {
|
|
299
|
+
cancelRetry(state);
|
|
210
300
|
state.generation += 1;
|
|
211
301
|
state.snapshot = void 0;
|
|
212
302
|
await context.catalog.reload();
|
|
213
303
|
return;
|
|
214
304
|
}
|
|
215
305
|
if (state.inflight !== void 0) return state.inflight;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (state.inflight === run) state.inflight = void 0;
|
|
219
|
-
});
|
|
220
|
-
state.inflight = run;
|
|
221
|
-
return run;
|
|
306
|
+
cancelRetry(state);
|
|
307
|
+
return start(0);
|
|
222
308
|
};
|
|
223
309
|
}
|
|
224
310
|
function isKiroCredentialEvent(event) {
|
|
@@ -239,22 +325,23 @@ async function consumeEvents(iterator, discover) {
|
|
|
239
325
|
if (result.done) return;
|
|
240
326
|
const event = result.value;
|
|
241
327
|
if (isKiroCredentialEvent(event)) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
} catch {
|
|
245
|
-
}
|
|
328
|
+
void discover(event.type).catch(() => {
|
|
329
|
+
});
|
|
246
330
|
}
|
|
247
331
|
}
|
|
248
332
|
} catch {
|
|
249
333
|
}
|
|
250
334
|
}
|
|
251
|
-
async function registerDiscovery(context, resources) {
|
|
335
|
+
async function registerDiscovery(context, resources, options) {
|
|
252
336
|
const { location } = await context.integration.list();
|
|
253
337
|
const state = {
|
|
254
338
|
cwd: location.directory,
|
|
339
|
+
options,
|
|
255
340
|
snapshot: void 0,
|
|
256
341
|
generation: 0,
|
|
257
|
-
inflight: void 0
|
|
342
|
+
inflight: void 0,
|
|
343
|
+
probeTimer: void 0,
|
|
344
|
+
retryTimer: void 0
|
|
258
345
|
};
|
|
259
346
|
resources.state = state;
|
|
260
347
|
const discover = createDiscover(context, state);
|
|
@@ -265,12 +352,13 @@ async function registerDiscovery(context, resources) {
|
|
|
265
352
|
const iterator = context.event.subscribe()[Symbol.asyncIterator]();
|
|
266
353
|
resources.eventIterator = iterator;
|
|
267
354
|
resources.eventTask = consumeEvents(iterator, discover);
|
|
268
|
-
if (await context.integration.connection.active(KIRO_INTEGRATION_ID)) {
|
|
355
|
+
if (options.discover && await context.integration.connection.active(KIRO_INTEGRATION_ID)) {
|
|
269
356
|
void discover("setup").catch(() => {
|
|
270
357
|
});
|
|
271
358
|
}
|
|
272
359
|
return async () => {
|
|
273
360
|
state.generation += 1;
|
|
361
|
+
clearDiscoveryTimers(state);
|
|
274
362
|
const errors = [];
|
|
275
363
|
const eventIterator = resources.eventIterator;
|
|
276
364
|
resources.eventIterator = void 0;
|
|
@@ -299,16 +387,39 @@ async function registerDiscovery(context, resources) {
|
|
|
299
387
|
|
|
300
388
|
// src/server/aisdk.ts
|
|
301
389
|
var KIRO_SDK_PACKAGE = "kiro-acp-ai-provider";
|
|
390
|
+
var CLIENT_INFO = { name: "opencode-kiro", version };
|
|
302
391
|
function createAisdkResources() {
|
|
303
392
|
return {
|
|
304
393
|
ownedSdks: /* @__PURE__ */ new Set(),
|
|
305
394
|
cache: /* @__PURE__ */ new Map(),
|
|
306
|
-
|
|
395
|
+
disposeHooks: []
|
|
307
396
|
};
|
|
308
397
|
}
|
|
309
398
|
function isKiroPackage(pkg) {
|
|
310
399
|
return pkg === KIRO_SDK_PACKAGE || pkg === `aisdk:${KIRO_SDK_PACKAGE}`;
|
|
311
400
|
}
|
|
401
|
+
var SETTINGS_ALLOWLIST = [
|
|
402
|
+
"cwd",
|
|
403
|
+
"model",
|
|
404
|
+
"agent",
|
|
405
|
+
"trustAllTools",
|
|
406
|
+
"agentPrompt",
|
|
407
|
+
"env",
|
|
408
|
+
"clientInfo",
|
|
409
|
+
"sessionId",
|
|
410
|
+
"contextWindow",
|
|
411
|
+
"contextWindows",
|
|
412
|
+
"mcpTimeout",
|
|
413
|
+
"stall"
|
|
414
|
+
];
|
|
415
|
+
function sanitizeSettings(options) {
|
|
416
|
+
const settings = {};
|
|
417
|
+
for (const key of SETTINGS_ALLOWLIST) {
|
|
418
|
+
const value = options[key];
|
|
419
|
+
if (value !== void 0) settings[key] = value;
|
|
420
|
+
}
|
|
421
|
+
return settings;
|
|
422
|
+
}
|
|
312
423
|
function isJsonSafe(value) {
|
|
313
424
|
if (value === null) return true;
|
|
314
425
|
const kind = typeof value;
|
|
@@ -329,19 +440,20 @@ function stableStringify(value) {
|
|
|
329
440
|
}
|
|
330
441
|
return JSON.stringify(value);
|
|
331
442
|
}
|
|
332
|
-
function
|
|
333
|
-
return isJsonSafe(
|
|
443
|
+
function stableSettingsKey(settings) {
|
|
444
|
+
return isJsonSafe(settings) ? stableStringify(settings) : void 0;
|
|
334
445
|
}
|
|
335
446
|
async function registerAisdkHook(context, resources) {
|
|
336
|
-
const
|
|
447
|
+
const sdkRegistration = await context.aisdk.hook(
|
|
337
448
|
"sdk",
|
|
338
449
|
async (event) => {
|
|
339
450
|
if (!isKiroPackage(event.package)) return;
|
|
340
|
-
const
|
|
341
|
-
const key =
|
|
451
|
+
const settings = { ...sanitizeSettings(event.options), clientInfo: CLIENT_INFO };
|
|
452
|
+
const key = stableSettingsKey(settings);
|
|
342
453
|
let sdk = key === void 0 ? void 0 : resources.cache.get(key);
|
|
343
454
|
if (sdk === void 0) {
|
|
344
|
-
|
|
455
|
+
const { createKiroAcp } = await import("kiro-acp-ai-provider");
|
|
456
|
+
sdk = createKiroAcp(settings);
|
|
345
457
|
resources.ownedSdks.add(sdk);
|
|
346
458
|
if (key !== void 0) resources.cache.set(key, sdk);
|
|
347
459
|
}
|
|
@@ -349,14 +461,34 @@ async function registerAisdkHook(context, resources) {
|
|
|
349
461
|
},
|
|
350
462
|
{ providerID: KIRO_PROVIDER_ID }
|
|
351
463
|
);
|
|
352
|
-
resources.
|
|
464
|
+
resources.disposeHooks.push(sdkRegistration.dispose);
|
|
465
|
+
let languageRegistration;
|
|
466
|
+
try {
|
|
467
|
+
languageRegistration = await context.aisdk.hook(
|
|
468
|
+
"language",
|
|
469
|
+
async (event) => {
|
|
470
|
+
const effort = typeof event.options.effort === "string" ? event.options.effort : void 0;
|
|
471
|
+
const overrides = effort === void 0 ? void 0 : { effort };
|
|
472
|
+
event.language = event.sdk.languageModel(String(event.model.modelID), overrides);
|
|
473
|
+
},
|
|
474
|
+
{ providerID: KIRO_PROVIDER_ID }
|
|
475
|
+
);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
await sdkRegistration.dispose().catch(() => {
|
|
478
|
+
});
|
|
479
|
+
resources.disposeHooks.length = 0;
|
|
480
|
+
throw error;
|
|
481
|
+
}
|
|
482
|
+
resources.disposeHooks.push(languageRegistration.dispose);
|
|
353
483
|
return async () => {
|
|
354
484
|
const errors = [];
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
485
|
+
const disposeHooks = resources.disposeHooks.splice(0);
|
|
486
|
+
for (const dispose of disposeHooks) {
|
|
487
|
+
try {
|
|
488
|
+
await dispose();
|
|
489
|
+
} catch (error) {
|
|
490
|
+
errors.push(error);
|
|
491
|
+
}
|
|
360
492
|
}
|
|
361
493
|
const owned = Array.from(resources.ownedSdks);
|
|
362
494
|
resources.ownedSdks.clear();
|
|
@@ -405,17 +537,35 @@ function buildCleanup(state) {
|
|
|
405
537
|
}
|
|
406
538
|
|
|
407
539
|
// src/server.ts
|
|
540
|
+
function resolveOptions(raw) {
|
|
541
|
+
const stall = resolveStall(raw.stall);
|
|
542
|
+
return {
|
|
543
|
+
agent: typeof raw.agent === "string" && raw.agent !== "" ? raw.agent : "opencode",
|
|
544
|
+
mcpTimeout: typeof raw.mcpTimeout === "number" && Number.isFinite(raw.mcpTimeout) && raw.mcpTimeout > 0 ? raw.mcpTimeout : 45,
|
|
545
|
+
discover: typeof raw.discover === "boolean" ? raw.discover : true,
|
|
546
|
+
...stall !== void 0 ? { stall } : {}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function resolveStall(raw) {
|
|
550
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
551
|
+
const { afterMs, live } = raw;
|
|
552
|
+
const stall = {};
|
|
553
|
+
if (typeof afterMs === "number" && Number.isFinite(afterMs) && afterMs >= 0) stall.afterMs = afterMs;
|
|
554
|
+
if (live === "off" || live === "reasoning") stall.live = live;
|
|
555
|
+
return Object.keys(stall).length > 0 ? stall : void 0;
|
|
556
|
+
}
|
|
408
557
|
var plugin = {
|
|
409
558
|
id: "kiro",
|
|
410
|
-
// tui: true (dist/promise/plugin.d.ts
|
|
559
|
+
// tui: true (dist/promise/plugin.d.ts) — the host auto-loads this
|
|
411
560
|
// package's `./tui` entrypoint for npm-channel installs (single config entry)
|
|
412
561
|
tui: true,
|
|
413
562
|
async setup(context) {
|
|
563
|
+
const options = resolveOptions(context.options ?? {});
|
|
414
564
|
const state = createServerState();
|
|
415
565
|
const cleanup = buildCleanup(state);
|
|
416
566
|
try {
|
|
417
567
|
state.disposers.push(await registerAuth(context, state.auth));
|
|
418
|
-
state.disposers.push(await registerDiscovery(context, state.discovery));
|
|
568
|
+
state.disposers.push(await registerDiscovery(context, state.discovery, options));
|
|
419
569
|
state.disposers.push(await registerAisdkHook(context, state.aisdk));
|
|
420
570
|
} catch (error) {
|
|
421
571
|
await Promise.resolve(cleanup()).catch(() => {
|
package/dist/tui.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
addMessageMetadata,
|
|
3
|
+
messageMetadata,
|
|
4
|
+
readPartMetadata
|
|
5
|
+
} from "./chunk-LRAGM3PR.js";
|
|
5
6
|
|
|
6
7
|
// src/tui/transient-credits.ts
|
|
7
8
|
var SEPARATOR = "\0";
|
|
@@ -26,34 +27,31 @@ function recordTextEnded(store, event) {
|
|
|
26
27
|
const { sessionID, assistantMessageID, ordinal, state } = data;
|
|
27
28
|
if (!isValidKeyPart(sessionID) || !isValidKeyPart(assistantMessageID)) return;
|
|
28
29
|
if (typeof ordinal !== "number" || !Number.isInteger(ordinal) || ordinal < 0) return;
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
-
store.entries.set(transientKey(sessionID, assistantMessageID, ordinal),
|
|
30
|
+
const metadata = readPartMetadata(state);
|
|
31
|
+
if (!metadata) return;
|
|
32
|
+
store.entries.set(transientKey(sessionID, assistantMessageID, ordinal), metadata);
|
|
32
33
|
}
|
|
33
34
|
function transientForMessage(store, sessionID, assistantMessageID) {
|
|
34
|
-
|
|
35
|
-
let bestOrdinal = -1;
|
|
35
|
+
const entries = [];
|
|
36
36
|
for (const [key, value] of store.entries) {
|
|
37
37
|
const parsed = parseKey(key);
|
|
38
38
|
if (!parsed || parsed[0] !== sessionID || parsed[1] !== assistantMessageID) continue;
|
|
39
|
-
|
|
40
|
-
bestOrdinal = parsed[2];
|
|
41
|
-
best = value;
|
|
42
|
-
}
|
|
39
|
+
entries.push([parsed[2], value]);
|
|
43
40
|
}
|
|
44
|
-
return
|
|
41
|
+
if (entries.length === 0) return void 0;
|
|
42
|
+
entries.sort((a, b) => a[0] - b[0]);
|
|
43
|
+
const credits = entries.findLast(([, value]) => value.credits !== void 0)?.[1];
|
|
44
|
+
const status = entries.findLast(([, value]) => value.status !== void 0)?.[1]?.status;
|
|
45
|
+
return {
|
|
46
|
+
...credits ? { credits: credits.credits, unit: credits.unit } : {},
|
|
47
|
+
...status ? { status } : {}
|
|
48
|
+
};
|
|
45
49
|
}
|
|
46
50
|
function mergedMessageCredits(store, sessionID, durableMessages) {
|
|
47
51
|
return durableMessages.filter((message) => message.type === "assistant").reduce(
|
|
48
52
|
(acc, message) => {
|
|
49
|
-
const durable =
|
|
50
|
-
|
|
51
|
-
if (!hit) return acc;
|
|
52
|
-
return {
|
|
53
|
-
total: acc.total + hit.credits,
|
|
54
|
-
unit: hit.unit ?? acc.unit,
|
|
55
|
-
present: true
|
|
56
|
-
};
|
|
53
|
+
const durable = messageMetadata(message.content ?? []);
|
|
54
|
+
return addMessageMetadata(acc, durable ?? transientForMessage(store, sessionID, message.id));
|
|
57
55
|
},
|
|
58
56
|
{ total: 0, unit: void 0, present: false }
|
|
59
57
|
);
|
|
@@ -68,7 +66,7 @@ function reconcile(store, sessionID, durableMessages) {
|
|
|
68
66
|
store.entries.delete(key);
|
|
69
67
|
continue;
|
|
70
68
|
}
|
|
71
|
-
if (
|
|
69
|
+
if (messageMetadata(message.content ?? []) !== void 0) store.entries.delete(key);
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
function clear(store) {
|
|
@@ -109,19 +107,20 @@ var plugin = {
|
|
|
109
107
|
const store = memoryStore ? memoryStore[0] : createTransientStore();
|
|
110
108
|
if (!memoryStore) disposers.push(() => clear(store));
|
|
111
109
|
const [{ createCreditsBoxView }, { createCreditsChipView }, { createSignal }] = await Promise.all([
|
|
112
|
-
import("./credits-box-view-
|
|
113
|
-
import("./credits-chip-view-
|
|
110
|
+
import("./credits-box-view-2OGZPIW6.js"),
|
|
111
|
+
import("./credits-chip-view-M7PLKDN4.js"),
|
|
114
112
|
import("solid-js")
|
|
115
113
|
]);
|
|
116
114
|
const [transientVersion, setTransientVersion] = createSignal(0);
|
|
117
|
-
const
|
|
115
|
+
const recordEnded = (event) => {
|
|
118
116
|
try {
|
|
119
117
|
recordTextEnded(store, event);
|
|
120
118
|
setTransientVersion((version) => version + 1);
|
|
121
119
|
} catch {
|
|
122
120
|
}
|
|
123
|
-
}
|
|
124
|
-
disposers.push(
|
|
121
|
+
};
|
|
122
|
+
disposers.push(context.data.on("session.text.ended", recordEnded));
|
|
123
|
+
disposers.push(context.data.on("session.reasoning.ended", recordEnded));
|
|
125
124
|
const creditsFor = (sessionID) => () => {
|
|
126
125
|
if (!sessionID) return EMPTY_CREDITS;
|
|
127
126
|
transientVersion();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-kiro",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.5",
|
|
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,12 +38,15 @@
|
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=20"
|
|
40
40
|
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"tag": "beta"
|
|
43
|
+
},
|
|
41
44
|
"peerDependencies": {
|
|
42
45
|
"@opencode-ai/plugin": "0.0.0-dev-18686"
|
|
43
46
|
},
|
|
44
47
|
"dependencies": {
|
|
45
48
|
"@opentui/solid": "0.5.9",
|
|
46
|
-
"kiro-acp-ai-provider": "3.
|
|
49
|
+
"kiro-acp-ai-provider": "3.2.0",
|
|
47
50
|
"solid-js": "1.9.12"
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|
package/dist/chunk-M7OUVABX.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// src/tui/credits.ts
|
|
2
|
-
function isRecord(value) {
|
|
3
|
-
return typeof value === "object" && value !== null;
|
|
4
|
-
}
|
|
5
|
-
function readCreditState(state) {
|
|
6
|
-
if (!isRecord(state)) return void 0;
|
|
7
|
-
if (typeof state.credits !== "number" || !Number.isFinite(state.credits)) return void 0;
|
|
8
|
-
return {
|
|
9
|
-
credits: state.credits,
|
|
10
|
-
unit: typeof state.creditsUnit === "string" && state.creditsUnit.length > 0 ? state.creditsUnit : void 0
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
function readPartCredits(part) {
|
|
14
|
-
if (!isRecord(part)) return void 0;
|
|
15
|
-
if (part.type !== "text" && part.type !== "reasoning") return void 0;
|
|
16
|
-
return readCreditState(part.state);
|
|
17
|
-
}
|
|
18
|
-
function messageCredits(parts) {
|
|
19
|
-
const carriers = parts.map(readPartCredits).filter((value) => value !== void 0);
|
|
20
|
-
const last = carriers.at(-1);
|
|
21
|
-
if (!last) return void 0;
|
|
22
|
-
return {
|
|
23
|
-
credits: last.credits,
|
|
24
|
-
unit: last.unit ?? carriers.findLast((carrier) => carrier.unit !== void 0)?.unit
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
var creditsAmount = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
|
|
28
|
-
function formatCredits(value, unit) {
|
|
29
|
-
const amount = creditsAmount.format(Number.isFinite(value) ? value : 0);
|
|
30
|
-
if (!unit) return amount;
|
|
31
|
-
const label = value === 1 || unit.endsWith("s") ? unit : `${unit}s`;
|
|
32
|
-
return `${amount} ${label}`;
|
|
33
|
-
}
|
|
34
|
-
var money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
|
|
35
|
-
|
|
36
|
-
export {
|
|
37
|
-
readCreditState,
|
|
38
|
-
messageCredits,
|
|
39
|
-
formatCredits
|
|
40
|
-
};
|