axexec 1.0.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/LICENSE +21 -0
- package/README.md +196 -0
- package/bin/axexec +17 -0
- package/dist/agents/claude-code/adapter.d.ts +6 -0
- package/dist/agents/claude-code/adapter.js +71 -0
- package/dist/agents/claude-code/parse-event.d.ts +16 -0
- package/dist/agents/claude-code/parse-event.js +185 -0
- package/dist/agents/claude-code/types.d.ts +180 -0
- package/dist/agents/claude-code/types.js +112 -0
- package/dist/agents/codex/adapter.d.ts +6 -0
- package/dist/agents/codex/adapter.js +62 -0
- package/dist/agents/codex/item-parsers.d.ts +17 -0
- package/dist/agents/codex/item-parsers.js +126 -0
- package/dist/agents/codex/parse-event.d.ts +38 -0
- package/dist/agents/codex/parse-event.js +141 -0
- package/dist/agents/codex/types.d.ts +407 -0
- package/dist/agents/codex/types.js +188 -0
- package/dist/agents/copilot/adapter.d.ts +11 -0
- package/dist/agents/copilot/adapter.js +81 -0
- package/dist/agents/copilot/parse-event.d.ts +20 -0
- package/dist/agents/copilot/parse-event.js +137 -0
- package/dist/agents/copilot/stream-session.d.ts +10 -0
- package/dist/agents/copilot/stream-session.js +120 -0
- package/dist/agents/copilot/tail-file.d.ts +11 -0
- package/dist/agents/copilot/tail-file.js +52 -0
- package/dist/agents/copilot/transform-event.d.ts +19 -0
- package/dist/agents/copilot/transform-event.js +100 -0
- package/dist/agents/copilot/types.d.ts +320 -0
- package/dist/agents/copilot/types.js +220 -0
- package/dist/agents/copilot/watch-session.d.ts +26 -0
- package/dist/agents/copilot/watch-session.js +186 -0
- package/dist/agents/gemini/adapter.d.ts +6 -0
- package/dist/agents/gemini/adapter.js +78 -0
- package/dist/agents/gemini/parse-event.d.ts +18 -0
- package/dist/agents/gemini/parse-event.js +144 -0
- package/dist/agents/gemini/types.d.ts +162 -0
- package/dist/agents/gemini/types.js +103 -0
- package/dist/agents/opencode/adapter.d.ts +16 -0
- package/dist/agents/opencode/adapter.js +142 -0
- package/dist/agents/opencode/cleanup-session.d.ts +17 -0
- package/dist/agents/opencode/cleanup-session.js +41 -0
- package/dist/agents/opencode/create-session-start-event.d.ts +18 -0
- package/dist/agents/opencode/create-session-start-event.js +24 -0
- package/dist/agents/opencode/detect-empty-session.d.ts +25 -0
- package/dist/agents/opencode/detect-empty-session.js +42 -0
- package/dist/agents/opencode/map-error-to-event.d.ts +10 -0
- package/dist/agents/opencode/map-error-to-event.js +55 -0
- package/dist/agents/opencode/parse-message-part.d.ts +24 -0
- package/dist/agents/opencode/parse-message-part.js +112 -0
- package/dist/agents/opencode/parse-sse-event.d.ts +23 -0
- package/dist/agents/opencode/parse-sse-event.js +125 -0
- package/dist/agents/opencode/process-sse-events.d.ts +24 -0
- package/dist/agents/opencode/process-sse-events.js +66 -0
- package/dist/agents/opencode/server-types.d.ts +509 -0
- package/dist/agents/opencode/server-types.js +293 -0
- package/dist/agents/opencode/session-api.d.ts +56 -0
- package/dist/agents/opencode/session-api.js +151 -0
- package/dist/agents/opencode/spawn-server.d.ts +29 -0
- package/dist/agents/opencode/spawn-server.js +95 -0
- package/dist/agents/opencode/sse-client.d.ts +33 -0
- package/dist/agents/opencode/sse-client.js +145 -0
- package/dist/agents/registry.d.ts +15 -0
- package/dist/agents/registry.js +24 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +119 -0
- package/dist/credentials/get-credential-environment.d.ts +13 -0
- package/dist/credentials/get-credential-environment.js +46 -0
- package/dist/credentials/install-credentials.d.ts +27 -0
- package/dist/credentials/install-credentials.js +102 -0
- package/dist/credentials/save-json-file.d.ts +11 -0
- package/dist/credentials/save-json-file.js +21 -0
- package/dist/credentials/types.d.ts +24 -0
- package/dist/credentials/types.js +4 -0
- package/dist/credentials/write-agent-credentials.d.ts +36 -0
- package/dist/credentials/write-agent-credentials.js +91 -0
- package/dist/determine-session-success.d.ts +15 -0
- package/dist/determine-session-success.js +25 -0
- package/dist/format-event-tsv.d.ts +23 -0
- package/dist/format-event-tsv.js +136 -0
- package/dist/parse-credentials.d.ts +13 -0
- package/dist/parse-credentials.js +63 -0
- package/dist/parse-iso-timestamp.d.ts +21 -0
- package/dist/parse-iso-timestamp.js +37 -0
- package/dist/resolve-binary.d.ts +29 -0
- package/dist/resolve-binary.js +46 -0
- package/dist/resolve-output-mode.d.ts +39 -0
- package/dist/resolve-output-mode.js +39 -0
- package/dist/run-agent.d.ts +32 -0
- package/dist/run-agent.js +146 -0
- package/dist/stream-agent.d.ts +20 -0
- package/dist/stream-agent.js +207 -0
- package/dist/types/adapter.d.ts +101 -0
- package/dist/types/adapter.js +14 -0
- package/dist/types/events.d.ts +82 -0
- package/dist/types/events.js +13 -0
- package/dist/types/options.d.ts +41 -0
- package/dist/types/options.js +4 -0
- package/dist/validate-agent.d.ts +20 -0
- package/dist/validate-agent.js +50 -0
- package/dist/write-event.d.ts +23 -0
- package/dist/write-event.js +43 -0
- package/package.json +79 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE event types from OpenCode server mode.
|
|
3
|
+
*
|
|
4
|
+
* These types represent the events streamed via SSE from:
|
|
5
|
+
* `GET /event` endpoint on `opencode serve`
|
|
6
|
+
*
|
|
7
|
+
* The parser transforms these into normalized {@link AxexecEvent} types.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Session Events
|
|
12
|
+
// =============================================================================
|
|
13
|
+
/** Session info schema */
|
|
14
|
+
const SessionInfo = z.object({
|
|
15
|
+
id: z.string(),
|
|
16
|
+
parentID: z.string().optional(),
|
|
17
|
+
title: z.string().optional(),
|
|
18
|
+
projectID: z.string().optional(),
|
|
19
|
+
share: z.string().optional(),
|
|
20
|
+
updated: z.number(),
|
|
21
|
+
created: z.number(),
|
|
22
|
+
});
|
|
23
|
+
/** session.created event */
|
|
24
|
+
const SessionCreatedEvent = z.object({
|
|
25
|
+
type: z.literal("session.created"),
|
|
26
|
+
properties: z.object({
|
|
27
|
+
info: SessionInfo,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
/** session.updated event */
|
|
31
|
+
const SessionUpdatedEvent = z.object({
|
|
32
|
+
type: z.literal("session.updated"),
|
|
33
|
+
properties: z.object({
|
|
34
|
+
info: SessionInfo,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
/** session.idle event - signals session has finished processing */
|
|
38
|
+
const SessionIdleEvent = z.object({
|
|
39
|
+
type: z.literal("session.idle"),
|
|
40
|
+
properties: z.object({
|
|
41
|
+
sessionID: z.string(),
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
/** Session status types */
|
|
45
|
+
const SessionStatusInfo = z.discriminatedUnion("type", [
|
|
46
|
+
z.object({ type: z.literal("idle") }),
|
|
47
|
+
z.object({
|
|
48
|
+
type: z.literal("retry"),
|
|
49
|
+
attempt: z.number(),
|
|
50
|
+
message: z.string(),
|
|
51
|
+
next: z.number(),
|
|
52
|
+
}),
|
|
53
|
+
z.object({ type: z.literal("busy") }),
|
|
54
|
+
]);
|
|
55
|
+
/** session.status event */
|
|
56
|
+
const SessionStatusEvent = z.object({
|
|
57
|
+
type: z.literal("session.status"),
|
|
58
|
+
properties: z.object({
|
|
59
|
+
sessionID: z.string(),
|
|
60
|
+
status: SessionStatusInfo,
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
/** session.error event */
|
|
64
|
+
const SessionErrorEvent = z.object({
|
|
65
|
+
type: z.literal("session.error"),
|
|
66
|
+
properties: z.object({
|
|
67
|
+
sessionID: z.string().optional(),
|
|
68
|
+
error: z
|
|
69
|
+
.object({
|
|
70
|
+
name: z.string(),
|
|
71
|
+
data: z
|
|
72
|
+
.looseObject({
|
|
73
|
+
message: z.string().optional(),
|
|
74
|
+
providerID: z.string().optional(),
|
|
75
|
+
statusCode: z.number().optional(),
|
|
76
|
+
isRetryable: z.boolean().optional(),
|
|
77
|
+
})
|
|
78
|
+
.optional(),
|
|
79
|
+
})
|
|
80
|
+
.optional(),
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// Message Part Events
|
|
85
|
+
// =============================================================================
|
|
86
|
+
/** Token usage in step-finish parts */
|
|
87
|
+
const StepTokens = z.object({
|
|
88
|
+
input: z.number(),
|
|
89
|
+
output: z.number(),
|
|
90
|
+
reasoning: z.number(),
|
|
91
|
+
cache: z.object({
|
|
92
|
+
read: z.number(),
|
|
93
|
+
write: z.number(),
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
/** Text part */
|
|
97
|
+
const TextPart = z.object({
|
|
98
|
+
id: z.string(),
|
|
99
|
+
sessionID: z.string(),
|
|
100
|
+
messageID: z.string(),
|
|
101
|
+
type: z.literal("text"),
|
|
102
|
+
text: z.string(),
|
|
103
|
+
synthetic: z.boolean().optional(),
|
|
104
|
+
ignored: z.boolean().optional(),
|
|
105
|
+
time: z
|
|
106
|
+
.object({
|
|
107
|
+
start: z.number(),
|
|
108
|
+
end: z.number().optional(),
|
|
109
|
+
})
|
|
110
|
+
.optional(),
|
|
111
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
112
|
+
});
|
|
113
|
+
/** Reasoning part (chain-of-thought) */
|
|
114
|
+
const ReasoningPart = z.object({
|
|
115
|
+
id: z.string(),
|
|
116
|
+
sessionID: z.string(),
|
|
117
|
+
messageID: z.string(),
|
|
118
|
+
type: z.literal("reasoning"),
|
|
119
|
+
text: z.string(),
|
|
120
|
+
time: z
|
|
121
|
+
.object({
|
|
122
|
+
start: z.number(),
|
|
123
|
+
end: z.number().optional(),
|
|
124
|
+
})
|
|
125
|
+
.optional(),
|
|
126
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
127
|
+
});
|
|
128
|
+
/** Tool state - pending */
|
|
129
|
+
const ToolStatePending = z.object({
|
|
130
|
+
status: z.literal("pending"),
|
|
131
|
+
input: z.record(z.string(), z.unknown()),
|
|
132
|
+
raw: z.string().optional(),
|
|
133
|
+
});
|
|
134
|
+
/** Tool state - running */
|
|
135
|
+
const ToolStateRunning = z.object({
|
|
136
|
+
status: z.literal("running"),
|
|
137
|
+
input: z.record(z.string(), z.unknown()),
|
|
138
|
+
title: z.string().optional(),
|
|
139
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
140
|
+
time: z.object({ start: z.number() }),
|
|
141
|
+
});
|
|
142
|
+
/** Tool state - completed */
|
|
143
|
+
const ToolStateCompleted = z.object({
|
|
144
|
+
status: z.literal("completed"),
|
|
145
|
+
input: z.record(z.string(), z.unknown()),
|
|
146
|
+
output: z.string(),
|
|
147
|
+
title: z.string(),
|
|
148
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
149
|
+
time: z.object({
|
|
150
|
+
start: z.number(),
|
|
151
|
+
end: z.number(),
|
|
152
|
+
compacted: z.number().optional(),
|
|
153
|
+
}),
|
|
154
|
+
attachments: z.array(z.unknown()).optional(),
|
|
155
|
+
});
|
|
156
|
+
/** Tool state - error */
|
|
157
|
+
const ToolStateError = z.object({
|
|
158
|
+
status: z.literal("error"),
|
|
159
|
+
input: z.record(z.string(), z.unknown()),
|
|
160
|
+
error: z.string(),
|
|
161
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
162
|
+
time: z.object({
|
|
163
|
+
start: z.number(),
|
|
164
|
+
end: z.number(),
|
|
165
|
+
}),
|
|
166
|
+
});
|
|
167
|
+
/** Tool state discriminated union */
|
|
168
|
+
const ToolState = z.discriminatedUnion("status", [
|
|
169
|
+
ToolStatePending,
|
|
170
|
+
ToolStateRunning,
|
|
171
|
+
ToolStateCompleted,
|
|
172
|
+
ToolStateError,
|
|
173
|
+
]);
|
|
174
|
+
/** Tool part */
|
|
175
|
+
const ToolPart = z.object({
|
|
176
|
+
id: z.string(),
|
|
177
|
+
sessionID: z.string(),
|
|
178
|
+
messageID: z.string(),
|
|
179
|
+
type: z.literal("tool"),
|
|
180
|
+
callID: z.string(),
|
|
181
|
+
tool: z.string(),
|
|
182
|
+
state: ToolState,
|
|
183
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
184
|
+
});
|
|
185
|
+
/** Step-start part */
|
|
186
|
+
const StepStartPart = z.object({
|
|
187
|
+
id: z.string(),
|
|
188
|
+
sessionID: z.string(),
|
|
189
|
+
messageID: z.string(),
|
|
190
|
+
type: z.literal("step-start"),
|
|
191
|
+
snapshot: z.string().optional(),
|
|
192
|
+
});
|
|
193
|
+
/** Step-finish part with cost and token data */
|
|
194
|
+
const StepFinishPart = z.object({
|
|
195
|
+
id: z.string(),
|
|
196
|
+
sessionID: z.string(),
|
|
197
|
+
messageID: z.string(),
|
|
198
|
+
type: z.literal("step-finish"),
|
|
199
|
+
reason: z.string().optional(),
|
|
200
|
+
snapshot: z.string().optional(),
|
|
201
|
+
cost: z.number(),
|
|
202
|
+
tokens: StepTokens,
|
|
203
|
+
});
|
|
204
|
+
/** All part types */
|
|
205
|
+
const Part = z.discriminatedUnion("type", [
|
|
206
|
+
TextPart,
|
|
207
|
+
ReasoningPart,
|
|
208
|
+
ToolPart,
|
|
209
|
+
StepStartPart,
|
|
210
|
+
StepFinishPart,
|
|
211
|
+
]);
|
|
212
|
+
/** message.part.updated event */
|
|
213
|
+
const MessagePartUpdatedEvent = z.object({
|
|
214
|
+
type: z.literal("message.part.updated"),
|
|
215
|
+
properties: z.object({
|
|
216
|
+
part: Part,
|
|
217
|
+
delta: z.string().optional(),
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
// =============================================================================
|
|
221
|
+
// Permission Events
|
|
222
|
+
// =============================================================================
|
|
223
|
+
/** permission.updated event - requires user approval */
|
|
224
|
+
const PermissionUpdatedEvent = z.object({
|
|
225
|
+
type: z.literal("permission.updated"),
|
|
226
|
+
properties: z.object({
|
|
227
|
+
id: z.string(),
|
|
228
|
+
sessionID: z.string(),
|
|
229
|
+
messageID: z.string().optional(),
|
|
230
|
+
title: z.string(),
|
|
231
|
+
description: z.string().optional(),
|
|
232
|
+
tool: z.string().optional(),
|
|
233
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
234
|
+
time: z.object({
|
|
235
|
+
created: z.number(),
|
|
236
|
+
responded: z.number().optional(),
|
|
237
|
+
}),
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
// =============================================================================
|
|
241
|
+
// Server Events
|
|
242
|
+
// =============================================================================
|
|
243
|
+
/** server.connected event - first event on SSE connect */
|
|
244
|
+
const ServerConnectedEvent = z.object({
|
|
245
|
+
type: z.literal("server.connected"),
|
|
246
|
+
properties: z.object({}),
|
|
247
|
+
});
|
|
248
|
+
/** server.heartbeat event - keepalive every 30s */
|
|
249
|
+
const ServerHeartbeatEvent = z.object({
|
|
250
|
+
type: z.literal("server.heartbeat"),
|
|
251
|
+
properties: z.object({}),
|
|
252
|
+
});
|
|
253
|
+
// =============================================================================
|
|
254
|
+
// Union Type
|
|
255
|
+
// =============================================================================
|
|
256
|
+
/** All SSE event types we handle */
|
|
257
|
+
const OpenCodeSSEEvent = z.discriminatedUnion("type", [
|
|
258
|
+
// Server events
|
|
259
|
+
ServerConnectedEvent,
|
|
260
|
+
ServerHeartbeatEvent,
|
|
261
|
+
// Session events
|
|
262
|
+
SessionCreatedEvent,
|
|
263
|
+
SessionUpdatedEvent,
|
|
264
|
+
SessionIdleEvent,
|
|
265
|
+
SessionStatusEvent,
|
|
266
|
+
SessionErrorEvent,
|
|
267
|
+
// Message events
|
|
268
|
+
MessagePartUpdatedEvent,
|
|
269
|
+
// Permission events
|
|
270
|
+
PermissionUpdatedEvent,
|
|
271
|
+
]);
|
|
272
|
+
// =============================================================================
|
|
273
|
+
// Type Guards
|
|
274
|
+
// =============================================================================
|
|
275
|
+
function isSessionCreatedEvent(event) {
|
|
276
|
+
return event.type === "session.created";
|
|
277
|
+
}
|
|
278
|
+
function isSessionIdleEvent(event) {
|
|
279
|
+
return event.type === "session.idle";
|
|
280
|
+
}
|
|
281
|
+
function isSessionErrorEvent(event) {
|
|
282
|
+
return event.type === "session.error";
|
|
283
|
+
}
|
|
284
|
+
function isMessagePartUpdatedEvent(event) {
|
|
285
|
+
return event.type === "message.part.updated";
|
|
286
|
+
}
|
|
287
|
+
function isPermissionUpdatedEvent(event) {
|
|
288
|
+
return event.type === "permission.updated";
|
|
289
|
+
}
|
|
290
|
+
// =============================================================================
|
|
291
|
+
// Exports
|
|
292
|
+
// =============================================================================
|
|
293
|
+
export { OpenCodeSSEEvent, isMessagePartUpdatedEvent, isPermissionUpdatedEvent, isSessionCreatedEvent, isSessionErrorEvent, isSessionIdleEvent, };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode session API client.
|
|
3
|
+
*
|
|
4
|
+
* Handles session creation, prompt submission, and cleanup via HTTP API.
|
|
5
|
+
*/
|
|
6
|
+
/** Error thrown when session creation fails */
|
|
7
|
+
declare class SessionCreateError extends Error {
|
|
8
|
+
readonly statusCode?: number;
|
|
9
|
+
constructor(message: string, statusCode?: number);
|
|
10
|
+
}
|
|
11
|
+
/** Error thrown when prompt submission fails */
|
|
12
|
+
declare class PromptSubmitError extends Error {
|
|
13
|
+
readonly statusCode?: number;
|
|
14
|
+
constructor(message: string, statusCode?: number);
|
|
15
|
+
}
|
|
16
|
+
/** Response from POST /session */
|
|
17
|
+
interface SessionResponse {
|
|
18
|
+
id: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
parentID?: string;
|
|
21
|
+
projectID?: string;
|
|
22
|
+
updated: number;
|
|
23
|
+
created: number;
|
|
24
|
+
}
|
|
25
|
+
/** Model info extracted from messages */
|
|
26
|
+
interface MessageModelInfo {
|
|
27
|
+
providerID: string;
|
|
28
|
+
modelID: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a new session on the OpenCode server.
|
|
32
|
+
*/
|
|
33
|
+
declare function createSession(baseUrl: string, directory: string): Promise<SessionResponse>;
|
|
34
|
+
/**
|
|
35
|
+
* Sends a prompt to an existing session.
|
|
36
|
+
*
|
|
37
|
+
* Uses prompt_async endpoint which returns immediately and processes
|
|
38
|
+
* the prompt asynchronously. Events are streamed via SSE.
|
|
39
|
+
*/
|
|
40
|
+
declare function sendPrompt(baseUrl: string, sessionId: string, prompt: string, directory: string, model?: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Aborts a running session with timeout.
|
|
43
|
+
*/
|
|
44
|
+
declare function abortSession(baseUrl: string, sessionId: string, directory: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Disposes the server instance with timeout.
|
|
47
|
+
*/
|
|
48
|
+
declare function disposeInstance(baseUrl: string, directory: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Queries messages for a session and extracts model info from the first assistant message.
|
|
51
|
+
*
|
|
52
|
+
* Returns undefined if no assistant message found or query fails.
|
|
53
|
+
*/
|
|
54
|
+
declare function getSessionModelInfo(baseUrl: string, sessionId: string, directory: string): Promise<MessageModelInfo | undefined>;
|
|
55
|
+
export { PromptSubmitError, SessionCreateError, abortSession, createSession, disposeInstance, getSessionModelInfo, sendPrompt, };
|
|
56
|
+
export type { MessageModelInfo };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode session API client.
|
|
3
|
+
*
|
|
4
|
+
* Handles session creation, prompt submission, and cleanup via HTTP API.
|
|
5
|
+
*/
|
|
6
|
+
/** Error thrown when session creation fails */
|
|
7
|
+
class SessionCreateError extends Error {
|
|
8
|
+
statusCode;
|
|
9
|
+
constructor(message, statusCode) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "SessionCreateError";
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Error thrown when prompt submission fails */
|
|
16
|
+
class PromptSubmitError extends Error {
|
|
17
|
+
statusCode;
|
|
18
|
+
constructor(message, statusCode) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "PromptSubmitError";
|
|
21
|
+
this.statusCode = statusCode;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Parses "provider/model" string into components */
|
|
25
|
+
function parseModel(model) {
|
|
26
|
+
const slashIndex = model.indexOf("/");
|
|
27
|
+
if (slashIndex === -1)
|
|
28
|
+
return undefined;
|
|
29
|
+
return {
|
|
30
|
+
providerID: model.slice(0, slashIndex),
|
|
31
|
+
modelID: model.slice(slashIndex + 1),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new session on the OpenCode server.
|
|
36
|
+
*/
|
|
37
|
+
async function createSession(baseUrl, directory) {
|
|
38
|
+
const response = await fetch(`${baseUrl}/session`, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
"x-opencode-directory": directory,
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({}),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new SessionCreateError(`Failed to create session: ${response.status} ${response.statusText}`, response.status);
|
|
48
|
+
}
|
|
49
|
+
return (await response.json());
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Sends a prompt to an existing session.
|
|
53
|
+
*
|
|
54
|
+
* Uses prompt_async endpoint which returns immediately and processes
|
|
55
|
+
* the prompt asynchronously. Events are streamed via SSE.
|
|
56
|
+
*/
|
|
57
|
+
async function sendPrompt(baseUrl, sessionId, prompt, directory, model) {
|
|
58
|
+
// Build request body
|
|
59
|
+
const body = {
|
|
60
|
+
parts: [{ type: "text", text: prompt }],
|
|
61
|
+
};
|
|
62
|
+
// Add model if provided and valid
|
|
63
|
+
if (model) {
|
|
64
|
+
const parsed = parseModel(model);
|
|
65
|
+
if (parsed) {
|
|
66
|
+
body.model = parsed;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const response = await fetch(`${baseUrl}/session/${sessionId}/prompt_async`, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"x-opencode-directory": directory,
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify(body),
|
|
76
|
+
});
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new PromptSubmitError(`Failed to send prompt: ${response.status} ${response.statusText}`, response.status);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Aborts a running session with timeout.
|
|
83
|
+
*/
|
|
84
|
+
async function abortSession(baseUrl, sessionId, directory) {
|
|
85
|
+
try {
|
|
86
|
+
await fetch(`${baseUrl}/session/${sessionId}/abort`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
"x-opencode-directory": directory,
|
|
90
|
+
},
|
|
91
|
+
signal: AbortSignal.timeout(2000),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Ignore errors during abort - server may already be down
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Disposes the server instance with timeout.
|
|
100
|
+
*/
|
|
101
|
+
async function disposeInstance(baseUrl, directory) {
|
|
102
|
+
try {
|
|
103
|
+
await fetch(`${baseUrl}/instance/dispose`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: {
|
|
106
|
+
"x-opencode-directory": directory,
|
|
107
|
+
},
|
|
108
|
+
signal: AbortSignal.timeout(2000),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Ignore errors during dispose - server may already be down
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Queries messages for a session and extracts model info from the first assistant message.
|
|
117
|
+
*
|
|
118
|
+
* Returns undefined if no assistant message found or query fails.
|
|
119
|
+
*/
|
|
120
|
+
async function getSessionModelInfo(baseUrl, sessionId, directory) {
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(`${baseUrl}/session/${sessionId}/message`, {
|
|
123
|
+
method: "GET",
|
|
124
|
+
headers: {
|
|
125
|
+
"x-opencode-directory": directory,
|
|
126
|
+
},
|
|
127
|
+
signal: AbortSignal.timeout(2000),
|
|
128
|
+
});
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const messages = (await response.json());
|
|
133
|
+
// Find the first assistant message
|
|
134
|
+
for (const message of messages) {
|
|
135
|
+
if (message.info.role === "assistant" &&
|
|
136
|
+
message.info.modelID &&
|
|
137
|
+
message.info.providerID) {
|
|
138
|
+
return {
|
|
139
|
+
providerID: message.info.providerID,
|
|
140
|
+
modelID: message.info.modelID,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Ignore errors - caller will use fallback
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export { PromptSubmitError, SessionCreateError, abortSession, createSession, disposeInstance, getSessionModelInfo, sendPrompt, };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode server spawning and management.
|
|
3
|
+
*
|
|
4
|
+
* Handles spawning the OpenCode server process and waiting for it to start.
|
|
5
|
+
*/
|
|
6
|
+
import type { ChildProcess } from "node:child_process";
|
|
7
|
+
/** Error thrown when server fails to start */
|
|
8
|
+
declare class ServerStartError extends Error {
|
|
9
|
+
constructor(message: string, cause?: Error);
|
|
10
|
+
}
|
|
11
|
+
/** Result of spawning the server */
|
|
12
|
+
interface SpawnServerResult {
|
|
13
|
+
url: string;
|
|
14
|
+
process: ChildProcess;
|
|
15
|
+
}
|
|
16
|
+
/** Options for spawning the server */
|
|
17
|
+
interface SpawnServerOptions {
|
|
18
|
+
cwd: string;
|
|
19
|
+
signal: AbortSignal;
|
|
20
|
+
/** Environment variables to pass to the server process */
|
|
21
|
+
env?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Spawns OpenCode server and waits for it to start.
|
|
25
|
+
*
|
|
26
|
+
* @returns Server URL and process handle
|
|
27
|
+
*/
|
|
28
|
+
declare function spawnServer(options: SpawnServerOptions): Promise<SpawnServerResult>;
|
|
29
|
+
export { ServerStartError, spawnServer };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode server spawning and management.
|
|
3
|
+
*
|
|
4
|
+
* Handles spawning the OpenCode server process and waiting for it to start.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { resolveBinary } from "../../resolve-binary.js";
|
|
8
|
+
/** Error thrown when server fails to start */
|
|
9
|
+
class ServerStartError extends Error {
|
|
10
|
+
constructor(message, cause) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ServerStartError";
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Spawns OpenCode server and waits for it to start.
|
|
18
|
+
*
|
|
19
|
+
* @returns Server URL and process handle
|
|
20
|
+
*/
|
|
21
|
+
async function spawnServer(options) {
|
|
22
|
+
const { cwd, signal, env } = options;
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const bin = resolveBinary({
|
|
25
|
+
name: "opencode",
|
|
26
|
+
command: "opencode",
|
|
27
|
+
environmentVariable: "AXEXEC_OPENCODE_PATH",
|
|
28
|
+
installHint: "npm install -g opencode-ai",
|
|
29
|
+
});
|
|
30
|
+
const child = spawn(bin, ["serve", "--port", "0"], {
|
|
31
|
+
cwd,
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
env: { ...process.env, ...env },
|
|
34
|
+
});
|
|
35
|
+
let stderrBuffer = "";
|
|
36
|
+
let resolved = false;
|
|
37
|
+
// Cleanup function to remove all listeners
|
|
38
|
+
const cleanup = () => {
|
|
39
|
+
child.stdout.removeListener("data", onStdoutData);
|
|
40
|
+
child.stderr.removeListener("data", onStderrData);
|
|
41
|
+
child.removeListener("error", onError);
|
|
42
|
+
child.removeListener("close", onClose);
|
|
43
|
+
signal.removeEventListener("abort", onAbort);
|
|
44
|
+
};
|
|
45
|
+
// Handle spawn errors
|
|
46
|
+
const onError = (error) => {
|
|
47
|
+
cleanup();
|
|
48
|
+
if (error.code === "ENOENT") {
|
|
49
|
+
reject(new ServerStartError("OpenCode binary not found. Make sure opencode-ai is installed.", error));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
reject(new ServerStartError(`Failed to spawn OpenCode server`, error));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
child.once("error", onError);
|
|
56
|
+
// Capture stderr for error messages
|
|
57
|
+
const onStderrData = (chunk) => {
|
|
58
|
+
stderrBuffer += chunk.toString();
|
|
59
|
+
};
|
|
60
|
+
child.stderr.on("data", onStderrData);
|
|
61
|
+
// Watch stdout for "listening on" message
|
|
62
|
+
let buffer = "";
|
|
63
|
+
const onStdoutData = (chunk) => {
|
|
64
|
+
buffer += chunk.toString();
|
|
65
|
+
// Parse "opencode server listening on http://127.0.0.1:XXXX"
|
|
66
|
+
const match = buffer.match(/opencode server listening on (https?:\/\/[^\s]+)/u);
|
|
67
|
+
if (match?.[1]) {
|
|
68
|
+
const url = match[1];
|
|
69
|
+
resolved = true;
|
|
70
|
+
cleanup();
|
|
71
|
+
resolve({ url, process: child });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
child.stdout.on("data", onStdoutData);
|
|
75
|
+
// Handle early exit (including code 0 before URL is detected)
|
|
76
|
+
const onClose = (code) => {
|
|
77
|
+
if (resolved)
|
|
78
|
+
return; // Already resolved, ignore
|
|
79
|
+
cleanup();
|
|
80
|
+
const message = stderrBuffer.trim()
|
|
81
|
+
? `Server exited with code ${code}: ${stderrBuffer}`
|
|
82
|
+
: `Server exited with code ${code} before startup complete`;
|
|
83
|
+
reject(new ServerStartError(message));
|
|
84
|
+
};
|
|
85
|
+
child.once("close", onClose);
|
|
86
|
+
// Handle abort signal
|
|
87
|
+
const onAbort = () => {
|
|
88
|
+
cleanup();
|
|
89
|
+
child.kill("SIGTERM");
|
|
90
|
+
reject(new ServerStartError("Server start aborted"));
|
|
91
|
+
};
|
|
92
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export { ServerStartError, spawnServer };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE client for OpenCode server mode.
|
|
3
|
+
*
|
|
4
|
+
* Connects to the OpenCode server's /event endpoint and streams
|
|
5
|
+
* parsed SSE events.
|
|
6
|
+
*/
|
|
7
|
+
import type { OpenCodeSSEEvent } from "./server-types.js";
|
|
8
|
+
/** Options for SSE client */
|
|
9
|
+
interface SSEClientOptions {
|
|
10
|
+
/** Base URL of OpenCode server (e.g., "http://127.0.0.1:4096") */
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
/** Working directory to set via header */
|
|
13
|
+
directory: string;
|
|
14
|
+
/** Abort signal for cancellation */
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
/** Callback to receive the reader for manual cancellation */
|
|
17
|
+
onReader?: (reader: ReadableStreamDefaultReader<Uint8Array>) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Error when SSE connection fails */
|
|
20
|
+
declare class SSEConnectionError extends Error {
|
|
21
|
+
readonly statusCode?: number;
|
|
22
|
+
constructor(message: string, statusCode?: number);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Connects to OpenCode server's SSE endpoint and yields events.
|
|
26
|
+
*
|
|
27
|
+
* The generator will yield events until:
|
|
28
|
+
* - The connection is closed by the server
|
|
29
|
+
* - The abort signal is triggered
|
|
30
|
+
* - An error occurs
|
|
31
|
+
*/
|
|
32
|
+
declare function streamSSE(options: SSEClientOptions): AsyncGenerator<OpenCodeSSEEvent>;
|
|
33
|
+
export { SSEConnectionError, streamSSE };
|