@kognitivedev/vercel-ai-provider 0.1.4 → 0.1.6

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/dist/index.js CHANGED
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.createCognitiveLayer = createCognitiveLayer;
4
15
  const ai_1 = require("ai");
@@ -37,10 +48,22 @@ function createLogger(logLevel) {
37
48
  },
38
49
  };
39
50
  }
51
+ const PROMPT_CACHE_TTL_MS = 60000; // 1 minute
52
+ /**
53
+ * Interpolate {{variable}} placeholders in a template string.
54
+ * Unmatched variables are left as-is.
55
+ */
56
+ function interpolateTemplate(content, variables) {
57
+ return content.replace(/\{\{(\w+)\}\}/g, (_, key) => { var _a; return (_a = variables[key]) !== null && _a !== void 0 ? _a : `{{${key}}}`; });
58
+ }
40
59
  // Session-scoped snapshot cache: sessionKey → formatted memory block
41
60
  const sessionSnapshots = new Map();
42
61
  // Regex to detect if memory has already been injected
43
62
  const MEMORY_TAG_REGEX = /<MemoryContext>/i;
63
+ // Symbol-keyed property to track session settings on model objects
64
+ const SESSION_KEY = Symbol.for("cl:session");
65
+ // Session key → prompt metadata (populated by cl.streamText/cl.generateText, read by middleware)
66
+ const sessionPromptMetadata = new Map();
44
67
  /**
45
68
  * Check if any system message already contains a <MemoryContext> block.
46
69
  */
@@ -65,11 +88,43 @@ function createCognitiveLayer(config) {
65
88
  // Default to 'info' log level
66
89
  const logLevel = clConfig.logLevel || 'info';
67
90
  const logger = createLogger(logLevel);
91
+ const authHeaders = {
92
+ "Content-Type": "application/json",
93
+ "Authorization": `Bearer ${clConfig.apiKey}`,
94
+ };
95
+ // Prompt cache: slug → CachedPrompt
96
+ const promptCache = new Map();
97
+ const resolvePrompt = async (slug) => {
98
+ const cached = promptCache.get(slug);
99
+ if (cached && Date.now() - cached.fetchedAt < PROMPT_CACHE_TTL_MS) {
100
+ logger.debug("Using cached prompt", { slug, version: cached.version });
101
+ return cached;
102
+ }
103
+ const res = await fetch(`${baseUrl}/api/cognitive/prompt?slug=${encodeURIComponent(slug)}`, {
104
+ headers: { "Authorization": `Bearer ${clConfig.apiKey}` },
105
+ });
106
+ if (!res.ok) {
107
+ const body = await res.text();
108
+ throw new Error(`Failed to resolve prompt "${slug}": ${res.status} ${body}`);
109
+ }
110
+ const data = await res.json();
111
+ const entry = {
112
+ promptId: data.promptId,
113
+ slug: data.slug,
114
+ version: data.version,
115
+ content: data.content,
116
+ fetchedAt: Date.now(),
117
+ gatewaySlug: data.gatewaySlug,
118
+ };
119
+ promptCache.set(slug, entry);
120
+ logger.info("Prompt resolved", { slug, version: entry.version });
121
+ return entry;
122
+ };
68
123
  const logConversation = async (payload) => {
69
124
  try {
70
125
  await fetch(`${baseUrl}/api/cognitive/log`, {
71
126
  method: "POST",
72
- headers: { "Content-Type": "application/json" },
127
+ headers: authHeaders,
73
128
  body: JSON.stringify(Object.assign(Object.assign({}, payload), { type: "conversation", timestamp: new Date().toISOString() })),
74
129
  });
75
130
  }
@@ -77,12 +132,12 @@ function createCognitiveLayer(config) {
77
132
  logger.error("Log failed", e);
78
133
  }
79
134
  };
80
- const triggerProcessing = (userId, agentId, sessionId) => {
135
+ const triggerProcessing = (userId, projectId, sessionId) => {
81
136
  const run = () => {
82
137
  fetch(`${baseUrl}/api/cognitive/process`, {
83
138
  method: "POST",
84
- headers: { "Content-Type": "application/json" },
85
- body: JSON.stringify({ userId, agentId, sessionId }),
139
+ headers: authHeaders,
140
+ body: JSON.stringify({ userId, sessionId }),
86
141
  }).catch(e => logger.error("Process trigger failed", e));
87
142
  };
88
143
  if (processDelay > 0) {
@@ -95,154 +150,298 @@ function createCognitiveLayer(config) {
95
150
  const withMemorySystemPrompt = (params, incomingMessages, memoryPrompt) => {
96
151
  var _a;
97
152
  const nextParams = Object.assign({}, params);
98
- // 1) If caller provided a top-level system prompt, overwrite it.
99
- if (nextParams.system) {
100
- nextParams.system = memoryPrompt;
101
- return { nextParams, messages: incomingMessages, mode: "overwrite-param" };
102
- }
103
- // 2) If first message is system, replace its content.
153
+ // 1) If first message is system, append memory to its content (without mutating original).
104
154
  if (incomingMessages.length > 0 && ((_a = incomingMessages[0]) === null || _a === void 0 ? void 0 : _a.role) === "system") {
105
- const updated = [...incomingMessages];
106
- updated[0] = Object.assign(Object.assign({}, updated[0]), { content: memoryPrompt });
155
+ const original = incomingMessages[0];
156
+ const updatedContent = typeof original.content === "string"
157
+ ? original.content + "\n\n" + memoryPrompt
158
+ : memoryPrompt;
159
+ const updated = [Object.assign(Object.assign({}, original), { content: updatedContent }), ...incomingMessages.slice(1)];
107
160
  return { nextParams, messages: updated, mode: "overwrite-first-system" };
108
161
  }
109
- // 3) Otherwise prepend a system message.
162
+ // 2) Otherwise prepend a system message.
110
163
  const updated = [{ role: "system", content: memoryPrompt }, ...incomingMessages];
111
164
  return { nextParams, messages: updated, mode: "prepend-system" };
112
165
  };
113
- return (modelId, settings, providerOptions) => {
114
- // Pass provider options through to the underlying provider
115
- const model = (providerOptions
116
- ? provider(modelId, providerOptions)
117
- : provider(modelId));
118
- const userId = settings === null || settings === void 0 ? void 0 : settings.userId;
119
- const agentId = (settings === null || settings === void 0 ? void 0 : settings.agentId) || clConfig.defaultAgentId || "default";
120
- const sessionId = settings === null || settings === void 0 ? void 0 : settings.sessionId;
121
- const sessionMissing = !!userId && !sessionId;
122
- if (sessionMissing) {
123
- logger.warn("sessionId is required to log and process memories; skipping logging until provided.");
124
- }
125
- return (0, ai_1.wrapLanguageModel)({
126
- model,
127
- middleware: {
128
- async transformParams({ params }) {
129
- if (!userId)
130
- return params;
131
- const incomingMessages = Array.isArray(params.prompt)
132
- ? params.prompt
133
- : [];
134
- // 1) Check if memory is already injected in messages
135
- if (hasExistingMemoryInjection(incomingMessages)) {
136
- logger.debug("Memory already injected, skipping");
137
- return params;
138
- }
139
- // 2) Check session cache
140
- const sessionKey = `${userId}:${agentId}:${sessionId || "default"}`;
141
- let systemPromptToAdd = sessionSnapshots.get(sessionKey);
142
- // 3) Fetch snapshot only if not cached
143
- if (systemPromptToAdd === undefined) {
144
- try {
145
- const url = `${baseUrl}/api/cognitive/snapshot?userId=${userId}&agentId=${agentId}&appId=${clConfig.appId}`;
146
- const res = await fetch(url);
147
- if (res.ok) {
148
- const data = await res.json();
149
- const systemBlock = data.systemBlock || "";
150
- const userContextBlock = data.userContextBlock || "";
151
- systemPromptToAdd =
152
- systemBlock !== "" || userContextBlock !== ""
153
- ? `
166
+ const buildMiddleware = (userId, projectId, sessionId, modelId) => ({
167
+ specificationVersion: 'v3',
168
+ async transformParams({ params }) {
169
+ if (!userId)
170
+ return params;
171
+ const incomingMessages = Array.isArray(params.prompt)
172
+ ? params.prompt
173
+ : [];
174
+ // 1) Check if memory is already injected in messages
175
+ if (hasExistingMemoryInjection(incomingMessages)) {
176
+ logger.debug("Memory already injected, skipping");
177
+ return params;
178
+ }
179
+ // 2) Check session cache
180
+ const sessionKey = `${userId}:${projectId}:${sessionId || "default"}`;
181
+ let systemPromptToAdd = sessionSnapshots.get(sessionKey);
182
+ // 3) Fetch snapshot only if not cached
183
+ if (systemPromptToAdd === undefined) {
184
+ try {
185
+ const url = `${baseUrl}/api/cognitive/snapshot?userId=${userId}`;
186
+ const res = await fetch(url, {
187
+ headers: { "Authorization": `Bearer ${clConfig.apiKey}` },
188
+ });
189
+ if (res.ok) {
190
+ const data = await res.json();
191
+ const systemBlock = data.systemBlock || "";
192
+ const userContextBlock = data.userContextBlock || "";
193
+ systemPromptToAdd =
194
+ systemBlock !== "" || userContextBlock !== ""
195
+ ? `
154
196
  <MemoryContext>
155
197
  Use the following memory to stay consistent. Prefer UserContext facts for answers; AgentHeuristics guide style, safety, and priorities.
156
198
  ${systemBlock || "None"}
157
199
  ${userContextBlock || "None"}
158
200
  </MemoryContext>
159
- `.trim()
160
- : "";
161
- // Cache the snapshot for this session
162
- sessionSnapshots.set(sessionKey, systemPromptToAdd);
163
- logger.info("Snapshot fetched and cached", {
164
- userId,
165
- agentId,
166
- sessionId,
167
- sessionKey,
168
- systemLen: systemBlock.length,
169
- userLen: userContextBlock.length,
170
- });
171
- // At debug level, log the full snapshot data
172
- logger.debug("Full snapshot data", {
173
- systemBlock,
174
- userContextBlock,
175
- rawData: data,
176
- });
177
- }
178
- else {
179
- logger.warn("Snapshot fetch failed", { status: res.status });
180
- systemPromptToAdd = "";
181
- sessionSnapshots.set(sessionKey, systemPromptToAdd);
182
- }
183
- }
184
- catch (e) {
185
- logger.warn("Failed to fetch snapshot", e);
186
- systemPromptToAdd = "";
187
- sessionSnapshots.set(sessionKey, systemPromptToAdd);
188
- }
189
- }
190
- else {
191
- logger.debug("Using cached snapshot for session", { sessionKey });
192
- }
193
- if (!systemPromptToAdd) {
194
- return Object.assign(Object.assign({}, params), { messages: incomingMessages });
195
- }
196
- const { nextParams, messages: messagesWithMemory } = withMemorySystemPrompt(params, incomingMessages, systemPromptToAdd);
197
- logger.info("Injecting memory system prompt", {
198
- sessionKey,
199
- promptLength: systemPromptToAdd.length,
200
- });
201
- logger.debug("Injected prompt content", { systemPromptToAdd });
202
- return Object.assign(Object.assign({}, nextParams), { prompt: messagesWithMemory });
203
- },
204
- async wrapGenerate({ doGenerate, params }) {
205
- var _a;
206
- const result = await doGenerate();
207
- if (userId && sessionId) {
208
- const messagesInput = params.messages || params.prompt || [];
209
- const resultMessages = (_a = result === null || result === void 0 ? void 0 : result.response) === null || _a === void 0 ? void 0 : _a.messages;
210
- const assistantMessage = (result === null || result === void 0 ? void 0 : result.text)
211
- ? [{ role: "assistant", content: [{ type: "text", text: result.text }] }]
212
- : [];
213
- const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
214
- ? resultMessages
215
- : [...messagesInput, ...assistantMessage];
216
- logConversation({
201
+ `.trim()
202
+ : "";
203
+ // Cache the snapshot for this session
204
+ sessionSnapshots.set(sessionKey, systemPromptToAdd);
205
+ logger.info("Snapshot fetched and cached", {
217
206
  userId,
218
- agentId,
207
+ projectId,
219
208
  sessionId,
220
- messages: finalMessages,
221
- modelId,
222
- }).then(() => triggerProcessing(userId, agentId, sessionId));
209
+ sessionKey,
210
+ systemLen: systemBlock.length,
211
+ userLen: userContextBlock.length,
212
+ });
213
+ // At debug level, log the full snapshot data
214
+ logger.debug("Full snapshot data", {
215
+ systemBlock,
216
+ userContextBlock,
217
+ rawData: data,
218
+ });
223
219
  }
224
- return result;
225
- },
226
- async wrapStream({ doStream, params }) {
227
- var _a;
228
- const result = await doStream();
229
- if (userId && sessionId) {
230
- const messagesInput = params.messages || params.prompt || [];
231
- const resultMessages = (_a = result === null || result === void 0 ? void 0 : result.response) === null || _a === void 0 ? void 0 : _a.messages;
232
- const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
233
- ? resultMessages
234
- : messagesInput;
235
- logConversation({
236
- userId,
237
- agentId,
238
- sessionId,
239
- messages: finalMessages,
240
- modelId,
241
- }).then(() => triggerProcessing(userId, agentId, sessionId));
220
+ else {
221
+ logger.warn("Snapshot fetch failed", { status: res.status });
222
+ systemPromptToAdd = "";
223
+ sessionSnapshots.set(sessionKey, systemPromptToAdd);
242
224
  }
243
- return result;
244
225
  }
226
+ catch (e) {
227
+ logger.warn("Failed to fetch snapshot", e);
228
+ systemPromptToAdd = "";
229
+ sessionSnapshots.set(sessionKey, systemPromptToAdd);
230
+ }
231
+ }
232
+ else {
233
+ logger.debug("Using cached snapshot for session", { sessionKey });
234
+ }
235
+ if (!systemPromptToAdd) {
236
+ return params;
237
+ }
238
+ const { nextParams, messages: messagesWithMemory } = withMemorySystemPrompt(params, incomingMessages, systemPromptToAdd);
239
+ logger.info("Injecting memory system prompt", {
240
+ sessionKey,
241
+ promptLength: systemPromptToAdd.length,
242
+ });
243
+ logger.debug("Injected prompt content", { systemPromptToAdd });
244
+ return Object.assign(Object.assign({}, nextParams), { prompt: messagesWithMemory });
245
+ },
246
+ async wrapGenerate({ doGenerate, params }) {
247
+ var _a, _b;
248
+ let result;
249
+ try {
250
+ result = await doGenerate();
245
251
  }
252
+ catch (err) {
253
+ logger.error("doGenerate failed", err);
254
+ logger.error("doGenerate params.prompt", JSON.stringify((_a = params.prompt) === null || _a === void 0 ? void 0 : _a.map((m) => ({ role: m.role, contentType: typeof m.content, contentLength: Array.isArray(m.content) ? m.content.length : undefined })), null, 2));
255
+ throw err;
256
+ }
257
+ if (userId && sessionId) {
258
+ const sessionKey = `${userId}:${projectId}:${sessionId}`;
259
+ const promptMeta = sessionPromptMetadata.get(sessionKey);
260
+ const messagesInput = params.messages || params.prompt || [];
261
+ const resultMessages = (_b = result === null || result === void 0 ? void 0 : result.response) === null || _b === void 0 ? void 0 : _b.messages;
262
+ const assistantMessage = (result === null || result === void 0 ? void 0 : result.text)
263
+ ? [{ role: "assistant", content: [{ type: "text", text: result.text }] }]
264
+ : [];
265
+ const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
266
+ ? resultMessages
267
+ : [...messagesInput, ...assistantMessage];
268
+ logConversation(Object.assign({ userId,
269
+ projectId,
270
+ sessionId, messages: finalMessages, modelId, usage: result.usage }, (promptMeta && {
271
+ promptSlug: promptMeta.promptSlug,
272
+ promptVersion: promptMeta.promptVersion,
273
+ promptId: promptMeta.promptId,
274
+ }))).then(() => triggerProcessing(userId, projectId, sessionId));
275
+ }
276
+ return result;
277
+ },
278
+ async wrapStream({ doStream, params }) {
279
+ var _a;
280
+ let result;
281
+ try {
282
+ logger.debug("Starting doStream with params", JSON.stringify(params, null, 2));
283
+ result = await doStream();
284
+ }
285
+ catch (err) {
286
+ console.log(err.cause);
287
+ console.log(err.stack);
288
+ logger.error("doStream failed", err);
289
+ throw err;
290
+ }
291
+ if (userId && sessionId) {
292
+ const sessionKey = `${userId}:${projectId}:${sessionId}`;
293
+ const promptMeta = sessionPromptMetadata.get(sessionKey);
294
+ const messagesInput = params.messages || params.prompt || [];
295
+ const resultMessages = (_a = result === null || result === void 0 ? void 0 : result.response) === null || _a === void 0 ? void 0 : _a.messages;
296
+ const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
297
+ ? resultMessages
298
+ : messagesInput;
299
+ let streamUsage;
300
+ let accumulatedText = '';
301
+ const originalStream = result.stream;
302
+ const transformStream = new TransformStream({
303
+ transform(chunk, controller) {
304
+ if (chunk.type === 'text-delta') {
305
+ accumulatedText += chunk.delta;
306
+ }
307
+ if (chunk.type === 'finish' && chunk.usage) {
308
+ streamUsage = chunk.usage;
309
+ }
310
+ controller.enqueue(chunk);
311
+ },
312
+ flush() {
313
+ const allMessages = accumulatedText
314
+ ? [...finalMessages, { role: "assistant", content: [{ type: "text", text: accumulatedText }] }]
315
+ : finalMessages;
316
+ logConversation(Object.assign({ userId,
317
+ projectId,
318
+ sessionId, messages: allMessages, modelId, usage: streamUsage }, (promptMeta && {
319
+ promptSlug: promptMeta.promptSlug,
320
+ promptVersion: promptMeta.promptVersion,
321
+ promptId: promptMeta.promptId,
322
+ }))).then(() => triggerProcessing(userId, projectId, sessionId));
323
+ }
324
+ });
325
+ result.stream = originalStream.pipeThrough(transformStream);
326
+ }
327
+ return result;
328
+ }
329
+ });
330
+ const resolveModel = (originalModel, gatewaySlug) => {
331
+ if (!gatewaySlug || !clConfig.providerFactory) {
332
+ if (gatewaySlug && !clConfig.providerFactory) {
333
+ logger.warn("Gateway config found but no providerFactory provided");
334
+ }
335
+ return originalModel;
336
+ }
337
+ try {
338
+ const gatewayURL = `${baseUrl}/api/cognitive/gateway/${gatewaySlug}`;
339
+ const modelId = originalModel.modelId || 'default';
340
+ const rawModel = clConfig.providerFactory(gatewayURL)(modelId);
341
+ const session = originalModel[SESSION_KEY];
342
+ if (!session)
343
+ return rawModel;
344
+ const wrapped = (0, ai_1.wrapLanguageModel)({
345
+ model: rawModel,
346
+ middleware: buildMiddleware(session.userId, session.projectId, session.sessionId, modelId),
347
+ });
348
+ wrapped[SESSION_KEY] = session;
349
+ return wrapped;
350
+ }
351
+ catch (err) {
352
+ logger.error("Failed to create gateway model, falling back to original", err);
353
+ return originalModel;
354
+ }
355
+ };
356
+ const clWrapper = (modelId, settings, providerOptions) => {
357
+ // Pass provider options through to the underlying provider
358
+ const model = (providerOptions
359
+ ? provider(modelId, providerOptions)
360
+ : provider(modelId));
361
+ const userId = settings === null || settings === void 0 ? void 0 : settings.userId;
362
+ const projectId = (settings === null || settings === void 0 ? void 0 : settings.projectId) || clConfig.projectId || "default";
363
+ const sessionId = settings === null || settings === void 0 ? void 0 : settings.sessionId;
364
+ const sessionMissing = !!userId && !sessionId;
365
+ if (sessionMissing) {
366
+ logger.warn("sessionId is required to log and process memories; skipping logging until provided.");
367
+ }
368
+ const wrappedModel = (0, ai_1.wrapLanguageModel)({
369
+ model: model,
370
+ middleware: buildMiddleware(userId, projectId, sessionId, modelId),
371
+ });
372
+ // Track session settings on the model for use in cl.streamText/cl.generateText
373
+ if (userId && sessionId) {
374
+ wrappedModel[SESSION_KEY] = { userId, projectId, sessionId };
375
+ }
376
+ return wrappedModel;
377
+ };
378
+ const clStreamText = async (options) => {
379
+ const { prompt: promptConfig } = options, rest = __rest(options, ["prompt"]);
380
+ // Resolve and interpolate prompt
381
+ const resolved = await resolvePrompt(promptConfig.slug);
382
+ const system = promptConfig.variables
383
+ ? interpolateTemplate(resolved.content, promptConfig.variables)
384
+ : resolved.content;
385
+ // Store prompt metadata for the session (read by middleware during logging)
386
+ const session = options.model[SESSION_KEY];
387
+ if (session) {
388
+ const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
389
+ sessionPromptMetadata.set(sessionKey, {
390
+ promptSlug: resolved.slug,
391
+ promptVersion: resolved.version,
392
+ promptId: resolved.promptId,
393
+ });
394
+ }
395
+ logger.info("cl.streamText called", {
396
+ slug: promptConfig.slug,
397
+ version: resolved.version,
398
+ systemLength: system.length,
399
+ });
400
+ const model = resolveModel(options.model, resolved.gatewaySlug);
401
+ return (0, ai_1.streamText)(Object.assign(Object.assign({}, rest), { model, system }));
402
+ };
403
+ const clGenerateText = async (options) => {
404
+ const { prompt: promptConfig } = options, rest = __rest(options, ["prompt"]);
405
+ // Resolve and interpolate prompt
406
+ const resolved = await resolvePrompt(promptConfig.slug);
407
+ const system = promptConfig.variables
408
+ ? interpolateTemplate(resolved.content, promptConfig.variables)
409
+ : resolved.content;
410
+ // Store prompt metadata for the session (read by middleware during logging)
411
+ const session = options.model[SESSION_KEY];
412
+ if (session) {
413
+ const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
414
+ sessionPromptMetadata.set(sessionKey, {
415
+ promptSlug: resolved.slug,
416
+ promptVersion: resolved.version,
417
+ promptId: resolved.promptId,
418
+ });
419
+ }
420
+ logger.info("cl.generateText called", {
421
+ slug: promptConfig.slug,
422
+ version: resolved.version,
423
+ systemLength: system.length,
246
424
  });
425
+ const model = resolveModel(options.model, resolved.gatewaySlug);
426
+ return (0, ai_1.generateText)(Object.assign(Object.assign({}, rest), { model, system }));
247
427
  };
428
+ // Return the model wrapper function with streamText/generateText attached
429
+ return Object.assign(clWrapper, {
430
+ streamText: clStreamText,
431
+ generateText: clGenerateText,
432
+ resolvePrompt,
433
+ logConversation,
434
+ triggerProcessing,
435
+ clearPromptCache: () => promptCache.clear(),
436
+ clearSessionCache: (sessionKey) => {
437
+ if (sessionKey) {
438
+ sessionSnapshots.delete(sessionKey);
439
+ sessionPromptMetadata.delete(sessionKey);
440
+ }
441
+ else {
442
+ sessionSnapshots.clear();
443
+ sessionPromptMetadata.clear();
444
+ }
445
+ },
446
+ });
248
447
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kognitivedev/vercel-ai-provider",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "publishConfig": {
@@ -9,14 +9,16 @@
9
9
  "scripts": {
10
10
  "build": "tsc",
11
11
  "dev": "tsc -w",
12
+ "test": "vitest run",
12
13
  "prepublishOnly": "npm run build"
13
14
  },
14
15
  "peerDependencies": {
15
- "ai": "^4.0.0 || ^5.0.0"
16
+ "ai": "^5.0.0 || ^6.0.0"
16
17
  },
17
18
  "devDependencies": {
18
19
  "typescript": "^5.0.0",
19
- "ai": "latest",
20
- "@types/node": "^20.0.0"
20
+ "ai": "^6.0.0",
21
+ "@types/node": "^20.0.0",
22
+ "vitest": "^3.0.0"
21
23
  }
22
24
  }
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { createCognitiveLayer } from "../index";
3
+ import { streamText } from "ai";
4
+ import { MockLanguageModelV3, convertArrayToReadableStream } from "ai/test";
5
+
6
+ describe("wrapStream logging", () => {
7
+ let fetchCalls: { url: string; body: any }[];
8
+
9
+ beforeEach(() => {
10
+ fetchCalls = [];
11
+
12
+ vi.stubGlobal(
13
+ "fetch",
14
+ vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
15
+ const urlStr = typeof url === "string" ? url : url.toString();
16
+
17
+ if (urlStr.includes("/api/cognitive/snapshot")) {
18
+ return new Response(
19
+ JSON.stringify({ systemBlock: "", userContextBlock: "" }),
20
+ { status: 200, headers: { "Content-Type": "application/json" } }
21
+ );
22
+ }
23
+
24
+ if (urlStr.includes("/api/cognitive/log")) {
25
+ const body = JSON.parse(init?.body as string);
26
+ fetchCalls.push({ url: urlStr, body });
27
+ return new Response(JSON.stringify({ ok: true }), { status: 200 });
28
+ }
29
+
30
+ if (urlStr.includes("/api/cognitive/process")) {
31
+ return new Response(JSON.stringify({ ok: true }), { status: 200 });
32
+ }
33
+
34
+ return new Response("not found", { status: 404 });
35
+ })
36
+ );
37
+ });
38
+
39
+ it("should include assistant message in logged conversation after streaming", async () => {
40
+ const mockModel = new MockLanguageModelV3({
41
+ doStream: async () => ({
42
+ stream: convertArrayToReadableStream([
43
+ { type: "text-start" as const, id: "t1" },
44
+ { type: "text-delta" as const, id: "t1", delta: "Hello" },
45
+ { type: "text-delta" as const, id: "t1", delta: " world" },
46
+ { type: "text-end" as const, id: "t1" },
47
+ {
48
+ type: "finish" as const,
49
+ finishReason: {
50
+ unified: "stop" as const,
51
+ raw: undefined,
52
+ },
53
+ usage: {
54
+ inputTokens: { total: 10, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },
55
+ outputTokens: { total: 5, text: undefined, reasoning: undefined },
56
+ },
57
+ },
58
+ ] satisfies import("@ai-sdk/provider").LanguageModelV3StreamPart[]),
59
+ }),
60
+ });
61
+
62
+ const mockProvider = () => mockModel;
63
+
64
+ const cl = createCognitiveLayer({
65
+ provider: mockProvider,
66
+ clConfig: {
67
+ apiKey: "test-api-key",
68
+ appId: "test-app",
69
+ projectId: "test-project",
70
+ processDelayMs: 0,
71
+ logLevel: "none",
72
+ },
73
+ });
74
+
75
+ const model = cl("mock-model", {
76
+ userId: "user-1",
77
+ projectId: "project-1",
78
+ sessionId: "session-1",
79
+ });
80
+
81
+ const result = streamText({
82
+ model,
83
+ messages: [{ role: "user", content: "Hi" }],
84
+ });
85
+
86
+ // Fully consume the stream
87
+ const text = await result.text;
88
+ expect(text).toBe("Hello world");
89
+
90
+ // Wait for async logConversation to complete
91
+ await new Promise((r) => setTimeout(r, 100));
92
+
93
+ // Find the log call
94
+ const logCall = fetchCalls.find((c) => c.url.includes("/api/cognitive/log"));
95
+ expect(logCall).toBeDefined();
96
+
97
+ const messages = logCall!.body.messages;
98
+ const assistantMsg = messages.find((m: any) => m.role === "assistant");
99
+ expect(assistantMsg).toBeDefined();
100
+ expect(assistantMsg.content).toEqual([
101
+ { type: "text", text: "Hello world" },
102
+ ]);
103
+ });
104
+ });