/** * Canon agent verb contract — the canonical, runtime-agnostic vocabulary for * the conversational/HITL actions an agent deliberately takes against Canon * (v1 scope: messaging, HITL interactions, contact sharing, and the read * verbs bindings need alongside them). * * One verb = one intent-level action. Each runtime binding (Hermes native * tool, MCP server, codex dynamicTools, agent-sdk method, CLI) projects these * verbs into its native tool surface; the names, argument shapes, limits, and * result vocabularies defined here are the single source of truth. JSON * Schemas for each verb live in `verbSchemas.ts` and are also emitted as a * plain JSON artifact at build time (`dist/canon-verbs.schema.json`) so * non-TypeScript consumers (the Python hermes plugin, external integrators) * can consume the identical contract. * * Deliberately NOT verbs: replying in the active conversation, streaming * partials, typing, and read receipts stay host-mediated — the model talks * and the platform delivers. `no_reply` is the deliberate exception: it is the * ABSENCE of a reply, and only an intent can express that — silence has no * host-mediated channel of its own. Still out of scope pending their own design * pass: plan approval (the fourth runtime-interaction kind — coding-host * concern today) and block/mute. Their REST surfaces remain directly * callable. * * Scope note: this module DESCRIBES the contract (canonical shapes + the * server-enforced limits, with enforcement sites cited). Enforcement itself * stays where it runs today — functions/src. The server-side verb endpoint that * validates against these schemas is live: POST /agent/verbs/:verb * (functions/src/index.ts, functions/src/api/agentVerbs.ts). * * Normativity: the JSON Schemas in `verbSchemas.ts` (and the emitted * canon-verbs.schema.json) are the normative contract. The TypeScript types * here are a convenience projection — corrections flow schema -> type, and * the dual-witness fixtures in verbContract.test.ts (each fixture is both * compile-checked against the type and validated against the schema) guard * the two from drifting. Single-sourcing the types from the schemas is a * planned follow-up. * * Byte-sensitive limits: JSON Schema `maxLength` counts UTF-16 code units, * but several server limits count UTF-8 bytes or serialized-JSON length, * which no standard keyword expresses. Bindings MUST run * `findVerbByteLimitViolations()` (or equivalent checks from the emitted * canon-verbs.limits.json) after schema validation. */ /** Identifier for this contract document. */ export declare const CANON_VERBS_SCHEMA_VERSION = "canon.verbs.v1"; /** $id of the emitted JSON Schema bundle. */ export declare const CANON_VERBS_SCHEMA_ID = "https://canonmsg.com/schemas/canon.verbs.v1.json"; /** Namespace prefix bindings should use for flat tool names (e.g. `canon_send_to`). */ export declare const CANON_VERB_NAMESPACE = "canon"; /** * $id of the canonical canon.card.v1 document schema * (@canonmsg/rich-cards RUNTIME_CARD_JSON_SCHEMA_V1). The verbs bundle only * validates the card ENVELOPE; compose the full document schema into card * verbs via `getVerbInputSchema(verb, { cardSchema })`. */ export declare const CANON_CARD_SCHEMA_ID = "https://canonmsg.com/schemas/canon.card.v1.json"; /** * Identifier patterns, mirrored from the enforcing sites: * - RUNTIME_ID_PATTERN: conversation/input/approval ids — * functions/src/utils/runtimeRequestHelpers.ts (`/^[A-Za-z0-9_.:-]{1,160}$/`) * - CARD_ID_PATTERN / ACTION_ID_PATTERN: functions/src/api/interactionCard.ts * and @canonmsg/rich-cards RUNTIME_CARD_ACTION_ID_PATTERN (80 chars) * - QUESTION_ID_PATTERN: functions/src/api/interactionInput.ts (120 chars) * - SESSION_RULE_TOOL_PATTERN: functions/src/callable/respondToInteraction.ts */ export declare const VERB_ID_PATTERNS: { readonly runtimeId: "^[A-Za-z0-9_.:-]{1,160}$"; readonly cardId: "^[A-Za-z0-9_.:-]{1,80}$"; readonly actionId: "^[A-Za-z0-9_.:-]{1,80}$"; readonly questionId: "^[A-Za-z0-9_.:-]{1,120}$"; readonly sessionRuleToolPattern: "^[\\w.*:-]{1,128}$"; readonly runtimeCorrelationValue: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly runtimeMethod: "^[A-Za-z0-9_][A-Za-z0-9_./:\\-]{0,255}$"; readonly runtimeHandleKey: "^[A-Za-z0-9_.:-]{1,80}$"; /** UUID issued by Canon's resumable-media session endpoint. */ readonly resumableUploadId: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"; }; /** * Server-enforced limits, single-sourced. Each value cites its enforcement * site; keep the citation current when a limit moves. */ export declare const VERB_LIMITS: { /** Message text — UTF-8 bytes (functions/src/api/sendMessage.ts MAX_MESSAGE_TEXT_BYTES). */ readonly messageTextBytes: 4096; /** * Serialized metadata JSON length in UTF-16 code units — the server checks * JSON.stringify(metadata).length, not bytes (sendMessage.ts:716-724 inline; * parseBody.ts maxJsonBytes despite the name). */ readonly messageMetadataJsonChars: 4096; /** Attachments per message (sendMessage.ts MAX_MESSAGE_ATTACHMENTS). */ readonly messageAttachments: 10; /** Client-supplied messageId — chars and UTF-8 bytes (sendMessage.ts). */ readonly messageIdChars: 160; readonly messageIdBytes: 256; /** Self-context note (functions/src/utils/selfContexts.ts SELF_CONTEXT_CONTEXT_LIMIT). */ readonly selfContextChars: 1000; /** * Contact-request note. Values longer than this are truncated by senders * (slice(0,497)+'...') — the truncation is currently triplicated in * functions/src/api/sendContextualMessage.ts, packages/core/src/reach-out.ts * and the hermes plugin; this constant is the canonical figure. */ readonly contactRequestNoteChars: 500; /** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */ readonly groupMembers: 50; /** Runtime input (functions/src/api/interactionInput.ts). */ readonly inputTitleChars: 160; readonly inputPromptChars: 4000; readonly inputChoices: 12; readonly inputChoiceLabelChars: 120; readonly inputChoiceValueChars: 200; readonly inputChoiceDescriptionChars: 300; readonly inputQuestions: 12; readonly inputQuestionChars: 1000; readonly inputQuestionHeaderChars: 120; readonly inputSecretNameChars: 160; readonly inputAnswerChars: 8192; /** Approval (functions/src/api/interactionApproval.ts). */ readonly toolNameChars: 128; readonly toolSummaryChars: 1000; readonly approvalDetails: 8; readonly approvalDetailLabelChars: 80; readonly approvalDetailValueChars: 500; readonly diffFiles: 100; readonly diffPathChars: 1024; readonly diffFileBytes: number; readonly diffTotalBytes: number; /** Card envelope acceptance (functions/src/api/interactionCard.ts — server caps; * authoring caps in @canonmsg/rich-cards are stricter: title 120, fallback 500, blocks 24). */ readonly cardEnvelopeBytes: number; readonly cardServerTitleChars: 200; readonly cardServerFallbackTextChars: 2000; readonly cardServerBlocks: 64; /** Response-values caps enforced on submit (callable respondToInteraction.ts * MAX_VALUES_BYTES/MAX_VALUES_DEPTH; interactionCard.ts re-checks bytes on * consume via MAX_REPLY_BYTES). */ readonly cardValuesBytes: number; readonly cardValuesDepth: 8; /** Native correlation metadata (functions/src/api/interactionKinds.ts normalizeNative). */ readonly nativeKeys: 24; readonly nativeValueChars: 256; readonly nativeHandles: 16; /** Deadlines (functions/src/utils/runtimeRequestHelpers.ts). */ readonly minTimeoutMs: 1000; /** input/card/plan ceiling (30 minutes). */ readonly maxTimeoutMs: number; /** approval-only ceiling (72 hours — owner ruling 2026-07-10). */ readonly maxApprovalTimeoutMs: number; /** turnId / runtimeId fields on interaction creators. */ readonly turnIdChars: 128; /** Reaction key (functions/src/api/reactToMessage.ts normalizeReactionKey). */ readonly reactionEmojiChars: 64; /** * Group name authoring cap (enforced on rename via updateNameServer; group * CREATE does not length-check today — treat as the authoring contract). */ readonly groupNameChars: 100; /** * `no_reply.reason` authoring cap. There is no server enforcement beyond the * intent schema — the handler logs the reason's presence and nothing else — * so this constant is the canonical figure. */ readonly noReplyReasonChars: 500; }; /** * Sender-side rate limits enforced by POST /messages/send * (functions/src/api/sendMessage.ts). Bindings should surface 429s with the * retryAfter the server returns rather than re-deriving these. */ export declare const VERB_RATE_LIMITS: { readonly senderMessagesPer5Min: 300; readonly conversationMessagesPer5Min: 120; readonly agentPeerMessagesPerHour: 30; }; /** The one self-context type the platform accepts today. */ export declare const SELF_CONTEXT_TYPE = "cross_session"; /** Canonical verb names. */ export declare const CANON_VERB_NAMES: readonly ["send_to", "request_input", "request_approval", "check_approval", "send_card", "request_card", "share_contact", "react", "forward", "create_group", "add_member", "remove_member", "leave_conversation", "list_contacts", "list_conversations", "no_reply"]; export type CanonVerbName = (typeof CANON_VERB_NAMES)[number]; /** Private note-to-self attached to a cross-conversation send. */ export interface VerbSelfContext { type: typeof SELF_CONTEXT_TYPE; /** <= VERB_LIMITS.selfContextChars. Visible only to the sending agent. */ context: string; } export type VerbSessionSelection = { mode: 'new'; } | { mode: 'continue_latest'; } | { mode: 'continue_or_create'; } | { mode: 'specific'; conversationId: string; }; /** * Bounded runtime correlation carried in the public wire envelope. These are * opaque identifiers only; workspace paths, commands, prompts, summaries, * and model labels belong in encrypted content or owner-local runtime state. */ export interface VerbNativeMetadata { runtime?: string; method?: string; requestId?: string; provider?: string; origin?: string; surface?: string; threadId?: string; turnId?: string; runId?: string; itemId?: string; toolCallId?: string; approvalId?: string; pluginId?: string; sessionKey?: string; nodeId?: string; handles?: Record; } export declare const VERB_NATIVE_METADATA_KEYS: readonly ["runtime", "method", "requestId", "provider", "origin", "surface", "threadId", "turnId", "runId", "itemId", "toolCallId", "approvalId", "pluginId", "sessionKey", "nodeId", "handles"]; /** * Keep only the typed, content-free runtime correlation contract. Invalid or * unknown fields are omitted on compatibility REST paths; canon.verb-wire.v1 * rejects the same fields through its strict JSON Schema. */ export declare function normalizeVerbNativeMetadata(value: unknown): VerbNativeMetadata | undefined; export interface VerbMediaAttachment { kind: 'image' | 'audio' | 'video' | 'file'; /** Must come from a canonical Canon upload/finalization response or the GIF picker. */ url: string; /** Server-issued resumable identity; preserve it so durable sends retain temporary media. */ uploadId?: string; mimeType?: string; fileName?: string; sizeBytes?: number; width?: number; height?: number; durationMs?: number; thumbnailUrl?: string; processingStatus?: 'processing' | 'ready' | 'failed'; processingErrorCode?: 'invalid_video' | 'video_limits_exceeded' | 'video_processing_failed'; } /** * Well-known turn-protocol keys inside the free-form metadata envelope * (see turnProtocol.ts TurnMetadata — the authoritative type). */ export interface VerbTurnMetadata { turnId?: string | null; turnSemantics?: 'progress' | 'turn_complete' | 'control'; deliveryIntent?: 'queue' | 'interrupt' | 'interleave' | 'stop'; replyBehavior?: 'allow_auto_reply' | 'suppress_auto_reply'; [key: string]: unknown; } /** Message composition options shared by send_to (mirrors POST /messages/send). */ export interface VerbMessageOptions { messageId?: string; contentType?: 'text' | 'image' | 'audio' | 'video' | 'file'; attachments?: VerbMediaAttachment[]; mentions?: string[]; replyTo?: string; replyToPosition?: number; metadata?: VerbTurnMetadata; } /** * Message another conversation or user (admission-aware reach-out), optionally * carrying a private self-context so the agent recognizes its own transfer in * both sessions. Merges today's reach_out / send_contextual_message / * cross-conversation send into one verb. */ export interface SendToInput { /** Exactly one of targetConversationId / targetUserId / canonContactId. */ targetConversationId?: string; targetUserId?: string; /** * Contact-card identity. NOT a wire field on any send endpoint: bindings * resolve it to a targetUserId via POST /admission/resolve first (the * two-step core reachOutToCanonContact runs). */ canonContactId?: string; /** Required unless messageOptions.attachments carries the content. */ text?: string; /** * The conversation this send originates from. Required when selfContext is * present (the self-context links source -> target). */ sourceConversationId?: string; selfContext?: VerbSelfContext; /** Contact-request note when admission requires approval; defaults to text. */ requestMessage?: string; /** Only meaningful for user targets that are agents. Default: continue_or_create. */ sessionSelection?: VerbSessionSelection; messageOptions?: VerbMessageOptions; } export type SendToResult = { status: 'messaged'; conversationId: string; messageId?: string; selfContextId?: string; created?: boolean; reused?: boolean; sessionSelection?: string; } | { status: 'requested'; requestId: string | null; deferredIntentId?: string | null; } | { status: 'pending'; requestId: string | null; deferredIntentId?: string | null; } | { status: 'setup_required'; reason: string; } | { status: 'no_session'; reason: string; } | { status: 'blocked'; reason: string; } | { status: 'unavailable'; reason: string; }; export type VerbInputKind = 'clarify' | 'sudo' | 'secret'; export interface VerbInputChoice { label: string; value?: string; description?: string; } export interface VerbInputQuestion { id: string; question: string; header?: string; choices?: VerbInputChoice[]; allowOther?: boolean; isSecret?: boolean; multiSelect?: boolean; } /** * Ask a human a structured question mid-turn (HITL input card). `sudo`, * `secret`, `sensitive:true`, and any `isSecret` question force owner-only * routing server-side; bindings must not let the model redirect the responder * for those. */ export interface RequestInputInput { /** Bindings default this to the active conversation. */ conversationId?: string; /** Durable single-use id; generated by the binding when omitted. */ inputId?: string; kind?: VerbInputKind; title?: string; prompt?: string; choices?: VerbInputChoice[]; questions?: VerbInputQuestion[]; secretName?: string; sensitive?: boolean; responseUserId?: string; native?: VerbNativeMetadata; turnId?: string; /** Relative deadline; server ceiling is VERB_LIMITS.maxTimeoutMs (30 min). */ timeoutMs?: number; /** Absolute epoch-ms deadline; wins over timeoutMs when both given. */ expiresAt?: number; } export type RequestInputResult = { status: 'submitted'; inputId: string; value: string; answers?: Record; } | { status: 'cancelled'; inputId: string; } | { status: 'timeout'; inputId: string; }; export type VerbApprovalRisk = 'low' | 'normal' | 'high' | 'destructive'; export type VerbApprovalCategory = 'command' | 'file' | 'network' | 'browser' | 'mcp' | 'plugin' | 'canon' | 'tool'; export interface VerbApprovalDetail { label: string; value: string; monospace?: boolean; } export interface VerbUnifiedDiffFile { path: string; status: 'modified' | 'created' | 'deleted' | 'renamed'; oldPath?: string; additions?: number; deletions?: number; diff?: string; suppressed?: boolean; } export interface VerbUnifiedDiff { files: VerbUnifiedDiffFile[]; truncated?: boolean; } export interface VerbSessionRule { type: 'approve-all' | 'approve-tool' | 'deny-tool'; toolPattern?: string; expiresAt?: string | null; } /** * Ask a human to allow or deny an action. `mode: 'blocking'` waits for the * decision inside the verb call (default timeout 5 min); `mode: 'detached'` * returns `pending` immediately and the decision is fetched later with * check_approval (ceiling 72h). Timeouts fail closed to deny. * * Binding-local options are deliberately not wire fields: session-rule caches * and their bypass (agent-sdk/core `ignoreSessionRules`) live in the binding, * never on the wire — the server has no such field. */ export interface RequestApprovalInput { conversationId?: string; /** Server-generated when omitted. Durable single-use id. */ approvalId?: string; toolName: string; toolSummary: string; mode?: 'blocking' | 'detached'; riskLevel?: 'normal' | 'destructive'; risk?: VerbApprovalRisk; category?: VerbApprovalCategory; details?: VerbApprovalDetail[]; diff?: VerbUnifiedDiff; native?: VerbNativeMetadata; runtimeId?: string; turnId?: string; responseUserId?: string; /** Server forces false when the responder is not the agent owner. */ allowSessionRule?: boolean; timeoutMs?: number; expiresAt?: number; } export type RequestApprovalResult = { status: 'allow'; approvalId: string; sessionRule?: VerbSessionRule; respondedBy?: string; } | { status: 'deny'; approvalId: string; sessionRule?: VerbSessionRule; respondedBy?: string; } | { status: 'timeout'; approvalId: string; } | { status: 'pending'; approvalId: string; conversationId?: string; expiresAt: number; responseUserId?: string; }; export interface CheckApprovalInput { approvalId: string; /** Bindings must bind checks to the conversation that created the approval. */ conversationId?: string; } /** * `unknown` is NOT a denial — it means the id is unrecognized (consumed * tombstone expired, or foreign id). Callers must treat only `resolved` with * `decision` as an answer. */ export type CheckApprovalResult = { status: 'resolved'; approvalId: string; decision: 'allow' | 'deny'; respondedBy?: string; conversationId?: string; } | { status: 'pending'; approvalId: string; expiresAt?: number; conversationId?: string; } | { status: 'expired'; approvalId: string; conversationId?: string; } | { status: 'unknown'; approvalId: string; }; /** * A canon.card.v1 document. The full document contract is * @canonmsg/rich-cards RUNTIME_CARD_JSON_SCHEMA_V1 * ($id https://canonmsg.com/schemas/canon.card.v1.json) — authoring caps * title 120 / fallbackText 500 / blocks 24; the server envelope accepts up to * 200 / 2000 / 64 and 32 KiB total (interactionCard.ts validateCardEnvelope). */ export interface VerbRuntimeCard { schema: 'canon.card.v1'; cardId?: string; title: string; fallbackText: string; /** At least one block (schema enforces minItems: 1). */ blocks: unknown[]; [key: string]: unknown; } /** Display a card with no actions — fire-and-forget, no pending state. */ export interface SendCardInput { conversationId?: string; card: VerbRuntimeCard; cardId?: string; native?: VerbNativeMetadata; runtimeId?: string; turnId?: string; } export interface SendCardResult { status: 'displayed'; cardId: string; conversationId?: string; responseUserId?: string; } /** Show an interactive card (>=1 actions block) and wait for the response. */ export interface RequestCardInput extends SendCardInput { responseUserId?: string; timeoutMs?: number; expiresAt?: number; } export type RequestCardResult = { status: 'submitted'; cardId: string; actionId?: string; values?: Record; /** Canon-authenticated responder (server-verified against responseUserId). */ respondedBy?: string; } | { status: 'cancelled'; cardId: string; respondedBy?: string; } | { status: 'timeout'; cardId: string; }; /** * Share a contact card into a conversation (POST /messages/send with * contentType 'contact_card'). The shared user must be in the sending * agent's contacts (403 otherwise); the owner shortcut in sendMessage.ts * applies only to human senders sharing their own agents. The server * snapshots the card. */ export interface ShareContactInput { conversationId: string; contactUserId: string; text?: string; messageId?: string; } export interface ShareContactResult { status: 'shared'; messageId: string; } /** Toggle an emoji reaction on a message (idempotent toggle server-side). */ export interface ReactInput { conversationId: string; messageId: string; /** <= VERB_LIMITS.reactionEmojiChars after normalization. Content-adjacent * (open decision D5: MLS messengers encrypt reactions) — rides the body. */ emoji: string; } export interface ReactResult { status: 'reacted'; action: 'added' | 'removed'; reactions?: Record; } /** Forward an existing message into another conversation. Under E2EE this * becomes a client-side re-encrypt; the wire verb carries only routing ids * plus an optional caption. */ export interface ForwardInput { sourceConversationId: string; targetConversationId: string; messageId: string; /** Optional caption; <= VERB_LIMITS.messageTextBytes UTF-8 bytes. */ text?: string; } export interface ForwardResult { status: 'forwarded'; messageId: string; targetConversationId: string; forwardedFrom?: unknown; } export interface CreateGroupInput { /** Optional conversation title (authoring cap VERB_LIMITS.groupNameChars). */ name?: string; /** Other members; the caller is added automatically. Cap * VERB_LIMITS.groupMembers including the creator. Each target's * groupJoinPolicy is enforced server-side. */ memberIds: string[]; } export interface PendingGroupInviteResult { userId: string; requestId: string; } export interface CreateGroupResult { status: 'created'; conversationId: string; /** Members admitted synchronously during creation. */ added: string[]; /** group_invite requests awaiting exact policy approval. */ pending: PendingGroupInviteResult[]; /** Members the staged create could not admit: hard policy denials * (closed, blocked, inactive, not-found, …). */ skipped: Array<{ userId: string; reason: string; }>; } /** * Error code on the 400 a create_group receives when no requested member can * be added or invited. A creator-only group with at least one valid pending * invite is allowed; the error detail carries the attempted partition. */ export declare const CREATE_GROUP_NO_ADDABLE_MEMBERS_CODE = "CREATE_GROUP_NO_ADDABLE_MEMBERS"; export interface AddMemberInput { conversationId: string; userId: string; } /** Policy approval yields one exact pending group invite. */ export type AddMemberResult = { status: 'added'; } | { status: 'pending'; requestId: string; }; export interface RemoveMemberInput { conversationId: string; userId: string; } export interface RemoveMemberResult { status: 'removed'; } export interface LeaveConversationInput { conversationId: string; } export interface LeaveConversationResult { status: 'left'; } export type ListContactsInput = Record; export interface VerbContact { /** The contact's userId. */ id: string; /** Vocabulary: direct_add | phone_book | contact_request | link | qr | group | open_inbound_message | unknown (server passes strings through). */ source: string; addedAt: string | null; displayNameOverride: string | null; } export interface ListContactsResult { contacts: VerbContact[]; } export interface ListConversationsInput { /** Optional client-side cap; the REST endpoint returns all memberships. */ limit?: number; } export interface VerbConversationSummary { id: string; type: 'direct' | 'group'; /** Raw passthrough — may be absent entirely on direct chats. */ name?: string | null; topic: string | null; memberIds: string[]; membershipModel?: 'unified'; membershipRevision?: number; participantTypes?: Record; participantSummary?: { humanCount: number; agentCount: number; totalCount: number; }; runtimeSessionKind?: 'direct' | 'group'; admissionId?: string | null; historyStartAt?: string | null; isAgentChat: boolean; hasUnread?: boolean; lastMessage: { text: string | null; messageId?: string; senderId: string; senderType: string; contentType?: string; timestamp: string | null; } | null; createdAt: string | null; } export interface ListConversationsResult { conversations: VerbConversationSummary[]; } /** Deliberate silence: end the turn without posting anything. */ export interface NoReplyInput { /** Bindings default this to the active conversation when they have one. */ conversationId?: string; /** Private rationale — logged only, never rendered. <= noReplyReasonChars. */ reason?: string; } export interface NoReplyResult { status: 'acknowledged'; conversationId?: string; note?: string; } /** * The `note` the server returns on a `no_reply` ack — the model's closure * sentence. It lives here, not in a host, because every binding surfaces the * server's result verbatim and a runtime that answers `no_reply` locally must * say the same thing. */ export declare const NO_REPLY_ACK_NOTE = "Acknowledged \u2014 nothing was posted to the conversation."; /** Flat tool name for a verb on namespaced tool surfaces (e.g. `canon_send_to`). */ export declare function canonVerbToolName(verb: CanonVerbName): string; export interface VerbLimitViolation { path: string; message: string; } /** * The server limits JSON Schema cannot express: UTF-8 byte caps and * serialized-JSON length caps. Bindings MUST run this after schema * validation; the server enforces the same checks with 400s. */ export declare function findVerbByteLimitViolations(verb: CanonVerbName, input: Record): VerbLimitViolation[]; /** * Machine-readable limits companion to the schema bundle, emitted as * dist/canon-verbs.limits.json so non-TypeScript bindings can apply the * byte-sensitive checks JSON Schema cannot express. */ export declare const CANON_VERB_LIMITS_ARTIFACT: { readonly schemaVersion: "canon.verbs.v1"; readonly limits: { /** Message text — UTF-8 bytes (functions/src/api/sendMessage.ts MAX_MESSAGE_TEXT_BYTES). */ readonly messageTextBytes: 4096; /** * Serialized metadata JSON length in UTF-16 code units — the server checks * JSON.stringify(metadata).length, not bytes (sendMessage.ts:716-724 inline; * parseBody.ts maxJsonBytes despite the name). */ readonly messageMetadataJsonChars: 4096; /** Attachments per message (sendMessage.ts MAX_MESSAGE_ATTACHMENTS). */ readonly messageAttachments: 10; /** Client-supplied messageId — chars and UTF-8 bytes (sendMessage.ts). */ readonly messageIdChars: 160; readonly messageIdBytes: 256; /** Self-context note (functions/src/utils/selfContexts.ts SELF_CONTEXT_CONTEXT_LIMIT). */ readonly selfContextChars: 1000; /** * Contact-request note. Values longer than this are truncated by senders * (slice(0,497)+'...') — the truncation is currently triplicated in * functions/src/api/sendContextualMessage.ts, packages/core/src/reach-out.ts * and the hermes plugin; this constant is the canonical figure. */ readonly contactRequestNoteChars: 500; /** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */ readonly groupMembers: 50; /** Runtime input (functions/src/api/interactionInput.ts). */ readonly inputTitleChars: 160; readonly inputPromptChars: 4000; readonly inputChoices: 12; readonly inputChoiceLabelChars: 120; readonly inputChoiceValueChars: 200; readonly inputChoiceDescriptionChars: 300; readonly inputQuestions: 12; readonly inputQuestionChars: 1000; readonly inputQuestionHeaderChars: 120; readonly inputSecretNameChars: 160; readonly inputAnswerChars: 8192; /** Approval (functions/src/api/interactionApproval.ts). */ readonly toolNameChars: 128; readonly toolSummaryChars: 1000; readonly approvalDetails: 8; readonly approvalDetailLabelChars: 80; readonly approvalDetailValueChars: 500; readonly diffFiles: 100; readonly diffPathChars: 1024; readonly diffFileBytes: number; readonly diffTotalBytes: number; /** Card envelope acceptance (functions/src/api/interactionCard.ts — server caps; * authoring caps in @canonmsg/rich-cards are stricter: title 120, fallback 500, blocks 24). */ readonly cardEnvelopeBytes: number; readonly cardServerTitleChars: 200; readonly cardServerFallbackTextChars: 2000; readonly cardServerBlocks: 64; /** Response-values caps enforced on submit (callable respondToInteraction.ts * MAX_VALUES_BYTES/MAX_VALUES_DEPTH; interactionCard.ts re-checks bytes on * consume via MAX_REPLY_BYTES). */ readonly cardValuesBytes: number; readonly cardValuesDepth: 8; /** Native correlation metadata (functions/src/api/interactionKinds.ts normalizeNative). */ readonly nativeKeys: 24; readonly nativeValueChars: 256; readonly nativeHandles: 16; /** Deadlines (functions/src/utils/runtimeRequestHelpers.ts). */ readonly minTimeoutMs: 1000; /** input/card/plan ceiling (30 minutes). */ readonly maxTimeoutMs: number; /** approval-only ceiling (72 hours — owner ruling 2026-07-10). */ readonly maxApprovalTimeoutMs: number; /** turnId / runtimeId fields on interaction creators. */ readonly turnIdChars: 128; /** Reaction key (functions/src/api/reactToMessage.ts normalizeReactionKey). */ readonly reactionEmojiChars: 64; /** * Group name authoring cap (enforced on rename via updateNameServer; group * CREATE does not length-check today — treat as the authoring contract). */ readonly groupNameChars: 100; /** * `no_reply.reason` authoring cap. There is no server enforcement beyond the * intent schema — the handler logs the reason's presence and nothing else — * so this constant is the canonical figure. */ readonly noReplyReasonChars: 500; }; readonly rateLimits: { readonly senderMessagesPer5Min: 300; readonly conversationMessagesPer5Min: 120; readonly agentPeerMessagesPerHour: 30; }; readonly idPatterns: { readonly runtimeId: "^[A-Za-z0-9_.:-]{1,160}$"; readonly cardId: "^[A-Za-z0-9_.:-]{1,80}$"; readonly actionId: "^[A-Za-z0-9_.:-]{1,80}$"; readonly questionId: "^[A-Za-z0-9_.:-]{1,120}$"; readonly sessionRuleToolPattern: "^[\\w.*:-]{1,128}$"; readonly runtimeCorrelationValue: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$"; readonly runtimeMethod: "^[A-Za-z0-9_][A-Za-z0-9_./:\\-]{0,255}$"; readonly runtimeHandleKey: "^[A-Za-z0-9_.:-]{1,80}$"; /** UUID issued by Canon's resumable-media session endpoint. */ readonly resumableUploadId: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"; }; readonly byteSemantics: { readonly 'send_to.text': "utf8_bytes<=messageTextBytes"; readonly 'share_contact.text': "utf8_bytes<=messageTextBytes"; readonly 'forward.text': "utf8_bytes<=messageTextBytes"; readonly 'send_to.messageOptions.messageId': "utf8_bytes<=messageIdBytes"; readonly 'share_contact.messageId': "utf8_bytes<=messageIdBytes"; readonly 'send_to.messageOptions.metadata': "json_stringify_chars<=messageMetadataJsonChars"; readonly 'send_card.card': "json_utf8_bytes<=cardEnvelopeBytes"; readonly 'request_card.card': "json_utf8_bytes<=cardEnvelopeBytes"; }; };