pi-openai-toolkit 0.14.2 → 0.14.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-openai-toolkit",
3
- "version": "0.14.2",
3
+ "version": "0.14.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -96,6 +96,7 @@
96
96
  "src/web-search/payload.ts",
97
97
  "src/web-search/prompt.ts",
98
98
  "src/web-search/types.ts",
99
+ "docs/internals.md",
99
100
  "README.md",
100
101
  "README.zh.md",
101
102
  "LICENSE",
package/src/config.ts CHANGED
@@ -47,6 +47,7 @@ const COMPACTION_FIELDS = new Set([
47
47
  "remoteCompactModel",
48
48
  "nativeFallback",
49
49
  "responsesApis",
50
+ "gatewayContextModels",
50
51
  "contextReminderThresholdPercent",
51
52
  "notifyOnLoad",
52
53
  "debug",
@@ -347,6 +348,15 @@ function applyCompactionConfig(
347
348
  resolved.responsesApis = apis;
348
349
  }
349
350
 
351
+ const gatewayContextModels = toStringList(
352
+ raw.gatewayContextModels,
353
+ "compaction.gatewayContextModels",
354
+ warnings,
355
+ );
356
+ if (gatewayContextModels !== undefined) {
357
+ resolved.gatewayContextModels = gatewayContextModels;
358
+ }
359
+
350
360
  if (typeof raw.artifactRoot === "string" && raw.artifactRoot.trim().length > 0) {
351
361
  resolved.artifactRoot = raw.artifactRoot.trim();
352
362
  } else if (raw.artifactRoot !== undefined) {
@@ -3,16 +3,15 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { mergeProviderHeaders } from "../provider-headers";
4
4
  import { CODEX_CLIENT_VERSION } from "../responses-headers";
5
5
  import { normalizeBaseUrl } from "../runtime";
6
+ import { isExactModelAllowed } from "../model-scope";
6
7
  import {
7
8
  isNonEmptyString,
8
9
  type CodexContextProvider,
9
10
  type CodexContextProviderResolution,
10
11
  } from "./types";
11
- import { ASTRA_MODEL_ID } from "../types";
12
12
 
13
13
  const CODEX_PROVIDER = "openai-codex";
14
14
  const CODEX_API = "openai-codex-responses";
15
- const GATEWAY_PROVIDER = "uwoacrimson";
16
15
  const GATEWAY_API = "openai-responses";
17
16
  const CODEX_TOOL_ORIGINATOR = "codex_cli_rs";
18
17
  const CODEX_GATEWAY_ORIGINATOR = "codex_cli_rs";
@@ -32,12 +31,14 @@ export function isNativeCodexModel(
32
31
 
33
32
  export function isCodexGatewayModel(
34
33
  model: ExtensionContext["model"] | undefined,
34
+ gatewayModels: 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.
39
- if (!model || model.provider !== GATEWAY_PROVIDER || model.api !== GATEWAY_API) return false;
40
- return model.id === ASTRA_MODEL_ID;
36
+ // Gateway coverage is an operator allowlist of exact "provider/model" keys
37
+ // (compaction.gatewayContextModels). The backend relay must actually pass
38
+ // hosted window markers through; models outside the list stay on remote
39
+ // compaction v2.
40
+ if (!model || model.api !== GATEWAY_API) return false;
41
+ return isExactModelAllowed(model, gatewayModels);
41
42
  }
42
43
 
43
44
  export function normalizeCodexBackendBaseUrl(baseUrl: string | undefined | null): string | undefined {
@@ -172,6 +173,7 @@ export function codexContextProviderHeaders(
172
173
  export async function resolveCodexContextProvider(
173
174
  ctx: ExtensionContext,
174
175
  modelOverride: ExtensionContext["model"] = ctx.model,
176
+ gatewayModels: readonly string[] = [],
175
177
  ): Promise<CodexContextProviderResolution> {
176
178
  const model = modelOverride;
177
179
  const descriptor = {
@@ -183,13 +185,9 @@ export async function resolveCodexContextProvider(
183
185
  if (!model) return { ok: false, reason: "unsupported-model" };
184
186
 
185
187
  const isNative = isNativeCodexModel(model);
186
- const isGateway = isCodexGatewayModel(model);
188
+ const isGateway = isCodexGatewayModel(model, gatewayModels);
187
189
  if (!isNative && !isGateway) {
188
- return {
189
- ok: false,
190
- reason: model.provider === GATEWAY_PROVIDER ? "unsupported-model" : "unsupported-model",
191
- ...descriptor,
192
- };
190
+ return { ok: false, reason: "unsupported-model", ...descriptor };
193
191
  }
194
192
  if (!isNative && model.api !== GATEWAY_API) {
195
193
  return { ok: false, reason: "unsupported-api", ...descriptor };
@@ -217,7 +215,7 @@ export async function resolveCodexContextProvider(
217
215
  provider: {
218
216
  kind: "codex-gateway",
219
217
  route: "codex-gateway",
220
- provider: GATEWAY_PROVIDER,
218
+ provider: model.provider,
221
219
  api: GATEWAY_API,
222
220
  model: model.id,
223
221
  baseUrl: rawBaseUrl,
@@ -78,11 +78,12 @@ export async function callHistoryNotesBackend(
78
78
  mode: "tokens",
79
79
  limit: TOOL_OUTPUT_TOKEN_LIMIT,
80
80
  },
81
+ gatewayModels: readonly string[] = [],
81
82
  ): Promise<HistoryNotesResult> {
82
83
  const timeoutSignal = AbortSignal.timeout(BACKEND_TIMEOUT_MS);
83
84
  if (signal?.aborted) return { ok: false, reason: "aborted" };
84
85
  const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
85
- const providerResult = await resolveProviderWithDeadline(ctx, requestSignal, timeoutSignal);
86
+ const providerResult = await resolveProviderWithDeadline(ctx, requestSignal, timeoutSignal, gatewayModels);
86
87
  if (providerResult === "aborted") return { ok: false, reason: "aborted" };
87
88
  if (providerResult === "timeout") return { ok: false, reason: "backend-timeout" };
88
89
  const provider = providerResult;
@@ -134,6 +135,7 @@ async function resolveProviderWithDeadline(
134
135
  ctx: ExtensionContext,
135
136
  requestSignal: AbortSignal,
136
137
  timeoutSignal: AbortSignal,
138
+ gatewayModels: readonly string[] = [],
137
139
  ): Promise<Awaited<ReturnType<typeof resolveCodexContextProvider>> | "aborted" | "timeout"> {
138
140
  if (requestSignal.aborted) return timeoutSignal.aborted ? "timeout" : "aborted";
139
141
  return new Promise((resolve) => {
@@ -146,7 +148,7 @@ async function resolveProviderWithDeadline(
146
148
  };
147
149
  const onAbort = () => finish(timeoutSignal.aborted ? "timeout" : "aborted");
148
150
  requestSignal.addEventListener("abort", onAbort, { once: true });
149
- resolveCodexContextProvider(ctx, ctx.model).then(
151
+ resolveCodexContextProvider(ctx, ctx.model, gatewayModels).then(
150
152
  (value) => finish(value),
151
153
  () => finish({ ok: false, reason: "auth-resolution-failed" }),
152
154
  );
@@ -156,6 +158,7 @@ async function resolveProviderWithDeadline(
156
158
  export async function loadHistoryNotesThreadHint(
157
159
  ctx: ExtensionContext,
158
160
  signal?: AbortSignal,
161
+ gatewayModels: readonly string[] = [],
159
162
  ): Promise<string | undefined> {
160
163
  const result = await callHistoryNotesBackend(
161
164
  THREAD_HINT_ENDPOINT,
@@ -163,6 +166,7 @@ export async function loadHistoryNotesThreadHint(
163
166
  ctx,
164
167
  signal,
165
168
  { mode: "bytes", limit: THREAD_HINT_MAX_BYTES },
169
+ gatewayModels,
166
170
  );
167
171
  if (!result.ok) {
168
172
  if (result.reason === "aborted") throw new Error("Remote context hint request was aborted");
@@ -178,6 +182,7 @@ export async function executeHistoryNotesTool(
178
182
  params: Record<string, unknown>,
179
183
  ctx: ExtensionContext,
180
184
  signal?: AbortSignal,
185
+ gatewayModels: readonly string[] = [],
181
186
  ): Promise<AgentToolResult<CodexHistoryNotesDetails>> {
182
187
  const endpoint = namespace === "history"
183
188
  ? HISTORY_ENDPOINTS[action as HistoryAction]
@@ -190,6 +195,7 @@ export async function executeHistoryNotesTool(
190
195
  ctx,
191
196
  signal,
192
197
  undefined,
198
+ gatewayModels,
193
199
  );
194
200
  if (!result.ok) throw new Error(formatHistoryNotesFailure(result.reason, result.status));
195
201
  const modelResult = { ...result.value };
@@ -85,6 +85,7 @@ export function createContextManagementTools(
85
85
  pi: ExtensionAPI,
86
86
  manager: CodexContextWindowManager,
87
87
  isActive: (ctx: ExtensionContext) => Promise<boolean> | boolean,
88
+ getGatewayModels: () => readonly string[] = () => [],
88
89
  ): ContextManagementTools {
89
90
  const assertActive = async (ctx: ExtensionContext): Promise<void> => {
90
91
  if (!(await isActive(ctx))) throw new Error("remote-context-inactive");
@@ -142,7 +143,7 @@ export function createContextManagementTools(
142
143
  ],
143
144
  execute: async (_id, params, _signal, _update, ctx) => {
144
145
  await assertActive(ctx);
145
- return executeHistoryNotesTool("history", params.action, params as Record<string, unknown>, ctx, _signal);
146
+ return executeHistoryNotesTool("history", params.action, params as Record<string, unknown>, ctx, _signal, getGatewayModels());
146
147
  },
147
148
  };
148
149
  const notes: ToolDefinition<typeof NOTES_PARAMETERS, CodexHistoryNotesDetails> = {
@@ -161,7 +162,7 @@ export function createContextManagementTools(
161
162
  executionMode: "sequential",
162
163
  execute: async (_id, params, _signal, _update, ctx) => {
163
164
  await assertActive(ctx);
164
- return executeHistoryNotesTool("notes", params.action, params as Record<string, unknown>, ctx, _signal);
165
+ return executeHistoryNotesTool("notes", params.action, params as Record<string, unknown>, ctx, _signal, getGatewayModels());
165
166
  },
166
167
  };
167
168
  return { newContext, getContextRemaining, history, notes };
@@ -293,9 +294,10 @@ export function registerContextManagementTools(
293
294
  pi: ExtensionAPI,
294
295
  manager: CodexContextWindowManager,
295
296
  isActive: (ctx: ExtensionContext) => Promise<boolean> | boolean,
297
+ getGatewayModels?: () => readonly string[],
296
298
  ): ContextManagementToolController {
297
299
  const controller = new ContextManagementToolController(pi);
298
- controller.register(createContextManagementTools(pi, manager, isActive));
300
+ controller.register(createContextManagementTools(pi, manager, isActive, getGatewayModels));
299
301
  return controller;
300
302
  }
301
303
 
@@ -67,7 +67,8 @@ export type NativeCodexContextProvider = {
67
67
  export type GatewayCodexContextProvider = {
68
68
  kind: "codex-gateway";
69
69
  route: "codex-gateway";
70
- provider: "uwoacrimson";
70
+ /** Operator-defined provider name; matched via compaction.gatewayContextModels. */
71
+ provider: string;
71
72
  api: "openai-responses";
72
73
  model: string;
73
74
  baseUrl: string;