@kognitivedev/adapter-chat-kognitive 0.2.29

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.
@@ -0,0 +1,2 @@
1
+
2
+ $ tsc
@@ -0,0 +1,11 @@
1
+ $ vitest run
2
+
3
+ RUN v3.2.4 /Users/vserifsaglam/work/memory-experiment/packages/adapter-chat-kognitive
4
+
5
+ ✓ src/__tests__/kognitive-backend.test.ts (12 tests) 16ms
6
+
7
+ Test Files 1 passed (1)
8
+ Tests 12 passed (12)
9
+ Start at 17:30:04
10
+ Duration 605ms (transform 79ms, setup 0ms, collect 142ms, tests 16ms, environment 0ms, prepare 160ms)
11
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # @kognitivedev/adapter-chat-kognitive
2
+
3
+ ## 0.2.29
4
+
5
+ ### Patch Changes
6
+
7
+ - release
8
+
9
+ - Updated dependencies []:
10
+ - @kognitivedev/shared@0.2.29
11
+ - @kognitivedev/ui@0.2.29
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # @kognitivedev/adapter-chat-kognitive
2
+
3
+ Backend adapter for `@kognitivedev/ui` that targets Kognitive-compatible chat routes.
4
+
5
+ Default routes:
6
+
7
+ - `/api/kognitive/agents/:agentName/stream`
8
+ - `/api/kognitive/threads/agents/:agentName`
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const vitest_1 = require("vitest");
9
+ const shared_1 = require("@kognitivedev/shared");
10
+ const index_1 = require("../index");
11
+ (0, vitest_1.describe)("createKognitiveChatBackend", () => {
12
+ const originalFetch = globalThis.fetch;
13
+ const fetchMock = vitest_1.vi.fn();
14
+ const userMessage = {
15
+ id: "u1",
16
+ role: "user",
17
+ parts: [{ type: "text", text: "Hello" }],
18
+ };
19
+ function loadFixture(name) {
20
+ const fixturePath = node_path_1.default.resolve(__dirname, "../../../shared/src/__tests__/fixtures/runtime", `${name}.json`);
21
+ return JSON.parse((0, node_fs_1.readFileSync)(fixturePath, "utf8"));
22
+ }
23
+ (0, vitest_1.beforeEach)(() => {
24
+ fetchMock.mockReset();
25
+ globalThis.fetch = fetchMock;
26
+ });
27
+ (0, vitest_1.afterEach)(() => {
28
+ globalThis.fetch = originalFetch;
29
+ });
30
+ (0, vitest_1.it)("uses product-local kognitive routes by default", async () => {
31
+ fetchMock.mockResolvedValueOnce(new Response("", {
32
+ status: 200,
33
+ headers: { "Content-Type": "text/event-stream" },
34
+ }));
35
+ const backend = (0, index_1.createKognitiveChatBackend)();
36
+ const client = backend.createExecutionClient({ agentName: "assistant" });
37
+ await client.stream({
38
+ messages: [userMessage],
39
+ onEvent: async () => { },
40
+ });
41
+ (0, vitest_1.expect)(fetchMock).toHaveBeenCalledWith("/api/kognitive/agents/assistant/stream", vitest_1.expect.objectContaining({ method: "POST" }));
42
+ });
43
+ (0, vitest_1.it)("preserves the canonical request body shape", async () => {
44
+ fetchMock.mockResolvedValueOnce(new Response("", {
45
+ status: 200,
46
+ headers: { "Content-Type": "text/event-stream" },
47
+ }));
48
+ const backend = (0, index_1.createKognitiveChatBackend)();
49
+ const client = backend.createExecutionClient({
50
+ agentName: "assistant",
51
+ runScope: "session",
52
+ resourceId: { userId: "user-1" },
53
+ });
54
+ await client.stream({
55
+ messages: [userMessage],
56
+ sessionId: "session-1",
57
+ body: { customField: true },
58
+ onEvent: async () => { },
59
+ });
60
+ (0, vitest_1.expect)(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
61
+ customField: true,
62
+ sessionId: "session-1",
63
+ runScope: "session",
64
+ resourceId: { userId: "user-1" },
65
+ messages: [{ role: "user", content: "Hello" }],
66
+ });
67
+ });
68
+ vitest_1.it.each([
69
+ "assistant-tool-stream",
70
+ "assistant-reasoning-stream",
71
+ "assistant-error-stream",
72
+ "assistant-cancelled-stream",
73
+ ])("matches the shared canonical request fixture for %s", async (fixtureName) => {
74
+ fetchMock.mockResolvedValueOnce(new Response("", {
75
+ status: 200,
76
+ headers: { "Content-Type": "text/event-stream" },
77
+ }));
78
+ const fixture = loadFixture(fixtureName);
79
+ const backend = (0, index_1.createKognitiveChatBackend)({
80
+ serverUrl: "http://localhost:3001",
81
+ streamPath: "/api/runtime/agents/:agentName/stream",
82
+ });
83
+ await backend.createExecutionClient({
84
+ agentName: "assistant",
85
+ }).stream({
86
+ messages: fixture.request.messages,
87
+ sessionId: fixture.request.sessionId,
88
+ onEvent: async () => { },
89
+ });
90
+ (0, vitest_1.expect)(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual(fixture.request.expectedRequestBody);
91
+ });
92
+ (0, vitest_1.it)("supports custom stream and thread routes", async () => {
93
+ fetchMock
94
+ .mockResolvedValueOnce(new Response("", {
95
+ status: 200,
96
+ headers: { "Content-Type": "text/event-stream" },
97
+ }))
98
+ .mockResolvedValueOnce(new Response(JSON.stringify({
99
+ threads: [],
100
+ total: 0,
101
+ }), {
102
+ status: 200,
103
+ headers: { "Content-Type": "application/json" },
104
+ }));
105
+ const backend = (0, index_1.createKognitiveChatBackend)({
106
+ serverUrl: "https://example.com",
107
+ streamPath: "/api/runtime/agents/:agentName/stream",
108
+ threadBasePath: "/api/cognitive/threads/agents/:agentName",
109
+ });
110
+ const executionClient = backend.createExecutionClient({ agentName: "assistant" });
111
+ const threadClient = backend.createThreadClient({ agentName: "assistant" });
112
+ await executionClient.stream({
113
+ messages: [userMessage],
114
+ onEvent: async () => { },
115
+ });
116
+ await threadClient.list({ limit: 10, offset: 5 });
117
+ (0, vitest_1.expect)(fetchMock.mock.calls[0]?.[0]).toBe("https://example.com/api/runtime/agents/assistant/stream");
118
+ (0, vitest_1.expect)(fetchMock.mock.calls[1]?.[0]).toBe("https://example.com/api/cognitive/threads/agents/assistant?limit=10&offset=5");
119
+ });
120
+ (0, vitest_1.it)("preserves canonical tool-result and file history when sending the next turn", async () => {
121
+ fetchMock.mockResolvedValueOnce(new Response("", {
122
+ status: 200,
123
+ headers: { "Content-Type": "text/event-stream" },
124
+ }));
125
+ const backend = (0, index_1.createKognitiveChatBackend)();
126
+ const client = backend.createExecutionClient({ agentName: "assistant" });
127
+ await client.stream({
128
+ messages: [
129
+ {
130
+ id: "assistant-1",
131
+ role: "assistant",
132
+ parts: [
133
+ { type: "tool-call", toolCallId: "table-1", toolName: "table", input: { topic: "revenue" } },
134
+ { type: "tool-result", toolCallId: "table-1", toolName: "table", result: { rows: [{ region: "EMEA" }] } },
135
+ { type: "text", text: "Here is the breakdown." },
136
+ ],
137
+ },
138
+ {
139
+ id: "user-2",
140
+ role: "user",
141
+ parts: [
142
+ {
143
+ type: "file",
144
+ data: "JVBERi0xLjc=",
145
+ filename: "q3.pdf",
146
+ mediaType: "application/pdf",
147
+ },
148
+ ],
149
+ },
150
+ ],
151
+ onEvent: async () => { },
152
+ });
153
+ (0, vitest_1.expect)(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
154
+ messages: [
155
+ {
156
+ role: "assistant",
157
+ content: [
158
+ { type: "tool-call", toolCallId: "table-1", toolName: "table", input: { topic: "revenue" } },
159
+ { type: "tool-result", toolCallId: "table-1", toolName: "table", result: { rows: [{ region: "EMEA" }] } },
160
+ { type: "text", text: "Here is the breakdown." },
161
+ ],
162
+ },
163
+ {
164
+ role: "user",
165
+ content: [
166
+ {
167
+ type: "file",
168
+ data: "JVBERi0xLjc=",
169
+ filename: "q3.pdf",
170
+ mediaType: "application/pdf",
171
+ },
172
+ ],
173
+ },
174
+ ],
175
+ });
176
+ });
177
+ (0, vitest_1.it)("maps stream events into the normalized event sink", async () => {
178
+ fetchMock.mockResolvedValueOnce(new Response([
179
+ "event: custom",
180
+ 'data: {"role":"assistant","content":"Hi"}',
181
+ "",
182
+ "",
183
+ ].join("\n"), {
184
+ status: 200,
185
+ headers: { "Content-Type": "text/event-stream" },
186
+ }));
187
+ const onEvent = vitest_1.vi.fn(async () => { });
188
+ const backend = (0, index_1.createKognitiveChatBackend)();
189
+ const client = backend.createExecutionClient({ agentName: "assistant" });
190
+ await client.stream({
191
+ messages: [userMessage],
192
+ onEvent,
193
+ });
194
+ (0, vitest_1.expect)(onEvent).toHaveBeenCalledWith("custom", {
195
+ role: "assistant",
196
+ content: "Hi",
197
+ });
198
+ });
199
+ (0, vitest_1.it)("throws typed moderation errors", async () => {
200
+ fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({
201
+ error: "USER_RESTRICTED",
202
+ code: "user_banned",
203
+ kind: "ban",
204
+ surface: "runtime_ui",
205
+ reasonCode: "abuse",
206
+ message: "This user is banned.",
207
+ retryable: false,
208
+ expiresAt: null,
209
+ }), {
210
+ status: 403,
211
+ headers: { "Content-Type": "application/json" },
212
+ }));
213
+ const backend = (0, index_1.createKognitiveChatBackend)();
214
+ const client = backend.createExecutionClient({ agentName: "assistant" });
215
+ await (0, vitest_1.expect)(client.stream({
216
+ messages: [userMessage],
217
+ onEvent: async () => { },
218
+ })).rejects.toBeInstanceOf(shared_1.ModerationError);
219
+ });
220
+ (0, vitest_1.it)("supports thread create and update routes", async () => {
221
+ fetchMock
222
+ .mockResolvedValueOnce(new Response(JSON.stringify({
223
+ thread: { sessionId: "s1", title: "Prototype", metadata: { pinned: true } },
224
+ }), {
225
+ status: 200,
226
+ headers: { "Content-Type": "application/json" },
227
+ }))
228
+ .mockResolvedValueOnce(new Response(JSON.stringify({
229
+ thread: { sessionId: "s1", title: "Renamed" },
230
+ }), {
231
+ status: 200,
232
+ headers: { "Content-Type": "application/json" },
233
+ }))
234
+ .mockResolvedValueOnce(new Response(JSON.stringify({
235
+ thread: {
236
+ sessionId: "s1",
237
+ title: "Renamed",
238
+ metadata: { platforms: { selectedConnectionIds: ["conn-1"] } },
239
+ },
240
+ }), {
241
+ status: 200,
242
+ headers: { "Content-Type": "application/json" },
243
+ }));
244
+ const backend = (0, index_1.createKognitiveChatBackend)();
245
+ const threadClient = backend.createThreadClient({ agentName: "assistant" });
246
+ await threadClient.create({ title: "Prototype", metadata: { pinned: true } });
247
+ await threadClient.rename?.("s1", "Renamed");
248
+ const updated = await threadClient.updateMetadata?.("s1", {
249
+ platforms: { selectedConnectionIds: ["conn-1"] },
250
+ });
251
+ (0, vitest_1.expect)(fetchMock.mock.calls[0]?.[0]).toBe("/api/kognitive/threads/agents/assistant");
252
+ (0, vitest_1.expect)(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
253
+ title: "Prototype",
254
+ metadata: { pinned: true },
255
+ });
256
+ (0, vitest_1.expect)(fetchMock.mock.calls[1]?.[0]).toBe("/api/kognitive/threads/agents/assistant/s1");
257
+ (0, vitest_1.expect)(JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body))).toEqual({
258
+ title: "Renamed",
259
+ });
260
+ (0, vitest_1.expect)(fetchMock.mock.calls[2]?.[0]).toBe("/api/kognitive/threads/agents/assistant/s1");
261
+ (0, vitest_1.expect)(JSON.parse(String(fetchMock.mock.calls[2]?.[1]?.body))).toEqual({
262
+ metadataPatch: { platforms: { selectedConnectionIds: ["conn-1"] } },
263
+ });
264
+ (0, vitest_1.expect)(updated?.metadata).toEqual({ platforms: { selectedConnectionIds: ["conn-1"] } });
265
+ });
266
+ (0, vitest_1.it)("surfaces thread metadata update errors", async () => {
267
+ fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({
268
+ error: "metadata failed",
269
+ }), {
270
+ status: 500,
271
+ headers: { "Content-Type": "application/json" },
272
+ }));
273
+ const backend = (0, index_1.createKognitiveChatBackend)();
274
+ const threadClient = backend.createThreadClient({
275
+ agentName: "assistant",
276
+ resourceId: { userId: "user-1" },
277
+ });
278
+ await (0, vitest_1.expect)(threadClient.updateMetadata?.("s1", {
279
+ platforms: { selectedConnectionIds: [] },
280
+ })).rejects.toThrow("metadata failed");
281
+ (0, vitest_1.expect)(fetchMock.mock.calls[0]?.[0]).toBe("/api/kognitive/threads/agents/assistant/s1?userId=user-1");
282
+ });
283
+ });
@@ -0,0 +1,11 @@
1
+ import type { ChatBackendAdapter, ChatBackendContext } from "@kognitivedev/ui";
2
+ export interface KognitiveChatBackendOptions {
3
+ serverUrl?: string;
4
+ streamPath?: string | ((context: ChatBackendContext) => string);
5
+ threadBasePath?: string | ((context: ChatBackendContext) => string);
6
+ headers?: Record<string, string> | ((context: ChatBackendContext) => Record<string, string> | undefined);
7
+ fetch?: typeof fetch;
8
+ }
9
+ export declare function createKognitiveChatBackend(options?: KognitiveChatBackendOptions): ChatBackendAdapter;
10
+ export declare const createKognitiveUIChatBackend: typeof createKognitiveChatBackend;
11
+ export type KognitiveUIChatBackendOptions = KognitiveChatBackendOptions;
package/dist/index.js ADDED
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createKognitiveUIChatBackend = void 0;
4
+ exports.createKognitiveChatBackend = createKognitiveChatBackend;
5
+ const shared_1 = require("@kognitivedev/shared");
6
+ const ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
7
+ function isAbsoluteUrl(value) {
8
+ return ABSOLUTE_URL_RE.test(value);
9
+ }
10
+ function resolveServerUrl(serverUrl) {
11
+ const resolved = serverUrl ?? "";
12
+ if (resolved === "/")
13
+ return "";
14
+ return resolved.replace(/\/$/, "");
15
+ }
16
+ function joinServerPath(serverUrl, path) {
17
+ const normalizedServerUrl = resolveServerUrl(serverUrl);
18
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
19
+ return `${normalizedServerUrl}${normalizedPath}`;
20
+ }
21
+ function appendQueryParams(urlString, params) {
22
+ if (isAbsoluteUrl(urlString)) {
23
+ const url = new URL(urlString);
24
+ for (const [key, value] of Object.entries(params)) {
25
+ if (value !== undefined) {
26
+ url.searchParams.set(key, value);
27
+ }
28
+ }
29
+ return url.toString();
30
+ }
31
+ const [pathAndQuery, hash = ""] = urlString.split("#", 2);
32
+ const [pathname, query = ""] = pathAndQuery.split("?", 2);
33
+ const searchParams = new URLSearchParams(query);
34
+ for (const [key, value] of Object.entries(params)) {
35
+ if (value !== undefined) {
36
+ searchParams.set(key, value);
37
+ }
38
+ }
39
+ const nextQuery = searchParams.toString();
40
+ return `${pathname}${nextQuery ? `?${nextQuery}` : ""}${hash ? `#${hash}` : ""}`;
41
+ }
42
+ function resolveTemplate(template, context, fallback) {
43
+ const resolved = typeof template === "function" ? template(context) : template ?? fallback;
44
+ return resolved.replace(/:agentName/g, encodeURIComponent(context.agentName));
45
+ }
46
+ function resolveEndpoint(serverUrl, path) {
47
+ if (isAbsoluteUrl(path)) {
48
+ return path.replace(/\/$/, "");
49
+ }
50
+ return joinServerPath(serverUrl, path);
51
+ }
52
+ function resolveAdapterHeaders(context, source) {
53
+ return {
54
+ ...(typeof source === "function" ? source(context) ?? {} : source ?? {}),
55
+ ...(context.headers ?? {}),
56
+ };
57
+ }
58
+ function asRecord(value) {
59
+ return value && typeof value === "object" && !Array.isArray(value)
60
+ ? value
61
+ : undefined;
62
+ }
63
+ function extractInlineFileData(url) {
64
+ if (!url.startsWith("data:")) {
65
+ return undefined;
66
+ }
67
+ const commaIndex = url.indexOf(",");
68
+ if (commaIndex === -1) {
69
+ return undefined;
70
+ }
71
+ const metadata = url.slice(0, commaIndex);
72
+ const payload = url.slice(commaIndex + 1);
73
+ if (metadata.includes(";base64")) {
74
+ return payload;
75
+ }
76
+ try {
77
+ return decodeURIComponent(payload);
78
+ }
79
+ catch {
80
+ return payload;
81
+ }
82
+ }
83
+ function serializeRuntimePart(part) {
84
+ if (part.type !== "file") {
85
+ return part;
86
+ }
87
+ const filePart = part;
88
+ if (filePart.data !== undefined) {
89
+ return filePart;
90
+ }
91
+ const inlineData = typeof filePart.url === "string" ? extractInlineFileData(filePart.url) : undefined;
92
+ if (inlineData === undefined) {
93
+ return filePart;
94
+ }
95
+ return {
96
+ type: "file",
97
+ data: inlineData,
98
+ filename: filePart.filename ?? filePart.name,
99
+ mediaType: filePart.mediaType ?? filePart.mimeType ?? filePart.mime_type,
100
+ };
101
+ }
102
+ function serializeRuntimeMetadata(metadata) {
103
+ if (!metadata || typeof metadata !== "object") {
104
+ return metadata;
105
+ }
106
+ const { feedback: _ignoredFeedback, ...rest } = metadata;
107
+ return Object.keys(rest).length > 0 ? rest : undefined;
108
+ }
109
+ function uiMessageToRuntimeMessage(message) {
110
+ return {
111
+ role: message.role,
112
+ content: message.parts.map(serializeRuntimePart),
113
+ metadata: serializeRuntimeMetadata(message.metadata),
114
+ };
115
+ }
116
+ async function buildKognitiveTransportError(response) {
117
+ const responseText = await response.clone().text();
118
+ if (responseText !== "") {
119
+ try {
120
+ const parsed = JSON.parse(responseText);
121
+ if ((0, shared_1.isModerationErrorPayload)(parsed)) {
122
+ return new shared_1.ModerationError(parsed, response.status);
123
+ }
124
+ if (parsed && typeof parsed === "object" && typeof parsed.error === "string") {
125
+ return new Error(parsed.error);
126
+ }
127
+ }
128
+ catch {
129
+ // fall back to raw text
130
+ }
131
+ }
132
+ return new Error(`Kognitive transport failed: HTTP ${response.status}`);
133
+ }
134
+ function createKognitiveChatBackend(options = {}) {
135
+ const serverUrl = resolveServerUrl(options.serverUrl);
136
+ const fetchImpl = options.fetch ?? fetch;
137
+ return {
138
+ capabilities: {
139
+ threads: true,
140
+ threadFork: true,
141
+ threadInterrupt: true,
142
+ threadDelete: true,
143
+ threadRename: true,
144
+ threadArchive: true,
145
+ messageFeedback: true,
146
+ },
147
+ createExecutionClient(context) {
148
+ const api = resolveEndpoint(serverUrl, resolveTemplate(options.streamPath, context, "/api/kognitive/agents/:agentName/stream"));
149
+ const baseHeaders = {
150
+ "Content-Type": "application/json",
151
+ ...resolveAdapterHeaders(context, options.headers),
152
+ };
153
+ return {
154
+ async stream(request) {
155
+ const contextBody = asRecord(context.body);
156
+ const requestBody = asRecord(request.body);
157
+ const mergedMetadata = {
158
+ ...(asRecord(contextBody?.metadata) ?? {}),
159
+ ...(asRecord(requestBody?.metadata) ?? {}),
160
+ };
161
+ const response = await fetchImpl(api, {
162
+ method: "POST",
163
+ headers: baseHeaders,
164
+ body: JSON.stringify({
165
+ ...(contextBody ?? {}),
166
+ ...(requestBody ?? {}),
167
+ ...(Object.keys(mergedMetadata).length > 0 ? { metadata: mergedMetadata } : {}),
168
+ ...(request.sessionId ? { sessionId: request.sessionId } : {}),
169
+ ...(context.runScope ? { runScope: context.runScope } : {}),
170
+ ...(context.resourceId ? { resourceId: context.resourceId } : {}),
171
+ messages: (0, shared_1.buildCanonicalConversationMessages)(request.messages.map(uiMessageToRuntimeMessage)),
172
+ }),
173
+ signal: request.signal,
174
+ });
175
+ if (!response.ok) {
176
+ throw await buildKognitiveTransportError(response);
177
+ }
178
+ await (0, shared_1.readKognitiveEventStream)(response, async (eventName, data) => {
179
+ if (eventName === "messages" || eventName === "custom" || eventName === "debug") {
180
+ await request.onEvent(eventName, data);
181
+ }
182
+ });
183
+ },
184
+ };
185
+ },
186
+ createThreadClient(context) {
187
+ const apiBase = resolveEndpoint(serverUrl, resolveTemplate(options.threadBasePath, context, "/api/kognitive/threads/agents/:agentName")).replace(/\/$/, "");
188
+ const headers = {
189
+ "Content-Type": "application/json",
190
+ ...resolveAdapterHeaders(context, options.headers),
191
+ };
192
+ const userId = context.resourceId?.userId ? String(context.resourceId.userId) : undefined;
193
+ const buildUrl = (path = "") => appendQueryParams(`${apiBase}${path}`, { userId });
194
+ return {
195
+ async list(query) {
196
+ const url = appendQueryParams(buildUrl(), {
197
+ userId,
198
+ ...(query?.limit !== undefined ? { limit: String(query.limit) } : {}),
199
+ ...(query?.offset !== undefined ? { offset: String(query.offset) } : {}),
200
+ });
201
+ const res = await fetchImpl(url, { headers });
202
+ if (!res.ok) {
203
+ throw new Error((await res.json().catch(() => ({ error: "Failed to list threads" }))).error);
204
+ }
205
+ const data = await res.json();
206
+ return {
207
+ threads: (data.threads ?? []),
208
+ total: typeof data.total === "number" ? data.total : (data.threads ?? []).length,
209
+ };
210
+ },
211
+ async get(sessionId) {
212
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}`), { headers });
213
+ if (!res.ok) {
214
+ throw new Error((await res.json().catch(() => ({ error: "Failed to get thread" }))).error);
215
+ }
216
+ return (await res.json());
217
+ },
218
+ async create(input) {
219
+ const res = await fetchImpl(buildUrl(), {
220
+ method: "POST",
221
+ headers,
222
+ body: JSON.stringify({
223
+ ...(userId ? { userId } : {}),
224
+ ...(input ?? {}),
225
+ }),
226
+ });
227
+ if (!res.ok) {
228
+ throw new Error((await res.json().catch(() => ({ error: "Failed to create thread" }))).error);
229
+ }
230
+ const data = await res.json();
231
+ return data.thread;
232
+ },
233
+ async fork(sessionId) {
234
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}/fork`), {
235
+ method: "POST",
236
+ headers,
237
+ body: JSON.stringify(userId ? { userId } : {}),
238
+ });
239
+ if (!res.ok) {
240
+ throw new Error((await res.json().catch(() => ({ error: "Failed to fork thread" }))).error);
241
+ }
242
+ const data = await res.json();
243
+ return data.thread;
244
+ },
245
+ async interrupt(sessionId) {
246
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}/interrupt`), {
247
+ method: "POST",
248
+ headers,
249
+ });
250
+ if (!res.ok) {
251
+ throw new Error((await res.json().catch(() => ({ error: "Failed to interrupt thread" }))).error);
252
+ }
253
+ },
254
+ async delete(sessionId) {
255
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}`), {
256
+ method: "DELETE",
257
+ headers,
258
+ });
259
+ if (!res.ok && res.status !== 204) {
260
+ throw new Error((await res.json().catch(() => ({ error: "Failed to delete thread" }))).error);
261
+ }
262
+ },
263
+ async rename(sessionId, title) {
264
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}`), {
265
+ method: "PATCH",
266
+ headers,
267
+ body: JSON.stringify({ title }),
268
+ });
269
+ if (!res.ok) {
270
+ throw new Error((await res.json().catch(() => ({ error: "Failed to rename thread" }))).error);
271
+ }
272
+ const data = await res.json();
273
+ return data.thread;
274
+ },
275
+ async updateMetadata(sessionId, metadataPatch) {
276
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}`), {
277
+ method: "PATCH",
278
+ headers,
279
+ body: JSON.stringify({ metadataPatch }),
280
+ });
281
+ if (!res.ok) {
282
+ throw new Error((await res.json().catch(() => ({ error: "Failed to update thread metadata" }))).error);
283
+ }
284
+ const data = await res.json();
285
+ return data.thread;
286
+ },
287
+ async archive(sessionId) {
288
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}`), {
289
+ method: "PATCH",
290
+ headers,
291
+ body: JSON.stringify({ archived: true }),
292
+ });
293
+ if (!res.ok) {
294
+ throw new Error((await res.json().catch(() => ({ error: "Failed to archive thread" }))).error);
295
+ }
296
+ const data = await res.json();
297
+ return data.thread;
298
+ },
299
+ async unarchive(sessionId) {
300
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}`), {
301
+ method: "PATCH",
302
+ headers,
303
+ body: JSON.stringify({ archived: false }),
304
+ });
305
+ if (!res.ok) {
306
+ throw new Error((await res.json().catch(() => ({ error: "Failed to unarchive thread" }))).error);
307
+ }
308
+ const data = await res.json();
309
+ return data.thread;
310
+ },
311
+ async setMessageFeedback(sessionId, messageId, value) {
312
+ const res = await fetchImpl(buildUrl(`/${encodeURIComponent(sessionId)}/messages/${encodeURIComponent(messageId)}/feedback`), {
313
+ method: "PUT",
314
+ headers,
315
+ body: JSON.stringify({ value }),
316
+ });
317
+ if (!res.ok) {
318
+ throw new Error((await res.json().catch(() => ({ error: "Failed to set message feedback" }))).error);
319
+ }
320
+ },
321
+ };
322
+ },
323
+ };
324
+ }
325
+ exports.createKognitiveUIChatBackend = createKognitiveChatBackend;