pi-openai-toolkit 0.7.2 → 0.8.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.
@@ -1,5 +1,5 @@
1
1
  import { writeDebugArtifact } from "./debug";
2
- import { mergeProviderHeaders } from "./provider-headers";
2
+ import { buildResponsesRequestHeaders } from "./responses-headers";
3
3
  import type { NativeCompactionRuntime } from "./runtime";
4
4
  import type { NativeCompactionRequestBody, ResponsesInputItem } from "./serializer";
5
5
  import type { ArtifactContext, CompactionConfig } from "./types";
@@ -114,57 +114,13 @@ function normalizeResponseTimestamp(value: unknown): string | undefined {
114
114
  return Number.isNaN(parsed) ? trimmed : new Date(parsed).toISOString();
115
115
  }
116
116
 
117
- function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
118
- const parts = token.split(".");
119
- if (parts.length !== 3) {
120
- return undefined;
121
- }
122
-
123
- try {
124
- const payloadText = Buffer.from(parts[1]!, "base64url").toString("utf8");
125
- const payload = JSON.parse(payloadText);
126
- return isRecord(payload) ? payload : undefined;
127
- } catch {
128
- return undefined;
129
- }
130
- }
131
-
132
- function extractCodexAccountId(token: string): string | undefined {
133
- const payload = decodeJwtPayload(token);
134
- const authClaims = payload?.["https://api.openai.com/auth"];
135
- if (!isRecord(authClaims)) {
136
- return undefined;
137
- }
138
-
139
- const accountId = authClaims.chatgpt_account_id;
140
- return isNonEmptyString(accountId) ? accountId.trim() : undefined;
141
- }
142
-
143
- function buildCodexUserAgent(): string {
144
- const platform = typeof process !== "undefined" ? process.platform : "browser";
145
- const arch = typeof process !== "undefined" ? process.arch : "unknown";
146
- return `pi (${platform}; ${arch})`;
147
- }
148
-
149
117
  function toHeaders(runtime: NativeCompactionRuntime): Record<string, string> {
150
- const headers = new Headers(mergeProviderHeaders(runtime.currentModel.headers, runtime.headers));
151
- headers.set("accept", SSE_CONTENT_TYPE);
152
- headers.set("content-type", JSON_CONTENT_TYPE);
153
- if (!headers.has("authorization")) {
154
- headers.set("authorization", `Bearer ${runtime.apiKey}`);
155
- }
156
-
157
- if (runtime.api === "openai-codex-responses") {
158
- const accountId = extractCodexAccountId(runtime.apiKey);
159
- if (accountId) {
160
- headers.set("chatgpt-account-id", accountId);
161
- }
162
- headers.set("originator", "pi");
163
- headers.set("user-agent", buildCodexUserAgent());
164
- headers.set("openai-beta", "responses=experimental");
165
- }
166
-
167
- return Object.fromEntries(headers.entries());
118
+ return Object.fromEntries(
119
+ buildResponsesRequestHeaders(runtime, {
120
+ accept: SSE_CONTENT_TYPE,
121
+ contentType: JSON_CONTENT_TYPE,
122
+ }).entries(),
123
+ );
168
124
  }
169
125
 
170
126
  export function buildRemoteV2CompactionRequest(
@@ -0,0 +1,67 @@
1
+ import type { ProviderHeaders } from "@earendil-works/pi-ai";
2
+ import { mergeProviderHeaders } from "./provider-headers";
3
+
4
+ export type ResponsesHeaderRuntime = {
5
+ api: string;
6
+ apiKey: string;
7
+ headers?: ProviderHeaders;
8
+ currentModel: {
9
+ headers?: ProviderHeaders;
10
+ };
11
+ };
12
+
13
+ function isRecord(value: unknown): value is Record<string, unknown> {
14
+ return !!value && typeof value === "object" && !Array.isArray(value);
15
+ }
16
+
17
+ function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
18
+ const parts = token.split(".");
19
+ if (parts.length !== 3) return undefined;
20
+ try {
21
+ const payload = JSON.parse(Buffer.from(parts[1]!, "base64url").toString("utf8")) as unknown;
22
+ return isRecord(payload) ? payload : undefined;
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ }
27
+
28
+ function extractCodexAccountId(token: string): string | undefined {
29
+ const authClaims = decodeJwtPayload(token)?.["https://api.openai.com/auth"];
30
+ if (!isRecord(authClaims)) return undefined;
31
+ const accountId = authClaims.chatgpt_account_id;
32
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : undefined;
33
+ }
34
+
35
+ function buildCodexUserAgent(): string {
36
+ const platform = typeof process !== "undefined" ? process.platform : "browser";
37
+ const arch = typeof process !== "undefined" ? process.arch : "unknown";
38
+ return `pi (${platform}; ${arch})`;
39
+ }
40
+
41
+ export function buildResponsesRequestHeaders(
42
+ runtime: ResponsesHeaderRuntime,
43
+ options: { accept: string; contentType?: string },
44
+ ): Headers {
45
+ const headers = new Headers(
46
+ mergeProviderHeaders(runtime.currentModel.headers, runtime.headers),
47
+ );
48
+ headers.set("accept", options.accept);
49
+ headers.set("content-type", options.contentType ?? "application/json");
50
+ if (!headers.has("authorization")) {
51
+ headers.set("authorization", `Bearer ${runtime.apiKey}`);
52
+ }
53
+
54
+ if (runtime.api === "openai-codex-responses") {
55
+ const accountId = extractCodexAccountId(runtime.apiKey);
56
+ if (accountId) headers.set("chatgpt-account-id", accountId);
57
+ headers.set("originator", "pi");
58
+ headers.set("user-agent", buildCodexUserAgent());
59
+ headers.set("openai-beta", "responses=experimental");
60
+ }
61
+
62
+ return headers;
63
+ }
64
+
65
+ export const _responsesHeadersTest = {
66
+ extractCodexAccountId,
67
+ };
package/src/runtime.ts CHANGED
@@ -7,8 +7,9 @@ const CODEX_RESPONSES_PATH = "codex/responses";
7
7
  const OPENAI_COMPACT_PATH = "responses/compact";
8
8
  const CODEX_COMPACT_PATH = "codex/responses/compact";
9
9
 
10
- type ResponsesCompactApi = (typeof RESPONSES_COMPACT_CAPABLE_APIS)[number];
10
+ export type ResponsesApi = (typeof RESPONSES_COMPACT_CAPABLE_APIS)[number];
11
11
 
12
+ type ResponsesCompactApi = ResponsesApi;
12
13
  type RuntimeModel = Model<Api>;
13
14
 
14
15
  export type ParsedModelSpec = {
@@ -35,6 +36,8 @@ export type NativeCompactionSupportOptions = {
35
36
  responsesApis?: readonly string[];
36
37
  };
37
38
 
39
+ export type ResponsesSupportOptions = NativeCompactionSupportOptions;
40
+
38
41
  export type ResponsesCompatibleRequestPayload = {
39
42
  model: string;
40
43
  input: unknown[];
@@ -42,19 +45,22 @@ export type ResponsesCompatibleRequestPayload = {
42
45
  [key: string]: unknown;
43
46
  };
44
47
 
45
- export type NativeCompactionRuntime = {
48
+ export type ResponsesRuntime = {
46
49
  provider: string;
47
- api: ResponsesCompactApi;
50
+ api: ResponsesApi;
48
51
  model: string;
49
52
  baseUrl: string;
50
53
  apiKey: string;
51
54
  headers?: ProviderHeaders;
52
55
  responsesPath: string;
53
56
  responsesUrl: string;
57
+ currentModel: RuntimeModel;
58
+ };
59
+
60
+ export type NativeCompactionRuntime = ResponsesRuntime & {
54
61
  compactPath: string;
55
62
  compactUrl: string;
56
63
  payload?: ResponsesCompatibleRequestPayload;
57
- currentModel: RuntimeModel;
58
64
  };
59
65
 
60
66
  export type NativeCompactionEnvironmentFailure = {
@@ -77,6 +83,15 @@ export type NativeCompactionEnvironmentResolution =
77
83
  | NativeCompactionEnvironmentFailure
78
84
  | NativeCompactionEnvironmentSuccess;
79
85
 
86
+ export type ResponsesEnvironmentSuccess = {
87
+ ok: true;
88
+ runtime: ResponsesRuntime;
89
+ };
90
+
91
+ export type ResponsesEnvironmentResolution =
92
+ | NativeCompactionEnvironmentFailure
93
+ | ResponsesEnvironmentSuccess;
94
+
80
95
  export type RemoteCompactionExecution = {
81
96
  /** Active session model. Its identity owns replay matching and is never mutated. */
82
97
  consumer: NativeCompactionRuntime;
@@ -327,6 +342,30 @@ async function resolveNativeCompactionEnvironmentForModel(
327
342
  };
328
343
  }
329
344
 
345
+ export async function resolveResponsesEnvironment(
346
+ ctx: ExtensionContext,
347
+ options: ResponsesSupportOptions = {},
348
+ ): Promise<ResponsesEnvironmentResolution> {
349
+ const resolution = await resolveNativeCompactionEnvironmentForModel(ctx, ctx.model, options);
350
+ if (!resolution.ok) return resolution;
351
+
352
+ const runtime = resolution.runtime;
353
+ return {
354
+ ok: true,
355
+ runtime: {
356
+ provider: runtime.provider,
357
+ api: runtime.api,
358
+ model: runtime.model,
359
+ baseUrl: runtime.baseUrl,
360
+ apiKey: runtime.apiKey,
361
+ headers: runtime.headers,
362
+ responsesPath: runtime.responsesPath,
363
+ responsesUrl: runtime.responsesUrl,
364
+ currentModel: runtime.currentModel,
365
+ },
366
+ };
367
+ }
368
+
330
369
  export async function resolveNativeCompactionEnvironment(
331
370
  ctx: ExtensionContext,
332
371
  options: NativeCompactionSupportOptions = {},
package/src/types.ts CHANGED
@@ -4,6 +4,7 @@ import type { CompactionEntry, CompactionResult, ExtensionContext, SessionEntry
4
4
  export const TOOLKIT_ID = "pi-openai-toolkit";
5
5
  export const COMPACTION_EXTENSION_ID = `${TOOLKIT_ID}:compaction`;
6
6
  export const WEB_SEARCH_EXTENSION_ID = `${TOOLKIT_ID}:web-search`;
7
+ export const IMAGE_GENERATION_EXTENSION_ID = `${TOOLKIT_ID}:image-generation`;
7
8
  export const DEFAULT_ARTIFACT_ROOT = "~/.pi/agent/artifacts/pi-openai-toolkit/compaction";
8
9
  export const REDACTED_VALUE = "[REDACTED]";
9
10
  /**
@@ -82,9 +83,16 @@ export type WebSearchConfig = {
82
83
  models: string[];
83
84
  };
84
85
 
86
+ export type ImageGenerationConfig = {
87
+ enabled: boolean;
88
+ /** Exact provider/model keys allowed to use toolkit-native image generation. */
89
+ models: string[];
90
+ };
91
+
85
92
  export type ToolkitConfig = {
86
93
  compaction: CompactionConfig;
87
94
  webSearch: WebSearchConfig;
95
+ imageGeneration: ImageGenerationConfig;
88
96
  };
89
97
 
90
98
  export type LoadedToolkitConfig = {
@@ -438,7 +446,13 @@ export const DEFAULT_WEB_SEARCH_CONFIG: WebSearchConfig = {
438
446
  models: [],
439
447
  };
440
448
 
449
+ export const DEFAULT_IMAGE_GENERATION_CONFIG: ImageGenerationConfig = {
450
+ enabled: true,
451
+ models: [],
452
+ };
453
+
441
454
  export const DEFAULT_TOOLKIT_CONFIG: ToolkitConfig = {
442
455
  compaction: DEFAULT_COMPACTION_CONFIG,
443
456
  webSearch: DEFAULT_WEB_SEARCH_CONFIG,
457
+ imageGeneration: DEFAULT_IMAGE_GENERATION_CONFIG,
444
458
  };
@@ -1,3 +1,4 @@
1
+ import { getExactModelKey, isExactModelAllowed } from "../model-scope";
1
2
  import type { WebSearchConfig } from "../types";
2
3
 
3
4
  export const WEB_SEARCH_CAPABLE_APIS = ["openai-responses", "openai-codex-responses"] as const;
@@ -28,20 +29,17 @@ export type WebSearchPayloadTransform = {
28
29
  };
29
30
 
30
31
  export function getWebSearchModelKey(model: WebSearchModel | undefined): string | undefined {
31
- if (!model?.provider || !model.id) return undefined;
32
- return `${model.provider}/${model.id}`;
32
+ return getExactModelKey(model);
33
33
  }
34
34
 
35
35
  export function isWebSearchEnabledForModel(
36
36
  model: WebSearchModel | undefined,
37
37
  config: WebSearchConfig,
38
38
  ): boolean {
39
- const modelKey = getWebSearchModelKey(model);
40
39
  return (
41
40
  config.enabled &&
42
41
  typeof model?.api === "string" &&
43
42
  (WEB_SEARCH_CAPABLE_APIS as readonly string[]).includes(model.api) &&
44
- modelKey !== undefined &&
45
- config.models.includes(modelKey)
43
+ isExactModelAllowed(model, config.models)
46
44
  );
47
45
  }
@@ -14,6 +14,7 @@
14
14
  "include": [
15
15
  "extensions/compaction.ts",
16
16
  "extensions/web-search.ts",
17
+ "extensions/image-generation.ts",
17
18
  "src/compact-client.ts",
18
19
  "src/config.ts",
19
20
  "src/debug.ts",
@@ -21,9 +22,20 @@
21
22
  "src/deferred-tool-carryover.ts",
22
23
  "src/inline-compaction.ts",
23
24
  "src/extension-runtime.ts",
25
+ "src/image-generation/artifacts.ts",
26
+ "src/image-generation/client.ts",
27
+ "src/image-generation/eligibility.ts",
28
+ "src/image-generation/extension.ts",
29
+ "src/image-generation/protocol.ts",
30
+ "src/image-generation/references.ts",
31
+ "src/image-generation/render.ts",
32
+ "src/image-generation/service.ts",
33
+ "src/image-generation/types.ts",
34
+ "src/model-scope.ts",
24
35
  "src/native-fallback.ts",
25
36
  "src/payload-rewrite.ts",
26
37
  "src/provider-headers.ts",
38
+ "src/responses-headers.ts",
27
39
  "src/remote-v2-client.ts",
28
40
  "src/request-context-cache.ts",
29
41
  "src/runtime.ts",