mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(ai): add GPT-6.1 Terra
Add gpt-6.1-terra to the OpenAI, Azure OpenAI Responses, and OpenAI Codex providers with official pricing ($1 input, $0.05 cached input, $1.25 cache writes, $5 output, long-context tier above 272k), tool search, additional tools, and mid-conversation effort changes, as for GPT-6.1 Sol. Unlike Sol it accepts reasoning.effort "none", so off is supported; both reject "minimal" (verified against Codex). Codex serves it with a 500K context window.
This commit is contained in:
parent
89c921d8b5
commit
363ad78bf0
5 changed files with 68 additions and 13 deletions
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
- Added optional `openBrowserOnConfirm` and `copyCodeOnConfirm` to `device_code` auth events and `OAuthDeviceCodeInfo`. Both default to `true`; set them to `false` to stop clients from offering to open the verification page or copy the code ([#9282](https://github.com/earendil-works/pi/issues/9282), [#9301](https://github.com/earendil-works/pi/pull/9301))
|
||||
- Added `StreamingJsonParser`, an incremental, tolerant parser for JSON text that arrives in pieces (each piece is read once; snapshots of the partial value never change once returned), and `ToolCallArgumentsStream`, which every provider now uses for streamed tool-call arguments; its `append()` also returns the changes each piece made (`JsonChange`: a value set at a path, or text appended to a string), for writers that keep their own copy ([#9265](https://github.com/earendil-works/pi/issues/9265))
|
||||
- Added GPT-6.1 Terra to the OpenAI, Azure OpenAI Responses, and OpenAI Codex providers with official pricing ($1 input, $0.05 cached input, $1.25 cache writes, $5 output, long-context tier above 272k), tool search, and mid-conversation effort changes. Unlike GPT-6.1 Sol it supports `off` (reasoning effort `none`); Codex serves it with a 500K context window
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -378,6 +378,7 @@ const OPENAI_TOOL_SEARCH_MODEL_IDS = new Set([
|
|||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6.1-terra",
|
||||
]);
|
||||
const OPENAI_ADDITIONAL_TOOLS_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS;
|
||||
const OPENAI_MID_CONVO_SYSTEM_MESSAGE_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS;
|
||||
|
|
@ -389,10 +390,17 @@ const OPENAI_CODEX_ADDITIONAL_TOOLS_MODEL_IDS = new Set([
|
|||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6.1-terra",
|
||||
]);
|
||||
// OpenAI documents configuration_update for the GPT-6 family.
|
||||
// https://developers.openai.com/api/docs/guides/reasoning
|
||||
const OPENAI_MID_CONVO_EFFORT_MODEL_IDS = new Set(["gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-sol"]);
|
||||
const OPENAI_MID_CONVO_EFFORT_MODEL_IDS = new Set([
|
||||
"gpt-6-astra",
|
||||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6.1-terra",
|
||||
]);
|
||||
const OPENAI_LONG_CONTEXT_INPUT_THRESHOLD = 272000;
|
||||
const OPENAI_SHORT_CONTEXT_CAPPED_MODEL_IDS = new Set([
|
||||
"gpt-5.4",
|
||||
|
|
@ -404,6 +412,7 @@ const OPENAI_SHORT_CONTEXT_CAPPED_MODEL_IDS = new Set([
|
|||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6.1-terra",
|
||||
]);
|
||||
const OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS = new Set([
|
||||
"gpt-5.4",
|
||||
|
|
@ -417,6 +426,7 @@ const OPENAI_LONG_CONTEXT_PRICING_MODEL_IDS = new Set([
|
|||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6.1-terra",
|
||||
]);
|
||||
|
||||
// Keep the generated default no less restrictive than coding-agent's historical
|
||||
|
|
@ -455,6 +465,7 @@ const OPENAI_STANDARD_COSTS: Record<string, ModelCost> = {
|
|||
"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 },
|
||||
"gpt-6.1-sol": { input: 2, output: 10, cacheRead: 0.1, cacheWrite: 2.5 },
|
||||
"gpt-6.1-terra": { input: 1, output: 5, cacheRead: 0.05, cacheWrite: 1.25 },
|
||||
};
|
||||
|
||||
const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([
|
||||
|
|
@ -1072,13 +1083,15 @@ function applyThinkingLevelMetadata(model: Model<any>): void {
|
|||
(model.id === "gpt-6-astra" ||
|
||||
model.id === "gpt-6-sol" ||
|
||||
model.id === "gpt-6-luna" ||
|
||||
model.id === "gpt-6.1-sol") &&
|
||||
model.id === "gpt-6.1-sol" ||
|
||||
model.id === "gpt-6.1-terra") &&
|
||||
(model.api === "openai-responses" ||
|
||||
model.api === "azure-openai-responses" ||
|
||||
model.api === "openai-codex-responses")
|
||||
) {
|
||||
mergeThinkingLevelMap(model, {
|
||||
// GPT-6 Astra and GPT-6.1 Sol reject reasoning.effort "none".
|
||||
// GPT-6 Astra and GPT-6.1 Sol reject reasoning.effort "none"; GPT-6.1 Terra accepts it. All reject
|
||||
// "minimal" (verified on Codex, October 2026).
|
||||
off: model.id === "gpt-6-astra" || model.id === "gpt-6.1-sol" ? null : "none",
|
||||
minimal: null,
|
||||
low: "low",
|
||||
|
|
@ -2957,6 +2970,18 @@ async function generateModels() {
|
|||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "gpt-6.1-terra",
|
||||
name: "GPT-6.1 Terra",
|
||||
api: "openai-responses",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
provider: "openai",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6.1-terra"]),
|
||||
contextWindow: OPENAI_LONG_CONTEXT_INPUT_THRESHOLD,
|
||||
maxTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "gpt-6-astra",
|
||||
name: "GPT-6 Astra",
|
||||
|
|
@ -3177,7 +3202,7 @@ async function generateModels() {
|
|||
// GPT-6.1 Sol uses the 500k default returned by /backend-api/codex/models.
|
||||
const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
||||
const CODEX_CONTEXT = 272000;
|
||||
const CODEX_GPT_61_SOL_CONTEXT = 500000;
|
||||
const CODEX_GPT_61_CONTEXT = 500000;
|
||||
const CODEX_GPT_56_CONTEXT = 272000;
|
||||
const CODEX_MAX_TOKENS = 128000;
|
||||
const codexModels: Model<"openai-codex-responses">[] = [
|
||||
|
|
@ -3190,7 +3215,19 @@ async function generateModels() {
|
|||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6.1-sol"]),
|
||||
contextWindow: CODEX_GPT_61_SOL_CONTEXT,
|
||||
contextWindow: CODEX_GPT_61_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
{
|
||||
id: "gpt-6.1-terra",
|
||||
name: "GPT-6.1 Terra",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: CODEX_BASE_URL,
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: withOpenAiLongContextPricing(OPENAI_STANDARD_COSTS["gpt-6.1-terra"]),
|
||||
contextWindow: CODEX_GPT_61_CONTEXT,
|
||||
maxTokens: CODEX_MAX_TOKENS,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -240,12 +240,17 @@ 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", "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();
|
||||
},
|
||||
);
|
||||
it.each([
|
||||
"gpt-5.6-luna",
|
||||
"gpt-5.6-sol",
|
||||
"gpt-5.6-terra",
|
||||
"gpt-6-astra",
|
||||
"gpt-6-luna",
|
||||
"gpt-6-sol",
|
||||
"gpt-6.1-terra",
|
||||
] as const)("does not enable cache warming from the documented TTL alone for %s", (modelId) => {
|
||||
expect(getModel("openai", modelId).promptCache).toBeUndefined();
|
||||
});
|
||||
|
||||
it.skipIf(!process.env.OPENAI_API_KEY)(
|
||||
"should not set prompt_cache_retention when PI_CACHE_RETENTION is not set",
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ describe("max thinking level", () => {
|
|||
expect(clampThinkingLevel(model, "xhigh")).toBe("max");
|
||||
});
|
||||
|
||||
it.each(["gpt-5.6-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-sol"] as const)(
|
||||
it.each(["gpt-5.6-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-sol", "gpt-6.1-terra"] as const)(
|
||||
"sends max to the Codex Responses API for %s",
|
||||
async (modelId) => {
|
||||
const model = getModel("openai-codex", modelId)!;
|
||||
|
|
|
|||
|
|
@ -93,13 +93,14 @@ describe("getSupportedThinkingLevels", () => {
|
|||
"gpt-6-sol",
|
||||
"gpt-6-luna",
|
||||
"gpt-6.1-sol",
|
||||
"gpt-6.1-terra",
|
||||
] 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", "gpt-6-sol", "gpt-6-luna"] as const)(
|
||||
it.each(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-terra"] as const)(
|
||||
"includes xhigh and max for OpenAI %s models",
|
||||
(modelId) => {
|
||||
const model = getModel("openai", modelId);
|
||||
|
|
@ -123,11 +124,22 @@ describe("getSupportedThinkingLevels", () => {
|
|||
}
|
||||
});
|
||||
|
||||
// GPT-6.1 Terra accepts reasoning.effort "none" but, like GPT-6.1 Sol, rejects "minimal".
|
||||
it("supports off but not minimal for GPT-6.1 Terra", () => {
|
||||
for (const provider of ["openai", "azure", "openai-codex"] as const) {
|
||||
const model = getModel(provider, "gpt-6.1-terra");
|
||||
expect(model).toBeDefined();
|
||||
expect(model!.thinkingLevelMap?.off).toBe("none");
|
||||
expect(model!.thinkingLevelMap?.minimal).toBe(provider === "openai-codex" ? "low" : null);
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["gpt-6-sol", { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }, 272000],
|
||||
["gpt-6-luna", { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 }, 272000],
|
||||
// Codex serves GPT-6.1 Sol with a 500K default context window (#10840)
|
||||
["gpt-6.1-sol", { input: 2, output: 10, cacheRead: 0.1, cacheWrite: 2.5 }, 500000],
|
||||
["gpt-6.1-terra", { input: 1, output: 5, cacheRead: 0.05, cacheWrite: 1.25 }, 500000],
|
||||
] as const)("includes official metadata for OpenAI and Codex %s", (modelId, cost, codexContextWindow) => {
|
||||
for (const provider of ["openai", "openai-codex"] as const) {
|
||||
const model = getModel(provider, modelId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue