@fungi.computer/agar 0.0.0-stage → 0.0.1
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 +21 -0
- package/README.md +766 -2
- package/dist/agent-message-meta.d.ts +74 -0
- package/dist/agent-message-meta.js +63 -0
- package/dist/agents.d.ts +170 -0
- package/dist/agents.js +218 -0
- package/dist/app-build-contract.d.ts +43 -0
- package/dist/app-build-contract.js +24 -0
- package/dist/apps.d.ts +66 -0
- package/dist/apps.js +47 -0
- package/dist/backend-declaration.d.ts +6 -0
- package/dist/backend-declaration.js +65 -0
- package/dist/backend-definition.d.ts +88 -0
- package/dist/backend-definition.js +158 -0
- package/dist/backend-refusal.d.ts +10 -0
- package/dist/backend-refusal.js +10 -0
- package/dist/backend.d.ts +30 -0
- package/dist/backend.js +28 -0
- package/dist/capabilities.d.ts +30 -0
- package/dist/capabilities.js +50 -0
- package/dist/channels.d.ts +142 -0
- package/dist/channels.js +97 -0
- package/dist/commands.d.ts +33 -0
- package/dist/commands.js +46 -0
- package/dist/computer-contracts.d.ts +73 -0
- package/dist/computer-contracts.js +57 -0
- package/dist/computer.d.ts +294 -0
- package/dist/computer.js +319 -0
- package/dist/connect.d.ts +11 -0
- package/dist/connect.js +211 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.js +106 -0
- package/dist/failure.d.ts +2 -0
- package/dist/failure.js +9 -0
- package/dist/files.d.ts +25 -0
- package/dist/files.js +84 -0
- package/dist/game-servers.d.ts +530 -0
- package/dist/game-servers.js +244 -0
- package/dist/gills.d.ts +43 -0
- package/dist/gills.js +174 -0
- package/dist/git-commit.d.ts +143 -0
- package/dist/git-commit.js +251 -0
- package/dist/git-rpc.d.ts +310 -0
- package/dist/git-rpc.js +166 -0
- package/dist/git-steps.d.ts +579 -0
- package/dist/git-steps.js +804 -0
- package/dist/git-wire.d.ts +33 -0
- package/dist/git-wire.js +240 -0
- package/dist/git.d.ts +322 -0
- package/dist/git.js +98 -0
- package/dist/host.d.ts +97 -0
- package/dist/host.js +732 -0
- package/dist/inbox.d.ts +99 -0
- package/dist/inbox.js +184 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +15 -0
- package/dist/inference.d.ts +17 -0
- package/dist/inference.js +115 -0
- package/dist/internal/app-error.d.ts +8 -0
- package/dist/internal/app-error.js +36 -0
- package/dist/internal/app-session.d.ts +8 -0
- package/dist/internal/app-session.js +551 -0
- package/dist/internal/back-inputs.d.ts +22 -0
- package/dist/internal/back-inputs.js +93 -0
- package/dist/internal/browser-bridge.d.ts +30 -0
- package/dist/internal/browser-bridge.js +203 -0
- package/dist/internal/capabilities.d.ts +13 -0
- package/dist/internal/capabilities.js +138 -0
- package/dist/internal/commands.d.ts +9 -0
- package/dist/internal/commands.js +257 -0
- package/dist/internal/disconnect-host.d.ts +7 -0
- package/dist/internal/disconnect-host.js +19 -0
- package/dist/internal/git-edit-tree.d.ts +6 -0
- package/dist/internal/git-edit-tree.js +46 -0
- package/dist/internal/memory-fs.d.ts +39 -0
- package/dist/internal/memory-fs.js +154 -0
- package/dist/internal/protocol.d.ts +116 -0
- package/dist/internal/protocol.js +6 -0
- package/dist/internal/query-feed.d.ts +23 -0
- package/dist/internal/query-feed.js +111 -0
- package/dist/internal/result.d.ts +22 -0
- package/dist/internal/result.js +61 -0
- package/dist/internal/rpc-contract.d.ts +24 -0
- package/dist/internal/rpc-contract.js +78 -0
- package/dist/internal/schemas.d.ts +307 -0
- package/dist/internal/schemas.js +524 -0
- package/dist/internal/store.d.ts +8 -0
- package/dist/internal/store.js +30 -0
- package/dist/internal/terminal-gate.d.ts +17 -0
- package/dist/internal/terminal-gate.js +94 -0
- package/dist/lifecycle.d.ts +44 -0
- package/dist/lifecycle.js +1 -0
- package/dist/marketplace.d.ts +159 -0
- package/dist/marketplace.js +102 -0
- package/dist/media-json.d.ts +181 -0
- package/dist/media-json.js +204 -0
- package/dist/media-library.d.ts +11 -0
- package/dist/media-library.js +15 -0
- package/dist/media-request-id.d.ts +7 -0
- package/dist/media-request-id.js +16 -0
- package/dist/media.d.ts +354 -0
- package/dist/media.js +654 -0
- package/dist/method-refusal.d.ts +64 -0
- package/dist/method-refusal.js +48 -0
- package/dist/pending.d.ts +10 -0
- package/dist/pending.js +10 -0
- package/dist/query.d.ts +76 -0
- package/dist/query.js +195 -0
- package/dist/react-mount.d.ts +23 -0
- package/dist/react-mount.js +108 -0
- package/dist/react.d.ts +41 -0
- package/dist/react.js +111 -0
- package/dist/read-copy.d.ts +103 -0
- package/dist/read-copy.js +195 -0
- package/dist/roles/files.d.ts +295 -0
- package/dist/roles/files.js +184 -0
- package/dist/root.d.ts +21 -0
- package/dist/root.js +1 -0
- package/dist/rpc.d.ts +1 -0
- package/dist/rpc.js +1 -0
- package/dist/status.d.ts +17 -0
- package/dist/status.js +50 -0
- package/dist/storage-wire.d.ts +167 -0
- package/dist/storage-wire.js +115 -0
- package/dist/storage.d.ts +72 -0
- package/dist/storage.js +143 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.js +1 -0
- package/dist/test-kit/git.d.ts +31 -0
- package/dist/test-kit/git.js +174 -0
- package/dist/ui.d.ts +169 -0
- package/dist/ui.js +10 -0
- package/dist/window.d.ts +41 -0
- package/dist/window.js +15 -0
- package/dist/world.d.ts +16 -0
- package/dist/world.js +17 -0
- package/docs/api/README.md +28 -0
- package/docs/api/app-build-contract.md +113 -0
- package/docs/api/backend-declaration.md +62 -0
- package/docs/api/backend-definition.md +215 -0
- package/docs/api/backend.md +269 -0
- package/docs/api/errors.md +169 -0
- package/docs/api/files.md +91 -0
- package/docs/api/gills.md +164 -0
- package/docs/api/git-commit.md +279 -0
- package/docs/api/git-rpc.md +243 -0
- package/docs/api/git-steps.md +1010 -0
- package/docs/api/git-wire.md +197 -0
- package/docs/api/git.md +579 -0
- package/docs/api/index.md +2880 -0
- package/docs/api/media-json.md +507 -0
- package/docs/api/media-request-id.md +32 -0
- package/docs/api/modules.md +32 -0
- package/docs/api/query.md +253 -0
- package/docs/api/react.md +216 -0
- package/docs/api/read-copy.md +204 -0
- package/docs/api/rpc.md +128 -0
- package/docs/api/storage.md +620 -0
- package/package.json +197 -3
package/dist/connect.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { reportCatchFailure } from "./failure.js";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import * as Predicate from "effect/Predicate";
|
|
4
|
+
/**
|
|
5
|
+
* WHY: the iframe owns one authenticated page epoch; this door validates the
|
|
6
|
+
* host before handing transport ownership to the internal session (2026-08-27).
|
|
7
|
+
*/
|
|
8
|
+
import { AppError } from "./errors.js";
|
|
9
|
+
import { connectAppPort } from "./internal/app-session.js";
|
|
10
|
+
import { APP_CONNECT_TYPE, APP_PROTOCOL_VERSION, APP_READY_TYPE, } from "./internal/protocol.js";
|
|
11
|
+
import { parseConnect } from "./internal/schemas.js";
|
|
12
|
+
let liveConnection = false;
|
|
13
|
+
/** Owns the pending handshake and its one-way transition into a live epoch. */
|
|
14
|
+
class PendingAppConnection {
|
|
15
|
+
options;
|
|
16
|
+
#appWindow;
|
|
17
|
+
#origin;
|
|
18
|
+
#port;
|
|
19
|
+
#settled = false;
|
|
20
|
+
#readyRetry;
|
|
21
|
+
#deadline;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.#origin = expectedOrigin(options.expectedHostOrigin);
|
|
25
|
+
this.#appWindow = globalThis.window;
|
|
26
|
+
}
|
|
27
|
+
start() {
|
|
28
|
+
performance.mark("fungi.app-open.agar-connect-start");
|
|
29
|
+
const origin = this.#origin;
|
|
30
|
+
if (!origin)
|
|
31
|
+
return Promise.reject(invalidOrigin());
|
|
32
|
+
const error = this.#startError();
|
|
33
|
+
if (error)
|
|
34
|
+
return Promise.reject(error);
|
|
35
|
+
liveConnection = true;
|
|
36
|
+
return this.#awaitConnection(origin);
|
|
37
|
+
}
|
|
38
|
+
#awaitConnection(origin) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const finish = (error, root) => {
|
|
41
|
+
this.#finish(error, root, onMessage, onAbort, resolve, reject);
|
|
42
|
+
};
|
|
43
|
+
const onAbort = () => {
|
|
44
|
+
finish(cancelled());
|
|
45
|
+
};
|
|
46
|
+
const onMessage = (event) => {
|
|
47
|
+
this.#onMessage(event, origin, finish, onMessage);
|
|
48
|
+
};
|
|
49
|
+
this.#appWindow.addEventListener("message", onMessage);
|
|
50
|
+
this.options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
51
|
+
const notify = () => {
|
|
52
|
+
try {
|
|
53
|
+
performance.mark("fungi.app-open.agar-ready-sent");
|
|
54
|
+
this.#appWindow.parent.postMessage({ protocolVersion: APP_PROTOCOL_VERSION, type: APP_READY_TYPE }, origin);
|
|
55
|
+
}
|
|
56
|
+
catch (cause) {
|
|
57
|
+
reportCatchFailure("connect.notify-host", cause);
|
|
58
|
+
finish(notifyError(cause));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// A remounted document can announce itself before its host sees load.
|
|
62
|
+
// Retry only the handshake, never an admitted App operation.
|
|
63
|
+
this.#readyRetry = setInterval(notify, 200);
|
|
64
|
+
this.#deadline = setTimeout(() => {
|
|
65
|
+
finish(disconnected());
|
|
66
|
+
}, 10_000);
|
|
67
|
+
notify();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
#finish(error, root, onMessage, onAbort, resolve, reject) {
|
|
71
|
+
if (this.#settled)
|
|
72
|
+
return;
|
|
73
|
+
this.#settled = true;
|
|
74
|
+
this.#stopReady();
|
|
75
|
+
this.#appWindow.removeEventListener("message", onMessage);
|
|
76
|
+
this.options.signal?.removeEventListener("abort", onAbort);
|
|
77
|
+
if (error || !root) {
|
|
78
|
+
this.#port?.close();
|
|
79
|
+
liveConnection = false;
|
|
80
|
+
reject(error ?? disconnected());
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolve(root);
|
|
84
|
+
}
|
|
85
|
+
#onMessage(event, origin, finish, onMessage) {
|
|
86
|
+
if (!isConnectMessage(event.data))
|
|
87
|
+
return;
|
|
88
|
+
performance.mark("fungi.app-open.agar-connect-received");
|
|
89
|
+
if (event.source !== this.#appWindow.parent || event.origin !== origin) {
|
|
90
|
+
finish(identityError());
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const message = parseConnection(event, finish);
|
|
94
|
+
if (!message)
|
|
95
|
+
return;
|
|
96
|
+
const port = event.ports.length === 1 ? event.ports[0] : undefined;
|
|
97
|
+
if (!port) {
|
|
98
|
+
finish(protocolError("The app host did not transfer exactly one port"));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.#port = port;
|
|
102
|
+
this.#stopReady();
|
|
103
|
+
this.#appWindow.removeEventListener("message", onMessage);
|
|
104
|
+
void connectAppPort({
|
|
105
|
+
epoch: message.epoch,
|
|
106
|
+
isActive: () => !this.#settled,
|
|
107
|
+
onDisposed: () => {
|
|
108
|
+
liveConnection = false;
|
|
109
|
+
},
|
|
110
|
+
port,
|
|
111
|
+
window: this.#appWindow,
|
|
112
|
+
}).then((root) => {
|
|
113
|
+
performance.mark("fungi.app-open.agar-root-ready");
|
|
114
|
+
finish(undefined, root);
|
|
115
|
+
}, (cause) => {
|
|
116
|
+
finish(connectionError(cause));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
#stopReady() {
|
|
120
|
+
clearInterval(this.#readyRetry);
|
|
121
|
+
clearTimeout(this.#deadline);
|
|
122
|
+
this.#readyRetry = undefined;
|
|
123
|
+
this.#deadline = undefined;
|
|
124
|
+
}
|
|
125
|
+
#startError() {
|
|
126
|
+
if (this.options.signal?.aborted)
|
|
127
|
+
return cancelled();
|
|
128
|
+
if (liveConnection)
|
|
129
|
+
return new AppError("An app connection is already live", {
|
|
130
|
+
code: "IDENTITY",
|
|
131
|
+
retryable: false,
|
|
132
|
+
});
|
|
133
|
+
if (Predicate.isUndefined(globalThis.window) ||
|
|
134
|
+
this.#appWindow.parent === this.#appWindow)
|
|
135
|
+
return new AppError("The app host is unavailable", {
|
|
136
|
+
code: "DISCONNECTED",
|
|
137
|
+
retryable: true,
|
|
138
|
+
});
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Connects this iframe page to its authenticated host. */
|
|
143
|
+
export function connectApp(options) {
|
|
144
|
+
return new PendingAppConnection(options).start();
|
|
145
|
+
}
|
|
146
|
+
function cancelled() {
|
|
147
|
+
return new AppError("App connection was cancelled", {
|
|
148
|
+
code: "CANCELLED",
|
|
149
|
+
retryable: false,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function connectionError(cause) {
|
|
153
|
+
return cause instanceof AppError
|
|
154
|
+
? cause
|
|
155
|
+
: new AppError("Unable to connect to the app host", {
|
|
156
|
+
cause,
|
|
157
|
+
code: "PROTOCOL",
|
|
158
|
+
retryable: false,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function disconnected() {
|
|
162
|
+
return new AppError("The app host disconnected", {
|
|
163
|
+
code: "DISCONNECTED",
|
|
164
|
+
retryable: true,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function expectedOrigin(value) {
|
|
168
|
+
if (!URL.canParse(value))
|
|
169
|
+
return undefined;
|
|
170
|
+
const origin = new URL(value).origin;
|
|
171
|
+
return origin === value ? origin : undefined;
|
|
172
|
+
}
|
|
173
|
+
function identityError() {
|
|
174
|
+
return new AppError("The app connection identity is invalid", {
|
|
175
|
+
code: "IDENTITY",
|
|
176
|
+
retryable: false,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function invalidOrigin() {
|
|
180
|
+
return new AppError("expectedHostOrigin must be an origin", {
|
|
181
|
+
code: "INVALID_ARGUMENT",
|
|
182
|
+
retryable: false,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const isConnectMessage = Schema.is(Schema.Struct({ type: Schema.Literal(APP_CONNECT_TYPE) }));
|
|
186
|
+
function notifyError(cause) {
|
|
187
|
+
return new AppError("Unable to notify the app host", {
|
|
188
|
+
cause,
|
|
189
|
+
code: "DISCONNECTED",
|
|
190
|
+
retryable: true,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function parseConnection(event, finish) {
|
|
194
|
+
try {
|
|
195
|
+
return parseConnect(event.data);
|
|
196
|
+
}
|
|
197
|
+
catch (cause) {
|
|
198
|
+
reportCatchFailure("connect.decode-message", cause);
|
|
199
|
+
finish(cause instanceof AppError
|
|
200
|
+
? cause
|
|
201
|
+
: new AppError("Malformed app connection", {
|
|
202
|
+
cause,
|
|
203
|
+
code: "PROTOCOL",
|
|
204
|
+
retryable: false,
|
|
205
|
+
}));
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function protocolError(message) {
|
|
210
|
+
return new AppError(message, { code: "PROTOCOL", retryable: false });
|
|
211
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** The closed set of reasons an AppError can state; every SDK and
|
|
2
|
+
* host-reported failure is tagged with exactly one of these codes. */
|
|
3
|
+
export type AppErrorCode = "CANCELLED" | "CAPABILITY_DENIED" | "CAPABILITY_REVOKED" | "COMMAND_NOT_DECLARED" | "CONFLICT" | "DISCONNECTED" | "FILE_TOO_LARGE" | "HOST_FAILURE" | "IDENTITY" | "INSUFFICIENT_CREDIT" | "INVALID_ARGUMENT" | "PROTOCOL" | "SAFETY_FILTER_REFUSED" | "SELECTION_CHANGED" | "TIMEOUT";
|
|
4
|
+
/** Required construction options for an AppError. */
|
|
5
|
+
export type AppErrorOptions = {
|
|
6
|
+
readonly cause?: unknown;
|
|
7
|
+
readonly code: AppErrorCode;
|
|
8
|
+
/** Every error states retry truth explicitly; absence is not a verdict. */
|
|
9
|
+
readonly retryable: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** The SDK's one typed failure. Carries a closed `code` naming the failure
|
|
12
|
+
* kind and an explicit `retryable` verdict, instead of leaving callers to
|
|
13
|
+
* guess either from the message or exception shape. */
|
|
14
|
+
export declare class AppError extends Error {
|
|
15
|
+
readonly code: AppErrorCode;
|
|
16
|
+
readonly retryable: boolean;
|
|
17
|
+
constructor(message: string, options: AppErrorOptions);
|
|
18
|
+
}
|
|
19
|
+
/** Trusted presentation copy; overrides never change recovery or retry policy. */
|
|
20
|
+
export type ErrorCopy = Readonly<{
|
|
21
|
+
title: string;
|
|
22
|
+
message: string;
|
|
23
|
+
}>;
|
|
24
|
+
/** Recovery hints are for the caller's UI, never an instruction to retry automatically. */
|
|
25
|
+
export type ErrorDescription = ErrorCopy & Readonly<{
|
|
26
|
+
retryable: boolean;
|
|
27
|
+
action?: "grant" | "retry" | "reconnect" | "billing";
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Describe an SDK-decoded failure without exposing exception or request data.
|
|
31
|
+
* The error owner retains retry truth. Grant and reconnect hints describe
|
|
32
|
+
* prerequisites, so they can accompany a non-retryable operation.
|
|
33
|
+
*/
|
|
34
|
+
export declare function describeAppError(cause: unknown, overrides?: Partial<Record<AppErrorCode, ErrorCopy>>): ErrorDescription;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** The SDK's one typed failure. Carries a closed `code` naming the failure
|
|
2
|
+
* kind and an explicit `retryable` verdict, instead of leaving callers to
|
|
3
|
+
* guess either from the message or exception shape. */
|
|
4
|
+
export class AppError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
retryable;
|
|
7
|
+
constructor(message, options) {
|
|
8
|
+
super(message, { cause: options.cause });
|
|
9
|
+
this.name = "AppError";
|
|
10
|
+
this.code = options.code;
|
|
11
|
+
this.retryable = options.retryable;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// User-facing wording approved by Levi; keep it in one reviewable table.
|
|
15
|
+
const errorCopy = {
|
|
16
|
+
CANCELLED: {
|
|
17
|
+
title: "Request cancelled",
|
|
18
|
+
message: "This request was cancelled.",
|
|
19
|
+
},
|
|
20
|
+
CAPABILITY_DENIED: {
|
|
21
|
+
title: "Access needed",
|
|
22
|
+
message: "This App needs permission to do that.",
|
|
23
|
+
action: "grant",
|
|
24
|
+
},
|
|
25
|
+
CAPABILITY_REVOKED: {
|
|
26
|
+
title: "Access changed",
|
|
27
|
+
message: "This App no longer has permission to do that.",
|
|
28
|
+
action: "grant",
|
|
29
|
+
},
|
|
30
|
+
COMMAND_NOT_DECLARED: {
|
|
31
|
+
title: "Action unavailable",
|
|
32
|
+
message: "This action isn't available in this App.",
|
|
33
|
+
},
|
|
34
|
+
CONFLICT: {
|
|
35
|
+
title: "File changed",
|
|
36
|
+
message: "This file changed elsewhere. Review it before saving again.",
|
|
37
|
+
},
|
|
38
|
+
DISCONNECTED: {
|
|
39
|
+
title: "Connection lost",
|
|
40
|
+
message: "Reopen the App to connect again.",
|
|
41
|
+
action: "reconnect",
|
|
42
|
+
},
|
|
43
|
+
HOST_FAILURE: {
|
|
44
|
+
title: "Couldn't complete the request",
|
|
45
|
+
message: "The service couldn't complete this request.",
|
|
46
|
+
},
|
|
47
|
+
FILE_TOO_LARGE: {
|
|
48
|
+
title: "File too large",
|
|
49
|
+
message: "Choose a file within the supported size limit.",
|
|
50
|
+
},
|
|
51
|
+
IDENTITY: {
|
|
52
|
+
title: "Couldn't connect",
|
|
53
|
+
message: "Reopen the App to connect again.",
|
|
54
|
+
action: "reconnect",
|
|
55
|
+
},
|
|
56
|
+
INSUFFICIENT_CREDIT: {
|
|
57
|
+
title: "Something went wrong",
|
|
58
|
+
message: "The App couldn't complete this request.",
|
|
59
|
+
action: "billing",
|
|
60
|
+
},
|
|
61
|
+
INVALID_ARGUMENT: {
|
|
62
|
+
title: "Request not accepted",
|
|
63
|
+
message: "The App couldn't use the details in this request.",
|
|
64
|
+
},
|
|
65
|
+
PROTOCOL: {
|
|
66
|
+
title: "Couldn't read the response",
|
|
67
|
+
message: "The App received a response it couldn't use.",
|
|
68
|
+
},
|
|
69
|
+
SAFETY_FILTER_REFUSED: {
|
|
70
|
+
title: "Safety filter refused this prompt",
|
|
71
|
+
message: "The model's safety filter refused this prompt — try rewording.",
|
|
72
|
+
},
|
|
73
|
+
SELECTION_CHANGED: {
|
|
74
|
+
title: "Selection changed",
|
|
75
|
+
message: "The selection changed during this request. Reopen the App to continue.",
|
|
76
|
+
action: "reconnect",
|
|
77
|
+
},
|
|
78
|
+
TIMEOUT: {
|
|
79
|
+
title: "Still waiting",
|
|
80
|
+
message: "The request didn't respond in time. Its outcome isn't known yet.",
|
|
81
|
+
},
|
|
82
|
+
UNKNOWN: {
|
|
83
|
+
title: "Something went wrong",
|
|
84
|
+
message: "The App couldn't complete this request.",
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Describe an SDK-decoded failure without exposing exception or request data.
|
|
89
|
+
* The error owner retains retry truth. Grant and reconnect hints describe
|
|
90
|
+
* prerequisites, so they can accompany a non-retryable operation.
|
|
91
|
+
*/
|
|
92
|
+
export function describeAppError(cause, overrides) {
|
|
93
|
+
if (!(cause instanceof AppError))
|
|
94
|
+
return { ...errorCopy.UNKNOWN, retryable: false };
|
|
95
|
+
const entry = errorCopy[cause.code];
|
|
96
|
+
const copy = overrides?.[cause.code] ?? entry;
|
|
97
|
+
const action = entry.action ?? (cause.retryable ? "retry" : undefined);
|
|
98
|
+
const description = {
|
|
99
|
+
title: copy.title,
|
|
100
|
+
message: copy.message,
|
|
101
|
+
retryable: cause.retryable,
|
|
102
|
+
};
|
|
103
|
+
if (action)
|
|
104
|
+
return { ...description, action };
|
|
105
|
+
return description;
|
|
106
|
+
}
|
package/dist/failure.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as Redacted from "effect/Redacted";
|
|
2
|
+
/** Retain the original Agar failure without printing exception or request data. */
|
|
3
|
+
export function reportCatchFailure(operation, cause) {
|
|
4
|
+
console.error(JSON.stringify({
|
|
5
|
+
source: "agar",
|
|
6
|
+
operation,
|
|
7
|
+
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
8
|
+
}), Redacted.make(cause));
|
|
9
|
+
}
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** A parent-owned read. Closing it aborts the parent HTTP operation. */
|
|
2
|
+
export type AppFileRead = Disposable & {
|
|
3
|
+
/** Resolve with the underlying Response once it is available. */
|
|
4
|
+
response(): Promise<Response>;
|
|
5
|
+
/** Release the parent's open read, aborting it if still in flight. */
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
/** Native Response crosses RPC; File is constructed only in the receiving App. */
|
|
9
|
+
export declare function readAppFile(read: AppFileRead, options: {
|
|
10
|
+
name: string;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}): Promise<File>;
|
|
13
|
+
/** A parent-owned upload; close requests transport abort, never promises rollback. */
|
|
14
|
+
export type AppFileWrite = Disposable & {
|
|
15
|
+
/** Stream the upload's content to the parent-owned destination. */
|
|
16
|
+
write(content: ReadableStream<Uint8Array>): Promise<void>;
|
|
17
|
+
/** Request that the parent abort or finalize the upload transport. */
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
/** Stream a Blob's bytes to a parent-owned upload. Aborting `signal` closes
|
|
21
|
+
* the upload early; a close failure after a successful write is reported,
|
|
22
|
+
* never thrown, since it cannot change an already-acknowledged commit. */
|
|
23
|
+
export declare function writeAppFile(write: AppFileWrite, content: Blob, options: {
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
}): Promise<void>;
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { disposeRpcValue } from "./internal/capabilities.js";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { reportCatchFailure } from "./failure.js";
|
|
4
|
+
import { AppError } from "./errors.js";
|
|
5
|
+
const decodeTooLarge = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({ error: Schema.Literal("too_large") })));
|
|
6
|
+
/** Native Response crosses RPC; File is constructed only in the receiving App. */
|
|
7
|
+
export async function readAppFile(read, options) {
|
|
8
|
+
let closing;
|
|
9
|
+
const close = () => (closing ??= read.close());
|
|
10
|
+
const abort = () => {
|
|
11
|
+
// The finalizer awaits this same close; the event callback cannot await it.
|
|
12
|
+
void close().catch((cause) => {
|
|
13
|
+
reportCatchFailure("files.read.abort-close", cause);
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
17
|
+
try {
|
|
18
|
+
options.signal?.throwIfAborted();
|
|
19
|
+
const result = await read.response();
|
|
20
|
+
try {
|
|
21
|
+
const response = Schema.decodeUnknownSync(Schema.instanceOf(Response))(result);
|
|
22
|
+
if (response.status === 413) {
|
|
23
|
+
const failure = decodeTooLarge(await response.text());
|
|
24
|
+
throw new AppError("App file exceeds the read bound", {
|
|
25
|
+
code: failure._tag === "Some" ? "FILE_TOO_LARGE" : "PROTOCOL",
|
|
26
|
+
retryable: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (!response.ok)
|
|
30
|
+
throw new Error(`App file unavailable (${String(response.status)})`);
|
|
31
|
+
const blob = await response.blob();
|
|
32
|
+
options.signal?.throwIfAborted();
|
|
33
|
+
return new File([blob], options.name, {
|
|
34
|
+
type: blob.type,
|
|
35
|
+
lastModified: 0,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
disposeRpcValue(result);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
options.signal?.removeEventListener("abort", abort);
|
|
44
|
+
try {
|
|
45
|
+
await close();
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
read[Symbol.dispose]();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Stream a Blob's bytes to a parent-owned upload. Aborting `signal` closes
|
|
53
|
+
* the upload early; a close failure after a successful write is reported,
|
|
54
|
+
* never thrown, since it cannot change an already-acknowledged commit. */
|
|
55
|
+
export async function writeAppFile(write, content, options) {
|
|
56
|
+
let closing;
|
|
57
|
+
const close = () => (closing ??= write.close());
|
|
58
|
+
const abort = () => {
|
|
59
|
+
void close().catch((cause) => {
|
|
60
|
+
reportCatchFailure("files.write.abort-close", cause);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
64
|
+
try {
|
|
65
|
+
options.signal?.throwIfAborted();
|
|
66
|
+
await write.write(content.stream());
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
options.signal?.removeEventListener("abort", abort);
|
|
70
|
+
// Cleanup cannot change an acknowledged commit or replace the primary write error.
|
|
71
|
+
try {
|
|
72
|
+
await close();
|
|
73
|
+
}
|
|
74
|
+
catch (cause) {
|
|
75
|
+
reportCatchFailure("files.write.remote-close", cause);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
write[Symbol.dispose]();
|
|
79
|
+
}
|
|
80
|
+
catch (cause) {
|
|
81
|
+
reportCatchFailure("files.write.local-dispose", cause);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|