189 lines
7.0 KiB
TypeScript
189 lines
7.0 KiB
TypeScript
import type { BackendProfile, JsonRecord } from "./types.js";
|
|
|
|
export const backendProfileIdPattern = /^[a-z0-9][a-z0-9-]{0,63}$/u;
|
|
|
|
export interface BackendProfileSpec {
|
|
profile: BackendProfile;
|
|
backendKind: "codex-app-server-stdio";
|
|
protocol: "codex-app-server-jsonrpc-stdio";
|
|
transport: "stdio";
|
|
command: "codex app-server --listen stdio://";
|
|
status: "registered";
|
|
requiredSecretKeys: readonly string[];
|
|
defaultSecretName: string;
|
|
profileIsolation: "profile-scoped-codex-home";
|
|
description: string;
|
|
}
|
|
|
|
const builtinBackendProfileSpecs: readonly BackendProfileSpec[] = [
|
|
{
|
|
profile: "codex",
|
|
backendKind: "codex-app-server-stdio",
|
|
protocol: "codex-app-server-jsonrpc-stdio",
|
|
transport: "stdio",
|
|
command: "codex app-server --listen stdio://",
|
|
status: "registered",
|
|
requiredSecretKeys: ["auth.json", "config.toml"],
|
|
defaultSecretName: "agentrun-v01-provider-codex",
|
|
profileIsolation: "profile-scoped-codex-home",
|
|
description: "Default Codex-compatible profile",
|
|
},
|
|
{
|
|
profile: "deepseek",
|
|
backendKind: "codex-app-server-stdio",
|
|
protocol: "codex-app-server-jsonrpc-stdio",
|
|
transport: "stdio",
|
|
command: "codex app-server --listen stdio://",
|
|
status: "registered",
|
|
requiredSecretKeys: ["auth.json", "config.toml"],
|
|
defaultSecretName: "agentrun-v01-provider-deepseek",
|
|
profileIsolation: "profile-scoped-codex-home",
|
|
description: "DeepSeek-compatible profile through Codex app-server stdio",
|
|
},
|
|
{
|
|
profile: "minimax-m3",
|
|
backendKind: "codex-app-server-stdio",
|
|
protocol: "codex-app-server-jsonrpc-stdio",
|
|
transport: "stdio",
|
|
command: "codex app-server --listen stdio://",
|
|
status: "registered",
|
|
requiredSecretKeys: ["auth.json", "config.toml"],
|
|
defaultSecretName: "agentrun-v01-provider-minimax-m3",
|
|
profileIsolation: "profile-scoped-codex-home",
|
|
description: "MiniMax M3 OpenAI-compatible profile through Codex app-server stdio",
|
|
},
|
|
{
|
|
profile: "dsflash-go",
|
|
backendKind: "codex-app-server-stdio",
|
|
protocol: "codex-app-server-jsonrpc-stdio",
|
|
transport: "stdio",
|
|
command: "codex app-server --listen stdio://",
|
|
status: "registered",
|
|
requiredSecretKeys: ["auth.json", "config.toml", "model-catalog.json"],
|
|
defaultSecretName: "agentrun-v01-provider-dsflash-go",
|
|
profileIsolation: "profile-scoped-codex-home",
|
|
description: "DeepSeek V4 Flash profile through OpenCode Zen Go Moon Bridge",
|
|
},
|
|
];
|
|
|
|
export const backendProfileSpecs = builtinBackendProfileSpecs;
|
|
|
|
export const backendProfiles = builtinBackendProfileSpecs.map((item) => item.profile) as readonly BackendProfile[];
|
|
|
|
export function compareBackendProfiles(left: string, right: string): number {
|
|
const leftIndex = builtinBackendProfileSpecs.findIndex((item) => item.profile === left);
|
|
const rightIndex = builtinBackendProfileSpecs.findIndex((item) => item.profile === right);
|
|
if (leftIndex >= 0 || rightIndex >= 0) {
|
|
if (leftIndex < 0) return 1;
|
|
if (rightIndex < 0) return -1;
|
|
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
|
|
}
|
|
return left.localeCompare(right);
|
|
}
|
|
|
|
export function defaultSecretNameForProfile(profile: string): string {
|
|
return `agentrun-v01-provider-${profile}`;
|
|
}
|
|
|
|
function dynamicBackendProfileSpec(profile: string): BackendProfileSpec {
|
|
return {
|
|
profile,
|
|
backendKind: "codex-app-server-stdio",
|
|
protocol: "codex-app-server-jsonrpc-stdio",
|
|
transport: "stdio",
|
|
command: "codex app-server --listen stdio://",
|
|
status: "registered",
|
|
requiredSecretKeys: ["auth.json", "config.toml"],
|
|
defaultSecretName: defaultSecretNameForProfile(profile),
|
|
profileIsolation: "profile-scoped-codex-home",
|
|
description: `Dynamic Codex-compatible profile ${profile}`,
|
|
};
|
|
}
|
|
|
|
export function backendProfileSpec(profile: string): BackendProfileSpec | null {
|
|
if (!isBackendProfileSlug(profile)) return null;
|
|
return builtinBackendProfileSpecs.find((item) => item.profile === profile) ?? dynamicBackendProfileSpec(profile);
|
|
}
|
|
|
|
export function isBackendProfileSlug(value: string): boolean {
|
|
return backendProfileIdPattern.test(value);
|
|
}
|
|
|
|
export function isBackendProfile(value: string): value is BackendProfile {
|
|
return isBackendProfileSlug(value);
|
|
}
|
|
|
|
export function backendCapability(spec: BackendProfileSpec): JsonRecord {
|
|
const defaultSecretRef = { name: spec.defaultSecretName, keys: [...spec.requiredSecretKeys], valuesPrinted: false };
|
|
return {
|
|
profile: spec.profile,
|
|
backendKind: spec.backendKind,
|
|
protocol: spec.protocol,
|
|
transport: spec.transport,
|
|
command: spec.command,
|
|
status: spec.status,
|
|
requiredSecretKeys: [...spec.requiredSecretKeys],
|
|
defaultSecretRef,
|
|
preflight: {
|
|
readiness: "requires-profile-secret-ref",
|
|
defaultSecretRef,
|
|
requiredSecretKeys: [...spec.requiredSecretKeys],
|
|
profileIsolation: spec.profileIsolation,
|
|
credentialValuesPrinted: false,
|
|
valuesPrinted: false,
|
|
},
|
|
profileIsolation: spec.profileIsolation,
|
|
description: spec.description,
|
|
};
|
|
}
|
|
|
|
export function mergeBackendCapability(profile: string, storedCapabilities: JsonRecord): JsonRecord {
|
|
const spec = backendProfileSpec(profile);
|
|
if (!spec) return { profile, ...storedCapabilities, valuesPrinted: false };
|
|
const base = backendCapability(spec);
|
|
return {
|
|
...base,
|
|
...storedCapabilities,
|
|
defaultSecretRef: base.defaultSecretRef,
|
|
preflight: base.preflight,
|
|
};
|
|
}
|
|
|
|
export function backendCapabilities(profiles: readonly string[] = backendProfiles): JsonRecord[] {
|
|
const profileIds = new Set<BackendProfile>(backendProfiles);
|
|
for (const profile of profiles) {
|
|
if (isBackendProfileSlug(profile)) profileIds.add(profile as BackendProfile);
|
|
}
|
|
return [...profileIds]
|
|
.sort(compareBackendProfiles)
|
|
.map((profile) => backendCapability(backendProfileSpec(profile) as BackendProfileSpec));
|
|
}
|
|
|
|
export function backendCapabilitiesSqlValues(profiles?: readonly BackendProfile[], options: { requiredSecretKeysByProfile?: Record<string, readonly string[]> } = {}): string {
|
|
const specs = profiles
|
|
? profiles.map((profile) => {
|
|
const spec = backendProfileSpec(profile);
|
|
if (!spec) throw new Error(`unknown backend profile for SQL seed: ${profile}`);
|
|
return spec;
|
|
})
|
|
: builtinBackendProfileSpecs;
|
|
return specs.map((spec) => {
|
|
const requiredSecretKeys = options.requiredSecretKeysByProfile?.[spec.profile] ?? spec.requiredSecretKeys;
|
|
const capabilities = JSON.stringify({
|
|
backendKind: spec.backendKind,
|
|
protocol: spec.protocol,
|
|
transport: spec.transport,
|
|
command: spec.command,
|
|
requiredSecretKeys,
|
|
defaultSecretRef: { name: spec.defaultSecretName, keys: requiredSecretKeys },
|
|
profileIsolation: spec.profileIsolation,
|
|
description: spec.description,
|
|
});
|
|
return `('${sqlString(spec.profile)}', '${sqlString(capabilities)}'::jsonb, '{"mode":"manual-runner-v0.1"}'::jsonb, '{"status":"${sqlString(spec.status)}"}'::jsonb, now())`;
|
|
}).join(",\n");
|
|
}
|
|
|
|
function sqlString(value: string): string {
|
|
return value.replace(/'/gu, "''");
|
|
}
|