Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,30 @@ the item when done — git log is the history.
Replace per-broker computation with one shared derive call;
brokers emit raw fields (qty, markPrice, multiplier, side,
avgCost) and downstream math is contract-uniform.
- [ ] Native Anthropic full provider — replaces `agent-sdk` for the
api-key chat path so non-subscription Anthropic credentials
(Claude API, MiniMax, GLM, Kimi, DeepSeek) stop spawning a
Claude Code subprocess every chat turn. Subscription credentials
(loginMethod=claudeai) physically need agent-sdk and stay there.
Shape: parallel to `CodexProvider` (~270 lines) — uses
`@anthropic-ai/sdk` directly, manual tool loop with
tool_use/tool_result content blocks, streaming events, history
serialization, Vercel→Anthropic tool format conversion. Then
wires into `GenerateRouter` (likely as new backend value
`anthropic-native`, or replaces `agent-sdk` for non-claudeai
profiles via the preset's chat adapter declaration once
preset-driven chat routing lands). Cleans up the per-vendor
`/v1` baseUrl hack in preset-catalog along the way (native SDK
hits `/v1/messages` by default, all four Anthropic-compat
vendors accept that path). ~4-6h focused work.
- [ ] Native OpenAI Chat Completions full provider — companion to the
Anthropic native work. Reuses the `openai` SDK we already have
(codex provider uses `client.responses.stream()`; this would
use `client.chat.completions.stream()`). Lets us drop
`vercel-openai` adapter entirely and gives Custom + OpenAI-compat
third parties (Together, Groq, vLLM, LM Studio, Ollama) a
proper light chat path. Same structural shape as the Anthropic
one. ~3-4h.
- [ ] Unified config hot-reload. Right now every consumer of a config
section has to solve "did the user edit this?" on its own —
Telegram/MCP-Ask via `reconnectConnectors`, opentypebb via lazy
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{
"name": "open-alice",
"version": "0.10.0-beta.0",
"version": "0.10.0-beta.1",
"description": "File-based trading agent engine",
"type": "module",
"scripts": {
"dev": "tsx watch src/main.ts",
"dev:ui": "pnpm --filter open-alice-ui dev",
"predev": "turbo run build --filter=@traderalice/opentypebb --filter=@traderalice/ibkr",
"prebuild": "tsx scripts/build-migration-index.ts",
"build": "turbo run build && tsup src/main.ts --format esm --dts",
"build:migration-index": "tsx scripts/build-migration-index.ts",
"start": "node dist/main.js",
"test": "vitest run",
"test:e2e": "vitest run --config vitest.e2e.config.ts",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions scripts/build-migration-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Generate src/migrations/INDEX.md from REGISTRY metadata.
*
* Run via: pnpm build:migration-index
*
* Wired to the `prebuild` hook so `pnpm build` always regenerates.
* INDEX.md is committed to the repo — a PR that adds a migration
* without an INDEX.md update is a visible red flag for reviewers.
*/
import { writeFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { REGISTRY } from '../src/migrations/registry.js'

const here = fileURLToPath(import.meta.url)
const repoRoot = resolve(dirname(here), '..')
const out = resolve(repoRoot, 'src/migrations/INDEX.md')

function escape(s: string): string {
return s.replace(/\|/g, '\\|').replace(/\n/g, ' ')
}

const rows = REGISTRY.map(m =>
`| \`${m.id}\` | ${m.appVersion} | ${m.introducedAt} | ${m.affects.join(', ')} | ${escape(m.summary)} |`,
)

const md = `<!-- Auto-generated by scripts/build-migration-index.ts. Do not edit by hand. -->
<!-- Regenerate with \`pnpm build:migration-index\`. -->

# Migration Index

Each row corresponds to one migration in \`src/migrations/\`. The runner applies pending migrations in this order on every boot, recording applied IDs in \`data/config/_meta.json\`. Migrations are idempotent in their body in addition to the journal-level guard.

| ID | App Version | Date | Affects | Summary |
|----|-------------|------|---------|---------|
${rows.join('\n')}
`

writeFileSync(out, md)
console.log(`Wrote ${out} (${REGISTRY.length} migrations)`)
85 changes: 85 additions & 0 deletions src/ai-providers/preset-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

import { z } from 'zod'
import type { SdkAdapterDeclaration, SdkAdapterId } from './sdk-adapters.js'

// ==================== Types ====================

Expand All @@ -25,6 +26,20 @@ export interface EndpointOption {
label: string
}

/**
* Adapter declaration block for a preset. `available` lists every SDK
* adapter the preset's credential can drive, each with a builder that
* maps the credential into that SDK's standard config shape.
*
* `test` names the adapter used by the wizard's "Test" button — pick
* the lightest available so non-subscription presets skip the heavy
* agent-sdk subprocess.
*/
export interface PresetSdkAdapters {
available: SdkAdapterDeclaration[]
test: SdkAdapterId
}

export interface PresetDef {
id: string
label: string
Expand All @@ -36,6 +51,9 @@ export interface PresetDef {
models?: ModelOption[]
endpoints?: EndpointOption[]
writeOnlyFields?: string[]
/** Internal — not exposed to the wizard JSON Schema. Drives the
* test-path adapter selection in GenerateRouter.askForTest. */
sdkAdapters?: PresetSdkAdapters
}

// ==================== Official: Claude ====================
Expand All @@ -57,6 +75,12 @@ export const CLAUDE_OAUTH: PresetDef = {
{ id: 'claude-opus-4-6', label: 'Claude Opus 4.6' },
{ id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' },
],
sdkAdapters: {
available: [
{ id: 'agent-sdk', config: () => ({ loginMethod: 'claudeai' }) },
],
test: 'agent-sdk',
},
}

export const CLAUDE_API: PresetDef = {
Expand All @@ -79,6 +103,13 @@ export const CLAUDE_API: PresetDef = {
{ id: 'claude-haiku-4-5', label: 'Claude Haiku 4.5' },
],
writeOnlyFields: ['apiKey'],
sdkAdapters: {
available: [
{ id: 'vercel-anthropic', config: (c) => ({ apiKey: c.apiKey, baseURL: c.baseUrl }) },
{ id: 'agent-sdk', config: (c) => ({ apiKey: c.apiKey, baseUrl: c.baseUrl, loginMethod: 'api-key' }) },
],
test: 'vercel-anthropic',
},
}

// ==================== Official: OpenAI Codex ====================
Expand All @@ -99,6 +130,12 @@ export const CODEX_OAUTH: PresetDef = {
{ id: 'gpt-5.4', label: 'GPT 5.4' },
{ id: 'gpt-5.4-mini', label: 'GPT 5.4 Mini' },
],
sdkAdapters: {
available: [
{ id: 'codex', config: () => ({ loginMethod: 'codex-oauth' }) },
],
test: 'codex',
},
}

export const CODEX_API: PresetDef = {
Expand All @@ -118,6 +155,13 @@ export const CODEX_API: PresetDef = {
{ id: 'gpt-5.4-mini', label: 'GPT 5.4 Mini' },
],
writeOnlyFields: ['apiKey'],
sdkAdapters: {
available: [
{ id: 'vercel-openai', config: (c) => ({ apiKey: c.apiKey, baseURL: c.baseUrl }) },
{ id: 'codex', config: (c) => ({ apiKey: c.apiKey, baseUrl: c.baseUrl, loginMethod: 'api-key' }) },
],
test: 'vercel-openai',
},
}

// ==================== Official: Gemini ====================
Expand All @@ -139,6 +183,12 @@ export const GEMINI: PresetDef = {
{ id: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
],
writeOnlyFields: ['apiKey'],
sdkAdapters: {
available: [
{ id: 'vercel-google', config: (c) => ({ apiKey: c.apiKey, baseURL: c.baseUrl }) },
],
test: 'vercel-google',
},
}

// ==================== Third-party: MiniMax ====================
Expand All @@ -165,6 +215,16 @@ export const MINIMAX: PresetDef = {
{ id: 'MiniMax-M2.7', label: 'MiniMax M2.7' },
],
writeOnlyFields: ['apiKey'],
sdkAdapters: {
available: [
// MiniMax serves Anthropic API at `/anthropic/v1/messages`.
// @ai-sdk/anthropic appends `/messages` directly, so the
// preset must append `/v1` to the user's baseUrl.
{ id: 'vercel-anthropic', config: (c) => ({ apiKey: c.apiKey, baseURL: c.baseUrl ? `${c.baseUrl}/v1` : undefined }) },
{ id: 'agent-sdk', config: (c) => ({ apiKey: c.apiKey, baseUrl: c.baseUrl, loginMethod: 'api-key' }) },
],
test: 'vercel-anthropic',
},
}

// ==================== Third-party: GLM (Zhipu) ====================
Expand Down Expand Up @@ -194,6 +254,14 @@ export const GLM: PresetDef = {
{ id: 'glm-4.5-air', label: 'GLM 4.5 Air' },
],
writeOnlyFields: ['apiKey'],
sdkAdapters: {
available: [
// GLM serves Anthropic API at `/anthropic/v1/messages` (path probe).
{ id: 'vercel-anthropic', config: (c) => ({ apiKey: c.apiKey, baseURL: c.baseUrl ? `${c.baseUrl}/v1` : undefined }) },
{ id: 'agent-sdk', config: (c) => ({ apiKey: c.apiKey, baseUrl: c.baseUrl, loginMethod: 'api-key' }) },
],
test: 'vercel-anthropic',
},
}

// ==================== Third-party: Kimi (Moonshot) ====================
Expand Down Expand Up @@ -226,6 +294,14 @@ export const KIMI: PresetDef = {
{ id: 'kimi-k2.5', label: 'Kimi K2.5' },
],
writeOnlyFields: ['apiKey'],
sdkAdapters: {
available: [
// Moonshot serves Anthropic API at `/anthropic/v1/messages` (path probe).
{ id: 'vercel-anthropic', config: (c) => ({ apiKey: c.apiKey, baseURL: c.baseUrl ? `${c.baseUrl}/v1` : undefined }) },
{ id: 'agent-sdk', config: (c) => ({ apiKey: c.apiKey, baseUrl: c.baseUrl, loginMethod: 'api-key' }) },
],
test: 'vercel-anthropic',
},
}

// ==================== Third-party: DeepSeek ====================
Expand All @@ -252,6 +328,15 @@ export const DEEPSEEK: PresetDef = {
{ id: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash (cheap/fast)' },
],
writeOnlyFields: ['apiKey'],
sdkAdapters: {
available: [
// DeepSeek serves Anthropic API at `/anthropic/messages` (no /v1
// segment), unlike MiniMax/GLM/Kimi which need /v1 appended.
{ id: 'vercel-anthropic', config: (c) => ({ apiKey: c.apiKey, baseURL: c.baseUrl }) },
{ id: 'agent-sdk', config: (c) => ({ apiKey: c.apiKey, baseUrl: c.baseUrl, loginMethod: 'api-key' }) },
],
test: 'vercel-anthropic',
},
}

// ==================== Custom ====================
Expand Down
Loading
Loading