@maiaos/sdk 0.1.0-canary.2

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 (198) hide show
  1. package/README.md +177 -0
  2. package/dist/agent-permission/agent-permission.d.ts +9 -0
  3. package/dist/agent-permission/agent-permission.js +5 -0
  4. package/dist/agent-permission/effect.d.ts +4 -0
  5. package/dist/agent-permission/effect.js +34 -0
  6. package/dist/agent-permission/index.d.ts +2 -0
  7. package/dist/agent-permission/index.js +2 -0
  8. package/dist/approval/approval.d.ts +10 -0
  9. package/dist/approval/approval.js +5 -0
  10. package/dist/approval/effect.d.ts +5 -0
  11. package/dist/approval/effect.js +43 -0
  12. package/dist/approval/index.d.ts +2 -0
  13. package/dist/approval/index.js +2 -0
  14. package/dist/call/call.d.ts +26 -0
  15. package/dist/call/call.js +6 -0
  16. package/dist/call/effect.d.ts +5 -0
  17. package/dist/call/effect.js +46 -0
  18. package/dist/call/index.d.ts +2 -0
  19. package/dist/call/index.js +2 -0
  20. package/dist/client/effect.d.ts +8 -0
  21. package/dist/client/effect.js +283 -0
  22. package/dist/client/index.d.ts +2 -0
  23. package/dist/client/index.js +2 -0
  24. package/dist/client/local-node-client.d.ts +100 -0
  25. package/dist/client/local-node-client.js +4 -0
  26. package/dist/connection/connection.d.ts +19 -0
  27. package/dist/connection/connection.js +5 -0
  28. package/dist/connection/effect.d.ts +5 -0
  29. package/dist/connection/effect.js +119 -0
  30. package/dist/connection/index.d.ts +2 -0
  31. package/dist/connection/index.js +2 -0
  32. package/dist/container/container.d.ts +14 -0
  33. package/dist/container/container.js +5 -0
  34. package/dist/container/effect.d.ts +4 -0
  35. package/dist/container/effect.js +90 -0
  36. package/dist/container/index.d.ts +2 -0
  37. package/dist/container/index.js +2 -0
  38. package/dist/discovery/discovery.d.ts +11 -0
  39. package/dist/discovery/discovery.js +5 -0
  40. package/dist/discovery/effect.d.ts +5 -0
  41. package/dist/discovery/effect.js +44 -0
  42. package/dist/discovery/index.d.ts +2 -0
  43. package/dist/discovery/index.js +2 -0
  44. package/dist/execution/effect.d.ts +4 -0
  45. package/dist/execution/effect.js +8 -0
  46. package/dist/execution/index.d.ts +2 -0
  47. package/dist/execution/index.js +2 -0
  48. package/dist/execution/relocation.d.ts +10 -0
  49. package/dist/execution/relocation.js +4 -0
  50. package/dist/feature-bindings/feature-bindings.d.ts +10 -0
  51. package/dist/feature-bindings/feature-bindings.js +2 -0
  52. package/dist/feature-bindings/index.d.ts +2 -0
  53. package/dist/feature-bindings/index.js +2 -0
  54. package/dist/hold/effect.d.ts +5 -0
  55. package/dist/hold/effect.js +61 -0
  56. package/dist/hold/hold.d.ts +12 -0
  57. package/dist/hold/hold.js +5 -0
  58. package/dist/hold/index.d.ts +2 -0
  59. package/dist/hold/index.js +2 -0
  60. package/dist/index.d.ts +26 -0
  61. package/dist/index.js +21 -0
  62. package/dist/interaction/effect.d.ts +5 -0
  63. package/dist/interaction/effect.js +79 -0
  64. package/dist/interaction/index.d.ts +2 -0
  65. package/dist/interaction/index.js +2 -0
  66. package/dist/interaction/interaction.d.ts +12 -0
  67. package/dist/interaction/interaction.js +5 -0
  68. package/dist/node/attach.d.ts +11 -0
  69. package/dist/node/attach.js +33 -0
  70. package/dist/node/bridge-frame.d.ts +18 -0
  71. package/dist/node/bridge-frame.js +67 -0
  72. package/dist/node/bridge.d.ts +31 -0
  73. package/dist/node/bridge.js +148 -0
  74. package/dist/node/discovery.d.ts +187 -0
  75. package/dist/node/discovery.js +408 -0
  76. package/dist/node/effect.d.ts +9 -0
  77. package/dist/node/effect.js +9 -0
  78. package/dist/node/fetch.d.ts +2 -0
  79. package/dist/node/fetch.js +2 -0
  80. package/dist/node/http.d.ts +27 -0
  81. package/dist/node/http.js +258 -0
  82. package/dist/node/index.d.ts +3 -0
  83. package/dist/node/index.js +3 -0
  84. package/dist/node/local-node.d.ts +26 -0
  85. package/dist/node/local-node.js +67 -0
  86. package/dist/node/session.d.ts +15 -0
  87. package/dist/node/session.js +27 -0
  88. package/dist/node/ssh.d.ts +16 -0
  89. package/dist/node/ssh.js +74 -0
  90. package/dist/node/transport.d.ts +52 -0
  91. package/dist/node/transport.js +211 -0
  92. package/dist/node-changes/effect.d.ts +11 -0
  93. package/dist/node-changes/effect.js +61 -0
  94. package/dist/node-changes/index.d.ts +2 -0
  95. package/dist/node-changes/index.js +2 -0
  96. package/dist/node-changes/node-changes.d.ts +21 -0
  97. package/dist/node-changes/node-changes.js +23 -0
  98. package/dist/port-lease/effect.d.ts +5 -0
  99. package/dist/port-lease/effect.js +53 -0
  100. package/dist/port-lease/index.d.ts +2 -0
  101. package/dist/port-lease/index.js +2 -0
  102. package/dist/port-lease/port-lease.d.ts +11 -0
  103. package/dist/port-lease/port-lease.js +5 -0
  104. package/dist/presentation/client.d.ts +10 -0
  105. package/dist/presentation/client.js +4 -0
  106. package/dist/presentation/effect.d.ts +5 -0
  107. package/dist/presentation/effect.js +30 -0
  108. package/dist/presentation/index.d.ts +5 -0
  109. package/dist/presentation/index.js +3 -0
  110. package/dist/presentation/project-list.d.ts +7 -0
  111. package/dist/presentation/project-list.js +24 -0
  112. package/dist/presentation/session-list.d.ts +7 -0
  113. package/dist/presentation/session-list.js +32 -0
  114. package/dist/project-guard/effect.d.ts +5 -0
  115. package/dist/project-guard/effect.js +39 -0
  116. package/dist/project-guard/index.d.ts +2 -0
  117. package/dist/project-guard/index.js +2 -0
  118. package/dist/project-guard/project-guard.d.ts +10 -0
  119. package/dist/project-guard/project-guard.js +5 -0
  120. package/dist/project-workspace/effect.d.ts +5 -0
  121. package/dist/project-workspace/effect.js +124 -0
  122. package/dist/project-workspace/index.d.ts +2 -0
  123. package/dist/project-workspace/index.js +2 -0
  124. package/dist/project-workspace/project-workspace.d.ts +23 -0
  125. package/dist/project-workspace/project-workspace.js +4 -0
  126. package/dist/provider-pool/effect.d.ts +4 -0
  127. package/dist/provider-pool/effect.js +5 -0
  128. package/dist/provider-pool/index.d.ts +2 -0
  129. package/dist/provider-pool/index.js +2 -0
  130. package/dist/provider-pool/provider-pool.d.ts +7 -0
  131. package/dist/provider-pool/provider-pool.js +4 -0
  132. package/dist/remote-node/effect.d.ts +4 -0
  133. package/dist/remote-node/effect.js +57 -0
  134. package/dist/remote-node/index.d.ts +2 -0
  135. package/dist/remote-node/index.js +2 -0
  136. package/dist/remote-node/remote-node.d.ts +12 -0
  137. package/dist/remote-node/remote-node.js +5 -0
  138. package/dist/request/classify.d.ts +15 -0
  139. package/dist/request/classify.js +96 -0
  140. package/dist/request/client-error.d.ts +10 -0
  141. package/dist/request/client-error.js +11 -0
  142. package/dist/request/effect.d.ts +22 -0
  143. package/dist/request/effect.js +104 -0
  144. package/dist/request/envelope-schema.d.ts +2 -0
  145. package/dist/request/envelope-schema.js +2 -0
  146. package/dist/request/index.d.ts +5 -0
  147. package/dist/request/index.js +4 -0
  148. package/dist/request/maia-client.d.ts +21 -0
  149. package/dist/request/maia-client.js +9 -0
  150. package/dist/request/options.d.ts +9 -0
  151. package/dist/request/options.js +2 -0
  152. package/dist/run-ledger/effect.d.ts +5 -0
  153. package/dist/run-ledger/effect.js +72 -0
  154. package/dist/run-ledger/index.d.ts +2 -0
  155. package/dist/run-ledger/index.js +2 -0
  156. package/dist/run-ledger/run-ledger.d.ts +12 -0
  157. package/dist/run-ledger/run-ledger.js +2 -0
  158. package/dist/session/effect.d.ts +4 -0
  159. package/dist/session/effect.js +7 -0
  160. package/dist/session/index.d.ts +2 -0
  161. package/dist/session/index.js +2 -0
  162. package/dist/session/mailbox.d.ts +9 -0
  163. package/dist/session/mailbox.js +4 -0
  164. package/dist/session-creation/effect.d.ts +25 -0
  165. package/dist/session-creation/effect.js +269 -0
  166. package/dist/session-creation/index.d.ts +3 -0
  167. package/dist/session-creation/index.js +3 -0
  168. package/dist/session-creation/session-creation.d.ts +18 -0
  169. package/dist/session-creation/session-creation.js +24 -0
  170. package/dist/session-creation/tasks.d.ts +69 -0
  171. package/dist/session-creation/tasks.js +37 -0
  172. package/dist/session-view/index.d.ts +2 -0
  173. package/dist/session-view/index.js +2 -0
  174. package/dist/session-view/view.d.ts +42 -0
  175. package/dist/session-view/view.js +137 -0
  176. package/dist/stream/effect.d.ts +29 -0
  177. package/dist/stream/effect.js +77 -0
  178. package/dist/stream/index.d.ts +2 -0
  179. package/dist/stream/index.js +2 -0
  180. package/dist/stream/stream.d.ts +9 -0
  181. package/dist/stream/stream.js +2 -0
  182. package/dist/voice/effect.d.ts +7 -0
  183. package/dist/voice/effect.js +71 -0
  184. package/dist/voice/index.d.ts +4 -0
  185. package/dist/voice/index.js +3 -0
  186. package/dist/voice/managed-turn.d.ts +9 -0
  187. package/dist/voice/managed-turn.js +4 -0
  188. package/dist/voice/orchestrator-contract.d.ts +5 -0
  189. package/dist/voice/orchestrator-contract.js +4 -0
  190. package/dist/voice/orchestrator.d.ts +25 -0
  191. package/dist/voice/orchestrator.js +169 -0
  192. package/dist/voice/pi-relay-contract.d.ts +4 -0
  193. package/dist/voice/pi-relay-contract.js +2 -0
  194. package/dist/voice/pi-relay.d.ts +12 -0
  195. package/dist/voice/pi-relay.js +9 -0
  196. package/dist/voice/wire-schema.d.ts +215 -0
  197. package/dist/voice/wire-schema.js +16 -0
  198. package/package.json +84 -0
@@ -0,0 +1,258 @@
1
+ import { MAX_NODE_API_BODY_BYTES, NODE_API_AUTHORIZATION_HEADER, NODE_API_COORDINATOR_PRINCIPAL, NODE_API_HTTP_METHOD, NODE_API_INVOCATION_CREDENTIAL_HEADER, NODE_API_JSON_CONTENT_TYPE, NODE_API_OPERATION_PATH, NODE_API_PRINCIPAL_HEADER, NODE_API_SESSION_CREDENTIAL_SCHEME, nodeApiAuthenticationSchema, nodeApiEndpointSchema, nodeApiHttpRequestSchema, nodeApiHttpResponseSchema, nodeApiInvocationAuthenticationSchema, } from "@maiaos/protocol";
2
+ import * as Data from "effect/Data";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Option from "effect/Option";
5
+ import * as Schema from "effect/Schema";
6
+ import * as Semaphore from "effect/Semaphore";
7
+ import { makeLocalNodeEffectClient, } from "../client/effect.js";
8
+ import { makeLocalNodeEffectRequester, } from "../request/effect.js";
9
+ import { MaiaClientError, } from "../request/index.js";
10
+ import { platformFetch } from "./fetch.js";
11
+ const MAX_HTTP_IN_FLIGHT_REQUESTS = 8;
12
+ const REQUEST_DEADLINE_MS = 5000;
13
+ class NodeApiTransportError extends Data.TaggedError("NodeApiTransportError") {
14
+ }
15
+ const transportError = (reason, message) => new NodeApiTransportError({ message, reason });
16
+ const CLIENT_ERROR_CODES = new Map([
17
+ ["cancelled", "cancelled"],
18
+ ["deadline_exceeded", "deadline_exceeded"],
19
+ ["invalid_response", "invalid_response"],
20
+ ["outcome_unknown", "outcome_unknown"],
21
+ ["saturated", "saturated"],
22
+ ["transport_failure", "transport_failure"],
23
+ ]);
24
+ export const toClientError = (error, signal) => {
25
+ if (error instanceof MaiaClientError)
26
+ return error;
27
+ if (signal?.aborted === true) {
28
+ return new MaiaClientError("cancelled", "The Maia Node API request was cancelled.", { cause: error });
29
+ }
30
+ if (error instanceof NodeApiTransportError) {
31
+ const code = CLIENT_ERROR_CODES.get(error.reason) ?? "transport_failure";
32
+ return new MaiaClientError(code, error.message, { cause: error });
33
+ }
34
+ return new MaiaClientError("transport_failure", "The Maia Node API transport failed.", { cause: error });
35
+ };
36
+ const invalidOptions = () => transportError("transport_failure", "The Maia Node API transport options are invalid.");
37
+ const requireSingleAuthority = (options) => (options.authorizationScheme === "session" &&
38
+ (options.invocationAuthentication !== undefined ||
39
+ options.principal !== undefined)) ||
40
+ (options.principal !== undefined &&
41
+ options.invocationAuthentication !== undefined)
42
+ ? Effect.fail(invalidOptions())
43
+ : Effect.succeed(options);
44
+ const parseOptions = (options) => Effect.try({
45
+ catch: invalidOptions,
46
+ try: () => ({
47
+ authorizationScheme: options.authorizationScheme ?? "bearer",
48
+ credential: options.credential,
49
+ endpoint: nodeApiEndpointSchema.parse(options.endpoint),
50
+ fetch: options.fetch ?? platformFetch(),
51
+ ...(options.principal === undefined
52
+ ? {}
53
+ : { principal: options.principal }),
54
+ ...(options.invocationAuthentication === undefined
55
+ ? {}
56
+ : {
57
+ invocationAuthentication: nodeApiInvocationAuthenticationSchema.parse(options.invocationAuthentication),
58
+ }),
59
+ }),
60
+ });
61
+ const validateCredential = (token) => Effect.try({
62
+ catch: () => transportError("transport_failure", "The Maia Node API credential is invalid."),
63
+ try: () => nodeApiAuthenticationSchema.parse({
64
+ scheme: "maia.node.bearer/v1",
65
+ token,
66
+ }).token,
67
+ });
68
+ const credentialUnavailable = () => transportError("transport_failure", "The Maia Node API credential is unavailable.");
69
+ const readCredentialSource = (credential) => {
70
+ if (typeof credential === "string")
71
+ return Effect.succeed(credential);
72
+ return Effect.isEffect(credential)
73
+ ? Effect.mapError(credential, credentialUnavailable)
74
+ : Effect.tryPromise({ catch: credentialUnavailable, try: credential });
75
+ };
76
+ export const resolveCredential = (credential) => readCredentialSource(credential).pipe(Effect.flatMap(validateCredential));
77
+ const responseTooLarge = () => new TypeError("Response body exceeds its resource limit.");
78
+ const decodeBody = (bytes, state) => Effect.try({
79
+ catch: (error) => error,
80
+ try: () => {
81
+ if (state.offset === 0)
82
+ throw new TypeError("Response body is empty.");
83
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, state.offset));
84
+ },
85
+ });
86
+ const readBodyChunks = (reader, bytes, state) => Effect.tryPromise({ catch: (error) => error, try: () => reader.read() }).pipe(Effect.flatMap((next) => {
87
+ if (next.done)
88
+ return decodeBody(bytes, state);
89
+ if (state.offset + next.value.byteLength > bytes.byteLength) {
90
+ return Effect.tryPromise({
91
+ catch: (error) => error,
92
+ try: () => reader.cancel("response_too_large"),
93
+ }).pipe(Effect.andThen(Effect.fail(responseTooLarge())));
94
+ }
95
+ bytes.set(next.value, state.offset);
96
+ state.offset += next.value.byteLength;
97
+ return readBodyChunks(reader, bytes, state);
98
+ }));
99
+ const readBoundedBody = (body) => Effect.acquireUseRelease(Effect.try({ catch: (error) => error, try: () => body.getReader() }), (reader) => readBodyChunks(reader, new Uint8Array(MAX_NODE_API_BODY_BYTES), {
100
+ offset: 0,
101
+ }).pipe(Effect.onInterrupt(() => Effect.sync(() => {
102
+ void reader.cancel().catch(() => false);
103
+ }))), (reader) => Effect.sync(() => {
104
+ reader.releaseLock();
105
+ }));
106
+ const hasOversizedContentLength = (response) => {
107
+ const raw = response.headers.get("content-length");
108
+ if (raw === null || !/^\d+$/u.test(raw))
109
+ return false;
110
+ return Number(raw) > MAX_NODE_API_BODY_BYTES;
111
+ };
112
+ const readBoundedResponse = (response) => (hasOversizedContentLength(response) || response.body === null
113
+ ? Effect.fail(responseTooLarge())
114
+ : readBoundedBody(response.body)).pipe(Effect.mapError(() => transportError("invalid_response", "The Maia Node API response could not be read within its limits.")));
115
+ const validateResponseMetadata = (response) => {
116
+ if (response.redirected || response.url === "") {
117
+ return Effect.fail(transportError("invalid_response", "The Maia Node API returned an invalid HTTP response."));
118
+ }
119
+ const contentType = response.headers
120
+ .get("content-type")
121
+ ?.split(";", 1)[0]
122
+ ?.trim();
123
+ return contentType === NODE_API_JSON_CONTENT_TYPE
124
+ ? Effect.void
125
+ : Effect.fail(transportError("invalid_response", "The Maia Node API returned an unsupported response type."));
126
+ };
127
+ const decodeJson = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
128
+ const decodeResponseBody = (body) => decodeJson(body).pipe(Effect.flatMap((value) => Effect.try({
129
+ catch: (error) => error,
130
+ try: () => nodeApiHttpResponseSchema.parse(value),
131
+ })), Effect.mapError(() => transportError("invalid_response", "The Maia Node API response failed runtime validation.")));
132
+ const decodeHttpResponse = Effect.fn("NodeApiTransport.decodeResponse")(function* (response, requestUrl) {
133
+ const decoded = yield* validateResponseMetadata(response).pipe(Effect.andThen(readBoundedResponse(response)), Effect.flatMap(decodeResponseBody), Effect.mapError(() => dispatchFailure(true)));
134
+ if (response.status === 401 &&
135
+ response.url === requestUrl &&
136
+ decoded.correlationId === undefined &&
137
+ !decoded.payload.ok &&
138
+ decoded.payload.error.code === "authentication_failed") {
139
+ return yield* Effect.fail(new MaiaClientError("remote_error", "The Maia Node API rejected authentication.", { remoteError: decoded.payload.error }));
140
+ }
141
+ return decoded;
142
+ });
143
+ const encodeHttpBody = (envelope) => Effect.try({
144
+ catch: () => transportError("transport_failure", "The Maia Node API request failed runtime validation."),
145
+ try: () => JSON.stringify(nodeApiHttpRequestSchema.parse(envelope)),
146
+ }).pipe(Effect.filterOrFail((body) => new TextEncoder().encode(body).byteLength <= MAX_NODE_API_BODY_BYTES, () => transportError("transport_failure", "The Maia Node API request exceeded its resource limit.")));
147
+ const authorizationValue = (options, token) => options.authorizationScheme === "session"
148
+ ? `${NODE_API_SESSION_CREDENTIAL_SCHEME} ${token}`
149
+ : `Bearer ${token}`;
150
+ const requestHeaders = (options, token) => {
151
+ const headers = new Headers([
152
+ ["content-type", NODE_API_JSON_CONTENT_TYPE],
153
+ [NODE_API_AUTHORIZATION_HEADER, authorizationValue(options, token)],
154
+ ]);
155
+ if (options.invocationAuthentication !== undefined) {
156
+ headers.set(NODE_API_INVOCATION_CREDENTIAL_HEADER, options.invocationAuthentication.credential);
157
+ }
158
+ if (options.principal !== undefined)
159
+ headers.set(NODE_API_PRINCIPAL_HEADER, NODE_API_COORDINATOR_PRINCIPAL);
160
+ return headers;
161
+ };
162
+ const dispatchFailure = (dispatched) => dispatched
163
+ ? transportError("outcome_unknown", "The Maia Node API request outcome is unknown.")
164
+ : transportError("transport_failure", "The Maia Node API request could not be dispatched.");
165
+ const forwardAbort = (source, target) => {
166
+ const abort = () => {
167
+ target.abort(source.reason);
168
+ };
169
+ if (source.aborted)
170
+ abort();
171
+ else
172
+ source.addEventListener("abort", abort, { once: true });
173
+ return () => {
174
+ source.removeEventListener("abort", abort);
175
+ };
176
+ };
177
+ const combinedRequestSignal = (effectSignal, callerSignal) => {
178
+ const controller = new AbortController();
179
+ const releases = [forwardAbort(effectSignal, controller)];
180
+ if (callerSignal !== undefined) {
181
+ releases.push(forwardAbort(callerSignal, controller));
182
+ }
183
+ return {
184
+ release: () => {
185
+ releases.forEach((release) => {
186
+ release();
187
+ });
188
+ },
189
+ signal: controller.signal,
190
+ };
191
+ };
192
+ const dispatchHttpRequest = (input) => Effect.tryPromise({
193
+ catch: () => dispatchFailure(input.context.dispatchState.dispatched),
194
+ try: (effectSignal) => {
195
+ const combined = combinedRequestSignal(effectSignal, input.context.signal);
196
+ input.context.dispatchState.dispatched = true;
197
+ return input.options
198
+ .fetch(`${input.options.endpoint.origin}${NODE_API_OPERATION_PATH}`, {
199
+ body: input.body,
200
+ cache: "no-store",
201
+ credentials: "omit",
202
+ headers: requestHeaders(input.options, input.token),
203
+ method: NODE_API_HTTP_METHOD,
204
+ redirect: "error",
205
+ referrerPolicy: "no-referrer",
206
+ signal: combined.signal,
207
+ })
208
+ .finally(combined.release);
209
+ },
210
+ });
211
+ const executeHttpRequest = Effect.fn("NodeApiTransport.request")(function* (context) {
212
+ const options = yield* requireSingleAuthority(context.options).pipe(Effect.flatMap(parseOptions));
213
+ if (typeof options.fetch !== "function") {
214
+ return yield* Effect.fail(transportError("transport_failure", "No Maia Node API HTTP implementation is available."));
215
+ }
216
+ const [body, token] = yield* Effect.all([
217
+ encodeHttpBody(context.envelope),
218
+ resolveCredential(options.credential),
219
+ ]);
220
+ const response = yield* dispatchHttpRequest({
221
+ body,
222
+ context,
223
+ options: { ...options, fetch: options.fetch },
224
+ token,
225
+ });
226
+ return yield* decodeHttpResponse(response, `${options.endpoint.origin}${NODE_API_OPERATION_PATH}`);
227
+ });
228
+ const deadlineFailure = (state) => state.dispatched
229
+ ? transportError("outcome_unknown", "The Maia Node API request outcome is unknown.")
230
+ : transportError("deadline_exceeded", "The Maia Node API request deadline expired before dispatch.");
231
+ const admitHttpRequest = (concurrency, context) => concurrency
232
+ .withPermitsIfAvailable(1)(executeHttpRequest(context).pipe(Effect.timeoutOrElse({
233
+ duration: REQUEST_DEADLINE_MS,
234
+ orElse: () => Effect.fail(deadlineFailure(context.dispatchState)),
235
+ })))
236
+ .pipe(Effect.flatMap((admitted) => Option.isSome(admitted)
237
+ ? Effect.succeed(admitted.value)
238
+ : Effect.fail(transportError("saturated", "The Maia Node API transport reached its concurrency limit."))));
239
+ export const makeNodeApiHttpRequest = (options) => {
240
+ const concurrency = Semaphore.makeUnsafe(MAX_HTTP_IN_FLIGHT_REQUESTS);
241
+ return (envelope, requestOptions) => {
242
+ const signal = requestOptions?.signal;
243
+ return admitHttpRequest(concurrency, {
244
+ dispatchState: { dispatched: false },
245
+ envelope,
246
+ options,
247
+ ...(signal === undefined ? {} : { signal }),
248
+ });
249
+ };
250
+ };
251
+ export const makeNodeApiEffectTransport = (options) => {
252
+ const request = makeNodeApiHttpRequest(options);
253
+ return {
254
+ request: (envelope, requestOptions) => request(envelope, requestOptions).pipe(Effect.mapError((error) => toClientError(error, requestOptions?.signal))),
255
+ };
256
+ };
257
+ export const createNodeApiEffectClient = (options) => makeLocalNodeEffectClient(makeLocalNodeEffectRequester(makeNodeApiEffectTransport(options)));
258
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1,3 @@
1
+ export { acknowledgeAttachedSessionDelivery, type AttachedCurrentContextOptions, type AttachedLocalNodeOptions, checkAttachedSessionInbox, connectNodeApi, type DiscoveredNodeApiConnection, type LocalNodeConnection, type LocalNodeDiscoveryOptions, type NodeApiDiscoveryOptions, sendAttachedSessionMessage, showAttachedCurrentContext, } from "./local-node.js";
2
+ export { attachSession, createNodeApiClient, createNodeApiTransport, type NodeApiConnection, type NodeApiCredential, type NodeApiObservationOptions, type NodeApiObservationRequest, type NodeApiTransportOptions, type SessionAttachOptions, type SessionAttachRequest, subscribeNodeApiObservations, } from "./transport.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { acknowledgeAttachedSessionDelivery, checkAttachedSessionInbox, connectNodeApi, sendAttachedSessionMessage, showAttachedCurrentContext, } from "./local-node.js";
2
+ export { attachSession, createNodeApiClient, createNodeApiTransport, subscribeNodeApiObservations, } from "./transport.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,26 @@
1
+ import type { InvocationContextShowResult, LocalNodeStatusResult, SessionInboxAckParameters, SessionInboxAckResult, SessionInboxCheckParameters, SessionInboxCheckResult, SessionMessageSendParameters, SessionMessageSendResult } from "@maiaos/protocol";
2
+ import { type NodeApiConnection } from "./transport.js";
3
+ export type LocalNodeDiscoveryOptions = {
4
+ readonly principal?: "coordinator";
5
+ readonly profile?: string;
6
+ readonly runtimeRootOverride?: string;
7
+ readonly signal?: AbortSignal;
8
+ };
9
+ export type NodeApiDiscoveryOptions = LocalNodeDiscoveryOptions;
10
+ export type DiscoveredNodeApiConnection = NodeApiConnection & {
11
+ readonly status: LocalNodeStatusResult;
12
+ };
13
+ export type LocalNodeConnection = DiscoveredNodeApiConnection;
14
+ export declare const principalOf: (options: Pick<NodeApiDiscoveryOptions, "principal">) => {
15
+ readonly principal?: "coordinator";
16
+ };
17
+ export declare const connectNodeApi: (options?: NodeApiDiscoveryOptions) => Promise<DiscoveredNodeApiConnection>;
18
+ export type AttachedLocalNodeOptions = LocalNodeDiscoveryOptions & {
19
+ readonly messageId?: string;
20
+ };
21
+ export type AttachedCurrentContextOptions = AttachedLocalNodeOptions;
22
+ export declare const showAttachedCurrentContext: (options?: AttachedCurrentContextOptions) => Promise<InvocationContextShowResult>;
23
+ export declare const sendAttachedSessionMessage: (parameters: SessionMessageSendParameters, options?: AttachedLocalNodeOptions) => Promise<SessionMessageSendResult>;
24
+ export declare const checkAttachedSessionInbox: (parameters: SessionInboxCheckParameters, options?: AttachedLocalNodeOptions) => Promise<SessionInboxCheckResult>;
25
+ export declare const acknowledgeAttachedSessionDelivery: (parameters: SessionInboxAckParameters, options?: AttachedLocalNodeOptions) => Promise<SessionInboxAckResult>;
26
+ //# sourceMappingURL=local-node.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import * as Crypto from "effect/Crypto";
3
+ import * as Effect from "effect/Effect";
4
+ import * as PlatformError from "effect/PlatformError";
5
+ import { MaiaClientError } from "../request/index.js";
6
+ import { acquireAttachedCredential, attachedTransportOptions, connectDiscoveredNodeApi, discoveryFailure, newMessageId, requireAttachmentPlatform, } from "./discovery.js";
7
+ import { createNodeApiClient } from "./transport.js";
8
+ const digestAlgorithms = {
9
+ ["SHA-1"]: "sha1",
10
+ ["SHA-256"]: "sha256",
11
+ ["SHA-384"]: "sha384",
12
+ ["SHA-512"]: "sha512",
13
+ };
14
+ const facadeCrypto = Crypto.make({
15
+ digest: (algorithm, data) => Effect.try({
16
+ catch: (cause) => PlatformError.systemError({
17
+ _tag: "Unknown",
18
+ cause,
19
+ description: "Could not compute digest",
20
+ method: "digest",
21
+ module: "Crypto",
22
+ }),
23
+ try: () => Uint8Array.from(createHash(digestAlgorithms[algorithm]).update(data).digest()),
24
+ }),
25
+ randomBytes: (size) => randomBytes(size),
26
+ });
27
+ const runFacade = (program, signal) => Effect.runPromise(Effect.provideService(program, Crypto.Crypto, facadeCrypto), signal === undefined ? undefined : { signal });
28
+ export const principalOf = (options) => options.principal === undefined ? {} : { principal: options.principal };
29
+ export const connectNodeApi = (options = {}) => runFacade(connectDiscoveredNodeApi(options, (descriptor, token) => {
30
+ const client = createNodeApiClient({
31
+ credential: token,
32
+ endpoint: descriptor.endpoint,
33
+ ...principalOf(options),
34
+ });
35
+ return {
36
+ client,
37
+ status: (messageId) => Effect.tryPromise({
38
+ catch: (error) => error,
39
+ try: (signal) => client.status({ messageId, signal }),
40
+ }),
41
+ };
42
+ }), options.signal).catch((error) => {
43
+ if (options.signal?.aborted === true) {
44
+ throw new MaiaClientError("cancelled", "The Maia Node API discovery was cancelled.", { cause: error });
45
+ }
46
+ throw discoveryFailure(error);
47
+ });
48
+ const readAttachedCredential = (options) => runFacade(acquireAttachedCredential(), options.signal).catch((error) => {
49
+ throw new MaiaClientError(options.signal?.aborted === true ? "cancelled" : "transport_failure", "The inherited Invocation attachment is invalid or unavailable.", { cause: error });
50
+ });
51
+ const attachedLocalNodeClient = (descriptor, credential) => createNodeApiClient(attachedTransportOptions(descriptor, credential, (read) => () => Effect.runPromise(read)));
52
+ const attachedRequestOptions = (prefix, options) => runFacade(options.messageId === undefined
53
+ ? newMessageId(prefix)
54
+ : Effect.succeed(options.messageId)).then((messageId) => ({
55
+ messageId,
56
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
57
+ }));
58
+ const withAttachedLocalNode = (options, operation) => runFacade(requireAttachmentPlatform())
59
+ .then(() => connectNodeApi(options))
60
+ .then((connection) => readAttachedCredential(options).then((credential) => operation(attachedLocalNodeClient(connection.descriptor, credential)).finally(() => {
61
+ credential.fill(0);
62
+ })));
63
+ export const showAttachedCurrentContext = (options = {}) => withAttachedLocalNode(options, (client) => attachedRequestOptions("context_attached", options).then((requestOptions) => client.showCurrentContext({}, requestOptions)));
64
+ export const sendAttachedSessionMessage = (parameters, options = {}) => withAttachedLocalNode(options, (client) => attachedRequestOptions("session_message", options).then((requestOptions) => client.sendSessionMessage(parameters, requestOptions)));
65
+ export const checkAttachedSessionInbox = (parameters, options = {}) => withAttachedLocalNode(options, (client) => attachedRequestOptions("session_inbox_check", options).then((requestOptions) => client.checkSessionInbox(parameters, requestOptions)));
66
+ export const acknowledgeAttachedSessionDelivery = (parameters, options = {}) => withAttachedLocalNode(options, (client) => attachedRequestOptions("session_inbox_ack", options).then((requestOptions) => client.acknowledgeSessionDelivery(parameters, requestOptions)));
67
+ //# sourceMappingURL=local-node.js.map
@@ -0,0 +1,15 @@
1
+ import { type LocalNodeStatusResult } from "@maiaos/protocol";
2
+ import type * as Crypto from "effect/Crypto";
3
+ import * as Effect from "effect/Effect";
4
+ import type { LocalNodeEffectClient } from "../client/effect.js";
5
+ import { MaiaClientError } from "../request/index.js";
6
+ export type SessionNodeApiEffectOptions = {
7
+ readonly credential: string;
8
+ readonly origin: string;
9
+ };
10
+ export type SessionNodeApiEffectConnection = {
11
+ readonly client: LocalNodeEffectClient;
12
+ readonly status: LocalNodeStatusResult;
13
+ };
14
+ export declare const connectSessionNodeApiEffect: (options: SessionNodeApiEffectOptions) => Effect.Effect<SessionNodeApiEffectConnection, MaiaClientError, Crypto.Crypto>;
15
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { NODE_API_OBSERVATION_PATH, NODE_API_OPERATION_PATH, nodeApiEndpointSchema, } from "@maiaos/protocol";
2
+ import * as Effect from "effect/Effect";
3
+ import { MaiaClientError } from "../request/index.js";
4
+ import { newMessageId } from "./discovery.js";
5
+ import { createNodeApiEffectClient } from "./http.js";
6
+ const invalidOrigin = () => new MaiaClientError("transport_failure", "The Maia Node origin of this Session is invalid.");
7
+ const sessionEndpoint = (origin) => Effect.try({
8
+ catch: invalidOrigin,
9
+ try: () => nodeApiEndpointSchema.parse({
10
+ observationPath: NODE_API_OBSERVATION_PATH,
11
+ operationPath: NODE_API_OPERATION_PATH,
12
+ origin,
13
+ transport: "http-websocket",
14
+ }),
15
+ });
16
+ export const connectSessionNodeApiEffect = (options) => Effect.gen(function* () {
17
+ const endpoint = yield* sessionEndpoint(options.origin);
18
+ const client = createNodeApiEffectClient({
19
+ authorizationScheme: "session",
20
+ credential: options.credential,
21
+ endpoint,
22
+ });
23
+ const messageId = yield* newMessageId("discovery");
24
+ const status = yield* client.status({ messageId });
25
+ return { client, status };
26
+ });
27
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1,16 @@
1
+ export declare const SSH_OPTIONS: readonly ["-o", "BatchMode=yes", "-o", "ConnectTimeout=15", "-o", "ServerAliveInterval=15", "-o", "ServerAliveCountMax=4", "-o", "ForwardAgent=no", "-o", "ForwardX11=no", "-o", "ClearAllForwardings=yes", "-o", "GSSAPIDelegateCredentials=no", "-o", "PermitLocalCommand=no", "-o", "Tunnel=no", "-o", "ControlMaster=no", "-o", "ControlPath=none", "-o", "SendEnv=-*"];
2
+ export declare const PATH_SEGMENT_PATTERN: RegExp;
3
+ export declare const isPlainAbsolutePath: (path: string) => boolean;
4
+ export declare const shellQuote: (value: string) => string;
5
+ export declare const REMOTE_BOOTSTRAP = "eval \"$(printf %s \"$1\" | base64 -d)\"";
6
+ export declare const encodedScriptBytes: (script: string) => number;
7
+ export declare const bootstrapArguments: (script: string) => readonly string[];
8
+ export declare const remoteCommandWords: (script: string) => readonly string[];
9
+ export declare const sshCommandArguments: (input: {
10
+ readonly compress?: boolean;
11
+ readonly host: string;
12
+ readonly script: string;
13
+ readonly terminal?: boolean;
14
+ }) => readonly string[];
15
+ export declare const bridgeScript: (launcherPath: string) => string;
16
+ //# sourceMappingURL=ssh.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { REMOTE_NODE_SSH_HOST_PATTERN } from "@maiaos/protocol";
3
+ export const SSH_OPTIONS = [
4
+ "-o",
5
+ "BatchMode=yes",
6
+ "-o",
7
+ "ConnectTimeout=15",
8
+ "-o",
9
+ "ServerAliveInterval=15",
10
+ "-o",
11
+ "ServerAliveCountMax=4",
12
+ "-o",
13
+ "ForwardAgent=no",
14
+ "-o",
15
+ "ForwardX11=no",
16
+ "-o",
17
+ "ClearAllForwardings=yes",
18
+ "-o",
19
+ "GSSAPIDelegateCredentials=no",
20
+ "-o",
21
+ "PermitLocalCommand=no",
22
+ "-o",
23
+ "Tunnel=no",
24
+ "-o",
25
+ "ControlMaster=no",
26
+ "-o",
27
+ "ControlPath=none",
28
+ "-o",
29
+ "SendEnv=-*",
30
+ ];
31
+ export const PATH_SEGMENT_PATTERN = /^(?!\.{1,2}$)[A-Za-z0-9._-]{1,128}$/u;
32
+ const ABSOLUTE_PATH_PATTERN = /^\/[A-Za-z0-9._/-]{1,512}$/u;
33
+ export const isPlainAbsolutePath = (path) => ABSOLUTE_PATH_PATTERN.test(path) &&
34
+ path
35
+ .slice(1)
36
+ .split("/")
37
+ .every((segment) => PATH_SEGMENT_PATTERN.test(segment));
38
+ export const shellQuote = (value) => `'${value.replaceAll("'", String.raw `'\''`)}'`;
39
+ export const REMOTE_BOOTSTRAP = 'eval "$(printf %s "$1" | base64 -d)"';
40
+ const encodeScript = (script) => Buffer.from(script, "utf8").toString("base64");
41
+ export const encodedScriptBytes = (script) => Math.ceil(Buffer.byteLength(script, "utf8") / 3) * 4;
42
+ export const bootstrapArguments = (script) => [
43
+ "-c",
44
+ REMOTE_BOOTSTRAP,
45
+ "maia-remote",
46
+ encodeScript(script),
47
+ ];
48
+ export const remoteCommandWords = (script) => [
49
+ "sh",
50
+ "-c",
51
+ shellQuote(REMOTE_BOOTSTRAP),
52
+ "maia-remote",
53
+ encodeScript(script),
54
+ ];
55
+ export const sshCommandArguments = (input) => {
56
+ if (!REMOTE_NODE_SSH_HOST_PATTERN.test(input.host)) {
57
+ throw new TypeError("The SSH host is invalid.");
58
+ }
59
+ return [
60
+ ...SSH_OPTIONS,
61
+ input.terminal === true ? "-t" : "-T",
62
+ ...(input.compress === true ? ["-o", "Compression=yes"] : []),
63
+ "--",
64
+ input.host,
65
+ ...remoteCommandWords(input.script),
66
+ ];
67
+ };
68
+ export const bridgeScript = (launcherPath) => {
69
+ if (!isPlainAbsolutePath(launcherPath)) {
70
+ throw new TypeError("The launcher path is not a plain absolute path.");
71
+ }
72
+ return `exec ${shellQuote(launcherPath)}`;
73
+ };
74
+ //# sourceMappingURL=ssh.js.map
@@ -0,0 +1,52 @@
1
+ import { type NodeApiConnectionDescriptor, type NodeApiEndpoint, type NodeApiInvocationAuthentication, type NodeApiObservationFrame, type NodeApiObservationSubscribe, type SessionAttachFrame, type SessionAttachSubscribe } from "@maiaos/protocol";
2
+ import * as Stream from "effect/Stream";
3
+ import { type LocalNodeClient } from "../client/index.js";
4
+ import { MaiaClientError, type MaiaTransport } from "../request/index.js";
5
+ type Fetch = typeof globalThis.fetch;
6
+ type WebSocketEvent = {
7
+ readonly data?: unknown;
8
+ };
9
+ type WebSocketCloseEvent = {
10
+ readonly code?: number;
11
+ readonly reason?: string;
12
+ };
13
+ type WebSocketLike = {
14
+ readonly addEventListener: (type: "close" | "error" | "message" | "open", listener: (event: WebSocketCloseEvent | WebSocketEvent) => void) => void;
15
+ readonly close: (code?: number, reason?: string) => void;
16
+ readonly readyState?: number;
17
+ readonly removeEventListener: (type: "close" | "error" | "message" | "open", listener: (event: WebSocketCloseEvent | WebSocketEvent) => void) => void;
18
+ readonly send: (data: string) => void;
19
+ };
20
+ type WebSocketFactory = (url: string, protocols: readonly string[]) => WebSocketLike;
21
+ export type NodeApiCredential = (() => Promise<string>) | string;
22
+ export type NodeApiTransportOptions = {
23
+ readonly authorizationScheme?: "bearer" | "session";
24
+ readonly credential: NodeApiCredential;
25
+ readonly endpoint: NodeApiEndpoint;
26
+ readonly fetch?: Fetch;
27
+ readonly invocationAuthentication?: NodeApiInvocationAuthentication;
28
+ readonly principal?: "coordinator";
29
+ };
30
+ export type NodeApiConnection = {
31
+ readonly client: LocalNodeClient;
32
+ readonly descriptor: NodeApiConnectionDescriptor;
33
+ };
34
+ export type NodeApiObservationRequest = Omit<NodeApiObservationSubscribe, "authentication">;
35
+ export type NodeApiObservationOptions = {
36
+ readonly credential: NodeApiCredential;
37
+ readonly endpoint: NodeApiEndpoint;
38
+ readonly signal?: AbortSignal;
39
+ readonly subscribe: NodeApiObservationRequest;
40
+ readonly webSocketFactory?: WebSocketFactory;
41
+ };
42
+ export type SessionAttachRequest = Omit<SessionAttachSubscribe, "authentication">;
43
+ export type SessionAttachOptions = Omit<NodeApiObservationOptions, "subscribe"> & {
44
+ readonly request: SessionAttachRequest;
45
+ };
46
+ export declare const createNodeApiTransport: (options: NodeApiTransportOptions) => MaiaTransport;
47
+ export declare const createNodeApiClient: (options: NodeApiTransportOptions) => LocalNodeClient;
48
+ export declare const subscribeNodeApiObservations: (options: NodeApiObservationOptions) => AsyncIterable<NodeApiObservationFrame>;
49
+ export declare const makeSessionAttachStream: (options: SessionAttachOptions) => Stream.Stream<SessionAttachFrame, MaiaClientError>;
50
+ export declare const attachSession: (options: SessionAttachOptions) => AsyncIterable<SessionAttachFrame>;
51
+ export {};
52
+ //# sourceMappingURL=transport.d.ts.map