@company-semantics/contracts 0.25.0 → 0.26.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 +6 -1
- package/src/guards/config.ts +110 -1
- package/src/guards/index.ts +6 -0
- package/src/index.ts +46 -0
- package/src/mcp/index.ts +14 -0
- package/src/message-parts/builder.ts +186 -0
- package/src/message-parts/index.ts +37 -0
- package/src/message-parts/types.ts +156 -0
- package/src/message-parts/wire.ts +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@company-semantics/contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -53,12 +53,17 @@
|
|
|
53
53
|
"guard:scripts-deps": "npx tsx scripts/ci/scripts-deps-guard.ts",
|
|
54
54
|
"guard:version-tag": "npx tsx scripts/ci/version-tag-guard.ts",
|
|
55
55
|
"guard:version-tag:json": "npx tsx scripts/ci/version-tag-guard.ts --json",
|
|
56
|
+
"guard:decisions-deprecation": "npx tsx scripts/ci/decisions-deprecation-guard.ts",
|
|
57
|
+
"guard:decisions-deprecation:json": "npx tsx scripts/ci/decisions-deprecation-guard.ts --json",
|
|
56
58
|
"guard:test": "vitest run scripts/ci/__tests__",
|
|
57
59
|
"release": "npx tsx scripts/release.ts",
|
|
58
60
|
"prepublishOnly": "echo 'ERROR: Publishing is CI-only via tag push. Use pnpm release instead.' && exit 1",
|
|
59
61
|
"test": "vitest run scripts/ci/__tests__"
|
|
60
62
|
},
|
|
61
63
|
"packageManager": "pnpm@10.25.0",
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": "22.x"
|
|
66
|
+
},
|
|
62
67
|
"devDependencies": {
|
|
63
68
|
"@types/node": "^25.0.3",
|
|
64
69
|
"husky": "^9.1.7",
|
package/src/guards/config.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Each repo provides its own config values; shared guards consume them.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { CheckResult } from './types.js';
|
|
11
|
+
import type { CheckResult, Soc2ControlArea } from './types.js';
|
|
12
12
|
|
|
13
13
|
// =============================================================================
|
|
14
14
|
// Size Limits
|
|
@@ -376,6 +376,115 @@ export interface SubdirectoryAffinityBaseline {
|
|
|
376
376
|
ignoredDirectories?: string[];
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
// =============================================================================
|
|
380
|
+
// SOC 2 Guard Configuration Types
|
|
381
|
+
// =============================================================================
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Configuration for the secrets detection guard.
|
|
385
|
+
* This is a blocking control for SOC 2 Access Control (AC).
|
|
386
|
+
*/
|
|
387
|
+
export interface SecretsDetectionConfig {
|
|
388
|
+
/** Source directory to scan (relative to repo root) */
|
|
389
|
+
srcDir?: string;
|
|
390
|
+
/** Additional directories to scan */
|
|
391
|
+
additionalDirs?: string[];
|
|
392
|
+
/** File patterns to exclude (globs) */
|
|
393
|
+
excludePatterns?: string[];
|
|
394
|
+
/** Additional secret patterns to detect (regex strings) */
|
|
395
|
+
additionalPatterns?: string[];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Configuration for the structured logging guard.
|
|
400
|
+
* This is a non-blocking control for SOC 2 Logging & Monitoring (LM).
|
|
401
|
+
*/
|
|
402
|
+
export interface StructuredLoggingConfig {
|
|
403
|
+
/** Entry point files to check (relative to repo root) */
|
|
404
|
+
entryPoints?: string[];
|
|
405
|
+
/** Source directory to scan for logging (fallback if no entry points) */
|
|
406
|
+
srcDir?: string;
|
|
407
|
+
/** Recognized logging libraries */
|
|
408
|
+
loggingLibraries?: string[];
|
|
409
|
+
/** Logger instantiation patterns (regex strings) */
|
|
410
|
+
instantiationPatterns?: string[];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Configuration for the alerts config guard.
|
|
415
|
+
* This is a non-blocking control for SOC 2 Logging & Monitoring (LM).
|
|
416
|
+
*/
|
|
417
|
+
export interface AlertsConfigGuardConfig {
|
|
418
|
+
/**
|
|
419
|
+
* Files or directories to check for alerting configuration.
|
|
420
|
+
* Supports both file paths and directory paths.
|
|
421
|
+
*/
|
|
422
|
+
files?: string[];
|
|
423
|
+
/**
|
|
424
|
+
* Whether this check should be skipped.
|
|
425
|
+
* Use for repos that don't have alerting (e.g., pure libraries).
|
|
426
|
+
*/
|
|
427
|
+
skip?: boolean;
|
|
428
|
+
/**
|
|
429
|
+
* Evidence string when alerting is explicitly skipped.
|
|
430
|
+
*/
|
|
431
|
+
skipEvidence?: string;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Configuration for the backup config guard.
|
|
436
|
+
* This is a non-blocking control for SOC 2 Backup & Recovery (BR).
|
|
437
|
+
*/
|
|
438
|
+
export interface BackupConfigGuardConfig {
|
|
439
|
+
/**
|
|
440
|
+
* Files or directories to check for backup configuration.
|
|
441
|
+
*/
|
|
442
|
+
files?: string[];
|
|
443
|
+
/**
|
|
444
|
+
* Whether this check should be skipped.
|
|
445
|
+
* Use for repos that don't manage backups (e.g., frontend-only).
|
|
446
|
+
*/
|
|
447
|
+
skip?: boolean;
|
|
448
|
+
/**
|
|
449
|
+
* Evidence string when backup check is explicitly skipped.
|
|
450
|
+
*/
|
|
451
|
+
skipEvidence?: string;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* SOC 2 compliance baselines.
|
|
456
|
+
* Configures SOC 2 control guards for a repository.
|
|
457
|
+
*
|
|
458
|
+
* Design: Product repos provide data only; CI orchestrator owns guard implementations.
|
|
459
|
+
*/
|
|
460
|
+
export interface Soc2Baselines {
|
|
461
|
+
/** Enable SOC 2 compliance reporting */
|
|
462
|
+
enabled?: boolean;
|
|
463
|
+
/** Advisory mode: all controls become non-blocking for visibility-only rollout */
|
|
464
|
+
advisoryMode?: boolean;
|
|
465
|
+
/** Repository name (for evidence) */
|
|
466
|
+
repository?: string;
|
|
467
|
+
/** Secrets detection configuration */
|
|
468
|
+
secretsDetection?: SecretsDetectionConfig;
|
|
469
|
+
/** Structured logging configuration */
|
|
470
|
+
structuredLogging?: StructuredLoggingConfig;
|
|
471
|
+
/** Alerts config configuration */
|
|
472
|
+
alertsConfig?: AlertsConfigGuardConfig;
|
|
473
|
+
/** Backup config configuration */
|
|
474
|
+
backupConfig?: BackupConfigGuardConfig;
|
|
475
|
+
/** Controls to explicitly skip (with evidence) */
|
|
476
|
+
skipControls?: {
|
|
477
|
+
area: Soc2ControlArea;
|
|
478
|
+
evidence: string;
|
|
479
|
+
}[];
|
|
480
|
+
/**
|
|
481
|
+
* Controls that remain blocking even in advisory mode.
|
|
482
|
+
* Use to gradually promote controls from advisory to blocking.
|
|
483
|
+
* Example: ['AC'] to make secrets-detection blocking first.
|
|
484
|
+
*/
|
|
485
|
+
alwaysBlockingControls?: Soc2ControlArea[];
|
|
486
|
+
}
|
|
487
|
+
|
|
379
488
|
/**
|
|
380
489
|
* Registry of checks grouped by tier.
|
|
381
490
|
* Each repo exports this from guard-entries.ts for universal orchestration.
|
package/src/guards/index.ts
CHANGED
|
@@ -54,6 +54,12 @@ export type {
|
|
|
54
54
|
FileClusterBaseline,
|
|
55
55
|
SubdirectoryAffinityBaseline,
|
|
56
56
|
MetaBaselines,
|
|
57
|
+
// SOC 2 Guard Configuration types
|
|
58
|
+
SecretsDetectionConfig,
|
|
59
|
+
StructuredLoggingConfig,
|
|
60
|
+
AlertsConfigGuardConfig,
|
|
61
|
+
BackupConfigGuardConfig,
|
|
62
|
+
Soc2Baselines,
|
|
57
63
|
} from './config.js';
|
|
58
64
|
|
|
59
65
|
// Config constants (type-level defaults)
|
package/src/index.ts
CHANGED
|
@@ -70,6 +70,17 @@ export type {
|
|
|
70
70
|
ContractsFreshnessBaseline,
|
|
71
71
|
CoverageBaseline,
|
|
72
72
|
MetaBaselines,
|
|
73
|
+
// SOC 2 Compliance types
|
|
74
|
+
Soc2ControlArea,
|
|
75
|
+
Soc2ControlStatus,
|
|
76
|
+
Soc2ControlResult,
|
|
77
|
+
Soc2ComplianceOutput,
|
|
78
|
+
// SOC 2 Guard Configuration types
|
|
79
|
+
SecretsDetectionConfig,
|
|
80
|
+
StructuredLoggingConfig,
|
|
81
|
+
AlertsConfigGuardConfig,
|
|
82
|
+
BackupConfigGuardConfig,
|
|
83
|
+
Soc2Baselines,
|
|
73
84
|
} from './guards/index.js'
|
|
74
85
|
|
|
75
86
|
export {
|
|
@@ -78,6 +89,11 @@ export {
|
|
|
78
89
|
DEFAULT_SKIP_DIRECTORIES,
|
|
79
90
|
DEFAULT_DOMAIN_SECTIONS,
|
|
80
91
|
DEFAULT_INFRA_SECTIONS,
|
|
92
|
+
// SOC 2 Compliance constants
|
|
93
|
+
SOC2_CONTROL_NAMES,
|
|
94
|
+
REQUIRED_SOC2_CONTROLS,
|
|
95
|
+
BLOCKING_SOC2_CONTROLS,
|
|
96
|
+
SOC2_SCHEMA_VERSION,
|
|
81
97
|
} from './guards/index.js'
|
|
82
98
|
|
|
83
99
|
// Compatibility manifest (CI guard vocabulary)
|
|
@@ -99,4 +115,34 @@ export type {
|
|
|
99
115
|
MCPToolDescriptor,
|
|
100
116
|
ToolDiscoveryResponse,
|
|
101
117
|
ToolListMessagePart,
|
|
118
|
+
ToolListDataPart,
|
|
102
119
|
} from './mcp/index.js'
|
|
120
|
+
|
|
121
|
+
// Message part types and builder functions
|
|
122
|
+
// @see ADR-2026-01-022 for design rationale
|
|
123
|
+
export type {
|
|
124
|
+
TextPart,
|
|
125
|
+
ToolListPart,
|
|
126
|
+
StatusPanelPart,
|
|
127
|
+
StatusPanelEntry,
|
|
128
|
+
ChartPart,
|
|
129
|
+
ChartDataPoint,
|
|
130
|
+
TablePart,
|
|
131
|
+
ConfirmationPart,
|
|
132
|
+
SurfacePart,
|
|
133
|
+
AssistantMessagePart,
|
|
134
|
+
PartBuilderState,
|
|
135
|
+
AddPartResult,
|
|
136
|
+
} from './message-parts/index.js'
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
isTextPart,
|
|
140
|
+
isSurfacePart,
|
|
141
|
+
createPartBuilder,
|
|
142
|
+
addText,
|
|
143
|
+
addSurface,
|
|
144
|
+
addPart,
|
|
145
|
+
buildParts,
|
|
146
|
+
getDroppedCount,
|
|
147
|
+
WireSurfaceBuilder,
|
|
148
|
+
} from './message-parts/index.js'
|
package/src/mcp/index.ts
CHANGED
|
@@ -87,3 +87,17 @@ export interface ToolListMessagePart {
|
|
|
87
87
|
type: 'tool-list'
|
|
88
88
|
tools: MCPToolDescriptor[]
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Wire protocol type for tool list data part.
|
|
93
|
+
*
|
|
94
|
+
* Transport-level type (SSE stream part).
|
|
95
|
+
* Uses AI SDK's `data-{name}` convention for custom data parts.
|
|
96
|
+
*
|
|
97
|
+
* The `data-` prefix signals to the AI SDK that this is a custom data part.
|
|
98
|
+
* Frontend normalizes this to `tool-list` for semantic handling.
|
|
99
|
+
*/
|
|
100
|
+
export interface ToolListDataPart {
|
|
101
|
+
type: 'data-tool-list'
|
|
102
|
+
data: { tools: MCPToolDescriptor[] }
|
|
103
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Part Builder Functions
|
|
3
|
+
*
|
|
4
|
+
* Functional builder for constructing well-ordered message parts.
|
|
5
|
+
* Enforces invariant: narrative (text) parts before surface parts.
|
|
6
|
+
*
|
|
7
|
+
* STATE MACHINE MODEL:
|
|
8
|
+
* PartBuilder implements a two-state machine:
|
|
9
|
+
*
|
|
10
|
+
* NarrativeState → accepts text or surface
|
|
11
|
+
* SurfaceState → accepts surface only (text rejected)
|
|
12
|
+
*
|
|
13
|
+
* Transitions are one-way. Once the first surface is added,
|
|
14
|
+
* the turn is in "surface phase" permanently.
|
|
15
|
+
*
|
|
16
|
+
* This prevents "post-surface commentary" features from sneaking in.
|
|
17
|
+
*
|
|
18
|
+
* @see DECISIONS.md ADR-2026-01-022 for design rationale
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { AssistantMessagePart, TextPart, SurfacePart } from './types.js'
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Builder State
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Immutable state for the part builder.
|
|
29
|
+
* Tracks collected parts and whether surfaces have been added.
|
|
30
|
+
*/
|
|
31
|
+
export interface PartBuilderState {
|
|
32
|
+
/** Collected parts in order */
|
|
33
|
+
readonly parts: readonly AssistantMessagePart[]
|
|
34
|
+
/** Whether any surface part has been added (true = SurfaceState) */
|
|
35
|
+
readonly hasSurface: boolean
|
|
36
|
+
/** Whether dev mode is enabled (throws on invariant violation) */
|
|
37
|
+
readonly devMode: boolean
|
|
38
|
+
/** Count of dropped text parts (for logging) */
|
|
39
|
+
readonly droppedCount: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Result of adding a part to the builder.
|
|
44
|
+
*/
|
|
45
|
+
export interface AddPartResult {
|
|
46
|
+
/** Updated builder state */
|
|
47
|
+
state: PartBuilderState
|
|
48
|
+
/** Whether the part was accepted (false = dropped) */
|
|
49
|
+
accepted: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Builder Creation
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a new part builder state.
|
|
58
|
+
*
|
|
59
|
+
* @param devMode - If true, throws on invariant violations. If false, silently drops.
|
|
60
|
+
* @returns Initial builder state (NarrativeState)
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const state = createPartBuilder(process.env.NODE_ENV !== 'production');
|
|
64
|
+
*/
|
|
65
|
+
export function createPartBuilder(devMode = false): PartBuilderState {
|
|
66
|
+
return {
|
|
67
|
+
parts: [],
|
|
68
|
+
hasSurface: false,
|
|
69
|
+
devMode,
|
|
70
|
+
droppedCount: 0,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// Part Addition
|
|
76
|
+
// =============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Add a text part to the builder.
|
|
80
|
+
*
|
|
81
|
+
* INVARIANT: Text parts MUST come before surface parts.
|
|
82
|
+
* - Dev mode: throws Error if text added after surface
|
|
83
|
+
* - Prod mode: silently drops, increments droppedCount
|
|
84
|
+
*
|
|
85
|
+
* @param state - Current builder state
|
|
86
|
+
* @param text - Text content to add
|
|
87
|
+
* @returns Result with updated state and acceptance status
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* let state = createPartBuilder();
|
|
91
|
+
* const result = addText(state, "Hello, world!");
|
|
92
|
+
* state = result.state;
|
|
93
|
+
*/
|
|
94
|
+
export function addText(state: PartBuilderState, text: string): AddPartResult {
|
|
95
|
+
const part: TextPart = { type: 'text', text }
|
|
96
|
+
|
|
97
|
+
if (state.hasSurface) {
|
|
98
|
+
if (state.devMode) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
'PartBuilder invariant violation: text part added after surface part. ' +
|
|
101
|
+
'Narrative content must come before structured surfaces. ' +
|
|
102
|
+
`Parts so far: ${state.parts.length}, dropped: ${state.droppedCount}`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
// Prod mode: drop silently
|
|
106
|
+
return {
|
|
107
|
+
state: { ...state, droppedCount: state.droppedCount + 1 },
|
|
108
|
+
accepted: false,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
state: { ...state, parts: [...state.parts, part] },
|
|
114
|
+
accepted: true,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Add a surface part to the builder.
|
|
120
|
+
*
|
|
121
|
+
* Surface parts mark the transition from NarrativeState to SurfaceState.
|
|
122
|
+
* After a surface is added, subsequent text parts are rejected.
|
|
123
|
+
*
|
|
124
|
+
* @param state - Current builder state
|
|
125
|
+
* @param part - Surface part to add
|
|
126
|
+
* @returns Result with updated state (always accepted)
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* let state = createPartBuilder();
|
|
130
|
+
* state = addSurface(state, { type: 'tool-list', tools: [...] }).state;
|
|
131
|
+
*/
|
|
132
|
+
export function addSurface(
|
|
133
|
+
state: PartBuilderState,
|
|
134
|
+
part: SurfacePart
|
|
135
|
+
): AddPartResult {
|
|
136
|
+
return {
|
|
137
|
+
state: {
|
|
138
|
+
...state,
|
|
139
|
+
parts: [...state.parts, part],
|
|
140
|
+
hasSurface: true,
|
|
141
|
+
},
|
|
142
|
+
accepted: true,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Add any message part to the builder.
|
|
148
|
+
* Dispatches to addText or addSurface based on part type.
|
|
149
|
+
*
|
|
150
|
+
* @param state - Current builder state
|
|
151
|
+
* @param part - Part to add
|
|
152
|
+
* @returns Result with updated state and acceptance status
|
|
153
|
+
*/
|
|
154
|
+
export function addPart(
|
|
155
|
+
state: PartBuilderState,
|
|
156
|
+
part: AssistantMessagePart
|
|
157
|
+
): AddPartResult {
|
|
158
|
+
if (part.type === 'text') {
|
|
159
|
+
return addText(state, part.text)
|
|
160
|
+
}
|
|
161
|
+
return addSurface(state, part)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// Builder Finalization
|
|
166
|
+
// =============================================================================
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract the final parts array from the builder.
|
|
170
|
+
*
|
|
171
|
+
* @param state - Final builder state
|
|
172
|
+
* @returns Array of message parts in correct order
|
|
173
|
+
*/
|
|
174
|
+
export function buildParts(state: PartBuilderState): AssistantMessagePart[] {
|
|
175
|
+
return [...state.parts]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get count of dropped parts (useful for logging/metrics).
|
|
180
|
+
*
|
|
181
|
+
* @param state - Builder state
|
|
182
|
+
* @returns Number of parts dropped due to invariant violations
|
|
183
|
+
*/
|
|
184
|
+
export function getDroppedCount(state: PartBuilderState): number {
|
|
185
|
+
return state.droppedCount
|
|
186
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Parts Barrel
|
|
3
|
+
*
|
|
4
|
+
* Re-exports message part vocabulary types and builder functions.
|
|
5
|
+
* Import from '@company-semantics/contracts' (root).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
export type {
|
|
10
|
+
TextPart,
|
|
11
|
+
ToolListPart,
|
|
12
|
+
StatusPanelPart,
|
|
13
|
+
StatusPanelEntry,
|
|
14
|
+
ChartPart,
|
|
15
|
+
ChartDataPoint,
|
|
16
|
+
TablePart,
|
|
17
|
+
ConfirmationPart,
|
|
18
|
+
SurfacePart,
|
|
19
|
+
AssistantMessagePart,
|
|
20
|
+
} from './types.js'
|
|
21
|
+
|
|
22
|
+
// Type guards
|
|
23
|
+
export { isTextPart, isSurfacePart } from './types.js'
|
|
24
|
+
|
|
25
|
+
// Builder functions
|
|
26
|
+
export type { PartBuilderState, AddPartResult } from './builder.js'
|
|
27
|
+
export {
|
|
28
|
+
createPartBuilder,
|
|
29
|
+
addText,
|
|
30
|
+
addSurface,
|
|
31
|
+
addPart,
|
|
32
|
+
buildParts,
|
|
33
|
+
getDroppedCount,
|
|
34
|
+
} from './builder.js'
|
|
35
|
+
|
|
36
|
+
// Wire format builder
|
|
37
|
+
export { WireSurfaceBuilder } from './wire.js'
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Parts Vocabulary
|
|
3
|
+
*
|
|
4
|
+
* Canonical types for structured assistant message output.
|
|
5
|
+
* Enables rich UI rendering beyond plain text.
|
|
6
|
+
*
|
|
7
|
+
* INVARIANTS:
|
|
8
|
+
* - Narrative (text) parts MUST come before surface parts
|
|
9
|
+
* - SurfacePart is an extensible union (add new kinds as needed)
|
|
10
|
+
* - TextPart is the ONLY part type that may be streamed char-by-char
|
|
11
|
+
* - Once a surface is added, the turn is in "surface phase" permanently
|
|
12
|
+
*
|
|
13
|
+
* @see DECISIONS.md ADR-2026-01-022 for design rationale
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ToolListMessagePart } from '../mcp/index.js'
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Narrative Parts (Streamable)
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Text content part.
|
|
24
|
+
* The only part type that MAY be streamed character-by-character.
|
|
25
|
+
*
|
|
26
|
+
* Design: Aligns with AI SDK's TextPart for interoperability.
|
|
27
|
+
*/
|
|
28
|
+
export interface TextPart {
|
|
29
|
+
type: 'text'
|
|
30
|
+
/** The text content (may be partial during streaming) */
|
|
31
|
+
text: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Surface Parts (Atomic, Non-Streamable)
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Re-export ToolListMessagePart as ToolListPart for consistency.
|
|
40
|
+
*/
|
|
41
|
+
export type ToolListPart = ToolListMessagePart
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Status panel surface part.
|
|
45
|
+
* Displays system status, connection states, or progress indicators.
|
|
46
|
+
*/
|
|
47
|
+
export interface StatusPanelPart {
|
|
48
|
+
type: 'status-panel'
|
|
49
|
+
/** Panel title */
|
|
50
|
+
title: string
|
|
51
|
+
/** Status entries */
|
|
52
|
+
entries: StatusPanelEntry[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Single entry in a status panel.
|
|
57
|
+
*/
|
|
58
|
+
export interface StatusPanelEntry {
|
|
59
|
+
/** Entry label */
|
|
60
|
+
label: string
|
|
61
|
+
/** Current status */
|
|
62
|
+
status: 'ok' | 'warning' | 'error' | 'pending'
|
|
63
|
+
/** Optional detail text */
|
|
64
|
+
detail?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Chart surface part.
|
|
69
|
+
* Renders a data visualization.
|
|
70
|
+
*/
|
|
71
|
+
export interface ChartPart {
|
|
72
|
+
type: 'chart'
|
|
73
|
+
/** Chart type */
|
|
74
|
+
chartType: 'bar' | 'line' | 'pie' | 'area'
|
|
75
|
+
/** Chart title */
|
|
76
|
+
title?: string
|
|
77
|
+
/** Data points */
|
|
78
|
+
data: ChartDataPoint[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Single data point for charts.
|
|
83
|
+
*/
|
|
84
|
+
export interface ChartDataPoint {
|
|
85
|
+
label: string
|
|
86
|
+
value: number
|
|
87
|
+
/** Optional color (CSS color string) */
|
|
88
|
+
color?: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Table surface part.
|
|
93
|
+
* Renders tabular data.
|
|
94
|
+
*/
|
|
95
|
+
export interface TablePart {
|
|
96
|
+
type: 'table'
|
|
97
|
+
/** Table title */
|
|
98
|
+
title?: string
|
|
99
|
+
/** Column headers */
|
|
100
|
+
headers: string[]
|
|
101
|
+
/** Row data (each row is an array of cell values) */
|
|
102
|
+
rows: string[][]
|
|
103
|
+
}
|
|
104
|
+
|
|
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
|
+
// =============================================================================
|
|
120
|
+
// Part Unions
|
|
121
|
+
// =============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Surface parts are rendered atomically (never streamed).
|
|
125
|
+
* Extensible: add new surface types to this union.
|
|
126
|
+
*/
|
|
127
|
+
export type SurfacePart =
|
|
128
|
+
| ToolListPart
|
|
129
|
+
| StatusPanelPart
|
|
130
|
+
| ChartPart
|
|
131
|
+
| TablePart
|
|
132
|
+
| ConfirmationPart
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* All assistant message part types.
|
|
136
|
+
* Union of narrative (text) and surface parts.
|
|
137
|
+
*/
|
|
138
|
+
export type AssistantMessagePart = TextPart | SurfacePart
|
|
139
|
+
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// Type Guards
|
|
142
|
+
// =============================================================================
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Type guard for TextPart.
|
|
146
|
+
*/
|
|
147
|
+
export function isTextPart(part: AssistantMessagePart): part is TextPart {
|
|
148
|
+
return part.type === 'text'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Type guard for SurfacePart.
|
|
153
|
+
*/
|
|
154
|
+
export function isSurfacePart(part: AssistantMessagePart): part is SurfacePart {
|
|
155
|
+
return part.type !== 'text'
|
|
156
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire Surface Builder
|
|
3
|
+
*
|
|
4
|
+
* Factory functions for creating wire-format surface parts.
|
|
5
|
+
* These follow AI SDK's `data-{name}` convention for custom data parts.
|
|
6
|
+
*
|
|
7
|
+
* Use these in backend code when writing to UIMessageStream.
|
|
8
|
+
* Frontend normalizes wire format to semantic types.
|
|
9
|
+
*
|
|
10
|
+
* @see DECISIONS.md ADR-2026-01-022 for design rationale
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { MCPToolDescriptor, ToolListDataPart } from '../mcp/index.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Factory for creating wire-format surface parts.
|
|
17
|
+
*
|
|
18
|
+
* Wire parts use AI SDK's `data-{name}` type convention.
|
|
19
|
+
* These are transport-level types, not semantic types.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // In ChatService.ts
|
|
23
|
+
* import { WireSurfaceBuilder } from '@company-semantics/contracts';
|
|
24
|
+
* writer.write(WireSurfaceBuilder.toolList(userTools));
|
|
25
|
+
*/
|
|
26
|
+
export const WireSurfaceBuilder = {
|
|
27
|
+
/**
|
|
28
|
+
* Build a tool-list data part for streaming.
|
|
29
|
+
* Use after LLM text stream completes.
|
|
30
|
+
*
|
|
31
|
+
* @param tools - Array of tool descriptors to include
|
|
32
|
+
* @returns Wire-format tool list part ready for stream
|
|
33
|
+
*/
|
|
34
|
+
toolList(tools: MCPToolDescriptor[]): ToolListDataPart {
|
|
35
|
+
return {
|
|
36
|
+
type: 'data-tool-list',
|
|
37
|
+
data: { tools },
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
} as const
|