mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 04:57:09 +02:00
fix(models): expose codex runtime context caps
This commit is contained in:
@@ -91,6 +91,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Feishu: suppress duplicate final card delivery when idle closes a streaming card before the final payload arrives. (#68491) Thanks @MoerAI.
|
||||
- Signal: preserve sender attachment filenames and resolve missing MIME types from those filenames, so Linux `signal-cli` voice notes without `contentType` still enter audio transcription. Fixes #48614. Thanks @mindfury.
|
||||
- Telegram/agents: suppress the phantom "Agent couldn't generate a response" fallback after a reply was already committed through the messaging tool. (#70623) Thanks @chinar-amrutkar.
|
||||
- Models/CLI: show provider runtime `contextTokens` beside native `contextWindow` in `openclaw models list`, and align `openai-codex/gpt-5.5` with Codex's 272K runtime cap plus 400K native window. Fixes #71403.
|
||||
- Dashboard/security: avoid writing tokenized Control UI URLs or SSH hints to runtime logs, keeping gateway bearer fragments out of console-captured logs readable through `logs.tail`. (#70029) Thanks @Ziy1-Tan.
|
||||
- Providers/OpenRouter: treat DeepSeek refs as cache-TTL eligible without injecting Anthropic cache-control markers, aligning context pruning with OpenRouter-managed prompt caching. (#51983) Thanks @QuinnH496.
|
||||
- Control UI/browser: defer temp-dir access-mode constants until Node-only temp-dir resolution runs, preventing browser bundles from crashing when `node:fs` constants are stubbed. (#48930) Thanks @Valentinws.
|
||||
|
||||
@@ -50,6 +50,10 @@ Notes:
|
||||
- `models list --all` includes bundled provider-owned static catalog rows even
|
||||
when you have not authenticated with that provider yet. Those rows still show
|
||||
as unavailable until matching auth is configured.
|
||||
- `models list` keeps native model metadata and runtime caps distinct. In table
|
||||
output, `Ctx` shows `contextTokens/contextWindow` when an effective runtime
|
||||
cap differs from the native context window; JSON rows include `contextTokens`
|
||||
when a provider exposes that cap.
|
||||
- `models list --provider <id>` filters by provider id, such as `moonshot` or
|
||||
`openai-codex`. It does not accept display labels from interactive provider
|
||||
pickers, such as `Moonshot AI`.
|
||||
|
||||
@@ -30,11 +30,9 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
|
||||
`google-gemini-cli`, or `codex-cli` when you want a local CLI backend.
|
||||
Legacy `claude-cli/*`, `google-gemini-cli/*`, and `codex-cli/*` refs migrate
|
||||
back to canonical provider refs with the runtime recorded separately.
|
||||
- GPT-5.5 is currently available through subscription/OAuth routes:
|
||||
`openai-codex/gpt-5.5` in PI or `openai/gpt-5.5` with the Codex app-server
|
||||
harness. The direct API-key route for `openai/gpt-5.5` is supported once
|
||||
OpenAI enables GPT-5.5 on the public API; until then use API-enabled models
|
||||
such as `openai/gpt-5.4` for `OPENAI_API_KEY` setups.
|
||||
- GPT-5.5 is available through `openai-codex/gpt-5.5` in PI, the native
|
||||
Codex app-server harness, and the public OpenAI API when the bundled PI
|
||||
catalog exposes `openai/gpt-5.5` for your install.
|
||||
|
||||
## Plugin-owned provider behavior
|
||||
|
||||
@@ -73,10 +71,10 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
- Provider: `openai`
|
||||
- Auth: `OPENAI_API_KEY`
|
||||
- Optional rotation: `OPENAI_API_KEYS`, `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`, plus `OPENCLAW_LIVE_OPENAI_KEY` (single override)
|
||||
- Example models: `openai/gpt-5.4`, `openai/gpt-5.4-mini`
|
||||
- GPT-5.5 direct API support is future-ready here once OpenAI exposes GPT-5.5 on the API
|
||||
- Verify direct API availability with `openclaw models list --provider openai`
|
||||
before using `openai/gpt-5.5` without the Codex app-server runtime
|
||||
- Example models: `openai/gpt-5.5`, `openai/gpt-5.4`, `openai/gpt-5.4-mini`
|
||||
- GPT-5.5 direct API support depends on the bundled PI catalog version for
|
||||
your install; verify with `openclaw models list --provider openai` before
|
||||
using `openai/gpt-5.5` without the Codex app-server runtime.
|
||||
- CLI: `openclaw onboard --auth-choice openai-api-key`
|
||||
- Default transport is `auto` (WebSocket-first, SSE fallback)
|
||||
- Override per model via `agents.defaults.models["openai/<model>"].params.transport` (`"sse"`, `"websocket"`, or `"auto"`)
|
||||
@@ -133,9 +131,9 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
`User-Agent`) are only attached on native Codex traffic to
|
||||
`chatgpt.com/backend-api`, not generic OpenAI-compatible proxies
|
||||
- Shares the same `/fast` toggle and `params.fastMode` config as direct `openai/*`; OpenClaw maps that to `service_tier=priority`
|
||||
- `openai-codex/gpt-5.5` keeps native `contextWindow = 1000000` and a default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai-codex.models[].contextTokens`
|
||||
- `openai-codex/gpt-5.5` uses the Codex catalog native `contextWindow = 400000` and default runtime `contextTokens = 272000`; override the runtime cap with `models.providers.openai-codex.models[].contextTokens`
|
||||
- Policy note: OpenAI Codex OAuth is explicitly supported for external tools/workflows like OpenClaw.
|
||||
- Current GPT-5.5 access uses this OAuth/subscription route until OpenAI enables GPT-5.5 on the public API.
|
||||
- Use `openai-codex/gpt-5.5` when you want the Codex OAuth/subscription route; use `openai/gpt-5.5` when your API-key setup and local catalog expose the public API route.
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -415,7 +415,7 @@ OpenClaw uses the built-in model catalog. Add custom providers via `models.provi
|
||||
- `request.allowPrivateNetwork`: when `true`, allow HTTPS to `baseUrl` when DNS resolves to private, CGNAT, or similar ranges, via the provider HTTP fetch guard (operator opt-in for trusted self-hosted OpenAI-compatible endpoints). WebSocket uses the same `request` for headers/TLS but not that fetch SSRF gate. Default `false`.
|
||||
- `models.providers.*.models`: explicit provider model catalog entries.
|
||||
- `models.providers.*.models.*.contextWindow`: native model context window metadata.
|
||||
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`.
|
||||
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`; `openclaw models list` shows both values when they differ.
|
||||
- `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
|
||||
- `models.providers.*.models.*.compat.requiresStringContent`: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When `true`, OpenClaw flattens pure text `messages[].content` arrays into plain strings before sending the request.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery`: Bedrock auto-discovery settings root.
|
||||
|
||||
@@ -333,7 +333,7 @@ describe("openai codex provider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses Pi metadata for gpt-5.5 and local launch metadata for gpt-5.5-pro", () => {
|
||||
it("keeps Pi cost metadata but applies Codex context metadata for gpt-5.5", () => {
|
||||
const provider = buildOpenAICodexProviderPlugin();
|
||||
|
||||
const model = provider.resolveDynamicModel?.({
|
||||
@@ -343,7 +343,7 @@ describe("openai codex provider", () => {
|
||||
createCodexTemplate({
|
||||
id: "gpt-5.5",
|
||||
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
||||
contextWindow: 400_000,
|
||||
contextWindow: 272_000,
|
||||
}),
|
||||
) as never,
|
||||
});
|
||||
@@ -358,6 +358,7 @@ describe("openai codex provider", () => {
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
maxTokens: 128_000,
|
||||
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
||||
});
|
||||
@@ -387,7 +388,7 @@ describe("openai codex provider", () => {
|
||||
baseUrl: "https://chatgpt.com/backend-api/codex",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 1_000_000,
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
maxTokens: 128_000,
|
||||
});
|
||||
|
||||
@@ -50,8 +50,8 @@ const OPENAI_CODEX_GPT_54_MODEL_ID = "gpt-5.4";
|
||||
const OPENAI_CODEX_GPT_54_LEGACY_MODEL_ID = "gpt-5.4-codex";
|
||||
const OPENAI_CODEX_GPT_54_PRO_MODEL_ID = "gpt-5.4-pro";
|
||||
const OPENAI_CODEX_GPT_54_MINI_MODEL_ID = "gpt-5.4-mini";
|
||||
const OPENAI_CODEX_GPT_55_NATIVE_CONTEXT_TOKENS = 1_000_000;
|
||||
const OPENAI_CODEX_GPT_55_DEFAULT_CONTEXT_TOKENS = 272_000;
|
||||
const OPENAI_CODEX_GPT_55_CODEX_CONTEXT_TOKENS = 400_000;
|
||||
const OPENAI_CODEX_GPT_55_DEFAULT_RUNTIME_CONTEXT_TOKENS = 272_000;
|
||||
const OPENAI_CODEX_GPT_55_PRO_NATIVE_CONTEXT_TOKENS = 1_000_000;
|
||||
const OPENAI_CODEX_GPT_55_PRO_DEFAULT_CONTEXT_TOKENS = 272_000;
|
||||
const OPENAI_CODEX_GPT_54_NATIVE_CONTEXT_TOKENS = 1_050_000;
|
||||
@@ -188,7 +188,11 @@ function resolveCodexForwardCompatModel(ctx: ProviderResolveDynamicModelContext)
|
||||
| ProviderRuntimeModel
|
||||
| undefined;
|
||||
return (
|
||||
model ??
|
||||
withDefaultCodexContextMetadata({
|
||||
model,
|
||||
contextWindow: OPENAI_CODEX_GPT_55_CODEX_CONTEXT_TOKENS,
|
||||
contextTokens: OPENAI_CODEX_GPT_55_DEFAULT_RUNTIME_CONTEXT_TOKENS,
|
||||
}) ??
|
||||
normalizeModelCompat({
|
||||
id: trimmedModelId,
|
||||
name: trimmedModelId,
|
||||
@@ -198,8 +202,8 @@ function resolveCodexForwardCompatModel(ctx: ProviderResolveDynamicModelContext)
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: OPENAI_CODEX_GPT_55_NATIVE_CONTEXT_TOKENS,
|
||||
contextTokens: OPENAI_CODEX_GPT_55_DEFAULT_CONTEXT_TOKENS,
|
||||
contextWindow: OPENAI_CODEX_GPT_55_CODEX_CONTEXT_TOKENS,
|
||||
contextTokens: OPENAI_CODEX_GPT_55_DEFAULT_RUNTIME_CONTEXT_TOKENS,
|
||||
maxTokens: OPENAI_CODEX_GPT_54_MAX_TOKENS,
|
||||
} as ProviderRuntimeModel)
|
||||
);
|
||||
@@ -280,6 +284,27 @@ function resolveCodexForwardCompatModel(ctx: ProviderResolveDynamicModelContext)
|
||||
);
|
||||
}
|
||||
|
||||
function withDefaultCodexContextMetadata(params: {
|
||||
model: ProviderRuntimeModel | undefined;
|
||||
contextWindow: number;
|
||||
contextTokens: number;
|
||||
}): ProviderRuntimeModel | undefined {
|
||||
if (!params.model) {
|
||||
return undefined;
|
||||
}
|
||||
const contextTokens =
|
||||
typeof params.model.contextTokens === "number"
|
||||
? params.model.contextTokens
|
||||
: typeof params.model.contextWindow === "number" && params.model.contextWindow > 0
|
||||
? Math.min(params.contextTokens, params.model.contextWindow)
|
||||
: params.contextTokens;
|
||||
return {
|
||||
...params.model,
|
||||
contextWindow: params.contextWindow,
|
||||
contextTokens,
|
||||
};
|
||||
}
|
||||
|
||||
async function refreshOpenAICodexOAuthCredential(cred: OAuthCredential) {
|
||||
try {
|
||||
const { refreshOpenAICodexToken } = await import("./openai-codex-provider.runtime.js");
|
||||
|
||||
@@ -231,24 +231,37 @@ function buildDynamicModel(
|
||||
case "openai-codex": {
|
||||
const isLegacyGpt54Alias = lower === "gpt-5.4-codex";
|
||||
if (lower === "gpt-5.5") {
|
||||
return (
|
||||
(params.modelRegistry.find("openai-codex", modelId) as ResolvedModelLike | null) ??
|
||||
cloneTemplate(
|
||||
undefined,
|
||||
modelId,
|
||||
{
|
||||
provider: "openai-codex",
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: OPENAI_CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: OPENROUTER_FALLBACK_COST,
|
||||
contextWindow: 1_000_000,
|
||||
contextTokens: 272_000,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
{},
|
||||
)
|
||||
const model = params.modelRegistry.find(
|
||||
"openai-codex",
|
||||
modelId,
|
||||
) as ResolvedModelLike | null;
|
||||
if (model) {
|
||||
const modelContextTokens = model.contextTokens;
|
||||
const modelContextWindow = model.contextWindow;
|
||||
const contextTokens =
|
||||
typeof modelContextTokens === "number"
|
||||
? modelContextTokens
|
||||
: Math.min(
|
||||
272_000,
|
||||
typeof modelContextWindow === "number" ? modelContextWindow : 272_000,
|
||||
);
|
||||
return { ...model, contextWindow: 400_000, contextTokens };
|
||||
}
|
||||
return cloneTemplate(
|
||||
undefined,
|
||||
modelId,
|
||||
{
|
||||
provider: "openai-codex",
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: OPENAI_CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: OPENROUTER_FALLBACK_COST,
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
maxTokens: 128_000,
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
const template =
|
||||
|
||||
@@ -58,6 +58,7 @@ export function buildOpenAICodexForwardCompatExpectation(
|
||||
baseUrl: string;
|
||||
} {
|
||||
const isGpt54 = id === "gpt-5.4";
|
||||
const isGpt55 = id === "gpt-5.5";
|
||||
const isGpt54Mini = id === "gpt-5.4-mini";
|
||||
const isSpark = id === "gpt-5.3-codex-spark";
|
||||
return {
|
||||
@@ -74,8 +75,8 @@ export function buildOpenAICodexForwardCompatExpectation(
|
||||
: isGpt54Mini
|
||||
? { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }
|
||||
: OPENAI_CODEX_TEMPLATE_MODEL.cost,
|
||||
contextWindow: isGpt54 ? 1_050_000 : isSpark ? 128_000 : 272000,
|
||||
...(isGpt54 ? { contextTokens: 272_000 } : {}),
|
||||
contextWindow: isGpt54 ? 1_050_000 : isGpt55 ? 400_000 : isSpark ? 128_000 : 272000,
|
||||
...(isGpt54 || isGpt55 ? { contextTokens: 272_000 } : {}),
|
||||
maxTokens: 128000,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1156,7 +1156,7 @@ describe("resolveModel", () => {
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 1_000_000,
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
maxTokens: 128_000,
|
||||
});
|
||||
|
||||
@@ -229,6 +229,7 @@ async function buildAllOpenAiCodexRows(opts: { supplementCatalog?: boolean } = {
|
||||
const seenKeys = listRowsModule.appendDiscoveredRows({
|
||||
rows: rows as never,
|
||||
models: loaded.models as never,
|
||||
modelRegistry: loaded.registry as never,
|
||||
context: context as never,
|
||||
});
|
||||
if (opts.supplementCatalog !== false) {
|
||||
@@ -576,6 +577,74 @@ describe("modelsListCommand forward-compat", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses provider runtime metadata for discovered codex gpt-5.5 rows", async () => {
|
||||
mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] });
|
||||
mocks.loadModelRegistry.mockResolvedValueOnce({
|
||||
models: [
|
||||
{
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.5",
|
||||
name: "GPT-5.5",
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
input: ["text", "image"],
|
||||
contextWindow: 272000,
|
||||
maxTokens: 128000,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
],
|
||||
availableKeys: new Set(["openai-codex/gpt-5.5"]),
|
||||
registry: {
|
||||
getAll: () => [
|
||||
{
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.5",
|
||||
name: "GPT-5.5",
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
input: ["text", "image"],
|
||||
contextWindow: 272000,
|
||||
maxTokens: 128000,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
mocks.resolveModelWithRegistry.mockImplementation(
|
||||
({ provider, modelId }: { provider: string; modelId: string }) =>
|
||||
provider === "openai-codex" && modelId === "gpt-5.5"
|
||||
? {
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.5",
|
||||
name: "GPT-5.5",
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
input: ["text", "image"],
|
||||
contextWindow: 400000,
|
||||
contextTokens: 272000,
|
||||
maxTokens: 128000,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const runtime = createRuntime();
|
||||
await modelsListCommand(
|
||||
{ all: true, provider: "openai-codex", json: true },
|
||||
runtime as never,
|
||||
);
|
||||
|
||||
expect(
|
||||
lastPrintedRows<{ key: string; contextWindow: number; contextTokens?: number }>(),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
key: "openai-codex/gpt-5.5",
|
||||
contextWindow: 400000,
|
||||
contextTokens: 272000,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("suppresses direct openai gpt-5.3-codex-spark rows in --all output", async () => {
|
||||
mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] });
|
||||
const rows: unknown[] = [];
|
||||
|
||||
@@ -15,6 +15,23 @@ const OPENROUTER_MODEL = {
|
||||
} as const;
|
||||
|
||||
describe("toModelRow", () => {
|
||||
it("keeps native context metadata and effective runtime context tokens distinct", () => {
|
||||
const row = toModelRow({
|
||||
model: {
|
||||
...OPENROUTER_MODEL,
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
} as never,
|
||||
key: "openrouter/openai/gpt-5.4",
|
||||
tags: [],
|
||||
});
|
||||
|
||||
expect(row).toMatchObject({
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("marks models available from auth profiles without loading model discovery", () => {
|
||||
const authStore: AuthProfileStore = {
|
||||
version: 1,
|
||||
|
||||
@@ -11,6 +11,7 @@ export type ListRowModel = {
|
||||
input: Array<"text" | "image">;
|
||||
baseUrl?: string;
|
||||
contextWindow?: number | null;
|
||||
contextTokens?: number | null;
|
||||
};
|
||||
|
||||
export type ModelAuthAvailabilityResolver = (params: {
|
||||
@@ -97,6 +98,7 @@ export function toModelRow(params: {
|
||||
name: model.name || model.id,
|
||||
input,
|
||||
contextWindow: model.contextWindow ?? null,
|
||||
...(typeof model.contextTokens === "number" ? { contextTokens: model.contextTokens } : {}),
|
||||
local,
|
||||
available,
|
||||
tags: Array.from(mergedTags),
|
||||
|
||||
@@ -57,6 +57,7 @@ export async function appendAllModelRowSources(
|
||||
appendDiscoveredRows({
|
||||
rows: params.rows,
|
||||
models: params.modelRegistry.getAll(),
|
||||
modelRegistry: params.modelRegistry,
|
||||
context: params.context,
|
||||
});
|
||||
}
|
||||
@@ -66,6 +67,7 @@ export async function appendAllModelRowSources(
|
||||
const seenKeys = appendDiscoveredRows({
|
||||
rows: params.rows,
|
||||
models: params.modelRegistry?.getAll() ?? [],
|
||||
modelRegistry: params.modelRegistry,
|
||||
context: params.context,
|
||||
});
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ function toConfiguredProviderListModel(params: {
|
||||
baseUrl: params.model.baseUrl ?? params.providerConfig.baseUrl,
|
||||
input: resolveConfiguredModelInput({ model: params.model }),
|
||||
contextWindow: params.model.contextWindow ?? DEFAULT_CONTEXT_TOKENS,
|
||||
contextTokens: params.model.contextTokens,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,6 +144,7 @@ function shouldListConfiguredProviderModel(params: {
|
||||
export function appendDiscoveredRows(params: {
|
||||
rows: ModelRow[];
|
||||
models: Model<Api>[];
|
||||
modelRegistry?: ModelRegistry;
|
||||
context: RowBuilderContext;
|
||||
}): Set<string> {
|
||||
const seenKeys = new Set<string>();
|
||||
@@ -156,7 +158,26 @@ export function appendDiscoveredRows(params: {
|
||||
|
||||
for (const model of sorted) {
|
||||
const key = modelKey(model.provider, model.id);
|
||||
appendVisibleRow({ rows: params.rows, model, key, context: params.context, seenKeys });
|
||||
const resolvedModel = params.modelRegistry
|
||||
? resolveModelWithRegistry({
|
||||
provider: model.provider,
|
||||
modelId: model.id,
|
||||
modelRegistry: params.modelRegistry,
|
||||
cfg: params.context.cfg,
|
||||
agentDir: params.context.agentDir,
|
||||
})
|
||||
: undefined;
|
||||
const rowModel =
|
||||
resolvedModel && modelKey(resolvedModel.provider, resolvedModel.id) === key
|
||||
? resolvedModel
|
||||
: model;
|
||||
appendVisibleRow({
|
||||
rows: params.rows,
|
||||
model: rowModel,
|
||||
key,
|
||||
context: params.context,
|
||||
seenKeys,
|
||||
});
|
||||
}
|
||||
|
||||
return seenKeys;
|
||||
|
||||
26
src/commands/models/list.table.test.ts
Normal file
26
src/commands/models/list.table.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { printModelTable } from "./list.table.js";
|
||||
import type { ModelRow } from "./list.types.js";
|
||||
|
||||
describe("printModelTable", () => {
|
||||
it("prints effective and native context values when a runtime cap differs", () => {
|
||||
const runtime = { log: vi.fn(), error: vi.fn() };
|
||||
const rows: ModelRow[] = [
|
||||
{
|
||||
key: "openai-codex/gpt-5.5",
|
||||
name: "GPT-5.5",
|
||||
input: "text+image",
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
local: false,
|
||||
available: true,
|
||||
tags: [],
|
||||
missing: false,
|
||||
},
|
||||
];
|
||||
|
||||
printModelTable(rows, runtime as never);
|
||||
|
||||
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("266k/391k"));
|
||||
});
|
||||
});
|
||||
@@ -7,10 +7,22 @@ import { formatTokenK } from "./shared.js";
|
||||
|
||||
const MODEL_PAD = 42;
|
||||
const INPUT_PAD = 10;
|
||||
const CTX_PAD = 8;
|
||||
const CTX_PAD = 11;
|
||||
const LOCAL_PAD = 5;
|
||||
const AUTH_PAD = 5;
|
||||
|
||||
function formatContextLabel(row: ModelRow): string {
|
||||
if (
|
||||
typeof row.contextTokens === "number" &&
|
||||
Number.isFinite(row.contextTokens) &&
|
||||
row.contextTokens > 0 &&
|
||||
row.contextTokens !== row.contextWindow
|
||||
) {
|
||||
return `${formatTokenK(row.contextTokens)}/${formatTokenK(row.contextWindow)}`;
|
||||
}
|
||||
return formatTokenK(row.contextWindow);
|
||||
}
|
||||
|
||||
export function printModelTable(
|
||||
rows: ModelRow[],
|
||||
runtime: RuntimeEnv,
|
||||
@@ -45,7 +57,7 @@ export function printModelTable(
|
||||
for (const row of rows) {
|
||||
const keyLabel = pad(truncate(sanitizeTerminalText(row.key), MODEL_PAD), MODEL_PAD);
|
||||
const inputLabel = pad(sanitizeTerminalText(row.input) || "-", INPUT_PAD);
|
||||
const ctxLabel = pad(formatTokenK(row.contextWindow), CTX_PAD);
|
||||
const ctxLabel = pad(formatContextLabel(row), CTX_PAD);
|
||||
const localText = row.local === null ? "-" : row.local ? "yes" : "no";
|
||||
const localLabel = pad(localText, LOCAL_PAD);
|
||||
const authText = row.available === null ? "-" : row.available ? "yes" : "no";
|
||||
|
||||
@@ -10,6 +10,7 @@ export type ModelRow = {
|
||||
name: string;
|
||||
input: string;
|
||||
contextWindow: number | null;
|
||||
contextTokens?: number;
|
||||
local: boolean | null;
|
||||
available: boolean | null;
|
||||
tags: string[];
|
||||
|
||||
@@ -563,7 +563,7 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr
|
||||
});
|
||||
});
|
||||
|
||||
it("uses Pi registry metadata for codex gpt-5.5 models", () => {
|
||||
it("keeps Pi cost metadata but applies Codex context metadata for gpt-5.5 models", () => {
|
||||
const provider = requireProviderContractProvider("openai-codex");
|
||||
const model = provider.resolveDynamicModel?.({
|
||||
provider: "openai-codex",
|
||||
@@ -578,7 +578,7 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
input: ["text", "image"],
|
||||
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
|
||||
contextWindow: 400_000,
|
||||
contextWindow: 272_000,
|
||||
maxTokens: 128_000,
|
||||
})
|
||||
: null,
|
||||
@@ -590,6 +590,7 @@ export function describeOpenAIProviderRuntimeContract(load: ProviderRuntimeContr
|
||||
provider: "openai-codex",
|
||||
api: "openai-codex-responses",
|
||||
contextWindow: 400_000,
|
||||
contextTokens: 272_000,
|
||||
maxTokens: 128_000,
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user