feat(code-runtime): execute Node programs through confined processes
This commit is contained in:
1 parent
f95f7ec8dc
commit
75ed8da3e0
54 files changed
+1933
-2277
No files matched your search
@@ -0,0 +1,66 @@
|
||||
/** 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 } }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user