graphlit-client 1.0.20250922001 → 1.0.20250924002
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/dist/client.js +5 -3
- package/dist/model-mapping.d.ts +6 -0
- package/dist/model-mapping.js +32 -0
- package/dist/streaming/llm-formatters.js +38 -37
- package/dist/streaming/providers.js +45 -21
- package/dist/streaming/ui-event-adapter.d.ts +7 -0
- package/dist/streaming/ui-event-adapter.js +46 -8
- package/dist/types/ui-events.d.ts +9 -4
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -6,7 +6,7 @@ import { RetryLink } from "@apollo/client/link/retry/index.js";
|
|
|
6
6
|
import * as Types from "./generated/graphql-types.js";
|
|
7
7
|
import * as Documents from "./generated/graphql-documents.js";
|
|
8
8
|
import * as dotenv from "dotenv";
|
|
9
|
-
import { getServiceType, getModelName } from "./model-mapping.js";
|
|
9
|
+
import { getServiceType, getModelName, getModelEnum } from "./model-mapping.js";
|
|
10
10
|
import { UIEventAdapter } from "./streaming/ui-event-adapter.js";
|
|
11
11
|
import { formatMessagesForOpenAI, formatMessagesForAnthropic, formatMessagesForGoogle, formatMessagesForMistral, formatMessagesForBedrock, } from "./streaming/llm-formatters.js";
|
|
12
12
|
import { streamWithOpenAI, streamWithAnthropic, streamWithGoogle, streamWithGroq, streamWithCerebras, streamWithCohere, streamWithMistral, streamWithBedrock, streamWithDeepseek, streamWithXai, } from "./streaming/providers.js";
|
|
@@ -2174,12 +2174,14 @@ class Graphlit {
|
|
|
2174
2174
|
}
|
|
2175
2175
|
// Create UI event adapter with model information
|
|
2176
2176
|
const modelName = fullSpec ? getModelName(fullSpec) : undefined;
|
|
2177
|
+
const modelEnum = fullSpec ? getModelEnum(fullSpec) : undefined;
|
|
2177
2178
|
const serviceType = fullSpec ? getServiceType(fullSpec) : undefined;
|
|
2178
2179
|
uiAdapter = new UIEventAdapter(onEvent, actualConversationId, {
|
|
2179
2180
|
smoothingEnabled: options?.smoothingEnabled ?? true,
|
|
2180
2181
|
chunkingStrategy: options?.chunkingStrategy ?? "word",
|
|
2181
2182
|
smoothingDelay: options?.smoothingDelay ?? 30,
|
|
2182
|
-
model:
|
|
2183
|
+
model: modelEnum,
|
|
2184
|
+
modelName: modelName,
|
|
2183
2185
|
modelService: serviceType,
|
|
2184
2186
|
});
|
|
2185
2187
|
// Start the streaming conversation
|
|
@@ -2486,7 +2488,7 @@ class Graphlit {
|
|
|
2486
2488
|
// Mistral API requires that we don't pass tools when sending tool results
|
|
2487
2489
|
const shouldPassTools = toolResponseCount === 0 ? tools : undefined;
|
|
2488
2490
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
2489
|
-
console.log(`🔍 [Mistral] Passing tools: ${shouldPassTools ?
|
|
2491
|
+
console.log(`🔍 [Mistral] Passing tools: ${shouldPassTools ? "YES" : "NO"} (tool responses in messages: ${toolResponseCount})`);
|
|
2490
2492
|
}
|
|
2491
2493
|
await this.streamWithMistral(specification, mistralMessages, shouldPassTools, uiAdapter, (message, calls, usage) => {
|
|
2492
2494
|
roundMessage = message;
|
package/dist/model-mapping.d.ts
CHANGED
|
@@ -16,3 +16,9 @@ export declare function isStreamingSupported(serviceType?: string): boolean;
|
|
|
16
16
|
* @returns The service type string
|
|
17
17
|
*/
|
|
18
18
|
export declare function getServiceType(specification: any): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Get the model enum value from specification
|
|
21
|
+
* @param specification - The specification object
|
|
22
|
+
* @returns The model enum value
|
|
23
|
+
*/
|
|
24
|
+
export declare function getModelEnum(specification: any): string | undefined;
|
package/dist/model-mapping.js
CHANGED
|
@@ -264,3 +264,35 @@ export function isStreamingSupported(serviceType) {
|
|
|
264
264
|
export function getServiceType(specification) {
|
|
265
265
|
return specification?.serviceType;
|
|
266
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Get the model enum value from specification
|
|
269
|
+
* @param specification - The specification object
|
|
270
|
+
* @returns The model enum value
|
|
271
|
+
*/
|
|
272
|
+
export function getModelEnum(specification) {
|
|
273
|
+
const serviceType = specification?.serviceType;
|
|
274
|
+
switch (serviceType) {
|
|
275
|
+
case Types.ModelServiceTypes.OpenAi:
|
|
276
|
+
return specification?.openAI?.model;
|
|
277
|
+
case Types.ModelServiceTypes.Anthropic:
|
|
278
|
+
return specification?.anthropic?.model;
|
|
279
|
+
case Types.ModelServiceTypes.Google:
|
|
280
|
+
return specification?.google?.model;
|
|
281
|
+
case Types.ModelServiceTypes.Groq:
|
|
282
|
+
return specification?.groq?.model;
|
|
283
|
+
case Types.ModelServiceTypes.Cerebras:
|
|
284
|
+
return specification?.cerebras?.model;
|
|
285
|
+
case Types.ModelServiceTypes.Cohere:
|
|
286
|
+
return specification?.cohere?.model;
|
|
287
|
+
case Types.ModelServiceTypes.Mistral:
|
|
288
|
+
return specification?.mistral?.model;
|
|
289
|
+
case Types.ModelServiceTypes.Bedrock:
|
|
290
|
+
return specification?.bedrock?.model;
|
|
291
|
+
case Types.ModelServiceTypes.Deepseek:
|
|
292
|
+
return specification?.deepseek?.model;
|
|
293
|
+
case Types.ModelServiceTypes.Xai:
|
|
294
|
+
return specification?.xai?.model;
|
|
295
|
+
default:
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -108,56 +108,57 @@ export function formatMessagesForAnthropic(messages) {
|
|
|
108
108
|
systemPrompt = trimmedMessage;
|
|
109
109
|
break;
|
|
110
110
|
case ConversationRoleTypes.Assistant:
|
|
111
|
-
const content = [];
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
.trim();
|
|
139
|
-
if (textWithoutThinking) {
|
|
140
|
-
content.push({
|
|
141
|
-
type: "text",
|
|
142
|
-
text: textWithoutThinking,
|
|
143
|
-
});
|
|
111
|
+
const content = [];
|
|
112
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
113
|
+
console.log(`🔍 [formatMessagesForAnthropic] Processing assistant message: "${trimmedMessage.substring(0, 200)}..."`);
|
|
114
|
+
console.log(`🔍 [formatMessagesForAnthropic] Has tool calls: ${message.toolCalls?.length || 0}`);
|
|
115
|
+
}
|
|
116
|
+
// Check if message contains thinking content (for Anthropic compatibility)
|
|
117
|
+
const hasThinking = trimmedMessage.includes('<thinking');
|
|
118
|
+
if (hasThinking) {
|
|
119
|
+
// Parse thinking and text content separately for proper Anthropic format
|
|
120
|
+
const thinkingMatch = trimmedMessage.match(/<thinking(?:\s+signature="([^"]*)")?\s*>(.*?)<\/thinking>/s);
|
|
121
|
+
const thinkingSignature = thinkingMatch ? thinkingMatch[1] : '';
|
|
122
|
+
const thinkingContent = thinkingMatch ? thinkingMatch[2].trim() : '';
|
|
123
|
+
const textContent = trimmedMessage.replace(/<thinking(?:\s+signature="[^"]*")?\s*>.*?<\/thinking>/s, '').trim();
|
|
124
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
125
|
+
console.log(`🔍 [formatMessagesForAnthropic] Found thinking content: ${thinkingContent.length} chars`);
|
|
126
|
+
console.log(`🔍 [formatMessagesForAnthropic] Text content after thinking: "${textContent}"`);
|
|
127
|
+
console.log(`🔍 [formatMessagesForAnthropic] Signature: "${thinkingSignature}"`);
|
|
128
|
+
}
|
|
129
|
+
// CRITICAL: When thinking is enabled, thinking block must come first
|
|
130
|
+
if (thinkingContent) {
|
|
131
|
+
const thinkingBlock = {
|
|
132
|
+
type: "thinking",
|
|
133
|
+
thinking: thinkingContent,
|
|
134
|
+
};
|
|
135
|
+
// Include signature if present
|
|
136
|
+
if (thinkingSignature) {
|
|
137
|
+
thinkingBlock.signature = thinkingSignature;
|
|
144
138
|
}
|
|
139
|
+
content.push(thinkingBlock);
|
|
145
140
|
}
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
// Add text content after thinking block
|
|
142
|
+
if (textContent) {
|
|
148
143
|
content.push({
|
|
149
144
|
type: "text",
|
|
150
|
-
text:
|
|
145
|
+
text: textContent,
|
|
151
146
|
});
|
|
152
147
|
}
|
|
153
148
|
}
|
|
154
149
|
else if (trimmedMessage) {
|
|
155
|
-
//
|
|
150
|
+
// Regular text content
|
|
151
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
152
|
+
console.log(`🔍 [formatMessagesForAnthropic] No thinking found, adding text content`);
|
|
153
|
+
}
|
|
156
154
|
content.push({
|
|
157
155
|
type: "text",
|
|
158
156
|
text: trimmedMessage,
|
|
159
157
|
});
|
|
160
158
|
}
|
|
159
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
160
|
+
console.log(`🔍 [formatMessagesForAnthropic] Content array: ${content.map(c => c.type).join(', ')}`);
|
|
161
|
+
}
|
|
161
162
|
// Add tool uses if present
|
|
162
163
|
if (message.toolCalls && message.toolCalls.length > 0) {
|
|
163
164
|
for (const toolCall of message.toolCalls) {
|
|
@@ -421,15 +421,17 @@ onEvent, onComplete, abortSignal, thinkingConfig) {
|
|
|
421
421
|
if (!modelName) {
|
|
422
422
|
throw new Error(`No model name found for Anthropic specification: ${specification.name}`);
|
|
423
423
|
}
|
|
424
|
+
// Calculate smart default for max_tokens based on thinking mode
|
|
425
|
+
const defaultMaxTokens = thinkingConfig ? 32768 : 8192;
|
|
424
426
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
425
|
-
console.log(`🤖 [Anthropic] Model Config: Service=Anthropic | Model=${modelName} | Temperature=${specification.anthropic?.temperature} | MaxTokens=${specification.anthropic?.completionTokenLimit ||
|
|
427
|
+
console.log(`🤖 [Anthropic] Model Config: Service=Anthropic | Model=${modelName} | Temperature=${specification.anthropic?.temperature} | MaxTokens=${specification.anthropic?.completionTokenLimit || defaultMaxTokens} | SystemPrompt=${systemPrompt ? "Yes" : "No"} | Tools=${tools?.length || 0} | Thinking=${!!thinkingConfig} | Spec="${specification.name}"`);
|
|
426
428
|
}
|
|
427
429
|
// Use proper Anthropic SDK types for the config
|
|
428
430
|
const streamConfig = {
|
|
429
431
|
model: modelName,
|
|
430
432
|
messages,
|
|
431
433
|
stream: true,
|
|
432
|
-
max_tokens: specification.anthropic?.completionTokenLimit ||
|
|
434
|
+
max_tokens: specification.anthropic?.completionTokenLimit || defaultMaxTokens,
|
|
433
435
|
};
|
|
434
436
|
// Handle temperature based on thinking configuration
|
|
435
437
|
if (thinkingConfig) {
|
|
@@ -496,14 +498,17 @@ onEvent, onComplete, abortSignal, thinkingConfig) {
|
|
|
496
498
|
console.log(`[Anthropic] Usage data captured from message_start.message:`, usageData);
|
|
497
499
|
}
|
|
498
500
|
}
|
|
499
|
-
else if (chunk.type === "message_delta" &&
|
|
501
|
+
else if (chunk.type === "message_delta" &&
|
|
502
|
+
chunk.usage &&
|
|
503
|
+
!usageData?.input_tokens) {
|
|
500
504
|
// Only use message_delta if we don't have input_tokens yet
|
|
501
505
|
usageData = chunk.usage;
|
|
502
506
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
503
507
|
console.log(`[Anthropic] Usage data captured from ${chunk.type}:`, usageData);
|
|
504
508
|
}
|
|
505
509
|
}
|
|
506
|
-
else if ((chunk.type === "message_delta" || chunk.type === "message_start") &&
|
|
510
|
+
else if ((chunk.type === "message_delta" || chunk.type === "message_start") &&
|
|
511
|
+
chunk.usage) {
|
|
507
512
|
// Merge usage data if we have partial data
|
|
508
513
|
if (usageData) {
|
|
509
514
|
usageData = { ...usageData, ...chunk.usage };
|
|
@@ -746,14 +751,24 @@ onEvent, onComplete, abortSignal, thinkingConfig) {
|
|
|
746
751
|
}
|
|
747
752
|
}
|
|
748
753
|
}
|
|
749
|
-
// Final check:
|
|
750
|
-
const validToolCalls = toolCalls
|
|
754
|
+
// Final check: normalize and validate tool calls
|
|
755
|
+
const validToolCalls = toolCalls
|
|
756
|
+
.map((tc, idx) => {
|
|
757
|
+
// For tools with no parameters, Anthropic doesn't send input_json_delta
|
|
758
|
+
// So we need to convert empty arguments to valid JSON
|
|
759
|
+
if (tc.arguments === "") {
|
|
760
|
+
tc.arguments = "{}";
|
|
761
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
762
|
+
console.log(`[Anthropic] Normalized empty arguments to "{}" for tool ${tc.name}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
751
765
|
if (!isValidJSON(tc.arguments)) {
|
|
752
766
|
console.warn(`[Anthropic] Filtering out incomplete tool call ${idx} (${tc.name}) with INVALID JSON (${tc.arguments.length} chars)`);
|
|
753
|
-
return
|
|
767
|
+
return null;
|
|
754
768
|
}
|
|
755
|
-
return
|
|
756
|
-
})
|
|
769
|
+
return tc;
|
|
770
|
+
})
|
|
771
|
+
.filter((tc) => tc !== null);
|
|
757
772
|
if (toolCalls.length !== validToolCalls.length) {
|
|
758
773
|
console.log(`[Anthropic] Filtered out ${toolCalls.length - validToolCalls.length} incomplete tool calls`);
|
|
759
774
|
console.log(`[Anthropic] Successfully processed ${validToolCalls.length} valid tool calls`);
|
|
@@ -825,19 +840,26 @@ onEvent, onComplete, abortSignal, thinkingConfig) {
|
|
|
825
840
|
}
|
|
826
841
|
console.log(`✅ [Anthropic] Final message (${fullMessage.length} chars): "${fullMessage}"`);
|
|
827
842
|
}
|
|
828
|
-
// Include thinking content in
|
|
829
|
-
let
|
|
830
|
-
if (
|
|
831
|
-
|
|
832
|
-
|
|
843
|
+
// Include thinking content in message when there are tool calls (required by Anthropic API)
|
|
844
|
+
let messageWithThinking = fullMessage;
|
|
845
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
846
|
+
console.log(`🧠 [Anthropic] Debug - validToolCalls: ${validToolCalls.length}, thinking content: ${completeThinkingContent.length} chars, fullMessage: ${fullMessage.length} chars`);
|
|
847
|
+
}
|
|
848
|
+
if (validToolCalls.length > 0 && completeThinkingContent.trim()) {
|
|
849
|
+
// Include thinking content with signature for API compatibility
|
|
850
|
+
const thinkingXml = completeThinkingSignature
|
|
833
851
|
? `<thinking signature="${completeThinkingSignature}">${completeThinkingContent}</thinking>`
|
|
834
852
|
: `<thinking>${completeThinkingContent}</thinking>`;
|
|
835
|
-
|
|
853
|
+
messageWithThinking = `${thinkingXml}\n${fullMessage}`.trim();
|
|
836
854
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
837
|
-
console.log(`🧠 [Anthropic] Including thinking content (${completeThinkingContent.length} chars
|
|
855
|
+
console.log(`🧠 [Anthropic] Including thinking content with message due to tool calls (${completeThinkingContent.length} chars, signature: ${completeThinkingSignature?.length || 0})`);
|
|
856
|
+
console.log(`🧠 [Anthropic] Final stored message: "${messageWithThinking}"`);
|
|
838
857
|
}
|
|
839
858
|
}
|
|
840
|
-
|
|
859
|
+
else if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING && completeThinkingContent.trim()) {
|
|
860
|
+
console.log(`🧠 [Anthropic] Thinking content captured (${completeThinkingContent.length} chars) - not including (no tool calls)`);
|
|
861
|
+
}
|
|
862
|
+
onComplete(messageWithThinking, validToolCalls, usageData);
|
|
841
863
|
}
|
|
842
864
|
catch (error) {
|
|
843
865
|
// Handle Anthropic-specific errors
|
|
@@ -1095,7 +1117,8 @@ onEvent, onComplete, abortSignal) {
|
|
|
1095
1117
|
continue;
|
|
1096
1118
|
}
|
|
1097
1119
|
// Only add if it's not already included in fullMessage
|
|
1098
|
-
if (!fullMessage.includes(finalText) &&
|
|
1120
|
+
if (!fullMessage.includes(finalText) &&
|
|
1121
|
+
!fullMessage.endsWith(finalText)) {
|
|
1099
1122
|
fullMessage += finalText;
|
|
1100
1123
|
onEvent({
|
|
1101
1124
|
type: "token",
|
|
@@ -1526,7 +1549,8 @@ onEvent, onComplete, abortSignal) {
|
|
|
1526
1549
|
}
|
|
1527
1550
|
}
|
|
1528
1551
|
// Check for finish reason
|
|
1529
|
-
if (chunk.choices[0].finish_reason === "tool_calls" &&
|
|
1552
|
+
if (chunk.choices[0].finish_reason === "tool_calls" &&
|
|
1553
|
+
toolCalls.length > 0) {
|
|
1530
1554
|
// Emit tool_call_parsed events for completed tool calls
|
|
1531
1555
|
for (const toolCall of toolCalls) {
|
|
1532
1556
|
onEvent({
|
|
@@ -2227,7 +2251,7 @@ onEvent, onComplete, abortSignal) {
|
|
|
2227
2251
|
}));
|
|
2228
2252
|
}
|
|
2229
2253
|
else {
|
|
2230
|
-
console.log(`[Mistral] No tools provided - tools parameter is ${tools === undefined ?
|
|
2254
|
+
console.log(`[Mistral] No tools provided - tools parameter is ${tools === undefined ? "undefined" : "empty array"}`);
|
|
2231
2255
|
}
|
|
2232
2256
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
2233
2257
|
console.log(`[Mistral] Stream config:`, JSON.stringify({
|
|
@@ -2274,7 +2298,7 @@ onEvent, onComplete, abortSignal) {
|
|
|
2274
2298
|
console.log(`[Mistral] Attempting to create stream with retry configuration`);
|
|
2275
2299
|
}
|
|
2276
2300
|
// Log final config being sent
|
|
2277
|
-
console.log(`[Mistral] Sending request with tools: ${streamConfig.tools ?
|
|
2301
|
+
console.log(`[Mistral] Sending request with tools: ${streamConfig.tools ? "YES" : "NO"}`);
|
|
2278
2302
|
stream = await mistralClient.chat.stream(streamConfig, {
|
|
2279
2303
|
retries: {
|
|
2280
2304
|
strategy: "backoff",
|
|
@@ -9,6 +9,7 @@ export declare class UIEventAdapter {
|
|
|
9
9
|
private onEvent;
|
|
10
10
|
private conversationId;
|
|
11
11
|
private model?;
|
|
12
|
+
private modelName?;
|
|
12
13
|
private modelService?;
|
|
13
14
|
private tokenCount;
|
|
14
15
|
private currentMessage;
|
|
@@ -26,6 +27,7 @@ export declare class UIEventAdapter {
|
|
|
26
27
|
private chunkQueue;
|
|
27
28
|
private contextWindowUsage?;
|
|
28
29
|
private finalMetrics?;
|
|
30
|
+
private roundThinkingContent?;
|
|
29
31
|
private reasoningContent;
|
|
30
32
|
private reasoningFormat?;
|
|
31
33
|
private reasoningSignature?;
|
|
@@ -38,6 +40,7 @@ export declare class UIEventAdapter {
|
|
|
38
40
|
chunkingStrategy?: ChunkingStrategy;
|
|
39
41
|
smoothingDelay?: number;
|
|
40
42
|
model?: string;
|
|
43
|
+
modelName?: string;
|
|
41
44
|
modelService?: string;
|
|
42
45
|
});
|
|
43
46
|
/**
|
|
@@ -82,4 +85,8 @@ export declare class UIEventAdapter {
|
|
|
82
85
|
* Set usage data from native provider
|
|
83
86
|
*/
|
|
84
87
|
setUsageData(usage: any): void;
|
|
88
|
+
/**
|
|
89
|
+
* Set thinking content for this round (for conversation history formatting)
|
|
90
|
+
*/
|
|
91
|
+
setRoundThinkingContent(thinkingContent: string): void;
|
|
85
92
|
}
|
|
@@ -7,7 +7,8 @@ import { ChunkBuffer } from "./chunk-buffer.js";
|
|
|
7
7
|
export class UIEventAdapter {
|
|
8
8
|
onEvent;
|
|
9
9
|
conversationId;
|
|
10
|
-
model;
|
|
10
|
+
model; // This will now be the enum value
|
|
11
|
+
modelName; // This will be the actual model name (e.g., "claude-sonnet-4-0")
|
|
11
12
|
modelService;
|
|
12
13
|
tokenCount = 0;
|
|
13
14
|
currentMessage = "";
|
|
@@ -25,6 +26,7 @@ export class UIEventAdapter {
|
|
|
25
26
|
chunkQueue = []; // Queue of chunks waiting to be emitted
|
|
26
27
|
contextWindowUsage;
|
|
27
28
|
finalMetrics;
|
|
29
|
+
roundThinkingContent; // Store thinking content for conversation history
|
|
28
30
|
reasoningContent = "";
|
|
29
31
|
reasoningFormat;
|
|
30
32
|
reasoningSignature;
|
|
@@ -37,6 +39,7 @@ export class UIEventAdapter {
|
|
|
37
39
|
this.conversationId = conversationId;
|
|
38
40
|
this.smoothingDelay = options.smoothingDelay ?? 30;
|
|
39
41
|
this.model = options.model;
|
|
42
|
+
this.modelName = options.modelName;
|
|
40
43
|
this.modelService = options.modelService;
|
|
41
44
|
this.conversationStartTime = Date.now(); // Capture when conversation began
|
|
42
45
|
if (options.smoothingEnabled) {
|
|
@@ -130,14 +133,16 @@ export class UIEventAdapter {
|
|
|
130
133
|
this.lastTokenTime = now;
|
|
131
134
|
this.tokenCount++;
|
|
132
135
|
// Check if we're resuming after tool calls and need to add newlines
|
|
133
|
-
if (this.hadToolCallsBeforeResume &&
|
|
136
|
+
if (this.hadToolCallsBeforeResume &&
|
|
137
|
+
this.hasToolCallsInProgress === false) {
|
|
134
138
|
// We had tool calls before and now we're receiving content again
|
|
135
139
|
// Add double newline to separate the content from tool results
|
|
136
|
-
if (this.currentMessage.length > 0 &&
|
|
140
|
+
if (this.currentMessage.length > 0 &&
|
|
141
|
+
!this.currentMessage.endsWith("\n\n")) {
|
|
137
142
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
138
143
|
console.log(`📝 [UIEventAdapter] Adding newlines after tool calls before resuming content`);
|
|
139
144
|
}
|
|
140
|
-
this.currentMessage +=
|
|
145
|
+
this.currentMessage += "\n\n";
|
|
141
146
|
}
|
|
142
147
|
// Reset the flag now that we've added the newlines
|
|
143
148
|
this.hadToolCallsBeforeResume = false;
|
|
@@ -330,6 +335,7 @@ export class UIEventAdapter {
|
|
|
330
335
|
tokens: tokens, // Now we have the actual LLM token count!
|
|
331
336
|
toolCalls: Array.from(this.activeToolCalls.values()).map((t) => t.toolCall),
|
|
332
337
|
model: this.model,
|
|
338
|
+
modelName: this.modelName,
|
|
333
339
|
modelService: this.modelService,
|
|
334
340
|
};
|
|
335
341
|
// Add final timing metadata
|
|
@@ -383,6 +389,17 @@ export class UIEventAdapter {
|
|
|
383
389
|
}
|
|
384
390
|
// Store final metrics for later retrieval
|
|
385
391
|
this.finalMetrics = finalMetrics;
|
|
392
|
+
// Check if there are tool calls that haven't been executed yet
|
|
393
|
+
const hasPendingToolCalls = Array.from(this.activeToolCalls.values()).some((toolData) => toolData.status === "ready" ||
|
|
394
|
+
toolData.status === "preparing" ||
|
|
395
|
+
toolData.status === "executing");
|
|
396
|
+
if (hasPendingToolCalls) {
|
|
397
|
+
// Don't emit conversation_completed yet - tool execution will continue
|
|
398
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
399
|
+
console.log(`🔄 [UIEventAdapter] Skipping conversation_completed - ${this.activeToolCalls.size} tool calls pending execution`);
|
|
400
|
+
}
|
|
401
|
+
return; // Exit without emitting conversation_completed
|
|
402
|
+
}
|
|
386
403
|
// Include context window usage if available
|
|
387
404
|
const event = {
|
|
388
405
|
type: "conversation_completed",
|
|
@@ -395,10 +412,19 @@ export class UIEventAdapter {
|
|
|
395
412
|
// Add native provider usage data if available
|
|
396
413
|
if (this.usageData) {
|
|
397
414
|
event.usage = {
|
|
398
|
-
promptTokens: this.usageData.prompt_tokens ||
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
415
|
+
promptTokens: this.usageData.prompt_tokens ||
|
|
416
|
+
this.usageData.promptTokens ||
|
|
417
|
+
this.usageData.input_tokens ||
|
|
418
|
+
0,
|
|
419
|
+
completionTokens: this.usageData.completion_tokens ||
|
|
420
|
+
this.usageData.completionTokens ||
|
|
421
|
+
this.usageData.output_tokens ||
|
|
422
|
+
0,
|
|
423
|
+
totalTokens: this.usageData.total_tokens ||
|
|
424
|
+
this.usageData.totalTokens ||
|
|
425
|
+
(this.usageData.input_tokens || 0) +
|
|
426
|
+
(this.usageData.output_tokens || 0) ||
|
|
427
|
+
0,
|
|
402
428
|
model: this.model,
|
|
403
429
|
provider: this.modelService,
|
|
404
430
|
};
|
|
@@ -491,6 +517,9 @@ export class UIEventAdapter {
|
|
|
491
517
|
if (this.model) {
|
|
492
518
|
message.model = this.model;
|
|
493
519
|
}
|
|
520
|
+
if (this.modelName) {
|
|
521
|
+
message.modelName = this.modelName;
|
|
522
|
+
}
|
|
494
523
|
if (this.modelService) {
|
|
495
524
|
message.modelService = this.modelService;
|
|
496
525
|
}
|
|
@@ -635,4 +664,13 @@ export class UIEventAdapter {
|
|
|
635
664
|
console.log(`📊 [UIEventAdapter] Usage data set:`, usage);
|
|
636
665
|
}
|
|
637
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* Set thinking content for this round (for conversation history formatting)
|
|
669
|
+
*/
|
|
670
|
+
setRoundThinkingContent(thinkingContent) {
|
|
671
|
+
this.roundThinkingContent = thinkingContent;
|
|
672
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
673
|
+
console.log(`🧠 [UIEventAdapter] Thinking content set for conversation history (${thinkingContent.length} chars)`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
638
676
|
}
|
|
@@ -20,6 +20,13 @@ export type ContextWindowEvent = {
|
|
|
20
20
|
};
|
|
21
21
|
timestamp: Date;
|
|
22
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Extended conversation message with additional streaming metadata
|
|
25
|
+
*/
|
|
26
|
+
export type StreamingConversationMessage = Partial<ConversationMessage> & {
|
|
27
|
+
message: string;
|
|
28
|
+
modelName?: string;
|
|
29
|
+
};
|
|
23
30
|
/**
|
|
24
31
|
* Simplified UI-focused streaming events using GraphQL types
|
|
25
32
|
*/
|
|
@@ -30,9 +37,7 @@ export type AgentStreamEvent = {
|
|
|
30
37
|
model?: string;
|
|
31
38
|
} | ContextWindowEvent | {
|
|
32
39
|
type: "message_update";
|
|
33
|
-
message:
|
|
34
|
-
message: string;
|
|
35
|
-
};
|
|
40
|
+
message: StreamingConversationMessage;
|
|
36
41
|
isStreaming: boolean;
|
|
37
42
|
metrics?: {
|
|
38
43
|
ttft?: number;
|
|
@@ -55,7 +60,7 @@ export type AgentStreamEvent = {
|
|
|
55
60
|
isComplete: boolean;
|
|
56
61
|
} | {
|
|
57
62
|
type: "conversation_completed";
|
|
58
|
-
message:
|
|
63
|
+
message: StreamingConversationMessage;
|
|
59
64
|
metrics?: {
|
|
60
65
|
ttft?: number;
|
|
61
66
|
totalTime: number;
|