Value opencode Go plan requests; per-duration cache prices; usage under answers
- opencode Go reports plan requests as cost 0 with the usage in allowance;
ModelConfig::cost values them at the model's list prices (charged amounts
and free requests stay as reported). Rows booked at 0 are valued once on
start (idempotent).
- opencode's price list now has per-duration cache prices for some models
({"30m": ..., "12h": ...}): the shortest duration is used, also in tiers.
Saving the provider's models failed with "untagged enum ModelEntry".
- Each answer shows tokens sent/received and the cost of the turn's requests
(usage.cost filled when messages are saved and loaded).
- The chat title keeps a minimum width in a crowded top bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
33612bf6d4
commit
1e8418336d
13 files changed
+231
-17
No files matched your search
@@ -245,8 +245,10 @@ const MessageList = memo(function MessageList({
|
||||
baseDir,
|
||||
onRewind,
|
||||
highlight,
|
||||
currency,
|
||||
}: {
|
||||
messages: Message[];
|
||||
currency: string;
|
||||
results: Map<string, ToolResult>;
|
||||
busy: boolean;
|
||||
canEdit: boolean;
|
||||
@@ -280,13 +282,52 @@ const MessageList = memo(function MessageList({
|
||||
));
|
||||
// Finished turns with tool steps fold into one line (the answer stays visible).
|
||||
const steps = b.messages.flatMap((m) => m.parts.filter((p) => p.type === "tool_call")) as ToolCall[];
|
||||
if (i === lastWork || steps.length < 2) return <div key={key}>{items}</div>;
|
||||
return <WorkBlock key={key} messages={b.messages} steps={steps} open={!!highlight && b.messages.some((m) => m.id === highlight)} baseDir={baseDir}>{items}</WorkBlock>;
|
||||
const usage = !(busy && i === lastWork) && <TurnUsage messages={b.messages} currency={currency} />;
|
||||
if (i === lastWork || steps.length < 2)
|
||||
return (
|
||||
<div key={key}>
|
||||
{items}
|
||||
{usage}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div key={key}>
|
||||
<WorkBlock messages={b.messages} steps={steps} open={!!highlight && b.messages.some((m) => m.id === highlight)} baseDir={baseDir}>
|
||||
{items}
|
||||
</WorkBlock>
|
||||
{usage}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
/** Tokens (in/out) and cost of one answer: all requests of the turn together. */
|
||||
function TurnUsage({ messages, currency }: { messages: Message[]; currency: string }) {
|
||||
const t = useT();
|
||||
let inp = 0;
|
||||
let out = 0;
|
||||
let cost = 0;
|
||||
let requests = 0;
|
||||
for (const m of messages) {
|
||||
const u = m.role === "assistant" ? m.usage : undefined;
|
||||
if (!u) continue;
|
||||
requests++;
|
||||
inp += u.input_tokens + u.cache_read_tokens + u.cache_write_tokens;
|
||||
out += u.output_tokens;
|
||||
cost += u.cost ?? 0;
|
||||
}
|
||||
if (!requests) return null;
|
||||
return (
|
||||
<div className="turn-usage" title={t("chat.turnUsageHint", { n: requests })}>
|
||||
{fmtCompact(inp)}
|
||||
<Icon name="arrow-up" className="tiny" /> {fmtCompact(out)}
|
||||
<Icon name="arrow-down" className="tiny" /> · {fmtCost(cost, currency)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** A finished turn: "42 steps · 3 files" and the answer; the steps on click. */
|
||||
function WorkBlock({ messages, steps, open: forced, baseDir, children }: { messages: Message[]; steps: ToolCall[]; open: boolean; baseDir: string; children: ReactNode }) {
|
||||
const t = useT();
|
||||
@@ -825,6 +866,7 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
baseDir={session.cwd}
|
||||
onRewind={onRewind}
|
||||
highlight={highlight}
|
||||
currency={stats?.currency ?? "USD"}
|
||||
/>
|
||||
{win?.newer && (
|
||||
<button className="back-to-latest" onClick={() => void showLatest()}>
|
||||
|
||||
Reference in new issue
Block a user