@outputai/cli 0.7.1-next.ae5bab4.0 → 0.7.1-next.bd6bd49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,8 @@ import packageJson from '../../package.json' with { type: 'json' };
6
6
  const execFile = promisify(execFileCb);
7
7
  const debug = debugFactory('output-cli:npm-update');
8
8
  const PACKAGE_NAME = packageJson.name;
9
+ const REGISTRY_URL = 'https://registry.npmjs.org';
10
+ const REGISTRY_TIMEOUT_MS = 5000;
9
11
  /* eslint-disable @typescript-eslint/no-explicit-any */
10
12
  function findVersionInTree(deps) {
11
13
  if (!deps) {
@@ -33,9 +35,15 @@ function parseNpmLsVersion(output) {
33
35
  }
34
36
  export async function fetchLatestVersion() {
35
37
  try {
36
- const { stdout } = await execFile('npm', ['view', PACKAGE_NAME, 'version']);
37
- const version = stdout.trim();
38
- return version || null;
38
+ const response = await fetch(`${REGISTRY_URL}/${PACKAGE_NAME}/latest`, {
39
+ signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS)
40
+ });
41
+ if (!response.ok) {
42
+ debug('Registry responded with status %d', response.status);
43
+ return null;
44
+ }
45
+ const data = await response.json();
46
+ return data.version || null;
39
47
  }
40
48
  catch (error) {
41
49
  debug('Failed to fetch latest version: %O', error);
@@ -8,24 +8,37 @@ vi.mock('node:child_process', () => ({
8
8
  vi.mock('node:util', () => ({
9
9
  promisify: vi.fn(() => mockExecFile)
10
10
  }));
11
+ const mockFetch = vi.fn();
12
+ vi.stubGlobal('fetch', mockFetch);
11
13
  describe('npm_update_service', () => {
12
14
  beforeEach(() => {
13
15
  vi.clearAllMocks();
14
16
  });
15
17
  describe('fetchLatestVersion', () => {
16
- it('should return version from npm view output', async () => {
17
- mockExecFile.mockResolvedValue({ stdout: '1.2.3\n' });
18
+ it('should return version from the registry response', async () => {
19
+ mockFetch.mockResolvedValue({
20
+ ok: true,
21
+ json: async () => ({ version: '1.2.3' })
22
+ });
18
23
  const result = await fetchLatestVersion();
19
24
  expect(result).toBe('1.2.3');
20
- expect(mockExecFile).toHaveBeenCalledWith('npm', ['view', '@outputai/cli', 'version']);
25
+ expect(mockFetch).toHaveBeenCalledWith('https://registry.npmjs.org/@outputai/cli/latest', { signal: expect.any(AbortSignal) });
21
26
  });
22
- it('should return null on empty output', async () => {
23
- mockExecFile.mockResolvedValue({ stdout: '' });
27
+ it('should return null on non-ok response', async () => {
28
+ mockFetch.mockResolvedValue({ ok: false, status: 404 });
29
+ const result = await fetchLatestVersion();
30
+ expect(result).toBeNull();
31
+ });
32
+ it('should return null when response has no version', async () => {
33
+ mockFetch.mockResolvedValue({
34
+ ok: true,
35
+ json: async () => ({})
36
+ });
24
37
  const result = await fetchLatestVersion();
25
38
  expect(result).toBeNull();
26
39
  });
27
- it('should return null on whitespace-only output', async () => {
28
- mockExecFile.mockResolvedValue({ stdout: ' \n' });
40
+ it('should return null on network failure or timeout', async () => {
41
+ mockFetch.mockRejectedValue(new Error('aborted'));
29
42
  const result = await fetchLatestVersion();
30
43
  expect(result).toBeNull();
31
44
  });
@@ -3,4 +3,22 @@ export interface VersionCheckResult {
3
3
  currentVersion: string;
4
4
  latestVersion: string;
5
5
  }
6
- export declare function checkForUpdate(currentVersion: string, cacheDir?: string): Promise<VersionCheckResult>;
6
+ export declare function readCachedResult(currentVersion: string, cacheDir: string): Promise<VersionCheckResult | null>;
7
+ /**
8
+ * Fetches the latest published version and persists the comparison to the
9
+ * cache file. Skips the write and returns false when the latest version
10
+ * can't be determined so the next invocation retries.
11
+ */
12
+ export declare function refreshVersionCheck(currentVersion: string, cacheDir: string): Promise<boolean>;
13
+ /**
14
+ * Entry point for the detached refresh helper. Validates the argv contract
15
+ * (`<currentVersion> <cacheDir>`) and returns the process exit code:
16
+ * 0 on success, 1 on bad args, 2 when the latest version couldn't be fetched.
17
+ */
18
+ export declare function runRefresh(argv: string[]): Promise<number>;
19
+ /**
20
+ * Refreshes the version-check cache in a detached child process so the
21
+ * registry roundtrip never blocks the invoked command. The result is picked
22
+ * up from the cache on the next invocation.
23
+ */
24
+ export declare function spawnBackgroundRefresh(currentVersion: string, cacheDir: string): void;
@@ -1,9 +1,13 @@
1
1
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import debugFactory from 'debug';
3
6
  import { fetchLatestVersion, isOutdated } from '#services/npm_update_service.js';
7
+ const debug = debugFactory('output-cli:version-check');
4
8
  const CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours
5
9
  const CACHE_FILENAME = 'version_check.json';
6
- async function readCache(cacheDir, currentVersion) {
10
+ export async function readCachedResult(currentVersion, cacheDir) {
7
11
  try {
8
12
  const raw = await readFile(join(cacheDir, CACHE_FILENAME), 'utf-8');
9
13
  const cache = JSON.parse(raw);
@@ -15,7 +19,8 @@ async function readCache(cacheDir, currentVersion) {
15
19
  }
16
20
  return cache.result;
17
21
  }
18
- catch {
22
+ catch (error) {
23
+ debug('Failed to read version cache: %O', error);
19
24
  return null;
20
25
  }
21
26
  }
@@ -25,28 +30,59 @@ async function writeCache(cacheDir, result) {
25
30
  const cache = { timestamp: Date.now(), result };
26
31
  await writeFile(join(cacheDir, CACHE_FILENAME), JSON.stringify(cache));
27
32
  }
28
- catch {
29
- // Silently ignore cache write failures
33
+ catch (error) {
34
+ debug('Failed to write version cache: %O', error);
30
35
  }
31
36
  }
32
- export async function checkForUpdate(currentVersion, cacheDir) {
33
- if (cacheDir) {
34
- const cached = await readCache(cacheDir, currentVersion);
35
- if (cached) {
36
- return cached;
37
- }
38
- }
37
+ /**
38
+ * Fetches the latest published version and persists the comparison to the
39
+ * cache file. Skips the write and returns false when the latest version
40
+ * can't be determined so the next invocation retries.
41
+ */
42
+ export async function refreshVersionCheck(currentVersion, cacheDir) {
39
43
  const latestVersion = await fetchLatestVersion();
40
44
  if (!latestVersion) {
41
- return { updateAvailable: false, currentVersion, latestVersion: currentVersion };
45
+ debug('Latest version unavailable, skipping cache write');
46
+ return false;
42
47
  }
43
- const result = {
48
+ await writeCache(cacheDir, {
44
49
  updateAvailable: isOutdated(currentVersion, latestVersion),
45
50
  currentVersion,
46
51
  latestVersion
47
- };
48
- if (cacheDir) {
49
- await writeCache(cacheDir, result);
52
+ });
53
+ return true;
54
+ }
55
+ /**
56
+ * Entry point for the detached refresh helper. Validates the argv contract
57
+ * (`<currentVersion> <cacheDir>`) and returns the process exit code:
58
+ * 0 on success, 1 on bad args, 2 when the latest version couldn't be fetched.
59
+ */
60
+ export async function runRefresh(argv) {
61
+ const [, , currentVersion, cacheDir] = argv;
62
+ if (!currentVersion || !cacheDir) {
63
+ console.error('Usage: refresh_version_check.js <currentVersion> <cacheDir>');
64
+ return 1;
65
+ }
66
+ return await refreshVersionCheck(currentVersion, cacheDir) ? 0 : 2;
67
+ }
68
+ /**
69
+ * Refreshes the version-check cache in a detached child process so the
70
+ * registry roundtrip never blocks the invoked command. The result is picked
71
+ * up from the cache on the next invocation.
72
+ */
73
+ export function spawnBackgroundRefresh(currentVersion, cacheDir) {
74
+ try {
75
+ const scriptPath = fileURLToPath(new URL('../scripts/refresh_version_check.js', import.meta.url));
76
+ // stdio is discarded in normal use; surface the child's output when
77
+ // debugging is on so refresh failures are diagnosable
78
+ const stdio = debugFactory.enabled('output-cli:version-check') ? 'inherit' : 'ignore';
79
+ spawn(process.execPath, [scriptPath, currentVersion, cacheDir], {
80
+ detached: true,
81
+ stdio
82
+ }).unref();
83
+ }
84
+ catch (error) {
85
+ // Best-effort: a failed refresh only delays the update banner
86
+ debug('Failed to spawn background version refresh: %O', error);
50
87
  }
51
- return result;
52
88
  }
@@ -1,7 +1,8 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { checkForUpdate } from './version_check.js';
2
+ import { readCachedResult, refreshVersionCheck, runRefresh, spawnBackgroundRefresh } from './version_check.js';
3
3
  import { fetchLatestVersion, isOutdated } from '#services/npm_update_service.js';
4
4
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
5
+ import { spawn } from 'node:child_process';
5
6
  vi.mock('#services/npm_update_service.js', () => ({
6
7
  fetchLatestVersion: vi.fn(),
7
8
  isOutdated: vi.fn()
@@ -11,96 +12,125 @@ vi.mock('node:fs/promises', () => ({
11
12
  writeFile: vi.fn(),
12
13
  mkdir: vi.fn()
13
14
  }));
15
+ vi.mock('node:child_process', () => ({
16
+ spawn: vi.fn()
17
+ }));
18
+ const { mockDebugEnabled } = vi.hoisted(() => ({ mockDebugEnabled: vi.fn() }));
19
+ vi.mock('debug', () => ({
20
+ default: Object.assign(vi.fn(() => vi.fn()), { enabled: mockDebugEnabled })
21
+ }));
14
22
  describe('version_check', () => {
23
+ const cacheDir = '/tmp/test-cache';
15
24
  beforeEach(() => {
16
25
  vi.clearAllMocks();
26
+ mockDebugEnabled.mockReturnValue(false);
17
27
  });
18
- describe('checkForUpdate', () => {
19
- it('should return updateAvailable true when outdated', async () => {
20
- vi.mocked(fetchLatestVersion).mockResolvedValue('1.0.0');
21
- vi.mocked(isOutdated).mockReturnValue(true);
22
- const result = await checkForUpdate('0.8.4');
23
- expect(result).toEqual({
24
- updateAvailable: true,
25
- currentVersion: '0.8.4',
26
- latestVersion: '1.0.0'
27
- });
28
- });
29
- it('should return updateAvailable false when up to date', async () => {
30
- vi.mocked(fetchLatestVersion).mockResolvedValue('0.8.4');
31
- vi.mocked(isOutdated).mockReturnValue(false);
32
- const result = await checkForUpdate('0.8.4');
33
- expect(result).toEqual({
34
- updateAvailable: false,
35
- currentVersion: '0.8.4',
36
- latestVersion: '0.8.4'
37
- });
38
- });
39
- it('should return updateAvailable false when fetch fails', async () => {
40
- vi.mocked(fetchLatestVersion).mockResolvedValue(null);
41
- const result = await checkForUpdate('0.8.4');
42
- expect(result.updateAvailable).toBe(false);
43
- expect(isOutdated).not.toHaveBeenCalled();
44
- });
45
- });
46
- describe('caching', () => {
47
- const cacheDir = '/tmp/test-cache';
28
+ describe('readCachedResult', () => {
48
29
  it('should return cached result when cache is fresh', async () => {
49
30
  const cached = {
50
31
  timestamp: Date.now() - 1000,
51
32
  result: { updateAvailable: true, currentVersion: '0.8.4', latestVersion: '1.0.0' }
52
33
  };
53
34
  vi.mocked(readFile).mockResolvedValue(JSON.stringify(cached));
54
- const result = await checkForUpdate('0.8.4', cacheDir);
35
+ const result = await readCachedResult('0.8.4', cacheDir);
55
36
  expect(result).toEqual(cached.result);
56
- expect(fetchLatestVersion).not.toHaveBeenCalled();
57
37
  });
58
- it('should fetch fresh result when cache is expired', async () => {
38
+ it('should return null when cache is expired', async () => {
59
39
  const cached = {
60
40
  timestamp: Date.now() - (5 * 60 * 60 * 1000), // 5 hours ago
61
41
  result: { updateAvailable: true, currentVersion: '0.8.4', latestVersion: '0.9.0' }
62
42
  };
63
43
  vi.mocked(readFile).mockResolvedValue(JSON.stringify(cached));
64
- vi.mocked(fetchLatestVersion).mockResolvedValue('1.0.0');
65
- vi.mocked(isOutdated).mockReturnValue(true);
66
- const result = await checkForUpdate('0.8.4', cacheDir);
67
- expect(fetchLatestVersion).toHaveBeenCalled();
68
- expect(result.latestVersion).toBe('1.0.0');
44
+ expect(await readCachedResult('0.8.4', cacheDir)).toBeNull();
69
45
  });
70
- it('should fetch fresh result when cached version differs from current', async () => {
46
+ it('should return null when cached version differs from current', async () => {
71
47
  const cached = {
72
48
  timestamp: Date.now() - 1000,
73
49
  result: { updateAvailable: true, currentVersion: '0.8.4', latestVersion: '1.0.0' }
74
50
  };
75
51
  vi.mocked(readFile).mockResolvedValue(JSON.stringify(cached));
76
- vi.mocked(fetchLatestVersion).mockResolvedValue('1.0.0');
77
- vi.mocked(isOutdated).mockReturnValue(true);
78
- const result = await checkForUpdate('0.9.0', cacheDir);
79
- expect(fetchLatestVersion).toHaveBeenCalled();
80
- expect(result.currentVersion).toBe('0.9.0');
52
+ expect(await readCachedResult('0.9.0', cacheDir)).toBeNull();
81
53
  });
82
- it('should fetch fresh result when cache file is missing', async () => {
54
+ it('should return null when cache file is missing', async () => {
83
55
  vi.mocked(readFile).mockRejectedValue(new Error('ENOENT'));
84
- vi.mocked(fetchLatestVersion).mockResolvedValue('1.0.0');
85
- vi.mocked(isOutdated).mockReturnValue(true);
86
- const result = await checkForUpdate('0.8.4', cacheDir);
87
- expect(fetchLatestVersion).toHaveBeenCalled();
88
- expect(result.latestVersion).toBe('1.0.0');
56
+ expect(await readCachedResult('0.8.4', cacheDir)).toBeNull();
89
57
  });
90
- it('should write cache after fresh fetch', async () => {
91
- vi.mocked(readFile).mockRejectedValue(new Error('ENOENT'));
58
+ it('should return null when cache file is corrupt', async () => {
59
+ vi.mocked(readFile).mockResolvedValue('not json');
60
+ expect(await readCachedResult('0.8.4', cacheDir)).toBeNull();
61
+ });
62
+ });
63
+ describe('refreshVersionCheck', () => {
64
+ it('should write comparison result to the cache', async () => {
92
65
  vi.mocked(fetchLatestVersion).mockResolvedValue('1.0.0');
93
66
  vi.mocked(isOutdated).mockReturnValue(true);
94
- await checkForUpdate('0.8.4', cacheDir);
67
+ await expect(refreshVersionCheck('0.8.4', cacheDir)).resolves.toBe(true);
95
68
  expect(mkdir).toHaveBeenCalledWith(cacheDir, { recursive: true });
96
- expect(writeFile).toHaveBeenCalled();
69
+ expect(writeFile).toHaveBeenCalledTimes(1);
70
+ const written = JSON.parse(vi.mocked(writeFile).mock.calls[0][1]);
71
+ expect(written.result).toEqual({
72
+ updateAvailable: true,
73
+ currentVersion: '0.8.4',
74
+ latestVersion: '1.0.0'
75
+ });
76
+ // A missing/invalid timestamp would make the cache immortal (NaN > TTL is false)
77
+ expect(written.timestamp).toBeCloseTo(Date.now(), -3);
78
+ });
79
+ it('should skip the cache write when fetch fails so the next invocation retries', async () => {
80
+ vi.mocked(fetchLatestVersion).mockResolvedValue(null);
81
+ await expect(refreshVersionCheck('0.8.4', cacheDir)).resolves.toBe(false);
82
+ expect(isOutdated).not.toHaveBeenCalled();
83
+ expect(writeFile).not.toHaveBeenCalled();
97
84
  });
98
- it('should skip caching when no cacheDir provided', async () => {
85
+ });
86
+ describe('runRefresh', () => {
87
+ it('should refresh the cache from argv in spawn order (script, currentVersion, cacheDir)', async () => {
99
88
  vi.mocked(fetchLatestVersion).mockResolvedValue('1.0.0');
100
89
  vi.mocked(isOutdated).mockReturnValue(true);
101
- await checkForUpdate('0.8.4');
102
- expect(readFile).not.toHaveBeenCalled();
90
+ const exitCode = await runRefresh(['node', 'refresh_version_check.js', '0.8.4', cacheDir]);
91
+ expect(exitCode).toBe(0);
92
+ expect(mkdir).toHaveBeenCalledWith(cacheDir, { recursive: true });
93
+ const written = JSON.parse(vi.mocked(writeFile).mock.calls[0][1]);
94
+ expect(written.result.currentVersion).toBe('0.8.4');
95
+ expect(written.result.latestVersion).toBe('1.0.0');
96
+ });
97
+ it('should exit with a distinct code when the latest version cannot be fetched', async () => {
98
+ vi.mocked(fetchLatestVersion).mockResolvedValue(null);
99
+ const exitCode = await runRefresh(['node', 'refresh_version_check.js', '0.8.4', cacheDir]);
100
+ expect(exitCode).toBe(2);
101
+ expect(writeFile).not.toHaveBeenCalled();
102
+ });
103
+ it('should exit non-zero with usage on missing args without fetching', async () => {
104
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
105
+ const exitCode = await runRefresh(['node', 'refresh_version_check.js']);
106
+ expect(exitCode).toBe(1);
107
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Usage:'));
108
+ expect(fetchLatestVersion).not.toHaveBeenCalled();
103
109
  expect(writeFile).not.toHaveBeenCalled();
110
+ errorSpy.mockRestore();
111
+ });
112
+ });
113
+ describe('spawnBackgroundRefresh', () => {
114
+ it('should spawn a detached unref-ed helper with version and cache dir args', () => {
115
+ const unref = vi.fn();
116
+ vi.mocked(spawn).mockReturnValue({ unref });
117
+ spawnBackgroundRefresh('0.8.4', cacheDir);
118
+ expect(spawn).toHaveBeenCalledWith(process.execPath, [expect.stringContaining('refresh_version_check.js'), '0.8.4', cacheDir], { detached: true, stdio: 'ignore' });
119
+ expect(unref).toHaveBeenCalled();
120
+ });
121
+ it('should inherit stdio when the version-check debug namespace is enabled', () => {
122
+ mockDebugEnabled.mockReturnValue(true);
123
+ const unref = vi.fn();
124
+ vi.mocked(spawn).mockReturnValue({ unref });
125
+ spawnBackgroundRefresh('0.8.4', cacheDir);
126
+ expect(mockDebugEnabled).toHaveBeenCalledWith('output-cli:version-check');
127
+ expect(spawn).toHaveBeenCalledWith(process.execPath, expect.any(Array), { detached: true, stdio: 'inherit' });
128
+ });
129
+ it('should swallow spawn failures', () => {
130
+ vi.mocked(spawn).mockImplementation(() => {
131
+ throw new Error('spawn failure');
132
+ });
133
+ expect(() => spawnBackgroundRefresh('0.8.4', cacheDir)).not.toThrow();
104
134
  });
105
135
  });
106
136
  });
@@ -2,6 +2,10 @@
2
2
  * Cost Calculator Types
3
3
  *
4
4
  * TypeScript interfaces for trace parsing, pricing configuration, and cost reports.
5
+ *
6
+ * Costs are sourced from the trace events themselves (the as-charged "original"
7
+ * cost), with `costs.yml` applied as an optional override layer (the "adjusted"
8
+ * cost). Both figures are surfaced per model and per host.
5
9
  */
6
10
  export interface TokenUsage {
7
11
  inputTokens?: number;
@@ -9,6 +13,25 @@ export interface TokenUsage {
9
13
  cachedInputTokens?: number;
10
14
  reasoningTokens?: number;
11
15
  }
16
+ export type { LLMUsageEvent } from '@outputai/llm';
17
+ import type { LLMUsageEvent } from '@outputai/llm';
18
+ export type LLMUsageLine = LLMUsageEvent['usage'][number];
19
+ export interface HTTPCostEvent {
20
+ type: 'http:request:cost';
21
+ url: string;
22
+ requestId: string;
23
+ total: number;
24
+ }
25
+ export interface HTTPCountEvent {
26
+ type: 'http:request:count';
27
+ url: string;
28
+ requestId: string;
29
+ }
30
+ export interface NodeAttributes {
31
+ 'llm:usage'?: LLMUsageEvent;
32
+ 'http:request:cost'?: HTTPCostEvent;
33
+ 'http:request:count'?: HTTPCountEvent;
34
+ }
12
35
  export interface TraceNode {
13
36
  id?: string;
14
37
  kind: string;
@@ -17,15 +40,16 @@ export interface TraceNode {
17
40
  endedAt?: number;
18
41
  children?: TraceNode[];
19
42
  input?: Record<string, unknown>;
20
- output?: Record<string, unknown> & {
21
- usage?: TokenUsage;
22
- };
43
+ output?: Record<string, unknown>;
44
+ attributes?: NodeAttributes;
23
45
  }
24
46
  export interface LLMCall {
25
47
  stepName: string;
26
48
  llmName: string;
27
49
  model: string;
28
50
  usage: TokenUsage;
51
+ originalCost: number;
52
+ lines: LLMUsageLine[];
29
53
  }
30
54
  export interface HTTPCall {
31
55
  stepName: string;
@@ -34,6 +58,8 @@ export interface HTTPCall {
34
58
  input: Record<string, unknown>;
35
59
  output: Record<string, unknown>;
36
60
  status?: number;
61
+ host: string;
62
+ originalCost?: number;
37
63
  }
38
64
  export interface ModelPricing {
39
65
  provider: string;
@@ -80,47 +106,60 @@ export interface LLMCostResult {
80
106
  output: number;
81
107
  cached: number;
82
108
  reasoning: number;
83
- cost: number;
84
- warning?: string;
109
+ originalCost: number;
110
+ adjustedCost: number;
85
111
  }
86
112
  export interface ServiceCostResult {
87
113
  step: string;
88
114
  cost: number;
89
115
  usage: string;
116
+ kind: 'computed' | 'estimated' | 'failed';
90
117
  model?: string;
91
118
  endpoint?: string;
92
119
  warning?: string;
93
120
  details?: Record<string, unknown>;
94
121
  }
95
- export interface ServiceCostSummary {
96
- serviceName: string;
97
- calls: ServiceCostResult[];
98
- totalCost: number;
122
+ export interface HTTPCostResult {
123
+ step: string;
124
+ host: string;
125
+ usage: string;
126
+ originalCost: number;
127
+ adjustedCost: number;
128
+ }
129
+ export interface HostCostSummary {
130
+ host: string;
131
+ calls: HTTPCostResult[];
132
+ originalTotalCost: number;
133
+ adjustedTotalCost: number;
99
134
  }
100
135
  export interface CostReport {
101
136
  traceFile: string;
102
137
  workflowName: string;
103
138
  durationMs: number | null;
104
139
  llmCalls: LLMCostResult[];
105
- llmTotalCost: number;
140
+ llmOriginalCost: number;
141
+ llmAdjustedCost: number;
106
142
  totalInputTokens: number;
107
143
  totalOutputTokens: number;
108
144
  totalCachedTokens: number;
109
145
  totalReasoningTokens: number;
110
- unknownModels: string[];
111
- services: ServiceCostSummary[];
112
- serviceTotalCost: number;
146
+ httpCosts: HostCostSummary[];
147
+ httpOriginalCost: number;
148
+ httpAdjustedCost: number;
149
+ originalTotalCost: number;
113
150
  totalCost: number;
114
151
  }
115
152
  export interface LLMModelSummary {
116
153
  model: string;
117
154
  count: number;
118
- cost: number;
155
+ originalCost: number;
156
+ adjustedCost: number;
119
157
  }
120
- export interface ServiceSummary {
121
- serviceName: string;
158
+ export interface HostSummary {
159
+ host: string;
122
160
  callCount: number;
123
- cost: number;
161
+ originalCost: number;
162
+ adjustedCost: number;
124
163
  }
125
164
  export interface VerboseFlags {
126
165
  hasReasoning: boolean;
@@ -132,18 +171,20 @@ export interface ParsedCostData {
132
171
  duration: string;
133
172
  llmModels: LLMModelSummary[];
134
173
  llmTotalCalls: number;
135
- llmTotalCost: number;
136
- services: ServiceSummary[];
137
- serviceTotalCalls: number;
138
- serviceTotalCost: number;
174
+ llmOriginalCost: number;
175
+ llmAdjustedCost: number;
176
+ hosts: HostSummary[];
177
+ httpTotalCalls: number;
178
+ httpOriginalCost: number;
179
+ httpAdjustedCost: number;
139
180
  verbose: VerboseFlags;
140
181
  llmCalls: LLMCostResult[];
141
- serviceDetails: ServiceCostSummary[];
182
+ httpDetails: HostCostSummary[];
142
183
  totalInputTokens: number;
143
184
  totalOutputTokens: number;
144
185
  totalCachedTokens: number;
145
186
  totalReasoningTokens: number;
187
+ originalTotalCost: number;
146
188
  totalCost: number;
147
- unknownModels: string[];
148
189
  isEmpty: boolean;
149
190
  }
@@ -2,5 +2,9 @@
2
2
  * Cost Calculator Types
3
3
  *
4
4
  * TypeScript interfaces for trace parsing, pricing configuration, and cost reports.
5
+ *
6
+ * Costs are sourced from the trace events themselves (the as-charged "original"
7
+ * cost), with `costs.yml` applied as an optional override layer (the "adjusted"
8
+ * cost). Both figures are surfaced per model and per host.
5
9
  */
6
10
  export {};