@fungi.computer/darkroom 0.1.0 → 0.2.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.
- package/README.md +12 -3
- package/dist/app-media-contracts.d.ts +28 -0
- package/dist/app-media-contracts.js +2 -0
- package/dist/browser-failure.d.ts +7 -0
- package/dist/browser-failure.js +3 -0
- package/dist/cloudflare/provider.js +14 -15
- package/dist/execution-error.d.ts +10 -2
- package/dist/execution-error.js +7 -0
- package/dist/media-binding.d.ts +8 -2
- package/dist/media-binding.js +21 -2
- package/dist/media-operation-owner.d.ts +48 -11
- package/dist/media-operation-owner.js +28 -16
- package/dist/media-operation.js +17 -7
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -0
- package/docs/api/app-media-contracts.md +100 -110
- package/docs/api/browser.md +34 -2
- package/docs/api/cloudflare.md +35 -26
- package/docs/api/index.md +285 -343
- package/docs/api/server.md +561 -89
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -156,9 +156,18 @@ Agar's public `pendingSchema`. `appMediaModelsOutcomeSchema` and
|
|
|
156
156
|
closed admission codes, the Media attachment-budget refusal, the existing
|
|
157
157
|
`agentAppMediaLibraryFailureSchema`, and `appMediaBrokerFailureSchema`. The
|
|
158
158
|
latter preserves the broker projection: HTTP `code` becomes `error`, HTTP
|
|
159
|
-
`error` becomes `message`, and `stage
|
|
160
|
-
failures and transport envelopes are separate
|
|
161
|
-
as these outcomes.
|
|
159
|
+
`error` becomes `message`, and `stage`, `retryable` and optional `provider`
|
|
160
|
+
facts stay unchanged. HTTP failures and transport envelopes are separate
|
|
161
|
+
representations and do not decode as these outcomes.
|
|
162
|
+
|
|
163
|
+
Provider facts use Agar's canonical Media grammar: an allowlisted error category
|
|
164
|
+
and bounded numeric code, or `result_download` and a native HTTP status. The
|
|
165
|
+
durable owner retains those facts through interruption and replay, including
|
|
166
|
+
safety refusals, without retaining the original error message or cause in the
|
|
167
|
+
failure record. A terminal provider failure never redispatches the paid attempt.
|
|
168
|
+
The fresh pre-release schema replaces the safety-only refusal table; old preview
|
|
169
|
+
stores require an explicit host reset, not an automatic migration or dual
|
|
170
|
+
reader.
|
|
162
171
|
|
|
163
172
|
See the [generated API](docs/api/README.md) for signatures and host ports. The
|
|
164
173
|
Cloudflare entry needs the native Workers type declarations in a TypeScript
|
|
@@ -221,6 +221,13 @@ export declare const appMediaBrokerFailureSchema: z.ZodObject<{
|
|
|
221
221
|
readonly "destination-write": "destination-write";
|
|
222
222
|
}>;
|
|
223
223
|
retryable: z.ZodBoolean;
|
|
224
|
+
provider: z.ZodOptional<z.ZodPipe<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodTransform<Readonly<{
|
|
225
|
+
category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
|
|
226
|
+
code: number | null;
|
|
227
|
+
}> | Readonly<{
|
|
228
|
+
category: "result_download";
|
|
229
|
+
status: number;
|
|
230
|
+
}>, unknown>>>;
|
|
224
231
|
}, z.core.$strict>;
|
|
225
232
|
/** Refusal payloads emitted by the App media broker, separate from transport errors. */
|
|
226
233
|
export declare const appMediaRefusalSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
@@ -268,6 +275,13 @@ export declare const appMediaRefusalSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
268
275
|
readonly "destination-write": "destination-write";
|
|
269
276
|
}>;
|
|
270
277
|
retryable: z.ZodBoolean;
|
|
278
|
+
provider: z.ZodOptional<z.ZodPipe<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodTransform<Readonly<{
|
|
279
|
+
category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
|
|
280
|
+
code: number | null;
|
|
281
|
+
}> | Readonly<{
|
|
282
|
+
category: "result_download";
|
|
283
|
+
status: number;
|
|
284
|
+
}>, unknown>>>;
|
|
271
285
|
}, z.core.$strict>]>;
|
|
272
286
|
/** Model-list payload or an existing broker refusal. */
|
|
273
287
|
export declare const appMediaModelsOutcomeSchema: z.ZodUnion<readonly [z.ZodLazy<z.ZodArray<z.ZodReadonly<z.ZodObject<{
|
|
@@ -429,6 +443,13 @@ export declare const appMediaModelsOutcomeSchema: z.ZodUnion<readonly [z.ZodLazy
|
|
|
429
443
|
readonly "destination-write": "destination-write";
|
|
430
444
|
}>;
|
|
431
445
|
retryable: z.ZodBoolean;
|
|
446
|
+
provider: z.ZodOptional<z.ZodPipe<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodTransform<Readonly<{
|
|
447
|
+
category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
|
|
448
|
+
code: number | null;
|
|
449
|
+
}> | Readonly<{
|
|
450
|
+
category: "result_download";
|
|
451
|
+
status: number;
|
|
452
|
+
}>, unknown>>>;
|
|
432
453
|
}, z.core.$strict>]>]>;
|
|
433
454
|
/** Completed generation, Agar polling result, or an existing broker refusal. */
|
|
434
455
|
export declare const appMediaGenerationOutcomeSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
@@ -496,4 +517,11 @@ export declare const appMediaGenerationOutcomeSchema: z.ZodUnion<readonly [z.Zod
|
|
|
496
517
|
readonly "destination-write": "destination-write";
|
|
497
518
|
}>;
|
|
498
519
|
retryable: z.ZodBoolean;
|
|
520
|
+
provider: z.ZodOptional<z.ZodPipe<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodTransform<Readonly<{
|
|
521
|
+
category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
|
|
522
|
+
code: number | null;
|
|
523
|
+
}> | Readonly<{
|
|
524
|
+
category: "result_download";
|
|
525
|
+
status: number;
|
|
526
|
+
}>, unknown>>>;
|
|
499
527
|
}, z.core.$strict>]>]>;
|
|
@@ -75,6 +75,8 @@ export const appMediaBrokerFailureSchema = z.strictObject({
|
|
|
75
75
|
stage: mediaFailureSchema.shape.stage,
|
|
76
76
|
/** Original retry truth. */
|
|
77
77
|
retryable: mediaFailureSchema.shape.retryable,
|
|
78
|
+
/** Original bounded provider facts, when available. */
|
|
79
|
+
provider: mediaFailureSchema.shape.provider,
|
|
78
80
|
});
|
|
79
81
|
/** Refusal payloads emitted by the App media broker, separate from transport errors. */
|
|
80
82
|
export const appMediaRefusalSchema = z.union([
|
|
@@ -33,6 +33,13 @@ export declare const mediaFailureSchema: z.ZodObject<{
|
|
|
33
33
|
readonly SAFETY_FILTER_REFUSED: "SAFETY_FILTER_REFUSED";
|
|
34
34
|
}>;
|
|
35
35
|
retryable: z.ZodBoolean;
|
|
36
|
+
provider: z.ZodOptional<z.ZodPipe<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodTransform<Readonly<{
|
|
37
|
+
category: "InferenceUpstreamError" | "AiInternalError" | "TypeError" | "AbortError" | "TimeoutError" | "Error";
|
|
38
|
+
code: number | null;
|
|
39
|
+
}> | Readonly<{
|
|
40
|
+
category: "result_download";
|
|
41
|
+
status: number;
|
|
42
|
+
}>, unknown>>>;
|
|
36
43
|
}, z.core.$strict>;
|
|
37
44
|
/** Sanitized Media failure carried from the authenticated host to Agar. */
|
|
38
45
|
export type MediaFailure = z.output<typeof mediaFailureSchema>;
|
package/dist/browser-failure.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as z from "zod";
|
|
|
2
2
|
import { mediaFailureStageSchema } from "./execution-error.js";
|
|
3
3
|
import { Option, Schema } from "effect";
|
|
4
4
|
import { reportCatchFailure } from "./failure.js";
|
|
5
|
+
import { mediaProviderDiagnosticWireSchema } from "@fungi.computer/agar/errors";
|
|
5
6
|
/** Sanitized HTTP refusal emitted by a Media host and decoded at the browser boundary. */
|
|
6
7
|
export const mediaFailureSchema = z.strictObject({
|
|
7
8
|
/** Host-supplied presentation copy. */
|
|
@@ -23,6 +24,8 @@ export const mediaFailureSchema = z.strictObject({
|
|
|
23
24
|
}),
|
|
24
25
|
/** Whether repeating this exact attempt may recover. */
|
|
25
26
|
retryable: z.boolean(),
|
|
27
|
+
/** Allowlisted provider facts only; the original cause never crosses HTTP. */
|
|
28
|
+
provider: mediaProviderDiagnosticWireSchema.optional(),
|
|
26
29
|
});
|
|
27
30
|
const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Json));
|
|
28
31
|
function parseMediaFailureJson(json) {
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import * as Redacted from "effect/Redacted";
|
|
3
3
|
import { buildProviderInput, getMediaModel, estimateImageMilliunits, normalizeMediaProviderOutput, mediaProviderWireSchema, } from "../index.js";
|
|
4
4
|
import { MediaExecutionError, MediaSafetyFilterRefusal, } from "../execution-error.js";
|
|
5
|
+
import { decodeMediaProviderErrorCategory, } from "@fungi.computer/agar/errors";
|
|
5
6
|
import { estimateGatewayMilliunits, gatewayInput, isGatewayModel, } from "../gateway-models.js";
|
|
6
7
|
import { GATEWAY_VIDEO_MAX_BYTES, gatewayOutputSchema, isMp4, } from "../gateway-wire.js";
|
|
7
8
|
import { videoProviderAdapters } from "../video-providers/index.js";
|
|
@@ -31,16 +32,6 @@ const providerFailureCode = z.lazy(() => z
|
|
|
31
32
|
.transform((message) => Number(message.split(":", 1)[0]))
|
|
32
33
|
.nullable()
|
|
33
34
|
.catch(null));
|
|
34
|
-
const providerFailureCategory = z.lazy(() => z
|
|
35
|
-
.enum([
|
|
36
|
-
"InferenceUpstreamError",
|
|
37
|
-
"AiInternalError",
|
|
38
|
-
"TypeError",
|
|
39
|
-
"AbortError",
|
|
40
|
-
"TimeoutError",
|
|
41
|
-
"Error",
|
|
42
|
-
])
|
|
43
|
-
.catch("Error"));
|
|
44
35
|
// Recognize the refusal, not the generic Workers AI error number or prompt text.
|
|
45
36
|
const safetyFilterMessage = z.lazy(() => z.union([
|
|
46
37
|
z
|
|
@@ -64,17 +55,21 @@ function workersAiInput(payload) {
|
|
|
64
55
|
}
|
|
65
56
|
/** Retain the original provider failure alongside its bounded code and category. */
|
|
66
57
|
function providerFailure(model, cause) {
|
|
58
|
+
const provider = {
|
|
59
|
+
category: decodeMediaProviderErrorCategory(cause instanceof Error ? cause.name : undefined),
|
|
60
|
+
code: providerFailureCode.parse(cause instanceof Error ? cause.message : undefined),
|
|
61
|
+
};
|
|
67
62
|
// Workers correlates this structured event with the request trace. Never log
|
|
68
63
|
// the provider message/body: either can contain customer prompt content.
|
|
69
64
|
console.error("Media provider request failed", {
|
|
70
65
|
model,
|
|
71
|
-
providerCode:
|
|
72
|
-
category:
|
|
66
|
+
providerCode: provider.code,
|
|
67
|
+
category: provider.category,
|
|
73
68
|
}, Redacted.make(cause));
|
|
74
69
|
return cause instanceof Error &&
|
|
75
70
|
safetyFilterMessage.safeParse(cause.message).success
|
|
76
|
-
? new MediaSafetyFilterRefusal({ cause })
|
|
77
|
-
: new MediaExecutionError("provider", { cause });
|
|
71
|
+
? new MediaSafetyFilterRefusal({ cause, provider })
|
|
72
|
+
: new MediaExecutionError("provider", { cause, provider });
|
|
78
73
|
}
|
|
79
74
|
/** Run a gateway model and fetch its result URL within the byte bound. */
|
|
80
75
|
async function generateThroughGateway(ai, settings, references, signal, options, fetchResult, outputFormat) {
|
|
@@ -95,7 +90,9 @@ async function generateThroughGateway(ai, settings, references, signal, options,
|
|
|
95
90
|
const url = videoProviderAdapters[model.video.provider].decodeResultUrl(providerValue);
|
|
96
91
|
const response = await fetchResult(url, { signal: signal ?? null });
|
|
97
92
|
if (!response.ok)
|
|
98
|
-
throw new
|
|
93
|
+
throw new MediaExecutionError("normalization", {
|
|
94
|
+
provider: { category: "result_download", status: response.status },
|
|
95
|
+
});
|
|
99
96
|
const bytes = await readResponseBytes(response, GATEWAY_VIDEO_MAX_BYTES);
|
|
100
97
|
if (!isMp4(bytes))
|
|
101
98
|
throw new Error("gateway video is not an MP4");
|
|
@@ -107,6 +104,8 @@ async function generateThroughGateway(ai, settings, references, signal, options,
|
|
|
107
104
|
}), { fetchUrl: (url) => fetchResult(url, { signal: signal ?? null }) });
|
|
108
105
|
}
|
|
109
106
|
catch (cause) {
|
|
107
|
+
if (cause instanceof MediaExecutionError)
|
|
108
|
+
throw cause;
|
|
110
109
|
throw new MediaExecutionError("normalization", { cause });
|
|
111
110
|
}
|
|
112
111
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
+
import { type MediaProviderDiagnostic } from "@fungi.computer/agar/errors";
|
|
2
3
|
/** Public diagnostic labels only; never provider messages or request data. */
|
|
3
4
|
export declare const mediaFailureStageSchema: z.ZodEnum<{
|
|
4
5
|
/** Host admission before operation creation. */
|
|
@@ -26,17 +27,24 @@ export declare const mediaFailureStageSchema: z.ZodEnum<{
|
|
|
26
27
|
}>;
|
|
27
28
|
/** Execution stage at which a generation failed. */
|
|
28
29
|
export type MediaFailureStage = Exclude<z.output<typeof mediaFailureStageSchema>, "admission" | "settings">;
|
|
30
|
+
/** Private cause plus an independently decoded, safe provider diagnostic. */
|
|
31
|
+
export type MediaExecutionErrorOptions = ErrorOptions & {
|
|
32
|
+
/** Safe provider facts, when the failure came from provider transport. */
|
|
33
|
+
readonly provider?: MediaProviderDiagnostic | undefined;
|
|
34
|
+
};
|
|
29
35
|
/** A bounded stage marker for failures that cross the Media HTTP boundary. */
|
|
30
36
|
export declare class MediaExecutionError extends Error {
|
|
31
37
|
/** Execution stage where the failure occurred. */
|
|
32
38
|
readonly stage: MediaFailureStage;
|
|
39
|
+
/** Safe provider facts only; the original cause remains private. */
|
|
40
|
+
readonly provider: MediaProviderDiagnostic | undefined;
|
|
33
41
|
constructor(
|
|
34
42
|
/** Execution stage where the failure occurred. */
|
|
35
|
-
stage: MediaFailureStage, options?:
|
|
43
|
+
stage: MediaFailureStage, options?: MediaExecutionErrorOptions);
|
|
36
44
|
}
|
|
37
45
|
/** A provider safety filter refused generation; no output was delivered. */
|
|
38
46
|
export declare class MediaSafetyFilterRefusal extends MediaExecutionError {
|
|
39
|
-
constructor(options?:
|
|
47
|
+
constructor(options?: MediaExecutionErrorOptions);
|
|
40
48
|
}
|
|
41
49
|
/** Why the host's billing owner refused to admit a new generation. */
|
|
42
50
|
export type MediaBillingRefusalCode = "insufficient_credits" | "daily_limit_reached" | "denied";
|
package/dist/execution-error.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
+
import { decodeMediaProviderDiagnostic, } from "@fungi.computer/agar/errors";
|
|
2
3
|
/** Public diagnostic labels only; never provider messages or request data. */
|
|
3
4
|
export const mediaFailureStageSchema = z.enum({
|
|
4
5
|
/** Host admission before operation creation. */
|
|
@@ -27,12 +28,18 @@ export const mediaFailureStageSchema = z.enum({
|
|
|
27
28
|
/** A bounded stage marker for failures that cross the Media HTTP boundary. */
|
|
28
29
|
export class MediaExecutionError extends Error {
|
|
29
30
|
stage;
|
|
31
|
+
/** Safe provider facts only; the original cause remains private. */
|
|
32
|
+
provider;
|
|
30
33
|
constructor(
|
|
31
34
|
/** Execution stage where the failure occurred. */
|
|
32
35
|
stage, options) {
|
|
33
36
|
super(`Media ${stage} failed`, options);
|
|
34
37
|
this.stage = stage;
|
|
35
38
|
this.name = "MediaExecutionError";
|
|
39
|
+
this.provider =
|
|
40
|
+
options?.provider === undefined
|
|
41
|
+
? undefined
|
|
42
|
+
: decodeMediaProviderDiagnostic(options.provider);
|
|
36
43
|
}
|
|
37
44
|
}
|
|
38
45
|
/** A provider safety filter refused generation; no output was delivered. */
|
package/dist/media-binding.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
import * as Schema from "effect/Schema";
|
|
3
2
|
import { mediaRequestedImageFormatSchema, type MediaGenerationSettings } from "./index.js";
|
|
4
3
|
export { mediaResultSchema } from "./media-operation-contract.js";
|
|
5
4
|
/** Image settings and path references crossing a scoped MEDIA binding. */
|
|
@@ -272,17 +271,24 @@ export declare const mediaPendingSchema: z.ZodLazy<z.ZodObject<{
|
|
|
272
271
|
retryAfterSeconds: z.ZodNumber;
|
|
273
272
|
}, z.core.$strict>>;
|
|
274
273
|
/** Parsing and model refusal stay shared; authorization remains host-owned. */
|
|
275
|
-
export declare function parseMediaBindingCall(raw:
|
|
274
|
+
export declare function parseMediaBindingCall(raw: z.core.util.JSONType): Readonly<{
|
|
275
|
+
/** The binding input passed its shared grammar. */
|
|
276
276
|
kind: "parsed";
|
|
277
|
+
/** Validated command for host admission. */
|
|
277
278
|
call: MediaBindingCall;
|
|
278
279
|
}> | Readonly<{
|
|
280
|
+
/** Input size, grammar or model selection was refused. */
|
|
279
281
|
kind: "refused";
|
|
282
|
+
/** Safe refusal returned without provider dispatch. */
|
|
280
283
|
result: z.core.util.JSONType;
|
|
281
284
|
}>;
|
|
282
285
|
/** Relative service input after the host attaches its trusted Computer to references. */
|
|
283
286
|
export type MediaBindingRequest = Readonly<{
|
|
287
|
+
/** Host-selected output filename within the App's library folder. */
|
|
284
288
|
path: string;
|
|
289
|
+
/** Validated selection with the host's Computer attached to references. */
|
|
285
290
|
settings: MediaGenerationSettings;
|
|
291
|
+
/** Optional image encoding; video output remains MP4. */
|
|
286
292
|
outputFormat?: z.output<typeof mediaRequestedImageFormatSchema>;
|
|
287
293
|
}>;
|
|
288
294
|
/** Choose the same fixed filename on both hosts without a recursive App lookup. */
|
package/dist/media-binding.js
CHANGED
|
@@ -13,12 +13,17 @@ const mediaBindingLibraryPathSchema = z
|
|
|
13
13
|
.refine(Schema.is(mediaLibraryPathSchema), "Select an absolute library folder");
|
|
14
14
|
/** Image settings and path references crossing a scoped MEDIA binding. */
|
|
15
15
|
export const mediaImageBindingInputSchema = z.strictObject({
|
|
16
|
+
/** Select image generation rather than video. */
|
|
16
17
|
kind: z.literal("image"),
|
|
18
|
+
/** Attempt identity retained through polling and replay. */
|
|
17
19
|
requestId: mediaRequestIdSchema,
|
|
20
|
+
/** App-owned absolute folder; the host supplies Computer authority. */
|
|
18
21
|
libraryPath: mediaBindingLibraryPathSchema,
|
|
22
|
+
/** Image model selection, references and inference settings. */
|
|
19
23
|
settings: mediaGenerationSelectionSchema
|
|
20
24
|
.omit({ durationSeconds: true })
|
|
21
25
|
.extend({
|
|
26
|
+
/** Optional inference-step count for models that support it. */
|
|
22
27
|
steps: z
|
|
23
28
|
.number()
|
|
24
29
|
.int()
|
|
@@ -27,13 +32,18 @@ export const mediaImageBindingInputSchema = z.strictObject({
|
|
|
27
32
|
.optional(),
|
|
28
33
|
})
|
|
29
34
|
.refine((settings) => getMediaModel(settings.model)?.kind === "image", "Select an image model from listModels"),
|
|
35
|
+
/** Requested image encoding, validated against the selected model. */
|
|
30
36
|
outputFormat: mediaRequestedImageFormatSchema.optional(),
|
|
31
37
|
});
|
|
32
38
|
/** Video settings and path references crossing the same scoped binding. */
|
|
33
39
|
export const mediaVideoBindingInputSchema = z.strictObject({
|
|
40
|
+
/** Select video generation rather than an image. */
|
|
34
41
|
kind: z.literal("video"),
|
|
42
|
+
/** Attempt identity retained through polling and replay. */
|
|
35
43
|
requestId: mediaRequestIdSchema,
|
|
44
|
+
/** App-owned absolute folder; the host supplies Computer authority. */
|
|
36
45
|
libraryPath: mediaBindingLibraryPathSchema,
|
|
46
|
+
/** Catalog-backed video model, size, duration and references. */
|
|
37
47
|
settings: mediaVideoSelectionSchema,
|
|
38
48
|
});
|
|
39
49
|
/** One generation identity, including the App-owned folder captured for replay. */
|
|
@@ -44,17 +54,23 @@ export const mediaBindingGenerationSchema = z.discriminatedUnion("kind", [
|
|
|
44
54
|
/** Maintained MEDIA method grammar; guest input carries no principal or Computer. */
|
|
45
55
|
export const mediaBindingCallSchema = z.discriminatedUnion("operation", [
|
|
46
56
|
z.strictObject({
|
|
57
|
+
/** Read the model catalog without dispatching generation. */
|
|
47
58
|
operation: z.literal("models"),
|
|
59
|
+
/** Catalog reads carry no guest authority or generation settings. */
|
|
48
60
|
input: z.strictObject({}),
|
|
49
61
|
}),
|
|
50
62
|
z.strictObject({
|
|
63
|
+
/** Execute or poll one admitted generation identity. */
|
|
51
64
|
operation: z.literal("generate"),
|
|
65
|
+
/** The App's captured generation selection and library folder. */
|
|
52
66
|
input: mediaBindingGenerationSchema,
|
|
53
67
|
}),
|
|
54
68
|
]);
|
|
55
69
|
/** Background-generation reply emitted by the durable service. */
|
|
56
70
|
export const mediaPendingSchema = z.lazy(() => z.strictObject({
|
|
71
|
+
/** Background work has been accepted but is not yet delivered. */
|
|
57
72
|
status: z.literal("pending"),
|
|
73
|
+
/** Host-supplied delay before polling the same attempt again. */
|
|
58
74
|
retryAfterSeconds: z.number().int().positive(),
|
|
59
75
|
}));
|
|
60
76
|
/** Parsing and model refusal stay shared; authorization remains host-owned. */
|
|
@@ -122,12 +138,15 @@ export async function readMediaBindingReply(input, computerId, response) {
|
|
|
122
138
|
const failure = mediaFailureSchema.safeParse(value);
|
|
123
139
|
if (!failure.success)
|
|
124
140
|
return { error: "unavailable" };
|
|
125
|
-
|
|
141
|
+
const projected = {
|
|
126
142
|
error: failure.data.code,
|
|
127
143
|
message: failure.data.error,
|
|
128
144
|
stage: failure.data.stage,
|
|
129
145
|
retryable: failure.data.retryable,
|
|
130
|
-
}
|
|
146
|
+
};
|
|
147
|
+
if (failure.data.provider !== undefined)
|
|
148
|
+
projected.provider = failure.data.provider;
|
|
149
|
+
return z.json().parse(appMediaBrokerFailureSchema.parse(projected));
|
|
131
150
|
}
|
|
132
151
|
if (input.operation === "models")
|
|
133
152
|
return z.json().parse(mediaModelsSchema.parse(value));
|
|
@@ -2,6 +2,8 @@ import { type MediaGenerationSettings } from "./index.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { MediaOperationContext, MediaOperationBucket } from "./media-operation-storage.js";
|
|
4
4
|
import type { MediaProvider, MediaReferenceBytes } from "./index.js";
|
|
5
|
+
import { type MediaFailureStage } from "./execution-error.js";
|
|
6
|
+
import { type MediaProviderDiagnostic } from "@fungi.computer/agar/errors";
|
|
5
7
|
/** Fixed generation destination captured before provider dispatch. */
|
|
6
8
|
export declare const mediaOperationDestinationSchema: z.ZodLazy<z.ZodObject<{
|
|
7
9
|
computerId: z.ZodString;
|
|
@@ -72,8 +74,15 @@ export type MediaOperationProjection = Readonly<{
|
|
|
72
74
|
state: MediaOperationState;
|
|
73
75
|
/** Terminal format mismatch after provider execution. */
|
|
74
76
|
spentFailure: "output_format_mismatch" | null;
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
+
/** Safe terminal provider failure; no original cause or provider message. */
|
|
78
|
+
providerFailure: Readonly<{
|
|
79
|
+
/** Provider dispatch or output normalization, not a save failure. */
|
|
80
|
+
stage: Extract<MediaFailureStage, "provider" | "normalization">;
|
|
81
|
+
/** Preserve the model's safety refusal classification on replay. */
|
|
82
|
+
safetyRefused: boolean;
|
|
83
|
+
/** Allowlisted facts only; never the original error message or cause. */
|
|
84
|
+
provider?: MediaProviderDiagnostic | undefined;
|
|
85
|
+
}> | null;
|
|
77
86
|
/** Pinned host price or unmetered quote. */
|
|
78
87
|
estimatedCostMilliunits: number | null;
|
|
79
88
|
/** Retained or saved image metadata. */
|
|
@@ -182,8 +191,15 @@ export declare class MediaOperationOwner {
|
|
|
182
191
|
state: MediaOperationState;
|
|
183
192
|
/** Terminal format mismatch after provider execution. */
|
|
184
193
|
spentFailure: "output_format_mismatch" | null;
|
|
185
|
-
/**
|
|
186
|
-
|
|
194
|
+
/** Safe terminal provider failure; no original cause or provider message. */
|
|
195
|
+
providerFailure: Readonly<{
|
|
196
|
+
/** Provider dispatch or output normalization, not a save failure. */
|
|
197
|
+
stage: Extract<MediaFailureStage, "provider" | "normalization">;
|
|
198
|
+
/** Preserve the model's safety refusal classification on replay. */
|
|
199
|
+
safetyRefused: boolean;
|
|
200
|
+
/** Allowlisted facts only; never the original error message or cause. */
|
|
201
|
+
provider?: MediaProviderDiagnostic | undefined;
|
|
202
|
+
}> | null;
|
|
187
203
|
/** Pinned host price or unmetered quote. */
|
|
188
204
|
estimatedCostMilliunits: number | null;
|
|
189
205
|
/** Retained or saved image metadata. */
|
|
@@ -204,7 +220,7 @@ export declare class MediaOperationOwner {
|
|
|
204
220
|
/** Run a background generation once; a retried alarm never redispatches. */
|
|
205
221
|
alarm(): Promise<void>;
|
|
206
222
|
/** An aborted in-flight provider call is terminal for this request ID. */
|
|
207
|
-
interruptGeneration(leaseId: string,
|
|
223
|
+
interruptGeneration(leaseId: string, failureInput?: NonNullable<MediaOperationProjection["providerFailure"]>): MediaOperationProjection;
|
|
208
224
|
/** Retain immutable provider output before making the save state visible. */
|
|
209
225
|
recordGenerated(leaseId: string, bytes: Uint8Array, formatInput: string): Promise<MediaRetentionResult>;
|
|
210
226
|
/** Read exact retained bytes under the current save lease. */
|
|
@@ -225,8 +241,15 @@ export declare class MediaOperationOwner {
|
|
|
225
241
|
state: MediaOperationState;
|
|
226
242
|
/** Terminal format mismatch after provider execution. */
|
|
227
243
|
spentFailure: "output_format_mismatch" | null;
|
|
228
|
-
/**
|
|
229
|
-
|
|
244
|
+
/** Safe terminal provider failure; no original cause or provider message. */
|
|
245
|
+
providerFailure: Readonly<{
|
|
246
|
+
/** Provider dispatch or output normalization, not a save failure. */
|
|
247
|
+
stage: Extract<MediaFailureStage, "provider" | "normalization">;
|
|
248
|
+
/** Preserve the model's safety refusal classification on replay. */
|
|
249
|
+
safetyRefused: boolean;
|
|
250
|
+
/** Allowlisted facts only; never the original error message or cause. */
|
|
251
|
+
provider?: MediaProviderDiagnostic | undefined;
|
|
252
|
+
}> | null;
|
|
230
253
|
/** Pinned host price or unmetered quote. */
|
|
231
254
|
estimatedCostMilliunits: number | null;
|
|
232
255
|
/** Retained or saved image metadata. */
|
|
@@ -256,8 +279,15 @@ export declare class MediaOperationOwner {
|
|
|
256
279
|
state: MediaOperationState;
|
|
257
280
|
/** Terminal format mismatch after provider execution. */
|
|
258
281
|
spentFailure: "output_format_mismatch" | null;
|
|
259
|
-
/**
|
|
260
|
-
|
|
282
|
+
/** Safe terminal provider failure; no original cause or provider message. */
|
|
283
|
+
providerFailure: Readonly<{
|
|
284
|
+
/** Provider dispatch or output normalization, not a save failure. */
|
|
285
|
+
stage: Extract<MediaFailureStage, "provider" | "normalization">;
|
|
286
|
+
/** Preserve the model's safety refusal classification on replay. */
|
|
287
|
+
safetyRefused: boolean;
|
|
288
|
+
/** Allowlisted facts only; never the original error message or cause. */
|
|
289
|
+
provider?: MediaProviderDiagnostic | undefined;
|
|
290
|
+
}> | null;
|
|
261
291
|
/** Pinned host price or unmetered quote. */
|
|
262
292
|
estimatedCostMilliunits: number | null;
|
|
263
293
|
/** Retained or saved image metadata. */
|
|
@@ -283,8 +313,15 @@ export declare class MediaOperationOwner {
|
|
|
283
313
|
state: MediaOperationState;
|
|
284
314
|
/** Terminal format mismatch after provider execution. */
|
|
285
315
|
spentFailure: "output_format_mismatch" | null;
|
|
286
|
-
/**
|
|
287
|
-
|
|
316
|
+
/** Safe terminal provider failure; no original cause or provider message. */
|
|
317
|
+
providerFailure: Readonly<{
|
|
318
|
+
/** Provider dispatch or output normalization, not a save failure. */
|
|
319
|
+
stage: Extract<MediaFailureStage, "provider" | "normalization">;
|
|
320
|
+
/** Preserve the model's safety refusal classification on replay. */
|
|
321
|
+
safetyRefused: boolean;
|
|
322
|
+
/** Allowlisted facts only; never the original error message or cause. */
|
|
323
|
+
provider?: MediaProviderDiagnostic | undefined;
|
|
324
|
+
}> | null;
|
|
288
325
|
/** Pinned host price or unmetered quote. */
|
|
289
326
|
estimatedCostMilliunits: number | null;
|
|
290
327
|
/** Retained or saved image metadata. */
|
|
@@ -3,8 +3,13 @@ import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
|
3
3
|
import { mediaGenerationSettingsSchema, validateMediaGenerationSettings, } from "./index.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { reportCatchFailure } from "./failure.js";
|
|
6
|
-
import { MediaSafetyFilterRefusal } from "./execution-error.js";
|
|
7
|
-
|
|
6
|
+
import { MediaExecutionError, MediaSafetyFilterRefusal, mediaFailureStageSchema, } from "./execution-error.js";
|
|
7
|
+
import { mediaProviderDiagnosticWireSchema, } from "@fungi.computer/agar/errors";
|
|
8
|
+
const providerFailureSchema = z.strictObject({
|
|
9
|
+
stage: mediaFailureStageSchema.extract(["provider", "normalization"]),
|
|
10
|
+
safetyRefused: z.boolean(),
|
|
11
|
+
provider: mediaProviderDiagnosticWireSchema.optional(),
|
|
12
|
+
});
|
|
8
13
|
const ids = defineIdFamily({
|
|
9
14
|
family: "hub-media-operation",
|
|
10
15
|
version: 1,
|
|
@@ -143,7 +148,7 @@ const schemaStatements = [
|
|
|
143
148
|
dispatched_at INTEGER
|
|
144
149
|
) STRICT`,
|
|
145
150
|
"CREATE TABLE hub_media_spent_failure (singleton INTEGER PRIMARY KEY CHECK(singleton=1),code TEXT NOT NULL CHECK(code='output_format_mismatch')) STRICT",
|
|
146
|
-
"CREATE TABLE
|
|
151
|
+
"CREATE TABLE hub_media_provider_failure (singleton INTEGER PRIMARY KEY CHECK(singleton=1),failure_json TEXT NOT NULL) STRICT",
|
|
147
152
|
];
|
|
148
153
|
function fingerprint(input) {
|
|
149
154
|
return JSON.stringify(input);
|
|
@@ -235,8 +240,8 @@ export class MediaOperationOwner {
|
|
|
235
240
|
}
|
|
236
241
|
else {
|
|
237
242
|
// Pre-launch stores use the current fresh schema or get reset by the host.
|
|
238
|
-
// Missing
|
|
239
|
-
this.ctx.storage.sql.exec("SELECT
|
|
243
|
+
// Missing failure facts are not healed or incrementally migrated here.
|
|
244
|
+
this.ctx.storage.sql.exec("SELECT failure_json FROM hub_media_provider_failure LIMIT 0");
|
|
240
245
|
}
|
|
241
246
|
this.initialized = true;
|
|
242
247
|
}
|
|
@@ -301,12 +306,14 @@ export class MediaOperationOwner {
|
|
|
301
306
|
code: z.literal("output_format_mismatch"),
|
|
302
307
|
})
|
|
303
308
|
.parse(failure).code;
|
|
304
|
-
const
|
|
305
|
-
.exec("SELECT
|
|
309
|
+
const failureRow = this.ctx.storage.sql
|
|
310
|
+
.exec("SELECT failure_json FROM hub_media_provider_failure WHERE singleton=1")
|
|
306
311
|
.toArray()[0];
|
|
307
|
-
const
|
|
312
|
+
const providerFailure = failureRow === undefined
|
|
308
313
|
? null
|
|
309
|
-
:
|
|
314
|
+
: this.decodeJson(z
|
|
315
|
+
.strictObject({ failure_json: z.string().max(1024) })
|
|
316
|
+
.parse(failureRow).failure_json, providerFailureSchema);
|
|
310
317
|
return Object.freeze({
|
|
311
318
|
operationId: record.operationId,
|
|
312
319
|
requestId: record.requestId,
|
|
@@ -314,7 +321,7 @@ export class MediaOperationOwner {
|
|
|
314
321
|
owner: record.owner,
|
|
315
322
|
state: record.state,
|
|
316
323
|
spentFailure,
|
|
317
|
-
|
|
324
|
+
providerFailure,
|
|
318
325
|
estimatedCostMilliunits: record.estimatedCostMilliunits,
|
|
319
326
|
image: record.image,
|
|
320
327
|
result: record.result,
|
|
@@ -684,8 +691,13 @@ export class MediaOperationOwner {
|
|
|
684
691
|
// Nothing was delivered; the next retry sees interrupted and releases
|
|
685
692
|
// the billing hold at zero units.
|
|
686
693
|
try {
|
|
687
|
-
this.interruptGeneration(leaseId, cause instanceof
|
|
688
|
-
|
|
694
|
+
this.interruptGeneration(leaseId, cause instanceof MediaExecutionError &&
|
|
695
|
+
(cause.stage === "provider" || cause.stage === "normalization")
|
|
696
|
+
? {
|
|
697
|
+
stage: cause.stage,
|
|
698
|
+
safetyRefused: cause instanceof MediaSafetyFilterRefusal,
|
|
699
|
+
provider: cause.provider,
|
|
700
|
+
}
|
|
689
701
|
: undefined);
|
|
690
702
|
}
|
|
691
703
|
catch (cause) {
|
|
@@ -702,8 +714,8 @@ export class MediaOperationOwner {
|
|
|
702
714
|
this.ctx.storage.sql.exec("UPDATE hub_media_operation SET lease_id=NULL, lease_expires_at=NULL WHERE singleton=1 AND state='writing' AND lease_id=?", leaseId);
|
|
703
715
|
}
|
|
704
716
|
/** An aborted in-flight provider call is terminal for this request ID. */
|
|
705
|
-
interruptGeneration(leaseId,
|
|
706
|
-
const
|
|
717
|
+
interruptGeneration(leaseId, failureInput) {
|
|
718
|
+
const failure = providerFailureSchema.optional().parse(failureInput);
|
|
707
719
|
const record = this.requireRecord();
|
|
708
720
|
if (record.state !== "generating" || record.leaseId !== leaseId)
|
|
709
721
|
throw new MediaOperationError("lease_lost");
|
|
@@ -720,8 +732,8 @@ export class MediaOperationOwner {
|
|
|
720
732
|
writeReceipt: null,
|
|
721
733
|
result: null,
|
|
722
734
|
});
|
|
723
|
-
if (
|
|
724
|
-
this.ctx.storage.sql.exec("INSERT INTO
|
|
735
|
+
if (failure !== undefined)
|
|
736
|
+
this.ctx.storage.sql.exec("INSERT INTO hub_media_provider_failure(singleton,failure_json) VALUES(1,?)", JSON.stringify(failure));
|
|
725
737
|
return this.project(interrupted);
|
|
726
738
|
});
|
|
727
739
|
}
|
package/dist/media-operation.js
CHANGED
|
@@ -72,8 +72,13 @@ export function executeMediaOperation(input) {
|
|
|
72
72
|
}
|
|
73
73
|
if (claim.kind === "interrupted") {
|
|
74
74
|
yield* settleNothing();
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
const failure = claim.operation.providerFailure;
|
|
76
|
+
if (failure !== null)
|
|
77
|
+
return yield* Effect.fail(failure.safetyRefused
|
|
78
|
+
? new MediaSafetyFilterRefusal({ provider: failure.provider })
|
|
79
|
+
: new MediaExecutionError(failure.stage, {
|
|
80
|
+
provider: failure.provider,
|
|
81
|
+
}));
|
|
77
82
|
}
|
|
78
83
|
if (claim.kind !== "claimed" || claim.leaseId === undefined)
|
|
79
84
|
return {
|
|
@@ -149,11 +154,16 @@ export function executeMediaOperation(input) {
|
|
|
149
154
|
}, "provider").pipe(
|
|
150
155
|
// A provider failure delivered nothing and wrote no output: end the
|
|
151
156
|
// request now and release its hold rather than leave it unresolved.
|
|
152
|
-
Effect.tapError((error) =>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
Effect.tapError((error) => {
|
|
158
|
+
const stage = error.stage;
|
|
159
|
+
return stage === "provider" || stage === "normalization"
|
|
160
|
+
? call(() => input.operation.interruptGeneration(leaseId, {
|
|
161
|
+
stage,
|
|
162
|
+
safetyRefused: error instanceof MediaSafetyFilterRefusal,
|
|
163
|
+
provider: error.provider,
|
|
164
|
+
}), "operation").pipe(Effect.andThen(settleNothing()), Effect.ignore)
|
|
165
|
+
: Effect.void;
|
|
166
|
+
}));
|
|
157
167
|
if (generation.kind === "interrupted") {
|
|
158
168
|
yield* settleNothing();
|
|
159
169
|
return { kind: "interrupted" };
|
package/dist/server.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { validateMediaGenerationSettings, type MediaProvider, type MediaReference, type MediaReferenceBytes } from "./index.js";
|
|
5
5
|
import { type MediaExecution, type MediaMetering } from "./media-operation-contract.js";
|
|
6
6
|
import { MediaExecutionError, type MediaBillingRefusalCode } from "./execution-error.js";
|
|
7
|
-
export { MediaBillingRefusal, MediaExecutionError, type MediaBillingRefusalCode, type MediaFailureStage, } from "./execution-error.js";
|
|
7
|
+
export { MediaBillingRefusal, MediaExecutionError, type MediaBillingRefusalCode, type MediaFailureStage, type MediaExecutionErrorOptions, } from "./execution-error.js";
|
|
8
8
|
export { mediaUnits, type MediaUnits } from "./gateway-models.js";
|
|
9
9
|
export type { MediaMetering, MediaOperation, } from "./media-operation-contract.js";
|
|
10
10
|
export { MediaOperationOwner, mediaOperationDestinationSchema, mediaOperationCoordinatesSchema, mediaOperationStateSchema, mediaImageSchema, mediaOperationResultSchema, } from "./media-operation-owner.js";
|
package/dist/server.js
CHANGED
|
@@ -51,6 +51,7 @@ function failure(error, refusalMessages) {
|
|
|
51
51
|
stage: "provider",
|
|
52
52
|
code: "SAFETY_FILTER_REFUSED",
|
|
53
53
|
retryable: false,
|
|
54
|
+
provider: error.provider,
|
|
54
55
|
}, { status: 400, headers });
|
|
55
56
|
if (error instanceof MediaExecutionError &&
|
|
56
57
|
error.cause instanceof MediaOutputFormatMismatch)
|
|
@@ -105,6 +106,7 @@ function failure(error, refusalMessages) {
|
|
|
105
106
|
stage: error.stage,
|
|
106
107
|
code: "HOST_FAILURE",
|
|
107
108
|
retryable: error.stage !== "normalization" && error.stage !== "provider",
|
|
109
|
+
provider: error instanceof MediaExecutionError ? error.provider : undefined,
|
|
108
110
|
}, {
|
|
109
111
|
status: 503,
|
|
110
112
|
headers,
|