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:
@@ -88,6 +88,12 @@ interface ParsedFrontmatter {
|
||||
top_k?: number;
|
||||
min_p?: number;
|
||||
presence_penalty?: number;
|
||||
// v2.6 sampling-streamjson-tokens (#11): llama.cpp sampler extensions.
|
||||
top_n_sigma?: number;
|
||||
dry_multiplier?: number;
|
||||
dry_base?: number;
|
||||
dry_allowed_length?: number;
|
||||
dry_penalty_last_n?: number;
|
||||
tools?: string[];
|
||||
description?: string;
|
||||
model?: string;
|
||||
@@ -178,6 +184,63 @@ function parseFrontmatter(yaml: string): { data: ParsedFrontmatter; errors: stri
|
||||
} else {
|
||||
errors.push(`presence_penalty must be a number (got "${valueRaw}")`);
|
||||
}
|
||||
} else if (key === 'top_n_sigma') {
|
||||
// v2.6 #11: llama.cpp top-n-sigma sampler. Float ≥ 0 (typical 0-3).
|
||||
// Mirrors top_p/min_p: store then warn on out-of-range (non-numeric
|
||||
// hard-fails the block).
|
||||
const n = Number(valueRaw);
|
||||
if (Number.isFinite(n)) {
|
||||
data.top_n_sigma = n;
|
||||
if (n < 0) {
|
||||
console.warn(`agents: top_n_sigma ${n} out of range (≥0), ignoring (falling back to default)`);
|
||||
}
|
||||
} else {
|
||||
errors.push(`top_n_sigma must be a number (got "${valueRaw}")`);
|
||||
}
|
||||
} else if (key === 'dry_multiplier') {
|
||||
// v2.6 #11: DRY repetition-penalty multiplier. Float ≥ 0 (0 disables DRY).
|
||||
const n = Number(valueRaw);
|
||||
if (Number.isFinite(n)) {
|
||||
data.dry_multiplier = n;
|
||||
if (n < 0) {
|
||||
console.warn(`agents: dry_multiplier ${n} out of range (≥0), ignoring (falling back to default)`);
|
||||
}
|
||||
} else {
|
||||
errors.push(`dry_multiplier must be a number (got "${valueRaw}")`);
|
||||
}
|
||||
} else if (key === 'dry_base') {
|
||||
// v2.6 #11: DRY penalty growth base. Float ≥ 0.
|
||||
const n = Number(valueRaw);
|
||||
if (Number.isFinite(n)) {
|
||||
data.dry_base = n;
|
||||
if (n < 0) {
|
||||
console.warn(`agents: dry_base ${n} out of range (≥0), ignoring (falling back to default)`);
|
||||
}
|
||||
} else {
|
||||
errors.push(`dry_base must be a number (got "${valueRaw}")`);
|
||||
}
|
||||
} else if (key === 'dry_allowed_length') {
|
||||
// v2.6 #11: DRY max sequence length not penalized. Integer ≥ 0.
|
||||
const n = Number(valueRaw);
|
||||
if (Number.isInteger(n)) {
|
||||
data.dry_allowed_length = n;
|
||||
if (n < 0) {
|
||||
console.warn(`agents: dry_allowed_length ${n} out of range (≥0), ignoring (falling back to default)`);
|
||||
}
|
||||
} else {
|
||||
errors.push(`dry_allowed_length must be an integer (got "${valueRaw}")`);
|
||||
}
|
||||
} else if (key === 'dry_penalty_last_n') {
|
||||
// v2.6 #11: DRY lookback window. Integer ≥ -1 (-1 = whole context, 0 = off).
|
||||
const n = Number(valueRaw);
|
||||
if (Number.isInteger(n)) {
|
||||
data.dry_penalty_last_n = n;
|
||||
if (n < -1) {
|
||||
console.warn(`agents: dry_penalty_last_n ${n} out of range (≥-1), ignoring (falling back to default)`);
|
||||
}
|
||||
} else {
|
||||
errors.push(`dry_penalty_last_n must be an integer (got "${valueRaw}")`);
|
||||
}
|
||||
} else if (key === 'tools') {
|
||||
if (valueRaw === '') {
|
||||
data.tools = [];
|
||||
@@ -354,6 +417,11 @@ function parseAgentSection(section: RawSection): Omit<Agent, 'source'> {
|
||||
top_k: typeof fm.top_k === 'number' ? fm.top_k : null,
|
||||
min_p: typeof fm.min_p === 'number' ? fm.min_p : null,
|
||||
presence_penalty: typeof fm.presence_penalty === 'number' ? fm.presence_penalty : null,
|
||||
top_n_sigma: typeof fm.top_n_sigma === 'number' ? fm.top_n_sigma : null,
|
||||
dry_multiplier: typeof fm.dry_multiplier === 'number' ? fm.dry_multiplier : null,
|
||||
dry_base: typeof fm.dry_base === 'number' ? fm.dry_base : null,
|
||||
dry_allowed_length: typeof fm.dry_allowed_length === 'number' ? fm.dry_allowed_length : null,
|
||||
dry_penalty_last_n: typeof fm.dry_penalty_last_n === 'number' ? fm.dry_penalty_last_n : null,
|
||||
tools: filteredTools,
|
||||
model: typeof fm.model === 'string' && fm.model.length > 0 ? fm.model : null,
|
||||
max_tool_calls: typeof fm.max_tool_calls === 'number' ? fm.max_tool_calls : null,
|
||||
|
||||
Reference in New Issue
Block a user