Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions src/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,65 @@ export type RuntimesListResponse = z.infer<typeof RuntimesListResponseSchema>;
export const GatewayTypeSchema = z.enum(["telegram", "farcaster", "slack"]);
export type GatewayType = z.infer<typeof GatewayTypeSchema>;

/** How a framework receives events from a provider. */
export const ChannelTransportModeSchema = z.enum([
"polling",
"webhook",
"socket",
"http",
"relay",
]);
export type ChannelTransportMode = z.infer<typeof ChannelTransportModeSchema>;

/** Who owns the connector process and its credentials. */
export const ChannelManagementModeSchema = z.enum([
"runtime-native",
"perkos-managed-relay",
"user-managed-native",
]);
export type ChannelManagementMode = z.infer<typeof ChannelManagementModeSchema>;

export const ChannelLoginModeSchema = z.enum([
"token",
"oauth",
"qr",
"embedded-signup",
]);
export type ChannelLoginMode = z.infer<typeof ChannelLoginModeSchema>;

export const RuntimeChannelFieldSchema = z.object({
key: z.string().min(1),
label: z.string().min(1),
required: z.boolean(),
secret: z.boolean(),
input: z.enum(["text", "password", "url", "select", "boolean"]),
options: z.array(z.object({ value: z.string(), label: z.string() })).optional(),
help: z.string().optional(),
});
export type RuntimeChannelField = z.infer<typeof RuntimeChannelFieldSchema>;

/**
* Versionable capability advertised by the PerkOS control plane for one
* framework/provider/transport combination. The UI renders this schema rather
* than assuming Hermes and OpenClaw accept the same configuration.
*/
export const RuntimeChannelCapabilitySchema = z.object({
adapterId: z.string().min(1),
framework: AgentRuntimeSchema,
provider: GatewayTypeSchema,
label: z.string().min(1),
transportMode: ChannelTransportModeSchema,
managementMode: ChannelManagementModeSchema,
loginMode: ChannelLoginModeSchema,
requiresAlwaysOn: z.boolean(),
requiresPersistentStorage: z.boolean(),
supportsManagedRelay: z.boolean(),
fields: z.array(RuntimeChannelFieldSchema),
});
export type RuntimeChannelCapability = z.infer<
typeof RuntimeChannelCapabilitySchema
>;

export const AgentGatewayStatusSchema = z.enum(["pending", "active", "error"]);
export type AgentGatewayStatus = z.infer<typeof AgentGatewayStatusSchema>;

Expand All @@ -301,13 +360,19 @@ export type AgentGatewayStatus = z.infer<typeof AgentGatewayStatusSchema>;
*/
export const AgentGatewayRecordSchema = z.object({
type: GatewayTypeSchema,
adapterId: z.string().optional(),
framework: AgentRuntimeSchema.optional(),
transportMode: ChannelTransportModeSchema.optional(),
managementMode: ChannelManagementModeSchema.optional(),
requiresAlwaysOn: z.boolean().optional(),
enabled: z.boolean(),
nonSecretConfig: z.record(z.string(), z.string()),
secretsProvided: z.array(z.string()),
/** formKey → AWS Secrets Manager ARN. Populated by the gateways POST route. */
secretArns: z.record(z.string(), z.string()).optional(),
status: AgentGatewayStatusSchema,
statusMessage: z.string().optional(),
lastProbeAt: z.string().optional(),
createdAt: z.string(),
updatedAt: z.string(),
});
Expand All @@ -326,6 +391,9 @@ export type AgentGateways = z.infer<typeof AgentGatewaysSchema>;
*/
export const GatewayUpsertInputSchema = z.object({
type: GatewayTypeSchema,
adapterId: z.string().optional(),
transportMode: ChannelTransportModeSchema.optional(),
managementMode: ChannelManagementModeSchema.optional(),
enabled: z.boolean(),
nonSecretConfig: z.record(z.string(), z.string()).optional(),
secrets: z.record(z.string(), z.string()).optional(),
Expand Down
29 changes: 29 additions & 0 deletions tests/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";

import {
AgentGatewayRecordSchema,
RuntimeChannelCapabilitySchema,
AgentRuntimeSchema,
AgentSchema,
AgentStatusSchema,
Expand Down Expand Up @@ -148,6 +149,34 @@ describe("GatewayTypeSchema + AgentGatewayRecordSchema", () => {
});
});

describe("RuntimeChannelCapabilitySchema", () => {
it("models a framework-specific Telegram transport", () => {
const out = RuntimeChannelCapabilitySchema.parse({
adapterId: "hermes.telegram.polling.v1",
framework: "Hermes",
provider: "telegram",
label: "Telegram · Hermes polling",
transportMode: "polling",
managementMode: "runtime-native",
loginMode: "token",
requiresAlwaysOn: true,
requiresPersistentStorage: false,
supportsManagedRelay: false,
fields: [
{
key: "botToken",
label: "Bot token",
required: true,
secret: true,
input: "password",
},
],
});
expect(out.framework).toBe("Hermes");
expect(out.transportMode).toBe("polling");
});
});

describe("GatewayUpsertInputSchema", () => {
it("parses a Farcaster enable with secrets stripped at the boundary", () => {
const out = GatewayUpsertInputSchema.parse({
Expand Down
Loading