@juli-os/ledger 0.0.0-stage → 0.1.1
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.
- package/LICENSE +202 -0
- package/README.md +61 -2
- package/dist/contracts/entities.d.ts +64 -0
- package/dist/contracts/statuses.d.ts +34 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1248 -0
- package/dist/juli-ledger.js +1281 -0
- package/dist/platform/ledger/casefile.d.ts +47 -0
- package/dist/platform/ledger/pricing.d.ts +57 -0
- package/dist/platform/ledger/store.d.ts +98 -0
- package/dist/platform/ledger/usage.d.ts +72 -0
- package/dist/platform/shared/clock.d.ts +9 -0
- package/dist/platform/shared/json.d.ts +10 -0
- package/package.json +39 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { JsonValue } from '../shared/json.ts';
|
|
2
|
+
export interface CaseDecision {
|
|
3
|
+
readonly seq: number;
|
|
4
|
+
readonly action: string;
|
|
5
|
+
readonly by: string;
|
|
6
|
+
readonly note: string;
|
|
7
|
+
readonly at: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ArtifactRef {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly role: string;
|
|
13
|
+
readonly key: string;
|
|
14
|
+
readonly seq: number;
|
|
15
|
+
readonly sha8: string;
|
|
16
|
+
readonly bytes: number;
|
|
17
|
+
}
|
|
18
|
+
export interface CaseFileDoc {
|
|
19
|
+
readonly original: Readonly<Record<string, string>>;
|
|
20
|
+
readonly extract: JsonValue | null;
|
|
21
|
+
readonly executions: readonly JsonValue[];
|
|
22
|
+
readonly draft: JsonValue | null;
|
|
23
|
+
readonly decisions: readonly CaseDecision[];
|
|
24
|
+
readonly references: readonly string[];
|
|
25
|
+
readonly artifacts: readonly ArtifactRef[];
|
|
26
|
+
readonly updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const emptyCaseFile: (now: Date) => CaseFileDoc;
|
|
29
|
+
export declare const parseCaseFile: (raw: string | null, now: Date) => CaseFileDoc;
|
|
30
|
+
export declare const serializeCaseFile: (cf: CaseFileDoc) => string;
|
|
31
|
+
export declare const withOriginal: (cf: CaseFileDoc, o: {
|
|
32
|
+
from: string;
|
|
33
|
+
fromName?: string;
|
|
34
|
+
subject: string;
|
|
35
|
+
body: string;
|
|
36
|
+
messageId: string;
|
|
37
|
+
}, now: Date) => CaseFileDoc;
|
|
38
|
+
export declare const appendSection: (cf: CaseFileDoc, section: "extract" | "draft", value: JsonValue, now: Date) => CaseFileDoc;
|
|
39
|
+
export declare const appendExecution: (cf: CaseFileDoc, execution: JsonValue, now: Date) => CaseFileDoc;
|
|
40
|
+
export declare const appendDecision: (cf: CaseFileDoc, d: Omit<CaseDecision, "at">, now: Date) => CaseFileDoc;
|
|
41
|
+
export declare const appendReference: (cf: CaseFileDoc, messageId: string, now: Date) => CaseFileDoc;
|
|
42
|
+
export declare const originalEmail: (cf: CaseFileDoc) => {
|
|
43
|
+
from: string;
|
|
44
|
+
subject: string;
|
|
45
|
+
body: string;
|
|
46
|
+
messageId: string;
|
|
47
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface TierWhen {
|
|
2
|
+
readonly inputLt?: number;
|
|
3
|
+
readonly inputGte?: number;
|
|
4
|
+
readonly outputLt?: number;
|
|
5
|
+
readonly outputGte?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PriceTier {
|
|
8
|
+
readonly when: TierWhen;
|
|
9
|
+
readonly input: number;
|
|
10
|
+
readonly cache: number | null;
|
|
11
|
+
readonly output: number;
|
|
12
|
+
readonly note: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ModelPrice {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly tiers: readonly PriceTier[];
|
|
18
|
+
}
|
|
19
|
+
export declare const PRICING_SOURCE = "https://docs.bigmodel.cn/cn/guide/start/pricing";
|
|
20
|
+
export declare const PRICING_RETRIEVED_AT = "2026-10-06";
|
|
21
|
+
export declare const BIGMODEL_PRICES: readonly ModelPrice[];
|
|
22
|
+
export interface UsageRow {
|
|
23
|
+
readonly model: string;
|
|
24
|
+
readonly inputTokens: number;
|
|
25
|
+
readonly outputTokens: number;
|
|
26
|
+
readonly cachedTokens: number;
|
|
27
|
+
}
|
|
28
|
+
export interface CostBreakdown {
|
|
29
|
+
readonly cache: number;
|
|
30
|
+
readonly input: number;
|
|
31
|
+
readonly output: number;
|
|
32
|
+
readonly total: number;
|
|
33
|
+
}
|
|
34
|
+
export interface ModelUsageCost {
|
|
35
|
+
readonly model: string;
|
|
36
|
+
readonly label: string;
|
|
37
|
+
readonly free: boolean;
|
|
38
|
+
readonly priced: boolean;
|
|
39
|
+
readonly calls: number;
|
|
40
|
+
readonly inputTokens: number;
|
|
41
|
+
readonly cachedTokens: number;
|
|
42
|
+
readonly inputTokensChargeable: number;
|
|
43
|
+
readonly outputTokens: number;
|
|
44
|
+
readonly tiersHit: readonly string[];
|
|
45
|
+
readonly costCny: CostBreakdown;
|
|
46
|
+
}
|
|
47
|
+
export interface UsageCost {
|
|
48
|
+
readonly currency: 'CNY';
|
|
49
|
+
readonly unit: 'per_1m_tokens';
|
|
50
|
+
readonly pricingSource: string;
|
|
51
|
+
readonly pricingRetrievedAt: string;
|
|
52
|
+
readonly byModel: readonly ModelUsageCost[];
|
|
53
|
+
readonly totalCny: CostBreakdown;
|
|
54
|
+
readonly unpricedModels: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
export declare const findPrice: (model: string) => ModelPrice | null;
|
|
57
|
+
export declare const priceUsage: (rows: readonly UsageRow[]) => UsageCost;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { JsonRecord, JsonValue } from '../shared/json.ts';
|
|
3
|
+
import { type Clock, type IdGen } from '../shared/clock.ts';
|
|
4
|
+
import type { LedgerEvent, Prompt, Run, Step, StepKind, StepStatus, TaskView, Workflow, WorkflowStatus } from '../../contracts/entities.ts';
|
|
5
|
+
import { type CaseFileDoc } from './casefile.ts';
|
|
6
|
+
export interface LifecycleStore {
|
|
7
|
+
readonly db: DatabaseSync;
|
|
8
|
+
createWorkflow(w: {
|
|
9
|
+
kind: string;
|
|
10
|
+
title: string;
|
|
11
|
+
meta?: JsonRecord;
|
|
12
|
+
parentId?: string;
|
|
13
|
+
status?: WorkflowStatus;
|
|
14
|
+
owner?: string;
|
|
15
|
+
}): Workflow;
|
|
16
|
+
getWorkflow(id: string): Workflow | null;
|
|
17
|
+
listWorkflows(limit?: number, status?: string): readonly Workflow[];
|
|
18
|
+
setWorkflowStatus(id: string, status: WorkflowStatus): void;
|
|
19
|
+
setWorkflowSession(id: string, session: string): void;
|
|
20
|
+
updateWorkflowMeta(id: string, patch: JsonRecord): JsonRecord;
|
|
21
|
+
setWorkflowRouting(id: string, session: string, project: string): void;
|
|
22
|
+
assignThread(workflowId: string, threadId: string): void;
|
|
23
|
+
latestByMessageId(messageId: string, excludeWorkflowId?: string): Workflow | null;
|
|
24
|
+
recentBySender(from: string, sinceIso: string, excludeWorkflowId?: string, limit?: number): readonly Workflow[];
|
|
25
|
+
loadCaseFile(id: string): CaseFileDoc;
|
|
26
|
+
saveCaseFile(id: string, cf: CaseFileDoc): void;
|
|
27
|
+
createStep(s: {
|
|
28
|
+
workflowId: string;
|
|
29
|
+
seq: number;
|
|
30
|
+
kind: StepKind;
|
|
31
|
+
title: string;
|
|
32
|
+
input?: JsonRecord;
|
|
33
|
+
}): Step;
|
|
34
|
+
insertStepsAt(workflowId: string, afterSeq: number, steps: readonly {
|
|
35
|
+
kind: StepKind;
|
|
36
|
+
title: string;
|
|
37
|
+
input?: JsonRecord;
|
|
38
|
+
}[]): void;
|
|
39
|
+
getStep(id: string): Step | null;
|
|
40
|
+
stepsForWorkflow(id: string): readonly Step[];
|
|
41
|
+
currentStep(id: string): Step | null;
|
|
42
|
+
setStepStatus(id: string, status: StepStatus): void;
|
|
43
|
+
setStepInput(id: string, patch: JsonRecord): void;
|
|
44
|
+
insertRelay(r: {
|
|
45
|
+
workflowId: string;
|
|
46
|
+
stepId: string;
|
|
47
|
+
session: string;
|
|
48
|
+
text: string;
|
|
49
|
+
marker: string;
|
|
50
|
+
}): string;
|
|
51
|
+
pendingRelays(): readonly {
|
|
52
|
+
id: string;
|
|
53
|
+
workflowId: string;
|
|
54
|
+
stepId: string;
|
|
55
|
+
session: string;
|
|
56
|
+
text: string;
|
|
57
|
+
marker: string;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
attempts: number;
|
|
60
|
+
lastError: string;
|
|
61
|
+
}[];
|
|
62
|
+
markRelayDelivered(id: string): void;
|
|
63
|
+
markRelayAttempt(id: string, error: string): void;
|
|
64
|
+
setStepOutput(id: string, patch: JsonRecord): void;
|
|
65
|
+
appendStepOutputItem(id: string, key: string, entry: JsonValue, cap: number): void;
|
|
66
|
+
claimRunningStep(id: string): boolean;
|
|
67
|
+
listSteps(status: StepStatus, limit?: number): readonly Step[];
|
|
68
|
+
runningAgentStepForSession(session: string): Step | null;
|
|
69
|
+
sessionHasOpenWork(session: string, excludeWorkflowId?: string): boolean;
|
|
70
|
+
appendEvent(entityType: 'workflow' | 'step' | 'judgment' | 'agent', entityId: string, type: string, payload?: JsonRecord): LedgerEvent;
|
|
71
|
+
listEvents(limit?: number, types?: readonly string[]): readonly LedgerEvent[];
|
|
72
|
+
eventsSince(afterId: number, limit?: number): readonly LedgerEvent[];
|
|
73
|
+
entityEvents(entityType: LedgerEvent['entityType'], entityId: string, type: string, limit?: number): readonly LedgerEvent[];
|
|
74
|
+
lastEventAt(entityType: LedgerEvent['entityType'], entityId: string): string;
|
|
75
|
+
createRun(workflowId: string, session: string): Run;
|
|
76
|
+
hasActiveDedup(key: string): boolean;
|
|
77
|
+
hasSeenDedup(key: string): boolean;
|
|
78
|
+
sentMessageIdExists(messageId: string): boolean;
|
|
79
|
+
recordSentMessageId(messageId: string, meta?: {
|
|
80
|
+
to?: string;
|
|
81
|
+
subject?: string;
|
|
82
|
+
}): void;
|
|
83
|
+
activeRunForSession(session: string): Run | null;
|
|
84
|
+
createPrompt(runId: string, content: string, source: string): Prompt;
|
|
85
|
+
latestPromptForSession(session: string, statuses: readonly Prompt['status'][]): Prompt | null;
|
|
86
|
+
prune(retentionDays: number): {
|
|
87
|
+
events: number;
|
|
88
|
+
workflows: number;
|
|
89
|
+
};
|
|
90
|
+
taskViews(limit?: number): {
|
|
91
|
+
readonly views: readonly TaskView[];
|
|
92
|
+
readonly map: Readonly<Record<string, string>>;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export declare const createLedgerStore: (dbPath: string, deps?: {
|
|
96
|
+
readonly clock?: Clock;
|
|
97
|
+
readonly ids?: IdGen;
|
|
98
|
+
}) => LifecycleStore;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type UsageCost } from './pricing.ts';
|
|
2
|
+
export interface UsageRecord {
|
|
3
|
+
readonly id: number;
|
|
4
|
+
readonly source: string;
|
|
5
|
+
readonly note: string;
|
|
6
|
+
readonly model: string;
|
|
7
|
+
readonly inputTokens: number;
|
|
8
|
+
readonly outputTokens: number;
|
|
9
|
+
readonly cachedTokens: number;
|
|
10
|
+
readonly durationMs: number;
|
|
11
|
+
readonly error: string;
|
|
12
|
+
readonly createdAt: string;
|
|
13
|
+
}
|
|
14
|
+
export interface UsageStats {
|
|
15
|
+
readonly totalCalls: number;
|
|
16
|
+
readonly totalInputTokens: number;
|
|
17
|
+
readonly totalOutputTokens: number;
|
|
18
|
+
readonly byModel: readonly {
|
|
19
|
+
readonly model: string;
|
|
20
|
+
readonly calls: number;
|
|
21
|
+
readonly inputTokens: number;
|
|
22
|
+
readonly outputTokens: number;
|
|
23
|
+
}[];
|
|
24
|
+
}
|
|
25
|
+
export interface TimelinePoint {
|
|
26
|
+
readonly hour: string;
|
|
27
|
+
readonly calls: number;
|
|
28
|
+
readonly inputTokens: number;
|
|
29
|
+
readonly outputTokens: number;
|
|
30
|
+
}
|
|
31
|
+
export interface UsageStore {
|
|
32
|
+
record(r: {
|
|
33
|
+
source: string;
|
|
34
|
+
note?: string;
|
|
35
|
+
model?: string;
|
|
36
|
+
inputTokens?: number;
|
|
37
|
+
outputTokens?: number;
|
|
38
|
+
cachedTokens?: number;
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
}): void;
|
|
42
|
+
stats(sinceHours?: number): UsageStats;
|
|
43
|
+
timeline(hours?: number): readonly TimelinePoint[];
|
|
44
|
+
byLocalDay(days?: number): readonly {
|
|
45
|
+
readonly day: string;
|
|
46
|
+
readonly inputTokens: number;
|
|
47
|
+
readonly outputTokens: number;
|
|
48
|
+
readonly cachedTokens: number;
|
|
49
|
+
readonly calls: number;
|
|
50
|
+
}[];
|
|
51
|
+
perSessionWindows(tmuxSession: string, windows: readonly (readonly [string, string])[]): {
|
|
52
|
+
count: number;
|
|
53
|
+
inputTokens: number;
|
|
54
|
+
outputTokens: number;
|
|
55
|
+
};
|
|
56
|
+
perSessionWindowsRows(tmuxSession: string, windows: readonly (readonly [string, string])[]): readonly {
|
|
57
|
+
model: string;
|
|
58
|
+
inputTokens: number;
|
|
59
|
+
outputTokens: number;
|
|
60
|
+
cachedTokens: number;
|
|
61
|
+
}[];
|
|
62
|
+
sessionWindowsPriced(sessions: readonly string[], windows: readonly (readonly [string, string])[]): UsageCost;
|
|
63
|
+
latestInputTokens(tmuxSession: string): number;
|
|
64
|
+
lastClaudeCodeAt(tmuxSession: string): string;
|
|
65
|
+
recent(limit?: number): readonly UsageRecord[];
|
|
66
|
+
recordClaudeSession(claudeSessionId: string, tmuxSession: string, transcriptPath: string, cwd: string): void;
|
|
67
|
+
recordClaudeSessionPrecise(claudeSessionId: string, tmuxSession: string): void;
|
|
68
|
+
ingestProjectTranscripts(claudeProjectsDir: string): number;
|
|
69
|
+
needsClaudeReplay(): boolean;
|
|
70
|
+
resetClaudeIngest(): void;
|
|
71
|
+
}
|
|
72
|
+
export declare const createUsageStore: (dbPath: string, now?: () => Date) => UsageStore;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
|
|
2
|
+
readonly [k: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
export type JsonRecord = {
|
|
5
|
+
readonly [k: string]: JsonValue;
|
|
6
|
+
};
|
|
7
|
+
export declare const safeParse: (raw: string | null | undefined) => JsonValue | null;
|
|
8
|
+
export declare const asRecord: (v: JsonValue | null | undefined) => JsonRecord;
|
|
9
|
+
export declare const asString: (v: JsonValue | undefined) => string;
|
|
10
|
+
export declare const truncate: (s: string, n: number) => string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juli-os/ledger",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "juli-os L1 layer: the ledger of record for agent work — workflows, steps, case files, and token usage in one zero-dependency SQLite layer.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"juli-ledger": "./dist/juli-ledger.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22.5.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.build.json && esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js && esbuild bin/juli-ledger.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/juli-ledger.js && chmod +x dist/juli-ledger.js && python3 scripts/postbuild.py",
|
|
28
|
+
"test": "node --test \"test/*.test.ts\"",
|
|
29
|
+
"smoke:dist": "node --input-type=module -e \"import('./dist/index.js').then(async m => { const { join } = await import('node:path'); const { tmpdir } = await import('node:os'); const s = m.createLedgerStore(join(tmpdir(), 'peel-dist-' + Date.now() + '.db')); const wf = s.createWorkflow({kind:'case',title:'dist smoke'}); if (!s.getWorkflow(wf.id)) process.exit(1); console.log('DIST SMOKE OK'); })\"",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"esbuild": "^0.24.0",
|
|
34
|
+
"typescript": "^5.6.0",
|
|
35
|
+
"@types/node": "^22.0.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"registry": "https://registry.npmjs.org/",
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"license": "Apache-2.0"
|
|
6
42
|
}
|