feat: add buildChildrenIndex and shared formatCost helper
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { buildChildrenIndex } from './subagentCost';
|
||||
import { buildChildrenIndex, formatCost } from './subagentCost';
|
||||
|
||||
function makeSession(id: string, cost: number, parentID?: string): Session {
|
||||
return { id, cost, parentID } as unknown as Session;
|
||||
@@ -15,3 +15,11 @@ describe('buildChildrenIndex', () => {
|
||||
expect(index.get('root')).toEqual([childA, childB]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatCost', () => {
|
||||
test('prefixes with $ and trims trailing zeros', () => {
|
||||
expect(formatCost(1.5)).toBe('$1.5');
|
||||
expect(formatCost(0.0001)).toBe('$0.0001');
|
||||
expect(formatCost(2)).toBe('$2');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
|
||||
// Spend is read against a budget, so it keeps its real precision instead of
|
||||
// collapsing to two decimals. Trailing zeros are dropped so exact values stay
|
||||
// short. Relocated from WorkStatusPrimaryGroup.tsx so both that component and
|
||||
// WorkStatusSubagentsSection share one implementation.
|
||||
const trimZeros = (value: string): string =>
|
||||
(value.includes('.') ? value.replace(/0+$/, '').replace(/\.$/, '') : value);
|
||||
|
||||
export const formatCost = (cost: number): string => `$${trimZeros(cost.toFixed(4))}`;
|
||||
|
||||
/**
|
||||
* Groups a flat live-session list by parentID. One pass, O(n). Sessions
|
||||
* without a parentID (roots) are simply absent as keys — callers look up a
|
||||
* specific id's children via `.get(id) ?? []`.
|
||||
*/
|
||||
export function buildChildrenIndex(sessions: Session[]): Map<string, Session[]> {
|
||||
const index = new Map<string, Session[]>();
|
||||
for (const session of sessions) {
|
||||
const parentID = (session as unknown as { parentID?: string }).parentID;
|
||||
if (!parentID) continue;
|
||||
const existing = index.get(parentID);
|
||||
if (existing) {
|
||||
existing.push(session);
|
||||
} else {
|
||||
index.set(parentID, [session]);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function sessionCost(session: Session | undefined): number {
|
||||
const cost = (session as unknown as { cost?: number } | undefined)?.cost;
|
||||
return typeof cost === 'number' ? cost : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Own cost plus every descendant's cost, recursively. Cycle-guarded with a
|
||||
* visited set: parentID should form a tree, but this does not trust that
|
||||
* invariant blindly (mirrors opencode-session-cost's src/cost.ts).
|
||||
*/
|
||||
export function computeSubtreeCost(
|
||||
id: string,
|
||||
sessionsById: Map<string, Session>,
|
||||
childrenByParent: Map<string, Session[]>,
|
||||
visited: Set<string> = new Set(),
|
||||
): number {
|
||||
if (visited.has(id)) return 0;
|
||||
visited.add(id);
|
||||
|
||||
let total = sessionCost(sessionsById.get(id));
|
||||
const children = childrenByParent.get(id) ?? [];
|
||||
for (const child of children) {
|
||||
total += computeSubtreeCost(child.id, sessionsById, childrenByParent, visited);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
Reference in New Issue
Block a user