@narumitw/pi-codex-compact 0.51.3 → 0.53.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/README.md +90 -90
- package/dist/chunks/chunk-7H7JJ7ZV.js +37 -0
- package/dist/chunks/chunk-7H7JJ7ZV.js.map +7 -0
- package/dist/chunks/{settings-menu-PEDLURUH.js → settings-menu-TVWWDPBF.js} +52 -35
- package/dist/chunks/settings-menu-TVWWDPBF.js.map +7 -0
- package/dist/index.ts +541 -101
- package/dist/index.ts.map +4 -4
- package/package.json +53 -57
- package/src/checkpoint.ts +265 -218
- package/src/codex-compact.ts +252 -229
- package/src/model-api.ts +56 -5
- package/src/protocol.ts +318 -179
- package/src/remote-compact.ts +276 -0
- package/src/remote-shared.ts +16 -0
- package/src/remote-types.ts +46 -0
- package/src/remote-v2.ts +66 -0
- package/src/remote.ts +14 -116
- package/src/settings-menu.ts +213 -201
- package/src/settings.ts +228 -176
- package/src/terminal.ts +6 -0
- package/dist/chunks/chunk-6TZ2L2BM.js +0 -13
- package/dist/chunks/chunk-6TZ2L2BM.js.map +0 -7
- package/dist/chunks/settings-menu-PEDLURUH.js.map +0 -7
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ResponsesCompactionProfile } from "./model-api.js";
|
|
3
|
+
import {
|
|
4
|
+
CodexCompactionProtocolError,
|
|
5
|
+
type CollectedCompactResponse,
|
|
6
|
+
collectCompactResponse,
|
|
7
|
+
expandRemoteCompactionPayload,
|
|
8
|
+
type JsonObject,
|
|
9
|
+
} from "./protocol.js";
|
|
10
|
+
import { collectProviderUsage } from "./remote-shared.js";
|
|
11
|
+
import {
|
|
12
|
+
abortError,
|
|
13
|
+
assertPreparedInput,
|
|
14
|
+
isJsonObject,
|
|
15
|
+
type RemoteCompactionRequest,
|
|
16
|
+
type RemoteCompactionResponse,
|
|
17
|
+
} from "./remote-types.js";
|
|
18
|
+
|
|
19
|
+
const OFFICIAL_COMPACT_FIELDS = [
|
|
20
|
+
"model",
|
|
21
|
+
"input",
|
|
22
|
+
"instructions",
|
|
23
|
+
"previous_response_id",
|
|
24
|
+
"prompt_cache_key",
|
|
25
|
+
"prompt_cache_retention",
|
|
26
|
+
"service_tier",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const CODEX_COMPACT_FIELDS = [
|
|
30
|
+
"model",
|
|
31
|
+
"input",
|
|
32
|
+
"instructions",
|
|
33
|
+
"tools",
|
|
34
|
+
"parallel_tool_calls",
|
|
35
|
+
"reasoning",
|
|
36
|
+
"service_tier",
|
|
37
|
+
"prompt_cache_key",
|
|
38
|
+
"text",
|
|
39
|
+
"access_programs",
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
function compactPayload(payload: JsonObject, profile: ResponsesCompactionProfile): JsonObject {
|
|
43
|
+
const fields = profile === "codex-responses-v1" ? CODEX_COMPACT_FIELDS : OFFICIAL_COMPACT_FIELDS;
|
|
44
|
+
const result: JsonObject = {};
|
|
45
|
+
for (const field of fields) {
|
|
46
|
+
if (Object.hasOwn(payload, field) && payload[field] !== undefined) {
|
|
47
|
+
result[field] = structuredClone(payload[field]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (typeof result.model !== "string" || result.model.length === 0) {
|
|
51
|
+
throw new CodexCompactionProtocolError("Responses payload is missing a model");
|
|
52
|
+
}
|
|
53
|
+
assertPreparedInput(result);
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function requestUrl(input: string | URL | Request): URL {
|
|
58
|
+
return new URL(input instanceof Request ? input.url : String(input));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function responsesCompactUrl(input: string | URL | Request): URL {
|
|
62
|
+
const original = requestUrl(input);
|
|
63
|
+
if (!original.pathname.endsWith("/responses")) {
|
|
64
|
+
throw new CodexCompactionProtocolError("Provider request URL does not end with the Responses endpoint");
|
|
65
|
+
}
|
|
66
|
+
const compact = new URL(original);
|
|
67
|
+
compact.pathname = `${compact.pathname}/compact`;
|
|
68
|
+
if (compact.origin !== original.origin) {
|
|
69
|
+
throw new CodexCompactionProtocolError("Responses Compact URL changed origin");
|
|
70
|
+
}
|
|
71
|
+
return compact;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function mergedHeaders(input: string | URL | Request, init?: RequestInit): Headers {
|
|
75
|
+
const headers = new Headers(input instanceof Request ? input.headers : undefined);
|
|
76
|
+
new Headers(init?.headers).forEach((value, name) => {
|
|
77
|
+
headers.set(name, value);
|
|
78
|
+
});
|
|
79
|
+
headers.delete("content-encoding");
|
|
80
|
+
headers.delete("content-length");
|
|
81
|
+
headers.set("accept", "application/json");
|
|
82
|
+
headers.set("content-type", "application/json");
|
|
83
|
+
return headers;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mergedSignal(
|
|
87
|
+
input: string | URL | Request,
|
|
88
|
+
init: RequestInit | undefined,
|
|
89
|
+
ownerSignal: AbortSignal,
|
|
90
|
+
): AbortSignal {
|
|
91
|
+
const signals = [ownerSignal];
|
|
92
|
+
if (input instanceof Request) signals.push(input.signal);
|
|
93
|
+
if (init?.signal) signals.push(init.signal);
|
|
94
|
+
return signals.length === 1 ? ownerSignal : AbortSignal.any(signals);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function nonRetryableBridgeFailure(error: unknown): Response {
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
return Response.json(
|
|
100
|
+
{
|
|
101
|
+
error: {
|
|
102
|
+
message,
|
|
103
|
+
type: "invalid_request_error",
|
|
104
|
+
code: "invalid_compact_response",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{ status: 400 },
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function nonNegativeInteger(value: unknown): value is number {
|
|
112
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function optionalUsageDetail(details: unknown, field: string): number {
|
|
116
|
+
if (details === undefined || details === null) return 0;
|
|
117
|
+
if (!isJsonObject(details)) {
|
|
118
|
+
throw new CodexCompactionProtocolError("Responses Compact response has invalid usage details");
|
|
119
|
+
}
|
|
120
|
+
const value = details[field];
|
|
121
|
+
if (value === undefined || value === null) return 0;
|
|
122
|
+
if (!nonNegativeInteger(value)) {
|
|
123
|
+
throw new CodexCompactionProtocolError(`Responses Compact response has invalid usage detail ${field}`);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function validatedUsage(response: JsonObject): JsonObject {
|
|
129
|
+
const usage = response.usage;
|
|
130
|
+
if (!isJsonObject(usage)) {
|
|
131
|
+
throw new CodexCompactionProtocolError("Responses Compact response is missing usage");
|
|
132
|
+
}
|
|
133
|
+
for (const field of ["input_tokens", "output_tokens", "total_tokens"] as const) {
|
|
134
|
+
if (!nonNegativeInteger(usage[field])) {
|
|
135
|
+
throw new CodexCompactionProtocolError(`Responses Compact response has invalid ${field}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const inputTokens = usage.input_tokens as number;
|
|
139
|
+
const outputTokens = usage.output_tokens as number;
|
|
140
|
+
const totalTokens = usage.total_tokens as number;
|
|
141
|
+
const cachedTokens = optionalUsageDetail(usage.input_tokens_details, "cached_tokens");
|
|
142
|
+
const cacheWriteTokens = optionalUsageDetail(usage.input_tokens_details, "cache_write_tokens");
|
|
143
|
+
const reasoningTokens = optionalUsageDetail(usage.output_tokens_details, "reasoning_tokens");
|
|
144
|
+
if (cachedTokens + cacheWriteTokens > inputTokens || reasoningTokens > outputTokens) {
|
|
145
|
+
throw new CodexCompactionProtocolError("Responses Compact response has inconsistent usage details");
|
|
146
|
+
}
|
|
147
|
+
if (totalTokens !== inputTokens + outputTokens) {
|
|
148
|
+
throw new CodexCompactionProtocolError("Responses Compact response has inconsistent total usage");
|
|
149
|
+
}
|
|
150
|
+
return structuredClone(usage);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function syntheticCompletion(result: CollectedCompactResponse, payload: JsonObject): Response {
|
|
154
|
+
const completed = {
|
|
155
|
+
id: typeof result.response.id === "string" ? result.response.id : "resp_pi_compact_bridge",
|
|
156
|
+
object: "response",
|
|
157
|
+
created_at:
|
|
158
|
+
typeof result.response.created_at === "number" ? result.response.created_at : Math.floor(Date.now() / 1000),
|
|
159
|
+
status: "completed",
|
|
160
|
+
model: payload.model,
|
|
161
|
+
output: [],
|
|
162
|
+
parallel_tool_calls: false,
|
|
163
|
+
tool_choice: "auto",
|
|
164
|
+
tools: [],
|
|
165
|
+
usage: validatedUsage(result.response),
|
|
166
|
+
};
|
|
167
|
+
const events = [
|
|
168
|
+
{ type: "response.created", response: { ...completed, status: "in_progress" } },
|
|
169
|
+
{ type: "response.completed", response: completed },
|
|
170
|
+
];
|
|
171
|
+
return new Response(events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join(""), {
|
|
172
|
+
status: 200,
|
|
173
|
+
headers: { "content-type": "text/event-stream" },
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function requestResponsesCompact(request: RemoteCompactionRequest): Promise<RemoteCompactionResponse> {
|
|
178
|
+
if (request.signal.aborted) throw abortError();
|
|
179
|
+
let preparedPayload: JsonObject | undefined;
|
|
180
|
+
let sentInput: JsonObject[] | undefined;
|
|
181
|
+
let compactResult: CollectedCompactResponse | undefined;
|
|
182
|
+
let bridgeError: unknown;
|
|
183
|
+
let dispatchInFlight = false;
|
|
184
|
+
let successfulResponses = 0;
|
|
185
|
+
const baseFetch = request.fetch ?? globalThis.fetch;
|
|
186
|
+
const bridgeFetch: typeof globalThis.fetch = async (input, init) => {
|
|
187
|
+
if (request.signal.aborted) throw abortError();
|
|
188
|
+
if (successfulResponses > 0) {
|
|
189
|
+
bridgeError = new CodexCompactionProtocolError("Provider dispatched again after Responses Compact succeeded");
|
|
190
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
191
|
+
}
|
|
192
|
+
if (dispatchInFlight) {
|
|
193
|
+
bridgeError = new CodexCompactionProtocolError("Provider dispatched overlapping Responses Compact requests");
|
|
194
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
195
|
+
}
|
|
196
|
+
if (!preparedPayload) {
|
|
197
|
+
bridgeError = new CodexCompactionProtocolError("Provider dispatched before exposing its request payload");
|
|
198
|
+
return nonRetryableBridgeFailure(bridgeError);
|
|
199
|
+
}
|
|
200
|
+
let compactUrl: URL;
|
|
201
|
+
try {
|
|
202
|
+
compactUrl = responsesCompactUrl(input);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
bridgeError = error;
|
|
205
|
+
return nonRetryableBridgeFailure(error);
|
|
206
|
+
}
|
|
207
|
+
const signal = mergedSignal(input, init, request.signal);
|
|
208
|
+
dispatchInFlight = true;
|
|
209
|
+
try {
|
|
210
|
+
const response = await baseFetch(compactUrl, {
|
|
211
|
+
...init,
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: mergedHeaders(input, init),
|
|
214
|
+
body: JSON.stringify(preparedPayload),
|
|
215
|
+
signal,
|
|
216
|
+
});
|
|
217
|
+
if (!response.ok) return response;
|
|
218
|
+
try {
|
|
219
|
+
const result = await collectCompactResponse(response, { signal });
|
|
220
|
+
successfulResponses += 1;
|
|
221
|
+
if (successfulResponses !== 1) {
|
|
222
|
+
throw new CodexCompactionProtocolError(
|
|
223
|
+
"Provider returned more than one successful Responses Compact response",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
compactResult = result;
|
|
227
|
+
return syntheticCompletion(result, preparedPayload);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
bridgeError = error;
|
|
230
|
+
return nonRetryableBridgeFailure(error);
|
|
231
|
+
}
|
|
232
|
+
} finally {
|
|
233
|
+
dispatchInFlight = false;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const stream = request.provider.stream(request.model, request.context, {
|
|
238
|
+
apiKey: request.apiKey,
|
|
239
|
+
headers: request.headers,
|
|
240
|
+
env: request.env,
|
|
241
|
+
signal: request.signal,
|
|
242
|
+
transport: "sse",
|
|
243
|
+
cacheRetention: "none",
|
|
244
|
+
timeoutMs: request.requestTimeoutMs ?? 5 * 60 * 1000,
|
|
245
|
+
maxRetries: request.maxRetries ?? 2,
|
|
246
|
+
fetch: bridgeFetch,
|
|
247
|
+
onPayload: (payload) => {
|
|
248
|
+
if (preparedPayload) {
|
|
249
|
+
throw new CodexCompactionProtocolError("Provider exposed more than one compaction request payload");
|
|
250
|
+
}
|
|
251
|
+
const expanded = expandRemoteCompactionPayload(payload, request.priorCheckpoint);
|
|
252
|
+
preparedPayload = compactPayload(expanded, request.profile);
|
|
253
|
+
sentInput = assertPreparedInput(preparedPayload);
|
|
254
|
+
return expanded;
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
let usage: Usage;
|
|
259
|
+
try {
|
|
260
|
+
usage = await collectProviderUsage(stream, request.signal);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (bridgeError) throw bridgeError;
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
if (request.signal.aborted) throw abortError();
|
|
266
|
+
if (bridgeError) throw bridgeError;
|
|
267
|
+
if (!preparedPayload || !sentInput || !compactResult || successfulResponses !== 1) {
|
|
268
|
+
throw new CodexCompactionProtocolError("Provider did not complete exactly one Responses Compact request");
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
item: compactResult.item,
|
|
272
|
+
promptInput: sentInput,
|
|
273
|
+
compactedOutput: compactResult.output,
|
|
274
|
+
usage,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AssistantMessageEventStream, Usage } from "@earendil-works/pi-ai";
|
|
2
|
+
import { abortError } from "./remote-types.js";
|
|
3
|
+
|
|
4
|
+
export async function collectProviderUsage(stream: AssistantMessageEventStream, signal: AbortSignal): Promise<Usage> {
|
|
5
|
+
let usage: Usage | undefined;
|
|
6
|
+
for await (const event of stream) {
|
|
7
|
+
if (signal.aborted) throw abortError();
|
|
8
|
+
if (event.type === "error") {
|
|
9
|
+
throw new Error(event.error.errorMessage ?? "Responses compaction request failed");
|
|
10
|
+
}
|
|
11
|
+
if (event.type === "done") usage = event.message.usage;
|
|
12
|
+
}
|
|
13
|
+
if (signal.aborted) throw abortError();
|
|
14
|
+
if (!usage) throw new Error("Responses provider stream ended without completion usage");
|
|
15
|
+
return usage;
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Api, Context, Model, Provider, ProviderHeaders, Usage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { RemoteCompactionProtocol, ResponsesCompactionProfile } from "./model-api.js";
|
|
3
|
+
import type { JsonObject } from "./protocol.js";
|
|
4
|
+
|
|
5
|
+
export interface PriorCheckpointPayload {
|
|
6
|
+
marker: string;
|
|
7
|
+
replacementHistory: readonly unknown[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RemoteCompactionRequest {
|
|
11
|
+
provider: Provider;
|
|
12
|
+
model: Model<Api>;
|
|
13
|
+
profile: ResponsesCompactionProfile;
|
|
14
|
+
context: Context;
|
|
15
|
+
protocol: RemoteCompactionProtocol;
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
headers?: ProviderHeaders;
|
|
18
|
+
env?: Record<string, string>;
|
|
19
|
+
signal: AbortSignal;
|
|
20
|
+
priorCheckpoint?: PriorCheckpointPayload;
|
|
21
|
+
requestTimeoutMs?: number;
|
|
22
|
+
maxRetries?: number;
|
|
23
|
+
fetch?: typeof globalThis.fetch;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RemoteCompactionResponse {
|
|
27
|
+
item: JsonObject;
|
|
28
|
+
promptInput: JsonObject[];
|
|
29
|
+
compactedOutput?: JsonObject[];
|
|
30
|
+
usage: Usage;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isJsonObject(value: unknown): value is JsonObject {
|
|
34
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function abortError(): DOMException {
|
|
38
|
+
return new DOMException("Compaction aborted", "AbortError");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function assertPreparedInput(payload: JsonObject): JsonObject[] {
|
|
42
|
+
if (!Array.isArray(payload.input) || !payload.input.every(isJsonObject)) {
|
|
43
|
+
throw new Error("Prepared compaction payload has invalid input items");
|
|
44
|
+
}
|
|
45
|
+
return structuredClone(payload.input);
|
|
46
|
+
}
|
package/src/remote-v2.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CodexCompactionProtocolError,
|
|
3
|
+
type CollectedCompaction,
|
|
4
|
+
collectCompactionSse,
|
|
5
|
+
prepareRemoteCompactionPayload,
|
|
6
|
+
} from "./protocol.js";
|
|
7
|
+
import { collectProviderUsage } from "./remote-shared.js";
|
|
8
|
+
import {
|
|
9
|
+
abortError,
|
|
10
|
+
assertPreparedInput,
|
|
11
|
+
type RemoteCompactionRequest,
|
|
12
|
+
type RemoteCompactionResponse,
|
|
13
|
+
} from "./remote-types.js";
|
|
14
|
+
|
|
15
|
+
export async function requestRemoteCompactionV2(request: RemoteCompactionRequest): Promise<RemoteCompactionResponse> {
|
|
16
|
+
if (request.signal.aborted) throw abortError();
|
|
17
|
+
let sentInput: ReturnType<typeof assertPreparedInput> | undefined;
|
|
18
|
+
const inspections: Promise<{ ok: true; value: CollectedCompaction } | { ok: false; error: unknown }>[] = [];
|
|
19
|
+
const baseFetch = request.fetch ?? globalThis.fetch;
|
|
20
|
+
const inspectedFetch: typeof globalThis.fetch = async (input, init) => {
|
|
21
|
+
const response = await baseFetch(input, init);
|
|
22
|
+
if (!response.ok || !response.body) return response;
|
|
23
|
+
const [providerBody, inspectionBody] = response.body.tee();
|
|
24
|
+
const inspection = collectCompactionSse(inspectionBody, { signal: request.signal }).then(
|
|
25
|
+
(value) => ({ ok: true as const, value }),
|
|
26
|
+
(error: unknown) => ({ ok: false as const, error }),
|
|
27
|
+
);
|
|
28
|
+
inspections.push(inspection);
|
|
29
|
+
return new Response(providerBody, {
|
|
30
|
+
status: response.status,
|
|
31
|
+
statusText: response.statusText,
|
|
32
|
+
headers: response.headers,
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const stream = request.provider.stream(request.model, request.context, {
|
|
37
|
+
apiKey: request.apiKey,
|
|
38
|
+
headers: request.headers,
|
|
39
|
+
env: request.env,
|
|
40
|
+
signal: request.signal,
|
|
41
|
+
transport: "sse",
|
|
42
|
+
cacheRetention: "none",
|
|
43
|
+
timeoutMs: request.requestTimeoutMs ?? 5 * 60 * 1000,
|
|
44
|
+
maxRetries: request.maxRetries ?? 2,
|
|
45
|
+
fetch: inspectedFetch,
|
|
46
|
+
onPayload: (payload) => {
|
|
47
|
+
const prepared = prepareRemoteCompactionPayload(payload, request.priorCheckpoint);
|
|
48
|
+
sentInput = assertPreparedInput(prepared).slice(0, -1);
|
|
49
|
+
return prepared;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const usage = await collectProviderUsage(stream, request.signal);
|
|
54
|
+
if (!sentInput) {
|
|
55
|
+
throw new CodexCompactionProtocolError("Provider did not expose a request payload");
|
|
56
|
+
}
|
|
57
|
+
if (inspections.length !== 1) {
|
|
58
|
+
throw new CodexCompactionProtocolError(
|
|
59
|
+
`Provider exposed ${inspections.length} successful SSE responses; expected exactly one`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const inspection = await inspections[0];
|
|
63
|
+
if (request.signal.aborted) throw abortError();
|
|
64
|
+
if (!inspection.ok) throw inspection.error;
|
|
65
|
+
return { item: inspection.value.item, promptInput: sentInput, usage };
|
|
66
|
+
}
|
package/src/remote.ts
CHANGED
|
@@ -1,117 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export interface RemoteCompactionRequest {
|
|
16
|
-
provider: Provider;
|
|
17
|
-
model: Model<"openai-codex-responses">;
|
|
18
|
-
context: Context;
|
|
19
|
-
apiKey?: string;
|
|
20
|
-
headers?: ProviderHeaders;
|
|
21
|
-
env?: Record<string, string>;
|
|
22
|
-
signal: AbortSignal;
|
|
23
|
-
priorCheckpoint?: PriorCheckpointPayload;
|
|
24
|
-
requestTimeoutMs?: number;
|
|
25
|
-
maxRetries?: number;
|
|
26
|
-
fetch?: typeof globalThis.fetch;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface RemoteCompactionResponse {
|
|
30
|
-
item: JsonObject;
|
|
31
|
-
promptInput: JsonObject[];
|
|
32
|
-
usage: Usage;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const EMPTY_USAGE: Usage = {
|
|
36
|
-
input: 0,
|
|
37
|
-
output: 0,
|
|
38
|
-
cacheRead: 0,
|
|
39
|
-
cacheWrite: 0,
|
|
40
|
-
totalTokens: 0,
|
|
41
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function isObject(value: unknown): value is JsonObject {
|
|
45
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function abortError(): DOMException {
|
|
49
|
-
return new DOMException("Compaction aborted", "AbortError");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function requestRemoteCompaction(
|
|
53
|
-
request: RemoteCompactionRequest,
|
|
54
|
-
): Promise<RemoteCompactionResponse> {
|
|
55
|
-
if (request.signal.aborted) throw abortError();
|
|
56
|
-
let sentInput: JsonObject[] | undefined;
|
|
57
|
-
const inspections: Promise<
|
|
58
|
-
{ ok: true; value: CollectedCompaction } | { ok: false; error: unknown }
|
|
59
|
-
>[] = [];
|
|
60
|
-
const baseFetch = request.fetch ?? globalThis.fetch;
|
|
61
|
-
const inspectedFetch: typeof globalThis.fetch = async (input, init) => {
|
|
62
|
-
const response = await baseFetch(input, init);
|
|
63
|
-
if (!response.ok || !response.body) return response;
|
|
64
|
-
const [providerBody, inspectionBody] = response.body.tee();
|
|
65
|
-
const inspection = collectCompactionSse(inspectionBody, { signal: request.signal }).then(
|
|
66
|
-
(value) => ({ ok: true as const, value }),
|
|
67
|
-
(error: unknown) => ({ ok: false as const, error }),
|
|
68
|
-
);
|
|
69
|
-
inspections.push(inspection);
|
|
70
|
-
return new Response(providerBody, {
|
|
71
|
-
status: response.status,
|
|
72
|
-
statusText: response.statusText,
|
|
73
|
-
headers: response.headers,
|
|
74
|
-
});
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const stream = request.provider.stream(request.model, request.context, {
|
|
78
|
-
apiKey: request.apiKey,
|
|
79
|
-
headers: request.headers,
|
|
80
|
-
env: request.env,
|
|
81
|
-
signal: request.signal,
|
|
82
|
-
transport: "sse",
|
|
83
|
-
cacheRetention: "none",
|
|
84
|
-
timeoutMs: request.requestTimeoutMs ?? 5 * 60 * 1000,
|
|
85
|
-
maxRetries: request.maxRetries ?? 2,
|
|
86
|
-
fetch: inspectedFetch,
|
|
87
|
-
onPayload: (payload) => {
|
|
88
|
-
const prepared = prepareRemoteCompactionPayload(payload, request.priorCheckpoint);
|
|
89
|
-
if (!Array.isArray(prepared.input) || !prepared.input.every(isObject)) {
|
|
90
|
-
throw new CodexCompactionProtocolError(
|
|
91
|
-
"Prepared compaction payload has invalid input items",
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
sentInput = structuredClone(prepared.input.slice(0, -1)) as JsonObject[];
|
|
95
|
-
return prepared;
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
let usage = EMPTY_USAGE;
|
|
100
|
-
for await (const event of stream) {
|
|
101
|
-
if (request.signal.aborted) throw abortError();
|
|
102
|
-
if (event.type === "error") {
|
|
103
|
-
throw new Error(event.error.errorMessage ?? "Codex remote compaction request failed");
|
|
104
|
-
}
|
|
105
|
-
if (event.type === "done") usage = event.message.usage;
|
|
106
|
-
}
|
|
107
|
-
if (request.signal.aborted) throw abortError();
|
|
108
|
-
if (!sentInput)
|
|
109
|
-
throw new CodexCompactionProtocolError("Provider did not expose a request payload");
|
|
110
|
-
if (inspections.length === 0) {
|
|
111
|
-
throw new CodexCompactionProtocolError("Provider response did not expose an SSE body");
|
|
112
|
-
}
|
|
113
|
-
const inspection = await inspections.at(-1);
|
|
114
|
-
if (request.signal.aborted) throw abortError();
|
|
115
|
-
if (!inspection?.ok) throw inspection?.error ?? new Error("Remote compaction inspection failed");
|
|
116
|
-
return { item: inspection.value.item, promptInput: sentInput, usage };
|
|
1
|
+
import { requestResponsesCompact } from "./remote-compact.js";
|
|
2
|
+
import type { RemoteCompactionRequest, RemoteCompactionResponse } from "./remote-types.js";
|
|
3
|
+
import { requestRemoteCompactionV2 } from "./remote-v2.js";
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
PriorCheckpointPayload,
|
|
7
|
+
RemoteCompactionRequest,
|
|
8
|
+
RemoteCompactionResponse,
|
|
9
|
+
} from "./remote-types.js";
|
|
10
|
+
|
|
11
|
+
export function requestRemoteCompaction(request: RemoteCompactionRequest): Promise<RemoteCompactionResponse> {
|
|
12
|
+
return request.protocol === "responses-compact"
|
|
13
|
+
? requestResponsesCompact(request)
|
|
14
|
+
: requestRemoteCompactionV2(request);
|
|
117
15
|
}
|