fix: 让 gitbundle 自动使用 G14 git mirror
This commit is contained in:
@@ -250,7 +250,6 @@ function summarizeResourceBundle(resourceBundleRef: ResourceBundleRef | null): J
|
||||
repoUrl: resourceBundleRef.repoUrl,
|
||||
ref: resourceBundleRef.ref ?? null,
|
||||
commitId: resourceBundleRef.commitId ?? null,
|
||||
gitMirror: resourceBundleRef.gitMirror ? { enabled: resourceBundleRef.gitMirror.enabled ?? true, baseUrl: resourceBundleRef.gitMirror.baseUrl ?? null, valuesPrinted: false } : { enabled: false, baseUrl: null, valuesPrinted: false },
|
||||
bundles: { count: resourceBundleRef.bundles.length, items: resourceBundleRef.bundles.map((item) => ({ name: item.name ?? null, repoUrl: item.repoUrl ?? resourceBundleRef.repoUrl, ref: item.ref ?? resourceBundleRef.ref ?? null, commitId: item.commitId ?? resourceBundleRef.commitId ?? null, subpath: item.subpath, targetPath: item.targetPath, valuesPrinted: false })), valuesPrinted: false },
|
||||
requiredSkills: { count: resourceBundleRef.requiredSkills?.length ?? 0, names: resourceBundleRef.requiredSkills?.map((item) => item.name) ?? [], valuesPrinted: false },
|
||||
promptRefs: { count: resourceBundleRef.promptRefs?.length ?? 0, names: resourceBundleRef.promptRefs?.map((item) => item.name) ?? [], valuesPrinted: false },
|
||||
|
||||
@@ -84,10 +84,6 @@ export interface ResourceBundleRef extends JsonRecord {
|
||||
repoUrl: string;
|
||||
commitId?: string;
|
||||
ref?: string;
|
||||
gitMirror?: {
|
||||
enabled?: boolean;
|
||||
baseUrl?: string;
|
||||
};
|
||||
bundles: GitBundleItemRef[];
|
||||
promptRefs?: Array<{
|
||||
name: string;
|
||||
|
||||
@@ -89,8 +89,7 @@ export function validateResourceBundleRef(value: unknown): ResourceBundleRef | n
|
||||
if (commitId) validateCommitId(commitId, "resourceBundleRef.commitId");
|
||||
const ref = validateGitRef(record.ref, "resourceBundleRef.ref");
|
||||
rejectLegacyResourceBundleFields(record);
|
||||
const gitMirror = validateGitMirror(record.gitMirror);
|
||||
const result: ResourceBundleRef = { kind: "gitbundle", repoUrl, ...(commitId ? { commitId } : {}), ...(ref ? { ref } : {}), ...(gitMirror ? { gitMirror } : {}), bundles: validateResourceGitBundles(record.bundles, repoUrl, commitId, ref) };
|
||||
const result: ResourceBundleRef = { kind: "gitbundle", repoUrl, ...(commitId ? { commitId } : {}), ...(ref ? { ref } : {}), bundles: validateResourceGitBundles(record.bundles, repoUrl, commitId, ref) };
|
||||
if (record.promptRefs !== undefined) result.promptRefs = validateResourcePromptRefs(record.promptRefs);
|
||||
if (record.requiredSkills !== undefined) result.requiredSkills = validateResourceRequiredSkills(record.requiredSkills);
|
||||
if (record.submodules !== undefined && record.submodules !== false) throw new AgentRunError("schema-invalid", "resourceBundleRef.submodules can only be false in v0.1", { httpStatus: 400 });
|
||||
@@ -101,29 +100,8 @@ export function validateResourceBundleRef(value: unknown): ResourceBundleRef | n
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateGitMirror(value: unknown): NonNullable<ResourceBundleRef["gitMirror"]> | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
const record = asRecord(value, "resourceBundleRef.gitMirror");
|
||||
const enabled = record.enabled === undefined ? true : record.enabled;
|
||||
if (typeof enabled !== "boolean") throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.enabled must be boolean", { httpStatus: 400 });
|
||||
const baseUrl = optionalString(record.baseUrl);
|
||||
if (baseUrl) validateGitMirrorBaseUrl(baseUrl);
|
||||
return { enabled, ...(baseUrl ? { baseUrl } : {}) };
|
||||
}
|
||||
|
||||
function validateGitMirrorBaseUrl(value: string): void {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(value);
|
||||
} catch {
|
||||
throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.baseUrl must be an http(s) URL", { httpStatus: 400 });
|
||||
}
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.baseUrl must use http or https", { httpStatus: 400 });
|
||||
if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.baseUrl must not include credentials, query, or fragment", { httpStatus: 400 });
|
||||
}
|
||||
|
||||
function rejectLegacyResourceBundleFields(record: JsonRecord): void {
|
||||
for (const field of ["toolAliases", "skillRefs", "workspaceFiles", "subdir", "sparsePaths"] as const) {
|
||||
for (const field of ["toolAliases", "skillRefs", "workspaceFiles", "subdir", "sparsePaths", "gitMirror"] as const) {
|
||||
if (record[field] !== undefined) throw new AgentRunError("schema-invalid", `resourceBundleRef.${field} is removed; use resourceBundleRef.bundles[] with kind=gitbundle`, { httpStatus: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ interface GitCheckout {
|
||||
}
|
||||
|
||||
interface GitMirrorConfig {
|
||||
enabled: true;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
@@ -146,13 +145,13 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
|
||||
}
|
||||
|
||||
function gitMirrorConfig(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
|
||||
return normalizeGitMirrorConfig(resourceBundleRef.gitMirror, env);
|
||||
void resourceBundleRef;
|
||||
return defaultGitMirrorConfig(env);
|
||||
}
|
||||
|
||||
function normalizeGitMirrorConfig(gitMirror: ResourceBundleRef["gitMirror"] | GitMirrorConfig | undefined, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
|
||||
if (!gitMirror || gitMirror.enabled === false) return undefined;
|
||||
const baseUrl = optionalNonEmpty(gitMirror.baseUrl) ?? optionalNonEmpty(env.AGENTRUN_GIT_MIRROR_BASE_URL) ?? "http://git-mirror-http.devops-infra.svc.cluster.local";
|
||||
return { enabled: true, baseUrl: baseUrl.replace(/\/+$/u, "") };
|
||||
function defaultGitMirrorConfig(env: NodeJS.ProcessEnv): GitMirrorConfig {
|
||||
const baseUrl = optionalNonEmpty(env.AGENTRUN_GIT_MIRROR_BASE_URL) ?? "http://git-mirror-http.devops-infra.svc.cluster.local";
|
||||
return { baseUrl: normalizeMirrorBaseUrl(baseUrl) };
|
||||
}
|
||||
|
||||
function defaultGitBundleSource(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv, gitMirror?: GitMirrorConfig): GitBundleSource {
|
||||
@@ -199,17 +198,20 @@ async function checkoutGitSource(checkoutRoot: string, source: GitBundleSource):
|
||||
}
|
||||
|
||||
function gitSourceIdentity(source: GitBundleSource): JsonRecord {
|
||||
return { repoUrl: source.repoUrl, commitId: source.commitId ?? null, ref: source.ref ?? null, gitMirror: source.gitMirror ? { enabled: true, baseUrl: source.gitMirror.baseUrl } : null };
|
||||
return { repoUrl: source.repoUrl, commitId: source.commitId ?? null, ref: source.ref ?? null, gitMirror: source.gitMirror ? { baseUrl: source.gitMirror.baseUrl } : null };
|
||||
}
|
||||
|
||||
export function resolveGitBundleFetchSource(repoUrl: string, gitMirror?: ResourceBundleRef["gitMirror"] | GitMirrorConfig, env: NodeJS.ProcessEnv = process.env): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
|
||||
const mirror = normalizeGitMirrorConfig(gitMirror, env);
|
||||
if (!mirror) return { fetchRepoUrl: repoUrl, mirrorUsed: false };
|
||||
export function resolveGitBundleFetchSource(repoUrl: string, gitMirror?: GitMirrorConfig, env: NodeJS.ProcessEnv = process.env): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
|
||||
const mirror = gitMirror ? { baseUrl: normalizeMirrorBaseUrl(gitMirror.baseUrl) } : defaultGitMirrorConfig(env);
|
||||
const githubPath = githubRepoPath(repoUrl);
|
||||
if (!githubPath) return { fetchRepoUrl: repoUrl, mirrorUsed: false };
|
||||
return { fetchRepoUrl: `${mirror.baseUrl}/${githubPath}.git`, mirrorUsed: true, mirrorBaseUrl: mirror.baseUrl };
|
||||
}
|
||||
|
||||
function normalizeMirrorBaseUrl(baseUrl: string): string {
|
||||
return baseUrl.replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
function gitFetchSource(source: GitBundleSource): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
|
||||
return resolveGitBundleFetchSource(source.repoUrl, source.gitMirror);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ManagerClient } from "../../mgr/client.js";
|
||||
import { startManagerServer } from "../../mgr/server.js";
|
||||
import { MemoryAgentRunStore } from "../../mgr/store.js";
|
||||
import type { JsonRecord } from "../../common/types.js";
|
||||
import { validateResourceBundleRef } from "../../common/validation.js";
|
||||
import { resolveGitBundleFetchSource } from "../../runner/resource-bundle.js";
|
||||
import { assertNoSecretLeak, loadArtificerImageRef, type SelfTestCase } from "../harness.js";
|
||||
|
||||
@@ -30,7 +31,7 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.equal(shownImageRef.repoUrl, "git@github.com:pikasTech/agentrun.git");
|
||||
assert.equal(shownImageRef.commitId, artificerImageRef.commitId);
|
||||
assert.equal(shownImageRef.dockerfilePath, "deploy/container/Containerfile");
|
||||
assert.equal(((shownItem.resourceBundleRef as JsonRecord).gitMirror as JsonRecord).enabled, false);
|
||||
assert.equal(Object.hasOwn(shownItem.resourceBundleRef as JsonRecord, "gitMirror"), false);
|
||||
|
||||
const rendered = await client.post("/api/v1/aipod-specs/Artificer/render", { prompt: "处理 pikasTech/unidesk#245", idempotencyKey: "selftest-aipod-artificer" }) as JsonRecord;
|
||||
assert.equal(rendered.action, "aipod-spec-render");
|
||||
@@ -58,7 +59,7 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.equal(toolCredentials.some((item) => item.tool === "unidesk-ssh" && ((item.projection as JsonRecord).envName) === "UNIDESK_SSH_CLIENT_TOKEN"), true);
|
||||
assert.equal(toolCredentials.some((item) => item.tool === "github" && ((item.projection as JsonRecord).kind) === "volume" && ((item.projection as JsonRecord).mountPath) === "/home/agentrun/.ssh"), true);
|
||||
const bundle = task.resourceBundleRef as JsonRecord;
|
||||
assert.equal(((bundle.gitMirror as JsonRecord).enabled), false);
|
||||
assert.equal(Object.hasOwn(bundle, "gitMirror"), false);
|
||||
const bundles = bundle.bundles as JsonRecord[];
|
||||
const toolBundle = bundles.find((item) => item.name === "agentrun-runner-tools") as JsonRecord | undefined;
|
||||
assert.equal(toolBundle?.repoUrl, "git@github.com:pikasTech/agentrun.git");
|
||||
@@ -69,15 +70,16 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.equal((bundle.requiredSkills as JsonRecord[]).some((item) => item.name === "unidesk-gh"), true);
|
||||
assertNoSecretLeak(rendered);
|
||||
|
||||
const mirrored = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { enabled: true, baseUrl: "http://mirror.example.test/root/" }, {});
|
||||
const mirrored = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { baseUrl: "http://mirror.example.test/root/" }, {});
|
||||
assert.equal(mirrored.fetchRepoUrl, "http://mirror.example.test/root/pikasTech/unidesk.git");
|
||||
assert.equal(mirrored.mirrorUsed, true);
|
||||
const disabledMirror = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { enabled: false, baseUrl: "http://mirror.example.test/root/" }, {});
|
||||
assert.equal(disabledMirror.fetchRepoUrl, "git@github.com:pikasTech/unidesk.git");
|
||||
assert.equal(disabledMirror.mirrorUsed, false);
|
||||
const nonGithub = resolveGitBundleFetchSource("ssh://git@example.test/repo.git", { enabled: true, baseUrl: "http://mirror.example.test" }, {});
|
||||
const defaultMirror = resolveGitBundleFetchSource("https://github.com/pikasTech/unidesk.git", undefined, { AGENTRUN_GIT_MIRROR_BASE_URL: "http://mirror.example.test/base" });
|
||||
assert.equal(defaultMirror.fetchRepoUrl, "http://mirror.example.test/base/pikasTech/unidesk.git");
|
||||
assert.equal(defaultMirror.mirrorUsed, true);
|
||||
const nonGithub = resolveGitBundleFetchSource("ssh://git@example.test/repo.git", { baseUrl: "http://mirror.example.test" }, {});
|
||||
assert.equal(nonGithub.fetchRepoUrl, "ssh://git@example.test/repo.git");
|
||||
assert.equal(nonGithub.mirrorUsed, false);
|
||||
assert.throws(() => validateResourceBundleRef({ kind: "gitbundle", repoUrl: "git@github.com:pikasTech/unidesk.git", ref: "master", gitMirror: { enabled: false }, bundles: [{ subpath: ".", targetPath: "." }] }), /resourceBundleRef.gitMirror is removed/u);
|
||||
|
||||
const submitPlan = await runCliJson(context, server.baseUrl, ["queue", "submit", "--aipod", "Artificer", "--prompt", "处理 pikasTech/unidesk#245", "--idempotency-key", "selftest-aipod-cli", "--dry-run"]);
|
||||
assert.equal(submitPlan.ok, true);
|
||||
@@ -91,7 +93,7 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.equal(commands.some((item) => item.includes("aipod-specs render <name>")), true);
|
||||
assert.equal(commands.some((item) => item.includes("queue submit --aipod <name>")), true);
|
||||
assertNoSecretLeak(submitPlan);
|
||||
return { name: "aipod-spec", tests: ["aipod-spec-yaml-parser-runtime-compatible", "aipod-spec-artificer-image-ref-render", "aipod-spec-artificer-direct-ssh-render", "aipod-spec-git-mirror-url", "queue-submit-aipod-dry-run", "aipod-cli-help"] };
|
||||
return { name: "aipod-spec", tests: ["aipod-spec-yaml-parser-runtime-compatible", "aipod-spec-artificer-image-ref-render", "aipod-spec-artificer-github-url-render", "aipod-spec-git-mirror-url", "queue-submit-aipod-dry-run", "aipod-cli-help"] };
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user