@oh-my-pi/pi-agent-core 14.8.1 → 14.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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.9.0] - 2026-05-10
6
+ ### Added
7
+
8
+ - Added `Agent#metadata` field forwarded to every API request; callers can set arbitrary provider metadata (e.g. `metadata.user_id`) once and have it applied to all subsequent stream calls without modifying per-call options
9
+ - Added `Agent#setMetadataResolver(fn)` for installing a function that resolves request metadata at call time. The `metadata` getter dispatches through the resolver on every read (including the snapshot taken per `prompt()`), so callers reflect mutable external state (e.g. live OAuth account UUID after a token refresh) without manual re-syncs. Plain `agent.metadata = …` continues to set a static value and clears any installed resolver.
10
+
11
+ ### Added
12
+
13
+ - Added an `onSseEvent` agent option and loop config forwarding path for raw provider SSE diagnostics.
14
+
5
15
  ## [14.7.6] - 2026-05-07
6
16
 
7
17
  ### Added
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-agent-core",
4
- "version": "14.8.1",
4
+ "version": "14.9.0",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -35,9 +35,9 @@
35
35
  "fmt": "biome format --write ."
36
36
  },
37
37
  "dependencies": {
38
- "@oh-my-pi/pi-ai": "14.8.1",
39
- "@oh-my-pi/pi-natives": "14.8.1",
40
- "@oh-my-pi/pi-utils": "14.8.1"
38
+ "@oh-my-pi/pi-ai": "14.9.0",
39
+ "@oh-my-pi/pi-natives": "14.9.0",
40
+ "@oh-my-pi/pi-utils": "14.9.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@sinclair/typebox": "^0.34.49",
package/src/agent-loop.ts CHANGED
@@ -385,15 +385,22 @@ async function streamAssistantResponse(
385
385
 
386
386
  const streamFunction = streamFn || streamSimple;
387
387
 
388
- // Resolve API key (important for expiring tokens)
388
+ // Resolve API key (important for expiring tokens) — do this before resolving
389
+ // metadata so that the session-sticky credential recorded by getApiKey is
390
+ // visible to metadataResolver (e.g. for the correct account_uuid in metadata.user_id).
389
391
  const resolvedApiKey =
390
392
  (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
391
393
 
394
+ // Re-resolve metadata after credential selection so the per-request value
395
+ // reflects the credential actually used, not the snapshot from AgentLoopConfig construction.
396
+ const resolvedMetadata = config.metadataResolver ? config.metadataResolver(config.model.provider) : config.metadata;
397
+
392
398
  const dynamicToolChoice = config.getToolChoice?.();
393
399
  const dynamicReasoning = config.getReasoning?.();
394
400
  const response = await streamFunction(config.model, llmContext, {
395
401
  ...config,
396
402
  apiKey: resolvedApiKey,
403
+ metadata: resolvedMetadata,
397
404
  toolChoice: dynamicToolChoice ?? config.toolChoice,
398
405
  reasoning: dynamicReasoning ?? config.reasoning,
399
406
  signal,
package/src/agent.ts CHANGED
@@ -135,6 +135,10 @@ export interface AgentOptions {
135
135
  * Inspect provider response metadata after headers arrive and before streaming body consumption.
136
136
  */
137
137
  onResponse?: SimpleStreamOptions["onResponse"];
138
+ /**
139
+ * Inspect raw Server-Sent Events from HTTP streaming providers.
140
+ */
141
+ onSseEvent?: SimpleStreamOptions["onSseEvent"];
138
142
  /**
139
143
  * Inspect assistant streaming events before they are emitted to subscribers.
140
144
  * Use this when abort decisions must happen before buffered events continue flowing.
@@ -233,6 +237,8 @@ export class Agent {
233
237
  #followUpMode: "all" | "one-at-a-time";
234
238
  #interruptMode: "immediate" | "wait";
235
239
  #sessionId?: string;
240
+ #metadata?: Record<string, unknown>;
241
+ #metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
236
242
  #providerSessionState?: Map<string, ProviderSessionState>;
237
243
  #thinkingBudgets?: ThinkingBudgets;
238
244
  #temperature?: number;
@@ -256,6 +262,7 @@ export class Agent {
256
262
  #getToolChoice?: () => ToolChoice | undefined;
257
263
  #onPayload?: SimpleStreamOptions["onPayload"];
258
264
  #onResponse?: SimpleStreamOptions["onResponse"];
265
+ #onSseEvent?: SimpleStreamOptions["onSseEvent"];
259
266
  #onAssistantMessageEvent?: (message: AssistantMessage, event: AssistantMessageEvent) => void;
260
267
 
261
268
  /** Buffered Cursor tool results with text length at time of call (for correct ordering) */
@@ -287,6 +294,7 @@ export class Agent {
287
294
  this.getApiKey = opts.getApiKey;
288
295
  this.#onPayload = opts.onPayload;
289
296
  this.#onResponse = opts.onResponse;
297
+ this.#onSseEvent = opts.onSseEvent;
290
298
  this.#getToolContext = opts.getToolContext;
291
299
  this.#cursorExecHandlers = opts.cursorExecHandlers;
292
300
  this.#cursorOnToolResult = opts.cursorOnToolResult;
@@ -313,6 +321,48 @@ export class Agent {
313
321
  this.#sessionId = value;
314
322
  }
315
323
 
324
+ /**
325
+ * Static metadata forwarded to every API request when no resolver is installed
326
+ * (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
327
+ * clears any installed resolver.
328
+ *
329
+ * For live/provider-aware metadata (e.g. Anthropic OAuth `account_uuid` that
330
+ * must reflect the credential selected per-request), use
331
+ * {@link setMetadataResolver} and read via {@link metadataForProvider}.
332
+ */
333
+ get metadata(): Record<string, unknown> | undefined {
334
+ return this.#metadata;
335
+ }
336
+
337
+ set metadata(value: Record<string, unknown> | undefined) {
338
+ this.#metadata = value;
339
+ this.#metadataResolver = undefined;
340
+ }
341
+
342
+ /**
343
+ * Resolve request metadata for the given provider at call time. When a
344
+ * resolver is installed via {@link setMetadataResolver}, it is invoked with
345
+ * the provider string so the result can be scoped (e.g. `account_uuid` is
346
+ * only included for `"anthropic"` requests). Falls back to the static
347
+ * {@link metadata} value when no resolver is set.
348
+ */
349
+ metadataForProvider(provider: string): Record<string, unknown> | undefined {
350
+ if (this.#metadataResolver) return this.#metadataResolver(provider);
351
+ return this.#metadata;
352
+ }
353
+
354
+ /**
355
+ * Install a function that resolves request metadata at call time. The
356
+ * resolver receives the target provider string and can gate provider-specific
357
+ * fields (e.g. `account_uuid` only for `"anthropic"`). Invoked per LLM
358
+ * request by `agent-loop` after `getApiKey` selects the session-sticky
359
+ * credential. Pass `undefined` to clear and revert to the static
360
+ * {@link metadata} value.
361
+ */
362
+ setMetadataResolver(resolver: ((provider: string) => Record<string, unknown> | undefined) | undefined): void {
363
+ this.#metadataResolver = resolver;
364
+ }
365
+
316
366
  /**
317
367
  * Get provider-scoped mutable session state store.
318
368
  */
@@ -435,6 +485,14 @@ export class Agent {
435
485
  return () => this.#listeners.delete(fn);
436
486
  }
437
487
 
488
+ setProviderResponseInterceptor(fn: SimpleStreamOptions["onResponse"] | undefined): void {
489
+ this.#onResponse = fn;
490
+ }
491
+
492
+ setRawSseEventInterceptor(fn: SimpleStreamOptions["onSseEvent"] | undefined): void {
493
+ this.#onSseEvent = fn;
494
+ }
495
+
438
496
  setAssistantMessageEventInterceptor(
439
497
  fn: ((message: AssistantMessage, event: AssistantMessageEvent) => void) | undefined,
440
498
  ): void {
@@ -777,6 +835,8 @@ export class Agent {
777
835
  hideThinkingSummary: this.#hideThinkingSummary,
778
836
  interruptMode: this.#interruptMode,
779
837
  sessionId: this.#sessionId,
838
+ metadata: this.#metadataResolver ? undefined : this.#metadata,
839
+ metadataResolver: this.#metadataResolver,
780
840
  providerSessionState: this.#providerSessionState,
781
841
  thinkingBudgets: this.#thinkingBudgets,
782
842
  maxRetryDelayMs: this.#maxRetryDelayMs,
@@ -786,6 +846,7 @@ export class Agent {
786
846
  transformContext: this.#transformContext,
787
847
  onPayload: this.#onPayload,
788
848
  onResponse: this.#onResponse,
849
+ onSseEvent: this.#onSseEvent,
789
850
  getApiKey: this.getApiKey,
790
851
  getToolContext: this.#getToolContext,
791
852
  syncContextBeforeModelCall: async context => {
package/src/types.ts CHANGED
@@ -39,6 +39,16 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
39
39
  */
40
40
  sessionId?: string;
41
41
 
42
+ /**
43
+ * Optional resolver called per LLM request to produce request metadata.
44
+ * When set, the agent loop evaluates it **after** `getApiKey` resolves the
45
+ * session-sticky credential, ensuring the metadata's `account_uuid` reflects
46
+ * the credential actually used for the request (not the credential that was
47
+ * current when `AgentLoopConfig` was first constructed). Overrides the static
48
+ * `metadata` field when present.
49
+ */
50
+ metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
51
+
42
52
  /**
43
53
  * Converts AgentMessage[] to LLM-compatible Message[] before each LLM call.
44
54
  *