feat: add v0.1 runtime skeleton

This commit is contained in:
Codex
2026-05-29 10:52:41 +08:00
parent 4b33e67484
commit 5deb9fa7fd
20 changed files with 1238 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
import { createHash, randomUUID } from "node:crypto";
import type { BackendProfile, CreateCommandInput, CreateRunInput, ExecutionPolicy, JsonRecord, JsonValue } from "./types.js";
import { AgentRunError } from "./errors.js";
const allowedTenants = new Set(["unidesk", "hwlab"]);
const allowedBackends = new Set<BackendProfile>(["codex"]);
export function nowIso(): string {
return new Date().toISOString();
}
export function newId(prefix: string): string {
return `${prefix}_${randomUUID().replace(/-/gu, "")}`;
}
export function stableHash(value: JsonValue): string {
return createHash("sha256").update(JSON.stringify(sortJson(value))).digest("hex");
}
function sortJson(value: JsonValue): JsonValue {
if (Array.isArray(value)) return value.map(sortJson);
if (typeof value !== "object" || value === null) return value;
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, sortJson(entry)]));
}
export function asRecord(value: unknown, fieldName: string): JsonRecord {
if (typeof value === "object" && value !== null && !Array.isArray(value)) return value as JsonRecord;
throw new AgentRunError("schema-invalid", `${fieldName} must be an object`, { httpStatus: 400 });
}
function requiredString(record: JsonRecord, key: string): string {
const value = record[key];
if (typeof value !== "string" || value.trim().length === 0) throw new AgentRunError("schema-invalid", `${key} is required`, { httpStatus: 400 });
return value.trim();
}
function requiredRecord(record: JsonRecord, key: string): JsonRecord {
return asRecord(record[key], key);
}
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 executionPolicy = validateExecutionPolicy(requiredRecord(record, "executionPolicy"));
return {
tenantId,
projectId: requiredString(record, "projectId"),
workspaceRef: requiredRecord(record, "workspaceRef") as CreateRunInput["workspaceRef"],
providerId: requiredString(record, "providerId"),
backendProfile,
executionPolicy,
traceSink: record.traceSink ?? null,
};
}
export function validateExecutionPolicy(record: JsonRecord): ExecutionPolicy {
const timeout = record.timeoutMs;
if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) throw new AgentRunError("schema-invalid", "executionPolicy.timeoutMs must be a positive number", { httpStatus: 400 });
const secretScope = asRecord(record.secretScope ?? {}, "executionPolicy.secretScope");
if (secretScope.allowCredentialEcho !== undefined && secretScope.allowCredentialEcho !== false) throw new AgentRunError("tenant-policy-denied", "allowCredentialEcho must be false", { httpStatus: 403 });
const providerCredentials = Array.isArray(secretScope.providerCredentials) ? secretScope.providerCredentials : [];
for (const credential of providerCredentials) {
const item = asRecord(credential, "providerCredential");
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 secretScopeResult: ExecutionPolicy["secretScope"] = { allowCredentialEcho: false };
if (providerCredentials.length > 0) secretScopeResult.providerCredentials = providerCredentials as NonNullable<ExecutionPolicy["secretScope"]["providerCredentials"]>;
return {
sandbox: requiredString(record, "sandbox"),
approval: requiredString(record, "approval"),
timeoutMs: timeout,
network: requiredString(record, "network"),
secretScope: secretScopeResult,
};
}
export function validateCreateCommand(input: unknown): CreateCommandInput {
const record = asRecord(input, "command");
const type = requiredString(record, "type");
if (type !== "turn" && type !== "interrupt") throw new AgentRunError("schema-invalid", `command type ${type} is not supported`, { httpStatus: 400 });
const payload = asRecord(record.payload ?? {}, "payload");
const idempotencyKey = typeof record.idempotencyKey === "string" && record.idempotencyKey.trim().length > 0 ? record.idempotencyKey.trim() : undefined;
return { type, payload, ...(idempotencyKey ? { idempotencyKey } : {}) };
}