@kognitivedev/ui 0.2.11
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/.turbo/turbo-build.log +2 -0
- package/CHANGELOG.md +19 -0
- package/README.md +264 -0
- package/dist/__tests__/context-provider.test.d.ts +1 -0
- package/dist/__tests__/context-provider.test.js +38 -0
- package/dist/__tests__/event-emitter.test.d.ts +1 -0
- package/dist/__tests__/event-emitter.test.js +62 -0
- package/dist/__tests__/keyboard-shortcuts.test.d.ts +1 -0
- package/dist/__tests__/keyboard-shortcuts.test.js +36 -0
- package/dist/__tests__/kognitive-runtime.test.d.ts +1 -0
- package/dist/__tests__/kognitive-runtime.test.js +58 -0
- package/dist/__tests__/kognitive-transport.test.d.ts +1 -0
- package/dist/__tests__/kognitive-transport.test.js +96 -0
- package/dist/__tests__/make-tool-ui.test.d.ts +1 -0
- package/dist/__tests__/make-tool-ui.test.js +50 -0
- package/dist/__tests__/message-helpers.test.d.ts +1 -0
- package/dist/__tests__/message-helpers.test.js +80 -0
- package/dist/__tests__/thread-manager.test.d.ts +1 -0
- package/dist/__tests__/thread-manager.test.js +84 -0
- package/dist/__tests__/tool-ui-registry.test.d.ts +1 -0
- package/dist/__tests__/tool-ui-registry.test.js +68 -0
- package/dist/__tests__/toolkit.test.d.ts +1 -0
- package/dist/__tests__/toolkit.test.js +33 -0
- package/dist/components/attachment-preview.d.ts +8 -0
- package/dist/components/attachment-preview.js +10 -0
- package/dist/components/composer.d.ts +6 -0
- package/dist/components/composer.js +10 -0
- package/dist/components/error-banner.d.ts +6 -0
- package/dist/components/error-banner.js +8 -0
- package/dist/components/markdown-content.d.ts +13 -0
- package/dist/components/markdown-content.js +31 -0
- package/dist/components/message.d.ts +7 -0
- package/dist/components/message.js +28 -0
- package/dist/components/status-indicator.d.ts +6 -0
- package/dist/components/status-indicator.js +16 -0
- package/dist/components/suggestions.d.ts +4 -0
- package/dist/components/suggestions.js +12 -0
- package/dist/components/thread-list.d.ts +4 -0
- package/dist/components/thread-list.js +20 -0
- package/dist/components/thread.d.ts +15 -0
- package/dist/components/thread.js +19 -0
- package/dist/components/tool-approval.d.ts +7 -0
- package/dist/components/tool-approval.js +12 -0
- package/dist/components/tool-invocation.d.ts +5 -0
- package/dist/components/tool-invocation.js +33 -0
- package/dist/context/kognitive-context.d.ts +2 -0
- package/dist/context/kognitive-context.js +6 -0
- package/dist/context/types.d.ts +44 -0
- package/dist/context/types.js +2 -0
- package/dist/context/use-kognitive.d.ts +1 -0
- package/dist/context/use-kognitive.js +7 -0
- package/dist/hooks/use-auto-scroll.d.ts +2 -0
- package/dist/hooks/use-auto-scroll.js +18 -0
- package/dist/hooks/use-copy-to-clipboard.d.ts +4 -0
- package/dist/hooks/use-copy-to-clipboard.js +13 -0
- package/dist/hooks/use-keyboard-shortcuts.d.ts +8 -0
- package/dist/hooks/use-keyboard-shortcuts.js +24 -0
- package/dist/hooks/use-kognitive-chat.d.ts +38 -0
- package/dist/hooks/use-kognitive-chat.js +35 -0
- package/dist/hooks/use-kognitive-event.d.ts +12 -0
- package/dist/hooks/use-kognitive-event.js +47 -0
- package/dist/hooks/use-streaming-status.d.ts +2 -0
- package/dist/hooks/use-streaming-status.js +8 -0
- package/dist/hooks/use-thread-manager.d.ts +20 -0
- package/dist/hooks/use-thread-manager.js +83 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +92 -0
- package/dist/kognitive-ui.d.ts +48 -0
- package/dist/kognitive-ui.js +130 -0
- package/dist/primitives/action-bar/action-bar-copy.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-copy.js +16 -0
- package/dist/primitives/action-bar/action-bar-edit.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-edit.js +11 -0
- package/dist/primitives/action-bar/action-bar-feedback.d.ts +10 -0
- package/dist/primitives/action-bar/action-bar-feedback.js +30 -0
- package/dist/primitives/action-bar/action-bar-retry.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-retry.js +25 -0
- package/dist/primitives/action-bar/action-bar-root.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-root.js +7 -0
- package/dist/primitives/action-bar/action-bar-stop.d.ts +6 -0
- package/dist/primitives/action-bar/action-bar-stop.js +11 -0
- package/dist/primitives/action-bar/index.d.ts +14 -0
- package/dist/primitives/action-bar/index.js +17 -0
- package/dist/primitives/composer/composer-attachment-trigger.d.ts +7 -0
- package/dist/primitives/composer/composer-attachment-trigger.js +33 -0
- package/dist/primitives/composer/composer-attachments.d.ts +7 -0
- package/dist/primitives/composer/composer-attachments.js +16 -0
- package/dist/primitives/composer/composer-input.d.ts +6 -0
- package/dist/primitives/composer/composer-input.js +19 -0
- package/dist/primitives/composer/composer-root.d.ts +6 -0
- package/dist/primitives/composer/composer-root.js +91 -0
- package/dist/primitives/composer/composer-send.d.ts +6 -0
- package/dist/primitives/composer/composer-send.js +10 -0
- package/dist/primitives/composer/edit-composer-root.d.ts +11 -0
- package/dist/primitives/composer/edit-composer-root.js +24 -0
- package/dist/primitives/composer/index.d.ts +15 -0
- package/dist/primitives/composer/index.js +19 -0
- package/dist/primitives/composer/use-composer.d.ts +12 -0
- package/dist/primitives/composer/use-composer.js +6 -0
- package/dist/primitives/message/index.d.ts +9 -0
- package/dist/primitives/message/index.js +13 -0
- package/dist/primitives/message/message-content.d.ts +25 -0
- package/dist/primitives/message/message-content.js +70 -0
- package/dist/primitives/message/message-role.d.ts +6 -0
- package/dist/primitives/message/message-role.js +11 -0
- package/dist/primitives/message/message-root.d.ts +9 -0
- package/dist/primitives/message/message-root.js +38 -0
- package/dist/primitives/message/use-message.d.ts +10 -0
- package/dist/primitives/message/use-message.js +6 -0
- package/dist/primitives/thread/index.d.ts +17 -0
- package/dist/primitives/thread/index.js +21 -0
- package/dist/primitives/thread/thread-empty.d.ts +5 -0
- package/dist/primitives/thread/thread-empty.js +11 -0
- package/dist/primitives/thread/thread-error.d.ts +6 -0
- package/dist/primitives/thread/thread-error.js +11 -0
- package/dist/primitives/thread/thread-loading.d.ts +5 -0
- package/dist/primitives/thread/thread-loading.js +11 -0
- package/dist/primitives/thread/thread-messages.d.ts +6 -0
- package/dist/primitives/thread/thread-messages.js +9 -0
- package/dist/primitives/thread/thread-root.d.ts +6 -0
- package/dist/primitives/thread/thread-root.js +12 -0
- package/dist/primitives/thread/thread-scroll-to-bottom.d.ts +6 -0
- package/dist/primitives/thread/thread-scroll-to-bottom.js +14 -0
- package/dist/primitives/thread/thread-suggestions.d.ts +6 -0
- package/dist/primitives/thread/thread-suggestions.js +14 -0
- package/dist/primitives/thread/use-thread.d.ts +9 -0
- package/dist/primitives/thread/use-thread.js +6 -0
- package/dist/primitives/thread-list/index.d.ts +11 -0
- package/dist/primitives/thread-list/index.js +15 -0
- package/dist/primitives/thread-list/thread-list-item.d.ts +9 -0
- package/dist/primitives/thread-list/thread-list-item.js +13 -0
- package/dist/primitives/thread-list/thread-list-items.d.ts +6 -0
- package/dist/primitives/thread-list/thread-list-items.js +9 -0
- package/dist/primitives/thread-list/thread-list-new.d.ts +6 -0
- package/dist/primitives/thread-list/thread-list-new.js +13 -0
- package/dist/primitives/thread-list/thread-list-root.d.ts +6 -0
- package/dist/primitives/thread-list/thread-list-root.js +16 -0
- package/dist/primitives/thread-list/use-thread-list.d.ts +9 -0
- package/dist/primitives/thread-list/use-thread-list.js +6 -0
- package/dist/primitives/tool-ui/index.d.ts +7 -0
- package/dist/primitives/tool-ui/index.js +11 -0
- package/dist/primitives/tool-ui/tool-ui-fallback.d.ts +5 -0
- package/dist/primitives/tool-ui/tool-ui-fallback.js +20 -0
- package/dist/primitives/tool-ui/tool-ui-root.d.ts +5 -0
- package/dist/primitives/tool-ui/tool-ui-root.js +20 -0
- package/dist/primitives/tool-ui/use-tool-ui.d.ts +2 -0
- package/dist/primitives/tool-ui/use-tool-ui.js +8 -0
- package/dist/runtime/kognitive-runtime.d.ts +30 -0
- package/dist/runtime/kognitive-runtime.js +32 -0
- package/dist/runtime/kognitive-transport.d.ts +26 -0
- package/dist/runtime/kognitive-transport.js +42 -0
- package/dist/runtime/thread-manager.d.ts +17 -0
- package/dist/runtime/thread-manager.js +62 -0
- package/dist/runtime/types.d.ts +58 -0
- package/dist/runtime/types.js +2 -0
- package/dist/tool-ui/make-tool-ui.d.ts +59 -0
- package/dist/tool-ui/make-tool-ui.js +10 -0
- package/dist/tool-ui/registry.d.ts +9 -0
- package/dist/tool-ui/registry.js +26 -0
- package/dist/tool-ui/tool-ui-context.d.ts +2 -0
- package/dist/tool-ui/tool-ui-context.js +6 -0
- package/dist/tool-ui/toolkit.d.ts +31 -0
- package/dist/tool-ui/toolkit.js +33 -0
- package/dist/tool-ui/types.d.ts +19 -0
- package/dist/tool-ui/types.js +2 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +8 -0
- package/dist/utils/create-context.d.ts +1 -0
- package/dist/utils/create-context.js +16 -0
- package/dist/utils/message-helpers.d.ts +6 -0
- package/dist/utils/message-helpers.js +46 -0
- package/package.json +56 -0
- package/src/__tests__/context-provider.test.ts +43 -0
- package/src/__tests__/event-emitter.test.ts +69 -0
- package/src/__tests__/keyboard-shortcuts.test.ts +55 -0
- package/src/__tests__/kognitive-runtime.test.ts +62 -0
- package/src/__tests__/kognitive-transport.test.ts +113 -0
- package/src/__tests__/make-tool-ui.test.ts +60 -0
- package/src/__tests__/message-helpers.test.ts +101 -0
- package/src/__tests__/thread-manager.test.ts +118 -0
- package/src/__tests__/tool-ui-registry.test.ts +80 -0
- package/src/__tests__/toolkit.test.ts +37 -0
- package/src/components/attachment-preview.tsx +46 -0
- package/src/components/composer.tsx +59 -0
- package/src/components/error-banner.tsx +33 -0
- package/src/components/markdown-content.tsx +64 -0
- package/src/components/message.tsx +145 -0
- package/src/components/status-indicator.tsx +26 -0
- package/src/components/suggestions.tsx +27 -0
- package/src/components/thread-list.tsx +69 -0
- package/src/components/thread.tsx +89 -0
- package/src/components/tool-approval.tsx +54 -0
- package/src/components/tool-invocation.tsx +94 -0
- package/src/context/kognitive-context.tsx +8 -0
- package/src/context/types.ts +43 -0
- package/src/context/use-kognitive.ts +5 -0
- package/src/hooks/use-auto-scroll.ts +19 -0
- package/src/hooks/use-copy-to-clipboard.ts +16 -0
- package/src/hooks/use-keyboard-shortcuts.ts +34 -0
- package/src/hooks/use-kognitive-chat.ts +73 -0
- package/src/hooks/use-kognitive-event.ts +56 -0
- package/src/hooks/use-streaming-status.ts +7 -0
- package/src/hooks/use-thread-manager.ts +114 -0
- package/src/index.ts +56 -0
- package/src/kognitive-ui.tsx +216 -0
- package/src/primitives/action-bar/action-bar-copy.tsx +30 -0
- package/src/primitives/action-bar/action-bar-edit.tsx +24 -0
- package/src/primitives/action-bar/action-bar-feedback.tsx +59 -0
- package/src/primitives/action-bar/action-bar-retry.tsx +38 -0
- package/src/primitives/action-bar/action-bar-root.tsx +14 -0
- package/src/primitives/action-bar/action-bar-stop.tsx +24 -0
- package/src/primitives/action-bar/index.ts +15 -0
- package/src/primitives/composer/composer-attachment-trigger.tsx +70 -0
- package/src/primitives/composer/composer-attachments.tsx +36 -0
- package/src/primitives/composer/composer-input.tsx +46 -0
- package/src/primitives/composer/composer-root.tsx +130 -0
- package/src/primitives/composer/composer-send.tsx +23 -0
- package/src/primitives/composer/edit-composer-root.tsx +52 -0
- package/src/primitives/composer/index.ts +17 -0
- package/src/primitives/composer/use-composer.ts +19 -0
- package/src/primitives/message/index.ts +11 -0
- package/src/primitives/message/message-content.tsx +117 -0
- package/src/primitives/message/message-role.tsx +13 -0
- package/src/primitives/message/message-root.tsx +64 -0
- package/src/primitives/message/use-message.ts +17 -0
- package/src/primitives/thread/index.ts +19 -0
- package/src/primitives/thread/thread-empty.tsx +12 -0
- package/src/primitives/thread/thread-error.tsx +18 -0
- package/src/primitives/thread/thread-loading.tsx +12 -0
- package/src/primitives/thread/thread-messages.tsx +12 -0
- package/src/primitives/thread/thread-root.tsx +28 -0
- package/src/primitives/thread/thread-scroll-to-bottom.tsx +26 -0
- package/src/primitives/thread/thread-suggestions.tsx +31 -0
- package/src/primitives/thread/use-thread.ts +16 -0
- package/src/primitives/thread-list/index.ts +13 -0
- package/src/primitives/thread-list/thread-list-item.tsx +37 -0
- package/src/primitives/thread-list/thread-list-items.tsx +19 -0
- package/src/primitives/thread-list/thread-list-new.tsx +26 -0
- package/src/primitives/thread-list/thread-list-root.tsx +29 -0
- package/src/primitives/thread-list/use-thread-list.ts +16 -0
- package/src/primitives/tool-ui/index.ts +9 -0
- package/src/primitives/tool-ui/tool-ui-fallback.tsx +63 -0
- package/src/primitives/tool-ui/tool-ui-root.tsx +26 -0
- package/src/primitives/tool-ui/use-tool-ui.ts +7 -0
- package/src/runtime/kognitive-runtime.ts +56 -0
- package/src/runtime/kognitive-transport.ts +56 -0
- package/src/runtime/thread-manager.ts +92 -0
- package/src/runtime/types.ts +63 -0
- package/src/tool-ui/make-tool-ui.ts +71 -0
- package/src/tool-ui/registry.ts +27 -0
- package/src/tool-ui/tool-ui-context.tsx +8 -0
- package/src/tool-ui/toolkit.ts +40 -0
- package/src/tool-ui/types.ts +29 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/create-context.ts +18 -0
- package/src/utils/message-helpers.ts +42 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kognitivedev/ui",
|
|
3
|
+
"version": "0.2.11",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc -w",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
13
|
+
"test": "vitest run"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@kognitivedev/shared": "^0.2.11",
|
|
17
|
+
"clsx": "^2.1.0",
|
|
18
|
+
"tailwind-merge": "^3.0.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=19.0.0",
|
|
22
|
+
"@ai-sdk/react": ">=3.0.0",
|
|
23
|
+
"ai": ">=6.0.0",
|
|
24
|
+
"zod": ">=3.23.0"
|
|
25
|
+
},
|
|
26
|
+
"optionalDependencies": {
|
|
27
|
+
"@kognitivedev/tools": "^0.2.11"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "^5.0.0",
|
|
31
|
+
"@types/react": "^19",
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
33
|
+
"react": "^19.0.0",
|
|
34
|
+
"@ai-sdk/react": "^3.0.0",
|
|
35
|
+
"ai": "^6.0.0",
|
|
36
|
+
"zod": "^3.23.0",
|
|
37
|
+
"vitest": "^3.0.0"
|
|
38
|
+
},
|
|
39
|
+
"description": "React components for building agentic chat UIs with Kognitive",
|
|
40
|
+
"keywords": [
|
|
41
|
+
"kognitive",
|
|
42
|
+
"ai",
|
|
43
|
+
"chat",
|
|
44
|
+
"ui",
|
|
45
|
+
"react",
|
|
46
|
+
"agent",
|
|
47
|
+
"tools"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/kognitivedev/kognitive",
|
|
53
|
+
"directory": "packages/ui"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://kognitive.dev"
|
|
56
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ToolUIRegistry } from "../tool-ui/registry";
|
|
3
|
+
|
|
4
|
+
describe("Context isolation", () => {
|
|
5
|
+
it("ToolUIRegistry can be constructed standalone", () => {
|
|
6
|
+
const registry = new ToolUIRegistry();
|
|
7
|
+
expect(registry.names()).toHaveLength(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("ToolUIRegistry supports full CRUD lifecycle", () => {
|
|
11
|
+
const registry = new ToolUIRegistry();
|
|
12
|
+
|
|
13
|
+
// Initially empty
|
|
14
|
+
expect(registry.has("tool-a")).toBe(false);
|
|
15
|
+
expect(registry.get("tool-a")).toBeUndefined();
|
|
16
|
+
|
|
17
|
+
// Register
|
|
18
|
+
const component = () => null;
|
|
19
|
+
registry.register({ toolName: "tool-a", component });
|
|
20
|
+
expect(registry.has("tool-a")).toBe(true);
|
|
21
|
+
expect(registry.get("tool-a")).toBe(component);
|
|
22
|
+
expect(registry.names()).toContain("tool-a");
|
|
23
|
+
|
|
24
|
+
// Overwrite
|
|
25
|
+
const component2 = () => null;
|
|
26
|
+
registry.register({ toolName: "tool-a", component: component2 });
|
|
27
|
+
expect(registry.get("tool-a")).toBe(component2);
|
|
28
|
+
|
|
29
|
+
// Multiple
|
|
30
|
+
registry.register({ toolName: "tool-b", component });
|
|
31
|
+
expect(registry.names()).toHaveLength(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("ToolUIRegistry.registerAll registers multiple at once", () => {
|
|
35
|
+
const registry = new ToolUIRegistry();
|
|
36
|
+
registry.registerAll([
|
|
37
|
+
{ toolName: "a", component: () => null },
|
|
38
|
+
{ toolName: "b", component: () => null },
|
|
39
|
+
{ toolName: "c", component: () => null },
|
|
40
|
+
]);
|
|
41
|
+
expect(registry.names()).toHaveLength(3);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { KognitiveEventEmitter } from "../hooks/use-kognitive-event";
|
|
3
|
+
|
|
4
|
+
describe("KognitiveEventEmitter", () => {
|
|
5
|
+
it("emits events to registered listeners", () => {
|
|
6
|
+
const emitter = new KognitiveEventEmitter();
|
|
7
|
+
const handler = vi.fn();
|
|
8
|
+
emitter.on("message:send", handler);
|
|
9
|
+
emitter.emit("message:send", { text: "hello" });
|
|
10
|
+
expect(handler).toHaveBeenCalledWith({ text: "hello" });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("supports multiple listeners for the same event", () => {
|
|
14
|
+
const emitter = new KognitiveEventEmitter();
|
|
15
|
+
const h1 = vi.fn();
|
|
16
|
+
const h2 = vi.fn();
|
|
17
|
+
emitter.on("message:received", h1);
|
|
18
|
+
emitter.on("message:received", h2);
|
|
19
|
+
emitter.emit("message:received", "data");
|
|
20
|
+
expect(h1).toHaveBeenCalledOnce();
|
|
21
|
+
expect(h2).toHaveBeenCalledOnce();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns an unsubscribe function", () => {
|
|
25
|
+
const emitter = new KognitiveEventEmitter();
|
|
26
|
+
const handler = vi.fn();
|
|
27
|
+
const unsub = emitter.on("status:change", handler);
|
|
28
|
+
emitter.emit("status:change", "streaming");
|
|
29
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
30
|
+
|
|
31
|
+
unsub();
|
|
32
|
+
emitter.emit("status:change", "ready");
|
|
33
|
+
expect(handler).toHaveBeenCalledOnce(); // not called again
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("does not throw when emitting with no listeners", () => {
|
|
37
|
+
const emitter = new KognitiveEventEmitter();
|
|
38
|
+
expect(() => emitter.emit("error", new Error("test"))).not.toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("swallows listener errors", () => {
|
|
42
|
+
const emitter = new KognitiveEventEmitter();
|
|
43
|
+
emitter.on("tool:invoked", () => { throw new Error("boom"); });
|
|
44
|
+
const good = vi.fn();
|
|
45
|
+
emitter.on("tool:invoked", good);
|
|
46
|
+
|
|
47
|
+
expect(() => emitter.emit("tool:invoked", {})).not.toThrow();
|
|
48
|
+
expect(good).toHaveBeenCalledOnce();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("supports all event types", () => {
|
|
52
|
+
const emitter = new KognitiveEventEmitter();
|
|
53
|
+
const events = [
|
|
54
|
+
"message:send",
|
|
55
|
+
"message:received",
|
|
56
|
+
"status:change",
|
|
57
|
+
"tool:invoked",
|
|
58
|
+
"tool:completed",
|
|
59
|
+
"error",
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
const handler = vi.fn();
|
|
64
|
+
emitter.on(event, handler);
|
|
65
|
+
emitter.emit(event, "test");
|
|
66
|
+
expect(handler).toHaveBeenCalledWith("test");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Test the shortcut matching logic directly (not the hook, since it needs React)
|
|
4
|
+
describe("Keyboard shortcut matching", () => {
|
|
5
|
+
function matchShortcut(
|
|
6
|
+
shortcut: { key: string; ctrl?: boolean; meta?: boolean; shift?: boolean },
|
|
7
|
+
event: { key: string; ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean },
|
|
8
|
+
): boolean {
|
|
9
|
+
const ctrlOrMeta = shortcut.ctrl || shortcut.meta;
|
|
10
|
+
const matchesMod = ctrlOrMeta ? !!(event.ctrlKey || event.metaKey) : true;
|
|
11
|
+
const matchesShift = shortcut.shift ? !!event.shiftKey : !event.shiftKey;
|
|
12
|
+
return event.key.toLowerCase() === shortcut.key.toLowerCase() && matchesMod && matchesShift;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
it("matches simple key", () => {
|
|
16
|
+
expect(matchShortcut({ key: "Escape" }, { key: "Escape" })).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("does not match different key", () => {
|
|
20
|
+
expect(matchShortcut({ key: "Escape" }, { key: "Enter" })).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("matches Ctrl+key", () => {
|
|
24
|
+
expect(matchShortcut({ key: "c", ctrl: true }, { key: "c", ctrlKey: true })).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("matches Meta+key (Mac Cmd)", () => {
|
|
28
|
+
expect(matchShortcut({ key: "c", meta: true }, { key: "c", metaKey: true })).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("matches Ctrl+Shift+key", () => {
|
|
32
|
+
expect(matchShortcut(
|
|
33
|
+
{ key: "c", ctrl: true, shift: true },
|
|
34
|
+
{ key: "c", ctrlKey: true, shiftKey: true },
|
|
35
|
+
)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does not match when shift is required but not pressed", () => {
|
|
39
|
+
expect(matchShortcut(
|
|
40
|
+
{ key: "c", ctrl: true, shift: true },
|
|
41
|
+
{ key: "c", ctrlKey: true, shiftKey: false },
|
|
42
|
+
)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not match when shift is pressed but not required", () => {
|
|
46
|
+
expect(matchShortcut(
|
|
47
|
+
{ key: "c", ctrl: true },
|
|
48
|
+
{ key: "c", ctrlKey: true, shiftKey: true },
|
|
49
|
+
)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("is case insensitive", () => {
|
|
53
|
+
expect(matchShortcut({ key: "escape" }, { key: "Escape" })).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { KognitiveRuntime } from "../runtime/kognitive-runtime";
|
|
3
|
+
|
|
4
|
+
describe("KognitiveRuntime", () => {
|
|
5
|
+
it("creates a transport from config", () => {
|
|
6
|
+
const runtime = new KognitiveRuntime({
|
|
7
|
+
baseUrl: "http://localhost:3001",
|
|
8
|
+
agentName: "test-agent",
|
|
9
|
+
});
|
|
10
|
+
const transport = runtime.createTransport();
|
|
11
|
+
expect(transport).toBeDefined();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("creates a transport with custom sessionId", () => {
|
|
15
|
+
const runtime = new KognitiveRuntime({
|
|
16
|
+
baseUrl: "http://localhost:3001",
|
|
17
|
+
agentName: "test-agent",
|
|
18
|
+
});
|
|
19
|
+
const transport = runtime.createTransport("session-123");
|
|
20
|
+
expect(transport).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("creates ThreadManager when threads=true", () => {
|
|
24
|
+
const runtime = new KognitiveRuntime({
|
|
25
|
+
baseUrl: "http://localhost:3001",
|
|
26
|
+
agentName: "test-agent",
|
|
27
|
+
threads: true,
|
|
28
|
+
});
|
|
29
|
+
expect(runtime.threadManager).not.toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("does not create ThreadManager when threads=false", () => {
|
|
33
|
+
const runtime = new KognitiveRuntime({
|
|
34
|
+
baseUrl: "http://localhost:3001",
|
|
35
|
+
agentName: "test-agent",
|
|
36
|
+
});
|
|
37
|
+
expect(runtime.threadManager).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("uses custom threadApiBase", () => {
|
|
41
|
+
const runtime = new KognitiveRuntime({
|
|
42
|
+
baseUrl: "http://localhost:3001",
|
|
43
|
+
agentName: "test-agent",
|
|
44
|
+
threads: true,
|
|
45
|
+
threadApiBase: "http://custom:4000/threads",
|
|
46
|
+
});
|
|
47
|
+
expect(runtime.threadManager).not.toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("stores config for later access", () => {
|
|
51
|
+
const config = {
|
|
52
|
+
baseUrl: "http://localhost:3001",
|
|
53
|
+
agentName: "test-agent",
|
|
54
|
+
headers: { Authorization: "Bearer token" },
|
|
55
|
+
resourceId: { userId: "user-1" },
|
|
56
|
+
};
|
|
57
|
+
const runtime = new KognitiveRuntime(config);
|
|
58
|
+
expect(runtime.config.baseUrl).toBe(config.baseUrl);
|
|
59
|
+
expect(runtime.config.agentName).toBe(config.agentName);
|
|
60
|
+
expect(runtime.config.headers).toEqual(config.headers);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createKognitiveTransport } from "../runtime/kognitive-transport";
|
|
3
|
+
|
|
4
|
+
describe("createKognitiveTransport", () => {
|
|
5
|
+
describe("URL resolution", () => {
|
|
6
|
+
it("uses baseUrl + agentName to construct URL", () => {
|
|
7
|
+
const transport = createKognitiveTransport({
|
|
8
|
+
baseUrl: "http://localhost:3001",
|
|
9
|
+
agentName: "my-agent",
|
|
10
|
+
});
|
|
11
|
+
expect(transport).toBeDefined();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("uses explicit api URL when provided", () => {
|
|
15
|
+
const transport = createKognitiveTransport({
|
|
16
|
+
api: "/api/chat",
|
|
17
|
+
});
|
|
18
|
+
expect(transport).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("api takes priority over baseUrl + agentName", () => {
|
|
22
|
+
const transport = createKognitiveTransport({
|
|
23
|
+
api: "/api/custom",
|
|
24
|
+
baseUrl: "http://localhost:3001",
|
|
25
|
+
agentName: "agent",
|
|
26
|
+
});
|
|
27
|
+
expect(transport).toBeDefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("throws when neither api nor baseUrl+agentName provided", () => {
|
|
31
|
+
expect(() =>
|
|
32
|
+
createKognitiveTransport({}),
|
|
33
|
+
).toThrow("requires either `api` or both `baseUrl` and `agentName`");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("throws when only baseUrl provided (no agentName)", () => {
|
|
37
|
+
expect(() =>
|
|
38
|
+
createKognitiveTransport({ baseUrl: "http://localhost:3001" }),
|
|
39
|
+
).toThrow();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("throws when only agentName provided (no baseUrl)", () => {
|
|
43
|
+
expect(() =>
|
|
44
|
+
createKognitiveTransport({ agentName: "my-agent" }),
|
|
45
|
+
).toThrow();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("URL construction", () => {
|
|
50
|
+
it("URL-encodes the agent name", () => {
|
|
51
|
+
const transport = createKognitiveTransport({
|
|
52
|
+
baseUrl: "http://localhost:3001",
|
|
53
|
+
agentName: "agent with spaces",
|
|
54
|
+
});
|
|
55
|
+
expect(transport).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("strips trailing slash from baseUrl", () => {
|
|
59
|
+
const transport = createKognitiveTransport({
|
|
60
|
+
baseUrl: "http://localhost:3001/",
|
|
61
|
+
agentName: "test",
|
|
62
|
+
});
|
|
63
|
+
expect(transport).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("config passthrough", () => {
|
|
68
|
+
it("accepts sessionId", () => {
|
|
69
|
+
const transport = createKognitiveTransport({
|
|
70
|
+
api: "/api/chat",
|
|
71
|
+
sessionId: "sess-123",
|
|
72
|
+
});
|
|
73
|
+
expect(transport).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("accepts full resourceId with userId, sessionId, organizationId", () => {
|
|
77
|
+
const transport = createKognitiveTransport({
|
|
78
|
+
api: "/api/chat",
|
|
79
|
+
resourceId: {
|
|
80
|
+
userId: "user-1",
|
|
81
|
+
sessionId: "sess-1",
|
|
82
|
+
organizationId: "org-1",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
expect(transport).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("accepts custom headers", () => {
|
|
89
|
+
const transport = createKognitiveTransport({
|
|
90
|
+
api: "/api/chat",
|
|
91
|
+
headers: { Authorization: "Bearer token123" },
|
|
92
|
+
});
|
|
93
|
+
expect(transport).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("accepts extra body fields", () => {
|
|
97
|
+
const transport = createKognitiveTransport({
|
|
98
|
+
api: "/api/chat",
|
|
99
|
+
body: { agentName: "assistant", customField: true },
|
|
100
|
+
});
|
|
101
|
+
expect(transport).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("sends agentName in body when using api mode", () => {
|
|
105
|
+
// When api is set and agentName is provided, agentName should be in body
|
|
106
|
+
const transport = createKognitiveTransport({
|
|
107
|
+
api: "/api/chat",
|
|
108
|
+
agentName: "assistant",
|
|
109
|
+
});
|
|
110
|
+
expect(transport).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { makeToolUI } from "../tool-ui/make-tool-ui";
|
|
3
|
+
import { ToolUIRegistry } from "../tool-ui/registry";
|
|
4
|
+
|
|
5
|
+
describe("makeToolUI", () => {
|
|
6
|
+
it("creates a registration from a tool object with id", () => {
|
|
7
|
+
const render = () => null;
|
|
8
|
+
const reg = makeToolUI({
|
|
9
|
+
tool: { id: "weather" },
|
|
10
|
+
render,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(reg.toolName).toBe("weather");
|
|
14
|
+
expect(reg.component).toBe(render);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("creates a registration from toolName string", () => {
|
|
18
|
+
const render = () => null;
|
|
19
|
+
const reg = makeToolUI({
|
|
20
|
+
toolName: "database_query",
|
|
21
|
+
render,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(reg.toolName).toBe("database_query");
|
|
25
|
+
expect(reg.component).toBe(render);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("integrates with ToolUIRegistry", () => {
|
|
29
|
+
const reg = makeToolUI({
|
|
30
|
+
tool: { id: "my-tool" },
|
|
31
|
+
render: () => null,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const registry = new ToolUIRegistry();
|
|
35
|
+
registry.register(reg);
|
|
36
|
+
|
|
37
|
+
expect(registry.has("my-tool")).toBe(true);
|
|
38
|
+
expect(registry.get("my-tool")).toBe(reg.component);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("tool.id takes priority over toolName in tool-based registration", () => {
|
|
42
|
+
const reg = makeToolUI({
|
|
43
|
+
tool: { id: "from-tool-id" },
|
|
44
|
+
render: () => null,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(reg.toolName).toBe("from-tool-id");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("multiple registrations can coexist in registry", () => {
|
|
51
|
+
const reg1 = makeToolUI({ tool: { id: "tool-a" }, render: () => "a" });
|
|
52
|
+
const reg2 = makeToolUI({ toolName: "tool-b", render: () => "b" });
|
|
53
|
+
|
|
54
|
+
const registry = new ToolUIRegistry();
|
|
55
|
+
registry.register(reg1);
|
|
56
|
+
registry.register(reg2);
|
|
57
|
+
|
|
58
|
+
expect(registry.names()).toEqual(expect.arrayContaining(["tool-a", "tool-b"]));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
import {
|
|
4
|
+
extractTextContent,
|
|
5
|
+
getMessageText,
|
|
6
|
+
getMessagePreview,
|
|
7
|
+
getToolInvocations,
|
|
8
|
+
hasToolInvocations,
|
|
9
|
+
} from "../utils/message-helpers";
|
|
10
|
+
|
|
11
|
+
function makeMessage(parts: UIMessage["parts"], role: "user" | "assistant" = "assistant"): UIMessage {
|
|
12
|
+
return {
|
|
13
|
+
id: "msg-1",
|
|
14
|
+
role,
|
|
15
|
+
parts,
|
|
16
|
+
createdAt: new Date(),
|
|
17
|
+
} as UIMessage;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("extractTextContent", () => {
|
|
21
|
+
it("returns string content as-is", () => {
|
|
22
|
+
expect(extractTextContent("hello")).toBe("hello");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("extracts text from an array of parts", () => {
|
|
26
|
+
expect(
|
|
27
|
+
extractTextContent([
|
|
28
|
+
{ text: "hello" },
|
|
29
|
+
{ text: "world" },
|
|
30
|
+
]),
|
|
31
|
+
).toBe("hello\nworld");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns empty string for non-string non-array input", () => {
|
|
35
|
+
expect(extractTextContent(42)).toBe("");
|
|
36
|
+
expect(extractTextContent(null)).toBe("");
|
|
37
|
+
expect(extractTextContent(undefined)).toBe("");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("skips non-text parts", () => {
|
|
41
|
+
expect(
|
|
42
|
+
extractTextContent([
|
|
43
|
+
{ text: "keep" },
|
|
44
|
+
{ type: "image" },
|
|
45
|
+
{ text: "this" },
|
|
46
|
+
]),
|
|
47
|
+
).toBe("keep\nthis");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("getMessageText", () => {
|
|
52
|
+
it("extracts text from message parts", () => {
|
|
53
|
+
const msg = makeMessage([
|
|
54
|
+
{ type: "text", text: "Hello " },
|
|
55
|
+
{ type: "text", text: "world" },
|
|
56
|
+
]);
|
|
57
|
+
expect(getMessageText(msg)).toBe("Hello \nworld");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("skips non-text parts", () => {
|
|
61
|
+
const msg = makeMessage([
|
|
62
|
+
{ type: "text", text: "before" },
|
|
63
|
+
{ type: "text", text: "after" },
|
|
64
|
+
]);
|
|
65
|
+
expect(getMessageText(msg)).toBe("before\nafter");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns empty string for message with no text parts", () => {
|
|
69
|
+
const msg = makeMessage([]);
|
|
70
|
+
expect(getMessageText(msg)).toBe("");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("getMessagePreview", () => {
|
|
75
|
+
it("returns full text when under maxLength", () => {
|
|
76
|
+
const msg = makeMessage([{ type: "text", text: "Short" }]);
|
|
77
|
+
expect(getMessagePreview(msg)).toBe("Short");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("truncates and adds ellipsis when over maxLength", () => {
|
|
81
|
+
const longText = "A".repeat(100);
|
|
82
|
+
const msg = makeMessage([{ type: "text", text: longText }]);
|
|
83
|
+
const preview = getMessagePreview(msg, 50);
|
|
84
|
+
expect(preview.length).toBeLessThanOrEqual(53); // 50 + "..."
|
|
85
|
+
expect(preview.endsWith("...")).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("getToolInvocations", () => {
|
|
90
|
+
it("returns empty array when no tool invocations", () => {
|
|
91
|
+
const msg = makeMessage([{ type: "text", text: "hello" }]);
|
|
92
|
+
expect(getToolInvocations(msg)).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("hasToolInvocations", () => {
|
|
97
|
+
it("returns false when message has no tool invocations", () => {
|
|
98
|
+
const msg = makeMessage([{ type: "text", text: "hello" }]);
|
|
99
|
+
expect(hasToolInvocations(msg)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { ThreadManager } from "../runtime/thread-manager";
|
|
3
|
+
|
|
4
|
+
describe("ThreadManager", () => {
|
|
5
|
+
const mockFetch = vi.fn();
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
9
|
+
mockFetch.mockReset();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const manager = new ThreadManager({
|
|
13
|
+
baseUrl: "http://localhost:3001/api/agents/my-agent/threads",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("list() fetches threads", async () => {
|
|
17
|
+
mockFetch.mockResolvedValueOnce({
|
|
18
|
+
ok: true,
|
|
19
|
+
json: async () => ({ threads: [{ sessionId: "s1", title: "Hello" }] }),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const threads = await manager.list();
|
|
23
|
+
expect(threads).toHaveLength(1);
|
|
24
|
+
expect(threads[0].sessionId).toBe("s1");
|
|
25
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
26
|
+
"http://localhost:3001/api/agents/my-agent/threads",
|
|
27
|
+
expect.objectContaining({ headers: expect.any(Object) }),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("get() fetches a single thread", async () => {
|
|
32
|
+
mockFetch.mockResolvedValueOnce({
|
|
33
|
+
ok: true,
|
|
34
|
+
json: async () => ({
|
|
35
|
+
session: { id: "1", sessionId: "s1", messageCount: 5 },
|
|
36
|
+
events: [],
|
|
37
|
+
traces: [],
|
|
38
|
+
runs: [],
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const detail = await manager.get("s1");
|
|
43
|
+
expect(detail.session.sessionId).toBe("s1");
|
|
44
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
45
|
+
"http://localhost:3001/api/agents/my-agent/threads/s1",
|
|
46
|
+
expect.objectContaining({ headers: expect.any(Object) }),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("create() POSTs to create a thread", async () => {
|
|
51
|
+
mockFetch.mockResolvedValueOnce({
|
|
52
|
+
ok: true,
|
|
53
|
+
json: async () => ({ thread: { sessionId: "new-s", title: "" } }),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const thread = await manager.create();
|
|
57
|
+
expect(thread.sessionId).toBe("new-s");
|
|
58
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
59
|
+
"http://localhost:3001/api/agents/my-agent/threads",
|
|
60
|
+
expect.objectContaining({ method: "POST" }),
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("fork() POSTs to fork a thread", async () => {
|
|
65
|
+
mockFetch.mockResolvedValueOnce({
|
|
66
|
+
ok: true,
|
|
67
|
+
json: async () => ({ thread: { sessionId: "forked-s", title: "" } }),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const thread = await manager.fork("s1");
|
|
71
|
+
expect(thread.sessionId).toBe("forked-s");
|
|
72
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
73
|
+
"http://localhost:3001/api/agents/my-agent/threads/s1/fork",
|
|
74
|
+
expect.objectContaining({ method: "POST" }),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("interrupt() POSTs to interrupt a thread", async () => {
|
|
79
|
+
mockFetch.mockResolvedValueOnce({ ok: true, json: async () => ({}) });
|
|
80
|
+
|
|
81
|
+
await manager.interrupt("s1");
|
|
82
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
83
|
+
"http://localhost:3001/api/agents/my-agent/threads/s1/interrupt",
|
|
84
|
+
expect.objectContaining({ method: "POST" }),
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("throws on HTTP error", async () => {
|
|
89
|
+
mockFetch.mockResolvedValueOnce({
|
|
90
|
+
ok: false,
|
|
91
|
+
json: async () => ({ error: "Not found" }),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await expect(manager.get("bad-id")).rejects.toThrow("Not found");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("includes custom headers", async () => {
|
|
98
|
+
const authManager = new ThreadManager({
|
|
99
|
+
baseUrl: "http://localhost:3001/api/threads",
|
|
100
|
+
headers: { Authorization: "Bearer secret" },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
mockFetch.mockResolvedValueOnce({
|
|
104
|
+
ok: true,
|
|
105
|
+
json: async () => ({ threads: [] }),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await authManager.list();
|
|
109
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
110
|
+
expect.any(String),
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
headers: expect.objectContaining({
|
|
113
|
+
Authorization: "Bearer secret",
|
|
114
|
+
}),
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|