@bogyie/opencode-kiro-plugin 0.3.21 → 0.3.22
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 +5 -5
- package/dist/plugin.js +49 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,9 +38,9 @@ The plugin resolves credentials in this order:
|
|
|
38
38
|
3. Local Kiro CLI session token from the Kiro CLI SQLite store
|
|
39
39
|
4. `kiro-cli whoami` diagnostics for CLI session visibility
|
|
40
40
|
|
|
41
|
-
During OpenCode startup, the plugin checks Kiro auth in the blocking `config` hook. If no `KIRO_API_KEY` or active `kiro-cli` session exists, it starts the configured `kiro-cli login --use-device-flow
|
|
41
|
+
During OpenCode startup, the plugin checks Kiro auth in the blocking `config` hook. If no `KIRO_API_KEY` or active `kiro-cli` session exists, it starts the configured login flow once and waits for completion before provider registration continues. Personal login methods use `kiro-cli login --use-device-flow`. IAM Identity Center login uses direct OIDC device authorization for the configured Start URL and region, so it opens the Identity Center device URL and avoids localhost callback failures. After auth succeeds, startup runs best-effort model discovery with `kiro-cli chat --list-models --format json` when the Kiro CLI session is available. If startup discovery fails, the plugin keeps the last stored model list. If no stored list exists yet, it injects an `auto` placeholder model so Kiro still appears in OpenCode's provider connector.
|
|
42
42
|
|
|
43
|
-
You can open OpenCode's provider connector, choose Kiro, and select `Kiro device login`.
|
|
43
|
+
You can open OpenCode's provider connector, choose Kiro, and select `Kiro device login`. Personal login methods use `kiro-cli login --use-device-flow`, wait for the local Kiro CLI session to become authenticated, then store a local transport marker in OpenCode auth. IAM Identity Center login uses the OIDC device authorization flow directly, waits for token completion, and stores a `kiro-device:` credential in OpenCode auth. Configure `login.method` as `builder-id`, `google`, `github`, or `organization` to preselect the login method. The plugin maps `builder-id`, `google`, and `github` to `--license free`; it maps `organization` to the IAM Identity Center device flow. If no OpenCode connector credential or API key is configured, direct fetch still reads the active Kiro CLI session token and calls Kiro's REST/EventStream endpoint directly. If an API/model call using a CLI-backed local transport fails with an auth error, the selected transport starts the configured Kiro CLI login flow and retries the request once. Requests using stored `kiro-device:` credentials refresh tokens directly and do not fall back to opening Kiro CLI login pages. `cli-chat` mode uses the official `kiro-cli chat --no-interactive` surface and depends on the local Kiro CLI login state. `acp` mode uses the official `kiro-cli acp` surface, but is still treated as an explicit backend while its real-world protocol behavior is validated across Kiro CLI versions.
|
|
44
44
|
|
|
45
45
|
For personal Kiro login with GitHub:
|
|
46
46
|
|
|
@@ -79,7 +79,7 @@ For AWS IAM Identity Center login, configure the default device-flow Start URL s
|
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
For IAM Identity Center, configure `login.method: "organization"`, `login.identityProvider`, and `login.region` in plugin options
|
|
82
|
+
For IAM Identity Center, configure `login.method: "organization"`, `login.identityProvider`, and `login.region` in plugin options. The connector opens the Identity Center device URL for that Start URL and region, then waits until the device authorization completes.
|
|
83
83
|
|
|
84
84
|
Use the `kiro_status` plugin tool to inspect provider id, backend, region, auth method, and discovered model count. Use `kiro_refresh_models` when you explicitly want to run the configured model discovery command and update the in-memory and stored model cache. Secrets are redacted in diagnostics.
|
|
85
85
|
|
|
@@ -122,7 +122,7 @@ Supported values:
|
|
|
122
122
|
|
|
123
123
|
## Model Churn Handling
|
|
124
124
|
|
|
125
|
-
The resolver intentionally avoids a hard whitelist. At startup, the plugin reads the current Kiro CLI model list with `kiro-cli chat --list-models --format json`
|
|
125
|
+
The resolver intentionally avoids a hard whitelist. At startup, after auth succeeds, the plugin reads the current Kiro CLI model list with `kiro-cli chat --list-models --format json` when `kiro-cli whoami` succeeds, then stores successful results in a local cache. Direct IAM Identity Center device auth is available immediately for chat requests, but it does not write a Kiro CLI session, so CLI-based model discovery may keep using the previous cache until the Kiro CLI is also logged in. Failed or skipped refreshes do not clear the last successful cache. If there is no successful cache yet, startup falls back to the `auto` placeholder so the provider can still be connected before discovery succeeds.
|
|
126
126
|
|
|
127
127
|
Useful options:
|
|
128
128
|
|
|
@@ -172,7 +172,7 @@ This keeps new Kiro model ids usable before the package is updated without adver
|
|
|
172
172
|
|
|
173
173
|
The plugin injects `provider.kiro` with the `auto` placeholder needed for OpenCode's provider connector. Define `provider.kiro.models` yourself only when you need explicit model-picker metadata such as a display name or context limit. Use plugin `modelAliases` for aliases that should resolve one requested model id to another.
|
|
174
174
|
|
|
175
|
-
`modelDiscoveryCommand` defaults to `["kiro-cli", "chat", "--list-models", "--format", "json"]`. Set `modelDiscovery` to `"off"` to disable startup discovery and `kiro_refresh_models`; in that mode the plugin uses any stored cache and then the `auto` fallback.
|
|
175
|
+
`modelDiscoveryCommand` defaults to `["kiro-cli", "chat", "--list-models", "--format", "json"]`. Set `modelDiscovery` to `"off"` to disable startup discovery and `kiro_refresh_models`; in that mode the plugin uses any stored cache and then the `auto` fallback. `/v1/models` uses only the discovery/cache path: it never invokes the chat transport or login fallback, and returns the cached list or `auto` if discovery fails. For the default Kiro CLI discovery command, startup and forced refresh first check `kiro-cli whoami`; if the CLI is not authenticated, discovery is skipped instead of prompting for another login. OpenCode title-generation requests also suppress login fallback so startup helper traffic cannot open a second Kiro login window. A successful connector login also triggers a forced model refresh only when that refresh can run without prompting. Discovery stdout can be a JSON array, `{ "models": [...] }`, `{ "data": [...] }`, Kiro CLI list-models JSON, Kiro CLI plain list output, or one model id per line.
|
|
176
176
|
|
|
177
177
|
## Troubleshooting
|
|
178
178
|
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
import { KiroAcpTransport } from "./acp-transport.js";
|
|
3
4
|
import { authorizeKiroDevice, credentialFromKiroDeviceAuthKey, detectAuth, encodeKiroDeviceAuthKey, isKiroDeviceAuthKey, kiroDeviceVerificationUrl, KIRO_LOCAL_TRANSPORT_KEY, pollKiroDeviceToken, readKiroCliSessionCredential, resolveApiKey, startKiroCliLoginOnce, runKiroLoginFlowOnce, } from "./auth.js";
|
|
4
5
|
import { KiroCliChatTransport } from "./cli-transport.js";
|
|
@@ -99,6 +100,19 @@ function loginOptions(base, inputs) {
|
|
|
99
100
|
function isOrganizationLogin(login) {
|
|
100
101
|
return login.method === "organization" || login.license === "pro" || Boolean(login.identityProvider || login.region);
|
|
101
102
|
}
|
|
103
|
+
function isKiroCliModelDiscoveryCommand(command) {
|
|
104
|
+
const executable = command[0]?.split(/[\\/]/).pop();
|
|
105
|
+
return executable === "kiro-cli" && command.includes("chat") && command.includes("--list-models");
|
|
106
|
+
}
|
|
107
|
+
function openExternalUrl(url) {
|
|
108
|
+
if (process.env.OPENCODE_KIRO_BROWSER_OPEN === "0")
|
|
109
|
+
return;
|
|
110
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
111
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
112
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
113
|
+
child.on("error", () => undefined);
|
|
114
|
+
child.unref();
|
|
115
|
+
}
|
|
102
116
|
function loginPrompts(options) {
|
|
103
117
|
const prompts = [];
|
|
104
118
|
const login = options.login;
|
|
@@ -145,6 +159,8 @@ function localTransport(options, accessToken, behavior = {}) {
|
|
|
145
159
|
const login = () => {
|
|
146
160
|
if (behavior.loginOnAuthFailure === false)
|
|
147
161
|
return Promise.resolve(false);
|
|
162
|
+
if (isKiroDeviceAuthKey(accessToken))
|
|
163
|
+
return Promise.resolve(false);
|
|
148
164
|
return runKiroLoginFlowOnce({
|
|
149
165
|
login: loginOptions(options.login),
|
|
150
166
|
});
|
|
@@ -207,6 +223,7 @@ export function createKiroPlugin() {
|
|
|
207
223
|
}
|
|
208
224
|
let discovery;
|
|
209
225
|
let lastModelDiscoveryAt = 0;
|
|
226
|
+
let startupDeviceAuthKey;
|
|
210
227
|
let startupLogin;
|
|
211
228
|
let startupLoginAttempted = false;
|
|
212
229
|
const refreshModels = async (force = false) => {
|
|
@@ -215,7 +232,14 @@ export function createKiroPlugin() {
|
|
|
215
232
|
return [];
|
|
216
233
|
}
|
|
217
234
|
if (!discovery) {
|
|
218
|
-
discovery =
|
|
235
|
+
discovery = (async () => {
|
|
236
|
+
if (isKiroCliModelDiscoveryCommand(options.modelDiscoveryCommand)) {
|
|
237
|
+
const auth = await detectAuth().catch(() => undefined);
|
|
238
|
+
if (!auth?.authenticated)
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
return discoverModelsFromCommand(options.modelDiscoveryCommand[0], options.modelDiscoveryCommand.slice(1));
|
|
242
|
+
})()
|
|
219
243
|
.then((models) => {
|
|
220
244
|
if (models.length > 0) {
|
|
221
245
|
lastModelDiscoveryAt = Date.now();
|
|
@@ -232,6 +256,8 @@ export function createKiroPlugin() {
|
|
|
232
256
|
return discovery;
|
|
233
257
|
};
|
|
234
258
|
const ensureStartupAuthenticated = async () => {
|
|
259
|
+
if (startupDeviceAuthKey)
|
|
260
|
+
return true;
|
|
235
261
|
const current = await detectAuth().catch(() => undefined);
|
|
236
262
|
if (current?.authenticated)
|
|
237
263
|
return true;
|
|
@@ -241,7 +267,23 @@ export function createKiroPlugin() {
|
|
|
241
267
|
return false;
|
|
242
268
|
startupLoginAttempted = true;
|
|
243
269
|
startupLogin = (async () => {
|
|
244
|
-
const
|
|
270
|
+
const login = loginOptions(options.login);
|
|
271
|
+
if (isOrganizationLogin(login) && login.identityProvider) {
|
|
272
|
+
try {
|
|
273
|
+
const authorization = await authorizeKiroDevice(login);
|
|
274
|
+
openExternalUrl(kiroDeviceVerificationUrl(login.identityProvider, authorization.userCode));
|
|
275
|
+
const credential = await pollKiroDeviceToken(authorization, {
|
|
276
|
+
region: options.region,
|
|
277
|
+
...(options.profileArn ? { profileArn: options.profileArn } : {}),
|
|
278
|
+
});
|
|
279
|
+
startupDeviceAuthKey = encodeKiroDeviceAuthKey(credential);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const session = startKiroCliLoginOnce(login);
|
|
245
287
|
const prompted = await session.waitForPrompt(options.requestTimeoutMs);
|
|
246
288
|
if (!prompted)
|
|
247
289
|
return false;
|
|
@@ -262,7 +304,7 @@ export function createKiroPlugin() {
|
|
|
262
304
|
if (localServer)
|
|
263
305
|
return localServer;
|
|
264
306
|
const localFetch = async (input, init) => {
|
|
265
|
-
const transport = localTransport(options, bearerToken(init), {
|
|
307
|
+
const transport = localTransport(options, bearerToken(init) || startupDeviceAuthKey, {
|
|
266
308
|
loginOnAuthFailure: shouldLoginOnAuthFailure(init),
|
|
267
309
|
});
|
|
268
310
|
return createKiroFetch({
|
|
@@ -292,10 +334,12 @@ export function createKiroPlugin() {
|
|
|
292
334
|
region: options.region,
|
|
293
335
|
...(options.profileArn ? { profileArn: options.profileArn } : {}),
|
|
294
336
|
});
|
|
337
|
+
const key = encodeKiroDeviceAuthKey(credential);
|
|
338
|
+
startupDeviceAuthKey = key;
|
|
295
339
|
await refreshModels(true).catch(() => []);
|
|
296
340
|
return {
|
|
297
341
|
type: "success",
|
|
298
|
-
key
|
|
342
|
+
key,
|
|
299
343
|
metadata: {
|
|
300
344
|
source: "kiro-device-auth",
|
|
301
345
|
},
|
|
@@ -384,7 +428,7 @@ export function createKiroPlugin() {
|
|
|
384
428
|
const apiKey = await resolveApiKey(auth);
|
|
385
429
|
const server = await ensureLocalServer();
|
|
386
430
|
return {
|
|
387
|
-
apiKey: apiKey || KIRO_LOCAL_TRANSPORT_KEY,
|
|
431
|
+
apiKey: apiKey || startupDeviceAuthKey || KIRO_LOCAL_TRANSPORT_KEY,
|
|
388
432
|
baseURL: server.baseURL,
|
|
389
433
|
};
|
|
390
434
|
},
|