graphlit-client 1.0.20250612001 → 1.0.20250612003

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
@@ -1566,12 +1566,7 @@ class Graphlit {
1566
1566
  ? (await this.getSpecification(specification.id))
1567
1567
  .specification
1568
1568
  : undefined;
1569
- // Check streaming support
1570
- if (fullSpec && !this.supportsStreaming(fullSpec)) {
1571
- throw new Error("Streaming is not supported for this specification. " +
1572
- "Use promptAgent() instead or configure a streaming client.");
1573
- }
1574
- // Ensure conversation
1569
+ // Ensure conversation exists first (before streaming check)
1575
1570
  let actualConversationId = conversationId;
1576
1571
  if (!actualConversationId) {
1577
1572
  const createResponse = await this.createConversation({
@@ -1586,6 +1581,47 @@ class Graphlit {
1586
1581
  throw new Error("Failed to create conversation");
1587
1582
  }
1588
1583
  }
1584
+ // Check streaming support - fallback to promptAgent if not supported
1585
+ if (fullSpec && !this.supportsStreaming(fullSpec)) {
1586
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
1587
+ console.log("\n⚠️ [streamAgent] Streaming not supported, falling back to promptAgent with same conversation");
1588
+ }
1589
+ // Fallback to promptAgent using the same conversation and parameters
1590
+ const promptResult = await this.promptAgent(prompt, actualConversationId, // Preserve conversation
1591
+ specification, tools, toolHandlers, {
1592
+ maxToolRounds: maxRounds,
1593
+ }, mimeType, data, contentFilter, augmentedFilter, correlationId);
1594
+ // Convert promptAgent result to streaming events
1595
+ onEvent({
1596
+ type: "conversation_started",
1597
+ conversationId: actualConversationId,
1598
+ timestamp: new Date(),
1599
+ });
1600
+ // Emit the final message as a single update (simulating streaming)
1601
+ onEvent({
1602
+ type: "message_update",
1603
+ message: {
1604
+ __typename: "ConversationMessage",
1605
+ message: promptResult.message,
1606
+ role: Types.ConversationRoleTypes.Assistant,
1607
+ timestamp: new Date().toISOString(),
1608
+ toolCalls: [],
1609
+ },
1610
+ isStreaming: false,
1611
+ });
1612
+ // Emit completion event
1613
+ onEvent({
1614
+ type: "conversation_completed",
1615
+ message: {
1616
+ __typename: "ConversationMessage",
1617
+ message: promptResult.message,
1618
+ role: Types.ConversationRoleTypes.Assistant,
1619
+ timestamp: new Date().toISOString(),
1620
+ toolCalls: [],
1621
+ },
1622
+ });
1623
+ return; // Exit early after successful fallback
1624
+ }
1589
1625
  // Create UI event adapter
1590
1626
  uiAdapter = new UIEventAdapter(onEvent, actualConversationId, {
1591
1627
  smoothingEnabled: options?.smoothingEnabled ?? true,
@@ -636,6 +636,7 @@ export const DescribeEncodedImage = gql `
636
636
  posts
637
637
  chapters
638
638
  questions
639
+ quotes
639
640
  video {
640
641
  width
641
642
  height
@@ -770,6 +771,7 @@ export const DescribeImage = gql `
770
771
  posts
771
772
  chapters
772
773
  questions
774
+ quotes
773
775
  video {
774
776
  width
775
777
  height
@@ -1625,6 +1627,7 @@ export const PublishContents = gql `
1625
1627
  posts
1626
1628
  chapters
1627
1629
  questions
1630
+ quotes
1628
1631
  video {
1629
1632
  width
1630
1633
  height
@@ -1749,6 +1752,7 @@ export const PublishText = gql `
1749
1752
  posts
1750
1753
  chapters
1751
1754
  questions
1755
+ quotes
1752
1756
  video {
1753
1757
  width
1754
1758
  height
@@ -1880,6 +1884,7 @@ export const QueryContents = gql `
1880
1884
  posts
1881
1885
  chapters
1882
1886
  questions
1887
+ quotes
1883
1888
  video {
1884
1889
  width
1885
1890
  height
@@ -2316,6 +2321,7 @@ export const AskGraphlit = gql `
2316
2321
  posts
2317
2322
  chapters
2318
2323
  questions
2324
+ quotes
2319
2325
  video {
2320
2326
  width
2321
2327
  height
@@ -2485,6 +2491,7 @@ export const CompleteConversation = gql `
2485
2491
  posts
2486
2492
  chapters
2487
2493
  questions
2494
+ quotes
2488
2495
  video {
2489
2496
  width
2490
2497
  height
@@ -2662,6 +2669,7 @@ export const CompleteConversation = gql `
2662
2669
  posts
2663
2670
  chapters
2664
2671
  questions
2672
+ quotes
2665
2673
  video {
2666
2674
  width
2667
2675
  height
@@ -2801,6 +2809,7 @@ export const ContinueConversation = gql `
2801
2809
  posts
2802
2810
  chapters
2803
2811
  questions
2812
+ quotes
2804
2813
  video {
2805
2814
  width
2806
2815
  height
@@ -2978,6 +2987,7 @@ export const ContinueConversation = gql `
2978
2987
  posts
2979
2988
  chapters
2980
2989
  questions
2990
+ quotes
2981
2991
  video {
2982
2992
  width
2983
2993
  height
@@ -3165,6 +3175,7 @@ export const FormatConversation = gql `
3165
3175
  posts
3166
3176
  chapters
3167
3177
  questions
3178
+ quotes
3168
3179
  video {
3169
3180
  width
3170
3181
  height
@@ -3342,6 +3353,7 @@ export const FormatConversation = gql `
3342
3353
  posts
3343
3354
  chapters
3344
3355
  questions
3356
+ quotes
3345
3357
  video {
3346
3358
  width
3347
3359
  height
@@ -3484,6 +3496,7 @@ export const GetConversation = gql `
3484
3496
  posts
3485
3497
  chapters
3486
3498
  questions
3499
+ quotes
3487
3500
  video {
3488
3501
  width
3489
3502
  height
@@ -3799,6 +3812,7 @@ export const Prompt = gql `
3799
3812
  posts
3800
3813
  chapters
3801
3814
  questions
3815
+ quotes
3802
3816
  video {
3803
3817
  width
3804
3818
  height
@@ -3944,6 +3958,7 @@ export const PromptConversation = gql `
3944
3958
  posts
3945
3959
  chapters
3946
3960
  questions
3961
+ quotes
3947
3962
  video {
3948
3963
  width
3949
3964
  height
@@ -4121,6 +4136,7 @@ export const PromptConversation = gql `
4121
4136
  posts
4122
4137
  chapters
4123
4138
  questions
4139
+ quotes
4124
4140
  video {
4125
4141
  width
4126
4142
  height
@@ -4256,6 +4272,7 @@ export const PublishConversation = gql `
4256
4272
  posts
4257
4273
  chapters
4258
4274
  questions
4275
+ quotes
4259
4276
  video {
4260
4277
  width
4261
4278
  height
@@ -4387,6 +4404,7 @@ export const QueryConversations = gql `
4387
4404
  posts
4388
4405
  chapters
4389
4406
  questions
4407
+ quotes
4390
4408
  video {
4391
4409
  width
4392
4410
  height
@@ -4728,6 +4746,7 @@ export const ReviseContent = gql `
4728
4746
  posts
4729
4747
  chapters
4730
4748
  questions
4749
+ quotes
4731
4750
  video {
4732
4751
  width
4733
4752
  height
@@ -4870,6 +4889,7 @@ export const ReviseEncodedImage = gql `
4870
4889
  posts
4871
4890
  chapters
4872
4891
  questions
4892
+ quotes
4873
4893
  video {
4874
4894
  width
4875
4895
  height
@@ -5011,6 +5031,7 @@ export const ReviseImage = gql `
5011
5031
  posts
5012
5032
  chapters
5013
5033
  questions
5034
+ quotes
5014
5035
  video {
5015
5036
  width
5016
5037
  height
@@ -5152,6 +5173,7 @@ export const ReviseText = gql `
5152
5173
  posts
5153
5174
  chapters
5154
5175
  questions
5176
+ quotes
5155
5177
  video {
5156
5178
  width
5157
5179
  height
@@ -8187,6 +8209,7 @@ export const PromptSpecifications = gql `
8187
8209
  posts
8188
8210
  chapters
8189
8211
  questions
8212
+ quotes
8190
8213
  video {
8191
8214
  width
8192
8215
  height
@@ -1645,6 +1645,8 @@ export type Content = {
1645
1645
  posts?: Maybe<Array<Scalars['String']['output']>>;
1646
1646
  /** The followup questions which can be asked about the content. */
1647
1647
  questions?: Maybe<Array<Scalars['String']['output']>>;
1648
+ /** Quotes extracted from the content. */
1649
+ quotes?: Maybe<Array<Scalars['String']['output']>>;
1648
1650
  /** The relevance score of the content. */
1649
1651
  relevance?: Maybe<Scalars['Float']['output']>;
1650
1652
  /** The renditions generated from this content. */
@@ -2206,6 +2208,8 @@ export type ContentUpdateInput = {
2206
2208
  posts?: InputMaybe<Array<Scalars['String']['input']>>;
2207
2209
  /** The followup questions which can be asked about the content. */
2208
2210
  questions?: InputMaybe<Array<Scalars['String']['input']>>;
2211
+ /** Quotes extracted from the content. */
2212
+ quotes?: InputMaybe<Array<Scalars['String']['input']>>;
2209
2213
  /** The content shape metadata. */
2210
2214
  shape?: InputMaybe<ShapeMetadataInput>;
2211
2215
  /** The content summary. */
@@ -2529,6 +2533,8 @@ export type ConversationUpdateInput = {
2529
2533
  filter?: InputMaybe<ContentCriteriaInput>;
2530
2534
  /** The ID of the conversation to update. */
2531
2535
  id: Scalars['ID']['input'];
2536
+ /** The conversation messages. */
2537
+ messages?: InputMaybe<Array<ConversationMessageInput>>;
2532
2538
  /** The name of the conversation. */
2533
2539
  name?: InputMaybe<Scalars['String']['input']>;
2534
2540
  /** The LLM specification used by this conversation, optional. */
@@ -13039,6 +13045,8 @@ export declare enum SummarizationTypes {
13039
13045
  Posts = "POSTS",
13040
13046
  /** Questions */
13041
13047
  Questions = "QUESTIONS",
13048
+ /** Quote */
13049
+ Quotes = "QUOTES",
13042
13050
  /** Summary */
13043
13051
  Summary = "SUMMARY"
13044
13052
  }
@@ -14739,6 +14747,7 @@ export type DescribeEncodedImageMutation = {
14739
14747
  posts?: Array<string> | null;
14740
14748
  chapters?: Array<string> | null;
14741
14749
  questions?: Array<string> | null;
14750
+ quotes?: Array<string> | null;
14742
14751
  video?: {
14743
14752
  __typename?: 'VideoMetadata';
14744
14753
  width?: number | null;
@@ -14881,6 +14890,7 @@ export type DescribeImageMutation = {
14881
14890
  posts?: Array<string> | null;
14882
14891
  chapters?: Array<string> | null;
14883
14892
  questions?: Array<string> | null;
14893
+ quotes?: Array<string> | null;
14884
14894
  video?: {
14885
14895
  __typename?: 'VideoMetadata';
14886
14896
  width?: number | null;
@@ -15821,6 +15831,7 @@ export type PublishContentsMutation = {
15821
15831
  posts?: Array<string> | null;
15822
15832
  chapters?: Array<string> | null;
15823
15833
  questions?: Array<string> | null;
15834
+ quotes?: Array<string> | null;
15824
15835
  video?: {
15825
15836
  __typename?: 'VideoMetadata';
15826
15837
  width?: number | null;
@@ -15953,6 +15964,7 @@ export type PublishTextMutation = {
15953
15964
  posts?: Array<string> | null;
15954
15965
  chapters?: Array<string> | null;
15955
15966
  questions?: Array<string> | null;
15967
+ quotes?: Array<string> | null;
15956
15968
  video?: {
15957
15969
  __typename?: 'VideoMetadata';
15958
15970
  width?: number | null;
@@ -16081,6 +16093,7 @@ export type QueryContentsQuery = {
16081
16093
  posts?: Array<string> | null;
16082
16094
  chapters?: Array<string> | null;
16083
16095
  questions?: Array<string> | null;
16096
+ quotes?: Array<string> | null;
16084
16097
  error?: string | null;
16085
16098
  owner: {
16086
16099
  __typename?: 'Owner';
@@ -16623,6 +16636,7 @@ export type AskGraphlitMutation = {
16623
16636
  posts?: Array<string> | null;
16624
16637
  chapters?: Array<string> | null;
16625
16638
  questions?: Array<string> | null;
16639
+ quotes?: Array<string> | null;
16626
16640
  video?: {
16627
16641
  __typename?: 'VideoMetadata';
16628
16642
  width?: number | null;
@@ -16811,6 +16825,7 @@ export type CompleteConversationMutation = {
16811
16825
  posts?: Array<string> | null;
16812
16826
  chapters?: Array<string> | null;
16813
16827
  questions?: Array<string> | null;
16828
+ quotes?: Array<string> | null;
16814
16829
  video?: {
16815
16830
  __typename?: 'VideoMetadata';
16816
16831
  width?: number | null;
@@ -17003,6 +17018,7 @@ export type CompleteConversationMutation = {
17003
17018
  posts?: Array<string> | null;
17004
17019
  chapters?: Array<string> | null;
17005
17020
  questions?: Array<string> | null;
17021
+ quotes?: Array<string> | null;
17006
17022
  video?: {
17007
17023
  __typename?: 'VideoMetadata';
17008
17024
  width?: number | null;
@@ -17153,6 +17169,7 @@ export type ContinueConversationMutation = {
17153
17169
  posts?: Array<string> | null;
17154
17170
  chapters?: Array<string> | null;
17155
17171
  questions?: Array<string> | null;
17172
+ quotes?: Array<string> | null;
17156
17173
  video?: {
17157
17174
  __typename?: 'VideoMetadata';
17158
17175
  width?: number | null;
@@ -17345,6 +17362,7 @@ export type ContinueConversationMutation = {
17345
17362
  posts?: Array<string> | null;
17346
17363
  chapters?: Array<string> | null;
17347
17364
  questions?: Array<string> | null;
17365
+ quotes?: Array<string> | null;
17348
17366
  video?: {
17349
17367
  __typename?: 'VideoMetadata';
17350
17368
  width?: number | null;
@@ -17559,6 +17577,7 @@ export type FormatConversationMutation = {
17559
17577
  posts?: Array<string> | null;
17560
17578
  chapters?: Array<string> | null;
17561
17579
  questions?: Array<string> | null;
17580
+ quotes?: Array<string> | null;
17562
17581
  video?: {
17563
17582
  __typename?: 'VideoMetadata';
17564
17583
  width?: number | null;
@@ -17751,6 +17770,7 @@ export type FormatConversationMutation = {
17751
17770
  posts?: Array<string> | null;
17752
17771
  chapters?: Array<string> | null;
17753
17772
  questions?: Array<string> | null;
17773
+ quotes?: Array<string> | null;
17754
17774
  video?: {
17755
17775
  __typename?: 'VideoMetadata';
17756
17776
  width?: number | null;
@@ -17906,6 +17926,7 @@ export type GetConversationQuery = {
17906
17926
  posts?: Array<string> | null;
17907
17927
  chapters?: Array<string> | null;
17908
17928
  questions?: Array<string> | null;
17929
+ quotes?: Array<string> | null;
17909
17930
  video?: {
17910
17931
  __typename?: 'VideoMetadata';
17911
17932
  width?: number | null;
@@ -18284,6 +18305,7 @@ export type PromptMutation = {
18284
18305
  posts?: Array<string> | null;
18285
18306
  chapters?: Array<string> | null;
18286
18307
  questions?: Array<string> | null;
18308
+ quotes?: Array<string> | null;
18287
18309
  video?: {
18288
18310
  __typename?: 'VideoMetadata';
18289
18311
  width?: number | null;
@@ -18439,6 +18461,7 @@ export type PromptConversationMutation = {
18439
18461
  posts?: Array<string> | null;
18440
18462
  chapters?: Array<string> | null;
18441
18463
  questions?: Array<string> | null;
18464
+ quotes?: Array<string> | null;
18442
18465
  video?: {
18443
18466
  __typename?: 'VideoMetadata';
18444
18467
  width?: number | null;
@@ -18631,6 +18654,7 @@ export type PromptConversationMutation = {
18631
18654
  posts?: Array<string> | null;
18632
18655
  chapters?: Array<string> | null;
18633
18656
  questions?: Array<string> | null;
18657
+ quotes?: Array<string> | null;
18634
18658
  video?: {
18635
18659
  __typename?: 'VideoMetadata';
18636
18660
  width?: number | null;
@@ -18757,6 +18781,7 @@ export type PublishConversationMutation = {
18757
18781
  posts?: Array<string> | null;
18758
18782
  chapters?: Array<string> | null;
18759
18783
  questions?: Array<string> | null;
18784
+ quotes?: Array<string> | null;
18760
18785
  video?: {
18761
18786
  __typename?: 'VideoMetadata';
18762
18787
  width?: number | null;
@@ -18919,6 +18944,7 @@ export type QueryConversationsQuery = {
18919
18944
  posts?: Array<string> | null;
18920
18945
  chapters?: Array<string> | null;
18921
18946
  questions?: Array<string> | null;
18947
+ quotes?: Array<string> | null;
18922
18948
  video?: {
18923
18949
  __typename?: 'VideoMetadata';
18924
18950
  width?: number | null;
@@ -19326,6 +19352,7 @@ export type ReviseContentMutation = {
19326
19352
  posts?: Array<string> | null;
19327
19353
  chapters?: Array<string> | null;
19328
19354
  questions?: Array<string> | null;
19355
+ quotes?: Array<string> | null;
19329
19356
  video?: {
19330
19357
  __typename?: 'VideoMetadata';
19331
19358
  width?: number | null;
@@ -19478,6 +19505,7 @@ export type ReviseEncodedImageMutation = {
19478
19505
  posts?: Array<string> | null;
19479
19506
  chapters?: Array<string> | null;
19480
19507
  questions?: Array<string> | null;
19508
+ quotes?: Array<string> | null;
19481
19509
  video?: {
19482
19510
  __typename?: 'VideoMetadata';
19483
19511
  width?: number | null;
@@ -19629,6 +19657,7 @@ export type ReviseImageMutation = {
19629
19657
  posts?: Array<string> | null;
19630
19658
  chapters?: Array<string> | null;
19631
19659
  questions?: Array<string> | null;
19660
+ quotes?: Array<string> | null;
19632
19661
  video?: {
19633
19662
  __typename?: 'VideoMetadata';
19634
19663
  width?: number | null;
@@ -19780,6 +19809,7 @@ export type ReviseTextMutation = {
19780
19809
  posts?: Array<string> | null;
19781
19810
  chapters?: Array<string> | null;
19782
19811
  questions?: Array<string> | null;
19812
+ quotes?: Array<string> | null;
19783
19813
  video?: {
19784
19814
  __typename?: 'VideoMetadata';
19785
19815
  width?: number | null;
@@ -23592,6 +23622,7 @@ export type PromptSpecificationsMutation = {
23592
23622
  posts?: Array<string> | null;
23593
23623
  chapters?: Array<string> | null;
23594
23624
  questions?: Array<string> | null;
23625
+ quotes?: Array<string> | null;
23595
23626
  video?: {
23596
23627
  __typename?: 'VideoMetadata';
23597
23628
  width?: number | null;
@@ -1866,6 +1866,8 @@ export var SummarizationTypes;
1866
1866
  SummarizationTypes["Posts"] = "POSTS";
1867
1867
  /** Questions */
1868
1868
  SummarizationTypes["Questions"] = "QUESTIONS";
1869
+ /** Quote */
1870
+ SummarizationTypes["Quotes"] = "QUOTES";
1869
1871
  /** Summary */
1870
1872
  SummarizationTypes["Summary"] = "SUMMARY";
1871
1873
  })(SummarizationTypes || (SummarizationTypes = {}));
@@ -21,9 +21,22 @@ onEvent, onComplete) {
21
21
  // Performance metrics
22
22
  const startTime = Date.now();
23
23
  let firstTokenTime = 0;
24
+ let firstMeaningfulContentTime = 0;
24
25
  let tokenCount = 0;
26
+ let toolArgumentTokens = 0;
25
27
  let lastEventTime = 0;
26
28
  const interTokenDelays = [];
29
+ // Tool calling metrics
30
+ const toolMetrics = {
31
+ totalTools: 0,
32
+ successfulTools: 0,
33
+ failedTools: 0,
34
+ toolTimes: [],
35
+ currentToolStart: 0,
36
+ roundStartTime: startTime,
37
+ rounds: [],
38
+ currentRound: 1
39
+ };
27
40
  try {
28
41
  const modelName = getModelName(specification);
29
42
  if (!modelName) {
@@ -84,13 +97,20 @@ onEvent, onComplete) {
84
97
  fullMessage += delta.content;
85
98
  tokenCount++;
86
99
  const currentTime = Date.now();
87
- // Track TTFT
100
+ // Track TTFT (first token regardless of type)
88
101
  if (firstTokenTime === 0) {
89
102
  firstTokenTime = currentTime - startTime;
90
103
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
91
104
  console.log(`\n⚡ [OpenAI] Time to First Token (TTFT): ${firstTokenTime}ms`);
92
105
  }
93
106
  }
107
+ // Track first meaningful content (excludes tool calls)
108
+ if (firstMeaningfulContentTime === 0 && delta.content.trim()) {
109
+ firstMeaningfulContentTime = currentTime - startTime;
110
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
111
+ console.log(`\n🎯 [OpenAI] Time to First Meaningful Content: ${firstMeaningfulContentTime}ms`);
112
+ }
113
+ }
94
114
  // Track inter-token delays
95
115
  if (lastEventTime > 0) {
96
116
  const delay = currentTime - lastEventTime;
@@ -115,6 +135,22 @@ onEvent, onComplete) {
115
135
  name: "",
116
136
  arguments: "",
117
137
  };
138
+ // Track tool metrics
139
+ toolMetrics.totalTools++;
140
+ toolMetrics.currentToolStart = Date.now();
141
+ toolMetrics.toolTimes.push({
142
+ name: toolCallDelta.function?.name || "unknown",
143
+ startTime: toolMetrics.currentToolStart,
144
+ argumentBuildTime: 0,
145
+ totalTime: 0
146
+ });
147
+ // Track TTFT for first tool if no content yet
148
+ if (firstTokenTime === 0) {
149
+ firstTokenTime = Date.now() - startTime;
150
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
151
+ console.log(`\n⚡ [OpenAI] Time to First Token (Tool Call): ${firstTokenTime}ms`);
152
+ }
153
+ }
118
154
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
119
155
  console.log(`[OpenAI] Starting new tool call: ${toolCalls[index].id}`);
120
156
  }
@@ -134,6 +170,8 @@ onEvent, onComplete) {
134
170
  }
135
171
  if (toolCallDelta.function?.arguments) {
136
172
  toolCalls[index].arguments += toolCallDelta.function.arguments;
173
+ // Count tool argument tokens (rough estimate: ~4 chars per token)
174
+ toolArgumentTokens += Math.ceil(toolCallDelta.function.arguments.length / 4);
137
175
  // Debug logging for partial JSON accumulation
138
176
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
139
177
  console.log(`[OpenAI] Tool ${toolCalls[index].name} - Partial JSON chunk: "${toolCallDelta.function.arguments}"`);
@@ -148,20 +186,33 @@ onEvent, onComplete) {
148
186
  }
149
187
  }
150
188
  }
151
- // Emit complete events for tool calls
152
- for (const toolCall of toolCalls) {
189
+ // Emit complete events for tool calls and finalize metrics
190
+ for (let i = 0; i < toolCalls.length; i++) {
191
+ const toolCall = toolCalls[i];
192
+ const currentTime = Date.now();
193
+ // Update tool metrics
194
+ if (i < toolMetrics.toolTimes.length) {
195
+ const toolTime = toolMetrics.toolTimes[i];
196
+ toolTime.argumentBuildTime = currentTime - toolTime.startTime;
197
+ toolTime.totalTime = toolTime.argumentBuildTime; // For streaming, this is the same
198
+ toolTime.name = toolCall.name; // Update with final name
199
+ }
200
+ // Track tool success/failure
201
+ try {
202
+ JSON.parse(toolCall.arguments);
203
+ toolMetrics.successfulTools++;
204
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
205
+ console.log(`[OpenAI] ✅ Valid JSON for ${toolCall.name}`);
206
+ }
207
+ }
208
+ catch (e) {
209
+ toolMetrics.failedTools++;
210
+ console.error(`[OpenAI] ❌ Invalid JSON for ${toolCall.name}: ${e}`);
211
+ }
153
212
  // Log the final JSON for debugging
154
213
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
155
214
  console.log(`[OpenAI] Tool ${toolCall.name} complete with arguments (${toolCall.arguments.length} chars):`);
156
215
  console.log(toolCall.arguments);
157
- // Validate JSON
158
- try {
159
- JSON.parse(toolCall.arguments);
160
- console.log(`[OpenAI] ✅ Valid JSON for ${toolCall.name}`);
161
- }
162
- catch (e) {
163
- console.error(`[OpenAI] ❌ Invalid JSON for ${toolCall.name}: ${e}`);
164
- }
165
216
  }
166
217
  onEvent({
167
218
  type: "tool_call_complete",
@@ -176,22 +227,60 @@ onEvent, onComplete) {
176
227
  if (process.env.DEBUG_GRAPHLIT_STREAMING && toolCalls.length > 0) {
177
228
  console.log(`[OpenAI] Successfully processed ${toolCalls.length} tool calls`);
178
229
  }
179
- // Calculate final metrics
230
+ // Calculate final metrics including tool calling insights
180
231
  const totalTime = Date.now() - startTime;
181
- const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
232
+ const totalTokens = tokenCount + toolArgumentTokens;
233
+ const tokensPerSecond = totalTokens > 0 ? totalTokens / (totalTime / 1000) : 0;
234
+ // Finalize round metrics
235
+ if (toolCalls.length > 0) {
236
+ const roundEndTime = Date.now();
237
+ const totalToolTime = toolMetrics.toolTimes.reduce((sum, tool) => sum + tool.totalTime, 0);
238
+ const llmTime = totalTime - totalToolTime;
239
+ toolMetrics.rounds.push({
240
+ roundNumber: toolMetrics.currentRound,
241
+ llmTime: llmTime,
242
+ toolTime: totalToolTime,
243
+ toolCount: toolCalls.length
244
+ });
245
+ }
182
246
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
183
247
  console.log("\n📊 [OpenAI] Performance Metrics:");
184
248
  console.log(` ⏱️ Total Time: ${totalTime}ms`);
185
249
  console.log(` ⚡ Time to First Token (TTFT): ${firstTokenTime}ms`);
186
- console.log(` 📈 Tokens Generated: ${tokenCount}`);
250
+ if (firstMeaningfulContentTime > 0) {
251
+ console.log(` 🎯 Time to First Meaningful Content: ${firstMeaningfulContentTime}ms`);
252
+ }
253
+ console.log(` 📈 Content Tokens: ${tokenCount}`);
254
+ console.log(` 🔧 Tool Argument Tokens: ${toolArgumentTokens}`);
255
+ console.log(` 📊 Total Tokens: ${totalTokens}`);
187
256
  console.log(` 💨 Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
257
+ // Tool calling metrics
258
+ if (toolCalls.length > 0) {
259
+ console.log(`\n🔧 [OpenAI] Tool Calling Metrics:`);
260
+ console.log(` 🛠️ Total Tools Called: ${toolMetrics.totalTools}`);
261
+ console.log(` ✅ Successful Tools: ${toolMetrics.successfulTools}`);
262
+ console.log(` ❌ Failed Tools: ${toolMetrics.failedTools}`);
263
+ console.log(` 📊 Success Rate: ${((toolMetrics.successfulTools / toolMetrics.totalTools) * 100).toFixed(1)}%`);
264
+ // Tool timing details
265
+ toolMetrics.toolTimes.forEach((tool, idx) => {
266
+ console.log(` 🔨 Tool ${idx + 1} (${tool.name}): ${tool.argumentBuildTime}ms`);
267
+ });
268
+ const avgToolTime = toolMetrics.toolTimes.reduce((sum, tool) => sum + tool.totalTime, 0) / toolMetrics.toolTimes.length;
269
+ console.log(` ⏱️ Average Tool Time: ${avgToolTime.toFixed(2)}ms`);
270
+ // Round metrics
271
+ toolMetrics.rounds.forEach(round => {
272
+ const efficiency = round.toolCount > 0 ? (round.llmTime / (round.llmTime + round.toolTime) * 100).toFixed(1) : 100;
273
+ console.log(` 🔄 Round ${round.roundNumber}: LLM=${round.llmTime}ms, Tools=${round.toolTime}ms (${round.toolCount} tools), Efficiency=${efficiency}%`);
274
+ });
275
+ }
188
276
  if (interTokenDelays.length > 0) {
189
277
  const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
190
278
  const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
191
279
  const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
192
280
  const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
193
281
  const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
194
- console.log(` Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
282
+ console.log(`\n[OpenAI] Inter-Token Timing:`);
283
+ console.log(` 📊 Average Delay: ${avgDelay.toFixed(2)}ms`);
195
284
  console.log(` 📊 P50 Delay: ${p50Delay}ms`);
196
285
  console.log(` ⚠️ P95 Delay: ${p95Delay}ms`);
197
286
  console.log(` 🚨 P99 Delay: ${p99Delay}ms`);
@@ -201,10 +290,7 @@ onEvent, onComplete) {
201
290
  onComplete(fullMessage, toolCalls);
202
291
  }
203
292
  catch (error) {
204
- onEvent({
205
- type: "error",
206
- error: error instanceof Error ? error.message : "OpenAI streaming failed",
207
- });
293
+ // Don't emit error event here - let the client handle it to avoid duplicates
208
294
  throw error;
209
295
  }
210
296
  }
@@ -218,9 +304,22 @@ onEvent, onComplete) {
218
304
  // Performance metrics
219
305
  const startTime = Date.now();
220
306
  let firstTokenTime = 0;
307
+ let firstMeaningfulContentTime = 0;
221
308
  let tokenCount = 0;
309
+ let toolArgumentTokens = 0;
222
310
  let lastEventTime = 0;
223
311
  const interTokenDelays = [];
312
+ // Tool calling metrics
313
+ const toolMetrics = {
314
+ totalTools: 0,
315
+ successfulTools: 0,
316
+ failedTools: 0,
317
+ toolTimes: [],
318
+ currentToolStart: 0,
319
+ roundStartTime: startTime,
320
+ rounds: [],
321
+ currentRound: 1
322
+ };
224
323
  try {
225
324
  const modelName = getModelName(specification);
226
325
  if (!modelName) {
@@ -274,6 +373,22 @@ onEvent, onComplete) {
274
373
  arguments: "",
275
374
  };
276
375
  toolCalls.push(toolCall);
376
+ // Track tool metrics
377
+ toolMetrics.totalTools++;
378
+ toolMetrics.currentToolStart = Date.now();
379
+ toolMetrics.toolTimes.push({
380
+ name: toolCall.name,
381
+ startTime: toolMetrics.currentToolStart,
382
+ argumentBuildTime: 0,
383
+ totalTime: 0
384
+ });
385
+ // Track TTFT for first tool if no content yet
386
+ if (firstTokenTime === 0) {
387
+ firstTokenTime = Date.now() - startTime;
388
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
389
+ console.log(`\n⚡ [Anthropic] Time to First Token (Tool Call): ${firstTokenTime}ms`);
390
+ }
391
+ }
277
392
  onEvent({
278
393
  type: "tool_call_start",
279
394
  toolCall: {
@@ -288,13 +403,20 @@ onEvent, onComplete) {
288
403
  fullMessage += chunk.delta.text;
289
404
  tokenCount++;
290
405
  const currentTime = Date.now();
291
- // Track TTFT
406
+ // Track TTFT (first token regardless of type)
292
407
  if (firstTokenTime === 0) {
293
408
  firstTokenTime = currentTime - startTime;
294
409
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
295
410
  console.log(`\n⚡ [Anthropic] Time to First Token (TTFT): ${firstTokenTime}ms`);
296
411
  }
297
412
  }
413
+ // Track first meaningful content (excludes tool calls)
414
+ if (firstMeaningfulContentTime === 0 && chunk.delta.text.trim()) {
415
+ firstMeaningfulContentTime = currentTime - startTime;
416
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
417
+ console.log(`\n🎯 [Anthropic] Time to First Meaningful Content: ${firstMeaningfulContentTime}ms`);
418
+ }
419
+ }
298
420
  // Track inter-token delays
299
421
  if (lastEventTime > 0) {
300
422
  const delay = currentTime - lastEventTime;
@@ -314,6 +436,8 @@ onEvent, onComplete) {
314
436
  const currentTool = toolCalls[toolCalls.length - 1];
315
437
  if (currentTool) {
316
438
  currentTool.arguments += chunk.delta.partial_json;
439
+ // Count tool argument tokens (rough estimate: ~4 chars per token)
440
+ toolArgumentTokens += Math.ceil(chunk.delta.partial_json.length / 4);
317
441
  // Debug logging for partial JSON accumulation
318
442
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
319
443
  console.log(`[Anthropic] Tool ${currentTool.name} - Partial JSON chunk: "${chunk.delta.partial_json}"`);
@@ -332,6 +456,27 @@ onEvent, onComplete) {
332
456
  // Tool call complete
333
457
  const currentTool = toolCalls[toolCalls.length - 1];
334
458
  if (currentTool) {
459
+ const currentTime = Date.now();
460
+ // Update tool metrics
461
+ const toolIndex = toolCalls.length - 1;
462
+ if (toolIndex < toolMetrics.toolTimes.length) {
463
+ const toolTime = toolMetrics.toolTimes[toolIndex];
464
+ toolTime.argumentBuildTime = currentTime - toolTime.startTime;
465
+ toolTime.totalTime = toolTime.argumentBuildTime;
466
+ toolTime.name = currentTool.name;
467
+ }
468
+ // Track tool success/failure
469
+ try {
470
+ JSON.parse(currentTool.arguments);
471
+ toolMetrics.successfulTools++;
472
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
473
+ console.log(`[Anthropic] ✅ Valid JSON for ${currentTool.name}`);
474
+ }
475
+ }
476
+ catch (e) {
477
+ toolMetrics.failedTools++;
478
+ console.error(`[Anthropic] ❌ Invalid JSON for ${currentTool.name}: ${e}`);
479
+ }
335
480
  // Log the final JSON for debugging
336
481
  if (process.env.DEBUG_GRAPHLIT_STREAMING ||
337
482
  !isValidJSON(currentTool.arguments)) {
@@ -343,16 +488,6 @@ onEvent, onComplete) {
343
488
  currentTool.arguments.length > 100) {
344
489
  console.warn(`[Anthropic] WARNING: JSON may be truncated - doesn't end with '}': ...${lastChars}`);
345
490
  }
346
- // Validate JSON
347
- try {
348
- JSON.parse(currentTool.arguments);
349
- if (process.env.DEBUG_GRAPHLIT_STREAMING) {
350
- console.log(`[Anthropic] ✅ Valid JSON for ${currentTool.name}`);
351
- }
352
- }
353
- catch (e) {
354
- console.error(`[Anthropic] ❌ Invalid JSON for ${currentTool.name}: ${e}`);
355
- }
356
491
  }
357
492
  onEvent({
358
493
  type: "tool_call_complete",
@@ -402,22 +537,60 @@ onEvent, onComplete) {
402
537
  console.log(`[Anthropic] Filtered out ${toolCalls.length - validToolCalls.length} incomplete tool calls`);
403
538
  console.log(`[Anthropic] Successfully processed ${validToolCalls.length} valid tool calls`);
404
539
  }
405
- // Calculate final metrics
540
+ // Calculate final metrics including tool calling insights
406
541
  const totalTime = Date.now() - startTime;
407
- const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
542
+ const totalTokens = tokenCount + toolArgumentTokens;
543
+ const tokensPerSecond = totalTokens > 0 ? totalTokens / (totalTime / 1000) : 0;
544
+ // Finalize round metrics
545
+ if (validToolCalls.length > 0) {
546
+ const roundEndTime = Date.now();
547
+ const totalToolTime = toolMetrics.toolTimes.reduce((sum, tool) => sum + tool.totalTime, 0);
548
+ const llmTime = totalTime - totalToolTime;
549
+ toolMetrics.rounds.push({
550
+ roundNumber: toolMetrics.currentRound,
551
+ llmTime: llmTime,
552
+ toolTime: totalToolTime,
553
+ toolCount: validToolCalls.length
554
+ });
555
+ }
408
556
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
409
557
  console.log("\n📊 [Anthropic] Performance Metrics:");
410
558
  console.log(` ⏱️ Total Time: ${totalTime}ms`);
411
559
  console.log(` ⚡ Time to First Token (TTFT): ${firstTokenTime}ms`);
412
- console.log(` 📈 Tokens Generated: ${tokenCount}`);
560
+ if (firstMeaningfulContentTime > 0) {
561
+ console.log(` 🎯 Time to First Meaningful Content: ${firstMeaningfulContentTime}ms`);
562
+ }
563
+ console.log(` 📈 Content Tokens: ${tokenCount}`);
564
+ console.log(` 🔧 Tool Argument Tokens: ${toolArgumentTokens}`);
565
+ console.log(` 📊 Total Tokens: ${totalTokens}`);
413
566
  console.log(` 💨 Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
567
+ // Tool calling metrics
568
+ if (validToolCalls.length > 0) {
569
+ console.log(`\n🔧 [Anthropic] Tool Calling Metrics:`);
570
+ console.log(` 🛠️ Total Tools Called: ${toolMetrics.totalTools}`);
571
+ console.log(` ✅ Successful Tools: ${toolMetrics.successfulTools}`);
572
+ console.log(` ❌ Failed Tools: ${toolMetrics.failedTools}`);
573
+ console.log(` 📊 Success Rate: ${((toolMetrics.successfulTools / toolMetrics.totalTools) * 100).toFixed(1)}%`);
574
+ // Tool timing details
575
+ toolMetrics.toolTimes.forEach((tool, idx) => {
576
+ console.log(` 🔨 Tool ${idx + 1} (${tool.name}): ${tool.argumentBuildTime}ms`);
577
+ });
578
+ const avgToolTime = toolMetrics.toolTimes.reduce((sum, tool) => sum + tool.totalTime, 0) / toolMetrics.toolTimes.length;
579
+ console.log(` ⏱️ Average Tool Time: ${avgToolTime.toFixed(2)}ms`);
580
+ // Round metrics
581
+ toolMetrics.rounds.forEach(round => {
582
+ const efficiency = round.toolCount > 0 ? (round.llmTime / (round.llmTime + round.toolTime) * 100).toFixed(1) : 100;
583
+ console.log(` 🔄 Round ${round.roundNumber}: LLM=${round.llmTime}ms, Tools=${round.toolTime}ms (${round.toolCount} tools), Efficiency=${efficiency}%`);
584
+ });
585
+ }
414
586
  if (interTokenDelays.length > 0) {
415
587
  const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
416
588
  const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
417
589
  const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
418
590
  const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
419
591
  const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
420
- console.log(` Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
592
+ console.log(`\n[Anthropic] Inter-Token Timing:`);
593
+ console.log(` 📊 Average Delay: ${avgDelay.toFixed(2)}ms`);
421
594
  console.log(` 📊 P50 Delay: ${p50Delay}ms`);
422
595
  console.log(` ⚠️ P95 Delay: ${p95Delay}ms`);
423
596
  console.log(` 🚨 P99 Delay: ${p99Delay}ms`);
@@ -427,10 +600,7 @@ onEvent, onComplete) {
427
600
  onComplete(fullMessage, validToolCalls);
428
601
  }
429
602
  catch (error) {
430
- onEvent({
431
- type: "error",
432
- error: error instanceof Error ? error.message : "Anthropic streaming failed",
433
- });
603
+ // Don't emit error event here - let the client handle it to avoid duplicates
434
604
  throw error;
435
605
  }
436
606
  }
@@ -444,9 +614,22 @@ onEvent, onComplete) {
444
614
  // Performance metrics
445
615
  const startTime = Date.now();
446
616
  let firstTokenTime = 0;
617
+ let firstMeaningfulContentTime = 0;
447
618
  let tokenCount = 0;
619
+ let toolArgumentTokens = 0;
448
620
  let lastEventTime = 0;
449
621
  const interTokenDelays = [];
622
+ // Tool calling metrics
623
+ const toolMetrics = {
624
+ totalTools: 0,
625
+ successfulTools: 0,
626
+ failedTools: 0,
627
+ toolTimes: [],
628
+ currentToolStart: 0,
629
+ roundStartTime: startTime,
630
+ rounds: [],
631
+ currentRound: 1
632
+ };
450
633
  try {
451
634
  const modelName = getModelName(specification);
452
635
  if (!modelName) {
@@ -520,6 +703,22 @@ onEvent, onComplete) {
520
703
  }
521
704
  if (text) {
522
705
  fullMessage += text;
706
+ tokenCount++;
707
+ const currentTime = Date.now();
708
+ // Track TTFT (first token regardless of type)
709
+ if (firstTokenTime === 0) {
710
+ firstTokenTime = currentTime - startTime;
711
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
712
+ console.log(`\n⚡ [Google] Time to First Token (TTFT): ${firstTokenTime}ms`);
713
+ }
714
+ }
715
+ // Track first meaningful content
716
+ if (firstMeaningfulContentTime === 0 && text.trim()) {
717
+ firstMeaningfulContentTime = currentTime - startTime;
718
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
719
+ console.log(`\n🎯 [Google] Time to First Meaningful Content: ${firstMeaningfulContentTime}ms`);
720
+ }
721
+ }
523
722
  onEvent({
524
723
  type: "token",
525
724
  token: text,
@@ -542,6 +741,23 @@ onEvent, onComplete) {
542
741
  arguments: JSON.stringify(part.functionCall.args || {}),
543
742
  };
544
743
  toolCalls.push(toolCall);
744
+ // Track tool metrics
745
+ toolMetrics.totalTools++;
746
+ const argumentString = JSON.stringify(part.functionCall.args || {});
747
+ toolArgumentTokens += Math.ceil(argumentString.length / 4);
748
+ toolMetrics.toolTimes.push({
749
+ name: part.functionCall.name,
750
+ startTime: Date.now(),
751
+ argumentBuildTime: 0, // Google returns complete args at once
752
+ totalTime: 0
753
+ });
754
+ // Track TTFT for first tool if no content yet
755
+ if (firstTokenTime === 0) {
756
+ firstTokenTime = Date.now() - startTime;
757
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
758
+ console.log(`\n⚡ [Google] Time to First Token (Tool Call): ${firstTokenTime}ms`);
759
+ }
760
+ }
545
761
  // Emit tool call events
546
762
  onEvent({
547
763
  type: "tool_call_start",
@@ -555,18 +771,28 @@ onEvent, onComplete) {
555
771
  toolCallId: toolCall.id,
556
772
  argumentDelta: toolCall.arguments,
557
773
  });
558
- // Log completion and validate JSON
774
+ // Update tool metrics and validate JSON
775
+ const toolIndex = toolCalls.length - 1;
776
+ if (toolIndex < toolMetrics.toolTimes.length) {
777
+ const toolTime = toolMetrics.toolTimes[toolIndex];
778
+ toolTime.totalTime = Date.now() - toolTime.startTime;
779
+ toolTime.argumentBuildTime = toolTime.totalTime; // Google returns complete args
780
+ }
781
+ try {
782
+ JSON.parse(toolCall.arguments);
783
+ toolMetrics.successfulTools++;
784
+ if (process.env.DEBUG_GRAPHLIT_STREAMING) {
785
+ console.log(`[Google] ✅ Valid JSON for ${toolCall.name}`);
786
+ }
787
+ }
788
+ catch (e) {
789
+ toolMetrics.failedTools++;
790
+ console.error(`[Google] ❌ Invalid JSON for ${toolCall.name}: ${e}`);
791
+ }
792
+ // Log completion
559
793
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
560
794
  console.log(`[Google] Tool ${toolCall.name} complete with arguments (${toolCall.arguments.length} chars):`);
561
795
  console.log(toolCall.arguments);
562
- // Validate JSON
563
- try {
564
- JSON.parse(toolCall.arguments);
565
- console.log(`[Google] ✅ Valid JSON for ${toolCall.name}`);
566
- }
567
- catch (e) {
568
- console.error(`[Google] ❌ Invalid JSON for ${toolCall.name}: ${e}`);
569
- }
570
796
  }
571
797
  onEvent({
572
798
  type: "tool_call_complete",
@@ -653,22 +879,60 @@ onEvent, onComplete) {
653
879
  if (process.env.DEBUG_GRAPHLIT_STREAMING && toolCalls.length > 0) {
654
880
  console.log(`[Google] Successfully processed ${toolCalls.length} tool calls`);
655
881
  }
656
- // Calculate final metrics
882
+ // Calculate final metrics including tool calling insights
657
883
  const totalTime = Date.now() - startTime;
658
- const tokensPerSecond = tokenCount > 0 ? tokenCount / (totalTime / 1000) : 0;
884
+ const totalTokens = tokenCount + toolArgumentTokens;
885
+ const tokensPerSecond = totalTokens > 0 ? totalTokens / (totalTime / 1000) : 0;
886
+ // Finalize round metrics
887
+ if (toolCalls.length > 0) {
888
+ const roundEndTime = Date.now();
889
+ const totalToolTime = toolMetrics.toolTimes.reduce((sum, tool) => sum + tool.totalTime, 0);
890
+ const llmTime = totalTime - totalToolTime;
891
+ toolMetrics.rounds.push({
892
+ roundNumber: toolMetrics.currentRound,
893
+ llmTime: llmTime,
894
+ toolTime: totalToolTime,
895
+ toolCount: toolCalls.length
896
+ });
897
+ }
659
898
  if (process.env.DEBUG_GRAPHLIT_STREAMING) {
660
899
  console.log("\n📊 [Google] Performance Metrics:");
661
900
  console.log(` ⏱️ Total Time: ${totalTime}ms`);
662
901
  console.log(` ⚡ Time to First Token (TTFT): ${firstTokenTime}ms`);
663
- console.log(` 📈 Tokens Generated: ${tokenCount}`);
902
+ if (firstMeaningfulContentTime > 0) {
903
+ console.log(` 🎯 Time to First Meaningful Content: ${firstMeaningfulContentTime}ms`);
904
+ }
905
+ console.log(` 📈 Content Tokens: ${tokenCount}`);
906
+ console.log(` 🔧 Tool Argument Tokens: ${toolArgumentTokens}`);
907
+ console.log(` 📊 Total Tokens: ${totalTokens}`);
664
908
  console.log(` 💨 Tokens Per Second (TPS): ${tokensPerSecond.toFixed(2)}`);
909
+ // Tool calling metrics
910
+ if (toolCalls.length > 0) {
911
+ console.log(`\n🔧 [Google] Tool Calling Metrics:`);
912
+ console.log(` 🛠️ Total Tools Called: ${toolMetrics.totalTools}`);
913
+ console.log(` ✅ Successful Tools: ${toolMetrics.successfulTools}`);
914
+ console.log(` ❌ Failed Tools: ${toolMetrics.failedTools}`);
915
+ console.log(` 📊 Success Rate: ${((toolMetrics.successfulTools / toolMetrics.totalTools) * 100).toFixed(1)}%`);
916
+ // Tool timing details
917
+ toolMetrics.toolTimes.forEach((tool, idx) => {
918
+ console.log(` 🔨 Tool ${idx + 1} (${tool.name}): ${tool.argumentBuildTime}ms`);
919
+ });
920
+ const avgToolTime = toolMetrics.toolTimes.reduce((sum, tool) => sum + tool.totalTime, 0) / toolMetrics.toolTimes.length;
921
+ console.log(` ⏱️ Average Tool Time: ${avgToolTime.toFixed(2)}ms`);
922
+ // Round metrics
923
+ toolMetrics.rounds.forEach(round => {
924
+ const efficiency = round.toolCount > 0 ? (round.llmTime / (round.llmTime + round.toolTime) * 100).toFixed(1) : 100;
925
+ console.log(` 🔄 Round ${round.roundNumber}: LLM=${round.llmTime}ms, Tools=${round.toolTime}ms (${round.toolCount} tools), Efficiency=${efficiency}%`);
926
+ });
927
+ }
665
928
  if (interTokenDelays.length > 0) {
666
929
  const avgDelay = interTokenDelays.reduce((a, b) => a + b, 0) / interTokenDelays.length;
667
930
  const sortedDelays = [...interTokenDelays].sort((a, b) => a - b);
668
931
  const p50Delay = sortedDelays[Math.floor(sortedDelays.length * 0.5)];
669
932
  const p95Delay = sortedDelays[Math.floor(sortedDelays.length * 0.95)];
670
933
  const p99Delay = sortedDelays[Math.floor(sortedDelays.length * 0.99)];
671
- console.log(` Average Inter-Token Delay: ${avgDelay.toFixed(2)}ms`);
934
+ console.log(`\n[Google] Inter-Token Timing:`);
935
+ console.log(` 📊 Average Delay: ${avgDelay.toFixed(2)}ms`);
672
936
  console.log(` 📊 P50 Delay: ${p50Delay}ms`);
673
937
  console.log(` ⚠️ P95 Delay: ${p95Delay}ms`);
674
938
  console.log(` 🚨 P99 Delay: ${p99Delay}ms`);
@@ -678,10 +942,7 @@ onEvent, onComplete) {
678
942
  onComplete(fullMessage, toolCalls);
679
943
  }
680
944
  catch (error) {
681
- onEvent({
682
- type: "error",
683
- error: error instanceof Error ? error.message : "Google streaming failed",
684
- });
945
+ // Don't emit error event here - let the client handle it to avoid duplicates
685
946
  throw error;
686
947
  }
687
948
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphlit-client",
3
- "version": "1.0.20250612001",
3
+ "version": "1.0.20250612003",
4
4
  "description": "Graphlit API Client for TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/client.js",