@fungi.computer/agar 0.2.0 → 0.3.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/README.md +34 -18
- package/dist/agent-message-meta.d.ts +6 -1
- package/dist/agent-message-meta.js +12 -1
- package/dist/approvals.d.ts +5 -0
- package/dist/approvals.js +36 -0
- package/dist/backend-refusal.d.ts +1 -0
- package/dist/backend-refusal.js +1 -0
- package/dist/computer-contracts.d.ts +0 -17
- package/dist/computer-contracts.js +0 -19
- package/dist/connect.d.ts +3 -3
- package/dist/connect.js +7 -17
- package/dist/errors.d.ts +39 -1
- package/dist/errors.js +62 -5
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/internal/media-diagnostic.d.ts +11 -0
- package/dist/internal/media-diagnostic.js +21 -0
- package/dist/internal/schemas.d.ts +7 -0
- package/dist/internal/schemas.js +17 -21
- package/dist/media-json.d.ts +14 -0
- package/dist/media-json.js +1 -0
- package/dist/media.d.ts +11 -0
- package/dist/media.js +6 -0
- package/dist/query.d.ts +2 -2
- package/dist/react-mount.d.ts +7 -1
- package/dist/react-mount.js +28 -8
- package/dist/window.d.ts +4 -0
- package/docs/api/README.md +15 -15
- package/docs/api/backend-definition.md +3 -3
- package/docs/api/backend.md +1 -0
- package/docs/api/errors.md +104 -0
- package/docs/api/index.md +87 -26
- package/docs/api/media-json.md +32 -59
- package/docs/api/query.md +1 -1
- package/docs/api/react.md +13 -6
- package/package.json +12 -7
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# Agar (`@fungi.computer/agar`)
|
|
2
2
|
|
|
3
3
|
Agar defines typed App backends and host capabilities. Most people want
|
|
4
|
-
[Oyster](https://docs.fungi.computer/
|
|
5
|
-
[Shiitake](https://docs.fungi.computer/
|
|
6
|
-
Agents.
|
|
4
|
+
[Oyster](https://docs.fungi.computer/oyster/) to run Apps or
|
|
5
|
+
[Shiitake](https://docs.fungi.computer/shiitake/) to run Agents.
|
|
7
6
|
|
|
8
7
|
Agar is the App contract for fungi.computer. It defines an App's typed backend
|
|
9
8
|
methods, connects its frontend to a host through typed capabilities, and gives
|
|
@@ -336,10 +335,15 @@ the declared methods separately.
|
|
|
336
335
|
|
|
337
336
|
## Connect and release
|
|
338
337
|
|
|
339
|
-
connectApp
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
338
|
+
`connectApp({ host: "parent", signal })` discovers the host through the existing
|
|
339
|
+
parent handshake, without reading a referrer or accepting an origin parameter.
|
|
340
|
+
Serve the App document with CSP `frame-ancestors` pinned to its allowed host.
|
|
341
|
+
The browser checks the entire ancestor chain; without that policy an unrelated
|
|
342
|
+
site embedding the App could become its host. Hub and Oyster supply this policy
|
|
343
|
+
on their App document routes. Only the public, secret-free ready announcement
|
|
344
|
+
uses `"*"`; connection acceptance checks the actual parent Window, its
|
|
345
|
+
non-opaque origin, protocol and single transferred port before returning an
|
|
346
|
+
AppRoot. It permits one live connection per frontend page.
|
|
343
347
|
|
|
344
348
|
The root supplies:
|
|
345
349
|
|
|
@@ -557,14 +561,18 @@ const dispose = mountApp(root, { name: "Bazaar" }, ({ app }) => (
|
|
|
557
561
|
import.meta.hot?.dispose(dispose);
|
|
558
562
|
```
|
|
559
563
|
|
|
560
|
-
`mountApp`
|
|
561
|
-
|
|
564
|
+
`mountApp` discovers its actual parent through Agar's existing handshake,
|
|
565
|
+
independently of referrer policy. The parent must have a non-opaque origin and
|
|
566
|
+
transfer exactly one port for the validated epoch. Top-level launches remain
|
|
567
|
+
standalone; embedded pages without a valid handshake report `not-hosted` with an
|
|
568
|
+
`AppError` and a visible alert rather than silently becoming standalone. It
|
|
569
|
+
applies and subscribes to the host's theme, and restores the original theme on
|
|
562
570
|
disconnect or unmount. Its render callback receives `connecting`, `live`,
|
|
563
|
-
`standalone` (
|
|
564
|
-
connection). Only `live` carries an AppRoot. The
|
|
565
|
-
pending handshake, releases subscriptions, disposes
|
|
566
|
-
React. A late connection is disposed too. Existing
|
|
567
|
-
marks are emitted at root render and commits.
|
|
571
|
+
`standalone` (a top-level launch), `not-hosted` (an initial connection failure),
|
|
572
|
+
or `ended` (a closed live connection). Only `live` carries an AppRoot. The
|
|
573
|
+
returned disposer aborts a pending handshake, releases subscriptions, disposes
|
|
574
|
+
the connection and unmounts React. A late connection is disposed too. Existing
|
|
575
|
+
App-open React performance marks are emitted at root render and commits.
|
|
568
576
|
|
|
569
577
|
Supply `queryClient` to wrap the App in `QueryClientProvider`. The caller owns
|
|
570
578
|
that client, its cache policy and grant invalidation; mounting does not clear
|
|
@@ -751,6 +759,13 @@ The exported `mediaGenerationDescriptionSchema`,
|
|
|
751
759
|
`mediaGenerationJsonReplySchema` and `mediaGenerationReferenceSchema` decode
|
|
752
760
|
these boundaries with the existing Agar Media grammar.
|
|
753
761
|
|
|
762
|
+
Failures may include safe `provider` facts: an allowlisted category and bounded
|
|
763
|
+
code, or `result_download` and an HTTP status. JSON, RPC and
|
|
764
|
+
`MediaGenerationError.provider` preserve the same facts and retry truth.
|
|
765
|
+
`./errors` exposes declaration-safe diagnostic types and decoder helpers; one
|
|
766
|
+
internal Effect schema owns validation. Private provider messages, bodies, URLs
|
|
767
|
+
and causes are not diagnostic fields and are rejected at this boundary.
|
|
768
|
+
|
|
754
769
|
Attempts retain their canonical request ID when restored or when polling
|
|
755
770
|
Pending. The shared Agar Pending validator requires an integer `retryAfterMs`
|
|
756
771
|
from 1000 to 60000. Cancellation aborts the attempt's active local requests;
|
|
@@ -761,7 +776,8 @@ the platform DOM types and `ESNext.Disposable`.
|
|
|
761
776
|
See the [generated API](docs/api/README.md) for signatures and host ports.
|
|
762
777
|
|
|
763
778
|
`@fungi.computer/agar/computer-contracts` is the runtime-neutral public home for
|
|
764
|
-
|
|
765
|
-
definitions can validate
|
|
766
|
-
|
|
767
|
-
|
|
779
|
+
Computer admission, input, dispatch, timeout and attachment-budget outcomes. App
|
|
780
|
+
backend definitions can validate these outcomes without importing the Computer
|
|
781
|
+
RPC client. Apps request ordinary Computer execution; Workflow Git credential
|
|
782
|
+
issuance, carriage, renewal and revocation stay inside the trusted host and
|
|
783
|
+
native execution boundary, not the App contract.
|
|
@@ -3,6 +3,7 @@ import * as z from "zod";
|
|
|
3
3
|
export declare const FungiAuthorSchema: z.ZodLazy<z.ZodObject<{
|
|
4
4
|
id: z.ZodString;
|
|
5
5
|
name: z.ZodString;
|
|
6
|
+
kind: z.ZodOptional<z.ZodLiteral<"agent">>;
|
|
6
7
|
}, z.core.$strict>>;
|
|
7
8
|
/** Who a Session line came from, as the admitting host authenticated them. */
|
|
8
9
|
export type FungiAuthor = z.infer<typeof FungiAuthorSchema>;
|
|
@@ -34,7 +35,9 @@ export declare const FungiMessageMetaSchema: z.ZodObject<{
|
|
|
34
35
|
author: z.ZodOptional<z.ZodLazy<z.ZodObject<{
|
|
35
36
|
id: z.ZodString;
|
|
36
37
|
name: z.ZodString;
|
|
38
|
+
kind: z.ZodOptional<z.ZodLiteral<"agent">>;
|
|
37
39
|
}, z.core.$strict>>>;
|
|
40
|
+
sourceRequestId: z.ZodOptional<z.ZodLazy<z.ZodString>>;
|
|
38
41
|
delegation: z.ZodOptional<z.ZodObject<{
|
|
39
42
|
ancestors: z.ZodArray<z.ZodLazy<z.ZodString>>;
|
|
40
43
|
maxDepth: z.ZodNumber;
|
|
@@ -68,7 +71,9 @@ export declare function messageAuthor(meta: MessageMetaValue | undefined): Fungi
|
|
|
68
71
|
export declare function messageDelegationReply(meta: MessageMetaValue | undefined): FungiDelegationReply | undefined;
|
|
69
72
|
/** The metadata the host attaches for one authenticated author, as JSON. */
|
|
70
73
|
export type AuthorMeta = Readonly<{
|
|
71
|
-
author: Readonly<
|
|
74
|
+
author: Readonly<{
|
|
75
|
+
[K in keyof FungiAuthor]: Exclude<FungiAuthor[K], undefined>;
|
|
76
|
+
}>;
|
|
72
77
|
}>;
|
|
73
78
|
/** The metadata the host attaches for one authenticated author. */
|
|
74
79
|
export declare function authorMeta(author: FungiAuthor): AuthorMeta;
|
|
@@ -3,6 +3,12 @@ import * as z from "zod";
|
|
|
3
3
|
export const FungiAuthorSchema = z.lazy(() => z.strictObject({
|
|
4
4
|
id: z.string().min(1).max(256),
|
|
5
5
|
name: z.string().min(1).max(128),
|
|
6
|
+
/**
|
|
7
|
+
* Present only when the author is another Agent the host admitted a
|
|
8
|
+
* delegated send or reply from; a line's text never sets it. Human
|
|
9
|
+
* authors carry no kind.
|
|
10
|
+
*/
|
|
11
|
+
kind: z.literal("agent").optional(),
|
|
6
12
|
}));
|
|
7
13
|
const identifier = z.lazy(() => z.string().min(1).max(256));
|
|
8
14
|
/**
|
|
@@ -31,6 +37,8 @@ export const FungiDelegationReplySchema = z.lazy(() => z.strictObject({
|
|
|
31
37
|
*/
|
|
32
38
|
export const FungiMessageMetaSchema = z.strictObject({
|
|
33
39
|
author: FungiAuthorSchema.optional(),
|
|
40
|
+
/** Original caller identity when the host translates its request into an owner key. */
|
|
41
|
+
sourceRequestId: identifier.optional(),
|
|
34
42
|
/** Host-admitted delegation path and its inherited abuse bound. */
|
|
35
43
|
delegation: z
|
|
36
44
|
.strictObject({
|
|
@@ -59,5 +67,8 @@ export function messageDelegationReply(meta) {
|
|
|
59
67
|
}
|
|
60
68
|
/** The metadata the host attaches for one authenticated author. */
|
|
61
69
|
export function authorMeta(author) {
|
|
62
|
-
|
|
70
|
+
const { id, name, kind } = author;
|
|
71
|
+
return kind === undefined
|
|
72
|
+
? { author: { id, name } }
|
|
73
|
+
: { author: { id, name, kind } };
|
|
63
74
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Approvals } from "@fungi.computer/approvals";
|
|
2
|
+
import { type CapabilityContract } from "./capabilities.js";
|
|
3
|
+
export type { Approvals } from "@fungi.computer/approvals";
|
|
4
|
+
/** Member-bound approval view. Authentication and current rights belong to the host. */
|
|
5
|
+
export declare const approvals: CapabilityContract<Approvals>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { ApprovalViewSchema, ApprovalRefusalReasonSchema, AutoApproveInputSchema, } from "@fungi.computer/approvals";
|
|
4
|
+
import { defineCapability } from "./capabilities.js";
|
|
5
|
+
import { rpcDataSchema, rpcHandleSchema, rpcMethod, } from "./internal/rpc-contract.js";
|
|
6
|
+
const refusal = Schema.Struct({
|
|
7
|
+
kind: Schema.Literal("refused"),
|
|
8
|
+
reason: ApprovalRefusalReasonSchema,
|
|
9
|
+
});
|
|
10
|
+
const page = Schema.Struct({
|
|
11
|
+
items: Schema.Array(ApprovalViewSchema),
|
|
12
|
+
nextCursor: Schema.NullOr(Schema.String),
|
|
13
|
+
});
|
|
14
|
+
const answer = Schema.Union([
|
|
15
|
+
Schema.Struct({ kind: Schema.Literal("answered"), view: ApprovalViewSchema }),
|
|
16
|
+
refusal,
|
|
17
|
+
]);
|
|
18
|
+
const policy = Schema.Union([
|
|
19
|
+
Schema.Struct({
|
|
20
|
+
kind: Schema.Literal("updated"),
|
|
21
|
+
rule: AutoApproveInputSchema,
|
|
22
|
+
}),
|
|
23
|
+
refusal,
|
|
24
|
+
]);
|
|
25
|
+
// Match the existing storage/Agent capability adapters: the maintained RPC
|
|
26
|
+
// helpers bind native stub receivers, decode data and release result envelopes.
|
|
27
|
+
const data = (schema) => rpcDataSchema(z
|
|
28
|
+
.unknown()
|
|
29
|
+
.transform((value) => Schema.decodeUnknownSync(schema, { onExcessProperty: "error" })(value)));
|
|
30
|
+
/** Member-bound approval view. Authentication and current rights belong to the host. */
|
|
31
|
+
export const approvals = defineCapability("approvals", 1, z.lazy(() => rpcHandleSchema({
|
|
32
|
+
list: rpcMethod(data(page)),
|
|
33
|
+
get: rpcMethod(data(Schema.NullOr(ApprovalViewSchema))),
|
|
34
|
+
answer: rpcMethod(data(answer)),
|
|
35
|
+
setAutoApprove: rpcMethod(data(policy)),
|
|
36
|
+
})));
|
|
@@ -3,6 +3,7 @@ import * as z from "zod";
|
|
|
3
3
|
export declare const appBackendRefusalSchema: z.ZodObject<{
|
|
4
4
|
error: z.ZodLiteral<"App execution unavailable">;
|
|
5
5
|
reason: z.ZodEnum<{
|
|
6
|
+
disabled: "disabled";
|
|
6
7
|
exec_isolation_required: "exec_isolation_required";
|
|
7
8
|
execution_not_admitted: "execution_not_admitted";
|
|
8
9
|
artifact_unavailable: "artifact_unavailable";
|
package/dist/backend-refusal.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
/** Public metadata for one host-admitted Workflow Git pass; parsing never grants authority. */
|
|
3
|
-
export declare const workflowGitPassResultSchema: z.ZodLazy<z.ZodObject<{
|
|
4
|
-
passId: z.ZodString;
|
|
5
|
-
remote: z.ZodURL;
|
|
6
|
-
expiresAt: z.ZodNumber;
|
|
7
|
-
bearer: z.ZodExactOptional<z.ZodString>;
|
|
8
|
-
}, z.core.$strict>>;
|
|
9
|
-
/** The complete remote stays opaque; the host owns admission, renewal and revocation. */
|
|
10
|
-
export type WorkflowGitPassResult = z.output<typeof workflowGitPassResultSchema>;
|
|
11
2
|
/** Existing attachment budget operation kinds shared by Computer and Media. */
|
|
12
3
|
export declare const connectorOperationBudgetKindSchema: z.ZodEnum<{
|
|
13
4
|
write: "write";
|
|
@@ -63,11 +54,3 @@ export declare const appComputerExecTimeoutSchema: z.ZodLazy<z.ZodObject<{
|
|
|
63
54
|
error: z.ZodLiteral<"timeout">;
|
|
64
55
|
requestId: z.ZodString;
|
|
65
56
|
}, z.core.$strict>>;
|
|
66
|
-
/** The Workflow Git-pass owner has exactly these existing domain refusals. */
|
|
67
|
-
export declare const workflowGitPassRefusalSchema: z.ZodLazy<z.ZodObject<{
|
|
68
|
-
error: z.ZodEnum<{
|
|
69
|
-
unavailable: "unavailable";
|
|
70
|
-
revoked: "revoked";
|
|
71
|
-
not_granted: "not_granted";
|
|
72
|
-
}>;
|
|
73
|
-
}, z.core.$strict>>;
|
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
/** Public metadata for one host-admitted Workflow Git pass; parsing never grants authority. */
|
|
3
|
-
export const workflowGitPassResultSchema = z.lazy(() => z.strictObject({
|
|
4
|
-
passId: z.string().min(1),
|
|
5
|
-
remote: z.url({ protocol: /^https$/, abort: true }).refine((value) => {
|
|
6
|
-
const remote = new URL(value);
|
|
7
|
-
return remote.username === "" && remote.password === "";
|
|
8
|
-
}, "Workflow Git remotes must not contain credentials"),
|
|
9
|
-
expiresAt: z.number().int().positive(),
|
|
10
|
-
/**
|
|
11
|
-
* Present when the host admits this pass by `Authorization: Bearer` on the
|
|
12
|
-
* remote. It is never part of the remote; a command receives it only as
|
|
13
|
-
* command-local Git config, never written to a repository config.
|
|
14
|
-
*/
|
|
15
|
-
bearer: z.string().min(1).exactOptional(),
|
|
16
|
-
}));
|
|
17
2
|
/** Existing attachment budget operation kinds shared by Computer and Media. */
|
|
18
3
|
export const connectorOperationBudgetKindSchema = z.enum([
|
|
19
4
|
"exec",
|
|
@@ -51,7 +36,3 @@ export const appComputerExecTimeoutSchema = z.lazy(() => z.strictObject({
|
|
|
51
36
|
error: z.literal("timeout"),
|
|
52
37
|
requestId: z.string(),
|
|
53
38
|
}));
|
|
54
|
-
/** The Workflow Git-pass owner has exactly these existing domain refusals. */
|
|
55
|
-
export const workflowGitPassRefusalSchema = z.lazy(() => z.strictObject({
|
|
56
|
-
error: z.enum(["revoked", "not_granted", "unavailable"]),
|
|
57
|
-
}));
|
package/dist/connect.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { AppRoot } from "./root.js";
|
|
2
2
|
/** Options for connecting this iframe page to its authenticated host. */
|
|
3
3
|
export type ConnectAppOptions = {
|
|
4
|
-
/** The
|
|
5
|
-
*
|
|
6
|
-
readonly
|
|
4
|
+
/** The parent is the host. Serve the App with CSP `frame-ancestors` pinned
|
|
5
|
+
* to its allowed host so an unrelated site cannot become its parent. */
|
|
6
|
+
readonly host: "parent";
|
|
7
7
|
/** Cancels a pending connection attempt. */
|
|
8
8
|
readonly signal?: AbortSignal;
|
|
9
9
|
};
|
package/dist/connect.js
CHANGED
|
@@ -14,28 +14,23 @@ let liveConnection = false;
|
|
|
14
14
|
class PendingAppConnection {
|
|
15
15
|
options;
|
|
16
16
|
#appWindow;
|
|
17
|
-
#origin;
|
|
18
17
|
#port;
|
|
19
18
|
#settled = false;
|
|
20
19
|
#readyRetry;
|
|
21
20
|
#deadline;
|
|
22
21
|
constructor(options) {
|
|
23
22
|
this.options = options;
|
|
24
|
-
this.#origin = expectedOrigin(options.expectedHostOrigin);
|
|
25
23
|
this.#appWindow = globalThis.window;
|
|
26
24
|
}
|
|
27
25
|
start() {
|
|
28
26
|
performance.mark("fungi.app-open.agar-connect-start");
|
|
29
|
-
const origin = this.#origin;
|
|
30
|
-
if (!origin)
|
|
31
|
-
return Promise.reject(invalidOrigin());
|
|
32
27
|
const error = this.#startError();
|
|
33
28
|
if (error)
|
|
34
29
|
return Promise.reject(error);
|
|
35
30
|
liveConnection = true;
|
|
36
|
-
return this.#awaitConnection(
|
|
31
|
+
return this.#awaitConnection();
|
|
37
32
|
}
|
|
38
|
-
#awaitConnection(
|
|
33
|
+
#awaitConnection() {
|
|
39
34
|
return new Promise((resolve, reject) => {
|
|
40
35
|
const finish = (error, root) => {
|
|
41
36
|
this.#finish(error, root, onMessage, onAbort, resolve, reject);
|
|
@@ -44,14 +39,14 @@ class PendingAppConnection {
|
|
|
44
39
|
finish(cancelled());
|
|
45
40
|
};
|
|
46
41
|
const onMessage = (event) => {
|
|
47
|
-
this.#onMessage(event,
|
|
42
|
+
this.#onMessage(event, finish, onMessage);
|
|
48
43
|
};
|
|
49
44
|
this.#appWindow.addEventListener("message", onMessage);
|
|
50
45
|
this.options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
51
46
|
const notify = () => {
|
|
52
47
|
try {
|
|
53
48
|
performance.mark("fungi.app-open.agar-ready-sent");
|
|
54
|
-
this.#appWindow.parent.postMessage({ protocolVersion: APP_PROTOCOL_VERSION, type: APP_READY_TYPE },
|
|
49
|
+
this.#appWindow.parent.postMessage({ protocolVersion: APP_PROTOCOL_VERSION, type: APP_READY_TYPE }, "*");
|
|
55
50
|
}
|
|
56
51
|
catch (cause) {
|
|
57
52
|
reportCatchFailure("connect.notify-host", cause);
|
|
@@ -82,11 +77,12 @@ class PendingAppConnection {
|
|
|
82
77
|
}
|
|
83
78
|
resolve(root);
|
|
84
79
|
}
|
|
85
|
-
#onMessage(event,
|
|
80
|
+
#onMessage(event, finish, onMessage) {
|
|
86
81
|
if (!isConnectMessage(event.data))
|
|
87
82
|
return;
|
|
88
83
|
performance.mark("fungi.app-open.agar-connect-received");
|
|
89
|
-
if (event.source !== this.#appWindow.parent ||
|
|
84
|
+
if (event.source !== this.#appWindow.parent ||
|
|
85
|
+
expectedOrigin(event.origin) === undefined) {
|
|
90
86
|
finish(identityError());
|
|
91
87
|
return;
|
|
92
88
|
}
|
|
@@ -176,12 +172,6 @@ function identityError() {
|
|
|
176
172
|
retryable: false,
|
|
177
173
|
});
|
|
178
174
|
}
|
|
179
|
-
function invalidOrigin() {
|
|
180
|
-
return new AppError("expectedHostOrigin must be an origin", {
|
|
181
|
-
code: "INVALID_ARGUMENT",
|
|
182
|
-
retryable: false,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
175
|
const isConnectMessage = Schema.is(Schema.Struct({ type: Schema.Literal(APP_CONNECT_TYPE) }));
|
|
186
176
|
function notifyError(cause) {
|
|
187
177
|
return new AppError("Unable to notify the app host", {
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
/** Declaration-safe projection of the canonical Media diagnostic grammar. */
|
|
3
|
+
export type MediaProviderDiagnostic = Readonly<{
|
|
4
|
+
/** Allowlisted Workers AI category; never a provider message. */
|
|
5
|
+
category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
|
|
6
|
+
/** Four- or five-digit provider prefix, or null when absent. */
|
|
7
|
+
code: number | null;
|
|
8
|
+
}> | Readonly<{
|
|
9
|
+
/** A completed video result could not be downloaded. */
|
|
10
|
+
category: "result_download";
|
|
11
|
+
/** Native Response status, including zero. */
|
|
12
|
+
status: number;
|
|
13
|
+
}>;
|
|
14
|
+
/** Decode only safe provider facts, rejecting unmodeled private fields. */
|
|
15
|
+
export declare function decodeMediaProviderDiagnostic(input: unknown): MediaProviderDiagnostic;
|
|
16
|
+
/**
|
|
17
|
+
* Documentable wire projection of the canonical diagnostic grammar.
|
|
18
|
+
* Maintained converters preserve its closed objects and numeric bounds;
|
|
19
|
+
* decoding still returns the declaration-safe public diagnostic type.
|
|
20
|
+
*/
|
|
21
|
+
export declare const mediaProviderDiagnosticWireSchema: z.ZodPipe<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodTransform<Readonly<{
|
|
22
|
+
/** Allowlisted Workers AI category; never a provider message. */
|
|
23
|
+
category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
|
|
24
|
+
/** Four- or five-digit provider prefix, or null when absent. */
|
|
25
|
+
code: number | null;
|
|
26
|
+
}> | Readonly<{
|
|
27
|
+
/** A completed video result could not be downloaded. */
|
|
28
|
+
category: "result_download";
|
|
29
|
+
/** Native Response status, including zero. */
|
|
30
|
+
status: number;
|
|
31
|
+
}>, unknown>>;
|
|
32
|
+
/** Decode an ordinary Error name without retaining arbitrary upstream text. */
|
|
33
|
+
export declare function decodeMediaProviderErrorCategory(input: string | undefined): Exclude<MediaProviderDiagnostic["category"], "result_download">;
|
|
1
34
|
/** The closed set of reasons an AppError can state; every SDK and
|
|
2
35
|
* host-reported failure is tagged with exactly one of these codes. */
|
|
3
|
-
export
|
|
36
|
+
export declare const appErrorCodes: readonly ["CANCELLED", "CAPABILITY_DENIED", "CAPABILITY_REVOKED", "COMMAND_NOT_DECLARED", "CONFLICT", "DELIVERY_PENDING", "DISCONNECTED", "FILE_TOO_LARGE", "HOST_FAILURE", "IDENTITY", "INSUFFICIENT_CREDIT", "INVALID_ARGUMENT", "PROTOCOL", "SAFETY_FILTER_REFUSED", "SELECTION_CHANGED", "TIMEOUT"];
|
|
37
|
+
export type AppErrorCode = (typeof appErrorCodes)[number];
|
|
38
|
+
/** One pending-delivery refusal shared by admission and the shell. */
|
|
39
|
+
export type AppDeliveryPending = Readonly<{
|
|
40
|
+
code: Extract<AppErrorCode, "DELIVERY_PENDING">;
|
|
41
|
+
}>;
|
|
4
42
|
/** Required construction options for an AppError. */
|
|
5
43
|
export type AppErrorOptions = {
|
|
6
44
|
readonly cause?: unknown;
|
package/dist/errors.js
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { mediaProviderDiagnosticSchema, mediaProviderErrorCategorySchema, } from "./internal/media-diagnostic.js";
|
|
5
|
+
/** Decode only safe provider facts, rejecting unmodeled private fields. */
|
|
6
|
+
export function decodeMediaProviderDiagnostic(input) {
|
|
7
|
+
// Bidirectional projection check, erased from the Promise declaration graph.
|
|
8
|
+
return Schema.decodeUnknownSync(mediaProviderDiagnosticSchema, {
|
|
9
|
+
onExcessProperty: "error",
|
|
10
|
+
})(input);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Documentable wire projection of the canonical diagnostic grammar.
|
|
14
|
+
* Maintained converters preserve its closed objects and numeric bounds;
|
|
15
|
+
* decoding still returns the declaration-safe public diagnostic type.
|
|
16
|
+
*/
|
|
17
|
+
export const mediaProviderDiagnosticWireSchema = z
|
|
18
|
+
.fromJSONSchema(Schema.toJsonSchemaDocument(mediaProviderDiagnosticSchema, {
|
|
19
|
+
onExcessProperty: "error",
|
|
20
|
+
}).schema)
|
|
21
|
+
.transform(decodeMediaProviderDiagnostic);
|
|
22
|
+
/** Decode an ordinary Error name without retaining arbitrary upstream text. */
|
|
23
|
+
export function decodeMediaProviderErrorCategory(input) {
|
|
24
|
+
return (Option.getOrUndefined(Schema.decodeUnknownOption(mediaProviderErrorCategorySchema)(input)) ?? "Error");
|
|
25
|
+
}
|
|
26
|
+
/** The closed set of reasons an AppError can state; every SDK and
|
|
27
|
+
* host-reported failure is tagged with exactly one of these codes. */
|
|
28
|
+
export const appErrorCodes = [
|
|
29
|
+
"CANCELLED",
|
|
30
|
+
"CAPABILITY_DENIED",
|
|
31
|
+
"CAPABILITY_REVOKED",
|
|
32
|
+
"COMMAND_NOT_DECLARED",
|
|
33
|
+
"CONFLICT",
|
|
34
|
+
"DELIVERY_PENDING",
|
|
35
|
+
"DISCONNECTED",
|
|
36
|
+
"FILE_TOO_LARGE",
|
|
37
|
+
"HOST_FAILURE",
|
|
38
|
+
"IDENTITY",
|
|
39
|
+
"INSUFFICIENT_CREDIT",
|
|
40
|
+
"INVALID_ARGUMENT",
|
|
41
|
+
"PROTOCOL",
|
|
42
|
+
"SAFETY_FILTER_REFUSED",
|
|
43
|
+
"SELECTION_CHANGED",
|
|
44
|
+
"TIMEOUT",
|
|
45
|
+
];
|
|
46
|
+
const describedAppErrorSchema = Schema.Struct({
|
|
47
|
+
name: Schema.Literal("AppError"),
|
|
48
|
+
code: Schema.Literals(appErrorCodes),
|
|
49
|
+
retryable: Schema.Boolean,
|
|
50
|
+
});
|
|
1
51
|
/** The SDK's one typed failure. Carries a closed `code` naming the failure
|
|
2
52
|
* kind and an explicit `retryable` verdict, instead of leaving callers to
|
|
3
53
|
* guess either from the message or exception shape. */
|
|
@@ -35,6 +85,10 @@ const errorCopy = {
|
|
|
35
85
|
title: "File changed",
|
|
36
86
|
message: "This file changed elsewhere. Review it before saving again.",
|
|
37
87
|
},
|
|
88
|
+
DELIVERY_PENDING: {
|
|
89
|
+
title: "App delivery pending",
|
|
90
|
+
message: "The App hasn't finished getting ready. Try opening it again.",
|
|
91
|
+
},
|
|
38
92
|
DISCONNECTED: {
|
|
39
93
|
title: "Connection lost",
|
|
40
94
|
message: "Reopen the App to connect again.",
|
|
@@ -90,15 +144,18 @@ const errorCopy = {
|
|
|
90
144
|
* prerequisites, so they can accompany a non-retryable operation.
|
|
91
145
|
*/
|
|
92
146
|
export function describeAppError(cause, overrides) {
|
|
93
|
-
|
|
147
|
+
// The picked shell and host can be separate bundles of this same SDK.
|
|
148
|
+
const decoded = Schema.decodeUnknownOption(describedAppErrorSchema)(cause);
|
|
149
|
+
if (Option.isNone(decoded))
|
|
94
150
|
return { ...errorCopy.UNKNOWN, retryable: false };
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
151
|
+
const error = decoded.value;
|
|
152
|
+
const entry = errorCopy[error.code];
|
|
153
|
+
const copy = overrides?.[error.code] ?? entry;
|
|
154
|
+
const action = entry.action ?? (error.retryable ? "retry" : undefined);
|
|
98
155
|
const description = {
|
|
99
156
|
title: copy.title,
|
|
100
157
|
message: copy.message,
|
|
101
|
-
retryable:
|
|
158
|
+
retryable: error.retryable,
|
|
102
159
|
};
|
|
103
160
|
if (action)
|
|
104
161
|
return { ...description, action };
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type { ConnectAppOptions } from "./connect.js";
|
|
|
16
16
|
export { APP_CONNECT_TYPE, APP_PROTOCOL_VERSION, APP_READY_TYPE, } from "./internal/protocol.js";
|
|
17
17
|
export type { AppConnectMessage, AppReadyMessage, } from "./internal/protocol.js";
|
|
18
18
|
export { AppError } from "./errors.js";
|
|
19
|
+
export { appDeliveryPendingSchema } from "./internal/schemas.js";
|
|
20
|
+
export type { AppDeliveryPending } from "./errors.js";
|
|
19
21
|
export { statusActionAllowed, statusText } from "./status.js";
|
|
20
22
|
export { APP_BROWSER_SAVE_MAX_BYTES, APP_BROWSER_COPY_MAX_BYTES, APP_STATUS_ITEMS_MAX, APP_STATUS_OPTIONS_MAX, APP_STATUS_TEXT_MAX, } from "./ui.js";
|
|
21
23
|
export { parseStatusAction as parseAppStatusAction, parseStatusItems as parseAppStatusItems, } from "./internal/schemas.js";
|
|
@@ -30,3 +32,4 @@ export { APP_NAVIGATION_CONTRACT_VERSION, APP_NAVIGATION_CRUMBS_MAX, APP_NAVIGAT
|
|
|
30
32
|
export type { AppBackHandler, AppCrumb, AppHeaderCommand, AppHeaderStatus, AppNavigationActivation, AppNavigationActivationHandler, AppNavigationState, AppWindow, BeforeAppClose, HostNavigation, } from "./window.js";
|
|
31
33
|
export { appStorage, AppStorageError } from "./storage.js";
|
|
32
34
|
export type { AppStorage, AppStorageWatch, AppStorageWrite, AppStorageReset, ResolvedAppStorage, } from "./storage.js";
|
|
35
|
+
export { approvals } from "./approvals.js";
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,11 @@ export { connectApp } from "./connect.js";
|
|
|
8
8
|
/** Data exchanged by iframe and host before the RPC MessagePort takes over. */
|
|
9
9
|
export { APP_CONNECT_TYPE, APP_PROTOCOL_VERSION, APP_READY_TYPE, } from "./internal/protocol.js";
|
|
10
10
|
export { AppError } from "./errors.js";
|
|
11
|
+
export { appDeliveryPendingSchema } from "./internal/schemas.js";
|
|
11
12
|
export { statusActionAllowed, statusText } from "./status.js";
|
|
12
13
|
export { APP_BROWSER_SAVE_MAX_BYTES, APP_BROWSER_COPY_MAX_BYTES, APP_STATUS_ITEMS_MAX, APP_STATUS_OPTIONS_MAX, APP_STATUS_TEXT_MAX, } from "./ui.js";
|
|
13
14
|
export { parseStatusAction as parseAppStatusAction, parseStatusItems as parseAppStatusItems, } from "./internal/schemas.js";
|
|
14
15
|
export { resultRowSchemaV1 } from "@fungi.computer/gills";
|
|
15
16
|
export { APP_NAVIGATION_CONTRACT_VERSION, APP_NAVIGATION_CRUMBS_MAX, APP_NAVIGATION_LABEL_MAX, APP_NAVIGATION_MENU_MAX, APP_NAVIGATION_PRIMARY_MAX, APP_NAVIGATION_SIBLINGS_MAX, APP_NAVIGATION_STATUS_MAX, APP_NAVIGATION_TITLE_MAX, inertHostNavigation, navigationActivationAllowed, } from "./window.js";
|
|
16
17
|
export { appStorage, AppStorageError } from "./storage.js";
|
|
18
|
+
export { approvals } from "./approvals.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
/** Names emitted by the maintained Workers AI boundary, never arbitrary names. */
|
|
3
|
+
export declare const mediaProviderErrorCategorySchema: Schema.Literals<readonly ["InferenceUpstreamError", "AiInternalError", "TypeError", "AbortError", "TimeoutError", "Error"]>;
|
|
4
|
+
/** One canonical grammar for safe provider facts across Media transports. */
|
|
5
|
+
export declare const mediaProviderDiagnosticSchema: Schema.Union<readonly [Schema.Struct<{
|
|
6
|
+
readonly category: Schema.Literals<readonly ["InferenceUpstreamError", "AiInternalError", "TypeError", "AbortError", "TimeoutError", "Error"]>;
|
|
7
|
+
readonly code: Schema.NullOr<Schema.Int>;
|
|
8
|
+
}>, Schema.Struct<{
|
|
9
|
+
readonly category: Schema.Literal<"result_download">;
|
|
10
|
+
readonly status: Schema.Int;
|
|
11
|
+
}>]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
/** Names emitted by the maintained Workers AI boundary, never arbitrary names. */
|
|
3
|
+
export const mediaProviderErrorCategorySchema = Schema.Literals([
|
|
4
|
+
"InferenceUpstreamError",
|
|
5
|
+
"AiInternalError",
|
|
6
|
+
"TypeError",
|
|
7
|
+
"AbortError",
|
|
8
|
+
"TimeoutError",
|
|
9
|
+
"Error",
|
|
10
|
+
]);
|
|
11
|
+
/** One canonical grammar for safe provider facts across Media transports. */
|
|
12
|
+
export const mediaProviderDiagnosticSchema = Schema.Union([
|
|
13
|
+
Schema.Struct({
|
|
14
|
+
category: mediaProviderErrorCategorySchema,
|
|
15
|
+
code: Schema.NullOr(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 99_999 }))),
|
|
16
|
+
}),
|
|
17
|
+
Schema.Struct({
|
|
18
|
+
category: Schema.Literal("result_download"),
|
|
19
|
+
status: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 599 })),
|
|
20
|
+
}),
|
|
21
|
+
]);
|
|
@@ -12,6 +12,10 @@ import type { CapabilityGrantWire, AppHostReconfigurationWire, CommandInvocation
|
|
|
12
12
|
import type { RpcResult } from "./result.js";
|
|
13
13
|
import { type AppNavigationActivation } from "../window.js";
|
|
14
14
|
import { type AppConnectMessage, type AppReadyMessage, type CommandInvocationWire, type ConnectRequestWire, type ReservedChord, type VersionedFact } from "./protocol.js";
|
|
15
|
+
/** Effect projection of the error owner's declaration-safe pending refusal. */
|
|
16
|
+
export declare const appDeliveryPendingSchema: Schema.Struct<{
|
|
17
|
+
readonly code: Schema.Literals<readonly ["DELIVERY_PENDING"]>;
|
|
18
|
+
}>;
|
|
15
19
|
export declare function appInvocation(value: CommandInvocationWire, signal: AbortSignal): Readonly<AppCommandInvocation>;
|
|
16
20
|
export declare const parseCommand: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => Readonly<{
|
|
17
21
|
readonly id: string;
|
|
@@ -275,6 +279,7 @@ export declare const parseNavigationState: (input: unknown, options?: import("ef
|
|
|
275
279
|
primary?: readonly Readonly<{
|
|
276
280
|
id: string;
|
|
277
281
|
label: string;
|
|
282
|
+
icon?: "play";
|
|
278
283
|
variant?: "primary" | "outline" | "ghost";
|
|
279
284
|
tone?: "destructive";
|
|
280
285
|
disabled?: boolean;
|
|
@@ -282,6 +287,7 @@ export declare const parseNavigationState: (input: unknown, options?: import("ef
|
|
|
282
287
|
menu?: readonly Readonly<{
|
|
283
288
|
id: string;
|
|
284
289
|
label: string;
|
|
290
|
+
icon?: "play";
|
|
285
291
|
variant?: "primary" | "outline" | "ghost";
|
|
286
292
|
tone?: "destructive";
|
|
287
293
|
disabled?: boolean;
|
|
@@ -289,6 +295,7 @@ export declare const parseNavigationState: (input: unknown, options?: import("ef
|
|
|
289
295
|
status?: readonly Readonly<{
|
|
290
296
|
id: string;
|
|
291
297
|
label: string;
|
|
298
|
+
presentation?: "bead";
|
|
292
299
|
tone?: "neutral" | "info" | "warning" | "success" | "error";
|
|
293
300
|
}>[];
|
|
294
301
|
}>;
|