Problem
Clients cannot implement safe Codex-style mid-task auto-compaction from above session.stream().
Current SDK shape:
MessageOptions only exposes images, files, outputFormat, includePartialMessages, and abortSignal:
|
export interface MessageOptions { |
|
images?: Base64ImageSource[]; |
|
files?: DocumentSource[]; |
|
outputFormat?: OutputFormat; |
|
includePartialMessages?: boolean; |
|
abortSignal?: AbortSignal; |
|
} |
streamFromClient() sends one client.addUserMessage(...), then yields bridge messages until the daemon reports completion:
|
export async function* streamFromClient( |
|
client: StreamableClient, |
|
sessionId: string, |
|
activeBridges: Set<MessageBridge>, |
|
prompt: string, |
|
options?: MessageOptions |
|
): AsyncGenerator<DroidStreamEvent, void, undefined> { |
|
throwIfAborted(options?.abortSignal); |
|
|
|
const startedAt = Date.now(); |
|
let resolveDone: () => void = () => {}; |
|
const donePromise = new Promise<void>((resolve) => { |
|
resolveDone = resolve; |
|
}); |
|
const bridge = new MessageBridge(resolveDone, { |
|
includePartialMessages: options?.includePartialMessages, |
|
sessionId, |
|
startedAt, |
|
outputFormat: options?.outputFormat, |
|
}); |
|
activeBridges.add(bridge); |
|
const unsubscribe = client.onNotification(bridge.notificationHandler); |
|
let resolveAbort: () => void = () => {}; |
|
const abortPromise = new Promise<void>((resolve) => { |
|
resolveAbort = resolve; |
|
}); |
|
const cleanupAbortSignal = wireAbortSignal(options?.abortSignal, () => { |
|
bridge.signalDone(); |
|
resolveAbort(); |
|
void client.interruptSession().catch(() => {}); |
|
}); |
|
|
|
try { |
|
await Promise.race([ |
|
client.addUserMessage({ |
|
text: prompt, |
|
images: options?.images, |
|
files: options?.files, |
|
outputFormat: options?.outputFormat, |
|
}), |
|
donePromise, |
|
abortPromise, |
|
]); |
|
throwIfAborted(options?.abortSignal); |
|
|
|
for await (const msg of bridge.messages()) { |
|
throwIfAborted(options?.abortSignal); |
|
yield msg; |
|
} |
|
throwIfAborted(options?.abortSignal); |
|
} finally { |
|
cleanupAbortSignal(); |
|
unsubscribe(); |
|
activeBridges.delete(bridge); |
|
} |
|
} |
DaemonSession.stream() and DroidSession.stream() delegate directly to streamFromClient():
|
async *stream( |
|
prompt: string, |
|
options?: MessageOptions |
|
): AsyncGenerator<DroidStreamEvent, void, undefined> { |
|
this._ensureNotClosed(); |
|
yield* streamFromClient( |
|
this._client, |
|
this._sessionId, |
|
this._activeBridges, |
|
prompt, |
|
options |
|
); |
|
async *stream( |
|
prompt: string, |
|
options?: MessageOptions |
|
): AsyncGenerator<DroidStreamEvent, void, undefined> { |
|
this._ensureNotClosed(); |
|
yield* streamFromClient( |
|
this._client, |
|
this._sessionId, |
|
this._activeBridges, |
|
prompt, |
|
options |
|
); |
- Public stream/add_user_message schemas do not include a same-turn auto-compaction checkpoint option:
- rich client schema has message fields but no compaction config:
|
export const AddUserMessageRequestParamsSchema = z.object({ |
|
messageId: z.string().optional(), |
|
text: z.string(), |
|
images: Base64ImageSourceSchema.array().optional(), |
|
files: DocumentSourceSchema.array().optional(), |
|
outputFormat: OutputFormatSchema.optional(), |
|
skipAgentLoop: z.boolean().optional(), |
|
queuePlacement: z.nativeEnum(QueuePlacement).optional(), |
|
role: z.nativeEnum(MessageRole).optional(), |
|
visibility: z.nativeEnum(MessageVisibility).optional(), |
|
userMessageSource: z.nativeEnum(SessionOrigin).optional(), |
|
}); |
- daemon schema only extends with
sessionId:
|
export const DaemonAddUserMessageRequestParamsSchema = |
|
AddUserMessageRequestParamsSchema.extend({ |
|
sessionId: z.string(), |
|
}); |
- legacy/CLI schema is strict and only includes attachments/output:
|
export const AddUserMessageRequestParamsSchema = z |
|
.object({ |
|
messageId: z.string().optional(), |
|
text: z.string(), |
|
images: z.array(Base64ImageSourceSchema).optional(), |
|
files: z.array(DocumentSourceSchema).optional(), |
|
outputFormat: OutputFormatSchema.optional(), |
|
}) |
|
.strict(); |
Settings-level compaction fields such as compactionTokenLimit, compactionTokenLimitPerModel, or compactionThresholdCheckEnabled do not solve this by themselves: they are not a same-turn checkpoint in the stream/add_user_message path. Because the SDK stream is not a backpressure checkpoint, clients should not react to tool_result notifications by calling compactSession() while the live turn is still active. The daemon may already be continuing into the next model request.
Requested capability
Expose an SDK/daemon auto-compaction checkpoint that runs after tool results are persisted and before the next model request inside a single session.stream() turn.
Desired flow:
model request finishes
tool calls run
tool results are appended to session history
daemon checks whether auto-compaction is due
if due: emit compacting status, compact session, refresh context, adopt swapped backing session id
daemon continues with the next model request
Possible SDK API
Either add stream options that are forwarded into droid.add_user_message / daemon request params:
session.stream(prompt, {
includePartialMessages: true,
autoCompactTokenLimit,
// optional if daemon owns the whole policy:
shouldAutoCompact,
beforeNextModelRequest,
onCompactionStatus,
});
Or keep the daemon fully policy-owned and expose only the effective threshold/config field, e.g.:
session.stream(prompt, {
includePartialMessages: true,
autoCompactTokenLimit,
});
Required semantics
- Never compact while assistant text is actively streaming.
- Compact only between model calls, after tool results are appended and before the next model request.
- Emit active status events while paused, e.g.
compacting_conversation, Compacting conversation..., and Compaction complete.
- If compaction swaps the backing session id, the same live continuation must continue against the new backing session.
- Queued sends should wait until compaction finishes.
- Existing manual
compactSession() remains a separate operation and can still be rejected by clients while active.
Downstream context
Droid Control tracks the downstream need here:
anasibnanwar1-droid/droid-maxxing#42
That app can handle user-visible thresholds/UI, but it needs the SDK/daemon checkpoint to implement true same-task mid-task compaction safely.
Problem
Clients cannot implement safe Codex-style mid-task auto-compaction from above
session.stream().Current SDK shape:
MessageOptionsonly exposesimages,files,outputFormat,includePartialMessages, andabortSignal:droid-sdk-typescript/src/helpers.ts
Lines 68 to 74 in 655254a
streamFromClient()sends oneclient.addUserMessage(...), then yields bridge messages until the daemon reports completion:droid-sdk-typescript/src/helpers.ts
Lines 87 to 142 in 655254a
DaemonSession.stream()andDroidSession.stream()delegate directly tostreamFromClient():droid-sdk-typescript/src/daemon/session.ts
Lines 65 to 76 in 655254a
droid-sdk-typescript/src/session.ts
Lines 130 to 141 in 655254a
droid-sdk-typescript/src/protocol/client.ts
Lines 201 to 212 in 655254a
sessionId:droid-sdk-typescript/src/protocol/daemon/droid.ts
Lines 155 to 158 in 655254a
droid-sdk-typescript/src/schemas/client.ts
Lines 276 to 284 in 655254a
Settings-level compaction fields such as
compactionTokenLimit,compactionTokenLimitPerModel, orcompactionThresholdCheckEnableddo not solve this by themselves: they are not a same-turn checkpoint in the stream/add_user_message path. Because the SDK stream is not a backpressure checkpoint, clients should not react totool_resultnotifications by callingcompactSession()while the live turn is still active. The daemon may already be continuing into the next model request.Requested capability
Expose an SDK/daemon auto-compaction checkpoint that runs after tool results are persisted and before the next model request inside a single
session.stream()turn.Desired flow:
Possible SDK API
Either add stream options that are forwarded into
droid.add_user_message/ daemon request params:Or keep the daemon fully policy-owned and expose only the effective threshold/config field, e.g.:
Required semantics
compacting_conversation,Compacting conversation..., andCompaction complete.compactSession()remains a separate operation and can still be rejected by clients while active.Downstream context
Droid Control tracks the downstream need here:
anasibnanwar1-droid/droid-maxxing#42
That app can handle user-visible thresholds/UI, but it needs the SDK/daemon checkpoint to implement true same-task mid-task compaction safely.