@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,616 @@
1
+ /** Checked execution of pinned Agent Channel capabilities. No product lifecycle or persistence. */
2
+ import { Cause, Context, Effect, Layer, Option, Predicate, Schema } from "effect";
3
+
4
+ import * as AgentChannel from "./AgentChannel.ts";
5
+ import { ConnectionResolver } from "./ConnectionResolver.ts";
6
+ import type { AuthResolutionRequirements } from "./internal/auth-execution.ts";
7
+ import { findHandler } from "./internal/handler-context.ts";
8
+ import type * as Plugin from "./Plugin.ts";
9
+
10
+ /** Host-only connection authority context. It is never included in handler inputs. */
11
+ export const ConnectionContext = Schema.Struct({
12
+ actor: Schema.optionalKey(
13
+ Schema.Union([
14
+ Schema.Struct({ kind: Schema.Literal("user"), userId: AgentChannel.Key }),
15
+ Schema.Struct({
16
+ kind: Schema.Literal("system"),
17
+ reason: Schema.optionalKey(AgentChannel.Key),
18
+ }),
19
+ ]),
20
+ ),
21
+ source: Schema.optionalKey(
22
+ Schema.Struct({ kind: AgentChannel.Key, subjectId: Schema.optionalKey(AgentChannel.Key) }),
23
+ ),
24
+ tenant: Schema.optionalKey(
25
+ Schema.Struct({ organizationId: AgentChannel.Key, teamId: AgentChannel.Key }),
26
+ ),
27
+ });
28
+ const Pin = Schema.Struct({ channelId: AgentChannel.Key });
29
+ const Input = Schema.Struct({
30
+ ...Pin.fields,
31
+ command: Schema.Unknown,
32
+ connectionId: Schema.optionalKey(AgentChannel.Key),
33
+ context: Schema.optionalKey(ConnectionContext),
34
+ definitionSettings: Schema.Unknown,
35
+ deploymentSettings: Schema.Unknown,
36
+ });
37
+ const AccessInput = Schema.Struct({
38
+ ...Pin.fields,
39
+ connectionId: Schema.optionalKey(AgentChannel.Key),
40
+ context: Schema.optionalKey(ConnectionContext),
41
+ definitionSettings: Schema.Unknown,
42
+ deploymentSettings: Schema.Unknown,
43
+ identity: AgentChannel.VerifiedIdentity,
44
+ });
45
+ export type Pin = typeof Pin.Type;
46
+ export type Input = typeof Input.Type;
47
+ export interface Interface {
48
+ /** Reauthorize an already verified identity; does not verify a transport or execute provider operations. */
49
+ readonly checkAccess: (
50
+ input: unknown,
51
+ ) => Effect.Effect<AgentChannel.AccessOutcome, AgentChannel.ChannelError>;
52
+ readonly get: (pin: unknown) => Effect.Effect<AgentChannel.Definition, AgentChannel.ChannelError>;
53
+ readonly decodeDefinitionSettings: (
54
+ input: unknown,
55
+ ) => Effect.Effect<Schema.Json, AgentChannel.ChannelError>;
56
+ readonly decodeDeploymentSettings: (
57
+ input: unknown,
58
+ ) => Effect.Effect<Schema.Json, AgentChannel.ChannelError>;
59
+ readonly execute: (
60
+ input: unknown,
61
+ ) => Effect.Effect<AgentChannel.CheckedResult, AgentChannel.ChannelError>;
62
+ }
63
+ export class Service extends Context.Service<Service, Interface>()(
64
+ "@dual-os/sdk/AgentChannelRuntime",
65
+ ) {}
66
+
67
+ type Definitions<P extends Plugin.Any> = P["agentChannels"][number]["definition"];
68
+ type ChannelHandlers<P extends Plugin.Any, C> = C extends AgentChannel.Any
69
+ ? AgentChannel.Handler<`${P["identifier"]}/${C["identifier"]}`>
70
+ : never;
71
+ type PluginHandlers<P> = P extends Plugin.Any
72
+ ? ChannelHandlers<P, P["agentChannels"][number]>
73
+ : never;
74
+ type ExtensionRequirements<D> = D extends {
75
+ readonly extensions: infer Extensions extends Readonly<Record<string, Schema.Top>>;
76
+ }
77
+ ?
78
+ | Extensions[keyof Extensions]["DecodingServices"]
79
+ | Extensions[keyof Extensions]["EncodingServices"]
80
+ : never;
81
+ export type Requirements<P extends Plugin.Any> =
82
+ | PluginHandlers<P>
83
+ | ExtensionRequirements<Definitions<P>>
84
+ | Definitions<P>["definitionSettings"]["DecodingServices"]
85
+ | Definitions<P>["deploymentSettings"]["DecodingServices"]
86
+ | Definitions<P>["state"]["DecodingServices"]
87
+ | Definitions<P>["state"]["EncodingServices"]
88
+ | AuthResolutionRequirements<Definitions<P>["authMethods"]>
89
+ | (Definitions<P>["authMethods"][number] extends never ? never : ConnectionResolver.Service);
90
+
91
+ const fail = (code: typeof AgentChannel.ErrorCode.Type) => new AgentChannel.ChannelError({ code });
92
+ const safe = <A, E, R>(
93
+ effect: Effect.Effect<A, E, R>,
94
+ code: typeof AgentChannel.ErrorCode.Type,
95
+ ): Effect.Effect<A, AgentChannel.ChannelError, R> =>
96
+ effect.pipe(
97
+ Effect.catchCause(cause =>
98
+ Cause.hasInterrupts(cause) ? Effect.interrupt : Effect.fail(fail(code)),
99
+ ),
100
+ );
101
+ const AssetResultJson = Schema.Json.check(
102
+ Schema.makeFilter(
103
+ value => new TextEncoder().encode(JSON.stringify(value)).byteLength <= 14_680_064,
104
+ ),
105
+ );
106
+ const Json = Schema.Json.check(
107
+ Schema.makeFilter(
108
+ value => new TextEncoder().encode(JSON.stringify(value)).byteLength <= 2_097_152,
109
+ ),
110
+ );
111
+ const strict = { onExcessProperty: "error" } as const;
112
+ const decode = <S extends Schema.Top>(
113
+ schema: S,
114
+ input: unknown,
115
+ code: typeof AgentChannel.ErrorCode.Type,
116
+ ) =>
117
+ safe(
118
+ Effect.suspend(() => Schema.decodeUnknownEffect(schema)(input, strict)),
119
+ code,
120
+ );
121
+ const json = (value: unknown, code: typeof AgentChannel.ErrorCode.Type) =>
122
+ decode(Json, value, code);
123
+ const decodeData = Effect.fn("AgentChannelRuntime.decodeData")(function* <S extends Schema.Top>(
124
+ schema: S,
125
+ input: unknown,
126
+ code: typeof AgentChannel.ErrorCode.Type,
127
+ largeAssetResult = false,
128
+ ) {
129
+ const boundary = largeAssetResult ? AssetResultJson : Json;
130
+ yield* decode(boundary, input, code);
131
+ const value = yield* decode(schema, input, code);
132
+ yield* decode(boundary, value, code);
133
+ return value;
134
+ });
135
+
136
+ const invokeHandler = (
137
+ handler: AgentChannel.Handler,
138
+ effect: Effect.Effect<unknown, unknown, unknown>,
139
+ ) =>
140
+ effect.pipe(
141
+ Effect.provideContext(handler.context),
142
+ Effect.catch(error =>
143
+ decode(
144
+ Schema.TaggedStruct("AgentChannelHandlerError", { code: AgentChannel.FailureCode }),
145
+ Predicate.isObject(error) ? { ...error } : error,
146
+ "provider_failure",
147
+ ).pipe(
148
+ Effect.flatMap(checked =>
149
+ Effect.fail(
150
+ new AgentChannel.ChannelError({ code: "provider_failure", providerCode: checked.code }),
151
+ ),
152
+ ),
153
+ ),
154
+ ),
155
+ Effect.catchCause(cause => {
156
+ if (Cause.hasInterrupts(cause)) return Effect.interrupt;
157
+ const error = Cause.findErrorOption(cause);
158
+ return Option.isSome(error) && error.value instanceof AgentChannel.ChannelError
159
+ ? Effect.fail(error.value)
160
+ : Effect.fail(fail("provider_failure"));
161
+ }),
162
+ );
163
+
164
+ const checkExtensions = Effect.fn("AgentChannelRuntime.checkExtensions")(function* (
165
+ definition: AgentChannel.Definition,
166
+ extensions: Readonly<Record<string, Schema.Json>> | undefined,
167
+ code: typeof AgentChannel.ErrorCode.Type,
168
+ ) {
169
+ for (const [key, value] of Object.entries(extensions ?? {})) {
170
+ const schema = definition.extensions?.[key];
171
+ if (schema === undefined) return yield* fail(code);
172
+ // Extensions are checked values, not a provider payload escape hatch. No normalization.
173
+ yield* decodeData(Schema.toType(schema), value, code);
174
+ }
175
+ });
176
+ const checkMessage = (
177
+ features: AgentChannel.Features,
178
+ destination: AgentChannel.Destination,
179
+ content: AgentChannel.Content,
180
+ ): boolean =>
181
+ (destination.kind === "direct" || features.groups) &&
182
+ (destination.threadKey === undefined || features.threads) &&
183
+ (content.mentions === undefined || features.mentions) &&
184
+ (content.assets === undefined || features.assets) &&
185
+ (content.replyToMessageKey === undefined || features.replies);
186
+
187
+ const checkDelivery = (
188
+ features: AgentChannel.Features,
189
+ command: Extract<AgentChannel.Command | AgentChannel.CommandV2, { kind: "deliver" }>,
190
+ ): boolean =>
191
+ "content" in command
192
+ ? checkMessage(features, command.destination, command.content)
193
+ : checkMessage(features, command.destination, { text: "" }) &&
194
+ (features.operations ?? []).includes(command.operation.kind);
195
+
196
+ const Material = Schema.Struct({
197
+ connectionId: AgentChannel.Key,
198
+ // The plugin worker's resolver marks its JSON-carried material.
199
+ encoding: Schema.optionalKey(Schema.Literal("json")),
200
+ material: Schema.Unknown,
201
+ methodId: AgentChannel.Key,
202
+ providerId: AgentChannel.Key,
203
+ setup: Schema.Unknown,
204
+ });
205
+ const ResolveHook = Schema.declare(
206
+ (value): value is (material: unknown, context: { readonly setup: unknown }) => unknown =>
207
+ Predicate.isFunction(value),
208
+ );
209
+ const resolveAuth = Effect.fn("AgentChannelRuntime.resolveAuth")(function* (
210
+ providerId: string,
211
+ channelId: string,
212
+ definition: AgentChannel.Definition,
213
+ connectionId: string | undefined,
214
+ phase: string,
215
+ context: typeof ConnectionContext.Type | undefined,
216
+ ) {
217
+ if (definition.authMethods.length === 0) {
218
+ if (connectionId !== undefined) return yield* fail("invalid_auth");
219
+ return undefined;
220
+ }
221
+ // The host alone selects/authorizes the pinned connection. Its records never reach the plugin.
222
+ if (connectionId === undefined) return yield* fail("invalid_auth");
223
+ const resolver = yield* Effect.serviceOption(ConnectionResolver.Service);
224
+ if (Option.isNone(resolver)) return yield* fail("invalid_auth");
225
+ const raw = yield* safe(
226
+ Effect.suspend(() =>
227
+ resolver.value.resolve({
228
+ connectionId,
229
+ target: { phase, providerId, targetId: channelId, targetKind: "agent-channel" },
230
+ ...(context === undefined ? {} : { context }),
231
+ }),
232
+ ),
233
+ "invalid_auth",
234
+ );
235
+ const resolved = yield* decode(Material, raw, "invalid_auth");
236
+ if (resolved.providerId !== providerId || resolved.connectionId !== connectionId) {
237
+ return yield* fail("invalid_auth");
238
+ }
239
+ const method = definition.authMethods.find(
240
+ method =>
241
+ resolved.methodId === method.identifier ||
242
+ resolved.methodId === `${providerId}/${method.identifier}`,
243
+ );
244
+ if (method === undefined) return yield* fail("invalid_auth");
245
+ // Material the worker received as JSON decodes through each schema's JSON codec.
246
+ const decodeAuth = <S extends Schema.Top>(schema: S, value: unknown) =>
247
+ decode(
248
+ resolved.encoding === "json" ? Schema.toCodecJson(schema) : schema,
249
+ value,
250
+ "invalid_auth",
251
+ );
252
+ const setup =
253
+ method.setup === undefined
254
+ ? yield* decode(Schema.Undefined, resolved.setup, "invalid_auth")
255
+ : yield* decodeAuth(method.setup, resolved.setup);
256
+ const material = yield* decodeAuth(method.material, resolved.material);
257
+ let value: unknown;
258
+ if (method.resolve === undefined) {
259
+ value =
260
+ method.material === method.value
261
+ ? material
262
+ : yield* decode(method.value, material, "invalid_auth");
263
+ } else {
264
+ const hook = yield* decode(ResolveHook, method.resolve, "invalid_auth");
265
+ const output = yield* safe(
266
+ Effect.suspend(() => {
267
+ const result = hook(material, { setup });
268
+ return Effect.isEffect(result) ? result : Effect.succeed(result);
269
+ }),
270
+ "invalid_auth",
271
+ );
272
+ value = yield* decode(method.value, output, "invalid_auth");
273
+ }
274
+ // Schemas may use Redacted for auth. Only this checked value, never material/setup/binding,
275
+ // is exposed to the command handler.
276
+ return { methodId: method.identifier, value };
277
+ });
278
+
279
+ const find = (plugins: readonly Plugin.Any[], pin: Pin) => {
280
+ for (const provider of plugins) {
281
+ const channel = provider.agentChannels.find(
282
+ channel => `${provider.identifier}/${channel.identifier}` === pin.channelId,
283
+ );
284
+ if (channel !== undefined) return { definition: channel.definition, provider };
285
+ }
286
+ return undefined;
287
+ };
288
+
289
+ /** Build from the installed plugins. */
290
+ export const layer = <P extends Plugin.Any>(
291
+ plugins: readonly P[],
292
+ ): Layer.Layer<Service, never, Requirements<P>> =>
293
+ Layer.effect(
294
+ Service,
295
+ Effect.gen(function* () {
296
+ const context = yield* Effect.context<Requirements<P>>();
297
+ const get = Effect.fn("AgentChannelRuntime.get")(function* (input: unknown) {
298
+ const pin = yield* decode(Pin, input, "invalid_input");
299
+ const found = find(plugins, pin);
300
+ if (found === undefined) return yield* fail("not_found");
301
+ yield* safe(
302
+ Effect.sync(() => {
303
+ AgentChannel.validateDefinition(
304
+ found.definition,
305
+ found.provider.identifier,
306
+ found.provider.authMethods,
307
+ );
308
+ }),
309
+ "invalid_definition",
310
+ );
311
+ const handler = Option.fromNullishOr(
312
+ findHandler(
313
+ context,
314
+ found.provider.identifier,
315
+ AgentChannel.handlerKey(pin.channelId),
316
+ ) as AgentChannel.Handler | undefined,
317
+ );
318
+ if (
319
+ Option.isNone(handler) ||
320
+ !Predicate.isObject(handler.value.runtime) ||
321
+ !Predicate.isFunction(handler.value.runtime.execute) ||
322
+ !Predicate.isFunction(handler.value.runtime.verify) ||
323
+ !Predicate.isFunction(handler.value.runtime.checkAccess)
324
+ ) {
325
+ return yield* fail("handler_unavailable");
326
+ }
327
+ return found.definition;
328
+ });
329
+ const checkAccess = Effect.fn("AgentChannelRuntime.checkAccess")(function* (input: unknown) {
330
+ yield* json(input, "invalid_input");
331
+ const request = yield* decode(AccessInput, input, "invalid_input");
332
+ const definition = yield* get({ channelId: request.channelId });
333
+ const found = find(plugins, request);
334
+ if (found === undefined) return yield* fail("not_found");
335
+ const definitionSettings = yield* decodeData(
336
+ definition.definitionSettings,
337
+ request.definitionSettings,
338
+ "invalid_input",
339
+ );
340
+ const deploymentSettings = yield* decodeData(
341
+ definition.deploymentSettings,
342
+ request.deploymentSettings,
343
+ "invalid_input",
344
+ );
345
+ if (!checkMessage(definition.features, request.identity.conversation, { text: "" })) {
346
+ return yield* fail("unsupported_feature");
347
+ }
348
+ const auth = yield* resolveAuth(
349
+ found.provider.identifier,
350
+ request.channelId,
351
+ definition,
352
+ request.connectionId,
353
+ "check_access",
354
+ request.context,
355
+ );
356
+ const handler = Option.fromNullishOr(
357
+ findHandler(
358
+ context,
359
+ found.provider.identifier,
360
+ AgentChannel.handlerKey(request.channelId),
361
+ ) as AgentChannel.Handler | undefined,
362
+ );
363
+ if (Option.isNone(handler)) return yield* fail("handler_unavailable");
364
+ const output = yield* invokeHandler(
365
+ handler.value,
366
+ Effect.suspend(() =>
367
+ handler.value.runtime.checkAccess(request.identity, {
368
+ definitionSettings,
369
+ deploymentSettings,
370
+ ...(auth === undefined ? {} : { auth }),
371
+ }),
372
+ ),
373
+ );
374
+ return yield* decodeData(AgentChannel.AccessOutcome, output, "invalid_result");
375
+ });
376
+ const execute = Effect.fn("AgentChannelRuntime.execute")(function* (input: unknown) {
377
+ yield* json(input, "invalid_input");
378
+ const request = yield* decode(Input, input, "invalid_input");
379
+ const definition = yield* get({ channelId: request.channelId });
380
+ const found = find(plugins, request);
381
+ if (found === undefined) return yield* fail("not_found");
382
+ const definitionSettings = yield* decodeData(
383
+ definition.definitionSettings,
384
+ request.definitionSettings,
385
+ "invalid_input",
386
+ );
387
+ const deploymentSettings = yield* decodeData(
388
+ definition.deploymentSettings,
389
+ request.deploymentSettings,
390
+ "invalid_input",
391
+ );
392
+ const schemas = AgentChannel.schemasFor(definition);
393
+ const command: AgentChannel.Command | AgentChannel.CommandV2 = yield* decodeData(
394
+ schemas.command,
395
+ request.command,
396
+ "invalid_input",
397
+ );
398
+ yield* checkExtensions(definition, command.extensions, "invalid_input");
399
+ if (command.kind === "deliver" && command.operationKey !== command.deliveryKey) {
400
+ return yield* fail("invalid_input");
401
+ }
402
+ if (command.kind === "deliver" && !checkDelivery(definition.features, command)) {
403
+ return yield* fail("unsupported_feature");
404
+ }
405
+ if (
406
+ command.kind === "deliver" &&
407
+ "operation" in command &&
408
+ command.operation.kind === "set_reaction" &&
409
+ command.operation.target.kind === "activation" &&
410
+ command.activationMessageKey === undefined
411
+ ) {
412
+ return yield* fail("invalid_input");
413
+ }
414
+ if (
415
+ command.kind === "read_context" &&
416
+ (definition.features.readContext !== true ||
417
+ !checkMessage(definition.features, command.destination, { text: "" }))
418
+ ) {
419
+ return yield* fail("unsupported_feature");
420
+ }
421
+ const handler = Option.fromNullishOr(
422
+ findHandler(
423
+ context,
424
+ found.provider.identifier,
425
+ AgentChannel.handlerKey(request.channelId),
426
+ ) as AgentChannel.Handler | undefined,
427
+ );
428
+ if (Option.isNone(handler)) return yield* fail("handler_unavailable");
429
+ const auth = yield* resolveAuth(
430
+ found.provider.identifier,
431
+ request.channelId,
432
+ definition,
433
+ request.connectionId,
434
+ command.kind,
435
+ request.context,
436
+ );
437
+ // Keep correlation facts independent of any mutation by provider code.
438
+ const operationKey = command.operationKey;
439
+ const kind = command.kind;
440
+ const deliveryKey = command.kind === "deliver" ? command.deliveryKey : undefined;
441
+ const assetKey = command.kind === "read_asset" ? command.asset.assetKey : undefined;
442
+ const adapterContext = {
443
+ definitionSettings,
444
+ deploymentSettings,
445
+ ...(auth === undefined ? {} : { auth }),
446
+ };
447
+ if (
448
+ !Predicate.isFunction(handler.value.runtime.execute) ||
449
+ !Predicate.isFunction(handler.value.runtime.verify) ||
450
+ !Predicate.isFunction(handler.value.runtime.checkAccess)
451
+ ) {
452
+ return yield* fail("handler_unavailable");
453
+ }
454
+ if (command.kind === "receive") {
455
+ yield* decodeData(
456
+ AgentChannel.Verification,
457
+ yield* invokeHandler(
458
+ handler.value,
459
+ Effect.suspend(() => handler.value.runtime.verify(command.request, adapterContext)),
460
+ ),
461
+ "invalid_result",
462
+ );
463
+ }
464
+ if (command.kind === "read_asset") {
465
+ if (!definition.features.assets) return yield* fail("unsupported_feature");
466
+ const decision = yield* decodeData(
467
+ AgentChannel.AccessOutcome,
468
+ yield* invokeHandler(
469
+ handler.value,
470
+ Effect.suspend(() =>
471
+ handler.value.runtime.checkAccess(command.identity, adapterContext),
472
+ ),
473
+ ),
474
+ "invalid_result",
475
+ );
476
+ if (decision.decision !== "allow") {
477
+ return yield* new AgentChannel.ChannelError({
478
+ code: "provider_failure",
479
+ providerCode: "unauthorized",
480
+ });
481
+ }
482
+ }
483
+ const output = yield* invokeHandler(
484
+ handler.value,
485
+ Effect.suspend(() => handler.value.runtime.execute(command, adapterContext)),
486
+ );
487
+ const result: AgentChannel.Result | AgentChannel.ResultV2 = yield* decodeData(
488
+ schemas.typeResult,
489
+ output,
490
+ "invalid_result",
491
+ kind === "read_asset",
492
+ );
493
+ if (
494
+ result.kind !== kind ||
495
+ result.operationKey !== operationKey ||
496
+ (result.kind === "deliver" && result.deliveryKey !== deliveryKey) ||
497
+ (result.kind === "read_asset" && result.assetKey !== assetKey)
498
+ ) {
499
+ return yield* fail("invalid_result");
500
+ }
501
+ yield* checkExtensions(definition, result.extensions, "invalid_result");
502
+ if (
503
+ result.kind === "deliver" &&
504
+ result.status === "pending" &&
505
+ !definition.features.deliveryState
506
+ ) {
507
+ return yield* fail("invalid_result");
508
+ }
509
+ const allowedEvents: AgentChannel.EventV2[] = [];
510
+ const deniedEventKeys: string[] = [];
511
+ if (result.kind === "receive") {
512
+ if (result.deniedEventKeys !== undefined) return yield* fail("invalid_result");
513
+ if (
514
+ result.activationProof === "verified" &&
515
+ (result.response === undefined || result.events.length !== 0)
516
+ ) {
517
+ return yield* fail("invalid_result");
518
+ }
519
+ const eventKeys = new Set<string>();
520
+ for (const event of result.events) {
521
+ if (eventKeys.has(event.eventKey)) return yield* fail("invalid_result");
522
+ eventKeys.add(event.eventKey);
523
+ if (
524
+ event.kind === "message" &&
525
+ !checkMessage(definition.features, event.conversation, event.content)
526
+ ) {
527
+ return yield* fail("invalid_result");
528
+ }
529
+ if (
530
+ (event.kind === "echo" || event.kind === "stop") &&
531
+ !checkMessage(definition.features, event.conversation, { text: "" })
532
+ ) {
533
+ return yield* fail("invalid_result");
534
+ }
535
+ if (event.kind === "delivery_update" && !definition.features.deliveryState) {
536
+ return yield* fail("invalid_result");
537
+ }
538
+ yield* checkExtensions(definition, event.extensions, "invalid_result");
539
+ let denied = false;
540
+ if (event.kind === "message" || event.kind === "echo" || event.kind === "stop") {
541
+ const identity = yield* decodeData(
542
+ AgentChannel.VerifiedIdentity,
543
+ { conversation: event.conversation, senderKey: event.senderKey },
544
+ "invalid_result",
545
+ );
546
+ const decision = yield* decodeData(
547
+ AgentChannel.AccessOutcome,
548
+ yield* invokeHandler(
549
+ handler.value,
550
+ Effect.suspend(() => handler.value.runtime.checkAccess(identity, adapterContext)),
551
+ ),
552
+ "invalid_result",
553
+ );
554
+ denied = decision.decision === "deny";
555
+ }
556
+ if (denied) {
557
+ deniedEventKeys.push(event.eventKey);
558
+ continue;
559
+ }
560
+ allowedEvents.push(event);
561
+ }
562
+ }
563
+ // Store the encoded state so subsequent pinned commands can decode it exactly once.
564
+ const encoded = yield* safe(
565
+ Schema.encodeUnknownEffect(schemas.result)(
566
+ result.kind === "receive"
567
+ ? {
568
+ ...result,
569
+ events: allowedEvents,
570
+ ...(deniedEventKeys.length === 0 ? {} : { deniedEventKeys }),
571
+ }
572
+ : result,
573
+ strict,
574
+ ),
575
+ "invalid_result",
576
+ );
577
+ return yield* decodeData(
578
+ AgentChannel.CheckedResult,
579
+ encoded,
580
+ "invalid_result",
581
+ kind === "read_asset",
582
+ );
583
+ });
584
+ // SAFETY: Requirements above account for each schema, auth method and Channel handler. The
585
+ // dynamic lookup erases their union; the captured application context discharges it here.
586
+ const run = <A>(
587
+ effect: Effect.Effect<A, AgentChannel.ChannelError, unknown>,
588
+ ): Effect.Effect<A, AgentChannel.ChannelError> =>
589
+ // @effect-diagnostics-next-line unsafeEffectTypeAssertion:off -- Channel handlers are checked and their captured context satisfies the declared Requirements (SAFETY above).
590
+ effect.pipe(Effect.provideContext(context)) as Effect.Effect<A, AgentChannel.ChannelError>;
591
+ const decodeSettings = (field: "definitionSettings" | "deploymentSettings") =>
592
+ Effect.fn("AgentChannelRuntime.decodeSettings")(function* (input: unknown) {
593
+ const request = yield* decode(
594
+ Schema.Struct({ ...Pin.fields, value: Schema.Json }),
595
+ input,
596
+ "invalid_input",
597
+ );
598
+ const definition = yield* get({ channelId: request.channelId });
599
+ const schema = definition[field];
600
+ const value = yield* decodeData(schema, request.value, "invalid_input");
601
+ return yield* json(
602
+ yield* safe(Schema.encodeUnknownEffect(schema)(value, strict), "invalid_input"),
603
+ "invalid_input",
604
+ );
605
+ });
606
+ return Service.of({
607
+ checkAccess: input => run(checkAccess(input)),
608
+ decodeDefinitionSettings: input => run(decodeSettings("definitionSettings")(input)),
609
+ decodeDeploymentSettings: input => run(decodeSettings("deploymentSettings")(input)),
610
+ execute: input => run(execute(input)),
611
+ get: input => run(get(input)),
612
+ });
613
+ }),
614
+ );
615
+
616
+ export * as AgentChannelRuntime from "./AgentChannelRuntime.ts";