effect-vercel 0.3.0 → 0.5.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 +53 -23
- package/dist/AiGateway.d.ts +44 -0
- package/dist/{ai-gateway.js → AiGateway.js} +5 -106
- package/dist/AiGateway.js.map +1 -0
- package/dist/AiGatewayCredentials.d.ts +45 -0
- package/dist/AiGatewayCredentials.js +94 -0
- package/dist/AiGatewayCredentials.js.map +1 -0
- package/dist/Functions.d.ts +14 -0
- package/dist/Functions.js +34 -0
- package/dist/Functions.js.map +1 -0
- package/dist/Oidc.d.ts +23 -0
- package/dist/Oidc.js +59 -0
- package/dist/Oidc.js.map +1 -0
- package/dist/{runtime-cache.d.ts → RuntimeCache.d.ts} +5 -8
- package/dist/{runtime-cache.js → RuntimeCache.js} +6 -12
- package/dist/RuntimeCache.js.map +1 -0
- package/package.json +9 -6
- package/src/AiGateway.ts +1 -1
- package/src/AiGatewayCredentials.ts +53 -55
- package/src/Functions.ts +34 -0
- package/src/Oidc.ts +75 -0
- package/src/{VercelRuntimeCache.ts → RuntimeCache.ts} +2 -2
- package/dist/VercelOidc-DCkdXW3E.js +0 -70
- package/dist/VercelOidc-DCkdXW3E.js.map +0 -1
- package/dist/ai-gateway.d.ts +0 -87
- package/dist/ai-gateway.js.map +0 -1
- package/dist/oidc.d.ts +0 -27
- package/dist/oidc.js +0 -2
- package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
- package/dist/runtime-cache.js.map +0 -1
- package/src/VercelOidc.ts +0 -86
- package/src/ai-gateway.ts +0 -2
- package/src/oidc.ts +0 -1
- package/src/runtime-cache.ts +0 -1
package/dist/Oidc.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Oidc.js","names":["holder"],"sources":["../src/Oidc.ts"],"sourcesContent":["/**\n * The OIDC token Vercel issues to a deployment.\n *\n * `ambientToken` reads the token the platform provides. In Functions it\n * arrives on each request as the `x-vercel-oidc-token` header and rotates, so\n * it is read on every run, never cached. In builds and after `vercel env pull`\n * it is `VERCEL_OIDC_TOKEN`. Use it to authenticate to the AI Gateway, or\n * exchange it for cloud credentials (AWS `AssumeRoleWithWebIdentity`, GCP\n * workload identity federation).\n *\n * @example\n * ```ts\n * import * as Oidc from \"effect-vercel/Oidc\"\n * import { Effect, Redacted } from \"effect\"\n *\n * const program = Effect.gen(function* () {\n * const token = yield* Oidc.ambientToken\n * // pass Redacted.value(token) as the web identity token\n * })\n * ```\n */\n\nimport { Config, Data, Effect, Redacted } from \"effect\"\n\n/** The environment variable read when no request carries a token. */\nexport const TOKEN_ENV = \"VERCEL_OIDC_TOKEN\"\n\n/** No usable OIDC token. */\nexport class OidcError extends Data.TaggedError(\"VercelOidcError\")<{\n readonly message: string\n readonly hints: ReadonlyArray<string>\n readonly cause?: unknown\n}> {}\n\n/** How to make a token available. */\nexport const hints: ReadonlyArray<string> = [\n \"Run on Vercel, where each request carries an OIDC token.\",\n `Or run \\`vercel env pull\\` locally to populate ${TOKEN_ENV}.`,\n]\n\n/**\n * Vercel exposes the current request's context on a well-known global symbol.\n * In Functions, the request's headers live there. `@vercel/oidc` reads the\n * same place.\n */\nconst REQUEST_CONTEXT = Symbol.for(\"@vercel/request-context\")\n\ntype RequestContext = { readonly headers?: Record<string, string | undefined> }\n\n/** The `x-vercel-oidc-token` header of the current request, if set. */\nconst requestToken = (): string | undefined => {\n const holder = globalThis as typeof globalThis & {\n [REQUEST_CONTEXT]?: { get?: () => RequestContext | undefined }\n }\n const token = holder[REQUEST_CONTEXT]?.get?.()?.headers?.[\"x-vercel-oidc-token\"]\n return token === \"\" ? undefined : token\n}\n\nconst envToken = Config.NonEmptyString(TOKEN_ENV).pipe(Config.map(Redacted.make))\n\n/**\n * The ambient token: the request header, else `VERCEL_OIDC_TOKEN`. Empty\n * values count as unset. Fails when neither is set. Does not refresh an\n * expired local token; re-run `vercel env pull`.\n */\nexport const ambientToken: Effect.Effect<Redacted.Redacted<string>, OidcError> = Effect.suspend(\n () => {\n const token = requestToken()\n if (token !== undefined) return Effect.succeed(Redacted.make(token))\n return Effect.mapError(\n envToken,\n (cause) => new OidcError({ message: \"No Vercel OIDC token is available.\", hints, cause }),\n )\n },\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,YAAY;;AAGzB,IAAa,YAAb,cAA+B,KAAK,YAAY,iBAAiB,CAAC,CAI/D,CAAC;;AAGJ,MAAa,QAA+B,CAC1C,4DACA,kDAAkD,UAAU,EAC9D;;;;;;AAOA,MAAM,kBAAkB,OAAO,IAAI,yBAAyB;;AAK5D,MAAM,qBAAyC;CAI7C,MAAM,QAAQA,WAAO,gBAAgB,EAAE,MAAM,CAAC,EAAE,UAAU;CAC1D,OAAO,UAAU,KAAK,KAAA,IAAY;AACpC;AAEA,MAAM,WAAW,OAAO,eAAe,SAAS,CAAC,CAAC,KAAK,OAAO,IAAI,SAAS,IAAI,CAAC;;;;;;AAOhF,MAAa,eAAoE,OAAO,cAChF;CACJ,MAAM,QAAQ,aAAa;CAC3B,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO,QAAQ,SAAS,KAAK,KAAK,CAAC;CACnE,OAAO,OAAO,SACZ,WACC,UAAU,IAAI,UAAU;EAAE,SAAS;EAAsC;EAAO;CAAM,CAAC,CAC1F;AACF,CACF"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Layer } from "effect";
|
|
2
2
|
import { RuntimeCache } from "@vercel/functions";
|
|
3
3
|
import { Persistence } from "effect/persistence";
|
|
4
|
-
|
|
5
|
-
export { layer, layerBacking, makeBacking };
|
|
6
|
-
}
|
|
4
|
+
//#region src/RuntimeCache.d.ts
|
|
7
5
|
/**
|
|
8
6
|
* A `BackingPersistence` over `cache`.
|
|
9
7
|
*
|
|
@@ -17,11 +15,10 @@ declare namespace VercelRuntimeCache_d_exports {
|
|
|
17
15
|
* `getCache()` hashes keys with a 32-bit hash by default, which can collide.
|
|
18
16
|
* Pass a cache made with a stronger `keyHashFunction`, as `layerBacking` does.
|
|
19
17
|
*/
|
|
20
|
-
declare const makeBacking: (cache: RuntimeCache) => Persistence.BackingPersistence["Service"];
|
|
18
|
+
export declare const makeBacking: (cache: RuntimeCache) => Persistence.BackingPersistence["Service"];
|
|
21
19
|
/** `BackingPersistence` over `getCache()`, with SHA-256 key hashing. */
|
|
22
|
-
declare const layerBacking: Layer.Layer<Persistence.BackingPersistence>;
|
|
20
|
+
export declare const layerBacking: Layer.Layer<Persistence.BackingPersistence>;
|
|
23
21
|
/** `Persistence` backed by the Vercel Runtime Cache. */
|
|
24
|
-
declare const layer: Layer.Layer<Persistence.Persistence>;
|
|
22
|
+
export declare const layer: Layer.Layer<Persistence.Persistence>;
|
|
25
23
|
//#endregion
|
|
26
|
-
|
|
27
|
-
//# sourceMappingURL=runtime-cache.d.ts.map
|
|
24
|
+
//# sourceMappingURL=RuntimeCache.d.ts.map
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
1
|
import { Duration, Effect, Layer } from "effect";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
2
|
import { getCache } from "@vercel/functions";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
5
4
|
import { Persistence } from "effect/persistence";
|
|
6
|
-
//#region src/
|
|
5
|
+
//#region src/RuntimeCache.ts
|
|
7
6
|
/**
|
|
8
7
|
* Effect `Persistence` backed by the Vercel Runtime Cache, so a
|
|
9
8
|
* `PersistedCache` shares results across Function instances in a region.
|
|
@@ -13,7 +12,7 @@ import { Persistence } from "effect/persistence";
|
|
|
13
12
|
*
|
|
14
13
|
* @example
|
|
15
14
|
* ```ts
|
|
16
|
-
* import
|
|
15
|
+
* import * as RuntimeCache from "effect-vercel/RuntimeCache"
|
|
17
16
|
* import { Effect } from "effect"
|
|
18
17
|
* import { PersistedCache } from "effect/persistence"
|
|
19
18
|
*
|
|
@@ -23,14 +22,9 @@ import { Persistence } from "effect/persistence";
|
|
|
23
22
|
* timeToLive: () => "1 hour",
|
|
24
23
|
* })
|
|
25
24
|
* return yield* users.get(new GetUser({ id: 1 }))
|
|
26
|
-
* }).pipe(Effect.scoped, Effect.provide(
|
|
25
|
+
* }).pipe(Effect.scoped, Effect.provide(RuntimeCache.layer))
|
|
27
26
|
* ```
|
|
28
27
|
*/
|
|
29
|
-
var VercelRuntimeCache_exports = /* @__PURE__ */ __exportAll({
|
|
30
|
-
layer: () => layer,
|
|
31
|
-
layerBacking: () => layerBacking,
|
|
32
|
-
makeBacking: () => makeBacking
|
|
33
|
-
});
|
|
34
28
|
const sha256 = (key) => createHash("sha256").update(key).digest("hex");
|
|
35
29
|
/**
|
|
36
30
|
* A `BackingPersistence` over `cache`.
|
|
@@ -95,6 +89,6 @@ const layerBacking = Layer.sync(Persistence.BackingPersistence)(() => makeBackin
|
|
|
95
89
|
/** `Persistence` backed by the Vercel Runtime Cache. */
|
|
96
90
|
const layer = Persistence.layer.pipe(Layer.provide(layerBacking));
|
|
97
91
|
//#endregion
|
|
98
|
-
export {
|
|
92
|
+
export { layer, layerBacking, makeBacking };
|
|
99
93
|
|
|
100
|
-
//# sourceMappingURL=
|
|
94
|
+
//# sourceMappingURL=RuntimeCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RuntimeCache.js","names":[],"sources":["../src/RuntimeCache.ts"],"sourcesContent":["/**\n * Effect `Persistence` backed by the Vercel Runtime Cache, so a\n * `PersistedCache` shares results across Function instances in a region.\n *\n * Outside Vercel, `getCache` falls back to an in-memory cache, so the same\n * layer works in local dev and tests.\n *\n * @example\n * ```ts\n * import * as RuntimeCache from \"effect-vercel/RuntimeCache\"\n * import { Effect } from \"effect\"\n * import { PersistedCache } from \"effect/persistence\"\n *\n * const program = Effect.gen(function* () {\n * const users = yield* PersistedCache.make(fetchUser, {\n * storeId: \"users\",\n * timeToLive: () => \"1 hour\",\n * })\n * return yield* users.get(new GetUser({ id: 1 }))\n * }).pipe(Effect.scoped, Effect.provide(RuntimeCache.layer))\n * ```\n */\n\nimport { createHash } from \"node:crypto\"\nimport { getCache, type RuntimeCache } from \"@vercel/functions\"\nimport { Duration, Effect, Layer } from \"effect\"\nimport { Persistence } from \"effect/persistence\"\n\nconst sha256 = (key: string) => createHash(\"sha256\").update(key).digest(\"hex\")\n\n/**\n * A `BackingPersistence` over `cache`.\n *\n * Entries are keyed by the JSON pair `[storeId, key]`, so no two stores share\n * a key. Each entry is tagged with a hash of the store id, since tags are\n * joined with `,` and sent unescaped, and `clear` expires that tag. Entries\n * are sent without a name, so keys don't reach Vercel's o11y or the request\n * headers, where non-Latin-1 characters would make the write fail. TTLs round\n * up to whole seconds, the cache's resolution.\n *\n * `getCache()` hashes keys with a 32-bit hash by default, which can collide.\n * Pass a cache made with a stronger `keyHashFunction`, as `layerBacking` does.\n */\nexport const makeBacking = (cache: RuntimeCache): Persistence.BackingPersistence[\"Service\"] =>\n Persistence.BackingPersistence.of({\n make: (storeId) =>\n Effect.sync(() => {\n const prefixed = (key: string) => JSON.stringify([storeId, key])\n const tag = `effect-persistence:${sha256(storeId)}`\n\n const get = (key: string) =>\n Effect.tryPromise({\n try: () => cache.get(prefixed(key)),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to get key ${key}`, cause }),\n }).pipe(\n Effect.map((value) =>\n typeof value === \"object\" && value !== null ? value : undefined,\n ),\n )\n\n const set = (key: string, value: object, ttl: Duration.Duration | undefined) =>\n Effect.tryPromise({\n try: () =>\n cache.set(prefixed(key), value, {\n name: \"\",\n tags: [tag],\n ...(ttl && { ttl: Math.max(1, Math.ceil(Duration.toSeconds(ttl))) }),\n }),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to set key ${key}`, cause }),\n })\n\n return {\n get,\n getMany: (keys) => Effect.forEach(keys, get, { concurrency: \"unbounded\" }),\n set,\n // Writes run concurrently, so keep only the last entry for each key.\n setMany: (entries) =>\n Effect.forEach(\n new Map(entries.map(([key, value, ttl]) => [key, [value, ttl] as const])),\n ([key, [value, ttl]]) => set(key, value, ttl),\n { concurrency: \"unbounded\", discard: true },\n ),\n remove: (key) =>\n Effect.tryPromise({\n try: () => cache.delete(prefixed(key)),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to remove key ${key}`, cause }),\n }),\n clear: Effect.tryPromise({\n try: () => cache.expireTag(tag),\n catch: (cause) =>\n new Persistence.PersistenceError({ message: `Failed to clear ${storeId}`, cause }),\n }),\n }\n }),\n })\n\n/** `BackingPersistence` over `getCache()`, with SHA-256 key hashing. */\nexport const layerBacking: Layer.Layer<Persistence.BackingPersistence> = Layer.sync(\n Persistence.BackingPersistence,\n)(() => makeBacking(getCache({ keyHashFunction: sha256 })))\n\n/** `Persistence` backed by the Vercel Runtime Cache. */\nexport const layer: Layer.Layer<Persistence.Persistence> = Persistence.layer.pipe(\n Layer.provide(layerBacking),\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAM,UAAU,QAAgB,WAAW,QAAQ,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK;;;;;;;;;;;;;;AAe7E,MAAa,eAAe,UAC1B,YAAY,mBAAmB,GAAG,EAChC,OAAO,YACL,OAAO,WAAW;CAChB,MAAM,YAAY,QAAgB,KAAK,UAAU,CAAC,SAAS,GAAG,CAAC;CAC/D,MAAM,MAAM,sBAAsB,OAAO,OAAO;CAEhD,MAAM,OAAO,QACX,OAAO,WAAW;EAChB,WAAW,MAAM,IAAI,SAAS,GAAG,CAAC;EAClC,QAAQ,UACN,IAAI,YAAY,iBAAiB;GAAE,SAAS,qBAAqB;GAAO;EAAM,CAAC;CACnF,CAAC,CAAC,CAAC,KACD,OAAO,KAAK,UACV,OAAO,UAAU,YAAY,UAAU,OAAO,QAAQ,KAAA,CACxD,CACF;CAEF,MAAM,OAAO,KAAa,OAAe,QACvC,OAAO,WAAW;EAChB,WACE,MAAM,IAAI,SAAS,GAAG,GAAG,OAAO;GAC9B,MAAM;GACN,MAAM,CAAC,GAAG;GACV,GAAI,OAAO,EAAE,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,UAAU,GAAG,CAAC,CAAC,EAAE;EACpE,CAAC;EACH,QAAQ,UACN,IAAI,YAAY,iBAAiB;GAAE,SAAS,qBAAqB;GAAO;EAAM,CAAC;CACnF,CAAC;CAEH,OAAO;EACL;EACA,UAAU,SAAS,OAAO,QAAQ,MAAM,KAAK,EAAE,aAAa,YAAY,CAAC;EACzE;EAEA,UAAU,YACR,OAAO,QACL,IAAI,IAAI,QAAQ,KAAK,CAAC,KAAK,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,GAAG,CAAU,CAAC,CAAC,IACvE,CAAC,KAAK,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,GAAG,GAC5C;GAAE,aAAa;GAAa,SAAS;EAAK,CAC5C;EACF,SAAS,QACP,OAAO,WAAW;GAChB,WAAW,MAAM,OAAO,SAAS,GAAG,CAAC;GACrC,QAAQ,UACN,IAAI,YAAY,iBAAiB;IAAE,SAAS,wBAAwB;IAAO;GAAM,CAAC;EACtF,CAAC;EACH,OAAO,OAAO,WAAW;GACvB,WAAW,MAAM,UAAU,GAAG;GAC9B,QAAQ,UACN,IAAI,YAAY,iBAAiB;IAAE,SAAS,mBAAmB;IAAW;GAAM,CAAC;EACrF,CAAC;CACH;AACF,CAAC,EACL,CAAC;;AAGH,MAAa,eAA4D,MAAM,KAC7E,YAAY,kBACd,CAAC,OAAO,YAAY,SAAS,EAAE,iBAAiB,OAAO,CAAC,CAAC,CAAC;;AAG1D,MAAa,QAA8C,YAAY,MAAM,KAC3E,MAAM,QAAQ,YAAY,CAC5B"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-vercel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Effect integrations for Vercel: AI Gateway, OIDC, Runtime Cache",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Effect integrations for Vercel: AI Gateway, OIDC, Runtime Cache, waitUntil",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-gateway",
|
|
7
7
|
"effect",
|
|
8
8
|
"effect-ai",
|
|
9
9
|
"oidc",
|
|
10
10
|
"runtime-cache",
|
|
11
|
-
"vercel"
|
|
11
|
+
"vercel",
|
|
12
|
+
"wait-until"
|
|
12
13
|
],
|
|
13
14
|
"homepage": "https://github.com/duailibe/effect-vercel#readme",
|
|
14
15
|
"bugs": {
|
|
@@ -27,9 +28,11 @@
|
|
|
27
28
|
"type": "module",
|
|
28
29
|
"sideEffects": false,
|
|
29
30
|
"exports": {
|
|
30
|
-
"./
|
|
31
|
-
"./
|
|
32
|
-
"./
|
|
31
|
+
"./AiGateway": "./dist/AiGateway.js",
|
|
32
|
+
"./AiGatewayCredentials": "./dist/AiGatewayCredentials.js",
|
|
33
|
+
"./Functions": "./dist/Functions.js",
|
|
34
|
+
"./Oidc": "./dist/Oidc.js",
|
|
35
|
+
"./RuntimeCache": "./dist/RuntimeCache.js",
|
|
33
36
|
"./package.json": "./package.json"
|
|
34
37
|
},
|
|
35
38
|
"publishConfig": {
|
package/src/AiGateway.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
17
|
* ```ts
|
|
18
|
-
* import
|
|
18
|
+
* import * as AiGateway from "effect-vercel/AiGateway"
|
|
19
19
|
* import { AnthropicLanguageModel } from "@effect/ai-anthropic"
|
|
20
20
|
* import { Layer } from "effect"
|
|
21
21
|
* import { FetchHttpClient } from "effect/http"
|
|
@@ -8,23 +8,23 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```ts
|
|
11
|
-
* import
|
|
11
|
+
* import * as AiGatewayCredentials from "effect-vercel/AiGatewayCredentials"
|
|
12
12
|
*
|
|
13
|
-
* AiGatewayCredentials.layer
|
|
13
|
+
* AiGatewayCredentials.layer // AI_GATEWAY_API_KEY, else the Vercel OIDC token
|
|
14
14
|
* AiGatewayCredentials.layerFromEnv // AI_GATEWAY_API_KEY only
|
|
15
|
-
* AiGatewayCredentials.
|
|
15
|
+
* AiGatewayCredentials.layerFromOidc // the Vercel OIDC token only
|
|
16
16
|
* AiGatewayCredentials.layerFromApiKey("…") // a fixed value
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { Config, Context, Data, Effect, Layer, Option, Redacted } from "effect"
|
|
21
|
-
import * as
|
|
21
|
+
import * as Oidc from "./Oidc.js"
|
|
22
22
|
|
|
23
23
|
/** The environment variable `layerFromEnv` reads. */
|
|
24
24
|
export const API_KEY_ENV = "AI_GATEWAY_API_KEY"
|
|
25
25
|
|
|
26
26
|
/** Which layer produced or failed to produce a credential. */
|
|
27
|
-
export type Source = "api-key" | "env" | "
|
|
27
|
+
export type Source = "api-key" | "env" | "oidc" | "default"
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* A credential ready to put on a request. `method` is what the gateway
|
|
@@ -46,7 +46,7 @@ export class AiGatewayCredentialsError extends Data.TaggedError("AiGatewayCreden
|
|
|
46
46
|
export class AiGatewayCredentials extends Context.Service<
|
|
47
47
|
AiGatewayCredentials,
|
|
48
48
|
Effect.Effect<Resolved, AiGatewayCredentialsError>
|
|
49
|
-
>()("effect-vercel/
|
|
49
|
+
>()("effect-vercel/AiGatewayCredentials") {}
|
|
50
50
|
|
|
51
51
|
/** The request headers that carry `credentials`. */
|
|
52
52
|
export const formatHeaders = (credentials: Resolved): Record<string, string> => ({
|
|
@@ -57,23 +57,16 @@ export const formatHeaders = (credentials: Resolved): Record<string, string> =>
|
|
|
57
57
|
const hints: Record<Source, ReadonlyArray<string>> = {
|
|
58
58
|
"api-key": [],
|
|
59
59
|
env: [`Set ${API_KEY_ENV} to an AI Gateway API key.`],
|
|
60
|
-
|
|
60
|
+
oidc: Oidc.hints,
|
|
61
61
|
default: [
|
|
62
62
|
`Set ${API_KEY_ENV} to an AI Gateway API key.`,
|
|
63
63
|
"Or run on Vercel / `vercel env pull` so an OIDC token is available.",
|
|
64
64
|
],
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*/
|
|
71
|
-
type Lookup = Effect.Effect<Option.Option<Resolved>, AiGatewayCredentialsError>
|
|
72
|
-
|
|
73
|
-
const apiKeyLookup: Lookup = Config.option(Config.Redacted(API_KEY_ENV)).pipe(
|
|
74
|
-
Effect.map(Option.map((token) => ({ method: "api-key" as const, token }))),
|
|
75
|
-
Effect.catchTag(
|
|
76
|
-
"ConfigError",
|
|
67
|
+
/** `AI_GATEWAY_API_KEY`, or `None` when unset. Fails only when it can't be read. */
|
|
68
|
+
const envApiKey = Config.option(Config.Redacted(API_KEY_ENV)).pipe(
|
|
69
|
+
Effect.mapError(
|
|
77
70
|
(cause) =>
|
|
78
71
|
new AiGatewayCredentialsError({
|
|
79
72
|
message: `Failed to read ${API_KEY_ENV}.`,
|
|
@@ -84,37 +77,34 @@ const apiKeyLookup: Lookup = Config.option(Config.Redacted(API_KEY_ENV)).pipe(
|
|
|
84
77
|
),
|
|
85
78
|
)
|
|
86
79
|
|
|
87
|
-
const
|
|
88
|
-
|
|
80
|
+
const fromEnv: Effect.Effect<Resolved, AiGatewayCredentialsError> = Effect.flatMap(
|
|
81
|
+
envApiKey,
|
|
82
|
+
Option.match({
|
|
83
|
+
onNone: () =>
|
|
84
|
+
Effect.fail(
|
|
85
|
+
new AiGatewayCredentialsError({
|
|
86
|
+
message: `${API_KEY_ENV} is not set.`,
|
|
87
|
+
source: "env",
|
|
88
|
+
hints: hints.env,
|
|
89
|
+
}),
|
|
90
|
+
),
|
|
91
|
+
onSome: (token) => Effect.succeed({ method: "api-key" as const, token }),
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const fromOidc: Effect.Effect<Resolved, AiGatewayCredentialsError> = Oidc.ambientToken.pipe(
|
|
96
|
+
Effect.map((token) => ({ method: "oidc" as const, token })),
|
|
89
97
|
Effect.mapError(
|
|
90
98
|
(cause) =>
|
|
91
99
|
new AiGatewayCredentialsError({
|
|
92
100
|
message: cause.message,
|
|
93
|
-
source: "
|
|
101
|
+
source: "oidc",
|
|
94
102
|
hints: cause.hints,
|
|
95
103
|
cause,
|
|
96
104
|
}),
|
|
97
105
|
),
|
|
98
106
|
)
|
|
99
107
|
|
|
100
|
-
const firstOf = (lookups: ReadonlyArray<Lookup>): Lookup =>
|
|
101
|
-
Effect.gen(function* () {
|
|
102
|
-
for (const lookup of lookups) {
|
|
103
|
-
const found = yield* lookup
|
|
104
|
-
if (Option.isSome(found)) return found
|
|
105
|
-
}
|
|
106
|
-
return Option.none()
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
const toLayer = (lookup: Lookup, source: Source, message: string) =>
|
|
110
|
-
Layer.succeed(AiGatewayCredentials)(
|
|
111
|
-
Effect.flatMap(lookup, (found) =>
|
|
112
|
-
Option.isSome(found)
|
|
113
|
-
? Effect.succeed(found.value)
|
|
114
|
-
: Effect.fail(new AiGatewayCredentialsError({ message, source, hints: hints[source] })),
|
|
115
|
-
),
|
|
116
|
-
)
|
|
117
|
-
|
|
118
108
|
/** A fixed API key. */
|
|
119
109
|
export const layerFromApiKey = (
|
|
120
110
|
apiKey: string | Redacted.Redacted<string>,
|
|
@@ -127,26 +117,34 @@ export const layerFromApiKey = (
|
|
|
127
117
|
)
|
|
128
118
|
|
|
129
119
|
/** `AI_GATEWAY_API_KEY`: local dev, CI, or anywhere off Vercel. */
|
|
130
|
-
export const layerFromEnv: Layer.Layer<AiGatewayCredentials> =
|
|
131
|
-
|
|
132
|
-
"env",
|
|
133
|
-
`${API_KEY_ENV} is not set.`,
|
|
134
|
-
)
|
|
120
|
+
export const layerFromEnv: Layer.Layer<AiGatewayCredentials> =
|
|
121
|
+
Layer.succeed(AiGatewayCredentials)(fromEnv)
|
|
135
122
|
|
|
136
|
-
/** The Vercel OIDC token (see `
|
|
137
|
-
export const
|
|
138
|
-
|
|
139
|
-
"vercel-oidc",
|
|
140
|
-
"No Vercel OIDC token is available.",
|
|
141
|
-
)
|
|
123
|
+
/** The Vercel OIDC token (see `Oidc.ambientToken`). */
|
|
124
|
+
export const layerFromOidc: Layer.Layer<AiGatewayCredentials> =
|
|
125
|
+
Layer.succeed(AiGatewayCredentials)(fromOidc)
|
|
142
126
|
|
|
143
127
|
/**
|
|
144
|
-
* The default: `layerFromEnv`, then `
|
|
128
|
+
* The default: `layerFromEnv`, then `layerFromOidc`. Same order as
|
|
145
129
|
* Vercel's own SDK, so it works locally with an API key and on Vercel without
|
|
146
|
-
* one.
|
|
130
|
+
* one. Falls back only when the key is unset, not when it can't be read.
|
|
147
131
|
*/
|
|
148
|
-
export const layer: Layer.Layer<AiGatewayCredentials> =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
132
|
+
export const layer: Layer.Layer<AiGatewayCredentials> = Layer.succeed(AiGatewayCredentials)(
|
|
133
|
+
Effect.flatMap(
|
|
134
|
+
envApiKey,
|
|
135
|
+
Option.match({
|
|
136
|
+
onNone: () =>
|
|
137
|
+
Effect.mapError(
|
|
138
|
+
fromOidc,
|
|
139
|
+
(cause) =>
|
|
140
|
+
new AiGatewayCredentialsError({
|
|
141
|
+
message: "No AI Gateway credential found.",
|
|
142
|
+
source: "default",
|
|
143
|
+
hints: hints.default,
|
|
144
|
+
cause,
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
onSome: (token) => Effect.succeed({ method: "api-key" as const, token }),
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
152
150
|
)
|
package/src/Functions.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect bindings for `@vercel/functions`.
|
|
3
|
+
*
|
|
4
|
+
* `waitUntil` keeps the Function running until a fiber ends, so work forked
|
|
5
|
+
* during a request can finish after the response is sent.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import * as Functions from "effect-vercel/Functions"
|
|
10
|
+
* import { Effect } from "effect"
|
|
11
|
+
*
|
|
12
|
+
* const handler = Effect.gen(function* () {
|
|
13
|
+
* yield* Effect.forkDetach(sendReceipt).pipe(Effect.tap(Functions.waitUntil))
|
|
14
|
+
* return new Response("OK")
|
|
15
|
+
* })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { waitUntil as vercelWaitUntil } from "@vercel/functions"
|
|
20
|
+
import { Effect, type Fiber } from "effect"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Keeps the Function running until `fiber` ends, as `waitUntil` from
|
|
24
|
+
* `@vercel/functions` does for a promise. Off Vercel it does nothing.
|
|
25
|
+
*
|
|
26
|
+
* The fiber must outlive the request fiber, so fork it with
|
|
27
|
+
* `Effect.forkDetach` or into a scope that outlives the request. A child
|
|
28
|
+
* fiber is interrupted when the request fiber ends. Failures are not
|
|
29
|
+
* reported, so handle them in the forked effect.
|
|
30
|
+
*/
|
|
31
|
+
export const waitUntil = <A, E>(fiber: Fiber.Fiber<A, E>): Effect.Effect<void> =>
|
|
32
|
+
Effect.sync(() =>
|
|
33
|
+
vercelWaitUntil(new Promise<void>((resolve) => fiber.addObserver(() => resolve()))),
|
|
34
|
+
)
|
package/src/Oidc.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OIDC token Vercel issues to a deployment.
|
|
3
|
+
*
|
|
4
|
+
* `ambientToken` reads the token the platform provides. In Functions it
|
|
5
|
+
* arrives on each request as the `x-vercel-oidc-token` header and rotates, so
|
|
6
|
+
* it is read on every run, never cached. In builds and after `vercel env pull`
|
|
7
|
+
* it is `VERCEL_OIDC_TOKEN`. Use it to authenticate to the AI Gateway, or
|
|
8
|
+
* exchange it for cloud credentials (AWS `AssumeRoleWithWebIdentity`, GCP
|
|
9
|
+
* workload identity federation).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import * as Oidc from "effect-vercel/Oidc"
|
|
14
|
+
* import { Effect, Redacted } from "effect"
|
|
15
|
+
*
|
|
16
|
+
* const program = Effect.gen(function* () {
|
|
17
|
+
* const token = yield* Oidc.ambientToken
|
|
18
|
+
* // pass Redacted.value(token) as the web identity token
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { Config, Data, Effect, Redacted } from "effect"
|
|
24
|
+
|
|
25
|
+
/** The environment variable read when no request carries a token. */
|
|
26
|
+
export const TOKEN_ENV = "VERCEL_OIDC_TOKEN"
|
|
27
|
+
|
|
28
|
+
/** No usable OIDC token. */
|
|
29
|
+
export class OidcError extends Data.TaggedError("VercelOidcError")<{
|
|
30
|
+
readonly message: string
|
|
31
|
+
readonly hints: ReadonlyArray<string>
|
|
32
|
+
readonly cause?: unknown
|
|
33
|
+
}> {}
|
|
34
|
+
|
|
35
|
+
/** How to make a token available. */
|
|
36
|
+
export const hints: ReadonlyArray<string> = [
|
|
37
|
+
"Run on Vercel, where each request carries an OIDC token.",
|
|
38
|
+
`Or run \`vercel env pull\` locally to populate ${TOKEN_ENV}.`,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Vercel exposes the current request's context on a well-known global symbol.
|
|
43
|
+
* In Functions, the request's headers live there. `@vercel/oidc` reads the
|
|
44
|
+
* same place.
|
|
45
|
+
*/
|
|
46
|
+
const REQUEST_CONTEXT = Symbol.for("@vercel/request-context")
|
|
47
|
+
|
|
48
|
+
type RequestContext = { readonly headers?: Record<string, string | undefined> }
|
|
49
|
+
|
|
50
|
+
/** The `x-vercel-oidc-token` header of the current request, if set. */
|
|
51
|
+
const requestToken = (): string | undefined => {
|
|
52
|
+
const holder = globalThis as typeof globalThis & {
|
|
53
|
+
[REQUEST_CONTEXT]?: { get?: () => RequestContext | undefined }
|
|
54
|
+
}
|
|
55
|
+
const token = holder[REQUEST_CONTEXT]?.get?.()?.headers?.["x-vercel-oidc-token"]
|
|
56
|
+
return token === "" ? undefined : token
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const envToken = Config.NonEmptyString(TOKEN_ENV).pipe(Config.map(Redacted.make))
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The ambient token: the request header, else `VERCEL_OIDC_TOKEN`. Empty
|
|
63
|
+
* values count as unset. Fails when neither is set. Does not refresh an
|
|
64
|
+
* expired local token; re-run `vercel env pull`.
|
|
65
|
+
*/
|
|
66
|
+
export const ambientToken: Effect.Effect<Redacted.Redacted<string>, OidcError> = Effect.suspend(
|
|
67
|
+
() => {
|
|
68
|
+
const token = requestToken()
|
|
69
|
+
if (token !== undefined) return Effect.succeed(Redacted.make(token))
|
|
70
|
+
return Effect.mapError(
|
|
71
|
+
envToken,
|
|
72
|
+
(cause) => new OidcError({ message: "No Vercel OIDC token is available.", hints, cause }),
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```ts
|
|
10
|
-
* import
|
|
10
|
+
* import * as RuntimeCache from "effect-vercel/RuntimeCache"
|
|
11
11
|
* import { Effect } from "effect"
|
|
12
12
|
* import { PersistedCache } from "effect/persistence"
|
|
13
13
|
*
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* timeToLive: () => "1 hour",
|
|
18
18
|
* })
|
|
19
19
|
* return yield* users.get(new GetUser({ id: 1 }))
|
|
20
|
-
* }).pipe(Effect.scoped, Effect.provide(
|
|
20
|
+
* }).pipe(Effect.scoped, Effect.provide(RuntimeCache.layer))
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { Config, Data, Effect, Option, Redacted } from "effect";
|
|
3
|
-
//#region src/VercelOidc.ts
|
|
4
|
-
/**
|
|
5
|
-
* The OIDC token Vercel issues to a deployment.
|
|
6
|
-
*
|
|
7
|
-
* In Functions the token arrives on each request as the `x-vercel-oidc-token`
|
|
8
|
-
* header and rotates, so it is read on every run of `token`/`find`, never
|
|
9
|
-
* cached. In builds and after `vercel env pull` it is `VERCEL_OIDC_TOKEN`.
|
|
10
|
-
* Use it to authenticate to the AI Gateway, or exchange it for cloud
|
|
11
|
-
* credentials (AWS `AssumeRoleWithWebIdentity`, GCP workload identity
|
|
12
|
-
* federation).
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { VercelOidc } from "effect-vercel/oidc"
|
|
17
|
-
* import { Effect, Redacted } from "effect"
|
|
18
|
-
*
|
|
19
|
-
* const program = Effect.gen(function* () {
|
|
20
|
-
* const token = yield* VercelOidc.token
|
|
21
|
-
* // pass Redacted.value(token) as the web identity token
|
|
22
|
-
* })
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
var VercelOidc_exports = /* @__PURE__ */ __exportAll({
|
|
26
|
-
TOKEN_ENV: () => TOKEN_ENV,
|
|
27
|
-
VercelOidcError: () => VercelOidcError,
|
|
28
|
-
find: () => find,
|
|
29
|
-
hints: () => hints,
|
|
30
|
-
token: () => token
|
|
31
|
-
});
|
|
32
|
-
/** The environment variable read when no request carries a token. */
|
|
33
|
-
const TOKEN_ENV = "VERCEL_OIDC_TOKEN";
|
|
34
|
-
/** No usable OIDC token. */
|
|
35
|
-
var VercelOidcError = class extends Data.TaggedError("VercelOidcError") {};
|
|
36
|
-
/** How to make a token available. */
|
|
37
|
-
const hints = ["Run on Vercel, where each request carries an OIDC token.", `Or run \`vercel env pull\` locally to populate ${TOKEN_ENV}.`];
|
|
38
|
-
/**
|
|
39
|
-
* Vercel exposes the current request's context on a well-known global symbol.
|
|
40
|
-
* In Functions, the `x-vercel-oidc-token` header of the request lives there.
|
|
41
|
-
* Mirrors `@vercel/oidc`'s `getVercelOidcTokenSync`, minus the dev-time
|
|
42
|
-
* refresh that package also offers.
|
|
43
|
-
*/
|
|
44
|
-
const REQUEST_CONTEXT = Symbol.for("@vercel/request-context");
|
|
45
|
-
const fromRequestContext = Effect.sync(() => {
|
|
46
|
-
const holder = globalThis;
|
|
47
|
-
return Option.fromNullishOr(holder[REQUEST_CONTEXT]?.get?.()?.headers?.["x-vercel-oidc-token"]).pipe(Option.map(Redacted.make));
|
|
48
|
-
});
|
|
49
|
-
const fromEnv = Config.option(Config.Redacted(TOKEN_ENV)).pipe(Effect.catchTag("ConfigError", (cause) => new VercelOidcError({
|
|
50
|
-
message: `Failed to read ${TOKEN_ENV}.`,
|
|
51
|
-
hints,
|
|
52
|
-
cause
|
|
53
|
-
})));
|
|
54
|
-
/**
|
|
55
|
-
* The token if one is available: the request header, else `VERCEL_OIDC_TOKEN`.
|
|
56
|
-
* `None` means neither is set, so a caller can fall back to something else.
|
|
57
|
-
*/
|
|
58
|
-
const find = Effect.flatMap(fromRequestContext, (found) => Option.isSome(found) ? Effect.succeed(found) : fromEnv);
|
|
59
|
-
/** Like `find`, but fails when no token is available. */
|
|
60
|
-
const token = Effect.flatMap(find, Option.match({
|
|
61
|
-
onNone: () => Effect.fail(new VercelOidcError({
|
|
62
|
-
message: "No Vercel OIDC token is available.",
|
|
63
|
-
hints
|
|
64
|
-
})),
|
|
65
|
-
onSome: Effect.succeed
|
|
66
|
-
}));
|
|
67
|
-
//#endregion
|
|
68
|
-
export { find as n, hints as r, VercelOidc_exports as t };
|
|
69
|
-
|
|
70
|
-
//# sourceMappingURL=VercelOidc-DCkdXW3E.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"VercelOidc-DCkdXW3E.js","names":[],"sources":["../src/VercelOidc.ts"],"sourcesContent":["/**\n * The OIDC token Vercel issues to a deployment.\n *\n * In Functions the token arrives on each request as the `x-vercel-oidc-token`\n * header and rotates, so it is read on every run of `token`/`find`, never\n * cached. In builds and after `vercel env pull` it is `VERCEL_OIDC_TOKEN`.\n * Use it to authenticate to the AI Gateway, or exchange it for cloud\n * credentials (AWS `AssumeRoleWithWebIdentity`, GCP workload identity\n * federation).\n *\n * @example\n * ```ts\n * import { VercelOidc } from \"effect-vercel/oidc\"\n * import { Effect, Redacted } from \"effect\"\n *\n * const program = Effect.gen(function* () {\n * const token = yield* VercelOidc.token\n * // pass Redacted.value(token) as the web identity token\n * })\n * ```\n */\n\nimport { Config, Data, Effect, Option, Redacted } from \"effect\"\n\n/** The environment variable read when no request carries a token. */\nexport const TOKEN_ENV = \"VERCEL_OIDC_TOKEN\"\n\n/** No usable OIDC token. */\nexport class VercelOidcError extends Data.TaggedError(\"VercelOidcError\")<{\n readonly message: string\n readonly hints: ReadonlyArray<string>\n readonly cause?: unknown\n}> {}\n\n/** How to make a token available. */\nexport const hints: ReadonlyArray<string> = [\n \"Run on Vercel, where each request carries an OIDC token.\",\n `Or run \\`vercel env pull\\` locally to populate ${TOKEN_ENV}.`,\n]\n\n/**\n * Vercel exposes the current request's context on a well-known global symbol.\n * In Functions, the `x-vercel-oidc-token` header of the request lives there.\n * Mirrors `@vercel/oidc`'s `getVercelOidcTokenSync`, minus the dev-time\n * refresh that package also offers.\n */\nconst REQUEST_CONTEXT = Symbol.for(\"@vercel/request-context\")\n\ntype RequestContext = { readonly headers?: Record<string, string | undefined> }\n\nconst fromRequestContext = Effect.sync(() => {\n const holder = globalThis as typeof globalThis & {\n [REQUEST_CONTEXT]?: { get?: () => RequestContext | undefined }\n }\n return Option.fromNullishOr(\n holder[REQUEST_CONTEXT]?.get?.()?.headers?.[\"x-vercel-oidc-token\"],\n ).pipe(Option.map(Redacted.make))\n})\n\nconst fromEnv = Config.option(Config.Redacted(TOKEN_ENV)).pipe(\n Effect.catchTag(\n \"ConfigError\",\n (cause) => new VercelOidcError({ message: `Failed to read ${TOKEN_ENV}.`, hints, cause }),\n ),\n)\n\n/**\n * The token if one is available: the request header, else `VERCEL_OIDC_TOKEN`.\n * `None` means neither is set, so a caller can fall back to something else.\n */\nexport const find: Effect.Effect<\n Option.Option<Redacted.Redacted<string>>,\n VercelOidcError\n> = Effect.flatMap(fromRequestContext, (found) =>\n Option.isSome(found) ? Effect.succeed(found) : fromEnv,\n)\n\n/** Like `find`, but fails when no token is available. */\nexport const token: Effect.Effect<Redacted.Redacted<string>, VercelOidcError> = Effect.flatMap(\n find,\n Option.match({\n onNone: () =>\n Effect.fail(new VercelOidcError({ message: \"No Vercel OIDC token is available.\", hints })),\n onSome: Effect.succeed,\n }),\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,YAAY;;AAGzB,IAAa,kBAAb,cAAqC,KAAK,YAAY,iBAAiB,CAAC,CAIrE,CAAC;;AAGJ,MAAa,QAA+B,CAC1C,4DACA,kDAAkD,UAAU,EAC9D;;;;;;;AAQA,MAAM,kBAAkB,OAAO,IAAI,yBAAyB;AAI5D,MAAM,qBAAqB,OAAO,WAAW;CAC3C,MAAM,SAAS;CAGf,OAAO,OAAO,cACZ,OAAO,gBAAgB,EAAE,MAAM,CAAC,EAAE,UAAU,sBAC9C,CAAC,CAAC,KAAK,OAAO,IAAI,SAAS,IAAI,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,OAAO,OAAO,OAAO,SAAS,SAAS,CAAC,CAAC,CAAC,KACxD,OAAO,SACL,gBACC,UAAU,IAAI,gBAAgB;CAAE,SAAS,kBAAkB,UAAU;CAAI;CAAO;AAAM,CAAC,CAC1F,CACF;;;;;AAMA,MAAa,OAGT,OAAO,QAAQ,qBAAqB,UACtC,OAAO,OAAO,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAI,OACjD;;AAGA,MAAa,QAAmE,OAAO,QACrF,MACA,OAAO,MAAM;CACX,cACE,OAAO,KAAK,IAAI,gBAAgB;EAAE,SAAS;EAAsC;CAAM,CAAC,CAAC;CAC3F,QAAQ,OAAO;AACjB,CAAC,CACH"}
|
package/dist/ai-gateway.d.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { AnthropicClient } from "@effect/ai-anthropic";
|
|
2
|
-
import { Context, Effect, Layer, Redacted } from "effect";
|
|
3
|
-
import { HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/http";
|
|
4
|
-
declare namespace AiGatewayCredentials_d_exports {
|
|
5
|
-
export { API_KEY_ENV, AiGatewayCredentials, AiGatewayCredentialsError, Resolved, Source, formatHeaders, layer$1 as layer, layerFromApiKey, layerFromEnv, layerFromVercelOidc };
|
|
6
|
-
}
|
|
7
|
-
/** The environment variable `layerFromEnv` reads. */
|
|
8
|
-
declare const API_KEY_ENV = "AI_GATEWAY_API_KEY";
|
|
9
|
-
/** Which layer produced or failed to produce a credential. */
|
|
10
|
-
type Source = "api-key" | "env" | "vercel-oidc" | "default";
|
|
11
|
-
/**
|
|
12
|
-
* A credential ready to put on a request. `method` is what the gateway
|
|
13
|
-
* records as the auth method; it matches the values Vercel's own SDK sends.
|
|
14
|
-
*/
|
|
15
|
-
interface Resolved {
|
|
16
|
-
readonly method: "api-key" | "oidc";
|
|
17
|
-
readonly token: Redacted.Redacted<string>;
|
|
18
|
-
}
|
|
19
|
-
declare const AiGatewayCredentialsError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
20
|
-
readonly _tag: "AiGatewayCredentialsError";
|
|
21
|
-
} & Readonly<A>;
|
|
22
|
-
/** No credential could be produced for a gateway request. */
|
|
23
|
-
declare class AiGatewayCredentialsError extends AiGatewayCredentialsError_base<{
|
|
24
|
-
readonly message: string;
|
|
25
|
-
readonly source: Source;
|
|
26
|
-
readonly hints: ReadonlyArray<string>;
|
|
27
|
-
readonly cause?: unknown;
|
|
28
|
-
}> {}
|
|
29
|
-
declare const AiGatewayCredentials_base: Context.ServiceClass<AiGatewayCredentials, "effect-vercel/ai-gateway/AiGatewayCredentials", Effect.Effect<Resolved, AiGatewayCredentialsError, never>>;
|
|
30
|
-
declare class AiGatewayCredentials extends AiGatewayCredentials_base {}
|
|
31
|
-
/** The request headers that carry `credentials`. */
|
|
32
|
-
declare const formatHeaders: (credentials: Resolved) => Record<string, string>;
|
|
33
|
-
/** A fixed API key. */
|
|
34
|
-
declare const layerFromApiKey: (apiKey: string | Redacted.Redacted<string>) => Layer.Layer<AiGatewayCredentials>;
|
|
35
|
-
/** `AI_GATEWAY_API_KEY`: local dev, CI, or anywhere off Vercel. */
|
|
36
|
-
declare const layerFromEnv: Layer.Layer<AiGatewayCredentials>;
|
|
37
|
-
/** The Vercel OIDC token (see `VercelOidc.find`). Not refreshed when it expires locally. */
|
|
38
|
-
declare const layerFromVercelOidc: Layer.Layer<AiGatewayCredentials>;
|
|
39
|
-
/**
|
|
40
|
-
* The default: `layerFromEnv`, then `layerFromVercelOidc`. Same order as
|
|
41
|
-
* Vercel's own SDK, so it works locally with an API key and on Vercel without
|
|
42
|
-
* one.
|
|
43
|
-
*/
|
|
44
|
-
declare const layer$1: Layer.Layer<AiGatewayCredentials>;
|
|
45
|
-
declare namespace AiGateway_d_exports {
|
|
46
|
-
export { AI_GATEWAY_URL, dropNullCacheControl, fillMissingResponseKeys, layer, layerWithoutCredentials, make, patchResponseBody };
|
|
47
|
-
}
|
|
48
|
-
/** Base URL of the Vercel AI Gateway. */
|
|
49
|
-
declare const AI_GATEWAY_URL = "https://ai-gateway.vercel.sh";
|
|
50
|
-
/**
|
|
51
|
-
* Builds the Anthropic client against the gateway, authenticating each
|
|
52
|
-
* request with `credentials`. A credential failure fails that request as an
|
|
53
|
-
* `HttpClientError`, which Effect AI surfaces as a network `AiError`.
|
|
54
|
-
*/
|
|
55
|
-
declare const make: (credentials: typeof AiGatewayCredentials.Service) => Effect.Effect<AnthropicClient.Service, never, HttpClient.HttpClient>;
|
|
56
|
-
/**
|
|
57
|
-
* `AnthropicClient` backed by the gateway, authenticating with the
|
|
58
|
-
* `AiGatewayCredentials` you provide (see that module's `layerFrom*` layers).
|
|
59
|
-
* Needs an `HttpClient` too.
|
|
60
|
-
*/
|
|
61
|
-
declare const layerWithoutCredentials: Layer.Layer<AnthropicClient.AnthropicClient, never, AiGatewayCredentials | HttpClient.HttpClient>;
|
|
62
|
-
/**
|
|
63
|
-
* `layerWithoutCredentials` with the default `AiGatewayCredentials.layer`:
|
|
64
|
-
* `AI_GATEWAY_API_KEY`, else the Vercel OIDC token. Needs an `HttpClient`.
|
|
65
|
-
* Building never fails; without a usable credential, the requests fail.
|
|
66
|
-
*/
|
|
67
|
-
declare const layer: Layer.Layer<AnthropicClient.AnthropicClient, never, HttpClient.HttpClient>;
|
|
68
|
-
/**
|
|
69
|
-
* The Effect provider writes `"cache_control": null` on every content block
|
|
70
|
-
* it has no cache setting for. Anthropic accepts that; the gateway rejects
|
|
71
|
-
* the request with `messages.0.content: Invalid input`. Remove the null keys.
|
|
72
|
-
*/
|
|
73
|
-
declare function dropNullCacheControl(request: HttpClientRequest.HttpClientRequest): HttpClientRequest.HttpClientRequest;
|
|
74
|
-
/**
|
|
75
|
-
* The gateway's Anthropic-shaped responses leave out keys that Anthropic
|
|
76
|
-
* always sends and the Effect provider's schema requires: the cache and
|
|
77
|
-
* service-tier usage fields, the `signature` of a thinking block from a
|
|
78
|
-
* non-Anthropic model, and `type`/`request_id` on error envelopes, whose
|
|
79
|
-
* `error.type` may also be a gateway-specific value. Fill them with the values
|
|
80
|
-
* Anthropic uses when there is nothing to report. Streaming responses are not
|
|
81
|
-
* JSON and pass through untouched.
|
|
82
|
-
*/
|
|
83
|
-
declare function fillMissingResponseKeys(response: HttpClientResponse.HttpClientResponse): Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError.HttpClientError>;
|
|
84
|
-
declare function patchResponseBody(body: Record<string, unknown>): Record<string, unknown>;
|
|
85
|
-
//#endregion
|
|
86
|
-
export { AiGateway_d_exports as AiGateway, AiGatewayCredentials_d_exports as AiGatewayCredentials };
|
|
87
|
-
//# sourceMappingURL=ai-gateway.d.ts.map
|