@oh-my-pi/pi-agent-core 15.10.0 → 15.10.2

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,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.10.2] - 2026-06-08
6
+
7
+ ### Fixed
8
+
9
+ - Fixed proxy stream silently returning a zero-token success response when the server disconnects without sending a `done` or `error` terminal SSE event. The stream now throws an error, surfacing the disconnect as an `error` event with `stopReason: "error"` and resolving `finalResultPromise`, instead of defaulting to `stopReason: "stop"` with empty content and leaving `stream.result()` callers hanging indefinitely.
10
+
11
+ ## [15.10.1] - 2026-06-07
12
+
13
+ ### Added
14
+
15
+ - Added optional `promptCacheKey` support to `AgentOptions` and `Agent` via a new `promptCacheKey` property so providers can receive a caller-provided prompt cache key
16
+ - Added optional `ApiKeyResolveContext` parameter to `getApiKey` in `AgentOptions` and `AgentLoopConfig` so key resolvers can receive retry context
17
+
18
+ ### Changed
19
+
20
+ - Enabled streaming API calls to re-resolve credentials through the `getApiKey` callback when retries occur after authentication-related errors
21
+ - `Agent.abort(reason?)` now forwards `reason` to the underlying `AbortController`, and the synthesized aborted assistant message carries that reason on `errorMessage` (string or non-`AbortError` `Error` message) instead of always defaulting to `"Request was aborted"`. Bare `abort()` is unchanged.
22
+
23
+ ### Fixed
24
+
25
+ - Fixed handling of short-lived API keys so that expired tokens are retried with a refreshed value during 401/usage-limit failures
26
+ - Ensured fallback API key resolution uses the initially configured static `apiKey` when `getApiKey` is present
27
+ - Wrapped oneshot LLM completions (`instrumentedCompleteSimple`: handoff, compaction/branch summaries) in an `EventLoopKeepalive`. These run outside the agent `#runLoop`, so without the keepalive Bun's event loop stopped servicing timers while parked on the completion promise — freezing host spinners (e.g. the `/handoff` loader) until an unrelated terminal resize poked the loop into rendering again.
28
+
5
29
  ## [15.9.5] - 2026-06-05
6
30
 
7
31
  ### Fixed
@@ -53,3 +53,10 @@ export declare function agentLoopContinueDetailed(context: AgentContext, config:
53
53
  };
54
54
  export declare const INTENT_FIELD = "_i";
55
55
  export declare function normalizeTools(tools: AgentContext["tools"], injectIntent: boolean): Context["tools"];
56
+ /** Resolve the human-readable reason an abort carried. A caller that aborts via
57
+ * `AbortController.abort(reason)` with a string or a non-`AbortError` `Error`
58
+ * (e.g. the coding agent's user-interrupt label) gets that text surfaced on the
59
+ * synthesized assistant message's `errorMessage`; a bare `abort()` (whose
60
+ * `signal.reason` is the default `AbortError` `DOMException`) falls back to the
61
+ * generic sentinel that downstream renderers treat as "no specific reason". */
62
+ export declare function abortReasonText(signal: AbortSignal | undefined): string;
@@ -1,4 +1,4 @@
1
- import { type AssistantMessage, type AssistantMessageEvent, type CursorExecHandlers, type CursorToolResultHandler, type Effort, type ImageContent, type Message, type Model, type ProviderSessionState, type ServiceTier, type SimpleStreamOptions, type ThinkingBudgets, type ToolChoice } from "@oh-my-pi/pi-ai";
1
+ import { type ApiKeyResolveContext, type AssistantMessage, type AssistantMessageEvent, type CursorExecHandlers, type CursorToolResultHandler, type Effort, type ImageContent, type Message, type Model, type ProviderSessionState, type ServiceTier, type SimpleStreamOptions, type ThinkingBudgets, type ToolChoice } from "@oh-my-pi/pi-ai";
2
2
  import type { AppendOnlyContextManager } from "./append-only-context";
3
3
  import type { HarmonyAuditEvent } from "./harmony-leak";
4
4
  import type { AgentEvent, AgentLoopConfig, AgentMessage, AgentState, AgentTool, AgentToolContext, StreamFn, ToolCallContext } from "./types";
@@ -51,6 +51,11 @@ export interface AgentOptions {
51
51
  * Used by providers that support session-based caching (e.g., OpenAI Codex).
52
52
  */
53
53
  sessionId?: string;
54
+ /**
55
+ * Optional prompt cache key forwarded to LLM providers.
56
+ * When omitted, providers may fall back to sessionId.
57
+ */
58
+ promptCacheKey?: string;
54
59
  /**
55
60
  * Shared provider state map for session-scoped transport/session caches.
56
61
  */
@@ -59,7 +64,7 @@ export interface AgentOptions {
59
64
  * Resolves an API key dynamically for each LLM call.
60
65
  * Useful for expiring tokens (e.g., GitHub Copilot OAuth).
61
66
  */
62
- getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
67
+ getApiKey?: (provider: string, ctx?: ApiKeyResolveContext) => Promise<string | undefined> | string | undefined;
63
68
  /**
64
69
  * Inspect or replace provider payloads before they are sent.
65
70
  */
@@ -159,7 +164,7 @@ export interface AgentPromptOptions {
159
164
  export declare class Agent {
160
165
  #private;
161
166
  streamFn: StreamFn;
162
- getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
167
+ getApiKey?: (provider: string, ctx?: ApiKeyResolveContext) => Promise<string | undefined> | string | undefined;
163
168
  /**
164
169
  * Hook invoked after tool arguments are validated and before execution.
165
170
  * Reassign at any time to swap the implementation (e.g. on extension reload).
@@ -180,6 +185,14 @@ export declare class Agent {
180
185
  * Call this when switching sessions (new session, branch, resume).
181
186
  */
182
187
  set sessionId(value: string | undefined);
188
+ /**
189
+ * Get the prompt cache key forwarded to providers.
190
+ */
191
+ get promptCacheKey(): string | undefined;
192
+ /**
193
+ * Set the prompt cache key forwarded to providers.
194
+ */
195
+ set promptCacheKey(value: string | undefined);
183
196
  /**
184
197
  * Static metadata forwarded to every API request when no resolver is installed
185
198
  * (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
@@ -317,7 +330,7 @@ export declare class Agent {
317
330
  */
318
331
  popLastFollowUp(): AgentMessage | undefined;
319
332
  clearMessages(): void;
320
- abort(): void;
333
+ abort(reason?: unknown): void;
321
334
  waitForIdle(): Promise<void>;
322
335
  reset(): void;
323
336
  /** Send a prompt with an AgentMessage */
@@ -3,7 +3,7 @@
3
3
  * The server manages auth and proxies requests to LLM providers.
4
4
  */
5
5
  import { type AssistantMessage, type AssistantMessageEvent, type Context, EventStream, type Model, type SimpleStreamOptions, type StopReason } from "@oh-my-pi/pi-ai";
6
- declare class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
6
+ export declare class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
7
7
  constructor();
8
8
  }
9
9
  /**
@@ -81,4 +81,3 @@ export interface ProxyStreamOptions extends SimpleStreamOptions {
81
81
  * ```
82
82
  */
83
83
  export declare function streamProxy(model: Model, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream;
84
- export {};
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Effort, ImageContent, Message, Model, SimpleStreamOptions, Static, streamSimple, TextContent, Tool, ToolChoice, ToolResultMessage, TSchema } from "@oh-my-pi/pi-ai";
1
+ import type { ApiKeyResolveContext, AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Effort, ImageContent, Message, Model, SimpleStreamOptions, Static, streamSimple, TextContent, Tool, ToolChoice, ToolResultMessage, TSchema } from "@oh-my-pi/pi-ai";
2
2
  import type { AppendOnlyContextManager } from "./append-only-context";
3
3
  import type { HarmonyAuditEvent } from "./harmony-leak";
4
4
  import type { AgentRunCoverage, AgentRunSummary } from "./run-collector";
@@ -85,7 +85,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
85
85
  * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire
86
86
  * during long-running tool execution phases.
87
87
  */
88
- getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
88
+ getApiKey?: (provider: string, ctx?: ApiKeyResolveContext) => Promise<string | undefined> | string | undefined;
89
89
  /**
90
90
  * Returns steering messages to inject into the conversation mid-run.
91
91
  *
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": "15.10.0",
4
+ "version": "15.10.2",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://omp.sh",
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": "15.10.0",
39
- "@oh-my-pi/pi-natives": "15.10.0",
40
- "@oh-my-pi/pi-utils": "15.10.0",
38
+ "@oh-my-pi/pi-ai": "15.10.2",
39
+ "@oh-my-pi/pi-natives": "15.10.2",
40
+ "@oh-my-pi/pi-utils": "15.10.2",
41
41
  "@opentelemetry/api": "^1.9.1"
42
42
  },
43
43
  "devDependencies": {
package/src/agent-loop.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Transforms to Message[] only at the LLM call boundary.
4
4
  */
5
5
  import {
6
+ type ApiKeyResolveContext,
6
7
  type AssistantMessage,
7
8
  type AssistantMessageEvent,
8
9
  type Context,
@@ -727,8 +728,9 @@ async function streamAssistantResponse(
727
728
  // Resolve API key (important for expiring tokens) — do this before resolving
728
729
  // metadata so that the session-sticky credential recorded by getApiKey is
729
730
  // visible to metadataResolver (e.g. for the correct account_uuid in metadata.user_id).
731
+ const staticApiKey = typeof config.apiKey === "string" ? config.apiKey : undefined;
730
732
  const resolvedApiKey =
731
- (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
733
+ (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || staticApiKey;
732
734
 
733
735
  // Re-resolve metadata after credential selection so the per-request value
734
736
  // reflects the credential actually used, not the snapshot from AgentLoopConfig construction.
@@ -798,7 +800,19 @@ async function streamAssistantResponse(
798
800
  return await runInActiveSpan(chatSpan, async () => {
799
801
  const response = await streamFunction(config.model, llmContext, {
800
802
  ...config,
801
- apiKey: resolvedApiKey,
803
+ // Hand streamSimple a resolver so its central auth-retry policy can
804
+ // re-resolve on 401 / usage-limit: the initial step reuses the key
805
+ // already resolved above (which set the session-sticky credential
806
+ // feeding metadataResolver), and retry steps forward the a/b/c ctx
807
+ // to config.getApiKey (force-refresh, then rotate). With no
808
+ // getApiKey hook the caller's own apiKey (string or resolver) flows
809
+ // through unchanged.
810
+ apiKey: config.getApiKey
811
+ ? (ctx: ApiKeyResolveContext) =>
812
+ ctx.error === undefined
813
+ ? resolvedApiKey
814
+ : Promise.resolve(config.getApiKey!(config.model.provider, ctx))
815
+ : config.apiKey,
802
816
  metadata: resolvedMetadata,
803
817
  toolChoice: effectiveToolChoice,
804
818
  reasoning: effectiveReasoning,
@@ -839,7 +853,14 @@ async function streamAssistantResponse(
839
853
  let detachAbortListener: (() => void) | undefined;
840
854
  if (requestSignal) {
841
855
  if (requestSignal.aborted) {
842
- const aborted = emitAbortedAssistantMessage(partialMessage, addedPartial, context, config, stream);
856
+ const aborted = emitAbortedAssistantMessage(
857
+ partialMessage,
858
+ addedPartial,
859
+ context,
860
+ config,
861
+ stream,
862
+ requestSignal,
863
+ );
843
864
  await finishChat(aborted);
844
865
  return aborted;
845
866
  }
@@ -861,7 +882,14 @@ async function streamAssistantResponse(
861
882
  if (capped) return capped;
862
883
  }
863
884
  responseIterator.return?.()?.catch(() => {});
864
- const aborted = emitAbortedAssistantMessage(partialMessage, addedPartial, context, config, stream);
885
+ const aborted = emitAbortedAssistantMessage(
886
+ partialMessage,
887
+ addedPartial,
888
+ context,
889
+ config,
890
+ stream,
891
+ requestSignal,
892
+ );
865
893
  await finishChat(aborted);
866
894
  return aborted;
867
895
  }
@@ -874,7 +902,14 @@ async function streamAssistantResponse(
874
902
  const capped = await finishCappedAssistantMessage();
875
903
  if (capped) return capped;
876
904
  }
877
- const aborted = emitAbortedAssistantMessage(partialMessage, addedPartial, context, config, stream);
905
+ const aborted = emitAbortedAssistantMessage(
906
+ partialMessage,
907
+ addedPartial,
908
+ context,
909
+ config,
910
+ stream,
911
+ requestSignal,
912
+ );
878
913
  await finishChat(aborted);
879
914
  return aborted;
880
915
  }
@@ -982,14 +1017,30 @@ async function streamAssistantResponse(
982
1017
  }
983
1018
  }
984
1019
 
1020
+ /** Resolve the human-readable reason an abort carried. A caller that aborts via
1021
+ * `AbortController.abort(reason)` with a string or a non-`AbortError` `Error`
1022
+ * (e.g. the coding agent's user-interrupt label) gets that text surfaced on the
1023
+ * synthesized assistant message's `errorMessage`; a bare `abort()` (whose
1024
+ * `signal.reason` is the default `AbortError` `DOMException`) falls back to the
1025
+ * generic sentinel that downstream renderers treat as "no specific reason". */
1026
+ export function abortReasonText(signal: AbortSignal | undefined): string {
1027
+ const reason = signal?.reason;
1028
+ if (typeof reason === "string" && reason.trim().length > 0) return reason;
1029
+ if (reason instanceof Error && reason.name !== "AbortError" && reason.message.trim().length > 0) {
1030
+ return reason.message;
1031
+ }
1032
+ return "Request was aborted";
1033
+ }
1034
+
985
1035
  function emitAbortedAssistantMessage(
986
1036
  partialMessage: AssistantMessage | null,
987
1037
  addedPartial: boolean,
988
1038
  context: AgentContext,
989
1039
  config: AgentLoopConfig,
990
1040
  stream: EventStream<AgentEvent, AgentMessage[]>,
1041
+ requestSignal: AbortSignal | undefined,
991
1042
  ): AssistantMessage {
992
- const errorMessage = "Request was aborted";
1043
+ const errorMessage = abortReasonText(requestSignal);
993
1044
  const abortedMessage: AssistantMessage = partialMessage
994
1045
  ? { ...partialMessage, stopReason: "aborted", errorMessage }
995
1046
  : {
package/src/agent.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { isPromise } from "node:util/types";
5
5
  import {
6
+ type ApiKeyResolveContext,
6
7
  type AssistantMessage,
7
8
  type AssistantMessageEvent,
8
9
  type CursorExecHandlers,
@@ -21,7 +22,7 @@ import {
21
22
  type ToolChoice,
22
23
  type ToolResultMessage,
23
24
  } from "@oh-my-pi/pi-ai";
24
- import { agentLoop, agentLoopContinue } from "./agent-loop";
25
+ import { abortReasonText, agentLoop, agentLoopContinue } from "./agent-loop";
25
26
  import type { AppendOnlyContextManager } from "./append-only-context";
26
27
  import type { HarmonyAuditEvent } from "./harmony-leak";
27
28
  import type {
@@ -132,6 +133,11 @@ export interface AgentOptions {
132
133
  * Used by providers that support session-based caching (e.g., OpenAI Codex).
133
134
  */
134
135
  sessionId?: string;
136
+ /**
137
+ * Optional prompt cache key forwarded to LLM providers.
138
+ * When omitted, providers may fall back to sessionId.
139
+ */
140
+ promptCacheKey?: string;
135
141
  /**
136
142
  * Shared provider state map for session-scoped transport/session caches.
137
143
  */
@@ -141,7 +147,7 @@ export interface AgentOptions {
141
147
  * Resolves an API key dynamically for each LLM call.
142
148
  * Useful for expiring tokens (e.g., GitHub Copilot OAuth).
143
149
  */
144
- getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
150
+ getApiKey?: (provider: string, ctx?: ApiKeyResolveContext) => Promise<string | undefined> | string | undefined;
145
151
 
146
152
  /**
147
153
  * Inspect or replace provider payloads before they are sent.
@@ -283,6 +289,7 @@ export class Agent {
283
289
  #interruptMode: "immediate" | "wait";
284
290
  #maxToolCallsPerTurn?: number;
285
291
  #sessionId?: string;
292
+ #promptCacheKey?: string;
286
293
  #metadata?: Record<string, unknown>;
287
294
  #metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
288
295
  #providerSessionState?: Map<string, ProviderSessionState>;
@@ -319,7 +326,7 @@ export class Agent {
319
326
  #cursorToolResultBuffer: CursorToolResultEntry[] = [];
320
327
 
321
328
  streamFn: StreamFn;
322
- getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
329
+ getApiKey?: (provider: string, ctx?: ApiKeyResolveContext) => Promise<string | undefined> | string | undefined;
323
330
  /**
324
331
  * Hook invoked after tool arguments are validated and before execution.
325
332
  * Reassign at any time to swap the implementation (e.g. on extension reload).
@@ -344,6 +351,7 @@ export class Agent {
344
351
  this.#maxToolCallsPerTurn = opts.maxToolCallsPerTurn;
345
352
  this.streamFn = opts.streamFn || streamSimple;
346
353
  this.#sessionId = opts.sessionId;
354
+ this.#promptCacheKey = opts.promptCacheKey;
347
355
  this.#providerSessionState = opts.providerSessionState;
348
356
  this.#thinkingBudgets = opts.thinkingBudgets;
349
357
  this.#temperature = opts.temperature;
@@ -390,6 +398,20 @@ export class Agent {
390
398
  this.#sessionId = value;
391
399
  }
392
400
 
401
+ /**
402
+ * Get the prompt cache key forwarded to providers.
403
+ */
404
+ get promptCacheKey(): string | undefined {
405
+ return this.#promptCacheKey;
406
+ }
407
+
408
+ /**
409
+ * Set the prompt cache key forwarded to providers.
410
+ */
411
+ set promptCacheKey(value: string | undefined) {
412
+ this.#promptCacheKey = value;
413
+ }
414
+
393
415
  /**
394
416
  * Static metadata forwarded to every API request when no resolver is installed
395
417
  * (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
@@ -768,8 +790,8 @@ export class Agent {
768
790
  this.#state.messages.length = 0;
769
791
  }
770
792
 
771
- abort() {
772
- this.#abortController?.abort();
793
+ abort(reason?: unknown) {
794
+ this.#abortController?.abort(reason);
773
795
  }
774
796
 
775
797
  waitForIdle(): Promise<void> {
@@ -936,6 +958,7 @@ export class Agent {
936
958
  interruptMode: this.#interruptMode,
937
959
  maxToolCallsPerTurn: this.#maxToolCallsPerTurn,
938
960
  sessionId: this.#sessionId,
961
+ promptCacheKey: this.#promptCacheKey,
939
962
  metadata: this.#metadataResolver ? undefined : this.#metadata,
940
963
  metadataResolver: this.#metadataResolver,
941
964
  providerSessionState: this.#providerSessionState,
@@ -1053,8 +1076,12 @@ export class Agent {
1053
1076
  }
1054
1077
  }
1055
1078
  } catch (err) {
1056
- const errorMessage = err instanceof Error ? err.message : String(err);
1057
1079
  const stoppedForAbort = this.#abortController?.signal.aborted === true;
1080
+ const errorMessage = stoppedForAbort
1081
+ ? abortReasonText(this.#abortController?.signal)
1082
+ : err instanceof Error
1083
+ ? err.message
1084
+ : String(err);
1058
1085
  const shouldEmitVisibleOutputBlockedError = !stoppedForAbort && isAnthropicOutputBlockedError(errorMessage);
1059
1086
  const assistantPartial = partial?.role === "assistant" ? partial : undefined;
1060
1087
  const hadAssistantStart = assistantPartial !== undefined;
package/src/proxy.ts CHANGED
@@ -16,8 +16,8 @@ import { calculateCost } from "@oh-my-pi/pi-ai/models";
16
16
  import { parseStreamingJson } from "@oh-my-pi/pi-ai/utils/json-parse";
17
17
  import { readSseJson } from "@oh-my-pi/pi-utils";
18
18
 
19
- // Create stream class matching ProxyMessageEventStream
20
- class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
19
+ // Event stream adapter for proxy SSE events
20
+ export class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
21
21
  constructor() {
22
22
  super(
23
23
  event => event.type === "done" || event.type === "error",
@@ -167,9 +167,12 @@ export function streamProxy(model: Model, context: Context, options: ProxyStream
167
167
  }
168
168
  }
169
169
 
170
- if (options.signal?.aborted && !sawTerminalEvent) {
171
- const reason = options.signal.reason;
172
- throw reason instanceof Error ? reason : new Error(String(reason ?? "Request aborted"));
170
+ if (!sawTerminalEvent) {
171
+ if (options.signal?.aborted) {
172
+ const reason = options.signal.reason;
173
+ throw reason instanceof Error ? reason : new Error(String(reason ?? "Request aborted"));
174
+ }
175
+ throw new Error("Proxy stream ended without a terminal event (done or error)");
173
176
  }
174
177
 
175
178
  stream.end();
package/src/telemetry.ts CHANGED
@@ -50,6 +50,7 @@ import {
50
50
  } from "@opentelemetry/api";
51
51
  import { AgentRunCollector, type AgentRunCoverage, type AgentRunSummary, type ToolStatus } from "./run-collector";
52
52
  import type { AgentTool } from "./types";
53
+ import { EventLoopKeepalive } from "./utils/yield";
53
54
 
54
55
  /** Default tracer name. Override via {@link AgentTelemetryConfig.tracerName}. */
55
56
  export const DEFAULT_TRACER_NAME = "@oh-my-pi/pi-agent-core";
@@ -1629,6 +1630,13 @@ export async function instrumentedCompleteSimple<TApi extends Api>(
1629
1630
  options: SimpleStreamOptions,
1630
1631
  span: InstrumentedChatSpanOptions,
1631
1632
  ): Promise<AssistantMessage> {
1633
+ // Oneshot LLM calls (handoff, compaction/branch summaries) run outside the
1634
+ // agent `#runLoop`, which is where the EventLoopKeepalive normally lives.
1635
+ // Without it, Bun's JSC loop stops servicing timers while parked on the
1636
+ // long-lived completion promise, freezing any host spinner (e.g. the
1637
+ // `/handoff` Loader) until an unrelated I/O event (a terminal resize)
1638
+ // pokes the loop. Keep the loop healthy for the duration of the call.
1639
+ using _keepalive = new EventLoopKeepalive();
1632
1640
  const { telemetry, parent, oneshotKind } = span;
1633
1641
  const stepNumber = span.stepNumber ?? -1;
1634
1642
  const reasoning = options.reasoning;
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ ApiKeyResolveContext,
2
3
  AssistantMessage,
3
4
  AssistantMessageEvent,
4
5
  AssistantMessageEventStream,
@@ -112,7 +113,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
112
113
  * Useful for short-lived OAuth tokens (e.g., GitHub Copilot) that may expire
113
114
  * during long-running tool execution phases.
114
115
  */
115
- getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
116
+ getApiKey?: (provider: string, ctx?: ApiKeyResolveContext) => Promise<string | undefined> | string | undefined;
116
117
 
117
118
  /**
118
119
  * Returns steering messages to inject into the conversation mid-run.