fix: 收窄 Artificer pika 配置范围

This commit is contained in:
Codex
2026-07-12 17:57:44 +02:00
parent 18017306b9
commit acb9674454
14 changed files with 50 additions and 874 deletions
+1 -30
View File
@@ -427,45 +427,16 @@ export function readYamlInputFromArgs(args: string[]): string {
export async function aipodRenderInputFromArgs(args: string[], trailingPromptStart: number, overrides: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
const input = await optionalJsonBody(args);
assertAipodRenderProviderAuthority(args, input);
const prompt = optionalPromptFromArgs(args, args.length);
const prompt = optionalPromptFromArgs(args, trailingPromptStart);
if (prompt !== null) input.prompt = prompt;
copyAgentRunOptions(args, input, ["tenant-id", "project-id", "queue", "node", "lane", "title", "provider-id", "idempotency-key", "session-id", "model", "reasoning-effort"]);
const priority = agentRunOption(args, "priority");
if (priority) input.priority = Number(priority);
const workspaceRef = jsonObjectOption(args, "workspace-json");
if (workspaceRef !== null) input.workspaceRef = workspaceRef;
void trailingPromptStart;
return { ...input, ...overrides };
}
function assertAipodRenderProviderAuthority(args: string[], input: Record<string, unknown>): void {
const forbiddenFlags = ["profile", "backend-profile", "execution-policy-json"]
.filter((flag) => agentRunHasFlag(args, flag))
.map((flag) => `--${flag}`);
if (forbiddenFlags.length > 0) {
throw new AgentRunRestError(
"validation-failed",
`AipodSpec owns backendProfile, provider credential SecretRef, and executionPolicy; remove ${forbiddenFlags.join(", ")} and use only --model/--reasoning-effort for task-level model selection`,
);
}
const forbiddenTopLevel = ["profile", "backendProfile", "modelConfig", "executionPolicy", "providerCredentials", "providerCredential", "secretRef"]
.filter((key) => Object.hasOwn(input, key));
const payload = record(input.payload);
const forbiddenPayload = ["model", "reasoningEffort", "modelConfig", "backendProfile", "executionPolicy", "providerCredentials", "providerCredential", "secretRef"]
.filter((key) => Object.hasOwn(payload, key));
if (forbiddenTopLevel.length > 0 || forbiddenPayload.length > 0) {
const fields = [
...forbiddenTopLevel,
...forbiddenPayload.map((key) => `payload.${key}`),
];
throw new AgentRunRestError(
"validation-failed",
`Aipod render input cannot override provider authority or place model overrides in payload: ${fields.join(", ")}; use top-level model/reasoningEffort`,
);
}
}
export function readPromptFromArgs(args: string[], trailingStart: number): string {
const prompt = optionalPromptFromArgs(args, trailingStart);
if (prompt === null) throw new AgentRunRestError("validation-failed", "prompt is required; use --prompt-stdin, --prompt-file, --prompt, or a trailing prompt");
+5 -20
View File
@@ -68,9 +68,9 @@ export function agentRunHelp(): unknown {
"bun scripts/cli.ts agentrun retry task/<taskId> --idempotency-key <key> --reason <text> --dry-run",
"bun scripts/cli.ts agentrun get attempts --task <taskId> --limit 20",
"bun scripts/cli.ts agentrun dispatch task/<taskId>",
"bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin --model <name> --reasoning-effort <level> --idempotency-key <key>",
"bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin --idempotency-key <key>",
"bun scripts/cli.ts agentrun apply -f - --dry-run",
"bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin --model <name> --reasoning-effort <level>",
"bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin",
"bun scripts/cli.ts agentrun explain task",
"bun scripts/cli.ts agentrun explain session-policy",
"bun scripts/cli.ts agentrun control-plane plan --node NC01 --lane nc01-v02",
@@ -316,21 +316,13 @@ export function agentRunHelpText(args: string[]): string {
return "Usage: bun scripts/cli.ts agentrun dispatch task/<taskId>";
}
if (verb === "create") {
return [
"Usage: bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin [--model <name>] [--reasoning-effort <level>] [--idempotency-key <key>] [--dry-run]",
"",
"AipodSpec owns provider credentials and execution policy. Task-level model/reasoning overrides keep the YAML sourceProfile and canonical effectiveBackendProfile fixed.",
].join("\n");
return "Usage: bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin [--idempotency-key <key>] [--dry-run]";
}
if (verb === "apply") {
return "Usage: bun scripts/cli.ts agentrun apply -f task.yaml|json|- [--dry-run]\nTask manifests use kind: Task and spec: <AgentRun task payload>.";
}
if (verb === "send") {
return [
"Usage: bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin [--model <name>] [--reasoning-effort <level>] [--dry-run]",
"",
"AipodSpec owns provider credentials and execution policy. The server decides whether this becomes an internal steer or a new turn from session state.",
].join("\n");
return "Usage: bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin\nThe server decides whether this becomes an internal steer or a new turn from session state.";
}
if (verb === "explain") return agentRunExplain(kind ?? "task");
if (verb === "control-plane") {
@@ -390,13 +382,6 @@ export function agentRunHelpText(args: string[]): string {
].join("\n");
}
if (verb !== undefined && isAgentRunRestCompatGroup(verb)) {
if ((verb === "aipod-specs" || verb === "aipods") && kind === "render") {
return [
`Usage: bun scripts/cli.ts agentrun ${verb} render <aipod> [--model <name>] [--reasoning-effort <level>] [-o json|yaml] [--full|--raw]`,
"",
"Human output shows sourceProfile, effectiveBackendProfile, model/reasoning resolution, and SecretRef-only provider binding with valuesPrinted=false.",
].join("\n");
}
return [
`Compatibility group: agentrun ${verb} ...`,
"",
@@ -426,7 +411,7 @@ export function agentRunHelpText(args: string[]): string {
" bun scripts/cli.ts agentrun logs session/<sessionId> --tail 100",
" bun scripts/cli.ts agentrun retry task/<taskId> --idempotency-key <key> --reason <text> --dry-run",
" bun scripts/cli.ts agentrun get attempts --task <taskId> --limit 20",
" bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin --model <name> --reasoning-effort <level>",
" bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin",
"",
"Machine/debug output:",
" -o json|yaml emits a stable UniDesk resource object without the global JSON envelope.",
+6 -11
View File
@@ -11,8 +11,6 @@ import {
import {
agentRunLaneSummary,
agentRunProviderCredentialRefs,
agentRunProviderCredentialRefsForSelection,
agentRunProviderCredentialSecrets,
resolveAgentRunLaneTarget,
type AgentRunLaneSecretSpec,
type AgentRunLaneSpec,
@@ -108,9 +106,6 @@ export function providerProfileHelp(): string {
" bun scripts/cli.ts job status <jobId> --tail-bytes 12000",
" bun scripts/cli.ts agentrun provider-profile validate --node NC01 --lane nc01-v02 --profile gpt.pika --wait",
"",
"Profile identity:",
" --profile accepts the YAML source selector or canonical AgentRun backend slug. Output always discloses sourceProfile and effectiveBackendProfile separately.",
"",
"Boundary:",
" confirmed apply returns an async job only after the provider Secret gate reports an exact data no-op; the worker skips the provider Secret write, publishes runtime GitOps using the current manager image, refreshes Argo and restarts the manager without rebuilding images or creating a PipelineRun.",
].join("\n");
@@ -133,7 +128,7 @@ async function providerProfileApplyAsync(config: UniDeskConfig, options: Provide
const plan = providerProfilePlan({ ...options, confirm: false, dryRun: true });
if (plan.ok !== true) return plan;
const { spec, profile } = resolveProviderProfile(options);
const secrets = [...agentRunProviderCredentialSecrets(spec, profile)];
const secrets = spec.secrets.filter((secret) => secret.providerCredentialProfile === profile);
const activationTargets = providerProfileActivationTargets(inspectProviderProfileSources(spec, secrets));
const activationPreflight = await providerSecretActivationPreflight(config, spec, activationTargets, { full: options.full || options.raw });
if (activationPreflight.ok !== true) {
@@ -206,9 +201,9 @@ async function providerProfileRuntimeStatus(config: UniDeskConfig, options: Prov
function resolveProviderProfile(options: ProviderProfileOptions): { configPath: string; spec: AgentRunLaneSpec; profile: string; secrets: AgentRunLaneSecretSpec[] } {
if (options.profile === null) throw new Error("provider-profile requires --profile");
const target = resolveAgentRunLaneTarget(options);
const secrets = [...agentRunProviderCredentialSecrets(target.spec, options.profile)];
const secrets = target.spec.secrets.filter((secret) => secret.providerCredentialProfile === options.profile);
if (secrets.length === 0) {
const declared = agentRunProviderCredentialRefs(target.spec).map((item) => `${item.sourceProfile}->${item.profile}`);
const declared = agentRunProviderCredentialRefs(target.spec).map((item) => item.profile);
throw new Error(`provider profile ${options.profile} is not declared in ${target.configPath}; declared profiles: ${declared.join(", ") || "(none)"}`);
}
return { ...target, profile: options.profile, secrets };
@@ -764,11 +759,11 @@ async function publishProfileGitops(config: UniDeskConfig, spec: AgentRunLaneSpe
}
function providerProfileSummary(spec: AgentRunLaneSpec, profile: string, secrets: readonly AgentRunLaneSecretSpec[]): Record<string, unknown> {
const credential = agentRunProviderCredentialRefsForSelection(spec, profile)[0] ?? null;
const credential = agentRunProviderCredentialRefs(spec, profile)[0] ?? null;
return {
sourceProfile: credential?.sourceProfile ?? profile,
effectiveBackendProfile: credential?.profile ?? null,
profile,
declared: true,
backendProfile: profile === "gpt.pika" ? "gpt-pika" : profile,
secretIds: secrets.map((secret) => secret.id),
secretRef: credential?.secretRef ?? null,
configuredKeys: credential?.secretRef.keys ?? secrets.map((secret) => secret.targetRef.key),
+2 -3
View File
@@ -278,9 +278,8 @@ export function renderAgentRunControlPlaneActionSummary(result: Record<string, u
lines.push(
"",
"PROVIDER CREDENTIALS",
renderTable(["SOURCE PROFILE", "BACKEND PROFILE", "NAMESPACE", "SECRET", "KEYS", "VALUES"], providerCredentials.slice(0, 8).map((credential) => [
displayValue(credential.sourceProfile),
displayValue(credential.effectiveBackendProfile),
renderTable(["PROFILE", "NAMESPACE", "SECRET", "KEYS", "VALUES"], providerCredentials.slice(0, 8).map((credential) => [
displayValue(credential.profile),
displayValue(credential.namespace),
displayValue(credential.name),
Array.isArray(credential.keys) ? credential.keys.map(String).join(",") : "-",
+1 -2
View File
@@ -38,7 +38,7 @@ import { agentRunDryRunPlan, readAgentRunClientConfig } from "./config";
import { status } from "./control-plane";
import { arrayRecords, displayValue, isRecord, nextPagedResourceCommand, parseTaskManifest, pickCompact, relativeAge, renderFailureLines, renderResourceNextLines, renderTable, resourceName, shortId, stringOrDash, truncateMultiline, truncateOneLine } from "./options";
import { activeAgentRunRestTarget, agentRunRestRequest, runAgentRunRestCommand } from "./rest-bridge";
import { renderAipodBindingHumanLines, renderSessionSendResult } from "./session-send-render";
import { renderSessionSendResult } from "./session-send-render";
import { record, stringOrNull } from "./utils";
export function resolveAgentRunCancelPolicyTarget(config: UniDeskConfig | null, options: AgentRunResourceOptions): { configPath: string; spec: AgentRunLaneSpec; source: "selected-lane" | "default-lane" } | null {
@@ -495,7 +495,6 @@ export function renderMutationSummary(command: string, raw: Record<string, unkno
if (mutation !== undefined) lines.push(`Mutation: ${String(mutation)}`);
if (decision !== null) lines.push(`Decision: ${decision}`);
if (internalCommandType !== null) lines.push(`InternalCommandType: ${internalCommandType}`);
lines.push(...renderAipodBindingHumanLines(record(data.aipodBinding ?? raw.aipodBinding)));
lines.push(...renderCancelLifecycleMutationLines(record(data.cancelLifecycle ?? raw.cancelLifecycle)));
const next = record(raw.next ?? data.next);
const nextLines = (overrideNextLines ?? Object.values(next).map(String)).filter((line) => line.length > 0).slice(0, 5);
+15 -212
View File
@@ -43,7 +43,6 @@ import { arrayRecords, displayValue, isRecord, pickCompact, renderTable, truncat
import { innerData, pathValue, renderMachine, renderedCliResult } from "./render";
import { parseResourceOptions, stripAgentRunResourceWrapperArgs } from "./resource-actions";
import { AGENTRUN_GIT_MIRROR_RETRY_MAX_ATTEMPTS, agentRunGitMirrorRetryDelayMs, agentRunGitMirrorRetrySummary, agentRunGitMirrorRetryableFailure, createYamlLaneJobScript, yamlLaneGitMirrorJobManifest, yamlLaneGitMirrorStatusScript, yamlLaneJobProbeScript } from "./secrets";
import { renderAipodBindingHumanLines } from "./session-send-render";
import { capture, captureJsonPayload, compactCapture, nonNegativeIntegerOrNull, progressEvent, record, shQuote, sleep, stringOrNull } from "./utils";
export async function readGitMirrorStatus(config: UniDeskConfig, target: { configPath: string; spec: AgentRunLaneSpec }): Promise<Record<string, unknown> & { result: SshCaptureResult; raw: string; summary: Record<string, unknown> }> {
@@ -286,10 +285,10 @@ export async function runAgentRunRestCompatCommand(config: UniDeskConfig | null,
const options = parseAgentRunRestCompatOptions(args);
const command = `agentrun ${canonicalArgs.join(" ")}`.trim();
try {
return await withAgentRunRestTarget(resolveAgentRunRestTarget(config, options), async () => {
const raw = await runAgentRunRestCommand(config, group, stripAgentRunResourceWrapperArgs(args));
return renderAgentRunRestCompatResult(command, group, stripAgentRunResourceWrapperArgs(args), raw, options);
const raw = await withAgentRunRestTarget(resolveAgentRunRestTarget(config, options), async () => {
return await runAgentRunRestCommand(config, group, stripAgentRunResourceWrapperArgs(args));
});
return renderAgentRunRestCompatResult(command, group, stripAgentRunResourceWrapperArgs(args), raw, options);
} catch (error) {
if (error instanceof AgentRunRestError) {
if (options.raw || options.full || options.output === "json" || options.output === "yaml") {
@@ -336,7 +335,6 @@ export function renderAgentRunAipodSpecRenderSummary(command: string, raw: Recor
const namespace = stringOrNull(bridge.namespace) ?? stringOrNull(runnerJobDefaults.namespace) ?? "-";
const providerCredentials = agentRunRenderedProviderCredentials(secretScope, aipod);
const toolCredentials = agentRunRenderedToolCredentials(secretScope, aipod);
const aipodBinding = agentRunAipodBindingDisclosure(task, requestedAipod, record(data.modelResolution));
const bundleNames = agentRunResourceBundleNames(resourceBundle.bundles, "name");
const skillNames = agentRunResourceBundleNames(resourceBundle.requiredSkills, "name");
const targetArgs = agentRunCompatTargetCliArgs(node, lane);
@@ -345,7 +343,6 @@ export function renderAgentRunAipodSpecRenderSummary(command: string, raw: Recor
` AipodSpec: ${displayValue(aipod.name ?? requestedAipod)} hash=${displayValue(aipod.specHash)}`,
` Target: node=${displayValue(node)} lane=${displayValue(lane)} namespace=${displayValue(namespace)} bridge=${displayValue(bridge.mode)}`,
` TaskPolicy: backendProfile=${displayValue(task.backendProfile)} providerId=${displayValue(task.providerId)} queue=${displayValue(task.queue)} lane=${displayValue(task.lane)}`,
...renderAipodBindingHumanLines(aipodBinding),
` WorkspaceRef: ${agentRunCompactJson(task.workspaceRef, 140)}`,
` Execution: ${agentRunCompactJson(pickCompact(executionPolicy, ["sandbox", "approval", "timeoutMs", "network"]), 160)}`,
" Values: secret payloads are not printed; valuesPrinted=false",
@@ -568,16 +565,7 @@ export async function runAgentRunRunnerRest(action: string | undefined, id: stri
export async function runAgentRunAipodSpecsRest(action: string | undefined, id: string | undefined, args: string[]): Promise<Record<string, unknown>> {
if (action === "list") return await agentRunRestRequest("agentrun aipod-specs list", "GET", "/api/v1/aipod-specs");
if (action === "show" && id) return await agentRunRestRequest("agentrun aipod-specs show", "GET", `/api/v1/aipod-specs/${encodeURIComponent(id)}`);
if (action === "render" && id) {
const renderInput = await aipodRenderInputFromArgs(args, 2);
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(id)}/render`, renderInput);
const renderedData = record(innerData(rendered));
const task = record(renderedData.queueTask);
if (Object.keys(task).length === 0) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${id} render did not return queueTask`);
normalizeAipodRenderedQueueTask(task, args, id);
agentRunAipodBindingDisclosure(task, id, record(renderedData.modelResolution), renderInput);
return rendered;
}
if (action === "render" && id) return await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(id)}/render`, await aipodRenderInputFromArgs(args, 2));
if (action === "apply" || action === "set") {
const yaml = readYamlInputFromArgs(args);
const pathValue = id ? `/api/v1/aipod-specs/${encodeURIComponent(id)}` : "/api/v1/aipod-specs";
@@ -593,16 +581,13 @@ export async function runAgentRunAipodSpecsRest(action: string | undefined, id:
export async function submitQueueTaskRest(args: string[]): Promise<Record<string, unknown>> {
const aipod = agentRunOption(args, "aipod") ?? agentRunOption(args, "aipod-spec");
if (aipod) {
const renderInput = await aipodRenderInputFromArgs(args, 2);
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, renderInput);
const renderedData = record(innerData(rendered));
const body = normalizeAipodRenderedQueueTask(record(renderedData.queueTask), args, aipod);
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, await aipodRenderInputFromArgs(args, 2));
const body = normalizeAipodRenderedQueueTask(record(record(innerData(rendered)).queueTask), args, aipod);
if (Object.keys(body).length === 0) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return queueTask`);
const aipodBinding = agentRunAipodBindingDisclosure(body, aipod, record(renderedData.modelResolution), renderInput);
if (agentRunHasFlag(args, "dry-run")) {
return agentRunDryRunPlan("queue-submit", "/api/v1/queue/tasks", body, queueSubmitConfirmCommand(args, aipod), "POST", {
jsonInput: { source: "aipod-spec", aipod, valuesPrinted: false },
aipodBinding,
aipodBinding: agentRunAipodBindingDisclosure(body, aipod),
});
}
return await agentRunRestRequest("agentrun queue submit", "POST", "/api/v1/queue/tasks", body);
@@ -718,83 +703,12 @@ export async function sessionRunBodyFromArgs(sessionId: string, args: string[],
export function normalizeAipodRenderedQueueTask(task: Record<string, unknown>, args: string[], aipod: string): Record<string, unknown> {
if (Object.keys(task).length === 0) return task;
const sessionPolicy = readAgentRunClientConfig().client.sessionPolicy;
const policyTarget = resolveAgentRunSessionPolicyTarget();
const explicitProfile = agentRunOption(args, "profile") ?? agentRunOption(args, "backend-profile");
if (explicitProfile !== null) {
throw new AgentRunRestError("validation-failed", `AipodSpec ${aipod} owns backendProfile; explicit profile override ${explicitProfile} is not allowed`);
}
const renderedProfile = stringOrNull(task.backendProfile);
if (renderedProfile === null) {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return queueTask.backendProfile`);
}
const providerCredentials = agentRunProviderCredentialRefs(policyTarget.spec, renderedProfile);
if (providerCredentials.length === 0) {
throw new AgentRunRestError("validation-failed", `${policyTarget.configPath} has no providerCredential Secret binding for AipodSpec ${aipod} backendProfile=${renderedProfile} on ${policyTarget.spec.nodeId}/${policyTarget.spec.lane}`);
}
const basePolicy = record(task.executionPolicy);
if (Object.keys(basePolicy).length === 0) {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return queueTask.executionPolicy`);
}
validateAipodRenderedProviderCredentials(basePolicy, renderedProfile, providerCredentials, policyTarget, aipod);
const normalized: Record<string, unknown> = { ...task };
normalized.tenantId = stringOrNull(normalized.tenantId) ?? sessionPolicy.tenantId;
normalized.projectId = stringOrNull(normalized.projectId) ?? sessionPolicy.projectId;
normalized.providerId = stringOrNull(normalized.providerId) ?? defaultAgentRunProviderId(sessionPolicy, policyTarget);
normalized.backendProfile = renderedProfile;
if (!isRecord(normalized.workspaceRef)) normalized.workspaceRef = cloneJsonRecord(sessionPolicy.workspaceRef);
normalized.executionPolicy = defaultAgentRunExecutionPolicy(renderedProfile, sessionPolicy, policyTarget, {
includeToolCredentials: true,
basePolicy,
});
return normalized;
void args;
void aipod;
return { ...task };
}
function validateAipodRenderedProviderCredentials(
executionPolicy: Record<string, unknown>,
backendProfile: string,
expectedCredentials: readonly ReturnType<typeof agentRunProviderCredentialRefs>[number][],
policyTarget: ReturnType<typeof resolveAgentRunSessionPolicyTarget>,
aipod: string,
): void {
const rendered = arrayRecords(record(executionPolicy.secretScope).providerCredentials);
if (rendered.length === 0) {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return executionPolicy.secretScope.providerCredentials`);
}
const expectedByName = new Map(expectedCredentials.map((credential) => [credential.secretRef.name, credential]));
const seen = new Set<string>();
for (const [index, credential] of rendered.entries()) {
const renderedProfile = stringOrNull(credential.profile);
if (renderedProfile !== backendProfile) {
throw new AgentRunRestError("validation-failed", `aipod-spec ${aipod} providerCredentials[${index}].profile=${renderedProfile ?? "(missing)"} must exactly match queueTask.backendProfile=${backendProfile}`);
}
const secretRef = record(credential.secretRef);
const name = stringOrNull(secretRef.name);
const expected = name === null ? undefined : expectedByName.get(name);
if (name === null || expected === undefined || seen.has(name)) {
throw new AgentRunRestError("validation-failed", `aipod-spec ${aipod} provider credential SecretRef ${name ?? "(missing)"} is not the unique YAML binding for backendProfile=${backendProfile}`);
}
seen.add(name);
const namespace = stringOrNull(secretRef.namespace);
if (namespace !== null && namespace !== expected.secretRef.namespace) {
throw new AgentRunRestError("validation-failed", `aipod-spec ${aipod} provider credential ${name} namespace conflicts with YAML lane ${policyTarget.spec.nodeId}/${policyTarget.spec.lane}`);
}
const keys = Array.isArray(secretRef.keys) ? secretRef.keys.map(String) : [];
if (JSON.stringify([...keys].sort()) !== JSON.stringify([...expected.secretRef.keys].sort())) {
throw new AgentRunRestError("validation-failed", `aipod-spec ${aipod} provider credential ${name} keys conflict with YAML lane ${policyTarget.spec.nodeId}/${policyTarget.spec.lane}`);
}
}
if (seen.size !== expectedCredentials.length) {
throw new AgentRunRestError("validation-failed", `aipod-spec ${aipod} provider credential bindings are incomplete for backendProfile=${backendProfile}`);
}
}
export function agentRunAipodBindingDisclosure(
task: Record<string, unknown>,
aipod: string,
modelResolution: Record<string, unknown>,
renderInput: Record<string, unknown> | null = null,
): Record<string, unknown> {
export function agentRunAipodBindingDisclosure(task: Record<string, unknown>, aipod: string): Record<string, unknown> {
const policyTarget = resolveAgentRunSessionPolicyTarget();
const executionPolicy = record(task.executionPolicy);
const secretScope = record(executionPolicy.secretScope);
@@ -816,42 +730,6 @@ export function agentRunAipodBindingDisclosure(
projection: record(credential.projection),
valuesPrinted: false,
}));
const payload = record(task.payload);
const modelConfig = record(payload.modelConfig);
const model = requiredAipodResolutionString(modelResolution.model, aipod, "modelResolution.model");
const reasoningEffort = requiredAipodResolutionString(modelResolution.reasoningEffort, aipod, "modelResolution.reasoningEffort");
const modelSource = requiredAipodResolutionSource(modelResolution.modelSource, aipod, "modelResolution.modelSource");
const reasoningEffortSource = requiredAipodResolutionSource(modelResolution.reasoningEffortSource, aipod, "modelResolution.reasoningEffortSource");
const modelOverridden = requiredAipodResolutionBoolean(modelResolution.modelOverridden, aipod, "modelResolution.modelOverridden");
const reasoningEffortOverridden = requiredAipodResolutionBoolean(modelResolution.reasoningEffortOverridden, aipod, "modelResolution.reasoningEffortOverridden");
const backendProfile = requiredAipodResolutionString(task.backendProfile, aipod, "queueTask.backendProfile");
const resolvedBackendProfile = requiredAipodResolutionString(modelResolution.backendProfile, aipod, "modelResolution.backendProfile");
const backendProfileSource = requiredAipodResolutionString(modelResolution.backendProfileSource, aipod, "modelResolution.backendProfileSource");
assertAipodResolutionEqual(payload.model, model, aipod, "queueTask.payload.model", "modelResolution.model");
assertAipodResolutionEqual(payload.reasoningEffort, reasoningEffort, aipod, "queueTask.payload.reasoningEffort", "modelResolution.reasoningEffort");
assertAipodResolutionEqual(modelConfig.model, model, aipod, "queueTask.payload.modelConfig.model", "modelResolution.model");
assertAipodResolutionEqual(modelConfig.reasoningEffort, reasoningEffort, aipod, "queueTask.payload.modelConfig.reasoningEffort", "modelResolution.reasoningEffort");
assertAipodResolutionEqual(modelConfig.modelSource, modelSource, aipod, "queueTask.payload.modelConfig.modelSource", "modelResolution.modelSource");
assertAipodResolutionEqual(modelConfig.reasoningEffortSource, reasoningEffortSource, aipod, "queueTask.payload.modelConfig.reasoningEffortSource", "modelResolution.reasoningEffortSource");
if (modelConfig.valuesPrinted !== false || modelResolution.valuesPrinted !== false) {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} model resolution must disclose valuesPrinted=false`);
}
if (modelOverridden !== (modelSource === "render-input-override") || reasoningEffortOverridden !== (reasoningEffortSource === "render-input-override")) {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} model resolution override booleans conflict with source fields`);
}
if (renderInput !== null) {
validateAipodRenderOverride(renderInput, "model", model, modelSource, modelOverridden, aipod);
validateAipodRenderOverride(renderInput, "reasoningEffort", reasoningEffort, reasoningEffortSource, reasoningEffortOverridden, aipod);
}
if (resolvedBackendProfile !== backendProfile || backendProfileSource !== "aipod-spec") {
throw new AgentRunRestError("validation-failed", `aipod-spec ${aipod} modelResolution backendProfile must match queueTask.backendProfile and remain aipod-spec-owned`);
}
const credentialBindings = agentRunProviderCredentialRefs(policyTarget.spec, backendProfile);
if (credentialBindings.length !== 1) {
throw new AgentRunRestError("validation-failed", `${policyTarget.configPath} must declare one unique providerCredential Secret binding for AipodSpec ${aipod} backendProfile=${backendProfile}`);
}
const credentialBinding = credentialBindings[0]!;
validateAipodModelResolutionCredential(modelResolution, credentialBinding, aipod);
return {
aipod,
node: policyTarget.spec.nodeId,
@@ -859,15 +737,7 @@ export function agentRunAipodBindingDisclosure(
namespace: policyTarget.spec.runtime.namespace,
policySource: policyTarget.source,
providerId: task.providerId ?? null,
sourceProfile: credentialBinding.sourceProfile,
effectiveBackendProfile: backendProfile,
backendProfileSource,
model,
reasoningEffort,
modelSource,
reasoningEffortSource,
modelOverridden,
reasoningEffortOverridden,
backendProfile: task.backendProfile ?? null,
workspaceRef: task.workspaceRef ?? null,
executionPolicy: pickCompact(executionPolicy, ["sandbox", "approval", "timeoutMs", "network"]),
providerCredentials,
@@ -876,70 +746,6 @@ export function agentRunAipodBindingDisclosure(
};
}
function requiredAipodResolutionString(value: unknown, aipod: string, pathValue: string): string {
const parsed = stringOrNull(value);
if (parsed === null) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return non-empty ${pathValue}`);
return parsed;
}
function requiredAipodResolutionSource(value: unknown, aipod: string, pathValue: string): "aipod-spec-default" | "render-input-override" {
const parsed = requiredAipodResolutionString(value, aipod, pathValue);
if (parsed !== "aipod-spec-default" && parsed !== "render-input-override") {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} ${pathValue} must be aipod-spec-default or render-input-override`);
}
return parsed;
}
function requiredAipodResolutionBoolean(value: unknown, aipod: string, pathValue: string): boolean {
if (typeof value !== "boolean") throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return boolean ${pathValue}`);
return value;
}
function assertAipodResolutionEqual(actual: unknown, expected: string, aipod: string, actualPath: string, expectedPath: string): void {
if (actual !== expected) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} ${actualPath} must match ${expectedPath}`);
}
function validateAipodRenderOverride(
renderInput: Record<string, unknown>,
key: "model" | "reasoningEffort",
effectiveValue: string,
source: "aipod-spec-default" | "render-input-override",
overridden: boolean,
aipod: string,
): void {
const requested = Object.hasOwn(renderInput, key);
const expectedSource = requested ? "render-input-override" : "aipod-spec-default";
if (source !== expectedSource || overridden !== requested) {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} ${key} override disclosure does not match render input`);
}
if (requested && renderInput[key] !== effectiveValue) {
throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} effective ${key} does not match the explicit render input`);
}
}
function validateAipodModelResolutionCredential(
modelResolution: Record<string, unknown>,
expected: ReturnType<typeof agentRunProviderCredentialRefs>[number],
aipod: string,
): void {
const credential = record(modelResolution.providerCredential);
const secretRef = record(credential.secretRef);
const keys = Array.isArray(secretRef.keys) && secretRef.keys.every((key) => typeof key === "string")
? secretRef.keys.map(String).sort()
: null;
if (
credential.profile !== expected.profile
|| secretRef.namespace !== expected.secretRef.namespace
|| secretRef.name !== expected.secretRef.name
|| keys === null
|| JSON.stringify(keys) !== JSON.stringify([...expected.secretRef.keys].sort())
|| credential.valuesPrinted !== false
|| secretRef.valuesPrinted !== false
) {
throw new AgentRunRestError("validation-failed", `aipod-spec ${aipod} modelResolution.providerCredential must match the unique YAML SecretRef for backendProfile=${expected.profile}`);
}
}
export function agentRunSessionRunPolicyDisclosure(runBody: Record<string, unknown>): Record<string, unknown> {
const policyTarget = resolveAgentRunSessionPolicyTarget();
const executionPolicy = record(runBody.executionPolicy);
@@ -967,20 +773,17 @@ export function agentRunSessionRunPolicyDisclosure(runBody: Record<string, unkno
}
export async function sessionSendWithAipodRest(sessionId: string, aipod: string, args: string[]): Promise<Record<string, unknown>> {
const renderInput = await aipodRenderInputFromArgs(args, 2, { sessionId });
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, renderInput);
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, await aipodRenderInputFromArgs(args, 2, { sessionId }));
const renderedData = record(innerData(rendered));
const task = normalizeAipodRenderedQueueTask(record(renderedData.queueTask), args, aipod);
if (Object.keys(task).length === 0) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return queueTask`);
const aipodBinding = agentRunAipodBindingDisclosure(task, aipod, record(renderedData.modelResolution), renderInput);
const sessionRef = record(task.sessionRef);
const metadata = record(sessionRef.metadata);
const title = agentRunOption(args, "title") ?? stringOrNull(task.title);
if (title) metadata.title = title;
const sessionPolicy = readAgentRunClientConfig().client.sessionPolicy;
const policyTarget = resolveAgentRunSessionPolicyTarget();
const backendProfile = stringOrNull(task.backendProfile);
if (backendProfile === null) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} normalized queueTask has no backendProfile`);
const backendProfile = stringOrNull(task.backendProfile) ?? sessionPolicy.backendProfile;
const executionPolicy = record(task.executionPolicy);
const runBody: Record<string, unknown> = {
tenantId: stringOrNull(task.tenantId) ?? sessionPolicy.tenantId,
@@ -1015,7 +818,7 @@ export async function sessionSendWithAipodRest(sessionId: string, aipod: string,
aipod,
profile: String(task.backendProfile ?? ""),
sessionPolicy: agentRunSessionRunPolicyDisclosure(runBody),
aipodBinding,
aipodBinding: agentRunAipodBindingDisclosure(task, aipod),
valuesPrinted: false,
},
bridge: response.bridge,
+1 -8
View File
@@ -116,17 +116,14 @@ export function renderAgentRunSecretSyncText(payload: Record<string, unknown>):
`SECRET PLAN count=${displayValue(plan.secretCount)} omitted=${displayValue(plan.omittedCount)}`,
planItems.length === 0
? "-"
: renderTable(["ID", "TARGET", "KEY", "SOURCE", "SOURCE PROFILE", "BACKEND PROFILE", "PRESENT", "FINGERPRINT", "STATUS"], planItems.map((item) => {
: renderTable(["ID", "TARGET", "KEY", "SOURCE", "PRESENT", "FINGERPRINT", "STATUS"], planItems.map((item) => {
const source = record(item.source);
const target = record(item.target);
const profile = record(item.profile);
return [
displayValue(item.id),
displayValue(target.identity),
displayValue(target.key),
displayValue(source.mode),
displayValue(profile.sourceProfile),
displayValue(profile.effectiveBackendProfile),
displayValue(source.present),
shortFingerprint(source.fingerprint),
source.present === true ? "ready" : "unavailable",
@@ -204,10 +201,6 @@ function compactSecretPlanItem(item: Record<string, unknown>, identity: Record<s
fingerprint: boundedText(item.fingerprint, 96),
...(boundedText(item.degradedReason) === null ? {} : { reason: boundedText(item.degradedReason) }),
},
profile: {
sourceProfile: boundedText(item.sourceProfile),
effectiveBackendProfile: boundedText(item.effectiveBackendProfile),
},
};
}
-4
View File
@@ -875,8 +875,6 @@ export type LaneSecretSource = {
sourceFormat?: "env" | "raw-token" | null;
codexConfig?: AgentRunCodexConfigSpec | null;
jsonValue?: unknown;
providerCredentialSourceProfile?: string | null;
providerCredentialProfile?: string | null;
targetRef: { namespace: string; name: string; key: string };
transform?: "codex-auth-json-openai-api-key" | "local-postgres-database" | "local-postgres-user" | "local-postgres-database-url" | null;
};
@@ -1158,8 +1156,6 @@ export function collectLaneSecretSources(spec: AgentRunLaneSpec): LaneSecretSour
transform: secret.transform,
codexConfig: secret.codexConfig,
jsonValue: secret.jsonValue,
providerCredentialSourceProfile: secret.providerCredentialSourceProfile,
providerCredentialProfile: secret.providerCredentialProfile,
targetRef: secret.targetRef,
});
}
@@ -16,7 +16,6 @@ type SessionSendProjection = {
resources: Record<string, unknown>;
runnerAdmission: Record<string, unknown> | null;
request: Record<string, unknown> | null;
aipodBinding: Record<string, unknown> | null;
error: Record<string, unknown> | null;
next: {
actions: Array<{ command: string; mutation: boolean; reason: string }>;
@@ -52,7 +51,6 @@ export function sessionSendSuccessProjection(command: string, raw: Record<string
const rawRunnerAdmission = record(data.runnerAdmission);
const runnerAdmission = compactRunnerAdmission(rawRunnerAdmission, dispatchIntent);
const request = compactRequest(record(data.request));
const aipodBinding = compactAipodBinding(record(data.aipodBinding));
const decision = stringOrNull(data.decision);
const dryRun = data.dryRun === true;
const reuseRun = decision === null
@@ -102,7 +100,6 @@ export function sessionSendSuccessProjection(command: string, raw: Record<string
}),
runnerAdmission,
request,
aipodBinding,
error: null,
next: {
actions: nextActions,
@@ -202,7 +199,6 @@ export function sessionSendErrorProjection(raw: Record<string, unknown>): Sessio
}),
runnerAdmission,
request: null,
aipodBinding: null,
error: errorProjection,
next: {
actions: nextActions,
@@ -241,7 +237,6 @@ function renderSessionSendHuman(command: string, projection: SessionSendProjecti
lines.push(`CreateRunner: ${display(projection.createRunner)}`);
lines.push(`Mutation: ${display(projection.mutation)}`);
lines.push(`PartialWrite: ${display(projection.partialWrite)}`);
if (projection.aipodBinding !== null) lines.push(...renderAipodBindingHumanLines(projection.aipodBinding));
const resources = Object.values(projection.resources).filter((value) => value !== null).map(record);
if (resources.length > 0) {
lines.push("", "Resources:");
@@ -275,59 +270,6 @@ function renderSessionSendHuman(command: string, projection: SessionSendProjecti
return { ok: projection.ok, command, renderedText: lines.join("\n"), contentType: "text/plain" };
}
export function compactAipodBinding(value: Record<string, unknown>): Record<string, unknown> | null {
if (Object.keys(value).length === 0) return null;
const providerCredentials = Array.isArray(value.providerCredentials)
? value.providerCredentials.slice(0, 4).map(record).map((credential) => {
const secretRef = record(credential.secretRef);
return {
profile: stringOrNull(credential.profile),
secretRef: {
name: stringOrNull(secretRef.name),
keys: Array.isArray(secretRef.keys) ? secretRef.keys.filter((key): key is string => typeof key === "string").slice(0, 8) : [],
},
valuesPrinted: false,
};
})
: [];
return {
aipod: stringOrNull(value.aipod),
node: stringOrNull(value.node),
lane: stringOrNull(value.lane),
namespace: stringOrNull(value.namespace),
sourceProfile: stringOrNull(value.sourceProfile),
effectiveBackendProfile: stringOrNull(value.effectiveBackendProfile),
backendProfileSource: stringOrNull(value.backendProfileSource),
model: stringOrNull(value.model),
reasoningEffort: stringOrNull(value.reasoningEffort),
modelSource: stringOrNull(value.modelSource),
reasoningEffortSource: stringOrNull(value.reasoningEffortSource),
modelOverridden: typeof value.modelOverridden === "boolean" ? value.modelOverridden : null,
reasoningEffortOverridden: typeof value.reasoningEffortOverridden === "boolean" ? value.reasoningEffortOverridden : null,
providerCredentials,
valuesPrinted: false,
};
}
export function renderAipodBindingHumanLines(value: Record<string, unknown>): string[] {
const binding = compactAipodBinding(value);
if (binding === null) return [];
const credentials = Array.isArray(binding.providerCredentials) ? binding.providerCredentials.map(record) : [];
const lines = [
"",
"AIPOD BINDING",
` Provider: sourceProfile=${display(binding.sourceProfile)} effectiveBackendProfile=${display(binding.effectiveBackendProfile)} source=${display(binding.backendProfileSource)}`,
` Model: model=${display(binding.model)} reasoningEffort=${display(binding.reasoningEffort)}`,
` Resolution: modelSource=${display(binding.modelSource)} modelOverridden=${display(binding.modelOverridden)} reasoningEffortSource=${display(binding.reasoningEffortSource)} reasoningEffortOverridden=${display(binding.reasoningEffortOverridden)}`,
];
for (const credential of credentials) {
const secretRef = record(credential.secretRef);
const keys = Array.isArray(secretRef.keys) ? secretRef.keys.map(String).join(",") : "";
lines.push(` SecretRef: profile=${display(credential.profile)} ref=${display(binding.namespace)}/${display(secretRef.name)} keys=${keys || "-"} valuesPrinted=false`);
}
return lines;
}
function compactResources(resources: Record<string, Record<string, unknown> | null>): Record<string, unknown> {
const result: Record<string, unknown> = {
session: resources.session,
+2 -6
View File
@@ -149,15 +149,13 @@ export async function restartYamlLane(config: UniDeskConfig, options: LaneConfir
namespace: secret.targetRef.namespace,
name: secret.targetRef.name,
key: secret.targetRef.key,
sourceProfile: secret.providerCredentialSourceProfile,
effectiveBackendProfile: secret.providerCredentialProfile,
providerCredentialProfile: secret.providerCredentialProfile,
valuesPrinted: false,
})),
valuesPrinted: false,
},
providerCredentials: agentRunProviderCredentialRefs(spec).map((credential) => ({
sourceProfile: credential.sourceProfile,
effectiveBackendProfile: credential.profile,
profile: credential.profile,
namespace: credential.secretRef.namespace,
name: credential.secretRef.name,
keys: credential.secretRef.keys,
@@ -230,8 +228,6 @@ export async function secretSync(config: UniDeskConfig, options: SecretSyncOptio
sourceMode: item.sourceMode,
sourceKey: item.sourceKey,
transform: item.transform ?? null,
sourceProfile: item.providerCredentialSourceProfile ?? null,
effectiveBackendProfile: item.providerCredentialProfile ?? null,
...inspection.summary,
valuesPrinted: false,
}));