@respan/cli 0.6.0 → 0.6.2

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.
@@ -607,7 +607,7 @@ function createSpans(sessionId, turnNum, userMsg, assistantMsgs, toolResults, co
607
607
  provider_id: "",
608
608
  span_path: "",
609
609
  input: promptMessages.length ? JSON.stringify(promptMessages) : "",
610
- output: completionMessage ? JSON.stringify(completionMessage) : "",
610
+ output: finalOutput,
611
611
  timestamp: timestampStr,
612
612
  start_time: startTimeStr,
613
613
  metadata,
@@ -627,7 +627,7 @@ function createSpans(sessionId, turnNum, userMsg, assistantMsgs, toolResults, co
627
627
  provider_id: "anthropic",
628
628
  metadata: {},
629
629
  input: promptMessages.length ? JSON.stringify(promptMessages) : "",
630
- output: completionMessage ? JSON.stringify(completionMessage) : "",
630
+ output: finalOutput,
631
631
  prompt_messages: promptMessages,
632
632
  completion_message: completionMessage,
633
633
  timestamp: genEnd,
@@ -256,7 +256,7 @@ function createSpans(sessionId, turnNum, userMsg, assistantMsgs, toolResults, co
256
256
  provider_id: '',
257
257
  span_path: '',
258
258
  input: promptMessages.length ? JSON.stringify(promptMessages) : '',
259
- output: completionMessage ? JSON.stringify(completionMessage) : '',
259
+ output: finalOutput,
260
260
  timestamp: timestampStr,
261
261
  start_time: startTimeStr,
262
262
  metadata,
@@ -277,7 +277,7 @@ function createSpans(sessionId, turnNum, userMsg, assistantMsgs, toolResults, co
277
277
  provider_id: 'anthropic',
278
278
  metadata: {},
279
279
  input: promptMessages.length ? JSON.stringify(promptMessages) : '',
280
- output: completionMessage ? JSON.stringify(completionMessage) : '',
280
+ output: finalOutput,
281
281
  prompt_messages: promptMessages,
282
282
  completion_message: completionMessage,
283
283
  timestamp: genEnd,
@@ -475,6 +475,7 @@ function extractTurns(events) {
475
475
  tool_calls: [],
476
476
  tool_outputs: [],
477
477
  reasoning: false,
478
+ reasoning_text: "",
478
479
  token_usage: {}
479
480
  };
480
481
  } else if (msgType === "task_complete" && current) {
@@ -512,13 +513,23 @@ function extractTurns(events) {
512
513
  timestamp
513
514
  });
514
515
  } else if (itemType === "reasoning") {
515
- if (current) current.reasoning = true;
516
+ if (current) {
517
+ current.reasoning = true;
518
+ const summary = String(payload.summary ?? payload.text ?? "");
519
+ if (summary) current.reasoning_text += (current.reasoning_text ? "\n" : "") + summary;
520
+ }
516
521
  } else if (itemType === "web_search_call") {
517
522
  const action = payload.action ?? {};
523
+ const syntheticId = `web_search_${timestamp}`;
518
524
  current.tool_calls.push({
519
525
  name: "web_search",
520
526
  arguments: JSON.stringify({ query: action.query ?? "" }),
521
- call_id: `web_search_${timestamp}`,
527
+ call_id: syntheticId,
528
+ timestamp
529
+ });
530
+ current.tool_outputs.push({
531
+ call_id: syntheticId,
532
+ output: `Search: ${action.query ?? ""}`,
522
533
  timestamp
523
534
  });
524
535
  }
@@ -570,7 +581,7 @@ function createSpans(sessionId, turnNum, turn, config) {
570
581
  provider_id: "",
571
582
  span_path: "",
572
583
  input: promptMessages.length ? JSON.stringify(promptMessages) : "",
573
- output: completionMessage ? JSON.stringify(completionMessage) : "",
584
+ output: turn.assistant_message,
574
585
  timestamp: endTimeStr,
575
586
  start_time: startTimeStr,
576
587
  metadata,
@@ -587,7 +598,7 @@ function createSpans(sessionId, turnNum, turn, config) {
587
598
  provider_id: "openai",
588
599
  metadata: {},
589
600
  input: promptMessages.length ? JSON.stringify(promptMessages) : "",
590
- output: completionMessage ? JSON.stringify(completionMessage) : "",
601
+ output: turn.assistant_message,
591
602
  prompt_messages: promptMessages,
592
603
  completion_message: completionMessage,
593
604
  timestamp: endTimeStr,
@@ -607,7 +618,7 @@ function createSpans(sessionId, turnNum, turn, config) {
607
618
  provider_id: "",
608
619
  metadata: reasoningTokens > 0 ? { reasoning_tokens: reasoningTokens } : {},
609
620
  input: "",
610
- output: reasoningTokens > 0 ? `[Reasoning: ${reasoningTokens} tokens]` : "[Reasoning]",
621
+ output: turn.reasoning_text || (reasoningTokens > 0 ? `[Reasoning: ${reasoningTokens} tokens]` : "[Reasoning]"),
611
622
  timestamp: endTimeStr,
612
623
  start_time: startTimeStr
613
624
  });
@@ -99,6 +99,7 @@ function extractTurns(events) {
99
99
  tool_calls: [],
100
100
  tool_outputs: [],
101
101
  reasoning: false,
102
+ reasoning_text: '',
102
103
  token_usage: {},
103
104
  };
104
105
  }
@@ -147,15 +148,26 @@ function extractTurns(events) {
147
148
  });
148
149
  }
149
150
  else if (itemType === 'reasoning') {
150
- if (current)
151
+ if (current) {
151
152
  current.reasoning = true;
153
+ const summary = String(payload.summary ?? payload.text ?? '');
154
+ if (summary)
155
+ current.reasoning_text += (current.reasoning_text ? '\n' : '') + summary;
156
+ }
152
157
  }
153
158
  else if (itemType === 'web_search_call') {
154
159
  const action = (payload.action ?? {});
160
+ const syntheticId = `web_search_${timestamp}`;
155
161
  current.tool_calls.push({
156
162
  name: 'web_search',
157
163
  arguments: JSON.stringify({ query: action.query ?? '' }),
158
- call_id: `web_search_${timestamp}`,
164
+ call_id: syntheticId,
165
+ timestamp,
166
+ });
167
+ // Web search has no separate output event; record query as output
168
+ current.tool_outputs.push({
169
+ call_id: syntheticId,
170
+ output: `Search: ${action.query ?? ''}`,
159
171
  timestamp,
160
172
  });
161
173
  }
@@ -218,7 +230,7 @@ function createSpans(sessionId, turnNum, turn, config) {
218
230
  provider_id: '',
219
231
  span_path: '',
220
232
  input: promptMessages.length ? JSON.stringify(promptMessages) : '',
221
- output: completionMessage ? JSON.stringify(completionMessage) : '',
233
+ output: turn.assistant_message,
222
234
  timestamp: endTimeStr,
223
235
  start_time: startTimeStr,
224
236
  metadata,
@@ -236,7 +248,7 @@ function createSpans(sessionId, turnNum, turn, config) {
236
248
  provider_id: 'openai',
237
249
  metadata: {},
238
250
  input: promptMessages.length ? JSON.stringify(promptMessages) : '',
239
- output: completionMessage ? JSON.stringify(completionMessage) : '',
251
+ output: turn.assistant_message,
240
252
  prompt_messages: promptMessages,
241
253
  completion_message: completionMessage,
242
254
  timestamp: endTimeStr,
@@ -257,7 +269,7 @@ function createSpans(sessionId, turnNum, turn, config) {
257
269
  provider_id: '',
258
270
  metadata: reasoningTokens > 0 ? { reasoning_tokens: reasoningTokens } : {},
259
271
  input: '',
260
- output: reasoningTokens > 0 ? `[Reasoning: ${reasoningTokens} tokens]` : '[Reasoning]',
272
+ output: turn.reasoning_text || (reasoningTokens > 0 ? `[Reasoning: ${reasoningTokens} tokens]` : '[Reasoning]'),
261
273
  timestamp: endTimeStr,
262
274
  start_time: startTimeStr,
263
275
  });
@@ -417,10 +417,20 @@ function clearStreamState(sessionId) {
417
417
  function extractMessages(hookData) {
418
418
  const llmReq = hookData.llm_request ?? {};
419
419
  const messages = llmReq.messages ?? [];
420
- return messages.map((msg) => ({
421
- role: String(msg.role ?? "user") === "model" ? "assistant" : String(msg.role ?? "user"),
422
- content: truncate(String(msg.content ?? ""), MAX_CHARS)
423
- }));
420
+ for (let i = messages.length - 1; i >= 0; i--) {
421
+ const role = String(messages[i].role ?? "user");
422
+ if (role === "user") {
423
+ return [{ role: "user", content: truncate(String(messages[i].content ?? ""), MAX_CHARS) }];
424
+ }
425
+ }
426
+ if (messages.length > 0) {
427
+ const last = messages[messages.length - 1];
428
+ return [{
429
+ role: String(last.role ?? "user") === "model" ? "assistant" : String(last.role ?? "user"),
430
+ content: truncate(String(last.content ?? ""), MAX_CHARS)
431
+ }];
432
+ }
433
+ return [];
424
434
  }
425
435
  function detectModel(hookData) {
426
436
  const override = process.env.RESPAN_GEMINI_MODEL;
@@ -443,8 +453,9 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
443
453
  spanName: "gemini-cli"
444
454
  });
445
455
  const safeId = sessionId.replace(/[/\\]/g, "_").slice(0, 50);
446
- const traceUniqueId = `gcli_${safeId}`;
447
- const rootSpanId = `gcli_${safeId}_root`;
456
+ const turnTs = beginTime.replace(/[^0-9]/g, "").slice(0, 14);
457
+ const traceUniqueId = `gcli_${safeId}_${turnTs}`;
458
+ const rootSpanId = `gcli_${safeId}_${turnTs}_root`;
448
459
  const threadId = `gcli_${sessionId}`;
449
460
  const llmReq = hookData.llm_request ?? {};
450
461
  const reqConfig = llmReq.config ?? {};
@@ -463,7 +474,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
463
474
  provider_id: "",
464
475
  span_path: "",
465
476
  input: promptMessages.length ? JSON.stringify(promptMessages) : "",
466
- output: JSON.stringify(completionMessage),
477
+ output: truncate(outputText, MAX_CHARS),
467
478
  timestamp: endTime,
468
479
  start_time: beginTime,
469
480
  metadata,
@@ -471,7 +482,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
471
482
  });
472
483
  const genSpan = {
473
484
  trace_unique_id: traceUniqueId,
474
- span_unique_id: `gcli_${safeId}_gen`,
485
+ span_unique_id: `gcli_${safeId}_${turnTs}_gen`,
475
486
  span_parent_id: rootSpanId,
476
487
  span_name: "gemini.chat",
477
488
  span_workflow_name: workflowName,
@@ -480,7 +491,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
480
491
  provider_id: "google",
481
492
  metadata: {},
482
493
  input: promptMessages.length ? JSON.stringify(promptMessages) : "",
483
- output: JSON.stringify(completionMessage),
494
+ output: truncate(outputText, MAX_CHARS),
484
495
  timestamp: endTime,
485
496
  start_time: beginTime,
486
497
  prompt_tokens: tokens.prompt_tokens,
@@ -494,7 +505,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
494
505
  if (thoughtsTokens > 0) {
495
506
  spans.push({
496
507
  trace_unique_id: traceUniqueId,
497
- span_unique_id: `gcli_${safeId}_reasoning`,
508
+ span_unique_id: `gcli_${safeId}_${turnTs}_reasoning`,
498
509
  span_parent_id: rootSpanId,
499
510
  span_name: "Reasoning",
500
511
  span_workflow_name: workflowName,
@@ -522,7 +533,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
522
533
  const toolLat = latencySeconds(toolStart, toolEnd);
523
534
  spans.push({
524
535
  trace_unique_id: traceUniqueId,
525
- span_unique_id: `gcli_${safeId}_tool_${i + 1}`,
536
+ span_unique_id: `gcli_${safeId}_${turnTs}_tool_${i + 1}`,
526
537
  span_parent_id: rootSpanId,
527
538
  span_name: `Tool: ${displayName}`,
528
539
  span_workflow_name: workflowName,
@@ -710,6 +721,17 @@ function processChunk(hookData) {
710
721
  }
711
722
  }
712
723
  }
724
+ const grounding = candidates[0].groundingMetadata ?? llmResp.groundingMetadata;
725
+ if (grounding && typeof grounding === "object") {
726
+ const queries = grounding.webSearchQueries ?? grounding.searchQueries ?? [];
727
+ if (queries.length > 0) {
728
+ chunkToolDetails.push({
729
+ name: "google_web_search",
730
+ args: { queries },
731
+ output: truncate(queries.join(", "), MAX_CHARS)
732
+ });
733
+ }
734
+ }
713
735
  }
714
736
  const messages = hookData.llm_request?.messages ?? [];
715
737
  const currentMsgCount = messages.length;
@@ -737,6 +759,14 @@ function processChunk(hookData) {
737
759
  state.accumulated_text += chunkText;
738
760
  state.last_tokens = completionTokens || state.last_tokens;
739
761
  if (thoughtsTokens > 0) state.thoughts_tokens = thoughtsTokens;
762
+ }
763
+ const groundingDetails = chunkToolDetails.filter((d) => d.name === "google_web_search");
764
+ if (groundingDetails.length) {
765
+ state.tool_details = [...state.tool_details ?? [], ...groundingDetails];
766
+ state.tool_turns = (state.tool_turns ?? 0) + groundingDetails.length;
767
+ debug(`Grounding search detected: ${groundingDetails.length} queries`);
768
+ }
769
+ if (chunkText || groundingDetails.length) {
740
770
  saveStreamState(sessionId, state);
741
771
  debug(`Accumulated chunk: +${chunkText.length} chars, total=${state.accumulated_text.length}`);
742
772
  }
@@ -118,10 +118,21 @@ function clearStreamState(sessionId) {
118
118
  function extractMessages(hookData) {
119
119
  const llmReq = (hookData.llm_request ?? {});
120
120
  const messages = (llmReq.messages ?? []);
121
- return messages.map((msg) => ({
122
- role: String(msg.role ?? 'user') === 'model' ? 'assistant' : String(msg.role ?? 'user'),
123
- content: truncate(String(msg.content ?? ''), MAX_CHARS),
124
- }));
121
+ // Only include the last user message, not the full conversation history
122
+ for (let i = messages.length - 1; i >= 0; i--) {
123
+ const role = String(messages[i].role ?? 'user');
124
+ if (role === 'user') {
125
+ return [{ role: 'user', content: truncate(String(messages[i].content ?? ''), MAX_CHARS) }];
126
+ }
127
+ }
128
+ if (messages.length > 0) {
129
+ const last = messages[messages.length - 1];
130
+ return [{
131
+ role: String(last.role ?? 'user') === 'model' ? 'assistant' : String(last.role ?? 'user'),
132
+ content: truncate(String(last.content ?? ''), MAX_CHARS),
133
+ }];
134
+ }
135
+ return [];
125
136
  }
126
137
  function detectModel(hookData) {
127
138
  const override = process.env.RESPAN_GEMINI_MODEL;
@@ -146,8 +157,10 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
146
157
  spanName: 'gemini-cli',
147
158
  });
148
159
  const safeId = sessionId.replace(/[/\\]/g, '_').slice(0, 50);
149
- const traceUniqueId = `gcli_${safeId}`;
150
- const rootSpanId = `gcli_${safeId}_root`;
160
+ // Use first chunk timestamp to differentiate turns within the same session
161
+ const turnTs = beginTime.replace(/[^0-9]/g, '').slice(0, 14);
162
+ const traceUniqueId = `gcli_${safeId}_${turnTs}`;
163
+ const rootSpanId = `gcli_${safeId}_${turnTs}_root`;
151
164
  const threadId = `gcli_${sessionId}`;
152
165
  // LLM config
153
166
  const llmReq = (hookData.llm_request ?? {});
@@ -171,7 +184,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
171
184
  provider_id: '',
172
185
  span_path: '',
173
186
  input: promptMessages.length ? JSON.stringify(promptMessages) : '',
174
- output: JSON.stringify(completionMessage),
187
+ output: truncate(outputText, MAX_CHARS),
175
188
  timestamp: endTime,
176
189
  start_time: beginTime,
177
190
  metadata,
@@ -180,7 +193,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
180
193
  // Generation child span
181
194
  const genSpan = {
182
195
  trace_unique_id: traceUniqueId,
183
- span_unique_id: `gcli_${safeId}_gen`,
196
+ span_unique_id: `gcli_${safeId}_${turnTs}_gen`,
184
197
  span_parent_id: rootSpanId,
185
198
  span_name: 'gemini.chat',
186
199
  span_workflow_name: workflowName,
@@ -189,7 +202,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
189
202
  provider_id: 'google',
190
203
  metadata: {},
191
204
  input: promptMessages.length ? JSON.stringify(promptMessages) : '',
192
- output: JSON.stringify(completionMessage),
205
+ output: truncate(outputText, MAX_CHARS),
193
206
  timestamp: endTime,
194
207
  start_time: beginTime,
195
208
  prompt_tokens: tokens.prompt_tokens,
@@ -206,7 +219,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
206
219
  if (thoughtsTokens > 0) {
207
220
  spans.push({
208
221
  trace_unique_id: traceUniqueId,
209
- span_unique_id: `gcli_${safeId}_reasoning`,
222
+ span_unique_id: `gcli_${safeId}_${turnTs}_reasoning`,
210
223
  span_parent_id: rootSpanId,
211
224
  span_name: 'Reasoning',
212
225
  span_workflow_name: workflowName,
@@ -237,7 +250,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
237
250
  const toolLat = latencySeconds(toolStart, toolEnd);
238
251
  spans.push({
239
252
  trace_unique_id: traceUniqueId,
240
- span_unique_id: `gcli_${safeId}_tool_${i + 1}`,
253
+ span_unique_id: `gcli_${safeId}_${turnTs}_tool_${i + 1}`,
241
254
  span_parent_id: rootSpanId,
242
255
  span_name: `Tool: ${displayName}`,
243
256
  span_workflow_name: workflowName,
@@ -436,6 +449,18 @@ function processChunk(hookData) {
436
449
  }
437
450
  }
438
451
  }
452
+ // Detect server-side grounding (google_web_search) from groundingMetadata
453
+ const grounding = (candidates[0].groundingMetadata ?? llmResp.groundingMetadata);
454
+ if (grounding && typeof grounding === 'object') {
455
+ const queries = (grounding.webSearchQueries ?? grounding.searchQueries ?? []);
456
+ if (queries.length > 0) {
457
+ chunkToolDetails.push({
458
+ name: 'google_web_search',
459
+ args: { queries },
460
+ output: truncate(queries.join(', '), MAX_CHARS),
461
+ });
462
+ }
463
+ }
439
464
  }
440
465
  const messages = (hookData.llm_request?.messages ?? []);
441
466
  const currentMsgCount = messages.length;
@@ -460,7 +485,7 @@ function processChunk(hookData) {
460
485
  }
461
486
  }
462
487
  state.msg_count = currentMsgCount;
463
- // Accumulate text
488
+ // Accumulate text and grounding tool details
464
489
  if (chunkText) {
465
490
  if (!state.first_chunk_time)
466
491
  state.first_chunk_time = nowISO();
@@ -468,6 +493,15 @@ function processChunk(hookData) {
468
493
  state.last_tokens = completionTokens || state.last_tokens;
469
494
  if (thoughtsTokens > 0)
470
495
  state.thoughts_tokens = thoughtsTokens;
496
+ }
497
+ // Save grounding tool details (these arrive with text, not as separate tool turns)
498
+ const groundingDetails = chunkToolDetails.filter(d => d.name === 'google_web_search');
499
+ if (groundingDetails.length) {
500
+ state.tool_details = [...(state.tool_details ?? []), ...groundingDetails];
501
+ state.tool_turns = (state.tool_turns ?? 0) + groundingDetails.length;
502
+ debug(`Grounding search detected: ${groundingDetails.length} queries`);
503
+ }
504
+ if (chunkText || groundingDetails.length) {
471
505
  saveStreamState(sessionId, state);
472
506
  debug(`Accumulated chunk: +${chunkText.length} chars, total=${state.accumulated_text.length}`);
473
507
  }