opencode-kiro 0.2.0 → 0.3.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/README.md CHANGED
@@ -8,8 +8,8 @@ opencode learns the `kiro` provider and its available models (US region) from th
8
8
  - **Auth** via the official `kiro-cli` login flow (`opencode auth login`, then "Kiro CLI Login")
9
9
  - **Provider options loader**: the `cwd`, `agent`, `trustAllTools`, `mcpTimeout` values
10
10
  opencode forwards into the SDK factory
11
- - **TUI credits display**: an opt-in sidebar context box showing tokens, context usage,
12
- and Kiro credits
11
+ - **TUI credits display**: an opt-in Kiro credits box in the sidebar (appended below the
12
+ native Context box) plus a matching footer credits chip
13
13
 
14
14
  The `kiro` provider resolves to [`kiro-acp-ai-provider`](https://www.npmjs.com/package/kiro-acp-ai-provider),
15
15
  an AI-SDK provider that talks to your locally installed `kiro-cli` over Kiro's
@@ -38,13 +38,13 @@ The installer reads this package's `exports` and detects both plugin entrypoints
38
38
  (`./server` and `./tui`), then patches **both** config files automatically:
39
39
 
40
40
  - `.opencode/opencode.json`: the server plugin (auth)
41
- - `.opencode/tui.json`: the TUI plugin (credits sidebar box)
41
+ - `.opencode/tui.json`: the TUI plugin (credits sidebar box + footer chip)
42
42
 
43
43
  (with `--global`: `~/.config/opencode/opencode.json` and `~/.config/opencode/tui.json`)
44
44
 
45
45
  You do **not** add a `provider.kiro` block; opencode loads the `kiro` provider and its
46
- models straight from the models.dev catalog. Then disable the builtin context box, see
47
- [Credits in the sidebar](#credits-in-the-sidebar).
46
+ models straight from the models.dev catalog. See
47
+ [Credits in the sidebar](#credits-in-the-sidebar) for what the TUI plugin adds.
48
48
 
49
49
  ### Manual alternative
50
50
 
@@ -64,12 +64,12 @@ project's `.opencode/` directory, at the project root, or in the global
64
64
 
65
65
  ```json
66
66
  {
67
- "plugin": ["opencode-kiro"],
68
- "plugin_enabled": { "internal:sidebar-context": false }
67
+ "plugin": ["opencode-kiro"]
69
68
  }
70
69
  ```
71
70
 
72
- (`plugin_enabled` is the [credits sidebar](#credits-in-the-sidebar) step, recommended now, explained below.)
71
+ That single `plugin` entry is all the [credits sidebar](#credits-in-the-sidebar) box and
72
+ footer chip need; there is no `plugin_enabled` step.
73
73
 
74
74
  ### Local development (path source)
75
75
 
@@ -87,8 +87,8 @@ npm install && npm run build
87
87
 
88
88
  opencode resolves the right entrypoint per file from the package `exports`. Note that
89
89
  path-sourced TUI plugins **must export an `id`** (opencode rejects them otherwise);
90
- this package ships `{ id: "opencode-kiro", tui }`, so the id (and your
91
- `plugin_enabled` keys) are identical across path and npm installs.
90
+ this package ships `{ id: "opencode-kiro", tui }`, so the id is identical across path
91
+ and npm installs.
92
92
 
93
93
  Because the provider now comes from the catalog rather than the plugin, a local checkout
94
94
  also needs a catalog that includes `kiro`. opencode reads its catalog from
@@ -154,19 +154,30 @@ level still produces a reasoning trail.
154
154
  ## Credits in the sidebar
155
155
 
156
156
  Kiro is subscription-metered: requests consume **credits**, and the dollar cost
157
- opencode normally displays for Kiro turns is always $0.00. The plugin therefore
158
- registers a **full replacement** for the built-in sidebar context box, showing
159
- tokens, context percentage, and a spend line.
157
+ opencode normally displays for Kiro turns is always $0.00. To surface credits the
158
+ plugin **appends** a small Kiro credits box in the sidebar, rendered right below the
159
+ native **Context** box. It does **not** replace or disable any builtin section: the
160
+ native Context box stays and keeps showing the usual tokens, context percentage, and
161
+ cost.
160
162
 
161
- The spend line adapts to what the session actually used:
163
+ The credits box renders only for Kiro sessions; for a non-Kiro session it shows nothing,
164
+ so that session's sidebar is unchanged. The credits value and its unit come from the
165
+ metadata the SDK attaches to each message part (kiro-cli reports the unit); nothing is
166
+ hardcoded client-side.
162
167
 
163
- - **Dollars only** (no Kiro turns): the builtin's exact `$X.XX spent` line, so
164
- non-Kiro sessions look identical to the built-in box.
165
- - **Credits only** (Kiro turns, no dollar cost): a single `N credits` line.
166
- - **Both** (the session used a dollar-based model AND Kiro in one session): two
167
- stacked lines, `$X.XX spent` then `N credits`, so neither figure is hidden.
168
+ All you need in `tui.json` is the plugin entry:
168
169
 
169
- Disable the builtin box in `tui.json` so only the replacement renders:
170
+ ```json
171
+ {
172
+ "plugin": ["opencode-kiro"]
173
+ }
174
+ ```
175
+
176
+ There is no `plugin_enabled` step anymore.
177
+
178
+ ### Migrating from 0.2.1 and earlier
179
+
180
+ Older versions replaced the native Context box with a clone and disabled the builtin via:
170
181
 
171
182
  ```json
172
183
  {
@@ -174,19 +185,14 @@ Disable the builtin box in `tui.json` so only the replacement renders:
174
185
  }
175
186
  ```
176
187
 
177
- Without this you will see **two** context boxes (cosmetic duplication: the builtin
178
- one plus the plugin's). The credits value and its unit come from the metadata the SDK
179
- attaches to each message part (kiro-cli reports the unit); nothing is hardcoded client-side.
180
-
181
- Trade-off: the replacement box applies to **every** session and disabling the builtin
182
- is global. The replacement reproduces the builtin `$X.XX spent` line for non-Kiro
183
- sessions (and stacks it above credits when a session used both), so mixed-provider
184
- users keep dollar cost in the sidebar; if you prefer the original built-in box, leave
185
- it enabled at the cost of the duplicate box.
188
+ If you upgraded from 0.2.1 or earlier and still have that line in your `tui.json`,
189
+ remove it yourself: the plugin no longer manages `plugin_enabled`. Once the line is
190
+ gone the native Context box returns and shows the usual tokens / context % / cost, and
191
+ the Kiro credits box appears in a separate box right below it.
186
192
 
187
193
  ## Known limitation
188
194
 
189
- **Credits render in the TUI only.** Two TUI surfaces show them: the sidebar context
195
+ **Credits render in the TUI only.** Two TUI surfaces show them: the sidebar credits
190
196
  box (above) and the input/prompt meta row chip (`session_prompt_right`), which sits
191
197
  above the host's `$` cost chip. Every other cost surface (ACP clients, the web app,
192
198
  desktop, web share pages, and CLI cost output) shows $0.00 for Kiro sessions. The
@@ -225,9 +231,8 @@ would require opencode core changes and is intentionally out of scope for this p
225
231
  |---|---|
226
232
  | `kiro-cli is not installed` during auth | Install kiro-cli from <https://kiro.dev/docs/cli/> and ensure it is on `PATH` for the opencode process. |
227
233
  | Auth times out after ~120s | Complete the browser login faster, or run `kiro-cli login` yourself, then re-run `opencode auth login` (fast path). |
228
- | Two "Context" boxes in the sidebar | Add `"plugin_enabled": { "internal:sidebar-context": false }` to `tui.json` (see [Credits in the sidebar](#credits-in-the-sidebar)). |
229
234
  | No credits line / credits stay 0 | Credits appear after the first **completed** kiro turn; cancelled turns and turns without usage metadata contribute nothing. Check the TUI plugin is `active` in the Plugins dialog (and listed in `tui.json`). |
230
- | Credits sidebar box never appears (even with `tui.json` configured correctly) | The TUI credits box renders only when `opencode-kiro` is resolvable in opencode's package cache. If the package is missing from the cache the box silently does not appear. Fix: ensure `opencode-kiro` is installed so it resolves in the cache. Do **not** manually clear the package cache: clearing can trigger a flaky on-demand refetch that fails with an "unknown git error". |
235
+ | Credits box never appears (even with `tui.json` configured correctly) | The TUI credits box renders only when `opencode-kiro` is resolvable in opencode's package cache. If the package is missing from the cache the box silently does not appear. Fix: ensure `opencode-kiro` is installed so it resolves in the cache. Do **not** manually clear the package cache: clearing can trigger a flaky on-demand refetch that fails with an "unknown git error". |
231
236
  | `kiro` provider not showing in `opencode models` | The provider comes from the models.dev catalog, not this plugin. Ensure your opencode version ships a catalog that includes `kiro` (run `opencode models --refresh` to update the cache). For local development, point opencode at a kiro-inclusive catalog via `OPENCODE_MODELS_PATH=/path/to/api.json` (see [Local development](#local-development-path-source)). |
232
237
  | `sdk.languageModel is not a function` | A stale `kiro-acp-ai-provider` from before the 2.0.x line resolved from opencode's package cache. Remove the cached copy (`$XDG_CACHE_HOME/opencode/packages/kiro-acp-ai-provider`, default `~/.cache/opencode/packages/...`) and retry; the factory auto-discovery clash was fixed in the 2.0.x line, and this plugin currently pins 2.0.2. |
233
238
  | Path install rejected (`must export id`) | Run `npm run build` in your checkout first and reference the repo root (both entry modules export ids). |
@@ -0,0 +1,40 @@
1
+ import {
2
+ formatCredits,
3
+ sumSessionCredits
4
+ } from "./chunk-MQVMKLNA.js";
5
+
6
+ // src/tui/credits-box-view.ts
7
+ import { createElement, effect, insert, insertNode, setProp } from "@opentui/solid";
8
+ import { createMemo } from "solid-js";
9
+ function createCreditsBoxView(api, sessionID) {
10
+ const theme = () => api.theme.current;
11
+ const messages = createMemo(() => api.state.session.messages(sessionID));
12
+ const credits = createMemo(() => sumSessionCredits(messages(), (messageID) => api.state.part(messageID)));
13
+ const root = createElement("box");
14
+ insertNode(
15
+ root,
16
+ headerLine(theme, () => credits().present ? "Kiro" : "")
17
+ );
18
+ insertNode(
19
+ root,
20
+ mutedLine(theme, () => credits().present ? formatCredits(credits().total, credits().unit) : "")
21
+ );
22
+ return root;
23
+ }
24
+ function headerLine(theme, content) {
25
+ const line = createElement("text");
26
+ effect(() => setProp(line, "fg", theme().text));
27
+ const bold = createElement("b");
28
+ insert(bold, content);
29
+ insertNode(line, bold);
30
+ return line;
31
+ }
32
+ function mutedLine(theme, content) {
33
+ const line = createElement("text");
34
+ effect(() => setProp(line, "fg", theme().textMuted));
35
+ insert(line, content);
36
+ return line;
37
+ }
38
+ export {
39
+ createCreditsBoxView
40
+ };
package/dist/server.d.ts CHANGED
@@ -10,10 +10,11 @@ declare function readToken(tokenPath: string | undefined): Promise<{
10
10
  type: "failed";
11
11
  }>;
12
12
  declare function notifyIfTokenExpired(client: PluginInput["client"] | undefined): Promise<void>;
13
+ declare function enableSidebarConfig(path: string, input: PluginInput): Promise<void>;
13
14
  declare const KiroAuthPlugin: Plugin;
14
15
  declare const _default: {
15
16
  id: string;
16
17
  server: (input: PluginInput) => Promise<Hooks>;
17
18
  };
18
19
 
19
- export { KiroAuthPlugin, _default as default, kiroTokenPath, notifyIfTokenExpired, readToken };
20
+ export { KiroAuthPlugin, _default as default, enableSidebarConfig, kiroTokenPath, notifyIfTokenExpired, readToken };
package/dist/server.js CHANGED
@@ -2,7 +2,6 @@
2
2
  import { homedir } from "os";
3
3
  import { dirname, join } from "path";
4
4
  var KIRO_PLUGIN_NAME = "opencode-kiro";
5
- var SIDEBAR_PLUGIN_ID = "internal:sidebar-context";
6
5
  var server = async (input) => {
7
6
  await notifyIfTokenExpired(input.client);
8
7
  const tuiPath = tuiConfigPath();
@@ -30,7 +29,7 @@ var server = async (input) => {
30
29
  trustAllTools: true,
31
30
  mcpTimeout: 45,
32
31
  contextWindows: Object.fromEntries(
33
- Object.values(provider.models).filter((m) => (m.limit?.context ?? 0) > 0).map((m) => [m.api.id, m.limit.context])
32
+ Object.values(provider?.models ?? {}).filter((m) => m.api?.id && (m.limit?.context ?? 0) > 0).map((m) => [m.api.id, m.limit.context])
34
33
  )
35
34
  }),
36
35
  methods: [
@@ -92,6 +91,14 @@ var server = async (input) => {
92
91
  }
93
92
  ]
94
93
  },
94
+ // Ensure a kiro provider entry exists so a stored login plus a kiro-less
95
+ // catalog cannot crash opencode (core derefs an undefined provider during
96
+ // auth init). Mutates in place; the ??= keeps it idempotent and never
97
+ // clobbers a real models.dev kiro entry when one is present.
98
+ config: async (input2) => {
99
+ input2.provider ??= {};
100
+ input2.provider.kiro ??= {};
101
+ },
95
102
  provider: {
96
103
  id: "kiro",
97
104
  // Inject per-model reasoning-effort variants, consumed as providerOptions.kiro.reasoningEffort.
@@ -167,10 +174,7 @@ async function readTuiConfig(path) {
167
174
  function isSidebarConfigured(config) {
168
175
  if (!config) return false;
169
176
  const plugin = config.plugin;
170
- const enabled = config.plugin_enabled;
171
- const hasPlugin = Array.isArray(plugin) && plugin.includes(KIRO_PLUGIN_NAME);
172
- const sidebarOff = typeof enabled === "object" && enabled !== null && enabled[SIDEBAR_PLUGIN_ID] === false;
173
- return hasPlugin && sidebarOff;
177
+ return Array.isArray(plugin) && plugin.includes(KIRO_PLUGIN_NAME);
174
178
  }
175
179
  async function enableSidebarConfig(path, input) {
176
180
  try {
@@ -197,9 +201,6 @@ async function enableSidebarConfig(path, input) {
197
201
  const plugin = Array.isArray(config.plugin) ? [...config.plugin] : [];
198
202
  if (!plugin.includes(KIRO_PLUGIN_NAME)) plugin.push(KIRO_PLUGIN_NAME);
199
203
  config.plugin = plugin;
200
- const enabled = typeof config.plugin_enabled === "object" && config.plugin_enabled !== null && !Array.isArray(config.plugin_enabled) ? config.plugin_enabled : {};
201
- enabled[SIDEBAR_PLUGIN_ID] = false;
202
- config.plugin_enabled = enabled;
203
204
  await mkdir(dirname(path), { recursive: true });
204
205
  await writeFile(path, JSON.stringify(config, null, 2) + "\n", "utf8");
205
206
  try {
@@ -219,6 +220,7 @@ var server_default = { id: "kiro", server };
219
220
  export {
220
221
  KiroAuthPlugin,
221
222
  server_default as default,
223
+ enableSidebarConfig,
222
224
  kiroTokenPath,
223
225
  notifyIfTokenExpired,
224
226
  readToken
package/dist/tui.js CHANGED
@@ -9,13 +9,13 @@ import {
9
9
 
10
10
  // src/tui.ts
11
11
  var tui = async (api) => {
12
- const { createContextView } = await import("./context-view-E6Q2FXL3.js");
12
+ const { createCreditsBoxView } = await import("./credits-box-view-BYKGQHSS.js");
13
13
  const { createCreditsChipView } = await import("./credits-chip-view-RL5JAEZ5.js");
14
14
  api.slots.register({
15
- order: 100,
15
+ order: 150,
16
16
  slots: {
17
17
  sidebar_content(_ctx, props) {
18
- return createContextView(api, props.session_id);
18
+ return createCreditsBoxView(api, props.session_id);
19
19
  },
20
20
  session_prompt_right(_ctx, props) {
21
21
  return createCreditsChipView(api, props.session_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-kiro",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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)",
@@ -1,64 +0,0 @@
1
- import {
2
- spendLines,
3
- sumSessionCredits
4
- } from "./chunk-MQVMKLNA.js";
5
-
6
- // src/tui/context-view.ts
7
- import { createElement, effect, insert, insertNode, setProp } from "@opentui/solid";
8
- import { createMemo } from "solid-js";
9
- function createContextView(api, sessionID) {
10
- const theme = () => api.theme.current;
11
- const messages = createMemo(() => api.state.session.messages(sessionID));
12
- const usage = createMemo(() => {
13
- const last = messages().findLast(
14
- (item) => item.role === "assistant" && item.tokens.output > 0
15
- );
16
- if (!last) {
17
- return {
18
- tokens: 0,
19
- percent: null
20
- };
21
- }
22
- const tokens = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write;
23
- const model = api.state.provider.find((item) => item.id === last.providerID)?.models[last.modelID];
24
- return {
25
- tokens,
26
- percent: model?.limit.context ? Math.round(tokens / model.limit.context * 100) : null
27
- };
28
- });
29
- const credits = createMemo(() => sumSessionCredits(messages(), (messageID) => api.state.part(messageID)));
30
- const cost = createMemo(() => api.state.session.get(sessionID)?.cost ?? 0);
31
- const root = createElement("box");
32
- insertNode(root, headerLine(theme));
33
- insertNode(
34
- root,
35
- mutedLine(theme, () => `${usage().tokens.toLocaleString()} tokens`)
36
- );
37
- insertNode(
38
- root,
39
- mutedLine(theme, () => `${usage().percent ?? 0}% used`)
40
- );
41
- const costLines = createMemo(() => spendLines({ cost: cost(), credits: credits() }));
42
- insertNode(
43
- root,
44
- mutedLine(theme, () => costLines().join("\n"))
45
- );
46
- return root;
47
- }
48
- function headerLine(theme) {
49
- const line = createElement("text");
50
- effect(() => setProp(line, "fg", theme().text));
51
- const bold = createElement("b");
52
- insert(bold, "Context");
53
- insertNode(line, bold);
54
- return line;
55
- }
56
- function mutedLine(theme, content) {
57
- const line = createElement("text");
58
- effect(() => setProp(line, "fg", theme().textMuted));
59
- insert(line, content);
60
- return line;
61
- }
62
- export {
63
- createContextView
64
- };