@roll-agent/relay-protocol 0.1.1 → 0.2.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.
Files changed (35) hide show
  1. package/README.md +90 -39
  2. package/dist/conformance.d.ts +571 -4
  3. package/dist/conformance.js +677 -8
  4. package/dist/index.d.ts +3977 -303
  5. package/dist/index.js +664 -77
  6. package/dist/reference-adapter.d.ts +56 -0
  7. package/dist/reference-adapter.js +122 -0
  8. package/dist/schema/roll-relay-protocol-v1.1.schema.json +3791 -0
  9. package/fixtures/v1.1/invalid-approval-projection-raw-input-leak.json +19 -0
  10. package/fixtures/v1.1/invalid-authentication-local-only.json +12 -0
  11. package/fixtures/v1.1/invalid-file-picker-local-only.json +12 -0
  12. package/fixtures/v1.1/invalid-interaction-cancelled-reason-leak.json +10 -0
  13. package/fixtures/v1.1/invalid-interaction-resolved-result-leak.json +18 -0
  14. package/fixtures/v1.1/invalid-legacy-approval-candidate.json +7 -0
  15. package/fixtures/v1.1/invalid-legacy-approval-respond.json +7 -0
  16. package/fixtures/v1.1/invalid-n-minus-one-device-connect.json +6 -0
  17. package/fixtures/v1.1/invalid-non-normal-sensitivity.json +21 -0
  18. package/fixtures/v1.1/invalid-runtime-event-tool-input-leak.json +22 -0
  19. package/fixtures/v1.1/invalid-runtime-event-tool-output-leak.json +21 -0
  20. package/fixtures/v1.1/invalid-user-input-projection-secret-leak.json +22 -0
  21. package/fixtures/v1.1/manifest.json +673 -0
  22. package/fixtures/v1.1/valid-device-connect.json +6 -0
  23. package/fixtures/v1.1/valid-interaction-cancelled.json +9 -0
  24. package/fixtures/v1.1/valid-interaction-candidate-user-input.json +25 -0
  25. package/fixtures/v1.1/valid-interaction-request-approval.json +17 -0
  26. package/fixtures/v1.1/valid-interaction-request-user-input.json +40 -0
  27. package/fixtures/v1.1/valid-interaction-resolved.json +9 -0
  28. package/fixtures/v1.1/valid-runtime-ack.json +5 -0
  29. package/fixtures/v1.1/valid-runtime-encrypted-interaction.json +10 -0
  30. package/fixtures/v1.1/valid-runtime-event-runtime-n-minus-one.json +16 -0
  31. package/fixtures/v1.1/valid-runtime-event.json +16 -0
  32. package/fixtures/v1.1/valid-runtime-gap.json +7 -0
  33. package/fixtures/v1.1/valid-runtime-request.json +10 -0
  34. package/fixtures/v1.1/valid-runtime-response.json +8 -0
  35. package/package.json +8 -2
@@ -1,4 +1,4 @@
1
- import { RELAY_ERROR_CODES, RELAY_ERROR_RETRYABILITY, RELAY_PROTOCOL_VERSION, RELAY_REQUEST_METHOD_DISPOSITIONS, RELAY_REQUEST_METHOD_VALUES, RELAY_REQUEST_REPLAY_DISPOSITIONS, RELAY_REQUEST_METHODS, classifyRelayAck, classifyRelayRequestReplay, getRelayRequestMethodDisposition, negotiateRelayProtocolVersion, parseRelayRequestParams, parseRelayRequestResult, relayMessageSchema, relayRuntimeRequestSchema, } from "./index.js";
1
+ import { RELAY_ERROR_CODES, RELAY_ERROR_RETRYABILITY, RELAY_INTERACTION_METHODS_V11, RELAY_PROTOCOL_VERSION, RELAY_REQUEST_METHOD_DISPOSITIONS, RELAY_REQUEST_METHOD_DISPOSITIONS_V11, RELAY_REQUEST_METHOD_VALUES, RELAY_REQUEST_METHOD_VALUES_V11, RELAY_REQUEST_REPLAY_DISPOSITIONS, RELAY_REQUEST_METHODS, RELAY_REQUEST_METHODS_V11, classifyRelayAck, classifyRelayRequestReplay, getRelayRequestMethodDisposition, getRelayRequestMethodDispositionForVersion, negotiateRelayProtocolVersion, parseRelayMessageForVersion, parseRelayRequestParams, parseRelayRequestParamsForVersion, parseRelayRequestResult, parseRelayRequestResultForVersion, relayMessageSchema, relayRuntimeRequestSchema, relayRuntimeRequestSchemaV11, } from "./index.js";
2
2
  const IDS = {
3
3
  runtime: "00000000-0000-4000-8000-000000000501",
4
4
  thread: "00000000-0000-4000-8000-000000000502",
@@ -10,6 +10,8 @@ const IDS = {
10
10
  workspace: "00000000-0000-4000-8000-000000000507",
11
11
  secondWorkspace: "00000000-0000-4000-8000-000000000510",
12
12
  envelope: "00000000-0000-4000-8000-000000000508",
13
+ interaction: "00000000-0000-4000-8000-000000000511",
14
+ secondInteraction: "00000000-0000-4000-8000-000000000512",
13
15
  };
14
16
  export const RELAY_FRAME_CONFORMANCE_CASES = [
15
17
  {
@@ -186,11 +188,478 @@ export const RELAY_FRAME_CONFORMANCE_CASES = [
186
188
  },
187
189
  },
188
190
  ];
191
+ export const RELAY_FRAME_CONFORMANCE_CASES_V11 = [
192
+ {
193
+ id: "device-connect-v11",
194
+ fixtureName: "valid-device-connect.json",
195
+ valid: true,
196
+ frame: {
197
+ type: "device.connect",
198
+ protocolVersion: "1.1",
199
+ deviceId: IDS.device,
200
+ pairingToken: "pairing-token-with-sufficient-length",
201
+ },
202
+ },
203
+ {
204
+ id: "runtime-request-v11",
205
+ fixtureName: "valid-runtime-request.json",
206
+ valid: true,
207
+ frame: {
208
+ type: "runtime.request",
209
+ requestId: IDS.request,
210
+ workspaceId: IDS.workspace,
211
+ method: "thread.snapshot",
212
+ params: { threadId: IDS.thread, limit: 100 },
213
+ },
214
+ },
215
+ {
216
+ id: "interaction-candidate-user-input",
217
+ fixtureName: "valid-interaction-candidate-user-input.json",
218
+ valid: true,
219
+ frame: {
220
+ type: "runtime.request",
221
+ requestId: IDS.secondRequest,
222
+ workspaceId: IDS.workspace,
223
+ method: "interaction.candidate",
224
+ params: {
225
+ interactionId: IDS.interaction,
226
+ threadId: IDS.thread,
227
+ turnId: IDS.turn,
228
+ method: "userInput.request",
229
+ candidate: {
230
+ status: "submitted",
231
+ values: [
232
+ { id: "region", value: "north" },
233
+ { id: "workspace", value: "target-workspace" },
234
+ ],
235
+ },
236
+ },
237
+ },
238
+ },
239
+ {
240
+ id: "runtime-response-v11",
241
+ fixtureName: "valid-runtime-response.json",
242
+ valid: true,
243
+ frame: {
244
+ type: "runtime.response",
245
+ requestId: IDS.request,
246
+ workspaceId: IDS.workspace,
247
+ result: { accepted: true },
248
+ },
249
+ },
250
+ {
251
+ id: "runtime-event-v11",
252
+ fixtureName: "valid-runtime-event.json",
253
+ valid: true,
254
+ frame: {
255
+ type: "runtime.event",
256
+ workspaceId: IDS.workspace,
257
+ relaySequence: 0,
258
+ event: {
259
+ protocolVersion: "1.1",
260
+ runtimeInstanceId: IDS.runtime,
261
+ sequence: 0,
262
+ timestamp: "2026-08-04T00:00:00.000Z",
263
+ threadId: IDS.thread,
264
+ turnId: IDS.turn,
265
+ event: { type: "turn.completed" },
266
+ },
267
+ },
268
+ },
269
+ {
270
+ id: "runtime-event-runtime-n-minus-one",
271
+ fixtureName: "valid-runtime-event-runtime-n-minus-one.json",
272
+ valid: true,
273
+ frame: {
274
+ type: "runtime.event",
275
+ workspaceId: IDS.workspace,
276
+ relaySequence: 1,
277
+ event: {
278
+ protocolVersion: "1.0",
279
+ runtimeInstanceId: IDS.runtime,
280
+ sequence: 1,
281
+ timestamp: "2026-08-04T00:00:01.000Z",
282
+ threadId: IDS.thread,
283
+ turnId: IDS.turn,
284
+ event: { type: "turn.completed" },
285
+ },
286
+ },
287
+ },
288
+ {
289
+ id: "runtime-ack-v11",
290
+ fixtureName: "valid-runtime-ack.json",
291
+ valid: true,
292
+ frame: {
293
+ type: "runtime.ack",
294
+ workspaceId: IDS.workspace,
295
+ throughRelaySequence: 1,
296
+ },
297
+ },
298
+ {
299
+ id: "runtime-gap-v11",
300
+ fixtureName: "valid-runtime-gap.json",
301
+ valid: true,
302
+ frame: {
303
+ type: "runtime.gap",
304
+ workspaceId: IDS.workspace,
305
+ fromRelaySequence: 0,
306
+ throughRelaySequence: 2,
307
+ recovery: "thread.snapshot",
308
+ },
309
+ },
310
+ {
311
+ id: "runtime-encrypted-interaction-v11",
312
+ fixtureName: "valid-runtime-encrypted-interaction.json",
313
+ valid: true,
314
+ frame: {
315
+ type: "runtime.encrypted",
316
+ workspaceId: IDS.workspace,
317
+ envelopeId: IDS.envelope,
318
+ payloadKind: "interaction",
319
+ relaySequence: 2,
320
+ algorithm: "example-aead",
321
+ nonce: "opaque-nonce",
322
+ ciphertext: "opaque-ciphertext",
323
+ },
324
+ },
325
+ {
326
+ id: "interaction-request-approval",
327
+ fixtureName: "valid-interaction-request-approval.json",
328
+ valid: true,
329
+ frame: {
330
+ type: "interaction.request",
331
+ workspaceId: IDS.workspace,
332
+ relaySequence: 3,
333
+ interactionId: IDS.interaction,
334
+ threadId: IDS.thread,
335
+ turnId: IDS.turn,
336
+ expiresAt: "2026-08-04T00:05:00.000Z",
337
+ sensitivity: "normal",
338
+ method: "approval.request",
339
+ projection: {
340
+ approvalId: IDS.approval,
341
+ agentName: "workspace-agent",
342
+ toolName: "deploy",
343
+ explanation: "部署前需要确认。",
344
+ },
345
+ },
346
+ },
347
+ {
348
+ id: "interaction-request-user-input",
349
+ fixtureName: "valid-interaction-request-user-input.json",
350
+ valid: true,
351
+ frame: {
352
+ type: "interaction.request",
353
+ workspaceId: IDS.workspace,
354
+ relaySequence: 4,
355
+ interactionId: IDS.interaction,
356
+ threadId: IDS.thread,
357
+ turnId: IDS.turn,
358
+ expiresAt: "2026-08-04T00:05:00.000Z",
359
+ sensitivity: "normal",
360
+ method: "userInput.request",
361
+ projection: {
362
+ title: "部署配置",
363
+ controls: [
364
+ {
365
+ id: "region",
366
+ type: "choice",
367
+ label: "部署区域",
368
+ required: true,
369
+ multiple: false,
370
+ options: [
371
+ { id: "north", label: "北区" },
372
+ { id: "south", label: "南区" },
373
+ ],
374
+ },
375
+ {
376
+ id: "workspace",
377
+ type: "text",
378
+ label: "目标 Workspace",
379
+ required: true,
380
+ maxLength: 100,
381
+ },
382
+ ],
383
+ },
384
+ },
385
+ },
386
+ {
387
+ id: "interaction-resolved",
388
+ fixtureName: "valid-interaction-resolved.json",
389
+ valid: true,
390
+ frame: {
391
+ type: "interaction.resolved",
392
+ workspaceId: IDS.workspace,
393
+ relaySequence: 5,
394
+ interactionId: IDS.interaction,
395
+ threadId: IDS.thread,
396
+ turnId: IDS.turn,
397
+ method: "userInput.request",
398
+ },
399
+ },
400
+ {
401
+ id: "interaction-cancelled",
402
+ fixtureName: "valid-interaction-cancelled.json",
403
+ valid: true,
404
+ frame: {
405
+ type: "interaction.cancelled",
406
+ workspaceId: IDS.workspace,
407
+ relaySequence: 6,
408
+ interactionId: IDS.interaction,
409
+ threadId: IDS.thread,
410
+ turnId: IDS.turn,
411
+ method: "approval.request",
412
+ },
413
+ },
414
+ {
415
+ id: "n-minus-one-device-connect-rejected",
416
+ fixtureName: "invalid-n-minus-one-device-connect.json",
417
+ valid: false,
418
+ frame: {
419
+ type: "device.connect",
420
+ protocolVersion: "1.0",
421
+ deviceId: IDS.device,
422
+ pairingToken: "pairing-token-with-sufficient-length",
423
+ },
424
+ },
425
+ {
426
+ id: "legacy-approval-respond-rejected",
427
+ fixtureName: "invalid-legacy-approval-respond.json",
428
+ valid: false,
429
+ frame: {
430
+ type: "runtime.request",
431
+ requestId: IDS.request,
432
+ workspaceId: IDS.workspace,
433
+ method: "approval.respond",
434
+ params: {},
435
+ },
436
+ },
437
+ {
438
+ id: "legacy-approval-candidate-rejected",
439
+ fixtureName: "invalid-legacy-approval-candidate.json",
440
+ valid: false,
441
+ frame: {
442
+ type: "runtime.request",
443
+ requestId: IDS.request,
444
+ workspaceId: IDS.workspace,
445
+ method: "approval.candidate",
446
+ params: {},
447
+ },
448
+ },
449
+ {
450
+ id: "approval-projection-raw-input-leak",
451
+ fixtureName: "invalid-approval-projection-raw-input-leak.json",
452
+ valid: false,
453
+ frame: {
454
+ type: "interaction.request",
455
+ workspaceId: IDS.workspace,
456
+ relaySequence: 7,
457
+ interactionId: IDS.interaction,
458
+ threadId: IDS.thread,
459
+ turnId: IDS.turn,
460
+ expiresAt: "2026-08-04T00:05:00.000Z",
461
+ sensitivity: "normal",
462
+ method: "approval.request",
463
+ projection: {
464
+ approvalId: IDS.approval,
465
+ agentName: "workspace-agent",
466
+ toolName: "deploy",
467
+ input: { secret: "relay-security-sentinel" },
468
+ },
469
+ },
470
+ },
471
+ {
472
+ id: "user-input-projection-secret-leak",
473
+ fixtureName: "invalid-user-input-projection-secret-leak.json",
474
+ valid: false,
475
+ frame: {
476
+ type: "interaction.request",
477
+ workspaceId: IDS.workspace,
478
+ relaySequence: 8,
479
+ interactionId: IDS.interaction,
480
+ threadId: IDS.thread,
481
+ turnId: IDS.turn,
482
+ expiresAt: "2026-08-04T00:05:00.000Z",
483
+ sensitivity: "normal",
484
+ method: "userInput.request",
485
+ projection: {
486
+ controls: [
487
+ {
488
+ id: "workspace",
489
+ type: "text",
490
+ label: "目标 Workspace",
491
+ required: true,
492
+ },
493
+ ],
494
+ secret: "relay-security-sentinel",
495
+ },
496
+ },
497
+ },
498
+ {
499
+ id: "non-normal-sensitivity-rejected",
500
+ fixtureName: "invalid-non-normal-sensitivity.json",
501
+ valid: false,
502
+ frame: {
503
+ type: "interaction.request",
504
+ workspaceId: IDS.workspace,
505
+ relaySequence: 9,
506
+ interactionId: IDS.interaction,
507
+ threadId: IDS.thread,
508
+ turnId: IDS.turn,
509
+ expiresAt: "2026-08-04T00:05:00.000Z",
510
+ sensitivity: "secret",
511
+ method: "userInput.request",
512
+ projection: {
513
+ controls: [
514
+ {
515
+ id: "workspace",
516
+ type: "text",
517
+ label: "目标 Workspace",
518
+ required: true,
519
+ },
520
+ ],
521
+ },
522
+ },
523
+ },
524
+ {
525
+ id: "authentication-local-only",
526
+ fixtureName: "invalid-authentication-local-only.json",
527
+ valid: false,
528
+ frame: {
529
+ type: "interaction.request",
530
+ workspaceId: IDS.workspace,
531
+ relaySequence: 10,
532
+ interactionId: IDS.interaction,
533
+ threadId: IDS.thread,
534
+ turnId: IDS.turn,
535
+ expiresAt: "2026-08-04T00:05:00.000Z",
536
+ sensitivity: "normal",
537
+ method: "authentication.request",
538
+ projection: {},
539
+ },
540
+ },
541
+ {
542
+ id: "file-picker-local-only",
543
+ fixtureName: "invalid-file-picker-local-only.json",
544
+ valid: false,
545
+ frame: {
546
+ type: "interaction.request",
547
+ workspaceId: IDS.workspace,
548
+ relaySequence: 11,
549
+ interactionId: IDS.interaction,
550
+ threadId: IDS.thread,
551
+ turnId: IDS.turn,
552
+ expiresAt: "2026-08-04T00:05:00.000Z",
553
+ sensitivity: "normal",
554
+ method: "filePicker.request",
555
+ projection: {},
556
+ },
557
+ },
558
+ {
559
+ id: "interaction-resolved-result-leak",
560
+ fixtureName: "invalid-interaction-resolved-result-leak.json",
561
+ valid: false,
562
+ frame: {
563
+ type: "interaction.resolved",
564
+ workspaceId: IDS.workspace,
565
+ relaySequence: 12,
566
+ interactionId: IDS.interaction,
567
+ threadId: IDS.thread,
568
+ turnId: IDS.turn,
569
+ method: "userInput.request",
570
+ result: {
571
+ status: "submitted",
572
+ values: [{ id: "workspace", value: "relay-security-sentinel" }],
573
+ },
574
+ },
575
+ },
576
+ {
577
+ id: "interaction-cancelled-reason-leak",
578
+ fixtureName: "invalid-interaction-cancelled-reason-leak.json",
579
+ valid: false,
580
+ frame: {
581
+ type: "interaction.cancelled",
582
+ workspaceId: IDS.workspace,
583
+ relaySequence: 13,
584
+ interactionId: IDS.interaction,
585
+ threadId: IDS.thread,
586
+ turnId: IDS.turn,
587
+ method: "approval.request",
588
+ reason: "relay-security-sentinel",
589
+ },
590
+ },
591
+ {
592
+ id: "runtime-event-tool-input-leak",
593
+ fixtureName: "invalid-runtime-event-tool-input-leak.json",
594
+ valid: false,
595
+ frame: {
596
+ type: "runtime.event",
597
+ workspaceId: IDS.workspace,
598
+ relaySequence: 14,
599
+ event: {
600
+ protocolVersion: "1.1",
601
+ runtimeInstanceId: IDS.runtime,
602
+ sequence: 14,
603
+ timestamp: "2026-08-04T00:00:14.000Z",
604
+ threadId: IDS.thread,
605
+ turnId: IDS.turn,
606
+ event: {
607
+ type: "tool.started",
608
+ toolCallId: "tool-1",
609
+ agentName: "workspace-agent",
610
+ toolName: "deploy",
611
+ input: { secret: "relay-security-sentinel" },
612
+ },
613
+ },
614
+ },
615
+ },
616
+ {
617
+ id: "runtime-event-tool-output-leak",
618
+ fixtureName: "invalid-runtime-event-tool-output-leak.json",
619
+ valid: false,
620
+ frame: {
621
+ type: "runtime.event",
622
+ workspaceId: IDS.workspace,
623
+ relaySequence: 15,
624
+ event: {
625
+ protocolVersion: "1.1",
626
+ runtimeInstanceId: IDS.runtime,
627
+ sequence: 15,
628
+ timestamp: "2026-08-04T00:00:15.000Z",
629
+ threadId: IDS.thread,
630
+ turnId: IDS.turn,
631
+ event: {
632
+ type: "tool.output",
633
+ toolCallId: "tool-1",
634
+ agentName: "workspace-agent",
635
+ toolName: "deploy",
636
+ stream: "stdout",
637
+ delta: "relay-security-sentinel",
638
+ },
639
+ },
640
+ },
641
+ },
642
+ ];
189
643
  export const RELAY_NEGOTIATION_CONFORMANCE_CASES = [
190
644
  { id: "same-version", peerVersions: ["1.0"], expected: "1.0" },
191
645
  { id: "unknown-before-supported", peerVersions: ["9.9", "1.0"], expected: "1.0" },
192
646
  { id: "unknown-only", peerVersions: ["9.9"], expected: undefined },
193
647
  ];
648
+ export const RELAY_NEGOTIATION_CONFORMANCE_CASES_V11 = [
649
+ { id: "same-version-v11", peerVersions: ["1.1"], expected: "1.1" },
650
+ {
651
+ id: "latest-supported-version-preferred",
652
+ peerVersions: ["1.0", "1.1"],
653
+ expected: "1.1",
654
+ },
655
+ { id: "n-minus-one-fallback", peerVersions: ["1.0"], expected: "1.0" },
656
+ {
657
+ id: "unknown-before-supported-v11",
658
+ peerVersions: ["9.9", "1.1"],
659
+ expected: "1.1",
660
+ },
661
+ { id: "unknown-only-v11", peerVersions: ["9.9"], expected: undefined },
662
+ ];
194
663
  export const RELAY_METHOD_REGISTRY_CONFORMANCE_CASES = [
195
664
  ...RELAY_REQUEST_METHOD_VALUES.map((value) => ({
196
665
  id: `method-disposition-${value}`,
@@ -203,6 +672,28 @@ export const RELAY_METHOD_REGISTRY_CONFORMANCE_CASES = [
203
672
  expected: undefined,
204
673
  },
205
674
  ];
675
+ export const RELAY_METHOD_REGISTRY_CONFORMANCE_CASES_V11 = [
676
+ ...RELAY_REQUEST_METHOD_VALUES_V11.map((value) => ({
677
+ id: `method-disposition-v11-${value}`,
678
+ value,
679
+ expected: RELAY_REQUEST_METHOD_DISPOSITIONS_V11[value],
680
+ })),
681
+ {
682
+ id: "legacy-approval-respond-not-recognized-v11",
683
+ value: RELAY_REQUEST_METHODS.approvalRespond,
684
+ expected: undefined,
685
+ },
686
+ {
687
+ id: "legacy-approval-candidate-not-recognized-v11",
688
+ value: RELAY_REQUEST_METHODS.approvalCandidate,
689
+ expected: undefined,
690
+ },
691
+ {
692
+ id: "unknown-method-not-recognized-v11",
693
+ value: "runtime.futureMethod",
694
+ expected: undefined,
695
+ },
696
+ ];
206
697
  export const RELAY_METHOD_CONFORMANCE_CASES = [
207
698
  {
208
699
  id: "approval-candidate-params",
@@ -237,6 +728,77 @@ export const RELAY_METHOD_CONFORMANCE_CASES = [
237
728
  value: { accepted: true },
238
729
  },
239
730
  ];
731
+ export const RELAY_METHOD_CONFORMANCE_CASES_V11 = [
732
+ {
733
+ id: "interaction-candidate-approval-params",
734
+ method: RELAY_REQUEST_METHODS_V11.interactionCandidate,
735
+ kind: "params",
736
+ valid: true,
737
+ value: {
738
+ interactionId: IDS.interaction,
739
+ threadId: IDS.thread,
740
+ turnId: IDS.turn,
741
+ method: RELAY_INTERACTION_METHODS_V11.approvalRequest,
742
+ candidate: { decision: "approve" },
743
+ },
744
+ },
745
+ {
746
+ id: "interaction-candidate-user-input-params",
747
+ method: RELAY_REQUEST_METHODS_V11.interactionCandidate,
748
+ kind: "params",
749
+ valid: true,
750
+ value: {
751
+ interactionId: IDS.interaction,
752
+ threadId: IDS.thread,
753
+ turnId: IDS.turn,
754
+ method: RELAY_INTERACTION_METHODS_V11.userInputRequest,
755
+ candidate: {
756
+ status: "submitted",
757
+ values: [
758
+ { id: "region", value: ["north"] },
759
+ { id: "workspace", value: "production" },
760
+ ],
761
+ },
762
+ },
763
+ },
764
+ {
765
+ id: "interaction-candidate-method-result-mismatch",
766
+ method: RELAY_REQUEST_METHODS_V11.interactionCandidate,
767
+ kind: "params",
768
+ valid: false,
769
+ value: {
770
+ interactionId: IDS.interaction,
771
+ threadId: IDS.thread,
772
+ turnId: IDS.turn,
773
+ method: RELAY_INTERACTION_METHODS_V11.approvalRequest,
774
+ candidate: { status: "submitted", values: [] },
775
+ },
776
+ },
777
+ {
778
+ id: "interaction-candidate-missing-identity",
779
+ method: RELAY_REQUEST_METHODS_V11.interactionCandidate,
780
+ kind: "params",
781
+ valid: false,
782
+ value: {
783
+ method: RELAY_INTERACTION_METHODS_V11.userInputRequest,
784
+ candidate: { status: "cancelled" },
785
+ },
786
+ },
787
+ {
788
+ id: "interaction-candidate-result",
789
+ method: RELAY_REQUEST_METHODS_V11.interactionCandidate,
790
+ kind: "result",
791
+ valid: true,
792
+ value: { accepted: true },
793
+ },
794
+ {
795
+ id: "interaction-candidate-result-must-be-accepted",
796
+ method: RELAY_REQUEST_METHODS_V11.interactionCandidate,
797
+ kind: "result",
798
+ valid: false,
799
+ value: { accepted: false },
800
+ },
801
+ ];
240
802
  const replayRequest = relayRuntimeRequestSchema.parse(RELAY_FRAME_CONFORMANCE_CASES.find((entry) => entry.id === "runtime-request")?.frame);
241
803
  export const RELAY_REPLAY_CONFORMANCE_CASES = [
242
804
  {
@@ -297,6 +859,73 @@ export const RELAY_REPLAY_CONFORMANCE_CASES = [
297
859
  },
298
860
  },
299
861
  ];
862
+ const replayRequestV11 = relayRuntimeRequestSchemaV11.parse(RELAY_FRAME_CONFORMANCE_CASES_V11.find((entry) => entry.id === "interaction-candidate-user-input")
863
+ ?.frame);
864
+ export const RELAY_REPLAY_CONFORMANCE_CASES_V11 = [
865
+ {
866
+ id: "interaction-candidate-duplicate",
867
+ existing: replayRequestV11,
868
+ candidate: relayRuntimeRequestSchemaV11.parse({
869
+ ...replayRequestV11,
870
+ params: {
871
+ turnId: IDS.turn,
872
+ interactionId: IDS.interaction,
873
+ candidate: {
874
+ values: [
875
+ { value: "north", id: "region" },
876
+ { value: "target-workspace", id: "workspace" },
877
+ ],
878
+ status: "submitted",
879
+ },
880
+ method: RELAY_INTERACTION_METHODS_V11.userInputRequest,
881
+ threadId: IDS.thread,
882
+ },
883
+ }),
884
+ expected: { disposition: RELAY_REQUEST_REPLAY_DISPOSITIONS.replay },
885
+ },
886
+ {
887
+ id: "interaction-candidate-conflict",
888
+ existing: replayRequestV11,
889
+ candidate: relayRuntimeRequestSchemaV11.parse({
890
+ ...replayRequestV11,
891
+ params: {
892
+ interactionId: IDS.secondInteraction,
893
+ threadId: IDS.thread,
894
+ turnId: IDS.turn,
895
+ method: RELAY_INTERACTION_METHODS_V11.userInputRequest,
896
+ candidate: {
897
+ status: "submitted",
898
+ values: [
899
+ { id: "region", value: "north" },
900
+ { id: "workspace", value: "target-workspace" },
901
+ ],
902
+ },
903
+ },
904
+ }),
905
+ expected: {
906
+ disposition: RELAY_REQUEST_REPLAY_DISPOSITIONS.conflict,
907
+ errorCode: RELAY_ERROR_CODES.requestIdConflict,
908
+ },
909
+ },
910
+ {
911
+ id: "interaction-candidate-different-request-id",
912
+ existing: replayRequestV11,
913
+ candidate: relayRuntimeRequestSchemaV11.parse({
914
+ ...replayRequestV11,
915
+ requestId: IDS.request,
916
+ }),
917
+ expected: { disposition: RELAY_REQUEST_REPLAY_DISPOSITIONS.new },
918
+ },
919
+ {
920
+ id: "interaction-candidate-different-workspace",
921
+ existing: replayRequestV11,
922
+ candidate: relayRuntimeRequestSchemaV11.parse({
923
+ ...replayRequestV11,
924
+ workspaceId: IDS.secondWorkspace,
925
+ }),
926
+ expected: { disposition: RELAY_REQUEST_REPLAY_DISPOSITIONS.new },
927
+ },
928
+ ];
300
929
  export const RELAY_ACK_CONFORMANCE_CASES = [
301
930
  {
302
931
  id: "initial-advance",
@@ -366,7 +995,7 @@ export const RELAY_ERROR_CONFORMANCE_CASES = Object.values(RELAY_ERROR_CODES).ma
366
995
  }));
367
996
  export const runtimeRelayProtocolConformanceAdapter = {
368
997
  validateFrame: (value) => relayMessageSchema.safeParse(value).success,
369
- negotiate: negotiateRelayProtocolVersion,
998
+ negotiate: (peerVersions) => peerVersions.includes(RELAY_PROTOCOL_VERSION) ? RELAY_PROTOCOL_VERSION : undefined,
370
999
  getRequestMethodDisposition: getRelayRequestMethodDisposition,
371
1000
  validateMethodValue(method, kind, value) {
372
1001
  try {
@@ -386,21 +1015,61 @@ export const runtimeRelayProtocolConformanceAdapter = {
386
1015
  classifyAck: classifyRelayAck,
387
1016
  getErrorRetryability: (code) => RELAY_ERROR_RETRYABILITY[code],
388
1017
  };
1018
+ export const runtimeRelayProtocolConformanceAdapterV10 = runtimeRelayProtocolConformanceAdapter;
1019
+ export const runtimeRelayProtocolConformanceAdapterV11 = {
1020
+ validateFrame(value) {
1021
+ try {
1022
+ parseRelayMessageForVersion("1.1", value);
1023
+ return true;
1024
+ }
1025
+ catch {
1026
+ return false;
1027
+ }
1028
+ },
1029
+ negotiate: negotiateRelayProtocolVersion,
1030
+ getRequestMethodDisposition: (value) => getRelayRequestMethodDispositionForVersion("1.1", value),
1031
+ validateMethodValue(method, kind, value) {
1032
+ try {
1033
+ if (kind === "params") {
1034
+ parseRelayRequestParamsForVersion("1.1", method, value);
1035
+ }
1036
+ else {
1037
+ parseRelayRequestResultForVersion("1.1", method, value);
1038
+ }
1039
+ return true;
1040
+ }
1041
+ catch {
1042
+ return false;
1043
+ }
1044
+ },
1045
+ classifyReplay: classifyRelayRequestReplay,
1046
+ classifyAck: classifyRelayAck,
1047
+ getErrorRetryability: (code) => RELAY_ERROR_RETRYABILITY[code],
1048
+ };
389
1049
  export function runRelayProtocolConformance(adapter) {
1050
+ return runRelayProtocolConformanceCases(RELAY_PROTOCOL_VERSION, adapter, RELAY_FRAME_CONFORMANCE_CASES, RELAY_NEGOTIATION_CONFORMANCE_CASES, RELAY_METHOD_REGISTRY_CONFORMANCE_CASES, RELAY_METHOD_CONFORMANCE_CASES, RELAY_REPLAY_CONFORMANCE_CASES);
1051
+ }
1052
+ export function runRelayProtocolConformanceForVersion(...args) {
1053
+ if (args[0] === "1.0") {
1054
+ return runRelayProtocolConformance(args[1]);
1055
+ }
1056
+ return runRelayProtocolConformanceCases(args[0], args[1], RELAY_FRAME_CONFORMANCE_CASES_V11, RELAY_NEGOTIATION_CONFORMANCE_CASES_V11, RELAY_METHOD_REGISTRY_CONFORMANCE_CASES_V11, RELAY_METHOD_CONFORMANCE_CASES_V11, RELAY_REPLAY_CONFORMANCE_CASES_V11);
1057
+ }
1058
+ function runRelayProtocolConformanceCases(protocolVersion, adapter, frames, negotiations, methodRegistry, methods, replay) {
390
1059
  const failures = [];
391
- for (const entry of RELAY_FRAME_CONFORMANCE_CASES) {
1060
+ for (const entry of frames) {
392
1061
  recordFailure(failures, entry.id, entry.valid, adapter.validateFrame(entry.frame));
393
1062
  }
394
- for (const entry of RELAY_NEGOTIATION_CONFORMANCE_CASES) {
1063
+ for (const entry of negotiations) {
395
1064
  recordFailure(failures, entry.id, entry.expected ?? "unsupported", adapter.negotiate(entry.peerVersions) ?? "unsupported");
396
1065
  }
397
- for (const entry of RELAY_METHOD_REGISTRY_CONFORMANCE_CASES) {
1066
+ for (const entry of methodRegistry) {
398
1067
  recordFailure(failures, entry.id, entry.expected ?? "unrecognized", adapter.getRequestMethodDisposition(entry.value) ?? "unrecognized");
399
1068
  }
400
- for (const entry of RELAY_METHOD_CONFORMANCE_CASES) {
1069
+ for (const entry of methods) {
401
1070
  recordFailure(failures, entry.id, entry.valid, adapter.validateMethodValue(entry.method, entry.kind, entry.value));
402
1071
  }
403
- for (const entry of RELAY_REPLAY_CONFORMANCE_CASES) {
1072
+ for (const entry of replay) {
404
1073
  const actual = adapter.classifyReplay(entry.existing, entry.candidate);
405
1074
  recordFailure(failures, `${entry.id}-disposition`, entry.expected.disposition, actual.disposition);
406
1075
  recordFailure(failures, `${entry.id}-error-code`, "errorCode" in entry.expected ? entry.expected.errorCode : "none", actual.errorCode ?? "none");
@@ -412,7 +1081,7 @@ export function runRelayProtocolConformance(adapter) {
412
1081
  recordFailure(failures, entry.id, entry.retryable, adapter.getErrorRetryability(entry.code));
413
1082
  }
414
1083
  return {
415
- protocolVersion: RELAY_PROTOCOL_VERSION,
1084
+ protocolVersion,
416
1085
  passed: failures.length === 0,
417
1086
  failures,
418
1087
  };