@company-semantics/contracts 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.ts CHANGED
@@ -133,6 +133,38 @@ export type {
133
133
  AssistantMessagePart,
134
134
  PartBuilderState,
135
135
  AddPartResult,
136
+ // Preview types (Phase 3)
137
+ PreviewArtifactKind,
138
+ PreviewArtifact,
139
+ PreviewData,
140
+ PreviewPart,
141
+ PreviewDataPart,
142
+ // Confirmation types (Phase 4)
143
+ ConfirmationChoice,
144
+ ConfirmationRisk,
145
+ ConfirmationData,
146
+ ConfirmationDataPart,
147
+ ConfirmationResponseData,
148
+ ConfirmationResponsePart,
149
+ ConfirmationResponseDataPart,
150
+ // Execution result types (Phase 5)
151
+ ExecutionArtifactStatus,
152
+ ExecutionResultData,
153
+ ExecutionResultPart,
154
+ ExecutionResultDataPart,
155
+ // Message lifecycle types (Phase 1 streaming)
156
+ // @see ADR-CONT-027 for design rationale
157
+ StreamPhase,
158
+ MessageLifecycleEventType,
159
+ MessageLifecycleEvent,
160
+ MessageStartEvent,
161
+ MessageDeltaEvent,
162
+ MessageCompleteEvent,
163
+ ToolResultRenderClass,
164
+ ToolResultPart,
165
+ MessageStartDataPart,
166
+ MessageDeltaDataPart,
167
+ MessageCompleteDataPart,
136
168
  } from './message-parts/index.js'
137
169
 
138
170
  export {
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { WireSurfaceBuilder } from '../wire.js'
3
+ import type { ConfirmationData, ConfirmationResponseData } from '../confirmation.js'
4
+
5
+ describe('WireSurfaceBuilder.confirmation', () => {
6
+ it('creates a confirmation data part', () => {
7
+ const data: ConfirmationData = {
8
+ actionId: 'msg-123',
9
+ title: 'Send Slack message to #sales',
10
+ prompt: 'Confirm sending this message?',
11
+ risk: 'low',
12
+ }
13
+
14
+ const result = WireSurfaceBuilder.confirmation(data)
15
+
16
+ expect(result.type).toBe('data-confirmation')
17
+ expect(result.data).toEqual(data)
18
+ })
19
+
20
+ it('preserves actionId exactly', () => {
21
+ const data: ConfirmationData = {
22
+ actionId: 'complex-action-id-12345',
23
+ title: 'Test',
24
+ prompt: 'Confirm?',
25
+ }
26
+
27
+ const result = WireSurfaceBuilder.confirmation(data)
28
+
29
+ expect(result.data.actionId).toBe('complex-action-id-12345')
30
+ })
31
+
32
+ it('handles confirmation without optional risk level', () => {
33
+ const data: ConfirmationData = {
34
+ actionId: 'no-risk-specified',
35
+ title: 'Simple action',
36
+ prompt: 'Do you want to proceed?',
37
+ }
38
+
39
+ const result = WireSurfaceBuilder.confirmation(data)
40
+
41
+ expect(result.type).toBe('data-confirmation')
42
+ expect(result.data.risk).toBeUndefined()
43
+ })
44
+
45
+ it('preserves all risk levels', () => {
46
+ const risks: Array<'low' | 'medium' | 'high'> = ['low', 'medium', 'high']
47
+
48
+ for (const risk of risks) {
49
+ const data: ConfirmationData = {
50
+ actionId: `risk-${risk}`,
51
+ title: `${risk} risk action`,
52
+ prompt: 'Confirm?',
53
+ risk,
54
+ }
55
+
56
+ const result = WireSurfaceBuilder.confirmation(data)
57
+
58
+ expect(result.data.risk).toBe(risk)
59
+ }
60
+ })
61
+ })
62
+
63
+ describe('WireSurfaceBuilder.confirmationResponse', () => {
64
+ it('creates a confirmation response for confirm choice', () => {
65
+ const data: ConfirmationResponseData = {
66
+ actionId: 'msg-123',
67
+ choice: 'confirm',
68
+ }
69
+
70
+ const result = WireSurfaceBuilder.confirmationResponse(data)
71
+
72
+ expect(result.type).toBe('data-confirmation-response')
73
+ expect(result.data.actionId).toBe('msg-123')
74
+ expect(result.data.choice).toBe('confirm')
75
+ })
76
+
77
+ it('creates a confirmation response for cancel choice', () => {
78
+ const data: ConfirmationResponseData = {
79
+ actionId: 'msg-123',
80
+ choice: 'cancel',
81
+ }
82
+
83
+ const result = WireSurfaceBuilder.confirmationResponse(data)
84
+
85
+ expect(result.type).toBe('data-confirmation-response')
86
+ expect(result.data.choice).toBe('cancel')
87
+ })
88
+
89
+ it('preserves actionId exactly in response', () => {
90
+ const data: ConfirmationResponseData = {
91
+ actionId: 'complex-action-id-12345',
92
+ choice: 'confirm',
93
+ }
94
+
95
+ const result = WireSurfaceBuilder.confirmationResponse(data)
96
+
97
+ expect(result.data.actionId).toBe('complex-action-id-12345')
98
+ })
99
+ })
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { WireSurfaceBuilder } from '../wire.js'
3
+ import type { PreviewData, PreviewArtifact } from '../preview.js'
4
+
5
+ describe('WireSurfaceBuilder.preview', () => {
6
+ it('creates a preview data part', () => {
7
+ const data: PreviewData = {
8
+ actionId: 'msg-123',
9
+ title: 'Send Slack message to #sales',
10
+ description: 'This will notify the sales team about the new pricing.',
11
+ artifacts: [
12
+ {
13
+ kind: 'message',
14
+ label: '#sales',
15
+ content: {
16
+ channel: 'C123456',
17
+ text: 'New pricing is live!',
18
+ },
19
+ },
20
+ ],
21
+ }
22
+
23
+ const result = WireSurfaceBuilder.preview(data)
24
+
25
+ expect(result.type).toBe('data-preview')
26
+ expect(result.data).toEqual(data)
27
+ })
28
+
29
+ it('preserves artifact structure exactly', () => {
30
+ const artifacts: PreviewArtifact[] = [
31
+ {
32
+ kind: 'api_call',
33
+ label: 'POST /api/notify',
34
+ content: { method: 'POST', body: { message: 'test' } },
35
+ metadata: { target: 'internal-api', impact: 'low' },
36
+ },
37
+ {
38
+ kind: 'notification',
39
+ label: 'Email to team@example.com',
40
+ content: { to: 'team@example.com', subject: 'Update' },
41
+ },
42
+ ]
43
+
44
+ const data: PreviewData = {
45
+ actionId: 'multi-123',
46
+ title: 'Multi-step action',
47
+ artifacts,
48
+ }
49
+
50
+ const result = WireSurfaceBuilder.preview(data)
51
+
52
+ expect(result.data.artifacts).toEqual(artifacts)
53
+ expect(result.data.artifacts[0].metadata?.impact).toBe('low')
54
+ })
55
+
56
+ it('handles empty artifacts array', () => {
57
+ const data: PreviewData = {
58
+ actionId: 'empty-preview',
59
+ title: 'No actions preview',
60
+ artifacts: [],
61
+ }
62
+
63
+ const result = WireSurfaceBuilder.preview(data)
64
+
65
+ expect(result.type).toBe('data-preview')
66
+ expect(result.data.artifacts).toEqual([])
67
+ })
68
+
69
+ it('preserves all artifact kinds', () => {
70
+ const allKinds: PreviewArtifact[] = [
71
+ { kind: 'message', label: 'Slack message', content: {} },
72
+ { kind: 'api_call', label: 'API call', content: {} },
73
+ { kind: 'diff', label: 'File change', content: {} },
74
+ { kind: 'notification', label: 'Notification', content: {} },
75
+ { kind: 'task', label: 'Task creation', content: {} },
76
+ { kind: 'calendar', label: 'Calendar event', content: {} },
77
+ ]
78
+
79
+ const data: PreviewData = {
80
+ actionId: 'all-kinds',
81
+ title: 'All artifact kinds',
82
+ artifacts: allKinds,
83
+ }
84
+
85
+ const result = WireSurfaceBuilder.preview(data)
86
+
87
+ expect(result.data.artifacts).toHaveLength(6)
88
+ expect(result.data.artifacts.map((a) => a.kind)).toEqual([
89
+ 'message',
90
+ 'api_call',
91
+ 'diff',
92
+ 'notification',
93
+ 'task',
94
+ 'calendar',
95
+ ])
96
+ })
97
+ })
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Confirmation Surface Types
3
+ *
4
+ * Confirmation surfaces request explicit user decision on a proposed action.
5
+ * Only structured signals are accepted - no natural language inference.
6
+ *
7
+ * CONFIRMATION MODE INVARIANTS:
8
+ * - Confirmation is deterministic and system-owned
9
+ * - No tool calls are allowed in confirmation mode
10
+ * - Confirmation must reference a specific actionId
11
+ * - Only structured confirmation-response signals are accepted
12
+ * - Natural language is never used to infer confirmation ("yes", "go ahead" = invalid)
13
+ * - Execution is impossible in Phase 4 - only Phase 5 can execute
14
+ * - At most one action is confirmable at a time
15
+ * - Mismatched actionId fails closed and re-prompts
16
+ *
17
+ * @see DECISIONS.md for design rationale
18
+ */
19
+
20
+ /**
21
+ * Choice options for confirmation.
22
+ */
23
+ export type ConfirmationChoice = 'confirm' | 'cancel';
24
+
25
+ /**
26
+ * Risk level for confirmation prompts.
27
+ */
28
+ export type ConfirmationRisk = 'low' | 'medium' | 'high';
29
+
30
+ /**
31
+ * Confirmation request data payload.
32
+ */
33
+ export interface ConfirmationData {
34
+ /** Must match the active preview's actionId */
35
+ actionId: string;
36
+ /** Summary of the action being confirmed */
37
+ title: string;
38
+ /** The confirmation prompt, e.g. "Confirm sending this Slack message?" */
39
+ prompt: string;
40
+ /** Optional risk level for UI styling */
41
+ risk?: ConfirmationRisk;
42
+ }
43
+
44
+ /**
45
+ * Confirmation request message part (semantic type).
46
+ */
47
+ export interface ConfirmationPart {
48
+ type: 'confirmation';
49
+ data: ConfirmationData;
50
+ }
51
+
52
+ /**
53
+ * Confirmation request data part (wire format).
54
+ */
55
+ export interface ConfirmationDataPart {
56
+ type: 'data-confirmation';
57
+ data: ConfirmationData;
58
+ }
59
+
60
+ // === Confirmation Response Types (New in Phase 4) ===
61
+
62
+ /**
63
+ * Confirmation response data payload.
64
+ * Sent by the user (via UI button or structured message) to confirm/cancel.
65
+ */
66
+ export interface ConfirmationResponseData {
67
+ /** Must match the active confirmation's actionId */
68
+ actionId: string;
69
+ /** The user's decision */
70
+ choice: ConfirmationChoice;
71
+ }
72
+
73
+ /**
74
+ * Confirmation response message part (semantic type).
75
+ */
76
+ export interface ConfirmationResponsePart {
77
+ type: 'confirmation-response';
78
+ data: ConfirmationResponseData;
79
+ }
80
+
81
+ /**
82
+ * Confirmation response data part (wire format).
83
+ */
84
+ export interface ConfirmationResponseDataPart {
85
+ type: 'data-confirmation-response';
86
+ data: ConfirmationResponseData;
87
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Execution Result Surface Types
3
+ *
4
+ * Surfaces for displaying execution results to the user.
5
+ *
6
+ * EXECUTION MODE INVARIANTS:
7
+ * - Execution occurs only after confirmation with matching actionId
8
+ * - Each actionId executes at most once (idempotency)
9
+ * - All executions produce audit records (success or failure)
10
+ * - Execution is synchronous and deterministic
11
+ * - Tools are explicitly allowlisted per artifact kind
12
+ * - No retries, no background work, no rollback
13
+ * - Failures stop execution immediately
14
+ *
15
+ * @see DECISIONS.md for design rationale
16
+ */
17
+
18
+ import type { PreviewArtifactKind } from './preview.js';
19
+
20
+ /**
21
+ * Status of a single artifact's execution.
22
+ */
23
+ export interface ExecutionArtifactStatus {
24
+ kind: PreviewArtifactKind;
25
+ label: string;
26
+ status: 'success' | 'failed' | 'skipped';
27
+ error?: string;
28
+ }
29
+
30
+ /**
31
+ * Execution result data payload.
32
+ */
33
+ export interface ExecutionResultData {
34
+ actionId: string;
35
+ status: 'success' | 'failed';
36
+ artifacts: ExecutionArtifactStatus[];
37
+ }
38
+
39
+ /**
40
+ * Execution result message part (semantic type).
41
+ */
42
+ export interface ExecutionResultPart {
43
+ type: 'execution-result';
44
+ data: ExecutionResultData;
45
+ }
46
+
47
+ /**
48
+ * Execution result data part (wire format).
49
+ */
50
+ export interface ExecutionResultDataPart {
51
+ type: 'data-execution-result';
52
+ data: ExecutionResultData;
53
+ }
@@ -14,11 +14,54 @@ export type {
14
14
  ChartPart,
15
15
  ChartDataPoint,
16
16
  TablePart,
17
- ConfirmationPart,
18
17
  SurfacePart,
19
18
  AssistantMessagePart,
20
19
  } from './types.js'
21
20
 
21
+ // Preview types
22
+ export type {
23
+ PreviewArtifactKind,
24
+ PreviewArtifact,
25
+ PreviewData,
26
+ PreviewPart,
27
+ PreviewDataPart,
28
+ } from './preview.js'
29
+
30
+ // Confirmation types
31
+ export type {
32
+ ConfirmationChoice,
33
+ ConfirmationRisk,
34
+ ConfirmationData,
35
+ ConfirmationPart,
36
+ ConfirmationDataPart,
37
+ ConfirmationResponseData,
38
+ ConfirmationResponsePart,
39
+ ConfirmationResponseDataPart,
40
+ } from './confirmation.js'
41
+
42
+ // Execution result types
43
+ export type {
44
+ ExecutionArtifactStatus,
45
+ ExecutionResultData,
46
+ ExecutionResultPart,
47
+ ExecutionResultDataPart,
48
+ } from './execution.js'
49
+
50
+ // Lifecycle types (Phase 1 streaming)
51
+ export type {
52
+ StreamPhase,
53
+ MessageLifecycleEventType,
54
+ MessageLifecycleEvent,
55
+ MessageStartEvent,
56
+ MessageDeltaEvent,
57
+ MessageCompleteEvent,
58
+ ToolResultRenderClass,
59
+ ToolResultPart,
60
+ MessageStartDataPart,
61
+ MessageDeltaDataPart,
62
+ MessageCompleteDataPart,
63
+ } from './lifecycle.js'
64
+
22
65
  // Type guards
23
66
  export { isTextPart, isSurfacePart } from './types.js'
24
67
 
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Message Lifecycle Event Vocabulary
3
+ *
4
+ * Explicit lifecycle events for message streaming.
5
+ * Backend is the single authority for message lifecycle state.
6
+ *
7
+ * INVARIANTS:
8
+ * - Every message stream MUST emit: start -> delta* -> complete
9
+ * - messageId MUST be consistent across all events for a single message
10
+ * - version field enables protocol evolution without breaking changes
11
+ *
12
+ * @see ADR-CONT-027 for design rationale
13
+ */
14
+
15
+ // =============================================================================
16
+ // Stream Phase
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Derived stream phase for UI state management.
21
+ * Frontend derives this from lifecycle events, not vice versa.
22
+ *
23
+ * - idle: No active stream (initial state, or after complete)
24
+ * - streaming: Between message.start and message.complete
25
+ * - complete: After message.complete received
26
+ */
27
+ export type StreamPhase = 'idle' | 'streaming' | 'complete'
28
+
29
+ // =============================================================================
30
+ // Lifecycle Event Types
31
+ // =============================================================================
32
+
33
+ /**
34
+ * Lifecycle event type discriminator.
35
+ */
36
+ export type MessageLifecycleEventType =
37
+ | 'message.start'
38
+ | 'message.delta'
39
+ | 'message.complete'
40
+
41
+ /**
42
+ * Base interface for all message lifecycle events.
43
+ *
44
+ * @property type - Event discriminator
45
+ * @property version - Protocol version (add now, expensive to retrofit later)
46
+ * @property messageId - Correlation identifier for the message
47
+ * @property timestamp - ISO 8601 timestamp of event emission
48
+ */
49
+ export interface MessageLifecycleEvent {
50
+ type: MessageLifecycleEventType
51
+ version: 1
52
+ messageId: string
53
+ timestamp: string
54
+ }
55
+
56
+ /**
57
+ * Message start event.
58
+ * Emitted when a new message stream begins.
59
+ */
60
+ export interface MessageStartEvent extends MessageLifecycleEvent {
61
+ type: 'message.start'
62
+ }
63
+
64
+ /**
65
+ * Message delta event.
66
+ * Emitted for each text chunk during streaming.
67
+ * Only contains narrative text, not surface parts.
68
+ */
69
+ export interface MessageDeltaEvent extends MessageLifecycleEvent {
70
+ type: 'message.delta'
71
+ /** The text delta (narrative content only) */
72
+ delta: string
73
+ }
74
+
75
+ /**
76
+ * Message complete event.
77
+ * Emitted when the message stream ends.
78
+ * Signals that all narrative content has been streamed.
79
+ */
80
+ export interface MessageCompleteEvent extends MessageLifecycleEvent {
81
+ type: 'message.complete'
82
+ /** Total length of narrative content for validation */
83
+ narrativeLength: number
84
+ }
85
+
86
+ // =============================================================================
87
+ // Tool Result Rendering Classification
88
+ // =============================================================================
89
+
90
+ /**
91
+ * Tool result rendering classification.
92
+ * This is protocol metadata, not a UI decision.
93
+ *
94
+ * - inline: Render during streaming (narrative-like)
95
+ * Examples: search snippets, numbers, short text, intermediate reasoning
96
+ * No atomicity guarantee, safe to interleave with text
97
+ *
98
+ * - surface: Render only after message.complete (UI commitment)
99
+ * Examples: tool lists, cards, dashboards, MCP UI components
100
+ * Atomic, deterministic, never stream
101
+ */
102
+ export type ToolResultRenderClass = 'inline' | 'surface'
103
+
104
+ /**
105
+ * Tool result part with rendering classification.
106
+ */
107
+ export interface ToolResultPart {
108
+ type: 'tool-result'
109
+ /** Tool identifier */
110
+ toolName: string
111
+ /** Rendering classification */
112
+ render: ToolResultRenderClass
113
+ /** Tool-specific payload */
114
+ payload: unknown
115
+ /** Optional message correlation (enables out-of-band arrival, multi-message chains) */
116
+ messageId?: string
117
+ }
118
+
119
+ // =============================================================================
120
+ // Wire Format Types
121
+ // =============================================================================
122
+
123
+ /**
124
+ * Wire format for message start event.
125
+ * Follows AI SDK's data-{name} convention.
126
+ */
127
+ export interface MessageStartDataPart {
128
+ type: 'data-message-start'
129
+ data: Omit<MessageStartEvent, 'type'>
130
+ }
131
+
132
+ /**
133
+ * Wire format for message delta event.
134
+ * Note: Standard text-delta may be used instead for AI SDK compatibility.
135
+ */
136
+ export interface MessageDeltaDataPart {
137
+ type: 'data-message-delta'
138
+ data: Omit<MessageDeltaEvent, 'type'>
139
+ }
140
+
141
+ /**
142
+ * Wire format for message complete event.
143
+ */
144
+ export interface MessageCompleteDataPart {
145
+ type: 'data-message-complete'
146
+ data: Omit<MessageCompleteEvent, 'type'>
147
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Preview Surface Types
3
+ *
4
+ * Preview surfaces represent exactly what would happen if an action were executed.
5
+ * They are read-only by contract and contain no side effects.
6
+ *
7
+ * PREVIEW MODE INVARIANTS:
8
+ * - Preview surfaces are read-only
9
+ * - Preview mode allows zero side effects
10
+ * - Preview artifacts must be exactly executable later
11
+ * - Execution is impossible without a later mode transition
12
+ * - LLMs never execute actions in preview mode
13
+ * - Preview mode is entered when system emits data-preview surface (not user intent)
14
+ * - User messages during preview do not affect mode; only assistant output controls persistence
15
+ * - At most one preview proposal is active at a time (multiple previews are sequential, not concurrent)
16
+ *
17
+ * @see DECISIONS.md for design rationale
18
+ */
19
+
20
+ /**
21
+ * Preview artifact kinds.
22
+ * Each kind represents a different type of action that could be executed.
23
+ */
24
+ export type PreviewArtifactKind =
25
+ | 'message' // Slack/chat message to send
26
+ | 'api_call' // External API call
27
+ | 'diff' // File or document change
28
+ | 'notification' // Notification to user/team
29
+ | 'task' // Task creation
30
+ | 'calendar' // Calendar event
31
+
32
+ /**
33
+ * A single artifact within a preview.
34
+ * Represents one atomic action that would be taken.
35
+ */
36
+ export interface PreviewArtifact {
37
+ /** The kind of action this artifact represents */
38
+ kind: PreviewArtifactKind
39
+ /** Human-readable label for this artifact */
40
+ label: string
41
+ /** The exact payload that would be executed - must be JSON-safe */
42
+ content: unknown
43
+ /** Optional metadata for display purposes */
44
+ metadata?: {
45
+ /** Target system (e.g., "slack", "google-calendar") */
46
+ target?: string
47
+ /** Estimated impact level */
48
+ impact?: 'low' | 'medium' | 'high'
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Preview surface data payload.
54
+ */
55
+ export interface PreviewData {
56
+ /** Stable identifier for this preview - used for confirmation tracking */
57
+ actionId: string
58
+ /** Human-readable summary of what will happen */
59
+ title: string
60
+ /** Optional detailed explanation */
61
+ description?: string
62
+ /** The artifacts that would be created/sent/modified */
63
+ artifacts: PreviewArtifact[]
64
+ }
65
+
66
+ /**
67
+ * Preview message part (semantic type).
68
+ */
69
+ export interface PreviewPart {
70
+ type: 'preview'
71
+ data: PreviewData
72
+ }
73
+
74
+ /**
75
+ * Preview data part (wire format).
76
+ * Uses AI SDK's data-{name} convention.
77
+ */
78
+ export interface PreviewDataPart {
79
+ type: 'data-preview'
80
+ data: PreviewData
81
+ }
@@ -14,6 +14,8 @@
14
14
  */
15
15
 
16
16
  import type { ToolListMessagePart } from '../mcp/index.js'
17
+ import type { PreviewPart } from './preview.js'
18
+ import type { ConfirmationPart, ConfirmationResponsePart } from './confirmation.js'
17
19
 
18
20
  // =============================================================================
19
21
  // Narrative Parts (Streamable)
@@ -102,20 +104,6 @@ export interface TablePart {
102
104
  rows: string[][]
103
105
  }
104
106
 
105
- /**
106
- * Confirmation request surface part.
107
- * Requests user confirmation before proceeding.
108
- */
109
- export interface ConfirmationPart {
110
- type: 'confirmation'
111
- /** What is being confirmed */
112
- action: string
113
- /** Optional details */
114
- details?: string
115
- /** Confirmation ID for response tracking */
116
- confirmationId: string
117
- }
118
-
119
107
  // =============================================================================
120
108
  // Part Unions
121
109
  // =============================================================================
@@ -123,6 +111,9 @@ export interface ConfirmationPart {
123
111
  /**
124
112
  * Surface parts are rendered atomically (never streamed).
125
113
  * Extensible: add new surface types to this union.
114
+ *
115
+ * Note: ConfirmationResponsePart is user-originated but included
116
+ * here for uniform parsing. Do not render as UI surface.
126
117
  */
127
118
  export type SurfacePart =
128
119
  | ToolListPart
@@ -130,6 +121,8 @@ export type SurfacePart =
130
121
  | ChartPart
131
122
  | TablePart
132
123
  | ConfirmationPart
124
+ | ConfirmationResponsePart
125
+ | PreviewPart
133
126
 
134
127
  /**
135
128
  * All assistant message part types.
@@ -11,6 +11,18 @@
11
11
  */
12
12
 
13
13
  import type { MCPToolDescriptor, ToolListDataPart } from '../mcp/index.js'
14
+ import type { PreviewDataPart, PreviewData } from './preview.js'
15
+ import type {
16
+ ConfirmationData,
17
+ ConfirmationDataPart,
18
+ ConfirmationResponseData,
19
+ ConfirmationResponseDataPart,
20
+ } from './confirmation.js'
21
+ import type { ExecutionResultData, ExecutionResultDataPart } from './execution.js'
22
+ import type {
23
+ MessageStartDataPart,
24
+ MessageCompleteDataPart,
25
+ } from './lifecycle.js'
14
26
 
15
27
  /**
16
28
  * Factory for creating wire-format surface parts.
@@ -37,4 +49,129 @@ export const WireSurfaceBuilder = {
37
49
  data: { tools },
38
50
  }
39
51
  },
52
+
53
+ /**
54
+ * Build a preview data part for streaming.
55
+ * Previews show exactly what would happen if an action were executed.
56
+ *
57
+ * INVARIANTS:
58
+ * - Preview data must be exactly executable later
59
+ * - No placeholders or approximations
60
+ * - actionId must be stable for confirmation tracking
61
+ *
62
+ * @param data - Preview data containing actionId, title, and artifacts
63
+ * @returns Wire-format preview part ready for stream
64
+ */
65
+ preview(data: PreviewData): PreviewDataPart {
66
+ return {
67
+ type: 'data-preview',
68
+ data,
69
+ }
70
+ },
71
+
72
+ /**
73
+ * Build a confirmation request data part for streaming.
74
+ * Requests explicit user decision on a proposed action.
75
+ *
76
+ * INVARIANTS:
77
+ * - actionId must match the active preview
78
+ * - Only structured responses are accepted
79
+ *
80
+ * @param data - Confirmation data containing actionId, title, and prompt
81
+ * @returns Wire-format confirmation part ready for stream
82
+ */
83
+ confirmation(data: ConfirmationData): ConfirmationDataPart {
84
+ return {
85
+ type: 'data-confirmation',
86
+ data,
87
+ }
88
+ },
89
+
90
+ /**
91
+ * Build a confirmation response data part.
92
+ * Used when processing user's confirm/cancel decision.
93
+ *
94
+ * INVARIANTS:
95
+ * - actionId must match the active confirmation
96
+ * - choice must be 'confirm' or 'cancel'
97
+ *
98
+ * @param data - Response data containing actionId and choice
99
+ * @returns Wire-format confirmation response part
100
+ */
101
+ confirmationResponse(data: ConfirmationResponseData): ConfirmationResponseDataPart {
102
+ return {
103
+ type: 'data-confirmation-response',
104
+ data,
105
+ }
106
+ },
107
+
108
+ /**
109
+ * Build an execution result data part for streaming.
110
+ * Reports the outcome of an executed action.
111
+ *
112
+ * INVARIANTS:
113
+ * - actionId must match the executed action
114
+ * - All artifacts have a status (success, failed, or skipped)
115
+ * - Execution is complete and audited before this is sent
116
+ *
117
+ * @param data - Execution result data
118
+ * @returns Wire-format execution result part ready for stream
119
+ */
120
+ executionResult(data: ExecutionResultData): ExecutionResultDataPart {
121
+ return {
122
+ type: 'data-execution-result',
123
+ data,
124
+ }
125
+ },
126
+
127
+ // =========================================================================
128
+ // Message Lifecycle Events (Phase 1)
129
+ // =========================================================================
130
+
131
+ /**
132
+ * Build a message start event for streaming.
133
+ * Emit at the beginning of a new message stream.
134
+ *
135
+ * INVARIANTS:
136
+ * - Must be emitted before any text-delta or surface parts
137
+ * - messageId must be consistent across all events for this message
138
+ *
139
+ * @param messageId - Unique identifier for this message
140
+ * @returns Wire-format message start event
141
+ */
142
+ messageStart(messageId: string): MessageStartDataPart {
143
+ return {
144
+ type: 'data-message-start',
145
+ data: {
146
+ version: 1,
147
+ messageId,
148
+ timestamp: new Date().toISOString(),
149
+ },
150
+ }
151
+ },
152
+
153
+ /**
154
+ * Build a message complete event for streaming.
155
+ * Emit after all narrative content has been streamed.
156
+ *
157
+ * INVARIANTS:
158
+ * - Must be emitted after all text-delta events
159
+ * - Surface parts (if any) should be emitted after this
160
+ * - narrativeLength must match total text content length
161
+ *
162
+ * @param messageId - Message identifier (must match start event)
163
+ * @param narrativeLength - Total length of streamed narrative text
164
+ * @returns Wire-format message complete event
165
+ */
166
+ messageComplete(messageId: string, narrativeLength: number): MessageCompleteDataPart {
167
+ return {
168
+ type: 'data-message-complete',
169
+ data: {
170
+ version: 1,
171
+ messageId,
172
+ narrativeLength,
173
+ timestamp: new Date().toISOString(),
174
+ },
175
+ }
176
+ },
40
177
  } as const