@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.
- package/LICENSE +4 -0
- package/README.md +19 -2
- package/dist/Action.d.ts +201 -0
- package/dist/Action.d.ts.map +1 -0
- package/dist/Action.js +172 -0
- package/dist/Action.js.map +1 -0
- package/dist/AgentChannel.d.ts +1717 -0
- package/dist/AgentChannel.d.ts.map +1 -0
- package/dist/AgentChannel.js +708 -0
- package/dist/AgentChannel.js.map +1 -0
- package/dist/AgentChannelConformance.d.ts +1543 -0
- package/dist/AgentChannelConformance.d.ts.map +1 -0
- package/dist/AgentChannelConformance.js +163 -0
- package/dist/AgentChannelConformance.js.map +1 -0
- package/dist/AgentChannelRuntime.d.ts +78 -0
- package/dist/AgentChannelRuntime.d.ts.map +1 -0
- package/dist/AgentChannelRuntime.js +270 -0
- package/dist/AgentChannelRuntime.js.map +1 -0
- package/dist/AiProvider.d.ts +132 -0
- package/dist/AiProvider.d.ts.map +1 -0
- package/dist/AiProvider.js +100 -0
- package/dist/AiProvider.js.map +1 -0
- package/dist/AuthMethod.d.ts +381 -0
- package/dist/AuthMethod.d.ts.map +1 -0
- package/dist/AuthMethod.js +225 -0
- package/dist/AuthMethod.js.map +1 -0
- package/dist/AuthMethodBinding.d.ts +77 -0
- package/dist/AuthMethodBinding.d.ts.map +1 -0
- package/dist/AuthMethodBinding.js +31 -0
- package/dist/AuthMethodBinding.js.map +1 -0
- package/dist/AuthMethodExecution.d.ts +223 -0
- package/dist/AuthMethodExecution.d.ts.map +1 -0
- package/dist/AuthMethodExecution.js +277 -0
- package/dist/AuthMethodExecution.js.map +1 -0
- package/dist/ConnectionResolver.d.ts +219 -0
- package/dist/ConnectionResolver.d.ts.map +1 -0
- package/dist/ConnectionResolver.js +153 -0
- package/dist/ConnectionResolver.js.map +1 -0
- package/dist/DefinitionError.d.ts +67 -0
- package/dist/DefinitionError.d.ts.map +1 -0
- package/dist/DefinitionError.js +82 -0
- package/dist/DefinitionError.js.map +1 -0
- package/dist/DualRegistry.d.ts +71 -0
- package/dist/DualRegistry.d.ts.map +1 -0
- package/dist/DualRegistry.js +96 -0
- package/dist/DualRegistry.js.map +1 -0
- package/dist/DualRuntime.d.ts +279 -0
- package/dist/DualRuntime.d.ts.map +1 -0
- package/dist/DualRuntime.js +311 -0
- package/dist/DualRuntime.js.map +1 -0
- package/dist/Files.d.ts +80 -0
- package/dist/Files.d.ts.map +1 -0
- package/dist/Files.js +66 -0
- package/dist/Files.js.map +1 -0
- package/dist/Group.d.ts +106 -0
- package/dist/Group.d.ts.map +1 -0
- package/dist/Group.js +86 -0
- package/dist/Group.js.map +1 -0
- package/dist/Http.d.ts +342 -0
- package/dist/Http.d.ts.map +1 -0
- package/dist/Http.js +677 -0
- package/dist/Http.js.map +1 -0
- package/dist/Mcp.d.ts +109 -0
- package/dist/Mcp.d.ts.map +1 -0
- package/dist/Mcp.js +176 -0
- package/dist/Mcp.js.map +1 -0
- package/dist/OAuth2TokenRefresher.d.ts +18 -0
- package/dist/OAuth2TokenRefresher.d.ts.map +1 -0
- package/dist/OAuth2TokenRefresher.js +19 -0
- package/dist/OAuth2TokenRefresher.js.map +1 -0
- package/dist/OAuthFlow.d.ts +71 -0
- package/dist/OAuthFlow.d.ts.map +1 -0
- package/dist/OAuthFlow.js +488 -0
- package/dist/OAuthFlow.js.map +1 -0
- package/dist/Plugin.d.ts +383 -0
- package/dist/Plugin.d.ts.map +1 -0
- package/dist/Plugin.js +249 -0
- package/dist/Plugin.js.map +1 -0
- package/dist/PluginRuntime.d.ts +3186 -0
- package/dist/PluginRuntime.d.ts.map +1 -0
- package/dist/PluginRuntime.js +331 -0
- package/dist/PluginRuntime.js.map +1 -0
- package/dist/Runner.d.ts +1 -0
- package/dist/Runner.js +124 -0
- package/dist/Runner.js.map +1 -0
- package/dist/ToolFile.d.ts +23 -0
- package/dist/ToolFile.d.ts.map +1 -0
- package/dist/ToolFile.js +33 -0
- package/dist/ToolFile.js.map +1 -0
- package/dist/Trigger.d.ts +358 -0
- package/dist/Trigger.d.ts.map +1 -0
- package/dist/Trigger.js +287 -0
- package/dist/Trigger.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -0
- package/dist/internal/auth-context.d.ts +23 -0
- package/dist/internal/auth-context.d.ts.map +1 -0
- package/dist/internal/auth-context.js +1 -0
- package/dist/internal/auth-execution.d.ts +27 -0
- package/dist/internal/auth-execution.d.ts.map +1 -0
- package/dist/internal/auth-execution.js +66 -0
- package/dist/internal/auth-execution.js.map +1 -0
- package/dist/internal/auth-runtime.d.ts +5 -0
- package/dist/internal/auth-runtime.d.ts.map +1 -0
- package/dist/internal/auth-runtime.js +16 -0
- package/dist/internal/auth-runtime.js.map +1 -0
- package/dist/internal/capabilities.d.ts +24 -0
- package/dist/internal/capabilities.d.ts.map +1 -0
- package/dist/internal/capabilities.js +22 -0
- package/dist/internal/capabilities.js.map +1 -0
- package/dist/internal/children.d.ts +34 -0
- package/dist/internal/children.d.ts.map +1 -0
- package/dist/internal/children.js +53 -0
- package/dist/internal/children.js.map +1 -0
- package/dist/internal/collection-utils.d.ts +4 -0
- package/dist/internal/collection-utils.d.ts.map +1 -0
- package/dist/internal/collection-utils.js +6 -0
- package/dist/internal/collection-utils.js.map +1 -0
- package/dist/internal/dual/actions.d.ts +33 -0
- package/dist/internal/dual/actions.d.ts.map +1 -0
- package/dist/internal/dual/actions.js +26 -0
- package/dist/internal/dual/actions.js.map +1 -0
- package/dist/internal/dual/invoke.d.ts +63 -0
- package/dist/internal/dual/invoke.d.ts.map +1 -0
- package/dist/internal/dual/invoke.js +75 -0
- package/dist/internal/dual/invoke.js.map +1 -0
- package/dist/internal/dual/registry.d.ts +125 -0
- package/dist/internal/dual/registry.d.ts.map +1 -0
- package/dist/internal/dual/registry.js +172 -0
- package/dist/internal/dual/registry.js.map +1 -0
- package/dist/internal/dual/triggers.d.ts +29 -0
- package/dist/internal/dual/triggers.d.ts.map +1 -0
- package/dist/internal/dual/triggers.js +26 -0
- package/dist/internal/dual/triggers.js.map +1 -0
- package/dist/internal/handler-context.d.ts +9 -0
- package/dist/internal/handler-context.d.ts.map +1 -0
- package/dist/internal/handler-context.js +31 -0
- package/dist/internal/handler-context.js.map +1 -0
- package/dist/internal/identifier.d.ts +6 -0
- package/dist/internal/identifier.d.ts.map +1 -0
- package/dist/internal/identifier.js +28 -0
- package/dist/internal/identifier.js.map +1 -0
- package/dist/internal/plugin-handlers.d.ts +38 -0
- package/dist/internal/plugin-handlers.d.ts.map +1 -0
- package/dist/internal/plugin-handlers.js +52 -0
- package/dist/internal/plugin-handlers.js.map +1 -0
- package/dist/internal/proto.d.ts +7 -0
- package/dist/internal/proto.d.ts.map +1 -0
- package/dist/internal/proto.js +10 -0
- package/dist/internal/proto.js.map +1 -0
- package/dist/internal/schema-runtime.d.ts +6 -0
- package/dist/internal/schema-runtime.d.ts.map +1 -0
- package/dist/internal/schema-runtime.js +10 -0
- package/dist/internal/schema-runtime.js.map +1 -0
- package/dist/internal/trigger-runtime-shared.d.ts +41 -0
- package/dist/internal/trigger-runtime-shared.d.ts.map +1 -0
- package/dist/internal/trigger-runtime-shared.js +132 -0
- package/dist/internal/trigger-runtime-shared.js.map +1 -0
- package/dist/internal/trigger-runtime.d.ts +12 -0
- package/dist/internal/trigger-runtime.d.ts.map +1 -0
- package/dist/internal/trigger-runtime.js +95 -0
- package/dist/internal/trigger-runtime.js.map +1 -0
- package/dist/internal/types.d.ts +7 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +1 -0
- package/dist/remote-runtime.d.ts +585 -0
- package/dist/remote-runtime.d.ts.map +1 -0
- package/dist/remote-runtime.js +367 -0
- package/dist/remote-runtime.js.map +1 -0
- package/package.json +55 -4
- package/src/Action.ts +370 -0
- package/src/AgentChannel.ts +860 -0
- package/src/AgentChannelConformance.ts +124 -0
- package/src/AgentChannelRuntime.ts +616 -0
- package/src/AiProvider.ts +255 -0
- package/src/AuthMethod.ts +954 -0
- package/src/AuthMethodBinding.ts +112 -0
- package/src/AuthMethodExecution.ts +542 -0
- package/src/ConnectionResolver.ts +265 -0
- package/src/DefinitionError.ts +115 -0
- package/src/DualRegistry.ts +152 -0
- package/src/DualRuntime.ts +1081 -0
- package/src/Files.ts +89 -0
- package/src/Group.ts +292 -0
- package/src/Http.ts +1205 -0
- package/src/Mcp.ts +299 -0
- package/src/OAuth2TokenRefresher.ts +22 -0
- package/src/OAuthFlow.ts +874 -0
- package/src/Plugin.ts +912 -0
- package/src/PluginRuntime.ts +414 -0
- package/src/Runner.ts +248 -0
- package/src/ToolFile.ts +28 -0
- package/src/Trigger.ts +722 -0
- package/src/index.ts +158 -0
- package/src/internal/auth-context.ts +27 -0
- package/src/internal/auth-execution.ts +241 -0
- package/src/internal/auth-runtime.ts +20 -0
- package/src/internal/capabilities.ts +55 -0
- package/src/internal/children.ts +126 -0
- package/src/internal/collection-utils.ts +8 -0
- package/src/internal/dual/actions.ts +116 -0
- package/src/internal/dual/invoke.ts +307 -0
- package/src/internal/dual/registry.ts +340 -0
- package/src/internal/dual/triggers.ts +128 -0
- package/src/internal/handler-context.ts +57 -0
- package/src/internal/identifier.ts +27 -0
- package/src/internal/plugin-handlers.ts +146 -0
- package/src/internal/proto.ts +8 -0
- package/src/internal/schema-runtime.ts +15 -0
- package/src/internal/trigger-runtime-shared.ts +449 -0
- package/src/internal/trigger-runtime.ts +223 -0
- package/src/internal/types.ts +12 -0
- package/src/remote-runtime.ts +469 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { Context } from "effect";
|
|
2
|
+
import { Effect, Record, String } from "effect";
|
|
3
|
+
|
|
4
|
+
import type * as Trigger from "../Trigger.ts";
|
|
5
|
+
|
|
6
|
+
import type { RuntimeConnectionErrorsFor } from "./auth-execution.ts";
|
|
7
|
+
import { mergeUnder } from "./handler-context.ts";
|
|
8
|
+
import {
|
|
9
|
+
decodeConfig,
|
|
10
|
+
decodeState,
|
|
11
|
+
type EntryAuthMethods,
|
|
12
|
+
type EntryTrigger,
|
|
13
|
+
type TriggerAppError,
|
|
14
|
+
type TriggerEncodedState,
|
|
15
|
+
type TriggerEncodedOutput,
|
|
16
|
+
type TriggerIdOf,
|
|
17
|
+
type TriggerRuntimeRequirements,
|
|
18
|
+
requirePollingHook,
|
|
19
|
+
requireWebhookHook,
|
|
20
|
+
validateOutputs,
|
|
21
|
+
validateStateResult,
|
|
22
|
+
withRuntimeContext,
|
|
23
|
+
} from "./trigger-runtime-shared.ts";
|
|
24
|
+
|
|
25
|
+
/** Erased runtime entry: every phase implementation works on this view while
|
|
26
|
+
* the public signatures re-expose precise per-entry types. */
|
|
27
|
+
type AnyEntry = Trigger.RuntimeTriggerEntry<
|
|
28
|
+
Trigger.Trigger<string, Trigger.TriggerKind, any, any, any, any>
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
type PhaseHook<HookResult> = {
|
|
32
|
+
readonly hook: (input: any, context: any) => Effect.Effect<HookResult, any, any>;
|
|
33
|
+
readonly context: Context.Context<never>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type PhaseError =
|
|
37
|
+
| Trigger.InvalidTriggerConfigError
|
|
38
|
+
| Trigger.InvalidTriggerStateError
|
|
39
|
+
| Trigger.UnsupportedTriggerRuntimeError;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Shared execution skeleton for all trigger runtime phases: decode config and
|
|
43
|
+
* state, resolve the runtime connection, run the hook inside the registered
|
|
44
|
+
* handler context, then hand the raw hook result to the phase finisher.
|
|
45
|
+
*/
|
|
46
|
+
const runPhase = <HookResult, Result>(options: {
|
|
47
|
+
readonly entry: AnyEntry;
|
|
48
|
+
readonly phase: Trigger.RuntimePhase;
|
|
49
|
+
readonly request: Trigger.LifecycleRequest;
|
|
50
|
+
readonly input?: object;
|
|
51
|
+
readonly hook: Effect.Effect<PhaseHook<HookResult>, Trigger.UnsupportedTriggerRuntimeError, any>;
|
|
52
|
+
readonly finish: (result: HookResult) => Effect.Effect<Result, unknown, unknown>;
|
|
53
|
+
}): Effect.Effect<Result, PhaseError | unknown, unknown> =>
|
|
54
|
+
Effect.gen(function* () {
|
|
55
|
+
const { entry, phase, request } = options;
|
|
56
|
+
const runtime = yield* options.hook;
|
|
57
|
+
const config = yield* decodeConfig(entry.triggerId, entry.trigger.config, request.config);
|
|
58
|
+
const state = yield* decodeState(entry.triggerId, phase, entry.trigger.state, request.state);
|
|
59
|
+
|
|
60
|
+
const input = { ...options.input, config, state };
|
|
61
|
+
|
|
62
|
+
const result = yield* withRuntimeContext(
|
|
63
|
+
entry,
|
|
64
|
+
phase,
|
|
65
|
+
request.connectionId,
|
|
66
|
+
request.context,
|
|
67
|
+
context => Effect.suspend(() => runtime.hook(input, context)),
|
|
68
|
+
).pipe(Effect.updateContext(current => mergeUnder(runtime.context, current)));
|
|
69
|
+
|
|
70
|
+
return yield* options.finish(result);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const meta = <Phase extends Trigger.RuntimePhase>(entry: AnyEntry, phase: Phase) => ({
|
|
74
|
+
phase,
|
|
75
|
+
triggerId: entry.triggerId,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const finishEmit =
|
|
79
|
+
(entry: AnyEntry, phase: "parse" | "poll") => (emitted: Trigger.EmitResult<unknown, unknown>) =>
|
|
80
|
+
validateOutputs(entry.triggerId, phase, entry.trigger.output, emitted.events).pipe(
|
|
81
|
+
Effect.zip(validateStateResult(entry.triggerId, phase, entry.trigger.state, emitted.state)),
|
|
82
|
+
Effect.map(([events, state]) => ({ events, meta: meta(entry, phase), state })),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
export const activate: <Entry extends Trigger.RuntimeTriggerEntry<any, any, any, any>>(
|
|
86
|
+
entry: Entry,
|
|
87
|
+
request: Trigger.WebhookActivationRequest,
|
|
88
|
+
) => Effect.Effect<
|
|
89
|
+
Trigger.ActivateSuccess<TriggerIdOf<Entry>, TriggerEncodedState<EntryTrigger<Entry>>>,
|
|
90
|
+
| Trigger.InvalidTriggerConfigError
|
|
91
|
+
| Trigger.InvalidTriggerStateError
|
|
92
|
+
| Trigger.UnsupportedTriggerRuntimeError
|
|
93
|
+
| RuntimeConnectionErrorsFor<EntryAuthMethods<Entry>>
|
|
94
|
+
| TriggerAppError<EntryTrigger<Entry>>,
|
|
95
|
+
TriggerRuntimeRequirements<Entry, "activate">
|
|
96
|
+
> = (entry, request) =>
|
|
97
|
+
runPhase({
|
|
98
|
+
entry,
|
|
99
|
+
finish: result =>
|
|
100
|
+
validateStateResult(entry.triggerId, "activate", entry.trigger.state, result.state).pipe(
|
|
101
|
+
Effect.map(state => ({ meta: meta(entry, "activate"), state })),
|
|
102
|
+
),
|
|
103
|
+
hook: requireWebhookHook(entry, "activate"),
|
|
104
|
+
input: { webhookUrl: request.webhookUrl },
|
|
105
|
+
phase: "activate",
|
|
106
|
+
request,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const deactivate: <Entry extends Trigger.RuntimeTriggerEntry<any, any, any, any>>(
|
|
110
|
+
entry: Entry,
|
|
111
|
+
request: Trigger.WebhookActivationRequest,
|
|
112
|
+
) => Effect.Effect<
|
|
113
|
+
Trigger.DeactivateSuccess<TriggerIdOf<Entry>>,
|
|
114
|
+
| Trigger.InvalidTriggerConfigError
|
|
115
|
+
| Trigger.InvalidTriggerStateError
|
|
116
|
+
| Trigger.UnsupportedTriggerRuntimeError
|
|
117
|
+
| RuntimeConnectionErrorsFor<EntryAuthMethods<Entry>>
|
|
118
|
+
| TriggerAppError<EntryTrigger<Entry>>,
|
|
119
|
+
TriggerRuntimeRequirements<Entry, "deactivate">
|
|
120
|
+
> = (entry, request) =>
|
|
121
|
+
runPhase({
|
|
122
|
+
entry,
|
|
123
|
+
finish: () => Effect.succeed({ meta: meta(entry, "deactivate") }),
|
|
124
|
+
hook: requireWebhookHook(entry, "deactivate"),
|
|
125
|
+
input: { webhookUrl: request.webhookUrl },
|
|
126
|
+
phase: "deactivate",
|
|
127
|
+
request,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const lowercaseHeaders = (request: Trigger.WebhookRequest): Trigger.WebhookRequest =>
|
|
131
|
+
request.headers === undefined
|
|
132
|
+
? request
|
|
133
|
+
: { ...request, headers: Record.mapKeys(request.headers, String.toLowerCase) };
|
|
134
|
+
|
|
135
|
+
export const verify: <Entry extends Trigger.RuntimeTriggerEntry<any, any, any, any>>(
|
|
136
|
+
entry: Entry,
|
|
137
|
+
request: Trigger.WebhookExecutionRequest,
|
|
138
|
+
) => Effect.Effect<
|
|
139
|
+
Trigger.VerifySuccess<TriggerIdOf<Entry>, TriggerEncodedState<EntryTrigger<Entry>>>,
|
|
140
|
+
| Trigger.InvalidTriggerConfigError
|
|
141
|
+
| Trigger.InvalidTriggerStateError
|
|
142
|
+
| Trigger.UnsupportedTriggerRuntimeError
|
|
143
|
+
| RuntimeConnectionErrorsFor<EntryAuthMethods<Entry>>
|
|
144
|
+
| TriggerAppError<EntryTrigger<Entry>>,
|
|
145
|
+
TriggerRuntimeRequirements<Entry, "verify">
|
|
146
|
+
> = (entry, request) =>
|
|
147
|
+
runPhase({
|
|
148
|
+
entry,
|
|
149
|
+
finish: Effect.fnUntraced(function* (result) {
|
|
150
|
+
const state = yield* validateStateResult(
|
|
151
|
+
entry.triggerId,
|
|
152
|
+
"verify",
|
|
153
|
+
entry.trigger.state,
|
|
154
|
+
result?.state,
|
|
155
|
+
);
|
|
156
|
+
return {
|
|
157
|
+
verified: true as const,
|
|
158
|
+
...(result?.response === undefined ? {} : { response: result.response }),
|
|
159
|
+
...(state === undefined ? {} : { state }),
|
|
160
|
+
meta: meta(entry, "verify"),
|
|
161
|
+
};
|
|
162
|
+
}),
|
|
163
|
+
hook: requireWebhookHook(entry, "verify"),
|
|
164
|
+
input: { request: lowercaseHeaders(request.request), webhookUrl: request.webhookUrl },
|
|
165
|
+
phase: "verify",
|
|
166
|
+
request,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export const parse: <Entry extends Trigger.RuntimeTriggerEntry<any, any, any, any>>(
|
|
170
|
+
entry: Entry,
|
|
171
|
+
request: Trigger.WebhookExecutionRequest,
|
|
172
|
+
) => Effect.Effect<
|
|
173
|
+
Trigger.EmittedEventsSuccess<
|
|
174
|
+
TriggerIdOf<Entry>,
|
|
175
|
+
"parse",
|
|
176
|
+
TriggerEncodedOutput<EntryTrigger<Entry>>,
|
|
177
|
+
TriggerEncodedState<EntryTrigger<Entry>>
|
|
178
|
+
>,
|
|
179
|
+
| Trigger.InvalidTriggerConfigError
|
|
180
|
+
| Trigger.InvalidTriggerStateError
|
|
181
|
+
| Trigger.InvalidTriggerOutputError
|
|
182
|
+
| Trigger.UnsupportedTriggerRuntimeError
|
|
183
|
+
| RuntimeConnectionErrorsFor<EntryAuthMethods<Entry>>
|
|
184
|
+
| TriggerAppError<EntryTrigger<Entry>>,
|
|
185
|
+
TriggerRuntimeRequirements<Entry, "parse">
|
|
186
|
+
> = (entry, request) =>
|
|
187
|
+
runPhase({
|
|
188
|
+
entry,
|
|
189
|
+
finish: finishEmit(entry, "parse"),
|
|
190
|
+
hook: requireWebhookHook(entry, "parse"),
|
|
191
|
+
input: { request: lowercaseHeaders(request.request), webhookUrl: request.webhookUrl },
|
|
192
|
+
phase: "parse",
|
|
193
|
+
request,
|
|
194
|
+
}) as never;
|
|
195
|
+
|
|
196
|
+
export const poll: <Entry extends Trigger.RuntimeTriggerEntry<any, any, any, any>>(
|
|
197
|
+
entry: Entry,
|
|
198
|
+
request: Trigger.PollExecutionRequest,
|
|
199
|
+
) => Effect.Effect<
|
|
200
|
+
Trigger.EmittedEventsSuccess<
|
|
201
|
+
TriggerIdOf<Entry>,
|
|
202
|
+
"poll",
|
|
203
|
+
TriggerEncodedOutput<EntryTrigger<Entry>>,
|
|
204
|
+
TriggerEncodedState<EntryTrigger<Entry>>
|
|
205
|
+
>,
|
|
206
|
+
| Trigger.InvalidTriggerConfigError
|
|
207
|
+
| Trigger.InvalidTriggerStateError
|
|
208
|
+
| Trigger.InvalidTriggerOutputError
|
|
209
|
+
| Trigger.UnsupportedTriggerRuntimeError
|
|
210
|
+
| RuntimeConnectionErrorsFor<EntryAuthMethods<Entry>>
|
|
211
|
+
| TriggerAppError<EntryTrigger<Entry>>,
|
|
212
|
+
TriggerRuntimeRequirements<Entry, "poll">
|
|
213
|
+
> = (entry, request) =>
|
|
214
|
+
// Polling handlers are executed at least once per platform occurrence. Hosts
|
|
215
|
+
// pass stable occurrence metadata in request.context.source.metadata, so
|
|
216
|
+
// handlers should be GET-like/idempotent for repeated scheduledAt values.
|
|
217
|
+
runPhase({
|
|
218
|
+
entry,
|
|
219
|
+
finish: finishEmit(entry, "poll"),
|
|
220
|
+
hook: requirePollingHook(entry),
|
|
221
|
+
phase: "poll",
|
|
222
|
+
request,
|
|
223
|
+
}) as never;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Small type-level utilities shared across the SDK internals. */
|
|
2
|
+
import type { MachineIdSeparator } from "./identifier.ts";
|
|
3
|
+
|
|
4
|
+
export type IsAny<Value> = 0 extends 1 & Value ? true : false;
|
|
5
|
+
|
|
6
|
+
export type IsGenericArray<Values extends readonly unknown[]> = number extends Values["length"]
|
|
7
|
+
? true
|
|
8
|
+
: false;
|
|
9
|
+
|
|
10
|
+
export type JoinPrefix<Prefix extends string, Identifier extends string> = Prefix extends ""
|
|
11
|
+
? Identifier
|
|
12
|
+
: `${Prefix}${typeof MachineIdSeparator}${Identifier}`;
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-only SDK lifecycle requests for an installed, isolated integration plugin.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.3.0
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
Array,
|
|
8
|
+
Cause,
|
|
9
|
+
Effect,
|
|
10
|
+
Layer,
|
|
11
|
+
Match,
|
|
12
|
+
Option,
|
|
13
|
+
Predicate,
|
|
14
|
+
Redacted,
|
|
15
|
+
Schema,
|
|
16
|
+
flow,
|
|
17
|
+
pipe,
|
|
18
|
+
} from "effect";
|
|
19
|
+
|
|
20
|
+
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
21
|
+
import type { HttpClientResponse } from "effect/http";
|
|
22
|
+
|
|
23
|
+
import { ActionHandlerError, RetryableActionHandlerError } from "./Action.ts";
|
|
24
|
+
import * as AgentChannel from "./AgentChannel.ts";
|
|
25
|
+
import { AuthMethodExecution } from "./AuthMethodExecution.ts";
|
|
26
|
+
import { ConnectionResolver } from "./ConnectionResolver.ts";
|
|
27
|
+
import { DualRuntime } from "./DualRuntime.ts";
|
|
28
|
+
import { Files } from "./Files.ts";
|
|
29
|
+
|
|
30
|
+
export type Identity = typeof Identity.Type;
|
|
31
|
+
export const Identity = Schema.Struct({
|
|
32
|
+
artifactDigest: Schema.String.check(Schema.isPattern(/^[a-f0-9]{128}$/u)),
|
|
33
|
+
invocationId: Schema.NonEmptyString,
|
|
34
|
+
organizationId: Schema.NonEmptyString,
|
|
35
|
+
pluginKey: Schema.NonEmptyString,
|
|
36
|
+
pluginVersion: Schema.NonEmptyString,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const connection = Schema.Struct({
|
|
40
|
+
connectionId: Schema.NonEmptyString,
|
|
41
|
+
material: Schema.Json,
|
|
42
|
+
methodId: Schema.NonEmptyString,
|
|
43
|
+
providerId: Schema.NonEmptyString,
|
|
44
|
+
setup: Schema.optionalKey(Schema.Json),
|
|
45
|
+
});
|
|
46
|
+
const context = Schema.Struct({
|
|
47
|
+
actor: Schema.Union([
|
|
48
|
+
Schema.Struct({ kind: Schema.Literal("user"), userId: Schema.NonEmptyString }),
|
|
49
|
+
Schema.Struct({ kind: Schema.Literal("system"), reason: Schema.optionalKey(Schema.String) }),
|
|
50
|
+
]),
|
|
51
|
+
metadata: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json)),
|
|
52
|
+
source: Schema.Struct({
|
|
53
|
+
kind: Schema.NonEmptyString,
|
|
54
|
+
metadata: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json)),
|
|
55
|
+
subjectId: Schema.optionalKey(Schema.String),
|
|
56
|
+
}),
|
|
57
|
+
tenant: Schema.Struct({ organizationId: Schema.NonEmptyString, teamId: Schema.NonEmptyString }),
|
|
58
|
+
});
|
|
59
|
+
const trigger = {
|
|
60
|
+
config: Schema.Json,
|
|
61
|
+
state: Schema.optionalKey(Schema.Json),
|
|
62
|
+
triggerId: Schema.NonEmptyString,
|
|
63
|
+
};
|
|
64
|
+
const webhook = { ...trigger, webhookUrl: Schema.String };
|
|
65
|
+
const channel = {
|
|
66
|
+
channelId: Schema.NonEmptyString,
|
|
67
|
+
definitionSettings: Schema.Json,
|
|
68
|
+
deploymentSettings: Schema.Json,
|
|
69
|
+
};
|
|
70
|
+
const delivery = {
|
|
71
|
+
...webhook,
|
|
72
|
+
request: Schema.Struct({
|
|
73
|
+
body: Schema.Json,
|
|
74
|
+
headers: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
|
|
75
|
+
method: Schema.optionalKey(Schema.String),
|
|
76
|
+
query: Schema.optionalKey(
|
|
77
|
+
Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Array(Schema.String)])),
|
|
78
|
+
),
|
|
79
|
+
rawBody: Schema.Uint8ArrayFromBase64,
|
|
80
|
+
url: Schema.optionalKey(Schema.String),
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
export type Operation = typeof Operation.Type;
|
|
84
|
+
export const Operation = Schema.Union([
|
|
85
|
+
Schema.TaggedStruct("invoke", { actionId: Schema.NonEmptyString, input: Schema.Json }),
|
|
86
|
+
Schema.TaggedStruct("activate", webhook),
|
|
87
|
+
Schema.TaggedStruct("deactivate", webhook),
|
|
88
|
+
Schema.TaggedStruct("verify", delivery),
|
|
89
|
+
Schema.TaggedStruct("parse", delivery),
|
|
90
|
+
Schema.TaggedStruct("poll", trigger),
|
|
91
|
+
Schema.TaggedStruct("channelCheckAccess", {
|
|
92
|
+
...channel,
|
|
93
|
+
identity: AgentChannel.VerifiedIdentity,
|
|
94
|
+
}),
|
|
95
|
+
Schema.TaggedStruct("channelExecute", { ...channel, command: Schema.Json }),
|
|
96
|
+
AuthMethodExecution.JsonRequest,
|
|
97
|
+
]);
|
|
98
|
+
/** Where this call reads and stores the caller's files, and the token that lets it. */
|
|
99
|
+
export type FileAccess = typeof FileAccess.Type;
|
|
100
|
+
export const FileAccess = Schema.Struct({ token: Schema.NonEmptyString, url: Schema.String });
|
|
101
|
+
export type Request = typeof Request.Type;
|
|
102
|
+
export const Request = Schema.Struct({
|
|
103
|
+
connection: Schema.optionalKey(connection),
|
|
104
|
+
context,
|
|
105
|
+
files: Schema.optionalKey(FileAccess),
|
|
106
|
+
identity: Identity,
|
|
107
|
+
operation: Operation,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export type Failure = typeof Failure.Type;
|
|
111
|
+
export const Failure = Schema.Struct({
|
|
112
|
+
category: Schema.Literals(["validation", "connection", "handler", "initialization", "protocol"]),
|
|
113
|
+
/** The Agent Channel failure the host rebuilds as its `ChannelError`. */
|
|
114
|
+
channel: Schema.optionalKey(
|
|
115
|
+
Schema.Struct({
|
|
116
|
+
code: AgentChannel.ErrorCode,
|
|
117
|
+
providerCode: Schema.optionalKey(AgentChannel.FailureCode),
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
code: Schema.String,
|
|
121
|
+
fields: Schema.optionalKey(Schema.Array(Schema.Struct({ path: Schema.String }))),
|
|
122
|
+
message: Schema.String,
|
|
123
|
+
retryAfterSeconds: Schema.optionalKey(Schema.Finite),
|
|
124
|
+
retryable: Schema.Boolean,
|
|
125
|
+
/** The provider's HTTP status, kept so the host can report why a provider rejected the call. */
|
|
126
|
+
status: Schema.optionalKey(Schema.Int),
|
|
127
|
+
});
|
|
128
|
+
export type Response = typeof Response.Type;
|
|
129
|
+
export const Response = Schema.Union([
|
|
130
|
+
Schema.TaggedStruct("success", { identity: Identity, value: Schema.optionalKey(Schema.Json) }),
|
|
131
|
+
Schema.TaggedStruct("failure", { error: Failure, identity: Identity }),
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
// Plugin causes and schema descriptions can contain credentials or submitted input.
|
|
135
|
+
export function failure(error: unknown): Failure {
|
|
136
|
+
if (Schema.is(AgentChannel.ChannelError)(error)) {
|
|
137
|
+
return {
|
|
138
|
+
category: "handler",
|
|
139
|
+
channel: Option.match(Option.fromUndefinedOr(error.providerCode), {
|
|
140
|
+
onNone: () => ({ code: error.code }),
|
|
141
|
+
onSome: providerCode => ({ code: error.code, providerCode }),
|
|
142
|
+
}),
|
|
143
|
+
code: error._tag,
|
|
144
|
+
message: "Agent Channel operation failed",
|
|
145
|
+
retryable: false,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
Schema.is(Files.FileError)(error) ||
|
|
150
|
+
(Schema.is(ActionHandlerError)(error) && Schema.is(Files.FileError)(error.cause))
|
|
151
|
+
) {
|
|
152
|
+
return {
|
|
153
|
+
category: "handler",
|
|
154
|
+
code: "FileError",
|
|
155
|
+
message: "A Dual Files operation failed",
|
|
156
|
+
retryable: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (
|
|
160
|
+
Schema.is(DualRuntime.InvalidActionInputError)(error) ||
|
|
161
|
+
Schema.is(DualRuntime.InvalidActionOutputError)(error)
|
|
162
|
+
) {
|
|
163
|
+
return {
|
|
164
|
+
category: "validation",
|
|
165
|
+
code: error._tag,
|
|
166
|
+
fields: Array.map(error.fields, field => ({ path: field.path })),
|
|
167
|
+
message: "Plugin action validation failed",
|
|
168
|
+
retryable: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (Schema.is(RetryableActionHandlerError)(error)) {
|
|
172
|
+
return {
|
|
173
|
+
category: "handler",
|
|
174
|
+
code: error._tag,
|
|
175
|
+
message: "Plugin handler failed",
|
|
176
|
+
retryable: true,
|
|
177
|
+
...Option.match(Option.fromUndefinedOr(error.retryAfterSeconds), {
|
|
178
|
+
onNone: () => ({}),
|
|
179
|
+
onSome: seconds => ({ retryAfterSeconds: seconds }),
|
|
180
|
+
}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const tag =
|
|
184
|
+
Predicate.hasProperty(error, "_tag") && Predicate.isString(error._tag)
|
|
185
|
+
? error._tag
|
|
186
|
+
: "PluginHandlerError";
|
|
187
|
+
const category = pipe(
|
|
188
|
+
Match.value(tag),
|
|
189
|
+
Match.when("SchemaError", () => "validation" as const),
|
|
190
|
+
Match.whenOr(
|
|
191
|
+
"InvalidTriggerConfigError",
|
|
192
|
+
"InvalidTriggerStateError",
|
|
193
|
+
"InvalidTriggerOutputError",
|
|
194
|
+
() => "validation" as const,
|
|
195
|
+
),
|
|
196
|
+
Match.whenOr(
|
|
197
|
+
"ConnectionRequiredError",
|
|
198
|
+
"InvalidConnectionIdError",
|
|
199
|
+
"InvalidResolvedConnectionError",
|
|
200
|
+
"ConnectionIdNotAllowedError",
|
|
201
|
+
"AuthMethodNotFoundError",
|
|
202
|
+
"AuthMethodHookError",
|
|
203
|
+
"ExpectedOAuth2MethodError",
|
|
204
|
+
"OAuth2RefreshTokenUnavailableError",
|
|
205
|
+
() => "connection" as const,
|
|
206
|
+
),
|
|
207
|
+
Match.orElse(() => "handler" as const),
|
|
208
|
+
);
|
|
209
|
+
return {
|
|
210
|
+
category,
|
|
211
|
+
code: Array.contains(
|
|
212
|
+
[
|
|
213
|
+
"SchemaError",
|
|
214
|
+
"InvalidTriggerConfigError",
|
|
215
|
+
"InvalidTriggerStateError",
|
|
216
|
+
"InvalidTriggerOutputError",
|
|
217
|
+
"ConnectionRequiredError",
|
|
218
|
+
"InvalidConnectionIdError",
|
|
219
|
+
"InvalidResolvedConnectionError",
|
|
220
|
+
"ConnectionIdNotAllowedError",
|
|
221
|
+
"AuthMethodNotFoundError",
|
|
222
|
+
"AuthMethodHookError",
|
|
223
|
+
"ExpectedOAuth2MethodError",
|
|
224
|
+
"OAuth2RefreshTokenUnavailableError",
|
|
225
|
+
"ActionNotFoundError",
|
|
226
|
+
"ActionHandlerNotFoundError",
|
|
227
|
+
"ActionHandlerError",
|
|
228
|
+
"TriggerNotFoundError",
|
|
229
|
+
"UnsupportedTriggerRuntimeError",
|
|
230
|
+
"FileError",
|
|
231
|
+
"SandboxAccessNotDeclaredError",
|
|
232
|
+
"SandboxRequestNotAllowedError",
|
|
233
|
+
],
|
|
234
|
+
tag,
|
|
235
|
+
)
|
|
236
|
+
? tag
|
|
237
|
+
: "PluginHandlerError",
|
|
238
|
+
message: "Plugin operation failed",
|
|
239
|
+
retryable: false,
|
|
240
|
+
...(Schema.is(ActionHandlerError)(error) && Schema.is(Schema.Int)(error.status)
|
|
241
|
+
? { status: error.status }
|
|
242
|
+
: {}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const unavailableFiles = Files.Service.of({
|
|
247
|
+
read: () =>
|
|
248
|
+
Effect.fail(Files.FileError.make({ message: "Dual Files is not available to this operation" })),
|
|
249
|
+
write: () =>
|
|
250
|
+
Effect.fail(Files.FileError.make({ message: "Dual Files is not available to this operation" })),
|
|
251
|
+
});
|
|
252
|
+
export const filesUnavailable = Layer.succeed(Files.Service, unavailableFiles);
|
|
253
|
+
|
|
254
|
+
const FileFailure = Schema.Struct({ message: Schema.String });
|
|
255
|
+
|
|
256
|
+
// The host's file routes do the reading and storing, as the caller the token names.
|
|
257
|
+
function filesService(access: FileAccess, http: HttpClient.HttpClient) {
|
|
258
|
+
const client = HttpClient.mapRequest(
|
|
259
|
+
http,
|
|
260
|
+
flow(
|
|
261
|
+
HttpClientRequest.prependUrl(`${access.url}/api/plugin-files`),
|
|
262
|
+
HttpClientRequest.bearerToken(access.token),
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
function call<A>(
|
|
266
|
+
request: HttpClientRequest.HttpClientRequest,
|
|
267
|
+
read: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<A, unknown>,
|
|
268
|
+
) {
|
|
269
|
+
return pipe(
|
|
270
|
+
Effect.gen(function* () {
|
|
271
|
+
const response = yield* client.execute(request);
|
|
272
|
+
if (response.status < 200 || response.status >= 300) {
|
|
273
|
+
return yield* Files.FileError.make(
|
|
274
|
+
yield* Schema.decodeUnknownEffect(FileFailure)(yield* response.json),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return yield* read(response);
|
|
278
|
+
}),
|
|
279
|
+
Effect.mapError(cause =>
|
|
280
|
+
Schema.is(Files.FileError)(cause)
|
|
281
|
+
? cause
|
|
282
|
+
: Files.FileError.make({
|
|
283
|
+
cause: Redacted.make(cause),
|
|
284
|
+
message: "Cannot reach Dual Files",
|
|
285
|
+
}),
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return Files.Service.of({
|
|
290
|
+
read: file =>
|
|
291
|
+
call(HttpClientRequest.get(`/${encodeURIComponent(file.id)}`), response =>
|
|
292
|
+
Effect.map(response.arrayBuffer, buffer => new Uint8Array(buffer)),
|
|
293
|
+
),
|
|
294
|
+
write: file =>
|
|
295
|
+
call(
|
|
296
|
+
pipe(
|
|
297
|
+
HttpClientRequest.post("", { urlParams: { name: file.name } }),
|
|
298
|
+
HttpClientRequest.bodyUint8Array(file.bytes, file.contentType ?? undefined),
|
|
299
|
+
),
|
|
300
|
+
response => Effect.flatMap(response.json, Schema.decodeUnknownEffect(Files.File)),
|
|
301
|
+
),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function connectionService(request: Request) {
|
|
306
|
+
return ConnectionResolver.Service.of({
|
|
307
|
+
resolve: input => {
|
|
308
|
+
const selected = request.connection;
|
|
309
|
+
if (selected === undefined) {
|
|
310
|
+
return Effect.fail(ConnectionResolver.ConnectionRequiredError.make(input.target));
|
|
311
|
+
}
|
|
312
|
+
if (
|
|
313
|
+
selected.providerId !== input.target.providerId ||
|
|
314
|
+
(input.connectionId !== undefined && input.connectionId !== selected.connectionId)
|
|
315
|
+
) {
|
|
316
|
+
return Effect.fail(
|
|
317
|
+
ConnectionResolver.InvalidConnectionIdError.make({
|
|
318
|
+
...input.target,
|
|
319
|
+
connectionId: input.connectionId ?? selected.connectionId,
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return Effect.succeed({ ...selected, encoding: "json" as const, setup: selected.setup });
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
export function connectionLayer(request: Request) {
|
|
328
|
+
return Layer.succeed(ConnectionResolver.Service, connectionService(request));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const run = Effect.fnUntraced(
|
|
332
|
+
function* (request: Request) {
|
|
333
|
+
const runtime = yield* DualRuntime.Service;
|
|
334
|
+
const connectionId = Option.match(Option.fromUndefinedOr(request.connection), {
|
|
335
|
+
onNone: () => ({}),
|
|
336
|
+
onSome: selected => ({ connectionId: selected.connectionId }),
|
|
337
|
+
});
|
|
338
|
+
const target = {
|
|
339
|
+
...connectionId,
|
|
340
|
+
context: request.context,
|
|
341
|
+
pluginKey: request.identity.pluginKey,
|
|
342
|
+
};
|
|
343
|
+
const operation = request.operation;
|
|
344
|
+
if (operation._tag === "invoke") {
|
|
345
|
+
return yield* runtime.invokeJson({ ...operation, ...target });
|
|
346
|
+
}
|
|
347
|
+
if (operation._tag === "poll") {
|
|
348
|
+
const result = yield* flow(
|
|
349
|
+
runtime.pollTrigger,
|
|
350
|
+
Effect.mapError(failure),
|
|
351
|
+
)({ ...operation, ...target });
|
|
352
|
+
if (result.state === undefined) return { events: result.events, meta: result.meta };
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
if (operation._tag === "activate") {
|
|
356
|
+
const result = yield* flow(
|
|
357
|
+
runtime.activateTrigger,
|
|
358
|
+
Effect.mapError(failure),
|
|
359
|
+
)({ ...operation, ...target });
|
|
360
|
+
if (result.state === undefined) return { meta: result.meta };
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
if (operation._tag === "deactivate") {
|
|
364
|
+
return yield* flow(
|
|
365
|
+
runtime.deactivateTrigger,
|
|
366
|
+
Effect.mapError(failure),
|
|
367
|
+
)({ ...operation, ...target });
|
|
368
|
+
}
|
|
369
|
+
if (operation._tag === "verify") {
|
|
370
|
+
return yield* flow(
|
|
371
|
+
runtime.verifyTrigger,
|
|
372
|
+
Effect.mapError(failure),
|
|
373
|
+
)({ ...operation, ...target });
|
|
374
|
+
}
|
|
375
|
+
if (operation._tag === "parse") {
|
|
376
|
+
const result = yield* flow(
|
|
377
|
+
runtime.parseTrigger,
|
|
378
|
+
Effect.mapError(failure),
|
|
379
|
+
)({ ...operation, ...target });
|
|
380
|
+
if (result.state === undefined) return { events: result.events, meta: result.meta };
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
if (operation._tag === "channelCheckAccess") {
|
|
384
|
+
return yield* runtime.checkAccessAgentChannel({
|
|
385
|
+
channelId: operation.channelId,
|
|
386
|
+
definitionSettings: operation.definitionSettings,
|
|
387
|
+
deploymentSettings: operation.deploymentSettings,
|
|
388
|
+
identity: operation.identity,
|
|
389
|
+
...connectionId,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (operation._tag === "channelExecute") {
|
|
393
|
+
return yield* runtime.executeAgentChannel({
|
|
394
|
+
channelId: operation.channelId,
|
|
395
|
+
command: operation.command,
|
|
396
|
+
definitionSettings: operation.definitionSettings,
|
|
397
|
+
deploymentSettings: operation.deploymentSettings,
|
|
398
|
+
...connectionId,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return yield* runtime.executeAuthJson(operation);
|
|
403
|
+
},
|
|
404
|
+
Effect.mapError(error => (Schema.is(Failure)(error) ? error : failure(error))),
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
/** Execute only in the guest after importing and building its installed plugin. */
|
|
408
|
+
export const execute = Effect.fn("RemoteRuntime.execute")(function* (request: Request) {
|
|
409
|
+
// The guest runner supplies its fetch client; a host calling in-process has no file routes to reach.
|
|
410
|
+
const http = yield* Effect.serviceOption(HttpClient.HttpClient);
|
|
411
|
+
if (request.identity.organizationId !== request.context.tenant.organizationId) {
|
|
412
|
+
return {
|
|
413
|
+
_tag: "failure",
|
|
414
|
+
error: {
|
|
415
|
+
category: "protocol",
|
|
416
|
+
code: "OrganizationMismatch",
|
|
417
|
+
message: "Invocation organization does not match its context",
|
|
418
|
+
retryable: false,
|
|
419
|
+
},
|
|
420
|
+
identity: request.identity,
|
|
421
|
+
} satisfies Response;
|
|
422
|
+
}
|
|
423
|
+
return yield* pipe(
|
|
424
|
+
run(request),
|
|
425
|
+
Effect.provideService(ConnectionResolver.Service, connectionService(request)),
|
|
426
|
+
Effect.provideService(
|
|
427
|
+
Files.Service,
|
|
428
|
+
pipe(
|
|
429
|
+
Option.fromUndefinedOr(request.files),
|
|
430
|
+
Option.flatMap(access => Option.map(http, client => filesService(access, client))),
|
|
431
|
+
Option.getOrElse(() => unavailableFiles),
|
|
432
|
+
),
|
|
433
|
+
),
|
|
434
|
+
Effect.flatMap(
|
|
435
|
+
Effect.fnUntraced(function* (value) {
|
|
436
|
+
if (value === undefined) {
|
|
437
|
+
return { _tag: "success", identity: request.identity } satisfies Response;
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
_tag: "success",
|
|
441
|
+
identity: request.identity,
|
|
442
|
+
value: yield* pipe(
|
|
443
|
+
Schema.decodeUnknownEffect(Schema.Json)(value),
|
|
444
|
+
Effect.mapError(failure),
|
|
445
|
+
),
|
|
446
|
+
} satisfies Response;
|
|
447
|
+
}),
|
|
448
|
+
),
|
|
449
|
+
Effect.catch(error =>
|
|
450
|
+
Effect.succeed({ _tag: "failure", error, identity: request.identity } satisfies Response),
|
|
451
|
+
),
|
|
452
|
+
Effect.catchCauseIf(
|
|
453
|
+
cause => !Cause.hasInterrupts(cause),
|
|
454
|
+
() =>
|
|
455
|
+
Effect.succeed({
|
|
456
|
+
_tag: "failure",
|
|
457
|
+
error: {
|
|
458
|
+
category: "handler",
|
|
459
|
+
code: "PluginDefect",
|
|
460
|
+
message: "Plugin operation failed",
|
|
461
|
+
retryable: false,
|
|
462
|
+
},
|
|
463
|
+
identity: request.identity,
|
|
464
|
+
} satisfies Response),
|
|
465
|
+
),
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
export * as RemoteRuntime from "./remote-runtime.ts";
|