156 lines
6.7 KiB
TypeScript
156 lines
6.7 KiB
TypeScript
// SPEC: PJ2026-010205 HWLAB接入 draft-2026-06-17-r0; PJ2026-0104010803 Workbench唯一投影 draft-2026-06-19-p1-agentrun-incremental-cursor.
|
|
// Responsibility: AgentRun backend profile, internal repo source authority, provider identity, and trace metadata.
|
|
|
|
import { truthyFlag } from "./server-http-utils.ts";
|
|
|
|
export const DEFAULT_AGENTRUN_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
|
export const GITEA_AGENTRUN_REPO_URL = "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git";
|
|
|
|
const ADAPTER_ID = "agentrun-v01";
|
|
const AGENTRUN_BACKEND_PREFIX = ADAPTER_ID;
|
|
const AGENTRUN_RUNNER_KIND = "agentrun-v01-shared-runner";
|
|
const AGENTRUN_SESSION_MODE = "agentrun-v01-durable-session";
|
|
const AGENTRUN_IMPLEMENTATION_TYPE = "agentrun-v01-shared-execution-infra";
|
|
const AGENTRUN_PROVIDER_TRACE_PROTOCOL = "agentrun-v01-jsonrpc";
|
|
const AGENTRUN_BACKEND_ALIASES = Object.freeze({ "codex-api": "codex", codex: "codex", "gpt.pika": "gpt-pika" });
|
|
const AGENTRUN_BACKEND_PROFILE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/u;
|
|
|
|
export function resolveAgentRunBackendProfile(env = process.env, params = {}) {
|
|
const requested = String(params.providerProfile ?? params.codeAgentProviderProfile ?? env.HWLAB_CODE_AGENT_AGENTRUN_BACKEND_PROFILE ?? env.HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE ?? "deepseek").trim().toLowerCase();
|
|
const fallback = String(env.HWLAB_CODE_AGENT_AGENTRUN_DEFAULT_BACKEND_PROFILE ?? "deepseek").trim().toLowerCase() || "deepseek";
|
|
const resolved = requested === "runtime-default" ? fallback : requested;
|
|
if (!resolved || resolved === "runtime-default") return "deepseek";
|
|
const mapped = AGENTRUN_BACKEND_ALIASES[resolved] ?? resolved;
|
|
return AGENTRUN_BACKEND_PROFILE_ID_PATTERN.test(mapped) ? mapped : "deepseek";
|
|
}
|
|
|
|
export function agentRunTraceMeta(env = process.env) {
|
|
return {
|
|
runnerKind: AGENTRUN_RUNNER_KIND,
|
|
workspace: resolveAgentRunRepoUrl(env),
|
|
sandbox: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SANDBOX, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, "danger-full-access"),
|
|
protocol: AGENTRUN_PROVIDER_TRACE_PROTOCOL,
|
|
sessionMode: AGENTRUN_SESSION_MODE,
|
|
implementationType: AGENTRUN_IMPLEMENTATION_TYPE
|
|
};
|
|
}
|
|
|
|
export function backendForBackendProfile(profile) {
|
|
return `${AGENTRUN_BACKEND_PREFIX}/${resolveAgentRunBackendProfile({}, { providerProfile: profile })}`;
|
|
}
|
|
|
|
export function resolveAgentRunRepoUrl(env = process.env) {
|
|
const raw = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_REPO_URL, env.HWLAB_BOOT_READ_URL, DEFAULT_AGENTRUN_REPO_URL);
|
|
const url = new URL(raw);
|
|
const host = url.hostname.toLowerCase();
|
|
const allowedByTest = truthyFlag(env.HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL) && ["127.0.0.1", "localhost"].includes(host);
|
|
url.pathname = url.pathname.replace(/\/+$/u, "");
|
|
url.search = "";
|
|
url.hash = "";
|
|
const normalized = url.toString().replace(/\/+$/u, "");
|
|
if (url.protocol !== "http:" || (!isAllowedAgentRunRepoUrl(normalized) && !allowedByTest)) {
|
|
throw Object.assign(new Error(`HWLAB_CODE_AGENT_AGENTRUN_REPO_URL must use an approved internal k3s git mirror (${DEFAULT_AGENTRUN_REPO_URL} or ${GITEA_AGENTRUN_REPO_URL}); got ${redactUrl(raw)}`), {
|
|
code: "agentrun_internal_repo_url_required",
|
|
statusCode: 500
|
|
});
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function isAllowedAgentRunRepoUrl(repoUrl) {
|
|
return repoUrl === DEFAULT_AGENTRUN_REPO_URL || repoUrl === GITEA_AGENTRUN_REPO_URL;
|
|
}
|
|
|
|
export function requireAgentRunSourceCommit(env) {
|
|
const text = String(env.HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT ?? "").trim().toLowerCase();
|
|
if (/^[0-9a-f]{40}$/u.test(text)) return text;
|
|
throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT must be a full 40-character source commit before AgentRun dispatch"), {
|
|
code: "agentrun_bundle_source_commit_invalid",
|
|
statusCode: 500
|
|
});
|
|
}
|
|
|
|
export function resolveAgentRunProviderId(env = process.env) {
|
|
const providerId = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID);
|
|
if (!providerId) {
|
|
throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID must be configured from the runtime lane node id before AgentRun dispatch"), {
|
|
code: "agentrun_provider_id_required",
|
|
statusCode: 500
|
|
});
|
|
}
|
|
if (!/^[A-Za-z0-9._-]+$/u.test(providerId)) {
|
|
throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID must be a simple node id"), {
|
|
code: "agentrun_provider_id_invalid",
|
|
statusCode: 500
|
|
});
|
|
}
|
|
return providerId;
|
|
}
|
|
|
|
export function agentRunProviderIdOrNull(env = process.env) {
|
|
try {
|
|
return resolveAgentRunProviderId(env);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function modelForBackendProfile(profile, env = process.env) {
|
|
const resolved = resolveAgentRunBackendProfile({}, { providerProfile: profile });
|
|
if (resolved === "codex") return firstNonEmpty(env.HWLAB_CODE_AGENT_CODEX_API_MODEL, env.HWLAB_CODE_AGENT_MODEL, "gpt-5.5");
|
|
if (resolved === "minimax-m3") return firstNonEmpty(env.HWLAB_CODE_AGENT_MINIMAX_M3_MODEL, "MiniMax-M3");
|
|
if (resolved === "deepseek") return firstNonEmpty(env.HWLAB_CODE_AGENT_DEEPSEEK_MODEL, "deepseek-chat");
|
|
if (resolved === "dsflash-go") return firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_DSFLASH_GO_MODEL, env.HWLAB_CODE_AGENT_DSFLASH_GO_MODEL, "deepseek-v4-flash");
|
|
const profileEnvKey = dynamicBackendProfileEnvKey(resolved);
|
|
return firstNonEmpty(env[`HWLAB_CODE_AGENT_AGENTRUN_${profileEnvKey}_MODEL`], env[`HWLAB_CODE_AGENT_${profileEnvKey}_MODEL`], resolved);
|
|
}
|
|
|
|
export function providerForBackendProfile(profile) {
|
|
const resolved = resolveAgentRunBackendProfile({}, { providerProfile: profile });
|
|
if (resolved === "codex") return "codex-api";
|
|
if (resolved === "gpt-pika") return "gpt.pika";
|
|
return resolved;
|
|
}
|
|
|
|
export function agentRunAvailabilityBlocker(error, scope) {
|
|
return {
|
|
code: error?.code ?? `agentrun_${String(scope).replace(/[^a-z0-9]+/giu, "_")}_blocked`,
|
|
type: "agent_blocker",
|
|
scope: `agentrun-${scope}`,
|
|
status: "open",
|
|
sourceIssue: "pikasTech/HWLAB#879",
|
|
summary: error?.message ?? `AgentRun ${scope} is not configured correctly.` ,
|
|
secretMaterialRead: false,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function dynamicBackendProfileEnvKey(profile) {
|
|
return String(profile ?? "")
|
|
.trim()
|
|
.toUpperCase()
|
|
.replace(/[^A-Z0-9]+/gu, "_")
|
|
.replace(/^_+|_+$/gu, "") || "PROFILE";
|
|
}
|
|
|
|
function firstNonEmpty(...values) {
|
|
for (const value of values) {
|
|
const text = String(value ?? "").trim();
|
|
if (text) return text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function redactUrl(value) {
|
|
try {
|
|
const url = new URL(value);
|
|
if (url.username || url.password) {
|
|
url.username = "***";
|
|
url.password = "";
|
|
}
|
|
return url.toString();
|
|
} catch {
|
|
return "<invalid-url>";
|
|
}
|
|
}
|