Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 102 additions & 102 deletions pkg/workflow/js/parse_copilot_log.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -479,136 +479,136 @@ function parseDebugLogFormat(logContent) {
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) {
model = jsonData.model;
}
// Extract model info
if (jsonData.model) {
model = jsonData.model;
}

// Process the choices in the response
if (jsonData.choices && Array.isArray(jsonData.choices)) {
for (const choice of jsonData.choices) {
if (choice.message) {
const message = choice.message;

// Create an assistant entry
const content = [];
const toolResults = []; // Collect tool calls to create synthetic results (debug logs don't include actual results)

if (message.content && message.content.trim()) {
content.push({
type: "text",
text: message.content,
});
}
// Process the choices in the response
if (jsonData.choices && Array.isArray(jsonData.choices)) {
for (const choice of jsonData.choices) {
if (choice.message) {
const message = choice.message;

if (message.tool_calls && Array.isArray(message.tool_calls)) {
for (const toolCall of message.tool_calls) {
if (toolCall.function) {
let toolName = toolCall.function.name;
let args = {};

// Parse tool name (handle github- prefix and bash)
if (toolName.startsWith("github-")) {
toolName = "mcp__github__" + toolName.substring(7);
} else if (toolName === "bash") {
toolName = "Bash";
}
// Create an assistant entry
const content = [];
const toolResults = []; // Collect tool calls to create synthetic results (debug logs don't include actual results)

// Parse arguments
try {
args = JSON.parse(toolCall.function.arguments);
} catch (e) {
args = {};
if (message.content && message.content.trim()) {
content.push({
type: "text",
text: message.content,
});
}

if (message.tool_calls && Array.isArray(message.tool_calls)) {
for (const toolCall of message.tool_calls) {
if (toolCall.function) {
let toolName = toolCall.function.name;
let args = {};

// Parse tool name (handle github- prefix and bash)
if (toolName.startsWith("github-")) {
toolName = "mcp__github__" + toolName.substring(7);
} else if (toolName === "bash") {
toolName = "Bash";
}

// Parse arguments
try {
args = JSON.parse(toolCall.function.arguments);
} catch (e) {
args = {};
}

const toolId = toolCall.id || `tool_${Date.now()}_${Math.random()}`;
content.push({
type: "tool_use",
id: toolId,
name: toolName,
input: args,
});

// Create a corresponding tool result (assume success since we don't have actual results in debug logs)
toolResults.push({
type: "tool_result",
tool_use_id: toolId,
content: "", // No actual output available in debug logs
is_error: false, // Assume success
});
}
}
}

const toolId = toolCall.id || `tool_${Date.now()}_${Math.random()}`;
content.push({
type: "tool_use",
id: toolId,
name: toolName,
input: args,
});
if (content.length > 0) {
entries.push({
type: "assistant",
message: { content },
});
turnCount++;

// Create a corresponding tool result (assume success since we don't have actual results in debug logs)
toolResults.push({
type: "tool_result",
tool_use_id: toolId,
content: "", // No actual output available in debug logs
is_error: false, // Assume success
// Add tool results as a user message if we have any
if (toolResults.length > 0) {
entries.push({
type: "user",
message: { content: toolResults },
});
}
}
}
}

if (content.length > 0) {
entries.push({
type: "assistant",
message: { content },
});
turnCount++;
// Accumulate usage/result entry from each response
if (jsonData.usage) {
// Initialize accumulator if needed
if (!entries._accumulatedUsage) {
entries._accumulatedUsage = {
input_tokens: 0,
output_tokens: 0,
};
}

// Add tool results as a user message if we have any
if (toolResults.length > 0) {
entries.push({
type: "user",
message: { content: toolResults },
});
}
// 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;
}
}
}

// Accumulate usage/result entry from each response
if (jsonData.usage) {
// Initialize accumulator if needed
if (!entries._accumulatedUsage) {
entries._accumulatedUsage = {
input_tokens: 0,
output_tokens: 0,
// Store result entry with accumulated usage
entries._lastResult = {
type: "result",
num_turns: turnCount,
usage: entries._accumulatedUsage,
};
}

// 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: entries._accumulatedUsage,
};
}
} catch (e) {
// Skip invalid JSON blocks
}
} catch (e) {
// Skip invalid JSON blocks
}
}

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);
}
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\] /, "");
Expand Down Expand Up @@ -704,7 +704,7 @@ function parseDebugLogFormat(logContent) {
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) {
Expand All @@ -713,7 +713,7 @@ function parseDebugLogFormat(logContent) {
if (jsonData.usage.completion_tokens) {
entries._accumulatedUsage.output_tokens += jsonData.usage.completion_tokens;
}

// Store result entry with accumulated usage
entries._lastResult = {
type: "result",
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/js/parse_copilot_log.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ describe("parse_copilot_log.cjs", () => {
expect(result).toContain("**Token Usage:**");
expect(result).toContain("- Input: 300");
expect(result).toContain("- Output: 60");

// Should have 2 turns
expect(result).toContain("**Turns:** 2");
});
Expand Down