@copilotkit/channels-core 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/channels-core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Platform-agnostic JSX channel engine for CopilotKit (createChannel, Thread, PlatformAdapter, ActionStore).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "@ag-ui/client": "0.0.57",
52
52
  "@ag-ui/core": "0.0.57",
53
53
  "zod-to-json-schema": "^3.24.1",
54
- "@copilotkit/channels-ui": "~0.1.1",
54
+ "@copilotkit/channels-ui": "~0.2.0",
55
55
  "@copilotkit/core": "^1.62.3",
56
56
  "@copilotkit/shared": "^1.62.3"
57
57
  },
@@ -1,171 +0,0 @@
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 Intelligence Channel name. Required for Intelligence Channel
99
- * Bots — it ties the runtime declaration to the Intelligence setup — and
100
- * optional for local/custom adapters. Validated by the Channel runtime
101
- * (`startChannels`), 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 Channel 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 Channel runtime. */
128
- readonly name?: string;
129
- /** Declared slash-command names (normalized). Surfaced for Channel 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
@@ -1 +0,0 @@
1
- {"version":3,"file":"create-bot.d.ts","sourceRoot":"","sources":["../src/create-bot.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=create-bot.foundations.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create-bot.foundations.test.d.ts","sourceRoot":"","sources":["../src/create-bot.foundations.test.ts"],"names":[],"mappings":""}
@@ -1,147 +0,0 @@
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
- });