@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,414 @@
|
|
|
1
|
+
import { Cause, Effect, Queue, Redacted, Schema, Stream, pipe } from "effect";
|
|
2
|
+
|
|
3
|
+
import { HttpClientRequest } from "effect/http";
|
|
4
|
+
|
|
5
|
+
import * as AuthMethodExecution from "./AuthMethodExecution.ts";
|
|
6
|
+
import * as DualRuntime from "./DualRuntime.ts";
|
|
7
|
+
import { OAuthFlow, OAuthGrantRejected } from "./OAuthFlow.ts";
|
|
8
|
+
import { RemoteRuntime } from "./remote-runtime.ts";
|
|
9
|
+
|
|
10
|
+
export class PluginExecutionError extends Schema.TaggedError<PluginExecutionError>()(
|
|
11
|
+
"PluginExecutionError",
|
|
12
|
+
{
|
|
13
|
+
category: Schema.Literals(["transport", "runner", "protocol", "cleanup", "artifact-expired"]),
|
|
14
|
+
cause: Schema.optionalKey(Schema.Redacted(Schema.Defect())),
|
|
15
|
+
message: Schema.String,
|
|
16
|
+
},
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
export const maxPayloadBytes = 1024 * 1024;
|
|
20
|
+
export const maxMetadataBytes = 64 * 1024 * 1024;
|
|
21
|
+
/** An Agent Channel result carries a read asset of up to 14 MiB. */
|
|
22
|
+
export const maxChannelResultBytes = 16 * 1024 * 1024;
|
|
23
|
+
export const cancellationTimeout = "5 seconds";
|
|
24
|
+
|
|
25
|
+
/** The listener half of a Node or Bun IPC channel: a process or a child process. */
|
|
26
|
+
export interface IpcEvents {
|
|
27
|
+
readonly on: (event: string, listener: (value: unknown) => void) => unknown;
|
|
28
|
+
readonly removeListener: (event: string, listener: (value: unknown) => void) => unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The sending half of a Node or Bun IPC channel: a process or a child process. */
|
|
32
|
+
export interface IpcSender {
|
|
33
|
+
readonly connected: boolean;
|
|
34
|
+
readonly send?: ((message: object) => unknown) | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Receive native Node and Bun IPC messages without interpreting plugin logs. */
|
|
38
|
+
export function ipcMessages(channel: IpcEvents) {
|
|
39
|
+
return Stream.callback<unknown, PluginExecutionError>(queue => {
|
|
40
|
+
const message = (value: unknown) => {
|
|
41
|
+
Queue.offerUnsafe(queue, value);
|
|
42
|
+
};
|
|
43
|
+
const disconnected = () => {
|
|
44
|
+
Queue.endUnsafe(queue);
|
|
45
|
+
};
|
|
46
|
+
const failed = (cause: unknown) => {
|
|
47
|
+
Queue.failCauseUnsafe(
|
|
48
|
+
queue,
|
|
49
|
+
Cause.fail(
|
|
50
|
+
PluginExecutionError.make({
|
|
51
|
+
category: "runner",
|
|
52
|
+
cause: Redacted.make(cause),
|
|
53
|
+
message: "Plugin IPC channel failed",
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
return Effect.acquireRelease(
|
|
59
|
+
Effect.sync(() => {
|
|
60
|
+
channel.on("message", message);
|
|
61
|
+
channel.on("disconnect", disconnected);
|
|
62
|
+
channel.on("error", failed);
|
|
63
|
+
}),
|
|
64
|
+
() =>
|
|
65
|
+
Effect.sync(() => {
|
|
66
|
+
channel.removeListener("message", message);
|
|
67
|
+
channel.removeListener("disconnect", disconnected);
|
|
68
|
+
channel.removeListener("error", failed);
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Queue a JSON message; the runtime reports asynchronous send failures through its error event. */
|
|
75
|
+
export function sendIpc(channel: IpcSender, value: object) {
|
|
76
|
+
return Effect.try({
|
|
77
|
+
try: () => {
|
|
78
|
+
if (!channel.connected || channel.send === undefined) {
|
|
79
|
+
throw PluginExecutionError.make({
|
|
80
|
+
category: "runner",
|
|
81
|
+
message: "Plugin IPC channel is closed",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
channel.send(value);
|
|
85
|
+
},
|
|
86
|
+
catch: cause =>
|
|
87
|
+
PluginExecutionError.make({
|
|
88
|
+
category: "runner",
|
|
89
|
+
cause: Redacted.make(cause),
|
|
90
|
+
message: "Cannot send plugin IPC message",
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type OAuthContinuation = typeof OAuthContinuation.Type;
|
|
96
|
+
export const OAuthContinuation = Schema.Struct({
|
|
97
|
+
expiresAt: Schema.DateFromString,
|
|
98
|
+
pkceVerifier: Schema.NullOr(Schema.String),
|
|
99
|
+
redirectUri: Schema.String,
|
|
100
|
+
state: Schema.String,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export type OAuthStarted = typeof OAuthStarted.Type;
|
|
104
|
+
export const OAuthStarted = Schema.Struct({
|
|
105
|
+
authorizationUrl: Schema.String,
|
|
106
|
+
continuation: OAuthContinuation,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const oauth = {
|
|
110
|
+
authMethodId: Schema.NonEmptyString,
|
|
111
|
+
providerId: Schema.NonEmptyString,
|
|
112
|
+
setup: Schema.optionalKey(Schema.Json),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** The origins a Connection's credential may reach from a run sandbox, and the headers one request gets. */
|
|
116
|
+
export type SandboxAccessResult = typeof SandboxAccessResult.Type;
|
|
117
|
+
export const SandboxAccessResult = Schema.Struct({
|
|
118
|
+
headers: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
|
|
119
|
+
origins: Schema.Array(Schema.String),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export type AuthRequest = typeof AuthRequest.Type;
|
|
123
|
+
export const AuthRequest = Schema.TaggedStruct("auth", {
|
|
124
|
+
identity: RemoteRuntime.Identity,
|
|
125
|
+
operation: Schema.Union([
|
|
126
|
+
AuthMethodExecution.JsonRequest,
|
|
127
|
+
Schema.TaggedStruct("oauthStart", { ...oauth, redirectUri: Schema.String }),
|
|
128
|
+
Schema.TaggedStruct("oauthComplete", {
|
|
129
|
+
...oauth,
|
|
130
|
+
continuation: OAuthContinuation,
|
|
131
|
+
query: Schema.Record(Schema.String, Schema.String),
|
|
132
|
+
}),
|
|
133
|
+
// Without `request`, only the origins; with it, the headers the plugin adds to that request.
|
|
134
|
+
Schema.TaggedStruct("sandboxAccess", {
|
|
135
|
+
...oauth,
|
|
136
|
+
material: Schema.Json,
|
|
137
|
+
request: Schema.optionalKey(
|
|
138
|
+
Schema.Struct({
|
|
139
|
+
headers: Schema.Record(Schema.String, Schema.String),
|
|
140
|
+
method: Schema.Literals([
|
|
141
|
+
"GET",
|
|
142
|
+
"POST",
|
|
143
|
+
"PUT",
|
|
144
|
+
"DELETE",
|
|
145
|
+
"PATCH",
|
|
146
|
+
"HEAD",
|
|
147
|
+
"OPTIONS",
|
|
148
|
+
"TRACE",
|
|
149
|
+
"QUERY",
|
|
150
|
+
]),
|
|
151
|
+
url: Schema.String,
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
}),
|
|
155
|
+
]),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/** Decode an Agent Channel's settings outside any tenant, as publication validates them. */
|
|
159
|
+
export type ChannelSettingsRequest = typeof ChannelSettingsRequest.Type;
|
|
160
|
+
export const ChannelSettingsRequest = Schema.TaggedStruct("channelSettings", {
|
|
161
|
+
identity: RemoteRuntime.Identity,
|
|
162
|
+
operation: Schema.TaggedStruct("decodeChannelSettings", {
|
|
163
|
+
channelId: Schema.NonEmptyString,
|
|
164
|
+
field: Schema.Literals(["definitionSettings", "deploymentSettings"]),
|
|
165
|
+
value: Schema.Json,
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export type ExecutionRequest = typeof ExecutionRequest.Type;
|
|
170
|
+
export const ExecutionRequest = Schema.Union([
|
|
171
|
+
RemoteRuntime.Request,
|
|
172
|
+
AuthRequest,
|
|
173
|
+
ChannelSettingsRequest,
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
export type Request = typeof Request.Type;
|
|
177
|
+
export const Request = Schema.Union([
|
|
178
|
+
ExecutionRequest,
|
|
179
|
+
Schema.TaggedStruct("extract", { identity: RemoteRuntime.Identity }),
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
export type Input = typeof Input.Type;
|
|
183
|
+
export const Input = Schema.Union([
|
|
184
|
+
Request,
|
|
185
|
+
Schema.TaggedStruct("cancel", { identity: RemoteRuntime.Identity }),
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
export type Output = typeof Output.Type;
|
|
189
|
+
export const Output = Schema.Union([
|
|
190
|
+
RemoteRuntime.Response,
|
|
191
|
+
Schema.TaggedStruct("interrupted", { identity: RemoteRuntime.Identity }),
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
export function checkMessageSize(value: unknown, maximum: number) {
|
|
195
|
+
if (new TextEncoder().encode(JSON.stringify(value)).byteLength <= maximum) return Effect.void;
|
|
196
|
+
return Effect.fail(
|
|
197
|
+
PluginExecutionError.make({
|
|
198
|
+
category: "protocol",
|
|
199
|
+
message: "Plugin message exceeds its transport limit",
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const encodeInput = Effect.fnUntraced(function* (input: Input) {
|
|
205
|
+
const encoded = yield* Schema.encodeEffect(Input)(input);
|
|
206
|
+
yield* checkMessageSize(encoded, maxPayloadBytes);
|
|
207
|
+
return encoded;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
export const decodeInput = Effect.fnUntraced(function* (input: unknown) {
|
|
211
|
+
const json = yield* Schema.decodeUnknownEffect(Schema.Json)(input);
|
|
212
|
+
yield* checkMessageSize(json, maxPayloadBytes);
|
|
213
|
+
return yield* Schema.decodeUnknownEffect(Input)(json);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export const encodeOutput = Effect.fnUntraced(function* (output: Output, maximum: number) {
|
|
217
|
+
const encoded = yield* Schema.encodeEffect(Output)(output);
|
|
218
|
+
yield* checkMessageSize(encoded, maximum);
|
|
219
|
+
return encoded;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
export const decodeOutput = Effect.fnUntraced(function* (output: unknown) {
|
|
223
|
+
const json = yield* Schema.decodeUnknownEffect(Schema.Json)(output);
|
|
224
|
+
yield* checkMessageSize(json, maxMetadataBytes);
|
|
225
|
+
return yield* Schema.decodeUnknownEffect(Output)(json);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
export function responseLimit(request: Request) {
|
|
229
|
+
if ("_tag" in request && request._tag === "extract") return maxMetadataBytes;
|
|
230
|
+
if ("context" in request && request.operation._tag === "channelExecute") {
|
|
231
|
+
return maxChannelResultBytes;
|
|
232
|
+
}
|
|
233
|
+
return maxPayloadBytes;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export type Invocation = typeof Invocation.Type;
|
|
237
|
+
export const Invocation = Schema.Struct({
|
|
238
|
+
entrypoint: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]+(?:[./][A-Za-z0-9_-]+)*$/u)),
|
|
239
|
+
exportName: Schema.String.check(Schema.isPattern(/^[A-Za-z_$][A-Za-z0-9_$]*$/u)),
|
|
240
|
+
request: Request,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
export const encodeRequest = Effect.fnUntraced(function* (request: Request) {
|
|
244
|
+
const encoded = yield* pipe(
|
|
245
|
+
Schema.encodeEffect(Schema.fromJsonString(Request))(request),
|
|
246
|
+
Effect.mapError(cause =>
|
|
247
|
+
PluginExecutionError.make({
|
|
248
|
+
cause: Redacted.make(cause),
|
|
249
|
+
category: "protocol",
|
|
250
|
+
message: "Invalid plugin request",
|
|
251
|
+
}),
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
if (new TextEncoder().encode(encoded).byteLength > maxPayloadBytes) {
|
|
255
|
+
return yield* PluginExecutionError.make({
|
|
256
|
+
category: "protocol",
|
|
257
|
+
message: "Plugin request exceeds the 1 MiB transport limit",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return encoded;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export const readBounded = Effect.fnUntraced(function* <E, R>(
|
|
264
|
+
input: Stream.Stream<Uint8Array, E, R>,
|
|
265
|
+
maximum = maxPayloadBytes,
|
|
266
|
+
) {
|
|
267
|
+
const output = { bytes: Buffer.alloc(maximum), size: 0 };
|
|
268
|
+
yield* pipe(
|
|
269
|
+
input,
|
|
270
|
+
Stream.runForEach(chunk =>
|
|
271
|
+
Effect.gen(function* () {
|
|
272
|
+
if (output.size + chunk.byteLength > maximum) {
|
|
273
|
+
return yield* PluginExecutionError.make({
|
|
274
|
+
category: "protocol",
|
|
275
|
+
message: "Plugin payload exceeds its transport limit",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
Buffer.from(chunk).copy(output.bytes, output.size);
|
|
279
|
+
output.size += chunk.byteLength;
|
|
280
|
+
}),
|
|
281
|
+
),
|
|
282
|
+
Effect.mapError(cause =>
|
|
283
|
+
PluginExecutionError.make({
|
|
284
|
+
cause: Redacted.make(cause),
|
|
285
|
+
category: "protocol",
|
|
286
|
+
message: "Cannot read plugin payload",
|
|
287
|
+
}),
|
|
288
|
+
),
|
|
289
|
+
);
|
|
290
|
+
return output.bytes.toString("utf8", 0, output.size);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
export const verifyResponse = Effect.fnUntraced(function* <Response extends Output>(
|
|
294
|
+
request: Request,
|
|
295
|
+
response: Response,
|
|
296
|
+
) {
|
|
297
|
+
if (
|
|
298
|
+
response.identity.invocationId !== request.identity.invocationId ||
|
|
299
|
+
response.identity.artifactDigest !== request.identity.artifactDigest ||
|
|
300
|
+
response.identity.organizationId !== request.identity.organizationId ||
|
|
301
|
+
response.identity.pluginKey !== request.identity.pluginKey ||
|
|
302
|
+
response.identity.pluginVersion !== request.identity.pluginVersion
|
|
303
|
+
) {
|
|
304
|
+
return yield* PluginExecutionError.make({
|
|
305
|
+
category: "protocol",
|
|
306
|
+
message: "Plugin result does not match its invocation",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return response;
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const executeAuth = Effect.fnUntraced(
|
|
313
|
+
function* (request: AuthRequest) {
|
|
314
|
+
const operation = request.operation;
|
|
315
|
+
const runtime = yield* DualRuntime.Service;
|
|
316
|
+
if (operation._tag === "sandboxAccess") {
|
|
317
|
+
const access = yield* runtime.resolveSandboxAccess({
|
|
318
|
+
...operation,
|
|
319
|
+
encoding: "json",
|
|
320
|
+
setup: operation.setup,
|
|
321
|
+
});
|
|
322
|
+
if (operation.request === undefined) {
|
|
323
|
+
return yield* Schema.encodeEffect(SandboxAccessResult)({ origins: access.origins });
|
|
324
|
+
}
|
|
325
|
+
const authorized = yield* access.authorize(
|
|
326
|
+
HttpClientRequest.make(operation.request.method)(operation.request.url, {
|
|
327
|
+
headers: operation.request.headers,
|
|
328
|
+
}),
|
|
329
|
+
);
|
|
330
|
+
return yield* Schema.encodeEffect(SandboxAccessResult)({
|
|
331
|
+
headers: authorized.headers,
|
|
332
|
+
origins: access.origins,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (operation._tag !== "oauthStart" && operation._tag !== "oauthComplete") {
|
|
336
|
+
return yield* runtime.executeAuthJson(operation);
|
|
337
|
+
}
|
|
338
|
+
const method = yield* runtime.getAuthMethod(operation);
|
|
339
|
+
const setup = yield* runtime.decodeAuthMethodSetup({
|
|
340
|
+
...operation,
|
|
341
|
+
encoding: "json",
|
|
342
|
+
setup: operation.setup,
|
|
343
|
+
});
|
|
344
|
+
const oauth = yield* OAuthFlow.Service;
|
|
345
|
+
if (operation._tag === "oauthStart") {
|
|
346
|
+
return yield* Schema.encodeEffect(OAuthStarted)(
|
|
347
|
+
yield* oauth.start({ method, redirectUri: operation.redirectUri, setup }),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
const completed = yield* oauth.complete({ ...operation, method, setup });
|
|
351
|
+
return yield* runtime.executeAuthJson(
|
|
352
|
+
yield* Schema.decodeUnknownEffect(AuthMethodExecution.JsonRequest)({
|
|
353
|
+
...operation,
|
|
354
|
+
_tag: "prepareOAuth2Tokens",
|
|
355
|
+
tokens: completed.tokens,
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
},
|
|
359
|
+
(effect, request) =>
|
|
360
|
+
pipe(
|
|
361
|
+
effect,
|
|
362
|
+
Effect.flatMap(
|
|
363
|
+
Effect.fnUntraced(function* (value) {
|
|
364
|
+
const result = { _tag: "success" as const, identity: request.identity };
|
|
365
|
+
if (value === undefined) return result;
|
|
366
|
+
return { ...result, value: yield* Schema.decodeUnknownEffect(Schema.Json)(value) };
|
|
367
|
+
}),
|
|
368
|
+
),
|
|
369
|
+
Effect.catch(error => {
|
|
370
|
+
const cause = error._tag === "AuthMethodHookError" ? error.cause : error;
|
|
371
|
+
return Effect.succeed({
|
|
372
|
+
_tag: "failure" as const,
|
|
373
|
+
error: {
|
|
374
|
+
...RemoteRuntime.failure(error),
|
|
375
|
+
...(Schema.is(OAuthGrantRejected)(cause) ? { code: "OAuthGrantRejected" } : {}),
|
|
376
|
+
},
|
|
377
|
+
identity: request.identity,
|
|
378
|
+
});
|
|
379
|
+
}),
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const executeChannelSettings = Effect.fnUntraced(
|
|
384
|
+
function* (request: ChannelSettingsRequest) {
|
|
385
|
+
const runtime = yield* DualRuntime.Service;
|
|
386
|
+
const { channelId, field, value } = request.operation;
|
|
387
|
+
const input = { channelId, value };
|
|
388
|
+
if (field === "definitionSettings") {
|
|
389
|
+
return yield* runtime.decodeAgentChannelDefinitionSettings(input);
|
|
390
|
+
}
|
|
391
|
+
return yield* runtime.decodeAgentChannelDeploymentSettings(input);
|
|
392
|
+
},
|
|
393
|
+
(effect, request) =>
|
|
394
|
+
pipe(
|
|
395
|
+
effect,
|
|
396
|
+
Effect.map(value => ({ _tag: "success", identity: request.identity, value }) as const),
|
|
397
|
+
Effect.catch(error =>
|
|
398
|
+
Effect.succeed({
|
|
399
|
+
_tag: "failure" as const,
|
|
400
|
+
error: RemoteRuntime.failure(error),
|
|
401
|
+
identity: request.identity,
|
|
402
|
+
}),
|
|
403
|
+
),
|
|
404
|
+
),
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
/** Execute one request against the plugin built in this process; only the guest runner calls it in production. */
|
|
408
|
+
export const execute = Effect.fnUntraced(function* (request: ExecutionRequest) {
|
|
409
|
+
if (!("_tag" in request)) return yield* RemoteRuntime.execute(request);
|
|
410
|
+
if (request._tag === "auth") return yield* executeAuth(request);
|
|
411
|
+
return yield* executeChannelSettings(request);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
export * as PluginRuntime from "./PluginRuntime.ts";
|
package/src/Runner.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Array,
|
|
5
|
+
Cause,
|
|
6
|
+
ConfigProvider,
|
|
7
|
+
Context,
|
|
8
|
+
Deferred,
|
|
9
|
+
Effect,
|
|
10
|
+
Exit,
|
|
11
|
+
Fiber,
|
|
12
|
+
FiberMap,
|
|
13
|
+
Formatter,
|
|
14
|
+
Inspectable,
|
|
15
|
+
Layer,
|
|
16
|
+
Match,
|
|
17
|
+
Option,
|
|
18
|
+
Predicate,
|
|
19
|
+
Redacted,
|
|
20
|
+
Schema,
|
|
21
|
+
Scope,
|
|
22
|
+
Stdio,
|
|
23
|
+
Stream,
|
|
24
|
+
String,
|
|
25
|
+
pipe,
|
|
26
|
+
} from "effect";
|
|
27
|
+
|
|
28
|
+
import { FetchHttpClient } from "effect/http";
|
|
29
|
+
|
|
30
|
+
import * as DualRegistry from "./DualRegistry.ts";
|
|
31
|
+
import * as DualRuntime from "./DualRuntime.ts";
|
|
32
|
+
import { OAuthFlow, oauth2TokenRefresherLayer } from "./OAuthFlow.ts";
|
|
33
|
+
import * as Plugin from "./Plugin.ts";
|
|
34
|
+
import { PluginRuntime } from "./PluginRuntime.ts";
|
|
35
|
+
import { RemoteRuntime } from "./remote-runtime.ts";
|
|
36
|
+
|
|
37
|
+
const guestOAuth = pipe(
|
|
38
|
+
oauth2TokenRefresherLayer,
|
|
39
|
+
Layer.provideMerge(OAuthFlow.layer),
|
|
40
|
+
Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({}))),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
function initializationError(cause: unknown) {
|
|
44
|
+
// Each hop of the runner maps its failure here; the first error keeps its message.
|
|
45
|
+
if (Schema.is(PluginRuntime.PluginExecutionError)(cause)) return cause;
|
|
46
|
+
return PluginRuntime.PluginExecutionError.make({
|
|
47
|
+
category: "runner",
|
|
48
|
+
cause: Redacted.make(cause),
|
|
49
|
+
message:
|
|
50
|
+
pipe(
|
|
51
|
+
Match.value(cause),
|
|
52
|
+
Match.when(Predicate.isString, String.trim),
|
|
53
|
+
Match.orElse(value =>
|
|
54
|
+
Formatter.format(Inspectable.toJson(value), { ignoreToString: true, space: 2 }),
|
|
55
|
+
),
|
|
56
|
+
) || "No error details returned by the plugin.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// This executable runs only in the plugin's process, using its own SDK and Effect.
|
|
61
|
+
pipe(
|
|
62
|
+
Effect.gen(function* () {
|
|
63
|
+
const guestServices = yield* Effect.context<NodeServices.NodeServices>();
|
|
64
|
+
const scope = yield* Scope.Scope;
|
|
65
|
+
const runtimeScope = yield* Scope.fork(scope, "sequential");
|
|
66
|
+
const failed = yield* Deferred.make<never, PluginRuntime.PluginExecutionError>();
|
|
67
|
+
const invocations = yield* FiberMap.make<
|
|
68
|
+
string,
|
|
69
|
+
RemoteRuntime.Response,
|
|
70
|
+
PluginRuntime.PluginExecutionError
|
|
71
|
+
>();
|
|
72
|
+
const stdio = yield* Stdio.Stdio;
|
|
73
|
+
const args = yield* Schema.decodeUnknownEffect(
|
|
74
|
+
Schema.Tuple([
|
|
75
|
+
Schema.NonEmptyString,
|
|
76
|
+
Schema.NonEmptyString,
|
|
77
|
+
Schema.Literals(["inspect", "execute"]),
|
|
78
|
+
Schema.NonEmptyString,
|
|
79
|
+
]),
|
|
80
|
+
)(yield* stdio.args);
|
|
81
|
+
const load = yield* Effect.cached(
|
|
82
|
+
Effect.forkIn(
|
|
83
|
+
Effect.gen(function* () {
|
|
84
|
+
const exports = yield* Schema.decodeUnknownEffect(
|
|
85
|
+
Schema.Record(Schema.String, Schema.Unknown),
|
|
86
|
+
)(yield* Effect.tryPromise({ try: () => import(args[0]), catch: initializationError }));
|
|
87
|
+
const plugin = exports[args[1]];
|
|
88
|
+
if (!Plugin.isPlugin(plugin)) {
|
|
89
|
+
return yield* PluginRuntime.PluginExecutionError.make({
|
|
90
|
+
category: "protocol",
|
|
91
|
+
message: "The configured export is not an integration plugin",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
yield* Effect.try({
|
|
95
|
+
try: () => Plugin.make(plugin.identifier),
|
|
96
|
+
catch: initializationError,
|
|
97
|
+
});
|
|
98
|
+
const runtime = yield* Effect.cached(
|
|
99
|
+
Effect.forkIn(
|
|
100
|
+
pipe(
|
|
101
|
+
Layer.buildWithScope(
|
|
102
|
+
pipe(
|
|
103
|
+
// @effect-diagnostics-next-line anyUnknownInErrorContext:off -- Imported plugin requirements are erased; the guest supplies only its own services.
|
|
104
|
+
DualRuntime.toLayer(DualRuntime.fromPlugin(plugin, args[3])),
|
|
105
|
+
Layer.provideMerge(
|
|
106
|
+
Layer.mergeAll(
|
|
107
|
+
RemoteRuntime.filesUnavailable,
|
|
108
|
+
FetchHttpClient.layer,
|
|
109
|
+
guestOAuth,
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
runtimeScope,
|
|
114
|
+
),
|
|
115
|
+
Effect.provideContext(Context.makeUnsafe<unknown>(guestServices.mapUnsafe)),
|
|
116
|
+
),
|
|
117
|
+
runtimeScope,
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
return { plugin, runtime };
|
|
121
|
+
}),
|
|
122
|
+
runtimeScope,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
const execute = Effect.fnUntraced(function* (request: PluginRuntime.Request) {
|
|
126
|
+
return yield* pipe(
|
|
127
|
+
Effect.gen(function* () {
|
|
128
|
+
const { plugin, runtime } = yield* Fiber.join(yield* load);
|
|
129
|
+
if (
|
|
130
|
+
request.identity.pluginKey !== plugin.identifier &&
|
|
131
|
+
!(args[2] === "inspect" && "_tag" in request && request._tag === "extract")
|
|
132
|
+
) {
|
|
133
|
+
return yield* PluginRuntime.PluginExecutionError.make({
|
|
134
|
+
category: "protocol",
|
|
135
|
+
message: "Plugin identity does not match the request",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (request.identity.pluginVersion !== args[3])
|
|
139
|
+
return yield* PluginRuntime.PluginExecutionError.make({
|
|
140
|
+
category: "protocol",
|
|
141
|
+
message: "Plugin release does not match the request",
|
|
142
|
+
});
|
|
143
|
+
if ("_tag" in request && request._tag === "extract") {
|
|
144
|
+
return {
|
|
145
|
+
_tag: "success",
|
|
146
|
+
identity: request.identity,
|
|
147
|
+
value: yield* Schema.decodeEffect(Schema.fromJsonString(Schema.Json))(
|
|
148
|
+
yield* Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown))(
|
|
149
|
+
DualRegistry.materializePlugin(plugin, {
|
|
150
|
+
version: request.identity.pluginVersion,
|
|
151
|
+
}),
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
} satisfies RemoteRuntime.Response;
|
|
155
|
+
}
|
|
156
|
+
return yield* pipe(
|
|
157
|
+
PluginRuntime.execute(request),
|
|
158
|
+
Effect.provideContext(yield* Fiber.join(yield* runtime)),
|
|
159
|
+
);
|
|
160
|
+
}),
|
|
161
|
+
Effect.catchCauseIf(
|
|
162
|
+
cause => !Cause.hasInterrupts(cause),
|
|
163
|
+
cause =>
|
|
164
|
+
args[2] === "inspect"
|
|
165
|
+
? Effect.fail(initializationError(Cause.squash(cause)))
|
|
166
|
+
: Effect.succeed({
|
|
167
|
+
_tag: "failure",
|
|
168
|
+
identity: request.identity,
|
|
169
|
+
error: {
|
|
170
|
+
category: "initialization",
|
|
171
|
+
code: "PluginInitializationFailed",
|
|
172
|
+
message: "Cannot initialize the installed plugin",
|
|
173
|
+
retryable: false,
|
|
174
|
+
},
|
|
175
|
+
} satisfies RemoteRuntime.Response),
|
|
176
|
+
),
|
|
177
|
+
Effect.mapError(initializationError),
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
yield* Effect.raceFirst(
|
|
181
|
+
pipe(
|
|
182
|
+
PluginRuntime.ipcMessages(process),
|
|
183
|
+
Stream.runForEach(
|
|
184
|
+
Effect.fnUntraced(function* (message) {
|
|
185
|
+
const input = yield* PluginRuntime.decodeInput(message);
|
|
186
|
+
const key = yield* Schema.encodeEffect(Schema.fromJsonString(RemoteRuntime.Identity))(
|
|
187
|
+
input.identity,
|
|
188
|
+
);
|
|
189
|
+
if ("_tag" in input && input._tag === "cancel") {
|
|
190
|
+
const fiber = Option.getOrUndefined(yield* FiberMap.get(invocations, key));
|
|
191
|
+
if (fiber !== undefined) yield* pipe(Fiber.interrupt(fiber), Effect.forkIn(scope));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (
|
|
195
|
+
(yield* FiberMap.has(invocations, key)) ||
|
|
196
|
+
(yield* FiberMap.size(invocations)) >= 16
|
|
197
|
+
) {
|
|
198
|
+
return yield* PluginRuntime.PluginExecutionError.make({
|
|
199
|
+
category: "protocol",
|
|
200
|
+
message: "Plugin invocation is duplicate or exceeds concurrency",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const fiber = yield* FiberMap.run(invocations, key, Effect.scoped(execute(input)));
|
|
204
|
+
yield* pipe(
|
|
205
|
+
Effect.gen(function* () {
|
|
206
|
+
const exit = yield* Fiber.await(fiber);
|
|
207
|
+
const send = Effect.fnUntraced(function* (output: PluginRuntime.Output) {
|
|
208
|
+
const encoded = yield* PluginRuntime.encodeOutput(
|
|
209
|
+
output,
|
|
210
|
+
PluginRuntime.responseLimit(input),
|
|
211
|
+
);
|
|
212
|
+
if (args[2] !== "inspect") return yield* PluginRuntime.sendIpc(process, encoded);
|
|
213
|
+
// Bun can discard queued data on disconnect; wait for its native send callback.
|
|
214
|
+
yield* Effect.callback<void, PluginRuntime.PluginExecutionError>(resume => {
|
|
215
|
+
if (process.send === undefined) {
|
|
216
|
+
resume(Effect.fail(initializationError("Plugin IPC channel is closed")));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
process.send(encoded, error => {
|
|
220
|
+
if (error !== null) resume(Effect.fail(initializationError(error)));
|
|
221
|
+
else resume(Effect.void);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
if (process.connected) process.disconnect();
|
|
225
|
+
});
|
|
226
|
+
if (Exit.isSuccess(exit)) return yield* send(exit.value);
|
|
227
|
+
if (Array.every(exit.cause.reasons, Cause.isInterruptReason)) {
|
|
228
|
+
return yield* send({ _tag: "interrupted", identity: input.identity });
|
|
229
|
+
}
|
|
230
|
+
return yield* initializationError(Cause.squash(exit.cause));
|
|
231
|
+
}),
|
|
232
|
+
Effect.catchCause(cause =>
|
|
233
|
+
Deferred.fail(failed, initializationError(Cause.squash(cause))),
|
|
234
|
+
),
|
|
235
|
+
Effect.forkIn(scope),
|
|
236
|
+
);
|
|
237
|
+
}),
|
|
238
|
+
),
|
|
239
|
+
Effect.mapError(initializationError),
|
|
240
|
+
),
|
|
241
|
+
Deferred.await(failed),
|
|
242
|
+
);
|
|
243
|
+
}),
|
|
244
|
+
Effect.scoped,
|
|
245
|
+
// @effect-diagnostics-next-line strictEffectProvide:off -- This executable owns the guest process services.
|
|
246
|
+
Effect.provide(NodeServices.layer),
|
|
247
|
+
NodeRuntime.runMain({ disableErrorReporting: process.argv[4] !== "inspect" }),
|
|
248
|
+
);
|
package/src/ToolFile.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-safe file results that hosts can persist without reconstructing displayed text.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.0.0
|
|
5
|
+
*/
|
|
6
|
+
import { Schema as EffectSchema } from "effect";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Complete file content and metadata. Hosts validate the bytes before saving them;
|
|
10
|
+
* a filename is a suggestion, never a trusted destination path.
|
|
11
|
+
*
|
|
12
|
+
* @since 0.0.0
|
|
13
|
+
* @category schemas
|
|
14
|
+
*/
|
|
15
|
+
export const Schema = EffectSchema.TaggedStruct("ToolFile", {
|
|
16
|
+
contentBase64: EffectSchema.String.annotate({
|
|
17
|
+
contentEncoding: "base64",
|
|
18
|
+
description: "Complete file bytes in Base64. Preserve this field without truncation.",
|
|
19
|
+
}),
|
|
20
|
+
contentType: EffectSchema.NullOr(EffectSchema.String),
|
|
21
|
+
filename: EffectSchema.NullOr(EffectSchema.String),
|
|
22
|
+
sizeBytes: EffectSchema.Natural,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/** @since 0.0.0 @category models */
|
|
26
|
+
export type File = typeof Schema.Type;
|
|
27
|
+
|
|
28
|
+
export * as ToolFile from "./ToolFile.ts";
|