@a3s-lab/code 0.3.1 → 0.4.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/README.md +371 -509
- package/dist/chat.d.ts +97 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +179 -0
- package/dist/chat.js.map +1 -0
- package/dist/client.d.ts +199 -25
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +149 -18
- package/dist/client.js.map +1 -1
- package/dist/generate.d.ts +130 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +283 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +44 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -2
- package/dist/index.js.map +1 -1
- package/dist/message.d.ts +157 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +279 -0
- package/dist/message.js.map +1 -0
- package/dist/provider.d.ts +64 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +60 -0
- package/dist/provider.js.map +1 -0
- package/dist/session.d.ts +540 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +1092 -0
- package/dist/session.js.map +1 -0
- package/dist/tool.d.ts +106 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +71 -0
- package/dist/tool.js.map +1 -0
- package/package.json +1 -1
- package/proto/code_agent.proto +256 -14
package/dist/session.js
ADDED
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session — The core abstraction for A3S Code SDK.
|
|
3
|
+
*
|
|
4
|
+
* A Session binds a workspace and model at creation time (immutable).
|
|
5
|
+
* All agentic, generation, streaming, and context management calls are methods on the session.
|
|
6
|
+
*
|
|
7
|
+
* Every `session.send()` can trigger the AgenticLoop — when the model calls tools,
|
|
8
|
+
* the session automatically enters the loop (generate → tool call → execute → reflect → repeat).
|
|
9
|
+
*
|
|
10
|
+
* Supports `using` syntax for automatic cleanup via Symbol.asyncDispose.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { A3sClient, createProvider } from '@a3s-lab/code';
|
|
15
|
+
*
|
|
16
|
+
* const client = new A3sClient();
|
|
17
|
+
* const openai = createProvider({ name: 'openai', apiKey: 'sk-xxx' });
|
|
18
|
+
*
|
|
19
|
+
* await using session = await client.createSession({
|
|
20
|
+
* model: openai('gpt-4o'),
|
|
21
|
+
* workspace: '/project',
|
|
22
|
+
* system: 'You are a senior software engineer.',
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Simple question — model answers directly
|
|
26
|
+
* const { text } = await session.send('What is TypeScript?');
|
|
27
|
+
*
|
|
28
|
+
* // Complex task — auto-enters AgenticLoop
|
|
29
|
+
* const { text, steps, toolCalls } = await session.send(
|
|
30
|
+
* 'Refactor the auth module to use JWT',
|
|
31
|
+
* );
|
|
32
|
+
*
|
|
33
|
+
* // Streaming with real-time events
|
|
34
|
+
* const { eventStream } = session.sendStream('Fix all TODOs in src/');
|
|
35
|
+
* for await (const event of eventStream) {
|
|
36
|
+
* if (event.type === 'text') process.stdout.write(event.content);
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Internal Helpers
|
|
42
|
+
// ============================================================================
|
|
43
|
+
function resolveMessages(prompt, messages) {
|
|
44
|
+
if (messages && messages.length > 0)
|
|
45
|
+
return messages;
|
|
46
|
+
if (prompt)
|
|
47
|
+
return [{ role: 'user', content: prompt }];
|
|
48
|
+
throw new Error('Either "prompt" or "messages" must be provided');
|
|
49
|
+
}
|
|
50
|
+
async function executeClientTool(toolDef, toolCall, onToolCall) {
|
|
51
|
+
const args = toolCall.arguments ? JSON.parse(toolCall.arguments) : {};
|
|
52
|
+
const event = {
|
|
53
|
+
toolCallId: toolCall.id,
|
|
54
|
+
toolName: toolCall.name,
|
|
55
|
+
args,
|
|
56
|
+
};
|
|
57
|
+
if (onToolCall) {
|
|
58
|
+
const callbackResult = await onToolCall(event);
|
|
59
|
+
if (callbackResult !== undefined && !toolDef.execute) {
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
output: typeof callbackResult === 'string'
|
|
63
|
+
? callbackResult
|
|
64
|
+
: JSON.stringify(callbackResult),
|
|
65
|
+
error: '',
|
|
66
|
+
metadata: {},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (toolDef.execute) {
|
|
71
|
+
try {
|
|
72
|
+
const result = await toolDef.execute(args, { toolCallId: toolCall.id });
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
output: typeof result === 'string' ? result : JSON.stringify(result),
|
|
76
|
+
error: '',
|
|
77
|
+
metadata: {},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
output: '',
|
|
84
|
+
error: err instanceof Error ? err.message : String(err),
|
|
85
|
+
metadata: {},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
output: '',
|
|
92
|
+
error: `Tool "${toolCall.name}" has no execute function and onToolCall did not return a result`,
|
|
93
|
+
metadata: {},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Map friendly lane name to proto enum */
|
|
97
|
+
function laneNameToProto(lane) {
|
|
98
|
+
const map = {
|
|
99
|
+
control: 'SESSION_LANE_CONTROL',
|
|
100
|
+
query: 'SESSION_LANE_QUERY',
|
|
101
|
+
execute: 'SESSION_LANE_EXECUTE',
|
|
102
|
+
generate: 'SESSION_LANE_GENERATE',
|
|
103
|
+
};
|
|
104
|
+
return map[lane];
|
|
105
|
+
}
|
|
106
|
+
/** Map friendly handler mode to proto enum */
|
|
107
|
+
function handlerModeToProto(mode) {
|
|
108
|
+
const map = {
|
|
109
|
+
internal: 'TASK_HANDLER_MODE_INTERNAL',
|
|
110
|
+
external: 'TASK_HANDLER_MODE_EXTERNAL',
|
|
111
|
+
hybrid: 'TASK_HANDLER_MODE_HYBRID',
|
|
112
|
+
};
|
|
113
|
+
return map[mode];
|
|
114
|
+
}
|
|
115
|
+
/** Map friendly timeout action to proto enum */
|
|
116
|
+
function timeoutActionToProto(action) {
|
|
117
|
+
if (action === 'auto-approve')
|
|
118
|
+
return 'TIMEOUT_ACTION_AUTO_APPROVE';
|
|
119
|
+
return 'TIMEOUT_ACTION_REJECT';
|
|
120
|
+
}
|
|
121
|
+
/** Map friendly permission action to proto enum */
|
|
122
|
+
function permissionActionToProto(action) {
|
|
123
|
+
const map = {
|
|
124
|
+
allow: 'PERMISSION_DECISION_ALLOW',
|
|
125
|
+
deny: 'PERMISSION_DECISION_DENY',
|
|
126
|
+
ask: 'PERMISSION_DECISION_ASK',
|
|
127
|
+
};
|
|
128
|
+
return map[action] || 'PERMISSION_DECISION_ALLOW';
|
|
129
|
+
}
|
|
130
|
+
/** Map a proto AgenticGenerateEvent to an SDK AgentLoopEvent */
|
|
131
|
+
function mapProtoAgenticEvent(event) {
|
|
132
|
+
switch (event.type) {
|
|
133
|
+
case 'text':
|
|
134
|
+
return event.content ? { type: 'text', content: event.content } : null;
|
|
135
|
+
case 'tool_call':
|
|
136
|
+
if (!event.toolCall)
|
|
137
|
+
return null;
|
|
138
|
+
return {
|
|
139
|
+
type: 'tool_call',
|
|
140
|
+
toolName: event.toolCall.name,
|
|
141
|
+
args: event.toolCall.arguments ? JSON.parse(event.toolCall.arguments) : {},
|
|
142
|
+
toolCallId: event.toolCall.id,
|
|
143
|
+
};
|
|
144
|
+
case 'tool_result':
|
|
145
|
+
return event.toolResult
|
|
146
|
+
? {
|
|
147
|
+
type: 'tool_result',
|
|
148
|
+
toolCallId: event.toolCallId || '',
|
|
149
|
+
output: event.toolResult.output,
|
|
150
|
+
success: event.toolResult.success,
|
|
151
|
+
}
|
|
152
|
+
: null;
|
|
153
|
+
case 'step_finish':
|
|
154
|
+
return {
|
|
155
|
+
type: 'step_finish',
|
|
156
|
+
stepIndex: event.stepIndex ?? 0,
|
|
157
|
+
text: event.stepText || '',
|
|
158
|
+
toolCalls: [],
|
|
159
|
+
};
|
|
160
|
+
case 'error':
|
|
161
|
+
return {
|
|
162
|
+
type: 'error',
|
|
163
|
+
message: event.errorMessage || 'Unknown error',
|
|
164
|
+
recoverable: event.recoverable ?? false,
|
|
165
|
+
};
|
|
166
|
+
case 'done':
|
|
167
|
+
return { type: 'done', finishReason: event.finishReason || 'stop' };
|
|
168
|
+
case 'confirmation_required':
|
|
169
|
+
return {
|
|
170
|
+
type: 'confirmation_required',
|
|
171
|
+
confirmationId: event.confirmationId || '',
|
|
172
|
+
toolName: event.toolName || '',
|
|
173
|
+
args: event.toolArgs ? JSON.parse(event.toolArgs) : {},
|
|
174
|
+
timeout: event.timeoutMs ?? 30000,
|
|
175
|
+
};
|
|
176
|
+
case 'confirmation_received':
|
|
177
|
+
return { type: 'confirmation_received', approved: event.approved ?? false };
|
|
178
|
+
case 'subagent_start':
|
|
179
|
+
return {
|
|
180
|
+
type: 'subagent_start',
|
|
181
|
+
agentName: event.agentName || '',
|
|
182
|
+
task: event.agentTask || '',
|
|
183
|
+
sessionId: event.agentSessionId || '',
|
|
184
|
+
};
|
|
185
|
+
case 'subagent_end':
|
|
186
|
+
return {
|
|
187
|
+
type: 'subagent_end',
|
|
188
|
+
agentName: event.agentName || '',
|
|
189
|
+
result: event.agentResult || '',
|
|
190
|
+
};
|
|
191
|
+
case 'context_compact':
|
|
192
|
+
return {
|
|
193
|
+
type: 'context_compact',
|
|
194
|
+
beforeTokens: event.beforeTokens ?? 0,
|
|
195
|
+
afterTokens: event.afterTokens ?? 0,
|
|
196
|
+
};
|
|
197
|
+
default:
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Session Class
|
|
203
|
+
// ============================================================================
|
|
204
|
+
/**
|
|
205
|
+
* Session — The core object for interacting with A3S Code.
|
|
206
|
+
*
|
|
207
|
+
* Created via `client.createSession()`. Workspace and model are immutable
|
|
208
|
+
* after creation. Supports `await using` for automatic cleanup.
|
|
209
|
+
*/
|
|
210
|
+
export class Session {
|
|
211
|
+
/** The underlying A3S client */
|
|
212
|
+
_client;
|
|
213
|
+
/** Session ID on the server */
|
|
214
|
+
id;
|
|
215
|
+
/** Whether this session has been closed */
|
|
216
|
+
_closed = false;
|
|
217
|
+
/** Custom registered agents */
|
|
218
|
+
_customAgents = [];
|
|
219
|
+
/** @internal — Use client.createSession() instead */
|
|
220
|
+
constructor(client, sessionId) {
|
|
221
|
+
this._client = client;
|
|
222
|
+
this.id = sessionId;
|
|
223
|
+
}
|
|
224
|
+
// --------------------------------------------------------------------------
|
|
225
|
+
// Text Generation
|
|
226
|
+
// --------------------------------------------------------------------------
|
|
227
|
+
/**
|
|
228
|
+
* Generate text from the language model.
|
|
229
|
+
*
|
|
230
|
+
* Supports multi-step tool calling via `tools` and `maxSteps`.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* const { text } = await session.generateText({ prompt: 'Hello' });
|
|
235
|
+
*
|
|
236
|
+
* // With tools
|
|
237
|
+
* const { text, steps } = await session.generateText({
|
|
238
|
+
* prompt: 'What is the weather?',
|
|
239
|
+
* tools: { weather: weatherTool },
|
|
240
|
+
* maxSteps: 5,
|
|
241
|
+
* });
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
async generateText(options) {
|
|
245
|
+
this._ensureOpen();
|
|
246
|
+
const messages = resolveMessages(options.prompt, options.messages);
|
|
247
|
+
const maxSteps = options.maxSteps ?? 1;
|
|
248
|
+
const allSteps = [];
|
|
249
|
+
let fullText = '';
|
|
250
|
+
let lastFinishReason = 'stop';
|
|
251
|
+
const allToolCalls = [];
|
|
252
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
253
|
+
const stepMessages = step === 0 ? messages : [];
|
|
254
|
+
const response = await this._client.generate(this.id, stepMessages);
|
|
255
|
+
const stepText = response.message?.content || '';
|
|
256
|
+
fullText += stepText;
|
|
257
|
+
lastFinishReason = response.finishReason;
|
|
258
|
+
const stepToolCalls = response.toolCalls || [];
|
|
259
|
+
const stepToolResults = [];
|
|
260
|
+
allToolCalls.push(...stepToolCalls);
|
|
261
|
+
// Execute client-side tools
|
|
262
|
+
const clientToolCalls = stepToolCalls.filter((tc) => options.tools && tc.name in options.tools);
|
|
263
|
+
for (const tc of clientToolCalls) {
|
|
264
|
+
const toolDef = options.tools[tc.name];
|
|
265
|
+
const result = await executeClientTool(toolDef, tc, options.onToolCall);
|
|
266
|
+
stepToolResults.push(result);
|
|
267
|
+
tc.result = result;
|
|
268
|
+
}
|
|
269
|
+
const stepResult = {
|
|
270
|
+
stepIndex: step,
|
|
271
|
+
text: stepText,
|
|
272
|
+
toolCalls: stepToolCalls,
|
|
273
|
+
toolResults: stepToolResults,
|
|
274
|
+
usage: response.usage,
|
|
275
|
+
finishReason: response.finishReason,
|
|
276
|
+
};
|
|
277
|
+
allSteps.push(stepResult);
|
|
278
|
+
if (options.onStepFinish) {
|
|
279
|
+
await options.onStepFinish(stepResult);
|
|
280
|
+
}
|
|
281
|
+
if (stepToolCalls.length === 0 || response.finishReason !== 'tool_calls') {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
text: fullText,
|
|
287
|
+
usage: allSteps.length > 0 ? allSteps[allSteps.length - 1].usage : undefined,
|
|
288
|
+
finishReason: lastFinishReason,
|
|
289
|
+
toolCalls: allToolCalls,
|
|
290
|
+
steps: allSteps,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
// --------------------------------------------------------------------------
|
|
294
|
+
// Text Streaming
|
|
295
|
+
// --------------------------------------------------------------------------
|
|
296
|
+
/**
|
|
297
|
+
* Stream text from the language model.
|
|
298
|
+
*
|
|
299
|
+
* Returns immediately with stream handles. Supports multi-step tool calling.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const { textStream } = session.streamText({ prompt: 'Explain this' });
|
|
304
|
+
* for await (const chunk of textStream) {
|
|
305
|
+
* process.stdout.write(chunk);
|
|
306
|
+
* }
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
streamText(options) {
|
|
310
|
+
this._ensureOpen();
|
|
311
|
+
const messages = resolveMessages(options.prompt, options.messages);
|
|
312
|
+
const maxSteps = options.maxSteps ?? 1;
|
|
313
|
+
let fullText = '';
|
|
314
|
+
let finalUsage;
|
|
315
|
+
let finalFinishReason;
|
|
316
|
+
const allSteps = [];
|
|
317
|
+
let resolveText;
|
|
318
|
+
let resolveUsage;
|
|
319
|
+
let resolveFinishReason;
|
|
320
|
+
let resolveSteps;
|
|
321
|
+
let rejectText;
|
|
322
|
+
const textPromise = new Promise((res, rej) => {
|
|
323
|
+
resolveText = res;
|
|
324
|
+
rejectText = rej;
|
|
325
|
+
});
|
|
326
|
+
const usagePromise = new Promise((res) => {
|
|
327
|
+
resolveUsage = res;
|
|
328
|
+
});
|
|
329
|
+
const finishReasonPromise = new Promise((res) => {
|
|
330
|
+
resolveFinishReason = res;
|
|
331
|
+
});
|
|
332
|
+
const stepsPromise = new Promise((res) => {
|
|
333
|
+
resolveSteps = res;
|
|
334
|
+
});
|
|
335
|
+
const chunks = [];
|
|
336
|
+
let streamDone = false;
|
|
337
|
+
const waiters = [];
|
|
338
|
+
function notifyWaiters() {
|
|
339
|
+
for (const w of waiters.splice(0))
|
|
340
|
+
w();
|
|
341
|
+
}
|
|
342
|
+
const sessionId = this.id;
|
|
343
|
+
const client = this._client;
|
|
344
|
+
const produce = (async () => {
|
|
345
|
+
try {
|
|
346
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
347
|
+
const stepMessages = step === 0 ? messages : [];
|
|
348
|
+
const stream = client.streamGenerate(sessionId, stepMessages);
|
|
349
|
+
let stepText = '';
|
|
350
|
+
const stepToolCalls = [];
|
|
351
|
+
const stepToolResults = [];
|
|
352
|
+
let stepFinishReason;
|
|
353
|
+
for await (const chunk of stream) {
|
|
354
|
+
if (chunk.content) {
|
|
355
|
+
fullText += chunk.content;
|
|
356
|
+
stepText += chunk.content;
|
|
357
|
+
}
|
|
358
|
+
if (chunk.toolCall)
|
|
359
|
+
stepToolCalls.push(chunk.toolCall);
|
|
360
|
+
if (chunk.finishReason) {
|
|
361
|
+
stepFinishReason = chunk.finishReason;
|
|
362
|
+
finalFinishReason = chunk.finishReason;
|
|
363
|
+
}
|
|
364
|
+
chunks.push(chunk);
|
|
365
|
+
notifyWaiters();
|
|
366
|
+
}
|
|
367
|
+
// Execute client-side tools
|
|
368
|
+
if (options.tools) {
|
|
369
|
+
for (const tc of stepToolCalls) {
|
|
370
|
+
if (tc.name in options.tools) {
|
|
371
|
+
const toolDef = options.tools[tc.name];
|
|
372
|
+
const result = await executeClientTool(toolDef, tc, options.onToolCall);
|
|
373
|
+
stepToolResults.push(result);
|
|
374
|
+
tc.result = result;
|
|
375
|
+
chunks.push({
|
|
376
|
+
type: 'tool_result',
|
|
377
|
+
sessionId,
|
|
378
|
+
content: '',
|
|
379
|
+
toolCall: tc,
|
|
380
|
+
toolResult: result,
|
|
381
|
+
metadata: {},
|
|
382
|
+
});
|
|
383
|
+
notifyWaiters();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const stepResult = {
|
|
388
|
+
stepIndex: step,
|
|
389
|
+
text: stepText,
|
|
390
|
+
toolCalls: stepToolCalls,
|
|
391
|
+
toolResults: stepToolResults,
|
|
392
|
+
usage: undefined,
|
|
393
|
+
finishReason: stepFinishReason,
|
|
394
|
+
};
|
|
395
|
+
allSteps.push(stepResult);
|
|
396
|
+
if (options.onStepFinish) {
|
|
397
|
+
await options.onStepFinish(stepResult);
|
|
398
|
+
}
|
|
399
|
+
if (stepToolCalls.length === 0 || stepFinishReason !== 'tool_calls') {
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
resolveText(fullText);
|
|
404
|
+
resolveUsage(finalUsage);
|
|
405
|
+
resolveFinishReason(finalFinishReason);
|
|
406
|
+
resolveSteps(allSteps);
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
rejectText(err);
|
|
410
|
+
throw err;
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
streamDone = true;
|
|
414
|
+
notifyWaiters();
|
|
415
|
+
}
|
|
416
|
+
})();
|
|
417
|
+
produce.catch(() => { });
|
|
418
|
+
function createIterator(transform) {
|
|
419
|
+
return {
|
|
420
|
+
[Symbol.asyncIterator]() {
|
|
421
|
+
let index = 0;
|
|
422
|
+
return {
|
|
423
|
+
async next() {
|
|
424
|
+
while (true) {
|
|
425
|
+
if (index < chunks.length) {
|
|
426
|
+
const val = transform(chunks[index++]);
|
|
427
|
+
if (val !== null)
|
|
428
|
+
return { value: val, done: false };
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (streamDone) {
|
|
432
|
+
return { value: undefined, done: true };
|
|
433
|
+
}
|
|
434
|
+
await new Promise((r) => waiters.push(r));
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
textStream: createIterator((c) => (c.content ? c.content : null)),
|
|
443
|
+
fullStream: createIterator((c) => c),
|
|
444
|
+
toolStream: createIterator((c) => (c.toolCall ? c.toolCall : null)),
|
|
445
|
+
text: textPromise,
|
|
446
|
+
usage: usagePromise,
|
|
447
|
+
finishReason: finishReasonPromise,
|
|
448
|
+
steps: stepsPromise,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
// --------------------------------------------------------------------------
|
|
452
|
+
// Structured Output
|
|
453
|
+
// --------------------------------------------------------------------------
|
|
454
|
+
/**
|
|
455
|
+
* Generate a structured object from the language model.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* const { object } = await session.generateObject({
|
|
460
|
+
* schema: JSON.stringify({ type: 'object', properties: { name: { type: 'string' } } }),
|
|
461
|
+
* prompt: 'Extract the name',
|
|
462
|
+
* });
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
async generateObject(options) {
|
|
466
|
+
this._ensureOpen();
|
|
467
|
+
const messages = resolveMessages(options.prompt, options.messages);
|
|
468
|
+
const response = await this._client.generateStructured(this.id, messages, options.schema);
|
|
469
|
+
let parsed;
|
|
470
|
+
try {
|
|
471
|
+
parsed = JSON.parse(response.data);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
parsed = response.data;
|
|
475
|
+
}
|
|
476
|
+
return { object: parsed, data: response.data, usage: response.usage };
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Stream a structured object from the language model.
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* const { partialStream, object } = session.streamObject({
|
|
484
|
+
* schema: '{"type":"object","properties":{"items":{"type":"array"}}}',
|
|
485
|
+
* prompt: 'List project files',
|
|
486
|
+
* });
|
|
487
|
+
* for await (const partial of partialStream) {
|
|
488
|
+
* console.log('partial:', partial);
|
|
489
|
+
* }
|
|
490
|
+
* const result = await object;
|
|
491
|
+
* ```
|
|
492
|
+
*/
|
|
493
|
+
streamObject(options) {
|
|
494
|
+
this._ensureOpen();
|
|
495
|
+
const messages = resolveMessages(options.prompt, options.messages);
|
|
496
|
+
let fullData = '';
|
|
497
|
+
let resolveObject;
|
|
498
|
+
let resolveData;
|
|
499
|
+
let rejectAll;
|
|
500
|
+
const objectPromise = new Promise((res, rej) => {
|
|
501
|
+
resolveObject = res;
|
|
502
|
+
rejectAll = rej;
|
|
503
|
+
});
|
|
504
|
+
const dataPromise = new Promise((res) => {
|
|
505
|
+
resolveData = res;
|
|
506
|
+
});
|
|
507
|
+
const sessionId = this.id;
|
|
508
|
+
const client = this._client;
|
|
509
|
+
const partialStream = {
|
|
510
|
+
[Symbol.asyncIterator]() {
|
|
511
|
+
let started = false;
|
|
512
|
+
let iter;
|
|
513
|
+
return {
|
|
514
|
+
async next() {
|
|
515
|
+
if (!started) {
|
|
516
|
+
started = true;
|
|
517
|
+
const stream = client.streamGenerateStructured(sessionId, messages, options.schema);
|
|
518
|
+
iter = stream[Symbol.asyncIterator]();
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const result = await iter.next();
|
|
522
|
+
if (result.done) {
|
|
523
|
+
resolveData(fullData);
|
|
524
|
+
try {
|
|
525
|
+
resolveObject(JSON.parse(fullData));
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
resolveObject(fullData);
|
|
529
|
+
}
|
|
530
|
+
return { value: undefined, done: true };
|
|
531
|
+
}
|
|
532
|
+
fullData += result.value.data;
|
|
533
|
+
return { value: result.value.data, done: false };
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
rejectAll(err);
|
|
537
|
+
throw err;
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
return { partialStream, object: objectPromise, data: dataPromise };
|
|
544
|
+
}
|
|
545
|
+
// --------------------------------------------------------------------------
|
|
546
|
+
// Context Management
|
|
547
|
+
// --------------------------------------------------------------------------
|
|
548
|
+
/** Get context usage (token counts, message count) */
|
|
549
|
+
async getContextUsage() {
|
|
550
|
+
this._ensureOpen();
|
|
551
|
+
const resp = await this._client.getContextUsage(this.id);
|
|
552
|
+
return resp.usage;
|
|
553
|
+
}
|
|
554
|
+
/** Compact the conversation context to save tokens */
|
|
555
|
+
async compactContext() {
|
|
556
|
+
this._ensureOpen();
|
|
557
|
+
await this._client.compactContext(this.id);
|
|
558
|
+
}
|
|
559
|
+
/** Clear conversation history */
|
|
560
|
+
async clearContext() {
|
|
561
|
+
this._ensureOpen();
|
|
562
|
+
await this._client.clearContext(this.id);
|
|
563
|
+
}
|
|
564
|
+
/** Get conversation messages */
|
|
565
|
+
async getMessages(limit, offset) {
|
|
566
|
+
this._ensureOpen();
|
|
567
|
+
return this._client.getMessages(this.id, limit, offset);
|
|
568
|
+
}
|
|
569
|
+
// --------------------------------------------------------------------------
|
|
570
|
+
// AgenticLoop: send() / sendStream()
|
|
571
|
+
// --------------------------------------------------------------------------
|
|
572
|
+
/**
|
|
573
|
+
* Send a message to the agent. Automatically enters the server-side
|
|
574
|
+
* AgenticLoop when the model calls tools (plan → execute → reflect → repeat).
|
|
575
|
+
*
|
|
576
|
+
* The entire loop runs on the server — built-in tools, planning, reflection,
|
|
577
|
+
* and HITL are all handled server-side. Client-side tools (if provided) are
|
|
578
|
+
* used as a fallback for tools not available on the server.
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* ```typescript
|
|
582
|
+
* // Simple question — no tools needed
|
|
583
|
+
* const { text } = await session.send('What is TypeScript?');
|
|
584
|
+
*
|
|
585
|
+
* // Complex task — server runs full AgenticLoop
|
|
586
|
+
* const { text, steps, toolCalls } = await session.send(
|
|
587
|
+
* 'Refactor the auth module to use JWT',
|
|
588
|
+
* { maxSteps: 50 },
|
|
589
|
+
* );
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
async send(prompt, options = {}) {
|
|
593
|
+
this._ensureOpen();
|
|
594
|
+
const strategyMap = {
|
|
595
|
+
auto: 'AGENTIC_STRATEGY_AUTO',
|
|
596
|
+
direct: 'AGENTIC_STRATEGY_DIRECT',
|
|
597
|
+
planned: 'AGENTIC_STRATEGY_PLANNED',
|
|
598
|
+
iterative: 'AGENTIC_STRATEGY_ITERATIVE',
|
|
599
|
+
parallel: 'AGENTIC_STRATEGY_PARALLEL',
|
|
600
|
+
};
|
|
601
|
+
const resp = await this._client.agenticGenerate(this.id, prompt, {
|
|
602
|
+
strategy: strategyMap[options.strategy || 'auto'],
|
|
603
|
+
maxSteps: options.maxSteps ?? 50,
|
|
604
|
+
reflection: options.reflection ?? true,
|
|
605
|
+
planning: options.planning === true || options.planning === 'auto',
|
|
606
|
+
});
|
|
607
|
+
// Map server response to SDK types
|
|
608
|
+
const steps = (resp.steps || []).map((s) => ({
|
|
609
|
+
stepIndex: s.stepIndex,
|
|
610
|
+
text: s.text,
|
|
611
|
+
toolCalls: s.toolCalls || [],
|
|
612
|
+
toolResults: s.toolResults || [],
|
|
613
|
+
usage: s.usage,
|
|
614
|
+
finishReason: s.finishReason,
|
|
615
|
+
}));
|
|
616
|
+
// Emit events for callbacks
|
|
617
|
+
if (options.onEvent) {
|
|
618
|
+
await options.onEvent({ type: 'done', finishReason: resp.finishReason });
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
text: resp.text,
|
|
622
|
+
steps,
|
|
623
|
+
toolCalls: resp.toolCalls || [],
|
|
624
|
+
usage: resp.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
625
|
+
finishReason: resp.finishReason || 'stop',
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Stream a message to the agent. Returns an event stream for real-time UI
|
|
630
|
+
* and a promise for the final result.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```typescript
|
|
634
|
+
* const { eventStream, result } = session.sendStream('Fix all TODOs in src/');
|
|
635
|
+
* for await (const event of eventStream) {
|
|
636
|
+
* if (event.type === 'text') process.stdout.write(event.content);
|
|
637
|
+
* if (event.type === 'tool_call') console.log(`🔧 ${event.toolName}`);
|
|
638
|
+
* }
|
|
639
|
+
* const final = await result;
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
642
|
+
sendStream(prompt, options = {}) {
|
|
643
|
+
this._ensureOpen();
|
|
644
|
+
const maxSteps = options.maxSteps ?? 50;
|
|
645
|
+
const events = [];
|
|
646
|
+
let streamDone = false;
|
|
647
|
+
const waiters = [];
|
|
648
|
+
function pushEvent(event) {
|
|
649
|
+
events.push(event);
|
|
650
|
+
for (const w of waiters.splice(0))
|
|
651
|
+
w();
|
|
652
|
+
}
|
|
653
|
+
const resultPromise = (async () => {
|
|
654
|
+
const allSteps = [];
|
|
655
|
+
const allToolCalls = [];
|
|
656
|
+
let fullText = '';
|
|
657
|
+
let totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
658
|
+
try {
|
|
659
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
660
|
+
if (options.signal?.aborted) {
|
|
661
|
+
pushEvent({ type: 'done', finishReason: 'cancelled' });
|
|
662
|
+
return { text: fullText, steps: allSteps, toolCalls: allToolCalls, usage: totalUsage, finishReason: 'cancelled' };
|
|
663
|
+
}
|
|
664
|
+
const messages = step === 0 ? [{ role: 'user', content: prompt }] : [];
|
|
665
|
+
const stream = this._client.streamGenerate(this.id, messages);
|
|
666
|
+
let stepText = '';
|
|
667
|
+
const stepToolCalls = [];
|
|
668
|
+
let stepFinishReason;
|
|
669
|
+
for await (const chunk of stream) {
|
|
670
|
+
if (chunk.content) {
|
|
671
|
+
fullText += chunk.content;
|
|
672
|
+
stepText += chunk.content;
|
|
673
|
+
pushEvent({ type: 'text', content: chunk.content });
|
|
674
|
+
}
|
|
675
|
+
if (chunk.toolCall) {
|
|
676
|
+
stepToolCalls.push(chunk.toolCall);
|
|
677
|
+
const args = chunk.toolCall.arguments ? JSON.parse(chunk.toolCall.arguments) : {};
|
|
678
|
+
pushEvent({ type: 'tool_call', toolName: chunk.toolCall.name, args, toolCallId: chunk.toolCall.id });
|
|
679
|
+
}
|
|
680
|
+
if (chunk.finishReason) {
|
|
681
|
+
stepFinishReason = chunk.finishReason;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
allToolCalls.push(...stepToolCalls);
|
|
685
|
+
// Execute client-side tools
|
|
686
|
+
const stepToolResults = [];
|
|
687
|
+
if (options.tools) {
|
|
688
|
+
for (const tc of stepToolCalls) {
|
|
689
|
+
if (tc.name in options.tools) {
|
|
690
|
+
const toolDef = options.tools[tc.name];
|
|
691
|
+
const result = await executeClientTool(toolDef, tc, options.onToolCall);
|
|
692
|
+
stepToolResults.push(result);
|
|
693
|
+
tc.result = result;
|
|
694
|
+
pushEvent({ type: 'tool_result', toolCallId: tc.id, output: result.output, success: result.success });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const stepResult = {
|
|
699
|
+
stepIndex: step,
|
|
700
|
+
text: stepText,
|
|
701
|
+
toolCalls: stepToolCalls,
|
|
702
|
+
toolResults: stepToolResults,
|
|
703
|
+
usage: undefined,
|
|
704
|
+
finishReason: stepFinishReason,
|
|
705
|
+
};
|
|
706
|
+
allSteps.push(stepResult);
|
|
707
|
+
pushEvent({ type: 'step_finish', stepIndex: step, text: stepText, toolCalls: stepToolCalls });
|
|
708
|
+
if (options.onStepFinish) {
|
|
709
|
+
await options.onStepFinish(stepResult);
|
|
710
|
+
}
|
|
711
|
+
if (stepToolCalls.length === 0 || stepFinishReason !== 'tool_calls') {
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const finishReason = allSteps.length >= maxSteps ? 'max_steps' : 'stop';
|
|
716
|
+
pushEvent({ type: 'done', finishReason });
|
|
717
|
+
return { text: fullText, steps: allSteps, toolCalls: allToolCalls, usage: totalUsage, finishReason };
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
streamDone = true;
|
|
721
|
+
for (const w of waiters.splice(0))
|
|
722
|
+
w();
|
|
723
|
+
}
|
|
724
|
+
})();
|
|
725
|
+
// Suppress unhandled rejection on the background promise
|
|
726
|
+
resultPromise.catch(() => { });
|
|
727
|
+
const eventStream = {
|
|
728
|
+
[Symbol.asyncIterator]() {
|
|
729
|
+
let index = 0;
|
|
730
|
+
return {
|
|
731
|
+
async next() {
|
|
732
|
+
while (true) {
|
|
733
|
+
if (index < events.length) {
|
|
734
|
+
return { value: events[index++], done: false };
|
|
735
|
+
}
|
|
736
|
+
if (streamDone) {
|
|
737
|
+
return { value: undefined, done: true };
|
|
738
|
+
}
|
|
739
|
+
await new Promise((r) => waiters.push(r));
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
};
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
return { eventStream, result: resultPromise };
|
|
746
|
+
}
|
|
747
|
+
// --------------------------------------------------------------------------
|
|
748
|
+
// Delegation (Subagents)
|
|
749
|
+
// --------------------------------------------------------------------------
|
|
750
|
+
/**
|
|
751
|
+
* Delegate a task to a built-in or custom agent.
|
|
752
|
+
*
|
|
753
|
+
* Uses server-side delegation — the server creates a child session with
|
|
754
|
+
* the agent's restricted permissions and executes the task.
|
|
755
|
+
*
|
|
756
|
+
* @example
|
|
757
|
+
* ```typescript
|
|
758
|
+
* const result = await session.delegate('explore', 'Find all API endpoints');
|
|
759
|
+
* console.log(result.text);
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
762
|
+
async delegate(agent, task, options) {
|
|
763
|
+
this._ensureOpen();
|
|
764
|
+
const resp = await this._client.delegateToAgent(this.id, agent, task, {
|
|
765
|
+
maxSteps: options?.maxSteps ?? 50,
|
|
766
|
+
allowedTools: options?.allowedTools,
|
|
767
|
+
});
|
|
768
|
+
const steps = (resp.steps || []).map((s) => ({
|
|
769
|
+
stepIndex: s.stepIndex,
|
|
770
|
+
text: s.text,
|
|
771
|
+
toolCalls: s.toolCalls || [],
|
|
772
|
+
toolResults: s.toolResults || [],
|
|
773
|
+
usage: s.usage,
|
|
774
|
+
finishReason: s.finishReason,
|
|
775
|
+
}));
|
|
776
|
+
return {
|
|
777
|
+
text: resp.text,
|
|
778
|
+
steps,
|
|
779
|
+
toolCalls: resp.toolCalls || [],
|
|
780
|
+
usage: resp.usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
781
|
+
finishReason: resp.finishReason || 'stop',
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Delegate a task to a subagent with streaming.
|
|
786
|
+
*
|
|
787
|
+
* Uses server-side streaming delegation for real-time event updates.
|
|
788
|
+
*/
|
|
789
|
+
delegateStream(agent, task, options) {
|
|
790
|
+
this._ensureOpen();
|
|
791
|
+
const events = [];
|
|
792
|
+
let streamDone = false;
|
|
793
|
+
const waiters = [];
|
|
794
|
+
function pushEvent(event) {
|
|
795
|
+
events.push(event);
|
|
796
|
+
for (const w of waiters.splice(0))
|
|
797
|
+
w();
|
|
798
|
+
}
|
|
799
|
+
const resultPromise = (async () => {
|
|
800
|
+
const allSteps = [];
|
|
801
|
+
const allToolCalls = [];
|
|
802
|
+
let fullText = '';
|
|
803
|
+
let totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
804
|
+
try {
|
|
805
|
+
const stream = this._client.streamDelegateToAgent(this.id, agent, task, {
|
|
806
|
+
maxSteps: options?.maxSteps ?? 50,
|
|
807
|
+
allowedTools: options?.allowedTools,
|
|
808
|
+
});
|
|
809
|
+
for await (const event of stream) {
|
|
810
|
+
const mapped = mapProtoAgenticEvent(event);
|
|
811
|
+
if (mapped)
|
|
812
|
+
pushEvent(mapped);
|
|
813
|
+
if (event.content)
|
|
814
|
+
fullText += event.content;
|
|
815
|
+
if (event.toolCall)
|
|
816
|
+
allToolCalls.push(event.toolCall);
|
|
817
|
+
if (event.usage)
|
|
818
|
+
totalUsage = event.usage;
|
|
819
|
+
}
|
|
820
|
+
const finishReason = 'stop';
|
|
821
|
+
pushEvent({ type: 'done', finishReason });
|
|
822
|
+
return { text: fullText, steps: allSteps, toolCalls: allToolCalls, usage: totalUsage, finishReason };
|
|
823
|
+
}
|
|
824
|
+
finally {
|
|
825
|
+
streamDone = true;
|
|
826
|
+
for (const w of waiters.splice(0))
|
|
827
|
+
w();
|
|
828
|
+
}
|
|
829
|
+
})();
|
|
830
|
+
resultPromise.catch(() => { });
|
|
831
|
+
const eventStream = {
|
|
832
|
+
[Symbol.asyncIterator]() {
|
|
833
|
+
let index = 0;
|
|
834
|
+
return {
|
|
835
|
+
async next() {
|
|
836
|
+
while (true) {
|
|
837
|
+
if (index < events.length) {
|
|
838
|
+
return { value: events[index++], done: false };
|
|
839
|
+
}
|
|
840
|
+
if (streamDone) {
|
|
841
|
+
return { value: undefined, done: true };
|
|
842
|
+
}
|
|
843
|
+
await new Promise((r) => waiters.push(r));
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
};
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
return { eventStream, result: resultPromise };
|
|
850
|
+
}
|
|
851
|
+
// --------------------------------------------------------------------------
|
|
852
|
+
// Lane Queue Management
|
|
853
|
+
// --------------------------------------------------------------------------
|
|
854
|
+
/** Set lane execution mode (internal/external/hybrid) */
|
|
855
|
+
async setLaneHandler(lane, config) {
|
|
856
|
+
this._ensureOpen();
|
|
857
|
+
const protoLane = laneNameToProto(lane);
|
|
858
|
+
const protoConfig = {
|
|
859
|
+
mode: handlerModeToProto(config.mode),
|
|
860
|
+
timeoutMs: config.timeout ?? 60_000,
|
|
861
|
+
};
|
|
862
|
+
await this._client.setLaneHandler(this.id, protoLane, protoConfig);
|
|
863
|
+
}
|
|
864
|
+
/** Get lane handler configuration */
|
|
865
|
+
async getLaneHandler(lane) {
|
|
866
|
+
this._ensureOpen();
|
|
867
|
+
const resp = await this._client.getLaneHandler(this.id, laneNameToProto(lane));
|
|
868
|
+
if (!resp.config)
|
|
869
|
+
return undefined;
|
|
870
|
+
const modeMap = {
|
|
871
|
+
TASK_HANDLER_MODE_INTERNAL: 'internal',
|
|
872
|
+
TASK_HANDLER_MODE_EXTERNAL: 'external',
|
|
873
|
+
TASK_HANDLER_MODE_HYBRID: 'hybrid',
|
|
874
|
+
};
|
|
875
|
+
return {
|
|
876
|
+
mode: modeMap[resp.config.mode] || 'internal',
|
|
877
|
+
timeout: resp.config.timeoutMs,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
/** List tasks waiting for external processing */
|
|
881
|
+
async listPendingTasks() {
|
|
882
|
+
this._ensureOpen();
|
|
883
|
+
const resp = await this._client.listPendingExternalTasks(this.id);
|
|
884
|
+
return resp.tasks;
|
|
885
|
+
}
|
|
886
|
+
/** Complete an external task */
|
|
887
|
+
async completeTask(taskId, result) {
|
|
888
|
+
this._ensureOpen();
|
|
889
|
+
await this._client.completeExternalTask(this.id, taskId, result.success, result.output, result.error);
|
|
890
|
+
}
|
|
891
|
+
// --------------------------------------------------------------------------
|
|
892
|
+
// Skills Management
|
|
893
|
+
// --------------------------------------------------------------------------
|
|
894
|
+
/** Load skills from a directory (uses server-side batch loading) */
|
|
895
|
+
async loadSkills(dir, recursive = true) {
|
|
896
|
+
this._ensureOpen();
|
|
897
|
+
const resp = await this._client.loadSkillsFromDir(this.id, dir, recursive);
|
|
898
|
+
return resp.loadedSkills || [];
|
|
899
|
+
}
|
|
900
|
+
/** Load a single skill by name */
|
|
901
|
+
async loadSkill(name, content) {
|
|
902
|
+
this._ensureOpen();
|
|
903
|
+
await this._client.loadSkill(this.id, name, content);
|
|
904
|
+
}
|
|
905
|
+
/** Add an inline skill definition */
|
|
906
|
+
async addSkill(skill) {
|
|
907
|
+
this._ensureOpen();
|
|
908
|
+
await this._client.loadSkill(this.id, skill.name, skill.content);
|
|
909
|
+
}
|
|
910
|
+
/** Unload a skill */
|
|
911
|
+
async unloadSkill(name) {
|
|
912
|
+
this._ensureOpen();
|
|
913
|
+
await this._client.unloadSkill(this.id, name);
|
|
914
|
+
}
|
|
915
|
+
/** List available skills */
|
|
916
|
+
async listSkills() {
|
|
917
|
+
this._ensureOpen();
|
|
918
|
+
const resp = await this._client.listSkills(this.id);
|
|
919
|
+
return resp.skills.map((s) => ({
|
|
920
|
+
name: s.name,
|
|
921
|
+
description: s.description,
|
|
922
|
+
loaded: true,
|
|
923
|
+
source: 'builtin',
|
|
924
|
+
}));
|
|
925
|
+
}
|
|
926
|
+
// --------------------------------------------------------------------------
|
|
927
|
+
// Built-in Agents
|
|
928
|
+
// --------------------------------------------------------------------------
|
|
929
|
+
/** List available agents (built-in + custom) */
|
|
930
|
+
async listAgents() {
|
|
931
|
+
this._ensureOpen();
|
|
932
|
+
return [
|
|
933
|
+
{ name: 'explore', description: 'Read-only codebase exploration', mode: 'subagent' },
|
|
934
|
+
{ name: 'plan', description: 'Read-only planning and analysis', mode: 'subagent' },
|
|
935
|
+
{ name: 'general', description: 'Multi-step task execution', mode: 'subagent' },
|
|
936
|
+
...this._customAgents.map((a) => ({
|
|
937
|
+
name: a.name,
|
|
938
|
+
description: a.description,
|
|
939
|
+
mode: 'subagent',
|
|
940
|
+
})),
|
|
941
|
+
];
|
|
942
|
+
}
|
|
943
|
+
/** Register a custom agent */
|
|
944
|
+
registerAgent(def) {
|
|
945
|
+
this._ensureOpen();
|
|
946
|
+
this._customAgents.push(def);
|
|
947
|
+
}
|
|
948
|
+
// --------------------------------------------------------------------------
|
|
949
|
+
// HITL & Permissions
|
|
950
|
+
// --------------------------------------------------------------------------
|
|
951
|
+
/** Set HITL confirmation policy */
|
|
952
|
+
async setConfirmation(config) {
|
|
953
|
+
this._ensureOpen();
|
|
954
|
+
const policy = {
|
|
955
|
+
enabled: true,
|
|
956
|
+
requireConfirmTools: config.requireConfirmation || [],
|
|
957
|
+
autoApproveTools: config.autoApprove || [],
|
|
958
|
+
defaultTimeoutMs: config.timeout ?? 30_000,
|
|
959
|
+
timeoutAction: timeoutActionToProto(config.timeoutAction),
|
|
960
|
+
yoloLanes: [],
|
|
961
|
+
};
|
|
962
|
+
await this._client.setConfirmationPolicy(this.id, policy);
|
|
963
|
+
}
|
|
964
|
+
/** Set tool permission policy */
|
|
965
|
+
async setPermissions(config) {
|
|
966
|
+
this._ensureOpen();
|
|
967
|
+
const allow = [];
|
|
968
|
+
const deny = [];
|
|
969
|
+
const ask = [];
|
|
970
|
+
for (const rule of config.rules || []) {
|
|
971
|
+
const ruleStr = rule.pattern ? `${rule.tool}:${rule.pattern}` : rule.tool;
|
|
972
|
+
if (rule.action === 'allow')
|
|
973
|
+
allow.push({ rule: ruleStr });
|
|
974
|
+
else if (rule.action === 'deny')
|
|
975
|
+
deny.push({ rule: ruleStr });
|
|
976
|
+
else
|
|
977
|
+
ask.push({ rule: ruleStr });
|
|
978
|
+
}
|
|
979
|
+
const policy = {
|
|
980
|
+
enabled: true,
|
|
981
|
+
allow,
|
|
982
|
+
deny,
|
|
983
|
+
ask,
|
|
984
|
+
defaultDecision: permissionActionToProto(config.defaultAction || 'allow'),
|
|
985
|
+
};
|
|
986
|
+
await this._client.setPermissionPolicy(this.id, policy);
|
|
987
|
+
}
|
|
988
|
+
/** Respond to a confirmation request */
|
|
989
|
+
async confirm(confirmationId, approved, reason) {
|
|
990
|
+
this._ensureOpen();
|
|
991
|
+
await this._client.confirmToolExecution(this.id, confirmationId, approved, reason);
|
|
992
|
+
}
|
|
993
|
+
// --------------------------------------------------------------------------
|
|
994
|
+
// Observability & Stats
|
|
995
|
+
// --------------------------------------------------------------------------
|
|
996
|
+
/** Get session statistics (tokens, cost, message count, tool calls) */
|
|
997
|
+
async getStats() {
|
|
998
|
+
this._ensureOpen();
|
|
999
|
+
const [ctx, cost] = await Promise.all([
|
|
1000
|
+
this._client.getContextUsage(this.id),
|
|
1001
|
+
this._client.getCostSummary({ sessionId: this.id }),
|
|
1002
|
+
]);
|
|
1003
|
+
return {
|
|
1004
|
+
totalTokens: cost.totalTokens,
|
|
1005
|
+
promptTokens: cost.totalPromptTokens,
|
|
1006
|
+
completionTokens: cost.totalCompletionTokens,
|
|
1007
|
+
totalCost: cost.totalCostUsd,
|
|
1008
|
+
messageCount: ctx.usage?.messageCount ?? 0,
|
|
1009
|
+
toolCallCount: cost.callCount,
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
/** Get per-tool execution metrics */
|
|
1013
|
+
async getToolMetrics() {
|
|
1014
|
+
this._ensureOpen();
|
|
1015
|
+
const resp = await this._client.getToolMetrics(this.id);
|
|
1016
|
+
const result = {};
|
|
1017
|
+
for (const t of resp.tools) {
|
|
1018
|
+
result[t.toolName] = t;
|
|
1019
|
+
}
|
|
1020
|
+
return result;
|
|
1021
|
+
}
|
|
1022
|
+
/** Get cost breakdown by model and day */
|
|
1023
|
+
async getCostSummary() {
|
|
1024
|
+
this._ensureOpen();
|
|
1025
|
+
return this._client.getCostSummary({ sessionId: this.id });
|
|
1026
|
+
}
|
|
1027
|
+
/** Get per-lane queue statistics */
|
|
1028
|
+
async getQueueStats() {
|
|
1029
|
+
this._ensureOpen();
|
|
1030
|
+
const resp = await this._client.getQueueStats(this.id);
|
|
1031
|
+
const mapLane = (lane) => ({
|
|
1032
|
+
pending: lane?.pending ?? 0,
|
|
1033
|
+
active: lane?.active ?? 0,
|
|
1034
|
+
external: lane?.external ?? 0,
|
|
1035
|
+
completed: lane?.completed ?? 0,
|
|
1036
|
+
failed: lane?.failed ?? 0,
|
|
1037
|
+
});
|
|
1038
|
+
return {
|
|
1039
|
+
control: mapLane(resp.control),
|
|
1040
|
+
query: mapLane(resp.query),
|
|
1041
|
+
execute: mapLane(resp.execute),
|
|
1042
|
+
generate: mapLane(resp.generate),
|
|
1043
|
+
deadLetters: resp.deadLetters ?? 0,
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
/** Update session configuration (cannot change model or workspace) */
|
|
1047
|
+
async configure(options) {
|
|
1048
|
+
this._ensureOpen();
|
|
1049
|
+
const resp = await this._client.getSession(this.id);
|
|
1050
|
+
const existing = resp.session?.config;
|
|
1051
|
+
if (existing) {
|
|
1052
|
+
await this._client.configureSession(this.id, {
|
|
1053
|
+
...existing,
|
|
1054
|
+
autoCompact: options.autoCompact ?? existing.autoCompact,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/** Subscribe to real-time agent events for this session */
|
|
1059
|
+
subscribeEvents(eventTypes) {
|
|
1060
|
+
this._ensureOpen();
|
|
1061
|
+
return this._client.subscribeEvents(this.id, eventTypes);
|
|
1062
|
+
}
|
|
1063
|
+
// --------------------------------------------------------------------------
|
|
1064
|
+
// Lifecycle
|
|
1065
|
+
// --------------------------------------------------------------------------
|
|
1066
|
+
/** Close the session and release server resources */
|
|
1067
|
+
async close() {
|
|
1068
|
+
if (this._closed)
|
|
1069
|
+
return;
|
|
1070
|
+
this._closed = true;
|
|
1071
|
+
try {
|
|
1072
|
+
await this._client.destroySession(this.id);
|
|
1073
|
+
}
|
|
1074
|
+
catch {
|
|
1075
|
+
// Ignore cleanup errors
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
/** Support `await using session = ...` for automatic cleanup */
|
|
1079
|
+
async [Symbol.asyncDispose]() {
|
|
1080
|
+
await this.close();
|
|
1081
|
+
}
|
|
1082
|
+
/** Whether this session has been closed */
|
|
1083
|
+
get closed() {
|
|
1084
|
+
return this._closed;
|
|
1085
|
+
}
|
|
1086
|
+
_ensureOpen() {
|
|
1087
|
+
if (this._closed) {
|
|
1088
|
+
throw new Error(`Session ${this.id} has been closed`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
//# sourceMappingURL=session.js.map
|