Compare commits
12 Commits
task-maste
...
feat-gener
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58cc143aef | ||
|
|
3aac7ac349 | ||
|
|
f68330efb3 | ||
|
|
1d197fe9c2 | ||
|
|
7660a29a1a | ||
|
|
0aaa105021 | ||
|
|
6b15788c58 | ||
|
|
a2de49dd90 | ||
|
|
2063dc4b7d | ||
|
|
7e6319a56f | ||
|
|
b0504a00d5 | ||
|
|
b16023ab2f |
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix complexity score not showing for `task-master show` and `task-master list`
|
|
||||||
|
|
||||||
- Added complexity score on "next task" when running `task-master list`
|
|
||||||
- Added colors to complexity to reflect complexity (easy, medium, hard)
|
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
# task-master-ai
|
# task-master-ai
|
||||||
|
|
||||||
## 0.27.3
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- [#1254](https://github.com/eyaltoledano/claude-task-master/pull/1254) [`af53525`](https://github.com/eyaltoledano/claude-task-master/commit/af53525cbc660a595b67d4bb90d906911c71f45d) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fixed issue where `tm show` command could not find subtasks using dotted notation IDs (e.g., '8.1').
|
|
||||||
- The command now properly searches within parent task subtasks and returns the correct subtask information.
|
|
||||||
|
|
||||||
## 0.27.2
|
## 0.27.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -281,14 +281,9 @@ export class ListTasksCommand extends Command {
|
|||||||
const priorityBreakdown = getPriorityBreakdown(tasks);
|
const priorityBreakdown = getPriorityBreakdown(tasks);
|
||||||
|
|
||||||
// Find next task following the same logic as findNextTask
|
// Find next task following the same logic as findNextTask
|
||||||
const nextTaskInfo = this.findNextTask(tasks);
|
const nextTask = this.findNextTask(tasks);
|
||||||
|
|
||||||
// Get the full task object with complexity data already included
|
// Display dashboard boxes
|
||||||
const nextTask = nextTaskInfo
|
|
||||||
? tasks.find((t) => String(t.id) === String(nextTaskInfo.id))
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
// Display dashboard boxes (nextTask already has complexity from storage enrichment)
|
|
||||||
displayDashboards(
|
displayDashboards(
|
||||||
taskStats,
|
taskStats,
|
||||||
subtaskStats,
|
subtaskStats,
|
||||||
@@ -308,16 +303,14 @@ export class ListTasksCommand extends Command {
|
|||||||
|
|
||||||
// Display recommended next task section immediately after table
|
// Display recommended next task section immediately after table
|
||||||
if (nextTask) {
|
if (nextTask) {
|
||||||
const description = getTaskDescription(nextTask);
|
// Find the full task object to get description
|
||||||
|
const fullTask = tasks.find((t) => String(t.id) === String(nextTask.id));
|
||||||
|
const description = fullTask ? getTaskDescription(fullTask) : undefined;
|
||||||
|
|
||||||
displayRecommendedNextTask({
|
displayRecommendedNextTask({
|
||||||
id: nextTask.id,
|
...nextTask,
|
||||||
title: nextTask.title,
|
status: 'pending', // Next task is typically pending
|
||||||
priority: nextTask.priority,
|
description
|
||||||
status: nextTask.status,
|
|
||||||
dependencies: nextTask.dependencies,
|
|
||||||
description,
|
|
||||||
complexity: nextTask.complexity as number | undefined
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
displayRecommendedNextTask(undefined);
|
displayRecommendedNextTask(undefined);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import type { Task, TaskPriority } from '@tm/core/types';
|
import type { Task, TaskPriority } from '@tm/core/types';
|
||||||
import { getComplexityWithColor } from '../../utils/ui.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Statistics for task collection
|
* Statistics for task collection
|
||||||
@@ -480,7 +479,7 @@ export function displayDependencyDashboard(
|
|||||||
? chalk.cyan(nextTask.dependencies.join(', '))
|
? chalk.cyan(nextTask.dependencies.join(', '))
|
||||||
: chalk.gray('None')
|
: chalk.gray('None')
|
||||||
}\n` +
|
}\n` +
|
||||||
`Complexity: ${nextTask?.complexity !== undefined ? getComplexityWithColor(nextTask.complexity) : chalk.gray('N/A')}`;
|
`Complexity: ${nextTask?.complexity || chalk.gray('N/A')}`;
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import type { Task } from '@tm/core/types';
|
import type { Task } from '@tm/core/types';
|
||||||
import { getComplexityWithColor } from '../../utils/ui.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next task display options
|
* Next task display options
|
||||||
@@ -18,7 +17,6 @@ export interface NextTaskDisplayOptions {
|
|||||||
status?: string;
|
status?: string;
|
||||||
dependencies?: (string | number)[];
|
dependencies?: (string | number)[];
|
||||||
description?: string;
|
description?: string;
|
||||||
complexity?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,11 +82,6 @@ export function displayRecommendedNextTask(
|
|||||||
: chalk.cyan(task.dependencies.join(', '));
|
: chalk.cyan(task.dependencies.join(', '));
|
||||||
content.push(`Dependencies: ${depsDisplay}`);
|
content.push(`Dependencies: ${depsDisplay}`);
|
||||||
|
|
||||||
// Complexity with color and label
|
|
||||||
if (typeof task.complexity === 'number') {
|
|
||||||
content.push(`Complexity: ${getComplexityWithColor(task.complexity)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description if available
|
// Description if available
|
||||||
if (task.description) {
|
if (task.description) {
|
||||||
content.push('');
|
content.push('');
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ import Table from 'cli-table3';
|
|||||||
import { marked, MarkedExtension } from 'marked';
|
import { marked, MarkedExtension } from 'marked';
|
||||||
import { markedTerminal } from 'marked-terminal';
|
import { markedTerminal } from 'marked-terminal';
|
||||||
import type { Task } from '@tm/core/types';
|
import type { Task } from '@tm/core/types';
|
||||||
import {
|
import { getStatusWithColor, getPriorityWithColor } from '../../utils/ui.js';
|
||||||
getStatusWithColor,
|
|
||||||
getPriorityWithColor,
|
|
||||||
getComplexityWithColor
|
|
||||||
} from '../../utils/ui.js';
|
|
||||||
|
|
||||||
// Configure marked to use terminal renderer with subtle colors
|
// Configure marked to use terminal renderer with subtle colors
|
||||||
marked.use(
|
marked.use(
|
||||||
@@ -112,9 +108,7 @@ export function displayTaskProperties(task: Task): void {
|
|||||||
getStatusWithColor(task.status),
|
getStatusWithColor(task.status),
|
||||||
getPriorityWithColor(task.priority),
|
getPriorityWithColor(task.priority),
|
||||||
deps,
|
deps,
|
||||||
typeof task.complexity === 'number'
|
'N/A',
|
||||||
? getComplexityWithColor(task.complexity)
|
|
||||||
: chalk.gray('N/A'),
|
|
||||||
task.description || ''
|
task.description || ''
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
|||||||
@@ -84,23 +84,7 @@ export function getPriorityWithColor(priority: TaskPriority): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get complexity color and label based on score thresholds
|
* Get colored complexity display
|
||||||
*/
|
|
||||||
function getComplexityLevel(score: number): {
|
|
||||||
color: (text: string) => string;
|
|
||||||
label: string;
|
|
||||||
} {
|
|
||||||
if (score >= 7) {
|
|
||||||
return { color: chalk.hex('#CC0000'), label: 'High' };
|
|
||||||
} else if (score >= 4) {
|
|
||||||
return { color: chalk.hex('#FF8800'), label: 'Medium' };
|
|
||||||
} else {
|
|
||||||
return { color: chalk.green, label: 'Low' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get colored complexity display with dot indicator (simple format)
|
|
||||||
*/
|
*/
|
||||||
export function getComplexityWithColor(complexity: number | string): string {
|
export function getComplexityWithColor(complexity: number | string): string {
|
||||||
const score =
|
const score =
|
||||||
@@ -110,20 +94,13 @@ export function getComplexityWithColor(complexity: number | string): string {
|
|||||||
return chalk.gray('N/A');
|
return chalk.gray('N/A');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { color } = getComplexityLevel(score);
|
if (score >= 8) {
|
||||||
return color(`● ${score}`);
|
return chalk.red.bold(`${score} (High)`);
|
||||||
}
|
} else if (score >= 5) {
|
||||||
|
return chalk.yellow(`${score} (Medium)`);
|
||||||
/**
|
} else {
|
||||||
* Get colored complexity display with /10 format (for dashboards)
|
return chalk.green(`${score} (Low)`);
|
||||||
*/
|
|
||||||
export function getComplexityWithScore(complexity: number | undefined): string {
|
|
||||||
if (typeof complexity !== 'number') {
|
|
||||||
return chalk.gray('N/A');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { color, label } = getComplexityLevel(complexity);
|
|
||||||
return color(`${complexity}/10 (${label})`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,12 +323,8 @@ export function createTaskTable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showComplexity) {
|
if (showComplexity) {
|
||||||
// Show complexity score from report if available
|
// Show N/A if no complexity score
|
||||||
if (typeof task.complexity === 'number') {
|
row.push(chalk.gray('N/A'));
|
||||||
row.push(getComplexityWithColor(task.complexity));
|
|
||||||
} else {
|
|
||||||
row.push(chalk.gray('N/A'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table.push(row);
|
table.push(row);
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
## 0.25.4
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [[`af53525`](https://github.com/eyaltoledano/claude-task-master/commit/af53525cbc660a595b67d4bb90d906911c71f45d)]:
|
|
||||||
- task-master-ai@0.27.3
|
|
||||||
|
|
||||||
## 0.25.3
|
## 0.25.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"displayName": "TaskMaster",
|
"displayName": "TaskMaster",
|
||||||
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
||||||
"version": "0.25.4",
|
"version": "0.25.3",
|
||||||
"publisher": "Hamster",
|
"publisher": "Hamster",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
6573
package-lock.json
generated
6573
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "task-master-ai",
|
"name": "task-master-ai",
|
||||||
"version": "0.27.3",
|
"version": "0.27.2",
|
||||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ export class TaskEntity implements Task {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
assignee?: string;
|
assignee?: string;
|
||||||
complexity?: Task['complexity'];
|
complexity?: Task['complexity'];
|
||||||
recommendedSubtasks?: number;
|
|
||||||
expansionPrompt?: string;
|
|
||||||
complexityReasoning?: string;
|
|
||||||
|
|
||||||
constructor(data: Task | (Omit<Task, 'id'> & { id: number | string })) {
|
constructor(data: Task | (Omit<Task, 'id'> & { id: number | string })) {
|
||||||
this.validate(data);
|
this.validate(data);
|
||||||
@@ -65,9 +62,6 @@ export class TaskEntity implements Task {
|
|||||||
this.tags = data.tags;
|
this.tags = data.tags;
|
||||||
this.assignee = data.assignee;
|
this.assignee = data.assignee;
|
||||||
this.complexity = data.complexity;
|
this.complexity = data.complexity;
|
||||||
this.recommendedSubtasks = data.recommendedSubtasks;
|
|
||||||
this.expansionPrompt = data.expansionPrompt;
|
|
||||||
this.complexityReasoning = data.complexityReasoning;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,10 +246,7 @@ export class TaskEntity implements Task {
|
|||||||
actualEffort: this.actualEffort,
|
actualEffort: this.actualEffort,
|
||||||
tags: this.tags,
|
tags: this.tags,
|
||||||
assignee: this.assignee,
|
assignee: this.assignee,
|
||||||
complexity: this.complexity,
|
complexity: this.complexity
|
||||||
recommendedSubtasks: this.recommendedSubtasks,
|
|
||||||
expansionPrompt: this.expansionPrompt,
|
|
||||||
complexityReasoning: this.complexityReasoning
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,12 +61,3 @@ export { getLogger, createLogger, setGlobalLogger } from './logger/index.js';
|
|||||||
|
|
||||||
// Re-export executors
|
// Re-export executors
|
||||||
export * from './executors/index.js';
|
export * from './executors/index.js';
|
||||||
|
|
||||||
// Re-export reports
|
|
||||||
export {
|
|
||||||
ComplexityReportManager,
|
|
||||||
type ComplexityReport,
|
|
||||||
type ComplexityReportMetadata,
|
|
||||||
type ComplexityAnalysis,
|
|
||||||
type TaskComplexityData
|
|
||||||
} from './reports/index.js';
|
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview ComplexityReportManager - Handles loading and managing complexity analysis reports
|
|
||||||
* Follows the same pattern as ConfigManager and AuthManager
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import type {
|
|
||||||
ComplexityReport,
|
|
||||||
ComplexityAnalysis,
|
|
||||||
TaskComplexityData
|
|
||||||
} from './types.js';
|
|
||||||
import { getLogger } from '../logger/index.js';
|
|
||||||
|
|
||||||
const logger = getLogger('ComplexityReportManager');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages complexity analysis reports
|
|
||||||
* Handles loading, caching, and providing complexity data for tasks
|
|
||||||
*/
|
|
||||||
export class ComplexityReportManager {
|
|
||||||
private projectRoot: string;
|
|
||||||
private reportCache: Map<string, ComplexityReport> = new Map();
|
|
||||||
|
|
||||||
constructor(projectRoot: string) {
|
|
||||||
this.projectRoot = projectRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the path to the complexity report file for a given tag
|
|
||||||
*/
|
|
||||||
private getReportPath(tag?: string): string {
|
|
||||||
const reportsDir = path.join(this.projectRoot, '.taskmaster', 'reports');
|
|
||||||
const tagSuffix = tag && tag !== 'master' ? `_${tag}` : '';
|
|
||||||
return path.join(reportsDir, `task-complexity-report${tagSuffix}.json`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load complexity report for a given tag
|
|
||||||
* Results are cached to avoid repeated file reads
|
|
||||||
*/
|
|
||||||
async loadReport(tag?: string): Promise<ComplexityReport | null> {
|
|
||||||
const resolvedTag = tag || 'master';
|
|
||||||
const cacheKey = resolvedTag;
|
|
||||||
|
|
||||||
// Check cache first
|
|
||||||
if (this.reportCache.has(cacheKey)) {
|
|
||||||
return this.reportCache.get(cacheKey)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportPath = this.getReportPath(tag);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if file exists
|
|
||||||
await fs.access(reportPath);
|
|
||||||
|
|
||||||
// Read and parse the report
|
|
||||||
const content = await fs.readFile(reportPath, 'utf-8');
|
|
||||||
const report = JSON.parse(content) as ComplexityReport;
|
|
||||||
|
|
||||||
// Validate basic structure
|
|
||||||
if (!report.meta || !Array.isArray(report.complexityAnalysis)) {
|
|
||||||
logger.warn(
|
|
||||||
`Invalid complexity report structure at ${reportPath}, ignoring`
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache the report
|
|
||||||
this.reportCache.set(cacheKey, report);
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Loaded complexity report for tag '${resolvedTag}' with ${report.complexityAnalysis.length} analyses`
|
|
||||||
);
|
|
||||||
|
|
||||||
return report;
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.code === 'ENOENT') {
|
|
||||||
// File doesn't exist - this is normal, not all projects have complexity reports
|
|
||||||
logger.debug(`No complexity report found for tag '${resolvedTag}'`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other errors (parsing, permissions, etc.)
|
|
||||||
logger.warn(
|
|
||||||
`Failed to load complexity report for tag '${resolvedTag}': ${error.message}`
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get complexity data for a specific task ID
|
|
||||||
*/
|
|
||||||
async getComplexityForTask(
|
|
||||||
taskId: string | number,
|
|
||||||
tag?: string
|
|
||||||
): Promise<TaskComplexityData | null> {
|
|
||||||
const report = await this.loadReport(tag);
|
|
||||||
if (!report) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the analysis for this task
|
|
||||||
const analysis = report.complexityAnalysis.find(
|
|
||||||
(a) => String(a.taskId) === String(taskId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!analysis) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to TaskComplexityData format
|
|
||||||
return {
|
|
||||||
complexityScore: analysis.complexityScore,
|
|
||||||
recommendedSubtasks: analysis.recommendedSubtasks,
|
|
||||||
expansionPrompt: analysis.expansionPrompt,
|
|
||||||
complexityReasoning: analysis.complexityReasoning
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get complexity data for multiple tasks at once
|
|
||||||
* More efficient than calling getComplexityForTask multiple times
|
|
||||||
*/
|
|
||||||
async getComplexityForTasks(
|
|
||||||
taskIds: (string | number)[],
|
|
||||||
tag?: string
|
|
||||||
): Promise<Map<string, TaskComplexityData>> {
|
|
||||||
const result = new Map<string, TaskComplexityData>();
|
|
||||||
const report = await this.loadReport(tag);
|
|
||||||
|
|
||||||
if (!report) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a map for fast lookups
|
|
||||||
const analysisMap = new Map<string, ComplexityAnalysis>();
|
|
||||||
report.complexityAnalysis.forEach((analysis) => {
|
|
||||||
analysisMap.set(String(analysis.taskId), analysis);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Map each task ID to its complexity data
|
|
||||||
taskIds.forEach((taskId) => {
|
|
||||||
const analysis = analysisMap.get(String(taskId));
|
|
||||||
if (analysis) {
|
|
||||||
result.set(String(taskId), {
|
|
||||||
complexityScore: analysis.complexityScore,
|
|
||||||
recommendedSubtasks: analysis.recommendedSubtasks,
|
|
||||||
expansionPrompt: analysis.expansionPrompt,
|
|
||||||
complexityReasoning: analysis.complexityReasoning
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the report cache
|
|
||||||
* @param tag - Specific tag to clear, or undefined to clear all cached reports
|
|
||||||
* Useful when reports are regenerated or modified externally
|
|
||||||
*/
|
|
||||||
clearCache(tag?: string): void {
|
|
||||||
if (tag) {
|
|
||||||
this.reportCache.delete(tag);
|
|
||||||
} else {
|
|
||||||
// Clear all cached reports
|
|
||||||
this.reportCache.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a complexity report exists for a tag
|
|
||||||
*/
|
|
||||||
async hasReport(tag?: string): Promise<boolean> {
|
|
||||||
const reportPath = this.getReportPath(tag);
|
|
||||||
try {
|
|
||||||
await fs.access(reportPath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview Reports module exports
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { ComplexityReportManager } from './complexity-report-manager.js';
|
|
||||||
export type {
|
|
||||||
ComplexityReport,
|
|
||||||
ComplexityReportMetadata,
|
|
||||||
ComplexityAnalysis,
|
|
||||||
TaskComplexityData
|
|
||||||
} from './types.js';
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview Type definitions for complexity analysis reports
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Analysis result for a single task
|
|
||||||
*/
|
|
||||||
export interface ComplexityAnalysis {
|
|
||||||
/** Task ID being analyzed */
|
|
||||||
taskId: string | number;
|
|
||||||
/** Task title */
|
|
||||||
taskTitle: string;
|
|
||||||
/** Complexity score (1-10 scale) */
|
|
||||||
complexityScore: number;
|
|
||||||
/** Recommended number of subtasks */
|
|
||||||
recommendedSubtasks: number;
|
|
||||||
/** AI-generated prompt for task expansion */
|
|
||||||
expansionPrompt: string;
|
|
||||||
/** Reasoning behind the complexity assessment */
|
|
||||||
complexityReasoning: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata about the complexity report
|
|
||||||
*/
|
|
||||||
export interface ComplexityReportMetadata {
|
|
||||||
/** When the report was generated */
|
|
||||||
generatedAt: string;
|
|
||||||
/** Number of tasks analyzed in this run */
|
|
||||||
tasksAnalyzed: number;
|
|
||||||
/** Total number of tasks in the file */
|
|
||||||
totalTasks?: number;
|
|
||||||
/** Total analyses in the report (across all runs) */
|
|
||||||
analysisCount?: number;
|
|
||||||
/** Complexity threshold score used */
|
|
||||||
thresholdScore: number;
|
|
||||||
/** Project name */
|
|
||||||
projectName?: string;
|
|
||||||
/** Whether research mode was used */
|
|
||||||
usedResearch: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complete complexity analysis report
|
|
||||||
*/
|
|
||||||
export interface ComplexityReport {
|
|
||||||
/** Report metadata */
|
|
||||||
meta: ComplexityReportMetadata;
|
|
||||||
/** Array of complexity analyses */
|
|
||||||
complexityAnalysis: ComplexityAnalysis[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complexity data to be attached to a Task
|
|
||||||
*/
|
|
||||||
export interface TaskComplexityData {
|
|
||||||
/** Complexity score (1-10 scale) */
|
|
||||||
complexityScore?: number;
|
|
||||||
/** Recommended number of subtasks */
|
|
||||||
recommendedSubtasks?: number;
|
|
||||||
/** AI-generated expansion prompt */
|
|
||||||
expansionPrompt?: string;
|
|
||||||
/** Reasoning behind the assessment */
|
|
||||||
complexityReasoning?: string;
|
|
||||||
}
|
|
||||||
@@ -135,28 +135,15 @@ export class TaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a single task by ID - delegates to storage layer
|
* Get a single task by ID
|
||||||
*/
|
*/
|
||||||
async getTask(taskId: string, tag?: string): Promise<Task | null> {
|
async getTask(taskId: string, tag?: string): Promise<Task | null> {
|
||||||
// Use provided tag or get active tag
|
const result = await this.getTaskList({
|
||||||
const activeTag = tag || this.getActiveTag();
|
tag,
|
||||||
|
includeSubtasks: true
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
return result.tasks.find((t) => t.id === taskId) || null;
|
||||||
// Delegate to storage layer which handles the specific logic for tasks vs subtasks
|
|
||||||
return await this.storage.loadTask(String(taskId), activeTag);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TaskMasterError(
|
|
||||||
`Failed to get task ${taskId}`,
|
|
||||||
ERROR_CODES.STORAGE_ERROR,
|
|
||||||
{
|
|
||||||
operation: 'getTask',
|
|
||||||
resource: 'task',
|
|
||||||
taskId: String(taskId),
|
|
||||||
tag: activeTag
|
|
||||||
},
|
|
||||||
error as Error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -397,6 +384,16 @@ export class TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Complexity filter
|
||||||
|
if (filter.complexity) {
|
||||||
|
const complexities = Array.isArray(filter.complexity)
|
||||||
|
? filter.complexity
|
||||||
|
: [filter.complexity];
|
||||||
|
if (!task.complexity || !complexities.includes(task.complexity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Search filter
|
// Search filter
|
||||||
if (filter.search) {
|
if (filter.search) {
|
||||||
const searchLower = filter.search.toLowerCase();
|
const searchLower = filter.search.toLowerCase();
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import type {
|
|||||||
import { FormatHandler } from './format-handler.js';
|
import { FormatHandler } from './format-handler.js';
|
||||||
import { FileOperations } from './file-operations.js';
|
import { FileOperations } from './file-operations.js';
|
||||||
import { PathResolver } from './path-resolver.js';
|
import { PathResolver } from './path-resolver.js';
|
||||||
import { ComplexityReportManager } from '../../reports/complexity-report-manager.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File-based storage implementation using a single tasks.json file with separated concerns
|
* File-based storage implementation using a single tasks.json file with separated concerns
|
||||||
@@ -20,13 +19,11 @@ export class FileStorage implements IStorage {
|
|||||||
private formatHandler: FormatHandler;
|
private formatHandler: FormatHandler;
|
||||||
private fileOps: FileOperations;
|
private fileOps: FileOperations;
|
||||||
private pathResolver: PathResolver;
|
private pathResolver: PathResolver;
|
||||||
private complexityManager: ComplexityReportManager;
|
|
||||||
|
|
||||||
constructor(projectPath: string) {
|
constructor(projectPath: string) {
|
||||||
this.formatHandler = new FormatHandler();
|
this.formatHandler = new FormatHandler();
|
||||||
this.fileOps = new FileOperations();
|
this.fileOps = new FileOperations();
|
||||||
this.pathResolver = new PathResolver(projectPath);
|
this.pathResolver = new PathResolver(projectPath);
|
||||||
this.complexityManager = new ComplexityReportManager(projectPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,7 +87,6 @@ export class FileStorage implements IStorage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load tasks from the single tasks.json file for a specific tag
|
* Load tasks from the single tasks.json file for a specific tag
|
||||||
* Enriches tasks with complexity data from the complexity report
|
|
||||||
*/
|
*/
|
||||||
async loadTasks(tag?: string): Promise<Task[]> {
|
async loadTasks(tag?: string): Promise<Task[]> {
|
||||||
const filePath = this.pathResolver.getTasksPath();
|
const filePath = this.pathResolver.getTasksPath();
|
||||||
@@ -98,10 +94,7 @@ export class FileStorage implements IStorage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const rawData = await this.fileOps.readJson(filePath);
|
const rawData = await this.fileOps.readJson(filePath);
|
||||||
const tasks = this.formatHandler.extractTasks(rawData, resolvedTag);
|
return this.formatHandler.extractTasks(rawData, resolvedTag);
|
||||||
|
|
||||||
// Enrich tasks with complexity data
|
|
||||||
return await this.enrichTasksWithComplexity(tasks, resolvedTag);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
return []; // File doesn't exist, return empty array
|
return []; // File doesn't exist, return empty array
|
||||||
@@ -112,65 +105,9 @@ export class FileStorage implements IStorage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a single task by ID from the tasks.json file
|
* Load a single task by ID from the tasks.json file
|
||||||
* Handles both regular tasks and subtasks (with dotted notation like "1.2")
|
|
||||||
*/
|
*/
|
||||||
async loadTask(taskId: string, tag?: string): Promise<Task | null> {
|
async loadTask(taskId: string, tag?: string): Promise<Task | null> {
|
||||||
const tasks = await this.loadTasks(tag);
|
const tasks = await this.loadTasks(tag);
|
||||||
|
|
||||||
// Check if this is a subtask (contains a dot)
|
|
||||||
if (taskId.includes('.')) {
|
|
||||||
const [parentId, subtaskId] = taskId.split('.');
|
|
||||||
const parentTask = tasks.find((t) => String(t.id) === parentId);
|
|
||||||
|
|
||||||
if (!parentTask || !parentTask.subtasks) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subtask = parentTask.subtasks.find(
|
|
||||||
(st) => String(st.id) === subtaskId
|
|
||||||
);
|
|
||||||
if (!subtask) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toFullSubId = (maybeDotId: string | number): string => {
|
|
||||||
const depId = String(maybeDotId);
|
|
||||||
return depId.includes('.') ? depId : `${parentTask.id}.${depId}`;
|
|
||||||
};
|
|
||||||
const resolvedDependencies =
|
|
||||||
subtask.dependencies?.map((dep) => toFullSubId(dep)) ?? [];
|
|
||||||
|
|
||||||
// Return a Task-like object for the subtask with the full dotted ID
|
|
||||||
// Following the same pattern as findTaskById in utils.js
|
|
||||||
const subtaskResult = {
|
|
||||||
...subtask,
|
|
||||||
id: taskId, // Use the full dotted ID
|
|
||||||
title: subtask.title || `Subtask ${subtaskId}`,
|
|
||||||
description: subtask.description || '',
|
|
||||||
status: subtask.status || 'pending',
|
|
||||||
priority: subtask.priority || parentTask.priority || 'medium',
|
|
||||||
dependencies: resolvedDependencies,
|
|
||||||
details: subtask.details || '',
|
|
||||||
testStrategy: subtask.testStrategy || '',
|
|
||||||
subtasks: [],
|
|
||||||
tags: parentTask.tags || [],
|
|
||||||
assignee: subtask.assignee || parentTask.assignee,
|
|
||||||
complexity: subtask.complexity || parentTask.complexity,
|
|
||||||
createdAt: subtask.createdAt || parentTask.createdAt,
|
|
||||||
updatedAt: subtask.updatedAt || parentTask.updatedAt,
|
|
||||||
// Add reference to parent task for context (like utils.js does)
|
|
||||||
parentTask: {
|
|
||||||
id: parentTask.id,
|
|
||||||
title: parentTask.title,
|
|
||||||
status: parentTask.status
|
|
||||||
},
|
|
||||||
isSubtask: true
|
|
||||||
};
|
|
||||||
|
|
||||||
return subtaskResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle regular task lookup
|
|
||||||
return tasks.find((task) => String(task.id) === String(taskId)) || null;
|
return tasks.find((task) => String(task.id) === String(taskId)) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,46 +540,6 @@ export class FileStorage implements IStorage {
|
|||||||
|
|
||||||
await this.saveTasks(tasks, targetTag);
|
await this.saveTasks(tasks, targetTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enrich tasks with complexity data from the complexity report
|
|
||||||
* Private helper method called by loadTasks()
|
|
||||||
*/
|
|
||||||
private async enrichTasksWithComplexity(
|
|
||||||
tasks: Task[],
|
|
||||||
tag: string
|
|
||||||
): Promise<Task[]> {
|
|
||||||
// Get all task IDs for bulk lookup
|
|
||||||
const taskIds = tasks.map((t) => t.id);
|
|
||||||
|
|
||||||
// Load complexity data for all tasks at once (more efficient)
|
|
||||||
const complexityMap = await this.complexityManager.getComplexityForTasks(
|
|
||||||
taskIds,
|
|
||||||
tag
|
|
||||||
);
|
|
||||||
|
|
||||||
// If no complexity data found, return tasks as-is
|
|
||||||
if (complexityMap.size === 0) {
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enrich each task with its complexity data
|
|
||||||
return tasks.map((task) => {
|
|
||||||
const complexityData = complexityMap.get(String(task.id));
|
|
||||||
if (!complexityData) {
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge complexity data into the task
|
|
||||||
return {
|
|
||||||
...task,
|
|
||||||
complexity: complexityData.complexityScore,
|
|
||||||
recommendedSubtasks: complexityData.recommendedSubtasks,
|
|
||||||
expansionPrompt: complexityData.expansionPrompt,
|
|
||||||
complexityReasoning: complexityData.complexityReasoning
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export as default for convenience
|
// Export as default for convenience
|
||||||
|
|||||||
@@ -72,13 +72,7 @@ export interface Task {
|
|||||||
actualEffort?: number;
|
actualEffort?: number;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
assignee?: string;
|
assignee?: string;
|
||||||
|
complexity?: TaskComplexity;
|
||||||
// Complexity analysis (from complexity report)
|
|
||||||
// Can be either enum ('simple' | 'moderate' | 'complex' | 'very-complex') or numeric score (1-10)
|
|
||||||
complexity?: TaskComplexity | number;
|
|
||||||
recommendedSubtasks?: number;
|
|
||||||
expansionPrompt?: string;
|
|
||||||
complexityReasoning?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,6 +145,7 @@ export interface TaskFilter {
|
|||||||
hasSubtasks?: boolean;
|
hasSubtasks?: boolean;
|
||||||
search?: string;
|
search?: string;
|
||||||
assignee?: string;
|
assignee?: string;
|
||||||
|
complexity?: TaskComplexity | TaskComplexity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -93,55 +93,31 @@ function _getProvider(providerName) {
|
|||||||
|
|
||||||
// Helper function to get cost for a specific model
|
// Helper function to get cost for a specific model
|
||||||
function _getCostForModel(providerName, modelId) {
|
function _getCostForModel(providerName, modelId) {
|
||||||
const DEFAULT_COST = {
|
const DEFAULT_COST = { inputCost: 0, outputCost: 0, currency: 'USD' };
|
||||||
inputCost: 0,
|
|
||||||
outputCost: 0,
|
|
||||||
currency: 'USD',
|
|
||||||
isUnknown: false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!MODEL_MAP || !MODEL_MAP[providerName]) {
|
if (!MODEL_MAP || !MODEL_MAP[providerName]) {
|
||||||
log(
|
log(
|
||||||
'warn',
|
'warn',
|
||||||
`Provider "${providerName}" not found in MODEL_MAP. Cannot determine cost for model ${modelId}.`
|
`Provider "${providerName}" not found in MODEL_MAP. Cannot determine cost for model ${modelId}.`
|
||||||
);
|
);
|
||||||
return { ...DEFAULT_COST, isUnknown: true };
|
return DEFAULT_COST;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelData = MODEL_MAP[providerName].find((m) => m.id === modelId);
|
const modelData = MODEL_MAP[providerName].find((m) => m.id === modelId);
|
||||||
|
|
||||||
if (!modelData) {
|
if (!modelData?.cost_per_1m_tokens) {
|
||||||
log(
|
log(
|
||||||
'debug',
|
'debug',
|
||||||
`Model "${modelId}" not found under provider "${providerName}". Assuming unknown cost.`
|
`Cost data not found for model "${modelId}" under provider "${providerName}". Assuming zero cost.`
|
||||||
);
|
);
|
||||||
return { ...DEFAULT_COST, isUnknown: true };
|
return DEFAULT_COST;
|
||||||
}
|
|
||||||
|
|
||||||
// Check if cost_per_1m_tokens is explicitly null (unknown pricing)
|
|
||||||
if (modelData.cost_per_1m_tokens === null) {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`Cost data is null for model "${modelId}" under provider "${providerName}". Pricing unknown.`
|
|
||||||
);
|
|
||||||
return { ...DEFAULT_COST, isUnknown: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if cost_per_1m_tokens is missing/undefined (also unknown)
|
|
||||||
if (modelData.cost_per_1m_tokens === undefined) {
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`Cost data not found for model "${modelId}" under provider "${providerName}". Pricing unknown.`
|
|
||||||
);
|
|
||||||
return { ...DEFAULT_COST, isUnknown: true };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const costs = modelData.cost_per_1m_tokens;
|
const costs = modelData.cost_per_1m_tokens;
|
||||||
return {
|
return {
|
||||||
inputCost: costs.input || 0,
|
inputCost: costs.input || 0,
|
||||||
outputCost: costs.output || 0,
|
outputCost: costs.output || 0,
|
||||||
currency: costs.currency || 'USD',
|
currency: costs.currency || 'USD'
|
||||||
isUnknown: false
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,8 +867,8 @@ async function logAiUsage({
|
|||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const totalTokens = (inputTokens || 0) + (outputTokens || 0);
|
const totalTokens = (inputTokens || 0) + (outputTokens || 0);
|
||||||
|
|
||||||
// Destructure currency along with costs and unknown flag
|
// Destructure currency along with costs
|
||||||
const { inputCost, outputCost, currency, isUnknown } = _getCostForModel(
|
const { inputCost, outputCost, currency } = _getCostForModel(
|
||||||
providerName,
|
providerName,
|
||||||
modelId
|
modelId
|
||||||
);
|
);
|
||||||
@@ -914,8 +890,7 @@ async function logAiUsage({
|
|||||||
outputTokens: outputTokens || 0,
|
outputTokens: outputTokens || 0,
|
||||||
totalTokens,
|
totalTokens,
|
||||||
totalCost,
|
totalCost,
|
||||||
currency, // Add currency to the telemetry data
|
currency // Add currency to the telemetry data
|
||||||
isUnknownCost: isUnknown // Flag to indicate if pricing is unknown
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (getDebugFlag()) {
|
if (getDebugFlag()) {
|
||||||
|
|||||||
@@ -619,29 +619,9 @@ async function tags(
|
|||||||
headers.push(chalk.cyan.bold('Description'));
|
headers.push(chalk.cyan.bold('Description'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate dynamic column widths based on terminal width
|
|
||||||
const terminalWidth = Math.max(process.stdout.columns || 120, 80);
|
|
||||||
const usableWidth = Math.floor(terminalWidth * 0.95);
|
|
||||||
|
|
||||||
let colWidths;
|
|
||||||
if (showMetadata) {
|
|
||||||
// With metadata: Tag Name, Tasks, Completed, Created, Description
|
|
||||||
const widths = [0.25, 0.1, 0.12, 0.15, 0.38];
|
|
||||||
colWidths = widths.map((w, i) =>
|
|
||||||
Math.max(Math.floor(usableWidth * w), i === 0 ? 15 : 8)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Without metadata: Tag Name, Tasks, Completed
|
|
||||||
const widths = [0.7, 0.15, 0.15];
|
|
||||||
colWidths = widths.map((w, i) =>
|
|
||||||
Math.max(Math.floor(usableWidth * w), i === 0 ? 20 : 10)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const table = new Table({
|
const table = new Table({
|
||||||
head: headers,
|
head: headers,
|
||||||
colWidths: colWidths,
|
colWidths: showMetadata ? [20, 10, 12, 15, 50] : [25, 10, 12]
|
||||||
wordWrap: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add rows
|
// Add rows
|
||||||
|
|||||||
@@ -2310,8 +2310,7 @@ function displayAiUsageSummary(telemetryData, outputType = 'cli') {
|
|||||||
outputTokens,
|
outputTokens,
|
||||||
totalTokens,
|
totalTokens,
|
||||||
totalCost,
|
totalCost,
|
||||||
commandName,
|
commandName
|
||||||
isUnknownCost
|
|
||||||
} = telemetryData;
|
} = telemetryData;
|
||||||
|
|
||||||
let summary = chalk.bold.blue('AI Usage Summary:') + '\n';
|
let summary = chalk.bold.blue('AI Usage Summary:') + '\n';
|
||||||
@@ -2321,10 +2320,7 @@ function displayAiUsageSummary(telemetryData, outputType = 'cli') {
|
|||||||
summary += chalk.gray(
|
summary += chalk.gray(
|
||||||
` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n`
|
` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n`
|
||||||
);
|
);
|
||||||
|
summary += chalk.gray(` Est. Cost: $${totalCost.toFixed(6)}`);
|
||||||
// Show "Unknown" if pricing data is not available, otherwise show the cost
|
|
||||||
const costDisplay = isUnknownCost ? 'Unknown' : `$${totalCost.toFixed(6)}`;
|
|
||||||
summary += chalk.gray(` Est. Cost: ${costDisplay}`);
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
boxen(summary, {
|
boxen(summary, {
|
||||||
|
|||||||
@@ -176,19 +176,12 @@ export class BaseAIProvider {
|
|||||||
`${this.name} generateText completed successfully for model: ${params.modelId}`
|
`${this.name} generateText completed successfully for model: ${params.modelId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputTokens =
|
|
||||||
result.usage?.inputTokens ?? result.usage?.promptTokens ?? 0;
|
|
||||||
const outputTokens =
|
|
||||||
result.usage?.outputTokens ?? result.usage?.completionTokens ?? 0;
|
|
||||||
const totalTokens =
|
|
||||||
result.usage?.totalTokens ?? inputTokens + outputTokens;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: result.text,
|
text: result.text,
|
||||||
usage: {
|
usage: {
|
||||||
inputTokens,
|
inputTokens: result.usage?.promptTokens,
|
||||||
outputTokens,
|
outputTokens: result.usage?.completionTokens,
|
||||||
totalTokens
|
totalTokens: result.usage?.totalTokens
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -303,19 +296,12 @@ export class BaseAIProvider {
|
|||||||
`${this.name} generateObject completed successfully for model: ${params.modelId}`
|
`${this.name} generateObject completed successfully for model: ${params.modelId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputTokens =
|
|
||||||
result.usage?.inputTokens ?? result.usage?.promptTokens ?? 0;
|
|
||||||
const outputTokens =
|
|
||||||
result.usage?.outputTokens ?? result.usage?.completionTokens ?? 0;
|
|
||||||
const totalTokens =
|
|
||||||
result.usage?.totalTokens ?? inputTokens + outputTokens;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
object: result.object,
|
object: result.object,
|
||||||
usage: {
|
usage: {
|
||||||
inputTokens,
|
inputTokens: result.usage?.promptTokens,
|
||||||
outputTokens,
|
outputTokens: result.usage?.completionTokens,
|
||||||
totalTokens
|
totalTokens: result.usage?.totalTokens
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user