Merge pull request #409 from pikasTech/issue-408-agentrun-provider-session-policy
R4: AgentRun provider/session policy YAML 化
This commit is contained in:
+42
-1
@@ -33,6 +33,21 @@ auth:
|
|||||||
client:
|
client:
|
||||||
role: render-only
|
role: render-only
|
||||||
transport: direct-http
|
transport: direct-http
|
||||||
|
sessionPolicy:
|
||||||
|
tenantId: unidesk
|
||||||
|
projectId: default
|
||||||
|
providerId: G14
|
||||||
|
backendProfile: codex
|
||||||
|
workspaceRef:
|
||||||
|
kind: opaque
|
||||||
|
path: .
|
||||||
|
executionPolicy:
|
||||||
|
sandbox: workspace-write
|
||||||
|
approval: never
|
||||||
|
timeoutMs: 900000
|
||||||
|
network: enabled
|
||||||
|
secretScope:
|
||||||
|
allowCredentialEcho: false
|
||||||
|
|
||||||
controlPlane:
|
controlPlane:
|
||||||
default:
|
default:
|
||||||
@@ -178,7 +193,25 @@ controlPlane:
|
|||||||
name: agentrun-v01-mgr-db
|
name: agentrun-v01-mgr-db
|
||||||
key: DATABASE_URL
|
key: DATABASE_URL
|
||||||
localPostgresExpectedAbsent: false
|
localPostgresExpectedAbsent: false
|
||||||
secrets: []
|
secrets:
|
||||||
|
- id: provider-codex-auth-json
|
||||||
|
sourceMode: file
|
||||||
|
sourceRef: /root/.codex/auth.json
|
||||||
|
targetRef:
|
||||||
|
namespace: agentrun-v01
|
||||||
|
name: agentrun-v01-provider-codex
|
||||||
|
key: auth.json
|
||||||
|
providerCredential:
|
||||||
|
profile: codex
|
||||||
|
- id: provider-codex-config
|
||||||
|
sourceMode: file
|
||||||
|
sourceRef: /root/.codex/config.toml
|
||||||
|
targetRef:
|
||||||
|
namespace: agentrun-v01
|
||||||
|
name: agentrun-v01-provider-codex
|
||||||
|
key: config.toml
|
||||||
|
providerCredential:
|
||||||
|
profile: codex
|
||||||
|
|
||||||
v02:
|
v02:
|
||||||
node: D601
|
node: D601
|
||||||
@@ -334,6 +367,8 @@ controlPlane:
|
|||||||
namespace: agentrun-v02
|
namespace: agentrun-v02
|
||||||
name: agentrun-v01-provider-codex
|
name: agentrun-v01-provider-codex
|
||||||
key: auth.json
|
key: auth.json
|
||||||
|
providerCredential:
|
||||||
|
profile: codex
|
||||||
- id: provider-codex-config
|
- id: provider-codex-config
|
||||||
sourceMode: file
|
sourceMode: file
|
||||||
sourceRef: /root/.codex/config.toml
|
sourceRef: /root/.codex/config.toml
|
||||||
@@ -341,6 +376,8 @@ controlPlane:
|
|||||||
namespace: agentrun-v02
|
namespace: agentrun-v02
|
||||||
name: agentrun-v01-provider-codex
|
name: agentrun-v01-provider-codex
|
||||||
key: config.toml
|
key: config.toml
|
||||||
|
providerCredential:
|
||||||
|
profile: codex
|
||||||
- id: provider-deepseek-auth-json
|
- id: provider-deepseek-auth-json
|
||||||
sourceMode: file
|
sourceMode: file
|
||||||
sourceRef: /root/.codex-deepseek-v4-pro/auth.json
|
sourceRef: /root/.codex-deepseek-v4-pro/auth.json
|
||||||
@@ -348,6 +385,8 @@ controlPlane:
|
|||||||
namespace: agentrun-v02
|
namespace: agentrun-v02
|
||||||
name: agentrun-v01-provider-deepseek
|
name: agentrun-v01-provider-deepseek
|
||||||
key: auth.json
|
key: auth.json
|
||||||
|
providerCredential:
|
||||||
|
profile: deepseek
|
||||||
- id: provider-deepseek-config
|
- id: provider-deepseek-config
|
||||||
sourceMode: file
|
sourceMode: file
|
||||||
sourceRef: agentrun/d601-v02-provider-deepseek-config.toml
|
sourceRef: agentrun/d601-v02-provider-deepseek-config.toml
|
||||||
@@ -355,6 +394,8 @@ controlPlane:
|
|||||||
namespace: agentrun-v02
|
namespace: agentrun-v02
|
||||||
name: agentrun-v01-provider-deepseek
|
name: agentrun-v01-provider-deepseek
|
||||||
key: config.toml
|
key: config.toml
|
||||||
|
providerCredential:
|
||||||
|
profile: deepseek
|
||||||
- id: tool-github-pr-token
|
- id: tool-github-pr-token
|
||||||
sourceRef: /root/.config/unidesk/github.env
|
sourceRef: /root/.config/unidesk/github.env
|
||||||
sourceKey: GH_TOKEN
|
sourceKey: GH_TOKEN
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ export interface AgentRunLaneSecretSpec {
|
|||||||
readonly sourceMode: "env" | "file";
|
readonly sourceMode: "env" | "file";
|
||||||
readonly sourceKey: string | null;
|
readonly sourceKey: string | null;
|
||||||
readonly targetRef: AgentRunSecretRef;
|
readonly targetRef: AgentRunSecretRef;
|
||||||
|
readonly providerCredentialProfile: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentRunProviderCredentialRef {
|
||||||
|
readonly profile: string;
|
||||||
|
readonly secretRef: {
|
||||||
|
readonly namespace: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly keys: readonly string[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentRunLaneSpec {
|
export interface AgentRunLaneSpec {
|
||||||
@@ -193,10 +203,6 @@ export function resolveAgentRunLaneTarget(options: { node?: string | null; lane?
|
|||||||
return { configPath: config.sourcePath, spec };
|
return { configPath: config.sourcePath, spec };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function agentRunDefaultProviderId(env: NodeJS.ProcessEnv = process.env): string {
|
|
||||||
return resolveAgentRunLaneTarget({}, env).spec.nodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function agentRunLaneSummary(spec: AgentRunLaneSpec): Record<string, unknown> {
|
export function agentRunLaneSummary(spec: AgentRunLaneSpec): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
node: {
|
node: {
|
||||||
@@ -306,11 +312,43 @@ export function agentRunLaneSummary(spec: AgentRunLaneSpec): Record<string, unkn
|
|||||||
sourceMode: secret.sourceMode,
|
sourceMode: secret.sourceMode,
|
||||||
sourceKey: secret.sourceKey,
|
sourceKey: secret.sourceKey,
|
||||||
targetRef: secret.targetRef,
|
targetRef: secret.targetRef,
|
||||||
|
providerCredential: secret.providerCredentialProfile === null ? null : { profile: secret.providerCredentialProfile },
|
||||||
|
valuesPrinted: false,
|
||||||
|
})),
|
||||||
|
providerCredentials: agentRunProviderCredentialRefs(spec).map((credential) => ({
|
||||||
|
profile: credential.profile,
|
||||||
|
secretRef: credential.secretRef,
|
||||||
valuesPrinted: false,
|
valuesPrinted: false,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function agentRunProviderCredentialRefs(spec: AgentRunLaneSpec, profile?: string | null): readonly AgentRunProviderCredentialRef[] {
|
||||||
|
const groups = new Map<string, { profile: string; namespace: string; name: string; keys: string[] }>();
|
||||||
|
for (const secret of spec.secrets) {
|
||||||
|
const credentialProfile = secret.providerCredentialProfile;
|
||||||
|
if (credentialProfile === null) continue;
|
||||||
|
if (profile !== undefined && profile !== null && credentialProfile !== profile) continue;
|
||||||
|
const groupKey = `${credentialProfile}\0${secret.targetRef.namespace}\0${secret.targetRef.name}`;
|
||||||
|
const group = groups.get(groupKey) ?? {
|
||||||
|
profile: credentialProfile,
|
||||||
|
namespace: secret.targetRef.namespace,
|
||||||
|
name: secret.targetRef.name,
|
||||||
|
keys: [],
|
||||||
|
};
|
||||||
|
if (!group.keys.includes(secret.targetRef.key)) group.keys.push(secret.targetRef.key);
|
||||||
|
groups.set(groupKey, group);
|
||||||
|
}
|
||||||
|
return Array.from(groups.values()).map((group) => ({
|
||||||
|
profile: group.profile,
|
||||||
|
secretRef: {
|
||||||
|
namespace: group.namespace,
|
||||||
|
name: group.name,
|
||||||
|
keys: group.keys,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export function agentRunPipelineRunName(spec: AgentRunLaneSpec, sourceCommit: string): string {
|
export function agentRunPipelineRunName(spec: AgentRunLaneSpec, sourceCommit: string): string {
|
||||||
return `${spec.ci.pipelineRunPrefix}-${sourceCommit.slice(0, 12)}`;
|
return `${spec.ci.pipelineRunPrefix}-${sourceCommit.slice(0, 12)}`;
|
||||||
}
|
}
|
||||||
@@ -531,15 +569,26 @@ function parseLaneSecret(input: Record<string, unknown>, path: string): AgentRun
|
|||||||
if (sourceMode !== "env" && sourceMode !== "file") throw new Error(`${path}.sourceMode must be env or file`);
|
if (sourceMode !== "env" && sourceMode !== "file") throw new Error(`${path}.sourceMode must be env or file`);
|
||||||
const sourceKey = optionalStringField(input, "sourceKey", path) ?? null;
|
const sourceKey = optionalStringField(input, "sourceKey", path) ?? null;
|
||||||
if (sourceMode === "env" && sourceKey === null) throw new Error(`${path}.sourceKey is required when sourceMode is env`);
|
if (sourceMode === "env" && sourceKey === null) throw new Error(`${path}.sourceKey is required when sourceMode is env`);
|
||||||
|
const providerCredential = parseProviderCredentialBinding(input, `${path}.providerCredential`);
|
||||||
return {
|
return {
|
||||||
id: stringField(input, "id", path),
|
id: stringField(input, "id", path),
|
||||||
sourceRef: secretSourceRefField(input, "sourceRef", path),
|
sourceRef: secretSourceRefField(input, "sourceRef", path),
|
||||||
sourceMode,
|
sourceMode,
|
||||||
sourceKey,
|
sourceKey,
|
||||||
targetRef: parseNamespacedSecretRef(recordField(input, "targetRef", path), `${path}.targetRef`),
|
targetRef: parseNamespacedSecretRef(recordField(input, "targetRef", path), `${path}.targetRef`),
|
||||||
|
providerCredentialProfile: providerCredential,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseProviderCredentialBinding(input: Record<string, unknown>, path: string): string | null {
|
||||||
|
const value = input.providerCredential;
|
||||||
|
if (value === undefined || value === null) return null;
|
||||||
|
const record = asRecord(value, path);
|
||||||
|
const profile = stringField(record, "profile", path);
|
||||||
|
validateSimpleId(profile, path);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
function parseGitMirrorRepository(input: Record<string, unknown>, path: string): AgentRunGitMirrorRepositorySpec {
|
function parseGitMirrorRepository(input: Record<string, unknown>, path: string): AgentRunGitMirrorRepositorySpec {
|
||||||
const gitopsBranch = optionalStringField(input, "gitopsBranch", path);
|
const gitopsBranch = optionalStringField(input, "gitopsBranch", path);
|
||||||
return {
|
return {
|
||||||
|
|||||||
+117
-31
@@ -10,9 +10,9 @@ import { runRemoteSshCommandCapture } from "./remote";
|
|||||||
import { startJob } from "./jobs";
|
import { startJob } from "./jobs";
|
||||||
import {
|
import {
|
||||||
AGENTRUN_CONFIG_PATH,
|
AGENTRUN_CONFIG_PATH,
|
||||||
agentRunDefaultProviderId,
|
|
||||||
agentRunLaneSummary,
|
agentRunLaneSummary,
|
||||||
agentRunPipelineRunName,
|
agentRunPipelineRunName,
|
||||||
|
agentRunProviderCredentialRefs,
|
||||||
resolveAgentRunLaneTarget,
|
resolveAgentRunLaneTarget,
|
||||||
type AgentRunLaneSpec,
|
type AgentRunLaneSpec,
|
||||||
} from "./agentrun-lanes";
|
} from "./agentrun-lanes";
|
||||||
@@ -45,6 +45,7 @@ export function agentRunHelp(): unknown {
|
|||||||
"bun scripts/cli.ts agentrun apply -f - --dry-run",
|
"bun scripts/cli.ts agentrun apply -f - --dry-run",
|
||||||
"bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin",
|
"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 task",
|
||||||
|
"bun scripts/cli.ts agentrun explain session-policy",
|
||||||
"bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02",
|
"bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02",
|
||||||
"bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --dry-run",
|
"bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --dry-run",
|
||||||
"bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02",
|
"bun scripts/cli.ts agentrun control-plane status --node D601 --lane v02",
|
||||||
@@ -1536,6 +1537,7 @@ function nextPagedResourceCommand(command: string, nextAfterSeq: string, limit:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function agentRunExplain(kindRaw: string): string {
|
function agentRunExplain(kindRaw: string): string {
|
||||||
|
if (kindRaw === "session-policy" || kindRaw === "provider-policy") return renderAgentRunSessionPolicyExplanation();
|
||||||
const kind = parseResourceKind(kindRaw);
|
const kind = parseResourceKind(kindRaw);
|
||||||
if (kind === "task") {
|
if (kind === "task") {
|
||||||
return [
|
return [
|
||||||
@@ -4599,21 +4601,21 @@ function isExplicitSessionSendBody(input: Record<string, unknown>): boolean {
|
|||||||
|
|
||||||
async function sessionRunBodyFromArgs(sessionId: string, args: string[], input: Record<string, unknown>): Promise<Record<string, unknown>> {
|
async function sessionRunBodyFromArgs(sessionId: string, args: string[], input: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||||
const existing = await fetchAgentRunSessionOrNull(sessionId, args);
|
const existing = await fetchAgentRunSessionOrNull(sessionId, args);
|
||||||
const defaultProviderId = agentRunDefaultProviderId();
|
const sessionPolicy = readAgentRunClientConfig().client.sessionPolicy;
|
||||||
const profile = agentRunOption(args, "profile")
|
const profile = agentRunOption(args, "profile")
|
||||||
?? agentRunOption(args, "backend-profile")
|
?? agentRunOption(args, "backend-profile")
|
||||||
?? stringOrNull(input.backendProfile)
|
?? stringOrNull(input.backendProfile)
|
||||||
?? stringOrNull(existing?.backendProfile)
|
?? stringOrNull(existing?.backendProfile)
|
||||||
?? "codex";
|
?? sessionPolicy.backendProfile;
|
||||||
const body: Record<string, unknown> = { ...input };
|
const body: Record<string, unknown> = { ...input };
|
||||||
body.tenantId = agentRunOption(args, "tenant-id") ?? stringOrNull(body.tenantId) ?? stringOrNull(existing?.tenantId) ?? "unidesk";
|
body.tenantId = agentRunOption(args, "tenant-id") ?? stringOrNull(body.tenantId) ?? stringOrNull(existing?.tenantId) ?? sessionPolicy.tenantId;
|
||||||
body.projectId = agentRunOption(args, "project-id") ?? stringOrNull(body.projectId) ?? stringOrNull(existing?.projectId) ?? "default";
|
body.projectId = agentRunOption(args, "project-id") ?? stringOrNull(body.projectId) ?? stringOrNull(existing?.projectId) ?? sessionPolicy.projectId;
|
||||||
body.providerId = agentRunOption(args, "provider-id") ?? stringOrNull(body.providerId) ?? stringOrNull(existing?.providerId) ?? defaultProviderId;
|
body.providerId = agentRunOption(args, "provider-id") ?? stringOrNull(body.providerId) ?? stringOrNull(existing?.providerId) ?? sessionPolicy.providerId;
|
||||||
body.backendProfile = profile;
|
body.backendProfile = profile;
|
||||||
body.workspaceRef = jsonObjectOption(args, "workspace-json") ?? record(body.workspaceRef);
|
body.workspaceRef = jsonObjectOption(args, "workspace-json") ?? record(body.workspaceRef);
|
||||||
if (Object.keys(record(body.workspaceRef)).length === 0) body.workspaceRef = { kind: "opaque", path: "." };
|
if (Object.keys(record(body.workspaceRef)).length === 0) body.workspaceRef = cloneJsonRecord(sessionPolicy.workspaceRef);
|
||||||
body.executionPolicy = jsonObjectOption(args, "execution-policy-json") ?? record(body.executionPolicy);
|
body.executionPolicy = jsonObjectOption(args, "execution-policy-json") ?? record(body.executionPolicy);
|
||||||
if (Object.keys(record(body.executionPolicy)).length === 0) body.executionPolicy = defaultAgentRunExecutionPolicy(profile);
|
if (Object.keys(record(body.executionPolicy)).length === 0) body.executionPolicy = defaultAgentRunExecutionPolicy(profile, sessionPolicy);
|
||||||
const inheritedSessionRef = existing === null ? {} : {
|
const inheritedSessionRef = existing === null ? {} : {
|
||||||
conversationId: existing.conversationId,
|
conversationId: existing.conversationId,
|
||||||
threadId: existing.threadId,
|
threadId: existing.threadId,
|
||||||
@@ -4636,15 +4638,17 @@ async function sessionSendWithAipodRest(sessionId: string, aipod: string, args:
|
|||||||
const metadata = record(sessionRef.metadata);
|
const metadata = record(sessionRef.metadata);
|
||||||
const title = agentRunOption(args, "title") ?? stringOrNull(task.title);
|
const title = agentRunOption(args, "title") ?? stringOrNull(task.title);
|
||||||
if (title) metadata.title = title;
|
if (title) metadata.title = title;
|
||||||
const defaultProviderId = agentRunDefaultProviderId();
|
const sessionPolicy = readAgentRunClientConfig().client.sessionPolicy;
|
||||||
|
const backendProfile = stringOrNull(task.backendProfile) ?? sessionPolicy.backendProfile;
|
||||||
|
const executionPolicy = record(task.executionPolicy);
|
||||||
const runBody: Record<string, unknown> = {
|
const runBody: Record<string, unknown> = {
|
||||||
tenantId: task.tenantId,
|
tenantId: stringOrNull(task.tenantId) ?? sessionPolicy.tenantId,
|
||||||
projectId: task.projectId,
|
projectId: stringOrNull(task.projectId) ?? sessionPolicy.projectId,
|
||||||
providerId: task.providerId ?? defaultProviderId,
|
providerId: stringOrNull(task.providerId) ?? sessionPolicy.providerId,
|
||||||
backendProfile: task.backendProfile,
|
backendProfile,
|
||||||
workspaceRef: task.workspaceRef ?? { kind: "opaque", path: "." },
|
workspaceRef: task.workspaceRef ?? cloneJsonRecord(sessionPolicy.workspaceRef),
|
||||||
sessionRef: { ...sessionRef, sessionId, metadata },
|
sessionRef: { ...sessionRef, sessionId, metadata },
|
||||||
executionPolicy: task.executionPolicy,
|
executionPolicy: Object.keys(executionPolicy).length === 0 ? defaultAgentRunExecutionPolicy(backendProfile, sessionPolicy) : executionPolicy,
|
||||||
resourceBundleRef: task.resourceBundleRef,
|
resourceBundleRef: task.resourceBundleRef,
|
||||||
traceSink: { kind: "aipod-session", aipod, sessionId, valuesPrinted: false },
|
traceSink: { kind: "aipod-session", aipod, sessionId, valuesPrinted: false },
|
||||||
};
|
};
|
||||||
@@ -4808,6 +4812,7 @@ export function parseAgentRunClientConfigYaml(raw: string, sourcePath = "config/
|
|||||||
const transport = stringFieldFromRecord(client, "transport", "client");
|
const transport = stringFieldFromRecord(client, "transport", "client");
|
||||||
if (role !== "render-only") throw new Error(`${sourcePath}: client.role must be render-only`);
|
if (role !== "render-only") throw new Error(`${sourcePath}: client.role must be render-only`);
|
||||||
if (transport !== "direct-http") throw new Error(`${sourcePath}: client.transport must be direct-http`);
|
if (transport !== "direct-http") throw new Error(`${sourcePath}: client.transport must be direct-http`);
|
||||||
|
const sessionPolicy = readAgentRunSessionPolicyConfig(client, sourcePath);
|
||||||
const publicExposure = readAgentRunPublicExposureConfig(input.publicExposure, baseUrl.replace(/\/+$/u, "/"), sourcePath);
|
const publicExposure = readAgentRunPublicExposureConfig(input.publicExposure, baseUrl.replace(/\/+$/u, "/"), sourcePath);
|
||||||
return {
|
return {
|
||||||
sourcePath,
|
sourcePath,
|
||||||
@@ -4824,11 +4829,35 @@ export function parseAgentRunClientConfigYaml(raw: string, sourcePath = "config/
|
|||||||
client: {
|
client: {
|
||||||
role,
|
role,
|
||||||
transport,
|
transport,
|
||||||
|
sessionPolicy,
|
||||||
},
|
},
|
||||||
publicExposure,
|
publicExposure,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readAgentRunSessionPolicyConfig(client: Record<string, unknown>, sourcePath: string): AgentRunSessionPolicyConfig {
|
||||||
|
const pathValue = "client.sessionPolicy";
|
||||||
|
const sessionPolicy = record(client.sessionPolicy);
|
||||||
|
const workspaceRef = record(sessionPolicy.workspaceRef);
|
||||||
|
const executionPolicy = record(sessionPolicy.executionPolicy);
|
||||||
|
const secretScope = record(executionPolicy.secretScope);
|
||||||
|
stringFieldFromRecord(workspaceRef, "kind", `${pathValue}.workspaceRef`);
|
||||||
|
stringFieldFromRecord(executionPolicy, "sandbox", `${pathValue}.executionPolicy`);
|
||||||
|
stringFieldFromRecord(executionPolicy, "approval", `${pathValue}.executionPolicy`);
|
||||||
|
positiveIntegerFieldFromRecord(executionPolicy, "timeoutMs", `${pathValue}.executionPolicy`);
|
||||||
|
stringFieldFromRecord(executionPolicy, "network", `${pathValue}.executionPolicy`);
|
||||||
|
booleanFieldFromRecord(secretScope, "allowCredentialEcho", `${pathValue}.executionPolicy.secretScope`);
|
||||||
|
return {
|
||||||
|
sourcePath,
|
||||||
|
tenantId: stringFieldFromRecord(sessionPolicy, "tenantId", pathValue),
|
||||||
|
projectId: stringFieldFromRecord(sessionPolicy, "projectId", pathValue),
|
||||||
|
providerId: stringFieldFromRecord(sessionPolicy, "providerId", pathValue),
|
||||||
|
backendProfile: stringFieldFromRecord(sessionPolicy, "backendProfile", pathValue),
|
||||||
|
workspaceRef: cloneJsonRecord(workspaceRef),
|
||||||
|
executionPolicy: cloneJsonRecord(executionPolicy),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function validateAgentRunConfigEnvelope(input: Record<string, unknown>, sourcePath: string): void {
|
function validateAgentRunConfigEnvelope(input: Record<string, unknown>, sourcePath: string): void {
|
||||||
if (input.version !== 1) throw new Error(`${sourcePath}: version must be 1`);
|
if (input.version !== 1) throw new Error(`${sourcePath}: version must be 1`);
|
||||||
if (input.kind !== "AgentRunConfig") throw new Error(`${sourcePath}: kind must be AgentRunConfig`);
|
if (input.kind !== "AgentRunConfig") throw new Error(`${sourcePath}: kind must be AgentRunConfig`);
|
||||||
@@ -5115,28 +5144,58 @@ function jsonInputDisclosureFromArgs(args: string[]): Record<string, unknown> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultAgentRunExecutionPolicy(profile: string): Record<string, unknown> {
|
function defaultAgentRunExecutionPolicy(profile: string, sessionPolicy: AgentRunSessionPolicyConfig = readAgentRunClientConfig().client.sessionPolicy): Record<string, unknown> {
|
||||||
const keys = profile === "sub2api" || profile === "codex" ? ["auth.json", "config.toml"] : ["auth.json", "config.toml"];
|
const { spec } = resolveAgentRunLaneTarget({});
|
||||||
|
const credentials = agentRunProviderCredentialRefs(spec, profile);
|
||||||
|
if (credentials.length === 0) {
|
||||||
|
throw new AgentRunRestError("validation-failed", `config/agentrun.yaml has no providerCredential Secret binding for backendProfile=${profile} on default lane ${spec.nodeId}/${spec.lane}`);
|
||||||
|
}
|
||||||
|
const providerCredentials = credentials.map((credential) => {
|
||||||
|
if (credential.secretRef.namespace !== spec.runtime.namespace) {
|
||||||
|
throw new AgentRunRestError("validation-failed", `providerCredential ${profile} Secret ${credential.secretRef.name} is in namespace ${credential.secretRef.namespace}, expected default lane namespace ${spec.runtime.namespace}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
profile: credential.profile,
|
||||||
|
secretRef: {
|
||||||
|
name: credential.secretRef.name,
|
||||||
|
keys: credential.secretRef.keys,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const basePolicy = cloneJsonRecord(sessionPolicy.executionPolicy);
|
||||||
|
const secretScope = record(basePolicy.secretScope);
|
||||||
return {
|
return {
|
||||||
sandbox: "workspace-write",
|
...basePolicy,
|
||||||
approval: "never",
|
|
||||||
timeoutMs: 900000,
|
|
||||||
network: "enabled",
|
|
||||||
secretScope: {
|
secretScope: {
|
||||||
allowCredentialEcho: false,
|
...secretScope,
|
||||||
providerCredentials: [
|
providerCredentials,
|
||||||
{
|
|
||||||
profile,
|
|
||||||
secretRef: {
|
|
||||||
name: `agentrun-v01-provider-${profile}`,
|
|
||||||
keys,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderAgentRunSessionPolicyExplanation(): string {
|
||||||
|
const config = readAgentRunClientConfig();
|
||||||
|
const { spec } = resolveAgentRunLaneTarget({});
|
||||||
|
const sessionPolicy = config.client.sessionPolicy;
|
||||||
|
const credentials = agentRunProviderCredentialRefs(spec, sessionPolicy.backendProfile);
|
||||||
|
const execution = defaultAgentRunExecutionPolicy(sessionPolicy.backendProfile, sessionPolicy);
|
||||||
|
const credentialSources = credentials.map((credential) => ({
|
||||||
|
profile: credential.profile,
|
||||||
|
secretRef: credential.secretRef,
|
||||||
|
valuesPrinted: false,
|
||||||
|
}));
|
||||||
|
return [
|
||||||
|
"KIND: session-policy",
|
||||||
|
`CONFIG: ${config.sourcePath}`,
|
||||||
|
`DEFAULT LANE: ${spec.nodeId}/${spec.lane}`,
|
||||||
|
`DEFAULTS: tenantId=${sessionPolicy.tenantId} projectId=${sessionPolicy.projectId} providerId=${sessionPolicy.providerId} backendProfile=${sessionPolicy.backendProfile}`,
|
||||||
|
`WORKSPACE: ${JSON.stringify(sessionPolicy.workspaceRef)}`,
|
||||||
|
`EXECUTION: ${JSON.stringify(execution)}`,
|
||||||
|
`PROVIDER CREDENTIAL SOURCES: ${JSON.stringify(credentialSources)}`,
|
||||||
|
"VALUES: secret payloads are not printed",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
function safeAgentRunEnvelope(envelope: Record<string, unknown>): Record<string, unknown> {
|
function safeAgentRunEnvelope(envelope: Record<string, unknown>): Record<string, unknown> {
|
||||||
return pickCompact(envelope, ["ok", "failureKind", "message", "code", "details", "valuesPrinted"]);
|
return pickCompact(envelope, ["ok", "failureKind", "message", "code", "details", "valuesPrinted"]);
|
||||||
}
|
}
|
||||||
@@ -5160,6 +5219,22 @@ function numberFieldFromRecord(obj: Record<string, unknown>, key: string, pathVa
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function positiveIntegerFieldFromRecord(obj: Record<string, unknown>, key: string, pathValue: string): number {
|
||||||
|
const value = obj[key];
|
||||||
|
if (!Number.isInteger(value) || Number(value) <= 0) throw new Error(`${pathValue}.${key} must be a positive integer`);
|
||||||
|
return Number(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function booleanFieldFromRecord(obj: Record<string, unknown>, key: string, pathValue: string): boolean {
|
||||||
|
const value = obj[key];
|
||||||
|
if (typeof value !== "boolean") throw new Error(`${pathValue}.${key} must be a boolean`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneJsonRecord(value: Record<string, unknown>): Record<string, unknown> {
|
||||||
|
return JSON.parse(JSON.stringify(value)) as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
interface AgentRunClientConfig {
|
interface AgentRunClientConfig {
|
||||||
sourcePath: string;
|
sourcePath: string;
|
||||||
manager: {
|
manager: {
|
||||||
@@ -5175,10 +5250,21 @@ interface AgentRunClientConfig {
|
|||||||
client: {
|
client: {
|
||||||
role: string;
|
role: string;
|
||||||
transport: string;
|
transport: string;
|
||||||
|
sessionPolicy: AgentRunSessionPolicyConfig;
|
||||||
};
|
};
|
||||||
publicExposure: AgentRunPublicExposure | null;
|
publicExposure: AgentRunPublicExposure | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AgentRunSessionPolicyConfig {
|
||||||
|
sourcePath: string;
|
||||||
|
tenantId: string;
|
||||||
|
projectId: string;
|
||||||
|
providerId: string;
|
||||||
|
backendProfile: string;
|
||||||
|
workspaceRef: Record<string, unknown>;
|
||||||
|
executionPolicy: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
interface AgentRunPublicExposure {
|
interface AgentRunPublicExposure {
|
||||||
enabled: true;
|
enabled: true;
|
||||||
proxyName: string;
|
proxyName: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user