Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to @blockrun/llm will be documented in this file.

## Unreleased

### Fixed

- **Export `VideoClient` from the package entry point.** The class was
fully implemented in `src/video.ts` and documented in the README
(`import { VideoClient } from '@blockrun/llm'`), but the export was
missing from `src/index.ts`. Downstream consumers (Franklin,
franklin-canvas) had to hand-roll their own x402 + polling loop
against `/v1/videos/generations` even though a working client was
already shipped — they just couldn't reach it. Restores the
promised public surface; no source changes to `video.ts`.

## 2.1.0

### Added
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
export { LLMClient, default } from "./client";
export { ImageClient } from "./image";
export { MusicClient } from "./music";
export { VideoClient } from "./video";
export { SearchClient } from "./search";
export {
XClient,
Expand Down
25 changes: 16 additions & 9 deletions test/unit/cost-log.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import * as path from 'path';
import * as os from 'os';
import { logCost, getCostSummary } from '../../src/cost-log.js';

const COST_LOG = path.join(os.homedir(), '.blockrun', 'data', 'costs.jsonl');
// `src/cost-log.ts` was rewritten to share the on-wire schema with
// Franklin's AgentClient — new path is `~/.blockrun/cost_log.jsonl`,
// new schema uses `ts` / `endpoint` / `cost_usd` instead of the
// previous `timestamp` / `costUsd` / `inputTokens`. These tests track
// the current shape.
const COST_LOG = path.join(os.homedir(), '.blockrun', 'cost_log.jsonl');
const BACKUP = COST_LOG + '.bak';

describe('Cost Log Module', () => {
beforeEach(() => {
// Move any real on-disk log out of the way so tests run against an
// empty file and don't permanently destroy the user's ledger.
if (fs.existsSync(COST_LOG)) {
fs.copyFileSync(COST_LOG, BACKUP);
fs.unlinkSync(COST_LOG);
Expand All @@ -24,18 +31,18 @@ describe('Cost Log Module', () => {

it('should log a cost entry', () => {
logCost({
timestamp: '2026-03-22T10:00:00Z',
ts: Date.now() / 1000,
endpoint: '/v1/chat/completions',
cost_usd: 0.002,
model: 'openai/gpt-5.4',
inputTokens: 100,
outputTokens: 50,
costUsd: 0.002,
});

expect(fs.existsSync(COST_LOG)).toBe(true);
const content = fs.readFileSync(COST_LOG, 'utf-8').trim();
const entry = JSON.parse(content);
expect(entry.model).toBe('openai/gpt-5.4');
expect(entry.costUsd).toBe(0.002);
expect(entry.cost_usd).toBe(0.002);
expect(entry.endpoint).toBe('/v1/chat/completions');
});

it('should return empty summary when no logs', () => {
Expand All @@ -46,9 +53,9 @@ describe('Cost Log Module', () => {
});

it('should summarize multiple entries', () => {
logCost({ timestamp: '', model: 'openai/gpt-5.4', inputTokens: 100, outputTokens: 50, costUsd: 0.01 });
logCost({ timestamp: '', model: 'anthropic/claude-sonnet-4.6', inputTokens: 200, outputTokens: 100, costUsd: 0.02 });
logCost({ timestamp: '', model: 'openai/gpt-5.4', inputTokens: 50, outputTokens: 25, costUsd: 0.005 });
logCost({ ts: 1, endpoint: '/v1/chat/completions', cost_usd: 0.01, model: 'openai/gpt-5.4' });
logCost({ ts: 2, endpoint: '/v1/chat/completions', cost_usd: 0.02, model: 'anthropic/claude-sonnet-4.6' });
logCost({ ts: 3, endpoint: '/v1/chat/completions', cost_usd: 0.005, model: 'openai/gpt-5.4' });

const summary = getCostSummary();
expect(summary.calls).toBe(3);
Expand Down
Loading