mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(hub,web): support custom Claude models via settings.json (customClaudeModels)
This commit is contained in:
@@ -20,6 +20,8 @@ export interface Settings {
|
||||
listenPort?: number
|
||||
publicUrl?: string
|
||||
corsOrigins?: string[]
|
||||
/** Custom model names offered in the Claude model picker (e.g. DeepSeek via ANTHROPIC_BASE_URL). */
|
||||
customClaudeModels?: string[]
|
||||
}
|
||||
|
||||
export function getSettingsFile(dataDir: string): string {
|
||||
|
||||
+2
-1
@@ -258,7 +258,8 @@ export async function startHub(options: StartHubOptions = {}): Promise<HubInstan
|
||||
socketEngine: socketServer.engine,
|
||||
corsOrigins,
|
||||
relayMode: relayFlag.enabled,
|
||||
officialWebUrl
|
||||
officialWebUrl,
|
||||
dataDir: config.dataDir
|
||||
})
|
||||
|
||||
// Start the bot if configured
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { WebAppEnv } from '../middleware/auth'
|
||||
import { getSettingsFile, readSettings } from '../../config/settings'
|
||||
|
||||
/**
|
||||
* Custom Claude model names configured in settings.json
|
||||
* (`customClaudeModels`). Claude Code has no model catalog API like Codex,
|
||||
* so users routing Claude through a custom ANTHROPIC_BASE_URL list their
|
||||
* endpoint's model names here to surface them in the New Session picker.
|
||||
*/
|
||||
export function createClaudeModelsRoutes(dataDir: string): Hono<WebAppEnv> {
|
||||
const app = new Hono<WebAppEnv>()
|
||||
|
||||
app.get('/claude/custom-models', async (c) => {
|
||||
const settings = await readSettings(getSettingsFile(dataDir))
|
||||
const models = Array.isArray(settings?.customClaudeModels)
|
||||
? settings.customClaudeModels
|
||||
: []
|
||||
return c.json({ models })
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import { createCodexDesktopRoutes } from './routes/codexDesktop'
|
||||
import { createPushRoutes } from './routes/push'
|
||||
import { createDevicesRoutes } from './routes/devices'
|
||||
import { createVoiceRoutes } from './routes/voice'
|
||||
import { createClaudeModelsRoutes } from './routes/claudeModels'
|
||||
import type { SSEManager } from '../sse/sseManager'
|
||||
import type { VisibilityTracker } from '../visibility/visibilityTracker'
|
||||
import type { Server as BunServer, ServerWebSocket } from 'bun'
|
||||
@@ -219,6 +220,7 @@ function createWebApp(options: {
|
||||
embeddedAssetMap: Map<string, EmbeddedWebAsset> | null
|
||||
relayMode?: boolean
|
||||
officialWebUrl?: string
|
||||
dataDir: string
|
||||
}): Hono<WebAppEnv> {
|
||||
const app = new Hono<WebAppEnv>()
|
||||
|
||||
@@ -257,6 +259,7 @@ function createWebApp(options: {
|
||||
getSyncEngine: options.getSyncEngine
|
||||
}))
|
||||
app.route('/api', createPushRoutes(options.store, options.vapidPublicKey))
|
||||
app.route('/api', createClaudeModelsRoutes(options.dataDir))
|
||||
app.route('/api', createDevicesRoutes(options.store))
|
||||
app.route('/api', createVoiceRoutes())
|
||||
|
||||
@@ -374,6 +377,7 @@ export async function startWebServer(options: {
|
||||
corsOrigins?: string[]
|
||||
relayMode?: boolean
|
||||
officialWebUrl?: string
|
||||
dataDir: string
|
||||
}): Promise<BunServer<WebSocketData>> {
|
||||
const isCompiled = isBunCompiled()
|
||||
const embeddedAssetMap = isCompiled ? await loadEmbeddedAssetMap() : null
|
||||
@@ -386,6 +390,7 @@ export async function startWebServer(options: {
|
||||
vapidPublicKey: options.vapidPublicKey,
|
||||
corsOrigins: options.corsOrigins,
|
||||
embeddedAssetMap,
|
||||
dataDir: options.dataDir,
|
||||
relayMode: options.relayMode,
|
||||
officialWebUrl: options.officialWebUrl
|
||||
})
|
||||
|
||||
@@ -198,6 +198,10 @@ export class ApiClient {
|
||||
return await this.request<PushVapidPublicKeyResponse>('/api/push/vapid-public-key')
|
||||
}
|
||||
|
||||
async getClaudeCustomModels(): Promise<{ models: string[] }> {
|
||||
return await this.request<{ models: string[] }>('/api/claude/custom-models')
|
||||
}
|
||||
|
||||
async subscribePushNotifications(payload: PushSubscriptionPayload): Promise<void> {
|
||||
await this.request('/api/push/subscribe', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
shouldRestoreNewSessionFormDraft
|
||||
} from './newSessionFormDraft'
|
||||
import type { AgentType, LaunchEffort, CodexReasoningEffort, NewSessionServiceTier, SessionType } from './types'
|
||||
import { MODEL_OPTIONS } from './types'
|
||||
import { ActionButtons } from './ActionButtons'
|
||||
import { AgentSelector } from './AgentSelector'
|
||||
import { CollaborationModeSelector } from './CollaborationModeSelector'
|
||||
@@ -246,6 +247,32 @@ export function NewSession(props: {
|
||||
machineId,
|
||||
enabled: agent === 'codex' && Boolean(machineId)
|
||||
})
|
||||
const [claudeCustomModels, setClaudeCustomModels] = useState<string[]>([])
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
if (!props.api) {
|
||||
return
|
||||
}
|
||||
props.api.getClaudeCustomModels().then((result) => {
|
||||
if (!cancelled) {
|
||||
setClaudeCustomModels(Array.isArray(result.models) ? result.models : [])
|
||||
}
|
||||
}).catch(() => {
|
||||
// Custom models are optional — fall back to the built-in presets.
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [props.api])
|
||||
const claudeModelOptions = useMemo(() => {
|
||||
const options = [...MODEL_OPTIONS.claude]
|
||||
for (const modelName of claudeCustomModels) {
|
||||
if (!options.some((option) => option.value === modelName)) {
|
||||
options.push({ value: modelName, label: modelName })
|
||||
}
|
||||
}
|
||||
return options
|
||||
}, [claudeCustomModels])
|
||||
const runnerSpawnError = useMemo(
|
||||
() => formatRunnerSpawnError(selectedMachine),
|
||||
[selectedMachine]
|
||||
@@ -1321,7 +1348,9 @@ export function NewSession(props: {
|
||||
agent={agent}
|
||||
model={model}
|
||||
options={
|
||||
agent === 'codex'
|
||||
agent === 'claude'
|
||||
? claudeModelOptions
|
||||
: agent === 'codex'
|
||||
? codexModelOptions
|
||||
: agent === 'grok'
|
||||
? grokModelOptions
|
||||
|
||||
Reference in New Issue
Block a user