@linxiraos/pi-agent-core 1.1.7 → 1.1.9
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 +9 -12
- package/package.json +73 -79
- package/src/agent-loop.ts +81 -16
- package/src/compaction/compaction-v2-streaming.ts +44 -35
- package/src/compaction/compaction.ts +89 -18
- package/src/compaction/messages.ts +2 -0
- package/src/compaction/openai.ts +6 -3
- package/src/compaction/pruning.ts +1 -0
- package/src/compaction/shake.ts +1 -0
- package/src/telemetry.ts +2 -2
- package/src/types.ts +34 -7
- package/THIRD-PARTY-NOTICES.txt +0 -22909
- package/dist/types/agent-loop.d.ts +0 -150
- package/dist/types/agent.d.ts +0 -472
- package/dist/types/append-only-context.d.ts +0 -131
- package/dist/types/compaction/branch-summarization.d.ts +0 -102
- package/dist/types/compaction/compaction-v2-streaming.d.ts +0 -85
- package/dist/types/compaction/compaction.d.ts +0 -341
- package/dist/types/compaction/entries.d.ts +0 -120
- package/dist/types/compaction/errors.d.ts +0 -38
- package/dist/types/compaction/index.d.ts +0 -14
- package/dist/types/compaction/message-cache.d.ts +0 -25
- package/dist/types/compaction/messages.d.ts +0 -103
- package/dist/types/compaction/openai.d.ts +0 -118
- package/dist/types/compaction/pruning.d.ts +0 -106
- package/dist/types/compaction/shake.d.ts +0 -104
- package/dist/types/compaction/tool-protection.d.ts +0 -19
- package/dist/types/compaction/transcript-tokens.d.ts +0 -76
- package/dist/types/compaction/utils.d.ts +0 -63
- package/dist/types/compaction.d.ts +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/pause.d.ts +0 -46
- package/dist/types/proxy.d.ts +0 -91
- package/dist/types/replay-policy.d.ts +0 -5
- package/dist/types/run-collector.d.ts +0 -196
- package/dist/types/telemetry.d.ts +0 -625
- package/dist/types/thinking.d.ts +0 -18
- package/dist/types/tokenizer.d.ts +0 -78
- package/dist/types/types.d.ts +0 -786
- package/dist/types/utils/yield.d.ts +0 -71
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.1.9] - 2026-09-05
|
|
6
|
+
|
|
7
|
+
- v18.1.10 sync baseline (compaction and tool-arg stream updates).
|
|
8
|
+
|
|
9
|
+
## [1.1.8] - 2026-09-04
|
|
10
|
+
|
|
11
|
+
- OMP sync v18.1.2–v18.1.5: Agent Hub activity stream groundwork, declarative provider auth registry, and tool-roster notice plumbing shared with the session layer.
|
|
12
|
+
- Fixed the session extension rebinding path so freshly prepared extensions bind without re-evaluating the module.
|
|
13
|
+
|
|
5
14
|
## [1.1.7] - 2026-09-01
|
|
6
15
|
|
|
7
16
|
- 同步上游 OMP v18.0.11(`b8ce33a58911c26bed1d84f0db9a5e2e727c49a2`)。
|
|
@@ -15,18 +24,6 @@
|
|
|
15
24
|
|
|
16
25
|
- 同步上游 OMP v18.0.5 / v18.0.6(内部运行时与构建改进)。
|
|
17
26
|
|
|
18
|
-
## [1.1.2] - 2026-08-25
|
|
19
|
-
|
|
20
|
-
### Fixed
|
|
21
|
-
|
|
22
|
-
- Republished as 1.1.2 to reset the `latest` tag after the broken 1.1.0 (no functional change over 1.1.1).
|
|
23
|
-
|
|
24
|
-
## [1.1.1] - 2026-08-25
|
|
25
|
-
|
|
26
|
-
### Fixed
|
|
27
|
-
|
|
28
|
-
- Published tarballs now carry real dependency versions instead of Bun's `catalog:` protocol (1.1.0 installs failed with "Unsupported URL Type catalog:").
|
|
29
|
-
|
|
30
27
|
## [1.1.0] - 2026-08-25
|
|
31
28
|
|
|
32
29
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,81 +1,75 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
"./*": {
|
|
77
|
-
"types": "./dist/types/*.d.ts",
|
|
78
|
-
"import": "./src/*.ts"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
2
|
+
"exports": {
|
|
3
|
+
"./*": {
|
|
4
|
+
"import": "./src/*.ts",
|
|
5
|
+
"types": "./src/*.ts"
|
|
6
|
+
},
|
|
7
|
+
"./compaction/*": {
|
|
8
|
+
"import": "./src/compaction/*.ts",
|
|
9
|
+
"types": "./src/compaction/*.ts"
|
|
10
|
+
},
|
|
11
|
+
"./compaction": {
|
|
12
|
+
"import": "./src/compaction.ts",
|
|
13
|
+
"types": "./src/compaction.ts"
|
|
14
|
+
},
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./src/index.ts",
|
|
17
|
+
"types": "./src/index.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"bun": ">=1.3.14"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@linxiraos/pi-omptype": "1.1.9",
|
|
30
|
+
"@types/bun": "^1.3.14",
|
|
31
|
+
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
32
|
+
"@opentelemetry/context-async-hooks": "^2.9.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@linxiraos/pi-snapcompact": "1.1.9",
|
|
36
|
+
"@linxiraos/pi-wire": "1.1.9",
|
|
37
|
+
"@linxiraos/pi-utils": "1.1.9",
|
|
38
|
+
"@linxiraos/pi-natives": "1.1.9",
|
|
39
|
+
"@linxiraos/pi-catalog": "1.1.9",
|
|
40
|
+
"@linxiraos/pi-ai": "1.1.9",
|
|
41
|
+
"@opentelemetry/api": "^1.9.1"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "bun test --parallel",
|
|
45
|
+
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
46
|
+
"check": "tsgo -p tsconfig.json --noEmit"
|
|
47
|
+
},
|
|
48
|
+
"types": "./src/index.ts",
|
|
49
|
+
"main": "./src/index.ts",
|
|
50
|
+
"keywords": [
|
|
51
|
+
"ai",
|
|
52
|
+
"agent",
|
|
53
|
+
"llm",
|
|
54
|
+
"transport",
|
|
55
|
+
"state-management"
|
|
56
|
+
],
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/Linxira-OS/linxira-zeta/issues"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"directory": "packages/agent",
|
|
62
|
+
"url": "git+https://github.com/Linxira-OS/linxira-zeta.git",
|
|
63
|
+
"type": "git"
|
|
64
|
+
},
|
|
65
|
+
"license": "MIT",
|
|
66
|
+
"contributors": [
|
|
67
|
+
"Mario Zechner"
|
|
68
|
+
],
|
|
69
|
+
"author": "Can Boluk",
|
|
70
|
+
"homepage": "https://linxira-os.github.io/zeta/",
|
|
71
|
+
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
72
|
+
"version": "1.1.9",
|
|
73
|
+
"name": "@linxiraos/pi-agent-core",
|
|
74
|
+
"type": "module"
|
|
81
75
|
}
|
package/src/agent-loop.ts
CHANGED
|
@@ -73,6 +73,7 @@ import type {
|
|
|
73
73
|
AgentMessage,
|
|
74
74
|
AgentPreModelCallResult,
|
|
75
75
|
AgentTool,
|
|
76
|
+
AgentToolArgStream,
|
|
76
77
|
AgentToolCall,
|
|
77
78
|
AgentToolResult,
|
|
78
79
|
AgentTurnEndContext,
|
|
@@ -768,7 +769,7 @@ function createDetailedCapture(config: AgentLoopConfig): {
|
|
|
768
769
|
const wired: AgentLoopConfig = {
|
|
769
770
|
...config,
|
|
770
771
|
telemetry: {
|
|
771
|
-
...
|
|
772
|
+
...config.telemetry,
|
|
772
773
|
onRunEnd: (summary, coverage) => {
|
|
773
774
|
captured = { summary, coverage };
|
|
774
775
|
userHook?.(summary, coverage);
|
|
@@ -924,7 +925,10 @@ function extractIntent(args: Record<string, unknown>): { intent?: string; stripp
|
|
|
924
925
|
if (typeof intent !== "string") {
|
|
925
926
|
return { strippedArgs };
|
|
926
927
|
}
|
|
927
|
-
const trimmed = intent
|
|
928
|
+
const trimmed = intent
|
|
929
|
+
.trim()
|
|
930
|
+
.replace(/\s*\.+$/, "")
|
|
931
|
+
.trim();
|
|
928
932
|
return { intent: trimmed.length > 0 ? trimmed : undefined, strippedArgs };
|
|
929
933
|
}
|
|
930
934
|
|
|
@@ -1775,6 +1779,17 @@ async function streamAssistantResponse(
|
|
|
1775
1779
|
let partialMessage: AssistantMessage | null = null;
|
|
1776
1780
|
let addedPartial = false;
|
|
1777
1781
|
const completedToolCallIds = new Set<string>();
|
|
1782
|
+
const argStreams = new Map<number, { id: string; stream: AgentToolArgStream }>();
|
|
1783
|
+
const cancelArgStreams = (): void => {
|
|
1784
|
+
for (const { id, stream: argStream } of argStreams.values()) {
|
|
1785
|
+
try {
|
|
1786
|
+
argStream.cancel();
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
logger.debug("Tool argument stream cancel failed", { toolCallId: id, error });
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
argStreams.clear();
|
|
1792
|
+
};
|
|
1778
1793
|
|
|
1779
1794
|
const responseIterator = response[Symbol.asyncIterator]();
|
|
1780
1795
|
const finishAbortedStream = async (): Promise<AssistantMessage> => {
|
|
@@ -1888,6 +1903,52 @@ async function streamAssistantResponse(
|
|
|
1888
1903
|
// when the LLM is streaming chunks faster than the loop can rest.
|
|
1889
1904
|
await yieldIfDue();
|
|
1890
1905
|
|
|
1906
|
+
if (event.type === "toolcall_start") {
|
|
1907
|
+
const block = event.partial.content[event.contentIndex];
|
|
1908
|
+
if (block?.type === "toolCall") {
|
|
1909
|
+
const tool = resolveToolForCall(context.tools, block, config.resolveFallbackTool);
|
|
1910
|
+
if (tool?.openArgStream) {
|
|
1911
|
+
try {
|
|
1912
|
+
const argStream = tool.openArgStream({
|
|
1913
|
+
toolCallId: block.id,
|
|
1914
|
+
toolName: block.name,
|
|
1915
|
+
customWireName: block.customWireName,
|
|
1916
|
+
emit: update =>
|
|
1917
|
+
stream.push({
|
|
1918
|
+
type: "tool_stream_update",
|
|
1919
|
+
toolCallId: block.id,
|
|
1920
|
+
toolName: block.name,
|
|
1921
|
+
update,
|
|
1922
|
+
}),
|
|
1923
|
+
});
|
|
1924
|
+
if (argStream) argStreams.set(event.contentIndex, { id: block.id, stream: argStream });
|
|
1925
|
+
} catch (error) {
|
|
1926
|
+
logger.debug("Tool argument stream open failed", { toolCallId: block.id, error });
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
} else if (event.type === "toolcall_delta") {
|
|
1931
|
+
const entry = argStreams.get(event.contentIndex);
|
|
1932
|
+
if (entry) {
|
|
1933
|
+
try {
|
|
1934
|
+
entry.stream.push(event.delta);
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
logger.debug("Tool argument stream push failed", { toolCallId: entry.id, error });
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
} else if (event.type === "toolcall_end") {
|
|
1940
|
+
const entry = argStreams.get(event.contentIndex);
|
|
1941
|
+
if (entry) {
|
|
1942
|
+
try {
|
|
1943
|
+
entry.stream.end(event.toolCall.arguments);
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
logger.debug("Tool argument stream end failed", { toolCallId: entry.id, error });
|
|
1946
|
+
} finally {
|
|
1947
|
+
argStreams.delete(event.contentIndex);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1891
1952
|
switch (event.type) {
|
|
1892
1953
|
case "start":
|
|
1893
1954
|
partialMessage = event.partial;
|
|
@@ -1944,6 +2005,7 @@ async function streamAssistantResponse(
|
|
|
1944
2005
|
}
|
|
1945
2006
|
} finally {
|
|
1946
2007
|
detachAbortListener?.();
|
|
2008
|
+
cancelArgStreams();
|
|
1947
2009
|
}
|
|
1948
2010
|
|
|
1949
2011
|
let trailing = await response.result();
|
|
@@ -2440,8 +2502,9 @@ async function executeToolCalls(
|
|
|
2440
2502
|
// Queued steering hard-aborts only interruptible waits and raises the
|
|
2441
2503
|
// cooperative soft signal for everything else: the boundary dequeue
|
|
2442
2504
|
// below injects the message as soon as running tools finish (or
|
|
2443
|
-
// background themselves), and not-yet-started
|
|
2444
|
-
// Idempotent — a second steer poll after the abort is
|
|
2505
|
+
// background themselves), and not-yet-started interruptible waits
|
|
2506
|
+
// are skipped. Idempotent — a second steer poll after the abort is
|
|
2507
|
+
// a no-op.
|
|
2445
2508
|
if (!steeringAbortController.signal.aborted) {
|
|
2446
2509
|
interruptState.triggered = true;
|
|
2447
2510
|
interruptState.source = steeringSource ?? "unknown";
|
|
@@ -2495,16 +2558,18 @@ async function executeToolCalls(
|
|
|
2495
2558
|
};
|
|
2496
2559
|
|
|
2497
2560
|
const runTool = async (record: (typeof records)[number], index: number): Promise<void> => {
|
|
2498
|
-
// A pending interrupt preempts not-yet-started
|
|
2499
|
-
// injects promptly
|
|
2500
|
-
// interruptible
|
|
2501
|
-
//
|
|
2502
|
-
//
|
|
2503
|
-
//
|
|
2504
|
-
// `todo`/`write`
|
|
2505
|
-
//
|
|
2506
|
-
// still
|
|
2507
|
-
|
|
2561
|
+
// A pending interrupt preempts not-yet-started *interruptible* waits so
|
|
2562
|
+
// the message injects promptly instead of sitting out a `hub wait`.
|
|
2563
|
+
// Non-interruptible work is never skipped, whatever the source: the
|
|
2564
|
+
// expensive part — generating the call — is already paid, the tool
|
|
2565
|
+
// itself is cheap, and a skip only makes the model re-emit the same
|
|
2566
|
+
// call after the steer lands (#10439). The same guarantee is what keeps
|
|
2567
|
+
// a batched `todo`/`write` queued behind an aborted wait alive (#7493)
|
|
2568
|
+
// and lets a subagent's already-emitted terminal `yield` commit when
|
|
2569
|
+
// the parent steers mid-stream (#10645). The steer still injects at the
|
|
2570
|
+
// batch boundary; the cooperative soft signal lets long-running tools
|
|
2571
|
+
// step aside on their own.
|
|
2572
|
+
if (interruptState.triggered && record.interruptible) {
|
|
2508
2573
|
// Skip both span emission and the collector orphan record here. The
|
|
2509
2574
|
// tail sweep below (after `Promise.allSettled`) is the single path
|
|
2510
2575
|
// that handles "no result message was produced" — it calls
|
|
@@ -2719,8 +2784,8 @@ async function executeToolCalls(
|
|
|
2719
2784
|
|
|
2720
2785
|
// While tool calls are in flight, queued steering or interrupting IRC would
|
|
2721
2786
|
// otherwise wait out the tools' own window. Poll only non-consuming queues:
|
|
2722
|
-
// detection hard-aborts interruptible waits
|
|
2723
|
-
// (auto-background bash),
|
|
2787
|
+
// detection hard-aborts interruptible waits (running or not yet started)
|
|
2788
|
+
// and soft-signals cooperative tools (auto-background bash), so the boundary
|
|
2724
2789
|
// dequeue below injects the message promptly. Gated on immediate-interrupt
|
|
2725
2790
|
// mode; checkSteering is idempotent (no-op once triggered).
|
|
2726
2791
|
const watchSteeringWhileRunning =
|
|
@@ -27,6 +27,7 @@ import { captureOpenAIHttpError } from "@linxiraos/pi-ai/utils/openai-http";
|
|
|
27
27
|
import {
|
|
28
28
|
applyCodexResidencyHeader,
|
|
29
29
|
CODEX_BASE_URL,
|
|
30
|
+
codexRoutingHint,
|
|
30
31
|
getCodexAccountId,
|
|
31
32
|
OPENAI_HEADER_VALUES,
|
|
32
33
|
OPENAI_HEADERS,
|
|
@@ -70,15 +71,11 @@ export interface CompactionV2Usage {
|
|
|
70
71
|
reasoningOutputTokens?: number;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
/**
|
|
74
|
+
/** Provider-ready Responses body and local state needed for V2 compaction. */
|
|
74
75
|
export interface CompactionV2Request {
|
|
75
|
-
|
|
76
|
+
body: OpenAICodexCompactionBody;
|
|
76
77
|
input: unknown[];
|
|
77
|
-
instructions: string;
|
|
78
78
|
retainedMessageBudget: number;
|
|
79
|
-
tools?: unknown[];
|
|
80
|
-
/** Responses reasoning param (effort + summary), matching a normal turn; omitted for non-reasoning models. */
|
|
81
|
-
reasoning?: { effort: string; summary: string };
|
|
82
79
|
sessionId?: string;
|
|
83
80
|
promptCacheKey?: string;
|
|
84
81
|
}
|
|
@@ -204,13 +201,44 @@ export function buildCompactionV2Request(
|
|
|
204
201
|
retainedMessageBudget?: number;
|
|
205
202
|
},
|
|
206
203
|
): CompactionV2Request {
|
|
207
|
-
|
|
204
|
+
const cacheOptions = { sessionId: options?.sessionId, promptCacheKey: options?.promptCacheKey };
|
|
205
|
+
const promptCacheKey = getOpenAIPromptCacheKey(cacheOptions);
|
|
206
|
+
const body: OpenAICodexCompactionBody = {
|
|
208
207
|
model: resolveCompactionV2Model(model),
|
|
209
208
|
input,
|
|
210
209
|
instructions,
|
|
210
|
+
stream: true,
|
|
211
|
+
store: false,
|
|
212
|
+
...(options?.reasoning || model.useResponsesLite
|
|
213
|
+
? {
|
|
214
|
+
reasoning: model.useResponsesLite ? { ...options?.reasoning, context: "all_turns" } : options?.reasoning,
|
|
215
|
+
include: ["reasoning.encrypted_content"],
|
|
216
|
+
}
|
|
217
|
+
: {}),
|
|
218
|
+
...(promptCacheKey ? { prompt_cache_key: promptCacheKey } : {}),
|
|
219
|
+
...(options?.tools && options.tools.length > 0 ? { tools: options.tools, tool_choice: "auto" } : {}),
|
|
220
|
+
};
|
|
221
|
+
if (model.useResponsesLite) {
|
|
222
|
+
applyCodexResponsesLiteShape(body);
|
|
223
|
+
}
|
|
224
|
+
return buildCompactionV2RequestFromBody(model, body, options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Wrap a body built by the normal Codex serializer for V2 compaction transport. */
|
|
228
|
+
export function buildCompactionV2RequestFromBody(
|
|
229
|
+
model: Model,
|
|
230
|
+
body: OpenAICodexCompactionBody,
|
|
231
|
+
options?: {
|
|
232
|
+
sessionId?: string;
|
|
233
|
+
promptCacheKey?: string;
|
|
234
|
+
retainedMessageBudget?: number;
|
|
235
|
+
},
|
|
236
|
+
): CompactionV2Request {
|
|
237
|
+
const input = Array.isArray(body.input) ? body.input : [];
|
|
238
|
+
return {
|
|
239
|
+
body: { ...body, model: resolveCompactionV2Model(model), input },
|
|
240
|
+
input,
|
|
211
241
|
retainedMessageBudget: resolveCompactionV2RetainedMessageBudget(options?.retainedMessageBudget),
|
|
212
|
-
reasoning: options?.reasoning,
|
|
213
|
-
tools: options?.tools,
|
|
214
242
|
sessionId: options?.sessionId,
|
|
215
243
|
promptCacheKey: options?.promptCacheKey,
|
|
216
244
|
};
|
|
@@ -311,37 +339,17 @@ async function attemptCompactionV2Streaming(
|
|
|
311
339
|
},
|
|
312
340
|
): Promise<CompactionV2Response> {
|
|
313
341
|
// Faithful to Codex: append the compaction trigger as the final input item
|
|
314
|
-
// of an otherwise-normal Responses request
|
|
315
|
-
//
|
|
316
|
-
const cacheOptions = { sessionId: request.sessionId, promptCacheKey: request.promptCacheKey };
|
|
317
|
-
const promptCacheKey = getOpenAIPromptCacheKey(cacheOptions);
|
|
342
|
+
// of an otherwise-normal Responses request. `store` remains false —
|
|
343
|
+
// compaction must never persist a server-side response object.
|
|
318
344
|
const body: OpenAICodexCompactionBody = {
|
|
319
|
-
|
|
345
|
+
...request.body,
|
|
320
346
|
input: [...request.input, COMPACTION_TRIGGER_ITEM],
|
|
321
|
-
instructions: request.instructions,
|
|
322
|
-
stream: true,
|
|
323
347
|
store: false,
|
|
324
|
-
|
|
325
|
-
? {
|
|
326
|
-
// Lite implies gpt-5.4+, where codex-rs sends `all_turns` replay.
|
|
327
|
-
reasoning: model.useResponsesLite
|
|
328
|
-
? { ...(request.reasoning ?? {}), context: "all_turns" }
|
|
329
|
-
: request.reasoning,
|
|
330
|
-
include: ["reasoning.encrypted_content"],
|
|
331
|
-
}
|
|
332
|
-
: {}),
|
|
333
|
-
...(promptCacheKey ? { prompt_cache_key: promptCacheKey } : {}),
|
|
334
|
-
...(request.tools && request.tools.length > 0 ? { tools: request.tools, tool_choice: "auto" } : {}),
|
|
348
|
+
stream: true,
|
|
335
349
|
};
|
|
336
350
|
if (options.codexMetadata) {
|
|
337
351
|
body.client_metadata = options.codexMetadata.clientMetadata;
|
|
338
352
|
}
|
|
339
|
-
// Responses Lite models take the same rewrite on the compaction stream:
|
|
340
|
-
// instructions/tools ride as input items (codex-rs `compact_remote_v2`
|
|
341
|
-
// builds through `build_responses_request`).
|
|
342
|
-
if (model.useResponsesLite) {
|
|
343
|
-
applyCodexResponsesLiteShape(body);
|
|
344
|
-
}
|
|
345
353
|
|
|
346
354
|
if (shouldUseCodexProviderTransport(model)) {
|
|
347
355
|
const eventStream = await openCodexCompactionEventStream(model, body, {
|
|
@@ -403,13 +411,13 @@ function buildCompactionV2Headers(
|
|
|
403
411
|
? {
|
|
404
412
|
"content-type": "application/json",
|
|
405
413
|
"api-key": apiKey,
|
|
406
|
-
...
|
|
414
|
+
...model.headers,
|
|
407
415
|
}
|
|
408
416
|
: {
|
|
409
417
|
"content-type": "application/json",
|
|
410
418
|
...resolveOpenAIRequestSetup(
|
|
411
419
|
{ provider: model.provider, id: model.id, baseUrl: model.baseUrl, headers: model.headers },
|
|
412
|
-
{ apiKey, messages: [],
|
|
420
|
+
{ apiKey, messages: [], sessionId: request.sessionId ?? routingSessionId, promptCacheSessionId },
|
|
413
421
|
).headers,
|
|
414
422
|
};
|
|
415
423
|
if (api === "openai-codex-responses" || model.provider === "openai-codex") {
|
|
@@ -426,6 +434,7 @@ function buildCompactionV2Headers(
|
|
|
426
434
|
headers[OPENAI_HEADERS.BETA] = OPENAI_HEADER_VALUES.BETA_RESPONSES;
|
|
427
435
|
headers[OPENAI_HEADERS.ORIGINATOR] = OPENAI_HEADER_VALUES.ORIGINATOR_CODEX;
|
|
428
436
|
headers[OPENAI_HEADERS.CODEX_BETA_FEATURES] = OPENAI_HEADER_VALUES.REMOTE_COMPACTION_V2;
|
|
437
|
+
headers[OPENAI_HEADERS.ROUTING_HINT] = codexRoutingHint(request.body.model, undefined);
|
|
429
438
|
if (model.useResponsesLite) {
|
|
430
439
|
headers[OPENAI_HEADERS.RESPONSES_LITE] = "true";
|
|
431
440
|
}
|
|
@@ -25,7 +25,12 @@ import {
|
|
|
25
25
|
} from "@linxiraos/pi-ai";
|
|
26
26
|
import type { Dialect } from "@linxiraos/pi-ai/dialect";
|
|
27
27
|
import * as AIError from "@linxiraos/pi-ai/error";
|
|
28
|
-
import {
|
|
28
|
+
import type { InputItem as CodexInputItem } from "@linxiraos/pi-ai/providers/openai-codex/request-transformer";
|
|
29
|
+
import {
|
|
30
|
+
buildTransformedCodexRequestBody,
|
|
31
|
+
createOpenAICodexCompactionRequestContext,
|
|
32
|
+
type OpenAICodexCompactionBody,
|
|
33
|
+
} from "@linxiraos/pi-ai/providers/openai-codex-responses";
|
|
29
34
|
import { convertTools } from "@linxiraos/pi-ai/providers/openai-responses";
|
|
30
35
|
import { buildResponsesInput, resolveOpenAICompatPolicy } from "@linxiraos/pi-ai/providers/openai-shared";
|
|
31
36
|
import { stripOpenAIResponsesOutputOnlyStatusesForReplay } from "@linxiraos/pi-ai/utils";
|
|
@@ -39,6 +44,7 @@ import { Tokenizer } from "../tokenizer";
|
|
|
39
44
|
import type { AgentMessage } from "../types";
|
|
40
45
|
import {
|
|
41
46
|
buildCompactionV2Request,
|
|
47
|
+
buildCompactionV2RequestFromBody,
|
|
42
48
|
getCompactionV2PreserveData,
|
|
43
49
|
requestCompactionV2Streaming,
|
|
44
50
|
shouldUseCompactionV2Streaming,
|
|
@@ -660,7 +666,8 @@ export interface SummaryOptions {
|
|
|
660
666
|
promptOverride?: string;
|
|
661
667
|
extraContext?: string[];
|
|
662
668
|
remoteEndpoint?: string;
|
|
663
|
-
|
|
669
|
+
/** Stable system-prompt segments from the live turn, preserved for provider cache reuse. */
|
|
670
|
+
remoteSystemPrompt?: string[];
|
|
664
671
|
initiatorOverride?: MessageAttribution;
|
|
665
672
|
metadata?: Record<string, unknown>;
|
|
666
673
|
convertToLlm?: ConvertToLlm;
|
|
@@ -1433,6 +1440,19 @@ export function prepareCompaction(
|
|
|
1433
1440
|
// ============================================================================
|
|
1434
1441
|
|
|
1435
1442
|
const TURN_PREFIX_SUMMARIZATION_PROMPT = prompt.render(compactionTurnPrefixPrompt);
|
|
1443
|
+
function isCodexResponsesModel(model: Model): model is Model<"openai-codex-responses"> {
|
|
1444
|
+
return model.api === "openai-codex-responses";
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function isCodexInputItem(item: Record<string, unknown>): item is CodexInputItem & Record<string, unknown> {
|
|
1448
|
+
return (
|
|
1449
|
+
(item.id === undefined || item.id === null || typeof item.id === "string") &&
|
|
1450
|
+
(item.type === undefined || item.type === null || typeof item.type === "string") &&
|
|
1451
|
+
(item.role === undefined || typeof item.role === "string") &&
|
|
1452
|
+
(item.call_id === undefined || item.call_id === null || typeof item.call_id === "string") &&
|
|
1453
|
+
(item.name === undefined || typeof item.name === "string")
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1436
1456
|
|
|
1437
1457
|
function openAiCompatSupportsImageDetailOriginal(model: Model): boolean {
|
|
1438
1458
|
const compat = model.compat;
|
|
@@ -1546,7 +1566,7 @@ export async function compact(
|
|
|
1546
1566
|
promptOverride: options?.promptOverride,
|
|
1547
1567
|
extraContext: options?.extraContext,
|
|
1548
1568
|
remoteEndpoint: settings.remoteEnabled === false ? undefined : settings.remoteEndpoint,
|
|
1549
|
-
|
|
1569
|
+
remoteSystemPrompt: options?.remoteSystemPrompt,
|
|
1550
1570
|
initiatorOverride: options?.initiatorOverride,
|
|
1551
1571
|
metadata: options?.metadata,
|
|
1552
1572
|
convertToLlm: options?.convertToLlm,
|
|
@@ -1598,17 +1618,63 @@ export async function compact(
|
|
|
1598
1618
|
previousRemoteCompaction?.provider === model.provider
|
|
1599
1619
|
? previousRemoteCompaction.replacementHistory
|
|
1600
1620
|
: undefined;
|
|
1601
|
-
const
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
)
|
|
1621
|
+
const messages = (summaryOptions.convertToLlm ?? defaultConvertToLlm)(remoteMessages);
|
|
1622
|
+
const remoteSystemPrompt = summaryOptions.remoteSystemPrompt ?? [SUMMARIZATION_SYSTEM_PROMPT];
|
|
1623
|
+
let codexBody: OpenAICodexCompactionBody | undefined;
|
|
1624
|
+
let remoteHistory: Array<Record<string, unknown>>;
|
|
1625
|
+
if (isCodexResponsesModel(model)) {
|
|
1626
|
+
const previousCodexInput: CodexInputItem[] = [];
|
|
1627
|
+
for (const item of previousReplacementHistory ?? []) {
|
|
1628
|
+
if (!isCodexInputItem(item)) {
|
|
1629
|
+
throw new Error("Stored Codex V2 compaction history contains an invalid input item");
|
|
1630
|
+
}
|
|
1631
|
+
previousCodexInput.push(item);
|
|
1632
|
+
}
|
|
1633
|
+
codexBody = await buildTransformedCodexRequestBody(
|
|
1634
|
+
model,
|
|
1635
|
+
{ systemPrompt: remoteSystemPrompt, messages, tools: summaryOptions.tools },
|
|
1636
|
+
{
|
|
1637
|
+
reasoning: resolveCompactionEffort(model, summaryOptions.thinkingLevel),
|
|
1638
|
+
forceReasoningOff: summaryOptions.thinkingLevel === ThinkingLevel.Off,
|
|
1639
|
+
responsesLite: model.useResponsesLite,
|
|
1640
|
+
sessionId: summaryOptions.sessionId,
|
|
1641
|
+
promptCacheKey: summaryOptions.promptCacheKey,
|
|
1642
|
+
providerSessionState: summaryOptions.providerSessionState,
|
|
1643
|
+
codexCompaction: createOpenAICodexCompactionRequestContext({
|
|
1644
|
+
context: summaryOptions.codexCompaction,
|
|
1645
|
+
implementation: "responses_compaction_v2",
|
|
1646
|
+
}),
|
|
1647
|
+
},
|
|
1648
|
+
undefined,
|
|
1649
|
+
previousCodexInput,
|
|
1650
|
+
);
|
|
1651
|
+
const input = Array.isArray(codexBody.input) ? codexBody.input : [];
|
|
1652
|
+
const nativeInput: Array<Record<string, unknown>> = [];
|
|
1653
|
+
for (const item of input) {
|
|
1654
|
+
if (!isRecord(item)) {
|
|
1655
|
+
throw new Error("Codex V2 compaction input contains a non-object item");
|
|
1656
|
+
}
|
|
1657
|
+
nativeInput.push(item);
|
|
1658
|
+
}
|
|
1659
|
+
remoteHistory = stripOpenAIResponsesOutputOnlyStatusesForReplay(nativeInput);
|
|
1660
|
+
codexBody.input = remoteHistory;
|
|
1661
|
+
} else {
|
|
1662
|
+
remoteHistory = buildOpenAiResponsesCompactionInput(messages, model, previousReplacementHistory);
|
|
1663
|
+
}
|
|
1606
1664
|
if (remoteHistory.length > 0) {
|
|
1607
1665
|
try {
|
|
1608
|
-
const instructions =
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1666
|
+
const instructions = codexBody
|
|
1667
|
+
? typeof codexBody.instructions === "string"
|
|
1668
|
+
? codexBody.instructions
|
|
1669
|
+
: ""
|
|
1670
|
+
: remoteSystemPrompt.join("\n\n");
|
|
1671
|
+
const tools = codexBody
|
|
1672
|
+
? Array.isArray(codexBody.tools)
|
|
1673
|
+
? codexBody.tools
|
|
1674
|
+
: undefined
|
|
1675
|
+
: summaryOptions.tools
|
|
1676
|
+
? convertTools(summaryOptions.tools, model.compat.supportsStrictMode, model)
|
|
1677
|
+
: undefined;
|
|
1612
1678
|
const trimmed = trimRemoteCompactionInputToContextWindow(
|
|
1613
1679
|
remoteHistory,
|
|
1614
1680
|
new Tokenizer(model),
|
|
@@ -1626,13 +1692,18 @@ export async function compact(
|
|
|
1626
1692
|
contextWindow: model.contextWindow,
|
|
1627
1693
|
});
|
|
1628
1694
|
}
|
|
1629
|
-
const
|
|
1630
|
-
tools,
|
|
1631
|
-
reasoning: buildCompactionV2Reasoning(model, summaryOptions.thinkingLevel),
|
|
1695
|
+
const requestOptions = {
|
|
1632
1696
|
sessionId: summaryOptions.sessionId,
|
|
1633
1697
|
promptCacheKey: summaryOptions.promptCacheKey,
|
|
1634
1698
|
retainedMessageBudget: settings.v2RetainedMessageBudget,
|
|
1635
|
-
}
|
|
1699
|
+
};
|
|
1700
|
+
const request = codexBody
|
|
1701
|
+
? buildCompactionV2RequestFromBody(model, { ...codexBody, input: trimmed.input }, requestOptions)
|
|
1702
|
+
: buildCompactionV2Request(model, trimmed.input, instructions, {
|
|
1703
|
+
...requestOptions,
|
|
1704
|
+
tools,
|
|
1705
|
+
reasoning: buildCompactionV2Reasoning(model, summaryOptions.thinkingLevel),
|
|
1706
|
+
});
|
|
1636
1707
|
const remote = await withAuth(
|
|
1637
1708
|
apiKey,
|
|
1638
1709
|
key =>
|
|
@@ -1644,7 +1715,7 @@ export async function compact(
|
|
|
1644
1715
|
}),
|
|
1645
1716
|
{ signal },
|
|
1646
1717
|
);
|
|
1647
|
-
preserveData = { ...
|
|
1718
|
+
preserveData = { ...preserveData, ...storeCompactionV2PreserveData(remote, model) };
|
|
1648
1719
|
usedRemoteCompaction = true;
|
|
1649
1720
|
} catch (err) {
|
|
1650
1721
|
// A user/session abort is a cancellation, not a remote failure —
|
|
@@ -1685,7 +1756,7 @@ export async function compact(
|
|
|
1685
1756
|
model,
|
|
1686
1757
|
key,
|
|
1687
1758
|
remoteHistory,
|
|
1688
|
-
summaryOptions.
|
|
1759
|
+
summaryOptions.remoteSystemPrompt?.join("\n\n") ?? SUMMARIZATION_SYSTEM_PROMPT,
|
|
1689
1760
|
signal,
|
|
1690
1761
|
{
|
|
1691
1762
|
fetch: summaryOptions.fetch,
|