import { InMemoryModelRegistry, type ModelRegistry } from "../agent/models.js"; export type Env = Readonly>; type EnvSource = Env | (() => Env); const DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api"; const DEFAULT_SONNET_MODEL = "anthropic/claude-sonnet-5"; const DEFAULT_SONNET_LABEL = "Claude Sonnet 5"; const DEFAULT_AGENT_MAX_TURNS = 25; export interface ProviderRuntimeSettings { readonly id: string; readonly baseUrl: string; readonly authToken: string; readonly anthropicApiKey: string; readonly sdkEnv: Record; } export interface RuntimeScope { readonly projectId?: string; } export interface RunPolicyInput { readonly projectId: string; readonly roleId: string; } export interface RunPolicy { readonly maxTurns: number; } export interface RuntimeSettings { provider(providerId: string, scope?: RuntimeScope): Promise; modelRegistry(scope?: RuntimeScope): Promise; runPolicy(input: RunPolicyInput): Promise; } export class EnvRuntimeSettings implements RuntimeSettings { private readonly envSource: () => Env; constructor(env: EnvSource = process.env) { this.envSource = typeof env === "function" ? env : () => env; } async provider(providerId: string, scope?: RuntimeScope): Promise { void scope; if (providerId !== "openrouter") { throw new Error(`unknown provider runtime settings: ${providerId}`); } const env = this.envSource(); const baseUrl = readEnv(env, "ANTHROPIC_BASE_URL") ?? DEFAULT_OPENROUTER_BASE_URL; const authToken = requireSetting(env, "ANTHROPIC_AUTH_TOKEN"); const anthropicApiKey = readEnv(env, "ANTHROPIC_API_KEY") ?? ""; return { id: providerId, baseUrl, authToken, anthropicApiKey, sdkEnv: { ANTHROPIC_BASE_URL: baseUrl, ANTHROPIC_AUTH_TOKEN: authToken, ANTHROPIC_API_KEY: anthropicApiKey, }, }; } async modelRegistry(scope?: RuntimeScope): Promise { void scope; return createDefaultModelRegistry(this.envSource()); } async runPolicy(input: RunPolicyInput): Promise { void input; return { maxTurns: positiveIntegerEnv(this.envSource(), "HUB_AGENT_MAX_TURNS", DEFAULT_AGENT_MAX_TURNS), }; } } export function createEnvRuntimeSettings(env: EnvSource = process.env): RuntimeSettings { return new EnvRuntimeSettings(env); } export function defaultSonnetModel(env: Env = process.env): string { return readEnv(env, "ANTHROPIC_DEFAULT_SONNET_MODEL") ?? DEFAULT_SONNET_MODEL; } export function createDefaultModelRegistry(env: Env = process.env): InMemoryModelRegistry { const sonnetModel = defaultSonnetModel(env); const sonnetLabel = readEnv(env, "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME") ?? (sonnetModel === DEFAULT_SONNET_MODEL ? DEFAULT_SONNET_LABEL : sonnetModel); return new InMemoryModelRegistry( [ { id: sonnetModel, label: sonnetLabel, toolCapable: true }, ], [ { id: "draft", label: "草稿", defaultModel: sonnetModel }, { id: "review", label: "审校", defaultModel: sonnetModel }, ], ); } function readEnv(env: Env, name: string): string | undefined { const value = env[name]?.trim(); return value === undefined || value === "" ? undefined : value; } function requireSetting(env: Env, name: string): string { const value = readEnv(env, name); if (value === undefined) { throw new Error(`missing required runtime setting: ${name}`); } return value; } function positiveIntegerEnv(env: Env, name: string, fallback: number): number { const raw = readEnv(env, name); if (raw === undefined) return fallback; const value = Number(raw); if (!Number.isInteger(value) || value <= 0) { throw new Error(`runtime setting ${name} must be a positive integer`); } return value; }