Skip to content

Commit f2b01bb

Browse files
authored
feat(openai): add chat-latest model override
Add openai/chat-latest as an explicit direct API-key OpenAI model override, document the moving alias, and normalize unsupported Responses text verbosity for that model.
1 parent 5852f5d commit f2b01bb

6 files changed

Lines changed: 190 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
1616
- Docs/iMessage: deprecate BlueBubbles for new OpenClaw setups, document the upstream server-release rationale, and point new iMessage deployments toward the native `imsg` path while keeping BlueBubbles as a supported legacy fallback.
1717
- Discord/voice: make voice capture less choppy by extending the default post-speech silence grace to 2.5s, add `voice.captureSilenceGraceMs` for noisy Discord sessions, and tighten the spoken-output prompt around live STT fragments. Thanks @vincentkoc.
1818
- Discord/streaming: default Discord replies to progress draft previews so tool/work activity appears in one edited Discord message unless `channels.discord.streaming.mode` is set to `off`.
19+
- OpenAI: support `openai/chat-latest` as an explicit direct API-key model override for trying the moving ChatGPT Instant API alias without changing the stable default model.
1920
- Plugins/install: add `npm-pack:<path.tgz>` installs so local npm pack artifacts run through the same managed npm-root install, lockfile verification, dependency scan, and install-record path as registry npm plugins.
2021
- Codex app-server: disarm the short post-tool completion watchdog after current-turn activity, expose `appServer.turnCompletionIdleTimeoutMs`, and include raw assistant item context in idle-timeout diagnostics so status-only post-tool stalls stop failing as idle. Fixes #77984. Thanks @roseware-dev and @rubencu.
2122
- Plugin skills/Windows: publish plugin-provided skill directories as junctions on Windows so standard users without Developer Mode can register plugin skills without symlink EPERM failures. Fixes #77958. (#77971) Thanks @hclsys and @jarro.

docs/providers/openai.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ changing config.
3232
| ---------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------- |
3333
| ChatGPT/Codex subscription with native Codex runtime | `openai/gpt-5.5` plus `agentRuntime.id: "codex"` | Recommended Codex setup for most users. Sign in with `openai-codex` auth. |
3434
| Direct API-key billing | `openai/gpt-5.5` | Set `OPENAI_API_KEY` or run OpenAI API-key onboarding. |
35+
| Latest ChatGPT Instant API alias | `openai/chat-latest` | Direct API-key only. Moving alias for experiments, not the default. |
3536
| ChatGPT/Codex subscription auth through PI | `openai-codex/gpt-5.5` | Use only when you intentionally want the normal PI runner. |
3637
| Image generation or editing | `openai/gpt-image-2` | Works with either `OPENAI_API_KEY` or OpenAI Codex OAuth. |
3738
| Transparent-background images | `openai/gpt-image-1.5` | Use `outputFormat=png` or `webp` and `openai.background=transparent`. |
@@ -165,6 +166,23 @@ Choose your preferred auth method and follow the setup steps.
165166
}
166167
```
167168

169+
To try ChatGPT's current Instant model from the OpenAI API, set the model
170+
to `openai/chat-latest`:
171+
172+
```json5
173+
{
174+
env: { OPENAI_API_KEY: "sk-..." },
175+
agents: { defaults: { model: { primary: "openai/chat-latest" } } },
176+
}
177+
```
178+
179+
`chat-latest` is a moving alias. OpenAI documents it as the latest Instant
180+
model used in ChatGPT and recommends `gpt-5.5` for production API usage, so
181+
keep `openai/gpt-5.5` as the stable default unless you explicitly want that
182+
alias behavior. The alias currently accepts only `medium` text verbosity, so
183+
OpenClaw normalizes incompatible OpenAI text-verbosity overrides for this
184+
model.
185+
168186
<Warning>
169187
OpenClaw does **not** expose `openai/gpt-5.3-codex-spark`. Live OpenAI API requests reject that model, and the current Codex catalog does not expose it either.
170188
</Warning>

extensions/openai/openai-provider.live.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest";
55
import { buildOpenAIProvider } from "./openai-provider.js";
66

77
const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "";
8-
const DEFAULT_LIVE_MODEL_IDS = ["gpt-5.5", "gpt-5.4-mini", "gpt-5.4-nano"] as const;
8+
const DEFAULT_LIVE_MODEL_IDS = ["chat-latest", "gpt-5.5", "gpt-5.4-mini", "gpt-5.4-nano"] as const;
99
const liveEnabled = OPENAI_API_KEY.trim().length > 0 && process.env.OPENCLAW_LIVE_TEST === "1";
1010
const describeLive = liveEnabled ? describe : describe.skip;
1111

@@ -16,6 +16,8 @@ type LiveModelCase = {
1616
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
1717
contextWindow: number;
1818
maxTokens: number;
19+
reasoning: boolean;
20+
textVerbosity: "low" | "medium";
1921
};
2022

2123
function findOpenAIModel(modelId: string): Model<Api> | null {
@@ -24,6 +26,17 @@ function findOpenAIModel(modelId: string): Model<Api> | null {
2426

2527
function resolveLiveModelCase(modelId: string): LiveModelCase {
2628
switch (modelId) {
29+
case "chat-latest":
30+
return {
31+
modelId,
32+
templateId: "gpt-5.5",
33+
templateName: "GPT-5.5",
34+
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
35+
contextWindow: 400_000,
36+
maxTokens: 128_000,
37+
reasoning: false,
38+
textVerbosity: "medium",
39+
};
2740
case "gpt-5.5":
2841
return {
2942
modelId,
@@ -32,6 +45,8 @@ function resolveLiveModelCase(modelId: string): LiveModelCase {
3245
cost: { input: 5, output: 30, cacheRead: 0, cacheWrite: 0 },
3346
contextWindow: 1_000_000,
3447
maxTokens: 128_000,
48+
reasoning: true,
49+
textVerbosity: "low",
3550
};
3651
case "gpt-5.5-pro":
3752
return {
@@ -41,6 +56,8 @@ function resolveLiveModelCase(modelId: string): LiveModelCase {
4156
cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 },
4257
contextWindow: 1_000_000,
4358
maxTokens: 128_000,
59+
reasoning: true,
60+
textVerbosity: "low",
4461
};
4562
case "gpt-5.4":
4663
return {
@@ -50,6 +67,8 @@ function resolveLiveModelCase(modelId: string): LiveModelCase {
5067
cost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
5168
contextWindow: 400_000,
5269
maxTokens: 128_000,
70+
reasoning: true,
71+
textVerbosity: "low",
5372
};
5473
case "gpt-5.4-pro":
5574
return {
@@ -59,6 +78,8 @@ function resolveLiveModelCase(modelId: string): LiveModelCase {
5978
cost: { input: 21, output: 168, cacheRead: 0, cacheWrite: 0 },
6079
contextWindow: 400_000,
6180
maxTokens: 128_000,
81+
reasoning: true,
82+
textVerbosity: "low",
6283
};
6384
case "gpt-5.4-mini":
6485
return {
@@ -68,6 +89,8 @@ function resolveLiveModelCase(modelId: string): LiveModelCase {
6889
cost: { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0 },
6990
contextWindow: 400_000,
7091
maxTokens: 128_000,
92+
reasoning: true,
93+
textVerbosity: "low",
7194
};
7295
case "gpt-5.4-nano":
7396
return {
@@ -77,6 +100,8 @@ function resolveLiveModelCase(modelId: string): LiveModelCase {
77100
cost: { input: 0.05, output: 0.4, cacheRead: 0.005, cacheWrite: 0 },
78101
contextWindow: 400_000,
79102
maxTokens: 128_000,
103+
reasoning: true,
104+
textVerbosity: "low",
80105
};
81106
default:
82107
throw new Error(`Unsupported live OpenAI model: ${modelId}`);
@@ -113,7 +138,7 @@ describeLive("buildOpenAIProvider live", () => {
113138
provider: "openai",
114139
api: "openai-completions",
115140
baseUrl: "https://api.openai.com/v1",
116-
reasoning: true,
141+
reasoning: liveCase.reasoning,
117142
input: ["text", "image"],
118143
cost: liveCase.cost,
119144
contextWindow: liveCase.contextWindow,
@@ -146,6 +171,7 @@ describeLive("buildOpenAIProvider live", () => {
146171
id: liveCase.modelId,
147172
api: "openai-responses",
148173
baseUrl: "https://api.openai.com/v1",
174+
reasoning: liveCase.reasoning,
149175
});
150176

151177
const client = new OpenAI({
@@ -158,8 +184,8 @@ describeLive("buildOpenAIProvider live", () => {
158184
instructions: "Return exactly OK and no other text.",
159185
input: "Return exactly OK.",
160186
max_output_tokens: 64,
161-
reasoning: { effort: "none" },
162-
text: { verbosity: "low" },
187+
...(liveCase.reasoning ? { reasoning: { effort: "none" as const } } : {}),
188+
text: { verbosity: liveCase.textVerbosity },
163189
});
164190

165191
expect(response.output_text.trim()).toMatch(/^OK[.!]?$/);

extensions/openai/openai-provider.test.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,60 @@ describe("buildOpenAIProvider", () => {
282282
});
283283
});
284284

285+
it("resolves chat-latest as an explicit direct API model override", () => {
286+
const provider = buildOpenAIProvider();
287+
288+
const model = provider.resolveDynamicModel?.({
289+
provider: "openai",
290+
modelId: "chat-latest",
291+
modelRegistry: {
292+
find: (_provider: string, id: string) =>
293+
id === "gpt-5.5"
294+
? {
295+
id,
296+
name: "GPT-5.5",
297+
provider: "openai",
298+
api: "openai-responses",
299+
baseUrl: "https://api.openai.com/v1",
300+
reasoning: true,
301+
input: ["text", "image"],
302+
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
303+
contextWindow: 1_050_000,
304+
maxTokens: 128_000,
305+
}
306+
: null,
307+
} as never,
308+
});
309+
310+
expect(model).toMatchObject({
311+
provider: "openai",
312+
id: "chat-latest",
313+
api: "openai-responses",
314+
baseUrl: "https://api.openai.com/v1",
315+
reasoning: false,
316+
input: ["text", "image"],
317+
contextWindow: 400_000,
318+
maxTokens: 128_000,
319+
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
320+
});
321+
322+
const fallback = provider.resolveDynamicModel?.({
323+
provider: "openai",
324+
modelId: "chat-latest",
325+
modelRegistry: { find: () => null },
326+
} as never);
327+
328+
expect(fallback).toMatchObject({
329+
provider: "openai",
330+
id: "chat-latest",
331+
api: "openai-responses",
332+
reasoning: false,
333+
contextWindow: 400_000,
334+
maxTokens: 128_000,
335+
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 },
336+
});
337+
});
338+
285339
it("leaves gpt-5.5 to Pi and resolves gpt-5.5-pro locally", () => {
286340
const provider = buildOpenAIProvider();
287341

@@ -340,7 +394,7 @@ describe("buildOpenAIProvider", () => {
340394
});
341395
});
342396

343-
it("surfaces gpt-5.5 in xhigh without synthetic catalog metadata", () => {
397+
it("keeps chat-latest and gpt-5.5 out of synthetic catalog metadata", () => {
344398
const provider = buildOpenAIProvider();
345399

346400
expect(
@@ -363,6 +417,12 @@ describe("buildOpenAIProvider", () => {
363417
id: "gpt-5.5",
364418
}),
365419
);
420+
expect(entries).not.toContainEqual(
421+
expect.objectContaining({
422+
provider: "openai",
423+
id: "chat-latest",
424+
}),
425+
);
366426
});
367427

368428
it("keeps modern live selection on OpenAI 5.2+ and current Codex models", () => {
@@ -387,6 +447,12 @@ describe("buildOpenAIProvider", () => {
387447
modelId: "gpt-5.4",
388448
} as never),
389449
).toBe(true);
450+
expect(
451+
provider.isModernModelRef?.({
452+
provider: "openai",
453+
modelId: "chat-latest",
454+
} as never),
455+
).toBe(true);
390456
expect(
391457
provider.isModernModelRef?.({
392458
provider: "openai",
@@ -521,6 +587,40 @@ describe("buildOpenAIProvider", () => {
521587
expect(result.payload.tools).toEqual([{ type: "web_search" }]);
522588
});
523589

590+
it("clamps chat-latest text verbosity to the only live-supported value", () => {
591+
const provider = buildOpenAIProvider();
592+
const wrap = provider.wrapStreamFn;
593+
expect(wrap).toBeTypeOf("function");
594+
if (!wrap) {
595+
throw new Error("expected OpenAI wrapper");
596+
}
597+
const extraParams = provider.prepareExtraParams?.({
598+
provider: "openai",
599+
modelId: "chat-latest",
600+
extraParams: {
601+
textVerbosity: "low",
602+
},
603+
} as never);
604+
const result = runWrappedPayloadCase({
605+
wrap,
606+
provider: "openai",
607+
modelId: "chat-latest",
608+
extraParams: extraParams ?? undefined,
609+
model: {
610+
api: "openai-responses",
611+
provider: "openai",
612+
id: "chat-latest",
613+
baseUrl: "https://api.openai.com/v1",
614+
contextWindow: 400_000,
615+
} as Model<"openai-responses">,
616+
payload: {
617+
text: { verbosity: "high" },
618+
},
619+
});
620+
621+
expect(result.payload.text).toEqual({ verbosity: "medium" });
622+
});
623+
524624
it("uses native OpenAI web search instead of the managed web_search function", () => {
525625
const provider = buildOpenAIProvider();
526626
const wrap = provider.wrapStreamFn;

extensions/openai/openai-provider.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { resolveOpenAIThinkingProfile } from "./thinking-policy.js";
2424

2525
const PROVIDER_ID = "openai";
26+
const OPENAI_CHAT_LATEST_MODEL_ID = "chat-latest";
2627
const OPENAI_GPT_55_MODEL_ID = "gpt-5.5";
2728
const OPENAI_GPT_55_PRO_MODEL_ID = "gpt-5.5-pro";
2829
const OPENAI_GPT_54_MODEL_ID = "gpt-5.4";
@@ -35,6 +36,7 @@ const OPENAI_GPT_54_PRO_CONTEXT_TOKENS = 1_050_000;
3536
const OPENAI_GPT_54_MINI_CONTEXT_TOKENS = 400_000;
3637
const OPENAI_GPT_54_NANO_CONTEXT_TOKENS = 400_000;
3738
const OPENAI_GPT_54_MAX_TOKENS = 128_000;
39+
const OPENAI_CHAT_LATEST_COST = { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 } as const;
3840
const OPENAI_GPT_55_PRO_COST = { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 } as const;
3941
const OPENAI_GPT_54_COST = { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 } as const;
4042
const OPENAI_GPT_54_PRO_COST = { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 } as const;
@@ -60,7 +62,13 @@ const OPENAI_GPT_54_TEMPLATE_MODEL_IDS = ["gpt-5.2"] as const;
6062
const OPENAI_GPT_54_PRO_TEMPLATE_MODEL_IDS = ["gpt-5.2-pro", "gpt-5.2"] as const;
6163
const OPENAI_GPT_54_MINI_TEMPLATE_MODEL_IDS = ["gpt-5-mini"] as const;
6264
const OPENAI_GPT_54_NANO_TEMPLATE_MODEL_IDS = ["gpt-5-nano", "gpt-5-mini"] as const;
65+
const OPENAI_CHAT_LATEST_TEMPLATE_MODEL_IDS = [
66+
OPENAI_GPT_55_MODEL_ID,
67+
OPENAI_GPT_54_MODEL_ID,
68+
"gpt-5.2",
69+
] as const;
6370
const OPENAI_MODERN_MODEL_IDS = [
71+
OPENAI_CHAT_LATEST_MODEL_ID,
6472
OPENAI_GPT_55_MODEL_ID,
6573
OPENAI_GPT_55_PRO_MODEL_ID,
6674
OPENAI_GPT_54_MODEL_ID,
@@ -69,6 +77,7 @@ const OPENAI_MODERN_MODEL_IDS = [
6977
OPENAI_GPT_54_NANO_MODEL_ID,
7078
"gpt-5.2",
7179
] as const;
80+
7281
function shouldUseOpenAIResponsesTransport(params: {
7382
provider: string;
7483
api?: string | null;
@@ -106,7 +115,19 @@ function resolveOpenAIGptForwardCompatModel(ctx: ProviderResolveDynamicModelCont
106115
const lower = normalizeLowercaseStringOrEmpty(trimmedModelId);
107116
let templateIds: readonly string[];
108117
let patch: Partial<ProviderRuntimeModel>;
109-
if (lower === OPENAI_GPT_55_PRO_MODEL_ID) {
118+
if (lower === OPENAI_CHAT_LATEST_MODEL_ID) {
119+
templateIds = OPENAI_CHAT_LATEST_TEMPLATE_MODEL_IDS;
120+
patch = {
121+
api: "openai-responses",
122+
provider: PROVIDER_ID,
123+
baseUrl: "https://api.openai.com/v1",
124+
reasoning: false,
125+
input: ["text", "image"],
126+
cost: OPENAI_CHAT_LATEST_COST,
127+
contextWindow: 400_000,
128+
maxTokens: OPENAI_GPT_54_MAX_TOKENS,
129+
};
130+
} else if (lower === OPENAI_GPT_55_PRO_MODEL_ID) {
110131
templateIds = OPENAI_GPT_55_PRO_TEMPLATE_MODEL_IDS;
111132
patch = {
112133
api: "openai-responses",
@@ -182,7 +203,7 @@ function resolveOpenAIGptForwardCompatModel(ctx: ProviderResolveDynamicModelCont
182203
id: trimmedModelId,
183204
name: trimmedModelId,
184205
...patch,
185-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
206+
cost: patch.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
186207
contextWindow: patch.contextWindow ?? DEFAULT_CONTEXT_TOKENS,
187208
maxTokens: patch.maxTokens ?? DEFAULT_CONTEXT_TOKENS,
188209
} as ProviderRuntimeModel)

0 commit comments

Comments
 (0)