@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/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
- import { wrapLanguageModel } from "ai";
2
- import { LanguageModelV2 } from "@ai-sdk/provider";
1
+ import {
2
+ wrapLanguageModel,
3
+ streamText as aiStreamText,
4
+ generateText as aiGenerateText,
5
+ type LanguageModel,
6
+ } from "ai";
3
7
 
4
8
  /**
5
9
  * Log levels for controlling verbosity of CognitiveLayer logging.
@@ -50,10 +54,10 @@ function createLogger(logLevel: LogLevel) {
50
54
  }
51
55
 
52
56
  export interface CognitiveLayerConfig {
53
- appId: string;
54
- defaultAgentId?: string;
57
+ apiKey: string;
58
+ appId?: string;
59
+ projectId?: string;
55
60
  baseUrl?: string;
56
- apiKey?: string;
57
61
  /**
58
62
  * Delay in milliseconds before triggering memory processing after a response.
59
63
  * Set to 0 to disable automatic processing.
@@ -62,21 +66,77 @@ export interface CognitiveLayerConfig {
62
66
  processDelayMs?: number;
63
67
  /**
64
68
  * Log level for controlling verbosity of CognitiveLayer logging.
65
- * - 'none': No logging
66
- * - 'error': Only errors
67
- * - 'warn': Errors and warnings
68
- * - 'info': Errors, warnings, and info messages
69
- * - 'debug': All messages including detailed snapshot data
70
69
  * Default: 'info'
71
70
  */
72
71
  logLevel?: LogLevel;
72
+ /**
73
+ * Factory for creating a provider that routes through a gateway URL.
74
+ */
75
+ providerFactory?: (baseURL: string) => (modelId: string) => LanguageModel;
73
76
  }
74
77
 
75
78
  export type CLModelWrapper = (
76
79
  modelId: string,
77
- settings?: { userId?: string; agentId?: string; sessionId?: string },
80
+ settings?: { userId?: string; projectId?: string; sessionId?: string },
78
81
  providerOptions?: Record<string, unknown>
79
- ) => LanguageModelV2;
82
+ ) => LanguageModel;
83
+
84
+ export interface PromptConfig {
85
+ slug: string;
86
+ variables?: Record<string, string>;
87
+ }
88
+
89
+ export type CLStreamTextOptions = Omit<Parameters<typeof aiStreamText>[0], 'system' | 'prompt'> & {
90
+ prompt: PromptConfig;
91
+ };
92
+
93
+ export type CLGenerateTextOptions = Omit<Parameters<typeof aiGenerateText>[0], 'system' | 'prompt'> & {
94
+ prompt: PromptConfig;
95
+ };
96
+
97
+ export interface LogConversationPayload {
98
+ userId: string;
99
+ projectId: string;
100
+ sessionId: string;
101
+ messages: any[];
102
+ memorySystemPrompt?: string;
103
+ modelId?: string;
104
+ usage?: Record<string, unknown>;
105
+ promptSlug?: string;
106
+ promptVersion?: number;
107
+ promptId?: string;
108
+ }
109
+
110
+ export type CognitiveLayer = CLModelWrapper & {
111
+ streamText: (options: CLStreamTextOptions) => Promise<ReturnType<typeof aiStreamText>>;
112
+ generateText: (options: CLGenerateTextOptions) => ReturnType<typeof aiGenerateText>;
113
+ resolvePrompt: (slug: string) => Promise<CachedPrompt>;
114
+ logConversation: (payload: LogConversationPayload) => Promise<void>;
115
+ triggerProcessing: (userId: string, projectId: string, sessionId: string) => void;
116
+ clearPromptCache: () => void;
117
+ clearSessionCache: (sessionKey?: string) => void;
118
+ };
119
+
120
+ // ─── Prompt Cache ────────────────────────────────────────────────────
121
+
122
+ export interface CachedPrompt {
123
+ promptId: string;
124
+ slug: string;
125
+ version: number;
126
+ content: string;
127
+ fetchedAt: number;
128
+ gatewaySlug?: string;
129
+ }
130
+
131
+ const PROMPT_CACHE_TTL_MS = 60_000; // 1 minute
132
+
133
+ /**
134
+ * Interpolate {{variable}} placeholders in a template string.
135
+ * Unmatched variables are left as-is.
136
+ */
137
+ function interpolateTemplate(content: string, variables: Record<string, string>): string {
138
+ return content.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] ?? `{{${key}}}`);
139
+ }
80
140
 
81
141
  // Session-scoped snapshot cache: sessionKey → formatted memory block
82
142
  const sessionSnapshots = new Map<string, string>();
@@ -84,6 +144,12 @@ const sessionSnapshots = new Map<string, string>();
84
144
  // Regex to detect if memory has already been injected
85
145
  const MEMORY_TAG_REGEX = /<MemoryContext>/i;
86
146
 
147
+ // Symbol-keyed property to track session settings on model objects
148
+ const SESSION_KEY = Symbol.for("cl:session");
149
+
150
+ // Session key → prompt metadata (populated by cl.streamText/cl.generateText, read by middleware)
151
+ const sessionPromptMetadata = new Map<string, { promptSlug: string; promptVersion: number; promptId: string }>();
152
+
87
153
  /**
88
154
  * Check if any system message already contains a <MemoryContext> block.
89
155
  */
@@ -105,7 +171,7 @@ function hasExistingMemoryInjection(messages: any[]): boolean {
105
171
  export function createCognitiveLayer(config: {
106
172
  provider: any;
107
173
  clConfig: CognitiveLayerConfig;
108
- }): CLModelWrapper {
174
+ }): CognitiveLayer {
109
175
  const { provider, clConfig } = config;
110
176
  const baseUrl = clConfig.baseUrl || "http://localhost:3001";
111
177
  // Default to 500ms delay to allow DB writes to settle
@@ -114,18 +180,48 @@ export function createCognitiveLayer(config: {
114
180
  const logLevel = clConfig.logLevel || 'info';
115
181
  const logger = createLogger(logLevel);
116
182
 
117
- const logConversation = async (payload: {
118
- userId: string;
119
- agentId: string;
120
- sessionId: string;
121
- messages: any[];
122
- memorySystemPrompt?: string;
123
- modelId?: string;
124
- }) => {
183
+ const authHeaders = {
184
+ "Content-Type": "application/json",
185
+ "Authorization": `Bearer ${clConfig.apiKey}`,
186
+ };
187
+
188
+ // Prompt cache: slug → CachedPrompt
189
+ const promptCache = new Map<string, CachedPrompt>();
190
+
191
+ const resolvePrompt = async (slug: string): Promise<CachedPrompt> => {
192
+ const cached = promptCache.get(slug);
193
+ if (cached && Date.now() - cached.fetchedAt < PROMPT_CACHE_TTL_MS) {
194
+ logger.debug("Using cached prompt", { slug, version: cached.version });
195
+ return cached;
196
+ }
197
+
198
+ const res = await fetch(`${baseUrl}/api/cognitive/prompt?slug=${encodeURIComponent(slug)}`, {
199
+ headers: { "Authorization": `Bearer ${clConfig.apiKey}` },
200
+ });
201
+ if (!res.ok) {
202
+ const body = await res.text();
203
+ throw new Error(`Failed to resolve prompt "${slug}": ${res.status} ${body}`);
204
+ }
205
+
206
+ const data = await res.json();
207
+ const entry: CachedPrompt = {
208
+ promptId: data.promptId,
209
+ slug: data.slug,
210
+ version: data.version,
211
+ content: data.content,
212
+ fetchedAt: Date.now(),
213
+ gatewaySlug: data.gatewaySlug,
214
+ };
215
+ promptCache.set(slug, entry);
216
+ logger.info("Prompt resolved", { slug, version: entry.version });
217
+ return entry;
218
+ };
219
+
220
+ const logConversation = async (payload: LogConversationPayload) => {
125
221
  try {
126
222
  await fetch(`${baseUrl}/api/cognitive/log`, {
127
223
  method: "POST",
128
- headers: { "Content-Type": "application/json" },
224
+ headers: authHeaders,
129
225
  body: JSON.stringify({
130
226
  ...payload,
131
227
  type: "conversation",
@@ -137,12 +233,12 @@ export function createCognitiveLayer(config: {
137
233
  }
138
234
  };
139
235
 
140
- const triggerProcessing = (userId: string, agentId: string, sessionId: string) => {
236
+ const triggerProcessing = (userId: string, projectId: string, sessionId: string) => {
141
237
  const run = () => {
142
238
  fetch(`${baseUrl}/api/cognitive/process`, {
143
239
  method: "POST",
144
- headers: { "Content-Type": "application/json" },
145
- body: JSON.stringify({ userId, agentId, sessionId }),
240
+ headers: authHeaders,
241
+ body: JSON.stringify({ userId, sessionId }),
146
242
  }).catch(e => logger.error("Process trigger failed", e));
147
243
  };
148
244
 
@@ -157,183 +253,358 @@ export function createCognitiveLayer(config: {
157
253
  params: any,
158
254
  incomingMessages: any[],
159
255
  memoryPrompt: string
160
- ): { nextParams: any; messages: any[]; mode: "overwrite-param" | "overwrite-first-system" | "prepend-system" } => {
256
+ ): { nextParams: any; messages: any[]; mode: "overwrite-first-system" | "prepend-system" } => {
161
257
  const nextParams = { ...params };
162
258
 
163
- // 1) If caller provided a top-level system prompt, overwrite it.
164
- if (nextParams.system) {
165
- nextParams.system = memoryPrompt;
166
- return { nextParams, messages: incomingMessages, mode: "overwrite-param" };
167
- }
168
-
169
- // 2) If first message is system, replace its content.
259
+ // 1) If first message is system, append memory to its content (without mutating original).
170
260
  if (incomingMessages.length > 0 && incomingMessages[0]?.role === "system") {
171
- const updated = [...incomingMessages];
172
- updated[0] = { ...updated[0], content: memoryPrompt };
261
+ const original = incomingMessages[0];
262
+ const updatedContent =
263
+ typeof original.content === "string"
264
+ ? original.content + "\n\n" + memoryPrompt
265
+ : memoryPrompt;
266
+ const updated = [{ ...original, content: updatedContent }, ...incomingMessages.slice(1)];
173
267
  return { nextParams, messages: updated, mode: "overwrite-first-system" };
174
268
  }
175
269
 
176
- // 3) Otherwise prepend a system message.
270
+ // 2) Otherwise prepend a system message.
177
271
  const updated = [{ role: "system", content: memoryPrompt }, ...incomingMessages];
178
272
  return { nextParams, messages: updated, mode: "prepend-system" };
179
273
  };
180
274
 
181
- return (
182
- modelId: string,
183
- settings?: { userId?: string; agentId?: string; sessionId?: string },
184
- providerOptions?: Record<string, unknown>
185
- ) => {
186
- // Pass provider options through to the underlying provider
187
- const model = (
188
- providerOptions
189
- ? provider(modelId, providerOptions)
190
- : provider(modelId)
191
- ) as LanguageModelV2;
192
- const userId = settings?.userId;
193
- const agentId = settings?.agentId || clConfig.defaultAgentId || "default";
194
- const sessionId = settings?.sessionId;
195
- const sessionMissing = !!userId && !sessionId;
196
-
197
- if (sessionMissing) {
198
- logger.warn("sessionId is required to log and process memories; skipping logging until provided.");
199
- }
275
+ const buildMiddleware = (userId: string | undefined, projectId: string, sessionId: string | undefined, modelId: string) => ({
276
+ specificationVersion: 'v3' as const,
277
+ async transformParams({ params }: { params: any }) {
278
+ if (!userId) return params;
200
279
 
201
- return wrapLanguageModel({
202
- model,
203
- middleware: {
204
- async transformParams({ params }) {
205
- if (!userId) return params;
280
+ const incomingMessages = Array.isArray((params as any).prompt)
281
+ ? (params as any).prompt
282
+ : [];
206
283
 
207
- const incomingMessages = Array.isArray((params as any).prompt)
208
- ? (params as any).prompt
209
- : [];
284
+ // 1) Check if memory is already injected in messages
285
+ if (hasExistingMemoryInjection(incomingMessages)) {
286
+ logger.debug("Memory already injected, skipping");
287
+ return params;
288
+ }
210
289
 
211
- // 1) Check if memory is already injected in messages
212
- if (hasExistingMemoryInjection(incomingMessages)) {
213
- logger.debug("Memory already injected, skipping");
214
- return params;
215
- }
290
+ // 2) Check session cache
291
+ const sessionKey = `${userId}:${projectId}:${sessionId || "default"}`;
292
+ let systemPromptToAdd = sessionSnapshots.get(sessionKey);
216
293
 
217
- // 2) Check session cache
218
- const sessionKey = `${userId}:${agentId}:${sessionId || "default"}`;
219
- let systemPromptToAdd = sessionSnapshots.get(sessionKey);
220
-
221
- // 3) Fetch snapshot only if not cached
222
- if (systemPromptToAdd === undefined) {
223
- try {
224
- const url = `${baseUrl}/api/cognitive/snapshot?userId=${userId}&agentId=${agentId}&appId=${clConfig.appId}`;
225
- const res = await fetch(url);
226
- if (res.ok) {
227
- const data = await res.json();
228
- const systemBlock = data.systemBlock || "";
229
- const userContextBlock = data.userContextBlock || "";
230
- systemPromptToAdd =
231
- systemBlock !== "" || userContextBlock !== ""
232
- ? `
294
+ // 3) Fetch snapshot only if not cached
295
+ if (systemPromptToAdd === undefined) {
296
+ try {
297
+ const url = `${baseUrl}/api/cognitive/snapshot?userId=${userId}`;
298
+ const res = await fetch(url, {
299
+ headers: { "Authorization": `Bearer ${clConfig.apiKey}` },
300
+ });
301
+ if (res.ok) {
302
+ const data = await res.json();
303
+ const systemBlock = data.systemBlock || "";
304
+ const userContextBlock = data.userContextBlock || "";
305
+ systemPromptToAdd =
306
+ systemBlock !== "" || userContextBlock !== ""
307
+ ? `
233
308
  <MemoryContext>
234
309
  Use the following memory to stay consistent. Prefer UserContext facts for answers; AgentHeuristics guide style, safety, and priorities.
235
310
  ${systemBlock || "None"}
236
311
  ${userContextBlock || "None"}
237
312
  </MemoryContext>
238
- `.trim()
239
- : "";
240
-
241
- // Cache the snapshot for this session
242
- sessionSnapshots.set(sessionKey, systemPromptToAdd);
243
-
244
- logger.info("Snapshot fetched and cached", {
245
- userId,
246
- agentId,
247
- sessionId,
248
- sessionKey,
249
- systemLen: systemBlock.length,
250
- userLen: userContextBlock.length,
251
- });
252
- // At debug level, log the full snapshot data
253
- logger.debug("Full snapshot data", {
254
- systemBlock,
255
- userContextBlock,
256
- rawData: data,
257
- });
258
- } else {
259
- logger.warn("Snapshot fetch failed", { status: res.status });
260
- systemPromptToAdd = "";
261
- sessionSnapshots.set(sessionKey, systemPromptToAdd);
262
- }
263
- } catch (e) {
264
- logger.warn("Failed to fetch snapshot", e);
265
- systemPromptToAdd = "";
266
- sessionSnapshots.set(sessionKey, systemPromptToAdd);
267
- }
313
+ `.trim()
314
+ : "";
315
+
316
+ // Cache the snapshot for this session
317
+ sessionSnapshots.set(sessionKey, systemPromptToAdd);
318
+
319
+ logger.info("Snapshot fetched and cached", {
320
+ userId,
321
+ projectId,
322
+ sessionId,
323
+ sessionKey,
324
+ systemLen: systemBlock.length,
325
+ userLen: userContextBlock.length,
326
+ });
327
+ // At debug level, log the full snapshot data
328
+ logger.debug("Full snapshot data", {
329
+ systemBlock,
330
+ userContextBlock,
331
+ rawData: data,
332
+ });
268
333
  } else {
269
- logger.debug("Using cached snapshot for session", { sessionKey });
334
+ logger.warn("Snapshot fetch failed", { status: res.status });
335
+ systemPromptToAdd = "";
336
+ sessionSnapshots.set(sessionKey, systemPromptToAdd);
270
337
  }
338
+ } catch (e) {
339
+ logger.warn("Failed to fetch snapshot", e);
340
+ systemPromptToAdd = "";
341
+ sessionSnapshots.set(sessionKey, systemPromptToAdd);
342
+ }
343
+ } else {
344
+ logger.debug("Using cached snapshot for session", { sessionKey });
345
+ }
271
346
 
272
- if (!systemPromptToAdd) {
273
- return { ...params, messages: incomingMessages };
274
- }
347
+ if (!systemPromptToAdd) {
348
+ return params;
349
+ }
275
350
 
276
- const { nextParams, messages: messagesWithMemory } = withMemorySystemPrompt(
277
- params,
278
- incomingMessages,
279
- systemPromptToAdd
280
- );
351
+ const { nextParams, messages: messagesWithMemory } = withMemorySystemPrompt(
352
+ params,
353
+ incomingMessages,
354
+ systemPromptToAdd
355
+ );
281
356
 
282
- logger.info("Injecting memory system prompt", {
283
- sessionKey,
284
- promptLength: systemPromptToAdd.length,
285
- });
286
- logger.debug("Injected prompt content", { systemPromptToAdd });
357
+ logger.info("Injecting memory system prompt", {
358
+ sessionKey,
359
+ promptLength: systemPromptToAdd.length,
360
+ });
361
+ logger.debug("Injected prompt content", { systemPromptToAdd });
287
362
 
288
- return { ...nextParams, prompt: messagesWithMemory };
289
- },
363
+ return { ...nextParams, prompt: messagesWithMemory };
364
+ },
290
365
 
291
- async wrapGenerate({ doGenerate, params }) {
292
- const result = await doGenerate();
366
+ async wrapGenerate({ doGenerate, params }: { doGenerate: any; params: any }) {
367
+ let result;
368
+ try {
369
+ result = await doGenerate();
370
+ } catch (err) {
371
+ logger.error("doGenerate failed", err);
372
+ logger.error("doGenerate params.prompt", JSON.stringify((params as any).prompt?.map((m: any) => ({ role: m.role, contentType: typeof m.content, contentLength: Array.isArray(m.content) ? m.content.length : undefined })), null, 2));
373
+ throw err;
374
+ }
293
375
 
294
- if (userId && sessionId) {
295
- const messagesInput = (params as any).messages || (params as any).prompt || [];
296
- const resultMessages = (result as any)?.response?.messages;
297
- const assistantMessage = (result as any)?.text
298
- ? [{ role: "assistant", content: [{ type: "text", text: (result as any).text }] }]
299
- : [];
300
- const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
301
- ? resultMessages
302
- : [...messagesInput, ...assistantMessage];
376
+ if (userId && sessionId) {
377
+ const sessionKey = `${userId}:${projectId}:${sessionId}`;
378
+ const promptMeta = sessionPromptMetadata.get(sessionKey);
379
+
380
+ const messagesInput = (params as any).messages || (params as any).prompt || [];
381
+ const resultMessages = (result as any)?.response?.messages;
382
+ const assistantMessage = (result as any)?.text
383
+ ? [{ role: "assistant", content: [{ type: "text", text: (result as any).text }] }]
384
+ : [];
385
+ const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
386
+ ? resultMessages
387
+ : [...messagesInput, ...assistantMessage];
388
+
389
+ logConversation({
390
+ userId,
391
+ projectId,
392
+ sessionId,
393
+ messages: finalMessages,
394
+ modelId,
395
+ usage: result.usage,
396
+ ...(promptMeta && {
397
+ promptSlug: promptMeta.promptSlug,
398
+ promptVersion: promptMeta.promptVersion,
399
+ promptId: promptMeta.promptId,
400
+ }),
401
+ }).then(() => triggerProcessing(userId, projectId, sessionId));
402
+ }
303
403
 
304
- logConversation({
305
- userId,
306
- agentId,
307
- sessionId,
308
- messages: finalMessages,
309
- modelId,
310
- }).then(() => triggerProcessing(userId, agentId, sessionId));
311
- }
404
+ return result;
405
+ },
406
+ async wrapStream({ doStream, params }: { doStream: any; params: any }) {
407
+ let result;
408
+ try {
409
+ logger.debug("Starting doStream with params", JSON.stringify(params, null, 2));
410
+ result = await doStream();
411
+ } catch (err) {
412
+ console.log((err as TypeError).cause)
413
+ console.log((err as TypeError).stack)
414
+ logger.error("doStream failed", err);
415
+
416
+ throw err;
417
+ }
418
+
419
+ if (userId && sessionId) {
420
+ const sessionKey = `${userId}:${projectId}:${sessionId}`;
421
+ const promptMeta = sessionPromptMetadata.get(sessionKey);
312
422
 
313
- return result;
314
- },
315
- async wrapStream({ doStream, params }) {
316
- const result = await doStream();
423
+ const messagesInput = (params as any).messages || (params as any).prompt || [];
424
+ const resultMessages = (result as any)?.response?.messages;
425
+ const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
426
+ ? resultMessages
427
+ : messagesInput;
317
428
 
318
- if (userId && sessionId) {
319
- const messagesInput = (params as any).messages || (params as any).prompt || [];
320
- const resultMessages = (result as any)?.response?.messages;
321
- const finalMessages = Array.isArray(resultMessages) && resultMessages.length > 0
322
- ? resultMessages
323
- : messagesInput;
429
+ let streamUsage: Record<string, unknown> | undefined;
430
+ let accumulatedText = '';
431
+
432
+ const originalStream = result.stream;
433
+ const transformStream = new TransformStream({
434
+ transform(chunk, controller) {
435
+ if (chunk.type === 'text-delta') {
436
+ accumulatedText += chunk.delta;
437
+ }
438
+ if (chunk.type === 'finish' && chunk.usage) {
439
+ streamUsage = chunk.usage;
440
+ }
441
+ controller.enqueue(chunk);
442
+ },
443
+ flush() {
444
+ const allMessages = accumulatedText
445
+ ? [...finalMessages, { role: "assistant", content: [{ type: "text", text: accumulatedText }] }]
446
+ : finalMessages;
324
447
 
325
448
  logConversation({
326
449
  userId,
327
- agentId,
450
+ projectId,
328
451
  sessionId,
329
- messages: finalMessages,
452
+ messages: allMessages,
330
453
  modelId,
331
- }).then(() => triggerProcessing(userId, agentId, sessionId));
454
+ usage: streamUsage,
455
+ ...(promptMeta && {
456
+ promptSlug: promptMeta.promptSlug,
457
+ promptVersion: promptMeta.promptVersion,
458
+ promptId: promptMeta.promptId,
459
+ }),
460
+ }).then(() => triggerProcessing(userId, projectId, sessionId));
332
461
  }
462
+ });
333
463
 
334
- return result;
335
- }
464
+ result.stream = originalStream.pipeThrough(transformStream);
465
+ }
466
+
467
+ return result;
468
+ }
469
+ });
470
+
471
+ const resolveModel = (originalModel: LanguageModel, gatewaySlug?: string): LanguageModel => {
472
+ if (!gatewaySlug || !clConfig.providerFactory) {
473
+ if (gatewaySlug && !clConfig.providerFactory) {
474
+ logger.warn("Gateway config found but no providerFactory provided");
336
475
  }
476
+ return originalModel;
477
+ }
478
+
479
+ try {
480
+ const gatewayURL = `${baseUrl}/api/cognitive/gateway/${gatewaySlug}`;
481
+ const modelId = (originalModel as any).modelId || 'default';
482
+ const rawModel = clConfig.providerFactory(gatewayURL)(modelId);
483
+
484
+ const session = (originalModel as any)[SESSION_KEY];
485
+ if (!session) return rawModel as LanguageModel;
486
+
487
+ const wrapped = wrapLanguageModel({
488
+ model: rawModel as any,
489
+ middleware: buildMiddleware(session.userId, session.projectId, session.sessionId, modelId) as any,
490
+ });
491
+ (wrapped as any)[SESSION_KEY] = session;
492
+ return wrapped as LanguageModel;
493
+ } catch (err) {
494
+ logger.error("Failed to create gateway model, falling back to original", err);
495
+ return originalModel;
496
+ }
497
+ };
498
+
499
+ const clWrapper: CLModelWrapper = (
500
+ modelId: string,
501
+ settings?: { userId?: string; projectId?: string; sessionId?: string },
502
+ providerOptions?: Record<string, unknown>
503
+ ) => {
504
+ // Pass provider options through to the underlying provider
505
+ const model = (
506
+ providerOptions
507
+ ? provider(modelId, providerOptions)
508
+ : provider(modelId)
509
+ ) as LanguageModel;
510
+ const userId = settings?.userId;
511
+ const projectId = settings?.projectId || clConfig.projectId || "default";
512
+ const sessionId = settings?.sessionId;
513
+ const sessionMissing = !!userId && !sessionId;
514
+
515
+ if (sessionMissing) {
516
+ logger.warn("sessionId is required to log and process memories; skipping logging until provided.");
517
+ }
518
+
519
+ const wrappedModel = wrapLanguageModel({
520
+ model: model as any,
521
+ middleware: buildMiddleware(userId, projectId, sessionId, modelId) as any,
522
+ }) as LanguageModel;
523
+
524
+ // Track session settings on the model for use in cl.streamText/cl.generateText
525
+ if (userId && sessionId) {
526
+ (wrappedModel as any)[SESSION_KEY] = { userId, projectId, sessionId };
527
+ }
528
+
529
+ return wrappedModel;
530
+ };
531
+
532
+ const clStreamText = async (options: CLStreamTextOptions) => {
533
+ const { prompt: promptConfig, ...rest } = options;
534
+
535
+ // Resolve and interpolate prompt
536
+ const resolved = await resolvePrompt(promptConfig.slug);
537
+ const system = promptConfig.variables
538
+ ? interpolateTemplate(resolved.content, promptConfig.variables)
539
+ : resolved.content;
540
+
541
+ // Store prompt metadata for the session (read by middleware during logging)
542
+ const session = (options.model as any)[SESSION_KEY] as { userId: string; projectId: string; sessionId: string } | undefined;
543
+ if (session) {
544
+ const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
545
+ sessionPromptMetadata.set(sessionKey, {
546
+ promptSlug: resolved.slug,
547
+ promptVersion: resolved.version,
548
+ promptId: resolved.promptId,
549
+ });
550
+ }
551
+
552
+ logger.info("cl.streamText called", {
553
+ slug: promptConfig.slug,
554
+ version: resolved.version,
555
+ systemLength: system.length,
556
+ });
557
+
558
+ const model = resolveModel(options.model, resolved.gatewaySlug);
559
+ return aiStreamText({ ...rest, model, system } as any);
560
+ };
561
+
562
+ const clGenerateText = async (options: CLGenerateTextOptions) => {
563
+ const { prompt: promptConfig, ...rest } = options;
564
+
565
+ // Resolve and interpolate prompt
566
+ const resolved = await resolvePrompt(promptConfig.slug);
567
+ const system = promptConfig.variables
568
+ ? interpolateTemplate(resolved.content, promptConfig.variables)
569
+ : resolved.content;
570
+
571
+ // Store prompt metadata for the session (read by middleware during logging)
572
+ const session = (options.model as any)[SESSION_KEY] as { userId: string; projectId: string; sessionId: string } | undefined;
573
+ if (session) {
574
+ const sessionKey = `${session.userId}:${session.projectId}:${session.sessionId}`;
575
+ sessionPromptMetadata.set(sessionKey, {
576
+ promptSlug: resolved.slug,
577
+ promptVersion: resolved.version,
578
+ promptId: resolved.promptId,
579
+ });
580
+ }
581
+
582
+ logger.info("cl.generateText called", {
583
+ slug: promptConfig.slug,
584
+ version: resolved.version,
585
+ systemLength: system.length,
337
586
  });
587
+
588
+ const model = resolveModel(options.model, resolved.gatewaySlug);
589
+ return aiGenerateText({ ...rest, model, system } as any);
338
590
  };
591
+
592
+ // Return the model wrapper function with streamText/generateText attached
593
+ return Object.assign(clWrapper, {
594
+ streamText: clStreamText,
595
+ generateText: clGenerateText,
596
+ resolvePrompt,
597
+ logConversation,
598
+ triggerProcessing,
599
+ clearPromptCache: () => promptCache.clear(),
600
+ clearSessionCache: (sessionKey?: string) => {
601
+ if (sessionKey) {
602
+ sessionSnapshots.delete(sessionKey);
603
+ sessionPromptMetadata.delete(sessionKey);
604
+ } else {
605
+ sessionSnapshots.clear();
606
+ sessionPromptMetadata.clear();
607
+ }
608
+ },
609
+ });
339
610
  }