@zuoyehaoduoa/wire 0.1.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/README.md ADDED
@@ -0,0 +1,668 @@
1
+ # @zuoyehaoduoa/wire
2
+
3
+ Canonical wire specification package for codex-deck clients and services.
4
+
5
+ This package defines shared wire contracts as TypeScript types + Zod schemas. It is intentionally small and focused on protocol-level data only.
6
+
7
+ ## Quick Example
8
+
9
+ Session protocol format example (decrypted payload):
10
+
11
+ ```json
12
+ {
13
+ "role": "session",
14
+ "content": {
15
+ "id": "msg_01",
16
+ "time": 1739347230000,
17
+ "role": "agent",
18
+ "turn": "turn_01",
19
+ "ev": {
20
+ "t": "text",
21
+ "text": "I found the issue in api/session.ts"
22
+ }
23
+ },
24
+ "meta": {
25
+ "sentFrom": "cli"
26
+ }
27
+ }
28
+ ```
29
+
30
+ Session protocol user envelope (decrypted payload):
31
+
32
+ ```json
33
+ {
34
+ "role": "session",
35
+ "content": {
36
+ "id": "msg_user_01",
37
+ "time": 1739347231000,
38
+ "role": "user",
39
+ "ev": {
40
+ "t": "text",
41
+ "text": "fix the failing test"
42
+ }
43
+ },
44
+ "meta": {
45
+ "sentFrom": "cli"
46
+ }
47
+ }
48
+ ```
49
+
50
+ Protocol invariants:
51
+
52
+ - outer `role = "session"` marks modern session-protocol payloads.
53
+ - inside `content`, envelope `role` is only `"user"` or `"agent"`.
54
+
55
+ Wire-level encrypted container:
56
+
57
+ ```json
58
+ {
59
+ "id": "msg-db-row-id",
60
+ "seq": 101,
61
+ "localId": null,
62
+ "content": {
63
+ "t": "encrypted",
64
+ "c": "BASE64_ENCRYPTED_PAYLOAD"
65
+ },
66
+ "createdAt": 1739347230000,
67
+ "updatedAt": 1739347230000
68
+ }
69
+ ```
70
+
71
+ ## Purpose
72
+
73
+ `@zuoyehaoduoa/wire` centralizes definitions for:
74
+
75
+ - encrypted message/update payloads
76
+ - session protocol envelope and event stream
77
+ - helper for creating valid session envelopes
78
+
79
+ The goal is to keep CLI/app/server/agent on the same wire contract and avoid schema drift.
80
+
81
+ ## Package Identity
82
+
83
+ - Name: `@zuoyehaoduoa/wire`
84
+ - Workspace path: `wire`
85
+ - Entry: `src/index.ts`
86
+ - Runtime deps: `zod`, `@paralleldrive/cuid2`
87
+
88
+ ## Public Exports
89
+
90
+ `src/index.ts` exports everything from:
91
+
92
+ - `src/messages.ts`
93
+ - `src/sessionProtocol.ts`
94
+
95
+ ### `messages.ts` exports
96
+
97
+ Schemas + inferred types:
98
+
99
+ - `SessionMessageContentSchema`
100
+ - `SessionMessage`
101
+ - `SessionMessageSchema`
102
+ - `MessageMetaSchema`
103
+ - `MessageMeta`
104
+ - `SessionProtocolMessageSchema`
105
+ - `SessionProtocolMessage`
106
+ - `MessageContentSchema`
107
+ - `MessageContent`
108
+ - `VersionedEncryptedValueSchema`
109
+ - `VersionedEncryptedValue`
110
+ - `VersionedNullableEncryptedValueSchema`
111
+ - `VersionedNullableEncryptedValue`
112
+ - `UpdateNewMessageBodySchema`
113
+ - `UpdateNewMessageBody`
114
+ - `UpdateSessionBodySchema`
115
+ - `UpdateSessionBody`
116
+ - `VersionedMachineEncryptedValueSchema`
117
+ - `VersionedMachineEncryptedValue`
118
+ - `UpdateMachineBodySchema`
119
+ - `UpdateMachineBody`
120
+ - `CoreUpdateBodySchema`
121
+ - `CoreUpdateBody`
122
+ - `CoreUpdateContainerSchema`
123
+ - `CoreUpdateContainer`
124
+
125
+ ### `sessionProtocol.ts` exports
126
+
127
+ Schemas + inferred types:
128
+
129
+ - `sessionRoleSchema`
130
+ - `SessionRole`
131
+ - `sessionTextEventSchema`
132
+ - `sessionServiceMessageEventSchema`
133
+ - `sessionToolCallStartEventSchema`
134
+ - `sessionToolCallEndEventSchema`
135
+ - `sessionFileEventSchema`
136
+ - `sessionTurnStartEventSchema`
137
+ - `sessionStartEventSchema`
138
+ - `sessionTurnEndStatusSchema`
139
+ - `SessionTurnEndStatus`
140
+ - `sessionTurnEndEventSchema`
141
+ - `sessionStopEventSchema`
142
+ - `sessionEventSchema`
143
+ - `SessionEvent`
144
+ - `sessionEnvelopeSchema`
145
+ - `SessionEnvelope`
146
+ - `CreateEnvelopeOptions`
147
+ - `createEnvelope(...)`
148
+
149
+ ## Wire Type Specifications
150
+
151
+ ## Common Primitive Rules
152
+
153
+ These are schema-level requirements, not just recommendations.
154
+
155
+ - `id`, `sid`, `machineId`, `call`, `name`, `title`, `description`, `ref`: `string`
156
+ - `seq`, `createdAt`, `updatedAt`, `size`, `width`, `height`, `version`, `activeAt`: `number`
157
+ - All nullable fields are explicitly marked with `.nullable()`.
158
+ - All optional fields are explicitly marked with `.optional()`.
159
+ - `.nullish()` means `undefined | null | <type>`.
160
+
161
+ ## Message/Update Specs (`messages.ts`)
162
+
163
+ ### `SessionMessageContentSchema`
164
+
165
+ ```ts
166
+ {
167
+ t: "encrypted";
168
+ c: string;
169
+ }
170
+ ```
171
+
172
+ Meaning:
173
+
174
+ - `t` is a strict discriminator with value `'encrypted'`.
175
+ - `c` is encrypted payload bytes encoded as a string (typically base64 in current usage).
176
+
177
+ ### `SessionMessageSchema`
178
+
179
+ ```ts
180
+ {
181
+ id: string;
182
+ seq: number;
183
+ localId?: string | null;
184
+ content: SessionMessageContent;
185
+ createdAt: number;
186
+ updatedAt: number;
187
+ }
188
+ ```
189
+
190
+ Notes:
191
+
192
+ - `localId` is `.nullish()` for compatibility with different producers.
193
+ - `createdAt` and `updatedAt` are required in this shared schema.
194
+
195
+ ### `MessageMetaSchema`
196
+
197
+ ```ts
198
+ {
199
+ sentFrom?: string;
200
+ permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'read-only' | 'safe-yolo' | 'yolo';
201
+ model?: string | null;
202
+ fallbackModel?: string | null;
203
+ customSystemPrompt?: string | null;
204
+ appendSystemPrompt?: string | null;
205
+ allowedTools?: string[] | null;
206
+ disallowedTools?: string[] | null;
207
+ displayText?: string;
208
+ }
209
+ ```
210
+
211
+ ## Top-Level Decrypted Payload Specs (`messages.ts`)
212
+
213
+ ### `SessionProtocolMessageSchema` (modern decrypted payload wrapper)
214
+
215
+ ```ts
216
+ {
217
+ role: 'session';
218
+ content: SessionEnvelope;
219
+ meta?: MessageMeta;
220
+ }
221
+ ```
222
+
223
+ ### `MessageContentSchema`
224
+
225
+ Equivalent to `SessionProtocolMessageSchema`.
226
+
227
+ ## Message/Update Specs (`messages.ts`) Continued
228
+
229
+ ### `VersionedEncryptedValueSchema`
230
+
231
+ ```ts
232
+ {
233
+ version: number;
234
+ value: string;
235
+ }
236
+ ```
237
+
238
+ Used for encrypted, version-tracked blobs that cannot be null when present.
239
+
240
+ ### `VersionedNullableEncryptedValueSchema`
241
+
242
+ ```ts
243
+ {
244
+ version: number;
245
+ value: string | null;
246
+ }
247
+ ```
248
+
249
+ Used where payload presence can be intentionally reset to null while still versioning.
250
+
251
+ ### `VersionedMachineEncryptedValueSchema`
252
+
253
+ ```ts
254
+ {
255
+ version: number;
256
+ value: string;
257
+ }
258
+ ```
259
+
260
+ Machine update variant. Equivalent shape to `VersionedEncryptedValueSchema`.
261
+
262
+ ### `UpdateNewMessageBodySchema`
263
+
264
+ ```ts
265
+ {
266
+ t: "new-message";
267
+ sid: string;
268
+ message: SessionMessage;
269
+ }
270
+ ```
271
+
272
+ ### `UpdateSessionBodySchema`
273
+
274
+ ```ts
275
+ {
276
+ t: 'update-session';
277
+ id: string;
278
+ metadata?: VersionedEncryptedValue | null;
279
+ agentState?: VersionedNullableEncryptedValue | null;
280
+ }
281
+ ```
282
+
283
+ Important distinction:
284
+
285
+ - `metadata.value` is `string` when metadata block exists.
286
+ - `agentState.value` may be `string` or `null` when block exists.
287
+
288
+ ### `UpdateMachineBodySchema`
289
+
290
+ ```ts
291
+ {
292
+ t: 'update-machine';
293
+ machineId: string;
294
+ metadata?: VersionedMachineEncryptedValue | null;
295
+ daemonState?: VersionedMachineEncryptedValue | null;
296
+ active?: boolean;
297
+ activeAt?: number;
298
+ }
299
+ ```
300
+
301
+ ### `CoreUpdateBodySchema`
302
+
303
+ Discriminated union on `t` with exactly 3 variants:
304
+
305
+ - `'new-message'`
306
+ - `'update-session'`
307
+ - `'update-machine'`
308
+
309
+ ### `CoreUpdateContainerSchema`
310
+
311
+ ```ts
312
+ {
313
+ id: string;
314
+ seq: number;
315
+ body: CoreUpdateBody;
316
+ createdAt: number;
317
+ }
318
+ ```
319
+
320
+ ## Session Protocol Specs (`sessionProtocol.ts`)
321
+
322
+ ## Role
323
+
324
+ ### `sessionRoleSchema`
325
+
326
+ ```ts
327
+ "user" | "agent";
328
+ ```
329
+
330
+ Role meaning:
331
+
332
+ - `'user'`: user-originated envelope.
333
+ - `'agent'`: agent-originated envelope.
334
+
335
+ ## Event Variants
336
+
337
+ `sessionEventSchema` is a discriminated union on `t` with 9 variants.
338
+
339
+ ### 1) Text event
340
+
341
+ ```ts
342
+ {
343
+ t: 'text';
344
+ text: string;
345
+ thinking?: boolean;
346
+ }
347
+ ```
348
+
349
+ ### 2) Service event
350
+
351
+ ```ts
352
+ {
353
+ t: "service";
354
+ text: string;
355
+ }
356
+ ```
357
+
358
+ ### 3) Tool-call-start event
359
+
360
+ ```ts
361
+ {
362
+ t: "tool-call-start";
363
+ call: string;
364
+ name: string;
365
+ title: string;
366
+ description: string;
367
+ args: Record<string, unknown>;
368
+ }
369
+ ```
370
+
371
+ ### 4) Tool-call-end event
372
+
373
+ ```ts
374
+ {
375
+ t: "tool-call-end";
376
+ call: string;
377
+ }
378
+ ```
379
+
380
+ ### 5) File event
381
+
382
+ ```ts
383
+ {
384
+ t: 'file';
385
+ ref: string;
386
+ name: string;
387
+ size: number;
388
+ image?: {
389
+ width: number;
390
+ height: number;
391
+ thumbhash: string;
392
+ };
393
+ }
394
+ ```
395
+
396
+ ### 6) Turn-start event
397
+
398
+ ```ts
399
+ {
400
+ t: "turn-start";
401
+ }
402
+ ```
403
+
404
+ ### 7) Start event
405
+
406
+ ```ts
407
+ {
408
+ t: 'start';
409
+ title?: string;
410
+ }
411
+ ```
412
+
413
+ ### 8) Turn-end event
414
+
415
+ ```ts
416
+ {
417
+ t: "turn-end";
418
+ status: "completed" | "failed" | "cancelled";
419
+ }
420
+ ```
421
+
422
+ ### 9) Stop event
423
+
424
+ ```ts
425
+ {
426
+ t: "stop";
427
+ }
428
+ ```
429
+
430
+ ## Envelope
431
+
432
+ ### `sessionEnvelopeSchema`
433
+
434
+ ```ts
435
+ {
436
+ id: string;
437
+ time: number;
438
+ role: 'user' | 'agent';
439
+ turn?: string;
440
+ subagent?: string; // must pass cuid2 validation when present
441
+ ev: SessionEvent;
442
+ }
443
+ ```
444
+
445
+ Additional validation (`superRefine`):
446
+
447
+ - If `ev.t === 'service'`, then `role` MUST be `'agent'`.
448
+ - If `ev.t === 'start'` or `ev.t === 'stop'`, then `role` MUST be `'agent'`.
449
+ - If `subagent` is present, it MUST satisfy `isCuid(...)`.
450
+
451
+ ## Helper Function Contract
452
+
453
+ ### `createEnvelope(role, ev, opts?)`
454
+
455
+ Input:
456
+
457
+ - `role: SessionRole`
458
+ - `ev: SessionEvent`
459
+ - `opts?: { id?: string; time?: number; turn?: string; subagent?: string }`
460
+
461
+ Behavior:
462
+
463
+ - If `opts.id` is absent, generates id using `createId()`.
464
+ - If `opts.time` is absent, sets `time` to `Date.now()`.
465
+ - Includes `turn` only when provided.
466
+ - Includes `subagent` only when provided.
467
+
468
+ Output:
469
+
470
+ - Returns a `SessionEnvelope` parsed by `sessionEnvelopeSchema`.
471
+ - Throws on invalid combinations (for example `role = 'user'` with `ev.t = 'service'`).
472
+
473
+ ## Normative JSON Examples
474
+
475
+ ## Update container with `new-message`
476
+
477
+ ```json
478
+ {
479
+ "id": "upd-1",
480
+ "seq": 100,
481
+ "createdAt": 1739347200000,
482
+ "body": {
483
+ "t": "new-message",
484
+ "sid": "session-1",
485
+ "message": {
486
+ "id": "msg-1",
487
+ "seq": 55,
488
+ "localId": null,
489
+ "content": {
490
+ "t": "encrypted",
491
+ "c": "Zm9v"
492
+ },
493
+ "createdAt": 1739347199000,
494
+ "updatedAt": 1739347199000
495
+ }
496
+ }
497
+ }
498
+ ```
499
+
500
+ ### Decrypted `new-message` content example
501
+
502
+ `message.content.c` (ciphertext) decrypts into the payload below for a session-protocol message:
503
+
504
+ ```json
505
+ {
506
+ "role": "session",
507
+ "content": {
508
+ "id": "env_01",
509
+ "time": 1739347232000,
510
+ "role": "agent",
511
+ "turn": "turn_01",
512
+ "ev": {
513
+ "t": "text",
514
+ "text": "I found 3 TODOs."
515
+ }
516
+ },
517
+ "meta": {
518
+ "sentFrom": "cli"
519
+ }
520
+ }
521
+ ```
522
+
523
+ Clients emit only the modern payload (`role = "session"` with `content.role = "user"`).
524
+
525
+ ## Update container with `update-session`
526
+
527
+ ```json
528
+ {
529
+ "id": "upd-2",
530
+ "seq": 101,
531
+ "createdAt": 1739347210000,
532
+ "body": {
533
+ "t": "update-session",
534
+ "id": "session-1",
535
+ "metadata": {
536
+ "version": 8,
537
+ "value": "BASE64..."
538
+ },
539
+ "agentState": {
540
+ "version": 13,
541
+ "value": null
542
+ }
543
+ }
544
+ }
545
+ ```
546
+
547
+ ## Update container with `update-machine`
548
+
549
+ ```json
550
+ {
551
+ "id": "upd-3",
552
+ "seq": 102,
553
+ "createdAt": 1739347220000,
554
+ "body": {
555
+ "t": "update-machine",
556
+ "machineId": "machine-1",
557
+ "metadata": {
558
+ "version": 2,
559
+ "value": "BASE64..."
560
+ },
561
+ "daemonState": {
562
+ "version": 3,
563
+ "value": "BASE64..."
564
+ },
565
+ "active": true,
566
+ "activeAt": 1739347220000
567
+ }
568
+ }
569
+ ```
570
+
571
+ ## Session protocol envelope
572
+
573
+ ```json
574
+ {
575
+ "id": "x8s1k2...",
576
+ "role": "agent",
577
+ "turn": "turn-42",
578
+ "ev": {
579
+ "t": "turn-start"
580
+ }
581
+ }
582
+ ```
583
+
584
+ ## Parsing/Validation Usage
585
+
586
+ ```ts
587
+ import {
588
+ CoreUpdateContainerSchema,
589
+ sessionEnvelopeSchema,
590
+ } from "@zuoyehaoduoa/wire";
591
+
592
+ const maybeUpdate = CoreUpdateContainerSchema.safeParse(input);
593
+ if (!maybeUpdate.success) {
594
+ // invalid update payload
595
+ }
596
+
597
+ const maybeEnvelope = sessionEnvelopeSchema.safeParse(envelopeInput);
598
+ if (!maybeEnvelope.success) {
599
+ // invalid envelope/event payload
600
+ }
601
+ ```
602
+
603
+ ## Build and Distribution Specification
604
+
605
+ `package.json` contract:
606
+
607
+ - `main`: `./dist/index.cjs`
608
+ - `module`: `./dist/index.mjs`
609
+ - `types`: `./dist/index.d.cts`
610
+ - `exports["."]` provides both CJS and ESM entrypoints with type paths.
611
+
612
+ Build script:
613
+
614
+ - `shx rm -rf dist && npx tsc --noEmit && pkgroll`
615
+
616
+ Tests:
617
+
618
+ - `vitest` against `src/*.test.ts`
619
+
620
+ Publish gate:
621
+
622
+ - `prepublishOnly` runs build + test
623
+
624
+ Published files:
625
+
626
+ - `dist`
627
+ - `package.json`
628
+ - `README.md`
629
+
630
+ ## Monorepo Build Dependency Behavior
631
+
632
+ In this repository, consumer workspaces import `@zuoyehaoduoa/wire` through the local workspace package.
633
+
634
+ That means on a clean checkout:
635
+
636
+ 1. Build wire first: `pnpm --dir wire build`
637
+ 2. Then build/typecheck dependents.
638
+
639
+ After publishing to npm, dependents consume prebuilt artifacts from the published tarball.
640
+
641
+ ## Change Policy
642
+
643
+ When modifying wire schemas:
644
+
645
+ - Prefer additive changes to keep older consumers compatible.
646
+ - Treat discriminator values (`t`) as protocol-level API and avoid breaking renames.
647
+ - Document semantic changes in this README.
648
+ - Bump package version before downstream releases that depend on new schema behavior.
649
+
650
+ ## Development Commands
651
+
652
+ ```bash
653
+ # from repository root
654
+ pnpm --dir wire build
655
+ pnpm --dir wire test
656
+ ```
657
+
658
+ ## Release Commands (maintainers)
659
+
660
+ ```bash
661
+ # interactive release target selection from repo root
662
+ pnpm release
663
+
664
+ # direct release invocation
665
+ pnpm --dir wire release
666
+ ```
667
+
668
+ This prepares release artifacts using the same `release-it` flow as other publishable libraries in the monorepo.