From 9c31eae9922614898fbb7ba94054e0f52a856167 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 9 Aug 2026 10:14:31 -0700 Subject: [PATCH 1/2] chat: prototype continuation turns after errors Model recovery as a new turn whose continuation message references the latest failed turn. Preserve the failed turn unchanged and add no new user input. This types-only prototype intentionally omits generated clients and schemas.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- types/channels-chat/actions.ts | 12 +- types/channels-chat/reducer.ts | 22 ++++ types/channels-chat/state.ts | 81 ++++++++++++-- ...tinuation-starts-new-turn-after-error.json | 95 ++++++++++++++++ ...ntinuation-completes-as-adjacent-turn.json | 103 +++++++++++++++++ ...tinuation-requires-latest-failed-turn.json | 104 ++++++++++++++++++ ...6-chat-continuation-rejects-new-input.json | 104 ++++++++++++++++++ ...t-continuation-noop-while-turn-active.json | 90 +++++++++++++++ 8 files changed, 596 insertions(+), 15 deletions(-) create mode 100644 types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json create mode 100644 types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json create mode 100644 types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json create mode 100644 types/test-cases/reducers/266-chat-continuation-rejects-new-input.json create mode 100644 types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json diff --git a/types/channels-chat/actions.ts b/types/channels-chat/actions.ts index caecaeb11..e60acc8ef 100644 --- a/types/channels-chat/actions.ts +++ b/types/channels-chat/actions.ts @@ -20,6 +20,7 @@ import type { ToolCallRiskAssessment, ToolInput, Turn, + TurnMessage, } from './state.js'; import { ToolCallConfirmationReason, @@ -56,9 +57,12 @@ interface ToolCallActionBase { // ─── Chat Actions ─────────────────────────────────────────────────────────── /** - * A new message has been sent to the agent, and a new turn starts. + * A new turn starts from a user message or continuation. * - * A client is only allowed to send {@link MessageKind.User} messages. + * A client may start a turn with a {@link MessageKind.User} message, or + * continue the latest failed turn with a {@link MessageKind.Continuation} + * message. A continuation starts a new turn and leaves the failed turn + * unchanged in history. * * @category Chat Actions * @version 1 @@ -70,8 +74,8 @@ export interface ChatTurnStartedAction { turnId: string; /** ISO 8601 timestamp when this turn started. */ startedAt: string; - /** The new message */ - message: Message; + /** The message that initiates the turn. */ + message: TurnMessage; /** If this turn was auto-started from a queued message, the ID of that message */ queuedMessageId?: string; /** diff --git a/types/channels-chat/reducer.ts b/types/channels-chat/reducer.ts index b170cefb2..888644703 100644 --- a/types/channels-chat/reducer.ts +++ b/types/channels-chat/reducer.ts @@ -16,6 +16,8 @@ import type { PendingMessage, ConfirmationOption, ToolCallContributor, + ContinuationMessage, + TurnMessage, } from './state.js'; import { TurnState, @@ -25,6 +27,7 @@ import { ToolCallContributorKind, ResponsePartKind, PendingMessageKind, + MessageKind, } from './state.js'; import { SessionStatus } from '../channels-session/state.js'; import type { ChatAction } from '../action-origin.generated.js'; @@ -32,6 +35,10 @@ import { softAssertNever } from '../common/reducer-helpers.js'; // ─── Helpers ───────────────────────────────────────────────────────────────── +function isContinuationMessage(message: TurnMessage): message is ContinuationMessage { + return message.origin.kind === MessageKind.Continuation; +} + /** Extracts the common base fields shared by all tool call lifecycle states. */ function tcBase(tc: ToolCallState) { return { @@ -343,6 +350,21 @@ export function chatReducer(state: ChatState, action: ChatAction, log?: (msg: st // ── Turn Lifecycle ──────────────────────────────────────────────────── case ActionType.ChatTurnStarted: { + if (isContinuationMessage(action.message)) { + const previousTurn = state.turns[state.turns.length - 1]; + if ( + state.activeTurn + || !previousTurn + || previousTurn.id !== action.message.origin.turnId + || previousTurn.state !== TurnState.Error + || state.turns.some(turn => turn.id === action.turnId) + || action.message.text.length > 0 + || action.message.attachments !== undefined + || action.queuedMessageId !== undefined + ) { + return state; + } + } let next: ChatState = { ...state, activeTurn: { diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 8e107a23b..67801026d 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -553,7 +553,7 @@ export interface Turn { /** Turn duration in milliseconds. */ duration?: number; /** The message that initiated the turn */ - message: Message; + message: TurnMessage; /** * All response content in stream order: text, tool calls, reasoning, and content refs. * @@ -580,7 +580,7 @@ export interface ActiveTurn { /** ISO 8601 timestamp when this turn started. */ startedAt: string; /** The message that initiated the turn */ - message: Message; + message: TurnMessage; /** * All response content in stream order: text, tool calls, reasoning, and content refs. * @@ -592,7 +592,8 @@ export interface ActiveTurn { } /** - * Discriminant for {@link MessageOrigin} — identifies who produced a message. + * Discriminant for {@link MessageOrigin} — identifies a message's origin or + * continuation provenance. * * @category Turn Types */ @@ -611,23 +612,53 @@ export enum MessageKind { Tool = 'tool', /** A system-generated notification rather than a direct user message. */ SystemNotification = 'systemNotification', + /** + * Starts a new turn that continues a preceding failed turn without adding + * another user message. + */ + Continuation = 'continuation', } /** - * Identifies the origin of a {@link Message} — who produced it. For the message - * that initiates a turn ({@link Turn.message}), this is also the origin of the - * turn; for steering or queued messages it is just the origin of that message. + * Identifies the actor that produced an ordinary {@link Message}. * * @category Turn Types */ -export interface MessageOrigin { +export interface ActorMessageOrigin { /** The kind of actor that produced the message. */ - kind: MessageKind; + kind: MessageKind.User + | MessageKind.Agent + | MessageKind.Tool + | MessageKind.SystemNotification; +} + +/** + * Identifies a message that starts a new turn as a continuation of a preceding + * failed turn. + * + * Continuation messages carry no new user input: {@link Message.text} MUST be + * empty and {@link Message.attachments} MUST be absent. The referenced turn + * remains unchanged in history. + * + * @category Turn Types + */ +export interface ContinuationMessageOrigin { + /** Discriminant */ + kind: MessageKind.Continuation; + /** Identifier of the failed turn being continued. */ + turnId: string; } /** - * A message that initiates or steers a turn. Messages can originate from the - * user, the agent, a tool, or be system-generated (see {@link MessageOrigin}). + * Identifies the origin or provenance of a {@link TurnMessage}. + * + * @category Turn Types + */ +export type MessageOrigin = ActorMessageOrigin | ContinuationMessageOrigin; + +/** + * An ordinary message that initiates or steers a turn. Messages can originate + * from the user, the agent, a tool, or be system-generated. * * Attachments MAY be referenced inside {@link Message.text} via their * {@link MessageAttachmentBase.range} field. Attachments without a range are @@ -640,7 +671,7 @@ export interface Message { /** Message text */ text: string; /** The origin of the message */ - origin: MessageOrigin; + origin: ActorMessageOrigin; /** File/selection attachments */ attachments?: MessageAttachment[]; /** @@ -671,6 +702,34 @@ export interface Message { _meta?: Record; } +/** + * A message that starts a new turn to continue a preceding failed turn without + * adding user input. + * + * @category Turn Types + */ +export interface ContinuationMessage { + /** Continuations add no message text. */ + text: ''; + /** Continuation provenance. */ + origin: ContinuationMessageOrigin; + /** Continuations cannot carry attachments. */ + attachments?: never; + /** Optional model override for the continuation attempt. */ + model?: ModelSelection; + /** Optional custom-agent override for the continuation attempt. */ + agent?: AgentSelection; + /** Additional provider-specific metadata for this continuation. */ + _meta?: Record; +} + +/** + * A message that starts a turn. + * + * @category Turn Types + */ +export type TurnMessage = Message | ContinuationMessage; + /** * Common fields shared by all {@link MessageAttachment} variants. * diff --git a/types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json b/types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json new file mode 100644 index 000000000..a02e64c25 --- /dev/null +++ b/types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json @@ -0,0 +1,95 @@ +{ + "description": "chat/turnStarted starts a new continuation turn after the latest failed turn without rewriting history", + "reducer": "chat", + "initial": { + "turns": [ + { + "id": "turn-1", + "startedAt": "1970-01-01T00:00:01.000Z", + "duration": 1000, + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [ + { + "kind": "markdown", + "id": "markdown-1", + "content": "Partial response" + } + ], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 2, + "modifiedAt": "1970-01-01T00:00:02.000Z" + }, + "actions": [ + { + "type": "chat/turnStarted", + "turnId": "turn-2", + "startedAt": "1970-01-01T00:00:03.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + } + } + ], + "expected": { + "turns": [ + { + "id": "turn-1", + "startedAt": "1970-01-01T00:00:01.000Z", + "duration": 1000, + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [ + { + "kind": "markdown", + "id": "markdown-1", + "content": "Partial response" + } + ], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + } + ], + "activeTurn": { + "id": "turn-2", + "startedAt": "1970-01-01T00:00:03.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + }, + "responseParts": [], + "usage": null + }, + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 8, + "modifiedAt": "1970-01-01T00:00:09.999Z" + } +} diff --git a/types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json b/types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json new file mode 100644 index 000000000..13b1aabcb --- /dev/null +++ b/types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json @@ -0,0 +1,103 @@ +{ + "description": "a continuation completes as a distinct turn adjacent to the failed source turn", + "reducer": "chat", + "initial": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 2, + "modifiedAt": "1970-01-01T00:00:02.000Z" + }, + "actions": [ + { + "type": "chat/turnStarted", + "turnId": "turn-2", + "startedAt": "1970-01-01T00:00:03.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + } + }, + { + "type": "chat/responsePart", + "turnId": "turn-2", + "part": { + "kind": "markdown", + "id": "markdown-2", + "content": "Recovered" + } + }, + { + "type": "chat/turnComplete", + "turnId": "turn-2", + "duration": 2000 + } + ], + "expected": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + }, + { + "id": "turn-2", + "startedAt": "1970-01-01T00:00:03.000Z", + "duration": 2000, + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + }, + "responseParts": [ + { + "kind": "markdown", + "id": "markdown-2", + "content": "Recovered" + } + ], + "usage": null, + "state": "complete", + "error": null + } + ], + "activeTurn": null, + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 1, + "modifiedAt": "1970-01-01T00:00:09.999Z" + } +} diff --git a/types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json b/types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json new file mode 100644 index 000000000..8d5f3d574 --- /dev/null +++ b/types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json @@ -0,0 +1,104 @@ +{ + "description": "continuation starts are ignored unless they reference the latest failed turn", + "reducer": "chat", + "initial": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "First", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "First failure" + } + }, + { + "id": "turn-2", + "message": { + "text": "Second", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "complete", + "error": null + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 1, + "modifiedAt": "1970-01-01T00:00:02.000Z" + }, + "actions": [ + { + "type": "chat/turnStarted", + "turnId": "turn-3", + "startedAt": "1970-01-01T00:00:03.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + } + }, + { + "type": "chat/turnStarted", + "turnId": "turn-4", + "startedAt": "1970-01-01T00:00:04.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-2" + } + } + } + ], + "expected": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "First", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "First failure" + } + }, + { + "id": "turn-2", + "message": { + "text": "Second", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "complete", + "error": null + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 1, + "modifiedAt": "1970-01-01T00:00:02.000Z" + } +} diff --git a/types/test-cases/reducers/266-chat-continuation-rejects-new-input.json b/types/test-cases/reducers/266-chat-continuation-rejects-new-input.json new file mode 100644 index 000000000..153f00e17 --- /dev/null +++ b/types/test-cases/reducers/266-chat-continuation-rejects-new-input.json @@ -0,0 +1,104 @@ +{ + "description": "continuation messages cannot smuggle new user input or queued-message identity", + "reducer": "chat", + "initial": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 2, + "modifiedAt": "1970-01-01T00:00:02.000Z" + }, + "actions": [ + { + "type": "chat/turnStarted", + "turnId": "turn-2", + "startedAt": "1970-01-01T00:00:03.000Z", + "message": { + "text": "Try again", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + } + }, + { + "type": "chat/turnStarted", + "turnId": "turn-3", + "startedAt": "1970-01-01T00:00:04.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + }, + "attachments": [] + } + }, + { + "type": "chat/turnStarted", + "turnId": "turn-4", + "startedAt": "1970-01-01T00:00:05.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + }, + "queuedMessageId": "queued-1" + }, + { + "type": "chat/turnStarted", + "turnId": "turn-1", + "startedAt": "1970-01-01T00:00:06.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + } + } + ], + "expected": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 2, + "modifiedAt": "1970-01-01T00:00:02.000Z" + } +} diff --git a/types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json b/types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json new file mode 100644 index 000000000..c33236c97 --- /dev/null +++ b/types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json @@ -0,0 +1,90 @@ +{ + "description": "a continuation cannot start while another turn is active", + "reducer": "chat", + "initial": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + } + ], + "activeTurn": { + "id": "turn-2", + "startedAt": "1970-01-01T00:00:02.000Z", + "message": { + "text": "Other work", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null + }, + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 8, + "modifiedAt": "1970-01-01T00:00:02.000Z" + }, + "actions": [ + { + "type": "chat/turnStarted", + "turnId": "turn-3", + "startedAt": "1970-01-01T00:00:03.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation", + "turnId": "turn-1" + } + } + } + ], + "expected": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "runtime", + "message": "Something broke" + } + } + ], + "activeTurn": { + "id": "turn-2", + "startedAt": "1970-01-01T00:00:02.000Z", + "message": { + "text": "Other work", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null + }, + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 8, + "modifiedAt": "1970-01-01T00:00:02.000Z" + } +} From 3fd2781c307d92db3a92f2a2f0d13a056c90b71a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 9 Aug 2026 11:09:50 -0700 Subject: [PATCH 2/2] chat: advertise continuation eligibility Mark continuable turn errors explicitly and make continuation turns implicitly follow the immediately preceding failed turn.\n\n(Written by Copilot)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- types/channels-chat/actions.ts | 7 +- types/channels-chat/reducer.ts | 5 +- types/channels-chat/state.ts | 27 +++++--- ...tinuation-starts-new-turn-after-error.json | 12 ++-- ...ntinuation-completes-as-adjacent-turn.json | 12 ++-- ...tinuation-requires-latest-failed-turn.json | 12 ++-- ...6-chat-continuation-rejects-new-input.json | 18 +++-- ...t-continuation-noop-while-turn-active.json | 9 +-- ...hat-continuation-requires-eligibility.json | 65 +++++++++++++++++++ 9 files changed, 122 insertions(+), 45 deletions(-) create mode 100644 types/test-cases/reducers/268-chat-continuation-requires-eligibility.json diff --git a/types/channels-chat/actions.ts b/types/channels-chat/actions.ts index e60acc8ef..91ebf6bd3 100644 --- a/types/channels-chat/actions.ts +++ b/types/channels-chat/actions.ts @@ -5,7 +5,7 @@ */ import { ActionType } from '../common/actions.js'; -import type { StringOrMarkdown, ErrorInfo, FileEdit, UsageInfo, URI } from '../common/state.js'; +import type { StringOrMarkdown, FileEdit, UsageInfo, URI } from '../common/state.js'; import type { McpAuthRequirement } from '../channels-session/state.js'; import type { Message, @@ -20,6 +20,7 @@ import type { ToolCallRiskAssessment, ToolInput, Turn, + TurnError, TurnMessage, } from './state.js'; import { @@ -492,8 +493,8 @@ export interface ChatErrorAction { * data. */ duration: number; - /** Error details */ - error: ErrorInfo; + /** Error details and optional continuation eligibility. */ + error: TurnError; /** * Additional provider-specific metadata for this action. * diff --git a/types/channels-chat/reducer.ts b/types/channels-chat/reducer.ts index 888644703..d37c47e54 100644 --- a/types/channels-chat/reducer.ts +++ b/types/channels-chat/reducer.ts @@ -17,6 +17,7 @@ import type { ConfirmationOption, ToolCallContributor, ContinuationMessage, + TurnError, TurnMessage, } from './state.js'; import { @@ -177,7 +178,7 @@ function endTurn( turnState: TurnState, duration: number, terminalStatus?: SessionStatus.Error, - error?: { errorType: string; message: string; stack?: string }, + error?: TurnError, ): ChatState { if (!state.activeTurn || state.activeTurn.id !== turnId) { return state; @@ -355,8 +356,8 @@ export function chatReducer(state: ChatState, action: ChatAction, log?: (msg: st if ( state.activeTurn || !previousTurn - || previousTurn.id !== action.message.origin.turnId || previousTurn.state !== TurnState.Error + || previousTurn.error?.continuation !== true || state.turns.some(turn => turn.id === action.turnId) || action.message.text.length > 0 || action.message.attachments !== undefined diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 67801026d..6a6b9ba4e 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -522,6 +522,20 @@ export const enum TurnState { Error = 'error', } +/** + * Error details for a failed turn. + * + * When {@link continuation} is present, a client may start a new adjacent turn + * with a {@link ContinuationMessage}. The continuation proceeds from the + * failed turn without adding user input or changing the failed turn. + * + * @category Turn Types + */ +export interface TurnError extends ErrorInfo { + /** Whether the latest failed turn may be continued. */ + continuation?: true; +} + /** * Discriminant for {@link MessageAttachment} variants. * @@ -566,7 +580,7 @@ export interface Turn { /** How the turn ended */ state: TurnState; /** Error details if state is `'error'` */ - error?: ErrorInfo; + error?: TurnError; } /** @@ -633,20 +647,17 @@ export interface ActorMessageOrigin { } /** - * Identifies a message that starts a new turn as a continuation of a preceding - * failed turn. + * Identifies a message that starts a new turn as a continuation of the + * immediately preceding failed turn. * - * Continuation messages carry no new user input: {@link Message.text} MUST be - * empty and {@link Message.attachments} MUST be absent. The referenced turn - * remains unchanged in history. + * Continuation messages carry no new user input: their text is empty and they + * have no attachments. The preceding turn remains unchanged in history. * * @category Turn Types */ export interface ContinuationMessageOrigin { /** Discriminant */ kind: MessageKind.Continuation; - /** Identifier of the failed turn being continued. */ - turnId: string; } /** diff --git a/types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json b/types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json index a02e64c25..e9fd0d4fe 100644 --- a/types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json +++ b/types/test-cases/reducers/263-chat-continuation-starts-new-turn-after-error.json @@ -24,7 +24,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } } ], @@ -41,8 +42,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } } } @@ -70,7 +70,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } } ], @@ -80,8 +81,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } }, "responseParts": [], diff --git a/types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json b/types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json index 13b1aabcb..ef7437455 100644 --- a/types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json +++ b/types/test-cases/reducers/264-chat-continuation-completes-as-adjacent-turn.json @@ -16,7 +16,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } } ], @@ -33,8 +34,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } } }, @@ -68,7 +68,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } }, { @@ -78,8 +79,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } }, "responseParts": [ diff --git a/types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json b/types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json index 8d5f3d574..3890566c5 100644 --- a/types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json +++ b/types/test-cases/reducers/265-chat-continuation-requires-latest-failed-turn.json @@ -16,7 +16,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "First failure" + "message": "First failure", + "continuation": true } }, { @@ -46,8 +47,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } } }, @@ -58,8 +58,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-2" + "kind": "continuation" } } } @@ -79,7 +78,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "First failure" + "message": "First failure", + "continuation": true } }, { diff --git a/types/test-cases/reducers/266-chat-continuation-rejects-new-input.json b/types/test-cases/reducers/266-chat-continuation-rejects-new-input.json index 153f00e17..f20376a99 100644 --- a/types/test-cases/reducers/266-chat-continuation-rejects-new-input.json +++ b/types/test-cases/reducers/266-chat-continuation-rejects-new-input.json @@ -16,7 +16,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } } ], @@ -33,8 +34,7 @@ "message": { "text": "Try again", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } } }, @@ -45,8 +45,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" }, "attachments": [] } @@ -58,8 +57,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } }, "queuedMessageId": "queued-1" @@ -71,8 +69,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } } } @@ -92,7 +89,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } } ], diff --git a/types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json b/types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json index c33236c97..a3f950b2e 100644 --- a/types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json +++ b/types/test-cases/reducers/267-chat-continuation-noop-while-turn-active.json @@ -16,7 +16,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } } ], @@ -45,8 +46,7 @@ "message": { "text": "", "origin": { - "kind": "continuation", - "turnId": "turn-1" + "kind": "continuation" } } } @@ -66,7 +66,8 @@ "state": "error", "error": { "errorType": "runtime", - "message": "Something broke" + "message": "Something broke", + "continuation": true } } ], diff --git a/types/test-cases/reducers/268-chat-continuation-requires-eligibility.json b/types/test-cases/reducers/268-chat-continuation-requires-eligibility.json new file mode 100644 index 000000000..1b95cc3b7 --- /dev/null +++ b/types/test-cases/reducers/268-chat-continuation-requires-eligibility.json @@ -0,0 +1,65 @@ +{ + "description": "a failed turn without continuation eligibility cannot be continued", + "reducer": "chat", + "initial": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "invalidRequest", + "message": "The request cannot be retried" + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 2, + "modifiedAt": "1970-01-01T00:00:02.000Z" + }, + "actions": [ + { + "type": "chat/turnStarted", + "turnId": "turn-2", + "startedAt": "1970-01-01T00:00:03.000Z", + "message": { + "text": "", + "origin": { + "kind": "continuation" + } + } + } + ], + "expected": { + "turns": [ + { + "id": "turn-1", + "message": { + "text": "Fix the bug", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null, + "state": "error", + "error": { + "errorType": "invalidRequest", + "message": "The request cannot be retried" + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 2, + "modifiedAt": "1970-01-01T00:00:02.000Z" + } +}