@bogyie/opencode-kiro-plugin 0.1.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +21 -0
  3. package/README.md +187 -0
  4. package/dist/acp-client.d.ts +57 -0
  5. package/dist/acp-client.js +171 -0
  6. package/dist/acp-transport.d.ts +33 -0
  7. package/dist/acp-transport.js +358 -0
  8. package/dist/auth.d.ts +25 -0
  9. package/dist/auth.js +77 -0
  10. package/dist/cli-transport.d.ts +17 -0
  11. package/dist/cli-transport.js +52 -0
  12. package/dist/config.d.ts +27 -0
  13. package/dist/config.js +76 -0
  14. package/dist/errors.d.ts +10 -0
  15. package/dist/errors.js +63 -0
  16. package/dist/fetch-adapter.d.ts +13 -0
  17. package/dist/fetch-adapter.js +35 -0
  18. package/dist/index.d.ts +24 -0
  19. package/dist/index.js +20 -0
  20. package/dist/kiro-cli.d.ts +8 -0
  21. package/dist/kiro-cli.js +16 -0
  22. package/dist/kiro-transport.d.ts +33 -0
  23. package/dist/kiro-transport.js +250 -0
  24. package/dist/model-cache.d.ts +19 -0
  25. package/dist/model-cache.js +33 -0
  26. package/dist/model-discovery.d.ts +5 -0
  27. package/dist/model-discovery.js +76 -0
  28. package/dist/model-resolver.d.ts +29 -0
  29. package/dist/model-resolver.js +79 -0
  30. package/dist/models.d.ts +14 -0
  31. package/dist/models.js +70 -0
  32. package/dist/plugin.d.ts +8 -0
  33. package/dist/plugin.js +188 -0
  34. package/dist/request-adapter.d.ts +93 -0
  35. package/dist/request-adapter.js +221 -0
  36. package/dist/response-adapter.d.ts +25 -0
  37. package/dist/response-adapter.js +108 -0
  38. package/docs/community-implementations.md +59 -0
  39. package/docs/e2e-validation.md +196 -0
  40. package/docs/implementation-plan.md +309 -0
  41. package/docs/implementation-strategy.md +241 -0
  42. package/docs/kiro-integration-notes.md +129 -0
  43. package/docs/opencode-provider-notes.md +87 -0
  44. package/docs/references.md +50 -0
  45. package/docs/research-summary.md +69 -0
  46. package/examples/opencode.jsonc +47 -0
  47. package/package.json +67 -0
@@ -0,0 +1,358 @@
1
+ import { createAcpStdioClient, } from "./acp-client.js";
2
+ import { promptForCli } from "./cli-transport.js";
3
+ import { KiroPluginError } from "./errors.js";
4
+ function record(value) {
5
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
6
+ }
7
+ function arrayValue(value) {
8
+ return Array.isArray(value) ? value : [];
9
+ }
10
+ function stringValue(value) {
11
+ return typeof value === "string" && value ? value : undefined;
12
+ }
13
+ export function acpPermissionResponse(params, trustAllTools = false) {
14
+ const options = arrayValue(record(params)?.options).map(record).filter((item) => item !== undefined);
15
+ const preferredKinds = trustAllTools ? ["allow_always", "allow_once"] : ["reject_once", "reject_always"];
16
+ const selected = preferredKinds
17
+ .map((kind) => options.find((option) => option.kind === kind))
18
+ .find((option) => option !== undefined) ?? options[0];
19
+ const optionId = stringValue(selected?.optionId);
20
+ if (!optionId) {
21
+ throw new KiroPluginError("ACP permission request did not include selectable options.", "KIRO_ACP_PROTOCOL_ERROR", 502);
22
+ }
23
+ return {
24
+ outcome: {
25
+ outcome: "selected",
26
+ optionId,
27
+ },
28
+ };
29
+ }
30
+ function unsupportedAcpRequest(method) {
31
+ throw new KiroPluginError(`Unsupported ACP client request: ${method}`, "KIRO_ACP_UNSUPPORTED_REQUEST", 502);
32
+ }
33
+ function sessionIdFrom(result) {
34
+ const item = record(result);
35
+ const session = record(item?.session);
36
+ const id = stringValue(item?.sessionId) ?? stringValue(item?.id) ?? stringValue(session?.id);
37
+ if (!id) {
38
+ throw new KiroPluginError("ACP session/new response did not include a session id.", "KIRO_ACP_PROTOCOL_ERROR", 502);
39
+ }
40
+ return id;
41
+ }
42
+ function notificationUpdate(notification) {
43
+ if (notification.method !== "session/notification" && notification.method !== "session/update")
44
+ return undefined;
45
+ const params = record(notification.params);
46
+ return record(params?.update) ?? record(params?.notification) ?? params;
47
+ }
48
+ function updateType(update) {
49
+ const nested = record(update.update);
50
+ return (stringValue(update.type) ??
51
+ stringValue(update.sessionUpdate) ??
52
+ stringValue(nested?.type) ??
53
+ stringValue(nested?.sessionUpdate) ??
54
+ stringValue(update.kind) ??
55
+ stringValue(nested?.kind));
56
+ }
57
+ function textFromContent(value) {
58
+ if (typeof value === "string")
59
+ return value;
60
+ if (Array.isArray(value))
61
+ return value.map(textFromContent).filter(Boolean).join("");
62
+ const item = record(value);
63
+ if (!item)
64
+ return "";
65
+ return stringValue(item.text) ?? stringValue(item.content) ?? stringValue(item.delta) ?? "";
66
+ }
67
+ function textFromUpdate(update) {
68
+ const type = updateType(update);
69
+ if (type !== "AgentMessageChunk")
70
+ return "";
71
+ return (textFromContent(update.content) ||
72
+ textFromContent(update.text) ||
73
+ textFromContent(update.chunk) ||
74
+ textFromContent(record(update.delta)?.text));
75
+ }
76
+ function isTurnEnd(update) {
77
+ return updateType(update) === "TurnEnd";
78
+ }
79
+ function normalizeToolName(input) {
80
+ const name = input.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
81
+ return name || "tool";
82
+ }
83
+ function jsonArguments(value) {
84
+ if (value === undefined)
85
+ return "{}";
86
+ if (typeof value === "string")
87
+ return value;
88
+ return JSON.stringify(value);
89
+ }
90
+ function firstDefined(...values) {
91
+ return values.find((value) => value !== undefined);
92
+ }
93
+ function toolCallDetail(update) {
94
+ const detail = record(update.update) ?? update;
95
+ return (record(detail.toolCall) ??
96
+ record(detail.tool_call) ??
97
+ record(detail.call) ??
98
+ record(record(detail.delta)?.toolCall) ??
99
+ record(record(detail.delta)?.tool_call) ??
100
+ detail);
101
+ }
102
+ function toolCallFromUpdate(update, modelId) {
103
+ const type = updateType(update);
104
+ if (type !== "ToolCall" && type !== "ToolCallUpdate" && type !== "tool_call" && type !== "tool_call_update")
105
+ return undefined;
106
+ const detail = toolCallDetail(update);
107
+ const id = stringValue(detail.toolCallId) ??
108
+ stringValue(detail.tool_call_id) ??
109
+ stringValue(detail.id) ??
110
+ stringValue(detail.callId) ??
111
+ `acp-tool-${crypto.randomUUID()}`;
112
+ const explicitName = stringValue(detail.name) ??
113
+ stringValue(detail.toolName) ??
114
+ stringValue(detail.tool_name);
115
+ const rawArguments = firstDefined(detail.rawInput, detail.input, detail.parameters, detail.params, detail.args, detail.arguments);
116
+ if ((type === "ToolCallUpdate" || type === "tool_call_update") && rawArguments === undefined && !explicitName) {
117
+ return undefined;
118
+ }
119
+ const rawName = explicitName ?? (rawArguments !== undefined ? stringValue(detail.kind) ?? stringValue(detail.title) : undefined) ?? "tool";
120
+ return {
121
+ type: "tool_call",
122
+ id,
123
+ name: normalizeToolName(rawName),
124
+ arguments: jsonArguments(rawArguments),
125
+ modelId,
126
+ };
127
+ }
128
+ function acpPromptContent(request) {
129
+ const content = [{ type: "text", text: promptForCli(request) }];
130
+ for (const image of request.images) {
131
+ content.push({
132
+ type: "image",
133
+ mimeType: `image/${image.format === "jpeg" ? "jpeg" : image.format}`,
134
+ data: Buffer.from(image.bytes).toString("base64"),
135
+ });
136
+ }
137
+ for (const document of request.documents) {
138
+ const mimeType = documentMimeType(document.format);
139
+ const uri = `attachment://${encodeURIComponent(document.name)}`;
140
+ if (document.format === "txt" || document.format === "md" || document.format === "csv" || document.format === "html") {
141
+ content.push({
142
+ type: "resource",
143
+ resource: {
144
+ uri,
145
+ mimeType,
146
+ text: Buffer.from(document.bytes).toString("utf8"),
147
+ },
148
+ });
149
+ continue;
150
+ }
151
+ content.push({
152
+ type: "resource",
153
+ resource: {
154
+ uri,
155
+ mimeType,
156
+ blob: Buffer.from(document.bytes).toString("base64"),
157
+ },
158
+ });
159
+ }
160
+ return content;
161
+ }
162
+ function documentMimeType(format) {
163
+ if (format === "pdf")
164
+ return "application/pdf";
165
+ if (format === "txt")
166
+ return "text/plain";
167
+ if (format === "md")
168
+ return "text/markdown";
169
+ if (format === "csv")
170
+ return "text/csv";
171
+ if (format === "html")
172
+ return "text/html";
173
+ if (format === "doc")
174
+ return "application/msword";
175
+ if (format === "docx")
176
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
177
+ if (format === "xls")
178
+ return "application/vnd.ms-excel";
179
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
180
+ }
181
+ function timeout(promise, ms, message) {
182
+ let timer;
183
+ const deadline = new Promise((_, reject) => {
184
+ timer = setTimeout(() => reject(new KiroPluginError(message, "KIRO_ACP_TIMEOUT", 504)), ms);
185
+ });
186
+ return Promise.race([promise, deadline]).finally(() => {
187
+ if (timer)
188
+ clearTimeout(timer);
189
+ });
190
+ }
191
+ class AsyncQueue {
192
+ #values = [];
193
+ #waiting = [];
194
+ #closed = false;
195
+ #error;
196
+ push(value) {
197
+ if (this.#closed || this.#error)
198
+ return;
199
+ this.#values.push(value);
200
+ this.#wake();
201
+ }
202
+ close() {
203
+ this.#closed = true;
204
+ this.#wake();
205
+ }
206
+ fail(error) {
207
+ this.#error = error;
208
+ this.#wake();
209
+ }
210
+ #wake() {
211
+ for (const resolve of this.#waiting.splice(0))
212
+ resolve();
213
+ }
214
+ async *[Symbol.asyncIterator]() {
215
+ for (;;) {
216
+ if (this.#values.length > 0) {
217
+ const value = this.#values.shift();
218
+ if (value !== undefined)
219
+ yield value;
220
+ continue;
221
+ }
222
+ if (this.#error)
223
+ throw this.#error;
224
+ if (this.#closed)
225
+ return;
226
+ await new Promise((resolve) => {
227
+ this.#waiting.push(resolve);
228
+ });
229
+ }
230
+ }
231
+ }
232
+ export class KiroAcpTransport {
233
+ #options;
234
+ constructor(options = {}) {
235
+ this.#options = options;
236
+ }
237
+ #client() {
238
+ if (this.#options.client)
239
+ return { client: this.#options.client, owned: false };
240
+ const stdioOptions = {
241
+ ...(this.#options.command ? { command: this.#options.command } : {}),
242
+ ...(this.#options.args ? { args: this.#options.args } : {}),
243
+ ...(this.#options.cwd ? { cwd: this.#options.cwd } : {}),
244
+ };
245
+ return {
246
+ client: createAcpStdioClient({
247
+ ...stdioOptions,
248
+ requestHandler: this.#requestHandler(),
249
+ }),
250
+ owned: true,
251
+ };
252
+ }
253
+ #requestHandler() {
254
+ return (message) => {
255
+ if (message.method === "session/request_permission") {
256
+ return acpPermissionResponse(message.params, this.#options.trustAllTools === true);
257
+ }
258
+ return unsupportedAcpRequest(message.method);
259
+ };
260
+ }
261
+ async #startSession(client, request) {
262
+ await client.request("initialize", {
263
+ protocolVersion: 1,
264
+ clientCapabilities: {
265
+ fs: {
266
+ readTextFile: false,
267
+ writeTextFile: false,
268
+ },
269
+ terminal: false,
270
+ },
271
+ clientInfo: this.#options.clientInfo ?? {
272
+ name: "opencode-kiro-plugin",
273
+ version: "0.1.0",
274
+ },
275
+ });
276
+ const sessionId = sessionIdFrom(await client.request("session/new", {
277
+ cwd: this.#options.cwd ?? process.cwd(),
278
+ mcpServers: [],
279
+ }));
280
+ if (request.modelId !== "auto") {
281
+ await client.request("session/set_model", {
282
+ sessionId,
283
+ model: request.modelId,
284
+ });
285
+ }
286
+ return sessionId;
287
+ }
288
+ async generate(request) {
289
+ const chunks = [];
290
+ const toolCalls = new Map();
291
+ for await (const event of this.stream(request)) {
292
+ if (event.type === "tool_call") {
293
+ toolCalls.set(event.id, event);
294
+ continue;
295
+ }
296
+ chunks.push(event.text);
297
+ }
298
+ const collectedToolCalls = [...toolCalls.values()];
299
+ return {
300
+ text: chunks.join(""),
301
+ modelId: request.modelId,
302
+ ...(collectedToolCalls.length > 0 ? { toolCalls: collectedToolCalls } : {}),
303
+ };
304
+ }
305
+ async *stream(request) {
306
+ const { client, owned } = this.#client();
307
+ const promptTimeoutMs = this.#options.promptTimeoutMs ?? 120_000;
308
+ const queue = new AsyncQueue();
309
+ let sessionId;
310
+ let completed = false;
311
+ const seenToolCallPayloads = new Map();
312
+ const timer = setTimeout(() => {
313
+ queue.fail(new KiroPluginError("Timed out waiting for ACP TurnEnd notification.", "KIRO_ACP_TIMEOUT", 504));
314
+ }, promptTimeoutMs);
315
+ const unsubscribe = client.onNotification((notification) => {
316
+ const update = notificationUpdate(notification);
317
+ if (!update)
318
+ return;
319
+ const text = textFromUpdate(update);
320
+ if (text)
321
+ queue.push({ type: "text", text, modelId: request.modelId });
322
+ const toolCall = toolCallFromUpdate(update, request.modelId);
323
+ if (toolCall) {
324
+ const payload = `${toolCall.name}\n${toolCall.arguments}`;
325
+ if (seenToolCallPayloads.get(toolCall.id) !== payload) {
326
+ seenToolCallPayloads.set(toolCall.id, payload);
327
+ queue.push(toolCall);
328
+ }
329
+ }
330
+ if (isTurnEnd(update)) {
331
+ completed = true;
332
+ queue.close();
333
+ }
334
+ });
335
+ try {
336
+ sessionId = await this.#startSession(client, request);
337
+ const prompt = client.request("session/prompt", {
338
+ sessionId,
339
+ content: acpPromptContent(request),
340
+ }).catch((error) => {
341
+ queue.fail(error);
342
+ });
343
+ for await (const event of queue) {
344
+ yield event;
345
+ }
346
+ await timeout(prompt, promptTimeoutMs, "Timed out waiting for ACP prompt response.");
347
+ }
348
+ finally {
349
+ clearTimeout(timer);
350
+ unsubscribe();
351
+ if (sessionId && !completed) {
352
+ await client.request("session/cancel", { sessionId }).catch(() => undefined);
353
+ }
354
+ if (owned)
355
+ client.close?.();
356
+ }
357
+ }
358
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ export type AuthMethod = "api-key" | "cli-session" | "none";
2
+ export interface AuthDiagnostics {
3
+ readonly authenticated: boolean;
4
+ readonly method: AuthMethod;
5
+ readonly region: string;
6
+ readonly message: string;
7
+ readonly account?: string;
8
+ }
9
+ export interface CommandResult {
10
+ readonly ok: boolean;
11
+ readonly stdout: string;
12
+ readonly stderr: string;
13
+ readonly error?: unknown;
14
+ }
15
+ export interface CommandRunOptions {
16
+ readonly timeoutMs?: number;
17
+ }
18
+ export type CommandRunner = (command: string, args: ReadonlyArray<string>, options?: CommandRunOptions) => Promise<CommandResult>;
19
+ export declare function runCommand(command: string, args: ReadonlyArray<string>, options?: CommandRunOptions): Promise<CommandResult>;
20
+ export declare function redacted(value: string | undefined): string | undefined;
21
+ export declare function detectAuth(env?: NodeJS.ProcessEnv, runner?: CommandRunner): Promise<AuthDiagnostics>;
22
+ export declare function resolveApiKey(auth: () => Promise<{
23
+ type: string;
24
+ key?: string;
25
+ }>, env?: NodeJS.ProcessEnv): Promise<string>;
package/dist/auth.js ADDED
@@ -0,0 +1,77 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ export async function runCommand(command, args, options = {}) {
5
+ try {
6
+ const result = await execFileAsync(command, [...args], { timeout: options.timeoutMs ?? 5000 });
7
+ return { ok: true, stdout: result.stdout, stderr: result.stderr };
8
+ }
9
+ catch (error) {
10
+ const partial = error;
11
+ return {
12
+ ok: false,
13
+ stdout: partial.stdout ?? "",
14
+ stderr: partial.stderr ?? "",
15
+ error,
16
+ };
17
+ }
18
+ }
19
+ export function redacted(value) {
20
+ if (!value)
21
+ return undefined;
22
+ if (value.length <= 8)
23
+ return "***";
24
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
25
+ }
26
+ function parseWhoami(stdout) {
27
+ const trimmed = stdout.trim();
28
+ if (!trimmed)
29
+ return undefined;
30
+ try {
31
+ const parsed = JSON.parse(trimmed);
32
+ const account = parsed.email ?? parsed.username ?? parsed.account;
33
+ return typeof account === "string" ? account : undefined;
34
+ }
35
+ catch {
36
+ return trimmed.split(/\s+/)[0];
37
+ }
38
+ }
39
+ export async function detectAuth(env = process.env, runner = runCommand) {
40
+ const region = env.KIRO_REGION || env.AWS_REGION || "us-east-1";
41
+ if (env.KIRO_API_KEY) {
42
+ return {
43
+ authenticated: true,
44
+ method: "api-key",
45
+ region,
46
+ message: `Using KIRO_API_KEY (${redacted(env.KIRO_API_KEY)})`,
47
+ };
48
+ }
49
+ const whoami = await runner("kiro-cli", ["whoami"]);
50
+ if (whoami.ok) {
51
+ const account = parseWhoami(whoami.stdout);
52
+ return {
53
+ authenticated: true,
54
+ method: "cli-session",
55
+ region,
56
+ message: account ? `Using kiro-cli session for ${account}` : "Using active kiro-cli session",
57
+ ...(account ? { account } : {}),
58
+ };
59
+ }
60
+ return {
61
+ authenticated: false,
62
+ method: "none",
63
+ region,
64
+ message: "No KIRO_API_KEY or active kiro-cli session found. Run `kiro-cli login` or configure KIRO_API_KEY.",
65
+ };
66
+ }
67
+ export async function resolveApiKey(auth, env = process.env) {
68
+ if (env.KIRO_API_KEY)
69
+ return env.KIRO_API_KEY;
70
+ try {
71
+ const credential = await auth();
72
+ return credential.type === "api" && credential.key ? credential.key : "";
73
+ }
74
+ catch {
75
+ return "";
76
+ }
77
+ }
@@ -0,0 +1,17 @@
1
+ import type { CommandRunner } from "./auth.js";
2
+ import type { KiroTransport } from "./fetch-adapter.js";
3
+ import type { KiroGenerateRequest } from "./request-adapter.js";
4
+ import type { KiroGenerateResponse } from "./response-adapter.js";
5
+ export interface CliChatTransportOptions {
6
+ readonly runner?: CommandRunner;
7
+ readonly trustAllTools?: boolean;
8
+ readonly requestTimeoutMs?: number;
9
+ }
10
+ export declare function promptForCli(request: KiroGenerateRequest): string;
11
+ export declare function cliChatArgs(request: KiroGenerateRequest, options?: Pick<CliChatTransportOptions, "trustAllTools">): string[];
12
+ export declare function sanitizeCliChatOutput(output: string): string;
13
+ export declare class KiroCliChatTransport implements KiroTransport {
14
+ #private;
15
+ constructor(options?: CliChatTransportOptions);
16
+ generate(request: KiroGenerateRequest): Promise<KiroGenerateResponse>;
17
+ }
@@ -0,0 +1,52 @@
1
+ import { runCommand } from "./auth.js";
2
+ import { KiroPluginError } from "./errors.js";
3
+ export function promptForCli(request) {
4
+ const parts = [
5
+ request.system ? `System:\n${request.system}` : "",
6
+ ...request.history.map((turn) => `${turn.role}:\n${turn.content}`),
7
+ `user:\n${request.prompt}`,
8
+ ].filter(Boolean);
9
+ return parts.join("\n\n");
10
+ }
11
+ export function cliChatArgs(request, options = {}) {
12
+ return [
13
+ "chat",
14
+ "--no-interactive",
15
+ ...(options.trustAllTools ? ["--trust-all-tools"] : []),
16
+ promptForCli(request),
17
+ ];
18
+ }
19
+ export function sanitizeCliChatOutput(output) {
20
+ const text = output.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\r/g, "");
21
+ const lines = text
22
+ .split("\n")
23
+ .map((line) => line.trimEnd())
24
+ .filter((line) => !line.includes("Credits:"));
25
+ if (lines[0]?.trimStart().startsWith(">")) {
26
+ lines[0] = lines[0].replace(/^\s*>\s*/, "");
27
+ }
28
+ return lines.join("\n").trim();
29
+ }
30
+ export class KiroCliChatTransport {
31
+ #runner;
32
+ #trustAllTools;
33
+ #requestTimeoutMs;
34
+ constructor(options = {}) {
35
+ this.#runner = options.runner ?? runCommand;
36
+ this.#trustAllTools = options.trustAllTools === true;
37
+ this.#requestTimeoutMs = options.requestTimeoutMs ?? 120_000;
38
+ }
39
+ async generate(request) {
40
+ const result = await this.#runner("kiro-cli", cliChatArgs(request, { trustAllTools: this.#trustAllTools }), {
41
+ timeoutMs: this.#requestTimeoutMs,
42
+ });
43
+ if (!result.ok) {
44
+ const authError = result.stderr.toLowerCase().includes("not logged in");
45
+ throw new KiroPluginError(result.stderr.trim() || "kiro-cli chat failed", authError ? "KIRO_AUTH_ERROR" : "KIRO_CLI_FAILED", authError ? 401 : 502);
46
+ }
47
+ return {
48
+ text: sanitizeCliChatOutput(result.stdout),
49
+ modelId: request.modelId,
50
+ };
51
+ }
52
+ }
@@ -0,0 +1,27 @@
1
+ export type BackendMode = "auto" | "fetch" | "cli-chat" | "acp";
2
+ export type ModelDiscoveryMode = "auto" | "off";
3
+ export interface KiroPluginOptions {
4
+ readonly providerID: string;
5
+ readonly region: string;
6
+ readonly endpoint?: string;
7
+ readonly backend: BackendMode;
8
+ readonly modelDiscovery: ModelDiscoveryMode;
9
+ readonly modelDiscoveryCommand: ReadonlyArray<string>;
10
+ readonly modelCacheTtlSeconds: number;
11
+ readonly requestTimeoutMs?: number;
12
+ readonly maxAttempts: number;
13
+ readonly profileArn?: string;
14
+ readonly userAgent?: string;
15
+ readonly agentMode?: string;
16
+ readonly modelAliases: Readonly<Record<string, string>>;
17
+ readonly extraModels: Readonly<Record<string, Record<string, unknown>>>;
18
+ readonly hiddenModels: Readonly<Record<string, string>>;
19
+ readonly disabledModels: ReadonlyArray<string>;
20
+ readonly disableModelPassThrough: boolean;
21
+ readonly trustAllTools: boolean;
22
+ }
23
+ export declare const DEFAULT_PROVIDER_ID = "kiro";
24
+ export declare const DEFAULT_REGION = "us-east-1";
25
+ export declare const DEFAULT_MODEL_CACHE_TTL_SECONDS: number;
26
+ export declare const DEFAULT_MAX_ATTEMPTS = 3;
27
+ export declare function loadOptions(raw?: unknown): KiroPluginOptions;
package/dist/config.js ADDED
@@ -0,0 +1,76 @@
1
+ export const DEFAULT_PROVIDER_ID = "kiro";
2
+ export const DEFAULT_REGION = "us-east-1";
3
+ export const DEFAULT_MODEL_CACHE_TTL_SECONDS = 6 * 60 * 60;
4
+ export const DEFAULT_MAX_ATTEMPTS = 3;
5
+ const BACKENDS = new Set(["auto", "fetch", "cli-chat", "acp"]);
6
+ const DISCOVERY_MODES = new Set(["auto", "off"]);
7
+ function stringRecord(value) {
8
+ if (!value || typeof value !== "object" || Array.isArray(value))
9
+ return {};
10
+ return Object.fromEntries(Object.entries(value)
11
+ .filter((entry) => typeof entry[1] === "string")
12
+ .map(([key, item]) => [key, item]));
13
+ }
14
+ function stringArray(value) {
15
+ if (!Array.isArray(value))
16
+ return [];
17
+ return value.filter((item) => typeof item === "string");
18
+ }
19
+ function objectRecord(value) {
20
+ if (!value || typeof value !== "object" || Array.isArray(value))
21
+ return {};
22
+ return Object.fromEntries(Object.entries(value).filter((entry) => Boolean(entry[0]) && Boolean(entry[1]) && typeof entry[1] === "object" && !Array.isArray(entry[1])));
23
+ }
24
+ function positiveNumber(value, fallback) {
25
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
26
+ return fallback;
27
+ return value;
28
+ }
29
+ function positiveInteger(value, fallback) {
30
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1)
31
+ return fallback;
32
+ return Math.max(1, Math.floor(value));
33
+ }
34
+ function optionalPositiveNumber(value) {
35
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
36
+ return undefined;
37
+ return value;
38
+ }
39
+ function optionalString(value) {
40
+ if (typeof value !== "string")
41
+ return undefined;
42
+ const trimmed = value.trim();
43
+ return trimmed || undefined;
44
+ }
45
+ export function loadOptions(raw = {}) {
46
+ const input = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
47
+ const backend = typeof input.backend === "string" && BACKENDS.has(input.backend) ? input.backend : "auto";
48
+ const modelDiscovery = typeof input.modelDiscovery === "string" && DISCOVERY_MODES.has(input.modelDiscovery)
49
+ ? input.modelDiscovery
50
+ : "auto";
51
+ const requestTimeoutMs = optionalPositiveNumber(input.requestTimeoutMs);
52
+ const endpoint = optionalString(input.endpoint);
53
+ const profileArn = optionalString(input.profileArn);
54
+ const userAgent = optionalString(input.userAgent);
55
+ const agentMode = optionalString(input.agentMode);
56
+ return {
57
+ providerID: typeof input.providerID === "string" && input.providerID ? input.providerID : DEFAULT_PROVIDER_ID,
58
+ region: typeof input.region === "string" && input.region ? input.region : DEFAULT_REGION,
59
+ ...(endpoint ? { endpoint } : {}),
60
+ backend,
61
+ modelDiscovery,
62
+ modelDiscoveryCommand: stringArray(input.modelDiscoveryCommand),
63
+ modelCacheTtlSeconds: positiveNumber(input.modelCacheTtlSeconds, DEFAULT_MODEL_CACHE_TTL_SECONDS),
64
+ ...(requestTimeoutMs ? { requestTimeoutMs } : {}),
65
+ maxAttempts: positiveInteger(input.maxAttempts, DEFAULT_MAX_ATTEMPTS),
66
+ ...(profileArn ? { profileArn } : {}),
67
+ ...(userAgent ? { userAgent } : {}),
68
+ ...(agentMode ? { agentMode } : {}),
69
+ modelAliases: stringRecord(input.modelAliases),
70
+ extraModels: objectRecord(input.extraModels),
71
+ hiddenModels: stringRecord(input.hiddenModels),
72
+ disabledModels: stringArray(input.disabledModels),
73
+ disableModelPassThrough: input.disableModelPassThrough === true,
74
+ trustAllTools: input.trustAllTools === true,
75
+ };
76
+ }
@@ -0,0 +1,10 @@
1
+ export declare class KiroPluginError extends Error {
2
+ readonly code: string;
3
+ readonly status: number;
4
+ constructor(message: string, code: string, status?: number);
5
+ }
6
+ export declare class UnsupportedBackendError extends KiroPluginError {
7
+ constructor(message?: string);
8
+ }
9
+ export declare function normalizeError(error: unknown): KiroPluginError;
10
+ export declare function errorResponse(error: unknown): Response;