@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,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을 붙입니다.