Files
deepseek-harness/packages/code-runtime/code-runtime-node/src/output-ledger.ts
T

67 lines
3.1 KiB
TypeScript

/** Combined byte accounting for retained program output. */
import type { CodeJsonValue, CodeRunFailure, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import { jsonStringBytesUpTo, jsonValueBytesUpTo, truncateJsonStringBytes } from './output-json.ts'
/** One run's combined outer-output ledger; binding values never enter it. */
export class OutputLedger {
private bytes = 2 // JSON serialization of the empty logs array: []
private entries = 0
constructor(private readonly maxBytes: number) {}
/** Admit one exact log entry, or report that the hard cap was crossed. */
admit(text: string, sink: string[]): boolean {
const separatorBytes = this.entries > 0 ? 1 : 0
const stringBytes = jsonStringBytesUpTo(text, this.maxBytes - this.bytes - separatorBytes)
if (stringBytes === undefined) return false
this.bytes += stringBytes + separatorBytes
this.entries += 1
sink.push(text)
return true
}
/** Finalize a successful absent-or-JSON completion against the combined cap. */
success(logs: string[], value?: CodeJsonValue): CodeRunResult {
if (value !== undefined && jsonValueBytesUpTo(value, this.maxBytes - this.bytes) === undefined) return this.limit(logs)
return { logs, ...value !== undefined ? { value } : {} }
}
/** Finalize a failure diagnostic, with output-limit taking precedence when combined bytes exceed the cap. */
failure(logs: string[], error: CodeRunFailure): CodeRunResult {
if (jsonStringBytesUpTo(error.message, this.maxBytes - this.bytes) === undefined) return this.limit(logs)
return { logs, error }
}
/** Build the explicit output-limit failure while retaining a fitting prefix of the final log. */
limit(logs: string[]): CodeRunResult {
const fullMessage = `outer output exceeded ${this.maxBytes} bytes`
// The fixed diagnostic is ASCII, so every character is one byte plus the quotes.
const messageBytes = fullMessage.length + 2
const retained: string[] = []
let retainedBytes = 2
const logBudget = this.maxBytes - messageBytes
for (const text of logs) {
const separatorBytes = retained.length > 0 ? 1 : 0
const availableBytes = logBudget - retainedBytes - separatorBytes
const stringBytes = jsonStringBytesUpTo(text, availableBytes)
if (stringBytes !== undefined) {
retained.push(text)
retainedBytes += stringBytes + separatorBytes
continue
}
const prefix = truncateJsonStringBytes(text, availableBytes)
if (prefix.length > 0) {
const prefixBytes = jsonStringBytesUpTo(prefix, availableBytes)
/* v8 ignore next -- truncateJsonStringBytes guarantees its returned prefix fits the same budget. */
if (prefixBytes === undefined) throw new Error('output ledger produced an oversized log prefix')
retained.push(prefix)
retainedBytes += prefixBytes + separatorBytes
}
break
}
const availableMessageBytes = this.maxBytes - retainedBytes
const message = truncateJsonStringBytes(fullMessage, availableMessageBytes)
return { logs: retained, error: { kind: 'output-limit', message } }
}
}