graphlit-client 1.0.20250611019 → 1.0.20250612001

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 CHANGED
@@ -1419,26 +1419,28 @@ class Graphlit {
1419
1419
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1420
1420
  console.log("[supportsStreaming] Checking support for:", {
1421
1421
  serviceType,
1422
- hasOpenAI: this.openaiClient !== undefined,
1423
- hasAnthropic: this.anthropicClient !== undefined,
1424
- hasGoogle: this.googleClient !== undefined,
1422
+ hasOpenAI: OpenAI !== undefined || this.openaiClient !== undefined,
1423
+ hasAnthropic: Anthropic !== undefined || this.anthropicClient !== undefined,
1424
+ hasGoogle: GoogleGenerativeAI !== undefined || this.googleClient !== undefined,
1425
1425
  });
1426
1426
  }
1427
1427
  switch (serviceType) {
1428
1428
  case Types.ModelServiceTypes.OpenAi:
1429
- return this.openaiClient !== undefined;
1429
+ return OpenAI !== undefined || this.openaiClient !== undefined;
1430
1430
  case Types.ModelServiceTypes.Anthropic:
1431
- return this.anthropicClient !== undefined;
1431
+ return Anthropic !== undefined || this.anthropicClient !== undefined;
1432
1432
  case Types.ModelServiceTypes.Google:
1433
- return this.googleClient !== undefined;
1433
+ return GoogleGenerativeAI !== undefined || this.googleClient !== undefined;
1434
1434
  default:
1435
1435
  return false;
1436
1436
  }
1437
1437
  }
1438
1438
  // If we have no specification, check if any client is available
1439
- return (this.openaiClient !== undefined ||
1440
- this.anthropicClient !== undefined ||
1441
- this.googleClient !== undefined);
1439
+ // Check both module-level SDKs and instance-level clients
1440
+ const hasOpenAI = OpenAI !== undefined || this.openaiClient !== undefined;
1441
+ const hasAnthropic = Anthropic !== undefined || this.anthropicClient !== undefined;
1442
+ const hasGoogle = GoogleGenerativeAI !== undefined || this.googleClient !== undefined;
1443
+ return hasOpenAI || hasAnthropic || hasGoogle;
1442
1444
  }
1443
1445
  /**
1444
1446
  * Execute an agent with non-streaming response
@@ -1640,9 +1642,7 @@ class Graphlit {
1640
1642
  throw new Error("Failed to format conversation");
1641
1643
  }
1642
1644
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1643
- console.log("\nšŸ“‹ [formatConversation] Response:");
1644
- console.log("Formatted message:", formattedMessage.message);
1645
- console.log("Full formatConversation response:", JSON.stringify(formatResponse.formatConversation, null, 2));
1645
+ console.log("\nšŸ“‹ [formatConversation] Response", formattedMessage.message);
1646
1646
  }
1647
1647
  // Build message array with conversation history
1648
1648
  const messages = [];
@@ -1658,12 +1658,21 @@ class Graphlit {
1658
1658
  // Use the formatted message from formatConversation which already includes
1659
1659
  // all context, RAG results, and conversation history
1660
1660
  if (formattedMessage) {
1661
- messages.push({
1661
+ const messageToAdd = {
1662
1662
  __typename: "ConversationMessage",
1663
1663
  role: formattedMessage.role || Types.ConversationRoleTypes.User,
1664
1664
  message: formattedMessage.message,
1665
1665
  timestamp: formattedMessage.timestamp || new Date().toISOString(),
1666
- });
1666
+ };
1667
+ // Add image data if provided
1668
+ if (mimeType && data) {
1669
+ messageToAdd.mimeType = mimeType;
1670
+ messageToAdd.data = data;
1671
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1672
+ console.log(`\nšŸ–¼ļø [Streaming] Adding image data to message: ${mimeType}, ${data.length} chars`);
1673
+ }
1674
+ }
1675
+ messages.push(messageToAdd);
1667
1676
  }
1668
1677
  else {
1669
1678
  throw new Error("No formatted message returned from formatConversation");
@@ -1677,7 +1686,16 @@ class Graphlit {
1677
1686
  let toolCalls = [];
1678
1687
  let roundMessage = "";
1679
1688
  // Stream with appropriate provider
1680
- if (serviceType === Types.ModelServiceTypes.OpenAi && OpenAI) {
1689
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1690
+ console.log(`\nšŸ”€ [Streaming Decision] Service: ${serviceType}, Round: ${currentRound}`);
1691
+ console.log(` OpenAI available: ${!!(OpenAI || this.openaiClient)}`);
1692
+ console.log(` Anthropic available: ${!!(Anthropic || this.anthropicClient)}`);
1693
+ console.log(` Google available: ${!!(GoogleGenerativeAI || this.googleClient)}`);
1694
+ }
1695
+ if (serviceType === Types.ModelServiceTypes.OpenAi && (OpenAI || this.openaiClient)) {
1696
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1697
+ console.log(`\nāœ… [Streaming] Using OpenAI native streaming (Round ${currentRound})`);
1698
+ }
1681
1699
  const openaiMessages = formatMessagesForOpenAI(messages);
1682
1700
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1683
1701
  console.log("\nšŸ” [OpenAI] Formatted messages being sent to LLM:");
@@ -1688,9 +1706,15 @@ class Graphlit {
1688
1706
  roundMessage = message;
1689
1707
  toolCalls = calls;
1690
1708
  });
1709
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1710
+ console.log(`\nšŸ [Streaming] OpenAI native streaming completed (Round ${currentRound})`);
1711
+ }
1691
1712
  }
1692
1713
  else if (serviceType === Types.ModelServiceTypes.Anthropic &&
1693
- Anthropic) {
1714
+ (Anthropic || this.anthropicClient)) {
1715
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1716
+ console.log(`\nāœ… [Streaming] Using Anthropic native streaming (Round ${currentRound})`);
1717
+ }
1694
1718
  const { system, messages: anthropicMessages } = formatMessagesForAnthropic(messages);
1695
1719
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1696
1720
  console.log("\nšŸ” [Anthropic] Formatted messages being sent to LLM:");
@@ -1702,9 +1726,15 @@ class Graphlit {
1702
1726
  roundMessage = message;
1703
1727
  toolCalls = calls;
1704
1728
  });
1729
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1730
+ console.log(`\nšŸ [Streaming] Anthropic native streaming completed (Round ${currentRound})`);
1731
+ }
1705
1732
  }
1706
1733
  else if (serviceType === Types.ModelServiceTypes.Google &&
1707
- GoogleGenerativeAI) {
1734
+ (GoogleGenerativeAI || this.googleClient)) {
1735
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1736
+ console.log(`\nāœ… [Streaming] Using Google native streaming (Round ${currentRound})`);
1737
+ }
1708
1738
  const googleMessages = formatMessagesForGoogle(messages);
1709
1739
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1710
1740
  console.log("\nšŸ” [Google] Formatted messages being sent to LLM:");
@@ -1717,10 +1747,21 @@ class Graphlit {
1717
1747
  roundMessage = message;
1718
1748
  toolCalls = calls;
1719
1749
  });
1750
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1751
+ console.log(`\nšŸ [Streaming] Google native streaming completed (Round ${currentRound})`);
1752
+ }
1720
1753
  }
1721
1754
  else {
1722
1755
  // Fallback to non-streaming
1756
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1757
+ console.log(`\nāš ļø [Fallback] No native streaming available for ${serviceType} (Round ${currentRound})`);
1758
+ console.log(` Falling back to non-streaming promptConversation`);
1759
+ console.log(` This should NOT happen if clients are properly set!`);
1760
+ }
1723
1761
  await this.fallbackToNonStreaming(prompt, conversationId, specification, tools, mimeType, data, uiAdapter, correlationId);
1762
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1763
+ console.log(`\nšŸ [Fallback] Non-streaming fallback completed (Round ${currentRound})`);
1764
+ }
1724
1765
  break;
1725
1766
  }
1726
1767
  // Update the full message
@@ -1964,8 +2005,21 @@ class Graphlit {
1964
2005
  * Fallback to non-streaming when streaming is not available
1965
2006
  */
1966
2007
  async fallbackToNonStreaming(prompt, conversationId, specification, tools, mimeType, data, uiAdapter, correlationId) {
2008
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
2009
+ console.log(`\nšŸ”„ [Fallback] Starting non-streaming fallback`);
2010
+ console.log(` Conversation ID: ${conversationId}`);
2011
+ console.log(` Specification: ${specification.name} (${specification.serviceType})`);
2012
+ console.log(` Prompt: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`);
2013
+ console.log(` About to call promptConversation...`);
2014
+ }
1967
2015
  const response = await this.promptConversation(prompt, conversationId, { id: specification.id }, mimeType, data, tools, false, false, correlationId);
1968
2016
  const message = response.promptConversation?.message;
2017
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
2018
+ console.log(`\nāœ… [Fallback] promptConversation completed`);
2019
+ console.log(` Response message length: ${message?.message?.length || 0} chars`);
2020
+ console.log(` Response preview: "${message?.message?.substring(0, 100) || 'NO MESSAGE'}${(message?.message?.length || 0) > 100 ? '...' : ''}"`);
2021
+ console.log(` Now simulating streaming by splitting into tokens...`);
2022
+ }
1969
2023
  if (message?.message) {
1970
2024
  // Simulate streaming by emitting tokens
1971
2025
  const words = message.message.split(" ");
@@ -1974,46 +2028,72 @@ class Graphlit {
1974
2028
  uiAdapter.handleEvent({ type: "token", token });
1975
2029
  }
1976
2030
  uiAdapter.handleEvent({ type: "message", message: message.message });
2031
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
2032
+ console.log(`\nšŸŽÆ [Fallback] Completed token simulation (${words.length} tokens)`);
2033
+ }
1977
2034
  }
1978
2035
  }
1979
2036
  /**
1980
2037
  * Stream with OpenAI client
1981
2038
  */
1982
2039
  async streamWithOpenAI(specification, messages, tools, uiAdapter, onComplete) {
1983
- if (!OpenAI) {
2040
+ // Check if we have either the OpenAI module or a provided client
2041
+ if (!OpenAI && !this.openaiClient) {
1984
2042
  throw new Error("OpenAI client not available");
1985
2043
  }
1986
2044
  // Use provided client or create a new one
1987
2045
  const openaiClient = this.openaiClient ||
1988
- new OpenAI({
2046
+ (OpenAI ? new OpenAI({
1989
2047
  apiKey: process.env.OPENAI_API_KEY || "",
1990
- });
2048
+ }) : (() => { throw new Error("OpenAI module not available"); })());
2049
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
2050
+ console.log("\nšŸš€ [Graphlit SDK] Routing to OpenAI streaming provider");
2051
+ console.log(` šŸ“‹ Specification: ${specification.name} (${specification.id})`);
2052
+ console.log(` šŸ“ Messages: ${messages.length}`);
2053
+ console.log(` šŸ”§ Tools: ${tools?.length || 0}`);
2054
+ }
1991
2055
  await streamWithOpenAI(specification, messages, tools, openaiClient, (event) => uiAdapter.handleEvent(event), onComplete);
1992
2056
  }
1993
2057
  /**
1994
2058
  * Stream with Anthropic client
1995
2059
  */
1996
2060
  async streamWithAnthropic(specification, messages, systemPrompt, tools, uiAdapter, onComplete) {
1997
- if (!Anthropic) {
2061
+ // Check if we have either the Anthropic module or a provided client
2062
+ if (!Anthropic && !this.anthropicClient) {
1998
2063
  throw new Error("Anthropic client not available");
1999
2064
  }
2000
2065
  // Use provided client or create a new one
2001
2066
  const anthropicClient = this.anthropicClient ||
2002
- new Anthropic({
2067
+ (Anthropic ? new Anthropic({
2003
2068
  apiKey: process.env.ANTHROPIC_API_KEY || "",
2004
- });
2069
+ }) : (() => { throw new Error("Anthropic module not available"); })());
2070
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
2071
+ console.log("\nšŸš€ [Graphlit SDK] Routing to Anthropic streaming provider");
2072
+ console.log(` šŸ“‹ Specification: ${specification.name} (${specification.id})`);
2073
+ console.log(` šŸ“ Messages: ${messages.length}`);
2074
+ console.log(` šŸ”§ Tools: ${tools?.length || 0}`);
2075
+ console.log(` šŸ’¬ System Prompt: ${systemPrompt ? 'Yes' : 'No'}`);
2076
+ }
2005
2077
  await streamWithAnthropic(specification, messages, systemPrompt, tools, anthropicClient, (event) => uiAdapter.handleEvent(event), onComplete);
2006
2078
  }
2007
2079
  /**
2008
2080
  * Stream with Google client
2009
2081
  */
2010
2082
  async streamWithGoogle(specification, messages, systemPrompt, tools, uiAdapter, onComplete) {
2011
- if (!GoogleGenerativeAI) {
2083
+ // Check if we have either the Google module or a provided client
2084
+ if (!GoogleGenerativeAI && !this.googleClient) {
2012
2085
  throw new Error("Google GenerativeAI client not available");
2013
2086
  }
2014
2087
  // Use provided client or create a new one
2015
2088
  const googleClient = this.googleClient ||
2016
- new GoogleGenerativeAI(process.env.GOOGLE_API_KEY || "");
2089
+ (GoogleGenerativeAI ? new GoogleGenerativeAI(process.env.GOOGLE_API_KEY || "") : (() => { throw new Error("Google GenerativeAI module not available"); })());
2090
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
2091
+ console.log("\nšŸš€ [Graphlit SDK] Routing to Google streaming provider");
2092
+ console.log(` šŸ“‹ Specification: ${specification.name} (${specification.id})`);
2093
+ console.log(` šŸ“ Messages: ${messages.length}`);
2094
+ console.log(` šŸ”§ Tools: ${tools?.length || 0}`);
2095
+ console.log(` šŸ’¬ System Prompt: ${systemPrompt ? 'Yes' : 'No'}`);
2096
+ }
2017
2097
  await streamWithGoogle(specification, messages, systemPrompt, tools, googleClient, (event) => uiAdapter.handleEvent(event), onComplete);
2018
2098
  }
2019
2099
  // Helper method to execute tools for promptAgent
@@ -4,7 +4,13 @@ import { ConversationMessage } from "../generated/graphql-types.js";
4
4
  */
5
5
  export interface OpenAIMessage {
6
6
  role: "system" | "user" | "assistant" | "tool";
7
- content?: string;
7
+ content?: string | Array<{
8
+ type: "text" | "image_url";
9
+ text?: string;
10
+ image_url?: {
11
+ url: string;
12
+ };
13
+ }>;
8
14
  tool_calls?: Array<{
9
15
  id: string;
10
16
  type: "function";
@@ -21,8 +27,13 @@ export interface OpenAIMessage {
21
27
  export interface AnthropicMessage {
22
28
  role: "user" | "assistant";
23
29
  content: string | Array<{
24
- type: "text" | "tool_use" | "tool_result";
30
+ type: "text" | "image" | "tool_use" | "tool_result";
25
31
  text?: string;
32
+ source?: {
33
+ type: "base64";
34
+ media_type: string;
35
+ data: string;
36
+ };
26
37
  id?: string;
27
38
  name?: string;
28
39
  input?: unknown;
@@ -37,6 +48,10 @@ export interface GoogleMessage {
37
48
  role: "user" | "model";
38
49
  parts: Array<{
39
50
  text?: string;
51
+ inlineData?: {
52
+ mimeType: string;
53
+ data: string;
54
+ };
40
55
  functionCall?: {
41
56
  name: string;
42
57
  args: unknown;
@@ -53,10 +53,36 @@ export function formatMessagesForOpenAI(messages) {
53
53
  });
54
54
  break;
55
55
  default: // User messages
56
- formattedMessages.push({
57
- role: "user",
58
- content: trimmedMessage,
59
- });
56
+ // Check if this message has image data
57
+ if (message.mimeType && message.data) {
58
+ // Multi-modal message with image
59
+ const contentParts = [];
60
+ // Add text content if present
61
+ if (trimmedMessage) {
62
+ contentParts.push({
63
+ type: "text",
64
+ text: trimmedMessage,
65
+ });
66
+ }
67
+ // Add image content
68
+ contentParts.push({
69
+ type: "image_url",
70
+ image_url: {
71
+ url: `data:${message.mimeType};base64,${message.data}`,
72
+ },
73
+ });
74
+ formattedMessages.push({
75
+ role: "user",
76
+ content: contentParts,
77
+ });
78
+ }
79
+ else {
80
+ // Text-only message
81
+ formattedMessages.push({
82
+ role: "user",
83
+ content: trimmedMessage,
84
+ });
85
+ }
60
86
  break;
61
87
  }
62
88
  }
@@ -122,10 +148,38 @@ export function formatMessagesForAnthropic(messages) {
122
148
  });
123
149
  break;
124
150
  default: // User messages
125
- formattedMessages.push({
126
- role: "user",
127
- content: trimmedMessage,
128
- });
151
+ // Check if this message has image data
152
+ if (message.mimeType && message.data) {
153
+ // Multi-modal message with image
154
+ const contentParts = [];
155
+ // Add text content if present
156
+ if (trimmedMessage) {
157
+ contentParts.push({
158
+ type: "text",
159
+ text: trimmedMessage,
160
+ });
161
+ }
162
+ // Add image content
163
+ contentParts.push({
164
+ type: "image",
165
+ source: {
166
+ type: "base64",
167
+ media_type: message.mimeType,
168
+ data: message.data,
169
+ },
170
+ });
171
+ formattedMessages.push({
172
+ role: "user",
173
+ content: contentParts,
174
+ });
175
+ }
176
+ else {
177
+ // Text-only message
178
+ formattedMessages.push({
179
+ role: "user",
180
+ content: trimmedMessage,
181
+ });
182
+ }
129
183
  break;
130
184
  }
131
185
  }
@@ -138,9 +192,14 @@ export function formatMessagesForAnthropic(messages) {
138
192
  export function formatMessagesForGoogle(messages) {
139
193
  const formattedMessages = [];
140
194
  for (const message of messages) {
141
- if (!message.role || !message.message?.trim())
195
+ if (!message.role)
142
196
  continue;
143
- const trimmedMessage = message.message.trim();
197
+ // Allow messages with image data even if they have no text content
198
+ const hasContent = message.message?.trim();
199
+ const hasImageData = message.mimeType && message.data;
200
+ if (!hasContent && !hasImageData)
201
+ continue;
202
+ const trimmedMessage = message.message?.trim() || "";
144
203
  switch (message.role) {
145
204
  case ConversationRoleTypes.System:
146
205
  // Google handles system prompts differently, usually as part of the first user message
@@ -176,10 +235,33 @@ export function formatMessagesForGoogle(messages) {
176
235
  });
177
236
  break;
178
237
  default: // User messages
179
- formattedMessages.push({
180
- role: "user",
181
- parts: [{ text: trimmedMessage }],
182
- });
238
+ // Check if this message has image data
239
+ if (message.mimeType && message.data) {
240
+ // Multi-modal message with image
241
+ const parts = [];
242
+ // Add text content if present
243
+ if (trimmedMessage) {
244
+ parts.push({ text: trimmedMessage });
245
+ }
246
+ // Add image content
247
+ parts.push({
248
+ inlineData: {
249
+ mimeType: message.mimeType,
250
+ data: message.data,
251
+ },
252
+ });
253
+ formattedMessages.push({
254
+ role: "user",
255
+ parts,
256
+ });
257
+ }
258
+ else {
259
+ // Text-only message
260
+ formattedMessages.push({
261
+ role: "user",
262
+ parts: [{ text: trimmedMessage }],
263
+ });
264
+ }
183
265
  break;
184
266
  }
185
267
  }
@@ -18,6 +18,12 @@ export async function streamWithOpenAI(specification, messages, tools, openaiCli
18
18
  onEvent, onComplete) {
19
19
  let fullMessage = "";
20
20
  let toolCalls = [];
21
+ // Performance metrics
22
+ const startTime = Date.now();
23
+ let firstTokenTime = 0;
24
+ let tokenCount = 0;
25
+ let lastEventTime = 0;
26
+ const interTokenDelays = [];
21
27
  try {
22
28
  const modelName = getModelName(specification);
23
29
  if (!modelName) {
@@ -55,6 +61,9 @@ onEvent, onComplete) {
55
61
  },
56
62
  }));
57
63
  }
64
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
65
+ console.log("\nā±ļø [OpenAI] Starting LLM call at:", new Date().toISOString());
66
+ }
58
67
  const stream = await openaiClient.chat.completions.create(streamConfig);
59
68
  for await (const chunk of stream) {
60
69
  const delta = chunk.choices[0]?.delta;
@@ -73,9 +82,23 @@ onEvent, onComplete) {
73
82
  }
74
83
  if (delta?.content) {
75
84
  fullMessage += delta.content;
85
+ tokenCount++;
86
+ const currentTime = Date.now();
87
+ // Track TTFT
88
+ if (firstTokenTime === 0) {
89
+ firstTokenTime = currentTime - startTime;
90
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
91
+ console.log(`\n⚔ [OpenAI] Time to First Token (TTFT): ${firstTokenTime}ms`);
92
+ }
93
+ }
94
+ // Track inter-token delays
95
+ if (lastEventTime > 0) {
96
+ const delay = currentTime - lastEventTime;
97
+ interTokenDelays.push(delay);
98
+ }
99
+ lastEventTime = currentTime;
76
100
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
77
- console.log(`[OpenAI] Message accumulated: ${fullMessage.length} chars total`);
78
- console.log(`[OpenAI] Current full message: "${fullMessage}"`);
101
+ console.log(`[OpenAI] Token #${tokenCount}: "${delta.content}" | Accumulated: ${fullMessage.length} chars`);
79
102
  }
80
103
  onEvent({
81
104
  type: "token",
@@ -153,8 +176,27 @@ onEvent, onComplete) {
153
176
  if (process.env.DEBUG_GRAPHLIT_STREAMING && toolCalls.length > 0) {
154
177
  console.log(`[OpenAI] Successfully processed ${toolCalls.length} tool calls`);
155
178
  }
179
+ // Calculate final metrics
180
+ const totalTime = Date.now() - startTime;
181
+ const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
156
182
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
157
- console.log(`[OpenAI] Streaming complete. Final message: "${fullMessage}" (${fullMessage.length} chars)`);
183
+ console.log("\nšŸ“Š [OpenAI] Performance Metrics:");
184
+ console.log(` ā±ļø Total Time: ${totalTime}ms`);
185
+ console.log(` ⚔ Time to First Token (TTFT): ${firstTokenTime}ms`);
186
+ console.log(` šŸ“ˆ Tokens Generated: ${tokenCount}`);
187
+ console.log(` šŸ’Ø Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
188
+ if (interTokenDelays.length > 0) {
189
+ const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
190
+ const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
191
+ const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
192
+ const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
193
+ const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
194
+ console.log(` ā³ Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
195
+ console.log(` šŸ“Š P50 Delay: ${p50Delay}ms`);
196
+ console.log(` āš ļø P95 Delay: ${p95Delay}ms`);
197
+ console.log(` 🚨 P99 Delay: ${p99Delay}ms`);
198
+ }
199
+ console.log(`\nāœ… [OpenAI] Final message (${fullMessage.length} chars): "${fullMessage}"`);
158
200
  }
159
201
  onComplete(fullMessage, toolCalls);
160
202
  }
@@ -173,6 +215,12 @@ export async function streamWithAnthropic(specification, messages, systemPrompt,
173
215
  onEvent, onComplete) {
174
216
  let fullMessage = "";
175
217
  let toolCalls = [];
218
+ // Performance metrics
219
+ const startTime = Date.now();
220
+ let firstTokenTime = 0;
221
+ let tokenCount = 0;
222
+ let lastEventTime = 0;
223
+ const interTokenDelays = [];
176
224
  try {
177
225
  const modelName = getModelName(specification);
178
226
  if (!modelName) {
@@ -207,6 +255,9 @@ onEvent, onComplete) {
207
255
  input_schema: tool.schema ? JSON.parse(tool.schema) : {},
208
256
  }));
209
257
  }
258
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
259
+ console.log("\nā±ļø [Anthropic] Starting LLM call at:", new Date().toISOString());
260
+ }
210
261
  const stream = await anthropicClient.messages.create(streamConfig);
211
262
  let activeContentBlock = false;
212
263
  for await (const chunk of stream) {
@@ -234,10 +285,25 @@ onEvent, onComplete) {
234
285
  }
235
286
  else if (chunk.type === "content_block_delta") {
236
287
  if (chunk.delta.type === "text_delta") {
288
+ fullMessage += chunk.delta.text;
289
+ tokenCount++;
290
+ const currentTime = Date.now();
291
+ // Track TTFT
292
+ if (firstTokenTime === 0) {
293
+ firstTokenTime = currentTime - startTime;
294
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
295
+ console.log(`\n⚔ [Anthropic] Time to First Token (TTFT): ${firstTokenTime}ms`);
296
+ }
297
+ }
298
+ // Track inter-token delays
299
+ if (lastEventTime > 0) {
300
+ const delay = currentTime - lastEventTime;
301
+ interTokenDelays.push(delay);
302
+ }
303
+ lastEventTime = currentTime;
237
304
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
238
- console.log(`[Anthropic] Text delta: "${chunk.delta.text}"`);
305
+ console.log(`[Anthropic] Token #${tokenCount}: "${chunk.delta.text}" | Accumulated: ${fullMessage.length} chars`);
239
306
  }
240
- fullMessage += chunk.delta.text;
241
307
  onEvent({
242
308
  type: "token",
243
309
  token: chunk.delta.text,
@@ -336,6 +402,28 @@ onEvent, onComplete) {
336
402
  console.log(`[Anthropic] Filtered out ${toolCalls.length - validToolCalls.length} incomplete tool calls`);
337
403
  console.log(`[Anthropic] Successfully processed ${validToolCalls.length} valid tool calls`);
338
404
  }
405
+ // Calculate final metrics
406
+ const totalTime = Date.now() - startTime;
407
+ const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
408
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
409
+ console.log("\nšŸ“Š [Anthropic] Performance Metrics:");
410
+ console.log(` ā±ļø Total Time: ${totalTime}ms`);
411
+ console.log(` ⚔ Time to First Token (TTFT): ${firstTokenTime}ms`);
412
+ console.log(` šŸ“ˆ Tokens Generated: ${tokenCount}`);
413
+ console.log(` šŸ’Ø Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
414
+ if (interTokenDelays.length > 0) {
415
+ const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
416
+ const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
417
+ const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
418
+ const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
419
+ const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
420
+ console.log(` ā³ Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
421
+ console.log(` šŸ“Š P50 Delay: ${p50Delay}ms`);
422
+ console.log(` āš ļø P95 Delay: ${p95Delay}ms`);
423
+ console.log(` 🚨 P99 Delay: ${p99Delay}ms`);
424
+ }
425
+ console.log(`\nāœ… [Anthropic] Final message (${fullMessage.length} chars): "${fullMessage}"`);
426
+ }
339
427
  onComplete(fullMessage, validToolCalls);
340
428
  }
341
429
  catch (error) {
@@ -353,6 +441,12 @@ export async function streamWithGoogle(specification, messages, systemPrompt, to
353
441
  onEvent, onComplete) {
354
442
  let fullMessage = "";
355
443
  let toolCalls = [];
444
+ // Performance metrics
445
+ const startTime = Date.now();
446
+ let firstTokenTime = 0;
447
+ let tokenCount = 0;
448
+ let lastEventTime = 0;
449
+ const interTokenDelays = [];
356
450
  try {
357
451
  const modelName = getModelName(specification);
358
452
  if (!modelName) {
@@ -559,6 +653,28 @@ onEvent, onComplete) {
559
653
  if (process.env.DEBUG_GRAPHLIT_STREAMING && toolCalls.length > 0) {
560
654
  console.log(`[Google] Successfully processed ${toolCalls.length} tool calls`);
561
655
  }
656
+ // Calculate final metrics
657
+ const totalTime = Date.now() - startTime;
658
+ const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
659
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
660
+ console.log("\nšŸ“Š [Google] Performance Metrics:");
661
+ console.log(` ā±ļø Total Time: ${totalTime}ms`);
662
+ console.log(` ⚔ Time to First Token (TTFT): ${firstTokenTime}ms`);
663
+ console.log(` šŸ“ˆ Tokens Generated: ${tokenCount}`);
664
+ console.log(` šŸ’Ø Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
665
+ if (interTokenDelays.length > 0) {
666
+ const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
667
+ const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
668
+ const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
669
+ const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
670
+ const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
671
+ console.log(` ā³ Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
672
+ console.log(` šŸ“Š P50 Delay: ${p50Delay}ms`);
673
+ console.log(` āš ļø P95 Delay: ${p95Delay}ms`);
674
+ console.log(` 🚨 P99 Delay: ${p99Delay}ms`);
675
+ }
676
+ console.log(`\nāœ… [Google] Final message (${fullMessage.length} chars): "${fullMessage}"`);
677
+ }
562
678
  onComplete(fullMessage, toolCalls);
563
679
  }
564
680
  catch (error) {
@@ -150,21 +150,17 @@ export class UIEventAdapter {
150
150
  }
151
151
  }
152
152
  handleComplete() {
153
- // Flush any remaining chunks from buffer
154
- if (this.chunkBuffer) {
155
- const remaining = this.chunkBuffer.flush();
156
- this.chunkQueue.push(...remaining);
157
- }
158
153
  // Clear any pending updates
159
154
  if (this.updateTimer) {
160
155
  globalThis.clearTimeout(this.updateTimer);
161
156
  this.updateTimer = undefined;
162
157
  }
163
- // Immediately flush all queued chunks
164
- while (this.chunkQueue.length > 0) {
165
- const chunk = this.chunkQueue.shift();
166
- this.currentMessage += chunk;
158
+ // DO NOT re-process chunks here - they should already be in currentMessage
159
+ // Just clear any remaining state
160
+ if (this.chunkBuffer) {
161
+ this.chunkBuffer.flush(); // Clear the buffer but don't use the result
167
162
  }
163
+ this.chunkQueue.length = 0; // Clear any remaining queue
168
164
  this.isStreaming = false;
169
165
  // Create final message
170
166
  const finalMessage = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphlit-client",
3
- "version": "1.0.20250611019",
3
+ "version": "1.0.20250612001",
4
4
  "description": "Graphlit API Client for TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/client.js",