@howaboua/pi-codex-conversion 1.5.5-dev.25.f80a775 → 1.5.5

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.
@@ -1,257 +0,0 @@
1
- import type { NativeCompactionRuntime } from "./compaction-runtime.ts";
2
- import type { NativeCompactionRequestBody } from "./serializer.ts";
3
-
4
- const JSON_CONTENT_TYPE = "application/json";
5
-
6
- type CompactResponseEnvelope = {
7
- id?: string;
8
- created_at?: number | string;
9
- output: unknown[];
10
- [key: string]: unknown;
11
- };
12
-
13
- export type NativeCompactionClientFailureReason =
14
- | "aborted"
15
- | "network-error"
16
- | "non-2xx"
17
- | "empty-body"
18
- | "invalid-json"
19
- | "malformed-response"
20
- | "empty-output";
21
-
22
- export type NativeCompactionClientSuccess = {
23
- ok: true;
24
- status: number;
25
- compactedWindow: unknown[];
26
- compactResponseId?: string;
27
- createdAt?: string;
28
- response: CompactResponseEnvelope;
29
- };
30
-
31
- export type NativeCompactionClientFailure = {
32
- ok: false;
33
- reason: NativeCompactionClientFailureReason;
34
- status?: number;
35
- errorMessage?: string;
36
- responseText?: string;
37
- responseJson?: unknown;
38
- };
39
-
40
- export type NativeCompactionClientResult = NativeCompactionClientSuccess | NativeCompactionClientFailure;
41
-
42
- export type ExecuteNativeCompactionOptions = {
43
- runtime: NativeCompactionRuntime;
44
- request: NativeCompactionRequestBody;
45
- signal?: AbortSignal;
46
- };
47
-
48
- function isRecord(value: unknown): value is Record<string, unknown> {
49
- return !!value && typeof value === "object" && !Array.isArray(value);
50
- }
51
-
52
- function isAbortError(error: unknown): boolean {
53
- return (
54
- (error instanceof DOMException && error.name === "AbortError") ||
55
- (error instanceof Error && (error.name === "AbortError" || error.name === "ABORT_ERR"))
56
- );
57
- }
58
-
59
- function normalizeResponseTimestamp(value: unknown): string | undefined {
60
- if (typeof value === "number" && Number.isFinite(value)) {
61
- const milliseconds = value > 1_000_000_000_000 ? value : value * 1000;
62
- return new Date(milliseconds).toISOString();
63
- }
64
-
65
- if (typeof value !== "string") {
66
- return undefined;
67
- }
68
-
69
- const trimmed = value.trim();
70
- if (!trimmed) {
71
- return undefined;
72
- }
73
-
74
- const parsed = Date.parse(trimmed);
75
- return Number.isNaN(parsed) ? trimmed : new Date(parsed).toISOString();
76
- }
77
-
78
- function isCompactOutputItem(value: unknown): value is Record<string, unknown> {
79
- return isRecord(value);
80
- }
81
-
82
- function isCompactResponseEnvelope(value: unknown): value is CompactResponseEnvelope {
83
- return isRecord(value) && Array.isArray(value.output) && value.output.every(isCompactOutputItem);
84
- }
85
-
86
- function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
87
- const parts = token.split(".");
88
- if (parts.length !== 3) {
89
- return undefined;
90
- }
91
-
92
- try {
93
- const payloadText = Buffer.from(parts[1]!, "base64url").toString("utf8");
94
- const payload = JSON.parse(payloadText);
95
- return isRecord(payload) ? payload : undefined;
96
- } catch {
97
- return undefined;
98
- }
99
- }
100
-
101
- function extractCodexAccountId(token: string): string | undefined {
102
- const payload = decodeJwtPayload(token);
103
- const authClaims = payload?.["https://api.openai.com/auth"];
104
- if (!isRecord(authClaims)) {
105
- return undefined;
106
- }
107
-
108
- const accountId = authClaims.chatgpt_account_id;
109
- return typeof accountId === "string" && accountId.trim().length > 0 ? accountId.trim() : undefined;
110
- }
111
-
112
- function buildCodexUserAgent(): string {
113
- const platform = typeof process !== "undefined" ? process.platform : "browser";
114
- const arch = typeof process !== "undefined" ? process.arch : "unknown";
115
- return `pi (${platform}; ${arch})`;
116
- }
117
-
118
- function extractBearerToken(headers: Headers): string | undefined {
119
- const authorization = headers.get("authorization")?.trim();
120
- const match = authorization?.match(/^Bearer\s+(.+)$/i);
121
- return match?.[1]?.trim() || undefined;
122
- }
123
-
124
- function toHeaders(runtime: NativeCompactionRuntime): Record<string, string> {
125
- const headers = new Headers(runtime.currentModel.headers ?? {});
126
- for (const [key, value] of Object.entries(runtime.headers ?? {})) {
127
- headers.set(key, value);
128
- }
129
- headers.set("accept", JSON_CONTENT_TYPE);
130
- headers.set("content-type", JSON_CONTENT_TYPE);
131
- if (runtime.apiKey) {
132
- headers.set("authorization", `Bearer ${runtime.apiKey}`);
133
- }
134
-
135
- if (runtime.provider === "openai-codex") {
136
- const accountId = extractCodexAccountId(runtime.apiKey ?? extractBearerToken(headers) ?? "");
137
- if (accountId) {
138
- headers.set("chatgpt-account-id", accountId);
139
- }
140
- headers.set("originator", "pi");
141
- headers.set("user-agent", buildCodexUserAgent());
142
- headers.set("openai-beta", "responses=experimental");
143
- }
144
-
145
- return Object.fromEntries(headers.entries());
146
- }
147
-
148
- export async function executeNativeCompaction(
149
- options: ExecuteNativeCompactionOptions,
150
- ): Promise<NativeCompactionClientResult> {
151
- const { runtime, request, signal } = options;
152
- const headers = toHeaders(runtime);
153
-
154
- if (signal?.aborted) {
155
- const aborted: NativeCompactionClientFailure = {
156
- ok: false,
157
- reason: "aborted",
158
- };
159
- return aborted;
160
- }
161
-
162
- try {
163
- const response = await fetch(runtime.compactUrl, {
164
- method: "POST",
165
- headers,
166
- body: JSON.stringify(request),
167
- signal,
168
- });
169
- const responseText = await response.text();
170
-
171
- if (!response.ok) {
172
- let responseJson: unknown;
173
- if (responseText.trim().length > 0) {
174
- try {
175
- responseJson = JSON.parse(responseText);
176
- } catch {
177
- responseJson = undefined;
178
- }
179
- }
180
-
181
- const failure: NativeCompactionClientFailure = {
182
- ok: false,
183
- reason: "non-2xx",
184
- status: response.status,
185
- responseText: responseText || undefined,
186
- responseJson,
187
- };
188
- return failure;
189
- }
190
-
191
- if (!responseText.trim()) {
192
- const failure: NativeCompactionClientFailure = {
193
- ok: false,
194
- reason: "empty-body",
195
- status: response.status,
196
- };
197
- return failure;
198
- }
199
-
200
- let parsed: unknown;
201
- try {
202
- parsed = JSON.parse(responseText);
203
- } catch (error) {
204
- const failure: NativeCompactionClientFailure = {
205
- ok: false,
206
- reason: "invalid-json",
207
- status: response.status,
208
- errorMessage: error instanceof Error ? error.message : String(error),
209
- responseText,
210
- };
211
- return failure;
212
- }
213
-
214
- if (!isCompactResponseEnvelope(parsed)) {
215
- const failure: NativeCompactionClientFailure = {
216
- ok: false,
217
- reason: "malformed-response",
218
- status: response.status,
219
- responseJson: parsed,
220
- };
221
- return failure;
222
- }
223
-
224
- if (parsed.output.length === 0) {
225
- const failure: NativeCompactionClientFailure = {
226
- ok: false,
227
- reason: "empty-output",
228
- status: response.status,
229
- responseJson: parsed,
230
- };
231
- return failure;
232
- }
233
-
234
- const success: NativeCompactionClientSuccess = {
235
- ok: true,
236
- status: response.status,
237
- compactedWindow: [...parsed.output],
238
- compactResponseId: typeof parsed.id === "string" && parsed.id.trim() ? parsed.id.trim() : undefined,
239
- createdAt: normalizeResponseTimestamp(parsed.created_at),
240
- response: parsed,
241
- };
242
- return success;
243
- } catch (error) {
244
- const failure: NativeCompactionClientFailure = isAbortError(error)
245
- ? {
246
- ok: false,
247
- reason: "aborted",
248
- }
249
- : {
250
- ok: false,
251
- reason: "network-error",
252
- errorMessage: error instanceof Error ? error.message : String(error),
253
- };
254
-
255
- return failure;
256
- }
257
- }
@@ -1,80 +0,0 @@
1
- const COMPACTION_ITEM_TYPES = new Set(["compaction", "compaction_summary"]);
2
-
3
- function isRecord(value: unknown): value is Record<string, unknown> {
4
- return !!value && typeof value === "object" && !Array.isArray(value);
5
- }
6
-
7
- function cloneStructuredValue(value: unknown): unknown {
8
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
9
- return value;
10
- }
11
- if (Array.isArray(value)) {
12
- return value.map(cloneStructuredValue);
13
- }
14
- if (isRecord(value)) {
15
- const clone: Record<string, unknown> = {};
16
- for (const [key, nested] of Object.entries(value)) {
17
- clone[key] = cloneStructuredValue(nested);
18
- }
19
- return clone;
20
- }
21
- throw new Error(`Unsupported structured compact output value: ${typeof value}`);
22
- }
23
-
24
- function cloneCompactedOutputItem(item: Record<string, unknown>): Record<string, unknown> | undefined {
25
- try {
26
- return cloneStructuredValue(item) as Record<string, unknown>;
27
- } catch {
28
- return undefined;
29
- }
30
- }
31
-
32
- export function shouldKeepCompactedOutputItem(item: unknown): item is Record<string, unknown> {
33
- return isRecord(item) && typeof item.type === "string";
34
- }
35
-
36
- export function sanitizeCompactedWindow(output: readonly unknown[]): Record<string, unknown>[] {
37
- const sanitized: Record<string, unknown>[] = [];
38
- for (const item of output) {
39
- if (!shouldKeepCompactedOutputItem(item)) continue;
40
- const cloned = cloneCompactedOutputItem(item);
41
- if (cloned) sanitized.push(cloned);
42
- }
43
- return sanitized;
44
- }
45
-
46
- export function extractCompactionSummaryText(compactedWindow: readonly unknown[]): string | undefined {
47
- for (const item of compactedWindow) {
48
- if (!isRecord(item) || typeof item.type !== "string" || !COMPACTION_ITEM_TYPES.has(item.type)) continue;
49
- if (typeof item.encrypted_content === "string" && item.encrypted_content.trim().length > 0) return item.encrypted_content.trim();
50
- }
51
- return undefined;
52
- }
53
-
54
- export function hasCompactionOutputItem(compactedWindow: readonly unknown[]): boolean {
55
- return compactedWindow.some((item) => isRecord(item) && typeof item.type === "string" && COMPACTION_ITEM_TYPES.has(item.type));
56
- }
57
-
58
- function describeOutputItem(item: unknown): string {
59
- if (!isRecord(item)) return typeof item;
60
- const type = typeof item.type === "string" ? item.type : "<missing-type>";
61
- const role = typeof item.role === "string" ? `/${item.role}` : "";
62
- const content = Array.isArray(item.content) ? ` content=${item.content.length}` : "";
63
- const keys = Object.keys(item).sort().slice(0, 8).join(",");
64
- return `${type}${role}${content} keys=[${keys}]`;
65
- }
66
-
67
- export function summarizeCompactionOutputForDiagnostics(rawOutput: readonly unknown[], sanitizedOutput: readonly unknown[]): string {
68
- const rawTypes = rawOutput.map((item) => isRecord(item) && typeof item.type === "string" ? item.type : typeof item);
69
- const sanitizedTypes = sanitizedOutput.map((item) => isRecord(item) && typeof item.type === "string" ? item.type : typeof item);
70
- const rawCounts = countValues(rawTypes);
71
- const sanitizedCounts = countValues(sanitizedTypes);
72
- const sample = rawOutput.slice(0, 8).map((item, index) => `${index}: ${describeOutputItem(item)}`).join("; ");
73
- return `raw=${rawOutput.length} {${rawCounts}}; sanitized=${sanitizedOutput.length} {${sanitizedCounts}}; sample=${sample || "<empty>"}`;
74
- }
75
-
76
- function countValues(values: readonly string[]): string {
77
- const counts = new Map<string, number>();
78
- for (const value of values) counts.set(value, (counts.get(value) ?? 0) + 1);
79
- return Array.from(counts.entries()).map(([value, count]) => `${value}:${count}`).join(", ");
80
- }
@@ -1,272 +0,0 @@
1
- import type { Api, Model } from "@earendil-works/pi-ai";
2
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
-
4
- export const DEFAULT_SUPPORTED_PROVIDERS = ["openai", "openai-codex"] as const;
5
- export const DEFAULT_SUPPORTED_APIS = ["openai-responses", "openai-codex-responses"] as const;
6
- const OPENAI_COMPACT_PATH = "responses/compact";
7
- const CODEX_COMPACT_PATH = "codex/responses/compact";
8
-
9
- type BuiltInSupportedProvider = (typeof DEFAULT_SUPPORTED_PROVIDERS)[number];
10
- type DefaultSupportedApi = (typeof DEFAULT_SUPPORTED_APIS)[number];
11
-
12
- type RuntimeModel = Model<Api>;
13
-
14
- type NativeCompactionFailureReason =
15
- | "disabled"
16
- | "missing-model"
17
- | "unsupported-provider"
18
- | "unsupported-api"
19
- | "missing-base-url"
20
- | "missing-api-key"
21
- | "unsupported-payload"
22
- | "payload-model-mismatch";
23
-
24
- export type NativeCompactionSupportOptions = {
25
- enabled?: boolean;
26
- supportedProviders?: readonly string[];
27
- supportedApis?: readonly string[];
28
- };
29
-
30
- export type ResponsesCompatibleRequestPayload = {
31
- model: string;
32
- input: unknown[];
33
- instructions?: unknown;
34
- [key: string]: unknown;
35
- };
36
-
37
- export type NativeCompactionRuntime = {
38
- provider: string;
39
- api: DefaultSupportedApi;
40
- apiFamily: DefaultSupportedApi;
41
- model: string;
42
- baseUrl: string;
43
- apiKey?: string;
44
- headers?: Record<string, string>;
45
- compactPath: string;
46
- compactUrl: string;
47
- payload?: ResponsesCompatibleRequestPayload;
48
- currentModel: RuntimeModel;
49
- };
50
-
51
- export type NativeCompactionEnvironmentFailure = {
52
- ok: false;
53
- reason: NativeCompactionFailureReason;
54
- provider?: string;
55
- api?: string;
56
- model?: string;
57
- baseUrl?: string;
58
- };
59
-
60
- export type NativeCompactionEnvironmentSuccess = {
61
- ok: true;
62
- runtime: NativeCompactionRuntime;
63
- };
64
-
65
- export type NativeCompactionEnvironmentResolution =
66
- | NativeCompactionEnvironmentFailure
67
- | NativeCompactionEnvironmentSuccess;
68
-
69
- function normalizeConfiguredSet(values: readonly string[] | undefined, defaults: readonly string[]): Set<string> {
70
- const source = values && values.length > 0 ? values : defaults;
71
- return new Set(source.map((value) => value.trim()).filter((value) => value.length > 0));
72
- }
73
-
74
- export function normalizeBaseUrl(baseUrl: string | undefined | null): string | undefined {
75
- const normalized = baseUrl?.trim().replace(/\/+$/, "");
76
- return normalized ? normalized : undefined;
77
- }
78
-
79
- function buildOpenAICompactUrl(baseUrl: string): string {
80
- const normalized = normalizeBaseUrl(baseUrl) ?? baseUrl;
81
- if (normalized.endsWith("/responses")) {
82
- return `${normalized}/compact`;
83
- }
84
- return `${normalized}/${OPENAI_COMPACT_PATH}`;
85
- }
86
-
87
- function buildCodexCompactUrl(baseUrl: string): string {
88
- const normalized = normalizeBaseUrl(baseUrl) ?? baseUrl;
89
- if (normalized.endsWith("/codex/responses")) {
90
- return `${normalized}/compact`;
91
- }
92
- if (normalized.endsWith("/codex")) {
93
- return `${normalized}/responses/compact`;
94
- }
95
- return `${normalized}/${CODEX_COMPACT_PATH}`;
96
- }
97
-
98
- export function buildCompactUrl(baseUrl: string, api: DefaultSupportedApi): string {
99
- return api === "openai-codex-responses" ? buildCodexCompactUrl(baseUrl) : buildOpenAICompactUrl(baseUrl);
100
- }
101
-
102
- export function buildCompactPath(api: DefaultSupportedApi): string {
103
- return api === "openai-codex-responses" ? CODEX_COMPACT_PATH : OPENAI_COMPACT_PATH;
104
- }
105
-
106
- export function isSupportedProvider(provider: string): provider is BuiltInSupportedProvider {
107
- return (DEFAULT_SUPPORTED_PROVIDERS as readonly string[]).includes(provider);
108
- }
109
-
110
- async function resolveRequestAuth(
111
- ctx: ExtensionContext,
112
- model: RuntimeModel,
113
- ): Promise<{ apiKey?: string; headers?: Record<string, string> }> {
114
- const modelRegistry = ctx.modelRegistry as {
115
- getApiKeyAndHeaders?: (currentModel: RuntimeModel) => Promise<
116
- | { ok: true; apiKey?: string; headers?: Record<string, string> }
117
- | { ok: false; error: string }
118
- >;
119
- };
120
-
121
- if (typeof modelRegistry.getApiKeyAndHeaders !== "function") {
122
- return {};
123
- }
124
-
125
- const auth = await modelRegistry.getApiKeyAndHeaders(model);
126
- return auth.ok ? { apiKey: auth.apiKey, headers: auth.headers } : {};
127
- }
128
-
129
- export function isSupportedApi(api: string): api is DefaultSupportedApi {
130
- return (DEFAULT_SUPPORTED_APIS as readonly string[]).includes(api);
131
- }
132
-
133
- export function isResponsesCompatiblePayload(payload: unknown): payload is ResponsesCompatibleRequestPayload {
134
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
135
- return false;
136
- }
137
-
138
- const candidate = payload as Record<string, unknown>;
139
- return typeof candidate.model === "string" && Array.isArray(candidate.input);
140
- }
141
-
142
- export function getRuntimeModelDescriptor(model: RuntimeModel | undefined): {
143
- provider?: string;
144
- api?: string;
145
- model?: string;
146
- baseUrl?: string;
147
- } {
148
- if (!model) {
149
- return {};
150
- }
151
-
152
- return {
153
- provider: model.provider,
154
- api: model.api,
155
- model: model.id,
156
- baseUrl: normalizeBaseUrl(model.baseUrl),
157
- };
158
- }
159
-
160
- export async function resolveNativeCompactionEnvironment(
161
- ctx: ExtensionContext,
162
- options: NativeCompactionSupportOptions = {},
163
- payload?: unknown,
164
- ): Promise<NativeCompactionEnvironmentResolution> {
165
- if (options.enabled === false) {
166
- return {
167
- ok: false,
168
- reason: "disabled",
169
- };
170
- }
171
-
172
- const currentModel = ctx.model;
173
- const descriptor = getRuntimeModelDescriptor(currentModel);
174
- if (!currentModel || !descriptor.provider || !descriptor.api || !descriptor.model) {
175
- return {
176
- ok: false,
177
- reason: "missing-model",
178
- ...descriptor,
179
- };
180
- }
181
-
182
- const supportedProviders = normalizeConfiguredSet(options.supportedProviders, DEFAULT_SUPPORTED_PROVIDERS);
183
- if (!supportedProviders.has(descriptor.provider)) {
184
- return {
185
- ok: false,
186
- reason: "unsupported-provider",
187
- ...descriptor,
188
- };
189
- }
190
-
191
- const supportedApis = normalizeConfiguredSet(options.supportedApis, DEFAULT_SUPPORTED_APIS);
192
- if (!supportedApis.has(descriptor.api)) {
193
- return {
194
- ok: false,
195
- reason: "unsupported-api",
196
- ...descriptor,
197
- };
198
- }
199
-
200
- if (!isSupportedApi(descriptor.api)) {
201
- return {
202
- ok: false,
203
- reason: "unsupported-api",
204
- ...descriptor,
205
- };
206
- }
207
-
208
- if (!descriptor.baseUrl) {
209
- return {
210
- ok: false,
211
- reason: "missing-base-url",
212
- ...descriptor,
213
- };
214
- }
215
-
216
- let requestPayload: ResponsesCompatibleRequestPayload | undefined;
217
- if (payload !== undefined) {
218
- if (!isResponsesCompatiblePayload(payload)) {
219
- return {
220
- ok: false,
221
- reason: "unsupported-payload",
222
- ...descriptor,
223
- };
224
- }
225
-
226
- if (payload.model !== descriptor.model) {
227
- return {
228
- ok: false,
229
- reason: "payload-model-mismatch",
230
- ...descriptor,
231
- };
232
- }
233
-
234
- requestPayload = payload;
235
- }
236
-
237
- const { apiKey, headers } = await resolveRequestAuth(ctx, currentModel);
238
- const hasAuthorizationHeader = Object.entries(headers ?? {}).some(([key, value]) => key.toLowerCase() === "authorization" && value.trim().length > 0);
239
- if (!apiKey && !hasAuthorizationHeader) {
240
- return {
241
- ok: false,
242
- reason: "missing-api-key",
243
- ...descriptor,
244
- };
245
- }
246
-
247
- return {
248
- ok: true,
249
- runtime: {
250
- provider: descriptor.provider,
251
- api: descriptor.api,
252
- apiFamily: descriptor.api,
253
- model: descriptor.model,
254
- baseUrl: descriptor.baseUrl,
255
- apiKey,
256
- headers,
257
- compactPath: buildCompactPath(descriptor.api),
258
- compactUrl: buildCompactUrl(descriptor.baseUrl, descriptor.api),
259
- payload: requestPayload,
260
- currentModel,
261
- },
262
- };
263
- }
264
-
265
- export async function getNativeCompactionRuntime(
266
- ctx: ExtensionContext,
267
- options: NativeCompactionSupportOptions = {},
268
- payload?: unknown,
269
- ): Promise<NativeCompactionRuntime | undefined> {
270
- const resolution = await resolveNativeCompactionEnvironment(ctx, options, payload);
271
- return resolution.ok ? resolution.runtime : undefined;
272
- }