@canonmsg/backend-contracts 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canon-verb-wire.schema.json +367 -0
- package/dist/canon-verbs.limits.json +70 -0
- package/dist/canon-verbs.schema.json +1744 -0
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/verbContract.js +288 -0
- package/dist/cjs/verbSchemas.js +1144 -0
- package/dist/cjs/verbWire.js +634 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/verbContract.d.ts +760 -0
- package/dist/verbContract.js +283 -0
- package/dist/verbSchemas.d.ts +1460 -0
- package/dist/verbSchemas.js +1139 -0
- package/dist/verbWire.d.ts +405 -0
- package/dist/verbWire.js +628 -0
- package/package.json +8 -3
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canon agent verb contract — the canonical, runtime-agnostic vocabulary for
|
|
3
|
+
* the conversational/HITL actions an agent deliberately takes against Canon
|
|
4
|
+
* (v1 scope: messaging, HITL interactions, contact sharing, and the read
|
|
5
|
+
* verbs bindings need alongside them).
|
|
6
|
+
*
|
|
7
|
+
* One verb = one intent-level action. Each runtime binding (Hermes native
|
|
8
|
+
* tool, MCP server, codex dynamicTools, agent-sdk method, CLI) projects these
|
|
9
|
+
* verbs into its native tool surface; the names, argument shapes, limits, and
|
|
10
|
+
* result vocabularies defined here are the single source of truth. JSON
|
|
11
|
+
* Schemas for each verb live in `verbSchemas.ts` and are also emitted as a
|
|
12
|
+
* plain JSON artifact at build time (`dist/canon-verbs.schema.json`) so
|
|
13
|
+
* non-TypeScript consumers (the Python hermes plugin, external integrators)
|
|
14
|
+
* can consume the identical contract.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately NOT verbs: replying in the active conversation, streaming
|
|
17
|
+
* partials, typing, and read receipts stay host-mediated — the model talks
|
|
18
|
+
* and the platform delivers. Still out of scope pending their own design
|
|
19
|
+
* pass: plan approval (the fourth runtime-interaction kind — coding-host
|
|
20
|
+
* concern today) and block/mute. Their REST surfaces remain directly
|
|
21
|
+
* callable.
|
|
22
|
+
*
|
|
23
|
+
* Scope note: this module DESCRIBES the contract (canonical shapes + the
|
|
24
|
+
* server-enforced limits, with enforcement sites cited). Enforcement itself
|
|
25
|
+
* stays where it runs today — functions/src. Server-side verb endpoints that
|
|
26
|
+
* validate against these schemas are a planned follow-up.
|
|
27
|
+
*
|
|
28
|
+
* Normativity: the JSON Schemas in `verbSchemas.ts` (and the emitted
|
|
29
|
+
* canon-verbs.schema.json) are the normative contract. The TypeScript types
|
|
30
|
+
* here are a convenience projection — corrections flow schema -> type, and
|
|
31
|
+
* the dual-witness fixtures in verbContract.test.ts (each fixture is both
|
|
32
|
+
* compile-checked against the type and validated against the schema) guard
|
|
33
|
+
* the two from drifting. Single-sourcing the types from the schemas is a
|
|
34
|
+
* planned follow-up.
|
|
35
|
+
*
|
|
36
|
+
* Byte-sensitive limits: JSON Schema `maxLength` counts UTF-16 code units,
|
|
37
|
+
* but several server limits count UTF-8 bytes or serialized-JSON length,
|
|
38
|
+
* which no standard keyword expresses. Bindings MUST run
|
|
39
|
+
* `findVerbByteLimitViolations()` (or equivalent checks from the emitted
|
|
40
|
+
* canon-verbs.limits.json) after schema validation.
|
|
41
|
+
*/
|
|
42
|
+
/** Identifier for this contract document. */
|
|
43
|
+
export declare const CANON_VERBS_SCHEMA_VERSION = "canon.verbs.v1";
|
|
44
|
+
/** $id of the emitted JSON Schema bundle. */
|
|
45
|
+
export declare const CANON_VERBS_SCHEMA_ID = "https://canonmsg.com/schemas/canon.verbs.v1.json";
|
|
46
|
+
/** Namespace prefix bindings should use for flat tool names (e.g. `canon_send_to`). */
|
|
47
|
+
export declare const CANON_VERB_NAMESPACE = "canon";
|
|
48
|
+
/**
|
|
49
|
+
* $id of the canonical canon.card.v1 document schema
|
|
50
|
+
* (@canonmsg/rich-cards RUNTIME_CARD_JSON_SCHEMA_V1). The verbs bundle only
|
|
51
|
+
* validates the card ENVELOPE; compose the full document schema into card
|
|
52
|
+
* verbs via `getVerbInputSchema(verb, { cardSchema })`.
|
|
53
|
+
*/
|
|
54
|
+
export declare const CANON_CARD_SCHEMA_ID = "https://canonmsg.com/schemas/canon.card.v1.json";
|
|
55
|
+
/**
|
|
56
|
+
* Identifier patterns, mirrored from the enforcing sites:
|
|
57
|
+
* - RUNTIME_ID_PATTERN: conversation/input/approval ids —
|
|
58
|
+
* functions/src/utils/runtimeRequestHelpers.ts (`/^[A-Za-z0-9_.:-]{1,160}$/`)
|
|
59
|
+
* - CARD_ID_PATTERN / ACTION_ID_PATTERN: functions/src/api/interactionCard.ts
|
|
60
|
+
* and @canonmsg/rich-cards RUNTIME_CARD_ACTION_ID_PATTERN (80 chars)
|
|
61
|
+
* - QUESTION_ID_PATTERN: functions/src/api/interactionInput.ts (120 chars)
|
|
62
|
+
* - SESSION_RULE_TOOL_PATTERN: functions/src/callable/respondToInteraction.ts
|
|
63
|
+
*/
|
|
64
|
+
export declare const VERB_ID_PATTERNS: {
|
|
65
|
+
readonly runtimeId: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
66
|
+
readonly cardId: "^[A-Za-z0-9_.:-]{1,80}$";
|
|
67
|
+
readonly actionId: "^[A-Za-z0-9_.:-]{1,80}$";
|
|
68
|
+
readonly questionId: "^[A-Za-z0-9_.:-]{1,120}$";
|
|
69
|
+
readonly sessionRuleToolPattern: "^[\\w.*:-]{1,128}$";
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Server-enforced limits, single-sourced. Each value cites its enforcement
|
|
73
|
+
* site; keep the citation current when a limit moves.
|
|
74
|
+
*/
|
|
75
|
+
export declare const VERB_LIMITS: {
|
|
76
|
+
/** Message text — UTF-8 bytes (functions/src/api/sendMessage.ts MAX_MESSAGE_TEXT_BYTES). */
|
|
77
|
+
readonly messageTextBytes: 4096;
|
|
78
|
+
/**
|
|
79
|
+
* Serialized metadata JSON length in UTF-16 code units — the server checks
|
|
80
|
+
* JSON.stringify(metadata).length, not bytes (sendMessage.ts:716-724 inline;
|
|
81
|
+
* parseBody.ts maxJsonBytes despite the name).
|
|
82
|
+
*/
|
|
83
|
+
readonly messageMetadataJsonChars: 4096;
|
|
84
|
+
/** Attachments per message (sendMessage.ts MAX_MESSAGE_ATTACHMENTS). */
|
|
85
|
+
readonly messageAttachments: 10;
|
|
86
|
+
/** Client-supplied messageId — chars and UTF-8 bytes (sendMessage.ts). */
|
|
87
|
+
readonly messageIdChars: 160;
|
|
88
|
+
readonly messageIdBytes: 256;
|
|
89
|
+
/** Self-context note (functions/src/utils/selfContexts.ts SELF_CONTEXT_CONTEXT_LIMIT). */
|
|
90
|
+
readonly selfContextChars: 1000;
|
|
91
|
+
/**
|
|
92
|
+
* Contact-request note. Values longer than this are truncated by senders
|
|
93
|
+
* (slice(0,497)+'...') — the truncation is currently triplicated in
|
|
94
|
+
* functions/src/api/sendContextualMessage.ts, packages/core/src/reach-out.ts
|
|
95
|
+
* and the hermes plugin; this constant is the canonical figure.
|
|
96
|
+
*/
|
|
97
|
+
readonly contactRequestNoteChars: 500;
|
|
98
|
+
/** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */
|
|
99
|
+
readonly groupMembers: 50;
|
|
100
|
+
/** Runtime input (functions/src/api/interactionInput.ts). */
|
|
101
|
+
readonly inputTitleChars: 160;
|
|
102
|
+
readonly inputPromptChars: 4000;
|
|
103
|
+
readonly inputChoices: 12;
|
|
104
|
+
readonly inputChoiceLabelChars: 120;
|
|
105
|
+
readonly inputChoiceValueChars: 200;
|
|
106
|
+
readonly inputChoiceDescriptionChars: 300;
|
|
107
|
+
readonly inputQuestions: 12;
|
|
108
|
+
readonly inputQuestionChars: 1000;
|
|
109
|
+
readonly inputQuestionHeaderChars: 120;
|
|
110
|
+
readonly inputSecretNameChars: 160;
|
|
111
|
+
readonly inputAnswerChars: 8192;
|
|
112
|
+
/** Approval (functions/src/api/interactionApproval.ts). */
|
|
113
|
+
readonly toolNameChars: 128;
|
|
114
|
+
readonly toolSummaryChars: 1000;
|
|
115
|
+
readonly approvalDetails: 8;
|
|
116
|
+
readonly approvalDetailLabelChars: 80;
|
|
117
|
+
readonly approvalDetailValueChars: 500;
|
|
118
|
+
readonly diffFiles: 100;
|
|
119
|
+
readonly diffPathChars: 1024;
|
|
120
|
+
readonly diffFileBytes: number;
|
|
121
|
+
readonly diffTotalBytes: number;
|
|
122
|
+
/** Card envelope acceptance (functions/src/api/interactionCard.ts — server caps;
|
|
123
|
+
* authoring caps in @canonmsg/rich-cards are stricter: title 120, fallback 500, blocks 24). */
|
|
124
|
+
readonly cardEnvelopeBytes: number;
|
|
125
|
+
readonly cardServerTitleChars: 200;
|
|
126
|
+
readonly cardServerFallbackTextChars: 2000;
|
|
127
|
+
readonly cardServerBlocks: 64;
|
|
128
|
+
/** Response-values caps enforced on submit (callable respondToInteraction.ts
|
|
129
|
+
* MAX_VALUES_BYTES/MAX_VALUES_DEPTH; interactionCard.ts re-checks bytes on
|
|
130
|
+
* consume via MAX_REPLY_BYTES). */
|
|
131
|
+
readonly cardValuesBytes: number;
|
|
132
|
+
readonly cardValuesDepth: 8;
|
|
133
|
+
/** Native correlation metadata (functions/src/api/interactionKinds.ts normalizeNative). */
|
|
134
|
+
readonly nativeKeys: 24;
|
|
135
|
+
readonly nativeValueChars: 256;
|
|
136
|
+
readonly nativeHandles: 16;
|
|
137
|
+
/** Deadlines (functions/src/utils/runtimeRequestHelpers.ts). */
|
|
138
|
+
readonly minTimeoutMs: 1000;
|
|
139
|
+
/** input/card/plan ceiling (30 minutes). */
|
|
140
|
+
readonly maxTimeoutMs: number;
|
|
141
|
+
/** approval-only ceiling (72 hours — owner ruling 2026-07-10). */
|
|
142
|
+
readonly maxApprovalTimeoutMs: number;
|
|
143
|
+
/** turnId / runtimeId fields on interaction creators. */
|
|
144
|
+
readonly turnIdChars: 128;
|
|
145
|
+
/** Reaction key (functions/src/api/reactToMessage.ts normalizeReactionKey). */
|
|
146
|
+
readonly reactionEmojiChars: 64;
|
|
147
|
+
/**
|
|
148
|
+
* Group name authoring cap (enforced on rename via updateNameServer; group
|
|
149
|
+
* CREATE does not length-check today — treat as the authoring contract).
|
|
150
|
+
*/
|
|
151
|
+
readonly groupNameChars: 100;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Sender-side rate limits enforced by POST /messages/send
|
|
155
|
+
* (functions/src/api/sendMessage.ts). Bindings should surface 429s with the
|
|
156
|
+
* retryAfter the server returns rather than re-deriving these.
|
|
157
|
+
*/
|
|
158
|
+
export declare const VERB_RATE_LIMITS: {
|
|
159
|
+
readonly senderMessagesPer5Min: 300;
|
|
160
|
+
readonly conversationMessagesPer5Min: 120;
|
|
161
|
+
readonly agentPeerMessagesPerHour: 30;
|
|
162
|
+
};
|
|
163
|
+
/** The one self-context type the platform accepts today. */
|
|
164
|
+
export declare const SELF_CONTEXT_TYPE = "cross_session";
|
|
165
|
+
/** Canonical verb names. */
|
|
166
|
+
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_contact_requests", "list_conversations"];
|
|
167
|
+
export type CanonVerbName = (typeof CANON_VERB_NAMES)[number];
|
|
168
|
+
/** Private note-to-self attached to a cross-conversation send. */
|
|
169
|
+
export interface VerbSelfContext {
|
|
170
|
+
type: typeof SELF_CONTEXT_TYPE;
|
|
171
|
+
/** <= VERB_LIMITS.selfContextChars. Visible only to the sending agent. */
|
|
172
|
+
context: string;
|
|
173
|
+
}
|
|
174
|
+
export type VerbSessionSelection = {
|
|
175
|
+
mode: 'new';
|
|
176
|
+
} | {
|
|
177
|
+
mode: 'continue_latest';
|
|
178
|
+
} | {
|
|
179
|
+
mode: 'continue_or_create';
|
|
180
|
+
} | {
|
|
181
|
+
mode: 'specific';
|
|
182
|
+
conversationId: string;
|
|
183
|
+
};
|
|
184
|
+
export interface VerbMediaAttachment {
|
|
185
|
+
kind: 'image' | 'audio' | 'video' | 'file';
|
|
186
|
+
/** Must come from /media/upload (Canon storage) or the GIF picker — server allowlisted. */
|
|
187
|
+
url: string;
|
|
188
|
+
mimeType?: string;
|
|
189
|
+
fileName?: string;
|
|
190
|
+
sizeBytes?: number;
|
|
191
|
+
width?: number;
|
|
192
|
+
height?: number;
|
|
193
|
+
durationMs?: number;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Well-known turn-protocol keys inside the free-form metadata envelope
|
|
197
|
+
* (see turnProtocol.ts TurnMetadata — the authoritative type).
|
|
198
|
+
*/
|
|
199
|
+
export interface VerbTurnMetadata {
|
|
200
|
+
turnId?: string | null;
|
|
201
|
+
turnSemantics?: 'progress' | 'turn_complete' | 'control';
|
|
202
|
+
deliveryIntent?: 'queue' | 'interrupt' | 'interleave' | 'stop';
|
|
203
|
+
replyBehavior?: 'allow_auto_reply' | 'suppress_auto_reply';
|
|
204
|
+
[key: string]: unknown;
|
|
205
|
+
}
|
|
206
|
+
/** Message composition options shared by send_to (mirrors POST /messages/send). */
|
|
207
|
+
export interface VerbMessageOptions {
|
|
208
|
+
messageId?: string;
|
|
209
|
+
contentType?: 'text' | 'image' | 'audio' | 'video' | 'file';
|
|
210
|
+
attachments?: VerbMediaAttachment[];
|
|
211
|
+
mentions?: string[];
|
|
212
|
+
replyTo?: string;
|
|
213
|
+
replyToPosition?: number;
|
|
214
|
+
metadata?: VerbTurnMetadata;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Message another conversation or user (admission-aware reach-out), optionally
|
|
218
|
+
* carrying a private self-context so the agent recognizes its own transfer in
|
|
219
|
+
* both sessions. Merges today's reach_out / send_contextual_message /
|
|
220
|
+
* cross-conversation send into one verb.
|
|
221
|
+
*/
|
|
222
|
+
export interface SendToInput {
|
|
223
|
+
/** Exactly one of targetConversationId / targetUserId / canonContactId. */
|
|
224
|
+
targetConversationId?: string;
|
|
225
|
+
targetUserId?: string;
|
|
226
|
+
/**
|
|
227
|
+
* Contact-card identity. NOT a wire field on any send endpoint: bindings
|
|
228
|
+
* resolve it to a targetUserId via POST /admission/resolve first (the
|
|
229
|
+
* two-step core reachOutToCanonContact runs).
|
|
230
|
+
*/
|
|
231
|
+
canonContactId?: string;
|
|
232
|
+
/** Required unless messageOptions.attachments carries the content. */
|
|
233
|
+
text?: string;
|
|
234
|
+
/**
|
|
235
|
+
* The conversation this send originates from. Required when selfContext is
|
|
236
|
+
* present (the self-context links source -> target).
|
|
237
|
+
*/
|
|
238
|
+
sourceConversationId?: string;
|
|
239
|
+
selfContext?: VerbSelfContext;
|
|
240
|
+
/** Contact-request note when admission requires approval; defaults to text. */
|
|
241
|
+
requestMessage?: string;
|
|
242
|
+
/** Only meaningful for user targets that are agents. Default: continue_or_create. */
|
|
243
|
+
sessionSelection?: VerbSessionSelection;
|
|
244
|
+
/** Coding-agent session setup; only applied when the target is an agent. */
|
|
245
|
+
sessionConfig?: Record<string, unknown> | null;
|
|
246
|
+
messageOptions?: VerbMessageOptions;
|
|
247
|
+
}
|
|
248
|
+
export type SendToResult = {
|
|
249
|
+
status: 'messaged';
|
|
250
|
+
conversationId: string;
|
|
251
|
+
messageId?: string;
|
|
252
|
+
selfContextId?: string;
|
|
253
|
+
created?: boolean;
|
|
254
|
+
reused?: boolean;
|
|
255
|
+
sessionSelection?: string;
|
|
256
|
+
} | {
|
|
257
|
+
status: 'requested';
|
|
258
|
+
requestId: string | null;
|
|
259
|
+
deferredIntentId?: string | null;
|
|
260
|
+
} | {
|
|
261
|
+
status: 'pending';
|
|
262
|
+
requestId: string | null;
|
|
263
|
+
deferredIntentId?: string | null;
|
|
264
|
+
} | {
|
|
265
|
+
status: 'setup_required';
|
|
266
|
+
reason: string;
|
|
267
|
+
} | {
|
|
268
|
+
status: 'no_session';
|
|
269
|
+
reason: string;
|
|
270
|
+
} | {
|
|
271
|
+
status: 'blocked';
|
|
272
|
+
reason: string;
|
|
273
|
+
} | {
|
|
274
|
+
status: 'unavailable';
|
|
275
|
+
reason: string;
|
|
276
|
+
};
|
|
277
|
+
export type VerbInputKind = 'clarify' | 'sudo' | 'secret';
|
|
278
|
+
export interface VerbInputChoice {
|
|
279
|
+
label: string;
|
|
280
|
+
value?: string;
|
|
281
|
+
description?: string;
|
|
282
|
+
}
|
|
283
|
+
export interface VerbInputQuestion {
|
|
284
|
+
id: string;
|
|
285
|
+
question: string;
|
|
286
|
+
header?: string;
|
|
287
|
+
choices?: VerbInputChoice[];
|
|
288
|
+
allowOther?: boolean;
|
|
289
|
+
isSecret?: boolean;
|
|
290
|
+
multiSelect?: boolean;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Ask a human a structured question mid-turn (HITL input card). `sudo`,
|
|
294
|
+
* `secret`, `sensitive:true`, and any `isSecret` question force owner-only
|
|
295
|
+
* routing server-side; bindings must not let the model redirect the responder
|
|
296
|
+
* for those.
|
|
297
|
+
*/
|
|
298
|
+
export interface RequestInputInput {
|
|
299
|
+
/** Bindings default this to the active conversation. */
|
|
300
|
+
conversationId?: string;
|
|
301
|
+
/** Durable single-use id; generated by the binding when omitted. */
|
|
302
|
+
inputId?: string;
|
|
303
|
+
kind?: VerbInputKind;
|
|
304
|
+
title?: string;
|
|
305
|
+
prompt?: string;
|
|
306
|
+
choices?: VerbInputChoice[];
|
|
307
|
+
questions?: VerbInputQuestion[];
|
|
308
|
+
secretName?: string;
|
|
309
|
+
sensitive?: boolean;
|
|
310
|
+
responseUserId?: string;
|
|
311
|
+
native?: Record<string, unknown>;
|
|
312
|
+
turnId?: string;
|
|
313
|
+
/** Relative deadline; server ceiling is VERB_LIMITS.maxTimeoutMs (30 min). */
|
|
314
|
+
timeoutMs?: number;
|
|
315
|
+
/** Absolute epoch-ms deadline; wins over timeoutMs when both given. */
|
|
316
|
+
expiresAt?: number;
|
|
317
|
+
}
|
|
318
|
+
export type RequestInputResult = {
|
|
319
|
+
status: 'submitted';
|
|
320
|
+
inputId: string;
|
|
321
|
+
value: string;
|
|
322
|
+
answers?: Record<string, {
|
|
323
|
+
answers: string[];
|
|
324
|
+
}>;
|
|
325
|
+
} | {
|
|
326
|
+
status: 'cancelled';
|
|
327
|
+
inputId: string;
|
|
328
|
+
} | {
|
|
329
|
+
status: 'timeout';
|
|
330
|
+
inputId: string;
|
|
331
|
+
};
|
|
332
|
+
export type VerbApprovalRisk = 'low' | 'normal' | 'high' | 'destructive';
|
|
333
|
+
export type VerbApprovalCategory = 'command' | 'file' | 'network' | 'browser' | 'mcp' | 'plugin' | 'canon' | 'tool';
|
|
334
|
+
export interface VerbApprovalDetail {
|
|
335
|
+
label: string;
|
|
336
|
+
value: string;
|
|
337
|
+
monospace?: boolean;
|
|
338
|
+
}
|
|
339
|
+
export interface VerbUnifiedDiffFile {
|
|
340
|
+
path: string;
|
|
341
|
+
status: 'modified' | 'created' | 'deleted' | 'renamed';
|
|
342
|
+
oldPath?: string;
|
|
343
|
+
additions?: number;
|
|
344
|
+
deletions?: number;
|
|
345
|
+
diff?: string;
|
|
346
|
+
suppressed?: boolean;
|
|
347
|
+
}
|
|
348
|
+
export interface VerbUnifiedDiff {
|
|
349
|
+
files: VerbUnifiedDiffFile[];
|
|
350
|
+
truncated?: boolean;
|
|
351
|
+
}
|
|
352
|
+
export interface VerbSessionRule {
|
|
353
|
+
type: 'approve-all' | 'approve-tool' | 'deny-tool';
|
|
354
|
+
toolPattern?: string;
|
|
355
|
+
expiresAt?: string | null;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Ask a human to allow or deny an action. `mode: 'blocking'` waits for the
|
|
359
|
+
* decision inside the verb call (default timeout 5 min); `mode: 'detached'`
|
|
360
|
+
* returns `pending` immediately and the decision is fetched later with
|
|
361
|
+
* check_approval (ceiling 72h). Timeouts fail closed to deny.
|
|
362
|
+
*
|
|
363
|
+
* Binding-local options are deliberately not wire fields: session-rule caches
|
|
364
|
+
* and their bypass (agent-sdk/core `ignoreSessionRules`) live in the binding,
|
|
365
|
+
* never on the wire — the server has no such field.
|
|
366
|
+
*/
|
|
367
|
+
export interface RequestApprovalInput {
|
|
368
|
+
conversationId?: string;
|
|
369
|
+
/** Server-generated when omitted. Durable single-use id. */
|
|
370
|
+
approvalId?: string;
|
|
371
|
+
toolName: string;
|
|
372
|
+
toolSummary: string;
|
|
373
|
+
mode?: 'blocking' | 'detached';
|
|
374
|
+
riskLevel?: 'normal' | 'destructive';
|
|
375
|
+
risk?: VerbApprovalRisk;
|
|
376
|
+
category?: VerbApprovalCategory;
|
|
377
|
+
details?: VerbApprovalDetail[];
|
|
378
|
+
diff?: VerbUnifiedDiff;
|
|
379
|
+
native?: Record<string, unknown>;
|
|
380
|
+
runtimeId?: string;
|
|
381
|
+
turnId?: string;
|
|
382
|
+
responseUserId?: string;
|
|
383
|
+
/** Server forces false when the responder is not the agent owner. */
|
|
384
|
+
allowSessionRule?: boolean;
|
|
385
|
+
timeoutMs?: number;
|
|
386
|
+
expiresAt?: number;
|
|
387
|
+
}
|
|
388
|
+
export type RequestApprovalResult = {
|
|
389
|
+
status: 'allow';
|
|
390
|
+
approvalId: string;
|
|
391
|
+
sessionRule?: VerbSessionRule;
|
|
392
|
+
respondedBy?: string;
|
|
393
|
+
} | {
|
|
394
|
+
status: 'deny';
|
|
395
|
+
approvalId: string;
|
|
396
|
+
sessionRule?: VerbSessionRule;
|
|
397
|
+
respondedBy?: string;
|
|
398
|
+
} | {
|
|
399
|
+
status: 'timeout';
|
|
400
|
+
approvalId: string;
|
|
401
|
+
} | {
|
|
402
|
+
status: 'pending';
|
|
403
|
+
approvalId: string;
|
|
404
|
+
conversationId?: string;
|
|
405
|
+
expiresAt: number;
|
|
406
|
+
responseUserId?: string;
|
|
407
|
+
};
|
|
408
|
+
export interface CheckApprovalInput {
|
|
409
|
+
approvalId: string;
|
|
410
|
+
/** Bindings must bind checks to the conversation that created the approval. */
|
|
411
|
+
conversationId?: string;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* `unknown` is NOT a denial — it means the id is unrecognized (consumed
|
|
415
|
+
* tombstone expired, or foreign id). Callers must treat only `resolved` with
|
|
416
|
+
* `decision` as an answer.
|
|
417
|
+
*/
|
|
418
|
+
export type CheckApprovalResult = {
|
|
419
|
+
status: 'resolved';
|
|
420
|
+
approvalId: string;
|
|
421
|
+
decision: 'allow' | 'deny';
|
|
422
|
+
respondedBy?: string;
|
|
423
|
+
conversationId?: string;
|
|
424
|
+
} | {
|
|
425
|
+
status: 'pending';
|
|
426
|
+
approvalId: string;
|
|
427
|
+
expiresAt?: number;
|
|
428
|
+
conversationId?: string;
|
|
429
|
+
} | {
|
|
430
|
+
status: 'expired';
|
|
431
|
+
approvalId: string;
|
|
432
|
+
conversationId?: string;
|
|
433
|
+
} | {
|
|
434
|
+
status: 'unknown';
|
|
435
|
+
approvalId: string;
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* A canon.card.v1 document. The full document contract is
|
|
439
|
+
* @canonmsg/rich-cards RUNTIME_CARD_JSON_SCHEMA_V1
|
|
440
|
+
* ($id https://canonmsg.com/schemas/canon.card.v1.json) — authoring caps
|
|
441
|
+
* title 120 / fallbackText 500 / blocks 24; the server envelope accepts up to
|
|
442
|
+
* 200 / 2000 / 64 and 32 KiB total (interactionCard.ts validateCardEnvelope).
|
|
443
|
+
*/
|
|
444
|
+
export interface VerbRuntimeCard {
|
|
445
|
+
schema: 'canon.card.v1';
|
|
446
|
+
cardId?: string;
|
|
447
|
+
title: string;
|
|
448
|
+
fallbackText: string;
|
|
449
|
+
/** At least one block (schema enforces minItems: 1). */
|
|
450
|
+
blocks: unknown[];
|
|
451
|
+
[key: string]: unknown;
|
|
452
|
+
}
|
|
453
|
+
/** Display a card with no actions — fire-and-forget, no pending state. */
|
|
454
|
+
export interface SendCardInput {
|
|
455
|
+
conversationId?: string;
|
|
456
|
+
card: VerbRuntimeCard;
|
|
457
|
+
cardId?: string;
|
|
458
|
+
native?: Record<string, unknown>;
|
|
459
|
+
runtimeId?: string;
|
|
460
|
+
turnId?: string;
|
|
461
|
+
}
|
|
462
|
+
export interface SendCardResult {
|
|
463
|
+
status: 'displayed';
|
|
464
|
+
cardId: string;
|
|
465
|
+
conversationId?: string;
|
|
466
|
+
responseUserId?: string;
|
|
467
|
+
}
|
|
468
|
+
/** Show an interactive card (>=1 actions block) and wait for the response. */
|
|
469
|
+
export interface RequestCardInput extends SendCardInput {
|
|
470
|
+
responseUserId?: string;
|
|
471
|
+
timeoutMs?: number;
|
|
472
|
+
expiresAt?: number;
|
|
473
|
+
}
|
|
474
|
+
export type RequestCardResult = {
|
|
475
|
+
status: 'submitted';
|
|
476
|
+
cardId: string;
|
|
477
|
+
actionId?: string;
|
|
478
|
+
values?: Record<string, unknown>;
|
|
479
|
+
/** Canon-authenticated responder (server-verified against responseUserId). */
|
|
480
|
+
respondedBy?: string;
|
|
481
|
+
} | {
|
|
482
|
+
status: 'cancelled';
|
|
483
|
+
cardId: string;
|
|
484
|
+
respondedBy?: string;
|
|
485
|
+
} | {
|
|
486
|
+
status: 'timeout';
|
|
487
|
+
cardId: string;
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* Share a contact card into a conversation (POST /messages/send with
|
|
491
|
+
* contentType 'contact_card'). The shared user must be in the sending
|
|
492
|
+
* agent's contacts (403 otherwise); the owner shortcut in sendMessage.ts
|
|
493
|
+
* applies only to human senders sharing their own agents. The server
|
|
494
|
+
* snapshots the card.
|
|
495
|
+
*/
|
|
496
|
+
export interface ShareContactInput {
|
|
497
|
+
conversationId: string;
|
|
498
|
+
contactUserId: string;
|
|
499
|
+
text?: string;
|
|
500
|
+
messageId?: string;
|
|
501
|
+
}
|
|
502
|
+
export interface ShareContactResult {
|
|
503
|
+
status: 'shared';
|
|
504
|
+
messageId: string;
|
|
505
|
+
}
|
|
506
|
+
/** Toggle an emoji reaction on a message (idempotent toggle server-side). */
|
|
507
|
+
export interface ReactInput {
|
|
508
|
+
conversationId: string;
|
|
509
|
+
messageId: string;
|
|
510
|
+
/** <= VERB_LIMITS.reactionEmojiChars after normalization. Content-adjacent
|
|
511
|
+
* (open decision D5: MLS messengers encrypt reactions) — rides the body. */
|
|
512
|
+
emoji: string;
|
|
513
|
+
}
|
|
514
|
+
export interface ReactResult {
|
|
515
|
+
status: 'reacted';
|
|
516
|
+
action: 'added' | 'removed';
|
|
517
|
+
reactions?: Record<string, unknown>;
|
|
518
|
+
}
|
|
519
|
+
/** Forward an existing message into another conversation. Under E2EE this
|
|
520
|
+
* becomes a client-side re-encrypt; the wire verb carries only routing ids
|
|
521
|
+
* plus an optional caption. */
|
|
522
|
+
export interface ForwardInput {
|
|
523
|
+
sourceConversationId: string;
|
|
524
|
+
targetConversationId: string;
|
|
525
|
+
messageId: string;
|
|
526
|
+
/** Optional caption; <= VERB_LIMITS.messageTextBytes UTF-8 bytes. */
|
|
527
|
+
text?: string;
|
|
528
|
+
}
|
|
529
|
+
export interface ForwardResult {
|
|
530
|
+
status: 'forwarded';
|
|
531
|
+
messageId: string;
|
|
532
|
+
targetConversationId: string;
|
|
533
|
+
forwardedFrom?: unknown;
|
|
534
|
+
}
|
|
535
|
+
export interface CreateGroupInput {
|
|
536
|
+
/** Group name (authoring cap VERB_LIMITS.groupNameChars). */
|
|
537
|
+
name: string;
|
|
538
|
+
/** Other members; the caller is added automatically. Cap
|
|
539
|
+
* VERB_LIMITS.groupMembers including the creator. Each target's
|
|
540
|
+
* groupJoinPolicy is enforced server-side. */
|
|
541
|
+
memberIds: string[];
|
|
542
|
+
}
|
|
543
|
+
export interface CreateGroupResult {
|
|
544
|
+
status: 'created';
|
|
545
|
+
conversationId: string;
|
|
546
|
+
/** approval-required members — group_invite requests awaiting their approver. */
|
|
547
|
+
pending?: Array<{
|
|
548
|
+
userId: string;
|
|
549
|
+
requestId: string;
|
|
550
|
+
}>;
|
|
551
|
+
/** members the staged create could not admit: policy denials (owner-only,
|
|
552
|
+
* blocked, not-found, …) and setup-required coding agents (v1 scoping —
|
|
553
|
+
* only their owner can provide session setup, from the app). */
|
|
554
|
+
skipped?: Array<{
|
|
555
|
+
userId: string;
|
|
556
|
+
reason: string;
|
|
557
|
+
}>;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Error code on the 400 a create_group receives when NO member is directly
|
|
561
|
+
* addable (approval-required members can only be invited to an existing
|
|
562
|
+
* group; a creator-only group is not created). The error detail carries the
|
|
563
|
+
* partition: pendingRequired[] and skipped[].
|
|
564
|
+
*/
|
|
565
|
+
export declare const CREATE_GROUP_NO_ADDABLE_MEMBERS_CODE = "CREATE_GROUP_NO_ADDABLE_MEMBERS";
|
|
566
|
+
/**
|
|
567
|
+
* Error code on the 403 a create_group receives when the CREATOR itself is
|
|
568
|
+
* a coding agent requiring explicit session setup: the group helper prepares
|
|
569
|
+
* session configs for every coding-agent member including the creator, and
|
|
570
|
+
* only the agent's human owner may provide them (v1 scoping — the owner
|
|
571
|
+
* creates the group from the app instead).
|
|
572
|
+
*/
|
|
573
|
+
export declare const CREATE_GROUP_CREATOR_SETUP_REQUIRED_CODE = "CREATE_GROUP_CREATOR_SETUP_REQUIRED";
|
|
574
|
+
export interface AddMemberInput {
|
|
575
|
+
conversationId: string;
|
|
576
|
+
userId: string;
|
|
577
|
+
}
|
|
578
|
+
/** approval-required targets yield a pending group_invite contact request. */
|
|
579
|
+
export type AddMemberResult = {
|
|
580
|
+
status: 'added';
|
|
581
|
+
} | {
|
|
582
|
+
status: 'pending';
|
|
583
|
+
requestId: string;
|
|
584
|
+
};
|
|
585
|
+
export interface RemoveMemberInput {
|
|
586
|
+
conversationId: string;
|
|
587
|
+
userId: string;
|
|
588
|
+
}
|
|
589
|
+
export interface RemoveMemberResult {
|
|
590
|
+
status: 'removed';
|
|
591
|
+
}
|
|
592
|
+
export interface LeaveConversationInput {
|
|
593
|
+
conversationId: string;
|
|
594
|
+
}
|
|
595
|
+
export interface LeaveConversationResult {
|
|
596
|
+
status: 'left';
|
|
597
|
+
}
|
|
598
|
+
export type ListContactsInput = Record<string, never>;
|
|
599
|
+
export interface VerbContact {
|
|
600
|
+
/** The contact's userId. */
|
|
601
|
+
id: string;
|
|
602
|
+
/** Vocabulary: direct_add | phone_book | contact_request | link | qr | group | open_inbound_message | unknown (server passes strings through). */
|
|
603
|
+
source: string;
|
|
604
|
+
addedAt: string | null;
|
|
605
|
+
displayNameOverride: string | null;
|
|
606
|
+
}
|
|
607
|
+
export interface ListContactsResult {
|
|
608
|
+
contacts: VerbContact[];
|
|
609
|
+
}
|
|
610
|
+
export type ListContactRequestsInput = Record<string, never>;
|
|
611
|
+
/** Items are SerializedContactRequest (contactRequest.ts) — pending inbound only, newest first, capped at 100. */
|
|
612
|
+
export interface ListContactRequestsResult {
|
|
613
|
+
requests: unknown[];
|
|
614
|
+
}
|
|
615
|
+
export interface ListConversationsInput {
|
|
616
|
+
/** Optional client-side cap; the REST endpoint returns all memberships. */
|
|
617
|
+
limit?: number;
|
|
618
|
+
}
|
|
619
|
+
export interface VerbConversationSummary {
|
|
620
|
+
id: string;
|
|
621
|
+
type: 'direct' | 'group';
|
|
622
|
+
/** Raw passthrough — may be absent entirely on direct chats. */
|
|
623
|
+
name?: string | null;
|
|
624
|
+
topic: string | null;
|
|
625
|
+
memberIds: string[];
|
|
626
|
+
isAgentChat: boolean;
|
|
627
|
+
hasUnread?: boolean;
|
|
628
|
+
lastMessage: {
|
|
629
|
+
text: string | null;
|
|
630
|
+
messageId?: string;
|
|
631
|
+
senderId: string;
|
|
632
|
+
senderType: string;
|
|
633
|
+
contentType?: string;
|
|
634
|
+
timestamp: string | null;
|
|
635
|
+
} | null;
|
|
636
|
+
createdAt: string | null;
|
|
637
|
+
}
|
|
638
|
+
export interface ListConversationsResult {
|
|
639
|
+
conversations: VerbConversationSummary[];
|
|
640
|
+
}
|
|
641
|
+
/** Flat tool name for a verb on namespaced tool surfaces (e.g. `canon_send_to`). */
|
|
642
|
+
export declare function canonVerbToolName(verb: CanonVerbName): string;
|
|
643
|
+
export interface VerbLimitViolation {
|
|
644
|
+
path: string;
|
|
645
|
+
message: string;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* The server limits JSON Schema cannot express: UTF-8 byte caps and
|
|
649
|
+
* serialized-JSON length caps. Bindings MUST run this after schema
|
|
650
|
+
* validation; the server enforces the same checks with 400s.
|
|
651
|
+
*/
|
|
652
|
+
export declare function findVerbByteLimitViolations(verb: CanonVerbName, input: Record<string, unknown>): VerbLimitViolation[];
|
|
653
|
+
/**
|
|
654
|
+
* Machine-readable limits companion to the schema bundle, emitted as
|
|
655
|
+
* dist/canon-verbs.limits.json so non-TypeScript bindings can apply the
|
|
656
|
+
* byte-sensitive checks JSON Schema cannot express.
|
|
657
|
+
*/
|
|
658
|
+
export declare const CANON_VERB_LIMITS_ARTIFACT: {
|
|
659
|
+
readonly schemaVersion: "canon.verbs.v1";
|
|
660
|
+
readonly limits: {
|
|
661
|
+
/** Message text — UTF-8 bytes (functions/src/api/sendMessage.ts MAX_MESSAGE_TEXT_BYTES). */
|
|
662
|
+
readonly messageTextBytes: 4096;
|
|
663
|
+
/**
|
|
664
|
+
* Serialized metadata JSON length in UTF-16 code units — the server checks
|
|
665
|
+
* JSON.stringify(metadata).length, not bytes (sendMessage.ts:716-724 inline;
|
|
666
|
+
* parseBody.ts maxJsonBytes despite the name).
|
|
667
|
+
*/
|
|
668
|
+
readonly messageMetadataJsonChars: 4096;
|
|
669
|
+
/** Attachments per message (sendMessage.ts MAX_MESSAGE_ATTACHMENTS). */
|
|
670
|
+
readonly messageAttachments: 10;
|
|
671
|
+
/** Client-supplied messageId — chars and UTF-8 bytes (sendMessage.ts). */
|
|
672
|
+
readonly messageIdChars: 160;
|
|
673
|
+
readonly messageIdBytes: 256;
|
|
674
|
+
/** Self-context note (functions/src/utils/selfContexts.ts SELF_CONTEXT_CONTEXT_LIMIT). */
|
|
675
|
+
readonly selfContextChars: 1000;
|
|
676
|
+
/**
|
|
677
|
+
* Contact-request note. Values longer than this are truncated by senders
|
|
678
|
+
* (slice(0,497)+'...') — the truncation is currently triplicated in
|
|
679
|
+
* functions/src/api/sendContextualMessage.ts, packages/core/src/reach-out.ts
|
|
680
|
+
* and the hermes plugin; this constant is the canonical figure.
|
|
681
|
+
*/
|
|
682
|
+
readonly contactRequestNoteChars: 500;
|
|
683
|
+
/** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */
|
|
684
|
+
readonly groupMembers: 50;
|
|
685
|
+
/** Runtime input (functions/src/api/interactionInput.ts). */
|
|
686
|
+
readonly inputTitleChars: 160;
|
|
687
|
+
readonly inputPromptChars: 4000;
|
|
688
|
+
readonly inputChoices: 12;
|
|
689
|
+
readonly inputChoiceLabelChars: 120;
|
|
690
|
+
readonly inputChoiceValueChars: 200;
|
|
691
|
+
readonly inputChoiceDescriptionChars: 300;
|
|
692
|
+
readonly inputQuestions: 12;
|
|
693
|
+
readonly inputQuestionChars: 1000;
|
|
694
|
+
readonly inputQuestionHeaderChars: 120;
|
|
695
|
+
readonly inputSecretNameChars: 160;
|
|
696
|
+
readonly inputAnswerChars: 8192;
|
|
697
|
+
/** Approval (functions/src/api/interactionApproval.ts). */
|
|
698
|
+
readonly toolNameChars: 128;
|
|
699
|
+
readonly toolSummaryChars: 1000;
|
|
700
|
+
readonly approvalDetails: 8;
|
|
701
|
+
readonly approvalDetailLabelChars: 80;
|
|
702
|
+
readonly approvalDetailValueChars: 500;
|
|
703
|
+
readonly diffFiles: 100;
|
|
704
|
+
readonly diffPathChars: 1024;
|
|
705
|
+
readonly diffFileBytes: number;
|
|
706
|
+
readonly diffTotalBytes: number;
|
|
707
|
+
/** Card envelope acceptance (functions/src/api/interactionCard.ts — server caps;
|
|
708
|
+
* authoring caps in @canonmsg/rich-cards are stricter: title 120, fallback 500, blocks 24). */
|
|
709
|
+
readonly cardEnvelopeBytes: number;
|
|
710
|
+
readonly cardServerTitleChars: 200;
|
|
711
|
+
readonly cardServerFallbackTextChars: 2000;
|
|
712
|
+
readonly cardServerBlocks: 64;
|
|
713
|
+
/** Response-values caps enforced on submit (callable respondToInteraction.ts
|
|
714
|
+
* MAX_VALUES_BYTES/MAX_VALUES_DEPTH; interactionCard.ts re-checks bytes on
|
|
715
|
+
* consume via MAX_REPLY_BYTES). */
|
|
716
|
+
readonly cardValuesBytes: number;
|
|
717
|
+
readonly cardValuesDepth: 8;
|
|
718
|
+
/** Native correlation metadata (functions/src/api/interactionKinds.ts normalizeNative). */
|
|
719
|
+
readonly nativeKeys: 24;
|
|
720
|
+
readonly nativeValueChars: 256;
|
|
721
|
+
readonly nativeHandles: 16;
|
|
722
|
+
/** Deadlines (functions/src/utils/runtimeRequestHelpers.ts). */
|
|
723
|
+
readonly minTimeoutMs: 1000;
|
|
724
|
+
/** input/card/plan ceiling (30 minutes). */
|
|
725
|
+
readonly maxTimeoutMs: number;
|
|
726
|
+
/** approval-only ceiling (72 hours — owner ruling 2026-07-10). */
|
|
727
|
+
readonly maxApprovalTimeoutMs: number;
|
|
728
|
+
/** turnId / runtimeId fields on interaction creators. */
|
|
729
|
+
readonly turnIdChars: 128;
|
|
730
|
+
/** Reaction key (functions/src/api/reactToMessage.ts normalizeReactionKey). */
|
|
731
|
+
readonly reactionEmojiChars: 64;
|
|
732
|
+
/**
|
|
733
|
+
* Group name authoring cap (enforced on rename via updateNameServer; group
|
|
734
|
+
* CREATE does not length-check today — treat as the authoring contract).
|
|
735
|
+
*/
|
|
736
|
+
readonly groupNameChars: 100;
|
|
737
|
+
};
|
|
738
|
+
readonly rateLimits: {
|
|
739
|
+
readonly senderMessagesPer5Min: 300;
|
|
740
|
+
readonly conversationMessagesPer5Min: 120;
|
|
741
|
+
readonly agentPeerMessagesPerHour: 30;
|
|
742
|
+
};
|
|
743
|
+
readonly idPatterns: {
|
|
744
|
+
readonly runtimeId: "^[A-Za-z0-9_.:-]{1,160}$";
|
|
745
|
+
readonly cardId: "^[A-Za-z0-9_.:-]{1,80}$";
|
|
746
|
+
readonly actionId: "^[A-Za-z0-9_.:-]{1,80}$";
|
|
747
|
+
readonly questionId: "^[A-Za-z0-9_.:-]{1,120}$";
|
|
748
|
+
readonly sessionRuleToolPattern: "^[\\w.*:-]{1,128}$";
|
|
749
|
+
};
|
|
750
|
+
readonly byteSemantics: {
|
|
751
|
+
readonly 'send_to.text': "utf8_bytes<=messageTextBytes";
|
|
752
|
+
readonly 'share_contact.text': "utf8_bytes<=messageTextBytes";
|
|
753
|
+
readonly 'forward.text': "utf8_bytes<=messageTextBytes";
|
|
754
|
+
readonly 'send_to.messageOptions.messageId': "utf8_bytes<=messageIdBytes";
|
|
755
|
+
readonly 'share_contact.messageId': "utf8_bytes<=messageIdBytes";
|
|
756
|
+
readonly 'send_to.messageOptions.metadata': "json_stringify_chars<=messageMetadataJsonChars";
|
|
757
|
+
readonly 'send_card.card': "json_utf8_bytes<=cardEnvelopeBytes";
|
|
758
|
+
readonly 'request_card.card': "json_utf8_bytes<=cardEnvelopeBytes";
|
|
759
|
+
};
|
|
760
|
+
};
|