@bogyie/opencode-kiro-plugin 0.2.5 → 0.2.7
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 +3 -3
- package/dist/cli-transport.d.ts +6 -1
- package/dist/cli-transport.js +105 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +41 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ The plugin resolves credentials in this order:
|
|
|
37
37
|
2. OpenCode auth input for provider `kiro`
|
|
38
38
|
3. `kiro-cli whoami` diagnostics for CLI session visibility
|
|
39
39
|
|
|
40
|
-
Direct fetch mode requires an API key/token usable by the Kiro/CodeWhisperer client. `cli-chat` mode uses the official `kiro-cli chat --no-interactive` surface and depends on the local Kiro CLI login state.
|
|
40
|
+
Direct fetch mode requires an API key/token usable by the Kiro/CodeWhisperer client. `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.
|
|
41
41
|
|
|
42
42
|
Use the `kiro_status` plugin tool to inspect provider id, backend, region, auth method, and discovered model count. Secrets are redacted in diagnostics.
|
|
43
43
|
|
|
@@ -65,9 +65,9 @@ Configure the backend through plugin options:
|
|
|
65
65
|
|
|
66
66
|
Supported values:
|
|
67
67
|
|
|
68
|
-
- `auto`: use CodeWhisperer fetch transport when an API key is available; otherwise use
|
|
68
|
+
- `auto`: use CodeWhisperer fetch transport when an API key is available; otherwise use streaming `cli-chat` with the local Kiro CLI login.
|
|
69
69
|
- `fetch`: require the direct Kiro/CodeWhisperer fetch path. If no usable auth is available, requests fail with a structured backend/auth error.
|
|
70
|
-
- `cli-chat`:
|
|
70
|
+
- `cli-chat`: spawn `kiro-cli chat --no-interactive --model <model>` and stream stdout chunks as they arrive. Chunk granularity is controlled by Kiro CLI.
|
|
71
71
|
- `acp`: launch `kiro-cli acp`, initialize a session, optionally set the requested model, send the prompt, and collect `AgentMessageChunk` notifications until `TurnEnd`.
|
|
72
72
|
|
|
73
73
|
`trustAllTools` affects both `cli-chat` and ACP permission handling. In ACP mode, permission requests are rejected by default and allowed only when `trustAllTools: true`.
|
package/dist/cli-transport.d.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import type { CommandRunner } from "./auth.js";
|
|
2
2
|
import type { KiroTransport } from "./fetch-adapter.js";
|
|
3
3
|
import type { KiroGenerateRequest } from "./request-adapter.js";
|
|
4
|
-
import type { KiroGenerateResponse } from "./response-adapter.js";
|
|
4
|
+
import type { KiroGenerateResponse, KiroStreamEvent } from "./response-adapter.js";
|
|
5
|
+
import { type ChildProcess } from "node:child_process";
|
|
5
6
|
export interface CliChatTransportOptions {
|
|
6
7
|
readonly runner?: CommandRunner;
|
|
8
|
+
readonly spawner?: CliChatSpawner;
|
|
7
9
|
readonly trustAllTools?: boolean;
|
|
8
10
|
readonly requestTimeoutMs?: number;
|
|
9
11
|
}
|
|
12
|
+
export type CliChatSpawner = (command: string, args: ReadonlyArray<string>) => ChildProcess;
|
|
10
13
|
export declare function promptForCli(request: KiroGenerateRequest): string;
|
|
11
14
|
export declare function cliChatArgs(request: KiroGenerateRequest, options?: Pick<CliChatTransportOptions, "trustAllTools">): string[];
|
|
12
15
|
export declare function sanitizeCliChatOutput(output: string): string;
|
|
16
|
+
export declare function sanitizeCliChatStreamingOutput(output: string): string;
|
|
13
17
|
export declare class KiroCliChatTransport implements KiroTransport {
|
|
14
18
|
#private;
|
|
15
19
|
constructor(options?: CliChatTransportOptions);
|
|
16
20
|
generate(request: KiroGenerateRequest): Promise<KiroGenerateResponse>;
|
|
21
|
+
stream(request: KiroGenerateRequest): AsyncIterable<KiroStreamEvent>;
|
|
17
22
|
}
|
package/dist/cli-transport.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runCommand } from "./auth.js";
|
|
2
2
|
import { KiroPluginError } from "./errors.js";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
3
4
|
export function promptForCli(request) {
|
|
4
5
|
const parts = [
|
|
5
6
|
request.system ? `System:\n${request.system}` : "",
|
|
@@ -29,12 +30,65 @@ export function sanitizeCliChatOutput(output) {
|
|
|
29
30
|
}
|
|
30
31
|
return lines.join("\n").trim();
|
|
31
32
|
}
|
|
33
|
+
export function sanitizeCliChatStreamingOutput(output) {
|
|
34
|
+
const text = output.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\r/g, "");
|
|
35
|
+
const lines = text
|
|
36
|
+
.split("\n")
|
|
37
|
+
.filter((line) => !line.includes("Credits:"));
|
|
38
|
+
if (lines[0]?.trimStart().startsWith(">")) {
|
|
39
|
+
lines[0] = lines[0].replace(/^\s*>\s*/, "");
|
|
40
|
+
}
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
class AsyncQueue {
|
|
44
|
+
#values = [];
|
|
45
|
+
#waiting = [];
|
|
46
|
+
#closed = false;
|
|
47
|
+
#error;
|
|
48
|
+
push(value) {
|
|
49
|
+
if (this.#closed || this.#error)
|
|
50
|
+
return;
|
|
51
|
+
this.#values.push(value);
|
|
52
|
+
this.#wake();
|
|
53
|
+
}
|
|
54
|
+
close() {
|
|
55
|
+
this.#closed = true;
|
|
56
|
+
this.#wake();
|
|
57
|
+
}
|
|
58
|
+
fail(error) {
|
|
59
|
+
this.#error = error;
|
|
60
|
+
this.#wake();
|
|
61
|
+
}
|
|
62
|
+
#wake() {
|
|
63
|
+
for (const resolve of this.#waiting.splice(0))
|
|
64
|
+
resolve();
|
|
65
|
+
}
|
|
66
|
+
async *[Symbol.asyncIterator]() {
|
|
67
|
+
for (;;) {
|
|
68
|
+
if (this.#values.length > 0) {
|
|
69
|
+
const value = this.#values.shift();
|
|
70
|
+
if (value !== undefined)
|
|
71
|
+
yield value;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (this.#error)
|
|
75
|
+
throw this.#error;
|
|
76
|
+
if (this.#closed)
|
|
77
|
+
return;
|
|
78
|
+
await new Promise((resolve) => {
|
|
79
|
+
this.#waiting.push(resolve);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
32
84
|
export class KiroCliChatTransport {
|
|
33
85
|
#runner;
|
|
86
|
+
#spawner;
|
|
34
87
|
#trustAllTools;
|
|
35
88
|
#requestTimeoutMs;
|
|
36
89
|
constructor(options = {}) {
|
|
37
90
|
this.#runner = options.runner ?? runCommand;
|
|
91
|
+
this.#spawner = options.spawner ?? ((command, args) => spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] }));
|
|
38
92
|
this.#trustAllTools = options.trustAllTools === true;
|
|
39
93
|
this.#requestTimeoutMs = options.requestTimeoutMs ?? 120_000;
|
|
40
94
|
}
|
|
@@ -55,4 +109,55 @@ export class KiroCliChatTransport {
|
|
|
55
109
|
modelId: request.modelId,
|
|
56
110
|
};
|
|
57
111
|
}
|
|
112
|
+
async *stream(request) {
|
|
113
|
+
const child = this.#spawner("kiro-cli", cliChatArgs(request, { trustAllTools: this.#trustAllTools }));
|
|
114
|
+
const queue = new AsyncQueue();
|
|
115
|
+
let rawStdout = "";
|
|
116
|
+
let emittedText = "";
|
|
117
|
+
let stderr = "";
|
|
118
|
+
let sawText = false;
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
child.kill();
|
|
121
|
+
queue.fail(new KiroPluginError("Timed out waiting for kiro-cli chat response.", "KIRO_TIMEOUT", 504));
|
|
122
|
+
}, this.#requestTimeoutMs);
|
|
123
|
+
child.stdout?.on("data", (chunk) => {
|
|
124
|
+
rawStdout += chunk.toString("utf8");
|
|
125
|
+
const cleaned = sanitizeCliChatStreamingOutput(rawStdout);
|
|
126
|
+
if (!cleaned.startsWith(emittedText))
|
|
127
|
+
return;
|
|
128
|
+
const delta = cleaned.slice(emittedText.length);
|
|
129
|
+
emittedText = cleaned;
|
|
130
|
+
if (!delta)
|
|
131
|
+
return;
|
|
132
|
+
sawText = true;
|
|
133
|
+
queue.push({ type: "text", text: delta, modelId: request.modelId });
|
|
134
|
+
});
|
|
135
|
+
child.stderr?.on("data", (chunk) => {
|
|
136
|
+
stderr += chunk.toString("utf8");
|
|
137
|
+
});
|
|
138
|
+
child.on("error", (error) => {
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
queue.fail(new KiroPluginError(error.message, "KIRO_CLI_FAILED", 502));
|
|
141
|
+
});
|
|
142
|
+
child.on("exit", (code, signal) => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
if (code === 0) {
|
|
145
|
+
if (sawText)
|
|
146
|
+
queue.close();
|
|
147
|
+
else
|
|
148
|
+
queue.fail(new KiroPluginError(stderr.trim() || "kiro-cli chat completed without returning assistant text", "KIRO_EMPTY_RESPONSE", 502));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const authError = stderr.toLowerCase().includes("not logged in");
|
|
152
|
+
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
|
+
});
|
|
154
|
+
try {
|
|
155
|
+
yield* queue;
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
if (!child.killed && child.exitCode === null)
|
|
160
|
+
child.kill();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
58
163
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { AcpJsonRpcClient, createAcpStdioClient, decodeJsonRpc, encodeJsonRpc }
|
|
|
2
2
|
export type { AcpConnection, AcpNotificationHandler, AcpStdioClientOptions, JsonRpcMessage } from "./acp-client.js";
|
|
3
3
|
export { acpPermissionResponse, KiroAcpTransport } from "./acp-transport.js";
|
|
4
4
|
export type { AcpSessionClient, KiroAcpTransportOptions } from "./acp-transport.js";
|
|
5
|
-
export { createKiroPlugin, KiroPlugin } from "./plugin.js";
|
|
5
|
+
export { createKiroPlugin, effectiveBackend, KiroPlugin } from "./plugin.js";
|
|
6
6
|
export { detectAuth, redacted, resolveApiKey } from "./auth.js";
|
|
7
7
|
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
8
8
|
export { createKiroFetch } from "./fetch-adapter.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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
|
-
export { createKiroPlugin, KiroPlugin } from "./plugin.js";
|
|
4
|
+
export { createKiroPlugin, effectiveBackend, KiroPlugin } from "./plugin.js";
|
|
5
5
|
export { detectAuth, redacted, resolveApiKey } from "./auth.js";
|
|
6
6
|
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
7
7
|
export { createKiroFetch } from "./fetch-adapter.js";
|
package/dist/plugin.d.ts
CHANGED
|
@@ -2,7 +2,10 @@ import type { Plugin } from "@opencode-ai/plugin";
|
|
|
2
2
|
import type { KiroAcpTransportOptions } from "./acp-transport.js";
|
|
3
3
|
import type { KiroPluginOptions } from "./config.js";
|
|
4
4
|
import type { KiroTransportOptions } from "./kiro-transport.js";
|
|
5
|
+
type EffectiveBackend = "fetch" | "cli-chat" | "acp" | "none";
|
|
6
|
+
export declare function effectiveBackend(options: Pick<KiroPluginOptions, "backend">, accessToken?: string): EffectiveBackend;
|
|
5
7
|
export declare function acpTransportOptions(options: Pick<KiroPluginOptions, "requestTimeoutMs" | "trustAllTools">): KiroAcpTransportOptions;
|
|
6
8
|
export declare function fetchTransportOptions(options: Pick<KiroPluginOptions, "region" | "endpoint" | "profileArn" | "userAgent" | "agentMode" | "maxAttempts" | "requestTimeoutMs">, accessToken: string): KiroTransportOptions;
|
|
7
9
|
export declare function createKiroPlugin(): Plugin;
|
|
8
10
|
export declare const KiroPlugin: Plugin;
|
|
11
|
+
export {};
|
package/dist/plugin.js
CHANGED
|
@@ -30,30 +30,52 @@ function modelRecord(value) {
|
|
|
30
30
|
return {};
|
|
31
31
|
return Object.fromEntries(Object.entries(value).filter((entry) => Boolean(entry[0]) && Boolean(entry[1]) && typeof entry[1] === "object" && !Array.isArray(entry[1])));
|
|
32
32
|
}
|
|
33
|
+
function visibleProviderModels(cache, configuredModels, hiddenModels, disabledModels) {
|
|
34
|
+
const discovered = discoveredProviderModels(cache);
|
|
35
|
+
const modelIDs = new Set([...Object.keys(discovered), ...Object.keys(configuredModels), ...Object.keys(hiddenModels)]);
|
|
36
|
+
return Object.fromEntries([...modelIDs]
|
|
37
|
+
.filter((id) => !disabledModels.has(normalizeModelName(id)))
|
|
38
|
+
.map((id) => [
|
|
39
|
+
id,
|
|
40
|
+
{
|
|
41
|
+
...(discovered[id] ?? {}),
|
|
42
|
+
...(configuredModels[id] ?? {}),
|
|
43
|
+
},
|
|
44
|
+
]));
|
|
45
|
+
}
|
|
33
46
|
function bearerToken(init) {
|
|
34
47
|
const header = new Headers(init?.headers).get("authorization");
|
|
35
48
|
const match = header?.match(/^Bearer\s+(.+)$/i);
|
|
36
49
|
return match?.[1] || undefined;
|
|
37
50
|
}
|
|
38
|
-
function
|
|
51
|
+
export function effectiveBackend(options, accessToken) {
|
|
52
|
+
const apiKey = accessToken || process.env.KIRO_API_KEY;
|
|
39
53
|
if (options.backend === "acp")
|
|
54
|
+
return "acp";
|
|
55
|
+
if (options.backend === "cli-chat")
|
|
56
|
+
return "cli-chat";
|
|
57
|
+
if (options.backend === "fetch")
|
|
58
|
+
return apiKey && apiKey !== "kiro-plugin-local-transport" ? "fetch" : "none";
|
|
59
|
+
if (apiKey && apiKey !== "kiro-plugin-local-transport")
|
|
60
|
+
return "fetch";
|
|
61
|
+
return "cli-chat";
|
|
62
|
+
}
|
|
63
|
+
function localTransport(options, accessToken) {
|
|
64
|
+
const backend = effectiveBackend(options, accessToken);
|
|
65
|
+
if (backend === "acp")
|
|
40
66
|
return new KiroAcpTransport(acpTransportOptions(options));
|
|
41
|
-
if (
|
|
67
|
+
if (backend === "cli-chat") {
|
|
42
68
|
return new KiroCliChatTransport({
|
|
43
69
|
trustAllTools: options.trustAllTools,
|
|
44
70
|
...(options.requestTimeoutMs ? { requestTimeoutMs: options.requestTimeoutMs } : {}),
|
|
45
71
|
});
|
|
46
72
|
}
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
if (backend === "fetch") {
|
|
74
|
+
const apiKey = accessToken || process.env.KIRO_API_KEY;
|
|
75
|
+
if (!apiKey || apiKey === "kiro-plugin-local-transport")
|
|
76
|
+
return undefined;
|
|
49
77
|
return new CodeWhispererKiroTransport(fetchTransportOptions(options, apiKey));
|
|
50
78
|
}
|
|
51
|
-
if (options.backend === "auto") {
|
|
52
|
-
return new KiroCliChatTransport({
|
|
53
|
-
trustAllTools: options.trustAllTools,
|
|
54
|
-
...(options.requestTimeoutMs ? { requestTimeoutMs: options.requestTimeoutMs } : {}),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
79
|
return undefined;
|
|
58
80
|
}
|
|
59
81
|
export function acpTransportOptions(options) {
|
|
@@ -80,6 +102,7 @@ export function createKiroPlugin() {
|
|
|
80
102
|
const modelCache = new ModelCache(options.modelCacheTtlSeconds);
|
|
81
103
|
let localServer;
|
|
82
104
|
let configuredModels = { ...options.extraModels };
|
|
105
|
+
let userModelOverrides;
|
|
83
106
|
const disabledModels = new Set(options.disabledModels.map(normalizeModelName));
|
|
84
107
|
if (Object.keys(options.extraModels).length > 0) {
|
|
85
108
|
modelCache.update(Object.keys(options.extraModels).map((id) => ({ id: normalizeModelName(id) })));
|
|
@@ -131,15 +154,16 @@ export function createKiroPlugin() {
|
|
|
131
154
|
config.provider[options.providerID] ??= {};
|
|
132
155
|
const provider = config.provider[options.providerID];
|
|
133
156
|
const server = await ensureLocalServer();
|
|
157
|
+
userModelOverrides ??= modelRecord(provider.models);
|
|
134
158
|
configuredModels = {
|
|
135
159
|
...options.extraModels,
|
|
136
|
-
...
|
|
160
|
+
...userModelOverrides,
|
|
137
161
|
};
|
|
138
162
|
provider.name ??= "Kiro";
|
|
139
163
|
provider.npm = "@ai-sdk/openai-compatible";
|
|
140
164
|
provider.api = server.baseURL;
|
|
141
165
|
provider.options ??= {};
|
|
142
|
-
provider.models = configuredModels;
|
|
166
|
+
provider.models = visibleProviderModels(modelCache, configuredModels, options.hiddenModels, disabledModels);
|
|
143
167
|
},
|
|
144
168
|
auth: {
|
|
145
169
|
provider: options.providerID,
|
|
@@ -185,6 +209,7 @@ export function createKiroPlugin() {
|
|
|
185
209
|
output: [
|
|
186
210
|
`provider: ${options.providerID}`,
|
|
187
211
|
`backend: ${options.backend}`,
|
|
212
|
+
`effective backend: ${effectiveBackend(options)}`,
|
|
188
213
|
`region: ${auth.region}`,
|
|
189
214
|
`auth: ${auth.method}`,
|
|
190
215
|
`authenticated: ${auth.authenticated ? "yes" : "no"}`,
|
|
@@ -194,6 +219,7 @@ export function createKiroPlugin() {
|
|
|
194
219
|
metadata: {
|
|
195
220
|
providerID: options.providerID,
|
|
196
221
|
backend: options.backend,
|
|
222
|
+
effectiveBackend: effectiveBackend(options),
|
|
197
223
|
region: auth.region,
|
|
198
224
|
authMethod: auth.method,
|
|
199
225
|
authenticated: auth.authenticated,
|
|
@@ -206,20 +232,13 @@ export function createKiroPlugin() {
|
|
|
206
232
|
id: options.providerID,
|
|
207
233
|
models: async (provider) => {
|
|
208
234
|
await discoverIfStale();
|
|
209
|
-
const discovered = discoveredProviderModels(modelCache);
|
|
210
235
|
const existing = modelRecord(provider.models);
|
|
211
|
-
const
|
|
212
|
-
...Object.keys(discovered),
|
|
213
|
-
...Object.keys(configuredModels),
|
|
214
|
-
...Object.keys(options.hiddenModels),
|
|
215
|
-
]);
|
|
236
|
+
const visibleModels = visibleProviderModels(modelCache, configuredModels, options.hiddenModels, disabledModels);
|
|
216
237
|
const server = await ensureLocalServer();
|
|
217
|
-
return Object.fromEntries(
|
|
218
|
-
.filter((id) => !disabledModels.has(normalizeModelName(id)))
|
|
238
|
+
return Object.fromEntries(Object.keys(visibleModels)
|
|
219
239
|
.map((id) => {
|
|
220
240
|
const model = {
|
|
221
|
-
...(
|
|
222
|
-
...(configuredModels[id] ?? {}),
|
|
241
|
+
...(visibleModels[id] ?? {}),
|
|
223
242
|
...(existing[id] ?? {}),
|
|
224
243
|
};
|
|
225
244
|
return [
|