1
>
/*---------------------------------------------------------------------------------------------
claudePromptQueue.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
7
>
import { DeferredPromise } from '../../../../base/common/async.js';
8
>
import { Disposable } from '../../../../base/common/lifecycle.js';
9
>
import { StopWatch } from '../../../../base/common/stopwatch.js';
10
>
import { ILogService } from '../../../log/common/log.js';
11
>
12
>
/**
13
>
* One {@link SDKUserMessage} the queue has handed to (or is about to
14
>
* hand to) the SDK. Lifecycle:
15
>
* 1. Created by the caller and pushed via {@link ClaudePromptQueue.push}.
16
>
* 2. Shifted off the to-yield list and pushed to the yielded list when
17
>
* the prompt iterable hands it to the SDK.
18
>
* 3. Shifted off the yielded list and {@link deferred} settled when
19
>
* the matching SDK `result` message arrives (via
20
>
* {@link ClaudePromptQueue.settleHead}).
21
>
*/
22
>
export interface IPendingSdkMessage {
23
>
readonly sdkMessage: SDKUserMessage;
24
>
readonly sdkUuid: string;
25
>
readonly turnId: string;
26
>
readonly stopWatch: StopWatch;
27
>
readonly deferred: DeferredPromise<void>;
28
>
readonly steeringPendingId?: string;
29
>
}
30
>
31
>
/**
32
>
* Owns the prompt queue + the async iterable handed to
33
>
* `WarmQuery.query()`. Knows nothing about the SDK Query lifecycle,
34
>
* config push, or message dispatch — those live on the pipeline.
35
>
*
36
>
* Invariants:
37
>
* • Pushing wakes the iterable's parked `next()`.
38
>
* • The iterable returns `done` when the supplied `getAbortSignal()`
39
>
* is aborted; pipeline calls {@link notifyAborted} after flipping
40
>
* the controller so the parked `next()` returns immediately.
41
>
* • {@link settleHead} pops the head of the yielded list (called by
42
>
* the consumer loop on every `result` message).
43
>
* • {@link failAll} rejects every pending deferred and clears both
44
>
* lists; used by abort and crash fan-out.
45
>
* • {@link resetForRebind} re-creates the parked deferred for a fresh
46
>
* Query binding (the queue itself survives across rebinds).
47
>
*/
48
>
export class ClaudePromptQueue extends Disposable {
49
>
50
>
private _toYield: IPendingSdkMessage[] = [];
51
>
private _yielded: IPendingSdkMessage[] = [];
52
>
/**
53
>
* Entries that have been popped by {@link settleHead} during the
54
>
* current turn but whose deferreds haven't been completed yet — we
55
>
* batch-complete them when the turn fully drains so an intermediate
56
>
* `result` (steering preempt; CONTEXT.md M10) does NOT settle the
57
>
* original `sendMessage`'s deferred.
58
>
*/
59
>
private _popped: IPendingSdkMessage[] = [];
60
>
private _pendingPromptDeferred = new DeferredPromise<void>();
61
>
62
>
readonly iterable: AsyncIterable<SDKUserMessage> = {
63
>
[Symbol.asyncIterator]: () => ({
64
next: async () => {
65
while (true) {