diff --git a/.changeset/patch-fix-copilot-log-parser.md b/.changeset/patch-fix-copilot-log-parser.md new file mode 100644 index 0000000000..c3cb0a54eb --- /dev/null +++ b/.changeset/patch-fix-copilot-log-parser.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Fix Copilot log parser: accumulate token usage and improve JSON block detection diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index 2f3c97ecf1..625b3695bb 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -2479,12 +2479,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2547,12 +2549,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2560,6 +2573,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -2632,12 +2649,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index a894cb039f..eaaf2d246d 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -3593,12 +3593,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3661,12 +3663,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3674,6 +3687,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3746,12 +3763,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index ccf73be49f..daa46585e3 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -3014,12 +3014,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3082,12 +3084,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3095,6 +3108,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3167,12 +3184,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 62b753482e..a4063df573 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -2657,12 +2657,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2725,12 +2727,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2738,6 +2751,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -2810,12 +2827,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index d2d898a101..2c0badd1c6 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -2914,12 +2914,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2982,12 +2984,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2995,6 +3008,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3067,12 +3084,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 89dce27449..50fae58af9 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -935,12 +935,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -1003,12 +1005,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -1016,6 +1029,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -1088,12 +1105,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index ad44134e45..bfa03ea9c8 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -3435,12 +3435,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3503,12 +3505,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3516,6 +3529,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3588,12 +3605,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index f6e788793f..b4a9013695 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -2451,12 +2451,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2519,12 +2521,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2532,6 +2545,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -2604,12 +2621,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 0fe75a275f..0b47eecb13 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -3543,12 +3543,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3611,12 +3613,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3624,6 +3637,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3696,12 +3713,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index b21fbed1a5..c4e7f5c4cd 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -3059,12 +3059,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3127,12 +3129,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3140,6 +3153,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3212,12 +3229,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index dc9908ea6b..0429a8cac7 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -3787,12 +3787,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3855,12 +3857,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3868,6 +3881,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3940,12 +3957,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 0dd0d688b1..8b109d1bbb 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -3846,12 +3846,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3914,12 +3916,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3927,6 +3940,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3999,12 +4016,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index cd88f38860..f321bb8b7f 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -2546,12 +2546,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2614,12 +2616,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2627,6 +2640,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -2699,12 +2716,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 1f29fa6cba..8636139c2a 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -2469,12 +2469,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2537,12 +2539,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2550,6 +2563,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -2622,12 +2639,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 3fa80ed864..3116830418 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -4059,12 +4059,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -4127,12 +4129,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -4140,6 +4153,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -4212,12 +4229,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 21b2c8ada1..e592f302e4 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -2419,12 +2419,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2487,12 +2489,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2500,6 +2513,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -2572,12 +2589,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/test-jqschema.lock.yml b/.github/workflows/test-jqschema.lock.yml index 91c1322aed..3e76318cc5 100644 --- a/.github/workflows/test-jqschema.lock.yml +++ b/.github/workflows/test-jqschema.lock.yml @@ -1009,12 +1009,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -1077,12 +1079,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -1090,6 +1103,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -1162,12 +1179,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/test-post-steps.lock.yml b/.github/workflows/test-post-steps.lock.yml index bbfc343f9e..df85491cd4 100644 --- a/.github/workflows/test-post-steps.lock.yml +++ b/.github/workflows/test-post-steps.lock.yml @@ -906,12 +906,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -974,12 +976,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -987,6 +1000,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -1059,12 +1076,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/test-svelte.lock.yml b/.github/workflows/test-svelte.lock.yml index f37b3b86ad..858d7a361c 100644 --- a/.github/workflows/test-svelte.lock.yml +++ b/.github/workflows/test-svelte.lock.yml @@ -944,12 +944,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -1012,12 +1014,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -1025,6 +1038,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -1097,12 +1114,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 051ebed1dc..727b3cdcb0 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -2944,12 +2944,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -3012,12 +3014,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -3025,6 +3038,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -3097,12 +3114,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index 377e759000..bab3df07f8 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -2714,12 +2714,14 @@ jobs: } if (inDataBlock) { const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + if (!isJsonContent) { + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); if (jsonData.model) { model = jsonData.model; } @@ -2782,12 +2784,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { @@ -2795,6 +2808,10 @@ jobs: } inDataBlock = false; currentJsonLines = []; + continue; + } else if (hasTimestamp && isJsonContent) { + currentJsonLines.push(cleanLine); + } } else { const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); currentJsonLines.push(cleanLine); @@ -2867,12 +2884,23 @@ jobs: } } if (jsonData.usage) { - const resultEntry = { + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/pkg/workflow/js/parse_copilot_log.cjs b/pkg/workflow/js/parse_copilot_log.cjs index 932d689528..6bd4427c7d 100644 --- a/pkg/workflow/js/parse_copilot_log.cjs +++ b/pkg/workflow/js/parse_copilot_log.cjs @@ -473,16 +473,23 @@ function parseDebugLogFormat(logContent) { // While in a data block, accumulate lines if (inDataBlock) { - // Check if this line starts with timestamp AND NOT [DEBUG] (new non-JSON log entry) + // Check if this line starts with timestamp const hasTimestamp = line.match(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z /); - const hasDebug = line.includes("[DEBUG]"); - if (hasTimestamp && !hasDebug) { - // This is a new log line (not part of JSON) - end of JSON block, process what we have - if (currentJsonLines.length > 0) { - try { - const jsonStr = currentJsonLines.join("\n"); - const jsonData = JSON.parse(jsonStr); + if (hasTimestamp) { + // Strip the timestamp and [DEBUG] prefix to see what remains + const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); + + // If after stripping, the line starts with JSON characters, it's part of JSON + // Otherwise, it's a new log entry and we should end the block + const isJsonContent = /^[{\[}\]"]/.test(cleanLine) || cleanLine.trim().startsWith('"'); + + if (!isJsonContent) { + // This is a new log line (not JSON content) - end of JSON block, process what we have + if (currentJsonLines.length > 0) { + try { + const jsonStr = currentJsonLines.join("\n"); + const jsonData = JSON.parse(jsonStr); // Extract model info if (jsonData.model) { @@ -563,16 +570,31 @@ function parseDebugLogFormat(logContent) { } } - // Add usage/result entry if this is the last response + // Accumulate usage/result entry from each response if (jsonData.usage) { - const resultEntry = { + // Initialize accumulator if needed + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + + // Accumulate token counts from this response + // OpenAI uses prompt_tokens/completion_tokens, normalize to input_tokens/output_tokens + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + + // Store result entry with accumulated usage + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - - // Store for later (we'll add it at the end) - entries._lastResult = resultEntry; } } } catch (e) { @@ -582,6 +604,11 @@ function parseDebugLogFormat(logContent) { inDataBlock = false; currentJsonLines = []; + continue; // Don't add this line to JSON + } else if (hasTimestamp && isJsonContent) { + // This line has a timestamp but is JSON content - strip prefix and add + currentJsonLines.push(cleanLine); + } } else { // This line is part of the JSON - add it (remove [DEBUG] prefix if present) const cleanLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z \[DEBUG\] /, ""); @@ -670,12 +697,29 @@ function parseDebugLogFormat(logContent) { } if (jsonData.usage) { - const resultEntry = { + // Initialize accumulator if needed + if (!entries._accumulatedUsage) { + entries._accumulatedUsage = { + input_tokens: 0, + output_tokens: 0, + }; + } + + // Accumulate token counts from this response + // OpenAI uses prompt_tokens/completion_tokens, normalize to input_tokens/output_tokens + if (jsonData.usage.prompt_tokens) { + entries._accumulatedUsage.input_tokens += jsonData.usage.prompt_tokens; + } + if (jsonData.usage.completion_tokens) { + entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens; + } + + // Store result entry with accumulated usage + entries._lastResult = { type: "result", num_turns: turnCount, - usage: jsonData.usage, + usage: entries._accumulatedUsage, }; - entries._lastResult = resultEntry; } } } catch (e) { diff --git a/pkg/workflow/js/parse_copilot_log.test.cjs b/pkg/workflow/js/parse_copilot_log.test.cjs index 33e0cd12fc..c0dfbf92dd 100644 --- a/pkg/workflow/js/parse_copilot_log.test.cjs +++ b/pkg/workflow/js/parse_copilot_log.test.cjs @@ -742,6 +742,57 @@ describe("parse_copilot_log.cjs", () => { expect(result).toContain("**Token Usage:**"); }); + it("should accumulate token usage across multiple API responses in debug logs", () => { + // Test token accumulation - using format that matches existing successful tests + const debugLogWith2Responses = `2025-10-21T01:00:00.000Z [INFO] Starting Copilot CLI: 0.0.350 +2025-10-21T01:00:01.000Z [DEBUG] response (Request-ID test-1): +2025-10-21T01:00:01.000Z [DEBUG] data: +{ + "id": "chatcmpl-1", + "model": "claude-sonnet-4", + "choices": [{ + "message": { + "role": "assistant", + "content": "I'll help you." + }, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 100, + "completion_tokens": 50, + "total_tokens": 150 + } +} +2025-10-21T01:00:02.000Z [DEBUG] response (Request-ID test-2): +2025-10-21T01:00:02.000Z [DEBUG] data: +{ + "id": "chatcmpl-2", + "model": "claude-sonnet-4", + "choices": [{ + "message": { + "role": "assistant", + "content": "Done!" + }, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 200, + "completion_tokens": 10, + "total_tokens": 210 + } +}`; + + const result = parseCopilotLog(debugLogWith2Responses); + + // Should show accumulated tokens: 100+200=300 input, 50+10=60 output + expect(result).toContain("**Token Usage:**"); + expect(result).toContain("- Input: 300"); + expect(result).toContain("- Output: 60"); + + // Should have 2 turns + expect(result).toContain("**Turns:** 2"); + }); + it("should extract premium request count from log content using regex", () => { // Test that the regex extraction works const logWithPremiumInfo =