mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 19:14:33 -07:00
feat(headless): gate diagnostic output behind --debug flag
Diagnostic monitoring data (auth_method, user_tier, api_requests, api_errors, retry_count, RETRY events, LOOP_DETECTED events, and stderr warnings) is now only emitted when --debug / -d is passed. Without the flag, headless output is identical to before — no new fields, no new events, no stderr noise. This keeps default output clean for piped workflows while making diagnostics available on demand.
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
exports[`runNonInteractive > should emit appropriate error event in streaming JSON mode: 'loop detected' 1`] = `
|
||||
"{"type":"init","timestamp":"<TIMESTAMP>","session_id":"test-session-id","model":"test-model"}
|
||||
{"type":"message","timestamp":"<TIMESTAMP>","role":"user","content":"Loop test"}
|
||||
{"type":"loop_detected","timestamp":"<TIMESTAMP>"}
|
||||
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0,"api_requests":0,"api_errors":0}}
|
||||
{"type":"error","timestamp":"<TIMESTAMP>","severity":"warning","message":"Loop detected, stopping execution"}
|
||||
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0}}
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`runNonInteractive > should emit appropriate error event in streaming JS
|
||||
"{"type":"init","timestamp":"<TIMESTAMP>","session_id":"test-session-id","model":"test-model"}
|
||||
{"type":"message","timestamp":"<TIMESTAMP>","role":"user","content":"Max turns test"}
|
||||
{"type":"error","timestamp":"<TIMESTAMP>","severity":"error","message":"Maximum session turns exceeded"}
|
||||
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0,"api_requests":0,"api_errors":0}}
|
||||
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0}}
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -23,7 +23,7 @@ exports[`runNonInteractive > should emit appropriate events for streaming JSON o
|
||||
{"type":"tool_use","timestamp":"<TIMESTAMP>","tool_name":"testTool","tool_id":"tool-1","parameters":{"arg1":"value1"}}
|
||||
{"type":"tool_result","timestamp":"<TIMESTAMP>","tool_id":"tool-1","status":"success","output":"Tool executed successfully"}
|
||||
{"type":"message","timestamp":"<TIMESTAMP>","role":"assistant","content":"Final answer","delta":true}
|
||||
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0,"api_requests":0,"api_errors":0}}
|
||||
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0}}
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ export async function runNonInteractive({
|
||||
|
||||
const startTime = Date.now();
|
||||
let retryCount = 0;
|
||||
const debugMode = config.getDebugMode();
|
||||
const streamFormatter =
|
||||
config.getOutputFormat() === OutputFormat.STREAM_JSON
|
||||
? new StreamJsonFormatter()
|
||||
@@ -185,6 +186,7 @@ export async function runNonInteractive({
|
||||
|
||||
const handleRetryAttempt = (payload: RetryAttemptPayload) => {
|
||||
retryCount++;
|
||||
if (!debugMode) return;
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.RETRY,
|
||||
@@ -252,8 +254,10 @@ export async function runNonInteractive({
|
||||
}
|
||||
|
||||
// Emit init event for streaming JSON
|
||||
const authMethod = config.getContentGeneratorConfig()?.authType;
|
||||
const userTier = config.getUserTierName();
|
||||
const authMethod = debugMode
|
||||
? config.getContentGeneratorConfig()?.authType
|
||||
: undefined;
|
||||
const userTier = debugMode ? config.getUserTierName() : undefined;
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.INIT,
|
||||
@@ -373,18 +377,27 @@ export async function runNonInteractive({
|
||||
}
|
||||
toolCallRequests.push(event.value);
|
||||
} else if (event.type === GeminiEventType.LoopDetected) {
|
||||
const loopType = event.value?.loopType;
|
||||
if (streamFormatter) {
|
||||
if (debugMode) {
|
||||
const loopType = event.value?.loopType;
|
||||
if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.LOOP_DETECTED,
|
||||
timestamp: new Date().toISOString(),
|
||||
...(loopType && { loop_type: loopType }),
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
const loopTypeStr = loopType ? ` (${loopType})` : '';
|
||||
process.stderr.write(
|
||||
`[WARNING] Loop detected${loopTypeStr}, stopping execution\n`,
|
||||
);
|
||||
}
|
||||
} else if (streamFormatter) {
|
||||
streamFormatter.emitEvent({
|
||||
type: JsonStreamEventType.LOOP_DETECTED,
|
||||
type: JsonStreamEventType.ERROR,
|
||||
timestamp: new Date().toISOString(),
|
||||
...(loopType && { loop_type: loopType }),
|
||||
severity: 'warning',
|
||||
message: 'Loop detected, stopping execution',
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
const loopTypeStr = loopType ? ` (${loopType})` : '';
|
||||
process.stderr.write(
|
||||
`[WARNING] Loop detected${loopTypeStr}, stopping execution\n`,
|
||||
);
|
||||
}
|
||||
} else if (event.type === GeminiEventType.MaxSessionTurns) {
|
||||
if (streamFormatter) {
|
||||
@@ -413,7 +426,7 @@ export async function runNonInteractive({
|
||||
stats: streamFormatter.convertToStreamStats(
|
||||
metrics,
|
||||
durationMs,
|
||||
retryCount,
|
||||
{ retryCount, includeDiagnostics: debugMode },
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -513,7 +526,7 @@ export async function runNonInteractive({
|
||||
stats: streamFormatter.convertToStreamStats(
|
||||
metrics,
|
||||
durationMs,
|
||||
retryCount,
|
||||
{ retryCount, includeDiagnostics: debugMode },
|
||||
),
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
@@ -545,11 +558,10 @@ export async function runNonInteractive({
|
||||
type: JsonStreamEventType.RESULT,
|
||||
timestamp: new Date().toISOString(),
|
||||
status: 'success',
|
||||
stats: streamFormatter.convertToStreamStats(
|
||||
metrics,
|
||||
durationMs,
|
||||
stats: streamFormatter.convertToStreamStats(metrics, durationMs, {
|
||||
retryCount,
|
||||
),
|
||||
includeDiagnostics: debugMode,
|
||||
}),
|
||||
});
|
||||
} else if (config.getOutputFormat() === OutputFormat.JSON) {
|
||||
const formatter = new JsonFormatter();
|
||||
|
||||
@@ -39,25 +39,21 @@ export class StreamJsonFormatter {
|
||||
convertToStreamStats(
|
||||
metrics: SessionMetrics,
|
||||
durationMs: number,
|
||||
retryCount?: number,
|
||||
options?: { retryCount?: number; includeDiagnostics?: boolean },
|
||||
): StreamStats {
|
||||
let totalTokens = 0;
|
||||
let inputTokens = 0;
|
||||
let outputTokens = 0;
|
||||
let cached = 0;
|
||||
let input = 0;
|
||||
let apiRequests = 0;
|
||||
let apiErrors = 0;
|
||||
|
||||
// Aggregate token counts and API stats across all models
|
||||
// Aggregate token counts across all models
|
||||
for (const modelMetrics of Object.values(metrics.models)) {
|
||||
totalTokens += modelMetrics.tokens.total;
|
||||
inputTokens += modelMetrics.tokens.prompt;
|
||||
outputTokens += modelMetrics.tokens.candidates;
|
||||
cached += modelMetrics.tokens.cached;
|
||||
input += modelMetrics.tokens.input;
|
||||
apiRequests += modelMetrics.api.totalRequests;
|
||||
apiErrors += modelMetrics.api.totalErrors;
|
||||
}
|
||||
|
||||
const stats: StreamStats = {
|
||||
@@ -68,12 +64,21 @@ export class StreamJsonFormatter {
|
||||
input,
|
||||
duration_ms: durationMs,
|
||||
tool_calls: metrics.tools.totalCalls,
|
||||
api_requests: apiRequests,
|
||||
api_errors: apiErrors,
|
||||
};
|
||||
|
||||
if (retryCount && retryCount > 0) {
|
||||
stats.retry_count = retryCount;
|
||||
if (options?.includeDiagnostics) {
|
||||
let apiRequests = 0;
|
||||
let apiErrors = 0;
|
||||
for (const modelMetrics of Object.values(metrics.models)) {
|
||||
apiRequests += modelMetrics.api.totalRequests;
|
||||
apiErrors += modelMetrics.api.totalErrors;
|
||||
}
|
||||
stats.api_requests = apiRequests;
|
||||
stats.api_errors = apiErrors;
|
||||
|
||||
if (options.retryCount && options.retryCount > 0) {
|
||||
stats.retry_count = options.retryCount;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
|
||||
@@ -96,8 +96,8 @@ for model in "${MODELS[@]}"; do
|
||||
STDERRFILE=$(mktemp)
|
||||
exit_code=0
|
||||
|
||||
echo -e " ${DIM}Running with -o stream-json ...${RESET}"
|
||||
node "$CLI" -p "$PROMPT" -y -m "$model" -o stream-json \
|
||||
echo -e " ${DIM}Running with -o stream-json -d ...${RESET}"
|
||||
node "$CLI" -p "$PROMPT" -y -m "$model" -o stream-json -d \
|
||||
>"$TMPFILE" 2>"$STDERRFILE" || exit_code=$?
|
||||
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
|
||||
Reference in New Issue
Block a user