101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
|
|
import { compressVerboseText, truncateTextWithHeadAndTail } from '../requestPayloadCompression';
|
||
|
|
import type { ToolOutputStore } from './toolOutputStore';
|
||
|
|
import { redactSecretsForModel } from './modelSecretRedaction';
|
||
|
|
|
||
|
|
export const MAX_LIVE_TERMINAL_STDOUT_CHARS = 24_000;
|
||
|
|
export const MAX_LIVE_TERMINAL_STDERR_CHARS = 12_000;
|
||
|
|
|
||
|
|
export interface TerminalExecuteResult {
|
||
|
|
stdout: string;
|
||
|
|
stderr: string;
|
||
|
|
exitCode: number | null;
|
||
|
|
command?: string;
|
||
|
|
sessionId?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface TerminalOutputHandle {
|
||
|
|
kind: 'terminal-output';
|
||
|
|
sessionId: string;
|
||
|
|
command?: string;
|
||
|
|
totalStdoutChars: number;
|
||
|
|
totalStderrChars: number;
|
||
|
|
handleId?: string;
|
||
|
|
restartPersistenceAvailable?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface FitTerminalExecuteResultOptions {
|
||
|
|
chatSessionId?: string;
|
||
|
|
toolOutputStore?: ToolOutputStore;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function fitTerminalExecuteResultForModel(
|
||
|
|
result: TerminalExecuteResult,
|
||
|
|
options?: FitTerminalExecuteResultOptions,
|
||
|
|
): TerminalExecuteResult {
|
||
|
|
const stdout = truncateTextWithHeadAndTail(
|
||
|
|
redactSecretsForModel(compressVerboseText(result.stdout)),
|
||
|
|
MAX_LIVE_TERMINAL_STDOUT_CHARS,
|
||
|
|
);
|
||
|
|
const stderr = truncateTextWithHeadAndTail(
|
||
|
|
redactSecretsForModel(compressVerboseText(result.stderr)),
|
||
|
|
MAX_LIVE_TERMINAL_STDERR_CHARS,
|
||
|
|
);
|
||
|
|
|
||
|
|
const fitted: TerminalExecuteResult = {
|
||
|
|
...result,
|
||
|
|
command: result.command ? redactSecretsForModel(result.command) : result.command,
|
||
|
|
stdout,
|
||
|
|
stderr,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (
|
||
|
|
stdout.length < result.stdout.length
|
||
|
|
|| stderr.length < result.stderr.length
|
||
|
|
) {
|
||
|
|
const fullContent = [
|
||
|
|
result.command ? `command: ${result.command}` : '',
|
||
|
|
result.stdout ? `stdout:\n${result.stdout}` : '',
|
||
|
|
result.stderr ? `stderr:\n${result.stderr}` : '',
|
||
|
|
].filter(Boolean).join('\n\n');
|
||
|
|
|
||
|
|
let handleId: string | undefined;
|
||
|
|
if (options?.toolOutputStore && options.chatSessionId && fullContent) {
|
||
|
|
handleId = options.toolOutputStore.store({
|
||
|
|
chatSessionId: options.chatSessionId,
|
||
|
|
capabilityId: 'terminal.execute',
|
||
|
|
sessionId: result.sessionId,
|
||
|
|
content: fullContent,
|
||
|
|
}).id;
|
||
|
|
}
|
||
|
|
|
||
|
|
const handle: TerminalOutputHandle = {
|
||
|
|
kind: 'terminal-output',
|
||
|
|
sessionId: result.sessionId ?? 'unknown',
|
||
|
|
command: result.command ? redactSecretsForModel(result.command) : result.command,
|
||
|
|
totalStdoutChars: result.stdout.length,
|
||
|
|
totalStderrChars: result.stderr.length,
|
||
|
|
handleId,
|
||
|
|
restartPersistenceAvailable: false,
|
||
|
|
};
|
||
|
|
fitted.stdout = appendOutputHandleNotice(stdout, handle, 'stdout');
|
||
|
|
if (result.stderr) {
|
||
|
|
fitted.stderr = appendOutputHandleNotice(stderr, handle, 'stderr');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return fitted;
|
||
|
|
}
|
||
|
|
|
||
|
|
function appendOutputHandleNotice(
|
||
|
|
truncated: string,
|
||
|
|
handle: TerminalOutputHandle,
|
||
|
|
stream: 'stdout' | 'stderr',
|
||
|
|
): string {
|
||
|
|
const totalChars = stream === 'stdout' ? handle.totalStdoutChars : handle.totalStderrChars;
|
||
|
|
const handleSuffix = handle.handleId ? ` handleId=${handle.handleId}` : '';
|
||
|
|
const restartSuffix = handle.handleId && handle.restartPersistenceAvailable === false
|
||
|
|
? ' restartPersistence=unavailable (read before closing the app)'
|
||
|
|
: '';
|
||
|
|
return `${truncated}\n\n[output handle: session=${handle.sessionId}${handle.command ? ` command=${handle.command}` : ''} ${stream}=${totalChars} chars truncated for model context${handleSuffix}${restartSuffix}]`;
|
||
|
|
}
|