@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.
- package/CHANGELOG.md +34 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/acp-client.d.ts +57 -0
- package/dist/acp-client.js +171 -0
- package/dist/acp-transport.d.ts +33 -0
- package/dist/acp-transport.js +358 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.js +77 -0
- package/dist/cli-transport.d.ts +17 -0
- package/dist/cli-transport.js +52 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +76 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +63 -0
- package/dist/fetch-adapter.d.ts +13 -0
- package/dist/fetch-adapter.js +35 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +20 -0
- package/dist/kiro-cli.d.ts +8 -0
- package/dist/kiro-cli.js +16 -0
- package/dist/kiro-transport.d.ts +33 -0
- package/dist/kiro-transport.js +250 -0
- package/dist/model-cache.d.ts +19 -0
- package/dist/model-cache.js +33 -0
- package/dist/model-discovery.d.ts +5 -0
- package/dist/model-discovery.js +76 -0
- package/dist/model-resolver.d.ts +29 -0
- package/dist/model-resolver.js +79 -0
- package/dist/models.d.ts +14 -0
- package/dist/models.js +70 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.js +188 -0
- package/dist/request-adapter.d.ts +93 -0
- package/dist/request-adapter.js +221 -0
- package/dist/response-adapter.d.ts +25 -0
- package/dist/response-adapter.js +108 -0
- package/docs/community-implementations.md +59 -0
- package/docs/e2e-validation.md +196 -0
- package/docs/implementation-plan.md +309 -0
- package/docs/implementation-strategy.md +241 -0
- package/docs/kiro-integration-notes.md +129 -0
- package/docs/opencode-provider-notes.md +87 -0
- package/docs/references.md +50 -0
- package/docs/research-summary.md +69 -0
- package/examples/opencode.jsonc +47 -0
- package/package.json +67 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export class KiroPluginError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
status;
|
|
4
|
+
constructor(message, code, status = 500) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.name = "KiroPluginError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class UnsupportedBackendError extends KiroPluginError {
|
|
12
|
+
constructor(message = "Kiro fetch transport is not implemented for this backend yet.") {
|
|
13
|
+
super(message, "UNSUPPORTED_BACKEND", 501);
|
|
14
|
+
this.name = "UnsupportedBackendError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function textOf(error) {
|
|
18
|
+
if (error instanceof Error)
|
|
19
|
+
return error.message;
|
|
20
|
+
if (typeof error === "string")
|
|
21
|
+
return error;
|
|
22
|
+
return "Unknown Kiro plugin error";
|
|
23
|
+
}
|
|
24
|
+
function nameOf(error) {
|
|
25
|
+
return error instanceof Error ? error.name : "";
|
|
26
|
+
}
|
|
27
|
+
function statusOf(error) {
|
|
28
|
+
if (!error || typeof error !== "object")
|
|
29
|
+
return undefined;
|
|
30
|
+
const status = error.status;
|
|
31
|
+
const metadataStatus = error.$metadata?.httpStatusCode;
|
|
32
|
+
return typeof status === "number" ? status : typeof metadataStatus === "number" ? metadataStatus : undefined;
|
|
33
|
+
}
|
|
34
|
+
export function normalizeError(error) {
|
|
35
|
+
if (error instanceof KiroPluginError)
|
|
36
|
+
return error;
|
|
37
|
+
const message = textOf(error);
|
|
38
|
+
const lower = `${nameOf(error)} ${message}`.toLowerCase();
|
|
39
|
+
const status = statusOf(error);
|
|
40
|
+
if (status === 401 || status === 403 || lower.includes("unauthorized") || lower.includes("not logged in")) {
|
|
41
|
+
return new KiroPluginError(message, "KIRO_AUTH_ERROR", status ?? 401);
|
|
42
|
+
}
|
|
43
|
+
if (status === 429 || lower.includes("rate limit") || lower.includes("quota")) {
|
|
44
|
+
return new KiroPluginError(message, "KIRO_RATE_LIMIT", 429);
|
|
45
|
+
}
|
|
46
|
+
if (status && status >= 500) {
|
|
47
|
+
return new KiroPluginError(message, "KIRO_UPSTREAM_ERROR", status);
|
|
48
|
+
}
|
|
49
|
+
if (lower.includes("network") || lower.includes("timeout") || lower.includes("econn")) {
|
|
50
|
+
return new KiroPluginError(message, "KIRO_NETWORK_ERROR", 502);
|
|
51
|
+
}
|
|
52
|
+
return new KiroPluginError(message, "KIRO_PLUGIN_ERROR");
|
|
53
|
+
}
|
|
54
|
+
export function errorResponse(error) {
|
|
55
|
+
const known = normalizeError(error);
|
|
56
|
+
return Response.json({
|
|
57
|
+
error: {
|
|
58
|
+
message: known.message,
|
|
59
|
+
type: known.code,
|
|
60
|
+
code: known.code,
|
|
61
|
+
},
|
|
62
|
+
}, { status: known.status });
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type KiroGenerateRequest } from "./request-adapter.js";
|
|
2
|
+
import { type KiroGenerateResponse, type KiroStreamEvent } from "./response-adapter.js";
|
|
3
|
+
import type { ModelResolver } from "./model-resolver.js";
|
|
4
|
+
export interface KiroTransport {
|
|
5
|
+
generate(request: KiroGenerateRequest): Promise<KiroGenerateResponse>;
|
|
6
|
+
stream?(request: KiroGenerateRequest): AsyncIterable<KiroStreamEvent>;
|
|
7
|
+
}
|
|
8
|
+
export interface KiroFetchOptions {
|
|
9
|
+
readonly resolver: ModelResolver;
|
|
10
|
+
readonly transport?: KiroTransport;
|
|
11
|
+
}
|
|
12
|
+
export type FetchAdapter = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
13
|
+
export declare function createKiroFetch(options: KiroFetchOptions): FetchAdapter;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { errorResponse, UnsupportedBackendError } from "./errors.js";
|
|
2
|
+
import { readOpenAIRequest, toKiroGenerateRequest } from "./request-adapter.js";
|
|
3
|
+
import { toOpenAIChatResponse, toOpenAIChatStreamResponse } from "./response-adapter.js";
|
|
4
|
+
const unsupportedTransport = {
|
|
5
|
+
async generate() {
|
|
6
|
+
throw new UnsupportedBackendError();
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
async function* streamGeneratedResponse(transport, request) {
|
|
10
|
+
const response = await transport.generate(request);
|
|
11
|
+
if (response.text)
|
|
12
|
+
yield { type: "text", text: response.text, modelId: response.modelId ?? request.modelId };
|
|
13
|
+
for (const toolCall of response.toolCalls ?? [])
|
|
14
|
+
yield toolCall;
|
|
15
|
+
}
|
|
16
|
+
export function createKiroFetch(options) {
|
|
17
|
+
const transport = options.transport ?? unsupportedTransport;
|
|
18
|
+
return async (input, init) => {
|
|
19
|
+
try {
|
|
20
|
+
const request = await readOpenAIRequest(input, init);
|
|
21
|
+
const kiroRequest = toKiroGenerateRequest(request, options.resolver);
|
|
22
|
+
if (request.stream === true && transport.stream) {
|
|
23
|
+
return toOpenAIChatStreamResponse(transport.stream(kiroRequest), kiroRequest.modelId);
|
|
24
|
+
}
|
|
25
|
+
if (request.stream === true) {
|
|
26
|
+
return toOpenAIChatStreamResponse(streamGeneratedResponse(transport, kiroRequest), kiroRequest.modelId);
|
|
27
|
+
}
|
|
28
|
+
const response = await transport.generate(kiroRequest);
|
|
29
|
+
return toOpenAIChatResponse(response, kiroRequest.modelId);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return errorResponse(error);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { AcpJsonRpcClient, createAcpStdioClient, decodeJsonRpc, encodeJsonRpc } from "./acp-client.js";
|
|
2
|
+
export type { AcpConnection, AcpNotificationHandler, AcpStdioClientOptions, JsonRpcMessage } from "./acp-client.js";
|
|
3
|
+
export { acpPermissionResponse, KiroAcpTransport } from "./acp-transport.js";
|
|
4
|
+
export type { AcpSessionClient, KiroAcpTransportOptions } from "./acp-transport.js";
|
|
5
|
+
export { createKiroPlugin, KiroPlugin } from "./plugin.js";
|
|
6
|
+
export { detectAuth, redacted, resolveApiKey } from "./auth.js";
|
|
7
|
+
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
8
|
+
export { createKiroFetch } from "./fetch-adapter.js";
|
|
9
|
+
export { loadOptions } from "./config.js";
|
|
10
|
+
export { getKiroCliStatus, getKiroCliVersion } from "./kiro-cli.js";
|
|
11
|
+
export { additionalModelRequestFields, CodeWhispererKiroTransport, collectAssistantText, createCodeWhispererClient, streamAssistantText, toGenerateAssistantResponseInput, usageFromMetadataEvent, } from "./kiro-transport.js";
|
|
12
|
+
export { ModelCache } from "./model-cache.js";
|
|
13
|
+
export { discoverModelsFromCommand, parseDiscoveredModels, refreshModelCacheFromCommand } from "./model-discovery.js";
|
|
14
|
+
export { ModelResolutionError, ModelResolver, normalizeModelName } from "./model-resolver.js";
|
|
15
|
+
export { FALLBACK_MODELS } from "./models.js";
|
|
16
|
+
export { toKiroGenerateRequest } from "./request-adapter.js";
|
|
17
|
+
export type { KiroGenerateRequest, KiroModelOptions } from "./request-adapter.js";
|
|
18
|
+
export { toOpenAIChatResponse, toOpenAIChatStreamResponse } from "./response-adapter.js";
|
|
19
|
+
export type { KiroStreamEvent, KiroToolCallChunk } from "./response-adapter.js";
|
|
20
|
+
declare const _default: {
|
|
21
|
+
id: string;
|
|
22
|
+
server: import("@opencode-ai/plugin").Plugin;
|
|
23
|
+
};
|
|
24
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { KiroPlugin } from "./plugin.js";
|
|
2
|
+
export { AcpJsonRpcClient, createAcpStdioClient, decodeJsonRpc, encodeJsonRpc } from "./acp-client.js";
|
|
3
|
+
export { acpPermissionResponse, KiroAcpTransport } from "./acp-transport.js";
|
|
4
|
+
export { createKiroPlugin, KiroPlugin } from "./plugin.js";
|
|
5
|
+
export { detectAuth, redacted, resolveApiKey } from "./auth.js";
|
|
6
|
+
export { cliChatArgs, KiroCliChatTransport, promptForCli } from "./cli-transport.js";
|
|
7
|
+
export { createKiroFetch } from "./fetch-adapter.js";
|
|
8
|
+
export { loadOptions } from "./config.js";
|
|
9
|
+
export { getKiroCliStatus, getKiroCliVersion } from "./kiro-cli.js";
|
|
10
|
+
export { additionalModelRequestFields, CodeWhispererKiroTransport, collectAssistantText, createCodeWhispererClient, streamAssistantText, toGenerateAssistantResponseInput, usageFromMetadataEvent, } from "./kiro-transport.js";
|
|
11
|
+
export { ModelCache } from "./model-cache.js";
|
|
12
|
+
export { discoverModelsFromCommand, parseDiscoveredModels, refreshModelCacheFromCommand } from "./model-discovery.js";
|
|
13
|
+
export { ModelResolutionError, ModelResolver, normalizeModelName } from "./model-resolver.js";
|
|
14
|
+
export { FALLBACK_MODELS } from "./models.js";
|
|
15
|
+
export { toKiroGenerateRequest } from "./request-adapter.js";
|
|
16
|
+
export { toOpenAIChatResponse, toOpenAIChatStreamResponse } from "./response-adapter.js";
|
|
17
|
+
export default {
|
|
18
|
+
id: "kiro",
|
|
19
|
+
server: KiroPlugin,
|
|
20
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AuthDiagnostics, CommandRunner } from "./auth.js";
|
|
2
|
+
export interface KiroCliStatus {
|
|
3
|
+
readonly installed: boolean;
|
|
4
|
+
readonly version?: string;
|
|
5
|
+
readonly auth: AuthDiagnostics;
|
|
6
|
+
}
|
|
7
|
+
export declare function getKiroCliVersion(runner?: CommandRunner): Promise<string | undefined>;
|
|
8
|
+
export declare function getKiroCliStatus(env?: NodeJS.ProcessEnv, runner?: CommandRunner): Promise<KiroCliStatus>;
|
package/dist/kiro-cli.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { detectAuth, runCommand } from "./auth.js";
|
|
2
|
+
export async function getKiroCliVersion(runner = runCommand) {
|
|
3
|
+
const result = await runner("kiro-cli", ["--version"]);
|
|
4
|
+
if (!result.ok)
|
|
5
|
+
return undefined;
|
|
6
|
+
return result.stdout.trim() || undefined;
|
|
7
|
+
}
|
|
8
|
+
export async function getKiroCliStatus(env = process.env, runner = runCommand) {
|
|
9
|
+
const version = await getKiroCliVersion(runner);
|
|
10
|
+
const auth = await detectAuth(env, runner);
|
|
11
|
+
return {
|
|
12
|
+
installed: version !== undefined,
|
|
13
|
+
auth,
|
|
14
|
+
...(version ? { version } : {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type GenerateAssistantResponseCommandInput, type GenerateAssistantResponseCommandOutput, type ChatResponseStream } from "@aws/codewhisperer-streaming-client";
|
|
2
|
+
import type { Command } from "@smithy/smithy-client";
|
|
3
|
+
import type { KiroTransport } from "./fetch-adapter.js";
|
|
4
|
+
import type { KiroGenerateRequest } from "./request-adapter.js";
|
|
5
|
+
import type { KiroGenerateResponse, KiroStreamEvent } from "./response-adapter.js";
|
|
6
|
+
export interface KiroTransportOptions {
|
|
7
|
+
readonly region: string;
|
|
8
|
+
readonly accessToken: string;
|
|
9
|
+
readonly endpoint?: string;
|
|
10
|
+
readonly profileArn?: string;
|
|
11
|
+
readonly userAgent?: string;
|
|
12
|
+
readonly agentMode?: string;
|
|
13
|
+
readonly maxAttempts?: number;
|
|
14
|
+
readonly requestTimeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CodeWhispererClientLike {
|
|
17
|
+
send(command: Command<any, any, any, any, any>): Promise<GenerateAssistantResponseCommandOutput>;
|
|
18
|
+
destroy?(): void;
|
|
19
|
+
}
|
|
20
|
+
export type CodeWhispererClientFactory = (options: KiroTransportOptions) => CodeWhispererClientLike;
|
|
21
|
+
export declare function additionalModelRequestFields(request: KiroGenerateRequest): Record<string, unknown> | undefined;
|
|
22
|
+
export declare function usageFromMetadataEvent(event: ChatResponseStream): KiroGenerateResponse["usage"] | undefined;
|
|
23
|
+
export declare function createCodeWhispererClient(options: KiroTransportOptions): CodeWhispererClientLike;
|
|
24
|
+
export declare function toGenerateAssistantResponseInput(request: KiroGenerateRequest, options?: Pick<KiroTransportOptions, "profileArn" | "agentMode">): GenerateAssistantResponseCommandInput;
|
|
25
|
+
export declare function collectAssistantText(stream: AsyncIterable<ChatResponseStream> | undefined): Promise<KiroGenerateResponse>;
|
|
26
|
+
export declare function streamAssistantText(stream: AsyncIterable<ChatResponseStream> | undefined): AsyncIterable<KiroStreamEvent>;
|
|
27
|
+
export declare class CodeWhispererKiroTransport implements KiroTransport {
|
|
28
|
+
#private;
|
|
29
|
+
constructor(options: KiroTransportOptions, factory?: CodeWhispererClientFactory);
|
|
30
|
+
generate(request: KiroGenerateRequest): Promise<KiroGenerateResponse>;
|
|
31
|
+
stream(request: KiroGenerateRequest): AsyncIterable<KiroStreamEvent>;
|
|
32
|
+
dispose(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { CodeWhispererStreamingClient, GenerateAssistantResponseCommand, } from "@aws/codewhisperer-streaming-client";
|
|
2
|
+
import { KiroPluginError } from "./errors.js";
|
|
3
|
+
const DEFAULT_USER_AGENT = "KiroIDE";
|
|
4
|
+
const DEFAULT_AGENT_MODE = "vibe";
|
|
5
|
+
export function additionalModelRequestFields(request) {
|
|
6
|
+
const fields = {};
|
|
7
|
+
if (request.modelOptions.temperature !== undefined)
|
|
8
|
+
fields.temperature = request.modelOptions.temperature;
|
|
9
|
+
if (request.modelOptions.maxTokens !== undefined)
|
|
10
|
+
fields.max_tokens = request.modelOptions.maxTokens;
|
|
11
|
+
if (request.modelOptions.reasoningEffort) {
|
|
12
|
+
fields.output_config = {
|
|
13
|
+
effort: request.modelOptions.reasoningEffort,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return Object.keys(fields).length > 0 ? fields : undefined;
|
|
17
|
+
}
|
|
18
|
+
function numberValue(value) {
|
|
19
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function usageFromMetadataEvent(event) {
|
|
22
|
+
const usage = event.metadataEvent?.tokenUsage;
|
|
23
|
+
if (!usage)
|
|
24
|
+
return undefined;
|
|
25
|
+
const uncachedInput = numberValue(usage.uncachedInputTokens) ?? 0;
|
|
26
|
+
const cacheRead = numberValue(usage.cacheReadInputTokens) ?? 0;
|
|
27
|
+
const cacheWrite = numberValue(usage.cacheWriteInputTokens) ?? 0;
|
|
28
|
+
const outputTokens = numberValue(usage.outputTokens);
|
|
29
|
+
return {
|
|
30
|
+
inputTokens: uncachedInput + cacheRead + cacheWrite,
|
|
31
|
+
...(outputTokens !== undefined ? { outputTokens } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function createCodeWhispererClient(options) {
|
|
35
|
+
const client = new CodeWhispererStreamingClient({
|
|
36
|
+
region: options.region,
|
|
37
|
+
endpoint: options.endpoint ?? `https://q.${options.region}.amazonaws.com`,
|
|
38
|
+
token: async () => ({ token: options.accessToken }),
|
|
39
|
+
customUserAgent: [[options.userAgent ?? DEFAULT_USER_AGENT]],
|
|
40
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
41
|
+
retryMode: "standard",
|
|
42
|
+
});
|
|
43
|
+
client.middlewareStack.add((next) => async (args) => {
|
|
44
|
+
args.request.headers["x-amzn-kiro-agent-mode"] = options.agentMode ?? DEFAULT_AGENT_MODE;
|
|
45
|
+
return next(args);
|
|
46
|
+
}, { step: "build", name: "addKiroAgentMode" });
|
|
47
|
+
return client;
|
|
48
|
+
}
|
|
49
|
+
export function toGenerateAssistantResponseInput(request, options = {}) {
|
|
50
|
+
const content = request.prompt;
|
|
51
|
+
const modelFields = additionalModelRequestFields(request);
|
|
52
|
+
return {
|
|
53
|
+
...(options.profileArn ? { profileArn: options.profileArn } : {}),
|
|
54
|
+
agentMode: options.agentMode ?? DEFAULT_AGENT_MODE,
|
|
55
|
+
...(modelFields ? { additionalModelRequestFields: modelFields } : {}),
|
|
56
|
+
conversationState: {
|
|
57
|
+
chatTriggerType: "MANUAL",
|
|
58
|
+
history: [
|
|
59
|
+
...(request.system
|
|
60
|
+
? [
|
|
61
|
+
{
|
|
62
|
+
userInputMessage: {
|
|
63
|
+
content: request.system,
|
|
64
|
+
origin: "AI_EDITOR",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
: []),
|
|
69
|
+
...request.history.map((turn) => turn.role === "assistant"
|
|
70
|
+
? {
|
|
71
|
+
assistantResponseMessage: {
|
|
72
|
+
content: turn.content,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
: {
|
|
76
|
+
userInputMessage: {
|
|
77
|
+
content: turn.content,
|
|
78
|
+
origin: "AI_EDITOR",
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
],
|
|
82
|
+
currentMessage: {
|
|
83
|
+
userInputMessage: {
|
|
84
|
+
content,
|
|
85
|
+
modelId: request.modelId,
|
|
86
|
+
origin: "AI_EDITOR",
|
|
87
|
+
...(request.images.length > 0
|
|
88
|
+
? {
|
|
89
|
+
images: request.images.map((image) => ({
|
|
90
|
+
format: image.format,
|
|
91
|
+
source: { bytes: image.bytes },
|
|
92
|
+
})),
|
|
93
|
+
}
|
|
94
|
+
: {}),
|
|
95
|
+
...(request.documents.length > 0
|
|
96
|
+
? {
|
|
97
|
+
documents: request.documents.map((document) => ({
|
|
98
|
+
name: document.name,
|
|
99
|
+
format: document.format,
|
|
100
|
+
source: { bytes: document.bytes },
|
|
101
|
+
})),
|
|
102
|
+
}
|
|
103
|
+
: {}),
|
|
104
|
+
userInputMessageContext: request.tools.length > 0 || request.toolResults.length > 0
|
|
105
|
+
? {
|
|
106
|
+
...(request.tools.length > 0
|
|
107
|
+
? {
|
|
108
|
+
tools: request.tools.map((item) => ({
|
|
109
|
+
toolSpecification: {
|
|
110
|
+
name: item.name,
|
|
111
|
+
...(item.description ? { description: item.description } : {}),
|
|
112
|
+
inputSchema: { json: item.inputSchema },
|
|
113
|
+
},
|
|
114
|
+
})),
|
|
115
|
+
}
|
|
116
|
+
: {}),
|
|
117
|
+
...(request.toolResults.length > 0
|
|
118
|
+
? {
|
|
119
|
+
toolResults: request.toolResults.map((item) => ({
|
|
120
|
+
toolUseId: item.toolUseId,
|
|
121
|
+
content: [{ text: item.content }],
|
|
122
|
+
status: "SUCCESS",
|
|
123
|
+
...(item.toolName ? { toolName: item.toolName } : {}),
|
|
124
|
+
})),
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
127
|
+
}
|
|
128
|
+
: undefined,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export async function collectAssistantText(stream) {
|
|
135
|
+
if (!stream)
|
|
136
|
+
return { text: "" };
|
|
137
|
+
let text = "";
|
|
138
|
+
let modelId;
|
|
139
|
+
let usage;
|
|
140
|
+
for await (const event of stream) {
|
|
141
|
+
if (event.assistantResponseEvent) {
|
|
142
|
+
text += event.assistantResponseEvent.content ?? "";
|
|
143
|
+
modelId = event.assistantResponseEvent.modelId ?? modelId;
|
|
144
|
+
}
|
|
145
|
+
usage = usageFromMetadataEvent(event) ?? usage;
|
|
146
|
+
if (event.error) {
|
|
147
|
+
throw new Error(event.error.message ?? "Kiro stream returned an error event");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
text,
|
|
152
|
+
...(modelId ? { modelId } : {}),
|
|
153
|
+
...(usage ? { usage } : {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export async function* streamAssistantText(stream) {
|
|
157
|
+
if (!stream)
|
|
158
|
+
return;
|
|
159
|
+
const toolInputs = new Map();
|
|
160
|
+
for await (const event of stream) {
|
|
161
|
+
if (event.assistantResponseEvent?.content) {
|
|
162
|
+
yield {
|
|
163
|
+
type: "text",
|
|
164
|
+
text: event.assistantResponseEvent.content,
|
|
165
|
+
...(event.assistantResponseEvent.modelId ? { modelId: event.assistantResponseEvent.modelId } : {}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (event.toolUseEvent?.toolUseId && event.toolUseEvent.name) {
|
|
169
|
+
const id = event.toolUseEvent.toolUseId;
|
|
170
|
+
const current = toolInputs.get(id) ?? { name: event.toolUseEvent.name, input: "" };
|
|
171
|
+
current.input += event.toolUseEvent.input ?? "";
|
|
172
|
+
current.name = event.toolUseEvent.name;
|
|
173
|
+
toolInputs.set(id, current);
|
|
174
|
+
if (event.toolUseEvent.stop) {
|
|
175
|
+
yield {
|
|
176
|
+
type: "tool_call",
|
|
177
|
+
id,
|
|
178
|
+
name: current.name,
|
|
179
|
+
arguments: current.input,
|
|
180
|
+
};
|
|
181
|
+
toolInputs.delete(id);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const usage = usageFromMetadataEvent(event);
|
|
185
|
+
if (usage) {
|
|
186
|
+
yield {
|
|
187
|
+
type: "text",
|
|
188
|
+
text: "",
|
|
189
|
+
usage,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (event.error) {
|
|
193
|
+
throw new Error(event.error.message ?? "Kiro stream returned an error event");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function collectChunks(chunks, fallbackModelId) {
|
|
198
|
+
let text = "";
|
|
199
|
+
let modelId;
|
|
200
|
+
let usage;
|
|
201
|
+
const toolCalls = [];
|
|
202
|
+
for await (const chunk of chunks) {
|
|
203
|
+
if (chunk.type === "tool_call") {
|
|
204
|
+
toolCalls.push(chunk);
|
|
205
|
+
modelId = chunk.modelId ?? modelId;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
text += chunk.text;
|
|
209
|
+
modelId = chunk.modelId ?? modelId;
|
|
210
|
+
if (chunk.usage)
|
|
211
|
+
usage = chunk.usage;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
text,
|
|
215
|
+
modelId: modelId ?? fallbackModelId,
|
|
216
|
+
...(toolCalls.length > 0 ? { toolCalls } : {}),
|
|
217
|
+
...(usage ? { usage } : {}),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
221
|
+
if (!timeoutMs)
|
|
222
|
+
return promise;
|
|
223
|
+
let timer;
|
|
224
|
+
const deadline = new Promise((_, reject) => {
|
|
225
|
+
timer = setTimeout(() => reject(new KiroPluginError(message, "KIRO_TIMEOUT", 504)), timeoutMs);
|
|
226
|
+
});
|
|
227
|
+
return Promise.race([promise, deadline]).finally(() => {
|
|
228
|
+
if (timer)
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
export class CodeWhispererKiroTransport {
|
|
233
|
+
#options;
|
|
234
|
+
#client;
|
|
235
|
+
constructor(options, factory = createCodeWhispererClient) {
|
|
236
|
+
this.#options = options;
|
|
237
|
+
this.#client = factory(options);
|
|
238
|
+
}
|
|
239
|
+
async generate(request) {
|
|
240
|
+
return collectChunks(this.stream(request), request.modelId);
|
|
241
|
+
}
|
|
242
|
+
async *stream(request) {
|
|
243
|
+
const input = toGenerateAssistantResponseInput(request, this.#options);
|
|
244
|
+
const output = await withTimeout(this.#client.send(new GenerateAssistantResponseCommand(input)), this.#options.requestTimeoutMs, "Timed out waiting for Kiro response.");
|
|
245
|
+
yield* streamAssistantText(output.generateAssistantResponseResponse);
|
|
246
|
+
}
|
|
247
|
+
dispose() {
|
|
248
|
+
this.#client.destroy?.();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CachedModelInfo {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly name?: string;
|
|
4
|
+
readonly contextLimit?: number;
|
|
5
|
+
readonly outputLimit?: number;
|
|
6
|
+
readonly raw?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare class ModelCache {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(ttlSeconds: number);
|
|
11
|
+
update(models: ReadonlyArray<CachedModelInfo>, now?: number): void;
|
|
12
|
+
get(id: string): CachedModelInfo | undefined;
|
|
13
|
+
has(id: string): boolean;
|
|
14
|
+
all(): CachedModelInfo[];
|
|
15
|
+
ids(): string[];
|
|
16
|
+
isEmpty(): boolean;
|
|
17
|
+
isStale(now?: number): boolean;
|
|
18
|
+
get updatedAt(): number | undefined;
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class ModelCache {
|
|
2
|
+
#ttlMs;
|
|
3
|
+
#models = new Map();
|
|
4
|
+
#updatedAt = 0;
|
|
5
|
+
constructor(ttlSeconds) {
|
|
6
|
+
this.#ttlMs = ttlSeconds * 1000;
|
|
7
|
+
}
|
|
8
|
+
update(models, now = Date.now()) {
|
|
9
|
+
this.#models = new Map(models.map((model) => [model.id, model]));
|
|
10
|
+
this.#updatedAt = now;
|
|
11
|
+
}
|
|
12
|
+
get(id) {
|
|
13
|
+
return this.#models.get(id);
|
|
14
|
+
}
|
|
15
|
+
has(id) {
|
|
16
|
+
return this.#models.has(id);
|
|
17
|
+
}
|
|
18
|
+
all() {
|
|
19
|
+
return [...this.#models.values()];
|
|
20
|
+
}
|
|
21
|
+
ids() {
|
|
22
|
+
return [...this.#models.keys()].sort();
|
|
23
|
+
}
|
|
24
|
+
isEmpty() {
|
|
25
|
+
return this.#models.size === 0;
|
|
26
|
+
}
|
|
27
|
+
isStale(now = Date.now()) {
|
|
28
|
+
return this.#updatedAt === 0 || now - this.#updatedAt > this.#ttlMs;
|
|
29
|
+
}
|
|
30
|
+
get updatedAt() {
|
|
31
|
+
return this.#updatedAt === 0 ? undefined : this.#updatedAt;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CommandRunner } from "./auth.js";
|
|
2
|
+
import type { CachedModelInfo, ModelCache } from "./model-cache.js";
|
|
3
|
+
export declare function parseDiscoveredModels(raw: string): CachedModelInfo[];
|
|
4
|
+
export declare function discoverModelsFromCommand(command: string, args: ReadonlyArray<string>, runner?: CommandRunner): Promise<CachedModelInfo[]>;
|
|
5
|
+
export declare function refreshModelCacheFromCommand(cache: ModelCache, command: string, args: ReadonlyArray<string>, runner?: CommandRunner): Promise<CachedModelInfo[]>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { runCommand } from "./auth.js";
|
|
2
|
+
import { normalizeModelName } from "./model-resolver.js";
|
|
3
|
+
function record(value) {
|
|
4
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
5
|
+
}
|
|
6
|
+
function stringValue(value) {
|
|
7
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
8
|
+
}
|
|
9
|
+
function fromItem(item) {
|
|
10
|
+
if (typeof item === "string") {
|
|
11
|
+
const id = normalizeModelName(item);
|
|
12
|
+
return id ? { id, raw: item } : undefined;
|
|
13
|
+
}
|
|
14
|
+
const object = record(item);
|
|
15
|
+
if (!object)
|
|
16
|
+
return undefined;
|
|
17
|
+
const id = stringValue(object.id) ?? stringValue(object.modelId) ?? stringValue(object.model) ?? stringValue(object.name);
|
|
18
|
+
if (!id)
|
|
19
|
+
return undefined;
|
|
20
|
+
const normalized = normalizeModelName(id);
|
|
21
|
+
if (!normalized)
|
|
22
|
+
return undefined;
|
|
23
|
+
const name = stringValue(object.name) ?? stringValue(object.displayName) ?? stringValue(object.label);
|
|
24
|
+
return {
|
|
25
|
+
id: normalized,
|
|
26
|
+
...(name ? { name } : {}),
|
|
27
|
+
raw: item,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function fromJson(value) {
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
return value.map(fromItem).filter((item) => item !== undefined);
|
|
33
|
+
const object = record(value);
|
|
34
|
+
if (!object)
|
|
35
|
+
return [];
|
|
36
|
+
const items = object.models ?? object.data ?? object.items;
|
|
37
|
+
return Array.isArray(items) ? fromJson(items) : [];
|
|
38
|
+
}
|
|
39
|
+
function fromLines(raw) {
|
|
40
|
+
return raw
|
|
41
|
+
.split(/\r?\n/)
|
|
42
|
+
.map((line) => line.trim())
|
|
43
|
+
.filter((line) => line && !line.includes(" ") && !line.includes("\t"))
|
|
44
|
+
.map(fromItem)
|
|
45
|
+
.filter((item) => item !== undefined);
|
|
46
|
+
}
|
|
47
|
+
export function parseDiscoveredModels(raw) {
|
|
48
|
+
const trimmed = raw.trim();
|
|
49
|
+
if (!trimmed)
|
|
50
|
+
return [];
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(trimmed);
|
|
53
|
+
const models = fromJson(parsed);
|
|
54
|
+
if (models.length > 0)
|
|
55
|
+
return dedupe(models);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Fall back to line parsing below.
|
|
59
|
+
}
|
|
60
|
+
return dedupe(fromLines(trimmed));
|
|
61
|
+
}
|
|
62
|
+
function dedupe(models) {
|
|
63
|
+
return [...new Map(models.map((model) => [model.id, model])).values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
64
|
+
}
|
|
65
|
+
export async function discoverModelsFromCommand(command, args, runner = runCommand) {
|
|
66
|
+
const result = await runner(command, [...args]);
|
|
67
|
+
if (!result.ok)
|
|
68
|
+
return [];
|
|
69
|
+
return parseDiscoveredModels(result.stdout);
|
|
70
|
+
}
|
|
71
|
+
export async function refreshModelCacheFromCommand(cache, command, args, runner = runCommand) {
|
|
72
|
+
const models = await discoverModelsFromCommand(command, args, runner);
|
|
73
|
+
if (models.length > 0)
|
|
74
|
+
cache.update(models);
|
|
75
|
+
return models;
|
|
76
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ModelCache } from "./model-cache.js";
|
|
2
|
+
export type ModelResolutionSource = "cache" | "hidden" | "passthrough";
|
|
3
|
+
export interface ModelResolution {
|
|
4
|
+
readonly internalID: string;
|
|
5
|
+
readonly source: ModelResolutionSource;
|
|
6
|
+
readonly original: string;
|
|
7
|
+
readonly normalized: string;
|
|
8
|
+
readonly verified: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ModelResolverOptions {
|
|
11
|
+
readonly cache: ModelCache;
|
|
12
|
+
readonly aliases?: Readonly<Record<string, string>>;
|
|
13
|
+
readonly hiddenModels?: Readonly<Record<string, string>>;
|
|
14
|
+
readonly disabledModels?: ReadonlyArray<string>;
|
|
15
|
+
readonly disablePassThrough?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class ModelResolutionError extends Error {
|
|
18
|
+
readonly model: string;
|
|
19
|
+
readonly suggestions: ReadonlyArray<string>;
|
|
20
|
+
constructor(message: string, model: string, suggestions: ReadonlyArray<string>);
|
|
21
|
+
}
|
|
22
|
+
export declare function normalizeModelName(input: string): string;
|
|
23
|
+
export declare class ModelResolver {
|
|
24
|
+
#private;
|
|
25
|
+
constructor(options: ModelResolverOptions);
|
|
26
|
+
resolve(model: string): ModelResolution;
|
|
27
|
+
availableModels(): string[];
|
|
28
|
+
suggestions(model: string): string[];
|
|
29
|
+
}
|