forked from EduCraft/curriculum-project-hub
feat(hub): 抽出运行时配置
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { InMemoryModelRegistry, type ModelRegistry } from "../agent/models.js";
|
||||
|
||||
export type Env = Readonly<Record<string, string | undefined>>;
|
||||
|
||||
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<string, string | undefined>;
|
||||
}
|
||||
|
||||
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<ProviderRuntimeSettings>;
|
||||
modelRegistry(scope?: RuntimeScope): Promise<ModelRegistry>;
|
||||
runPolicy(input: RunPolicyInput): Promise<RunPolicy>;
|
||||
}
|
||||
|
||||
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<ProviderRuntimeSettings> {
|
||||
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<ModelRegistry> {
|
||||
void scope;
|
||||
return createDefaultModelRegistry(this.envSource());
|
||||
}
|
||||
|
||||
async runPolicy(input: RunPolicyInput): Promise<RunPolicy> {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user