@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/dist/codec.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { Array, Effect, Match, Option, Schema, pipe } from "effect";
|
|
3
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
4
|
+
//#region src/codec.ts
|
|
5
|
+
var codec_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
PROVIDER: () => "fal",
|
|
7
|
+
httpError: () => httpError,
|
|
8
|
+
missingImageField: () => missingImageField,
|
|
9
|
+
transportFailure: () => transportFailure
|
|
10
|
+
});
|
|
11
|
+
const PROVIDER = "fal";
|
|
12
|
+
const transportFailure = (cause) => new AiError.Unavailable({
|
|
13
|
+
provider: "fal",
|
|
14
|
+
raw: cause
|
|
15
|
+
});
|
|
16
|
+
const Detail = Schema.Struct({
|
|
17
|
+
loc: Schema.optional(Schema.Array(Schema.Unknown)),
|
|
18
|
+
msg: Schema.optional(Schema.String),
|
|
19
|
+
type: Schema.optional(Schema.String)
|
|
20
|
+
});
|
|
21
|
+
/** Wrapped in `detail` off the gateway, bare off the runner. */
|
|
22
|
+
const WireError = Schema.Union([Schema.Struct({ detail: Schema.Array(Detail) }), Schema.Array(Detail)]);
|
|
23
|
+
const decodeWireError = Schema.decodeUnknownEffect(Schema.fromJsonString(WireError));
|
|
24
|
+
const detailsOf = (wire) => "detail" in wire ? wire.detail : wire;
|
|
25
|
+
/** The one refusal `type` fal documents; every other type is a bad field. */
|
|
26
|
+
const CONTENT_POLICY = "content_policy_violation";
|
|
27
|
+
const refusalOf = (wire, raw) => pipe(detailsOf(wire), Array.findFirst((detail) => detail.type === CONTENT_POLICY), Option.map((detail) => new AiError.ContentFiltered({
|
|
28
|
+
provider: "fal",
|
|
29
|
+
...detail.msg !== void 0 && { reason: detail.msg },
|
|
30
|
+
raw
|
|
31
|
+
})));
|
|
32
|
+
const statusError = (status, body) => Match.value(status).pipe(Match.when(429, () => new AiError.RateLimited({
|
|
33
|
+
provider: "fal",
|
|
34
|
+
raw: body
|
|
35
|
+
})), Match.whenOr(408, 504, () => new AiError.Timeout({
|
|
36
|
+
provider: "fal",
|
|
37
|
+
raw: body
|
|
38
|
+
})), Match.when(401, () => new AiError.AuthFailed({
|
|
39
|
+
provider: "fal",
|
|
40
|
+
subtype: "auth",
|
|
41
|
+
raw: body
|
|
42
|
+
})), Match.when(403, () => new AiError.AuthFailed({
|
|
43
|
+
provider: "fal",
|
|
44
|
+
subtype: "permission",
|
|
45
|
+
raw: body
|
|
46
|
+
})), Match.when(402, () => new AiError.AuthFailed({
|
|
47
|
+
provider: "fal",
|
|
48
|
+
subtype: "billing",
|
|
49
|
+
raw: body
|
|
50
|
+
})), Match.when(413, () => new AiError.ContextLengthExceeded({
|
|
51
|
+
provider: "fal",
|
|
52
|
+
raw: body
|
|
53
|
+
})), Match.when((n) => n >= 500, (n) => new AiError.Unavailable({
|
|
54
|
+
provider: "fal",
|
|
55
|
+
status: n,
|
|
56
|
+
raw: body
|
|
57
|
+
})), Match.orElse(() => new AiError.InvalidRequest({
|
|
58
|
+
provider: "fal",
|
|
59
|
+
raw: body
|
|
60
|
+
})));
|
|
61
|
+
/** Never fails: the returned error is the value. */
|
|
62
|
+
const httpError = (status, body) => decodeWireError(body).pipe(Effect.map((wire) => Option.getOrElse(refusalOf(wire, body), () => statusError(status, body))), Effect.orElseSucceed(() => statusError(status, body)));
|
|
63
|
+
/** `image_url`, `image_urls`, `input_image_urls`, `reference_image_url`, … */
|
|
64
|
+
const IMAGE_FIELD = /^[a-z_]*image[a-z_]*_urls?$/;
|
|
65
|
+
/**
|
|
66
|
+
* The reference field this endpoint asked for, if it rejected the request
|
|
67
|
+
* for want of one. `Effect` only because the decode is; it never fails.
|
|
68
|
+
*/
|
|
69
|
+
const missingImageField = (body) => decodeWireError(body).pipe(Effect.map((wire) => pipe(detailsOf(wire), Array.filter((detail) => detail.type === "missing"), Array.map((detail) => detail.loc?.[1]), Array.findFirst((name) => typeof name === "string" && IMAGE_FIELD.test(name)))), Effect.orElseSucceed(() => Option.none()));
|
|
70
|
+
//#endregion
|
|
71
|
+
export { PROVIDER, httpError, missingImageField, codec_exports as t, transportFailure };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=codec.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Match, Option, Schema, pipe } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\n\nexport const PROVIDER = \"fal\"\n\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: PROVIDER, raw: cause })\n\n// ---------------------------------------------------------------------------\n// Errors\n//\n// fal validates request bodies with Pydantic, so a rejected field and a\n// refused prompt arrive the same way: 422 with a `detail` array. The entry's\n// `type` is what separates them. Errors that carry `detail` as a plain\n// string (a missing app, a bad key) fail this decode and fall through to the\n// status mapping, which is where they belong.\n//\n// Reference: https://fal.ai/docs/model-apis/errors\n// ---------------------------------------------------------------------------\n\nconst Detail = Schema.Struct({\n loc: Schema.optional(Schema.Array(Schema.Unknown)),\n msg: Schema.optional(Schema.String),\n type: Schema.optional(Schema.String),\n})\ntype Detail = typeof Detail.Type\n\n/** Wrapped in `detail` off the gateway, bare off the runner. */\nconst WireError = Schema.Union([\n Schema.Struct({ detail: Schema.Array(Detail) }),\n Schema.Array(Detail),\n])\ntype WireError = typeof WireError.Type\n\nconst decodeWireError = Schema.decodeUnknownEffect(Schema.fromJsonString(WireError))\n\nconst detailsOf = (wire: WireError): ReadonlyArray<Detail> =>\n \"detail\" in wire ? wire.detail : wire\n\n/** The one refusal `type` fal documents; every other type is a bad field. */\nconst CONTENT_POLICY = \"content_policy_violation\"\n\nconst refusalOf = (wire: WireError, raw: string): Option.Option<AiError.AiError> =>\n pipe(\n detailsOf(wire),\n Arr.findFirst((detail) => detail.type === CONTENT_POLICY),\n Option.map(\n (detail) =>\n new AiError.ContentFiltered({\n provider: PROVIDER,\n ...(detail.msg !== undefined && { reason: detail.msg }),\n raw,\n }),\n ),\n )\n\nconst statusError = (status: number, body: string): AiError.AiError =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: PROVIDER, raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: PROVIDER, raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: PROVIDER, subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: PROVIDER, subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: PROVIDER, subtype: \"billing\", raw: body }),\n ),\n Match.when(\n 413,\n (): AiError.AiError => new AiError.ContextLengthExceeded({ provider: PROVIDER, raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError => new AiError.Unavailable({ provider: PROVIDER, status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: PROVIDER, raw: body }),\n ),\n )\n\n/** Never fails: the returned error is the value. */\nexport const httpError = (status: number, body: string): Effect.Effect<AiError.AiError> =>\n decodeWireError(body).pipe(\n Effect.map((wire) => Option.getOrElse(refusalOf(wire, body), () => statusError(status, body))),\n Effect.orElseSucceed(() => statusError(status, body)),\n )\n\n// ---------------------------------------------------------------------------\n// Reference-field discovery\n//\n// Endpoints spell the reference field four different ways and the id does\n// not say which. Rather than table fal's catalogue, read the answer off\n// the endpoint's own validation error: a `missing` entry names the field\n// it wanted in `loc`.\n// ---------------------------------------------------------------------------\n\n/** `image_url`, `image_urls`, `input_image_urls`, `reference_image_url`, … */\nconst IMAGE_FIELD = /^[a-z_]*image[a-z_]*_urls?$/\n\n/**\n * The reference field this endpoint asked for, if it rejected the request\n * for want of one. `Effect` only because the decode is; it never fails.\n */\nexport const missingImageField = (body: string): Effect.Effect<Option.Option<string>> =>\n decodeWireError(body).pipe(\n Effect.map((wire) =>\n pipe(\n detailsOf(wire),\n Arr.filter((detail) => detail.type === \"missing\"),\n Arr.map((detail) => detail.loc?.[1]),\n Arr.findFirst((name): name is string => typeof name === \"string\" && IMAGE_FIELD.test(name)),\n ),\n ),\n Effect.orElseSucceed(() => Option.none<string>()),\n )\n"],"mappings":";;;;;;;;;;AAGA,MAAa,WAAW;AAExB,MAAa,oBAAoB,UAC/B,IAAI,QAAQ,YAAY;CAAE,UAAA;CAAoB,KAAK;AAAM,CAAC;AAc5D,MAAM,SAAS,OAAO,OAAO;CAC3B,KAAK,OAAO,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC;CACjD,KAAK,OAAO,SAAS,OAAO,MAAM;CAClC,MAAM,OAAO,SAAS,OAAO,MAAM;AACrC,CAAC;;AAID,MAAM,YAAY,OAAO,MAAM,CAC7B,OAAO,OAAO,EAAE,QAAQ,OAAO,MAAM,MAAM,EAAE,CAAC,GAC9C,OAAO,MAAM,MAAM,CACrB,CAAC;AAGD,MAAM,kBAAkB,OAAO,oBAAoB,OAAO,eAAe,SAAS,CAAC;AAEnF,MAAM,aAAa,SACjB,YAAY,OAAO,KAAK,SAAS;;AAGnC,MAAM,iBAAiB;AAEvB,MAAM,aAAa,MAAiB,QAClC,KACE,UAAU,IAAI,GACdA,MAAI,WAAW,WAAW,OAAO,SAAS,cAAc,GACxD,OAAO,KACJ,WACC,IAAI,QAAQ,gBAAgB;CAC1B,UAAA;CACA,GAAI,OAAO,QAAQ,KAAA,KAAa,EAAE,QAAQ,OAAO,IAAI;CACrD;AACF,CAAC,CACL,CACF;AAEF,MAAM,eAAe,QAAgB,SACnC,MAAM,MAAM,MAAM,CAAC,CAAC,KAClB,MAAM,KACJ,WACuB,IAAI,QAAQ,YAAY;CAAE,UAAA;CAAoB,KAAK;AAAK,CAAC,CAClF,GACA,MAAM,OACJ,KACA,WACuB,IAAI,QAAQ,QAAQ;CAAE,UAAA;CAAoB,KAAK;AAAK,CAAC,CAC9E,GACA,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAA;CAAoB,SAAS;CAAQ,KAAK;AAAK,CAAC,CAC7E,GACA,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAA;CAAoB,SAAS;CAAc,KAAK;AAAK,CAAC,CACnF,GACA,MAAM,KACJ,WAEE,IAAI,QAAQ,WAAW;CAAE,UAAA;CAAoB,SAAS;CAAW,KAAK;AAAK,CAAC,CAChF,GACA,MAAM,KACJ,WACuB,IAAI,QAAQ,sBAAsB;CAAE,UAAA;CAAoB,KAAK;AAAK,CAAC,CAC5F,GACA,MAAM,MACH,MAAM,KAAK,MACX,MAAuB,IAAI,QAAQ,YAAY;CAAE,UAAA;CAAoB,QAAQ;CAAG,KAAK;AAAK,CAAC,CAC9F,GACA,MAAM,aACmB,IAAI,QAAQ,eAAe;CAAE,UAAA;CAAoB,KAAK;AAAK,CAAC,CACrF,CACF;;AAGF,MAAa,aAAa,QAAgB,SACxC,gBAAgB,IAAI,CAAC,CAAC,KACpB,OAAO,KAAK,SAAS,OAAO,UAAU,UAAU,MAAM,IAAI,SAAS,YAAY,QAAQ,IAAI,CAAC,CAAC,GAC7F,OAAO,oBAAoB,YAAY,QAAQ,IAAI,CAAC,CACtD;;AAYF,MAAM,cAAc;;;;;AAMpB,MAAa,qBAAqB,SAChC,gBAAgB,IAAI,CAAC,CAAC,KACpB,OAAO,KAAK,SACV,KACE,UAAU,IAAI,GACdA,MAAI,QAAQ,WAAW,OAAO,SAAS,SAAS,GAChDA,MAAI,KAAK,WAAW,OAAO,MAAM,EAAE,GACnCA,MAAI,WAAW,SAAyB,OAAO,SAAS,YAAY,YAAY,KAAK,IAAI,CAAC,CAC5F,CACF,GACA,OAAO,oBAAoB,OAAO,KAAa,CAAC,CAClD"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { FalImageEditModel, FalImageModel } from "./models.mjs";
|
|
2
|
+
import { t as FalImageGenerator_d_exports } from "./FalImageGenerator.mjs";
|
|
3
|
+
import { t as codec_d_exports } from "./codec.mjs";
|
|
4
|
+
export { FalImageEditModel, FalImageGenerator_d_exports as FalImageGenerator, FalImageModel, codec_d_exports as codec };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/models.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* fal endpoint identifiers (as of September 2026). Unlike every other
|
|
4
|
+
* provider here, the "model" is a *path*: it selects the endpoint, and
|
|
5
|
+
* text-to-image and edit are separate endpoints of the same model
|
|
6
|
+
* family. Pass a `FalImageModel` to `generate` and a `FalImageEditModel`
|
|
7
|
+
* to `edit`.
|
|
8
|
+
*
|
|
9
|
+
* The `(string & {})` tail keeps autocomplete on the literals while
|
|
10
|
+
* accepting any string, so fal's whole catalogue works without an SDK
|
|
11
|
+
* update: the id is the model page's URL after `fal.ai/models/`.
|
|
12
|
+
*
|
|
13
|
+
* Read that literally. Whether an id carries the `fal-ai/` prefix varies
|
|
14
|
+
* even between generations of one model (`fal-ai/bytedance/seedream/v4.5/edit`
|
|
15
|
+
* has it, `bytedance/seedream/v5/pro/text-to-image` does not), and a
|
|
16
|
+
* wrong guess is fal's "Application not found", not a 404 you can read.
|
|
17
|
+
*
|
|
18
|
+
* Reference: https://fal.ai/models
|
|
19
|
+
*/
|
|
20
|
+
type FalImageModel = "fal-ai/flux-2-pro" | "fal-ai/flux/schnell" | "fal-ai/flux/dev" | "bytedance/seedream/v5/pro/text-to-image" | "alibaba/qwen-image-3/text-to-image" | "meta/muse-image/text-to-image" | "openai/gpt-image-2" | "fal-ai/nano-banana-2" | (string & {});
|
|
21
|
+
/**
|
|
22
|
+
* Edit endpoints. A generate id sent to `edit` gets fal's 422 for an
|
|
23
|
+
* unknown field.
|
|
24
|
+
*
|
|
25
|
+
* Which wire field the references ride in differs per endpoint and the
|
|
26
|
+
* adapter works that out on its own, so any edit endpoint fal hosts
|
|
27
|
+
* works here, single-image ones included.
|
|
28
|
+
*/
|
|
29
|
+
type FalImageEditModel = "fal-ai/flux-2-pro/edit" | "bytedance/seedream/v5/pro/edit" | "fal-ai/bytedance/seedream/v4.5/edit" | "alibaba/qwen-image-3/edit" | "fal-ai/nano-banana-2/edit" | "openai/gpt-image-2/edit" | "fal-ai/qwen-image-edit" | "fal-ai/flux/dev/image-to-image" | (string & {});
|
|
30
|
+
//#endregion
|
|
31
|
+
export { FalImageEditModel, FalImageModel };
|
|
32
|
+
//# sourceMappingURL=models.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;;;;;;;;;;;;;;;;;;KAkBY;;;;;;;;;KAoBA"}
|
package/dist/models.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effect-uai/fal",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "fal provider for @effect-uai/core.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"effect",
|
|
8
|
+
"fal",
|
|
9
|
+
"flux",
|
|
10
|
+
"image-generation",
|
|
11
|
+
"seedream"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/betalyra/effect-uai/tree/main/packages/providers/fal",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/betalyra/effect-uai/issues"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Betalyra",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/betalyra/effect-uai",
|
|
22
|
+
"directory": "packages/providers/fal"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "./dist/index.mjs",
|
|
32
|
+
"types": "./dist/index.d.mts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.mts",
|
|
36
|
+
"import": "./dist/index.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./FalImageGenerator": {
|
|
39
|
+
"types": "./dist/FalImageGenerator.d.mts",
|
|
40
|
+
"import": "./dist/FalImageGenerator.mjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"effect": "4.0.0-rc.111",
|
|
48
|
+
"typescript": "^7.0.2",
|
|
49
|
+
"@effect-uai/core": "0.13.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@effect-uai/core": ">=0.14.0 <1",
|
|
53
|
+
"effect": ">=4.0.0-rc.111 <5.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsdown",
|
|
57
|
+
"typecheck": "tsc --noEmit"
|
|
58
|
+
}
|
|
59
|
+
}
|