mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-31 21:52:54 +00:00
feat: wire paseo agent provider catalog
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user