mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Compare commits
2 Commits
v0.1.105
...
lenient-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eec33773a | ||
|
|
5616c6af6a |
@@ -195,6 +195,9 @@ Single file, validated with `PersistedConfigSchema`.
|
||||
|
||||
All fields are optional with sensible defaults.
|
||||
|
||||
Config parsing strips unrecognized object keys so a config written by a newer daemon does not brick
|
||||
older read paths such as `paseo daemon status`. Malformed known fields still fail validation.
|
||||
|
||||
`agents.metadataGeneration.providers` controls the preferred structured-generation fallback order for daemon-side metadata tasks such as commit messages, PR text, branch names, and generated agent titles. Entries are tried first in the configured order, then Paseo falls through to dynamically discovered defaults and finally the current selection when available.
|
||||
|
||||
Local speech model ids are intentionally narrow: STT uses `parakeet-tdt-0.6b-v2-int8`, TTS uses `kokoro-en-v0_19`, and turn detection uses the bundled Silero VAD model.
|
||||
|
||||
@@ -587,8 +587,8 @@ describe("PersistedConfigSchema logging config", () => {
|
||||
expect(parsed.log?.format).toBe("json");
|
||||
});
|
||||
|
||||
test("rejects unknown logging config fields", () => {
|
||||
const result = PersistedConfigSchema.safeParse({
|
||||
test("strips unknown logging config fields", () => {
|
||||
const parsed = PersistedConfigSchema.parse({
|
||||
log: {
|
||||
console: {
|
||||
level: "info",
|
||||
@@ -597,7 +597,7 @@ describe("PersistedConfigSchema logging config", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(parsed.log?.console).toEqual({ level: "info" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -669,6 +669,42 @@ describe("loadPersistedConfig", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("loads a config with unrecognized persisted fields", () => {
|
||||
const home = createTempHome();
|
||||
const configPath = path.join(home, "config.json");
|
||||
try {
|
||||
writeFileSync(
|
||||
configPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
daemon: {
|
||||
listen: "127.0.0.1:6767",
|
||||
futureDaemonSetting: { enabled: true },
|
||||
relay: {
|
||||
enabled: true,
|
||||
futureRelaySetting: "enabled",
|
||||
},
|
||||
},
|
||||
futureRootSetting: true,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
const config = loadPersistedConfig(home);
|
||||
|
||||
expect(config.daemon?.listen).toBe("127.0.0.1:6767");
|
||||
expect(config.daemon?.relay?.enabled).toBe(true);
|
||||
expect((config.daemon as Record<string, unknown>)?.futureDaemonSetting).toBeUndefined();
|
||||
expect((config.daemon?.relay as Record<string, unknown>)?.futureRelaySetting).toBeUndefined();
|
||||
expect((config as Record<string, unknown>).futureRootSetting).toBeUndefined();
|
||||
} finally {
|
||||
rmSync(home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("loads a config that still uses the removed providers.openai.voice block", () => {
|
||||
const home = createTempHome();
|
||||
const configPath = path.join(home, "config.json");
|
||||
|
||||
@@ -25,7 +25,7 @@ const LogConfigSchema = z
|
||||
level: LogLevelSchema.optional(),
|
||||
format: LogFormatSchema.optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
|
||||
file: z
|
||||
@@ -37,20 +37,20 @@ const LogConfigSchema = z
|
||||
maxSize: z.string().min(1).optional(),
|
||||
maxFiles: z.number().int().positive().optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const OpenAiSpeechEndpointSchema = z
|
||||
.object({
|
||||
apiKey: z.string().trim().min(1).optional(),
|
||||
baseUrl: z.string().trim().min(1).optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const OpenAiProviderSchema = z
|
||||
.object({
|
||||
@@ -59,26 +59,26 @@ const OpenAiProviderSchema = z
|
||||
stt: OpenAiSpeechEndpointSchema.optional(),
|
||||
tts: OpenAiSpeechEndpointSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const LocalSpeechProviderSchema = z
|
||||
.object({
|
||||
modelsDir: z.string().min(1).optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const ProvidersSchema = z
|
||||
.object({
|
||||
openai: OpenAiProviderSchema.optional(),
|
||||
local: LocalSpeechProviderSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const WorktreesConfigSchema = z
|
||||
.object({
|
||||
root: z.string().min(1).optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const BcryptHashSchema = z.string().regex(/^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/, {
|
||||
message: "Expected a bcrypt hash",
|
||||
@@ -88,7 +88,7 @@ const DaemonAuthSchema = z
|
||||
.object({
|
||||
password: BcryptHashSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const SpeechProviderIdSchema = z
|
||||
.string()
|
||||
@@ -106,10 +106,10 @@ const FeatureDictationSchema = z
|
||||
language: z.string().trim().min(1).optional(),
|
||||
confidenceThreshold: z.number().optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const FeatureVoiceModeSchema = z
|
||||
.object({
|
||||
@@ -119,7 +119,7 @@ const FeatureVoiceModeSchema = z
|
||||
provider: z.string().optional(),
|
||||
model: z.string().min(1).optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
stt: z
|
||||
.object({
|
||||
@@ -127,13 +127,13 @@ const FeatureVoiceModeSchema = z
|
||||
model: z.string().min(1).optional(),
|
||||
language: z.string().trim().min(1).optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
turnDetection: z
|
||||
.object({
|
||||
provider: SpeechProviderIdSchema.optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
tts: z
|
||||
.object({
|
||||
@@ -143,17 +143,17 @@ const FeatureVoiceModeSchema = z
|
||||
speakerId: z.number().int().optional(),
|
||||
speed: z.number().optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const FeatureWebUiSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
distDir: z.string().min(1).optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const StructuredGenerationProviderConfigSchema = z
|
||||
.object({
|
||||
@@ -161,13 +161,13 @@ const StructuredGenerationProviderConfigSchema = z
|
||||
model: z.string().min(1).optional(),
|
||||
thinkingOptionId: z.string().min(1).optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const AgentMetadataGenerationSchema = z
|
||||
.object({
|
||||
providers: z.array(StructuredGenerationProviderConfigSchema).optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
const BUILTIN_PROVIDER_IDS = ["claude", "codex", "copilot", "opencode", "pi", "omp"] as const;
|
||||
|
||||
@@ -254,7 +254,7 @@ export const PersistedConfigSchema = z
|
||||
.object({
|
||||
allowedOrigins: z.array(z.string()).optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
relay: z
|
||||
.object({
|
||||
@@ -264,7 +264,7 @@ export const PersistedConfigSchema = z
|
||||
useTls: z.boolean().optional(),
|
||||
publicUseTls: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
serviceProxy: z
|
||||
.object({
|
||||
@@ -275,11 +275,11 @@ export const PersistedConfigSchema = z
|
||||
listen: z.string().optional(),
|
||||
publicBaseUrl: z.url().optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
auth: DaemonAuthSchema.optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.transform(({ allowedHosts, ...daemon }) => {
|
||||
const hostnames = daemon.hostnames ?? allowedHosts;
|
||||
return hostnames === undefined ? daemon : { ...daemon, hostnames };
|
||||
@@ -290,7 +290,7 @@ export const PersistedConfigSchema = z
|
||||
.object({
|
||||
baseUrl: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
|
||||
providers: ProvidersSchema.optional(),
|
||||
@@ -300,7 +300,7 @@ export const PersistedConfigSchema = z
|
||||
providers: z.preprocess(normalizeAgentProviders, ProviderOverridesSchema).optional(),
|
||||
metadataGeneration: AgentMetadataGenerationSchema.optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
features: z
|
||||
.object({
|
||||
@@ -308,12 +308,12 @@ export const PersistedConfigSchema = z
|
||||
voiceMode: FeatureVoiceModeSchema.optional(),
|
||||
webUi: FeatureWebUiSchema.optional(),
|
||||
})
|
||||
.strict()
|
||||
.strip()
|
||||
.optional(),
|
||||
|
||||
log: LogConfigSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
.strip();
|
||||
|
||||
type PersistedConfigSchemaOutput = z.infer<typeof PersistedConfigSchema>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user