feat: 支持 runner tool credential 装配

This commit is contained in:
Codex
2026-06-02 00:22:38 +08:00
parent ac14a06436
commit 159b99e763
10 changed files with 163 additions and 14 deletions
+10
View File
@@ -68,6 +68,16 @@ export interface ExecutionPolicy extends JsonRecord {
profile: BackendProfile | string;
secretRef: SecretRef;
}>;
toolCredentials?: Array<{
tool: string;
purpose?: string;
secretRef: SecretRef;
projection: {
kind: "env";
envName: string;
secretKey?: string;
};
}>;
allowCredentialEcho?: false;
};
}
+33
View File
@@ -122,8 +122,10 @@ export function validateExecutionPolicy(record: JsonRecord): ExecutionPolicy {
if (!keys.includes(requiredKey)) throw new AgentRunError("schema-invalid", `provider credential ${profile} secretRef.keys must include ${requiredKey}`, { httpStatus: 400 });
}
}
const toolCredentials = validateToolCredentials(secretScope.toolCredentials);
const secretScopeResult: ExecutionPolicy["secretScope"] = { allowCredentialEcho: false };
if (providerCredentials.length > 0) secretScopeResult.providerCredentials = providerCredentials as NonNullable<ExecutionPolicy["secretScope"]["providerCredentials"]>;
if (toolCredentials.length > 0) secretScopeResult.toolCredentials = toolCredentials;
return {
sandbox: requiredString(record, "sandbox"),
approval: requiredString(record, "approval"),
@@ -133,6 +135,37 @@ export function validateExecutionPolicy(record: JsonRecord): ExecutionPolicy {
};
}
function validateToolCredentials(value: unknown): NonNullable<ExecutionPolicy["secretScope"]["toolCredentials"]> {
if (value === undefined) return [];
if (!Array.isArray(value)) throw new AgentRunError("schema-invalid", "toolCredentials must be an array", { httpStatus: 400 });
if (value.length > 8) throw new AgentRunError("schema-invalid", "toolCredentials must contain at most 8 entries", { httpStatus: 400 });
const seen = new Set<string>();
return value.map((credential, index) => {
const item = asRecord(credential, `toolCredentials[${index}]`);
const tool = requiredString(item, "tool");
if (tool !== "github") throw new AgentRunError("schema-invalid", `tool credential ${tool} is not supported in v0.1`, { httpStatus: 400, details: { allowedTools: ["github"] } });
const purpose = optionalString(item.purpose);
const secretRef = validateSecretRef(asRecord(item.secretRef, `toolCredentials[${index}].secretRef`));
const keys = secretRef.keys ?? [];
if (keys.length === 0) throw new AgentRunError("schema-invalid", `tool credential ${tool} secretRef.keys must not be empty`, { httpStatus: 400 });
const projection = asRecord(item.projection, `toolCredentials[${index}].projection`);
const kind = requiredString(projection, "kind");
if (kind !== "env") throw new AgentRunError("schema-invalid", "toolCredentials[].projection.kind must be env in v0.1", { httpStatus: 400 });
const envName = requiredString(projection, "envName");
validateEnvName(envName, `toolCredentials[${index}].projection.envName`);
const secretKey = optionalString(projection.secretKey) ?? keys[0];
if (!secretKey || !keys.includes(secretKey)) throw new AgentRunError("schema-invalid", `tool credential ${tool} projection.secretKey must be included in secretRef.keys`, { httpStatus: 400 });
const identity = `${tool}:${purpose ?? ""}:${envName}`;
if (seen.has(identity)) throw new AgentRunError("schema-invalid", `tool credential projection ${identity} is duplicated`, { httpStatus: 400 });
seen.add(identity);
return { tool, ...(purpose ? { purpose } : {}), secretRef, projection: { kind: "env", envName, secretKey } };
});
}
export function validateEnvName(name: string, fieldName: string): void {
if (!/^[A-Z_][A-Z0-9_]{0,63}$/u.test(name)) throw new AgentRunError("schema-invalid", `${fieldName} must be an uppercase env name`, { httpStatus: 400 });
}
function validateSecretRef(record: JsonRecord): SecretRef {
const name = requiredString(record, "name");
const result: SecretRef = { name };