@bogyie/opencode-kiro-plugin 0.3.0 → 0.3.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 +4 -4
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +64 -5
- package/dist/cli-transport.d.ts +1 -1
- package/dist/cli-transport.js +31 -19
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/kiro-rest-transport.d.ts +1 -1
- package/dist/kiro-rest-transport.js +17 -17
- package/dist/model-discovery.d.ts +9 -2
- package/dist/model-discovery.js +28 -6
- package/dist/plugin.js +8 -3
- package/package.json +4 -4
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
|
-
When no usable Kiro CLI session is available,
|
|
41
|
+
When no usable Kiro CLI session is available, OpenCode's startup model discovery may start the same Kiro CLI login flow automatically. The plugin runs `kiro-cli login --use-device-flow`, waits for login to complete through `kiro-cli whoami`, and then runs the model discovery command one more time. If that login fails or times out, discovery fails for that startup attempt; it does not loop.
|
|
42
42
|
|
|
43
|
-
The
|
|
43
|
+
You can also open OpenCode's provider connector, choose Kiro, and select `Kiro CLI login`. The connector flow 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. 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 credentials expire during use, direct fetch and `cli-chat` start the shared Kiro login flow, wait for it to complete, and retry the failed request once. `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
|
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
|
|
|
@@ -77,7 +77,7 @@ Supported values:
|
|
|
77
77
|
|
|
78
78
|
## Model Churn Handling
|
|
79
79
|
|
|
80
|
-
The resolver intentionally avoids a hard whitelist. By default, the plugin reads the current Kiro CLI model list with `kiro-cli chat --list-models --format json`. Runtime discovery is the source of truth for the model picker;
|
|
80
|
+
The resolver intentionally avoids a hard whitelist. By default, the plugin reads the current Kiro CLI model list with `kiro-cli chat --list-models --format json`. Runtime discovery is the source of truth for the model picker; if discovery cannot return any models, the plugin exposes an `auto` placeholder so OpenCode keeps the Kiro provider visible for connection.
|
|
81
81
|
|
|
82
82
|
Useful options:
|
|
83
83
|
|
|
@@ -127,7 +127,7 @@ This keeps new Kiro model ids usable before the package is updated without adver
|
|
|
127
127
|
|
|
128
128
|
The plugin injects `provider.kiro` automatically. You only need to add `provider.kiro.models` yourself when overriding OpenCode 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.
|
|
129
129
|
|
|
130
|
-
`modelDiscoveryCommand` defaults to `["kiro-cli", "chat", "--list-models", "--format", "json"]`. Set `modelDiscovery` to `"off"` to skip runtime discovery. 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.
|
|
130
|
+
`modelDiscoveryCommand` defaults to `["kiro-cli", "chat", "--list-models", "--format", "json"]`. Set `modelDiscovery` to `"off"` to skip runtime discovery. If this command fails with an auth error, the plugin starts Kiro CLI login, waits for it to finish, and retries the command once. 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.
|
|
131
131
|
|
|
132
132
|
## Troubleshooting
|
|
133
133
|
|
package/dist/auth.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ export interface KiroLoginSession {
|
|
|
37
37
|
waitForPrompt(timeoutMs?: number): Promise<boolean>;
|
|
38
38
|
waitForAuth(runner?: CommandRunner): Promise<boolean>;
|
|
39
39
|
}
|
|
40
|
+
export interface KiroLoginFlowOptions {
|
|
41
|
+
readonly spawner?: ProcessSpawner;
|
|
42
|
+
readonly runner?: CommandRunner;
|
|
43
|
+
readonly promptTimeoutMs?: number;
|
|
44
|
+
}
|
|
40
45
|
export declare const KIRO_LOGIN_URL = "https://view.awsapps.com/start";
|
|
41
46
|
export declare const DEFAULT_KIRO_CLI_DB_PATH: string;
|
|
42
47
|
export declare const DEFAULT_KIRO_CLI_TOKEN_KEYS: readonly ["kirocli:odic:token", "codewhisperer:odic:token"];
|
|
@@ -47,6 +52,7 @@ export declare function extractKiroLoginUrl(output: string): string;
|
|
|
47
52
|
export declare function extractKiroLoginCode(output: string): string | undefined;
|
|
48
53
|
export declare function startKiroCliLogin(spawner?: ProcessSpawner): KiroLoginSession;
|
|
49
54
|
export declare function startKiroCliLoginOnce(spawner?: ProcessSpawner): KiroLoginSession;
|
|
55
|
+
export declare function runKiroLoginFlowOnce(options?: KiroLoginFlowOptions): Promise<boolean>;
|
|
50
56
|
export declare function redacted(value: string | undefined): string | undefined;
|
|
51
57
|
export declare function detectAuth(env?: NodeJS.ProcessEnv, runner?: CommandRunner): Promise<AuthDiagnostics>;
|
|
52
58
|
export declare function resolveApiKey(auth: () => Promise<{
|
package/dist/auth.js
CHANGED
|
@@ -11,6 +11,7 @@ export const DEFAULT_KIRO_CLI_TOKEN_KEYS = ["kirocli:odic:token", "codewhisperer
|
|
|
11
11
|
const LOGIN_REUSE_WINDOW_MS = 2 * 60 * 1000;
|
|
12
12
|
let sharedLoginSession;
|
|
13
13
|
let sharedLoginStartedAt = 0;
|
|
14
|
+
let sharedLoginFlow;
|
|
14
15
|
export async function runCommand(command, args, options = {}) {
|
|
15
16
|
try {
|
|
16
17
|
const result = await execFileAsync(command, [...args], { timeout: options.timeoutMs ?? 5000 });
|
|
@@ -95,11 +96,59 @@ export async function readKiroCliSessionCredential(options = {}, runner = runCom
|
|
|
95
96
|
function delay(ms) {
|
|
96
97
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
97
98
|
}
|
|
98
|
-
function
|
|
99
|
-
return /https?:\/\/[^\s"'<>]
|
|
99
|
+
function urls(text) {
|
|
100
|
+
return text.match(/https?:\/\/[^\s"'<>]+/g) ?? [];
|
|
101
|
+
}
|
|
102
|
+
function isLocalCallbackUrl(value) {
|
|
103
|
+
try {
|
|
104
|
+
const url = new URL(value);
|
|
105
|
+
const hostname = url.hostname.toLowerCase();
|
|
106
|
+
return ((hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]") &&
|
|
107
|
+
url.pathname.includes("/callback"));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function loginUrlFromLocalCallbackUrl(value) {
|
|
114
|
+
if (!isLocalCallbackUrl(value))
|
|
115
|
+
return undefined;
|
|
116
|
+
try {
|
|
117
|
+
const issuerUrl = new URL(value).searchParams.get("issuer_url");
|
|
118
|
+
if (!issuerUrl || isLocalCallbackUrl(issuerUrl))
|
|
119
|
+
return undefined;
|
|
120
|
+
const parsed = new URL(issuerUrl);
|
|
121
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed.toString() : undefined;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function isKiroSigninUrl(value) {
|
|
128
|
+
try {
|
|
129
|
+
const url = new URL(value);
|
|
130
|
+
return url.hostname.toLowerCase() === "app.kiro.dev" && url.pathname === "/signin";
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function firstLoginUrl(text) {
|
|
137
|
+
const candidates = urls(text);
|
|
138
|
+
const kiroSigninUrl = candidates.find(isKiroSigninUrl);
|
|
139
|
+
if (kiroSigninUrl)
|
|
140
|
+
return kiroSigninUrl;
|
|
141
|
+
for (const url of candidates) {
|
|
142
|
+
const callbackLoginUrl = loginUrlFromLocalCallbackUrl(url);
|
|
143
|
+
if (callbackLoginUrl)
|
|
144
|
+
return callbackLoginUrl;
|
|
145
|
+
if (!isLocalCallbackUrl(url))
|
|
146
|
+
return url;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
100
149
|
}
|
|
101
150
|
export function extractKiroLoginUrl(output) {
|
|
102
|
-
return
|
|
151
|
+
return firstLoginUrl(output) ?? KIRO_LOGIN_URL;
|
|
103
152
|
}
|
|
104
153
|
export function extractKiroLoginCode(output) {
|
|
105
154
|
return /\b[A-Z0-9]{4}-[A-Z0-9]{4}\b/.exec(output)?.[0] ?? /\b[A-Z0-9]{8,}\b/.exec(output)?.[0];
|
|
@@ -135,13 +184,13 @@ export function startKiroCliLogin(spawner = (command, args) => spawn(command, [.
|
|
|
135
184
|
while (Date.now() < deadline) {
|
|
136
185
|
if (extractKiroLoginCode(output))
|
|
137
186
|
return true;
|
|
138
|
-
if (
|
|
187
|
+
if (firstLoginUrl(output))
|
|
139
188
|
return true;
|
|
140
189
|
if (exited && exitCode !== 0)
|
|
141
190
|
return false;
|
|
142
191
|
await delay(100);
|
|
143
192
|
}
|
|
144
|
-
return Boolean(extractKiroLoginCode(output) ||
|
|
193
|
+
return Boolean(extractKiroLoginCode(output) || firstLoginUrl(output));
|
|
145
194
|
},
|
|
146
195
|
async waitForAuth(runner = runCommand) {
|
|
147
196
|
const deadline = Date.now() + 10 * 60 * 1000;
|
|
@@ -191,6 +240,16 @@ export function startKiroCliLoginOnce(spawner = (command, args) => spawn(command
|
|
|
191
240
|
sharedLoginStartedAt = now;
|
|
192
241
|
return wrapped;
|
|
193
242
|
}
|
|
243
|
+
export async function runKiroLoginFlowOnce(options = {}) {
|
|
244
|
+
sharedLoginFlow ??= (async () => {
|
|
245
|
+
const session = startKiroCliLoginOnce(options.spawner);
|
|
246
|
+
await session.waitForPrompt(options.promptTimeoutMs);
|
|
247
|
+
return session.waitForAuth(options.runner);
|
|
248
|
+
})().finally(() => {
|
|
249
|
+
sharedLoginFlow = undefined;
|
|
250
|
+
});
|
|
251
|
+
return sharedLoginFlow;
|
|
252
|
+
}
|
|
194
253
|
export function redacted(value) {
|
|
195
254
|
if (!value)
|
|
196
255
|
return undefined;
|
package/dist/cli-transport.d.ts
CHANGED
|
@@ -6,7 +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
|
|
9
|
+
readonly login?: () => Promise<boolean>;
|
|
10
10
|
readonly trustAllTools?: boolean;
|
|
11
11
|
readonly requestTimeoutMs?: number;
|
|
12
12
|
}
|
package/dist/cli-transport.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runCommand,
|
|
1
|
+
import { runCommand, runKiroLoginFlowOnce } 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,34 +84,22 @@ class AsyncQueue {
|
|
|
84
84
|
export class KiroCliChatTransport {
|
|
85
85
|
#runner;
|
|
86
86
|
#spawner;
|
|
87
|
-
#
|
|
87
|
+
#login;
|
|
88
88
|
#trustAllTools;
|
|
89
89
|
#requestTimeoutMs;
|
|
90
90
|
constructor(options = {}) {
|
|
91
91
|
this.#runner = options.runner ?? runCommand;
|
|
92
92
|
this.#spawner = options.spawner ?? ((command, args) => spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] }));
|
|
93
|
-
this.#
|
|
94
|
-
startKiroCliLoginOnce();
|
|
95
|
-
});
|
|
93
|
+
this.#login = options.login ?? runKiroLoginFlowOnce;
|
|
96
94
|
this.#trustAllTools = options.trustAllTools === true;
|
|
97
95
|
this.#requestTimeoutMs = options.requestTimeoutMs ?? 120_000;
|
|
98
96
|
}
|
|
99
|
-
#
|
|
100
|
-
try {
|
|
101
|
-
this.#loginStarter();
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// Keep the original model-call auth error visible to OpenCode.
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
async generate(request) {
|
|
97
|
+
async #runCliChat(request) {
|
|
108
98
|
const result = await this.#runner("kiro-cli", cliChatArgs(request, { trustAllTools: this.#trustAllTools }), {
|
|
109
99
|
timeoutMs: this.#requestTimeoutMs,
|
|
110
100
|
});
|
|
111
101
|
if (!result.ok) {
|
|
112
102
|
const authError = result.stderr.toLowerCase().includes("not logged in");
|
|
113
|
-
if (authError)
|
|
114
|
-
this.#startLoginAfterAuthFailure();
|
|
115
103
|
throw new KiroPluginError(result.stderr.trim() || "kiro-cli chat failed", authError ? "KIRO_AUTH_ERROR" : "KIRO_CLI_FAILED", authError ? 401 : 502);
|
|
116
104
|
}
|
|
117
105
|
const text = sanitizeCliChatOutput(result.stdout);
|
|
@@ -123,7 +111,20 @@ export class KiroCliChatTransport {
|
|
|
123
111
|
modelId: request.modelId,
|
|
124
112
|
};
|
|
125
113
|
}
|
|
126
|
-
async
|
|
114
|
+
async generate(request) {
|
|
115
|
+
try {
|
|
116
|
+
return await this.#runCliChat(request);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (!(error instanceof KiroPluginError) || error.code !== "KIRO_AUTH_ERROR")
|
|
120
|
+
throw error;
|
|
121
|
+
const authenticated = await this.#login();
|
|
122
|
+
if (!authenticated)
|
|
123
|
+
throw error;
|
|
124
|
+
return this.#runCliChat(request);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async *#streamCliChat(request) {
|
|
127
128
|
const child = this.#spawner("kiro-cli", cliChatArgs(request, { trustAllTools: this.#trustAllTools }));
|
|
128
129
|
const queue = new AsyncQueue();
|
|
129
130
|
let rawStdout = "";
|
|
@@ -163,8 +164,6 @@ export class KiroCliChatTransport {
|
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
165
166
|
const authError = stderr.toLowerCase().includes("not logged in");
|
|
166
|
-
if (authError)
|
|
167
|
-
this.#startLoginAfterAuthFailure();
|
|
168
167
|
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));
|
|
169
168
|
});
|
|
170
169
|
try {
|
|
@@ -176,4 +175,17 @@ export class KiroCliChatTransport {
|
|
|
176
175
|
child.kill();
|
|
177
176
|
}
|
|
178
177
|
}
|
|
178
|
+
async *stream(request) {
|
|
179
|
+
try {
|
|
180
|
+
yield* this.#streamCliChat(request);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if (!(error instanceof KiroPluginError) || error.code !== "KIRO_AUTH_ERROR")
|
|
184
|
+
throw error;
|
|
185
|
+
const authenticated = await this.#login();
|
|
186
|
+
if (!authenticated)
|
|
187
|
+
throw error;
|
|
188
|
+
yield* this.#streamCliChat(request);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
179
191
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { AcpConnection, AcpNotificationHandler, AcpStdioClientOptions, Json
|
|
|
3
3
|
export { acpPermissionResponse, KiroAcpTransport } from "./acp-transport.js";
|
|
4
4
|
export type { AcpSessionClient, KiroAcpTransportOptions } from "./acp-transport.js";
|
|
5
5
|
export { createKiroPlugin, effectiveBackend, KiroPlugin } from "./plugin.js";
|
|
6
|
-
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, startKiroCliLogin, startKiroCliLoginOnce, } from "./auth.js";
|
|
6
|
+
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, runKiroLoginFlowOnce, startKiroCliLogin, startKiroCliLoginOnce, } from "./auth.js";
|
|
7
7
|
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
8
8
|
export { createKiroFetch } from "./fetch-adapter.js";
|
|
9
9
|
export { loadOptions } from "./config.js";
|
|
@@ -12,7 +12,7 @@ export { additionalModelRequestFields, CodeWhispererKiroTransport, collectAssist
|
|
|
12
12
|
export { KiroRestTransport, toKiroRestPayload } from "./kiro-rest-transport.js";
|
|
13
13
|
export type { KiroRestTransportOptions } from "./kiro-rest-transport.js";
|
|
14
14
|
export { ModelCache } from "./model-cache.js";
|
|
15
|
-
export { discoverModelsFromCommand, parseDiscoveredModels, refreshModelCacheFromCommand } from "./model-discovery.js";
|
|
15
|
+
export { discoverModelsFromCommand, isModelDiscoveryAuthFailure, parseDiscoveredModels, refreshModelCacheFromCommand } from "./model-discovery.js";
|
|
16
16
|
export { ModelResolutionError, ModelResolver, normalizeModelName } from "./model-resolver.js";
|
|
17
17
|
export { FALLBACK_MODELS } from "./models.js";
|
|
18
18
|
export { toKiroGenerateRequest } from "./request-adapter.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { KiroPlugin } from "./plugin.js";
|
|
|
2
2
|
export { AcpJsonRpcClient, createAcpStdioClient, decodeJsonRpc, encodeJsonRpc } from "./acp-client.js";
|
|
3
3
|
export { acpPermissionResponse, KiroAcpTransport } from "./acp-transport.js";
|
|
4
4
|
export { createKiroPlugin, effectiveBackend, KiroPlugin } from "./plugin.js";
|
|
5
|
-
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, startKiroCliLogin, startKiroCliLoginOnce, } from "./auth.js";
|
|
5
|
+
export { detectAuth, extractKiroLoginUrl, KIRO_LOGIN_URL, readKiroCliSessionCredential, redacted, regionFromProfileArn, resolveApiKey, runKiroLoginFlowOnce, startKiroCliLogin, startKiroCliLoginOnce, } from "./auth.js";
|
|
6
6
|
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
7
7
|
export { createKiroFetch } from "./fetch-adapter.js";
|
|
8
8
|
export { loadOptions } from "./config.js";
|
|
@@ -10,7 +10,7 @@ export { getKiroCliStatus, getKiroCliVersion } from "./kiro-cli.js";
|
|
|
10
10
|
export { additionalModelRequestFields, CodeWhispererKiroTransport, collectAssistantText, createCodeWhispererClient, streamAssistantText, toGenerateAssistantResponseInput, usageFromMetadataEvent, } from "./kiro-transport.js";
|
|
11
11
|
export { KiroRestTransport, toKiroRestPayload } from "./kiro-rest-transport.js";
|
|
12
12
|
export { ModelCache } from "./model-cache.js";
|
|
13
|
-
export { discoverModelsFromCommand, parseDiscoveredModels, refreshModelCacheFromCommand } from "./model-discovery.js";
|
|
13
|
+
export { discoverModelsFromCommand, isModelDiscoveryAuthFailure, parseDiscoveredModels, refreshModelCacheFromCommand } from "./model-discovery.js";
|
|
14
14
|
export { ModelResolutionError, ModelResolver, normalizeModelName } from "./model-resolver.js";
|
|
15
15
|
export { FALLBACK_MODELS } from "./models.js";
|
|
16
16
|
export { toKiroGenerateRequest } from "./request-adapter.js";
|
|
@@ -17,7 +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
|
|
20
|
+
readonly login?: () => Promise<boolean>;
|
|
21
21
|
}
|
|
22
22
|
export declare function toKiroRestPayload(request: KiroGenerateRequest, profileArn?: string): Record<string, unknown>;
|
|
23
23
|
export declare class KiroRestTransport implements KiroTransport {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { KiroPluginError } from "./errors.js";
|
|
2
|
-
import { readKiroCliSessionCredential, regionFromProfileArn,
|
|
2
|
+
import { readKiroCliSessionCredential, regionFromProfileArn, runKiroLoginFlowOnce, } 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,28 +313,17 @@ export class KiroRestTransport {
|
|
|
313
313
|
#options;
|
|
314
314
|
#fetcher;
|
|
315
315
|
#credentialProvider;
|
|
316
|
-
#
|
|
316
|
+
#login;
|
|
317
317
|
constructor(options, dependencies = {}) {
|
|
318
318
|
this.#options = options;
|
|
319
319
|
this.#fetcher = dependencies.fetcher ?? fetch;
|
|
320
320
|
this.#credentialProvider = dependencies.credentialProvider ?? (() => readKiroCliSessionCredential());
|
|
321
|
-
this.#
|
|
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
|
-
}
|
|
321
|
+
this.#login = dependencies.login ?? runKiroLoginFlowOnce;
|
|
332
322
|
}
|
|
333
323
|
async #credentials() {
|
|
334
324
|
const session = this.#options.accessToken ? undefined : await this.#credentialProvider();
|
|
335
325
|
const accessToken = this.#options.accessToken ?? session?.accessToken;
|
|
336
326
|
if (!accessToken) {
|
|
337
|
-
this.#startLoginAfterAuthFailure();
|
|
338
327
|
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);
|
|
339
328
|
}
|
|
340
329
|
const profileArn = this.#options.profileArn ?? session?.profileArn;
|
|
@@ -347,7 +336,7 @@ export class KiroRestTransport {
|
|
|
347
336
|
async generate(request) {
|
|
348
337
|
return collectChunks(this.stream(request), request.modelId);
|
|
349
338
|
}
|
|
350
|
-
async
|
|
339
|
+
async *#streamOnce(request) {
|
|
351
340
|
const credentials = await this.#credentials();
|
|
352
341
|
const payload = toKiroRestPayload(request, credentials.profileArn);
|
|
353
342
|
let lastError;
|
|
@@ -368,8 +357,6 @@ export class KiroRestTransport {
|
|
|
368
357
|
lastError = new KiroPluginError(message, code, response.status);
|
|
369
358
|
if (response.status === 429 || response.status >= 500)
|
|
370
359
|
continue;
|
|
371
|
-
if (code === "KIRO_AUTH_ERROR")
|
|
372
|
-
this.#startLoginAfterAuthFailure();
|
|
373
360
|
throw lastError;
|
|
374
361
|
}
|
|
375
362
|
if (!response.body)
|
|
@@ -424,4 +411,17 @@ export class KiroRestTransport {
|
|
|
424
411
|
if (lastError)
|
|
425
412
|
throw lastError;
|
|
426
413
|
}
|
|
414
|
+
async *stream(request) {
|
|
415
|
+
try {
|
|
416
|
+
yield* this.#streamOnce(request);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
if (!(error instanceof KiroPluginError) || error.code !== "KIRO_AUTH_ERROR")
|
|
420
|
+
throw error;
|
|
421
|
+
const authenticated = await this.#login();
|
|
422
|
+
if (!authenticated)
|
|
423
|
+
throw error;
|
|
424
|
+
yield* this.#streamOnce(request);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
427
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { CommandRunner } from "./auth.js";
|
|
2
2
|
import type { CachedModelInfo, ModelCache } from "./model-cache.js";
|
|
3
|
+
export type KiroLoginFlowRunner = () => Promise<boolean>;
|
|
4
|
+
export interface ModelDiscoveryCommandOptions {
|
|
5
|
+
readonly runner?: CommandRunner;
|
|
6
|
+
readonly loginOnAuthFailure?: boolean;
|
|
7
|
+
readonly login?: KiroLoginFlowRunner;
|
|
8
|
+
}
|
|
3
9
|
export declare function parseDiscoveredModels(raw: string): CachedModelInfo[];
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
10
|
+
export declare function isModelDiscoveryAuthFailure(output: string): boolean;
|
|
11
|
+
export declare function discoverModelsFromCommand(command: string, args: ReadonlyArray<string>, runnerOrOptions?: CommandRunner | ModelDiscoveryCommandOptions): Promise<CachedModelInfo[]>;
|
|
12
|
+
export declare function refreshModelCacheFromCommand(cache: ModelCache, command: string, args: ReadonlyArray<string>, runnerOrOptions?: CommandRunner | ModelDiscoveryCommandOptions): Promise<CachedModelInfo[]>;
|
package/dist/model-discovery.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runCommand } from "./auth.js";
|
|
1
|
+
import { runCommand, runKiroLoginFlowOnce } from "./auth.js";
|
|
2
2
|
import { normalizeModelName } from "./model-resolver.js";
|
|
3
3
|
function record(value) {
|
|
4
4
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
@@ -76,17 +76,39 @@ export function parseDiscoveredModels(raw) {
|
|
|
76
76
|
}
|
|
77
77
|
return dedupe(fromLines(trimmed));
|
|
78
78
|
}
|
|
79
|
+
export function isModelDiscoveryAuthFailure(output) {
|
|
80
|
+
const text = output.toLowerCase();
|
|
81
|
+
return (text.includes("not logged in") ||
|
|
82
|
+
text.includes("not authenticated") ||
|
|
83
|
+
text.includes("unauthorized") ||
|
|
84
|
+
text.includes("authorization") ||
|
|
85
|
+
text.includes("authentication") ||
|
|
86
|
+
text.includes("auth token") ||
|
|
87
|
+
text.includes("token expired") ||
|
|
88
|
+
text.includes("expired token"));
|
|
89
|
+
}
|
|
79
90
|
function dedupe(models) {
|
|
80
91
|
return [...new Map(models.map((model) => [model.id, model])).values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
81
92
|
}
|
|
82
|
-
export async function discoverModelsFromCommand(command, args,
|
|
93
|
+
export async function discoverModelsFromCommand(command, args, runnerOrOptions = runCommand) {
|
|
94
|
+
const options = typeof runnerOrOptions === "function" ? { runner: runnerOrOptions } : runnerOrOptions;
|
|
95
|
+
const runner = options.runner ?? runCommand;
|
|
83
96
|
const result = await runner(command, [...args]);
|
|
84
|
-
if (!result.ok)
|
|
85
|
-
|
|
97
|
+
if (!result.ok) {
|
|
98
|
+
if (!options.loginOnAuthFailure || !isModelDiscoveryAuthFailure(`${result.stdout}\n${result.stderr}`))
|
|
99
|
+
return [];
|
|
100
|
+
const loggedIn = await (options.login ?? runKiroLoginFlowOnce)();
|
|
101
|
+
if (!loggedIn)
|
|
102
|
+
return [];
|
|
103
|
+
const retry = await runner(command, [...args]);
|
|
104
|
+
if (!retry.ok)
|
|
105
|
+
return [];
|
|
106
|
+
return parseDiscoveredModels(retry.stdout);
|
|
107
|
+
}
|
|
86
108
|
return parseDiscoveredModels(result.stdout);
|
|
87
109
|
}
|
|
88
|
-
export async function refreshModelCacheFromCommand(cache, command, args,
|
|
89
|
-
const models = await discoverModelsFromCommand(command, args,
|
|
110
|
+
export async function refreshModelCacheFromCommand(cache, command, args, runnerOrOptions = runCommand) {
|
|
111
|
+
const models = await discoverModelsFromCommand(command, args, runnerOrOptions);
|
|
90
112
|
if (models.length > 0)
|
|
91
113
|
cache.update(models);
|
|
92
114
|
return models;
|
package/dist/plugin.js
CHANGED
|
@@ -9,6 +9,8 @@ import { ModelCache } from "./model-cache.js";
|
|
|
9
9
|
import { refreshModelCacheFromCommand } from "./model-discovery.js";
|
|
10
10
|
import { ModelResolver, normalizeModelName } from "./model-resolver.js";
|
|
11
11
|
import { KiroRestTransport } from "./kiro-rest-transport.js";
|
|
12
|
+
const PLACEHOLDER_MODEL_ID = "auto";
|
|
13
|
+
const PLACEHOLDER_MODEL = { name: "Auto" };
|
|
12
14
|
function discoveredProviderModels(cache) {
|
|
13
15
|
return Object.fromEntries(cache.all().map((model) => [
|
|
14
16
|
model.id,
|
|
@@ -33,7 +35,7 @@ function modelRecord(value) {
|
|
|
33
35
|
function visibleProviderModels(cache, configuredModels, hiddenModels, disabledModels) {
|
|
34
36
|
const discovered = discoveredProviderModels(cache);
|
|
35
37
|
const modelIDs = new Set([...Object.keys(discovered), ...Object.keys(configuredModels), ...Object.keys(hiddenModels)]);
|
|
36
|
-
|
|
38
|
+
const models = Object.fromEntries([...modelIDs]
|
|
37
39
|
.filter((id) => !disabledModels.has(normalizeModelName(id)))
|
|
38
40
|
.map((id) => [
|
|
39
41
|
id,
|
|
@@ -42,6 +44,9 @@ function visibleProviderModels(cache, configuredModels, hiddenModels, disabledMo
|
|
|
42
44
|
...(configuredModels[id] ?? {}),
|
|
43
45
|
},
|
|
44
46
|
]));
|
|
47
|
+
if (Object.keys(models).length > 0 || disabledModels.has(PLACEHOLDER_MODEL_ID))
|
|
48
|
+
return models;
|
|
49
|
+
return { [PLACEHOLDER_MODEL_ID]: PLACEHOLDER_MODEL };
|
|
45
50
|
}
|
|
46
51
|
function bearerToken(init) {
|
|
47
52
|
const header = new Headers(init?.headers).get("authorization");
|
|
@@ -110,11 +115,11 @@ export function createKiroPlugin() {
|
|
|
110
115
|
const discoverIfStale = async () => {
|
|
111
116
|
if (options.modelDiscovery === "off" ||
|
|
112
117
|
options.modelDiscoveryCommand.length === 0 ||
|
|
113
|
-
|
|
118
|
+
discoveryAttempted) {
|
|
114
119
|
return;
|
|
115
120
|
}
|
|
116
121
|
discoveryAttempted = true;
|
|
117
|
-
discovery ??= refreshModelCacheFromCommand(modelCache, options.modelDiscoveryCommand[0], options.modelDiscoveryCommand.slice(1))
|
|
122
|
+
discovery ??= refreshModelCacheFromCommand(modelCache, options.modelDiscoveryCommand[0], options.modelDiscoveryCommand.slice(1), { loginOnAuthFailure: true })
|
|
118
123
|
.catch(() => undefined)
|
|
119
124
|
.then(() => {
|
|
120
125
|
discovery = undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bogyie/opencode-kiro-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "OpenCode plugin for using Kiro as a provider adapter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
],
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "git+https://github.com/
|
|
39
|
+
"url": "git+https://github.com/Bogyie/opencode-kiro-plugin.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/
|
|
42
|
+
"url": "https://github.com/Bogyie/opencode-kiro-plugin/issues"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://github.com/
|
|
44
|
+
"homepage": "https://github.com/Bogyie/opencode-kiro-plugin#readme",
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public",
|