Files
clawdbot/src/gateway/protocol/schema.ts
2025-12-13 12:38:08 +00:00

548 lines
16 KiB
TypeScript

import { type Static, type TSchema, Type } from "@sinclair/typebox";
const NonEmptyString = Type.String({ minLength: 1 });
export const PresenceEntrySchema = Type.Object(
{
host: Type.Optional(NonEmptyString),
ip: Type.Optional(NonEmptyString),
version: Type.Optional(NonEmptyString),
mode: Type.Optional(NonEmptyString),
lastInputSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
reason: Type.Optional(NonEmptyString),
tags: Type.Optional(Type.Array(NonEmptyString)),
text: Type.Optional(Type.String()),
ts: Type.Integer({ minimum: 0 }),
instanceId: Type.Optional(NonEmptyString),
},
{ additionalProperties: false },
);
export const HealthSnapshotSchema = Type.Any();
export const StateVersionSchema = Type.Object(
{
presence: Type.Integer({ minimum: 0 }),
health: Type.Integer({ minimum: 0 }),
},
{ additionalProperties: false },
);
export const SnapshotSchema = Type.Object(
{
presence: Type.Array(PresenceEntrySchema),
health: HealthSnapshotSchema,
stateVersion: StateVersionSchema,
uptimeMs: Type.Integer({ minimum: 0 }),
},
{ additionalProperties: false },
);
export const TickEventSchema = Type.Object(
{
ts: Type.Integer({ minimum: 0 }),
},
{ additionalProperties: false },
);
export const ShutdownEventSchema = Type.Object(
{
reason: NonEmptyString,
restartExpectedMs: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
);
export const ConnectParamsSchema = Type.Object(
{
minProtocol: Type.Integer({ minimum: 1 }),
maxProtocol: Type.Integer({ minimum: 1 }),
client: Type.Object(
{
name: NonEmptyString,
version: NonEmptyString,
platform: NonEmptyString,
mode: NonEmptyString,
instanceId: Type.Optional(NonEmptyString),
},
{ additionalProperties: false },
),
caps: Type.Optional(Type.Array(NonEmptyString, { default: [] })),
auth: Type.Optional(
Type.Object(
{
token: Type.Optional(Type.String()),
},
{ additionalProperties: false },
),
),
locale: Type.Optional(Type.String()),
userAgent: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);
export const HelloOkSchema = Type.Object(
{
type: Type.Literal("hello-ok"),
protocol: Type.Integer({ minimum: 1 }),
server: Type.Object(
{
version: NonEmptyString,
commit: Type.Optional(NonEmptyString),
host: Type.Optional(NonEmptyString),
connId: NonEmptyString,
},
{ additionalProperties: false },
),
features: Type.Object(
{
methods: Type.Array(NonEmptyString),
events: Type.Array(NonEmptyString),
},
{ additionalProperties: false },
),
snapshot: SnapshotSchema,
policy: Type.Object(
{
maxPayload: Type.Integer({ minimum: 1 }),
maxBufferedBytes: Type.Integer({ minimum: 1 }),
tickIntervalMs: Type.Integer({ minimum: 1 }),
},
{ additionalProperties: false },
),
},
{ additionalProperties: false },
);
export const ErrorShapeSchema = Type.Object(
{
code: NonEmptyString,
message: NonEmptyString,
details: Type.Optional(Type.Unknown()),
retryable: Type.Optional(Type.Boolean()),
retryAfterMs: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
);
export const RequestFrameSchema = Type.Object(
{
type: Type.Literal("req"),
id: NonEmptyString,
method: NonEmptyString,
params: Type.Optional(Type.Unknown()),
},
{ additionalProperties: false },
);
export const ResponseFrameSchema = Type.Object(
{
type: Type.Literal("res"),
id: NonEmptyString,
ok: Type.Boolean(),
payload: Type.Optional(Type.Unknown()),
error: Type.Optional(ErrorShapeSchema),
},
{ additionalProperties: false },
);
export const EventFrameSchema = Type.Object(
{
type: Type.Literal("event"),
event: NonEmptyString,
payload: Type.Optional(Type.Unknown()),
seq: Type.Optional(Type.Integer({ minimum: 0 })),
stateVersion: Type.Optional(StateVersionSchema),
},
{ additionalProperties: false },
);
// Discriminated union of all top-level frames. Using a discriminator makes
// downstream codegen (quicktype) produce tighter types instead of all-optional
// blobs.
export const GatewayFrameSchema = Type.Union(
[RequestFrameSchema, ResponseFrameSchema, EventFrameSchema],
{ discriminator: "type" },
);
export const AgentEventSchema = Type.Object(
{
runId: NonEmptyString,
seq: Type.Integer({ minimum: 0 }),
stream: NonEmptyString,
ts: Type.Integer({ minimum: 0 }),
data: Type.Record(Type.String(), Type.Unknown()),
},
{ additionalProperties: false },
);
export const SendParamsSchema = Type.Object(
{
to: NonEmptyString,
message: NonEmptyString,
mediaUrl: Type.Optional(Type.String()),
provider: Type.Optional(Type.String()),
idempotencyKey: NonEmptyString,
},
{ additionalProperties: false },
);
export const AgentParamsSchema = Type.Object(
{
message: NonEmptyString,
to: Type.Optional(Type.String()),
sessionId: Type.Optional(Type.String()),
sessionKey: Type.Optional(Type.String()),
thinking: Type.Optional(Type.String()),
deliver: Type.Optional(Type.Boolean()),
channel: Type.Optional(Type.String()),
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
idempotencyKey: NonEmptyString,
},
{ additionalProperties: false },
);
export const WakeParamsSchema = Type.Object(
{
mode: Type.Union([Type.Literal("now"), Type.Literal("next-heartbeat")]),
text: NonEmptyString,
},
{ additionalProperties: false },
);
export const NodePairRequestParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
displayName: Type.Optional(NonEmptyString),
platform: Type.Optional(NonEmptyString),
version: Type.Optional(NonEmptyString),
remoteIp: Type.Optional(NonEmptyString),
},
{ additionalProperties: false },
);
export const NodePairListParamsSchema = Type.Object(
{},
{ additionalProperties: false },
);
export const NodePairApproveParamsSchema = Type.Object(
{ requestId: NonEmptyString },
{ additionalProperties: false },
);
export const NodePairRejectParamsSchema = Type.Object(
{ requestId: NonEmptyString },
{ additionalProperties: false },
);
export const NodePairVerifyParamsSchema = Type.Object(
{ nodeId: NonEmptyString, token: NonEmptyString },
{ additionalProperties: false },
);
export const CronScheduleSchema = Type.Union([
Type.Object(
{
kind: Type.Literal("at"),
atMs: Type.Integer({ minimum: 0 }),
},
{ additionalProperties: false },
),
Type.Object(
{
kind: Type.Literal("every"),
everyMs: Type.Integer({ minimum: 1 }),
anchorMs: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
),
Type.Object(
{
kind: Type.Literal("cron"),
expr: NonEmptyString,
tz: Type.Optional(Type.String()),
},
{ additionalProperties: false },
),
]);
export const CronPayloadSchema = Type.Union([
Type.Object(
{
kind: Type.Literal("systemEvent"),
text: NonEmptyString,
},
{ additionalProperties: false },
),
Type.Object(
{
kind: Type.Literal("agentTurn"),
message: NonEmptyString,
thinking: Type.Optional(Type.String()),
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 1 })),
deliver: Type.Optional(Type.Boolean()),
channel: Type.Optional(
Type.Union([
Type.Literal("last"),
Type.Literal("whatsapp"),
Type.Literal("telegram"),
]),
),
to: Type.Optional(Type.String()),
bestEffortDeliver: Type.Optional(Type.Boolean()),
},
{ additionalProperties: false },
),
]);
export const CronIsolationSchema = Type.Object(
{
postToMainPrefix: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);
export const CronJobStateSchema = Type.Object(
{
nextRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
runningAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
lastRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
lastStatus: Type.Optional(
Type.Union([
Type.Literal("ok"),
Type.Literal("error"),
Type.Literal("skipped"),
]),
),
lastError: Type.Optional(Type.String()),
lastDurationMs: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
);
export const CronJobSchema = Type.Object(
{
id: NonEmptyString,
name: Type.Optional(Type.String()),
enabled: Type.Boolean(),
createdAtMs: Type.Integer({ minimum: 0 }),
updatedAtMs: Type.Integer({ minimum: 0 }),
schedule: CronScheduleSchema,
sessionTarget: Type.Union([Type.Literal("main"), Type.Literal("isolated")]),
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
payload: CronPayloadSchema,
isolation: Type.Optional(CronIsolationSchema),
state: CronJobStateSchema,
},
{ additionalProperties: false },
);
export const CronListParamsSchema = Type.Object(
{
includeDisabled: Type.Optional(Type.Boolean()),
},
{ additionalProperties: false },
);
export const CronStatusParamsSchema = Type.Object(
{},
{ additionalProperties: false },
);
export const CronAddParamsSchema = Type.Object(
{
name: Type.Optional(Type.String()),
enabled: Type.Optional(Type.Boolean()),
schedule: CronScheduleSchema,
sessionTarget: Type.Union([Type.Literal("main"), Type.Literal("isolated")]),
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
payload: CronPayloadSchema,
isolation: Type.Optional(CronIsolationSchema),
},
{ additionalProperties: false },
);
export const CronUpdateParamsSchema = Type.Object(
{
id: NonEmptyString,
patch: Type.Partial(CronAddParamsSchema),
},
{ additionalProperties: false },
);
export const CronRemoveParamsSchema = Type.Object(
{
id: NonEmptyString,
},
{ additionalProperties: false },
);
export const CronRunParamsSchema = Type.Object(
{
id: NonEmptyString,
mode: Type.Optional(
Type.Union([Type.Literal("due"), Type.Literal("force")]),
),
},
{ additionalProperties: false },
);
export const CronRunsParamsSchema = Type.Object(
{
id: NonEmptyString,
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000 })),
},
{ additionalProperties: false },
);
export const CronRunLogEntrySchema = Type.Object(
{
ts: Type.Integer({ minimum: 0 }),
jobId: NonEmptyString,
action: Type.Literal("finished"),
status: Type.Optional(
Type.Union([
Type.Literal("ok"),
Type.Literal("error"),
Type.Literal("skipped"),
]),
),
error: Type.Optional(Type.String()),
summary: Type.Optional(Type.String()),
runAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
durationMs: Type.Optional(Type.Integer({ minimum: 0 })),
nextRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
);
// WebChat/WebSocket-native chat methods
export const ChatHistoryParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
},
{ additionalProperties: false },
);
export const ChatSendParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
message: NonEmptyString,
thinking: Type.Optional(Type.String()),
deliver: Type.Optional(Type.Boolean()),
attachments: Type.Optional(Type.Array(Type.Unknown())),
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
idempotencyKey: NonEmptyString,
},
{ additionalProperties: false },
);
export const ChatEventSchema = Type.Object(
{
runId: NonEmptyString,
sessionKey: NonEmptyString,
seq: Type.Integer({ minimum: 0 }),
state: Type.Union([
Type.Literal("delta"),
Type.Literal("final"),
Type.Literal("error"),
]),
message: Type.Optional(Type.Unknown()),
errorMessage: Type.Optional(Type.String()),
usage: Type.Optional(Type.Unknown()),
stopReason: Type.Optional(Type.String()),
},
{ additionalProperties: false },
);
export const ProtocolSchemas: Record<string, TSchema> = {
ConnectParams: ConnectParamsSchema,
HelloOk: HelloOkSchema,
RequestFrame: RequestFrameSchema,
ResponseFrame: ResponseFrameSchema,
EventFrame: EventFrameSchema,
GatewayFrame: GatewayFrameSchema,
PresenceEntry: PresenceEntrySchema,
StateVersion: StateVersionSchema,
Snapshot: SnapshotSchema,
ErrorShape: ErrorShapeSchema,
AgentEvent: AgentEventSchema,
SendParams: SendParamsSchema,
AgentParams: AgentParamsSchema,
WakeParams: WakeParamsSchema,
NodePairRequestParams: NodePairRequestParamsSchema,
NodePairListParams: NodePairListParamsSchema,
NodePairApproveParams: NodePairApproveParamsSchema,
NodePairRejectParams: NodePairRejectParamsSchema,
NodePairVerifyParams: NodePairVerifyParamsSchema,
CronJob: CronJobSchema,
CronListParams: CronListParamsSchema,
CronStatusParams: CronStatusParamsSchema,
CronAddParams: CronAddParamsSchema,
CronUpdateParams: CronUpdateParamsSchema,
CronRemoveParams: CronRemoveParamsSchema,
CronRunParams: CronRunParamsSchema,
CronRunsParams: CronRunsParamsSchema,
CronRunLogEntry: CronRunLogEntrySchema,
ChatHistoryParams: ChatHistoryParamsSchema,
ChatSendParams: ChatSendParamsSchema,
ChatEvent: ChatEventSchema,
TickEvent: TickEventSchema,
ShutdownEvent: ShutdownEventSchema,
};
export const PROTOCOL_VERSION = 2 as const;
export type ConnectParams = Static<typeof ConnectParamsSchema>;
export type HelloOk = Static<typeof HelloOkSchema>;
export type RequestFrame = Static<typeof RequestFrameSchema>;
export type ResponseFrame = Static<typeof ResponseFrameSchema>;
export type EventFrame = Static<typeof EventFrameSchema>;
export type GatewayFrame = Static<typeof GatewayFrameSchema>;
export type Snapshot = Static<typeof SnapshotSchema>;
export type PresenceEntry = Static<typeof PresenceEntrySchema>;
export type ErrorShape = Static<typeof ErrorShapeSchema>;
export type StateVersion = Static<typeof StateVersionSchema>;
export type AgentEvent = Static<typeof AgentEventSchema>;
export type WakeParams = Static<typeof WakeParamsSchema>;
export type NodePairRequestParams = Static<typeof NodePairRequestParamsSchema>;
export type NodePairListParams = Static<typeof NodePairListParamsSchema>;
export type NodePairApproveParams = Static<typeof NodePairApproveParamsSchema>;
export type NodePairRejectParams = Static<typeof NodePairRejectParamsSchema>;
export type NodePairVerifyParams = Static<typeof NodePairVerifyParamsSchema>;
export type CronJob = Static<typeof CronJobSchema>;
export type CronListParams = Static<typeof CronListParamsSchema>;
export type CronStatusParams = Static<typeof CronStatusParamsSchema>;
export type CronAddParams = Static<typeof CronAddParamsSchema>;
export type CronUpdateParams = Static<typeof CronUpdateParamsSchema>;
export type CronRemoveParams = Static<typeof CronRemoveParamsSchema>;
export type CronRunParams = Static<typeof CronRunParamsSchema>;
export type CronRunsParams = Static<typeof CronRunsParamsSchema>;
export type CronRunLogEntry = Static<typeof CronRunLogEntrySchema>;
export type ChatEvent = Static<typeof ChatEventSchema>;
export type TickEvent = Static<typeof TickEventSchema>;
export type ShutdownEvent = Static<typeof ShutdownEventSchema>;
export const ErrorCodes = {
NOT_LINKED: "NOT_LINKED",
AGENT_TIMEOUT: "AGENT_TIMEOUT",
INVALID_REQUEST: "INVALID_REQUEST",
UNAVAILABLE: "UNAVAILABLE",
} as const;
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
export function errorShape(
code: ErrorCode,
message: string,
opts?: { details?: unknown; retryable?: boolean; retryAfterMs?: number },
): ErrorShape {
return {
code,
message,
...opts,
};
}