pi-openai-toolkit 0.13.1 → 0.14.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.
@@ -8,6 +8,7 @@ import {
8
8
  type CodexContextProvider,
9
9
  type CodexContextProviderResolution,
10
10
  } from "./types";
11
+ import { ASTRA_MODEL_ID } from "../types";
11
12
 
12
13
  const CODEX_PROVIDER = "openai-codex";
13
14
  const CODEX_API = "openai-codex-responses";
@@ -31,11 +32,12 @@ export function isNativeCodexModel(
31
32
 
32
33
  export function isCodexGatewayModel(
33
34
  model: ExtensionContext["model"] | undefined,
34
- allowlist: readonly string[] = [],
35
35
  ): boolean {
36
+ // Only the Astra SKU has a verified Codex-compatible gateway relay path
37
+ // (NEWapi + CLIProxyAPI passthrough); other gateway models stay on remote
38
+ // compaction v2. Matching is on the bare model id, by design.
36
39
  if (!model || model.provider !== GATEWAY_PROVIDER || model.api !== GATEWAY_API) return false;
37
- const spec = `${model.provider}/${model.id}`;
38
- return allowlist.some((entry) => entry.trim() === spec);
40
+ return model.id === ASTRA_MODEL_ID;
39
41
  }
40
42
 
41
43
  export function normalizeCodexBackendBaseUrl(baseUrl: string | undefined | null): string | undefined {
@@ -170,7 +172,6 @@ export function codexContextProviderHeaders(
170
172
  export async function resolveCodexContextProvider(
171
173
  ctx: ExtensionContext,
172
174
  modelOverride: ExtensionContext["model"] = ctx.model,
173
- gatewayAllowlist: readonly string[] = [],
174
175
  ): Promise<CodexContextProviderResolution> {
175
176
  const model = modelOverride;
176
177
  const descriptor = {
@@ -182,7 +183,7 @@ export async function resolveCodexContextProvider(
182
183
  if (!model) return { ok: false, reason: "unsupported-model" };
183
184
 
184
185
  const isNative = isNativeCodexModel(model);
185
- const isGateway = isCodexGatewayModel(model, gatewayAllowlist);
186
+ const isGateway = isCodexGatewayModel(model);
186
187
  if (!isNative && !isGateway) {
187
188
  return {
188
189
  ok: false,
@@ -78,12 +78,11 @@ export async function callHistoryNotesBackend(
78
78
  mode: "tokens",
79
79
  limit: TOOL_OUTPUT_TOKEN_LIMIT,
80
80
  },
81
- gatewayAllowlist: readonly string[] = [],
82
81
  ): Promise<HistoryNotesResult> {
83
82
  const timeoutSignal = AbortSignal.timeout(BACKEND_TIMEOUT_MS);
84
83
  if (signal?.aborted) return { ok: false, reason: "aborted" };
85
84
  const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
86
- const providerResult = await resolveProviderWithDeadline(ctx, requestSignal, timeoutSignal, gatewayAllowlist);
85
+ const providerResult = await resolveProviderWithDeadline(ctx, requestSignal, timeoutSignal);
87
86
  if (providerResult === "aborted") return { ok: false, reason: "aborted" };
88
87
  if (providerResult === "timeout") return { ok: false, reason: "backend-timeout" };
89
88
  const provider = providerResult;
@@ -135,7 +134,6 @@ async function resolveProviderWithDeadline(
135
134
  ctx: ExtensionContext,
136
135
  requestSignal: AbortSignal,
137
136
  timeoutSignal: AbortSignal,
138
- gatewayAllowlist: readonly string[],
139
137
  ): Promise<Awaited<ReturnType<typeof resolveCodexContextProvider>> | "aborted" | "timeout"> {
140
138
  if (requestSignal.aborted) return timeoutSignal.aborted ? "timeout" : "aborted";
141
139
  return new Promise((resolve) => {
@@ -148,7 +146,7 @@ async function resolveProviderWithDeadline(
148
146
  };
149
147
  const onAbort = () => finish(timeoutSignal.aborted ? "timeout" : "aborted");
150
148
  requestSignal.addEventListener("abort", onAbort, { once: true });
151
- resolveCodexContextProvider(ctx, ctx.model, gatewayAllowlist).then(
149
+ resolveCodexContextProvider(ctx, ctx.model).then(
152
150
  (value) => finish(value),
153
151
  () => finish({ ok: false, reason: "auth-resolution-failed" }),
154
152
  );
@@ -158,7 +156,6 @@ async function resolveProviderWithDeadline(
158
156
  export async function loadHistoryNotesThreadHint(
159
157
  ctx: ExtensionContext,
160
158
  signal?: AbortSignal,
161
- gatewayAllowlist: readonly string[] = [],
162
159
  ): Promise<string | undefined> {
163
160
  const result = await callHistoryNotesBackend(
164
161
  THREAD_HINT_ENDPOINT,
@@ -166,7 +163,6 @@ export async function loadHistoryNotesThreadHint(
166
163
  ctx,
167
164
  signal,
168
165
  { mode: "bytes", limit: THREAD_HINT_MAX_BYTES },
169
- gatewayAllowlist,
170
166
  );
171
167
  if (!result.ok) {
172
168
  if (result.reason === "aborted") throw new Error("Remote context hint request was aborted");
@@ -182,7 +178,6 @@ export async function executeHistoryNotesTool(
182
178
  params: Record<string, unknown>,
183
179
  ctx: ExtensionContext,
184
180
  signal?: AbortSignal,
185
- gatewayAllowlist: readonly string[] = [],
186
181
  ): Promise<AgentToolResult<CodexHistoryNotesDetails>> {
187
182
  const endpoint = namespace === "history"
188
183
  ? HISTORY_ENDPOINTS[action as HistoryAction]
@@ -195,7 +190,6 @@ export async function executeHistoryNotesTool(
195
190
  ctx,
196
191
  signal,
197
192
  undefined,
198
- gatewayAllowlist,
199
193
  );
200
194
  if (!result.ok) throw new Error(formatHistoryNotesFailure(result.reason, result.status));
201
195
  const modelResult = { ...result.value };
@@ -85,10 +85,7 @@ export function createContextManagementTools(
85
85
  pi: ExtensionAPI,
86
86
  manager: CodexContextWindowManager,
87
87
  isActive: (ctx: ExtensionContext) => Promise<boolean> | boolean,
88
- gatewayAllowlist: readonly string[] | (() => readonly string[]) = [],
89
88
  ): ContextManagementTools {
90
- const getGatewayAllowlist = (): readonly string[] =>
91
- typeof gatewayAllowlist === "function" ? gatewayAllowlist() : gatewayAllowlist;
92
89
  const assertActive = async (ctx: ExtensionContext): Promise<void> => {
93
90
  if (!(await isActive(ctx))) throw new Error("remote-context-inactive");
94
91
  };
@@ -145,7 +142,7 @@ export function createContextManagementTools(
145
142
  ],
146
143
  execute: async (_id, params, _signal, _update, ctx) => {
147
144
  await assertActive(ctx);
148
- return executeHistoryNotesTool("history", params.action, params as Record<string, unknown>, ctx, _signal, getGatewayAllowlist());
145
+ return executeHistoryNotesTool("history", params.action, params as Record<string, unknown>, ctx, _signal);
149
146
  },
150
147
  };
151
148
  const notes: ToolDefinition<typeof NOTES_PARAMETERS, CodexHistoryNotesDetails> = {
@@ -164,7 +161,7 @@ export function createContextManagementTools(
164
161
  executionMode: "sequential",
165
162
  execute: async (_id, params, _signal, _update, ctx) => {
166
163
  await assertActive(ctx);
167
- return executeHistoryNotesTool("notes", params.action, params as Record<string, unknown>, ctx, _signal, getGatewayAllowlist());
164
+ return executeHistoryNotesTool("notes", params.action, params as Record<string, unknown>, ctx, _signal);
168
165
  },
169
166
  };
170
167
  return { newContext, getContextRemaining, history, notes };
@@ -296,10 +293,9 @@ export function registerContextManagementTools(
296
293
  pi: ExtensionAPI,
297
294
  manager: CodexContextWindowManager,
298
295
  isActive: (ctx: ExtensionContext) => Promise<boolean> | boolean,
299
- gatewayAllowlist: readonly string[] | (() => readonly string[]) = [],
300
296
  ): ContextManagementToolController {
301
297
  const controller = new ContextManagementToolController(pi);
302
- controller.register(createContextManagementTools(pi, manager, isActive, gatewayAllowlist));
298
+ controller.register(createContextManagementTools(pi, manager, isActive));
303
299
  return controller;
304
300
  }
305
301
 
@@ -58,15 +58,9 @@ export class CodexContextWindowManager {
58
58
  private rolloverPending = false;
59
59
  private trimPendingWindowId: string | undefined;
60
60
  private readonly loadThreadHint: ThreadHintLoader;
61
- private readonly getGatewayAllowlist: () => readonly string[];
62
61
 
63
- constructor(
64
- loadThreadHint?: ThreadHintLoader,
65
- gatewayAllowlist: readonly string[] | (() => readonly string[]) = [],
66
- ) {
67
- this.getGatewayAllowlist = typeof gatewayAllowlist === "function" ? gatewayAllowlist : () => gatewayAllowlist;
68
- this.loadThreadHint = loadThreadHint ?? ((ctx, signal) =>
69
- loadHistoryNotesThreadHint(ctx, signal, this.getGatewayAllowlist()));
62
+ constructor(loadThreadHint?: ThreadHintLoader) {
63
+ this.loadThreadHint = loadThreadHint ?? ((ctx, signal) => loadHistoryNotesThreadHint(ctx, signal));
70
64
  }
71
65
 
72
66
  reset(): void {
@@ -109,7 +109,7 @@ async function isRemoteContextActive(
109
109
  model: ExtensionContext["model"] = ctx.model,
110
110
  ): Promise<boolean> {
111
111
  if (!config.enabled || config.contextManagement !== "remote") return false;
112
- const resolution = await resolveCodexContextProvider(ctx, model, config.codexGatewayModels);
112
+ const resolution = await resolveCodexContextProvider(ctx, model);
113
113
  return resolution.ok;
114
114
  }
115
115
 
@@ -117,7 +117,8 @@ function isCodexContextModel(
117
117
  model: ExtensionContext["model"] | undefined,
118
118
  config: CompactionConfig,
119
119
  ): boolean {
120
- return isNativeCodexModel(model) || isCodexGatewayModel(model, config.codexGatewayModels);
120
+ return config.contextManagement === "remote"
121
+ && (isNativeCodexModel(model) || isCodexGatewayModel(model));
121
122
  }
122
123
 
123
124
  function notifyRemoteContextFailure(ctx: ExtensionContext, reason: string): void {
@@ -369,7 +370,7 @@ async function handleSessionBeforeCompact(
369
370
 
370
371
  // Remote Context management owns this eligible Codex session. It persists a
371
372
  // no-summary boundary and deliberately never calls remote_compaction_v2.
372
- if (config.contextManagement === "remote" && isCodexContextModel(ctx.model, config)) {
373
+ if (isCodexContextModel(ctx.model, config)) {
373
374
  if (await remoteContextActive(ctx, config)) {
374
375
  try {
375
376
  dependencies.contextWindows.synchronize(ctx);
@@ -598,7 +599,7 @@ async function handleBeforeProviderRequest(
598
599
  return undefined;
599
600
  }
600
601
  }
601
- if (config.contextManagement === "remote" && isCodexContextModel(ctx.model, config)) {
602
+ if (isCodexContextModel(ctx.model, config)) {
602
603
  // Keep Codex Remote mutually exclusive with the legacy replay
603
604
  // pipeline when authentication or tool ownership is unavailable.
604
605
  return undefined;
@@ -758,10 +759,7 @@ export default function registerCompactionExtension(
758
759
  overrides: Partial<CompactionDependencies> = {},
759
760
  ) {
760
761
  const loadConfig = overrides.loadConfig ?? loadToolkitConfig;
761
- const contextWindows = overrides.contextWindows ?? new CodexContextWindowManager(
762
- undefined,
763
- () => loadConfig().config.compaction.codexGatewayModels,
764
- );
762
+ const contextWindows = overrides.contextWindows ?? new CodexContextWindowManager();
765
763
  const dependencies: CompactionDependencies = {
766
764
  loadConfig,
767
765
  remoteCompact: executeRemoteV2Compaction,
@@ -777,7 +775,6 @@ export default function registerCompactionExtension(
777
775
  const config = dependencies.loadConfig().config.compaction;
778
776
  return tools.isRegistered && await isRemoteContextActive(ctx, config);
779
777
  },
780
- () => dependencies.loadConfig().config.compaction.codexGatewayModels,
781
778
  );
782
779
  const remoteContextActive: RemoteContextActive = async (ctx, config, model = ctx.model) =>
783
780
  tools.isRegistered && await isRemoteContextActive(ctx, config, model);
@@ -804,7 +801,7 @@ export default function registerCompactionExtension(
804
801
  activationReason = "tool-name-conflict";
805
802
  notifyRemoteContextFailure(ctx, activationReason);
806
803
  } else {
807
- const remoteResolution = await resolveCodexContextProvider(ctx, ctx.model, config.codexGatewayModels);
804
+ const remoteResolution = await resolveCodexContextProvider(ctx, ctx.model);
808
805
  activationReason = remoteResolution.ok ? "codex-context-unavailable" : remoteResolution.reason;
809
806
  notifyRemoteContextFailure(ctx, activationReason);
810
807
  }
@@ -858,9 +855,9 @@ export default function registerCompactionExtension(
858
855
  pi.on("before_provider_request", (event, ctx) => handleBeforeProviderRequest(event, ctx, dependencies.loadConfig, contextWindows, remoteContextActive));
859
856
  pi.on("before_provider_headers", async (event, ctx) => {
860
857
  const config = dependencies.loadConfig().config.compaction;
861
- if (config.contextManagement !== "remote" || !isCodexContextModel(ctx.model, config)) return;
858
+ if (!isCodexContextModel(ctx.model, config)) return;
862
859
  if (!(await remoteContextActive(ctx, config))) return;
863
- const provider = await resolveCodexContextProvider(ctx, ctx.model, config.codexGatewayModels);
860
+ const provider = await resolveCodexContextProvider(ctx, ctx.model);
864
861
  if (provider.ok && provider.provider.kind === "codex-gateway") {
865
862
  const sessionId = getSessionId(ctx);
866
863
  const gatewayHeaders = codexContextProviderHeaders(provider.provider, {
@@ -891,7 +888,7 @@ export default function registerCompactionExtension(
891
888
  });
892
889
  pi.on("message_end", (event, ctx) => {
893
890
  const config = dependencies.loadConfig().config.compaction;
894
- if (config.contextManagement !== "remote" || !isCodexContextModel(ctx.model, config) || !tools.isRegistered) return undefined;
891
+ if (!isCodexContextModel(ctx.model, config) || !tools.isRegistered) return undefined;
895
892
  const message = routeContextNamespaceToolMessage(event.message);
896
893
  return message === event.message ? undefined : { message };
897
894
  });
package/src/types.ts CHANGED
@@ -13,6 +13,12 @@ export const REDACTED_VALUE = "[REDACTED]";
13
13
  * `compaction.responsesApis` in config.json may only narrow this set.
14
14
  */
15
15
  export const RESPONSES_COMPACT_CAPABLE_APIS = ["openai-responses", "openai-codex-responses"] as const;
16
+ /**
17
+ * The Codex model whose gateways are verified to speak the alpha window and
18
+ * Astra compatibility protocols. Remote Context and the Astra layer match on
19
+ * this bare model id; no operator allowlist is consulted.
20
+ */
21
+ export const ASTRA_MODEL_ID = "gpt-6-astra";
16
22
  export const LEGACY_NATIVE_COMPACTION_STRATEGY = "openai-native-compact-v1";
17
23
  export const REMOTE_V2_COMPACTION_STRATEGY = "openai-remote-compaction-v2";
18
24
  export const NATIVE_COMPACTION_STRATEGY = REMOTE_V2_COMPACTION_STRATEGY;
@@ -58,8 +64,6 @@ export type CompactionConfig = {
58
64
  enabled: boolean;
59
65
  /** Optional Codex Remote Context management. Disabled by default for compatibility. */
60
66
  contextManagement: ContextManagementMode;
61
- /** Exact provider/model specs allowed to use the extension's CPA-backed Codex gateway. */
62
- codexGatewayModels: string[];
63
67
  /**
64
68
  * Allow a Responses session whose latest compaction was not created by this extension
65
69
  * to restart native compaction from Pi's current serialized session context.
@@ -99,12 +103,6 @@ export type ImageGenerationConfig = {
99
103
  enabled: boolean;
100
104
  };
101
105
 
102
- export type CodexAstraConfig = {
103
- enabled: boolean;
104
- /** Exact provider/model keys that receive the stable-effort `configuration_update` rewrite. */
105
- models: string[];
106
- };
107
-
108
106
  /** `side-effect` reviews bash/write/edit plus extras; `all` reviews every tool call. */
109
107
  export type AutoModeGate = "side-effect" | "all";
110
108
 
@@ -170,7 +168,6 @@ export type ToolkitConfig = {
170
168
  webSearch: WebSearchConfig;
171
169
  imageGeneration: ImageGenerationConfig;
172
170
  autoMode: AutoModeConfig;
173
- codexAstra: CodexAstraConfig;
174
171
  };
175
172
 
176
173
  export type LoadedToolkitConfig = {
@@ -505,7 +502,6 @@ export const DEFAULT_NATIVE_FALLBACK_CONFIG: NativeFallbackConfig = {
505
502
  export const DEFAULT_COMPACTION_CONFIG: CompactionConfig = {
506
503
  enabled: true,
507
504
  contextManagement: "off",
508
- codexGatewayModels: [],
509
505
  allowCompactionContinuityBreak: false,
510
506
  remoteCompactModel: undefined,
511
507
  nativeFallback: { ...DEFAULT_NATIVE_FALLBACK_CONFIG },
@@ -528,11 +524,6 @@ export const DEFAULT_IMAGE_GENERATION_CONFIG: ImageGenerationConfig = {
528
524
  enabled: false,
529
525
  };
530
526
 
531
- export const DEFAULT_CODEX_ASTRA_CONFIG: CodexAstraConfig = {
532
- enabled: false,
533
- models: [],
534
- };
535
-
536
527
  export const DEFAULT_AUTO_MODE_CONFIG: AutoModeConfig = {
537
528
  enabled: true,
538
529
  models: [],
@@ -561,5 +552,4 @@ export const DEFAULT_TOOLKIT_CONFIG: ToolkitConfig = {
561
552
  webSearch: DEFAULT_WEB_SEARCH_CONFIG,
562
553
  imageGeneration: DEFAULT_IMAGE_GENERATION_CONFIG,
563
554
  autoMode: DEFAULT_AUTO_MODE_CONFIG,
564
- codexAstra: DEFAULT_CODEX_ASTRA_CONFIG,
565
555
  };