fix(app): keep submitted prompts in timeline order (#2259)
Canonical provider message IDs and optimistic client IDs occupy different namespaces. Preserve both identities for deterministic reconciliation while retaining a dated content fallback for older daemon timelines.
This commit is contained in:
@@ -7881,7 +7881,12 @@ test("authoritative timeline includes provider-emitted submitted user prompt", a
|
||||
type: "timeline",
|
||||
provider: this.provider,
|
||||
turnId,
|
||||
item: { type: "user_message", text, messageId: options?.messageId },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text,
|
||||
messageId: "provider-message-1",
|
||||
clientMessageId: options?.clientMessageId,
|
||||
},
|
||||
});
|
||||
this.pushEvent({ type: "turn_completed", provider: this.provider, turnId });
|
||||
}, 0);
|
||||
@@ -7907,13 +7912,16 @@ test("authoritative timeline includes provider-emitted submitted user prompt", a
|
||||
workspaceId: undefined,
|
||||
});
|
||||
|
||||
await manager.runAgent(snapshot.id, "hello from composer", { messageId: "msg-client-1" });
|
||||
await manager.runAgent(snapshot.id, "hello from composer", {
|
||||
clientMessageId: "msg-client-1",
|
||||
});
|
||||
|
||||
const timeline = manager.fetchTimeline(snapshot.id, { direction: "tail", limit: 20 }).rows;
|
||||
expect(timeline.map((row) => row.item)).toContainEqual({
|
||||
type: "user_message",
|
||||
text: "hello from composer",
|
||||
messageId: "msg-client-1",
|
||||
messageId: "provider-message-1",
|
||||
clientMessageId: "msg-client-1",
|
||||
});
|
||||
} finally {
|
||||
await manager.flush().catch(() => undefined);
|
||||
|
||||
@@ -190,7 +190,7 @@ test("sendPromptToAgent forwards the client message id as run options", async ()
|
||||
|
||||
expect(streamAgentSpy).toHaveBeenCalledWith("agent-1", "hello", {
|
||||
outputSchema: { type: "object" },
|
||||
messageId: "msg-client-1",
|
||||
clientMessageId: "msg-client-1",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ export async function sendPromptToAgent(
|
||||
}
|
||||
|
||||
const runOptions = params.messageId
|
||||
? { ...params.runOptions, messageId: params.messageId }
|
||||
? { ...params.runOptions, clientMessageId: params.messageId }
|
||||
: params.runOptions;
|
||||
|
||||
return await startAgentRun(params.agentManager, params.agentId, params.prompt, params.logger, {
|
||||
|
||||
@@ -199,7 +199,7 @@ export interface AgentRunOptions {
|
||||
outputSchema?: unknown;
|
||||
resumeFrom?: AgentPersistenceHandle;
|
||||
maxThinkingTokens?: number;
|
||||
messageId?: string;
|
||||
clientMessageId?: string;
|
||||
}
|
||||
|
||||
export interface AgentUsage {
|
||||
@@ -368,7 +368,7 @@ export interface CompactionTimelineItem {
|
||||
}
|
||||
|
||||
export type AgentTimelineItem =
|
||||
| { type: "user_message"; text: string; messageId?: string }
|
||||
| { type: "user_message"; text: string; messageId?: string; clientMessageId?: string }
|
||||
| { type: "assistant_message"; text: string; messageId?: string }
|
||||
| { type: "reasoning"; text: string }
|
||||
| ToolCallTimelineItem
|
||||
|
||||
@@ -75,7 +75,7 @@ test("session create forwards clientMessageId to the initial prompt run options"
|
||||
});
|
||||
|
||||
expect(streamAgent).toHaveBeenCalledWith("agent-1", "hello from create", {
|
||||
messageId: "msg-create-1",
|
||||
clientMessageId: "msg-create-1",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ async function resolveSessionCreateAgent(
|
||||
input.outputSchema || clientMessageId
|
||||
? {
|
||||
...(input.outputSchema ? { outputSchema: input.outputSchema } : {}),
|
||||
...(clientMessageId ? { messageId: clientMessageId } : {}),
|
||||
...(clientMessageId ? { clientMessageId } : {}),
|
||||
}
|
||||
: undefined;
|
||||
const workspaceId = setupContinuation ? createdWorkspaceId : input.workspaceId;
|
||||
|
||||
@@ -2329,7 +2329,9 @@ describe("ACPAgentSession", () => {
|
||||
events.push(event);
|
||||
});
|
||||
|
||||
const { turnId } = await session.startTurn("hello", { messageId: "msg-client-1" });
|
||||
const { turnId } = await session.startTurn("hello", {
|
||||
clientMessageId: "msg-client-1",
|
||||
});
|
||||
|
||||
expect(prompt).toHaveBeenCalledWith({
|
||||
sessionId: "session-1",
|
||||
@@ -2343,7 +2345,12 @@ describe("ACPAgentSession", () => {
|
||||
type: "timeline",
|
||||
provider: "claude-acp",
|
||||
turnId,
|
||||
item: { type: "user_message", text: "hello", messageId: "msg-client-1" },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: "hello",
|
||||
messageId: "msg-client-1",
|
||||
clientMessageId: "msg-client-1",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -2362,7 +2369,7 @@ describe("ACPAgentSession", () => {
|
||||
events.push(event);
|
||||
});
|
||||
|
||||
await session.startTurn("hello", { messageId: "msg-client-1" });
|
||||
await session.startTurn("hello", { clientMessageId: "msg-client-1" });
|
||||
await session.sessionUpdate({
|
||||
sessionId: "session-1",
|
||||
update: {
|
||||
@@ -2389,7 +2396,7 @@ describe("ACPAgentSession", () => {
|
||||
events.push(event);
|
||||
});
|
||||
|
||||
await session.startTurn("hello", { messageId: "msg-client-1" });
|
||||
await session.startTurn("hello", { clientMessageId: "msg-client-1" });
|
||||
await session.sessionUpdate({
|
||||
sessionId: "session-1",
|
||||
update: {
|
||||
@@ -2404,7 +2411,12 @@ describe("ACPAgentSession", () => {
|
||||
{
|
||||
type: "timeline",
|
||||
provider: "claude-acp",
|
||||
item: { type: "user_message", text: "hello", messageId: "msg-client-1" },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: "hello",
|
||||
messageId: "msg-client-1",
|
||||
clientMessageId: "msg-client-1",
|
||||
},
|
||||
turnId: expect.any(String),
|
||||
},
|
||||
]);
|
||||
@@ -2428,7 +2440,7 @@ describe("ACPAgentSession", () => {
|
||||
events.push(event);
|
||||
});
|
||||
|
||||
await session.startTurn("first", { messageId: "msg-client-1" });
|
||||
await session.startTurn("first", { clientMessageId: "msg-client-1" });
|
||||
await session.sessionUpdate({
|
||||
sessionId: "session-1",
|
||||
update: {
|
||||
@@ -2440,7 +2452,7 @@ describe("ACPAgentSession", () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
await session.startTurn("second", { messageId: "msg-client-2" });
|
||||
await session.startTurn("second", { clientMessageId: "msg-client-2" });
|
||||
await session.sessionUpdate({
|
||||
sessionId: "session-1",
|
||||
update: {
|
||||
@@ -2455,13 +2467,23 @@ describe("ACPAgentSession", () => {
|
||||
{
|
||||
type: "timeline",
|
||||
provider: "claude-acp",
|
||||
item: { type: "user_message", text: "first", messageId: "msg-client-1" },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: "first",
|
||||
messageId: "msg-client-1",
|
||||
clientMessageId: "msg-client-1",
|
||||
},
|
||||
turnId: expect.any(String),
|
||||
},
|
||||
{
|
||||
type: "timeline",
|
||||
provider: "claude-acp",
|
||||
item: { type: "user_message", text: "second", messageId: "msg-client-2" },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: "second",
|
||||
messageId: "msg-client-2",
|
||||
clientMessageId: "msg-client-2",
|
||||
},
|
||||
turnId: expect.any(String),
|
||||
},
|
||||
]);
|
||||
@@ -2479,7 +2501,7 @@ describe("ACPAgentSession", () => {
|
||||
events.push(event);
|
||||
});
|
||||
|
||||
await session.startTurn("hello", { messageId: "msg-client-1" });
|
||||
await session.startTurn("hello", { clientMessageId: "msg-client-1" });
|
||||
await session.sessionUpdate({
|
||||
sessionId: "session-1",
|
||||
update: {
|
||||
@@ -2495,7 +2517,12 @@ describe("ACPAgentSession", () => {
|
||||
{
|
||||
type: "timeline",
|
||||
provider: "claude-acp",
|
||||
item: { type: "user_message", text: "hello", messageId: "msg-client-1" },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: "hello",
|
||||
messageId: "msg-client-1",
|
||||
clientMessageId: "msg-client-1",
|
||||
},
|
||||
turnId: expect.any(String),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1474,13 +1474,13 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
||||
}
|
||||
|
||||
const turnId = randomUUID();
|
||||
const messageId = options?.messageId ?? randomUUID();
|
||||
const messageId = options?.clientMessageId ?? randomUUID();
|
||||
this.activeForegroundTurnId = turnId;
|
||||
this.fallbackAssistantMessageId = null;
|
||||
this.activeSubmittedUserMessage = null;
|
||||
this.emitBootstrapThreadEvent();
|
||||
this.pushEvent({ type: "turn_started", provider: this.provider, turnId });
|
||||
this.emitSubmittedUserMessage(prompt, messageId, turnId);
|
||||
this.emitSubmittedUserMessage(prompt, messageId, turnId, options?.clientMessageId);
|
||||
|
||||
void this.connection
|
||||
.prompt({
|
||||
@@ -2687,6 +2687,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
||||
prompt: AgentPromptInput,
|
||||
messageId: string,
|
||||
turnId: string,
|
||||
clientMessageId?: string,
|
||||
): void {
|
||||
const text = extractPromptText(prompt);
|
||||
if (text.trim().length === 0) {
|
||||
@@ -2698,7 +2699,12 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
||||
type: "timeline",
|
||||
provider: this.provider,
|
||||
turnId,
|
||||
item: { type: "user_message", text, messageId },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text,
|
||||
messageId,
|
||||
...(clientMessageId ? { clientMessageId } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2043,7 +2043,7 @@ class ClaudeAgentSession implements AgentSession {
|
||||
|
||||
async startTurn(
|
||||
prompt: AgentPromptInput,
|
||||
_options?: AgentRunOptions,
|
||||
options?: AgentRunOptions,
|
||||
): Promise<{ turnId: string }> {
|
||||
if (this.closed) {
|
||||
throw new Error("Claude session is closed");
|
||||
@@ -2109,7 +2109,7 @@ class ClaudeAgentSession implements AgentSession {
|
||||
this.input.push(sdkMessage);
|
||||
setTimeout(() => {
|
||||
if (this.activeForegroundTurnId === turnId) {
|
||||
this.emitSubmittedUserMessage(sdkMessage, turnId);
|
||||
this.emitSubmittedUserMessage(sdkMessage, turnId, options?.clientMessageId);
|
||||
}
|
||||
}, 0);
|
||||
} catch (error) {
|
||||
@@ -3686,6 +3686,7 @@ class ClaudeAgentSession implements AgentSession {
|
||||
private emitSubmittedUserMessage(
|
||||
message: Extract<SDKMessage, { type: "user" }>,
|
||||
turnId: string,
|
||||
clientMessageId?: string,
|
||||
): void {
|
||||
const events: AgentStreamEvent[] = [];
|
||||
this.appendUserMessageEvents(message, events);
|
||||
@@ -3695,7 +3696,11 @@ class ClaudeAgentSession implements AgentSession {
|
||||
this.foregroundHasVisibleActivity = true;
|
||||
for (const event of events) {
|
||||
if (event.type === "timeline") {
|
||||
this.notifySubscribers({ ...event, turnId });
|
||||
const item =
|
||||
event.item.type === "user_message" && clientMessageId
|
||||
? { ...event.item, clientMessageId }
|
||||
: event.item;
|
||||
this.notifySubscribers({ ...event, item, turnId });
|
||||
} else {
|
||||
this.notifySubscribers(event);
|
||||
}
|
||||
|
||||
@@ -1014,6 +1014,30 @@ describe("Codex app-server provider", () => {
|
||||
await session.close();
|
||||
});
|
||||
|
||||
test("correlates a Codex user message with the submitting client message", async () => {
|
||||
const appServer = createFakeCodexAppServer();
|
||||
const session = new CodexAppServerAgentSession(
|
||||
createConfig({ cwd: "/workspace/project" }),
|
||||
null,
|
||||
createTestLogger(),
|
||||
async () => appServer.child,
|
||||
);
|
||||
|
||||
await session.startTurn("remember this", { clientMessageId: "client-message" });
|
||||
const userMessage = waitForNextTimelineItem(session, "user_message");
|
||||
emitCodexUserMessage(appServer, { id: "codex-message", text: "remember this" });
|
||||
|
||||
await expect(userMessage).resolves.toMatchObject({
|
||||
item: {
|
||||
type: "user_message",
|
||||
messageId: "codex-message",
|
||||
clientMessageId: "client-message",
|
||||
},
|
||||
});
|
||||
appServer.completeTurn();
|
||||
await session.close();
|
||||
});
|
||||
|
||||
test("configures Codex app-server to use a custom provider base URL", async () => {
|
||||
const capturedRequests = await runCustomCodexProviderTurn(
|
||||
"codex-iisb",
|
||||
|
||||
@@ -3096,6 +3096,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
private readonly subscribers = new Set<(event: AgentStreamEvent) => void>();
|
||||
private nextTurnOrdinal = 0;
|
||||
private activeForegroundTurnId: string | null = null;
|
||||
private activeClientMessageId: string | null = null;
|
||||
private cachedRuntimeInfo: AgentRuntimeInfo | null = null;
|
||||
private serviceTier: "fast" | null = null;
|
||||
private planModeEnabled = false;
|
||||
@@ -3820,6 +3821,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
|
||||
const turnId = this.createTurnId();
|
||||
this.activeForegroundTurnId = turnId;
|
||||
this.activeClientMessageId = options?.clientMessageId ?? null;
|
||||
this.currentTurnId = null;
|
||||
|
||||
try {
|
||||
@@ -3835,6 +3837,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
await this.client.request("turn/start", turnStart.params, TURN_START_TIMEOUT_MS);
|
||||
} catch (error) {
|
||||
this.activeForegroundTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -4238,6 +4241,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
this.pendingSubAgentNotificationsByThreadId.clear();
|
||||
this.subscribers.clear();
|
||||
this.activeForegroundTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
if (this.client) {
|
||||
await this.client.dispose();
|
||||
}
|
||||
@@ -5287,6 +5291,7 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
});
|
||||
}
|
||||
this.activeForegroundTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.pendingSubAgentNotificationsByThreadId.clear();
|
||||
this.resetTurnTrackingState();
|
||||
}
|
||||
@@ -5825,7 +5830,11 @@ export class CodexAppServerAgentSession implements AgentSession {
|
||||
if (!this.rememberCodexUserMessageTurn(timelineItem.messageId)) {
|
||||
return;
|
||||
}
|
||||
this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item: timelineItem });
|
||||
const item = this.activeClientMessageId
|
||||
? { ...timelineItem, clientMessageId: this.activeClientMessageId }
|
||||
: timelineItem;
|
||||
this.activeClientMessageId = null;
|
||||
this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, item });
|
||||
}
|
||||
|
||||
private warnUnknownNotificationMethod(method: string, params: unknown): void {
|
||||
|
||||
@@ -606,7 +606,7 @@ export class MockLoadTestAgentSession implements AgentSession {
|
||||
|
||||
async startTurn(
|
||||
prompt: AgentPromptInput,
|
||||
_options?: AgentRunOptions,
|
||||
options?: AgentRunOptions,
|
||||
): Promise<{ turnId: string }> {
|
||||
if (this.activeTurn) {
|
||||
throw new Error("Mock load-test provider already has an active turn");
|
||||
@@ -648,6 +648,7 @@ export class MockLoadTestAgentSession implements AgentSession {
|
||||
type: "user_message",
|
||||
text: promptToText(prompt),
|
||||
messageId: userMessageId,
|
||||
...(options?.clientMessageId ? { clientMessageId: options.clientMessageId } : {}),
|
||||
},
|
||||
});
|
||||
}, 0);
|
||||
|
||||
@@ -896,6 +896,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
private activeAskUserDialog: ActiveAskUserDialog | null = null;
|
||||
private pendingCombinedAskUserResponse: PendingCombinedAskUserResponse | null = null;
|
||||
private activeTurnId: string | null = null;
|
||||
private activeClientMessageId: string | null = null;
|
||||
private activeAssistantMessageId: string | null = null;
|
||||
private activeTurnStarted = false;
|
||||
private activeTurnHasUserMessage = false;
|
||||
@@ -974,7 +975,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
});
|
||||
}
|
||||
|
||||
async startTurn(prompt: AgentPromptInput, _options?: AgentRunOptions): Promise<StartTurnResult> {
|
||||
async startTurn(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<StartTurnResult> {
|
||||
if (this.activeTurnId) {
|
||||
throw new Error("An OMP turn is already active");
|
||||
}
|
||||
@@ -983,6 +984,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
const turnId = randomUUID();
|
||||
this.live = true;
|
||||
this.activeTurnId = turnId;
|
||||
this.activeClientMessageId = options?.clientMessageId ?? null;
|
||||
this.activeAssistantMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activeTurnHasUserMessage = false;
|
||||
@@ -1010,6 +1012,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
return;
|
||||
}
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activeTurnHasUserMessage = false;
|
||||
this.activeAssistantMessageId = null;
|
||||
@@ -1143,6 +1146,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
}
|
||||
if (turnId && this.activeTurnId === turnId) {
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activeTurnHasUserMessage = false;
|
||||
this.activeAssistantMessageId = null;
|
||||
@@ -1329,6 +1333,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: promptText,
|
||||
...(this.activeClientMessageId ? { clientMessageId: this.activeClientMessageId } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1764,6 +1769,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
}
|
||||
const turnId = this.activeTurnId;
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activeTurnHasUserMessage = false;
|
||||
this.clearNoTurnBuffers();
|
||||
@@ -2003,6 +2009,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
}
|
||||
const nativeMessage = event.message as OmpAgentMessage & { id?: unknown; entryId?: unknown };
|
||||
const messageId = readNativeMessageId(nativeMessage);
|
||||
const clientMessageId = this.activeClientMessageId;
|
||||
const emitUserMessage = (resolvedMessageId?: string): void => {
|
||||
if (resolvedMessageId) {
|
||||
// OMP re-emits user message_end frames for entries it has already
|
||||
@@ -2021,6 +2028,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
type: "user_message",
|
||||
text,
|
||||
...(resolvedMessageId ? { messageId: resolvedMessageId } : {}),
|
||||
...(clientMessageId ? { clientMessageId } : {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -2085,6 +2093,7 @@ export class OmpAgentSession implements AgentSession {
|
||||
|
||||
private completeTurn(turnId: string | undefined, messages: OmpAgentMessage[]): void {
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeAssistantMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activeTurnHasUserMessage = false;
|
||||
|
||||
@@ -1707,6 +1707,7 @@ export interface OpenCodeEventTranslationState {
|
||||
cwd?: string;
|
||||
messageRoles: Map<string, OpenCodeMessageRole>;
|
||||
pendingUserMessageText?: string | null;
|
||||
pendingClientMessageId?: string | null;
|
||||
emittedUserMessageIds?: Set<string>;
|
||||
accumulatedUsage: AgentUsage;
|
||||
sessionTotalCostUsd?: number;
|
||||
@@ -2370,7 +2371,12 @@ function appendOpenCodeUserMessageUpdated(
|
||||
events.push({
|
||||
type: "timeline",
|
||||
provider: "opencode",
|
||||
item: { type: "user_message", text, messageId: info.id },
|
||||
item: {
|
||||
type: "user_message",
|
||||
text,
|
||||
messageId: info.id,
|
||||
...(state.pendingClientMessageId ? { clientMessageId: state.pendingClientMessageId } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2895,6 +2901,7 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
/** Tracks the role of each message by ID to distinguish user from assistant messages */
|
||||
private messageRoles = new Map<string, OpenCodeMessageRole>();
|
||||
private pendingUserMessageText: string | null = null;
|
||||
private pendingClientMessageId: string | null = null;
|
||||
private emittedUserMessageIds = new Set<string>();
|
||||
/** Tracks streamed textual part IDs to suppress final full-text echoes from OpenCode. */
|
||||
private streamedPartKeys = new Set<string>();
|
||||
@@ -3103,6 +3110,7 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
|
||||
const parts = buildOpenCodePromptParts(prompt);
|
||||
this.pendingUserMessageText = buildOpenCodeUserTimelineText(prompt);
|
||||
this.pendingClientMessageId = options?.clientMessageId ?? null;
|
||||
this.suppressAssistantMessagesUntilIdle.active = false;
|
||||
const model = this.parseModel(this.config.model);
|
||||
const thinkingOptionId = this.config.thinkingOptionId;
|
||||
@@ -3645,6 +3653,7 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
this.subAgentsByCallId.clear();
|
||||
this.subAgentCallIdByChildSessionId.clear();
|
||||
this.pendingUserMessageText = null;
|
||||
this.pendingClientMessageId = null;
|
||||
this.abortController = null;
|
||||
this.notifySubscribers({ type: "turn_started", provider: "opencode" }, turnId);
|
||||
return turnId;
|
||||
@@ -3670,6 +3679,7 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
this.runningToolCalls.clear();
|
||||
}
|
||||
this.pendingUserMessageText = null;
|
||||
this.pendingClientMessageId = null;
|
||||
this.activeForegroundTurnId = null;
|
||||
this.activeForegroundTurnSource = null;
|
||||
this.abortController = null;
|
||||
@@ -4068,6 +4078,7 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
cwd: this.config.cwd,
|
||||
messageRoles: this.messageRoles,
|
||||
pendingUserMessageText: this.pendingUserMessageText,
|
||||
pendingClientMessageId: this.pendingClientMessageId,
|
||||
emittedUserMessageIds: this.emittedUserMessageIds,
|
||||
accumulatedUsage: this.accumulatedUsage,
|
||||
sessionTotalCostUsd: this.sessionTotalCostUsd,
|
||||
|
||||
@@ -258,6 +258,7 @@ interface PiCapturedEntry extends PiCapturedUserMessageEntry {
|
||||
interface PendingPiUserMessage {
|
||||
text: string;
|
||||
turnId: string | undefined;
|
||||
clientMessageId?: string;
|
||||
}
|
||||
|
||||
interface PendingExtensionResult {
|
||||
@@ -1179,6 +1180,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
private activeAskUserDialog: ActiveAskUserDialog | null = null;
|
||||
private pendingCombinedAskUserResponse: PendingCombinedAskUserResponse | null = null;
|
||||
private activeTurnId: string | null = null;
|
||||
private activeClientMessageId: string | null = null;
|
||||
private activeAssistantMessageId: string | null = null;
|
||||
private activeTurnStarted = false;
|
||||
private activeNoTurnPromptText: string | null = null;
|
||||
@@ -1240,7 +1242,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
});
|
||||
}
|
||||
|
||||
async startTurn(prompt: AgentPromptInput, _options?: AgentRunOptions): Promise<StartTurnResult> {
|
||||
async startTurn(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<StartTurnResult> {
|
||||
if (this.activeTurnId) {
|
||||
throw new Error("A Pi turn is already active");
|
||||
}
|
||||
@@ -1248,6 +1250,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
const payload = convertPromptInput(prompt, { model: this.state.model });
|
||||
const turnId = randomUUID();
|
||||
this.activeTurnId = turnId;
|
||||
this.activeClientMessageId = options?.clientMessageId ?? null;
|
||||
this.activeAssistantMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activePromptRequestId = null;
|
||||
@@ -1278,6 +1281,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
return;
|
||||
}
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activeAssistantMessageId = null;
|
||||
this.clearNoTurnBuffers();
|
||||
@@ -1393,6 +1397,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
await this.runtimeSession.abort();
|
||||
if (turnId && this.activeTurnId === turnId) {
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.activeAssistantMessageId = null;
|
||||
this.clearNoTurnBuffers();
|
||||
@@ -1574,6 +1579,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: promptText,
|
||||
...(this.activeClientMessageId ? { clientMessageId: this.activeClientMessageId } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1796,6 +1802,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
type: "user_message",
|
||||
text: pending.text,
|
||||
messageId: entry.id,
|
||||
...(pending.clientMessageId ? { clientMessageId: pending.clientMessageId } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1961,6 +1968,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
}
|
||||
const turnId = this.activeTurnId;
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.clearNoTurnBuffers();
|
||||
this.emit({
|
||||
@@ -2170,7 +2178,11 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
this.pendingUserMessages.push({ text, turnId });
|
||||
this.pendingUserMessages.push({
|
||||
text,
|
||||
turnId,
|
||||
...(this.activeClientMessageId ? { clientMessageId: this.activeClientMessageId } : {}),
|
||||
});
|
||||
void this.requestEntryCapture("message_end").catch((error: unknown) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
this.emit({
|
||||
@@ -2221,6 +2233,7 @@ export class PiRpcAgentSession implements AgentSession {
|
||||
|
||||
private completeTurn(turnId: string | undefined, messages: PiAgentMessage[]): void {
|
||||
this.activeTurnId = null;
|
||||
this.activeClientMessageId = null;
|
||||
this.activeAssistantMessageId = null;
|
||||
this.activeTurnStarted = false;
|
||||
this.clearNoTurnBuffers();
|
||||
|
||||
Reference in New Issue
Block a user