mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 10:05:14 +00:00
fix(ai): append system prompt to codex bridge message instead of converting to input
Previously the system prompt was converted to an input message in convertMessages, then stripped out by filterPiSystemPrompts. Now the system prompt is passed directly to transformRequestBody and appended after CODEX_PI_BRIDGE in the bridge message.
This commit is contained in:
parent
9a147559c0
commit
bb50738f7e
15 changed files with 908 additions and 127 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- OpenAI Codex OAuth provider with Responses API streaming support: `openai-codex-responses` streaming provider with SSE parsing, tool-call handling, usage/cost tracking, and PKCE OAuth flow ([#451](https://github.com/badlogic/pi-mono/pull/451) by [@kim0](https://github.com/kim0))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Vertex AI dummy value for `getEnvApiKey()`: Returns `"<authenticated>"` when Application Default Credentials are configured (`~/.config/gcloud/application_default_credentials.json` exists) and both `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION` are set. This allows `streamSimple()` to work with Vertex AI without explicit `apiKey` option. The ADC credentials file existence check is cached per-process to avoid repeated filesystem access.
|
||||
|
|
|
|||
|
|
@ -2773,6 +2773,637 @@ export const MODELS = {
|
|||
maxTokens: 100000,
|
||||
} satisfies Model<"openai-responses">,
|
||||
},
|
||||
"openai-codex": {
|
||||
"codex-mini-latest": {
|
||||
id: "codex-mini-latest",
|
||||
name: "Codex Mini Latest",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5": {
|
||||
id: "gpt-5",
|
||||
name: "gpt-5",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5-codex": {
|
||||
id: "gpt-5-codex",
|
||||
name: "gpt-5-codex",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5-codex-mini": {
|
||||
id: "gpt-5-codex-mini",
|
||||
name: "gpt-5-codex-mini",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5-codex-mini-high": {
|
||||
id: "gpt-5-codex-mini-high",
|
||||
name: "gpt-5-codex-mini-high",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5-codex-mini-medium": {
|
||||
id: "gpt-5-codex-mini-medium",
|
||||
name: "gpt-5-codex-mini-medium",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5-mini": {
|
||||
id: "gpt-5-mini",
|
||||
name: "gpt-5-mini",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5-nano": {
|
||||
id: "gpt-5-nano",
|
||||
name: "gpt-5-nano",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1": {
|
||||
id: "gpt-5.1",
|
||||
name: "GPT-5.1",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-chat-latest": {
|
||||
id: "gpt-5.1-chat-latest",
|
||||
name: "gpt-5.1-chat-latest",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex": {
|
||||
id: "gpt-5.1-codex",
|
||||
name: "GPT-5.1 Codex",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-high": {
|
||||
id: "gpt-5.1-codex-high",
|
||||
name: "gpt-5.1-codex-high",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-low": {
|
||||
id: "gpt-5.1-codex-low",
|
||||
name: "gpt-5.1-codex-low",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-max": {
|
||||
id: "gpt-5.1-codex-max",
|
||||
name: "GPT-5.1 Codex Max",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-max-high": {
|
||||
id: "gpt-5.1-codex-max-high",
|
||||
name: "gpt-5.1-codex-max-high",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-max-low": {
|
||||
id: "gpt-5.1-codex-max-low",
|
||||
name: "gpt-5.1-codex-max-low",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-max-medium": {
|
||||
id: "gpt-5.1-codex-max-medium",
|
||||
name: "gpt-5.1-codex-max-medium",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-max-xhigh": {
|
||||
id: "gpt-5.1-codex-max-xhigh",
|
||||
name: "gpt-5.1-codex-max-xhigh",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-medium": {
|
||||
id: "gpt-5.1-codex-medium",
|
||||
name: "gpt-5.1-codex-medium",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-mini": {
|
||||
id: "gpt-5.1-codex-mini",
|
||||
name: "GPT-5.1 Codex Mini",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-mini-high": {
|
||||
id: "gpt-5.1-codex-mini-high",
|
||||
name: "gpt-5.1-codex-mini-high",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-codex-mini-medium": {
|
||||
id: "gpt-5.1-codex-mini-medium",
|
||||
name: "gpt-5.1-codex-mini-medium",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-high": {
|
||||
id: "gpt-5.1-high",
|
||||
name: "gpt-5.1-high",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-low": {
|
||||
id: "gpt-5.1-low",
|
||||
name: "gpt-5.1-low",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-medium": {
|
||||
id: "gpt-5.1-medium",
|
||||
name: "gpt-5.1-medium",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.1-none": {
|
||||
id: "gpt-5.1-none",
|
||||
name: "gpt-5.1-none",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2": {
|
||||
id: "gpt-5.2",
|
||||
name: "GPT-5.2",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-codex": {
|
||||
id: "gpt-5.2-codex",
|
||||
name: "GPT-5.2 Codex",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-codex-high": {
|
||||
id: "gpt-5.2-codex-high",
|
||||
name: "gpt-5.2-codex-high",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-codex-low": {
|
||||
id: "gpt-5.2-codex-low",
|
||||
name: "gpt-5.2-codex-low",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-codex-medium": {
|
||||
id: "gpt-5.2-codex-medium",
|
||||
name: "gpt-5.2-codex-medium",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-codex-xhigh": {
|
||||
id: "gpt-5.2-codex-xhigh",
|
||||
name: "gpt-5.2-codex-xhigh",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-high": {
|
||||
id: "gpt-5.2-high",
|
||||
name: "gpt-5.2-high",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-low": {
|
||||
id: "gpt-5.2-low",
|
||||
name: "gpt-5.2-low",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-medium": {
|
||||
id: "gpt-5.2-medium",
|
||||
name: "gpt-5.2-medium",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-none": {
|
||||
id: "gpt-5.2-none",
|
||||
name: "gpt-5.2-none",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
"gpt-5.2-xhigh": {
|
||||
id: "gpt-5.2-xhigh",
|
||||
name: "gpt-5.2-xhigh",
|
||||
api: "openai-codex-responses",
|
||||
provider: "openai-codex",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-codex-responses">,
|
||||
},
|
||||
"openrouter": {
|
||||
"ai21/jamba-large-1.7": {
|
||||
id: "ai21/jamba-large-1.7",
|
||||
|
|
@ -2825,23 +3456,6 @@ export const MODELS = {
|
|||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"alibaba/tongyi-deepresearch-30b-a3b:free": {
|
||||
id: "alibaba/tongyi-deepresearch-30b-a3b:free",
|
||||
name: "Tongyi DeepResearch 30B A3B (free)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"allenai/olmo-3-7b-instruct": {
|
||||
id: "allenai/olmo-3-7b-instruct",
|
||||
name: "AllenAI: Olmo 3 7B Instruct",
|
||||
|
|
@ -3395,13 +4009,13 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.19999999999999998,
|
||||
output: 0.88,
|
||||
cacheRead: 0.106,
|
||||
input: 0.19,
|
||||
output: 0.87,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 163840,
|
||||
maxTokens: 4096,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"deepseek/deepseek-chat-v3.1": {
|
||||
id: "deepseek/deepseek-chat-v3.1",
|
||||
|
|
@ -3429,13 +4043,13 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.3,
|
||||
output: 1.2,
|
||||
input: 0.7,
|
||||
output: 2.4,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 163840,
|
||||
maxTokens: 4096,
|
||||
maxTokens: 163840,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"deepseek/deepseek-r1-0528": {
|
||||
id: "deepseek/deepseek-r1-0528",
|
||||
|
|
@ -3811,6 +4425,23 @@ export const MODELS = {
|
|||
contextWindow: 128000,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"kwaipilot/kat-coder-pro": {
|
||||
id: "kwaipilot/kat-coder-pro",
|
||||
name: "Kwaipilot: KAT-Coder-Pro V1",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.207,
|
||||
output: 0.828,
|
||||
cacheRead: 0.0414,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 256000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"kwaipilot/kat-coder-pro:free": {
|
||||
id: "kwaipilot/kat-coder-pro:free",
|
||||
name: "Kwaipilot: KAT-Coder-Pro V1 (free)",
|
||||
|
|
@ -4636,13 +5267,13 @@ export const MODELS = {
|
|||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.456,
|
||||
output: 1.8399999999999999,
|
||||
input: 0.5,
|
||||
output: 2.4,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"moonshotai/kimi-k2-0905": {
|
||||
id: "moonshotai/kimi-k2-0905",
|
||||
|
|
@ -4687,13 +5318,13 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.39999999999999997,
|
||||
output: 1.75,
|
||||
input: 0.32,
|
||||
output: 0.48,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65535,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"nex-agi/deepseek-v3.1-nex-n1:free": {
|
||||
id: "nex-agi/deepseek-v3.1-nex-n1:free",
|
||||
|
|
@ -4729,23 +5360,6 @@ export const MODELS = {
|
|||
contextWindow: 32768,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"nousresearch/hermes-4-405b": {
|
||||
id: "nousresearch/hermes-4-405b",
|
||||
name: "Nous: Hermes 4 405B",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.3,
|
||||
output: 1.2,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"nousresearch/hermes-4-70b": {
|
||||
id: "nousresearch/hermes-4-70b",
|
||||
name: "Nous: Hermes 4 70B",
|
||||
|
|
@ -5560,7 +6174,7 @@ export const MODELS = {
|
|||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-oss-safeguard-20b": {
|
||||
id: "openai/gpt-oss-safeguard-20b",
|
||||
|
|
@ -6217,8 +6831,8 @@ export const MODELS = {
|
|||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.19999999999999998,
|
||||
output: 1.2,
|
||||
input: 0.12,
|
||||
output: 0.56,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
|
|
@ -6234,8 +6848,8 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.3,
|
||||
output: 1.2,
|
||||
input: 0.44999999999999996,
|
||||
output: 3.5,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
|
|
@ -6463,23 +7077,6 @@ export const MODELS = {
|
|||
contextWindow: 163840,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"tngtech/tng-r1t-chimera:free": {
|
||||
id: "tngtech/tng-r1t-chimera:free",
|
||||
name: "TNG: R1T Chimera (free)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 163840,
|
||||
maxTokens: 163840,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"x-ai/grok-3": {
|
||||
id: "x-ai/grok-3",
|
||||
name: "xAI: Grok 3",
|
||||
|
|
@ -6676,13 +7273,13 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.13,
|
||||
output: 0.85,
|
||||
input: 0.049999999999999996,
|
||||
output: 0.22,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 98304,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"z-ai/glm-4.5-air:free": {
|
||||
id: "z-ai/glm-4.5-air:free",
|
||||
|
|
@ -6699,7 +7296,7 @@ export const MODELS = {
|
|||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
maxTokens: 96000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"z-ai/glm-4.5v": {
|
||||
id: "z-ai/glm-4.5v",
|
||||
|
|
@ -6778,13 +7375,13 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.39999999999999997,
|
||||
output: 1.5,
|
||||
input: 0.16,
|
||||
output: 0.7999999999999999,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 202752,
|
||||
maxTokens: 65535,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"xai": {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|||
codexInstructions,
|
||||
codexOptions,
|
||||
options?.codexMode ?? true,
|
||||
context.systemPrompt,
|
||||
);
|
||||
|
||||
const headers = createCodexHeaders(model.headers, accountId, apiKey, transformedBody.prompt_cache_key);
|
||||
|
|
@ -477,14 +478,6 @@ function convertMessages(model: Model<"openai-codex-responses">, context: Contex
|
|||
|
||||
const transformedMessages = transformMessages(context.messages, model);
|
||||
|
||||
if (context.systemPrompt) {
|
||||
const role = model.reasoning ? "developer" : "system";
|
||||
messages.push({
|
||||
role,
|
||||
content: sanitizeSurrogates(context.systemPrompt),
|
||||
});
|
||||
}
|
||||
|
||||
let msgIndex = 0;
|
||||
for (const msg of transformedMessages) {
|
||||
if (msg.role === "user") {
|
||||
|
|
|
|||
|
|
@ -43,4 +43,6 @@ You are running Codex through pi, a terminal coding assistant. The tools and rul
|
|||
1. Using edit, not apply_patch
|
||||
2. No plan tools used
|
||||
3. Only the tools listed above are called
|
||||
|
||||
Below are additional system instruction you MUST follow when responding:
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -210,45 +210,22 @@ function filterInput(input: InputItem[] | undefined): InputItem[] | undefined {
|
|||
});
|
||||
}
|
||||
|
||||
function getContentText(item: InputItem): string {
|
||||
if (typeof item.content === "string") {
|
||||
return item.content;
|
||||
}
|
||||
if (Array.isArray(item.content)) {
|
||||
return item.content
|
||||
.filter((c) => typeof c === "object" && c !== null && (c as { type?: string }).type === "input_text")
|
||||
.map((c) => (c as { text?: string }).text)
|
||||
.filter((text): text is string => typeof text === "string")
|
||||
.join("\n");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function isPiSystemPrompt(item: InputItem): boolean {
|
||||
const isSystemRole = item.role === "developer" || item.role === "system";
|
||||
if (!isSystemRole) return false;
|
||||
const contentText = getContentText(item).trim();
|
||||
if (!contentText) return false;
|
||||
return contentText.startsWith(
|
||||
"You are an expert coding assistant. You help users with coding tasks by reading files, executing commands",
|
||||
);
|
||||
}
|
||||
|
||||
async function filterPiSystemPrompts(input: InputItem[] | undefined): Promise<InputItem[] | undefined> {
|
||||
if (!Array.isArray(input)) return input;
|
||||
return input.filter((item) => item.role === "user" || !isPiSystemPrompt(item));
|
||||
}
|
||||
|
||||
function addCodexBridgeMessage(input: InputItem[] | undefined, hasTools: boolean): InputItem[] | undefined {
|
||||
function addCodexBridgeMessage(
|
||||
input: InputItem[] | undefined,
|
||||
hasTools: boolean,
|
||||
systemPrompt?: string,
|
||||
): InputItem[] | undefined {
|
||||
if (!hasTools || !Array.isArray(input)) return input;
|
||||
|
||||
const bridgeText = systemPrompt ? `${CODEX_PI_BRIDGE}\n\n${systemPrompt}` : CODEX_PI_BRIDGE;
|
||||
|
||||
const bridgeMessage: InputItem = {
|
||||
type: "message",
|
||||
role: "developer",
|
||||
content: [
|
||||
{
|
||||
type: "input_text",
|
||||
text: CODEX_PI_BRIDGE,
|
||||
text: bridgeText,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -278,6 +255,7 @@ export async function transformRequestBody(
|
|||
codexInstructions: string,
|
||||
options: CodexRequestOptions = {},
|
||||
codexMode = true,
|
||||
systemPrompt?: string,
|
||||
): Promise<RequestBody> {
|
||||
const normalizedModel = normalizeModel(body.model);
|
||||
|
||||
|
|
@ -290,8 +268,7 @@ export async function transformRequestBody(
|
|||
body.input = filterInput(body.input);
|
||||
|
||||
if (codexMode) {
|
||||
body.input = await filterPiSystemPrompts(body.input);
|
||||
body.input = addCodexBridgeMessage(body.input, !!body.tools);
|
||||
body.input = addCodexBridgeMessage(body.input, !!body.tools, systemPrompt);
|
||||
} else {
|
||||
body.input = addToolRemapMessage(body.input, !!body.tools);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import type { Api, Context, Model, OptionsForApi } from "../src/types.js";
|
|||
import { resolveApiKey } from "./oauth.js";
|
||||
|
||||
// Resolve OAuth tokens at module level (async, runs before tests)
|
||||
const geminiCliToken = await resolveApiKey("google-gemini-cli");
|
||||
const [geminiCliToken, openaiCodexToken] = await Promise.all([
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
|
||||
async function testAbortSignal<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
const context: Context = {
|
||||
|
|
@ -139,4 +142,16 @@ describe("AI Providers Abort Tests", () => {
|
|||
await testImmediateAbort(llm, { apiKey: geminiCliToken });
|
||||
});
|
||||
});
|
||||
|
||||
describe("OpenAI Codex Provider Abort", () => {
|
||||
it.skipIf(!openaiCodexToken)("should abort mid-stream", { retry: 3 }, async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testAbortSignal(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
|
||||
it.skipIf(!openaiCodexToken)("should handle immediate abort", { retry: 3 }, async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testImmediateAbort(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
// Lorem ipsum paragraph for realistic token estimation
|
||||
const LOREM_IPSUM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. `;
|
||||
|
|
@ -263,6 +264,26 @@ describe("Context overflow error handling", () => {
|
|||
);
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// OpenAI Codex (OAuth)
|
||||
// Uses ChatGPT Plus/Pro subscription via OAuth
|
||||
// =============================================================================
|
||||
|
||||
describe("OpenAI Codex (OAuth)", () => {
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should detect overflow via isContextOverflow",
|
||||
async () => {
|
||||
const model = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
const result = await testContextOverflow(model, openaiCodexToken!);
|
||||
logResult(result);
|
||||
|
||||
expect(result.stopReason).toBe("error");
|
||||
expect(isContextOverflow(result.response, model.contextWindow)).toBe(true);
|
||||
},
|
||||
120000,
|
||||
);
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// xAI
|
||||
// Expected pattern: "maximum prompt length is X but the request contains Y"
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
async function testEmptyMessage<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
// Test with completely empty content array
|
||||
|
|
@ -573,4 +574,42 @@ describe("AI Providers Empty Message Tests", () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("OpenAI Codex Provider Empty Messages", () => {
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle empty content array",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testEmptyMessage(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle empty string content",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testEmptyStringMessage(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle whitespace-only content",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testWhitespaceOnlyMessage(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle empty assistant message in conversation",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testEmptyAssistantMessage(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
/**
|
||||
* Test that tool results containing only images work correctly across all providers.
|
||||
|
|
@ -394,4 +395,24 @@ describe("Tool Results with Images", () => {
|
|||
|
||||
// Note: gpt-oss-120b-medium does not support images, so not tested here
|
||||
});
|
||||
|
||||
describe("OpenAI Codex Provider", () => {
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle tool result with only image",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await handleToolWithImageResult(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle tool result with text and image",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await handleToolWithTextAndImageResult(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
// Calculator tool definition (same as examples)
|
||||
// Note: Using StringEnum helper because Google's API doesn't support anyOf/const patterns
|
||||
|
|
@ -878,6 +879,34 @@ describe("Generate E2E Tests", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("OpenAI Codex Provider (gpt-5.2-xhigh)", () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
|
||||
it.skipIf(!openaiCodexToken)("should complete basic text generation", { retry: 3 }, async () => {
|
||||
await basicTextGeneration(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
|
||||
it.skipIf(!openaiCodexToken)("should handle tool calling", { retry: 3 }, async () => {
|
||||
await handleToolCall(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
|
||||
it.skipIf(!openaiCodexToken)("should handle streaming", { retry: 3 }, async () => {
|
||||
await handleStreaming(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
|
||||
it.skipIf(!openaiCodexToken)("should handle thinking", { retry: 3 }, async () => {
|
||||
await handleThinking(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
|
||||
it.skipIf(!openaiCodexToken)("should handle multi-turn with thinking and tools", { retry: 3 }, async () => {
|
||||
await multiTurn(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
|
||||
it.skipIf(!openaiCodexToken)("should handle image input", { retry: 3 }, async () => {
|
||||
await handleImage(llm, { apiKey: openaiCodexToken });
|
||||
});
|
||||
});
|
||||
|
||||
// Check if ollama is installed and local LLM tests are enabled
|
||||
let ollamaInstalled = false;
|
||||
if (!process.env.PI_NO_LOCAL_LLM) {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
async function testTokensOnAbort<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
const context: Context = {
|
||||
|
|
@ -43,11 +44,12 @@ async function testTokensOnAbort<TApi extends Api>(llm: Model<TApi>, options: Op
|
|||
|
||||
expect(msg.stopReason).toBe("aborted");
|
||||
|
||||
// OpenAI providers, Gemini CLI, zai, and the GPT-OSS model on Antigravity only send usage in the final chunk,
|
||||
// OpenAI providers, OpenAI Codex, Gemini CLI, zai, and the GPT-OSS model on Antigravity only send usage in the final chunk,
|
||||
// so when aborted they have no token stats Anthropic and Google send usage information early in the stream
|
||||
if (
|
||||
llm.api === "openai-completions" ||
|
||||
llm.api === "openai-responses" ||
|
||||
llm.api === "openai-codex-responses" ||
|
||||
llm.provider === "google-gemini-cli" ||
|
||||
llm.provider === "zai" ||
|
||||
(llm.provider === "google-antigravity" && llm.id.includes("gpt-oss"))
|
||||
|
|
@ -217,4 +219,15 @@ describe("Token Statistics on Abort", () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("OpenAI Codex Provider", () => {
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should include token stats when aborted mid-stream",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testTokensOnAbort(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
// Simple calculate tool
|
||||
const calculateSchema = Type.Object({
|
||||
|
|
@ -244,4 +245,15 @@ describe("Tool Call Without Result Tests", () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("OpenAI Codex Provider", () => {
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should filter out tool calls without corresponding tool results",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const model = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testToolCallWithoutResult(model, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
// Generate a long system prompt to trigger caching (>2k bytes for most providers)
|
||||
const LONG_SYSTEM_PROMPT = `You are a helpful assistant. Be concise in your responses.
|
||||
|
|
@ -533,4 +534,27 @@ describe("totalTokens field", () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// OpenAI Codex (OAuth)
|
||||
// =========================================================================
|
||||
|
||||
describe("OpenAI Codex (OAuth)", () => {
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should return totalTokens equal to sum of components",
|
||||
{ retry: 3, timeout: 60000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
|
||||
console.log(`\nOpenAI Codex / ${llm.id}:`);
|
||||
const { first, second } = await testTotalTokensWithCache(llm, { apiKey: openaiCodexToken });
|
||||
|
||||
logUsage("First request", first);
|
||||
logUsage("Second request", second);
|
||||
|
||||
assertTotalTokensEqualsComponents(first);
|
||||
assertTotalTokensEqualsComponents(second);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ const oauthTokens = await Promise.all([
|
|||
resolveApiKey("github-copilot"),
|
||||
resolveApiKey("google-gemini-cli"),
|
||||
resolveApiKey("google-antigravity"),
|
||||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken] = oauthTokens;
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
/**
|
||||
* Test for Unicode surrogate pair handling in tool results.
|
||||
|
|
@ -615,4 +616,33 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => {
|
|||
await testUnpairedHighSurrogate(llm);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OpenAI Codex Provider Unicode Handling", () => {
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle emoji in tool results",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testEmojiInToolResults(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle real-world LinkedIn comment data with emoji",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testRealWorldLinkedInData(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!openaiCodexToken)(
|
||||
"gpt-5.2-xhigh - should handle unpaired high surrogate (0xD83D) in tool results",
|
||||
{ retry: 3, timeout: 30000 },
|
||||
async () => {
|
||||
const llm = getModel("openai-codex", "gpt-5.2-xhigh");
|
||||
await testUnpairedHighSurrogate(llm, { apiKey: openaiCodexToken });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- OpenAI Codex OAuth provider support: access Codex models via ChatGPT Plus/Pro subscription using `/login openai-codex` ([#451](https://github.com/badlogic/pi-mono/pull/451) by [@kim0](https://github.com/kim0))
|
||||
|
||||
## [0.35.0] - 2026-01-05
|
||||
|
||||
This release unifies hooks and custom tools into a single "extensions" system and renames "slash commands" to "prompt templates". ([#454](https://github.com/badlogic/pi-mono/issues/454))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue