@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 +1 -1
- package/src/index.ts +32 -0
- package/src/message-parts/__tests__/confirmation.test.ts +99 -0
- package/src/message-parts/__tests__/preview.test.ts +97 -0
- package/src/message-parts/confirmation.ts +87 -0
- package/src/message-parts/execution.ts +53 -0
- package/src/message-parts/index.ts +44 -1
- package/src/message-parts/lifecycle.ts +147 -0
- package/src/message-parts/preview.ts +81 -0
- package/src/message-parts/types.ts +7 -14
- package/src/message-parts/wire.ts +137 -0
package/package.json
CHANGED
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
|