@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.
- package/README.md +177 -0
- package/dist/agent-permission/agent-permission.d.ts +9 -0
- package/dist/agent-permission/agent-permission.js +5 -0
- package/dist/agent-permission/effect.d.ts +4 -0
- package/dist/agent-permission/effect.js +34 -0
- package/dist/agent-permission/index.d.ts +2 -0
- package/dist/agent-permission/index.js +2 -0
- package/dist/approval/approval.d.ts +10 -0
- package/dist/approval/approval.js +5 -0
- package/dist/approval/effect.d.ts +5 -0
- package/dist/approval/effect.js +43 -0
- package/dist/approval/index.d.ts +2 -0
- package/dist/approval/index.js +2 -0
- package/dist/call/call.d.ts +26 -0
- package/dist/call/call.js +6 -0
- package/dist/call/effect.d.ts +5 -0
- package/dist/call/effect.js +46 -0
- package/dist/call/index.d.ts +2 -0
- package/dist/call/index.js +2 -0
- package/dist/client/effect.d.ts +8 -0
- package/dist/client/effect.js +283 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -0
- package/dist/client/local-node-client.d.ts +100 -0
- package/dist/client/local-node-client.js +4 -0
- package/dist/connection/connection.d.ts +19 -0
- package/dist/connection/connection.js +5 -0
- package/dist/connection/effect.d.ts +5 -0
- package/dist/connection/effect.js +119 -0
- package/dist/connection/index.d.ts +2 -0
- package/dist/connection/index.js +2 -0
- package/dist/container/container.d.ts +14 -0
- package/dist/container/container.js +5 -0
- package/dist/container/effect.d.ts +4 -0
- package/dist/container/effect.js +90 -0
- package/dist/container/index.d.ts +2 -0
- package/dist/container/index.js +2 -0
- package/dist/discovery/discovery.d.ts +11 -0
- package/dist/discovery/discovery.js +5 -0
- package/dist/discovery/effect.d.ts +5 -0
- package/dist/discovery/effect.js +44 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.js +2 -0
- package/dist/execution/effect.d.ts +4 -0
- package/dist/execution/effect.js +8 -0
- package/dist/execution/index.d.ts +2 -0
- package/dist/execution/index.js +2 -0
- package/dist/execution/relocation.d.ts +10 -0
- package/dist/execution/relocation.js +4 -0
- package/dist/feature-bindings/feature-bindings.d.ts +10 -0
- package/dist/feature-bindings/feature-bindings.js +2 -0
- package/dist/feature-bindings/index.d.ts +2 -0
- package/dist/feature-bindings/index.js +2 -0
- package/dist/hold/effect.d.ts +5 -0
- package/dist/hold/effect.js +61 -0
- package/dist/hold/hold.d.ts +12 -0
- package/dist/hold/hold.js +5 -0
- package/dist/hold/index.d.ts +2 -0
- package/dist/hold/index.js +2 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +21 -0
- package/dist/interaction/effect.d.ts +5 -0
- package/dist/interaction/effect.js +79 -0
- package/dist/interaction/index.d.ts +2 -0
- package/dist/interaction/index.js +2 -0
- package/dist/interaction/interaction.d.ts +12 -0
- package/dist/interaction/interaction.js +5 -0
- package/dist/node/attach.d.ts +11 -0
- package/dist/node/attach.js +33 -0
- package/dist/node/bridge-frame.d.ts +18 -0
- package/dist/node/bridge-frame.js +67 -0
- package/dist/node/bridge.d.ts +31 -0
- package/dist/node/bridge.js +148 -0
- package/dist/node/discovery.d.ts +187 -0
- package/dist/node/discovery.js +408 -0
- package/dist/node/effect.d.ts +9 -0
- package/dist/node/effect.js +9 -0
- package/dist/node/fetch.d.ts +2 -0
- package/dist/node/fetch.js +2 -0
- package/dist/node/http.d.ts +27 -0
- package/dist/node/http.js +258 -0
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.js +3 -0
- package/dist/node/local-node.d.ts +26 -0
- package/dist/node/local-node.js +67 -0
- package/dist/node/session.d.ts +15 -0
- package/dist/node/session.js +27 -0
- package/dist/node/ssh.d.ts +16 -0
- package/dist/node/ssh.js +74 -0
- package/dist/node/transport.d.ts +52 -0
- package/dist/node/transport.js +211 -0
- package/dist/node-changes/effect.d.ts +11 -0
- package/dist/node-changes/effect.js +61 -0
- package/dist/node-changes/index.d.ts +2 -0
- package/dist/node-changes/index.js +2 -0
- package/dist/node-changes/node-changes.d.ts +21 -0
- package/dist/node-changes/node-changes.js +23 -0
- package/dist/port-lease/effect.d.ts +5 -0
- package/dist/port-lease/effect.js +53 -0
- package/dist/port-lease/index.d.ts +2 -0
- package/dist/port-lease/index.js +2 -0
- package/dist/port-lease/port-lease.d.ts +11 -0
- package/dist/port-lease/port-lease.js +5 -0
- package/dist/presentation/client.d.ts +10 -0
- package/dist/presentation/client.js +4 -0
- package/dist/presentation/effect.d.ts +5 -0
- package/dist/presentation/effect.js +30 -0
- package/dist/presentation/index.d.ts +5 -0
- package/dist/presentation/index.js +3 -0
- package/dist/presentation/project-list.d.ts +7 -0
- package/dist/presentation/project-list.js +24 -0
- package/dist/presentation/session-list.d.ts +7 -0
- package/dist/presentation/session-list.js +32 -0
- package/dist/project-guard/effect.d.ts +5 -0
- package/dist/project-guard/effect.js +39 -0
- package/dist/project-guard/index.d.ts +2 -0
- package/dist/project-guard/index.js +2 -0
- package/dist/project-guard/project-guard.d.ts +10 -0
- package/dist/project-guard/project-guard.js +5 -0
- package/dist/project-workspace/effect.d.ts +5 -0
- package/dist/project-workspace/effect.js +124 -0
- package/dist/project-workspace/index.d.ts +2 -0
- package/dist/project-workspace/index.js +2 -0
- package/dist/project-workspace/project-workspace.d.ts +23 -0
- package/dist/project-workspace/project-workspace.js +4 -0
- package/dist/provider-pool/effect.d.ts +4 -0
- package/dist/provider-pool/effect.js +5 -0
- package/dist/provider-pool/index.d.ts +2 -0
- package/dist/provider-pool/index.js +2 -0
- package/dist/provider-pool/provider-pool.d.ts +7 -0
- package/dist/provider-pool/provider-pool.js +4 -0
- package/dist/remote-node/effect.d.ts +4 -0
- package/dist/remote-node/effect.js +57 -0
- package/dist/remote-node/index.d.ts +2 -0
- package/dist/remote-node/index.js +2 -0
- package/dist/remote-node/remote-node.d.ts +12 -0
- package/dist/remote-node/remote-node.js +5 -0
- package/dist/request/classify.d.ts +15 -0
- package/dist/request/classify.js +96 -0
- package/dist/request/client-error.d.ts +10 -0
- package/dist/request/client-error.js +11 -0
- package/dist/request/effect.d.ts +22 -0
- package/dist/request/effect.js +104 -0
- package/dist/request/envelope-schema.d.ts +2 -0
- package/dist/request/envelope-schema.js +2 -0
- package/dist/request/index.d.ts +5 -0
- package/dist/request/index.js +4 -0
- package/dist/request/maia-client.d.ts +21 -0
- package/dist/request/maia-client.js +9 -0
- package/dist/request/options.d.ts +9 -0
- package/dist/request/options.js +2 -0
- package/dist/run-ledger/effect.d.ts +5 -0
- package/dist/run-ledger/effect.js +72 -0
- package/dist/run-ledger/index.d.ts +2 -0
- package/dist/run-ledger/index.js +2 -0
- package/dist/run-ledger/run-ledger.d.ts +12 -0
- package/dist/run-ledger/run-ledger.js +2 -0
- package/dist/session/effect.d.ts +4 -0
- package/dist/session/effect.js +7 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/mailbox.d.ts +9 -0
- package/dist/session/mailbox.js +4 -0
- package/dist/session-creation/effect.d.ts +25 -0
- package/dist/session-creation/effect.js +269 -0
- package/dist/session-creation/index.d.ts +3 -0
- package/dist/session-creation/index.js +3 -0
- package/dist/session-creation/session-creation.d.ts +18 -0
- package/dist/session-creation/session-creation.js +24 -0
- package/dist/session-creation/tasks.d.ts +69 -0
- package/dist/session-creation/tasks.js +37 -0
- package/dist/session-view/index.d.ts +2 -0
- package/dist/session-view/index.js +2 -0
- package/dist/session-view/view.d.ts +42 -0
- package/dist/session-view/view.js +137 -0
- package/dist/stream/effect.d.ts +29 -0
- package/dist/stream/effect.js +77 -0
- package/dist/stream/index.d.ts +2 -0
- package/dist/stream/index.js +2 -0
- package/dist/stream/stream.d.ts +9 -0
- package/dist/stream/stream.js +2 -0
- package/dist/voice/effect.d.ts +7 -0
- package/dist/voice/effect.js +71 -0
- package/dist/voice/index.d.ts +4 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/managed-turn.d.ts +9 -0
- package/dist/voice/managed-turn.js +4 -0
- package/dist/voice/orchestrator-contract.d.ts +5 -0
- package/dist/voice/orchestrator-contract.js +4 -0
- package/dist/voice/orchestrator.d.ts +25 -0
- package/dist/voice/orchestrator.js +169 -0
- package/dist/voice/pi-relay-contract.d.ts +4 -0
- package/dist/voice/pi-relay-contract.js +2 -0
- package/dist/voice/pi-relay.d.ts +12 -0
- package/dist/voice/pi-relay.js +9 -0
- package/dist/voice/wire-schema.d.ts +215 -0
- package/dist/voice/wire-schema.js +16 -0
- package/package.json +84 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { close as closeFileDescriptor, constants, fstat as inspectFileDescriptor, read as readFileDescriptor, } from "node:fs";
|
|
2
|
+
import { lstat, open } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { LOCAL_NODE_TOKEN_LENGTH, localNodeTokenSchema, MAX_NODE_API_CONNECTION_DESCRIPTOR_BYTES, nodeApiConnectionDescriptorSchema, } from "@maiaos/protocol";
|
|
5
|
+
import { INVOCATION_ATTACHMENT_CREDENTIAL_BYTES } from "@maiaos/protocol/invocation-context-candidate";
|
|
6
|
+
import { resolveCurrentLocalNodeProfileLocation, } from "@maiaos/protocol/node-profile";
|
|
7
|
+
import * as Crypto from "effect/Crypto";
|
|
8
|
+
import * as Data from "effect/Data";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Schema from "effect/Schema";
|
|
11
|
+
import { MaiaClientError } from "../request/index.js";
|
|
12
|
+
import { createNodeApiEffectClient } from "./http.js";
|
|
13
|
+
const INVOCATION_ATTACHMENT_DESCRIPTOR = 3;
|
|
14
|
+
const INVOCATION_ATTACHMENT_READ_DEADLINE = "1 second";
|
|
15
|
+
const MAX_CREDENTIAL_FILE_BYTES = LOCAL_NODE_TOKEN_LENGTH + 1;
|
|
16
|
+
const NEWLINE_BYTE = 10;
|
|
17
|
+
const PRIVATE_MODE_MASK = 0o077;
|
|
18
|
+
const REQUEST_DEADLINE_MS = 5000;
|
|
19
|
+
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
20
|
+
const decodeJson = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
|
|
21
|
+
class LocalNodeTransportError extends Data.TaggedError("LocalNodeTransportError") {
|
|
22
|
+
}
|
|
23
|
+
const makeTransportError = (reason, message) => new LocalNodeTransportError({ message, reason });
|
|
24
|
+
const failTransport = (reason, message) => Effect.fail(makeTransportError(reason, message));
|
|
25
|
+
const ownedByCurrentUser = (metadata) => typeof process.getuid !== "function" ||
|
|
26
|
+
metadata.uid === BigInt(process.getuid());
|
|
27
|
+
const hasPrivateMode = (metadata) => process.platform === "win32" ||
|
|
28
|
+
(Number(metadata.mode) & PRIVATE_MODE_MASK) === 0;
|
|
29
|
+
const validatePrivateDirectoryMetadata = Effect.fn("LocalNodeTransport.validatePrivateDirectoryMetadata")(function* (metadata) {
|
|
30
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
31
|
+
return yield* Effect.fail(makeTransportError("endpoint_invalid", "A Maia local transport directory is unsafe."));
|
|
32
|
+
}
|
|
33
|
+
if (!ownedByCurrentUser(metadata)) {
|
|
34
|
+
return yield* Effect.fail(makeTransportError("endpoint_invalid", "A Maia local transport directory has an invalid owner."));
|
|
35
|
+
}
|
|
36
|
+
if (!hasPrivateMode(metadata)) {
|
|
37
|
+
return yield* Effect.fail(makeTransportError("endpoint_invalid", "A Maia local transport directory is not private."));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
const validatePrivateDirectory = Effect.fn("LocalNodeTransport.validatePrivateDirectory")(function* (path) {
|
|
41
|
+
const metadata = yield* Effect.tryPromise({
|
|
42
|
+
catch: () => makeTransportError("endpoint_invalid", "A Maia local transport directory is unavailable."),
|
|
43
|
+
try: () => lstat(path, { bigint: true }),
|
|
44
|
+
});
|
|
45
|
+
yield* validatePrivateDirectoryMetadata(metadata);
|
|
46
|
+
});
|
|
47
|
+
const sameFileSnapshot = (left, right) => left.dev === right.dev &&
|
|
48
|
+
left.ino === right.ino &&
|
|
49
|
+
left.size === right.size &&
|
|
50
|
+
left.ctimeNs === right.ctimeNs &&
|
|
51
|
+
left.mtimeNs === right.mtimeNs;
|
|
52
|
+
const validateCredentialMetadata = Effect.fn("LocalNodeTransport.validateCredentialMetadata")(function* (metadata) {
|
|
53
|
+
if (!metadata.isFile() ||
|
|
54
|
+
metadata.isSymbolicLink() ||
|
|
55
|
+
metadata.nlink !== 1n ||
|
|
56
|
+
metadata.size > BigInt(MAX_CREDENTIAL_FILE_BYTES)) {
|
|
57
|
+
return yield* failTransport("credential_invalid", "The Maia local credential is unsafe.");
|
|
58
|
+
}
|
|
59
|
+
if (!ownedByCurrentUser(metadata)) {
|
|
60
|
+
return yield* failTransport("credential_invalid", "The Maia local credential has an invalid owner.");
|
|
61
|
+
}
|
|
62
|
+
if (!hasPrivateMode(metadata)) {
|
|
63
|
+
return yield* failTransport("credential_invalid", "The Maia local credential is not private.");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const readBoundedCredential = Effect.fn("LocalNodeTransport.readBoundedCredential")(function* (handle) {
|
|
67
|
+
const bytes = Buffer.alloc(MAX_CREDENTIAL_FILE_BYTES + 1);
|
|
68
|
+
let offset = 0;
|
|
69
|
+
let complete = false;
|
|
70
|
+
yield* Effect.whileLoop({
|
|
71
|
+
body: () => Effect.tryPromise({
|
|
72
|
+
catch: () => makeTransportError("credential_invalid", "The Maia local credential could not be read."),
|
|
73
|
+
try: () => handle.read(bytes, offset, bytes.byteLength - offset, offset),
|
|
74
|
+
}),
|
|
75
|
+
step: (result) => {
|
|
76
|
+
complete = result.bytesRead === 0;
|
|
77
|
+
offset += result.bytesRead;
|
|
78
|
+
},
|
|
79
|
+
while: () => !complete && offset < bytes.byteLength,
|
|
80
|
+
});
|
|
81
|
+
return bytes.subarray(0, offset);
|
|
82
|
+
});
|
|
83
|
+
const decodeCredential = Effect.fn("LocalNodeTransport.decodeCredential")(function* (bytes) {
|
|
84
|
+
return yield* Effect.try({
|
|
85
|
+
catch: () => makeTransportError("credential_invalid", "The Maia local credential is invalid."),
|
|
86
|
+
try: () => {
|
|
87
|
+
if (bytes.byteLength !== MAX_CREDENTIAL_FILE_BYTES ||
|
|
88
|
+
bytes[MAX_CREDENTIAL_FILE_BYTES - 1] !== NEWLINE_BYTE) {
|
|
89
|
+
throw new Error("Invalid local credential framing.");
|
|
90
|
+
}
|
|
91
|
+
const token = utf8Decoder.decode(bytes.subarray(0, LOCAL_NODE_TOKEN_LENGTH));
|
|
92
|
+
return localNodeTokenSchema.parse(token);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
const statCredentialHandle = Effect.fn("LocalNodeTransport.statCredentialHandle")(function* (handle) {
|
|
97
|
+
return yield* Effect.tryPromise({
|
|
98
|
+
catch: () => makeTransportError("credential_invalid", "The Maia local credential metadata is unavailable."),
|
|
99
|
+
try: () => handle.stat({ bigint: true }),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
const statCredentialPath = Effect.fn("LocalNodeTransport.statCredentialPath")(function* (credentialPath) {
|
|
103
|
+
return yield* Effect.tryPromise({
|
|
104
|
+
catch: () => makeTransportError("credential_invalid", "The Maia local credential changed while being read."),
|
|
105
|
+
try: () => lstat(credentialPath, { bigint: true }),
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
const validateCredentialSnapshot = Effect.fn("LocalNodeTransport.validateCredentialSnapshot")(function* (input) {
|
|
109
|
+
const after = yield* statCredentialHandle(input.handle);
|
|
110
|
+
yield* validateCredentialMetadata(after);
|
|
111
|
+
const current = yield* statCredentialPath(input.credentialPath);
|
|
112
|
+
yield* validateCredentialMetadata(current);
|
|
113
|
+
if (!sameFileSnapshot(input.before, after) ||
|
|
114
|
+
!sameFileSnapshot(after, current)) {
|
|
115
|
+
return yield* Effect.fail(makeTransportError("credential_invalid", "The Maia local credential changed while being read."));
|
|
116
|
+
}
|
|
117
|
+
return yield* decodeCredential(input.bytes);
|
|
118
|
+
});
|
|
119
|
+
const readCredentialHandle = Effect.fn("LocalNodeTransport.readCredentialHandle")(function* (credentialPath, handle) {
|
|
120
|
+
const before = yield* statCredentialHandle(handle);
|
|
121
|
+
yield* validateCredentialMetadata(before);
|
|
122
|
+
const bytes = yield* readBoundedCredential(handle);
|
|
123
|
+
return yield* validateCredentialSnapshot({
|
|
124
|
+
before,
|
|
125
|
+
bytes,
|
|
126
|
+
credentialPath,
|
|
127
|
+
handle,
|
|
128
|
+
}).pipe(Effect.ensuring(Effect.sync(() => bytes.fill(0))));
|
|
129
|
+
});
|
|
130
|
+
const openCredential = Effect.fn("LocalNodeTransport.openCredential")(function* (credentialPath) {
|
|
131
|
+
const flags = constants.O_RDONLY |
|
|
132
|
+
(process.platform === "win32"
|
|
133
|
+
? 0
|
|
134
|
+
: constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
135
|
+
return yield* Effect.tryPromise({
|
|
136
|
+
catch: () => makeTransportError("credential_invalid", "The Maia local credential is unavailable."),
|
|
137
|
+
try: () => open(credentialPath, flags),
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
const closeCredential = Effect.fn("LocalNodeTransport.closeCredential")(function* (handle) {
|
|
141
|
+
yield* Effect.tryPromise({
|
|
142
|
+
catch: () => makeTransportError("credential_invalid", "The Maia local credential could not be closed."),
|
|
143
|
+
try: () => handle.close(),
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
const readCredential = Effect.fn("LocalNodeTransport.readCredential")(function* (credentialPath) {
|
|
147
|
+
yield* validatePrivateDirectory(dirname(credentialPath)).pipe(Effect.mapError(() => makeTransportError("credential_invalid", "The Maia local credential directory is unsafe.")));
|
|
148
|
+
return yield* Effect.acquireUseRelease(openCredential(credentialPath), (handle) => readCredentialHandle(credentialPath, handle), closeCredential);
|
|
149
|
+
});
|
|
150
|
+
const encodeInvocationCredential = (credential) => {
|
|
151
|
+
const copy = Buffer.from(credential);
|
|
152
|
+
try {
|
|
153
|
+
return copy.toString("base64url");
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
copy.fill(0);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const inheritedAttachmentError = () => makeTransportError("credential_invalid", "The inherited Invocation attachment is invalid or unavailable.");
|
|
160
|
+
const inspectInheritedAttachment = () => Effect.callback((resume) => {
|
|
161
|
+
inspectFileDescriptor(INVOCATION_ATTACHMENT_DESCRIPTOR, (error, metadata) => {
|
|
162
|
+
resume(error === null
|
|
163
|
+
? Effect.succeed(metadata)
|
|
164
|
+
: Effect.fail(inheritedAttachmentError()));
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
const readInheritedChunk = Effect.fn("LocalNodeAttachment.readChunk")(function* (buffer, offset, length) {
|
|
168
|
+
return yield* Effect.callback((resume) => {
|
|
169
|
+
readFileDescriptor(INVOCATION_ATTACHMENT_DESCRIPTOR, { buffer, length, offset }, (error, bytesRead) => {
|
|
170
|
+
resume(error === null
|
|
171
|
+
? Effect.succeed(bytesRead)
|
|
172
|
+
: Effect.fail(inheritedAttachmentError()));
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
const fillInheritedCredential = (buffer, offset = 0) => offset === buffer.byteLength
|
|
177
|
+
? Effect.void
|
|
178
|
+
: readInheritedChunk(buffer, offset, buffer.byteLength - offset).pipe(Effect.flatMap((bytesRead) => bytesRead === 0
|
|
179
|
+
? Effect.fail(inheritedAttachmentError())
|
|
180
|
+
: fillInheritedCredential(buffer, offset + bytesRead)));
|
|
181
|
+
const closeInheritedAttachment = () => Effect.callback((resume) => {
|
|
182
|
+
closeFileDescriptor(INVOCATION_ATTACHMENT_DESCRIPTOR, () => {
|
|
183
|
+
resume(Effect.void);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
const readInheritedAttachmentCredential = Effect.fn("LocalNodeAttachment.readCredential")(function* () {
|
|
187
|
+
const metadata = yield* inspectInheritedAttachment();
|
|
188
|
+
if (!metadata.isFIFO())
|
|
189
|
+
return yield* Effect.fail(inheritedAttachmentError());
|
|
190
|
+
const credential = Buffer.alloc(INVOCATION_ATTACHMENT_CREDENTIAL_BYTES);
|
|
191
|
+
const extra = Buffer.alloc(1);
|
|
192
|
+
return yield* Effect.gen(function* () {
|
|
193
|
+
yield* fillInheritedCredential(credential);
|
|
194
|
+
const extraBytes = yield* readInheritedChunk(extra, 0, 1);
|
|
195
|
+
if (extraBytes !== 0)
|
|
196
|
+
return yield* Effect.fail(inheritedAttachmentError());
|
|
197
|
+
return Uint8Array.from(credential);
|
|
198
|
+
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
199
|
+
credential.fill(0);
|
|
200
|
+
extra.fill(0);
|
|
201
|
+
})));
|
|
202
|
+
});
|
|
203
|
+
const acquireInheritedAttachmentCredential = () => Effect.acquireUseRelease(Effect.void, () => readInheritedAttachmentCredential().pipe(Effect.timeoutOrElse({
|
|
204
|
+
duration: INVOCATION_ATTACHMENT_READ_DEADLINE,
|
|
205
|
+
orElse: () => Effect.fail(inheritedAttachmentError()),
|
|
206
|
+
})), () => closeInheritedAttachment());
|
|
207
|
+
const validateDescriptorMetadata = Effect.fn("LocalNodeDiscovery.validateDescriptorMetadata")(function* (metadata) {
|
|
208
|
+
if (!metadata.isFile() ||
|
|
209
|
+
metadata.isSymbolicLink() ||
|
|
210
|
+
metadata.nlink !== 1n ||
|
|
211
|
+
metadata.size <= 0n ||
|
|
212
|
+
metadata.size > BigInt(MAX_NODE_API_CONNECTION_DESCRIPTOR_BYTES)) {
|
|
213
|
+
return yield* failTransport("discovery_invalid", "The Maia local connection descriptor is unsafe.");
|
|
214
|
+
}
|
|
215
|
+
if (!ownedByCurrentUser(metadata) || !hasPrivateMode(metadata)) {
|
|
216
|
+
return yield* failTransport("discovery_invalid", "The Maia local connection descriptor is not private.");
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
const openDescriptor = Effect.fn("LocalNodeDiscovery.openDescriptor")(function* (path) {
|
|
220
|
+
const flags = constants.O_RDONLY |
|
|
221
|
+
(process.platform === "win32"
|
|
222
|
+
? 0
|
|
223
|
+
: constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
224
|
+
return yield* Effect.tryPromise({
|
|
225
|
+
catch: () => makeTransportError("discovery_invalid", "The Maia local connection descriptor is unavailable."),
|
|
226
|
+
try: () => open(path, flags),
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
const closeDescriptor = Effect.fn("LocalNodeDiscovery.closeDescriptor")(function* (handle) {
|
|
230
|
+
yield* Effect.tryPromise({
|
|
231
|
+
catch: () => makeTransportError("discovery_invalid", "The Maia local connection descriptor could not be closed."),
|
|
232
|
+
try: () => handle.close(),
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
const readDescriptorChunk = (handle, bytes, offset) => Effect.tryPromise({
|
|
236
|
+
catch: () => makeTransportError("discovery_invalid", "The Maia local connection descriptor could not be read."),
|
|
237
|
+
try: () => handle.read(bytes, offset, bytes.byteLength - offset, offset),
|
|
238
|
+
});
|
|
239
|
+
const readDescriptorBytes = Effect.fn("LocalNodeDiscovery.readDescriptorBytes")(function* (handle) {
|
|
240
|
+
const bytes = Buffer.alloc(MAX_NODE_API_CONNECTION_DESCRIPTOR_BYTES + 1);
|
|
241
|
+
let offset = 0;
|
|
242
|
+
let complete = false;
|
|
243
|
+
yield* Effect.whileLoop({
|
|
244
|
+
body: () => readDescriptorChunk(handle, bytes, offset),
|
|
245
|
+
step: (result) => {
|
|
246
|
+
complete = result.bytesRead === 0;
|
|
247
|
+
offset += result.bytesRead;
|
|
248
|
+
},
|
|
249
|
+
while: () => !complete && offset < bytes.byteLength,
|
|
250
|
+
});
|
|
251
|
+
if (offset === 0 || offset > MAX_NODE_API_CONNECTION_DESCRIPTOR_BYTES) {
|
|
252
|
+
bytes.fill(0);
|
|
253
|
+
return yield* failTransport("discovery_invalid", "The Maia local connection descriptor exceeds its resource limit.");
|
|
254
|
+
}
|
|
255
|
+
return bytes.subarray(0, offset);
|
|
256
|
+
});
|
|
257
|
+
const statDescriptorHandle = Effect.fn("LocalNodeDiscovery.statDescriptorHandle")(function* (handle) {
|
|
258
|
+
return yield* Effect.tryPromise({
|
|
259
|
+
catch: () => makeTransportError("discovery_invalid", "The Maia local connection descriptor metadata is unavailable."),
|
|
260
|
+
try: () => handle.stat({ bigint: true }),
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
const statDescriptorPath = Effect.fn("LocalNodeDiscovery.statDescriptorPath")(function* (path) {
|
|
264
|
+
return yield* Effect.tryPromise({
|
|
265
|
+
catch: () => makeTransportError("discovery_invalid", "The Maia local connection descriptor changed while being read."),
|
|
266
|
+
try: () => lstat(path, { bigint: true }),
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
const resolveDiscoveryLocation = Effect.fn("LocalNodeDiscovery.resolveLocation")(function* (options) {
|
|
270
|
+
return yield* Effect.try({
|
|
271
|
+
catch: () => makeTransportError("discovery_invalid", "The Maia local profile could not be resolved."),
|
|
272
|
+
try: () => resolveCurrentLocalNodeProfileLocation({
|
|
273
|
+
profile: options.profile ?? "default",
|
|
274
|
+
...(options.runtimeRootOverride === undefined
|
|
275
|
+
? {}
|
|
276
|
+
: { runtimeRootOverride: options.runtimeRootOverride }),
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
const transportErrorFromStatusFailure = (error) => {
|
|
281
|
+
if (error instanceof MaiaClientError &&
|
|
282
|
+
(error.code === "deadline_exceeded" ||
|
|
283
|
+
error.code === "outcome_unknown" ||
|
|
284
|
+
error.code === "saturated")) {
|
|
285
|
+
return makeTransportError(error.code, error.message);
|
|
286
|
+
}
|
|
287
|
+
return makeTransportError("transport_failed", "The discovered Maia local node is unavailable.");
|
|
288
|
+
};
|
|
289
|
+
const validateDiscoveredIdentity = Effect.fn("LocalNodeDiscovery.validateIdentity")(function* (descriptor, status) {
|
|
290
|
+
if (status.instanceId !== descriptor.instanceId ||
|
|
291
|
+
status.lifecycle !== "ready" ||
|
|
292
|
+
status.pid !== descriptor.pid ||
|
|
293
|
+
status.profile !== descriptor.profile ||
|
|
294
|
+
status.startedAt !== descriptor.startedAt) {
|
|
295
|
+
return yield* failTransport("discovery_invalid", "The discovered Maia local node identity did not match.");
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
const withDiscoveryDeadline = (effect) => effect.pipe(Effect.timeoutOrElse({
|
|
299
|
+
duration: REQUEST_DEADLINE_MS,
|
|
300
|
+
orElse: () => Effect.fail(makeTransportError("deadline_exceeded", "The Maia local discovery deadline expired.")),
|
|
301
|
+
}));
|
|
302
|
+
const classifiedDiscoveryError = (error) => error instanceof LocalNodeTransportError &&
|
|
303
|
+
(error.reason === "deadline_exceeded" ||
|
|
304
|
+
error.reason === "outcome_unknown" ||
|
|
305
|
+
error.reason === "saturated")
|
|
306
|
+
? new MaiaClientError(error.reason, error.message, { cause: error })
|
|
307
|
+
: undefined;
|
|
308
|
+
const invalidDescriptor = () => makeTransportError("discovery_invalid", "The Maia Node API connection descriptor is invalid.");
|
|
309
|
+
const decodeNodeApiDescriptor = Effect.fn("NodeApiDiscovery.decodeDescriptor")(function* (bytes) {
|
|
310
|
+
return yield* Effect.try({
|
|
311
|
+
catch: invalidDescriptor,
|
|
312
|
+
try: () => utf8Decoder.decode(bytes),
|
|
313
|
+
}).pipe(Effect.flatMap((text) => Effect.mapError(decodeJson(text), invalidDescriptor)), Effect.flatMap((value) => Effect.try({
|
|
314
|
+
catch: invalidDescriptor,
|
|
315
|
+
try: () => nodeApiConnectionDescriptorSchema.parse(value),
|
|
316
|
+
})), Effect.ensuring(Effect.sync(() => bytes.fill(0))));
|
|
317
|
+
});
|
|
318
|
+
const readNodeApiDescriptorHandle = Effect.fn("NodeApiDiscovery.readDescriptorHandle")(function* (path, handle) {
|
|
319
|
+
const before = yield* statDescriptorHandle(handle);
|
|
320
|
+
yield* validateDescriptorMetadata(before);
|
|
321
|
+
if (before.size > BigInt(MAX_NODE_API_CONNECTION_DESCRIPTOR_BYTES)) {
|
|
322
|
+
return yield* failTransport("discovery_invalid", "The Maia Node API connection descriptor exceeds its resource limit.");
|
|
323
|
+
}
|
|
324
|
+
const bytes = yield* readDescriptorBytes(handle);
|
|
325
|
+
const after = yield* statDescriptorHandle(handle);
|
|
326
|
+
yield* validateDescriptorMetadata(after);
|
|
327
|
+
const current = yield* statDescriptorPath(path);
|
|
328
|
+
yield* validateDescriptorMetadata(current);
|
|
329
|
+
if (!sameFileSnapshot(before, after) || !sameFileSnapshot(after, current)) {
|
|
330
|
+
bytes.fill(0);
|
|
331
|
+
return yield* failTransport("discovery_invalid", "The Maia Node API connection descriptor changed while being read.");
|
|
332
|
+
}
|
|
333
|
+
return yield* decodeNodeApiDescriptor(bytes);
|
|
334
|
+
});
|
|
335
|
+
const readNodeApiDescriptor = Effect.fn("NodeApiDiscovery.readDescriptor")(function* (path) {
|
|
336
|
+
yield* validatePrivateDirectory(dirname(path)).pipe(Effect.mapError(() => makeTransportError("discovery_invalid", "The Maia Node API connection directory is unsafe.")));
|
|
337
|
+
const beforeOpen = yield* statDescriptorPath(path);
|
|
338
|
+
yield* validateDescriptorMetadata(beforeOpen);
|
|
339
|
+
return yield* Effect.acquireUseRelease(openDescriptor(path), (handle) => readNodeApiDescriptorHandle(path, handle), closeDescriptor);
|
|
340
|
+
});
|
|
341
|
+
const validateDiscoveredNodeApiDescriptor = Effect.fn("NodeApiDiscovery.validateDescriptor")(function* (descriptor, expected) {
|
|
342
|
+
if (descriptor.profile !== expected.profile ||
|
|
343
|
+
descriptor.credentialPath !== expected.credentialPath) {
|
|
344
|
+
return yield* failTransport("discovery_invalid", "The Maia Node API connection descriptor does not match its profile.");
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const readDiscoveredNodeApi = Effect.fn("NodeApiDiscovery.read")(function* (options) {
|
|
348
|
+
const location = yield* resolveDiscoveryLocation(options);
|
|
349
|
+
const descriptor = yield* readNodeApiDescriptor(location.connectionDescriptorPath);
|
|
350
|
+
yield* validateDiscoveredNodeApiDescriptor(descriptor, location);
|
|
351
|
+
const token = yield* readCredential(descriptor.credentialPath);
|
|
352
|
+
return { descriptor, token };
|
|
353
|
+
});
|
|
354
|
+
export const newMessageId = (prefix) => Effect.gen(function* () {
|
|
355
|
+
const crypto = yield* Crypto.Crypto;
|
|
356
|
+
const uuid = yield* Effect.orDie(crypto.randomUUIDv4);
|
|
357
|
+
return `${prefix}_${uuid.replaceAll("-", "")}`;
|
|
358
|
+
});
|
|
359
|
+
export const connectDiscoveredNodeApi = (options, open) => withDiscoveryDeadline(Effect.gen(function* () {
|
|
360
|
+
const { descriptor, token } = yield* readDiscoveredNodeApi(options);
|
|
361
|
+
const opened = open(descriptor, token);
|
|
362
|
+
const messageId = yield* newMessageId("discovery");
|
|
363
|
+
const status = yield* opened
|
|
364
|
+
.status(messageId)
|
|
365
|
+
.pipe(Effect.mapError(transportErrorFromStatusFailure));
|
|
366
|
+
yield* validateDiscoveredIdentity(descriptor, status);
|
|
367
|
+
return { client: opened.client, descriptor, status };
|
|
368
|
+
}));
|
|
369
|
+
export const discoveryFailure = (error) => classifiedDiscoveryError(error) ??
|
|
370
|
+
new MaiaClientError("transport_failure", "The Maia Node API could not be discovered.", { cause: error });
|
|
371
|
+
export const connectNodeApiEffect = (options = {}) => connectDiscoveredNodeApi(options, (descriptor, token) => {
|
|
372
|
+
const client = createNodeApiEffectClient({
|
|
373
|
+
credential: token,
|
|
374
|
+
endpoint: descriptor.endpoint,
|
|
375
|
+
...(options.principal === undefined
|
|
376
|
+
? {}
|
|
377
|
+
: { principal: options.principal }),
|
|
378
|
+
});
|
|
379
|
+
return { client, status: (messageId) => client.status({ messageId }) };
|
|
380
|
+
}).pipe(Effect.mapError(discoveryFailure));
|
|
381
|
+
export const acquireAttachedCredential = () => acquireInheritedAttachmentCredential();
|
|
382
|
+
const attachedCredentialFailure = (error) => new MaiaClientError("transport_failure", "The inherited Invocation attachment is invalid or unavailable.", { cause: error });
|
|
383
|
+
export const attachedTransportOptions = (descriptor, credential, readToken) => ({
|
|
384
|
+
credential: readToken(readCredential(descriptor.credentialPath)),
|
|
385
|
+
endpoint: descriptor.endpoint,
|
|
386
|
+
invocationAuthentication: {
|
|
387
|
+
credential: encodeInvocationCredential(credential),
|
|
388
|
+
scheme: "maia.invocation-attachment/v1",
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
export const requireAttachmentPlatform = () => process.platform === "darwin"
|
|
392
|
+
? Effect.void
|
|
393
|
+
: Effect.fail(new MaiaClientError("transport_failure", "Inherited Invocation attachments are unsupported on this platform."));
|
|
394
|
+
const attachedMessageId = (prefix, options) => options.messageId === undefined
|
|
395
|
+
? newMessageId(prefix)
|
|
396
|
+
: Effect.succeed(options.messageId);
|
|
397
|
+
const withAttachedLocalNodeEffect = (options, operation) => Effect.gen(function* () {
|
|
398
|
+
yield* requireAttachmentPlatform();
|
|
399
|
+
const connection = yield* connectNodeApiEffect(options);
|
|
400
|
+
const credential = yield* acquireAttachedCredential().pipe(Effect.mapError(attachedCredentialFailure));
|
|
401
|
+
const client = createNodeApiEffectClient(attachedTransportOptions(connection.descriptor, credential, (read) => read));
|
|
402
|
+
return yield* operation(client).pipe(Effect.ensuring(Effect.sync(() => credential.fill(0))));
|
|
403
|
+
});
|
|
404
|
+
export const showAttachedCurrentContextEffect = (options = {}) => withAttachedLocalNodeEffect(options, (client) => attachedMessageId("context_attached", options).pipe(Effect.flatMap((messageId) => client.showCurrentContext({}, { messageId }))));
|
|
405
|
+
export const sendAttachedSessionMessageEffect = (parameters, options = {}) => withAttachedLocalNodeEffect(options, (client) => attachedMessageId("session_message", options).pipe(Effect.flatMap((messageId) => client.sendSessionMessage(parameters, { messageId }))));
|
|
406
|
+
export const checkAttachedSessionInboxEffect = (parameters, options = {}) => withAttachedLocalNodeEffect(options, (client) => attachedMessageId("session_inbox_check", options).pipe(Effect.flatMap((messageId) => client.checkSessionInbox(parameters, { messageId }))));
|
|
407
|
+
export const acknowledgeAttachedSessionDeliveryEffect = (parameters, options = {}) => withAttachedLocalNodeEffect(options, (client) => attachedMessageId("session_inbox_ack", options).pipe(Effect.flatMap((messageId) => client.acknowledgeSessionDelivery(parameters, { messageId }))));
|
|
408
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createLocalNodeEffectClient, type LocalNodeEffectClient, } from "../client/effect.js";
|
|
2
|
+
export { awaitSessionRunningEffect, createSessionsEffect, planSessionCreationEffect, type SessionCreationEffectClient, } from "../session-creation/effect.js";
|
|
3
|
+
export { attachSessionEffect, type SessionAttachEffectOptions, } from "./attach.js";
|
|
4
|
+
export { BridgeError, type BridgeHello, type NodeBridge, openNodeBridge, } from "./bridge.js";
|
|
5
|
+
export { decodeRequestFrame, decodeServerFrame, encodeFrame, type FrameLine, splitFrameLines, } from "./bridge-frame.js";
|
|
6
|
+
export { acknowledgeAttachedSessionDeliveryEffect, type AttachedLocalNodeEffectOptions, checkAttachedSessionInboxEffect, connectNodeApiEffect, type DiscoveredNodeApiEffectConnection, type NodeApiEffectDiscoveryOptions, sendAttachedSessionMessageEffect, showAttachedCurrentContextEffect, } from "./discovery.js";
|
|
7
|
+
export { connectSessionNodeApiEffect, type SessionNodeApiEffectConnection, type SessionNodeApiEffectOptions, } from "./session.js";
|
|
8
|
+
export { bootstrapArguments, bridgeScript, encodedScriptBytes, isPlainAbsolutePath, PATH_SEGMENT_PATTERN, REMOTE_BOOTSTRAP, remoteCommandWords, shellQuote, SSH_OPTIONS, sshCommandArguments, } from "./ssh.js";
|
|
9
|
+
//# sourceMappingURL=effect.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createLocalNodeEffectClient, } from "../client/effect.js";
|
|
2
|
+
export { awaitSessionRunningEffect, createSessionsEffect, planSessionCreationEffect, } from "../session-creation/effect.js";
|
|
3
|
+
export { attachSessionEffect, } from "./attach.js";
|
|
4
|
+
export { BridgeError, openNodeBridge, } from "./bridge.js";
|
|
5
|
+
export { decodeRequestFrame, decodeServerFrame, encodeFrame, splitFrameLines, } from "./bridge-frame.js";
|
|
6
|
+
export { acknowledgeAttachedSessionDeliveryEffect, checkAttachedSessionInboxEffect, connectNodeApiEffect, sendAttachedSessionMessageEffect, showAttachedCurrentContextEffect, } from "./discovery.js";
|
|
7
|
+
export { connectSessionNodeApiEffect, } from "./session.js";
|
|
8
|
+
export { bootstrapArguments, bridgeScript, encodedScriptBytes, isPlainAbsolutePath, PATH_SEGMENT_PATTERN, REMOTE_BOOTSTRAP, remoteCommandWords, shellQuote, SSH_OPTIONS, sshCommandArguments, } from "./ssh.js";
|
|
9
|
+
//# sourceMappingURL=effect.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ProtocolRequestEnvelope } from "@maiaos/protocol";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import { type LocalNodeEffectClient } from "../client/effect.js";
|
|
4
|
+
import { type MaiaEffectTransport } from "../request/effect.js";
|
|
5
|
+
import { MaiaClientError, type TransportRequestOptions } from "../request/index.js";
|
|
6
|
+
import type { NodeApiCredential, NodeApiTransportOptions } from "./transport.js";
|
|
7
|
+
export type NodeApiEffectCredential = Effect.Effect<string, unknown> | NodeApiCredential;
|
|
8
|
+
export type NodeApiEffectTransportOptions = Omit<NodeApiTransportOptions, "credential"> & {
|
|
9
|
+
readonly credential: NodeApiEffectCredential;
|
|
10
|
+
};
|
|
11
|
+
type NodeApiTransportErrorReason = "cancelled" | "deadline_exceeded" | "invalid_response" | "outcome_unknown" | "saturated" | "transport_failure";
|
|
12
|
+
declare const NodeApiTransportError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
13
|
+
readonly _tag: "NodeApiTransportError";
|
|
14
|
+
} & Readonly<A>;
|
|
15
|
+
declare class NodeApiTransportError extends NodeApiTransportError_base<{
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly reason: NodeApiTransportErrorReason;
|
|
18
|
+
}> {
|
|
19
|
+
}
|
|
20
|
+
export declare const toClientError: (error: unknown, signal?: AbortSignal) => MaiaClientError;
|
|
21
|
+
export declare const resolveCredential: (credential: NodeApiEffectCredential) => Effect.Effect<string, NodeApiTransportError, never>;
|
|
22
|
+
export type NodeApiHttpRequest = (envelope: ProtocolRequestEnvelope<unknown>, requestOptions?: TransportRequestOptions) => Effect.Effect<unknown, unknown>;
|
|
23
|
+
export declare const makeNodeApiHttpRequest: (options: NodeApiEffectTransportOptions) => NodeApiHttpRequest;
|
|
24
|
+
export declare const makeNodeApiEffectTransport: (options: NodeApiEffectTransportOptions) => MaiaEffectTransport;
|
|
25
|
+
export declare const createNodeApiEffectClient: (options: NodeApiEffectTransportOptions) => LocalNodeEffectClient;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=http.d.ts.map
|