fix: add resetAbortController/getAbortSignal to QueryEngine (from upstream CCB)
This commit is contained in:
parent
4a99c68408
commit
3013b05191
|
|
@ -86,7 +86,9 @@ import {
|
||||||
|
|
||||||
// Lazy: MessageSelector.tsx pulls React/ink; only needed for message filtering at query time
|
// Lazy: MessageSelector.tsx pulls React/ink; only needed for message filtering at query time
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const messageSelector = (): typeof import('src/components/MessageSelector.js') | null => {
|
const messageSelector = ():
|
||||||
|
| typeof import('src/components/MessageSelector.js')
|
||||||
|
| null => {
|
||||||
try {
|
try {
|
||||||
return require('src/components/MessageSelector.js')
|
return require('src/components/MessageSelector.js')
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -650,20 +652,19 @@ export class QueryEngine {
|
||||||
|
|
||||||
if (fileHistoryEnabled() && persistSession) {
|
if (fileHistoryEnabled() && persistSession) {
|
||||||
const _sel = messageSelector()
|
const _sel = messageSelector()
|
||||||
const _filter = _sel?.selectableUserMessagesFilter ?? ((_msg: unknown) => true)
|
const _filter =
|
||||||
messagesFromUserInput
|
_sel?.selectableUserMessagesFilter ?? ((_msg: unknown) => true)
|
||||||
.filter(_filter)
|
messagesFromUserInput.filter(_filter).forEach(message => {
|
||||||
.forEach(message => {
|
void fileHistoryMakeSnapshot(
|
||||||
void fileHistoryMakeSnapshot(
|
(updater: (prev: FileHistoryState) => FileHistoryState) => {
|
||||||
(updater: (prev: FileHistoryState) => FileHistoryState) => {
|
setAppState(prev => ({
|
||||||
setAppState(prev => ({
|
...prev,
|
||||||
...prev,
|
fileHistory: updater(prev.fileHistory),
|
||||||
fileHistory: updater(prev.fileHistory),
|
}))
|
||||||
}))
|
},
|
||||||
},
|
message.uuid,
|
||||||
message.uuid,
|
)
|
||||||
)
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track current message usage (reset on each message_start)
|
// Track current message usage (reset on each message_start)
|
||||||
|
|
@ -716,7 +717,8 @@ export class QueryEngine {
|
||||||
message.subtype === 'compact_boundary'
|
message.subtype === 'compact_boundary'
|
||||||
) {
|
) {
|
||||||
const compactMsg = message as SystemCompactBoundaryMessage
|
const compactMsg = message as SystemCompactBoundaryMessage
|
||||||
const tailUuid = compactMsg.compactMetadata?.preservedSegment?.tailUuid
|
const tailUuid =
|
||||||
|
compactMsg.compactMetadata?.preservedSegment?.tailUuid
|
||||||
if (tailUuid) {
|
if (tailUuid) {
|
||||||
const tailIdx = this.mutableMessages.findLastIndex(
|
const tailIdx = this.mutableMessages.findLastIndex(
|
||||||
m => m.uuid === tailUuid,
|
m => m.uuid === tailUuid,
|
||||||
|
|
@ -776,7 +778,10 @@ export class QueryEngine {
|
||||||
// streamed responses, this is null at content_block_stop time;
|
// streamed responses, this is null at content_block_stop time;
|
||||||
// the real value arrives via message_delta (handled below).
|
// the real value arrives via message_delta (handled below).
|
||||||
const msg = message as Message
|
const msg = message as Message
|
||||||
const stopReason = msg.message?.stop_reason as string | null | undefined
|
const stopReason = msg.message?.stop_reason as
|
||||||
|
| string
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
if (stopReason != null) {
|
if (stopReason != null) {
|
||||||
lastStopReason = stopReason
|
lastStopReason = stopReason
|
||||||
}
|
}
|
||||||
|
|
@ -806,11 +811,15 @@ export class QueryEngine {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'stream_event': {
|
case 'stream_event': {
|
||||||
const event = (message as unknown as { event: Record<string, unknown> }).event
|
const event = (
|
||||||
|
message as unknown as { event: Record<string, unknown> }
|
||||||
|
).event
|
||||||
if (event.type === 'message_start') {
|
if (event.type === 'message_start') {
|
||||||
// Reset current message usage for new message
|
// Reset current message usage for new message
|
||||||
currentMessageUsage = EMPTY_USAGE
|
currentMessageUsage = EMPTY_USAGE
|
||||||
const eventMessage = event.message as { usage: BetaMessageDeltaUsage }
|
const eventMessage = event.message as {
|
||||||
|
usage: BetaMessageDeltaUsage
|
||||||
|
}
|
||||||
currentMessageUsage = updateUsage(
|
currentMessageUsage = updateUsage(
|
||||||
currentMessageUsage,
|
currentMessageUsage,
|
||||||
eventMessage.usage,
|
eventMessage.usage,
|
||||||
|
|
@ -859,7 +868,15 @@ export class QueryEngine {
|
||||||
void recordTranscript(messages)
|
void recordTranscript(messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachment = msg.attachment as { type: string; data?: unknown; turnCount?: number; maxTurns?: number; prompt?: string; source_uuid?: string; [key: string]: unknown }
|
const attachment = msg.attachment as {
|
||||||
|
type: string
|
||||||
|
data?: unknown
|
||||||
|
turnCount?: number
|
||||||
|
maxTurns?: number
|
||||||
|
prompt?: string
|
||||||
|
source_uuid?: string
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
// Extract structured output from StructuredOutput tool calls
|
// Extract structured output from StructuredOutput tool calls
|
||||||
if (attachment.type === 'structured_output') {
|
if (attachment.type === 'structured_output') {
|
||||||
|
|
@ -900,10 +917,7 @@ export class QueryEngine {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Yield queued_command attachments as SDK user message replays
|
// Yield queued_command attachments as SDK user message replays
|
||||||
else if (
|
else if (replayUserMessages && attachment.type === 'queued_command') {
|
||||||
replayUserMessages &&
|
|
||||||
attachment.type === 'queued_command'
|
|
||||||
) {
|
|
||||||
yield {
|
yield {
|
||||||
type: 'user',
|
type: 'user',
|
||||||
message: {
|
message: {
|
||||||
|
|
@ -931,10 +945,7 @@ export class QueryEngine {
|
||||||
// never shrinks (memory leak in long SDK sessions). The subtype
|
// never shrinks (memory leak in long SDK sessions). The subtype
|
||||||
// check lives inside the injected callback so feature-gated strings
|
// check lives inside the injected callback so feature-gated strings
|
||||||
// stay out of this file (excluded-strings check).
|
// stay out of this file (excluded-strings check).
|
||||||
const snipResult = this.config.snipReplay?.(
|
const snipResult = this.config.snipReplay?.(msg, this.mutableMessages)
|
||||||
msg,
|
|
||||||
this.mutableMessages,
|
|
||||||
)
|
|
||||||
if (snipResult !== undefined) {
|
if (snipResult !== undefined) {
|
||||||
if (snipResult.executed) {
|
if (snipResult.executed) {
|
||||||
this.mutableMessages.length = 0
|
this.mutableMessages.length = 0
|
||||||
|
|
@ -944,10 +955,7 @@ export class QueryEngine {
|
||||||
}
|
}
|
||||||
this.mutableMessages.push(msg)
|
this.mutableMessages.push(msg)
|
||||||
// Yield compact boundary messages to SDK
|
// Yield compact boundary messages to SDK
|
||||||
if (
|
if (msg.subtype === 'compact_boundary' && msg.compactMetadata) {
|
||||||
msg.subtype === 'compact_boundary' &&
|
|
||||||
msg.compactMetadata
|
|
||||||
) {
|
|
||||||
const compactMsg = msg as SystemCompactBoundaryMessage
|
const compactMsg = msg as SystemCompactBoundaryMessage
|
||||||
// Release pre-compaction messages for GC. The boundary was just
|
// Release pre-compaction messages for GC. The boundary was just
|
||||||
// pushed so it's the last element. query.ts already uses
|
// pushed so it's the last element. query.ts already uses
|
||||||
|
|
@ -967,11 +975,18 @@ export class QueryEngine {
|
||||||
subtype: 'compact_boundary' as const,
|
subtype: 'compact_boundary' as const,
|
||||||
session_id: getSessionId(),
|
session_id: getSessionId(),
|
||||||
uuid: msg.uuid,
|
uuid: msg.uuid,
|
||||||
compact_metadata: toSDKCompactMetadata(compactMsg.compactMetadata),
|
compact_metadata: toSDKCompactMetadata(
|
||||||
|
compactMsg.compactMetadata,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (msg.subtype === 'api_error') {
|
if (msg.subtype === 'api_error') {
|
||||||
const apiErrorMsg = msg as Message & { retryAttempt: number; maxRetries: number; retryInMs: number; error: APIError }
|
const apiErrorMsg = msg as Message & {
|
||||||
|
retryAttempt: number
|
||||||
|
maxRetries: number
|
||||||
|
retryInMs: number
|
||||||
|
error: APIError
|
||||||
|
}
|
||||||
yield {
|
yield {
|
||||||
type: 'system',
|
type: 'system',
|
||||||
subtype: 'api_retry' as const,
|
subtype: 'api_retry' as const,
|
||||||
|
|
@ -997,7 +1012,10 @@ export class QueryEngine {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'tool_use_summary': {
|
case 'tool_use_summary': {
|
||||||
const msg = message as Message & { summary: unknown; precedingToolUseIds: unknown }
|
const msg = message as Message & {
|
||||||
|
summary: unknown
|
||||||
|
precedingToolUseIds: unknown
|
||||||
|
}
|
||||||
// Yield tool use summary messages to SDK
|
// Yield tool use summary messages to SDK
|
||||||
yield {
|
yield {
|
||||||
type: 'tool_use_summary' as const,
|
type: 'tool_use_summary' as const,
|
||||||
|
|
@ -1106,7 +1124,10 @@ export class QueryEngine {
|
||||||
const edeResultType = result?.type ?? 'undefined'
|
const edeResultType = result?.type ?? 'undefined'
|
||||||
const edeLastContentType =
|
const edeLastContentType =
|
||||||
result?.type === 'assistant'
|
result?.type === 'assistant'
|
||||||
? (last(result.message!.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[])?.type ?? 'none')
|
? (last(
|
||||||
|
result.message!
|
||||||
|
.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[],
|
||||||
|
)?.type ?? 'none')
|
||||||
: 'n/a'
|
: 'n/a'
|
||||||
|
|
||||||
// Flush buffered transcript writes before yielding result.
|
// Flush buffered transcript writes before yielding result.
|
||||||
|
|
@ -1164,7 +1185,10 @@ export class QueryEngine {
|
||||||
let isApiError = false
|
let isApiError = false
|
||||||
|
|
||||||
if (result.type === 'assistant') {
|
if (result.type === 'assistant') {
|
||||||
const lastContent = last(result.message!.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[])
|
const lastContent = last(
|
||||||
|
result.message!
|
||||||
|
.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[],
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
lastContent?.type === 'text' &&
|
lastContent?.type === 'text' &&
|
||||||
!SYNTHETIC_MESSAGES.has(lastContent.text)
|
!SYNTHETIC_MESSAGES.has(lastContent.text)
|
||||||
|
|
@ -1201,6 +1225,17 @@ export class QueryEngine {
|
||||||
this.abortController.abort()
|
this.abortController.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Reset the abort controller so the next submitMessage() call can start
|
||||||
|
* with a fresh, non-aborted signal. Must be called after interrupt(). */
|
||||||
|
resetAbortController(): void {
|
||||||
|
this.abortController = createAbortController()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Expose the current abort signal for external consumers (e.g. ACP bridge). */
|
||||||
|
getAbortSignal(): AbortSignal {
|
||||||
|
return this.abortController.signal
|
||||||
|
}
|
||||||
|
|
||||||
getMessages(): readonly Message[] {
|
getMessages(): readonly Message[] {
|
||||||
return this.mutableMessages
|
return this.mutableMessages
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user