feat: AI provider preset system + profile UX + Codex fixes#112
Merged
luokerenx4 merged 18 commits intomasterfrom Apr 8, 2026
Merged
feat: AI provider preset system + profile UX + Codex fixes#112luokerenx4 merged 18 commits intomasterfrom
luokerenx4 merged 18 commits intomasterfrom
Conversation
Verifies real API communication against the ChatGPT subscription endpoint: - Basic text response - Tool call with correct call_id/name/arguments (via output_item.done) - Full tool call round-trip (send result back, get final text) - Multi-turn structured input (model references prior context) Skips gracefully if ~/.codex/auth.json is not present. Runs via `pnpm test:e2e`, excluded from regular `pnpm test`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Presets are constraint-based templates that define which fields are locked/hidden/required and what models are available to choose from. Users pick a preset, then fill only the editable fields. Built-in presets: - Claude (OAuth subscription / API key) - OpenAI/Codex (ChatGPT subscription / API key) - Google Gemini - MiniMax (third-party, Anthropic-compatible API) - Custom (full control) Also fixes OAuth model handling: when loginMethod is 'claudeai', model is optional — omitting it lets Claude Code pick based on the user's subscription plan. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UX redesign: - Main page is a clean profile list with "Set Default" / "Edit" buttons - Edit opens a modal (preset-aware form with locked/hidden fields) - New Profile opens a modal (step 1: choose preset, step 2: fill form) - No more inline forms or mixed state between list and editor Remove global API keys: - apiKeys field migrated into individual profile.apiKey fields - resolveProfile() no longer falls back to global keys - Removed PUT /api-keys and GET /api-keys/status endpoints - Removed frontend ApiKeysForm and related API methods Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Presets now generate JSON Schema from Zod definitions. Frontend renders
forms dynamically from schema — no hardcoded field logic.
Backend (presets.ts):
- Each preset defined as a Zod schema + metadata (label, description, hint)
- z.toJSONSchema() serializes to JSON Schema, post-processed for:
- oneOf + const + title for model dropdowns with labels
- writeOnly for password fields (apiKey)
- z.literal() → const (hidden, value baked in)
Frontend (AIProviderPage.tsx):
- SchemaForm component: generic JSON Schema → form renderer
- const → hidden, oneOf → labeled dropdown, writeOnly → password,
enum → dropdown, string → text input
- required/default/description from schema
- Removes PresetFields, PresetField, PresetModelOption types
- Preset hint renders as guidance text above form
Adding a new provider preset only requires editing presets.ts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Profile objects no longer have a `label` field — the profiles Record key (name) serves as both unique identifier and display name. - Remove label from profile schemas, ResolvedProfile, frontend types - Presets gain defaultName — official presets auto-fill the name field, users only need to fill name for Custom presets - Frontend displays profile key directly in list and modal titles Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MiniMax's Anthropic-compatible API works with Claude Agent SDK, not Vercel AI SDK (which appends /messages and gets 404). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add currency tracking throughout the UTA abstraction layer to fix incorrect display of non-USD positions (e.g. HKD stocks shown as USD). - Add `currency` field to Position, `baseCurrency` to AccountInfo - New FxService with dual-table design: hardcoded default rates for offline resilience + live cache from market-data currency client - Lookup priority: live → stale cache → default table → 1:1 fallback - All four brokers (IBKR, Alpaca, CCXT, Mock) populate currency fields - CCXT normalizes stablecoins (USDT/USDC/BUSD) to USD at broker layer - AccountManager aggregates equity with FX conversion to USD - Snapshots and trading tools carry currency information through - 15 new tests for FxService covering full degradation chain Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…link - preset-catalog.ts: pure data file with Zod-defined preset declarations. Update model versions by editing only this file. - presets.ts: slimmed to pure serialization (Zod → JSON Schema + post-processing) - Profile schema gains `preset` field — links profile to its source preset. Edit modal looks up preset by profile.preset instead of reverse-matching. - useSchemaForm hook: parses JSON Schema into field descriptors + form state. Components just render fields by type, no schema parsing logic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- useSchemaForm: reset formData when schema changes (fixes "Model is required" when selecting a preset in create modal) - Remove select-custom field type — oneOf fields render as strict dropdowns without "Custom..." option. Official preset model lists are enforced, not escapable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove modelOptional and "Auto" option — OAuth presets now default to a specific model (claude-sonnet-4-6, gpt-5.4) instead of allowing empty. Simplifies validation and avoids the "Model is required" error when users select Auto. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n test - Official preset names are read-only (locked to defaultName) - Already-configured presets show "Already configured" and are disabled - "Create & Test" button: saves profile then sends "Hi" to verify connectivity, displays response or error - New POST /config/profiles/:slug/test endpoint via AgentCenter.testProfile() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
handleCreateSave no longer closes modal — the modal manages its own lifecycle: save → test → show result → auto-close on success after 2s. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Profile names with "/" break URL routing (Hono treats it as path separator). Fixed by: - Renaming "OpenAI / Codex" to "OpenAI Codex" in preset defaults - Adding encodeURIComponent() to all profile API URL paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…treaming The ChatGPT subscription endpoint returns 400 for non-streaming responses.create() calls. Changed ask() to use responses.stream() matching the same pattern as generate(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AIProvider.ask() now takes optional ResolvedProfile — providers use it for auth/model/endpoint instead of falling back to defaults. Fixes Codex ask() always using OAuth regardless of profile loginMethod. GenerateRouter gains askWithProfile() for inline (unsaved) profiles. Test endpoint changed to POST /profiles/test — accepts profile data, tests connectivity WITHOUT saving first. Frontend flow: validate → test (with profile data) → show result → save on success Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Test Connection button runs the test. On success, button changes to Save for user to explicitly confirm. No more auto-save after test. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…llback When loginMethod is 'api-key', the Claude Code CLI would silently use a local OAuth session from ~/.claude/ instead of the provided ANTHROPIC_API_KEY. Setting CLAUDE_CODE_SIMPLE=1 (equivalent to --bare) disables OAuth entirely, forcing the CLI to use the API key. Investigated via Claude Code source: forceLoginMethod only controls which OAuth provider to use, not API key vs OAuth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When askAgentSdk returns ok:false (e.g. CLI exit code 1 from invalid API key), ask() was returning the error message as successful text. Now it throws, so the test endpoint correctly reports failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Major overhaul of the AI provider configuration system, building on the Codex integration and profile system from PR #106.
Preset System
preset-catalog.tsonlyconst= locked,oneOf= dropdown with labels,writeOnly= passworduseSchemaFormhook — generic JSON Schema → form renderer, no hardcoded field logicProfile UX
labelremoved, profile key = display nameCodex Provider Fixes
store: falserequired by ChatGPT subscription endpointinputmust be array (not string)response.output_item.donefor function callcall_id(notarguments.done)ask()uses streaming (non-streaming returns 400 on subscription endpoint)toResponsesInput()Agent SDK Fixes
CLAUDE_CODE_SIMPLE=1forces API key mode, prevents OAuth fallback when local login existsask()now throws onok: falseinstead of returning error text as successask(prompt, profile?)— all providers accept profile parameter for correct authE2E Tests
Test plan
pnpm test— 974 tests passingpnpm build— clean🤖 Generated with Claude Code