@kognitivedev/vercel-ai-provider 0.2.22 → 0.2.26
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/CHANGELOG.md +48 -0
- package/README.md +29 -4
- package/dist/__tests__/cognitive-layer-extra.test.js +151 -0
- package/dist/__tests__/wrap-stream-logging.test.js +131 -9
- package/dist/index.d.ts +55 -5
- package/dist/index.js +643 -137
- package/package.json +6 -4
- package/src/__tests__/cognitive-layer-extra.test.ts +158 -1
- package/src/__tests__/wrap-stream-logging.test.ts +152 -10
- package/src/index.ts +746 -145
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @kognitivedev/vercel-ai-provider
|
|
2
2
|
|
|
3
|
+
## 0.2.26
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- release
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @kognitivedev/memory@0.2.26
|
|
11
|
+
- @kognitivedev/prompthub@0.2.26
|
|
12
|
+
- @kognitivedev/shared@0.2.26
|
|
13
|
+
- @kognitivedev/tools@0.2.26
|
|
14
|
+
|
|
15
|
+
## 0.2.25
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- release
|
|
20
|
+
|
|
21
|
+
- Updated dependencies []:
|
|
22
|
+
- @kognitivedev/memory@0.2.25
|
|
23
|
+
- @kognitivedev/prompthub@0.2.25
|
|
24
|
+
- @kognitivedev/shared@0.2.25
|
|
25
|
+
- @kognitivedev/tools@0.2.25
|
|
26
|
+
|
|
27
|
+
## 0.2.24
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- release
|
|
32
|
+
|
|
33
|
+
- Updated dependencies []:
|
|
34
|
+
- @kognitivedev/memory@0.2.24
|
|
35
|
+
- @kognitivedev/prompthub@0.2.24
|
|
36
|
+
- @kognitivedev/shared@0.2.24
|
|
37
|
+
- @kognitivedev/tools@0.2.24
|
|
38
|
+
|
|
39
|
+
## 0.2.23
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- release
|
|
44
|
+
|
|
45
|
+
- Updated dependencies []:
|
|
46
|
+
- @kognitivedev/memory@0.2.23
|
|
47
|
+
- @kognitivedev/prompthub@0.2.23
|
|
48
|
+
- @kognitivedev/shared@0.2.23
|
|
49
|
+
- @kognitivedev/tools@0.2.23
|
|
50
|
+
|
|
3
51
|
## 0.2.22
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @kognitivedev/vercel-ai-provider
|
|
2
2
|
|
|
3
|
-
Vercel AI SDK provider wrapper that integrates the Kognitive memory layer into your AI applications.
|
|
3
|
+
Vercel AI SDK provider wrapper that integrates the Kognitive memory layer into your AI applications. It injects memory context, emits live turn traces, and logs finalized conversation snapshots back to the backend.
|
|
4
|
+
|
|
5
|
+
For new runs, the provider can also emit incremental live trace milestones to the backend while a generation or stream is still in progress.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -28,8 +30,9 @@ const cl = createCognitiveLayer({
|
|
|
28
30
|
provider: openai,
|
|
29
31
|
clConfig: {
|
|
30
32
|
appId: "my-app",
|
|
31
|
-
|
|
32
|
-
baseUrl: "http://localhost:3001"
|
|
33
|
+
projectId: "project-123",
|
|
34
|
+
baseUrl: "http://localhost:3001",
|
|
35
|
+
apiKey: process.env.COGNITIVE_API_KEY!,
|
|
33
36
|
}
|
|
34
37
|
});
|
|
35
38
|
|
|
@@ -37,12 +40,18 @@ const cl = createCognitiveLayer({
|
|
|
37
40
|
const { text } = await generateText({
|
|
38
41
|
model: cl("gpt-4o", {
|
|
39
42
|
userId: "user-123",
|
|
40
|
-
sessionId: "session-abc"
|
|
43
|
+
sessionId: "session-abc",
|
|
41
44
|
}),
|
|
42
45
|
prompt: "What's my favorite color?"
|
|
43
46
|
});
|
|
44
47
|
```
|
|
45
48
|
|
|
49
|
+
For tracing, treat `projectId` and `sessionId` as required. Regular chat integrations now follow:
|
|
50
|
+
|
|
51
|
+
- `1 session -> 1 agent_run -> many turn traces`
|
|
52
|
+
- each completed turn writes its own `conversation_snapshot`
|
|
53
|
+
- each turn emits its own live `trace-events`
|
|
54
|
+
|
|
46
55
|
## Configuration
|
|
47
56
|
|
|
48
57
|
### `CognitiveLayerConfig`
|
|
@@ -156,6 +165,22 @@ interface PromptConfig {
|
|
|
156
165
|
|
|
157
166
|
## Usage Examples
|
|
158
167
|
|
|
168
|
+
## Live Trace Events
|
|
169
|
+
|
|
170
|
+
When `userId`, `projectId`, and `sessionId` are available, the provider now uses three complementary backend paths:
|
|
171
|
+
|
|
172
|
+
- `POST /api/cognitive/agent-run` to ensure or update the logical run
|
|
173
|
+
- `POST /api/cognitive/trace-events` for append-only live milestones such as trace start, assistant progress, and tool completion
|
|
174
|
+
- `POST /api/cognitive/log` for the finalized conversation snapshot after the turn completes
|
|
175
|
+
|
|
176
|
+
For chat apps, the provider reuses one session-scoped run for the lifetime of the session and emits one new trace per turn.
|
|
177
|
+
|
|
178
|
+
This is additive:
|
|
179
|
+
|
|
180
|
+
- existing memory logging behavior stays the same
|
|
181
|
+
- old traces without live events remain valid
|
|
182
|
+
- dashboards normalize both legacy traces and event-backed traces into one read model
|
|
183
|
+
|
|
159
184
|
### With OpenAI
|
|
160
185
|
|
|
161
186
|
```typescript
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
3
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
4
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
5
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
6
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
7
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
8
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
9
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
10
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
11
|
+
function fulfill(value) { resume("next", value); }
|
|
12
|
+
function reject(value) { resume("throw", value); }
|
|
13
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
14
|
+
};
|
|
2
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
16
|
const vitest_1 = require("vitest");
|
|
4
17
|
const test_1 = require("ai/test");
|
|
18
|
+
const shared_1 = require("@kognitivedev/shared");
|
|
5
19
|
const promptHubMocks = vitest_1.vi.hoisted(() => ({
|
|
6
20
|
resolvePrompt: vitest_1.vi.fn(),
|
|
7
21
|
}));
|
|
@@ -59,6 +73,12 @@ function setupSnapshotAndLogFetch() {
|
|
|
59
73
|
if (urlStr.includes("/api/cognitive/log")) {
|
|
60
74
|
return new Response(JSON.stringify({ ok: true }), { status: 201 });
|
|
61
75
|
}
|
|
76
|
+
if (urlStr.includes("/api/cognitive/agent-run")) {
|
|
77
|
+
return new Response(JSON.stringify({ ok: true, runDbId: "run-db-1" }), { status: 200 });
|
|
78
|
+
}
|
|
79
|
+
if (urlStr.includes("/api/cognitive/trace-events")) {
|
|
80
|
+
return new Response(JSON.stringify({ ok: true, traceDbId: "trace-db-1" }), { status: 200 });
|
|
81
|
+
}
|
|
62
82
|
if (urlStr.includes("/api/cognitive/process")) {
|
|
63
83
|
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
|
64
84
|
}
|
|
@@ -121,6 +141,51 @@ function makeLayer(overrides) {
|
|
|
121
141
|
cl.clearSessionCache("user-1:project-1:session-1");
|
|
122
142
|
cl.clearSessionCache();
|
|
123
143
|
});
|
|
144
|
+
(0, vitest_1.it)("supports direct system usage without a managed prompt", async () => {
|
|
145
|
+
setupSnapshotAndLogFetch();
|
|
146
|
+
const cl = makeLayer();
|
|
147
|
+
const model = cl("mock-model", {
|
|
148
|
+
userId: "user-1",
|
|
149
|
+
projectId: "project-1",
|
|
150
|
+
sessionId: "session-1",
|
|
151
|
+
});
|
|
152
|
+
await cl.streamText({
|
|
153
|
+
model,
|
|
154
|
+
system: "Direct system prompt",
|
|
155
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
156
|
+
});
|
|
157
|
+
(0, vitest_1.expect)(ai_1.streamText).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
158
|
+
system: "Direct system prompt",
|
|
159
|
+
}));
|
|
160
|
+
(0, vitest_1.expect)(promptHubMocks.resolvePrompt).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
(0, vitest_1.it)("optionally auto-injects the topic memory tool into cl.streamText", async () => {
|
|
163
|
+
setupSnapshotAndLogFetch();
|
|
164
|
+
const cl = makeLayer();
|
|
165
|
+
const model = cl("mock-model", {
|
|
166
|
+
userId: "user-1",
|
|
167
|
+
projectId: "project-1",
|
|
168
|
+
sessionId: "session-1",
|
|
169
|
+
});
|
|
170
|
+
await cl.streamText({
|
|
171
|
+
model,
|
|
172
|
+
system: "Direct system prompt",
|
|
173
|
+
messages: [{ role: "user", content: "What do you know about Evital?" }],
|
|
174
|
+
tools: {
|
|
175
|
+
weather: { description: "Weather tool" },
|
|
176
|
+
},
|
|
177
|
+
kognitive: { autoTopicMemoryTool: true },
|
|
178
|
+
});
|
|
179
|
+
(0, vitest_1.expect)(ai_1.streamText).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
180
|
+
tools: vitest_1.expect.objectContaining({
|
|
181
|
+
weather: vitest_1.expect.any(Object),
|
|
182
|
+
"get-topic-memory": vitest_1.expect.objectContaining({
|
|
183
|
+
description: vitest_1.expect.stringContaining("Retrieve topic-scoped historical memory"),
|
|
184
|
+
execute: vitest_1.expect.any(Function),
|
|
185
|
+
}),
|
|
186
|
+
}),
|
|
187
|
+
}));
|
|
188
|
+
});
|
|
124
189
|
(0, vitest_1.it)("uses a gateway model when providerFactory is available", async () => {
|
|
125
190
|
setupSnapshotAndLogFetch();
|
|
126
191
|
promptHubMocks.resolvePrompt.mockResolvedValue({
|
|
@@ -150,6 +215,68 @@ function makeLayer(overrides) {
|
|
|
150
215
|
system: "Prompt body",
|
|
151
216
|
}));
|
|
152
217
|
});
|
|
218
|
+
(0, vitest_1.it)("converts streamText fullStream into message events", async () => {
|
|
219
|
+
const stream = (0, index_1.toMessageEventStream)({
|
|
220
|
+
fullStream: (function () {
|
|
221
|
+
return __asyncGenerator(this, arguments, function* () {
|
|
222
|
+
yield yield __await({ type: "tool-call", toolCallId: "tool-1", toolName: "get-topic-memory", input: { entity: "Evital" } });
|
|
223
|
+
yield yield __await({ type: "tool-result", toolCallId: "tool-1", toolName: "get-topic-memory", result: { summary: "Known details" } });
|
|
224
|
+
yield yield __await({ type: "text-delta", delta: "Known details" });
|
|
225
|
+
});
|
|
226
|
+
})(),
|
|
227
|
+
});
|
|
228
|
+
const reader = stream.getReader();
|
|
229
|
+
const chunks = [];
|
|
230
|
+
while (true) {
|
|
231
|
+
const { done, value } = await reader.read();
|
|
232
|
+
if (done)
|
|
233
|
+
break;
|
|
234
|
+
chunks.push(value);
|
|
235
|
+
}
|
|
236
|
+
(0, vitest_1.expect)(chunks).toEqual([
|
|
237
|
+
{
|
|
238
|
+
event: "messages",
|
|
239
|
+
data: { type: "tool-call", id: "tool-1", name: "get-topic-memory", input: { entity: "Evital" } },
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
event: "messages",
|
|
243
|
+
data: { type: "tool-result", id: "tool-1", name: "get-topic-memory", result: { summary: "Known details" } },
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
event: "messages",
|
|
247
|
+
data: { token: "Known details" },
|
|
248
|
+
},
|
|
249
|
+
]);
|
|
250
|
+
});
|
|
251
|
+
(0, vitest_1.it)("converts generateText results into message events", async () => {
|
|
252
|
+
const stream = (0, index_1.toGeneratedMessageEventStream)({
|
|
253
|
+
text: "Known details",
|
|
254
|
+
toolCalls: [{ toolCallId: "tool-1", toolName: "get-topic-memory", input: { entity: "Evital" } }],
|
|
255
|
+
toolResults: [{ toolCallId: "tool-1", toolName: "get-topic-memory", result: { summary: "Known details" } }],
|
|
256
|
+
});
|
|
257
|
+
const reader = stream.getReader();
|
|
258
|
+
const chunks = [];
|
|
259
|
+
while (true) {
|
|
260
|
+
const { done, value } = await reader.read();
|
|
261
|
+
if (done)
|
|
262
|
+
break;
|
|
263
|
+
chunks.push(value);
|
|
264
|
+
}
|
|
265
|
+
(0, vitest_1.expect)(chunks).toEqual([
|
|
266
|
+
{
|
|
267
|
+
event: "messages",
|
|
268
|
+
data: { type: "tool-call", id: "tool-1", name: "get-topic-memory", input: { entity: "Evital" } },
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
event: "messages",
|
|
272
|
+
data: { type: "tool-result", id: "tool-1", name: "get-topic-memory", result: { summary: "Known details" } },
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
event: "messages",
|
|
276
|
+
data: { token: "Known details" },
|
|
277
|
+
},
|
|
278
|
+
]);
|
|
279
|
+
});
|
|
153
280
|
(0, vitest_1.it)("passes prompt tag and stores tag/ab metadata in logging payload", async () => {
|
|
154
281
|
var _a, _b, _c, _d;
|
|
155
282
|
const fetchSpy = setupSnapshotAndLogFetch();
|
|
@@ -256,4 +383,28 @@ function makeLayer(overrides) {
|
|
|
256
383
|
}));
|
|
257
384
|
(0, vitest_1.expect)(vitest_1.vi.mocked(ai_1.generateText).mock.calls[0][0]).not.toHaveProperty("system");
|
|
258
385
|
});
|
|
386
|
+
(0, vitest_1.it)("rethrows moderation errors from prompt resolution", async () => {
|
|
387
|
+
setupSnapshotAndLogFetch();
|
|
388
|
+
promptHubMocks.resolvePrompt.mockRejectedValue(new shared_1.ModerationError({
|
|
389
|
+
error: "USER_RESTRICTED",
|
|
390
|
+
code: "user_restricted",
|
|
391
|
+
kind: "restriction",
|
|
392
|
+
surface: "prompt_hub",
|
|
393
|
+
reasonCode: "policy_violation",
|
|
394
|
+
message: "Prompt blocked.",
|
|
395
|
+
retryable: false,
|
|
396
|
+
expiresAt: null,
|
|
397
|
+
}));
|
|
398
|
+
const cl = makeLayer();
|
|
399
|
+
const model = cl("mock-model", {
|
|
400
|
+
userId: "user-1",
|
|
401
|
+
projectId: "project-1",
|
|
402
|
+
sessionId: "session-1",
|
|
403
|
+
});
|
|
404
|
+
await (0, vitest_1.expect)(cl.generateText({
|
|
405
|
+
model,
|
|
406
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
407
|
+
prompt: { slug: "blocked" },
|
|
408
|
+
})).rejects.toBeInstanceOf(shared_1.ModerationError);
|
|
409
|
+
});
|
|
259
410
|
});
|
|
@@ -6,8 +6,12 @@ const ai_1 = require("ai");
|
|
|
6
6
|
const test_1 = require("ai/test");
|
|
7
7
|
(0, vitest_1.describe)("wrapStream logging", () => {
|
|
8
8
|
let fetchCalls;
|
|
9
|
+
let traceEventCalls;
|
|
10
|
+
let agentRunCalls;
|
|
9
11
|
(0, vitest_1.beforeEach)(() => {
|
|
10
12
|
fetchCalls = [];
|
|
13
|
+
traceEventCalls = [];
|
|
14
|
+
agentRunCalls = [];
|
|
11
15
|
vitest_1.vi.stubGlobal("fetch", vitest_1.vi.fn(async (url, init) => {
|
|
12
16
|
const urlStr = typeof url === "string" ? url : url.toString();
|
|
13
17
|
if (urlStr.includes("/api/cognitive/snapshot")) {
|
|
@@ -18,6 +22,16 @@ const test_1 = require("ai/test");
|
|
|
18
22
|
fetchCalls.push({ url: urlStr, body });
|
|
19
23
|
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
|
20
24
|
}
|
|
25
|
+
if (urlStr.includes("/api/cognitive/agent-run")) {
|
|
26
|
+
const body = JSON.parse(init === null || init === void 0 ? void 0 : init.body);
|
|
27
|
+
agentRunCalls.push({ url: urlStr, body });
|
|
28
|
+
return new Response(JSON.stringify({ ok: true, runDbId: "run-db-1" }), { status: 200 });
|
|
29
|
+
}
|
|
30
|
+
if (urlStr.includes("/api/cognitive/trace-events")) {
|
|
31
|
+
const body = JSON.parse(init === null || init === void 0 ? void 0 : init.body);
|
|
32
|
+
traceEventCalls.push({ url: urlStr, body });
|
|
33
|
+
return new Response(JSON.stringify({ ok: true, traceDbId: "trace-db-1" }), { status: 200 });
|
|
34
|
+
}
|
|
21
35
|
if (urlStr.includes("/api/cognitive/process")) {
|
|
22
36
|
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
|
23
37
|
}
|
|
@@ -85,7 +99,7 @@ const test_1 = require("ai/test");
|
|
|
85
99
|
const logCall = fetchCalls.find((c) => c.url.includes("/api/cognitive/log"));
|
|
86
100
|
(0, vitest_1.expect)(logCall).toBeDefined();
|
|
87
101
|
const messages = logCall.body.messages;
|
|
88
|
-
// Assistant message should contain
|
|
102
|
+
// Assistant message should contain the full replay-safe turn.
|
|
89
103
|
const assistantMsg = messages.find((m) => m.role === "assistant");
|
|
90
104
|
(0, vitest_1.expect)(assistantMsg).toBeDefined();
|
|
91
105
|
(0, vitest_1.expect)(assistantMsg.content).toEqual([
|
|
@@ -96,11 +110,6 @@ const test_1 = require("ai/test");
|
|
|
96
110
|
toolName: "get_weather",
|
|
97
111
|
input: '{"city":"London"}',
|
|
98
112
|
},
|
|
99
|
-
]);
|
|
100
|
-
// Tool results should be in a separate tool message
|
|
101
|
-
const toolMsg = messages.find((m) => m.role === "tool");
|
|
102
|
-
(0, vitest_1.expect)(toolMsg).toBeDefined();
|
|
103
|
-
(0, vitest_1.expect)(toolMsg.content).toEqual([
|
|
104
113
|
{
|
|
105
114
|
type: "tool-result",
|
|
106
115
|
toolCallId: "call-1",
|
|
@@ -108,6 +117,7 @@ const test_1 = require("ai/test");
|
|
|
108
117
|
result: { temperature: 15, unit: "celsius" },
|
|
109
118
|
},
|
|
110
119
|
]);
|
|
120
|
+
(0, vitest_1.expect)(messages.some((m) => m.role === "tool")).toBe(false);
|
|
111
121
|
// Spans should include the tool call with populated previews
|
|
112
122
|
const spans = logCall.body.spans;
|
|
113
123
|
const toolSpan = spans === null || spans === void 0 ? void 0 : spans.find((s) => s.spanType === "tool");
|
|
@@ -115,6 +125,15 @@ const test_1 = require("ai/test");
|
|
|
115
125
|
(0, vitest_1.expect)(toolSpan.toolName).toBe("get_weather");
|
|
116
126
|
(0, vitest_1.expect)(toolSpan.inputPreview).toContain("London");
|
|
117
127
|
(0, vitest_1.expect)(toolSpan.outputPreview).toContain("15");
|
|
128
|
+
(0, vitest_1.expect)(traceEventCalls.some((call) => call.body.start)).toBe(true);
|
|
129
|
+
(0, vitest_1.expect)(traceEventCalls.some((call) => Array.isArray(call.body.events) &&
|
|
130
|
+
call.body.events.some((event) => event.eventType === "tool.started"))).toBe(true);
|
|
131
|
+
(0, vitest_1.expect)(traceEventCalls.some((call) => Array.isArray(call.body.events) &&
|
|
132
|
+
call.body.events.some((event) => event.eventType === "tool.completed"))).toBe(true);
|
|
133
|
+
(0, vitest_1.expect)(agentRunCalls).toHaveLength(2);
|
|
134
|
+
(0, vitest_1.expect)(agentRunCalls[0].body.status).toBe("running");
|
|
135
|
+
(0, vitest_1.expect)(agentRunCalls[1].body.status).toBe("running");
|
|
136
|
+
(0, vitest_1.expect)(agentRunCalls[0].body.runId).toBe(agentRunCalls[1].body.runId);
|
|
118
137
|
});
|
|
119
138
|
(0, vitest_1.it)("should include tool definitions in logged conversation when tools are present", async () => {
|
|
120
139
|
const mockModel = new test_1.MockLanguageModelV3({
|
|
@@ -231,8 +250,111 @@ const test_1 = require("ai/test");
|
|
|
231
250
|
const messages = logCall.body.messages;
|
|
232
251
|
const assistantMsg = messages.find((m) => m.role === "assistant");
|
|
233
252
|
(0, vitest_1.expect)(assistantMsg).toBeDefined();
|
|
234
|
-
(0, vitest_1.expect)(assistantMsg.content).
|
|
235
|
-
|
|
236
|
-
|
|
253
|
+
(0, vitest_1.expect)(assistantMsg.content).toBe("Hello world");
|
|
254
|
+
(0, vitest_1.expect)(traceEventCalls.some((call) => Array.isArray(call.body.events) &&
|
|
255
|
+
call.body.events.some((event) => event.eventType === "assistant.progress"))).toBe(true);
|
|
256
|
+
(0, vitest_1.expect)(traceEventCalls.some((call) => { var _a; return ((_a = call.body.finish) === null || _a === void 0 ? void 0 : _a.state) === "completed"; })).toBe(true);
|
|
257
|
+
(0, vitest_1.expect)(agentRunCalls).toHaveLength(2);
|
|
258
|
+
(0, vitest_1.expect)(agentRunCalls[0].body.runId).toBe(agentRunCalls[1].body.runId);
|
|
259
|
+
(0, vitest_1.expect)(logCall.body.turnIndex).toBe(0);
|
|
260
|
+
(0, vitest_1.expect)(typeof logCall.body.turnId).toBe("string");
|
|
261
|
+
});
|
|
262
|
+
(0, vitest_1.it)("reuses one session run but increments turn metadata per turn", async () => {
|
|
263
|
+
const makeModel = () => new test_1.MockLanguageModelV3({
|
|
264
|
+
doStream: async () => ({
|
|
265
|
+
stream: (0, test_1.convertArrayToReadableStream)([
|
|
266
|
+
{ type: "text-start", id: "t1" },
|
|
267
|
+
{ type: "text-delta", id: "t1", delta: "Hello" },
|
|
268
|
+
{ type: "text-end", id: "t1" },
|
|
269
|
+
{
|
|
270
|
+
type: "finish",
|
|
271
|
+
finishReason: { unified: "stop", raw: undefined },
|
|
272
|
+
usage: {
|
|
273
|
+
inputTokens: { total: 10, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },
|
|
274
|
+
outputTokens: { total: 5, text: undefined, reasoning: undefined },
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
]),
|
|
278
|
+
}),
|
|
279
|
+
});
|
|
280
|
+
const cl = (0, index_1.createCognitiveLayer)({
|
|
281
|
+
provider: () => makeModel(),
|
|
282
|
+
clConfig: {
|
|
283
|
+
apiKey: "test-api-key",
|
|
284
|
+
appId: "test-app",
|
|
285
|
+
projectId: "test-project",
|
|
286
|
+
processDelayMs: 0,
|
|
287
|
+
logLevel: "none",
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
const model = cl("mock-model", {
|
|
291
|
+
userId: "user-1",
|
|
292
|
+
projectId: "project-1",
|
|
293
|
+
sessionId: "session-1",
|
|
294
|
+
});
|
|
295
|
+
await (0, ai_1.streamText)({ model, messages: [{ role: "user", content: "first" }] }).text;
|
|
296
|
+
await (0, ai_1.streamText)({ model, messages: [{ role: "user", content: "second" }] }).text;
|
|
297
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
298
|
+
const logCalls = fetchCalls.filter((call) => call.url.includes("/api/cognitive/log"));
|
|
299
|
+
const runCalls = agentRunCalls.filter((call) => call.url.includes("/api/cognitive/agent-run"));
|
|
300
|
+
(0, vitest_1.expect)(logCalls).toHaveLength(2);
|
|
301
|
+
(0, vitest_1.expect)(logCalls.map((call) => call.body.turnIndex)).toEqual([0, 1]);
|
|
302
|
+
(0, vitest_1.expect)(new Set(logCalls.map((call) => call.body.turnId)).size).toBe(2);
|
|
303
|
+
(0, vitest_1.expect)(new Set(runCalls.map((call) => call.body.runId)).size).toBe(1);
|
|
304
|
+
});
|
|
305
|
+
(0, vitest_1.it)("forwards automatic thread title config from providerOptions to the log payload", async () => {
|
|
306
|
+
const mockModel = new test_1.MockLanguageModelV3({
|
|
307
|
+
doStream: async () => ({
|
|
308
|
+
stream: (0, test_1.convertArrayToReadableStream)([
|
|
309
|
+
{ type: "text-start", id: "t1" },
|
|
310
|
+
{ type: "text-delta", id: "t1", delta: "Knowledge base ready" },
|
|
311
|
+
{ type: "text-end", id: "t1" },
|
|
312
|
+
{
|
|
313
|
+
type: "finish",
|
|
314
|
+
finishReason: { unified: "stop", raw: undefined },
|
|
315
|
+
usage: {
|
|
316
|
+
inputTokens: { total: 12, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },
|
|
317
|
+
outputTokens: { total: 4, text: undefined, reasoning: undefined },
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
]),
|
|
321
|
+
}),
|
|
322
|
+
});
|
|
323
|
+
const cl = (0, index_1.createCognitiveLayer)({
|
|
324
|
+
provider: () => mockModel,
|
|
325
|
+
clConfig: {
|
|
326
|
+
apiKey: "test-api-key",
|
|
327
|
+
appId: "test-app",
|
|
328
|
+
projectId: "test-project",
|
|
329
|
+
processDelayMs: 0,
|
|
330
|
+
logLevel: "none",
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
const model = cl("mock-model", {
|
|
334
|
+
userId: "user-1",
|
|
335
|
+
projectId: "project-1",
|
|
336
|
+
sessionId: "session-1",
|
|
337
|
+
});
|
|
338
|
+
await (0, ai_1.streamText)({
|
|
339
|
+
model,
|
|
340
|
+
messages: [{ role: "user", content: "Search the knowledge base for onboarding docs" }],
|
|
341
|
+
providerOptions: {
|
|
342
|
+
kognitive: {
|
|
343
|
+
automaticTitle: {
|
|
344
|
+
trigger: "first-message",
|
|
345
|
+
strategy: "first-message",
|
|
346
|
+
maxLength: 60,
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
}).text;
|
|
351
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
352
|
+
const logCall = fetchCalls.find((call) => call.url.includes("/api/cognitive/log"));
|
|
353
|
+
(0, vitest_1.expect)(logCall).toBeDefined();
|
|
354
|
+
(0, vitest_1.expect)(logCall.body.automaticTitle).toEqual({
|
|
355
|
+
trigger: "first-message",
|
|
356
|
+
strategy: "first-message",
|
|
357
|
+
maxLength: 60,
|
|
358
|
+
});
|
|
237
359
|
});
|
|
238
360
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { streamText as aiStreamText, generateText as aiGenerateText, type LanguageModel } from "ai";
|
|
2
|
+
import { type AutomaticThreadTitleConfig } from "@kognitivedev/shared";
|
|
2
3
|
export { renderTemplate, type TemplateVariables } from "./template";
|
|
3
4
|
/**
|
|
4
5
|
* Log levels for controlling verbosity of CognitiveLayer logging.
|
|
@@ -40,11 +41,16 @@ export interface PromptConfig {
|
|
|
40
41
|
variables?: Record<string, string | boolean>;
|
|
41
42
|
tag?: string;
|
|
42
43
|
}
|
|
43
|
-
export
|
|
44
|
-
|
|
44
|
+
export interface CognitiveToolOptions {
|
|
45
|
+
autoTopicMemoryTool?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export type CLStreamTextOptions = Omit<Parameters<typeof aiStreamText>[0], 'prompt'> & {
|
|
48
|
+
prompt?: PromptConfig;
|
|
49
|
+
kognitive?: CognitiveToolOptions;
|
|
45
50
|
};
|
|
46
|
-
export type CLGenerateTextOptions = Omit<Parameters<typeof aiGenerateText>[0], '
|
|
47
|
-
prompt
|
|
51
|
+
export type CLGenerateTextOptions = Omit<Parameters<typeof aiGenerateText>[0], 'prompt'> & {
|
|
52
|
+
prompt?: PromptConfig;
|
|
53
|
+
kognitive?: CognitiveToolOptions;
|
|
48
54
|
};
|
|
49
55
|
export interface LogConversationPayload {
|
|
50
56
|
userId: string;
|
|
@@ -74,6 +80,8 @@ export interface LogConversationPayload {
|
|
|
74
80
|
parameters?: Record<string, unknown>;
|
|
75
81
|
}>;
|
|
76
82
|
agentRunId?: string;
|
|
83
|
+
turnId?: string;
|
|
84
|
+
turnIndex?: number;
|
|
77
85
|
metadata?: Record<string, unknown>;
|
|
78
86
|
spans?: Array<{
|
|
79
87
|
spanKey: string;
|
|
@@ -87,6 +95,10 @@ export interface LogConversationPayload {
|
|
|
87
95
|
errorMessage?: string;
|
|
88
96
|
metadata?: Record<string, unknown>;
|
|
89
97
|
}>;
|
|
98
|
+
automaticTitle?: AutomaticThreadTitleConfig;
|
|
99
|
+
}
|
|
100
|
+
interface LogConversationResult {
|
|
101
|
+
generatedTitle: string | null;
|
|
90
102
|
}
|
|
91
103
|
export type CognitiveLayer = CLModelWrapper & {
|
|
92
104
|
streamText: (options: CLStreamTextOptions) => Promise<ReturnType<typeof aiStreamText>>;
|
|
@@ -95,7 +107,7 @@ export type CognitiveLayer = CLModelWrapper & {
|
|
|
95
107
|
userId?: string;
|
|
96
108
|
tag?: string;
|
|
97
109
|
}) => Promise<CachedPrompt>;
|
|
98
|
-
logConversation: (payload: LogConversationPayload) => Promise<
|
|
110
|
+
logConversation: (payload: LogConversationPayload) => Promise<LogConversationResult | null>;
|
|
99
111
|
triggerProcessing: (userId: string, sessionId: string) => void;
|
|
100
112
|
clearSessionCache: (sessionKey?: string) => void;
|
|
101
113
|
};
|
|
@@ -110,6 +122,44 @@ export interface CachedPrompt {
|
|
|
110
122
|
abTestId?: string;
|
|
111
123
|
variant?: "control" | "variant";
|
|
112
124
|
}
|
|
125
|
+
export declare function toMessageEventStream(result: {
|
|
126
|
+
fullStream: AsyncIterable<any>;
|
|
127
|
+
}): ReadableStream<{
|
|
128
|
+
event: string;
|
|
129
|
+
data: unknown;
|
|
130
|
+
}>;
|
|
131
|
+
export declare function toGeneratedMessageEventStream(result: {
|
|
132
|
+
text?: string;
|
|
133
|
+
toolCalls?: Array<{
|
|
134
|
+
toolCallId?: string;
|
|
135
|
+
toolName?: string;
|
|
136
|
+
input?: unknown;
|
|
137
|
+
}>;
|
|
138
|
+
toolResults?: Array<{
|
|
139
|
+
toolCallId?: string;
|
|
140
|
+
toolName?: string;
|
|
141
|
+
output?: unknown;
|
|
142
|
+
result?: unknown;
|
|
143
|
+
isError?: boolean;
|
|
144
|
+
}>;
|
|
145
|
+
steps?: Array<{
|
|
146
|
+
toolCalls?: Array<{
|
|
147
|
+
toolCallId?: string;
|
|
148
|
+
toolName?: string;
|
|
149
|
+
input?: unknown;
|
|
150
|
+
}>;
|
|
151
|
+
toolResults?: Array<{
|
|
152
|
+
toolCallId?: string;
|
|
153
|
+
toolName?: string;
|
|
154
|
+
output?: unknown;
|
|
155
|
+
result?: unknown;
|
|
156
|
+
isError?: boolean;
|
|
157
|
+
}>;
|
|
158
|
+
}>;
|
|
159
|
+
}): ReadableStream<{
|
|
160
|
+
event: string;
|
|
161
|
+
data: unknown;
|
|
162
|
+
}>;
|
|
113
163
|
export declare function createCognitiveLayer(config: {
|
|
114
164
|
provider: any;
|
|
115
165
|
clConfig: CognitiveLayerConfig;
|