@copilotkit/channels 0.1.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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/action-registry.d.ts +59 -0
- package/dist/action-registry.d.ts.map +1 -0
- package/dist/action-registry.js +217 -0
- package/dist/action-registry.test.d.ts +2 -0
- package/dist/action-registry.test.d.ts.map +1 -0
- package/dist/action-registry.test.js +115 -0
- package/dist/action-store.d.ts +21 -0
- package/dist/action-store.d.ts.map +1 -0
- package/dist/action-store.js +23 -0
- package/dist/action-store.test.d.ts +2 -0
- package/dist/action-store.test.d.ts.map +1 -0
- package/dist/action-store.test.js +27 -0
- package/dist/codec.d.ts +25 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +1 -0
- package/dist/commands.d.ts +73 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +38 -0
- package/dist/commands.test.d.ts +2 -0
- package/dist/commands.test.d.ts.map +1 -0
- package/dist/commands.test.js +23 -0
- package/dist/create-bot.d.ts +171 -0
- package/dist/create-bot.d.ts.map +1 -0
- package/dist/create-bot.foundations.test.d.ts +2 -0
- package/dist/create-bot.foundations.test.d.ts.map +1 -0
- package/dist/create-bot.foundations.test.js +147 -0
- package/dist/create-bot.js +576 -0
- package/dist/create-bot.test.d.ts +2 -0
- package/dist/create-bot.test.d.ts.map +1 -0
- package/dist/create-bot.test.js +936 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/mint-id.d.ts +3 -0
- package/dist/mint-id.d.ts.map +1 -0
- package/dist/mint-id.js +20 -0
- package/dist/mint-id.test.d.ts +2 -0
- package/dist/mint-id.test.d.ts.map +1 -0
- package/dist/mint-id.test.js +16 -0
- package/dist/modal-submit.test.d.ts +2 -0
- package/dist/modal-submit.test.d.ts.map +1 -0
- package/dist/modal-submit.test.js +68 -0
- package/dist/platform-adapter.d.ts +312 -0
- package/dist/platform-adapter.d.ts.map +1 -0
- package/dist/platform-adapter.js +1 -0
- package/dist/platform-adapter.test.d.ts +2 -0
- package/dist/platform-adapter.test.d.ts.map +1 -0
- package/dist/platform-adapter.test.js +40 -0
- package/dist/reactions.test.d.ts +2 -0
- package/dist/reactions.test.d.ts.map +1 -0
- package/dist/reactions.test.js +197 -0
- package/dist/run-loop.d.ts +41 -0
- package/dist/run-loop.d.ts.map +1 -0
- package/dist/run-loop.js +102 -0
- package/dist/run-loop.test.d.ts +2 -0
- package/dist/run-loop.test.d.ts.map +1 -0
- package/dist/run-loop.test.js +125 -0
- package/dist/standard-schema.d.ts +52 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +66 -0
- package/dist/state/duration.d.ts +3 -0
- package/dist/state/duration.d.ts.map +1 -0
- package/dist/state/duration.js +16 -0
- package/dist/state/duration.test.d.ts +2 -0
- package/dist/state/duration.test.d.ts.map +1 -0
- package/dist/state/duration.test.js +13 -0
- package/dist/state/kv-action-store.d.ts +7 -0
- package/dist/state/kv-action-store.d.ts.map +1 -0
- package/dist/state/kv-action-store.js +9 -0
- package/dist/state/kv-action-store.test.d.ts +2 -0
- package/dist/state/kv-action-store.test.d.ts.map +1 -0
- package/dist/state/kv-action-store.test.js +21 -0
- package/dist/state/memory-store.d.ts +41 -0
- package/dist/state/memory-store.d.ts.map +1 -0
- package/dist/state/memory-store.js +110 -0
- package/dist/state/memory-store.test.d.ts +2 -0
- package/dist/state/memory-store.test.d.ts.map +1 -0
- package/dist/state/memory-store.test.js +22 -0
- package/dist/state/state-conversation-store.d.ts +14 -0
- package/dist/state/state-conversation-store.d.ts.map +1 -0
- package/dist/state/state-conversation-store.js +22 -0
- package/dist/state/state-conversation-store.test.d.ts +2 -0
- package/dist/state/state-conversation-store.test.d.ts.map +1 -0
- package/dist/state/state-conversation-store.test.js +58 -0
- package/dist/state/state-store.d.ts +63 -0
- package/dist/state/state-store.d.ts.map +1 -0
- package/dist/state/state-store.js +1 -0
- package/dist/telemetry/bot-telemetry.d.ts +31 -0
- package/dist/telemetry/bot-telemetry.d.ts.map +1 -0
- package/dist/telemetry/bot-telemetry.js +70 -0
- package/dist/telemetry/bot-telemetry.test.d.ts +2 -0
- package/dist/telemetry/bot-telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/bot-telemetry.test.js +56 -0
- package/dist/telemetry/create-bot-telemetry.test.d.ts +2 -0
- package/dist/telemetry/create-bot-telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/create-bot-telemetry.test.js +71 -0
- package/dist/telemetry/e2e-telemetry.test.d.ts +2 -0
- package/dist/telemetry/e2e-telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/e2e-telemetry.test.js +56 -0
- package/dist/telemetry/events-catalog.test.d.ts +2 -0
- package/dist/telemetry/events-catalog.test.d.ts.map +1 -0
- package/dist/telemetry/events-catalog.test.js +8 -0
- package/dist/telemetry/install-id.d.ts +8 -0
- package/dist/telemetry/install-id.d.ts.map +1 -0
- package/dist/telemetry/install-id.js +43 -0
- package/dist/telemetry/install-id.test.d.ts +2 -0
- package/dist/telemetry/install-id.test.d.ts.map +1 -0
- package/dist/telemetry/install-id.test.js +43 -0
- package/dist/telemetry/platform-allowlist-coverage.test.d.ts +2 -0
- package/dist/telemetry/platform-allowlist-coverage.test.d.ts.map +1 -0
- package/dist/telemetry/platform-allowlist-coverage.test.js +45 -0
- package/dist/telemetry/sanitize-error.d.ts +3 -0
- package/dist/telemetry/sanitize-error.d.ts.map +1 -0
- package/dist/telemetry/sanitize-error.js +31 -0
- package/dist/telemetry/sanitize-error.test.d.ts +2 -0
- package/dist/telemetry/sanitize-error.test.d.ts.map +1 -0
- package/dist/telemetry/sanitize-error.test.js +33 -0
- package/dist/testing/fake-adapter.d.ts +137 -0
- package/dist/testing/fake-adapter.d.ts.map +1 -0
- package/dist/testing/fake-adapter.js +273 -0
- package/dist/testing/fake-agent.d.ts +28 -0
- package/dist/testing/fake-agent.d.ts.map +1 -0
- package/dist/testing/fake-agent.js +36 -0
- package/dist/testing/state-store-conformance.d.ts +4 -0
- package/dist/testing/state-store-conformance.d.ts.map +1 -0
- package/dist/testing/state-store-conformance.js +117 -0
- package/dist/thread-capabilities.test.d.ts +2 -0
- package/dist/thread-capabilities.test.d.ts.map +1 -0
- package/dist/thread-capabilities.test.js +77 -0
- package/dist/thread-ephemeral.test.d.ts +2 -0
- package/dist/thread-ephemeral.test.d.ts.map +1 -0
- package/dist/thread-ephemeral.test.js +45 -0
- package/dist/thread-reactions.test.d.ts +2 -0
- package/dist/thread-reactions.test.d.ts.map +1 -0
- package/dist/thread-reactions.test.js +38 -0
- package/dist/thread.d.ts +152 -0
- package/dist/thread.d.ts.map +1 -0
- package/dist/thread.js +329 -0
- package/dist/thread.test.d.ts +2 -0
- package/dist/thread.test.d.ts.map +1 -0
- package/dist/thread.test.js +83 -0
- package/dist/tools.d.ts +67 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +38 -0
- package/dist/tools.test.d.ts +2 -0
- package/dist/tools.test.d.ts.map +1 -0
- package/dist/tools.test.js +31 -0
- package/dist/transcripts.d.ts +52 -0
- package/dist/transcripts.d.ts.map +1 -0
- package/dist/transcripts.js +67 -0
- package/dist/transcripts.test.d.ts +2 -0
- package/dist/transcripts.test.d.ts.map +1 -0
- package/dist/transcripts.test.js +108 -0
- package/package.json +72 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @deprecated Configure `createBot({ state })` instead. Action snapshots are stored via `StateStore.kv`. */
|
|
2
|
+
export class InMemoryActionStore {
|
|
3
|
+
map = new Map();
|
|
4
|
+
async put(id, snap, ttlMs) {
|
|
5
|
+
this.map.set(id, {
|
|
6
|
+
snap,
|
|
7
|
+
expiresAt: ttlMs ? Date.now() + ttlMs : undefined,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
async get(id) {
|
|
11
|
+
const e = this.map.get(id);
|
|
12
|
+
if (!e)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (e.expiresAt !== undefined && Date.now() > e.expiresAt) {
|
|
15
|
+
this.map.delete(id);
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return e.snap;
|
|
19
|
+
}
|
|
20
|
+
async delete(id) {
|
|
21
|
+
this.map.delete(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-store.test.d.ts","sourceRoot":"","sources":["../src/action-store.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { InMemoryActionStore } from "./action-store.js";
|
|
3
|
+
describe("InMemoryActionStore", () => {
|
|
4
|
+
it("puts and gets a snapshot", async () => {
|
|
5
|
+
const s = new InMemoryActionStore();
|
|
6
|
+
await s.put("id1", { path: [0], conversationKey: "c" });
|
|
7
|
+
expect(await s.get("id1")).toMatchObject({ path: [0] });
|
|
8
|
+
});
|
|
9
|
+
it("expires entries past ttl", async () => {
|
|
10
|
+
vi.useFakeTimers();
|
|
11
|
+
try {
|
|
12
|
+
const s = new InMemoryActionStore();
|
|
13
|
+
await s.put("id1", { path: [0], conversationKey: "c" }, 1000);
|
|
14
|
+
vi.advanceTimersByTime(1500);
|
|
15
|
+
expect(await s.get("id1")).toBeUndefined();
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
vi.useRealTimers();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
it("deletes entries", async () => {
|
|
22
|
+
const s = new InMemoryActionStore();
|
|
23
|
+
await s.put("id1", { path: [0], conversationKey: "c" });
|
|
24
|
+
await s.delete("id1");
|
|
25
|
+
expect(await s.get("id1")).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
});
|
package/dist/codec.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BotNode } from "@copilotkit/channels-ui";
|
|
2
|
+
/**
|
|
3
|
+
* A pure, dependency-free, per-platform codec shared by the local adapter and
|
|
4
|
+
* the Intelligence side. It exists so platform *semantics* (how to render IR to
|
|
5
|
+
* a native payload, and — later — how to normalize a native event to the
|
|
6
|
+
* neutral ingress shape) live in ONE place, instead of being duplicated between
|
|
7
|
+
* a credentialed local adapter (Bolt/discord.js) and the Connector Outbox /
|
|
8
|
+
* webhook ingress.
|
|
9
|
+
*
|
|
10
|
+
* Only the two creds/connection-bound concerns stay per-side: the transport
|
|
11
|
+
* (who holds the platform connection) and the credentialed send. The codec
|
|
12
|
+
* excludes both — `renderEgress` is pure (IR → native payload); the actual
|
|
13
|
+
* send happens in the Connector Outbox with Intelligence-owned credentials.
|
|
14
|
+
*
|
|
15
|
+
* TODO(OSS-363): add `normalizeIngress(raw): NeutralEvent` once the pure Slack
|
|
16
|
+
* ingress mapping (mention stripping, stable event-id derivation, real-user
|
|
17
|
+
* filtering, field extraction) is extracted from the Bolt listener so both the
|
|
18
|
+
* local adapter and Intelligence's webhook ingress consume the same logic.
|
|
19
|
+
*/
|
|
20
|
+
export interface PlatformCodec {
|
|
21
|
+
readonly platform: string;
|
|
22
|
+
/** IR → native payload. Pure; opaque to bot core. */
|
|
23
|
+
renderEgress(ir: BotNode[]): unknown;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,YAAY,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;CACtC"}
|
package/dist/codec.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { InferSchemaOutput, ObjectSchema } from "./standard-schema.js";
|
|
2
|
+
import type { Thread, PlatformUser, ModalView } from "@copilotkit/channels-ui";
|
|
3
|
+
/**
|
|
4
|
+
* Context handed to a slash-command handler. `text` is the raw argument string
|
|
5
|
+
* (the form Slack delivers); `options` is the parsed, typed form, populated by
|
|
6
|
+
* surfaces that deliver structured args natively (e.g. Discord). On text-only
|
|
7
|
+
* surfaces `options` is empty — read `text` there.
|
|
8
|
+
*/
|
|
9
|
+
export interface CommandContext<TOptions = Record<string, never>> {
|
|
10
|
+
/** The conversation the command was invoked in. */
|
|
11
|
+
thread: Thread;
|
|
12
|
+
/** The invoked command name, normalized (no leading slash, lower-cased). */
|
|
13
|
+
command: string;
|
|
14
|
+
/** Raw argument string after the command name. */
|
|
15
|
+
text: string;
|
|
16
|
+
/** Parsed, typed options (empty on surfaces that only deliver `text`). */
|
|
17
|
+
options: TOptions;
|
|
18
|
+
/** The invoking user, when the surface provides it. */
|
|
19
|
+
user?: PlatformUser;
|
|
20
|
+
platform: string;
|
|
21
|
+
/** Open a modal in response to this command (requires the surface's trigger; `undefined` when unavailable). */
|
|
22
|
+
openModal?(view: ModalView): Promise<{
|
|
23
|
+
ok: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A slash command. Defined like a {@link import("./tools.js").BotTool}: a name,
|
|
29
|
+
* an optional Standard Schema for typed options, and a handler. The `options`
|
|
30
|
+
* schema maps natively to surfaces with structured args (Discord) and is used
|
|
31
|
+
* to register/validate there; on text-only surfaces (Slack) args arrive via
|
|
32
|
+
* `ctx.text`.
|
|
33
|
+
*/
|
|
34
|
+
export interface BotCommand<Schema extends ObjectSchema = ObjectSchema> {
|
|
35
|
+
/** Command name without the leading slash (e.g. `"triage"`). Matched case-insensitively. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Human description — help text, and the registration label on surfaces like Discord. */
|
|
38
|
+
description?: string;
|
|
39
|
+
/** Optional Standard Schema for typed options. */
|
|
40
|
+
options?: Schema;
|
|
41
|
+
handler(ctx: CommandContext<InferSchemaOutput<Schema>>): void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Define a {@link BotCommand} with full type inference: `ctx.options` is
|
|
45
|
+
* inferred from `options`.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* const triage = defineBotCommand({
|
|
49
|
+
* name: "triage",
|
|
50
|
+
* description: "Summarize and file the current thread.",
|
|
51
|
+
* options: z.object({ priority: z.enum(["low", "high"]).optional() }),
|
|
52
|
+
* async handler({ thread, text, options }) {
|
|
53
|
+
* await thread.runAgent({ prompt: `Triage: ${text}`, });
|
|
54
|
+
* },
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function defineBotCommand<Schema extends ObjectSchema>(command: BotCommand<Schema>): BotCommand<Schema>;
|
|
59
|
+
/**
|
|
60
|
+
* Platform-neutral descriptor an adapter may use to register a command with the
|
|
61
|
+
* surface (e.g. Discord's application-command API). Produced from a
|
|
62
|
+
* {@link BotCommand} by {@link toCommandSpec}.
|
|
63
|
+
*/
|
|
64
|
+
export interface CommandSpec {
|
|
65
|
+
name: string;
|
|
66
|
+
description: string;
|
|
67
|
+
/** JSON Schema for the options, or `undefined` for a free-text command. */
|
|
68
|
+
options?: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
/** Normalize a command name for matching: drop a leading slash, lower-case, trim. */
|
|
71
|
+
export declare function normalizeCommandName(name: string): string;
|
|
72
|
+
export declare function toCommandSpec(command: BotCommand): CommandSpec;
|
|
73
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,WAAW,cAAc,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAC9D,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,OAAO,EAAE,QAAQ,CAAC;IAClB,uDAAuD;IACvD,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,+GAA+G;IAC/G,SAAS,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU,CAAC,MAAM,SAAS,YAAY,GAAG,YAAY;IACpE,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,YAAY,EAC1D,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,GAC1B,UAAU,CAAC,MAAM,CAAC,CAEpB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,WAAW,CAU9D"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { toJsonSchema } from "./standard-schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Define a {@link BotCommand} with full type inference: `ctx.options` is
|
|
4
|
+
* inferred from `options`.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const triage = defineBotCommand({
|
|
8
|
+
* name: "triage",
|
|
9
|
+
* description: "Summarize and file the current thread.",
|
|
10
|
+
* options: z.object({ priority: z.enum(["low", "high"]).optional() }),
|
|
11
|
+
* async handler({ thread, text, options }) {
|
|
12
|
+
* await thread.runAgent({ prompt: `Triage: ${text}`, });
|
|
13
|
+
* },
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function defineBotCommand(command) {
|
|
18
|
+
return command;
|
|
19
|
+
}
|
|
20
|
+
/** Normalize a command name for matching: drop a leading slash, lower-case, trim. */
|
|
21
|
+
export function normalizeCommandName(name) {
|
|
22
|
+
// Collapse "-"→"_" so a command routes the same whether invoked as
|
|
23
|
+
// /file-issue (Slack/Discord allow hyphens) or /file_issue (Telegram forbids
|
|
24
|
+
// them; its adapter converts on registration). This is the ROUTING key only —
|
|
25
|
+
// per-adapter display names are decided in each adapter / `toCommandSpec`.
|
|
26
|
+
return name.trim().replace(/^\//, "").toLowerCase().replace(/-/g, "_");
|
|
27
|
+
}
|
|
28
|
+
export function toCommandSpec(command) {
|
|
29
|
+
return {
|
|
30
|
+
// Preserve hyphens here (display/registration name): Discord and Slack
|
|
31
|
+
// accept them, so `/file-issue` stays `/file-issue` there. Telegram's
|
|
32
|
+
// adapter converts hyphens to underscores itself; routing still matches
|
|
33
|
+
// because `normalizeCommandName` collapses both forms.
|
|
34
|
+
name: command.name.trim().replace(/^\//, "").toLowerCase(),
|
|
35
|
+
description: command.description ?? "",
|
|
36
|
+
options: command.options ? toJsonSchema(command.options) : undefined,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.test.d.ts","sourceRoot":"","sources":["../src/commands.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { normalizeCommandName, toCommandSpec, defineBotCommand, } from "./commands.js";
|
|
3
|
+
describe("normalizeCommandName", () => {
|
|
4
|
+
it("strips a leading slash, lowercases, and collapses hyphens to underscores", () => {
|
|
5
|
+
expect(normalizeCommandName("/File-Issue")).toBe("file_issue");
|
|
6
|
+
expect(normalizeCommandName("file-issue")).toBe("file_issue");
|
|
7
|
+
expect(normalizeCommandName("file_issue")).toBe("file_issue");
|
|
8
|
+
expect(normalizeCommandName("agent")).toBe("agent");
|
|
9
|
+
});
|
|
10
|
+
it("routes /file-issue and /file_issue to the same key (so Telegram's converted name still matches)", () => {
|
|
11
|
+
expect(normalizeCommandName("file-issue")).toBe(normalizeCommandName("file_issue"));
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe("toCommandSpec", () => {
|
|
15
|
+
it("preserves hyphens in the display name (Slack/Discord allow them)", () => {
|
|
16
|
+
const cmd = defineBotCommand({
|
|
17
|
+
name: "file-issue",
|
|
18
|
+
description: "File an issue",
|
|
19
|
+
handler: async () => { },
|
|
20
|
+
});
|
|
21
|
+
expect(toCommandSpec(cmd).name).toBe("file-issue");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { PlatformAdapter, ModalSubmitResult } from "./platform-adapter.js";
|
|
2
|
+
import type { ActionStore } from "./action-store.js";
|
|
3
|
+
import type { StateStore } from "./state/state-store.js";
|
|
4
|
+
import type { BotTool, ContextEntry } from "./tools.js";
|
|
5
|
+
import type { BotCommand, CommandContext } from "./commands.js";
|
|
6
|
+
import { Thread } from "./thread.js";
|
|
7
|
+
import type { AbstractAgent } from "@ag-ui/client";
|
|
8
|
+
import type { InteractionContext, IncomingMessage, PlatformUser, EmojiValue, ComponentFn, MessageRef } from "@copilotkit/channels-ui";
|
|
9
|
+
import { Transcripts } from "./transcripts.js";
|
|
10
|
+
import type { Identity, TranscriptsConfig } from "./transcripts.js";
|
|
11
|
+
import type { StandardSchemaV1, InferSchemaOutput } from "./standard-schema.js";
|
|
12
|
+
export type LockConflictDecision = "drop" | "force";
|
|
13
|
+
/**
|
|
14
|
+
* Any `@copilotkit/channels-ui` component function, regardless of its props type.
|
|
15
|
+
* Accepting `(props: never)` lets a component with required, strongly-typed
|
|
16
|
+
* props (e.g. `({ title }: { title: string }) => …`) be passed to
|
|
17
|
+
* `createBot({ components })` without a cast — the registry only ever calls it
|
|
18
|
+
* with the props persisted in the store, so the specific shape isn't needed here.
|
|
19
|
+
*/
|
|
20
|
+
export type BotComponent = (props: never) => ReturnType<ComponentFn>;
|
|
21
|
+
export type BotHandler<TState = unknown> = (ctx: {
|
|
22
|
+
thread: StatefulThread<TState>;
|
|
23
|
+
message: IncomingMessage;
|
|
24
|
+
}) => void | Promise<void>;
|
|
25
|
+
/** Handler for a "conversation opened" lifecycle event (e.g. the Slack assistant pane). */
|
|
26
|
+
export type ThreadStartHandler<TState = unknown> = (ctx: {
|
|
27
|
+
thread: StatefulThread<TState>;
|
|
28
|
+
user?: PlatformUser;
|
|
29
|
+
}) => void | Promise<void>;
|
|
30
|
+
/** Event passed to an `onReaction` handler. */
|
|
31
|
+
export interface ReactionEvent {
|
|
32
|
+
/** Normalized name when recognized, else the raw platform token. */
|
|
33
|
+
emoji: EmojiValue;
|
|
34
|
+
/** Platform-native token. */
|
|
35
|
+
rawEmoji: string;
|
|
36
|
+
/** true = added, false = removed. */
|
|
37
|
+
added: boolean;
|
|
38
|
+
/** The reacting user, when the platform reports one. */
|
|
39
|
+
user?: PlatformUser;
|
|
40
|
+
messageId: string;
|
|
41
|
+
/** Update-capable ref to the reacted message (`thread.update(messageRef, ui)`). */
|
|
42
|
+
messageRef: MessageRef;
|
|
43
|
+
threadId?: string;
|
|
44
|
+
thread: Thread;
|
|
45
|
+
adapter: PlatformAdapter;
|
|
46
|
+
raw: unknown;
|
|
47
|
+
}
|
|
48
|
+
export type ReactionHandler = (evt: ReactionEvent) => void | Promise<void>;
|
|
49
|
+
/** Event passed to an `onModalSubmit` handler. */
|
|
50
|
+
export interface ModalSubmitEvent {
|
|
51
|
+
callbackId: string;
|
|
52
|
+
values: Record<string, unknown>;
|
|
53
|
+
user?: PlatformUser;
|
|
54
|
+
/** Present when the submission carried a conversation context. */
|
|
55
|
+
thread?: Thread;
|
|
56
|
+
privateMetadata?: string;
|
|
57
|
+
raw: unknown;
|
|
58
|
+
}
|
|
59
|
+
export type ModalSubmitHandler = (evt: ModalSubmitEvent) => ModalSubmitResult | void | Promise<ModalSubmitResult | void>;
|
|
60
|
+
/** Event passed to an `onModalClose` handler. */
|
|
61
|
+
export interface ModalCloseEvent {
|
|
62
|
+
callbackId: string;
|
|
63
|
+
user?: PlatformUser;
|
|
64
|
+
privateMetadata?: string;
|
|
65
|
+
raw: unknown;
|
|
66
|
+
}
|
|
67
|
+
export type ModalCloseHandler = (evt: ModalCloseEvent) => void | Promise<void>;
|
|
68
|
+
/** The per-thread state type implied by the configured `store.state` schema. */
|
|
69
|
+
type ThreadStateOf<TSchema extends StandardSchemaV1 | undefined> = TSchema extends StandardSchemaV1 ? InferSchemaOutput<TSchema> : unknown;
|
|
70
|
+
/** A Thread whose state()/setState() are narrowed to the configured state type. */
|
|
71
|
+
export type StatefulThread<TState> = Omit<Thread, "setState" | "state"> & {
|
|
72
|
+
setState(value: TState): Promise<void>;
|
|
73
|
+
state(): Promise<TState | undefined>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Persistence and per-thread state configuration. Groups the pluggable
|
|
77
|
+
* backend, optional state schema, transcript storage, and turn-lock/dedup
|
|
78
|
+
* tuning under a single `store` option.
|
|
79
|
+
*/
|
|
80
|
+
export interface StoreConfig<TStateSchema extends StandardSchemaV1 | undefined = undefined> {
|
|
81
|
+
/** Pluggable persistence backend. Defaults to in-memory MemoryStore (lost on restart). */
|
|
82
|
+
adapter?: StateStore;
|
|
83
|
+
/** Standard Schema for per-thread state. When set, thread.state()/setState() are typed to its output and setState validates at runtime. */
|
|
84
|
+
state?: TStateSchema;
|
|
85
|
+
/** Resolve a stable cross-platform identity key (e.g. email). Paired with `transcripts`. */
|
|
86
|
+
identity?: Identity;
|
|
87
|
+
/** Cross-platform transcript storage config. Paired with `identity`. */
|
|
88
|
+
transcripts?: TranscriptsConfig;
|
|
89
|
+
/** What to do when a turn arrives while a prior turn on the same conversationKey is processing. */
|
|
90
|
+
onLockConflict?: LockConflictDecision | ((conversationKey: string, message: IncomingMessage) => LockConflictDecision | Promise<LockConflictDecision>);
|
|
91
|
+
/** TTL (ms) for the per-conversation turn lock. Default 60_000. */
|
|
92
|
+
lockTtl?: number;
|
|
93
|
+
/** TTL (ms) for the inbound event dedup window. Default 300_000. */
|
|
94
|
+
dedupTtl?: number;
|
|
95
|
+
}
|
|
96
|
+
export interface CreateBotOptions<TStateSchema extends StandardSchemaV1 | undefined = undefined> {
|
|
97
|
+
/**
|
|
98
|
+
* Project-unique bot identifier. Required for managed (Intelligence-delivered)
|
|
99
|
+
* bots — it ties the runtime declaration to the Intelligence setup — and
|
|
100
|
+
* optional for local/custom adapters. Validated by the managed runtime
|
|
101
|
+
* (`startManagedBots`), not here.
|
|
102
|
+
*/
|
|
103
|
+
name?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Adapters supplied at construction. Optional — adapters can also be attached
|
|
106
|
+
* before `start()` via {@link Bot.addAdapter} (the managed runtime uses this).
|
|
107
|
+
*/
|
|
108
|
+
adapters?: PlatformAdapter[];
|
|
109
|
+
agent?: AbstractAgent | ((threadId: string) => AbstractAgent);
|
|
110
|
+
/** @deprecated Pass `store.adapter` instead. */
|
|
111
|
+
actionStore?: ActionStore;
|
|
112
|
+
tools?: BotTool[];
|
|
113
|
+
context?: ContextEntry[];
|
|
114
|
+
/**
|
|
115
|
+
* Named JSX components used in interactive messages. Registering them here
|
|
116
|
+
* lets the bot re-render and re-fire their handlers after a restart (durable
|
|
117
|
+
* actions); without registration, a click on a message posted before the
|
|
118
|
+
* restart degrades to "action expired".
|
|
119
|
+
*/
|
|
120
|
+
components?: BotComponent[];
|
|
121
|
+
/** Slash commands. Forwarded to adapters that support them; ignored elsewhere. */
|
|
122
|
+
commands?: BotCommand[];
|
|
123
|
+
/** Persistence, per-thread state schema, transcripts, and lock/dedup tuning. */
|
|
124
|
+
store?: StoreConfig<TStateSchema>;
|
|
125
|
+
}
|
|
126
|
+
export interface Bot<TState = unknown> {
|
|
127
|
+
/** Project-unique identifier from `createBot({ name })`; used by the managed runtime. */
|
|
128
|
+
readonly name?: string;
|
|
129
|
+
/** Declared slash-command names (normalized). Surfaced for managed activation metadata. */
|
|
130
|
+
readonly commandNames: string[];
|
|
131
|
+
onMention(h: BotHandler<TState>): void;
|
|
132
|
+
onMessage(h: BotHandler<TState>): void;
|
|
133
|
+
/**
|
|
134
|
+
* A conversation surface opened (e.g. the Slack assistant pane). Greet, set
|
|
135
|
+
* suggested prompts, set a title, or run the agent. Adapters without the
|
|
136
|
+
* concept never fire this.
|
|
137
|
+
*/
|
|
138
|
+
onThreadStarted(h: ThreadStartHandler<TState>): void;
|
|
139
|
+
/** Handle clicks on a specific action `id`. `ctx.action.value` is typed as `TValue`. */
|
|
140
|
+
onInteraction<TValue = unknown>(id: string, h: (ctx: InteractionContext<TValue>) => void | Promise<void>): void;
|
|
141
|
+
/**
|
|
142
|
+
* Handle an agent interrupt (an `on_interrupt` custom event). `payload` is the
|
|
143
|
+
* event's value; pass `TPayload` to type it, e.g.
|
|
144
|
+
* `onInterrupt<{ question: string }>("ask", ...)`.
|
|
145
|
+
*/
|
|
146
|
+
onInterrupt<TPayload = unknown>(eventName: string, h: (args: {
|
|
147
|
+
payload: TPayload;
|
|
148
|
+
thread: StatefulThread<TState>;
|
|
149
|
+
}) => void | Promise<void>): void;
|
|
150
|
+
/** Register a slash command (with optional typed options). */
|
|
151
|
+
onCommand(command: BotCommand): void;
|
|
152
|
+
/** Register a free-text slash command by name. */
|
|
153
|
+
onCommand(name: string, handler: (ctx: CommandContext) => void | Promise<void>): void;
|
|
154
|
+
/** React to emoji reactions. Pass emoji name(s) for a specific match, or omit for a catch-all. */
|
|
155
|
+
onReaction(handler: ReactionHandler): void;
|
|
156
|
+
onReaction(emoji: EmojiValue | EmojiValue[], handler: ReactionHandler): void;
|
|
157
|
+
/** Handle a modal submission for `callbackId`. Return `{ errors }` to keep it open. */
|
|
158
|
+
onModalSubmit(callbackId: string, handler: ModalSubmitHandler): void;
|
|
159
|
+
/** Handle a modal dismissal for `callbackId` (Slack `view_closed`). */
|
|
160
|
+
onModalClose(callbackId: string, handler: ModalCloseHandler): void;
|
|
161
|
+
tool(t: BotTool): void;
|
|
162
|
+
/** Attach an adapter before `start()`. Throws if called after the bot has started. */
|
|
163
|
+
addAdapter(adapter: PlatformAdapter): void;
|
|
164
|
+
start(): Promise<void>;
|
|
165
|
+
stop(): Promise<void>;
|
|
166
|
+
/** Cross-platform transcript store. Append, list, and delete entries per user. */
|
|
167
|
+
transcripts: Transcripts;
|
|
168
|
+
}
|
|
169
|
+
export declare function createBot<TStateSchema extends StandardSchemaV1 | undefined = undefined>(opts: CreateBotOptions<TStateSchema>): Bot<ThreadStateOf<TStateSchema>>;
|
|
170
|
+
export {};
|
|
171
|
+
//# sourceMappingURL=create-bot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-bot.d.ts","sourceRoot":"","sources":["../src/create-bot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-bot.foundations.test.d.ts","sourceRoot":"","sources":["../src/create-bot.foundations.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { createBot } from "./create-bot.js";
|
|
3
|
+
import { FakeAdapter } from "./testing/fake-adapter.js";
|
|
4
|
+
import { FakeAgent } from "./testing/fake-agent.js";
|
|
5
|
+
import { MemoryStore } from "./state/memory-store.js";
|
|
6
|
+
const tick = () => new Promise((r) => setTimeout(r, 0));
|
|
7
|
+
describe("createBot — optional adapters + addAdapter", () => {
|
|
8
|
+
it("starts with no adapters and runs one added before start()", async () => {
|
|
9
|
+
const fake = new FakeAdapter();
|
|
10
|
+
const bot = createBot({ agent: () => new FakeAgent() });
|
|
11
|
+
bot.addAdapter(fake);
|
|
12
|
+
await bot.start();
|
|
13
|
+
expect(fake.started).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it("throws when addAdapter is called after start()", async () => {
|
|
16
|
+
const bot = createBot({
|
|
17
|
+
adapters: [new FakeAdapter()],
|
|
18
|
+
agent: () => new FakeAgent(),
|
|
19
|
+
});
|
|
20
|
+
await bot.start();
|
|
21
|
+
expect(() => bot.addAdapter(new FakeAdapter())).toThrow(/start/i);
|
|
22
|
+
});
|
|
23
|
+
it("is idempotent: a second start() does not re-start adapters or rebuild state", async () => {
|
|
24
|
+
const fake = new FakeAdapter();
|
|
25
|
+
const bot = createBot({
|
|
26
|
+
adapters: [fake],
|
|
27
|
+
agent: () => new FakeAgent(),
|
|
28
|
+
});
|
|
29
|
+
const startSpy = vi.spyOn(fake, "start");
|
|
30
|
+
await bot.start();
|
|
31
|
+
const transcriptsAfterFirst = bot.transcripts;
|
|
32
|
+
await bot.start(); // second call must be a no-op
|
|
33
|
+
expect(startSpy).toHaveBeenCalledTimes(1);
|
|
34
|
+
// Same transcript-store instance → state (locks/dedup/actions) not wiped.
|
|
35
|
+
expect(bot.transcripts).toBe(transcriptsAfterFirst);
|
|
36
|
+
});
|
|
37
|
+
it("allows a real restart after stop() (start → stop → start re-inits)", async () => {
|
|
38
|
+
const fake = new FakeAdapter();
|
|
39
|
+
const bot = createBot({
|
|
40
|
+
adapters: [fake],
|
|
41
|
+
agent: () => new FakeAgent(),
|
|
42
|
+
});
|
|
43
|
+
const startSpy = vi.spyOn(fake, "start");
|
|
44
|
+
await bot.start();
|
|
45
|
+
await bot.stop();
|
|
46
|
+
await bot.start(); // stop() cleared `started`, so this is a real restart
|
|
47
|
+
expect(startSpy).toHaveBeenCalledTimes(2);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("createBot — transcripts deferred to start()", () => {
|
|
51
|
+
it("throws if bot.transcripts is accessed before start()", () => {
|
|
52
|
+
const bot = createBot({
|
|
53
|
+
adapters: [new FakeAdapter()],
|
|
54
|
+
agent: () => new FakeAgent(),
|
|
55
|
+
store: {
|
|
56
|
+
adapter: new MemoryStore(),
|
|
57
|
+
identity: () => "u@x.com",
|
|
58
|
+
transcripts: {},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
expect(() => bot.transcripts).toThrow(/start/i);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("createBot — store resolution", () => {
|
|
65
|
+
it("uses an adapter-provided stateStore when no explicit store.adapter", async () => {
|
|
66
|
+
const adapterStore = new MemoryStore();
|
|
67
|
+
// Seed the adapter's store via a throwaway bot so we can prove the real
|
|
68
|
+
// bot reads from that exact instance.
|
|
69
|
+
const seeder = createBot({
|
|
70
|
+
adapters: [new FakeAdapter()],
|
|
71
|
+
agent: () => new FakeAgent(),
|
|
72
|
+
store: {
|
|
73
|
+
adapter: adapterStore,
|
|
74
|
+
identity: () => "u@x.com",
|
|
75
|
+
transcripts: {},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
await seeder.start();
|
|
79
|
+
await seeder.transcripts.append({ platform: "fake", conversationKey: "c" }, { role: "user", text: "seeded" }, { userKey: "u@x.com" });
|
|
80
|
+
const fake = new FakeAdapter();
|
|
81
|
+
fake.stateStore = adapterStore;
|
|
82
|
+
const bot = createBot({
|
|
83
|
+
adapters: [fake],
|
|
84
|
+
agent: () => new FakeAgent(),
|
|
85
|
+
store: { identity: () => "u@x.com", transcripts: {} },
|
|
86
|
+
});
|
|
87
|
+
await bot.start();
|
|
88
|
+
const entries = await bot.transcripts.list({ userKey: "u@x.com" });
|
|
89
|
+
expect(entries.map((e) => e.text)).toContain("seeded");
|
|
90
|
+
});
|
|
91
|
+
it("explicit store.adapter wins over an adapter-provided one, silently", async () => {
|
|
92
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
93
|
+
const fake = new FakeAdapter();
|
|
94
|
+
fake.stateStore = new MemoryStore();
|
|
95
|
+
const explicit = new MemoryStore();
|
|
96
|
+
const bot = createBot({
|
|
97
|
+
adapters: [fake],
|
|
98
|
+
agent: () => new FakeAgent(),
|
|
99
|
+
store: {
|
|
100
|
+
adapter: explicit,
|
|
101
|
+
identity: () => "u@x.com",
|
|
102
|
+
transcripts: {},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
await bot.start();
|
|
106
|
+
expect(warn).not.toHaveBeenCalled();
|
|
107
|
+
warn.mockRestore();
|
|
108
|
+
});
|
|
109
|
+
it("warns when ≥2 adapters provide a stateStore", async () => {
|
|
110
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
111
|
+
const a = new FakeAdapter({ platform: "a" });
|
|
112
|
+
a.stateStore = new MemoryStore();
|
|
113
|
+
const b = new FakeAdapter({ platform: "b" });
|
|
114
|
+
b.stateStore = new MemoryStore();
|
|
115
|
+
const bot = createBot({
|
|
116
|
+
adapters: [a, b],
|
|
117
|
+
agent: () => new FakeAgent(),
|
|
118
|
+
});
|
|
119
|
+
await bot.start();
|
|
120
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("state store"));
|
|
121
|
+
warn.mockRestore();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe("createBot — id propagation to handler context", () => {
|
|
125
|
+
it("threads turnId/deliveryId from IncomingTurn onto message", async () => {
|
|
126
|
+
const fake = new FakeAdapter();
|
|
127
|
+
const bot = createBot({ adapters: [fake], agent: () => new FakeAgent() });
|
|
128
|
+
let seen = {};
|
|
129
|
+
bot.onMessage(async ({ message }) => {
|
|
130
|
+
seen = {
|
|
131
|
+
turnId: message.turnId,
|
|
132
|
+
deliveryId: message.deliveryId,
|
|
133
|
+
eventId: message.eventId,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
await bot.start();
|
|
137
|
+
fake.emitTurn({
|
|
138
|
+
userText: "hi",
|
|
139
|
+
conversationKey: "c1",
|
|
140
|
+
eventId: "e1",
|
|
141
|
+
turnId: "t1",
|
|
142
|
+
deliveryId: "d1",
|
|
143
|
+
});
|
|
144
|
+
await tick();
|
|
145
|
+
expect(seen).toEqual({ turnId: "t1", deliveryId: "d1", eventId: "e1" });
|
|
146
|
+
});
|
|
147
|
+
});
|