@saadnvd1/agent-os 0.3.471 → 0.3.473
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/README.md +100 -0
- package/app/api/models/route.ts +34 -0
- package/app/api/models/test/route.ts +17 -0
- package/app/api/sessions/[id]/summarize/route.ts +4 -1
- package/app/api/tasks/[id]/model/route.ts +23 -0
- package/app/api/tasks/route.ts +5 -1
- package/app/api/usage/compare/route.ts +21 -0
- package/app/api/usage/route.ts +7 -4
- package/app/api/workspaces/[id]/models/route.ts +27 -0
- package/components/CommandPalette/useAppCommands.ts +19 -1
- package/components/Draft/DraftChips.tsx +28 -0
- package/components/Draft/DraftPanel.tsx +1 -1
- package/components/Models/ModelSelect.tsx +67 -0
- package/components/Models/ModelsSection.tsx +199 -0
- package/components/Models/ProviderCard.tsx +271 -0
- package/components/SessionList/SidebarHeader.tsx +5 -1
- package/components/Settings/SettingsDialog.test.ts +3 -0
- package/components/Settings/SettingsDialog.tsx +9 -0
- package/components/Settings/commands.ts +19 -0
- package/components/Settings/entry-points.test.ts +1 -0
- package/components/Tasks/ResumeOnModel.tsx +72 -0
- package/components/Tasks/TaskRow.tsx +4 -0
- package/components/Usage/CompareView.tsx +211 -0
- package/components/Usage/ModelSpend.tsx +133 -0
- package/components/Usage/UsageDialog.tsx +38 -3
- package/components/Usage/format.ts +9 -0
- package/components/Workspaces/WorkspaceModelsForm.tsx +71 -0
- package/components/Workspaces/WorkspaceSettings.tsx +2 -0
- package/data/models/index.ts +125 -0
- package/data/usage/index.ts +3 -0
- package/lib/agents/launch.ts +11 -2
- package/lib/chat/context.ts +1 -1
- package/lib/chat/runner.ts +5 -0
- package/lib/chat/worker/extras.ts +12 -0
- package/lib/chat/worker/host.ts +13 -2
- package/lib/chat/worker/main.ts +5 -2
- package/lib/db/migrations.ts +28 -0
- package/lib/db/types.ts +6 -0
- package/lib/drafts.ts +3 -0
- package/lib/models/config.ts +218 -0
- package/lib/models/launch.ts +93 -0
- package/lib/models/plan-step.ts +94 -0
- package/lib/models/providers.ts +163 -0
- package/lib/models/resolve.ts +246 -0
- package/lib/models/resume.ts +99 -0
- package/lib/models/roles.ts +98 -0
- package/lib/models/secrets.ts +86 -0
- package/lib/models/session-model.ts +40 -0
- package/lib/models/test-provider.ts +116 -0
- package/lib/orchestrator/act.ts +13 -1
- package/lib/orchestrator/brief.ts +2 -1
- package/lib/orchestrator/claude-cli.ts +44 -15
- package/lib/orchestrator/home.ts +2 -2
- package/lib/orchestrator/review.ts +2 -0
- package/lib/orchestrator/scope.ts +2 -0
- package/lib/orchestrator/serve.ts +13 -0
- package/lib/orchestrator/tool-names.ts +1 -0
- package/lib/orchestrator/tool-schemas.ts +43 -0
- package/lib/orchestrator/tools.ts +3 -1
- package/lib/pr-generation.ts +5 -2
- package/lib/providers/registry.ts +1 -1
- package/lib/session-titles.ts +1 -1
- package/lib/sessions/launch.ts +19 -4
- package/lib/settings-url.ts +1 -0
- package/lib/tasks/index.ts +59 -8
- package/lib/tasks/queue.ts +14 -5
- package/lib/tasks/start.ts +7 -2
- package/lib/usage/by-model.ts +105 -0
- package/lib/usage/check-runs.ts +5 -2
- package/lib/usage/compare.ts +280 -0
- package/lib/usage/cost.ts +71 -0
- package/lib/usage/report.ts +26 -0
- package/lib/usage/turns.ts +79 -18
- package/package.json +1 -1
- package/stores/usageUi.ts +11 -2
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a turn would cost at a provider's list prices (the price table in
|
|
3
|
+
* Settings > Models). For Anthropic that's the API's price, not what the
|
|
4
|
+
* plan charges: the plan's own measure is its share of the 5-hour window.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ANTHROPIC_ALIASES, type ModelPrice } from "../models/providers";
|
|
8
|
+
import { ANTHROPIC } from "../models/roles";
|
|
9
|
+
|
|
10
|
+
export interface PricedRow {
|
|
11
|
+
provider: string | null;
|
|
12
|
+
model: string | null;
|
|
13
|
+
cost_usd: number;
|
|
14
|
+
input_tokens: number;
|
|
15
|
+
output_tokens: number;
|
|
16
|
+
cache_read_tokens: number;
|
|
17
|
+
cache_write_tokens: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const UNRECORDED = "unrecorded";
|
|
21
|
+
|
|
22
|
+
// The row's provider and model as the table names them: turns from before
|
|
23
|
+
// routing have neither, and ran on Anthropic.
|
|
24
|
+
export function rowModel(r: Pick<PricedRow, "provider" | "model">): {
|
|
25
|
+
provider: string;
|
|
26
|
+
model: string;
|
|
27
|
+
} {
|
|
28
|
+
const provider = r.provider ?? ANTHROPIC;
|
|
29
|
+
const raw = r.model ?? UNRECORDED;
|
|
30
|
+
const model = provider === ANTHROPIC ? (ANTHROPIC_ALIASES[raw] ?? raw) : raw;
|
|
31
|
+
return { provider, model };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Dollars, and whether it came from the table (false: the agent's own
|
|
35
|
+
// figure, for a model the table doesn't price).
|
|
36
|
+
export function estimateCost(
|
|
37
|
+
r: PricedRow,
|
|
38
|
+
prices: Record<string, ModelPrice>
|
|
39
|
+
): { usd: number; priced: boolean } {
|
|
40
|
+
const { provider, model } = rowModel(r);
|
|
41
|
+
const p = prices[`${provider}/${model}`];
|
|
42
|
+
if (!p) return { usd: r.cost_usd, priced: false };
|
|
43
|
+
const usd =
|
|
44
|
+
(r.input_tokens * p.input +
|
|
45
|
+
r.cache_write_tokens * p.cacheWrite +
|
|
46
|
+
r.cache_read_tokens * p.cacheRead +
|
|
47
|
+
r.output_tokens * p.output) /
|
|
48
|
+
1_000_000;
|
|
49
|
+
return { usd, priced: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Each Anthropic turn's share of the plan's 5-hour window, in points: how
|
|
53
|
+
// far the window moved since the turn before it (any session's) ended.
|
|
54
|
+
// Turns overlap, so one turn's share is rough; summed over many it isn't.
|
|
55
|
+
// A drop means the window reset, and all of the new reading is this turn's;
|
|
56
|
+
// the first reading has nothing to measure from.
|
|
57
|
+
export function planShares(
|
|
58
|
+
rows: { at: number; provider: string | null; window_pct: number | null }[]
|
|
59
|
+
): number[] {
|
|
60
|
+
const order = rows.map((r, i) => ({ r, i })).sort((a, b) => a.r.at - b.r.at);
|
|
61
|
+
const out = new Array<number>(rows.length).fill(0);
|
|
62
|
+
let prev: number | null = null;
|
|
63
|
+
for (const { r, i } of order) {
|
|
64
|
+
if ((r.provider ?? ANTHROPIC) !== ANTHROPIC || r.window_pct == null)
|
|
65
|
+
continue;
|
|
66
|
+
const pct = r.window_pct;
|
|
67
|
+
out[i] = prev === null ? 0 : pct < prev ? pct : pct - prev;
|
|
68
|
+
prev = pct;
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
package/lib/usage/report.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { db } from "../db";
|
|
2
|
+
import { effectiveModels } from "../models/config";
|
|
3
|
+
import { LEDGER_SQL } from "./compare";
|
|
4
|
+
import { spendByModel, type ModelTurnRow, type SpendByModel } from "./by-model";
|
|
2
5
|
import { listWorkspaces } from "../workspaces";
|
|
3
6
|
import {
|
|
4
7
|
aggregateUsage,
|
|
@@ -43,3 +46,26 @@ export function checkRunReport(range: UsageRange, now = Date.now()) {
|
|
|
43
46
|
const names = Object.fromEntries(listWorkspaces().map((w) => [w.id, w.name]));
|
|
44
47
|
return aggregateUsage(named, range, names, now);
|
|
45
48
|
}
|
|
49
|
+
|
|
50
|
+
const HOUR = 3600 * 1000;
|
|
51
|
+
|
|
52
|
+
// Spend by model and role in the plan's current 5-hour window (its start
|
|
53
|
+
// from when it resets, or the last five hours) and the last 24 hours.
|
|
54
|
+
export function modelUsage(
|
|
55
|
+
fiveHourResetsIn: number | null | undefined,
|
|
56
|
+
now = Date.now()
|
|
57
|
+
): { fiveHour: SpendByModel; day: SpendByModel } {
|
|
58
|
+
const day = now - 24 * HOUR;
|
|
59
|
+
const fiveHour =
|
|
60
|
+
fiveHourResetsIn != null
|
|
61
|
+
? now + fiveHourResetsIn * 1000 - 5 * HOUR
|
|
62
|
+
: now - 5 * HOUR;
|
|
63
|
+
const rows = db
|
|
64
|
+
.prepare(LEDGER_SQL)
|
|
65
|
+
.all({ from: Math.min(day, fiveHour), to: now }) as ModelTurnRow[];
|
|
66
|
+
const { prices } = effectiveModels();
|
|
67
|
+
return {
|
|
68
|
+
fiveHour: spendByModel(rows, fiveHour, prices),
|
|
69
|
+
day: spendByModel(rows, day, prices),
|
|
70
|
+
};
|
|
71
|
+
}
|
package/lib/usage/turns.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import { db, type Session } from "../db";
|
|
8
8
|
import type { UsageTotals } from "../chat/events";
|
|
9
9
|
import { totalTokens, turnDelta } from "../chat/context";
|
|
10
|
+
import { readUsage } from "../orchestrator/usage";
|
|
11
|
+
import { ANTHROPIC, sessionRole } from "../models/roles";
|
|
10
12
|
|
|
11
13
|
// Where a worker's totals start: a resumed conversation continues from the
|
|
12
14
|
// totals its transcript saved, which the last worker stored; a new one
|
|
@@ -30,12 +32,22 @@ function savedTotals(sessionId: string): UsageTotals | null {
|
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
//
|
|
35
|
+
// The plan's 5-hour window when a turn on it ended, for its share of the
|
|
36
|
+
// plan (lib/usage/compare): unknown when the statusline isn't sampling.
|
|
37
|
+
export function planWindowPct(): number | null {
|
|
38
|
+
const u = readUsage();
|
|
39
|
+
return "window" in u && u.window ? u.window.pct : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Records the turn, with the model, provider and role it ran as, and
|
|
43
|
+
// returns the totals to measure the next one from.
|
|
34
44
|
export function recordTurn(
|
|
35
45
|
session: Pick<Session, "id" | "name" | "workspace_id">,
|
|
36
46
|
prev: UsageTotals | null,
|
|
37
47
|
totals: UsageTotals,
|
|
38
|
-
at = Date.now()
|
|
48
|
+
at = Date.now(),
|
|
49
|
+
durationMs: number | null = null,
|
|
50
|
+
windowPct: () => number | null = planWindowPct
|
|
39
51
|
): UsageTotals {
|
|
40
52
|
const turn = turnDelta(prev, totals);
|
|
41
53
|
db.transaction(() => {
|
|
@@ -46,25 +58,35 @@ export function recordTurn(
|
|
|
46
58
|
// A local command (/context, /mcp) spends nothing.
|
|
47
59
|
if (turn.costUsd <= 0 && totalTokens(turn) <= 0) return;
|
|
48
60
|
const current = db
|
|
49
|
-
.prepare(
|
|
61
|
+
.prepare(
|
|
62
|
+
`SELECT name, workspace_id, model, model_provider, role, task_status,
|
|
63
|
+
plan_phase FROM sessions WHERE id = ?`
|
|
64
|
+
)
|
|
50
65
|
.get(session.id) as
|
|
51
|
-
|
|
|
66
|
+
| Pick<
|
|
67
|
+
Session,
|
|
68
|
+
| "name"
|
|
69
|
+
| "workspace_id"
|
|
70
|
+
| "model"
|
|
71
|
+
| "model_provider"
|
|
72
|
+
| "role"
|
|
73
|
+
| "task_status"
|
|
74
|
+
| "plan_phase"
|
|
75
|
+
>
|
|
52
76
|
| undefined;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
session.id,
|
|
59
|
-
current?.name ?? session.name,
|
|
60
|
-
current?.workspace_id ?? session.workspace_id,
|
|
77
|
+
const provider = current?.model_provider ?? ANTHROPIC;
|
|
78
|
+
insertTurn({
|
|
79
|
+
sessionId: session.id,
|
|
80
|
+
sessionName: current?.name ?? session.name,
|
|
81
|
+
workspaceId: current?.workspace_id ?? session.workspace_id,
|
|
61
82
|
at,
|
|
62
|
-
turn
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
turn,
|
|
84
|
+
model: current?.model ?? null,
|
|
85
|
+
provider,
|
|
86
|
+
role: current ? sessionRole(current) : null,
|
|
87
|
+
durationMs,
|
|
88
|
+
windowPct: provider === ANTHROPIC ? windowPct() : null,
|
|
89
|
+
});
|
|
68
90
|
})();
|
|
69
91
|
return totals;
|
|
70
92
|
}
|
|
@@ -96,3 +118,42 @@ export function recordCompaction(
|
|
|
96
118
|
c.after ?? null
|
|
97
119
|
);
|
|
98
120
|
}
|
|
121
|
+
|
|
122
|
+
export interface TurnRecord {
|
|
123
|
+
sessionId: string;
|
|
124
|
+
sessionName: string;
|
|
125
|
+
workspaceId: string | null;
|
|
126
|
+
at: number;
|
|
127
|
+
turn: UsageTotals;
|
|
128
|
+
model: string | null;
|
|
129
|
+
provider: string;
|
|
130
|
+
role: string | null;
|
|
131
|
+
durationMs: number | null;
|
|
132
|
+
windowPct: number | null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// One row of the usage ledger: a chat turn, or a one-shot check's run.
|
|
136
|
+
export function insertTurn(r: TurnRecord): void {
|
|
137
|
+
const { turn } = r;
|
|
138
|
+
db.prepare(
|
|
139
|
+
`INSERT INTO chat_turns (session_id, session_name, workspace_id, at, cost_usd,
|
|
140
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
141
|
+
model, provider, role, duration_ms, window_pct)
|
|
142
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
143
|
+
).run(
|
|
144
|
+
r.sessionId,
|
|
145
|
+
r.sessionName,
|
|
146
|
+
r.workspaceId,
|
|
147
|
+
r.at,
|
|
148
|
+
turn.costUsd,
|
|
149
|
+
turn.inputTokens,
|
|
150
|
+
turn.outputTokens,
|
|
151
|
+
turn.cacheReadTokens,
|
|
152
|
+
turn.cacheWriteTokens,
|
|
153
|
+
r.model,
|
|
154
|
+
r.provider,
|
|
155
|
+
r.role,
|
|
156
|
+
r.durationMs,
|
|
157
|
+
r.windowPct
|
|
158
|
+
);
|
|
159
|
+
}
|
package/package.json
CHANGED
package/stores/usageUi.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { proxy } from "valtio";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export type UsageTab = "spend" | "models" | "compare";
|
|
4
|
+
|
|
5
|
+
export const usageUi = proxy<{ open: boolean; tab: UsageTab }>({
|
|
6
|
+
open: false,
|
|
7
|
+
tab: "spend",
|
|
8
|
+
});
|
|
4
9
|
|
|
5
10
|
export const usageUiActions = {
|
|
6
|
-
open: () => {
|
|
11
|
+
open: (tab: UsageTab = "spend") => {
|
|
12
|
+
usageUi.tab = tab;
|
|
7
13
|
usageUi.open = true;
|
|
8
14
|
},
|
|
9
15
|
setOpen: (open: boolean) => {
|
|
10
16
|
usageUi.open = open;
|
|
11
17
|
},
|
|
18
|
+
setTab: (tab: UsageTab) => {
|
|
19
|
+
usageUi.tab = tab;
|
|
20
|
+
},
|
|
12
21
|
};
|