Isolate turn audio hydration with async local context

This commit is contained in:
William Valentin
2026-02-22 21:41:49 -08:00
parent a761813375
commit b477705806
+46 -39
View File
@@ -10,6 +10,7 @@ import type { OutboundAttachmentCollector } from './attachments.js';
import { buildUserMessage } from '../../models/media.js';
import { getElevationWindow } from '../../security/elevation.js';
import { auditLogger } from '../../audit/index.js';
import { AsyncLocalStorage } from 'node:async_hooks';
export interface ToolUseEvent {
type: 'start' | 'end';
@@ -83,6 +84,10 @@ export interface NativeAgentTurnAudioInput {
mime_type?: string;
}
interface NativeAgentRunContext {
turnAudioInput?: AudioToolInput;
}
export class NativeAgent {
private static readonly EMPTY_RESPONSE_FALLBACK =
'I could not generate a response for that. Please try again.';
@@ -106,7 +111,7 @@ export class NativeAgent {
private _runInProgress = false;
private _runAbortController?: AbortController;
private modelTimeoutMs: number;
private _currentTurnAudioInput?: AudioToolInput;
private readonly _runContext = new AsyncLocalStorage<NativeAgentRunContext>();
constructor(config: NativeAgentConfig) {
this.modelClient = config.modelClient;
@@ -134,48 +139,49 @@ export class NativeAgent {
): Promise<string> {
this._cancelRequested = false;
this._runAbortController = new AbortController();
this._currentTurnAudioInput = this.normalizeTurnAudioInput(turnAudioInput) ?? this.extractLatestAudioInputFromAttachments(attachments);
const normalizedTurnAudioInput = this.normalizeTurnAudioInput(turnAudioInput)
?? this.extractLatestAudioInputFromAttachments(attachments);
if ('clearAbort' in this.modelClient && typeof this.modelClient.clearAbort === 'function') {
this.modelClient.clearAbort();
}
this._runInProgress = true;
return await this._runContext.run({ turnAudioInput: normalizedTurnAudioInput }, async () => {
// Detect and strip !!think prefix for per-message thinking mode
try {
if (userMessage.startsWith('!!think ') || userMessage === '!!think') {
this._thinking = true;
userMessage = userMessage.replace(/^!!think\s*/, '').trim() || 'Think about this.';
} else {
this._thinking = false;
}
// Detect and strip !!think prefix for per-message thinking mode
try {
if (userMessage.startsWith('!!think ') || userMessage === '!!think') {
this._thinking = true;
userMessage = userMessage.replace(/^!!think\s*/, '').trim() || 'Think about this.';
} else {
this._thinking = false;
const userMsg = buildUserMessage(userMessage, attachments);
if (this.session) {
this.session.addMessage(userMsg);
} else {
this.inMemoryHistory.push(userMsg);
}
// If no tools configured, use the simple single-turn path
if (!this.toolRegistry || !this.toolExecutor) {
return await this.singleTurn();
}
return await this.toolLoop();
} catch (error) {
if (this.isAbortError(error)) {
const cancelledMsg = 'Operation cancelled by user.';
this.addToHistory({ role: 'assistant', content: cancelledMsg });
return cancelledMsg;
}
throw error;
} finally {
this._runInProgress = false;
this._cancelRequested = false;
this._runAbortController = undefined;
}
const userMsg = buildUserMessage(userMessage, attachments);
if (this.session) {
this.session.addMessage(userMsg);
} else {
this.inMemoryHistory.push(userMsg);
}
// If no tools configured, use the simple single-turn path
if (!this.toolRegistry || !this.toolExecutor) {
return await this.singleTurn();
}
return await this.toolLoop();
} catch (error) {
if (this.isAbortError(error)) {
const cancelledMsg = 'Operation cancelled by user.';
this.addToHistory({ role: 'assistant', content: cancelledMsg });
return cancelledMsg;
}
throw error;
} finally {
this._runInProgress = false;
this._cancelRequested = false;
this._runAbortController = undefined;
this._currentTurnAudioInput = undefined;
}
});
}
private async singleTurn(): Promise<string> {
@@ -662,8 +668,9 @@ export class NativeAgent {
: {};
const original = this.summarizeAudioToolArgs(args);
if (this._currentTurnAudioInput) {
this.applyAudioToolInput(args, this._currentTurnAudioInput);
const runTurnAudioInput = this._runContext.getStore()?.turnAudioInput;
if (runTurnAudioInput) {
this.applyAudioToolInput(args, runTurnAudioInput);
this.logAudioArgsRewrite('latest_audio_preferred', 'latest_turn', original, args);
return args;
}