@kognitivedev/vercel-ai-provider 0.2.21 → 0.2.26
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 +58 -0
- package/README.md +29 -4
- package/dist/__tests__/cognitive-layer-extra.test.js +151 -0
- package/dist/__tests__/wrap-stream-logging.test.js +131 -9
- package/dist/index.d.ts +55 -5
- package/dist/index.js +643 -137
- package/package.json +6 -4
- package/src/__tests__/cognitive-layer-extra.test.ts +158 -1
- package/src/__tests__/wrap-stream-logging.test.ts +152 -10
- package/src/index.ts +746 -145
package/src/index.ts
CHANGED
|
@@ -4,7 +4,21 @@ import {
|
|
|
4
4
|
generateText as aiGenerateText,
|
|
5
5
|
type LanguageModel,
|
|
6
6
|
} from "ai";
|
|
7
|
-
import {
|
|
7
|
+
import { MemoryClient } from "@kognitivedev/memory";
|
|
8
|
+
import { createTopicMemoryTool, TOPIC_MEMORY_TOOL_ID } from "@kognitivedev/tools";
|
|
9
|
+
import {
|
|
10
|
+
normalizeAutomaticThreadTitleConfig,
|
|
11
|
+
buildRemoteLogPayload,
|
|
12
|
+
buildRemoteRunPayload,
|
|
13
|
+
buildRemoteTraceFinishPayload,
|
|
14
|
+
buildRemoteTracePreviews,
|
|
15
|
+
buildRemoteTraceStartPayload,
|
|
16
|
+
createRemoteExecutionContext,
|
|
17
|
+
isSessionScopedExecution,
|
|
18
|
+
isModerationError,
|
|
19
|
+
normalizeRemoteUsage,
|
|
20
|
+
type AutomaticThreadTitleConfig,
|
|
21
|
+
} from "@kognitivedev/shared";
|
|
8
22
|
import { createPromptHubClient } from "@kognitivedev/prompthub";
|
|
9
23
|
export { renderTemplate, type TemplateVariables } from "./template";
|
|
10
24
|
import { renderTemplate } from "./template";
|
|
@@ -108,12 +122,18 @@ export interface PromptConfig {
|
|
|
108
122
|
tag?: string;
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
export
|
|
112
|
-
|
|
125
|
+
export interface CognitiveToolOptions {
|
|
126
|
+
autoTopicMemoryTool?: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type CLStreamTextOptions = Omit<Parameters<typeof aiStreamText>[0], 'prompt'> & {
|
|
130
|
+
prompt?: PromptConfig;
|
|
131
|
+
kognitive?: CognitiveToolOptions;
|
|
113
132
|
};
|
|
114
133
|
|
|
115
|
-
export type CLGenerateTextOptions = Omit<Parameters<typeof aiGenerateText>[0], '
|
|
116
|
-
prompt
|
|
134
|
+
export type CLGenerateTextOptions = Omit<Parameters<typeof aiGenerateText>[0], 'prompt'> & {
|
|
135
|
+
prompt?: PromptConfig;
|
|
136
|
+
kognitive?: CognitiveToolOptions;
|
|
117
137
|
};
|
|
118
138
|
|
|
119
139
|
export interface LogConversationPayload {
|
|
@@ -140,6 +160,8 @@ export interface LogConversationPayload {
|
|
|
140
160
|
durationMs?: number;
|
|
141
161
|
tools?: Array<{ name: string; description?: string; parameters?: Record<string, unknown> }>;
|
|
142
162
|
agentRunId?: string;
|
|
163
|
+
turnId?: string;
|
|
164
|
+
turnIndex?: number;
|
|
143
165
|
metadata?: Record<string, unknown>;
|
|
144
166
|
spans?: Array<{
|
|
145
167
|
spanKey: string;
|
|
@@ -153,6 +175,11 @@ export interface LogConversationPayload {
|
|
|
153
175
|
errorMessage?: string;
|
|
154
176
|
metadata?: Record<string, unknown>;
|
|
155
177
|
}>;
|
|
178
|
+
automaticTitle?: AutomaticThreadTitleConfig;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
interface LogConversationResult {
|
|
182
|
+
generatedTitle: string | null;
|
|
156
183
|
}
|
|
157
184
|
|
|
158
185
|
export type CognitiveLayer = CLModelWrapper & {
|
|
@@ -162,7 +189,7 @@ export type CognitiveLayer = CLModelWrapper & {
|
|
|
162
189
|
slug: string,
|
|
163
190
|
userId?: string | { userId?: string; tag?: string }
|
|
164
191
|
) => Promise<CachedPrompt>;
|
|
165
|
-
logConversation: (payload: LogConversationPayload) => Promise<
|
|
192
|
+
logConversation: (payload: LogConversationPayload) => Promise<LogConversationResult | null>;
|
|
166
193
|
triggerProcessing: (userId: string, sessionId: string) => void;
|
|
167
194
|
clearSessionCache: (sessionKey?: string) => void;
|
|
168
195
|
};
|
|
@@ -181,24 +208,6 @@ export interface CachedPrompt {
|
|
|
181
208
|
variant?: "control" | "variant";
|
|
182
209
|
}
|
|
183
210
|
|
|
184
|
-
function getContentText(content: any): string {
|
|
185
|
-
if (typeof content === "string") {
|
|
186
|
-
if (content.includes("data:image/") && content.includes("base64,")) return "[Image]";
|
|
187
|
-
return content;
|
|
188
|
-
}
|
|
189
|
-
if (!Array.isArray(content)) return "";
|
|
190
|
-
|
|
191
|
-
return content.map((part) => {
|
|
192
|
-
if (!part || typeof part !== "object") return "";
|
|
193
|
-
if (typeof part.text === "string") return part.text;
|
|
194
|
-
if (part.type === "tool-call" && typeof part.toolName === "string") return `Called ${part.toolName}`;
|
|
195
|
-
if (part.type === "tool-result") return "Received tool result";
|
|
196
|
-
if (part.type === "image" || part.type === "image_url") return "[Image]";
|
|
197
|
-
if (part.type === "file") return "[File]";
|
|
198
|
-
return "";
|
|
199
|
-
}).filter(Boolean).join(" ");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
211
|
/**
|
|
203
212
|
* Unwraps V2/V3 ToolResultOutput discriminated union to a displayable value.
|
|
204
213
|
* Stream ToolResult uses plain `result` (passthrough), while prompt ToolResultPart
|
|
@@ -223,16 +232,6 @@ function extractOutputValue(raw: unknown): unknown {
|
|
|
223
232
|
}
|
|
224
233
|
}
|
|
225
234
|
|
|
226
|
-
function buildTracePreviews(messages: any[]): { requestPreview: string; responsePreview: string } {
|
|
227
|
-
const request = [...messages].reverse().find((message) => message?.role === "user");
|
|
228
|
-
const response = [...messages].reverse().find((message) => message?.role === "assistant");
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
requestPreview: request ? getContentText(request.content).slice(0, 220) : "No request captured",
|
|
232
|
-
responsePreview: response ? getContentText(response.content).slice(0, 240) : "No response captured",
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
235
|
function buildTraceSpansFromMessages(messages: any[]): Array<{
|
|
237
236
|
spanKey: string;
|
|
238
237
|
parentSpanKey?: string;
|
|
@@ -328,6 +327,174 @@ const SESSION_KEY = Symbol.for("cl:session");
|
|
|
328
327
|
// Session key → prompt metadata (populated by cl.streamText/cl.generateText, read by middleware)
|
|
329
328
|
const sessionPromptMetadata = new Map<string, { promptSlug: string; promptVersion: number; promptId: string; tag?: string; abTestId?: string; variant?: "control" | "variant" }>();
|
|
330
329
|
|
|
330
|
+
function toAISDKTopicMemoryTool(
|
|
331
|
+
userId: string,
|
|
332
|
+
baseUrl: string,
|
|
333
|
+
apiKey: string,
|
|
334
|
+
): Record<string, unknown> {
|
|
335
|
+
const tool = createTopicMemoryTool({ apiKey, baseUrl });
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
description: tool.description,
|
|
339
|
+
inputSchema: tool.inputSchema,
|
|
340
|
+
execute: async (input: unknown) => {
|
|
341
|
+
const result = await tool.execute(input as any, {
|
|
342
|
+
abortSignal: new AbortController().signal,
|
|
343
|
+
resourceId: { userId },
|
|
344
|
+
metadata: undefined,
|
|
345
|
+
emit: () => {},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return tool.toModelOutput ? tool.toModelOutput(result) : result;
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function withAutoInjectedTools(
|
|
354
|
+
params: Record<string, unknown>,
|
|
355
|
+
session: { userId: string; projectId?: string; sessionId?: string } | undefined,
|
|
356
|
+
baseUrl: string,
|
|
357
|
+
apiKey: string,
|
|
358
|
+
options?: CognitiveToolOptions,
|
|
359
|
+
): Record<string, unknown> {
|
|
360
|
+
if (!options?.autoTopicMemoryTool || !isValidId(session?.userId)) {
|
|
361
|
+
return params;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const existingTools = params.tools;
|
|
365
|
+
if (existingTools && (typeof existingTools !== "object" || Array.isArray(existingTools))) {
|
|
366
|
+
return params;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const toolSet = { ...((existingTools as Record<string, unknown> | undefined) ?? {}) };
|
|
370
|
+
if (TOPIC_MEMORY_TOOL_ID in toolSet) {
|
|
371
|
+
return params;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
toolSet[TOPIC_MEMORY_TOOL_ID] = toAISDKTopicMemoryTool(session.userId, baseUrl, apiKey);
|
|
375
|
+
return {
|
|
376
|
+
...params,
|
|
377
|
+
tools: toolSet,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function toMessageEventStream(
|
|
382
|
+
result: { fullStream: AsyncIterable<any> },
|
|
383
|
+
): ReadableStream<{ event: string; data: unknown }> {
|
|
384
|
+
return new ReadableStream({
|
|
385
|
+
async start(controller) {
|
|
386
|
+
try {
|
|
387
|
+
for await (const chunk of result.fullStream) {
|
|
388
|
+
if (chunk.type === "text-delta" && chunk.delta) {
|
|
389
|
+
controller.enqueue({
|
|
390
|
+
event: "messages",
|
|
391
|
+
data: { token: chunk.delta },
|
|
392
|
+
});
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (chunk.type === "tool-call") {
|
|
397
|
+
controller.enqueue({
|
|
398
|
+
event: "messages",
|
|
399
|
+
data: {
|
|
400
|
+
type: "tool-call",
|
|
401
|
+
id: chunk.toolCallId,
|
|
402
|
+
name: chunk.toolName,
|
|
403
|
+
input: chunk.input,
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (chunk.type === "tool-result") {
|
|
410
|
+
controller.enqueue({
|
|
411
|
+
event: "messages",
|
|
412
|
+
data: {
|
|
413
|
+
type: "tool-result",
|
|
414
|
+
id: chunk.toolCallId,
|
|
415
|
+
name: chunk.toolName,
|
|
416
|
+
result: chunk.result,
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (chunk.type === "error") {
|
|
423
|
+
controller.enqueue({
|
|
424
|
+
event: "debug",
|
|
425
|
+
data: {
|
|
426
|
+
type: "error",
|
|
427
|
+
message: chunk.error instanceof Error ? chunk.error.message : String(chunk.error),
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} finally {
|
|
433
|
+
controller.close();
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function toGeneratedMessageEventStream(
|
|
440
|
+
result: {
|
|
441
|
+
text?: string;
|
|
442
|
+
toolCalls?: Array<{ toolCallId?: string; toolName?: string; input?: unknown }>;
|
|
443
|
+
toolResults?: Array<{ toolCallId?: string; toolName?: string; output?: unknown; result?: unknown; isError?: boolean }>;
|
|
444
|
+
steps?: Array<{
|
|
445
|
+
toolCalls?: Array<{ toolCallId?: string; toolName?: string; input?: unknown }>;
|
|
446
|
+
toolResults?: Array<{ toolCallId?: string; toolName?: string; output?: unknown; result?: unknown; isError?: boolean }>;
|
|
447
|
+
}>;
|
|
448
|
+
},
|
|
449
|
+
): ReadableStream<{ event: string; data: unknown }> {
|
|
450
|
+
return new ReadableStream({
|
|
451
|
+
start(controller) {
|
|
452
|
+
const steps = result.steps?.length
|
|
453
|
+
? result.steps
|
|
454
|
+
: [{
|
|
455
|
+
toolCalls: result.toolCalls,
|
|
456
|
+
toolResults: result.toolResults,
|
|
457
|
+
}];
|
|
458
|
+
|
|
459
|
+
for (const step of steps) {
|
|
460
|
+
for (const toolCall of step.toolCalls ?? []) {
|
|
461
|
+
controller.enqueue({
|
|
462
|
+
event: "messages",
|
|
463
|
+
data: {
|
|
464
|
+
type: "tool-call",
|
|
465
|
+
id: toolCall.toolCallId,
|
|
466
|
+
name: toolCall.toolName ?? "tool",
|
|
467
|
+
input: toolCall.input,
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const toolResult of step.toolResults ?? []) {
|
|
473
|
+
controller.enqueue({
|
|
474
|
+
event: "messages",
|
|
475
|
+
data: {
|
|
476
|
+
type: "tool-result",
|
|
477
|
+
id: toolResult.toolCallId,
|
|
478
|
+
name: toolResult.toolName ?? "tool",
|
|
479
|
+
result: toolResult.output ?? toolResult.result,
|
|
480
|
+
...(toolResult.isError ? { isError: true } : {}),
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (result.text) {
|
|
487
|
+
controller.enqueue({
|
|
488
|
+
event: "messages",
|
|
489
|
+
data: { token: result.text },
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
controller.close();
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
331
498
|
/**
|
|
332
499
|
* Check if any system message already contains a <MemoryContext> block.
|
|
333
500
|
*/
|
|
@@ -357,6 +524,11 @@ export function createCognitiveLayer(config: {
|
|
|
357
524
|
// Default to 'info' log level
|
|
358
525
|
const logLevel = clConfig.logLevel || 'info';
|
|
359
526
|
const logger = createLogger(logLevel);
|
|
527
|
+
const memoryClient = new MemoryClient({
|
|
528
|
+
apiKey: clConfig.apiKey,
|
|
529
|
+
baseUrl,
|
|
530
|
+
logger: logger as any,
|
|
531
|
+
});
|
|
360
532
|
|
|
361
533
|
const authHeaders = {
|
|
362
534
|
"Content-Type": "application/json",
|
|
@@ -368,6 +540,23 @@ export function createCognitiveLayer(config: {
|
|
|
368
540
|
apiKey: clConfig.apiKey,
|
|
369
541
|
logger,
|
|
370
542
|
});
|
|
543
|
+
const sessionTurnIndexes = new Map<string, number>();
|
|
544
|
+
|
|
545
|
+
const reserveTurnIndex = (
|
|
546
|
+
agentName: string,
|
|
547
|
+
userId: string | undefined,
|
|
548
|
+
sessionId: string | undefined,
|
|
549
|
+
requestedRunScope?: string,
|
|
550
|
+
): number | undefined => {
|
|
551
|
+
if (!isValidId(userId) || !isValidId(sessionId) || !isSessionScopedExecution(requestedRunScope, sessionId ? "session" : "invocation")) {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const key = `${agentName}:${getSessionKey(userId, sessionId)}`;
|
|
556
|
+
const nextIndex = sessionTurnIndexes.get(key) ?? 0;
|
|
557
|
+
sessionTurnIndexes.set(key, nextIndex + 1);
|
|
558
|
+
return nextIndex;
|
|
559
|
+
};
|
|
371
560
|
|
|
372
561
|
const resolvePrompt = async (slug: string, userIdOrOptions?: string | { userId?: string; tag?: string }): Promise<CachedPrompt> => {
|
|
373
562
|
const userId = typeof userIdOrOptions === "string"
|
|
@@ -409,7 +598,7 @@ export function createCognitiveLayer(config: {
|
|
|
409
598
|
|
|
410
599
|
const logConversation = async (payload: LogConversationPayload) => {
|
|
411
600
|
try {
|
|
412
|
-
await fetch(`${baseUrl}/api/cognitive/log`, {
|
|
601
|
+
const response = await fetch(`${baseUrl}/api/cognitive/log`, {
|
|
413
602
|
method: "POST",
|
|
414
603
|
headers: authHeaders,
|
|
415
604
|
body: JSON.stringify({
|
|
@@ -418,18 +607,74 @@ export function createCognitiveLayer(config: {
|
|
|
418
607
|
timestamp: new Date().toISOString(),
|
|
419
608
|
}),
|
|
420
609
|
});
|
|
610
|
+
if (!response.ok) {
|
|
611
|
+
logger.warn("Log failed", { status: response.status });
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
const data = await response.json().catch(() => null) as { generatedTitle?: unknown } | null;
|
|
615
|
+
return {
|
|
616
|
+
generatedTitle: typeof data?.generatedTitle === "string" ? data.generatedTitle : null,
|
|
617
|
+
};
|
|
421
618
|
} catch (e) {
|
|
422
619
|
logger.error("Log failed", e);
|
|
620
|
+
return null;
|
|
423
621
|
}
|
|
424
622
|
};
|
|
425
623
|
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
fetch(`${baseUrl}/api/cognitive/
|
|
624
|
+
const postAgentRun = async (payload: Record<string, unknown>) => {
|
|
625
|
+
try {
|
|
626
|
+
const response = await fetch(`${baseUrl}/api/cognitive/agent-run`, {
|
|
627
|
+
method: "POST",
|
|
628
|
+
headers: authHeaders,
|
|
629
|
+
body: JSON.stringify(payload),
|
|
630
|
+
});
|
|
631
|
+
if (!response.ok) {
|
|
632
|
+
logger.warn("Agent run request failed", { status: response.status });
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
return await response.json().catch(() => null);
|
|
636
|
+
} catch (e) {
|
|
637
|
+
logger.error("Agent run request failed", e);
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
const postTraceEvents = async (payload: Record<string, unknown>) => {
|
|
643
|
+
try {
|
|
644
|
+
const response = await fetch(`${baseUrl}/api/cognitive/trace-events`, {
|
|
429
645
|
method: "POST",
|
|
430
646
|
headers: authHeaders,
|
|
431
|
-
body: JSON.stringify(
|
|
432
|
-
})
|
|
647
|
+
body: JSON.stringify(payload),
|
|
648
|
+
});
|
|
649
|
+
if (!response.ok) {
|
|
650
|
+
logger.warn("Trace events request failed", { status: response.status });
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
return await response.json().catch(() => null);
|
|
654
|
+
} catch (e) {
|
|
655
|
+
logger.error("Trace events request failed", e);
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const appendLiveTraceEvents = async (payload: {
|
|
661
|
+
traceDbId?: string;
|
|
662
|
+
traceId: string;
|
|
663
|
+
sessionDbId?: string;
|
|
664
|
+
events: Array<{ eventType: string; spanKey?: string; status?: string; payload?: Record<string, unknown> }>;
|
|
665
|
+
}) => {
|
|
666
|
+
await postTraceEvents({
|
|
667
|
+
traceDbId: payload.traceDbId,
|
|
668
|
+
traceId: payload.traceId,
|
|
669
|
+
sessionDbId: payload.sessionDbId,
|
|
670
|
+
events: payload.events,
|
|
671
|
+
});
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
const triggerProcessing = (userId: string, sessionId: string) => {
|
|
675
|
+
const run = () => {
|
|
676
|
+
memoryClient.triggerProcessing(userId, sessionId)
|
|
677
|
+
.catch((e) => logger.error("Process trigger failed", e));
|
|
433
678
|
};
|
|
434
679
|
|
|
435
680
|
if (processDelay > 0) {
|
|
@@ -462,6 +707,33 @@ export function createCognitiveLayer(config: {
|
|
|
462
707
|
return { nextParams, messages: updated, mode: "prepend-system" };
|
|
463
708
|
};
|
|
464
709
|
|
|
710
|
+
const getKognitiveProviderConfig = (params: any): Record<string, unknown> | undefined => {
|
|
711
|
+
const providerOptionsKognitive = params?.providerOptions?.kognitive;
|
|
712
|
+
if (providerOptionsKognitive && typeof providerOptionsKognitive === "object") {
|
|
713
|
+
return providerOptionsKognitive as Record<string, unknown>;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const providerMetadataKognitive = params?.providerMetadata?.kognitive;
|
|
717
|
+
if (providerMetadataKognitive && typeof providerMetadataKognitive === "object") {
|
|
718
|
+
return providerMetadataKognitive as Record<string, unknown>;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return undefined;
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const resolveRequestedRunScope = (params: any): string | undefined => {
|
|
725
|
+
const direct = typeof params?.runScope === "string" ? params.runScope : undefined;
|
|
726
|
+
const kognitiveConfig = getKognitiveProviderConfig(params);
|
|
727
|
+
const providerMetadataScope = typeof kognitiveConfig?.runScope === "string"
|
|
728
|
+
? kognitiveConfig.runScope
|
|
729
|
+
: undefined;
|
|
730
|
+
return direct ?? providerMetadataScope;
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
const resolveAutomaticTitleConfig = (params: any): AutomaticThreadTitleConfig | undefined => {
|
|
734
|
+
return normalizeAutomaticThreadTitleConfig(getKognitiveProviderConfig(params)?.automaticTitle);
|
|
735
|
+
};
|
|
736
|
+
|
|
465
737
|
const buildMiddleware = (userId: string | undefined, projectId: string | undefined, sessionId: string | undefined, modelId: string) => ({
|
|
466
738
|
specificationVersion: 'v3' as const,
|
|
467
739
|
async transformParams({ params }: { params: any }) {
|
|
@@ -484,68 +756,38 @@ export function createCognitiveLayer(config: {
|
|
|
484
756
|
// 3) Fetch snapshot only if not cached
|
|
485
757
|
if (systemPromptToAdd === undefined) {
|
|
486
758
|
try {
|
|
487
|
-
const url = `${baseUrl}/api/cognitive/snapshot?userId=${userId}`;
|
|
488
759
|
logger.debug("Fetching snapshot from backend", {
|
|
489
760
|
userId,
|
|
490
761
|
sessionId,
|
|
491
|
-
url
|
|
762
|
+
url: `${baseUrl}/api/cognitive/snapshot?userId=${userId}`,
|
|
492
763
|
baseUrl,
|
|
493
764
|
apiKeyHint: maskSecret(clConfig.apiKey),
|
|
494
765
|
});
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
766
|
+
const snapshot = await memoryClient.getSnapshot(userId);
|
|
767
|
+
const systemBlock = snapshot?.systemBlock || "";
|
|
768
|
+
const userContextBlock = snapshot?.userContextBlock || "";
|
|
769
|
+
const topicIndexBlock = snapshot?.topicIndexBlock || "";
|
|
770
|
+
const topicContextBlock = snapshot?.topicContextBlock || "";
|
|
771
|
+
systemPromptToAdd = snapshot ? memoryClient.buildMemoryBlock(snapshot) : "";
|
|
772
|
+
|
|
773
|
+
sessionSnapshots.set(sessionKey, systemPromptToAdd);
|
|
774
|
+
|
|
775
|
+
logger.info("Snapshot fetched and cached", {
|
|
499
776
|
userId,
|
|
500
777
|
sessionId,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
778
|
+
sessionKey,
|
|
779
|
+
systemLen: systemBlock.length,
|
|
780
|
+
userLen: userContextBlock.length,
|
|
781
|
+
topicIndexLen: topicIndexBlock.length,
|
|
782
|
+
topicContextLen: topicContextBlock.length,
|
|
783
|
+
});
|
|
784
|
+
logger.debug("Full snapshot data", {
|
|
785
|
+
systemBlock,
|
|
786
|
+
userContextBlock,
|
|
787
|
+
topicIndexBlock,
|
|
788
|
+
topicContextBlock,
|
|
789
|
+
rawData: snapshot,
|
|
504
790
|
});
|
|
505
|
-
if (res.ok) {
|
|
506
|
-
const data = await res.json();
|
|
507
|
-
const systemBlock = data.systemBlock || "";
|
|
508
|
-
const userContextBlock = data.userContextBlock || "";
|
|
509
|
-
systemPromptToAdd =
|
|
510
|
-
systemBlock !== "" || userContextBlock !== ""
|
|
511
|
-
? `
|
|
512
|
-
<MemoryContext>
|
|
513
|
-
Use the following memory to stay consistent. Prefer UserContext facts for answers; AgentHeuristics guide style, safety, and priorities.
|
|
514
|
-
${systemBlock || "None"}
|
|
515
|
-
${userContextBlock || "None"}
|
|
516
|
-
</MemoryContext>
|
|
517
|
-
`.trim()
|
|
518
|
-
: "";
|
|
519
|
-
|
|
520
|
-
// Cache the snapshot for this session
|
|
521
|
-
sessionSnapshots.set(sessionKey, systemPromptToAdd);
|
|
522
|
-
|
|
523
|
-
logger.info("Snapshot fetched and cached", {
|
|
524
|
-
userId,
|
|
525
|
-
sessionId,
|
|
526
|
-
sessionKey,
|
|
527
|
-
systemLen: systemBlock.length,
|
|
528
|
-
userLen: userContextBlock.length,
|
|
529
|
-
});
|
|
530
|
-
// At debug level, log the full snapshot data
|
|
531
|
-
logger.debug("Full snapshot data", {
|
|
532
|
-
systemBlock,
|
|
533
|
-
userContextBlock,
|
|
534
|
-
rawData: data,
|
|
535
|
-
});
|
|
536
|
-
} else {
|
|
537
|
-
const body = await res.text();
|
|
538
|
-
logger.warn("Snapshot fetch failed", { status: res.status });
|
|
539
|
-
logger.debug("Snapshot response body preview", {
|
|
540
|
-
userId,
|
|
541
|
-
projectId,
|
|
542
|
-
sessionId,
|
|
543
|
-
status: res.status,
|
|
544
|
-
bodyPreview: previewText(body),
|
|
545
|
-
});
|
|
546
|
-
systemPromptToAdd = "";
|
|
547
|
-
sessionSnapshots.set(sessionKey, systemPromptToAdd);
|
|
548
|
-
}
|
|
549
791
|
} catch (e) {
|
|
550
792
|
logger.warn("Failed to fetch snapshot", e);
|
|
551
793
|
systemPromptToAdd = "";
|
|
@@ -576,10 +818,86 @@ ${userContextBlock || "None"}
|
|
|
576
818
|
|
|
577
819
|
async wrapGenerate({ doGenerate, params }: { doGenerate: any; params: any }) {
|
|
578
820
|
const startedAt = new Date();
|
|
821
|
+
const requestedRunScope = resolveRequestedRunScope(params);
|
|
822
|
+
const remoteAgentName = clConfig.appId ?? modelId;
|
|
823
|
+
const turnIndex = reserveTurnIndex(remoteAgentName, userId, sessionId, requestedRunScope);
|
|
824
|
+
const remoteExecution = createRemoteExecutionContext({
|
|
825
|
+
agentName: remoteAgentName,
|
|
826
|
+
sessionId,
|
|
827
|
+
requestedRunScope,
|
|
828
|
+
runId: getKognitiveProviderConfig(params)?.agentRunId as string | undefined,
|
|
829
|
+
turnIndex,
|
|
830
|
+
});
|
|
831
|
+
const traceId = remoteExecution.traceId;
|
|
832
|
+
let liveTraceDbId: string | undefined;
|
|
833
|
+
if (isValidId(userId) && isValidId(sessionId)) {
|
|
834
|
+
const messagesInput = (params as any).prompt || (params as any).messages || [];
|
|
835
|
+
const { requestPreview } = buildRemoteTracePreviews(messagesInput);
|
|
836
|
+
await postAgentRun(buildRemoteRunPayload({
|
|
837
|
+
execution: remoteExecution,
|
|
838
|
+
userId,
|
|
839
|
+
sessionId,
|
|
840
|
+
modelId,
|
|
841
|
+
status: "running",
|
|
842
|
+
inputPreview: requestPreview,
|
|
843
|
+
startedAt: startedAt.toISOString(),
|
|
844
|
+
metadata: {
|
|
845
|
+
appId: clConfig.appId,
|
|
846
|
+
},
|
|
847
|
+
sessionMetadata: {
|
|
848
|
+
kind: "chat",
|
|
849
|
+
agentName: remoteExecution.agentName,
|
|
850
|
+
},
|
|
851
|
+
}));
|
|
852
|
+
}
|
|
853
|
+
if (isValidId(userId) && isValidId(sessionId) && isValidId(projectId)) {
|
|
854
|
+
const messagesInput = (params as any).prompt || (params as any).messages || [];
|
|
855
|
+
const { requestPreview } = buildRemoteTracePreviews(messagesInput);
|
|
856
|
+
const traceResponse = await postTraceEvents(buildRemoteTraceStartPayload({
|
|
857
|
+
execution: remoteExecution,
|
|
858
|
+
userId,
|
|
859
|
+
projectId,
|
|
860
|
+
sessionId,
|
|
861
|
+
requestPreview,
|
|
862
|
+
modelId,
|
|
863
|
+
metadata: {
|
|
864
|
+
appId: clConfig.appId,
|
|
865
|
+
},
|
|
866
|
+
startedAt: startedAt.toISOString(),
|
|
867
|
+
}));
|
|
868
|
+
liveTraceDbId = typeof traceResponse?.traceDbId === "string" ? traceResponse.traceDbId : undefined;
|
|
869
|
+
}
|
|
579
870
|
let result;
|
|
580
871
|
try {
|
|
581
872
|
result = await doGenerate();
|
|
582
873
|
} catch (err) {
|
|
874
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
875
|
+
if (liveTraceDbId) {
|
|
876
|
+
await postTraceEvents(buildRemoteTraceFinishPayload({
|
|
877
|
+
execution: remoteExecution,
|
|
878
|
+
traceDbId: liveTraceDbId,
|
|
879
|
+
state: "error",
|
|
880
|
+
responsePreview: errorMessage.slice(0, 240),
|
|
881
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
882
|
+
errorMessage,
|
|
883
|
+
}));
|
|
884
|
+
}
|
|
885
|
+
if (isValidId(userId) && isValidId(sessionId)) {
|
|
886
|
+
await postAgentRun(buildRemoteRunPayload({
|
|
887
|
+
execution: remoteExecution,
|
|
888
|
+
userId,
|
|
889
|
+
sessionId,
|
|
890
|
+
modelId,
|
|
891
|
+
status: "failed",
|
|
892
|
+
errorMessage,
|
|
893
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
894
|
+
startedAt: startedAt.toISOString(),
|
|
895
|
+
completedAt: new Date().toISOString(),
|
|
896
|
+
metadata: {
|
|
897
|
+
appId: clConfig.appId,
|
|
898
|
+
},
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
583
901
|
logger.error("doGenerate failed", err);
|
|
584
902
|
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));
|
|
585
903
|
throw err;
|
|
@@ -589,7 +907,7 @@ ${userContextBlock || "None"}
|
|
|
589
907
|
const endedAt = new Date();
|
|
590
908
|
const sessionKey = getSessionKey(userId, sessionId);
|
|
591
909
|
const promptMeta = sessionPromptMetadata.get(sessionKey);
|
|
592
|
-
const
|
|
910
|
+
const automaticTitle = resolveAutomaticTitleConfig(params);
|
|
593
911
|
|
|
594
912
|
const messagesInput = (params as any).prompt || (params as any).messages || [];
|
|
595
913
|
|
|
@@ -619,11 +937,39 @@ ${userContextBlock || "None"}
|
|
|
619
937
|
? [{ role: "assistant", content: assistantParts }]
|
|
620
938
|
: [];
|
|
621
939
|
const finalMessages = [...messagesInput, ...assistantMessage];
|
|
622
|
-
const { requestPreview, responsePreview } =
|
|
940
|
+
const { requestPreview, responsePreview } = buildRemoteTracePreviews(finalMessages);
|
|
623
941
|
const spans = buildTraceSpansFromMessages(finalMessages);
|
|
624
942
|
const toolDefs = extractToolDefinitions(params);
|
|
943
|
+
const usage = normalizeRemoteUsage(result.usage);
|
|
944
|
+
|
|
945
|
+
await postAgentRun(buildRemoteRunPayload({
|
|
946
|
+
execution: remoteExecution,
|
|
947
|
+
userId,
|
|
948
|
+
sessionId,
|
|
949
|
+
modelId,
|
|
950
|
+
status: "completed",
|
|
951
|
+
inputPreview: requestPreview,
|
|
952
|
+
outputPreview: responsePreview,
|
|
953
|
+
inputTokens: usage.inputTokens,
|
|
954
|
+
outputTokens: usage.outputTokens,
|
|
955
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
956
|
+
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
957
|
+
startedAt: startedAt.toISOString(),
|
|
958
|
+
completedAt: endedAt.toISOString(),
|
|
959
|
+
metadata: {
|
|
960
|
+
appId: clConfig.appId,
|
|
961
|
+
...(promptMeta?.tag && { promptTag: promptMeta.tag }),
|
|
962
|
+
...(promptMeta?.abTestId && { abTestId: promptMeta.abTestId }),
|
|
963
|
+
...(promptMeta?.variant && { variant: promptMeta.variant }),
|
|
964
|
+
},
|
|
965
|
+
sessionMetadata: {
|
|
966
|
+
kind: "chat",
|
|
967
|
+
agentName: remoteExecution.agentName,
|
|
968
|
+
},
|
|
969
|
+
}));
|
|
625
970
|
|
|
626
|
-
logConversation({
|
|
971
|
+
const logResult = await logConversation(buildRemoteLogPayload({
|
|
972
|
+
execution: remoteExecution,
|
|
627
973
|
userId,
|
|
628
974
|
sessionId,
|
|
629
975
|
messages: finalMessages,
|
|
@@ -638,8 +984,6 @@ ${userContextBlock || "None"}
|
|
|
638
984
|
variant: promptMeta.variant,
|
|
639
985
|
}),
|
|
640
986
|
...(toolDefs && { tools: toolDefs }),
|
|
641
|
-
...(agentRunId && { agentRunId }),
|
|
642
|
-
traceId: randomUUID(),
|
|
643
987
|
requestPreview,
|
|
644
988
|
responsePreview,
|
|
645
989
|
state: "completed",
|
|
@@ -648,24 +992,111 @@ ${userContextBlock || "None"}
|
|
|
648
992
|
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
649
993
|
metadata: {
|
|
650
994
|
appId: clConfig.appId,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
995
|
+
...(promptMeta?.tag && { promptTag: promptMeta.tag }),
|
|
996
|
+
...(promptMeta?.abTestId && { abTestId: promptMeta.abTestId }),
|
|
997
|
+
...(promptMeta?.variant && { variant: promptMeta.variant }),
|
|
654
998
|
},
|
|
655
999
|
spans,
|
|
656
|
-
|
|
1000
|
+
automaticTitle,
|
|
1001
|
+
}) as unknown as LogConversationPayload);
|
|
1002
|
+
if (logResult) {
|
|
1003
|
+
triggerProcessing(userId, sessionId);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
await postTraceEvents(buildRemoteTraceFinishPayload({
|
|
1007
|
+
execution: remoteExecution,
|
|
1008
|
+
traceDbId: liveTraceDbId,
|
|
1009
|
+
state: "completed",
|
|
1010
|
+
responsePreview,
|
|
1011
|
+
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
1012
|
+
usage: result.usage,
|
|
1013
|
+
}));
|
|
657
1014
|
}
|
|
658
1015
|
|
|
659
1016
|
return result;
|
|
660
1017
|
},
|
|
661
1018
|
async wrapStream({ doStream, params }: { doStream: any; params: any }) {
|
|
662
1019
|
const startedAt = new Date();
|
|
663
|
-
const
|
|
1020
|
+
const requestedRunScope = resolveRequestedRunScope(params);
|
|
1021
|
+
const remoteAgentName = clConfig.appId ?? modelId;
|
|
1022
|
+
const turnIndex = reserveTurnIndex(remoteAgentName, userId, sessionId, requestedRunScope);
|
|
1023
|
+
const remoteExecution = createRemoteExecutionContext({
|
|
1024
|
+
agentName: remoteAgentName,
|
|
1025
|
+
sessionId,
|
|
1026
|
+
requestedRunScope,
|
|
1027
|
+
runId: getKognitiveProviderConfig(params)?.agentRunId as string | undefined,
|
|
1028
|
+
turnIndex,
|
|
1029
|
+
});
|
|
1030
|
+
const traceId = remoteExecution.traceId;
|
|
1031
|
+
let liveTraceDbId: string | undefined;
|
|
1032
|
+
if (isValidId(userId) && isValidId(sessionId)) {
|
|
1033
|
+
const messagesInput = (params as any).prompt || (params as any).messages || [];
|
|
1034
|
+
const { requestPreview } = buildRemoteTracePreviews(messagesInput);
|
|
1035
|
+
await postAgentRun(buildRemoteRunPayload({
|
|
1036
|
+
execution: remoteExecution,
|
|
1037
|
+
userId,
|
|
1038
|
+
sessionId,
|
|
1039
|
+
modelId,
|
|
1040
|
+
status: "running",
|
|
1041
|
+
inputPreview: requestPreview,
|
|
1042
|
+
startedAt: startedAt.toISOString(),
|
|
1043
|
+
metadata: {
|
|
1044
|
+
appId: clConfig.appId,
|
|
1045
|
+
},
|
|
1046
|
+
sessionMetadata: {
|
|
1047
|
+
kind: "chat",
|
|
1048
|
+
agentName: remoteExecution.agentName,
|
|
1049
|
+
},
|
|
1050
|
+
}));
|
|
1051
|
+
}
|
|
1052
|
+
if (isValidId(userId) && isValidId(sessionId) && isValidId(projectId)) {
|
|
1053
|
+
const messagesInput = (params as any).prompt || (params as any).messages || [];
|
|
1054
|
+
const { requestPreview } = buildRemoteTracePreviews(messagesInput);
|
|
1055
|
+
const traceResponse = await postTraceEvents(buildRemoteTraceStartPayload({
|
|
1056
|
+
execution: remoteExecution,
|
|
1057
|
+
userId,
|
|
1058
|
+
projectId,
|
|
1059
|
+
sessionId,
|
|
1060
|
+
requestPreview,
|
|
1061
|
+
modelId,
|
|
1062
|
+
metadata: {
|
|
1063
|
+
appId: clConfig.appId,
|
|
1064
|
+
},
|
|
1065
|
+
startedAt: startedAt.toISOString(),
|
|
1066
|
+
}));
|
|
1067
|
+
liveTraceDbId = typeof traceResponse?.traceDbId === "string" ? traceResponse.traceDbId : undefined;
|
|
1068
|
+
}
|
|
664
1069
|
let result;
|
|
665
1070
|
try {
|
|
666
1071
|
logger.debug("Starting doStream with params", JSON.stringify(params, null, 2));
|
|
667
1072
|
result = await doStream();
|
|
668
1073
|
} catch (err) {
|
|
1074
|
+
if (liveTraceDbId) {
|
|
1075
|
+
await postTraceEvents(buildRemoteTraceFinishPayload({
|
|
1076
|
+
execution: remoteExecution,
|
|
1077
|
+
traceDbId: liveTraceDbId,
|
|
1078
|
+
state: "error",
|
|
1079
|
+
responsePreview: err instanceof Error ? err.message.slice(0, 240) : "Stream failed",
|
|
1080
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
1081
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1082
|
+
}));
|
|
1083
|
+
}
|
|
1084
|
+
if (isValidId(userId) && isValidId(sessionId)) {
|
|
1085
|
+
await postAgentRun(buildRemoteRunPayload({
|
|
1086
|
+
execution: remoteExecution,
|
|
1087
|
+
userId,
|
|
1088
|
+
sessionId,
|
|
1089
|
+
modelId,
|
|
1090
|
+
status: "failed",
|
|
1091
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1092
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
1093
|
+
startedAt: startedAt.toISOString(),
|
|
1094
|
+
completedAt: new Date().toISOString(),
|
|
1095
|
+
metadata: {
|
|
1096
|
+
appId: clConfig.appId,
|
|
1097
|
+
},
|
|
1098
|
+
}));
|
|
1099
|
+
}
|
|
669
1100
|
console.log((err as TypeError).cause)
|
|
670
1101
|
console.log((err as TypeError).stack)
|
|
671
1102
|
logger.error("doStream failed", err);
|
|
@@ -676,7 +1107,7 @@ ${userContextBlock || "None"}
|
|
|
676
1107
|
if (isValidId(userId) && isValidId(sessionId)) {
|
|
677
1108
|
const sessionKey = getSessionKey(userId, sessionId);
|
|
678
1109
|
const promptMeta = sessionPromptMetadata.get(sessionKey);
|
|
679
|
-
const
|
|
1110
|
+
const automaticTitle = resolveAutomaticTitleConfig(params);
|
|
680
1111
|
|
|
681
1112
|
const messagesInput = (params as any).prompt || (params as any).messages || [];
|
|
682
1113
|
const resultMessages = (result as any)?.response?.messages;
|
|
@@ -686,7 +1117,10 @@ ${userContextBlock || "None"}
|
|
|
686
1117
|
|
|
687
1118
|
let streamUsage: Record<string, unknown> | undefined;
|
|
688
1119
|
let accumulatedText = '';
|
|
1120
|
+
let pendingProgressDelta = '';
|
|
1121
|
+
let lastProgressAt = Date.now();
|
|
689
1122
|
const toolCallInputs = new Map<string, { toolName: string; chunks: string[] }>();
|
|
1123
|
+
const startedToolCalls = new Set<string>();
|
|
690
1124
|
const completedToolCalls: any[] = [];
|
|
691
1125
|
const completedToolResults: any[] = [];
|
|
692
1126
|
|
|
@@ -695,6 +1129,23 @@ ${userContextBlock || "None"}
|
|
|
695
1129
|
transform(chunk, controller) {
|
|
696
1130
|
if (chunk.type === 'text-delta') {
|
|
697
1131
|
accumulatedText += chunk.delta;
|
|
1132
|
+
pendingProgressDelta += chunk.delta;
|
|
1133
|
+
if (liveTraceDbId && pendingProgressDelta && (pendingProgressDelta.length >= 80 || Date.now() - lastProgressAt >= 250)) {
|
|
1134
|
+
void appendLiveTraceEvents({
|
|
1135
|
+
traceDbId: liveTraceDbId,
|
|
1136
|
+
traceId,
|
|
1137
|
+
events: [{
|
|
1138
|
+
eventType: "assistant.progress",
|
|
1139
|
+
status: "active",
|
|
1140
|
+
payload: {
|
|
1141
|
+
text: pendingProgressDelta,
|
|
1142
|
+
preview: previewText(accumulatedText),
|
|
1143
|
+
},
|
|
1144
|
+
}],
|
|
1145
|
+
});
|
|
1146
|
+
pendingProgressDelta = '';
|
|
1147
|
+
lastProgressAt = Date.now();
|
|
1148
|
+
}
|
|
698
1149
|
}
|
|
699
1150
|
if (chunk.type === 'finish' && chunk.usage) {
|
|
700
1151
|
streamUsage = chunk.usage;
|
|
@@ -702,6 +1153,23 @@ ${userContextBlock || "None"}
|
|
|
702
1153
|
// Capture tool-call stream chunks (V2/V3 shared types)
|
|
703
1154
|
if (chunk.type === 'tool-input-start') {
|
|
704
1155
|
toolCallInputs.set(chunk.id, { toolName: chunk.toolName, chunks: [] });
|
|
1156
|
+
if (liveTraceDbId && !startedToolCalls.has(chunk.id)) {
|
|
1157
|
+
startedToolCalls.add(chunk.id);
|
|
1158
|
+
void appendLiveTraceEvents({
|
|
1159
|
+
traceDbId: liveTraceDbId,
|
|
1160
|
+
traceId,
|
|
1161
|
+
events: [{
|
|
1162
|
+
eventType: "tool.started",
|
|
1163
|
+
spanKey: chunk.id,
|
|
1164
|
+
status: "active",
|
|
1165
|
+
payload: {
|
|
1166
|
+
toolCallId: chunk.id,
|
|
1167
|
+
toolName: chunk.toolName,
|
|
1168
|
+
inputPreview: "",
|
|
1169
|
+
},
|
|
1170
|
+
}],
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
705
1173
|
}
|
|
706
1174
|
if (chunk.type === 'tool-input-delta') {
|
|
707
1175
|
const entry = toolCallInputs.get(chunk.id);
|
|
@@ -714,6 +1182,29 @@ ${userContextBlock || "None"}
|
|
|
714
1182
|
toolName: chunk.toolName,
|
|
715
1183
|
input: chunk.input,
|
|
716
1184
|
});
|
|
1185
|
+
if (!toolCallInputs.has(chunk.toolCallId)) {
|
|
1186
|
+
toolCallInputs.set(chunk.toolCallId, {
|
|
1187
|
+
toolName: chunk.toolName,
|
|
1188
|
+
chunks: typeof chunk.input === "string" ? [chunk.input] : [],
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
if (liveTraceDbId && !startedToolCalls.has(chunk.toolCallId)) {
|
|
1192
|
+
startedToolCalls.add(chunk.toolCallId);
|
|
1193
|
+
void appendLiveTraceEvents({
|
|
1194
|
+
traceDbId: liveTraceDbId,
|
|
1195
|
+
traceId,
|
|
1196
|
+
events: [{
|
|
1197
|
+
eventType: "tool.started",
|
|
1198
|
+
spanKey: chunk.toolCallId,
|
|
1199
|
+
status: "active",
|
|
1200
|
+
payload: {
|
|
1201
|
+
toolCallId: chunk.toolCallId,
|
|
1202
|
+
toolName: chunk.toolName,
|
|
1203
|
+
inputPreview: typeof chunk.input === "string" ? previewText(chunk.input) : "",
|
|
1204
|
+
},
|
|
1205
|
+
}],
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
717
1208
|
}
|
|
718
1209
|
if (chunk.type === 'tool-result') {
|
|
719
1210
|
completedToolResults.push({
|
|
@@ -722,11 +1213,41 @@ ${userContextBlock || "None"}
|
|
|
722
1213
|
toolName: chunk.toolName,
|
|
723
1214
|
result: chunk.result,
|
|
724
1215
|
});
|
|
1216
|
+
if (liveTraceDbId) {
|
|
1217
|
+
void appendLiveTraceEvents({
|
|
1218
|
+
traceDbId: liveTraceDbId,
|
|
1219
|
+
traceId,
|
|
1220
|
+
events: [{
|
|
1221
|
+
eventType: "tool.completed",
|
|
1222
|
+
spanKey: chunk.toolCallId,
|
|
1223
|
+
status: "completed",
|
|
1224
|
+
payload: {
|
|
1225
|
+
toolCallId: chunk.toolCallId,
|
|
1226
|
+
toolName: chunk.toolName,
|
|
1227
|
+
outputPreview: previewText(JSON.stringify(chunk.result)),
|
|
1228
|
+
},
|
|
1229
|
+
}],
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
725
1232
|
}
|
|
726
1233
|
controller.enqueue(chunk);
|
|
727
1234
|
},
|
|
728
1235
|
async flush() {
|
|
729
1236
|
const endedAt = new Date();
|
|
1237
|
+
if (liveTraceDbId && pendingProgressDelta) {
|
|
1238
|
+
await appendLiveTraceEvents({
|
|
1239
|
+
traceDbId: liveTraceDbId,
|
|
1240
|
+
traceId,
|
|
1241
|
+
events: [{
|
|
1242
|
+
eventType: "assistant.progress",
|
|
1243
|
+
status: "active",
|
|
1244
|
+
payload: {
|
|
1245
|
+
text: pendingProgressDelta,
|
|
1246
|
+
preview: previewText(accumulatedText),
|
|
1247
|
+
},
|
|
1248
|
+
}],
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
730
1251
|
|
|
731
1252
|
// Finalize any tool calls from incremental input chunks
|
|
732
1253
|
for (const [id, entry] of toolCallInputs) {
|
|
@@ -753,14 +1274,40 @@ ${userContextBlock || "None"}
|
|
|
753
1274
|
allMessages.push({ role: "tool", content: completedToolResults });
|
|
754
1275
|
}
|
|
755
1276
|
|
|
756
|
-
const { requestPreview, responsePreview } =
|
|
1277
|
+
const { requestPreview, responsePreview } = buildRemoteTracePreviews(allMessages);
|
|
757
1278
|
const spans = buildTraceSpansFromMessages(allMessages);
|
|
758
1279
|
const toolDefs = extractToolDefinitions(params);
|
|
1280
|
+
const usage = normalizeRemoteUsage(streamUsage);
|
|
759
1281
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1282
|
+
await postAgentRun(buildRemoteRunPayload({
|
|
1283
|
+
execution: remoteExecution,
|
|
1284
|
+
userId,
|
|
1285
|
+
sessionId,
|
|
1286
|
+
modelId,
|
|
1287
|
+
status: "completed",
|
|
1288
|
+
inputPreview: requestPreview,
|
|
1289
|
+
outputPreview: responsePreview,
|
|
1290
|
+
inputTokens: usage.inputTokens,
|
|
1291
|
+
outputTokens: usage.outputTokens,
|
|
1292
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
1293
|
+
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
1294
|
+
startedAt: startedAt.toISOString(),
|
|
1295
|
+
completedAt: endedAt.toISOString(),
|
|
1296
|
+
metadata: {
|
|
1297
|
+
appId: clConfig.appId,
|
|
1298
|
+
...(promptMeta?.tag && { promptTag: promptMeta.tag }),
|
|
1299
|
+
...(promptMeta?.abTestId && { abTestId: promptMeta.abTestId }),
|
|
1300
|
+
...(promptMeta?.variant && { variant: promptMeta.variant }),
|
|
1301
|
+
},
|
|
1302
|
+
sessionMetadata: {
|
|
1303
|
+
kind: "chat",
|
|
1304
|
+
agentName: remoteExecution.agentName,
|
|
1305
|
+
},
|
|
1306
|
+
}));
|
|
1307
|
+
|
|
1308
|
+
const logResult = await logConversation(buildRemoteLogPayload({
|
|
1309
|
+
execution: remoteExecution,
|
|
1310
|
+
userId,
|
|
764
1311
|
sessionId,
|
|
765
1312
|
messages: allMessages,
|
|
766
1313
|
modelId,
|
|
@@ -774,8 +1321,6 @@ ${userContextBlock || "None"}
|
|
|
774
1321
|
variant: promptMeta.variant,
|
|
775
1322
|
}),
|
|
776
1323
|
...(toolDefs && { tools: toolDefs }),
|
|
777
|
-
...(agentRunId && { agentRunId }),
|
|
778
|
-
traceId,
|
|
779
1324
|
requestPreview,
|
|
780
1325
|
responsePreview,
|
|
781
1326
|
state: "completed",
|
|
@@ -789,8 +1334,36 @@ ${userContextBlock || "None"}
|
|
|
789
1334
|
...(promptMeta?.variant && { variant: promptMeta.variant }),
|
|
790
1335
|
},
|
|
791
1336
|
spans,
|
|
792
|
-
|
|
793
|
-
|
|
1337
|
+
automaticTitle,
|
|
1338
|
+
}) as unknown as LogConversationPayload);
|
|
1339
|
+
if (logResult) {
|
|
1340
|
+
triggerProcessing(userId, sessionId);
|
|
1341
|
+
}
|
|
1342
|
+
if (logResult?.generatedTitle) {
|
|
1343
|
+
const currentProviderMetadata = result.providerMetadata && typeof result.providerMetadata === "object"
|
|
1344
|
+
? result.providerMetadata as Record<string, unknown>
|
|
1345
|
+
: {};
|
|
1346
|
+
const currentKognitiveMetadata = currentProviderMetadata.kognitive
|
|
1347
|
+
&& typeof currentProviderMetadata.kognitive === "object"
|
|
1348
|
+
? currentProviderMetadata.kognitive as Record<string, unknown>
|
|
1349
|
+
: {};
|
|
1350
|
+
result.providerMetadata = {
|
|
1351
|
+
...currentProviderMetadata,
|
|
1352
|
+
kognitive: {
|
|
1353
|
+
...currentKognitiveMetadata,
|
|
1354
|
+
generatedTitle: logResult.generatedTitle,
|
|
1355
|
+
},
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
await postTraceEvents(buildRemoteTraceFinishPayload({
|
|
1360
|
+
execution: remoteExecution,
|
|
1361
|
+
traceDbId: liveTraceDbId,
|
|
1362
|
+
state: "completed",
|
|
1363
|
+
responsePreview,
|
|
1364
|
+
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
1365
|
+
usage: streamUsage,
|
|
1366
|
+
}));
|
|
794
1367
|
}
|
|
795
1368
|
});
|
|
796
1369
|
|
|
@@ -865,25 +1438,30 @@ ${userContextBlock || "None"}
|
|
|
865
1438
|
};
|
|
866
1439
|
|
|
867
1440
|
const clStreamText = async (options: CLStreamTextOptions) => {
|
|
868
|
-
const { prompt: promptConfig, ...rest } = options;
|
|
869
|
-
|
|
870
|
-
const session = (options.model as any)[SESSION_KEY] as { userId: string; projectId?: string; sessionId?: string } | undefined;
|
|
1441
|
+
const { prompt: promptConfig, kognitive, ...rest } = options;
|
|
1442
|
+
const session = (options.model as any)[SESSION_KEY] as { userId: string; projectId?: string; sessionId?: string } | undefined;
|
|
871
1443
|
|
|
872
1444
|
// Resolve and interpolate prompt (graceful fallback on failure)
|
|
873
1445
|
let resolved: CachedPrompt | null = null;
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1446
|
+
if (promptConfig?.slug) {
|
|
1447
|
+
try {
|
|
1448
|
+
resolved = await resolvePrompt(promptConfig.slug, {
|
|
1449
|
+
userId: session?.userId,
|
|
1450
|
+
tag: promptConfig.tag,
|
|
1451
|
+
});
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
if (isModerationError(err)) {
|
|
1454
|
+
throw err;
|
|
1455
|
+
}
|
|
1456
|
+
logger.warn(`Failed to resolve prompt "${promptConfig.slug}", streaming without system prompt.`, err);
|
|
1457
|
+
}
|
|
881
1458
|
}
|
|
882
1459
|
|
|
883
|
-
let system
|
|
1460
|
+
let system = typeof rest.system === "string" ? rest.system : undefined;
|
|
884
1461
|
if (resolved) {
|
|
885
|
-
|
|
886
|
-
|
|
1462
|
+
const resolvedPromptConfig = promptConfig!;
|
|
1463
|
+
system = resolvedPromptConfig.variables
|
|
1464
|
+
? renderTemplate(resolved.content, resolvedPromptConfig.variables)
|
|
887
1465
|
: resolved.content;
|
|
888
1466
|
|
|
889
1467
|
// Store prompt metadata for the session (read by middleware during logging)
|
|
@@ -900,40 +1478,52 @@ ${userContextBlock || "None"}
|
|
|
900
1478
|
}
|
|
901
1479
|
|
|
902
1480
|
logger.info("cl.streamText called", {
|
|
903
|
-
slug:
|
|
1481
|
+
slug: resolvedPromptConfig.slug,
|
|
904
1482
|
version: resolved.version,
|
|
905
1483
|
systemLength: system.length,
|
|
906
1484
|
});
|
|
907
|
-
} else {
|
|
1485
|
+
} else if (promptConfig?.slug) {
|
|
908
1486
|
logger.info("cl.streamText called without resolved prompt", {
|
|
909
1487
|
slug: promptConfig.slug,
|
|
910
1488
|
});
|
|
911
1489
|
}
|
|
912
1490
|
|
|
913
1491
|
const model = resolveModel(options.model, resolved?.gatewaySlug);
|
|
914
|
-
|
|
1492
|
+
const nextParams = withAutoInjectedTools(
|
|
1493
|
+
{ ...rest, model, ...(system && { system }) },
|
|
1494
|
+
session,
|
|
1495
|
+
baseUrl,
|
|
1496
|
+
clConfig.apiKey,
|
|
1497
|
+
kognitive,
|
|
1498
|
+
);
|
|
1499
|
+
return aiStreamText(nextParams as any);
|
|
915
1500
|
};
|
|
916
1501
|
|
|
917
1502
|
const clGenerateText = async (options: CLGenerateTextOptions) => {
|
|
918
|
-
const { prompt: promptConfig, ...rest } = options;
|
|
919
|
-
|
|
920
|
-
const session = (options.model as any)[SESSION_KEY] as { userId: string; projectId?: string; sessionId?: string } | undefined;
|
|
1503
|
+
const { prompt: promptConfig, kognitive, ...rest } = options;
|
|
1504
|
+
const session = (options.model as any)[SESSION_KEY] as { userId: string; projectId?: string; sessionId?: string } | undefined;
|
|
921
1505
|
|
|
922
1506
|
// Resolve and interpolate prompt (graceful fallback on failure)
|
|
923
1507
|
let resolved: CachedPrompt | null = null;
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1508
|
+
if (promptConfig?.slug) {
|
|
1509
|
+
try {
|
|
1510
|
+
resolved = await resolvePrompt(promptConfig.slug, {
|
|
1511
|
+
userId: session?.userId,
|
|
1512
|
+
tag: promptConfig.tag,
|
|
1513
|
+
});
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
if (isModerationError(err)) {
|
|
1516
|
+
throw err;
|
|
1517
|
+
}
|
|
1518
|
+
logger.warn(`Failed to resolve prompt "${promptConfig.slug}", generating without system prompt.`, err);
|
|
1519
|
+
}
|
|
931
1520
|
}
|
|
932
1521
|
|
|
933
|
-
let system
|
|
1522
|
+
let system = typeof rest.system === "string" ? rest.system : undefined;
|
|
934
1523
|
if (resolved) {
|
|
935
|
-
|
|
936
|
-
|
|
1524
|
+
const resolvedPromptConfig = promptConfig!;
|
|
1525
|
+
system = resolvedPromptConfig.variables
|
|
1526
|
+
? renderTemplate(resolved.content, resolvedPromptConfig.variables)
|
|
937
1527
|
: resolved.content;
|
|
938
1528
|
|
|
939
1529
|
// Store prompt metadata for the session (read by middleware during logging)
|
|
@@ -950,18 +1540,25 @@ ${userContextBlock || "None"}
|
|
|
950
1540
|
}
|
|
951
1541
|
|
|
952
1542
|
logger.info("cl.generateText called", {
|
|
953
|
-
slug:
|
|
1543
|
+
slug: resolvedPromptConfig.slug,
|
|
954
1544
|
version: resolved.version,
|
|
955
1545
|
systemLength: system.length,
|
|
956
1546
|
});
|
|
957
|
-
} else {
|
|
1547
|
+
} else if (promptConfig?.slug) {
|
|
958
1548
|
logger.info("cl.generateText called without resolved prompt", {
|
|
959
1549
|
slug: promptConfig.slug,
|
|
960
1550
|
});
|
|
961
1551
|
}
|
|
962
1552
|
|
|
963
1553
|
const model = resolveModel(options.model, resolved?.gatewaySlug);
|
|
964
|
-
|
|
1554
|
+
const nextParams = withAutoInjectedTools(
|
|
1555
|
+
{ ...rest, model, ...(system && { system }) },
|
|
1556
|
+
session,
|
|
1557
|
+
baseUrl,
|
|
1558
|
+
clConfig.apiKey,
|
|
1559
|
+
kognitive,
|
|
1560
|
+
);
|
|
1561
|
+
return aiGenerateText(nextParams as any);
|
|
965
1562
|
};
|
|
966
1563
|
|
|
967
1564
|
// Return the model wrapper function with streamText/generateText attached
|
|
@@ -975,9 +1572,13 @@ ${userContextBlock || "None"}
|
|
|
975
1572
|
if (sessionKey) {
|
|
976
1573
|
sessionSnapshots.delete(sessionKey);
|
|
977
1574
|
sessionPromptMetadata.delete(sessionKey);
|
|
1575
|
+
for (const key of sessionTurnIndexes.keys()) {
|
|
1576
|
+
if (key.endsWith(`:${sessionKey}`)) sessionTurnIndexes.delete(key);
|
|
1577
|
+
}
|
|
978
1578
|
} else {
|
|
979
1579
|
sessionSnapshots.clear();
|
|
980
1580
|
sessionPromptMetadata.clear();
|
|
1581
|
+
sessionTurnIndexes.clear();
|
|
981
1582
|
}
|
|
982
1583
|
},
|
|
983
1584
|
});
|