feat: 支持 v0.1 deepseek backend profile
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import type { BackendProfile, JsonRecord } from "./types.js";
|
||||
|
||||
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: ["auth.json", "config.toml"];
|
||||
defaultSecretName: string;
|
||||
profileIsolation: "profile-scoped-codex-home";
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const backendProfileSpecs: 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",
|
||||
},
|
||||
];
|
||||
|
||||
export const backendProfiles = backendProfileSpecs.map((item) => item.profile) as readonly BackendProfile[];
|
||||
|
||||
export function backendProfileSpec(profile: string): BackendProfileSpec | null {
|
||||
return backendProfileSpecs.find((item) => item.profile === profile) ?? null;
|
||||
}
|
||||
|
||||
export function isBackendProfile(value: string): value is BackendProfile {
|
||||
return backendProfileSpec(value) !== null;
|
||||
}
|
||||
|
||||
export function backendCapability(spec: BackendProfileSpec): JsonRecord {
|
||||
return {
|
||||
profile: spec.profile,
|
||||
backendKind: spec.backendKind,
|
||||
protocol: spec.protocol,
|
||||
transport: spec.transport,
|
||||
command: spec.command,
|
||||
status: spec.status,
|
||||
requiredSecretKeys: [...spec.requiredSecretKeys],
|
||||
defaultSecretRef: { name: spec.defaultSecretName, keys: [...spec.requiredSecretKeys] },
|
||||
profileIsolation: spec.profileIsolation,
|
||||
description: spec.description,
|
||||
};
|
||||
}
|
||||
|
||||
export function backendCapabilities(): JsonRecord[] {
|
||||
return backendProfileSpecs.map(backendCapability);
|
||||
}
|
||||
|
||||
export function backendCapabilitiesSqlValues(): string {
|
||||
return backendProfileSpecs.map((spec) => {
|
||||
const capabilities = JSON.stringify({
|
||||
backendKind: spec.backendKind,
|
||||
protocol: spec.protocol,
|
||||
transport: spec.transport,
|
||||
command: spec.command,
|
||||
requiredSecretKeys: spec.requiredSecretKeys,
|
||||
defaultSecretRef: { name: spec.defaultSecretName, keys: spec.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, "''");
|
||||
}
|
||||
+1
-1
@@ -22,7 +22,7 @@ export type FailureKind =
|
||||
export type RunStatus = "pending" | "claimed" | "running" | "completed" | "failed" | "blocked" | "cancelled";
|
||||
export type CommandState = "pending" | "acknowledged" | "completed" | "failed" | "cancelled";
|
||||
export type TerminalStatus = "completed" | "failed" | "blocked" | "cancelled";
|
||||
export type BackendProfile = "codex";
|
||||
export type BackendProfile = "codex" | "deepseek";
|
||||
|
||||
export interface WorkspaceRef extends JsonRecord {
|
||||
kind: "git-worktree" | "host-path" | "kubernetes-pvc" | "opaque";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import type { BackendProfile, CreateCommandInput, CreateRunInput, ExecutionPolicy, JsonRecord, JsonValue } from "./types.js";
|
||||
import { AgentRunError } from "./errors.js";
|
||||
import { backendProfileSpec, backendProfiles, isBackendProfile } from "./backend-profiles.js";
|
||||
|
||||
const allowedTenants = new Set(["unidesk", "hwlab"]);
|
||||
const allowedBackends = new Set<BackendProfile>(["codex"]);
|
||||
|
||||
export function nowIso(): string {
|
||||
return new Date().toISOString();
|
||||
@@ -42,9 +42,11 @@ export function validateCreateRun(input: unknown): CreateRunInput {
|
||||
const record = asRecord(input, "run");
|
||||
const tenantId = requiredString(record, "tenantId");
|
||||
if (!allowedTenants.has(tenantId)) throw new AgentRunError("tenant-policy-denied", `tenantId ${tenantId} is not allowed`, { httpStatus: 403 });
|
||||
const backendProfile = requiredString(record, "backendProfile") as BackendProfile;
|
||||
if (!allowedBackends.has(backendProfile)) throw new AgentRunError("schema-invalid", `backendProfile ${backendProfile} is not supported in v0.1`, { httpStatus: 400 });
|
||||
const backendProfileValue = requiredString(record, "backendProfile");
|
||||
if (!isBackendProfile(backendProfileValue)) throw new AgentRunError("schema-invalid", `backendProfile ${backendProfileValue} is not supported in v0.1`, { httpStatus: 400, details: { allowedBackends: [...backendProfiles] } });
|
||||
const backendProfile = backendProfileValue as BackendProfile;
|
||||
const executionPolicy = validateExecutionPolicy(requiredRecord(record, "executionPolicy"));
|
||||
validateBackendSecretScope(backendProfile, executionPolicy);
|
||||
return {
|
||||
tenantId,
|
||||
projectId: requiredString(record, "projectId"),
|
||||
@@ -64,8 +66,15 @@ export function validateExecutionPolicy(record: JsonRecord): ExecutionPolicy {
|
||||
const providerCredentials = Array.isArray(secretScope.providerCredentials) ? secretScope.providerCredentials : [];
|
||||
for (const credential of providerCredentials) {
|
||||
const item = asRecord(credential, "providerCredential");
|
||||
const profile = typeof item.profile === "string" ? item.profile.trim() : "";
|
||||
if (profile.length === 0) throw new AgentRunError("schema-invalid", "provider credential profile is required", { httpStatus: 400 });
|
||||
if (!isBackendProfile(profile)) throw new AgentRunError("schema-invalid", `provider credential profile ${profile} is not supported in v0.1`, { httpStatus: 400, details: { allowedBackends: [...backendProfiles] } });
|
||||
const secretRef = asRecord(item.secretRef, "providerCredential.secretRef");
|
||||
if (typeof secretRef.name !== "string" || secretRef.name.length === 0) throw new AgentRunError("schema-invalid", "provider credential secretRef.name is required", { httpStatus: 400 });
|
||||
const keys = Array.isArray(secretRef.keys) ? secretRef.keys : [];
|
||||
for (const requiredKey of backendProfileSpec(profile)?.requiredSecretKeys ?? []) {
|
||||
if (!keys.includes(requiredKey)) throw new AgentRunError("schema-invalid", `provider credential ${profile} secretRef.keys must include ${requiredKey}`, { httpStatus: 400 });
|
||||
}
|
||||
}
|
||||
const secretScopeResult: ExecutionPolicy["secretScope"] = { allowCredentialEcho: false };
|
||||
if (providerCredentials.length > 0) secretScopeResult.providerCredentials = providerCredentials as NonNullable<ExecutionPolicy["secretScope"]["providerCredentials"]>;
|
||||
@@ -78,6 +87,15 @@ export function validateExecutionPolicy(record: JsonRecord): ExecutionPolicy {
|
||||
};
|
||||
}
|
||||
|
||||
function validateBackendSecretScope(backendProfile: BackendProfile, executionPolicy: ExecutionPolicy): void {
|
||||
const credentials = executionPolicy.secretScope.providerCredentials ?? [];
|
||||
const matching = credentials.filter((item) => item.profile === backendProfile);
|
||||
if (matching.length === 0) {
|
||||
throw new AgentRunError("secret-unavailable", `backendProfile ${backendProfile} requires a matching provider credential SecretRef`, { httpStatus: 400, details: { backendProfile, requiredSecretName: backendProfileSpec(backendProfile)?.defaultSecretName ?? null } });
|
||||
}
|
||||
if (matching.length > 1) throw new AgentRunError("schema-invalid", `backendProfile ${backendProfile} has multiple matching provider credentials`, { httpStatus: 400 });
|
||||
}
|
||||
|
||||
export function validateCreateCommand(input: unknown): CreateCommandInput {
|
||||
const record = asRecord(input, "command");
|
||||
const type = requiredString(record, "type");
|
||||
|
||||
Reference in New Issue
Block a user