@homeflare/alchemy 0.23.0 → 0.24.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 +15 -1
- package/dist/litellm/client.d.ts +76 -0
- package/dist/litellm/client.d.ts.map +1 -0
- package/dist/litellm/credentials.d.ts +48 -0
- package/dist/litellm/credentials.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +36 -0
- package/dist/litellm/fake-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +30 -0
- package/dist/litellm/fake-stack.d.ts.map +1 -0
- package/dist/litellm/generated/pass-through.d.ts +71 -0
- package/dist/litellm/generated/pass-through.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +13 -0
- package/dist/litellm/index.d.ts.map +1 -0
- package/dist/litellm/index.js +270 -0
- package/dist/litellm/index.js.map +14 -0
- package/dist/litellm/pass-through-endpoint.d.ts +94 -0
- package/dist/litellm/pass-through-endpoint.d.ts.map +1 -0
- package/dist/litellm/pass-through-form.d.ts +40 -0
- package/dist/litellm/pass-through-form.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +16 -0
- package/dist/litellm/providers.d.ts.map +1 -0
- package/dist/postgres/connection.d.ts +50 -0
- package/dist/postgres/connection.d.ts.map +1 -0
- package/dist/postgres/database-attrs.d.ts +79 -0
- package/dist/postgres/database-attrs.d.ts.map +1 -0
- package/dist/postgres/database-sql.d.ts +73 -0
- package/dist/postgres/database-sql.d.ts.map +1 -0
- package/dist/postgres/database.d.ts +63 -0
- package/dist/postgres/database.d.ts.map +1 -0
- package/dist/postgres/errors.d.ts +86 -0
- package/dist/postgres/errors.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +22 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +16 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +238 -0
- package/dist/postgres/index.js.map +15 -0
- package/dist/postgres/providers.d.ts +14 -0
- package/dist/postgres/providers.d.ts.map +1 -0
- package/dist/proxmox/index.js.map +2 -2
- package/dist/proxmox/zfs-pool.d.ts +10 -2
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/dist/release/archive.d.ts +6 -2
- package/dist/release/archive.d.ts.map +1 -1
- package/dist/release/binary-form.d.ts +14 -9
- package/dist/release/binary-form.d.ts.map +1 -1
- package/dist/release/binary-pins.d.ts +17 -0
- package/dist/release/binary-pins.d.ts.map +1 -0
- package/dist/release/catalog.d.ts.map +1 -1
- package/dist/release/index.js +90 -40
- package/dist/release/index.js.map +8 -7
- package/dist/release/tar.d.ts +9 -2
- package/dist/release/tar.d.ts.map +1 -1
- package/docs/litellm.md +110 -0
- package/docs/peers.md +8 -1
- package/docs/postgres.md +125 -0
- package/docs/release-binary-catalogs.md +40 -4
- package/docs/release-binary-refusals.md +22 -8
- package/docs/release-binary-upstream.md +33 -15
- package/package.json +10 -1
- package/src/litellm/client.ts +195 -0
- package/src/litellm/credentials.ts +73 -0
- package/src/litellm/fake-litellm.ts +114 -0
- package/src/litellm/fake-stack.ts +100 -0
- package/src/litellm/generated/pass-through.ts +73 -0
- package/src/litellm/index.ts +42 -0
- package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +66 -0
- package/src/litellm/pass-through-endpoint-replace.test.ts +84 -0
- package/src/litellm/pass-through-endpoint.test.ts +208 -0
- package/src/litellm/pass-through-endpoint.ts +258 -0
- package/src/litellm/pass-through-form.test.ts +128 -0
- package/src/litellm/pass-through-form.ts +136 -0
- package/src/litellm/providers.ts +19 -0
- package/src/postgres/connection.ts +62 -0
- package/src/postgres/database-attrs.ts +87 -0
- package/src/postgres/database-bigint-serialization.test.ts +140 -0
- package/src/postgres/database-lifecycle.test.ts +113 -0
- package/src/postgres/database-provenance.test.ts +118 -0
- package/src/postgres/database-refusals.test.ts +135 -0
- package/src/postgres/database-sql.ts +174 -0
- package/src/postgres/database.ts +193 -0
- package/src/postgres/errors.ts +131 -0
- package/src/postgres/fake-sql.ts +123 -0
- package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +67 -0
- package/src/postgres/index.ts +30 -0
- package/src/postgres/providers.ts +16 -0
- package/src/proxmox/zfs-pool.ts +10 -2
- package/src/release/archive.ts +10 -4
- package/src/release/binary-form.ts +24 -76
- package/src/release/binary-pins.ts +95 -0
- package/src/release/binary-root.test.ts +122 -0
- package/src/release/catalog.ts +10 -2
- package/src/release/tar-root.test.ts +114 -0
- package/src/release/tar.ts +78 -12
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Custom [Alchemy](https://alchemy.run) providers for gaps the vendor SDK leaves.
|
|
|
5
5
|
```sh
|
|
6
6
|
bun add @homeflare/alchemy alchemy@2.0.0-beta.79 effect@4.0.0-rc.115 \
|
|
7
7
|
@effect/platform-node@4.0.0-rc.115 cloudflare@4.5.0 mime@4.1.0 \
|
|
8
|
-
@distilled.cloud/cloudflare@1.0.0-rc.12
|
|
8
|
+
@distilled.cloud/cloudflare@1.0.0-rc.12 @effect/sql-pg@4.0.0-rc.115
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
⛔ **Every one of those is required, and you also need an `overrides` block** — see
|
|
@@ -206,6 +206,12 @@ with `HostFile`; `caddyProviders()` provides the transport, `http://127.0.0.1:20
|
|
|
206
206
|
- ★ **Managed Caddies run `--resume` with their own `XDG_CONFIG_HOME`**, so a restart runs the last
|
|
207
207
|
config Caddy accepted — and after one, SIGUSR1 has no file to reload. Why, and the rest: same doc.
|
|
208
208
|
|
|
209
|
+
## PostgreSQL — `@homeflare/alchemy/postgres`
|
|
210
|
+
|
|
211
|
+
`Postgres.Database`: create-and-assert over a self-hosted PostgreSQL 18 cluster, over
|
|
212
|
+
`@effect/sql-pg` — the same client upstream's own `alchemy/SQL/Postgres` binding uses. No
|
|
213
|
+
`ALTER DATABASE`, no password prop, and `delete` always refuses: [docs/postgres.md](./docs/postgres.md).
|
|
214
|
+
|
|
209
215
|
## Proxmox — `@homeflare/alchemy/proxmox`
|
|
210
216
|
|
|
211
217
|
PVE and PBS objects over the PVE API, `ProxmoxLxc` for containers, and the provisioning baseline
|
|
@@ -218,6 +224,14 @@ every cluster needs first. What it never does to a guest, and why: [docs/proxmox
|
|
|
218
224
|
against a table generated from NetBox's own OpenAPI document before the request is built.
|
|
219
225
|
⛔ Adopt-first, `retain` on removal, and read/write shapes that differ: [docs/netbox.md](./docs/netbox.md).
|
|
220
226
|
|
|
227
|
+
## LiteLLM — `@homeflare/alchemy/litellm`
|
|
228
|
+
|
|
229
|
+
`LiteLLM.PassThroughEndpoint` declares one route on LiteLLM's proxy that forwards to an
|
|
230
|
+
upstream target, generated from LiteLLM 1.100.0's own OpenAPI document. ⛔ Every pass-through
|
|
231
|
+
endpoint lives in ONE `general_settings` field (a whole-list read-modify-write), a path already
|
|
232
|
+
declared in `config.yaml` is refused rather than silently overridden, and a literal secret in a
|
|
233
|
+
forwarded header is refused at plan: [docs/litellm.md](./docs/litellm.md).
|
|
234
|
+
|
|
221
235
|
## GitHub — `@homeflare/alchemy/github`
|
|
222
236
|
|
|
223
237
|
`declareRepoPolicy` declares one repository's merge policy and its default-branch ruleset in a
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
|
+
import * as HttpClient from 'effect/unstable/http/HttpClient';
|
|
3
|
+
import { LitellmCredentials, type LitellmCredentialsError } from './credentials.ts';
|
|
4
|
+
import type { PassThroughGenericEndpoint } from './generated/pass-through.ts';
|
|
5
|
+
export type LitellmRequirements = HttpClient.HttpClient | LitellmCredentials;
|
|
6
|
+
/**
|
|
7
|
+
* The `litellm:<METHOD> <path>` operations this package writes to and reads from.
|
|
8
|
+
*
|
|
9
|
+
* ★ `codegen/litellm.ts` scans this FILE's text for these exact literals to pick which of
|
|
10
|
+
* LiteLLM 1.100.0's 696 paths to generate types from (codegen/litellm.ts header). Keep each one
|
|
11
|
+
* a literal string on one line; the generator matches text, it does not evaluate the object.
|
|
12
|
+
*/
|
|
13
|
+
export declare const LITELLM_OPERATIONS: {
|
|
14
|
+
readonly create: 'litellm:POST /config/pass_through_endpoint';
|
|
15
|
+
readonly delete: 'litellm:DELETE /config/pass_through_endpoint';
|
|
16
|
+
readonly list: 'litellm:GET /config/pass_through_endpoint';
|
|
17
|
+
readonly update: 'litellm:POST /config/pass_through_endpoint/{endpoint_id}';
|
|
18
|
+
};
|
|
19
|
+
declare const LitellmUnauthorizedError_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: "LitellmUnauthorizedError";
|
|
21
|
+
} & Readonly<A>;
|
|
22
|
+
export declare class LitellmUnauthorizedError extends LitellmUnauthorizedError_base<{
|
|
23
|
+
readonly status: number;
|
|
24
|
+
readonly method: string;
|
|
25
|
+
readonly path: string;
|
|
26
|
+
}> {
|
|
27
|
+
}
|
|
28
|
+
declare const LitellmBadRequestError_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 & {
|
|
29
|
+
readonly _tag: "LitellmBadRequestError";
|
|
30
|
+
} & Readonly<A>;
|
|
31
|
+
export declare class LitellmBadRequestError extends LitellmBadRequestError_base<{
|
|
32
|
+
readonly method: string;
|
|
33
|
+
readonly path: string;
|
|
34
|
+
readonly detail: string;
|
|
35
|
+
}> {
|
|
36
|
+
}
|
|
37
|
+
declare const LitellmHttpError_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 & {
|
|
38
|
+
readonly _tag: "LitellmHttpError";
|
|
39
|
+
} & Readonly<A>;
|
|
40
|
+
export declare class LitellmHttpError extends LitellmHttpError_base<{
|
|
41
|
+
readonly status: number;
|
|
42
|
+
readonly method: string;
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly detail: string;
|
|
45
|
+
}> {
|
|
46
|
+
}
|
|
47
|
+
declare const LitellmTransportError_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 & {
|
|
48
|
+
readonly _tag: "LitellmTransportError";
|
|
49
|
+
} & Readonly<A>;
|
|
50
|
+
export declare class LitellmTransportError extends LitellmTransportError_base<{
|
|
51
|
+
readonly method: string;
|
|
52
|
+
readonly path: string;
|
|
53
|
+
readonly cause: string;
|
|
54
|
+
}> {
|
|
55
|
+
}
|
|
56
|
+
export type LitellmError = LitellmUnauthorizedError | LitellmBadRequestError | LitellmHttpError | LitellmTransportError | LitellmCredentialsError;
|
|
57
|
+
/** GET /config/pass_through_endpoint — every row, config-file and DB alike. Not under the semaphore: reads don't race the field. */
|
|
58
|
+
export declare const listPassThroughEndpoints: () => Effect.Effect<readonly PassThroughGenericEndpoint[], LitellmError, LitellmRequirements>;
|
|
59
|
+
/** POST /config/pass_through_endpoint. `body` carries `id` — see pass-through-endpoint.ts. */
|
|
60
|
+
export declare const createPassThroughEndpoint: (body: PassThroughGenericEndpoint) => Effect.Effect<void, LitellmError, LitellmRequirements>;
|
|
61
|
+
/** POST /config/pass_through_endpoint/{endpoint_id}. Merges with `exclude_none` — see pass-through-form.ts. */
|
|
62
|
+
export declare const updatePassThroughEndpoint: (endpointId: string, body: Partial<PassThroughGenericEndpoint>) => Effect.Effect<void, LitellmError, LitellmRequirements>;
|
|
63
|
+
/**
|
|
64
|
+
* DELETE /config/pass_through_endpoint?endpoint_id=… — idempotent BY THIS FUNCTION, not by the
|
|
65
|
+
* vendor. ⚠️ Measured at v1.100.0: deleting an id the list does not have raises `HTTPException(400)`
|
|
66
|
+
* from `pass_through_endpoint_delete` — but the SAME route also 400s `not_allowed_access` for a
|
|
67
|
+
* non-PROXY_ADMIN caller or a disconnected DB (`update_config_general_settings`,
|
|
68
|
+
* proxy_server.py:16389-16416), on a row that never got deleted. A 400 alone cannot tell the two
|
|
69
|
+
* apart, so THIS FUNCTION DOESN'T GUESS FROM THE STATUS ALONE: on 400 it re-lists (a GET — no
|
|
70
|
+
* PROXY_ADMIN required, proxy_server.py:3245-3251) and only treats the delete as already-done if
|
|
71
|
+
* `endpointId` is genuinely absent; otherwise the delete failed and this re-fails with the ORIGINAL
|
|
72
|
+
* error, not a message match (S21 — a status-and-a-real-read decision, never body text).
|
|
73
|
+
*/
|
|
74
|
+
export declare const deletePassThroughEndpoint: (endpointId: string) => Effect.Effect<void, LitellmError, LitellmRequirements>;
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/litellm/client.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAG9D,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,6BAA6B,CAAC;AAErC,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,UAAU,GAAG,kBAAkB,CAAC;AAE7E;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB;aAC7B,MAAM,EAAE,4CAA4C;aACpD,MAAM,EAAE,8CAA8C;aACtD,IAAI,EAAE,2CAA2C;aACjD,MAAM,EAAE,0DAA0D;CAC1D,CAAC;;;;AAEX,qBAAa,wBAAyB,SAAQ,8BAA6C;IACzF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;CAAG;;;;AACL,qBAAa,sBAAuB,SAAQ,4BAA2C;IACrF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;CAAG;;;;AACL,qBAAa,gBAAiB,SAAQ,sBAAqC;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;CAAG;;;;AACL,qBAAa,qBAAsB,SAAQ,2BAA0C;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;CAAG;AAEL,MAAM,MAAM,YAAY,GACpB,wBAAwB,GACxB,sBAAsB,GACtB,gBAAgB,GAChB,qBAAqB,GACrB,uBAAuB,CAAC;AAwD5B,oIAAoI;AACpI,eAAO,MAAM,wBAAwB,QAAO,MAAM,CAAC,MAAM,CACvD,SAAS,0BAA0B,EAAE,EACrC,YAAY,EACZ,mBAAmB,CAIlB,CAAC;AASJ,8FAA8F;AAC9F,eAAO,MAAM,yBAAyB,SAC9B,0BAA0B,KAC/B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,mBAAmB,CACK,CAAC;AAE9D,+GAA+G;AAC/G,eAAO,MAAM,yBAAyB,eACxB,MAAM,QACZ,OAAO,CAAC,0BAA0B,CAAC,KACxC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,mBAAmB,CACuC,CAAC;AAEhG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,eACxB,MAAM,KACjB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,mBAAmB,CAcrD,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lazy Auth service every LiteLLM call reads from, and nothing else reads the environment.
|
|
3
|
+
*
|
|
4
|
+
* ★ LITELLM'S OWN VARIABLE NAMES, NOT A HOUSE CHOICE. `LITELLM_PROXY_URL` and
|
|
5
|
+
* `LITELLM_PROXY_API_KEY` are what LiteLLM's own CLI client reads
|
|
6
|
+
* (litellm/proxy/client/cli/main.py:70,78 at tag v1.100.0) — using the vendor's own names means
|
|
7
|
+
* a consumer who already has a `.env` for the `litellm` CLI needs nothing new for this package.
|
|
8
|
+
* ⛔ NO DEFAULT URL. A published package that fell back to a loopback address would let a
|
|
9
|
+
* consumer who forgot the variable watch every call fail against a proxy that is not theirs,
|
|
10
|
+
* instead of being told which variable is missing (S24/S25 — the house rule, not a vendor one).
|
|
11
|
+
* ⛔ THE KEY IS NEVER A PROP. Alchemy persists attributes unencrypted (S25); the master key that
|
|
12
|
+
* `update_config_general_settings` requires (PROXY_ADMIN only, proxy_server.py:16411 at the
|
|
13
|
+
* tag) is read fresh from the environment on every call, never stored in state.
|
|
14
|
+
* ★ LAZY: the service's VALUE is an `Effect` that resolves the credential, not the credential
|
|
15
|
+
* itself — so a layer built once still reads the environment inside each operation
|
|
16
|
+
* (`yield* yield* LitellmCredentials`), the idiom `alchemy/Auth`'s custom-auth-provider guide
|
|
17
|
+
* describes for a credential a layer must not capture at build time.
|
|
18
|
+
*/
|
|
19
|
+
import * as Context from 'effect/Context';
|
|
20
|
+
import * as Effect from 'effect/Effect';
|
|
21
|
+
import * as Layer from 'effect/Layer';
|
|
22
|
+
export declare const LITELLM_PROXY_URL_ENV = "LITELLM_PROXY_URL";
|
|
23
|
+
export declare const LITELLM_PROXY_API_KEY_ENV = "LITELLM_PROXY_API_KEY";
|
|
24
|
+
export interface LitellmCreds {
|
|
25
|
+
/** Trailing slash stripped. No default — see the header. */
|
|
26
|
+
readonly baseUrl: string;
|
|
27
|
+
/** LiteLLM's master (or a PROXY_ADMIN) key. Never logged, never an attribute. */
|
|
28
|
+
readonly apiKey: string;
|
|
29
|
+
}
|
|
30
|
+
declare const LitellmCredentialsError_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 & {
|
|
31
|
+
readonly _tag: "LitellmCredentialsError";
|
|
32
|
+
} & Readonly<A>;
|
|
33
|
+
export declare class LitellmCredentialsError extends LitellmCredentialsError_base<{
|
|
34
|
+
readonly message: string;
|
|
35
|
+
}> {
|
|
36
|
+
}
|
|
37
|
+
/** Read at call time. Exported so a test can call it directly against `process.env`. */
|
|
38
|
+
export declare const resolveCreds: Effect.Effect<LitellmCreds, LitellmCredentialsError>;
|
|
39
|
+
declare const LitellmCredentials_base: Context.ServiceClass<LitellmCredentials, "homeflare/litellm/Credentials", Effect.Effect<LitellmCreds, LitellmCredentialsError, never>>;
|
|
40
|
+
/** The Effect service a LiteLLM operation reads: `yield* yield* LitellmCredentials`. */
|
|
41
|
+
export declare class LitellmCredentials extends LitellmCredentials_base {
|
|
42
|
+
}
|
|
43
|
+
/** The default layer: resolves from the environment, fresh, on every `yield*`. */
|
|
44
|
+
export declare const litellmCredentialsLayer: Layer.Layer<LitellmCredentials>;
|
|
45
|
+
/** For a test: a fixed credential, never touching the environment. */
|
|
46
|
+
export declare const litellmCredentialsLayerFor: (creds: LitellmCreds) => Layer.Layer<LitellmCredentials>;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/litellm/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAE1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AACzD,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;;;;AAED,qBAAa,uBAAwB,SAAQ,6BAA4C;IACvF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AASL,wFAAwF;AACxF,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAW7E,CAAC;;AAEF,wFAAwF;AACxF,qBAAa,kBAAmB,SAAQ,uBAGJ;CAAG;AAEvC,kFAAkF;AAClF,eAAO,MAAM,uBAAuB,EAAE,KAAK,CAAC,KAAK,CAAC,kBAAkB,CAGnE,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,0BAA0B,UAAW,YAAY,KAAG,KAAK,CAAC,KAAK,CAAC,kBAAkB,CACrC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A loopback `Bun.serve` fake of LiteLLM's `/config/pass_through_endpoint` routes — TEST ONLY,
|
|
3
|
+
* never imported by `index.ts` (the netbox/proxmox house convention for test-only helpers).
|
|
4
|
+
*
|
|
5
|
+
* ★ MODELS THE WHOLE-FIELD READ-MODIFY-WRITE ON PURPOSE. `general_settings.pass_through_endpoints`
|
|
6
|
+
* is ONE field (client.ts's header), so every mutation here reads the CURRENT row list, waits
|
|
7
|
+
* (so two concurrent requests can interleave), then writes back what it captured — not what is
|
|
8
|
+
* there by the time it writes. Two `create` calls that race lose one row, exactly like the
|
|
9
|
+
* vendor field would without the client's per-base-URL semaphore serialising them.
|
|
10
|
+
* ⚠️ WHAT THIS FAKE DOES NOT MODEL: PROXY_ADMIN vs. a lesser role for most routes (any correct
|
|
11
|
+
* bearer token is accepted); and update-of-a-missing-id, which is not measured at the tag — this
|
|
12
|
+
* fake 400s it, the same status a missing delete gets, as a documented TEST SIMPLIFICATION, not a
|
|
13
|
+
* vendor fact (docs/litellm.md says so too). `forbidDelete` is the one exception: it models the
|
|
14
|
+
* real asymmetry client.ts's header describes — `DELETE` routes through
|
|
15
|
+
* `update_config_general_settings`, which 400s `not_allowed_access` for a non-PROXY_ADMIN caller
|
|
16
|
+
* (or a disconnected DB) on a row that is genuinely still live, the SAME status a missing-id
|
|
17
|
+
* delete gets (proxy_server.py:16389-16416 at v1.100.0).
|
|
18
|
+
*/
|
|
19
|
+
import type { PassThroughGenericEndpoint } from './generated/pass-through.ts';
|
|
20
|
+
export interface FakeRequest {
|
|
21
|
+
readonly method: string;
|
|
22
|
+
readonly path: string;
|
|
23
|
+
}
|
|
24
|
+
export interface FakeLitellm {
|
|
25
|
+
readonly url: string;
|
|
26
|
+
readonly rows: () => readonly PassThroughGenericEndpoint[];
|
|
27
|
+
readonly requests: () => readonly FakeRequest[];
|
|
28
|
+
readonly stop: () => void;
|
|
29
|
+
}
|
|
30
|
+
export declare const startFakeLitellm: (options?: {
|
|
31
|
+
readonly masterKey?: string;
|
|
32
|
+
readonly seed?: readonly PassThroughGenericEndpoint[];
|
|
33
|
+
/** Every `DELETE` 400s `not_allowed_access`, whether or not the row exists — see the file header. */
|
|
34
|
+
readonly forbidDelete?: boolean;
|
|
35
|
+
}) => FakeLitellm;
|
|
36
|
+
//# sourceMappingURL=fake-litellm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-litellm.d.ts","sourceRoot":"","sources":["../../src/litellm/fake-litellm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAE9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,0BAA0B,EAAE,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;CAC3B;AAKD,eAAO,MAAM,gBAAgB,aAAc;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACtD,qGAAqG;IACrG,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC,KAAG,WAyEH,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
|
+
import { type LitellmCreds } from './credentials.ts';
|
|
3
|
+
/** The planned action for each resource, deletions included, keyed by FQN. */
|
|
4
|
+
export type Planned = Readonly<Record<string, string>>;
|
|
5
|
+
/** A stack body: resource declarations, as an `alchemy.run.ts` would write them. */
|
|
6
|
+
export type StackBody = Effect.Effect<unknown, unknown, unknown>;
|
|
7
|
+
/** What the CLI would set for one deploy: `adopt` is `--adopt` (the AdoptPolicy service). */
|
|
8
|
+
export type DeployOptions = {
|
|
9
|
+
readonly adopt?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export interface FakeStack {
|
|
12
|
+
/** Plan, then apply, `body`. Resolves to the plan; rejects with the apply's failure. */
|
|
13
|
+
readonly deploy: (body: StackBody, options?: DeployOptions) => Promise<Planned>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ⚠️ CREDENTIALS/HTTPCLIENT ARE PROVIDED AROUND `deploy`, NOT BAKED INTO THE PROVIDER LAYER.
|
|
17
|
+
* `Provider.effect`'s own construction effect is `Effect.succeed(...)` (pass-through-endpoint.ts)
|
|
18
|
+
* — it never actually reads `LitellmCredentials`/`HttpClient`; only the HANDLERS it returns do,
|
|
19
|
+
* and those run later, whenever `Apply.ts` calls `provider.reconcile`/`read`/`delete`. Effect
|
|
20
|
+
* resolves a `yield*`'s service from whatever ambient context wraps the CALL SITE, so the layers
|
|
21
|
+
* those handlers need must wrap the whole plan+apply pipeline below, exactly where openbao's
|
|
22
|
+
* fake-stack.ts wraps `BaoEnv` around its own `deploy`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const fakeStack: (creds: LitellmCreds, name?: string) => FakeStack;
|
|
25
|
+
/** Every call that changed something, as `METHOD path`, in order. */
|
|
26
|
+
export declare const writesOf: (requests: readonly {
|
|
27
|
+
readonly method: string;
|
|
28
|
+
readonly path: string;
|
|
29
|
+
}[]) => string[];
|
|
30
|
+
//# sourceMappingURL=fake-stack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-stack.d.ts","sourceRoot":"","sources":["../../src/litellm/fake-stack.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,KAAK,YAAY,EAA8B,MAAM,kBAAkB,CAAC;AAGjF,8EAA8E;AAC9E,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD,oFAAoF;AACpF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAEjE,6FAA6F;AAC7F,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,SAAS;IACxB,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjF;AAiBD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,UAAW,YAAY,oBAA8B,SAgC1E,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,QAAQ,aACT,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,KACtE,MAAM,EACsF,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated from LiteLLM 1.100.0's OpenAPI document -- DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Run: bun codegen/litellm.ts (`--check` compares without writing)
|
|
5
|
+
* Manifest entry: `litellm-openapi` -- LiteLLM Proxy (app.openapi()) 1.100.0
|
|
6
|
+
* sha256 1b3e4d23da6aea92..., read from codegen/manifest.json's own note on how this document
|
|
7
|
+
* was produced (the vendor's own generator, cross-checked byte-identically — not a live read).
|
|
8
|
+
*
|
|
9
|
+
* Covers the 4 pass-through operations this package writes to:
|
|
10
|
+
* litellm:DELETE /config/pass_through_endpoint
|
|
11
|
+
* litellm:GET /config/pass_through_endpoint
|
|
12
|
+
* litellm:POST /config/pass_through_endpoint
|
|
13
|
+
* litellm:POST /config/pass_through_endpoint/{endpoint_id}
|
|
14
|
+
*
|
|
15
|
+
* Does NOT cover the other 692 paths LiteLLM 1.100.0 publishes — this generator is
|
|
16
|
+
* consumer-driven (codegen/litellm.ts), not exhaustive. See codegen/README.md.
|
|
17
|
+
*/
|
|
18
|
+
export interface PassThroughEndpointResponse {
|
|
19
|
+
readonly endpoints: readonly PassThroughGenericEndpoint[];
|
|
20
|
+
}
|
|
21
|
+
export interface PassThroughGenericEndpoint {
|
|
22
|
+
/**
|
|
23
|
+
* Whether authentication is required for the pass-through endpoint. Defaults to True so a pass-through silently created without an explicit value still requires a valid LiteLLM API key — set to False only if the endpoint is meant to be a public forwarder (e.g. an unauthenticated webhook target).
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
readonly auth?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The USD cost per request to the target endpoint. This is used to calculate the cost of the request to the target endpoint.
|
|
29
|
+
* @default 0
|
|
30
|
+
*/
|
|
31
|
+
readonly cost_per_request?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Key-value pairs of default query parameters to be sent with every request to this endpoint. These can be overridden by client-provided query parameters. For example: {'key': 'default_value', 'api_version': '2023-01'}
|
|
34
|
+
* @default {}
|
|
35
|
+
*/
|
|
36
|
+
readonly default_query_params?: Record<string, unknown>;
|
|
37
|
+
/** Guardrails configuration for this passthrough endpoint. Dict keys are guardrail names, values are optional settings for field targeting. When set, all org/team/key level guardrails will also execute. Defaults to None (no guardrails execute). */
|
|
38
|
+
readonly guardrails?: Record<string, PassThroughGuardrailSettings | null> | null;
|
|
39
|
+
/**
|
|
40
|
+
* Key-value pairs of headers to be forwarded with the request. You can set any key value pair here and it will be forwarded to your target endpoint
|
|
41
|
+
* @default {}
|
|
42
|
+
*/
|
|
43
|
+
readonly headers?: Record<string, unknown>;
|
|
44
|
+
/** Optional unique identifier for the pass-through endpoint. If not provided, endpoints will be identified by path for backwards compatibility. */
|
|
45
|
+
readonly id?: string | null;
|
|
46
|
+
/**
|
|
47
|
+
* If True, requests to subpaths of the path will be forwarded to the target endpoint. For example, if the path is /bria and include_subpath is True, requests to /bria/v1/text-to-image/base/2.3 will be forwarded to the target endpoint.
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
readonly include_subpath?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* True if this endpoint is defined in the config file, False if from DB. Config-defined endpoints cannot be edited via the UI.
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
readonly is_from_config?: boolean;
|
|
56
|
+
/** List of HTTP methods this endpoint handles (e.g., ['GET', 'POST']). If None or empty, all methods (GET, POST, PUT, DELETE, PATCH) are supported for backward compatibility. This allows the same path to have different targets for different HTTP methods. */
|
|
57
|
+
readonly methods?: readonly string[] | null;
|
|
58
|
+
/** The route to be added to the LiteLLM Proxy Server. */
|
|
59
|
+
readonly path: string;
|
|
60
|
+
/** The URL to which requests for this path should be forwarded. */
|
|
61
|
+
readonly target: string;
|
|
62
|
+
/** Upstream request timeout in seconds for this pass-through endpoint. If unset, uses general_settings.pass_through_request_timeout (default 600). */
|
|
63
|
+
readonly timeout?: number | null;
|
|
64
|
+
}
|
|
65
|
+
export interface PassThroughGuardrailSettings {
|
|
66
|
+
/** JSONPath expressions for input field targeting (pre_call). Examples: 'query', 'documents[*].text', 'messages[*].content'. If not specified, guardrail runs on entire request payload. */
|
|
67
|
+
readonly request_fields?: readonly string[] | null;
|
|
68
|
+
/** JSONPath expressions for output field targeting (post_call). Examples: 'results[*].text', 'output'. If not specified, guardrail runs on entire response payload. */
|
|
69
|
+
readonly response_fields?: readonly string[] | null;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=pass-through.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pass-through.d.ts","sourceRoot":"","sources":["../../../src/litellm/generated/pass-through.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,SAAS,0BAA0B,EAAE,CAAC;CAC3D;AAED,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxD,wPAAwP;IACxP,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,4BAA4B,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACjF;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,mJAAmJ;IACnJ,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,kQAAkQ;IAClQ,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC5C,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sJAAsJ;IACtJ,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,4LAA4L;IAC5L,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACnD,uKAAuK;IACvK,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CACrD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiteLLM providers for Alchemy.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ THIS BARREL IS THE PUBLIC API, deliberately smaller than the directory — `fake-litellm.ts`
|
|
5
|
+
* and the internals `pass-through-form.ts`/`client.ts` need but a consumer should not depend on
|
|
6
|
+
* directly are not re-exported here (netbox/index.ts's own rule).
|
|
7
|
+
*/
|
|
8
|
+
export { LITELLM_PROXY_API_KEY_ENV, LITELLM_PROXY_URL_ENV, LitellmCredentials, type LitellmCreds, LitellmCredentialsError, litellmCredentialsLayer, litellmCredentialsLayerFor, } from './credentials.ts';
|
|
9
|
+
export { LITELLM_OPERATIONS, LitellmBadRequestError, type LitellmError, LitellmHttpError, type LitellmRequirements, LitellmTransportError, LitellmUnauthorizedError, } from './client.ts';
|
|
10
|
+
export type { PassThroughEndpointResponse, PassThroughGenericEndpoint, PassThroughGuardrailSettings, } from './generated/pass-through.ts';
|
|
11
|
+
export { LiteLLMPassThroughEndpoint, LiteLLMPassThroughEndpointProvider, LitellmConfigPathConflictError, LitellmLiteralSecretHeaderError, LitellmUnaddressableRowError, isLiteLLMPassThroughEndpoint, type PassThroughEndpointAttributes, type PassThroughEndpointError, type PassThroughEndpointProps, } from './pass-through-endpoint.ts';
|
|
12
|
+
export { litellmProviders } from './providers.ts';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/litellm/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,KAAK,YAAY,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,YAAY,EACjB,gBAAgB,EAChB,KAAK,mBAAmB,EACxB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,0BAA0B,EAC1B,kCAAkC,EAClC,8BAA8B,EAC9B,+BAA+B,EAC/B,4BAA4B,EAC5B,4BAA4B,EAC5B,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,GAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// src/litellm/credentials.ts
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Data from "effect/Data";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
var LITELLM_PROXY_URL_ENV = "LITELLM_PROXY_URL";
|
|
7
|
+
var LITELLM_PROXY_API_KEY_ENV = "LITELLM_PROXY_API_KEY";
|
|
8
|
+
|
|
9
|
+
class LitellmCredentialsError extends Data.TaggedError("LitellmCredentialsError") {
|
|
10
|
+
}
|
|
11
|
+
var missing = (name) => new LitellmCredentialsError({
|
|
12
|
+
message: `${name} is not set. Export it — the same variable LiteLLM's own \`litellm\` CLI ` + "reads — before running a stack that declares a LiteLLM.PassThroughEndpoint."
|
|
13
|
+
});
|
|
14
|
+
var resolveCreds = Effect.gen(function* () {
|
|
15
|
+
const rawUrl = process.env[LITELLM_PROXY_URL_ENV];
|
|
16
|
+
if (rawUrl === undefined || rawUrl.trim() === "")
|
|
17
|
+
return yield* Effect.fail(missing(LITELLM_PROXY_URL_ENV));
|
|
18
|
+
const rawKey = process.env[LITELLM_PROXY_API_KEY_ENV];
|
|
19
|
+
if (rawKey === undefined || rawKey.trim() === "") {
|
|
20
|
+
return yield* Effect.fail(missing(LITELLM_PROXY_API_KEY_ENV));
|
|
21
|
+
}
|
|
22
|
+
return { baseUrl: rawUrl.trim().replace(/\/$/, ""), apiKey: rawKey.trim() };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
class LitellmCredentials extends Context.Service()("homeflare/litellm/Credentials") {
|
|
26
|
+
}
|
|
27
|
+
var litellmCredentialsLayer = Layer.succeed(LitellmCredentials, resolveCreds);
|
|
28
|
+
var litellmCredentialsLayerFor = (creds) => Layer.succeed(LitellmCredentials, Effect.succeed(creds));
|
|
29
|
+
// src/litellm/client.ts
|
|
30
|
+
import * as Data2 from "effect/Data";
|
|
31
|
+
import * as Effect2 from "effect/Effect";
|
|
32
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
33
|
+
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
34
|
+
import * as Semaphore from "effect/Semaphore";
|
|
35
|
+
var LITELLM_OPERATIONS = {
|
|
36
|
+
create: "litellm:POST /config/pass_through_endpoint",
|
|
37
|
+
delete: "litellm:DELETE /config/pass_through_endpoint",
|
|
38
|
+
list: "litellm:GET /config/pass_through_endpoint",
|
|
39
|
+
update: "litellm:POST /config/pass_through_endpoint/{endpoint_id}"
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
class LitellmUnauthorizedError extends Data2.TaggedError("LitellmUnauthorizedError") {
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class LitellmBadRequestError extends Data2.TaggedError("LitellmBadRequestError") {
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class LitellmHttpError extends Data2.TaggedError("LitellmHttpError") {
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class LitellmTransportError extends Data2.TaggedError("LitellmTransportError") {
|
|
52
|
+
}
|
|
53
|
+
var semaphores = new Map;
|
|
54
|
+
var semaphoreFor = (baseUrl) => Effect2.sync(() => {
|
|
55
|
+
const existing = semaphores.get(baseUrl);
|
|
56
|
+
if (existing !== undefined)
|
|
57
|
+
return existing;
|
|
58
|
+
const created = Semaphore.makeUnsafe(1);
|
|
59
|
+
semaphores.set(baseUrl, created);
|
|
60
|
+
return created;
|
|
61
|
+
});
|
|
62
|
+
var call = (method, path, body) => Effect2.gen(function* () {
|
|
63
|
+
const creds = yield* yield* LitellmCredentials;
|
|
64
|
+
const client = yield* HttpClient.HttpClient;
|
|
65
|
+
const url = `${creds.baseUrl}${path}`;
|
|
66
|
+
const request = HttpClientRequest.make(method)(url).pipe(HttpClientRequest.setHeaders({
|
|
67
|
+
Accept: "application/json",
|
|
68
|
+
Authorization: `Bearer ${creds.apiKey}`
|
|
69
|
+
}), body === undefined ? (self) => self : HttpClientRequest.bodyText(JSON.stringify(body), "application/json"));
|
|
70
|
+
const response = yield* client.execute(request).pipe(Effect2.mapError((cause) => new LitellmTransportError({ cause: String(cause), method, path })));
|
|
71
|
+
const text = yield* response.text.pipe(Effect2.mapError((cause) => new LitellmTransportError({ cause: String(cause), method, path })));
|
|
72
|
+
if (response.status === 401 || response.status === 403) {
|
|
73
|
+
return yield* Effect2.fail(new LitellmUnauthorizedError({ method, path, status: response.status }));
|
|
74
|
+
}
|
|
75
|
+
if (response.status === 400) {
|
|
76
|
+
return yield* Effect2.fail(new LitellmBadRequestError({ detail: text.slice(0, 300), method, path }));
|
|
77
|
+
}
|
|
78
|
+
if (response.status < 200 || response.status >= 300) {
|
|
79
|
+
return yield* Effect2.fail(new LitellmHttpError({ detail: text.slice(0, 300), method, path, status: response.status }));
|
|
80
|
+
}
|
|
81
|
+
return text === "" ? undefined : JSON.parse(text);
|
|
82
|
+
});
|
|
83
|
+
var listPassThroughEndpoints = () => call("GET", "/config/pass_through_endpoint").pipe(Effect2.map((body) => body.endpoints));
|
|
84
|
+
var mutate = (io) => Effect2.gen(function* () {
|
|
85
|
+
const creds = yield* yield* LitellmCredentials;
|
|
86
|
+
const semaphore = yield* semaphoreFor(creds.baseUrl);
|
|
87
|
+
return yield* semaphore.withPermits(1)(io);
|
|
88
|
+
});
|
|
89
|
+
var createPassThroughEndpoint = (body) => mutate(call("POST", "/config/pass_through_endpoint", body));
|
|
90
|
+
var updatePassThroughEndpoint = (endpointId, body) => mutate(call("POST", `/config/pass_through_endpoint/${encodeURIComponent(endpointId)}`, body));
|
|
91
|
+
var deletePassThroughEndpoint = (endpointId) => mutate(call("DELETE", `/config/pass_through_endpoint?endpoint_id=${encodeURIComponent(endpointId)}`).pipe(Effect2.catchTag("LitellmBadRequestError", (original) => Effect2.gen(function* () {
|
|
92
|
+
const rows = yield* listPassThroughEndpoints();
|
|
93
|
+
if (rows.some((row) => row.id === endpointId))
|
|
94
|
+
return yield* Effect2.fail(original);
|
|
95
|
+
})), Effect2.asVoid));
|
|
96
|
+
// src/litellm/pass-through-endpoint.ts
|
|
97
|
+
import { Resource } from "alchemy";
|
|
98
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
99
|
+
import { isResolved } from "alchemy/Diff";
|
|
100
|
+
import { createPhysicalName } from "alchemy/PhysicalName";
|
|
101
|
+
import * as Provider from "alchemy/Provider";
|
|
102
|
+
import * as Data3 from "effect/Data";
|
|
103
|
+
import * as Effect3 from "effect/Effect";
|
|
104
|
+
import * as Predicate from "effect/Predicate";
|
|
105
|
+
|
|
106
|
+
// src/litellm/pass-through-form.ts
|
|
107
|
+
import { deepEqual } from "alchemy/Diff";
|
|
108
|
+
var settled = (props) => ({
|
|
109
|
+
auth: props.auth ?? true,
|
|
110
|
+
cost_per_request: props.cost_per_request ?? 0,
|
|
111
|
+
default_query_params: props.default_query_params ?? {},
|
|
112
|
+
headers: props.headers ?? {},
|
|
113
|
+
include_subpath: props.include_subpath ?? false
|
|
114
|
+
});
|
|
115
|
+
var NULLABLE_FIELDS = ["timeout", "methods", "guardrails"];
|
|
116
|
+
var isUnset = (value) => value === undefined || value === null;
|
|
117
|
+
var prune = (value) => Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));
|
|
118
|
+
var createBody = (props, id) => prune({
|
|
119
|
+
...settled(props),
|
|
120
|
+
guardrails: isUnset(props.guardrails) ? undefined : props.guardrails,
|
|
121
|
+
id,
|
|
122
|
+
methods: isUnset(props.methods) ? undefined : props.methods,
|
|
123
|
+
path: props.path,
|
|
124
|
+
target: props.target,
|
|
125
|
+
timeout: isUnset(props.timeout) ? undefined : props.timeout
|
|
126
|
+
});
|
|
127
|
+
var updateBody = (props) => prune({
|
|
128
|
+
...settled(props),
|
|
129
|
+
guardrails: isUnset(props.guardrails) ? undefined : props.guardrails,
|
|
130
|
+
methods: isUnset(props.methods) ? undefined : props.methods,
|
|
131
|
+
path: props.path,
|
|
132
|
+
target: props.target,
|
|
133
|
+
timeout: isUnset(props.timeout) ? undefined : props.timeout
|
|
134
|
+
});
|
|
135
|
+
var needsReplace = (live, props) => NULLABLE_FIELDS.some((field) => !isUnset(live[field]) && isUnset(props[field]));
|
|
136
|
+
var matches = (live, props) => {
|
|
137
|
+
const fixed = settled(props);
|
|
138
|
+
return live.target === props.target && live.auth === fixed.auth && live.cost_per_request === fixed.cost_per_request && live.include_subpath === fixed.include_subpath && deepEqual(live.headers, fixed.headers) && deepEqual(live.default_query_params, fixed.default_query_params) && (isUnset(props.timeout) ? isUnset(live.timeout) : live.timeout === props.timeout) && (isUnset(props.methods) ? isUnset(live.methods) : deepEqual(live.methods, props.methods)) && (isUnset(props.guardrails) ? isUnset(live.guardrails) : deepEqual(live.guardrails, props.guardrails));
|
|
139
|
+
};
|
|
140
|
+
var SECRET_HEADER_NAMES = new Set(["authorization", "x-api-key", "cf-aig-authorization"]);
|
|
141
|
+
var literalSecretHeaders = (headers) => Object.entries(headers ?? {}).filter(([name, value]) => SECRET_HEADER_NAMES.has(name.toLowerCase()) && !String(value).includes("os.environ/")).map(([name]) => name);
|
|
142
|
+
|
|
143
|
+
// src/litellm/pass-through-endpoint.ts
|
|
144
|
+
class LitellmConfigPathConflictError extends Data3.TaggedError("LitellmConfigPathConflictError") {
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class LitellmUnaddressableRowError extends Data3.TaggedError("LitellmUnaddressableRowError") {
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class LitellmLiteralSecretHeaderError extends Data3.TaggedError("LitellmLiteralSecretHeaderError") {
|
|
151
|
+
}
|
|
152
|
+
var LiteLLMPassThroughEndpoint = Resource("LiteLLM.PassThroughEndpoint");
|
|
153
|
+
var isLiteLLMPassThroughEndpoint = (value) => Predicate.hasProperty(value, "Type") && value.Type === "LiteLLM.PassThroughEndpoint";
|
|
154
|
+
var toAttributes = (row, fallbackId) => ({
|
|
155
|
+
...row,
|
|
156
|
+
id: row.id ?? fallbackId
|
|
157
|
+
});
|
|
158
|
+
var physicalIdOf = (logicalId, instanceId) => createPhysicalName({ id: logicalId, instanceId, lowercase: true, maxLength: 64 });
|
|
159
|
+
var locate = (rows, objectId, path) => {
|
|
160
|
+
const mine = rows.find((row) => row.id === objectId);
|
|
161
|
+
const conflict = mine === undefined ? rows.find((row) => row.path === path) : undefined;
|
|
162
|
+
return { conflict, mine };
|
|
163
|
+
};
|
|
164
|
+
var conflictRefusal = (conflict, path) => conflict.is_from_config === true ? new LitellmConfigPathConflictError({ path }) : new LitellmUnaddressableRowError({ path });
|
|
165
|
+
var readConflict = (conflict, path) => conflict.is_from_config !== true && conflict.id !== undefined && conflict.id !== null ? Effect3.succeed(Unowned(toAttributes(conflict, conflict.id))) : Effect3.fail(conflictRefusal(conflict, path));
|
|
166
|
+
var refuseLiteralSecrets = (headers) => {
|
|
167
|
+
const bad = literalSecretHeaders(headers);
|
|
168
|
+
return bad.length === 0 ? Effect3.void : Effect3.fail(new LitellmLiteralSecretHeaderError({ headers: bad }));
|
|
169
|
+
};
|
|
170
|
+
var passThroughHandlers = {
|
|
171
|
+
read: ({
|
|
172
|
+
id,
|
|
173
|
+
instanceId,
|
|
174
|
+
olds,
|
|
175
|
+
output
|
|
176
|
+
}) => Effect3.gen(function* () {
|
|
177
|
+
const objectId = output?.id ?? (yield* physicalIdOf(id, instanceId));
|
|
178
|
+
const rows = yield* listPassThroughEndpoints();
|
|
179
|
+
const { conflict, mine } = locate(rows, objectId, olds.path);
|
|
180
|
+
if (mine !== undefined)
|
|
181
|
+
return toAttributes(mine, objectId);
|
|
182
|
+
if (conflict === undefined)
|
|
183
|
+
return;
|
|
184
|
+
return yield* readConflict(conflict, olds.path);
|
|
185
|
+
}),
|
|
186
|
+
diff: ({
|
|
187
|
+
news,
|
|
188
|
+
output
|
|
189
|
+
}) => Effect3.gen(function* () {
|
|
190
|
+
if (!isResolved(news))
|
|
191
|
+
return;
|
|
192
|
+
yield* refuseLiteralSecrets(news.headers);
|
|
193
|
+
if (output === undefined)
|
|
194
|
+
return;
|
|
195
|
+
if (needsReplace(output, news))
|
|
196
|
+
return { action: "replace", deleteFirst: true };
|
|
197
|
+
return matches(output, news) ? { action: "noop" } : { action: "update" };
|
|
198
|
+
}),
|
|
199
|
+
reconcile: ({
|
|
200
|
+
id,
|
|
201
|
+
instanceId,
|
|
202
|
+
news,
|
|
203
|
+
output
|
|
204
|
+
}) => Effect3.gen(function* () {
|
|
205
|
+
yield* refuseLiteralSecrets(news.headers);
|
|
206
|
+
const objectId = output?.id ?? (yield* physicalIdOf(id, instanceId));
|
|
207
|
+
const rows = yield* listPassThroughEndpoints();
|
|
208
|
+
const { conflict, mine } = locate(rows, objectId, news.path);
|
|
209
|
+
if (mine === undefined) {
|
|
210
|
+
if (conflict !== undefined)
|
|
211
|
+
return yield* Effect3.fail(conflictRefusal(conflict, news.path));
|
|
212
|
+
yield* createPassThroughEndpoint(createBody(news, objectId));
|
|
213
|
+
} else if (mine.is_from_config === true) {
|
|
214
|
+
return yield* Effect3.fail(new LitellmConfigPathConflictError({ path: news.path }));
|
|
215
|
+
} else if (needsReplace(toAttributes(mine, objectId), news)) {
|
|
216
|
+
yield* deletePassThroughEndpoint(objectId);
|
|
217
|
+
yield* createPassThroughEndpoint(createBody(news, objectId));
|
|
218
|
+
} else if (!matches(toAttributes(mine, objectId), news)) {
|
|
219
|
+
const body = updateBody(news);
|
|
220
|
+
if (Object.keys(body).length > 0)
|
|
221
|
+
yield* updatePassThroughEndpoint(objectId, body);
|
|
222
|
+
}
|
|
223
|
+
const after = (yield* listPassThroughEndpoints()).find((row) => row.id === objectId);
|
|
224
|
+
if (after === undefined) {
|
|
225
|
+
return yield* Effect3.die(new Error(`LiteLLM.PassThroughEndpoint ${news.path}: the write returned no error but id ` + `${objectId} is still absent from GET /config/pass_through_endpoint — read back ` + "rather than trusting the call that just returned."));
|
|
226
|
+
}
|
|
227
|
+
return toAttributes(after, objectId);
|
|
228
|
+
}),
|
|
229
|
+
delete: ({
|
|
230
|
+
id,
|
|
231
|
+
instanceId,
|
|
232
|
+
olds,
|
|
233
|
+
output
|
|
234
|
+
}) => Effect3.gen(function* () {
|
|
235
|
+
const objectId = output.id ?? (yield* physicalIdOf(id, instanceId));
|
|
236
|
+
const rows = yield* listPassThroughEndpoints();
|
|
237
|
+
const live = rows.find((row) => row.id === objectId);
|
|
238
|
+
if (live === undefined || live.is_from_config === true || live.path !== olds.path)
|
|
239
|
+
return;
|
|
240
|
+
yield* deletePassThroughEndpoint(objectId);
|
|
241
|
+
}),
|
|
242
|
+
list: () => Effect3.succeed([])
|
|
243
|
+
};
|
|
244
|
+
var LiteLLMPassThroughEndpointProvider = () => Provider.effect(LiteLLMPassThroughEndpoint, Effect3.succeed(LiteLLMPassThroughEndpoint.Provider.of(passThroughHandlers)));
|
|
245
|
+
// src/litellm/providers.ts
|
|
246
|
+
import * as Layer2 from "effect/Layer";
|
|
247
|
+
var litellmProviders = (creds = litellmCredentialsLayer) => LiteLLMPassThroughEndpointProvider().pipe(Layer2.provide(creds));
|
|
248
|
+
export {
|
|
249
|
+
LITELLM_OPERATIONS,
|
|
250
|
+
LITELLM_PROXY_API_KEY_ENV,
|
|
251
|
+
LITELLM_PROXY_URL_ENV,
|
|
252
|
+
LiteLLMPassThroughEndpoint,
|
|
253
|
+
LiteLLMPassThroughEndpointProvider,
|
|
254
|
+
LitellmBadRequestError,
|
|
255
|
+
LitellmConfigPathConflictError,
|
|
256
|
+
LitellmCredentials,
|
|
257
|
+
LitellmCredentialsError,
|
|
258
|
+
LitellmHttpError,
|
|
259
|
+
LitellmLiteralSecretHeaderError,
|
|
260
|
+
LitellmTransportError,
|
|
261
|
+
LitellmUnaddressableRowError,
|
|
262
|
+
LitellmUnauthorizedError,
|
|
263
|
+
isLiteLLMPassThroughEndpoint,
|
|
264
|
+
litellmCredentialsLayer,
|
|
265
|
+
litellmCredentialsLayerFor,
|
|
266
|
+
litellmProviders
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
//# debugId=9216FE33E5E3721264756E2164756E21
|
|
270
|
+
//# sourceMappingURL=index.js.map
|