@kognitivedev/vercel-ai-provider 0.1.8 → 0.2.0
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/README.md +112 -8
- package/dist/__tests__/template.test.d.ts +1 -0
- package/dist/__tests__/template.test.js +112 -0
- package/dist/__tests__/wrap-stream-logging.test.js +92 -0
- package/dist/index.d.ts +23 -1
- package/dist/index.js +257 -22
- package/dist/template.d.ts +2 -0
- package/dist/template.js +13 -0
- package/package.json +4 -1
- package/src/__tests__/template.test.ts +161 -0
- package/src/__tests__/wrap-stream-logging.test.ts +104 -0
- package/src/handlebars.d.ts +4 -0
- package/src/index.ts +307 -20
- package/src/template.ts +10 -0
package/README.md
CHANGED
|
@@ -151,7 +151,7 @@ Used with `cl.streamText()` and `cl.generateText()` to reference a managed promp
|
|
|
151
151
|
```typescript
|
|
152
152
|
interface PromptConfig {
|
|
153
153
|
slug: string;
|
|
154
|
-
variables?: Record<string, string>;
|
|
154
|
+
variables?: Record<string, string | boolean>;
|
|
155
155
|
}
|
|
156
156
|
```
|
|
157
157
|
|
|
@@ -265,7 +265,69 @@ const { text } = await generateText({
|
|
|
265
265
|
|
|
266
266
|
Use `cl.streamText()` and `cl.generateText()` to resolve prompts by slug from the backend. Prompts are fetched from the `/api/cognitive/prompt` endpoint and cached for 60 seconds.
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
Prompt templates use [Handlebars](https://handlebarsjs.com/) syntax, giving you variable interpolation and conditional blocks. This means prompts can be fully managed from the dashboard without any string concatenation in application code.
|
|
269
|
+
|
|
270
|
+
#### Template Syntax
|
|
271
|
+
|
|
272
|
+
**Variable interpolation** — insert a value by name:
|
|
273
|
+
|
|
274
|
+
```handlebars
|
|
275
|
+
Hello {{userName}}, welcome to {{companyName}}.
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Conditional blocks** — include content only when a variable is truthy:
|
|
279
|
+
|
|
280
|
+
```handlebars
|
|
281
|
+
{{#if hasImages}}
|
|
282
|
+
The user has attached images. Please analyze them.
|
|
283
|
+
{{/if}}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**If / else** — provide a fallback when the condition is falsy:
|
|
287
|
+
|
|
288
|
+
```handlebars
|
|
289
|
+
{{#if isPremium}}
|
|
290
|
+
You have access to all features.
|
|
291
|
+
{{else}}
|
|
292
|
+
Upgrade to premium for full access.
|
|
293
|
+
{{/if}}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Unless** — inverse of `if`, includes the block when the variable is falsy:
|
|
297
|
+
|
|
298
|
+
```handlebars
|
|
299
|
+
{{#unless hasHistory}}
|
|
300
|
+
This is a new user. Introduce yourself.
|
|
301
|
+
{{/unless}}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Nesting** — conditionals can be nested arbitrarily:
|
|
305
|
+
|
|
306
|
+
```handlebars
|
|
307
|
+
{{#if hasAttachments}}
|
|
308
|
+
Attachments provided.
|
|
309
|
+
{{#if hasImages}}
|
|
310
|
+
Includes images for visual analysis.
|
|
311
|
+
{{/if}}
|
|
312
|
+
{{/if}}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### Variable Types
|
|
316
|
+
|
|
317
|
+
Variables can be `string` or `boolean`:
|
|
318
|
+
|
|
319
|
+
| Type | Truthy | Falsy |
|
|
320
|
+
|------|--------|-------|
|
|
321
|
+
| `string` | Any non-empty string | `""` (empty string) |
|
|
322
|
+
| `boolean` | `true` | `false` |
|
|
323
|
+
|
|
324
|
+
Undefined variables render as empty string in interpolation and are treated as falsy in conditionals.
|
|
325
|
+
|
|
326
|
+
> **Note:** Content is not HTML-escaped — prompt text passes through as-is, which is the correct behavior for LLM prompts.
|
|
327
|
+
|
|
328
|
+
#### Usage with `cl.streamText()` / `cl.generateText()`
|
|
329
|
+
|
|
330
|
+
Pass variables in the `prompt.variables` field:
|
|
269
331
|
|
|
270
332
|
```typescript
|
|
271
333
|
// Stream with a managed prompt
|
|
@@ -275,13 +337,15 @@ const result = await cl.streamText({
|
|
|
275
337
|
sessionId: "session-abc"
|
|
276
338
|
}),
|
|
277
339
|
prompt: {
|
|
278
|
-
slug: "
|
|
340
|
+
slug: "fitness-coach",
|
|
279
341
|
variables: {
|
|
280
|
-
|
|
281
|
-
|
|
342
|
+
userName: "Sarah",
|
|
343
|
+
fitnessGoal: "Build muscle",
|
|
344
|
+
hasImages: true,
|
|
345
|
+
hasAttachments: false
|
|
282
346
|
}
|
|
283
347
|
},
|
|
284
|
-
messages: [{ role: "user", content: "
|
|
348
|
+
messages: [{ role: "user", content: "Check my squat form" }]
|
|
285
349
|
});
|
|
286
350
|
|
|
287
351
|
for await (const chunk of result.textStream) {
|
|
@@ -290,19 +354,59 @@ for await (const chunk of result.textStream) {
|
|
|
290
354
|
```
|
|
291
355
|
|
|
292
356
|
```typescript
|
|
293
|
-
// Generate with a managed prompt
|
|
357
|
+
// Generate with a managed prompt (string-only variables work too)
|
|
294
358
|
const { text } = await cl.generateText({
|
|
295
359
|
model: cl("gpt-4o", {
|
|
296
360
|
userId: "user-123",
|
|
297
361
|
sessionId: "session-abc"
|
|
298
362
|
}),
|
|
299
363
|
prompt: {
|
|
300
|
-
slug: "summarizer"
|
|
364
|
+
slug: "summarizer",
|
|
365
|
+
variables: {
|
|
366
|
+
language: "English"
|
|
367
|
+
}
|
|
301
368
|
},
|
|
302
369
|
messages: [{ role: "user", content: "Summarize this document..." }]
|
|
303
370
|
});
|
|
304
371
|
```
|
|
305
372
|
|
|
373
|
+
#### Standalone `renderTemplate()`
|
|
374
|
+
|
|
375
|
+
The template engine is also exported directly for use outside of prompt management:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { renderTemplate } from "@kognitivedev/vercel-ai-provider";
|
|
379
|
+
|
|
380
|
+
const result = renderTemplate(
|
|
381
|
+
"Hello {{name}}! {{#if isVip}}Welcome back, VIP.{{/if}}",
|
|
382
|
+
{ name: "Alice", isVip: true }
|
|
383
|
+
);
|
|
384
|
+
// → "Hello Alice! Welcome back, VIP."
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Full Example Template
|
|
388
|
+
|
|
389
|
+
A prompt template stored in the dashboard might look like:
|
|
390
|
+
|
|
391
|
+
```handlebars
|
|
392
|
+
You are a fitness coach AI assistant.
|
|
393
|
+
|
|
394
|
+
User: {{userName}}
|
|
395
|
+
Goal: {{fitnessGoal}}
|
|
396
|
+
|
|
397
|
+
{{#if hasImages}}
|
|
398
|
+
The user has attached images for form analysis. Please review them carefully.
|
|
399
|
+
{{/if}}
|
|
400
|
+
{{#if hasAttachments}}
|
|
401
|
+
Additional documents have been provided for context.
|
|
402
|
+
{{/if}}
|
|
403
|
+
{{#unless hasHistory}}
|
|
404
|
+
This is a new user with no prior conversation history. Introduce yourself.
|
|
405
|
+
{{/unless}}
|
|
406
|
+
|
|
407
|
+
Please provide personalized advice.
|
|
408
|
+
```
|
|
409
|
+
|
|
306
410
|
### Gateway Routing
|
|
307
411
|
|
|
308
412
|
When a prompt has a `gatewaySlug` configured on the backend, requests are automatically routed through the gateway endpoint at `{baseUrl}/api/cognitive/gateway/{gatewaySlug}`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const template_1 = require("../template");
|
|
5
|
+
(0, vitest_1.describe)("renderTemplate", () => {
|
|
6
|
+
(0, vitest_1.describe)("variable interpolation", () => {
|
|
7
|
+
(0, vitest_1.it)("replaces a single variable", () => {
|
|
8
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("Hello {{name}}", { name: "Alice" })).toBe("Hello Alice");
|
|
9
|
+
});
|
|
10
|
+
(0, vitest_1.it)("replaces multiple variables", () => {
|
|
11
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("{{greeting}}, {{name}}!", { greeting: "Hi", name: "Bob" })).toBe("Hi, Bob!");
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)("renders undefined variables as empty string", () => {
|
|
14
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("Hello {{name}}", {})).toBe("Hello ");
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)("does not HTML-escape content", () => {
|
|
17
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("{{content}}", { content: "<b>bold</b> & \"quoted\"" })).toBe("<b>bold</b> & \"quoted\"");
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
(0, vitest_1.describe)("{{#if}} / {{/if}}", () => {
|
|
21
|
+
(0, vitest_1.it)("includes block when variable is truthy string", () => {
|
|
22
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("start{{#if show}} visible{{/if}} end", { show: "yes" })).toBe("start visible end");
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.it)("includes block when variable is true", () => {
|
|
25
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("start{{#if show}} visible{{/if}} end", { show: true })).toBe("start visible end");
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("excludes block when variable is false", () => {
|
|
28
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("start{{#if show}} visible{{/if}} end", { show: false })).toBe("start end");
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.it)("excludes block when variable is undefined", () => {
|
|
31
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("start{{#if show}} visible{{/if}} end", {})).toBe("start end");
|
|
32
|
+
});
|
|
33
|
+
(0, vitest_1.it)("excludes block when variable is empty string", () => {
|
|
34
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("start{{#if show}} visible{{/if}} end", { show: "" })).toBe("start end");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
(0, vitest_1.describe)("{{#unless}} / {{/unless}}", () => {
|
|
38
|
+
(0, vitest_1.it)("includes block when variable is falsy", () => {
|
|
39
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("{{#unless premium}}Free tier{{/unless}}", { premium: false })).toBe("Free tier");
|
|
40
|
+
});
|
|
41
|
+
(0, vitest_1.it)("excludes block when variable is truthy", () => {
|
|
42
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("{{#unless premium}}Free tier{{/unless}}", { premium: true })).toBe("");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.describe)("{{else}} branches", () => {
|
|
46
|
+
(0, vitest_1.it)("renders else branch when if condition is false", () => {
|
|
47
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("{{#if vip}}VIP access{{else}}Standard access{{/if}}", { vip: false })).toBe("Standard access");
|
|
48
|
+
});
|
|
49
|
+
(0, vitest_1.it)("renders if branch when condition is true", () => {
|
|
50
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("{{#if vip}}VIP access{{else}}Standard access{{/if}}", { vip: true })).toBe("VIP access");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.describe)("nested conditionals", () => {
|
|
54
|
+
(0, vitest_1.it)("handles nested if blocks", () => {
|
|
55
|
+
const template = "{{#if a}}A{{#if b}}-B{{/if}}{{/if}}";
|
|
56
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)(template, { a: true, b: true })).toBe("A-B");
|
|
57
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)(template, { a: true, b: false })).toBe("A");
|
|
58
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)(template, { a: false, b: true })).toBe("");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.describe)("variables inside conditionals", () => {
|
|
62
|
+
(0, vitest_1.it)("interpolates variables within conditional blocks", () => {
|
|
63
|
+
(0, vitest_1.expect)((0, template_1.renderTemplate)("{{#if hasName}}Name: {{name}}{{/if}}", { hasName: true, name: "Alice" })).toBe("Name: Alice");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.describe)("real-world fitness app template", () => {
|
|
67
|
+
const template = `You are a fitness coach AI assistant.
|
|
68
|
+
|
|
69
|
+
User: {{userName}}
|
|
70
|
+
Goal: {{fitnessGoal}}
|
|
71
|
+
|
|
72
|
+
{{#if hasImages}}
|
|
73
|
+
The user has attached images for form analysis. Please review them carefully.
|
|
74
|
+
{{/if}}
|
|
75
|
+
{{#if hasAttachments}}
|
|
76
|
+
Additional documents have been provided for context.
|
|
77
|
+
{{/if}}
|
|
78
|
+
{{#unless hasHistory}}
|
|
79
|
+
This is a new user with no prior conversation history. Introduce yourself.
|
|
80
|
+
{{/unless}}
|
|
81
|
+
|
|
82
|
+
Please provide personalized advice.`;
|
|
83
|
+
(0, vitest_1.it)("renders with all flags true", () => {
|
|
84
|
+
const result = (0, template_1.renderTemplate)(template, {
|
|
85
|
+
userName: "Sarah",
|
|
86
|
+
fitnessGoal: "Build muscle",
|
|
87
|
+
hasImages: true,
|
|
88
|
+
hasAttachments: true,
|
|
89
|
+
hasHistory: true,
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.expect)(result).toContain("User: Sarah");
|
|
92
|
+
(0, vitest_1.expect)(result).toContain("Goal: Build muscle");
|
|
93
|
+
(0, vitest_1.expect)(result).toContain("attached images for form analysis");
|
|
94
|
+
(0, vitest_1.expect)(result).toContain("Additional documents");
|
|
95
|
+
(0, vitest_1.expect)(result).not.toContain("new user with no prior");
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.it)("renders with all flags false", () => {
|
|
98
|
+
const result = (0, template_1.renderTemplate)(template, {
|
|
99
|
+
userName: "Tom",
|
|
100
|
+
fitnessGoal: "Lose weight",
|
|
101
|
+
hasImages: false,
|
|
102
|
+
hasAttachments: false,
|
|
103
|
+
hasHistory: false,
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.expect)(result).toContain("User: Tom");
|
|
106
|
+
(0, vitest_1.expect)(result).toContain("Goal: Lose weight");
|
|
107
|
+
(0, vitest_1.expect)(result).not.toContain("attached images");
|
|
108
|
+
(0, vitest_1.expect)(result).not.toContain("Additional documents");
|
|
109
|
+
(0, vitest_1.expect)(result).toContain("new user with no prior");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { streamText as aiStreamText, generateText as aiGenerateText, type LanguageModel } from "ai";
|
|
2
|
+
export { renderTemplate, type TemplateVariables } from "./template";
|
|
2
3
|
/**
|
|
3
4
|
* Log levels for controlling verbosity of CognitiveLayer logging.
|
|
4
5
|
* - 'none': No logging
|
|
@@ -36,7 +37,7 @@ export type CLModelWrapper = (modelId: string, settings?: {
|
|
|
36
37
|
}, providerOptions?: Record<string, unknown>) => LanguageModel;
|
|
37
38
|
export interface PromptConfig {
|
|
38
39
|
slug: string;
|
|
39
|
-
variables?: Record<string, string>;
|
|
40
|
+
variables?: Record<string, string | boolean>;
|
|
40
41
|
}
|
|
41
42
|
export type CLStreamTextOptions = Omit<Parameters<typeof aiStreamText>[0], 'system' | 'prompt'> & {
|
|
42
43
|
prompt: PromptConfig;
|
|
@@ -55,6 +56,27 @@ export interface LogConversationPayload {
|
|
|
55
56
|
promptSlug?: string;
|
|
56
57
|
promptVersion?: number;
|
|
57
58
|
promptId?: string;
|
|
59
|
+
traceId?: string;
|
|
60
|
+
parentSpanId?: string;
|
|
61
|
+
requestPreview?: string;
|
|
62
|
+
responsePreview?: string;
|
|
63
|
+
state?: "active" | "completed" | "error";
|
|
64
|
+
startedAt?: string;
|
|
65
|
+
endedAt?: string;
|
|
66
|
+
durationMs?: number;
|
|
67
|
+
metadata?: Record<string, unknown>;
|
|
68
|
+
spans?: Array<{
|
|
69
|
+
spanKey: string;
|
|
70
|
+
parentSpanKey?: string;
|
|
71
|
+
name: string;
|
|
72
|
+
spanType: string;
|
|
73
|
+
status?: "active" | "completed" | "error";
|
|
74
|
+
inputPreview?: string;
|
|
75
|
+
outputPreview?: string;
|
|
76
|
+
toolName?: string;
|
|
77
|
+
errorMessage?: string;
|
|
78
|
+
metadata?: Record<string, unknown>;
|
|
79
|
+
}>;
|
|
58
80
|
}
|
|
59
81
|
export type CognitiveLayer = CLModelWrapper & {
|
|
60
82
|
streamText: (options: CLStreamTextOptions) => Promise<ReturnType<typeof aiStreamText>>;
|