mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(ai,coding-agent): add GPT-6 Sol and Luna support
This commit is contained in:
parent
3a624b82dd
commit
db91e03319
7 changed files with 154 additions and 46 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Added
|
||||
|
||||
- Added GPT-6 Sol and GPT-6 Luna for OpenAI API keys and OpenAI Codex subscriptions, with full reasoning-effort, prompt-caching, tool-search, long-context pricing, and official cost metadata.
|
||||
- Added Claude Opus 5.5 to the built-in Anthropic model catalog with adaptive thinking, 1M context, and official pricing metadata.
|
||||
- Added Grok 4.7 to the built-in xAI model catalog with long-context pricing metadata.
|
||||
|
||||
|
|
|
|||
|
|
@ -371,6 +371,8 @@ const OPENAI_TOOL_SEARCH_MODEL_IDS = new Set([
|
|||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-6-astra",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
]);
|
||||
const OPENAI_ADDITIONAL_TOOLS_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS;
|
||||
const OPENAI_MID_CONVO_SYSTEM_MESSAGE_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS;
|
||||
|
|
@ -379,6 +381,8 @@ const OPENAI_CODEX_ADDITIONAL_TOOLS_MODEL_IDS = new Set([
|
|||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-6-astra",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
]);
|
||||
const OPENAI_LONG_CONTEXT_INPUT_THRESHOLD = 272000;
|
||||
const OPENAI_SHORT_CONTEXT_CAPPED_MODEL_IDS = new Set([
|
||||
|
|
@ -388,6 +392,8 @@ const OPENAI_SHORT_CONTEXT_CAPPED_MODEL_IDS = new Set([
|
|||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-6-astra",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
]);
|
||||
const OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS = new Set([
|
||||
"gpt-5.4",
|
||||
|
|
@ -398,6 +404,8 @@ const OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS = new Set([
|
|||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-6-astra",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
]);
|
||||
|
||||
// Keep the generated default no less restrictive than coding-agent's historical
|
||||
|
|
@ -425,12 +433,16 @@ function withOpenAiLongContextPricing(cost: Model<Api>["cost"]): Model<Api>["cos
|
|||
};
|
||||
}
|
||||
|
||||
// OpenAI reduced GPT-5.6 Terra and Luna prices on 2026-07-30. Keep these
|
||||
// authoritative values until models.dev and passthrough catalogs catch up.
|
||||
// Keep current OpenAI prices authoritative until models.dev and passthrough
|
||||
// catalogs catch up.
|
||||
// https://developers.openai.com/api/docs/pricing
|
||||
const OPENAI_GPT_56_STANDARD_COSTS: Record<string, ModelCost> = {
|
||||
const OPENAI_STANDARD_COSTS: Record<string, ModelCost> = {
|
||||
"gpt-5.6-luna": { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25 },
|
||||
"gpt-5.6-sol": { input: 4, output: 20, cacheRead: 0.4, cacheWrite: 5 },
|
||||
"gpt-5.6-terra": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.5 },
|
||||
"gpt-6-astra": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
||||
"gpt-6-luna": { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 },
|
||||
"gpt-6-sol": { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
|
||||
};
|
||||
|
||||
const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([
|
||||
|
|
@ -444,6 +456,8 @@ const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([
|
|||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
]);
|
||||
const XAI_BUILTIN_EXCLUDED_MODEL_IDS = new Set([
|
||||
"grok-3",
|
||||
|
|
@ -557,13 +571,13 @@ function supportsOpenAiXhigh(modelId: string): boolean {
|
|||
modelId.includes("gpt-5.4") ||
|
||||
modelId.includes("gpt-5.5") ||
|
||||
modelId.includes("gpt-5.6") ||
|
||||
modelId.includes("gpt-6-astra")
|
||||
modelId.includes("gpt-6")
|
||||
);
|
||||
}
|
||||
|
||||
function supportsOpenAiMax(model: Model<Api>): boolean {
|
||||
return (
|
||||
(model.id.includes("gpt-5.6") || model.id.includes("gpt-6-astra")) &&
|
||||
(model.id.includes("gpt-5.6") || model.id.includes("gpt-6")) &&
|
||||
(model.api === "openai-responses" ||
|
||||
model.api === "azure-openai-responses" ||
|
||||
model.api === "openai-codex-responses" ||
|
||||
|
|
@ -1010,13 +1024,13 @@ function applyThinkingLevelMetadata(model: Model<any>): void {
|
|||
mergeThinkingLevelMap(model, { off: null });
|
||||
}
|
||||
if (
|
||||
model.id === "gpt-6-astra" &&
|
||||
(model.id === "gpt-6-astra" || model.id === "gpt-6-sol" || model.id === "gpt-6-luna") &&
|
||||
(model.api === "openai-responses" ||
|
||||
model.api === "azure-openai-responses" ||
|
||||
model.api === "openai-codex-responses")
|
||||
) {
|
||||
mergeThinkingLevelMap(model, {
|
||||
off: null,
|
||||
off: model.id === "gpt-6-astra" ? null : "none",
|
||||
minimal: null,
|
||||
low: "low",
|
||||
medium: "medium",
|
||||
|
|
@ -2724,12 +2738,12 @@ async function generateModels() {
|
|||
candidate.maxTokens = 128000;
|
||||
}
|
||||
if (candidate.provider === "openai" && OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS.has(candidate.id)) {
|
||||
const standardCost = OPENAI_GPT_56_STANDARD_COSTS[candidate.id];
|
||||
const standardCost = OPENAI_STANDARD_COSTS[candidate.id];
|
||||
candidate.cost = withOpenAiLongContextPricing(standardCost ?? candidate.cost);
|
||||
}
|
||||
// Cloudflare AI Gateway passes OpenAI usage through at OpenAI list prices.
|
||||
if (candidate.provider === "cloudflare-ai-gateway") {
|
||||
const standardCost = OPENAI_GPT_56_STANDARD_COSTS[candidate.id];
|
||||
const standardCost = OPENAI_STANDARD_COSTS[candidate.id];
|
||||
if (standardCost) candidate.cost = withOpenAiLongContextPricing(standardCost);
|
||||
}
|
||||
// models.dev reports gpt-5-pro output as 272000 (a duplicate of the input sub-limit);
|
||||
|
|
@ -2775,7 +2789,31 @@ async function generateModels() {
|
|||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing({ input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 }),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-astra"]),
|
||||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "gpt-6-sol",
|
||||
name: "GPT-6 Sol",
|
||||
api: "openai-responses",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-sol"]),
|
||||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "gpt-6-luna",
|
||||
name: "GPT-6 Luna",
|
||||
api: "openai-responses",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-luna"]),
|
||||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
|
|
@ -2787,7 +2825,7 @@ async function generateModels() {
|
|||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing({ input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 }),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-sol"]),
|
||||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
|
|
@ -2799,7 +2837,7 @@ async function generateModels() {
|
|||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-terra"]),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-terra"]),
|
||||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
|
|
@ -2811,7 +2849,7 @@ async function generateModels() {
|
|||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-luna"]),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-luna"]),
|
||||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
|
|
@ -2969,7 +3007,7 @@ async function generateModels() {
|
|||
|
||||
// OpenAI Codex (ChatGPT OAuth) models
|
||||
// NOTE: These are not fetched from models.dev; we keep a small, explicit list to avoid aliases.
|
||||
// Older model limits are based on observed server behavior; GPT-5.6 and GPT-6 Astra use Codex's 272k default catalog limit.
|
||||
// Older model limits are based on observed server behavior; GPT-5.6 and GPT-6 use Codex's 272k default catalog limit.
|
||||
const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
||||
const CODEX_CONTEXT = 272000;
|
||||
const CODEX_GPT_56_CONTEXT = 272000;
|
||||
|
|
@ -2984,7 +3022,31 @@ async function generateModels() {
|
|||
baseUrl: CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing({ input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 }),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-astra"]),
|
||||
contextWindow: CODEX_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
{
|
||||
id: "gpt-6-sol",
|
||||
name: "GPT-6 Sol",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-sol"]),
|
||||
contextWindow: CODEX_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
{
|
||||
id: "gpt-6-luna",
|
||||
name: "GPT-6 Luna",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6-luna"]),
|
||||
contextWindow: CODEX_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
|
|
@ -3020,7 +3082,7 @@ async function generateModels() {
|
|||
baseUrl: CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-luna"]),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-luna"]),
|
||||
contextWindow: CODEX_GPT_56_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
|
|
@ -3032,7 +3094,7 @@ async function generateModels() {
|
|||
baseUrl: CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing({ input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 }),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-sol"]),
|
||||
contextWindow: CODEX_GPT_56_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
|
|
@ -3044,7 +3106,7 @@ async function generateModels() {
|
|||
baseUrl: CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_GPT_56_STANDARD_COSTS["gpt-5.6-terra"]),
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-5.6-terra"]),
|
||||
contextWindow: CODEX_GPT_56_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
|
|||
});
|
||||
|
||||
describe("OpenAI Responses Provider", () => {
|
||||
it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra"] as const)(
|
||||
it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-astra", "gpt-6-luna", "gpt-6-sol"] as const)(
|
||||
"does not enable cache warming from the documented TTL alone for %s",
|
||||
(modelId) => {
|
||||
expect(getModel("openai", modelId).promptCache).toBeUndefined();
|
||||
|
|
@ -408,6 +408,8 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
|
|||
it.each([
|
||||
["gpt-4o-mini", "24h", undefined],
|
||||
["gpt-6-astra", undefined, { ttl: "30m" }],
|
||||
["gpt-6-sol", undefined, { ttl: "30m" }],
|
||||
["gpt-6-luna", undefined, { ttl: "30m" }],
|
||||
] as const)("should use the supported long cache field for %s", async (modelId, retention, cacheOptions) => {
|
||||
const model = getModel("openai", modelId);
|
||||
let capturedPayload: OpenAIResponsesCachePayload | undefined;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ describe("max thinking level", () => {
|
|||
expect(clampThinkingLevel(model, "max")).toBe("high");
|
||||
});
|
||||
|
||||
it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"] as const)(
|
||||
it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-luna", "gpt-6-sol"] as const)(
|
||||
"exposes xhigh and max for openai-codex/%s",
|
||||
(modelId) => {
|
||||
const model = getModel("openai-codex", modelId);
|
||||
|
|
@ -67,23 +67,26 @@ describe("max thinking level", () => {
|
|||
expect(clampThinkingLevel(model, "xhigh")).toBe("max");
|
||||
});
|
||||
|
||||
it.each(["gpt-5.6-sol", "gpt-6-astra"] as const)("sends max to the Codex Responses API for %s", async (modelId) => {
|
||||
const model = getModel("openai-codex", modelId)!;
|
||||
const context = normalizeContext({
|
||||
systemPrompt: "You are a helpful assistant.",
|
||||
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
|
||||
});
|
||||
let payload: unknown;
|
||||
it.each(["gpt-5.6-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna"] as const)(
|
||||
"sends max to the Codex Responses API for %s",
|
||||
async (modelId) => {
|
||||
const model = getModel("openai-codex", modelId)!;
|
||||
const context = normalizeContext({
|
||||
systemPrompt: "You are a helpful assistant.",
|
||||
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
|
||||
});
|
||||
let payload: unknown;
|
||||
|
||||
await streamSimpleOpenAICodexResponses(model, context, {
|
||||
apiKey: mockToken(),
|
||||
reasoning: "max",
|
||||
onPayload: (request) => {
|
||||
payload = request;
|
||||
throw new Error("payload captured");
|
||||
},
|
||||
}).result();
|
||||
await streamSimpleOpenAICodexResponses(model, context, {
|
||||
apiKey: mockToken(),
|
||||
reasoning: "max",
|
||||
onPayload: (request) => {
|
||||
payload = request;
|
||||
throw new Error("payload captured");
|
||||
},
|
||||
}).result();
|
||||
|
||||
expect(payload).toMatchObject({ reasoning: { effort: "max", summary: "auto" } });
|
||||
});
|
||||
expect(payload).toMatchObject({ reasoning: { effort: "max", summary: "auto" } });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -216,6 +216,8 @@ describe("openai-responses provider defaults", () => {
|
|||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
] as const)("sends none reasoning effort for OpenAI %s when no reasoning is requested", async (modelId) => {
|
||||
const model = getModel("openai", modelId);
|
||||
let capturedPayload: unknown;
|
||||
|
|
|
|||
|
|
@ -68,16 +68,21 @@ describe("getSupportedThinkingLevels", () => {
|
|||
expect(getSupportedThinkingLevels(model!)).not.toContain("max");
|
||||
});
|
||||
|
||||
it.each(["gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra"] as const)(
|
||||
"includes xhigh for openai-codex %s models",
|
||||
(modelId) => {
|
||||
const model = getModel("openai-codex", modelId);
|
||||
expect(model).toBeDefined();
|
||||
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
|
||||
},
|
||||
);
|
||||
it.each([
|
||||
"gpt-5.5",
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-5.6-luna",
|
||||
"gpt-6-astra",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
] as const)("includes xhigh for openai-codex %s models", (modelId) => {
|
||||
const model = getModel("openai-codex", modelId);
|
||||
expect(model).toBeDefined();
|
||||
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
|
||||
});
|
||||
|
||||
it.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] as const)(
|
||||
it.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-sol", "gpt-6-luna"] as const)(
|
||||
"includes xhigh and max for OpenAI %s models",
|
||||
(modelId) => {
|
||||
const model = getModel("openai", modelId);
|
||||
|
|
@ -86,6 +91,38 @@ describe("getSupportedThinkingLevels", () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["gpt-6-sol", { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }],
|
||||
["gpt-6-luna", { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 }],
|
||||
] as const)("includes official metadata for OpenAI and Codex %s", (modelId, cost) => {
|
||||
for (const provider of ["openai", "openai-codex"] as const) {
|
||||
const model = getModel(provider, modelId);
|
||||
expect(model).toMatchObject({
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
...cost,
|
||||
tiers: [
|
||||
{
|
||||
inputTokensAbove: 272000,
|
||||
input: cost.input * 2,
|
||||
output: cost.output * 1.5,
|
||||
cacheRead: cost.cacheRead * 2,
|
||||
cacheWrite: cost.cacheWrite * 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
contextWindow: 272000,
|
||||
maxTokens: 128000,
|
||||
compat: {
|
||||
supportsAdditionalTools: true,
|
||||
supportsMidConvoSystemMessages: true,
|
||||
supportsOpenAIGrammarTools: true,
|
||||
supportsToolSearch: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("includes only medium/high/xhigh for OpenAI GPT-5.5 Pro", () => {
|
||||
const model = getModel("openai", "gpt-5.5-pro");
|
||||
expect(model).toBeDefined();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Added
|
||||
|
||||
- Added inherited GPT-6 Sol and GPT-6 Luna support for OpenAI API keys and OpenAI Codex subscriptions.
|
||||
- Added inherited Claude Opus 5.5 support for Anthropic with adaptive thinking and a 1M context window.
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue