@copilotkit/bot 0.0.1

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/dist/action-registry.d.ts +20 -0
  4. package/dist/action-registry.d.ts.map +1 -0
  5. package/dist/action-registry.js +109 -0
  6. package/dist/action-registry.test.d.ts +2 -0
  7. package/dist/action-registry.test.d.ts.map +1 -0
  8. package/dist/action-registry.test.js +45 -0
  9. package/dist/action-store.d.ts +19 -0
  10. package/dist/action-store.d.ts.map +1 -0
  11. package/dist/action-store.js +22 -0
  12. package/dist/action-store.test.d.ts +2 -0
  13. package/dist/action-store.test.d.ts.map +1 -0
  14. package/dist/action-store.test.js +27 -0
  15. package/dist/commands.d.ts +68 -0
  16. package/dist/commands.d.ts.map +1 -0
  17. package/dist/commands.js +30 -0
  18. package/dist/create-bot.d.ts +44 -0
  19. package/dist/create-bot.d.ts.map +1 -0
  20. package/dist/create-bot.js +166 -0
  21. package/dist/create-bot.test.d.ts +2 -0
  22. package/dist/create-bot.test.d.ts.map +1 -0
  23. package/dist/create-bot.test.js +261 -0
  24. package/dist/index.d.ts +17 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +19 -0
  27. package/dist/mint-id.d.ts +3 -0
  28. package/dist/mint-id.d.ts.map +1 -0
  29. package/dist/mint-id.js +20 -0
  30. package/dist/mint-id.test.d.ts +2 -0
  31. package/dist/mint-id.test.d.ts.map +1 -0
  32. package/dist/mint-id.test.js +16 -0
  33. package/dist/platform-adapter.d.ts +125 -0
  34. package/dist/platform-adapter.d.ts.map +1 -0
  35. package/dist/platform-adapter.js +1 -0
  36. package/dist/platform-adapter.test.d.ts +2 -0
  37. package/dist/platform-adapter.test.d.ts.map +1 -0
  38. package/dist/platform-adapter.test.js +32 -0
  39. package/dist/run-loop.d.ts +38 -0
  40. package/dist/run-loop.d.ts.map +1 -0
  41. package/dist/run-loop.js +100 -0
  42. package/dist/run-loop.test.d.ts +2 -0
  43. package/dist/run-loop.test.d.ts.map +1 -0
  44. package/dist/run-loop.test.js +80 -0
  45. package/dist/standard-schema.d.ts +52 -0
  46. package/dist/standard-schema.d.ts.map +1 -0
  47. package/dist/standard-schema.js +66 -0
  48. package/dist/testing/fake-adapter.d.ts +44 -0
  49. package/dist/testing/fake-adapter.d.ts.map +1 -0
  50. package/dist/testing/fake-adapter.js +123 -0
  51. package/dist/testing/fake-agent.d.ts +28 -0
  52. package/dist/testing/fake-agent.d.ts.map +1 -0
  53. package/dist/testing/fake-agent.js +36 -0
  54. package/dist/thread.d.ts +60 -0
  55. package/dist/thread.d.ts.map +1 -0
  56. package/dist/thread.js +109 -0
  57. package/dist/tools.d.ts +67 -0
  58. package/dist/tools.d.ts.map +1 -0
  59. package/dist/tools.js +38 -0
  60. package/dist/tools.test.d.ts +2 -0
  61. package/dist/tools.test.d.ts.map +1 -0
  62. package/dist/tools.test.js +31 -0
  63. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Atai Barkai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # @copilotkit/bot
2
+
3
+ The **platform-agnostic bot engine**. It owns everything between an incoming
4
+ message and a rendered reply — handler registration, the agent
5
+ run/tool/interrupt loop, JSX action binding, and the `PlatformAdapter`
6
+ contract — without knowing anything about Slack (or any other surface). A
7
+ platform adapter plugs in at the boundary; `@copilotkit/bot-slack` is the
8
+ concrete Slack one.
9
+
10
+ It builds on `@copilotkit/bot-ui` (the JSX runtime + component vocabulary,
11
+ re-exported from here for convenience) and AG-UI (`@ag-ui/client`,
12
+ `@ag-ui/core`).
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ pnpm add @copilotkit/bot @copilotkit/bot-ui
18
+ # plus a platform adapter, e.g.
19
+ pnpm add @copilotkit/bot-slack
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ ```ts
25
+ import { createBot } from "@copilotkit/bot";
26
+ import { slack } from "@copilotkit/bot-slack"; // a concrete PlatformAdapter
27
+
28
+ const bot = createBot({
29
+ adapters: [slack({ botToken, appToken })],
30
+ agent: (threadId) => makeAgent(threadId), // AbstractAgent or (threadId) => AbstractAgent
31
+ tools: [...myTools], // BotTool[] forwarded on every runAgent
32
+ context: [...myContext], // ContextEntry[] forwarded on every runAgent
33
+ });
34
+
35
+ bot.onMention(({ thread }) => thread.runAgent());
36
+ bot.onMessage(({ thread }) => thread.runAgent());
37
+
38
+ await bot.start();
39
+ ```
40
+
41
+ `createBot(opts)` returns a `Bot`:
42
+
43
+ - `onMention(handler)` / `onMessage(handler)` — turn handlers receiving
44
+ `{ thread, message }`. (Routing is mention-preferred: if any mention
45
+ handler is registered, all turns route to it; otherwise message handlers
46
+ fire.)
47
+ - `onInteraction<TValue>(id, handler)` — explicit escape-hatch handler for a
48
+ known action id, bypassing the registry; `ctx.action.value` is typed `TValue`.
49
+ - `onInterrupt<TPayload>(eventName, handler)` — handle a captured agent
50
+ interrupt (LangGraph-style `on_interrupt`); receives `{ payload, thread }`
51
+ with `payload` typed `TPayload`.
52
+ - `onCommand(command)` / `onCommand(name, handler)` — register a slash command.
53
+ The handler gets `{ thread, command, text, options, user }`. `text` is the
54
+ raw args (Slack); `options` is the typed, parsed form (`defineBotCommand`
55
+ with an `options` Standard Schema) for surfaces with native structured args
56
+ (e.g. Discord). Forwarded to adapters that support commands and ignored
57
+ elsewhere — also pass them up front via `commands` in `CreateBotOptions`.
58
+ - `tool(t)` — register a `BotTool` (alternative to `opts.tools`); must be
59
+ added before `start()`.
60
+ - `start()` / `stop()` — bring adapters up / down.
61
+
62
+ `agent` is optional. If omitted, calling `thread.runAgent()` throws; supply
63
+ an `AbstractAgent` or a `(threadId) => AbstractAgent` factory.
64
+
65
+ ## `Thread`
66
+
67
+ A `Thread` is the per-conversation handle handed to your handlers and tool
68
+ contexts. It accepts any `Renderable` (JSX or a string) for posting.
69
+
70
+ ```ts
71
+ interface Thread {
72
+ readonly platform: string;
73
+ post(ui: Renderable): Promise<MessageRef>;
74
+ update(ref: MessageRef, ui: Renderable): Promise<MessageRef>;
75
+ delete(ref: MessageRef): Promise<void>;
76
+ stream(src: string | AsyncIterable<string>): Promise<MessageRef>;
77
+ runAgent(input?: {
78
+ context?: ContextEntry[];
79
+ tools?: BotTool[];
80
+ }): Promise<MessageRef | undefined>;
81
+ resume(value: unknown): Promise<MessageRef | undefined>;
82
+ awaitChoice<T = unknown>(ui: Renderable): Promise<T>;
83
+ }
84
+ ```
85
+
86
+ - `post` / `update` render the JSX to IR, **bind** every event-prop handler
87
+ in the tree (mint a content-stable id, snapshot it, rewrite the prop to
88
+ `{ id }`), then hand the IR to the adapter.
89
+ - `runAgent` resolves the conversation's agent session, creates the adapter's
90
+ `RunRenderer`, and drives the run/tool/interrupt loop. Per-run `tools` /
91
+ `context` are merged on top of the bot-level defaults for that run only.
92
+ - `resume(value)` re-enters a paused interrupt run with
93
+ `forwardedProps.command`.
94
+ - `awaitChoice<T>(ui)` posts a picker and blocks until an interaction in this
95
+ conversation resolves it to the clicked control's value (HITL); pass `T` to
96
+ type the returned value.
97
+
98
+ ## Tools & context
99
+
100
+ A `BotTool` is forwarded to the agent as a frontend tool; its handler runs in
101
+ the bot when the agent calls it. The handler `ctx` carries the `thread`, so a
102
+ tool can render JSX (`ctx.thread.post(<Card .../>)`) or run the agent further.
103
+
104
+ ```ts
105
+ interface BotTool<Schema extends ObjectSchema = ObjectSchema> {
106
+ name: string;
107
+ description: string;
108
+ parameters: Schema; // any Standard Schema (Zod/Valibot/ArkType/…)
109
+ handler(args, ctx: BotToolContext): Promise<unknown> | unknown;
110
+ }
111
+ ```
112
+
113
+ Define one with the non-curried `defineBotTool`, which infers the arg types
114
+ from `parameters`:
115
+
116
+ ```ts
117
+ defineBotTool({
118
+ name: "read_thread",
119
+ description: "Read the messages in the current conversation.",
120
+ parameters: z.object({}),
121
+ async handler(_args, { thread }) {
122
+ return await thread.getMessages();
123
+ },
124
+ });
125
+ ```
126
+
127
+ `parameters` (a Standard Schema) is converted to JSON Schema for the LLM and
128
+ validated on the way back. `BotToolContext` is `{ thread, message?, user?,
129
+ signal?, platform }` — a single shared type with no per-adapter generic.
130
+ Platform-specific power is reached only through capability-gated `thread`
131
+ methods (e.g. `thread.getMessages()`, `thread.lookupUser(query)`,
132
+ `thread.postFile(...)`), so a tool stays portable across surfaces.
133
+
134
+ A `ContextEntry` is `{ description: string; value: string }` — knowledge
135
+ folded into the agent's system context on each `runAgent`.
136
+
137
+ ## ActionStore
138
+
139
+ Inline JSX handlers are bound by content. Each interactive node gets a
140
+ **content-stable, opaque** minted id — `mintId(componentName, path, props)`
141
+ = `"ck:" + sha1(name | path | stableStringify(props)).slice(0,16)`. Only the
142
+ opaque id (plus any small `bind()` args) is stamped on the native token; no
143
+ props, PII, or secrets go over the wire.
144
+
145
+ On a click, the `ActionRegistry` resolves the handler from a hot in-memory
146
+ cache; on a miss it **rehydrates** by loading the snapshot from the
147
+ `ActionStore`, re-rendering the named component with the frozen props, and
148
+ re-walking to the handler's path.
149
+
150
+ The default `ActionStore` is `InMemoryActionStore` (a `Map` with optional
151
+ TTL). It is lost on restart: after a restart an old button click degrades to
152
+ an `ActionExpiredError` ("this action expired"), which `createBot` swallows.
153
+ **Durable actions require an external store (Redis / DB) — not shipped in
154
+ v1.** Implement the `ActionStore` interface (`put` / `get` / `delete`) and
155
+ pass it as `actionStore` to make actions survive restarts.
156
+
157
+ ## Writing a `PlatformAdapter`
158
+
159
+ To target a new surface, implement `PlatformAdapter` from this package. The
160
+ engine drives ingress through the `IngressSink` you receive in `start(sink)`
161
+ (`sink.onTurn(IncomingTurn)` / `sink.onInteraction(InteractionEvent)`) and
162
+ egress through your `post` / `update` / `stream` / `delete` (which receive
163
+ `BotNode[]` to translate to a native payload via `render`). You also provide
164
+ `createRunRenderer(target)` (an AG-UI `RunRenderer`: the subscriber to stream
165
+ into, plus accessors for captured tool calls and interrupts that the run-loop
166
+ reads after each `runAgent`), `decodeInteraction(raw)` (native event → opaque
167
+ `InteractionEvent`), `lookupUser`, a `conversationStore`
168
+ (`getOrCreate` → `AgentSession`), and the surface `capabilities` /
169
+ `ackDeadlineMs`. Optional capability methods like `getMessages(target)` and
170
+ `postFile(target, args)` back the matching `thread` methods when the surface
171
+ supports them. Slash commands are also capability-gated: an adapter forwards
172
+ invocations via `sink.onCommand(IncomingCommand)`, and may implement
173
+ `registerCommands(specs)` to publish the bot's declared commands up front
174
+ (e.g. Discord's application-command API); adapters that omit it are skipped.
175
+ See `@copilotkit/bot-slack` for a complete implementation.
176
+
177
+ ## Exports
178
+
179
+ `createBot`, `Bot`, `CreateBotOptions`, `BotHandler`; `Thread`; the
180
+ `PlatformAdapter` boundary types (`RunRenderer`, `IngressSink`,
181
+ `IncomingTurn`, `InteractionEvent`, `IncomingCommand`, `SurfaceCapabilities`,
182
+ `ReplyTarget`, `ConversationStore`, `AgentSession`, `CapturedToolCall`,
183
+ `CapturedInterrupt`, `UserQuery`); `ActionStore` / `InMemoryActionStore` /
184
+ `ActionSnapshot` / `ActionRegistry` / `ActionExpiredError`; `BotTool` /
185
+ `BotToolContext` / `defineBotTool` / `BotCommand` / `CommandContext` /
186
+ `CommandSpec` / `defineBotCommand` / `ContextEntry` /
187
+ `AgentToolDescriptor` / `ObjectSchema` and the tool helpers
188
+ (`toAgentToolDescriptors`, `parseToolArgs`, `stringifyHandlerResult`);
189
+ `mintId` / `stableStringify`; `runAgentLoop`; plus the re-exported
190
+ `@copilotkit/bot-ui` vocabulary.
@@ -0,0 +1,20 @@
1
+ import type { BotNode, InteractionContext, ComponentFn, Renderable } from "@copilotkit/bot-ui";
2
+ import type { ActionStore } from "./action-store.js";
3
+ export declare class ActionExpiredError extends Error {
4
+ constructor(id: string);
5
+ }
6
+ export declare class ActionRegistry {
7
+ private store;
8
+ private components;
9
+ private hot;
10
+ constructor(opts: {
11
+ store: ActionStore;
12
+ });
13
+ registerComponent(name: string, fn: ComponentFn): void;
14
+ clearHotCache(): void;
15
+ bindTree(componentName: string, props: Record<string, unknown>, conversationKey: string): Promise<BotNode[]>;
16
+ bindRenderable(ui: Renderable, conversationKey: string): Promise<BotNode[]>;
17
+ private walk;
18
+ dispatch(id: string, ctx: InteractionContext): Promise<unknown>;
19
+ }
20
+ //# sourceMappingURL=action-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-registry.d.ts","sourceRoot":"","sources":["../src/action-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAEP,kBAAkB,EAClB,WAAW,EACX,UAAU,EACX,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,EAAE,EAAE,MAAM;CAIvB;AAcD,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,GAAG,CAAmC;gBAElC,IAAI,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE;IAIxC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI;IAItD,aAAa,IAAI,IAAI;IAMf,QAAQ,CACZ,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC;IAYf,cAAc,CAClB,EAAE,EAAE,UAAU,EACd,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,EAAE,CAAC;YAgBP,IAAI;IAuCZ,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;CAetE"}
@@ -0,0 +1,109 @@
1
+ import { isBound, getBoundArgs, renderToIR } from "@copilotkit/bot-ui";
2
+ import { mintId } from "./mint-id.js";
3
+ export class ActionExpiredError extends Error {
4
+ constructor(id) {
5
+ super(`Action "${id}" has expired or is no longer available.`);
6
+ this.name = "ActionExpiredError";
7
+ }
8
+ }
9
+ const EVENT_PROPS = ["onClick", "onSelect", "onSubmit"];
10
+ function isComponentElement(ui) {
11
+ return (typeof ui === "object" &&
12
+ ui !== null &&
13
+ typeof ui.type === "function");
14
+ }
15
+ export class ActionRegistry {
16
+ store;
17
+ components = new Map();
18
+ hot = new Map();
19
+ constructor(opts) {
20
+ this.store = opts.store;
21
+ }
22
+ registerComponent(name, fn) {
23
+ this.components.set(name, fn);
24
+ }
25
+ clearHotCache() {
26
+ this.hot.clear();
27
+ }
28
+ // Renders the named component, binds all event-prop handlers in the tree
29
+ // (mint id, hot-cache + ActionStore snapshot, rewrite prop to { id }), returns the bound IR.
30
+ async bindTree(componentName, props, conversationKey) {
31
+ const fn = this.components.get(componentName);
32
+ const root = renderToIR((fn ? fn(props) : props));
33
+ await this.walk(root, [], componentName, props, conversationKey);
34
+ return root;
35
+ }
36
+ // Binds an arbitrary Renderable for posting. If `ui` is a component element
37
+ // (`{ type: fn, props }`), it is registered + bound by name (cold-path
38
+ // re-render supported). Otherwise the IR is bound inline with `component:""`,
39
+ // meaning a cold-cache dispatch throws ActionExpiredError (intended
40
+ // degradation for inline handlers that can't be re-derived).
41
+ async bindRenderable(ui, conversationKey) {
42
+ if (isComponentElement(ui)) {
43
+ const fn = ui.type;
44
+ const name = fn.name || "anonymous";
45
+ this.registerComponent(name, fn);
46
+ return this.bindTree(name, (ui.props ?? {}), conversationKey);
47
+ }
48
+ const root = renderToIR(ui);
49
+ await this.walk(root, [], "", undefined, conversationKey);
50
+ return root;
51
+ }
52
+ async walk(nodes, base, comp, props, conv) {
53
+ for (let i = 0; i < nodes.length; i++) {
54
+ const node = nodes[i];
55
+ const path = [...base, i];
56
+ for (const ep of EVENT_PROPS) {
57
+ const handler = node.props[ep];
58
+ if (typeof handler === "function") {
59
+ const fullPath = [...path, ep];
60
+ const id = mintId(comp, fullPath, props);
61
+ this.hot.set(id, handler);
62
+ await this.store.put(id, {
63
+ component: comp,
64
+ props,
65
+ path: fullPath,
66
+ conversationKey: conv,
67
+ boundArgs: isBound(handler) ? getBoundArgs(handler) : undefined,
68
+ });
69
+ node.props[ep] = { id };
70
+ }
71
+ }
72
+ const children = node.props.children;
73
+ if (Array.isArray(children)) {
74
+ await this.walk(children, [...path, "children"], comp, props, conv);
75
+ }
76
+ }
77
+ }
78
+ async dispatch(id, ctx) {
79
+ let handler = this.hot.get(id);
80
+ if (!handler) {
81
+ const snap = await this.store.get(id);
82
+ if (!snap || !snap.component)
83
+ throw new ActionExpiredError(id);
84
+ const fn = this.components.get(snap.component);
85
+ if (!fn)
86
+ throw new ActionExpiredError(id);
87
+ const tree = renderToIR(fn(snap.props));
88
+ handler = pluck(tree, snap.path);
89
+ if (!handler)
90
+ throw new ActionExpiredError(id);
91
+ }
92
+ return handler({ ...ctx, action: { ...ctx.action, id } });
93
+ }
94
+ }
95
+ function pluck(tree, path) {
96
+ let cur = tree;
97
+ for (const seg of path.slice(0, -1)) {
98
+ if (Array.isArray(cur))
99
+ cur = cur[seg];
100
+ else if (cur && typeof cur === "object")
101
+ cur = cur.props?.[seg];
102
+ else
103
+ return undefined;
104
+ }
105
+ const ep = path[path.length - 1];
106
+ const node = cur;
107
+ const h = node?.props?.[ep];
108
+ return typeof h === "function" ? h : undefined;
109
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=action-registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-registry.test.d.ts","sourceRoot":"","sources":["../src/action-registry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ActionRegistry, ActionExpiredError } from "./action-registry.js";
3
+ import { InMemoryActionStore } from "./action-store.js";
4
+ function Confirm(props) {
5
+ return {
6
+ type: "actions",
7
+ props: {
8
+ children: [
9
+ {
10
+ type: "button",
11
+ props: {
12
+ onClick: ({ action }) => `ok:${props.action}:${action.id}`,
13
+ children: "Yes",
14
+ },
15
+ },
16
+ ],
17
+ },
18
+ };
19
+ }
20
+ const ctx = {};
21
+ describe("ActionRegistry", () => {
22
+ it("binds onClick handlers and dispatches via hot cache", async () => {
23
+ const reg = new ActionRegistry({ store: new InMemoryActionStore() });
24
+ reg.registerComponent("Confirm", Confirm);
25
+ const ir = await reg.bindTree("Confirm", { action: "write" }, "conv1");
26
+ const button = ir[0].props.children[0];
27
+ const id = button.props.onClick.id;
28
+ expect(typeof id).toBe("string");
29
+ const out = await reg.dispatch(id, ctx);
30
+ expect(out).toContain("ok:write:");
31
+ });
32
+ it("cold path re-renders from snapshot when hot cache is cleared", async () => {
33
+ const reg = new ActionRegistry({ store: new InMemoryActionStore() });
34
+ reg.registerComponent("Confirm", Confirm);
35
+ const ir = await reg.bindTree("Confirm", { action: "write" }, "conv1");
36
+ const id = ir[0].props.children[0].props.onClick.id;
37
+ reg.clearHotCache();
38
+ const out = await reg.dispatch(id, ctx);
39
+ expect(out).toContain("ok:write:");
40
+ });
41
+ it("throws ActionExpiredError on full miss", async () => {
42
+ const reg = new ActionRegistry({ store: new InMemoryActionStore() });
43
+ await expect(reg.dispatch("ck:missing", ctx)).rejects.toBeInstanceOf(ActionExpiredError);
44
+ });
45
+ });
@@ -0,0 +1,19 @@
1
+ export interface ActionSnapshot {
2
+ component?: string;
3
+ props?: unknown;
4
+ path: (string | number)[];
5
+ boundArgs?: unknown;
6
+ conversationKey: string;
7
+ }
8
+ export interface ActionStore {
9
+ put(id: string, snap: ActionSnapshot, ttlMs?: number): Promise<void>;
10
+ get(id: string): Promise<ActionSnapshot | undefined>;
11
+ delete(id: string): Promise<void>;
12
+ }
13
+ export declare class InMemoryActionStore implements ActionStore {
14
+ private map;
15
+ put(id: string, snap: ActionSnapshot, ttlMs?: number): Promise<void>;
16
+ get(id: string): Promise<ActionSnapshot | undefined>;
17
+ delete(id: string): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=action-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-store.d.ts","sourceRoot":"","sources":["../src/action-store.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AACD,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,GAAG,CAAmE;IACxE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IASpD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxC"}
@@ -0,0 +1,22 @@
1
+ export class InMemoryActionStore {
2
+ map = new Map();
3
+ async put(id, snap, ttlMs) {
4
+ this.map.set(id, {
5
+ snap,
6
+ expiresAt: ttlMs ? Date.now() + ttlMs : undefined,
7
+ });
8
+ }
9
+ async get(id) {
10
+ const e = this.map.get(id);
11
+ if (!e)
12
+ return undefined;
13
+ if (e.expiresAt !== undefined && Date.now() > e.expiresAt) {
14
+ this.map.delete(id);
15
+ return undefined;
16
+ }
17
+ return e.snap;
18
+ }
19
+ async delete(id) {
20
+ this.map.delete(id);
21
+ }
22
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=action-store.test.d.ts.map
@@ -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
+ });
@@ -0,0 +1,68 @@
1
+ import type { InferSchemaOutput, ObjectSchema } from "./standard-schema.js";
2
+ import type { Thread, PlatformUser } from "@copilotkit/bot-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
+ }
22
+ /**
23
+ * A slash command. Defined like a {@link import("./tools.js").BotTool}: a name,
24
+ * an optional Standard Schema for typed options, and a handler. The `options`
25
+ * schema maps natively to surfaces with structured args (Discord) and is used
26
+ * to register/validate there; on text-only surfaces (Slack) args arrive via
27
+ * `ctx.text`.
28
+ */
29
+ export interface BotCommand<Schema extends ObjectSchema = ObjectSchema> {
30
+ /** Command name without the leading slash (e.g. `"triage"`). Matched case-insensitively. */
31
+ name: string;
32
+ /** Human description — help text, and the registration label on surfaces like Discord. */
33
+ description?: string;
34
+ /** Optional Standard Schema for typed options. */
35
+ options?: Schema;
36
+ handler(ctx: CommandContext<InferSchemaOutput<Schema>>): void | Promise<void>;
37
+ }
38
+ /**
39
+ * Define a {@link BotCommand} with full type inference: `ctx.options` is
40
+ * inferred from `options`.
41
+ *
42
+ * ```ts
43
+ * const triage = defineBotCommand({
44
+ * name: "triage",
45
+ * description: "Summarize and file the current thread.",
46
+ * options: z.object({ priority: z.enum(["low", "high"]).optional() }),
47
+ * async handler({ thread, text, options }) {
48
+ * await thread.runAgent({ prompt: `Triage: ${text}`, });
49
+ * },
50
+ * });
51
+ * ```
52
+ */
53
+ export declare function defineBotCommand<Schema extends ObjectSchema>(command: BotCommand<Schema>): BotCommand<Schema>;
54
+ /**
55
+ * Platform-neutral descriptor an adapter may use to register a command with the
56
+ * surface (e.g. Discord's application-command API). Produced from a
57
+ * {@link BotCommand} by {@link toCommandSpec}.
58
+ */
59
+ export interface CommandSpec {
60
+ name: string;
61
+ description: string;
62
+ /** JSON Schema for the options, or `undefined` for a free-text command. */
63
+ options?: Record<string, unknown>;
64
+ }
65
+ /** Normalize a command name for matching: drop a leading slash, lower-case, trim. */
66
+ export declare function normalizeCommandName(name: string): string;
67
+ export declare function toCommandSpec(command: BotCommand): CommandSpec;
68
+ //# 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,MAAM,oBAAoB,CAAC;AAE/D;;;;;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;CAClB;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,CAEzD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,WAAW,CAM9D"}
@@ -0,0 +1,30 @@
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
+ return name.trim().replace(/^\//, "").toLowerCase();
23
+ }
24
+ export function toCommandSpec(command) {
25
+ return {
26
+ name: normalizeCommandName(command.name),
27
+ description: command.description ?? "",
28
+ options: command.options ? toJsonSchema(command.options) : undefined,
29
+ };
30
+ }
@@ -0,0 +1,44 @@
1
+ import type { PlatformAdapter } from "./platform-adapter.js";
2
+ import { type ActionStore } from "./action-store.js";
3
+ import { type BotTool, type ContextEntry } from "./tools.js";
4
+ import { type BotCommand, type CommandContext } from "./commands.js";
5
+ import { Thread } from "./thread.js";
6
+ import type { AbstractAgent } from "@ag-ui/client";
7
+ import type { InteractionContext, IncomingMessage } from "@copilotkit/bot-ui";
8
+ export type BotHandler = (ctx: {
9
+ thread: Thread;
10
+ message: IncomingMessage;
11
+ }) => void | Promise<void>;
12
+ export interface CreateBotOptions {
13
+ adapters: PlatformAdapter[];
14
+ agent?: AbstractAgent | ((threadId: string) => AbstractAgent);
15
+ actionStore?: ActionStore;
16
+ tools?: BotTool[];
17
+ context?: ContextEntry[];
18
+ /** Slash commands. Forwarded to adapters that support them; ignored elsewhere. */
19
+ commands?: BotCommand[];
20
+ }
21
+ export interface Bot {
22
+ onMention(h: BotHandler): void;
23
+ onMessage(h: BotHandler): void;
24
+ /** Handle clicks on a specific action `id`. `ctx.action.value` is typed as `TValue`. */
25
+ onInteraction<TValue = unknown>(id: string, h: (ctx: InteractionContext<TValue>) => void | Promise<void>): void;
26
+ /**
27
+ * Handle an agent interrupt (an `on_interrupt` custom event). `payload` is the
28
+ * event's value; pass `TPayload` to type it, e.g.
29
+ * `onInterrupt<{ question: string }>("ask", ...)`.
30
+ */
31
+ onInterrupt<TPayload = unknown>(eventName: string, h: (args: {
32
+ payload: TPayload;
33
+ thread: Thread;
34
+ }) => void | Promise<void>): void;
35
+ /** Register a slash command (with optional typed options). */
36
+ onCommand(command: BotCommand): void;
37
+ /** Register a free-text slash command by name. */
38
+ onCommand(name: string, handler: (ctx: CommandContext) => void | Promise<void>): void;
39
+ tool(t: BotTool): void;
40
+ start(): Promise<void>;
41
+ stop(): Promise<void>;
42
+ }
43
+ export declare function createBot(opts: CreateBotOptions): Bot;
44
+ //# 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":"AAAA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAmB,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,CAAC;CAC1B,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,aAAa,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC;IAC9D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,GAAG;IAClB,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,wFAAwF;IACxF,aAAa,CAAC,MAAM,GAAG,OAAO,EAC5B,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC3D,IAAI,CAAC;IACR;;;;OAIG;IACH,WAAW,CAAC,QAAQ,GAAG,OAAO,EAC5B,SAAS,EAAE,MAAM,EACjB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACvE,IAAI,CAAC;IACR,8DAA8D;IAC9D,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACrC,kDAAkD;IAClD,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACrD,IAAI,CAAC;IACR,IAAI,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,GAAG,CA6MrD"}