@oh-my-pi/pi-agent-core 15.10.0 → 15.10.1
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 +18 -0
- package/dist/types/agent-loop.d.ts +7 -0
- package/dist/types/agent.d.ts +17 -4
- package/dist/types/types.d.ts +2 -2
- package/package.json +4 -4
- package/src/agent-loop.ts +57 -6
- package/src/agent.ts +33 -6
- package/src/telemetry.ts +8 -0
- package/src/types.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.10.1] - 2026-06-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added optional `promptCacheKey` support to `AgentOptions` and `Agent` via a new `promptCacheKey` property so providers can receive a caller-provided prompt cache key
|
|
10
|
+
- Added optional `ApiKeyResolveContext` parameter to `getApiKey` in `AgentOptions` and `AgentLoopConfig` so key resolvers can receive retry context
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Enabled streaming API calls to re-resolve credentials through the `getApiKey` callback when retries occur after authentication-related errors
|
|
15
|
+
- `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.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed handling of short-lived API keys so that expired tokens are retried with a refreshed value during 401/usage-limit failures
|
|
20
|
+
- Ensured fallback API key resolution uses the initially configured static `apiKey` when `getApiKey` is present
|
|
21
|
+
- 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.
|
|
22
|
+
|
|
5
23
|
## [15.9.5] - 2026-06-05
|
|
6
24
|
|
|
7
25
|
### 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;
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -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 */
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "15.10.1",
|
|
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.
|
|
39
|
-
"@oh-my-pi/pi-natives": "15.10.
|
|
40
|
-
"@oh-my-pi/pi-utils": "15.10.
|
|
38
|
+
"@oh-my-pi/pi-ai": "15.10.1",
|
|
39
|
+
"@oh-my-pi/pi-natives": "15.10.1",
|
|
40
|
+
"@oh-my-pi/pi-utils": "15.10.1",
|
|
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) ||
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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/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.
|