@kognitivedev/vercel-ai-provider 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +0 -11
- package/dist/__tests__/cognitive-layer-extra.test.d.ts +1 -0
- package/dist/__tests__/cognitive-layer-extra.test.js +209 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +33 -11
- package/package.json +3 -3
- package/src/__tests__/cognitive-layer-extra.test.ts +270 -0
- package/src/index.ts +49 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @kognitivedev/vercel-ai-provider
|
|
2
2
|
|
|
3
|
+
## 0.2.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- release
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @kognitivedev/prompthub@0.1.5
|
|
11
|
+
- @kognitivedev/shared@0.2.7
|
|
12
|
+
|
|
13
|
+
## 0.2.6
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- release
|
|
18
|
+
|
|
19
|
+
- Updated dependencies []:
|
|
20
|
+
- @kognitivedev/prompthub@0.1.4
|
|
21
|
+
- @kognitivedev/shared@0.2.6
|
|
22
|
+
|
|
3
23
|
## 0.2.5
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -109,7 +109,6 @@ type CognitiveLayer = CLModelWrapper & {
|
|
|
109
109
|
resolvePrompt: (slug: string) => Promise<CachedPrompt>;
|
|
110
110
|
logConversation: (payload: LogConversationPayload) => Promise<void>;
|
|
111
111
|
triggerProcessing: (userId: string, projectId: string, sessionId: string) => void;
|
|
112
|
-
clearPromptCache: () => void;
|
|
113
112
|
clearSessionCache: (sessionKey?: string) => void;
|
|
114
113
|
};
|
|
115
114
|
```
|
|
@@ -503,16 +502,6 @@ cl.triggerProcessing("user-123", "my-project", "session-abc");
|
|
|
503
502
|
|
|
504
503
|
---
|
|
505
504
|
|
|
506
|
-
### `cl.clearPromptCache()`
|
|
507
|
-
|
|
508
|
-
Clears all cached prompts, forcing the next `resolvePrompt` call to fetch fresh data from the backend.
|
|
509
|
-
|
|
510
|
-
```typescript
|
|
511
|
-
cl.clearPromptCache();
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
---
|
|
515
|
-
|
|
516
505
|
### `cl.clearSessionCache(sessionKey?)`
|
|
517
506
|
|
|
518
507
|
Clears cached memory snapshots. Pass a specific session key (`"userId:projectId:sessionId"`) to clear one session, or omit to clear all.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const aiMocks = vitest_1.vi.hoisted(() => ({
|
|
5
|
+
wrapLanguageModel: vitest_1.vi.fn(({ model }) => model),
|
|
6
|
+
streamText: vitest_1.vi.fn(async (options) => ({ options })),
|
|
7
|
+
generateText: vitest_1.vi.fn(async (options) => ({ options })),
|
|
8
|
+
}));
|
|
9
|
+
const promptHubMocks = vitest_1.vi.hoisted(() => ({
|
|
10
|
+
resolvePrompt: vitest_1.vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
vitest_1.vi.mock("ai", () => ({
|
|
13
|
+
wrapLanguageModel: aiMocks.wrapLanguageModel,
|
|
14
|
+
streamText: aiMocks.streamText,
|
|
15
|
+
generateText: aiMocks.generateText,
|
|
16
|
+
}));
|
|
17
|
+
vitest_1.vi.mock("@kognitivedev/prompthub", () => ({
|
|
18
|
+
createPromptHubClient: vitest_1.vi.fn(() => ({
|
|
19
|
+
resolvePrompt: promptHubMocks.resolvePrompt,
|
|
20
|
+
})),
|
|
21
|
+
}));
|
|
22
|
+
const index_1 = require("../index");
|
|
23
|
+
function makeLayer(overrides) {
|
|
24
|
+
return (0, index_1.createCognitiveLayer)({
|
|
25
|
+
provider: (modelId) => ({ modelId }),
|
|
26
|
+
clConfig: Object.assign({ apiKey: "test-api-key", baseUrl: "https://backend.example", projectId: "project-1", processDelayMs: 0, logLevel: "debug" }, overrides),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
(0, vitest_1.describe)("createCognitiveLayer extras", () => {
|
|
30
|
+
(0, vitest_1.beforeEach)(() => {
|
|
31
|
+
aiMocks.wrapLanguageModel.mockClear();
|
|
32
|
+
aiMocks.streamText.mockClear();
|
|
33
|
+
aiMocks.generateText.mockClear();
|
|
34
|
+
promptHubMocks.resolvePrompt.mockReset();
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.afterEach)(() => {
|
|
37
|
+
vitest_1.vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)("resolves prompts and renders variables", async () => {
|
|
40
|
+
promptHubMocks.resolvePrompt.mockResolvedValue({
|
|
41
|
+
promptId: "prompt-1",
|
|
42
|
+
slug: "welcome",
|
|
43
|
+
version: 4,
|
|
44
|
+
content: "Hello {{name}}",
|
|
45
|
+
fetchedAt: Date.now(),
|
|
46
|
+
gatewaySlug: "gateway-a",
|
|
47
|
+
});
|
|
48
|
+
const warnSpy = vitest_1.vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
49
|
+
const logSpy = vitest_1.vi.spyOn(console, "log").mockImplementation(() => { });
|
|
50
|
+
const cl = makeLayer();
|
|
51
|
+
const model = cl("mock-model", {
|
|
52
|
+
userId: "user-1",
|
|
53
|
+
projectId: "project-1",
|
|
54
|
+
sessionId: "session-1",
|
|
55
|
+
});
|
|
56
|
+
await cl.streamText({
|
|
57
|
+
model,
|
|
58
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
59
|
+
prompt: { slug: "welcome", variables: { name: "Ada" } },
|
|
60
|
+
});
|
|
61
|
+
(0, vitest_1.expect)(promptHubMocks.resolvePrompt).toHaveBeenCalledWith({
|
|
62
|
+
slug: "welcome",
|
|
63
|
+
userId: "user-1",
|
|
64
|
+
tag: undefined,
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.expect)(aiMocks.streamText).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
67
|
+
system: "Hello Ada",
|
|
68
|
+
model: vitest_1.expect.objectContaining({ modelId: "mock-model" }),
|
|
69
|
+
}));
|
|
70
|
+
(0, vitest_1.expect)(warnSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining("Gateway config found but no providerFactory provided"));
|
|
71
|
+
(0, vitest_1.expect)(logSpy).toHaveBeenCalled();
|
|
72
|
+
cl.clearSessionCache("user-1:project-1:session-1");
|
|
73
|
+
cl.clearSessionCache();
|
|
74
|
+
});
|
|
75
|
+
(0, vitest_1.it)("uses a gateway model when providerFactory is available", async () => {
|
|
76
|
+
promptHubMocks.resolvePrompt.mockResolvedValue({
|
|
77
|
+
promptId: "prompt-2",
|
|
78
|
+
slug: "gateway",
|
|
79
|
+
version: 1,
|
|
80
|
+
content: "Prompt body",
|
|
81
|
+
fetchedAt: Date.now(),
|
|
82
|
+
gatewaySlug: "gateway-a",
|
|
83
|
+
});
|
|
84
|
+
const providerFactory = vitest_1.vi.fn(() => vitest_1.vi.fn((modelId) => ({ modelId })));
|
|
85
|
+
const cl = makeLayer({ providerFactory });
|
|
86
|
+
const model = cl("mock-model", {
|
|
87
|
+
userId: "user-1",
|
|
88
|
+
projectId: "project-1",
|
|
89
|
+
sessionId: "session-1",
|
|
90
|
+
});
|
|
91
|
+
await cl.generateText({
|
|
92
|
+
model,
|
|
93
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
94
|
+
prompt: { slug: "gateway" },
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.expect)(providerFactory).toHaveBeenCalledWith("https://backend.example/api/cognitive/gateway/gateway-a", "test-api-key");
|
|
97
|
+
(0, vitest_1.expect)(aiMocks.generateText).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
98
|
+
model: vitest_1.expect.objectContaining({ modelId: "gateway-model" }),
|
|
99
|
+
system: "Prompt body",
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
(0, vitest_1.it)("passes prompt tag and stores tag/ab metadata in logging payload", async () => {
|
|
103
|
+
var _a, _b, _c, _d;
|
|
104
|
+
const backendResponse = {
|
|
105
|
+
promptId: "prompt-4",
|
|
106
|
+
slug: "tagged-welcome",
|
|
107
|
+
version: 2,
|
|
108
|
+
content: "Tagged {{name}}",
|
|
109
|
+
fetchedAt: Date.now(),
|
|
110
|
+
gatewaySlug: null,
|
|
111
|
+
tag: "production",
|
|
112
|
+
abTestId: "ab-test-1",
|
|
113
|
+
variant: "variant",
|
|
114
|
+
};
|
|
115
|
+
promptHubMocks.resolvePrompt.mockResolvedValue(backendResponse);
|
|
116
|
+
const fetchSpy = vitest_1.vi
|
|
117
|
+
.spyOn(globalThis, "fetch")
|
|
118
|
+
.mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 201 }));
|
|
119
|
+
const cl = makeLayer();
|
|
120
|
+
const model = cl("mock-model", {
|
|
121
|
+
userId: "user-2",
|
|
122
|
+
projectId: "project-1",
|
|
123
|
+
sessionId: "session-2",
|
|
124
|
+
});
|
|
125
|
+
await cl.generateText({
|
|
126
|
+
model,
|
|
127
|
+
messages: [{ role: "user", content: "Hi {{name}}" }],
|
|
128
|
+
prompt: { slug: "welcome", tag: "production", variables: { name: "Ada" } },
|
|
129
|
+
});
|
|
130
|
+
await Promise.resolve();
|
|
131
|
+
(0, vitest_1.expect)(promptHubMocks.resolvePrompt).toHaveBeenCalledWith({
|
|
132
|
+
slug: "welcome",
|
|
133
|
+
userId: "user-2",
|
|
134
|
+
tag: "production",
|
|
135
|
+
});
|
|
136
|
+
(0, vitest_1.expect)(fetchSpy).toHaveBeenCalledWith("https://backend.example/api/cognitive/log", vitest_1.expect.objectContaining({
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: vitest_1.expect.objectContaining({
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
Authorization: "Bearer test-api-key",
|
|
141
|
+
}),
|
|
142
|
+
body: vitest_1.expect.stringContaining('"promptSlug":"tagged-welcome"'),
|
|
143
|
+
}));
|
|
144
|
+
const logCall = fetchSpy.mock.calls.find(([url]) => String(url).includes("api/cognitive/log"));
|
|
145
|
+
(0, vitest_1.expect)(logCall).toBeDefined();
|
|
146
|
+
const calledWithBody = JSON.parse(logCall[1].body);
|
|
147
|
+
(0, vitest_1.expect)(calledWithBody.promptSlug).toBe("tagged-welcome");
|
|
148
|
+
(0, vitest_1.expect)(calledWithBody.promptVersion).toBe(2);
|
|
149
|
+
(0, vitest_1.expect)(calledWithBody.promptId).toBe("prompt-4");
|
|
150
|
+
(0, vitest_1.expect)(calledWithBody.tag).toBe("production");
|
|
151
|
+
(0, vitest_1.expect)(calledWithBody.abTestId).toBe("ab-test-1");
|
|
152
|
+
(0, vitest_1.expect)(calledWithBody.variant).toBe("variant");
|
|
153
|
+
(0, vitest_1.expect)((_a = calledWithBody.metadata) === null || _a === void 0 ? void 0 : _a.appId).toBeUndefined();
|
|
154
|
+
(0, vitest_1.expect)((_b = calledWithBody.metadata) === null || _b === void 0 ? void 0 : _b.promptTag).toBe("production");
|
|
155
|
+
(0, vitest_1.expect)((_c = calledWithBody.metadata) === null || _c === void 0 ? void 0 : _c.abTestId).toBe("ab-test-1");
|
|
156
|
+
(0, vitest_1.expect)((_d = calledWithBody.metadata) === null || _d === void 0 ? void 0 : _d.variant).toBe("variant");
|
|
157
|
+
fetchSpy.mockRestore();
|
|
158
|
+
});
|
|
159
|
+
(0, vitest_1.it)("falls back to the original model when gateway model creation fails", async () => {
|
|
160
|
+
promptHubMocks.resolvePrompt.mockResolvedValue({
|
|
161
|
+
promptId: "prompt-3",
|
|
162
|
+
slug: "gateway",
|
|
163
|
+
version: 1,
|
|
164
|
+
content: "Prompt body",
|
|
165
|
+
fetchedAt: Date.now(),
|
|
166
|
+
gatewaySlug: "gateway-a",
|
|
167
|
+
});
|
|
168
|
+
const errorSpy = vitest_1.vi.spyOn(console, "error").mockImplementation(() => { });
|
|
169
|
+
const cl = makeLayer({
|
|
170
|
+
providerFactory: () => {
|
|
171
|
+
throw new Error("gateway failure");
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
const model = cl("mock-model", {
|
|
175
|
+
userId: "user-1",
|
|
176
|
+
projectId: "project-1",
|
|
177
|
+
sessionId: "session-1",
|
|
178
|
+
});
|
|
179
|
+
await cl.streamText({
|
|
180
|
+
model,
|
|
181
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
182
|
+
prompt: { slug: "gateway" },
|
|
183
|
+
});
|
|
184
|
+
(0, vitest_1.expect)(errorSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining("Failed to create gateway model, falling back to original"), vitest_1.expect.any(Error));
|
|
185
|
+
(0, vitest_1.expect)(aiMocks.streamText).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
186
|
+
model: vitest_1.expect.objectContaining({ modelId: "mock-model" }),
|
|
187
|
+
}));
|
|
188
|
+
});
|
|
189
|
+
(0, vitest_1.it)("falls back cleanly when prompt resolution fails", async () => {
|
|
190
|
+
promptHubMocks.resolvePrompt.mockRejectedValue(new Error("not found"));
|
|
191
|
+
const warnSpy = vitest_1.vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
192
|
+
const cl = makeLayer();
|
|
193
|
+
const model = cl("mock-model", {
|
|
194
|
+
userId: "user-1",
|
|
195
|
+
projectId: "project-1",
|
|
196
|
+
sessionId: "session-1",
|
|
197
|
+
});
|
|
198
|
+
await cl.generateText({
|
|
199
|
+
model,
|
|
200
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
201
|
+
prompt: { slug: "missing" },
|
|
202
|
+
});
|
|
203
|
+
(0, vitest_1.expect)(warnSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Failed to resolve prompt "missing", generating without system prompt.'), vitest_1.expect.any(Error));
|
|
204
|
+
(0, vitest_1.expect)(aiMocks.generateText).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
205
|
+
model: vitest_1.expect.objectContaining({ modelId: "mock-model" }),
|
|
206
|
+
}));
|
|
207
|
+
(0, vitest_1.expect)(aiMocks.generateText.mock.calls[0][0]).not.toHaveProperty("system");
|
|
208
|
+
});
|
|
209
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type CLModelWrapper = (modelId: string, settings?: {
|
|
|
38
38
|
export interface PromptConfig {
|
|
39
39
|
slug: string;
|
|
40
40
|
variables?: Record<string, string | boolean>;
|
|
41
|
+
tag?: string;
|
|
41
42
|
}
|
|
42
43
|
export type CLStreamTextOptions = Omit<Parameters<typeof aiStreamText>[0], 'system' | 'prompt'> & {
|
|
43
44
|
prompt: PromptConfig;
|
|
@@ -56,6 +57,9 @@ export interface LogConversationPayload {
|
|
|
56
57
|
promptSlug?: string;
|
|
57
58
|
promptVersion?: number;
|
|
58
59
|
promptId?: string;
|
|
60
|
+
tag?: string;
|
|
61
|
+
abTestId?: string;
|
|
62
|
+
variant?: "control" | "variant";
|
|
59
63
|
traceId?: string;
|
|
60
64
|
parentSpanId?: string;
|
|
61
65
|
requestPreview?: string;
|
|
@@ -87,10 +91,12 @@ export interface LogConversationPayload {
|
|
|
87
91
|
export type CognitiveLayer = CLModelWrapper & {
|
|
88
92
|
streamText: (options: CLStreamTextOptions) => Promise<ReturnType<typeof aiStreamText>>;
|
|
89
93
|
generateText: (options: CLGenerateTextOptions) => ReturnType<typeof aiGenerateText>;
|
|
90
|
-
resolvePrompt: (slug: string, userId?: string
|
|
94
|
+
resolvePrompt: (slug: string, userId?: string | {
|
|
95
|
+
userId?: string;
|
|
96
|
+
tag?: string;
|
|
97
|
+
}) => Promise<CachedPrompt>;
|
|
91
98
|
logConversation: (payload: LogConversationPayload) => Promise<void>;
|
|
92
99
|
triggerProcessing: (userId: string, projectId: string, sessionId: string) => void;
|
|
93
|
-
clearPromptCache: () => void;
|
|
94
100
|
clearSessionCache: (sessionKey?: string) => void;
|
|
95
101
|
};
|
|
96
102
|
export interface CachedPrompt {
|
|
@@ -100,6 +106,9 @@ export interface CachedPrompt {
|
|
|
100
106
|
content: string;
|
|
101
107
|
fetchedAt: number;
|
|
102
108
|
gatewaySlug?: string;
|
|
109
|
+
tag?: string;
|
|
110
|
+
abTestId?: string;
|
|
111
|
+
variant?: "control" | "variant";
|
|
103
112
|
}
|
|
104
113
|
export declare function createCognitiveLayer(config: {
|
|
105
114
|
provider: any;
|
package/dist/index.js
CHANGED
|
@@ -214,15 +214,21 @@ function createCognitiveLayer(config) {
|
|
|
214
214
|
apiKey: clConfig.apiKey,
|
|
215
215
|
logger,
|
|
216
216
|
});
|
|
217
|
-
const resolvePrompt = async (slug,
|
|
217
|
+
const resolvePrompt = async (slug, userIdOrOptions) => {
|
|
218
218
|
var _a;
|
|
219
|
+
const userId = typeof userIdOrOptions === "string"
|
|
220
|
+
? userIdOrOptions
|
|
221
|
+
: userIdOrOptions === null || userIdOrOptions === void 0 ? void 0 : userIdOrOptions.userId;
|
|
222
|
+
const tag = typeof userIdOrOptions === "string"
|
|
223
|
+
? undefined
|
|
224
|
+
: userIdOrOptions === null || userIdOrOptions === void 0 ? void 0 : userIdOrOptions.tag;
|
|
219
225
|
logger.debug("Resolving prompt from backend", {
|
|
220
226
|
slug,
|
|
221
227
|
userId,
|
|
222
228
|
baseUrl,
|
|
223
229
|
apiKeyHint: maskSecret(clConfig.apiKey),
|
|
224
230
|
});
|
|
225
|
-
const data = await promptClient.resolvePrompt({ slug, userId });
|
|
231
|
+
const data = await promptClient.resolvePrompt({ slug, userId, tag });
|
|
226
232
|
const entry = {
|
|
227
233
|
promptId: data.promptId,
|
|
228
234
|
slug: data.slug,
|
|
@@ -230,6 +236,9 @@ function createCognitiveLayer(config) {
|
|
|
230
236
|
content: data.content,
|
|
231
237
|
fetchedAt: Date.now(),
|
|
232
238
|
gatewaySlug: data.gatewaySlug,
|
|
239
|
+
tag: data.tag,
|
|
240
|
+
abTestId: data.abTestId,
|
|
241
|
+
variant: data.variant,
|
|
233
242
|
};
|
|
234
243
|
logger.debug("Prompt resolved payload", {
|
|
235
244
|
slug,
|
|
@@ -444,10 +453,11 @@ ${userContextBlock || "None"}
|
|
|
444
453
|
promptSlug: promptMeta.promptSlug,
|
|
445
454
|
promptVersion: promptMeta.promptVersion,
|
|
446
455
|
promptId: promptMeta.promptId,
|
|
456
|
+
tag: promptMeta.tag,
|
|
457
|
+
abTestId: promptMeta.abTestId,
|
|
458
|
+
variant: promptMeta.variant,
|
|
447
459
|
})), (toolDefs && { tools: toolDefs })), (agentRunId && { agentRunId })), { traceId: (0, crypto_1.randomUUID)(), requestPreview,
|
|
448
|
-
responsePreview, state: "completed", startedAt: startedAt.toISOString(), endedAt: endedAt.toISOString(), durationMs: endedAt.getTime() - startedAt.getTime(), metadata: {
|
|
449
|
-
appId: clConfig.appId,
|
|
450
|
-
}, spans })).then(() => triggerProcessing(userId, projectId, sessionId));
|
|
460
|
+
responsePreview, state: "completed", startedAt: startedAt.toISOString(), endedAt: endedAt.toISOString(), durationMs: endedAt.getTime() - startedAt.getTime(), metadata: Object.assign(Object.assign(Object.assign({ appId: clConfig.appId }, ((promptMeta === null || promptMeta === void 0 ? void 0 : promptMeta.tag) && { promptTag: promptMeta.tag })), ((promptMeta === null || promptMeta === void 0 ? void 0 : promptMeta.abTestId) && { abTestId: promptMeta.abTestId })), ((promptMeta === null || promptMeta === void 0 ? void 0 : promptMeta.variant) && { variant: promptMeta.variant })), spans })).then(() => triggerProcessing(userId, projectId, sessionId));
|
|
451
461
|
}
|
|
452
462
|
return result;
|
|
453
463
|
},
|
|
@@ -552,11 +562,12 @@ ${userContextBlock || "None"}
|
|
|
552
562
|
promptSlug: promptMeta.promptSlug,
|
|
553
563
|
promptVersion: promptMeta.promptVersion,
|
|
554
564
|
promptId: promptMeta.promptId,
|
|
565
|
+
tag: promptMeta.tag,
|
|
566
|
+
abTestId: promptMeta.abTestId,
|
|
567
|
+
variant: promptMeta.variant,
|
|
555
568
|
})), (toolDefs && { tools: toolDefs })), (agentRunId && { agentRunId })), { traceId,
|
|
556
569
|
requestPreview,
|
|
557
|
-
responsePreview, state: "completed", startedAt: startedAt.toISOString(), endedAt: endedAt.toISOString(), durationMs: endedAt.getTime() - startedAt.getTime(), metadata: {
|
|
558
|
-
appId: clConfig.appId,
|
|
559
|
-
}, spans })).then(() => triggerProcessing(userId, projectId, sessionId))
|
|
570
|
+
responsePreview, state: "completed", startedAt: startedAt.toISOString(), endedAt: endedAt.toISOString(), durationMs: endedAt.getTime() - startedAt.getTime(), metadata: Object.assign(Object.assign(Object.assign({ appId: clConfig.appId }, ((promptMeta === null || promptMeta === void 0 ? void 0 : promptMeta.tag) && { promptTag: promptMeta.tag })), ((promptMeta === null || promptMeta === void 0 ? void 0 : promptMeta.abTestId) && { abTestId: promptMeta.abTestId })), ((promptMeta === null || promptMeta === void 0 ? void 0 : promptMeta.variant) && { variant: promptMeta.variant })), spans })).then(() => triggerProcessing(userId, projectId, sessionId))
|
|
560
571
|
.catch((e) => logger.error("Stream log failed", e));
|
|
561
572
|
}
|
|
562
573
|
});
|
|
@@ -621,7 +632,10 @@ ${userContextBlock || "None"}
|
|
|
621
632
|
// Resolve and interpolate prompt (graceful fallback on failure)
|
|
622
633
|
let resolved = null;
|
|
623
634
|
try {
|
|
624
|
-
resolved = await resolvePrompt(promptConfig.slug,
|
|
635
|
+
resolved = await resolvePrompt(promptConfig.slug, {
|
|
636
|
+
userId: session === null || session === void 0 ? void 0 : session.userId,
|
|
637
|
+
tag: promptConfig.tag,
|
|
638
|
+
});
|
|
625
639
|
}
|
|
626
640
|
catch (err) {
|
|
627
641
|
logger.warn(`Failed to resolve prompt "${promptConfig.slug}", streaming without system prompt.`, err);
|
|
@@ -638,6 +652,9 @@ ${userContextBlock || "None"}
|
|
|
638
652
|
promptSlug: resolved.slug,
|
|
639
653
|
promptVersion: resolved.version,
|
|
640
654
|
promptId: resolved.promptId,
|
|
655
|
+
tag: resolved.tag,
|
|
656
|
+
abTestId: resolved.abTestId,
|
|
657
|
+
variant: resolved.variant,
|
|
641
658
|
});
|
|
642
659
|
}
|
|
643
660
|
logger.info("cl.streamText called", {
|
|
@@ -660,7 +677,10 @@ ${userContextBlock || "None"}
|
|
|
660
677
|
// Resolve and interpolate prompt (graceful fallback on failure)
|
|
661
678
|
let resolved = null;
|
|
662
679
|
try {
|
|
663
|
-
resolved = await resolvePrompt(promptConfig.slug,
|
|
680
|
+
resolved = await resolvePrompt(promptConfig.slug, {
|
|
681
|
+
userId: session === null || session === void 0 ? void 0 : session.userId,
|
|
682
|
+
tag: promptConfig.tag,
|
|
683
|
+
});
|
|
664
684
|
}
|
|
665
685
|
catch (err) {
|
|
666
686
|
logger.warn(`Failed to resolve prompt "${promptConfig.slug}", generating without system prompt.`, err);
|
|
@@ -677,6 +697,9 @@ ${userContextBlock || "None"}
|
|
|
677
697
|
promptSlug: resolved.slug,
|
|
678
698
|
promptVersion: resolved.version,
|
|
679
699
|
promptId: resolved.promptId,
|
|
700
|
+
tag: resolved.tag,
|
|
701
|
+
abTestId: resolved.abTestId,
|
|
702
|
+
variant: resolved.variant,
|
|
680
703
|
});
|
|
681
704
|
}
|
|
682
705
|
logger.info("cl.generateText called", {
|
|
@@ -700,7 +723,6 @@ ${userContextBlock || "None"}
|
|
|
700
723
|
resolvePrompt,
|
|
701
724
|
logConversation,
|
|
702
725
|
triggerProcessing,
|
|
703
|
-
clearPromptCache: () => promptClient.clearPromptCache(),
|
|
704
726
|
clearSessionCache: (sessionKey) => {
|
|
705
727
|
if (sessionKey) {
|
|
706
728
|
sessionSnapshots.delete(sessionKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kognitivedev/vercel-ai-provider",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"publishConfig": {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"prepublishOnly": "npm run build"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@kognitivedev/prompthub": "^0.1.
|
|
17
|
-
"@kognitivedev/shared": "^0.2.
|
|
16
|
+
"@kognitivedev/prompthub": "^0.1.5",
|
|
17
|
+
"@kognitivedev/shared": "^0.2.7"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"ai": "^5.0.0 || ^6.0.0"
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import type { LanguageModel } from "ai";
|
|
3
|
+
|
|
4
|
+
const aiMocks = vi.hoisted(() => ({
|
|
5
|
+
wrapLanguageModel: vi.fn(({ model }: { model: unknown }) => model),
|
|
6
|
+
streamText: vi.fn(async (options: Record<string, unknown>) => ({ options })),
|
|
7
|
+
generateText: vi.fn(async (options: Record<string, unknown>) => ({ options })),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const promptHubMocks = vi.hoisted(() => ({
|
|
11
|
+
resolvePrompt: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock("ai", () => ({
|
|
15
|
+
wrapLanguageModel: aiMocks.wrapLanguageModel,
|
|
16
|
+
streamText: aiMocks.streamText,
|
|
17
|
+
generateText: aiMocks.generateText,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock("@kognitivedev/prompthub", () => ({
|
|
21
|
+
createPromptHubClient: vi.fn(() => ({
|
|
22
|
+
resolvePrompt: promptHubMocks.resolvePrompt,
|
|
23
|
+
})),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import { createCognitiveLayer } from "../index";
|
|
27
|
+
|
|
28
|
+
function makeLayer(overrides?: {
|
|
29
|
+
providerFactory?: (baseURL: string, apiKey: string) => (modelId: string) => LanguageModel;
|
|
30
|
+
}) {
|
|
31
|
+
return createCognitiveLayer({
|
|
32
|
+
provider: (modelId: string) => ({ modelId }),
|
|
33
|
+
clConfig: {
|
|
34
|
+
apiKey: "test-api-key",
|
|
35
|
+
baseUrl: "https://backend.example",
|
|
36
|
+
projectId: "project-1",
|
|
37
|
+
processDelayMs: 0,
|
|
38
|
+
logLevel: "debug",
|
|
39
|
+
...overrides,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("createCognitiveLayer extras", () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
aiMocks.wrapLanguageModel.mockClear();
|
|
47
|
+
aiMocks.streamText.mockClear();
|
|
48
|
+
aiMocks.generateText.mockClear();
|
|
49
|
+
promptHubMocks.resolvePrompt.mockReset();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
vi.restoreAllMocks();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("resolves prompts and renders variables", async () => {
|
|
57
|
+
promptHubMocks.resolvePrompt.mockResolvedValue({
|
|
58
|
+
promptId: "prompt-1",
|
|
59
|
+
slug: "welcome",
|
|
60
|
+
version: 4,
|
|
61
|
+
content: "Hello {{name}}",
|
|
62
|
+
fetchedAt: Date.now(),
|
|
63
|
+
gatewaySlug: "gateway-a",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
67
|
+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
68
|
+
const cl = makeLayer();
|
|
69
|
+
const model = cl("mock-model", {
|
|
70
|
+
userId: "user-1",
|
|
71
|
+
projectId: "project-1",
|
|
72
|
+
sessionId: "session-1",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await cl.streamText({
|
|
76
|
+
model,
|
|
77
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
78
|
+
prompt: { slug: "welcome", variables: { name: "Ada" } },
|
|
79
|
+
} as any);
|
|
80
|
+
|
|
81
|
+
expect(promptHubMocks.resolvePrompt).toHaveBeenCalledWith({
|
|
82
|
+
slug: "welcome",
|
|
83
|
+
userId: "user-1",
|
|
84
|
+
tag: undefined,
|
|
85
|
+
});
|
|
86
|
+
expect(aiMocks.streamText).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({
|
|
88
|
+
system: "Hello Ada",
|
|
89
|
+
model: expect.objectContaining({ modelId: "mock-model" }),
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
93
|
+
expect.stringContaining("Gateway config found but no providerFactory provided")
|
|
94
|
+
);
|
|
95
|
+
expect(logSpy).toHaveBeenCalled();
|
|
96
|
+
cl.clearSessionCache("user-1:project-1:session-1");
|
|
97
|
+
cl.clearSessionCache();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("uses a gateway model when providerFactory is available", async () => {
|
|
101
|
+
promptHubMocks.resolvePrompt.mockResolvedValue({
|
|
102
|
+
promptId: "prompt-2",
|
|
103
|
+
slug: "gateway",
|
|
104
|
+
version: 1,
|
|
105
|
+
content: "Prompt body",
|
|
106
|
+
fetchedAt: Date.now(),
|
|
107
|
+
gatewaySlug: "gateway-a",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const providerFactory = vi.fn(() => vi.fn((modelId: string) => ({ modelId } as unknown as LanguageModel)));
|
|
111
|
+
const cl = makeLayer({ providerFactory });
|
|
112
|
+
const model = cl("mock-model", {
|
|
113
|
+
userId: "user-1",
|
|
114
|
+
projectId: "project-1",
|
|
115
|
+
sessionId: "session-1",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await cl.generateText({
|
|
119
|
+
model,
|
|
120
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
121
|
+
prompt: { slug: "gateway" },
|
|
122
|
+
} as any);
|
|
123
|
+
|
|
124
|
+
expect(providerFactory).toHaveBeenCalledWith(
|
|
125
|
+
"https://backend.example/api/cognitive/gateway/gateway-a",
|
|
126
|
+
"test-api-key"
|
|
127
|
+
);
|
|
128
|
+
expect(aiMocks.generateText).toHaveBeenCalledWith(
|
|
129
|
+
expect.objectContaining({
|
|
130
|
+
model: expect.objectContaining({ modelId: "gateway-model" }),
|
|
131
|
+
system: "Prompt body",
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("passes prompt tag and stores tag/ab metadata in logging payload", async () => {
|
|
137
|
+
const backendResponse = {
|
|
138
|
+
promptId: "prompt-4",
|
|
139
|
+
slug: "tagged-welcome",
|
|
140
|
+
version: 2,
|
|
141
|
+
content: "Tagged {{name}}",
|
|
142
|
+
fetchedAt: Date.now(),
|
|
143
|
+
gatewaySlug: null,
|
|
144
|
+
tag: "production",
|
|
145
|
+
abTestId: "ab-test-1",
|
|
146
|
+
variant: "variant" as const,
|
|
147
|
+
};
|
|
148
|
+
promptHubMocks.resolvePrompt.mockResolvedValue(backendResponse);
|
|
149
|
+
|
|
150
|
+
const fetchSpy = vi
|
|
151
|
+
.spyOn(globalThis, "fetch")
|
|
152
|
+
.mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 201 }));
|
|
153
|
+
|
|
154
|
+
const cl = makeLayer();
|
|
155
|
+
const model = cl("mock-model", {
|
|
156
|
+
userId: "user-2",
|
|
157
|
+
projectId: "project-1",
|
|
158
|
+
sessionId: "session-2",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await cl.generateText({
|
|
162
|
+
model,
|
|
163
|
+
messages: [{ role: "user", content: "Hi {{name}}" }],
|
|
164
|
+
prompt: { slug: "welcome", tag: "production", variables: { name: "Ada" } },
|
|
165
|
+
} as any);
|
|
166
|
+
|
|
167
|
+
await Promise.resolve();
|
|
168
|
+
expect(promptHubMocks.resolvePrompt).toHaveBeenCalledWith({
|
|
169
|
+
slug: "welcome",
|
|
170
|
+
userId: "user-2",
|
|
171
|
+
tag: "production",
|
|
172
|
+
});
|
|
173
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
174
|
+
"https://backend.example/api/cognitive/log",
|
|
175
|
+
expect.objectContaining({
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: expect.objectContaining({
|
|
178
|
+
"Content-Type": "application/json",
|
|
179
|
+
Authorization: "Bearer test-api-key",
|
|
180
|
+
}),
|
|
181
|
+
body: expect.stringContaining('"promptSlug":"tagged-welcome"'),
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const logCall = fetchSpy.mock.calls.find(
|
|
186
|
+
([url]) => String(url).includes("api/cognitive/log"),
|
|
187
|
+
);
|
|
188
|
+
expect(logCall).toBeDefined();
|
|
189
|
+
const calledWithBody = JSON.parse(logCall![1]!.body as string);
|
|
190
|
+
expect(calledWithBody.promptSlug).toBe("tagged-welcome");
|
|
191
|
+
expect(calledWithBody.promptVersion).toBe(2);
|
|
192
|
+
expect(calledWithBody.promptId).toBe("prompt-4");
|
|
193
|
+
expect(calledWithBody.tag).toBe("production");
|
|
194
|
+
expect(calledWithBody.abTestId).toBe("ab-test-1");
|
|
195
|
+
expect(calledWithBody.variant).toBe("variant");
|
|
196
|
+
expect(calledWithBody.metadata?.appId).toBeUndefined();
|
|
197
|
+
expect(calledWithBody.metadata?.promptTag).toBe("production");
|
|
198
|
+
expect(calledWithBody.metadata?.abTestId).toBe("ab-test-1");
|
|
199
|
+
expect(calledWithBody.metadata?.variant).toBe("variant");
|
|
200
|
+
|
|
201
|
+
fetchSpy.mockRestore();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("falls back to the original model when gateway model creation fails", async () => {
|
|
205
|
+
promptHubMocks.resolvePrompt.mockResolvedValue({
|
|
206
|
+
promptId: "prompt-3",
|
|
207
|
+
slug: "gateway",
|
|
208
|
+
version: 1,
|
|
209
|
+
content: "Prompt body",
|
|
210
|
+
fetchedAt: Date.now(),
|
|
211
|
+
gatewaySlug: "gateway-a",
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
215
|
+
const cl = makeLayer({
|
|
216
|
+
providerFactory: () => {
|
|
217
|
+
throw new Error("gateway failure");
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
const model = cl("mock-model", {
|
|
221
|
+
userId: "user-1",
|
|
222
|
+
projectId: "project-1",
|
|
223
|
+
sessionId: "session-1",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await cl.streamText({
|
|
227
|
+
model,
|
|
228
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
229
|
+
prompt: { slug: "gateway" },
|
|
230
|
+
} as any);
|
|
231
|
+
|
|
232
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
233
|
+
expect.stringContaining("Failed to create gateway model, falling back to original"),
|
|
234
|
+
expect.any(Error)
|
|
235
|
+
);
|
|
236
|
+
expect(aiMocks.streamText).toHaveBeenCalledWith(
|
|
237
|
+
expect.objectContaining({
|
|
238
|
+
model: expect.objectContaining({ modelId: "mock-model" }),
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("falls back cleanly when prompt resolution fails", async () => {
|
|
244
|
+
promptHubMocks.resolvePrompt.mockRejectedValue(new Error("not found"));
|
|
245
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
246
|
+
const cl = makeLayer();
|
|
247
|
+
const model = cl("mock-model", {
|
|
248
|
+
userId: "user-1",
|
|
249
|
+
projectId: "project-1",
|
|
250
|
+
sessionId: "session-1",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await cl.generateText({
|
|
254
|
+
model,
|
|
255
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
256
|
+
prompt: { slug: "missing" },
|
|
257
|
+
} as any);
|
|
258
|
+
|
|
259
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
260
|
+
expect.stringContaining('Failed to resolve prompt "missing", generating without system prompt.'),
|
|
261
|
+
expect.any(Error)
|
|
262
|
+
);
|
|
263
|
+
expect(aiMocks.generateText).toHaveBeenCalledWith(
|
|
264
|
+
expect.objectContaining({
|
|
265
|
+
model: expect.objectContaining({ modelId: "mock-model" }),
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
expect(aiMocks.generateText.mock.calls[0][0]).not.toHaveProperty("system");
|
|
269
|
+
});
|
|
270
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -105,6 +105,7 @@ export type CLModelWrapper = (
|
|
|
105
105
|
export interface PromptConfig {
|
|
106
106
|
slug: string;
|
|
107
107
|
variables?: Record<string, string | boolean>;
|
|
108
|
+
tag?: string;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
export type CLStreamTextOptions = Omit<Parameters<typeof aiStreamText>[0], 'system' | 'prompt'> & {
|
|
@@ -126,6 +127,9 @@ export interface LogConversationPayload {
|
|
|
126
127
|
promptSlug?: string;
|
|
127
128
|
promptVersion?: number;
|
|
128
129
|
promptId?: string;
|
|
130
|
+
tag?: string;
|
|
131
|
+
abTestId?: string;
|
|
132
|
+
variant?: "control" | "variant";
|
|
129
133
|
traceId?: string;
|
|
130
134
|
parentSpanId?: string;
|
|
131
135
|
requestPreview?: string;
|
|
@@ -154,10 +158,12 @@ export interface LogConversationPayload {
|
|
|
154
158
|
export type CognitiveLayer = CLModelWrapper & {
|
|
155
159
|
streamText: (options: CLStreamTextOptions) => Promise<ReturnType<typeof aiStreamText>>;
|
|
156
160
|
generateText: (options: CLGenerateTextOptions) => ReturnType<typeof aiGenerateText>;
|
|
157
|
-
resolvePrompt: (
|
|
161
|
+
resolvePrompt: (
|
|
162
|
+
slug: string,
|
|
163
|
+
userId?: string | { userId?: string; tag?: string }
|
|
164
|
+
) => Promise<CachedPrompt>;
|
|
158
165
|
logConversation: (payload: LogConversationPayload) => Promise<void>;
|
|
159
166
|
triggerProcessing: (userId: string, projectId: string, sessionId: string) => void;
|
|
160
|
-
clearPromptCache: () => void;
|
|
161
167
|
clearSessionCache: (sessionKey?: string) => void;
|
|
162
168
|
};
|
|
163
169
|
|
|
@@ -170,6 +176,9 @@ export interface CachedPrompt {
|
|
|
170
176
|
content: string;
|
|
171
177
|
fetchedAt: number;
|
|
172
178
|
gatewaySlug?: string;
|
|
179
|
+
tag?: string;
|
|
180
|
+
abTestId?: string;
|
|
181
|
+
variant?: "control" | "variant";
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
function getContentText(content: any): string {
|
|
@@ -309,7 +318,7 @@ const MEMORY_TAG_REGEX = /<MemoryContext>/i;
|
|
|
309
318
|
const SESSION_KEY = Symbol.for("cl:session");
|
|
310
319
|
|
|
311
320
|
// Session key → prompt metadata (populated by cl.streamText/cl.generateText, read by middleware)
|
|
312
|
-
const sessionPromptMetadata = new Map<string, { promptSlug: string; promptVersion: number; promptId: string }>();
|
|
321
|
+
const sessionPromptMetadata = new Map<string, { promptSlug: string; promptVersion: number; promptId: string; tag?: string; abTestId?: string; variant?: "control" | "variant" }>();
|
|
313
322
|
|
|
314
323
|
/**
|
|
315
324
|
* Check if any system message already contains a <MemoryContext> block.
|
|
@@ -352,7 +361,13 @@ export function createCognitiveLayer(config: {
|
|
|
352
361
|
logger,
|
|
353
362
|
});
|
|
354
363
|
|
|
355
|
-
const resolvePrompt = async (slug: string, userId?: string): Promise<CachedPrompt> => {
|
|
364
|
+
const resolvePrompt = async (slug: string, userIdOrOptions?: string | { userId?: string; tag?: string }): Promise<CachedPrompt> => {
|
|
365
|
+
const userId = typeof userIdOrOptions === "string"
|
|
366
|
+
? userIdOrOptions
|
|
367
|
+
: userIdOrOptions?.userId;
|
|
368
|
+
const tag = typeof userIdOrOptions === "string"
|
|
369
|
+
? undefined
|
|
370
|
+
: userIdOrOptions?.tag;
|
|
356
371
|
logger.debug("Resolving prompt from backend", {
|
|
357
372
|
slug,
|
|
358
373
|
userId,
|
|
@@ -360,7 +375,7 @@ export function createCognitiveLayer(config: {
|
|
|
360
375
|
apiKeyHint: maskSecret(clConfig.apiKey),
|
|
361
376
|
});
|
|
362
377
|
|
|
363
|
-
const data = await promptClient.resolvePrompt({ slug, userId });
|
|
378
|
+
const data = await promptClient.resolvePrompt({ slug, userId, tag });
|
|
364
379
|
const entry: CachedPrompt = {
|
|
365
380
|
promptId: data.promptId,
|
|
366
381
|
slug: data.slug,
|
|
@@ -368,6 +383,9 @@ export function createCognitiveLayer(config: {
|
|
|
368
383
|
content: data.content,
|
|
369
384
|
fetchedAt: Date.now(),
|
|
370
385
|
gatewaySlug: data.gatewaySlug,
|
|
386
|
+
tag: data.tag,
|
|
387
|
+
abTestId: data.abTestId,
|
|
388
|
+
variant: data.variant,
|
|
371
389
|
};
|
|
372
390
|
logger.debug("Prompt resolved payload", {
|
|
373
391
|
slug,
|
|
@@ -611,6 +629,9 @@ ${userContextBlock || "None"}
|
|
|
611
629
|
promptSlug: promptMeta.promptSlug,
|
|
612
630
|
promptVersion: promptMeta.promptVersion,
|
|
613
631
|
promptId: promptMeta.promptId,
|
|
632
|
+
tag: promptMeta.tag,
|
|
633
|
+
abTestId: promptMeta.abTestId,
|
|
634
|
+
variant: promptMeta.variant,
|
|
614
635
|
}),
|
|
615
636
|
...(toolDefs && { tools: toolDefs }),
|
|
616
637
|
...(agentRunId && { agentRunId }),
|
|
@@ -623,6 +644,9 @@ ${userContextBlock || "None"}
|
|
|
623
644
|
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
624
645
|
metadata: {
|
|
625
646
|
appId: clConfig.appId,
|
|
647
|
+
...(promptMeta?.tag && { promptTag: promptMeta.tag }),
|
|
648
|
+
...(promptMeta?.abTestId && { abTestId: promptMeta.abTestId }),
|
|
649
|
+
...(promptMeta?.variant && { variant: promptMeta.variant }),
|
|
626
650
|
},
|
|
627
651
|
spans,
|
|
628
652
|
}).then(() => triggerProcessing(userId, projectId, sessionId));
|
|
@@ -742,6 +766,9 @@ ${userContextBlock || "None"}
|
|
|
742
766
|
promptSlug: promptMeta.promptSlug,
|
|
743
767
|
promptVersion: promptMeta.promptVersion,
|
|
744
768
|
promptId: promptMeta.promptId,
|
|
769
|
+
tag: promptMeta.tag,
|
|
770
|
+
abTestId: promptMeta.abTestId,
|
|
771
|
+
variant: promptMeta.variant,
|
|
745
772
|
}),
|
|
746
773
|
...(toolDefs && { tools: toolDefs }),
|
|
747
774
|
...(agentRunId && { agentRunId }),
|
|
@@ -754,6 +781,9 @@ ${userContextBlock || "None"}
|
|
|
754
781
|
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
755
782
|
metadata: {
|
|
756
783
|
appId: clConfig.appId,
|
|
784
|
+
...(promptMeta?.tag && { promptTag: promptMeta.tag }),
|
|
785
|
+
...(promptMeta?.abTestId && { abTestId: promptMeta.abTestId }),
|
|
786
|
+
...(promptMeta?.variant && { variant: promptMeta.variant }),
|
|
757
787
|
},
|
|
758
788
|
spans,
|
|
759
789
|
}).then(() => triggerProcessing(userId, projectId, sessionId))
|
|
@@ -839,7 +869,10 @@ ${userContextBlock || "None"}
|
|
|
839
869
|
// Resolve and interpolate prompt (graceful fallback on failure)
|
|
840
870
|
let resolved: CachedPrompt | null = null;
|
|
841
871
|
try {
|
|
842
|
-
resolved = await resolvePrompt(promptConfig.slug,
|
|
872
|
+
resolved = await resolvePrompt(promptConfig.slug, {
|
|
873
|
+
userId: session?.userId,
|
|
874
|
+
tag: promptConfig.tag,
|
|
875
|
+
});
|
|
843
876
|
} catch (err) {
|
|
844
877
|
logger.warn(`Failed to resolve prompt "${promptConfig.slug}", streaming without system prompt.`, err);
|
|
845
878
|
}
|
|
@@ -857,6 +890,9 @@ ${userContextBlock || "None"}
|
|
|
857
890
|
promptSlug: resolved.slug,
|
|
858
891
|
promptVersion: resolved.version,
|
|
859
892
|
promptId: resolved.promptId,
|
|
893
|
+
tag: resolved.tag,
|
|
894
|
+
abTestId: resolved.abTestId,
|
|
895
|
+
variant: resolved.variant,
|
|
860
896
|
});
|
|
861
897
|
}
|
|
862
898
|
|
|
@@ -883,7 +919,10 @@ ${userContextBlock || "None"}
|
|
|
883
919
|
// Resolve and interpolate prompt (graceful fallback on failure)
|
|
884
920
|
let resolved: CachedPrompt | null = null;
|
|
885
921
|
try {
|
|
886
|
-
resolved = await resolvePrompt(promptConfig.slug,
|
|
922
|
+
resolved = await resolvePrompt(promptConfig.slug, {
|
|
923
|
+
userId: session?.userId,
|
|
924
|
+
tag: promptConfig.tag,
|
|
925
|
+
});
|
|
887
926
|
} catch (err) {
|
|
888
927
|
logger.warn(`Failed to resolve prompt "${promptConfig.slug}", generating without system prompt.`, err);
|
|
889
928
|
}
|
|
@@ -901,6 +940,9 @@ ${userContextBlock || "None"}
|
|
|
901
940
|
promptSlug: resolved.slug,
|
|
902
941
|
promptVersion: resolved.version,
|
|
903
942
|
promptId: resolved.promptId,
|
|
943
|
+
tag: resolved.tag,
|
|
944
|
+
abTestId: resolved.abTestId,
|
|
945
|
+
variant: resolved.variant,
|
|
904
946
|
});
|
|
905
947
|
}
|
|
906
948
|
|
|
@@ -926,7 +968,6 @@ ${userContextBlock || "None"}
|
|
|
926
968
|
resolvePrompt,
|
|
927
969
|
logConversation,
|
|
928
970
|
triggerProcessing,
|
|
929
|
-
clearPromptCache: () => promptClient.clearPromptCache(),
|
|
930
971
|
clearSessionCache: (sessionKey?: string) => {
|
|
931
972
|
if (sessionKey) {
|
|
932
973
|
sessionSnapshots.delete(sessionKey);
|