@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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - Unreleased
|
|
4
|
+
|
|
5
|
+
Initial OpenCode Kiro provider plugin implementation.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- OpenCode server plugin entrypoint with `config`, `auth`, `provider`, and `tool` hooks.
|
|
10
|
+
- Kiro provider config injection using `@ai-sdk/openai-compatible`.
|
|
11
|
+
- Model resolver with normalization, aliases, hidden/manual models, disabled models, pass-through behavior, and configurable extra model presets.
|
|
12
|
+
- Optional `modelDiscoveryCommand` support for refreshing model cache/provider metadata from user-configured CLI/API output.
|
|
13
|
+
- Auth diagnostics for `KIRO_API_KEY`, OpenCode auth input, and `kiro-cli whoami`.
|
|
14
|
+
- `kiro_status` diagnostic tool.
|
|
15
|
+
- OpenAI-compatible request adapter for system prompts, chat history, multimodal data URL images/documents, tool specs, and tool results.
|
|
16
|
+
- OpenAI-compatible generation options for temperature, token limit, and reasoning/thinking effort are preserved as best-effort model request fields in direct fetch mode.
|
|
17
|
+
- CodeWhisperer metadata token usage is mapped to OpenAI-compatible non-streaming usage fields on a best-effort basis.
|
|
18
|
+
- CodeWhisperer/Kiro transport with streaming text, tool-call deltas, configurable retry attempts, and request timeout.
|
|
19
|
+
- CLI fallback using `kiro-cli chat --no-interactive`.
|
|
20
|
+
- CLI fallback responses are wrapped as OpenAI-compatible SSE for streaming OpenCode calls and sanitized to remove terminal prompt/credits output.
|
|
21
|
+
- CLI fallback child process timeout can be controlled with `requestTimeoutMs`.
|
|
22
|
+
- Direct fetch backend now exposes `endpoint`, `profileArn`, `userAgent`, and `agentMode` plugin options.
|
|
23
|
+
- ACP fallback using `kiro-cli acp` JSON-RPC stdio with initialize/session/model/prompt flow.
|
|
24
|
+
- ACP streaming for `AgentMessageChunk`, basic `ToolCall` events, and embedded document resources.
|
|
25
|
+
- ACP tool-call update handling now ignores progress/result-only updates that do not carry invocation payloads and deduplicates repeated tool calls by id in non-streaming responses.
|
|
26
|
+
- ACP agent-origin request handling for permission requests, with default reject and `trustAllTools` allow mode.
|
|
27
|
+
- ACP stdio client ignores non-JSON stdout log lines emitted by `kiro-cli acp --verbose` before JSON-RPC messages.
|
|
28
|
+
- Clean build artifacts before packaging and add a built package import smoke check.
|
|
29
|
+
- Structured error normalization for auth, quota/rate limit, upstream, network, unsupported backend, ACP, and timeout failures.
|
|
30
|
+
- README, local OpenCode config example, implementation notes, license notes, and package metadata tests.
|
|
31
|
+
|
|
32
|
+
### Notes
|
|
33
|
+
|
|
34
|
+
- ACP tool progress/result parity and real Kiro CLI end-to-end validation are still in progress.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 opencode-kiro-plugin contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# opencode-kiro-plugin
|
|
2
|
+
|
|
3
|
+
OpenCode server plugin that registers Kiro as an OpenAI-compatible provider and adapts requests to Kiro backends.
|
|
4
|
+
|
|
5
|
+
Status: early implementation. The CodeWhisperer streaming transport, CLI chat fallback, model resolver, multimodal request mapping, streaming text, and tool-call chunk mapping are implemented with unit tests. The ACP backend implements JSON-RPC stdio framing, initialize/session/model/prompt flow, streaming text from `AgentMessageChunk`, and basic `ToolCall` streaming; full ACP tool progress/result parity is still in progress.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
After the package is published to npm, add it directly to your OpenCode config:
|
|
10
|
+
|
|
11
|
+
```jsonc
|
|
12
|
+
{
|
|
13
|
+
"plugin": ["@bogyie/opencode-kiro-plugin"],
|
|
14
|
+
"provider": {
|
|
15
|
+
"kiro": {
|
|
16
|
+
"models": {
|
|
17
|
+
"sonnet": {
|
|
18
|
+
"name": "Sonnet alias"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For local development from this repository:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then add the plugin to your OpenCode config. See [examples/opencode.jsonc](examples/opencode.jsonc).
|
|
34
|
+
|
|
35
|
+
```jsonc
|
|
36
|
+
{
|
|
37
|
+
"plugin": ["file:/absolute/path/to/opencode-kiro-plugin"],
|
|
38
|
+
"provider": {
|
|
39
|
+
"kiro": {
|
|
40
|
+
"models": {
|
|
41
|
+
"sonnet": {
|
|
42
|
+
"name": "Sonnet alias"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Auth
|
|
51
|
+
|
|
52
|
+
The plugin resolves credentials in this order:
|
|
53
|
+
|
|
54
|
+
1. `KIRO_API_KEY`
|
|
55
|
+
2. OpenCode auth input for provider `kiro`
|
|
56
|
+
3. `kiro-cli whoami` diagnostics for CLI session visibility
|
|
57
|
+
|
|
58
|
+
Direct fetch mode requires an API key/token usable by the Kiro/CodeWhisperer client. `cli-chat` mode uses the official `kiro-cli chat --no-interactive` surface and depends on the local Kiro CLI login state.
|
|
59
|
+
|
|
60
|
+
Use the `kiro_status` plugin tool to inspect provider id, backend, region, auth method, and fallback model preset count. Secrets are redacted in diagnostics.
|
|
61
|
+
|
|
62
|
+
## Backend Modes
|
|
63
|
+
|
|
64
|
+
Configure the backend through plugin options:
|
|
65
|
+
|
|
66
|
+
```jsonc
|
|
67
|
+
{
|
|
68
|
+
"plugin": [
|
|
69
|
+
[
|
|
70
|
+
"@bogyie/opencode-kiro-plugin",
|
|
71
|
+
{
|
|
72
|
+
"backend": "auto",
|
|
73
|
+
"region": "us-east-1",
|
|
74
|
+
"endpoint": "https://q.us-east-1.amazonaws.com",
|
|
75
|
+
"maxAttempts": 3,
|
|
76
|
+
"requestTimeoutMs": 120000,
|
|
77
|
+
"agentMode": "vibe"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Supported values:
|
|
85
|
+
|
|
86
|
+
- `auto`: use CodeWhisperer fetch transport when an API key is available; otherwise use CLI chat fallback.
|
|
87
|
+
- `fetch`: require the direct Kiro/CodeWhisperer fetch path. If no usable auth is available, requests fail with a structured backend/auth error.
|
|
88
|
+
- `cli-chat`: call `kiro-cli chat --no-interactive`. This is official and stable, but Kiro CLI does not currently expose a guaranteed per-request model flag.
|
|
89
|
+
- `acp`: launch `kiro-cli acp`, initialize a session, optionally set the requested model, send the prompt, and collect `AgentMessageChunk` notifications until `TurnEnd`.
|
|
90
|
+
|
|
91
|
+
`trustAllTools` affects both `cli-chat` and ACP permission handling. In ACP mode, permission requests are rejected by default and allowed only when `trustAllTools: true`.
|
|
92
|
+
|
|
93
|
+
## Model Churn Handling
|
|
94
|
+
|
|
95
|
+
The resolver intentionally avoids a hard whitelist. Fallback presets are used for OpenCode UI metadata and cache bootstrap only.
|
|
96
|
+
|
|
97
|
+
Useful options:
|
|
98
|
+
|
|
99
|
+
```jsonc
|
|
100
|
+
{
|
|
101
|
+
"plugin": [
|
|
102
|
+
[
|
|
103
|
+
"@bogyie/opencode-kiro-plugin",
|
|
104
|
+
{
|
|
105
|
+
"modelCacheTtlSeconds": 21600,
|
|
106
|
+
"modelDiscovery": "auto",
|
|
107
|
+
"modelDiscoveryCommand": ["kiro-cli", "models", "--json"],
|
|
108
|
+
"modelAliases": {
|
|
109
|
+
"sonnet": "claude-sonnet-4.6",
|
|
110
|
+
"opus": "claude-opus-4.6"
|
|
111
|
+
},
|
|
112
|
+
"extraModels": {
|
|
113
|
+
"claude-opus-4-9": {
|
|
114
|
+
"name": "Claude Opus 4.9",
|
|
115
|
+
"limit": { "context": 1000000, "output": 64000 },
|
|
116
|
+
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
|
117
|
+
"tool_call": true
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"hiddenModels": {
|
|
121
|
+
"claude-sonnet-4.6-1m": "claude-sonnet-4.6-1m"
|
|
122
|
+
},
|
|
123
|
+
"disabledModels": ["old-model"],
|
|
124
|
+
"disableModelPassThrough": false
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Resolution order:
|
|
132
|
+
|
|
133
|
+
1. Alias mapping
|
|
134
|
+
2. Name normalization, for example `claude-sonnet-4-6` to `claude-sonnet-4.6`
|
|
135
|
+
3. Disabled model check
|
|
136
|
+
4. Dynamic cache hit
|
|
137
|
+
5. Extra model preset cache hit
|
|
138
|
+
6. Hidden/manual model mapping
|
|
139
|
+
7. Optimistic pass-through unless disabled
|
|
140
|
+
|
|
141
|
+
This keeps new Kiro model ids usable before the package is updated. Use `extraModels` when a new model should appear in OpenCode's model picker immediately. Set `disableModelPassThrough: true` only when you need strict model governance.
|
|
142
|
+
|
|
143
|
+
`modelDiscoveryCommand` is optional and intentionally not guessed by default because Kiro CLI model-list flags may vary by version. When configured, stdout can be a JSON array, `{ "models": [...] }`, `{ "data": [...] }`, or one model id per line.
|
|
144
|
+
|
|
145
|
+
## Troubleshooting
|
|
146
|
+
|
|
147
|
+
- `UNSUPPORTED_BACKEND`: selected mode has no usable transport. Check `backend` and auth.
|
|
148
|
+
- `KIRO_AUTH_ERROR`: login or API key is missing/invalid.
|
|
149
|
+
- `KIRO_RATE_LIMIT`: upstream quota or rate limit was hit.
|
|
150
|
+
- `KIRO_NETWORK_ERROR`: timeout or connectivity issue to Kiro/AWS endpoints.
|
|
151
|
+
- `KIRO_ACP_TIMEOUT`: ACP did not send a `TurnEnd` notification before the prompt timeout.
|
|
152
|
+
- `KIRO_ACP_PROCESS_ERROR` or `KIRO_ACP_PROCESS_EXITED`: `kiro-cli acp` could not start or exited while a request was pending.
|
|
153
|
+
|
|
154
|
+
Direct fetch mode uses AWS SDK standard retry behavior. Tune `maxAttempts` and `requestTimeoutMs` if you need stricter failure boundaries in automation. Fetch mode also accepts `endpoint`, `profileArn`, `userAgent`, and `agentMode` for controlled environments. `cli-chat` uses `requestTimeoutMs` for the `kiro-cli chat --no-interactive` child process, and ACP uses it while waiting for `session/prompt` completion and `TurnEnd`.
|
|
155
|
+
|
|
156
|
+
OpenAI-compatible `temperature`, `max_tokens`, `max_completion_tokens`, `reasoning_effort`, `reasoning.effort`, and `thinking.effort` are preserved for direct fetch mode through Kiro's `additionalModelRequestFields` path on a best-effort basis.
|
|
157
|
+
|
|
158
|
+
For local checks:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
npm test
|
|
162
|
+
npm run typecheck
|
|
163
|
+
npm run build
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
For real Kiro/OpenCode validation, use [docs/e2e-validation.md](docs/e2e-validation.md).
|
|
167
|
+
|
|
168
|
+
## Release
|
|
169
|
+
|
|
170
|
+
GitHub Release publishing drives npm publishing. To publish a new npm package:
|
|
171
|
+
|
|
172
|
+
1. Set the repository secret `NPM_TOKEN` to an npm automation token with publish access to `@bogyie/opencode-kiro-plugin`.
|
|
173
|
+
2. Update `package.json` and `package-lock.json` to the release version.
|
|
174
|
+
3. Create and publish a GitHub Release whose tag matches the package version, for example `v0.1.0`.
|
|
175
|
+
|
|
176
|
+
The `Publish npm package` workflow runs tests, typecheck, package smoke validation, `npm pack --dry-run`, and then `npm publish --provenance --access public`.
|
|
177
|
+
|
|
178
|
+
## License And References
|
|
179
|
+
|
|
180
|
+
This project is MIT licensed. See [LICENSE](LICENSE).
|
|
181
|
+
|
|
182
|
+
Reference policy:
|
|
183
|
+
|
|
184
|
+
- `tickernelz/opencode-kiro-auth` is MIT licensed and used as implementation reference material.
|
|
185
|
+
- Kiro CLI/IDE licensing and AWS service terms are separate from this plugin license. Use the plugin only in compliance with the applicable Kiro/AWS terms.
|
|
186
|
+
|
|
187
|
+
Research notes are kept in [docs](docs).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type JsonRpcId = string | number;
|
|
2
|
+
export interface JsonRpcRequest {
|
|
3
|
+
readonly jsonrpc: "2.0";
|
|
4
|
+
readonly id: JsonRpcId;
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly params?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface JsonRpcNotification {
|
|
9
|
+
readonly jsonrpc: "2.0";
|
|
10
|
+
readonly method: string;
|
|
11
|
+
readonly params?: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface JsonRpcSuccess {
|
|
14
|
+
readonly jsonrpc: "2.0";
|
|
15
|
+
readonly id: JsonRpcId;
|
|
16
|
+
readonly result: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface JsonRpcFailure {
|
|
19
|
+
readonly jsonrpc: "2.0";
|
|
20
|
+
readonly id: JsonRpcId;
|
|
21
|
+
readonly error: {
|
|
22
|
+
readonly code: number;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
readonly data?: unknown;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure;
|
|
28
|
+
export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse;
|
|
29
|
+
export interface AcpConnection {
|
|
30
|
+
send(message: JsonRpcMessage): void | Promise<void>;
|
|
31
|
+
close?(): void;
|
|
32
|
+
}
|
|
33
|
+
export type AcpNotificationHandler = (message: JsonRpcNotification) => void;
|
|
34
|
+
export type AcpRequestHandler = (message: JsonRpcRequest) => unknown | Promise<unknown>;
|
|
35
|
+
export interface AcpJsonRpcClientOptions {
|
|
36
|
+
readonly onRequest?: AcpRequestHandler;
|
|
37
|
+
}
|
|
38
|
+
export declare function encodeJsonRpc(message: JsonRpcMessage): string;
|
|
39
|
+
export declare function decodeJsonRpc(line: string): JsonRpcMessage;
|
|
40
|
+
export declare function decodeJsonRpcLine(line: string): JsonRpcMessage | undefined;
|
|
41
|
+
export declare class AcpJsonRpcClient {
|
|
42
|
+
#private;
|
|
43
|
+
constructor(connection: AcpConnection, options?: AcpJsonRpcClientOptions);
|
|
44
|
+
request(method: string, params?: unknown): Promise<unknown>;
|
|
45
|
+
receive(message: JsonRpcMessage): void;
|
|
46
|
+
rejectAll(error: unknown): void;
|
|
47
|
+
onNotification(handler: AcpNotificationHandler): () => void;
|
|
48
|
+
close(): void;
|
|
49
|
+
}
|
|
50
|
+
export interface AcpStdioClientOptions {
|
|
51
|
+
readonly command?: string;
|
|
52
|
+
readonly args?: ReadonlyArray<string>;
|
|
53
|
+
readonly cwd?: string;
|
|
54
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
55
|
+
readonly requestHandler?: AcpRequestHandler;
|
|
56
|
+
}
|
|
57
|
+
export declare function createAcpStdioClient(options?: AcpStdioClientOptions): AcpJsonRpcClient;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { KiroPluginError } from "./errors.js";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
export function encodeJsonRpc(message) {
|
|
4
|
+
return `${JSON.stringify(message)}\n`;
|
|
5
|
+
}
|
|
6
|
+
export function decodeJsonRpc(line) {
|
|
7
|
+
const parsed = JSON.parse(line);
|
|
8
|
+
if (!parsed || typeof parsed !== "object" || parsed.jsonrpc !== "2.0") {
|
|
9
|
+
throw new KiroPluginError("Invalid JSON-RPC 2.0 message.", "KIRO_ACP_PROTOCOL_ERROR", 502);
|
|
10
|
+
}
|
|
11
|
+
return parsed;
|
|
12
|
+
}
|
|
13
|
+
export function decodeJsonRpcLine(line) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (!trimmed)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (!trimmed.startsWith("{"))
|
|
18
|
+
return undefined;
|
|
19
|
+
return decodeJsonRpc(trimmed);
|
|
20
|
+
}
|
|
21
|
+
function hasId(message) {
|
|
22
|
+
return "id" in message;
|
|
23
|
+
}
|
|
24
|
+
function isNotification(message) {
|
|
25
|
+
return "method" in message && !("id" in message);
|
|
26
|
+
}
|
|
27
|
+
function isRequest(message) {
|
|
28
|
+
return "method" in message && "id" in message;
|
|
29
|
+
}
|
|
30
|
+
function hasError(message) {
|
|
31
|
+
return "error" in message;
|
|
32
|
+
}
|
|
33
|
+
export class AcpJsonRpcClient {
|
|
34
|
+
#nextId = 1;
|
|
35
|
+
#connection;
|
|
36
|
+
#onRequest;
|
|
37
|
+
#pending = new Map();
|
|
38
|
+
#notifications = new Set();
|
|
39
|
+
constructor(connection, options = {}) {
|
|
40
|
+
this.#connection = connection;
|
|
41
|
+
this.#onRequest = options.onRequest;
|
|
42
|
+
}
|
|
43
|
+
async request(method, params) {
|
|
44
|
+
const id = this.#nextId++;
|
|
45
|
+
const message = params === undefined ? { jsonrpc: "2.0", id, method } : { jsonrpc: "2.0", id, method, params };
|
|
46
|
+
const response = new Promise((resolve, reject) => {
|
|
47
|
+
this.#pending.set(id, { resolve, reject });
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
await this.#connection.send(message);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.#pending.delete(id);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
return response;
|
|
57
|
+
}
|
|
58
|
+
receive(message) {
|
|
59
|
+
if (isNotification(message)) {
|
|
60
|
+
for (const handler of this.#notifications)
|
|
61
|
+
handler(message);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (isRequest(message)) {
|
|
65
|
+
void this.#handleRequest(message);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!hasId(message))
|
|
69
|
+
return;
|
|
70
|
+
const pending = this.#pending.get(message.id);
|
|
71
|
+
if (!pending)
|
|
72
|
+
return;
|
|
73
|
+
this.#pending.delete(message.id);
|
|
74
|
+
if (hasError(message)) {
|
|
75
|
+
pending.reject(new KiroPluginError(message.error.message, "KIRO_ACP_ERROR", 502));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
pending.resolve(message.result);
|
|
79
|
+
}
|
|
80
|
+
async #handleRequest(message) {
|
|
81
|
+
try {
|
|
82
|
+
if (!this.#onRequest) {
|
|
83
|
+
await this.#connection.send({
|
|
84
|
+
jsonrpc: "2.0",
|
|
85
|
+
id: message.id,
|
|
86
|
+
error: { code: -32601, message: `Method not found: ${message.method}` },
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const result = await this.#onRequest(message);
|
|
91
|
+
await this.#connection.send({
|
|
92
|
+
jsonrpc: "2.0",
|
|
93
|
+
id: message.id,
|
|
94
|
+
result: result ?? null,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
await this.#connection.send({
|
|
99
|
+
jsonrpc: "2.0",
|
|
100
|
+
id: message.id,
|
|
101
|
+
error: {
|
|
102
|
+
code: -32603,
|
|
103
|
+
message: error instanceof Error ? error.message : "ACP request handler failed",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
rejectAll(error) {
|
|
109
|
+
for (const pending of this.#pending.values()) {
|
|
110
|
+
pending.reject(error);
|
|
111
|
+
}
|
|
112
|
+
this.#pending.clear();
|
|
113
|
+
}
|
|
114
|
+
onNotification(handler) {
|
|
115
|
+
this.#notifications.add(handler);
|
|
116
|
+
return () => {
|
|
117
|
+
this.#notifications.delete(handler);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
close() {
|
|
121
|
+
this.#connection.close?.();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function createAcpStdioClient(options = {}) {
|
|
125
|
+
let client;
|
|
126
|
+
const child = spawn(options.command ?? "kiro-cli", [...(options.args ?? ["acp"])], {
|
|
127
|
+
cwd: options.cwd,
|
|
128
|
+
env: options.env ?? process.env,
|
|
129
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
130
|
+
});
|
|
131
|
+
const connection = {
|
|
132
|
+
send(message) {
|
|
133
|
+
if (!child.stdin?.writable) {
|
|
134
|
+
throw new KiroPluginError("ACP process stdin is not writable.", "KIRO_ACP_PROCESS_ERROR", 502);
|
|
135
|
+
}
|
|
136
|
+
child.stdin.write(encodeJsonRpc(message));
|
|
137
|
+
},
|
|
138
|
+
close() {
|
|
139
|
+
child.kill();
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
client = new AcpJsonRpcClient(connection, options.requestHandler ? { onRequest: options.requestHandler } : {});
|
|
143
|
+
let stdout = "";
|
|
144
|
+
child.stdout?.on("data", (chunk) => {
|
|
145
|
+
stdout += chunk.toString("utf8");
|
|
146
|
+
for (;;) {
|
|
147
|
+
const index = stdout.indexOf("\n");
|
|
148
|
+
if (index < 0)
|
|
149
|
+
break;
|
|
150
|
+
const line = stdout.slice(0, index).trim();
|
|
151
|
+
stdout = stdout.slice(index + 1);
|
|
152
|
+
if (!line)
|
|
153
|
+
continue;
|
|
154
|
+
try {
|
|
155
|
+
const message = decodeJsonRpcLine(line);
|
|
156
|
+
if (message)
|
|
157
|
+
client?.receive(message);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
client?.rejectAll(error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
child.on("error", (error) => {
|
|
165
|
+
client?.rejectAll(new KiroPluginError(error.message, "KIRO_ACP_PROCESS_ERROR", 502));
|
|
166
|
+
});
|
|
167
|
+
child.on("exit", (code, signal) => {
|
|
168
|
+
client?.rejectAll(new KiroPluginError(`ACP process exited${code === null ? "" : ` with code ${code}`}${signal ? ` and signal ${signal}` : ""}.`, "KIRO_ACP_PROCESS_EXITED", 502));
|
|
169
|
+
});
|
|
170
|
+
return client;
|
|
171
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type AcpNotificationHandler } from "./acp-client.js";
|
|
2
|
+
import type { KiroTransport } from "./fetch-adapter.js";
|
|
3
|
+
import type { KiroGenerateRequest } from "./request-adapter.js";
|
|
4
|
+
import type { KiroGenerateResponse, KiroStreamEvent } from "./response-adapter.js";
|
|
5
|
+
export interface AcpSessionClient {
|
|
6
|
+
request(method: string, params?: unknown): Promise<unknown>;
|
|
7
|
+
onNotification(handler: AcpNotificationHandler): () => void;
|
|
8
|
+
close?(): void;
|
|
9
|
+
}
|
|
10
|
+
export interface KiroAcpTransportOptions {
|
|
11
|
+
readonly client?: AcpSessionClient;
|
|
12
|
+
readonly command?: string;
|
|
13
|
+
readonly args?: ReadonlyArray<string>;
|
|
14
|
+
readonly cwd?: string;
|
|
15
|
+
readonly promptTimeoutMs?: number;
|
|
16
|
+
readonly trustAllTools?: boolean;
|
|
17
|
+
readonly clientInfo?: {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly version: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare function acpPermissionResponse(params: unknown, trustAllTools?: boolean): {
|
|
23
|
+
outcome: {
|
|
24
|
+
outcome: "selected";
|
|
25
|
+
optionId: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export declare class KiroAcpTransport implements KiroTransport {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(options?: KiroAcpTransportOptions);
|
|
31
|
+
generate(request: KiroGenerateRequest): Promise<KiroGenerateResponse>;
|
|
32
|
+
stream(request: KiroGenerateRequest): AsyncIterable<KiroStreamEvent>;
|
|
33
|
+
}
|