@dual-os/sdk 0.0.0-stage → 0.8.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +19 -2
  3. package/dist/Action.d.ts +201 -0
  4. package/dist/Action.d.ts.map +1 -0
  5. package/dist/Action.js +172 -0
  6. package/dist/Action.js.map +1 -0
  7. package/dist/AgentChannel.d.ts +1717 -0
  8. package/dist/AgentChannel.d.ts.map +1 -0
  9. package/dist/AgentChannel.js +708 -0
  10. package/dist/AgentChannel.js.map +1 -0
  11. package/dist/AgentChannelConformance.d.ts +1543 -0
  12. package/dist/AgentChannelConformance.d.ts.map +1 -0
  13. package/dist/AgentChannelConformance.js +163 -0
  14. package/dist/AgentChannelConformance.js.map +1 -0
  15. package/dist/AgentChannelRuntime.d.ts +78 -0
  16. package/dist/AgentChannelRuntime.d.ts.map +1 -0
  17. package/dist/AgentChannelRuntime.js +270 -0
  18. package/dist/AgentChannelRuntime.js.map +1 -0
  19. package/dist/AiProvider.d.ts +132 -0
  20. package/dist/AiProvider.d.ts.map +1 -0
  21. package/dist/AiProvider.js +100 -0
  22. package/dist/AiProvider.js.map +1 -0
  23. package/dist/AuthMethod.d.ts +381 -0
  24. package/dist/AuthMethod.d.ts.map +1 -0
  25. package/dist/AuthMethod.js +225 -0
  26. package/dist/AuthMethod.js.map +1 -0
  27. package/dist/AuthMethodBinding.d.ts +77 -0
  28. package/dist/AuthMethodBinding.d.ts.map +1 -0
  29. package/dist/AuthMethodBinding.js +31 -0
  30. package/dist/AuthMethodBinding.js.map +1 -0
  31. package/dist/AuthMethodExecution.d.ts +223 -0
  32. package/dist/AuthMethodExecution.d.ts.map +1 -0
  33. package/dist/AuthMethodExecution.js +277 -0
  34. package/dist/AuthMethodExecution.js.map +1 -0
  35. package/dist/ConnectionResolver.d.ts +219 -0
  36. package/dist/ConnectionResolver.d.ts.map +1 -0
  37. package/dist/ConnectionResolver.js +153 -0
  38. package/dist/ConnectionResolver.js.map +1 -0
  39. package/dist/DefinitionError.d.ts +67 -0
  40. package/dist/DefinitionError.d.ts.map +1 -0
  41. package/dist/DefinitionError.js +82 -0
  42. package/dist/DefinitionError.js.map +1 -0
  43. package/dist/DualRegistry.d.ts +71 -0
  44. package/dist/DualRegistry.d.ts.map +1 -0
  45. package/dist/DualRegistry.js +96 -0
  46. package/dist/DualRegistry.js.map +1 -0
  47. package/dist/DualRuntime.d.ts +279 -0
  48. package/dist/DualRuntime.d.ts.map +1 -0
  49. package/dist/DualRuntime.js +311 -0
  50. package/dist/DualRuntime.js.map +1 -0
  51. package/dist/Files.d.ts +80 -0
  52. package/dist/Files.d.ts.map +1 -0
  53. package/dist/Files.js +66 -0
  54. package/dist/Files.js.map +1 -0
  55. package/dist/Group.d.ts +106 -0
  56. package/dist/Group.d.ts.map +1 -0
  57. package/dist/Group.js +86 -0
  58. package/dist/Group.js.map +1 -0
  59. package/dist/Http.d.ts +342 -0
  60. package/dist/Http.d.ts.map +1 -0
  61. package/dist/Http.js +677 -0
  62. package/dist/Http.js.map +1 -0
  63. package/dist/Mcp.d.ts +109 -0
  64. package/dist/Mcp.d.ts.map +1 -0
  65. package/dist/Mcp.js +176 -0
  66. package/dist/Mcp.js.map +1 -0
  67. package/dist/OAuth2TokenRefresher.d.ts +18 -0
  68. package/dist/OAuth2TokenRefresher.d.ts.map +1 -0
  69. package/dist/OAuth2TokenRefresher.js +19 -0
  70. package/dist/OAuth2TokenRefresher.js.map +1 -0
  71. package/dist/OAuthFlow.d.ts +71 -0
  72. package/dist/OAuthFlow.d.ts.map +1 -0
  73. package/dist/OAuthFlow.js +488 -0
  74. package/dist/OAuthFlow.js.map +1 -0
  75. package/dist/Plugin.d.ts +383 -0
  76. package/dist/Plugin.d.ts.map +1 -0
  77. package/dist/Plugin.js +249 -0
  78. package/dist/Plugin.js.map +1 -0
  79. package/dist/PluginRuntime.d.ts +3186 -0
  80. package/dist/PluginRuntime.d.ts.map +1 -0
  81. package/dist/PluginRuntime.js +331 -0
  82. package/dist/PluginRuntime.js.map +1 -0
  83. package/dist/Runner.d.ts +1 -0
  84. package/dist/Runner.js +124 -0
  85. package/dist/Runner.js.map +1 -0
  86. package/dist/ToolFile.d.ts +23 -0
  87. package/dist/ToolFile.d.ts.map +1 -0
  88. package/dist/ToolFile.js +33 -0
  89. package/dist/ToolFile.js.map +1 -0
  90. package/dist/Trigger.d.ts +358 -0
  91. package/dist/Trigger.d.ts.map +1 -0
  92. package/dist/Trigger.js +287 -0
  93. package/dist/Trigger.js.map +1 -0
  94. package/dist/_virtual/_rolldown/runtime.js +13 -0
  95. package/dist/index.d.ts +24 -0
  96. package/dist/index.js +24 -0
  97. package/dist/internal/auth-context.d.ts +23 -0
  98. package/dist/internal/auth-context.d.ts.map +1 -0
  99. package/dist/internal/auth-context.js +1 -0
  100. package/dist/internal/auth-execution.d.ts +27 -0
  101. package/dist/internal/auth-execution.d.ts.map +1 -0
  102. package/dist/internal/auth-execution.js +66 -0
  103. package/dist/internal/auth-execution.js.map +1 -0
  104. package/dist/internal/auth-runtime.d.ts +5 -0
  105. package/dist/internal/auth-runtime.d.ts.map +1 -0
  106. package/dist/internal/auth-runtime.js +16 -0
  107. package/dist/internal/auth-runtime.js.map +1 -0
  108. package/dist/internal/capabilities.d.ts +24 -0
  109. package/dist/internal/capabilities.d.ts.map +1 -0
  110. package/dist/internal/capabilities.js +22 -0
  111. package/dist/internal/capabilities.js.map +1 -0
  112. package/dist/internal/children.d.ts +34 -0
  113. package/dist/internal/children.d.ts.map +1 -0
  114. package/dist/internal/children.js +53 -0
  115. package/dist/internal/children.js.map +1 -0
  116. package/dist/internal/collection-utils.d.ts +4 -0
  117. package/dist/internal/collection-utils.d.ts.map +1 -0
  118. package/dist/internal/collection-utils.js +6 -0
  119. package/dist/internal/collection-utils.js.map +1 -0
  120. package/dist/internal/dual/actions.d.ts +33 -0
  121. package/dist/internal/dual/actions.d.ts.map +1 -0
  122. package/dist/internal/dual/actions.js +26 -0
  123. package/dist/internal/dual/actions.js.map +1 -0
  124. package/dist/internal/dual/invoke.d.ts +63 -0
  125. package/dist/internal/dual/invoke.d.ts.map +1 -0
  126. package/dist/internal/dual/invoke.js +75 -0
  127. package/dist/internal/dual/invoke.js.map +1 -0
  128. package/dist/internal/dual/registry.d.ts +125 -0
  129. package/dist/internal/dual/registry.d.ts.map +1 -0
  130. package/dist/internal/dual/registry.js +172 -0
  131. package/dist/internal/dual/registry.js.map +1 -0
  132. package/dist/internal/dual/triggers.d.ts +29 -0
  133. package/dist/internal/dual/triggers.d.ts.map +1 -0
  134. package/dist/internal/dual/triggers.js +26 -0
  135. package/dist/internal/dual/triggers.js.map +1 -0
  136. package/dist/internal/handler-context.d.ts +9 -0
  137. package/dist/internal/handler-context.d.ts.map +1 -0
  138. package/dist/internal/handler-context.js +31 -0
  139. package/dist/internal/handler-context.js.map +1 -0
  140. package/dist/internal/identifier.d.ts +6 -0
  141. package/dist/internal/identifier.d.ts.map +1 -0
  142. package/dist/internal/identifier.js +28 -0
  143. package/dist/internal/identifier.js.map +1 -0
  144. package/dist/internal/plugin-handlers.d.ts +38 -0
  145. package/dist/internal/plugin-handlers.d.ts.map +1 -0
  146. package/dist/internal/plugin-handlers.js +52 -0
  147. package/dist/internal/plugin-handlers.js.map +1 -0
  148. package/dist/internal/proto.d.ts +7 -0
  149. package/dist/internal/proto.d.ts.map +1 -0
  150. package/dist/internal/proto.js +10 -0
  151. package/dist/internal/proto.js.map +1 -0
  152. package/dist/internal/schema-runtime.d.ts +6 -0
  153. package/dist/internal/schema-runtime.d.ts.map +1 -0
  154. package/dist/internal/schema-runtime.js +10 -0
  155. package/dist/internal/schema-runtime.js.map +1 -0
  156. package/dist/internal/trigger-runtime-shared.d.ts +41 -0
  157. package/dist/internal/trigger-runtime-shared.d.ts.map +1 -0
  158. package/dist/internal/trigger-runtime-shared.js +132 -0
  159. package/dist/internal/trigger-runtime-shared.js.map +1 -0
  160. package/dist/internal/trigger-runtime.d.ts +12 -0
  161. package/dist/internal/trigger-runtime.d.ts.map +1 -0
  162. package/dist/internal/trigger-runtime.js +95 -0
  163. package/dist/internal/trigger-runtime.js.map +1 -0
  164. package/dist/internal/types.d.ts +7 -0
  165. package/dist/internal/types.d.ts.map +1 -0
  166. package/dist/internal/types.js +1 -0
  167. package/dist/remote-runtime.d.ts +585 -0
  168. package/dist/remote-runtime.d.ts.map +1 -0
  169. package/dist/remote-runtime.js +367 -0
  170. package/dist/remote-runtime.js.map +1 -0
  171. package/package.json +55 -4
  172. package/src/Action.ts +370 -0
  173. package/src/AgentChannel.ts +860 -0
  174. package/src/AgentChannelConformance.ts +124 -0
  175. package/src/AgentChannelRuntime.ts +616 -0
  176. package/src/AiProvider.ts +255 -0
  177. package/src/AuthMethod.ts +954 -0
  178. package/src/AuthMethodBinding.ts +112 -0
  179. package/src/AuthMethodExecution.ts +542 -0
  180. package/src/ConnectionResolver.ts +265 -0
  181. package/src/DefinitionError.ts +115 -0
  182. package/src/DualRegistry.ts +152 -0
  183. package/src/DualRuntime.ts +1081 -0
  184. package/src/Files.ts +89 -0
  185. package/src/Group.ts +292 -0
  186. package/src/Http.ts +1205 -0
  187. package/src/Mcp.ts +299 -0
  188. package/src/OAuth2TokenRefresher.ts +22 -0
  189. package/src/OAuthFlow.ts +874 -0
  190. package/src/Plugin.ts +912 -0
  191. package/src/PluginRuntime.ts +414 -0
  192. package/src/Runner.ts +248 -0
  193. package/src/ToolFile.ts +28 -0
  194. package/src/Trigger.ts +722 -0
  195. package/src/index.ts +158 -0
  196. package/src/internal/auth-context.ts +27 -0
  197. package/src/internal/auth-execution.ts +241 -0
  198. package/src/internal/auth-runtime.ts +20 -0
  199. package/src/internal/capabilities.ts +55 -0
  200. package/src/internal/children.ts +126 -0
  201. package/src/internal/collection-utils.ts +8 -0
  202. package/src/internal/dual/actions.ts +116 -0
  203. package/src/internal/dual/invoke.ts +307 -0
  204. package/src/internal/dual/registry.ts +340 -0
  205. package/src/internal/dual/triggers.ts +128 -0
  206. package/src/internal/handler-context.ts +57 -0
  207. package/src/internal/identifier.ts +27 -0
  208. package/src/internal/plugin-handlers.ts +146 -0
  209. package/src/internal/proto.ts +8 -0
  210. package/src/internal/schema-runtime.ts +15 -0
  211. package/src/internal/trigger-runtime-shared.ts +449 -0
  212. package/src/internal/trigger-runtime.ts +223 -0
  213. package/src/internal/types.ts +12 -0
  214. package/src/remote-runtime.ts +469 -0
@@ -0,0 +1,860 @@
1
+ /** Provider-neutral external Agent Channel contracts. @since 0.0.0 */
2
+ import type { Context, Effect, Pipeable, SchemaAST } from "effect";
3
+ import { Match, Predicate, Schema } from "effect";
4
+
5
+ import * as AuthMethod from "./AuthMethod.ts";
6
+ import { validateIdentifier } from "./internal/identifier.ts";
7
+ import { PipeableProto } from "./internal/proto.ts";
8
+ import * as ToolFile from "./ToolFile.ts";
9
+
10
+ export const TypeId = "@dual-os/sdk/AgentChannel" as const;
11
+ export type TypeId = typeof TypeId;
12
+
13
+ /** Keys are opaque and installation-scoped. Never parse them into product identities. */
14
+ export const Key = Schema.NonEmptyString.check(
15
+ Schema.isMaxLength(512),
16
+ Schema.isPattern(/^[^\p{Cc}]+$/u),
17
+ );
18
+ /**
19
+ * The command and result schemas a version uses. Absent means the original protocol, whose
20
+ * projection stays byte-identical for contracts already published with it. `v2` adds the stop
21
+ * event, conversation reads and output operations.
22
+ */
23
+ export const Protocol = Schema.Literals(["v2"]);
24
+ export type Protocol = typeof Protocol.Type;
25
+ /** What a `v2` destination can show. Every channel shows `present_outcome`. */
26
+ export const OperationKind = Schema.Literals([
27
+ "present_outcome",
28
+ "create_message",
29
+ "update_message",
30
+ "delete_message",
31
+ "set_reaction",
32
+ "set_activity",
33
+ "render_plan",
34
+ ]);
35
+ export type OperationKind = typeof OperationKind.Type;
36
+ export const Features = Schema.Struct({
37
+ assets: Schema.Boolean,
38
+ /** Whether replaying one durable delivery key is safe at the provider boundary. */
39
+ deliveryMode: Schema.Literals(["idempotent", "at-most-once"]),
40
+ deliveryState: Schema.Boolean,
41
+ directText: Schema.Literal(true),
42
+ groups: Schema.Boolean,
43
+ /** Maximum Unicode code points per physical text message, including server-added prefixes. */
44
+ maxTextLength: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0))),
45
+ mentions: Schema.Boolean,
46
+ /** `v2` only: the output operations every destination of this version shows. */
47
+ operations: Schema.optionalKey(Schema.Array(OperationKind)),
48
+ /** `v2` only: whether `read_context` returns the conversation's earlier messages. */
49
+ readContext: Schema.optionalKey(Schema.Boolean),
50
+ replies: Schema.Boolean,
51
+ /** Maximum age of the original responding input before preparation and delivery. */
52
+ responseWindowMs: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0))),
53
+ threads: Schema.Boolean,
54
+ });
55
+ export type Features = typeof Features.Type;
56
+
57
+ const HttpUrl = Schema.String.check(
58
+ Schema.isMaxLength(8192),
59
+ Schema.makeFilter(value => {
60
+ const url = URL.parse(value);
61
+ return (
62
+ url !== null &&
63
+ (url.protocol === "https:" || url.protocol === "http:") &&
64
+ url.username === "" &&
65
+ url.password === ""
66
+ );
67
+ }),
68
+ );
69
+ const Text = Schema.String.check(Schema.isMaxLength(65_536));
70
+ const ExtensionKey = Schema.String.check(
71
+ Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/),
72
+ );
73
+ const Extensions = Schema.Record(Schema.String, Schema.Json).check(
74
+ Schema.makeFilter(values => Object.keys(values).every(Schema.is(ExtensionKey))),
75
+ );
76
+ export const Destination = Schema.Struct({
77
+ conversationKey: Key,
78
+ kind: Schema.Literals(["direct", "group"]),
79
+ threadKey: Schema.optionalKey(Key),
80
+ });
81
+ export type Destination = typeof Destination.Type;
82
+ export const Asset = Schema.Struct({
83
+ assetKey: Key,
84
+ mediaType: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
85
+ url: HttpUrl,
86
+ });
87
+ export const Content = Schema.Struct({
88
+ assets: Schema.optionalKey(Schema.Array(Asset).check(Schema.isMaxLength(20))),
89
+ mentions: Schema.optionalKey(Schema.Array(Key).check(Schema.isMaxLength(100))),
90
+ replyToMessageKey: Schema.optionalKey(Key),
91
+ text: Text,
92
+ }).check(
93
+ Schema.makeFilter(content => content.text.length > 0 || (content.assets?.length ?? 0) > 0),
94
+ );
95
+ export type Content = typeof Content.Type;
96
+
97
+ /** The body is the unmodified UTF-8 provider body, before JSON parsing/signature verification. */
98
+ export const Request = Schema.Struct({
99
+ body: Schema.String.check(
100
+ Schema.isMaxLength(1_048_576),
101
+ Schema.makeFilter(body => new TextEncoder().encode(body).byteLength <= 1_048_576),
102
+ ),
103
+ headers: Schema.Record(
104
+ Schema.NonEmptyString.check(Schema.isMaxLength(256)),
105
+ Schema.String.check(Schema.isMaxLength(8192)),
106
+ ),
107
+ method: Schema.Literals(["GET", "POST", "PUT", "PATCH", "DELETE"]),
108
+ url: HttpUrl,
109
+ });
110
+ export type Request = typeof Request.Type;
111
+ const ResponseHeaders = Schema.Record(
112
+ Schema.NonEmptyString.check(Schema.isMaxLength(256), Schema.isPattern(/^[a-z0-9-]+$/)),
113
+ Schema.String.check(Schema.isMaxLength(8192)),
114
+ ).check(
115
+ Schema.makeFilter(headers =>
116
+ Object.keys(headers).every(name => name === "content-type" || name === "cache-control"),
117
+ ),
118
+ );
119
+ /** Bounded provider-required webhook response, returned only after request verification. */
120
+ export const WebhookResponse = Schema.Struct({
121
+ body: Schema.String.check(
122
+ Schema.isMaxLength(65_536),
123
+ Schema.makeFilter(body => new TextEncoder().encode(body).byteLength <= 65_536),
124
+ ),
125
+ headers: Schema.optionalKey(ResponseHeaders),
126
+ status: Schema.Int.check(Schema.isBetween({ maximum: 299, minimum: 200 })),
127
+ });
128
+ export type WebhookResponse = typeof WebhookResponse.Type;
129
+ export const Timestamp = Schema.String.check(
130
+ Schema.isMaxLength(64),
131
+ Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/),
132
+ Schema.makeFilter(value => {
133
+ const year = Number(value.slice(0, 4));
134
+ const month = Number(value.slice(5, 7));
135
+ const day = Number(value.slice(8, 10));
136
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
137
+ const days = month === 2 ? (leap ? 29 : 28) : [4, 6, 9, 11].includes(month) ? 30 : 31;
138
+ return (
139
+ month >= 1 &&
140
+ month <= 12 &&
141
+ day >= 1 &&
142
+ day <= days &&
143
+ Number(value.slice(11, 13)) < 24 &&
144
+ Number(value.slice(14, 16)) < 60 &&
145
+ Number(value.slice(17, 19)) < 60 &&
146
+ Schema.decodeUnknownExit(Schema.DateTimeUtcFromString)(value)._tag === "Success"
147
+ );
148
+ }),
149
+ );
150
+ export const VerifiedIdentity = Schema.Struct({ conversation: Destination, senderKey: Key });
151
+ export type VerifiedIdentity = typeof VerifiedIdentity.Type;
152
+ export const AccessOutcome = Schema.Struct({ decision: Schema.Literals(["allow", "deny"]) });
153
+ export type AccessOutcome = typeof AccessOutcome.Type;
154
+ export const Verification = Schema.Struct({ verified: Schema.Literal(true) });
155
+ export type Verification = typeof Verification.Type;
156
+ export const Exposure = Schema.Literals(["webhook", "host"]);
157
+ export const RequiredChecks = Schema.Tuple([Schema.Literal("access")]);
158
+ export const Message = Schema.Struct({
159
+ content: Content,
160
+ conversation: Destination,
161
+ eventKey: Key,
162
+ extensions: Schema.optionalKey(Extensions),
163
+ kind: Schema.Literal("message"),
164
+ messageKey: Key,
165
+ respond: Schema.Boolean,
166
+ senderKey: Key,
167
+ timestamp: Timestamp,
168
+ });
169
+ export const DeliveryUpdate = Schema.Struct({
170
+ deliveryKey: Key,
171
+ deliveryUpdateKey: Key,
172
+ eventKey: Key,
173
+ extensions: Schema.optionalKey(Extensions),
174
+ kind: Schema.Literal("delivery_update"),
175
+ providerDeliveryKey: Key,
176
+ sequence: Schema.Natural,
177
+ status: Schema.Literals(["succeeded", "failed"]),
178
+ timestamp: Timestamp,
179
+ });
180
+ /** Provider-confirmed own output. No content is accepted or added to conversation history. */
181
+ export const Echo = Schema.Struct({
182
+ ...VerifiedIdentity.fields,
183
+ eventKey: Key,
184
+ extensions: Schema.optionalKey(Extensions),
185
+ kind: Schema.Literal("echo"),
186
+ messageKey: Key,
187
+ timestamp: Timestamp,
188
+ });
189
+ export const Event = Schema.Union([Message, Echo, DeliveryUpdate]);
190
+ export type Event = typeof Event.Type;
191
+ /** `v2`: the sender asked the agent to stop working in this conversation. Access applies. */
192
+ export const Stop = Schema.Struct({
193
+ ...VerifiedIdentity.fields,
194
+ eventKey: Key,
195
+ extensions: Schema.optionalKey(Extensions),
196
+ kind: Schema.Literal("stop"),
197
+ timestamp: Timestamp,
198
+ });
199
+ export const EventV2 = Schema.Union([Message, Echo, DeliveryUpdate, Stop]);
200
+ export type EventV2 = typeof EventV2.Type;
201
+
202
+ /** The reactions every `v2` provider can show. Each provider maps them to its own emoji. */
203
+ export const Reaction = Schema.Literals([
204
+ "thumbs_up",
205
+ "thumbs_down",
206
+ "laugh",
207
+ "confused",
208
+ "heart",
209
+ "hooray",
210
+ "rocket",
211
+ "eyes",
212
+ ]);
213
+ export type Reaction = typeof Reaction.Type;
214
+ const ShortText = Schema.NonEmptyString.check(Schema.isMaxLength(200));
215
+ export const PlanItem = Schema.Struct({
216
+ itemKey: Key,
217
+ note: Schema.optionalKey(Schema.NonEmptyString.check(Schema.isMaxLength(2000))),
218
+ state: Schema.Literals(["pending", "in_progress", "completed", "failed"]),
219
+ title: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
220
+ });
221
+ /** A whole task list, always sent complete. Item keys are unique and stable across revisions. */
222
+ export const Plan = Schema.Struct({
223
+ items: Schema.Array(PlanItem).check(
224
+ Schema.isMaxLength(50),
225
+ Schema.makeFilter(items => new Set(items.map(item => item.itemKey)).size === items.length),
226
+ ),
227
+ title: Schema.optionalKey(Schema.NonEmptyString.check(Schema.isMaxLength(256))),
228
+ });
229
+ export type Plan = typeof Plan.Type;
230
+ /**
231
+ * One piece of `v2` output. A `reference` is the `providerDeliveryKey` the provider returned for
232
+ * the message or plan it made; only that provider reads it.
233
+ */
234
+ export const Operation = Schema.Union([
235
+ /**
236
+ * End the turn. `awaiting_input` ends it with a question, optionally with choices. The
237
+ * provider clears any activity it shows. Earlier parts of a long text arrive as
238
+ * `create_message` operations.
239
+ */
240
+ Schema.Struct({
241
+ kind: Schema.Literal("present_outcome"),
242
+ options: Schema.optionalKey(Schema.Array(ShortText).check(Schema.isMaxLength(10))),
243
+ outcome: Schema.Literals(["completed", "failed", "awaiting_input"]),
244
+ text: Text,
245
+ }).check(
246
+ Schema.makeFilter(
247
+ operation => operation.options === undefined || operation.outcome === "awaiting_input",
248
+ ),
249
+ ),
250
+ Schema.Struct({
251
+ kind: Schema.Literal("create_message"),
252
+ messageKey: Key,
253
+ text: Text.check(Schema.isMinLength(1)),
254
+ }),
255
+ Schema.Struct({
256
+ kind: Schema.Literal("update_message"),
257
+ messageKey: Key,
258
+ reference: Key,
259
+ text: Text.check(Schema.isMinLength(1)),
260
+ }),
261
+ Schema.Struct({ kind: Schema.Literal("delete_message"), messageKey: Key, reference: Key }),
262
+ /** Make the agent's reaction present (`active`) or absent. Desired state: repeating is safe. */
263
+ Schema.Struct({
264
+ active: Schema.Boolean,
265
+ kind: Schema.Literal("set_reaction"),
266
+ reaction: Reaction,
267
+ target: Schema.Union([
268
+ /** The message that started the turn: `activationMessageKey` of the command. */
269
+ Schema.Struct({ kind: Schema.Literal("activation") }),
270
+ Schema.Struct({ kind: Schema.Literal("message"), reference: Key }),
271
+ ]),
272
+ }),
273
+ /** What the agent is doing now, such as a typing indicator or a status line. Latest wins. */
274
+ Schema.Struct({
275
+ activity: Schema.Union([
276
+ Schema.Struct({ kind: Schema.Literal("working"), text: Schema.optionalKey(ShortText) }),
277
+ Schema.Struct({ kind: Schema.Literal("idle") }),
278
+ ]),
279
+ kind: Schema.Literal("set_activity"),
280
+ }),
281
+ /** Show this revision of the plan, replacing the one at `reference` when there is one. */
282
+ Schema.Struct({
283
+ kind: Schema.Literal("render_plan"),
284
+ plan: Plan,
285
+ reference: Schema.optionalKey(Key),
286
+ revision: Schema.Int.check(Schema.isGreaterThan(0)),
287
+ }),
288
+ ]);
289
+ export type Operation = typeof Operation.Type;
290
+ /** A message read back from the provider's conversation history. */
291
+ export const ContextMessage = Schema.Struct({
292
+ messageKey: Key,
293
+ /** The agent's own message. */
294
+ own: Schema.Boolean,
295
+ senderKey: Key,
296
+ senderName: Schema.optionalKey(Schema.NonEmptyString.check(Schema.isMaxLength(256))),
297
+ text: Text,
298
+ timestamp: Timestamp,
299
+ });
300
+ export type ContextMessage = typeof ContextMessage.Type;
301
+ /** Decoded originals are limited separately by the host; ordinary command JSON stays bounded. */
302
+ export const AssetFile = ToolFile.Schema.check(
303
+ Schema.makeFilter(
304
+ file =>
305
+ file.sizeBytes > 0 && file.sizeBytes <= 10_485_760 && file.contentBase64.length <= 13_981_016,
306
+ ),
307
+ );
308
+
309
+ /** Closed error vocabulary: provider messages, payloads, and causes never cross this seam. */
310
+ export const FailureCode = Schema.Literals([
311
+ "rejected",
312
+ "unauthorized",
313
+ "rate_limited",
314
+ "unavailable",
315
+ "unknown_completion",
316
+ /** The provider definitely refused this send because its delivery window was closed. */
317
+ "window_closed",
318
+ ]);
319
+ export class HandlerError extends Schema.TaggedError<HandlerError>()("AgentChannelHandlerError", {
320
+ code: FailureCode,
321
+ }) {}
322
+ export const ErrorCode = Schema.Literals([
323
+ "invalid_definition",
324
+ "not_found",
325
+ "invalid_input",
326
+ "invalid_auth",
327
+ "unsupported_feature",
328
+ "invalid_result",
329
+ "handler_unavailable",
330
+ "provider_failure",
331
+ ]);
332
+ export class ChannelError extends Schema.TaggedError<ChannelError>()("AgentChannelError", {
333
+ code: ErrorCode,
334
+ providerCode: Schema.optionalKey(FailureCode),
335
+ }) {
336
+ override get message(): string {
337
+ return `Agent Channel ${this.code}`;
338
+ }
339
+ }
340
+
341
+ const installedCommandSchema = <State extends Schema.Top>(state: State) => {
342
+ const operation = { extensions: Schema.optionalKey(Extensions), operationKey: Key };
343
+ const installed = { ...operation, installationKey: Key, state };
344
+ return Schema.Union([
345
+ Schema.Struct({
346
+ kind: Schema.Literal("activate"),
347
+ ...operation,
348
+ ingressUrl: HttpUrl,
349
+ state: Schema.optionalKey(state),
350
+ }),
351
+ Schema.Struct({ kind: Schema.Literal("deactivate"), ...installed }),
352
+ Schema.Struct({ kind: Schema.Literal("receive"), ...installed, request: Request }),
353
+ Schema.Struct({
354
+ kind: Schema.Literal("deliver"),
355
+ ...installed,
356
+ deliveryKey: Key,
357
+ destination: Destination,
358
+ content: Content,
359
+ /** Original responding input timestamp; retries must preserve this reference. */
360
+ responseToTimestamp: Schema.optionalKey(Timestamp),
361
+ }),
362
+ ]);
363
+ };
364
+
365
+ const installedCommandSchemaV2 = <State extends Schema.Top>(state: State) => {
366
+ const operation = { extensions: Schema.optionalKey(Extensions), operationKey: Key };
367
+ const installed = { ...operation, installationKey: Key, state };
368
+ return Schema.Union([
369
+ Schema.Struct({
370
+ kind: Schema.Literal("activate"),
371
+ ...operation,
372
+ ingressUrl: HttpUrl,
373
+ state: Schema.optionalKey(state),
374
+ }),
375
+ Schema.Struct({ kind: Schema.Literal("deactivate"), ...installed }),
376
+ Schema.Struct({ kind: Schema.Literal("receive"), ...installed, request: Request }),
377
+ Schema.Struct({
378
+ kind: Schema.Literal("deliver"),
379
+ ...installed,
380
+ /** The provider message that started the turn, when the host knows it. */
381
+ activationMessageKey: Schema.optionalKey(Key),
382
+ deliveryKey: Key,
383
+ destination: Destination,
384
+ operation: Operation,
385
+ /** Original responding input timestamp; retries must preserve this reference. */
386
+ responseToTimestamp: Schema.optionalKey(Timestamp),
387
+ }),
388
+ /** The conversation's latest messages, oldest first, at most `limit`. */
389
+ Schema.Struct({
390
+ kind: Schema.Literal("read_context"),
391
+ ...installed,
392
+ destination: Destination,
393
+ limit: Schema.Int.check(Schema.isBetween({ maximum: 200, minimum: 1 })),
394
+ }),
395
+ ]);
396
+ };
397
+
398
+ const installedResultSchema = <State extends Schema.Top>(state: State) => {
399
+ const operation = { extensions: Schema.optionalKey(Extensions), operationKey: Key };
400
+ return Schema.Union([
401
+ Schema.Struct({
402
+ kind: Schema.Literal("activate"),
403
+ ...operation,
404
+ installationKey: Key,
405
+ state,
406
+ /** Durable read-only provider state that the host must persist before retrying activation. */
407
+ checkpoint: Schema.optionalKey(Schema.Literal(true)),
408
+ /** The host must observe a verified webhook handshake before cutover. */
409
+ verification: Schema.optionalKey(Schema.Literal("ingress")),
410
+ }),
411
+ Schema.Struct({ kind: Schema.Literal("deactivate"), ...operation }),
412
+ Schema.Struct({
413
+ kind: Schema.Literal("receive"),
414
+ ...operation,
415
+ verified: Schema.Literal(true),
416
+ deniedEventKeys: Schema.optionalKey(Schema.Array(Key).check(Schema.isMaxLength(100))),
417
+ events: Schema.Array(Event).check(Schema.isMaxLength(100)),
418
+ response: Schema.optionalKey(WebhookResponse),
419
+ /** A provider-native setup challenge or ping proved the configured ingress. */
420
+ activationProof: Schema.optionalKey(Schema.Literal("verified")),
421
+ }),
422
+ Schema.Struct({
423
+ kind: Schema.Literal("deliver"),
424
+ ...operation,
425
+ deliveryKey: Key,
426
+ status: Schema.Literal("succeeded"),
427
+ providerDeliveryKey: Key,
428
+ }),
429
+ Schema.Struct({
430
+ kind: Schema.Literal("deliver"),
431
+ ...operation,
432
+ deliveryKey: Key,
433
+ status: Schema.Literal("pending"),
434
+ providerDeliveryKey: Key,
435
+ }),
436
+ Schema.Struct({
437
+ kind: Schema.Literal("deliver"),
438
+ ...operation,
439
+ deliveryKey: Key,
440
+ status: Schema.Literal("failed"),
441
+ code: Schema.Literals([
442
+ "rejected",
443
+ "unauthorized",
444
+ "rate_limited",
445
+ "unavailable",
446
+ "window_closed",
447
+ ]),
448
+ }),
449
+ ]);
450
+ };
451
+ // `v2` keeps every `v1` result except `receive`, which also carries stop events.
452
+ const installedResultSchemaV2 = <State extends Schema.Top>(state: State) => {
453
+ const operation = { extensions: Schema.optionalKey(Extensions), operationKey: Key };
454
+ return Schema.Union([
455
+ Schema.Struct({
456
+ kind: Schema.Literal("activate"),
457
+ ...operation,
458
+ installationKey: Key,
459
+ state,
460
+ /** Durable read-only provider state that the host must persist before retrying activation. */
461
+ checkpoint: Schema.optionalKey(Schema.Literal(true)),
462
+ /** The host must observe a verified webhook handshake before cutover. */
463
+ verification: Schema.optionalKey(Schema.Literal("ingress")),
464
+ }),
465
+ Schema.Struct({ kind: Schema.Literal("deactivate"), ...operation }),
466
+ Schema.Struct({
467
+ kind: Schema.Literal("receive"),
468
+ ...operation,
469
+ verified: Schema.Literal(true),
470
+ deniedEventKeys: Schema.optionalKey(Schema.Array(Key).check(Schema.isMaxLength(100))),
471
+ events: Schema.Array(EventV2).check(Schema.isMaxLength(100)),
472
+ response: Schema.optionalKey(WebhookResponse),
473
+ /** A provider-native setup challenge or ping proved the configured ingress. */
474
+ activationProof: Schema.optionalKey(Schema.Literal("verified")),
475
+ }),
476
+ Schema.Struct({
477
+ kind: Schema.Literal("deliver"),
478
+ ...operation,
479
+ deliveryKey: Key,
480
+ status: Schema.Literal("succeeded"),
481
+ providerDeliveryKey: Key,
482
+ }),
483
+ Schema.Struct({
484
+ kind: Schema.Literal("deliver"),
485
+ ...operation,
486
+ deliveryKey: Key,
487
+ status: Schema.Literal("pending"),
488
+ providerDeliveryKey: Key,
489
+ }),
490
+ Schema.Struct({
491
+ kind: Schema.Literal("deliver"),
492
+ ...operation,
493
+ deliveryKey: Key,
494
+ status: Schema.Literal("failed"),
495
+ code: Schema.Literals([
496
+ "rejected",
497
+ "unauthorized",
498
+ "rate_limited",
499
+ "unavailable",
500
+ "window_closed",
501
+ ]),
502
+ }),
503
+ Schema.Struct({
504
+ kind: Schema.Literal("read_context"),
505
+ ...operation,
506
+ messages: Schema.Array(ContextMessage).check(Schema.isMaxLength(200)),
507
+ }),
508
+ ]);
509
+ };
510
+ const readAssetCommand = <State extends Schema.Top>(state: State) =>
511
+ Schema.Struct({
512
+ asset: Asset,
513
+ extensions: Schema.optionalKey(Extensions),
514
+ identity: VerifiedIdentity,
515
+ installationKey: Key,
516
+ kind: Schema.Literal("read_asset"),
517
+ operationKey: Key,
518
+ state,
519
+ });
520
+ const readAssetResult = Schema.Struct({
521
+ assetKey: Key,
522
+ extensions: Schema.optionalKey(Extensions),
523
+ file: AssetFile,
524
+ kind: Schema.Literal("read_asset"),
525
+ operationKey: Key,
526
+ });
527
+ export const commandSchema = <State extends Schema.Top>(state: State) =>
528
+ Schema.Union([installedCommandSchema(state), readAssetCommand(state)]);
529
+ export const resultSchema = <State extends Schema.Top>(state: State) =>
530
+ Schema.Union([installedResultSchema(state), readAssetResult]);
531
+ export const commandSchemaV2 = <State extends Schema.Top>(state: State) =>
532
+ Schema.Union([installedCommandSchemaV2(state), readAssetCommand(state)]);
533
+ export const resultSchemaV2 = <State extends Schema.Top>(state: State) =>
534
+ Schema.Union([installedResultSchemaV2(state), readAssetResult]);
535
+ export type Command<State extends Schema.Top = Schema.Unknown> = ReturnType<
536
+ typeof commandSchema<State>
537
+ >["Type"];
538
+ export type Result<State extends Schema.Top = Schema.Unknown> = ReturnType<
539
+ typeof resultSchema<State>
540
+ >["Type"];
541
+ export type CommandV2<State extends Schema.Top = Schema.Unknown> = ReturnType<
542
+ typeof commandSchemaV2<State>
543
+ >["Type"];
544
+ export type ResultV2<State extends Schema.Top = Schema.Unknown> = ReturnType<
545
+ typeof resultSchemaV2<State>
546
+ >["Type"];
547
+ /** The command and result schemas of a Channel's protocol. */
548
+ export const schemasFor = (definition: Pick<Definition, "protocol" | "state">) =>
549
+ definition.protocol === "v2"
550
+ ? {
551
+ command: commandSchemaV2(definition.state),
552
+ result: resultSchemaV2(definition.state),
553
+ typeResult: resultSchemaV2(Schema.toType(definition.state)),
554
+ }
555
+ : {
556
+ command: commandSchema(definition.state),
557
+ result: resultSchema(definition.state),
558
+ typeResult: resultSchema(Schema.toType(definition.state)),
559
+ };
560
+
561
+ /**
562
+ * Encoded, JSON-safe runtime result suitable for host persistence. It accepts both protocols;
563
+ * a `v1` result decodes unchanged.
564
+ */
565
+ export const CheckedResult = resultSchemaV2(Schema.Json);
566
+ export type CheckedResult = typeof CheckedResult.Type;
567
+
568
+ /** Immutable catalog projection. Live schemas remain the execution validators. */
569
+ export const Contract = Schema.Struct({
570
+ definitionSettingsSchemaJson: Schema.Json,
571
+ deploymentSettingsSchemaJson: Schema.Json,
572
+ exposure: Exposure,
573
+ extensionsSchemaJson: Schema.Record(Schema.String, Schema.Json),
574
+ featuresJson: Features,
575
+ operationInputSchemaJson: Schema.Json,
576
+ operationOutputSchemaJson: Schema.Json,
577
+ protocol: Schema.optionalKey(Protocol),
578
+ requiredChecks: RequiredChecks,
579
+ stateSchemaJson: Schema.Json,
580
+ });
581
+ export type Contract = typeof Contract.Type;
582
+
583
+ export interface Definition<
584
+ Settings extends Schema.Top = Schema.Top,
585
+ Mappings extends Schema.Top = Schema.Top,
586
+ State extends Schema.Top = Schema.Top,
587
+ Methods extends readonly AuthMethod.Any[] = readonly AuthMethod.Any[],
588
+ > {
589
+ /** Absent for the original protocol. Publish a new release to change it. */
590
+ readonly protocol?: Protocol;
591
+ readonly definitionSettings: Settings;
592
+ readonly deploymentSettings: Mappings;
593
+ readonly state: State;
594
+ readonly features: Features;
595
+ readonly exposure: typeof Exposure.Type;
596
+ readonly requiredChecks: typeof RequiredChecks.Type;
597
+ /** Explicit subset of the provider's methods; [] means the channel accepts no connection. */
598
+ readonly authMethods: Methods;
599
+ /** Each key is provider/name. Values are checked on every command/result/event. */
600
+ readonly extensions?: Readonly<Record<string, Schema.Top>>;
601
+ }
602
+ /** Project the exact declaration once for SDK discovery and server contract comparison. */
603
+ export const projectContract = (
604
+ definition: Definition,
605
+ project: (schema: Schema.Top) => unknown = schema =>
606
+ Schema.toJsonSchemaDocument(schema, { onExcessProperty: "error" }),
607
+ ): Contract => {
608
+ const schemas = schemasFor(definition);
609
+ return Schema.decodeUnknownSync(Contract)(
610
+ {
611
+ definitionSettingsSchemaJson: project(definition.definitionSettings),
612
+ deploymentSettingsSchemaJson: project(definition.deploymentSettings),
613
+ exposure: definition.exposure,
614
+ extensionsSchemaJson: Object.fromEntries(
615
+ Object.entries(definition.extensions ?? {}).map(([key, schema]) => [key, project(schema)]),
616
+ ),
617
+ featuresJson: definition.features,
618
+ operationInputSchemaJson: project(schemas.command),
619
+ operationOutputSchemaJson: project(schemas.result),
620
+ ...(definition.protocol === undefined ? {} : { protocol: definition.protocol }),
621
+ requiredChecks: definition.requiredChecks,
622
+ stateSchemaJson: project(definition.state),
623
+ },
624
+ { onExcessProperty: "error" },
625
+ );
626
+ };
627
+ export interface Any extends Pipeable.Pipeable {
628
+ readonly [TypeId]: TypeId;
629
+ readonly identifier: string;
630
+ readonly name: string | undefined;
631
+ readonly description: string | undefined;
632
+ readonly definition: Definition;
633
+ }
634
+ export interface AgentChannel<
635
+ out Identifier extends string = string,
636
+ out D extends Definition = Definition,
637
+ > extends Any {
638
+ readonly identifier: Identifier;
639
+ readonly definition: D;
640
+ }
641
+ export type ResolvedAuth<Methods extends readonly AuthMethod.Any[]> =
642
+ Methods[number] extends infer M
643
+ ? M extends AuthMethod.Any
644
+ ? { readonly methodId: M["identifier"]; readonly value: AuthMethod.Value<M> }
645
+ : never
646
+ : never;
647
+ export type HandlerContext<D extends Definition> = {
648
+ readonly definitionSettings: D["definitionSettings"]["Type"];
649
+ readonly deploymentSettings: D["deploymentSettings"]["Type"];
650
+ } & (D["authMethods"] extends readonly []
651
+ ? {}
652
+ : number extends D["authMethods"]["length"]
653
+ ? { readonly auth?: ResolvedAuth<D["authMethods"]> }
654
+ : { readonly auth: ResolvedAuth<D["authMethods"]> });
655
+ /** The command a Channel's handler receives, by its protocol. */
656
+ export type CommandFor<D extends Definition> = D extends { readonly protocol: "v2" }
657
+ ? CommandV2<D["state"]>
658
+ : Command<D["state"]>;
659
+ /** The result a Channel's handler returns, by its protocol. */
660
+ export type ResultFor<D extends Definition> = D extends { readonly protocol: "v2" }
661
+ ? ResultV2<D["state"]>
662
+ : Result<D["state"]>;
663
+ export interface RuntimeContract<D extends Definition, R = never> {
664
+ readonly verify: (
665
+ request: Request,
666
+ context: HandlerContext<D>,
667
+ ) => Effect.Effect<Verification, HandlerError, R>;
668
+ readonly checkAccess: (
669
+ identity: VerifiedIdentity,
670
+ context: HandlerContext<D>,
671
+ ) => Effect.Effect<AccessOutcome, HandlerError, R>;
672
+ readonly execute: (
673
+ command: CommandFor<D>,
674
+ context: HandlerContext<D>,
675
+ ) => Effect.Effect<ResultFor<D>, HandlerError, R>;
676
+ }
677
+ /** Erased service representation. Input and output are checked by AgentChannelRuntime. */
678
+ export interface Handler<Id extends string = string> {
679
+ readonly channelId: Id;
680
+ readonly context: Context.Context<never>;
681
+ readonly runtime: {
682
+ readonly verify: (
683
+ request: unknown,
684
+ context: unknown,
685
+ ) => Effect.Effect<unknown, unknown, unknown>;
686
+ readonly checkAccess: (
687
+ identity: unknown,
688
+ context: unknown,
689
+ ) => Effect.Effect<unknown, unknown, unknown>;
690
+ readonly execute: (
691
+ command: unknown,
692
+ context: unknown,
693
+ ) => Effect.Effect<unknown, unknown, unknown>;
694
+ };
695
+ }
696
+ export const handlerKey = (channelId: string): string =>
697
+ `@dual-os/sdk/AgentChannel/Handler/${channelId}`;
698
+
699
+ const SchemaDefinition = Schema.declare(Schema.isSchema);
700
+ // Fail closed on holes such as Unknown nested inside an otherwise typed state schema.
701
+ const checkSchema = (schema: Schema.Top, allowOpaque = false): void => {
702
+ const seen = new Set<SchemaAST.AST>();
703
+ const visit = (ast: SchemaAST.AST): void => {
704
+ if (seen.has(ast)) return;
705
+ seen.add(ast);
706
+ if (
707
+ ast.annotations?.["parseOptions"] !== undefined ||
708
+ (ast.checks?.some(check => check.annotations?.["parseOptions"] !== undefined) ?? false)
709
+ ) {
710
+ throw new ChannelError({ code: "invalid_definition" });
711
+ }
712
+ ast.encoding?.forEach(link => {
713
+ visit(link.to);
714
+ });
715
+ Match.value(ast).pipe(
716
+ Match.tag("Unknown", "Any", "ObjectKeyword", () => {
717
+ throw new ChannelError({ code: "invalid_definition" });
718
+ }),
719
+ Match.tag("Declaration", declaration => {
720
+ if (!allowOpaque) throw new ChannelError({ code: "invalid_definition" });
721
+ declaration.typeParameters.forEach(visit);
722
+ }),
723
+ Match.tag("Objects", objects => {
724
+ objects.propertySignatures.forEach(property => visit(property.type));
725
+ objects.indexSignatures.forEach(index => {
726
+ // Filtered keys may disappear silently; a channel contract must retain them.
727
+ if (
728
+ index.parameter._tag !== "String" ||
729
+ index.parameter.checks !== undefined ||
730
+ index.parameter.encoding !== undefined
731
+ ) {
732
+ throw new ChannelError({ code: "invalid_definition" });
733
+ }
734
+ visit(index.parameter);
735
+ visit(index.type);
736
+ });
737
+ }),
738
+ Match.tag("Arrays", arrays => {
739
+ arrays.elements.forEach(visit);
740
+ arrays.rest.forEach(visit);
741
+ }),
742
+ Match.tag("Union", union => {
743
+ union.types.forEach(visit);
744
+ }),
745
+ Match.tag("Suspend", suspend => {
746
+ visit(suspend.thunk());
747
+ }),
748
+ Match.orElse(() => undefined),
749
+ );
750
+ };
751
+ visit(schema.ast);
752
+ };
753
+ const DefinitionSchema = Schema.Struct({
754
+ authMethods: Schema.Array(Schema.declare(AuthMethod.isAuthMethod)),
755
+ definitionSettings: SchemaDefinition,
756
+ deploymentSettings: SchemaDefinition,
757
+ exposure: Exposure,
758
+ extensions: Schema.optionalKey(
759
+ Schema.Record(Schema.String, SchemaDefinition).check(
760
+ Schema.makeFilter(values => Object.keys(values).every(Schema.is(ExtensionKey))),
761
+ ),
762
+ ),
763
+ features: Features,
764
+ protocol: Schema.optionalKey(Protocol),
765
+ requiredChecks: RequiredChecks,
766
+ state: SchemaDefinition,
767
+ }).check(
768
+ Schema.makeFilter(({ features, protocol }) => {
769
+ if (protocol === undefined) {
770
+ return features.operations === undefined && features.readContext === undefined;
771
+ }
772
+ const operations = features.operations ?? [];
773
+ return (
774
+ operations.includes("present_outcome") &&
775
+ new Set(operations).size === operations.length &&
776
+ // A long text arrives as messages before its outcome.
777
+ (features.maxTextLength === undefined || operations.includes("create_message"))
778
+ );
779
+ }),
780
+ );
781
+
782
+ /** Validate declarations at registration as well as at builder time. */
783
+ export const validateDefinition = (
784
+ definition: Definition,
785
+ providerId?: string,
786
+ methods?: readonly AuthMethod.Any[],
787
+ ): void => {
788
+ try {
789
+ Schema.decodeUnknownSync(DefinitionSchema)(definition, { onExcessProperty: "error" });
790
+ for (const schema of [
791
+ definition.definitionSettings,
792
+ definition.deploymentSettings,
793
+ definition.state,
794
+ ...Object.values(definition.extensions ?? {}),
795
+ ]) {
796
+ // A JSON projection must exist; it is catalog metadata, never the execution validator.
797
+ checkSchema(schema);
798
+ Schema.toJsonSchemaDocument(schema);
799
+ }
800
+ const seen = new Set<string>();
801
+ for (const method of definition.authMethods) {
802
+ if (seen.has(method.identifier) || (methods !== undefined && !methods.includes(method))) {
803
+ throw new ChannelError({ code: "invalid_definition" });
804
+ }
805
+ seen.add(method.identifier);
806
+ Schema.decodeUnknownSync(SchemaDefinition)(method.material);
807
+ Schema.decodeUnknownSync(SchemaDefinition)(method.value);
808
+ for (const schema of [
809
+ method.material,
810
+ method.value,
811
+ ...(method.setup === undefined ? [] : [method.setup]),
812
+ ]) {
813
+ checkSchema(schema, true);
814
+ }
815
+ }
816
+ if (
817
+ providerId !== undefined &&
818
+ Object.keys(definition.extensions ?? {}).some(key => !key.startsWith(`${providerId}/`))
819
+ ) {
820
+ throw new ChannelError({ code: "invalid_definition" });
821
+ }
822
+ } catch {
823
+ throw new ChannelError({ code: "invalid_definition" });
824
+ }
825
+ };
826
+
827
+ export interface ChannelOptions {
828
+ readonly name?: string;
829
+ readonly description?: string;
830
+ }
831
+
832
+ /** Create a Channel from its contract. */
833
+ export const make = <const Id extends string, const O extends Definition & ChannelOptions>(
834
+ identifier: Id,
835
+ options: O,
836
+ ): AgentChannel<Id, O> => {
837
+ validateIdentifier("agent-channel", identifier);
838
+ const { description, name, ...definition }: Definition & ChannelOptions = options;
839
+ validateDefinition(definition);
840
+ return {
841
+ ...PipeableProto,
842
+ [TypeId]: TypeId,
843
+ definition: Object.freeze({
844
+ ...definition,
845
+ authMethods: Object.freeze([...definition.authMethods]),
846
+ features: Object.freeze({ ...definition.features }),
847
+ requiredChecks: Object.freeze([...definition.requiredChecks]),
848
+ ...(definition.extensions === undefined
849
+ ? {}
850
+ : { extensions: Object.freeze({ ...definition.extensions }) }),
851
+ }),
852
+ description,
853
+ identifier,
854
+ name,
855
+ } as AgentChannel<Id, O>;
856
+ };
857
+ export const isAgentChannel = (value: unknown): value is Any =>
858
+ Predicate.hasProperty(value, TypeId);
859
+
860
+ export * as AgentChannel from "./AgentChannel.ts";