@canonmsg/backend-contracts 2.0.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/diffRedaction.js +73 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/message.js +44 -1
- package/dist/cjs/verbContract.js +288 -0
- package/dist/cjs/verbSchemas.js +1144 -0
- package/dist/cjs/verbWire.js +634 -0
- package/dist/diffRedaction.d.ts +6 -0
- package/dist/diffRedaction.js +68 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/message.d.ts +31 -1
- package/dist/message.js +44 -1
- 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,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Sensitive-content protection for approval diffs.
|
|
3
|
+
//
|
|
4
|
+
// Approval diffs include PRE-IMAGE context lines and ride on a message readable
|
|
5
|
+
// by every conversation member, so a secret-bearing file would leak existing
|
|
6
|
+
// secrets. The emitting host applies this before sending (it owns the file
|
|
7
|
+
// context); the server (@canonmsg/functions) mirrors the same suppression
|
|
8
|
+
// defensively. Single-sourced here in the zero-dependency backend-contracts
|
|
9
|
+
// leaf so @canonmsg/core (client-side sanitizer) and Functions (server mirror)
|
|
10
|
+
// share one implementation — Functions deliberately does not depend on core.
|
|
11
|
+
//
|
|
12
|
+
// Two deliberately narrow mechanisms so ordinary code diffs stay readable:
|
|
13
|
+
// 1. Path suppression — files whose path screams "secret store" lose their
|
|
14
|
+
// hunk text entirely; entry/status/counts survive.
|
|
15
|
+
// 2. High-confidence token redaction — prefix-anchored credential shapes
|
|
16
|
+
// (AWS/GitHub/Slack/Google/OpenAI-style keys, JWTs) are replaced with
|
|
17
|
+
// `[redacted]` inside remaining hunks. Private-key blocks suppress the
|
|
18
|
+
// whole file (redacting a PEM body line-by-line is fragile).
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.PRIVATE_KEY_BLOCK = void 0;
|
|
21
|
+
exports.isSensitiveDiffPath = isSensitiveDiffPath;
|
|
22
|
+
exports.redactSecretTokens = redactSecretTokens;
|
|
23
|
+
const SENSITIVE_DIFF_BASENAME_PATTERNS = [
|
|
24
|
+
/^\.env(\..+)?$/i,
|
|
25
|
+
/^\.npmrc$/i,
|
|
26
|
+
/^\.netrc$/i,
|
|
27
|
+
/^\.pgpass$/i,
|
|
28
|
+
/^\.git-credentials$/i,
|
|
29
|
+
/^\.htpasswd$/i,
|
|
30
|
+
/\.(pem|key|p12|pfx|keystore|jks|tfvars)$/i,
|
|
31
|
+
/^id_(rsa|dsa|ecdsa|ed25519)(\..+)?$/i,
|
|
32
|
+
// Word-bounded so "secrets.json" and "client_secret.yaml" match but
|
|
33
|
+
// "secretary.ts" does not.
|
|
34
|
+
/(^|[-_.])(credentials?|secrets?)([-_.]|$)/i,
|
|
35
|
+
/^serviceaccount.*\.json$/i,
|
|
36
|
+
/^kubeconfig(\..+)?$/i,
|
|
37
|
+
];
|
|
38
|
+
// Directory components that mark everything beneath them sensitive
|
|
39
|
+
// (~/.ssh/config, .aws/credentials, infra/secrets/db.yaml, …).
|
|
40
|
+
const SENSITIVE_DIFF_SEGMENT_PATTERNS = [
|
|
41
|
+
/^\.ssh$/i,
|
|
42
|
+
/^\.aws$/i,
|
|
43
|
+
/^\.gnupg$/i,
|
|
44
|
+
/^\.kube$/i,
|
|
45
|
+
/^secrets?$/i,
|
|
46
|
+
/^credentials?$/i,
|
|
47
|
+
];
|
|
48
|
+
const SECRET_TOKEN_PATTERNS = [
|
|
49
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
50
|
+
/gh[pousr]_[A-Za-z0-9]{36,255}/g,
|
|
51
|
+
/github_pat_[A-Za-z0-9_]{22,255}/g,
|
|
52
|
+
/xox[baprs]-[A-Za-z0-9-]{10,250}/g,
|
|
53
|
+
/AIza[0-9A-Za-z_-]{35}/g,
|
|
54
|
+
/sk-[A-Za-z0-9_-]{20,250}/g,
|
|
55
|
+
/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
|
|
56
|
+
];
|
|
57
|
+
/** Matches the header line of a PEM private-key block. */
|
|
58
|
+
exports.PRIVATE_KEY_BLOCK = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY( BLOCK)?-----/;
|
|
59
|
+
/** True when a diff file path (or rename source) matches the sensitive list. */
|
|
60
|
+
function isSensitiveDiffPath(path) {
|
|
61
|
+
const segments = path.split('/').filter(Boolean);
|
|
62
|
+
const basename = segments.pop() ?? path;
|
|
63
|
+
return SENSITIVE_DIFF_BASENAME_PATTERNS.some((pattern) => pattern.test(basename))
|
|
64
|
+
|| segments.some((segment) => SENSITIVE_DIFF_SEGMENT_PATTERNS.some((pattern) => pattern.test(segment)));
|
|
65
|
+
}
|
|
66
|
+
/** Replace high-confidence credential shapes inside hunk text with `[redacted]`. */
|
|
67
|
+
function redactSecretTokens(text) {
|
|
68
|
+
let redacted = text;
|
|
69
|
+
for (const pattern of SECRET_TOKEN_PATTERNS) {
|
|
70
|
+
redacted = redacted.replace(pattern, '[redacted]');
|
|
71
|
+
}
|
|
72
|
+
return redacted;
|
|
73
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./diffRedaction.js"), exports);
|
|
17
18
|
__exportStar(require("./media.js"), exports);
|
|
18
19
|
__exportStar(require("./message.js"), exports);
|
|
19
20
|
__exportStar(require("./runtimeCardFields.js"), exports);
|
|
@@ -21,3 +22,6 @@ __exportStar(require("./runtimeCardStorage.js"), exports);
|
|
|
21
22
|
__exportStar(require("./turnProtocol.js"), exports);
|
|
22
23
|
__exportStar(require("./agentBehaviorPolicy.js"), exports);
|
|
23
24
|
__exportStar(require("./contactRequest.js"), exports);
|
|
25
|
+
__exportStar(require("./verbContract.js"), exports);
|
|
26
|
+
__exportStar(require("./verbSchemas.js"), exports);
|
|
27
|
+
__exportStar(require("./verbWire.js"), exports);
|
package/dist/cjs/message.js
CHANGED
|
@@ -61,7 +61,8 @@ function normalizeContentType(value) {
|
|
|
61
61
|
|| value === 'audio'
|
|
62
62
|
|| value === 'video'
|
|
63
63
|
|| value === 'file'
|
|
64
|
-
|| value === 'contact_card'
|
|
64
|
+
|| value === 'contact_card'
|
|
65
|
+
|| value === 'interaction') {
|
|
65
66
|
return value;
|
|
66
67
|
}
|
|
67
68
|
throw new Error('Message is missing canonical contentType');
|
|
@@ -110,6 +111,41 @@ function normalizeContactCard(value) {
|
|
|
110
111
|
card.lifecycleState = value.lifecycleState;
|
|
111
112
|
return card;
|
|
112
113
|
}
|
|
114
|
+
const INTERACTION_KINDS = [
|
|
115
|
+
'contact', 'approval', 'input', 'plan', 'card',
|
|
116
|
+
];
|
|
117
|
+
function normalizeInteraction(value) {
|
|
118
|
+
if (!isRecord(value))
|
|
119
|
+
return undefined;
|
|
120
|
+
if (!INTERACTION_KINDS.includes(value.kind))
|
|
121
|
+
return undefined;
|
|
122
|
+
if (typeof value.requestId !== 'string' || value.requestId.length === 0)
|
|
123
|
+
return undefined;
|
|
124
|
+
const envelope = {
|
|
125
|
+
kind: value.kind,
|
|
126
|
+
requestId: value.requestId,
|
|
127
|
+
interactive: value.interactive === true,
|
|
128
|
+
};
|
|
129
|
+
if (typeof value.responseUserId === 'string')
|
|
130
|
+
envelope.responseUserId = value.responseUserId;
|
|
131
|
+
if (typeof value.expiresAt === 'string')
|
|
132
|
+
envelope.expiresAt = value.expiresAt;
|
|
133
|
+
if (typeof value.schemaVersion === 'string')
|
|
134
|
+
envelope.schemaVersion = value.schemaVersion;
|
|
135
|
+
if (isRecord(value.preview)) {
|
|
136
|
+
const preview = {};
|
|
137
|
+
if (typeof value.preview.title === 'string')
|
|
138
|
+
preview.title = value.preview.title;
|
|
139
|
+
if (Array.isArray(value.preview.blockKinds)) {
|
|
140
|
+
preview.blockKinds = value.preview.blockKinds.filter((entry) => typeof entry === 'string');
|
|
141
|
+
}
|
|
142
|
+
envelope.preview = preview;
|
|
143
|
+
}
|
|
144
|
+
if (isRecord(value.response)) {
|
|
145
|
+
envelope.response = value.response;
|
|
146
|
+
}
|
|
147
|
+
return envelope;
|
|
148
|
+
}
|
|
113
149
|
function serializeStoredMessage(input) {
|
|
114
150
|
const { data } = input;
|
|
115
151
|
const attachments = data.attachments === undefined
|
|
@@ -153,6 +189,13 @@ function serializeStoredMessage(input) {
|
|
|
153
189
|
if (runtimeCard) {
|
|
154
190
|
result.runtimeCard = runtimeCard;
|
|
155
191
|
}
|
|
192
|
+
const interaction = normalizeInteraction(data.interaction);
|
|
193
|
+
if (interaction) {
|
|
194
|
+
result.interaction = interaction;
|
|
195
|
+
}
|
|
196
|
+
if (typeof data.body === 'string') {
|
|
197
|
+
result.body = data.body;
|
|
198
|
+
}
|
|
156
199
|
const reactions = normalizeReactions(data.reactions);
|
|
157
200
|
if (reactions) {
|
|
158
201
|
result.reactions = reactions;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canon agent verb contract — the canonical, runtime-agnostic vocabulary for
|
|
4
|
+
* the conversational/HITL actions an agent deliberately takes against Canon
|
|
5
|
+
* (v1 scope: messaging, HITL interactions, contact sharing, and the read
|
|
6
|
+
* verbs bindings need alongside them).
|
|
7
|
+
*
|
|
8
|
+
* One verb = one intent-level action. Each runtime binding (Hermes native
|
|
9
|
+
* tool, MCP server, codex dynamicTools, agent-sdk method, CLI) projects these
|
|
10
|
+
* verbs into its native tool surface; the names, argument shapes, limits, and
|
|
11
|
+
* result vocabularies defined here are the single source of truth. JSON
|
|
12
|
+
* Schemas for each verb live in `verbSchemas.ts` and are also emitted as a
|
|
13
|
+
* plain JSON artifact at build time (`dist/canon-verbs.schema.json`) so
|
|
14
|
+
* non-TypeScript consumers (the Python hermes plugin, external integrators)
|
|
15
|
+
* can consume the identical contract.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately NOT verbs: replying in the active conversation, streaming
|
|
18
|
+
* partials, typing, and read receipts stay host-mediated — the model talks
|
|
19
|
+
* and the platform delivers. Still out of scope pending their own design
|
|
20
|
+
* pass: plan approval (the fourth runtime-interaction kind — coding-host
|
|
21
|
+
* concern today) and block/mute. Their REST surfaces remain directly
|
|
22
|
+
* callable.
|
|
23
|
+
*
|
|
24
|
+
* Scope note: this module DESCRIBES the contract (canonical shapes + the
|
|
25
|
+
* server-enforced limits, with enforcement sites cited). Enforcement itself
|
|
26
|
+
* stays where it runs today — functions/src. Server-side verb endpoints that
|
|
27
|
+
* validate against these schemas are a planned follow-up.
|
|
28
|
+
*
|
|
29
|
+
* Normativity: the JSON Schemas in `verbSchemas.ts` (and the emitted
|
|
30
|
+
* canon-verbs.schema.json) are the normative contract. The TypeScript types
|
|
31
|
+
* here are a convenience projection — corrections flow schema -> type, and
|
|
32
|
+
* the dual-witness fixtures in verbContract.test.ts (each fixture is both
|
|
33
|
+
* compile-checked against the type and validated against the schema) guard
|
|
34
|
+
* the two from drifting. Single-sourcing the types from the schemas is a
|
|
35
|
+
* planned follow-up.
|
|
36
|
+
*
|
|
37
|
+
* Byte-sensitive limits: JSON Schema `maxLength` counts UTF-16 code units,
|
|
38
|
+
* but several server limits count UTF-8 bytes or serialized-JSON length,
|
|
39
|
+
* which no standard keyword expresses. Bindings MUST run
|
|
40
|
+
* `findVerbByteLimitViolations()` (or equivalent checks from the emitted
|
|
41
|
+
* canon-verbs.limits.json) after schema validation.
|
|
42
|
+
*/
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.CANON_VERB_LIMITS_ARTIFACT = exports.CREATE_GROUP_CREATOR_SETUP_REQUIRED_CODE = exports.CREATE_GROUP_NO_ADDABLE_MEMBERS_CODE = exports.CANON_VERB_NAMES = exports.SELF_CONTEXT_TYPE = exports.VERB_RATE_LIMITS = exports.VERB_LIMITS = exports.VERB_ID_PATTERNS = exports.CANON_CARD_SCHEMA_ID = exports.CANON_VERB_NAMESPACE = exports.CANON_VERBS_SCHEMA_ID = exports.CANON_VERBS_SCHEMA_VERSION = void 0;
|
|
45
|
+
exports.canonVerbToolName = canonVerbToolName;
|
|
46
|
+
exports.findVerbByteLimitViolations = findVerbByteLimitViolations;
|
|
47
|
+
/** Identifier for this contract document. */
|
|
48
|
+
exports.CANON_VERBS_SCHEMA_VERSION = 'canon.verbs.v1';
|
|
49
|
+
/** $id of the emitted JSON Schema bundle. */
|
|
50
|
+
exports.CANON_VERBS_SCHEMA_ID = 'https://canonmsg.com/schemas/canon.verbs.v1.json';
|
|
51
|
+
/** Namespace prefix bindings should use for flat tool names (e.g. `canon_send_to`). */
|
|
52
|
+
exports.CANON_VERB_NAMESPACE = 'canon';
|
|
53
|
+
/**
|
|
54
|
+
* $id of the canonical canon.card.v1 document schema
|
|
55
|
+
* (@canonmsg/rich-cards RUNTIME_CARD_JSON_SCHEMA_V1). The verbs bundle only
|
|
56
|
+
* validates the card ENVELOPE; compose the full document schema into card
|
|
57
|
+
* verbs via `getVerbInputSchema(verb, { cardSchema })`.
|
|
58
|
+
*/
|
|
59
|
+
exports.CANON_CARD_SCHEMA_ID = 'https://canonmsg.com/schemas/canon.card.v1.json';
|
|
60
|
+
/**
|
|
61
|
+
* Identifier patterns, mirrored from the enforcing sites:
|
|
62
|
+
* - RUNTIME_ID_PATTERN: conversation/input/approval ids —
|
|
63
|
+
* functions/src/utils/runtimeRequestHelpers.ts (`/^[A-Za-z0-9_.:-]{1,160}$/`)
|
|
64
|
+
* - CARD_ID_PATTERN / ACTION_ID_PATTERN: functions/src/api/interactionCard.ts
|
|
65
|
+
* and @canonmsg/rich-cards RUNTIME_CARD_ACTION_ID_PATTERN (80 chars)
|
|
66
|
+
* - QUESTION_ID_PATTERN: functions/src/api/interactionInput.ts (120 chars)
|
|
67
|
+
* - SESSION_RULE_TOOL_PATTERN: functions/src/callable/respondToInteraction.ts
|
|
68
|
+
*/
|
|
69
|
+
exports.VERB_ID_PATTERNS = {
|
|
70
|
+
runtimeId: '^[A-Za-z0-9_.:-]{1,160}$',
|
|
71
|
+
cardId: '^[A-Za-z0-9_.:-]{1,80}$',
|
|
72
|
+
actionId: '^[A-Za-z0-9_.:-]{1,80}$',
|
|
73
|
+
questionId: '^[A-Za-z0-9_.:-]{1,120}$',
|
|
74
|
+
sessionRuleToolPattern: '^[\\w.*:-]{1,128}$',
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Server-enforced limits, single-sourced. Each value cites its enforcement
|
|
78
|
+
* site; keep the citation current when a limit moves.
|
|
79
|
+
*/
|
|
80
|
+
exports.VERB_LIMITS = {
|
|
81
|
+
/** Message text — UTF-8 bytes (functions/src/api/sendMessage.ts MAX_MESSAGE_TEXT_BYTES). */
|
|
82
|
+
messageTextBytes: 4096,
|
|
83
|
+
/**
|
|
84
|
+
* Serialized metadata JSON length in UTF-16 code units — the server checks
|
|
85
|
+
* JSON.stringify(metadata).length, not bytes (sendMessage.ts:716-724 inline;
|
|
86
|
+
* parseBody.ts maxJsonBytes despite the name).
|
|
87
|
+
*/
|
|
88
|
+
messageMetadataJsonChars: 4096,
|
|
89
|
+
/** Attachments per message (sendMessage.ts MAX_MESSAGE_ATTACHMENTS). */
|
|
90
|
+
messageAttachments: 10,
|
|
91
|
+
/** Client-supplied messageId — chars and UTF-8 bytes (sendMessage.ts). */
|
|
92
|
+
messageIdChars: 160,
|
|
93
|
+
messageIdBytes: 256,
|
|
94
|
+
/** Self-context note (functions/src/utils/selfContexts.ts SELF_CONTEXT_CONTEXT_LIMIT). */
|
|
95
|
+
selfContextChars: 1000,
|
|
96
|
+
/**
|
|
97
|
+
* Contact-request note. Values longer than this are truncated by senders
|
|
98
|
+
* (slice(0,497)+'...') — the truncation is currently triplicated in
|
|
99
|
+
* functions/src/api/sendContextualMessage.ts, packages/core/src/reach-out.ts
|
|
100
|
+
* and the hermes plugin; this constant is the canonical figure.
|
|
101
|
+
*/
|
|
102
|
+
contactRequestNoteChars: 500,
|
|
103
|
+
/** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */
|
|
104
|
+
groupMembers: 50,
|
|
105
|
+
/** Runtime input (functions/src/api/interactionInput.ts). */
|
|
106
|
+
inputTitleChars: 160,
|
|
107
|
+
inputPromptChars: 4000,
|
|
108
|
+
inputChoices: 12,
|
|
109
|
+
inputChoiceLabelChars: 120,
|
|
110
|
+
inputChoiceValueChars: 200,
|
|
111
|
+
inputChoiceDescriptionChars: 300,
|
|
112
|
+
inputQuestions: 12,
|
|
113
|
+
inputQuestionChars: 1000,
|
|
114
|
+
inputQuestionHeaderChars: 120,
|
|
115
|
+
inputSecretNameChars: 160,
|
|
116
|
+
inputAnswerChars: 8192,
|
|
117
|
+
/** Approval (functions/src/api/interactionApproval.ts). */
|
|
118
|
+
toolNameChars: 128,
|
|
119
|
+
toolSummaryChars: 1000,
|
|
120
|
+
approvalDetails: 8,
|
|
121
|
+
approvalDetailLabelChars: 80,
|
|
122
|
+
approvalDetailValueChars: 500,
|
|
123
|
+
diffFiles: 100,
|
|
124
|
+
diffPathChars: 1024,
|
|
125
|
+
diffFileBytes: 24 * 1024,
|
|
126
|
+
diffTotalBytes: 96 * 1024,
|
|
127
|
+
/** Card envelope acceptance (functions/src/api/interactionCard.ts — server caps;
|
|
128
|
+
* authoring caps in @canonmsg/rich-cards are stricter: title 120, fallback 500, blocks 24). */
|
|
129
|
+
cardEnvelopeBytes: 32 * 1024,
|
|
130
|
+
cardServerTitleChars: 200,
|
|
131
|
+
cardServerFallbackTextChars: 2000,
|
|
132
|
+
cardServerBlocks: 64,
|
|
133
|
+
/** Response-values caps enforced on submit (callable respondToInteraction.ts
|
|
134
|
+
* MAX_VALUES_BYTES/MAX_VALUES_DEPTH; interactionCard.ts re-checks bytes on
|
|
135
|
+
* consume via MAX_REPLY_BYTES). */
|
|
136
|
+
cardValuesBytes: 8 * 1024,
|
|
137
|
+
cardValuesDepth: 8,
|
|
138
|
+
/** Native correlation metadata (functions/src/api/interactionKinds.ts normalizeNative). */
|
|
139
|
+
nativeKeys: 24,
|
|
140
|
+
nativeValueChars: 256,
|
|
141
|
+
nativeHandles: 16,
|
|
142
|
+
/** Deadlines (functions/src/utils/runtimeRequestHelpers.ts). */
|
|
143
|
+
minTimeoutMs: 1000,
|
|
144
|
+
/** input/card/plan ceiling (30 minutes). */
|
|
145
|
+
maxTimeoutMs: 30 * 60 * 1000,
|
|
146
|
+
/** approval-only ceiling (72 hours — owner ruling 2026-07-10). */
|
|
147
|
+
maxApprovalTimeoutMs: 72 * 60 * 60 * 1000,
|
|
148
|
+
/** turnId / runtimeId fields on interaction creators. */
|
|
149
|
+
turnIdChars: 128,
|
|
150
|
+
/** Reaction key (functions/src/api/reactToMessage.ts normalizeReactionKey). */
|
|
151
|
+
reactionEmojiChars: 64,
|
|
152
|
+
/**
|
|
153
|
+
* Group name authoring cap (enforced on rename via updateNameServer; group
|
|
154
|
+
* CREATE does not length-check today — treat as the authoring contract).
|
|
155
|
+
*/
|
|
156
|
+
groupNameChars: 100,
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Sender-side rate limits enforced by POST /messages/send
|
|
160
|
+
* (functions/src/api/sendMessage.ts). Bindings should surface 429s with the
|
|
161
|
+
* retryAfter the server returns rather than re-deriving these.
|
|
162
|
+
*/
|
|
163
|
+
exports.VERB_RATE_LIMITS = {
|
|
164
|
+
senderMessagesPer5Min: 300,
|
|
165
|
+
conversationMessagesPer5Min: 120,
|
|
166
|
+
agentPeerMessagesPerHour: 30,
|
|
167
|
+
};
|
|
168
|
+
/** The one self-context type the platform accepts today. */
|
|
169
|
+
exports.SELF_CONTEXT_TYPE = 'cross_session';
|
|
170
|
+
/** Canonical verb names. */
|
|
171
|
+
exports.CANON_VERB_NAMES = [
|
|
172
|
+
'send_to',
|
|
173
|
+
'request_input',
|
|
174
|
+
'request_approval',
|
|
175
|
+
'check_approval',
|
|
176
|
+
'send_card',
|
|
177
|
+
'request_card',
|
|
178
|
+
'share_contact',
|
|
179
|
+
'react',
|
|
180
|
+
'forward',
|
|
181
|
+
'create_group',
|
|
182
|
+
'add_member',
|
|
183
|
+
'remove_member',
|
|
184
|
+
'leave_conversation',
|
|
185
|
+
'list_contacts',
|
|
186
|
+
'list_contact_requests',
|
|
187
|
+
'list_conversations',
|
|
188
|
+
];
|
|
189
|
+
/**
|
|
190
|
+
* Error code on the 400 a create_group receives when NO member is directly
|
|
191
|
+
* addable (approval-required members can only be invited to an existing
|
|
192
|
+
* group; a creator-only group is not created). The error detail carries the
|
|
193
|
+
* partition: pendingRequired[] and skipped[].
|
|
194
|
+
*/
|
|
195
|
+
exports.CREATE_GROUP_NO_ADDABLE_MEMBERS_CODE = 'CREATE_GROUP_NO_ADDABLE_MEMBERS';
|
|
196
|
+
/**
|
|
197
|
+
* Error code on the 403 a create_group receives when the CREATOR itself is
|
|
198
|
+
* a coding agent requiring explicit session setup: the group helper prepares
|
|
199
|
+
* session configs for every coding-agent member including the creator, and
|
|
200
|
+
* only the agent's human owner may provide them (v1 scoping — the owner
|
|
201
|
+
* creates the group from the app instead).
|
|
202
|
+
*/
|
|
203
|
+
exports.CREATE_GROUP_CREATOR_SETUP_REQUIRED_CODE = 'CREATE_GROUP_CREATOR_SETUP_REQUIRED';
|
|
204
|
+
/** Flat tool name for a verb on namespaced tool surfaces (e.g. `canon_send_to`). */
|
|
205
|
+
function canonVerbToolName(verb) {
|
|
206
|
+
return `${exports.CANON_VERB_NAMESPACE}_${verb}`;
|
|
207
|
+
}
|
|
208
|
+
const utf8Bytes = (value) => new TextEncoder().encode(value).length;
|
|
209
|
+
function checkTextBytes(path, value, into) {
|
|
210
|
+
if (typeof value === 'string' && utf8Bytes(value) > exports.VERB_LIMITS.messageTextBytes) {
|
|
211
|
+
into.push({
|
|
212
|
+
path,
|
|
213
|
+
message: `text exceeds ${exports.VERB_LIMITS.messageTextBytes} UTF-8 bytes (sendMessage.ts MAX_MESSAGE_TEXT_BYTES)`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function checkMessageId(path, value, into) {
|
|
218
|
+
if (typeof value === 'string' && utf8Bytes(value) > exports.VERB_LIMITS.messageIdBytes) {
|
|
219
|
+
into.push({
|
|
220
|
+
path,
|
|
221
|
+
message: `messageId exceeds ${exports.VERB_LIMITS.messageIdBytes} UTF-8 bytes (sendMessage.ts MAX_MESSAGE_ID_BYTES)`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The server limits JSON Schema cannot express: UTF-8 byte caps and
|
|
227
|
+
* serialized-JSON length caps. Bindings MUST run this after schema
|
|
228
|
+
* validation; the server enforces the same checks with 400s.
|
|
229
|
+
*/
|
|
230
|
+
function findVerbByteLimitViolations(verb, input) {
|
|
231
|
+
const violations = [];
|
|
232
|
+
if (verb === 'send_to' || verb === 'share_contact' || verb === 'forward') {
|
|
233
|
+
checkTextBytes('text', input.text, violations);
|
|
234
|
+
}
|
|
235
|
+
if (verb === 'share_contact') {
|
|
236
|
+
checkMessageId('messageId', input.messageId, violations);
|
|
237
|
+
}
|
|
238
|
+
if (verb === 'send_to') {
|
|
239
|
+
const options = input.messageOptions;
|
|
240
|
+
if (options && typeof options === 'object' && !Array.isArray(options)) {
|
|
241
|
+
const record = options;
|
|
242
|
+
checkMessageId('messageOptions.messageId', record.messageId, violations);
|
|
243
|
+
if (record.metadata !== undefined) {
|
|
244
|
+
const serialized = JSON.stringify(record.metadata);
|
|
245
|
+
if (typeof serialized === 'string' && serialized.length > exports.VERB_LIMITS.messageMetadataJsonChars) {
|
|
246
|
+
violations.push({
|
|
247
|
+
path: 'messageOptions.metadata',
|
|
248
|
+
message: `metadata serializes to more than ${exports.VERB_LIMITS.messageMetadataJsonChars} JSON characters `
|
|
249
|
+
+ '(sendMessage.ts checks JSON.stringify length)',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (verb === 'send_card' || verb === 'request_card') {
|
|
256
|
+
if (input.card !== undefined) {
|
|
257
|
+
const serialized = JSON.stringify(input.card);
|
|
258
|
+
if (typeof serialized === 'string' && utf8Bytes(serialized) > exports.VERB_LIMITS.cardEnvelopeBytes) {
|
|
259
|
+
violations.push({
|
|
260
|
+
path: 'card',
|
|
261
|
+
message: `card serializes to more than ${exports.VERB_LIMITS.cardEnvelopeBytes} bytes (interactionCard.ts MAX_CARD_BYTES)`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return violations;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Machine-readable limits companion to the schema bundle, emitted as
|
|
270
|
+
* dist/canon-verbs.limits.json so non-TypeScript bindings can apply the
|
|
271
|
+
* byte-sensitive checks JSON Schema cannot express.
|
|
272
|
+
*/
|
|
273
|
+
exports.CANON_VERB_LIMITS_ARTIFACT = {
|
|
274
|
+
schemaVersion: exports.CANON_VERBS_SCHEMA_VERSION,
|
|
275
|
+
limits: exports.VERB_LIMITS,
|
|
276
|
+
rateLimits: exports.VERB_RATE_LIMITS,
|
|
277
|
+
idPatterns: exports.VERB_ID_PATTERNS,
|
|
278
|
+
byteSemantics: {
|
|
279
|
+
'send_to.text': 'utf8_bytes<=messageTextBytes',
|
|
280
|
+
'share_contact.text': 'utf8_bytes<=messageTextBytes',
|
|
281
|
+
'forward.text': 'utf8_bytes<=messageTextBytes',
|
|
282
|
+
'send_to.messageOptions.messageId': 'utf8_bytes<=messageIdBytes',
|
|
283
|
+
'share_contact.messageId': 'utf8_bytes<=messageIdBytes',
|
|
284
|
+
'send_to.messageOptions.metadata': 'json_stringify_chars<=messageMetadataJsonChars',
|
|
285
|
+
'send_card.card': 'json_utf8_bytes<=cardEnvelopeBytes',
|
|
286
|
+
'request_card.card': 'json_utf8_bytes<=cardEnvelopeBytes',
|
|
287
|
+
},
|
|
288
|
+
};
|