mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 23:44:41 +00:00
feat(settings): import existing project setup
This commit is contained in:
@@ -34,37 +34,94 @@ async function ensureZodAotRuntimeImportExtensionPatch() {
|
||||
|
||||
async function ensureZodAotDiscriminatedUnionOutputPatch() {
|
||||
let discriminatedUnionEmitter = await readFile(discriminatedUnionPath, "utf8");
|
||||
let changed = false;
|
||||
|
||||
if (
|
||||
discriminatedUnionEmitter.includes(
|
||||
!discriminatedUnionEmitter.includes(
|
||||
"const needsOutputPropagation = ir.options.some(hasMutation);",
|
||||
)
|
||||
) {
|
||||
return;
|
||||
const importBefore = 'import { escapeString } from "../context.js";';
|
||||
const importAfter = 'import { escapeString, hasMutation } from "../context.js";';
|
||||
const outputFlagBefore =
|
||||
"const discKey = escapeString(ir.discriminator);\n let code = emit `";
|
||||
const outputFlagAfter =
|
||||
"const discKey = escapeString(ir.discriminator);\n const needsOutputPropagation = ir.options.some(hasMutation);\n let code = emit `";
|
||||
const propagationBefore =
|
||||
" ${g.visit(option, { input: objVar, output: objVar })}\n break;`;";
|
||||
const propagationAfter =
|
||||
' ${g.visit(option, { input: objVar, output: objVar })}\n ${needsOutputPropagation ? `${g.output}=${objVar};` : ""}\n break;`;';
|
||||
|
||||
if (
|
||||
!discriminatedUnionEmitter.includes(importBefore) ||
|
||||
!discriminatedUnionEmitter.includes(outputFlagBefore) ||
|
||||
!discriminatedUnionEmitter.includes(propagationBefore)
|
||||
) {
|
||||
throw new Error("zod-aot discriminated-union emitter shape changed; update the output patch");
|
||||
}
|
||||
|
||||
discriminatedUnionEmitter = discriminatedUnionEmitter
|
||||
.replace(importBefore, importAfter)
|
||||
.replace(outputFlagBefore, outputFlagAfter)
|
||||
.replace(propagationBefore, propagationAfter);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const importBefore = 'import { escapeString } from "../context.js";';
|
||||
const importAfter = 'import { escapeString, hasMutation } from "../context.js";';
|
||||
const outputFlagBefore = "const discKey = escapeString(ir.discriminator);\n let code = emit `";
|
||||
const outputFlagAfter =
|
||||
"const discKey = escapeString(ir.discriminator);\n const needsOutputPropagation = ir.options.some(hasMutation);\n let code = emit `";
|
||||
const propagationBefore =
|
||||
" ${g.visit(option, { input: objVar, output: objVar })}\n break;`;";
|
||||
const propagationAfter =
|
||||
' ${g.visit(option, { input: objVar, output: objVar })}\n ${needsOutputPropagation ? `${g.output}=${objVar};` : ""}\n break;`;';
|
||||
if (!discriminatedUnionEmitter.includes("function discriminatorValueLiteral(")) {
|
||||
const helperBefore = 'import { invalidType } from "../emit-issue.js";\n';
|
||||
const helperAfter = `${helperBefore}function discriminatorValueLiteral(value, option, discriminator) {
|
||||
const property = option?.type === "object" ? option.properties?.[discriminator] : undefined;
|
||||
if (property?.type === "literal" && property.values.length === 1) {
|
||||
return JSON.stringify(property.values[0]);
|
||||
}
|
||||
return escapeString(value);
|
||||
}
|
||||
`;
|
||||
const slowCaseBefore = " const option = ir.options[index];\n code += emit `";
|
||||
const slowCaseAfter =
|
||||
" const option = ir.options[index];\n const caseValue = discriminatorValueLiteral(value, option, ir.discriminator);\n code += emit `";
|
||||
const slowCaseValueBefore = " case ${escapeString(value)}:";
|
||||
const slowCaseValueAfter = " case ${caseValue}:";
|
||||
const validValuesBefore = `const validValues = Object.keys(ir.mapping)
|
||||
.map((v) => escapeString(v))
|
||||
.join(",");`;
|
||||
const validValuesAfter = `const validValues = Object.entries(ir.mapping)
|
||||
.map(([v, i]) => discriminatorValueLiteral(v, ir.options[i], ir.discriminator))
|
||||
.join(",");`;
|
||||
const fastCaseBefore =
|
||||
" const option = ir.options[index];\n const check = g.visit(option, { input: helperParam });";
|
||||
const fastCaseAfter =
|
||||
" const option = ir.options[index];\n const caseValue = discriminatorValueLiteral(value, option, ir.discriminator);\n const check = g.visit(option, { input: helperParam });";
|
||||
const fastCaseValueBefore =
|
||||
" cases.push(`case ${escapeString(value)}:return ${check};`);";
|
||||
const fastCaseValueAfter = " cases.push(`case ${caseValue}:return ${check};`);";
|
||||
|
||||
if (
|
||||
!discriminatedUnionEmitter.includes(importBefore) ||
|
||||
!discriminatedUnionEmitter.includes(outputFlagBefore) ||
|
||||
!discriminatedUnionEmitter.includes(propagationBefore)
|
||||
) {
|
||||
throw new Error("zod-aot discriminated-union emitter shape changed; update the output patch");
|
||||
if (
|
||||
!discriminatedUnionEmitter.includes(helperBefore) ||
|
||||
!discriminatedUnionEmitter.includes(slowCaseBefore) ||
|
||||
!discriminatedUnionEmitter.includes(slowCaseValueBefore) ||
|
||||
!discriminatedUnionEmitter.includes(validValuesBefore) ||
|
||||
!discriminatedUnionEmitter.includes(fastCaseBefore) ||
|
||||
!discriminatedUnionEmitter.includes(fastCaseValueBefore)
|
||||
) {
|
||||
throw new Error(
|
||||
"zod-aot discriminated-union emitter shape changed; update the literal patch",
|
||||
);
|
||||
}
|
||||
|
||||
discriminatedUnionEmitter = discriminatedUnionEmitter
|
||||
.replace(helperBefore, helperAfter)
|
||||
.replace(slowCaseBefore, slowCaseAfter)
|
||||
.replace(slowCaseValueBefore, slowCaseValueAfter)
|
||||
.replace(validValuesBefore, validValuesAfter)
|
||||
.replace(fastCaseBefore, fastCaseAfter)
|
||||
.replace(fastCaseValueBefore, fastCaseValueAfter);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
discriminatedUnionEmitter = discriminatedUnionEmitter
|
||||
.replace(importBefore, importAfter)
|
||||
.replace(outputFlagBefore, outputFlagAfter)
|
||||
.replace(propagationBefore, propagationAfter);
|
||||
await writeFile(discriminatedUnionPath, discriminatedUnionEmitter);
|
||||
if (changed) {
|
||||
await writeFile(discriminatedUnionPath, discriminatedUnionEmitter);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
|
||||
@@ -66,12 +66,19 @@ import {
|
||||
PaseoScriptEntryRawSchema,
|
||||
PaseoWorktreeConfigRawSchema,
|
||||
PaseoConfigRevisionSchema,
|
||||
ProjectConfigImportItemSchema,
|
||||
ProjectConfigImportPreviewSchema,
|
||||
ProjectConfigImportSourceSchema,
|
||||
ProjectConfigRpcErrorSchema,
|
||||
type PaseoConfigRaw,
|
||||
type PaseoConfigRevision,
|
||||
type PaseoMetadataGeneration,
|
||||
type PaseoMetadataGenerationEntry,
|
||||
type PaseoScriptEntryRaw,
|
||||
type ProjectConfigImportInput,
|
||||
type ProjectConfigImportItem,
|
||||
type ProjectConfigImportPreview,
|
||||
type ProjectConfigImportSource,
|
||||
type ProjectConfigRpcError,
|
||||
} from "./paseo-config-schema.js";
|
||||
export {
|
||||
@@ -81,11 +88,18 @@ export {
|
||||
PaseoMetadataGenerationSchema,
|
||||
PaseoScriptEntryRawSchema,
|
||||
PaseoWorktreeConfigRawSchema,
|
||||
ProjectConfigImportItemSchema,
|
||||
ProjectConfigImportPreviewSchema,
|
||||
ProjectConfigImportSourceSchema,
|
||||
type PaseoConfigRaw,
|
||||
type PaseoConfigRevision,
|
||||
type PaseoMetadataGeneration,
|
||||
type PaseoMetadataGenerationEntry,
|
||||
type PaseoScriptEntryRaw,
|
||||
type ProjectConfigImportInput,
|
||||
type ProjectConfigImportItem,
|
||||
type ProjectConfigImportPreview,
|
||||
type ProjectConfigImportSource,
|
||||
type ProjectConfigRpcError,
|
||||
};
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1165,6 +1179,22 @@ export const WriteProjectConfigRequestMessageSchema = z.object({
|
||||
expectedRevision: PaseoConfigRevisionSchema.nullable(),
|
||||
});
|
||||
|
||||
export const GetProjectConfigImportRequestMessageSchema = z.object({
|
||||
type: z.literal("project.config.get_import.request"),
|
||||
requestId: z.string(),
|
||||
repoRoot: z.string(),
|
||||
source: ProjectConfigImportSourceSchema,
|
||||
});
|
||||
|
||||
export const ApplyProjectConfigImportRequestMessageSchema = z.object({
|
||||
type: z.literal("project.config.apply_import.request"),
|
||||
requestId: z.string(),
|
||||
repoRoot: z.string(),
|
||||
source: ProjectConfigImportSourceSchema,
|
||||
expectedSourceRevision: z.string(),
|
||||
expectedPaseoRevision: PaseoConfigRevisionSchema.nullable(),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Dictation Streaming (lossless, resumable)
|
||||
// ============================================================================
|
||||
@@ -2336,6 +2366,8 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
SetDaemonConfigRequestMessageSchema,
|
||||
ReadProjectConfigRequestMessageSchema,
|
||||
WriteProjectConfigRequestMessageSchema,
|
||||
GetProjectConfigImportRequestMessageSchema,
|
||||
ApplyProjectConfigImportRequestMessageSchema,
|
||||
DictationStreamStartMessageSchema,
|
||||
DictationStreamChunkMessageSchema,
|
||||
DictationStreamFinishMessageSchema,
|
||||
@@ -2674,6 +2706,8 @@ export const ServerInfoStatusPayloadSchema = z
|
||||
providerRemoval: z.boolean().optional(),
|
||||
// COMPAT(importSessionWorkspaceTarget): added in v0.1.110, remove gate after 2027-01-16.
|
||||
importSessionWorkspaceTarget: z.boolean().optional(),
|
||||
// COMPAT(projectConfigImportConductor): added in v0.1.X, drop the gate when floor >= v0.1.X.
|
||||
projectConfigImportConductor: z.boolean().optional(),
|
||||
// COMPAT(forgeProviders): added in v0.1.106, drop the gate when daemon floor >= v0.1.106.
|
||||
// Daemon advertises pluggable non-GitHub forge support (the forge registry);
|
||||
// the client gates non-GitHub setup UI on it.
|
||||
@@ -3624,6 +3658,43 @@ export const WriteProjectConfigResponseMessageSchema = z.object({
|
||||
]),
|
||||
});
|
||||
|
||||
export const GetProjectConfigImportResponseMessageSchema = z.object({
|
||||
type: z.literal("project.config.get_import.response"),
|
||||
payload: z.discriminatedUnion("ok", [
|
||||
ProjectConfigImportPreviewSchema.extend({
|
||||
requestId: z.string(),
|
||||
ok: z.literal(true),
|
||||
}),
|
||||
z.object({
|
||||
requestId: z.string(),
|
||||
repoRoot: z.string(),
|
||||
ok: z.literal(false),
|
||||
error: ProjectConfigRpcErrorSchema,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const ApplyProjectConfigImportResponseMessageSchema = z.object({
|
||||
type: z.literal("project.config.apply_import.response"),
|
||||
payload: z.discriminatedUnion("ok", [
|
||||
z.object({
|
||||
requestId: z.string(),
|
||||
repoRoot: z.string(),
|
||||
source: ProjectConfigImportSourceSchema,
|
||||
ok: z.literal(true),
|
||||
config: PaseoConfigRawSchema,
|
||||
revision: PaseoConfigRevisionSchema,
|
||||
items: z.array(ProjectConfigImportItemSchema),
|
||||
}),
|
||||
z.object({
|
||||
requestId: z.string(),
|
||||
repoRoot: z.string(),
|
||||
ok: z.literal(false),
|
||||
error: ProjectConfigRpcErrorSchema,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
export const AgentPermissionRequestMessageSchema = z.object({
|
||||
type: z.literal("agent_permission_request"),
|
||||
payload: z.object({
|
||||
@@ -4846,6 +4917,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
SetDaemonConfigResponseMessageSchema,
|
||||
ReadProjectConfigResponseMessageSchema,
|
||||
WriteProjectConfigResponseMessageSchema,
|
||||
GetProjectConfigImportResponseMessageSchema,
|
||||
ApplyProjectConfigImportResponseMessageSchema,
|
||||
SetAgentModeResponseMessageSchema,
|
||||
SetAgentModelResponseMessageSchema,
|
||||
SetAgentThinkingResponseMessageSchema,
|
||||
|
||||
@@ -79,13 +79,67 @@ export const PaseoConfigRevisionSchema = z.object({
|
||||
size: z.number(),
|
||||
});
|
||||
|
||||
export const ProjectConfigImportSourceSchema = z.discriminatedUnion("kind", [
|
||||
z.object({ kind: z.literal("conductor") }),
|
||||
]);
|
||||
|
||||
export const ProjectConfigImportInputSchema = z.object({
|
||||
role: z.string(),
|
||||
relativePath: z.string(),
|
||||
});
|
||||
|
||||
export const ProjectConfigImportItemOutcomeSchema = z.enum([
|
||||
"import",
|
||||
"rewrite",
|
||||
"collision",
|
||||
"unsupported",
|
||||
]);
|
||||
|
||||
export const ProjectConfigImportItemSchema = z.object({
|
||||
key: z.string(),
|
||||
label: z.string(),
|
||||
outcome: ProjectConfigImportItemOutcomeSchema,
|
||||
detail: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ProjectConfigImportStatusSchema = z.enum([
|
||||
"available",
|
||||
"not_found",
|
||||
"nothing_to_import",
|
||||
]);
|
||||
|
||||
export const ProjectConfigImportPreviewSchema = z.object({
|
||||
repoRoot: z.string(),
|
||||
source: ProjectConfigImportSourceSchema,
|
||||
status: ProjectConfigImportStatusSchema,
|
||||
sourceRevision: z.string().nullable(),
|
||||
paseoRevision: PaseoConfigRevisionSchema.nullable(),
|
||||
inputs: z.array(ProjectConfigImportInputSchema),
|
||||
items: z.array(ProjectConfigImportItemSchema),
|
||||
preview: PaseoConfigRawSchema.nullable(),
|
||||
});
|
||||
|
||||
export const ProjectConfigRpcErrorSchema = z.discriminatedUnion("code", [
|
||||
z.object({ code: z.literal("project_not_found") }),
|
||||
z.object({
|
||||
code: z.literal("source_config_not_found"),
|
||||
source: ProjectConfigImportSourceSchema,
|
||||
}),
|
||||
z.object({
|
||||
code: z.literal("invalid_source_config"),
|
||||
source: ProjectConfigImportSourceSchema,
|
||||
relativePath: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
code: z.literal("stale_source_config"),
|
||||
source: ProjectConfigImportSourceSchema,
|
||||
}),
|
||||
z.object({ code: z.literal("invalid_project_config") }),
|
||||
z.object({
|
||||
code: z.literal("stale_project_config"),
|
||||
currentRevision: PaseoConfigRevisionSchema.nullable(),
|
||||
}),
|
||||
z.object({ code: z.literal("nothing_to_import") }),
|
||||
z.object({ code: z.literal("write_failed") }),
|
||||
]);
|
||||
|
||||
@@ -95,4 +149,8 @@ export type PaseoMetadataGeneration = z.infer<typeof PaseoMetadataGenerationSche
|
||||
export type PaseoConfigRaw = z.infer<typeof PaseoConfigRawSchema>;
|
||||
export type PaseoConfig = z.infer<typeof PaseoConfigSchema>;
|
||||
export type PaseoConfigRevision = z.infer<typeof PaseoConfigRevisionSchema>;
|
||||
export type ProjectConfigImportSource = z.infer<typeof ProjectConfigImportSourceSchema>;
|
||||
export type ProjectConfigImportInput = z.infer<typeof ProjectConfigImportInputSchema>;
|
||||
export type ProjectConfigImportItem = z.infer<typeof ProjectConfigImportItemSchema>;
|
||||
export type ProjectConfigImportPreview = z.infer<typeof ProjectConfigImportPreviewSchema>;
|
||||
export type ProjectConfigRpcError = z.infer<typeof ProjectConfigRpcErrorSchema>;
|
||||
|
||||
@@ -82,6 +82,24 @@ const SourceSchema = z.discriminatedUnion("type", [
|
||||
});
|
||||
});
|
||||
|
||||
it("routes boolean discriminated-union branches with boolean switch cases", async () => {
|
||||
const schema = await compileInlineSchema(`
|
||||
const SourceSchema = z.discriminatedUnion("ok", [
|
||||
z.object({ ok: z.literal(true), value: z.string() }),
|
||||
z.object({ ok: z.literal(false), error: z.string() }),
|
||||
]);
|
||||
`);
|
||||
|
||||
expect(schema.safeParse({ ok: true, value: "yes" })).toMatchObject({
|
||||
success: true,
|
||||
data: { ok: true, value: "yes" },
|
||||
});
|
||||
expect(schema.safeParse({ ok: false, error: "no" })).toMatchObject({
|
||||
success: true,
|
||||
data: { ok: false, error: "no" },
|
||||
});
|
||||
});
|
||||
|
||||
it("routes tool-call-like status unions through the current sequential item union", async () => {
|
||||
const schema = await compileInlineSchema(`
|
||||
const ToolCallItemSchema = z.discriminatedUnion("status", [
|
||||
|
||||
Reference in New Issue
Block a user