@oh-my-pi/pi-agent-core 11.8.2 → 11.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-agent-core",
3
- "version": "11.8.2",
3
+ "version": "11.9.0",
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.2",
28
- "@oh-my-pi/pi-tui": "11.8.2",
29
- "@oh-my-pi/pi-utils": "11.8.2"
27
+ "@oh-my-pi/pi-ai": "11.9.0",
28
+ "@oh-my-pi/pi-tui": "11.9.0",
29
+ "@oh-my-pi/pi-utils": "11.9.0"
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
- private _state: AgentState = {
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
- private listeners = new Set<(e: AgentEvent) => void>();
148
- private abortController?: AbortController;
149
- private convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
150
- private transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<AgentMessage[]>;
151
- private steeringQueue: AgentMessage[] = [];
152
- private followUpQueue: AgentMessage[] = [];
153
- private steeringMode: "all" | "one-at-a-time";
154
- private followUpMode: "all" | "one-at-a-time";
155
- private interruptMode: "immediate" | "wait";
156
- public streamFn: StreamFn;
157
- private _sessionId?: string;
158
- private _thinkingBudgets?: ThinkingBudgets;
159
- private _maxRetryDelayMs?: number;
160
- public getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
161
- private getToolContext?: (toolCall?: ToolCallContext) => AgentToolContext | undefined;
162
- private cursorExecHandlers?: CursorExecHandlers;
163
- private cursorOnToolResult?: CursorToolResultHandler;
164
- private runningPrompt?: Promise<void>;
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
- private _cursorToolResultBuffer: CursorToolResultEntry[] = [];
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._state = { ...this._state, ...opts.initialState };
173
- this.convertToLlm = opts.convertToLlm || defaultConvertToLlm;
174
- this.transformContext = opts.transformContext;
175
- this.steeringMode = opts.steeringMode || "one-at-a-time";
176
- this.followUpMode = opts.followUpMode || "one-at-a-time";
177
- this.interruptMode = opts.interruptMode || "immediate";
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._sessionId = opts.sessionId;
180
- this._thinkingBudgets = opts.thinkingBudgets;
181
- this._maxRetryDelayMs = opts.maxRetryDelayMs;
180
+ this.#sessionId = opts.sessionId;
181
+ this.#thinkingBudgets = opts.thinkingBudgets;
182
+ this.#maxRetryDelayMs = opts.maxRetryDelayMs;
182
183
  this.getApiKey = opts.getApiKey;
183
- this.getToolContext = opts.getToolContext;
184
- this.cursorExecHandlers = opts.cursorExecHandlers;
185
- this.cursorOnToolResult = opts.cursorOnToolResult;
186
- this.kimiApiFormat = opts.kimiApiFormat;
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._sessionId;
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._sessionId = value;
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._thinkingBudgets;
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._thinkingBudgets = value;
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._maxRetryDelayMs;
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._maxRetryDelayMs = value;
231
+ this.#maxRetryDelayMs = value;
231
232
  }
232
233
 
233
234
  get state(): AgentState {
234
- return this._state;
235
+ return this.#state;
235
236
  }
236
237
 
237
238
  subscribe(fn: (e: AgentEvent) => void): () => void {
238
- this.listeners.add(fn);
239
- return () => this.listeners.delete(fn);
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._state.streamMessage = event.message;
247
+ this.#state.streamMessage = event.message;
247
248
  break;
248
249
  case "message_end":
249
- this._state.streamMessage = null;
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._state.pendingToolCalls);
254
+ const pending = new Set(this.#state.pendingToolCalls);
254
255
  pending.add(event.toolCallId);
255
- this._state.pendingToolCalls = pending;
256
+ this.#state.pendingToolCalls = pending;
256
257
  break;
257
258
  }
258
259
  case "tool_execution_end": {
259
- const pending = new Set(this._state.pendingToolCalls);
260
+ const pending = new Set(this.#state.pendingToolCalls);
260
261
  pending.delete(event.toolCallId);
261
- this._state.pendingToolCalls = pending;
262
+ this.#state.pendingToolCalls = pending;
262
263
  break;
263
264
  }
264
265
  }
265
266
 
266
- this.emit(event);
267
+ this.#emit(event);
267
268
  }
268
269
 
269
270
  // State mutators
270
271
  setSystemPrompt(v: string) {
271
- this._state.systemPrompt = v;
272
+ this.#state.systemPrompt = v;
272
273
  }
273
274
 
274
275
  setModel(m: Model) {
275
- this._state.model = m;
276
+ this.#state.model = m;
276
277
  }
277
278
 
278
279
  setThinkingLevel(l: ThinkingLevel) {
279
- this._state.thinkingLevel = l;
280
+ this.#state.thinkingLevel = l;
280
281
  }
281
282
 
282
283
  setSteeringMode(mode: "all" | "one-at-a-time") {
283
- this.steeringMode = mode;
284
+ this.#steeringMode = mode;
284
285
  }
285
286
 
286
287
  getSteeringMode(): "all" | "one-at-a-time" {
287
- return this.steeringMode;
288
+ return this.#steeringMode;
288
289
  }
289
290
 
290
291
  setFollowUpMode(mode: "all" | "one-at-a-time") {
291
- this.followUpMode = mode;
292
+ this.#followUpMode = mode;
292
293
  }
293
294
 
294
295
  getFollowUpMode(): "all" | "one-at-a-time" {
295
- return this.followUpMode;
296
+ return this.#followUpMode;
296
297
  }
297
298
 
298
299
  setInterruptMode(mode: "immediate" | "wait") {
299
- this.interruptMode = mode;
300
+ this.#interruptMode = mode;
300
301
  }
301
302
 
302
303
  getInterruptMode(): "immediate" | "wait" {
303
- return this.interruptMode;
304
+ return this.#interruptMode;
304
305
  }
305
306
 
306
307
  setTools(t: AgentTool<any>[]) {
307
- this._state.tools = t;
308
+ this.#state.tools = t;
308
309
  }
309
310
 
310
311
  replaceMessages(ms: AgentMessage[]) {
311
- this._state.messages = ms.slice();
312
+ this.#state.messages = ms.slice();
312
313
  }
313
314
 
314
315
  appendMessage(m: AgentMessage) {
315
- this._state.messages = [...this._state.messages, m];
316
+ this.#state.messages = [...this.#state.messages, m];
316
317
  }
317
318
 
318
319
  popMessage(): AgentMessage | undefined {
319
- const messages = this._state.messages.slice(0, -1);
320
- const removed = this._state.messages.at(-1);
321
- this._state.messages = messages;
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._state.streamMessage === removed) {
324
- this._state.streamMessage = null;
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.steeringQueue.push(m);
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.followUpQueue.push(m);
344
+ this.#followUpQueue.push(m);
344
345
  }
345
346
 
346
347
  clearSteeringQueue() {
347
- this.steeringQueue = [];
348
+ this.#steeringQueue = [];
348
349
  }
349
350
 
350
351
  clearFollowUpQueue() {
351
- this.followUpQueue = [];
352
+ this.#followUpQueue = [];
352
353
  }
353
354
 
354
355
  clearAllQueues() {
355
- this.steeringQueue = [];
356
- this.followUpQueue = [];
356
+ this.#steeringQueue = [];
357
+ this.#followUpQueue = [];
357
358
  }
358
359
 
359
360
  hasQueuedMessages(): boolean {
360
- return this.steeringQueue.length > 0 || this.followUpQueue.length > 0;
361
+ return this.#steeringQueue.length > 0 || this.#followUpQueue.length > 0;
361
362
  }
362
363
 
363
- private dequeueSteeringMessages(): AgentMessage[] {
364
- if (this.steeringMode === "one-at-a-time") {
365
- if (this.steeringQueue.length > 0) {
366
- const first = this.steeringQueue[0];
367
- this.steeringQueue = this.steeringQueue.slice(1);
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.steeringQueue.slice();
373
- this.steeringQueue = [];
373
+ const steering = this.#steeringQueue.slice();
374
+ this.#steeringQueue = [];
374
375
  return steering;
375
376
  }
376
377
 
377
- private dequeueFollowUpMessages(): AgentMessage[] {
378
- if (this.followUpMode === "one-at-a-time") {
379
- if (this.followUpQueue.length > 0) {
380
- const first = this.followUpQueue[0];
381
- this.followUpQueue = this.followUpQueue.slice(1);
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.followUpQueue.slice();
387
- this.followUpQueue = [];
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.steeringQueue.pop();
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.followUpQueue.pop();
405
+ return this.#followUpQueue.pop();
405
406
  }
406
407
 
407
408
  clearMessages() {
408
- this._state.messages = [];
409
+ this.#state.messages = [];
409
410
  }
410
411
 
411
412
  abort() {
412
- this.abortController?.abort();
413
+ this.#abortController?.abort();
413
414
  }
414
415
 
415
416
  waitForIdle(): Promise<void> {
416
- return this.runningPrompt ?? Promise.resolve();
417
+ return this.#runningPrompt ?? Promise.resolve();
417
418
  }
418
419
 
419
420
  reset() {
420
- this._state.messages = [];
421
- this._state.isStreaming = false;
422
- this._state.streamMessage = null;
423
- this._state.pendingToolCalls = new Set<string>();
424
- this._state.error = undefined;
425
- this.steeringQueue = [];
426
- this.followUpQueue = [];
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._state.isStreaming) {
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._state.model;
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._runLoop(msgs, promptOptions);
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._state.isStreaming) {
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._state.messages;
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.dequeueSteeringMessages();
493
+ const queuedSteering = this.#dequeueSteeringMessages();
493
494
  if (queuedSteering.length > 0) {
494
- await this._runLoop(queuedSteering, { skipInitialSteeringPoll: true });
495
+ await this.#runLoop(queuedSteering, { skipInitialSteeringPoll: true });
495
496
  return;
496
497
  }
497
498
 
498
- const queuedFollowUp = this.dequeueFollowUpMessages();
499
+ const queuedFollowUp = this.#dequeueFollowUpMessages();
499
500
  if (queuedFollowUp.length > 0) {
500
- await this._runLoop(queuedFollowUp);
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._runLoop(undefined);
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
- private async _runLoop(
516
- messages?: AgentMessage[],
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.runningPrompt = new Promise<void>(resolve => {
525
- this.resolveRunningPrompt = resolve;
522
+ this.#runningPrompt = new Promise<void>(resolve => {
523
+ this.#resolveRunningPrompt = resolve;
526
524
  });
527
525
 
528
- this.abortController = new AbortController();
529
- this._state.isStreaming = true;
530
- this._state.streamMessage = null;
531
- this._state.error = undefined;
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._cursorToolResultBuffer = [];
532
+ this.#cursorToolResultBuffer = [];
535
533
 
536
- const reasoning = this._state.thinkingLevel === "off" ? undefined : this._state.thinkingLevel;
534
+ const reasoning = this.#state.thinkingLevel === "off" ? undefined : this.#state.thinkingLevel;
537
535
 
538
536
  const context: AgentContext = {
539
- systemPrompt: this._state.systemPrompt,
540
- messages: this._state.messages.slice(),
541
- tools: this._state.tools,
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.cursorExecHandlers || this.cursorOnToolResult
543
+ this.#cursorExecHandlers || this.#cursorOnToolResult
546
544
  ? async (message: ToolResultMessage) => {
547
545
  let finalMessage = message;
548
- if (this.cursorOnToolResult) {
546
+ if (this.#cursorOnToolResult) {
549
547
  try {
550
- const updated = await this.cursorOnToolResult(message);
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._getAssistantTextLength(this._state.streamMessage);
561
- this._cursorToolResultBuffer.push({ toolResult: finalMessage, textLengthAtCall: textLength });
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.interruptMode,
570
- sessionId: this._sessionId,
571
- thinkingBudgets: this._thinkingBudgets,
572
- maxRetryDelayMs: this._maxRetryDelayMs,
573
- kimiApiFormat: this.kimiApiFormat,
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.convertToLlm,
576
- transformContext: this.transformContext,
573
+ convertToLlm: this.#convertToLlm,
574
+ transformContext: this.#transformContext,
577
575
  getApiKey: this.getApiKey,
578
- getToolContext: this.getToolContext,
579
- cursorExecHandlers: this.cursorExecHandlers,
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.dequeueSteeringMessages();
584
+ return this.#dequeueSteeringMessages();
587
585
  },
588
- getFollowUpMessages: async () => this.dequeueFollowUpMessages(),
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.abortController.signal, this.streamFn)
596
- : agentLoopContinue(context, config, this.abortController.signal, this.streamFn);
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._state.streamMessage = event.message;
601
+ this.#state.streamMessage = event.message;
604
602
  break;
605
603
 
606
604
  case "message_update":
607
605
  partial = event.message;
608
- this._state.streamMessage = event.message;
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._cursorToolResultBuffer.length > 0) {
616
- this._emitCursorSplitAssistantMessage(event.message as AssistantMessage);
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._state.streamMessage = null;
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._state.pendingToolCalls);
622
+ const s = new Set(this.#state.pendingToolCalls);
625
623
  s.add(event.toolCallId);
626
- this._state.pendingToolCalls = s;
624
+ this.#state.pendingToolCalls = s;
627
625
  break;
628
626
  }
629
627
 
630
628
  case "tool_execution_end": {
631
- const s = new Set(this._state.pendingToolCalls);
629
+ const s = new Set(this.#state.pendingToolCalls);
632
630
  s.delete(event.toolCallId);
633
- this._state.pendingToolCalls = s;
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._state.error = (event.message as any).errorMessage;
637
+ this.#state.error = (event.message as any).errorMessage;
640
638
  }
641
639
  break;
642
640
 
643
641
  case "agent_end":
644
- this._state.isStreaming = false;
645
- this._state.streamMessage = null;
642
+ this.#state.isStreaming = false;
643
+ this.#state.streamMessage = null;
646
644
  break;
647
645
  }
648
646
 
649
647
  // Emit to listeners
650
- this.emit(event);
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.abortController?.signal.aborted) {
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.abortController?.signal.aborted ? "aborted" : "error",
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._state.error = err?.message || String(err);
691
- this.emit({ type: "agent_end", messages: [errorMsg] });
688
+ this.#state.error = err?.message || String(err);
689
+ this.#emit({ type: "agent_end", messages: [errorMsg] });
692
690
  } finally {
693
- this._state.isStreaming = false;
694
- this._state.streamMessage = null;
695
- this._state.pendingToolCalls = new Set<string>();
696
- this.abortController = undefined;
697
- this.resolveRunningPrompt?.();
698
- this.runningPrompt = undefined;
699
- this.resolveRunningPrompt = undefined;
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
- private emit(e: AgentEvent) {
704
- for (const listener of this.listeners) {
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
- private _getAssistantTextLength(message: AgentMessage | null): number {
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
- private _emitCursorSplitAssistantMessage(assistantMessage: AssistantMessage): void {
730
- const buffer = this._cursorToolResultBuffer;
731
- this._cursorToolResultBuffer = [];
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._state.streamMessage = null;
733
+ this.#state.streamMessage = null;
736
734
  this.appendMessage(assistantMessage);
737
- this.emit({ type: "message_end", message: assistantMessage });
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._state.streamMessage = null;
754
+ this.#state.streamMessage = null;
757
755
  this.appendMessage(assistantMessage);
758
- this.emit({ type: "message_end", message: assistantMessage });
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.emit({ type: "message_start", message: toolResult });
760
+ this.#emit({ type: "message_start", message: toolResult });
763
761
  this.appendMessage(toolResult);
764
- this.emit({ type: "message_end", message: toolResult });
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._state.streamMessage = null;
784
+ this.#state.streamMessage = null;
787
785
  this.appendMessage(preambleMessage);
788
- this.emit({ type: "message_end", message: preambleMessage });
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.emit({ type: "message_start", message: toolResult });
790
+ this.#emit({ type: "message_start", message: toolResult });
793
791
  this.appendMessage(toolResult);
794
- this.emit({ type: "message_end", message: toolResult });
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.emit({ type: "message_start", message: continuationMessage });
813
+ this.#emit({ type: "message_start", message: continuationMessage });
816
814
  this.appendMessage(continuationMessage);
817
- this.emit({ type: "message_end", message: continuationMessage });
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>(response.body!, options.signal)) {
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
- ) => ReturnType<typeof streamSimple> | Promise<ReturnType<typeof streamSimple>>;
18
+ ) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
18
19
 
19
20
  /**
20
21
  * Configuration for the agent loop.