5684cbef77
* tweak examples * tweak prompt * Implement support for output schemas * fix: add example for structured output with zod schema * tweak * remove redundant cast * fix input name * strip $schema * hack around vendor requirement * clarify required result output --------- Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
import { Ajv } from "ajv";
|
|
import { type } from "arktype";
|
|
import { log } from "../utils/cli.ts";
|
|
import type { ToolContext } from "./server.ts";
|
|
import { execute, tool } from "./shared.ts";
|
|
|
|
export const SetOutputParams = type({
|
|
value: type.string.describe("the output value to expose as a GitHub Action output"),
|
|
});
|
|
|
|
type JsonSchema = Record<string, unknown>;
|
|
|
|
function jsonSchemaToStandardSchema({
|
|
$schema: _,
|
|
...jsonSchema
|
|
}: JsonSchema): StandardSchemaV1<any> & { toJsonSchema(): JsonSchema } {
|
|
const ajv = new Ajv();
|
|
const validate = ajv.compile(jsonSchema);
|
|
|
|
return {
|
|
"~standard": {
|
|
version: 1,
|
|
// xsschema (used by fastmcp) hardcodes supported vendors instead of duck-typing toJsonSchema().
|
|
// "arktype" works because its converter is just `(schema) => schema.toJsonSchema()`.
|
|
vendor: "arktype",
|
|
validate(input: unknown) {
|
|
if (validate(input)) {
|
|
return { value: input };
|
|
}
|
|
return {
|
|
issues: (validate.errors ?? []).map((err) => ({
|
|
message: `${err.instancePath || "/"}: ${err.message ?? "validation error"}`,
|
|
path: err.instancePath ? err.instancePath.split("/").filter(Boolean) : [],
|
|
})),
|
|
};
|
|
},
|
|
},
|
|
toJsonSchema() {
|
|
return jsonSchema;
|
|
},
|
|
};
|
|
}
|
|
|
|
function storeOutput(ctx: ToolContext, value: string) {
|
|
const selfId = ctx.toolState.selfSubagentId;
|
|
if (selfId) {
|
|
const subagent = ctx.toolState.subagents.get(selfId);
|
|
if (subagent) {
|
|
subagent.output = value;
|
|
log.debug(`set_output: routed to subagent ${selfId} (value=${value.slice(0, 80)})`);
|
|
return { success: true, routed: "subagent" as const };
|
|
}
|
|
log.warning(
|
|
`set_output: selfSubagentId=${selfId} but subagent not found in map — routing to action output`
|
|
);
|
|
}
|
|
ctx.toolState.output = value;
|
|
return { success: true, routed: "action_output" as const };
|
|
}
|
|
|
|
export function SetOutputTool(ctx: ToolContext, outputSchema?: JsonSchema) {
|
|
if (outputSchema) {
|
|
return tool({
|
|
name: "set_output",
|
|
description:
|
|
"Set the structured action output. You MUST call this tool before finishing — the output is required. Pass the output object directly as the tool arguments (no wrapping needed).",
|
|
parameters: jsonSchemaToStandardSchema(outputSchema),
|
|
execute: execute(async (params) => {
|
|
return storeOutput(ctx, JSON.stringify(params));
|
|
}),
|
|
});
|
|
}
|
|
|
|
return tool({
|
|
name: "set_output",
|
|
description:
|
|
"Set the action output. When called by a subagent, returns a summary result to the orchestrator — this is the ONLY way to pass results back. When called by the orchestrator in standalone mode (trigger: unknown), exposes the value as the 'result' GitHub Action output for downstream workflow steps. Do NOT use this for progress reporting — use report_progress instead.",
|
|
parameters: SetOutputParams,
|
|
execute: execute(async (params) => {
|
|
return storeOutput(ctx, params.value);
|
|
}),
|
|
});
|
|
}
|