feat(telemetry): Integrate AI usage telemetry into update-subtask
This commit applies the standard telemetry pattern to the update-subtask command and its corresponding MCP tool.
Key Changes:
1. Core Logic (scripts/modules/task-manager/update-subtask-by-id.js):
- The call to generateTextService now includes commandName: 'update-subtask' and outputType.
- The full response { mainResult, telemetryData } is captured.
- mainResult (the AI-generated text) is used for the appended content.
- If running in CLI mode (outputFormat === 'text'), displayAiUsageSummary is called with the telemetryData.
- The function now returns { updatedSubtask: ..., telemetryData: ... }.
2. Direct Function (mcp-server/src/core/direct-functions/update-subtask-by-id.js):
- The call to the core updateSubtaskById function now passes the necessary context for telemetry (commandName, outputType).
- The successful response object now correctly extracts coreResult.telemetryData and includes it in the data.telemetryData field returned to the MCP client.
This commit is contained in:
@@ -229,18 +229,22 @@ async function _attemptProviderCallWithRetries(
|
||||
|
||||
while (retries <= MAX_RETRIES) {
|
||||
try {
|
||||
log(
|
||||
'info',
|
||||
`Attempt ${retries + 1}/${MAX_RETRIES + 1} calling ${fnName} (Provider: ${providerName}, Model: ${modelId}, Role: ${attemptRole})`
|
||||
);
|
||||
if (getDebugFlag()) {
|
||||
log(
|
||||
'info',
|
||||
`Attempt ${retries + 1}/${MAX_RETRIES + 1} calling ${fnName} (Provider: ${providerName}, Model: ${modelId}, Role: ${attemptRole})`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the specific provider function directly
|
||||
const result = await providerApiFn(callParams);
|
||||
|
||||
log(
|
||||
'info',
|
||||
`${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}`
|
||||
);
|
||||
if (getDebugFlag()) {
|
||||
log(
|
||||
'info',
|
||||
`${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}`
|
||||
);
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
log(
|
||||
@@ -253,13 +257,13 @@ async function _attemptProviderCallWithRetries(
|
||||
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retries - 1);
|
||||
log(
|
||||
'info',
|
||||
`Retryable error detected. Retrying in ${delay / 1000}s...`
|
||||
`Something went wrong on the provider side. Retrying in ${delay / 1000}s...`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
} else {
|
||||
log(
|
||||
'error',
|
||||
`Non-retryable error or max retries reached for role ${attemptRole} (${fnName} / ${providerName}).`
|
||||
`Something went wrong on the provider side. Max retries reached for role ${attemptRole} (${fnName} / ${providerName}).`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import Table from 'cli-table3';
|
||||
import {
|
||||
getStatusWithColor,
|
||||
startLoadingIndicator,
|
||||
stopLoadingIndicator
|
||||
stopLoadingIndicator,
|
||||
displayAiUsageSummary
|
||||
} from '../ui.js';
|
||||
import {
|
||||
log as consoleLog,
|
||||
@@ -154,8 +155,10 @@ async function updateSubtaskById(
|
||||
);
|
||||
}
|
||||
|
||||
let generatedContentString = ''; // Initialize to empty string
|
||||
let newlyAddedSnippet = ''; // <--- ADD THIS LINE: Variable to store the snippet for CLI display
|
||||
let generatedContentString = '';
|
||||
let newlyAddedSnippet = '';
|
||||
let aiServiceResponse = null;
|
||||
|
||||
try {
|
||||
const parentContext = {
|
||||
id: parentTask.id,
|
||||
@@ -202,73 +205,44 @@ Output Requirements:
|
||||
const role = useResearch ? 'research' : 'main';
|
||||
report('info', `Using AI text service with role: ${role}`);
|
||||
|
||||
// Store the entire response object from the AI service
|
||||
const aiServiceResponse = await generateTextService({
|
||||
aiServiceResponse = await generateTextService({
|
||||
prompt: userPrompt,
|
||||
systemPrompt: systemPrompt,
|
||||
role,
|
||||
session,
|
||||
projectRoot,
|
||||
maxRetries: 2
|
||||
maxRetries: 2,
|
||||
commandName: 'update-subtask',
|
||||
outputType: isMCP ? 'mcp' : 'cli'
|
||||
});
|
||||
|
||||
report(
|
||||
'info',
|
||||
`>>> DEBUG: AI Service Response Object: ${JSON.stringify(aiServiceResponse, null, 2)}`
|
||||
);
|
||||
report(
|
||||
'info',
|
||||
`>>> DEBUG: Extracted generatedContentString: "${generatedContentString}"`
|
||||
);
|
||||
|
||||
// Extract the actual text content from the mainResult property
|
||||
// and ensure it's a string, defaulting to empty if not.
|
||||
if (
|
||||
aiServiceResponse &&
|
||||
aiServiceResponse.mainResult &&
|
||||
typeof aiServiceResponse.mainResult.text === 'string'
|
||||
typeof aiServiceResponse.mainResult === 'string'
|
||||
) {
|
||||
generatedContentString = aiServiceResponse.mainResult.text;
|
||||
generatedContentString = aiServiceResponse.mainResult;
|
||||
} else {
|
||||
generatedContentString = ''; // Default to empty if mainResult.text is not a string or the path is invalid
|
||||
generatedContentString = '';
|
||||
report(
|
||||
'warn',
|
||||
'AI service response did not contain expected text string.'
|
||||
);
|
||||
}
|
||||
// The telemetryData would be in aiServiceResponse.telemetryData if needed elsewhere
|
||||
|
||||
report(
|
||||
'success',
|
||||
'Successfully received response object from AI service' // Log message updated for clarity
|
||||
);
|
||||
|
||||
if (outputFormat === 'text' && loadingIndicator) {
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
loadingIndicator = null;
|
||||
}
|
||||
|
||||
// This check now correctly validates the extracted string
|
||||
if (typeof generatedContentString !== 'string') {
|
||||
report(
|
||||
'warn',
|
||||
'AI mainResult was not a valid text string. Treating as empty.'
|
||||
);
|
||||
generatedContentString = ''; // Ensure it's a string for trim() later
|
||||
} else if (generatedContentString.trim() !== '') {
|
||||
report(
|
||||
'success',
|
||||
`Successfully extracted text from AI response using role: ${role}.`
|
||||
);
|
||||
}
|
||||
// No need for an else here, as an empty string from mainResult is a valid scenario
|
||||
// that will be handled by the `if (generatedContentString && generatedContentString.trim())` later.
|
||||
} catch (aiError) {
|
||||
report('error', `AI service call failed: ${aiError.message}`);
|
||||
if (outputFormat === 'text' && loadingIndicator) {
|
||||
stopLoadingIndicator(loadingIndicator); // Ensure stop on error
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
loadingIndicator = null;
|
||||
}
|
||||
throw aiError;
|
||||
}
|
||||
|
||||
// --- TIMESTAMP & FORMATTING LOGIC (Handled Locally) ---
|
||||
if (generatedContentString && generatedContentString.trim()) {
|
||||
// Check if the string is not empty
|
||||
const timestamp = new Date().toISOString();
|
||||
@@ -277,21 +251,15 @@ Output Requirements:
|
||||
|
||||
subtask.details =
|
||||
(subtask.details ? subtask.details + '\n' : '') + formattedBlock;
|
||||
report(
|
||||
'info',
|
||||
'Appended timestamped, formatted block with AI-generated content to subtask.details.'
|
||||
);
|
||||
} else {
|
||||
report(
|
||||
'warn',
|
||||
'AI response was empty or whitespace after trimming. Original details remain unchanged.'
|
||||
);
|
||||
newlyAddedSnippet = 'No new details were added by the AI.'; // <--- ADD THIS LINE: Set message for CLI
|
||||
newlyAddedSnippet = 'No new details were added by the AI.';
|
||||
}
|
||||
// --- END TIMESTAMP & FORMATTING LOGIC ---
|
||||
|
||||
const updatedSubtask = parentTask.subtasks[subtaskIndex];
|
||||
report('info', 'Updated subtask details locally after AI generation.');
|
||||
|
||||
if (outputFormat === 'text' && getDebugFlag(session)) {
|
||||
console.log(
|
||||
@@ -349,7 +317,15 @@ Output Requirements:
|
||||
)
|
||||
);
|
||||
}
|
||||
return updatedSubtask;
|
||||
|
||||
if (outputFormat === 'text' && aiServiceResponse.telemetryData) {
|
||||
displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
|
||||
}
|
||||
|
||||
return {
|
||||
updatedSubtask: updatedSubtask,
|
||||
telemetryData: aiServiceResponse.telemetryData
|
||||
};
|
||||
} catch (error) {
|
||||
if (outputFormat === 'text' && loadingIndicator) {
|
||||
stopLoadingIndicator(loadingIndicator);
|
||||
|
||||
Reference in New Issue
Block a user