@company-semantics/contracts 0.26.0 → 0.27.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.27.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.ts CHANGED
@@ -133,6 +133,25 @@ 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,
136
155
  } from './message-parts/index.js'
137
156
 
138
157
  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,39 @@ 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
+
22
50
  // Type guards
23
51
  export { isTextPart, isSurfacePart } from './types.js'
24
52
 
@@ -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,14 @@
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'
14
22
 
15
23
  /**
16
24
  * Factory for creating wire-format surface parts.
@@ -37,4 +45,78 @@ export const WireSurfaceBuilder = {
37
45
  data: { tools },
38
46
  }
39
47
  },
48
+
49
+ /**
50
+ * Build a preview data part for streaming.
51
+ * Previews show exactly what would happen if an action were executed.
52
+ *
53
+ * INVARIANTS:
54
+ * - Preview data must be exactly executable later
55
+ * - No placeholders or approximations
56
+ * - actionId must be stable for confirmation tracking
57
+ *
58
+ * @param data - Preview data containing actionId, title, and artifacts
59
+ * @returns Wire-format preview part ready for stream
60
+ */
61
+ preview(data: PreviewData): PreviewDataPart {
62
+ return {
63
+ type: 'data-preview',
64
+ data,
65
+ }
66
+ },
67
+
68
+ /**
69
+ * Build a confirmation request data part for streaming.
70
+ * Requests explicit user decision on a proposed action.
71
+ *
72
+ * INVARIANTS:
73
+ * - actionId must match the active preview
74
+ * - Only structured responses are accepted
75
+ *
76
+ * @param data - Confirmation data containing actionId, title, and prompt
77
+ * @returns Wire-format confirmation part ready for stream
78
+ */
79
+ confirmation(data: ConfirmationData): ConfirmationDataPart {
80
+ return {
81
+ type: 'data-confirmation',
82
+ data,
83
+ }
84
+ },
85
+
86
+ /**
87
+ * Build a confirmation response data part.
88
+ * Used when processing user's confirm/cancel decision.
89
+ *
90
+ * INVARIANTS:
91
+ * - actionId must match the active confirmation
92
+ * - choice must be 'confirm' or 'cancel'
93
+ *
94
+ * @param data - Response data containing actionId and choice
95
+ * @returns Wire-format confirmation response part
96
+ */
97
+ confirmationResponse(data: ConfirmationResponseData): ConfirmationResponseDataPart {
98
+ return {
99
+ type: 'data-confirmation-response',
100
+ data,
101
+ }
102
+ },
103
+
104
+ /**
105
+ * Build an execution result data part for streaming.
106
+ * Reports the outcome of an executed action.
107
+ *
108
+ * INVARIANTS:
109
+ * - actionId must match the executed action
110
+ * - All artifacts have a status (success, failed, or skipped)
111
+ * - Execution is complete and audited before this is sent
112
+ *
113
+ * @param data - Execution result data
114
+ * @returns Wire-format execution result part ready for stream
115
+ */
116
+ executionResult(data: ExecutionResultData): ExecutionResultDataPart {
117
+ return {
118
+ type: 'data-execution-result',
119
+ data,
120
+ }
121
+ },
40
122
  } as const