@effect-uai/fal 0.13.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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/FalImageGenerator.d.mts +152 -0
- package/dist/FalImageGenerator.d.mts.map +1 -0
- package/dist/FalImageGenerator.mjs +273 -0
- package/dist/FalImageGenerator.mjs.map +1 -0
- package/dist/codec.d.mts +17 -0
- package/dist/codec.d.mts.map +1 -0
- package/dist/codec.mjs +73 -0
- package/dist/codec.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +4 -0
- package/dist/models.d.mts +32 -0
- package/dist/models.d.mts.map +1 -0
- package/dist/models.mjs +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +59 -0
- package/src/FalImageGenerator.ts +524 -0
- package/src/codec.ts +130 -0
- package/src/index.ts +3 -0
- package/src/models.ts +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Betalyra Sociedade Unipessoal Lda.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @effect-uai/fal
|
|
2
|
+
|
|
3
|
+
fal provider for [`@effect-uai/core`](https://www.npmjs.com/package/@effect-uai/core).
|
|
4
|
+
|
|
5
|
+
Implements the `ImageGenerator` contract against fal's synchronous
|
|
6
|
+
endpoints, which is how you reach FLUX, Seedream, Qwen Image and the
|
|
7
|
+
rest of the open-weights field behind one key.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @effect-uai/fal @effect-uai/core effect
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
ESM-only. Requires `effect@4.x` and `@effect-uai/core` as peers.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Config, Effect, Layer } from "effect"
|
|
21
|
+
import { FetchHttpClient } from "effect/unstable/http"
|
|
22
|
+
import { layer as falLayer } from "@effect-uai/fal/FalImageGenerator"
|
|
23
|
+
|
|
24
|
+
const provider = Layer.unwrap(
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
const apiKey = yield* Config.redacted("FAL_API_KEY")
|
|
27
|
+
return falLayer({ apiKey })
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const layer = Layer.provide(provider, FetchHttpClient.layer)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The layer registers both the provider-typed `FalImageGenerator` tag and
|
|
35
|
+
the generic `ImageGenerator` tag.
|
|
36
|
+
|
|
37
|
+
## The model id is an endpoint
|
|
38
|
+
|
|
39
|
+
On fal the model selects the URL, and generating and editing are
|
|
40
|
+
separate endpoints of the same family:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
generate({ model: "bytedance/seedream/v5/pro/text-to-image", prompt })
|
|
44
|
+
edit({ model: "fal-ai/nano-banana-2/edit", prompt, images })
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Copy the id off the model's page on fal.ai and pass it as `model`.
|
|
48
|
+
|
|
49
|
+
## Docs
|
|
50
|
+
|
|
51
|
+
<https://effect-uai.betalyra.com/image-generation/providers/fal/>
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { FalImageEditModel, FalImageModel } from "./models.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Option, Redacted, Schema } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { GeneratedImage, ImageSource } from "@effect-uai/core/Image";
|
|
6
|
+
import { CommonImageEditRequest, CommonImageGenerateRequest, ImageGenerator, ImageGeneratorService, ImageResponse } from "@effect-uai/core/ImageGenerator";
|
|
7
|
+
declare namespace FalImageGenerator_d_exports {
|
|
8
|
+
export { Config, FalImageData, FalImageEditRequest, FalImageGenerateRequest, FalImageGenerator, FalImageGeneratorService, FalImageSize, FalResponseData, WireBody, buildBody, decodeWire, imageDataOf, imageSizeOf, layer, make, responseDataOf, toResponse };
|
|
9
|
+
}
|
|
10
|
+
/** A named preset (`"landscape_4_3"`, `"auto_2K"`, …) or exact pixels. */
|
|
11
|
+
type FalImageSize = string | {
|
|
12
|
+
readonly width: number;
|
|
13
|
+
readonly height: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Wire knobs shared by most fal image endpoints. fal is not one API but a
|
|
17
|
+
* catalogue of models behind one envelope, so only `prompt`, `num_images`
|
|
18
|
+
* and the output envelope are dependable across all of them; the fields
|
|
19
|
+
* here are the ones the image endpoints agree on.
|
|
20
|
+
*/
|
|
21
|
+
type FalImageKnobs = {
|
|
22
|
+
/** Sent as `image_size`. Takes precedence over the portable ratio and tier. */
|
|
23
|
+
readonly imageSize?: FalImageSize;
|
|
24
|
+
readonly seed?: number;
|
|
25
|
+
readonly outputFormat?: "jpeg" | "png" | "webp";
|
|
26
|
+
/** Diffusion knobs, open-weights models only. */
|
|
27
|
+
readonly numInferenceSteps?: number;
|
|
28
|
+
readonly guidanceScale?: number;
|
|
29
|
+
readonly enableSafetyChecker?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* `false` returns fal CDN links instead of inline bytes. The default is
|
|
32
|
+
* `true`: every other adapter here hands back bytes, and fal's links
|
|
33
|
+
* expire.
|
|
34
|
+
*/
|
|
35
|
+
readonly syncMode?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Fields only one endpoint takes: `acceleration`, `loras`,
|
|
38
|
+
* `safety_tolerance`, `thinking_level`, `aspect_ratio`. Merged last, so
|
|
39
|
+
* it also overrides anything derived above. Snake case, as the wire
|
|
40
|
+
* spells it. The escape hatch exists because fal's schemas are
|
|
41
|
+
* per-model and tabling them would go stale in a week.
|
|
42
|
+
*/
|
|
43
|
+
readonly input?: Record<string, unknown>;
|
|
44
|
+
};
|
|
45
|
+
type FalImageGenerateRequest = Omit<CommonImageGenerateRequest, "model"> & {
|
|
46
|
+
readonly model: FalImageModel;
|
|
47
|
+
} & FalImageKnobs;
|
|
48
|
+
type FalImageEditRequest = Omit<CommonImageEditRequest, "model"> & {
|
|
49
|
+
/** Edit is a *different endpoint* on fal, not a flag on the same one. */
|
|
50
|
+
readonly model: FalImageEditModel;
|
|
51
|
+
} & FalImageKnobs;
|
|
52
|
+
/**
|
|
53
|
+
* Per-image extras on `GeneratedImage.providerData.fal`. All optional:
|
|
54
|
+
* which of them an endpoint reports is per-model. Read with
|
|
55
|
+
* {@link imageDataOf}.
|
|
56
|
+
*/
|
|
57
|
+
declare const ImageData: Schema.Struct<{
|
|
58
|
+
readonly width: Schema.optional<Schema.Number>;
|
|
59
|
+
readonly height: Schema.optional<Schema.Number>;
|
|
60
|
+
readonly fileName: Schema.optional<Schema.String>;
|
|
61
|
+
readonly fileSize: Schema.optional<Schema.Number>;
|
|
62
|
+
}>;
|
|
63
|
+
type FalImageData = typeof ImageData.Type;
|
|
64
|
+
/** Response-level extras on `ImageResponse.providerData.fal`. */
|
|
65
|
+
declare const ResponseData: Schema.Struct<{
|
|
66
|
+
/** The seed the request resolved to. Send it back for the same image. */
|
|
67
|
+
readonly seed: Schema.optional<Schema.Number>;
|
|
68
|
+
readonly timings: Schema.optional<Schema.Unknown>;
|
|
69
|
+
readonly hasNsfwConcepts: Schema.optional<Schema.$Array<Schema.Boolean>>;
|
|
70
|
+
}>;
|
|
71
|
+
type FalResponseData = typeof ResponseData.Type;
|
|
72
|
+
type FalImageGeneratorService = {
|
|
73
|
+
readonly generate: (request: FalImageGenerateRequest) => Effect.Effect<ImageResponse, AiError.AiError>;
|
|
74
|
+
readonly edit: (request: FalImageEditRequest) => Effect.Effect<ImageResponse, AiError.AiError>;
|
|
75
|
+
/** Both always `Unsupported`: `ImageStreaming` is not registered by {@link layer}. */
|
|
76
|
+
readonly streamGeneration: ImageGeneratorService["streamGeneration"];
|
|
77
|
+
readonly streamEdit: ImageGeneratorService["streamEdit"];
|
|
78
|
+
};
|
|
79
|
+
declare const FalImageGenerator_base: Context.ServiceClass<FalImageGenerator, "@betalyra/effect-uai/providers/fal/FalImageGenerator", FalImageGeneratorService>;
|
|
80
|
+
/**
|
|
81
|
+
* Provider-typed service tag. Yield this for exact pixel sizes, seeds,
|
|
82
|
+
* diffusion knobs and the per-endpoint `input` passthrough; yield the
|
|
83
|
+
* generic `ImageGenerator` tag for provider-portable code. Both are
|
|
84
|
+
* registered by {@link layer}.
|
|
85
|
+
*/
|
|
86
|
+
declare class FalImageGenerator extends FalImageGenerator_base {}
|
|
87
|
+
type Config = {
|
|
88
|
+
readonly apiKey: Redacted.Redacted;
|
|
89
|
+
readonly baseUrl?: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The typed `imageSize` wins over the portable pair, which is then
|
|
93
|
+
* dropped. `undefined` means "send no size" and let the endpoint pick.
|
|
94
|
+
*/
|
|
95
|
+
declare const imageSizeOf: (request: Pick<FalImageGenerateRequest, "imageSize" | "aspectRatio" | "resolution">) => Effect.Effect<FalImageSize | undefined, AiError.AiError>;
|
|
96
|
+
type WireBody = Record<string, unknown> & {
|
|
97
|
+
readonly prompt: string;
|
|
98
|
+
};
|
|
99
|
+
declare const buildBody: (request: FalImageGenerateRequest, images: ReadonlyArray<ImageSource>, field?: string) => Effect.Effect<WireBody, AiError.AiError>;
|
|
100
|
+
/** A handful of endpoints return `image` singular; the rest return `images`. */
|
|
101
|
+
declare const Wire: Schema.Struct<{
|
|
102
|
+
readonly images: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
|
|
103
|
+
readonly url: Schema.String;
|
|
104
|
+
readonly content_type: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
105
|
+
readonly width: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
106
|
+
readonly height: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
107
|
+
readonly file_name: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
108
|
+
readonly file_size: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
109
|
+
}>>>>;
|
|
110
|
+
readonly image: Schema.optional<Schema.NullOr<Schema.Struct<{
|
|
111
|
+
readonly url: Schema.String;
|
|
112
|
+
readonly content_type: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
113
|
+
readonly width: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
114
|
+
readonly height: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
115
|
+
readonly file_name: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
116
|
+
readonly file_size: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
117
|
+
}>>>;
|
|
118
|
+
readonly has_nsfw_concepts: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Boolean>>>;
|
|
119
|
+
/** The seed a request resolved to, which is what makes a run repeatable. */
|
|
120
|
+
readonly seed: Schema.optional<Schema.NullOr<Schema.Number>>;
|
|
121
|
+
readonly timings: Schema.optional<Schema.NullOr<Schema.Unknown>>;
|
|
122
|
+
}>;
|
|
123
|
+
type Wire = typeof Wire.Type;
|
|
124
|
+
/**
|
|
125
|
+
* Decode from the raw body rather than parsed JSON, so a shape this
|
|
126
|
+
* adapter cannot read comes back with the body attached. Output schemas
|
|
127
|
+
* are per-model here, and "we could not read it" is unactionable without
|
|
128
|
+
* the thing we could not read.
|
|
129
|
+
*/
|
|
130
|
+
declare const decodeWire: (body: string) => Effect.Effect<Wire, AiError.AiError>;
|
|
131
|
+
declare const toResponse: (wire: Wire) => Effect.Effect<ImageResponse, AiError.AiError>;
|
|
132
|
+
/**
|
|
133
|
+
* Pixel dimensions and file metadata for one image, where the endpoint
|
|
134
|
+
* reported them. `providerData` is a shared slot, so this reads only the
|
|
135
|
+
* `fal` key and returns `None` for an image from another provider.
|
|
136
|
+
*/
|
|
137
|
+
declare const imageDataOf: (image: GeneratedImage) => Option.Option<FalImageData>;
|
|
138
|
+
/** The seed a request resolved to, plus timings and safety flags. */
|
|
139
|
+
declare const responseDataOf: (response: ImageResponse) => Option.Option<FalResponseData>;
|
|
140
|
+
declare const make: (cfg: Config) => Effect.Effect<FalImageGeneratorService, never, HttpClient.HttpClient>;
|
|
141
|
+
/**
|
|
142
|
+
* Layer registering the provider-typed tag and the generic
|
|
143
|
+
* `ImageGenerator`. A `CommonImageGenerateRequest` is structurally a
|
|
144
|
+
* `FalImageGenerateRequest` with no vendor knobs set, so the generic
|
|
145
|
+
* registration forwards directly.
|
|
146
|
+
*
|
|
147
|
+
* Does NOT register `ImageStreaming`.
|
|
148
|
+
*/
|
|
149
|
+
declare const layer: (cfg: Config) => Layer.Layer<FalImageGenerator | ImageGenerator, never, HttpClient.HttpClient>;
|
|
150
|
+
//#endregion
|
|
151
|
+
export { Config, FalImageData, FalImageEditRequest, FalImageGenerateRequest, FalImageGenerator, FalImageGeneratorService, FalImageSize, FalResponseData, WireBody, buildBody, decodeWire, imageDataOf, imageSizeOf, layer, make, responseDataOf, FalImageGenerator_d_exports as t, toResponse };
|
|
152
|
+
//# sourceMappingURL=FalImageGenerator.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FalImageGenerator.d.mts","names":[],"sources":["../src/FalImageGenerator.ts"],"mappings":";;;;;;;;;;KAqCY;WAAmC;WAAwB;;;;;;;;KAQlE;;WAEM,YAAY;WACZ;WACA;;WAEA;WACA;WACA;;;;;;WAMA;;;;;;;;WAQA,QAAQ;;KAGP,0BAA0B,KAAK;WAChC,OAAO;IACd;KAEQ,sBAAsB,KAAK;;WAE5B,OAAO;IACd;;;;;;cAOE,WAAS,OAAA;;;;;;KAMH,sBAAsB,UAAU;;cAGtC,cAAY,OAAA;;;;;;KAMN,yBAAyB,aAAa;KAEtC;WACD,WACP,SAAS,4BACN,OAAO,OAAO,eAAe,QAAQ;WACjC,OAAO,SAAS,wBAAwB,OAAO,OAAO,eAAe,QAAQ;;WAE7E,kBAAkB;WAClB,YAAY;;;;;;;;;cASV,0BAA0B;KAK3B;WACD,QAAQ,SAAS;WACjB;;;;;;cAgBE,cAAW,SACb,KAAK,yEACb,OAAO,OAAO,0BAA0B,QAAQ;KAmEvC,WAAW;WACZ;;cAGE,YAAS,SACX,yBAAuB,QACxB,cAAc,cAAY,mBAEjC,OAAO,OAAO,UAAU,QAAQ;;cA8C7B,MAAI,OAAA;;;;;;;;;;;;;;;;;;;;;;KAQL,cAAc,KAAK;;;;;;;cAUX,aAAU,iBAAmB,OAAO,OAAO,MAAM,QAAQ;cAqDzD,aAAU,MAAU,SAAO,OAAO,OAAO,eAAe,QAAQ;;;;;;cA6ChE,cAAW,OAAW,mBAAiB,OAAO,OAAO;;cAIrD,iBAAc,UAAc,kBAAgB,OAAO,OAAO;cAgG1D,OAAI,KACV,WACJ,OAAO,OAAO,iCAAiC,WAAW;;;;;;;;;cA0BhD,QAAK,KACX,WACJ,MAAM,MAAM,oBAAoB,uBAAuB,WAAW"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { httpError, missingImageField, transportFailure } from "./codec.mjs";
|
|
3
|
+
import { Context, Effect, Encoding, Layer, Match, Option, Redacted, Ref, Result, Schema, Stream, pipe } from "effect";
|
|
4
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
5
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
6
|
+
import * as Capabilities from "@effect-uai/core/Capabilities";
|
|
7
|
+
import { imageBase64, imageUrl, pixelsOf } from "@effect-uai/core/Image";
|
|
8
|
+
import { ImageGenerator } from "@effect-uai/core/ImageGenerator";
|
|
9
|
+
//#region src/FalImageGenerator.ts
|
|
10
|
+
var FalImageGenerator_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
FalImageGenerator: () => FalImageGenerator,
|
|
12
|
+
buildBody: () => buildBody,
|
|
13
|
+
decodeWire: () => decodeWire,
|
|
14
|
+
imageDataOf: () => imageDataOf,
|
|
15
|
+
imageSizeOf: () => imageSizeOf,
|
|
16
|
+
layer: () => layer,
|
|
17
|
+
make: () => make,
|
|
18
|
+
responseDataOf: () => responseDataOf,
|
|
19
|
+
toResponse: () => toResponse
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Per-image extras on `GeneratedImage.providerData.fal`. All optional:
|
|
23
|
+
* which of them an endpoint reports is per-model. Read with
|
|
24
|
+
* {@link imageDataOf}.
|
|
25
|
+
*/
|
|
26
|
+
const ImageData = Schema.Struct({
|
|
27
|
+
width: Schema.optional(Schema.Number),
|
|
28
|
+
height: Schema.optional(Schema.Number),
|
|
29
|
+
fileName: Schema.optional(Schema.String),
|
|
30
|
+
fileSize: Schema.optional(Schema.Number)
|
|
31
|
+
});
|
|
32
|
+
/** Response-level extras on `ImageResponse.providerData.fal`. */
|
|
33
|
+
const ResponseData = Schema.Struct({
|
|
34
|
+
/** The seed the request resolved to. Send it back for the same image. */
|
|
35
|
+
seed: Schema.optional(Schema.Number),
|
|
36
|
+
timings: Schema.optional(Schema.Unknown),
|
|
37
|
+
hasNsfwConcepts: Schema.optional(Schema.Array(Schema.Boolean))
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Provider-typed service tag. Yield this for exact pixel sizes, seeds,
|
|
41
|
+
* diffusion knobs and the per-endpoint `input` passthrough; yield the
|
|
42
|
+
* generic `ImageGenerator` tag for provider-portable code. Both are
|
|
43
|
+
* registered by {@link layer}.
|
|
44
|
+
*/
|
|
45
|
+
var FalImageGenerator = class extends Context.Service()("@betalyra/effect-uai/providers/fal/FalImageGenerator") {};
|
|
46
|
+
/**
|
|
47
|
+
* The typed `imageSize` wins over the portable pair, which is then
|
|
48
|
+
* dropped. `undefined` means "send no size" and let the endpoint pick.
|
|
49
|
+
*/
|
|
50
|
+
const imageSizeOf = (request) => Effect.gen(function* () {
|
|
51
|
+
if (request.imageSize !== void 0) {
|
|
52
|
+
const overridden = "`imageSize` sets the dimensions directly.";
|
|
53
|
+
yield* Capabilities.warnDroppedWhen(request.aspectRatio, {
|
|
54
|
+
provider: "fal",
|
|
55
|
+
capability: "aspectRatio",
|
|
56
|
+
field: "aspectRatio",
|
|
57
|
+
reason: overridden
|
|
58
|
+
});
|
|
59
|
+
yield* Capabilities.warnDroppedWhen(request.resolution, {
|
|
60
|
+
provider: "fal",
|
|
61
|
+
capability: "resolution",
|
|
62
|
+
field: "resolution",
|
|
63
|
+
reason: overridden
|
|
64
|
+
});
|
|
65
|
+
return request.imageSize;
|
|
66
|
+
}
|
|
67
|
+
return yield* Result.match(pixelsOf(request), {
|
|
68
|
+
onSuccess: Effect.succeed,
|
|
69
|
+
onFailure: (reason) => Effect.fail(new AiError.InvalidRequest({
|
|
70
|
+
provider: "fal",
|
|
71
|
+
param: "aspectRatio",
|
|
72
|
+
raw: `${reason} Set \`imageSize\` directly to bypass the derivation.`
|
|
73
|
+
}))
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* fal reads reference images from URLs, and a data URI is a URL, so
|
|
78
|
+
* every `ImageSource` has a wire form here. This is the one adapter
|
|
79
|
+
* where passing a `url` reference costs nothing.
|
|
80
|
+
*/
|
|
81
|
+
const referenceUrl = Match.type().pipe(Match.tag("url", (i) => i.url), Match.tag("base64", (i) => `data:${i.mimeType};base64,${i.base64}`), Match.tag("bytes", (i) => `data:${i.mimeType};base64,${Encoding.encodeBase64(i.bytes)}`), Match.exhaustive);
|
|
82
|
+
/** `_urls` takes the set, `_url` takes one. True of every spelling fal uses. */
|
|
83
|
+
const takesMany = (field) => field.endsWith("_urls");
|
|
84
|
+
/**
|
|
85
|
+
* References under the field the endpoint reads. A single-image field
|
|
86
|
+
* gets the last reference rather than the first: callers pass anchors
|
|
87
|
+
* before the image being changed, so the last is the one meant.
|
|
88
|
+
*/
|
|
89
|
+
const referenceFields = (images, field) => {
|
|
90
|
+
const last = images[images.length - 1];
|
|
91
|
+
return images.length === 0 || last === void 0 ? {} : takesMany(field) ? { [field]: images.map(referenceUrl) } : { [field]: referenceUrl(last) };
|
|
92
|
+
};
|
|
93
|
+
const buildBody = (request, images, field = "image_urls") => Effect.gen(function* () {
|
|
94
|
+
const imageSize = yield* imageSizeOf(request);
|
|
95
|
+
const references = referenceFields(images, field);
|
|
96
|
+
return {
|
|
97
|
+
prompt: request.prompt,
|
|
98
|
+
sync_mode: request.syncMode ?? true,
|
|
99
|
+
...imageSize !== void 0 && { image_size: imageSize },
|
|
100
|
+
...request.n !== void 0 && { num_images: request.n },
|
|
101
|
+
...request.seed !== void 0 && { seed: request.seed },
|
|
102
|
+
...request.outputFormat !== void 0 && { output_format: request.outputFormat },
|
|
103
|
+
...request.numInferenceSteps !== void 0 && { num_inference_steps: request.numInferenceSteps },
|
|
104
|
+
...request.guidanceScale !== void 0 && { guidance_scale: request.guidanceScale },
|
|
105
|
+
...request.enableSafetyChecker !== void 0 && { enable_safety_checker: request.enableSafetyChecker },
|
|
106
|
+
...references,
|
|
107
|
+
...request.input
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Every field is optional *and* nullable, because fal's per-model
|
|
112
|
+
* schemas disagree about both: one endpoint declares `width` required,
|
|
113
|
+
* the next declares it nullable and sends `null`. Only `url` is
|
|
114
|
+
* dependable. Anything beyond it reaches the caller through
|
|
115
|
+
* `providerData` rather than a promised field.
|
|
116
|
+
*/
|
|
117
|
+
const ImageFile = Schema.Struct({
|
|
118
|
+
url: Schema.String,
|
|
119
|
+
content_type: Schema.optional(Schema.NullOr(Schema.String)),
|
|
120
|
+
width: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
121
|
+
height: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
122
|
+
file_name: Schema.optional(Schema.NullOr(Schema.String)),
|
|
123
|
+
file_size: Schema.optional(Schema.NullOr(Schema.Number))
|
|
124
|
+
});
|
|
125
|
+
/** A handful of endpoints return `image` singular; the rest return `images`. */
|
|
126
|
+
const Wire = Schema.Struct({
|
|
127
|
+
images: Schema.optional(Schema.NullOr(Schema.Array(ImageFile))),
|
|
128
|
+
image: Schema.optional(Schema.NullOr(ImageFile)),
|
|
129
|
+
has_nsfw_concepts: Schema.optional(Schema.NullOr(Schema.Array(Schema.Boolean))),
|
|
130
|
+
/** The seed a request resolved to, which is what makes a run repeatable. */
|
|
131
|
+
seed: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
132
|
+
timings: Schema.optional(Schema.NullOr(Schema.Unknown))
|
|
133
|
+
});
|
|
134
|
+
const decodeBody = Schema.decodeUnknownEffect(Schema.fromJsonString(Wire));
|
|
135
|
+
/**
|
|
136
|
+
* Decode from the raw body rather than parsed JSON, so a shape this
|
|
137
|
+
* adapter cannot read comes back with the body attached. Output schemas
|
|
138
|
+
* are per-model here, and "we could not read it" is unactionable without
|
|
139
|
+
* the thing we could not read.
|
|
140
|
+
*/
|
|
141
|
+
const decodeWire = (body) => decodeBody(body).pipe(Effect.mapError(() => new AiError.GenerationFailed({
|
|
142
|
+
provider: "fal",
|
|
143
|
+
message: "fal returned a body with no readable image field.",
|
|
144
|
+
raw: body.length > 2e3 ? `${body.slice(0, 2e3)}… (${body.length} bytes)` : body
|
|
145
|
+
})));
|
|
146
|
+
const DATA_URI = /^data:([^;,]+);base64,(.*)$/s;
|
|
147
|
+
/** `sync_mode` returns a data URI; without it, a link that expires. */
|
|
148
|
+
const dataUri = (url) => pipe(Option.fromNullOr(DATA_URI.exec(url)), Option.flatMap(([, mimeType, base64]) => mimeType !== void 0 && base64 !== void 0 ? Option.some([mimeType, base64]) : Option.none()));
|
|
149
|
+
/** Only what the endpoint actually reported; absent keys stay absent. */
|
|
150
|
+
const imageExtras = (file) => ({
|
|
151
|
+
...file.width != null && { width: file.width },
|
|
152
|
+
...file.height != null && { height: file.height },
|
|
153
|
+
...file.file_name != null && { fileName: file.file_name },
|
|
154
|
+
...file.file_size != null && { fileSize: file.file_size }
|
|
155
|
+
});
|
|
156
|
+
/** No fal image model documents a watermark, so none is claimed. */
|
|
157
|
+
const generatedImage = (file) => {
|
|
158
|
+
const extras = imageExtras(file);
|
|
159
|
+
return {
|
|
160
|
+
image: Option.match(dataUri(file.url), {
|
|
161
|
+
onNone: () => imageUrl(file.url, file.content_type ?? void 0),
|
|
162
|
+
onSome: ([mimeType, base64]) => imageBase64(base64, mimeType)
|
|
163
|
+
}),
|
|
164
|
+
...Object.keys(extras).length > 0 && { providerData: { fal: extras } }
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
/** Every image flagged means nothing usable came back, which is a refusal. */
|
|
168
|
+
const refused = (wire) => {
|
|
169
|
+
const flags = wire.has_nsfw_concepts ?? [];
|
|
170
|
+
return flags.length > 0 && flags.every((flagged) => flagged);
|
|
171
|
+
};
|
|
172
|
+
const toResponse = (wire) => {
|
|
173
|
+
const files = [...wire.images ?? [], ...wire.image == null ? [] : [wire.image]];
|
|
174
|
+
if (refused(wire)) return Effect.fail(new AiError.ContentFiltered({
|
|
175
|
+
provider: "fal",
|
|
176
|
+
reason: "The safety checker flagged every generated image.",
|
|
177
|
+
raw: wire
|
|
178
|
+
}));
|
|
179
|
+
const extras = {
|
|
180
|
+
...wire.seed != null && { seed: wire.seed },
|
|
181
|
+
...wire.timings != null && { timings: wire.timings },
|
|
182
|
+
...wire.has_nsfw_concepts != null && { hasNsfwConcepts: wire.has_nsfw_concepts }
|
|
183
|
+
};
|
|
184
|
+
return files.length === 0 ? Effect.fail(new AiError.GenerationFailed({
|
|
185
|
+
provider: "fal",
|
|
186
|
+
message: "fal returned no image.",
|
|
187
|
+
raw: wire
|
|
188
|
+
})) : Effect.succeed({
|
|
189
|
+
images: files.map(generatedImage),
|
|
190
|
+
usage: {},
|
|
191
|
+
...Object.keys(extras).length > 0 && { providerData: { fal: extras } }
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
const decodeImageData = Schema.decodeUnknownOption(Schema.Struct({ fal: ImageData }));
|
|
195
|
+
const decodeResponseData = Schema.decodeUnknownOption(Schema.Struct({ fal: ResponseData }));
|
|
196
|
+
/**
|
|
197
|
+
* Pixel dimensions and file metadata for one image, where the endpoint
|
|
198
|
+
* reported them. `providerData` is a shared slot, so this reads only the
|
|
199
|
+
* `fal` key and returns `None` for an image from another provider.
|
|
200
|
+
*/
|
|
201
|
+
const imageDataOf = (image) => Option.map(decodeImageData(image.providerData), (d) => d.fal);
|
|
202
|
+
/** The seed a request resolved to, plus timings and safety flags. */
|
|
203
|
+
const responseDataOf = (response) => Option.map(decodeResponseData(response.providerData), (d) => d.fal);
|
|
204
|
+
const host = (cfg) => cfg.baseUrl ?? "https://fal.run";
|
|
205
|
+
const attempt = (cfg, model, request, images, field) => Effect.gen(function* () {
|
|
206
|
+
const client = yield* HttpClient.HttpClient;
|
|
207
|
+
const body = yield* buildBody(request, images, field);
|
|
208
|
+
const response = yield* client.execute(HttpClientRequest.post(`${host(cfg)}/${model}`).pipe(HttpClientRequest.setHeader("authorization", `Key ${Redacted.value(cfg.apiKey)}`), HttpClientRequest.bodyJsonUnsafe(body))).pipe(Effect.mapError(transportFailure));
|
|
209
|
+
const text = yield* response.text.pipe(Effect.mapError(transportFailure));
|
|
210
|
+
return {
|
|
211
|
+
status: response.status,
|
|
212
|
+
body: text
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
const finish = (attempt) => attempt.status >= 400 ? Effect.flatMap(httpError(attempt.status, attempt.body), Effect.fail) : Effect.flatMap(decodeWire(attempt.body), toResponse);
|
|
216
|
+
/**
|
|
217
|
+
* `learned` remembers what an endpoint asked for, so only the first call
|
|
218
|
+
* to one this adapter has no mapping for pays for the correction. The
|
|
219
|
+
* retry is free of inference cost: fal validates before it runs anything.
|
|
220
|
+
*/
|
|
221
|
+
const requestImages = (cfg, model, request, images, learned) => Effect.gen(function* () {
|
|
222
|
+
const field = (yield* Ref.get(learned))[model] ?? "image_urls";
|
|
223
|
+
const first = yield* attempt(cfg, model, request, images, field);
|
|
224
|
+
if (first.status < 400 || images.length === 0) return yield* finish(first);
|
|
225
|
+
const asked = yield* Effect.map(missingImageField(first.body), (name) => Option.flatMap(name, (wanted) => wanted === field ? Option.none() : Option.some(wanted)));
|
|
226
|
+
if (Option.isNone(asked)) return yield* finish(first);
|
|
227
|
+
yield* Ref.update(learned, (known) => ({
|
|
228
|
+
...known,
|
|
229
|
+
[model]: asked.value
|
|
230
|
+
}));
|
|
231
|
+
return yield* Effect.flatMap(attempt(cfg, model, request, images, asked.value), finish);
|
|
232
|
+
});
|
|
233
|
+
/**
|
|
234
|
+
* fal's `/stream` endpoint exists, but the events it carries are defined
|
|
235
|
+
* by each model rather than by fal, and no image endpoint documents
|
|
236
|
+
* partial images. {@link layer} therefore does not register
|
|
237
|
+
* `ImageStreaming`, and calling the top-level `streamGeneration` helper
|
|
238
|
+
* against this Layer alone fails to typecheck; this branch covers the
|
|
239
|
+
* service being used directly.
|
|
240
|
+
*/
|
|
241
|
+
const streamUnsupported = (capability) => Stream.fail(new AiError.Unsupported({
|
|
242
|
+
provider: "fal",
|
|
243
|
+
capability,
|
|
244
|
+
reason: "No fal image endpoint documents a partial-image stream. Use `generate` or `edit` and render when it resolves."
|
|
245
|
+
}));
|
|
246
|
+
const make = (cfg) => Effect.gen(function* () {
|
|
247
|
+
const client = yield* HttpClient.HttpClient;
|
|
248
|
+
const learned = yield* Ref.make({});
|
|
249
|
+
return {
|
|
250
|
+
generate: (request) => requestImages(cfg, request.model, request, [], learned).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
251
|
+
edit: (request) => requestImages(cfg, request.model, request, request.images, learned).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
252
|
+
streamGeneration: () => streamUnsupported("streamGeneration"),
|
|
253
|
+
streamEdit: () => streamUnsupported("streamEdit")
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
/**
|
|
257
|
+
* Layer registering the provider-typed tag and the generic
|
|
258
|
+
* `ImageGenerator`. A `CommonImageGenerateRequest` is structurally a
|
|
259
|
+
* `FalImageGenerateRequest` with no vendor knobs set, so the generic
|
|
260
|
+
* registration forwards directly.
|
|
261
|
+
*
|
|
262
|
+
* Does NOT register `ImageStreaming`.
|
|
263
|
+
*/
|
|
264
|
+
const layer = (cfg) => Layer.merge(Layer.effect(FalImageGenerator, make(cfg)), Layer.effect(ImageGenerator, Effect.map(make(cfg), (s) => ({
|
|
265
|
+
generate: (request) => s.generate(request),
|
|
266
|
+
edit: (request) => s.edit(request),
|
|
267
|
+
streamGeneration: (_request) => streamUnsupported("streamGeneration"),
|
|
268
|
+
streamEdit: (_request) => streamUnsupported("streamEdit")
|
|
269
|
+
}))));
|
|
270
|
+
//#endregion
|
|
271
|
+
export { FalImageGenerator, buildBody, decodeWire, imageDataOf, imageSizeOf, layer, make, responseDataOf, FalImageGenerator_exports as t, toResponse };
|
|
272
|
+
|
|
273
|
+
//# sourceMappingURL=FalImageGenerator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FalImageGenerator.mjs","names":[],"sources":["../src/FalImageGenerator.ts"],"sourcesContent":["import {\n Context,\n Effect,\n Encoding,\n Layer,\n Match,\n Option,\n Redacted,\n Ref,\n Result,\n Schema,\n Stream,\n pipe,\n} from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport type { GeneratedImage, ImageSource } from \"@effect-uai/core/Image\"\nimport { imageBase64, imageUrl, pixelsOf } from \"@effect-uai/core/Image\"\nimport type {\n CommonImageEditRequest,\n CommonImageGenerateRequest,\n CommonStreamImageEditRequest,\n CommonStreamImageRequest,\n ImageGeneratorService,\n ImageResponse,\n ImageStreamEvent,\n} from \"@effect-uai/core/ImageGenerator\"\nimport { ImageGenerator } from \"@effect-uai/core/ImageGenerator\"\nimport { PROVIDER, httpError, missingImageField, transportFailure } from \"./codec.js\"\nimport type { FalImageEditModel, FalImageModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** A named preset (`\"landscape_4_3\"`, `\"auto_2K\"`, …) or exact pixels. */\nexport type FalImageSize = string | { readonly width: number; readonly height: number }\n\n/**\n * Wire knobs shared by most fal image endpoints. fal is not one API but a\n * catalogue of models behind one envelope, so only `prompt`, `num_images`\n * and the output envelope are dependable across all of them; the fields\n * here are the ones the image endpoints agree on.\n */\ntype FalImageKnobs = {\n /** Sent as `image_size`. Takes precedence over the portable ratio and tier. */\n readonly imageSize?: FalImageSize\n readonly seed?: number\n readonly outputFormat?: \"jpeg\" | \"png\" | \"webp\"\n /** Diffusion knobs, open-weights models only. */\n readonly numInferenceSteps?: number\n readonly guidanceScale?: number\n readonly enableSafetyChecker?: boolean\n /**\n * `false` returns fal CDN links instead of inline bytes. The default is\n * `true`: every other adapter here hands back bytes, and fal's links\n * expire.\n */\n readonly syncMode?: boolean\n /**\n * Fields only one endpoint takes: `acceleration`, `loras`,\n * `safety_tolerance`, `thinking_level`, `aspect_ratio`. Merged last, so\n * it also overrides anything derived above. Snake case, as the wire\n * spells it. The escape hatch exists because fal's schemas are\n * per-model and tabling them would go stale in a week.\n */\n readonly input?: Record<string, unknown>\n}\n\nexport type FalImageGenerateRequest = Omit<CommonImageGenerateRequest, \"model\"> & {\n readonly model: FalImageModel\n} & FalImageKnobs\n\nexport type FalImageEditRequest = Omit<CommonImageEditRequest, \"model\"> & {\n /** Edit is a *different endpoint* on fal, not a flag on the same one. */\n readonly model: FalImageEditModel\n} & FalImageKnobs\n\n/**\n * Per-image extras on `GeneratedImage.providerData.fal`. All optional:\n * which of them an endpoint reports is per-model. Read with\n * {@link imageDataOf}.\n */\nconst ImageData = Schema.Struct({\n width: Schema.optional(Schema.Number),\n height: Schema.optional(Schema.Number),\n fileName: Schema.optional(Schema.String),\n fileSize: Schema.optional(Schema.Number),\n})\nexport type FalImageData = typeof ImageData.Type\n\n/** Response-level extras on `ImageResponse.providerData.fal`. */\nconst ResponseData = Schema.Struct({\n /** The seed the request resolved to. Send it back for the same image. */\n seed: Schema.optional(Schema.Number),\n timings: Schema.optional(Schema.Unknown),\n hasNsfwConcepts: Schema.optional(Schema.Array(Schema.Boolean)),\n})\nexport type FalResponseData = typeof ResponseData.Type\n\nexport type FalImageGeneratorService = {\n readonly generate: (\n request: FalImageGenerateRequest,\n ) => Effect.Effect<ImageResponse, AiError.AiError>\n readonly edit: (request: FalImageEditRequest) => Effect.Effect<ImageResponse, AiError.AiError>\n /** Both always `Unsupported`: `ImageStreaming` is not registered by {@link layer}. */\n readonly streamGeneration: ImageGeneratorService[\"streamGeneration\"]\n readonly streamEdit: ImageGeneratorService[\"streamEdit\"]\n}\n\n/**\n * Provider-typed service tag. Yield this for exact pixel sizes, seeds,\n * diffusion knobs and the per-endpoint `input` passthrough; yield the\n * generic `ImageGenerator` tag for provider-portable code. Both are\n * registered by {@link layer}.\n */\nexport class FalImageGenerator extends Context.Service<\n FalImageGenerator,\n FalImageGeneratorService\n>()(\"@betalyra/effect-uai/providers/fal/FalImageGenerator\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - size\n//\n// The portable request carries a ratio and a tier; fal endpoints take\n// `image_size`, either a named preset or `{ width, height }`. Pixels are\n// derived because the preset names differ per model family while the\n// object form is the shape they share.\n// ---------------------------------------------------------------------------\n\n/**\n * The typed `imageSize` wins over the portable pair, which is then\n * dropped. `undefined` means \"send no size\" and let the endpoint pick.\n */\nexport const imageSizeOf = (\n request: Pick<FalImageGenerateRequest, \"imageSize\" | \"aspectRatio\" | \"resolution\">,\n): Effect.Effect<FalImageSize | undefined, AiError.AiError> =>\n Effect.gen(function* () {\n if (request.imageSize !== undefined) {\n const overridden = \"`imageSize` sets the dimensions directly.\"\n yield* Capabilities.warnDroppedWhen(request.aspectRatio, {\n provider: PROVIDER,\n capability: \"aspectRatio\",\n field: \"aspectRatio\",\n reason: overridden,\n })\n yield* Capabilities.warnDroppedWhen(request.resolution, {\n provider: PROVIDER,\n capability: \"resolution\",\n field: \"resolution\",\n reason: overridden,\n })\n return request.imageSize\n }\n return yield* Result.match(pixelsOf(request), {\n onSuccess: Effect.succeed<FalImageSize | undefined>,\n onFailure: (reason) =>\n Effect.fail(\n new AiError.InvalidRequest({\n provider: PROVIDER,\n param: \"aspectRatio\",\n raw: `${reason} Set \\`imageSize\\` directly to bypass the derivation.`,\n }),\n ),\n })\n })\n\n// ---------------------------------------------------------------------------\n// Codec - request\n// ---------------------------------------------------------------------------\n\n/**\n * fal reads reference images from URLs, and a data URI is a URL, so\n * every `ImageSource` has a wire form here. This is the one adapter\n * where passing a `url` reference costs nothing.\n */\nconst referenceUrl: (image: ImageSource) => string = Match.type<ImageSource>().pipe(\n Match.tag(\"url\", (i) => i.url),\n Match.tag(\"base64\", (i) => `data:${i.mimeType};base64,${i.base64}`),\n Match.tag(\"bytes\", (i) => `data:${i.mimeType};base64,${Encoding.encodeBase64(i.bytes)}`),\n Match.exhaustive,\n)\n\n/** `_urls` takes the set, `_url` takes one. True of every spelling fal uses. */\nconst takesMany = (field: string): boolean => field.endsWith(\"_urls\")\n\n/**\n * References under the field the endpoint reads. A single-image field\n * gets the last reference rather than the first: callers pass anchors\n * before the image being changed, so the last is the one meant.\n */\nconst referenceFields = (\n images: ReadonlyArray<ImageSource>,\n field: string,\n): Record<string, unknown> => {\n const last = images[images.length - 1]\n return images.length === 0 || last === undefined\n ? {}\n : takesMany(field)\n ? { [field]: images.map(referenceUrl) }\n : { [field]: referenceUrl(last) }\n}\n\nexport type WireBody = Record<string, unknown> & {\n readonly prompt: string\n}\n\nexport const buildBody = (\n request: FalImageGenerateRequest,\n images: ReadonlyArray<ImageSource>,\n field: string = \"image_urls\",\n): Effect.Effect<WireBody, AiError.AiError> =>\n Effect.gen(function* () {\n const imageSize = yield* imageSizeOf(request)\n const references = referenceFields(images, field)\n return {\n prompt: request.prompt,\n // Inline bytes rather than an expiring CDN link, unless asked otherwise.\n sync_mode: request.syncMode ?? true,\n ...(imageSize !== undefined && { image_size: imageSize }),\n ...(request.n !== undefined && { num_images: request.n }),\n ...(request.seed !== undefined && { seed: request.seed }),\n ...(request.outputFormat !== undefined && { output_format: request.outputFormat }),\n ...(request.numInferenceSteps !== undefined && {\n num_inference_steps: request.numInferenceSteps,\n }),\n ...(request.guidanceScale !== undefined && { guidance_scale: request.guidanceScale }),\n ...(request.enableSafetyChecker !== undefined && {\n enable_safety_checker: request.enableSafetyChecker,\n }),\n ...references,\n ...request.input,\n }\n })\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\n/**\n * Every field is optional *and* nullable, because fal's per-model\n * schemas disagree about both: one endpoint declares `width` required,\n * the next declares it nullable and sends `null`. Only `url` is\n * dependable. Anything beyond it reaches the caller through\n * `providerData` rather than a promised field.\n */\nconst ImageFile = Schema.Struct({\n url: Schema.String,\n content_type: Schema.optional(Schema.NullOr(Schema.String)),\n width: Schema.optional(Schema.NullOr(Schema.Number)),\n height: Schema.optional(Schema.NullOr(Schema.Number)),\n file_name: Schema.optional(Schema.NullOr(Schema.String)),\n file_size: Schema.optional(Schema.NullOr(Schema.Number)),\n})\ntype ImageFile = typeof ImageFile.Type\n\n/** A handful of endpoints return `image` singular; the rest return `images`. */\nconst Wire = Schema.Struct({\n images: Schema.optional(Schema.NullOr(Schema.Array(ImageFile))),\n image: Schema.optional(Schema.NullOr(ImageFile)),\n has_nsfw_concepts: Schema.optional(Schema.NullOr(Schema.Array(Schema.Boolean))),\n /** The seed a request resolved to, which is what makes a run repeatable. */\n seed: Schema.optional(Schema.NullOr(Schema.Number)),\n timings: Schema.optional(Schema.NullOr(Schema.Unknown)),\n})\ntype Wire = typeof Wire.Type\n\nconst decodeBody = Schema.decodeUnknownEffect(Schema.fromJsonString(Wire))\n\n/**\n * Decode from the raw body rather than parsed JSON, so a shape this\n * adapter cannot read comes back with the body attached. Output schemas\n * are per-model here, and \"we could not read it\" is unactionable without\n * the thing we could not read.\n */\nexport const decodeWire = (body: string): Effect.Effect<Wire, AiError.AiError> =>\n decodeBody(body).pipe(\n Effect.mapError(\n () =>\n new AiError.GenerationFailed({\n provider: PROVIDER,\n message: \"fal returned a body with no readable image field.\",\n // `sync_mode` puts a whole image in the body, so the head is\n // what gets attached: enough to see the shape, not megabytes.\n raw: body.length > 2000 ? `${body.slice(0, 2000)}… (${body.length} bytes)` : body,\n }),\n ),\n )\n\nconst DATA_URI = /^data:([^;,]+);base64,(.*)$/s\n\n/** `sync_mode` returns a data URI; without it, a link that expires. */\nconst dataUri = (url: string): Option.Option<readonly [string, string]> =>\n pipe(\n Option.fromNullOr(DATA_URI.exec(url)),\n Option.flatMap(([, mimeType, base64]) =>\n mimeType !== undefined && base64 !== undefined\n ? Option.some([mimeType, base64] as const)\n : Option.none(),\n ),\n )\n\n/** Only what the endpoint actually reported; absent keys stay absent. */\nconst imageExtras = (file: ImageFile): FalImageData => ({\n ...(file.width != null && { width: file.width }),\n ...(file.height != null && { height: file.height }),\n ...(file.file_name != null && { fileName: file.file_name }),\n ...(file.file_size != null && { fileSize: file.file_size }),\n})\n\n/** No fal image model documents a watermark, so none is claimed. */\nconst generatedImage = (file: ImageFile): GeneratedImage => {\n const extras = imageExtras(file)\n return {\n image: Option.match(dataUri(file.url), {\n onNone: () => imageUrl(file.url, file.content_type ?? undefined),\n onSome: ([mimeType, base64]) => imageBase64(base64, mimeType),\n }),\n ...(Object.keys(extras).length > 0 && { providerData: { fal: extras } }),\n }\n}\n\n/** Every image flagged means nothing usable came back, which is a refusal. */\nconst refused = (wire: Wire): boolean => {\n const flags = wire.has_nsfw_concepts ?? []\n return flags.length > 0 && flags.every((flagged) => flagged)\n}\n\nexport const toResponse = (wire: Wire): Effect.Effect<ImageResponse, AiError.AiError> => {\n const files = [...(wire.images ?? []), ...(wire.image == null ? [] : [wire.image])]\n if (refused(wire)) {\n return Effect.fail(\n new AiError.ContentFiltered({\n provider: PROVIDER,\n reason: \"The safety checker flagged every generated image.\",\n raw: wire,\n }),\n )\n }\n const extras: FalResponseData = {\n ...(wire.seed != null && { seed: wire.seed }),\n ...(wire.timings != null && { timings: wire.timings }),\n ...(wire.has_nsfw_concepts != null && { hasNsfwConcepts: wire.has_nsfw_concepts }),\n }\n // fal bills per image and per megapixel, not per token, so there is no\n // usage to report.\n return files.length === 0\n ? Effect.fail(\n new AiError.GenerationFailed({\n provider: PROVIDER,\n message: \"fal returned no image.\",\n raw: wire,\n }),\n )\n : Effect.succeed({\n images: files.map(generatedImage),\n usage: {},\n ...(Object.keys(extras).length > 0 && { providerData: { fal: extras } }),\n })\n}\n\n// ---------------------------------------------------------------------------\n// Reading the extras\n// ---------------------------------------------------------------------------\n\nconst decodeImageData = Schema.decodeUnknownOption(Schema.Struct({ fal: ImageData }))\nconst decodeResponseData = Schema.decodeUnknownOption(Schema.Struct({ fal: ResponseData }))\n\n/**\n * Pixel dimensions and file metadata for one image, where the endpoint\n * reported them. `providerData` is a shared slot, so this reads only the\n * `fal` key and returns `None` for an image from another provider.\n */\nexport const imageDataOf = (image: GeneratedImage): Option.Option<FalImageData> =>\n Option.map(decodeImageData(image.providerData), (d) => d.fal)\n\n/** The seed a request resolved to, plus timings and safety flags. */\nexport const responseDataOf = (response: ImageResponse): Option.Option<FalResponseData> =>\n Option.map(decodeResponseData(response.providerData), (d) => d.fal)\n\n// ---------------------------------------------------------------------------\n// HTTP\n//\n// The synchronous endpoint: one request, one response. The queue API\n// (`queue.fal.run`) exists for jobs long enough to outlive a connection,\n// which image generation is not.\n// ---------------------------------------------------------------------------\n\nconst host = (cfg: Config): string => cfg.baseUrl ?? \"https://fal.run\"\n\ntype Attempt = { readonly status: number; readonly body: string }\n\nconst attempt = (\n cfg: Config,\n model: string,\n request: FalImageGenerateRequest,\n images: ReadonlyArray<ImageSource>,\n field: string,\n): Effect.Effect<Attempt, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const body = yield* buildBody(request, images, field)\n const response = yield* client\n .execute(\n HttpClientRequest.post(`${host(cfg)}/${model}`).pipe(\n HttpClientRequest.setHeader(\"authorization\", `Key ${Redacted.value(cfg.apiKey)}`),\n HttpClientRequest.bodyJsonUnsafe(body),\n ),\n )\n .pipe(Effect.mapError(transportFailure))\n const text = yield* response.text.pipe(Effect.mapError(transportFailure))\n return { status: response.status, body: text }\n })\n\nconst finish = (attempt: Attempt): Effect.Effect<ImageResponse, AiError.AiError> =>\n attempt.status >= 400\n ? Effect.flatMap(httpError(attempt.status, attempt.body), Effect.fail)\n : Effect.flatMap(decodeWire(attempt.body), toResponse)\n\n/**\n * `learned` remembers what an endpoint asked for, so only the first call\n * to one this adapter has no mapping for pays for the correction. The\n * retry is free of inference cost: fal validates before it runs anything.\n */\nconst requestImages = (\n cfg: Config,\n model: string,\n request: FalImageGenerateRequest,\n images: ReadonlyArray<ImageSource>,\n learned: Ref.Ref<Record<string, string>>,\n): Effect.Effect<ImageResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n // Current endpoints take `image_urls`; older ones want `image_url` or\n // another spelling, which their 422 names. Neither the id nor the\n // owner namespace predicts it, so guess and correct.\n const remembered = yield* Ref.get(learned)\n const field = remembered[model] ?? \"image_urls\"\n const first = yield* attempt(cfg, model, request, images, field)\n if (first.status < 400 || images.length === 0) return yield* finish(first)\n\n const asked = yield* Effect.map(missingImageField(first.body), (name) =>\n Option.flatMap(name, (wanted) =>\n wanted === field ? Option.none<string>() : Option.some(wanted),\n ),\n )\n if (Option.isNone(asked)) return yield* finish(first)\n\n yield* Ref.update(learned, (known) => ({ ...known, [model]: asked.value }))\n return yield* Effect.flatMap(attempt(cfg, model, request, images, asked.value), finish)\n })\n\n/**\n * fal's `/stream` endpoint exists, but the events it carries are defined\n * by each model rather than by fal, and no image endpoint documents\n * partial images. {@link layer} therefore does not register\n * `ImageStreaming`, and calling the top-level `streamGeneration` helper\n * against this Layer alone fails to typecheck; this branch covers the\n * service being used directly.\n */\nconst streamUnsupported = (capability: string): Stream.Stream<ImageStreamEvent, AiError.AiError> =>\n Stream.fail(\n new AiError.Unsupported({\n provider: PROVIDER,\n capability,\n reason:\n \"No fal image endpoint documents a partial-image stream. Use `generate` or `edit` and render when it resolves.\",\n }),\n )\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\nexport const make = (\n cfg: Config,\n): Effect.Effect<FalImageGeneratorService, never, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const learned = yield* Ref.make<Record<string, string>>({})\n return {\n generate: (request) =>\n requestImages(cfg, request.model, request, [], learned).pipe(\n Effect.provideService(HttpClient.HttpClient, client),\n ),\n edit: (request) =>\n requestImages(cfg, request.model, request, request.images, learned).pipe(\n Effect.provideService(HttpClient.HttpClient, client),\n ),\n streamGeneration: () => streamUnsupported(\"streamGeneration\"),\n streamEdit: () => streamUnsupported(\"streamEdit\"),\n }\n })\n\n/**\n * Layer registering the provider-typed tag and the generic\n * `ImageGenerator`. A `CommonImageGenerateRequest` is structurally a\n * `FalImageGenerateRequest` with no vendor knobs set, so the generic\n * registration forwards directly.\n *\n * Does NOT register `ImageStreaming`.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<FalImageGenerator | ImageGenerator, never, HttpClient.HttpClient> =>\n Layer.merge(\n Layer.effect(FalImageGenerator, make(cfg)),\n Layer.effect(\n ImageGenerator,\n Effect.map(make(cfg), (s): ImageGeneratorService => ({\n generate: (request: CommonImageGenerateRequest) =>\n s.generate(request as FalImageGenerateRequest),\n edit: (request: CommonImageEditRequest) => s.edit(request as FalImageEditRequest),\n streamGeneration: (_request: CommonStreamImageRequest) =>\n streamUnsupported(\"streamGeneration\"),\n streamEdit: (_request: CommonStreamImageEditRequest) => streamUnsupported(\"streamEdit\"),\n })),\n ),\n )\n"],"mappings":"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"}
|
package/dist/codec.d.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
3
|
+
declare namespace codec_d_exports {
|
|
4
|
+
export { PROVIDER, httpError, missingImageField, transportFailure };
|
|
5
|
+
}
|
|
6
|
+
declare const PROVIDER = "fal";
|
|
7
|
+
declare const transportFailure: (cause: unknown) => AiError.AiError;
|
|
8
|
+
/** Never fails: the returned error is the value. */
|
|
9
|
+
declare const httpError: (status: number, body: string) => Effect.Effect<AiError.AiError>;
|
|
10
|
+
/**
|
|
11
|
+
* The reference field this endpoint asked for, if it rejected the request
|
|
12
|
+
* for want of one. `Effect` only because the decode is; it never fails.
|
|
13
|
+
*/
|
|
14
|
+
declare const missingImageField: (body: string) => Effect.Effect<Option.Option<string>>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { PROVIDER, httpError, missingImageField, codec_d_exports as t, transportFailure };
|
|
17
|
+
//# sourceMappingURL=codec.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;cAGa;cAEA,mBAAgB,mBAAqB,QAAQ;;cA2F7C,YAAS,gBAAkB,iBAAiB,OAAO,OAAO,QAAQ;;;;;cAsBlE,oBAAiB,iBAAmB,OAAO,OAAO,OAAO"}
|