@fungi.computer/agar 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +34 -18
  2. package/dist/agent-message-meta.d.ts +21 -16
  3. package/dist/agent-message-meta.js +17 -6
  4. package/dist/agents.d.ts +86 -13
  5. package/dist/agents.js +48 -25
  6. package/dist/approvals.d.ts +5 -0
  7. package/dist/approvals.js +36 -0
  8. package/dist/apps.js +4 -4
  9. package/dist/backend-definition.d.ts +41 -4
  10. package/dist/backend-definition.js +70 -10
  11. package/dist/backend-refusal.d.ts +1 -0
  12. package/dist/backend-refusal.js +1 -0
  13. package/dist/backend.js +4 -4
  14. package/dist/channels.js +4 -4
  15. package/dist/computer-contracts.d.ts +11 -28
  16. package/dist/computer-contracts.js +10 -29
  17. package/dist/computer.js +16 -16
  18. package/dist/connect.d.ts +3 -3
  19. package/dist/connect.js +7 -17
  20. package/dist/errors.d.ts +39 -1
  21. package/dist/errors.js +62 -5
  22. package/dist/game-servers.js +2 -2
  23. package/dist/git-rpc.d.ts +23 -23
  24. package/dist/git-rpc.js +14 -16
  25. package/dist/git.d.ts +9 -9
  26. package/dist/git.js +8 -8
  27. package/dist/host.d.ts +3 -1
  28. package/dist/host.js +5 -1
  29. package/dist/index.d.ts +8 -4
  30. package/dist/index.js +5 -2
  31. package/dist/inference.js +2 -1
  32. package/dist/internal/app-session.js +37 -1
  33. package/dist/internal/media-diagnostic.d.ts +11 -0
  34. package/dist/internal/media-diagnostic.js +21 -0
  35. package/dist/internal/protocol.d.ts +4 -4
  36. package/dist/internal/schemas.d.ts +40 -7
  37. package/dist/internal/schemas.js +122 -35
  38. package/dist/marketplace.js +7 -5
  39. package/dist/media-json.d.ts +14 -0
  40. package/dist/media-json.js +1 -0
  41. package/dist/media-request-id.d.ts +1 -1
  42. package/dist/media-request-id.js +2 -2
  43. package/dist/media.d.ts +11 -0
  44. package/dist/media.js +8 -2
  45. package/dist/method-presentation.d.ts +7 -0
  46. package/dist/method-presentation.js +14 -0
  47. package/dist/method-refusal.d.ts +5 -5
  48. package/dist/method-refusal.js +4 -4
  49. package/dist/query.d.ts +2 -2
  50. package/dist/react-mount.d.ts +7 -1
  51. package/dist/react-mount.js +28 -8
  52. package/dist/react.d.ts +14 -2
  53. package/dist/react.js +40 -4
  54. package/dist/read-copy.d.ts +1 -1
  55. package/dist/read-copy.js +3 -6
  56. package/dist/storage.js +1 -1
  57. package/dist/test-kit/git.js +19 -2
  58. package/dist/window.d.ts +68 -0
  59. package/dist/window.js +31 -0
  60. package/docs/api/README.md +15 -15
  61. package/docs/api/backend-definition.md +83 -9
  62. package/docs/api/backend.md +45 -38
  63. package/docs/api/errors.md +104 -0
  64. package/docs/api/git-rpc.md +35 -35
  65. package/docs/api/git.md +66 -80
  66. package/docs/api/index.md +851 -143
  67. package/docs/api/media-json.md +32 -59
  68. package/docs/api/media-request-id.md +1 -1
  69. package/docs/api/method-presentation.md +21 -0
  70. package/docs/api/modules.md +1 -0
  71. package/docs/api/query.md +1 -1
  72. package/docs/api/react.md +23 -10
  73. package/package.json +16 -6
@@ -5,6 +5,7 @@ import * as Schema from "effect/Schema";
5
5
  */
6
6
  import { newMessagePortRpcSession, RpcStub, RpcTarget, } from "@fungi.computer/capnweb";
7
7
  import { statusActionAllowed } from "../status.js";
8
+ import { navigationActivationAllowed } from "../window.js";
8
9
  import { AppError } from "../errors.js";
9
10
  import { reportCatchFailure } from "../failure.js";
10
11
  import { createLocalBindingState, createReservedChordState, installBrowserBridge, } from "./browser-bridge.js";
@@ -13,7 +14,7 @@ import { createCapabilityDirectory, disposeRpcValue, } from "./capabilities.js";
13
14
  import { createAppCommands } from "./commands.js";
14
15
  import { APP_PROTOCOL_VERSION, } from "./protocol.js";
15
16
  import { callRpc, rpcFailure, rpcSuccess, unwrapRpcResult } from "./result.js";
16
- import { parseBrowserAction, parseBrowserActionOutcomeResult, parseBootstrapResult, parseHostReconfiguration, parseContext, parseErrorData, parseFact, parseAgentSessionSelection, parseIdentity, parseLifecycle, parseNavigation, parseStatusAction, parseStatusItems, parseNavigationStateInput, parsePermissionOutcomeResult, parsePermissionRequest, parseReservedChords, parseTheme, parseToast, parseVoidResult, } from "./schemas.js";
17
+ import { parseBrowserAction, parseBrowserActionOutcomeResult, parseBootstrapResult, parseHostReconfiguration, parseContext, parseErrorData, parseFact, parseAgentSessionSelection, parseIdentity, parseLifecycle, parseNavigation, parseNavigationActivation, parseNavigationState, parseStatusAction, parseStatusItems, parseNavigationStateInput, parsePermissionOutcomeResult, parsePermissionRequest, parseReservedChords, parseTheme, parseToast, parseVoidResult, } from "./schemas.js";
17
18
  const rpcStubSchema = Schema.declare((value) => value instanceof RpcStub);
18
19
  import { createReadonlyStore } from "./store.js";
19
20
  class AppClientTarget extends RpcTarget {
@@ -57,6 +58,21 @@ class AppClientTarget extends RpcTarget {
57
58
  return rpcFailure(cause);
58
59
  }
59
60
  }
61
+ async activateNavigation(value) {
62
+ try {
63
+ if (this.#disposed)
64
+ throw disconnected();
65
+ const activation = parseNavigationActivation(value);
66
+ // A shell's stale copy can still send a crumb or command the App has
67
+ // since dropped; only what it published now reaches the App.
68
+ if (!navigationActivationAllowed(this.#state.navigation, activation))
69
+ return rpcSuccess(false);
70
+ return rpcSuccess(await (this.#state.activate?.(activation) ?? false));
71
+ }
72
+ catch (cause) {
73
+ return rpcFailure(cause);
74
+ }
75
+ }
60
76
  async activateStatus(value) {
61
77
  try {
62
78
  if (this.#disposed)
@@ -231,6 +247,7 @@ function createBrokenPromise(remote) {
231
247
  function createClientState() {
232
248
  return {
233
249
  statusItems: [],
250
+ navigation: { canGoBack: false },
234
251
  bindings: createLocalBindingState(),
235
252
  chords: createReservedChordState([]),
236
253
  context: createReadonlyStore({
@@ -293,6 +310,7 @@ function createRootRuntime(options, identity, capabilities) {
293
310
  () => {
294
311
  options.state.beforeClose = undefined;
295
312
  options.state.back = undefined;
313
+ options.state.activate = undefined;
296
314
  options.state.statusAction = undefined;
297
315
  },
298
316
  () => {
@@ -370,8 +388,26 @@ function createAppWindow(state, remote, assertConnected) {
370
388
  assertConnected();
371
389
  const wire = parseNavigationStateInput(input);
372
390
  state.canGoBack = wire.canGoBack;
391
+ state.navigation = parseNavigationState(wire);
373
392
  await callRpc(remote.reportNavigation(wire), parseVoidResult);
374
393
  },
394
+ onActivate(handler) {
395
+ assertConnected();
396
+ if (state.activate !== undefined)
397
+ throw new AppError("The App already owns navigation activations", {
398
+ code: "INVALID_ARGUMENT",
399
+ retryable: false,
400
+ });
401
+ state.activate = handler;
402
+ let released = false;
403
+ return () => {
404
+ if (released)
405
+ return;
406
+ released = true;
407
+ if (state.activate === handler)
408
+ state.activate = undefined;
409
+ };
410
+ },
375
411
  onBack(handler) {
376
412
  assertConnected();
377
413
  if (state.back !== undefined)
@@ -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
+ ]);
@@ -2,7 +2,7 @@ import type { AppCommand } from "../commands.js";
2
2
  import type { WhistleExecutionOrigin, WhistleJsonValue } from "@fungi.computer/whistle";
3
3
  import type { AppContext, AppIdentity, AppLifecycle, FocusState } from "../lifecycle.js";
4
4
  import type { AppBrowserAction, AppBrowserActionOutcome, AgentSessionSelection, AppPermissionOutcome, AppPermissionRequest, AppStatusAction, AppStatusItem, FileNavigationInput, ToastInput } from "../ui.js";
5
- import type { APP_NAVIGATION_CONTRACT_VERSION } from "../window.js";
5
+ import type { APP_NAVIGATION_CONTRACT_VERSION, AppNavigationActivation, AppNavigationState } from "../window.js";
6
6
  import type { AppErrorData } from "./app-error.js";
7
7
  import type { RpcResult } from "./result.js";
8
8
  import type { WhistleFailure } from "@fungi.computer/whistle";
@@ -17,6 +17,8 @@ export type AppClientRpc = {
17
17
  requestClose(): Promise<RpcResult<boolean>>;
18
18
  /** Ask the App to go back one step in its own history; `true` when it did. */
19
19
  requestBack(): Promise<RpcResult<boolean>>;
20
+ /** The person used a crumb or header command; `true` when the App acted. */
21
+ activateNavigation(activation: AppNavigationActivation): Promise<RpcResult<boolean>>;
20
22
  /** The person used one of the App's status items; `true` when it acted. */
21
23
  activateStatus(action: AppStatusAction): Promise<RpcResult<boolean>>;
22
24
  updateContext(context: VersionedFact<AppContext>): Promise<RpcResult<void>>;
@@ -51,9 +53,7 @@ export type AppHostRpc = {
51
53
  /** The versioned navigation contract an App reports to its host. */
52
54
  export type NavigationStateWire = Readonly<{
53
55
  contractVersion: typeof APP_NAVIGATION_CONTRACT_VERSION;
54
- canGoBack: boolean;
55
- title?: string;
56
- }>;
56
+ }> & AppNavigationState;
57
57
  /** The App iframe's handshake message announcing it is ready to connect. */
58
58
  export type AppReadyMessage = {
59
59
  readonly protocolVersion: typeof APP_PROTOCOL_VERSION;
@@ -8,10 +8,14 @@ import type { WhistleJsonValue } from "@fungi.computer/whistle";
8
8
  import type { AppContext, AppIdentity, AppLifecycle, FocusState } from "../lifecycle.js";
9
9
  import type { AppBrowserAction, AppBrowserActionRefusal, AppBrowserActionOutcome, AppPermissionOutcome, AppStatusItem, FileNavigationInput, ToastInput } from "../ui.js";
10
10
  import type { AppErrorData } from "./app-error.js";
11
- import type { CapabilityGrantWire, AppHostReconfigurationWire, CommandInvocationResultWire, HostBootstrapWire, RegisteredCommandWire } from "./protocol.js";
11
+ import type { CapabilityGrantWire, AppHostReconfigurationWire, CommandInvocationResultWire, HostBootstrapWire, NavigationStateWire, RegisteredCommandWire } from "./protocol.js";
12
12
  import type { RpcResult } from "./result.js";
13
- import { APP_NAVIGATION_CONTRACT_VERSION } from "../window.js";
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;
@@ -38,6 +42,7 @@ export declare const parseCommand: (input: unknown, options?: import("effect/Sch
38
42
  } | undefined;
39
43
  readonly slash?: {
40
44
  readonly aliases: readonly string[];
45
+ readonly consumesDraft?: boolean | undefined;
41
46
  readonly order?: number | undefined;
42
47
  readonly arguments?: {
43
48
  readonly hint: string;
@@ -263,13 +268,41 @@ export declare const parseNavigation: (input: unknown, options?: import("effect/
263
268
  export declare const parseNavigationState: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => Readonly<{
264
269
  canGoBack: boolean;
265
270
  title?: string;
271
+ crumbs?: readonly Readonly<{
272
+ id: string;
273
+ label: string;
274
+ siblings?: readonly Readonly<{
275
+ id: string;
276
+ label: string;
277
+ }>[];
278
+ }>[];
279
+ primary?: readonly Readonly<{
280
+ id: string;
281
+ label: string;
282
+ icon?: "play";
283
+ variant?: "primary" | "outline" | "ghost";
284
+ tone?: "destructive";
285
+ disabled?: boolean;
286
+ }>[];
287
+ menu?: readonly Readonly<{
288
+ id: string;
289
+ label: string;
290
+ icon?: "play";
291
+ variant?: "primary" | "outline" | "ghost";
292
+ tone?: "destructive";
293
+ disabled?: boolean;
294
+ }>[];
295
+ status?: readonly Readonly<{
296
+ id: string;
297
+ label: string;
298
+ presentation?: "bead";
299
+ tone?: "neutral" | "info" | "warning" | "success" | "error";
300
+ }>[];
266
301
  }>;
267
302
  /** App edge: trim the title, drop an empty one, then check the wire bounds. */
268
- export declare const parseNavigationStateInput: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => Readonly<{
269
- contractVersion: typeof APP_NAVIGATION_CONTRACT_VERSION;
270
- canGoBack: boolean;
271
- title?: string;
272
- }>;
303
+ export declare const parseNavigationStateInput: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => NavigationStateWire;
304
+ /** Host to App: one crumb or command the person used. */
305
+ export declare const parseNavigationActivation: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => AppNavigationActivation;
273
306
  export declare const parseAgentSessionSelection: (input: unknown, options?: import("effect/SchemaAST").ParseOptions | undefined) => Readonly<{
274
307
  agentId: string;
275
308
  sessionId: string;
@@ -9,27 +9,10 @@ import { flow } from "effect/Function";
9
9
  import { parseCommandDefinition, parseJsonValue, } from "@fungi.computer/whistle/wire";
10
10
  import { isUnsafeStatusCodeUnit } from "../status.js";
11
11
  import { 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";
12
- import { APP_NAVIGATION_CONTRACT_VERSION, APP_NAVIGATION_TITLE_MAX, } from "../window.js";
13
- import { AppError } from "../errors.js";
12
+ import { 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, } from "../window.js";
13
+ import { AppError, appErrorCodes } from "../errors.js";
14
14
  import { parseFailure } from "@fungi.computer/whistle/wire";
15
15
  import { APP_CONNECT_TYPE, APP_PROTOCOL_VERSION, APP_READY_TYPE, } from "./protocol.js";
16
- const errorCodes = [
17
- "PROTOCOL",
18
- "IDENTITY",
19
- "INSUFFICIENT_CREDIT",
20
- "FILE_TOO_LARGE",
21
- "CAPABILITY_DENIED",
22
- "CAPABILITY_REVOKED",
23
- "COMMAND_NOT_DECLARED",
24
- "CONFLICT",
25
- "INVALID_ARGUMENT",
26
- "TIMEOUT",
27
- "CANCELLED",
28
- "DISCONNECTED",
29
- "HOST_FAILURE",
30
- "SELECTION_CHANGED",
31
- "SAFETY_FILTER_REFUSED",
32
- ];
33
16
  const boundedText = (maximum) => Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(maximum)));
34
17
  const boundedOptionalText = (maximum) => Schema.optional(boundedText(maximum));
35
18
  const safeInteger = Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.makeFilter(Number.isSafeInteger, {
@@ -175,15 +158,64 @@ const navigationSchema = Schema.Struct({
175
158
  const navigationTitleSchema = boundedText(APP_NAVIGATION_TITLE_MAX).pipe(Schema.check(Schema.makeFilter((title) => title.trim() === title, {
176
159
  message: "Expected a trimmed title",
177
160
  })));
161
+ const navigationIdSchema = boundedText(64);
162
+ const navigationLabelSchema = boundedText(APP_NAVIGATION_LABEL_MAX);
163
+ /** Ids name what a person used; repeats would make an activation ambiguous. */
164
+ const uniqueIds = (items) => new Set(items.map((item) => item.id)).size === items.length;
165
+ const crumbSchema = Schema.Struct({
166
+ id: navigationIdSchema,
167
+ label: navigationLabelSchema,
168
+ siblings: Schema.optional(Schema.Array(Schema.Struct({ id: navigationIdSchema, label: navigationLabelSchema })).pipe(Schema.check(Schema.isMaxLength(APP_NAVIGATION_SIBLINGS_MAX)), Schema.check(Schema.makeFilter(uniqueIds, {
169
+ message: "Expected unique crumb sibling ids",
170
+ })))),
171
+ });
172
+ const headerCommandSchema = Schema.Struct({
173
+ id: navigationIdSchema,
174
+ label: navigationLabelSchema,
175
+ icon: Schema.optional(Schema.Literal("play")),
176
+ variant: Schema.optional(Schema.Literals(["primary", "outline", "ghost"])),
177
+ tone: Schema.optional(Schema.Literal("destructive")),
178
+ disabled: Schema.optional(Schema.Boolean),
179
+ });
180
+ const commandsSchema = (maximum) => Schema.Array(headerCommandSchema).pipe(Schema.check(Schema.isMaxLength(maximum)), Schema.check(Schema.makeFilter(uniqueIds, {
181
+ message: "Expected unique header command ids",
182
+ })));
183
+ const headerStatusSchema = Schema.Struct({
184
+ id: navigationIdSchema,
185
+ label: navigationLabelSchema,
186
+ presentation: Schema.optional(Schema.Literal("bead")),
187
+ tone: Schema.optional(Schema.Literals(["neutral", "info", "warning", "success", "error"])),
188
+ });
189
+ const navigationFields = {
190
+ status: Schema.optional(Schema.Array(headerStatusSchema).pipe(Schema.check(Schema.isMaxLength(APP_NAVIGATION_STATUS_MAX)), Schema.check(Schema.makeFilter(uniqueIds, {
191
+ message: "Expected unique header status ids",
192
+ })))),
193
+ crumbs: Schema.optional(Schema.Array(crumbSchema).pipe(Schema.check(Schema.isMaxLength(APP_NAVIGATION_CRUMBS_MAX)), Schema.check(Schema.makeFilter(uniqueIds, { message: "Expected unique crumb ids" })))),
194
+ primary: Schema.optional(commandsSchema(APP_NAVIGATION_PRIMARY_MAX)),
195
+ menu: Schema.optional(commandsSchema(APP_NAVIGATION_MENU_MAX)),
196
+ };
178
197
  const navigationStateWireSchema = Schema.Struct({
179
198
  contractVersion: Schema.Literal(APP_NAVIGATION_CONTRACT_VERSION),
180
199
  canGoBack: Schema.Boolean,
181
200
  title: Schema.optional(navigationTitleSchema),
201
+ ...navigationFields,
182
202
  });
183
203
  const navigationStateInputSchema = Schema.Struct({
184
204
  canGoBack: Schema.Boolean,
185
205
  title: Schema.optional(Schema.String),
206
+ ...navigationFields,
186
207
  });
208
+ const navigationActivationSchema = Schema.Union([
209
+ Schema.Struct({
210
+ kind: Schema.Literal("crumb"),
211
+ crumbId: navigationIdSchema,
212
+ siblingId: Schema.optional(navigationIdSchema),
213
+ }),
214
+ Schema.Struct({
215
+ kind: Schema.Literal("action"),
216
+ actionId: navigationIdSchema,
217
+ }),
218
+ ]);
187
219
  const agentSessionSelectionSchema = Schema.NullOr(Schema.Struct({
188
220
  agentId: boundedText(256),
189
221
  sessionId: boundedText(256),
@@ -211,8 +243,13 @@ const toastSchema = Schema.Struct({
211
243
  message: boundedText(512),
212
244
  tone: Schema.optional(Schema.Literals(["info", "success", "warning", "error"])),
213
245
  });
246
+ const appErrorCodeSchema = Schema.Literals(appErrorCodes);
247
+ /** Effect projection of the error owner's declaration-safe pending refusal. */
248
+ export const appDeliveryPendingSchema = Schema.Struct({
249
+ code: appErrorCodeSchema.pick(["DELIVERY_PENDING"]),
250
+ });
214
251
  const errorSchema = Schema.Struct({
215
- code: Schema.Literals(errorCodes),
252
+ code: appErrorCodeSchema,
216
253
  message: boundedText(512),
217
254
  retryable: Schema.Boolean,
218
255
  });
@@ -339,25 +376,75 @@ export const parseStatusAction = decoder(Schema.Struct({
339
376
  ? { ...action }
340
377
  : { ...action, anchor: frozen({ ...action.anchor }) }), "INVALID_ARGUMENT");
341
378
  export const parseNavigation = decoder(navigationSchema, (value) => frozen({ ...value }), "INVALID_ARGUMENT");
379
+ function statusOf(item) {
380
+ let copy = { id: item.id, label: item.label };
381
+ if (item.presentation !== undefined)
382
+ copy = { ...copy, presentation: item.presentation };
383
+ if (item.tone !== undefined)
384
+ copy = { ...copy, tone: item.tone };
385
+ return frozen(copy);
386
+ }
387
+ function crumbOf(crumb) {
388
+ const copy = { id: crumb.id, label: crumb.label };
389
+ if (crumb.siblings === undefined)
390
+ return frozen(copy);
391
+ return frozen({
392
+ ...copy,
393
+ siblings: frozen(crumb.siblings.map((item) => frozen({ ...item }))),
394
+ });
395
+ }
396
+ function commandOf(command) {
397
+ let copy = { id: command.id, label: command.label };
398
+ if (command.icon !== undefined)
399
+ copy = { ...copy, icon: command.icon };
400
+ if (command.variant !== undefined)
401
+ copy = { ...copy, variant: command.variant };
402
+ if (command.tone !== undefined)
403
+ copy = { ...copy, tone: command.tone };
404
+ if (command.disabled !== undefined)
405
+ copy = { ...copy, disabled: command.disabled };
406
+ return frozen(copy);
407
+ }
408
+ /** One report's fields, each present only when the App sent it. */
409
+ function navigationOf(value) {
410
+ let state = { canGoBack: value.canGoBack };
411
+ if (value.title !== undefined)
412
+ state = { ...state, title: value.title };
413
+ if (value.crumbs !== undefined)
414
+ state = { ...state, crumbs: frozen(value.crumbs.map(crumbOf)) };
415
+ if (value.primary !== undefined)
416
+ state = { ...state, primary: frozen(value.primary.map(commandOf)) };
417
+ if (value.menu !== undefined)
418
+ state = { ...state, menu: frozen(value.menu.map(commandOf)) };
419
+ if (value.status !== undefined)
420
+ state = { ...state, status: frozen(value.status.map(statusOf)) };
421
+ return frozen(state);
422
+ }
342
423
  /** Host edge: one App report, projected without its contract version. */
343
- export const parseNavigationState = decoder(navigationStateWireSchema, (value) => frozen(value.title === undefined
344
- ? { canGoBack: value.canGoBack }
345
- : { canGoBack: value.canGoBack, title: value.title }), "INVALID_ARGUMENT");
424
+ export const parseNavigationState = decoder(navigationStateWireSchema, navigationOf, "INVALID_ARGUMENT");
346
425
  /** App edge: trim the title, drop an empty one, then check the wire bounds. */
347
426
  export const parseNavigationStateInput = decoder(navigationStateInputSchema, (value) => {
348
- const title = value.title?.trim();
349
- return parseNavigationStateWire(title === undefined || title.length === 0
350
- ? {
351
- contractVersion: APP_NAVIGATION_CONTRACT_VERSION,
352
- canGoBack: value.canGoBack,
353
- }
354
- : {
355
- contractVersion: APP_NAVIGATION_CONTRACT_VERSION,
356
- canGoBack: value.canGoBack,
357
- title,
358
- });
427
+ const trimmed = value.title?.trim();
428
+ return parseNavigationStateWire({
429
+ ...navigationOf({
430
+ ...value,
431
+ title: trimmed === undefined || trimmed.length === 0 ? undefined : trimmed,
432
+ }),
433
+ contractVersion: APP_NAVIGATION_CONTRACT_VERSION,
434
+ });
435
+ }, "INVALID_ARGUMENT");
436
+ const parseNavigationStateWire = decoder(navigationStateWireSchema, (value) => frozen({
437
+ ...navigationOf(value),
438
+ contractVersion: APP_NAVIGATION_CONTRACT_VERSION,
439
+ }), "INVALID_ARGUMENT");
440
+ /** Host to App: one crumb or command the person used. */
441
+ export const parseNavigationActivation = decoder(navigationActivationSchema, (value) => {
442
+ if (value.kind === "action")
443
+ return frozen({ ...value });
444
+ return frozen(value.siblingId === undefined
445
+ ? { kind: value.kind, crumbId: value.crumbId }
446
+ : value);
359
447
  }, "INVALID_ARGUMENT");
360
- const parseNavigationStateWire = decoder(navigationStateWireSchema, (value) => frozen({ ...value }), "INVALID_ARGUMENT");
361
448
  export const parseAgentSessionSelection = decoder(agentSessionSelectionSchema, (value) => value === null ? null : frozen({ ...value }), "INVALID_ARGUMENT");
362
449
  export const parsePermissionRequest = decoder(permissionRequestSchema, (value) => frozen({
363
450
  agents: frozen([...new Set(value.agents ?? [])]),
@@ -72,19 +72,21 @@ export const marketplaceAcquisitionSchema = Schema.Union([
72
72
  }),
73
73
  ]);
74
74
  /** Read the Marketplace gallery as the Team that mounted this App. */
75
- export const marketplaceBrowse = defineCapability("marketplace.browse", 1, rpcHandleSchema({
76
- list: rpcMethod(rpcDataSchema(z.unknown().transform((value) => decodeMarketplaceBrowseResult(value)))),
77
- }));
75
+ export const marketplaceBrowse = defineCapability("marketplace.browse", 1, z.lazy(() => rpcHandleSchema({
76
+ list: rpcMethod(rpcDataSchema(z
77
+ .unknown()
78
+ .transform((value) => decodeMarketplaceBrowseResult(value)))),
79
+ })));
78
80
  /**
79
81
  * Acquire a listing for the mounting Team. Free listings install at once;
80
82
  * paid listings return a hosted Checkout URL and install after payment.
81
83
  * The host re-checks Team authority and entitlement on every call.
82
84
  */
83
- export const marketplacePurchase = defineCapability("marketplace.purchase", 1, rpcHandleSchema({
85
+ export const marketplacePurchase = defineCapability("marketplace.purchase", 1, z.lazy(() => rpcHandleSchema({
84
86
  acquire: rpcMethod(rpcDataSchema(z
85
87
  .unknown()
86
88
  .transform((value) => Schema.decodeUnknownSync(marketplaceAcquisitionSchema)(value)))),
87
- }));
89
+ })));
88
90
  /** Anonymous discovery only; acquisition and launch still require Team admission. */
89
91
  export async function browsePublicMarketplace(hubOrigin, category, request = fetch) {
90
92
  const url = new URL("/api/public/marketplace/listings", hubOrigin);
@@ -110,6 +110,13 @@ export declare const mediaGenerationJsonOutcomeSchema: Schema.Union<readonly [Sc
110
110
  readonly failure: Schema.Struct<{
111
111
  readonly message: Schema.String;
112
112
  readonly stage: Schema.optional<Schema.Literals<readonly ["admission", "generation-authorization", "settings", "estimate", "billing", "operation", "provider", "normalization", "artifact", "destination-authorization", "destination-write"]>>;
113
+ readonly provider: Schema.optional<Schema.Union<readonly [Schema.Struct<{
114
+ readonly category: Schema.Literals<readonly ["InferenceUpstreamError", "AiInternalError", "TypeError", "AbortError", "TimeoutError", "Error"]>;
115
+ readonly code: Schema.NullOr<Schema.Int>;
116
+ }>, Schema.Struct<{
117
+ readonly category: Schema.Literal<"result_download">;
118
+ readonly status: Schema.Int;
119
+ }>]>>;
113
120
  readonly code: Schema.Literals<readonly ["CANCELLED", "CAPABILITY_DENIED", "CAPABILITY_REVOKED", "DISCONNECTED", "HOST_FAILURE", "IDENTITY", "INSUFFICIENT_CREDIT", "INVALID_ARGUMENT", "PROTOCOL", "SAFETY_FILTER_REFUSED", "TIMEOUT"]>;
114
121
  readonly retryable: Schema.Boolean;
115
122
  }>;
@@ -139,6 +146,13 @@ export declare const mediaGenerationJsonReplySchema: Schema.Union<readonly [Sche
139
146
  readonly failure: Schema.Struct<{
140
147
  readonly message: Schema.String;
141
148
  readonly stage: Schema.optional<Schema.Literals<readonly ["admission", "generation-authorization", "settings", "estimate", "billing", "operation", "provider", "normalization", "artifact", "destination-authorization", "destination-write"]>>;
149
+ readonly provider: Schema.optional<Schema.Union<readonly [Schema.Struct<{
150
+ readonly category: Schema.Literals<readonly ["InferenceUpstreamError", "AiInternalError", "TypeError", "AbortError", "TimeoutError", "Error"]>;
151
+ readonly code: Schema.NullOr<Schema.Int>;
152
+ }>, Schema.Struct<{
153
+ readonly category: Schema.Literal<"result_download">;
154
+ readonly status: Schema.Int;
155
+ }>]>>;
142
156
  readonly code: Schema.Literals<readonly ["CANCELLED", "CAPABILITY_DENIED", "CAPABILITY_REVOKED", "DISCONNECTED", "HOST_FAILURE", "IDENTITY", "INSUFFICIENT_CREDIT", "INVALID_ARGUMENT", "PROTOCOL", "SAFETY_FILTER_REFUSED", "TIMEOUT"]>;
143
157
  readonly retryable: Schema.Boolean;
144
158
  }>;
@@ -165,6 +165,7 @@ export class MediaGenerationAttempt extends RpcTarget {
165
165
  retryable: true,
166
166
  },
167
167
  };
168
+ // Preserve the decoded failure intact, including safe provider facts.
168
169
  return response;
169
170
  }
170
171
  finally {
@@ -4,4 +4,4 @@ export declare const mediaRequestIds: Readonly<{
4
4
  readonly request: import("@fungi.computer/cairn/id").IdCodec<"hub-media-request", "request", 1>;
5
5
  }>;
6
6
  /** Public wire grammar for the canonical retry identity, visible in App JSON schemas. */
7
- export declare const mediaRequestIdSchema: z.ZodString;
7
+ export declare const mediaRequestIdSchema: z.ZodLazy<z.ZodString>;
@@ -10,7 +10,7 @@ export const mediaRequestIds = defineIdFamily({
10
10
  },
11
11
  });
12
12
  /** Public wire grammar for the canonical retry identity, visible in App JSON schemas. */
13
- export const mediaRequestIdSchema = z
13
+ export const mediaRequestIdSchema = z.lazy(() => z
14
14
  .string()
15
15
  .length(35)
16
- .regex(/^media_request_[a-z0-9]{21}$/);
16
+ .regex(/^media_request_[a-z0-9]{21}$/));
package/dist/media.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as Schema from "effect/Schema";
2
+ import type { MediaProviderDiagnostic } from "./errors.js";
2
3
  export { mediaLibraryRequestSchema, mediaLibraryPathSchema, mediaLibraryResponseSchema, } from "./media-library.js";
3
4
  import type { CapabilityContract, CapabilityDirectory } from "./capabilities.js";
4
5
  import { AppError } from "./errors.js";
@@ -41,6 +42,8 @@ export type MediaGenerationFailure = Schema.Schema.Type<typeof mediaGenerationFa
41
42
  /** A decoded Media failure; null stage means the host could not identify it. */
42
43
  export declare class MediaGenerationError extends AppError {
43
44
  readonly stage: NonNullable<MediaGenerationFailure["stage"]> | null;
45
+ /** Bounded provider facts only; never the original provider cause. */
46
+ readonly provider: MediaProviderDiagnostic | undefined;
44
47
  constructor(failure: MediaGenerationFailure);
45
48
  }
46
49
  /** Transport-safe result from the host-owned generation attempt. */
@@ -341,6 +344,14 @@ export type MediaVideoResult = Schema.Schema.Type<typeof mediaVideoResultSchema>
341
344
  export declare const mediaGenerationFailureSchema: Schema.Struct<{
342
345
  readonly message: Schema.String;
343
346
  readonly stage: Schema.optional<Schema.Literals<readonly ["admission", "generation-authorization", "settings", "estimate", "billing", "operation", "provider", "normalization", "artifact", "destination-authorization", "destination-write"]>>;
347
+ /** Safe provider facts decoded by the one canonical Media grammar. */
348
+ readonly provider: Schema.optional<Schema.Union<readonly [Schema.Struct<{
349
+ readonly category: Schema.Literals<readonly ["InferenceUpstreamError", "AiInternalError", "TypeError", "AbortError", "TimeoutError", "Error"]>;
350
+ readonly code: Schema.NullOr<Schema.Int>;
351
+ }>, Schema.Struct<{
352
+ readonly category: Schema.Literal<"result_download">;
353
+ readonly status: Schema.Int;
354
+ }>]>>;
344
355
  readonly code: Schema.Literals<readonly ["CANCELLED", "CAPABILITY_DENIED", "CAPABILITY_REVOKED", "DISCONNECTED", "HOST_FAILURE", "IDENTITY", "INSUFFICIENT_CREDIT", "INVALID_ARGUMENT", "PROTOCOL", "SAFETY_FILTER_REFUSED", "TIMEOUT"]>;
345
356
  readonly retryable: Schema.Boolean;
346
357
  }>;
package/dist/media.js CHANGED
@@ -54,18 +54,22 @@ import * as z from "zod";
54
54
  import { defineCapability } from "./capabilities.js";
55
55
  import { rpcDataSchema, rpcHandleSchema, rpcMethod, rpcVoid, } from "./internal/rpc-contract.js";
56
56
  import * as Schema from "effect/Schema";
57
+ import { mediaProviderDiagnosticSchema } from "./internal/media-diagnostic.js";
57
58
  export { mediaLibraryRequestSchema, mediaLibraryPathSchema, mediaLibraryResponseSchema, } from "./media-library.js";
58
59
  import { AppError } from "./errors.js";
59
60
  import { ownedRpcValue } from "./internal/capabilities.js";
60
61
  /** A decoded Media failure; null stage means the host could not identify it. */
61
62
  export class MediaGenerationError extends AppError {
62
63
  stage;
64
+ /** Bounded provider facts only; never the original provider cause. */
65
+ provider;
63
66
  constructor(failure) {
64
67
  super(failure.message, {
65
68
  code: failure.code,
66
69
  retryable: failure.retryable,
67
70
  });
68
71
  this.stage = failure.stage ?? null;
72
+ this.provider = failure.provider;
69
73
  }
70
74
  }
71
75
  /** One narrow Media capability: discover the retained catalog and generate. */
@@ -273,6 +277,8 @@ export const mediaVideoResultSchema = Schema.Struct({
273
277
  export const mediaGenerationFailureSchema = Schema.Struct({
274
278
  message: text(512),
275
279
  stage: Schema.optional(Schema.Literals(mediaFailureStages)),
280
+ /** Safe provider facts decoded by the one canonical Media grammar. */
281
+ provider: Schema.optional(mediaProviderDiagnosticSchema),
276
282
  code: Schema.Literals([
277
283
  "CANCELLED",
278
284
  "CAPABILITY_DENIED",
@@ -327,7 +333,7 @@ const mediaAttemptHandleSchema = rpcHandleSchema({
327
333
  /** Capability contract for generating Media (images or video). Apps
328
334
  * declare it to request access; openMediaGeneration uses it to obtain
329
335
  * the host's handle. */
330
- export const mediaGeneration = defineCapability("media.generation", 5, rpcHandleSchema({
336
+ export const mediaGeneration = defineCapability("media.generation", 5, z.lazy(() => rpcHandleSchema({
331
337
  describe: rpcMethod(rpcDataSchema(z.unknown().transform((value) => {
332
338
  const { computerId, canGenerate, models } = Schema.decodeUnknownSync(descriptionSchema, {
333
339
  onExcessProperty: "error",
@@ -337,7 +343,7 @@ export const mediaGeneration = defineCapability("media.generation", 5, rpcHandle
337
343
  createAttempt: rpcMethod(mediaAttemptHandleSchema),
338
344
  restoreAttempt: rpcMethod(mediaAttemptHandleSchema),
339
345
  cancelAttempt: rpcVoid,
340
- }));
346
+ })));
341
347
  /** Parse a value received from the host and turn schema failures into the
342
348
  * transport-safe Agar error used by the other capability helpers. */
343
349
  function decode(schema, value) {
@@ -0,0 +1,7 @@
1
+ import type { ResultRowCodec } from "./backend-definition.js";
2
+ /**
3
+ * The shared result row's decoder and JSON boundary, for
4
+ * `defineAppMethod({ present: { verb, row, rows: resultRows } })`. Only
5
+ * backends that present import this module, so the rest carry no decoder.
6
+ */
7
+ export declare const resultRows: ResultRowCodec;
@@ -0,0 +1,14 @@
1
+ import * as z from "zod";
2
+ import * as Schema from "effect/Schema";
3
+ import { resultRowSchemaV1 } from "@fungi.computer/gills";
4
+ /**
5
+ * The shared result row's decoder and JSON boundary, for
6
+ * `defineAppMethod({ present: { verb, row, rows: resultRows } })`. Only
7
+ * backends that present import this module, so the rest carry no decoder.
8
+ */
9
+ export const resultRows = Object.freeze({
10
+ decode: Schema.decodeUnknownSync(resultRowSchemaV1, {
11
+ onExcessProperty: "error",
12
+ }),
13
+ schema: z.fromJSONSchema(Schema.toJsonSchemaDocument(resultRowSchemaV1).schema),
14
+ });
@@ -29,7 +29,7 @@ export declare class AppMethodRefusal extends Error {
29
29
  constructor(code: AppMethodRefusalCode, message: string);
30
30
  }
31
31
  /** A refused call as it crosses the generated method boundary. */
32
- export declare const appMethodRefusedReplySchema: z.ZodObject<{
32
+ export declare const appMethodRefusedReplySchema: z.ZodLazy<z.ZodObject<{
33
33
  kind: z.ZodLiteral<"refused">;
34
34
  code: z.ZodEnum<{
35
35
  unavailable: "unavailable";
@@ -39,14 +39,14 @@ export declare const appMethodRefusedReplySchema: z.ZodObject<{
39
39
  not_found: "not_found";
40
40
  }>;
41
41
  message: z.ZodString;
42
- }, z.core.$strict>;
42
+ }, z.core.$strict>>;
43
43
  /** A decoded {@link appMethodRefusedReplySchema} value. */
44
44
  export type AppMethodRefusedReply = z.output<typeof appMethodRefusedReplySchema>;
45
45
  /** Every generated App method RPC answers with exactly one of these. */
46
- export declare const appMethodReplySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
46
+ export declare const appMethodReplySchema: z.ZodLazy<z.ZodDiscriminatedUnion<[z.ZodObject<{
47
47
  kind: z.ZodLiteral<"ok">;
48
48
  value: z.ZodJSONSchema;
49
- }, z.core.$strict>, z.ZodObject<{
49
+ }, z.core.$strict>, z.ZodLazy<z.ZodObject<{
50
50
  kind: z.ZodLiteral<"refused">;
51
51
  code: z.ZodEnum<{
52
52
  unavailable: "unavailable";
@@ -56,7 +56,7 @@ export declare const appMethodReplySchema: z.ZodDiscriminatedUnion<[z.ZodObject<
56
56
  not_found: "not_found";
57
57
  }>;
58
58
  message: z.ZodString;
59
- }, z.core.$strict>], "kind">;
59
+ }, z.core.$strict>>], "kind">>;
60
60
  /** A decoded {@link appMethodReplySchema} value; the result is read-only JSON. */
61
61
  export type AppMethodReply = Readonly<{
62
62
  kind: "ok";