@tomradex/plugin-sdk 0.0.0-stage → 0.2.16

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.
@@ -0,0 +1,73 @@
1
+ import { SDK_CONTRACT } from "./manifest.js";
2
+ import { compareVersions, parseVersion, satisfies } from "./semver.js";
3
+ /**
4
+ * The contract each value the SDK exports first shipped in. A plugin imports
5
+ * the SDK from the Tomrade it runs in, not from its own bundle, so a name the
6
+ * oldest Tomrade its `engines.sdk` accepts does not have fails the plugin as it
7
+ * loads. A new export is listed here under the contract it ships in.
8
+ */
9
+ export const SDK_EXPORTS = {
10
+ ContributesSchema: "0.2.0",
11
+ MANIFEST_FILE: "0.2.0",
12
+ ManifestSchema: "0.2.0",
13
+ NAMESPACE: "0.2.0",
14
+ OFFICIAL_PUBLISHER: "0.2.0",
15
+ OSI_LICENSES: "0.2.0",
16
+ PLUGIN_ID: "0.2.0",
17
+ PermissionsSchema: "0.2.0",
18
+ RESERVED_NAMESPACES: "0.2.0",
19
+ SDK_CONTRACT: "0.2.0",
20
+ SDK_PLUGIN: "0.2.0",
21
+ ToolInputError: "0.2.0",
22
+ assertOwned: "0.2.0",
23
+ compareVersions: "0.2.0",
24
+ definePlugin: "0.2.0",
25
+ defineTool: "0.2.0",
26
+ isOpenSource: "0.2.0",
27
+ isSdkPlugin: "0.2.0",
28
+ isVersion: "0.2.0",
29
+ nameOf: "0.2.0",
30
+ namespaceOf: "0.2.0",
31
+ ownsId: "0.2.0",
32
+ parseManifest: "0.2.0",
33
+ parseVersion: "0.2.0",
34
+ publisherOf: "0.2.0",
35
+ requireProjectId: "0.2.0",
36
+ riskOf: "0.2.0",
37
+ satisfies: "0.2.0",
38
+ ImDeliveryError: "0.2.1",
39
+ IM_CAPABILITIES_V2: "0.2.5",
40
+ IM_MARKDOWN_DIALECTS: "0.2.5",
41
+ ImCapabilitiesSchema: "0.2.5",
42
+ resolveImCapabilities: "0.2.5",
43
+ splitImText: "0.2.5",
44
+ toImDialect: "0.2.5",
45
+ isRange: "0.2.7",
46
+ LANGUAGE_PACK_LIMITS: "0.2.8",
47
+ LanguageEntrySchema: "0.2.8",
48
+ LanguagePackFileSchema: "0.2.8",
49
+ languageCoverage: "0.2.8",
50
+ parseLanguagePack: "0.2.8",
51
+ slotsOf: "0.2.8",
52
+ SDK_EXPORTS: "0.2.9",
53
+ lowestContract: "0.2.9",
54
+ pickLanguage: "0.2.9",
55
+ wordsFor: "0.2.9",
56
+ RowSchema: "0.2.13",
57
+ CLI_HOOK_COVERAGE: "0.2.15",
58
+ CLI_HOOK_EVENTS: "0.2.15"
59
+ };
60
+ /** The oldest contract `range` accepts, up to this SDK's own; null when it accepts none of them. */
61
+ export function lowestContract(range) {
62
+ const known = [...Object.values(SDK_EXPORTS), SDK_CONTRACT];
63
+ // Every patch of each minor the SDK has shipped, since a contract that added no export is still one a Tomrade may run.
64
+ const lines = new Map();
65
+ for (const v of known) {
66
+ const p = parseVersion(v);
67
+ if (p)
68
+ lines.set(`${p[0]}.${p[1]}`, Math.max(lines.get(`${p[0]}.${p[1]}`) ?? 0, p[2]));
69
+ }
70
+ const candidates = [...lines].flatMap(([line, last]) => Array.from({ length: last + 1 }, (_, patch) => `${line}.${patch}`)).sort(compareVersions);
71
+ return candidates.find((v) => compareVersions(v, SDK_CONTRACT) <= 0 && satisfies(v, range)) ?? null;
72
+ }
73
+ //# sourceMappingURL=exports.js.map
@@ -0,0 +1,10 @@
1
+ import type { ImMarkdownDialect } from "./im.js";
2
+ /** Converts the common layer's Markdown into an adapter's dialect. */
3
+ export declare function toImDialect(markdown: string, dialect: ImMarkdownDialect): string;
4
+ /**
5
+ * Splits Markdown into messages that each fit `max` once converted, breaking
6
+ * between lines. A code block stays whole when it fits a message; a longer one
7
+ * is closed and reopened with its language rather than cut mid-fence.
8
+ */
9
+ export declare function splitImText(markdown: string, max: number, dialect?: ImMarkdownDialect): string[];
10
+ //# sourceMappingURL=im-markdown.d.ts.map
@@ -0,0 +1,257 @@
1
+ const ESCAPABLE = /[\\`*_~[\]()#>+\-.!|{}]/;
2
+ function inline(src) {
3
+ const out = [];
4
+ let v = "", raw = "", i = 0;
5
+ const flush = () => { if (raw)
6
+ out.push({ t: "text", v, raw }); v = raw = ""; };
7
+ const span = (open, close = open) => {
8
+ if (!src.startsWith(open, i) || /\s/.test(src[i + open.length] ?? " "))
9
+ return -1;
10
+ if (open === "_" && /[\p{L}\p{N}]/u.test(src[i - 1] ?? ""))
11
+ return -1;
12
+ for (let j = src.indexOf(close, i + open.length + 1); j > 0; j = src.indexOf(close, j + 1)) {
13
+ if (/\s/.test(src[j - 1]))
14
+ continue;
15
+ // A single delimiter must not be half of a double one.
16
+ if (close.length === 1 && (src[j + 1] === close || src[j - 1] === close))
17
+ continue;
18
+ if (close === "_" && /[\p{L}\p{N}]/u.test(src[j + 1] ?? ""))
19
+ continue;
20
+ return j;
21
+ }
22
+ return -1;
23
+ };
24
+ while (i < src.length) {
25
+ const ch = src[i];
26
+ if (ch === "\\" && ESCAPABLE.test(src[i + 1] ?? "")) {
27
+ v += src[i + 1];
28
+ raw += src.slice(i, i + 2);
29
+ i += 2;
30
+ continue;
31
+ }
32
+ if (ch === "`") {
33
+ const run = /^`+/.exec(src.slice(i))[0];
34
+ const end = src.indexOf(run, i + run.length);
35
+ if (end > 0) {
36
+ flush();
37
+ out.push({ t: "code", v: src.slice(i + run.length, end).replace(/^ ([\s\S]*) $/, "$1") });
38
+ i = end + run.length;
39
+ continue;
40
+ }
41
+ v += run;
42
+ raw += run;
43
+ i += run.length;
44
+ continue;
45
+ }
46
+ if (ch === "[") {
47
+ const m = /^\[([^\]\n]+)\]\(([^)\s]+)\)/.exec(src.slice(i));
48
+ if (m) {
49
+ flush();
50
+ out.push({ t: "link", label: inline(m[1]), url: m[2] });
51
+ i += m[0].length;
52
+ continue;
53
+ }
54
+ }
55
+ if (ch === "<") {
56
+ const m = /^<(https?:\/\/[^>\s]+)>/.exec(src.slice(i));
57
+ if (m) {
58
+ flush();
59
+ out.push({ t: "link", label: [{ t: "text", v: m[1], raw: m[1] }], url: m[1] });
60
+ i += m[0].length;
61
+ continue;
62
+ }
63
+ }
64
+ let matched = false;
65
+ for (const [d, t] of [["**", "b"], ["__", "b"], ["~~", "s"], ["*", "i"], ["_", "i"]]) {
66
+ const j = span(d);
67
+ if (j < 0)
68
+ continue;
69
+ flush();
70
+ out.push({ t, c: inline(src.slice(i + d.length, j)) });
71
+ i = j + d.length;
72
+ matched = true;
73
+ break;
74
+ }
75
+ if (matched)
76
+ continue;
77
+ v += ch;
78
+ raw += ch;
79
+ i++;
80
+ }
81
+ flush();
82
+ return out;
83
+ }
84
+ const tick = (v) => { const fence = v.includes("`") ? "``" : "`"; return `${fence}${fence.length > 1 ? " " : ""}${v}${fence.length > 1 ? " " : ""}${fence}`; };
85
+ const fence = (lang, body) => `\`\`\`${lang}\n${body}\n\`\`\``;
86
+ const html = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
87
+ const plainText = (nodes) => nodes.map(n => n.t === "text" || n.t === "code" ? n.v : n.t === "link" ? plainText(n.label) : plainText(n.c)).join("");
88
+ /** Markdown-family platforms; each drops what it does not render. */
89
+ const markdownish = (o) => ({
90
+ text: t => t.raw, code: tick, pre: fence, link: (l, u) => `[${l}](${u})`,
91
+ b: s => `**${s}**`, i: s => o.italic ? `*${s}*` : s, s: s => o.strike ? `~~${s}~~` : s,
92
+ heading: (n, s) => n <= o.headings ? `${"#".repeat(n)} ${s}` : `**${s}**`,
93
+ bullet: (ind, s) => `${ind}- ${s}`, ordered: (ind, n, s) => `${ind}${n}. ${s}`, quote: s => `> ${s}`, hr: o.hr
94
+ });
95
+ const WRITERS = {
96
+ plain: {
97
+ text: t => t.v, code: v => v, pre: (_l, b) => b, link: (l, u) => l === u ? u : `${l} (${u})`,
98
+ b: s => s, i: s => s, s: s => s, heading: (_n, s) => s,
99
+ bullet: (ind, s) => `${ind}• ${s}`, ordered: (ind, n, s) => `${ind}${n}. ${s}`, quote: s => `> ${s}`, hr: "———"
100
+ },
101
+ slack: {
102
+ text: t => html(t.v), code: v => tick(html(v)), pre: (_l, b) => fence("", html(b)),
103
+ link: (_l, u, plain) => `<${u.replace(/[|>]/g, encodeURIComponent)}|${html(plain).replace(/\|/g, "¦")}>`,
104
+ b: s => `*${s}*`, i: s => `_${s}_`, s: s => `~${s}~`, heading: (_n, s) => `*${s}*`,
105
+ bullet: (ind, s) => `${ind}• ${s}`, ordered: (ind, n, s) => `${ind}${n}. ${s}`, quote: s => `> ${s}`, hr: "———"
106
+ },
107
+ "telegram-html": {
108
+ text: t => html(t.v), code: v => `<code>${html(v)}</code>`,
109
+ pre: (l, b) => l ? `<pre><code class="language-${html(l)}">${html(b)}</code></pre>` : `<pre>${html(b)}</pre>`,
110
+ link: (l, u) => `<a href="${html(u).replace(/"/g, "&quot;")}">${l}</a>`,
111
+ b: s => `<b>${s}</b>`, i: s => `<i>${s}</i>`, s: s => `<s>${s}</s>`, heading: (_n, s) => `<b>${s}</b>`,
112
+ bullet: (ind, s) => `${ind}• ${s}`, ordered: (ind, n, s) => `${ind}${n}. ${s}`, quote: s => `<blockquote>${s}</blockquote>`, hr: "———",
113
+ finish: out => out.replace(/<\/blockquote>\n<blockquote>/g, "\n")
114
+ },
115
+ discord: markdownish({ headings: 3, italic: true, strike: true, hr: "———" }),
116
+ lark: markdownish({ headings: 0, italic: true, strike: true, hr: "---" }),
117
+ dingtalk: markdownish({ headings: 6, italic: true, strike: false, hr: "---" }),
118
+ wecom: markdownish({ headings: 6, italic: false, strike: false, hr: "———" })
119
+ };
120
+ function renderInline(nodes, w) {
121
+ return nodes.map(n => {
122
+ switch (n.t) {
123
+ case "text": return w.text(n);
124
+ case "code": return w.code(n.v);
125
+ case "link": return w.link(renderInline(n.label, w), n.url, plainText(n.label));
126
+ default: return w[n.t](renderInline(n.c, w));
127
+ }
128
+ }).join("");
129
+ }
130
+ const FENCE = /^ {0,3}(`{3,}|~{3,})\s*([\w+#.-]*)\s*$/;
131
+ function units(md) {
132
+ const out = [];
133
+ const lines = md.split("\n");
134
+ for (let i = 0; i < lines.length; i++) {
135
+ const open = FENCE.exec(lines[i]);
136
+ if (!open) {
137
+ out.push({ t: "line", v: lines[i] });
138
+ continue;
139
+ }
140
+ const body = [];
141
+ let j = i + 1;
142
+ for (; j < lines.length; j++) {
143
+ const close = /^ {0,3}(`{3,}|~{3,})\s*$/.exec(lines[j]);
144
+ if (close && close[1][0] === open[1][0] && close[1].length >= open[1].length)
145
+ break;
146
+ body.push(lines[j]);
147
+ }
148
+ out.push({ t: "code", lang: open[2], lines: body });
149
+ i = j;
150
+ }
151
+ return out;
152
+ }
153
+ function renderLine(line, w) {
154
+ let m;
155
+ if ((m = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line)))
156
+ return w.heading(m[1].length, renderInline(inline(m[2]), w));
157
+ if (/^ {0,3}([-*_])( *\1){2,} *$/.test(line))
158
+ return w.hr;
159
+ if ((m = /^(\s*)[-*+]\s+(.*)$/.exec(line)))
160
+ return w.bullet(m[1], renderInline(inline(m[2]), w));
161
+ if ((m = /^(\s*)(\d{1,9})[.)]\s+(.*)$/.exec(line)))
162
+ return w.ordered(m[1], m[2], renderInline(inline(m[3]), w));
163
+ if ((m = /^ {0,3}>\s?(.*)$/.exec(line)))
164
+ return w.quote(renderLine(m[1], w));
165
+ return renderInline(inline(line), w);
166
+ }
167
+ /** Converts the common layer's Markdown into an adapter's dialect. */
168
+ export function toImDialect(markdown, dialect) {
169
+ if (dialect === "markdown")
170
+ return markdown;
171
+ const w = WRITERS[dialect];
172
+ const out = units(markdown).map(u => u.t === "code" ? w.pre(u.lang, u.lines.join("\n")) : renderLine(u.v, w)).join("\n");
173
+ return w.finish ? w.finish(out) : out;
174
+ }
175
+ /** The longest prefix that still fits, never cutting a surrogate pair, preferring a space. */
176
+ function hardSplit(s, fits) {
177
+ const pieces = [];
178
+ let rest = s;
179
+ while (rest && !fits(rest)) {
180
+ let lo = 1, hi = rest.length - 1;
181
+ while (lo < hi) {
182
+ const mid = Math.ceil((lo + hi) / 2);
183
+ if (fits(rest.slice(0, mid)))
184
+ lo = mid;
185
+ else
186
+ hi = mid - 1;
187
+ }
188
+ let n = lo;
189
+ if (/[\uD800-\uDBFF]/.test(rest[n - 1]) && n > 1)
190
+ n--;
191
+ const space = rest.lastIndexOf(" ", n - 1);
192
+ if (space > n / 2)
193
+ n = space + 1;
194
+ pieces.push(rest.slice(0, n));
195
+ rest = rest.slice(n);
196
+ }
197
+ if (rest)
198
+ pieces.push(rest);
199
+ return pieces;
200
+ }
201
+ /**
202
+ * Splits Markdown into messages that each fit `max` once converted, breaking
203
+ * between lines. A code block stays whole when it fits a message; a longer one
204
+ * is closed and reopened with its language rather than cut mid-fence.
205
+ */
206
+ export function splitImText(markdown, max, dialect = "markdown") {
207
+ const render = (s) => toImDialect(s, dialect);
208
+ const fits = (s) => render(s).length <= max;
209
+ if (fits(markdown))
210
+ return [render(markdown)];
211
+ const pieces = [];
212
+ for (const u of units(markdown)) {
213
+ if (u.t === "line") {
214
+ pieces.push(...hardSplit(u.v, fits));
215
+ continue;
216
+ }
217
+ const whole = fence(u.lang, u.lines.join("\n"));
218
+ if (fits(whole)) {
219
+ pieces.push(whole);
220
+ continue;
221
+ }
222
+ let group = [];
223
+ const wrapped = (lines) => fence(u.lang, lines.join("\n"));
224
+ for (const line of u.lines) {
225
+ if (fits(wrapped([...group, line]))) {
226
+ group.push(line);
227
+ continue;
228
+ }
229
+ if (group.length)
230
+ pieces.push(wrapped(group));
231
+ group = [];
232
+ for (const part of hardSplit(line, p => fits(wrapped([p])))) {
233
+ if (fits(wrapped([...group, part])))
234
+ group.push(part);
235
+ else {
236
+ pieces.push(wrapped(group));
237
+ group = [part];
238
+ }
239
+ }
240
+ }
241
+ if (group.length)
242
+ pieces.push(wrapped(group));
243
+ }
244
+ const chunks = [];
245
+ let current = [];
246
+ for (const piece of pieces) {
247
+ if (current.length && !fits([...current, piece].join("\n"))) {
248
+ chunks.push(current.join("\n"));
249
+ current = [];
250
+ }
251
+ current.push(piece);
252
+ }
253
+ if (current.length)
254
+ chunks.push(current.join("\n"));
255
+ return chunks.map(c => c.replace(/^\n+|\n+$/g, "")).filter(Boolean).map(render);
256
+ }
257
+ //# sourceMappingURL=im-markdown.js.map
package/dist/im.d.ts ADDED
@@ -0,0 +1,319 @@
1
+ import { z } from "zod";
2
+ import type { Localized } from "./contract.js";
3
+ /** Provider-neutral transport contract. Adapters never receive Agent or project access. */
4
+ export interface ImAddress {
5
+ tenantId: string;
6
+ conversationId: string;
7
+ topicId?: string;
8
+ }
9
+ export interface ImAttachmentRef {
10
+ id: string;
11
+ name: string;
12
+ mimeType: string;
13
+ size: number;
14
+ }
15
+ /** SDK 0.2.5. An earlier message the adapter read beside an inbound one: the one it quotes, or its thread's first message. */
16
+ export interface ImContextMessage {
17
+ role: "quoted" | "root";
18
+ messageId: string;
19
+ author: {
20
+ id: string;
21
+ name?: string;
22
+ isBot: boolean;
23
+ };
24
+ text: string;
25
+ }
26
+ /**
27
+ * A native slash command or menu entry arrives as a message whose text is the
28
+ * plain command (`/stop`), so plain-text commands keep working everywhere.
29
+ */
30
+ export type ImInbound = {
31
+ eventId: string;
32
+ address: ImAddress;
33
+ actor: {
34
+ id: string;
35
+ name?: string;
36
+ isBot: boolean;
37
+ };
38
+ } & (
39
+ /** `ambient` (SDK 0.2.5): a group message that neither mentions nor replies to the bot, sent only by adapters that receive every group message. */
40
+ {
41
+ kind: "message";
42
+ messageId: string;
43
+ text: string;
44
+ activation: "dm" | "mention" | "reply" | "ambient";
45
+ attachments?: ImAttachmentRef[];
46
+ permalink?: string;
47
+ /** SDK 0.2.5. The message this one quotes or replies to; replying to the progress message steers the running turn. */
48
+ replyTo?: string;
49
+ /** SDK 0.2.5. What the adapter already holds of the quoted message and the thread's first message, so the Agent sees what is being discussed. */
50
+ context?: ImContextMessage[];
51
+ }
52
+ /** SDK 0.2.5. The person withdrew or edited a message they sent; a queued one that was withdrawn never runs, an edit never reruns. */
53
+ | {
54
+ kind: "retract";
55
+ messageId: string;
56
+ } | {
57
+ kind: "edit";
58
+ messageId: string;
59
+ }
60
+ /** An adapter acknowledges the platform within its callback deadline before awaiting `receive`; the outcome updates the card later. */
61
+ | {
62
+ kind: "action";
63
+ token: string;
64
+ value?: string | string[];
65
+ });
66
+ /** How an adapter writes formatted text. `markdown` passes the common layer's Markdown through unchanged. */
67
+ export type ImMarkdownDialect = "plain" | "markdown" | "slack" | "telegram-html" | "discord" | "lark" | "dingtalk" | "wecom";
68
+ export interface ImButton {
69
+ label: string;
70
+ token: string;
71
+ /** SDK 0.2.5. A hint; adapters without styled buttons ignore it. */
72
+ style?: "default" | "primary" | "danger";
73
+ }
74
+ /** SDK 0.2.5. Structured parts beside the text; the common layer folds what an adapter cannot show into `text`. */
75
+ export type ImElement = {
76
+ type: "section";
77
+ title?: string;
78
+ text?: string;
79
+ fields?: {
80
+ label: string;
81
+ value: string;
82
+ }[];
83
+ } | {
84
+ type: "links";
85
+ links: {
86
+ label: string;
87
+ url: string;
88
+ }[];
89
+ }
90
+ /** Answered with an action carrying the chosen value, or the list of values when `multiple`. */
91
+ | {
92
+ type: "select";
93
+ token: string;
94
+ placeholder?: string;
95
+ multiple?: boolean;
96
+ options: {
97
+ label: string;
98
+ value: string;
99
+ }[];
100
+ }
101
+ /** A free-text answer entry, answered with an action whose value is the text. */
102
+ | {
103
+ type: "input";
104
+ token: string;
105
+ label: string;
106
+ placeholder?: string;
107
+ };
108
+ export interface ImContent {
109
+ /** Already written in the adapter's declared Markdown dialect, and within its length limit. */
110
+ text: string;
111
+ actions?: ImButton[];
112
+ elements?: ImElement[];
113
+ files?: {
114
+ id: string;
115
+ name: string;
116
+ mimeType: string;
117
+ size: number;
118
+ }[];
119
+ }
120
+ export interface ImDelivery {
121
+ /** Stable across retries. A provider should use its native deduplication where available. */
122
+ operationId: string;
123
+ address: ImAddress;
124
+ replaceMessageId?: string;
125
+ /** SDK 0.2.5. Post inside this thread: the address's topic, or the triggering message on platforms with `threads`. */
126
+ threadId?: string;
127
+ /** SDK 0.2.5. Quote-reply to this message, on platforms without threads. */
128
+ replyTo?: string;
129
+ /** SDK 0.2.5. Show only to this actor; set only for adapters declaring `ephemeral`. */
130
+ visibleTo?: string;
131
+ content: ImContent;
132
+ }
133
+ export type ImConnectionState = "connecting" | "connected" | "offline" | "error";
134
+ /** The common layer's commands, for registering as native slash commands or menu entries. */
135
+ export interface ImCommand {
136
+ name: string;
137
+ description: string;
138
+ /** Shown as the argument hint, e.g. `token answer`. */
139
+ args?: string;
140
+ }
141
+ /** A reaction's meaning; the adapter picks the emoji (👀 / ✅ / ❌) and replaces the previous one. */
142
+ export type ImReaction = "received" | "done" | "failed";
143
+ export interface ImAdapterSession {
144
+ deliver(command: ImDelivery): Promise<{
145
+ messageId: string;
146
+ }>;
147
+ download?(ref: ImAttachmentRef): Promise<Uint8Array>;
148
+ /** SDK 0.2.5. Typing or the platform's "thinking" status; `idle` clears it. Best effort. */
149
+ indicate?(command: {
150
+ address: ImAddress;
151
+ threadId?: string;
152
+ state: "typing" | "thinking" | "idle";
153
+ }): Promise<void>;
154
+ /** SDK 0.2.5. Reacts to a message the person sent. Best effort. */
155
+ react?(command: {
156
+ address: ImAddress;
157
+ messageId: string;
158
+ reaction: ImReaction;
159
+ }): Promise<void>;
160
+ /** SDK 0.2.5. A native streaming message, used when `stream` is `native`. */
161
+ stream?: {
162
+ start(command: ImDelivery): Promise<{
163
+ messageId: string;
164
+ }>;
165
+ append(command: {
166
+ operationId: string;
167
+ address: ImAddress;
168
+ messageId: string;
169
+ delta: string;
170
+ }): Promise<void>;
171
+ /** Settles the message with its final content, which may differ from what was appended. */
172
+ stop(command: {
173
+ operationId: string;
174
+ address: ImAddress;
175
+ messageId: string;
176
+ content: ImContent;
177
+ }): Promise<{
178
+ messageId: string;
179
+ }>;
180
+ };
181
+ /** SDK 0.2.5. Registers the commands natively, when `nativeCommands`. Called after every connect. */
182
+ commands?(commands: ImCommand[]): Promise<void>;
183
+ /** SDK 0.2.5. The bot the credentials signed in as, checked against the platform; shown so the person sees setup worked. */
184
+ identity?: {
185
+ id: string;
186
+ name?: string;
187
+ };
188
+ close(): void | Promise<void>;
189
+ }
190
+ /** SDK 0.2.5. One value a person copies from the platform's developer console. */
191
+ export interface ImCredentialField {
192
+ key: string;
193
+ label: Localized;
194
+ /** Drawn as a password field. Every value is sealed by the OS keychain either way. */
195
+ secret?: boolean;
196
+ optional?: boolean;
197
+ placeholder?: string;
198
+ help?: Localized;
199
+ }
200
+ /** SDK 0.2.5. What connecting one bot takes, for the settings wizard. */
201
+ export interface ImAdapterSetup {
202
+ fields: ImCredentialField[];
203
+ /** What to do in the platform's console, in order; Markdown. */
204
+ steps: Localized[];
205
+ /** Scopes or permissions the app needs. */
206
+ permissions?: string[];
207
+ /** An app manifest to paste, e.g. Slack's. */
208
+ manifest?: {
209
+ format: "json" | "yaml";
210
+ text: string;
211
+ };
212
+ docsUrl?: string;
213
+ }
214
+ /** What the platform can do. Fields marked 0.2.5 are optional; leaving them out declares the 0.2.2 behaviour. */
215
+ export interface ImCapabilities {
216
+ edit: boolean;
217
+ buttons: boolean;
218
+ inboundFiles: boolean;
219
+ outboundFiles: boolean;
220
+ maxTextLength: number;
221
+ maxFileBytes: number;
222
+ /** SDK 0.2.5. Replies can go into a thread under the triggering message. */
223
+ threads?: boolean;
224
+ reactions?: boolean;
225
+ typing?: boolean;
226
+ /** `native`: session.stream. `edit`: throttled edits of one message. `none`: only the final message. */
227
+ stream?: "native" | "edit" | "none";
228
+ select?: boolean;
229
+ multiSelect?: boolean;
230
+ /** Free-text answer entries (`input` elements). */
231
+ textInput?: boolean;
232
+ /** Sections, fields and links render natively. */
233
+ cards?: boolean;
234
+ /** A message with buttons or selects can be updated in place; defaults to `edit`. */
235
+ cardUpdate?: boolean;
236
+ nativeCommands?: boolean;
237
+ ephemeral?: boolean;
238
+ /** Defaults to `markdown`: the text arrives as the common layer wrote it. */
239
+ markdown?: ImMarkdownDialect;
240
+ /** Messages and edits allowed per conversation in a window, e.g. WeCom's 30 a minute. */
241
+ rateLimit?: {
242
+ messages: number;
243
+ windowMs: number;
244
+ };
245
+ }
246
+ export declare const IM_MARKDOWN_DIALECTS: readonly ["plain", "markdown", "slack", "telegram-html", "discord", "lark", "dingtalk", "wecom"];
247
+ /** The capability fields only SDK 0.2.5 understands; declaring one needs `engines.sdk` of at least that. */
248
+ export declare const IM_CAPABILITIES_V2: readonly ["threads", "reactions", "typing", "stream", "select", "multiSelect", "textInput", "cards", "cardUpdate", "nativeCommands", "ephemeral", "markdown", "rateLimit"];
249
+ export declare const ImCapabilitiesSchema: z.ZodObject<{
250
+ edit: z.ZodBoolean;
251
+ buttons: z.ZodBoolean;
252
+ inboundFiles: z.ZodBoolean;
253
+ outboundFiles: z.ZodBoolean;
254
+ maxTextLength: z.ZodNumber;
255
+ maxFileBytes: z.ZodNumber;
256
+ threads: z.ZodOptional<z.ZodBoolean>;
257
+ reactions: z.ZodOptional<z.ZodBoolean>;
258
+ typing: z.ZodOptional<z.ZodBoolean>;
259
+ stream: z.ZodOptional<z.ZodEnum<{
260
+ edit: "edit";
261
+ native: "native";
262
+ none: "none";
263
+ }>>;
264
+ select: z.ZodOptional<z.ZodBoolean>;
265
+ multiSelect: z.ZodOptional<z.ZodBoolean>;
266
+ textInput: z.ZodOptional<z.ZodBoolean>;
267
+ cards: z.ZodOptional<z.ZodBoolean>;
268
+ cardUpdate: z.ZodOptional<z.ZodBoolean>;
269
+ nativeCommands: z.ZodOptional<z.ZodBoolean>;
270
+ ephemeral: z.ZodOptional<z.ZodBoolean>;
271
+ markdown: z.ZodOptional<z.ZodEnum<{
272
+ plain: "plain";
273
+ markdown: "markdown";
274
+ slack: "slack";
275
+ "telegram-html": "telegram-html";
276
+ discord: "discord";
277
+ lark: "lark";
278
+ dingtalk: "dingtalk";
279
+ wecom: "wecom";
280
+ }>>;
281
+ rateLimit: z.ZodOptional<z.ZodObject<{
282
+ messages: z.ZodNumber;
283
+ windowMs: z.ZodNumber;
284
+ }, z.core.$strip>>;
285
+ }, z.core.$strip>;
286
+ /** Every capability spelled out, with the 0.2.2 behaviour where an adapter is silent. */
287
+ export type ResolvedImCapabilities = Required<Omit<ImCapabilities, "rateLimit">> & Pick<ImCapabilities, "rateLimit">;
288
+ export declare function resolveImCapabilities(caps: ImCapabilities): ResolvedImCapabilities;
289
+ export interface ImAdapter {
290
+ /** Carries the plugin's namespace. */
291
+ id: string;
292
+ title: string;
293
+ capabilities: ImCapabilities;
294
+ /** SDK 0.2.5. Without it, connections take no credentials in settings and the adapter keeps its own. */
295
+ setup?: ImAdapterSetup;
296
+ /** Credentials never go into events, routes, Agent context or logs. */
297
+ open(connectionId: string, io: {
298
+ signal: AbortSignal;
299
+ /** SDK 0.2.5. The connection's values for `setup.fields`, decrypted. */
300
+ credentials: Record<string, string>;
301
+ /** Resolves after local persistence, not after the Agent finishes. */
302
+ receive(event: ImInbound): Promise<{
303
+ receiptId: string;
304
+ }>;
305
+ status(state: ImConnectionState, detail?: string): void;
306
+ /** Reads only handles explicitly included in a delivery for this connection. */
307
+ readOutboundFile(id: string): Promise<Uint8Array>;
308
+ }): Promise<ImAdapterSession>;
309
+ }
310
+ export interface ImApi {
311
+ register(adapter: ImAdapter): Promise<() => void>;
312
+ }
313
+ /** Only a known-unsent operation is safe to retry; unknown failures remain uncertain. */
314
+ export declare class ImDeliveryError extends Error {
315
+ readonly disposition: "retryable" | "uncertain" | "permanent";
316
+ readonly retryAfterMs?: number | undefined;
317
+ constructor(message: string, disposition: "retryable" | "uncertain" | "permanent", retryAfterMs?: number | undefined);
318
+ }
319
+ //# sourceMappingURL=im.d.ts.map