@kognitivedev/vercel-ai-provider 0.1.7 → 0.1.9

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.
@@ -24,6 +24,98 @@ const test_1 = require("ai/test");
24
24
  return new Response("not found", { status: 404 });
25
25
  }));
26
26
  });
27
+ (0, vitest_1.it)("should capture tool-call chunks and include them in logged conversation", async () => {
28
+ const mockModel = new test_1.MockLanguageModelV3({
29
+ doStream: async () => ({
30
+ stream: (0, test_1.convertArrayToReadableStream)([
31
+ { type: "text-start", id: "t1" },
32
+ { type: "text-delta", id: "t1", delta: "Let me check" },
33
+ { type: "text-end", id: "t1" },
34
+ {
35
+ type: "tool-call",
36
+ toolCallId: "call-1",
37
+ toolName: "get_weather",
38
+ input: '{"city":"London"}',
39
+ },
40
+ {
41
+ type: "tool-result",
42
+ toolCallId: "call-1",
43
+ toolName: "get_weather",
44
+ result: { temperature: 15, unit: "celsius" },
45
+ },
46
+ {
47
+ type: "finish",
48
+ finishReason: {
49
+ unified: "tool-calls",
50
+ raw: undefined,
51
+ },
52
+ usage: {
53
+ inputTokens: { total: 20, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },
54
+ outputTokens: { total: 15, text: undefined, reasoning: undefined },
55
+ },
56
+ },
57
+ ]),
58
+ }),
59
+ });
60
+ const mockProvider = () => mockModel;
61
+ const cl = (0, index_1.createCognitiveLayer)({
62
+ provider: mockProvider,
63
+ clConfig: {
64
+ apiKey: "test-api-key",
65
+ appId: "test-app",
66
+ projectId: "test-project",
67
+ processDelayMs: 0,
68
+ logLevel: "none",
69
+ },
70
+ });
71
+ const model = cl("mock-model", {
72
+ userId: "user-1",
73
+ projectId: "project-1",
74
+ sessionId: "session-1",
75
+ });
76
+ const result = (0, ai_1.streamText)({
77
+ model,
78
+ messages: [{ role: "user", content: "What's the weather in London?" }],
79
+ });
80
+ // Fully consume the stream
81
+ await result.text;
82
+ // Wait for async logConversation to complete
83
+ await new Promise((r) => setTimeout(r, 100));
84
+ // Find the log call
85
+ const logCall = fetchCalls.find((c) => c.url.includes("/api/cognitive/log"));
86
+ (0, vitest_1.expect)(logCall).toBeDefined();
87
+ const messages = logCall.body.messages;
88
+ // Assistant message should contain text + tool-call parts
89
+ const assistantMsg = messages.find((m) => m.role === "assistant");
90
+ (0, vitest_1.expect)(assistantMsg).toBeDefined();
91
+ (0, vitest_1.expect)(assistantMsg.content).toEqual([
92
+ { type: "text", text: "Let me check" },
93
+ {
94
+ type: "tool-call",
95
+ toolCallId: "call-1",
96
+ toolName: "get_weather",
97
+ input: '{"city":"London"}',
98
+ },
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
+ {
105
+ type: "tool-result",
106
+ toolCallId: "call-1",
107
+ toolName: "get_weather",
108
+ result: { temperature: 15, unit: "celsius" },
109
+ },
110
+ ]);
111
+ // Spans should include the tool call with populated previews
112
+ const spans = logCall.body.spans;
113
+ const toolSpan = spans === null || spans === void 0 ? void 0 : spans.find((s) => s.spanType === "tool");
114
+ (0, vitest_1.expect)(toolSpan).toBeDefined();
115
+ (0, vitest_1.expect)(toolSpan.toolName).toBe("get_weather");
116
+ (0, vitest_1.expect)(toolSpan.inputPreview).toContain("London");
117
+ (0, vitest_1.expect)(toolSpan.outputPreview).toContain("15");
118
+ });
27
119
  (0, vitest_1.it)("should include assistant message in logged conversation after streaming", async () => {
28
120
  const mockModel = new test_1.MockLanguageModelV3({
29
121
  doStream: async () => ({
package/dist/index.d.ts CHANGED
@@ -55,11 +55,32 @@ export interface LogConversationPayload {
55
55
  promptSlug?: string;
56
56
  promptVersion?: number;
57
57
  promptId?: string;
58
+ traceId?: string;
59
+ parentSpanId?: string;
60
+ requestPreview?: string;
61
+ responsePreview?: string;
62
+ state?: "active" | "completed" | "error";
63
+ startedAt?: string;
64
+ endedAt?: string;
65
+ durationMs?: number;
66
+ metadata?: Record<string, unknown>;
67
+ spans?: Array<{
68
+ spanKey: string;
69
+ parentSpanKey?: string;
70
+ name: string;
71
+ spanType: string;
72
+ status?: "active" | "completed" | "error";
73
+ inputPreview?: string;
74
+ outputPreview?: string;
75
+ toolName?: string;
76
+ errorMessage?: string;
77
+ metadata?: Record<string, unknown>;
78
+ }>;
58
79
  }
59
80
  export type CognitiveLayer = CLModelWrapper & {
60
81
  streamText: (options: CLStreamTextOptions) => Promise<ReturnType<typeof aiStreamText>>;
61
82
  generateText: (options: CLGenerateTextOptions) => ReturnType<typeof aiGenerateText>;
62
- resolvePrompt: (slug: string) => Promise<CachedPrompt>;
83
+ resolvePrompt: (slug: string, userId?: string) => Promise<CachedPrompt>;
63
84
  logConversation: (payload: LogConversationPayload) => Promise<void>;
64
85
  triggerProcessing: (userId: string, projectId: string, sessionId: string) => void;
65
86
  clearPromptCache: () => void;
package/dist/index.js CHANGED
@@ -13,12 +13,25 @@ var __rest = (this && this.__rest) || function (s, e) {
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.createCognitiveLayer = createCognitiveLayer;
15
15
  const ai_1 = require("ai");
16
+ const crypto_1 = require("crypto");
16
17
  function isValidId(value) {
17
18
  if (value == null || typeof value !== "string")
18
19
  return false;
19
20
  const trimmed = value.trim();
20
21
  return trimmed !== "" && trimmed !== "null" && trimmed !== "undefined";
21
22
  }
23
+ function maskSecret(secret) {
24
+ if (!secret)
25
+ return "missing";
26
+ if (secret.length <= 8)
27
+ return `${secret.slice(0, 2)}***`;
28
+ return `${secret.slice(0, 4)}...${secret.slice(-4)}`;
29
+ }
30
+ function previewText(value, maxLength = 240) {
31
+ if (value.length <= maxLength)
32
+ return value;
33
+ return `${value.slice(0, maxLength)}...`;
34
+ }
22
35
  const LOG_LEVEL_PRIORITY = {
23
36
  none: 0,
24
37
  error: 1,
@@ -55,6 +68,92 @@ function createLogger(logLevel) {
55
68
  };
56
69
  }
57
70
  const PROMPT_CACHE_TTL_MS = 60000; // 1 minute
71
+ function getContentText(content) {
72
+ if (typeof content === "string")
73
+ return content;
74
+ if (!Array.isArray(content))
75
+ return "";
76
+ return content.map((part) => {
77
+ if (!part || typeof part !== "object")
78
+ return "";
79
+ if (typeof part.text === "string")
80
+ return part.text;
81
+ if (part.type === "tool-call" && typeof part.toolName === "string")
82
+ return `Called ${part.toolName}`;
83
+ if (part.type === "tool-result")
84
+ return "Received tool result";
85
+ return "";
86
+ }).filter(Boolean).join(" ");
87
+ }
88
+ /**
89
+ * Unwraps V2/V3 ToolResultOutput discriminated union to a displayable value.
90
+ * Stream ToolResult uses plain `result` (passthrough), while prompt ToolResultPart
91
+ * uses `output` with a discriminated union: text, json, error-text, error-json, content, execution-denied.
92
+ */
93
+ function extractOutputValue(raw) {
94
+ var _a;
95
+ if (raw == null)
96
+ return raw;
97
+ if (typeof raw !== 'object')
98
+ return raw;
99
+ const obj = raw;
100
+ if (typeof obj.type !== 'string')
101
+ return raw;
102
+ switch (obj.type) {
103
+ case 'text':
104
+ case 'json':
105
+ case 'error-text':
106
+ case 'error-json':
107
+ case 'content':
108
+ return obj.value;
109
+ case 'execution-denied':
110
+ return `Execution denied: ${(_a = obj.reason) !== null && _a !== void 0 ? _a : 'unknown'}`;
111
+ default:
112
+ return raw;
113
+ }
114
+ }
115
+ function buildTracePreviews(messages) {
116
+ const request = [...messages].reverse().find((message) => (message === null || message === void 0 ? void 0 : message.role) === "user");
117
+ const response = [...messages].reverse().find((message) => (message === null || message === void 0 ? void 0 : message.role) === "assistant");
118
+ return {
119
+ requestPreview: request ? getContentText(request.content).slice(0, 220) : "No request captured",
120
+ responsePreview: response ? getContentText(response.content).slice(0, 240) : "No response captured",
121
+ };
122
+ }
123
+ function buildTraceSpansFromMessages(messages) {
124
+ var _a, _b;
125
+ const resultMap = new Map();
126
+ for (const message of messages) {
127
+ if (!Array.isArray(message === null || message === void 0 ? void 0 : message.content))
128
+ continue;
129
+ for (const part of message.content) {
130
+ if ((part === null || part === void 0 ? void 0 : part.type) === "tool-result" && typeof part.toolCallId === "string") {
131
+ resultMap.set(part.toolCallId, (_a = part.result) !== null && _a !== void 0 ? _a : part.output);
132
+ }
133
+ }
134
+ }
135
+ const spans = [];
136
+ for (const message of messages) {
137
+ if (!Array.isArray(message === null || message === void 0 ? void 0 : message.content))
138
+ continue;
139
+ for (const part of message.content) {
140
+ if ((part === null || part === void 0 ? void 0 : part.type) === "tool-call" && typeof part.toolCallId === "string") {
141
+ const result = resultMap.get(part.toolCallId);
142
+ spans.push({
143
+ spanKey: part.toolCallId,
144
+ parentSpanKey: "root",
145
+ name: typeof part.toolName === "string" ? part.toolName : "tool",
146
+ spanType: "tool",
147
+ status: "completed",
148
+ inputPreview: JSON.stringify((_b = part.input) !== null && _b !== void 0 ? _b : {}).slice(0, 220),
149
+ outputPreview: result != null ? JSON.stringify(extractOutputValue(result)).slice(0, 220) : "No tool result captured",
150
+ toolName: typeof part.toolName === "string" ? part.toolName : undefined,
151
+ });
152
+ }
153
+ }
154
+ }
155
+ return spans;
156
+ }
58
157
  /**
59
158
  * Interpolate {{variable}} placeholders in a template string.
60
159
  * Unmatched variables are left as-is.
@@ -100,17 +199,43 @@ function createCognitiveLayer(config) {
100
199
  };
101
200
  // Prompt cache: slug → CachedPrompt
102
201
  const promptCache = new Map();
103
- const resolvePrompt = async (slug) => {
104
- const cached = promptCache.get(slug);
202
+ const resolvePrompt = async (slug, userId) => {
203
+ var _a;
204
+ const cacheKey = userId ? `${slug}:${userId}` : slug;
205
+ const cached = promptCache.get(cacheKey);
105
206
  if (cached && Date.now() - cached.fetchedAt < PROMPT_CACHE_TTL_MS) {
106
207
  logger.debug("Using cached prompt", { slug, version: cached.version });
107
208
  return cached;
108
209
  }
109
- const res = await fetch(`${baseUrl}/api/cognitive/prompt?slug=${encodeURIComponent(slug)}`, {
210
+ const url = new URL(`${baseUrl}/api/cognitive/prompt`);
211
+ url.searchParams.set("slug", slug);
212
+ if (userId)
213
+ url.searchParams.set("userId", userId);
214
+ logger.debug("Resolving prompt from backend", {
215
+ slug,
216
+ userId,
217
+ url: url.toString(),
218
+ baseUrl,
219
+ apiKeyHint: maskSecret(clConfig.apiKey),
220
+ });
221
+ const res = await fetch(url.toString(), {
110
222
  headers: { "Authorization": `Bearer ${clConfig.apiKey}` },
111
223
  });
224
+ logger.debug("Prompt resolve response received", {
225
+ slug,
226
+ userId,
227
+ status: res.status,
228
+ ok: res.ok,
229
+ contentType: res.headers.get("content-type"),
230
+ });
112
231
  if (!res.ok) {
113
232
  const body = await res.text();
233
+ logger.debug("Prompt resolve response body preview", {
234
+ slug,
235
+ userId,
236
+ status: res.status,
237
+ bodyPreview: previewText(body),
238
+ });
114
239
  throw new Error(`Failed to resolve prompt "${slug}": ${res.status} ${body}`);
115
240
  }
116
241
  const data = await res.json();
@@ -122,7 +247,15 @@ function createCognitiveLayer(config) {
122
247
  fetchedAt: Date.now(),
123
248
  gatewaySlug: data.gatewaySlug,
124
249
  };
125
- promptCache.set(slug, entry);
250
+ promptCache.set(cacheKey, entry);
251
+ logger.debug("Prompt resolved payload", {
252
+ slug,
253
+ resolvedSlug: entry.slug,
254
+ version: entry.version,
255
+ promptId: entry.promptId,
256
+ contentLength: entry.content.length,
257
+ gatewaySlug: (_a = entry.gatewaySlug) !== null && _a !== void 0 ? _a : null,
258
+ });
126
259
  logger.info("Prompt resolved", { slug, version: entry.version });
127
260
  return entry;
128
261
  };
@@ -189,9 +322,25 @@ function createCognitiveLayer(config) {
189
322
  if (systemPromptToAdd === undefined) {
190
323
  try {
191
324
  const url = `${baseUrl}/api/cognitive/snapshot?userId=${userId}`;
325
+ logger.debug("Fetching snapshot from backend", {
326
+ userId,
327
+ projectId,
328
+ sessionId,
329
+ url,
330
+ baseUrl,
331
+ apiKeyHint: maskSecret(clConfig.apiKey),
332
+ });
192
333
  const res = await fetch(url, {
193
334
  headers: { "Authorization": `Bearer ${clConfig.apiKey}` },
194
335
  });
336
+ logger.debug("Snapshot response received", {
337
+ userId,
338
+ projectId,
339
+ sessionId,
340
+ status: res.status,
341
+ ok: res.ok,
342
+ contentType: res.headers.get("content-type"),
343
+ });
195
344
  if (res.ok) {
196
345
  const data = await res.json();
197
346
  const systemBlock = data.systemBlock || "";
@@ -224,7 +373,15 @@ ${userContextBlock || "None"}
224
373
  });
225
374
  }
226
375
  else {
376
+ const body = await res.text();
227
377
  logger.warn("Snapshot fetch failed", { status: res.status });
378
+ logger.debug("Snapshot response body preview", {
379
+ userId,
380
+ projectId,
381
+ sessionId,
382
+ status: res.status,
383
+ bodyPreview: previewText(body),
384
+ });
228
385
  systemPromptToAdd = "";
229
386
  sessionSnapshots.set(sessionKey, systemPromptToAdd);
230
387
  }
@@ -250,7 +407,8 @@ ${userContextBlock || "None"}
250
407
  return Object.assign(Object.assign({}, nextParams), { prompt: messagesWithMemory });
251
408
  },
252
409
  async wrapGenerate({ doGenerate, params }) {
253
- var _a, _b;
410
+ var _a;
411
+ const startedAt = new Date();
254
412
  let result;
255
413
  try {
256
414
  result = await doGenerate();
@@ -261,28 +419,57 @@ ${userContextBlock || "None"}
261
419
  throw err;
262
420
  }
263
421
  if (isValidId(userId) && isValidId(sessionId)) {
422
+ const endedAt = new Date();
264
423
  const sessionKey = `${userId}:${projectId}:${sessionId}`;
265
424
  const promptMeta = sessionPromptMetadata.get(sessionKey);
266
- const messagesInput = params.messages || params.prompt || [];
267
- const resultMessages = (_b = result === null || result === void 0 ? void 0 : result.response) === null || _b === void 0 ? void 0 : _b.messages;
268
- const assistantMessage = (result === null || result === void 0 ? void 0 : result.text)
269
- ? [{ role: "assistant", content: [{ type: "text", text: result.text }] }]
425
+ const messagesInput = params.prompt || params.messages || [];
426
+ // Build assistant message from result.content (V2/V3 GenerateResult)
427
+ const resultContent = Array.isArray(result === null || result === void 0 ? void 0 : result.content) ? result.content : [];
428
+ const assistantParts = [];
429
+ for (const part of resultContent) {
430
+ if ((part === null || part === void 0 ? void 0 : part.type) === 'text') {
431
+ assistantParts.push({ type: 'text', text: part.text });
432
+ }
433
+ else if ((part === null || part === void 0 ? void 0 : part.type) === 'tool-call') {
434
+ assistantParts.push({
435
+ type: 'tool-call',
436
+ toolCallId: part.toolCallId,
437
+ toolName: part.toolName,
438
+ input: part.input,
439
+ });
440
+ }
441
+ else if ((part === null || part === void 0 ? void 0 : part.type) === 'tool-result') {
442
+ assistantParts.push({
443
+ type: 'tool-result',
444
+ toolCallId: part.toolCallId,
445
+ toolName: part.toolName,
446
+ result: part.result,
447
+ });
448
+ }
449
+ }
450
+ const assistantMessage = assistantParts.length > 0
451
+ ? [{ role: "assistant", content: assistantParts }]
270
452
  : [];
271
- const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
272
- ? resultMessages
273
- : [...messagesInput, ...assistantMessage];
274
- logConversation(Object.assign({ userId,
453
+ const finalMessages = [...messagesInput, ...assistantMessage];
454
+ const { requestPreview, responsePreview } = buildTracePreviews(finalMessages);
455
+ const spans = buildTraceSpansFromMessages(finalMessages);
456
+ logConversation(Object.assign(Object.assign({ userId,
275
457
  projectId,
276
458
  sessionId, messages: finalMessages, modelId, usage: result.usage }, (promptMeta && {
277
459
  promptSlug: promptMeta.promptSlug,
278
460
  promptVersion: promptMeta.promptVersion,
279
461
  promptId: promptMeta.promptId,
280
- }))).then(() => triggerProcessing(userId, projectId, sessionId));
462
+ })), { traceId: (0, crypto_1.randomUUID)(), requestPreview,
463
+ responsePreview, state: "completed", startedAt: startedAt.toISOString(), endedAt: endedAt.toISOString(), durationMs: endedAt.getTime() - startedAt.getTime(), metadata: {
464
+ appId: clConfig.appId,
465
+ }, spans })).then(() => triggerProcessing(userId, projectId, sessionId));
281
466
  }
282
467
  return result;
283
468
  },
284
469
  async wrapStream({ doStream, params }) {
285
470
  var _a;
471
+ const startedAt = new Date();
472
+ const traceId = (0, crypto_1.randomUUID)();
286
473
  let result;
287
474
  try {
288
475
  logger.debug("Starting doStream with params", JSON.stringify(params, null, 2));
@@ -297,13 +484,16 @@ ${userContextBlock || "None"}
297
484
  if (isValidId(userId) && isValidId(sessionId)) {
298
485
  const sessionKey = `${userId}:${projectId}:${sessionId}`;
299
486
  const promptMeta = sessionPromptMetadata.get(sessionKey);
300
- const messagesInput = params.messages || params.prompt || [];
487
+ const messagesInput = params.prompt || params.messages || [];
301
488
  const resultMessages = (_a = result === null || result === void 0 ? void 0 : result.response) === null || _a === void 0 ? void 0 : _a.messages;
302
489
  const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
303
490
  ? resultMessages
304
491
  : messagesInput;
305
492
  let streamUsage;
306
493
  let accumulatedText = '';
494
+ const toolCallInputs = new Map();
495
+ const completedToolCalls = [];
496
+ const completedToolResults = [];
307
497
  const originalStream = result.stream;
308
498
  const transformStream = new TransformStream({
309
499
  transform(chunk, controller) {
@@ -313,19 +503,72 @@ ${userContextBlock || "None"}
313
503
  if (chunk.type === 'finish' && chunk.usage) {
314
504
  streamUsage = chunk.usage;
315
505
  }
506
+ // Capture tool-call stream chunks (V2/V3 shared types)
507
+ if (chunk.type === 'tool-input-start') {
508
+ toolCallInputs.set(chunk.id, { toolName: chunk.toolName, chunks: [] });
509
+ }
510
+ if (chunk.type === 'tool-input-delta') {
511
+ const entry = toolCallInputs.get(chunk.id);
512
+ if (entry)
513
+ entry.chunks.push(chunk.delta);
514
+ }
515
+ if (chunk.type === 'tool-call') {
516
+ completedToolCalls.push({
517
+ type: 'tool-call',
518
+ toolCallId: chunk.toolCallId,
519
+ toolName: chunk.toolName,
520
+ input: chunk.input,
521
+ });
522
+ }
523
+ if (chunk.type === 'tool-result') {
524
+ completedToolResults.push({
525
+ type: 'tool-result',
526
+ toolCallId: chunk.toolCallId,
527
+ toolName: chunk.toolName,
528
+ result: chunk.result,
529
+ });
530
+ }
316
531
  controller.enqueue(chunk);
317
532
  },
318
- flush() {
319
- const allMessages = accumulatedText
320
- ? [...finalMessages, { role: "assistant", content: [{ type: "text", text: accumulatedText }] }]
533
+ async flush() {
534
+ const endedAt = new Date();
535
+ // Finalize any tool calls from incremental input chunks
536
+ for (const [id, entry] of toolCallInputs) {
537
+ // Only add if not already captured via a tool-call chunk
538
+ if (!completedToolCalls.some((tc) => tc.toolCallId === id)) {
539
+ completedToolCalls.push({
540
+ type: 'tool-call',
541
+ toolCallId: id,
542
+ toolName: entry.toolName,
543
+ input: entry.chunks.join(''),
544
+ });
545
+ }
546
+ }
547
+ const assistantParts = [];
548
+ if (accumulatedText)
549
+ assistantParts.push({ type: "text", text: accumulatedText });
550
+ for (const tc of completedToolCalls)
551
+ assistantParts.push(tc);
552
+ const allMessages = assistantParts.length > 0
553
+ ? [...finalMessages, { role: "assistant", content: assistantParts }]
321
554
  : finalMessages;
322
- logConversation(Object.assign({ userId,
555
+ if (completedToolResults.length > 0) {
556
+ allMessages.push({ role: "tool", content: completedToolResults });
557
+ }
558
+ const { requestPreview, responsePreview } = buildTracePreviews(allMessages);
559
+ const spans = buildTraceSpansFromMessages(allMessages);
560
+ await logConversation(Object.assign(Object.assign({ userId,
323
561
  projectId,
324
562
  sessionId, messages: allMessages, modelId, usage: streamUsage }, (promptMeta && {
325
563
  promptSlug: promptMeta.promptSlug,
326
564
  promptVersion: promptMeta.promptVersion,
327
565
  promptId: promptMeta.promptId,
328
- }))).then(() => triggerProcessing(userId, projectId, sessionId));
566
+ })), { traceId,
567
+ requestPreview,
568
+ responsePreview, state: "completed", startedAt: startedAt.toISOString(), endedAt: endedAt.toISOString(), durationMs: endedAt.getTime() - startedAt.getTime(), metadata: {
569
+ appId: clConfig.appId,
570
+ }, spans }));
571
+ triggerProcessing(userId, projectId, sessionId);
329
572
  }
330
573
  });
331
574
  result.stream = originalStream.pipeThrough(transformStream);
@@ -376,60 +619,90 @@ ${userContextBlock || "None"}
376
619
  middleware: buildMiddleware(userId, projectId, sessionId, modelId),
377
620
  });
378
621
  // Track session settings on the model for use in cl.streamText/cl.generateText
379
- if (isValidId(userId) && isValidId(sessionId)) {
380
- wrappedModel[SESSION_KEY] = { userId, projectId, sessionId };
622
+ // Always store if userId is valid — sessionId may be missing but userId is still
623
+ // needed for prompt resolution (e.g. A/B test assignment)
624
+ if (isValidId(userId)) {
625
+ wrappedModel[SESSION_KEY] = { userId, projectId, sessionId: isValidId(sessionId) ? sessionId : undefined };
381
626
  }
382
627
  return wrappedModel;
383
628
  };
384
629
  const clStreamText = async (options) => {
385
630
  const { prompt: promptConfig } = options, rest = __rest(options, ["prompt"]);
386
- // Resolve and interpolate prompt
387
- const resolved = await resolvePrompt(promptConfig.slug);
388
- const system = promptConfig.variables
389
- ? interpolateTemplate(resolved.content, promptConfig.variables)
390
- : resolved.content;
391
- // Store prompt metadata for the session (read by middleware during logging)
392
631
  const session = options.model[SESSION_KEY];
393
- if (session) {
394
- const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
395
- sessionPromptMetadata.set(sessionKey, {
396
- promptSlug: resolved.slug,
397
- promptVersion: resolved.version,
398
- promptId: resolved.promptId,
632
+ // Resolve and interpolate prompt (graceful fallback on failure)
633
+ let resolved = null;
634
+ try {
635
+ resolved = await resolvePrompt(promptConfig.slug, session === null || session === void 0 ? void 0 : session.userId);
636
+ }
637
+ catch (err) {
638
+ logger.warn(`Failed to resolve prompt "${promptConfig.slug}", streaming without system prompt.`, err);
639
+ }
640
+ let system;
641
+ if (resolved) {
642
+ system = promptConfig.variables
643
+ ? interpolateTemplate(resolved.content, promptConfig.variables)
644
+ : resolved.content;
645
+ // Store prompt metadata for the session (read by middleware during logging)
646
+ if (session === null || session === void 0 ? void 0 : session.sessionId) {
647
+ const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
648
+ sessionPromptMetadata.set(sessionKey, {
649
+ promptSlug: resolved.slug,
650
+ promptVersion: resolved.version,
651
+ promptId: resolved.promptId,
652
+ });
653
+ }
654
+ logger.info("cl.streamText called", {
655
+ slug: promptConfig.slug,
656
+ version: resolved.version,
657
+ systemLength: system.length,
399
658
  });
400
659
  }
401
- logger.info("cl.streamText called", {
402
- slug: promptConfig.slug,
403
- version: resolved.version,
404
- systemLength: system.length,
405
- });
406
- const model = resolveModel(options.model, resolved.gatewaySlug);
407
- return (0, ai_1.streamText)(Object.assign(Object.assign({}, rest), { model, system }));
660
+ else {
661
+ logger.info("cl.streamText called without resolved prompt", {
662
+ slug: promptConfig.slug,
663
+ });
664
+ }
665
+ const model = resolveModel(options.model, resolved === null || resolved === void 0 ? void 0 : resolved.gatewaySlug);
666
+ return (0, ai_1.streamText)(Object.assign(Object.assign(Object.assign({}, rest), { model }), (system && { system })));
408
667
  };
409
668
  const clGenerateText = async (options) => {
410
669
  const { prompt: promptConfig } = options, rest = __rest(options, ["prompt"]);
411
- // Resolve and interpolate prompt
412
- const resolved = await resolvePrompt(promptConfig.slug);
413
- const system = promptConfig.variables
414
- ? interpolateTemplate(resolved.content, promptConfig.variables)
415
- : resolved.content;
416
- // Store prompt metadata for the session (read by middleware during logging)
417
670
  const session = options.model[SESSION_KEY];
418
- if (session) {
419
- const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
420
- sessionPromptMetadata.set(sessionKey, {
421
- promptSlug: resolved.slug,
422
- promptVersion: resolved.version,
423
- promptId: resolved.promptId,
671
+ // Resolve and interpolate prompt (graceful fallback on failure)
672
+ let resolved = null;
673
+ try {
674
+ resolved = await resolvePrompt(promptConfig.slug, session === null || session === void 0 ? void 0 : session.userId);
675
+ }
676
+ catch (err) {
677
+ logger.warn(`Failed to resolve prompt "${promptConfig.slug}", generating without system prompt.`, err);
678
+ }
679
+ let system;
680
+ if (resolved) {
681
+ system = promptConfig.variables
682
+ ? interpolateTemplate(resolved.content, promptConfig.variables)
683
+ : resolved.content;
684
+ // Store prompt metadata for the session (read by middleware during logging)
685
+ if (session === null || session === void 0 ? void 0 : session.sessionId) {
686
+ const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
687
+ sessionPromptMetadata.set(sessionKey, {
688
+ promptSlug: resolved.slug,
689
+ promptVersion: resolved.version,
690
+ promptId: resolved.promptId,
691
+ });
692
+ }
693
+ logger.info("cl.generateText called", {
694
+ slug: promptConfig.slug,
695
+ version: resolved.version,
696
+ systemLength: system.length,
424
697
  });
425
698
  }
426
- logger.info("cl.generateText called", {
427
- slug: promptConfig.slug,
428
- version: resolved.version,
429
- systemLength: system.length,
430
- });
431
- const model = resolveModel(options.model, resolved.gatewaySlug);
432
- return (0, ai_1.generateText)(Object.assign(Object.assign({}, rest), { model, system }));
699
+ else {
700
+ logger.info("cl.generateText called without resolved prompt", {
701
+ slug: promptConfig.slug,
702
+ });
703
+ }
704
+ const model = resolveModel(options.model, resolved === null || resolved === void 0 ? void 0 : resolved.gatewaySlug);
705
+ return (0, ai_1.generateText)(Object.assign(Object.assign(Object.assign({}, rest), { model }), (system && { system })));
433
706
  };
434
707
  // Return the model wrapper function with streamText/generateText attached
435
708
  return Object.assign(clWrapper, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kognitivedev/vercel-ai-provider",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "publishConfig": {