-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtask-input.ts
More file actions
144 lines (131 loc) · 5.34 KB
/
task-input.ts
File metadata and controls
144 lines (131 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { isPlainObject, stringList } from "./object-utils.js"
import type { SandboxToolPolicySnapshot } from "./sandbox-tool-policy.js"
export type TaskTargetKind = "repo" | "site" | "plugin" | "theme" | (string & {})
export const TASK_INPUT_SCHEMA = "wp-codebox/task-input/v1" as const
export const TASK_INPUT_VERSION = 1 as const
export interface TaskTarget {
kind: TaskTargetKind
ref?: string
path?: string
url?: string
}
export interface TaskInputPolicy {
approvals?: "never" | "on-write" | "on-command"
applyBack?: "disabled" | "reviewed"
sandbox?: "required" | "preferred"
[key: string]: unknown
}
export interface TaskInputAgentBundle {
source?: string
bundle?: Record<string, unknown>
slug?: string
on_conflict?: "error" | "skip" | "upgrade"
owner_id?: number
token_env?: string
}
export interface TaskInput {
schema: typeof TASK_INPUT_SCHEMA
version: typeof TASK_INPUT_VERSION
goal: string
target: Partial<TaskTarget>
allowed_tools: string[]
expected_artifacts: string[]
agent_bundles: TaskInputAgentBundle[]
sandbox_tool_policy: SandboxToolPolicySnapshot | Record<string, never>
policy: TaskInputPolicy
context: Record<string, unknown>
}
export type TaskInputRequest = Partial<Omit<TaskInput, "schema" | "version" | "goal">> & {
goal?: string
}
export const TASK_INPUT_JSON_SCHEMA = {
$id: TASK_INPUT_SCHEMA,
type: "object",
required: ["schema", "version", "goal", "target", "allowed_tools", "expected_artifacts", "agent_bundles", "sandbox_tool_policy", "policy", "context"],
properties: {
schema: { const: TASK_INPUT_SCHEMA, description: "Task input contract schema id." },
version: { const: TASK_INPUT_VERSION, description: "Task input contract version." },
goal: { type: "string", description: "User-facing outcome the sandboxed coding agent should accomplish." },
target: {
type: "object",
description: "Bounded target for the task, such as a repo, site, plugin, or theme.",
properties: {
kind: { type: "string" },
ref: { type: "string" },
path: { type: "string" },
url: { type: "string" },
},
},
allowed_tools: {
type: "array",
description: "Tool names the product caller expects the sandboxed agent to stay within.",
items: { type: "string" },
},
expected_artifacts: {
type: "array",
description: "Artifact kinds the caller wants back, such as patch, review, tests, preview, or package.",
items: { type: "string" },
},
agent_bundles: {
type: "array",
description: "Runtime agent bundles to import into the disposable sandbox before invoking the selected runtime agent.",
items: {
type: "object",
anyOf: [{ required: ["source"] }, { required: ["bundle"] }],
properties: {
source: { type: "string" },
bundle: { type: "object" },
slug: { type: "string" },
on_conflict: { enum: ["error", "skip", "upgrade"] },
owner_id: { type: "integer", minimum: 1 },
token_env: { type: "string" },
},
},
},
sandbox_tool_policy: {
type: "object",
description: "Resolved caller-owned sandbox tool policy snapshot. Codebox validates and enforces it without owning product-specific tool taxonomy.",
},
policy: {
type: "object",
description: "Caller policy hints for approvals, apply-back, sandboxing, and risk controls.",
},
context: {
type: "object",
description: "Additional non-secret caller context for the sandboxed task.",
},
},
} as const
export function normalizeTaskInput(input: TaskInputRequest): TaskInput {
const goal = String(input.goal ?? "").trim()
if (goal === "") throw new Error("goal is required.")
return {
schema: TASK_INPUT_SCHEMA,
version: TASK_INPUT_VERSION,
goal,
target: isPlainObject(input.target) ? input.target : {},
allowed_tools: stringList(input.allowed_tools),
expected_artifacts: stringList(input.expected_artifacts),
agent_bundles: normalizeAgentBundles(input.agent_bundles),
sandbox_tool_policy: isPlainObject(input.sandbox_tool_policy) ? input.sandbox_tool_policy as unknown as SandboxToolPolicySnapshot : {},
policy: isPlainObject(input.policy) ? input.policy : {},
context: isPlainObject(input.context) ? input.context : {},
}
}
function normalizeAgentBundles(value: unknown): TaskInputAgentBundle[] {
if (!Array.isArray(value)) return []
return value.flatMap((entry): TaskInputAgentBundle[] => {
if (!isPlainObject(entry)) return []
const source = typeof entry.source === "string" ? entry.source.trim() : ""
const bundle = isPlainObject(entry.bundle) ? entry.bundle : undefined
if (!source && !bundle) return []
const normalized: TaskInputAgentBundle = {}
if (source) normalized.source = source
if (bundle) normalized.bundle = bundle
if (typeof entry.slug === "string" && entry.slug.trim()) normalized.slug = entry.slug.trim()
normalized.on_conflict = ["error", "skip", "upgrade"].includes(String(entry.on_conflict)) ? entry.on_conflict as TaskInputAgentBundle["on_conflict"] : "upgrade"
if (Number.isSafeInteger(entry.owner_id) && Number(entry.owner_id) > 0) normalized.owner_id = Number(entry.owner_id)
if (typeof entry.token_env === "string" && entry.token_env.trim()) normalized.token_env = entry.token_env.trim()
return [normalized]
})
}