mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 05:56:19 +01:00
Add the pinned Kimi Code runtime, native instruction and model admission, scoped credentials, and pending-candidate recovery contracts. Retain current master release attestations and provider-pack compatibility. Co-Authored-By: Paperclip <noreply@paperclip.ing>
1053 lines
53 KiB
TypeScript
1053 lines
53 KiB
TypeScript
import { resolveGrokDisableWebSearch } from "../drivers/acpx/grok-web-search.js";
|
|
import { resolveGrokEffort } from "../drivers/acpx/grok-effort.js";
|
|
import { resolveClaudeEffort } from "../drivers/acpx/claude-effort.js";
|
|
import type { DotBindingSnapshot, MuseBindingSnapshot, ExternalBotProviderKind } from "./external-provider.js";
|
|
import { QUALIFIED_ACPX_VERSION, QUALIFIED_ACPX_PROFILE_DATA } from "../drivers/acpx/generated-profiles.js";
|
|
import { isSupportedAcpxProfileVersion, type AcpxProfileVersion } from "../drivers/acpx/profile-compatibility.js";
|
|
import { isProviderMode } from "./provider-mode.js";
|
|
import { createHash } from "node:crypto";
|
|
import type { PrpStructuredRunResult, PrpTerminalState } from "../protocol/replay-contract.js";
|
|
import { explicitTaskSkillNames, parseNativeRuntimeContext, type NativeRuntimeContextSnapshot } from "./runtime-context.js";
|
|
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V1 = "paperclip.native-execution-input.v1" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V2 = "paperclip.native-execution-input.v2" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V3 = "paperclip.native-execution-input.v3" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V10 = "paperclip.native-execution-input.v10" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V9 = "paperclip.native-execution-input.v9" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V8 = "paperclip.native-execution-input.v8" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V6 = "paperclip.native-execution-input.v6" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V4 = "paperclip.native-execution-input.v4" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA = "paperclip.native-execution-input.v5" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V7 = "paperclip.native-execution-input.v7" as const;
|
|
export type NativeHomeMode = "isolated" | "preconfigured";
|
|
export const NATIVE_MODEL_ENVELOPE_SCHEMA_V1 = "paperclip.native-model-envelope.v1" as const;
|
|
export const NATIVE_MODEL_ENVELOPE_SCHEMA_V2 = "paperclip.native-model-envelope.v2" as const;
|
|
export const NATIVE_MODEL_ENVELOPE_SCHEMA = "paperclip.native-model-envelope.v3" as const;
|
|
export const NATIVE_SESSION_IDLE_TIMEOUT_MAX_MS = 86_400_000;
|
|
|
|
export type NativeExecutionMode = "default" | "plan";
|
|
|
|
export interface NativePlanningContext {
|
|
documentId: string | null;
|
|
baseRevisionId: string | null;
|
|
baseRevisionNumber: number;
|
|
markdown: string;
|
|
sha256: string;
|
|
reviewContext: Record<string, unknown>;
|
|
}
|
|
|
|
export interface StrictCompletionContractInput {
|
|
revision: string;
|
|
objective: string;
|
|
criteria: Array<{ id: string; requirement: string }>;
|
|
}
|
|
|
|
export interface NativeInteractionResponseEnvelope {
|
|
interactionId: string;
|
|
kind:
|
|
| "connection_intent"
|
|
| "suggest_tasks"
|
|
| "ask_user_questions"
|
|
| "request_confirmation"
|
|
| "request_checkbox_confirmation"
|
|
| "request_item_verdicts";
|
|
response: Record<string, unknown>;
|
|
}
|
|
|
|
export interface NativeCredentialBindingRef {
|
|
bindingId: string;
|
|
service: string;
|
|
destination: string;
|
|
expiresAt: string | null;
|
|
displayName: string | null;
|
|
}
|
|
|
|
export type NativeSessionLifecyclePolicy =
|
|
| { mode: "per_turn"; idleTimeoutMs: null }
|
|
| { mode: "warm"; idleTimeoutMs: number };
|
|
|
|
export interface NativeManagedAgentProfileSnapshot {
|
|
profileId: string;
|
|
anthropicAgentId: string;
|
|
agentVersion: string;
|
|
environmentId: string;
|
|
betaVersion: "managed-agents-2026-04-01";
|
|
}
|
|
|
|
export interface NativeAwsAgentCoreProfileSnapshot {
|
|
profileId: string;
|
|
region: string;
|
|
accountId: string;
|
|
harnessArn: string;
|
|
harnessVersion: string;
|
|
endpointArn: string;
|
|
endpointQualifier: string;
|
|
agentRuntimeArn: string;
|
|
memoryArn: string;
|
|
memoryId: string;
|
|
invocationRoleArn: string;
|
|
contextBucket: string;
|
|
contextPrefix: string;
|
|
contextKmsKeyArn: string;
|
|
qualificationRevision: string;
|
|
eventExpiryDays: 90;
|
|
}
|
|
|
|
export type NativeAcpxAgent = keyof typeof QUALIFIED_ACPX_PROFILE_DATA;
|
|
|
|
function isNativeAcpxAgent(value: unknown): value is NativeAcpxAgent {
|
|
return typeof value === "string" && Object.hasOwn(QUALIFIED_ACPX_PROFILE_DATA, value);
|
|
}
|
|
export type NativeCodexApprovalPolicy = "never" | "on-request" | "untrusted";
|
|
export type NativeOpenCodePermissionMode = "allow" | "ask" | "deny";
|
|
export type NativeAcpxPermissionMode = "approve-all" | "approve-paperclip" | "approve-reads" | "deny-all";
|
|
export type NativeCursorMode = "agent" | "plan" | "ask";
|
|
|
|
/** Decodable history; current immutable admission comes from the shared manifest. */
|
|
export type NativeAcpxProfileVersion = AcpxProfileVersion;
|
|
|
|
export interface NativeAcpxProfileSnapshot {
|
|
driverKind: "acpx_runtime";
|
|
protocolVersion: 1;
|
|
acpxVersion: typeof QUALIFIED_ACPX_VERSION;
|
|
agent: NativeAcpxAgent;
|
|
agentProfileVersion: AcpxProfileVersion;
|
|
agentServerPackage: string;
|
|
agentServerVersion: string;
|
|
agentRuntimePackage: string | null;
|
|
agentRuntimeVersion: string | null;
|
|
commandDigest: string;
|
|
}
|
|
|
|
export type NativeProviderConfig =
|
|
| { kind: "codex"; model: string | null; approvalPolicy?: NativeCodexApprovalPolicy }
|
|
| { kind: "opencode"; model: string; permissionMode?: NativeOpenCodePermissionMode }
|
|
| {
|
|
kind: "claude_managed";
|
|
model: string;
|
|
managedProfile: NativeManagedAgentProfileSnapshot;
|
|
maxSessionListCostUsd: number;
|
|
}
|
|
| {
|
|
kind: "aws_agentcore";
|
|
model: string;
|
|
agentCoreProfile: NativeAwsAgentCoreProfileSnapshot;
|
|
maxEstimatedSessionCostUsd: number;
|
|
invocationLimits: {
|
|
maxIterations: number;
|
|
maxOutputTokens: number;
|
|
timeoutSeconds: number;
|
|
};
|
|
}
|
|
| {
|
|
kind: "acpx";
|
|
agent: NativeAcpxAgent;
|
|
model: string;
|
|
permissionMode?: NativeAcpxPermissionMode;
|
|
mode?: string;
|
|
piThinkingLevel?: "off" | "low" | "high" | "max";
|
|
/** Present only in persisted v1-v3 inputs. */
|
|
permissionPolicy?: "interactive";
|
|
profile: NativeAcpxProfileSnapshot;
|
|
};
|
|
|
|
export type NativeProviderConfigV4 =
|
|
| { kind: "codex"; model: string | null; approvalPolicy: NativeCodexApprovalPolicy }
|
|
| { kind: "opencode"; model: string; permissionMode: NativeOpenCodePermissionMode }
|
|
| Extract<NativeProviderConfig, { kind: "claude_managed" | "aws_agentcore" }>
|
|
| {
|
|
kind: "acpx";
|
|
agent: NativeAcpxAgent;
|
|
model: string;
|
|
permissionMode: NativeAcpxPermissionMode;
|
|
mode?: string;
|
|
piThinkingLevel?: "off" | "low" | "high" | "max";
|
|
profile: NativeAcpxProfileSnapshot;
|
|
};
|
|
|
|
export type NativeProviderConfigV5 =
|
|
| Exclude<NativeProviderConfigV4, { kind: "codex" }>
|
|
| (Extract<NativeProviderConfigV4, { kind: "codex" }> & { reasoningEffort?: string; webSearch?: "disabled" });
|
|
|
|
export interface NativeExecutionInputV1 {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V1;
|
|
binding: {
|
|
companyId: string;
|
|
runId: string;
|
|
issueId: string;
|
|
agentId: string;
|
|
agentKeyId?: string;
|
|
executionWorkspaceId: string;
|
|
};
|
|
task: {
|
|
identifier: string;
|
|
title: string;
|
|
description: string | null;
|
|
/** Redacted, server-authored task markdown including the current wake comment. */
|
|
prompt: string;
|
|
workMode: "standard";
|
|
};
|
|
workspace: {
|
|
cwd: string;
|
|
repoUrl: string | null;
|
|
repoRef: string | null;
|
|
branchName: string | null;
|
|
};
|
|
session: {
|
|
normalizedSessionId: string | null;
|
|
driverKind: "codex_app_server" | "opencode_server" | "claude_managed_agents_api" | "aws_agentcore_harness_api" | "acpx_runtime";
|
|
protocolVersion: 1;
|
|
lifecyclePolicy: NativeSessionLifecyclePolicy;
|
|
};
|
|
provider: NativeProviderConfig;
|
|
completionContract: {
|
|
id: string;
|
|
sha256: string;
|
|
schemaVersion: string;
|
|
contract: StrictCompletionContractInput;
|
|
};
|
|
interactionResponses: NativeInteractionResponseEnvelope[];
|
|
credentialBindings: NativeCredentialBindingRef[];
|
|
}
|
|
|
|
export interface NativeExecutionInputV2 extends Omit<NativeExecutionInputV1, "schema" | "task"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V2;
|
|
executionMode: NativeExecutionMode;
|
|
task: Omit<NativeExecutionInputV1["task"], "workMode"> & {
|
|
workMode: "standard" | "planning" | "ask" | "skill_test";
|
|
};
|
|
planningContext: NativePlanningContext | null;
|
|
}
|
|
|
|
export interface NativeExecutionInputV3 extends Omit<NativeExecutionInputV2, "schema"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V3;
|
|
runtimeContext: NativeRuntimeContextSnapshot;
|
|
}
|
|
|
|
export interface NativeExecutionInputV4 extends Omit<NativeExecutionInputV3, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V4;
|
|
provider: NativeProviderConfigV4;
|
|
/** Used only after the runtime proves provider-session recovery succeeded. */
|
|
continuationPrompt?: string | null;
|
|
/** Restored only when starting a fresh provider session, never on a resume. */
|
|
initialCommunicationGuidance?: string | null;
|
|
}
|
|
|
|
/** Identity and content revision supplied by the builder that owns the source. */
|
|
export interface NativeCompletionSource {
|
|
kind: "description" | "comment";
|
|
id: string;
|
|
revision: string;
|
|
}
|
|
|
|
export interface NativeCompletionSources {
|
|
/** References are valid only for this exact rendered prompt and contract revision. */
|
|
promptSha256: string;
|
|
contractRevision: string;
|
|
criteria: Array<{ id: string; source: NativeCompletionSource }>;
|
|
}
|
|
|
|
export interface NativeExecutionInputV5 extends Omit<NativeExecutionInputV4, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA;
|
|
provider: NativeProviderConfigV5;
|
|
completionSources?: NativeCompletionSources;
|
|
}
|
|
|
|
export interface NativeExecutionInputV6 extends Omit<NativeExecutionInputV5, "schema" | "provider" | "workspace" | "session"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V6;
|
|
provider: { kind: ExternalBotProviderKind; model: null; binding: DotBindingSnapshot };
|
|
workspace: { access: "none"; cwd: null; repoUrl: null; repoRef: null; branchName: null };
|
|
session: Omit<NativeExecutionInputV5["session"], "driverKind"> & { driverKind: "openai_dot_mcp" | "grok_bot_mcp" };
|
|
}
|
|
|
|
export interface NativeExecutionInputV7 extends Omit<NativeExecutionInputV5, "schema"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V7;
|
|
homeMode: NativeHomeMode;
|
|
}
|
|
|
|
export type NativeProviderConfigV9 = Exclude<NativeProviderConfigV5, { kind: "acpx" }>
|
|
| (Extract<NativeProviderConfigV5, { kind: "acpx" }> & { claudeEffort?: "low" | "medium" | "high" | "xhigh" | "max" });
|
|
export interface NativeExecutionInputV9 extends Omit<NativeExecutionInputV7, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V9;
|
|
provider: NativeProviderConfigV9;
|
|
}
|
|
|
|
export type NativeProviderConfigV10 = Exclude<NativeProviderConfigV9, { kind: "acpx" }>
|
|
| (Extract<NativeProviderConfigV9, { kind: "acpx" }> & { grokEffort?: "minimal" | "low" | "medium" | "high" | "xhigh"; grokDisableWebSearch?: boolean });
|
|
export interface NativeExecutionInputV10 extends Omit<NativeExecutionInputV9, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V10;
|
|
provider: NativeProviderConfigV10;
|
|
}
|
|
|
|
export interface NativeExecutionInputV8 extends Omit<NativeExecutionInputV6, "schema" | "provider" | "session"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V8;
|
|
provider: { kind: "muse"; model: null; binding: MuseBindingSnapshot; bridgeRevision: "muse-v1" };
|
|
session: Omit<NativeExecutionInputV6["session"], "driverKind"> & { driverKind: "muse_external" };
|
|
}
|
|
|
|
export type NativeExecutionInput = NativeExecutionInputV1 | NativeExecutionInputV2 | NativeExecutionInputV3 | NativeExecutionInputV4 | NativeExecutionInputV5 | NativeExecutionInputV6 | NativeExecutionInputV7 | NativeExecutionInputV8 | NativeExecutionInputV9 | NativeExecutionInputV10;
|
|
|
|
/** The only task data that may enter provider-visible model input. */
|
|
export interface NativeModelEnvelopeV1 {
|
|
schema: typeof NATIVE_MODEL_ENVELOPE_SCHEMA_V1;
|
|
task: NativeExecutionInputV1["task"];
|
|
/** Remote providers have no Paperclip workspace mounted in their service. */
|
|
workspace: Pick<NativeExecutionInputV1["workspace"], "cwd"> | null;
|
|
completionContract: StrictCompletionContractInput;
|
|
interactionResponses: NativeInteractionResponseEnvelope[];
|
|
}
|
|
|
|
export interface NativeModelEnvelopeV2 {
|
|
schema: typeof NATIVE_MODEL_ENVELOPE_SCHEMA_V2;
|
|
task: NativeExecutionInputV2["task"];
|
|
executionMode: NativeExecutionMode;
|
|
planningContext: NativePlanningContext | null;
|
|
workspace: Pick<NativeExecutionInputV2["workspace"], "cwd"> | null;
|
|
completionContract: StrictCompletionContractInput;
|
|
interactionResponses: NativeInteractionResponseEnvelope[];
|
|
}
|
|
|
|
|
|
/** Explicit model projection; execution-only fields never enter the model task. */
|
|
export interface NativeModelEnvelopeV3 extends Omit<NativeModelEnvelopeV2, "schema" | "task" | "completionContract"> {
|
|
requestedSkills: string[];
|
|
constraints?: string[];
|
|
schema: typeof NATIVE_MODEL_ENVELOPE_SCHEMA;
|
|
task: Pick<NativeExecutionInputV2["task"], "identifier" | "title" | "prompt" | "workMode">;
|
|
completionContract: Omit<StrictCompletionContractInput, "criteria"> & {
|
|
criteria: Array<{ id: string; requirement: string } | { id: string; source: NativeCompletionSource & { location: "task.prompt" } }>;
|
|
};
|
|
}
|
|
|
|
export interface NativeSessionExecutionResult {
|
|
result: PrpStructuredRunResult;
|
|
terminal: PrpTerminalState;
|
|
turnId: string | null;
|
|
normalizedSessionId: string;
|
|
providerSessionId: string | null;
|
|
driverKind: string;
|
|
driverVersion: string;
|
|
nativeEventCount: number;
|
|
highestContiguousSourceSeq: number;
|
|
usage: Record<string, unknown> | null;
|
|
/** The active durable goal reached a safe turn boundary for run rollover. */
|
|
goalRolloverRequired?: boolean;
|
|
}
|
|
|
|
export class NativeExecutionInputError extends Error {
|
|
readonly code = "native_execution_input_invalid" as const;
|
|
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = "NativeExecutionInputError";
|
|
}
|
|
}
|
|
|
|
function record(value: unknown, path: string): Record<string, unknown> {
|
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
throw new NativeExecutionInputError(`${path} must be an object`);
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function exactKeys(value: Record<string, unknown>, keys: readonly string[], path: string): void {
|
|
const allowed = new Set(keys);
|
|
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
if (unknown.length > 0) {
|
|
throw new NativeExecutionInputError(`${path} contains unknown field ${unknown[0]}`);
|
|
}
|
|
}
|
|
|
|
function text(value: unknown, path: string): string {
|
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
throw new NativeExecutionInputError(`${path} must be a non-empty string`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function nullableText(value: unknown, path: string): string | null {
|
|
return value === null ? null : text(value, path);
|
|
}
|
|
|
|
/**
|
|
* Strictly validates the closed Paperclip-to-runner launch contract. This is
|
|
* intentionally not an extensible metadata bag: new fields require a contract
|
|
* revision and an explicit security review.
|
|
*/
|
|
function parseExternalNativeExecutionInput(input: Record<string, unknown>): NativeExecutionInputV6 {
|
|
const provider = record(input.provider, "input.provider");
|
|
exactKeys(provider, ["kind", "model", "binding"], "input.provider");
|
|
const b = record(provider.binding, "input.provider.binding");
|
|
exactKeys(b, ["bindingId", "bindingGeneration", "companyId", "agentId", "acceptByUnixMs", "expiresAtUnixMs"], "input.provider.binding");
|
|
const binding = record(input.binding, "input.binding");
|
|
const workspace = record(input.workspace, "input.workspace");
|
|
exactKeys(workspace, ["access", "cwd", "repoUrl", "repoRef", "branchName"], "input.workspace");
|
|
const session = record(input.session, "input.session");
|
|
const driverKind = provider.kind === "grok_bot" ? "grok_bot_mcp" : "openai_dot_mcp";
|
|
if (!["openai_dot", "grok_bot"].includes(String(provider.kind)) || provider.model !== null || workspace.access !== "none"
|
|
|| [workspace.cwd, workspace.repoUrl, workspace.repoRef, workspace.branchName].some(v => v !== null)
|
|
|| session.driverKind !== driverKind || record(session.lifecyclePolicy, "input.session.lifecyclePolicy").mode !== "per_turn"
|
|
|| binding.companyId !== b.companyId || binding.agentId !== b.agentId
|
|
|| !Number.isSafeInteger(b.bindingGeneration) || Number(b.bindingGeneration) < 1
|
|
|| !Number.isSafeInteger(b.acceptByUnixMs) || !Number.isSafeInteger(b.expiresAtUnixMs)
|
|
|| Number(b.acceptByUnixMs) < 1 || Number(b.acceptByUnixMs) >= Number(b.expiresAtUnixMs)
|
|
|| Number(b.expiresAtUnixMs) - Number(b.acceptByUnixMs) > 86_400_000
|
|
|| !Array.isArray(input.credentialBindings) || input.credentialBindings.length !== 0) {
|
|
throw new NativeExecutionInputError("External v6 requires a bound per-turn remote service with no filesystem or credential access");
|
|
}
|
|
// Reuse the preceding closed contract's task/context validation. The local
|
|
// validation value never leaves this function or becomes an execution path.
|
|
const common = parseNativeExecutionInput({ ...input, schema: NATIVE_EXECUTION_INPUT_SCHEMA,
|
|
provider: { kind: "codex", model: null, approvalPolicy: "never" },
|
|
session: { ...session, driverKind: "codex_app_server" },
|
|
workspace: { cwd: "/", repoUrl: null, repoRef: null, branchName: null } }) as NativeExecutionInputV5;
|
|
return { ...common, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V6,
|
|
provider: { kind: provider.kind as ExternalBotProviderKind, model: null, binding: {
|
|
bindingId: text(b.bindingId, "input.provider.binding.bindingId"), bindingGeneration: Number(b.bindingGeneration),
|
|
companyId: text(b.companyId, "input.provider.binding.companyId"), agentId: text(b.agentId, "input.provider.binding.agentId"),
|
|
acceptByUnixMs: Number(b.acceptByUnixMs), expiresAtUnixMs: Number(b.expiresAtUnixMs),
|
|
} }, workspace: { access: "none", cwd: null, repoUrl: null, repoRef: null, branchName: null },
|
|
session: { ...common.session, driverKind } };
|
|
}
|
|
|
|
function parseMuseNativeExecutionInput(input: Record<string, unknown>): NativeExecutionInputV8 {
|
|
const provider = record(input.provider, "input.provider");
|
|
exactKeys(provider, ["kind", "model", "binding", "bridgeRevision"], "input.provider");
|
|
const session = record(input.session, "input.session");
|
|
if (provider.kind !== "muse" || provider.bridgeRevision !== "muse-v1" || session.driverKind !== "muse_external") {
|
|
throw new NativeExecutionInputError("Muse v8 requires the muse-v1 bridge and muse_external driver");
|
|
}
|
|
const common = parseExternalNativeExecutionInput({ ...input, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V6,
|
|
provider: { kind: "openai_dot", model: provider.model, binding: provider.binding },
|
|
session: { ...session, driverKind: "openai_dot_mcp" } });
|
|
return { ...common, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V8,
|
|
provider: { kind: "muse", model: null, binding: common.provider.binding, bridgeRevision: "muse-v1" },
|
|
session: { ...common.session, driverKind: "muse_external" } };
|
|
}
|
|
|
|
export function parseNativeExecutionInput(value: unknown): NativeExecutionInput {
|
|
const input = record(value, "input");
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V8) return parseMuseNativeExecutionInput(input);
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V6) return parseExternalNativeExecutionInput(input);
|
|
const isV10 = input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V10;
|
|
const isV9 = isV10 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V9;
|
|
const isV7 = isV9 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V7;
|
|
const isV5 = isV7 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA;
|
|
const isV4 = isV5 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V4;
|
|
const isV3 = isV4 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V3;
|
|
const isV2 = isV3 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V2;
|
|
exactKeys(input, [
|
|
"schema",
|
|
"binding",
|
|
"task",
|
|
"workspace",
|
|
"session",
|
|
"provider",
|
|
"completionContract",
|
|
"interactionResponses",
|
|
"credentialBindings",
|
|
...(isV2 ? ["executionMode", "planningContext"] : []),
|
|
...(isV3 ? ["runtimeContext"] : []),
|
|
...(isV4 ? ["continuationPrompt", "initialCommunicationGuidance"] : []),
|
|
...(isV5 ? ["completionSources"] : []),
|
|
...(isV7 ? ["homeMode"] : []),
|
|
], "input");
|
|
if (!isV2 && input.schema !== NATIVE_EXECUTION_INPUT_SCHEMA_V1) {
|
|
throw new NativeExecutionInputError(
|
|
`input.schema must be ${NATIVE_EXECUTION_INPUT_SCHEMA_V1}, ${NATIVE_EXECUTION_INPUT_SCHEMA_V2}, ${NATIVE_EXECUTION_INPUT_SCHEMA_V3}, ${NATIVE_EXECUTION_INPUT_SCHEMA_V4}, or ${NATIVE_EXECUTION_INPUT_SCHEMA}`,
|
|
);
|
|
}
|
|
|
|
if (isV7) {
|
|
if (input.homeMode !== "isolated" && input.homeMode !== "preconfigured") {
|
|
throw new NativeExecutionInputError("input.homeMode must be isolated or preconfigured");
|
|
}
|
|
const selected = record(input.provider, "input.provider");
|
|
if (input.homeMode === "preconfigured" && selected.kind === "acpx" && input.executionMode === "plan") {
|
|
throw new NativeExecutionInputError("preconfigured ACPX read-only execution is not supported; use isolated mode or native Codex");
|
|
}
|
|
if (input.homeMode === "preconfigured" && selected.kind === "acpx" && selected.permissionMode !== "approve-all") {
|
|
throw new NativeExecutionInputError("preconfigured home currently requires ACPX approve-all; local permission rules still apply");
|
|
}
|
|
if (input.homeMode === "preconfigured" && selected.kind === "acpx" && selected.agent === "codex") {
|
|
throw new NativeExecutionInputError("preconfigured ACPX Codex cannot protect Runner state; use native Codex or isolated mode");
|
|
}
|
|
if (input.homeMode === "preconfigured" && selected.kind !== "codex"
|
|
&& !(selected.kind === "acpx" && selected.agent === "claude")) {
|
|
throw new NativeExecutionInputError("preconfigured home supports native Codex and ACPX Claude only");
|
|
}
|
|
}
|
|
const binding = record(input.binding, "input.binding");
|
|
exactKeys(binding, ["companyId", "runId", "issueId", "agentId", "agentKeyId", "executionWorkspaceId"], "input.binding");
|
|
const task = record(input.task, "input.task");
|
|
exactKeys(task, ["identifier", "title", "description", "prompt", "workMode"], "input.task");
|
|
const workspace = record(input.workspace, "input.workspace");
|
|
exactKeys(workspace, ["cwd", "repoUrl", "repoRef", "branchName"], "input.workspace");
|
|
const session = record(input.session, "input.session");
|
|
exactKeys(session, ["normalizedSessionId", "driverKind", "protocolVersion", "lifecyclePolicy"], "input.session");
|
|
const completionContract = record(input.completionContract, "input.completionContract");
|
|
exactKeys(completionContract, ["id", "sha256", "schemaVersion", "contract"], "input.completionContract");
|
|
const contract = record(completionContract.contract, "input.completionContract.contract");
|
|
exactKeys(contract, ["revision", "objective", "criteria"], "input.completionContract.contract");
|
|
|
|
if (task.workMode !== "standard" && (!isV2 || task.workMode !== "planning" && task.workMode !== "ask" && task.workMode !== "skill_test")) {
|
|
throw new NativeExecutionInputError("input.task.workMode must be standard, planning, ask, or skill_test");
|
|
}
|
|
const executionMode: NativeExecutionMode = isV2 && input.executionMode === "plan" ? "plan" : "default";
|
|
if (isV2 && input.executionMode !== "default" && input.executionMode !== "plan") {
|
|
throw new NativeExecutionInputError("input.executionMode must be default or plan");
|
|
}
|
|
if (isV2 && task.workMode !== "planning" && executionMode === "plan") {
|
|
throw new NativeExecutionInputError("plan execution mode requires planning work mode");
|
|
}
|
|
let planningContext: NativePlanningContext | null = null;
|
|
if (isV2 && input.planningContext !== null) {
|
|
const context = record(input.planningContext, "input.planningContext");
|
|
exactKeys(context, [
|
|
"documentId", "baseRevisionId", "baseRevisionNumber", "markdown", "sha256", "reviewContext",
|
|
], "input.planningContext");
|
|
if (!Number.isSafeInteger(context.baseRevisionNumber) || Number(context.baseRevisionNumber) < 0) {
|
|
throw new NativeExecutionInputError("input.planningContext.baseRevisionNumber must be a non-negative integer");
|
|
}
|
|
planningContext = {
|
|
documentId: context.documentId === null ? null : text(context.documentId, "input.planningContext.documentId"),
|
|
baseRevisionId: context.baseRevisionId === null
|
|
? null
|
|
: text(context.baseRevisionId, "input.planningContext.baseRevisionId"),
|
|
baseRevisionNumber: Number(context.baseRevisionNumber),
|
|
markdown: typeof context.markdown === "string"
|
|
? context.markdown
|
|
: (() => { throw new NativeExecutionInputError("input.planningContext.markdown must be a string"); })(),
|
|
sha256: text(context.sha256, "input.planningContext.sha256"),
|
|
reviewContext: structuredClone(record(context.reviewContext, "input.planningContext.reviewContext")),
|
|
};
|
|
}
|
|
if (isV2 && executionMode === "plan" && planningContext === null) {
|
|
throw new NativeExecutionInputError("input.planningContext is required in plan execution mode");
|
|
}
|
|
if (isV2 && executionMode === "default" && input.planningContext !== null) {
|
|
throw new NativeExecutionInputError("input.planningContext is only valid in plan execution mode");
|
|
}
|
|
if (
|
|
(
|
|
session.driverKind !== "codex_app_server"
|
|
&& session.driverKind !== "opencode_server"
|
|
&& session.driverKind !== "claude_managed_agents_api"
|
|
&& session.driverKind !== "aws_agentcore_harness_api"
|
|
&& session.driverKind !== "acpx_runtime"
|
|
)
|
|
|| session.protocolVersion !== 1
|
|
) {
|
|
throw new NativeExecutionInputError("input.session must select a supported protocol version 1 driver");
|
|
}
|
|
const driverKind = session.driverKind as NativeExecutionInputV1["session"]["driverKind"];
|
|
const lifecyclePolicyValue = session.lifecyclePolicy === undefined
|
|
? { mode: "per_turn", idleTimeoutMs: null }
|
|
: record(session.lifecyclePolicy, "input.session.lifecyclePolicy");
|
|
exactKeys(lifecyclePolicyValue, ["mode", "idleTimeoutMs"], "input.session.lifecyclePolicy");
|
|
let lifecyclePolicy: NativeSessionLifecyclePolicy;
|
|
if (lifecyclePolicyValue.mode === "per_turn") {
|
|
if (lifecyclePolicyValue.idleTimeoutMs !== null) {
|
|
throw new NativeExecutionInputError("input.session.lifecyclePolicy.idleTimeoutMs must be null for per_turn");
|
|
}
|
|
lifecyclePolicy = { mode: "per_turn", idleTimeoutMs: null };
|
|
} else if (lifecyclePolicyValue.mode === "warm") {
|
|
if (
|
|
!Number.isSafeInteger(lifecyclePolicyValue.idleTimeoutMs)
|
|
|| Number(lifecyclePolicyValue.idleTimeoutMs) <= 0
|
|
|| Number(lifecyclePolicyValue.idleTimeoutMs) > NATIVE_SESSION_IDLE_TIMEOUT_MAX_MS
|
|
) {
|
|
throw new NativeExecutionInputError(
|
|
`input.session.lifecyclePolicy.idleTimeoutMs must be a positive integer no greater than ${NATIVE_SESSION_IDLE_TIMEOUT_MAX_MS} for warm`,
|
|
);
|
|
}
|
|
lifecyclePolicy = { mode: "warm", idleTimeoutMs: Number(lifecyclePolicyValue.idleTimeoutMs) };
|
|
} else {
|
|
throw new NativeExecutionInputError("input.session.lifecyclePolicy.mode must be per_turn or warm");
|
|
}
|
|
const provider = input.provider === undefined
|
|
? { kind: "codex", model: null }
|
|
: record(input.provider, "input.provider");
|
|
if (
|
|
provider.kind !== "codex"
|
|
&& provider.kind !== "opencode"
|
|
&& provider.kind !== "claude_managed"
|
|
&& provider.kind !== "aws_agentcore"
|
|
&& provider.kind !== "acpx"
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.kind must be codex, opencode, claude_managed, aws_agentcore, or acpx");
|
|
}
|
|
exactKeys(
|
|
provider,
|
|
provider.kind === "claude_managed"
|
|
? ["kind", "model", "managedProfile", "maxSessionListCostUsd"]
|
|
: provider.kind === "aws_agentcore"
|
|
? ["kind", "model", "agentCoreProfile", "maxEstimatedSessionCostUsd", "invocationLimits"]
|
|
: provider.kind === "acpx"
|
|
? ["kind", "agent", "model", isV4 ? "permissionMode" : "permissionPolicy", "profile", "piThinkingLevel", ...(isV4 ? ["mode"] : []), ...(isV9 ? ["claudeEffort"] : []), ...(isV10 ? ["grokEffort", "grokDisableWebSearch"] : [])]
|
|
: provider.kind === "codex" && isV4
|
|
? ["kind", "model", "approvalPolicy", ...(isV5 ? ["reasoningEffort", "webSearch"] : [])]
|
|
: provider.kind === "opencode" && isV4
|
|
? ["kind", "model", "permissionMode"]
|
|
: ["kind", "model"],
|
|
"input.provider",
|
|
);
|
|
if (
|
|
(provider.kind === "codex" && session.driverKind !== "codex_app_server")
|
|
|| (provider.kind === "opencode" && session.driverKind !== "opencode_server")
|
|
|| (provider.kind === "claude_managed" && session.driverKind !== "claude_managed_agents_api")
|
|
|| (provider.kind === "aws_agentcore" && session.driverKind !== "aws_agentcore_harness_api")
|
|
|| (provider.kind === "acpx" && session.driverKind !== "acpx_runtime")
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.kind does not match input.session.driverKind");
|
|
}
|
|
const providerModel = provider.model === null || provider.model === undefined
|
|
? null
|
|
: text(provider.model, "input.provider.model");
|
|
if (provider.kind === "opencode" && (providerModel === null || !providerModel.includes("/"))) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for opencode in provider/model form");
|
|
}
|
|
let parsedProvider: NativeProviderConfigV9 | NativeProviderConfig | NativeProviderConfigV5;
|
|
if (provider.kind === "claude_managed") {
|
|
if (providerModel === null) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for claude_managed");
|
|
}
|
|
const managedProfile = record(provider.managedProfile, "input.provider.managedProfile");
|
|
exactKeys(
|
|
managedProfile,
|
|
["profileId", "anthropicAgentId", "agentVersion", "environmentId", "betaVersion"],
|
|
"input.provider.managedProfile",
|
|
);
|
|
if (managedProfile.betaVersion !== "managed-agents-2026-04-01") {
|
|
throw new NativeExecutionInputError(
|
|
"input.provider.managedProfile.betaVersion must be managed-agents-2026-04-01",
|
|
);
|
|
}
|
|
if (
|
|
typeof provider.maxSessionListCostUsd !== "number"
|
|
|| !Number.isFinite(provider.maxSessionListCostUsd)
|
|
|| provider.maxSessionListCostUsd <= 0
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.maxSessionListCostUsd must be a positive finite number");
|
|
}
|
|
parsedProvider = {
|
|
kind: "claude_managed",
|
|
model: providerModel,
|
|
managedProfile: {
|
|
profileId: text(managedProfile.profileId, "input.provider.managedProfile.profileId"),
|
|
anthropicAgentId: text(
|
|
managedProfile.anthropicAgentId,
|
|
"input.provider.managedProfile.anthropicAgentId",
|
|
),
|
|
agentVersion: text(managedProfile.agentVersion, "input.provider.managedProfile.agentVersion"),
|
|
environmentId: text(managedProfile.environmentId, "input.provider.managedProfile.environmentId"),
|
|
betaVersion: "managed-agents-2026-04-01",
|
|
},
|
|
maxSessionListCostUsd: provider.maxSessionListCostUsd,
|
|
};
|
|
} else if (provider.kind === "aws_agentcore") {
|
|
if (providerModel === null) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for aws_agentcore");
|
|
}
|
|
const profile = record(provider.agentCoreProfile, "input.provider.agentCoreProfile");
|
|
exactKeys(profile, [
|
|
"profileId", "region", "accountId", "harnessArn", "harnessVersion", "endpointArn",
|
|
"endpointQualifier", "agentRuntimeArn", "memoryArn", "memoryId", "invocationRoleArn",
|
|
"contextBucket", "contextPrefix", "contextKmsKeyArn", "qualificationRevision", "eventExpiryDays",
|
|
], "input.provider.agentCoreProfile");
|
|
if (profile.eventExpiryDays !== 90) {
|
|
throw new NativeExecutionInputError("input.provider.agentCoreProfile.eventExpiryDays must be 90");
|
|
}
|
|
if (
|
|
typeof provider.maxEstimatedSessionCostUsd !== "number"
|
|
|| !Number.isFinite(provider.maxEstimatedSessionCostUsd)
|
|
|| provider.maxEstimatedSessionCostUsd <= 0
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.maxEstimatedSessionCostUsd must be a positive finite number");
|
|
}
|
|
const limits = record(provider.invocationLimits, "input.provider.invocationLimits");
|
|
exactKeys(limits, ["maxIterations", "maxOutputTokens", "timeoutSeconds"], "input.provider.invocationLimits");
|
|
const positiveInteger = (value: unknown, path: string): number => {
|
|
if (!Number.isSafeInteger(value) || Number(value) <= 0) {
|
|
throw new NativeExecutionInputError(`${path} must be a positive integer`);
|
|
}
|
|
return Number(value);
|
|
};
|
|
const maxIterations = positiveInteger(limits.maxIterations, "input.provider.invocationLimits.maxIterations");
|
|
const maxOutputTokens = positiveInteger(limits.maxOutputTokens, "input.provider.invocationLimits.maxOutputTokens");
|
|
const timeoutSeconds = positiveInteger(limits.timeoutSeconds, "input.provider.invocationLimits.timeoutSeconds");
|
|
if (maxIterations > 8) throw new NativeExecutionInputError("input.provider.invocationLimits.maxIterations exceeds 8");
|
|
if (maxOutputTokens > 4096) throw new NativeExecutionInputError("input.provider.invocationLimits.maxOutputTokens exceeds 4096");
|
|
if (timeoutSeconds > 300) throw new NativeExecutionInputError("input.provider.invocationLimits.timeoutSeconds exceeds 300");
|
|
parsedProvider = {
|
|
kind: "aws_agentcore",
|
|
model: providerModel,
|
|
agentCoreProfile: {
|
|
profileId: text(profile.profileId, "input.provider.agentCoreProfile.profileId"),
|
|
region: text(profile.region, "input.provider.agentCoreProfile.region"),
|
|
accountId: text(profile.accountId, "input.provider.agentCoreProfile.accountId"),
|
|
harnessArn: text(profile.harnessArn, "input.provider.agentCoreProfile.harnessArn"),
|
|
harnessVersion: text(profile.harnessVersion, "input.provider.agentCoreProfile.harnessVersion"),
|
|
endpointArn: text(profile.endpointArn, "input.provider.agentCoreProfile.endpointArn"),
|
|
endpointQualifier: text(profile.endpointQualifier, "input.provider.agentCoreProfile.endpointQualifier"),
|
|
agentRuntimeArn: text(profile.agentRuntimeArn, "input.provider.agentCoreProfile.agentRuntimeArn"),
|
|
memoryArn: text(profile.memoryArn, "input.provider.agentCoreProfile.memoryArn"),
|
|
memoryId: text(profile.memoryId, "input.provider.agentCoreProfile.memoryId"),
|
|
invocationRoleArn: text(profile.invocationRoleArn, "input.provider.agentCoreProfile.invocationRoleArn"),
|
|
contextBucket: text(profile.contextBucket, "input.provider.agentCoreProfile.contextBucket"),
|
|
contextPrefix: text(profile.contextPrefix, "input.provider.agentCoreProfile.contextPrefix"),
|
|
contextKmsKeyArn: text(profile.contextKmsKeyArn, "input.provider.agentCoreProfile.contextKmsKeyArn"),
|
|
qualificationRevision: text(profile.qualificationRevision, "input.provider.agentCoreProfile.qualificationRevision"),
|
|
eventExpiryDays: 90,
|
|
},
|
|
maxEstimatedSessionCostUsd: provider.maxEstimatedSessionCostUsd,
|
|
invocationLimits: { maxIterations, maxOutputTokens, timeoutSeconds },
|
|
};
|
|
} else if (provider.kind === "acpx") {
|
|
if (provider.piThinkingLevel !== undefined && (provider.agent !== "pi" || (typeof provider.piThinkingLevel !== "string" || !["off", "low", "high", "max"].includes(provider.piThinkingLevel)))) {
|
|
throw new NativeExecutionInputError("input.provider.piThinkingLevel must be off, low, high, or max and is supported only for Pi");
|
|
}
|
|
if (provider.mode !== undefined && !isProviderMode(provider.mode)) {
|
|
throw new NativeExecutionInputError("input.provider.mode must be a bounded nonempty provider mode identifier");
|
|
}
|
|
if (providerModel === null) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for acpx");
|
|
}
|
|
if (!isNativeAcpxAgent(provider.agent)) {
|
|
throw new NativeExecutionInputError("input.provider.agent must name a registered ACPX profile");
|
|
}
|
|
if (isV4) {
|
|
if (provider.permissionMode !== "approve-all" && provider.permissionMode !== "approve-paperclip" && provider.permissionMode !== "approve-reads" && provider.permissionMode !== "deny-all") {
|
|
throw new NativeExecutionInputError("input.provider.permissionMode must be approve-all, approve-paperclip, approve-reads, or deny-all");
|
|
}
|
|
} else if (provider.permissionPolicy !== "interactive") {
|
|
throw new NativeExecutionInputError("input.provider.permissionPolicy must be interactive");
|
|
}
|
|
const claudeEffort = resolveClaudeEffort(provider.agent, provider.claudeEffort);
|
|
const grokEffort = resolveGrokEffort(provider.agent, provider.grokEffort);
|
|
const grokDisableWebSearch = resolveGrokDisableWebSearch(provider.agent, provider.grokDisableWebSearch);
|
|
const profile = record(provider.profile, "input.provider.profile");
|
|
exactKeys(profile, [
|
|
"driverKind",
|
|
"protocolVersion",
|
|
"acpxVersion",
|
|
"agent",
|
|
"agentProfileVersion",
|
|
"agentServerPackage",
|
|
"agentServerVersion",
|
|
"agentRuntimePackage",
|
|
"agentRuntimeVersion",
|
|
"commandDigest",
|
|
], "input.provider.profile");
|
|
if (
|
|
profile.driverKind !== "acpx_runtime"
|
|
|| profile.protocolVersion !== 1
|
|
|| profile.acpxVersion !== QUALIFIED_ACPX_VERSION
|
|
|| profile.agent !== provider.agent
|
|
|| !isSupportedAcpxProfileVersion(provider.agent, profile.agentProfileVersion)
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.profile does not match the qualified ACPX v1 profile");
|
|
}
|
|
if (provider.agent === "pi" && profile.agentProfileVersion >= 13 && provider.piThinkingLevel === undefined) {
|
|
throw new NativeExecutionInputError("input.provider.piThinkingLevel is required for Pi profile 13 or later");
|
|
}
|
|
const runtimePackage = nullableText(profile.agentRuntimePackage, "input.provider.profile.agentRuntimePackage");
|
|
const runtimeVersion = nullableText(profile.agentRuntimeVersion, "input.provider.profile.agentRuntimeVersion");
|
|
if ((runtimePackage === null) !== (runtimeVersion === null)) {
|
|
throw new NativeExecutionInputError("input.provider.profile agent runtime package and version must both be present or null");
|
|
}
|
|
parsedProvider = {
|
|
kind: "acpx",
|
|
agent: provider.agent,
|
|
model: providerModel,
|
|
...(isV4
|
|
? { permissionMode: provider.permissionMode as NativeAcpxPermissionMode }
|
|
: { permissionPolicy: "interactive" as const }),
|
|
...(provider.mode === undefined ? {} : { mode: provider.mode as string }),
|
|
...(claudeEffort === undefined ? {} : { claudeEffort }),
|
|
...(grokEffort === undefined ? {} : { grokEffort }),
|
|
...(grokDisableWebSearch === undefined ? {} : { grokDisableWebSearch }),
|
|
...(provider.piThinkingLevel === undefined ? {} : { piThinkingLevel: provider.piThinkingLevel as "off" | "low" | "high" | "max" }),
|
|
profile: {
|
|
driverKind: "acpx_runtime",
|
|
protocolVersion: 1,
|
|
acpxVersion: QUALIFIED_ACPX_VERSION,
|
|
agent: provider.agent,
|
|
agentProfileVersion: profile.agentProfileVersion,
|
|
agentServerPackage: text(profile.agentServerPackage, "input.provider.profile.agentServerPackage"),
|
|
agentServerVersion: text(profile.agentServerVersion, "input.provider.profile.agentServerVersion"),
|
|
agentRuntimePackage: runtimePackage,
|
|
agentRuntimeVersion: runtimeVersion,
|
|
commandDigest: text(profile.commandDigest, "input.provider.profile.commandDigest"),
|
|
},
|
|
};
|
|
} else if (provider.kind === "opencode") {
|
|
if (isV4 && provider.permissionMode !== "allow" && provider.permissionMode !== "ask" && provider.permissionMode !== "deny") {
|
|
throw new NativeExecutionInputError("input.provider.permissionMode must be allow, ask, or deny");
|
|
}
|
|
parsedProvider = {
|
|
kind: "opencode",
|
|
model: providerModel!,
|
|
...(isV4
|
|
? { permissionMode: provider.permissionMode as NativeOpenCodePermissionMode }
|
|
: {}),
|
|
};
|
|
} else {
|
|
if (isV4 && provider.approvalPolicy !== "never" && provider.approvalPolicy !== "on-request" && provider.approvalPolicy !== "untrusted") {
|
|
throw new NativeExecutionInputError("input.provider.approvalPolicy must be never, on-request, or untrusted");
|
|
}
|
|
if (isV5 && provider.reasoningEffort !== undefined && !["minimal", "low", "medium", "high", "xhigh", "max", "ultra"].includes(String(provider.reasoningEffort))) {
|
|
throw new NativeExecutionInputError("input.provider.reasoningEffort is unsupported");
|
|
}
|
|
if (isV5 && provider.webSearch !== undefined && provider.webSearch !== "disabled") {
|
|
throw new NativeExecutionInputError("input.provider.webSearch must be disabled when specified");
|
|
}
|
|
parsedProvider = {
|
|
kind: "codex",
|
|
model: providerModel,
|
|
...(isV4
|
|
? { approvalPolicy: provider.approvalPolicy as NativeCodexApprovalPolicy }
|
|
: {}),
|
|
...(isV5 && provider.reasoningEffort !== undefined
|
|
? { reasoningEffort: text(provider.reasoningEffort, "input.provider.reasoningEffort") }
|
|
: {}),
|
|
...(isV5 && provider.webSearch === "disabled" ? { webSearch: "disabled" as const } : {}),
|
|
};
|
|
}
|
|
if (!Array.isArray(contract.criteria) || contract.criteria.length === 0) {
|
|
throw new NativeExecutionInputError("input.completionContract.contract.criteria must not be empty");
|
|
}
|
|
const criteria = contract.criteria.map((entry, index) => {
|
|
const criterion = record(entry, `input.completionContract.contract.criteria[${index}]`);
|
|
exactKeys(criterion, ["id", "requirement"], `input.completionContract.contract.criteria[${index}]`);
|
|
return { id: text(criterion.id, `criteria[${index}].id`), requirement: text(criterion.requirement, `criteria[${index}].requirement`) };
|
|
});
|
|
|
|
if (!Array.isArray(input.interactionResponses) || !Array.isArray(input.credentialBindings)) {
|
|
throw new NativeExecutionInputError("interactionResponses and credentialBindings must be arrays");
|
|
}
|
|
const interactionResponses = input.interactionResponses.map((entry, index) => {
|
|
const response = record(entry, `input.interactionResponses[${index}]`);
|
|
exactKeys(response, ["interactionId", "kind", "response"], `input.interactionResponses[${index}]`);
|
|
if (![
|
|
"connection_intent",
|
|
"suggest_tasks",
|
|
"ask_user_questions",
|
|
"request_confirmation",
|
|
"request_checkbox_confirmation",
|
|
"request_item_verdicts",
|
|
].includes(String(response.kind))) {
|
|
throw new NativeExecutionInputError(`input.interactionResponses[${index}].kind is unsupported`);
|
|
}
|
|
const kind = response.kind as NativeInteractionResponseEnvelope["kind"];
|
|
return {
|
|
interactionId: text(response.interactionId, `input.interactionResponses[${index}].interactionId`),
|
|
kind,
|
|
response: structuredClone(record(response.response, `input.interactionResponses[${index}].response`)),
|
|
};
|
|
});
|
|
const credentialBindings = input.credentialBindings.map((entry, index) => {
|
|
const bindingRef = record(entry, `input.credentialBindings[${index}]`);
|
|
exactKeys(bindingRef, ["bindingId", "service", "destination", "expiresAt", "displayName"], `input.credentialBindings[${index}]`);
|
|
return {
|
|
bindingId: text(bindingRef.bindingId, `credentialBindings[${index}].bindingId`),
|
|
service: text(bindingRef.service, `credentialBindings[${index}].service`),
|
|
destination: text(bindingRef.destination, `credentialBindings[${index}].destination`),
|
|
expiresAt: nullableText(bindingRef.expiresAt, `credentialBindings[${index}].expiresAt`),
|
|
displayName: nullableText(bindingRef.displayName, `credentialBindings[${index}].displayName`),
|
|
};
|
|
});
|
|
|
|
const common = {
|
|
binding: {
|
|
companyId: text(binding.companyId, "input.binding.companyId"),
|
|
runId: text(binding.runId, "input.binding.runId"),
|
|
issueId: text(binding.issueId, "input.binding.issueId"),
|
|
agentId: text(binding.agentId, "input.binding.agentId"),
|
|
...(binding.agentKeyId === undefined ? {} : { agentKeyId: text(binding.agentKeyId, "input.binding.agentKeyId") }),
|
|
executionWorkspaceId: text(binding.executionWorkspaceId, "input.binding.executionWorkspaceId"),
|
|
},
|
|
task: {
|
|
identifier: text(task.identifier, "input.task.identifier"),
|
|
title: text(task.title, "input.task.title"),
|
|
description: nullableText(task.description, "input.task.description"),
|
|
// `prompt` was added after native run inputs were already persisted.
|
|
// Recovery must retain those runs, so derive the same bounded model
|
|
// prompt from their task fields instead of making them unrecoverable.
|
|
prompt: text(task.prompt ?? task.description ?? task.title, "input.task.prompt"),
|
|
workMode: task.workMode as "standard" | "planning" | "ask" | "skill_test",
|
|
},
|
|
workspace: {
|
|
cwd: text(workspace.cwd, "input.workspace.cwd"),
|
|
repoUrl: nullableText(workspace.repoUrl, "input.workspace.repoUrl"),
|
|
repoRef: nullableText(workspace.repoRef, "input.workspace.repoRef"),
|
|
branchName: nullableText(workspace.branchName, "input.workspace.branchName"),
|
|
},
|
|
session: {
|
|
normalizedSessionId: nullableText(session.normalizedSessionId, "input.session.normalizedSessionId"),
|
|
driverKind,
|
|
protocolVersion: 1 as const,
|
|
lifecyclePolicy,
|
|
},
|
|
provider: parsedProvider,
|
|
completionContract: {
|
|
id: text(completionContract.id, "input.completionContract.id"),
|
|
sha256: text(completionContract.sha256, "input.completionContract.sha256"),
|
|
schemaVersion: text(completionContract.schemaVersion, "input.completionContract.schemaVersion"),
|
|
contract: {
|
|
revision: text(contract.revision, "input.completionContract.contract.revision"),
|
|
objective: text(contract.objective, "input.completionContract.contract.objective"),
|
|
criteria,
|
|
},
|
|
},
|
|
interactionResponses,
|
|
credentialBindings,
|
|
};
|
|
if (!isV2) {
|
|
return {
|
|
...common,
|
|
schema: NATIVE_EXECUTION_INPUT_SCHEMA_V1,
|
|
task: { ...common.task, workMode: "standard" },
|
|
};
|
|
}
|
|
const current = {
|
|
...common,
|
|
executionMode,
|
|
planningContext,
|
|
};
|
|
if (!isV3) return { ...current, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V2 };
|
|
const withRuntimeContext = { ...current, runtimeContext: parseNativeRuntimeContext(input.runtimeContext) };
|
|
if (!isV4) return { ...withRuntimeContext, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V3 };
|
|
const withPermissions = {
|
|
...withRuntimeContext,
|
|
...(input.continuationPrompt !== undefined ? { continuationPrompt: nullableText(input.continuationPrompt, "input.continuationPrompt") } : {}),
|
|
...(input.initialCommunicationGuidance !== undefined ? { initialCommunicationGuidance: nullableText(input.initialCommunicationGuidance, "input.initialCommunicationGuidance") } : {}),
|
|
provider: parsedProvider as NativeProviderConfigV4,
|
|
};
|
|
if (!isV5) return { ...withPermissions, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V4 };
|
|
if (isV10) return {
|
|
...withPermissions, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V10, homeMode: input.homeMode as NativeHomeMode,
|
|
provider: parsedProvider as NativeProviderConfigV10,
|
|
...(input.completionSources !== undefined ? { completionSources: parseCompletionSources(input.completionSources) } : {}),
|
|
};
|
|
if (isV9) return {
|
|
...withPermissions, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V9, homeMode: input.homeMode as NativeHomeMode,
|
|
provider: parsedProvider as NativeProviderConfigV9,
|
|
...(input.completionSources !== undefined ? { completionSources: parseCompletionSources(input.completionSources) } : {}),
|
|
};
|
|
return {
|
|
...withPermissions,
|
|
...(isV7 ? { schema: NATIVE_EXECUTION_INPUT_SCHEMA_V7, homeMode: input.homeMode as NativeHomeMode } : { schema: NATIVE_EXECUTION_INPUT_SCHEMA }),
|
|
provider: parsedProvider as NativeProviderConfigV5,
|
|
...(input.completionSources !== undefined ? { completionSources: parseCompletionSources(input.completionSources) } : {}),
|
|
};
|
|
}
|
|
|
|
function parseCompletionSources(value: unknown): NativeCompletionSources {
|
|
const sources = record(value, "input.completionSources");
|
|
exactKeys(sources, ["promptSha256", "contractRevision", "criteria"], "input.completionSources");
|
|
const digest = (value: unknown, path: string): string => {
|
|
const result = text(value, path);
|
|
if (!/^[a-f0-9]{64}$/.test(result)) throw new NativeExecutionInputError(`${path} must be a sha256 digest`);
|
|
return result;
|
|
};
|
|
if (!Array.isArray(sources.criteria)) throw new NativeExecutionInputError("input.completionSources.criteria must be an array");
|
|
const ids = new Set<string>();
|
|
return {
|
|
promptSha256: digest(sources.promptSha256, "input.completionSources.promptSha256"),
|
|
contractRevision: text(sources.contractRevision, "input.completionSources.contractRevision"),
|
|
criteria: sources.criteria.map((value) => {
|
|
const entry = record(value, "completion source criterion");
|
|
exactKeys(entry, ["id", "source"], "completion source criterion");
|
|
const id = text(entry.id, "completion source criterion.id");
|
|
if (ids.has(id)) throw new NativeExecutionInputError("duplicate completion source criterion");
|
|
ids.add(id);
|
|
const source = record(entry.source, "completion source");
|
|
exactKeys(source, ["kind", "id", "revision"], "completion source");
|
|
if (source.kind !== "description" && source.kind !== "comment") throw new NativeExecutionInputError("invalid completion source kind");
|
|
return { id, source: { kind: source.kind, id: text(source.id, "completion source.id"), revision: digest(source.revision, "completion source.revision") } };
|
|
}),
|
|
};
|
|
}
|
|
|
|
export interface NativeContinuationEnvelope {
|
|
schema: "paperclip.native-continuation.v1";
|
|
events: string;
|
|
completion: { revision: string; criterionIds: string[]; instruction: string };
|
|
}
|
|
|
|
export function buildNativeModelEnvelope(input: NativeExecutionInput, options: { resumedSession: true }): NativeModelEnvelopeV1 | NativeModelEnvelopeV2 | NativeModelEnvelopeV3 | NativeContinuationEnvelope;
|
|
export function buildNativeModelEnvelope(input: NativeExecutionInput): NativeModelEnvelopeV1 | NativeModelEnvelopeV2 | NativeModelEnvelopeV3;
|
|
export function buildNativeModelEnvelope(input: NativeExecutionInput, options?: { resumedSession: boolean }): NativeModelEnvelopeV1 | NativeModelEnvelopeV2 | NativeModelEnvelopeV3 | NativeContinuationEnvelope {
|
|
if (options?.resumedSession && "continuationPrompt" in input && input.continuationPrompt) {
|
|
return {
|
|
schema: "paperclip.native-continuation.v1",
|
|
events: input.continuationPrompt,
|
|
completion: {
|
|
revision: input.completionContract.contract.revision,
|
|
criterionIds: input.completionContract.contract.criteria.map((criterion) => criterion.id),
|
|
instruction: "Before ending this turn, obtain one accepted paperclip_finish or paperclip_block result. Earlier reports belong to earlier turns; a final message alone does not complete this turn.",
|
|
},
|
|
};
|
|
}
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V1) {
|
|
return {
|
|
schema: NATIVE_MODEL_ENVELOPE_SCHEMA_V1,
|
|
task: structuredClone(input.task),
|
|
workspace: input.provider.kind === "claude_managed" || input.provider.kind === "aws_agentcore"
|
|
? null
|
|
: { cwd: input.workspace.cwd },
|
|
completionContract: structuredClone(input.completionContract.contract),
|
|
interactionResponses: structuredClone(input.interactionResponses),
|
|
};
|
|
}
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V6 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V7 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V8 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V9 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V10) {
|
|
const sourceBinding = input.completionSources;
|
|
const references = sourceBinding?.contractRevision === input.completionContract.contract.revision
|
|
&& sourceBinding.promptSha256 === createHash("sha256").update(input.task.prompt).digest("hex")
|
|
? new Map(sourceBinding.criteria.map((criterion) => [criterion.id, criterion.source]))
|
|
: new Map<string, NativeCompletionSource>();
|
|
return {
|
|
schema: NATIVE_MODEL_ENVELOPE_SCHEMA,
|
|
requestedSkills: explicitTaskSkillNames(input.task.description, input.runtimeContext.skills.map((skill) => skill.runtimeName)),
|
|
task: {
|
|
identifier: input.task.identifier,
|
|
title: input.task.title,
|
|
prompt: !options?.resumedSession && input.initialCommunicationGuidance
|
|
? `${input.initialCommunicationGuidance}\n\n${input.task.prompt}`
|
|
: input.task.prompt,
|
|
workMode: input.task.workMode,
|
|
},
|
|
executionMode: input.executionMode,
|
|
planningContext: structuredClone(input.planningContext),
|
|
workspace: input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V6 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V8 || input.provider.kind === "claude_managed" || input.provider.kind === "aws_agentcore" ? null : { cwd: input.workspace.cwd },
|
|
completionContract: {
|
|
...structuredClone(input.completionContract.contract),
|
|
criteria: input.completionContract.contract.criteria.map((criterion) => {
|
|
const source = references.get(criterion.id);
|
|
return source && source.revision === createHash("sha256").update(criterion.requirement.trim()).digest("hex")
|
|
&& (source.kind !== "description" || source.id === input.binding.issueId)
|
|
? { id: criterion.id, source: { ...source, location: "task.prompt" as const } } : { ...criterion };
|
|
}),
|
|
},
|
|
interactionResponses: structuredClone(input.interactionResponses),
|
|
};
|
|
}
|
|
return {
|
|
schema: NATIVE_MODEL_ENVELOPE_SCHEMA_V2,
|
|
task: {
|
|
...structuredClone(input.task),
|
|
prompt: !options?.resumedSession && "initialCommunicationGuidance" in input && input.initialCommunicationGuidance
|
|
? `${input.initialCommunicationGuidance}\n\n${input.task.prompt}`
|
|
: input.task.prompt,
|
|
},
|
|
executionMode: input.executionMode,
|
|
planningContext: structuredClone(input.planningContext),
|
|
workspace: input.provider.kind === "claude_managed" || input.provider.kind === "aws_agentcore"
|
|
? null
|
|
: { cwd: input.workspace.cwd },
|
|
completionContract: structuredClone(input.completionContract.contract),
|
|
interactionResponses: structuredClone(input.interactionResponses),
|
|
};
|
|
}
|