docs: document auth profile failure policy contract (#89613)

* docs: document markdown marker renderer

* docs: document rendered markdown chunking

* docs: document markdown text chunking

* docs: document shared text chunking

* docs: document plugin text chunking exports

* docs: document avatar policy constants

* docs: document node match candidates

* docs: document scoped expiring id cache

* docs: document runtime import normalization

* docs: document string sample summaries

* docs: document session usage timeseries types

* docs: document session usage response types

* docs: document manifest frontmatter shapes

* docs: document channel route input metadata

* docs: document pair loop guard settings

* docs: document migration config patch helpers

* docs: document api provider registry

* docs: document tool call repair payloads

* docs: document plugin tool payload helpers

* docs: document lazy promise loader

* docs: document store writer queue state

* docs: document thread binding lifecycle

* docs: document concurrency helper contract

* docs: document gateway client info contract

* docs: document delivery context contracts

* docs: document secret ref defaults contract

* docs: document command gating contract

* docs: document avatar policy contract

* docs: document node match policy

* docs: document message channel normalization

* docs: document boolean parsing contract

* docs: document zod parse helpers

* docs: document direct dm guard policy

* docs: document fixed window limiter contract

* docs: document node presence event contract

* docs: document secret normalization contract

* docs: document progress draft line removal

* docs: document usage formatting contracts

* docs: document agent run status contract

* docs: document runtime import helpers

* docs: document provider utility ownership

* docs: document invalid config helpers

* docs: document json compat parser

* docs: document channel config metadata ownership

* docs: document channel logging helpers

* docs: document sender identity validation ownership

* docs: document string sampling helper

* docs: document global singleton helpers

* docs: document transcript tool helpers

* docs: document exec safe-bin normalization

* docs: document reaction level resolver

* docs: document account snapshot redaction boundary

* docs: document messaging target helpers

* docs: document thread binding messages

* docs: document conversation binding context

* docs: document conversation resolution helper

* docs: document owner display secret retention

* docs: document provider request config types

* docs: document skills config types

* docs: document memory config types

* docs: document imessage config types

* docs: document crestodian config types

* docs: document tools config policies

* docs: document shared config base types

* docs: document channel config contracts

* docs: document openclaw config state types

* docs: document model config contracts

* docs: document shared agent config types

* docs: document agent defaults config types

* docs: document secret input contracts

* docs: document auth config contracts

* docs: document gateway config contracts

* docs: document tool call stream repair contracts

* docs: document memory host facades

* docs: document llm core contracts

* docs: document markdown core contracts

* docs: document gateway connect error contracts

* docs: document gateway protocol primitives

* docs: document gateway frame schemas

* docs: document gateway device schemas

* docs: document gateway environment schemas

* docs: document gateway push schemas

* docs: document gateway plugin schemas

* docs: document gateway artifact schemas

* docs: document gateway command schemas

* docs: document gateway task schemas

* docs: document gateway exec approval schemas

* docs: document gateway secret schemas

* docs: document gateway config schemas

* docs: document gateway snapshot schemas

* docs: document gateway chat schemas

* docs: document gateway wizard schemas

* docs: document gateway node schemas

* docs: document gateway plugin approval schemas

* docs: document gateway talk schemas

* docs: document gateway agent schemas

* docs: document gateway session schemas

* docs: document gateway cron schemas

* docs: document gateway agent model skill schemas

* docs: document gateway skill proposal tool schemas

* docs: document gateway protocol registry

* docs: document gateway channel status schemas

* docs: document gateway schema regression tests

* docs: document gateway schema barrel

* docs: document gateway validator tests

* docs: document gateway primitive push tests

* docs: document gateway contract tests

* docs: document native protocol guard

* docs: document channel schema tests

* docs: document gateway protocol smoke tests

* docs: document gateway protocol entrypoint

* docs: document gateway protocol type exports

* docs: document gateway error codes

* docs: document protocol schema registry

* docs: document talk audio codec

* docs: document talk activation names

* docs: document talk consult questions

* docs: document talk consult tool

* docs: document talk run control contracts

* docs: document talk run control adapter

* docs: document talkback consult queue

* docs: document talk consult transcript guard

* docs: document talk fast context runtime

* docs: document forced talk consult coordinator

* docs: document talk output activity tracker

* docs: document talk event metrics

* docs: document talk diagnostics

* docs: document talk observability hook

* docs: document talk provider resolver

* docs: document talk provider registry

* docs: document talk runtime primitives

* docs: document talk consult controller logs

* docs: document channel identity helpers

* docs: document channel account allowlist helpers

* docs: document channel metadata draft controls

* docs: document channel ingress policy

* docs: document channel sender access gates

* docs: document channel catalog message contracts

* docs: document channel account plugin helpers

* docs: document configured binding helpers

* docs: document channel acp approval config helpers

* docs: document channel bundled config write helpers

* docs: document channel plugin utility contracts

* docs: document channel config access helpers

* docs: document channel message action helpers

* docs: document channel outbound runtime helpers

* docs: document channel pairing promotion helpers

* docs: document channel registry helpers

* docs: document channel setup wizard helpers

* docs: document channel lifecycle status helpers

* docs: document channel target thread helpers

* docs: document channel session binding helpers

* docs: document channel package module probes

* docs: document channel setup wizard contracts

* docs: document channel plugin API barrels

* docs: document channel contract test helpers

* docs: document channel core helpers

* docs: document small core facades

* docs: document provider runtime helpers

* docs: document persistence and realtime helpers

* docs: document mcp and state helpers

* docs: document tool planner contracts

* docs: document music generation runtime

* docs: document crestodian command flow

* docs: document utility helpers

* docs: document node host helpers

* docs: document transcript contracts

* docs: document trajectory export contracts

* docs: document image generation contracts

* docs: document routing helper contracts

* docs: document session helper contracts

* docs: document video generation contracts

* docs: document model catalog contracts

* docs: document proxy capture contracts

* docs: document status rendering contracts

* docs: document test helper contracts

* docs: document wizard setup contracts

* docs: document process contracts

* docs: document memory host sdk contracts

* docs: document tts contracts

* docs: document secrets runtime contracts

* docs: document shared helper contracts

* docs: document hook runtime contracts

* docs: document security audit contracts

* docs: document flow contracts

* docs: document media understanding contracts

* docs: document tui contracts

* docs: document logging contracts

* docs: document llm contracts

* docs: document cron contracts

* docs: document daemon contracts

* docs: document task contracts

* docs: document acp contracts

* docs: document test utility contracts

* docs: document skill contracts

* docs: document config contracts

* docs: document outbound infra contracts

* docs: document command analysis contracts

* docs: document provider usage infra contracts

* docs: document file safety infra contracts

* docs: document exec approval infra contracts

* docs: document gateway runtime infra contracts

* docs: document infra utility contracts

* docs: document infra queue storage contracts

* docs: document heartbeat infra contracts

* docs: document remaining infra contracts

* docs: document gateway auth contracts

* docs: document gateway display helpers

* docs: document gateway http helpers

* docs: document gateway node helpers

* docs: document gateway mcp helpers

* docs: document gateway support helpers

* docs: document gateway server runtime helpers

* docs: document gateway runtime bootstrap helpers

* docs: document gateway session events

* docs: document gateway utility helpers

* docs: document gateway talk helpers

* docs: document gateway helper contracts

* docs: document gateway server method helpers

* docs: document gateway server auth helpers

* docs: document gateway server tests

* docs: document gateway test helpers

* docs: document gateway node tests

* docs: document gateway channel tests

* docs: document gateway session tests

* docs: document gateway server startup tests

* docs: document gateway tool test helpers

* docs: document gateway server test helpers

* docs: document gateway server method tests

* docs: document remaining gateway tests

* docs: document plugin sdk public subpaths

* docs: document plugin sdk runtime helpers

* docs: document plugin sdk memory provider helpers

* docs: document plugin sdk runtime facades

* docs: document plugin sdk command approval helpers

* docs: document plugin sdk runtime types

* docs: document plugin sdk browser account helpers

* docs: document plugin sdk media memory helpers

* docs: document plugin sdk core tests

* docs: document plugin sdk contract helpers

* docs: document plugin sdk test helpers

* docs: document remaining plugin sdk tests

* docs: document cli utility helpers

* docs: document cli runtime helpers

* docs: document cli command registration helpers

* docs: document node cli helpers

* docs: document cli program registration

* docs: document message cli registration

* docs: document daemon cli helpers

* docs: document cli route parsers
This commit is contained in:
Peter Steinberger
2026-06-03 15:20:39 -07:00
committed by GitHub
parent 8de37e1ce4
commit 0b8aabe864
2172 changed files with 13969 additions and 363 deletions
@@ -2,7 +2,14 @@ import { Compile } from "typebox/compile";
import { describe, expect, it } from "vitest";
import { ChannelsStatusResultSchema, WebLoginWaitParamsSchema } from "./schema/channels.js";
/**
* Channel schema regressions for browser login and status diagnostics.
* These payloads are consumed by dashboard/operator UI, so QR payload bounds
* and event-loop diagnostic shape are part of the public gateway contract.
*/
describe("WebLoginWaitParamsSchema", () => {
/** Compiled validator reused across QR bounds cases. */
const validate = Compile(WebLoginWaitParamsSchema);
it("bounds caller-provided QR data URLs", () => {
@@ -26,6 +33,7 @@ describe("WebLoginWaitParamsSchema", () => {
});
describe("ChannelsStatusResultSchema", () => {
/** Compiled status validator for channel docking diagnostics. */
const validate = Compile(ChannelsStatusResultSchema);
it("accepts gateway event-loop diagnostics emitted by channels.status", () => {
@@ -1,3 +1,9 @@
/**
* Shared gateway client identity contract.
*
* These values cross the WebSocket handshake boundary, so additions must stay
* aligned with protocol schemas and server policy checks.
*/
function normalizeOptionalLowercaseString(raw?: string | null): string | undefined {
if (typeof raw !== "string") {
return undefined;
@@ -6,6 +12,7 @@ function normalizeOptionalLowercaseString(raw?: string | null): string | undefin
return normalized || undefined;
}
/** Canonical client ids accepted in gateway hello/connect payloads. */
export const GATEWAY_CLIENT_IDS = {
WEBCHAT_UI: "webchat-ui",
CONTROL_UI: "openclaw-control-ui",
@@ -30,6 +37,7 @@ export const GATEWAY_CLIENT_NAMES = GATEWAY_CLIENT_IDS;
/** Compatibility alias for internal callers that still use "name" terminology. */
export type GatewayClientName = GatewayClientId;
/** Coarse modes let policy group clients without matching every product id. */
export const GATEWAY_CLIENT_MODES = {
WEBCHAT: "webchat",
CLI: "cli",
@@ -45,16 +53,25 @@ export type GatewayClientMode = (typeof GATEWAY_CLIENT_MODES)[keyof typeof GATEW
/** Client metadata sent during gateway connection setup. */
export type GatewayClientInfo = {
/** Stable product/client identifier from `GATEWAY_CLIENT_IDS`. */
id: GatewayClientId;
/** Human-readable label for diagnostics; not used for policy decisions. */
displayName?: string;
/** Client app or package version reported by the connecting process. */
version: string;
/** Runtime platform string, such as `darwin`, `ios`, `android`, or `web`. */
platform: string;
/** Optional device family used by native clients for display and routing hints. */
deviceFamily?: string;
/** Native hardware/model identifier when available. */
modelIdentifier?: string;
/** Coarse category from `GATEWAY_CLIENT_MODES` for policy and diagnostics. */
mode: GatewayClientMode;
/** Per-installation or per-process id used to distinguish same-product clients. */
instanceId?: string;
};
/** Capability flags a client may advertise during the gateway handshake. */
export const GATEWAY_CLIENT_CAPS = {
TOOL_EVENTS: "tool-events",
} as const;
@@ -67,6 +84,8 @@ const GATEWAY_CLIENT_MODE_SET = new Set<GatewayClientMode>(Object.values(GATEWAY
/** Normalizes untrusted client ids and rejects unknown values. */
export function normalizeGatewayClientId(raw?: string | null): GatewayClientId | undefined {
// Handshake input is intentionally case-insensitive, but policy decisions use
// the canonical lowercase ids from the closed registry above.
const normalized = normalizeOptionalLowercaseString(raw);
if (!normalized) {
return undefined;
@@ -16,6 +16,14 @@ import {
resolveAuthConnectErrorDetailCode,
} from "./connect-error-details.js";
/**
* Connect error detail regressions for Gateway/WebSocket clients.
*
* These tests pin structured auth/pairing details, human-readable fallback
* formatting, and request-id sanitization because these strings surface in
* control UI reconnect flows and device pairing diagnostics.
*/
describe("readConnectErrorDetailCode", () => {
it("reads structured detail codes", () => {
expect(readConnectErrorDetailCode({ code: "AUTH_TOKEN_MISMATCH" })).toBe("AUTH_TOKEN_MISMATCH");
@@ -1,3 +1,9 @@
/**
* Shared gateway connect-error detail helpers.
*
* These details cross client/server boundaries, so readers normalize untrusted
* payloads before using them in reconnect decisions or user-facing messages.
*/
function normalizeOptionalString(value: unknown): string | undefined {
if (typeof value !== "string") {
return undefined;
@@ -18,6 +24,7 @@ function normalizeArrayBackedTrimmedStringList(value: unknown): string[] | undef
return values.length > 0 ? values : undefined;
}
/** Structured connect-error codes carried in gateway error `details.code`. */
export const ConnectErrorDetailCodes = {
AUTH_REQUIRED: "AUTH_REQUIRED",
AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
@@ -53,6 +60,7 @@ export const ConnectErrorDetailCodes = {
export type ConnectErrorDetailCode =
(typeof ConnectErrorDetailCodes)[keyof typeof ConnectErrorDetailCodes];
/** Pairing-specific reasons clients can display and use for reconnect policy. */
export const ConnectPairingRequiredReasons = {
NOT_PAIRED: "not-paired",
ROLE_UPGRADE: "role-upgrade",
@@ -63,6 +71,7 @@ export const ConnectPairingRequiredReasons = {
export type ConnectPairingRequiredReason =
(typeof ConnectPairingRequiredReasons)[keyof typeof ConnectPairingRequiredReasons];
/** Suggested client-side recovery action for structured connect errors. */
export type ConnectRecoveryNextStep =
| "retry_with_device_token"
| "update_auth_configuration"
@@ -70,11 +79,13 @@ export type ConnectRecoveryNextStep =
| "wait_then_retry"
| "review_auth_configuration";
/** Optional retry guidance extracted from gateway connect-error details. */
export type ConnectErrorRecoveryAdvice = {
canRetryWithDeviceToken?: boolean;
recommendedNextStep?: ConnectRecoveryNextStep;
};
/** Full structured details for pairing-required connect failures. */
export type PairingConnectErrorDetails = {
code: typeof ConnectErrorDetailCodes.PAIRING_REQUIRED;
reason?: ConnectPairingRequiredReason;
@@ -90,6 +101,7 @@ export type PairingConnectErrorDetails = {
approvedScopes?: string[];
};
/** Compact pairing-required subset used by reconnect/status surfaces. */
export type ConnectPairingRequiredDetails = Pick<
PairingConnectErrorDetails,
"reason" | "requestId"
@@ -152,6 +164,7 @@ const CONNECT_PAIRING_REQUIRED_MESSAGE_BY_REASON: Readonly<
"metadata-upgrade": "device metadata change pending approval",
};
/** Maps internal auth failure reasons to public connect-error detail codes. */
export function resolveAuthConnectErrorDetailCode(
reason: string | undefined,
): ConnectErrorDetailCode {
@@ -191,6 +204,7 @@ export function resolveAuthConnectErrorDetailCode(
}
}
/** Maps device-auth verifier reasons to public connect-error detail codes. */
export function resolveDeviceAuthConnectErrorDetailCode(
reason: string | undefined,
): ConnectErrorDetailCode {
@@ -212,6 +226,7 @@ export function resolveDeviceAuthConnectErrorDetailCode(
}
}
/** Reads a non-empty detail code from an untrusted error details payload. */
export function readConnectErrorDetailCode(details: unknown): string | null {
if (!details || typeof details !== "object" || Array.isArray(details)) {
return null;
@@ -220,6 +235,7 @@ export function readConnectErrorDetailCode(details: unknown): string | null {
return typeof code === "string" && code.trim().length > 0 ? code : null;
}
/** Extracts normalized retry advice from untrusted connect-error details. */
export function readConnectErrorRecoveryAdvice(details: unknown): ConnectErrorRecoveryAdvice {
if (!details || typeof details !== "object" || Array.isArray(details)) {
return {};
@@ -249,6 +265,7 @@ function normalizePairingConnectReason(value: unknown): ConnectPairingRequiredRe
: undefined;
}
/** Normalizes pairing request ids before echoing them in close reasons or UI text. */
export function normalizePairingConnectRequestId(value: unknown): string | undefined {
const normalized = normalizeOptionalString(value);
return normalized && PAIRING_CONNECT_REQUEST_ID_PATTERN.test(normalized) ? normalized : undefined;
@@ -287,6 +304,7 @@ function createPairingConnectErrorDetails(params: {
};
}
/** Human-readable requirement summary for a pairing-required reason. */
export function describePairingConnectRequirement(
reason: ConnectPairingRequiredReason | undefined,
): string {
@@ -295,6 +313,7 @@ export function describePairingConnectRequirement(
: "device approval is required";
}
/** Builds the gateway close/error message for a pairing-required connect failure. */
export function buildPairingConnectErrorMessage(
reason: ConnectPairingRequiredReason | undefined,
): string {
@@ -311,6 +330,7 @@ function buildPairingConnectRemediationHint(
: "Approve the pending device request before retrying.";
}
/** Short user-facing recovery title for pairing-required connect failures. */
export function buildPairingConnectRecoveryTitle(
reason: ConnectPairingRequiredReason | undefined,
): string {
@@ -319,6 +339,7 @@ export function buildPairingConnectRecoveryTitle(
: "Gateway pairing approval required.";
}
/** Builds sanitized structured details for a pairing-required connect failure. */
export function buildPairingConnectErrorDetails(params: {
reason: ConnectPairingRequiredReason | undefined;
requestId?: string;
@@ -356,6 +377,7 @@ export function buildPairingConnectErrorDetails(params: {
});
}
/** Builds a sanitized close reason string for WebSocket pairing rejections. */
export function buildPairingConnectCloseReason(params: {
reason: ConnectPairingRequiredReason | undefined;
requestId?: string;
@@ -365,6 +387,7 @@ export function buildPairingConnectCloseReason(params: {
return requestId ? `${message} (requestId: ${requestId})` : message;
}
/** Reads and backfills pairing-required details from an untrusted details object. */
export function readPairingConnectErrorDetails(
details: unknown,
): PairingConnectErrorDetails | null {
@@ -417,6 +440,7 @@ export function readPairingConnectErrorDetails(
});
}
/** Reads the compact pairing-required subset from untrusted connect details. */
export function readConnectPairingRequiredDetails(
details: unknown,
): ConnectPairingRequiredDetails | null {
@@ -430,6 +454,7 @@ export function readConnectPairingRequiredDetails(
};
}
/** Parses legacy/string-only pairing-required messages into structured details. */
export function readConnectPairingRequiredMessage(
message: string | null | undefined,
): ConnectPairingRequiredDetails | null {
@@ -462,6 +487,7 @@ export function readConnectPairingRequiredMessage(
};
}
/** Formats pairing-required details into the canonical user-facing message. */
export function formatConnectPairingRequiredMessage(details: unknown): string {
const pairing = readPairingConnectErrorDetails(details);
const base =
@@ -471,6 +497,7 @@ export function formatConnectPairingRequiredMessage(details: unknown): string {
return pairing?.requestId ? `${base} (requestId: ${pairing.requestId})` : base;
}
/** Formats connect errors using structured details before falling back to raw messages. */
export function formatConnectErrorMessage(params: { message?: string; details?: unknown }): string {
if (readConnectErrorDetailCode(params.details) === ConnectErrorDetailCodes.PAIRING_REQUIRED) {
return formatConnectPairingRequiredMessage(params.details);
@@ -9,6 +9,14 @@ import {
validateCronUpdateParams,
} from "./index.js";
/**
* Cron validator regressions for public scheduler RPC payloads.
*
* The cases cover both canonical `id` selectors and legacy `jobId` aliases,
* delivery routing, update clears, and run-log path traversal guards.
*/
/** Smallest valid cron job create payload shared by add/update variations. */
const minimalAddParams = {
name: "daily-summary",
schedule: { kind: "every", everyMs: 60_000 },
@@ -5,6 +5,12 @@ import {
validateExecApprovalsSetParams,
} from "./index.js";
/**
* Exec approval validator regressions for gateway and node-scoped policy
* writes. The fixtures pin runtime-owned allowlist metadata and command-span
* bounds because those contracts are consumed by approval UI and replay logic.
*/
describe("exec approvals protocol validators", () => {
it("accepts runtime-owned allowlist metadata on gateway and node set payloads", () => {
const file = {
@@ -36,6 +36,15 @@ import {
type ValidationError,
} from "./index.js";
/**
* Broad protocol validator smoke tests.
*
* This file exercises exported lazy validators, readable validation errors, and
* representative cross-surface payloads so schema registry changes fail before
* they reach CLI, Gateway, channel, or dashboard consumers.
*/
/** Builds a validation error fixture while keeping only the field under test noisy. */
const makeError = (overrides: Partial<ValidationError>): ValidationError => ({
keyword: "type",
instancePath: "",
@@ -45,6 +54,7 @@ const makeError = (overrides: Partial<ValidationError>): ValidationError => ({
...overrides,
});
/** Runtime shape shared by all exported lazy protocol validator functions. */
type ProtocolValidator = (value: unknown) => boolean;
describe("lazy protocol validators", () => {
+16
View File
@@ -1,3 +1,5 @@
// Public gateway protocol entrypoint. Keep this barrel aligned with schema.ts
// so clients can import wire types, JSON schemas, and validators from one place.
import { Compile, type Validator as TypeBoxValidator } from "typebox/compile";
import {
type AgentEvent,
@@ -464,11 +466,16 @@ export type ValidationError = {
message?: string;
};
/** Runtime validator shape shared by gateway clients and server handlers. */
export type ProtocolValidator<T = unknown> = ((data: unknown) => data is T) & {
/** Last validation errors, matching Ajv-style caller expectations. */
errors: ValidationError[] | null;
/** Original schema used by the validator, exposed for diagnostics/tests. */
schema: unknown;
};
// Defer TypeBox compilation until the first validation call. Importing this
// module is common in CLIs/tests, so eager compilation would add startup cost.
function lazyCompile<T = unknown>(schema: unknown): ProtocolValidator<T> {
let compiled: TypeBoxValidator | undefined;
let errors: ValidationError[] | null = null;
@@ -491,6 +498,7 @@ function lazyCompile<T = unknown>(schema: unknown): ProtocolValidator<T> {
enumerable: true,
get: () => errors,
set: (nextErrors: ValidationError[] | null | undefined) => {
// Preserve Ajv-compatible mutability for callers/tests that clear errors.
errors = nextErrors ?? null;
},
},
@@ -504,6 +512,8 @@ function lazyCompile<T = unknown>(schema: unknown): ProtocolValidator<T> {
return validate;
}
// Public per-method validators. Names intentionally mirror the exported schema
// constants so call sites can pair validation with the wire contract directly.
export const validateCommandsListParams = lazyCompile<CommandsListParams>(CommandsListParamsSchema);
export const validateConnectParams = lazyCompile<ConnectParams>(ConnectParamsSchema);
export const validateRequestFrame = lazyCompile<RequestFrame>(RequestFrameSchema);
@@ -873,6 +883,7 @@ function firstStringParam(value: unknown): string | undefined {
return undefined;
}
/** Convert validator errors into compact operator-facing failure text. */
export function formatValidationErrors(errors: ValidationError[] | null | undefined) {
if (!errors?.length) {
return "unknown validation error";
@@ -907,6 +918,8 @@ export function formatValidationErrors(errors: ValidationError[] | null | undefi
const failingKeyword =
typeof err?.params?.failingKeyword === "string" ? err.params.failingKeyword : "";
// TypeBox reports conditional required-property misses through if/then
// keywords, which otherwise hide the actionable missing-property context.
const message =
keyword === "then" || (keyword === "if" && failingKeyword === "then")
? "must have required conditional properties"
@@ -925,6 +938,8 @@ export function formatValidationErrors(errors: ValidationError[] | null | undefi
return unique.join("; ");
}
// Schema exports stay explicit to make additions/removals reviewable as public
// protocol surface changes.
export {
ConnectParamsSchema,
HelloOkSchema,
@@ -1132,6 +1147,7 @@ export {
errorShape,
};
// Type exports mirror the schema exports for downstream TypeScript consumers.
export type {
GatewayFrame,
ConnectParams,
@@ -3,6 +3,15 @@ import path from "node:path";
import { describe, it } from "vitest";
import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "./version.js";
/**
* Cross-language guard for Gateway protocol version constants.
*
* Native Swift/Kotlin clients and dev smoke scripts cannot derive these values
* from TypeScript at runtime, so this test keeps checked-in generated constants
* and connect payloads aligned with the package source of truth.
*/
/** Min/max protocol pair expected in every native client surface. */
type ProtocolLevels = {
min: number;
max: number;
@@ -13,10 +22,12 @@ const expectedLevels: ProtocolLevels = {
max: PROTOCOL_VERSION,
};
/** Reads a repo-relative source file used by a native protocol guard. */
async function readRepoFile(relativePath: string): Promise<string> {
return fs.readFile(path.join(process.cwd(), relativePath), "utf8");
}
/** Extracts one integer constant and reports the owning file on drift. */
function extractInteger(
content: string,
pattern: RegExp,
@@ -32,6 +43,7 @@ function extractInteger(
return Number.parseInt(match[1], 10);
}
/** Compares native min/max values to the TypeScript version constants. */
function assertLevelsMatch(relativePath: string, actual: ProtocolLevels): void {
if (actual.min === expectedLevels.min && actual.max === expectedLevels.max) {
return;
@@ -41,6 +53,7 @@ function assertLevelsMatch(relativePath: string, actual: ProtocolLevels): void {
);
}
/** Asserts a compatibility pattern exists in generated/native source text. */
function assertPattern(
content: string,
relativePath: string,
@@ -6,6 +6,11 @@ import {
} from "../../../src/test-utils/secret-ref-test-vectors.js";
import { SecretInputSchema, SecretRefSchema } from "./schema/primitives.js";
/**
* SecretRef schema regressions shared with core secret-ref test vectors.
* Exec-backed ids have stricter character rules than env/file refs, so these
* checks keep provider config payloads aligned with runtime secret resolution.
*/
describe("gateway protocol SecretRef schema", () => {
const validateSecretRef = Compile(SecretRefSchema);
const validateSecretInput = Compile(SecretInputSchema);
@@ -2,6 +2,11 @@ import { Compile } from "typebox/compile";
import { describe, expect, it } from "vitest";
import { PushTestResultSchema } from "./schema/push.js";
/**
* Push protocol schema regression for APNS test results.
* The transport field tells operators whether delivery used direct APNS or the
* relay path, so it is part of the public result contract.
*/
describe("gateway protocol push schema", () => {
const validatePushTestResult = Compile(PushTestResultSchema);
+6
View File
@@ -1,3 +1,9 @@
/**
* Public schema barrel for the gateway protocol package.
*
* Runtime validators import canonical TypeBox schemas from their owning modules;
* this barrel gives package consumers one stable path for schema-level imports.
*/
export * from "./schema/primitives.js";
export * from "./schema/agent.js";
export * from "./schema/agents-models-skills.js";
@@ -2,6 +2,11 @@ import { Value } from "typebox/value";
import { describe, expect, it } from "vitest";
import { AgentParamsSchema } from "./agent.js";
/**
* Regression coverage for agent-run schema payloads that carry internal
* completion events. These events are produced by child automation and consumed
* by parent agent runs, so the fixture mirrors the cross-runtime boundary.
*/
type AgentInternalEvent = {
type: "task_completion";
source: string;
@@ -17,6 +22,7 @@ type AgentInternalEvent = {
replyInstruction?: string;
};
/** Builds the smallest valid agent request that embeds one internal event. */
function makeAgentParamsWithInternalEvent(event: AgentInternalEvent) {
return {
message: "A music generation task finished. Process the completion update now.",
@@ -26,6 +32,7 @@ function makeAgentParamsWithInternalEvent(event: AgentInternalEvent) {
};
}
/** Representative generated-media completion event from a child task. */
const musicCompletionEvent: AgentInternalEvent = {
type: "task_completion",
source: "music_generation",
@@ -1,6 +1,13 @@
import { Type } from "typebox";
import { InputProvenanceSchema, NonEmptyString, SessionLabelString } from "./primitives.js";
/**
* Agent and channel-action gateway schemas.
*
* These payloads sit on the boundary between external channel adapters, gateway
* RPC callers, and the agent runtime. Keep public request fields documented
* because older CLI/channel clients may continue sending them across releases.
*/
const AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION = "task_completion";
const AGENT_INTERNAL_EVENT_SOURCES = [
"subagent",
@@ -11,6 +18,7 @@ const AGENT_INTERNAL_EVENT_SOURCES = [
] as const;
const AGENT_INTERNAL_EVENT_STATUSES = ["ok", "timeout", "error", "unknown"] as const;
/** Generated media/file attachment metadata carried by internal agent events. */
export const AgentGeneratedAttachmentSchema = Type.Object(
{
type: Type.Optional(Type.String({ enum: ["image", "audio", "video", "file"] })),
@@ -24,6 +32,7 @@ export const AgentGeneratedAttachmentSchema = Type.Object(
{ additionalProperties: false },
);
/** Internal completion event surfaced when child automation reports back to a parent run. */
export const AgentInternalEventSchema = Type.Object(
{
type: Type.Literal(AGENT_INTERNAL_EVENT_TYPE_TASK_COMPLETION),
@@ -43,6 +52,7 @@ export const AgentInternalEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Stream event emitted by the agent runtime over the gateway protocol. */
export const AgentEventSchema = Type.Object(
{
runId: NonEmptyString,
@@ -56,6 +66,7 @@ export const AgentEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Channel context injected into message actions so tools can reply in-place. */
export const MessageActionToolContextSchema = Type.Object(
{
currentChannelId: Type.Optional(Type.String()),
@@ -84,6 +95,7 @@ export const MessageActionToolContextSchema = Type.Object(
{ additionalProperties: false },
);
/** Request to execute a channel message action through a configured adapter. */
export const MessageActionParamsSchema = Type.Object(
{
channel: NonEmptyString,
@@ -106,6 +118,7 @@ export const MessageActionParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Outbound send request shared by channel adapters. */
export const SendParamsSchema = Type.Object(
{
to: NonEmptyString,
@@ -135,6 +148,7 @@ export const SendParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Poll creation request for adapters that support native polls. */
export const PollParamsSchema = Type.Object(
{
to: NonEmptyString,
@@ -157,6 +171,7 @@ export const PollParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Main agent-run request accepted by the gateway. */
export const AgentParamsSchema = Type.Object(
{
message: NonEmptyString,
@@ -212,6 +227,7 @@ export const AgentParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Identity lookup request for the current or selected agent/session. */
export const AgentIdentityParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -220,6 +236,7 @@ export const AgentIdentityParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Public display identity returned for an agent. */
export const AgentIdentityResultSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -233,6 +250,7 @@ export const AgentIdentityResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Waits for a submitted agent run to complete or time out. */
export const AgentWaitParamsSchema = Type.Object(
{
runId: NonEmptyString,
@@ -241,6 +259,7 @@ export const AgentWaitParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Wake request from external schedulers or devices into an agent session. */
export const WakeParamsSchema = Type.Object(
{
mode: Type.Union([Type.Literal("now"), Type.Literal("next-heartbeat")]),
@@ -6,6 +6,13 @@ import {
ToolsEffectiveResultSchema,
} from "./agents-models-skills.js";
/**
* Schema regression tests for agent metadata, skill proposals, and effective
* tool catalogs. These payloads are UI-facing but also consumed by runtime
* guards, so the fixtures exercise strictness at the public gateway boundary.
*/
/** Minimal effective-tools result used by strict notice tests. */
function toolsEffectiveResult() {
return {
agentId: "main",
@@ -1,6 +1,16 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Agent, model, skill, and tool catalog schemas.
*
* These contracts back dashboard selectors, agent management, model catalogs,
* skill upload/install flows, skill workshop proposals, and effective tool
* discovery. Keep public request/result schemas documented because they are
* shared by gateway RPC, CLI, and UI clients.
*/
/** Model option shown in selectors and model catalog results. */
export const ModelChoiceSchema = Type.Object(
{
id: NonEmptyString,
@@ -13,6 +23,7 @@ export const ModelChoiceSchema = Type.Object(
{ additionalProperties: false },
);
/** Condensed agent record returned by list APIs. */
export const AgentSummarySchema = Type.Object(
{
id: NonEmptyString,
@@ -73,8 +84,10 @@ export const AgentSummarySchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for listing configured agents. */
export const AgentsListParamsSchema = Type.Object({}, { additionalProperties: false });
/** Agent list result including the default agent and session scoping mode. */
export const AgentsListResultSchema = Type.Object(
{
defaultId: NonEmptyString,
@@ -85,6 +98,7 @@ export const AgentsListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Creates a configured agent with workspace, identity, and optional model. */
export const AgentsCreateParamsSchema = Type.Object(
{
name: NonEmptyString,
@@ -96,6 +110,7 @@ export const AgentsCreateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result returned after creating an agent. */
export const AgentsCreateResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -107,6 +122,7 @@ export const AgentsCreateResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Updates mutable agent identity, workspace, and model fields. */
export const AgentsUpdateParamsSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -119,6 +135,7 @@ export const AgentsUpdateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result returned after updating an agent. */
export const AgentsUpdateResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -127,6 +144,7 @@ export const AgentsUpdateResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Deletes an agent and optionally its workspace/config files. */
export const AgentsDeleteParamsSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -135,6 +153,7 @@ export const AgentsDeleteParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result returned after deleting an agent and unbinding sessions. */
export const AgentsDeleteResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -144,6 +163,7 @@ export const AgentsDeleteResultSchema = Type.Object(
{ additionalProperties: false },
);
/** File metadata and optional content for agent-local editable files. */
export const AgentsFileEntrySchema = Type.Object(
{
name: NonEmptyString,
@@ -156,6 +176,7 @@ export const AgentsFileEntrySchema = Type.Object(
{ additionalProperties: false },
);
/** Lists editable files for one agent. */
export const AgentsFilesListParamsSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -163,6 +184,7 @@ export const AgentsFilesListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Editable file list for an agent workspace. */
export const AgentsFilesListResultSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -172,6 +194,7 @@ export const AgentsFilesListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads one editable agent file by name. */
export const AgentsFilesGetParamsSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -180,6 +203,7 @@ export const AgentsFilesGetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result for reading one editable agent file. */
export const AgentsFilesGetResultSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -189,6 +213,7 @@ export const AgentsFilesGetResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Writes one editable agent file. */
export const AgentsFilesSetParamsSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -198,6 +223,7 @@ export const AgentsFilesSetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result returned after writing an editable agent file. */
export const AgentsFilesSetResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -208,6 +234,7 @@ export const AgentsFilesSetResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Model catalog request with optional visibility scope. */
export const ModelsListParamsSchema = Type.Object(
{
view: Type.Optional(
@@ -217,6 +244,7 @@ export const ModelsListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Model catalog result. */
export const ModelsListResultSchema = Type.Object(
{
models: Type.Array(ModelChoiceSchema),
@@ -224,6 +252,7 @@ export const ModelsListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads installed skill status, optionally for a selected agent. */
export const SkillsStatusParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -231,8 +260,10 @@ export const SkillsStatusParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for listing available skill bins. */
export const SkillsBinsParamsSchema = Type.Object({}, { additionalProperties: false });
/** Skill bin names available to the gateway. */
export const SkillsBinsResultSchema = Type.Object(
{
bins: Type.Array(NonEmptyString),
@@ -254,6 +285,7 @@ const SkillUploadDataBase64String = Type.String({
maxLength: 5_592_408,
});
/** Starts a chunked skill archive upload. */
export const SkillsUploadBeginParamsSchema = Type.Object(
{
kind: Type.Literal("skill-archive"),
@@ -266,6 +298,7 @@ export const SkillsUploadBeginParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Uploads one base64-encoded chunk for a skill archive. */
export const SkillsUploadChunkParamsSchema = Type.Object(
{
uploadId: NonEmptyString,
@@ -275,6 +308,7 @@ export const SkillsUploadChunkParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Commits a completed skill archive upload. */
export const SkillsUploadCommitParamsSchema = Type.Object(
{
uploadId: NonEmptyString,
@@ -283,6 +317,7 @@ export const SkillsUploadCommitParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Installs a skill from legacy install id, ClawHub, or uploaded archive. */
export const SkillsInstallParamsSchema = Type.Union([
Type.Object(
{
@@ -322,6 +357,7 @@ export const SkillsInstallParamsSchema = Type.Union([
),
]);
/** Updates installed skill settings or refreshes ClawHub-installed skills. */
export const SkillsUpdateParamsSchema = Type.Union([
Type.Object(
{
@@ -342,6 +378,7 @@ export const SkillsUpdateParamsSchema = Type.Union([
),
]);
/** Searches the skill registry. */
export const SkillsSearchParamsSchema = Type.Object(
{
query: Type.Optional(NonEmptyString),
@@ -350,6 +387,7 @@ export const SkillsSearchParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Ranked skill registry search results. */
export const SkillsSearchResultSchema = Type.Object(
{
results: Type.Array(
@@ -369,6 +407,7 @@ export const SkillsSearchResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads registry detail for one skill slug. */
export const SkillsDetailParamsSchema = Type.Object(
{
slug: NonEmptyString,
@@ -376,6 +415,7 @@ export const SkillsDetailParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads current security verdicts for configured skills. */
export const SkillsSecurityVerdictsParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -383,6 +423,7 @@ export const SkillsSecurityVerdictsParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Skill registry detail, latest version, metadata, and owner info. */
export const SkillsDetailResultSchema = Type.Object(
{
skill: Type.Union([
@@ -441,6 +482,7 @@ export const SkillsDetailResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Security verdict report for installed/requested skills. */
export const SkillsSecurityVerdictsResultSchema = Type.Object(
{
schema: Type.Literal("openclaw.skills.security-verdicts.v1"),
@@ -481,6 +523,7 @@ export const SkillsSecurityVerdictsResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads the rendered skill card for one installed skill. */
export const SkillsSkillCardParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -489,6 +532,7 @@ export const SkillsSkillCardParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Rendered skill card content and file metadata. */
export const SkillsSkillCardResultSchema = Type.Object(
{
schema: Type.Literal("openclaw.skills.skill-card.v1"),
@@ -507,19 +551,23 @@ const SkillProposalStatusSchema = Type.Union([
Type.Literal("quarantined"),
Type.Literal("stale"),
]);
/** Skill proposal operation type: new skill or update to an existing skill. */
const SkillProposalKindSchema = Type.Union([Type.Literal("create"), Type.Literal("update")]);
/** Scan state for proposed skill content before it can be applied. */
const SkillProposalScanStateSchema = Type.Union([
Type.Literal("pending"),
Type.Literal("clean"),
Type.Literal("failed"),
Type.Literal("quarantined"),
]);
/** Source that created the skill proposal record. */
const SkillProposalSourceSchema = Type.Union([
Type.Literal("skill-workshop"),
Type.Literal("cli"),
Type.Literal("gateway"),
]);
const SkillProposalContentString = Type.String({ minLength: 1, maxLength: 1_048_576 });
/** Support file payload accepted from proposal create/revise requests. */
const SkillProposalSupportFileInputSchema = Type.Object(
{
path: NonEmptyString,
@@ -527,6 +575,7 @@ const SkillProposalSupportFileInputSchema = Type.Object(
},
{ additionalProperties: false },
);
/** Stored support file metadata, including target conflict hashes for updates. */
const SkillProposalSupportFileSchema = Type.Object(
{
path: NonEmptyString,
@@ -538,6 +587,7 @@ const SkillProposalSupportFileSchema = Type.Object(
{ additionalProperties: false },
);
/** One static-scan finding against proposed skill content. */
const SkillProposalFindingSchema = Type.Object(
{
ruleId: NonEmptyString,
@@ -550,6 +600,7 @@ const SkillProposalFindingSchema = Type.Object(
{ additionalProperties: false },
);
/** Aggregated scan report attached to a proposal record. */
const SkillProposalScanSchema = Type.Object(
{
state: SkillProposalScanStateSchema,
@@ -562,6 +613,7 @@ const SkillProposalScanSchema = Type.Object(
{ additionalProperties: false },
);
/** Skill file target that a proposal creates or updates. */
const SkillProposalTargetSchema = Type.Object(
{
skillName: NonEmptyString,
@@ -574,6 +626,7 @@ const SkillProposalTargetSchema = Type.Object(
{ additionalProperties: false },
);
/** Optional runtime origin tying a proposal back to an agent turn. */
const SkillProposalOriginSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -584,6 +637,7 @@ const SkillProposalOriginSchema = Type.Object(
{ additionalProperties: false },
);
/** Full persisted skill proposal record. */
const SkillProposalRecordSchema = Type.Object(
{
schema: Type.Literal("openclaw.skill-workshop.proposal.v1"),
@@ -613,6 +667,7 @@ const SkillProposalRecordSchema = Type.Object(
{ additionalProperties: false },
);
/** Condensed proposal manifest entry for list views. */
const SkillProposalManifestEntrySchema = Type.Object(
{
id: NonEmptyString,
@@ -629,6 +684,7 @@ const SkillProposalManifestEntrySchema = Type.Object(
{ additionalProperties: false },
);
/** Lists skill-workshop proposals for the selected agent scope. */
export const SkillsProposalsListParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -636,6 +692,7 @@ export const SkillsProposalsListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Proposal manifest response for dashboard/workshop list views. */
export const SkillsProposalsListResultSchema = Type.Object(
{
schema: Type.Literal("openclaw.skill-workshop.proposals-manifest.v1"),
@@ -645,6 +702,7 @@ export const SkillsProposalsListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads a proposal record plus editable draft/support content. */
export const SkillsProposalInspectParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -653,6 +711,7 @@ export const SkillsProposalInspectParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Full proposal inspection result used before apply/revise decisions. */
export const SkillsProposalInspectResultSchema = Type.Object(
{
record: SkillProposalRecordSchema,
@@ -662,6 +721,7 @@ export const SkillsProposalInspectResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Creates a proposal for a new skill. */
export const SkillsProposalCreateParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -675,6 +735,7 @@ export const SkillsProposalCreateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Creates a proposal to update an existing skill. */
export const SkillsProposalUpdateParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -688,6 +749,7 @@ export const SkillsProposalUpdateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Replaces draft content/support files for an existing proposal. */
export const SkillsProposalReviseParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -701,6 +763,7 @@ export const SkillsProposalReviseParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Shared approve/reject/quarantine action payload for one proposal. */
export const SkillsProposalActionParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -710,6 +773,7 @@ export const SkillsProposalActionParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result returned after applying a skill proposal to disk. */
export const SkillsProposalApplyResultSchema = Type.Object(
{
record: SkillProposalRecordSchema,
@@ -718,8 +782,10 @@ export const SkillsProposalApplyResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Proposal record result returned after non-apply proposal actions. */
export const SkillsProposalRecordResultSchema = SkillProposalRecordSchema;
/** Reads the configured tool catalog for an agent. */
export const ToolsCatalogParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -728,6 +794,7 @@ export const ToolsCatalogParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads the effective tool set for one session. */
export const ToolsEffectiveParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -736,6 +803,7 @@ export const ToolsEffectiveParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Invokes one tool through the gateway tool dispatcher. */
export const ToolsInvokeParamsSchema = Type.Object(
{
name: NonEmptyString,
@@ -748,6 +816,7 @@ export const ToolsInvokeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Tool profile shown in catalog views. */
export const ToolCatalogProfileSchema = Type.Object(
{
id: Type.Union([
@@ -761,6 +830,7 @@ export const ToolCatalogProfileSchema = Type.Object(
{ additionalProperties: false },
);
/** Tool catalog entry before session-specific filtering is applied. */
export const ToolCatalogEntrySchema = Type.Object(
{
id: NonEmptyString,
@@ -785,6 +855,7 @@ export const ToolCatalogEntrySchema = Type.Object(
{ additionalProperties: false },
);
/** Group of related catalog tools from core or a plugin. */
export const ToolCatalogGroupSchema = Type.Object(
{
id: NonEmptyString,
@@ -796,6 +867,7 @@ export const ToolCatalogGroupSchema = Type.Object(
{ additionalProperties: false },
);
/** Tool catalog result for agent configuration UI. */
export const ToolsCatalogResultSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -805,6 +877,7 @@ export const ToolsCatalogResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Effective tool entry after session/profile/channel/plugin filtering. */
export const ToolsEffectiveEntrySchema = Type.Object(
{
id: NonEmptyString,
@@ -827,6 +900,7 @@ export const ToolsEffectiveEntrySchema = Type.Object(
{ additionalProperties: false },
);
/** Effective tool group shown to runtime/session callers. */
export const ToolsEffectiveGroupSchema = Type.Object(
{
id: Type.Union([
@@ -847,6 +921,7 @@ export const ToolsEffectiveGroupSchema = Type.Object(
{ additionalProperties: false },
);
/** Notice explaining runtime filtering such as quarantined tool schemas. */
export const ToolsEffectiveNoticeSchema = Type.Object(
{
id: NonEmptyString,
@@ -856,6 +931,7 @@ export const ToolsEffectiveNoticeSchema = Type.Object(
{ additionalProperties: false },
);
/** Effective tool set for a session, including profile and filtering notices. */
export const ToolsEffectiveResultSchema = Type.Object(
{
agentId: NonEmptyString,
@@ -866,6 +942,7 @@ export const ToolsEffectiveResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Normalized error shape for tool invocation failures. */
export const ToolsInvokeErrorSchema = Type.Object(
{
code: NonEmptyString,
@@ -875,6 +952,7 @@ export const ToolsInvokeErrorSchema = Type.Object(
{ additionalProperties: false },
);
/** Tool invocation result, including approval handoff when required. */
export const ToolsInvokeResultSchema = Type.Object(
{
ok: Type.Boolean(),
@@ -1,6 +1,12 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Artifact lookup and download protocol schemas.
*
* Artifacts are files or payloads produced by sessions, runs, tasks, or agents;
* these schemas keep lookup filters explicit and download results transport-safe.
*/
const ArtifactQueryParamsProperties = {
sessionKey: Type.Optional(NonEmptyString),
runId: Type.Optional(NonEmptyString),
@@ -8,10 +14,12 @@ const ArtifactQueryParamsProperties = {
agentId: Type.Optional(NonEmptyString),
};
/** Shared artifact filter payload used by list-style requests. */
export const ArtifactQueryParamsSchema = Type.Object(ArtifactQueryParamsProperties, {
additionalProperties: false,
});
/** Artifact lookup payload with a required artifact id plus optional scope filters. */
export const ArtifactGetParamsSchema = Type.Object(
{
...ArtifactQueryParamsProperties,
@@ -20,6 +28,7 @@ export const ArtifactGetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Public artifact metadata returned before or alongside download data. */
export const ArtifactSummarySchema = Type.Object(
{
id: NonEmptyString,
@@ -42,8 +51,10 @@ export const ArtifactSummarySchema = Type.Object(
{ additionalProperties: false },
);
/** List request payload for artifacts visible in the selected scope. */
export const ArtifactsListParamsSchema = ArtifactQueryParamsSchema;
/** List response containing artifact summaries only. */
export const ArtifactsListResultSchema = Type.Object(
{
artifacts: Type.Array(ArtifactSummarySchema),
@@ -51,8 +62,10 @@ export const ArtifactsListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Get request payload for one artifact summary. */
export const ArtifactsGetParamsSchema = ArtifactGetParamsSchema;
/** Get response containing one artifact summary. */
export const ArtifactsGetResultSchema = Type.Object(
{
artifact: ArtifactSummarySchema,
@@ -60,8 +73,10 @@ export const ArtifactsGetResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Download request payload for one artifact. */
export const ArtifactsDownloadParamsSchema = ArtifactGetParamsSchema;
/** Download response, either inline base64 bytes, URL, or metadata for unsupported modes. */
export const ArtifactsDownloadResultSchema = Type.Object(
{
artifact: ArtifactSummarySchema,
@@ -1,6 +1,15 @@
import { Type } from "typebox";
import { NonEmptyString, SecretInputSchema } from "./primitives.js";
/**
* Channel and Talk protocol schemas.
*
* Talk schemas are consumed by browser realtime clients, gateway relay sessions,
* and channel adapters, so the mode/transport/brain unions below are shared
* API vocabulary rather than provider-local implementation details.
*/
/** Toggles Talk mode for the gateway, with an optional rollout phase marker. */
export const TalkModeParamsSchema = Type.Object(
{
enabled: Type.Boolean(),
@@ -9,6 +18,7 @@ export const TalkModeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads Talk configuration; secrets are included only for trusted callers. */
export const TalkConfigParamsSchema = Type.Object(
{
includeSecrets: Type.Optional(Type.Boolean()),
@@ -16,6 +26,7 @@ export const TalkConfigParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** One-shot text-to-speech request with provider-specific voice tuning knobs. */
export const TalkSpeakParamsSchema = Type.Object(
{
text: NonEmptyString,
@@ -36,12 +47,14 @@ export const TalkSpeakParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Supported Talk session shapes exposed to clients and providers. */
const TalkModeSchema = Type.Union([
Type.Literal("realtime"),
Type.Literal("stt-tts"),
Type.Literal("transcription"),
]);
/** Transport families; browser clients branch on this value to choose setup flow. */
const TalkTransportSchema = Type.Union([
Type.Literal("webrtc"),
Type.Literal("provider-websocket"),
@@ -49,12 +62,14 @@ const TalkTransportSchema = Type.Union([
Type.Literal("managed-room"),
]);
/** How a Talk session delegates reasoning/tool use to the agent runtime. */
const TalkBrainSchema = Type.Union([
Type.Literal("agent-consult"),
Type.Literal("direct-tools"),
Type.Literal("none"),
]);
/** Agent control actions accepted from Talk clients and managed rooms. */
const TalkAgentControlModeSchema = Type.Union([
Type.Literal("status"),
Type.Literal("steer"),
@@ -62,6 +77,7 @@ const TalkAgentControlModeSchema = Type.Union([
Type.Literal("followup"),
]);
/** Stable event names emitted by Talk sessions across providers/transports. */
const TalkEventTypeSchema = Type.Union([
Type.Literal("session.started"),
Type.Literal("session.ready"),
@@ -93,6 +109,7 @@ const TalkEventTypeSchema = Type.Union([
Type.Literal("health.changed"),
]);
/** Event types that must carry a turn id for client-side stream correlation. */
const TURN_SCOPED_TALK_EVENT_TYPES = [
"turn.started",
"turn.ended",
@@ -112,6 +129,7 @@ const TURN_SCOPED_TALK_EVENT_TYPES = [
"tool.error",
];
/** Capture lifecycle events must include capture id to avoid cross-turn ambiguity. */
const CAPTURE_SCOPED_TALK_EVENT_TYPES = [
"capture.started",
"capture.stopped",
@@ -119,11 +137,13 @@ const CAPTURE_SCOPED_TALK_EVENT_TYPES = [
"capture.once",
];
/** Builds JSON Schema conditional requirements while avoiding reserved word syntax. */
function requireJsonSchemaProperties(properties: string[]): Record<string, { required: string[] }> {
const conditionalRequirementKey = ["th", "en"].join("");
return Object.fromEntries([[conditionalRequirementKey, { required: properties }]]);
}
/** Canonical Talk event envelope emitted to browser, relay, and channel consumers. */
export const TalkEventSchema = Type.Object(
{
id: NonEmptyString,
@@ -164,6 +184,7 @@ export const TalkEventSchema = Type.Object(
},
);
/** Creates a browser-facing Talk client session. */
export const TalkClientCreateParamsSchema = Type.Object(
{
sessionKey: Type.Optional(Type.String()),
@@ -181,6 +202,7 @@ export const TalkClientCreateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Tool-call request from a browser/client session back into the agent runtime. */
export const TalkClientToolCallParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
@@ -192,6 +214,7 @@ export const TalkClientToolCallParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Agent run identity returned after accepting a Talk client tool call. */
export const TalkClientToolCallResultSchema = Type.Object(
{
runId: NonEmptyString,
@@ -200,6 +223,7 @@ export const TalkClientToolCallResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Text steering request for a Talk session bound to an agent turn. */
export const TalkClientSteerParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
@@ -209,6 +233,7 @@ export const TalkClientSteerParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result of applying agent control to an embedded or reply-backed Talk run. */
export const TalkAgentControlResultSchema = Type.Object(
{
ok: Type.Boolean(),
@@ -239,6 +264,7 @@ export const TalkAgentControlResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Joins an existing managed-room Talk session. */
export const TalkSessionJoinParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -247,6 +273,7 @@ export const TalkSessionJoinParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Creates a gateway-managed Talk session for realtime, transcription, or relay use. */
export const TalkSessionCreateParamsSchema = Type.Object(
{
sessionKey: Type.Optional(Type.String()),
@@ -266,6 +293,7 @@ export const TalkSessionCreateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Appends base64 audio to an active Talk session. */
export const TalkSessionAppendAudioParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -275,6 +303,7 @@ export const TalkSessionAppendAudioParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Starts or advances a Talk turn within a session. */
export const TalkSessionTurnParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -283,6 +312,7 @@ export const TalkSessionTurnParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Cancels the active or named Talk turn. */
export const TalkSessionCancelTurnParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -292,6 +322,7 @@ export const TalkSessionCancelTurnParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Cancels currently streaming Talk output without necessarily ending the turn. */
export const TalkSessionCancelOutputParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -301,6 +332,7 @@ export const TalkSessionCancelOutputParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Submits a tool result back to a Talk provider session. */
export const TalkSessionSubmitToolResultParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -319,6 +351,7 @@ export const TalkSessionSubmitToolResultParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Steers a managed Talk session by session id rather than transcript key. */
export const TalkSessionSteerParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -329,6 +362,7 @@ export const TalkSessionSteerParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Closes a gateway-managed Talk session. */
export const TalkSessionCloseParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -336,6 +370,7 @@ export const TalkSessionCloseParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Mutable room state returned when a client joins a managed Talk room. */
const TalkSessionManagedRoomStateSchema = Type.Object(
{
activeClientId: Type.Optional(Type.String()),
@@ -345,6 +380,7 @@ const TalkSessionManagedRoomStateSchema = Type.Object(
{ additionalProperties: false },
);
/** Managed-room session record shared with browser clients. */
const TalkSessionManagedRoomRecordSchema = Type.Object(
{
id: NonEmptyString,
@@ -367,8 +403,10 @@ const TalkSessionManagedRoomRecordSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for reading configured Talk provider capabilities. */
export const TalkCatalogParamsSchema = Type.Object({}, { additionalProperties: false });
/** One provider entry in the Talk capability catalog. */
const TalkCatalogProviderSchema = Type.Object(
{
id: NonEmptyString,
@@ -413,6 +451,7 @@ const TalkCatalogProviderSchema = Type.Object(
{ additionalProperties: false },
);
/** Active provider plus all candidates for a Talk capability family. */
const TalkCatalogProviderGroupSchema = Type.Object(
{
activeProvider: Type.Optional(Type.String()),
@@ -421,6 +460,7 @@ const TalkCatalogProviderGroupSchema = Type.Object(
{ additionalProperties: false },
);
/** Provider, mode, transport, and audio-format catalog returned to clients. */
export const TalkCatalogResultSchema = Type.Object(
{
modes: Type.Array(TalkModeSchema),
@@ -433,6 +473,7 @@ export const TalkCatalogResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Audio format contract for realtime browser sessions. */
const BrowserRealtimeAudioContractSchema = Type.Object(
{
inputEncoding: Type.Union([Type.Literal("pcm16"), Type.Literal("g711_ulaw")]),
@@ -443,6 +484,7 @@ const BrowserRealtimeAudioContractSchema = Type.Object(
{ additionalProperties: false },
);
/** Session creation result with transport-specific ids and credentials. */
export const TalkSessionCreateResultSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -464,6 +506,7 @@ export const TalkSessionCreateResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Result for a Talk turn request, optionally including emitted events. */
export const TalkSessionTurnResultSchema = Type.Object(
{
ok: Type.Boolean(),
@@ -473,8 +516,10 @@ export const TalkSessionTurnResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Managed-room record returned to clients after joining an existing Talk session. */
export const TalkSessionJoinResultSchema = TalkSessionManagedRoomRecordSchema;
/** Generic success result for Talk session lifecycle calls. */
export const TalkSessionOkResultSchema = Type.Object(
{
ok: Type.Boolean(),
@@ -482,6 +527,7 @@ export const TalkSessionOkResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Browser WebRTC setup payload using provider SDP exchange. */
const BrowserRealtimeWebRtcSdpSessionSchema = Type.Object(
{
provider: NonEmptyString,
@@ -496,6 +542,7 @@ const BrowserRealtimeWebRtcSdpSessionSchema = Type.Object(
{ additionalProperties: false },
);
/** Browser websocket setup payload with JSON/PCM audio contract. */
const BrowserRealtimeJsonPcmWebSocketSessionSchema = Type.Object(
{
provider: NonEmptyString,
@@ -512,6 +559,7 @@ const BrowserRealtimeJsonPcmWebSocketSessionSchema = Type.Object(
{ additionalProperties: false },
);
/** Browser setup payload for gateway-relayed realtime audio. */
const BrowserRealtimeGatewayRelaySessionSchema = Type.Object(
{
provider: NonEmptyString,
@@ -525,6 +573,7 @@ const BrowserRealtimeGatewayRelaySessionSchema = Type.Object(
{ additionalProperties: false },
);
/** Browser setup payload for managed-room Talk sessions. */
const BrowserRealtimeManagedRoomSessionSchema = Type.Object(
{
provider: NonEmptyString,
@@ -538,6 +587,7 @@ const BrowserRealtimeManagedRoomSessionSchema = Type.Object(
{ additionalProperties: false },
);
/** Union of all browser Talk session setup payloads. */
export const TalkClientCreateResultSchema = Type.Union([
BrowserRealtimeWebRtcSdpSessionSchema,
BrowserRealtimeJsonPcmWebSocketSessionSchema,
@@ -545,14 +595,17 @@ export const TalkClientCreateResultSchema = Type.Union([
BrowserRealtimeManagedRoomSessionSchema,
]);
/** Secret-bearing provider fields; extra provider options remain provider-owned. */
const talkProviderFieldSchemas = {
apiKey: Type.Optional(SecretInputSchema),
};
/** Per-provider Talk config bag. */
const TalkProviderConfigSchema = Type.Object(talkProviderFieldSchemas, {
additionalProperties: true,
});
/** Realtime Talk defaults and provider selection stored in config. */
const TalkRealtimeConfigSchema = Type.Object(
{
provider: Type.Optional(Type.String()),
@@ -569,6 +622,7 @@ const TalkRealtimeConfigSchema = Type.Object(
{ additionalProperties: false },
);
/** Resolved active Talk provider plus its normalized provider config. */
const ResolvedTalkConfigSchema = Type.Object(
{
provider: Type.String(),
@@ -577,6 +631,7 @@ const ResolvedTalkConfigSchema = Type.Object(
{ additionalProperties: false },
);
/** Talk config subtree returned through gateway config APIs. */
const TalkConfigSchema = Type.Object(
{
provider: Type.Optional(Type.String()),
@@ -592,6 +647,7 @@ const TalkConfigSchema = Type.Object(
{ additionalProperties: false },
);
/** Full Talk config read result, including related session/UI context. */
export const TalkConfigResultSchema = Type.Object(
{
config: Type.Object(
@@ -620,6 +676,7 @@ export const TalkConfigResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Text-to-speech result with encoded audio and provider output metadata. */
export const TalkSpeakResultSchema = Type.Object(
{
audioBase64: NonEmptyString,
@@ -632,6 +689,7 @@ export const TalkSpeakResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Channel status request, optionally probing one channel before returning. */
export const ChannelsStatusParamsSchema = Type.Object(
{
probe: Type.Optional(Type.Boolean()),
@@ -641,8 +699,12 @@ export const ChannelsStatusParamsSchema = Type.Object(
{ additionalProperties: false },
);
// Channel docking: channels.status is intentionally schema-light so new
// channels can ship without protocol updates.
/**
* Per-account status snapshot for channel docking.
*
* This is intentionally schema-light so new channel-specific metadata can ship
* without a gateway protocol update; known fields stay documented for UI use.
*/
export const ChannelAccountSnapshotSchema = Type.Object(
{
accountId: NonEmptyString,
@@ -683,6 +745,7 @@ export const ChannelAccountSnapshotSchema = Type.Object(
{ additionalProperties: true },
);
/** UI label and icon metadata for one channel. */
export const ChannelUiMetaSchema = Type.Object(
{
id: NonEmptyString,
@@ -693,6 +756,7 @@ export const ChannelUiMetaSchema = Type.Object(
{ additionalProperties: false },
);
/** Event-loop health snapshot included with channel status responses. */
export const ChannelEventLoopHealthSchema = Type.Object(
{
degraded: Type.Boolean(),
@@ -712,6 +776,7 @@ export const ChannelEventLoopHealthSchema = Type.Object(
{ additionalProperties: false },
);
/** Full channel status result for dashboard and operator diagnostics. */
export const ChannelsStatusResultSchema = Type.Object(
{
ts: Type.Integer({ minimum: 0 }),
@@ -730,6 +795,7 @@ export const ChannelsStatusResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Logs out one channel account. */
export const ChannelsLogoutParamsSchema = Type.Object(
{
channel: NonEmptyString,
@@ -738,6 +804,7 @@ export const ChannelsLogoutParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Stops one channel account runtime. */
export const ChannelsStopParamsSchema = Type.Object(
{
channel: NonEmptyString,
@@ -746,6 +813,7 @@ export const ChannelsStopParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Starts one channel account runtime. */
export const ChannelsStartParamsSchema = Type.Object(
{
channel: NonEmptyString,
@@ -754,6 +822,7 @@ export const ChannelsStartParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Starts browser/web login for a channel account. */
export const WebLoginStartParamsSchema = Type.Object(
{
force: Type.Optional(Type.Boolean()),
@@ -769,6 +838,7 @@ const QrDataUrlSchema = Type.String({
pattern: "^data:image/png;base64,",
});
/** Waits for web login completion or the next QR code. */
export const WebLoginWaitParamsSchema = Type.Object(
{
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
@@ -1,31 +1,50 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Command catalog protocol schemas.
*
* Command entries describe native, skill, and plugin commands that clients can
* render or route; limits keep command catalogs bounded for UI and transport.
*/
/** Maximum command display/name length accepted in catalog entries. */
export const COMMAND_NAME_MAX_LENGTH = 200;
/** Maximum command description length accepted in catalog entries. */
export const COMMAND_DESCRIPTION_MAX_LENGTH = 2_000;
/** Maximum text aliases advertised for one command. */
export const COMMAND_ALIAS_MAX_ITEMS = 20;
/** Maximum declared arguments advertised for one command. */
export const COMMAND_ARGS_MAX_ITEMS = 20;
/** Maximum argument name length accepted in catalog entries. */
export const COMMAND_ARG_NAME_MAX_LENGTH = 200;
/** Maximum argument description length accepted in catalog entries. */
export const COMMAND_ARG_DESCRIPTION_MAX_LENGTH = 500;
/** Maximum static choices advertised for one argument. */
export const COMMAND_ARG_CHOICES_MAX_ITEMS = 50;
/** Maximum machine-readable choice value length. */
export const COMMAND_CHOICE_VALUE_MAX_LENGTH = 200;
/** Maximum user-facing choice label length. */
export const COMMAND_CHOICE_LABEL_MAX_LENGTH = 200;
/** Maximum commands returned by one catalog response. */
export const COMMAND_LIST_MAX_ITEMS = 500;
const BoundedNonEmptyString = (maxLength: number) => Type.String({ minLength: 1, maxLength });
/** Source system that contributed a command. */
export const CommandSourceSchema = Type.Union([
Type.Literal("native"),
Type.Literal("skill"),
Type.Literal("plugin"),
]);
/** Surfaces where a command may be invoked. */
export const CommandScopeSchema = Type.Union([
Type.Literal("text"),
Type.Literal("native"),
Type.Literal("both"),
]);
/** Coarse UI grouping for command catalog display. */
export const CommandCategorySchema = Type.Union([
Type.Literal("session"),
Type.Literal("options"),
@@ -36,6 +55,7 @@ export const CommandCategorySchema = Type.Union([
Type.Literal("docks"),
]);
/** Static argument choice shown to clients. */
export const CommandArgChoiceSchema = Type.Object(
{
value: Type.String({ maxLength: COMMAND_CHOICE_VALUE_MAX_LENGTH }),
@@ -44,6 +64,7 @@ export const CommandArgChoiceSchema = Type.Object(
{ additionalProperties: false },
);
/** One typed argument advertised for a command. */
export const CommandArgSchema = Type.Object(
{
name: BoundedNonEmptyString(COMMAND_ARG_NAME_MAX_LENGTH),
@@ -58,6 +79,7 @@ export const CommandArgSchema = Type.Object(
{ additionalProperties: false },
);
/** One command catalog entry visible to clients. */
export const CommandEntrySchema = Type.Object(
{
name: BoundedNonEmptyString(COMMAND_NAME_MAX_LENGTH),
@@ -77,6 +99,7 @@ export const CommandEntrySchema = Type.Object(
{ additionalProperties: false },
);
/** Command catalog request filters. */
export const CommandsListParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -87,6 +110,7 @@ export const CommandsListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Bounded command catalog response. */
export const CommandsListResultSchema = Type.Object(
{
commands: Type.Array(CommandEntrySchema, { maxItems: COMMAND_LIST_MAX_ITEMS }),
@@ -1,6 +1,12 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Gateway config and update protocol schemas.
*
* These payloads carry raw config text plus optional delivery context so the
* gateway can report edits/restarts back to the originating channel.
*/
const ConfigSchemaLookupPathString = Type.String({
minLength: 1,
maxLength: 1024,
@@ -17,8 +23,10 @@ const ConfigDeliveryContextSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for reading the current raw config. */
export const ConfigGetParamsSchema = Type.Object({}, { additionalProperties: false });
/** Full raw config replacement request with optional base hash guard. */
export const ConfigSetParamsSchema = Type.Object(
{
raw: NonEmptyString,
@@ -27,6 +35,7 @@ export const ConfigSetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Shared config apply/patch payload with optional restart notification context. */
const ConfigApplyLikeParamsSchema = Type.Object(
{
raw: NonEmptyString,
@@ -39,11 +48,15 @@ const ConfigApplyLikeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Raw config apply request that may schedule a restart. */
export const ConfigApplyParamsSchema = ConfigApplyLikeParamsSchema;
/** Raw config patch request that may schedule a restart. */
export const ConfigPatchParamsSchema = ConfigApplyLikeParamsSchema;
/** Empty request payload for fetching the generated config schema. */
export const ConfigSchemaParamsSchema = Type.Object({}, { additionalProperties: false });
/** Schema lookup request for one config path. */
export const ConfigSchemaLookupParamsSchema = Type.Object(
{
path: ConfigSchemaLookupPathString,
@@ -51,8 +64,10 @@ export const ConfigSchemaLookupParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for checking update/restart status. */
export const UpdateStatusParamsSchema = Type.Object({}, { additionalProperties: false });
/** Request payload for running an update/restart flow with optional channel delivery context. */
export const UpdateRunParamsSchema = Type.Object(
{
sessionKey: Type.Optional(Type.String()),
@@ -65,6 +80,7 @@ export const UpdateRunParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** UI metadata attached to config schema paths. */
export const ConfigUiHintSchema = Type.Object(
{
label: Type.Optional(Type.String()),
@@ -80,6 +96,7 @@ export const ConfigUiHintSchema = Type.Object(
{ additionalProperties: false },
);
/** Full generated config schema response. */
export const ConfigSchemaResponseSchema = Type.Object(
{
schema: Type.Unknown(),
@@ -90,6 +107,7 @@ export const ConfigSchemaResponseSchema = Type.Object(
{ additionalProperties: false },
);
/** Child entry returned when looking up a config schema path. */
export const ConfigSchemaLookupChildSchema = Type.Object(
{
key: NonEmptyString,
@@ -106,6 +124,7 @@ export const ConfigSchemaLookupChildSchema = Type.Object(
{ additionalProperties: false },
);
/** Schema lookup response for one config path and its immediate children. */
export const ConfigSchemaLookupResultSchema = Type.Object(
{
path: NonEmptyString,
@@ -1,6 +1,14 @@
import { Type, type TSchema } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Cron scheduler protocol schemas.
*
* These contracts describe scheduled agent turns, system events, delivery
* routing, run history, and mutable job state shared by gateway RPC clients.
*/
/** Builds create/patch payload variants while preserving per-call field optionality. */
function cronAgentTurnPayloadSchema(params: { message: TSchema; toolsAllow: TSchema }) {
return Type.Object(
{
@@ -18,13 +26,16 @@ function cronAgentTurnPayloadSchema(params: { message: TSchema; toolsAllow: TSch
);
}
/** Session target accepted by cron jobs. */
const CronSessionTargetSchema = Type.Union([
Type.Literal("main"),
Type.Literal("isolated"),
Type.Literal("current"),
Type.String({ pattern: "^session:.+" }),
]);
/** Whether a cron job waits for heartbeat processing or wakes immediately. */
const CronWakeModeSchema = Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]);
/** Run status factory reused for the active field and deprecated alias metadata. */
function cronRunStatusSchema(options: Record<string, unknown> = {}) {
return Type.Union([Type.Literal("ok"), Type.Literal("error"), Type.Literal("skipped")], options);
}
@@ -159,6 +170,7 @@ const CronRunLogJobIdSchema = Type.String({
pattern: "^[^/\\\\]+$",
});
/** Schedule expression for one-time, interval, or cron-expression jobs. */
export const CronScheduleSchema = Type.Union([
Type.Object(
{
@@ -186,6 +198,7 @@ export const CronScheduleSchema = Type.Union([
),
]);
/** Full cron payload for new jobs. */
export const CronPayloadSchema = Type.Union([
Type.Object(
{
@@ -200,6 +213,7 @@ export const CronPayloadSchema = Type.Union([
}),
]);
/** Partial cron payload for job updates. */
export const CronPayloadPatchSchema = Type.Union([
Type.Object(
{
@@ -214,6 +228,7 @@ export const CronPayloadPatchSchema = Type.Union([
}),
]);
/** Failure alert policy for repeated cron run failures. */
export const CronFailureAlertSchema = Type.Object(
{
after: Type.Optional(Type.Integer({ minimum: 1 })),
@@ -227,6 +242,7 @@ export const CronFailureAlertSchema = Type.Object(
{ additionalProperties: false },
);
/** Delivery destination used when failure alerts need a separate target. */
export const CronFailureDestinationSchema = Type.Object(
{
channel: Type.Optional(CronAnnounceChannelSchema),
@@ -301,12 +317,14 @@ const CronDeliveryWebhookSchema = Type.Object(
{ additionalProperties: false },
);
/** Delivery policy for cron run output. */
export const CronDeliverySchema = Type.Union([
CronDeliveryNoopSchema,
CronDeliveryAnnounceSchema,
CronDeliveryWebhookSchema,
]);
/** Patch shape for cron delivery policy updates. */
export const CronDeliveryPatchSchema = Type.Object(
{
mode: Type.Optional(
@@ -330,6 +348,7 @@ const CronFailureNotificationDeliverySchema = Type.Object(
{ additionalProperties: false },
);
/** Scheduler-maintained state for the latest run/delivery outcome. */
export const CronJobStateSchema = Type.Object(
{
nextRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
@@ -378,6 +397,7 @@ const CronJobStatePatchSchema = Type.Object(
{ additionalProperties: false },
);
/** Persisted cron job definition returned by scheduler list/get APIs. */
export const CronJobSchema = Type.Object(
{
id: NonEmptyString,
@@ -400,6 +420,7 @@ export const CronJobSchema = Type.Object(
{ additionalProperties: false },
);
/** Query params for listing cron jobs with filters and pagination. */
export const CronListParamsSchema = Type.Object(
{
includeDisabled: Type.Optional(Type.Boolean()),
@@ -416,10 +437,13 @@ export const CronListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for scheduler status. */
export const CronStatusParamsSchema = Type.Object({}, { additionalProperties: false });
/** Looks up a job by stable id or legacy jobId alias. */
export const CronGetParamsSchema = cronIdOrJobIdParams({});
/** Creates a scheduled job with schedule, target, payload, and delivery policy. */
export const CronAddParamsSchema = Type.Object(
{
name: NonEmptyString,
@@ -434,6 +458,7 @@ export const CronAddParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Mutable cron job fields accepted by update APIs. */
export const CronJobPatchSchema = Type.Object(
{
name: Type.Optional(NonEmptyString),
@@ -449,16 +474,20 @@ export const CronJobPatchSchema = Type.Object(
{ additionalProperties: false },
);
/** Updates a cron job by id or legacy jobId alias. */
export const CronUpdateParamsSchema = cronIdOrJobIdParams({
patch: CronJobPatchSchema,
});
/** Removes a cron job by id or legacy jobId alias. */
export const CronRemoveParamsSchema = cronIdOrJobIdParams({});
/** Runs a cron job immediately or only if due. */
export const CronRunParamsSchema = cronIdOrJobIdParams({
mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])),
});
/** Query params for cron run history. */
export const CronRunsParamsSchema = Type.Object(
{
scope: Type.Optional(Type.Union([Type.Literal("job"), Type.Literal("all")])),
@@ -479,6 +508,7 @@ export const CronRunsParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** One persisted cron run history entry. */
export const CronRunLogEntrySchema = Type.Object(
{
ts: Type.Integer({ minimum: 0 }),
@@ -1,23 +1,34 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Device pairing and token-management protocol schemas.
*
* These payloads cross the gateway approval boundary, so request ids and device
* ids stay explicit and feature handlers own the authorization checks.
*/
/** Lists pending and approved device pairing records. */
export const DevicePairListParamsSchema = Type.Object({}, { additionalProperties: false });
/** Approves a pending pairing request by request id. */
export const DevicePairApproveParamsSchema = Type.Object(
{ requestId: NonEmptyString },
{ additionalProperties: false },
);
/** Rejects a pending pairing request by request id. */
export const DevicePairRejectParamsSchema = Type.Object(
{ requestId: NonEmptyString },
{ additionalProperties: false },
);
/** Removes an approved or remembered device by device id. */
export const DevicePairRemoveParamsSchema = Type.Object(
{ deviceId: NonEmptyString },
{ additionalProperties: false },
);
/** Rotates or issues a device token for a specific role/scope grant. */
export const DeviceTokenRotateParamsSchema = Type.Object(
{
deviceId: NonEmptyString,
@@ -27,6 +38,7 @@ export const DeviceTokenRotateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Revokes one role-bound device token grant. */
export const DeviceTokenRevokeParamsSchema = Type.Object(
{
deviceId: NonEmptyString,
@@ -35,6 +47,7 @@ export const DeviceTokenRevokeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Event emitted when a client opens or refreshes a pairing request. */
export const DevicePairRequestedEventSchema = Type.Object(
{
requestId: NonEmptyString,
@@ -56,6 +69,7 @@ export const DevicePairRequestedEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Event emitted after a pairing request is approved, rejected, or otherwise resolved. */
export const DevicePairResolvedEventSchema = Type.Object(
{
requestId: NonEmptyString,
@@ -1,6 +1,13 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Environment inventory protocol schemas.
*
* Environments are runtime targets such as local hosts, VMs, or remote workers;
* this schema layer only describes their gateway-visible status summary.
*/
/** Runtime availability state for an environment target. */
export const EnvironmentStatusSchema = Type.String({
enum: ["available", "unavailable", "starting", "stopping", "error"],
});
@@ -18,10 +25,13 @@ function createEnvironmentSummarySchema() {
);
}
/** Public environment summary shown in listings and status responses. */
export const EnvironmentSummarySchema = createEnvironmentSummarySchema();
/** Empty request payload for listing known environments. */
export const EnvironmentsListParamsSchema = Type.Object({}, { additionalProperties: false });
/** List response containing all gateway-visible environment summaries. */
export const EnvironmentsListResultSchema = Type.Object(
{
environments: Type.Array(EnvironmentSummarySchema),
@@ -29,9 +39,11 @@ export const EnvironmentsListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Status lookup request for one environment id. */
export const EnvironmentsStatusParamsSchema = Type.Object(
{ environmentId: NonEmptyString },
{ additionalProperties: false },
);
/** Status lookup result for one environment id. */
export const EnvironmentsStatusResultSchema = createEnvironmentSummarySchema();
@@ -1,16 +1,25 @@
import type { ErrorShape } from "./types.js";
/** Gateway JSON-RPC style error codes shared by clients and server handlers. */
export const ErrorCodes = {
/** Client has not completed account/device linking for this gateway. */
NOT_LINKED: "NOT_LINKED",
/** Device exists but still needs an explicit pairing approval. */
NOT_PAIRED: "NOT_PAIRED",
/** Agent turn exceeded the gateway wait window. */
AGENT_TIMEOUT: "AGENT_TIMEOUT",
/** Request payload failed protocol validation or method preconditions. */
INVALID_REQUEST: "INVALID_REQUEST",
/** Approval resolution referenced a missing or expired approval request. */
APPROVAL_NOT_FOUND: "APPROVAL_NOT_FOUND",
/** Gateway service or required backend is temporarily unavailable. */
UNAVAILABLE: "UNAVAILABLE",
} as const;
/** Closed set of canonical gateway error code strings. */
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
/** Builds the canonical gateway error payload while preserving optional retry metadata. */
export function errorShape(
code: ErrorCode,
message: string,
@@ -1,6 +1,13 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Exec approval protocol schemas.
*
* These payloads cross the security-review boundary for command execution, so
* persisted policy, request snapshots, and resolve decisions stay explicit.
*/
/** One persisted allowlist entry for a command pattern or resolved executable. */
export const ExecApprovalsAllowlistEntrySchema = Type.Object(
{
id: Type.Optional(NonEmptyString),
@@ -22,10 +29,12 @@ const ExecApprovalsPolicyFields = {
autoAllowSkills: Type.Optional(Type.Boolean()),
};
/** Default exec approval policy shared by all agents unless overridden. */
export const ExecApprovalsDefaultsSchema = Type.Object(ExecApprovalsPolicyFields, {
additionalProperties: false,
});
/** Agent-specific exec approval policy and allowlist. */
export const ExecApprovalsAgentSchema = Type.Object(
{
...ExecApprovalsPolicyFields,
@@ -34,6 +43,7 @@ export const ExecApprovalsAgentSchema = Type.Object(
{ additionalProperties: false },
);
/** Versioned exec approvals config file edited through gateway APIs. */
export const ExecApprovalsFileSchema = Type.Object(
{
version: Type.Literal(1),
@@ -52,6 +62,7 @@ export const ExecApprovalsFileSchema = Type.Object(
{ additionalProperties: false },
);
/** Read snapshot with path/hash metadata for optimistic writes. */
export const ExecApprovalsSnapshotSchema = Type.Object(
{
path: NonEmptyString,
@@ -62,8 +73,10 @@ export const ExecApprovalsSnapshotSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for reading local exec approval policy. */
export const ExecApprovalsGetParamsSchema = Type.Object({}, { additionalProperties: false });
/** Local exec approval policy write request with optional base hash guard. */
export const ExecApprovalsSetParamsSchema = Type.Object(
{
file: ExecApprovalsFileSchema,
@@ -72,6 +85,7 @@ export const ExecApprovalsSetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Node-scoped request payload for reading exec approval policy. */
export const ExecApprovalsNodeGetParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -79,6 +93,7 @@ export const ExecApprovalsNodeGetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Node-scoped exec approval policy write request with optional base hash guard. */
export const ExecApprovalsNodeSetParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -88,6 +103,7 @@ export const ExecApprovalsNodeSetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Lookup request for one pending exec approval by id. */
export const ExecApprovalGetParamsSchema = Type.Object(
{
id: NonEmptyString,
@@ -95,6 +111,7 @@ export const ExecApprovalGetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Pending command execution approval request shown to reviewers. */
export const ExecApprovalRequestParamsSchema = Type.Object(
{
id: Type.Optional(NonEmptyString),
@@ -166,6 +183,7 @@ export const ExecApprovalRequestParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Reviewer decision payload for one pending exec approval. */
export const ExecApprovalResolveParamsSchema = Type.Object(
{
id: NonEmptyString,
@@ -2,6 +2,13 @@ import { Type } from "typebox";
import { GatewayClientIdSchema, GatewayClientModeSchema, NonEmptyString } from "./primitives.js";
import { SnapshotSchema, StateVersionSchema } from "./snapshot.js";
/**
* Top-level gateway frame schemas.
*
* These are the WebSocket envelope contracts; method/event payload schemas live
* in feature-specific modules and are referenced by runtime validators.
*/
/** Periodic server heartbeat event payload. */
export const TickEventSchema = Type.Object(
{
ts: Type.Integer({ minimum: 0 }),
@@ -9,6 +16,7 @@ export const TickEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Server shutdown notice event payload. */
export const ShutdownEventSchema = Type.Object(
{
reason: NonEmptyString,
@@ -17,6 +25,7 @@ export const ShutdownEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Initial client hello/connect payload sent before the gateway accepts frames. */
export const ConnectParamsSchema = Type.Object(
{
minProtocol: Type.Integer({ minimum: 1 }),
@@ -70,6 +79,7 @@ export const ConnectParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Successful gateway hello response with negotiated protocol and initial state. */
export const HelloOkSchema = Type.Object(
{
type: Type.Literal("hello-ok"),
@@ -124,6 +134,7 @@ export const HelloOkSchema = Type.Object(
{ additionalProperties: false },
);
/** Standard structured error shape used in response frames and connect failures. */
export const ErrorShapeSchema = Type.Object(
{
code: NonEmptyString,
@@ -135,6 +146,7 @@ export const ErrorShapeSchema = Type.Object(
{ additionalProperties: false },
);
/** Client request frame envelope; `method` selects the payload validator. */
export const RequestFrameSchema = Type.Object(
{
type: Type.Literal("req"),
@@ -145,6 +157,7 @@ export const RequestFrameSchema = Type.Object(
{ additionalProperties: false },
);
/** Server response frame envelope paired with a prior request id. */
export const ResponseFrameSchema = Type.Object(
{
type: Type.Literal("res"),
@@ -156,6 +169,7 @@ export const ResponseFrameSchema = Type.Object(
{ additionalProperties: false },
);
/** Server event frame envelope; `event` selects the payload validator. */
export const EventFrameSchema = Type.Object(
{
type: Type.Literal("event"),
@@ -2,6 +2,7 @@ import type { Static } from "typebox";
import { Type } from "typebox";
import { ChatSendSessionKeyString, InputProvenanceSchema, NonEmptyString } from "./primitives.js";
/** Cursor-based request for the gateway log tail endpoint. */
export const LogsTailParamsSchema = Type.Object(
{
cursor: Type.Optional(Type.Integer({ minimum: 0 })),
@@ -11,6 +12,7 @@ export const LogsTailParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Gateway log tail payload returned to dashboard clients. */
export const LogsTailResultSchema = Type.Object(
{
file: NonEmptyString,
@@ -23,7 +25,7 @@ export const LogsTailResultSchema = Type.Object(
{ additionalProperties: false },
);
// WebChat/WebSocket-native chat methods
/** Session-scoped history request used by WebChat and native WebSocket clients. */
export const ChatHistoryParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
@@ -34,6 +36,7 @@ export const ChatHistoryParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Lightweight chat metadata request; optional agent scope keeps selector state explicit. */
export const ChatMetadataParamsSchema = Type.Object(
{
agentId: Type.Optional(NonEmptyString),
@@ -41,6 +44,7 @@ export const ChatMetadataParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Fetches one stored chat message without forcing history callers to request huge payloads. */
export const ChatMessageGetParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
@@ -51,6 +55,7 @@ export const ChatMessageGetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result envelope for single-message lookup, including the stable miss/visibility reason. */
export const ChatMessageGetResultSchema = Type.Object(
{
ok: Type.Boolean(),
@@ -65,8 +70,10 @@ export const ChatMessageGetResultSchema = Type.Object(
},
{ additionalProperties: false },
);
/** Typed result shape for callers that branch on message availability. */
export type ChatMessageGetResult = Static<typeof ChatMessageGetResultSchema>;
/** User-to-agent send request; idempotency key lets clients safely retry transport failures. */
export const ChatSendParamsSchema = Type.Object(
{
sessionKey: ChatSendSessionKeyString,
@@ -89,6 +96,7 @@ export const ChatSendParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Cancels the active or named run for a chat session. */
export const ChatAbortParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
@@ -98,6 +106,7 @@ export const ChatAbortParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Inserts an operator-visible synthetic message into an existing chat transcript. */
export const ChatInjectParamsSchema = Type.Object(
{
sessionKey: NonEmptyString,
@@ -108,6 +117,7 @@ export const ChatInjectParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Shared event fields preserve stream ordering and route events to the right session. */
const ChatEventBaseSchema = {
runId: NonEmptyString,
sessionKey: NonEmptyString,
@@ -116,6 +126,7 @@ const ChatEventBaseSchema = {
seq: Type.Integer({ minimum: 0 }),
};
/** Stable error categories exposed over the chat stream. */
const ChatEventErrorKindSchema = Type.Union([
Type.Literal("refusal"),
Type.Literal("timeout"),
@@ -124,6 +135,7 @@ const ChatEventErrorKindSchema = Type.Union([
Type.Literal("unknown"),
]);
/** Incremental assistant output event; `replace` marks full-content refresh deltas. */
export const ChatDeltaEventSchema = Type.Object(
{
...ChatEventBaseSchema,
@@ -136,6 +148,7 @@ export const ChatDeltaEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Successful terminal event for a completed chat run. */
export const ChatFinalEventSchema = Type.Object(
{
...ChatEventBaseSchema,
@@ -147,6 +160,7 @@ export const ChatFinalEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Terminal event for user-initiated or coordinator-initiated cancellation. */
export const ChatAbortedEventSchema = Type.Object(
{
...ChatEventBaseSchema,
@@ -157,6 +171,7 @@ export const ChatAbortedEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Terminal event for failed chat runs with an optional normalized failure kind. */
export const ChatErrorEventSchema = Type.Object(
{
...ChatEventBaseSchema,
@@ -170,6 +185,7 @@ export const ChatErrorEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Public chat stream event union consumed by gateway protocol validators. */
export const ChatEventSchema = Type.Union([
ChatDeltaEventSchema,
ChatFinalEventSchema,
@@ -1,14 +1,17 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/** Pending node work classes that the gateway may queue for paired devices. */
const NodePendingWorkTypeSchema = Type.String({
enum: ["status.request", "location.request"],
});
/** Queue priority accepted when operators enqueue node work. */
const NodePendingWorkPrioritySchema = Type.String({
enum: ["normal", "high"],
});
/** Reasons a node can report itself alive without implying an operator action. */
export const NodePresenceAliveReasonSchema = Type.String({
enum: [
"background",
@@ -20,6 +23,7 @@ export const NodePresenceAliveReasonSchema = Type.String({
],
});
/** Presence heartbeat payload sent by remote nodes to refresh gateway state. */
export const NodePresenceAlivePayloadSchema = Type.Object(
{
trigger: NodePresenceAliveReasonSchema,
@@ -34,6 +38,7 @@ export const NodePresenceAlivePayloadSchema = Type.Object(
{ additionalProperties: false },
);
/** Normalized result for node-originated events after gateway dispatch. */
export const NodeEventResultSchema = Type.Object(
{
ok: Type.Boolean(),
@@ -44,6 +49,7 @@ export const NodeEventResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Pairing request metadata advertised by a node before trust is granted. */
export const NodePairRequestParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -63,35 +69,43 @@ export const NodePairRequestParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Lists pending node-pairing requests. */
export const NodePairListParamsSchema = Type.Object({}, { additionalProperties: false });
/** Approves a pending node-pairing request by request id. */
export const NodePairApproveParamsSchema = Type.Object(
{ requestId: NonEmptyString },
{ additionalProperties: false },
);
/** Rejects a pending node-pairing request by request id. */
export const NodePairRejectParamsSchema = Type.Object(
{ requestId: NonEmptyString },
{ additionalProperties: false },
);
/** Removes an already paired node from the gateway trust set. */
export const NodePairRemoveParamsSchema = Type.Object(
{ nodeId: NonEmptyString },
{ additionalProperties: false },
);
/** Verifies node ownership with a short-lived pairing token. */
export const NodePairVerifyParamsSchema = Type.Object(
{ nodeId: NonEmptyString, token: NonEmptyString },
{ additionalProperties: false },
);
/** Renames a paired node while preserving its stable node id. */
export const NodeRenameParamsSchema = Type.Object(
{ nodeId: NonEmptyString, displayName: NonEmptyString },
{ additionalProperties: false },
);
/** Lists paired nodes known to the gateway. */
export const NodeListParamsSchema = Type.Object({}, { additionalProperties: false });
/** Acknowledges queued node work that the node has consumed. */
export const NodePendingAckParamsSchema = Type.Object(
{
ids: Type.Array(NonEmptyString, { minItems: 1 }),
@@ -99,11 +113,13 @@ export const NodePendingAckParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Requests detailed metadata for one paired node. */
export const NodeDescribeParamsSchema = Type.Object(
{ nodeId: NonEmptyString },
{ additionalProperties: false },
);
/** Invokes a command on a paired node; idempotency allows safe retries. */
export const NodeInvokeParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -115,6 +131,7 @@ export const NodeInvokeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result callback payload for a node command invocation. */
export const NodeInvokeResultParamsSchema = Type.Object(
{
id: NonEmptyString,
@@ -135,6 +152,7 @@ export const NodeInvokeResultParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Generic node event envelope accepted by the gateway. */
export const NodeEventParamsSchema = Type.Object(
{
event: NonEmptyString,
@@ -144,6 +162,7 @@ export const NodeEventParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Request for a bounded batch of queued work assigned to the calling node. */
export const NodePendingDrainParamsSchema = Type.Object(
{
maxItems: Type.Optional(Type.Integer({ minimum: 1, maximum: 10 })),
@@ -151,6 +170,7 @@ export const NodePendingDrainParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** One queued node-work item returned by pending-work drain calls. */
export const NodePendingDrainItemSchema = Type.Object(
{
id: NonEmptyString,
@@ -163,6 +183,7 @@ export const NodePendingDrainItemSchema = Type.Object(
{ additionalProperties: false },
);
/** Drain response with a revision marker for node queue state. */
export const NodePendingDrainResultSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -173,6 +194,7 @@ export const NodePendingDrainResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Enqueues gateway-initiated work for a paired node. */
export const NodePendingEnqueueParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -184,6 +206,7 @@ export const NodePendingEnqueueParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Enqueue result echoes queue revision and whether wake delivery was attempted. */
export const NodePendingEnqueueResultSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -194,6 +217,7 @@ export const NodePendingEnqueueResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Event payload used by the gateway to ask a node to run a command. */
export const NodeInvokeRequestEventSchema = Type.Object(
{
id: NonEmptyString,
@@ -1,10 +1,18 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Plugin approval schemas.
*
* These payloads cross from plugin/tool execution into reviewer-facing UI, so
* title, description, decision set, and timeout limits are part of the public
* gateway contract.
*/
const MAX_PLUGIN_APPROVAL_TIMEOUT_MS = 600_000;
const PLUGIN_APPROVAL_TITLE_MAX_LENGTH = 80;
const PLUGIN_APPROVAL_DESCRIPTION_MAX_LENGTH = 256;
/** Approval request raised by a plugin before a sensitive tool action proceeds. */
export const PluginApprovalRequestParamsSchema = Type.Object(
{
pluginId: Type.Optional(NonEmptyString),
@@ -31,6 +39,7 @@ export const PluginApprovalRequestParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Reviewer decision payload resolving one pending plugin approval request. */
export const PluginApprovalResolveParamsSchema = Type.Object(
{
id: NonEmptyString,
@@ -1,8 +1,16 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Plugin control-surface protocol schemas.
*
* These payloads let the gateway expose plugin-provided UI actions without
* baking plugin-specific payload shapes into the core protocol.
*/
/** Arbitrary plugin-owned JSON payload carried opaquely through the gateway. */
export const PluginJsonValueSchema = Type.Unknown();
/** Descriptor for one plugin-provided control UI action or surface. */
export const PluginControlUiDescriptorSchema = Type.Object(
{
id: NonEmptyString,
@@ -23,8 +31,10 @@ export const PluginControlUiDescriptorSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for listing plugin UI descriptors. */
export const PluginsUiDescriptorsParamsSchema = Type.Object({}, { additionalProperties: false });
/** Response payload containing all plugin UI descriptors visible to the client. */
export const PluginsUiDescriptorsResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -33,6 +43,7 @@ export const PluginsUiDescriptorsResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Request payload for invoking one plugin-owned session action. */
export const PluginsSessionActionParamsSchema = Type.Object(
{
pluginId: NonEmptyString,
@@ -43,6 +54,7 @@ export const PluginsSessionActionParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Successful plugin action result, optionally continuing the agent turn. */
export const PluginsSessionActionSuccessResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -53,6 +65,7 @@ export const PluginsSessionActionSuccessResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Failed plugin action result with plugin-owned detail payload. */
export const PluginsSessionActionFailureResultSchema = Type.Object(
{
ok: Type.Literal(false),
@@ -63,6 +76,7 @@ export const PluginsSessionActionFailureResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Discriminated plugin action result returned to gateway clients. */
export const PluginsSessionActionResultSchema = Type.Union([
PluginsSessionActionSuccessResultSchema,
PluginsSessionActionFailureResultSchema,
@@ -8,20 +8,31 @@ import {
SINGLE_VALUE_FILE_REF_ID,
} from "../secret-ref-contract.js";
/**
* Shared schema primitives reused by gateway protocol request/result schemas.
*
* Keep these schemas small and transport-oriented; feature-specific validation
* belongs in the owning schema module or runtime handler.
*/
const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
const INPUT_PROVENANCE_KIND_VALUES = ["external_user", "inter_session", "internal_system"] as const;
const SESSION_LABEL_MAX_LENGTH = 512;
/** Non-empty string primitive for protocol fields that reject blank values. */
export const NonEmptyString = Type.String({ minLength: 1 });
/** Maximum stable session key length accepted by chat-send protocol requests. */
export const CHAT_SEND_SESSION_KEY_MAX_LENGTH = 512;
/** Chat-send session key string primitive with bounded length. */
export const ChatSendSessionKeyString = Type.String({
minLength: 1,
maxLength: CHAT_SEND_SESSION_KEY_MAX_LENGTH,
});
/** Human-readable session label primitive with bounded display length. */
export const SessionLabelString = Type.String({
minLength: 1,
maxLength: SESSION_LABEL_MAX_LENGTH,
});
/** Provenance marker for content copied from another user/session/system source. */
export const InputProvenanceSchema = Type.Object(
{
kind: Type.String({ enum: [...INPUT_PROVENANCE_KIND_VALUES] }),
@@ -33,10 +44,13 @@ export const InputProvenanceSchema = Type.Object(
{ additionalProperties: false },
);
/** Closed gateway client id schema aligned with `GATEWAY_CLIENT_IDS`. */
export const GatewayClientIdSchema = Type.Enum(GATEWAY_CLIENT_IDS);
/** Closed gateway client mode schema aligned with `GATEWAY_CLIENT_MODES`. */
export const GatewayClientModeSchema = Type.Enum(GATEWAY_CLIENT_MODES);
/** Supported secret reference backing stores for protocol SecretRef payloads. */
export const SecretRefSourceSchema = Type.Union([
Type.Literal("env"),
Type.Literal("file"),
@@ -87,10 +101,12 @@ const ExecSecretRefSchema = Type.Object(
{ additionalProperties: false },
);
/** Structured secret reference accepted by config and channel protocol payloads. */
export const SecretRefSchema = Type.Union([
EnvSecretRefSchema,
FileSecretRefSchema,
ExecSecretRefSchema,
]);
/** Secret input value: either an inline string or a structured SecretRef. */
export const SecretInputSchema = Type.Union([Type.String(), SecretRefSchema]);
@@ -1,3 +1,10 @@
/**
* Central registry for every gateway protocol schema.
*
* The keys in this object are the public schema names used by validators,
* generated static types, and protocol tooling. Add new entries here only after
* the owning schema module exports the canonical TypeBox schema.
*/
import type { TSchema } from "typebox";
import {
AgentEventSchema,
@@ -292,7 +299,9 @@ import {
WizardStepSchema,
} from "./wizard.js";
/** Public schema registry keyed by stable protocol schema name. */
export const ProtocolSchemas = {
// Handshake, transport frames, state snapshots, and shared error envelopes.
ConnectParams: ConnectParamsSchema,
HelloOk: HelloOkSchema,
RequestFrame: RequestFrameSchema,
@@ -303,6 +312,8 @@ export const ProtocolSchemas = {
StateVersion: StateVersionSchema,
Snapshot: SnapshotSchema,
ErrorShape: ErrorShapeSchema,
// Environment and agent-facing control RPC payloads.
EnvironmentStatus: EnvironmentStatusSchema,
EnvironmentSummary: EnvironmentSummarySchema,
EnvironmentsListParams: EnvironmentsListParamsSchema,
@@ -318,6 +329,8 @@ export const ProtocolSchemas = {
AgentIdentityResult: AgentIdentityResultSchema,
AgentWaitParams: AgentWaitParamsSchema,
WakeParams: WakeParamsSchema,
// Node pairing, invocation, presence, and pending-queue payloads.
NodePairRequestParams: NodePairRequestParamsSchema,
NodePairListParams: NodePairListParamsSchema,
NodePairApproveParams: NodePairApproveParamsSchema,
@@ -339,12 +352,16 @@ export const ProtocolSchemas = {
NodePendingEnqueueParams: NodePendingEnqueueParamsSchema,
NodePendingEnqueueResult: NodePendingEnqueueResultSchema,
NodeInvokeRequestEvent: NodeInvokeRequestEventSchema,
// Push and secret-resolution payloads used by mobile/control integrations.
PushTestParams: PushTestParamsSchema,
PushTestResult: PushTestResultSchema,
SecretsReloadParams: SecretsReloadParamsSchema,
SecretsResolveParams: SecretsResolveParamsSchema,
SecretsResolveAssignment: SecretsResolveAssignmentSchema,
SecretsResolveResult: SecretsResolveResultSchema,
// Session lifecycle, message routing, compaction, and usage accounting.
SessionsListParams: SessionsListParamsSchema,
SessionsCleanupParams: SessionsCleanupParamsSchema,
SessionsPreviewParams: SessionsPreviewParamsSchema,
@@ -372,6 +389,8 @@ export const ProtocolSchemas = {
SessionsDeleteParams: SessionsDeleteParamsSchema,
SessionsCompactParams: SessionsCompactParamsSchema,
SessionsUsageParams: SessionsUsageParamsSchema,
// Task ledger and config/wizard setup payloads.
TaskSummary: TaskSummarySchema,
TasksListParams: TasksListParamsSchema,
TasksListResult: TasksListResultSchema,
@@ -395,6 +414,8 @@ export const ProtocolSchemas = {
WizardNextResult: WizardNextResultSchema,
WizardStartResult: WizardStartResultSchema,
WizardStatusResult: WizardStatusResultSchema,
// Realtime Talk client/session events and channel control payloads.
TalkModeParams: TalkModeParamsSchema,
TalkEvent: TalkEventSchema,
TalkCatalogParams: TalkCatalogParamsSchema,
@@ -429,6 +450,8 @@ export const ProtocolSchemas = {
ChannelsLogoutParams: ChannelsLogoutParamsSchema,
WebLoginStartParams: WebLoginStartParamsSchema,
WebLoginWaitParams: WebLoginWaitParamsSchema,
// Agent files, artifacts, model catalogs, commands, tools, and skill workshop.
AgentSummary: AgentSummarySchema,
AgentsCreateParams: AgentsCreateParamsSchema,
AgentsCreateResult: AgentsCreateResultSchema,
@@ -497,6 +520,8 @@ export const ProtocolSchemas = {
SkillsUploadCommitParams: SkillsUploadCommitParamsSchema,
SkillsInstallParams: SkillsInstallParamsSchema,
SkillsUpdateParams: SkillsUpdateParamsSchema,
// Scheduler, logs, approval, plugin control, device, chat, and lifecycle events.
CronJob: CronJobSchema,
CronListParams: CronListParamsSchema,
CronStatusParams: CronStatusParamsSchema,
@@ -1,8 +1,15 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Push-notification protocol schemas.
*
* APNS test schemas exercise native push routing; Web Push schemas describe the
* browser subscription lifecycle exposed by the gateway.
*/
const ApnsEnvironmentSchema = Type.String({ enum: ["sandbox", "production"] });
/** Request payload for sending a test APNS notification to one node. */
export const PushTestParamsSchema = Type.Object(
{
nodeId: NonEmptyString,
@@ -13,6 +20,7 @@ export const PushTestParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result payload from an APNS push test, including provider status and transport. */
export const PushTestResultSchema = Type.Object(
{
ok: Type.Boolean(),
@@ -37,8 +45,10 @@ const WebPushKeysSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request payload for fetching the Web Push VAPID public key. */
export const WebPushVapidPublicKeyParamsSchema = Type.Object({}, { additionalProperties: false });
/** Browser Web Push subscription payload registered with the gateway. */
export const WebPushSubscribeParamsSchema = Type.Object(
{
endpoint: Type.String({ minLength: 1, maxLength: 2048, pattern: "^https://" }),
@@ -47,6 +57,7 @@ export const WebPushSubscribeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Browser Web Push endpoint removal payload. */
export const WebPushUnsubscribeParamsSchema = Type.Object(
{
endpoint: Type.String({ minLength: 1, maxLength: 2048, pattern: "^https://" }),
@@ -54,6 +65,7 @@ export const WebPushUnsubscribeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Request payload for sending a test Web Push notification to current subscriptions. */
export const WebPushTestParamsSchema = Type.Object(
{
title: Type.Optional(Type.String()),
@@ -62,14 +74,18 @@ export const WebPushTestParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Empty request type for fetching the Web Push VAPID public key. */
export type WebPushVapidPublicKeyParams = Record<string, never>;
/** Browser PushSubscription subset persisted by the gateway. */
export type WebPushSubscribeParams = {
endpoint: string;
keys: { p256dh: string; auth: string };
};
/** Browser PushSubscription endpoint removal request. */
export type WebPushUnsubscribeParams = {
endpoint: string;
};
/** Optional title/body overrides for a Web Push test notification. */
export type WebPushTestParams = {
title?: string;
body?: string;
@@ -1,8 +1,16 @@
import { Type, type Static } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Secret-provider protocol schemas.
*
* These payloads request secret materialization from the gateway while keeping
* caller scope, allowed paths, and provider overrides explicit.
*/
/** Empty request payload for reloading configured secret providers. */
export const SecretsReloadParamsSchema = Type.Object({}, { additionalProperties: false });
/** Request payload for resolving the secrets needed by one command invocation. */
export const SecretsResolveParamsSchema = Type.Object(
{
commandName: NonEmptyString,
@@ -23,8 +31,10 @@ export const SecretsResolveParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Static type for secret resolution requests. */
export type SecretsResolveParams = Static<typeof SecretsResolveParamsSchema>;
/** One resolved secret assignment path plus its provider-owned value. */
export const SecretsResolveAssignmentSchema = Type.Object(
{
path: Type.Optional(NonEmptyString),
@@ -34,6 +44,7 @@ export const SecretsResolveAssignmentSchema = Type.Object(
{ additionalProperties: false },
);
/** Secret resolution response with assignments and safe diagnostics. */
export const SecretsResolveResultSchema = Type.Object(
{
ok: Type.Optional(Type.Boolean()),
@@ -44,4 +55,5 @@ export const SecretsResolveResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Static type for secret resolution responses. */
export type SecretsResolveResult = Static<typeof SecretsResolveResultSchema>;
@@ -2,6 +2,15 @@ import { Type } from "typebox";
import { PluginJsonValueSchema } from "./plugins.js";
import { NonEmptyString, SessionLabelString } from "./primitives.js";
/**
* Session protocol schemas.
*
* These requests and results cover transcript discovery, lifecycle control,
* compaction checkpoints, per-session plugin state, and usage reporting. The
* schemas are shared by dashboard, CLI, ACP, and gateway RPC callers.
*/
/** Reason a compaction checkpoint was created. */
export const SessionCompactionCheckpointReasonSchema = Type.Union([
Type.Literal("manual"),
Type.Literal("auto-threshold"),
@@ -9,6 +18,7 @@ export const SessionCompactionCheckpointReasonSchema = Type.Union([
Type.Literal("timeout-retry"),
]);
/** Start/end event emitted while a session compaction operation runs. */
export const SessionOperationEventSchema = Type.Object(
{
operationId: NonEmptyString,
@@ -23,6 +33,7 @@ export const SessionOperationEventSchema = Type.Object(
{ additionalProperties: false },
);
/** Reference to the transcript location before or after compaction. */
export const SessionCompactionTranscriptReferenceSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -33,6 +44,7 @@ export const SessionCompactionTranscriptReferenceSchema = Type.Object(
{ additionalProperties: false },
);
/** Stored compaction checkpoint metadata for branching or restoring a session. */
export const SessionCompactionCheckpointSchema = Type.Object(
{
checkpointId: NonEmptyString,
@@ -50,6 +62,7 @@ export const SessionCompactionCheckpointSchema = Type.Object(
{ additionalProperties: false },
);
/** Lists sessions with optional scope, activity, label, and preview filters. */
export const SessionsListParamsSchema = Type.Object(
{
/**
@@ -84,6 +97,7 @@ export const SessionsListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Repairs or removes invalid session records from the selected agent scope. */
export const SessionsCleanupParamsSchema = Type.Object(
{
agent: Type.Optional(NonEmptyString),
@@ -96,6 +110,7 @@ export const SessionsCleanupParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads short previews for selected session keys. */
export const SessionsPreviewParamsSchema = Type.Object(
{
keys: Type.Array(NonEmptyString, { minItems: 1 }),
@@ -105,6 +120,7 @@ export const SessionsPreviewParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Describes one session and optional derived title/last-message previews. */
export const SessionsDescribeParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -114,6 +130,7 @@ export const SessionsDescribeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Resolves a session by key, raw session id, label, or parent/agent scope. */
export const SessionsResolveParamsSchema = Type.Object(
{
key: Type.Optional(NonEmptyString),
@@ -127,6 +144,7 @@ export const SessionsResolveParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Creates or adopts a session with optional model, label, and parent linkage. */
export const SessionsCreateParamsSchema = Type.Object(
{
key: Type.Optional(NonEmptyString),
@@ -141,6 +159,7 @@ export const SessionsCreateParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Sends one message into an existing session. */
export const SessionsSendParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -154,6 +173,7 @@ export const SessionsSendParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Subscribes a client to live message updates for one session. */
export const SessionsMessagesSubscribeParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -162,6 +182,7 @@ export const SessionsMessagesSubscribeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Removes a live message subscription for one session. */
export const SessionsMessagesUnsubscribeParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -170,6 +191,7 @@ export const SessionsMessagesUnsubscribeParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Aborts the active or named run for a session. */
export const SessionsAbortParamsSchema = Type.Object(
{
key: Type.Optional(NonEmptyString),
@@ -179,6 +201,7 @@ export const SessionsAbortParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Mutable per-session preferences and routing metadata. */
export const SessionsPatchParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -227,6 +250,7 @@ export const SessionsPatchParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Updates or clears one plugin namespace value on a session record. */
export const SessionsPluginPatchParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -238,6 +262,7 @@ export const SessionsPluginPatchParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Result returned after patching session plugin state. */
export const SessionsPluginPatchResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -247,6 +272,7 @@ export const SessionsPluginPatchResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Resets a session to a new or reset transcript state. */
export const SessionsResetParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -256,6 +282,7 @@ export const SessionsResetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Deletes a session record and optionally its transcript. */
export const SessionsDeleteParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -267,6 +294,7 @@ export const SessionsDeleteParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Requests manual compaction for a session transcript. */
export const SessionsCompactParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -276,6 +304,7 @@ export const SessionsCompactParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Lists compaction checkpoints for one session. */
export const SessionsCompactionListParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -284,6 +313,7 @@ export const SessionsCompactionListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Reads one compaction checkpoint by id. */
export const SessionsCompactionGetParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -293,6 +323,7 @@ export const SessionsCompactionGetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Creates a new branch from a compaction checkpoint. */
export const SessionsCompactionBranchParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -302,6 +333,7 @@ export const SessionsCompactionBranchParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Restores an existing session to a compaction checkpoint. */
export const SessionsCompactionRestoreParamsSchema = Type.Object(
{
key: NonEmptyString,
@@ -311,6 +343,7 @@ export const SessionsCompactionRestoreParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** List response for session compaction checkpoints. */
export const SessionsCompactionListResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -320,6 +353,7 @@ export const SessionsCompactionListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Get response for a single compaction checkpoint. */
export const SessionsCompactionGetResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -329,6 +363,7 @@ export const SessionsCompactionGetResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Branch response with the newly created session key and entry metadata. */
export const SessionsCompactionBranchResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -347,6 +382,7 @@ export const SessionsCompactionBranchResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Restore response with updated session entry metadata. */
export const SessionsCompactionRestoreResultSchema = Type.Object(
{
ok: Type.Literal(true),
@@ -364,6 +400,7 @@ export const SessionsCompactionRestoreResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Usage report query across one session, one agent, or all agent sessions. */
export const SessionsUsageParamsSchema = Type.Object(
{
/** Specific session key to analyze; if omitted returns sessions for the effective agent. */
@@ -1,6 +1,13 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Gateway state snapshot schemas.
*
* Snapshots are sent during hello and later event streams; they summarize node
* presence, health, session defaults, and version counters for clients.
*/
/** One gateway-visible presence record for a node/client/runtime. */
export const PresenceEntrySchema = Type.Object(
{
host: Type.Optional(NonEmptyString),
@@ -23,8 +30,10 @@ export const PresenceEntrySchema = Type.Object(
{ additionalProperties: false },
);
/** Health snapshot is intentionally opaque because providers contribute nested shapes. */
export const HealthSnapshotSchema = Type.Any();
/** Default session routing keys included in initial gateway snapshots. */
export const SessionDefaultsSchema = Type.Object(
{
defaultAgentId: NonEmptyString,
@@ -35,6 +44,7 @@ export const SessionDefaultsSchema = Type.Object(
{ additionalProperties: false },
);
/** Monotonic version counters for snapshot subtrees. */
export const StateVersionSchema = Type.Object(
{
presence: Type.Integer({ minimum: 0 }),
@@ -43,6 +53,7 @@ export const StateVersionSchema = Type.Object(
{ additionalProperties: false },
);
/** Initial and incremental gateway state snapshot payload. */
export const SnapshotSchema = Type.Object(
{
presence: Type.Array(PresenceEntrySchema),
@@ -1,6 +1,13 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/**
* Task ledger protocol schemas.
*
* Tasks represent long-running SDK/agent operations exposed through the gateway;
* these schemas keep list/get/cancel payloads bounded and status values closed.
*/
/** Closed task lifecycle statuses visible in the gateway task ledger. */
export const TaskLedgerStatusSchema = Type.Union([
Type.Literal("queued"),
Type.Literal("running"),
@@ -12,6 +19,7 @@ export const TaskLedgerStatusSchema = Type.Union([
const TimestampSchema = Type.Union([Type.String(), Type.Integer({ minimum: 0 })]);
/** Public task summary returned by task list/get/cancel responses. */
export const TaskSummarySchema = Type.Object(
{
id: NonEmptyString,
@@ -39,6 +47,7 @@ export const TaskSummarySchema = Type.Object(
{ additionalProperties: false },
);
/** Task list filters with bounded pagination. */
export const TasksListParamsSchema = Type.Object(
{
status: Type.Optional(Type.Union([TaskLedgerStatusSchema, Type.Array(TaskLedgerStatusSchema)])),
@@ -50,6 +59,7 @@ export const TasksListParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Task list page response. */
export const TasksListResultSchema = Type.Object(
{
tasks: Type.Array(TaskSummarySchema),
@@ -58,6 +68,7 @@ export const TasksListResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Lookup request for one task id. */
export const TasksGetParamsSchema = Type.Object(
{
taskId: NonEmptyString,
@@ -65,6 +76,7 @@ export const TasksGetParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Lookup result for one task summary. */
export const TasksGetResultSchema = Type.Object(
{
task: TaskSummarySchema,
@@ -72,6 +84,7 @@ export const TasksGetResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Cancel request for one task id with optional operator reason. */
export const TasksCancelParamsSchema = Type.Object(
{
taskId: NonEmptyString,
@@ -80,6 +93,7 @@ export const TasksCancelParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Cancel result, including the task snapshot when it was found. */
export const TasksCancelResultSchema = Type.Object(
{
found: Type.Boolean(),
@@ -1,9 +1,18 @@
/**
* Static TypeScript types derived from the canonical gateway protocol schemas.
*
* Keep aliases wired through `ProtocolSchemas` so validators, runtime schemas,
* and exported compile-time types cannot drift apart.
*/
import type { Static } from "typebox";
import { ProtocolSchemas } from "./protocol-schemas.js";
/** Stable schema names registered in the protocol schema registry. */
type ProtocolSchemaName = keyof typeof ProtocolSchemas;
/** Inferred TypeScript type for a named TypeBox protocol schema. */
type SchemaType<TName extends ProtocolSchemaName> = Static<(typeof ProtocolSchemas)[TName]>;
/** Connection handshake, envelope, snapshot, and shared error wire types. */
export type ConnectParams = SchemaType<"ConnectParams">;
export type HelloOk = SchemaType<"HelloOk">;
export type RequestFrame = SchemaType<"RequestFrame">;
@@ -14,12 +23,16 @@ export type Snapshot = SchemaType<"Snapshot">;
export type PresenceEntry = SchemaType<"PresenceEntry">;
export type ErrorShape = SchemaType<"ErrorShape">;
export type StateVersion = SchemaType<"StateVersion">;
/** Environment status RPC payloads used by CLI and Control UI surfaces. */
export type EnvironmentStatus = SchemaType<"EnvironmentStatus">;
export type EnvironmentSummary = SchemaType<"EnvironmentSummary">;
export type EnvironmentsListParams = SchemaType<"EnvironmentsListParams">;
export type EnvironmentsListResult = SchemaType<"EnvironmentsListResult">;
export type EnvironmentsStatusParams = SchemaType<"EnvironmentsStatusParams">;
export type EnvironmentsStatusResult = SchemaType<"EnvironmentsStatusResult">;
/** Agent activity, identity, send, poll, wait, and wake protocol payloads. */
export type AgentEvent = SchemaType<"AgentEvent">;
export type AgentIdentityParams = SchemaType<"AgentIdentityParams">;
export type AgentIdentityResult = SchemaType<"AgentIdentityResult">;
@@ -27,6 +40,8 @@ export type MessageActionParams = SchemaType<"MessageActionParams">;
export type PollParams = SchemaType<"PollParams">;
export type AgentWaitParams = SchemaType<"AgentWaitParams">;
export type WakeParams = SchemaType<"WakeParams">;
/** Node pairing, presence, invoke, and pending-queue protocol payloads. */
export type NodePairRequestParams = SchemaType<"NodePairRequestParams">;
export type NodePairListParams = SchemaType<"NodePairListParams">;
export type NodePairApproveParams = SchemaType<"NodePairApproveParams">;
@@ -47,8 +62,12 @@ export type NodePendingDrainParams = SchemaType<"NodePendingDrainParams">;
export type NodePendingDrainResult = SchemaType<"NodePendingDrainResult">;
export type NodePendingEnqueueParams = SchemaType<"NodePendingEnqueueParams">;
export type NodePendingEnqueueResult = SchemaType<"NodePendingEnqueueResult">;
/** Push notification test result contracts exposed through gateway RPC. */
export type PushTestParams = SchemaType<"PushTestParams">;
export type PushTestResult = SchemaType<"PushTestResult">;
/** Session lifecycle, message routing, compaction, patch, and usage payloads. */
export type SessionsListParams = SchemaType<"SessionsListParams">;
export type SessionsCleanupParams = SchemaType<"SessionsCleanupParams">;
export type SessionsPreviewParams = SchemaType<"SessionsPreviewParams">;
@@ -76,6 +95,8 @@ export type SessionsResetParams = SchemaType<"SessionsResetParams">;
export type SessionsDeleteParams = SchemaType<"SessionsDeleteParams">;
export type SessionsCompactParams = SchemaType<"SessionsCompactParams">;
export type SessionsUsageParams = SchemaType<"SessionsUsageParams">;
/** Task ledger query and cancellation payloads. */
export type TaskSummary = SchemaType<"TaskSummary">;
export type TasksListParams = SchemaType<"TasksListParams">;
export type TasksListResult = SchemaType<"TasksListResult">;
@@ -83,6 +104,8 @@ export type TasksGetParams = SchemaType<"TasksGetParams">;
export type TasksGetResult = SchemaType<"TasksGetResult">;
export type TasksCancelParams = SchemaType<"TasksCancelParams">;
export type TasksCancelResult = SchemaType<"TasksCancelResult">;
/** Config read/write/schema payloads plus update status and run controls. */
export type ConfigGetParams = SchemaType<"ConfigGetParams">;
export type ConfigSetParams = SchemaType<"ConfigSetParams">;
export type ConfigApplyParams = SchemaType<"ConfigApplyParams">;
@@ -92,6 +115,8 @@ export type ConfigSchemaLookupParams = SchemaType<"ConfigSchemaLookupParams">;
export type ConfigSchemaResponse = SchemaType<"ConfigSchemaResponse">;
export type ConfigSchemaLookupResult = SchemaType<"ConfigSchemaLookupResult">;
export type UpdateStatusParams = SchemaType<"UpdateStatusParams">;
/** Wizard setup flow payloads exchanged by CLI, UI, and gateway. */
export type WizardStartParams = SchemaType<"WizardStartParams">;
export type WizardNextParams = SchemaType<"WizardNextParams">;
export type WizardCancelParams = SchemaType<"WizardCancelParams">;
@@ -100,6 +125,8 @@ export type WizardStep = SchemaType<"WizardStep">;
export type WizardNextResult = SchemaType<"WizardNextResult">;
export type WizardStartResult = SchemaType<"WizardStartResult">;
export type WizardStatusResult = SchemaType<"WizardStatusResult">;
/** Realtime Talk client/session/event payloads. */
export type TalkEvent = SchemaType<"TalkEvent">;
export type TalkModeParams = SchemaType<"TalkModeParams">;
export type TalkCatalogParams = SchemaType<"TalkCatalogParams">;
@@ -127,6 +154,8 @@ export type TalkSessionCloseParams = SchemaType<"TalkSessionCloseParams">;
export type TalkSessionOkResult = SchemaType<"TalkSessionOkResult">;
export type TalkSpeakParams = SchemaType<"TalkSpeakParams">;
export type TalkSpeakResult = SchemaType<"TalkSpeakResult">;
/** Channel control and web-login payloads. */
export type ChannelsStatusParams = SchemaType<"ChannelsStatusParams">;
export type ChannelsStatusResult = SchemaType<"ChannelsStatusResult">;
export type ChannelsStartParams = SchemaType<"ChannelsStartParams">;
@@ -134,6 +163,8 @@ export type ChannelsStopParams = SchemaType<"ChannelsStopParams">;
export type ChannelsLogoutParams = SchemaType<"ChannelsLogoutParams">;
export type WebLoginStartParams = SchemaType<"WebLoginStartParams">;
export type WebLoginWaitParams = SchemaType<"WebLoginWaitParams">;
/** Agent config-file CRUD and artifact download/list payloads. */
export type AgentSummary = SchemaType<"AgentSummary">;
export type AgentsFileEntry = SchemaType<"AgentsFileEntry">;
export type AgentsCreateParams = SchemaType<"AgentsCreateParams">;
@@ -155,6 +186,8 @@ export type ArtifactsGetParams = SchemaType<"ArtifactsGetParams">;
export type ArtifactsGetResult = SchemaType<"ArtifactsGetResult">;
export type ArtifactsDownloadParams = SchemaType<"ArtifactsDownloadParams">;
export type ArtifactsDownloadResult = SchemaType<"ArtifactsDownloadResult">;
/** Model, command, plugin UI action, tool catalog, and skill workshop payloads. */
export type AgentsListParams = SchemaType<"AgentsListParams">;
export type AgentsListResult = SchemaType<"AgentsListResult">;
export type ModelChoice = SchemaType<"ModelChoice">;
@@ -207,6 +240,8 @@ export type SkillsUploadChunkParams = SchemaType<"SkillsUploadChunkParams">;
export type SkillsUploadCommitParams = SchemaType<"SkillsUploadCommitParams">;
export type SkillsInstallParams = SchemaType<"SkillsInstallParams">;
export type SkillsUpdateParams = SchemaType<"SkillsUpdateParams">;
/** Cron scheduler and run-log payloads. */
export type CronJob = SchemaType<"CronJob">;
export type CronListParams = SchemaType<"CronListParams">;
export type CronStatusParams = SchemaType<"CronStatusParams">;
@@ -217,6 +252,8 @@ export type CronRemoveParams = SchemaType<"CronRemoveParams">;
export type CronRunParams = SchemaType<"CronRunParams">;
export type CronRunsParams = SchemaType<"CronRunsParams">;
export type CronRunLogEntry = SchemaType<"CronRunLogEntry">;
/** Logs and approval payloads for chat, exec commands, plugins, and devices. */
export type LogsTailParams = SchemaType<"LogsTailParams">;
export type LogsTailResult = SchemaType<"LogsTailResult">;
export type ExecApprovalsGetParams = SchemaType<"ExecApprovalsGetParams">;
@@ -238,6 +275,8 @@ export type DeviceTokenRevokeParams = SchemaType<"DeviceTokenRevokeParams">;
export type ChatAbortParams = SchemaType<"ChatAbortParams">;
export type ChatInjectParams = SchemaType<"ChatInjectParams">;
export type ChatEvent = SchemaType<"ChatEvent">;
/** Gateway update and process lifecycle event payloads. */
export type UpdateRunParams = SchemaType<"UpdateRunParams">;
export type TickEvent = SchemaType<"TickEvent">;
export type ShutdownEvent = SchemaType<"ShutdownEvent">;
@@ -1,6 +1,7 @@
import { Type } from "typebox";
import { NonEmptyString } from "./primitives.js";
/** Runtime state reported for gateway-driven setup wizard sessions. */
const WizardRunStatusSchema = Type.Union([
Type.Literal("running"),
Type.Literal("done"),
@@ -8,6 +9,7 @@ const WizardRunStatusSchema = Type.Union([
Type.Literal("error"),
]);
/** Starts a setup wizard, optionally scoped to a local or remote workspace. */
export const WizardStartParamsSchema = Type.Object(
{
mode: Type.Optional(Type.Union([Type.Literal("local"), Type.Literal("remote")])),
@@ -16,6 +18,7 @@ export const WizardStartParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Client answer payload for the current wizard step. */
export const WizardAnswerSchema = Type.Object(
{
stepId: NonEmptyString,
@@ -24,6 +27,7 @@ export const WizardAnswerSchema = Type.Object(
{ additionalProperties: false },
);
/** Advances a wizard session, with an answer when the previous step requested input. */
export const WizardNextParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -32,6 +36,7 @@ export const WizardNextParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Shared session-id-only params for cancel and status requests. */
const WizardSessionIdParamsSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -39,10 +44,13 @@ const WizardSessionIdParamsSchema = Type.Object(
{ additionalProperties: false },
);
/** Cancels an active wizard session. */
export const WizardCancelParamsSchema = WizardSessionIdParamsSchema;
/** Reads status for an active or recently completed wizard session. */
export const WizardStatusParamsSchema = WizardSessionIdParamsSchema;
/** Selectable value shown in a choice-based wizard step. */
export const WizardStepOptionSchema = Type.Object(
{
value: Type.Unknown(),
@@ -52,6 +60,7 @@ export const WizardStepOptionSchema = Type.Object(
{ additionalProperties: false },
);
/** UI contract for one wizard step rendered by gateway clients. */
export const WizardStepSchema = Type.Object(
{
id: NonEmptyString,
@@ -76,6 +85,7 @@ export const WizardStepSchema = Type.Object(
{ additionalProperties: false },
);
/** Common response fields for start and next calls. */
const WizardResultFields = {
done: Type.Boolean(),
step: Type.Optional(WizardStepSchema),
@@ -83,10 +93,12 @@ const WizardResultFields = {
error: Type.Optional(Type.String()),
};
/** Result after advancing a wizard session. */
export const WizardNextResultSchema = Type.Object(WizardResultFields, {
additionalProperties: false,
});
/** Result returned when a wizard session is created. */
export const WizardStartResultSchema = Type.Object(
{
sessionId: NonEmptyString,
@@ -95,6 +107,7 @@ export const WizardStartResultSchema = Type.Object(
{ additionalProperties: false },
);
/** Minimal status poll result used when the client does not need the next step. */
export const WizardStatusResultSchema = Type.Object(
{
status: WizardRunStatusSchema,
@@ -3,6 +3,13 @@ import { describe, expect, it } from "vitest";
import { buildTalkConfigResponse } from "../../../src/config/talk.js";
import { validateTalkConfigResult } from "./index.js";
/**
* Talk config contract tests shared between config normalization and gateway
* protocol validation. Fixtures capture provider selection and timeout behavior
* so config changes cannot silently diverge from the public RPC response shape.
*/
/** Expected resolved provider/config selection for one fixture case. */
type ExpectedSelection = {
provider: string;
normalizedPayload: boolean;
@@ -10,6 +17,7 @@ type ExpectedSelection = {
apiKey?: string;
};
/** Fixture row that validates normalized Talk provider selection. */
type SelectionContractCase = {
id: string;
defaultProvider: string;
@@ -18,6 +26,7 @@ type SelectionContractCase = {
talk: Record<string, unknown>;
};
/** Fixture row that validates Talk silence-timeout normalization. */
type TimeoutContractCase = {
id: string;
fallback: number;
@@ -25,11 +34,13 @@ type TimeoutContractCase = {
talk: Record<string, unknown>;
};
/** JSON fixture file shape used by this contract test. */
type TalkConfigContractFixture = {
selectionCases: SelectionContractCase[];
timeoutCases: TimeoutContractCase[];
};
/** External fixture keeps the matrix readable and reusable across config edits. */
const fixturePath = new URL("../../../test/fixtures/talk-config-contract.json", import.meta.url);
const fixtures = JSON.parse(fs.readFileSync(fixturePath, "utf-8")) as TalkConfigContractFixture;
+1
View File
@@ -1,3 +1,4 @@
/** Public LLM core contracts shared by providers, plugin SDK wrappers, and tests. */
export * from "./types.js";
export * from "./utils/diagnostics.js";
export * from "./utils/event-stream.js";
+38 -2
View File
@@ -1,6 +1,7 @@
export type { AssistantMessageDiagnostic, DiagnosticErrorInfo } from "./utils/diagnostics.js";
import type { AssistantMessageDiagnostic } from "./utils/diagnostics.js";
/** Provider API families with first-class request/stream adapters in OpenClaw. */
export type KnownApi =
| "openai-completions"
| "mistral-conversations"
@@ -12,20 +13,29 @@ export type KnownApi =
| "google-generative-ai"
| "google-vertex";
/** Provider API id; custom providers can use ids outside the built-in set. */
export type Api = KnownApi | (string & {});
/** Image-generation API families with first-class adapters in OpenClaw. */
export type KnownImagesApi = "openrouter-images";
/** Image API id; custom image providers can use ids outside the built-in set. */
export type ImagesApi = KnownImagesApi | (string & {});
/** Provider id used for routing, diagnostics, and config lookups. */
export type Provider = string;
/** Image provider ids with first-class adapters in OpenClaw. */
export type KnownImagesProvider = "openrouter";
/** Image provider id used for routing, diagnostics, and config lookups. */
export type ImagesProvider = string;
/** Normalized reasoning-effort levels shared across provider-specific knobs. */
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
/** Model thinking setting including explicit disabled state. */
export type ModelThinkingLevel = "off" | ThinkingLevel;
/** Provider-specific values for normalized thinking levels. */
export type ThinkingLevelMap = Partial<Record<ModelThinkingLevel, string | null>>;
/** Token budgets for each thinking level (token-based providers only) */
@@ -37,18 +47,22 @@ export interface ThinkingBudgets {
max?: number;
}
// Base options all providers share
/** Prompt-cache retention preference shared by providers that expose cache controls. */
export type CacheRetention = "none" | "short" | "long";
/** Streaming transport preference for providers that support multiple transports. */
export type Transport = "sse" | "websocket" | "websocket-cached" | "auto";
/** Helper for hooks that may be synchronous or asynchronous. */
export type MaybePromise<T> = T | Promise<T>;
/** Minimal HTTP response metadata surfaced through provider hooks. */
export interface ProviderResponse {
status: number;
headers: Record<string, string>;
}
/** Request options shared by text streaming providers. */
export interface StreamOptions {
temperature?: number;
maxTokens?: number;
@@ -125,6 +139,7 @@ export interface StreamOptions {
export type ProviderStreamOptions = StreamOptions & Record<string, unknown>;
/** Request options shared by image-generation providers. */
export interface ImagesOptions {
signal?: AbortSignal;
apiKey?: string;
@@ -167,7 +182,7 @@ export interface ImagesOptions {
export type ProviderImagesOptions = ImagesOptions & Record<string, unknown>;
// Unified options with reasoning passed to streamSimple() and completeSimple()
/** Unified text options used by simple completion helpers. */
export interface SimpleStreamOptions extends StreamOptions {
reasoning?: ThinkingLevel;
/** Custom token budgets for thinking levels (token-based providers only) */
@@ -206,12 +221,14 @@ export interface TextSignatureV1 {
phase?: "commentary" | "final_answer";
}
/** Plain assistant/user text content block. */
export interface TextContent {
type: "text";
text: string;
textSignature?: string; // e.g., for OpenAI responses, message metadata (legacy id string or TextSignatureV1 JSON)
}
/** Provider reasoning/thinking content block, including opaque replay signatures. */
export interface ThinkingContent {
type: "thinking";
thinking: string;
@@ -222,12 +239,14 @@ export interface ThinkingContent {
redacted?: boolean;
}
/** Base64 image content block with MIME type metadata. */
export interface ImageContent {
type: "image";
data: string; // base64 encoded image data
mimeType: string; // e.g., "image/jpeg", "image/png"
}
/** Normalized assistant tool call emitted by providers or repaired from text. */
export interface ToolCall {
type: "toolCall";
id: string;
@@ -237,6 +256,7 @@ export interface ToolCall {
executionMode?: "sequential" | "parallel";
}
/** Normalized token and cost accounting for a provider response. */
export interface Usage {
input: number;
output: number;
@@ -252,14 +272,17 @@ export interface Usage {
};
}
/** Normalized assistant stop reasons across text providers. */
export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
/** User turn in a text-model conversation. */
export interface UserMessage {
role: "user";
content: string | (TextContent | ImageContent)[];
timestamp: number; // Unix timestamp in milliseconds
}
/** Assistant turn, including provider identity and final stop state. */
export interface AssistantMessage {
role: "assistant";
content: (TextContent | ThinkingContent | ToolCall)[];
@@ -275,6 +298,7 @@ export interface AssistantMessage {
timestamp: number; // Unix timestamp in milliseconds
}
/** Tool result turn that answers a prior assistant tool call. */
export interface ToolResultMessage<TDetails = unknown> {
role: "toolResult";
toolCallId: string;
@@ -285,17 +309,23 @@ export interface ToolResultMessage<TDetails = unknown> {
timestamp: number; // Unix timestamp in milliseconds
}
/** Any text-model conversation message supported by LLM core. */
export type Message = UserMessage | AssistantMessage | ToolResultMessage;
/** Image request input content accepted by image providers. */
export type ImagesInputContent = TextContent | ImageContent;
/** Image response output content returned by image providers. */
export type ImagesOutputContent = TextContent | ImageContent;
/** Image-generation request context. */
export interface ImagesContext {
input: ImagesInputContent[];
}
/** Normalized image-generation stop reasons. */
export type ImagesStopReason = "stop" | "error" | "aborted";
/** Final image-generation response shape. */
export interface AssistantImages {
api: ImagesApi;
provider: ImagesProvider;
@@ -310,12 +340,14 @@ export interface AssistantImages {
import type { TSchema } from "typebox";
/** Provider tool declaration with a TypeBox/JSON-schema parameter object. */
export interface Tool<TParameters extends TSchema = TSchema> {
name: string;
description: string;
parameters: TParameters;
}
/** Text-model request context shared by provider adapters. */
export interface Context {
systemPrompt?: string;
messages: Message[];
@@ -349,11 +381,15 @@ export type AssistantMessageEvent =
| { type: "error"; reason: Extract<StopReason, "aborted" | "error">; error: AssistantMessage };
export interface AssistantMessageEventStreamContract extends AsyncIterable<AssistantMessageEvent> {
/** Queue one stream event for consumers. */
push(event: AssistantMessageEvent): void;
/** Complete the stream and optionally resolve the final message. */
end(result?: AssistantMessage): void;
/** Final assistant message produced by the stream. */
result(): Promise<AssistantMessage>;
}
/** Read-only stream contract accepted by consumers that do not need to push events. */
export interface AssistantMessageEventStreamLike extends AsyncIterable<AssistantMessageEvent> {
result(): Promise<AssistantMessage>;
}
+4
View File
@@ -27,8 +27,11 @@ export interface ApiProvider<
TApi extends Api = Api,
TOptions extends StreamOptions = StreamOptions,
> {
/** Model API id this provider handles. */
api: TApi;
/** Full streaming adapter for callers that already own structured options. */
stream: StreamFunction<TApi, TOptions>;
/** Simple streaming adapter used by agent and plugin runtime defaults. */
streamSimple: StreamFunction<TApi, SimpleStreamOptions>;
}
@@ -72,6 +75,7 @@ function wrapStreamSimple<TApi extends Api>(
/** Registers or replaces the provider implementation for an API id. */
export function registerApiProvider<TApi extends Api, TOptions extends StreamOptions>(
provider: ApiProvider<TApi, TOptions>,
/** Optional source id used to unregister all providers owned by one plugin/runtime. */
sourceId?: string,
): void {
apiProviderRegistry.set(provider.api, {
+9 -1
View File
@@ -18,6 +18,8 @@ function scanParenAwareBreakpoints(text: string): { lastNewline: number; lastWhi
for (let i = 0; i < text.length; i++) {
const char = text[i];
// Parenthesized spans often contain rewritten links or file references;
// avoid splitting them unless the window has no safer outside break.
if (char === "(") {
depth += 1;
continue;
@@ -39,7 +41,11 @@ function scanParenAwareBreakpoints(text: string): { lastNewline: number; lastWhi
return { lastNewline, lastWhitespace };
}
/** Splits plain text at readable boundaries while avoiding breaks inside parentheses. */
/**
* Splits plain text into size-bounded chunks at readable boundaries.
*
* Returns the original text as one chunk when the limit is non-positive.
*/
export function chunkText(text: string, limit: number): string[] {
const early = resolveChunkEarlyReturn(text, limit);
if (early) {
@@ -56,6 +62,8 @@ export function chunkText(text: string, limit: number): string[] {
const windowEnd = Math.min(text.length, cursor + limit);
const window = text.slice(cursor, windowEnd);
const { lastNewline, lastWhitespace } = scanParenAwareBreakpoints(window);
// Prefer block boundaries, then spaces, then a hard size cut when no
// readable breakpoint exists inside this window.
const breakOffset = lastNewline > 0 ? lastNewline : lastWhitespace;
const end = breakOffset > 0 ? cursor + breakOffset : windowEnd;
chunks.push(text.slice(cursor, end));
+6
View File
@@ -1,7 +1,10 @@
import { scanFenceSpans, type FenceScanState, type FenceSpan } from "./fences.js";
/** Incremental inline-code scanner state carried across chunk boundaries. */
export type InlineCodeState = {
/** Whether the current scan is inside an unterminated inline code span. */
open: boolean;
/** Backtick run length required to close the current inline code span. */
ticks: number;
};
@@ -16,8 +19,11 @@ type InlineCodeSpansResult = {
};
type CodeSpanIndex = {
/** Inline-code state to carry into the next streamed chunk. */
inlineState: InlineCodeState;
/** Fenced-code state to carry into the next streamed chunk. */
fenceState: FenceScanState;
/** True when an offset is inside fenced code or inline code. */
isInside: (index: number) => boolean;
};
+1
View File
@@ -1,3 +1,4 @@
/** Public Markdown parsing, rendering, chunking, and table-conversion utilities. */
export * from "./code-spans.js";
export * from "./fences.js";
export * from "./frontmatter.js";
@@ -8,15 +8,21 @@ import {
/** A rendered chunk paired with the Markdown IR slice that produced it. */
export type RenderedMarkdownChunk<TRendered> = {
/** Rendered payload for this chunk after caller-specific escaping/link rewriting. */
rendered: TRendered;
/** Source IR slice used to produce the rendered payload. */
source: MarkdownIR;
};
/** Inputs for chunking Markdown IR against the final rendered payload size. */
export type RenderMarkdownIRChunksWithinLimitOptions<TRendered> = {
/** Parsed Markdown IR to split. */
ir: MarkdownIR;
/** Maximum measured size for each rendered chunk. */
limit: number;
/** Returns the size unit enforced by the target transport. */
measureRendered: (rendered: TRendered) => number;
/** Renders a candidate IR slice for measuring and final output. */
renderChunk: (ir: MarkdownIR) => TRendered;
};
@@ -139,6 +145,8 @@ function findMarkdownIRPreservedSplitIndex(text: string, start: number, limit: n
for (let index = start; index < maxEnd; index += 1) {
const char = text[index];
// Parenthesized text often carries rewritten file/link references; prefer
// keeping it intact unless no outside break exists in the current window.
if (char === "(") {
sawNonWhitespace = true;
parenDepth += 1;
@@ -157,6 +165,7 @@ function findMarkdownIRPreservedSplitIndex(text: string, start: number, limit: n
continue;
}
if (char === "\n") {
// Newlines preserve markdown block structure better than other spaces.
lastAnyNewlineBreak = index + 1;
if (parenDepth === 0) {
lastOutsideParenNewlineBreak = index + 1;
+7 -2
View File
@@ -1,12 +1,15 @@
import type { MarkdownIR, MarkdownLinkSpan, MarkdownStyle, MarkdownStyleSpan } from "./ir.js";
/** Marker pair used to wrap a styled Markdown span in the target renderer. */
export type RenderStyleMarker = {
open: string | ((span: MarkdownStyleSpan) => string);
close: string;
};
/** Optional marker map; omitted styles are emitted as plain escaped text. */
export type RenderStyleMap = Partial<Record<MarkdownStyle, RenderStyleMarker>>;
/** Link wrapper boundaries after a renderer has accepted or rewritten a link span. */
export type RenderLink = {
start: number;
end: number;
@@ -14,6 +17,7 @@ export type RenderLink = {
close: string;
};
/** Renderer hooks for converting Markdown IR into a marker-based target format. */
export type RenderOptions = {
styleMarkers: RenderStyleMap;
escapeText: (text: string) => string;
@@ -46,6 +50,7 @@ function sortStyleSpans(spans: MarkdownStyleSpan[]): MarkdownStyleSpan[] {
});
}
/** Renders Markdown IR by nesting configured style markers and optional link markers. */
export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions): string {
const text = ir.text ?? "";
if (!text) {
@@ -104,7 +109,7 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions
}
const points = [...boundaries].toSorted((a, b) => a - b);
// Unified stack for both styles and links, tracking close string and end position
// Links and styles share one stack so equal-end spans close in exact reverse open order.
const stack: { close: string; end: number }[] = [];
type OpeningItem =
| { end: number; open: string; close: string; kind: "link"; index: number }
@@ -121,7 +126,7 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions
for (let i = 0; i < points.length; i += 1) {
const pos = points[i];
// Close ALL elements (styles and links) in LIFO order at this position
// Close all elements at this boundary before opening replacements at the same offset.
while (stack.length && stack[stack.length - 1]?.end === pos) {
const item = stack.pop();
if (item) {
+1
View File
@@ -1 +1,2 @@
/** Table rendering modes used when markdown tables need plaintext-safe output. */
export type MarkdownTableMode = "off" | "bullets" | "code" | "block";
+3 -2
View File
@@ -1,3 +1,4 @@
/** Estimates decoded bytes without allocating a cleaned copy of the base64 payload. */
export function estimateBase64DecodedBytes(base64: string): number {
// Avoid `trim()`/`replace()` here: they allocate a second (potentially huge) string.
// We only need a conservative decoded-size estimate to enforce budgets before Buffer.from(..., "base64").
@@ -47,8 +48,8 @@ function isBase64DataChar(code: number): boolean {
}
/**
* Normalize and validate a base64 string.
* Returns canonical base64 (no whitespace) or undefined when invalid.
* Normalizes and validates a base64 string, returning canonical no-whitespace
* base64 only when the input has valid alphabet, padding, and length.
*/
export function canonicalizeBase64(base64: string): string | undefined {
let cleaned = "";
+7
View File
@@ -1,10 +1,16 @@
/** Default outbound image payload cap shared by media loaders and adapters. */
export const MAX_IMAGE_BYTES = 6 * 1024 * 1024; // 6MB
/** Default outbound audio payload cap shared by media loaders and adapters. */
export const MAX_AUDIO_BYTES = 16 * 1024 * 1024; // 16MB
/** Default outbound video payload cap shared by media loaders and adapters. */
export const MAX_VIDEO_BYTES = 16 * 1024 * 1024; // 16MB
/** Default outbound document payload cap shared by media loaders and adapters. */
export const MAX_DOCUMENT_BYTES = 100 * 1024 * 1024; // 100MB
/** Media families that share size-policy and MIME-classification behavior. */
export type MediaKind = "image" | "audio" | "video" | "document";
/** Maps a MIME type to the media family used for size limits and routing. */
export function mediaKindFromMime(mime?: string | null): MediaKind | undefined {
if (!mime) {
return undefined;
@@ -30,6 +36,7 @@ export function mediaKindFromMime(mime?: string | null): MediaKind | undefined {
return undefined;
}
/** Returns the default byte cap for a classified media family. */
export function maxBytesForKind(kind: MediaKind): number {
switch (kind) {
case "image":
@@ -1,3 +1,4 @@
/** Parses a Content-Length header as a safe integer or rejects malformed values. */
export function parseMediaContentLength(raw: string | null): number | null {
if (raw === null) {
return null;
+3
View File
@@ -1,13 +1,16 @@
import path from "node:path";
/** Returns the final filename segment for either POSIX or Windows-style paths. */
export function basenameFromAnyPath(value: string): string {
return path.win32.basename(path.posix.basename(value));
}
/** Returns the extension from the final filename segment of any path flavor. */
export function extnameFromAnyPath(value: string): string {
return path.extname(basenameFromAnyPath(value));
}
/** Returns the extensionless filename from the final segment of any path flavor. */
export function nameFromAnyPath(value: string): string {
const base = basenameFromAnyPath(value);
const ext = path.extname(base);
@@ -9,6 +9,8 @@ function normalizePosixAbsolutePath(value: string): string | undefined {
if (!trimmed || trimmed.includes("\0")) {
return undefined;
}
// Compare all roots as POSIX-style absolute paths so channel configs can use
// stable patterns even when a source reports Windows separators.
const normalized = path.posix.normalize(trimmed.replaceAll("\\", "/"));
const isAbsolute = normalized.startsWith("/") || WINDOWS_DRIVE_ABS_RE.test(normalized);
if (!isAbsolute || normalized === "/") {
@@ -44,6 +46,7 @@ function matchesRootPattern(params: { candidatePath: string; rootPattern: string
return true;
}
/** Validates an absolute inbound root pattern with whole-segment wildcards only. */
export function isValidInboundPathRootPattern(value: string): boolean {
const normalized = normalizePosixAbsolutePath(value);
if (!normalized) {
@@ -56,6 +59,7 @@ export function isValidInboundPathRootPattern(value: string): boolean {
return segments.every((segment) => segment === WILDCARD_SEGMENT || !segment.includes("*"));
}
/** Normalizes configured inbound attachment roots, dropping invalid or duplicate patterns. */
export function normalizeInboundPathRoots(roots?: readonly string[]): string[] {
const normalized: string[] = [];
const seen = new Set<string>();
@@ -76,6 +80,7 @@ export function normalizeInboundPathRoots(roots?: readonly string[]): string[] {
return normalized;
}
/** Merges inbound attachment root lists while preserving first-seen priority. */
export function mergeInboundPathRoots(
...rootsLists: Array<readonly string[] | undefined>
): string[] {
@@ -94,6 +99,7 @@ export function mergeInboundPathRoots(
return merged;
}
/** Checks whether a candidate inbound media path is covered by configured or fallback roots. */
export function isInboundPathAllowed(params: {
filePath: string;
roots: readonly string[];
@@ -1,5 +1,6 @@
import { canonicalizeBase64 } from "./base64.js";
/** Prefix used to distinguish inline data URLs from remote/local image references. */
export const INLINE_IMAGE_DATA_URL_PREFIX = "data:";
const IMAGE_SIGNATURES: Array<{
@@ -47,6 +48,7 @@ function startsWithDataUrl(value: string): boolean {
);
}
/** Sniffs supported inline image formats from decoded bytes. */
export function sniffInlineImageMime(buffer: Buffer): string | undefined {
return IMAGE_SIGNATURES.find((signature) => signature.matches(buffer))?.mime;
}
@@ -79,6 +81,7 @@ function metadataAllowsImageBase64(metadata: string[]): boolean {
return isImageMimeType && options.some((part) => part.toLowerCase() === "base64");
}
/** Canonicalizes trusted inline image data URLs and rejects malformed or non-image payloads. */
export function sanitizeInlineImageDataUrl(imageUrl: string): string | undefined {
const parsed = parseInlineImageDataUrl(imageUrl);
if (!parsed) {
@@ -99,5 +102,6 @@ export function sanitizeInlineImageDataUrl(imageUrl: string): string | undefined
if (!sniffedMimeType) {
return undefined;
}
// Trust the byte signature over caller-supplied metadata before reinlining.
return `data:${sniffedMimeType};base64,${canonicalPayload}`;
}
+4
View File
@@ -1,12 +1,15 @@
/** Cached async loader used by runtime boundaries that should import on first use. */
export type LazyPromiseLoader<T> = {
load(): Promise<T>;
clear(): void;
};
/** Controls whether a failed first import stays cached or is retried later. */
export type LazyPromiseLoaderOptions = {
cacheRejections?: boolean;
};
/** Creates a single-flight promise cache around a lazy import or other async loader. */
export function createLazyImportLoader<T>(
load: () => Promise<T>,
options: LazyPromiseLoaderOptions = {},
@@ -16,6 +19,7 @@ export function createLazyImportLoader<T>(
const createPromise = (): Promise<T> => {
const loaded = Promise.resolve().then(load);
if (options.cacheRejections !== true) {
// Failed optional-runtime imports should retry after install/config changes.
void loaded.catch(() => {
if (promise === loaded) {
promise = undefined;
@@ -1,6 +1,7 @@
const HTTP_URL_RE = /^https?:\/\//i;
const MXC_URL_RE = /^mxc:\/\//i;
/** Returns true for remote media URLs that should stay URL-backed instead of local-file-backed. */
export function isPassThroughRemoteMediaSource(value: string | null | undefined): boolean {
const normalized = value?.trim() ?? "";
return Boolean(normalized) && (HTTP_URL_RE.test(normalized) || MXC_URL_RE.test(normalized));
+11 -2
View File
@@ -2,7 +2,7 @@ import path from "node:path";
import { type MediaKind, mediaKindFromMime } from "./constants.js";
import { createLazyImportLoader } from "./lazy-import.js";
/** @internal */
/** Maximum byte prefix passed to dependency MIME sniffers for bounded memory/CPU work. */
export const FILE_TYPE_SNIFF_MAX_BYTES = 1024 * 1024;
// Map common mimes to preferred file extensions.
@@ -97,6 +97,7 @@ const AUDIO_FILE_EXTENSIONS = new Set([
const fileTypeModuleLoader = createLazyImportLoader(() => import("file-type"));
/** Normalizes MIME strings by dropping parameters, lowercasing, and folding APNG to PNG. */
export function normalizeMimeType(mime?: string | null): string | undefined {
if (!mime) {
return undefined;
@@ -108,7 +109,7 @@ export function normalizeMimeType(mime?: string | null): string | undefined {
return cleaned || undefined;
}
/** @internal */
/** Returns the bounded buffer prefix used for dependency MIME sniffing. */
export function sliceMimeSniffBuffer(buffer: Buffer): Buffer {
if (buffer.byteLength <= FILE_TYPE_SNIFF_MAX_BYTES) {
return buffer;
@@ -144,6 +145,7 @@ function sniffKnownAudioMagic(buffer: Buffer): string | undefined {
return undefined;
}
/** Extracts a lowercase extension from a local path or HTTP URL pathname. */
export function getFileExtension(filePath?: string | null): string | undefined {
if (!filePath) {
return undefined;
@@ -160,6 +162,7 @@ export function getFileExtension(filePath?: string | null): string | undefined {
return ext || undefined;
}
/** Maps a file path or URL extension to the preferred MIME type when known. */
export function mimeTypeFromFilePath(filePath?: string | null): string | undefined {
const ext = getFileExtension(filePath);
if (!ext) {
@@ -168,6 +171,7 @@ export function mimeTypeFromFilePath(filePath?: string | null): string | undefin
return MIME_BY_EXT[ext];
}
/** Returns true when a filename extension is a supported audio container. */
export function isAudioFileName(fileName?: string | null): boolean {
const ext = getFileExtension(fileName);
if (!ext) {
@@ -176,6 +180,7 @@ export function isAudioFileName(fileName?: string | null): boolean {
return AUDIO_FILE_EXTENSIONS.has(ext);
}
/** Detects the best MIME type from bytes, file path, and header metadata. */
export function detectMime(opts: {
buffer?: Buffer;
headerMime?: string | null;
@@ -232,6 +237,7 @@ async function detectMimeImpl(opts: {
return undefined;
}
/** Returns the preferred file extension for a normalized or raw MIME string. */
export function extensionForMime(mime?: string | null): string | undefined {
const normalized = normalizeMimeType(mime);
if (!normalized) {
@@ -240,6 +246,7 @@ export function extensionForMime(mime?: string | null): string | undefined {
return EXT_BY_MIME[normalized];
}
/** Returns true when content type or filename identifies GIF media. */
export function isGifMedia(opts: {
contentType?: string | null;
fileName?: string | null;
@@ -251,6 +258,7 @@ export function isGifMedia(opts: {
return ext === ".gif";
}
/** Maps image format labels from encoders/probes to MIME types. */
export function imageMimeFromFormat(format?: string | null): string | undefined {
if (!format) {
return undefined;
@@ -274,6 +282,7 @@ export function imageMimeFromFormat(format?: string | null): string | undefined
}
}
/** Normalizes a MIME string before classifying it into a media family. */
export function kindFromMime(mime?: string | null): MediaKind | undefined {
return mediaKindFromMime(normalizeMimeType(mime));
}
@@ -1,8 +1,10 @@
/** Details passed to byte-stream overflow error factories. */
export type ByteStreamLimitOverflow = {
size: number;
maxBytes: number;
};
/** Options for reading an async byte stream under a hard byte cap. */
export type ReadByteStreamWithLimitOptions = {
maxBytes: number;
onOverflow?: (params: ByteStreamLimitOverflow) => Error;
@@ -29,6 +31,8 @@ function destroyReadableOnOverflow(stream: unknown, err: Error): void {
destroy?: (error?: Error) => unknown;
cancel?: (reason?: unknown) => unknown;
};
// Stop upstream producers immediately after overflow; otherwise large media
// streams can continue buffering after the caller has already failed.
if (typeof readable.destroy === "function") {
try {
readable.destroy(err);
@@ -42,6 +46,7 @@ function destroyReadableOnOverflow(stream: unknown, err: Error): void {
}
}
/** Reads and concatenates an async byte stream, throwing once the byte cap is exceeded. */
export async function readByteStreamWithLimit(
stream: AsyncIterable<unknown>,
opts: ReadByteStreamWithLimitOptions,
@@ -23,6 +23,8 @@ async function readChunkWithIdleTimeout(
onIdleTimeout?.({ chunkTimeoutMs: resolvedChunkTimeoutMs }) ??
new Error(`Media download stalled: no data received for ${resolvedChunkTimeoutMs}ms`);
clear();
// Cancel the body with the same error so fetch-backed streams release
// sockets/buffers instead of idling after the caller times out.
void reader.cancel(error).catch(() => undefined);
reject(error);
}, resolvedChunkTimeoutMs);
@@ -123,6 +125,7 @@ async function readResponsePrefix(
};
}
/** Reads a response body under a byte cap, cancelling the stream on overflow or idle timeout. */
export async function readResponseWithLimit(
res: Response,
maxBytes: number,
@@ -146,6 +149,7 @@ export async function readResponseWithLimit(
return prefix.buffer;
}
/** Reads a small collapsed text prefix from a response body for diagnostics/errors. */
export async function readResponseTextSnippet(
res: Response,
opts?: {
@@ -1,3 +1,5 @@
// Agent-facing runtime facade for memory host packages.
// Keep exports here limited to config/state helpers that memory plugins may reuse.
export {
DEFAULT_AGENT_COMPACTION_RESERVE_TOKENS_FLOOR,
asToolParamsRecord,
@@ -1,3 +1,5 @@
// Config-facing runtime facade for memory host packages.
// This keeps memory plugins off broader core config modules and their private helpers.
export {
getRuntimeConfig,
hasConfiguredSecretInput,
@@ -1,3 +1,5 @@
// Memory-facing runtime facade for plugin registration, embeddings, and prompt artifacts.
// Re-export only stable host seams; plugin implementations should not import core internals.
export {
buildActiveMemoryPromptSection,
emptyPluginConfigSchema,
@@ -632,6 +632,7 @@ const STOP_WORDS_ZH = new Set([
"告诉",
]);
/** Returns true for low-value conversational tokens that should not drive FTS matching. */
export function isQueryStopWordToken(token: string): boolean {
return (
STOP_WORDS_EN.has(token) ||
+1
View File
@@ -1 +1,2 @@
// Public query helper facade for memory search token expansion.
export { extractKeywords, isQueryStopWordToken } from "./host/query-expansion.js";
+1
View File
@@ -1,3 +1,4 @@
/** Public repair utilities for model-emitted plain-text tool calls. */
export {
parseStandalonePlainTextToolCallBlocks,
stripPlainTextToolCallBlocks,
+10
View File
@@ -10,16 +10,25 @@ import {
skipWhitespace,
} from "./grammar.js";
/** Parsed standalone plain-text tool call block with source offsets for repair. */
export type PlainTextToolCallBlock = {
/** Parsed JSON arguments object. */
arguments: Record<string, unknown>;
/** Exclusive end offset of the parsed block. */
end: number;
/** Tool name parsed from bracket, Harmony, or XML-ish syntax. */
name: string;
/** Original text slice that produced this block. */
raw: string;
/** Inclusive start offset of the parsed block. */
start: number;
};
/** Parser limits and allowlist options for plain-text tool-call repair. */
export type PlainTextToolCallParseOptions = {
/** Optional allowlist of tool names that may be repaired. */
allowedToolNames?: Iterable<string>;
/** Maximum JSON payload size accepted for one repaired call. */
maxPayloadBytes?: number;
};
@@ -389,6 +398,7 @@ export function parseStandalonePlainTextToolCallBlocks(
return blocks.length > 0 ? blocks : null;
}
/** Removes full-line standalone plain-text tool-call blocks from user-visible text. */
export function stripPlainTextToolCallBlocks(text: string): string {
if (
!text ||
@@ -11,26 +11,36 @@ import {
} from "./grammar.js";
export type PlainTextToolCallNameMatcher = {
/** True only when the candidate is a complete tool name this request may repair. */
hasExactName(name: string): boolean;
/** True while streamed bytes still match at least one repairable tool name prefix. */
hasNamePrefix(prefix: string): boolean;
};
/** Result of repairing the final message carried by a provider stream `done` event. */
export type PlainTextToolCallMessageNormalization =
| { kind: "promoted" | "scrubbed"; message: Record<string, unknown> }
| undefined;
/** Stream-level hooks used to promote leaked text tool calls into provider events. */
export type PlainTextToolCallStreamNormalizerOptions = {
/** Expands a promoted final message into provider-native tool-call stream events. */
createPromotedToolCallEvents(message: Record<string, unknown>): Iterable<unknown>;
/** Tool-name matcher scoped to the exact request being normalized. */
matcher: PlainTextToolCallNameMatcher;
/** Repairs or scrubs the final done-message snapshot after text buffering completes. */
normalizeDoneMessage(params: {
message: unknown;
reason: unknown;
}): PlainTextToolCallMessageNormalization;
/** Stop after the first normalized done event when the wrapped provider has completed. */
stopAfterDone?: boolean;
};
const TEXT_TOOL_CALL_BUFFER_MAX_CHARS = 256_000;
// Keep a bounded prefix plus enough tail to notice closing markers after the cap;
// otherwise a huge leaked payload could either grow unbounded or lose the visible suffix.
const TEXT_TOOL_CALL_SUPPRESSED_SCAN_MAX_CHARS = TEXT_TOOL_CALL_BUFFER_MAX_CHARS + 64_000;
const TEXT_TOOL_CALL_SUPPRESSED_TAIL_CHARS =
TEXT_TOOL_CALL_SUPPRESSED_SCAN_MAX_CHARS - TEXT_TOOL_CALL_BUFFER_MAX_CHARS;
@@ -941,6 +951,7 @@ function scrubReclassifiedMixedTextFromError(
};
}
/** Scrubs final messages whose streamed plain-text tool-call prefix exceeded the buffer cap. */
export function scrubOverCapPlainTextToolCallMessage(params: {
candidateText: string | undefined;
matcher: PlainTextToolCallNameMatcher;
@@ -1039,6 +1050,7 @@ function isBufferedTextEvent(bufferedEvent: unknown): boolean {
);
}
/** Buffers provider stream text long enough to promote or hide leaked plain-text tool calls. */
export async function* normalizePlainTextToolCallStreamEvents(
source: AsyncIterable<unknown>,
options: PlainTextToolCallStreamNormalizerOptions,
@@ -1121,6 +1133,8 @@ export async function* normalizePlainTextToolCallStreamEvents(
continue;
}
if (suppressingOverCapTextToolCall) {
// Once the tentative tool call exceeds the cap, suppress text deltas until a closing
// marker proves whether the buffered prefix was a hidden call or mixed visible text.
if (hasSuppressedTextContentIndex && record.contentIndex !== suppressedTextContentIndex) {
if (isAllowedTextToolCallLikeEvent(record, options.matcher)) {
continue;
@@ -1171,6 +1185,8 @@ export async function* normalizePlainTextToolCallStreamEvents(
? stripSerializedToolCallPrefixes(bufferedText.trimStart(), options.matcher)
: null;
if (visibleText?.trim()) {
// A tool-call prefix followed by visible text must be reclassified: emit only the
// suffix now, then scrub the final done/error snapshots to match that stream history.
yield* flushScrubbedBufferedNonTextEvents(true);
reclassifiedMixedTextContentIndex = record.contentIndex;
hasReclassifiedMixedTextContentIndex = true;
+2
View File
@@ -17,6 +17,7 @@ import { classifyAcpToolApproval, type AcpApprovalClass } from "./approval-class
type PermissionOption = RequestPermissionRequest["options"][number];
// ACP permission resolution keeps readonly tool classes noninteractive and prompts for risky tools.
type PermissionResolverDeps = {
prompt?: (toolName: string | undefined, toolTitle?: string) => Promise<boolean>;
log?: (line: string) => void;
@@ -153,6 +154,7 @@ type AcpClientSpawnEnvOptions = {
stripKeys?: Iterable<string>;
};
/** Builds the sanitized environment used when spawning an ACP client process. */
export function resolveAcpClientSpawnEnv(
baseEnv: NodeJS.ProcessEnv = process.env,
options: AcpClientSpawnEnvOptions = {},
@@ -1,6 +1,7 @@
import type { AcpRuntimeErrorCode } from "../runtime/errors.js";
import { normalizeText } from "./runtime-options.js";
/** Captured backend attempt state used to decide whether failover is safe. */
export type BackendAttempt = {
backend: string;
error: string;
@@ -15,6 +15,7 @@ import { normalizeText } from "./runtime-options.js";
const ACP_BACKGROUND_TASK_TEXT_MAX_LENGTH = 160;
const ACP_BACKGROUND_TASK_PROGRESS_MAX_LENGTH = 240;
/** Context needed to mirror a child ACP turn into the requester task registry. */
export type BackgroundTaskContext = {
requesterSessionKey: string;
requesterOrigin?: DeliveryContext;
@@ -14,6 +14,7 @@ import type {
} from "./manager.types.js";
import { normalizeActorKey, requireReadySessionMeta } from "./manager.utils.js";
/** Cancels either the active ACP turn or the idle runtime handle for a session. */
export async function runManagerCancelSession(params: {
cfg: OpenClawConfig;
sessionKey: string;
+1
View File
@@ -59,6 +59,7 @@ import {
} from "./runtime-options.js";
import { SessionActorQueue } from "./session-actor-queue.js";
/** Coordinates ACP session metadata, runtime handles, per-session queues, and turn execution. */
export class AcpSessionManager {
private readonly actorQueue = new SessionActorQueue();
private readonly runtimeHandles = new ManagerRuntimeHandleCache();
@@ -17,6 +17,7 @@ import { withAcpRuntimeErrorBoundary } from "../runtime/errors.js";
import type { SessionAcpMeta, SessionEntry } from "./manager.types.js";
import { hasLegacyAcpIdentityProjection } from "./manager.utils.js";
/** Reconciles runtime-reported session identifiers into persisted ACP session metadata. */
export async function reconcileManagerRuntimeSessionIdentifiers(params: {
cfg: OpenClawConfig;
sessionKey: string;
@@ -22,6 +22,7 @@ import {
validateRuntimeOptionPatch,
} from "./runtime-options.js";
/** Initializes an ACP runtime session and persists its metadata before caching the handle. */
export async function runManagerInitializeSession(params: {
input: AcpInitializeSessionInput;
sessionKey: string;
@@ -16,6 +16,7 @@ import { RuntimeCache, type CachedRuntimeState } from "./runtime-cache.js";
import { normalizeText } from "./runtime-options.js";
import type { SessionActorQueue } from "./session-actor-queue.js";
/** Process-local cache of live ACP runtime handles keyed by canonical session actor. */
export class ManagerRuntimeHandleCache {
private readonly runtimeCache = new RuntimeCache();
private evictedRuntimeCount = 0;
@@ -96,6 +97,7 @@ export class ManagerRuntimeHandleCache {
}
for (const candidate of candidates) {
// Evict under the same actor queue so turns cannot race with runtime close.
await params.actorQueue.run(candidate.actorKey, async () => {
if (params.activeTurnBySession.has(candidate.actorKey)) {
return;
@@ -11,6 +11,7 @@ import type {
WriteManagerSessionMeta,
} from "./manager.types.js";
/** Detects acpx exits that are safe to retry with a fresh runtime handle. */
export function isRecoverableManagerAcpxExitError(message: string): boolean {
return /^acpx exited with (code \d+|signal [a-z0-9]+)/i.test(message.trim());
}
@@ -14,6 +14,7 @@ import type {
WithManagerSessionActor,
} from "./manager.types.js";
/** Resolves pending ACP session identities opportunistically during manager startup. */
export async function runManagerStartupIdentityReconcile(params: {
cfg: OpenClawConfig;
deps: Pick<AcpSessionManagerDeps, "listAcpSessions">;
+1
View File
@@ -16,6 +16,7 @@ import type {
import { requireReadySessionMeta } from "./manager.utils.js";
import { resolveRuntimeOptionsFromMeta } from "./runtime-options.js";
/** Reads a fresh ACP session status and reconciles runtime identifiers from the status response. */
export async function runManagerGetSessionStatus(params: {
cfg: OpenClawConfig;
sessionKey: string;
@@ -37,6 +37,7 @@ vi.mock("../runtime/registry.js", () => ({
export const hoisted = hoistedMocks;
// Shared ACP manager test harness with hoisted runtime/session-meta mocks.
const managerModule = await import("./manager.js");
export const AcpSessionManager = managerModule.AcpSessionManager;
export const resetAcpSessionManagerForTests = () =>
+1
View File
@@ -15,6 +15,7 @@ export type {
let ACP_SESSION_MANAGER_SINGLETON: AcpSessionManager | null = null;
/** Returns the process-wide ACP session manager singleton. */
export function getAcpSessionManager(): AcpSessionManager {
if (!ACP_SESSION_MANAGER_SINGLETON) {
ACP_SESSION_MANAGER_SINGLETON = new AcpSessionManager();
@@ -12,6 +12,7 @@ export type AcpTurnEventGate = {
open: boolean;
};
/** Summary of whether a turn stream emitted user-visible output or terminal events. */
export type AcpTurnStreamOutcome = {
sawOutput: boolean;
sawTerminalEvent: boolean;
@@ -113,6 +114,7 @@ export async function consumeAcpTurnStream(params: {
) => Promise<void> | void;
}): Promise<AcpTurnStreamOutcome> {
if (params.runtime.startTurn) {
// startTurn exposes result and event streams separately; coordinate both before reporting done.
const turn = params.runtime.startTurn(params.turn);
const eventsPromise = consumeAcpTurnEvents({
events: turn.events,
@@ -10,6 +10,7 @@ import { resolveRuntimeOptionsFromMeta } from "./runtime-options.js";
const ACP_TURN_TIMEOUT_CLEANUP_GRACE_MS = 2_000;
const ACP_TURN_TIMEOUT_REASON = "turn-timeout";
/** Resolves the effective ACP turn timeout from session runtime options or agent defaults. */
export function resolveTurnTimeoutMs(params: {
cfg: OpenClawConfig;
meta: SessionAcpMeta;
+1
View File
@@ -22,6 +22,7 @@ import {
upsertAcpSessionMeta,
} from "../runtime/session-meta.js";
/** Result of resolving persisted ACP metadata for a session key. */
export type AcpSessionResolution =
| {
kind: "none";
+1
View File
@@ -13,6 +13,7 @@ import {
} from "../../routing/session-key.js";
import type { AcpSessionResolution } from "./manager.types.js";
/** Resolves the agent id encoded in an ACP session key. */
export function resolveAcpAgentFromSessionKey(sessionKey: string, fallback = "main"): string {
const parsed = parseAgentSessionKey(sessionKey);
return normalizeAgentId(parsed?.agentId ?? fallback);
+1
View File
@@ -4,6 +4,7 @@ import type {
AcpRuntimeSessionMode,
} from "@openclaw/acp-core/runtime/types";
/** Cached runtime handle plus the configuration signature that made it reusable. */
export type CachedRuntimeState = {
runtime: AcpRuntime;
handle: AcpRuntimeHandle;
+1
View File
@@ -19,6 +19,7 @@ const MAX_BACKEND_OPTION_VALUE_LENGTH = 512;
const MAX_BACKEND_EXTRAS = 32;
const SAFE_OPTION_KEY_RE = /^[a-z0-9][a-z0-9._:-]*$/i;
// User-facing config aliases accepted by ACP clients and normalized to session runtime options.
const RUNTIME_CONFIG_OPTION_ALIASES = {
model: ["model"],
thinking: ["thinking", "effort", "reasoning_effort", "thought_level"],
@@ -1,5 +1,6 @@
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
/** Per-session async queue that serializes ACP runtime operations and exposes queue depth. */
export class SessionActorQueue {
private readonly queue = new KeyedAsyncQueue();
private readonly pendingBySession = new Map<string, number>();
@@ -26,6 +27,7 @@ export class SessionActorQueue {
this.pendingBySession.set(actorKey, (this.pendingBySession.get(actorKey) ?? 0) + 1);
},
onSettle: () => {
// Keep queue-depth accounting symmetric with enqueue even when operations reject.
const pending = (this.pendingBySession.get(actorKey) ?? 1) - 1;
if (pending <= 0) {
this.pendingBySession.delete(actorKey);
+1
View File
@@ -1,3 +1,4 @@
/** Normalizes ACP conversation identifiers from loose metadata values. */
export function normalizeConversationText(value: unknown): string {
if (typeof value === "string") {
return value.trim();
+1
View File
@@ -13,6 +13,7 @@ import {
} from "./persistent-bindings.types.js";
import { readAcpSessionEntry } from "./runtime/session-meta.js";
// Binding lifecycle keeps configured channel conversations attached to matching ACP sessions.
function sessionMatchesConfiguredBinding(params: {
cfg: OpenClawConfig;
spec: ConfiguredAcpBindingSpec;
+1
View File
@@ -10,6 +10,7 @@ import {
type ResolvedConfiguredAcpBinding,
} from "./persistent-bindings.types.js";
/** Resolves a configured ACP binding for a concrete channel conversation. */
export function resolveConfiguredAcpBindingRecord(params: {
cfg: OpenClawConfig;
channel: string;
@@ -17,6 +17,7 @@ export type AcpRuntimeAdapterContractParams = {
}) => void | Promise<void>;
};
/** Runs the shared behavioral contract for ACP runtime adapters. */
export async function runAcpRuntimeAdapterContract(
params: AcpRuntimeAdapterContractParams,
): Promise<void> {
+1
View File
@@ -2,6 +2,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { isAcpEnabledByPolicy } from "../policy.js";
import { getAcpRuntimeBackend } from "./registry.js";
/** Returns whether ACP runtime spawning is allowed and the selected backend is healthy enough. */
export function isAcpRuntimeSpawnAvailable(params: {
config?: OpenClawConfig;
sandboxed?: boolean;
+1
View File
@@ -1,6 +1,7 @@
import { configureAcpErrorRedactor } from "@openclaw/acp-core";
import { redactSensitiveText } from "../../logging/redact.js";
// Ensure ACP-core runtime errors use OpenClaw's secret redaction before re-export.
configureAcpErrorRedactor(redactSensitiveText);
export * from "@openclaw/acp-core/runtime/errors";
+2
View File
@@ -36,6 +36,7 @@ export type AcpSessionStoreEntry = {
storeReadFailed?: boolean;
};
// ACP metadata lives in SQLite but is keyed through the legacy JSON session store.
type AcpSessionsTable = OpenClawStateKyselyDatabase["acp_sessions"];
type AcpSessionMetaDatabase = Pick<OpenClawStateKyselyDatabase, "acp_sessions">;
type AcpSessionRow = Selectable<AcpSessionsTable>;
@@ -154,6 +155,7 @@ function selectAcpSessionRow(db: DatabaseSync, sessionKey: string): AcpSessionRo
}
function acpSessionRowMatchesEntry(row: AcpSessionRow, entry: SessionEntry | undefined): boolean {
// Rows tied to a specific sessionId are stale after the JSON session entry rotates.
return row.session_id == null || row.session_id === entry?.sessionId;
}
+1
View File
@@ -2,6 +2,7 @@ import { DEFAULT_SECRET_FILE_MAX_BYTES, readSecretFileSync } from "../infra/secr
const MAX_SECRET_FILE_BYTES = DEFAULT_SECRET_FILE_MAX_BYTES;
/** Reads an ACP secret file with the shared secret-file size and symlink policy. */
export function readSecretFromFile(filePath: string, label: string): string {
return readSecretFileSync(filePath, label, {
maxBytes: MAX_SECRET_FILE_BYTES,
+1
View File
@@ -10,6 +10,7 @@ type AcpSessionMeta = {
prefixCwd?: boolean;
};
/** Parses ACP request metadata into OpenClaw session routing hints. */
export function parseSessionMeta(meta: unknown): AcpSessionMeta {
if (!meta || typeof meta !== "object") {
return {};
@@ -12,6 +12,7 @@ import type { GatewayClient } from "../gateway/client.js";
import { AcpGatewayAgent } from "./translator.js";
import { createAcpConnection, createAcpGateway } from "./translator.test-helpers.js";
/** Builds a minimal ACP new-session request for translator tests. */
export function createNewSessionRequest(cwd = "/tmp"): NewSessionRequest {
return {
cwd,
+2
View File
@@ -12,6 +12,7 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coe
import { BASE_THINKING_LEVELS } from "../auto-reply/thinking.shared.js";
import type { GatewaySessionRow } from "../gateway/session-utils.js";
/** ACP config option ids exposed to compatible ACP clients. */
export const ACP_THOUGHT_LEVEL_CONFIG_ID = "thought_level";
export const ACP_FAST_MODE_CONFIG_ID = "fast_mode";
export const ACP_VERBOSE_LEVEL_CONFIG_ID = "verbose_level";
@@ -28,6 +29,7 @@ export type ClientCapabilityState = {
terminal: boolean;
};
/** Gateway session fields needed to build ACP session presentation state. */
export type GatewaySessionPresentationRow = Pick<
GatewaySessionRow,
| "key"
@@ -12,6 +12,7 @@ type PendingPromptHarness = {
runId: string;
};
// Shared prompt harness used by translator cancellation and lifecycle tests.
const DEFAULT_SESSION_ID = "session-1";
export const DEFAULT_SESSION_KEY = "agent:main:main";
const DEFAULT_PROMPT_TEXT = "hello";

Some files were not shown because too many files have changed in this diff Show More