@homeflare/alchemy 0.41.0 → 0.43.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/dist/litellm/budget-errors.d.ts +36 -0
- package/dist/litellm/budget-errors.d.ts.map +1 -0
- package/dist/litellm/budget-form.d.ts +50 -0
- package/dist/litellm/budget-form.d.ts.map +1 -0
- package/dist/litellm/budget-operations.d.ts +20 -0
- package/dist/litellm/budget-operations.d.ts.map +1 -0
- package/dist/litellm/budget.d.ts +61 -0
- package/dist/litellm/budget.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +5 -0
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/index.d.ts +2 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +125 -2
- package/dist/litellm/index.js.map +9 -5
- package/dist/litellm/operations.d.ts +22 -0
- package/dist/litellm/operations.d.ts.map +1 -1
- package/dist/litellm/providers.d.ts +1 -1
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/openbao/auth-role-form.d.ts +9 -0
- package/dist/openbao/auth-role-form.d.ts.map +1 -1
- package/dist/openbao/auth-role-wire.d.ts.map +1 -1
- package/dist/openbao/fake-engines-roles.d.ts.map +1 -1
- package/dist/openbao/index.js +9 -6
- package/dist/openbao/index.js.map +4 -4
- package/dist/postgres/connection.d.ts +20 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +1 -1
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/database.d.ts +1 -1
- package/dist/postgres/database.d.ts.map +1 -1
- package/dist/postgres/index.d.ts +5 -3
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +124 -38
- package/dist/postgres/index.js.map +8 -7
- package/dist/postgres/providers.d.ts +4 -2
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +21 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -0
- package/dist/proxmox/acl.d.ts.map +1 -1
- package/dist/proxmox/api-token.d.ts.map +1 -1
- package/dist/proxmox/ceph-flag-types.d.ts +57 -0
- package/dist/proxmox/ceph-flag-types.d.ts.map +1 -0
- package/dist/proxmox/ceph-flag-wire.d.ts +77 -0
- package/dist/proxmox/ceph-flag-wire.d.ts.map +1 -0
- package/dist/proxmox/ceph-flag.d.ts +15 -50
- package/dist/proxmox/ceph-flag.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd-write.d.ts +10 -13
- package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
- package/dist/proxmox/credential-errors.d.ts +18 -4
- package/dist/proxmox/credential-errors.d.ts.map +1 -1
- package/dist/proxmox/credentials.d.ts +35 -1
- package/dist/proxmox/credentials.d.ts.map +1 -1
- package/dist/proxmox/group.d.ts.map +1 -1
- package/dist/proxmox/index.js +805 -757
- package/dist/proxmox/index.js.map +21 -19
- package/dist/proxmox/lease-cache.d.ts +67 -5
- package/dist/proxmox/lease-cache.d.ts.map +1 -1
- package/dist/proxmox/mint.d.ts +4 -2
- package/dist/proxmox/mint.d.ts.map +1 -1
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/dist/proxmox/role.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/proxmox/unreadable-read.d.ts +8 -1
- package/dist/proxmox/unreadable-read.d.ts.map +1 -1
- package/dist/proxmox/user.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/docs/credentials.md +14 -1
- package/docs/litellm-budget.md +55 -0
- package/docs/litellm.md +7 -6
- package/docs/postgres.md +22 -0
- package/docs/proxmox-sdk-migration.md +45 -0
- package/package.json +3 -3
- package/src/litellm/budget-errors.ts +38 -0
- package/src/litellm/budget-form.ts +94 -0
- package/src/litellm/budget-operations.ts +51 -0
- package/src/litellm/budget.test.ts +105 -0
- package/src/litellm/budget.ts +127 -0
- package/src/litellm/fake-litellm.ts +50 -1
- package/src/litellm/index.ts +13 -0
- package/src/litellm/operations.ts +1 -1
- package/src/litellm/providers.ts +4 -1
- package/src/openbao/auth-role-form.test.ts +37 -0
- package/src/openbao/auth-role-form.ts +12 -1
- package/src/openbao/auth-role-wire.test.ts +1 -0
- package/src/openbao/auth-role-wire.ts +13 -1
- package/src/openbao/distilled-retry.test.ts +1 -0
- package/src/openbao/fake-engines-roles.ts +7 -1
- package/src/postgres/connection.ts +37 -3
- package/src/postgres/database-sql.ts +4 -3
- package/src/postgres/database.ts +3 -2
- package/src/postgres/index.ts +10 -3
- package/src/postgres/providers.ts +15 -2
- package/src/postgres/psql-executor.test.ts +106 -0
- package/src/postgres/psql-executor.ts +130 -0
- package/src/proxmox/acl.ts +7 -1
- package/src/proxmox/api-token.ts +7 -1
- package/src/proxmox/ceph-flag-types.ts +70 -0
- package/src/proxmox/ceph-flag-wire.test.ts +72 -0
- package/src/proxmox/ceph-flag-wire.ts +123 -0
- package/src/proxmox/ceph-flag.ts +77 -130
- package/src/proxmox/ceph-osd-write.ts +10 -13
- package/src/proxmox/credential-errors.ts +19 -5
- package/src/proxmox/credentials.test.ts +73 -1
- package/src/proxmox/credentials.ts +72 -1
- package/src/proxmox/group.ts +7 -1
- package/src/proxmox/lease-cache-mint.test.ts +70 -0
- package/src/proxmox/lease-cache.test.ts +83 -7
- package/src/proxmox/lease-cache.ts +74 -10
- package/src/proxmox/mint.ts +12 -5
- package/src/proxmox/node-network.ts +7 -1
- package/src/proxmox/role.ts +7 -1
- package/src/proxmox/storage.ts +7 -1
- package/src/proxmox/unreadable-read.ts +16 -4
- package/src/proxmox/user.ts +7 -1
- package/src/proxmox/zfs-pool.ts +7 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed refusals `LiteLLM.Budget` raises on top of `@distilled.cloud/litellm`'s own SDK errors.
|
|
3
|
+
*/
|
|
4
|
+
import type * as budgets from '@distilled.cloud/litellm/budget_management';
|
|
5
|
+
declare const LitellmBudgetHardCapUnjustifiedError_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 & {
|
|
6
|
+
readonly _tag: "LitellmBudgetHardCapUnjustifiedError";
|
|
7
|
+
} & Readonly<A>;
|
|
8
|
+
/** A `maxBudget` (a HARD cap: LiteLLM answers 429 once it is spent) declared with no reason. */
|
|
9
|
+
export declare class LitellmBudgetHardCapUnjustifiedError extends LitellmBudgetHardCapUnjustifiedError_base<{
|
|
10
|
+
readonly budgetId: string | undefined;
|
|
11
|
+
}> {
|
|
12
|
+
}
|
|
13
|
+
declare const LitellmBudgetFieldNotClearedError_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 & {
|
|
14
|
+
readonly _tag: "LitellmBudgetFieldNotClearedError";
|
|
15
|
+
} & Readonly<A>;
|
|
16
|
+
/**
|
|
17
|
+
* The write returned no error but the row still carries a limit the declaration does not.
|
|
18
|
+
* `/budget/update` may merge with `exclude_none` (the way `/config/pass_through_endpoint` does),
|
|
19
|
+
* in which case a limit can never be cleared by an update. Refused, never claimed as done.
|
|
20
|
+
*/
|
|
21
|
+
export declare class LitellmBudgetFieldNotClearedError extends LitellmBudgetFieldNotClearedError_base<{
|
|
22
|
+
readonly budgetId: string;
|
|
23
|
+
readonly fields: readonly string[];
|
|
24
|
+
}> {
|
|
25
|
+
}
|
|
26
|
+
declare const LitellmBudgetUnreadableError_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 & {
|
|
27
|
+
readonly _tag: "LitellmBudgetUnreadableError";
|
|
28
|
+
} & Readonly<A>;
|
|
29
|
+
/** `/budget/info` answered something that is not a list of budget rows. */
|
|
30
|
+
export declare class LitellmBudgetUnreadableError extends LitellmBudgetUnreadableError_base<{
|
|
31
|
+
readonly budgetId: string;
|
|
32
|
+
}> {
|
|
33
|
+
}
|
|
34
|
+
export type BudgetError = budgets.PostInfoBudgetBudgetInfoError | budgets.PostNewBudgetBudgetNewError | budgets.UpdateBudgetBudgetUpdatePostError | budgets.DeleteBudgetBudgetDeletePostError | LitellmBudgetHardCapUnjustifiedError | LitellmBudgetFieldNotClearedError | LitellmBudgetUnreadableError;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=budget-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget-errors.d.ts","sourceRoot":"","sources":["../../src/litellm/budget-errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,KAAK,OAAO,MAAM,4CAA4C,CAAC;;;;AAG3E,gGAAgG;AAChG,qBAAa,oCAAqC,SAAQ,0CAExD;IACA,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC,CAAC;CAAG;;;;AAEL;;;;GAIG;AACH,qBAAa,iCAAkC,SAAQ,uCAErD;IACA,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC,CAAC;CAAG;;;;AAEL,2EAA2E;AAC3E,qBAAa,4BAA6B,SAAQ,kCAAiD;IACjG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;CAAG;AAEL,MAAM,MAAM,WAAW,GACnB,OAAO,CAAC,6BAA6B,GACrC,OAAO,CAAC,2BAA2B,GACnC,OAAO,CAAC,iCAAiC,GACzC,OAAO,CAAC,iCAAiC,GACzC,oCAAoC,GACpC,iCAAiC,GACjC,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props, wire bodies and comparison for `LiteLLM.Budget`, testable without a server.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ NO SILENT `max_budget`. LiteLLM's own field doc: "Requests will fail if this budget (in USD)
|
|
5
|
+
* is exceeded" — a hard cap. On 2026-09-28 an undeclared $60/day `max_budget` on
|
|
6
|
+
* `hf-tier-interactive` 429'd the whole claude2 fleet. So `maxBudget` is never defaulted and is
|
|
7
|
+
* refused unless `maxBudgetReason` says why; `softBudget` is the monitoring field (it "will NOT
|
|
8
|
+
* fail if this is exceeded", it fires the alert).
|
|
9
|
+
* ⚠️ NOT MODELLED: `max_parallel_requests`, `model_max_budget`, `budget_reset_at`. The row read
|
|
10
|
+
* back (`BudgetListItem`, generated at LiteLLM 1.100.0) has none of them, so a declaration of
|
|
11
|
+
* them could never be diffed. Add them when a read that returns them is measured.
|
|
12
|
+
*/
|
|
13
|
+
import type * as budgets from '@distilled.cloud/litellm/budget_management';
|
|
14
|
+
export interface BudgetProps {
|
|
15
|
+
/** Live budget id. Set it to adopt an existing row (`hf-tier-agent`); default is a deterministic physical name. */
|
|
16
|
+
readonly budgetId?: string;
|
|
17
|
+
/** Alert threshold in USD. Requests keep flowing when exceeded. The monitoring field. */
|
|
18
|
+
readonly softBudget?: number;
|
|
19
|
+
/** HARD cap in USD: requests fail (429) once spent. Refused unless `maxBudgetReason` is set. */
|
|
20
|
+
readonly maxBudget?: number;
|
|
21
|
+
/** Why a hard cap is wanted. Required with `maxBudget`; recorded in the declaration only. */
|
|
22
|
+
readonly maxBudgetReason?: string;
|
|
23
|
+
/** Reset period, e.g. `'1d'`, `'28d'`. */
|
|
24
|
+
readonly budgetDuration?: string;
|
|
25
|
+
/** Requests per minute. */
|
|
26
|
+
readonly rpmLimit?: number;
|
|
27
|
+
/** Tokens per minute. */
|
|
28
|
+
readonly tpmLimit?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface BudgetAttributes {
|
|
31
|
+
readonly budgetId: string;
|
|
32
|
+
readonly softBudget: number | null;
|
|
33
|
+
readonly maxBudget: number | null;
|
|
34
|
+
readonly budgetDuration: string | null;
|
|
35
|
+
readonly rpmLimit: number | null;
|
|
36
|
+
readonly tpmLimit: number | null;
|
|
37
|
+
}
|
|
38
|
+
export declare const isHardCapUnjustified: (props: BudgetProps) => boolean;
|
|
39
|
+
/** Create body: only what is declared. */
|
|
40
|
+
export declare const createBody: (props: BudgetProps, budgetId: string) => budgets.PostNewBudgetBudgetNewRequest;
|
|
41
|
+
/** Update body: declared fields, plus an explicit `null` for a live limit the declaration dropped. */
|
|
42
|
+
export declare const updateBody: (props: BudgetProps, live: BudgetAttributes) => budgets.UpdateBudgetBudgetUpdatePostRequest;
|
|
43
|
+
/** Wire names of the fields where live and declared disagree. */
|
|
44
|
+
export declare const differing: (live: BudgetAttributes, props: BudgetProps) => readonly string[];
|
|
45
|
+
/**
|
|
46
|
+
* One row of `/budget/info`. `tpm_limit`/`rpm_limit` are `BigInt?` in LiteLLM's schema and come
|
|
47
|
+
* back as decimal strings (the generated `BudgetListItem` says so), hence `Number()`.
|
|
48
|
+
*/
|
|
49
|
+
export declare const toAttributes: (row: Record<string, unknown>) => BudgetAttributes;
|
|
50
|
+
//# sourceMappingURL=budget-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget-form.d.ts","sourceRoot":"","sources":["../../src/litellm/budget-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,KAAK,OAAO,MAAM,4CAA4C,CAAC;AAE3E,MAAM,WAAW,WAAW;IAC1B,mHAAmH;IACnH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,0CAA0C;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,2BAA2B;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAWD,eAAO,MAAM,oBAAoB,UAAW,WAAW,KAAG,OACoB,CAAC;AAE/E,0CAA0C;AAC1C,eAAO,MAAM,UAAU,UACd,WAAW,YACR,MAAM,KACf,OAAO,CAAC,6BAIV,CAAC;AAEF,sGAAsG;AACtG,eAAO,MAAM,UAAU,UACd,WAAW,QACZ,gBAAgB,KACrB,OAAO,CAAC,mCAOV,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,SAAS,SAAU,gBAAgB,SAAS,WAAW,KAAG,SAAS,MAAM,EACG,CAAC;AAK1F;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,gBAO1D,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four LiteLLM `/budget/*` calls `LiteLLM.Budget` uses, through the SDK's typed operations.
|
|
3
|
+
*
|
|
4
|
+
* ★ READS GO THROUGH `/budget/list` (the whole table, a bare array), not `/budget/info`. Both
|
|
5
|
+
* answer `body: unknown` in the generated schema, and what `/budget/info` does for an id that is
|
|
6
|
+
* not there is unmeasured; a list answers absence unambiguously.
|
|
7
|
+
* ⚠️ Unlike pass-through endpoints, each budget is its own DB row, so no semaphore.
|
|
8
|
+
* ⚠️ DELETE is idempotent BY THIS FUNCTION: a `BadRequest` re-lists and is swallowed only when the
|
|
9
|
+
* id is genuinely absent, never on message text (pass-through `operations.ts`' rule).
|
|
10
|
+
*/
|
|
11
|
+
import * as budgets from '@distilled.cloud/litellm/budget_management';
|
|
12
|
+
import * as Effect from 'effect/Effect';
|
|
13
|
+
import { type LitellmOpContext } from './operations.ts';
|
|
14
|
+
import { type BudgetAttributes } from './budget-form.ts';
|
|
15
|
+
import { LitellmBudgetUnreadableError } from './budget-errors.ts';
|
|
16
|
+
export declare const listBudgets: () => Effect.Effect<readonly BudgetAttributes[], budgets.ListBudgetBudgetListGetError | LitellmBudgetUnreadableError, LitellmOpContext>;
|
|
17
|
+
export declare const createBudget: (body: budgets.PostNewBudgetBudgetNewRequest) => Effect.Effect<void, budgets.PostNewBudgetBudgetNewError, import("@distilled.cloud/litellm/Credentials").Credentials>;
|
|
18
|
+
export declare const updateBudget: (body: budgets.UpdateBudgetBudgetUpdatePostRequest) => Effect.Effect<void, budgets.UpdateBudgetBudgetUpdatePostError, import("@distilled.cloud/litellm/Credentials").Credentials>;
|
|
19
|
+
export declare const deleteBudget: (id: string) => Effect.Effect<void, import("@distilled.cloud/caddy").BadGateway | budgets.BadRequest | import("@distilled.cloud/caddy").ConfigError | import("@distilled.cloud/caddy").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | LitellmBudgetUnreadableError | import("@distilled.cloud/litellm/Errors").LitellmParseError | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/litellm/Errors").UnknownLitellmError | budgets.UnprocessableEntity, LitellmOpContext>;
|
|
20
|
+
//# sourceMappingURL=budget-operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget-operations.d.ts","sourceRoot":"","sources":["../../src/litellm/budget-operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,OAAO,MAAM,4CAA4C,CAAC;AACtE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,KAAK,gBAAgB,EAAgB,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,KAAK,gBAAgB,EAAgB,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAElE,eAAO,MAAM,WAAW,QAAO,MAAM,CAAC,MAAM,CAC1C,SAAS,gBAAgB,EAAE,EAC3B,OAAO,CAAC,4BAA4B,GAAG,4BAA4B,EACnE,gBAAgB,CAaf,CAAC;AAEJ,eAAO,MAAM,YAAY,SAAU,OAAO,CAAC,6BAA6B,yHACA,CAAC;AAEzE,eAAO,MAAM,YAAY,SAAU,OAAO,CAAC,mCAAmC,+HACA,CAAC;AAE/E,eAAO,MAAM,YAAY,OAAQ,MAAM,ypBAUpC,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LiteLLM.Budget` — one row of LiteLLM's `LiteLLM_BudgetTable` (`/budget/*`), the "tier" keys and
|
|
3
|
+
* teams bind to (`hf-tier-*`).
|
|
4
|
+
*
|
|
5
|
+
* ★ MONITORING, NOT LIMITS (Tim, 2026-09-28): `softBudget` is the prop; `maxBudget` is a hard cap
|
|
6
|
+
* that 429s every caller and is refused without `maxBudgetReason` (budget-form.ts).
|
|
7
|
+
* ★ ADOPT BY id. `budgetId` names the live row; without it a deterministic physical name is used.
|
|
8
|
+
* A foreign row with that id is `Unowned`, so it needs `--adopt`.
|
|
9
|
+
* ★ `defaultRemovalPolicy: 'retain'` — deleting a budget row that keys still reference either
|
|
10
|
+
* fails or detaches them, and a detached key loses its rate limits. Opt in with
|
|
11
|
+
* `.pipe(RemovalPolicy.destroy())`.
|
|
12
|
+
* ⛔ CLEARING A LIMIT: sent as an explicit `null`. If this LiteLLM merges with `exclude_none` the
|
|
13
|
+
* row keeps the old value; reconcile reads back and FAILS (`LitellmBudgetFieldNotClearedError`)
|
|
14
|
+
* rather than report a cleared cap. Unmeasured against a live proxy at 1.103.0.
|
|
15
|
+
* ⛔ NO CREDENTIAL IS A PROP; `litellmProviders`' layer supplies `Credentials`.
|
|
16
|
+
*/
|
|
17
|
+
import { Resource } from 'alchemy';
|
|
18
|
+
import type { Input } from 'alchemy/Input';
|
|
19
|
+
import * as Provider from 'alchemy/Provider';
|
|
20
|
+
import * as Effect from 'effect/Effect';
|
|
21
|
+
import { LitellmBudgetFieldNotClearedError, LitellmBudgetHardCapUnjustifiedError } from './budget-errors.ts';
|
|
22
|
+
import { type BudgetAttributes, type BudgetProps } from './budget-form.ts';
|
|
23
|
+
export type { BudgetAttributes, BudgetProps };
|
|
24
|
+
export type { BudgetError } from './budget-errors.ts';
|
|
25
|
+
export interface LiteLLMBudget extends Resource<'LiteLLM.Budget', BudgetProps, BudgetAttributes> {
|
|
26
|
+
}
|
|
27
|
+
export declare const LiteLLMBudget: import("alchemy").ResourceClass<LiteLLMBudget>;
|
|
28
|
+
export declare const isLiteLLMBudget: (value: unknown) => value is LiteLLMBudget;
|
|
29
|
+
type Args<P> = {
|
|
30
|
+
id: string;
|
|
31
|
+
instanceId: string;
|
|
32
|
+
output: BudgetAttributes | undefined;
|
|
33
|
+
} & P;
|
|
34
|
+
/** ⛔ TEST-ONLY EXPORT. Not on index.ts's barrel. */
|
|
35
|
+
export declare const budgetHandlers: {
|
|
36
|
+
read: ({ id, instanceId, olds, output }: Args<{
|
|
37
|
+
olds: BudgetProps;
|
|
38
|
+
}>) => Effect.Effect<BudgetAttributes | undefined, import("./budget-errors.ts").LitellmBudgetUnreadableError | import("@distilled.cloud/litellm/budget_management").ListBudgetBudgetListGetError, import("alchemy").InstanceId | import("alchemy").Stack | import("alchemy").Stage | import("@distilled.cloud/litellm/Protocol").LitellmOpContext>;
|
|
39
|
+
diff: ({ news, output }: {
|
|
40
|
+
news: Input<BudgetProps>;
|
|
41
|
+
output: BudgetAttributes | undefined;
|
|
42
|
+
}) => Effect.Effect<{
|
|
43
|
+
readonly action: 'replace';
|
|
44
|
+
readonly deleteFirst: false;
|
|
45
|
+
} | {
|
|
46
|
+
readonly deleteFirst?: never;
|
|
47
|
+
readonly action: 'noop';
|
|
48
|
+
} | {
|
|
49
|
+
readonly deleteFirst?: never;
|
|
50
|
+
readonly action: 'update';
|
|
51
|
+
} | undefined, LitellmBudgetHardCapUnjustifiedError, never>;
|
|
52
|
+
reconcile: ({ id, instanceId, news, output }: Args<{
|
|
53
|
+
news: BudgetProps;
|
|
54
|
+
}>) => Effect.Effect<BudgetAttributes, import("@distilled.cloud/caddy").BadGateway | import("@distilled.cloud/litellm/budget_management").BadRequest | import("@distilled.cloud/caddy").ConfigError | import("@distilled.cloud/caddy").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | LitellmBudgetFieldNotClearedError | LitellmBudgetHardCapUnjustifiedError | import("./budget-errors.ts").LitellmBudgetUnreadableError | import("@distilled.cloud/litellm/Errors").LitellmParseError | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/litellm/Errors").UnknownLitellmError | import("@distilled.cloud/litellm/budget_management").UnprocessableEntity, import("alchemy").InstanceId | import("alchemy").Stack | import("alchemy").Stage | import("@distilled.cloud/litellm/Protocol").LitellmOpContext>;
|
|
55
|
+
delete: ({ output }: {
|
|
56
|
+
output: BudgetAttributes;
|
|
57
|
+
}) => Effect.Effect<void, import("@distilled.cloud/caddy").BadGateway | import("@distilled.cloud/litellm/budget_management").BadRequest | import("@distilled.cloud/caddy").ConfigError | import("@distilled.cloud/caddy").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/caddy").InternalServerError | import("./budget-errors.ts").LitellmBudgetUnreadableError | import("@distilled.cloud/litellm/Errors").LitellmParseError | import("@distilled.cloud/caddy").ServiceUnavailable | import("@distilled.cloud/caddy").TooManyRequests | import("@distilled.cloud/caddy").Unauthorized | import("@distilled.cloud/litellm/Errors").UnknownLitellmError | import("@distilled.cloud/litellm/budget_management").UnprocessableEntity, import("@distilled.cloud/litellm/Protocol").LitellmOpContext>;
|
|
58
|
+
list: () => Effect.Effect<never[], never, never>;
|
|
59
|
+
};
|
|
60
|
+
export declare const LiteLLMBudgetProvider: () => import("effect/Layer").Layer<Provider.Provider<LiteLLMBudget>, never, import("alchemy").Stack | import("alchemy").Stage | import("@distilled.cloud/litellm/Protocol").LitellmOpContext>;
|
|
61
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/litellm/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EACL,iCAAiC,EACjC,oCAAoC,EACrC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAKjB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,WAAW,aAAc,SAAQ,QAAQ,CAAC,gBAAgB,EAAE,WAAW,EAAE,gBAAgB,CAAC;CAAG;AAEnG,eAAO,MAAM,aAAa,gDAExB,CAAC;AAEH,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,KAAK,IAAI,aACe,CAAC;AAmB1E,KAAK,IAAI,CAAC,CAAC,IAAI;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACtC,GAAG,CAAC,CAAC;AAEN,oDAAoD;AACpD,eAAO,MAAM,cAAc;IACzB,IAAI,qCAAqC,IAAI,CAAC;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC;IAQpE,IAAI,qBAAqB;QAAE,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;QAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAA;KAAE;yBAOpE,SAAS;;;;yBAGb,MAAM;;;yBACN,QAAQ;;IAG3B,SAAS,qCAAqC,IAAI,CAAC;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC;IAuBzE,MAAM,eAAe;QAAE,MAAM,EAAE,gBAAgB,CAAA;KAAE;IAEjD,IAAI;CACL,CAAC;AAEF,eAAO,MAAM,qBAAqB,+LACyD,CAAC"}
|
|
@@ -37,11 +37,16 @@ export interface FakeLitellm {
|
|
|
37
37
|
readonly fetch: typeof globalThis.fetch;
|
|
38
38
|
readonly rows: () => readonly PassThroughGenericEndpoint[];
|
|
39
39
|
readonly requests: () => readonly FakeRequest[];
|
|
40
|
+
/** `/budget/*` rows, wire-shaped (`tpm_limit`/`rpm_limit` as decimal strings, like the real table). */
|
|
41
|
+
readonly budgets: () => readonly Record<string, unknown>[];
|
|
40
42
|
}
|
|
41
43
|
export declare const startFakeLitellm: (options?: {
|
|
42
44
|
readonly masterKey?: string;
|
|
43
45
|
readonly seed?: readonly PassThroughGenericEndpoint[];
|
|
44
46
|
/** Every `DELETE` 400s `not_allowed_access`, whether or not the row exists — see the file header. */
|
|
45
47
|
readonly forbidDelete?: boolean;
|
|
48
|
+
readonly budgetSeed?: readonly Record<string, unknown>[];
|
|
49
|
+
/** `/budget/update` drops `null`s instead of writing them (an `exclude_none` merge). */
|
|
50
|
+
readonly budgetExcludeNone?: boolean;
|
|
46
51
|
}) => FakeLitellm;
|
|
47
52
|
//# sourceMappingURL=fake-litellm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-litellm.d.ts","sourceRoot":"","sources":["../../src/litellm/fake-litellm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,sGAAsG;AACtG,eAAO,MAAM,SAAS,gCAAgC,CAAC;AAEvD,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,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,0BAA0B,EAAE,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"fake-litellm.d.ts","sourceRoot":"","sources":["../../src/litellm/fake-litellm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,sGAAsG;AACtG,eAAO,MAAM,SAAS,gCAAgC,CAAC;AAEvD,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,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,0BAA0B,EAAE,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,SAAS,WAAW,EAAE,CAAC;IAChD,uGAAuG;IACvG,QAAQ,CAAC,OAAO,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CAC5D;AAQD,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;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACzD,wFAAwF;IACxF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACtC,KAAG,WA6GH,CAAC"}
|
package/dist/litellm/index.d.ts
CHANGED
|
@@ -11,5 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export type { PassThroughEndpointResponse, PassThroughGenericEndpoint, PassThroughGuardrailSettings, } from '@distilled.cloud/litellm/misc';
|
|
13
13
|
export { LiteLLMPassThroughEndpoint, LiteLLMPassThroughEndpointProvider, LitellmConfigPathConflictError, LitellmLiteralSecretHeaderError, LitellmUnaddressableRowError, isLiteLLMPassThroughEndpoint, type PassThroughEndpointAttributes, type PassThroughEndpointError, type PassThroughEndpointProps, } from './pass-through-endpoint.ts';
|
|
14
|
+
export { LiteLLMBudget, LiteLLMBudgetProvider, isLiteLLMBudget, type BudgetAttributes, type BudgetError, type BudgetProps, } from './budget.ts';
|
|
15
|
+
export { LitellmBudgetFieldNotClearedError, LitellmBudgetHardCapUnjustifiedError, LitellmBudgetUnreadableError, } from './budget-errors.ts';
|
|
14
16
|
export { litellmProviders } from './providers.ts';
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/litellm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AACvC,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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/litellm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AACvC,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,EACL,aAAa,EACb,qBAAqB,EACrB,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iCAAiC,EACjC,oCAAoC,EACpC,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/litellm/index.js
CHANGED
|
@@ -191,19 +191,142 @@ var passThroughHandlers = {
|
|
|
191
191
|
list: () => Effect2.succeed([])
|
|
192
192
|
};
|
|
193
193
|
var LiteLLMPassThroughEndpointProvider = () => Provider.effect(LiteLLMPassThroughEndpoint, Effect2.succeed(LiteLLMPassThroughEndpoint.Provider.of(passThroughHandlers)));
|
|
194
|
+
// src/litellm/budget.ts
|
|
195
|
+
import { Resource as Resource2 } from "alchemy";
|
|
196
|
+
import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
|
|
197
|
+
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
198
|
+
import { createPhysicalName as createPhysicalName2 } from "alchemy/PhysicalName";
|
|
199
|
+
import * as Provider2 from "alchemy/Provider";
|
|
200
|
+
import * as Effect4 from "effect/Effect";
|
|
201
|
+
import * as Predicate2 from "effect/Predicate";
|
|
202
|
+
|
|
203
|
+
// src/litellm/budget-errors.ts
|
|
204
|
+
import * as Data2 from "effect/Data";
|
|
205
|
+
|
|
206
|
+
class LitellmBudgetHardCapUnjustifiedError extends Data2.TaggedError("LitellmBudgetHardCapUnjustifiedError") {
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
class LitellmBudgetFieldNotClearedError extends Data2.TaggedError("LitellmBudgetFieldNotClearedError") {
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class LitellmBudgetUnreadableError extends Data2.TaggedError("LitellmBudgetUnreadableError") {
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/litellm/budget-form.ts
|
|
216
|
+
var FIELDS = [
|
|
217
|
+
["softBudget", "soft_budget"],
|
|
218
|
+
["maxBudget", "max_budget"],
|
|
219
|
+
["budgetDuration", "budget_duration"],
|
|
220
|
+
["rpmLimit", "rpm_limit"],
|
|
221
|
+
["tpmLimit", "tpm_limit"]
|
|
222
|
+
];
|
|
223
|
+
var isHardCapUnjustified = (props) => props.maxBudget !== undefined && (props.maxBudgetReason ?? "").trim() === "";
|
|
224
|
+
var createBody2 = (props, budgetId) => {
|
|
225
|
+
const body = { budget_id: budgetId };
|
|
226
|
+
for (const [prop, wire] of FIELDS)
|
|
227
|
+
if (props[prop] !== undefined)
|
|
228
|
+
body[wire] = props[prop];
|
|
229
|
+
return body;
|
|
230
|
+
};
|
|
231
|
+
var updateBody2 = (props, live) => {
|
|
232
|
+
const body = { budget_id: live.budgetId };
|
|
233
|
+
for (const [prop, wire] of FIELDS) {
|
|
234
|
+
if (props[prop] !== undefined)
|
|
235
|
+
body[wire] = props[prop];
|
|
236
|
+
else if (live[prop] !== null)
|
|
237
|
+
body[wire] = null;
|
|
238
|
+
}
|
|
239
|
+
return body;
|
|
240
|
+
};
|
|
241
|
+
var differing = (live, props) => FIELDS.filter(([prop]) => live[prop] !== (props[prop] ?? null)).map(([, wire]) => wire);
|
|
242
|
+
var numberOrNull = (value) => value === null || value === undefined || value === "" ? null : Number(value);
|
|
243
|
+
var toAttributes2 = (row) => ({
|
|
244
|
+
budgetDuration: typeof row["budget_duration"] === "string" ? row["budget_duration"] : null,
|
|
245
|
+
budgetId: String(row["budget_id"]),
|
|
246
|
+
maxBudget: numberOrNull(row["max_budget"]),
|
|
247
|
+
rpmLimit: numberOrNull(row["rpm_limit"]),
|
|
248
|
+
softBudget: numberOrNull(row["soft_budget"]),
|
|
249
|
+
tpmLimit: numberOrNull(row["tpm_limit"])
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// src/litellm/budget-operations.ts
|
|
253
|
+
import * as budgets from "@distilled.cloud/litellm/budget_management";
|
|
254
|
+
import * as Effect3 from "effect/Effect";
|
|
255
|
+
var listBudgets = () => throughFetch(budgets.listBudgetBudgetListGet({})).pipe(Effect3.flatMap((response) => {
|
|
256
|
+
const rows = Array.isArray(response) ? response : response.body;
|
|
257
|
+
return Array.isArray(rows) ? Effect3.succeed(rows.map((row) => toAttributes2(row))) : Effect3.fail(new LitellmBudgetUnreadableError({ budgetId: "*" }));
|
|
258
|
+
}));
|
|
259
|
+
var createBudget = (body) => throughFetch(budgets.postNewBudgetBudgetNew(body)).pipe(Effect3.asVoid);
|
|
260
|
+
var updateBudget = (body) => throughFetch(budgets.updateBudgetBudgetUpdatePost(body)).pipe(Effect3.asVoid);
|
|
261
|
+
var deleteBudget = (id) => throughFetch(budgets.deleteBudgetBudgetDeletePost({ id })).pipe(Effect3.asVoid, Effect3.catchTag("BadRequest", (original) => listBudgets().pipe(Effect3.flatMap((rows) => rows.some((row) => row.budgetId === id) ? Effect3.fail(original) : Effect3.void))));
|
|
262
|
+
|
|
263
|
+
// src/litellm/budget.ts
|
|
264
|
+
var LiteLLMBudget = Resource2("LiteLLM.Budget", {
|
|
265
|
+
defaultRemovalPolicy: "retain"
|
|
266
|
+
});
|
|
267
|
+
var isLiteLLMBudget = (value) => Predicate2.hasProperty(value, "Type") && value.Type === "LiteLLM.Budget";
|
|
268
|
+
var refuseUnjustifiedCap = (props) => isHardCapUnjustified(props) ? Effect4.fail(new LitellmBudgetHardCapUnjustifiedError({ budgetId: props.budgetId })) : Effect4.void;
|
|
269
|
+
var idOf = (id, instanceId, props, output) => output?.budgetId !== undefined ? Effect4.succeed(output.budgetId) : props.budgetId !== undefined ? Effect4.succeed(props.budgetId) : createPhysicalName2({ id, instanceId, lowercase: true, maxLength: 64 });
|
|
270
|
+
var budgetHandlers = {
|
|
271
|
+
read: ({ id, instanceId, olds, output }) => Effect4.gen(function* () {
|
|
272
|
+
const budgetId = yield* idOf(id, instanceId, olds, output);
|
|
273
|
+
const live = (yield* listBudgets()).find((row) => row.budgetId === budgetId);
|
|
274
|
+
if (live === undefined)
|
|
275
|
+
return;
|
|
276
|
+
return output === undefined ? Unowned2(live) : live;
|
|
277
|
+
}),
|
|
278
|
+
diff: ({ news, output }) => Effect4.gen(function* () {
|
|
279
|
+
if (!isResolved2(news))
|
|
280
|
+
return;
|
|
281
|
+
yield* refuseUnjustifiedCap(news);
|
|
282
|
+
if (output === undefined)
|
|
283
|
+
return;
|
|
284
|
+
if (news.budgetId !== undefined && news.budgetId !== output.budgetId) {
|
|
285
|
+
return { action: "replace", deleteFirst: false };
|
|
286
|
+
}
|
|
287
|
+
return differing(output, news).length === 0 ? { action: "noop" } : { action: "update" };
|
|
288
|
+
}),
|
|
289
|
+
reconcile: ({ id, instanceId, news, output }) => Effect4.gen(function* () {
|
|
290
|
+
yield* refuseUnjustifiedCap(news);
|
|
291
|
+
const budgetId = yield* idOf(id, instanceId, news, output);
|
|
292
|
+
const before = (yield* listBudgets()).find((row) => row.budgetId === budgetId);
|
|
293
|
+
if (before === undefined)
|
|
294
|
+
yield* createBudget(createBody2(news, budgetId));
|
|
295
|
+
else if (differing(before, news).length > 0)
|
|
296
|
+
yield* updateBudget(updateBody2(news, before));
|
|
297
|
+
const after = (yield* listBudgets()).find((row) => row.budgetId === budgetId);
|
|
298
|
+
if (after === undefined) {
|
|
299
|
+
return yield* Effect4.die(new Error(`LiteLLM.Budget ${budgetId}: write returned no error but the row is absent.`));
|
|
300
|
+
}
|
|
301
|
+
const left = differing(after, news);
|
|
302
|
+
if (left.length > 0) {
|
|
303
|
+
return yield* Effect4.fail(new LitellmBudgetFieldNotClearedError({ budgetId, fields: left }));
|
|
304
|
+
}
|
|
305
|
+
return after;
|
|
306
|
+
}),
|
|
307
|
+
delete: ({ output }) => deleteBudget(output.budgetId),
|
|
308
|
+
list: () => Effect4.succeed([])
|
|
309
|
+
};
|
|
310
|
+
var LiteLLMBudgetProvider = () => Provider2.effect(LiteLLMBudget, Effect4.succeed(LiteLLMBudget.Provider.of(budgetHandlers)));
|
|
194
311
|
// src/litellm/providers.ts
|
|
195
312
|
import { CredentialsFromEnv } from "@distilled.cloud/litellm/Credentials";
|
|
196
313
|
import * as Layer from "effect/Layer";
|
|
197
|
-
var litellmProviders = (creds = CredentialsFromEnv) => LiteLLMPassThroughEndpointProvider().pipe(Layer.provideMerge(creds));
|
|
314
|
+
var litellmProviders = (creds = CredentialsFromEnv) => Layer.mergeAll(LiteLLMPassThroughEndpointProvider(), LiteLLMBudgetProvider()).pipe(Layer.provideMerge(creds));
|
|
198
315
|
export {
|
|
316
|
+
LiteLLMBudget,
|
|
317
|
+
LiteLLMBudgetProvider,
|
|
199
318
|
LiteLLMPassThroughEndpoint,
|
|
200
319
|
LiteLLMPassThroughEndpointProvider,
|
|
320
|
+
LitellmBudgetFieldNotClearedError,
|
|
321
|
+
LitellmBudgetHardCapUnjustifiedError,
|
|
322
|
+
LitellmBudgetUnreadableError,
|
|
201
323
|
LitellmConfigPathConflictError,
|
|
202
324
|
LitellmLiteralSecretHeaderError,
|
|
203
325
|
LitellmUnaddressableRowError,
|
|
326
|
+
isLiteLLMBudget,
|
|
204
327
|
isLiteLLMPassThroughEndpoint,
|
|
205
328
|
litellmProviders
|
|
206
329
|
};
|
|
207
330
|
|
|
208
|
-
//# debugId=
|
|
331
|
+
//# debugId=0E62C0ABB834992A64756E2164756E21
|
|
209
332
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/litellm/pass-through-endpoint.ts", "../src/litellm/operations.ts", "../src/litellm/pass-through-errors.ts", "../src/litellm/pass-through-form.ts", "../src/litellm/providers.ts"],
|
|
3
|
+
"sources": ["../src/litellm/pass-through-endpoint.ts", "../src/litellm/operations.ts", "../src/litellm/pass-through-errors.ts", "../src/litellm/pass-through-form.ts", "../src/litellm/budget.ts", "../src/litellm/budget-errors.ts", "../src/litellm/budget-form.ts", "../src/litellm/budget-operations.ts", "../src/litellm/providers.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * `LiteLLM.PassThroughEndpoint` — one row in LiteLLM's `general_settings.pass_through_endpoints`.\n *\n * ★ ADOPT-FRIENDLY BY id, NOT BY PATH. `output?.id` is used as the object's live id once the\n * engine has one — from our own prior create (the deterministic physical name) or from an\n * adopted foreign row — and `read` computes a FRESH physical name only for a resource the\n * engine has never resolved before. This is S7/S9's \"output is a cache of stable identifiers,\n * not proof the object exists\" and S13's \"an adopted object keeps its live name as an explicit\n * prop\", applied to an id LiteLLM lets a client choose (`_types.py:2192` at v1.100.0: create\n * takes a caller-supplied `id`, else `uuid4()`).\n *\n * ⛔ A PATH HELD BY AN `is_from_config` ROW IS REFUSED, NOT ADOPTED. A DB row on the same path\n * overrides it at runtime (`pass_through_endpoints.py` merges DB after config, at the tag) — so\n * creating one would silently take over a route the operator declared in `config.yaml`, the\n * exact \"DB row silently overrides the file\" the task names. `is_from_config` rows are also\n * never updated or deleted by this provider, for the same reason.\n * ⛔ A DB ROW WITH NO `id` (path-only, `_types.py`'s \"backwards compatibility\" case) CANNOT BE\n * ADDRESSED by the id-keyed update/delete calls this provider uses, so it is refused rather than\n * silently adopted into an id-based lifecycle it cannot actually manage — see docs/litellm.md.\n * ⛔ NO CREDENTIAL IS A PROP (S25). `LITELLM_PROXY_URL`/`LITELLM_PROXY_API_KEY` are read at call\n * time by `@distilled.cloud/litellm`'s own `CredentialsFromEnv` (providers.ts) — the same two\n * variable names the retired hand-rolled `credentials.ts` read.\n * ⛔ CREDENTIALS ARE NOT A RESOURCE REQUIREMENT. `LitellmOpContext` (`Credentials | HttpClient`)\n * belongs to the handlers at Apply, on `litellmProviders`'s layer. The 5th `Resource` parameter\n * used to be `LitellmRequirements | Stack | Stage`, so a stack body required `Credentials`, and\n * `Alchemy.Stack` rejects that (`\"LitellmCredentials\"` is not `\"Stack\"`). Measured 2026-09-24.\n */\nimport { Resource } from 'alchemy';\nimport { Unowned } from 'alchemy/AdoptPolicy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport { createPhysicalName } from 'alchemy/PhysicalName';\nimport * as Provider from 'alchemy/Provider';\nimport type * as misc from '@distilled.cloud/litellm/misc';\nimport * as Effect from 'effect/Effect';\nimport * as Predicate from 'effect/Predicate';\nimport {\n createPassThroughEndpoint,\n deletePassThroughEndpoint,\n listPassThroughEndpoints,\n updatePassThroughEndpoint,\n} from './operations.ts';\nimport {\n LitellmConfigPathConflictError,\n LitellmLiteralSecretHeaderError,\n LitellmUnaddressableRowError,\n} from './pass-through-errors.ts';\nimport {\n type PassThroughEndpointAttributes,\n type PassThroughEndpointProps,\n createBody,\n literalSecretHeaders,\n matches,\n needsReplace,\n updateBody,\n} from './pass-through-form.ts';\n\nexport type { PassThroughEndpointAttributes, PassThroughEndpointProps };\nexport {\n LitellmConfigPathConflictError,\n LitellmLiteralSecretHeaderError,\n LitellmUnaddressableRowError,\n type PassThroughEndpointError,\n} from './pass-through-errors.ts';\n\nexport interface LiteLLMPassThroughEndpoint extends Resource<\n 'LiteLLM.PassThroughEndpoint',\n PassThroughEndpointProps,\n PassThroughEndpointAttributes\n> {}\n\nexport const LiteLLMPassThroughEndpoint = Resource<LiteLLMPassThroughEndpoint>(\n 'LiteLLM.PassThroughEndpoint',\n);\n\nexport const isLiteLLMPassThroughEndpoint = (value: unknown): value is LiteLLMPassThroughEndpoint =>\n Predicate.hasProperty(value, 'Type') && value.Type === 'LiteLLM.PassThroughEndpoint';\n\nconst toAttributes = (\n row: misc.PassThroughGenericEndpoint,\n fallbackId: string,\n): PassThroughEndpointAttributes => ({\n ...row,\n id: row.id ?? fallbackId,\n});\n\nconst physicalIdOf = (logicalId: string, instanceId: string) =>\n createPhysicalName({ id: logicalId, instanceId, lowercase: true, maxLength: 64 });\n\n/**\n * Find this resource's live row (by id) and, when absent, the row that already holds its `path`\n * (a config-file row to refuse, or a DB row LiteLLM did not give an id — also refused, see the\n * file header — or a genuine foreign DB row to hand back `Unowned`).\n */\nconst locate = (\n rows: readonly misc.PassThroughGenericEndpoint[],\n objectId: string,\n path: string,\n): {\n readonly mine: misc.PassThroughGenericEndpoint | undefined;\n readonly conflict: misc.PassThroughGenericEndpoint | undefined;\n} => {\n const mine = rows.find((row) => row.id === objectId);\n const conflict = mine === undefined ? rows.find((row) => row.path === path) : undefined;\n return { conflict, mine };\n};\n\n/** The typed refusal for a `path` conflict that cannot ever be adopted (config row, or no id). */\nconst conflictRefusal = (conflict: misc.PassThroughGenericEndpoint, path: string) =>\n conflict.is_from_config === true\n ? new LitellmConfigPathConflictError({ path })\n : new LitellmUnaddressableRowError({ path });\n\n/** `read`'s answer for a path conflict: `Unowned` when it is a real, addressable DB row — a typed refusal otherwise. */\nconst readConflict = (conflict: misc.PassThroughGenericEndpoint, path: string) =>\n conflict.is_from_config !== true && conflict.id !== undefined && conflict.id !== null\n ? Effect.succeed(Unowned(toAttributes(conflict, conflict.id)))\n : Effect.fail(conflictRefusal(conflict, path));\n\nconst refuseLiteralSecrets = (headers: Readonly<Record<string, unknown>> | undefined) => {\n const bad = literalSecretHeaders(headers);\n return bad.length === 0\n ? Effect.void\n : Effect.fail(new LitellmLiteralSecretHeaderError({ headers: bad }));\n};\n\n/** ⛔ TEST-ONLY EXPORT. Not on index.ts's barrel — a lifecycle test calls these directly against the fake. */\nexport const passThroughHandlers = {\n read: ({\n id,\n instanceId,\n olds,\n output,\n }: {\n id: string;\n instanceId: string;\n olds: PassThroughEndpointProps;\n output: PassThroughEndpointAttributes | undefined;\n }) =>\n Effect.gen(function* () {\n const objectId = output?.id ?? (yield* physicalIdOf(id, instanceId));\n const rows = yield* listPassThroughEndpoints();\n const { conflict, mine } = locate(rows, objectId, olds.path);\n if (mine !== undefined) return toAttributes(mine, objectId);\n if (conflict === undefined) return undefined;\n return yield* readConflict(conflict, olds.path);\n }),\n\n diff: ({\n news,\n output,\n }: {\n news: Input<PassThroughEndpointProps>;\n output: PassThroughEndpointAttributes | undefined;\n }) =>\n Effect.gen(function* () {\n if (!isResolved(news)) return undefined;\n yield* refuseLiteralSecrets(news.headers);\n if (output === undefined) return undefined;\n // ⛔ deleteFirst: TRUE — a create-first replace mints a new instanceId/id before the old row\n // on `path` is gone, so the new generation's `reconcile` would refuse it as a foreign\n // conflict (Apply.ts). Same fix this kit's other unique-identity providers use.\n if (needsReplace(output, news)) return { action: 'replace', deleteFirst: true } as const;\n return matches(output, news)\n ? ({ action: 'noop' } as const)\n : ({ action: 'update' } as const);\n }),\n\n reconcile: ({\n id,\n instanceId,\n news,\n output,\n }: {\n id: string;\n instanceId: string;\n news: PassThroughEndpointProps;\n output: PassThroughEndpointAttributes | undefined;\n }) =>\n Effect.gen(function* () {\n yield* refuseLiteralSecrets(news.headers);\n const objectId = output?.id ?? (yield* physicalIdOf(id, instanceId));\n const rows = yield* listPassThroughEndpoints();\n const { conflict, mine } = locate(rows, objectId, news.path);\n\n if (mine === undefined) {\n // ⚠️ A CONFLICT HERE — reconcile, not read — means ownership shifted between plan and\n // apply (a config row or a foreign DB row landed on this path after `read` cleared it).\n // `read` gates a genuine adopt by returning `Unowned`; reconcile itself never adopts,\n // so ANY conflict here fails rather than risking a second row on one path.\n if (conflict !== undefined) return yield* Effect.fail(conflictRefusal(conflict, news.path));\n yield* createPassThroughEndpoint(createBody(news, objectId));\n } else if (mine.is_from_config === true) {\n // ⛔ NEVER UPDATE OR DELETE AN `is_from_config` ROW, even one this resource's own\n // deterministic id happens to match (a config.yaml author could in principle choose the\n // same id `createPhysicalName` would). Refuse rather than silently editing the file's row.\n return yield* Effect.fail(new LitellmConfigPathConflictError({ path: news.path }));\n } else if (needsReplace(toAttributes(mine, objectId), news)) {\n yield* deletePassThroughEndpoint(objectId);\n yield* createPassThroughEndpoint(createBody(news, objectId));\n } else if (!matches(toAttributes(mine, objectId), news)) {\n const body = updateBody(news);\n if (Object.keys(body).length > 0) yield* updatePassThroughEndpoint(objectId, body);\n }\n\n const after = (yield* listPassThroughEndpoints()).find((row) => row.id === objectId);\n if (after === undefined) {\n return yield* Effect.die(\n new Error(\n `LiteLLM.PassThroughEndpoint ${news.path}: the write returned no error but id ` +\n `${objectId} is still absent from GET /config/pass_through_endpoint — read back ` +\n 'rather than trusting the call that just returned.',\n ),\n );\n }\n return toAttributes(after, objectId);\n }),\n\n delete: ({\n id,\n instanceId,\n olds,\n output,\n }: {\n id: string;\n instanceId: string;\n olds: PassThroughEndpointProps;\n output: PassThroughEndpointAttributes;\n }) =>\n Effect.gen(function* () {\n const objectId = output.id ?? (yield* physicalIdOf(id, instanceId));\n const rows = yield* listPassThroughEndpoints();\n const live = rows.find((row) => row.id === objectId);\n // ⛔ NEVER DELETE AN `is_from_config` ROW, and a DB row this resource never created (a path\n // match with a different id) is not this resource's to remove either.\n if (live === undefined || live.is_from_config === true || live.path !== olds.path) return;\n yield* deletePassThroughEndpoint(objectId);\n }),\n\n list: () => Effect.succeed([]),\n};\n\nexport const LiteLLMPassThroughEndpointProvider = () =>\n Provider.effect(\n LiteLLMPassThroughEndpoint,\n Effect.succeed(LiteLLMPassThroughEndpoint.Provider.of(passThroughHandlers)),\n );\n",
|
|
6
|
-
"/**\n * The four LiteLLM `/config/pass_through_endpoint*` calls this package writes to and reads from —\n * now through `@distilled.cloud/litellm`'s typed operations (S23), not a hand-rolled `HttpClient`\n * client. `client.ts` is retired; nothing else in this package imported it.\n *\n * ⛔ STILL WRAPPED IN A PER-BASE-URL SEMAPHORE, UNCHANGED FROM `client.ts`. LiteLLM stores every\n * pass-through endpoint in ONE `general_settings.pass_through_endpoints` list\n * (`pass_through_endpoints.py` at tag v1.100.0, mirrored in the SDK's own `misc.ts` docstrings),\n * so create/update/delete are each a read-modify-write of the WHOLE list through\n * `update_config_general_settings`. Two of this package's own calls racing each other — two\n * endpoints created in the same deploy — would each read the list, add their own row and write\n * it back, and the second write would silently drop the first row. The SDK has no opinion on\n * this: it is a quirk of how THIS vendor stores the resource, not a distilled protocol concern,\n * so it stays here, one layer above the typed calls. One permit per proxy serialises this\n * package's own writes; it cannot serialise against the LiteLLM UI or a second deploy process,\n * which is why `pass-through-endpoint.ts`'s `reconcile` reads back after writing rather than\n * trusting the call that just returned (docs/litellm.md).\n * ⛔ NO STATUS SNIFFING (S21). Every failure the SDK's own typed operations declare — `BadRequest`\n * (400), `NotFound` (404 on update only), `UnprocessableEntity` (422) plus the shared\n * `Unauthorized`/`TooManyRequests`/`InternalServerError`/`BadGateway`/`ServiceUnavailable`/\n * `GatewayTimeout` every operation carries — is a distilled patch's doing\n * (`@distilled.cloud/litellm`'s own `patches/misc/*.json`, applied at generation time), never a\n * status check here or in `pass-through-endpoint.ts`. Deleting an id LiteLLM does not have\n * answers `BadRequest` (measured against the tag's `pass_through_endpoint_delete` — the id is\n * looked up in the list and a miss raises `HTTPException(400)`, not 404) — but so does a\n * genuinely-live row when the caller isn't PROXY_ADMIN, or the DB is disconnected. Telling those\n * apart by re-listing, not by the `BadRequest`'s message text, is `pass-through-endpoint.ts`'s\n * `deletePassThroughEndpoint`'s own job (S21 — a status-and-a-real-read decision, never body\n * text) — this file only forwards the typed error.\n */\nimport * as misc from '@distilled.cloud/litellm/misc';\nimport { Credentials } from '@distilled.cloud/litellm/Credentials';\nimport type { LitellmOpContext } from '@distilled.cloud/litellm/Protocol';\nimport type { ConfigError } from '@distilled.cloud/litellm/Errors';\nimport * as Effect from 'effect/Effect';\nimport * as Semaphore from 'effect/Semaphore';\nimport * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';\n\nexport type { LitellmOpContext } from '@distilled.cloud/litellm/Protocol';\nexport type LitellmRequirements = LitellmOpContext;\nexport type {\n PassThroughEndpointResponse,\n PassThroughGenericEndpoint,\n PassThroughGuardrailSettings,\n} from '@distilled.cloud/litellm/misc';\n\nconst semaphores = new Map<string, Semaphore.Semaphore>();\n\n/** One semaphore per base URL, created on first use. Never `Hash.structure` (S18) — a plain Map key. */\nconst semaphoreFor = (baseUrl: string): Effect.Effect<Semaphore.Semaphore> =>\n Effect.sync(() => {\n const existing = semaphores.get(baseUrl);\n if (existing !== undefined) return existing;\n const created = Semaphore.makeUnsafe(1);\n semaphores.set(baseUrl, created);\n return created;\n });\n\n/**\n * Serialises a write against this call's own base URL — see the file header.\n *\n * ⚠️ `Credentials` itself ends in `Effect.orDie` on a missing/misspelled env var (Q6, decision\n * 49 \"upstream wins\", 2026-09-24, matching distilled's own convention — 73 of 80 packages'\n * `credentials.ts` do the same at `homeflare/base`), so resolving it here can no longer fail\n * typed. The `| ConfigError` this function still adds to its return type stays only because\n * `@distilled.cloud/litellm`'s own `LitellmOpError` (protocol.ts) unconditionally declares\n * `ConfigError` as a member of every generated operation's error union regardless of\n * `Credentials`'s own type — so `E` (the wrapped operation's error) already contains it, and\n * this widening is a genuine no-op for callers, not dead type-state left over from the revert.\n */\n/**\n * ⛔ THE STACK'S AMBIENT `HttpClient` MAY BE CADDY'S. `caddyProviders()` provideMerges a client\n * that dials the admin listener and ignores the request host (`caddy-http-client.ts`). Measured\n * 2026-09-25: that client answers `GET /config/pass_through_endpoint` with HTTP 200 `null`\n * (Caddy's empty-config body). The decoded value then has no `endpoints`, and `locate` throws\n * `rows.find` of undefined. FetchHttpClient uses the URL the LiteLLM protocol built. An outer\n * `Fetch` service still wins inside that client, which is how the fake proxy stays the test\n * transport.\n */\
|
|
6
|
+
"/**\n * The four LiteLLM `/config/pass_through_endpoint*` calls this package writes to and reads from —\n * now through `@distilled.cloud/litellm`'s typed operations (S23), not a hand-rolled `HttpClient`\n * client. `client.ts` is retired; nothing else in this package imported it.\n *\n * ⛔ STILL WRAPPED IN A PER-BASE-URL SEMAPHORE, UNCHANGED FROM `client.ts`. LiteLLM stores every\n * pass-through endpoint in ONE `general_settings.pass_through_endpoints` list\n * (`pass_through_endpoints.py` at tag v1.100.0, mirrored in the SDK's own `misc.ts` docstrings),\n * so create/update/delete are each a read-modify-write of the WHOLE list through\n * `update_config_general_settings`. Two of this package's own calls racing each other — two\n * endpoints created in the same deploy — would each read the list, add their own row and write\n * it back, and the second write would silently drop the first row. The SDK has no opinion on\n * this: it is a quirk of how THIS vendor stores the resource, not a distilled protocol concern,\n * so it stays here, one layer above the typed calls. One permit per proxy serialises this\n * package's own writes; it cannot serialise against the LiteLLM UI or a second deploy process,\n * which is why `pass-through-endpoint.ts`'s `reconcile` reads back after writing rather than\n * trusting the call that just returned (docs/litellm.md).\n * ⛔ NO STATUS SNIFFING (S21). Every failure the SDK's own typed operations declare — `BadRequest`\n * (400), `NotFound` (404 on update only), `UnprocessableEntity` (422) plus the shared\n * `Unauthorized`/`TooManyRequests`/`InternalServerError`/`BadGateway`/`ServiceUnavailable`/\n * `GatewayTimeout` every operation carries — is a distilled patch's doing\n * (`@distilled.cloud/litellm`'s own `patches/misc/*.json`, applied at generation time), never a\n * status check here or in `pass-through-endpoint.ts`. Deleting an id LiteLLM does not have\n * answers `BadRequest` (measured against the tag's `pass_through_endpoint_delete` — the id is\n * looked up in the list and a miss raises `HTTPException(400)`, not 404) — but so does a\n * genuinely-live row when the caller isn't PROXY_ADMIN, or the DB is disconnected. Telling those\n * apart by re-listing, not by the `BadRequest`'s message text, is `pass-through-endpoint.ts`'s\n * `deletePassThroughEndpoint`'s own job (S21 — a status-and-a-real-read decision, never body\n * text) — this file only forwards the typed error.\n */\nimport * as misc from '@distilled.cloud/litellm/misc';\nimport { Credentials } from '@distilled.cloud/litellm/Credentials';\nimport type { LitellmOpContext } from '@distilled.cloud/litellm/Protocol';\nimport type { ConfigError } from '@distilled.cloud/litellm/Errors';\nimport * as Effect from 'effect/Effect';\nimport * as Semaphore from 'effect/Semaphore';\nimport * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';\n\nexport type { LitellmOpContext } from '@distilled.cloud/litellm/Protocol';\nexport type LitellmRequirements = LitellmOpContext;\nexport type {\n PassThroughEndpointResponse,\n PassThroughGenericEndpoint,\n PassThroughGuardrailSettings,\n} from '@distilled.cloud/litellm/misc';\n\nconst semaphores = new Map<string, Semaphore.Semaphore>();\n\n/** One semaphore per base URL, created on first use. Never `Hash.structure` (S18) — a plain Map key. */\nconst semaphoreFor = (baseUrl: string): Effect.Effect<Semaphore.Semaphore> =>\n Effect.sync(() => {\n const existing = semaphores.get(baseUrl);\n if (existing !== undefined) return existing;\n const created = Semaphore.makeUnsafe(1);\n semaphores.set(baseUrl, created);\n return created;\n });\n\n/**\n * Serialises a write against this call's own base URL — see the file header.\n *\n * ⚠️ `Credentials` itself ends in `Effect.orDie` on a missing/misspelled env var (Q6, decision\n * 49 \"upstream wins\", 2026-09-24, matching distilled's own convention — 73 of 80 packages'\n * `credentials.ts` do the same at `homeflare/base`), so resolving it here can no longer fail\n * typed. The `| ConfigError` this function still adds to its return type stays only because\n * `@distilled.cloud/litellm`'s own `LitellmOpError` (protocol.ts) unconditionally declares\n * `ConfigError` as a member of every generated operation's error union regardless of\n * `Credentials`'s own type — so `E` (the wrapped operation's error) already contains it, and\n * this widening is a genuine no-op for callers, not dead type-state left over from the revert.\n */\n/**\n * ⛔ THE STACK'S AMBIENT `HttpClient` MAY BE CADDY'S. `caddyProviders()` provideMerges a client\n * that dials the admin listener and ignores the request host (`caddy-http-client.ts`). Measured\n * 2026-09-25: that client answers `GET /config/pass_through_endpoint` with HTTP 200 `null`\n * (Caddy's empty-config body). The decoded value then has no `endpoints`, and `locate` throws\n * `rows.find` of undefined. FetchHttpClient uses the URL the LiteLLM protocol built. An outer\n * `Fetch` service still wins inside that client, which is how the fake proxy stays the test\n * transport.\n */\nexport const throughFetch = <A, E, R>(io: Effect.Effect<A, E, R>) =>\n io.pipe(Effect.provide(FetchHttpClient.layer));\n\nconst mutate = <A, E>(\n io: Effect.Effect<A, E, LitellmOpContext>,\n): Effect.Effect<A, E | ConfigError, LitellmOpContext> =>\n Effect.gen(function* () {\n const resolve = yield* Credentials;\n const creds = yield* resolve;\n const semaphore = yield* semaphoreFor(creds.apiBaseUrl);\n return yield* semaphore.withPermits(1)(throughFetch(io));\n });\n\nconst endpointsOf = (\n response: misc.PassThroughEndpointResponse,\n): Effect.Effect<readonly misc.PassThroughGenericEndpoint[]> => {\n const rows = response.endpoints;\n if (!Array.isArray(rows)) {\n return Effect.die(\n new Error(\n 'GET /config/pass_through_endpoint decoded no endpoints array. The call reached something other than the LiteLLM proxy.',\n ),\n );\n }\n return Effect.succeed(rows);\n};\n\n/** GET /config/pass_through_endpoint — every row, config-file and DB alike. Not under the semaphore: reads don't race the field. */\nexport const listPassThroughEndpoints = (): Effect.Effect<\n readonly misc.PassThroughGenericEndpoint[],\n misc.GetPassThroughEndpointsConfigPassThroughEndpointGetError,\n LitellmOpContext\n> =>\n throughFetch(misc.getPassThroughEndpointsConfigPassThroughEndpointGet({})).pipe(\n Effect.flatMap(endpointsOf),\n );\n\n/** POST /config/pass_through_endpoint. `body` carries `id` — see pass-through-form.ts. */\nexport const createPassThroughEndpoint = (\n body: misc.PassThroughGenericEndpoint,\n): Effect.Effect<\n void,\n misc.CreatePassThroughEndpointsConfigPassThroughEndpointPostError,\n LitellmOpContext\n> => mutate(misc.createPassThroughEndpointsConfigPassThroughEndpointPost(body).pipe(Effect.asVoid));\n\n/**\n * POST /config/pass_through_endpoint/{endpoint_id}. Merges with `exclude_none` — see\n * pass-through-form.ts. `body` is `pass-through-form.ts`'s own `UpdateBody`, not a blanket\n * `Partial<>` — `path`/`target` are the two fields the SDK's request type still requires\n * non-optionally on an update.\n */\nexport const updatePassThroughEndpoint = (\n endpointId: string,\n body: Partial<Omit<misc.PassThroughGenericEndpoint, 'path' | 'target'>> &\n Pick<misc.PassThroughGenericEndpoint, 'path' | 'target'>,\n): Effect.Effect<\n void,\n misc.UpdatePassThroughEndpointsConfigPassThroughEndpointEndpointIdPostError,\n LitellmOpContext\n> =>\n mutate(\n misc\n .updatePassThroughEndpointsConfigPassThroughEndpointEndpointIdPost({\n ...body,\n endpoint_id: endpointId,\n })\n .pipe(Effect.asVoid),\n );\n\n/**\n * DELETE /config/pass_through_endpoint?endpoint_id=… — idempotent BY THIS FUNCTION, not by the\n * vendor. ⚠️ Measured at v1.100.0: deleting an id the list does not have raises `HTTPException(400)`\n * from `pass_through_endpoint_delete`, which the SDK decodes as a typed `BadRequest` — but the SAME\n * route also raises the SAME `BadRequest` for `not_allowed_access` (a non-PROXY_ADMIN caller) or a\n * disconnected DB, on a row that never got deleted. A `BadRequest` alone cannot tell the two apart,\n * so THIS FUNCTION DOESN'T GUESS FROM THE TAG ALONE: on `BadRequest` it re-lists (a GET — no\n * PROXY_ADMIN required) and only treats the delete as already-done if `endpointId` is genuinely\n * absent; otherwise the delete failed and this re-fails with the ORIGINAL error, not a message\n * match (S21 — a status-and-a-real-read decision, never body text).\n */\nexport const deletePassThroughEndpoint = (\n endpointId: string,\n): Effect.Effect<\n void,\n misc.DeletePassThroughEndpointsConfigPassThroughEndpointDeleteError,\n LitellmOpContext\n> =>\n mutate(\n misc\n .deletePassThroughEndpointsConfigPassThroughEndpointDelete({ endpoint_id: endpointId })\n .pipe(\n Effect.asVoid,\n Effect.catchTag('BadRequest', (original) =>\n Effect.gen(function* () {\n const rows = yield* listPassThroughEndpoints();\n if (rows.some((row) => row.id === endpointId)) return yield* Effect.fail(original);\n }),\n ),\n ),\n );\n",
|
|
7
7
|
"/**\n * The typed refusals `LiteLLM.PassThroughEndpoint` raises on top of `@distilled.cloud/litellm`'s\n * own SDK errors — split out of pass-through-endpoint.ts to keep that file under the house's\n * 250-line cap once this migration added the four named SDK error-union re-exports.\n */\nimport type * as misc from '@distilled.cloud/litellm/misc';\nimport * as Data from 'effect/Data';\n\nexport class LitellmConfigPathConflictError extends Data.TaggedError(\n 'LitellmConfigPathConflictError',\n)<{\n readonly path: string;\n}> {}\nexport class LitellmUnaddressableRowError extends Data.TaggedError('LitellmUnaddressableRowError')<{\n readonly path: string;\n}> {}\nexport class LitellmLiteralSecretHeaderError extends Data.TaggedError(\n 'LitellmLiteralSecretHeaderError',\n)<{\n readonly headers: readonly string[];\n}> {}\n\n/**\n * ⛔ THE FOUR SDK ERROR UNIONS, NAMED, NOT `LitellmOpError` ALONE. `catchTag`'s tag-literal\n * inference needs a concrete union to resolve `\"BadRequest\"`/`\"NotFound\"` against (the same\n * reason `netbox/resource.ts` leaves `NotFound` folding to each resource file, not its shared\n * engine) — `operations.ts` already returns each call's own precise per-operation error type, so\n * this is just their union, not a re-widening.\n */\nexport type PassThroughEndpointError =\n | misc.GetPassThroughEndpointsConfigPassThroughEndpointGetError\n | misc.CreatePassThroughEndpointsConfigPassThroughEndpointPostError\n | misc.UpdatePassThroughEndpointsConfigPassThroughEndpointEndpointIdPostError\n | misc.DeletePassThroughEndpointsConfigPassThroughEndpointDeleteError\n | LitellmConfigPathConflictError\n | LitellmUnaddressableRowError\n | LitellmLiteralSecretHeaderError;\n",
|
|
8
8
|
"/**\n * The wire body for a `LiteLLM.PassThroughEndpoint`, the comparison that decides whether it needs\n * writing, and the refusals that stop a plan before it writes anything unsafe.\n *\n * ★ EXTRACTED SO IT CAN BE TESTED WITHOUT A SERVER — the same reason netbox keeps `prefix-form.ts`\n * beside `prefix.ts` (S32/testing-and-docs.md).\n *\n * ⛔ PROPS ARE `@distilled.cloud/litellm`'s OWN GENERATED TYPE, MINUS THE TWO FIELDS THIS RESOURCE\n * OWNS ITSELF: `id` (a deterministic physical name — pass-through-endpoint.ts) and\n * `is_from_config` (response-only; the vendor sets it, a declaration never does). Deriving\n * `Props` with `Omit<>` rather than hand-typing it means a vendor schema change the SDK's own\n * next regeneration picks up is felt here as a compiler error, not silently ignored — the same\n * reason this used to derive from the kit's own hand-generated `./generated/pass-through.ts`\n * (retired by this migration: the SDK's type is the same shape, generated from the same LiteLLM\n * 1.100.0 OpenAPI document, and keeping a second copy would only drift).\n */\nimport { deepEqual } from 'alchemy/Diff';\nimport type * as Generated from '@distilled.cloud/litellm/misc';\n\nexport type PassThroughEndpointProps = Omit<\n Generated.PassThroughGenericEndpoint,\n 'id' | 'is_from_config'\n>;\n\n/** What `read` returns: the props plus the two vendor-owned fields, `id` now guaranteed present. */\nexport type PassThroughEndpointAttributes = Omit<Generated.PassThroughGenericEndpoint, 'id'> & {\n readonly id: string;\n};\n\n/** ⚠️ NetBox `settled()`'s pattern: only fields the SCHEMA gives a vendor default for, copied from it (not assumed). */\nexport const settled = (props: PassThroughEndpointProps) => ({\n auth: props.auth ?? true,\n cost_per_request: props.cost_per_request ?? 0,\n default_query_params: props.default_query_params ?? {},\n headers: props.headers ?? {},\n include_subpath: props.include_subpath ?? false,\n});\n\n/** The three fields whose absence the vendor's `exclude_none` update can never write (see client.ts). */\nexport const NULLABLE_FIELDS = ['timeout', 'methods', 'guardrails'] as const;\n\nconst isUnset = (value: unknown): boolean => value === undefined || value === null;\n\n/**\n * ⚠️ BUILT AS A WIDER RECORD AND PRUNED, NOT AS THE GENERATED TYPE DIRECTLY.\n * `exactOptionalPropertyTypes` is on (packages/config/tsconfig.base.json), so writing\n * `timeout: undefined` into a `{ timeout?: number | null }` is a type error rather than an\n * absent key — the same reason `codegen/openapi.ts`'s `prune` exists, and the same fix.\n */\nconst prune = (value: Record<string, unknown>): Record<string, unknown> =>\n Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));\n\n/**\n * Full body for CREATE. `id` is the deterministic physical name (pass-through-endpoint.ts), sent\n * so a failed create is recoverable by `read` rather than orphaning a second row on retry.\n */\nexport const createBody = (\n props: PassThroughEndpointProps,\n id: string,\n): Generated.PassThroughGenericEndpoint =>\n prune({\n ...settled(props),\n guardrails: isUnset(props.guardrails) ? undefined : props.guardrails,\n id,\n methods: isUnset(props.methods) ? undefined : props.methods,\n path: props.path,\n target: props.target,\n timeout: isUnset(props.timeout) ? undefined : props.timeout,\n }) as unknown as Generated.PassThroughGenericEndpoint;\n\n/**\n * Partial body for UPDATE (`POST /{id}`). ⛔ ONLY FIELDS THIS DECLARATION ACTUALLY SETS — the\n * vendor merges the parsed body with `model_dump(exclude_none=True)` (measured at v1.100.0), so an\n * `undefined` OR an explicit `null` here would be dropped by the server anyway; sending neither is\n * the same outcome with no request made when nothing changed (`Object.keys(body).length === 0`\n * checked by the caller in pass-through-endpoint.ts).\n *\n * ⛔ `path`/`target` ARE NOT `Partial` HERE, UNLIKE EVERY OTHER FIELD. The SDK's own update\n * request type requires both non-optionally (the vendor's PATCH route still wants the full\n * route/target pair on every call, unlike the other fields' `exclude_none` merge) — typing them\n * as always-present here is what lets `operations.ts`'s `updatePassThroughEndpoint` pass this\n * straight to the SDK without an unsafe cast.\n */\nexport type UpdateBody = Partial<Omit<Generated.PassThroughGenericEndpoint, 'path' | 'target'>> &\n Pick<Generated.PassThroughGenericEndpoint, 'path' | 'target'>;\n\nexport const updateBody = (props: PassThroughEndpointProps): UpdateBody =>\n prune({\n ...settled(props),\n guardrails: isUnset(props.guardrails) ? undefined : props.guardrails,\n methods: isUnset(props.methods) ? undefined : props.methods,\n path: props.path,\n target: props.target,\n timeout: isUnset(props.timeout) ? undefined : props.timeout,\n }) as UpdateBody;\n\n/**\n * Whether ANY of the three nullable fields went from set (in `live`) to unset (in `props`) — the\n * one case `updateBody` cannot express, because the vendor's `exclude_none` merge silently keeps\n * the old value instead of clearing it. Detected here so `pass-through-endpoint.ts` can plan a\n * `replace` (delete-then-create) instead of an update that would look like it worked.\n */\nexport const needsReplace = (\n live: PassThroughEndpointAttributes,\n props: PassThroughEndpointProps,\n): boolean => NULLABLE_FIELDS.some((field) => !isUnset(live[field]) && isUnset(props[field]));\n\n/** Whether the live object already says what the declaration says — the `diff` \"noop\" test. */\nexport const matches = (\n live: PassThroughEndpointAttributes,\n props: PassThroughEndpointProps,\n): boolean => {\n const fixed = settled(props);\n return (\n live.target === props.target &&\n live.auth === fixed.auth &&\n live.cost_per_request === fixed.cost_per_request &&\n live.include_subpath === fixed.include_subpath &&\n deepEqual(live.headers, fixed.headers) &&\n deepEqual(live.default_query_params, fixed.default_query_params) &&\n (isUnset(props.timeout) ? isUnset(live.timeout) : live.timeout === props.timeout) &&\n (isUnset(props.methods) ? isUnset(live.methods) : deepEqual(live.methods, props.methods)) &&\n (isUnset(props.guardrails)\n ? isUnset(live.guardrails)\n : deepEqual(live.guardrails, props.guardrails))\n );\n};\n\n/**\n * A literal secret in a forwarded header (S25, house rule — not a vendor one; see\n * pass-through-endpoint.ts). Case-insensitive on the header name; the reference form is LiteLLM's\n * own, `os.environ/NAME` (`set_env_variables_in_header`, pass_through_endpoints.py:117-160 at the\n * tag) — a missing variable on the proxy host leaves that literal string in the header, which\n * nothing in the API can detect, so this is enforced here rather than trusted to the vendor.\n */\nconst SECRET_HEADER_NAMES = new Set(['authorization', 'x-api-key', 'cf-aig-authorization']);\n\nexport const literalSecretHeaders = (\n headers: Readonly<Record<string, unknown>> | undefined,\n): readonly string[] =>\n Object.entries(headers ?? {})\n .filter(\n ([name, value]) =>\n SECRET_HEADER_NAMES.has(name.toLowerCase()) && !String(value).includes('os.environ/'),\n )\n .map(([name]) => name);\n",
|
|
9
|
-
"/**\n *
|
|
9
|
+
"/**\n * `LiteLLM.Budget` — one row of LiteLLM's `LiteLLM_BudgetTable` (`/budget/*`), the \"tier\" keys and\n * teams bind to (`hf-tier-*`).\n *\n * ★ MONITORING, NOT LIMITS (Tim, 2026-09-28): `softBudget` is the prop; `maxBudget` is a hard cap\n * that 429s every caller and is refused without `maxBudgetReason` (budget-form.ts).\n * ★ ADOPT BY id. `budgetId` names the live row; without it a deterministic physical name is used.\n * A foreign row with that id is `Unowned`, so it needs `--adopt`.\n * ★ `defaultRemovalPolicy: 'retain'` — deleting a budget row that keys still reference either\n * fails or detaches them, and a detached key loses its rate limits. Opt in with\n * `.pipe(RemovalPolicy.destroy())`.\n * ⛔ CLEARING A LIMIT: sent as an explicit `null`. If this LiteLLM merges with `exclude_none` the\n * row keeps the old value; reconcile reads back and FAILS (`LitellmBudgetFieldNotClearedError`)\n * rather than report a cleared cap. Unmeasured against a live proxy at 1.103.0.\n * ⛔ NO CREDENTIAL IS A PROP; `litellmProviders`' layer supplies `Credentials`.\n */\nimport { Resource } from 'alchemy';\nimport { Unowned } from 'alchemy/AdoptPolicy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport { createPhysicalName } from 'alchemy/PhysicalName';\nimport * as Provider from 'alchemy/Provider';\nimport * as Effect from 'effect/Effect';\nimport * as Predicate from 'effect/Predicate';\nimport {\n LitellmBudgetFieldNotClearedError,\n LitellmBudgetHardCapUnjustifiedError,\n} from './budget-errors.ts';\nimport {\n type BudgetAttributes,\n type BudgetProps,\n createBody,\n differing,\n isHardCapUnjustified,\n updateBody,\n} from './budget-form.ts';\nimport { createBudget, deleteBudget, listBudgets, updateBudget } from './budget-operations.ts';\n\nexport type { BudgetAttributes, BudgetProps };\nexport type { BudgetError } from './budget-errors.ts';\n\nexport interface LiteLLMBudget extends Resource<'LiteLLM.Budget', BudgetProps, BudgetAttributes> {}\n\nexport const LiteLLMBudget = Resource<LiteLLMBudget>('LiteLLM.Budget', {\n defaultRemovalPolicy: 'retain',\n});\n\nexport const isLiteLLMBudget = (value: unknown): value is LiteLLMBudget =>\n Predicate.hasProperty(value, 'Type') && value.Type === 'LiteLLM.Budget';\n\nconst refuseUnjustifiedCap = (props: BudgetProps) =>\n isHardCapUnjustified(props)\n ? Effect.fail(new LitellmBudgetHardCapUnjustifiedError({ budgetId: props.budgetId }))\n : Effect.void;\n\nconst idOf = (\n id: string,\n instanceId: string,\n props: BudgetProps,\n output: BudgetAttributes | undefined,\n) =>\n output?.budgetId !== undefined\n ? Effect.succeed(output.budgetId)\n : props.budgetId !== undefined\n ? Effect.succeed(props.budgetId)\n : createPhysicalName({ id, instanceId, lowercase: true, maxLength: 64 });\n\ntype Args<P> = {\n id: string;\n instanceId: string;\n output: BudgetAttributes | undefined;\n} & P;\n\n/** ⛔ TEST-ONLY EXPORT. Not on index.ts's barrel. */\nexport const budgetHandlers = {\n read: ({ id, instanceId, olds, output }: Args<{ olds: BudgetProps }>) =>\n Effect.gen(function* () {\n const budgetId = yield* idOf(id, instanceId, olds, output);\n const live = (yield* listBudgets()).find((row) => row.budgetId === budgetId);\n if (live === undefined) return undefined;\n return output === undefined ? Unowned(live) : live;\n }),\n\n diff: ({ news, output }: { news: Input<BudgetProps>; output: BudgetAttributes | undefined }) =>\n Effect.gen(function* () {\n if (!isResolved(news)) return undefined;\n yield* refuseUnjustifiedCap(news);\n if (output === undefined) return undefined;\n // The id is identity: a different declared id is a different row.\n if (news.budgetId !== undefined && news.budgetId !== output.budgetId) {\n return { action: 'replace', deleteFirst: false } as const;\n }\n return differing(output, news).length === 0\n ? ({ action: 'noop' } as const)\n : ({ action: 'update' } as const);\n }),\n\n reconcile: ({ id, instanceId, news, output }: Args<{ news: BudgetProps }>) =>\n Effect.gen(function* () {\n yield* refuseUnjustifiedCap(news);\n const budgetId = yield* idOf(id, instanceId, news, output);\n const before = (yield* listBudgets()).find((row) => row.budgetId === budgetId);\n if (before === undefined) yield* createBudget(createBody(news, budgetId));\n else if (differing(before, news).length > 0) yield* updateBudget(updateBody(news, before));\n\n const after = (yield* listBudgets()).find((row) => row.budgetId === budgetId);\n if (after === undefined) {\n return yield* Effect.die(\n new Error(`LiteLLM.Budget ${budgetId}: write returned no error but the row is absent.`),\n );\n }\n const left = differing(after, news);\n if (left.length > 0) {\n return yield* Effect.fail(\n new LitellmBudgetFieldNotClearedError({ budgetId, fields: left }),\n );\n }\n return after;\n }),\n\n delete: ({ output }: { output: BudgetAttributes }) => deleteBudget(output.budgetId),\n\n list: () => Effect.succeed([]),\n};\n\nexport const LiteLLMBudgetProvider = () =>\n Provider.effect(LiteLLMBudget, Effect.succeed(LiteLLMBudget.Provider.of(budgetHandlers)));\n",
|
|
10
|
+
"/**\n * The typed refusals `LiteLLM.Budget` raises on top of `@distilled.cloud/litellm`'s own SDK errors.\n */\nimport type * as budgets from '@distilled.cloud/litellm/budget_management';\nimport * as Data from 'effect/Data';\n\n/** A `maxBudget` (a HARD cap: LiteLLM answers 429 once it is spent) declared with no reason. */\nexport class LitellmBudgetHardCapUnjustifiedError extends Data.TaggedError(\n 'LitellmBudgetHardCapUnjustifiedError',\n)<{\n readonly budgetId: string | undefined;\n}> {}\n\n/**\n * The write returned no error but the row still carries a limit the declaration does not.\n * `/budget/update` may merge with `exclude_none` (the way `/config/pass_through_endpoint` does),\n * in which case a limit can never be cleared by an update. Refused, never claimed as done.\n */\nexport class LitellmBudgetFieldNotClearedError extends Data.TaggedError(\n 'LitellmBudgetFieldNotClearedError',\n)<{\n readonly budgetId: string;\n readonly fields: readonly string[];\n}> {}\n\n/** `/budget/info` answered something that is not a list of budget rows. */\nexport class LitellmBudgetUnreadableError extends Data.TaggedError('LitellmBudgetUnreadableError')<{\n readonly budgetId: string;\n}> {}\n\nexport type BudgetError =\n | budgets.PostInfoBudgetBudgetInfoError\n | budgets.PostNewBudgetBudgetNewError\n | budgets.UpdateBudgetBudgetUpdatePostError\n | budgets.DeleteBudgetBudgetDeletePostError\n | LitellmBudgetHardCapUnjustifiedError\n | LitellmBudgetFieldNotClearedError\n | LitellmBudgetUnreadableError;\n",
|
|
11
|
+
"/**\n * Props, wire bodies and comparison for `LiteLLM.Budget`, testable without a server.\n *\n * ⛔ NO SILENT `max_budget`. LiteLLM's own field doc: \"Requests will fail if this budget (in USD)\n * is exceeded\" — a hard cap. On 2026-09-28 an undeclared $60/day `max_budget` on\n * `hf-tier-interactive` 429'd the whole claude2 fleet. So `maxBudget` is never defaulted and is\n * refused unless `maxBudgetReason` says why; `softBudget` is the monitoring field (it \"will NOT\n * fail if this is exceeded\", it fires the alert).\n * ⚠️ NOT MODELLED: `max_parallel_requests`, `model_max_budget`, `budget_reset_at`. The row read\n * back (`BudgetListItem`, generated at LiteLLM 1.100.0) has none of them, so a declaration of\n * them could never be diffed. Add them when a read that returns them is measured.\n */\nimport type * as budgets from '@distilled.cloud/litellm/budget_management';\n\nexport interface BudgetProps {\n /** Live budget id. Set it to adopt an existing row (`hf-tier-agent`); default is a deterministic physical name. */\n readonly budgetId?: string;\n /** Alert threshold in USD. Requests keep flowing when exceeded. The monitoring field. */\n readonly softBudget?: number;\n /** HARD cap in USD: requests fail (429) once spent. Refused unless `maxBudgetReason` is set. */\n readonly maxBudget?: number;\n /** Why a hard cap is wanted. Required with `maxBudget`; recorded in the declaration only. */\n readonly maxBudgetReason?: string;\n /** Reset period, e.g. `'1d'`, `'28d'`. */\n readonly budgetDuration?: string;\n /** Requests per minute. */\n readonly rpmLimit?: number;\n /** Tokens per minute. */\n readonly tpmLimit?: number;\n}\n\nexport interface BudgetAttributes {\n readonly budgetId: string;\n readonly softBudget: number | null;\n readonly maxBudget: number | null;\n readonly budgetDuration: string | null;\n readonly rpmLimit: number | null;\n readonly tpmLimit: number | null;\n}\n\n/** Declared field → wire field. The single list every comparison and body walks. */\nconst FIELDS = [\n ['softBudget', 'soft_budget'],\n ['maxBudget', 'max_budget'],\n ['budgetDuration', 'budget_duration'],\n ['rpmLimit', 'rpm_limit'],\n ['tpmLimit', 'tpm_limit'],\n] as const;\n\nexport const isHardCapUnjustified = (props: BudgetProps): boolean =>\n props.maxBudget !== undefined && (props.maxBudgetReason ?? '').trim() === '';\n\n/** Create body: only what is declared. */\nexport const createBody = (\n props: BudgetProps,\n budgetId: string,\n): budgets.PostNewBudgetBudgetNewRequest => {\n const body: Record<string, unknown> = { budget_id: budgetId };\n for (const [prop, wire] of FIELDS) if (props[prop] !== undefined) body[wire] = props[prop];\n return body as budgets.PostNewBudgetBudgetNewRequest;\n};\n\n/** Update body: declared fields, plus an explicit `null` for a live limit the declaration dropped. */\nexport const updateBody = (\n props: BudgetProps,\n live: BudgetAttributes,\n): budgets.UpdateBudgetBudgetUpdatePostRequest => {\n const body: Record<string, unknown> = { budget_id: live.budgetId };\n for (const [prop, wire] of FIELDS) {\n if (props[prop] !== undefined) body[wire] = props[prop];\n else if (live[prop] !== null) body[wire] = null;\n }\n return body as budgets.UpdateBudgetBudgetUpdatePostRequest;\n};\n\n/** Wire names of the fields where live and declared disagree. */\nexport const differing = (live: BudgetAttributes, props: BudgetProps): readonly string[] =>\n FIELDS.filter(([prop]) => live[prop] !== (props[prop] ?? null)).map(([, wire]) => wire);\n\nconst numberOrNull = (value: unknown): number | null =>\n value === null || value === undefined || value === '' ? null : Number(value);\n\n/**\n * One row of `/budget/info`. `tpm_limit`/`rpm_limit` are `BigInt?` in LiteLLM's schema and come\n * back as decimal strings (the generated `BudgetListItem` says so), hence `Number()`.\n */\nexport const toAttributes = (row: Record<string, unknown>): BudgetAttributes => ({\n budgetDuration: typeof row['budget_duration'] === 'string' ? row['budget_duration'] : null,\n budgetId: String(row['budget_id']),\n maxBudget: numberOrNull(row['max_budget']),\n rpmLimit: numberOrNull(row['rpm_limit']),\n softBudget: numberOrNull(row['soft_budget']),\n tpmLimit: numberOrNull(row['tpm_limit']),\n});\n",
|
|
12
|
+
"/**\n * The four LiteLLM `/budget/*` calls `LiteLLM.Budget` uses, through the SDK's typed operations.\n *\n * ★ READS GO THROUGH `/budget/list` (the whole table, a bare array), not `/budget/info`. Both\n * answer `body: unknown` in the generated schema, and what `/budget/info` does for an id that is\n * not there is unmeasured; a list answers absence unambiguously.\n * ⚠️ Unlike pass-through endpoints, each budget is its own DB row, so no semaphore.\n * ⚠️ DELETE is idempotent BY THIS FUNCTION: a `BadRequest` re-lists and is swallowed only when the\n * id is genuinely absent, never on message text (pass-through `operations.ts`' rule).\n */\nimport * as budgets from '@distilled.cloud/litellm/budget_management';\nimport * as Effect from 'effect/Effect';\nimport { type LitellmOpContext, throughFetch } from './operations.ts';\nimport { type BudgetAttributes, toAttributes } from './budget-form.ts';\nimport { LitellmBudgetUnreadableError } from './budget-errors.ts';\n\nexport const listBudgets = (): Effect.Effect<\n readonly BudgetAttributes[],\n budgets.ListBudgetBudgetListGetError | LitellmBudgetUnreadableError,\n LitellmOpContext\n> =>\n throughFetch(budgets.listBudgetBudgetListGet({})).pipe(\n // ⚠️ MEASURED with the fake: the SDK types this `{ body: unknown }` but hands a bare JSON array\n // back as the array itself, so accept both shapes and refuse anything else.\n Effect.flatMap((response) => {\n const rows: unknown = Array.isArray(response)\n ? response\n : (response as { body?: unknown }).body;\n return Array.isArray(rows)\n ? Effect.succeed(rows.map((row) => toAttributes(row as Record<string, unknown>)))\n : Effect.fail(new LitellmBudgetUnreadableError({ budgetId: '*' }));\n }),\n );\n\nexport const createBudget = (body: budgets.PostNewBudgetBudgetNewRequest) =>\n throughFetch(budgets.postNewBudgetBudgetNew(body)).pipe(Effect.asVoid);\n\nexport const updateBudget = (body: budgets.UpdateBudgetBudgetUpdatePostRequest) =>\n throughFetch(budgets.updateBudgetBudgetUpdatePost(body)).pipe(Effect.asVoid);\n\nexport const deleteBudget = (id: string) =>\n throughFetch(budgets.deleteBudgetBudgetDeletePost({ id })).pipe(\n Effect.asVoid,\n Effect.catchTag('BadRequest', (original) =>\n listBudgets().pipe(\n Effect.flatMap((rows) =>\n rows.some((row) => row.budgetId === id) ? Effect.fail(original) : Effect.void,\n ),\n ),\n ),\n );\n",
|
|
13
|
+
"/**\n * The LiteLLM provider, with its credential source, as one layer for a stack.\n *\n * Layer.mergeAll(litellmProviders(), …the stack's other providers)\n *\n * ★ THE DEFAULT READS `LITELLM_PROXY_URL`/`LITELLM_PROXY_API_KEY` AT CALL TIME, through\n * `@distilled.cloud/litellm`'s own `CredentialsFromEnv` — the same two variable names the\n * retired hand-rolled `credentials.ts` read — never at layer build. Pass another\n * `Layer.Layer<Credentials>` (see the SDK's own `credentials({ apiKey, baseUrl })` in a test) to\n * source them another way.\n * ⚠️ `HttpClient` is NOT provided here. It is ambient in every Alchemy runtime the same way it is\n * for `netboxProviders`-style families in this kit; a stack that already deploys anything over\n * HTTP already has one. Do not add a second client to this layer.\n * ⚠️ `Layer.provideMerge`, NEVER PLAIN `Layer.provide`, for the credentials layer. The handlers\n * still need `Credentials` when the engine calls `read`/`diff`/`reconcile`. `Provider.effect`\n * puts that need on the layer. Plain `provide` satisfies it only while the `Provider` value is\n * built, then hides `Credentials` from those later calls. Measured on Caddy, 2026-09-23:\n * `caddy/providers.ts`. `provideMerge` feeds credentials in and keeps them in the layer the\n * engine runs the handlers with.\n */\nimport { type Credentials, CredentialsFromEnv } from '@distilled.cloud/litellm/Credentials';\nimport * as Layer from 'effect/Layer';\nimport { LiteLLMBudgetProvider } from './budget.ts';\nimport { LiteLLMPassThroughEndpointProvider } from './pass-through-endpoint.ts';\n\nexport const litellmProviders = (creds: Layer.Layer<Credentials> = CredentialsFromEnv) =>\n Layer.mergeAll(LiteLLMPassThroughEndpointProvider(), LiteLLMBudgetProvider()).pipe(\n Layer.provideMerge(creds),\n );\n"
|
|
10
14
|
],
|
|
11
|
-
"mappings": ";AA2BA;AACA;AACA;AAEA;AACA;AAEA;AACA;;;ACLA;AACA;AAGA;AACA;AACA;AAUA,IAAM,aAAa,IAAI;AAGvB,IAAM,eAAe,CAAC,YACb,YAAK,MAAM;AAAA,EAChB,MAAM,WAAW,WAAW,IAAI,OAAO;AAAA,EACvC,IAAI,aAAa;AAAA,IAAW,OAAO;AAAA,EACnC,MAAM,UAAoB,qBAAW,CAAC;AAAA,EACtC,WAAW,IAAI,SAAS,OAAO;AAAA,EAC/B,OAAO;AAAA,CACR;
|
|
12
|
-
"debugId": "
|
|
15
|
+
"mappings": "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",
|
|
16
|
+
"debugId": "0E62C0ABB834992A64756E2164756E21",
|
|
13
17
|
"names": []
|
|
14
18
|
}
|