From 01ccfb6bf31752138348f7a63b16368129003991 Mon Sep 17 00:00:00 2001 From: pi Date: Sun, 12 Apr 2026 12:16:00 +0100 Subject: [PATCH] schema: export union for single/parallel modes; require single preset/task; expose single-model; update tests --- src/extension.test.ts | 51 +++++++++++++++++++++++++++++-------------- src/schema.ts | 19 ++++++++++------ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/extension.test.ts b/src/extension.test.ts index fbfb784..48e3abd 100644 --- a/src/extension.test.ts +++ b/src/extension.test.ts @@ -36,17 +36,24 @@ test("the extension entrypoint registers the subagent tool with the currently av assert.equal(registeredTools.length, 1); assert.equal(registeredTools[0]?.name, "subagent"); - // no single top-level model required now (handled at runtime) - assert.equal("agent" in registeredTools[0]?.parameters.properties, false); - assert.equal("agentScope" in registeredTools[0]?.parameters.properties, false); - assert.equal("confirmProjectAgents" in registeredTools[0]?.parameters.properties, false); - assert.equal("task" in registeredTools[0]?.parameters.properties, true); - assert.equal("preset" in registeredTools[0]?.parameters.properties, true); - assert.equal(registeredTools[0]?.parameters.properties.model, undefined); - assert.equal("agent" in registeredTools[0]?.parameters.properties.tasks.items.properties, false); - assert.equal("task" in registeredTools[0]?.parameters.properties.tasks.items.properties, true); - assert.equal("preset" in registeredTools[0]?.parameters.properties.tasks.items.properties, true); - assert.deepEqual(registeredTools[0]?.parameters.properties.tasks.items.properties.model.enum, [ + const params = registeredTools[0]?.parameters; + const branches = params?.anyOf ?? params?.oneOf ?? [params]; + // ensure union branches exist: single-mode and parallel-mode + assert(branches && branches.length === 2); + // no agent/agentScope/confirmProjectAgents in any branch + for (const key of ["agent", "agentScope", "confirmProjectAgents"]) { + assert.equal(branches.some((b: any) => b.properties && key in b.properties), false); + } + const singleBranch = branches.find((b: any) => b.properties && "task" in b.properties && "preset" in b.properties); + assert(singleBranch, "single-mode branch present"); + // single branch exposes optional top-level model + assert.equal("model" in singleBranch.properties, true); + const parallelBranch = branches.find((b: any) => b.properties && "tasks" in b.properties); + assert(parallelBranch, "parallel-mode branch present"); + assert.equal("agent" in parallelBranch.properties.tasks.items.properties, false); + assert.equal("task" in parallelBranch.properties.tasks.items.properties, true); + assert.equal("preset" in parallelBranch.properties.tasks.items.properties, true); + assert.deepEqual(parallelBranch.properties.tasks.items.properties.model.enum, [ "anthropic/claude-sonnet-4-5", "openai/gpt-5", ]); @@ -91,10 +98,16 @@ test("before_agent_start re-applies subagent registration when available models ); assert.equal(registeredTools.length, 1); - assert.deepEqual(registeredTools[0]?.parameters.properties.tasks.items.properties.model.enum, [ - "anthropic/claude-sonnet-4-5", - "openai/gpt-5", - ]); + { + const params = registeredTools[0]?.parameters; + const branches = params?.anyOf ?? params?.oneOf ?? [params]; + const parallelBranch = branches.find((b: any) => b.properties && "tasks" in b.properties); + assert(parallelBranch, "parallel branch present"); + assert.deepEqual(parallelBranch.properties.tasks.items.properties.model.enum, [ + "anthropic/claude-sonnet-4-5", + "openai/gpt-5", + ]); + } // then before agent start with a different model set — should re-register await handlers.before_agent_start?.( @@ -109,7 +122,13 @@ test("before_agent_start re-applies subagent registration when available models ); assert.equal(registeredTools.length, 2); - assert.deepEqual(registeredTools[1]?.parameters.properties.tasks.items.properties.model.enum, ["openai/gpt-6"]); + { + const params = registeredTools[1]?.parameters; + const branches = params?.anyOf ?? params?.oneOf ?? [params]; + const parallelBranch = branches.find((b: any) => b.properties && "tasks" in b.properties); + assert(parallelBranch, "parallel branch present"); + assert.deepEqual(parallelBranch.properties.tasks.items.properties.model.enum, ["openai/gpt-6"]); + } } finally { if (original === undefined) delete process.env.PI_SUBAGENTS_CHILD; else process.env.PI_SUBAGENTS_CHILD = original; diff --git a/src/schema.ts b/src/schema.ts index 09c8ab7..5e3c1dd 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -21,15 +21,20 @@ export function createTaskItemSchema(availableModels: readonly string[]) { export const TaskItemSchema = createTaskItemSchema([]); export function createSubagentParamsSchema(availableModels: readonly string[]) { - return Type.Object({ - // Single mode: provide preset + task - preset: Type.Optional(Type.String({ description: "Subagent preset name to use in single mode" })), - task: Type.Optional(Type.String({ description: "Single-mode delegated task" })), - // Parallel mode: provide tasks array where each item names its own preset - tasks: Type.Optional(Type.Array(createTaskItemSchema(availableModels), { description: "Parallel tasks" })), + // Single-mode schema: requires preset + task, exposes optional top-level model + const SingleMode = Type.Object({ + preset: Type.String({ description: "Subagent preset name to use in single mode" }), + task: Type.String({ description: "Single-mode delegated task" }), + model: createTaskModelSchema(availableModels), cwd: Type.Optional(Type.String({ description: "Single-mode working directory override" })), - }); + + // Parallel-mode schema: requires tasks array where each item contains its own preset and task + const ParallelMode = Type.Object({ + tasks: Type.Array(createTaskItemSchema(availableModels), { description: "Parallel tasks" }), + }); + + return Type.Union([SingleMode, ParallelMode], { description: "Either single-mode (preset+task) or parallel-mode (tasks array)" }); } export const SubagentParamsSchema = createSubagentParamsSchema([]);