@respan/cli 0.7.0 → 0.7.1
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/hooks/codex-cli.cjs +70 -48
- package/dist/hooks/codex-cli.js +76 -57
- package/dist/hooks/gemini-cli.cjs +30 -54
- package/dist/hooks/gemini-cli.js +33 -61
- package/oclif.manifest.json +776 -776
- package/package.json +1 -1
package/dist/hooks/codex-cli.cjs
CHANGED
|
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
27
27
|
var os2 = __toESM(require("node:os"), 1);
|
|
28
28
|
var path2 = __toESM(require("node:path"), 1);
|
|
29
|
+
var import_node_child_process = require("node:child_process");
|
|
29
30
|
|
|
30
31
|
// src/hooks/shared.ts
|
|
31
32
|
var fs = __toESM(require("node:fs"), 1);
|
|
@@ -699,50 +700,18 @@ function findLatestSessionFile() {
|
|
|
699
700
|
return null;
|
|
700
701
|
}
|
|
701
702
|
}
|
|
702
|
-
async function
|
|
703
|
+
async function mainWorker() {
|
|
703
704
|
const scriptStart = Date.now();
|
|
704
|
-
debug("
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
let payload;
|
|
710
|
-
try {
|
|
711
|
-
payload = JSON.parse(process.argv[2]);
|
|
712
|
-
} catch (e) {
|
|
713
|
-
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
714
|
-
process.exit(0);
|
|
715
|
-
}
|
|
716
|
-
const eventType = String(payload.type ?? "");
|
|
717
|
-
if (eventType !== "agent-turn-complete") {
|
|
718
|
-
debug(`Ignoring event type: ${eventType}`);
|
|
719
|
-
process.exit(0);
|
|
720
|
-
}
|
|
721
|
-
let sessionId = String(payload["thread-id"] ?? "");
|
|
722
|
-
if (!sessionId) {
|
|
723
|
-
debug("No thread-id in notify payload");
|
|
724
|
-
process.exit(0);
|
|
725
|
-
}
|
|
726
|
-
debug(`Processing notify: type=${eventType}, session=${sessionId}`);
|
|
705
|
+
debug("Worker started");
|
|
706
|
+
const sessionId = process.env._RESPAN_CODEX_SESSION;
|
|
707
|
+
const sessionFile = process.env._RESPAN_CODEX_FILE;
|
|
708
|
+
const cwd = process.env._RESPAN_CODEX_CWD ?? "";
|
|
727
709
|
const creds = resolveCredentials();
|
|
728
710
|
if (!creds) {
|
|
729
|
-
log("ERROR", "No API key
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
let sessionFile = findSessionFile(sessionId);
|
|
733
|
-
if (!sessionFile) {
|
|
734
|
-
const latest = findLatestSessionFile();
|
|
735
|
-
if (latest) {
|
|
736
|
-
sessionId = latest.sessionId;
|
|
737
|
-
sessionFile = latest.sessionFile;
|
|
738
|
-
} else {
|
|
739
|
-
debug("No session file found");
|
|
740
|
-
process.exit(0);
|
|
741
|
-
}
|
|
711
|
+
log("ERROR", "No API key");
|
|
712
|
+
return;
|
|
742
713
|
}
|
|
743
|
-
const cwd = String(payload.cwd ?? "");
|
|
744
714
|
const config = cwd ? loadRespanConfig(path2.join(cwd, ".codex", "respan.json")) : null;
|
|
745
|
-
if (config) debug(`Loaded respan.json config from ${cwd}`);
|
|
746
715
|
const maxAttempts = 3;
|
|
747
716
|
let turns = 0;
|
|
748
717
|
try {
|
|
@@ -774,20 +743,73 @@ async function main() {
|
|
|
774
743
|
}
|
|
775
744
|
if (turns > 0) break;
|
|
776
745
|
if (attempt < maxAttempts - 1) {
|
|
777
|
-
|
|
778
|
-
debug(`No turns processed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay}ms...`);
|
|
779
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
746
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
780
747
|
}
|
|
781
748
|
}
|
|
782
749
|
const duration = (Date.now() - scriptStart) / 1e3;
|
|
783
750
|
log("INFO", `Processed ${turns} turns in ${duration.toFixed(1)}s`);
|
|
784
|
-
if (duration > 180) log("WARN", `Hook took ${duration.toFixed(1)}s (>3min)`);
|
|
785
751
|
} catch (e) {
|
|
786
752
|
log("ERROR", `Failed to process session: ${e}`);
|
|
787
|
-
if (DEBUG_MODE) debug(String(e.stack ?? e));
|
|
788
753
|
}
|
|
789
754
|
}
|
|
790
|
-
main()
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
755
|
+
function main() {
|
|
756
|
+
if (process.env._RESPAN_CODEX_WORKER === "1") {
|
|
757
|
+
mainWorker().catch((e) => log("ERROR", `Worker crashed: ${e}`));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
debug("Codex hook started");
|
|
761
|
+
if (process.argv.length < 3) {
|
|
762
|
+
debug("No argument provided");
|
|
763
|
+
process.exit(0);
|
|
764
|
+
}
|
|
765
|
+
let payload;
|
|
766
|
+
try {
|
|
767
|
+
payload = JSON.parse(process.argv[2]);
|
|
768
|
+
} catch (e) {
|
|
769
|
+
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
770
|
+
process.exit(0);
|
|
771
|
+
}
|
|
772
|
+
const eventType = String(payload.type ?? "");
|
|
773
|
+
if (eventType !== "agent-turn-complete") {
|
|
774
|
+
debug(`Ignoring event type: ${eventType}`);
|
|
775
|
+
process.exit(0);
|
|
776
|
+
}
|
|
777
|
+
let sessionId = String(payload["thread-id"] ?? "");
|
|
778
|
+
if (!sessionId) {
|
|
779
|
+
debug("No thread-id in notify payload");
|
|
780
|
+
process.exit(0);
|
|
781
|
+
}
|
|
782
|
+
let sessionFile = findSessionFile(sessionId);
|
|
783
|
+
if (!sessionFile) {
|
|
784
|
+
const latest = findLatestSessionFile();
|
|
785
|
+
if (latest) {
|
|
786
|
+
sessionId = latest.sessionId;
|
|
787
|
+
sessionFile = latest.sessionFile;
|
|
788
|
+
} else {
|
|
789
|
+
debug("No session file found");
|
|
790
|
+
process.exit(0);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const cwd = String(payload.cwd ?? "");
|
|
794
|
+
debug(`Forking worker for session: ${sessionId}`);
|
|
795
|
+
try {
|
|
796
|
+
const scriptPath = __filename || process.argv[1];
|
|
797
|
+
const child = (0, import_node_child_process.execFile)("node", [scriptPath], {
|
|
798
|
+
env: {
|
|
799
|
+
...process.env,
|
|
800
|
+
_RESPAN_CODEX_WORKER: "1",
|
|
801
|
+
_RESPAN_CODEX_SESSION: sessionId,
|
|
802
|
+
_RESPAN_CODEX_FILE: sessionFile,
|
|
803
|
+
_RESPAN_CODEX_CWD: cwd
|
|
804
|
+
},
|
|
805
|
+
stdio: "ignore",
|
|
806
|
+
detached: true
|
|
807
|
+
});
|
|
808
|
+
child.unref();
|
|
809
|
+
debug("Worker launched");
|
|
810
|
+
} catch (e) {
|
|
811
|
+
log("ERROR", `Failed to fork worker: ${e}`);
|
|
812
|
+
}
|
|
813
|
+
process.exit(0);
|
|
814
|
+
}
|
|
815
|
+
main();
|
package/dist/hooks/codex-cli.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import * as fs from 'node:fs';
|
|
17
17
|
import * as os from 'node:os';
|
|
18
18
|
import * as path from 'node:path';
|
|
19
|
+
import { execFile } from 'node:child_process';
|
|
19
20
|
import { initLogging, log, debug, resolveCredentials, loadRespanConfig, loadState, saveState, acquireLock, sendSpans, addDefaultsToAll, resolveSpanFields, buildMetadata, nowISO, latencySeconds, truncate, } from './shared.js';
|
|
20
21
|
// ── Config ────────────────────────────────────────────────────────
|
|
21
22
|
const STATE_DIR = path.join(os.homedir(), '.codex', 'state');
|
|
@@ -363,57 +364,18 @@ function findLatestSessionFile() {
|
|
|
363
364
|
}
|
|
364
365
|
}
|
|
365
366
|
// ── Main ──────────────────────────────────────────────────────────
|
|
366
|
-
async function
|
|
367
|
+
async function mainWorker() {
|
|
367
368
|
const scriptStart = Date.now();
|
|
368
|
-
debug('
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
process.exit(0);
|
|
373
|
-
}
|
|
374
|
-
let payload;
|
|
375
|
-
try {
|
|
376
|
-
payload = JSON.parse(process.argv[2]);
|
|
377
|
-
}
|
|
378
|
-
catch (e) {
|
|
379
|
-
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
380
|
-
process.exit(0);
|
|
381
|
-
}
|
|
382
|
-
const eventType = String(payload.type ?? '');
|
|
383
|
-
if (eventType !== 'agent-turn-complete') {
|
|
384
|
-
debug(`Ignoring event type: ${eventType}`);
|
|
385
|
-
process.exit(0);
|
|
386
|
-
}
|
|
387
|
-
let sessionId = String(payload['thread-id'] ?? '');
|
|
388
|
-
if (!sessionId) {
|
|
389
|
-
debug('No thread-id in notify payload');
|
|
390
|
-
process.exit(0);
|
|
391
|
-
}
|
|
392
|
-
debug(`Processing notify: type=${eventType}, session=${sessionId}`);
|
|
369
|
+
debug('Worker started');
|
|
370
|
+
const sessionId = process.env._RESPAN_CODEX_SESSION;
|
|
371
|
+
const sessionFile = process.env._RESPAN_CODEX_FILE;
|
|
372
|
+
const cwd = process.env._RESPAN_CODEX_CWD ?? '';
|
|
393
373
|
const creds = resolveCredentials();
|
|
394
374
|
if (!creds) {
|
|
395
|
-
log('ERROR', 'No API key
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
// Find session file
|
|
399
|
-
let sessionFile = findSessionFile(sessionId);
|
|
400
|
-
if (!sessionFile) {
|
|
401
|
-
const latest = findLatestSessionFile();
|
|
402
|
-
if (latest) {
|
|
403
|
-
sessionId = latest.sessionId;
|
|
404
|
-
sessionFile = latest.sessionFile;
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
debug('No session file found');
|
|
408
|
-
process.exit(0);
|
|
409
|
-
}
|
|
375
|
+
log('ERROR', 'No API key');
|
|
376
|
+
return;
|
|
410
377
|
}
|
|
411
|
-
// Load config
|
|
412
|
-
const cwd = String(payload.cwd ?? '');
|
|
413
378
|
const config = cwd ? loadRespanConfig(path.join(cwd, '.codex', 'respan.json')) : null;
|
|
414
|
-
if (config)
|
|
415
|
-
debug(`Loaded respan.json config from ${cwd}`);
|
|
416
|
-
// Process with retry
|
|
417
379
|
const maxAttempts = 3;
|
|
418
380
|
let turns = 0;
|
|
419
381
|
try {
|
|
@@ -447,23 +409,80 @@ async function main() {
|
|
|
447
409
|
if (turns > 0)
|
|
448
410
|
break;
|
|
449
411
|
if (attempt < maxAttempts - 1) {
|
|
450
|
-
|
|
451
|
-
debug(`No turns processed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay}ms...`);
|
|
452
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
412
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
453
413
|
}
|
|
454
414
|
}
|
|
455
415
|
const duration = (Date.now() - scriptStart) / 1000;
|
|
456
416
|
log('INFO', `Processed ${turns} turns in ${duration.toFixed(1)}s`);
|
|
457
|
-
if (duration > 180)
|
|
458
|
-
log('WARN', `Hook took ${duration.toFixed(1)}s (>3min)`);
|
|
459
417
|
}
|
|
460
418
|
catch (e) {
|
|
461
419
|
log('ERROR', `Failed to process session: ${e}`);
|
|
462
|
-
if (DEBUG_MODE)
|
|
463
|
-
debug(String(e.stack ?? e));
|
|
464
420
|
}
|
|
465
421
|
}
|
|
466
|
-
main()
|
|
467
|
-
|
|
468
|
-
process.
|
|
469
|
-
});
|
|
422
|
+
function main() {
|
|
423
|
+
// Worker mode: re-invoked as detached subprocess
|
|
424
|
+
if (process.env._RESPAN_CODEX_WORKER === '1') {
|
|
425
|
+
mainWorker().catch((e) => log('ERROR', `Worker crashed: ${e}`));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
debug('Codex hook started');
|
|
429
|
+
if (process.argv.length < 3) {
|
|
430
|
+
debug('No argument provided');
|
|
431
|
+
process.exit(0);
|
|
432
|
+
}
|
|
433
|
+
let payload;
|
|
434
|
+
try {
|
|
435
|
+
payload = JSON.parse(process.argv[2]);
|
|
436
|
+
}
|
|
437
|
+
catch (e) {
|
|
438
|
+
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
439
|
+
process.exit(0);
|
|
440
|
+
}
|
|
441
|
+
const eventType = String(payload.type ?? '');
|
|
442
|
+
if (eventType !== 'agent-turn-complete') {
|
|
443
|
+
debug(`Ignoring event type: ${eventType}`);
|
|
444
|
+
process.exit(0);
|
|
445
|
+
}
|
|
446
|
+
let sessionId = String(payload['thread-id'] ?? '');
|
|
447
|
+
if (!sessionId) {
|
|
448
|
+
debug('No thread-id in notify payload');
|
|
449
|
+
process.exit(0);
|
|
450
|
+
}
|
|
451
|
+
// Find session file
|
|
452
|
+
let sessionFile = findSessionFile(sessionId);
|
|
453
|
+
if (!sessionFile) {
|
|
454
|
+
const latest = findLatestSessionFile();
|
|
455
|
+
if (latest) {
|
|
456
|
+
sessionId = latest.sessionId;
|
|
457
|
+
sessionFile = latest.sessionFile;
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
debug('No session file found');
|
|
461
|
+
process.exit(0);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Fork self as detached worker so Codex CLI doesn't block
|
|
465
|
+
const cwd = String(payload.cwd ?? '');
|
|
466
|
+
debug(`Forking worker for session: ${sessionId}`);
|
|
467
|
+
try {
|
|
468
|
+
const scriptPath = __filename || process.argv[1];
|
|
469
|
+
const child = execFile('node', [scriptPath], {
|
|
470
|
+
env: {
|
|
471
|
+
...process.env,
|
|
472
|
+
_RESPAN_CODEX_WORKER: '1',
|
|
473
|
+
_RESPAN_CODEX_SESSION: sessionId,
|
|
474
|
+
_RESPAN_CODEX_FILE: sessionFile,
|
|
475
|
+
_RESPAN_CODEX_CWD: cwd,
|
|
476
|
+
},
|
|
477
|
+
stdio: 'ignore',
|
|
478
|
+
detached: true,
|
|
479
|
+
});
|
|
480
|
+
child.unref();
|
|
481
|
+
debug('Worker launched');
|
|
482
|
+
}
|
|
483
|
+
catch (e) {
|
|
484
|
+
log('ERROR', `Failed to fork worker: ${e}`);
|
|
485
|
+
}
|
|
486
|
+
process.exit(0);
|
|
487
|
+
}
|
|
488
|
+
main();
|
|
@@ -438,6 +438,34 @@ function detectModel(hookData) {
|
|
|
438
438
|
const llmReq = hookData.llm_request ?? {};
|
|
439
439
|
return String(llmReq.model ?? "") || "gemini-cli";
|
|
440
440
|
}
|
|
441
|
+
function buildToolSpan(detail, idx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime) {
|
|
442
|
+
const toolName = detail?.name ?? "";
|
|
443
|
+
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
444
|
+
const toolOutput = detail?.output ?? "";
|
|
445
|
+
const displayName = toolName ? toolDisplayName(toolName) : `Call ${idx + 1}`;
|
|
446
|
+
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : "";
|
|
447
|
+
const toolMeta = {};
|
|
448
|
+
if (toolName) toolMeta.tool_name = toolName;
|
|
449
|
+
if (detail?.error) toolMeta.error = detail.error;
|
|
450
|
+
const toolStart = detail?.start_time ?? beginTime;
|
|
451
|
+
const toolEnd = detail?.end_time ?? endTime;
|
|
452
|
+
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
453
|
+
return {
|
|
454
|
+
trace_unique_id: traceUniqueId,
|
|
455
|
+
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${idx + 1}`,
|
|
456
|
+
span_parent_id: rootSpanId,
|
|
457
|
+
span_name: `Tool: ${displayName}`,
|
|
458
|
+
span_workflow_name: workflowName,
|
|
459
|
+
span_path: toolName ? `tool_${toolName}` : "tool_call",
|
|
460
|
+
provider_id: "",
|
|
461
|
+
metadata: toolMeta,
|
|
462
|
+
input: toolInputStr,
|
|
463
|
+
output: truncate(toolOutput, MAX_CHARS),
|
|
464
|
+
timestamp: toolEnd,
|
|
465
|
+
start_time: toolStart,
|
|
466
|
+
...toolLat !== void 0 ? { latency: toolLat } : {}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
441
469
|
function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurns, toolDetails, thoughtsTokens, textRounds, roundStartTimes) {
|
|
442
470
|
const spans = [];
|
|
443
471
|
const sessionId = String(hookData.session_id ?? "");
|
|
@@ -519,33 +547,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
|
|
|
519
547
|
}
|
|
520
548
|
if (r < rounds.length - 1) {
|
|
521
549
|
while (toolIdx < toolDetails.length) {
|
|
522
|
-
|
|
523
|
-
const toolName = detail?.name ?? "";
|
|
524
|
-
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
525
|
-
const toolOutput = detail?.output ?? "";
|
|
526
|
-
const displayName = toolName ? toolDisplayName(toolName) : `Call ${toolIdx + 1}`;
|
|
527
|
-
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : "";
|
|
528
|
-
const toolMeta = {};
|
|
529
|
-
if (toolName) toolMeta.tool_name = toolName;
|
|
530
|
-
if (detail?.error) toolMeta.error = detail.error;
|
|
531
|
-
const toolStart = detail?.start_time ?? beginTime;
|
|
532
|
-
const toolEnd = detail?.end_time ?? endTime;
|
|
533
|
-
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
534
|
-
spans.push({
|
|
535
|
-
trace_unique_id: traceUniqueId,
|
|
536
|
-
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${toolIdx + 1}`,
|
|
537
|
-
span_parent_id: rootSpanId,
|
|
538
|
-
span_name: `Tool: ${displayName}`,
|
|
539
|
-
span_workflow_name: workflowName,
|
|
540
|
-
span_path: toolName ? `tool_${toolName}` : "tool_call",
|
|
541
|
-
provider_id: "",
|
|
542
|
-
metadata: toolMeta,
|
|
543
|
-
input: toolInputStr,
|
|
544
|
-
output: truncate(toolOutput, MAX_CHARS),
|
|
545
|
-
timestamp: toolEnd,
|
|
546
|
-
start_time: toolStart,
|
|
547
|
-
...toolLat !== void 0 ? { latency: toolLat } : {}
|
|
548
|
-
});
|
|
550
|
+
spans.push(buildToolSpan(toolDetails[toolIdx], toolIdx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime));
|
|
549
551
|
toolIdx++;
|
|
550
552
|
const nextDetail = toolDetails[toolIdx];
|
|
551
553
|
if (nextDetail && roundStarts[r + 1] && nextDetail.start_time && nextDetail.start_time > roundStarts[r + 1]) break;
|
|
@@ -553,33 +555,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
|
|
|
553
555
|
}
|
|
554
556
|
}
|
|
555
557
|
while (toolIdx < toolDetails.length) {
|
|
556
|
-
|
|
557
|
-
const toolName = detail?.name ?? "";
|
|
558
|
-
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
559
|
-
const toolOutput = detail?.output ?? "";
|
|
560
|
-
const displayName = toolName ? toolDisplayName(toolName) : `Call ${toolIdx + 1}`;
|
|
561
|
-
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : "";
|
|
562
|
-
const toolMeta = {};
|
|
563
|
-
if (toolName) toolMeta.tool_name = toolName;
|
|
564
|
-
if (detail?.error) toolMeta.error = detail.error;
|
|
565
|
-
const toolStart = detail?.start_time ?? beginTime;
|
|
566
|
-
const toolEnd = detail?.end_time ?? endTime;
|
|
567
|
-
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
568
|
-
spans.push({
|
|
569
|
-
trace_unique_id: traceUniqueId,
|
|
570
|
-
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${toolIdx + 1}`,
|
|
571
|
-
span_parent_id: rootSpanId,
|
|
572
|
-
span_name: `Tool: ${displayName}`,
|
|
573
|
-
span_workflow_name: workflowName,
|
|
574
|
-
span_path: toolName ? `tool_${toolName}` : "tool_call",
|
|
575
|
-
provider_id: "",
|
|
576
|
-
metadata: toolMeta,
|
|
577
|
-
input: toolInputStr,
|
|
578
|
-
output: truncate(toolOutput, MAX_CHARS),
|
|
579
|
-
timestamp: toolEnd,
|
|
580
|
-
start_time: toolStart,
|
|
581
|
-
...toolLat !== void 0 ? { latency: toolLat } : {}
|
|
582
|
-
});
|
|
558
|
+
spans.push(buildToolSpan(toolDetails[toolIdx], toolIdx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime));
|
|
583
559
|
toolIdx++;
|
|
584
560
|
}
|
|
585
561
|
if (thoughtsTokens > 0) {
|
package/dist/hooks/gemini-cli.js
CHANGED
|
@@ -142,6 +142,36 @@ function detectModel(hookData) {
|
|
|
142
142
|
return String(llmReq.model ?? '') || 'gemini-cli';
|
|
143
143
|
}
|
|
144
144
|
// ── Span construction ─────────────────────────────────────────────
|
|
145
|
+
function buildToolSpan(detail, idx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime) {
|
|
146
|
+
const toolName = detail?.name ?? '';
|
|
147
|
+
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
148
|
+
const toolOutput = detail?.output ?? '';
|
|
149
|
+
const displayName = toolName ? toolDisplayName(toolName) : `Call ${idx + 1}`;
|
|
150
|
+
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : '';
|
|
151
|
+
const toolMeta = {};
|
|
152
|
+
if (toolName)
|
|
153
|
+
toolMeta.tool_name = toolName;
|
|
154
|
+
if (detail?.error)
|
|
155
|
+
toolMeta.error = detail.error;
|
|
156
|
+
const toolStart = detail?.start_time ?? beginTime;
|
|
157
|
+
const toolEnd = detail?.end_time ?? endTime;
|
|
158
|
+
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
159
|
+
return {
|
|
160
|
+
trace_unique_id: traceUniqueId,
|
|
161
|
+
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${idx + 1}`,
|
|
162
|
+
span_parent_id: rootSpanId,
|
|
163
|
+
span_name: `Tool: ${displayName}`,
|
|
164
|
+
span_workflow_name: workflowName,
|
|
165
|
+
span_path: toolName ? `tool_${toolName}` : 'tool_call',
|
|
166
|
+
provider_id: '',
|
|
167
|
+
metadata: toolMeta,
|
|
168
|
+
input: toolInputStr,
|
|
169
|
+
output: truncate(toolOutput, MAX_CHARS),
|
|
170
|
+
timestamp: toolEnd,
|
|
171
|
+
start_time: toolStart,
|
|
172
|
+
...(toolLat !== undefined ? { latency: toolLat } : {}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
145
175
|
function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurns, toolDetails, thoughtsTokens, textRounds, roundStartTimes) {
|
|
146
176
|
const spans = [];
|
|
147
177
|
const sessionId = String(hookData.session_id ?? '');
|
|
@@ -233,76 +263,18 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
|
|
|
233
263
|
}
|
|
234
264
|
// Tool spans that come after this round (before next round)
|
|
235
265
|
if (r < rounds.length - 1) {
|
|
236
|
-
// Emit all tools between this round and the next
|
|
237
266
|
while (toolIdx < toolDetails.length) {
|
|
238
|
-
|
|
239
|
-
const toolName = detail?.name ?? '';
|
|
240
|
-
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
241
|
-
const toolOutput = detail?.output ?? '';
|
|
242
|
-
const displayName = toolName ? toolDisplayName(toolName) : `Call ${toolIdx + 1}`;
|
|
243
|
-
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : '';
|
|
244
|
-
const toolMeta = {};
|
|
245
|
-
if (toolName)
|
|
246
|
-
toolMeta.tool_name = toolName;
|
|
247
|
-
if (detail?.error)
|
|
248
|
-
toolMeta.error = detail.error;
|
|
249
|
-
const toolStart = detail?.start_time ?? beginTime;
|
|
250
|
-
const toolEnd = detail?.end_time ?? endTime;
|
|
251
|
-
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
252
|
-
spans.push({
|
|
253
|
-
trace_unique_id: traceUniqueId,
|
|
254
|
-
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${toolIdx + 1}`,
|
|
255
|
-
span_parent_id: rootSpanId,
|
|
256
|
-
span_name: `Tool: ${displayName}`,
|
|
257
|
-
span_workflow_name: workflowName,
|
|
258
|
-
span_path: toolName ? `tool_${toolName}` : 'tool_call',
|
|
259
|
-
provider_id: '',
|
|
260
|
-
metadata: toolMeta,
|
|
261
|
-
input: toolInputStr,
|
|
262
|
-
output: truncate(toolOutput, MAX_CHARS),
|
|
263
|
-
timestamp: toolEnd,
|
|
264
|
-
start_time: toolStart,
|
|
265
|
-
...(toolLat !== undefined ? { latency: toolLat } : {}),
|
|
266
|
-
});
|
|
267
|
+
spans.push(buildToolSpan(toolDetails[toolIdx], toolIdx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime));
|
|
267
268
|
toolIdx++;
|
|
268
|
-
// If next tool starts after next round's start time, break — it belongs to a later gap
|
|
269
269
|
const nextDetail = toolDetails[toolIdx];
|
|
270
270
|
if (nextDetail && roundStarts[r + 1] && nextDetail.start_time && nextDetail.start_time > roundStarts[r + 1])
|
|
271
271
|
break;
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
|
-
// Any remaining tools not yet emitted
|
|
275
|
+
// Any remaining tools not yet emitted
|
|
276
276
|
while (toolIdx < toolDetails.length) {
|
|
277
|
-
|
|
278
|
-
const toolName = detail?.name ?? '';
|
|
279
|
-
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
280
|
-
const toolOutput = detail?.output ?? '';
|
|
281
|
-
const displayName = toolName ? toolDisplayName(toolName) : `Call ${toolIdx + 1}`;
|
|
282
|
-
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : '';
|
|
283
|
-
const toolMeta = {};
|
|
284
|
-
if (toolName)
|
|
285
|
-
toolMeta.tool_name = toolName;
|
|
286
|
-
if (detail?.error)
|
|
287
|
-
toolMeta.error = detail.error;
|
|
288
|
-
const toolStart = detail?.start_time ?? beginTime;
|
|
289
|
-
const toolEnd = detail?.end_time ?? endTime;
|
|
290
|
-
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
291
|
-
spans.push({
|
|
292
|
-
trace_unique_id: traceUniqueId,
|
|
293
|
-
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${toolIdx + 1}`,
|
|
294
|
-
span_parent_id: rootSpanId,
|
|
295
|
-
span_name: `Tool: ${displayName}`,
|
|
296
|
-
span_workflow_name: workflowName,
|
|
297
|
-
span_path: toolName ? `tool_${toolName}` : 'tool_call',
|
|
298
|
-
provider_id: '',
|
|
299
|
-
metadata: toolMeta,
|
|
300
|
-
input: toolInputStr,
|
|
301
|
-
output: truncate(toolOutput, MAX_CHARS),
|
|
302
|
-
timestamp: toolEnd,
|
|
303
|
-
start_time: toolStart,
|
|
304
|
-
...(toolLat !== undefined ? { latency: toolLat } : {}),
|
|
305
|
-
});
|
|
277
|
+
spans.push(buildToolSpan(toolDetails[toolIdx], toolIdx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime));
|
|
306
278
|
toolIdx++;
|
|
307
279
|
}
|
|
308
280
|
// Reasoning span
|