feat: wire paseo agent provider catalog

This commit is contained in:
Mohamed Boudra
2026-07-02 11:07:49 +02:00
parent fce3d14a83
commit 4e5737169f
20 changed files with 1226 additions and 83 deletions
@@ -30,7 +30,7 @@ describe("Paseo Agent config RPC schemas", () => {
name: "kimi-main",
providerType: "kimi-coding",
models: [{ id: "kimi-k3" }],
auth: { kind: "api_key", configured: true, source: "env" },
auth: { kind: "future_auth_kind", configured: true, source: "future_source" },
available: true,
error: null,
},
@@ -68,11 +68,120 @@ describe("Paseo Agent config RPC schemas", () => {
expect(JSON.stringify(parsed)).not.toContain("apiKey");
});
test("parses get_catalog request and response with forward-tolerant entries", () => {
const request = SessionInboundMessageSchema.parse({
type: "config.paseo_agent.get_catalog.request",
requestId: "req-catalog",
});
const response = SessionOutboundMessageSchema.parse({
type: "config.paseo_agent.get_catalog.response",
payload: {
requestId: "req-catalog",
catalog: [
{
id: "future-provider",
label: "Future Provider",
iconName: "sparkles",
docsUrl: "https://docs.example.test/provider",
api: "future-api",
baseUrl: "https://api.example.test",
headers: { "User-Agent": "PaseoTest/1" },
compat: { minHost: "0.1.104" },
auth: { kind: "future_oauth", flow: "future-flow", extraAuthField: true },
models: [
{
id: "future-model",
label: "Future Model",
futureModelField: "kept",
},
],
futureEntryField: { keep: true },
},
],
error: null,
},
});
expect(request.type).toBe("config.paseo_agent.get_catalog.request");
expect(response.payload.catalog[0]?.auth.kind).toBe("future_oauth");
expect(response.payload.catalog[0]?.futureEntryField).toEqual({ keep: true });
expect(response.payload.catalog[0]?.models[0]?.futureModelField).toBe("kept");
});
test("parses oauth.start request and device-code response", () => {
const request = SessionInboundMessageSchema.parse({
type: "config.paseo_agent.oauth.start.request",
requestId: "req-oauth-start",
name: "subscription",
});
const response = SessionOutboundMessageSchema.parse({
type: "config.paseo_agent.oauth.start.response",
payload: {
requestId: "req-oauth-start",
success: true,
name: "subscription",
authorization: {
kind: "device_code",
userCode: "ABCD-EFGH",
verificationUri: "https://auth.example.test/device",
intervalSeconds: 5,
expiresInSeconds: 900,
futureField: "kept",
},
error: null,
},
});
expect(request.name).toBe("subscription");
expect(response.payload.authorization?.kind).toBe("device_code");
expect(response.payload.authorization?.futureField).toBe("kept");
});
test("parses oauth.start auth-url response", () => {
const parsed = SessionOutboundMessageSchema.parse({
type: "config.paseo_agent.oauth.start.response",
payload: {
requestId: "req-oauth-start-url",
success: true,
name: "subscription",
authorization: {
kind: "auth_url",
url: "https://auth.example.test/oauth",
instructions: "Open this URL to continue.",
},
error: null,
},
});
expect(parsed.payload.authorization?.url).toBe("https://auth.example.test/oauth");
});
test("parses oauth.complete request and response", () => {
const request = SessionInboundMessageSchema.parse({
type: "config.paseo_agent.oauth.complete.request",
requestId: "req-oauth-complete",
name: "subscription",
});
const response = SessionOutboundMessageSchema.parse({
type: "config.paseo_agent.oauth.complete.response",
payload: {
requestId: "req-oauth-complete",
success: true,
name: "subscription",
auth: { kind: "oauth", configured: true, source: "stored" },
error: null,
},
});
expect(request.name).toBe("subscription");
expect(response.payload.auth?.configured).toBe(true);
});
test("preserves future OAuth credential fields on inbound schema parse", () => {
const parsed = SessionInboundMessageSchema.parse({
type: "config.paseo_agent.store_chatgpt_credential.request",
type: "config.paseo_agent.oauth.store_credential.request",
requestId: "req-oauth",
providerName: "chatgpt",
name: "subscription",
credential: {
type: "oauth",
access: "access-token",
@@ -86,6 +195,23 @@ describe("Paseo Agent config RPC schemas", () => {
expect(parsed.credential.futureField).toEqual({ keep: true });
});
test("parses oauth.store_credential response without credential material", () => {
const parsed = SessionOutboundMessageSchema.parse({
type: "config.paseo_agent.oauth.store_credential.response",
payload: {
requestId: "req-oauth",
success: true,
name: "subscription",
auth: { kind: "oauth", configured: true, source: "stored" },
error: null,
},
});
expect(parsed.payload.name).toBe("subscription");
expect(JSON.stringify(parsed)).not.toContain("access-token");
expect(JSON.stringify(parsed)).not.toContain("refresh-token");
});
test("parses ChatGPT provider config separately from credential storage", () => {
const parsed = SessionInboundMessageSchema.parse({
type: "config.paseo_agent.set_provider.request",
+125 -20
View File
@@ -1960,16 +1960,46 @@ const PaseoAgentOAuthCredentialSchema = z
})
.passthrough();
const PaseoAgentCatalogModelSchema = z
.object({
id: z.string().min(1),
label: z.string().min(1).optional(),
api: z.string().min(1).optional(),
reasoning: z.boolean().optional(),
contextWindow: z.number().int().positive().optional(),
maxTokens: z.number().int().positive().optional(),
})
.passthrough();
const PaseoAgentCatalogAuthSchema = z
.object({
kind: z.string().min(1),
})
.passthrough();
export const PaseoAgentCatalogEntrySchema = z
.object({
id: z.string().min(1),
label: z.string().min(1),
iconName: z.string().min(1).optional(),
docsUrl: z.string().optional(),
api: z.string().min(1),
baseUrl: z.string().min(1),
headers: z.record(z.string(), z.string()).optional(),
compat: z.record(z.string(), z.unknown()).optional(),
auth: PaseoAgentCatalogAuthSchema,
models: z.array(PaseoAgentCatalogModelSchema),
})
.passthrough();
export const PaseoAgentProviderAuthStateSchema = z
.object({
kind: z.enum(["api_key", "oauth", "none"]),
kind: z.string().min(1),
configured: z.boolean(),
source: z
.enum(["literal", "env", "default_env", "command", "stored", "refresh_token"])
.optional(),
source: z.string().min(1).optional(),
hint: z.string().optional(),
})
.strict();
.passthrough();
export const RedactedPaseoAgentProviderConfigSchema = z
.object({
@@ -1978,17 +2008,22 @@ export const RedactedPaseoAgentProviderConfigSchema = z
baseUrl: z.string().optional(),
api: z.string().optional(),
models: z.array(PaseoAgentProviderModelConfigSchema),
auth: PaseoAgentProviderAuthStateSchema,
auth: PaseoAgentProviderAuthStateSchema.optional(),
available: z.boolean(),
error: z.string().nullable().optional(),
})
.strict();
.passthrough();
export const PaseoAgentGetProvidersRequestSchema = z.object({
type: z.literal("config.paseo_agent.get_providers.request"),
requestId: z.string(),
});
export const PaseoAgentGetCatalogRequestSchema = z.object({
type: z.literal("config.paseo_agent.get_catalog.request"),
requestId: z.string(),
});
export const PaseoAgentSetProviderRequestSchema = z.object({
type: z.literal("config.paseo_agent.set_provider.request"),
requestId: z.string(),
@@ -2003,10 +2038,22 @@ export const PaseoAgentRemoveProviderRequestSchema = z.object({
name: z.string().trim().min(1),
});
export const PaseoAgentStoreChatGptCredentialRequestSchema = z.object({
type: z.literal("config.paseo_agent.store_chatgpt_credential.request"),
export const PaseoAgentOAuthStartRequestSchema = z.object({
type: z.literal("config.paseo_agent.oauth.start.request"),
requestId: z.string(),
providerName: z.string().trim().min(1),
name: z.string().trim().min(1),
});
export const PaseoAgentOAuthCompleteRequestSchema = z.object({
type: z.literal("config.paseo_agent.oauth.complete.request"),
requestId: z.string(),
name: z.string().trim().min(1),
});
export const PaseoAgentOAuthStoreCredentialRequestSchema = z.object({
type: z.literal("config.paseo_agent.oauth.store_credential.request"),
requestId: z.string(),
name: z.string().trim().min(1),
credential: PaseoAgentOAuthCredentialSchema,
});
@@ -2162,9 +2209,12 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
ListProviderModesRequestMessageSchema,
ListProviderFeaturesRequestMessageSchema,
PaseoAgentGetProvidersRequestSchema,
PaseoAgentGetCatalogRequestSchema,
PaseoAgentSetProviderRequestSchema,
PaseoAgentRemoveProviderRequestSchema,
PaseoAgentStoreChatGptCredentialRequestSchema,
PaseoAgentOAuthStartRequestSchema,
PaseoAgentOAuthCompleteRequestSchema,
PaseoAgentOAuthStoreCredentialRequestSchema,
ListAvailableProvidersRequestMessageSchema,
GetProvidersSnapshotRequestMessageSchema,
RefreshProvidersSnapshotRequestMessageSchema,
@@ -2458,6 +2508,8 @@ export const ServerInfoStatusPayloadSchema = z
agentForkContext: z.boolean().optional(),
// COMPAT(paseoAgentConfig): added in v0.1.103, remove gate after 2027-01-02.
paseoAgentConfig: z.boolean().optional(),
// COMPAT(paseoAgentCatalog): added in v0.1.104, drop the gate when floor >= v0.1.104
paseoAgentCatalog: z.boolean().optional(),
})
.optional(),
})
@@ -3974,6 +4026,15 @@ export const PaseoAgentGetProvidersResponseSchema = z.object({
}),
});
export const PaseoAgentGetCatalogResponseSchema = z.object({
type: z.literal("config.paseo_agent.get_catalog.response"),
payload: z.object({
requestId: z.string(),
catalog: z.array(PaseoAgentCatalogEntrySchema),
error: z.string().nullable(),
}),
});
export const PaseoAgentSetProviderResponseSchema = z.object({
type: z.literal("config.paseo_agent.set_provider.response"),
payload: z.object({
@@ -3994,13 +4055,47 @@ export const PaseoAgentRemoveProviderResponseSchema = z.object({
}),
});
export const PaseoAgentStoreChatGptCredentialResponseSchema = z.object({
type: z.literal("config.paseo_agent.store_chatgpt_credential.response"),
const PaseoAgentOAuthStartAuthorizationSchema = z
.object({
kind: z.string().min(1),
url: z.string().optional(),
instructions: z.string().optional(),
userCode: z.string().optional(),
verificationUri: z.string().optional(),
intervalSeconds: z.number().optional(),
expiresInSeconds: z.number().optional(),
})
.passthrough();
export const PaseoAgentOAuthStartResponseSchema = z.object({
type: z.literal("config.paseo_agent.oauth.start.response"),
payload: z.object({
requestId: z.string(),
success: z.boolean(),
providerName: z.string(),
auth: PaseoAgentProviderAuthStateSchema,
name: z.string(),
authorization: PaseoAgentOAuthStartAuthorizationSchema.nullable(),
error: z.string().nullable(),
}),
});
export const PaseoAgentOAuthCompleteResponseSchema = z.object({
type: z.literal("config.paseo_agent.oauth.complete.response"),
payload: z.object({
requestId: z.string(),
success: z.boolean(),
name: z.string(),
auth: PaseoAgentProviderAuthStateSchema.optional(),
error: z.string().nullable(),
}),
});
export const PaseoAgentOAuthStoreCredentialResponseSchema = z.object({
type: z.literal("config.paseo_agent.oauth.store_credential.response"),
payload: z.object({
requestId: z.string(),
success: z.boolean(),
name: z.string(),
auth: PaseoAgentProviderAuthStateSchema.optional(),
error: z.string().nullable(),
}),
});
@@ -4401,9 +4496,12 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
ListProviderModesResponseMessageSchema,
ListProviderFeaturesResponseMessageSchema,
PaseoAgentGetProvidersResponseSchema,
PaseoAgentGetCatalogResponseSchema,
PaseoAgentSetProviderResponseSchema,
PaseoAgentRemoveProviderResponseSchema,
PaseoAgentStoreChatGptCredentialResponseSchema,
PaseoAgentOAuthStartResponseSchema,
PaseoAgentOAuthCompleteResponseSchema,
PaseoAgentOAuthStoreCredentialResponseSchema,
ListAvailableProvidersResponseSchema,
GetProvidersSnapshotResponseMessageSchema,
ProvidersSnapshotUpdateMessageSchema,
@@ -4541,13 +4639,17 @@ export type RedactedPaseoAgentProviderConfig = z.infer<
>;
export type PaseoAgentProviderAuthState = z.infer<typeof PaseoAgentProviderAuthStateSchema>;
export type PaseoAgentOAuthCredential = z.infer<typeof PaseoAgentOAuthCredentialSchema>;
export type PaseoAgentCatalogEntry = z.infer<typeof PaseoAgentCatalogEntrySchema>;
export type PaseoAgentGetProvidersResponse = z.infer<typeof PaseoAgentGetProvidersResponseSchema>;
export type PaseoAgentGetCatalogResponse = z.infer<typeof PaseoAgentGetCatalogResponseSchema>;
export type PaseoAgentSetProviderResponse = z.infer<typeof PaseoAgentSetProviderResponseSchema>;
export type PaseoAgentRemoveProviderResponse = z.infer<
typeof PaseoAgentRemoveProviderResponseSchema
>;
export type PaseoAgentStoreChatGptCredentialResponse = z.infer<
typeof PaseoAgentStoreChatGptCredentialResponseSchema
export type PaseoAgentOAuthStartResponse = z.infer<typeof PaseoAgentOAuthStartResponseSchema>;
export type PaseoAgentOAuthCompleteResponse = z.infer<typeof PaseoAgentOAuthCompleteResponseSchema>;
export type PaseoAgentOAuthStoreCredentialResponse = z.infer<
typeof PaseoAgentOAuthStoreCredentialResponseSchema
>;
export type ListAvailableProvidersResponse = z.infer<typeof ListAvailableProvidersResponseSchema>;
export type DaemonGetStatusResponse = z.infer<typeof DaemonGetStatusResponseSchema>;
@@ -4626,10 +4728,13 @@ export type ListProviderFeaturesRequestMessage = z.infer<
typeof ListProviderFeaturesRequestMessageSchema
>;
export type PaseoAgentGetProvidersRequest = z.infer<typeof PaseoAgentGetProvidersRequestSchema>;
export type PaseoAgentGetCatalogRequest = z.infer<typeof PaseoAgentGetCatalogRequestSchema>;
export type PaseoAgentSetProviderRequest = z.infer<typeof PaseoAgentSetProviderRequestSchema>;
export type PaseoAgentRemoveProviderRequest = z.infer<typeof PaseoAgentRemoveProviderRequestSchema>;
export type PaseoAgentStoreChatGptCredentialRequest = z.infer<
typeof PaseoAgentStoreChatGptCredentialRequestSchema
export type PaseoAgentOAuthStartRequest = z.infer<typeof PaseoAgentOAuthStartRequestSchema>;
export type PaseoAgentOAuthCompleteRequest = z.infer<typeof PaseoAgentOAuthCompleteRequestSchema>;
export type PaseoAgentOAuthStoreCredentialRequest = z.infer<
typeof PaseoAgentOAuthStoreCredentialRequestSchema
>;
export type ListAvailableProvidersRequestMessage = z.infer<
typeof ListAvailableProvidersRequestMessageSchema