summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-13 12:22:18 -0600
committert <t@tjp.lol>2026-06-15 15:08:32 -0600
commiteb19fd812e15de5a6930daf2fcd0b2b0757687c7 (patch)
treeace85c641e48836fe45b289141213fe8984ccb52 /src
parent9f983c5d0ecc1c12f15eadf1add123e24995b660 (diff)
auth: openai_responses provider for Codex subscription (Tier 2)
Add the OpenAI Responses API wire dialect (openai_responses APIStyle + OpenAIResponsesConfig), a request serializer (instructions/input items, flat function tools, reasoning, store:false, include encrypted reasoning), and a streaming state machine over the typed response.* SSE events mapping to the shared block model. Wire dispatch, buildProviderConfig, the C-ABI free switch, and the reasoning selectors. Ship a commented Codex example in the default config. Implemented from the Responses API docs + the open-source Codex client and covered by fixture tests; live verification against a ChatGPT plan is still required (documented).
Diffstat (limited to 'src')
-rw-r--r--src/auth_manager.zig5
-rw-r--r--src/config_file.zig8
-rw-r--r--src/luarocks_runtime.zig24
-rw-r--r--src/tui_selectors.zig16
4 files changed, 48 insertions, 5 deletions
diff --git a/src/auth_manager.zig b/src/auth_manager.zig
index 263ec54..87b20ae 100644
--- a/src/auth_manager.zig
+++ b/src/auth_manager.zig
@@ -189,6 +189,11 @@ fn patchCredential(
c.base_url = base_url;
c.extra_headers = merged;
},
+ .openai_responses => |*c| {
+ c.api_key = cred.api_key;
+ c.base_url = base_url;
+ c.extra_headers = merged;
+ },
}
}
diff --git a/src/config_file.zig b/src/config_file.zig
index 1277661..cac003a 100644
--- a/src/config_file.zig
+++ b/src/config_file.zig
@@ -196,6 +196,14 @@ pub fn buildProviderConfig(
.prompt_cache = prov.prompt_cache,
.extra_headers = prov.extra_headers,
} },
+ .openai_responses => return .{ .openai_responses = .{
+ .api_key = api_key,
+ .base_url = prov.base_url,
+ .model = wire_model,
+ .reasoning = reasoning,
+ .max_tokens = max_tokens,
+ .extra_headers = prov.extra_headers,
+ } },
}
}
diff --git a/src/luarocks_runtime.zig b/src/luarocks_runtime.zig
index ac34ee0..3cfd02f 100644
--- a/src/luarocks_runtime.zig
+++ b/src/luarocks_runtime.zig
@@ -347,6 +347,30 @@ const default_base_config =
\\# Editor-Plugin-Version = "copilot-chat/0.26.7"
\\# Copilot-Integration-Id = "vscode-chat"
\\
+ \\# OpenAI Codex via a ChatGPT subscription (device login). Codex speaks the
+ \\# OpenAI Responses API, not Chat Completions, so style = "openai_responses".
+ \\# NOTE: this path is implemented but not yet verified against a live
+ \\# ChatGPT plan — see docs/oauth-provider-auth.md.
+ \\#
+ \\# [providers.codex]
+ \\# style = "openai_responses"
+ \\# base_url = "https://chatgpt.com/backend-api/codex"
+ \\# auth = "openai_codex"
+ \\#
+ \\# [providers.codex.extra_headers]
+ \\# OpenAI-Beta = "responses=experimental"
+ \\# originator = "codex_cli_rs"
+ \\#
+ \\# [auth.openai_codex]
+ \\# type = "oauth_device"
+ \\# dialect = "codex"
+ \\# client_id = "app_EMoamEEZ73f0CkXaXp7hrann"
+ \\# device_code_url = "https://auth.openai.com/api/accounts/deviceauth/usercode"
+ \\# device_poll_url = "https://auth.openai.com/api/accounts/deviceauth/token"
+ \\# verification_url = "https://auth.openai.com/codex/device"
+ \\# token_url = "https://auth.openai.com/oauth/token"
+ \\# account_id_jwt_claim = "https://api.openai.com/auth"
+ \\
\\# Tool/extension availability (glob patterns). Empty allow = allow all.
\\# [tools]
\\# allow = ["std.*"]
diff --git a/src/tui_selectors.zig b/src/tui_selectors.zig
index a37acb8..590d321 100644
--- a/src/tui_selectors.zig
+++ b/src/tui_selectors.zig
@@ -48,7 +48,10 @@ pub const ReasoningOption = struct {
detail: []const u8,
value: Value,
- pub const Value = union(APIStyle) {
+ /// Two payload families, decoupled from `APIStyle`: `openai_chat` carries
+ /// a `reasoning` effort (shared by the `openai_chat` and `openai_responses`
+ /// styles), `anthropic_messages` carries a thinking selection.
+ pub const Value = union(enum) {
/// OpenAI `reasoning` enum value.
openai_chat: ReasoningEffort,
/// Anthropic thinking selection: either disabled, or adaptive at an
@@ -85,7 +88,8 @@ pub const ReasoningOption = struct {
/// The reasoning options offered for a provider config's API style.
pub fn forStyle(style: APIStyle) []const ReasoningOption {
return switch (style) {
- .openai_chat => &openai,
+ // The Responses style takes the same reasoning-effort options.
+ .openai_chat, .openai_responses => &openai,
.anthropic_messages => &anthropic,
};
}
@@ -95,6 +99,7 @@ pub const ReasoningOption = struct {
switch (self.value) {
.openai_chat => |r| switch (cfg.*) {
.openai_chat => |*c| c.reasoning = r,
+ .openai_responses => |*c| c.reasoning = r,
.anthropic_messages => {},
},
.anthropic_messages => |sel| switch (cfg.*) {
@@ -105,7 +110,7 @@ pub const ReasoningOption = struct {
c.effort = e;
},
},
- .openai_chat => {},
+ .openai_chat, .openai_responses => {},
},
}
}
@@ -114,7 +119,8 @@ pub const ReasoningOption = struct {
/// preselect the picker on the active value).
pub fn matches(self: ReasoningOption, cfg: ProviderConfig) bool {
switch (self.value) {
- .openai_chat => |r| return cfg == .openai_chat and cfg.openai_chat.reasoning == r,
+ .openai_chat => |r| return (cfg == .openai_chat and cfg.openai_chat.reasoning == r) or
+ (cfg == .openai_responses and cfg.openai_responses.reasoning == r),
.anthropic_messages => |sel| {
if (cfg != .anthropic_messages) return false;
const c = cfg.anthropic_messages;
@@ -167,7 +173,7 @@ pub fn adaptiveFallback(cfg: *ProviderConfig) bool {
c.thinking_budget_tokens = budget;
return true;
},
- .openai_chat => return false,
+ .openai_chat, .openai_responses => return false,
}
}