@liontree/opencode-agent-sdk 0.1.0 → 0.1.1
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/README.md +90 -8
- package/dist/config.d.ts +1 -1
- package/dist/config.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/runtime.d.ts +24 -1
- package/dist/runtime.js +383 -55
- package/dist/types.d.ts +35 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This package wraps OpenCode's lower-level session and SSE APIs with a more agent
|
|
|
6
6
|
|
|
7
7
|
- declare agents once
|
|
8
8
|
- create reusable sessions
|
|
9
|
-
- call `query()` / `receiveResponse()` or `
|
|
9
|
+
- call `query()` / `receiveResponse()` or `runAgent()`
|
|
10
10
|
- consume normalized text, tool-call, status, error, and final-result events
|
|
11
11
|
|
|
12
12
|
It is inspired by the usability level of Claude's agent SDK, but it does not try to be API-compatible 1:1.
|
|
@@ -58,15 +58,14 @@ This package declares `@opencode-ai/sdk` and `opencode-ai` as dependencies, so y
|
|
|
58
58
|
npm install @liontree/opencode-agent-sdk
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
See `examples/subagents.ts` for a complete subagent lineage and `resumeAgent()` example.
|
|
62
|
+
|
|
61
63
|
```ts
|
|
62
64
|
import { createAgentRuntime } from "@liontree/opencode-agent-sdk"
|
|
63
65
|
|
|
64
66
|
const runtime = await createAgentRuntime({
|
|
65
67
|
directory: "/app",
|
|
66
68
|
model: "openai/gpt-5.4",
|
|
67
|
-
permission: {
|
|
68
|
-
"*": "allow",
|
|
69
|
-
},
|
|
70
69
|
mcp: {
|
|
71
70
|
terminal: {
|
|
72
71
|
type: "remote",
|
|
@@ -139,14 +138,63 @@ const runtime = await createAgentRuntime({
|
|
|
139
138
|
})
|
|
140
139
|
```
|
|
141
140
|
|
|
142
|
-
If you only need the final answer instead of a streamed event loop, use `
|
|
141
|
+
If you only need the final answer instead of a streamed event loop, use `runAgent()`:
|
|
143
142
|
|
|
144
143
|
```ts
|
|
145
|
-
const result = await session.
|
|
144
|
+
const result = await session.runAgent("Summarize the current repository")
|
|
146
145
|
|
|
147
146
|
console.log(result.text)
|
|
148
147
|
```
|
|
149
148
|
|
|
149
|
+
To stream descendant subagent activity in the same turn, pass `includeSubagents: true` and read `event.source.chainText`:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
await session.query("Investigate the auth flow", { includeSubagents: true })
|
|
153
|
+
|
|
154
|
+
for await (const event of session.receiveResponse()) {
|
|
155
|
+
if (event.type === "tool_call") {
|
|
156
|
+
console.log(`[${event.source.chainText}]`, event.toolName, event.status)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
You can also reopen an existing session and optionally continue it with another turn:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const reopened = await runtime.resumeAgent({
|
|
165
|
+
sessionID: "sess_123",
|
|
166
|
+
prompt: "Continue from the last auth findings.",
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
for await (const event of reopened.receiveResponse()) {
|
|
170
|
+
if (event.type === "text") {
|
|
171
|
+
process.stdout.write(event.text)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If you want a subagent to be able to launch another subagent via the `task` tool, you must allow it in that agent's permissions. OpenCode controls `task` separately from normal read/edit/bash permissions.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const runtime = await createAgentRuntime({
|
|
180
|
+
directory: "/app",
|
|
181
|
+
model: "openai/gpt-5.4",
|
|
182
|
+
config: {
|
|
183
|
+
agent: {
|
|
184
|
+
general: {
|
|
185
|
+
permission: {
|
|
186
|
+
task: {
|
|
187
|
+
"*": "allow",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Use `config.agent.<name>.permission.task` when you want to override a built-in agent such as `general`. For custom agents declared in `options.agents`, you can also set `permission` directly on the agent definition.
|
|
197
|
+
|
|
150
198
|
## Main API
|
|
151
199
|
|
|
152
200
|
### `createAgentRuntime(options)`
|
|
@@ -163,10 +211,29 @@ Creates a managed OpenCode runtime and injects:
|
|
|
163
211
|
|
|
164
212
|
Creates a reusable OpenCode session and returns an `OpencodeAgentSession`.
|
|
165
213
|
|
|
214
|
+
### `runtime.openSession(sessionID, { agent, model })`
|
|
215
|
+
|
|
216
|
+
Opens an existing OpenCode session and returns an `OpencodeAgentSession` handle.
|
|
217
|
+
|
|
218
|
+
### `runtime.listAgents()`
|
|
219
|
+
|
|
220
|
+
Lists the agents currently available from the OpenCode runtime, including built-in subagents such as `general` and `explore`.
|
|
221
|
+
|
|
222
|
+
### `runtime.runAgent({ agent, prompt, model })`
|
|
223
|
+
|
|
224
|
+
Creates a fresh session, runs one turn, and resolves the final result.
|
|
225
|
+
|
|
226
|
+
### `runtime.resumeAgent({ sessionID, prompt?, agent?, model?, includeSubagents? })`
|
|
227
|
+
|
|
228
|
+
Reopens an existing session and optionally starts another turn on it.
|
|
229
|
+
|
|
166
230
|
### `session.query(prompt, options)`
|
|
167
231
|
|
|
168
232
|
Starts one turn on the session.
|
|
169
233
|
|
|
234
|
+
- pass `includeSubagents: true` to receive descendant subagent-session events in the same stream
|
|
235
|
+
- to let a subagent launch more subagents, configure that agent's `permission.task`
|
|
236
|
+
|
|
170
237
|
### `session.receiveResponse()`
|
|
171
238
|
|
|
172
239
|
Consumes the active turn as an async stream of normalized events:
|
|
@@ -179,7 +246,7 @@ Consumes the active turn as an async stream of normalized events:
|
|
|
179
246
|
|
|
180
247
|
`receiveResponse()` is single-consumer per turn.
|
|
181
248
|
|
|
182
|
-
### `session.
|
|
249
|
+
### `session.runAgent(prompt, options)`
|
|
183
250
|
|
|
184
251
|
Convenience helper that internally calls `query()` and consumes the response stream until a final result is available.
|
|
185
252
|
|
|
@@ -212,9 +279,24 @@ Emitted for prompt failures, session errors, SSE errors, or shutdown problems.
|
|
|
212
279
|
|
|
213
280
|
Emitted exactly once when the final assistant message can be resolved.
|
|
214
281
|
|
|
282
|
+
## Source Metadata
|
|
283
|
+
|
|
284
|
+
Every normalized event now includes a `source` object describing where it came from.
|
|
285
|
+
|
|
286
|
+
- `source.agentType`: raw agent name such as `build`, `general`, or `explore`
|
|
287
|
+
- `source.agentLabel`: display label with sibling ordinal when needed, such as `general#2`
|
|
288
|
+
- `source.chainText`: readable lineage such as `build -> general#2 -> explore#1`
|
|
289
|
+
- `source.sessionID`: the session that produced the event
|
|
290
|
+
- `source.parentSessionID`: parent session ID when the event came from a descendant session
|
|
291
|
+
- `source.rootSessionID`: root session for the current lineage
|
|
292
|
+
- `source.taskID`: session ID when the event came from a subagent task, otherwise `null`
|
|
293
|
+
- `source.sourceToolCallID`: originating `task` tool call when known
|
|
294
|
+
|
|
295
|
+
`event.agent` and `result.agent` are preserved for compatibility and match `source.agentType`.
|
|
296
|
+
|
|
215
297
|
## Notes
|
|
216
298
|
|
|
217
299
|
- This SDK is higher-level than raw OpenCode, not a workflow engine.
|
|
218
|
-
- It does not implement
|
|
300
|
+
- It exposes OpenCode subagent lineage metadata, but it does not implement orchestration policy for you.
|
|
219
301
|
- It does not aim for complete Claude SDK compatibility.
|
|
220
302
|
- Model resolution order is: per-call override -> session default -> agent default -> runtime default -> OpenCode config.
|
package/dist/config.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentDefinition, JsonObject, JsonValue, ModelReference, ModelSpec } from "./types.js";
|
|
2
2
|
export type PlainObject = JsonObject;
|
|
3
3
|
type BuildRuntimeConfigOptions = {
|
|
4
|
-
agents
|
|
4
|
+
agents?: Record<string, AgentDefinition>;
|
|
5
5
|
config?: PlainObject;
|
|
6
6
|
mcp?: PlainObject;
|
|
7
7
|
model?: ModelReference;
|
package/dist/config.js
CHANGED
|
@@ -65,15 +65,25 @@ function buildAgentConfig(agents) {
|
|
|
65
65
|
mode: definition.mode ?? "primary",
|
|
66
66
|
prompt: definition.prompt,
|
|
67
67
|
};
|
|
68
|
+
if (definition.description) {
|
|
69
|
+
config.description = definition.description;
|
|
70
|
+
}
|
|
71
|
+
if (definition.model) {
|
|
72
|
+
config.model = toModelString(definition.model);
|
|
73
|
+
}
|
|
74
|
+
if (definition.permission) {
|
|
75
|
+
config.permission = definition.permission;
|
|
76
|
+
}
|
|
68
77
|
return [name, config];
|
|
69
78
|
});
|
|
70
79
|
return Object.fromEntries(entries);
|
|
71
80
|
}
|
|
72
81
|
export function buildRuntimeConfig({ agents, config, mcp, model, permission, rawConfigContent, }) {
|
|
82
|
+
const resolvedAgents = agents ?? {};
|
|
73
83
|
const inheritedInlineConfig = parseRuntimeConfigContent(resolveInlineConfigContent(rawConfigContent));
|
|
74
84
|
const optionConfig = config ?? {};
|
|
75
85
|
const managedConfig = {
|
|
76
|
-
agent: buildAgentConfig(
|
|
86
|
+
agent: buildAgentConfig(resolvedAgents),
|
|
77
87
|
};
|
|
78
88
|
if (mcp) {
|
|
79
89
|
managedConfig.mcp = mcp;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { buildRuntimeConfig, deepMergeConfig, normalizeModelReference, parseModelSpec, parseRuntimeConfigContent, resolveInlineConfigContent, } from "./config.js";
|
|
2
2
|
export { createAgentRuntime, OpencodeAgentRuntime, OpencodeAgentSession, type Session } from "./runtime.js";
|
|
3
|
-
export type { AgentDefinition, AgentErrorEvent, AgentErrorInfo, AgentQueryOptions, AgentResponseEvent, AgentResultEvent, AgentRunOptions, AgentRuntimeOptions, AgentRuntimeRunOptions, AgentSessionOptions, AgentStatusEvent, AgentTextEvent, AgentToolCallEvent, AgentTurnResult, JsonArray, JsonObject, JsonPrimitive, JsonValue, ModelReference, ModelSpec, } from "./types.js";
|
|
3
|
+
export type { AgentDefinition, AgentErrorEvent, AgentErrorInfo, AgentEventSource, AgentMode, AgentQueryOptions, AgentResponseEvent, AgentResultEvent, AgentRunOptions, AgentRuntimeOptions, AgentRuntimeRunOptions, AgentSessionOptions, AgentStatusEvent, AgentTextEvent, AgentToolCallEvent, AgentTurnResult, JsonArray, JsonObject, JsonPrimitive, JsonValue, ModelReference, ModelSpec, ResumeAgentOptions, RuntimeAgentInfo, } from "./types.js";
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type OpencodeClient, type Session } from "@opencode-ai/sdk/v2";
|
|
2
|
-
import type { AgentDefinition, AgentQueryOptions, AgentResponseEvent, AgentRunOptions, AgentRuntimeOptions, AgentRuntimeRunOptions, AgentSessionOptions, AgentTurnResult, ModelReference, ModelSpec } from "./types.js";
|
|
2
|
+
import type { AgentDefinition, AgentEventSource, AgentQueryOptions, AgentResponseEvent, AgentRunOptions, AgentRuntimeOptions, AgentRuntimeRunOptions, AgentSessionOptions, AgentTurnResult, ModelReference, ModelSpec, ResumeAgentOptions, RuntimeAgentInfo } from "./types.js";
|
|
3
3
|
type ManagedServer = {
|
|
4
4
|
close: () => void;
|
|
5
5
|
};
|
|
@@ -15,6 +15,7 @@ export declare class OpencodeAgentSession {
|
|
|
15
15
|
query(prompt: string, options?: AgentQueryOptions): Promise<void>;
|
|
16
16
|
receiveResponse(): AsyncGenerator<AgentResponseEvent, void, unknown>;
|
|
17
17
|
run(prompt: string, options?: AgentRunOptions): Promise<AgentTurnResult>;
|
|
18
|
+
runAgent(prompt: string, options?: AgentRunOptions): Promise<AgentTurnResult>;
|
|
18
19
|
abort(): Promise<void>;
|
|
19
20
|
interrupt(): Promise<void>;
|
|
20
21
|
}
|
|
@@ -25,12 +26,34 @@ export declare class OpencodeAgentRuntime {
|
|
|
25
26
|
private readonly defaultModel?;
|
|
26
27
|
private disposed;
|
|
27
28
|
private readonly agentDefinitions;
|
|
29
|
+
private runtimeAgentsPromise;
|
|
30
|
+
private readonly sessionInfoCache;
|
|
31
|
+
private readonly sessionNodeCache;
|
|
28
32
|
constructor(client: OpencodeClient, directory: string, agents: Record<string, AgentDefinition>, managedServer?: ManagedServer | undefined, defaultModel?: ModelReference | undefined);
|
|
33
|
+
private getSessionNode;
|
|
34
|
+
private getSiblingOrdinal;
|
|
35
|
+
trackSessionInfo(session: Session): void;
|
|
36
|
+
trackSessionAgentType(sessionID: string, agentType: string, force?: boolean): void;
|
|
37
|
+
trackTaskSessionLink(input: {
|
|
38
|
+
agentType: string | null;
|
|
39
|
+
childSessionID: string;
|
|
40
|
+
parentSessionID: string;
|
|
41
|
+
sourceToolCallID: string | null;
|
|
42
|
+
}): void;
|
|
43
|
+
getSessionInfo(sessionID: string): Promise<Session>;
|
|
44
|
+
ensureSessionAgentType(sessionID: string, fallbackAgentType?: string): Promise<string | null>;
|
|
45
|
+
primeSessionLineage(sessionID: string, fallbackAgentType?: string): Promise<void>;
|
|
46
|
+
getKnownEventSource(sessionID: string, fallbackAgentType?: string): AgentEventSource;
|
|
29
47
|
dispose(): Promise<void>;
|
|
30
48
|
getAgent(name: string): AgentDefinition;
|
|
49
|
+
listAgents(): Promise<RuntimeAgentInfo[]>;
|
|
50
|
+
private getRuntimeAgentInfo;
|
|
31
51
|
resolveModel(agentName: string, override?: ModelReference): Promise<ModelSpec>;
|
|
52
|
+
openSession(sessionID: string, options?: AgentSessionOptions): Promise<OpencodeAgentSession>;
|
|
32
53
|
createSession(options?: AgentSessionOptions): Promise<OpencodeAgentSession>;
|
|
33
54
|
run(options: AgentRuntimeRunOptions): Promise<AgentTurnResult>;
|
|
55
|
+
runAgent(options: AgentRuntimeRunOptions): Promise<AgentTurnResult>;
|
|
56
|
+
resumeAgent(options: ResumeAgentOptions): Promise<OpencodeAgentSession>;
|
|
34
57
|
}
|
|
35
58
|
export declare function createAgentRuntime(options: AgentRuntimeOptions): Promise<OpencodeAgentRuntime>;
|
|
36
59
|
export type { Session };
|
package/dist/runtime.js
CHANGED
|
@@ -7,9 +7,11 @@ function isSessionEvent(event) {
|
|
|
7
7
|
return (event.type === "message.part.updated" ||
|
|
8
8
|
event.type === "message.part.delta" ||
|
|
9
9
|
event.type === "message.updated" ||
|
|
10
|
+
event.type === "session.created" ||
|
|
10
11
|
event.type === "session.status" ||
|
|
11
12
|
event.type === "session.idle" ||
|
|
12
|
-
event.type === "session.error"
|
|
13
|
+
event.type === "session.error" ||
|
|
14
|
+
event.type === "session.updated");
|
|
13
15
|
}
|
|
14
16
|
function getEventSessionID(event) {
|
|
15
17
|
switch (event.type) {
|
|
@@ -19,14 +21,43 @@ function getEventSessionID(event) {
|
|
|
19
21
|
return event.properties.sessionID;
|
|
20
22
|
case "message.updated":
|
|
21
23
|
return event.properties.info.sessionID;
|
|
24
|
+
case "session.created":
|
|
25
|
+
return event.properties.info.id;
|
|
22
26
|
case "session.status":
|
|
23
27
|
return event.properties.sessionID;
|
|
24
28
|
case "session.idle":
|
|
25
29
|
return event.properties.sessionID;
|
|
26
30
|
case "session.error":
|
|
27
31
|
return event.properties.sessionID;
|
|
32
|
+
case "session.updated":
|
|
33
|
+
return event.properties.info.id;
|
|
28
34
|
}
|
|
29
35
|
}
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return value !== null && typeof value === "object";
|
|
38
|
+
}
|
|
39
|
+
function getRecordString(value, key) {
|
|
40
|
+
if (!isRecord(value)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const candidate = value[key];
|
|
44
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
|
|
45
|
+
}
|
|
46
|
+
function parseSubagentFromTitle(title) {
|
|
47
|
+
if (!title) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const match = /\(@([^()]+) subagent\)$/.exec(title.trim());
|
|
51
|
+
return match?.[1] ?? null;
|
|
52
|
+
}
|
|
53
|
+
function compareSessionNodes(a, b, sessionInfoCache) {
|
|
54
|
+
const aCreated = sessionInfoCache.get(a.sessionID)?.time.created ?? Number.MAX_SAFE_INTEGER;
|
|
55
|
+
const bCreated = sessionInfoCache.get(b.sessionID)?.time.created ?? Number.MAX_SAFE_INTEGER;
|
|
56
|
+
if (aCreated !== bCreated) {
|
|
57
|
+
return aCreated - bCreated;
|
|
58
|
+
}
|
|
59
|
+
return a.sessionID.localeCompare(b.sessionID);
|
|
60
|
+
}
|
|
30
61
|
function createIdleGate() {
|
|
31
62
|
let resolved = false;
|
|
32
63
|
let resolveSignal = () => { };
|
|
@@ -43,14 +74,16 @@ function createIdleGate() {
|
|
|
43
74
|
},
|
|
44
75
|
};
|
|
45
76
|
}
|
|
46
|
-
function createTurnContext() {
|
|
77
|
+
function createTurnContext(rootSessionID) {
|
|
47
78
|
return {
|
|
48
79
|
assistantMessageIDs: new Set(),
|
|
80
|
+
hasAssistantMessage: false,
|
|
49
81
|
idleGate: createIdleGate(),
|
|
50
|
-
|
|
51
|
-
|
|
82
|
+
lastStatusBySessionID: new Map([[rootSessionID, "pending"]]),
|
|
83
|
+
latestRootAssistantMessageID: null,
|
|
52
84
|
sawBusy: false,
|
|
53
85
|
textByPartID: new Map(),
|
|
86
|
+
trackedSessionIDs: new Set([rootSessionID]),
|
|
54
87
|
toolStatusByPartID: new Map(),
|
|
55
88
|
};
|
|
56
89
|
}
|
|
@@ -140,44 +173,64 @@ class ActiveTurn {
|
|
|
140
173
|
emit(event) {
|
|
141
174
|
this.queue.push(event);
|
|
142
175
|
}
|
|
143
|
-
|
|
176
|
+
getEventSource(sessionID, fallbackAgentType) {
|
|
177
|
+
const rootFallback = sessionID === this.args.sessionID ? this.args.agent : undefined;
|
|
178
|
+
return this.args.runtime.getKnownEventSource(sessionID, fallbackAgentType ?? rootFallback);
|
|
179
|
+
}
|
|
180
|
+
emitError(error, sessionID = this.args.sessionID, fallbackAgentType) {
|
|
181
|
+
const source = this.getEventSource(sessionID, fallbackAgentType);
|
|
144
182
|
this.emit({
|
|
145
183
|
type: "error",
|
|
146
|
-
agent:
|
|
147
|
-
sessionID
|
|
184
|
+
agent: source.agentType,
|
|
185
|
+
sessionID,
|
|
186
|
+
source,
|
|
148
187
|
error: toErrorInfo(error),
|
|
149
188
|
});
|
|
150
189
|
}
|
|
151
190
|
markIdleIfReady(context) {
|
|
152
|
-
if (context.sawBusy || context.
|
|
191
|
+
if (context.sawBusy || context.hasAssistantMessage) {
|
|
192
|
+
if (this.args.includeSubagents) {
|
|
193
|
+
for (const sessionID of context.trackedSessionIDs) {
|
|
194
|
+
if (context.lastStatusBySessionID.get(sessionID) !== "idle") {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
153
199
|
context.idleGate.mark();
|
|
154
200
|
}
|
|
155
201
|
}
|
|
156
|
-
emitStatus(context, status) {
|
|
157
|
-
|
|
202
|
+
emitStatus(sessionID, context, status, fallbackAgentType) {
|
|
203
|
+
const previous = context.lastStatusBySessionID.get(sessionID) ?? "";
|
|
204
|
+
if (!status || status === previous) {
|
|
158
205
|
return;
|
|
159
206
|
}
|
|
160
|
-
context.
|
|
207
|
+
context.lastStatusBySessionID.set(sessionID, status);
|
|
208
|
+
const source = this.getEventSource(sessionID, fallbackAgentType);
|
|
161
209
|
this.emit({
|
|
162
210
|
type: "status",
|
|
163
|
-
agent:
|
|
164
|
-
sessionID
|
|
211
|
+
agent: source.agentType,
|
|
212
|
+
sessionID,
|
|
213
|
+
source,
|
|
165
214
|
status,
|
|
166
215
|
});
|
|
167
216
|
}
|
|
168
|
-
emitTextEvent(event) {
|
|
217
|
+
emitTextEvent(sessionID, event) {
|
|
218
|
+
const source = this.getEventSource(sessionID);
|
|
169
219
|
this.emit({
|
|
170
220
|
type: "text",
|
|
171
|
-
agent:
|
|
172
|
-
sessionID
|
|
221
|
+
agent: source.agentType,
|
|
222
|
+
sessionID,
|
|
223
|
+
source,
|
|
173
224
|
...event,
|
|
174
225
|
});
|
|
175
226
|
}
|
|
176
|
-
emitToolCallEvent(event) {
|
|
227
|
+
emitToolCallEvent(sessionID, event) {
|
|
228
|
+
const source = this.getEventSource(sessionID);
|
|
177
229
|
this.emit({
|
|
178
230
|
type: "tool_call",
|
|
179
|
-
agent:
|
|
180
|
-
sessionID
|
|
231
|
+
agent: source.agentType,
|
|
232
|
+
sessionID,
|
|
233
|
+
source,
|
|
181
234
|
...event,
|
|
182
235
|
});
|
|
183
236
|
}
|
|
@@ -187,7 +240,7 @@ class ActiveTurn {
|
|
|
187
240
|
}
|
|
188
241
|
const previous = context.textByPartID.get(event.properties.partID) ?? "";
|
|
189
242
|
context.textByPartID.set(event.properties.partID, `${previous}${event.properties.delta}`);
|
|
190
|
-
this.emitTextEvent({
|
|
243
|
+
this.emitTextEvent(event.properties.sessionID, {
|
|
191
244
|
format: "delta",
|
|
192
245
|
messageID: event.properties.messageID,
|
|
193
246
|
partID: event.properties.partID,
|
|
@@ -210,7 +263,7 @@ class ActiveTurn {
|
|
|
210
263
|
if (!format || !text) {
|
|
211
264
|
return;
|
|
212
265
|
}
|
|
213
|
-
this.emitTextEvent({
|
|
266
|
+
this.emitTextEvent(part.sessionID, {
|
|
214
267
|
format,
|
|
215
268
|
messageID: part.messageID,
|
|
216
269
|
partID: part.id,
|
|
@@ -243,32 +296,97 @@ class ActiveTurn {
|
|
|
243
296
|
if (part.state.status === "error") {
|
|
244
297
|
event.error = part.state.error;
|
|
245
298
|
}
|
|
246
|
-
this.emitToolCallEvent({
|
|
299
|
+
this.emitToolCallEvent(part.sessionID, {
|
|
247
300
|
...event,
|
|
248
301
|
});
|
|
249
302
|
}
|
|
303
|
+
extractTaskChildSessionID(part) {
|
|
304
|
+
if (part.tool !== "task") {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
const metadata = "metadata" in part.state ? part.state.metadata : undefined;
|
|
308
|
+
return getRecordString(metadata, "sessionId") ?? getRecordString(part.state.input, "task_id");
|
|
309
|
+
}
|
|
310
|
+
extractTaskAgentType(part) {
|
|
311
|
+
if (part.tool !== "task") {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
return getRecordString(part.state.input, "subagent_type");
|
|
315
|
+
}
|
|
316
|
+
trackDiscoveredSessions(event, context) {
|
|
317
|
+
switch (event.type) {
|
|
318
|
+
case "session.created":
|
|
319
|
+
case "session.updated": {
|
|
320
|
+
this.args.runtime.trackSessionInfo(event.properties.info);
|
|
321
|
+
if (this.args.includeSubagents &&
|
|
322
|
+
event.properties.info.parentID &&
|
|
323
|
+
context.trackedSessionIDs.has(event.properties.info.parentID)) {
|
|
324
|
+
context.trackedSessionIDs.add(event.properties.info.id);
|
|
325
|
+
if (!context.lastStatusBySessionID.has(event.properties.info.id)) {
|
|
326
|
+
context.lastStatusBySessionID.set(event.properties.info.id, "pending");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
case "message.updated":
|
|
332
|
+
this.args.runtime.trackSessionAgentType(event.properties.info.sessionID, event.properties.info.agent);
|
|
333
|
+
return;
|
|
334
|
+
case "message.part.updated": {
|
|
335
|
+
if (!this.args.includeSubagents || event.properties.part.type !== "tool") {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const part = event.properties.part;
|
|
339
|
+
if (!context.trackedSessionIDs.has(part.sessionID)) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const childSessionID = this.extractTaskChildSessionID(part);
|
|
343
|
+
if (!childSessionID) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.args.runtime.trackTaskSessionLink({
|
|
347
|
+
agentType: this.extractTaskAgentType(part),
|
|
348
|
+
childSessionID,
|
|
349
|
+
parentSessionID: part.sessionID,
|
|
350
|
+
sourceToolCallID: part.callID,
|
|
351
|
+
});
|
|
352
|
+
context.trackedSessionIDs.add(childSessionID);
|
|
353
|
+
if (!context.lastStatusBySessionID.has(childSessionID)) {
|
|
354
|
+
context.lastStatusBySessionID.set(childSessionID, "pending");
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
default:
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
250
362
|
handleSessionEvent(event, context) {
|
|
251
363
|
switch (event.type) {
|
|
364
|
+
case "session.created":
|
|
365
|
+
case "session.updated":
|
|
366
|
+
return;
|
|
252
367
|
case "session.status":
|
|
253
368
|
if (event.properties.status.type === "busy") {
|
|
254
369
|
context.sawBusy = true;
|
|
255
370
|
}
|
|
371
|
+
this.emitStatus(event.properties.sessionID, context, event.properties.status.type);
|
|
256
372
|
if (event.properties.status.type === "idle") {
|
|
257
373
|
this.markIdleIfReady(context);
|
|
258
374
|
}
|
|
259
|
-
this.emitStatus(context, event.properties.status.type);
|
|
260
375
|
return;
|
|
261
376
|
case "session.idle":
|
|
262
377
|
this.markIdleIfReady(context);
|
|
263
|
-
this.emitStatus(context, "idle");
|
|
378
|
+
this.emitStatus(event.properties.sessionID, context, "idle");
|
|
264
379
|
return;
|
|
265
380
|
case "session.error":
|
|
266
|
-
this.emitError(event.properties.error);
|
|
381
|
+
this.emitError(event.properties.error, event.properties.sessionID);
|
|
267
382
|
return;
|
|
268
383
|
case "message.updated":
|
|
269
384
|
if (event.properties.info.role === "assistant") {
|
|
270
385
|
context.assistantMessageIDs.add(event.properties.info.id);
|
|
271
|
-
context.
|
|
386
|
+
context.hasAssistantMessage = true;
|
|
387
|
+
if (event.properties.info.sessionID === this.args.sessionID) {
|
|
388
|
+
context.latestRootAssistantMessageID = event.properties.info.id;
|
|
389
|
+
}
|
|
272
390
|
}
|
|
273
391
|
return;
|
|
274
392
|
case "message.part.delta":
|
|
@@ -344,12 +462,15 @@ class ActiveTurn {
|
|
|
344
462
|
};
|
|
345
463
|
}
|
|
346
464
|
async run() {
|
|
347
|
-
|
|
465
|
+
await this.args.runtime.primeSessionLineage(this.args.sessionID, this.args.agent);
|
|
466
|
+
const context = createTurnContext(this.args.sessionID);
|
|
348
467
|
const events = await this.subscribeToEvents();
|
|
349
468
|
const consumeTask = (async () => {
|
|
350
469
|
try {
|
|
351
470
|
for await (const event of events.stream) {
|
|
352
|
-
|
|
471
|
+
this.trackDiscoveredSessions(event, context);
|
|
472
|
+
const sessionID = getEventSessionID(event);
|
|
473
|
+
if (!sessionID || !context.trackedSessionIDs.has(sessionID)) {
|
|
353
474
|
continue;
|
|
354
475
|
}
|
|
355
476
|
this.handleSessionEvent(event, context);
|
|
@@ -380,12 +501,7 @@ class ActiveTurn {
|
|
|
380
501
|
}
|
|
381
502
|
catch (error) {
|
|
382
503
|
turnError = toErrorInfo(error);
|
|
383
|
-
this.
|
|
384
|
-
type: "error",
|
|
385
|
-
agent: this.args.agent,
|
|
386
|
-
sessionID: this.args.sessionID,
|
|
387
|
-
error: turnError,
|
|
388
|
-
});
|
|
504
|
+
this.emitError(turnError);
|
|
389
505
|
}
|
|
390
506
|
finally {
|
|
391
507
|
await delay(POST_IDLE_EVENT_DRAIN_MS);
|
|
@@ -404,12 +520,7 @@ class ActiveTurn {
|
|
|
404
520
|
catch (error) {
|
|
405
521
|
const shutdownError = toErrorInfo(error);
|
|
406
522
|
turnError ??= shutdownError;
|
|
407
|
-
this.
|
|
408
|
-
type: "error",
|
|
409
|
-
agent: this.args.agent,
|
|
410
|
-
sessionID: this.args.sessionID,
|
|
411
|
-
error: shutdownError,
|
|
412
|
-
});
|
|
523
|
+
this.emitError(shutdownError);
|
|
413
524
|
}
|
|
414
525
|
finally {
|
|
415
526
|
if (shutdownTimer) {
|
|
@@ -418,34 +529,31 @@ class ActiveTurn {
|
|
|
418
529
|
}
|
|
419
530
|
}
|
|
420
531
|
try {
|
|
421
|
-
const resolved = await this.resolveLatestAssistantMessage(this.args.sessionID, context.
|
|
532
|
+
const resolved = await this.resolveLatestAssistantMessage(this.args.sessionID, context.latestRootAssistantMessageID);
|
|
533
|
+
const source = this.getEventSource(this.args.sessionID, this.args.agent);
|
|
422
534
|
const result = {
|
|
423
|
-
agent:
|
|
535
|
+
agent: source.agentType,
|
|
424
536
|
error: turnError ?? getAssistantMessageError(resolved.info),
|
|
425
537
|
info: resolved.info,
|
|
426
|
-
messageID: resolved.info?.id ?? context.
|
|
538
|
+
messageID: resolved.info?.id ?? context.latestRootAssistantMessageID,
|
|
427
539
|
parts: resolved.parts,
|
|
428
540
|
sessionID: this.args.sessionID,
|
|
541
|
+
source,
|
|
429
542
|
text: extractTextFromParts(resolved.parts),
|
|
430
543
|
};
|
|
431
544
|
if (result.info || result.parts.length > 0 || result.error) {
|
|
432
545
|
this.finalResult = result;
|
|
433
546
|
this.emit({
|
|
434
547
|
type: "result",
|
|
435
|
-
agent:
|
|
548
|
+
agent: source.agentType,
|
|
436
549
|
sessionID: this.args.sessionID,
|
|
550
|
+
source,
|
|
437
551
|
result,
|
|
438
552
|
});
|
|
439
553
|
}
|
|
440
554
|
}
|
|
441
555
|
catch (error) {
|
|
442
|
-
|
|
443
|
-
this.emit({
|
|
444
|
-
type: "error",
|
|
445
|
-
agent: this.args.agent,
|
|
446
|
-
sessionID: this.args.sessionID,
|
|
447
|
-
error: resolveError,
|
|
448
|
-
});
|
|
556
|
+
this.emitError(toErrorInfo(error));
|
|
449
557
|
}
|
|
450
558
|
finally {
|
|
451
559
|
this.queue.close();
|
|
@@ -478,13 +586,16 @@ export class OpencodeAgentSession {
|
|
|
478
586
|
if (!agent) {
|
|
479
587
|
throw new Error("No agent selected. Pass agent to createSession(), query(), or run().");
|
|
480
588
|
}
|
|
589
|
+
this.runtime.trackSessionAgentType(this.id, agent, true);
|
|
481
590
|
const model = await this.runtime.resolveModel(agent, options.model ?? this.defaultModel);
|
|
482
591
|
this.activeTurn = new ActiveTurn({
|
|
483
592
|
agent,
|
|
484
593
|
client: this.runtime.client,
|
|
485
594
|
directory: this.runtime.directory,
|
|
595
|
+
includeSubagents: options.includeSubagents ?? false,
|
|
486
596
|
model,
|
|
487
597
|
prompt,
|
|
598
|
+
runtime: this.runtime,
|
|
488
599
|
sessionID: this.id,
|
|
489
600
|
});
|
|
490
601
|
}
|
|
@@ -528,6 +639,9 @@ export class OpencodeAgentSession {
|
|
|
528
639
|
}
|
|
529
640
|
throw new Error("Agent turn completed without a final result");
|
|
530
641
|
}
|
|
642
|
+
async runAgent(prompt, options = {}) {
|
|
643
|
+
return this.run(prompt, options);
|
|
644
|
+
}
|
|
531
645
|
async abort() {
|
|
532
646
|
await requireData("session.abort", this.runtime.client.session.abort({
|
|
533
647
|
sessionID: this.id,
|
|
@@ -545,6 +659,9 @@ export class OpencodeAgentRuntime {
|
|
|
545
659
|
defaultModel;
|
|
546
660
|
disposed = false;
|
|
547
661
|
agentDefinitions;
|
|
662
|
+
runtimeAgentsPromise = null;
|
|
663
|
+
sessionInfoCache = new Map();
|
|
664
|
+
sessionNodeCache = new Map();
|
|
548
665
|
constructor(client, directory, agents, managedServer, defaultModel) {
|
|
549
666
|
this.client = client;
|
|
550
667
|
this.directory = directory;
|
|
@@ -552,6 +669,149 @@ export class OpencodeAgentRuntime {
|
|
|
552
669
|
this.defaultModel = defaultModel;
|
|
553
670
|
this.agentDefinitions = new Map(Object.entries(agents));
|
|
554
671
|
}
|
|
672
|
+
getSessionNode(sessionID) {
|
|
673
|
+
let node = this.sessionNodeCache.get(sessionID);
|
|
674
|
+
if (!node) {
|
|
675
|
+
node = {
|
|
676
|
+
agentType: null,
|
|
677
|
+
parentSessionID: null,
|
|
678
|
+
sessionID,
|
|
679
|
+
sourceToolCallID: null,
|
|
680
|
+
title: null,
|
|
681
|
+
};
|
|
682
|
+
this.sessionNodeCache.set(sessionID, node);
|
|
683
|
+
}
|
|
684
|
+
return node;
|
|
685
|
+
}
|
|
686
|
+
getSiblingOrdinal(node) {
|
|
687
|
+
if (!node.parentSessionID || !node.agentType) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
const siblings = [...this.sessionNodeCache.values()]
|
|
691
|
+
.filter((candidate) => candidate.parentSessionID === node.parentSessionID && candidate.agentType === node.agentType)
|
|
692
|
+
.sort((left, right) => compareSessionNodes(left, right, this.sessionInfoCache));
|
|
693
|
+
const index = siblings.findIndex((candidate) => candidate.sessionID === node.sessionID);
|
|
694
|
+
return index >= 0 ? index + 1 : null;
|
|
695
|
+
}
|
|
696
|
+
trackSessionInfo(session) {
|
|
697
|
+
this.sessionInfoCache.set(session.id, session);
|
|
698
|
+
const node = this.getSessionNode(session.id);
|
|
699
|
+
node.parentSessionID = session.parentID ?? null;
|
|
700
|
+
node.title = session.title;
|
|
701
|
+
const titleAgent = parseSubagentFromTitle(session.title);
|
|
702
|
+
if (titleAgent && !node.agentType) {
|
|
703
|
+
node.agentType = titleAgent;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
trackSessionAgentType(sessionID, agentType, force = false) {
|
|
707
|
+
if (!agentType) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const node = this.getSessionNode(sessionID);
|
|
711
|
+
if (force || !node.agentType) {
|
|
712
|
+
node.agentType = agentType;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
trackTaskSessionLink(input) {
|
|
716
|
+
const node = this.getSessionNode(input.childSessionID);
|
|
717
|
+
if (!node.parentSessionID) {
|
|
718
|
+
node.parentSessionID = input.parentSessionID;
|
|
719
|
+
}
|
|
720
|
+
if (!node.sourceToolCallID && input.sourceToolCallID) {
|
|
721
|
+
node.sourceToolCallID = input.sourceToolCallID;
|
|
722
|
+
}
|
|
723
|
+
if (!node.agentType && input.agentType) {
|
|
724
|
+
node.agentType = input.agentType;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
async getSessionInfo(sessionID) {
|
|
728
|
+
const cached = this.sessionInfoCache.get(sessionID);
|
|
729
|
+
if (cached) {
|
|
730
|
+
return cached;
|
|
731
|
+
}
|
|
732
|
+
const session = await requireData("session.get", this.client.session.get({
|
|
733
|
+
sessionID,
|
|
734
|
+
directory: this.directory,
|
|
735
|
+
}));
|
|
736
|
+
this.trackSessionInfo(session);
|
|
737
|
+
return session;
|
|
738
|
+
}
|
|
739
|
+
async ensureSessionAgentType(sessionID, fallbackAgentType) {
|
|
740
|
+
const node = this.getSessionNode(sessionID);
|
|
741
|
+
if (node.agentType) {
|
|
742
|
+
return node.agentType;
|
|
743
|
+
}
|
|
744
|
+
const titleAgent = parseSubagentFromTitle(node.title);
|
|
745
|
+
if (titleAgent) {
|
|
746
|
+
node.agentType = titleAgent;
|
|
747
|
+
return node.agentType;
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
const messages = await requireData("session.messages", this.client.session.messages({
|
|
751
|
+
sessionID,
|
|
752
|
+
directory: this.directory,
|
|
753
|
+
}));
|
|
754
|
+
const messageWithAgent = [...messages]
|
|
755
|
+
.reverse()
|
|
756
|
+
.find((entry) => typeof entry.info.agent === "string" && entry.info.agent.length > 0);
|
|
757
|
+
if (messageWithAgent?.info.agent) {
|
|
758
|
+
node.agentType = messageWithAgent.info.agent;
|
|
759
|
+
return node.agentType;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
// Ignore cache warm-up failures and fall back below.
|
|
764
|
+
}
|
|
765
|
+
if (fallbackAgentType) {
|
|
766
|
+
node.agentType = fallbackAgentType;
|
|
767
|
+
return node.agentType;
|
|
768
|
+
}
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
async primeSessionLineage(sessionID, fallbackAgentType) {
|
|
772
|
+
let currentSessionID = sessionID;
|
|
773
|
+
while (currentSessionID) {
|
|
774
|
+
const session = await this.getSessionInfo(currentSessionID);
|
|
775
|
+
await this.ensureSessionAgentType(currentSessionID, currentSessionID === sessionID ? fallbackAgentType : undefined);
|
|
776
|
+
currentSessionID = session.parentID ?? null;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
getKnownEventSource(sessionID, fallbackAgentType) {
|
|
780
|
+
const leaf = this.getSessionNode(sessionID);
|
|
781
|
+
if (!leaf.agentType && fallbackAgentType) {
|
|
782
|
+
leaf.agentType = fallbackAgentType;
|
|
783
|
+
}
|
|
784
|
+
const lineage = [];
|
|
785
|
+
const seen = new Set();
|
|
786
|
+
let cursor = leaf;
|
|
787
|
+
while (cursor && !seen.has(cursor.sessionID)) {
|
|
788
|
+
lineage.push(cursor);
|
|
789
|
+
seen.add(cursor.sessionID);
|
|
790
|
+
cursor = cursor.parentSessionID ? this.sessionNodeCache.get(cursor.parentSessionID) : undefined;
|
|
791
|
+
}
|
|
792
|
+
const ordered = lineage.reverse();
|
|
793
|
+
const chain = ordered.map((node, index) => {
|
|
794
|
+
const agentType = node.agentType ?? (node.sessionID === sessionID ? fallbackAgentType : undefined) ?? "unknown";
|
|
795
|
+
if (index === 0) {
|
|
796
|
+
return agentType;
|
|
797
|
+
}
|
|
798
|
+
const ordinal = this.getSiblingOrdinal(node);
|
|
799
|
+
return ordinal ? `${agentType}#${ordinal}` : agentType;
|
|
800
|
+
});
|
|
801
|
+
const agentType = leaf.agentType ?? fallbackAgentType ?? "unknown";
|
|
802
|
+
return {
|
|
803
|
+
agentLabel: chain[chain.length - 1] ?? agentType,
|
|
804
|
+
agentType,
|
|
805
|
+
chain,
|
|
806
|
+
chainText: chain.join(" -> "),
|
|
807
|
+
depth: Math.max(chain.length - 1, 0),
|
|
808
|
+
parentSessionID: leaf.parentSessionID,
|
|
809
|
+
rootSessionID: ordered[0]?.sessionID ?? sessionID,
|
|
810
|
+
sessionID,
|
|
811
|
+
sourceToolCallID: leaf.sourceToolCallID,
|
|
812
|
+
taskID: leaf.parentSessionID ? sessionID : null,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
555
815
|
async dispose() {
|
|
556
816
|
if (this.disposed) {
|
|
557
817
|
return;
|
|
@@ -566,9 +826,49 @@ export class OpencodeAgentRuntime {
|
|
|
566
826
|
}
|
|
567
827
|
return agent;
|
|
568
828
|
}
|
|
829
|
+
async listAgents() {
|
|
830
|
+
if (!this.runtimeAgentsPromise) {
|
|
831
|
+
this.runtimeAgentsPromise = requireData("app.agents", this.client.app.agents({
|
|
832
|
+
directory: this.directory,
|
|
833
|
+
})).then((agents) => agents.map((agent) => {
|
|
834
|
+
const info = {
|
|
835
|
+
mode: agent.mode,
|
|
836
|
+
name: agent.name,
|
|
837
|
+
};
|
|
838
|
+
if (agent.color) {
|
|
839
|
+
info.color = agent.color;
|
|
840
|
+
}
|
|
841
|
+
if (agent.description) {
|
|
842
|
+
info.description = agent.description;
|
|
843
|
+
}
|
|
844
|
+
if (typeof agent.hidden === "boolean") {
|
|
845
|
+
info.hidden = agent.hidden;
|
|
846
|
+
}
|
|
847
|
+
if (agent.model) {
|
|
848
|
+
info.model = {
|
|
849
|
+
modelID: agent.model.modelID,
|
|
850
|
+
providerID: agent.model.providerID,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
if (typeof agent.native === "boolean") {
|
|
854
|
+
info.native = agent.native;
|
|
855
|
+
}
|
|
856
|
+
if (typeof agent.steps === "number") {
|
|
857
|
+
info.steps = agent.steps;
|
|
858
|
+
}
|
|
859
|
+
return info;
|
|
860
|
+
}));
|
|
861
|
+
}
|
|
862
|
+
return this.runtimeAgentsPromise;
|
|
863
|
+
}
|
|
864
|
+
async getRuntimeAgentInfo(name) {
|
|
865
|
+
const agents = await this.listAgents();
|
|
866
|
+
return agents.find((agent) => agent.name === name) ?? null;
|
|
867
|
+
}
|
|
569
868
|
async resolveModel(agentName, override) {
|
|
570
|
-
const
|
|
571
|
-
const
|
|
869
|
+
const configuredModel = this.agentDefinitions.get(agentName)?.model;
|
|
870
|
+
const runtimeModel = (await this.getRuntimeAgentInfo(agentName))?.model;
|
|
871
|
+
const selected = override ?? configuredModel ?? runtimeModel ?? this.defaultModel;
|
|
572
872
|
if (selected) {
|
|
573
873
|
return normalizeModelReference(selected);
|
|
574
874
|
}
|
|
@@ -581,6 +881,12 @@ export class OpencodeAgentRuntime {
|
|
|
581
881
|
}
|
|
582
882
|
return resolved;
|
|
583
883
|
}
|
|
884
|
+
async openSession(sessionID, options = {}) {
|
|
885
|
+
const session = await this.getSessionInfo(sessionID);
|
|
886
|
+
await this.primeSessionLineage(sessionID, options.agent);
|
|
887
|
+
const defaultAgent = (await this.ensureSessionAgentType(sessionID, options.agent ?? parseSubagentFromTitle(session.title) ?? undefined)) ?? undefined;
|
|
888
|
+
return new OpencodeAgentSession(this, sessionID, defaultAgent, options.model);
|
|
889
|
+
}
|
|
584
890
|
async createSession(options = {}) {
|
|
585
891
|
if (this.disposed) {
|
|
586
892
|
throw new Error("Runtime has been disposed");
|
|
@@ -588,6 +894,10 @@ export class OpencodeAgentRuntime {
|
|
|
588
894
|
const session = await requireData("session.create", this.client.session.create({
|
|
589
895
|
directory: this.directory,
|
|
590
896
|
}));
|
|
897
|
+
this.trackSessionInfo(session);
|
|
898
|
+
if (options.agent) {
|
|
899
|
+
this.trackSessionAgentType(session.id, options.agent);
|
|
900
|
+
}
|
|
591
901
|
return new OpencodeAgentSession(this, session.id, options.agent, options.model);
|
|
592
902
|
}
|
|
593
903
|
async run(options) {
|
|
@@ -598,10 +908,28 @@ export class OpencodeAgentRuntime {
|
|
|
598
908
|
const session = await this.createSession(sessionOptions);
|
|
599
909
|
return session.run(options.prompt);
|
|
600
910
|
}
|
|
911
|
+
async runAgent(options) {
|
|
912
|
+
return this.run(options);
|
|
913
|
+
}
|
|
914
|
+
async resumeAgent(options) {
|
|
915
|
+
const session = await this.openSession(options.sessionID, {
|
|
916
|
+
...(options.agent ? { agent: options.agent } : {}),
|
|
917
|
+
...(options.model ? { model: options.model } : {}),
|
|
918
|
+
});
|
|
919
|
+
if (options.prompt) {
|
|
920
|
+
await session.query(options.prompt, {
|
|
921
|
+
...(options.agent ? { agent: options.agent } : {}),
|
|
922
|
+
...(typeof options.includeSubagents === "boolean" ? { includeSubagents: options.includeSubagents } : {}),
|
|
923
|
+
...(options.model ? { model: options.model } : {}),
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
return session;
|
|
927
|
+
}
|
|
601
928
|
}
|
|
602
929
|
export async function createAgentRuntime(options) {
|
|
930
|
+
const agents = options.agents ?? {};
|
|
603
931
|
const runtimeConfig = buildRuntimeConfig({
|
|
604
|
-
agents
|
|
932
|
+
agents,
|
|
605
933
|
...(options.config ? { config: options.config } : {}),
|
|
606
934
|
...(options.mcp ? { mcp: options.mcp } : {}),
|
|
607
935
|
...(options.model ? { model: options.model } : {}),
|
|
@@ -614,5 +942,5 @@ export async function createAgentRuntime(options) {
|
|
|
614
942
|
timeout: options.timeoutMs ?? 15000,
|
|
615
943
|
config: runtimeConfig,
|
|
616
944
|
});
|
|
617
|
-
return new OpencodeAgentRuntime(client, options.directory,
|
|
945
|
+
return new OpencodeAgentRuntime(client, options.directory, agents, server, options.model);
|
|
618
946
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message, Part, ToolState } from "@opencode-ai/sdk/v2";
|
|
1
|
+
import type { Agent as OpencodeAgent, Message, Part, ToolState } from "@opencode-ai/sdk/v2";
|
|
2
2
|
export type JsonPrimitive = boolean | null | number | string;
|
|
3
3
|
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
|
4
4
|
export type JsonArray = JsonValue[];
|
|
@@ -10,14 +10,16 @@ export type ModelSpec = {
|
|
|
10
10
|
providerID: string;
|
|
11
11
|
};
|
|
12
12
|
export type ModelReference = ModelSpec | `${string}/${string}`;
|
|
13
|
+
export type AgentMode = "all" | "primary" | "subagent";
|
|
13
14
|
export type AgentDefinition = {
|
|
14
15
|
description?: string;
|
|
15
16
|
model?: ModelReference;
|
|
16
|
-
mode?:
|
|
17
|
+
mode?: AgentMode;
|
|
18
|
+
permission?: JsonObject;
|
|
17
19
|
prompt: string;
|
|
18
20
|
};
|
|
19
21
|
export type AgentRuntimeOptions = {
|
|
20
|
-
agents
|
|
22
|
+
agents?: Record<string, AgentDefinition>;
|
|
21
23
|
config?: JsonObject;
|
|
22
24
|
directory: string;
|
|
23
25
|
hostname?: string;
|
|
@@ -32,13 +34,20 @@ export type AgentSessionOptions = {
|
|
|
32
34
|
agent?: string;
|
|
33
35
|
model?: ModelReference;
|
|
34
36
|
};
|
|
35
|
-
export type AgentQueryOptions = AgentSessionOptions
|
|
36
|
-
|
|
37
|
+
export type AgentQueryOptions = AgentSessionOptions & {
|
|
38
|
+
includeSubagents?: boolean;
|
|
39
|
+
};
|
|
40
|
+
export type AgentRunOptions = AgentQueryOptions;
|
|
37
41
|
export type AgentRuntimeRunOptions = {
|
|
38
42
|
agent: string;
|
|
39
43
|
model?: ModelReference;
|
|
40
44
|
prompt: string;
|
|
41
45
|
};
|
|
46
|
+
export type ResumeAgentOptions = AgentSessionOptions & {
|
|
47
|
+
includeSubagents?: boolean;
|
|
48
|
+
prompt?: string;
|
|
49
|
+
sessionID: string;
|
|
50
|
+
};
|
|
42
51
|
export type AgentErrorInfo = {
|
|
43
52
|
code?: string;
|
|
44
53
|
message: string;
|
|
@@ -46,10 +55,26 @@ export type AgentErrorInfo = {
|
|
|
46
55
|
status?: number;
|
|
47
56
|
statusCode?: number;
|
|
48
57
|
};
|
|
58
|
+
export type AgentEventSource = {
|
|
59
|
+
agentLabel: string;
|
|
60
|
+
agentType: string;
|
|
61
|
+
chain: string[];
|
|
62
|
+
chainText: string;
|
|
63
|
+
depth: number;
|
|
64
|
+
parentSessionID: string | null;
|
|
65
|
+
rootSessionID: string;
|
|
66
|
+
sessionID: string;
|
|
67
|
+
sourceToolCallID: string | null;
|
|
68
|
+
taskID: string | null;
|
|
69
|
+
};
|
|
70
|
+
export type RuntimeAgentInfo = Pick<OpencodeAgent, "color" | "description" | "hidden" | "mode" | "name" | "native" | "steps"> & {
|
|
71
|
+
model?: ModelSpec;
|
|
72
|
+
};
|
|
49
73
|
export type AgentStatusEvent = {
|
|
50
74
|
agent: string;
|
|
51
75
|
sessionID: string;
|
|
52
76
|
status: string;
|
|
77
|
+
source: AgentEventSource;
|
|
53
78
|
type: "status";
|
|
54
79
|
};
|
|
55
80
|
export type AgentTextEvent = {
|
|
@@ -58,6 +83,7 @@ export type AgentTextEvent = {
|
|
|
58
83
|
messageID: string;
|
|
59
84
|
partID: string;
|
|
60
85
|
sessionID: string;
|
|
86
|
+
source: AgentEventSource;
|
|
61
87
|
text: string;
|
|
62
88
|
type: "text";
|
|
63
89
|
};
|
|
@@ -71,6 +97,7 @@ export type AgentToolCallEvent = {
|
|
|
71
97
|
output?: string;
|
|
72
98
|
partID: string;
|
|
73
99
|
sessionID: string;
|
|
100
|
+
source: AgentEventSource;
|
|
74
101
|
status: ToolState["status"];
|
|
75
102
|
title?: string;
|
|
76
103
|
toolName: string;
|
|
@@ -80,6 +107,7 @@ export type AgentErrorEvent = {
|
|
|
80
107
|
agent: string;
|
|
81
108
|
error: AgentErrorInfo;
|
|
82
109
|
sessionID: string;
|
|
110
|
+
source: AgentEventSource;
|
|
83
111
|
type: "error";
|
|
84
112
|
};
|
|
85
113
|
export type AgentTurnResult = {
|
|
@@ -89,12 +117,14 @@ export type AgentTurnResult = {
|
|
|
89
117
|
messageID: string | null;
|
|
90
118
|
parts: Part[];
|
|
91
119
|
sessionID: string;
|
|
120
|
+
source: AgentEventSource;
|
|
92
121
|
text: string;
|
|
93
122
|
};
|
|
94
123
|
export type AgentResultEvent = {
|
|
95
124
|
agent: string;
|
|
96
125
|
result: AgentTurnResult;
|
|
97
126
|
sessionID: string;
|
|
127
|
+
source: AgentEventSource;
|
|
98
128
|
type: "result";
|
|
99
129
|
};
|
|
100
130
|
export type AgentResponseEvent = AgentErrorEvent | AgentResultEvent | AgentStatusEvent | AgentTextEvent | AgentToolCallEvent;
|