@copilotkit/channels-core 0.5.0 → 0.5.1-canary.1785633429
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/README.md +35 -14
- package/dist/action-registry.d.ts +15 -3
- package/dist/action-registry.d.ts.map +1 -1
- package/dist/action-registry.js +61 -9
- package/dist/action-registry.test.js +107 -2
- package/dist/action-store.d.ts +23 -0
- package/dist/action-store.d.ts.map +1 -1
- package/dist/action-store.js +11 -0
- package/dist/canonical-run-loop.test.js +6 -1
- package/dist/channel-identity.test.d.ts +2 -0
- package/dist/channel-identity.test.d.ts.map +1 -0
- package/dist/channel-identity.test.js +297 -0
- package/dist/channel-memory.test.d.ts +2 -0
- package/dist/channel-memory.test.d.ts.map +1 -0
- package/dist/channel-memory.test.js +199 -0
- package/dist/commands.d.ts +5 -3
- package/dist/commands.d.ts.map +1 -1
- package/dist/create-channel.d.ts +30 -56
- package/dist/create-channel.d.ts.map +1 -1
- package/dist/create-channel.foundations.test.js +30 -7
- package/dist/create-channel.js +209 -72
- package/dist/create-channel.test.js +293 -66
- package/dist/hitl-continuation.test.d.ts +2 -0
- package/dist/hitl-continuation.test.d.ts.map +1 -0
- package/dist/hitl-continuation.test.js +393 -0
- package/dist/identity.d.ts +51 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +56 -0
- package/dist/identity.test.d.ts +2 -0
- package/dist/identity.test.d.ts.map +1 -0
- package/dist/identity.test.js +109 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/managed-v1-await-choice-guard.test.js +4 -1
- package/dist/memory.d.ts +23 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +25 -0
- package/dist/modal-submit.test.js +21 -6
- package/dist/open-modal.test.js +16 -4
- package/dist/platform-adapter.d.ts +29 -6
- package/dist/platform-adapter.d.ts.map +1 -1
- package/dist/platform-adapter.test.js +2 -0
- package/dist/reactions.test.js +49 -11
- package/dist/run-loop.test.js +30 -5
- package/dist/sanitize-agent-events.test.js +2 -0
- package/dist/source-platform.test.js +24 -9
- package/dist/state/kv-action-store.d.ts.map +1 -1
- package/dist/state/kv-action-store.js +1 -0
- package/dist/state/kv-action-store.test.js +11 -0
- package/dist/state/memory-store.d.ts +1 -0
- package/dist/state/memory-store.d.ts.map +1 -1
- package/dist/state/memory-store.js +9 -0
- package/dist/state/state-store.d.ts +2 -0
- package/dist/state/state-store.d.ts.map +1 -1
- package/dist/telemetry/create-channel-telemetry.test.js +4 -2
- package/dist/telemetry/e2e-telemetry.test.js +1 -0
- package/dist/telemetry/install-id.test.js +5 -0
- package/dist/testing/fake-adapter.d.ts +26 -9
- package/dist/testing/fake-adapter.d.ts.map +1 -1
- package/dist/testing/fake-adapter.js +65 -21
- package/dist/testing/state-store-conformance.d.ts.map +1 -1
- package/dist/testing/state-store-conformance.js +16 -0
- package/dist/thread-capabilities.test.js +29 -9
- package/dist/thread-ephemeral.test.js +8 -5
- package/dist/thread-promise-contract.test.js +4 -0
- package/dist/thread-reactions.test.js +8 -2
- package/dist/thread.d.ts +50 -7
- package/dist/thread.d.ts.map +1 -1
- package/dist/thread.js +163 -17
- package/dist/thread.test.js +4 -0
- package/dist/tools.d.ts +5 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +2 -1
- package/dist/transcripts.d.ts +6 -13
- package/dist/transcripts.d.ts.map +1 -1
- package/dist/transcripts.js +12 -12
- package/dist/transcripts.test.js +28 -29
- package/dist/welcome.test.d.ts +2 -0
- package/dist/welcome.test.d.ts.map +1 -0
- package/dist/welcome.test.js +53 -0
- package/package.json +5 -5
package/dist/transcripts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseDuration } from "./state/duration.js";
|
|
2
|
-
const keyFor = (
|
|
2
|
+
const keyFor = (userId) => `transcript:user:${userId}`;
|
|
3
3
|
export class Transcripts {
|
|
4
4
|
state;
|
|
5
5
|
cfg;
|
|
@@ -12,39 +12,39 @@ export class Transcripts {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Append a message to the user's transcript.
|
|
15
|
-
* No-ops silently when no
|
|
15
|
+
* No-ops silently when no canonical application user ID is supplied.
|
|
16
16
|
*/
|
|
17
17
|
async append(thread, msg, opts) {
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
18
|
+
const userId = opts.userId;
|
|
19
|
+
if (!userId)
|
|
20
20
|
return; // identity unresolved → no-op
|
|
21
21
|
const entry = {
|
|
22
22
|
role: msg.role ?? "user",
|
|
23
23
|
text: msg.text,
|
|
24
24
|
platform: thread.platform,
|
|
25
25
|
threadId: thread.conversationKey,
|
|
26
|
-
|
|
26
|
+
userId,
|
|
27
27
|
ts: Date.now(),
|
|
28
28
|
};
|
|
29
|
-
await this.state.list.append(keyFor(
|
|
29
|
+
await this.state.list.append(keyFor(userId), entry, {
|
|
30
30
|
maxLen: this.cfg.maxPerUser,
|
|
31
31
|
ttlMs: this.retentionMs,
|
|
32
32
|
});
|
|
33
33
|
if (this.retentionMs !== undefined) {
|
|
34
34
|
const cutoff = Date.now() - this.retentionMs;
|
|
35
|
-
const all = await this.state.list.range(keyFor(
|
|
35
|
+
const all = await this.state.list.range(keyFor(userId));
|
|
36
36
|
const expired = all.filter((e) => e.ts < cutoff).length;
|
|
37
37
|
if (expired > 0) {
|
|
38
38
|
const survivors = all.length - expired;
|
|
39
39
|
if (survivors <= 0)
|
|
40
|
-
await this.state.list.delete(keyFor(
|
|
40
|
+
await this.state.list.delete(keyFor(userId));
|
|
41
41
|
else
|
|
42
|
-
await this.state.list.trim(keyFor(
|
|
42
|
+
await this.state.list.trim(keyFor(userId), survivors);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
async list(q) {
|
|
47
|
-
let items = await this.state.list.range(keyFor(q.
|
|
47
|
+
let items = await this.state.list.range(keyFor(q.userId));
|
|
48
48
|
if (this.retentionMs !== undefined) {
|
|
49
49
|
const cutoff = Date.now() - this.retentionMs;
|
|
50
50
|
items = items.filter((e) => e.ts >= cutoff);
|
|
@@ -60,8 +60,8 @@ export class Transcripts {
|
|
|
60
60
|
return items; // oldest-first
|
|
61
61
|
}
|
|
62
62
|
async delete(q) {
|
|
63
|
-
const n = (await this.state.list.range(keyFor(q.
|
|
64
|
-
await this.state.list.delete(keyFor(q.
|
|
63
|
+
const n = (await this.state.list.range(keyFor(q.userId))).length;
|
|
64
|
+
await this.state.list.delete(keyFor(q.userId));
|
|
65
65
|
return { deleted: n };
|
|
66
66
|
}
|
|
67
67
|
}
|
package/dist/transcripts.test.js
CHANGED
|
@@ -8,45 +8,44 @@ const thread = {
|
|
|
8
8
|
describe("Transcripts", () => {
|
|
9
9
|
it("appends and lists oldest-first, filters compose", async () => {
|
|
10
10
|
const t = new Transcripts(new MemoryStore(), { maxPerUser: 100 });
|
|
11
|
-
await t.append(thread, { role: "user", text: "hi" }, {
|
|
12
|
-
await t.append(thread, { role: "assistant", text: "hello" }, {
|
|
13
|
-
const all = await t.list({
|
|
11
|
+
await t.append(thread, { role: "user", text: "hi" }, { userId: "u@x.com" });
|
|
12
|
+
await t.append(thread, { role: "assistant", text: "hello" }, { userId: "u@x.com" });
|
|
13
|
+
const all = await t.list({ userId: "u@x.com" });
|
|
14
14
|
expect(all.map((e) => e.role)).toEqual(["user", "assistant"]);
|
|
15
|
-
expect(await t.list({
|
|
15
|
+
expect(await t.list({ userId: "u@x.com", roles: ["user"] })).toHaveLength(1);
|
|
16
16
|
});
|
|
17
17
|
it("delete wipes and reports count", async () => {
|
|
18
18
|
const t = new Transcripts(new MemoryStore());
|
|
19
|
-
await t.append(thread, { role: "user", text: "x" }, {
|
|
20
|
-
expect(await t.delete({
|
|
21
|
-
expect(await t.list({
|
|
19
|
+
await t.append(thread, { role: "user", text: "x" }, { userId: "k" });
|
|
20
|
+
expect(await t.delete({ userId: "k" })).toEqual({ deleted: 1 });
|
|
21
|
+
expect(await t.list({ userId: "k" })).toEqual([]);
|
|
22
22
|
});
|
|
23
23
|
it("TTL expiry: entries not visible after retention window", async () => {
|
|
24
24
|
const t = new Transcripts(new MemoryStore(), { retention: "30ms" });
|
|
25
|
-
await t.append(thread, { role: "user", text: "hi" }, {
|
|
25
|
+
await t.append(thread, { role: "user", text: "hi" }, { userId: "u" });
|
|
26
26
|
await new Promise((r) => setTimeout(r, 60));
|
|
27
|
-
expect(await t.list({
|
|
27
|
+
expect(await t.list({ userId: "u" })).toEqual([]);
|
|
28
28
|
});
|
|
29
29
|
it("maxPerUser: only newest N entries are kept", async () => {
|
|
30
30
|
const t = new Transcripts(new MemoryStore(), { maxPerUser: 2 });
|
|
31
|
-
await t.append(thread, { role: "user", text: "1" }, {
|
|
32
|
-
await t.append(thread, { role: "user", text: "2" }, {
|
|
33
|
-
await t.append(thread, { role: "user", text: "3" }, {
|
|
34
|
-
const entries = await t.list({
|
|
31
|
+
await t.append(thread, { role: "user", text: "1" }, { userId: "u" });
|
|
32
|
+
await t.append(thread, { role: "user", text: "2" }, { userId: "u" });
|
|
33
|
+
await t.append(thread, { role: "user", text: "3" }, { userId: "u" });
|
|
34
|
+
const entries = await t.list({ userId: "u" });
|
|
35
35
|
expect(entries).toHaveLength(2);
|
|
36
36
|
expect(entries.map((e) => e.text)).toEqual(["2", "3"]);
|
|
37
37
|
});
|
|
38
|
-
it("no
|
|
38
|
+
it("no userId resolved: no-op, list returns empty for any key", async () => {
|
|
39
39
|
const t = new Transcripts(new MemoryStore());
|
|
40
|
-
|
|
41
|
-
await t.
|
|
42
|
-
expect(await t.list({ userKey: "anyone" })).toEqual([]);
|
|
40
|
+
await t.append(thread, { role: "user", text: "ghost" }, { userId: null });
|
|
41
|
+
expect(await t.list({ userId: "anyone" })).toEqual([]);
|
|
43
42
|
});
|
|
44
43
|
it("list with limit: returns only the last N entries", async () => {
|
|
45
44
|
const t = new Transcripts(new MemoryStore());
|
|
46
|
-
await t.append(thread, { role: "user", text: "a" }, {
|
|
47
|
-
await t.append(thread, { role: "user", text: "b" }, {
|
|
48
|
-
await t.append(thread, { role: "user", text: "c" }, {
|
|
49
|
-
const last = await t.list({
|
|
45
|
+
await t.append(thread, { role: "user", text: "a" }, { userId: "u" });
|
|
46
|
+
await t.append(thread, { role: "user", text: "b" }, { userId: "u" });
|
|
47
|
+
await t.append(thread, { role: "user", text: "c" }, { userId: "u" });
|
|
48
|
+
const last = await t.list({ userId: "u", limit: 1 });
|
|
50
49
|
expect(last).toHaveLength(1);
|
|
51
50
|
expect(last[0].text).toBe("c");
|
|
52
51
|
});
|
|
@@ -60,9 +59,9 @@ describe("Transcripts retention", () => {
|
|
|
60
59
|
const t = new Transcripts(store, { retention: "1h" });
|
|
61
60
|
vi.useFakeTimers();
|
|
62
61
|
try {
|
|
63
|
-
await t.append(thread, { role: "user", text: "old" }, {
|
|
62
|
+
await t.append(thread, { role: "user", text: "old" }, { userId: "u" });
|
|
64
63
|
vi.advanceTimersByTime(2 * 60 * 60 * 1000); // advance 2h
|
|
65
|
-
expect(await t.list({
|
|
64
|
+
expect(await t.list({ userId: "u" })).toEqual([]);
|
|
66
65
|
}
|
|
67
66
|
finally {
|
|
68
67
|
vi.useRealTimers();
|
|
@@ -78,11 +77,11 @@ describe("Transcripts retention", () => {
|
|
|
78
77
|
const t = new Transcripts(store, { retention: "1h" });
|
|
79
78
|
vi.useFakeTimers();
|
|
80
79
|
try {
|
|
81
|
-
await t.append(thread, { role: "user", text: "A" }, {
|
|
80
|
+
await t.append(thread, { role: "user", text: "A" }, { userId: "u" });
|
|
82
81
|
vi.advanceTimersByTime(50 * 60 * 1000); // +50m: A still within window, key TTL refreshed to +110m
|
|
83
|
-
await t.append(thread, { role: "user", text: "B" }, {
|
|
82
|
+
await t.append(thread, { role: "user", text: "B" }, { userId: "u" });
|
|
84
83
|
vi.advanceTimersByTime(50 * 60 * 1000); // +100m: key still live (expires +110m), but A is now 100m old
|
|
85
|
-
await t.append(thread, { role: "user", text: "C" }, {
|
|
84
|
+
await t.append(thread, { role: "user", text: "C" }, { userId: "u" });
|
|
86
85
|
const raw = await store.list.range("transcript:user:u");
|
|
87
86
|
expect(raw.map((e) => e.text)).toEqual(["B", "C"]); // A pruned by age, not whole-key expiry
|
|
88
87
|
}
|
|
@@ -95,10 +94,10 @@ describe("Transcripts retention", () => {
|
|
|
95
94
|
const t = new Transcripts(store, { retention: "1h" });
|
|
96
95
|
vi.useFakeTimers();
|
|
97
96
|
try {
|
|
98
|
-
await t.append(thread, { role: "user", text: "A" }, {
|
|
97
|
+
await t.append(thread, { role: "user", text: "A" }, { userId: "u" });
|
|
99
98
|
vi.advanceTimersByTime(30 * 60 * 1000); // advance 30m — A still within 1h
|
|
100
|
-
await t.append(thread, { role: "user", text: "B" }, {
|
|
101
|
-
const entries = await t.list({
|
|
99
|
+
await t.append(thread, { role: "user", text: "B" }, { userId: "u" });
|
|
100
|
+
const entries = await t.list({ userId: "u" });
|
|
102
101
|
expect(entries).toHaveLength(2);
|
|
103
102
|
}
|
|
104
103
|
finally {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"welcome.test.d.ts","sourceRoot":"","sources":["../src/welcome.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest";
|
|
2
|
+
import { createChannel } from "./create-channel.js";
|
|
3
|
+
import { FakeAdapter } from "./testing/fake-adapter.js";
|
|
4
|
+
test("routes a welcome lifecycle event without synthesizing a message turn", async () => {
|
|
5
|
+
const adapter = new FakeAdapter();
|
|
6
|
+
const onWelcome = vi.fn();
|
|
7
|
+
const onMessage = vi.fn();
|
|
8
|
+
const channel = createChannel({
|
|
9
|
+
identifyUser: () => ({ id: "app-user-1", name: "Ada App" }),
|
|
10
|
+
adapters: [adapter],
|
|
11
|
+
});
|
|
12
|
+
channel.onWelcome(onWelcome);
|
|
13
|
+
channel.onMessage(onMessage);
|
|
14
|
+
await channel.ɵruntime.start();
|
|
15
|
+
await adapter.emitWelcome({
|
|
16
|
+
conversationKey: "teams:tenant:conversation",
|
|
17
|
+
replyTarget: { conversationId: "conversation" },
|
|
18
|
+
platform: "teams",
|
|
19
|
+
actor: { id: "provider-user-1", kind: "human", name: "Ada Provider" },
|
|
20
|
+
});
|
|
21
|
+
expect(onWelcome).toHaveBeenCalledOnce();
|
|
22
|
+
expect(onWelcome).toHaveBeenCalledWith(expect.objectContaining({
|
|
23
|
+
user: { id: "app-user-1", name: "Ada App" },
|
|
24
|
+
actor: {
|
|
25
|
+
id: "provider-user-1",
|
|
26
|
+
kind: "human",
|
|
27
|
+
name: "Ada Provider",
|
|
28
|
+
},
|
|
29
|
+
platform: "teams",
|
|
30
|
+
}));
|
|
31
|
+
expect(onMessage).not.toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
test("exposes submitted input values separately from the action envelope", async () => {
|
|
34
|
+
const adapter = new FakeAdapter();
|
|
35
|
+
const handler = vi.fn();
|
|
36
|
+
const channel = createChannel({
|
|
37
|
+
identifyUser: "platform",
|
|
38
|
+
adapters: [adapter],
|
|
39
|
+
});
|
|
40
|
+
channel.onInteraction("approve", handler);
|
|
41
|
+
await channel.ɵruntime.start();
|
|
42
|
+
adapter.emitInteraction({
|
|
43
|
+
id: "approve",
|
|
44
|
+
value: { decision: "yes" },
|
|
45
|
+
values: { reason: "ready", priority: "high" },
|
|
46
|
+
platform: "teams",
|
|
47
|
+
});
|
|
48
|
+
await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce());
|
|
49
|
+
expect(handler).toHaveBeenCalledWith(expect.objectContaining({
|
|
50
|
+
action: { id: "approve", value: { decision: "yes" } },
|
|
51
|
+
values: { reason: "ready", priority: "high" },
|
|
52
|
+
}));
|
|
53
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/channels-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1-canary.1785633429",
|
|
4
4
|
"description": "Platform-agnostic JSX channel engine for CopilotKit (createChannel, Thread, PlatformAdapter, ActionStore).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@ag-ui/client": "0.0.57",
|
|
52
52
|
"@ag-ui/core": "0.0.57",
|
|
53
|
-
"
|
|
54
|
-
"@copilotkit/core": "
|
|
55
|
-
"@copilotkit/shared": "
|
|
56
|
-
"
|
|
53
|
+
"@copilotkit/channels-ui": "0.5.1-canary.1785633429",
|
|
54
|
+
"@copilotkit/core": "1.64.3-canary.1785633429",
|
|
55
|
+
"@copilotkit/shared": "1.64.3-canary.1785633429",
|
|
56
|
+
"zod-to-json-schema": "^3.24.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.10.0",
|