@get-bb/plugin-sdk 0.4.15 → 0.4.17
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 +3 -3
- package/bundled-types/bb-plugin-sdk-ai-services.d.ts +141 -0
- package/bundled-types/bb-plugin-sdk-app.d.ts +144 -21
- package/bundled-types/bb-plugin-sdk-host.d.ts +479 -2
- package/bundled-types/bb-plugin-sdk-internal-composer-customization-validation.d.ts +10 -1
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +339 -32
- package/bundled-types/bb-plugin-sdk-internal-plugin-app-collector.d.ts +2 -1
- package/bundled-types/bb-plugin-sdk-provider-bridge-acp.d.ts +824 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +1742 -211
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +2985 -2442
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +4 -3
- package/bundled-types/bb-plugin-sdk-testing.d.ts +551 -9
- package/bundled-types/bb-plugin-sdk.d.ts +1337 -1349
- package/dist/ai-services.js +91 -0
- package/dist/app.js +2 -2
- package/dist/host.js +14547 -1
- package/dist/internal/composer-customization-validation.js +11 -0
- package/dist/internal/host-policy.js +656 -91
- package/dist/internal/plugin-app-collector.js +58 -14
- package/dist/provider-bridge-acp.js +9940 -0
- package/dist/provider-bridge-testing.js +2979 -1395
- package/dist/provider-bridge-worker-entry.mjs +917 -0
- package/dist/provider-bridge.js +2255 -3718
- package/dist/replay-provider-child.mjs +650 -0
- package/dist/testing/app.js +63 -19
- package/dist/testing/index.js +803 -115
- package/package.json +14 -1
|
@@ -1,103 +1,3 @@
|
|
|
1
|
-
// ../provider-bridge-protocol/src/conformance/client.ts
|
|
2
|
-
function isWireMessage(value) {
|
|
3
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
-
}
|
|
5
|
-
var ConformanceClient = class {
|
|
6
|
-
constructor(transport, timeoutMs) {
|
|
7
|
-
this.transport = transport;
|
|
8
|
-
this.timeoutMs = timeoutMs;
|
|
9
|
-
}
|
|
10
|
-
transport;
|
|
11
|
-
timeoutMs;
|
|
12
|
-
nextId = 1;
|
|
13
|
-
log = [];
|
|
14
|
-
drainIntoLog() {
|
|
15
|
-
for (const raw of this.transport.takeMessages()) {
|
|
16
|
-
if (isWireMessage(raw)) {
|
|
17
|
-
this.log.push(raw);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
sendRaw(line) {
|
|
22
|
-
this.transport.send(line);
|
|
23
|
-
}
|
|
24
|
-
notify(method, params) {
|
|
25
|
-
this.transport.send(
|
|
26
|
-
JSON.stringify({
|
|
27
|
-
jsonrpc: "2.0",
|
|
28
|
-
method,
|
|
29
|
-
...params !== void 0 ? { params } : {}
|
|
30
|
-
})
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
request(method, params) {
|
|
34
|
-
const id = this.nextId;
|
|
35
|
-
this.nextId += 1;
|
|
36
|
-
this.transport.send(
|
|
37
|
-
JSON.stringify({
|
|
38
|
-
jsonrpc: "2.0",
|
|
39
|
-
id,
|
|
40
|
-
method,
|
|
41
|
-
...params !== void 0 ? { params } : {}
|
|
42
|
-
})
|
|
43
|
-
);
|
|
44
|
-
return id;
|
|
45
|
-
}
|
|
46
|
-
/** Poll until `resolve` yields a value or the deadline passes (→ null). */
|
|
47
|
-
async waitFor(resolve2) {
|
|
48
|
-
const deadline = Date.now() + this.timeoutMs;
|
|
49
|
-
for (; ; ) {
|
|
50
|
-
this.drainIntoLog();
|
|
51
|
-
const value = resolve2();
|
|
52
|
-
if (value !== void 0) {
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
if (Date.now() > deadline) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
await new Promise((r) => setTimeout(r, 15));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async waitForResponse(id) {
|
|
62
|
-
return this.waitFor(
|
|
63
|
-
() => this.log.find(
|
|
64
|
-
(message) => message.id === id && message.method === void 0
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
/** A settle window: drain for the given quiet period without expectations. */
|
|
69
|
-
async settle(quietMs) {
|
|
70
|
-
const deadline = Date.now() + quietMs;
|
|
71
|
-
while (Date.now() < deadline) {
|
|
72
|
-
this.drainIntoLog();
|
|
73
|
-
await new Promise((r) => setTimeout(r, 15));
|
|
74
|
-
}
|
|
75
|
-
this.drainIntoLog();
|
|
76
|
-
}
|
|
77
|
-
responsesFor(id) {
|
|
78
|
-
return this.log.filter(
|
|
79
|
-
(message) => message.id === id && message.method === void 0
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
notifications(method) {
|
|
83
|
-
return this.log.filter(
|
|
84
|
-
(message) => message.id === void 0 && typeof message.method === "string" && (method === void 0 || message.method === method)
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
var clientRequestCounter = 0;
|
|
89
|
-
function nextConformanceClientRequestId() {
|
|
90
|
-
const alphabet = "23456789abcdefghijkmnpqrstuvwxyz";
|
|
91
|
-
clientRequestCounter += 1;
|
|
92
|
-
let remaining = clientRequestCounter;
|
|
93
|
-
let suffix = "";
|
|
94
|
-
while (suffix.length < 10) {
|
|
95
|
-
suffix = alphabet[remaining % alphabet.length] + suffix;
|
|
96
|
-
remaining = Math.floor(remaining / alphabet.length);
|
|
97
|
-
}
|
|
98
|
-
return `creq_${suffix}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
1
|
// ../domain/src/shared-types.ts
|
|
102
2
|
import { z as z2 } from "zod";
|
|
103
3
|
|
|
@@ -431,6 +331,10 @@ import { z as z6 } from "zod";
|
|
|
431
331
|
|
|
432
332
|
// ../domain/src/plugin-interaction-limits.ts
|
|
433
333
|
var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
|
|
334
|
+
var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
335
|
+
function jsonByteLength(value) {
|
|
336
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
337
|
+
}
|
|
434
338
|
|
|
435
339
|
// ../domain/src/item-presentation.ts
|
|
436
340
|
import { z as z4 } from "zod";
|
|
@@ -692,26 +596,21 @@ var pluginPendingInteractionPayloadSchema = z6.object({
|
|
|
692
596
|
title: z6.string().trim().min(1).max(PLUGIN_INTERACTION_MAX_TITLE_LENGTH),
|
|
693
597
|
data: jsonValueSchema
|
|
694
598
|
});
|
|
695
|
-
var pendingInteractionPayloadSchema = z6.discriminatedUnion("kind", [
|
|
696
|
-
approvalPendingInteractionPayloadSchema,
|
|
697
|
-
userQuestionPendingInteractionPayloadSchema
|
|
698
|
-
]);
|
|
699
|
-
var planReviewInteractionRequestPayloadSchema = z6.object({
|
|
700
|
-
kind: z6.literal("plan_review"),
|
|
701
|
-
itemId: z6.string().min(1),
|
|
702
|
-
/** The plan body, as Markdown. */
|
|
703
|
-
plan: z6.string().min(1),
|
|
704
|
-
/** Where the provider saved the plan, or null when it kept it in memory. */
|
|
705
|
-
planFilePath: z6.string().min(1).nullable()
|
|
706
|
-
});
|
|
707
599
|
var pluginExtensionInteractionRequestPayloadSchema = z6.object({
|
|
708
600
|
kind: extensionKindSchema,
|
|
709
601
|
title: z6.string().trim().min(1).max(PLUGIN_INTERACTION_MAX_TITLE_LENGTH),
|
|
710
|
-
data: jsonValueSchema
|
|
602
|
+
data: jsonValueSchema.refine(
|
|
603
|
+
(value) => jsonByteLength(value) <= PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES,
|
|
604
|
+
{ message: "Plugin request data exceeds 64 KiB" }
|
|
605
|
+
)
|
|
711
606
|
});
|
|
712
607
|
var interactionRequestPayloadSchema = z6.union([
|
|
713
608
|
userQuestionPendingInteractionPayloadSchema,
|
|
714
|
-
|
|
609
|
+
pluginExtensionInteractionRequestPayloadSchema
|
|
610
|
+
]);
|
|
611
|
+
var pendingInteractionPayloadSchema = z6.union([
|
|
612
|
+
approvalPendingInteractionPayloadSchema,
|
|
613
|
+
userQuestionPendingInteractionPayloadSchema,
|
|
715
614
|
pluginExtensionInteractionRequestPayloadSchema
|
|
716
615
|
]);
|
|
717
616
|
var approvalDecisionDiscriminatorError = "Invalid discriminator value. Expected 'allow_once' | 'allow_for_session' | 'deny'";
|
|
@@ -746,14 +645,36 @@ var userQuestionPendingInteractionResolutionSchema = z6.object({
|
|
|
746
645
|
var pluginPendingInteractionResolutionSchema = z6.object({
|
|
747
646
|
kind: z6.literal("plugin_submitted")
|
|
748
647
|
});
|
|
648
|
+
var pluginExtensionInteractionResolutionSchema = z6.object({
|
|
649
|
+
kind: z6.literal("request_answer"),
|
|
650
|
+
value: jsonValueSchema
|
|
651
|
+
});
|
|
749
652
|
var pendingInteractionResolutionSchema = z6.union(
|
|
750
653
|
[
|
|
751
654
|
approvalPendingInteractionResolutionSchema,
|
|
752
655
|
userQuestionPendingInteractionResolutionSchema,
|
|
753
|
-
pluginPendingInteractionResolutionSchema
|
|
656
|
+
pluginPendingInteractionResolutionSchema,
|
|
657
|
+
pluginExtensionInteractionResolutionSchema
|
|
754
658
|
],
|
|
755
659
|
approvalDecisionDiscriminatorError
|
|
756
660
|
);
|
|
661
|
+
var approvalInteractionOutcomeSchema = z6.object({
|
|
662
|
+
payload: approvalPendingInteractionPayloadSchema,
|
|
663
|
+
resolution: approvalPendingInteractionResolutionSchema
|
|
664
|
+
});
|
|
665
|
+
var userQuestionInteractionOutcomeSchema = z6.object({
|
|
666
|
+
payload: userQuestionPendingInteractionPayloadSchema,
|
|
667
|
+
resolution: userQuestionPendingInteractionResolutionSchema
|
|
668
|
+
});
|
|
669
|
+
var pluginExtensionInteractionOutcomeSchema = z6.object({
|
|
670
|
+
payload: pluginExtensionInteractionRequestPayloadSchema,
|
|
671
|
+
resolution: pluginExtensionInteractionResolutionSchema
|
|
672
|
+
});
|
|
673
|
+
var providerInteractionOutcomeSchema = z6.union([
|
|
674
|
+
approvalInteractionOutcomeSchema,
|
|
675
|
+
userQuestionInteractionOutcomeSchema,
|
|
676
|
+
pluginExtensionInteractionOutcomeSchema
|
|
677
|
+
]);
|
|
757
678
|
var pendingInteractionProviderOriginSchema = z6.object({
|
|
758
679
|
kind: z6.literal("provider"),
|
|
759
680
|
providerId: z6.string().min(1),
|
|
@@ -771,10 +692,7 @@ var pendingInteractionCreateSchema = z6.object({
|
|
|
771
692
|
providerId: z6.string().min(1),
|
|
772
693
|
providerThreadId: z6.string().min(1),
|
|
773
694
|
providerRequestId: z6.string().min(1),
|
|
774
|
-
payload:
|
|
775
|
-
approvalPendingInteractionPayloadSchema,
|
|
776
|
-
userQuestionPendingInteractionPayloadSchema
|
|
777
|
-
])
|
|
695
|
+
payload: pendingInteractionPayloadSchema
|
|
778
696
|
});
|
|
779
697
|
var pendingInteractionBaseSchema = z6.object({
|
|
780
698
|
id: z6.string().min(1),
|
|
@@ -785,21 +703,30 @@ var pendingInteractionBaseSchema = z6.object({
|
|
|
785
703
|
expiresAt: z6.number().int().nonnegative().nullable().optional(),
|
|
786
704
|
resolvedAt: z6.number().int().nonnegative().nullable()
|
|
787
705
|
});
|
|
788
|
-
var
|
|
706
|
+
var providerPendingInteractionBaseSchema = pendingInteractionBaseSchema.extend({
|
|
789
707
|
turnId: z6.string().min(1),
|
|
790
708
|
providerId: z6.string().min(1),
|
|
791
709
|
providerThreadId: z6.string().min(1),
|
|
792
710
|
providerRequestId: z6.string().min(1),
|
|
793
|
-
origin: pendingInteractionProviderOriginSchema.optional()
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
711
|
+
origin: pendingInteractionProviderOriginSchema.optional()
|
|
712
|
+
});
|
|
713
|
+
var approvalPendingInteractionSchema = providerPendingInteractionBaseSchema.extend({
|
|
714
|
+
payload: approvalPendingInteractionPayloadSchema,
|
|
715
|
+
resolution: approvalPendingInteractionResolutionSchema.nullable()
|
|
716
|
+
});
|
|
717
|
+
var userQuestionPendingInteractionSchema = providerPendingInteractionBaseSchema.extend({
|
|
718
|
+
payload: userQuestionPendingInteractionPayloadSchema,
|
|
719
|
+
resolution: userQuestionPendingInteractionResolutionSchema.nullable()
|
|
802
720
|
});
|
|
721
|
+
var pluginExtensionPendingInteractionSchema = providerPendingInteractionBaseSchema.extend({
|
|
722
|
+
payload: pluginExtensionInteractionRequestPayloadSchema,
|
|
723
|
+
resolution: pluginExtensionInteractionResolutionSchema.nullable()
|
|
724
|
+
});
|
|
725
|
+
var providerPendingInteractionSchema = z6.union([
|
|
726
|
+
approvalPendingInteractionSchema,
|
|
727
|
+
userQuestionPendingInteractionSchema,
|
|
728
|
+
pluginExtensionPendingInteractionSchema
|
|
729
|
+
]);
|
|
803
730
|
var pluginPendingInteractionSchema = pendingInteractionBaseSchema.extend({
|
|
804
731
|
turnId: z6.string().min(1).nullable(),
|
|
805
732
|
origin: pendingInteractionPluginOriginSchema,
|
|
@@ -810,6 +737,45 @@ var pendingInteractionSchema = z6.union([
|
|
|
810
737
|
providerPendingInteractionSchema,
|
|
811
738
|
pluginPendingInteractionSchema
|
|
812
739
|
]);
|
|
740
|
+
var interactionLifecycleRecordBaseSchema = z6.object({
|
|
741
|
+
id: z6.string().min(1),
|
|
742
|
+
status: pendingInteractionStatusSchema,
|
|
743
|
+
statusReason: z6.string().nullable()
|
|
744
|
+
});
|
|
745
|
+
var interactionLifecycleProviderOriginSchema = z6.object({
|
|
746
|
+
kind: z6.literal("provider"),
|
|
747
|
+
providerId: z6.string().min(1),
|
|
748
|
+
providerRequestId: z6.string().min(1)
|
|
749
|
+
});
|
|
750
|
+
var approvalInteractionLifecycleRecordPayloadSchema = approvalPendingInteractionPayloadSchema.omit({ availableDecisions: true });
|
|
751
|
+
var approvalInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
|
|
752
|
+
origin: interactionLifecycleProviderOriginSchema,
|
|
753
|
+
payload: approvalInteractionLifecycleRecordPayloadSchema,
|
|
754
|
+
resolution: approvalPendingInteractionResolutionSchema.nullable()
|
|
755
|
+
});
|
|
756
|
+
var userQuestionInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
|
|
757
|
+
origin: interactionLifecycleProviderOriginSchema,
|
|
758
|
+
payload: userQuestionPendingInteractionPayloadSchema,
|
|
759
|
+
resolution: userQuestionPendingInteractionResolutionSchema.nullable()
|
|
760
|
+
});
|
|
761
|
+
var pluginInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
|
|
762
|
+
origin: pendingInteractionPluginOriginSchema,
|
|
763
|
+
payload: pluginPendingInteractionPayloadSchema.omit({ data: true }),
|
|
764
|
+
resolution: pluginPendingInteractionResolutionSchema.nullable()
|
|
765
|
+
});
|
|
766
|
+
var pluginExtensionInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
|
|
767
|
+
origin: interactionLifecycleProviderOriginSchema,
|
|
768
|
+
payload: pluginExtensionInteractionRequestPayloadSchema.omit({
|
|
769
|
+
data: true
|
|
770
|
+
}),
|
|
771
|
+
resolution: pluginExtensionInteractionResolutionSchema.omit({ value: true }).nullable()
|
|
772
|
+
});
|
|
773
|
+
var interactionLifecycleSchema = z6.union([
|
|
774
|
+
approvalInteractionLifecycleSchema,
|
|
775
|
+
userQuestionInteractionLifecycleSchema,
|
|
776
|
+
pluginInteractionLifecycleSchema,
|
|
777
|
+
pluginExtensionInteractionLifecycleSchema
|
|
778
|
+
]);
|
|
813
779
|
|
|
814
780
|
// ../domain/src/protocol-ids.ts
|
|
815
781
|
import { z as z7 } from "zod";
|
|
@@ -827,6 +793,11 @@ var systemEventTypeValues = [
|
|
|
827
793
|
"system/manager/user_message",
|
|
828
794
|
"system/thread/interrupted",
|
|
829
795
|
"system/operation",
|
|
796
|
+
"system/interaction/lifecycle",
|
|
797
|
+
// Legacy persisted per-shape interaction events; every status change now
|
|
798
|
+
// appends one `system/interaction/lifecycle`. Retained for read/decode
|
|
799
|
+
// only: `convertLegacyStoredThreadEvent` projects a stored row into the
|
|
800
|
+
// lifecycle event, so no consumer sees these types.
|
|
830
801
|
"system/permissionGrant/lifecycle",
|
|
831
802
|
"system/userQuestion/lifecycle",
|
|
832
803
|
"system/thread-provisioning",
|
|
@@ -956,6 +927,9 @@ var systemOperationEventDataSchema = z8.object({
|
|
|
956
927
|
operationId: z8.string(),
|
|
957
928
|
metadata: z8.record(z8.string(), jsonValueSchema).optional()
|
|
958
929
|
});
|
|
930
|
+
var systemInteractionLifecycleEventDataSchema = z8.object({
|
|
931
|
+
interaction: interactionLifecycleSchema
|
|
932
|
+
});
|
|
959
933
|
var systemPermissionGrantLifecycleEventDataSchema = z8.object({
|
|
960
934
|
interactionId: z8.string(),
|
|
961
935
|
providerId: z8.string(),
|
|
@@ -1163,6 +1137,10 @@ var threadEventScopeDefinitionByType = {
|
|
|
1163
1137
|
policy: "thread-or-turn",
|
|
1164
1138
|
rationale: "Thread-management operations use thread scope outside provider turns; tool-owned operations use turn scope so the operation stays with the tool call that caused it."
|
|
1165
1139
|
},
|
|
1140
|
+
"system/interaction/lifecycle": {
|
|
1141
|
+
policy: "thread-or-turn",
|
|
1142
|
+
rationale: "A provider interaction belongs to the turn that raised it; a plugin may raise one outside any turn."
|
|
1143
|
+
},
|
|
1166
1144
|
"system/permissionGrant/lifecycle": { policy: "turn" },
|
|
1167
1145
|
"system/userQuestion/lifecycle": { policy: "turn" },
|
|
1168
1146
|
"system/thread-provisioning": {
|
|
@@ -1558,8 +1536,6 @@ var threadEventItemSchema = z11.discriminatedUnion("type", [
|
|
|
1558
1536
|
server: z11.string().optional(),
|
|
1559
1537
|
tool: z11.string(),
|
|
1560
1538
|
arguments: z11.record(z11.string(), z11.unknown()).optional(),
|
|
1561
|
-
/** Server-enriched labels for a native plugin tool's timeline row. */
|
|
1562
|
-
statusLabels: z11.object({ pending: z11.string(), completed: z11.string() }).optional(),
|
|
1563
1539
|
status: threadEventItemStatusSchema,
|
|
1564
1540
|
result: z11.unknown().optional(),
|
|
1565
1541
|
error: z11.string().optional(),
|
|
@@ -1567,8 +1543,7 @@ var threadEventItemSchema = z11.discriminatedUnion("type", [
|
|
|
1567
1543
|
truncation: threadEventItemTruncationSchema.optional(),
|
|
1568
1544
|
/**
|
|
1569
1545
|
* The escape hatch for tools with no core kind: the bridge says how the
|
|
1570
|
-
* row reads
|
|
1571
|
-
* present (WS3 deletes `statusLabels`).
|
|
1546
|
+
* row reads (label, glyph, headline, suppression).
|
|
1572
1547
|
*/
|
|
1573
1548
|
...itemPresentationField,
|
|
1574
1549
|
parentToolCallId: z11.string().optional()
|
|
@@ -1905,6 +1880,10 @@ var unscopedSystemEventSchema = z11.discriminatedUnion("type", [
|
|
|
1905
1880
|
type: z11.literal("system/operation"),
|
|
1906
1881
|
threadId: z11.string()
|
|
1907
1882
|
}).merge(systemOperationEventDataSchema),
|
|
1883
|
+
z11.object({
|
|
1884
|
+
type: z11.literal("system/interaction/lifecycle"),
|
|
1885
|
+
threadId: z11.string()
|
|
1886
|
+
}).merge(systemInteractionLifecycleEventDataSchema),
|
|
1908
1887
|
z11.object({
|
|
1909
1888
|
type: z11.literal("system/permissionGrant/lifecycle"),
|
|
1910
1889
|
threadId: z11.string()
|
|
@@ -1949,20 +1928,33 @@ var rejectLegacyClientRequestSequenceSchema = z11.unknown().superRefine((value,
|
|
|
1949
1928
|
});
|
|
1950
1929
|
var threadEventSchema = rejectLegacyClientRequestSequenceSchema.pipe(
|
|
1951
1930
|
z11.union([providerEventSchema, systemEventSchema]).superRefine((event, ctx) => {
|
|
1952
|
-
const
|
|
1931
|
+
const result2 = validateThreadEventScope({
|
|
1953
1932
|
type: event.type,
|
|
1954
1933
|
scope: event.scope
|
|
1955
1934
|
});
|
|
1956
|
-
if (!
|
|
1935
|
+
if (!result2.valid) {
|
|
1957
1936
|
ctx.addIssue({
|
|
1958
1937
|
code: z11.ZodIssueCode.custom,
|
|
1959
|
-
message:
|
|
1938
|
+
message: result2.message ?? "Invalid thread event scope",
|
|
1960
1939
|
path: ["scope"]
|
|
1961
1940
|
});
|
|
1962
1941
|
return;
|
|
1963
1942
|
}
|
|
1964
1943
|
})
|
|
1965
1944
|
);
|
|
1945
|
+
function isThreadEventWithItem(event) {
|
|
1946
|
+
switch (event.type) {
|
|
1947
|
+
case "item/started":
|
|
1948
|
+
case "item/completed":
|
|
1949
|
+
case "item/delegation/progress":
|
|
1950
|
+
case "item/delegation/completed":
|
|
1951
|
+
case "item/backgroundTask/progress":
|
|
1952
|
+
case "item/backgroundTask/completed":
|
|
1953
|
+
return true;
|
|
1954
|
+
default:
|
|
1955
|
+
return false;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1966
1958
|
var threadEventTypeValues = [
|
|
1967
1959
|
...providerEventTypeValues,
|
|
1968
1960
|
...systemEventTypeValues
|
|
@@ -1973,6 +1965,23 @@ var threadEventTypeSchema = z11.string().refine(
|
|
|
1973
1965
|
"Invalid thread event type"
|
|
1974
1966
|
);
|
|
1975
1967
|
|
|
1968
|
+
// ../domain/src/plugin-icon.ts
|
|
1969
|
+
var PLUGIN_ICON_MAX_BYTES = 32 * 1024;
|
|
1970
|
+
var NAMESPACED_GLYPH_PATTERN = /^[a-z0-9-]+\/[a-z0-9][a-z0-9-]*$/u;
|
|
1971
|
+
function isNamespacedGlyph(glyph) {
|
|
1972
|
+
return NAMESPACED_GLYPH_PATTERN.test(glyph);
|
|
1973
|
+
}
|
|
1974
|
+
function parseNamespacedGlyph(glyph) {
|
|
1975
|
+
if (!isNamespacedGlyph(glyph)) {
|
|
1976
|
+
return null;
|
|
1977
|
+
}
|
|
1978
|
+
const separator = glyph.indexOf("/");
|
|
1979
|
+
return {
|
|
1980
|
+
pluginId: glyph.slice(0, separator),
|
|
1981
|
+
name: glyph.slice(separator + 1)
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1976
1985
|
// ../domain/src/provider-fork.ts
|
|
1977
1986
|
import { z as z12 } from "zod";
|
|
1978
1987
|
var PROVIDER_FORK_VALUES = ["none", "tip", "checkpoint"];
|
|
@@ -1996,6 +2005,7 @@ var availableModelSchema = z13.object({
|
|
|
1996
2005
|
defaultReasoningEffort: reasoningLevelSchema,
|
|
1997
2006
|
isDefault: z13.boolean()
|
|
1998
2007
|
});
|
|
2008
|
+
var providerModelCatalogScopeSchema = z13.enum(["host", "workspace"]);
|
|
1999
2009
|
var providerCapabilitiesSchema = z13.object({
|
|
2000
2010
|
supportsThreadArchive: z13.boolean(),
|
|
2001
2011
|
supportsThreadRename: z13.boolean(),
|
|
@@ -2008,7 +2018,15 @@ var providerCapabilitiesSchema = z13.object({
|
|
|
2008
2018
|
* whole sessions (tip-only) and cannot stop at a checkpoint.
|
|
2009
2019
|
*/
|
|
2010
2020
|
supportsSessionRewind: z13.boolean(),
|
|
2011
|
-
permissionModes: z13.array(permissionModeSchema).min(1)
|
|
2021
|
+
permissionModes: z13.array(permissionModeSchema).min(1),
|
|
2022
|
+
/**
|
|
2023
|
+
* How far one `model/list` answer travels: `"host"` when the bridge answers
|
|
2024
|
+
* from account or agent state and ignores the workspace path, so bb probes
|
|
2025
|
+
* once per machine; `"workspace"` when project configuration can change the
|
|
2026
|
+
* answer. Declared by the provider's plugin — core never infers it from an
|
|
2027
|
+
* id.
|
|
2028
|
+
*/
|
|
2029
|
+
modelCatalogScope: providerModelCatalogScopeSchema
|
|
2012
2030
|
});
|
|
2013
2031
|
var providerComposerCommandSchema = z13.object({
|
|
2014
2032
|
trigger: promptMentionCommandTriggerSchema,
|
|
@@ -2058,25 +2076,44 @@ var providerExtensionKindsSchema = z13.record(
|
|
|
2058
2076
|
);
|
|
2059
2077
|
var providerInfoSchema = z13.object({
|
|
2060
2078
|
id: z13.string(),
|
|
2079
|
+
/**
|
|
2080
|
+
* The plugin that registered the provider (`bb.providers.register`). The
|
|
2081
|
+
* owner of the provider's extension-kind namespace, and the bundle the
|
|
2082
|
+
* app loads on the first thread of this provider.
|
|
2083
|
+
*/
|
|
2084
|
+
pluginId: z13.string().min(1),
|
|
2061
2085
|
displayName: z13.string(),
|
|
2062
2086
|
/**
|
|
2063
2087
|
* Declared grouping key shared by related providers (the ACP agents).
|
|
2064
2088
|
* Absent when the provider declared none. Grouping only.
|
|
2065
2089
|
*/
|
|
2066
2090
|
family: z13.string().min(1).optional(),
|
|
2091
|
+
/**
|
|
2092
|
+
* The declared icon, projected by form. A plugin-relative asset path
|
|
2093
|
+
* (`icon: "./icons/agent.svg"`) is served by the provider-logo route and
|
|
2094
|
+
* arrives here as `logoUrl`; a named host glyph (`icon: "Zap"`) has no
|
|
2095
|
+
* bytes to serve and arrives as `icon.glyph`, the same vocabulary an
|
|
2096
|
+
* item presentation's `icon` uses. A declaration names at most one form,
|
|
2097
|
+
* so at most one of the two is set; `icon` is absent when the declaration
|
|
2098
|
+
* named a path or nothing. Clients draw a vendored brand mark first, then
|
|
2099
|
+
* `logoUrl`, then `icon.glyph`, then the display name's initial.
|
|
2100
|
+
*/
|
|
2101
|
+
icon: z13.object({ glyph: z13.string().min(1) }).optional(),
|
|
2067
2102
|
logoUrl: z13.string().min(1).nullable(),
|
|
2068
|
-
/** Sessionless maintenance
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2103
|
+
/** Sessionless maintenance requests the provider's bridge implements. */
|
|
2104
|
+
maintenance: z13.object({
|
|
2105
|
+
health: z13.boolean(),
|
|
2106
|
+
usage: z13.boolean(),
|
|
2107
|
+
installation: z13.boolean()
|
|
2108
|
+
}),
|
|
2072
2109
|
capabilities: providerCapabilitiesSchema,
|
|
2073
2110
|
composerActions: z13.array(providerComposerActionSchema),
|
|
2074
2111
|
available: z13.boolean(),
|
|
2075
2112
|
// -------------------------------------------------------------------------
|
|
2076
2113
|
// Target-state projection (docs/provider-plugin-api.md §1). Optional and
|
|
2077
2114
|
// unfilled until WS2a projects them from the plugin declaration; absence
|
|
2078
|
-
// means "the provider declared none", never a default. The
|
|
2079
|
-
//
|
|
2115
|
+
// means "the provider declared none", never a default. The
|
|
2116
|
+
// `capabilities.supports*` fields above stay until WS2a stabilizes the
|
|
2080
2117
|
// surface as one unit.
|
|
2081
2118
|
// -------------------------------------------------------------------------
|
|
2082
2119
|
strings: providerStringsSchema.optional(),
|
|
@@ -2122,954 +2159,358 @@ var dynamicToolSchema = z13.object({
|
|
|
2122
2159
|
presentation: threadEventItemPresentationSchema.optional()
|
|
2123
2160
|
});
|
|
2124
2161
|
|
|
2125
|
-
// ../provider-bridge-protocol/src/
|
|
2126
|
-
import { z as z23 } from "zod";
|
|
2127
|
-
|
|
2128
|
-
// ../provider-bridge-protocol/src/version.ts
|
|
2129
|
-
var PROVIDER_BRIDGE_PROTOCOL_VERSION = 2;
|
|
2130
|
-
var THREAD_DELTA_GRAMMAR_V3 = 3;
|
|
2131
|
-
|
|
2132
|
-
// ../provider-bridge-protocol/src/handshake.ts
|
|
2162
|
+
// ../provider-bridge-protocol/src/thread-delta.ts
|
|
2133
2163
|
import { z as z14 } from "zod";
|
|
2134
|
-
var
|
|
2135
|
-
|
|
2164
|
+
var THREAD_DELTA_NOTIFICATION_METHOD = "thread/delta";
|
|
2165
|
+
var deltaPresentationSchema = threadEventItemPresentationSchema;
|
|
2166
|
+
var THREAD_DELTA_KEY_SEPARATOR = "";
|
|
2167
|
+
var deltaKeyPartSchema = z14.string().min(1).refine((value) => !value.includes(THREAD_DELTA_KEY_SEPARATOR), {
|
|
2168
|
+
message: "provider keys must not contain the internal key separator (\\u001f)"
|
|
2136
2169
|
});
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
}
|
|
2142
|
-
var
|
|
2143
|
-
var
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2170
|
+
var deltaItemKeySchema = z14.object({
|
|
2171
|
+
providerItemId: deltaKeyPartSchema.optional(),
|
|
2172
|
+
channel: deltaKeyPartSchema.optional(),
|
|
2173
|
+
parentRef: deltaKeyPartSchema.optional()
|
|
2174
|
+
});
|
|
2175
|
+
var providerTurnIdSchema = deltaKeyPartSchema;
|
|
2176
|
+
var deltaFileChangeSchema = z14.object({
|
|
2177
|
+
path: z14.string(),
|
|
2178
|
+
/** The bridge states the change kind; the assembler never derives it. */
|
|
2179
|
+
kind: z14.enum(["add", "update", "delete"]),
|
|
2180
|
+
movePath: z14.string().optional(),
|
|
2181
|
+
/** Provider-supplied unified diff; preferred over old/new text building. */
|
|
2182
|
+
diff: z14.string().optional(),
|
|
2183
|
+
oldText: z14.string().optional(),
|
|
2184
|
+
/** When present the assembler builds the unified diff from old/new text. */
|
|
2185
|
+
newText: z14.string().optional()
|
|
2186
|
+
});
|
|
2187
|
+
var deltaBackgroundTaskShapeSchema = z14.object({
|
|
2188
|
+
type: z14.literal("backgroundTask"),
|
|
2152
2189
|
/**
|
|
2153
|
-
* The
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2190
|
+
* The provider's stable task id, shared by every generation (restart) of
|
|
2191
|
+
* the same task. Rides through to the canonical item so consumers can
|
|
2192
|
+
* correlate a restarted task with its earlier generations — the assembler
|
|
2193
|
+
* mints fresh item ids per generation, so identity must travel as data,
|
|
2194
|
+
* never as id text.
|
|
2156
2195
|
*/
|
|
2157
|
-
|
|
2196
|
+
familyId: z14.string().min(1),
|
|
2197
|
+
taskType: z14.string(),
|
|
2198
|
+
description: z14.string(),
|
|
2199
|
+
status: threadEventItemStatusSchema,
|
|
2200
|
+
taskStatus: backgroundTaskStatusSchema,
|
|
2201
|
+
skipTranscript: z14.boolean(),
|
|
2202
|
+
workflowName: z14.string().optional(),
|
|
2203
|
+
workflow: workflowProgressSnapshotSchema.optional(),
|
|
2204
|
+
usage: backgroundTaskUsageSchema.optional(),
|
|
2205
|
+
summary: z14.string().optional(),
|
|
2206
|
+
error: z14.string().optional(),
|
|
2207
|
+
outputFile: z14.string().optional()
|
|
2208
|
+
});
|
|
2209
|
+
var deltaFileReadShapeSchema = z14.object({
|
|
2210
|
+
type: z14.literal("fileRead"),
|
|
2211
|
+
path: z14.string(),
|
|
2212
|
+
cmd: z14.string().optional()
|
|
2213
|
+
});
|
|
2214
|
+
var deltaSearchShapeSchema = z14.object({
|
|
2215
|
+
type: z14.literal("search"),
|
|
2216
|
+
mode: threadEventSearchModeSchema,
|
|
2217
|
+
query: z14.string(),
|
|
2218
|
+
path: z14.string().optional(),
|
|
2219
|
+
cmd: z14.string().optional()
|
|
2220
|
+
});
|
|
2221
|
+
var deltaDelegationShapeSchema = z14.object({
|
|
2222
|
+
type: z14.literal("delegation"),
|
|
2223
|
+
childRef: deltaKeyPartSchema,
|
|
2224
|
+
label: z14.string(),
|
|
2225
|
+
background: z14.boolean(),
|
|
2226
|
+
summary: z14.string().optional()
|
|
2227
|
+
});
|
|
2228
|
+
var deltaPlanStepsShapeSchema = z14.object({
|
|
2229
|
+
type: z14.literal("planSteps"),
|
|
2230
|
+
steps: z14.array(threadEventPlanStepSchema),
|
|
2231
|
+
explanation: z14.string().optional()
|
|
2232
|
+
});
|
|
2233
|
+
var deltaExtensionShapeSchema = z14.object({
|
|
2234
|
+
type: z14.literal("extension"),
|
|
2235
|
+
kind: extensionKindSchema,
|
|
2236
|
+
payload: jsonValueSchema
|
|
2237
|
+
});
|
|
2238
|
+
var deltaItemShapeSchema = z14.discriminatedUnion("type", [
|
|
2239
|
+
z14.object({
|
|
2240
|
+
type: z14.literal("command"),
|
|
2241
|
+
command: z14.string(),
|
|
2242
|
+
cwd: z14.string(),
|
|
2243
|
+
aggregatedOutput: z14.string().optional(),
|
|
2244
|
+
exitCode: z14.number().optional(),
|
|
2245
|
+
durationMs: z14.number().optional()
|
|
2246
|
+
}),
|
|
2247
|
+
z14.object({
|
|
2248
|
+
type: z14.literal("fileChange"),
|
|
2249
|
+
/** Empty while a path is not yet known, including bare close fallbacks. */
|
|
2250
|
+
changes: z14.array(deltaFileChangeSchema)
|
|
2251
|
+
}),
|
|
2158
2252
|
/**
|
|
2159
|
-
* The
|
|
2160
|
-
*
|
|
2253
|
+
* The generic tool call: the escape hatch for tools with no core kind. In
|
|
2254
|
+
* grammar v3 the bridge says how the row reads through the delta's
|
|
2255
|
+
* `presentation` (label, icon, suppression) instead of core keeping a
|
|
2256
|
+
* tool-name table. A `tool` item without presentation is read as legacy
|
|
2257
|
+
* data: `@bb/domain`'s `upgradeLegacyToolItem` reshapes read/grep/glob/
|
|
2258
|
+
* find/ls by name and suppresses the Task- and Todo-family bookkeeping
|
|
2259
|
+
* calls when the stored row is parsed, and any other name renders with
|
|
2260
|
+
* the generic tool row — until the backfill migration, after which
|
|
2261
|
+
* `presentation` is required.
|
|
2161
2262
|
*/
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2263
|
+
z14.object({
|
|
2264
|
+
type: z14.literal("tool"),
|
|
2265
|
+
tool: z14.string(),
|
|
2266
|
+
server: z14.string().optional(),
|
|
2267
|
+
args: z14.unknown().optional(),
|
|
2268
|
+
result: z14.unknown().optional(),
|
|
2269
|
+
error: z14.string().optional(),
|
|
2270
|
+
durationMs: z14.number().optional()
|
|
2271
|
+
}),
|
|
2272
|
+
z14.object({ type: z14.literal("compaction") }),
|
|
2273
|
+
z14.object({ type: z14.literal("agentMessage"), text: z14.string() }),
|
|
2274
|
+
z14.object({
|
|
2275
|
+
type: z14.literal("reasoning"),
|
|
2276
|
+
summary: z14.array(z14.string()),
|
|
2277
|
+
content: z14.array(z14.string())
|
|
2278
|
+
}),
|
|
2279
|
+
z14.object({ type: z14.literal("plan"), text: z14.string() }),
|
|
2280
|
+
z14.object({
|
|
2281
|
+
type: z14.literal("webSearch"),
|
|
2282
|
+
queries: z14.array(z14.string()).min(1)
|
|
2283
|
+
}),
|
|
2284
|
+
z14.object({
|
|
2285
|
+
type: z14.literal("webFetch"),
|
|
2286
|
+
url: z14.string(),
|
|
2287
|
+
prompt: z14.string().nullable().optional(),
|
|
2288
|
+
pattern: z14.string().nullable()
|
|
2289
|
+
}),
|
|
2290
|
+
z14.object({ type: z14.literal("imageView"), path: z14.string() }),
|
|
2291
|
+
deltaBackgroundTaskShapeSchema,
|
|
2292
|
+
// Grammar v3 shapes. Every existing shape above is kept unchanged.
|
|
2293
|
+
deltaFileReadShapeSchema,
|
|
2294
|
+
deltaSearchShapeSchema,
|
|
2295
|
+
deltaDelegationShapeSchema,
|
|
2296
|
+
deltaPlanStepsShapeSchema,
|
|
2297
|
+
deltaExtensionShapeSchema
|
|
2298
|
+
]);
|
|
2299
|
+
var deltaProgressSnapshotSchema = z14.discriminatedUnion("type", [
|
|
2300
|
+
deltaBackgroundTaskShapeSchema,
|
|
2301
|
+
deltaDelegationShapeSchema
|
|
2302
|
+
]);
|
|
2303
|
+
var deltaTextChannelSchema = z14.enum([
|
|
2304
|
+
"agentMessage",
|
|
2305
|
+
"reasoningSummary",
|
|
2306
|
+
"reasoningText",
|
|
2307
|
+
"plan"
|
|
2308
|
+
]);
|
|
2309
|
+
var deltaOutputChannelSchema = z14.enum(["command", "fileChange"]);
|
|
2310
|
+
var deltaErrorSchema = z14.object({ message: z14.string() });
|
|
2311
|
+
var deltaAttachSchema = z14.enum(["open", "currentOrLast"]);
|
|
2312
|
+
var deltaNoTurnFallbackSchema = z14.object({
|
|
2313
|
+
raw: providerRawEventSchema,
|
|
2314
|
+
rawType: z14.string()
|
|
2315
|
+
});
|
|
2316
|
+
function requireExtensionPresentation(delta, ctx) {
|
|
2317
|
+
if (delta.item.type === "extension" && delta.presentation === void 0) {
|
|
2318
|
+
ctx.addIssue({
|
|
2319
|
+
code: "custom",
|
|
2320
|
+
message: "extension items require a presentation on item.open/item.close",
|
|
2321
|
+
path: ["presentation"]
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
var threadDeltaSchema = z14.discriminatedUnion("kind", [
|
|
2165
2326
|
/**
|
|
2166
|
-
*
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
2169
|
-
*
|
|
2327
|
+
* The provider consumed an input (immediate or steered). The assembler owns
|
|
2328
|
+
* the queue-until-turn-opens behavior and the terminal-turn invariant.
|
|
2329
|
+
* With `providerTurnId` the acceptance is emitted against that vouched turn
|
|
2330
|
+
* directly (codex correlates acceptance to a named native turn).
|
|
2170
2331
|
*/
|
|
2171
|
-
|
|
2332
|
+
z14.object({
|
|
2333
|
+
kind: z14.literal("input.accepted"),
|
|
2334
|
+
clientRequestId: clientTurnRequestIdSchema,
|
|
2335
|
+
providerTurnId: providerTurnIdSchema.optional()
|
|
2336
|
+
}),
|
|
2172
2337
|
/**
|
|
2173
|
-
*
|
|
2174
|
-
*
|
|
2175
|
-
*
|
|
2176
|
-
*
|
|
2177
|
-
*
|
|
2178
|
-
*
|
|
2338
|
+
* Input the provider itself injected into the conversation, with no bb
|
|
2339
|
+
* client request behind it (a pi extension's `sendMessage` custom message
|
|
2340
|
+
* that triggered or steered a turn). The assembler records it as a
|
|
2341
|
+
* `userMessage` item in the open turn so the transcript shows what the
|
|
2342
|
+
* model was answering. Dropped silently when no turn is open: the provider
|
|
2343
|
+
* appended it to its own context without running the agent, so there is no
|
|
2344
|
+
* bb turn to attach it to.
|
|
2179
2345
|
*/
|
|
2180
|
-
|
|
2346
|
+
z14.object({
|
|
2347
|
+
kind: z14.literal("input.provider"),
|
|
2348
|
+
text: z14.string().min(1),
|
|
2349
|
+
parentRef: deltaKeyPartSchema.optional()
|
|
2350
|
+
}),
|
|
2181
2351
|
/**
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
*
|
|
2186
|
-
*
|
|
2187
|
-
* the intersection with the runtime's `initialize` params range
|
|
2188
|
-
* ({@link negotiateGrammarVersion}); the runtime rejects a disjoint
|
|
2189
|
-
* range at startup.
|
|
2352
|
+
* An explicit provider signal opened work (pi `agent_start`, codex
|
|
2353
|
+
* `turn/started`). With `providerTurnId` the turn lives in the keyed
|
|
2354
|
+
* provider-turn space: several may be open at once (codex multiplexes
|
|
2355
|
+
* subagent child turns onto one thread) and none of the current-turn
|
|
2356
|
+
* machinery is touched.
|
|
2190
2357
|
*/
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2358
|
+
z14.object({
|
|
2359
|
+
kind: z14.literal("turn.open"),
|
|
2360
|
+
providerTurnId: providerTurnIdSchema.optional(),
|
|
2361
|
+
/** Provider-native parent tool-call id for delegated child turns. */
|
|
2362
|
+
parentRef: deltaKeyPartSchema.optional()
|
|
2363
|
+
}),
|
|
2195
2364
|
/**
|
|
2196
|
-
*
|
|
2197
|
-
*
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
2200
|
-
*
|
|
2201
|
-
* changes no behavior today; claude and codex declare `inject` before WS4
|
|
2202
|
-
* reads it.
|
|
2365
|
+
* The bridge's conclusion that the turn settled. `claimIfIdle: true` marks
|
|
2366
|
+
* fallback closers that own a turn only if accepted input is pending
|
|
2367
|
+
* (the old bridge-kit terminal-turn rule, applied centrally); an open turn is
|
|
2368
|
+
* always settled. A keyed boundary (`providerTurnId`) always emits — the
|
|
2369
|
+
* provider named the turn — and settles only that turn.
|
|
2203
2370
|
*/
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2371
|
+
z14.object({
|
|
2372
|
+
kind: z14.literal("turn.boundary"),
|
|
2373
|
+
status: threadEventTurnStatusSchema,
|
|
2374
|
+
error: deltaErrorSchema.optional(),
|
|
2375
|
+
providerCheckpointId: z14.string().min(1).optional(),
|
|
2376
|
+
claimIfIdle: z14.boolean().optional(),
|
|
2377
|
+
providerTurnId: providerTurnIdSchema.optional()
|
|
2378
|
+
}),
|
|
2209
2379
|
/**
|
|
2210
|
-
*
|
|
2211
|
-
*
|
|
2212
|
-
*
|
|
2380
|
+
* A parsed item opened. `attach: "currentOrLast"` pins the item to the turn
|
|
2381
|
+
* that is open or just closed without opening a new one (pi threshold
|
|
2382
|
+
* compaction); the default attaches to the open turn only. A known
|
|
2383
|
+
* `providerItemId` reuses its minted bb id (an explicit open reopens the
|
|
2384
|
+
* same item, codex's settle/reopen rule).
|
|
2213
2385
|
*/
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
(
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
}).
|
|
2228
|
-
|
|
2229
|
-
// ../provider-bridge-protocol/src/execution-options.ts
|
|
2230
|
-
import { z as z15 } from "zod";
|
|
2231
|
-
var bridgeExecutionOptionsSchema = z15.object({
|
|
2232
|
-
model: z15.string().min(1).optional(),
|
|
2233
|
-
serviceTier: serviceTierSchema.optional(),
|
|
2234
|
-
reasoningLevel: reasoningLevelSchema.optional(),
|
|
2386
|
+
z14.object({
|
|
2387
|
+
kind: z14.literal("item.open"),
|
|
2388
|
+
key: deltaItemKeySchema,
|
|
2389
|
+
item: deltaItemShapeSchema,
|
|
2390
|
+
/**
|
|
2391
|
+
* Grammar v3: how the row reads, persisted with the opened item. The
|
|
2392
|
+
* one place presentation travels. Optional for core shapes while v2
|
|
2393
|
+
* deltas are accepted; REQUIRED for `extension` shapes.
|
|
2394
|
+
*/
|
|
2395
|
+
presentation: deltaPresentationSchema.optional(),
|
|
2396
|
+
attach: deltaAttachSchema.optional(),
|
|
2397
|
+
providerTurnId: providerTurnIdSchema.optional(),
|
|
2398
|
+
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2399
|
+
}).superRefine(requireExtensionPresentation),
|
|
2235
2400
|
/**
|
|
2236
|
-
*
|
|
2237
|
-
*
|
|
2238
|
-
*
|
|
2401
|
+
* The item settled. `item` is REQUIRED and always carries the full terminal
|
|
2402
|
+
* item shape (Michael's uniform close rule, 2026-08-18): the assembler
|
|
2403
|
+
* builds the completed item from it. With a same-shaped item open under the
|
|
2404
|
+
* key, the terminal shape wins and the opened item contributes only its
|
|
2405
|
+
* minted id; with a different-shaped item open, the assembler closes the
|
|
2406
|
+
* opened shape and then emits the terminal shape (ACP's dual-complete);
|
|
2407
|
+
* with nothing open it builds the bare completed item.
|
|
2408
|
+
*
|
|
2409
|
+
* Provider-identified closes (`key.providerItemId`) dedup: a repeated close
|
|
2410
|
+
* for a settled id is dropped and an explicit `item.open` reopens the id
|
|
2411
|
+
* (codex retries the terminal notification after approvals).
|
|
2239
2412
|
*/
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
});
|
|
2261
|
-
var experimental_providerHealthSchema = z16.object({
|
|
2262
|
-
status: z16.enum([
|
|
2263
|
-
"ready",
|
|
2264
|
-
"not_installed",
|
|
2265
|
-
"unauthenticated",
|
|
2266
|
-
"expired",
|
|
2267
|
-
"unsupported_version",
|
|
2268
|
-
"unknown"
|
|
2269
|
-
]),
|
|
2270
|
-
statusMessage: z16.string().min(1).nullable(),
|
|
2271
|
-
accountEmail: z16.string().nullable(),
|
|
2272
|
-
planLabel: z16.string().min(1).nullable(),
|
|
2273
|
-
installedVersion: z16.string().min(1).nullable(),
|
|
2274
|
-
minimumSupportedVersion: z16.string().min(1).nullable(),
|
|
2275
|
-
canInstall: z16.boolean(),
|
|
2276
|
-
canUpdate: z16.boolean(),
|
|
2277
|
-
loginCommand: z16.string().min(1).nullable()
|
|
2278
|
-
}).passthrough();
|
|
2279
|
-
var experimental_providerUsageWindowSchema = z16.object({
|
|
2280
|
-
label: z16.string().min(1),
|
|
2281
|
-
usedPercent: z16.number().min(0).max(100),
|
|
2282
|
-
resetsAt: z16.string().min(1).nullable(),
|
|
2283
|
-
cost: z16.object({
|
|
2284
|
-
usedUsdCents: z16.number().int().nonnegative(),
|
|
2285
|
-
limitUsdCents: z16.number().int().positive()
|
|
2286
|
-
}).optional()
|
|
2287
|
-
}).passthrough();
|
|
2288
|
-
var experimental_providerUsageSchema = z16.discriminatedUnion("status", [
|
|
2289
|
-
z16.object({
|
|
2290
|
-
status: z16.literal("ok"),
|
|
2291
|
-
accountEmail: z16.string().email().nullable(),
|
|
2292
|
-
planLabel: z16.string().min(1).nullable(),
|
|
2293
|
-
windows: z16.array(experimental_providerUsageWindowSchema)
|
|
2294
|
-
}).passthrough(),
|
|
2295
|
-
z16.object({ status: z16.literal("not_installed") }).passthrough(),
|
|
2296
|
-
z16.object({ status: z16.literal("unauthenticated") }).passthrough(),
|
|
2297
|
-
z16.object({ status: z16.literal("expired") }).passthrough(),
|
|
2298
|
-
z16.object({
|
|
2299
|
-
status: z16.literal("error"),
|
|
2300
|
-
message: z16.string().min(1),
|
|
2301
|
-
planLabel: z16.string().min(1).nullable().default(null),
|
|
2302
|
-
accountEmail: z16.string().nullable().default(null)
|
|
2303
|
-
}).passthrough()
|
|
2304
|
-
]);
|
|
2305
|
-
var experimental_providerHealthResultSchema = z16.discriminatedUnion(
|
|
2306
|
-
"supported",
|
|
2307
|
-
[
|
|
2308
|
-
z16.object({ supported: z16.literal(false) }).passthrough(),
|
|
2309
|
-
z16.object({
|
|
2310
|
-
supported: z16.literal(true),
|
|
2311
|
-
health: experimental_providerHealthSchema
|
|
2312
|
-
}).passthrough()
|
|
2313
|
-
]
|
|
2314
|
-
);
|
|
2315
|
-
var experimental_providerUsageResultSchema = z16.discriminatedUnion(
|
|
2316
|
-
"supported",
|
|
2317
|
-
[
|
|
2318
|
-
z16.object({ supported: z16.literal(false) }).passthrough(),
|
|
2319
|
-
z16.object({
|
|
2320
|
-
supported: z16.literal(true),
|
|
2321
|
-
usage: experimental_providerUsageSchema
|
|
2322
|
-
}).passthrough()
|
|
2323
|
-
]
|
|
2324
|
-
);
|
|
2325
|
-
var experimental_providerInstallationActionKindSchema = z16.enum([
|
|
2326
|
-
"install",
|
|
2327
|
-
"update"
|
|
2328
|
-
]);
|
|
2329
|
-
var experimental_providerInstallationActionSchema = z16.object({
|
|
2330
|
-
kind: experimental_providerInstallationActionKindSchema,
|
|
2331
|
-
label: z16.enum(["Install", "Update"]),
|
|
2332
|
-
command: z16.string().min(1)
|
|
2333
|
-
}).passthrough();
|
|
2334
|
-
var experimental_providerInstallationSourceSchema = z16.enum([
|
|
2335
|
-
"notInstalled",
|
|
2336
|
-
"npmGlobal",
|
|
2337
|
-
"external"
|
|
2338
|
-
]);
|
|
2339
|
-
var experimental_providerInstallationStatusSchema = z16.object({
|
|
2340
|
-
executableName: z16.string().min(1),
|
|
2341
|
-
executablePath: z16.string().min(1).nullable(),
|
|
2342
|
-
installed: z16.boolean(),
|
|
2343
|
-
installSource: experimental_providerInstallationSourceSchema,
|
|
2344
|
-
currentVersion: z16.string().min(1).nullable(),
|
|
2345
|
-
latestVersion: z16.string().min(1).nullable(),
|
|
2346
|
-
minimumSupportedVersion: z16.string().min(1).nullable(),
|
|
2347
|
-
npmPackageName: z16.string().min(1).nullable(),
|
|
2348
|
-
npmGlobalPackageVersion: z16.string().min(1).nullable(),
|
|
2349
|
-
installAction: experimental_providerInstallationActionSchema.nullable(),
|
|
2350
|
-
needsUpdate: z16.boolean(),
|
|
2351
|
-
versionUnsupported: z16.boolean()
|
|
2352
|
-
}).passthrough();
|
|
2353
|
-
var experimental_providerInstallationRunParamsSchema = experimental_providerMaintenanceParamsSchema.extend({
|
|
2354
|
-
action: experimental_providerInstallationActionKindSchema
|
|
2355
|
-
});
|
|
2356
|
-
var experimental_providerInstallationCommandSchema = z16.object({
|
|
2357
|
-
command: z16.string().min(1),
|
|
2358
|
-
args: z16.array(z16.string()).max(64),
|
|
2359
|
-
displayCommand: z16.string().min(1)
|
|
2360
|
-
}).passthrough();
|
|
2361
|
-
var experimental_providerInstallationVerificationSchema = z16.discriminatedUnion("kind", [
|
|
2362
|
-
z16.object({ kind: z16.literal("installed") }).passthrough(),
|
|
2363
|
-
z16.object({
|
|
2364
|
-
kind: z16.literal("version_changed"),
|
|
2365
|
-
previousVersion: z16.string().min(1)
|
|
2366
|
-
}).passthrough(),
|
|
2367
|
-
z16.object({
|
|
2368
|
-
kind: z16.literal("version_at_least"),
|
|
2369
|
-
version: z16.string().min(1)
|
|
2370
|
-
}).passthrough()
|
|
2371
|
-
]);
|
|
2372
|
-
var experimental_providerInstallationRunResultSchema = z16.discriminatedUnion("available", [
|
|
2373
|
-
z16.object({
|
|
2374
|
-
available: z16.literal(false),
|
|
2375
|
-
message: z16.string().min(1)
|
|
2376
|
-
}).passthrough(),
|
|
2377
|
-
z16.object({
|
|
2378
|
-
available: z16.literal(true),
|
|
2379
|
-
command: experimental_providerInstallationCommandSchema,
|
|
2380
|
-
verification: experimental_providerInstallationVerificationSchema
|
|
2381
|
-
}).passthrough()
|
|
2382
|
-
]);
|
|
2383
|
-
|
|
2384
|
-
// ../provider-bridge-protocol/src/requests.ts
|
|
2385
|
-
import { z as z17 } from "zod";
|
|
2386
|
-
var BRIDGE_REQUEST_METHODS = {
|
|
2387
|
-
initialize: "initialize",
|
|
2388
|
-
modelList: "model/list",
|
|
2389
|
-
experimentalProviderHealth: "provider/health",
|
|
2390
|
-
experimentalProviderUsage: "provider/usage",
|
|
2391
|
-
experimentalProviderInstallationStatus: "provider/installation/status",
|
|
2392
|
-
experimentalProviderInstallationRun: "provider/installation/run",
|
|
2393
|
-
threadStart: "thread/start",
|
|
2394
|
-
threadResume: "thread/resume",
|
|
2395
|
-
threadFork: "thread/fork",
|
|
2396
|
-
threadStop: "thread/stop",
|
|
2397
|
-
threadDiscard: "thread/discard",
|
|
2398
|
-
threadNameSet: "thread/name/set",
|
|
2399
|
-
threadArchive: "thread/archive",
|
|
2400
|
-
threadUnarchive: "thread/unarchive",
|
|
2401
|
-
threadGoalClear: "thread/goal/clear",
|
|
2402
|
-
turnStart: "turn/start",
|
|
2403
|
-
turnSteer: "turn/steer",
|
|
2404
|
-
skillsConfigure: "skills/configure"
|
|
2405
|
-
};
|
|
2406
|
-
var sessionConstructionFields = {
|
|
2407
|
-
threadId: z17.string().min(1),
|
|
2408
|
-
cwd: z17.string().min(1),
|
|
2409
|
-
options: bridgeExecutionOptionsSchema,
|
|
2410
|
-
dynamicTools: z17.array(dynamicToolSchema).optional(),
|
|
2411
|
-
disallowedTools: z17.array(z17.string().min(1)).optional(),
|
|
2412
|
-
instructionMode: instructionModeSchema
|
|
2413
|
-
};
|
|
2414
|
-
var modelListParamsSchema = z17.object({ cwd: z17.string().min(1).optional() }).passthrough();
|
|
2415
|
-
var threadStartParamsSchema = z17.object({
|
|
2416
|
-
...sessionConstructionFields,
|
|
2417
|
-
input: z17.array(promptInputSchema).optional()
|
|
2418
|
-
}).passthrough();
|
|
2419
|
-
var threadResumeParamsSchema = z17.object({
|
|
2420
|
-
...sessionConstructionFields,
|
|
2421
|
-
providerThreadId: z17.string().min(1)
|
|
2422
|
-
}).passthrough();
|
|
2423
|
-
var threadForkParamsSchema = z17.object({
|
|
2424
|
-
...sessionConstructionFields,
|
|
2425
|
-
sourceProviderThreadId: z17.string().min(1),
|
|
2413
|
+
z14.object({
|
|
2414
|
+
kind: z14.literal("item.close"),
|
|
2415
|
+
key: deltaItemKeySchema,
|
|
2416
|
+
status: threadEventItemStatusSchema,
|
|
2417
|
+
resultText: z14.string().optional(),
|
|
2418
|
+
exitCode: z14.number().optional(),
|
|
2419
|
+
aggregatedOutput: z14.string().optional(),
|
|
2420
|
+
/** Terminal approval verdict (codex declined → denied). Default null. */
|
|
2421
|
+
approvalStatus: z14.literal("denied").optional(),
|
|
2422
|
+
item: deltaItemShapeSchema,
|
|
2423
|
+
/**
|
|
2424
|
+
* Grammar v3: the terminal presentation. Like `item`, the close carries
|
|
2425
|
+
* the full terminal form; when absent the opened item's presentation
|
|
2426
|
+
* survives onto the completed item (close-echo). REQUIRED for an
|
|
2427
|
+
* `extension` shape, which has nothing to echo without it.
|
|
2428
|
+
*/
|
|
2429
|
+
presentation: deltaPresentationSchema.optional(),
|
|
2430
|
+
providerTurnId: providerTurnIdSchema.optional(),
|
|
2431
|
+
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2432
|
+
}).superRefine(requireExtensionPresentation),
|
|
2426
2433
|
/**
|
|
2427
|
-
*
|
|
2428
|
-
* `
|
|
2429
|
-
*
|
|
2434
|
+
* Free-form progress on an open item (non-command tool updates), or — with
|
|
2435
|
+
* `snapshot` — a re-embedded snapshot of work that outlives its turn: a
|
|
2436
|
+
* background task (`item/backgroundTask/progress`) or, in grammar v3, a
|
|
2437
|
+
* background delegation (`item/delegation/progress`); both thread-scoped,
|
|
2438
|
+
* no turn required.
|
|
2439
|
+
*
|
|
2440
|
+
* Progress is throttled centrally by the assembler (one emission per item
|
|
2441
|
+
* key per policy interval, 500ms default; the newest suppressed snapshot is
|
|
2442
|
+
* flushed trailing-edge on the thread's next traffic once the window
|
|
2443
|
+
* elapses, and an `item.close` supersedes it). `flush: true` bypasses the
|
|
2444
|
+
* throttle and resets the window — status transitions must land immediately.
|
|
2430
2445
|
*/
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2446
|
+
z14.object({
|
|
2447
|
+
kind: z14.literal("item.progress"),
|
|
2448
|
+
key: deltaItemKeySchema,
|
|
2449
|
+
message: z14.string().optional(),
|
|
2450
|
+
snapshot: deltaProgressSnapshotSchema.optional(),
|
|
2451
|
+
flush: z14.boolean().optional(),
|
|
2452
|
+
providerTurnId: providerTurnIdSchema.optional(),
|
|
2453
|
+
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2454
|
+
}),
|
|
2436
2455
|
/**
|
|
2437
|
-
*
|
|
2438
|
-
*
|
|
2439
|
-
*
|
|
2440
|
-
*
|
|
2456
|
+
* Streamed text — the one streaming dialect. Every text stream is keyed
|
|
2457
|
+
* like every other item: by the provider's own item id when the provider
|
|
2458
|
+
* names its message items (codex), or by a bridge-chosen `key.channel`
|
|
2459
|
+
* (`"assistant"`, `"thinking-2"`) plus `key.parentRef` for providers whose
|
|
2460
|
+
* streams are anonymous (claude, pi, acp). The first delta for an unknown
|
|
2461
|
+
* key synthesizes the channel's `item/started`; later deltas (and deltas
|
|
2462
|
+
* for a provider id already opened or settled) reuse the mapped id. The
|
|
2463
|
+
* assembler accumulates the stream text per open item so `item.textClose`
|
|
2464
|
+
* can settle without a provider-final text.
|
|
2441
2465
|
*/
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
})
|
|
2450
|
-
var threadNameSetParamsSchema = z17.object({
|
|
2451
|
-
threadId: z17.string().min(1),
|
|
2452
|
-
providerThreadId: z17.string().min(1),
|
|
2453
|
-
title: z17.string().min(1)
|
|
2454
|
-
}).passthrough();
|
|
2455
|
-
var turnInputFields = {
|
|
2456
|
-
threadId: z17.string().min(1),
|
|
2457
|
-
providerThreadId: z17.string().min(1),
|
|
2458
|
-
input: z17.array(promptInputSchema),
|
|
2459
|
-
clientRequestId: clientTurnRequestIdSchema,
|
|
2460
|
-
options: bridgeExecutionOptionsSchema
|
|
2461
|
-
};
|
|
2462
|
-
var turnStartParamsSchema = z17.object(turnInputFields).passthrough();
|
|
2463
|
-
var turnSteerParamsSchema = z17.object({
|
|
2464
|
-
...turnInputFields,
|
|
2465
|
-
expectedTurnId: z17.string().min(1)
|
|
2466
|
-
}).passthrough();
|
|
2467
|
-
var skillsConfigureRootSchema = z17.object({
|
|
2468
|
-
id: z17.string().min(1),
|
|
2469
|
-
path: z17.string().min(1),
|
|
2470
|
-
skills: z17.array(
|
|
2471
|
-
z17.object({
|
|
2472
|
-
name: z17.string().min(1),
|
|
2473
|
-
description: z17.string()
|
|
2474
|
-
}).passthrough()
|
|
2475
|
-
)
|
|
2476
|
-
}).passthrough();
|
|
2477
|
-
var skillsConfigureParamsSchema = z17.object({
|
|
2478
|
-
roots: z17.array(skillsConfigureRootSchema)
|
|
2479
|
-
}).passthrough();
|
|
2480
|
-
var threadIdentityResultSchema = z17.object({
|
|
2481
|
-
providerThreadId: z17.string().min(1),
|
|
2482
|
-
/** Refines the handshake's `sessionRestore` for this session. */
|
|
2483
|
-
sessionRestorable: z17.boolean().optional()
|
|
2484
|
-
}).passthrough();
|
|
2485
|
-
var modelListResultSchema = z17.object({
|
|
2486
|
-
models: z17.array(availableModelSchema),
|
|
2487
|
-
selectedOnlyModels: z17.array(availableModelSchema).default([])
|
|
2488
|
-
}).passthrough();
|
|
2489
|
-
|
|
2490
|
-
// ../provider-bridge-protocol/src/notifications.ts
|
|
2491
|
-
import { z as z18 } from "zod";
|
|
2492
|
-
var threadIdentityNotificationSchema = z18.object({
|
|
2493
|
-
threadId: z18.string().min(1),
|
|
2494
|
-
providerThreadId: z18.string().min(1),
|
|
2495
|
-
/** Refines the handshake's `sessionRestore` for this session. */
|
|
2496
|
-
sessionRestorable: z18.boolean().optional()
|
|
2497
|
-
}).passthrough();
|
|
2498
|
-
var sessionReplacedNotificationSchema = z18.object({
|
|
2499
|
-
threadId: z18.string().min(1),
|
|
2500
|
-
/** Identity of the replacement session (may equal the old identity). */
|
|
2501
|
-
providerThreadId: z18.string().min(1).nullable(),
|
|
2502
|
-
/** Human-readable cause, shown in the timeline. */
|
|
2503
|
-
reason: z18.string().min(1),
|
|
2504
|
-
/** True when provider-side context did not survive the replacement. */
|
|
2505
|
-
contextLost: z18.boolean().default(false)
|
|
2506
|
-
}).passthrough();
|
|
2507
|
-
var providerRawNotificationSchema = z18.object({
|
|
2508
|
-
threadId: z18.string().min(1).optional(),
|
|
2509
|
-
coverage: z18.enum(["noise", "unknown"]),
|
|
2510
|
-
payload: z18.unknown()
|
|
2511
|
-
}).passthrough();
|
|
2512
|
-
var providerRecoveryNotificationSchema = z18.object({
|
|
2513
|
-
threadId: z18.string().min(1).optional(),
|
|
2514
|
-
kind: providerRecoveryKindSchema,
|
|
2515
|
-
message: z18.string().min(1),
|
|
2516
|
-
retryable: z18.boolean()
|
|
2517
|
-
}).passthrough();
|
|
2518
|
-
var errorNotificationSchema = z18.object({
|
|
2519
|
-
threadId: z18.string().min(1).optional(),
|
|
2520
|
-
message: z18.string().min(1)
|
|
2521
|
-
}).passthrough();
|
|
2522
|
-
|
|
2523
|
-
// ../provider-bridge-protocol/src/bridge-requests.ts
|
|
2524
|
-
import { z as z19 } from "zod";
|
|
2525
|
-
var toolCallRequestParamsSchema = z19.object({
|
|
2526
|
-
providerThreadId: z19.string().min(1),
|
|
2527
|
-
threadId: z19.string().min(1).optional(),
|
|
2528
|
-
turnId: z19.union([z19.string().min(1), z19.null()]),
|
|
2529
|
-
callId: z19.string().min(1),
|
|
2530
|
-
tool: z19.string().min(1),
|
|
2531
|
-
arguments: z19.unknown()
|
|
2532
|
-
}).passthrough();
|
|
2533
|
-
var toolCallResultSchema = z19.object({
|
|
2534
|
-
success: z19.boolean(),
|
|
2535
|
-
contentItems: z19.array(
|
|
2536
|
-
z19.discriminatedUnion("type", [
|
|
2537
|
-
z19.object({ type: z19.literal("inputText"), text: z19.string() }),
|
|
2538
|
-
z19.object({
|
|
2539
|
-
type: z19.literal("inputImage"),
|
|
2540
|
-
imageUrl: z19.string().min(1)
|
|
2541
|
-
})
|
|
2542
|
-
])
|
|
2543
|
-
)
|
|
2544
|
-
}).passthrough();
|
|
2545
|
-
var interactionRequestParamsSchema = z19.object({
|
|
2546
|
-
providerThreadId: z19.string().min(1),
|
|
2547
|
-
threadId: z19.string().min(1).optional(),
|
|
2548
|
-
turnId: z19.union([z19.string().min(1), z19.null()]),
|
|
2549
|
-
payload: pendingInteractionPayloadSchema,
|
|
2466
|
+
z14.object({
|
|
2467
|
+
kind: z14.literal("item.textDelta"),
|
|
2468
|
+
key: deltaItemKeySchema,
|
|
2469
|
+
channel: deltaTextChannelSchema,
|
|
2470
|
+
text: z14.string(),
|
|
2471
|
+
providerTurnId: providerTurnIdSchema.optional(),
|
|
2472
|
+
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2473
|
+
}),
|
|
2550
2474
|
/**
|
|
2551
|
-
*
|
|
2552
|
-
*
|
|
2553
|
-
*
|
|
2554
|
-
*
|
|
2555
|
-
*
|
|
2556
|
-
*
|
|
2475
|
+
* Settle a text stream. `text` present: the provider's final text, preferred
|
|
2476
|
+
* over the accumulated stream (and enough on its own — a close for a key
|
|
2477
|
+
* nothing streamed under completes a fresh item). `text` absent: settle
|
|
2478
|
+
* with the accumulated stream text, completing nothing when the stream only
|
|
2479
|
+
* ever received whitespace. Either way the key is released, so later text
|
|
2480
|
+
* mints a fresh item. `channel` says which item to mint for a bare close
|
|
2481
|
+
* and where a provider-final `text` lands on a reasoning item. Providers
|
|
2482
|
+
* that name their message items may instead settle through `item.close`
|
|
2483
|
+
* with the full terminal shape (the uniform close rule) — that is the same
|
|
2484
|
+
* item lifecycle, not a second streaming dialect.
|
|
2557
2485
|
*/
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
METHOD_NOT_FOUND: -32601,
|
|
2567
|
-
/** Generic bridge failure. */
|
|
2568
|
-
BRIDGE_ERROR: -32e3,
|
|
2569
|
-
/** A turn/steer arrived but the session has no active turn. */
|
|
2570
|
-
NO_ACTIVE_TURN: -32001,
|
|
2571
|
-
/** thread/resume for a session the provider can no longer restore. */
|
|
2572
|
-
SESSION_NOT_RESTORABLE: -32002,
|
|
2573
|
-
/** thread/fork with a checkpoint on a bridge that only forks at the tip. */
|
|
2574
|
-
FORK_CHECKPOINT_UNSUPPORTED: -32003
|
|
2575
|
-
};
|
|
2576
|
-
|
|
2577
|
-
// ../provider-bridge-protocol/src/thread-event-grammar.ts
|
|
2578
|
-
var ITEM_STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
2579
|
-
"item/agentMessage/delta",
|
|
2580
|
-
"item/plan/delta",
|
|
2581
|
-
"item/commandExecution/outputDelta",
|
|
2582
|
-
"item/fileChange/outputDelta",
|
|
2583
|
-
"item/reasoning/summaryTextDelta",
|
|
2584
|
-
"item/reasoning/textDelta",
|
|
2585
|
-
"item/mcpToolCall/progress",
|
|
2586
|
-
"item/toolCall/progress"
|
|
2587
|
-
]);
|
|
2588
|
-
var MAX_ITEM_IDS_PER_THREAD = 512;
|
|
2589
|
-
var THREAD_EVENT_GRAMMAR_RULES = {
|
|
2590
|
-
itemOpensBeforeDelta: "item/opens-before-delta",
|
|
2591
|
-
itemSettlesOnce: "item/settles-once",
|
|
2592
|
-
turnStartsOnce: "turn/starts-once",
|
|
2593
|
-
turnSettlesOnce: "turn/settles-once",
|
|
2594
|
-
turnKnown: "turn/known"
|
|
2595
|
-
};
|
|
2596
|
-
var OK = { kind: "ok" };
|
|
2597
|
-
var ThreadEventGrammar = class {
|
|
2598
|
-
#byThreadId = /* @__PURE__ */ new Map();
|
|
2599
|
-
clear() {
|
|
2600
|
-
this.#byThreadId.clear();
|
|
2601
|
-
}
|
|
2602
|
-
clearThread(threadId) {
|
|
2603
|
-
this.#byThreadId.delete(threadId);
|
|
2604
|
-
}
|
|
2605
|
-
observe(event) {
|
|
2606
|
-
const state = this.#stateFor(event.threadId);
|
|
2607
|
-
switch (event.type) {
|
|
2608
|
-
case "turn/started": {
|
|
2609
|
-
const turnId = turnIdOf(event);
|
|
2610
|
-
if (turnId === void 0) {
|
|
2611
|
-
return OK;
|
|
2612
|
-
}
|
|
2613
|
-
if (state.completedTurnIds.has(turnId)) {
|
|
2614
|
-
return violation(
|
|
2615
|
-
THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
|
|
2616
|
-
`turn/started for turn "${turnId}", which already completed`
|
|
2617
|
-
);
|
|
2618
|
-
}
|
|
2619
|
-
if (state.startedTurnIds.has(turnId)) {
|
|
2620
|
-
return violation(
|
|
2621
|
-
THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
|
|
2622
|
-
`turn/started for turn "${turnId}", which is already open`
|
|
2623
|
-
);
|
|
2624
|
-
}
|
|
2625
|
-
state.startedTurnIds.add(turnId);
|
|
2626
|
-
return OK;
|
|
2627
|
-
}
|
|
2628
|
-
case "turn/completed": {
|
|
2629
|
-
const turnId = turnIdOf(event);
|
|
2630
|
-
if (turnId === void 0) {
|
|
2631
|
-
return OK;
|
|
2632
|
-
}
|
|
2633
|
-
if (state.completedTurnIds.has(turnId)) {
|
|
2634
|
-
return violation(
|
|
2635
|
-
THREAD_EVENT_GRAMMAR_RULES.turnSettlesOnce,
|
|
2636
|
-
`turn/completed for turn "${turnId}", which already completed`
|
|
2637
|
-
);
|
|
2638
|
-
}
|
|
2639
|
-
if (!state.startedTurnIds.has(turnId)) {
|
|
2640
|
-
return violation(
|
|
2641
|
-
THREAD_EVENT_GRAMMAR_RULES.turnKnown,
|
|
2642
|
-
`turn/completed for turn "${turnId}", which never started`
|
|
2643
|
-
);
|
|
2644
|
-
}
|
|
2645
|
-
state.startedTurnIds.delete(turnId);
|
|
2646
|
-
state.completedTurnIds.add(turnId);
|
|
2647
|
-
return OK;
|
|
2648
|
-
}
|
|
2649
|
-
case "item/started": {
|
|
2650
|
-
state.openItemIds.add(event.item.id);
|
|
2651
|
-
state.settledItemIds.delete(event.item.id);
|
|
2652
|
-
trim(state.openItemIds);
|
|
2653
|
-
return OK;
|
|
2654
|
-
}
|
|
2655
|
-
case "item/completed":
|
|
2656
|
-
case "item/backgroundTask/completed":
|
|
2657
|
-
case "item/delegation/completed": {
|
|
2658
|
-
const itemId = event.item.id;
|
|
2659
|
-
if (state.settledItemIds.has(itemId)) {
|
|
2660
|
-
return violation(
|
|
2661
|
-
THREAD_EVENT_GRAMMAR_RULES.itemSettlesOnce,
|
|
2662
|
-
`${event.type} for item "${itemId}", which already settled`
|
|
2663
|
-
);
|
|
2664
|
-
}
|
|
2665
|
-
state.openItemIds.delete(itemId);
|
|
2666
|
-
state.settledItemIds.add(itemId);
|
|
2667
|
-
trim(state.settledItemIds);
|
|
2668
|
-
return OK;
|
|
2669
|
-
}
|
|
2670
|
-
case "item/backgroundTask/progress":
|
|
2671
|
-
case "item/delegation/progress": {
|
|
2672
|
-
return this.#checkOpenItem(state, event.type, event.item.id);
|
|
2673
|
-
}
|
|
2674
|
-
default: {
|
|
2675
|
-
if (!ITEM_STREAMING_EVENT_TYPES.has(event.type)) {
|
|
2676
|
-
return OK;
|
|
2677
|
-
}
|
|
2678
|
-
if (!("itemId" in event) || typeof event.itemId !== "string") {
|
|
2679
|
-
return OK;
|
|
2680
|
-
}
|
|
2681
|
-
return this.#checkOpenItem(state, event.type, event.itemId);
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
#checkOpenItem(state, eventType, itemId) {
|
|
2686
|
-
if (state.openItemIds.has(itemId)) {
|
|
2687
|
-
return OK;
|
|
2688
|
-
}
|
|
2689
|
-
return violation(
|
|
2690
|
-
THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta,
|
|
2691
|
-
`${eventType} for item "${itemId}" arrived before item/started`
|
|
2692
|
-
);
|
|
2693
|
-
}
|
|
2694
|
-
#stateFor(threadId) {
|
|
2695
|
-
const existing = this.#byThreadId.get(threadId);
|
|
2696
|
-
if (existing !== void 0) {
|
|
2697
|
-
return existing;
|
|
2698
|
-
}
|
|
2699
|
-
const created = {
|
|
2700
|
-
openItemIds: /* @__PURE__ */ new Set(),
|
|
2701
|
-
settledItemIds: /* @__PURE__ */ new Set(),
|
|
2702
|
-
startedTurnIds: /* @__PURE__ */ new Set(),
|
|
2703
|
-
completedTurnIds: /* @__PURE__ */ new Set()
|
|
2704
|
-
};
|
|
2705
|
-
this.#byThreadId.set(threadId, created);
|
|
2706
|
-
return created;
|
|
2707
|
-
}
|
|
2708
|
-
};
|
|
2709
|
-
function violation(rule, reason) {
|
|
2710
|
-
return { kind: "violation", rule, reason };
|
|
2711
|
-
}
|
|
2712
|
-
function turnIdOf(event) {
|
|
2713
|
-
return "scope" in event ? getThreadEventScopeTurnId(event.scope) : void 0;
|
|
2714
|
-
}
|
|
2715
|
-
function trim(itemIds) {
|
|
2716
|
-
while (itemIds.size > MAX_ITEM_IDS_PER_THREAD) {
|
|
2717
|
-
const oldest = itemIds.values().next();
|
|
2718
|
-
if (oldest.done === true) {
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
itemIds.delete(oldest.value);
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
|
|
2725
|
-
// ../provider-bridge-protocol/src/thread-delta.ts
|
|
2726
|
-
import { z as z20 } from "zod";
|
|
2727
|
-
var THREAD_DELTA_NOTIFICATION_METHOD = "thread/delta";
|
|
2728
|
-
var deltaPresentationSchema = threadEventItemPresentationSchema;
|
|
2729
|
-
var THREAD_DELTA_KEY_SEPARATOR = "";
|
|
2730
|
-
var deltaKeyPartSchema = z20.string().min(1).refine((value) => !value.includes(THREAD_DELTA_KEY_SEPARATOR), {
|
|
2731
|
-
message: "provider keys must not contain the internal key separator (\\u001f)"
|
|
2732
|
-
});
|
|
2733
|
-
var deltaItemKeySchema = z20.object({
|
|
2734
|
-
providerItemId: deltaKeyPartSchema.optional(),
|
|
2735
|
-
channel: deltaKeyPartSchema.optional(),
|
|
2736
|
-
parentRef: deltaKeyPartSchema.optional()
|
|
2737
|
-
});
|
|
2738
|
-
var providerTurnIdSchema = deltaKeyPartSchema;
|
|
2739
|
-
var deltaFileChangeSchema = z20.object({
|
|
2740
|
-
path: z20.string(),
|
|
2741
|
-
/** The bridge states the change kind; the assembler never derives it. */
|
|
2742
|
-
kind: z20.enum(["add", "update", "delete"]),
|
|
2743
|
-
movePath: z20.string().optional(),
|
|
2744
|
-
/** Provider-supplied unified diff; preferred over old/new text building. */
|
|
2745
|
-
diff: z20.string().optional(),
|
|
2746
|
-
oldText: z20.string().optional(),
|
|
2747
|
-
/** When present the assembler builds the unified diff from old/new text. */
|
|
2748
|
-
newText: z20.string().optional()
|
|
2749
|
-
});
|
|
2750
|
-
var deltaBackgroundTaskShapeSchema = z20.object({
|
|
2751
|
-
type: z20.literal("backgroundTask"),
|
|
2486
|
+
z14.object({
|
|
2487
|
+
kind: z14.literal("item.textClose"),
|
|
2488
|
+
key: deltaItemKeySchema,
|
|
2489
|
+
channel: deltaTextChannelSchema,
|
|
2490
|
+
text: z14.string().optional(),
|
|
2491
|
+
providerTurnId: providerTurnIdSchema.optional(),
|
|
2492
|
+
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2493
|
+
}),
|
|
2752
2494
|
/**
|
|
2753
|
-
*
|
|
2754
|
-
*
|
|
2755
|
-
* correlate a restarted task with its earlier generations — the assembler
|
|
2756
|
-
* mints fresh item ids per generation, so identity must travel as data,
|
|
2757
|
-
* never as id text.
|
|
2495
|
+
* Item-keyed exact output append (codex command/fileChange output deltas).
|
|
2496
|
+
* Never synthesizes an open and never diffs — the text is already a delta.
|
|
2758
2497
|
*/
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
workflow: workflowProgressSnapshotSchema.optional(),
|
|
2767
|
-
usage: backgroundTaskUsageSchema.optional(),
|
|
2768
|
-
summary: z20.string().optional(),
|
|
2769
|
-
error: z20.string().optional(),
|
|
2770
|
-
outputFile: z20.string().optional()
|
|
2771
|
-
});
|
|
2772
|
-
var deltaFileReadShapeSchema = z20.object({
|
|
2773
|
-
type: z20.literal("fileRead"),
|
|
2774
|
-
path: z20.string(),
|
|
2775
|
-
cmd: z20.string().optional()
|
|
2776
|
-
});
|
|
2777
|
-
var deltaSearchShapeSchema = z20.object({
|
|
2778
|
-
type: z20.literal("search"),
|
|
2779
|
-
mode: threadEventSearchModeSchema,
|
|
2780
|
-
query: z20.string(),
|
|
2781
|
-
path: z20.string().optional(),
|
|
2782
|
-
cmd: z20.string().optional()
|
|
2783
|
-
});
|
|
2784
|
-
var deltaDelegationShapeSchema = z20.object({
|
|
2785
|
-
type: z20.literal("delegation"),
|
|
2786
|
-
childRef: deltaKeyPartSchema,
|
|
2787
|
-
label: z20.string(),
|
|
2788
|
-
background: z20.boolean(),
|
|
2789
|
-
summary: z20.string().optional()
|
|
2790
|
-
});
|
|
2791
|
-
var deltaPlanStepsShapeSchema = z20.object({
|
|
2792
|
-
type: z20.literal("planSteps"),
|
|
2793
|
-
steps: z20.array(threadEventPlanStepSchema),
|
|
2794
|
-
explanation: z20.string().optional()
|
|
2795
|
-
});
|
|
2796
|
-
var deltaExtensionShapeSchema = z20.object({
|
|
2797
|
-
type: z20.literal("extension"),
|
|
2798
|
-
kind: extensionKindSchema,
|
|
2799
|
-
payload: jsonValueSchema
|
|
2800
|
-
});
|
|
2801
|
-
var deltaItemShapeSchema = z20.discriminatedUnion("type", [
|
|
2802
|
-
z20.object({
|
|
2803
|
-
type: z20.literal("command"),
|
|
2804
|
-
command: z20.string(),
|
|
2805
|
-
cwd: z20.string(),
|
|
2806
|
-
aggregatedOutput: z20.string().optional(),
|
|
2807
|
-
exitCode: z20.number().optional(),
|
|
2808
|
-
durationMs: z20.number().optional()
|
|
2809
|
-
}),
|
|
2810
|
-
z20.object({
|
|
2811
|
-
type: z20.literal("fileChange"),
|
|
2812
|
-
/** Empty only on bare close-without-open fallbacks (path unknown). */
|
|
2813
|
-
changes: z20.array(deltaFileChangeSchema)
|
|
2498
|
+
z14.object({
|
|
2499
|
+
kind: z14.literal("item.outputDelta"),
|
|
2500
|
+
key: deltaItemKeySchema,
|
|
2501
|
+
channel: deltaOutputChannelSchema,
|
|
2502
|
+
text: z14.string(),
|
|
2503
|
+
providerTurnId: providerTurnIdSchema.optional(),
|
|
2504
|
+
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2814
2505
|
}),
|
|
2815
2506
|
/**
|
|
2816
|
-
*
|
|
2817
|
-
*
|
|
2818
|
-
* `presentation` (label, icon, suppression) instead of core keeping a
|
|
2819
|
-
* tool-name table; a `tool` item without presentation renders with the
|
|
2820
|
-
* generic tool row.
|
|
2507
|
+
* Cumulative command output snapshot (pi bash). The assembler diffs
|
|
2508
|
+
* consecutive snapshots into `outputDelta`/`reset` events.
|
|
2821
2509
|
*/
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
tool: z20.string(),
|
|
2825
|
-
server: z20.string().optional(),
|
|
2826
|
-
args: z20.unknown().optional(),
|
|
2827
|
-
result: z20.unknown().optional(),
|
|
2828
|
-
error: z20.string().optional(),
|
|
2829
|
-
durationMs: z20.number().optional()
|
|
2830
|
-
}),
|
|
2831
|
-
z20.object({ type: z20.literal("compaction") }),
|
|
2832
|
-
z20.object({ type: z20.literal("agentMessage"), text: z20.string() }),
|
|
2833
|
-
z20.object({
|
|
2834
|
-
type: z20.literal("reasoning"),
|
|
2835
|
-
summary: z20.array(z20.string()),
|
|
2836
|
-
content: z20.array(z20.string())
|
|
2837
|
-
}),
|
|
2838
|
-
z20.object({ type: z20.literal("plan"), text: z20.string() }),
|
|
2839
|
-
z20.object({
|
|
2840
|
-
type: z20.literal("webSearch"),
|
|
2841
|
-
queries: z20.array(z20.string()).min(1)
|
|
2842
|
-
}),
|
|
2843
|
-
z20.object({
|
|
2844
|
-
type: z20.literal("webFetch"),
|
|
2845
|
-
url: z20.string(),
|
|
2846
|
-
prompt: z20.string().nullable().optional(),
|
|
2847
|
-
pattern: z20.string().nullable()
|
|
2848
|
-
}),
|
|
2849
|
-
z20.object({ type: z20.literal("imageView"), path: z20.string() }),
|
|
2850
|
-
deltaBackgroundTaskShapeSchema,
|
|
2851
|
-
// Grammar v3 shapes. Every existing shape above is kept unchanged.
|
|
2852
|
-
deltaFileReadShapeSchema,
|
|
2853
|
-
deltaSearchShapeSchema,
|
|
2854
|
-
deltaDelegationShapeSchema,
|
|
2855
|
-
deltaPlanStepsShapeSchema,
|
|
2856
|
-
deltaExtensionShapeSchema
|
|
2857
|
-
]);
|
|
2858
|
-
var deltaProgressSnapshotSchema = z20.discriminatedUnion("type", [
|
|
2859
|
-
deltaBackgroundTaskShapeSchema,
|
|
2860
|
-
deltaDelegationShapeSchema
|
|
2861
|
-
]);
|
|
2862
|
-
var deltaTextChannelSchema = z20.enum([
|
|
2863
|
-
"agentMessage",
|
|
2864
|
-
"reasoningSummary",
|
|
2865
|
-
"reasoningText",
|
|
2866
|
-
"plan"
|
|
2867
|
-
]);
|
|
2868
|
-
var deltaOutputChannelSchema = z20.enum(["command", "fileChange"]);
|
|
2869
|
-
var deltaErrorSchema = z20.object({ message: z20.string() });
|
|
2870
|
-
var deltaAttachSchema = z20.enum(["open", "currentOrLast"]);
|
|
2871
|
-
var deltaNoTurnFallbackSchema = z20.object({
|
|
2872
|
-
raw: providerRawEventSchema,
|
|
2873
|
-
rawType: z20.string()
|
|
2874
|
-
});
|
|
2875
|
-
function requireExtensionPresentation(delta, ctx) {
|
|
2876
|
-
if (delta.item.type === "extension" && delta.presentation === void 0) {
|
|
2877
|
-
ctx.addIssue({
|
|
2878
|
-
code: "custom",
|
|
2879
|
-
message: "extension items require a presentation on item.open/item.close",
|
|
2880
|
-
path: ["presentation"]
|
|
2881
|
-
});
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
2885
|
-
/**
|
|
2886
|
-
* The provider consumed an input (immediate or steered). The assembler owns
|
|
2887
|
-
* the queue-until-turn-opens behavior and the terminal-turn invariant.
|
|
2888
|
-
* With `providerTurnId` the acceptance is emitted against that vouched turn
|
|
2889
|
-
* directly (codex correlates acceptance to a named native turn).
|
|
2890
|
-
*/
|
|
2891
|
-
z20.object({
|
|
2892
|
-
kind: z20.literal("input.accepted"),
|
|
2893
|
-
clientRequestId: clientTurnRequestIdSchema,
|
|
2894
|
-
providerTurnId: providerTurnIdSchema.optional()
|
|
2895
|
-
}),
|
|
2896
|
-
/**
|
|
2897
|
-
* Input the provider itself injected into the conversation, with no bb
|
|
2898
|
-
* client request behind it (a pi extension's `sendMessage` custom message
|
|
2899
|
-
* that triggered or steered a turn). The assembler records it as a
|
|
2900
|
-
* `userMessage` item in the open turn so the transcript shows what the
|
|
2901
|
-
* model was answering. Dropped silently when no turn is open: the provider
|
|
2902
|
-
* appended it to its own context without running the agent, so there is no
|
|
2903
|
-
* bb turn to attach it to.
|
|
2904
|
-
*/
|
|
2905
|
-
z20.object({
|
|
2906
|
-
kind: z20.literal("input.provider"),
|
|
2907
|
-
text: z20.string().min(1),
|
|
2908
|
-
parentRef: deltaKeyPartSchema.optional()
|
|
2909
|
-
}),
|
|
2910
|
-
/**
|
|
2911
|
-
* An explicit provider signal opened work (pi `agent_start`, codex
|
|
2912
|
-
* `turn/started`). With `providerTurnId` the turn lives in the keyed
|
|
2913
|
-
* provider-turn space: several may be open at once (codex multiplexes
|
|
2914
|
-
* subagent child turns onto one thread) and none of the current-turn
|
|
2915
|
-
* machinery is touched.
|
|
2916
|
-
*/
|
|
2917
|
-
z20.object({
|
|
2918
|
-
kind: z20.literal("turn.open"),
|
|
2919
|
-
providerTurnId: providerTurnIdSchema.optional(),
|
|
2920
|
-
/** Provider-native parent tool-call id for delegated child turns. */
|
|
2921
|
-
parentRef: deltaKeyPartSchema.optional()
|
|
2922
|
-
}),
|
|
2923
|
-
/**
|
|
2924
|
-
* The bridge's conclusion that the turn settled. `claimIfIdle: true` marks
|
|
2925
|
-
* fallback closers that own a turn only if accepted input is pending
|
|
2926
|
-
* (the old bridge-kit terminal-turn rule, applied centrally); an open turn is
|
|
2927
|
-
* always settled. A keyed boundary (`providerTurnId`) always emits — the
|
|
2928
|
-
* provider named the turn — and settles only that turn.
|
|
2929
|
-
*/
|
|
2930
|
-
z20.object({
|
|
2931
|
-
kind: z20.literal("turn.boundary"),
|
|
2932
|
-
status: threadEventTurnStatusSchema,
|
|
2933
|
-
error: deltaErrorSchema.optional(),
|
|
2934
|
-
providerCheckpointId: z20.string().min(1).optional(),
|
|
2935
|
-
claimIfIdle: z20.boolean().optional(),
|
|
2936
|
-
providerTurnId: providerTurnIdSchema.optional()
|
|
2937
|
-
}),
|
|
2938
|
-
/**
|
|
2939
|
-
* A parsed item opened. `attach: "currentOrLast"` pins the item to the turn
|
|
2940
|
-
* that is open or just closed without opening a new one (pi threshold
|
|
2941
|
-
* compaction); the default attaches to the open turn only. A known
|
|
2942
|
-
* `providerItemId` reuses its minted bb id (an explicit open reopens the
|
|
2943
|
-
* same item, codex's settle/reopen rule).
|
|
2944
|
-
*/
|
|
2945
|
-
z20.object({
|
|
2946
|
-
kind: z20.literal("item.open"),
|
|
2947
|
-
key: deltaItemKeySchema,
|
|
2948
|
-
item: deltaItemShapeSchema,
|
|
2949
|
-
/**
|
|
2950
|
-
* Grammar v3: how the row reads, persisted with the opened item. The
|
|
2951
|
-
* one place presentation travels. Optional for core shapes while v2
|
|
2952
|
-
* deltas are accepted; REQUIRED for `extension` shapes.
|
|
2953
|
-
*/
|
|
2954
|
-
presentation: deltaPresentationSchema.optional(),
|
|
2955
|
-
attach: deltaAttachSchema.optional(),
|
|
2956
|
-
providerTurnId: providerTurnIdSchema.optional(),
|
|
2957
|
-
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2958
|
-
}).superRefine(requireExtensionPresentation),
|
|
2959
|
-
/**
|
|
2960
|
-
* The item settled. `item` is REQUIRED and always carries the full terminal
|
|
2961
|
-
* item shape (Michael's uniform close rule, 2026-08-18): the assembler
|
|
2962
|
-
* builds the completed item from it. With a same-shaped item open under the
|
|
2963
|
-
* key, the terminal shape wins and the opened item contributes only its
|
|
2964
|
-
* minted id; with a different-shaped item open, the assembler closes the
|
|
2965
|
-
* opened shape and then emits the terminal shape (ACP's dual-complete);
|
|
2966
|
-
* with nothing open it builds the bare completed item.
|
|
2967
|
-
*
|
|
2968
|
-
* Provider-identified closes (`key.providerItemId`) dedup: a repeated close
|
|
2969
|
-
* for a settled id is dropped and an explicit `item.open` reopens the id
|
|
2970
|
-
* (codex retries the terminal notification after approvals).
|
|
2971
|
-
*/
|
|
2972
|
-
z20.object({
|
|
2973
|
-
kind: z20.literal("item.close"),
|
|
2974
|
-
key: deltaItemKeySchema,
|
|
2975
|
-
status: threadEventItemStatusSchema,
|
|
2976
|
-
resultText: z20.string().optional(),
|
|
2977
|
-
exitCode: z20.number().optional(),
|
|
2978
|
-
aggregatedOutput: z20.string().optional(),
|
|
2979
|
-
/** Terminal approval verdict (codex declined → denied). Default null. */
|
|
2980
|
-
approvalStatus: z20.literal("denied").optional(),
|
|
2981
|
-
item: deltaItemShapeSchema,
|
|
2982
|
-
/**
|
|
2983
|
-
* Grammar v3: the terminal presentation. Like `item`, the close carries
|
|
2984
|
-
* the full terminal form; when absent the opened item's presentation
|
|
2985
|
-
* survives onto the completed item (close-echo). REQUIRED for an
|
|
2986
|
-
* `extension` shape, which has nothing to echo without it.
|
|
2987
|
-
*/
|
|
2988
|
-
presentation: deltaPresentationSchema.optional(),
|
|
2989
|
-
providerTurnId: providerTurnIdSchema.optional(),
|
|
2990
|
-
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
2991
|
-
}).superRefine(requireExtensionPresentation),
|
|
2992
|
-
/**
|
|
2993
|
-
* Free-form progress on an open item (non-command tool updates), or — with
|
|
2994
|
-
* `snapshot` — a re-embedded snapshot of work that outlives its turn: a
|
|
2995
|
-
* background task (`item/backgroundTask/progress`) or, in grammar v3, a
|
|
2996
|
-
* background delegation (`item/delegation/progress`); both thread-scoped,
|
|
2997
|
-
* no turn required.
|
|
2998
|
-
*
|
|
2999
|
-
* Progress is throttled centrally by the assembler (one emission per item
|
|
3000
|
-
* key per policy interval, 500ms default; the newest suppressed snapshot is
|
|
3001
|
-
* flushed trailing-edge on the thread's next traffic once the window
|
|
3002
|
-
* elapses, and an `item.close` supersedes it). `flush: true` bypasses the
|
|
3003
|
-
* throttle and resets the window — status transitions must land immediately.
|
|
3004
|
-
*/
|
|
3005
|
-
z20.object({
|
|
3006
|
-
kind: z20.literal("item.progress"),
|
|
3007
|
-
key: deltaItemKeySchema,
|
|
3008
|
-
message: z20.string().optional(),
|
|
3009
|
-
snapshot: deltaProgressSnapshotSchema.optional(),
|
|
3010
|
-
flush: z20.boolean().optional(),
|
|
3011
|
-
providerTurnId: providerTurnIdSchema.optional(),
|
|
3012
|
-
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
3013
|
-
}),
|
|
3014
|
-
/**
|
|
3015
|
-
* Streamed text — the one streaming dialect. Every text stream is keyed
|
|
3016
|
-
* like every other item: by the provider's own item id when the provider
|
|
3017
|
-
* names its message items (codex), or by a bridge-chosen `key.channel`
|
|
3018
|
-
* (`"assistant"`, `"thinking-2"`) plus `key.parentRef` for providers whose
|
|
3019
|
-
* streams are anonymous (claude, pi, acp). The first delta for an unknown
|
|
3020
|
-
* key synthesizes the channel's `item/started`; later deltas (and deltas
|
|
3021
|
-
* for a provider id already opened or settled) reuse the mapped id. The
|
|
3022
|
-
* assembler accumulates the stream text per open item so `item.textClose`
|
|
3023
|
-
* can settle without a provider-final text.
|
|
3024
|
-
*/
|
|
3025
|
-
z20.object({
|
|
3026
|
-
kind: z20.literal("item.textDelta"),
|
|
3027
|
-
key: deltaItemKeySchema,
|
|
3028
|
-
channel: deltaTextChannelSchema,
|
|
3029
|
-
text: z20.string(),
|
|
3030
|
-
providerTurnId: providerTurnIdSchema.optional(),
|
|
3031
|
-
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
3032
|
-
}),
|
|
3033
|
-
/**
|
|
3034
|
-
* Settle a text stream. `text` present: the provider's final text, preferred
|
|
3035
|
-
* over the accumulated stream (and enough on its own — a close for a key
|
|
3036
|
-
* nothing streamed under completes a fresh item). `text` absent: settle
|
|
3037
|
-
* with the accumulated stream text, completing nothing when the stream only
|
|
3038
|
-
* ever received whitespace. Either way the key is released, so later text
|
|
3039
|
-
* mints a fresh item. `channel` says which item to mint for a bare close
|
|
3040
|
-
* and where a provider-final `text` lands on a reasoning item. Providers
|
|
3041
|
-
* that name their message items may instead settle through `item.close`
|
|
3042
|
-
* with the full terminal shape (the uniform close rule) — that is the same
|
|
3043
|
-
* item lifecycle, not a second streaming dialect.
|
|
3044
|
-
*/
|
|
3045
|
-
z20.object({
|
|
3046
|
-
kind: z20.literal("item.textClose"),
|
|
3047
|
-
key: deltaItemKeySchema,
|
|
3048
|
-
channel: deltaTextChannelSchema,
|
|
3049
|
-
text: z20.string().optional(),
|
|
3050
|
-
providerTurnId: providerTurnIdSchema.optional(),
|
|
3051
|
-
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
3052
|
-
}),
|
|
3053
|
-
/**
|
|
3054
|
-
* Item-keyed exact output append (codex command/fileChange output deltas).
|
|
3055
|
-
* Never synthesizes an open and never diffs — the text is already a delta.
|
|
3056
|
-
*/
|
|
3057
|
-
z20.object({
|
|
3058
|
-
kind: z20.literal("item.outputDelta"),
|
|
3059
|
-
key: deltaItemKeySchema,
|
|
3060
|
-
channel: deltaOutputChannelSchema,
|
|
3061
|
-
text: z20.string(),
|
|
3062
|
-
providerTurnId: providerTurnIdSchema.optional(),
|
|
3063
|
-
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
3064
|
-
}),
|
|
3065
|
-
/**
|
|
3066
|
-
* Cumulative command output snapshot (pi bash). The assembler diffs
|
|
3067
|
-
* consecutive snapshots into `outputDelta`/`reset` events.
|
|
3068
|
-
*/
|
|
3069
|
-
z20.object({
|
|
3070
|
-
kind: z20.literal("command.outputSnapshot"),
|
|
2510
|
+
z14.object({
|
|
2511
|
+
kind: z14.literal("command.outputSnapshot"),
|
|
3071
2512
|
key: deltaItemKeySchema,
|
|
3072
|
-
text:
|
|
2513
|
+
text: z14.string(),
|
|
3073
2514
|
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
3074
2515
|
}),
|
|
3075
2516
|
/**
|
|
@@ -3082,11 +2523,11 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3082
2523
|
* `thread/tokenUsage/updated` only: a provider whose usage also measures
|
|
3083
2524
|
* the context window sends the `contextWindow` delta beside it.
|
|
3084
2525
|
*/
|
|
3085
|
-
|
|
3086
|
-
kind:
|
|
2526
|
+
z14.object({
|
|
2527
|
+
kind: z14.literal("usage"),
|
|
3087
2528
|
total: threadEventTokenUsageBreakdownSchema,
|
|
3088
2529
|
last: threadEventTokenUsageBreakdownSchema,
|
|
3089
|
-
modelContextWindow:
|
|
2530
|
+
modelContextWindow: z14.number().nullable(),
|
|
3090
2531
|
providerTurnId: providerTurnIdSchema.optional()
|
|
3091
2532
|
}),
|
|
3092
2533
|
/**
|
|
@@ -3096,33 +2537,33 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3096
2537
|
* (codex measures the window per native turn) and `attach` is then
|
|
3097
2538
|
* irrelevant.
|
|
3098
2539
|
*/
|
|
3099
|
-
|
|
3100
|
-
kind:
|
|
3101
|
-
used:
|
|
3102
|
-
size:
|
|
3103
|
-
estimated:
|
|
2540
|
+
z14.object({
|
|
2541
|
+
kind: z14.literal("contextWindow"),
|
|
2542
|
+
used: z14.number().nullable(),
|
|
2543
|
+
size: z14.number().nullable().optional(),
|
|
2544
|
+
estimated: z14.boolean(),
|
|
3104
2545
|
attach: deltaAttachSchema,
|
|
3105
2546
|
providerTurnId: providerTurnIdSchema.optional()
|
|
3106
2547
|
}),
|
|
3107
|
-
|
|
3108
|
-
kind:
|
|
2548
|
+
z14.object({
|
|
2549
|
+
kind: z14.literal("context.compacted"),
|
|
3109
2550
|
providerTurnId: providerTurnIdSchema.optional(),
|
|
3110
2551
|
noTurnFallback: deltaNoTurnFallbackSchema.optional()
|
|
3111
2552
|
}),
|
|
3112
|
-
|
|
2553
|
+
z14.object({ kind: z14.literal("context.cleared") }),
|
|
3113
2554
|
/** The aggregate working-tree diff for a turn (codex turn/diff/updated). */
|
|
3114
|
-
|
|
3115
|
-
kind:
|
|
3116
|
-
diff:
|
|
2555
|
+
z14.object({
|
|
2556
|
+
kind: z14.literal("turn.diff"),
|
|
2557
|
+
diff: z14.string(),
|
|
3117
2558
|
providerTurnId: providerTurnIdSchema.optional()
|
|
3118
2559
|
}),
|
|
3119
2560
|
// Thread metadata (codex thread lifecycle notifications).
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
kind:
|
|
3123
|
-
providerThreadId:
|
|
2561
|
+
z14.object({ kind: z14.literal("thread.started") }),
|
|
2562
|
+
z14.object({
|
|
2563
|
+
kind: z14.literal("thread.identity"),
|
|
2564
|
+
providerThreadId: z14.string().min(1)
|
|
3124
2565
|
}),
|
|
3125
|
-
|
|
2566
|
+
z14.object({ kind: z14.literal("thread.name"), name: z14.string().min(1) }),
|
|
3126
2567
|
/**
|
|
3127
2568
|
* Plugin-declared thread state (grammar v3): `"<pluginId>/<name>"` kinds
|
|
3128
2569
|
* beside the core thread-state family (usage, context window, rate limits,
|
|
@@ -3136,8 +2577,8 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3136
2577
|
* this union's discriminator; the item shape and the persisted item call
|
|
3137
2578
|
* the same value `kind`.
|
|
3138
2579
|
*/
|
|
3139
|
-
|
|
3140
|
-
kind:
|
|
2580
|
+
z14.object({
|
|
2581
|
+
kind: z14.literal("extension.state"),
|
|
3141
2582
|
extensionKind: extensionKindSchema,
|
|
3142
2583
|
payload: jsonValueSchema
|
|
3143
2584
|
}),
|
|
@@ -3147,8 +2588,8 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3147
2588
|
* bridge-side — it is seeded from a per-child post-initialize read the
|
|
3148
2589
|
* assembler never sees.
|
|
3149
2590
|
*/
|
|
3150
|
-
|
|
3151
|
-
kind:
|
|
2591
|
+
z14.object({
|
|
2592
|
+
kind: z14.literal("provider.rateLimits"),
|
|
3152
2593
|
rateLimits: providerRateLimitStateSchema
|
|
3153
2594
|
}),
|
|
3154
2595
|
/**
|
|
@@ -3158,16 +2599,16 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3158
2599
|
* turn; `threadScoped: true` pins thread scope (codex errors without a
|
|
3159
2600
|
* native turn id never attach to whatever turn happens to be open).
|
|
3160
2601
|
*/
|
|
3161
|
-
|
|
3162
|
-
kind:
|
|
3163
|
-
message:
|
|
3164
|
-
detail:
|
|
3165
|
-
willRetry:
|
|
2602
|
+
z14.object({
|
|
2603
|
+
kind: z14.literal("provider.error"),
|
|
2604
|
+
message: z14.string(),
|
|
2605
|
+
detail: z14.string().optional(),
|
|
2606
|
+
willRetry: z14.boolean().optional(),
|
|
3166
2607
|
category: providerErrorCategorySchema.optional(),
|
|
3167
2608
|
errorInfo: providerErrorInfoSchema.optional(),
|
|
3168
|
-
settlesTurn:
|
|
2609
|
+
settlesTurn: z14.boolean().optional(),
|
|
3169
2610
|
providerTurnId: providerTurnIdSchema.optional(),
|
|
3170
|
-
threadScoped:
|
|
2611
|
+
threadScoped: z14.boolean().optional()
|
|
3171
2612
|
}),
|
|
3172
2613
|
/**
|
|
3173
2614
|
* The provider switched models mid-flight (claude model fallback). Scoped to
|
|
@@ -3176,23 +2617,23 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3176
2617
|
* assistant fallback block against the later system duplicate stays
|
|
3177
2618
|
* bridge-side — it is keyed by the bridge's own segment tracking.
|
|
3178
2619
|
*/
|
|
3179
|
-
|
|
3180
|
-
kind:
|
|
3181
|
-
originalModel:
|
|
3182
|
-
fallbackModel:
|
|
3183
|
-
reason:
|
|
3184
|
-
message:
|
|
2620
|
+
z14.object({
|
|
2621
|
+
kind: z14.literal("provider.modelFallback"),
|
|
2622
|
+
originalModel: z14.string().min(1),
|
|
2623
|
+
fallbackModel: z14.string().min(1),
|
|
2624
|
+
reason: z14.enum(["refusal", "provider"]),
|
|
2625
|
+
message: z14.string()
|
|
3185
2626
|
}),
|
|
3186
2627
|
/**
|
|
3187
2628
|
* `vouchedTurn: true` scopes the warning to the open turn when one exists
|
|
3188
2629
|
* (ACP warnings are turn-scoped mid-turn); default is thread scope.
|
|
3189
2630
|
*/
|
|
3190
|
-
|
|
3191
|
-
kind:
|
|
3192
|
-
summary:
|
|
3193
|
-
details:
|
|
2631
|
+
z14.object({
|
|
2632
|
+
kind: z14.literal("provider.warning"),
|
|
2633
|
+
summary: z14.string().optional(),
|
|
2634
|
+
details: z14.string().optional(),
|
|
3194
2635
|
category: threadEventWarningCategorySchema.optional(),
|
|
3195
|
-
vouchedTurn:
|
|
2636
|
+
vouchedTurn: z14.boolean().optional()
|
|
3196
2637
|
}),
|
|
3197
2638
|
/**
|
|
3198
2639
|
* The bridge's visibility classification decided this raw event is unknown.
|
|
@@ -3203,12 +2644,12 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3203
2644
|
* "known event, no active turn" visibility fallback for events that
|
|
3204
2645
|
* otherwise translate to silence) and is dropped entirely mid-turn.
|
|
3205
2646
|
*/
|
|
3206
|
-
|
|
3207
|
-
kind:
|
|
2647
|
+
z14.object({
|
|
2648
|
+
kind: z14.literal("unhandled"),
|
|
3208
2649
|
raw: providerRawEventSchema,
|
|
3209
|
-
rawType:
|
|
3210
|
-
vouchedTurn:
|
|
3211
|
-
onlyIfNoTurn:
|
|
2650
|
+
rawType: z14.string(),
|
|
2651
|
+
vouchedTurn: z14.boolean(),
|
|
2652
|
+
onlyIfNoTurn: z14.boolean().optional(),
|
|
3212
2653
|
parentRef: deltaKeyPartSchema.optional(),
|
|
3213
2654
|
providerTurnId: providerTurnIdSchema.optional()
|
|
3214
2655
|
}),
|
|
@@ -3216,7 +2657,7 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3216
2657
|
* Lifecycle settlement: the session was interrupted. The assembler closes
|
|
3217
2658
|
* the open turn and open items as interrupted.
|
|
3218
2659
|
*/
|
|
3219
|
-
|
|
2660
|
+
z14.object({ kind: z14.literal("session.ended") }),
|
|
3220
2661
|
/**
|
|
3221
2662
|
* Provider-native id-space boundary: a new provider session was constructed
|
|
3222
2663
|
* for this thread (start/resume/fork/rebuild), so its native turn/item ids
|
|
@@ -3224,28 +2665,32 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
|
|
|
3224
2665
|
* sets, open items and streams; the bridge settles any open work first
|
|
3225
2666
|
* (nothing is in flight at any construction site).
|
|
3226
2667
|
*/
|
|
3227
|
-
|
|
2668
|
+
z14.object({ kind: z14.literal("session.reset") })
|
|
3228
2669
|
]);
|
|
3229
|
-
var threadDeltaNotificationParamsSchema =
|
|
3230
|
-
threadId:
|
|
3231
|
-
deltas:
|
|
2670
|
+
var threadDeltaNotificationParamsSchema = z14.object({
|
|
2671
|
+
threadId: z14.string().min(1),
|
|
2672
|
+
deltas: z14.array(threadDeltaSchema)
|
|
3232
2673
|
}).passthrough();
|
|
3233
2674
|
|
|
3234
2675
|
// ../provider-bridge-protocol/src/assembler/delta-assembler.ts
|
|
3235
2676
|
import { randomUUID } from "node:crypto";
|
|
3236
2677
|
|
|
2678
|
+
// ../provider-bridge-protocol/src/version.ts
|
|
2679
|
+
var PROVIDER_BRIDGE_PROTOCOL_VERSION = 2;
|
|
2680
|
+
var THREAD_DELTA_GRAMMAR_V3 = 3;
|
|
2681
|
+
|
|
3237
2682
|
// ../provider-bridge-protocol/src/bridge-kit/adapter-utils.ts
|
|
3238
|
-
import { z as
|
|
2683
|
+
import { z as z16 } from "zod";
|
|
3239
2684
|
|
|
3240
2685
|
// ../provider-bridge-protocol/src/bridge-kit/tool-arg-schemas.ts
|
|
3241
|
-
import { z as
|
|
3242
|
-
var bashArgsSchema =
|
|
3243
|
-
command:
|
|
3244
|
-
cwd:
|
|
2686
|
+
import { z as z15 } from "zod";
|
|
2687
|
+
var bashArgsSchema = z15.object({
|
|
2688
|
+
command: z15.string().optional(),
|
|
2689
|
+
cwd: z15.string().optional()
|
|
3245
2690
|
}).passthrough();
|
|
3246
|
-
var textBlockSchema =
|
|
3247
|
-
type:
|
|
3248
|
-
text:
|
|
2691
|
+
var textBlockSchema = z15.object({
|
|
2692
|
+
type: z15.literal("text"),
|
|
2693
|
+
text: z15.string()
|
|
3249
2694
|
});
|
|
3250
2695
|
|
|
3251
2696
|
// ../provider-bridge-protocol/src/bridge-kit/provider-visibility-helpers.ts
|
|
@@ -3254,10 +2699,10 @@ function isRecord(value) {
|
|
|
3254
2699
|
}
|
|
3255
2700
|
|
|
3256
2701
|
// ../provider-bridge-protocol/src/bridge-kit/adapter-utils.ts
|
|
3257
|
-
var contentWrapperSchema =
|
|
3258
|
-
content:
|
|
2702
|
+
var contentWrapperSchema = z16.object({
|
|
2703
|
+
content: z16.array(z16.unknown())
|
|
3259
2704
|
}).passthrough();
|
|
3260
|
-
var shellEnvironmentVariableKeySchema =
|
|
2705
|
+
var shellEnvironmentVariableKeySchema = z16.string().regex(/^[A-Z_][A-Z0-9_]*$/i);
|
|
3261
2706
|
var MAX_EXACT_LINE_DIFF_CELLS = 1e6;
|
|
3262
2707
|
function splitComparableLines(text) {
|
|
3263
2708
|
const normalized = text.replace(/\r\n?/gu, "\n");
|
|
@@ -4087,7 +3532,7 @@ function createDeltaAssembler(options) {
|
|
|
4087
3532
|
);
|
|
4088
3533
|
case "tool": {
|
|
4089
3534
|
const toolArguments = toOptionalRecord(shape.args);
|
|
4090
|
-
const
|
|
3535
|
+
const result2 = shape.result ?? close.resultText;
|
|
4091
3536
|
return withParentToolCallId(
|
|
4092
3537
|
{
|
|
4093
3538
|
type: "toolCall",
|
|
@@ -4096,7 +3541,7 @@ function createDeltaAssembler(options) {
|
|
|
4096
3541
|
tool: shape.tool,
|
|
4097
3542
|
...toolArguments ? { arguments: toolArguments } : {},
|
|
4098
3543
|
status: close.status,
|
|
4099
|
-
...
|
|
3544
|
+
...result2 === void 0 ? {} : { result: result2 },
|
|
4100
3545
|
...shape.error === void 0 ? {} : { error: shape.error },
|
|
4101
3546
|
...shape.durationMs === void 0 ? {} : { durationMs: shape.durationMs }
|
|
4102
3547
|
},
|
|
@@ -4864,178 +4309,942 @@ function createDeltaAssembler(options) {
|
|
|
4864
4309
|
case "turn.diff": {
|
|
4865
4310
|
const turnId = delta.providerTurnId !== void 0 ? resolveVouchedTurnId(state, delta.providerTurnId) : state.currentTurnId;
|
|
4866
4311
|
if (turnId === void 0) {
|
|
4867
|
-
return;
|
|
4312
|
+
return;
|
|
4313
|
+
}
|
|
4314
|
+
events.push({
|
|
4315
|
+
type: "turn/diff/updated",
|
|
4316
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4317
|
+
providerThreadId: "",
|
|
4318
|
+
scope: turnScope(turnId),
|
|
4319
|
+
diff: delta.diff
|
|
4320
|
+
});
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
case "thread.started": {
|
|
4324
|
+
events.push({
|
|
4325
|
+
type: "thread/started",
|
|
4326
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4327
|
+
scope: threadScope()
|
|
4328
|
+
});
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
case "thread.identity": {
|
|
4332
|
+
events.push({
|
|
4333
|
+
type: "thread/identity",
|
|
4334
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4335
|
+
providerThreadId: delta.providerThreadId,
|
|
4336
|
+
scope: threadScope()
|
|
4337
|
+
});
|
|
4338
|
+
return;
|
|
4339
|
+
}
|
|
4340
|
+
case "thread.name": {
|
|
4341
|
+
events.push({
|
|
4342
|
+
type: "thread/name/updated",
|
|
4343
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4344
|
+
providerThreadId: "",
|
|
4345
|
+
scope: threadScope(),
|
|
4346
|
+
threadName: delta.name
|
|
4347
|
+
});
|
|
4348
|
+
return;
|
|
4349
|
+
}
|
|
4350
|
+
case "provider.rateLimits": {
|
|
4351
|
+
events.push({
|
|
4352
|
+
type: "provider/rateLimits/updated",
|
|
4353
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4354
|
+
providerThreadId: "",
|
|
4355
|
+
scope: threadScope(),
|
|
4356
|
+
rateLimits: delta.rateLimits
|
|
4357
|
+
});
|
|
4358
|
+
return;
|
|
4359
|
+
}
|
|
4360
|
+
case "extension.state": {
|
|
4361
|
+
events.push({
|
|
4362
|
+
type: "thread/extensionState/updated",
|
|
4363
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4364
|
+
providerThreadId: "",
|
|
4365
|
+
scope: threadScope(),
|
|
4366
|
+
kind: delta.extensionKind,
|
|
4367
|
+
payload: delta.payload
|
|
4368
|
+
});
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
case "session.reset": {
|
|
4372
|
+
return;
|
|
4373
|
+
}
|
|
4374
|
+
case "session.ended": {
|
|
4375
|
+
const turnId = state.currentTurnId ?? (state.pendingAccepted.length > 0 ? ensureTurnOpen(state, events) : void 0);
|
|
4376
|
+
if (turnId === void 0) {
|
|
4377
|
+
return;
|
|
4378
|
+
}
|
|
4379
|
+
for (const open of state.openItemsByKey.values()) {
|
|
4380
|
+
if (open.threadAttached) {
|
|
4381
|
+
continue;
|
|
4382
|
+
}
|
|
4383
|
+
const streamed = open.text.length > 0 || open.summaryText.length > 0;
|
|
4384
|
+
const item = (streamed ? settleTextItem(open, void 0, void 0) : void 0) ?? completeStartedItem(
|
|
4385
|
+
open.item,
|
|
4386
|
+
{ status: "interrupted" },
|
|
4387
|
+
void 0
|
|
4388
|
+
);
|
|
4389
|
+
events.push({
|
|
4390
|
+
type: "item/completed",
|
|
4391
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4392
|
+
providerThreadId: "",
|
|
4393
|
+
scope: turnScope(turnId),
|
|
4394
|
+
item
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
events.push({
|
|
4398
|
+
type: "turn/completed",
|
|
4399
|
+
threadId: UNSTAMPED_THREAD_ID,
|
|
4400
|
+
providerThreadId: "",
|
|
4401
|
+
scope: turnScope(turnId),
|
|
4402
|
+
status: "interrupted"
|
|
4403
|
+
});
|
|
4404
|
+
finishTurn(state);
|
|
4405
|
+
return;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
return {
|
|
4410
|
+
assemble(args) {
|
|
4411
|
+
const events = [];
|
|
4412
|
+
const sink = {
|
|
4413
|
+
push: (...newEvents) => {
|
|
4414
|
+
for (const event of newEvents) {
|
|
4415
|
+
const textDelta = textDeltaFlushMs > 0 ? asTextDeltaEvent(event) : void 0;
|
|
4416
|
+
const state = states.get(args.threadId);
|
|
4417
|
+
if (textDelta !== void 0 && state !== void 0) {
|
|
4418
|
+
bufferTextDelta(state, textDelta, events);
|
|
4419
|
+
continue;
|
|
4420
|
+
}
|
|
4421
|
+
if (state !== void 0) {
|
|
4422
|
+
flushPendingText(state, events);
|
|
4423
|
+
}
|
|
4424
|
+
events.push(event);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
};
|
|
4428
|
+
const existing = args.deltas[0]?.kind === "session.reset" ? void 0 : states.get(args.threadId);
|
|
4429
|
+
if (existing !== void 0) {
|
|
4430
|
+
flushElapsedPendingText(existing, events);
|
|
4431
|
+
const progressKeysInBatch = /* @__PURE__ */ new Set();
|
|
4432
|
+
for (const delta of args.deltas) {
|
|
4433
|
+
if (delta.kind === "item.progress") {
|
|
4434
|
+
progressKeysInBatch.add(itemKeyString(delta.key));
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
flushElapsedPendingProgress(existing, sink, progressKeysInBatch);
|
|
4438
|
+
}
|
|
4439
|
+
for (const delta of args.deltas) {
|
|
4440
|
+
if (delta.kind === "session.reset") {
|
|
4441
|
+
const state = states.get(args.threadId);
|
|
4442
|
+
if (state !== void 0) {
|
|
4443
|
+
flushPendingText(state, events);
|
|
4444
|
+
}
|
|
4445
|
+
states.delete(args.threadId);
|
|
4446
|
+
continue;
|
|
4447
|
+
}
|
|
4448
|
+
if (delta.kind === "item.textClose" || delta.kind === "item.close" || delta.kind === "session.ended") {
|
|
4449
|
+
const state = states.get(args.threadId);
|
|
4450
|
+
if (state !== void 0) {
|
|
4451
|
+
flushPendingText(state, events);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
handleDelta(stateFor(args.threadId), delta, sink);
|
|
4455
|
+
}
|
|
4456
|
+
return events;
|
|
4457
|
+
},
|
|
4458
|
+
getBbItemId(threadId, providerItemId) {
|
|
4459
|
+
return states.get(threadId)?.bbItemIdByProviderItemId.get(providerItemId);
|
|
4460
|
+
},
|
|
4461
|
+
getProviderItemId(threadId, bbItemId) {
|
|
4462
|
+
return states.get(threadId)?.providerItemIdByBbItemId.get(bbItemId);
|
|
4463
|
+
},
|
|
4464
|
+
getBbTurnId(threadId, providerTurnId) {
|
|
4465
|
+
return states.get(threadId)?.bbTurnIdByProviderTurnId.get(providerTurnId);
|
|
4466
|
+
},
|
|
4467
|
+
getProviderTurnId(threadId, bbTurnId) {
|
|
4468
|
+
return states.get(threadId)?.providerTurnIdByBbTurnId.get(bbTurnId);
|
|
4469
|
+
},
|
|
4470
|
+
getOpenTurnId(threadId) {
|
|
4471
|
+
return states.get(threadId)?.currentTurnId;
|
|
4472
|
+
}
|
|
4473
|
+
};
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
// ../provider-bridge-protocol/src/testing/bridge-delta-assembly.ts
|
|
4477
|
+
function createBridgeDeltaEventCollector(providerId = "pi") {
|
|
4478
|
+
const assembler = createDeltaAssembler({ providerId, textDeltaFlushMs: 0 });
|
|
4479
|
+
return {
|
|
4480
|
+
assembler,
|
|
4481
|
+
assembleMessage(message) {
|
|
4482
|
+
if (message.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
|
|
4483
|
+
return [];
|
|
4484
|
+
}
|
|
4485
|
+
const parsed = threadDeltaNotificationParamsSchema.safeParse(
|
|
4486
|
+
message.params
|
|
4487
|
+
);
|
|
4488
|
+
if (!parsed.success) {
|
|
4489
|
+
throw new Error(
|
|
4490
|
+
`Invalid thread/delta notification: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(
|
|
4491
|
+
"; "
|
|
4492
|
+
)} (params: ${JSON.stringify(message.params)?.slice(0, 400)})`
|
|
4493
|
+
);
|
|
4494
|
+
}
|
|
4495
|
+
return assembler.assemble({
|
|
4496
|
+
threadId: parsed.data.threadId,
|
|
4497
|
+
deltas: parsed.data.deltas
|
|
4498
|
+
});
|
|
4499
|
+
}
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4502
|
+
function assembleCapturedThreadEvents(messages, providerId = "pi") {
|
|
4503
|
+
const collector = createBridgeDeltaEventCollector(providerId);
|
|
4504
|
+
return messages.flatMap((message) => collector.assembleMessage(message));
|
|
4505
|
+
}
|
|
4506
|
+
function toConformanceMessages() {
|
|
4507
|
+
throw new Error(
|
|
4508
|
+
"experimental_toConformanceMessages was removed: experimental_runBridgeConformance assembles thread/delta itself. Hand it a transport whose takeMessages returns the raw captured messages (CapturedBridgeJsonRpcOutput.takeMessages) and pass the bridge's providerId."
|
|
4509
|
+
);
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
// ../provider-bridge-protocol/src/conformance/client.ts
|
|
4513
|
+
import { z as z17 } from "zod";
|
|
4514
|
+
function isWireMessage(value) {
|
|
4515
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4516
|
+
}
|
|
4517
|
+
var threadDeltaAddressSchema = z17.object({ threadId: z17.string() }).passthrough();
|
|
4518
|
+
var ConformanceClient = class {
|
|
4519
|
+
constructor(transport, timeoutMs, collector) {
|
|
4520
|
+
this.transport = transport;
|
|
4521
|
+
this.timeoutMs = timeoutMs;
|
|
4522
|
+
this.collector = collector;
|
|
4523
|
+
}
|
|
4524
|
+
transport;
|
|
4525
|
+
timeoutMs;
|
|
4526
|
+
collector;
|
|
4527
|
+
nextId = 1;
|
|
4528
|
+
log = [];
|
|
4529
|
+
/** Every assembled event, in wire order. */
|
|
4530
|
+
events = [];
|
|
4531
|
+
drainIntoLog() {
|
|
4532
|
+
for (const raw of this.transport.takeMessages()) {
|
|
4533
|
+
if (!isWireMessage(raw)) {
|
|
4534
|
+
continue;
|
|
4535
|
+
}
|
|
4536
|
+
const logIndex = this.log.length;
|
|
4537
|
+
this.log.push(raw);
|
|
4538
|
+
if (raw.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
|
|
4539
|
+
continue;
|
|
4540
|
+
}
|
|
4541
|
+
const events = this.collector.assembleMessage(raw);
|
|
4542
|
+
const address = threadDeltaAddressSchema.parse(raw.params);
|
|
4543
|
+
for (const event of events) {
|
|
4544
|
+
this.events.push({ threadId: address.threadId, event, logIndex });
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
sendRaw(line) {
|
|
4549
|
+
this.transport.send(line);
|
|
4550
|
+
}
|
|
4551
|
+
notify(method, params) {
|
|
4552
|
+
this.transport.send(
|
|
4553
|
+
JSON.stringify({
|
|
4554
|
+
jsonrpc: "2.0",
|
|
4555
|
+
method,
|
|
4556
|
+
...params !== void 0 ? { params } : {}
|
|
4557
|
+
})
|
|
4558
|
+
);
|
|
4559
|
+
}
|
|
4560
|
+
request(method, params) {
|
|
4561
|
+
const id = this.nextId;
|
|
4562
|
+
this.nextId += 1;
|
|
4563
|
+
this.transport.send(
|
|
4564
|
+
JSON.stringify({
|
|
4565
|
+
jsonrpc: "2.0",
|
|
4566
|
+
id,
|
|
4567
|
+
method,
|
|
4568
|
+
...params !== void 0 ? { params } : {}
|
|
4569
|
+
})
|
|
4570
|
+
);
|
|
4571
|
+
return id;
|
|
4572
|
+
}
|
|
4573
|
+
/** Poll until `resolve` yields a value or the deadline passes (→ null). */
|
|
4574
|
+
async waitFor(resolve3) {
|
|
4575
|
+
const deadline = Date.now() + this.timeoutMs;
|
|
4576
|
+
for (; ; ) {
|
|
4577
|
+
this.drainIntoLog();
|
|
4578
|
+
const value = resolve3();
|
|
4579
|
+
if (value !== void 0) {
|
|
4580
|
+
return value;
|
|
4581
|
+
}
|
|
4582
|
+
if (Date.now() > deadline) {
|
|
4583
|
+
return null;
|
|
4584
|
+
}
|
|
4585
|
+
await new Promise((r) => setTimeout(r, 15));
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
async waitForResponse(id) {
|
|
4589
|
+
return this.waitFor(
|
|
4590
|
+
() => this.log.find(
|
|
4591
|
+
(message) => message.id === id && message.method === void 0
|
|
4592
|
+
)
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
/** A settle window: drain for the given quiet period without expectations. */
|
|
4596
|
+
async settle(quietMs) {
|
|
4597
|
+
const deadline = Date.now() + quietMs;
|
|
4598
|
+
while (Date.now() < deadline) {
|
|
4599
|
+
this.drainIntoLog();
|
|
4600
|
+
await new Promise((r) => setTimeout(r, 15));
|
|
4601
|
+
}
|
|
4602
|
+
this.drainIntoLog();
|
|
4603
|
+
}
|
|
4604
|
+
responsesFor(id) {
|
|
4605
|
+
return this.log.filter(
|
|
4606
|
+
(message) => message.id === id && message.method === void 0
|
|
4607
|
+
);
|
|
4608
|
+
}
|
|
4609
|
+
notifications(method) {
|
|
4610
|
+
return this.log.filter(
|
|
4611
|
+
(message) => message.id === void 0 && typeof message.method === "string" && (method === void 0 || message.method === method)
|
|
4612
|
+
);
|
|
4613
|
+
}
|
|
4614
|
+
};
|
|
4615
|
+
var clientRequestCounter = 0;
|
|
4616
|
+
function nextConformanceClientRequestId() {
|
|
4617
|
+
const alphabet = "23456789abcdefghijkmnpqrstuvwxyz";
|
|
4618
|
+
clientRequestCounter += 1;
|
|
4619
|
+
let remaining = clientRequestCounter;
|
|
4620
|
+
let suffix = "";
|
|
4621
|
+
while (suffix.length < 10) {
|
|
4622
|
+
suffix = alphabet[remaining % alphabet.length] + suffix;
|
|
4623
|
+
remaining = Math.floor(remaining / alphabet.length);
|
|
4624
|
+
}
|
|
4625
|
+
return `creq_${suffix}`;
|
|
4626
|
+
}
|
|
4627
|
+
|
|
4628
|
+
// ../provider-bridge-protocol/src/conformance/scenarios.ts
|
|
4629
|
+
import { z as z25 } from "zod";
|
|
4630
|
+
|
|
4631
|
+
// ../provider-bridge-protocol/src/handshake.ts
|
|
4632
|
+
import { z as z18 } from "zod";
|
|
4633
|
+
var bridgeGrammarVersionsSchema = z18.tuple([z18.number().int().positive(), z18.number().int().positive()]).refine(([min, max]) => min <= max, {
|
|
4634
|
+
message: "grammarVersions must be an ascending [min, max] range"
|
|
4635
|
+
});
|
|
4636
|
+
function negotiateGrammarVersion(runtime, bridge) {
|
|
4637
|
+
const min = Math.max(runtime[0], bridge[0]);
|
|
4638
|
+
const max = Math.min(runtime[1], bridge[1]);
|
|
4639
|
+
return min <= max ? max : null;
|
|
4640
|
+
}
|
|
4641
|
+
var bridgeSteerModeSchema = z18.enum(["inject", "queue"]);
|
|
4642
|
+
var bridgeCapabilitiesSchema = z18.object({
|
|
4643
|
+
/**
|
|
4644
|
+
* A released session can be re-attached later from its persisted
|
|
4645
|
+
* providerThreadId. The per-session `sessionRestorable` flag on
|
|
4646
|
+
* thread-identity results refines this (an agent update can drop restore
|
|
4647
|
+
* support mid-flight); this handshake value is the default for sessions
|
|
4648
|
+
* that do not say.
|
|
4649
|
+
*/
|
|
4650
|
+
sessionRestore: z18.boolean().default(false),
|
|
4651
|
+
/**
|
|
4652
|
+
* The bridge mirrors bb archive state into the provider's own session
|
|
4653
|
+
* list. When false the runtime never sends thread/archive or
|
|
4654
|
+
* thread/unarchive.
|
|
4655
|
+
*/
|
|
4656
|
+
threadArchive: z18.boolean().default(false),
|
|
4657
|
+
/**
|
|
4658
|
+
* The bridge pushes bb thread titles to the provider. When false the
|
|
4659
|
+
* runtime never sends thread/name/set.
|
|
4660
|
+
*/
|
|
4661
|
+
threadRename: z18.boolean().default(false),
|
|
4662
|
+
/** The bridge supports thread/goal/clear. */
|
|
4663
|
+
threadGoalClear: z18.boolean().default(false),
|
|
4664
|
+
/**
|
|
4665
|
+
* Session cloning support ({@link providerForkSchema} — the same
|
|
4666
|
+
* vocabulary the provider declaration uses). The declaration is a ceiling
|
|
4667
|
+
* for UI affordances; this is the operative truth, and it may only narrow
|
|
4668
|
+
* the declaration, never widen it.
|
|
4669
|
+
*/
|
|
4670
|
+
fork: providerForkSchema.default("none"),
|
|
4671
|
+
/**
|
|
4672
|
+
* Where the thread's approval policy is enforced. "runtime" bridges
|
|
4673
|
+
* forward every approval request and the runtime applies the thread
|
|
4674
|
+
* policy (including auto-deny). "provider" bridges enforce policy before
|
|
4675
|
+
* forwarding, so every forwarded request is already known to need user
|
|
4676
|
+
* input and the runtime must not reclassify it against mutable thread
|
|
4677
|
+
* settings.
|
|
4678
|
+
*/
|
|
4679
|
+
approvalEnforcedBy: z18.enum(["runtime", "provider"]).default("runtime"),
|
|
4680
|
+
/**
|
|
4681
|
+
* The `thread/delta` grammar range this bridge speaks. A bridge that says
|
|
4682
|
+
* nothing is read as speaking exactly the protocol version it negotiated,
|
|
4683
|
+
* so the default is `[2, 2]` — never a wider range it never claimed.
|
|
4684
|
+
* Every bridge in this repo emits v3 and reports `[3, 3]`, and the
|
|
4685
|
+
* runtime's assembler speaks `[3, 3]` only (`ASSEMBLER_GRAMMAR_VERSIONS`),
|
|
4686
|
+
* so a bridge that takes the default is refused at startup: the two
|
|
4687
|
+
* ranges must intersect ({@link negotiateGrammarVersion}) and both sides
|
|
4688
|
+
* emit the highest common version.
|
|
4689
|
+
*/
|
|
4690
|
+
grammarVersions: bridgeGrammarVersionsSchema.default([
|
|
4691
|
+
PROVIDER_BRIDGE_PROTOCOL_VERSION,
|
|
4692
|
+
PROVIDER_BRIDGE_PROTOCOL_VERSION
|
|
4693
|
+
]),
|
|
4694
|
+
/**
|
|
4695
|
+
* Mid-turn steer delivery ({@link bridgeSteerModeSchema}). Defaults to
|
|
4696
|
+
* `queue`, the conservative reading: absence is the definite "no" the
|
|
4697
|
+
* rest of this handshake uses, and `inject` is the stronger promise (the
|
|
4698
|
+
* steer reaches the model before the turn ends) a bridge must make
|
|
4699
|
+
* explicitly. Nothing in the runtime, server, or clients reads it today:
|
|
4700
|
+
* `turn/steer` is sent either way, and a steer whose turn is gone is
|
|
4701
|
+
* dropped on the bridge's `staleTurn` recovery hint or `NO_ACTIVE_TURN`
|
|
4702
|
+
* error (`steerTurn` in @bb/agent-runtime), whatever the mode. claude,
|
|
4703
|
+
* codex, and pi declare `inject`; ACP and the echo example declare
|
|
4704
|
+
* `queue`.
|
|
4705
|
+
*/
|
|
4706
|
+
steerMode: bridgeSteerModeSchema.default("queue"),
|
|
4707
|
+
/**
|
|
4708
|
+
* Which optional requests the bridge handles. `skills.configure`: the
|
|
4709
|
+
* bridge accepts `skills/configure` (bb's injected skill roots). When
|
|
4710
|
+
* false the runtime never sends it, so a bridge that answers unknown
|
|
4711
|
+
* methods with METHOD_NOT_FOUND — as the protocol instructs — still
|
|
4712
|
+
* starts threads; it simply runs without injected skills. A bridge that
|
|
4713
|
+
* handles the request declares it; the runtime never probes.
|
|
4714
|
+
*/
|
|
4715
|
+
skills: z18.object({ configure: z18.boolean().default(false) }).default({ configure: false })
|
|
4716
|
+
}).passthrough();
|
|
4717
|
+
var initializeParamsSchema = z18.object({
|
|
4718
|
+
protocolVersion: z18.number().int().positive(),
|
|
4719
|
+
client: z18.object({ name: z18.string().min(1), version: z18.string().min(1) }),
|
|
4720
|
+
/**
|
|
4721
|
+
* The `thread/delta` grammar range the runtime's assembler accepts (see
|
|
4722
|
+
* {@link negotiateGrammarVersion}). A runtime that predates the field
|
|
4723
|
+
* reads as speaking exactly its protocol version.
|
|
4724
|
+
*/
|
|
4725
|
+
grammarVersions: bridgeGrammarVersionsSchema.default([
|
|
4726
|
+
PROVIDER_BRIDGE_PROTOCOL_VERSION,
|
|
4727
|
+
PROVIDER_BRIDGE_PROTOCOL_VERSION
|
|
4728
|
+
])
|
|
4729
|
+
}).passthrough();
|
|
4730
|
+
var initializeResultSchema = z18.object({
|
|
4731
|
+
protocolVersion: z18.number().int().positive(),
|
|
4732
|
+
// An absent capabilities block reads as "no capabilities" via the inner
|
|
4733
|
+
// per-field defaults, so older bridges parse to explicit values.
|
|
4734
|
+
capabilities: z18.preprocess(
|
|
4735
|
+
(value) => value ?? {},
|
|
4736
|
+
bridgeCapabilitiesSchema
|
|
4737
|
+
)
|
|
4738
|
+
}).passthrough();
|
|
4739
|
+
|
|
4740
|
+
// ../provider-bridge-protocol/src/execution-options.ts
|
|
4741
|
+
import { z as z19 } from "zod";
|
|
4742
|
+
var bridgeExecutionOptionsSchema = z19.object({
|
|
4743
|
+
model: z19.string().min(1).optional(),
|
|
4744
|
+
serviceTier: serviceTierSchema.optional(),
|
|
4745
|
+
reasoningLevel: reasoningLevelSchema.optional(),
|
|
4746
|
+
/**
|
|
4747
|
+
* BB prompt mode (`"plan"`), present only when the prompt entered one
|
|
4748
|
+
* through the provider's declared composer action. Each bridge maps it
|
|
4749
|
+
* onto the agent's native equivalent.
|
|
4750
|
+
*/
|
|
4751
|
+
promptMode: promptModeSchema.optional(),
|
|
4752
|
+
/** Frozen for the life of a provider session; applied at construction. */
|
|
4753
|
+
instructions: z19.string().optional(),
|
|
4754
|
+
envVars: z19.record(z19.string(), z19.string()).optional(),
|
|
4755
|
+
/** Provider-scoped session options. Opaque outside the owning bridge. */
|
|
4756
|
+
providerOptions: z19.record(z19.string(), z19.unknown()).optional()
|
|
4757
|
+
}).and(runtimePermissionPolicySchema);
|
|
4758
|
+
|
|
4759
|
+
// ../provider-bridge-protocol/src/provider-maintenance.ts
|
|
4760
|
+
import { z as z20 } from "zod";
|
|
4761
|
+
var providerMaintenanceParamsSchema = z20.object({
|
|
4762
|
+
providerId: z20.string().min(1),
|
|
4763
|
+
cwd: z20.string().min(1).optional(),
|
|
4764
|
+
providerOptions: z20.record(z20.string(), z20.unknown()).optional()
|
|
4765
|
+
}).passthrough();
|
|
4766
|
+
var providerInstallationRequirementSchema = z20.enum([
|
|
4767
|
+
"thread_rewind"
|
|
4768
|
+
]);
|
|
4769
|
+
var providerInstallationStatusParamsSchema = providerMaintenanceParamsSchema.extend({
|
|
4770
|
+
requirement: providerInstallationRequirementSchema.optional()
|
|
4771
|
+
});
|
|
4772
|
+
var providerHealthSchema = z20.object({
|
|
4773
|
+
status: z20.enum([
|
|
4774
|
+
"ready",
|
|
4775
|
+
"not_installed",
|
|
4776
|
+
"unauthenticated",
|
|
4777
|
+
"expired",
|
|
4778
|
+
"unsupported_version",
|
|
4779
|
+
"unknown"
|
|
4780
|
+
]),
|
|
4781
|
+
statusMessage: z20.string().min(1).nullable(),
|
|
4782
|
+
accountEmail: z20.string().nullable(),
|
|
4783
|
+
planLabel: z20.string().min(1).nullable(),
|
|
4784
|
+
installedVersion: z20.string().min(1).nullable(),
|
|
4785
|
+
minimumSupportedVersion: z20.string().min(1).nullable(),
|
|
4786
|
+
canInstall: z20.boolean(),
|
|
4787
|
+
canUpdate: z20.boolean(),
|
|
4788
|
+
loginCommand: z20.string().min(1).nullable()
|
|
4789
|
+
}).passthrough();
|
|
4790
|
+
var providerUsageWindowSchema = z20.object({
|
|
4791
|
+
label: z20.string().min(1),
|
|
4792
|
+
usedPercent: z20.number().min(0).max(100),
|
|
4793
|
+
resetsAt: z20.string().min(1).nullable(),
|
|
4794
|
+
cost: z20.object({
|
|
4795
|
+
usedUsdCents: z20.number().int().nonnegative(),
|
|
4796
|
+
limitUsdCents: z20.number().int().positive()
|
|
4797
|
+
}).optional()
|
|
4798
|
+
}).passthrough();
|
|
4799
|
+
var providerUsageSchema = z20.discriminatedUnion("status", [
|
|
4800
|
+
z20.object({
|
|
4801
|
+
status: z20.literal("ok"),
|
|
4802
|
+
accountEmail: z20.string().email().nullable(),
|
|
4803
|
+
planLabel: z20.string().min(1).nullable(),
|
|
4804
|
+
windows: z20.array(providerUsageWindowSchema)
|
|
4805
|
+
}).passthrough(),
|
|
4806
|
+
z20.object({ status: z20.literal("not_installed") }).passthrough(),
|
|
4807
|
+
z20.object({ status: z20.literal("unauthenticated") }).passthrough(),
|
|
4808
|
+
z20.object({ status: z20.literal("expired") }).passthrough(),
|
|
4809
|
+
z20.object({
|
|
4810
|
+
status: z20.literal("error"),
|
|
4811
|
+
message: z20.string().min(1),
|
|
4812
|
+
planLabel: z20.string().min(1).nullable().default(null),
|
|
4813
|
+
accountEmail: z20.string().nullable().default(null)
|
|
4814
|
+
}).passthrough()
|
|
4815
|
+
]);
|
|
4816
|
+
var providerHealthResultSchema = z20.discriminatedUnion(
|
|
4817
|
+
"supported",
|
|
4818
|
+
[
|
|
4819
|
+
z20.object({ supported: z20.literal(false) }).passthrough(),
|
|
4820
|
+
z20.object({
|
|
4821
|
+
supported: z20.literal(true),
|
|
4822
|
+
health: providerHealthSchema
|
|
4823
|
+
}).passthrough()
|
|
4824
|
+
]
|
|
4825
|
+
);
|
|
4826
|
+
var providerUsageResultSchema = z20.discriminatedUnion(
|
|
4827
|
+
"supported",
|
|
4828
|
+
[
|
|
4829
|
+
z20.object({ supported: z20.literal(false) }).passthrough(),
|
|
4830
|
+
z20.object({
|
|
4831
|
+
supported: z20.literal(true),
|
|
4832
|
+
usage: providerUsageSchema
|
|
4833
|
+
}).passthrough()
|
|
4834
|
+
]
|
|
4835
|
+
);
|
|
4836
|
+
var providerInstallationActionKindSchema = z20.enum([
|
|
4837
|
+
"install",
|
|
4838
|
+
"update"
|
|
4839
|
+
]);
|
|
4840
|
+
var providerInstallationActionSchema = z20.object({
|
|
4841
|
+
kind: providerInstallationActionKindSchema,
|
|
4842
|
+
label: z20.enum(["Install", "Update"]),
|
|
4843
|
+
command: z20.string().min(1)
|
|
4844
|
+
}).passthrough();
|
|
4845
|
+
var providerInstallationSourceSchema = z20.enum([
|
|
4846
|
+
"notInstalled",
|
|
4847
|
+
"npmGlobal",
|
|
4848
|
+
"external"
|
|
4849
|
+
]);
|
|
4850
|
+
var providerInstallationStatusSchema = z20.object({
|
|
4851
|
+
executableName: z20.string().min(1),
|
|
4852
|
+
executablePath: z20.string().min(1).nullable(),
|
|
4853
|
+
installed: z20.boolean(),
|
|
4854
|
+
installSource: providerInstallationSourceSchema,
|
|
4855
|
+
currentVersion: z20.string().min(1).nullable(),
|
|
4856
|
+
latestVersion: z20.string().min(1).nullable(),
|
|
4857
|
+
minimumSupportedVersion: z20.string().min(1).nullable(),
|
|
4858
|
+
npmPackageName: z20.string().min(1).nullable(),
|
|
4859
|
+
npmGlobalPackageVersion: z20.string().min(1).nullable(),
|
|
4860
|
+
installAction: providerInstallationActionSchema.nullable(),
|
|
4861
|
+
needsUpdate: z20.boolean(),
|
|
4862
|
+
versionUnsupported: z20.boolean()
|
|
4863
|
+
}).passthrough();
|
|
4864
|
+
var providerInstallationRunParamsSchema = providerMaintenanceParamsSchema.extend({
|
|
4865
|
+
action: providerInstallationActionKindSchema
|
|
4866
|
+
});
|
|
4867
|
+
var providerInstallationCommandSchema = z20.object({
|
|
4868
|
+
command: z20.string().min(1),
|
|
4869
|
+
args: z20.array(z20.string()).max(64),
|
|
4870
|
+
displayCommand: z20.string().min(1)
|
|
4871
|
+
}).passthrough();
|
|
4872
|
+
var providerInstallationVerificationSchema = z20.discriminatedUnion("kind", [
|
|
4873
|
+
z20.object({ kind: z20.literal("installed") }).passthrough(),
|
|
4874
|
+
z20.object({
|
|
4875
|
+
kind: z20.literal("version_changed"),
|
|
4876
|
+
previousVersion: z20.string().min(1)
|
|
4877
|
+
}).passthrough(),
|
|
4878
|
+
z20.object({
|
|
4879
|
+
kind: z20.literal("version_at_least"),
|
|
4880
|
+
version: z20.string().min(1)
|
|
4881
|
+
}).passthrough()
|
|
4882
|
+
]);
|
|
4883
|
+
var providerInstallationRunResultSchema = z20.discriminatedUnion("available", [
|
|
4884
|
+
z20.object({
|
|
4885
|
+
available: z20.literal(false),
|
|
4886
|
+
message: z20.string().min(1)
|
|
4887
|
+
}).passthrough(),
|
|
4888
|
+
z20.object({
|
|
4889
|
+
available: z20.literal(true),
|
|
4890
|
+
command: providerInstallationCommandSchema,
|
|
4891
|
+
verification: providerInstallationVerificationSchema
|
|
4892
|
+
}).passthrough()
|
|
4893
|
+
]);
|
|
4894
|
+
|
|
4895
|
+
// ../provider-bridge-protocol/src/requests.ts
|
|
4896
|
+
import { z as z21 } from "zod";
|
|
4897
|
+
var BRIDGE_REQUEST_METHODS = {
|
|
4898
|
+
initialize: "initialize",
|
|
4899
|
+
modelList: "model/list",
|
|
4900
|
+
providerHealth: "provider/health",
|
|
4901
|
+
providerUsage: "provider/usage",
|
|
4902
|
+
providerInstallationStatus: "provider/installation/status",
|
|
4903
|
+
providerInstallationRun: "provider/installation/run",
|
|
4904
|
+
threadStart: "thread/start",
|
|
4905
|
+
threadResume: "thread/resume",
|
|
4906
|
+
threadFork: "thread/fork",
|
|
4907
|
+
threadStop: "thread/stop",
|
|
4908
|
+
threadDiscard: "thread/discard",
|
|
4909
|
+
threadNameSet: "thread/name/set",
|
|
4910
|
+
threadArchive: "thread/archive",
|
|
4911
|
+
threadUnarchive: "thread/unarchive",
|
|
4912
|
+
threadGoalClear: "thread/goal/clear",
|
|
4913
|
+
turnStart: "turn/start",
|
|
4914
|
+
turnSteer: "turn/steer",
|
|
4915
|
+
skillsConfigure: "skills/configure"
|
|
4916
|
+
};
|
|
4917
|
+
var sessionConstructionFields = {
|
|
4918
|
+
threadId: z21.string().min(1),
|
|
4919
|
+
cwd: z21.string().min(1),
|
|
4920
|
+
options: bridgeExecutionOptionsSchema,
|
|
4921
|
+
dynamicTools: z21.array(dynamicToolSchema).optional(),
|
|
4922
|
+
disallowedTools: z21.array(z21.string().min(1)).optional(),
|
|
4923
|
+
instructionMode: instructionModeSchema
|
|
4924
|
+
};
|
|
4925
|
+
var modelListParamsSchema = z21.object({ cwd: z21.string().min(1).optional() }).passthrough();
|
|
4926
|
+
var threadStartParamsSchema = z21.object({
|
|
4927
|
+
...sessionConstructionFields,
|
|
4928
|
+
input: z21.array(promptInputSchema).optional()
|
|
4929
|
+
}).passthrough();
|
|
4930
|
+
var threadResumeParamsSchema = z21.object({
|
|
4931
|
+
...sessionConstructionFields,
|
|
4932
|
+
providerThreadId: z21.string().min(1)
|
|
4933
|
+
}).passthrough();
|
|
4934
|
+
var threadForkParamsSchema = z21.object({
|
|
4935
|
+
...sessionConstructionFields,
|
|
4936
|
+
sourceProviderThreadId: z21.string().min(1),
|
|
4937
|
+
/**
|
|
4938
|
+
* Absent means fork at the tip. Bridges whose handshake advertises
|
|
4939
|
+
* `fork: "tip"` reject a request carrying a checkpoint instead of
|
|
4940
|
+
* silently cloning more history than the bb timeline shows.
|
|
4941
|
+
*/
|
|
4942
|
+
sourceProviderCheckpointId: z21.string().min(1).optional()
|
|
4943
|
+
}).passthrough();
|
|
4944
|
+
var threadStopParamsSchema = z21.object({
|
|
4945
|
+
threadId: z21.string().min(1),
|
|
4946
|
+
providerThreadId: z21.string().min(1),
|
|
4947
|
+
/**
|
|
4948
|
+
* "interrupt" stops an active turn and settles it as interrupted.
|
|
4949
|
+
* "release" detaches an idle session so its resources can be reclaimed;
|
|
4950
|
+
* it must never fabricate an interruption. One verb serving both intents
|
|
4951
|
+
* is the #1584 incident — the field is required.
|
|
4952
|
+
*/
|
|
4953
|
+
intent: z21.enum(["interrupt", "release"]),
|
|
4954
|
+
/** Non-null when the stop interrupts an active provider turn. */
|
|
4955
|
+
activeTurnId: z21.string().min(1).nullable()
|
|
4956
|
+
}).passthrough();
|
|
4957
|
+
var threadRefParams = z21.object({
|
|
4958
|
+
threadId: z21.string().min(1),
|
|
4959
|
+
providerThreadId: z21.string().min(1)
|
|
4960
|
+
}).passthrough();
|
|
4961
|
+
var threadNameSetParamsSchema = z21.object({
|
|
4962
|
+
threadId: z21.string().min(1),
|
|
4963
|
+
providerThreadId: z21.string().min(1),
|
|
4964
|
+
title: z21.string().min(1)
|
|
4965
|
+
}).passthrough();
|
|
4966
|
+
var turnInputFields = {
|
|
4967
|
+
threadId: z21.string().min(1),
|
|
4968
|
+
providerThreadId: z21.string().min(1),
|
|
4969
|
+
input: z21.array(promptInputSchema),
|
|
4970
|
+
clientRequestId: clientTurnRequestIdSchema,
|
|
4971
|
+
options: bridgeExecutionOptionsSchema
|
|
4972
|
+
};
|
|
4973
|
+
var turnStartParamsSchema = z21.object(turnInputFields).passthrough();
|
|
4974
|
+
var turnSteerParamsSchema = z21.object({
|
|
4975
|
+
...turnInputFields,
|
|
4976
|
+
expectedTurnId: z21.string().min(1)
|
|
4977
|
+
}).passthrough();
|
|
4978
|
+
var skillsConfigureRootSchema = z21.object({
|
|
4979
|
+
id: z21.string().min(1),
|
|
4980
|
+
path: z21.string().min(1),
|
|
4981
|
+
skills: z21.array(
|
|
4982
|
+
z21.object({
|
|
4983
|
+
name: z21.string().min(1),
|
|
4984
|
+
description: z21.string()
|
|
4985
|
+
}).passthrough()
|
|
4986
|
+
)
|
|
4987
|
+
}).passthrough();
|
|
4988
|
+
var skillsConfigureParamsSchema = z21.object({
|
|
4989
|
+
roots: z21.array(skillsConfigureRootSchema)
|
|
4990
|
+
}).passthrough();
|
|
4991
|
+
var threadIdentityResultSchema = z21.object({
|
|
4992
|
+
providerThreadId: z21.string().min(1),
|
|
4993
|
+
/** Refines the handshake's `sessionRestore` for this session. */
|
|
4994
|
+
sessionRestorable: z21.boolean().optional()
|
|
4995
|
+
}).passthrough();
|
|
4996
|
+
var modelListResultSchema = z21.object({
|
|
4997
|
+
models: z21.array(availableModelSchema),
|
|
4998
|
+
selectedOnlyModels: z21.array(availableModelSchema).default([])
|
|
4999
|
+
}).passthrough();
|
|
5000
|
+
|
|
5001
|
+
// ../provider-bridge-protocol/src/notifications.ts
|
|
5002
|
+
import { z as z23 } from "zod";
|
|
5003
|
+
|
|
5004
|
+
// ../provider-bridge-protocol/src/errors.ts
|
|
5005
|
+
import { z as z22 } from "zod";
|
|
5006
|
+
var BRIDGE_JSON_RPC_ERRORS = {
|
|
5007
|
+
/** Standard JSON-RPC: params failed schema validation. */
|
|
5008
|
+
INVALID_PARAMS: -32602,
|
|
5009
|
+
/** Standard JSON-RPC: method not implemented by this bridge. */
|
|
5010
|
+
METHOD_NOT_FOUND: -32601,
|
|
5011
|
+
/** Generic bridge failure. */
|
|
5012
|
+
BRIDGE_ERROR: -32e3,
|
|
5013
|
+
/** A turn/steer arrived but the session has no active turn. */
|
|
5014
|
+
NO_ACTIVE_TURN: -32001,
|
|
5015
|
+
/** thread/resume for a session the provider can no longer restore. */
|
|
5016
|
+
SESSION_NOT_RESTORABLE: -32002,
|
|
5017
|
+
/** thread/fork with a checkpoint on a bridge that only forks at the tip. */
|
|
5018
|
+
FORK_CHECKPOINT_UNSUPPORTED: -32003
|
|
5019
|
+
};
|
|
5020
|
+
var providerRecoveryHintSchema = z22.object({
|
|
5021
|
+
kind: providerRecoveryKindSchema,
|
|
5022
|
+
message: z22.string().min(1),
|
|
5023
|
+
retryable: z22.boolean()
|
|
5024
|
+
});
|
|
5025
|
+
var bridgeErrorDataSchema = z22.object({ recovery: providerRecoveryHintSchema.optional() }).passthrough();
|
|
5026
|
+
|
|
5027
|
+
// ../provider-bridge-protocol/src/notifications.ts
|
|
5028
|
+
var threadIdentityNotificationSchema = z23.object({
|
|
5029
|
+
threadId: z23.string().min(1),
|
|
5030
|
+
providerThreadId: z23.string().min(1),
|
|
5031
|
+
/** Refines the handshake's `sessionRestore` for this session. */
|
|
5032
|
+
sessionRestorable: z23.boolean().optional()
|
|
5033
|
+
}).passthrough();
|
|
5034
|
+
var sessionReplacedNotificationSchema = z23.object({
|
|
5035
|
+
threadId: z23.string().min(1),
|
|
5036
|
+
/** Identity of the replacement session (may equal the old identity). */
|
|
5037
|
+
providerThreadId: z23.string().min(1).nullable(),
|
|
5038
|
+
/** Human-readable cause, shown in the timeline. */
|
|
5039
|
+
reason: z23.string().min(1),
|
|
5040
|
+
/** True when provider-side context did not survive the replacement. */
|
|
5041
|
+
contextLost: z23.boolean().default(false)
|
|
5042
|
+
}).passthrough();
|
|
5043
|
+
var providerRawNotificationSchema = z23.object({
|
|
5044
|
+
threadId: z23.string().min(1).optional(),
|
|
5045
|
+
coverage: z23.enum(["noise", "unknown"]),
|
|
5046
|
+
payload: z23.unknown()
|
|
5047
|
+
}).passthrough();
|
|
5048
|
+
var providerRecoveryNotificationSchema = z23.object({
|
|
5049
|
+
threadId: z23.string().min(1).optional(),
|
|
5050
|
+
...providerRecoveryHintSchema.shape
|
|
5051
|
+
}).passthrough();
|
|
5052
|
+
var errorNotificationSchema = z23.object({
|
|
5053
|
+
threadId: z23.string().min(1).optional(),
|
|
5054
|
+
message: z23.string().min(1)
|
|
5055
|
+
}).passthrough();
|
|
5056
|
+
|
|
5057
|
+
// ../provider-bridge-protocol/src/bridge-requests.ts
|
|
5058
|
+
import { z as z24 } from "zod";
|
|
5059
|
+
var toolCallRequestParamsSchema = z24.object({
|
|
5060
|
+
providerThreadId: z24.string().min(1),
|
|
5061
|
+
threadId: z24.string().min(1).optional(),
|
|
5062
|
+
turnId: z24.union([z24.string().min(1), z24.null()]),
|
|
5063
|
+
callId: z24.string().min(1),
|
|
5064
|
+
tool: z24.string().min(1),
|
|
5065
|
+
arguments: z24.unknown()
|
|
5066
|
+
}).passthrough();
|
|
5067
|
+
var toolCallResultSchema = z24.object({
|
|
5068
|
+
success: z24.boolean(),
|
|
5069
|
+
contentItems: z24.array(
|
|
5070
|
+
z24.discriminatedUnion("type", [
|
|
5071
|
+
z24.object({ type: z24.literal("inputText"), text: z24.string() }),
|
|
5072
|
+
z24.object({
|
|
5073
|
+
type: z24.literal("inputImage"),
|
|
5074
|
+
imageUrl: z24.string().min(1)
|
|
5075
|
+
})
|
|
5076
|
+
])
|
|
5077
|
+
)
|
|
5078
|
+
}).passthrough();
|
|
5079
|
+
var interactionRequestParamsSchema = z24.object({
|
|
5080
|
+
providerThreadId: z24.string().min(1),
|
|
5081
|
+
threadId: z24.string().min(1).optional(),
|
|
5082
|
+
turnId: z24.union([z24.string().min(1), z24.null()]),
|
|
5083
|
+
payload: pendingInteractionPayloadSchema,
|
|
5084
|
+
/**
|
|
5085
|
+
* The request's turn id and approval-subject item ids are in the
|
|
5086
|
+
* provider's native id space (a `thread/delta` bridge holds no bb ids):
|
|
5087
|
+
* the runtime adapter translates them through the delta assembler's maps
|
|
5088
|
+
* before the interaction reaches the app. Omission means the ids are
|
|
5089
|
+
* already app-visible (bridges whose approval subjects never referenced
|
|
5090
|
+
* timeline ids — ACP's approval ids never matched timeline ids).
|
|
5091
|
+
*/
|
|
5092
|
+
providerNativeIds: z24.boolean().optional()
|
|
5093
|
+
}).passthrough();
|
|
5094
|
+
|
|
5095
|
+
// ../provider-bridge-protocol/src/thread-event-grammar.ts
|
|
5096
|
+
var ITEM_STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
5097
|
+
"item/agentMessage/delta",
|
|
5098
|
+
"item/plan/delta",
|
|
5099
|
+
"item/commandExecution/outputDelta",
|
|
5100
|
+
"item/fileChange/outputDelta",
|
|
5101
|
+
"item/reasoning/summaryTextDelta",
|
|
5102
|
+
"item/reasoning/textDelta",
|
|
5103
|
+
"item/mcpToolCall/progress",
|
|
5104
|
+
"item/toolCall/progress"
|
|
5105
|
+
]);
|
|
5106
|
+
var MAX_ITEM_IDS_PER_THREAD = 512;
|
|
5107
|
+
var THREAD_EVENT_GRAMMAR_RULES = {
|
|
5108
|
+
itemOpensBeforeDelta: "item/opens-before-delta",
|
|
5109
|
+
itemSettlesOnce: "item/settles-once",
|
|
5110
|
+
turnStartsOnce: "turn/starts-once",
|
|
5111
|
+
turnSettlesOnce: "turn/settles-once",
|
|
5112
|
+
turnKnown: "turn/known"
|
|
5113
|
+
};
|
|
5114
|
+
var OK = { kind: "ok" };
|
|
5115
|
+
var ThreadEventGrammar = class {
|
|
5116
|
+
#byThreadId = /* @__PURE__ */ new Map();
|
|
5117
|
+
clear() {
|
|
5118
|
+
this.#byThreadId.clear();
|
|
5119
|
+
}
|
|
5120
|
+
clearThread(threadId) {
|
|
5121
|
+
this.#byThreadId.delete(threadId);
|
|
5122
|
+
}
|
|
5123
|
+
observe(event) {
|
|
5124
|
+
const state = this.#stateFor(event.threadId);
|
|
5125
|
+
switch (event.type) {
|
|
5126
|
+
case "turn/started": {
|
|
5127
|
+
const turnId = turnIdOf(event);
|
|
5128
|
+
if (turnId === void 0) {
|
|
5129
|
+
return OK;
|
|
4868
5130
|
}
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
});
|
|
4884
|
-
return;
|
|
4885
|
-
}
|
|
4886
|
-
case "thread.identity": {
|
|
4887
|
-
events.push({
|
|
4888
|
-
type: "thread/identity",
|
|
4889
|
-
threadId: UNSTAMPED_THREAD_ID,
|
|
4890
|
-
providerThreadId: delta.providerThreadId,
|
|
4891
|
-
scope: threadScope()
|
|
4892
|
-
});
|
|
4893
|
-
return;
|
|
4894
|
-
}
|
|
4895
|
-
case "thread.name": {
|
|
4896
|
-
events.push({
|
|
4897
|
-
type: "thread/name/updated",
|
|
4898
|
-
threadId: UNSTAMPED_THREAD_ID,
|
|
4899
|
-
providerThreadId: "",
|
|
4900
|
-
scope: threadScope(),
|
|
4901
|
-
threadName: delta.name
|
|
4902
|
-
});
|
|
4903
|
-
return;
|
|
4904
|
-
}
|
|
4905
|
-
case "provider.rateLimits": {
|
|
4906
|
-
events.push({
|
|
4907
|
-
type: "provider/rateLimits/updated",
|
|
4908
|
-
threadId: UNSTAMPED_THREAD_ID,
|
|
4909
|
-
providerThreadId: "",
|
|
4910
|
-
scope: threadScope(),
|
|
4911
|
-
rateLimits: delta.rateLimits
|
|
4912
|
-
});
|
|
4913
|
-
return;
|
|
4914
|
-
}
|
|
4915
|
-
case "extension.state": {
|
|
4916
|
-
events.push({
|
|
4917
|
-
type: "thread/extensionState/updated",
|
|
4918
|
-
threadId: UNSTAMPED_THREAD_ID,
|
|
4919
|
-
providerThreadId: "",
|
|
4920
|
-
scope: threadScope(),
|
|
4921
|
-
kind: delta.extensionKind,
|
|
4922
|
-
payload: delta.payload
|
|
4923
|
-
});
|
|
4924
|
-
return;
|
|
4925
|
-
}
|
|
4926
|
-
case "session.reset": {
|
|
4927
|
-
return;
|
|
5131
|
+
if (state.completedTurnIds.has(turnId)) {
|
|
5132
|
+
return violation(
|
|
5133
|
+
THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
|
|
5134
|
+
`turn/started for turn "${turnId}", which already completed`
|
|
5135
|
+
);
|
|
5136
|
+
}
|
|
5137
|
+
if (state.startedTurnIds.has(turnId)) {
|
|
5138
|
+
return violation(
|
|
5139
|
+
THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
|
|
5140
|
+
`turn/started for turn "${turnId}", which is already open`
|
|
5141
|
+
);
|
|
5142
|
+
}
|
|
5143
|
+
state.startedTurnIds.add(turnId);
|
|
5144
|
+
return OK;
|
|
4928
5145
|
}
|
|
4929
|
-
case "
|
|
4930
|
-
const turnId =
|
|
5146
|
+
case "turn/completed": {
|
|
5147
|
+
const turnId = turnIdOf(event);
|
|
4931
5148
|
if (turnId === void 0) {
|
|
4932
|
-
return;
|
|
5149
|
+
return OK;
|
|
4933
5150
|
}
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
const streamed = open.text.length > 0 || open.summaryText.length > 0;
|
|
4939
|
-
const item = (streamed ? settleTextItem(open, void 0, void 0) : void 0) ?? completeStartedItem(
|
|
4940
|
-
open.item,
|
|
4941
|
-
{ status: "interrupted" },
|
|
4942
|
-
void 0
|
|
5151
|
+
if (state.completedTurnIds.has(turnId)) {
|
|
5152
|
+
return violation(
|
|
5153
|
+
THREAD_EVENT_GRAMMAR_RULES.turnSettlesOnce,
|
|
5154
|
+
`turn/completed for turn "${turnId}", which already completed`
|
|
4943
5155
|
);
|
|
4944
|
-
events.push({
|
|
4945
|
-
type: "item/completed",
|
|
4946
|
-
threadId: UNSTAMPED_THREAD_ID,
|
|
4947
|
-
providerThreadId: "",
|
|
4948
|
-
scope: turnScope(turnId),
|
|
4949
|
-
item
|
|
4950
|
-
});
|
|
4951
5156
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
status: "interrupted"
|
|
4958
|
-
});
|
|
4959
|
-
finishTurn(state);
|
|
4960
|
-
return;
|
|
4961
|
-
}
|
|
4962
|
-
}
|
|
4963
|
-
}
|
|
4964
|
-
return {
|
|
4965
|
-
assemble(args) {
|
|
4966
|
-
const events = [];
|
|
4967
|
-
const sink = {
|
|
4968
|
-
push: (...newEvents) => {
|
|
4969
|
-
for (const event of newEvents) {
|
|
4970
|
-
const textDelta = textDeltaFlushMs > 0 ? asTextDeltaEvent(event) : void 0;
|
|
4971
|
-
const state = states.get(args.threadId);
|
|
4972
|
-
if (textDelta !== void 0 && state !== void 0) {
|
|
4973
|
-
bufferTextDelta(state, textDelta, events);
|
|
4974
|
-
continue;
|
|
4975
|
-
}
|
|
4976
|
-
if (state !== void 0) {
|
|
4977
|
-
flushPendingText(state, events);
|
|
4978
|
-
}
|
|
4979
|
-
events.push(event);
|
|
4980
|
-
}
|
|
5157
|
+
if (!state.startedTurnIds.has(turnId)) {
|
|
5158
|
+
return violation(
|
|
5159
|
+
THREAD_EVENT_GRAMMAR_RULES.turnKnown,
|
|
5160
|
+
`turn/completed for turn "${turnId}", which never started`
|
|
5161
|
+
);
|
|
4981
5162
|
}
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
5163
|
+
state.startedTurnIds.delete(turnId);
|
|
5164
|
+
state.completedTurnIds.add(turnId);
|
|
5165
|
+
return OK;
|
|
5166
|
+
}
|
|
5167
|
+
case "item/started": {
|
|
5168
|
+
state.openItemIds.add(event.item.id);
|
|
5169
|
+
state.settledItemIds.delete(event.item.id);
|
|
5170
|
+
trim(state.openItemIds);
|
|
5171
|
+
return OK;
|
|
5172
|
+
}
|
|
5173
|
+
case "item/completed":
|
|
5174
|
+
case "item/backgroundTask/completed":
|
|
5175
|
+
case "item/delegation/completed": {
|
|
5176
|
+
const itemId = event.item.id;
|
|
5177
|
+
if (state.settledItemIds.has(itemId)) {
|
|
5178
|
+
return violation(
|
|
5179
|
+
THREAD_EVENT_GRAMMAR_RULES.itemSettlesOnce,
|
|
5180
|
+
`${event.type} for item "${itemId}", which already settled`
|
|
5181
|
+
);
|
|
4991
5182
|
}
|
|
4992
|
-
|
|
5183
|
+
state.openItemIds.delete(itemId);
|
|
5184
|
+
state.settledItemIds.add(itemId);
|
|
5185
|
+
trim(state.settledItemIds);
|
|
5186
|
+
return OK;
|
|
4993
5187
|
}
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
continue;
|
|
5188
|
+
case "item/backgroundTask/progress":
|
|
5189
|
+
case "item/delegation/progress": {
|
|
5190
|
+
return this.#checkOpenItem(state, event.type, event.item.id);
|
|
5191
|
+
}
|
|
5192
|
+
default: {
|
|
5193
|
+
if (!ITEM_STREAMING_EVENT_TYPES.has(event.type)) {
|
|
5194
|
+
return OK;
|
|
5002
5195
|
}
|
|
5003
|
-
if (
|
|
5004
|
-
|
|
5005
|
-
if (state !== void 0) {
|
|
5006
|
-
flushPendingText(state, events);
|
|
5007
|
-
}
|
|
5196
|
+
if (!("itemId" in event) || typeof event.itemId !== "string") {
|
|
5197
|
+
return OK;
|
|
5008
5198
|
}
|
|
5009
|
-
|
|
5199
|
+
return this.#checkOpenItem(state, event.type, event.itemId);
|
|
5010
5200
|
}
|
|
5011
|
-
return events;
|
|
5012
|
-
},
|
|
5013
|
-
getBbItemId(threadId, providerItemId) {
|
|
5014
|
-
return states.get(threadId)?.bbItemIdByProviderItemId.get(providerItemId);
|
|
5015
|
-
},
|
|
5016
|
-
getProviderItemId(threadId, bbItemId) {
|
|
5017
|
-
return states.get(threadId)?.providerItemIdByBbItemId.get(bbItemId);
|
|
5018
|
-
},
|
|
5019
|
-
getBbTurnId(threadId, providerTurnId) {
|
|
5020
|
-
return states.get(threadId)?.bbTurnIdByProviderTurnId.get(providerTurnId);
|
|
5021
|
-
},
|
|
5022
|
-
getProviderTurnId(threadId, bbTurnId) {
|
|
5023
|
-
return states.get(threadId)?.providerTurnIdByBbTurnId.get(bbTurnId);
|
|
5024
|
-
},
|
|
5025
|
-
getOpenTurnId(threadId) {
|
|
5026
|
-
return states.get(threadId)?.currentTurnId;
|
|
5027
5201
|
}
|
|
5028
|
-
}
|
|
5202
|
+
}
|
|
5203
|
+
#checkOpenItem(state, eventType, itemId) {
|
|
5204
|
+
if (state.openItemIds.has(itemId)) {
|
|
5205
|
+
return OK;
|
|
5206
|
+
}
|
|
5207
|
+
return violation(
|
|
5208
|
+
THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta,
|
|
5209
|
+
`${eventType} for item "${itemId}" arrived before item/started`
|
|
5210
|
+
);
|
|
5211
|
+
}
|
|
5212
|
+
#stateFor(threadId) {
|
|
5213
|
+
const existing = this.#byThreadId.get(threadId);
|
|
5214
|
+
if (existing !== void 0) {
|
|
5215
|
+
return existing;
|
|
5216
|
+
}
|
|
5217
|
+
const created = {
|
|
5218
|
+
openItemIds: /* @__PURE__ */ new Set(),
|
|
5219
|
+
settledItemIds: /* @__PURE__ */ new Set(),
|
|
5220
|
+
startedTurnIds: /* @__PURE__ */ new Set(),
|
|
5221
|
+
completedTurnIds: /* @__PURE__ */ new Set()
|
|
5222
|
+
};
|
|
5223
|
+
this.#byThreadId.set(threadId, created);
|
|
5224
|
+
return created;
|
|
5225
|
+
}
|
|
5226
|
+
};
|
|
5227
|
+
function violation(rule, reason) {
|
|
5228
|
+
return { kind: "violation", rule, reason };
|
|
5029
5229
|
}
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
function
|
|
5034
|
-
|
|
5230
|
+
function turnIdOf(event) {
|
|
5231
|
+
return "scope" in event ? getThreadEventScopeTurnId(event.scope) : void 0;
|
|
5232
|
+
}
|
|
5233
|
+
function trim(itemIds) {
|
|
5234
|
+
while (itemIds.size > MAX_ITEM_IDS_PER_THREAD) {
|
|
5235
|
+
const oldest = itemIds.values().next();
|
|
5236
|
+
if (oldest.done === true) {
|
|
5237
|
+
return;
|
|
5238
|
+
}
|
|
5239
|
+
itemIds.delete(oldest.value);
|
|
5240
|
+
}
|
|
5035
5241
|
}
|
|
5036
5242
|
|
|
5037
5243
|
// ../provider-bridge-protocol/src/conformance/scenarios.ts
|
|
5038
|
-
var
|
|
5244
|
+
var IDENTITY_RESULT_SHAPE = "{ providerThreadId, sessionRestorable? }";
|
|
5245
|
+
function identityProblem(parsed, result2) {
|
|
5246
|
+
return `the result must be ${IDENTITY_RESULT_SHAPE} \u2014 the runtime adopts no session without providerThreadId on the result (a thread/identity notification does not substitute for it); issues: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")} (got ${JSON.stringify(result2)})`;
|
|
5247
|
+
}
|
|
5039
5248
|
function pass(id, title) {
|
|
5040
5249
|
return { id, title, status: "pass", detail: "" };
|
|
5041
5250
|
}
|
|
@@ -5055,17 +5264,12 @@ function defaultOptions(fixture) {
|
|
|
5055
5264
|
}
|
|
5056
5265
|
function threadEvents(context, threadId) {
|
|
5057
5266
|
context.client.drainIntoLog();
|
|
5058
|
-
|
|
5059
|
-
for (const message of context.client.notifications(
|
|
5060
|
-
CONFORMANCE_ASSEMBLED_EVENT_METHOD
|
|
5061
|
-
)) {
|
|
5062
|
-
const parsed = assembledEventNotificationSchema.safeParse(message.params);
|
|
5063
|
-
if (parsed.success && parsed.data.threadId === threadId) {
|
|
5064
|
-
events.push(parsed.data.event);
|
|
5065
|
-
}
|
|
5066
|
-
}
|
|
5067
|
-
return events;
|
|
5267
|
+
return context.client.events.filter((entry) => entry.threadId === threadId).map((entry) => entry.event);
|
|
5068
5268
|
}
|
|
5269
|
+
var persistedEventSchema = z25.preprocess(
|
|
5270
|
+
(event) => JSON.parse(JSON.stringify(event)),
|
|
5271
|
+
threadEventSchema
|
|
5272
|
+
);
|
|
5069
5273
|
function errorCode(message) {
|
|
5070
5274
|
const code = message?.error?.code;
|
|
5071
5275
|
return typeof code === "number" ? code : void 0;
|
|
@@ -5074,12 +5278,12 @@ var ITEM_OPENS_BEFORE_DELTA_TITLE = "every item's first event is item/started";
|
|
|
5074
5278
|
function checkItemOpensBeforeDelta(events) {
|
|
5075
5279
|
const grammar = new ThreadEventGrammar();
|
|
5076
5280
|
for (const event of events) {
|
|
5077
|
-
const
|
|
5078
|
-
if (
|
|
5281
|
+
const result2 = grammar.observe(event);
|
|
5282
|
+
if (result2.kind === "violation" && result2.rule === THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta) {
|
|
5079
5283
|
return fail(
|
|
5080
5284
|
THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta,
|
|
5081
5285
|
ITEM_OPENS_BEFORE_DELTA_TITLE,
|
|
5082
|
-
|
|
5286
|
+
result2.reason
|
|
5083
5287
|
);
|
|
5084
5288
|
}
|
|
5085
5289
|
}
|
|
@@ -5092,6 +5296,44 @@ function checkItemOpensBeforeDelta(events) {
|
|
|
5092
5296
|
}
|
|
5093
5297
|
return pass("item/opens-before-delta", ITEM_OPENS_BEFORE_DELTA_TITLE);
|
|
5094
5298
|
}
|
|
5299
|
+
var PRESENTATION_ICONS_DECLARED_ID = "presentation/icon-namespaced-declared";
|
|
5300
|
+
var PRESENTATION_ICONS_DECLARED_TITLE = "every namespaced presentation glyph names one of the plugin's declared icons";
|
|
5301
|
+
function checkPresentationIconsDeclared(events, icons) {
|
|
5302
|
+
const declared = new Set(icons.names);
|
|
5303
|
+
let inspected = 0;
|
|
5304
|
+
for (const event of events) {
|
|
5305
|
+
if (!isThreadEventWithItem(event)) {
|
|
5306
|
+
continue;
|
|
5307
|
+
}
|
|
5308
|
+
if (event.item.type === "toolCall" && event.item.server === "bb") {
|
|
5309
|
+
continue;
|
|
5310
|
+
}
|
|
5311
|
+
const glyph = "presentation" in event.item ? event.item.presentation?.icon.glyph : void 0;
|
|
5312
|
+
if (glyph === void 0) {
|
|
5313
|
+
continue;
|
|
5314
|
+
}
|
|
5315
|
+
inspected += 1;
|
|
5316
|
+
const parsed = parseNamespacedGlyph(glyph);
|
|
5317
|
+
if (parsed === null) {
|
|
5318
|
+
continue;
|
|
5319
|
+
}
|
|
5320
|
+
if (parsed.pluginId !== icons.pluginId || !declared.has(parsed.name)) {
|
|
5321
|
+
return fail(
|
|
5322
|
+
PRESENTATION_ICONS_DECLARED_ID,
|
|
5323
|
+
PRESENTATION_ICONS_DECLARED_TITLE,
|
|
5324
|
+
`${event.type} ${event.item.type} "${event.item.id}" names presentation.icon "${glyph}", which is not an icon declared by plugin "${icons.pluginId}" (declared: ${icons.names.length === 0 ? "none" : icons.names.join(", ")}); the server would persist it as provider/unhandled`
|
|
5325
|
+
);
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
if (inspected === 0) {
|
|
5329
|
+
return skipped(
|
|
5330
|
+
PRESENTATION_ICONS_DECLARED_ID,
|
|
5331
|
+
PRESENTATION_ICONS_DECLARED_TITLE,
|
|
5332
|
+
"no item carried a presentation to inspect"
|
|
5333
|
+
);
|
|
5334
|
+
}
|
|
5335
|
+
return pass(PRESENTATION_ICONS_DECLARED_ID, PRESENTATION_ICONS_DECLARED_TITLE);
|
|
5336
|
+
}
|
|
5095
5337
|
async function runRpcHygieneScenarios(client) {
|
|
5096
5338
|
const results = [];
|
|
5097
5339
|
let unknownMethodsAnswered = false;
|
|
@@ -5200,29 +5442,23 @@ async function runHandshakeScenario(client) {
|
|
|
5200
5442
|
});
|
|
5201
5443
|
const response = await client.waitForResponse(id);
|
|
5202
5444
|
const title = "initialize answers a versioned handshake with capabilities";
|
|
5445
|
+
const failed = (detail) => ({
|
|
5446
|
+
results: [fail("handshake/initialize", title, detail)],
|
|
5447
|
+
capabilities: null
|
|
5448
|
+
});
|
|
5203
5449
|
if (response === null) {
|
|
5204
|
-
return
|
|
5450
|
+
return failed("no response");
|
|
5205
5451
|
}
|
|
5206
5452
|
const parsed = initializeResultSchema.safeParse(response.result);
|
|
5207
5453
|
if (!parsed.success) {
|
|
5208
|
-
return
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
title,
|
|
5212
|
-
`result did not parse: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(
|
|
5213
|
-
"; "
|
|
5214
|
-
)} (got ${JSON.stringify(response.result ?? response.error)})`
|
|
5215
|
-
)
|
|
5216
|
-
];
|
|
5454
|
+
return failed(
|
|
5455
|
+
`result did not parse: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")} (got ${JSON.stringify(response.result ?? response.error)})`
|
|
5456
|
+
);
|
|
5217
5457
|
}
|
|
5218
5458
|
if (parsed.data.protocolVersion !== PROVIDER_BRIDGE_PROTOCOL_VERSION) {
|
|
5219
|
-
return
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
title,
|
|
5223
|
-
`bridge answered protocol version ${parsed.data.protocolVersion}; this kit (and the runtime) require ${PROVIDER_BRIDGE_PROTOCOL_VERSION}`
|
|
5224
|
-
)
|
|
5225
|
-
];
|
|
5459
|
+
return failed(
|
|
5460
|
+
`bridge answered protocol version ${parsed.data.protocolVersion}; this kit (and the runtime) require ${PROVIDER_BRIDGE_PROTOCOL_VERSION}`
|
|
5461
|
+
);
|
|
5226
5462
|
}
|
|
5227
5463
|
const [bridgeMin, bridgeMax] = parsed.data.capabilities.grammarVersions;
|
|
5228
5464
|
if (negotiateGrammarVersion(
|
|
@@ -5230,15 +5466,53 @@ async function runHandshakeScenario(client) {
|
|
|
5230
5466
|
parsed.data.capabilities.grammarVersions
|
|
5231
5467
|
) === null) {
|
|
5232
5468
|
const [runtimeMin, runtimeMax] = ASSEMBLER_GRAMMAR_VERSIONS;
|
|
5469
|
+
return failed(
|
|
5470
|
+
`bridge reported grammarVersions [${bridgeMin}, ${bridgeMax}]; the runtime's assembler speaks [${runtimeMin}, ${runtimeMax}], so the handshake would be refused`
|
|
5471
|
+
);
|
|
5472
|
+
}
|
|
5473
|
+
return {
|
|
5474
|
+
results: [
|
|
5475
|
+
pass("handshake/initialize", title),
|
|
5476
|
+
...await runSkillsConfigureDeclaredScenario(
|
|
5477
|
+
client,
|
|
5478
|
+
parsed.data.capabilities.skills.configure
|
|
5479
|
+
)
|
|
5480
|
+
],
|
|
5481
|
+
capabilities: parsed.data.capabilities
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
5484
|
+
var SKILLS_CONFIGURE_DECLARED_ID = "skills/configure-declared";
|
|
5485
|
+
var SKILLS_CONFIGURE_DECLARED_TITLE = "skills/configure is handled iff the handshake declares skills.configure";
|
|
5486
|
+
async function runSkillsConfigureDeclaredScenario(client, declared) {
|
|
5487
|
+
const id = client.request(BRIDGE_REQUEST_METHODS.skillsConfigure, {
|
|
5488
|
+
roots: []
|
|
5489
|
+
});
|
|
5490
|
+
const response = await client.waitForResponse(id);
|
|
5491
|
+
if (response === null) {
|
|
5233
5492
|
return [
|
|
5234
5493
|
fail(
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5494
|
+
SKILLS_CONFIGURE_DECLARED_ID,
|
|
5495
|
+
SKILLS_CONFIGURE_DECLARED_TITLE,
|
|
5496
|
+
"skills/configure was not answered"
|
|
5497
|
+
)
|
|
5498
|
+
];
|
|
5499
|
+
}
|
|
5500
|
+
if (declared) {
|
|
5501
|
+
return response.error === void 0 ? [pass(SKILLS_CONFIGURE_DECLARED_ID, SKILLS_CONFIGURE_DECLARED_TITLE)] : [
|
|
5502
|
+
fail(
|
|
5503
|
+
SKILLS_CONFIGURE_DECLARED_ID,
|
|
5504
|
+
SKILLS_CONFIGURE_DECLARED_TITLE,
|
|
5505
|
+
`the handshake declares skills.configure but the request failed: ${JSON.stringify(response.error)}`
|
|
5238
5506
|
)
|
|
5239
5507
|
];
|
|
5240
5508
|
}
|
|
5241
|
-
return [pass(
|
|
5509
|
+
return errorCode(response) === BRIDGE_JSON_RPC_ERRORS.METHOD_NOT_FOUND ? [pass(SKILLS_CONFIGURE_DECLARED_ID, SKILLS_CONFIGURE_DECLARED_TITLE)] : [
|
|
5510
|
+
fail(
|
|
5511
|
+
SKILLS_CONFIGURE_DECLARED_ID,
|
|
5512
|
+
SKILLS_CONFIGURE_DECLARED_TITLE,
|
|
5513
|
+
`the handshake does not declare skills.configure, yet the bridge answered the request with ${JSON.stringify(response.error ?? response.result)}; declare it (the runtime never sends an undeclared request)`
|
|
5514
|
+
)
|
|
5515
|
+
];
|
|
5242
5516
|
}
|
|
5243
5517
|
async function runSessionLifecycleScenarios(context) {
|
|
5244
5518
|
const { client, fixture } = context;
|
|
@@ -5270,7 +5544,7 @@ async function runSessionLifecycleScenarios(context) {
|
|
|
5270
5544
|
fail(
|
|
5271
5545
|
"session/start-identity",
|
|
5272
5546
|
title,
|
|
5273
|
-
|
|
5547
|
+
identityProblem(parsed, response.result)
|
|
5274
5548
|
)
|
|
5275
5549
|
);
|
|
5276
5550
|
} else {
|
|
@@ -5331,20 +5605,27 @@ async function runSessionLifecycleScenarios(context) {
|
|
|
5331
5605
|
}
|
|
5332
5606
|
{
|
|
5333
5607
|
client.drainIntoLog();
|
|
5334
|
-
const
|
|
5335
|
-
|
|
5336
|
-
(message) => !assembledEventNotificationSchema.safeParse(message.params).success
|
|
5608
|
+
const invalid = client.events.filter(
|
|
5609
|
+
(entry) => !persistedEventSchema.safeParse(entry.event).success
|
|
5337
5610
|
);
|
|
5338
5611
|
const title2 = "every assembled event is a valid ThreadEvent";
|
|
5339
5612
|
results.push(
|
|
5340
5613
|
invalid.length === 0 ? pass("events/schema-valid", title2) : fail(
|
|
5341
5614
|
"events/schema-valid",
|
|
5342
5615
|
title2,
|
|
5343
|
-
`${invalid.length} assembled event
|
|
5616
|
+
`${invalid.length} assembled event(s) failed validation; first: ${JSON.stringify(invalid[0]?.event).slice(0, 400)}`
|
|
5344
5617
|
)
|
|
5345
5618
|
);
|
|
5346
5619
|
}
|
|
5347
5620
|
results.push(checkItemOpensBeforeDelta(threadEvents(context, threadId)));
|
|
5621
|
+
if (fixture.icons !== void 0) {
|
|
5622
|
+
results.push(
|
|
5623
|
+
checkPresentationIconsDeclared(
|
|
5624
|
+
threadEvents(context, threadId),
|
|
5625
|
+
fixture.icons
|
|
5626
|
+
)
|
|
5627
|
+
);
|
|
5628
|
+
}
|
|
5348
5629
|
}
|
|
5349
5630
|
if (context.providerThreadId === void 0) {
|
|
5350
5631
|
results.push(
|
|
@@ -5397,13 +5678,11 @@ async function runSessionLifecycleScenarios(context) {
|
|
|
5397
5678
|
results.push(pass("stop/release-not-interrupted", title));
|
|
5398
5679
|
}
|
|
5399
5680
|
}
|
|
5681
|
+
const uniquenessTitle = "turn and item ids never repeat across a resume";
|
|
5400
5682
|
if (context.providerThreadId === void 0) {
|
|
5401
5683
|
results.push(
|
|
5402
|
-
skipped(
|
|
5403
|
-
|
|
5404
|
-
"turn and item ids never repeat across a resume",
|
|
5405
|
-
startSkipDetail
|
|
5406
|
-
)
|
|
5684
|
+
skipped(RESUME_IDENTITY_ID, RESUME_IDENTITY_TITLE, startSkipDetail),
|
|
5685
|
+
skipped("session/resume-id-uniqueness", uniquenessTitle, startSkipDetail)
|
|
5407
5686
|
);
|
|
5408
5687
|
} else {
|
|
5409
5688
|
const resumeId = client.request(BRIDGE_REQUEST_METHODS.threadResume, {
|
|
@@ -5414,16 +5693,30 @@ async function runSessionLifecycleScenarios(context) {
|
|
|
5414
5693
|
instructionMode: "append"
|
|
5415
5694
|
});
|
|
5416
5695
|
const resumeResponse = await client.waitForResponse(resumeId);
|
|
5417
|
-
const title =
|
|
5418
|
-
|
|
5696
|
+
const title = uniquenessTitle;
|
|
5697
|
+
const resumed = resumeResponse === null || resumeResponse.error !== void 0 ? null : threadIdentityResultSchema.safeParse(resumeResponse.result);
|
|
5698
|
+
if (resumed === null) {
|
|
5699
|
+
const detail = resumeResponse === null ? "thread/resume was not answered" : `thread/resume failed: ${JSON.stringify(resumeResponse.error)}`;
|
|
5419
5700
|
results.push(
|
|
5701
|
+
skipped(RESUME_IDENTITY_ID, RESUME_IDENTITY_TITLE, detail),
|
|
5702
|
+
skipped("session/resume-id-uniqueness", title, detail)
|
|
5703
|
+
);
|
|
5704
|
+
} else if (!resumed.success) {
|
|
5705
|
+
results.push(
|
|
5706
|
+
fail(
|
|
5707
|
+
RESUME_IDENTITY_ID,
|
|
5708
|
+
RESUME_IDENTITY_TITLE,
|
|
5709
|
+
identityProblem(resumed, resumeResponse?.result)
|
|
5710
|
+
),
|
|
5420
5711
|
skipped(
|
|
5421
5712
|
"session/resume-id-uniqueness",
|
|
5422
5713
|
title,
|
|
5423
|
-
|
|
5714
|
+
"prerequisite session/resume-identity failed"
|
|
5424
5715
|
)
|
|
5425
5716
|
);
|
|
5426
5717
|
} else {
|
|
5718
|
+
context.providerThreadId = resumed.data.providerThreadId;
|
|
5719
|
+
results.push(pass(RESUME_IDENTITY_ID, RESUME_IDENTITY_TITLE));
|
|
5427
5720
|
const turnId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
|
|
5428
5721
|
threadId,
|
|
5429
5722
|
providerThreadId: context.providerThreadId,
|
|
@@ -5486,9 +5779,392 @@ async function runSessionLifecycleScenarios(context) {
|
|
|
5486
5779
|
}
|
|
5487
5780
|
}
|
|
5488
5781
|
}
|
|
5782
|
+
results.push(...await runForkIdentityScenario(context, threadId));
|
|
5489
5783
|
results.push(...await runZeroWorkTurnScenario(context, threadId));
|
|
5784
|
+
results.push(...await runArchivedResumeRecoveryScenario(context, threadId));
|
|
5785
|
+
results.push(...await runThreadsIndependentScenario(context, threadId));
|
|
5786
|
+
results.push(...await runInterruptStopScenario(context, threadId));
|
|
5787
|
+
if (context.providerThreadId !== void 0) {
|
|
5788
|
+
const releaseId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
|
|
5789
|
+
threadId,
|
|
5790
|
+
providerThreadId: context.providerThreadId,
|
|
5791
|
+
intent: "release",
|
|
5792
|
+
activeTurnId: null
|
|
5793
|
+
});
|
|
5794
|
+
await client.waitForResponse(releaseId);
|
|
5795
|
+
}
|
|
5490
5796
|
return results;
|
|
5491
5797
|
}
|
|
5798
|
+
var RESUME_IDENTITY_ID = "session/resume-identity";
|
|
5799
|
+
var RESUME_IDENTITY_TITLE = "thread/resume returns a provider thread identity";
|
|
5800
|
+
var FORK_IDENTITY_ID = "session/fork-identity";
|
|
5801
|
+
var FORK_IDENTITY_TITLE = "thread/fork returns a provider thread identity for the forked session";
|
|
5802
|
+
async function runForkIdentityScenario(context, threadId) {
|
|
5803
|
+
const { client, fixture } = context;
|
|
5804
|
+
if (context.fork === "none") {
|
|
5805
|
+
return [];
|
|
5806
|
+
}
|
|
5807
|
+
if (context.providerThreadId === void 0) {
|
|
5808
|
+
return [
|
|
5809
|
+
skipped(
|
|
5810
|
+
FORK_IDENTITY_ID,
|
|
5811
|
+
FORK_IDENTITY_TITLE,
|
|
5812
|
+
"prerequisite session/start-identity failed"
|
|
5813
|
+
)
|
|
5814
|
+
];
|
|
5815
|
+
}
|
|
5816
|
+
const forkThreadId = `${threadId}_fork`;
|
|
5817
|
+
const forkId = client.request(BRIDGE_REQUEST_METHODS.threadFork, {
|
|
5818
|
+
threadId: forkThreadId,
|
|
5819
|
+
cwd: fixture.cwd,
|
|
5820
|
+
sourceProviderThreadId: context.providerThreadId,
|
|
5821
|
+
options: defaultOptions(fixture),
|
|
5822
|
+
instructionMode: "append"
|
|
5823
|
+
});
|
|
5824
|
+
const response = await client.waitForResponse(forkId);
|
|
5825
|
+
if (response === null) {
|
|
5826
|
+
return [
|
|
5827
|
+
fail(FORK_IDENTITY_ID, FORK_IDENTITY_TITLE, "thread/fork was not answered")
|
|
5828
|
+
];
|
|
5829
|
+
}
|
|
5830
|
+
if (response.error !== void 0) {
|
|
5831
|
+
return [
|
|
5832
|
+
fail(
|
|
5833
|
+
FORK_IDENTITY_ID,
|
|
5834
|
+
FORK_IDENTITY_TITLE,
|
|
5835
|
+
`the handshake declares fork "${context.fork}", yet forking the lifecycle session at its tip failed: ${JSON.stringify(response.error)}`
|
|
5836
|
+
)
|
|
5837
|
+
];
|
|
5838
|
+
}
|
|
5839
|
+
const parsed = threadIdentityResultSchema.safeParse(response.result);
|
|
5840
|
+
if (!parsed.success) {
|
|
5841
|
+
return [
|
|
5842
|
+
fail(
|
|
5843
|
+
FORK_IDENTITY_ID,
|
|
5844
|
+
FORK_IDENTITY_TITLE,
|
|
5845
|
+
identityProblem(parsed, response.result)
|
|
5846
|
+
)
|
|
5847
|
+
];
|
|
5848
|
+
}
|
|
5849
|
+
const releaseId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
|
|
5850
|
+
threadId: forkThreadId,
|
|
5851
|
+
providerThreadId: parsed.data.providerThreadId,
|
|
5852
|
+
intent: "release",
|
|
5853
|
+
activeTurnId: null
|
|
5854
|
+
});
|
|
5855
|
+
await client.waitForResponse(releaseId);
|
|
5856
|
+
return [pass(FORK_IDENTITY_ID, FORK_IDENTITY_TITLE)];
|
|
5857
|
+
}
|
|
5858
|
+
var THREADS_INDEPENDENT_ID = "session/threads-independent";
|
|
5859
|
+
var THREADS_INDEPENDENT_TITLE = "requests on different threads are independent";
|
|
5860
|
+
async function runThreadsIndependentScenario(context, threadId) {
|
|
5861
|
+
const { client, fixture } = context;
|
|
5862
|
+
const interruptiblePromptInput = fixture.interruptiblePromptInput;
|
|
5863
|
+
if (interruptiblePromptInput === void 0) {
|
|
5864
|
+
return [];
|
|
5865
|
+
}
|
|
5866
|
+
if (context.providerThreadId === void 0) {
|
|
5867
|
+
return [
|
|
5868
|
+
skipped(
|
|
5869
|
+
THREADS_INDEPENDENT_ID,
|
|
5870
|
+
THREADS_INDEPENDENT_TITLE,
|
|
5871
|
+
"prerequisite session/start-identity failed"
|
|
5872
|
+
)
|
|
5873
|
+
];
|
|
5874
|
+
}
|
|
5875
|
+
const startedBefore = threadEvents(context, threadId).filter(
|
|
5876
|
+
(event) => event.type === "turn/started"
|
|
5877
|
+
).length;
|
|
5878
|
+
const holdId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
|
|
5879
|
+
threadId,
|
|
5880
|
+
providerThreadId: context.providerThreadId,
|
|
5881
|
+
input: interruptiblePromptInput,
|
|
5882
|
+
clientRequestId: nextConformanceClientRequestId(),
|
|
5883
|
+
options: defaultOptions(fixture)
|
|
5884
|
+
});
|
|
5885
|
+
const held = await client.waitFor(() => {
|
|
5886
|
+
const starts = threadEvents(context, threadId).filter(
|
|
5887
|
+
(event) => event.type === "turn/started"
|
|
5888
|
+
);
|
|
5889
|
+
return starts.length > startedBefore ? starts[startedBefore] : void 0;
|
|
5890
|
+
});
|
|
5891
|
+
await client.waitForResponse(holdId);
|
|
5892
|
+
if (held === null) {
|
|
5893
|
+
return [
|
|
5894
|
+
fail(
|
|
5895
|
+
THREADS_INDEPENDENT_ID,
|
|
5896
|
+
THREADS_INDEPENDENT_TITLE,
|
|
5897
|
+
"the interruptible prompt never opened a turn"
|
|
5898
|
+
)
|
|
5899
|
+
];
|
|
5900
|
+
}
|
|
5901
|
+
const otherThreadId = `${threadId}_other`;
|
|
5902
|
+
const startId = client.request(BRIDGE_REQUEST_METHODS.threadStart, {
|
|
5903
|
+
threadId: otherThreadId,
|
|
5904
|
+
cwd: fixture.cwd,
|
|
5905
|
+
options: defaultOptions(fixture),
|
|
5906
|
+
instructionMode: "append"
|
|
5907
|
+
});
|
|
5908
|
+
const startResponse = await client.waitForResponse(startId);
|
|
5909
|
+
const startResult = threadIdentityResultSchema.safeParse(
|
|
5910
|
+
startResponse?.result
|
|
5911
|
+
);
|
|
5912
|
+
if (startResponse === null || !startResult.success) {
|
|
5913
|
+
return [
|
|
5914
|
+
fail(
|
|
5915
|
+
THREADS_INDEPENDENT_ID,
|
|
5916
|
+
THREADS_INDEPENDENT_TITLE,
|
|
5917
|
+
`the second thread did not start while the first held a turn: ${JSON.stringify(startResponse?.error ?? startResponse?.result)}`
|
|
5918
|
+
)
|
|
5919
|
+
];
|
|
5920
|
+
}
|
|
5921
|
+
const turnId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
|
|
5922
|
+
threadId: otherThreadId,
|
|
5923
|
+
providerThreadId: startResult.data.providerThreadId,
|
|
5924
|
+
input: fixture.promptInput,
|
|
5925
|
+
clientRequestId: nextConformanceClientRequestId(),
|
|
5926
|
+
options: defaultOptions(fixture)
|
|
5927
|
+
});
|
|
5928
|
+
const completed = await client.waitFor(
|
|
5929
|
+
() => threadEvents(context, otherThreadId).find(
|
|
5930
|
+
(event) => event.type === "turn/completed"
|
|
5931
|
+
)
|
|
5932
|
+
);
|
|
5933
|
+
await client.waitForResponse(turnId);
|
|
5934
|
+
const stopId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
|
|
5935
|
+
threadId: otherThreadId,
|
|
5936
|
+
providerThreadId: startResult.data.providerThreadId,
|
|
5937
|
+
intent: "release",
|
|
5938
|
+
activeTurnId: null
|
|
5939
|
+
});
|
|
5940
|
+
await client.waitForResponse(stopId);
|
|
5941
|
+
if (completed === null) {
|
|
5942
|
+
return [
|
|
5943
|
+
fail(
|
|
5944
|
+
THREADS_INDEPENDENT_ID,
|
|
5945
|
+
THREADS_INDEPENDENT_TITLE,
|
|
5946
|
+
"the second thread's turn never completed while the first thread held a turn"
|
|
5947
|
+
)
|
|
5948
|
+
];
|
|
5949
|
+
}
|
|
5950
|
+
const firstStillOpen = !threadEvents(context, threadId).some(
|
|
5951
|
+
(event) => event.type === "turn/completed" && getThreadEventScopeTurnId(event.scope) === getThreadEventScopeTurnId(held.scope)
|
|
5952
|
+
);
|
|
5953
|
+
if (!firstStillOpen) {
|
|
5954
|
+
return [
|
|
5955
|
+
fail(
|
|
5956
|
+
THREADS_INDEPENDENT_ID,
|
|
5957
|
+
THREADS_INDEPENDENT_TITLE,
|
|
5958
|
+
"the held turn on the first thread settled while the second thread ran"
|
|
5959
|
+
)
|
|
5960
|
+
];
|
|
5961
|
+
}
|
|
5962
|
+
return [pass(THREADS_INDEPENDENT_ID, THREADS_INDEPENDENT_TITLE)];
|
|
5963
|
+
}
|
|
5964
|
+
var INTERRUPT_SETTLES_ID = "stop/interrupt-settles-before-result";
|
|
5965
|
+
var INTERRUPT_SETTLES_TITLE = "thread/stop {interrupt} settles the turn before it is answered";
|
|
5966
|
+
async function runInterruptStopScenario(context, threadId) {
|
|
5967
|
+
const { client, fixture } = context;
|
|
5968
|
+
const interruptiblePromptInput = fixture.interruptiblePromptInput;
|
|
5969
|
+
if (interruptiblePromptInput === void 0) {
|
|
5970
|
+
return [];
|
|
5971
|
+
}
|
|
5972
|
+
if (context.providerThreadId === void 0) {
|
|
5973
|
+
return [
|
|
5974
|
+
skipped(
|
|
5975
|
+
INTERRUPT_SETTLES_ID,
|
|
5976
|
+
INTERRUPT_SETTLES_TITLE,
|
|
5977
|
+
"prerequisite session/start-identity failed"
|
|
5978
|
+
)
|
|
5979
|
+
];
|
|
5980
|
+
}
|
|
5981
|
+
let started = openTurnStart(context, threadId);
|
|
5982
|
+
if (started === void 0) {
|
|
5983
|
+
const startedBefore = threadEvents(context, threadId).filter(
|
|
5984
|
+
(event) => event.type === "turn/started"
|
|
5985
|
+
).length;
|
|
5986
|
+
const turnRequestId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
|
|
5987
|
+
threadId,
|
|
5988
|
+
providerThreadId: context.providerThreadId,
|
|
5989
|
+
input: interruptiblePromptInput,
|
|
5990
|
+
clientRequestId: nextConformanceClientRequestId(),
|
|
5991
|
+
options: defaultOptions(fixture)
|
|
5992
|
+
});
|
|
5993
|
+
started = await client.waitFor(() => {
|
|
5994
|
+
const starts = threadEvents(context, threadId).filter(
|
|
5995
|
+
(event) => event.type === "turn/started"
|
|
5996
|
+
);
|
|
5997
|
+
return starts.length > startedBefore ? starts[startedBefore] : void 0;
|
|
5998
|
+
}) ?? void 0;
|
|
5999
|
+
await client.waitForResponse(turnRequestId);
|
|
6000
|
+
}
|
|
6001
|
+
if (started === void 0 || started.scope.kind !== "turn") {
|
|
6002
|
+
return [
|
|
6003
|
+
fail(
|
|
6004
|
+
INTERRUPT_SETTLES_ID,
|
|
6005
|
+
INTERRUPT_SETTLES_TITLE,
|
|
6006
|
+
"the interruptible prompt never opened a turn"
|
|
6007
|
+
)
|
|
6008
|
+
];
|
|
6009
|
+
}
|
|
6010
|
+
const bbTurnId = started.scope.turnId;
|
|
6011
|
+
const providerTurnId = context.resolveProviderTurnId(threadId, bbTurnId) ?? bbTurnId;
|
|
6012
|
+
const stopId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
|
|
6013
|
+
threadId,
|
|
6014
|
+
providerThreadId: context.providerThreadId,
|
|
6015
|
+
intent: "interrupt",
|
|
6016
|
+
activeTurnId: providerTurnId
|
|
6017
|
+
});
|
|
6018
|
+
const stopResponse = await client.waitForResponse(stopId);
|
|
6019
|
+
if (stopResponse === null) {
|
|
6020
|
+
return [
|
|
6021
|
+
fail(INTERRUPT_SETTLES_ID, INTERRUPT_SETTLES_TITLE, "thread/stop was not answered")
|
|
6022
|
+
];
|
|
6023
|
+
}
|
|
6024
|
+
if (stopResponse.error !== void 0) {
|
|
6025
|
+
return [
|
|
6026
|
+
fail(
|
|
6027
|
+
INTERRUPT_SETTLES_ID,
|
|
6028
|
+
INTERRUPT_SETTLES_TITLE,
|
|
6029
|
+
`thread/stop failed: ${JSON.stringify(stopResponse.error)}`
|
|
6030
|
+
)
|
|
6031
|
+
];
|
|
6032
|
+
}
|
|
6033
|
+
const responseIndex = client.log.indexOf(stopResponse);
|
|
6034
|
+
const completedIndex = client.events.find(
|
|
6035
|
+
(entry) => entry.threadId === threadId && entry.event.type === "turn/completed" && getThreadEventScopeTurnId(entry.event.scope) === bbTurnId
|
|
6036
|
+
)?.logIndex ?? -1;
|
|
6037
|
+
if (completedIndex === -1) {
|
|
6038
|
+
return [
|
|
6039
|
+
fail(
|
|
6040
|
+
INTERRUPT_SETTLES_ID,
|
|
6041
|
+
INTERRUPT_SETTLES_TITLE,
|
|
6042
|
+
"the interrupted turn never reached turn/completed"
|
|
6043
|
+
)
|
|
6044
|
+
];
|
|
6045
|
+
}
|
|
6046
|
+
if (completedIndex > responseIndex) {
|
|
6047
|
+
return [
|
|
6048
|
+
fail(
|
|
6049
|
+
INTERRUPT_SETTLES_ID,
|
|
6050
|
+
INTERRUPT_SETTLES_TITLE,
|
|
6051
|
+
"turn/completed arrived after the thread/stop response; the runtime had already detached the thread"
|
|
6052
|
+
)
|
|
6053
|
+
];
|
|
6054
|
+
}
|
|
6055
|
+
return [pass(INTERRUPT_SETTLES_ID, INTERRUPT_SETTLES_TITLE)];
|
|
6056
|
+
}
|
|
6057
|
+
var SESSION_ARCHIVED_RECOVERY_ID = "recovery/session-archived";
|
|
6058
|
+
var SESSION_ARCHIVED_RECOVERY_TITLE = "resuming an archived session is rejected with a sessionArchived hint";
|
|
6059
|
+
async function runArchivedResumeRecoveryScenario(context, threadId) {
|
|
6060
|
+
const { client, fixture } = context;
|
|
6061
|
+
const providerThreadId = context.providerThreadId;
|
|
6062
|
+
if (providerThreadId === void 0) {
|
|
6063
|
+
return [
|
|
6064
|
+
skipped(
|
|
6065
|
+
SESSION_ARCHIVED_RECOVERY_ID,
|
|
6066
|
+
SESSION_ARCHIVED_RECOVERY_TITLE,
|
|
6067
|
+
"prerequisite session/start-identity failed"
|
|
6068
|
+
)
|
|
6069
|
+
];
|
|
6070
|
+
}
|
|
6071
|
+
const archiveId = client.request(BRIDGE_REQUEST_METHODS.threadArchive, {
|
|
6072
|
+
threadId,
|
|
6073
|
+
providerThreadId
|
|
6074
|
+
});
|
|
6075
|
+
const archiveResponse = await client.waitForResponse(archiveId);
|
|
6076
|
+
if (archiveResponse === null) {
|
|
6077
|
+
return [
|
|
6078
|
+
fail(
|
|
6079
|
+
SESSION_ARCHIVED_RECOVERY_ID,
|
|
6080
|
+
SESSION_ARCHIVED_RECOVERY_TITLE,
|
|
6081
|
+
"thread/archive was not answered"
|
|
6082
|
+
)
|
|
6083
|
+
];
|
|
6084
|
+
}
|
|
6085
|
+
if (archiveResponse.error !== void 0) {
|
|
6086
|
+
return [];
|
|
6087
|
+
}
|
|
6088
|
+
const resumeParams = {
|
|
6089
|
+
threadId,
|
|
6090
|
+
cwd: fixture.cwd,
|
|
6091
|
+
providerThreadId,
|
|
6092
|
+
options: defaultOptions(fixture),
|
|
6093
|
+
instructionMode: "append"
|
|
6094
|
+
};
|
|
6095
|
+
const resumeId = client.request(
|
|
6096
|
+
BRIDGE_REQUEST_METHODS.threadResume,
|
|
6097
|
+
resumeParams
|
|
6098
|
+
);
|
|
6099
|
+
const resumeResponse = await client.waitForResponse(resumeId);
|
|
6100
|
+
const unarchiveId = client.request(BRIDGE_REQUEST_METHODS.threadUnarchive, {
|
|
6101
|
+
threadId,
|
|
6102
|
+
providerThreadId
|
|
6103
|
+
});
|
|
6104
|
+
await client.waitForResponse(unarchiveId);
|
|
6105
|
+
if (resumeResponse === null) {
|
|
6106
|
+
return [
|
|
6107
|
+
fail(
|
|
6108
|
+
SESSION_ARCHIVED_RECOVERY_ID,
|
|
6109
|
+
SESSION_ARCHIVED_RECOVERY_TITLE,
|
|
6110
|
+
"thread/resume of the archived session was not answered"
|
|
6111
|
+
)
|
|
6112
|
+
];
|
|
6113
|
+
}
|
|
6114
|
+
if (resumeResponse.error === void 0) {
|
|
6115
|
+
const resumed = threadIdentityResultSchema.safeParse(resumeResponse.result);
|
|
6116
|
+
if (!resumed.success) {
|
|
6117
|
+
return [
|
|
6118
|
+
fail(
|
|
6119
|
+
SESSION_ARCHIVED_RECOVERY_ID,
|
|
6120
|
+
SESSION_ARCHIVED_RECOVERY_TITLE,
|
|
6121
|
+
`the archived session was resumed with a result the runtime cannot adopt: ${identityProblem(resumed, resumeResponse.result)}`
|
|
6122
|
+
)
|
|
6123
|
+
];
|
|
6124
|
+
}
|
|
6125
|
+
context.providerThreadId = resumed.data.providerThreadId;
|
|
6126
|
+
return [];
|
|
6127
|
+
}
|
|
6128
|
+
const reResumeId = client.request(
|
|
6129
|
+
BRIDGE_REQUEST_METHODS.threadResume,
|
|
6130
|
+
resumeParams
|
|
6131
|
+
);
|
|
6132
|
+
const reResumeResponse = await client.waitForResponse(reResumeId);
|
|
6133
|
+
const data = bridgeErrorDataSchema.safeParse(resumeResponse.error.data);
|
|
6134
|
+
const kind = data.success ? data.data.recovery?.kind : void 0;
|
|
6135
|
+
if (kind !== "sessionArchived") {
|
|
6136
|
+
return [
|
|
6137
|
+
fail(
|
|
6138
|
+
SESSION_ARCHIVED_RECOVERY_ID,
|
|
6139
|
+
SESSION_ARCHIVED_RECOVERY_TITLE,
|
|
6140
|
+
`the rejection carried ${kind === void 0 ? "no recovery hint" : `kind "${kind}"`}: ${JSON.stringify(resumeResponse.error)}`
|
|
6141
|
+
)
|
|
6142
|
+
];
|
|
6143
|
+
}
|
|
6144
|
+
const notBack = (detail) => [
|
|
6145
|
+
fail(
|
|
6146
|
+
SESSION_ARCHIVED_RECOVERY_ID,
|
|
6147
|
+
SESSION_ARCHIVED_RECOVERY_TITLE,
|
|
6148
|
+
`the session did not come back: ${detail}`
|
|
6149
|
+
)
|
|
6150
|
+
];
|
|
6151
|
+
if (reResumeResponse === null) {
|
|
6152
|
+
return notBack("thread/resume after thread/unarchive was not answered");
|
|
6153
|
+
}
|
|
6154
|
+
if (reResumeResponse.error !== void 0) {
|
|
6155
|
+
return notBack(
|
|
6156
|
+
`thread/resume after thread/unarchive failed: ${JSON.stringify(reResumeResponse.error)}`
|
|
6157
|
+
);
|
|
6158
|
+
}
|
|
6159
|
+
const reResumed = threadIdentityResultSchema.safeParse(
|
|
6160
|
+
reResumeResponse.result
|
|
6161
|
+
);
|
|
6162
|
+
if (!reResumed.success) {
|
|
6163
|
+
return notBack(identityProblem(reResumed, reResumeResponse.result));
|
|
6164
|
+
}
|
|
6165
|
+
context.providerThreadId = reResumed.data.providerThreadId;
|
|
6166
|
+
return [pass(SESSION_ARCHIVED_RECOVERY_ID, SESSION_ARCHIVED_RECOVERY_TITLE)];
|
|
6167
|
+
}
|
|
5492
6168
|
var SETTLES_WITHOUT_ACTIVITY_ID = "turn/settles-without-activity";
|
|
5493
6169
|
var SETTLES_WITHOUT_ACTIVITY_TITLE = "a turn the provider completes without activity still settles";
|
|
5494
6170
|
async function runZeroWorkTurnScenario(context, threadId) {
|
|
@@ -5543,20 +6219,127 @@ async function runZeroWorkTurnScenario(context, threadId) {
|
|
|
5543
6219
|
}
|
|
5544
6220
|
return [pass(SETTLES_WITHOUT_ACTIVITY_ID, SETTLES_WITHOUT_ACTIVITY_TITLE)];
|
|
5545
6221
|
}
|
|
6222
|
+
function openTurnStart(context, threadId) {
|
|
6223
|
+
const events = threadEvents(context, threadId);
|
|
6224
|
+
const completedTurnIds = new Set(
|
|
6225
|
+
events.filter((event) => event.type === "turn/completed").map((event) => getThreadEventScopeTurnId(event.scope))
|
|
6226
|
+
);
|
|
6227
|
+
return [...events].reverse().find(
|
|
6228
|
+
(event) => event.type === "turn/started" && !completedTurnIds.has(getThreadEventScopeTurnId(event.scope))
|
|
6229
|
+
);
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6232
|
+
// ../provider-bridge-protocol/src/conformance/recorded.ts
|
|
6233
|
+
var RECORDED_CONFORMANCE_CELLS = [
|
|
6234
|
+
"turn-tools",
|
|
6235
|
+
"steer",
|
|
6236
|
+
"stop-interrupt",
|
|
6237
|
+
"approval-allow",
|
|
6238
|
+
"approval-deny",
|
|
6239
|
+
"user-question",
|
|
6240
|
+
"resume",
|
|
6241
|
+
"fork"
|
|
6242
|
+
];
|
|
6243
|
+
function result(id, title, detail) {
|
|
6244
|
+
return detail === null ? { id, title, status: "pass", detail: "" } : { id, title, status: "fail", detail };
|
|
6245
|
+
}
|
|
6246
|
+
function countTurns(events) {
|
|
6247
|
+
let started = 0;
|
|
6248
|
+
let completed = 0;
|
|
6249
|
+
for (const event of events) {
|
|
6250
|
+
if (event.type === "turn/started") started += 1;
|
|
6251
|
+
if (event.type === "turn/completed") completed += 1;
|
|
6252
|
+
}
|
|
6253
|
+
return { started, completed };
|
|
6254
|
+
}
|
|
6255
|
+
function checkRecordedCellReplay(replay) {
|
|
6256
|
+
const prefix = `recorded/${replay.cell}`;
|
|
6257
|
+
const results = [];
|
|
6258
|
+
const recordedTurns = countTurns(replay.recordedEvents);
|
|
6259
|
+
const liveTurns = countTurns(replay.events);
|
|
6260
|
+
results.push(
|
|
6261
|
+
result(
|
|
6262
|
+
`${prefix}/replays`,
|
|
6263
|
+
"the bridge answers every recorded runtime request and the replay needs no help",
|
|
6264
|
+
replay.stalls.length === 0 ? null : replay.stalls.join("; ")
|
|
6265
|
+
)
|
|
6266
|
+
);
|
|
6267
|
+
results.push(
|
|
6268
|
+
result(
|
|
6269
|
+
`${prefix}/events-schema-valid`,
|
|
6270
|
+
"every assembled event is a valid ThreadEvent",
|
|
6271
|
+
(() => {
|
|
6272
|
+
for (const [index, event] of replay.events.entries()) {
|
|
6273
|
+
const parsed = threadEventSchema.safeParse(event);
|
|
6274
|
+
if (!parsed.success) {
|
|
6275
|
+
return `event ${index} (${event.type}) failed: ${parsed.error.issues[0]?.message ?? "invalid"}`;
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
return null;
|
|
6279
|
+
})()
|
|
6280
|
+
)
|
|
6281
|
+
);
|
|
6282
|
+
results.push(
|
|
6283
|
+
result(
|
|
6284
|
+
`${prefix}/grammar`,
|
|
6285
|
+
"the event stream breaks no thread-event grammar rule",
|
|
6286
|
+
(() => {
|
|
6287
|
+
const grammar = new ThreadEventGrammar();
|
|
6288
|
+
for (const event of replay.events) {
|
|
6289
|
+
const verdict = grammar.observe(event);
|
|
6290
|
+
if (verdict.kind === "violation") {
|
|
6291
|
+
return `${verdict.rule} on ${event.type}: ${verdict.reason}`;
|
|
6292
|
+
}
|
|
6293
|
+
}
|
|
6294
|
+
return null;
|
|
6295
|
+
})()
|
|
6296
|
+
)
|
|
6297
|
+
);
|
|
6298
|
+
results.push(
|
|
6299
|
+
result(
|
|
6300
|
+
`${prefix}/turn-lifecycle`,
|
|
6301
|
+
"the replay opens and settles as many turns as the recording did",
|
|
6302
|
+
liveTurns.started === recordedTurns.started && liveTurns.completed === recordedTurns.completed && liveTurns.started === liveTurns.completed ? null : `replay ${liveTurns.started} started/${liveTurns.completed} completed, recording ${recordedTurns.started}/${recordedTurns.completed}`
|
|
6303
|
+
)
|
|
6304
|
+
);
|
|
6305
|
+
results.push(
|
|
6306
|
+
result(
|
|
6307
|
+
`${prefix}/not-empty`,
|
|
6308
|
+
"a recorded session that produced events still does",
|
|
6309
|
+
replay.recordedEvents.length === 0 || replay.events.length > 0 ? null : `the recording assembled ${replay.recordedEvents.length} events, the replay none`
|
|
6310
|
+
)
|
|
6311
|
+
);
|
|
6312
|
+
return results;
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6315
|
+
// ../provider-bridge-protocol/src/conformance/types.ts
|
|
6316
|
+
var CONFORMANCE_ASSEMBLED_EVENT_METHOD = "conformance/assembledEvent";
|
|
6317
|
+
function reportPassed(results) {
|
|
6318
|
+
return results.every((result2) => result2.status === "pass");
|
|
6319
|
+
}
|
|
5546
6320
|
|
|
5547
6321
|
// ../provider-bridge-protocol/src/conformance/index.ts
|
|
5548
6322
|
async function runBridgeConformance(options) {
|
|
6323
|
+
const collector = createBridgeDeltaEventCollector(options.providerId);
|
|
5549
6324
|
const client = new ConformanceClient(
|
|
5550
6325
|
options.transport,
|
|
5551
|
-
options.timeoutMs ?? 5e3
|
|
6326
|
+
options.timeoutMs ?? 5e3,
|
|
6327
|
+
collector
|
|
5552
6328
|
);
|
|
5553
6329
|
const results = [];
|
|
5554
6330
|
results.push(...await runRpcHygieneScenarios(client));
|
|
5555
|
-
|
|
6331
|
+
const handshake = await runHandshakeScenario(client);
|
|
6332
|
+
results.push(...handshake.results);
|
|
5556
6333
|
results.push(
|
|
5557
6334
|
...await runSessionLifecycleScenarios({
|
|
5558
6335
|
client,
|
|
5559
|
-
fixture: options.session
|
|
6336
|
+
fixture: options.session,
|
|
6337
|
+
// The same reverse mapping the runtime applies when it names a turn
|
|
6338
|
+
// to the bridge: the assembler's provider↔bb turn-id map.
|
|
6339
|
+
resolveProviderTurnId: (threadId, bbTurnId) => collector.assembler.getProviderTurnId(threadId, bbTurnId),
|
|
6340
|
+
// A failed handshake reads as the definite absences the schema
|
|
6341
|
+
// defaults to: no fork, so the fork rule is not attempted.
|
|
6342
|
+
fork: handshake.capabilities?.fork ?? "none"
|
|
5560
6343
|
})
|
|
5561
6344
|
);
|
|
5562
6345
|
await options.transport.close?.();
|
|
@@ -5564,36 +6347,36 @@ async function runBridgeConformance(options) {
|
|
|
5564
6347
|
}
|
|
5565
6348
|
function formatConformanceReport(report) {
|
|
5566
6349
|
return report.results.map(
|
|
5567
|
-
(
|
|
6350
|
+
(result2) => `${result2.status.padEnd(7)} ${result2.id}${result2.detail === "" ? "" : ` \u2014 ${result2.detail}`}`
|
|
5568
6351
|
).join("\n");
|
|
5569
6352
|
}
|
|
5570
6353
|
|
|
5571
6354
|
// ../provider-bridge-protocol/src/testing/bridge-json-rpc-test-helpers.ts
|
|
5572
|
-
import { z as
|
|
5573
|
-
var bridgeJsonRpcValueSchema =
|
|
5574
|
-
() =>
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
6355
|
+
import { z as z26 } from "zod";
|
|
6356
|
+
var bridgeJsonRpcValueSchema = z26.lazy(
|
|
6357
|
+
() => z26.union([
|
|
6358
|
+
z26.string(),
|
|
6359
|
+
z26.number(),
|
|
6360
|
+
z26.boolean(),
|
|
6361
|
+
z26.null(),
|
|
6362
|
+
z26.array(bridgeJsonRpcValueSchema),
|
|
6363
|
+
z26.record(z26.string(), bridgeJsonRpcValueSchema)
|
|
5581
6364
|
])
|
|
5582
6365
|
);
|
|
5583
|
-
var bridgeJsonRpcOutputSchema =
|
|
5584
|
-
jsonrpc:
|
|
5585
|
-
id:
|
|
5586
|
-
method:
|
|
6366
|
+
var bridgeJsonRpcOutputSchema = z26.object({
|
|
6367
|
+
jsonrpc: z26.literal("2.0"),
|
|
6368
|
+
id: z26.union([z26.string(), z26.number()]).optional(),
|
|
6369
|
+
method: z26.string().optional(),
|
|
5587
6370
|
params: bridgeJsonRpcValueSchema.optional(),
|
|
5588
6371
|
result: bridgeJsonRpcValueSchema.optional(),
|
|
5589
|
-
error:
|
|
5590
|
-
code:
|
|
5591
|
-
message:
|
|
6372
|
+
error: z26.object({
|
|
6373
|
+
code: z26.number(),
|
|
6374
|
+
message: z26.string(),
|
|
5592
6375
|
data: bridgeJsonRpcValueSchema.optional()
|
|
5593
6376
|
}).optional()
|
|
5594
6377
|
});
|
|
5595
6378
|
function waitForNextBridgeTick() {
|
|
5596
|
-
return new Promise((
|
|
6379
|
+
return new Promise((resolve3) => setTimeout(resolve3, 0));
|
|
5597
6380
|
}
|
|
5598
6381
|
function captureBridgeJsonRpcOutput() {
|
|
5599
6382
|
const messages = [];
|
|
@@ -5608,8 +6391,14 @@ function captureBridgeJsonRpcOutput() {
|
|
|
5608
6391
|
return true;
|
|
5609
6392
|
};
|
|
5610
6393
|
process.stdout.write = capturingWrite;
|
|
6394
|
+
let drained = 0;
|
|
5611
6395
|
return {
|
|
5612
6396
|
messages,
|
|
6397
|
+
takeMessages() {
|
|
6398
|
+
const fresh = messages.slice(drained);
|
|
6399
|
+
drained = messages.length;
|
|
6400
|
+
return fresh;
|
|
6401
|
+
},
|
|
5613
6402
|
restore() {
|
|
5614
6403
|
if (process.stdout.write === capturingWrite) {
|
|
5615
6404
|
process.stdout.write = originalWrite;
|
|
@@ -5647,6 +6436,7 @@ function createBridgeJsonRpcTestHarness(handleLine) {
|
|
|
5647
6436
|
const output = captureBridgeJsonRpcOutput();
|
|
5648
6437
|
return {
|
|
5649
6438
|
messages: output.messages,
|
|
6439
|
+
takeMessages: output.takeMessages,
|
|
5650
6440
|
flushWork: waitForNextBridgeTick,
|
|
5651
6441
|
hasResponse(id) {
|
|
5652
6442
|
return bridgeJsonRpcResponseExists({ id, output });
|
|
@@ -5663,49 +6453,6 @@ function createBridgeJsonRpcTestHarness(handleLine) {
|
|
|
5663
6453
|
};
|
|
5664
6454
|
}
|
|
5665
6455
|
|
|
5666
|
-
// ../provider-bridge-protocol/src/testing/bridge-delta-assembly.ts
|
|
5667
|
-
function createBridgeDeltaEventCollector(providerId = "pi") {
|
|
5668
|
-
const assembler = createDeltaAssembler({ providerId, textDeltaFlushMs: 0 });
|
|
5669
|
-
return {
|
|
5670
|
-
assembler,
|
|
5671
|
-
assembleMessage(message) {
|
|
5672
|
-
if (message.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
|
|
5673
|
-
return [];
|
|
5674
|
-
}
|
|
5675
|
-
const parsed = threadDeltaNotificationParamsSchema.safeParse(
|
|
5676
|
-
message.params
|
|
5677
|
-
);
|
|
5678
|
-
if (!parsed.success) {
|
|
5679
|
-
throw new Error(
|
|
5680
|
-
`Invalid thread/delta notification: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(
|
|
5681
|
-
"; "
|
|
5682
|
-
)} (params: ${JSON.stringify(message.params)?.slice(0, 400)})`
|
|
5683
|
-
);
|
|
5684
|
-
}
|
|
5685
|
-
return assembler.assemble({
|
|
5686
|
-
threadId: parsed.data.threadId,
|
|
5687
|
-
deltas: parsed.data.deltas
|
|
5688
|
-
});
|
|
5689
|
-
}
|
|
5690
|
-
};
|
|
5691
|
-
}
|
|
5692
|
-
function assembleCapturedThreadEvents(messages, providerId = "pi") {
|
|
5693
|
-
const collector = createBridgeDeltaEventCollector(providerId);
|
|
5694
|
-
return messages.flatMap((message) => collector.assembleMessage(message));
|
|
5695
|
-
}
|
|
5696
|
-
function toConformanceMessages(message, collector) {
|
|
5697
|
-
if (message.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
|
|
5698
|
-
return [message];
|
|
5699
|
-
}
|
|
5700
|
-
const threadId = typeof message.params?.threadId === "string" ? message.params.threadId : "";
|
|
5701
|
-
return collector.assembleMessage(message).map((event) => ({
|
|
5702
|
-
jsonrpc: "2.0",
|
|
5703
|
-
method: CONFORMANCE_ASSEMBLED_EVENT_METHOD,
|
|
5704
|
-
// ThreadEvents are JSON data; the capture type demands JsonValue.
|
|
5705
|
-
params: JSON.parse(JSON.stringify({ threadId, event }))
|
|
5706
|
-
}));
|
|
5707
|
-
}
|
|
5708
|
-
|
|
5709
6456
|
// ../provider-bridge-protocol/src/testing/calibration-diff.ts
|
|
5710
6457
|
var DEFAULT_INTERNED_ID_FIELDS = [
|
|
5711
6458
|
"turnId",
|
|
@@ -5760,6 +6507,38 @@ function normalizeCalibrationEvents(events, options = {}) {
|
|
|
5760
6507
|
const list = Array.isArray(wireShaped) ? wireShaped : [];
|
|
5761
6508
|
return list.map((event) => normalizeValue(event, interner, idFields));
|
|
5762
6509
|
}
|
|
6510
|
+
function diffCalibrationStreams(legacy, bridge) {
|
|
6511
|
+
const left = legacy.map((event) => JSON.stringify(event));
|
|
6512
|
+
const right = bridge.map((event) => JSON.stringify(event));
|
|
6513
|
+
const lengths = Array.from(
|
|
6514
|
+
{ length: left.length + 1 },
|
|
6515
|
+
() => new Array(right.length + 1).fill(0)
|
|
6516
|
+
);
|
|
6517
|
+
for (let i2 = left.length - 1; i2 >= 0; i2 -= 1) {
|
|
6518
|
+
for (let j2 = right.length - 1; j2 >= 0; j2 -= 1) {
|
|
6519
|
+
lengths[i2][j2] = left[i2] === right[j2] ? lengths[i2 + 1][j2 + 1] + 1 : Math.max(lengths[i2 + 1][j2], lengths[i2][j2 + 1]);
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6522
|
+
const onlyInLegacy = [];
|
|
6523
|
+
const onlyInBridge = [];
|
|
6524
|
+
let i = 0;
|
|
6525
|
+
let j = 0;
|
|
6526
|
+
while (i < left.length && j < right.length) {
|
|
6527
|
+
if (left[i] === right[j]) {
|
|
6528
|
+
i += 1;
|
|
6529
|
+
j += 1;
|
|
6530
|
+
} else if (lengths[i + 1][j] >= lengths[i][j + 1]) {
|
|
6531
|
+
onlyInLegacy.push(legacy[i]);
|
|
6532
|
+
i += 1;
|
|
6533
|
+
} else {
|
|
6534
|
+
onlyInBridge.push(bridge[j]);
|
|
6535
|
+
j += 1;
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6538
|
+
onlyInLegacy.push(...legacy.slice(i));
|
|
6539
|
+
onlyInBridge.push(...bridge.slice(j));
|
|
6540
|
+
return { onlyInLegacy, onlyInBridge };
|
|
6541
|
+
}
|
|
5763
6542
|
function describeCalibrationEvents(events) {
|
|
5764
6543
|
return events.map((event) => {
|
|
5765
6544
|
if (event === null || typeof event !== "object") {
|
|
@@ -5776,35 +6555,840 @@ function describeCalibrationEvents(events) {
|
|
|
5776
6555
|
}
|
|
5777
6556
|
|
|
5778
6557
|
// ../provider-bridge-protocol/src/testing/parity.ts
|
|
6558
|
+
import { spawn } from "node:child_process";
|
|
6559
|
+
import { existsSync as existsSync2, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
6560
|
+
import { tmpdir } from "node:os";
|
|
6561
|
+
import { isAbsolute, join as join2, resolve as resolve2 } from "node:path";
|
|
5779
6562
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5780
6563
|
|
|
5781
6564
|
// ../provider-bridge-protocol/src/bridge-kit/bounded-line-reader.ts
|
|
6565
|
+
import { StringDecoder } from "node:string_decoder";
|
|
5782
6566
|
var MAX_JSON_RPC_LINE_BYTES = 64 * 1024 * 1024;
|
|
6567
|
+
function readBoundedLines(args) {
|
|
6568
|
+
const maxLineBytes = args.maxLineBytes ?? MAX_JSON_RPC_LINE_BYTES;
|
|
6569
|
+
const decoder = new StringDecoder("utf8");
|
|
6570
|
+
let pending = "";
|
|
6571
|
+
let discarding = false;
|
|
6572
|
+
let discardedBytes = 0;
|
|
6573
|
+
args.input.on("data", (chunk) => {
|
|
6574
|
+
const text = typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
6575
|
+
let start = 0;
|
|
6576
|
+
for (; ; ) {
|
|
6577
|
+
const newlineIndex = text.indexOf("\n", start);
|
|
6578
|
+
if (newlineIndex === -1) {
|
|
6579
|
+
break;
|
|
6580
|
+
}
|
|
6581
|
+
if (discarding) {
|
|
6582
|
+
discarding = false;
|
|
6583
|
+
args.onOverflow(discardedBytes);
|
|
6584
|
+
discardedBytes = 0;
|
|
6585
|
+
} else {
|
|
6586
|
+
emit(pending + text.slice(start, newlineIndex));
|
|
6587
|
+
}
|
|
6588
|
+
pending = "";
|
|
6589
|
+
start = newlineIndex + 1;
|
|
6590
|
+
}
|
|
6591
|
+
const tail = text.slice(start);
|
|
6592
|
+
if (discarding) {
|
|
6593
|
+
discardedBytes += Buffer.byteLength(tail);
|
|
6594
|
+
return;
|
|
6595
|
+
}
|
|
6596
|
+
pending += tail;
|
|
6597
|
+
if (Buffer.byteLength(pending) > maxLineBytes) {
|
|
6598
|
+
discarding = true;
|
|
6599
|
+
discardedBytes = Buffer.byteLength(pending);
|
|
6600
|
+
pending = "";
|
|
6601
|
+
}
|
|
6602
|
+
});
|
|
6603
|
+
args.input.on("end", () => {
|
|
6604
|
+
if (!discarding && pending.length > 0) {
|
|
6605
|
+
emit(pending);
|
|
6606
|
+
}
|
|
6607
|
+
pending = "";
|
|
6608
|
+
args.onClose?.();
|
|
6609
|
+
});
|
|
6610
|
+
function emit(line) {
|
|
6611
|
+
args.onLine(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
6612
|
+
}
|
|
6613
|
+
}
|
|
5783
6614
|
|
|
5784
6615
|
// ../provider-bridge-protocol/src/testing/recording.ts
|
|
6616
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
5785
6617
|
import { join, resolve } from "node:path";
|
|
5786
6618
|
import { fileURLToPath } from "node:url";
|
|
6619
|
+
|
|
6620
|
+
// ../provider-bridge-protocol/src/bridge-kit/bridge-recorder.ts
|
|
6621
|
+
var BRIDGE_RECORDING_DIRECTIONS = [
|
|
6622
|
+
"runtime\u2192bridge",
|
|
6623
|
+
"bridge\u2192runtime",
|
|
6624
|
+
"provider\u2192bridge",
|
|
6625
|
+
"bridge\u2192provider"
|
|
6626
|
+
];
|
|
6627
|
+
function bridgeRecordingFileName(direction) {
|
|
6628
|
+
return `${direction}.ndjson`;
|
|
6629
|
+
}
|
|
6630
|
+
|
|
6631
|
+
// ../provider-bridge-protocol/src/testing/recording.ts
|
|
5787
6632
|
var COMMITTED_RECORDINGS_ROOT = fileURLToPath(
|
|
5788
6633
|
new URL("../../recordings", import.meta.url)
|
|
5789
6634
|
);
|
|
5790
6635
|
var RECORDINGS_CHECKOUT_ROOT = resolve(COMMITTED_RECORDINGS_ROOT, "../../..");
|
|
6636
|
+
function compareRecordingEntries(left, right) {
|
|
6637
|
+
return left.run - right.run || left.seq - right.seq;
|
|
6638
|
+
}
|
|
6639
|
+
function parseEntry(raw, file, lineNumber) {
|
|
6640
|
+
let parsed;
|
|
6641
|
+
try {
|
|
6642
|
+
parsed = JSON.parse(raw);
|
|
6643
|
+
} catch (error) {
|
|
6644
|
+
throw new Error(
|
|
6645
|
+
`${file}:${lineNumber}: not JSON (${error instanceof Error ? error.message : String(error)})`
|
|
6646
|
+
);
|
|
6647
|
+
}
|
|
6648
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.line !== "string" || typeof parsed.seq !== "number" || typeof parsed.dir !== "string") {
|
|
6649
|
+
throw new Error(`${file}:${lineNumber}: not a recording entry`);
|
|
6650
|
+
}
|
|
6651
|
+
const entry = parsed;
|
|
6652
|
+
return { ...entry, run: typeof entry.run === "number" ? entry.run : 0 };
|
|
6653
|
+
}
|
|
6654
|
+
function readBridgeRecordingLane(dir, direction) {
|
|
6655
|
+
const file = join(dir, bridgeRecordingFileName(direction));
|
|
6656
|
+
if (!existsSync(file)) {
|
|
6657
|
+
return [];
|
|
6658
|
+
}
|
|
6659
|
+
const entries = [];
|
|
6660
|
+
const lines = readFileSync(file, "utf8").split("\n");
|
|
6661
|
+
for (const [index, raw] of lines.entries()) {
|
|
6662
|
+
if (raw.length === 0) continue;
|
|
6663
|
+
const entry = parseEntry(raw, file, index + 1);
|
|
6664
|
+
if (entry.dir !== direction) {
|
|
6665
|
+
throw new Error(`${file}:${index + 1}: entry direction ${entry.dir} in the ${direction} lane`);
|
|
6666
|
+
}
|
|
6667
|
+
entries.push(entry);
|
|
6668
|
+
}
|
|
6669
|
+
return entries;
|
|
6670
|
+
}
|
|
6671
|
+
var CURRENT_BRIDGE_LANE_FILE = "bridge\u2192runtime.current.ndjson";
|
|
6672
|
+
function readCurrentBridgeLane(dir) {
|
|
6673
|
+
const file = join(dir, CURRENT_BRIDGE_LANE_FILE);
|
|
6674
|
+
if (!existsSync(file)) {
|
|
6675
|
+
return null;
|
|
6676
|
+
}
|
|
6677
|
+
const entries = [];
|
|
6678
|
+
const lines = readFileSync(file, "utf8").split("\n");
|
|
6679
|
+
for (const [index, raw] of lines.entries()) {
|
|
6680
|
+
if (raw.length === 0) continue;
|
|
6681
|
+
const entry = parseEntry(raw, file, index + 1);
|
|
6682
|
+
if (entry.dir !== "bridge\u2192runtime") {
|
|
6683
|
+
throw new Error(`${file}:${index + 1}: entry direction ${entry.dir} in the current bridge lane`);
|
|
6684
|
+
}
|
|
6685
|
+
entries.push(entry);
|
|
6686
|
+
}
|
|
6687
|
+
return entries;
|
|
6688
|
+
}
|
|
6689
|
+
function withCurrentBridgeLane(recording) {
|
|
6690
|
+
const current = readCurrentBridgeLane(recording.dir);
|
|
6691
|
+
if (current === null) {
|
|
6692
|
+
return recording;
|
|
6693
|
+
}
|
|
6694
|
+
const entries = [
|
|
6695
|
+
...recording.entries.filter((entry) => entry.dir !== "bridge\u2192runtime"),
|
|
6696
|
+
...current
|
|
6697
|
+
];
|
|
6698
|
+
entries.sort(compareRecordingEntries);
|
|
6699
|
+
return { ...recording, entries };
|
|
6700
|
+
}
|
|
6701
|
+
function readBridgeRecording(dir) {
|
|
6702
|
+
const manifestPath = join(dir, "manifest.json");
|
|
6703
|
+
const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : null;
|
|
6704
|
+
const entries = [];
|
|
6705
|
+
for (const direction of BRIDGE_RECORDING_DIRECTIONS) {
|
|
6706
|
+
entries.push(...readBridgeRecordingLane(dir, direction));
|
|
6707
|
+
}
|
|
6708
|
+
entries.sort(compareRecordingEntries);
|
|
6709
|
+
return { dir, manifest, entries };
|
|
6710
|
+
}
|
|
6711
|
+
function listRecordedCells(root) {
|
|
6712
|
+
const cells = [];
|
|
6713
|
+
if (!existsSync(root)) {
|
|
6714
|
+
return cells;
|
|
6715
|
+
}
|
|
6716
|
+
for (const provider of readdirSync(root).sort()) {
|
|
6717
|
+
const providerDir = join(root, provider);
|
|
6718
|
+
if (!statSync(providerDir).isDirectory()) continue;
|
|
6719
|
+
for (const cell of readdirSync(providerDir).sort()) {
|
|
6720
|
+
const dir = join(providerDir, cell);
|
|
6721
|
+
if (!statSync(dir).isDirectory()) continue;
|
|
6722
|
+
const hasLane = BRIDGE_RECORDING_DIRECTIONS.some(
|
|
6723
|
+
(direction) => existsSync(join(dir, bridgeRecordingFileName(direction)))
|
|
6724
|
+
);
|
|
6725
|
+
if (hasLane) {
|
|
6726
|
+
cells.push({ provider, cell, dir });
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6730
|
+
return cells;
|
|
6731
|
+
}
|
|
5791
6732
|
|
|
5792
6733
|
// ../provider-bridge-protocol/src/testing/parity.ts
|
|
6734
|
+
var SOURCE_BOOTSTRAP = fileURLToPath2(new URL("../bridge-worker-entry.ts", import.meta.url));
|
|
6735
|
+
var BUNDLED_BOOTSTRAP = fileURLToPath2(new URL("./provider-bridge-worker-entry.mjs", import.meta.url));
|
|
6736
|
+
function resolveProviderBridgeBootstrapPath() {
|
|
6737
|
+
if (existsSync2(SOURCE_BOOTSTRAP)) return SOURCE_BOOTSTRAP;
|
|
6738
|
+
if (existsSync2(BUNDLED_BOOTSTRAP)) return BUNDLED_BOOTSTRAP;
|
|
6739
|
+
throw new Error(
|
|
6740
|
+
`provider-bridge bootstrap not found at ${SOURCE_BOOTSTRAP} or ${BUNDLED_BOOTSTRAP}`
|
|
6741
|
+
);
|
|
6742
|
+
}
|
|
6743
|
+
function isTypeScriptPath(path) {
|
|
6744
|
+
return /\.[cm]?tsx?$/u.test(path);
|
|
6745
|
+
}
|
|
6746
|
+
function tsxSpecifier() {
|
|
6747
|
+
return import.meta.resolve("tsx");
|
|
6748
|
+
}
|
|
6749
|
+
function defaultNodeArgs(bootstrapPath, modulePath) {
|
|
6750
|
+
if (isTypeScriptPath(bootstrapPath)) {
|
|
6751
|
+
return ["--conditions=source", "--import", tsxSpecifier()];
|
|
6752
|
+
}
|
|
6753
|
+
return isTypeScriptPath(modulePath) ? ["--import", tsxSpecifier()] : [];
|
|
6754
|
+
}
|
|
6755
|
+
function resolveProviderBridgeLaunch(options) {
|
|
6756
|
+
if (!isAbsolute(options.modulePath)) {
|
|
6757
|
+
throw new Error(`bridge module path must be absolute: ${options.modulePath}`);
|
|
6758
|
+
}
|
|
6759
|
+
const bootstrapPath = options.bootstrapPath ?? resolveProviderBridgeBootstrapPath();
|
|
6760
|
+
const dataDir = options.dataDir ?? mkdtempSync(join2(tmpdir(), "bb-parity-data-"));
|
|
6761
|
+
return {
|
|
6762
|
+
command: process.execPath,
|
|
6763
|
+
args: [
|
|
6764
|
+
...options.nodeArgs ?? defaultNodeArgs(bootstrapPath, options.modulePath),
|
|
6765
|
+
bootstrapPath,
|
|
6766
|
+
options.modulePath,
|
|
6767
|
+
options.pluginId,
|
|
6768
|
+
dataDir
|
|
6769
|
+
],
|
|
6770
|
+
cwd: options.cwd ?? process.cwd(),
|
|
6771
|
+
env: {}
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6774
|
+
var DEFAULT_REPLAY_PROFILE = {
|
|
6775
|
+
dialect: "json-rpc",
|
|
6776
|
+
env: () => ({})
|
|
6777
|
+
};
|
|
6778
|
+
function rewriteRecordedMachineFacts(line, workspaceDir) {
|
|
6779
|
+
if (!line.includes('"PATH"') && !line.includes('"cwd"')) {
|
|
6780
|
+
return line;
|
|
6781
|
+
}
|
|
6782
|
+
let parsed;
|
|
6783
|
+
try {
|
|
6784
|
+
parsed = JSON.parse(line);
|
|
6785
|
+
} catch {
|
|
6786
|
+
return line;
|
|
6787
|
+
}
|
|
6788
|
+
const params = parsed.params;
|
|
6789
|
+
if (params === void 0) {
|
|
6790
|
+
return line;
|
|
6791
|
+
}
|
|
6792
|
+
let changed = false;
|
|
6793
|
+
const envVars = params.options?.envVars;
|
|
6794
|
+
if (envVars !== void 0 && typeof envVars.PATH === "string") {
|
|
6795
|
+
envVars.PATH = process.env.PATH ?? envVars.PATH;
|
|
6796
|
+
changed = true;
|
|
6797
|
+
}
|
|
6798
|
+
if (typeof params.cwd === "string") {
|
|
6799
|
+
params.cwd = workspaceDir;
|
|
6800
|
+
changed = true;
|
|
6801
|
+
}
|
|
6802
|
+
return changed ? JSON.stringify(parsed) : line;
|
|
6803
|
+
}
|
|
6804
|
+
function recordedWorkspaceDir(recording) {
|
|
6805
|
+
for (const entry of recording.entries) {
|
|
6806
|
+
if (entry.dir !== "runtime\u2192bridge") continue;
|
|
6807
|
+
const message = parseWire(entry.line);
|
|
6808
|
+
const cwd = message?.params?.cwd;
|
|
6809
|
+
if (typeof cwd === "string" && cwd.length > 0) return cwd;
|
|
6810
|
+
}
|
|
6811
|
+
return null;
|
|
6812
|
+
}
|
|
6813
|
+
function parseWire(line) {
|
|
6814
|
+
try {
|
|
6815
|
+
const parsed = JSON.parse(line);
|
|
6816
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
6817
|
+
} catch {
|
|
6818
|
+
return null;
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
function isRequest(message) {
|
|
6822
|
+
return message.id !== void 0 && typeof message.method === "string";
|
|
6823
|
+
}
|
|
6824
|
+
function isResponse(message) {
|
|
6825
|
+
return message.id !== void 0 && message.method === void 0;
|
|
6826
|
+
}
|
|
6827
|
+
function countTurnBoundaries(events) {
|
|
6828
|
+
let started = 0;
|
|
6829
|
+
let completed = 0;
|
|
6830
|
+
for (const event of events) {
|
|
6831
|
+
if (event.type === "turn/started") started += 1;
|
|
6832
|
+
if (event.type === "turn/completed") completed += 1;
|
|
6833
|
+
}
|
|
6834
|
+
return { started, completed };
|
|
6835
|
+
}
|
|
6836
|
+
function planRuntimeSteps(recording, assembler) {
|
|
6837
|
+
const steps = [];
|
|
6838
|
+
const assembled = [];
|
|
6839
|
+
for (const entry of recording.entries) {
|
|
6840
|
+
if (entry.dir === "bridge\u2192runtime") {
|
|
6841
|
+
const message = parseWire(entry.line);
|
|
6842
|
+
if (message !== null && message.method === THREAD_DELTA_NOTIFICATION_METHOD) {
|
|
6843
|
+
try {
|
|
6844
|
+
assembled.push(...assembler.assembleMessage(message));
|
|
6845
|
+
} catch {
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
continue;
|
|
6849
|
+
}
|
|
6850
|
+
if (entry.dir !== "runtime\u2192bridge") {
|
|
6851
|
+
continue;
|
|
6852
|
+
}
|
|
6853
|
+
steps.push({
|
|
6854
|
+
entry,
|
|
6855
|
+
message: parseWire(entry.line),
|
|
6856
|
+
gate: countTurnBoundaries(assembled),
|
|
6857
|
+
eventsBefore: assembled.length
|
|
6858
|
+
});
|
|
6859
|
+
}
|
|
6860
|
+
return steps;
|
|
6861
|
+
}
|
|
6862
|
+
function methodOfRecordedBridgeRequest(recording, response, id) {
|
|
6863
|
+
for (const entry of recording.entries) {
|
|
6864
|
+
if (entry.dir !== "bridge\u2192runtime" || entry.run !== response.run) continue;
|
|
6865
|
+
const message = parseWire(entry.line);
|
|
6866
|
+
if (message !== null && isRequest(message) && String(message.id) === String(id)) {
|
|
6867
|
+
return message.method;
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
return void 0;
|
|
6871
|
+
}
|
|
5793
6872
|
var REPLAY_CHILD_PATH = fileURLToPath2(new URL("./replay-provider-child.mjs", import.meta.url));
|
|
6873
|
+
var PARITY_INITIALIZE_ID = "parity-initialize";
|
|
6874
|
+
function sleep(ms) {
|
|
6875
|
+
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
6876
|
+
}
|
|
6877
|
+
async function replayRecording(options) {
|
|
6878
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
6879
|
+
const orderTimeoutMs = options.orderTimeoutMs ?? 5e3;
|
|
6880
|
+
const settleMs = options.settleMs ?? 750;
|
|
6881
|
+
const drainMs = options.drainMs ?? 300;
|
|
6882
|
+
const providerId = options.providerId;
|
|
6883
|
+
const profile = options.profile ?? DEFAULT_REPLAY_PROFILE;
|
|
6884
|
+
const recording = readBridgeRecording(options.recordingDir);
|
|
6885
|
+
const stateDir = mkdtempSync(join2(tmpdir(), "bb-parity-replay-"));
|
|
6886
|
+
const workspaceDir = mkdtempSync(join2(tmpdir(), "bb-parity-ws-"));
|
|
6887
|
+
const replayCommand = [
|
|
6888
|
+
process.execPath,
|
|
6889
|
+
REPLAY_CHILD_PATH,
|
|
6890
|
+
"--recording",
|
|
6891
|
+
resolve2(options.recordingDir),
|
|
6892
|
+
"--dialect",
|
|
6893
|
+
profile.dialect,
|
|
6894
|
+
"--state",
|
|
6895
|
+
stateDir
|
|
6896
|
+
];
|
|
6897
|
+
const cursorPath = join2(stateDir, "cursor");
|
|
6898
|
+
const setCursor = (position) => {
|
|
6899
|
+
writeFileSync(
|
|
6900
|
+
cursorPath,
|
|
6901
|
+
position === "end" ? "end" : `${position.run} ${position.seq}`
|
|
6902
|
+
);
|
|
6903
|
+
};
|
|
6904
|
+
const wrapperPath = join2(stateDir, "replay-provider.mjs");
|
|
6905
|
+
writeFileSync(
|
|
6906
|
+
wrapperPath,
|
|
6907
|
+
[
|
|
6908
|
+
"#!/usr/bin/env node",
|
|
6909
|
+
`process.argv.splice(2, 0, ${JSON.stringify(replayCommand.slice(2)).slice(1, -1)});`,
|
|
6910
|
+
`await import(${JSON.stringify(REPLAY_CHILD_PATH)});`,
|
|
6911
|
+
""
|
|
6912
|
+
].join("\n"),
|
|
6913
|
+
{ mode: 493 }
|
|
6914
|
+
);
|
|
6915
|
+
profile.prepareState?.({ recording, stateDir, workspaceDir });
|
|
6916
|
+
const launch = options.bridge;
|
|
6917
|
+
const child = spawn(launch.command, launch.args, {
|
|
6918
|
+
cwd: launch.cwd,
|
|
6919
|
+
env: {
|
|
6920
|
+
...process.env,
|
|
6921
|
+
...launch.env,
|
|
6922
|
+
...profile.env({ replayCommand, wrapperPath, stateDir })
|
|
6923
|
+
},
|
|
6924
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6925
|
+
});
|
|
6926
|
+
const recordedCwd = recordedWorkspaceDir(recording);
|
|
6927
|
+
const restoreRecordedWorkspace = (line) => recordedCwd === null || recordedCwd === workspaceDir ? line : line.split(workspaceDir).join(recordedCwd);
|
|
6928
|
+
const initializeId = PARITY_INITIALIZE_ID;
|
|
6929
|
+
const startedAt = Date.now();
|
|
6930
|
+
const lines = [];
|
|
6931
|
+
const lineTimes = [];
|
|
6932
|
+
const lineAfter = [];
|
|
6933
|
+
let lastSentRuntimeEntry = null;
|
|
6934
|
+
const events = [];
|
|
6935
|
+
const grammarViolations = [];
|
|
6936
|
+
const stalls = [];
|
|
6937
|
+
let stderr = "";
|
|
6938
|
+
const grammar = new ThreadEventGrammar();
|
|
6939
|
+
const liveAssembler = options.createAssembler(providerId);
|
|
6940
|
+
const planAssembler = (options.createPlanAssembler ?? options.createAssembler)(providerId);
|
|
6941
|
+
const exactPlan = options.planFromCurrentLane === true;
|
|
6942
|
+
const steps = planRuntimeSteps(
|
|
6943
|
+
exactPlan ? withCurrentBridgeLane(recording) : recording,
|
|
6944
|
+
planAssembler
|
|
6945
|
+
);
|
|
6946
|
+
const answeredIds = /* @__PURE__ */ new Set();
|
|
6947
|
+
const pendingBridgeRequests = [];
|
|
6948
|
+
const recordedAnswers = /* @__PURE__ */ new Map();
|
|
6949
|
+
for (const step of steps) {
|
|
6950
|
+
if (step.message !== null && isResponse(step.message)) {
|
|
6951
|
+
const method = methodOfRecordedBridgeRequest(recording, step.entry, step.message.id) ?? "?";
|
|
6952
|
+
const queue = recordedAnswers.get(method) ?? [];
|
|
6953
|
+
queue.push(step.message);
|
|
6954
|
+
recordedAnswers.set(method, queue);
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
let lastOutputAt = Date.now();
|
|
6958
|
+
const exited = new Promise((resolveExit) => {
|
|
6959
|
+
child.on("exit", (code) => resolveExit(code));
|
|
6960
|
+
});
|
|
6961
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
6962
|
+
stderr += chunk;
|
|
6963
|
+
options.onStderr?.(chunk);
|
|
6964
|
+
});
|
|
6965
|
+
function write(line) {
|
|
6966
|
+
if (child.stdin?.writable) {
|
|
6967
|
+
child.stdin.write(`${line}
|
|
6968
|
+
`);
|
|
6969
|
+
}
|
|
6970
|
+
}
|
|
6971
|
+
function answerBridgeRequest(message) {
|
|
6972
|
+
const method = message.method ?? "?";
|
|
6973
|
+
const queue = recordedAnswers.get(method);
|
|
6974
|
+
const recorded = queue?.shift();
|
|
6975
|
+
if (recorded === void 0) {
|
|
6976
|
+
stalls.push(`no recorded answer for bridge request ${method} (${String(message.id)})`);
|
|
6977
|
+
write(
|
|
6978
|
+
JSON.stringify({
|
|
6979
|
+
jsonrpc: "2.0",
|
|
6980
|
+
id: message.id,
|
|
6981
|
+
error: { code: -32e3, message: "parity replay: no recorded answer" }
|
|
6982
|
+
})
|
|
6983
|
+
);
|
|
6984
|
+
return;
|
|
6985
|
+
}
|
|
6986
|
+
write(JSON.stringify({ ...recorded, id: message.id }));
|
|
6987
|
+
}
|
|
6988
|
+
readBoundedLines({
|
|
6989
|
+
input: child.stdout,
|
|
6990
|
+
onLine: (rawLine) => {
|
|
6991
|
+
const line = restoreRecordedWorkspace(rawLine);
|
|
6992
|
+
lastOutputAt = Date.now();
|
|
6993
|
+
lines.push(line);
|
|
6994
|
+
lineTimes.push(lastOutputAt - startedAt);
|
|
6995
|
+
lineAfter.push(lastSentRuntimeEntry);
|
|
6996
|
+
const message = parseWire(line);
|
|
6997
|
+
if (message === null) return;
|
|
6998
|
+
if (isResponse(message)) {
|
|
6999
|
+
answeredIds.add(String(message.id));
|
|
7000
|
+
return;
|
|
7001
|
+
}
|
|
7002
|
+
if (isRequest(message)) {
|
|
7003
|
+
pendingBridgeRequests.push({ id: message.id, method: message.method });
|
|
7004
|
+
answerBridgeRequest(message);
|
|
7005
|
+
return;
|
|
7006
|
+
}
|
|
7007
|
+
if (message.method === THREAD_DELTA_NOTIFICATION_METHOD) {
|
|
7008
|
+
let assembled;
|
|
7009
|
+
try {
|
|
7010
|
+
assembled = liveAssembler.assembleMessage(message);
|
|
7011
|
+
} catch (error) {
|
|
7012
|
+
stalls.push(`invalid thread/delta: ${error instanceof Error ? error.message : String(error)}`);
|
|
7013
|
+
return;
|
|
7014
|
+
}
|
|
7015
|
+
for (const event of assembled) {
|
|
7016
|
+
const result2 = grammar.observe(event);
|
|
7017
|
+
if (result2.kind === "violation") {
|
|
7018
|
+
grammarViolations.push({ rule: result2.rule, reason: result2.reason, eventType: event.type });
|
|
7019
|
+
continue;
|
|
7020
|
+
}
|
|
7021
|
+
events.push(event);
|
|
7022
|
+
}
|
|
7023
|
+
}
|
|
7024
|
+
},
|
|
7025
|
+
onOverflow: (bytes) => {
|
|
7026
|
+
stalls.push(`oversized bridge line (${bytes} bytes)`);
|
|
7027
|
+
}
|
|
7028
|
+
});
|
|
7029
|
+
async function waitFor(label, predicate, limitMs = timeoutMs, reportStall = true) {
|
|
7030
|
+
const deadline = Date.now() + limitMs;
|
|
7031
|
+
while (!predicate()) {
|
|
7032
|
+
if (child.exitCode !== null) {
|
|
7033
|
+
stalls.push(`bridge exited while waiting for ${label}`);
|
|
7034
|
+
return;
|
|
7035
|
+
}
|
|
7036
|
+
if (Date.now() > deadline) {
|
|
7037
|
+
if (reportStall) stalls.push(`timed out waiting for ${label}`);
|
|
7038
|
+
return;
|
|
7039
|
+
}
|
|
7040
|
+
await sleep(10);
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
const firstStep = steps.find((step) => step.message !== null && isRequest(step.message));
|
|
7044
|
+
setCursor(firstStep === void 0 ? "end" : { run: firstStep.entry.run, seq: firstStep.entry.seq });
|
|
7045
|
+
write(
|
|
7046
|
+
JSON.stringify({
|
|
7047
|
+
jsonrpc: "2.0",
|
|
7048
|
+
id: initializeId,
|
|
7049
|
+
method: "initialize",
|
|
7050
|
+
params: {
|
|
7051
|
+
protocolVersion: PROVIDER_BRIDGE_PROTOCOL_VERSION,
|
|
7052
|
+
client: { name: "bb-parity", version: "0" }
|
|
7053
|
+
}
|
|
7054
|
+
})
|
|
7055
|
+
);
|
|
7056
|
+
await waitFor("initialize response", () => answeredIds.has(initializeId));
|
|
7057
|
+
const sentRequestIds = [];
|
|
7058
|
+
for (const step of steps) {
|
|
7059
|
+
if (step.message === null || !isRequest(step.message)) {
|
|
7060
|
+
if (step.message !== null && !isResponse(step.message)) {
|
|
7061
|
+
lastSentRuntimeEntry = { run: step.entry.run, seq: step.entry.seq, ts: step.entry.ts };
|
|
7062
|
+
write(step.entry.line);
|
|
7063
|
+
}
|
|
7064
|
+
continue;
|
|
7065
|
+
}
|
|
7066
|
+
const request = step.message;
|
|
7067
|
+
const method = request.method;
|
|
7068
|
+
await waitFor(
|
|
7069
|
+
`earlier requests before ${method}`,
|
|
7070
|
+
() => sentRequestIds.every((id) => answeredIds.has(id))
|
|
7071
|
+
);
|
|
7072
|
+
await waitFor(
|
|
7073
|
+
`${step.gate.started} turn/started and ${step.gate.completed} turn/completed before ${method}`,
|
|
7074
|
+
() => {
|
|
7075
|
+
const live = countTurnBoundaries(events);
|
|
7076
|
+
return live.started >= step.gate.started && live.completed >= step.gate.completed;
|
|
7077
|
+
}
|
|
7078
|
+
);
|
|
7079
|
+
await waitFor(
|
|
7080
|
+
`${step.eventsBefore} events before ${method}`,
|
|
7081
|
+
() => events.length >= step.eventsBefore || !exactPlan && Date.now() - lastOutputAt >= orderTimeoutMs,
|
|
7082
|
+
timeoutMs,
|
|
7083
|
+
exactPlan
|
|
7084
|
+
);
|
|
7085
|
+
await waitFor(
|
|
7086
|
+
`the stream to drain before ${method}`,
|
|
7087
|
+
() => Date.now() - lastOutputAt >= drainMs,
|
|
7088
|
+
timeoutMs,
|
|
7089
|
+
false
|
|
7090
|
+
);
|
|
7091
|
+
if (child.exitCode !== null) break;
|
|
7092
|
+
if (method === "thread/stop" && typeof request.params === "object" && request.params !== null && request.params.intent === "release") {
|
|
7093
|
+
const threadId = request.params.threadId;
|
|
7094
|
+
if (typeof threadId === "string") grammar.clearThread(threadId);
|
|
7095
|
+
}
|
|
7096
|
+
const rewritten = rewriteRecordedMachineFacts(step.entry.line, workspaceDir);
|
|
7097
|
+
const line = profile.rewriteRuntimeLine === void 0 ? rewritten : profile.rewriteRuntimeLine(rewritten, { replayCommand });
|
|
7098
|
+
lastSentRuntimeEntry = { run: step.entry.run, seq: step.entry.seq, ts: step.entry.ts };
|
|
7099
|
+
write(line);
|
|
7100
|
+
sentRequestIds.push(String(request.id));
|
|
7101
|
+
const nextStep = steps.slice(steps.indexOf(step) + 1).find((candidate) => candidate.message !== null && isRequest(candidate.message));
|
|
7102
|
+
setCursor(nextStep === void 0 ? "end" : { run: nextStep.entry.run, seq: nextStep.entry.seq });
|
|
7103
|
+
}
|
|
7104
|
+
setCursor("end");
|
|
7105
|
+
await waitFor("the last responses", () => sentRequestIds.every((id) => answeredIds.has(id)));
|
|
7106
|
+
await waitFor("the stream to settle", () => Date.now() - lastOutputAt >= settleMs);
|
|
7107
|
+
child.stdin?.end();
|
|
7108
|
+
const exitCode = await Promise.race([
|
|
7109
|
+
exited,
|
|
7110
|
+
sleep(timeoutMs).then(() => {
|
|
7111
|
+
stalls.push("bridge did not exit after stdin closed; killed");
|
|
7112
|
+
child.kill("SIGKILL");
|
|
7113
|
+
return null;
|
|
7114
|
+
})
|
|
7115
|
+
]);
|
|
7116
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
7117
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
7118
|
+
return {
|
|
7119
|
+
providerId,
|
|
7120
|
+
recordingDir: options.recordingDir,
|
|
7121
|
+
lines,
|
|
7122
|
+
lineTimes,
|
|
7123
|
+
lineAfter,
|
|
7124
|
+
events,
|
|
7125
|
+
grammarViolations,
|
|
7126
|
+
stalls,
|
|
7127
|
+
stderr,
|
|
7128
|
+
exitCode
|
|
7129
|
+
};
|
|
7130
|
+
}
|
|
7131
|
+
function assembleRecordedEvents(recording, createAssembler, providerId) {
|
|
7132
|
+
const assembler = createAssembler(providerId);
|
|
7133
|
+
const grammar = new ThreadEventGrammar();
|
|
7134
|
+
const events = [];
|
|
7135
|
+
const grammarViolations = [];
|
|
7136
|
+
const invalidDeltas = [];
|
|
7137
|
+
for (const entry of recording.entries) {
|
|
7138
|
+
if (entry.dir === "runtime\u2192bridge") {
|
|
7139
|
+
const message2 = parseWire(entry.line);
|
|
7140
|
+
if (message2 !== null && message2.method === "thread/stop" && typeof message2.params === "object" && message2.params !== null && message2.params.intent === "release") {
|
|
7141
|
+
const threadId = message2.params.threadId;
|
|
7142
|
+
if (typeof threadId === "string") grammar.clearThread(threadId);
|
|
7143
|
+
}
|
|
7144
|
+
continue;
|
|
7145
|
+
}
|
|
7146
|
+
if (entry.dir !== "bridge\u2192runtime") continue;
|
|
7147
|
+
const message = parseWire(entry.line);
|
|
7148
|
+
if (message === null || message.method !== THREAD_DELTA_NOTIFICATION_METHOD) continue;
|
|
7149
|
+
let assembled;
|
|
7150
|
+
try {
|
|
7151
|
+
assembled = assembler.assembleMessage(message);
|
|
7152
|
+
} catch (error) {
|
|
7153
|
+
invalidDeltas.push(error instanceof Error ? error.message : String(error));
|
|
7154
|
+
continue;
|
|
7155
|
+
}
|
|
7156
|
+
for (const event of assembled) {
|
|
7157
|
+
const result2 = grammar.observe(event);
|
|
7158
|
+
if (result2.kind === "violation") {
|
|
7159
|
+
grammarViolations.push({ rule: result2.rule, reason: result2.reason, eventType: event.type });
|
|
7160
|
+
continue;
|
|
7161
|
+
}
|
|
7162
|
+
events.push(event);
|
|
7163
|
+
}
|
|
7164
|
+
}
|
|
7165
|
+
return { events, grammarViolations, invalidDeltas };
|
|
7166
|
+
}
|
|
7167
|
+
var TIME_FIELDS = /* @__PURE__ */ new Set([
|
|
7168
|
+
"createdAt",
|
|
7169
|
+
"updatedAt",
|
|
7170
|
+
"startedAt",
|
|
7171
|
+
"completedAt",
|
|
7172
|
+
"startedAtMs",
|
|
7173
|
+
"completedAtMs",
|
|
7174
|
+
"timestamp",
|
|
7175
|
+
"ts",
|
|
7176
|
+
"resetsAtMs",
|
|
7177
|
+
"resetsAt",
|
|
7178
|
+
"expiresAt"
|
|
7179
|
+
]);
|
|
7180
|
+
function blankTimeFields(value) {
|
|
7181
|
+
if (Array.isArray(value)) {
|
|
7182
|
+
return value.map(blankTimeFields);
|
|
7183
|
+
}
|
|
7184
|
+
if (value !== null && typeof value === "object") {
|
|
7185
|
+
const out = {};
|
|
7186
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
7187
|
+
out[key] = TIME_FIELDS.has(key) && (typeof entry === "number" || typeof entry === "string") ? 0 : blankTimeFields(entry);
|
|
7188
|
+
}
|
|
7189
|
+
return out;
|
|
7190
|
+
}
|
|
7191
|
+
return value;
|
|
7192
|
+
}
|
|
7193
|
+
var ROW_ID_FIELDS = [
|
|
7194
|
+
"turnId",
|
|
7195
|
+
"itemId",
|
|
7196
|
+
"id",
|
|
7197
|
+
"parentToolCallId",
|
|
7198
|
+
"toolCallId",
|
|
7199
|
+
"callId",
|
|
7200
|
+
"requestId",
|
|
7201
|
+
"messageId",
|
|
7202
|
+
"rowId",
|
|
7203
|
+
"agentId",
|
|
7204
|
+
"taskId",
|
|
7205
|
+
"backgroundTaskId",
|
|
7206
|
+
"sourceItemId",
|
|
7207
|
+
"interactionId"
|
|
7208
|
+
];
|
|
7209
|
+
function normalizeParityEvents(events) {
|
|
7210
|
+
return blankTimeFields(normalizeCalibrationEvents(events));
|
|
7211
|
+
}
|
|
7212
|
+
function normalizeParityRows(rows) {
|
|
7213
|
+
return blankTimeFields(
|
|
7214
|
+
normalizeCalibrationEvents(rows, {
|
|
7215
|
+
internedIdFields: ROW_ID_FIELDS
|
|
7216
|
+
})
|
|
7217
|
+
);
|
|
7218
|
+
}
|
|
7219
|
+
function pointerSegments(path) {
|
|
7220
|
+
return path.split("/").filter((segment) => segment.length > 0);
|
|
7221
|
+
}
|
|
7222
|
+
function maskPath(value, path) {
|
|
7223
|
+
const segments = pointerSegments(path);
|
|
7224
|
+
if (segments.length === 0) {
|
|
7225
|
+
if (!Array.isArray(value)) return 0;
|
|
7226
|
+
const removed2 = value.length;
|
|
7227
|
+
value.length = 0;
|
|
7228
|
+
return removed2;
|
|
7229
|
+
}
|
|
7230
|
+
let removed = 0;
|
|
7231
|
+
const visit = (node, index) => {
|
|
7232
|
+
if (index >= segments.length || node === null || typeof node !== "object") {
|
|
7233
|
+
return;
|
|
7234
|
+
}
|
|
7235
|
+
const segment = segments[index];
|
|
7236
|
+
const last = index === segments.length - 1;
|
|
7237
|
+
if (segment === "**") {
|
|
7238
|
+
visit(node, index + 1);
|
|
7239
|
+
for (const child of Object.values(node)) {
|
|
7240
|
+
visit(child, index);
|
|
7241
|
+
}
|
|
7242
|
+
return;
|
|
7243
|
+
}
|
|
7244
|
+
const keys = segment === "*" ? Object.keys(node) : Object.hasOwn(node, segment) ? [segment] : [];
|
|
7245
|
+
for (const key of keys) {
|
|
7246
|
+
if (last) {
|
|
7247
|
+
if (Array.isArray(node)) {
|
|
7248
|
+
node[Number(key)] = null;
|
|
7249
|
+
} else {
|
|
7250
|
+
delete node[key];
|
|
7251
|
+
}
|
|
7252
|
+
removed += 1;
|
|
7253
|
+
} else {
|
|
7254
|
+
visit(node[key], index + 1);
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
};
|
|
7258
|
+
visit(value, 0);
|
|
7259
|
+
return removed;
|
|
7260
|
+
}
|
|
7261
|
+
function entryApplies(entry, provider, cell) {
|
|
7262
|
+
return (entry.provider === "*" || entry.provider === provider) && (entry.cell === "*" || entry.cell === cell);
|
|
7263
|
+
}
|
|
7264
|
+
function compareParity(oldRun, newRun, allowlist, scope) {
|
|
7265
|
+
const layers = {
|
|
7266
|
+
events: [normalizeParityEvents(oldRun.events), normalizeParityEvents(newRun.events)],
|
|
7267
|
+
rows: [normalizeParityRows(oldRun.rows), normalizeParityRows(newRun.rows)]
|
|
7268
|
+
};
|
|
7269
|
+
const staleAllowlist = [];
|
|
7270
|
+
for (const entry of allowlist) {
|
|
7271
|
+
if (!entryApplies(entry, scope.provider, scope.cell)) continue;
|
|
7272
|
+
const [oldSide, newSide] = layers[entry.layer];
|
|
7273
|
+
const removed = maskPath(oldSide, entry.path) + maskPath(newSide, entry.path);
|
|
7274
|
+
if (removed === 0) {
|
|
7275
|
+
staleAllowlist.push(entry);
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
const events = diffLayer(layers.events[0], layers.events[1]);
|
|
7279
|
+
const rows = diffLayer(layers.rows[0], layers.rows[1]);
|
|
7280
|
+
const grammar = diffLayer(
|
|
7281
|
+
(oldRun.grammarViolations ?? []).map((violation2) => `${violation2.rule}:${violation2.eventType}`),
|
|
7282
|
+
(newRun.grammarViolations ?? []).map((violation2) => `${violation2.rule}:${violation2.eventType}`)
|
|
7283
|
+
);
|
|
7284
|
+
const clean = (diff) => diff.onlyInOld.length === 0 && diff.onlyInNew.length === 0;
|
|
7285
|
+
return {
|
|
7286
|
+
provider: scope.provider,
|
|
7287
|
+
cell: scope.cell,
|
|
7288
|
+
events,
|
|
7289
|
+
rows,
|
|
7290
|
+
grammar,
|
|
7291
|
+
staleAllowlist,
|
|
7292
|
+
passed: clean(events) && clean(rows) && clean(grammar) && staleAllowlist.length === 0
|
|
7293
|
+
};
|
|
7294
|
+
}
|
|
7295
|
+
function diffLayer(oldSide, newSide) {
|
|
7296
|
+
const diff = diffCalibrationStreams(oldSide, newSide);
|
|
7297
|
+
return { onlyInOld: diff.onlyInLegacy, onlyInNew: diff.onlyInBridge };
|
|
7298
|
+
}
|
|
7299
|
+
|
|
7300
|
+
// ../provider-bridge-protocol/src/testing/rerecord.ts
|
|
7301
|
+
import { writeFileSync as writeFileSync2 } from "node:fs";
|
|
7302
|
+
import { join as join3 } from "node:path";
|
|
7303
|
+
var BRIDGE_TO_RUNTIME = "bridge\u2192runtime";
|
|
7304
|
+
function parseWireLine(line) {
|
|
7305
|
+
try {
|
|
7306
|
+
const parsed = JSON.parse(line);
|
|
7307
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
7308
|
+
} catch {
|
|
7309
|
+
return null;
|
|
7310
|
+
}
|
|
7311
|
+
}
|
|
7312
|
+
async function rerecordCurrentBridgeLane(options) {
|
|
7313
|
+
const recording = readBridgeRecording(options.recordingDir);
|
|
7314
|
+
const run = await replayRecording({ ...options, planFromCurrentLane: false });
|
|
7315
|
+
if (run.stalls.length > 0) {
|
|
7316
|
+
return { file: null, lines: 0, events: run.events.length, stalls: run.stalls };
|
|
7317
|
+
}
|
|
7318
|
+
const firstRuntime = recording.entries.find(
|
|
7319
|
+
(entry) => entry.dir === "runtime\u2192bridge"
|
|
7320
|
+
);
|
|
7321
|
+
const recordedRequestIds = /* @__PURE__ */ new Map();
|
|
7322
|
+
for (const entry of recording.entries) {
|
|
7323
|
+
if (entry.dir !== BRIDGE_TO_RUNTIME) continue;
|
|
7324
|
+
const message = parseWireLine(entry.line);
|
|
7325
|
+
if (message?.method === void 0 || message.id === void 0) continue;
|
|
7326
|
+
const queue = recordedRequestIds.get(message.method) ?? [];
|
|
7327
|
+
queue.push(message.id);
|
|
7328
|
+
recordedRequestIds.set(message.method, queue);
|
|
7329
|
+
}
|
|
7330
|
+
const entries = [];
|
|
7331
|
+
const perAnchor = /* @__PURE__ */ new Map();
|
|
7332
|
+
run.lines.forEach((rawLine, index) => {
|
|
7333
|
+
let line = rawLine;
|
|
7334
|
+
const message = parseWireLine(rawLine);
|
|
7335
|
+
if (message?.id === PARITY_INITIALIZE_ID) {
|
|
7336
|
+
return;
|
|
7337
|
+
}
|
|
7338
|
+
if (message?.method !== void 0 && message.id !== void 0) {
|
|
7339
|
+
const recordedId = recordedRequestIds.get(message.method)?.shift();
|
|
7340
|
+
if (recordedId !== void 0 && recordedId !== message.id) {
|
|
7341
|
+
line = JSON.stringify({ ...message, id: recordedId });
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
const anchor = run.lineAfter[index] ?? (firstRuntime ? {
|
|
7345
|
+
run: firstRuntime.run,
|
|
7346
|
+
seq: firstRuntime.seq - 1,
|
|
7347
|
+
ts: firstRuntime.ts
|
|
7348
|
+
} : { run: 0, seq: 0, ts: 0 });
|
|
7349
|
+
const anchorKey = `${anchor.run}:${anchor.seq}`;
|
|
7350
|
+
const ordinal = (perAnchor.get(anchorKey) ?? 0) + 1;
|
|
7351
|
+
perAnchor.set(anchorKey, ordinal);
|
|
7352
|
+
entries.push({
|
|
7353
|
+
ts: anchor.ts + ordinal,
|
|
7354
|
+
run: anchor.run,
|
|
7355
|
+
// Fractional: after the anchoring runtime entry, before the next one.
|
|
7356
|
+
seq: anchor.seq + ordinal / (run.lines.length + 1),
|
|
7357
|
+
dir: BRIDGE_TO_RUNTIME,
|
|
7358
|
+
line
|
|
7359
|
+
});
|
|
7360
|
+
});
|
|
7361
|
+
const file = join3(options.recordingDir, CURRENT_BRIDGE_LANE_FILE);
|
|
7362
|
+
writeFileSync2(
|
|
7363
|
+
file,
|
|
7364
|
+
entries.map((entry) => JSON.stringify(entry)).join("\n") + (entries.length > 0 ? "\n" : "")
|
|
7365
|
+
);
|
|
7366
|
+
return { file, lines: entries.length, events: run.events.length, stalls: [] };
|
|
7367
|
+
}
|
|
5794
7368
|
export {
|
|
5795
7369
|
ASSEMBLER_GRAMMAR_VERSIONS,
|
|
5796
7370
|
CONFORMANCE_ASSEMBLED_EVENT_METHOD,
|
|
5797
|
-
|
|
7371
|
+
CURRENT_BRIDGE_LANE_FILE,
|
|
7372
|
+
DEFAULT_REPLAY_PROFILE,
|
|
7373
|
+
PARITY_INITIALIZE_ID,
|
|
7374
|
+
RECORDED_CONFORMANCE_CELLS,
|
|
5798
7375
|
assembleCapturedThreadEvents as experimental_assembleCapturedThreadEvents,
|
|
7376
|
+
assembleRecordedEvents as experimental_assembleRecordedEvents,
|
|
5799
7377
|
captureBridgeJsonRpcOutput as experimental_captureBridgeJsonRpcOutput,
|
|
5800
|
-
|
|
7378
|
+
checkRecordedCellReplay as experimental_checkRecordedCellReplay,
|
|
7379
|
+
compareParity as experimental_compareParity,
|
|
5801
7380
|
createBridgeDeltaEventCollector as experimental_createBridgeDeltaEventCollector,
|
|
5802
7381
|
createBridgeJsonRpcTestHarness as experimental_createBridgeJsonRpcTestHarness,
|
|
5803
7382
|
createDeltaAssembler as experimental_createDeltaAssembler,
|
|
5804
7383
|
describeCalibrationEvents as experimental_describeCalibrationEvents,
|
|
5805
|
-
diffCumulativeText as experimental_diffCumulativeText,
|
|
5806
7384
|
formatConformanceReport as experimental_formatConformanceReport,
|
|
7385
|
+
listRecordedCells as experimental_listRecordedCells,
|
|
5807
7386
|
normalizeCalibrationEvents as experimental_normalizeCalibrationEvents,
|
|
7387
|
+
readBridgeRecording as experimental_readBridgeRecording,
|
|
7388
|
+
replayRecording as experimental_replayRecording,
|
|
7389
|
+
rerecordCurrentBridgeLane as experimental_rerecordCurrentBridgeLane,
|
|
7390
|
+
resolveProviderBridgeLaunch as experimental_resolveProviderBridgeLaunch,
|
|
5808
7391
|
runBridgeConformance as experimental_runBridgeConformance,
|
|
5809
|
-
toConformanceMessages as experimental_toConformanceMessages
|
|
7392
|
+
toConformanceMessages as experimental_toConformanceMessages,
|
|
7393
|
+
withCurrentBridgeLane as experimental_withCurrentBridgeLane
|
|
5810
7394
|
};
|