@oh-my-pi/pi-agent-core 18.13.3 → 18.14.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 +11 -0
- package/README.md +7 -9
- package/dist/types/agent.d.ts +12 -0
- package/dist/types/compaction/compaction-v2-streaming.d.ts +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/types.d.ts +5 -0
- package/package.json +8 -9
- package/src/agent-loop.ts +6 -24
- package/src/agent.ts +37 -8
- package/src/compaction/anthropic.ts +9 -4
- package/src/compaction/compaction-v2-streaming.ts +20 -17
- package/src/compaction/compaction.ts +10 -6
- package/src/compaction/openai.ts +3 -1
- package/src/compaction/prompts/anthropic-compaction-instructions.md +1 -2
- package/src/index.ts +0 -2
- package/src/telemetry.ts +0 -8
- package/src/types.ts +6 -0
- package/THIRD-PARTY-NOTICES.txt +0 -22901
- package/dist/types/utils/yield.d.ts +0 -71
- package/src/utils/yield.ts +0 -183
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.14.0] - 2026-10-29
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Removed the Bun event-loop workaround utilities from `@oh-my-pi/pi-agent-core/utils/yield`; agent loops no longer insert cooperative sleeps while streaming or between tool calls.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added `AgentTool.approvalRestrictionPaths` so tools can report the paths they read, allowing restricted-folder approvals to cover those paths without remembering approval separately for each path.
|
|
14
|
+
- Added `Agent.logContext`, which adds custom context fields to every log record written during an agent run.
|
|
15
|
+
|
|
5
16
|
## [18.13.3] - 2026-10-28
|
|
6
17
|
|
|
7
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ const agent = new Agent({
|
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
agent.subscribe(
|
|
24
|
+
agent.subscribe(event => {
|
|
25
25
|
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
26
26
|
// Stream just the new text chunk
|
|
27
27
|
process.stdout.write(event.assistantMessageEvent.delta);
|
|
@@ -219,7 +219,7 @@ await agent.waitForIdle(); // Wait for completion
|
|
|
219
219
|
### Events
|
|
220
220
|
|
|
221
221
|
```typescript
|
|
222
|
-
const unsubscribe = agent.subscribe(
|
|
222
|
+
const unsubscribe = agent.subscribe(event => {
|
|
223
223
|
console.log(event.type);
|
|
224
224
|
});
|
|
225
225
|
unsubscribe();
|
|
@@ -274,8 +274,8 @@ Handle custom types in `convertToLlm`:
|
|
|
274
274
|
|
|
275
275
|
```typescript
|
|
276
276
|
const agent = new Agent({
|
|
277
|
-
convertToLlm:
|
|
278
|
-
messages.flatMap(
|
|
277
|
+
convertToLlm: messages =>
|
|
278
|
+
messages.flatMap(m => {
|
|
279
279
|
if (m.role === "notification") return []; // Filter out
|
|
280
280
|
return [m];
|
|
281
281
|
}),
|
|
@@ -360,7 +360,7 @@ const context: AgentContext = {
|
|
|
360
360
|
|
|
361
361
|
const config: AgentLoopConfig = {
|
|
362
362
|
model: getModel("openai", "gpt-4o"),
|
|
363
|
-
convertToLlm:
|
|
363
|
+
convertToLlm: msgs => msgs.filter(m => ["user", "assistant", "toolResult"].includes(m.role)),
|
|
364
364
|
};
|
|
365
365
|
|
|
366
366
|
const userMessage = { role: "user", content: "Hello", timestamp: Date.now() };
|
|
@@ -376,6 +376,7 @@ for await (const event of agentLoopContinue(context, config)) {
|
|
|
376
376
|
```
|
|
377
377
|
|
|
378
378
|
## Run-level telemetry
|
|
379
|
+
|
|
379
380
|
Every `invoke_agent` produces two values alongside the OTEL spans:
|
|
380
381
|
|
|
381
382
|
- **`AgentRunSummary`** — chat / tool / usage / cost / error counters bucketed
|
|
@@ -447,10 +448,7 @@ Callers that drive the loop multiple times (verify pass, benchmark harness)
|
|
|
447
448
|
fold N summaries with `aggregateAgentRunSummaries` / `aggregateAgentRunCoverage`:
|
|
448
449
|
|
|
449
450
|
```typescript
|
|
450
|
-
import {
|
|
451
|
-
aggregateAgentRunSummaries,
|
|
452
|
-
aggregateAgentRunCoverage,
|
|
453
|
-
} from "@oh-my-pi/pi-agent";
|
|
451
|
+
import { aggregateAgentRunSummaries, aggregateAgentRunCoverage } from "@oh-my-pi/pi-agent";
|
|
454
452
|
|
|
455
453
|
const summaries: AgentRunSummary[] = [];
|
|
456
454
|
const coverages: AgentRunCoverage[] = [];
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -295,6 +295,11 @@ export declare class Agent {
|
|
|
295
295
|
* Set the prompt cache key forwarded to providers.
|
|
296
296
|
*/
|
|
297
297
|
set promptCacheKey(value: string | undefined);
|
|
298
|
+
/**
|
|
299
|
+
* Fields added to every log record written while this agent runs (the
|
|
300
|
+
* owning session's `sessionId`), so in-process agents' records stay apart.
|
|
301
|
+
*/
|
|
302
|
+
logContext: Readonly<Record<string, unknown>> | undefined;
|
|
298
303
|
/**
|
|
299
304
|
* Static metadata forwarded to every API request when no resolver is installed
|
|
300
305
|
* (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
|
|
@@ -379,6 +384,13 @@ export declare class Agent {
|
|
|
379
384
|
/** Strip tool descriptions from provider-bound specs; read per request. */
|
|
380
385
|
get pruneToolDescriptions(): boolean;
|
|
381
386
|
set pruneToolDescriptions(value: boolean);
|
|
387
|
+
/**
|
|
388
|
+
* Whether requests to `model` actually ship description-stripped tool specs.
|
|
389
|
+
* Owned dialects render full definitions in-band, so they never prune; the
|
|
390
|
+
* loop applies the same rule (`pruneToolDescriptions && !ownedDialect`).
|
|
391
|
+
* Context accounting reads this instead of the raw setting.
|
|
392
|
+
*/
|
|
393
|
+
prunesToolDescriptions(model: Model | undefined): boolean;
|
|
382
394
|
/** Inject/strip the intent field on tool calls; applies from the next prompt run. */
|
|
383
395
|
get intentTracing(): boolean;
|
|
384
396
|
set intentTracing(value: boolean);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* compaction item as replacement history.
|
|
8
8
|
*/
|
|
9
9
|
import type { Api, CodexCompactionContext, FetchImpl, Model, ProviderSessionState } from "@oh-my-pi/pi-ai";
|
|
10
|
-
import {
|
|
10
|
+
import type { OpenAICodexCompactionBody } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
11
11
|
import type { CatalogPolicyInputs } from "@oh-my-pi/pi-catalog/types";
|
|
12
12
|
/** Retained-message budget Codex uses after streamed V2 compaction. */
|
|
13
13
|
export declare const V2_RETAINED_MESSAGE_TOKEN_BUDGET = 64000;
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/types.d.ts
CHANGED
|
@@ -1111,6 +1111,11 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
1111
1111
|
* approving the call can record them. Omit it (or return `undefined`) and the call prompts by tier.
|
|
1112
1112
|
*/
|
|
1113
1113
|
approvalCacheTargets?: (args: unknown) => readonly string[] | undefined;
|
|
1114
|
+
/**
|
|
1115
|
+
* Paths a call reads beyond its working directory, matched against restricted folders when it
|
|
1116
|
+
* reports no `approvalCacheTargets`. Unlike cache targets they never key a remembered grant.
|
|
1117
|
+
*/
|
|
1118
|
+
approvalRestrictionPaths?: (args: unknown) => readonly string[] | undefined;
|
|
1114
1119
|
/** The main execution callback for this tool. */
|
|
1115
1120
|
execute: AgentToolExecFn<TParameters, TDetails, TTheme>;
|
|
1116
1121
|
/** Optional custom rendering for tool call display (returns UI component) */
|
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": "18.
|
|
4
|
+
"version": "18.14.0",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@oh-my-pi/pi-ai": "18.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
43
|
-
"@oh-my-pi/pi-natives": "18.
|
|
44
|
-
"@oh-my-pi/pi-utils": "18.
|
|
45
|
-
"@oh-my-pi/pi-wire": "18.
|
|
46
|
-
"@oh-my-pi/snapcompact": "18.
|
|
41
|
+
"@oh-my-pi/pi-ai": "18.14.0",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "18.14.0",
|
|
43
|
+
"@oh-my-pi/pi-natives": "18.14.0",
|
|
44
|
+
"@oh-my-pi/pi-utils": "18.14.0",
|
|
45
|
+
"@oh-my-pi/pi-wire": "18.14.0",
|
|
46
|
+
"@oh-my-pi/snapcompact": "18.14.0",
|
|
47
47
|
"@opentelemetry/api": "^1.9.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@oh-my-pi/omptype": "18.
|
|
50
|
+
"@oh-my-pi/omptype": "18.14.0",
|
|
51
51
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
52
52
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
53
53
|
"@types/bun": "^1.3.14"
|
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
"README.md",
|
|
61
61
|
"CHANGELOG.md",
|
|
62
62
|
"LICENSE",
|
|
63
|
-
"THIRD-PARTY-NOTICES.txt",
|
|
64
63
|
"dist/types"
|
|
65
64
|
],
|
|
66
65
|
"exports": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -109,7 +109,6 @@ import {
|
|
|
109
109
|
isSoftToolRequirement,
|
|
110
110
|
SPECULATIVE_STREAM_SESSION,
|
|
111
111
|
} from "./types";
|
|
112
|
-
import { yieldIfDue } from "./utils/yield";
|
|
113
112
|
|
|
114
113
|
async function waitForProcessPause(config: AgentLoopConfig, signal?: AbortSignal): Promise<void> {
|
|
115
114
|
try {
|
|
@@ -944,7 +943,8 @@ export function normalizeMessagesForProvider(
|
|
|
944
943
|
});
|
|
945
944
|
}
|
|
946
945
|
|
|
947
|
-
|
|
946
|
+
/** Schema description of the injected `i` field; `PI_INTENT_DESC` replaces it. */
|
|
947
|
+
const DEFAULT_INTENT_FIELD_DESCRIPTION = "concise intent";
|
|
948
948
|
const INTENT_SCHEMA_UNION_KEYS = ["anyOf", "oneOf"] as const;
|
|
949
949
|
|
|
950
950
|
// Memoize injection per input schema identity: normalizeTools runs on every
|
|
@@ -1021,7 +1021,7 @@ function injectIntentIntoSchema(
|
|
|
1021
1021
|
...schemaRecord,
|
|
1022
1022
|
properties: {
|
|
1023
1023
|
[INTENT_FIELD]: describeIntent
|
|
1024
|
-
? { type: "string", description:
|
|
1024
|
+
? { type: "string", description: Bun.env.PI_INTENT_DESC || DEFAULT_INTENT_FIELD_DESCRIPTION }
|
|
1025
1025
|
: { type: "string" },
|
|
1026
1026
|
...properties,
|
|
1027
1027
|
},
|
|
@@ -1072,9 +1072,8 @@ function resolveIntentMode(intent: AgentTool["intent"]): "require" | "optional"
|
|
|
1072
1072
|
}
|
|
1073
1073
|
|
|
1074
1074
|
/**
|
|
1075
|
-
* Longest `i` value accepted as an intent
|
|
1076
|
-
*
|
|
1077
|
-
* payload the model put in the wrong field, not a label.
|
|
1075
|
+
* Longest `i` value accepted as an intent; anything past this is a tool
|
|
1076
|
+
* payload the model put in the wrong field, not an intent.
|
|
1078
1077
|
*/
|
|
1079
1078
|
const MAX_INTENT_LENGTH = 200;
|
|
1080
1079
|
|
|
@@ -1336,9 +1335,6 @@ async function runLoopBody(
|
|
|
1336
1335
|
endAgentStream(stream, newMessages, telemetry, stepCounter.count);
|
|
1337
1336
|
return;
|
|
1338
1337
|
}
|
|
1339
|
-
// Yield at the top of each iteration to prevent busy-wait when
|
|
1340
|
-
// the agent loop is executing tool calls back-to-back.
|
|
1341
|
-
await yieldIfDue();
|
|
1342
1338
|
// Park at the turn boundary while the process-wide pause gate is
|
|
1343
1339
|
// engaged (host /pause). An external abort releases the park so a
|
|
1344
1340
|
// cancelled run still unwinds while everything else stays frozen.
|
|
@@ -2379,10 +2375,6 @@ async function streamAssistantResponse(
|
|
|
2379
2375
|
return await finishAbortedStream();
|
|
2380
2376
|
}
|
|
2381
2377
|
|
|
2382
|
-
// Yield to the event loop periodically to prevent busy-wait
|
|
2383
|
-
// when the LLM is streaming chunks faster than the loop can rest.
|
|
2384
|
-
await yieldIfDue();
|
|
2385
|
-
|
|
2386
2378
|
if (event.type === "toolcall_start") {
|
|
2387
2379
|
const block = event.partial.content[event.contentIndex];
|
|
2388
2380
|
if (block?.type === "toolCall") {
|
|
@@ -2790,14 +2782,7 @@ function buildToolCallAbortMessages(
|
|
|
2790
2782
|
* `signal.reason` is the default `AbortError` `DOMException`) falls back to the
|
|
2791
2783
|
* generic sentinel that downstream renderers treat as "no specific reason". */
|
|
2792
2784
|
export function abortReasonText(signal: AbortSignal | undefined): string {
|
|
2793
|
-
|
|
2794
|
-
if (scopedReason) return scopedReason.message;
|
|
2795
|
-
const reason = signal?.reason;
|
|
2796
|
-
if (typeof reason === "string" && reason.trim().length > 0) return reason;
|
|
2797
|
-
if (reason instanceof Error && reason.name !== "AbortError" && reason.message.trim().length > 0) {
|
|
2798
|
-
return reason.message;
|
|
2799
|
-
}
|
|
2800
|
-
return "Request was aborted";
|
|
2785
|
+
return toolScopedAbortReason(signal)?.message ?? AIError.callerAbortReason(signal) ?? "Request was aborted";
|
|
2801
2786
|
}
|
|
2802
2787
|
|
|
2803
2788
|
function emitAbortedAssistantMessage(
|
|
@@ -3917,9 +3902,6 @@ async function executeToolCalls(
|
|
|
3917
3902
|
await steeringWatchPromise?.catch(() => undefined);
|
|
3918
3903
|
clearInterval(steeringWatchTimer);
|
|
3919
3904
|
}
|
|
3920
|
-
// Yield after batch tool execution to let GC and I/O catch up,
|
|
3921
|
-
// especially when tool results are large (e.g. bash output).
|
|
3922
|
-
await yieldIfDue();
|
|
3923
3905
|
|
|
3924
3906
|
for (const record of records) {
|
|
3925
3907
|
if (!record.toolResultMessage) {
|
package/src/agent.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type ToolResultMessage,
|
|
24
24
|
} from "@oh-my-pi/pi-ai";
|
|
25
25
|
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
26
|
-
import { openAIResponsesReplaysNativeHistory } from "@oh-my-pi/pi-ai/providers/openai-responses";
|
|
26
|
+
import { openAIResponsesReplaysNativeHistory } from "@oh-my-pi/pi-ai/providers/openai-responses-state";
|
|
27
27
|
import type { HarmonyAuditEvent } from "@oh-my-pi/pi-ai/utils/harmony-leak";
|
|
28
28
|
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
|
29
29
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
@@ -69,7 +69,6 @@ import type {
|
|
|
69
69
|
ToolChoiceDirective,
|
|
70
70
|
} from "./types";
|
|
71
71
|
import { isSoftToolRequirement } from "./types";
|
|
72
|
-
import { EventLoopKeepalive } from "./utils/yield";
|
|
73
72
|
|
|
74
73
|
/**
|
|
75
74
|
* Default convertToLlm: Keep only LLM-compatible replay messages.
|
|
@@ -643,6 +642,12 @@ export class Agent {
|
|
|
643
642
|
this.#promptCacheKey = value;
|
|
644
643
|
}
|
|
645
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Fields added to every log record written while this agent runs (the
|
|
647
|
+
* owning session's `sessionId`), so in-process agents' records stay apart.
|
|
648
|
+
*/
|
|
649
|
+
logContext: Readonly<Record<string, unknown>> | undefined;
|
|
650
|
+
|
|
646
651
|
/**
|
|
647
652
|
* Static metadata forwarded to every API request when no resolver is installed
|
|
648
653
|
* (e.g. `metadata.user_id` for Anthropic session attribution). Setting this
|
|
@@ -819,6 +824,23 @@ export class Agent {
|
|
|
819
824
|
this.#pruneToolDescriptions = value;
|
|
820
825
|
}
|
|
821
826
|
|
|
827
|
+
/**
|
|
828
|
+
* Whether requests to `model` actually ship description-stripped tool specs.
|
|
829
|
+
* Owned dialects render full definitions in-band, so they never prune; the
|
|
830
|
+
* loop applies the same rule (`pruneToolDescriptions && !ownedDialect`).
|
|
831
|
+
* Context accounting reads this instead of the raw setting.
|
|
832
|
+
*/
|
|
833
|
+
prunesToolDescriptions(model: Model | undefined): boolean {
|
|
834
|
+
return this.#pruneToolDescriptions && model !== undefined && !this.#ownedDialect(model);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
#ownedDialect(model: Model): Dialect | undefined {
|
|
838
|
+
return (
|
|
839
|
+
(this.#dialectResolver ? this.#dialectResolver(model) : this.#dialect) ??
|
|
840
|
+
resolveOwnedDialectFromEnv(Bun.env.PI_DIALECT)
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
822
844
|
/** Inject/strip the intent field on tool calls; applies from the next prompt run. */
|
|
823
845
|
get intentTracing(): boolean {
|
|
824
846
|
return this.#intentTracing;
|
|
@@ -928,9 +950,7 @@ export class Agent {
|
|
|
928
950
|
): Promise<Context> {
|
|
929
951
|
const model = this.#state.model;
|
|
930
952
|
if (!model) throw new Error("No active model on agent");
|
|
931
|
-
const ownedDialect =
|
|
932
|
-
(this.#dialectResolver ? this.#dialectResolver(model) : this.#dialect) ??
|
|
933
|
-
resolveOwnedDialectFromEnv(Bun.env.PI_DIALECT);
|
|
953
|
+
const ownedDialect = this.#ownedDialect(model);
|
|
934
954
|
const messages = normalizeMessagesForProvider(llmMessages, model);
|
|
935
955
|
const tools = ownedDialect
|
|
936
956
|
? []
|
|
@@ -1653,7 +1673,7 @@ export class Agent {
|
|
|
1653
1673
|
promptOptions = imagesOrOptions as AgentPromptOptions | undefined;
|
|
1654
1674
|
}
|
|
1655
1675
|
|
|
1656
|
-
await this.#runLoop(msgs, promptOptions);
|
|
1676
|
+
await this.#inLogContext(() => this.#runLoop(msgs, promptOptions));
|
|
1657
1677
|
}
|
|
1658
1678
|
|
|
1659
1679
|
/**
|
|
@@ -1677,7 +1697,12 @@ export class Agent {
|
|
|
1677
1697
|
return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
1678
1698
|
}
|
|
1679
1699
|
|
|
1680
|
-
|
|
1700
|
+
continue(signal?: AbortSignal): Promise<void> {
|
|
1701
|
+
// Queued-message hooks run before the loop and are this agent's work too.
|
|
1702
|
+
return this.#inLogContext(() => this.#continue(signal));
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
async #continue(signal?: AbortSignal) {
|
|
1681
1706
|
if (this.#state.isStreaming) {
|
|
1682
1707
|
throw new AgentBusyError();
|
|
1683
1708
|
}
|
|
@@ -1759,6 +1784,11 @@ export class Agent {
|
|
|
1759
1784
|
}
|
|
1760
1785
|
}
|
|
1761
1786
|
|
|
1787
|
+
/** Run `fn` with {@link logContext} on every record it logs. */
|
|
1788
|
+
#inLogContext<T>(fn: () => T): T {
|
|
1789
|
+
return this.logContext ? logger.withContext(this.logContext, fn) : fn();
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1762
1792
|
/**
|
|
1763
1793
|
* Run the agent loop.
|
|
1764
1794
|
* If messages are provided, starts a new conversation turn with those messages.
|
|
@@ -1774,7 +1804,6 @@ export class Agent {
|
|
|
1774
1804
|
if (!model) throw new Error("No model configured");
|
|
1775
1805
|
|
|
1776
1806
|
let skipInitialSteeringPoll = options?.skipInitialSteeringPoll === true;
|
|
1777
|
-
using _ = new EventLoopKeepalive();
|
|
1778
1807
|
if (!runStateClaimed) {
|
|
1779
1808
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
1780
1809
|
this.#runningPrompt = promise;
|
|
@@ -18,7 +18,6 @@ import type {
|
|
|
18
18
|
Usage,
|
|
19
19
|
} from "@oh-my-pi/pi-ai";
|
|
20
20
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
21
|
-
import { resolveAnthropicCompactionEffort } from "@oh-my-pi/pi-ai/providers/anthropic";
|
|
22
21
|
import {
|
|
23
22
|
supportsAnthropicCompaction,
|
|
24
23
|
supportsBedrockConverseCompaction,
|
|
@@ -251,6 +250,14 @@ export async function requestAnthropicNativeCompaction(
|
|
|
251
250
|
signal: AbortSignal | undefined,
|
|
252
251
|
options: AnthropicNativeCompactionOptions,
|
|
253
252
|
): Promise<AnthropicNativeCompactionResponse> {
|
|
253
|
+
// Pure compaction policy checks must not load the provider; only an actual request needs it.
|
|
254
|
+
const reasoning = isAnthropicMessagesModel(model)
|
|
255
|
+
? (await import("@oh-my-pi/pi-ai/providers/anthropic")).resolveAnthropicCompactionEffort(
|
|
256
|
+
model,
|
|
257
|
+
request.context.messages,
|
|
258
|
+
request.reasoning,
|
|
259
|
+
)
|
|
260
|
+
: request.reasoning;
|
|
254
261
|
const response = await instrumentedCompleteSimple(
|
|
255
262
|
model,
|
|
256
263
|
request.context,
|
|
@@ -258,9 +265,7 @@ export async function requestAnthropicNativeCompaction(
|
|
|
258
265
|
apiKey,
|
|
259
266
|
signal,
|
|
260
267
|
maxTokens: request.maxTokens,
|
|
261
|
-
reasoning
|
|
262
|
-
? resolveAnthropicCompactionEffort(model, request.context.messages, request.reasoning)
|
|
263
|
-
: request.reasoning,
|
|
268
|
+
reasoning,
|
|
264
269
|
initiatorOverride: options.initiatorOverride,
|
|
265
270
|
metadata: options.metadata,
|
|
266
271
|
fetch: options.fetch,
|
|
@@ -11,11 +11,9 @@ import type { Api, CodexCompactionContext, FetchImpl, Model, ProviderSessionStat
|
|
|
11
11
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
12
12
|
import { createOpenAICodexCompactionRequestContext } from "@oh-my-pi/pi-ai/providers/openai-codex-compaction";
|
|
13
13
|
import { applyCodexResponsesLiteShape } from "@oh-my-pi/pi-ai/providers/openai-codex/request-transformer";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
type OpenAICodexCompactionBody,
|
|
18
|
-
type OpenAICodexCompatibilityMetadata,
|
|
14
|
+
import type {
|
|
15
|
+
OpenAICodexCompactionBody,
|
|
16
|
+
OpenAICodexCompatibilityMetadata,
|
|
19
17
|
} from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
20
18
|
import {
|
|
21
19
|
getOpenAIPromptCacheKey,
|
|
@@ -293,18 +291,21 @@ export async function requestCompactionV2Streaming(
|
|
|
293
291
|
|
|
294
292
|
const retryWait = options?.retryWait ?? ((delayMs: number) => Bun.sleep(delayMs));
|
|
295
293
|
const isCodexResponses = compactionV2Api(model) === "openai-codex-responses" || model.provider === "openai-codex";
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
294
|
+
let codexMetadata: OpenAICodexCompatibilityMetadata | undefined;
|
|
295
|
+
if (isCodexResponses && !shouldUseCodexProviderTransport(model)) {
|
|
296
|
+
// Metadata generation is request work; importing compaction policy must not load the Codex transport.
|
|
297
|
+
const { createOpenAICodexCompatibilityMetadata } =
|
|
298
|
+
await import("@oh-my-pi/pi-ai/providers/openai-codex-responses");
|
|
299
|
+
codexMetadata = createOpenAICodexCompatibilityMetadata({
|
|
300
|
+
sessionId: request.sessionId,
|
|
301
|
+
providerSessionState: options?.providerSessionState,
|
|
302
|
+
requestKind: "compaction",
|
|
303
|
+
compaction: createOpenAICodexCompactionRequestContext({
|
|
304
|
+
context: options?.codexCompaction,
|
|
305
|
+
implementation: "responses_compaction_v2",
|
|
306
|
+
}),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
308
309
|
let lastError: Error | undefined;
|
|
309
310
|
|
|
310
311
|
for (let attempt = 0; attempt <= V2_COMPACTION_MAX_RETRIES; attempt++) {
|
|
@@ -367,6 +368,8 @@ async function attemptCompactionV2Streaming(
|
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
if (shouldUseCodexProviderTransport(model)) {
|
|
371
|
+
// The native transport is only needed when sending a Codex compaction request.
|
|
372
|
+
const { openCodexCompactionEventStream } = await import("@oh-my-pi/pi-ai/providers/openai-codex-responses");
|
|
370
373
|
const eventStream = await openCodexCompactionEventStream(model, body, {
|
|
371
374
|
apiKey,
|
|
372
375
|
signal,
|
|
@@ -27,12 +27,8 @@ import {
|
|
|
27
27
|
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
28
28
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
29
29
|
import { createOpenAICodexCompactionRequestContext } from "@oh-my-pi/pi-ai/providers/openai-codex-compaction";
|
|
30
|
-
import {
|
|
31
|
-
buildTransformedCodexRequestBody,
|
|
32
|
-
type OpenAICodexCompactionBody,
|
|
33
|
-
} from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
30
|
+
import type { OpenAICodexCompactionBody } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
34
31
|
import type { InputItem as CodexInputItem } from "@oh-my-pi/pi-ai/providers/openai-codex/request-transformer";
|
|
35
|
-
import { convertTools } from "@oh-my-pi/pi-ai/providers/openai-responses";
|
|
36
32
|
import { buildResponsesInput, resolveOpenAICompatPolicy } from "@oh-my-pi/pi-ai/providers/openai-shared";
|
|
37
33
|
import { isSurvivingAssistant } from "@oh-my-pi/pi-ai/providers/transform-messages";
|
|
38
34
|
import { stripOpenAIResponsesOutputOnlyStatusesForReplay } from "@oh-my-pi/pi-ai/utils";
|
|
@@ -1890,6 +1886,9 @@ export async function compact(
|
|
|
1890
1886
|
let codexBody: OpenAICodexCompactionBody | undefined;
|
|
1891
1887
|
let remoteHistory: Array<Record<string, unknown>>;
|
|
1892
1888
|
if (isCodexResponsesModel(model)) {
|
|
1889
|
+
// Load the serializer only when building a Codex compaction request, not session policy setup.
|
|
1890
|
+
const { buildTransformedCodexRequestBody } =
|
|
1891
|
+
await import("@oh-my-pi/pi-ai/providers/openai-codex-responses");
|
|
1893
1892
|
const previousCodexInput: CodexInputItem[] = [];
|
|
1894
1893
|
for (const item of previousReplacementHistory ?? []) {
|
|
1895
1894
|
if (!isCodexInputItem(item)) {
|
|
@@ -1950,12 +1949,17 @@ export async function compact(
|
|
|
1950
1949
|
? codexBody.instructions
|
|
1951
1950
|
: ""
|
|
1952
1951
|
: remoteSystemPrompt.join("\n\n");
|
|
1952
|
+
// Responses serialization is needed only for this native compaction request.
|
|
1953
1953
|
const tools = codexBody
|
|
1954
1954
|
? Array.isArray(codexBody.tools)
|
|
1955
1955
|
? codexBody.tools
|
|
1956
1956
|
: undefined
|
|
1957
1957
|
: summaryOptions.tools
|
|
1958
|
-
?
|
|
1958
|
+
? (await import("@oh-my-pi/pi-ai/providers/openai-responses")).convertTools(
|
|
1959
|
+
summaryOptions.tools,
|
|
1960
|
+
model.compat.supportsStrictMode,
|
|
1961
|
+
model,
|
|
1962
|
+
)
|
|
1959
1963
|
: undefined;
|
|
1960
1964
|
const trimmed = trimRemoteCompactionInputToContextWindow(
|
|
1961
1965
|
remoteHistory,
|
package/src/compaction/openai.ts
CHANGED
|
@@ -19,7 +19,6 @@ import { attach, create, Flag, ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
|
|
19
19
|
import { getCodexAttestationHeader } from "@oh-my-pi/pi-ai/providers/openai-codex-attestation";
|
|
20
20
|
import { createOpenAICodexCompactionRequestContext } from "@oh-my-pi/pi-ai/providers/openai-codex-compaction";
|
|
21
21
|
import { applyCodexResponsesLiteShape } from "@oh-my-pi/pi-ai/providers/openai-codex/request-transformer";
|
|
22
|
-
import { createOpenAICodexCompatibilityMetadata } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
23
22
|
import {
|
|
24
23
|
applyInferenceHeaders,
|
|
25
24
|
applySessionHeader,
|
|
@@ -950,6 +949,9 @@ export async function requestOpenAiRemoteCompaction(
|
|
|
950
949
|
|
|
951
950
|
// Codex endpoints require additional auth headers
|
|
952
951
|
if (isCodexResponses) {
|
|
952
|
+
// Keep transport loading on the actual Codex request path, not compaction policy inspection.
|
|
953
|
+
const { createOpenAICodexCompatibilityMetadata } =
|
|
954
|
+
await import("@oh-my-pi/pi-ai/providers/openai-codex-responses");
|
|
953
955
|
const accountId = getCodexAccountId(apiKey);
|
|
954
956
|
if (accountId) {
|
|
955
957
|
headers[OPENAI_HEADERS.ACCOUNT_ID] = accountId;
|
|
@@ -2,7 +2,6 @@ The conversation above is the complete history to summarize. The API replaces ev
|
|
|
2
2
|
|
|
3
3
|
{{#if extraContext}}
|
|
4
4
|
{{extraContext}}
|
|
5
|
-
|
|
6
5
|
{{/if}}
|
|
7
6
|
{{basePrompt}}
|
|
8
7
|
{{#if customInstructions}}
|
|
@@ -10,4 +9,4 @@ The conversation above is the complete history to summarize. The API replaces ev
|
|
|
10
9
|
Additional focus: {{customInstructions}}
|
|
11
10
|
{{/if}}
|
|
12
11
|
|
|
13
|
-
You MUST NOT call any tools while writing the summary; respond with the summary text only.
|
|
12
|
+
You MUST NOT call any tools while writing the summary; respond with the summary text only.
|
package/src/index.ts
CHANGED
package/src/telemetry.ts
CHANGED
|
@@ -51,7 +51,6 @@ import {
|
|
|
51
51
|
} from "@opentelemetry/api";
|
|
52
52
|
import { AgentRunCollector, type AgentRunCoverage, type AgentRunSummary, type ToolStatus } from "./run-collector";
|
|
53
53
|
import type { AgentTool } from "./types";
|
|
54
|
-
import { EventLoopKeepalive } from "./utils/yield";
|
|
55
54
|
|
|
56
55
|
/** Default tracer name. Override via {@link AgentTelemetryConfig.tracerName}. */
|
|
57
56
|
export const DEFAULT_TRACER_NAME = "@oh-my-pi/pi-agent-core";
|
|
@@ -1840,13 +1839,6 @@ export async function instrumentedCompleteSimple<TApi extends Api>(
|
|
|
1840
1839
|
options: SimpleStreamOptions,
|
|
1841
1840
|
span: InstrumentedChatSpanOptions,
|
|
1842
1841
|
): Promise<AssistantMessage> {
|
|
1843
|
-
// Oneshot LLM calls (handoff, compaction/branch summaries) run outside the
|
|
1844
|
-
// agent `#runLoop`, which is where the EventLoopKeepalive normally lives.
|
|
1845
|
-
// Without it, Bun's JSC loop stops servicing timers while parked on the
|
|
1846
|
-
// long-lived completion promise, freezing any host spinner (e.g. the
|
|
1847
|
-
// `/handoff` Loader) until an unrelated I/O event (a terminal resize)
|
|
1848
|
-
// pokes the loop. Keep the loop healthy for the duration of the call.
|
|
1849
|
-
using _keepalive = new EventLoopKeepalive();
|
|
1850
1842
|
const { telemetry, parent, oneshotKind } = span;
|
|
1851
1843
|
const stepNumber = span.stepNumber ?? -1;
|
|
1852
1844
|
const reasoning = options.reasoning;
|
package/src/types.ts
CHANGED
|
@@ -1253,6 +1253,12 @@ export interface AgentTool<
|
|
|
1253
1253
|
*/
|
|
1254
1254
|
approvalCacheTargets?: (args: unknown) => readonly string[] | undefined;
|
|
1255
1255
|
|
|
1256
|
+
/**
|
|
1257
|
+
* Paths a call reads beyond its working directory, matched against restricted folders when it
|
|
1258
|
+
* reports no `approvalCacheTargets`. Unlike cache targets they never key a remembered grant.
|
|
1259
|
+
*/
|
|
1260
|
+
approvalRestrictionPaths?: (args: unknown) => readonly string[] | undefined;
|
|
1261
|
+
|
|
1256
1262
|
/** The main execution callback for this tool. */
|
|
1257
1263
|
execute: AgentToolExecFn<TParameters, TDetails, TTheme>;
|
|
1258
1264
|
|