@oh-my-pi/pi-agent-core 11.8.2 → 11.8.3
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/package.json +4 -4
- package/src/agent.ts +185 -187
- package/src/proxy.ts +4 -7
- package/src/types.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-agent-core",
|
|
3
|
-
"version": "11.8.
|
|
3
|
+
"version": "11.8.3",
|
|
4
4
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"test": "bun test"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@oh-my-pi/pi-ai": "11.8.
|
|
28
|
-
"@oh-my-pi/pi-tui": "11.8.
|
|
29
|
-
"@oh-my-pi/pi-utils": "11.8.
|
|
27
|
+
"@oh-my-pi/pi-ai": "11.8.3",
|
|
28
|
+
"@oh-my-pi/pi-tui": "11.8.3",
|
|
29
|
+
"@oh-my-pi/pi-utils": "11.8.3"
|
|
30
30
|
},
|
|
31
31
|
"keywords": [
|
|
32
32
|
"ai",
|
package/src/agent.ts
CHANGED
|
@@ -132,7 +132,7 @@ interface CursorToolResultEntry {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export class Agent {
|
|
135
|
-
|
|
135
|
+
#state: AgentState = {
|
|
136
136
|
systemPrompt: "",
|
|
137
137
|
model: getModel("google", "gemini-2.5-flash-lite-preview-06-17"),
|
|
138
138
|
thinkingLevel: "off",
|
|
@@ -144,53 +144,54 @@ export class Agent {
|
|
|
144
144
|
error: undefined,
|
|
145
145
|
};
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
private resolveRunningPrompt?: () => void;
|
|
166
|
-
private kimiApiFormat?: "openai" | "anthropic";
|
|
147
|
+
#listeners = new Set<(e: AgentEvent) => void>();
|
|
148
|
+
#abortController?: AbortController;
|
|
149
|
+
#convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
150
|
+
#transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
|
|
151
|
+
#steeringQueue: AgentMessage[] = [];
|
|
152
|
+
#followUpQueue: AgentMessage[] = [];
|
|
153
|
+
#steeringMode: "all" | "one-at-a-time";
|
|
154
|
+
#followUpMode: "all" | "one-at-a-time";
|
|
155
|
+
#interruptMode: "immediate" | "wait";
|
|
156
|
+
#sessionId?: string;
|
|
157
|
+
#thinkingBudgets?: ThinkingBudgets;
|
|
158
|
+
#maxRetryDelayMs?: number;
|
|
159
|
+
#getToolContext?: (toolCall?: ToolCallContext) => AgentToolContext | undefined;
|
|
160
|
+
#cursorExecHandlers?: CursorExecHandlers;
|
|
161
|
+
#cursorOnToolResult?: CursorToolResultHandler;
|
|
162
|
+
#runningPrompt?: Promise<void>;
|
|
163
|
+
#resolveRunningPrompt?: () => void;
|
|
164
|
+
#kimiApiFormat?: "openai" | "anthropic";
|
|
167
165
|
|
|
168
166
|
/** Buffered Cursor tool results with text length at time of call (for correct ordering) */
|
|
169
|
-
|
|
167
|
+
#cursorToolResultBuffer: CursorToolResultEntry[] = [];
|
|
168
|
+
|
|
169
|
+
streamFn: StreamFn;
|
|
170
|
+
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
|
170
171
|
|
|
171
172
|
constructor(opts: AgentOptions = {}) {
|
|
172
|
-
this
|
|
173
|
-
this
|
|
174
|
-
this
|
|
175
|
-
this
|
|
176
|
-
this
|
|
177
|
-
this
|
|
173
|
+
this.#state = { ...this.#state, ...opts.initialState };
|
|
174
|
+
this.#convertToLlm = opts.convertToLlm || defaultConvertToLlm;
|
|
175
|
+
this.#transformContext = opts.transformContext;
|
|
176
|
+
this.#steeringMode = opts.steeringMode || "one-at-a-time";
|
|
177
|
+
this.#followUpMode = opts.followUpMode || "one-at-a-time";
|
|
178
|
+
this.#interruptMode = opts.interruptMode || "immediate";
|
|
178
179
|
this.streamFn = opts.streamFn || streamSimple;
|
|
179
|
-
this
|
|
180
|
-
this
|
|
181
|
-
this
|
|
180
|
+
this.#sessionId = opts.sessionId;
|
|
181
|
+
this.#thinkingBudgets = opts.thinkingBudgets;
|
|
182
|
+
this.#maxRetryDelayMs = opts.maxRetryDelayMs;
|
|
182
183
|
this.getApiKey = opts.getApiKey;
|
|
183
|
-
this
|
|
184
|
-
this
|
|
185
|
-
this
|
|
186
|
-
this
|
|
184
|
+
this.#getToolContext = opts.getToolContext;
|
|
185
|
+
this.#cursorExecHandlers = opts.cursorExecHandlers;
|
|
186
|
+
this.#cursorOnToolResult = opts.cursorOnToolResult;
|
|
187
|
+
this.#kimiApiFormat = opts.kimiApiFormat;
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
/**
|
|
190
191
|
* Get the current session ID used for provider caching.
|
|
191
192
|
*/
|
|
192
193
|
get sessionId(): string | undefined {
|
|
193
|
-
return this
|
|
194
|
+
return this.#sessionId;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/**
|
|
@@ -198,28 +199,28 @@ export class Agent {
|
|
|
198
199
|
* Call this when switching sessions (new session, branch, resume).
|
|
199
200
|
*/
|
|
200
201
|
set sessionId(value: string | undefined) {
|
|
201
|
-
this
|
|
202
|
+
this.#sessionId = value;
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
/**
|
|
205
206
|
* Get the current thinking budgets.
|
|
206
207
|
*/
|
|
207
208
|
get thinkingBudgets(): ThinkingBudgets | undefined {
|
|
208
|
-
return this
|
|
209
|
+
return this.#thinkingBudgets;
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
/**
|
|
212
213
|
* Set custom thinking budgets for token-based providers.
|
|
213
214
|
*/
|
|
214
215
|
set thinkingBudgets(value: ThinkingBudgets | undefined) {
|
|
215
|
-
this
|
|
216
|
+
this.#thinkingBudgets = value;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
/**
|
|
219
220
|
* Get the current max retry delay in milliseconds.
|
|
220
221
|
*/
|
|
221
222
|
get maxRetryDelayMs(): number | undefined {
|
|
222
|
-
return this
|
|
223
|
+
return this.#maxRetryDelayMs;
|
|
223
224
|
}
|
|
224
225
|
|
|
225
226
|
/**
|
|
@@ -227,101 +228,101 @@ export class Agent {
|
|
|
227
228
|
* Set to 0 to disable the cap.
|
|
228
229
|
*/
|
|
229
230
|
set maxRetryDelayMs(value: number | undefined) {
|
|
230
|
-
this
|
|
231
|
+
this.#maxRetryDelayMs = value;
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
get state(): AgentState {
|
|
234
|
-
return this
|
|
235
|
+
return this.#state;
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
subscribe(fn: (e: AgentEvent) => void): () => void {
|
|
238
|
-
this
|
|
239
|
-
return () => this
|
|
239
|
+
this.#listeners.add(fn);
|
|
240
|
+
return () => this.#listeners.delete(fn);
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
emitExternalEvent(event: AgentEvent) {
|
|
243
244
|
switch (event.type) {
|
|
244
245
|
case "message_start":
|
|
245
246
|
case "message_update":
|
|
246
|
-
this.
|
|
247
|
+
this.#state.streamMessage = event.message;
|
|
247
248
|
break;
|
|
248
249
|
case "message_end":
|
|
249
|
-
this.
|
|
250
|
+
this.#state.streamMessage = null;
|
|
250
251
|
this.appendMessage(event.message);
|
|
251
252
|
break;
|
|
252
253
|
case "tool_execution_start": {
|
|
253
|
-
const pending = new Set(this.
|
|
254
|
+
const pending = new Set(this.#state.pendingToolCalls);
|
|
254
255
|
pending.add(event.toolCallId);
|
|
255
|
-
this.
|
|
256
|
+
this.#state.pendingToolCalls = pending;
|
|
256
257
|
break;
|
|
257
258
|
}
|
|
258
259
|
case "tool_execution_end": {
|
|
259
|
-
const pending = new Set(this.
|
|
260
|
+
const pending = new Set(this.#state.pendingToolCalls);
|
|
260
261
|
pending.delete(event.toolCallId);
|
|
261
|
-
this.
|
|
262
|
+
this.#state.pendingToolCalls = pending;
|
|
262
263
|
break;
|
|
263
264
|
}
|
|
264
265
|
}
|
|
265
266
|
|
|
266
|
-
this
|
|
267
|
+
this.#emit(event);
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
// State mutators
|
|
270
271
|
setSystemPrompt(v: string) {
|
|
271
|
-
this.
|
|
272
|
+
this.#state.systemPrompt = v;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
setModel(m: Model) {
|
|
275
|
-
this.
|
|
276
|
+
this.#state.model = m;
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
setThinkingLevel(l: ThinkingLevel) {
|
|
279
|
-
this.
|
|
280
|
+
this.#state.thinkingLevel = l;
|
|
280
281
|
}
|
|
281
282
|
|
|
282
283
|
setSteeringMode(mode: "all" | "one-at-a-time") {
|
|
283
|
-
this
|
|
284
|
+
this.#steeringMode = mode;
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
getSteeringMode(): "all" | "one-at-a-time" {
|
|
287
|
-
return this
|
|
288
|
+
return this.#steeringMode;
|
|
288
289
|
}
|
|
289
290
|
|
|
290
291
|
setFollowUpMode(mode: "all" | "one-at-a-time") {
|
|
291
|
-
this
|
|
292
|
+
this.#followUpMode = mode;
|
|
292
293
|
}
|
|
293
294
|
|
|
294
295
|
getFollowUpMode(): "all" | "one-at-a-time" {
|
|
295
|
-
return this
|
|
296
|
+
return this.#followUpMode;
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
setInterruptMode(mode: "immediate" | "wait") {
|
|
299
|
-
this
|
|
300
|
+
this.#interruptMode = mode;
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
getInterruptMode(): "immediate" | "wait" {
|
|
303
|
-
return this
|
|
304
|
+
return this.#interruptMode;
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
setTools(t: AgentTool<any>[]) {
|
|
307
|
-
this.
|
|
308
|
+
this.#state.tools = t;
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
replaceMessages(ms: AgentMessage[]) {
|
|
311
|
-
this.
|
|
312
|
+
this.#state.messages = ms.slice();
|
|
312
313
|
}
|
|
313
314
|
|
|
314
315
|
appendMessage(m: AgentMessage) {
|
|
315
|
-
this.
|
|
316
|
+
this.#state.messages = [...this.#state.messages, m];
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
popMessage(): AgentMessage | undefined {
|
|
319
|
-
const messages = this.
|
|
320
|
-
const removed = this.
|
|
321
|
-
this.
|
|
320
|
+
const messages = this.#state.messages.slice(0, -1);
|
|
321
|
+
const removed = this.#state.messages.at(-1);
|
|
322
|
+
this.#state.messages = messages;
|
|
322
323
|
|
|
323
|
-
if (removed && this.
|
|
324
|
-
this.
|
|
324
|
+
if (removed && this.#state.streamMessage === removed) {
|
|
325
|
+
this.#state.streamMessage = null;
|
|
325
326
|
}
|
|
326
327
|
|
|
327
328
|
return removed;
|
|
@@ -332,7 +333,7 @@ export class Agent {
|
|
|
332
333
|
* Delivered after current tool execution, skips remaining tools.
|
|
333
334
|
*/
|
|
334
335
|
steer(m: AgentMessage) {
|
|
335
|
-
this
|
|
336
|
+
this.#steeringQueue.push(m);
|
|
336
337
|
}
|
|
337
338
|
|
|
338
339
|
/**
|
|
@@ -340,51 +341,51 @@ export class Agent {
|
|
|
340
341
|
* Delivered only when agent has no more tool calls or steering messages.
|
|
341
342
|
*/
|
|
342
343
|
followUp(m: AgentMessage) {
|
|
343
|
-
this
|
|
344
|
+
this.#followUpQueue.push(m);
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
clearSteeringQueue() {
|
|
347
|
-
this
|
|
348
|
+
this.#steeringQueue = [];
|
|
348
349
|
}
|
|
349
350
|
|
|
350
351
|
clearFollowUpQueue() {
|
|
351
|
-
this
|
|
352
|
+
this.#followUpQueue = [];
|
|
352
353
|
}
|
|
353
354
|
|
|
354
355
|
clearAllQueues() {
|
|
355
|
-
this
|
|
356
|
-
this
|
|
356
|
+
this.#steeringQueue = [];
|
|
357
|
+
this.#followUpQueue = [];
|
|
357
358
|
}
|
|
358
359
|
|
|
359
360
|
hasQueuedMessages(): boolean {
|
|
360
|
-
return this
|
|
361
|
+
return this.#steeringQueue.length > 0 || this.#followUpQueue.length > 0;
|
|
361
362
|
}
|
|
362
363
|
|
|
363
|
-
|
|
364
|
-
if (this
|
|
365
|
-
if (this
|
|
366
|
-
const first = this
|
|
367
|
-
this
|
|
364
|
+
#dequeueSteeringMessages(): AgentMessage[] {
|
|
365
|
+
if (this.#steeringMode === "one-at-a-time") {
|
|
366
|
+
if (this.#steeringQueue.length > 0) {
|
|
367
|
+
const first = this.#steeringQueue[0];
|
|
368
|
+
this.#steeringQueue = this.#steeringQueue.slice(1);
|
|
368
369
|
return [first];
|
|
369
370
|
}
|
|
370
371
|
return [];
|
|
371
372
|
}
|
|
372
|
-
const steering = this
|
|
373
|
-
this
|
|
373
|
+
const steering = this.#steeringQueue.slice();
|
|
374
|
+
this.#steeringQueue = [];
|
|
374
375
|
return steering;
|
|
375
376
|
}
|
|
376
377
|
|
|
377
|
-
|
|
378
|
-
if (this
|
|
379
|
-
if (this
|
|
380
|
-
const first = this
|
|
381
|
-
this
|
|
378
|
+
#dequeueFollowUpMessages(): AgentMessage[] {
|
|
379
|
+
if (this.#followUpMode === "one-at-a-time") {
|
|
380
|
+
if (this.#followUpQueue.length > 0) {
|
|
381
|
+
const first = this.#followUpQueue[0];
|
|
382
|
+
this.#followUpQueue = this.#followUpQueue.slice(1);
|
|
382
383
|
return [first];
|
|
383
384
|
}
|
|
384
385
|
return [];
|
|
385
386
|
}
|
|
386
|
-
const followUp = this
|
|
387
|
-
this
|
|
387
|
+
const followUp = this.#followUpQueue.slice();
|
|
388
|
+
this.#followUpQueue = [];
|
|
388
389
|
return followUp;
|
|
389
390
|
}
|
|
390
391
|
|
|
@@ -393,7 +394,7 @@ export class Agent {
|
|
|
393
394
|
* Used by dequeue keybinding.
|
|
394
395
|
*/
|
|
395
396
|
popLastSteer(): AgentMessage | undefined {
|
|
396
|
-
return this
|
|
397
|
+
return this.#steeringQueue.pop();
|
|
397
398
|
}
|
|
398
399
|
|
|
399
400
|
/**
|
|
@@ -401,29 +402,29 @@ export class Agent {
|
|
|
401
402
|
* Used by dequeue keybinding.
|
|
402
403
|
*/
|
|
403
404
|
popLastFollowUp(): AgentMessage | undefined {
|
|
404
|
-
return this
|
|
405
|
+
return this.#followUpQueue.pop();
|
|
405
406
|
}
|
|
406
407
|
|
|
407
408
|
clearMessages() {
|
|
408
|
-
this.
|
|
409
|
+
this.#state.messages = [];
|
|
409
410
|
}
|
|
410
411
|
|
|
411
412
|
abort() {
|
|
412
|
-
this
|
|
413
|
+
this.#abortController?.abort();
|
|
413
414
|
}
|
|
414
415
|
|
|
415
416
|
waitForIdle(): Promise<void> {
|
|
416
|
-
return this
|
|
417
|
+
return this.#runningPrompt ?? Promise.resolve();
|
|
417
418
|
}
|
|
418
419
|
|
|
419
420
|
reset() {
|
|
420
|
-
this.
|
|
421
|
-
this.
|
|
422
|
-
this.
|
|
423
|
-
this.
|
|
424
|
-
this.
|
|
425
|
-
this
|
|
426
|
-
this
|
|
421
|
+
this.#state.messages = [];
|
|
422
|
+
this.#state.isStreaming = false;
|
|
423
|
+
this.#state.streamMessage = null;
|
|
424
|
+
this.#state.pendingToolCalls = new Set<string>();
|
|
425
|
+
this.#state.error = undefined;
|
|
426
|
+
this.#steeringQueue = [];
|
|
427
|
+
this.#followUpQueue = [];
|
|
427
428
|
}
|
|
428
429
|
|
|
429
430
|
/** Send a prompt with an AgentMessage */
|
|
@@ -434,13 +435,13 @@ export class Agent {
|
|
|
434
435
|
imagesOrOptions?: ImageContent[] | AgentPromptOptions,
|
|
435
436
|
options?: AgentPromptOptions,
|
|
436
437
|
) {
|
|
437
|
-
if (this.
|
|
438
|
+
if (this.#state.isStreaming) {
|
|
438
439
|
throw new Error(
|
|
439
440
|
"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.",
|
|
440
441
|
);
|
|
441
442
|
}
|
|
442
443
|
|
|
443
|
-
const model = this.
|
|
444
|
+
const model = this.#state.model;
|
|
444
445
|
if (!model) throw new Error("No model configured");
|
|
445
446
|
|
|
446
447
|
let msgs: AgentMessage[];
|
|
@@ -473,38 +474,38 @@ export class Agent {
|
|
|
473
474
|
promptOptions = imagesOrOptions as AgentPromptOptions | undefined;
|
|
474
475
|
}
|
|
475
476
|
|
|
476
|
-
await this
|
|
477
|
+
await this.#runLoop(msgs, promptOptions);
|
|
477
478
|
}
|
|
478
479
|
|
|
479
480
|
/**
|
|
480
481
|
* Continue from current context (used for retries and resuming queued messages).
|
|
481
482
|
*/
|
|
482
483
|
async continue() {
|
|
483
|
-
if (this.
|
|
484
|
+
if (this.#state.isStreaming) {
|
|
484
485
|
throw new Error("Agent is already processing. Wait for completion before continuing.");
|
|
485
486
|
}
|
|
486
487
|
|
|
487
|
-
const messages = this.
|
|
488
|
+
const messages = this.#state.messages;
|
|
488
489
|
if (messages.length === 0) {
|
|
489
490
|
throw new Error("No messages to continue from");
|
|
490
491
|
}
|
|
491
492
|
if (messages[messages.length - 1].role === "assistant") {
|
|
492
|
-
const queuedSteering = this
|
|
493
|
+
const queuedSteering = this.#dequeueSteeringMessages();
|
|
493
494
|
if (queuedSteering.length > 0) {
|
|
494
|
-
await this
|
|
495
|
+
await this.#runLoop(queuedSteering, { skipInitialSteeringPoll: true });
|
|
495
496
|
return;
|
|
496
497
|
}
|
|
497
498
|
|
|
498
|
-
const queuedFollowUp = this
|
|
499
|
+
const queuedFollowUp = this.#dequeueFollowUpMessages();
|
|
499
500
|
if (queuedFollowUp.length > 0) {
|
|
500
|
-
await this
|
|
501
|
+
await this.#runLoop(queuedFollowUp);
|
|
501
502
|
return;
|
|
502
503
|
}
|
|
503
504
|
|
|
504
505
|
throw new Error("Cannot continue from message role: assistant");
|
|
505
506
|
}
|
|
506
507
|
|
|
507
|
-
await this
|
|
508
|
+
await this.#runLoop(undefined);
|
|
508
509
|
}
|
|
509
510
|
|
|
510
511
|
/**
|
|
@@ -512,42 +513,39 @@ export class Agent {
|
|
|
512
513
|
* If messages are provided, starts a new conversation turn with those messages.
|
|
513
514
|
* Otherwise, continues from existing context.
|
|
514
515
|
*/
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
options?: AgentPromptOptions & { skipInitialSteeringPoll?: boolean },
|
|
518
|
-
) {
|
|
519
|
-
const model = this._state.model;
|
|
516
|
+
async #runLoop(messages?: AgentMessage[], options?: AgentPromptOptions & { skipInitialSteeringPoll?: boolean }) {
|
|
517
|
+
const model = this.#state.model;
|
|
520
518
|
if (!model) throw new Error("No model configured");
|
|
521
519
|
|
|
522
520
|
let skipInitialSteeringPoll = options?.skipInitialSteeringPoll === true;
|
|
523
521
|
|
|
524
|
-
this
|
|
525
|
-
this
|
|
522
|
+
this.#runningPrompt = new Promise<void>(resolve => {
|
|
523
|
+
this.#resolveRunningPrompt = resolve;
|
|
526
524
|
});
|
|
527
525
|
|
|
528
|
-
this
|
|
529
|
-
this.
|
|
530
|
-
this.
|
|
531
|
-
this.
|
|
526
|
+
this.#abortController = new AbortController();
|
|
527
|
+
this.#state.isStreaming = true;
|
|
528
|
+
this.#state.streamMessage = null;
|
|
529
|
+
this.#state.error = undefined;
|
|
532
530
|
|
|
533
531
|
// Clear Cursor tool result buffer at start of each run
|
|
534
|
-
this
|
|
532
|
+
this.#cursorToolResultBuffer = [];
|
|
535
533
|
|
|
536
|
-
const reasoning = this.
|
|
534
|
+
const reasoning = this.#state.thinkingLevel === "off" ? undefined : this.#state.thinkingLevel;
|
|
537
535
|
|
|
538
536
|
const context: AgentContext = {
|
|
539
|
-
systemPrompt: this.
|
|
540
|
-
messages: this.
|
|
541
|
-
tools: this.
|
|
537
|
+
systemPrompt: this.#state.systemPrompt,
|
|
538
|
+
messages: this.#state.messages.slice(),
|
|
539
|
+
tools: this.#state.tools,
|
|
542
540
|
};
|
|
543
541
|
|
|
544
542
|
const cursorOnToolResult =
|
|
545
|
-
this
|
|
543
|
+
this.#cursorExecHandlers || this.#cursorOnToolResult
|
|
546
544
|
? async (message: ToolResultMessage) => {
|
|
547
545
|
let finalMessage = message;
|
|
548
|
-
if (this
|
|
546
|
+
if (this.#cursorOnToolResult) {
|
|
549
547
|
try {
|
|
550
|
-
const updated = await this
|
|
548
|
+
const updated = await this.#cursorOnToolResult(message);
|
|
551
549
|
if (updated) {
|
|
552
550
|
finalMessage = updated;
|
|
553
551
|
}
|
|
@@ -557,8 +555,8 @@ export class Agent {
|
|
|
557
555
|
// Cursor executes tools server-side during streaming, so the assistant message
|
|
558
556
|
// already incorporates results. We buffer here and emit in correct order
|
|
559
557
|
// when the assistant message ends.
|
|
560
|
-
const textLength = this
|
|
561
|
-
this.
|
|
558
|
+
const textLength = this.#getAssistantTextLength(this.#state.streamMessage);
|
|
559
|
+
this.#cursorToolResultBuffer.push({ toolResult: finalMessage, textLengthAtCall: textLength });
|
|
562
560
|
return finalMessage;
|
|
563
561
|
}
|
|
564
562
|
: undefined;
|
|
@@ -566,88 +564,88 @@ export class Agent {
|
|
|
566
564
|
const config: AgentLoopConfig = {
|
|
567
565
|
model,
|
|
568
566
|
reasoning,
|
|
569
|
-
interruptMode: this
|
|
570
|
-
sessionId: this
|
|
571
|
-
thinkingBudgets: this
|
|
572
|
-
maxRetryDelayMs: this
|
|
573
|
-
kimiApiFormat: this
|
|
567
|
+
interruptMode: this.#interruptMode,
|
|
568
|
+
sessionId: this.#sessionId,
|
|
569
|
+
thinkingBudgets: this.#thinkingBudgets,
|
|
570
|
+
maxRetryDelayMs: this.#maxRetryDelayMs,
|
|
571
|
+
kimiApiFormat: this.#kimiApiFormat,
|
|
574
572
|
toolChoice: options?.toolChoice,
|
|
575
|
-
convertToLlm: this
|
|
576
|
-
transformContext: this
|
|
573
|
+
convertToLlm: this.#convertToLlm,
|
|
574
|
+
transformContext: this.#transformContext,
|
|
577
575
|
getApiKey: this.getApiKey,
|
|
578
|
-
getToolContext: this
|
|
579
|
-
cursorExecHandlers: this
|
|
576
|
+
getToolContext: this.#getToolContext,
|
|
577
|
+
cursorExecHandlers: this.#cursorExecHandlers,
|
|
580
578
|
cursorOnToolResult,
|
|
581
579
|
getSteeringMessages: async () => {
|
|
582
580
|
if (skipInitialSteeringPoll) {
|
|
583
581
|
skipInitialSteeringPoll = false;
|
|
584
582
|
return [];
|
|
585
583
|
}
|
|
586
|
-
return this
|
|
584
|
+
return this.#dequeueSteeringMessages();
|
|
587
585
|
},
|
|
588
|
-
getFollowUpMessages: async () => this
|
|
586
|
+
getFollowUpMessages: async () => this.#dequeueFollowUpMessages(),
|
|
589
587
|
};
|
|
590
588
|
|
|
591
589
|
let partial: AgentMessage | null = null;
|
|
592
590
|
|
|
593
591
|
try {
|
|
594
592
|
const stream = messages
|
|
595
|
-
? agentLoop(messages, context, config, this
|
|
596
|
-
: agentLoopContinue(context, config, this
|
|
593
|
+
? agentLoop(messages, context, config, this.#abortController.signal, this.streamFn)
|
|
594
|
+
: agentLoopContinue(context, config, this.#abortController.signal, this.streamFn);
|
|
597
595
|
|
|
598
596
|
for await (const event of stream) {
|
|
599
597
|
// Update internal state based on events
|
|
600
598
|
switch (event.type) {
|
|
601
599
|
case "message_start":
|
|
602
600
|
partial = event.message;
|
|
603
|
-
this.
|
|
601
|
+
this.#state.streamMessage = event.message;
|
|
604
602
|
break;
|
|
605
603
|
|
|
606
604
|
case "message_update":
|
|
607
605
|
partial = event.message;
|
|
608
|
-
this.
|
|
606
|
+
this.#state.streamMessage = event.message;
|
|
609
607
|
break;
|
|
610
608
|
|
|
611
609
|
case "message_end":
|
|
612
610
|
partial = null;
|
|
613
611
|
// Check if this is an assistant message with buffered Cursor tool results.
|
|
614
612
|
// If so, split the message to emit tool results at the correct position.
|
|
615
|
-
if (event.message.role === "assistant" && this.
|
|
616
|
-
this
|
|
613
|
+
if (event.message.role === "assistant" && this.#cursorToolResultBuffer.length > 0) {
|
|
614
|
+
this.#emitCursorSplitAssistantMessage(event.message as AssistantMessage);
|
|
617
615
|
continue; // Skip default emit - split method handles everything
|
|
618
616
|
}
|
|
619
|
-
this.
|
|
617
|
+
this.#state.streamMessage = null;
|
|
620
618
|
this.appendMessage(event.message);
|
|
621
619
|
break;
|
|
622
620
|
|
|
623
621
|
case "tool_execution_start": {
|
|
624
|
-
const s = new Set(this.
|
|
622
|
+
const s = new Set(this.#state.pendingToolCalls);
|
|
625
623
|
s.add(event.toolCallId);
|
|
626
|
-
this.
|
|
624
|
+
this.#state.pendingToolCalls = s;
|
|
627
625
|
break;
|
|
628
626
|
}
|
|
629
627
|
|
|
630
628
|
case "tool_execution_end": {
|
|
631
|
-
const s = new Set(this.
|
|
629
|
+
const s = new Set(this.#state.pendingToolCalls);
|
|
632
630
|
s.delete(event.toolCallId);
|
|
633
|
-
this.
|
|
631
|
+
this.#state.pendingToolCalls = s;
|
|
634
632
|
break;
|
|
635
633
|
}
|
|
636
634
|
|
|
637
635
|
case "turn_end":
|
|
638
636
|
if (event.message.role === "assistant" && (event.message as any).errorMessage) {
|
|
639
|
-
this.
|
|
637
|
+
this.#state.error = (event.message as any).errorMessage;
|
|
640
638
|
}
|
|
641
639
|
break;
|
|
642
640
|
|
|
643
641
|
case "agent_end":
|
|
644
|
-
this.
|
|
645
|
-
this.
|
|
642
|
+
this.#state.isStreaming = false;
|
|
643
|
+
this.#state.streamMessage = null;
|
|
646
644
|
break;
|
|
647
645
|
}
|
|
648
646
|
|
|
649
647
|
// Emit to listeners
|
|
650
|
-
this
|
|
648
|
+
this.#emit(event);
|
|
651
649
|
}
|
|
652
650
|
|
|
653
651
|
// Handle any remaining partial message
|
|
@@ -661,7 +659,7 @@ export class Agent {
|
|
|
661
659
|
if (!onlyEmpty) {
|
|
662
660
|
this.appendMessage(partial);
|
|
663
661
|
} else {
|
|
664
|
-
if (this
|
|
662
|
+
if (this.#abortController?.signal.aborted) {
|
|
665
663
|
throw new Error("Request was aborted");
|
|
666
664
|
}
|
|
667
665
|
}
|
|
@@ -681,33 +679,33 @@ export class Agent {
|
|
|
681
679
|
totalTokens: 0,
|
|
682
680
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
683
681
|
},
|
|
684
|
-
stopReason: this
|
|
682
|
+
stopReason: this.#abortController?.signal.aborted ? "aborted" : "error",
|
|
685
683
|
errorMessage: err?.message || String(err),
|
|
686
684
|
timestamp: Date.now(),
|
|
687
685
|
} as AgentMessage;
|
|
688
686
|
|
|
689
687
|
this.appendMessage(errorMsg);
|
|
690
|
-
this.
|
|
691
|
-
this
|
|
688
|
+
this.#state.error = err?.message || String(err);
|
|
689
|
+
this.#emit({ type: "agent_end", messages: [errorMsg] });
|
|
692
690
|
} finally {
|
|
693
|
-
this.
|
|
694
|
-
this.
|
|
695
|
-
this.
|
|
696
|
-
this
|
|
697
|
-
this
|
|
698
|
-
this
|
|
699
|
-
this
|
|
691
|
+
this.#state.isStreaming = false;
|
|
692
|
+
this.#state.streamMessage = null;
|
|
693
|
+
this.#state.pendingToolCalls = new Set<string>();
|
|
694
|
+
this.#abortController = undefined;
|
|
695
|
+
this.#resolveRunningPrompt?.();
|
|
696
|
+
this.#runningPrompt = undefined;
|
|
697
|
+
this.#resolveRunningPrompt = undefined;
|
|
700
698
|
}
|
|
701
699
|
}
|
|
702
700
|
|
|
703
|
-
|
|
704
|
-
for (const listener of this
|
|
701
|
+
#emit(e: AgentEvent) {
|
|
702
|
+
for (const listener of this.#listeners) {
|
|
705
703
|
listener(e);
|
|
706
704
|
}
|
|
707
705
|
}
|
|
708
706
|
|
|
709
707
|
/** Calculate total text length from an assistant message's content blocks */
|
|
710
|
-
|
|
708
|
+
#getAssistantTextLength(message: AgentMessage | null): number {
|
|
711
709
|
if (!message || message.role !== "assistant" || !Array.isArray(message.content)) {
|
|
712
710
|
return 0;
|
|
713
711
|
}
|
|
@@ -726,15 +724,15 @@ export class Agent {
|
|
|
726
724
|
*
|
|
727
725
|
* Output order: Assistant(preamble) -> ToolResults -> Assistant(continuation)
|
|
728
726
|
*/
|
|
729
|
-
|
|
730
|
-
const buffer = this
|
|
731
|
-
this
|
|
727
|
+
#emitCursorSplitAssistantMessage(assistantMessage: AssistantMessage): void {
|
|
728
|
+
const buffer = this.#cursorToolResultBuffer;
|
|
729
|
+
this.#cursorToolResultBuffer = [];
|
|
732
730
|
|
|
733
731
|
if (buffer.length === 0) {
|
|
734
732
|
// No tool results, emit normally
|
|
735
|
-
this.
|
|
733
|
+
this.#state.streamMessage = null;
|
|
736
734
|
this.appendMessage(assistantMessage);
|
|
737
|
-
this
|
|
735
|
+
this.#emit({ type: "message_end", message: assistantMessage });
|
|
738
736
|
return;
|
|
739
737
|
}
|
|
740
738
|
|
|
@@ -753,15 +751,15 @@ export class Agent {
|
|
|
753
751
|
// If no text or split point is 0 or at/past end, don't split
|
|
754
752
|
if (fullText.length === 0 || splitPoint <= 0 || splitPoint >= fullText.length) {
|
|
755
753
|
// Emit assistant message first, then tool results (original behavior but with buffered results)
|
|
756
|
-
this.
|
|
754
|
+
this.#state.streamMessage = null;
|
|
757
755
|
this.appendMessage(assistantMessage);
|
|
758
|
-
this
|
|
756
|
+
this.#emit({ type: "message_end", message: assistantMessage });
|
|
759
757
|
|
|
760
758
|
// Emit buffered tool results
|
|
761
759
|
for (const { toolResult } of buffer) {
|
|
762
|
-
this
|
|
760
|
+
this.#emit({ type: "message_start", message: toolResult });
|
|
763
761
|
this.appendMessage(toolResult);
|
|
764
|
-
this
|
|
762
|
+
this.#emit({ type: "message_end", message: toolResult });
|
|
765
763
|
}
|
|
766
764
|
return;
|
|
767
765
|
}
|
|
@@ -783,15 +781,15 @@ export class Agent {
|
|
|
783
781
|
};
|
|
784
782
|
|
|
785
783
|
// Emit preamble
|
|
786
|
-
this.
|
|
784
|
+
this.#state.streamMessage = null;
|
|
787
785
|
this.appendMessage(preambleMessage);
|
|
788
|
-
this
|
|
786
|
+
this.#emit({ type: "message_end", message: preambleMessage });
|
|
789
787
|
|
|
790
788
|
// Emit buffered tool results
|
|
791
789
|
for (const { toolResult } of buffer) {
|
|
792
|
-
this
|
|
790
|
+
this.#emit({ type: "message_start", message: toolResult });
|
|
793
791
|
this.appendMessage(toolResult);
|
|
794
|
-
this
|
|
792
|
+
this.#emit({ type: "message_end", message: toolResult });
|
|
795
793
|
}
|
|
796
794
|
|
|
797
795
|
// Emit continuation message (text after tools) if non-empty
|
|
@@ -812,9 +810,9 @@ export class Agent {
|
|
|
812
810
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
813
811
|
},
|
|
814
812
|
};
|
|
815
|
-
this
|
|
813
|
+
this.#emit({ type: "message_start", message: continuationMessage });
|
|
816
814
|
this.appendMessage(continuationMessage);
|
|
817
|
-
this
|
|
815
|
+
this.#emit({ type: "message_end", message: continuationMessage });
|
|
818
816
|
}
|
|
819
817
|
}
|
|
820
818
|
}
|
package/src/proxy.ts
CHANGED
|
@@ -148,7 +148,10 @@ export function streamProxy(model: Model, context: Context, options: ProxyStream
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
let sawTerminalEvent = false;
|
|
151
|
-
for await (const event of readSseJson<ProxyAssistantMessageEvent>(
|
|
151
|
+
for await (const event of readSseJson<ProxyAssistantMessageEvent>(
|
|
152
|
+
response.body as ReadableStream<Uint8Array>,
|
|
153
|
+
options.signal,
|
|
154
|
+
)) {
|
|
152
155
|
const parsedEvent = processProxyEvent(event, partial);
|
|
153
156
|
if (parsedEvent) {
|
|
154
157
|
if (parsedEvent.type === "done" || parsedEvent.type === "error") {
|
|
@@ -310,11 +313,5 @@ function processProxyEvent(
|
|
|
310
313
|
partial.errorMessage = proxyEvent.errorMessage;
|
|
311
314
|
partial.usage = proxyEvent.usage;
|
|
312
315
|
return { type: "error", reason: proxyEvent.reason, error: partial };
|
|
313
|
-
|
|
314
|
-
default: {
|
|
315
|
-
const _exhaustiveCheck: never = proxyEvent;
|
|
316
|
-
console.warn(`Unhandled proxy event type: ${(proxyEvent as any).type}`);
|
|
317
|
-
return undefined;
|
|
318
|
-
}
|
|
319
316
|
}
|
|
320
317
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AssistantMessageEvent,
|
|
3
|
+
AssistantMessageEventStream,
|
|
3
4
|
ImageContent,
|
|
4
5
|
Message,
|
|
5
6
|
Model,
|
|
@@ -14,7 +15,7 @@ import type { Static, TSchema } from "@sinclair/typebox";
|
|
|
14
15
|
/** Stream function - can return sync or Promise for async config lookup */
|
|
15
16
|
export type StreamFn = (
|
|
16
17
|
...args: Parameters<typeof streamSimple>
|
|
17
|
-
) =>
|
|
18
|
+
) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Configuration for the agent loop.
|