@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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface KiroGenerateResponse {
|
|
2
|
+
readonly text: string;
|
|
3
|
+
readonly modelId?: string;
|
|
4
|
+
readonly toolCalls?: ReadonlyArray<KiroToolCallChunk>;
|
|
5
|
+
readonly usage?: {
|
|
6
|
+
readonly inputTokens?: number;
|
|
7
|
+
readonly outputTokens?: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface KiroStreamChunk {
|
|
11
|
+
readonly type?: "text";
|
|
12
|
+
readonly text: string;
|
|
13
|
+
readonly modelId?: string;
|
|
14
|
+
readonly usage?: KiroGenerateResponse["usage"];
|
|
15
|
+
}
|
|
16
|
+
export interface KiroToolCallChunk {
|
|
17
|
+
readonly type: "tool_call";
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly arguments: string;
|
|
21
|
+
readonly modelId?: string;
|
|
22
|
+
}
|
|
23
|
+
export type KiroStreamEvent = KiroStreamChunk | KiroToolCallChunk;
|
|
24
|
+
export declare function toOpenAIChatResponse(response: KiroGenerateResponse, model: string): Response;
|
|
25
|
+
export declare function toOpenAIChatStreamResponse(chunks: AsyncIterable<KiroStreamEvent>, model: string): Response;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export function toOpenAIChatResponse(response, model) {
|
|
2
|
+
const toolCalls = response.toolCalls ?? [];
|
|
3
|
+
return Response.json({
|
|
4
|
+
id: `kiro-${crypto.randomUUID()}`,
|
|
5
|
+
object: "chat.completion",
|
|
6
|
+
created: Math.floor(Date.now() / 1000),
|
|
7
|
+
model: response.modelId ?? model,
|
|
8
|
+
choices: [
|
|
9
|
+
{
|
|
10
|
+
index: 0,
|
|
11
|
+
message: {
|
|
12
|
+
role: "assistant",
|
|
13
|
+
content: toolCalls.length > 0 ? response.text || null : response.text,
|
|
14
|
+
...(toolCalls.length > 0
|
|
15
|
+
? {
|
|
16
|
+
tool_calls: toolCalls.map((toolCall, index) => ({
|
|
17
|
+
index,
|
|
18
|
+
id: toolCall.id,
|
|
19
|
+
type: "function",
|
|
20
|
+
function: {
|
|
21
|
+
name: toolCall.name,
|
|
22
|
+
arguments: toolCall.arguments,
|
|
23
|
+
},
|
|
24
|
+
})),
|
|
25
|
+
}
|
|
26
|
+
: {}),
|
|
27
|
+
},
|
|
28
|
+
finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
usage: {
|
|
32
|
+
prompt_tokens: response.usage?.inputTokens ?? 0,
|
|
33
|
+
completion_tokens: response.usage?.outputTokens ?? 0,
|
|
34
|
+
total_tokens: (response.usage?.inputTokens ?? 0) + (response.usage?.outputTokens ?? 0),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function sse(data) {
|
|
39
|
+
return `data: ${JSON.stringify(data)}\n\n`;
|
|
40
|
+
}
|
|
41
|
+
export function toOpenAIChatStreamResponse(chunks, model) {
|
|
42
|
+
const encoder = new TextEncoder();
|
|
43
|
+
const body = new ReadableStream({
|
|
44
|
+
async start(controller) {
|
|
45
|
+
try {
|
|
46
|
+
const toolCallIndexes = new Map();
|
|
47
|
+
let nextToolCallIndex = 0;
|
|
48
|
+
let sawToolCall = false;
|
|
49
|
+
for await (const chunk of chunks) {
|
|
50
|
+
let delta;
|
|
51
|
+
if (chunk.type === "tool_call") {
|
|
52
|
+
sawToolCall = true;
|
|
53
|
+
let index = toolCallIndexes.get(chunk.id);
|
|
54
|
+
if (index === undefined) {
|
|
55
|
+
index = nextToolCallIndex;
|
|
56
|
+
nextToolCallIndex += 1;
|
|
57
|
+
toolCallIndexes.set(chunk.id, index);
|
|
58
|
+
}
|
|
59
|
+
delta = {
|
|
60
|
+
tool_calls: [
|
|
61
|
+
{
|
|
62
|
+
index,
|
|
63
|
+
id: chunk.id,
|
|
64
|
+
type: "function",
|
|
65
|
+
function: {
|
|
66
|
+
name: chunk.name,
|
|
67
|
+
arguments: chunk.arguments,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
if (!chunk.text)
|
|
75
|
+
continue;
|
|
76
|
+
delta = { content: chunk.text };
|
|
77
|
+
}
|
|
78
|
+
controller.enqueue(encoder.encode(sse({
|
|
79
|
+
id: `kiro-${crypto.randomUUID()}`,
|
|
80
|
+
object: "chat.completion.chunk",
|
|
81
|
+
created: Math.floor(Date.now() / 1000),
|
|
82
|
+
model: chunk.modelId ?? model,
|
|
83
|
+
choices: [
|
|
84
|
+
{
|
|
85
|
+
index: 0,
|
|
86
|
+
delta,
|
|
87
|
+
finish_reason: null,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
})));
|
|
91
|
+
}
|
|
92
|
+
controller.enqueue(encoder.encode(sse({ choices: [{ index: 0, delta: {}, finish_reason: sawToolCall ? "tool_calls" : "stop" }] })));
|
|
93
|
+
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
94
|
+
controller.close();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
controller.error(error);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
return new Response(body, {
|
|
102
|
+
headers: {
|
|
103
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
104
|
+
"cache-control": "no-cache",
|
|
105
|
+
connection: "keep-alive",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Community Implementations
|
|
2
|
+
|
|
3
|
+
조사일: 2026-07-07
|
|
4
|
+
|
|
5
|
+
## tickernelz/opencode-kiro-auth
|
|
6
|
+
|
|
7
|
+
Repository: https://github.com/tickernelz/opencode-kiro-auth
|
|
8
|
+
Package: `@zhafron/opencode-kiro-auth`
|
|
9
|
+
License: MIT
|
|
10
|
+
|
|
11
|
+
### What It Shows
|
|
12
|
+
|
|
13
|
+
이 레포는 OpenCode plugin 형태로 Kiro provider를 붙이는 가장 직접적인 참고 구현입니다.
|
|
14
|
+
|
|
15
|
+
핵심 구조:
|
|
16
|
+
|
|
17
|
+
- `package.json`
|
|
18
|
+
- ESM package
|
|
19
|
+
- `@opencode-ai/plugin` dependency
|
|
20
|
+
- `@aws/codewhisperer-streaming-client` dependency
|
|
21
|
+
- `src/plugin.ts`
|
|
22
|
+
- `config` hook에서 `provider.kiro`를 자동 주입
|
|
23
|
+
- `provider.kiro.npm = "@ai-sdk/openai-compatible"`
|
|
24
|
+
- provider-level `api`를 Kiro/Q endpoint base URL로 설정
|
|
25
|
+
- model metadata를 기본 config로 채움
|
|
26
|
+
- `auth.loader`에서 `baseURL`과 custom `fetch`를 반환
|
|
27
|
+
- `provider.models` hook에서 model api url/npm을 보정
|
|
28
|
+
- `src/plugin/sdk-client.ts`
|
|
29
|
+
- `CodeWhispererStreamingClient`를 생성
|
|
30
|
+
- `https://q.{region}.amazonaws.com` endpoint 사용
|
|
31
|
+
- `x-amzn-kiro-agent-mode: vibe` header 추가
|
|
32
|
+
- thinking effort를 `additionalModelRequestFields.output_config.effort`로 주입
|
|
33
|
+
- `src/plugin/request.ts`
|
|
34
|
+
- OpenAI-compatible request를 Kiro/CodeWhisperer request shape로 변환
|
|
35
|
+
- system prompt, tool calls, tool results, image input, thinking mode를 처리
|
|
36
|
+
- `src/constants.ts`
|
|
37
|
+
- Kiro auth endpoints, Q endpoint, model mapping, region helpers를 보유
|
|
38
|
+
|
|
39
|
+
### Useful Patterns
|
|
40
|
+
|
|
41
|
+
- OpenCode에는 OpenAI-compatible provider처럼 보이게 하고 custom `fetch`에서 실제 Kiro request로 변환합니다.
|
|
42
|
+
- `auth.loader`가 runtime credential과 fetch adapter를 함께 공급합니다.
|
|
43
|
+
- provider config 자동 주입으로 사용자의 `opencode.json` burden을 줄입니다.
|
|
44
|
+
- thinking 모델은 별도 virtual model 또는 variant로 표현할 수 있습니다.
|
|
45
|
+
|
|
46
|
+
### Cautions
|
|
47
|
+
|
|
48
|
+
- model mapping이 상수 중심이면 Kiro 모델 변경에 느리게 대응합니다.
|
|
49
|
+
- AWS/Kiro 내부 API 의존도가 있으므로 regression tests가 중요합니다.
|
|
50
|
+
- local Kiro CLI DB sync나 token handling은 사용자의 credential 저장소를 건드릴 수 있으므로 보수적으로 설계해야 합니다.
|
|
51
|
+
|
|
52
|
+
## Impact on This Project
|
|
53
|
+
|
|
54
|
+
기존 결론은 "Kiro 공식 LLM API가 문서화되지 않았으므로 CLI/ACP가 안전한 시작점"이었습니다. community 구현을 반영하면 구현 후보는 더 구체화됩니다.
|
|
55
|
+
|
|
56
|
+
권장 우선순위:
|
|
57
|
+
|
|
58
|
+
1. `opencode-kiro-auth` 패턴을 참고해 OpenCode plugin + custom fetch adapter를 prototype합니다.
|
|
59
|
+
2. 공식성/안정성이 더 필요한 사용자를 위해 CLI/ACP adapter를 별도 backend로 유지합니다.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# End-to-End Validation
|
|
2
|
+
|
|
3
|
+
This checklist verifies the plugin against a real OpenCode + Kiro environment. Unit tests cover adapters with fixtures; these checks require local Kiro auth and should be run before publishing a release.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Build the package:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install
|
|
11
|
+
npm run build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- Point OpenCode at this local plugin using `examples/opencode.jsonc` as a starting point.
|
|
15
|
+
- Run the diagnostic tool from OpenCode and confirm `kiro_status` reports the expected backend, region, and auth method.
|
|
16
|
+
|
|
17
|
+
## Backend: fetch
|
|
18
|
+
|
|
19
|
+
Use this when you have a token/API key that works with the CodeWhisperer/Kiro transport.
|
|
20
|
+
|
|
21
|
+
Config:
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
|
+
{
|
|
25
|
+
"plugin": [
|
|
26
|
+
[
|
|
27
|
+
"file:/absolute/path/to/opencode-kiro-plugin",
|
|
28
|
+
{
|
|
29
|
+
"backend": "fetch",
|
|
30
|
+
"region": "us-east-1",
|
|
31
|
+
"endpoint": "https://q.us-east-1.amazonaws.com",
|
|
32
|
+
"maxAttempts": 3,
|
|
33
|
+
"requestTimeoutMs": 120000,
|
|
34
|
+
"agentMode": "vibe"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Environment:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
export KIRO_API_KEY="..."
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Checks:
|
|
48
|
+
|
|
49
|
+
- Text prompt returns a normal assistant response.
|
|
50
|
+
- `stream: true` prompt renders incrementally.
|
|
51
|
+
- A prompt that triggers tool use returns OpenAI-compatible `tool_calls` in both streaming and non-streaming modes.
|
|
52
|
+
- A data URL image or PDF prompt reaches Kiro without external URL fetching.
|
|
53
|
+
- Invalid/expired auth returns `KIRO_AUTH_ERROR`, not a raw SDK exception.
|
|
54
|
+
- Rate/quota failure returns `KIRO_RATE_LIMIT`.
|
|
55
|
+
- Lowering `requestTimeoutMs` produces `KIRO_TIMEOUT`.
|
|
56
|
+
- Custom `endpoint`, `profileArn`, `userAgent`, and `agentMode` values are passed to the direct fetch transport when configured.
|
|
57
|
+
|
|
58
|
+
## Backend: cli-chat
|
|
59
|
+
|
|
60
|
+
Use this as the simplest official CLI fallback.
|
|
61
|
+
|
|
62
|
+
Config:
|
|
63
|
+
|
|
64
|
+
```jsonc
|
|
65
|
+
{
|
|
66
|
+
"plugin": [
|
|
67
|
+
[
|
|
68
|
+
"file:/absolute/path/to/opencode-kiro-plugin",
|
|
69
|
+
{
|
|
70
|
+
"backend": "cli-chat",
|
|
71
|
+
"requestTimeoutMs": 120000,
|
|
72
|
+
"trustAllTools": false
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Environment:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
kiro-cli whoami
|
|
83
|
+
kiro-cli chat --no-interactive "Say hello"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Checks:
|
|
87
|
+
|
|
88
|
+
- OpenCode prompt returns the same kind of text response as `kiro-cli chat --no-interactive`.
|
|
89
|
+
- Unauthenticated CLI state returns `KIRO_AUTH_ERROR`.
|
|
90
|
+
- CLI failures return `KIRO_CLI_FAILED` with a readable message.
|
|
91
|
+
- Model selection is documented as best-effort only; Kiro CLI does not currently expose a guaranteed per-request model flag for this path.
|
|
92
|
+
- If CLI responses take longer than the default, raise `requestTimeoutMs`; this timeout is passed to the child `kiro-cli chat` process.
|
|
93
|
+
|
|
94
|
+
## Backend: acp
|
|
95
|
+
|
|
96
|
+
Use this to validate the official Agent Client Protocol path.
|
|
97
|
+
|
|
98
|
+
Config:
|
|
99
|
+
|
|
100
|
+
```jsonc
|
|
101
|
+
{
|
|
102
|
+
"plugin": [
|
|
103
|
+
[
|
|
104
|
+
"file:/absolute/path/to/opencode-kiro-plugin",
|
|
105
|
+
{
|
|
106
|
+
"backend": "acp",
|
|
107
|
+
"requestTimeoutMs": 120000
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Environment:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
kiro-cli whoami
|
|
118
|
+
kiro-cli acp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Stop the standalone `kiro-cli acp` process after confirming it starts; the plugin will spawn its own process.
|
|
122
|
+
|
|
123
|
+
Checks:
|
|
124
|
+
|
|
125
|
+
- Text prompt creates an ACP session and returns assistant text.
|
|
126
|
+
- Streaming prompt emits `AgentMessageChunk` text through OpenAI-compatible SSE.
|
|
127
|
+
- Tool invocation emits OpenAI-compatible tool-call deltas.
|
|
128
|
+
- Permission requests are rejected by default; repeat with `trustAllTools: true` only in a disposable workspace if you need to verify allow flows.
|
|
129
|
+
- File input is sent as ACP embedded `resource` content; image input is sent as ACP `image` content.
|
|
130
|
+
- Timeout waiting for `TurnEnd` returns `KIRO_ACP_TIMEOUT`.
|
|
131
|
+
- Lowering `requestTimeoutMs` shortens ACP prompt and `TurnEnd` timeout handling.
|
|
132
|
+
- Missing `kiro-cli` or a process crash returns `KIRO_ACP_PROCESS_ERROR` or `KIRO_ACP_PROCESS_EXITED`.
|
|
133
|
+
|
|
134
|
+
## Model Churn Check
|
|
135
|
+
|
|
136
|
+
Before publishing, verify that new model ids can be used without a code release:
|
|
137
|
+
|
|
138
|
+
```jsonc
|
|
139
|
+
{
|
|
140
|
+
"plugin": [
|
|
141
|
+
[
|
|
142
|
+
"file:/absolute/path/to/opencode-kiro-plugin",
|
|
143
|
+
{
|
|
144
|
+
"extraModels": {
|
|
145
|
+
"new-model-id": {
|
|
146
|
+
"name": "New Model",
|
|
147
|
+
"limit": { "context": 200000, "output": 64000 },
|
|
148
|
+
"modalities": { "input": ["text"], "output": ["text"] },
|
|
149
|
+
"tool_call": true
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"modelAliases": {
|
|
153
|
+
"new-model": "new-model-id"
|
|
154
|
+
},
|
|
155
|
+
"modelDiscoveryCommand": ["kiro-cli", "models", "--json"]
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Checks:
|
|
163
|
+
|
|
164
|
+
- If your installed `kiro-cli` has a model-list command, set `modelDiscoveryCommand` to that command and confirm discovered models appear in provider metadata.
|
|
165
|
+
- The model appears in OpenCode provider metadata.
|
|
166
|
+
- The alias resolves to the configured id.
|
|
167
|
+
- Unknown models still pass through when `disableModelPassThrough` is false.
|
|
168
|
+
- Unknown models return suggestions when `disableModelPassThrough` is true.
|
|
169
|
+
|
|
170
|
+
## Release Gate
|
|
171
|
+
|
|
172
|
+
Run these local checks after any E2E pass:
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
npm test
|
|
176
|
+
npm run typecheck
|
|
177
|
+
npm run build
|
|
178
|
+
npm run smoke:package
|
|
179
|
+
npm pack --dry-run
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Record any real Kiro/API limitations in `CHANGELOG.md` before publishing.
|
|
183
|
+
|
|
184
|
+
## Latest Local Smoke
|
|
185
|
+
|
|
186
|
+
2026-07-07:
|
|
187
|
+
|
|
188
|
+
- `kiro-cli 2.6.1` and `opencode 1.17.14` were installed locally.
|
|
189
|
+
- `kiro-cli whoami` succeeded with an IAM Identity Center session; account identifiers were not recorded here.
|
|
190
|
+
- `kiro-cli chat --no-interactive "Say hello in one short sentence."` returned assistant text.
|
|
191
|
+
- Built `KiroCliChatTransport` returned assistant text with `requestTimeoutMs: 30000`.
|
|
192
|
+
- `opencode models kiro` in a temporary project using `file:/Users/boggy/repo/github.com/bogyie/opencode-kiro-plugin` listed `kiro/auto` and fallback models.
|
|
193
|
+
- `opencode run -m kiro/auto --format json "Say hello in one short sentence."` returned a text event and `finish_reason: stop` through the local plugin with `backend: "cli-chat"`.
|
|
194
|
+
- `kiro-cli acp --verbose` returned a valid `initialize` JSON-RPC response but also emitted a non-JSON stdout log line; the stdio client now ignores those log lines.
|
|
195
|
+
- Built `KiroAcpTransport` still failed at session creation with `KIRO_ACP_PROCESS_EXITED` because Kiro CLI 2.6.1 exits with code 0 after the current `session/new` request. Full ACP prompt E2E still needs the current Kiro ACP session schema confirmed before release.
|
|
196
|
+
- Full interactive OpenCode TUI E2E still needs to be run before release.
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Implementation Plan
|
|
2
|
+
|
|
3
|
+
목표: OpenCode에 Kiro provider를 plugin 형태로 추가한다.
|
|
4
|
+
|
|
5
|
+
기본 방향:
|
|
6
|
+
|
|
7
|
+
- 1차 제품 형태는 OpenCode server plugin입니다.
|
|
8
|
+
- OpenCode에는 `@ai-sdk/openai-compatible` provider처럼 보이게 합니다.
|
|
9
|
+
- 실제 요청은 plugin의 custom `fetch` adapter에서 Kiro/CodeWhisperer 요청으로 변환합니다.
|
|
10
|
+
- 모델 목록은 정적 whitelist가 아니라 dynamic resolver로 처리합니다.
|
|
11
|
+
- `kiro-cli acp`는 공식 경로 fallback이자 장기 안정성 검증 트랙으로 유지합니다.
|
|
12
|
+
|
|
13
|
+
## Non-Goals
|
|
14
|
+
|
|
15
|
+
- Kiro private token file을 기본 동작에서 직접 수정하지 않습니다.
|
|
16
|
+
- 모델 ID를 상수 whitelist로 막지 않습니다.
|
|
17
|
+
- 첫 버전에서 외부 proxy/server 배포까지 포함하지 않습니다.
|
|
18
|
+
|
|
19
|
+
## Milestone 0: Project Scaffold
|
|
20
|
+
|
|
21
|
+
목표: npm package로 빌드/테스트 가능한 OpenCode plugin 골격을 만든다.
|
|
22
|
+
|
|
23
|
+
작업:
|
|
24
|
+
|
|
25
|
+
- `package.json` 생성
|
|
26
|
+
- ESM package
|
|
27
|
+
- `@opencode-ai/plugin` dependency
|
|
28
|
+
- TypeScript, test runner 설정
|
|
29
|
+
- `tsconfig.json`, formatter 설정
|
|
30
|
+
- `src/index.ts`에서 default plugin module export
|
|
31
|
+
- `src/plugin.ts`에서 `config`, `auth`, `provider` hook skeleton 작성
|
|
32
|
+
- `test/`와 fixture 구조 생성
|
|
33
|
+
|
|
34
|
+
완료 기준:
|
|
35
|
+
|
|
36
|
+
- `npm test` 또는 선택한 test command가 실행됩니다.
|
|
37
|
+
- plugin package entrypoint가 import 가능합니다.
|
|
38
|
+
- OpenCode plugin module shape가 unit test로 검증됩니다.
|
|
39
|
+
|
|
40
|
+
## Milestone 1: Config Injection
|
|
41
|
+
|
|
42
|
+
목표: plugin 설치만으로 `provider.kiro` 기본 설정이 OpenCode config에 주입된다.
|
|
43
|
+
|
|
44
|
+
작업:
|
|
45
|
+
|
|
46
|
+
- `config` hook에서 `provider.kiro` 생성
|
|
47
|
+
- `npm: "@ai-sdk/openai-compatible"` 설정
|
|
48
|
+
- provider-level `api` 또는 `options.baseURL` 설정
|
|
49
|
+
- fallback model metadata 주입
|
|
50
|
+
- 사용자가 이미 정의한 provider/model 설정은 덮어쓰지 않고 merge
|
|
51
|
+
- `enabled_providers`/`disabled_providers`와 충돌하지 않게 처리
|
|
52
|
+
|
|
53
|
+
완료 기준:
|
|
54
|
+
|
|
55
|
+
- 빈 config에 Kiro provider가 생깁니다.
|
|
56
|
+
- 사용자 override가 유지됩니다.
|
|
57
|
+
- model limit, modality, variant metadata가 기본값으로 들어갑니다.
|
|
58
|
+
|
|
59
|
+
## Milestone 2: Model Resolver
|
|
60
|
+
|
|
61
|
+
목표: Kiro 모델 변경에 빠르게 대응할 수 있는 resolver를 만든다.
|
|
62
|
+
|
|
63
|
+
작업:
|
|
64
|
+
|
|
65
|
+
- `src/model-resolver.ts`
|
|
66
|
+
- alias resolution
|
|
67
|
+
- model name normalization
|
|
68
|
+
- hidden/manual model lookup
|
|
69
|
+
- optimistic pass-through
|
|
70
|
+
- suggestion generation
|
|
71
|
+
- `src/model-cache.ts`
|
|
72
|
+
- TTL cache
|
|
73
|
+
- stale detection
|
|
74
|
+
- refresh coordination
|
|
75
|
+
- fallback preset loading
|
|
76
|
+
- `src/models.ts`
|
|
77
|
+
- fallback preset only
|
|
78
|
+
- runtime validation에는 사용하지 않음
|
|
79
|
+
- config knobs
|
|
80
|
+
- `modelCacheTtlSeconds`
|
|
81
|
+
- `modelAliases`
|
|
82
|
+
- `hiddenModels`
|
|
83
|
+
- `disabledModels`
|
|
84
|
+
- `disableModelPassThrough`
|
|
85
|
+
|
|
86
|
+
완료 기준:
|
|
87
|
+
|
|
88
|
+
- `claude-sonnet-4-6`이 `claude-sonnet-4.6`으로 정규화됩니다.
|
|
89
|
+
- date suffix가 제거됩니다.
|
|
90
|
+
- alias가 먼저 적용됩니다.
|
|
91
|
+
- cache에 없는 모델도 pass-through됩니다.
|
|
92
|
+
- pass-through disable 시 명확한 에러와 suggestion을 반환합니다.
|
|
93
|
+
|
|
94
|
+
## Milestone 3: Auth and Environment Diagnostics
|
|
95
|
+
|
|
96
|
+
목표: 사용자가 Kiro 인증 상태를 명확히 알 수 있게 한다.
|
|
97
|
+
|
|
98
|
+
작업:
|
|
99
|
+
|
|
100
|
+
- `src/auth.ts`
|
|
101
|
+
- `KIRO_API_KEY` 감지
|
|
102
|
+
- `kiro-cli whoami` fallback
|
|
103
|
+
- region 설정 파싱
|
|
104
|
+
- auth method 상태 반환
|
|
105
|
+
- OpenCode `auth` hook
|
|
106
|
+
- API key 입력 방식
|
|
107
|
+
- login 안내 메시지
|
|
108
|
+
- custom fetch adapter에 credential 전달
|
|
109
|
+
- `kiro_status` custom tool 또는 diagnostic command
|
|
110
|
+
- plugin version
|
|
111
|
+
- region
|
|
112
|
+
- auth method
|
|
113
|
+
- model cache status
|
|
114
|
+
- selected backend
|
|
115
|
+
|
|
116
|
+
완료 기준:
|
|
117
|
+
|
|
118
|
+
- 인증이 없으면 실행 가능한 다음 단계가 표시됩니다.
|
|
119
|
+
- `KIRO_API_KEY`가 있으면 headless mode로 판단합니다.
|
|
120
|
+
- login session이 있으면 CLI 상태를 감지합니다.
|
|
121
|
+
- token 파일 내용을 로그에 출력하지 않습니다.
|
|
122
|
+
|
|
123
|
+
## Milestone 4: Custom Fetch Adapter Prototype
|
|
124
|
+
|
|
125
|
+
목표: OpenCode의 OpenAI-compatible request를 Kiro request로 변환해 응답을 반환한다.
|
|
126
|
+
|
|
127
|
+
작업:
|
|
128
|
+
|
|
129
|
+
- `src/fetch-adapter.ts`
|
|
130
|
+
- request body parse
|
|
131
|
+
- model resolver 적용
|
|
132
|
+
- OpenAI-compatible messages 변환
|
|
133
|
+
- system prompt 병합
|
|
134
|
+
- image input 변환
|
|
135
|
+
- tool definition 변환
|
|
136
|
+
- tool result 변환
|
|
137
|
+
- `src/kiro-client.ts`
|
|
138
|
+
- AWS/Kiro client wrapper
|
|
139
|
+
- region endpoint 구성
|
|
140
|
+
- retry boundary
|
|
141
|
+
- timeout 처리
|
|
142
|
+
- `src/response-adapter.ts`
|
|
143
|
+
- text response 변환
|
|
144
|
+
- streaming chunk 변환
|
|
145
|
+
- tool call 변환
|
|
146
|
+
- usage metadata best effort
|
|
147
|
+
|
|
148
|
+
완료 기준:
|
|
149
|
+
|
|
150
|
+
- 단순 text prompt가 OpenCode에서 Kiro로 전달됩니다.
|
|
151
|
+
- streaming off path가 동작합니다.
|
|
152
|
+
- Kiro 에러가 OpenCode 사용자에게 읽을 수 있는 메시지로 변환됩니다.
|
|
153
|
+
- fixture 기반 request/response 변환 테스트가 있습니다.
|
|
154
|
+
|
|
155
|
+
## Milestone 5: Streaming and Tool Calls
|
|
156
|
+
|
|
157
|
+
목표: OpenCode provider UX에 필요한 streaming/tool-call 품질을 확보한다.
|
|
158
|
+
|
|
159
|
+
작업:
|
|
160
|
+
|
|
161
|
+
- Kiro streaming event fixture 수집
|
|
162
|
+
- SSE stream 변환
|
|
163
|
+
- tool call start/delta/done event 매핑
|
|
164
|
+
- tool result continuation handling
|
|
165
|
+
- duplicate/orphan tool result 방어
|
|
166
|
+
- thinking/reasoning field 처리
|
|
167
|
+
|
|
168
|
+
완료 기준:
|
|
169
|
+
|
|
170
|
+
- streaming text가 OpenCode에서 점진적으로 표시됩니다.
|
|
171
|
+
- tool call이 OpenCode tool execution과 충돌하지 않습니다.
|
|
172
|
+
- tool result 이후 후속 응답이 이어집니다.
|
|
173
|
+
- reasoning/thinking model variant가 최소한 degraded mode로 동작합니다.
|
|
174
|
+
|
|
175
|
+
## Milestone 6: CLI/ACP Fallback Track
|
|
176
|
+
|
|
177
|
+
목표: custom fetch path가 깨졌을 때 사용할 공식 표면 fallback을 준비한다.
|
|
178
|
+
|
|
179
|
+
작업:
|
|
180
|
+
|
|
181
|
+
- `src/kiro-cli.ts`
|
|
182
|
+
- `kiro-cli` discovery
|
|
183
|
+
- version check
|
|
184
|
+
- `whoami`
|
|
185
|
+
- optional model list command probing
|
|
186
|
+
- `src/acp-client.ts`
|
|
187
|
+
- JSON-RPC stdio client
|
|
188
|
+
- initialize/session flow
|
|
189
|
+
- prompt/send/cancel
|
|
190
|
+
- backend selection
|
|
191
|
+
- `backend: "fetch" | "cli-chat" | "acp" | "auto"`
|
|
192
|
+
|
|
193
|
+
완료 기준:
|
|
194
|
+
|
|
195
|
+
- `backend=cli-chat`에서 non-interactive prompt가 동작합니다.
|
|
196
|
+
- `backend=acp` handshake fixture test가 있습니다.
|
|
197
|
+
- `backend=auto`는 fetch 실패 시 무조건 fallback하지 않고 명확한 policy로 동작합니다.
|
|
198
|
+
- `cli-chat`은 공식 `kiro-cli chat --no-interactive` 표면만 사용하므로, CLI가 별도 model flag를 공식화하기 전까지 model selection은 보장하지 않습니다.
|
|
199
|
+
|
|
200
|
+
## Milestone 7: Packaging and Documentation
|
|
201
|
+
|
|
202
|
+
목표: 사용자가 설치하고 문제를 진단할 수 있게 한다.
|
|
203
|
+
|
|
204
|
+
작업:
|
|
205
|
+
|
|
206
|
+
- README 사용법
|
|
207
|
+
- `opencode.json` 예시
|
|
208
|
+
- auth setup guide
|
|
209
|
+
- model resolver config guide
|
|
210
|
+
- troubleshooting guide
|
|
211
|
+
- license notice
|
|
212
|
+
- changelog
|
|
213
|
+
|
|
214
|
+
완료 기준:
|
|
215
|
+
|
|
216
|
+
- npm package로 설치 가능합니다.
|
|
217
|
+
- local path plugin으로 개발 테스트 가능합니다.
|
|
218
|
+
- README만 보고 최소 설정을 완료할 수 있습니다.
|
|
219
|
+
|
|
220
|
+
## Recommended Build Order
|
|
221
|
+
|
|
222
|
+
1. Milestone 0: scaffold
|
|
223
|
+
2. Milestone 2: model resolver
|
|
224
|
+
3. Milestone 1: config injection
|
|
225
|
+
4. Milestone 3: auth diagnostics
|
|
226
|
+
5. Milestone 4: custom fetch text-only prototype
|
|
227
|
+
6. Milestone 5: streaming/tool calls
|
|
228
|
+
7. Milestone 6: CLI/ACP fallback
|
|
229
|
+
8. Milestone 7: packaging/docs
|
|
230
|
+
|
|
231
|
+
이 순서로 가는 이유는 모델 resolver가 plugin config와 adapter 양쪽에서 공통으로 필요하고, Kiro 모델 변경 대응 요구사항을 가장 먼저 구조에 반영해야 하기 때문입니다.
|
|
232
|
+
|
|
233
|
+
## Initial File Layout
|
|
234
|
+
|
|
235
|
+
```txt
|
|
236
|
+
src/
|
|
237
|
+
index.ts
|
|
238
|
+
plugin.ts
|
|
239
|
+
config.ts
|
|
240
|
+
auth.ts
|
|
241
|
+
fetch-adapter.ts
|
|
242
|
+
response-adapter.ts
|
|
243
|
+
kiro-client.ts
|
|
244
|
+
kiro-cli.ts
|
|
245
|
+
acp-client.ts
|
|
246
|
+
model-cache.ts
|
|
247
|
+
model-resolver.ts
|
|
248
|
+
models.ts
|
|
249
|
+
errors.ts
|
|
250
|
+
logger.ts
|
|
251
|
+
test/
|
|
252
|
+
plugin.test.ts
|
|
253
|
+
config.test.ts
|
|
254
|
+
auth.test.ts
|
|
255
|
+
model-cache.test.ts
|
|
256
|
+
model-resolver.test.ts
|
|
257
|
+
fetch-adapter.test.ts
|
|
258
|
+
response-adapter.test.ts
|
|
259
|
+
fixtures/
|
|
260
|
+
openai-chat-request.json
|
|
261
|
+
kiro-generate-request.json
|
|
262
|
+
kiro-stream.txt
|
|
263
|
+
model-list.json
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Risk Controls
|
|
267
|
+
|
|
268
|
+
- License
|
|
269
|
+
- MIT 참고 코드도 그대로 복사하지 않고 독립 구현합니다.
|
|
270
|
+
|
|
271
|
+
- API stability
|
|
272
|
+
- Kiro endpoint/client wrapper를 한 모듈에 격리합니다.
|
|
273
|
+
- fixture tests로 request/response contract를 고정합니다.
|
|
274
|
+
|
|
275
|
+
- Model churn
|
|
276
|
+
- pass-through를 기본값으로 둡니다.
|
|
277
|
+
- fallback preset은 cache bootstrap과 UI 표시용으로만 씁니다.
|
|
278
|
+
|
|
279
|
+
- Credential safety
|
|
280
|
+
- token 값을 로그에 남기지 않습니다.
|
|
281
|
+
- token DB/file write는 기본 비활성화합니다.
|
|
282
|
+
- read-only credential discovery와 explicit API key를 우선합니다.
|
|
283
|
+
|
|
284
|
+
- User experience
|
|
285
|
+
- provider가 실패하면 "로그인 필요", "모델 미지원", "quota/rate limit", "network"를 구분합니다.
|
|
286
|
+
- unsupported model 에러에는 same-family suggestion을 제공합니다.
|
|
287
|
+
|
|
288
|
+
## First PR Scope
|
|
289
|
+
|
|
290
|
+
첫 PR은 transport 구현까지 욕심내지 않습니다.
|
|
291
|
+
|
|
292
|
+
포함:
|
|
293
|
+
|
|
294
|
+
- package scaffold
|
|
295
|
+
- plugin module skeleton
|
|
296
|
+
- config schema
|
|
297
|
+
- model resolver/cache
|
|
298
|
+
- config injection unit tests
|
|
299
|
+
- resolver unit tests
|
|
300
|
+
- README installation draft
|
|
301
|
+
|
|
302
|
+
제외:
|
|
303
|
+
|
|
304
|
+
- 실제 Kiro network call
|
|
305
|
+
- streaming
|
|
306
|
+
- tool call 변환
|
|
307
|
+
- ACP transport
|
|
308
|
+
|
|
309
|
+
첫 PR 완료 후 두 번째 PR에서 auth diagnostics와 text-only custom fetch prototype을 붙입니다.
|