refactor: remove schemaDefaults, use caller-provided dryRunMeta
Cursor completed removal of schemaDefaults. All dry-run paths now use explicit dryRunMeta from the caller.
This commit is contained in:
@@ -58,7 +58,7 @@ describe("createCommitterRole", () => {
|
||||
};
|
||||
const role = createCommitterRole(
|
||||
agent,
|
||||
{ provider, dryRun: null },
|
||||
{ provider, dryRun: null, dryRunMeta: { branch: "dry-run", message: "chore: dry run" } },
|
||||
{ cwd: repo, remote: "origin", threadId: null },
|
||||
);
|
||||
const out = await role(makeCtx());
|
||||
@@ -69,7 +69,11 @@ describe("createCommitterRole", () => {
|
||||
const agent: AgentFn = async () => {
|
||||
throw new Error("agent should not run");
|
||||
};
|
||||
const role = createCommitterRole(agent, { provider, dryRun: true });
|
||||
const role = createCommitterRole(agent, {
|
||||
provider,
|
||||
dryRun: true,
|
||||
dryRunMeta: { branch: "dry-run", message: "chore: dry run" },
|
||||
});
|
||||
const out = await role(makeCtx());
|
||||
expect(out.content).toBe("[dry-run] committer skipped");
|
||||
expect(out.meta).toEqual({ committed: true });
|
||||
@@ -87,7 +91,7 @@ describe("createCommitterRole", () => {
|
||||
const agent: AgentFn = async () => "plan text";
|
||||
const role = createCommitterRole(
|
||||
agent,
|
||||
{ provider, dryRun: null },
|
||||
{ provider, dryRun: null, dryRunMeta: { branch: "dry-run", message: "chore: dry run" } },
|
||||
{ cwd: repo, remote: "origin", threadId: null },
|
||||
);
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ const committerPlanSchema = z.object({
|
||||
message: z.string().describe("Single-line conventional commit subject"),
|
||||
});
|
||||
|
||||
export type CommitterPlanMeta = z.infer<typeof committerPlanSchema>;
|
||||
|
||||
export type CommitterGitConfig = {
|
||||
cwd: string;
|
||||
remote: string;
|
||||
@@ -90,7 +92,7 @@ Reply with enough detail that a maintainer understands the change; structured ex
|
||||
async function runCommitterPipeline(
|
||||
ctx: ThreadContext,
|
||||
agent: AgentFn,
|
||||
extract: { provider: LlmProvider; dryRun: boolean | null },
|
||||
extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: CommitterPlanMeta },
|
||||
gitConfig: CommitterGitConfig,
|
||||
): Promise<RoleResult<CommitterMeta>> {
|
||||
const cwd = gitConfig.cwd;
|
||||
@@ -107,6 +109,7 @@ async function runCommitterPipeline(
|
||||
const plan = await extractMetaOrThrow("committer-plan", raw, committerPlanSchema, {
|
||||
provider: extract.provider,
|
||||
dryRun: resolveExtractDryRun(extract.dryRun),
|
||||
dryRunMeta: extract.dryRunMeta,
|
||||
});
|
||||
|
||||
const branch = sanitizeBranch(plan.branch);
|
||||
@@ -129,7 +132,7 @@ async function runCommitterPipeline(
|
||||
*/
|
||||
export function createCommitterRole(
|
||||
adapter: AgentFn,
|
||||
extract: { provider: LlmProvider; dryRun: boolean | null },
|
||||
extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: CommitterPlanMeta },
|
||||
gitConfig: CommitterGitConfig = DEFAULT_COMMITTER_GIT_CONFIG,
|
||||
): Role<CommitterMeta> {
|
||||
const inner: Role<CommitterMeta> = async (ctx) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export {
|
||||
type CommitterGitConfig,
|
||||
type CommitterMeta,
|
||||
type CommitterPlanMeta,
|
||||
committerMetaSchema,
|
||||
createCommitterRole,
|
||||
DEFAULT_COMMITTER_GIT_CONFIG,
|
||||
|
||||
@@ -64,7 +64,7 @@ describe("createRole", () => {
|
||||
schema,
|
||||
systemPrompt: "hello",
|
||||
agent,
|
||||
extract: { provider, dryRun: null },
|
||||
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||
});
|
||||
|
||||
const out = await role(makeCtx());
|
||||
@@ -85,7 +85,7 @@ describe("createRole", () => {
|
||||
schema: z.object({ n: z.number() }),
|
||||
systemPrompt: "p",
|
||||
agent,
|
||||
extract: { provider, dryRun: null },
|
||||
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||
});
|
||||
await role(makeCtx());
|
||||
|
||||
@@ -103,7 +103,7 @@ describe("createRole", () => {
|
||||
schema,
|
||||
systemPrompt: async (ctx) => `rounds=${ctx.steps.length}`,
|
||||
agent,
|
||||
extract: { provider, dryRun: null },
|
||||
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||
});
|
||||
|
||||
const ctx = makeCtx();
|
||||
@@ -121,7 +121,7 @@ describe("createRole", () => {
|
||||
schema: z.object({ n: z.number() }),
|
||||
systemPrompt: "p",
|
||||
agent,
|
||||
extract: { provider, dryRun: null },
|
||||
extract: { provider, dryRun: null, dryRunMeta: { n: 0 } },
|
||||
});
|
||||
await role(makeCtx());
|
||||
|
||||
@@ -129,7 +129,7 @@ describe("createRole", () => {
|
||||
"r1",
|
||||
"raw",
|
||||
expect.anything(),
|
||||
expect.objectContaining({ provider, dryRun: false }),
|
||||
expect.objectContaining({ provider, dryRun: false, dryRunMeta: { n: 0 } }),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -142,7 +142,7 @@ describe("createRole", () => {
|
||||
schema: z.object({ n: z.number() }),
|
||||
systemPrompt: "p",
|
||||
agent,
|
||||
extract: { provider, dryRun: true },
|
||||
extract: { provider, dryRun: true, dryRunMeta: { n: 0 } },
|
||||
});
|
||||
await role(makeCtx());
|
||||
|
||||
@@ -150,7 +150,7 @@ describe("createRole", () => {
|
||||
"r2",
|
||||
"raw",
|
||||
expect.anything(),
|
||||
expect.objectContaining({ dryRun: true }),
|
||||
expect.objectContaining({ dryRun: true, dryRunMeta: { n: 0 } }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ const provider = {
|
||||
describe("extractMetaOrThrow", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
test("dryRun returns schema-shaped defaults without calling fetch", async () => {
|
||||
test("dryRun returns dryRunMeta without calling fetch", async () => {
|
||||
let calls = 0;
|
||||
globalThis.fetch = () => {
|
||||
calls += 1;
|
||||
@@ -23,12 +23,13 @@ describe("extractMetaOrThrow", () => {
|
||||
const out = await extractMetaOrThrow("r", "raw", schema, {
|
||||
provider,
|
||||
dryRun: true,
|
||||
dryRunMeta: { n: 7 },
|
||||
});
|
||||
|
||||
globalThis.fetch = originalFetch;
|
||||
|
||||
expect(calls).toBe(0);
|
||||
expect(out).toEqual({ n: 0 });
|
||||
expect(out).toEqual({ n: 7 });
|
||||
});
|
||||
|
||||
test("throws when extraction fails after retry", async () => {
|
||||
@@ -53,7 +54,7 @@ describe("extractMetaOrThrow", () => {
|
||||
const schema = z.object({ n: z.number() });
|
||||
|
||||
await expect(
|
||||
extractMetaOrThrow("plan", "text", schema, { provider, dryRun: false }),
|
||||
extractMetaOrThrow("plan", "text", schema, { provider, dryRun: false, dryRunMeta: { n: 0 } }),
|
||||
).rejects.toThrow(/structured extraction failed after retry/);
|
||||
|
||||
globalThis.fetch = originalFetch;
|
||||
@@ -91,6 +92,7 @@ describe("extractMetaOrThrow", () => {
|
||||
const out = await extractMetaOrThrow("committer-plan", "plan text", schema, {
|
||||
provider,
|
||||
dryRun: false,
|
||||
dryRunMeta: { branch: "", message: "" },
|
||||
});
|
||||
|
||||
globalThis.fetch = originalFetch;
|
||||
|
||||
@@ -55,6 +55,7 @@ describe("llmExtract", () => {
|
||||
model: "m",
|
||||
},
|
||||
dryRun: false,
|
||||
dryRunMeta: { name: "", description: "" },
|
||||
});
|
||||
|
||||
globalThis.fetch = originalFetch;
|
||||
@@ -105,6 +106,7 @@ describe("llmExtract", () => {
|
||||
schema,
|
||||
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
|
||||
dryRun: false,
|
||||
dryRunMeta: { n: 0 },
|
||||
});
|
||||
|
||||
globalThis.fetch = originalFetch;
|
||||
@@ -116,7 +118,7 @@ describe("llmExtract", () => {
|
||||
expect(result.error.kind).toBe("schema_validation_failed");
|
||||
});
|
||||
|
||||
test("dryRun skips fetch and returns schema-shaped stub values", async () => {
|
||||
test("dryRun skips fetch and returns dryRunMeta", async () => {
|
||||
let calls = 0;
|
||||
globalThis.fetch = () => {
|
||||
calls += 1;
|
||||
@@ -129,6 +131,7 @@ describe("llmExtract", () => {
|
||||
schema,
|
||||
provider: { baseUrl: "https://example.com", apiKey: "k", model: "m" },
|
||||
dryRun: true,
|
||||
dryRunMeta: { n: 42 },
|
||||
});
|
||||
|
||||
globalThis.fetch = originalFetch;
|
||||
@@ -138,6 +141,6 @@ describe("llmExtract", () => {
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.value).toEqual({ n: 0 });
|
||||
expect(result.value).toEqual({ n: 42 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,8 +10,9 @@ export type CreateRoleArgs<M extends Record<string, unknown>> = {
|
||||
agent: AgentFn;
|
||||
extract: {
|
||||
provider: LlmProvider;
|
||||
/** When `true`, structured extract returns schema-shaped defaults. When `null`, live API extract. */
|
||||
/** When `true`, structured extract returns `dryRunMeta`. When `null`, live API extract. */
|
||||
dryRun: boolean | null;
|
||||
dryRunMeta: M;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,6 +29,7 @@ export function createRole<M extends Record<string, unknown>>(args: CreateRoleAr
|
||||
const meta = await extractMetaOrThrow(args.name, raw, args.schema, {
|
||||
provider: args.extract.provider,
|
||||
dryRun: resolveExtractDryRun(args.extract.dryRun),
|
||||
dryRunMeta: args.extract.dryRunMeta,
|
||||
});
|
||||
return { content: raw, meta };
|
||||
};
|
||||
|
||||
@@ -6,13 +6,14 @@ export async function extractMetaOrThrow<T extends Record<string, unknown>>(
|
||||
roleName: string,
|
||||
raw: string,
|
||||
schema: z.ZodType<T>,
|
||||
options: { provider: LlmProvider; dryRun: boolean },
|
||||
options: { provider: LlmProvider; dryRun: boolean; dryRunMeta: T },
|
||||
): Promise<T> {
|
||||
const result = await llmExtractWithRetry({
|
||||
text: raw,
|
||||
schema,
|
||||
provider: options.provider,
|
||||
dryRun: options.dryRun,
|
||||
dryRunMeta: options.dryRunMeta,
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new Error(
|
||||
|
||||
@@ -3,7 +3,6 @@ export {
|
||||
type OnFailOptions,
|
||||
onFail,
|
||||
type RoleDecorator,
|
||||
schemaDefaults,
|
||||
type WithDryRunOptions,
|
||||
withDryRun,
|
||||
} from "@uncaged/workflow-util-role";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { err, ok, type Result } from "@uncaged/workflow";
|
||||
import { schemaDefaults } from "@uncaged/workflow-util-role";
|
||||
import * as z from "zod/v4";
|
||||
import type { LlmProvider } from "./types.js";
|
||||
|
||||
@@ -10,6 +9,8 @@ export type LlmExtractArgs<T> = {
|
||||
schema: z.ZodType<T>;
|
||||
provider: LlmProvider;
|
||||
dryRun: boolean;
|
||||
/** Returned when `dryRun` is true (ignored for live extract). */
|
||||
dryRunMeta: T;
|
||||
};
|
||||
|
||||
export type LlmError =
|
||||
@@ -128,7 +129,7 @@ async function performLlmExtract<T>(
|
||||
options: LlmExtractArgs<T> & { userContent: string },
|
||||
): Promise<Result<T, LlmError>> {
|
||||
if (options.dryRun) {
|
||||
return ok(schemaDefaults(options.schema) as T);
|
||||
return ok(options.dryRunMeta);
|
||||
}
|
||||
|
||||
const rawJsonSchema = z.toJSONSchema(options.schema) as Record<string, unknown>;
|
||||
|
||||
@@ -58,7 +58,11 @@ describe("createReviewerRole", () => {
|
||||
return "review done";
|
||||
};
|
||||
|
||||
const role = createReviewerRole(agent, { provider, dryRun: null });
|
||||
const role = createReviewerRole(agent, {
|
||||
provider,
|
||||
dryRun: null,
|
||||
dryRunMeta: { approved: true },
|
||||
});
|
||||
const out = await role(makeCtx());
|
||||
expect(out.meta).toEqual({ approved: true });
|
||||
});
|
||||
@@ -74,7 +78,7 @@ describe("createReviewerRole", () => {
|
||||
|
||||
const role = createReviewerRole(
|
||||
agent,
|
||||
{ provider, dryRun: null },
|
||||
{ provider, dryRun: null, dryRunMeta: { approved: false } },
|
||||
{
|
||||
cwd: "/proj",
|
||||
conventionsPath: null,
|
||||
|
||||
@@ -90,7 +90,7 @@ or
|
||||
*/
|
||||
export function createReviewerRole(
|
||||
adapter: AgentFn,
|
||||
extract: { provider: LlmProvider; dryRun: boolean | null },
|
||||
extract: { provider: LlmProvider; dryRun: boolean | null; dryRunMeta: ReviewerMeta },
|
||||
config: ReviewerConfig = DEFAULT_REVIEWER_CONFIG,
|
||||
): Role<ReviewerMeta> {
|
||||
return createRole({
|
||||
@@ -98,6 +98,10 @@ export function createReviewerRole(
|
||||
schema: reviewerMetaSchema,
|
||||
systemPrompt: async (ctx) => reviewerPrompt(config, ctx),
|
||||
agent: adapter,
|
||||
extract: { provider: extract.provider, dryRun: extract.dryRun },
|
||||
extract: {
|
||||
provider: extract.provider,
|
||||
dryRun: extract.dryRun,
|
||||
dryRunMeta: extract.dryRunMeta,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { AgentFn, Role } from "@uncaged/workflow";
|
||||
import { type CommitterMeta, createCommitterRole } from "@uncaged/workflow-role-committer";
|
||||
import {
|
||||
type CommitterMeta,
|
||||
type CommitterPlanMeta,
|
||||
createCommitterRole,
|
||||
} from "@uncaged/workflow-role-committer";
|
||||
import { createRole, type LlmProvider } from "@uncaged/workflow-role-llm";
|
||||
import { createReviewerRole, type ReviewerMeta } from "@uncaged/workflow-role-reviewer";
|
||||
import * as z from "zod/v4";
|
||||
@@ -33,6 +37,26 @@ export type PlannerMeta = z.infer<typeof plannerMetaSchema>;
|
||||
|
||||
export type CoderMeta = z.infer<typeof coderMetaSchema>;
|
||||
|
||||
const PLANNER_DRY_RUN_META: PlannerMeta = {
|
||||
plan: "",
|
||||
files: [],
|
||||
approach: "",
|
||||
};
|
||||
|
||||
const CODER_DRY_RUN_META: CoderMeta = {
|
||||
filesChanged: [],
|
||||
summary: "",
|
||||
};
|
||||
|
||||
const REVIEWER_DRY_RUN_META: ReviewerMeta = {
|
||||
approved: true,
|
||||
};
|
||||
|
||||
const COMMITTER_PLAN_DRY_RUN_META: CommitterPlanMeta = {
|
||||
branch: "dry-run",
|
||||
message: "chore: dry run",
|
||||
};
|
||||
|
||||
export type SolveIssueMeta = {
|
||||
planner: PlannerMeta;
|
||||
coder: CoderMeta;
|
||||
@@ -40,7 +64,7 @@ export type SolveIssueMeta = {
|
||||
committer: CommitterMeta;
|
||||
};
|
||||
|
||||
/** Wiring for workflow-role LLM structured extraction. Use null for schema-default dry runs (tests / stubs). */
|
||||
/** Wiring for workflow-role LLM structured extraction. Use `null` for stub extract (dry-run meta from built-in placeholders). */
|
||||
export type SolveIssueRolesConfig = {
|
||||
agent: AgentFn;
|
||||
workdir: string;
|
||||
@@ -83,7 +107,11 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
||||
schema: plannerMetaSchema,
|
||||
systemPrompt: PLANNER_SYSTEM,
|
||||
agent: config.agent,
|
||||
extract: { provider: extract.provider, dryRun: extract.dryRun },
|
||||
extract: {
|
||||
provider: extract.provider,
|
||||
dryRun: extract.dryRun,
|
||||
dryRunMeta: PLANNER_DRY_RUN_META,
|
||||
},
|
||||
});
|
||||
|
||||
const coder: Role<CoderMeta> = createRole({
|
||||
@@ -91,18 +119,30 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
||||
schema: coderMetaSchema,
|
||||
systemPrompt: CODER_SYSTEM,
|
||||
agent: config.agent,
|
||||
extract: { provider: extract.provider, dryRun: extract.dryRun },
|
||||
extract: {
|
||||
provider: extract.provider,
|
||||
dryRun: extract.dryRun,
|
||||
dryRunMeta: CODER_DRY_RUN_META,
|
||||
},
|
||||
});
|
||||
|
||||
const reviewer: Role<ReviewerMeta> = createReviewerRole(
|
||||
config.agent,
|
||||
{ provider: extract.provider, dryRun: extract.dryRun },
|
||||
{
|
||||
provider: extract.provider,
|
||||
dryRun: extract.dryRun,
|
||||
dryRunMeta: REVIEWER_DRY_RUN_META,
|
||||
},
|
||||
reviewerGit,
|
||||
);
|
||||
|
||||
const committer: Role<CommitterMeta> = createCommitterRole(
|
||||
config.agent,
|
||||
{ provider: extract.provider, dryRun: extract.dryRun },
|
||||
{
|
||||
provider: extract.provider,
|
||||
dryRun: extract.dryRun,
|
||||
dryRunMeta: COMMITTER_PLAN_DRY_RUN_META,
|
||||
},
|
||||
committerGit,
|
||||
);
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@ export {
|
||||
type WithDryRunOptions,
|
||||
withDryRun,
|
||||
} from "./decorators.js";
|
||||
export { schemaDefaults } from "./schema-defaults.js";
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import type * as z from "zod/v4";
|
||||
|
||||
type ZodTypeAny = z.ZodType;
|
||||
|
||||
type Def = Record<string, unknown> & { type: string };
|
||||
type TypeHandler = (schema: ZodTypeAny, def: Def) => unknown;
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isZodExactOptional(s: ZodTypeAny): boolean {
|
||||
return s.constructor.name === "ZodExactOptional";
|
||||
}
|
||||
|
||||
function resolveDefaultValue(defaultValue: unknown | (() => unknown)): unknown {
|
||||
if (typeof defaultValue === "function") {
|
||||
return (defaultValue as () => unknown)();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function mergeIntersection(left: unknown, right: unknown): unknown {
|
||||
if (isPlainObject(left) && isPlainObject(right)) {
|
||||
return { ...left, ...right };
|
||||
}
|
||||
return right;
|
||||
}
|
||||
|
||||
function defaultsForObject(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const shape = def.shape as Record<string, ZodTypeAny> | undefined;
|
||||
if (shape === undefined) {
|
||||
return {};
|
||||
}
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const key of Object.keys(shape)) {
|
||||
const child = shape[key];
|
||||
const cdef = child.def as { type: string };
|
||||
if (cdef.type === "optional") {
|
||||
if (isZodExactOptional(child)) {
|
||||
continue;
|
||||
}
|
||||
out[key] = undefined;
|
||||
} else {
|
||||
out[key] = schemaDefaultsInner(child);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function firstUnionOption(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const options = def.options as readonly ZodTypeAny[] | undefined;
|
||||
if (options === undefined || options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return schemaDefaultsInner(options[0]);
|
||||
}
|
||||
|
||||
function defaultsFromNullable(_schema: ZodTypeAny, _def: Def): unknown {
|
||||
return null;
|
||||
}
|
||||
|
||||
function defaultsFromInner(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const inner = def.innerType as ZodTypeAny | undefined;
|
||||
if (inner === undefined) {
|
||||
return null;
|
||||
}
|
||||
return schemaDefaultsInner(inner);
|
||||
}
|
||||
|
||||
function defaultsForPipe(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const out = def.out as ZodTypeAny | undefined;
|
||||
if (out === undefined) {
|
||||
return null;
|
||||
}
|
||||
return schemaDefaultsInner(out);
|
||||
}
|
||||
|
||||
function defaultsForIntersection(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const left = def.left as ZodTypeAny | undefined;
|
||||
const right = def.right as ZodTypeAny | undefined;
|
||||
if (left === undefined || right === undefined) {
|
||||
return null;
|
||||
}
|
||||
return mergeIntersection(schemaDefaultsInner(left), schemaDefaultsInner(right));
|
||||
}
|
||||
|
||||
function defaultsForTuple(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const items = def.items as readonly ZodTypeAny[] | undefined;
|
||||
if (items === undefined) {
|
||||
return [];
|
||||
}
|
||||
return items.map((item) => schemaDefaultsInner(item));
|
||||
}
|
||||
|
||||
function defaultsForLazy(schema: ZodTypeAny, def: Def): unknown {
|
||||
const inner =
|
||||
(schema as { _zod?: { innerType?: ZodTypeAny } })._zod?.innerType ??
|
||||
(def.getter as (() => ZodTypeAny) | undefined)?.();
|
||||
if (inner === undefined) {
|
||||
return null;
|
||||
}
|
||||
return schemaDefaultsInner(inner);
|
||||
}
|
||||
|
||||
function defaultsForPromise(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const inner = def.innerType as ZodTypeAny | undefined;
|
||||
if (inner === undefined) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return Promise.resolve(schemaDefaultsInner(inner));
|
||||
}
|
||||
|
||||
function firstEnumValue(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const entries = def.entries as Record<string, string | number> | undefined;
|
||||
if (entries === undefined) {
|
||||
return null;
|
||||
}
|
||||
const values = Object.values(entries);
|
||||
return values[0] ?? null;
|
||||
}
|
||||
|
||||
function firstLiteralValue(_schema: ZodTypeAny, def: Def): unknown {
|
||||
const values = def.values as unknown[] | undefined;
|
||||
if (values === undefined || values.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return values[0];
|
||||
}
|
||||
|
||||
const TYPE_HANDLERS: Record<string, TypeHandler> = {
|
||||
string: () => "",
|
||||
number: () => 0,
|
||||
boolean: () => false,
|
||||
bigint: () => 0n,
|
||||
date: () => new Date(0),
|
||||
symbol: () => Symbol(),
|
||||
undefined: () => undefined,
|
||||
null: () => null,
|
||||
void: () => undefined,
|
||||
any: () => null,
|
||||
unknown: () => null,
|
||||
never: () => undefined,
|
||||
nan: () => Number.NaN,
|
||||
array: () => [],
|
||||
object: defaultsForObject,
|
||||
record: () => ({}),
|
||||
map: () => new Map(),
|
||||
set: () => new Set(),
|
||||
enum: firstEnumValue,
|
||||
literal: firstLiteralValue,
|
||||
optional: () => undefined,
|
||||
nullable: defaultsFromNullable,
|
||||
default: (_s, def) => resolveDefaultValue(def.defaultValue as unknown | (() => unknown)),
|
||||
prefault: (_s, def) => resolveDefaultValue(def.defaultValue as unknown | (() => unknown)),
|
||||
nonoptional: defaultsFromInner,
|
||||
catch: defaultsFromInner,
|
||||
success: () => false,
|
||||
readonly: defaultsFromInner,
|
||||
union: firstUnionOption,
|
||||
xor: firstUnionOption,
|
||||
intersection: defaultsForIntersection,
|
||||
pipe: defaultsForPipe,
|
||||
transform: () => null,
|
||||
tuple: defaultsForTuple,
|
||||
lazy: defaultsForLazy,
|
||||
promise: defaultsForPromise,
|
||||
file: () => new File([], ""),
|
||||
function: () => null,
|
||||
custom: () => null,
|
||||
template_literal: () => "",
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces a structurally valid placeholder that mirrors primitive/array/object
|
||||
* shape for a Zod schema. Used for `llmExtract` dry runs so downstream code
|
||||
* does not throw on `undefined` fields.
|
||||
*/
|
||||
export function schemaDefaults(schema: z.ZodType): unknown {
|
||||
return schemaDefaultsInner(schema as ZodTypeAny);
|
||||
}
|
||||
|
||||
function schemaDefaultsInner(schema: ZodTypeAny): unknown {
|
||||
const def = schema.def as Def;
|
||||
const run = TYPE_HANDLERS[def.type];
|
||||
if (run === undefined) {
|
||||
return null;
|
||||
}
|
||||
return run(schema, def);
|
||||
}
|
||||
Reference in New Issue
Block a user