docks-kit 0.10.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,452 +0,0 @@
1
- import {
2
- CLAUDE_EFFORT_LEVELS,
3
- CODEX_REASONING_EFFORTS
4
- } from "./efforts"
5
- import { payloadText } from "./payload"
6
-
7
- export type WorkflowTool = "claude" | "codex"
8
- export type WorkflowCompany = "anthropic" | "openai"
9
- export type WorkflowRoleName = "orchestrator" | "reviewer" | "implementer"
10
- export type WorkflowBoundName = "minimum_score" | "max_rounds"
11
- export type WorkflowServiceTier = "fast"
12
-
13
- export interface WorkflowCandidate {
14
- readonly company: WorkflowCompany
15
- readonly tool: WorkflowTool
16
- readonly model: string
17
- readonly effort: string
18
- readonly service_tier?: WorkflowServiceTier
19
- }
20
-
21
- export interface WorkflowRole {
22
- readonly selector: string
23
- readonly candidates: ReadonlyArray<WorkflowCandidate>
24
- }
25
-
26
- export interface WorkflowRecordV1 {
27
- readonly schema: 1
28
- readonly orchestrator: WorkflowRole
29
- readonly reviewer: WorkflowRole
30
- readonly implementer: WorkflowRole
31
- readonly review: {
32
- readonly minimum_score: number
33
- readonly max_rounds: number
34
- }
35
- }
36
-
37
- export interface WorkflowRecordV2 {
38
- readonly schema: 2
39
- readonly orchestrator: WorkflowRole
40
- readonly reviewer: WorkflowRole
41
- readonly implementer: WorkflowRole
42
- readonly review: {
43
- readonly minimum_score: number
44
- readonly max_rounds: number
45
- }
46
- }
47
-
48
- export type WorkflowRecord = WorkflowRecordV1 | WorkflowRecordV2
49
-
50
- export interface WorkflowOverrides {
51
- readonly orchestrator?: string
52
- readonly reviewer?: string
53
- readonly implementer?: string
54
- readonly minimumScore?: string
55
- readonly maxRounds?: string
56
- }
57
-
58
- interface WorkflowProfile {
59
- readonly candidates: ReadonlyArray<WorkflowCandidate>
60
- }
61
-
62
- interface WorkflowRegistryCore {
63
- readonly schema: 2
64
- readonly profiles: Readonly<Record<string, WorkflowProfile>>
65
- readonly defaults: {
66
- readonly orchestrator: string
67
- readonly reviewer: string
68
- readonly implementer: string
69
- readonly review: {
70
- readonly minimum_score: number
71
- readonly max_rounds: number
72
- }
73
- }
74
- readonly exact_target_grammar: "<tool>:<model>@<effort>[+fast]"
75
- readonly availability: "checked_when_used"
76
- }
77
-
78
- export interface WorkflowRegistryView extends WorkflowRegistryCore {
79
- readonly tools: Readonly<Record<WorkflowTool, {
80
- readonly models: ReadonlyArray<string>
81
- readonly efforts: ReadonlyArray<string>
82
- }>>
83
- }
84
-
85
- type UnknownRecord = Record<string, unknown>
86
-
87
- export const WORKFLOW_RECORD_PREFIX = "Docks-workflow-models: "
88
-
89
- const TOOL_COMPANIES: Readonly<Record<WorkflowTool, WorkflowCompany>> = {
90
- claude: "anthropic",
91
- codex: "openai"
92
- }
93
-
94
- const isRecord = (value: unknown): value is UnknownRecord =>
95
- typeof value === "object" && value !== null && !Array.isArray(value)
96
-
97
- function hasExactKeys(value: UnknownRecord, keys: ReadonlyArray<string>): boolean {
98
- const actual = Object.keys(value).sort()
99
- return actual.length === keys.length && keys.every((key, index) => actual[index] === key)
100
- }
101
-
102
- function expectRecord(value: unknown, label: string, keys: ReadonlyArray<string>): UnknownRecord {
103
- if (!isRecord(value) || !hasExactKeys(value, [...keys].sort())) {
104
- throw new Error(`${label} must be a closed record`)
105
- }
106
- return value
107
- }
108
-
109
- function parseManifest(): UnknownRecord {
110
- const parsed = JSON.parse(payloadText("SoT/models.json")) as unknown
111
- if (!isRecord(parsed)) throw new Error("Embedded model catalog must be an object")
112
- return parsed
113
- }
114
-
115
- function toolModels(manifest: UnknownRecord, tool: WorkflowTool): ReadonlyArray<string> {
116
- const entry = manifest[tool]
117
- if (!isRecord(entry) || !Array.isArray(entry["models"])) {
118
- throw new Error(`Embedded ${tool} model catalog is invalid`)
119
- }
120
- const models = entry["models"].map((item) => {
121
- if (!isRecord(item) || typeof item["id"] !== "string") {
122
- throw new Error(`Embedded ${tool} model entry is invalid`)
123
- }
124
- return item["id"]
125
- })
126
- return models.filter((model) => model !== "default")
127
- }
128
-
129
- const toolEfforts = (tool: WorkflowTool): ReadonlyArray<string> =>
130
- tool === "claude" ? CLAUDE_EFFORT_LEVELS : CODEX_REASONING_EFFORTS
131
-
132
- function parseCandidate(
133
- value: unknown,
134
- label: string,
135
- tools: WorkflowRegistryView["tools"],
136
- allowServiceTier: boolean
137
- ): WorkflowCandidate {
138
- const serviceTier = isRecord(value) ? value["service_tier"] : undefined
139
- const keys = serviceTier === undefined
140
- ? ["company", "effort", "model", "tool"]
141
- : ["company", "effort", "model", "service_tier", "tool"]
142
- const candidate = expectRecord(value, label, keys)
143
- const tool = candidate["tool"]
144
- if (tool !== "claude" && tool !== "codex") throw new Error(`${label} has an invalid tool`)
145
- const company = candidate["company"]
146
- const expectedCompany = TOOL_COMPANIES[tool]
147
- if (company !== expectedCompany) throw new Error(`${label} has an invalid company`)
148
- const model = candidate["model"]
149
- if (typeof model !== "string" || !tools[tool].models.includes(model)) {
150
- throw new Error(`${label} has an unverified ${tool} model`)
151
- }
152
- const effort = candidate["effort"]
153
- if (typeof effort !== "string" || !tools[tool].efforts.includes(effort)) {
154
- throw new Error(`${label} has an unverified ${tool} effort`)
155
- }
156
- if (serviceTier !== undefined) {
157
- if (!allowServiceTier) throw new Error(`${label} requires workflow record schema 2 for a service tier`)
158
- if (tool !== "codex" || serviceTier !== "fast") throw new Error(`${label} has an invalid service tier`)
159
- return { company: expectedCompany, tool, model, effort, service_tier: "fast" }
160
- }
161
- return { company: expectedCompany, tool, model, effort }
162
- }
163
-
164
- function parseCandidates(
165
- value: unknown,
166
- label: string,
167
- tools: WorkflowRegistryView["tools"],
168
- allowServiceTier = false
169
- ): ReadonlyArray<WorkflowCandidate> {
170
- if (!Array.isArray(value) || value.length < 1 || value.length > 3) {
171
- throw new Error(`${label} must contain one to three candidates`)
172
- }
173
- return value.map((candidate, index) =>
174
- parseCandidate(candidate, `${label}[${index}]`, tools, allowServiceTier)
175
- )
176
- }
177
-
178
- function parseNumericBound(name: WorkflowBoundName, value: unknown): number {
179
- const [minimum, maximum] = name === "minimum_score" ? [0, 100] : [1, 10]
180
- if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) {
181
- throw new Error(`${name} must be an integer in ${minimum}..${maximum}`)
182
- }
183
- return value
184
- }
185
-
186
- function resolveSelector(
187
- selector: string,
188
- registry: Pick<WorkflowRegistryView, "profiles" | "tools">
189
- ): WorkflowRole {
190
- const profileMatch = /^profile:([A-Za-z0-9._-]+)$/.exec(selector)
191
- if (profileMatch !== null) {
192
- const profileName = profileMatch[1]!
193
- const profile = registry.profiles[profileName]
194
- if (profile === undefined) throw new Error(`Unknown workflow profile '${profileName}'`)
195
- return { selector, candidates: profile.candidates }
196
- }
197
-
198
- const exactMatch = /^(claude|codex):([A-Za-z0-9._-]+)@([A-Za-z0-9._-]+)(\+fast)?$/.exec(selector)
199
- if (exactMatch === null) {
200
- throw new Error(
201
- `Invalid workflow selector '${selector}' — expected profile:<name> or <tool>:<model>@<effort>[+fast]`
202
- )
203
- }
204
- const tool = exactMatch[1] as WorkflowTool
205
- const model = exactMatch[2]!
206
- const effort = exactMatch[3]!
207
- const fast = exactMatch[4] !== undefined
208
- if (!registry.tools[tool].models.includes(model)) {
209
- throw new Error(`Unknown ${tool} workflow model '${model}'`)
210
- }
211
- if (!registry.tools[tool].efforts.includes(effort)) {
212
- throw new Error(`Unknown ${tool} workflow effort '${effort}'`)
213
- }
214
- if (fast && tool !== "codex") {
215
- throw new Error(`Invalid workflow selector '${selector}' — Fast is available only for Codex selectors`)
216
- }
217
- const candidate: WorkflowCandidate = { company: TOOL_COMPANIES[tool], tool, model, effort }
218
- return {
219
- selector,
220
- candidates: [fast ? { ...candidate, service_tier: "fast" } : candidate]
221
- }
222
- }
223
-
224
- function loadWorkflowRegistry(): WorkflowRegistryView {
225
- const manifest = parseManifest()
226
- const tools: WorkflowRegistryView["tools"] = {
227
- claude: { models: toolModels(manifest, "claude"), efforts: toolEfforts("claude") },
228
- codex: { models: toolModels(manifest, "codex"), efforts: toolEfforts("codex") }
229
- }
230
- const workflow = expectRecord(
231
- manifest["workflow"],
232
- "Embedded workflow registry",
233
- ["availability", "defaults", "exact_target_grammar", "profiles", "schema"]
234
- )
235
- if (workflow["schema"] !== 2) throw new Error("Embedded workflow registry schema must be 2")
236
- if (workflow["exact_target_grammar"] !== "<tool>:<model>@<effort>[+fast]") {
237
- throw new Error("Embedded workflow exact-target grammar is invalid")
238
- }
239
- if (workflow["availability"] !== "checked_when_used") {
240
- throw new Error("Embedded workflow availability contract is invalid")
241
- }
242
-
243
- const rawProfiles = workflow["profiles"]
244
- if (!isRecord(rawProfiles) || Object.keys(rawProfiles).length === 0) {
245
- throw new Error("Embedded workflow profiles must be a nonempty record")
246
- }
247
- const profiles: Record<string, WorkflowProfile> = {}
248
- for (const [name, rawProfile] of Object.entries(rawProfiles)) {
249
- if (!/^[A-Za-z0-9._-]+$/.test(name)) throw new Error(`Invalid workflow profile name '${name}'`)
250
- const profile = expectRecord(rawProfile, `Workflow profile '${name}'`, ["candidates"])
251
- profiles[name] = {
252
- candidates: parseCandidates(profile["candidates"], `Workflow profile '${name}' candidates`, tools)
253
- }
254
- }
255
-
256
- const rawDefaults = expectRecord(
257
- workflow["defaults"],
258
- "Embedded workflow defaults",
259
- ["implementer", "orchestrator", "review", "reviewer"]
260
- )
261
- const rawReview = expectRecord(
262
- rawDefaults["review"],
263
- "Embedded workflow review defaults",
264
- ["max_rounds", "minimum_score"]
265
- )
266
- for (const role of ["orchestrator", "reviewer", "implementer"] as const) {
267
- if (typeof rawDefaults[role] !== "string") throw new Error(`Embedded workflow ${role} default is invalid`)
268
- }
269
- const registry: WorkflowRegistryView = {
270
- schema: 2,
271
- profiles,
272
- defaults: {
273
- orchestrator: rawDefaults["orchestrator"] as string,
274
- reviewer: rawDefaults["reviewer"] as string,
275
- implementer: rawDefaults["implementer"] as string,
276
- review: {
277
- minimum_score: parseNumericBound("minimum_score", rawReview["minimum_score"]),
278
- max_rounds: parseNumericBound("max_rounds", rawReview["max_rounds"])
279
- }
280
- },
281
- tools,
282
- exact_target_grammar: "<tool>:<model>@<effort>[+fast]",
283
- availability: "checked_when_used"
284
- }
285
- resolveSelector(registry.defaults.orchestrator, registry)
286
- resolveSelector(registry.defaults.reviewer, registry)
287
- resolveSelector(registry.defaults.implementer, registry)
288
- return registry
289
- }
290
-
291
- export function workflowRegistryView(): WorkflowRegistryView {
292
- return loadWorkflowRegistry()
293
- }
294
-
295
- export function resolveWorkflowSelector(selector: string): WorkflowRole {
296
- return resolveSelector(selector, loadWorkflowRegistry())
297
- }
298
-
299
- export function parseWorkflowBound(name: WorkflowBoundName, value: string): number {
300
- if (!/^[0-9]+$/.test(value)) {
301
- const range = name === "minimum_score" ? "0..100" : "1..10"
302
- throw new Error(`${name} must be a base-10 integer in ${range}`)
303
- }
304
- return parseNumericBound(name, Number(value))
305
- }
306
-
307
- export function defaultWorkflowRecord(): WorkflowRecordV1 {
308
- const registry = loadWorkflowRegistry()
309
- return {
310
- schema: 1,
311
- orchestrator: resolveSelector(registry.defaults.orchestrator, registry),
312
- reviewer: resolveSelector(registry.defaults.reviewer, registry),
313
- implementer: resolveSelector(registry.defaults.implementer, registry),
314
- review: registry.defaults.review
315
- }
316
- }
317
-
318
- function parseRole(
319
- value: unknown,
320
- name: WorkflowRoleName,
321
- registry: WorkflowRegistryView,
322
- schema: 1 | 2
323
- ): WorkflowRole {
324
- const role = expectRecord(value, `Workflow ${name} role`, ["candidates", "selector"])
325
- if (typeof role["selector"] !== "string") throw new Error(`Workflow ${name} selector is invalid`)
326
- const resolved = resolveSelector(role["selector"], registry)
327
- const candidates = parseCandidates(
328
- role["candidates"],
329
- `Workflow ${name} candidates`,
330
- registry.tools,
331
- schema === 2
332
- )
333
- if (compactJcs(candidates) !== compactJcs(resolved.candidates)) {
334
- throw new Error(`Workflow ${name} candidates do not match its selector`)
335
- }
336
- return resolved
337
- }
338
-
339
- function recordUsesFast(roles: ReadonlyArray<WorkflowRole>): boolean {
340
- return roles.some((role) => role.candidates.some((candidate) => candidate.service_tier === "fast"))
341
- }
342
-
343
- export function parseWorkflowRecord(value: unknown): WorkflowRecord {
344
- const record = expectRecord(
345
- value,
346
- "Workflow record",
347
- ["implementer", "orchestrator", "review", "reviewer", "schema"]
348
- )
349
- const schema = record["schema"]
350
- if (schema !== 1 && schema !== 2) throw new Error("Workflow record schema must be 1 or 2")
351
- const registry = loadWorkflowRegistry()
352
- const review = expectRecord(record["review"], "Workflow record review", ["max_rounds", "minimum_score"])
353
- const orchestrator = parseRole(record["orchestrator"], "orchestrator", registry, schema)
354
- const reviewer = parseRole(record["reviewer"], "reviewer", registry, schema)
355
- const implementer = parseRole(record["implementer"], "implementer", registry, schema)
356
- const parsed = {
357
- orchestrator,
358
- reviewer,
359
- implementer,
360
- review: {
361
- minimum_score: parseNumericBound("minimum_score", review["minimum_score"]),
362
- max_rounds: parseNumericBound("max_rounds", review["max_rounds"])
363
- }
364
- }
365
- if (schema === 2 && !recordUsesFast([orchestrator, reviewer, implementer])) {
366
- throw new Error("Workflow record schema 2 requires a Fast service tier")
367
- }
368
- return schema === 2 ? { schema: 2, ...parsed } : { schema: 1, ...parsed }
369
- }
370
-
371
- export function buildWorkflowRecord(
372
- overrides: WorkflowOverrides,
373
- base: WorkflowRecord = defaultWorkflowRecord()
374
- ): WorkflowRecord {
375
- const current = parseWorkflowRecord(base)
376
- const orchestrator = overrides.orchestrator === undefined
377
- ? current.orchestrator
378
- : resolveWorkflowSelector(overrides.orchestrator)
379
- const reviewer = overrides.reviewer === undefined
380
- ? current.reviewer
381
- : resolveWorkflowSelector(overrides.reviewer)
382
- const implementer = overrides.implementer === undefined
383
- ? current.implementer
384
- : resolveWorkflowSelector(overrides.implementer)
385
- const next = {
386
- orchestrator,
387
- reviewer,
388
- implementer,
389
- review: {
390
- minimum_score: overrides.minimumScore === undefined
391
- ? current.review.minimum_score
392
- : parseWorkflowBound("minimum_score", overrides.minimumScore),
393
- max_rounds: overrides.maxRounds === undefined
394
- ? current.review.max_rounds
395
- : parseWorkflowBound("max_rounds", overrides.maxRounds)
396
- }
397
- }
398
- return recordUsesFast([orchestrator, reviewer, implementer])
399
- ? { schema: 2, ...next }
400
- : { schema: 1, ...next }
401
- }
402
-
403
- function canonicalValue(value: unknown): unknown {
404
- if (value === null || typeof value === "string" || typeof value === "boolean") return value
405
- if (typeof value === "number") {
406
- if (!Number.isFinite(value)) throw new Error("JCS cannot encode a non-finite number")
407
- return value
408
- }
409
- if (Array.isArray(value)) return value.map(canonicalValue)
410
- if (isRecord(value)) {
411
- return Object.fromEntries(
412
- Object.entries(value)
413
- .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
414
- .map(([key, item]) => [key, canonicalValue(item)])
415
- )
416
- }
417
- throw new Error("JCS can encode only JSON values")
418
- }
419
-
420
- export function compactJcs(value: unknown): string {
421
- return JSON.stringify(canonicalValue(value))
422
- }
423
-
424
- export function renderWorkflowRecordLine(record: WorkflowRecord): string {
425
- return `${WORKFLOW_RECORD_PREFIX}${compactJcs(parseWorkflowRecord(record))}`
426
- }
427
-
428
- export function workflowRegistryJson(): string {
429
- return JSON.stringify(workflowRegistryView(), null, 2)
430
- }
431
-
432
- export function workflowCatalog(): string {
433
- const registry = workflowRegistryView()
434
- const profileLines = Object.entries(registry.profiles).flatMap(([name, profile]) => [
435
- ` profile:${name}`,
436
- ...profile.candidates.map(({ tool, model, effort }) => ` ${tool}:${model}@${effort}`)
437
- ])
438
- return [
439
- "Workflow model registry:",
440
- "Profiles:",
441
- ...profileLines,
442
- "Defaults:",
443
- ` orchestrator ${registry.defaults.orchestrator}`,
444
- ` reviewer ${registry.defaults.reviewer}`,
445
- ` implementer ${registry.defaults.implementer}`,
446
- ` review minimum score ${registry.defaults.review.minimum_score}; maximum rounds ${registry.defaults.review.max_rounds}`,
447
- `Exact targets: ${registry.exact_target_grammar}`,
448
- "Fast: append +fast to a Codex exact target. Without +fast, Codex roles use Standard.",
449
- "Compatibility: +fast requires a Docks consumer that supports workflow record schema 2.",
450
- "Availability: checked when used by Docks; docks-kit does not probe providers."
451
- ].join("\n")
452
- }