feat: sampling knobs + live PTY stream-json + token UI (v2.7.3)
Three small wins from boocode_code_review_v2 §1 #11/#7/#8. #11 sampling knobs: top_n_sigma + dry_* family as first-class Agent fields, threaded into the request body via providerOptions.openaiCompatible. Fixes a latent bug — top_k (rejected by the AI-SDK provider) and min_p (never passed to streamText) were dead on the wire; both now route through the same channel. --reasoning-budget documented in data/AGENTS.md. #7 live PTY stream-json: new stream-json-parser.ts line-buffers qwen/claude NDJSON and emits text/reasoning/tool frames live + persists, with a fallback to the old opaque slice. claude gets --output-format stream-json --verbose. #8 token UI: agent_sessions input/output_tokens/cost now flow through the route + type and render beside the AgentComposerBar session chip. Built by 3 parallel agents. Server 523 + coder 245 tests passing; builds + web tsc clean. Builds on v2.7.2. openspec sampling-streamjson-tokens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -185,6 +185,14 @@ interface Props {
|
||||
hasPriorTurn?: boolean;
|
||||
}
|
||||
|
||||
// Condensed token count: 950 → "950", 12_400 → "12.4K", 3_200_000 → "3.2M".
|
||||
// Sub-1000 stays exact; thousands/millions get one decimal, trailing .0 trimmed.
|
||||
function abbrevTokens(n: number): string {
|
||||
if (!Number.isFinite(n) || n < 1000) return String(Math.max(0, Math.round(n)));
|
||||
if (n < 1_000_000) return `${(n / 1000).toFixed(1).replace(/\.0$/, '')}K`;
|
||||
return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
|
||||
}
|
||||
|
||||
// Relative-time formatter for the resumed-chip title (e.g. "3m ago").
|
||||
function relativeTime(iso: string | null): string {
|
||||
if (!iso) return 'unknown';
|
||||
@@ -353,6 +361,21 @@ export function AgentComposerBar({ projectPath, value, onChange, onProviderComma
|
||||
: { label: 'new session', title: `${value.provider} starts a fresh session this turn` }
|
||||
: null;
|
||||
|
||||
// sampling-streamjson-tokens #8: condensed per-(chat,agent) token/cost readout
|
||||
// beside the session chip. Coerce — input/output are BIGINT (string over wire).
|
||||
// Hidden when no session row or all totals are zero (e.g. native boocode, which
|
||||
// holds no agent_sessions row, or a provider that hasn't run yet).
|
||||
const usageReadout = (() => {
|
||||
if (!sessionChip || !sessionRow) return null;
|
||||
const inTok = Number(sessionRow.input_tokens) || 0;
|
||||
const outTok = Number(sessionRow.output_tokens) || 0;
|
||||
const cost = Number(sessionRow.cost) || 0;
|
||||
if (inTok <= 0 && outTok <= 0 && cost <= 0) return null;
|
||||
const parts = [`${abbrevTokens(inTok)} in`, `${abbrevTokens(outTok)} out`];
|
||||
if (cost > 0) parts.push(`$${cost.toFixed(2)}`);
|
||||
return parts.join(' · ');
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1 px-2 py-1 border-b border-border bg-muted/20 shrink-0">
|
||||
<CompactPicker
|
||||
@@ -374,6 +397,14 @@ export function AgentComposerBar({ projectPath, value, onChange, onProviderComma
|
||||
{sessionChip.label}
|
||||
</span>
|
||||
)}
|
||||
{usageReadout && (
|
||||
<span
|
||||
className="text-[10px] text-muted-foreground tabular-nums whitespace-nowrap shrink-0"
|
||||
title="Tokens in · out · cost for this agent session"
|
||||
>
|
||||
{usageReadout}
|
||||
</span>
|
||||
)}
|
||||
<CompactPicker
|
||||
label="Mode"
|
||||
value={value.modeId ?? ''}
|
||||
|
||||
Reference in New Issue
Block a user