feat(FN-4389): complete Step 1 — extend token usage mapping
Fusion-Task-Id: FN-4389 Fusion-Task-Lineage: 366f9d57-c476-448f-96c0-6e753fc8b090
This commit is contained in:
@@ -24,7 +24,8 @@ describe("TaskStore", () => {
|
||||
inputTokens: 120,
|
||||
outputTokens: 45,
|
||||
cachedTokens: 30,
|
||||
totalTokens: 195,
|
||||
cacheWriteTokens: 9,
|
||||
totalTokens: 204,
|
||||
firstUsedAt: "2026-04-23T10:00:00.000Z",
|
||||
lastUsedAt: "2026-04-23T10:05:00.000Z",
|
||||
};
|
||||
@@ -47,7 +48,8 @@ describe("TaskStore", () => {
|
||||
inputTokens: 210,
|
||||
outputTokens: 80,
|
||||
cachedTokens: 40,
|
||||
totalTokens: 330,
|
||||
cacheWriteTokens: 15,
|
||||
totalTokens: 345,
|
||||
firstUsedAt: "2026-04-23T12:00:00.000Z",
|
||||
lastUsedAt: "2026-04-23T12:30:00.000Z",
|
||||
};
|
||||
@@ -70,7 +72,8 @@ describe("TaskStore", () => {
|
||||
inputTokens: 300,
|
||||
outputTokens: 120,
|
||||
cachedTokens: 50,
|
||||
totalTokens: 470,
|
||||
cacheWriteTokens: 25,
|
||||
totalTokens: 495,
|
||||
firstUsedAt: "2026-04-23T13:00:00.000Z",
|
||||
lastUsedAt: "2026-04-23T13:45:00.000Z",
|
||||
};
|
||||
@@ -87,6 +90,27 @@ describe("TaskStore", () => {
|
||||
expect(reloaded.tokenUsage).toEqual(tokenUsage);
|
||||
});
|
||||
|
||||
it("round-trips cacheWriteTokens specifically", async () => {
|
||||
const tokenUsage = {
|
||||
inputTokens: 1,
|
||||
outputTokens: 2,
|
||||
cachedTokens: 3,
|
||||
cacheWriteTokens: 1234,
|
||||
totalTokens: 1240,
|
||||
firstUsedAt: "2026-04-23T15:00:00.000Z",
|
||||
lastUsedAt: "2026-04-23T15:01:00.000Z",
|
||||
};
|
||||
|
||||
const task = await harness.store().createTask({
|
||||
description: "Cache write token round-trip",
|
||||
tokenUsage,
|
||||
});
|
||||
|
||||
const detail = await harness.store().getTask(task.id);
|
||||
expect(detail.tokenUsage?.cacheWriteTokens).toBe(1234);
|
||||
expect(detail.tokenUsage).toEqual(tokenUsage);
|
||||
});
|
||||
|
||||
it("clears token usage via null update and keeps it absent after reload", async () => {
|
||||
// Cross-instance persistence test — see counterpart above.
|
||||
harness.store().close();
|
||||
@@ -98,7 +122,8 @@ describe("TaskStore", () => {
|
||||
inputTokens: 99,
|
||||
outputTokens: 44,
|
||||
cachedTokens: 11,
|
||||
totalTokens: 154,
|
||||
cacheWriteTokens: 3,
|
||||
totalTokens: 157,
|
||||
firstUsedAt: "2026-04-23T14:00:00.000Z",
|
||||
lastUsedAt: "2026-04-23T14:01:00.000Z",
|
||||
},
|
||||
|
||||
@@ -86,6 +86,7 @@ interface TaskRow {
|
||||
tokenUsageInputTokens: number | null;
|
||||
tokenUsageOutputTokens: number | null;
|
||||
tokenUsageCachedTokens: number | null;
|
||||
tokenUsageCacheWriteTokens: number | null;
|
||||
tokenUsageTotalTokens: number | null;
|
||||
tokenUsageFirstUsedAt: string | null;
|
||||
tokenUsageLastUsedAt: string | null;
|
||||
@@ -1015,6 +1016,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
row.tokenUsageInputTokens === null
|
||||
|| row.tokenUsageOutputTokens === null
|
||||
|| row.tokenUsageCachedTokens === null
|
||||
|| row.tokenUsageCacheWriteTokens === null
|
||||
|| row.tokenUsageTotalTokens === null
|
||||
|| row.tokenUsageFirstUsedAt === null
|
||||
|| row.tokenUsageLastUsedAt === null
|
||||
@@ -1026,6 +1028,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
inputTokens: row.tokenUsageInputTokens,
|
||||
outputTokens: row.tokenUsageOutputTokens,
|
||||
cachedTokens: row.tokenUsageCachedTokens,
|
||||
cacheWriteTokens: row.tokenUsageCacheWriteTokens,
|
||||
totalTokens: row.tokenUsageTotalTokens,
|
||||
firstUsedAt: row.tokenUsageFirstUsedAt,
|
||||
lastUsedAt: row.tokenUsageLastUsedAt,
|
||||
@@ -1331,7 +1334,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
"planningModelProvider", "planningModelId",
|
||||
"mergeRetries", "workflowStepRetries", "stuckKillCount", "postReviewFixCount", "recoveryRetryCount", "taskDoneRetryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "nextRecoveryAt",
|
||||
"error", "summary", "thinkingLevel", "executionMode",
|
||||
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt",
|
||||
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageCacheWriteTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt",
|
||||
"createdAt", "updatedAt", "columnMovedAt", "executionStartedAt", "executionCompletedAt",
|
||||
"dependencies", "steps", "comments", "review", "reviewState", "workflowStepResults", "steeringComments",
|
||||
"attachments", "prInfo", "issueInfo", "githubTracking", "sourceIssueProvider", "sourceIssueRepository", "sourceIssueExternalIssueId", "sourceIssueNumber", "sourceIssueUrl", "mergeDetails",
|
||||
@@ -1380,7 +1383,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
"planningModelProvider", "planningModelId",
|
||||
"mergeRetries", "workflowStepRetries", "stuckKillCount", "postReviewFixCount", "recoveryRetryCount", "taskDoneRetryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "nextRecoveryAt",
|
||||
"error", "summary", "thinkingLevel", "executionMode",
|
||||
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt",
|
||||
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageCacheWriteTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt",
|
||||
"createdAt", "updatedAt", "columnMovedAt", "executionStartedAt", "executionCompletedAt",
|
||||
"dependencies", "steps", "attachments", "steeringComments",
|
||||
"comments", "review", "reviewState", "workflowStepResults", "prInfo", "issueInfo", "githubTracking", "sourceIssueProvider", "sourceIssueRepository", "sourceIssueExternalIssueId", "sourceIssueNumber", "sourceIssueUrl", "mergeDetails",
|
||||
@@ -1455,6 +1458,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
task.tokenUsage?.inputTokens ?? null,
|
||||
task.tokenUsage?.outputTokens ?? null,
|
||||
task.tokenUsage?.cachedTokens ?? null,
|
||||
task.tokenUsage?.cacheWriteTokens ?? null,
|
||||
task.tokenUsage?.totalTokens ?? null,
|
||||
task.tokenUsage?.firstUsedAt ?? null,
|
||||
task.tokenUsage?.lastUsedAt ?? null,
|
||||
@@ -1522,14 +1526,14 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
||||
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, nextRecoveryAt, error,
|
||||
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
||||
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
||||
tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
||||
executionStartedAt, executionCompletedAt,
|
||||
dependencies, steps, log, attachments, steeringComments,
|
||||
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
||||
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
||||
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
`).run(...this.getTaskPersistValues(task));
|
||||
this.db.bumpLastModified();
|
||||
@@ -1547,14 +1551,14 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
||||
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, mergeAuditBounceCount, nextRecoveryAt, error,
|
||||
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
||||
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
||||
tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
||||
executionStartedAt, executionCompletedAt,
|
||||
dependencies, steps, log, attachments, steeringComments,
|
||||
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
||||
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
||||
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
lineageId = excluded.lineageId,
|
||||
@@ -1597,6 +1601,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
tokenUsageInputTokens = excluded.tokenUsageInputTokens,
|
||||
tokenUsageOutputTokens = excluded.tokenUsageOutputTokens,
|
||||
tokenUsageCachedTokens = excluded.tokenUsageCachedTokens,
|
||||
tokenUsageCacheWriteTokens = excluded.tokenUsageCacheWriteTokens,
|
||||
tokenUsageTotalTokens = excluded.tokenUsageTotalTokens,
|
||||
tokenUsageFirstUsedAt = excluded.tokenUsageFirstUsedAt,
|
||||
tokenUsageLastUsedAt = excluded.tokenUsageLastUsedAt,
|
||||
|
||||
@@ -1100,9 +1100,11 @@ export interface TaskTokenUsage {
|
||||
inputTokens: number;
|
||||
/** Cumulative completion/output tokens consumed by the task. */
|
||||
outputTokens: number;
|
||||
/** Cumulative cache-hit tokens reported by providers. */
|
||||
/** Cumulative cache-read (cache hit) tokens reported by providers. */
|
||||
cachedTokens: number;
|
||||
/** Cumulative total tokens for the task (input + output + cached semantics per provider reporting). */
|
||||
/** Cumulative cache-write tokens reported by providers. */
|
||||
cacheWriteTokens: number;
|
||||
/** Cumulative total tokens for the task (input + output + cache-read + cache-write). */
|
||||
totalTokens: number;
|
||||
/** ISO-8601 timestamp of the first recorded usage event for this task. */
|
||||
firstUsedAt: string;
|
||||
@@ -4165,7 +4167,7 @@ export interface AgentHeartbeatRun {
|
||||
/** Session ID after execution */
|
||||
sessionIdAfter?: string;
|
||||
/** Token usage for this run */
|
||||
usageJson?: { inputTokens: number; outputTokens: number; cachedTokens: number };
|
||||
usageJson?: { inputTokens: number; outputTokens: number; cachedTokens: number; cacheWriteTokens: number };
|
||||
/** Structured result from the run */
|
||||
resultJson?: Record<string, unknown>;
|
||||
/** Snapshot of context at run start (taskId, projectId, etc.).
|
||||
|
||||
Reference in New Issue
Block a user