@bogyie/opencode-kiro-plugin 0.2.13 → 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
@@ -40,7 +40,7 @@ The plugin resolves credentials in this order:
40
40
 
41
41
  When no usable Kiro CLI session is available, open OpenCode's provider connector, choose Kiro, and select `Kiro CLI login`. The plugin starts `kiro-cli login --use-device-flow`, waits until the verification URL/device code is visible, returns them to the connector, and then waits until `kiro-cli whoami` succeeds. The same auth method also works from OpenCode's CLI provider connect flow.
42
42
 
43
- The plugin does not start login during OpenCode startup or as a side effect of a model request. Direct fetch mode uses a usable API key/token when one is configured; otherwise it reads the active Kiro CLI session token and calls Kiro's REST/EventStream endpoint directly. If no credential is available, requests fail with `KIRO_AUTH_ERROR` and you can connect from OpenCode's provider connector or run `kiro-cli login` manually. `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.
43
+ The plugin does not start login during OpenCode startup. Direct fetch mode uses a usable API key/token when one is configured; otherwise it reads the active Kiro CLI session token and calls Kiro's REST/EventStream endpoint directly. If a model request fails because credentials are missing or rejected, the plugin starts one shared Kiro CLI login session and still returns `KIRO_AUTH_ERROR` for that failed request; retry after the login completes. `cli-chat` mode uses the official `kiro-cli chat --no-interactive` surface and applies the same login-on-auth-failure behavior. `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
  Use the `kiro_status` plugin tool to inspect provider id, backend, region, auth method, and discovered model count. Secrets are redacted in diagnostics.
46
46
 
@@ -6,6 +6,7 @@ import { type ChildProcess } from "node:child_process";
6
6
  export interface CliChatTransportOptions {
7
7
  readonly runner?: CommandRunner;
8
8
  readonly spawner?: CliChatSpawner;
9
+ readonly loginStarter?: () => void;
9
10
  readonly trustAllTools?: boolean;
10
11
  readonly requestTimeoutMs?: number;
11
12
  }
@@ -1,4 +1,4 @@
1
- import { runCommand } from "./auth.js";
1
+ import { runCommand, startKiroCliLoginOnce } from "./auth.js";
2
2
  import { KiroPluginError } from "./errors.js";
3
3
  import { spawn } from "node:child_process";
4
4
  export function promptForCli(request) {
@@ -84,20 +84,34 @@ class AsyncQueue {
84
84
  export class KiroCliChatTransport {
85
85
  #runner;
86
86
  #spawner;
87
+ #loginStarter;
87
88
  #trustAllTools;
88
89
  #requestTimeoutMs;
89
90
  constructor(options = {}) {
90
91
  this.#runner = options.runner ?? runCommand;
91
92
  this.#spawner = options.spawner ?? ((command, args) => spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] }));
93
+ this.#loginStarter = options.loginStarter ?? (() => {
94
+ startKiroCliLoginOnce();
95
+ });
92
96
  this.#trustAllTools = options.trustAllTools === true;
93
97
  this.#requestTimeoutMs = options.requestTimeoutMs ?? 120_000;
94
98
  }
99
+ #startLoginAfterAuthFailure() {
100
+ try {
101
+ this.#loginStarter();
102
+ }
103
+ catch {
104
+ // Keep the original model-call auth error visible to OpenCode.
105
+ }
106
+ }
95
107
  async generate(request) {
96
108
  const result = await this.#runner("kiro-cli", cliChatArgs(request, { trustAllTools: this.#trustAllTools }), {
97
109
  timeoutMs: this.#requestTimeoutMs,
98
110
  });
99
111
  if (!result.ok) {
100
112
  const authError = result.stderr.toLowerCase().includes("not logged in");
113
+ if (authError)
114
+ this.#startLoginAfterAuthFailure();
101
115
  throw new KiroPluginError(result.stderr.trim() || "kiro-cli chat failed", authError ? "KIRO_AUTH_ERROR" : "KIRO_CLI_FAILED", authError ? 401 : 502);
102
116
  }
103
117
  const text = sanitizeCliChatOutput(result.stdout);
@@ -149,6 +163,8 @@ export class KiroCliChatTransport {
149
163
  return;
150
164
  }
151
165
  const authError = stderr.toLowerCase().includes("not logged in");
166
+ if (authError)
167
+ this.#startLoginAfterAuthFailure();
152
168
  queue.fail(new KiroPluginError(stderr.trim() || `kiro-cli chat exited${code === null ? "" : ` with code ${code}`}${signal ? ` and signal ${signal}` : ""}`, authError ? "KIRO_AUTH_ERROR" : "KIRO_CLI_FAILED", authError ? 401 : 502));
153
169
  });
154
170
  try {
@@ -17,6 +17,7 @@ export type KiroRestFetch = (input: RequestInfo | URL, init?: RequestInit) => Pr
17
17
  export interface KiroRestTransportDependencies {
18
18
  readonly fetcher?: KiroRestFetch;
19
19
  readonly credentialProvider?: KiroCredentialProvider;
20
+ readonly loginStarter?: () => void;
20
21
  }
21
22
  export declare function toKiroRestPayload(request: KiroGenerateRequest, profileArn?: string): Record<string, unknown>;
22
23
  export declare class KiroRestTransport implements KiroTransport {
@@ -1,5 +1,5 @@
1
1
  import { KiroPluginError } from "./errors.js";
2
- import { readKiroCliSessionCredential, regionFromProfileArn, } from "./auth.js";
2
+ import { readKiroCliSessionCredential, regionFromProfileArn, startKiroCliLoginOnce, } from "./auth.js";
3
3
  const DEFAULT_USER_AGENT = "KiroIDE";
4
4
  const DEFAULT_AGENT_MODE = "vibe";
5
5
  const STREAMING_SDK_VERSION = "1.0.34";
@@ -313,15 +313,28 @@ export class KiroRestTransport {
313
313
  #options;
314
314
  #fetcher;
315
315
  #credentialProvider;
316
+ #loginStarter;
316
317
  constructor(options, dependencies = {}) {
317
318
  this.#options = options;
318
319
  this.#fetcher = dependencies.fetcher ?? fetch;
319
320
  this.#credentialProvider = dependencies.credentialProvider ?? (() => readKiroCliSessionCredential());
321
+ this.#loginStarter = dependencies.loginStarter ?? (() => {
322
+ startKiroCliLoginOnce();
323
+ });
324
+ }
325
+ #startLoginAfterAuthFailure() {
326
+ try {
327
+ this.#loginStarter();
328
+ }
329
+ catch {
330
+ // Keep the original model-call auth error visible to OpenCode.
331
+ }
320
332
  }
321
333
  async #credentials() {
322
334
  const session = this.#options.accessToken ? undefined : await this.#credentialProvider();
323
335
  const accessToken = this.#options.accessToken ?? session?.accessToken;
324
336
  if (!accessToken) {
337
+ this.#startLoginAfterAuthFailure();
325
338
  throw new KiroPluginError("No Kiro API token or Kiro CLI session token found. Connect Kiro from OpenCode's provider connector or run `kiro-cli login`.", "KIRO_AUTH_ERROR", 401);
326
339
  }
327
340
  const profileArn = this.#options.profileArn ?? session?.profileArn;
@@ -355,6 +368,8 @@ export class KiroRestTransport {
355
368
  lastError = new KiroPluginError(message, code, response.status);
356
369
  if (response.status === 429 || response.status >= 500)
357
370
  continue;
371
+ if (code === "KIRO_AUTH_ERROR")
372
+ this.#startLoginAfterAuthFailure();
358
373
  throw lastError;
359
374
  }
360
375
  if (!response.body)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bogyie/opencode-kiro-plugin",
3
- "version": "0.2.13",
3
+ "version": "0.3.0",
4
4
  "description": "OpenCode plugin for using Kiro as a provider adapter",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",