@oai404iao/pi-codex-minimal-tools 1.4.0 → 2.0.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.
Files changed (99) hide show
  1. package/README.md +44 -17
  2. package/THIRD_PARTY_NOTICES.md +20 -0
  3. package/config.schema.json +1 -2
  4. package/index.ts +2 -0
  5. package/package.json +14 -13
  6. package/provenance/openai-codex-ddea03ad-astra.json +54 -0
  7. package/src/activation.ts +1 -56
  8. package/src/adapter/compaction/checkpoint.ts +1 -0
  9. package/src/adapter/compaction/collect.ts +1 -0
  10. package/src/adapter/compaction/http.ts +1 -0
  11. package/src/adapter/compaction/request.ts +1 -0
  12. package/src/adapter/compaction/transport.ts +1 -0
  13. package/src/adapter/compaction/websocket.ts +1 -0
  14. package/src/background-image-generation.ts +1 -574
  15. package/src/capabilities.ts +1 -146
  16. package/src/codex-http.ts +1 -133
  17. package/src/codex-identity-extension.ts +1 -339
  18. package/src/codex-request-profile.ts +1 -45
  19. package/src/codex-reserved-tools.ts +1 -323
  20. package/src/codex-wire-identity.ts +1 -596
  21. package/src/extension/prewarm-snapshot.ts +1 -0
  22. package/src/extension/provider-presentation.ts +1 -0
  23. package/src/extension/provider-runtime.ts +1 -0
  24. package/src/extension/register.ts +36 -0
  25. package/src/extension/startup-prewarm.ts +1 -0
  26. package/src/fast-mode.ts +1 -124
  27. package/src/glyphs.ts +1 -70
  28. package/src/index.ts +8 -331
  29. package/src/model-catalog/catalog.ts +1 -636
  30. package/src/model-catalog/default-models.json +8 -0
  31. package/src/model-catalog/runtime.ts +1 -113
  32. package/src/model-catalog/types.ts +1 -95
  33. package/src/native-compaction.ts +1 -393
  34. package/src/patch/apply.ts +1 -338
  35. package/src/patch/parser.ts +1 -224
  36. package/src/patch/render.ts +1 -201
  37. package/src/provider-headers.ts +1 -54
  38. package/src/provider-native-tools.ts +1 -71
  39. package/src/provider-shim.ts +20 -4511
  40. package/src/providers/codex-apply-patch-tool.ts +1 -23
  41. package/src/providers/openai-codex/cache-key.ts +1 -0
  42. package/src/providers/openai-codex/captured-stream.ts +1 -0
  43. package/src/providers/openai-codex/constants.ts +1 -0
  44. package/src/providers/openai-codex/continuation.ts +1 -0
  45. package/src/providers/openai-codex/errors.ts +1 -0
  46. package/src/providers/openai-codex/events.ts +1 -0
  47. package/src/providers/openai-codex/headers.ts +1 -0
  48. package/src/providers/openai-codex/lite.ts +1 -0
  49. package/src/providers/openai-codex/message.ts +1 -0
  50. package/src/providers/openai-codex/prewarm.ts +1 -0
  51. package/src/providers/openai-codex/proxy.ts +1 -0
  52. package/src/providers/openai-codex/reasoning.ts +1 -0
  53. package/src/providers/openai-codex/request-body.ts +1 -0
  54. package/src/providers/openai-codex/request-context.ts +1 -0
  55. package/src/providers/openai-codex/request-metadata.ts +1 -0
  56. package/src/providers/openai-codex/retry.ts +1 -0
  57. package/src/providers/openai-codex/runtime.ts +1 -0
  58. package/src/providers/openai-codex/sse.ts +1 -0
  59. package/src/providers/openai-codex/stream-effects.ts +1 -0
  60. package/src/providers/openai-codex/stream.ts +1 -0
  61. package/src/providers/openai-codex/types.ts +1 -0
  62. package/src/providers/openai-codex/urls.ts +1 -0
  63. package/src/providers/openai-codex/usage.ts +1 -0
  64. package/src/providers/openai-codex/websocket-connection.ts +1 -0
  65. package/src/providers/openai-codex/websocket-events.ts +1 -0
  66. package/src/providers/openai-codex/websocket-session.ts +1 -0
  67. package/src/providers/openai-codex/websocket-socket.ts +1 -0
  68. package/src/providers/openai-codex/websocket-stream.ts +1 -0
  69. package/src/providers/openai-responses-shared.ts +7 -1463
  70. package/src/providers/responses/citations.ts +1 -0
  71. package/src/providers/responses/history.ts +1 -0
  72. package/src/providers/responses/items.ts +1 -0
  73. package/src/providers/responses/markdown.ts +1 -0
  74. package/src/providers/responses/messages.ts +1 -0
  75. package/src/providers/responses/signatures.ts +1 -0
  76. package/src/providers/responses/stream-state.ts +1 -0
  77. package/src/providers/responses/stream.ts +1 -0
  78. package/src/providers/responses/text-renderer.ts +1 -0
  79. package/src/providers/responses/text.ts +1 -0
  80. package/src/providers/responses/tool-identity.ts +1 -0
  81. package/src/providers/responses/tools.ts +1 -0
  82. package/src/providers/responses/types.ts +1 -0
  83. package/src/providers/responses/usage.ts +1 -0
  84. package/src/settings.ts +1 -247
  85. package/src/subagent-inline.ts +1 -8
  86. package/src/tools/apply-patch.ts +1 -84
  87. package/src/tools/image-generation/capture.ts +1 -0
  88. package/src/tools/image-generation/display.ts +1 -0
  89. package/src/tools/image-generation/preview.ts +1 -0
  90. package/src/tools/image-generation/storage.ts +1 -0
  91. package/src/tools/image-generation/types.ts +1 -0
  92. package/src/tools/image-generation.ts +1 -274
  93. package/src/tools/view-image.ts +1 -98
  94. package/src/tools/web-search/activity.ts +1 -0
  95. package/src/tools/web-search/capture.ts +1 -0
  96. package/src/tools/web-search/render.ts +1 -0
  97. package/src/tools/web-search.ts +1 -554
  98. package/src/utils/images.ts +1 -73
  99. package/src/utils/theme.ts +1 -0
@@ -1,113 +1 @@
1
- import type { CodexRequestProfileOverride } from "../codex-request-profile.js";
2
- import {
3
- loadSettings,
4
- type CodexMinimalToolsSettings,
5
- } from "../settings.js";
6
- import { resolveModelProfile } from "./catalog.js";
7
- import type {
8
- ModelIdentityLike,
9
- ResolvedModelProfile,
10
- ResponsesEndpoint,
11
- } from "./types.js";
12
-
13
- export interface ResolvedCodexModelSettings extends CodexMinimalToolsSettings {
14
- modelProfile?: ResolvedModelProfile;
15
- modelProfileHash?: string;
16
- providerShimActive?: boolean;
17
- webSearchImplementation?: "hosted" | "standalone";
18
- imageGenerationImplementation?: "hosted" | "standalone";
19
- fastServiceTier?: string;
20
- fastCostMultiplier?: number;
21
- }
22
-
23
- function endpointUsesApiKey(endpoint: ResponsesEndpoint, model: ModelIdentityLike | undefined): boolean {
24
- if (endpoint === "openai") return true;
25
- if (endpoint === "codex") return false;
26
- return model?.provider !== "openai-codex";
27
- }
28
-
29
- export function supportsCodexResponsesApi(model: ModelIdentityLike | undefined): boolean {
30
- if (model?.api === "openai-responses" || model?.api === "openai-codex-responses") return true;
31
- if (model?.api) return false;
32
- return model?.provider === "openai" || model?.provider === "openai-codex";
33
- }
34
-
35
- export function loadModelSettings(
36
- model: ModelIdentityLike | undefined,
37
- cwd?: string,
38
- baseSettings = loadSettings(cwd),
39
- ): ResolvedCodexModelSettings {
40
- const modelProfile = resolveModelProfile(model, { settings: baseSettings });
41
- if (!modelProfile || !modelProfile.effective.enabled) {
42
- return {
43
- ...baseSettings,
44
- providerShimActive: false,
45
- nativeProviderTools: false,
46
- openaiTransport: "sse",
47
- openaiWebSocketPrewarm: false,
48
- compactionMode: "pi",
49
- requestProfile: {
50
- responsesMode: "standard",
51
- reasoningSummary: "auto",
52
- systemPromptPlacement: "instructions",
53
- patchTransport: "function",
54
- supportsHostedTools: false,
55
- supportsParallelTools: true,
56
- },
57
- apiKeyMode: model?.provider !== "openai-codex",
58
- imageGeneration: false,
59
- webSearchEnabled: false,
60
- viewImage: false,
61
- applyPatchEnabled: false,
62
- };
63
- }
64
-
65
- const effective = modelProfile.effective;
66
- const packageEnabled = baseSettings.enabled;
67
- const providerShimActive = packageEnabled
68
- && effective.responses.providerShim
69
- && supportsCodexResponsesApi(model);
70
- const configuredWebSearchImplementation = effective.tools.webSearch
71
- ? effective.tools.webSearch.implementation
72
- : undefined;
73
- const configuredImageGenerationImplementation = effective.tools.imageGeneration || undefined;
74
- const webSearchImplementation = !packageEnabled
75
- || (configuredWebSearchImplementation === "hosted" && !providerShimActive)
76
- ? undefined
77
- : configuredWebSearchImplementation;
78
- const imageGenerationImplementation = !packageEnabled
79
- || (configuredImageGenerationImplementation === "hosted" && !providerShimActive)
80
- ? undefined
81
- : configuredImageGenerationImplementation;
82
- const supportsHostedTools = webSearchImplementation === "hosted"
83
- || imageGenerationImplementation === "hosted";
84
- const usesProviderToolRewrite = Boolean(webSearchImplementation || imageGenerationImplementation);
85
- const requestProfile: CodexRequestProfileOverride = {
86
- responsesMode: effective.responses.mode,
87
- reasoningSummary: effective.responses.reasoningSummary,
88
- systemPromptPlacement: effective.responses.systemPromptPlacement,
89
- patchTransport: effective.tools.applyPatch === "custom" ? "custom" : "function",
90
- supportsHostedTools,
91
- supportsParallelTools: effective.tools.parallelCalls,
92
- };
93
- return {
94
- ...baseSettings,
95
- providerShimActive,
96
- nativeProviderTools: providerShimActive && usesProviderToolRewrite,
97
- openaiTransport: effective.responses.transport,
98
- openaiWebSocketPrewarm: effective.responses.websocketPrewarm,
99
- compactionMode: providerShimActive ? effective.compaction : "pi",
100
- requestProfile,
101
- apiKeyMode: endpointUsesApiKey(effective.responses.endpoint, model),
102
- imageGeneration: imageGenerationImplementation !== undefined,
103
- webSearchEnabled: webSearchImplementation !== undefined,
104
- viewImage: packageEnabled && effective.tools.viewImage,
105
- applyPatchEnabled: packageEnabled && effective.tools.applyPatch !== false,
106
- modelProfile,
107
- modelProfileHash: modelProfile.profileHash,
108
- webSearchImplementation,
109
- imageGenerationImplementation,
110
- fastServiceTier: providerShimActive && effective.fast ? effective.fast.serviceTier : undefined,
111
- fastCostMultiplier: providerShimActive && effective.fast ? effective.fast.costMultiplier : undefined,
112
- };
113
- }
1
+ export * from "@oai404iao/pi-codex-runtime/internal/model-catalog/runtime";
@@ -1,95 +1 @@
1
- export type ResponsesEndpoint = "auto" | "openai" | "codex";
2
- export type ResponsesMode = "standard" | "lite";
3
- export type ReasoningSummary = "auto" | "concise" | "detailed" | "none";
4
- export type ResponsesTransport = "sse" | "websocket" | "websocket-cached" | "auto";
5
- export type SystemPromptPlacement = "instructions" | "developer";
6
- export type PatchTransport = "function" | "custom";
7
- export type WebSearchContentType = "text" | "image";
8
- export type NativeCompactionMode = "pi" | "responses" | "responses-compact";
9
-
10
- export interface ModelIdentityLike {
11
- provider?: string;
12
- id?: string;
13
- name?: string;
14
- api?: string;
15
- }
16
-
17
- export interface ResponsesProfilePatch {
18
- providerShim?: boolean;
19
- endpoint?: ResponsesEndpoint;
20
- mode?: ResponsesMode;
21
- reasoningSummary?: ReasoningSummary;
22
- systemPromptPlacement?: SystemPromptPlacement;
23
- transport?: ResponsesTransport;
24
- websocketPrewarm?: boolean;
25
- }
26
-
27
- export interface WebSearchProfile {
28
- implementation: "hosted" | "standalone";
29
- contentTypes?: WebSearchContentType[];
30
- }
31
-
32
- export interface ModelToolsProfilePatch {
33
- parallelCalls?: boolean;
34
- applyPatch?: false | PatchTransport;
35
- webSearch?: false | WebSearchProfile;
36
- imageGeneration?: false | "hosted" | "standalone";
37
- viewImage?: boolean;
38
- }
39
-
40
- export interface FastModeProfile {
41
- serviceTier: string;
42
- costMultiplier?: number;
43
- }
44
-
45
- export interface ModelProfilePatch {
46
- id: string;
47
- extends?: string;
48
- enabled?: boolean;
49
- responses?: ResponsesProfilePatch;
50
- tools?: ModelToolsProfilePatch;
51
- compaction?: NativeCompactionMode;
52
- fast?: false | FastModeProfile;
53
- }
54
-
55
- export interface ModelCatalogFile {
56
- $schema?: string;
57
- version: 1;
58
- models: ModelProfilePatch[];
59
- }
60
-
61
- export interface EffectiveResponsesProfile {
62
- providerShim: boolean;
63
- endpoint: ResponsesEndpoint;
64
- mode: ResponsesMode;
65
- reasoningSummary: ReasoningSummary;
66
- systemPromptPlacement: SystemPromptPlacement;
67
- transport: ResponsesTransport;
68
- websocketPrewarm: boolean;
69
- }
70
-
71
- export interface EffectiveModelToolsProfile {
72
- parallelCalls: boolean;
73
- applyPatch: false | PatchTransport;
74
- webSearch: false | WebSearchProfile;
75
- imageGeneration: false | "hosted" | "standalone";
76
- viewImage: boolean;
77
- }
78
-
79
- export interface EffectiveModelProfile {
80
- enabled: boolean;
81
- responses: EffectiveResponsesProfile;
82
- tools: EffectiveModelToolsProfile;
83
- compaction: NativeCompactionMode;
84
- fast: false | FastModeProfile;
85
- }
86
-
87
- export type ModelProfileSource = "bundled" | "user" | "legacy";
88
-
89
- export interface ResolvedModelProfile {
90
- id: string;
91
- sources: ModelProfileSource[];
92
- profileHash: string;
93
- effective: EffectiveModelProfile;
94
- diagnostics: string[];
95
- }
1
+ export * from "@oai404iao/pi-codex-runtime/internal/model-catalog/types";
@@ -1,393 +1 @@
1
- import type {
2
- CompactionEntry,
3
- ExtensionAPI,
4
- ExtensionContext,
5
- SessionBeforeCompactEvent,
6
- SessionContext,
7
- SessionEntry,
8
- } from "@earendil-works/pi-coding-agent";
9
- import { buildSessionContext } from "@earendil-works/pi-coding-agent";
10
- import type { Api, AssistantMessage, Context, Model, ThinkingLevel, Tool } from "@earendil-works/pi-ai";
11
- import type { ModelLike } from "./capabilities.js";
12
- import { loadModelSettings } from "./model-catalog/runtime.js";
13
- import { resolveModelProfile } from "./model-catalog/catalog.js";
14
- import { hasCodexRequestAuth } from "./codex-http.js";
15
- import {
16
- requestOpenAINativeCompaction,
17
- sanitizeNativeCompactionOutput,
18
- type OpenAIResponsesProviderController,
19
- } from "./provider-shim.js";
20
- import {
21
- type CodexMinimalToolsSettings,
22
- } from "./settings.js";
23
-
24
- export const NATIVE_COMPACTION_DETAILS_KIND = "openai-native-compaction";
25
- export const NATIVE_COMPACTION_DETAILS_VERSION = 3;
26
-
27
- export type NativeCompactionMode = Exclude<CodexMinimalToolsSettings["compactionMode"], "pi">;
28
- type StoredNativeCompactionMode = NativeCompactionMode | "responses-context-management";
29
-
30
- export interface NativeCompactionDetails {
31
- kind: typeof NATIVE_COMPACTION_DETAILS_KIND;
32
- version: 1 | 2 | typeof NATIVE_COMPACTION_DETAILS_VERSION;
33
- mode: StoredNativeCompactionMode;
34
- provider: string;
35
- model: string;
36
- api: string;
37
- profileHash?: string;
38
- output: unknown[];
39
- /** Legacy context-management checkpoint source. New Responses compactions omit this. */
40
- sourceEntryId?: string;
41
- /** Legacy context-management checkpoint block. New Responses compactions omit this. */
42
- sourceBlockIndex?: number;
43
- }
44
-
45
- interface IndexedNativeCompactionEntry {
46
- entry: CompactionEntry<NativeCompactionDetails>;
47
- index: number;
48
- }
49
-
50
- type PiMessage = SessionContext["messages"][number];
51
- type PiMessages = SessionContext["messages"];
52
-
53
- function asRecord(value: unknown): Record<string, unknown> | undefined {
54
- return value && typeof value === "object" && !Array.isArray(value)
55
- ? value as Record<string, unknown>
56
- : undefined;
57
- }
58
-
59
- export function isNativeCompactionDetails(value: unknown): value is NativeCompactionDetails {
60
- const details = asRecord(value);
61
- return details?.kind === NATIVE_COMPACTION_DETAILS_KIND
62
- && (details.version === 1 || details.version === 2 || details.version === NATIVE_COMPACTION_DETAILS_VERSION)
63
- && (
64
- details.mode === "responses"
65
- || details.mode === "responses-compact"
66
- || (details.version === 1 && details.mode === "responses-context-management")
67
- )
68
- && typeof details.provider === "string"
69
- && typeof details.model === "string"
70
- && typeof details.api === "string"
71
- && Array.isArray(details.output);
72
- }
73
-
74
- function normalizedNativeCompactionMode(details: NativeCompactionDetails): NativeCompactionMode {
75
- return details.mode === "responses-context-management" ? "responses" : details.mode;
76
- }
77
-
78
- function isNativeCompactionSignature(signature: unknown): boolean {
79
- if (typeof signature !== "string" || !signature.startsWith("{")) return false;
80
- try {
81
- const item = asRecord(JSON.parse(signature));
82
- return item?.type === "compaction" || item?.type === "context_compaction";
83
- } catch {
84
- return false;
85
- }
86
- }
87
-
88
- function findLegacyMarkerBlockIndex(message: unknown): number | undefined {
89
- const candidate = asRecord(message);
90
- if (candidate?.role !== "assistant" || !Array.isArray(candidate.content)) return undefined;
91
- for (let index = candidate.content.length - 1; index >= 0; index--) {
92
- const block = asRecord(candidate.content[index]);
93
- if (block?.type === "thinking" && isNativeCompactionSignature(block.thinkingSignature)) {
94
- return index;
95
- }
96
- }
97
- return undefined;
98
- }
99
-
100
- function latestCompactionIndex(entries: readonly SessionEntry[]): number {
101
- for (let index = entries.length - 1; index >= 0; index--) {
102
- if (entries[index]?.type === "compaction") return index;
103
- }
104
- return -1;
105
- }
106
-
107
- function latestNativeCompactionEntry(entries: readonly SessionEntry[]): IndexedNativeCompactionEntry | undefined {
108
- const index = latestCompactionIndex(entries);
109
- if (index < 0) return undefined;
110
- const entry = entries[index];
111
- if (entry?.type !== "compaction" || !isNativeCompactionDetails(entry.details)) return undefined;
112
- return {
113
- entry: entry as CompactionEntry<NativeCompactionDetails>,
114
- index,
115
- };
116
- }
117
-
118
- function matchesModelIdentity(
119
- value: { provider: string; model: string; api: string; profileHash?: string },
120
- model: Model<Api>,
121
- ): boolean {
122
- if (value.provider !== model.provider || value.model !== model.id || value.api !== model.api) return false;
123
- if (!value.profileHash) return true;
124
- return resolveModelProfile(model as ModelLike)?.profileHash === value.profileHash;
125
- }
126
-
127
- function syntheticNativeAssistant(
128
- output: readonly unknown[],
129
- model: Model<Api>,
130
- timestamp: number,
131
- ): AssistantMessage {
132
- return {
133
- role: "assistant",
134
- api: model.api,
135
- provider: model.provider,
136
- model: model.id,
137
- content: output.map((item) => ({
138
- type: "thinking",
139
- thinking: "",
140
- thinkingSignature: JSON.stringify(item),
141
- redacted: true,
142
- })) as AssistantMessage["content"],
143
- usage: {
144
- input: 0,
145
- output: 0,
146
- cacheRead: 0,
147
- cacheWrite: 0,
148
- totalTokens: 0,
149
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
150
- },
151
- stopReason: "stop",
152
- timestamp,
153
- };
154
- }
155
-
156
- function withoutCompactionSummary(messages: PiMessages): PiMessages {
157
- return messages.filter((message) => message.role !== "compactionSummary");
158
- }
159
-
160
- function legacyTailAfterContextManagementMarker(
161
- messages: PiMessages,
162
- sourceBlockIndex: number | undefined,
163
- ): PiMessages {
164
- if (messages.length === 0) return messages;
165
- const first = messages[0];
166
- if (first?.role !== "assistant") return messages;
167
- const blockIndex = findLegacyMarkerBlockIndex(first) ?? sourceBlockIndex;
168
- if (blockIndex === undefined) return messages;
169
- const prefix = first.content.slice(0, blockIndex);
170
- const suffix = first.content.slice(blockIndex + 1);
171
- const resultIds = new Set(
172
- messages
173
- .filter((message): message is Extract<PiMessage, { role: "toolResult" }> => message.role === "toolResult")
174
- .map((message) => message.toolCallId),
175
- );
176
- const recoverTerminalMarker = suffix.length === 0 && prefix.some(
177
- (block) => block.type === "toolCall" && resultIds.has(block.id),
178
- );
179
- if (recoverTerminalMarker) {
180
- // Older versions appended a compaction discovered only in
181
- // response.completed after already-streamed output, even when its
182
- // authoritative output_index was first. Rotate that terminal marker back
183
- // in front so the call arguments and matching results survive replay.
184
- return prefix.length > 0 ? [{ ...first, content: prefix }, ...messages.slice(1)] : messages.slice(1);
185
- }
186
- return suffix.length > 0 ? [{ ...first, content: suffix }, ...messages.slice(1)] : messages.slice(1);
187
- }
188
-
189
- function messageTimestamp(message: PiMessage): number {
190
- return typeof message.timestamp === "number" ? message.timestamp : 0;
191
- }
192
-
193
- function messagesAfterEntry(entries: readonly SessionEntry[], entryIndex: number): PiMessages {
194
- const suffix = entries.slice(entryIndex + 1);
195
- if (suffix.length === 0) return [];
196
- return buildSessionContext(
197
- suffix as SessionEntry[],
198
- suffix[suffix.length - 1]?.id,
199
- ).messages;
200
- }
201
-
202
- /**
203
- * Responses requires every function/custom-tool output to have a matching call.
204
- * Compaction can expose malformed local history if a boundary lands inside a
205
- * tool turn, so rebuild tool turns atomically: drop orphan/duplicate results and
206
- * synthesize an aborted output for a surviving call with no result.
207
- */
208
- export function normalizeNativeCompactionToolPairs(messages: PiMessages): PiMessages {
209
- const resultByCallId = new Map<string, Extract<PiMessage, { role: "toolResult" }>>();
210
- for (const message of messages) {
211
- if (message.role === "toolResult" && !resultByCallId.has(message.toolCallId)) {
212
- resultByCallId.set(message.toolCallId, message);
213
- }
214
- }
215
-
216
- let changed = false;
217
- const normalized: PiMessage[] = [];
218
- const retainedResults = new Set<string>();
219
- for (const message of messages) {
220
- if (message.role === "toolResult") {
221
- changed = true;
222
- continue;
223
- }
224
- normalized.push(message);
225
- if (message.role !== "assistant") continue;
226
-
227
- for (const block of message.content) {
228
- if (block.type !== "toolCall" || retainedResults.has(block.id)) continue;
229
- retainedResults.add(block.id);
230
- const existing = resultByCallId.get(block.id);
231
- if (existing) {
232
- normalized.push(existing);
233
- continue;
234
- }
235
- changed = true;
236
- normalized.push({
237
- role: "toolResult",
238
- toolCallId: block.id,
239
- toolName: block.name,
240
- content: [{ type: "text", text: "aborted" }],
241
- isError: true,
242
- timestamp: messageTimestamp(message),
243
- } as PiMessage);
244
- }
245
- }
246
-
247
- if (retainedResults.size !== resultByCallId.size) changed = true;
248
- return changed ? normalized : messages;
249
- }
250
-
251
- /**
252
- * Replace Pi's textual compaction summary with the opaque native Responses
253
- * items saved in CompactionEntry.details. The opaque payload is replayed only
254
- * to the same provider, model, API, and current model-profile hash.
255
- */
256
- export function applyNativeCompactionContext(
257
- messages: PiMessages,
258
- branchEntries: readonly SessionEntry[],
259
- model: Model<Api> | undefined,
260
- ): PiMessages {
261
- if (!model || !resolveModelProfile(model as ModelLike)?.effective.enabled) return messages;
262
-
263
- const installed = latestNativeCompactionEntry(branchEntries);
264
- if (installed) {
265
- const details = installed.entry.details;
266
- if (!isNativeCompactionDetails(details)) return messages;
267
- if (!matchesModelIdentity(details, model)) return messages;
268
- const output = normalizedNativeCompactionMode(details) === "responses-compact"
269
- ? sanitizeNativeCompactionOutput(details.output)
270
- : details.output;
271
- const withoutSummary = withoutCompactionSummary(messages);
272
- let tail: PiMessages;
273
- if (details.sourceEntryId) {
274
- tail = legacyTailAfterContextManagementMarker(withoutSummary, details.sourceBlockIndex);
275
- } else {
276
- // The compaction entry is the semantic history boundary. Timestamps are
277
- // not safe here because messages queued while compaction is running can
278
- // be appended after the entry with an earlier creation timestamp.
279
- tail = messagesAfterEntry(branchEntries, installed.index);
280
- }
281
- return normalizeNativeCompactionToolPairs([
282
- syntheticNativeAssistant(output, model, new Date(installed.entry.timestamp).getTime()),
283
- ...tail,
284
- ]);
285
- }
286
- return messages;
287
- }
288
-
289
- function activeTools(pi: ExtensionAPI): Tool[] {
290
- const active = new Set(pi.getActiveTools());
291
- return pi.getAllTools()
292
- .filter((tool) => active.has(tool.name))
293
- .map((tool) => ({
294
- name: tool.name,
295
- description: tool.description,
296
- parameters: tool.parameters,
297
- })) as Tool[];
298
- }
299
-
300
- function compactionSummary(mode: NativeCompactionMode): string {
301
- return mode === "responses"
302
- ? "OpenAI Responses compaction replaced the earlier conversation. The opaque encrypted compaction state is preserved in this session by pi-codex-minimal-tools."
303
- : "OpenAI Responses /responses/compact replaced the earlier conversation. The opaque encrypted compaction state is preserved in this session by pi-codex-minimal-tools.";
304
- }
305
-
306
- async function buildNativeCompactionContext(
307
- pi: ExtensionAPI,
308
- event: SessionBeforeCompactEvent,
309
- ctx: ExtensionContext,
310
- model: Model<Api>,
311
- ): Promise<Context> {
312
- const session = buildSessionContext(event.branchEntries, ctx.sessionManager.getLeafId());
313
- return {
314
- systemPrompt: ctx.getSystemPrompt(),
315
- messages: applyNativeCompactionContext(
316
- session.messages,
317
- event.branchEntries,
318
- model,
319
- ) as Context["messages"],
320
- tools: activeTools(pi),
321
- };
322
- }
323
-
324
- export function registerNativeCompaction(
325
- pi: ExtensionAPI,
326
- providerController?: OpenAIResponsesProviderController,
327
- ): void {
328
- pi.on("context", (event, ctx) => {
329
- const settings = loadModelSettings(ctx.model as ModelLike | undefined, ctx.cwd);
330
- if (!settings.enabled || settings.compactionMode === "pi") return undefined;
331
- const messages = applyNativeCompactionContext(
332
- event.messages as PiMessages,
333
- ctx.sessionManager.getBranch(),
334
- ctx.model as Model<Api> | undefined,
335
- );
336
- return messages === event.messages ? undefined : { messages: messages as typeof event.messages };
337
- });
338
-
339
- pi.on("session_before_compact", async (event, ctx) => {
340
- const model = ctx.model as Model<Api> | undefined;
341
- const settings = loadModelSettings(model as ModelLike | undefined, ctx.cwd);
342
- const mode = settings.compactionMode;
343
- if (!settings.enabled || mode === "pi" || !model || !settings.modelProfile?.effective.enabled) return undefined;
344
-
345
- try {
346
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
347
- if (
348
- !auth.ok
349
- || !hasCodexRequestAuth({
350
- modelHeaders: model.headers,
351
- auth: { apiKey: auth.apiKey, headers: auth.headers },
352
- })
353
- ) {
354
- throw new Error(auth.ok ? "OpenAI request authentication is unavailable" : auth.error);
355
- }
356
- const sessionId = ctx.sessionManager.getSessionId();
357
- const context = await buildNativeCompactionContext(pi, event, ctx, model);
358
- const output = await requestOpenAINativeCompaction(model, context, {
359
- mode,
360
- apiKey: auth.apiKey ?? "",
361
- headers: auth.headers,
362
- signal: event.signal,
363
- reasoning: pi.getThinkingLevel() as ThinkingLevel,
364
- sessionId,
365
- turnId: providerController?.getCurrentTurnId(sessionId),
366
- settings,
367
- });
368
- return {
369
- compaction: {
370
- summary: compactionSummary(mode),
371
- firstKeptEntryId: event.preparation.firstKeptEntryId,
372
- tokensBefore: event.preparation.tokensBefore,
373
- details: {
374
- kind: NATIVE_COMPACTION_DETAILS_KIND,
375
- version: NATIVE_COMPACTION_DETAILS_VERSION,
376
- mode,
377
- provider: model.provider,
378
- model: model.id,
379
- api: model.api,
380
- profileHash: settings.modelProfileHash,
381
- output,
382
- } satisfies NativeCompactionDetails,
383
- },
384
- };
385
- } catch (error) {
386
- if (!event.signal.aborted) {
387
- const message = error instanceof Error ? error.message : String(error);
388
- ctx.ui.notify(`OpenAI native compaction failed; falling back to Pi compaction: ${message}`, "warning");
389
- }
390
- return undefined;
391
- }
392
- });
393
- }
1
+ export * from "@oai404iao/pi-codex-core/internal/native-compaction";