@homeflare/alchemy 0.28.0 → 0.29.1

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.
Files changed (221) hide show
  1. package/README.md +2 -0
  2. package/dist/caddy/admin-address.d.ts +27 -0
  3. package/dist/caddy/admin-address.d.ts.map +1 -0
  4. package/dist/caddy/admin-calls.d.ts +25 -28
  5. package/dist/caddy/admin-calls.d.ts.map +1 -1
  6. package/dist/caddy/admin.d.ts +28 -38
  7. package/dist/caddy/admin.d.ts.map +1 -1
  8. package/dist/caddy/caddy-http-client.d.ts +25 -0
  9. package/dist/caddy/caddy-http-client.d.ts.map +1 -0
  10. package/dist/caddy/config-errors.d.ts +24 -0
  11. package/dist/caddy/config-errors.d.ts.map +1 -0
  12. package/dist/caddy/config-lifecycle.d.ts +20 -49
  13. package/dist/caddy/config-lifecycle.d.ts.map +1 -1
  14. package/dist/caddy/config-reconcile.d.ts +45 -0
  15. package/dist/caddy/config-reconcile.d.ts.map +1 -0
  16. package/dist/caddy/config.d.ts +4 -3
  17. package/dist/caddy/config.d.ts.map +1 -1
  18. package/dist/caddy/fake-caddy.d.ts +10 -2
  19. package/dist/caddy/fake-caddy.d.ts.map +1 -1
  20. package/dist/caddy/index.d.ts +3 -3
  21. package/dist/caddy/index.d.ts.map +1 -1
  22. package/dist/caddy/index.js +487 -444
  23. package/dist/caddy/index.js.map +16 -12
  24. package/dist/caddy/local-admin.d.ts +20 -34
  25. package/dist/caddy/local-admin.d.ts.map +1 -1
  26. package/dist/caddy/providers.d.ts +19 -3
  27. package/dist/caddy/providers.d.ts.map +1 -1
  28. package/dist/cloudflare/mesh-node-token.d.ts +1 -1
  29. package/dist/cloudflare/mesh-node-token.d.ts.map +1 -1
  30. package/dist/forgejo/branch-protection.d.ts +4 -4
  31. package/dist/forgejo/org-actions-secrets.d.ts +4 -4
  32. package/dist/forgejo/org-label.d.ts +4 -4
  33. package/dist/forgejo/org-team.d.ts +4 -4
  34. package/dist/forgejo/repo-webhook.d.ts +4 -4
  35. package/dist/forgejo/repository.d.ts +4 -4
  36. package/dist/forgejo/team-member.d.ts +4 -4
  37. package/dist/github/declare-repo-baseline.d.ts +1 -1
  38. package/dist/github/declare-repo-baseline.d.ts.map +1 -1
  39. package/dist/github/index.d.ts +8 -4
  40. package/dist/github/index.d.ts.map +1 -1
  41. package/dist/github/index.js +83 -24
  42. package/dist/github/index.js.map +14 -12
  43. package/dist/github/repo-apply-order-fake-providers.d.ts +48 -0
  44. package/dist/github/repo-apply-order-fake-providers.d.ts.map +1 -0
  45. package/dist/github/repo-apply-order-fake-stack.d.ts +13 -0
  46. package/dist/github/repo-apply-order-fake-stack.d.ts.map +1 -0
  47. package/dist/github/repo-auto-merge-gate.d.ts +54 -0
  48. package/dist/github/repo-auto-merge-gate.d.ts.map +1 -0
  49. package/dist/github/repo-baseline-data.d.ts +10 -1
  50. package/dist/github/repo-baseline-data.d.ts.map +1 -1
  51. package/dist/github/repo-policy.d.ts +1 -1
  52. package/dist/github/repo-policy.d.ts.map +1 -1
  53. package/dist/github/repository-ruleset-errors.d.ts +3 -2
  54. package/dist/github/repository-ruleset-errors.d.ts.map +1 -1
  55. package/dist/github/repository-ruleset-form.d.ts.map +1 -1
  56. package/dist/github/repository-ruleset-guards.d.ts +3 -2
  57. package/dist/github/repository-ruleset-guards.d.ts.map +1 -1
  58. package/dist/github/repository-ruleset-octokit.d.ts +49 -0
  59. package/dist/github/repository-ruleset-octokit.d.ts.map +1 -1
  60. package/dist/github/repository-ruleset-probe.d.ts +2 -1
  61. package/dist/github/repository-ruleset-probe.d.ts.map +1 -1
  62. package/dist/github/repository-ruleset-props.d.ts +7 -4
  63. package/dist/github/repository-ruleset-props.d.ts.map +1 -1
  64. package/dist/github/repository-ruleset-providers.d.ts +42 -0
  65. package/dist/github/repository-ruleset-providers.d.ts.map +1 -0
  66. package/dist/github/repository-ruleset.d.ts +26 -4
  67. package/dist/github/repository-ruleset.d.ts.map +1 -1
  68. package/dist/litellm/fake-litellm.d.ts +27 -16
  69. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  70. package/dist/litellm/fake-stack.d.ts +9 -4
  71. package/dist/litellm/fake-stack.d.ts.map +1 -1
  72. package/dist/litellm/index.d.ts +7 -5
  73. package/dist/litellm/index.d.ts.map +1 -1
  74. package/dist/litellm/index.js +53 -123
  75. package/dist/litellm/index.js.map +8 -8
  76. package/dist/litellm/operations.d.ts +60 -0
  77. package/dist/litellm/operations.d.ts.map +1 -0
  78. package/dist/litellm/pass-through-endpoint.d.ts +10 -28
  79. package/dist/litellm/pass-through-endpoint.d.ts.map +1 -1
  80. package/dist/litellm/pass-through-errors.d.ts +37 -0
  81. package/dist/litellm/pass-through-errors.d.ts.map +1 -0
  82. package/dist/litellm/pass-through-form.d.ts +11 -4
  83. package/dist/litellm/pass-through-form.d.ts.map +1 -1
  84. package/dist/litellm/providers.d.ts +7 -5
  85. package/dist/litellm/providers.d.ts.map +1 -1
  86. package/dist/netbox/prefix.d.ts +4 -4
  87. package/dist/paperless/custom-field.d.ts +21 -21
  88. package/dist/paperless/custom-field.d.ts.map +1 -1
  89. package/dist/paperless/document-type.d.ts +34 -4
  90. package/dist/paperless/document-type.d.ts.map +1 -1
  91. package/dist/paperless/fake-paperless.d.ts +36 -19
  92. package/dist/paperless/fake-paperless.d.ts.map +1 -1
  93. package/dist/paperless/index.d.ts +7 -2
  94. package/dist/paperless/index.d.ts.map +1 -1
  95. package/dist/paperless/index.js +164 -228
  96. package/dist/paperless/index.js.map +10 -13
  97. package/dist/paperless/matching-locate.d.ts +42 -20
  98. package/dist/paperless/matching-locate.d.ts.map +1 -1
  99. package/dist/paperless/matching-types.d.ts +47 -18
  100. package/dist/paperless/matching-types.d.ts.map +1 -1
  101. package/dist/paperless/matching.d.ts +30 -27
  102. package/dist/paperless/matching.d.ts.map +1 -1
  103. package/dist/paperless/providers.d.ts +13 -7
  104. package/dist/paperless/providers.d.ts.map +1 -1
  105. package/dist/paperless/storage-path.d.ts +34 -4
  106. package/dist/paperless/storage-path.d.ts.map +1 -1
  107. package/dist/paperless/tag.d.ts +43 -8
  108. package/dist/paperless/tag.d.ts.map +1 -1
  109. package/dist/proxmox/acl.d.ts +30 -23
  110. package/dist/proxmox/acl.d.ts.map +1 -1
  111. package/dist/proxmox/distilled-pve.d.ts +89 -0
  112. package/dist/proxmox/distilled-pve.d.ts.map +1 -0
  113. package/dist/proxmox/index.js +663 -609
  114. package/dist/proxmox/index.js.map +12 -11
  115. package/dist/proxmox/members.d.ts +29 -10
  116. package/dist/proxmox/members.d.ts.map +1 -1
  117. package/docs/caddy-admin.md +1 -1
  118. package/docs/caddy.md +3 -2
  119. package/docs/credentials.md +16 -0
  120. package/docs/litellm.md +21 -13
  121. package/docs/paperless.md +12 -0
  122. package/docs/peers.md +5 -0
  123. package/docs/repo-policy.md +16 -1
  124. package/docs/repository-ruleset.md +53 -13
  125. package/docs/upstream-conformance.md +126 -10
  126. package/package.json +5 -2
  127. package/src/caddy/admin-address.ts +71 -0
  128. package/src/caddy/admin-calls.ts +48 -100
  129. package/src/caddy/admin.ts +37 -41
  130. package/src/caddy/adopt-scope.test.ts +1 -1
  131. package/src/caddy/caddy-http-client.ts +229 -0
  132. package/src/caddy/config-adopt-provider.test.ts +139 -0
  133. package/src/caddy/config-adopt.test.ts +60 -126
  134. package/src/caddy/config-errors.ts +47 -0
  135. package/src/caddy/config-lifecycle.test.ts +55 -42
  136. package/src/caddy/config-lifecycle.ts +72 -162
  137. package/src/caddy/config-reconcile.ts +138 -0
  138. package/src/caddy/config.ts +36 -42
  139. package/src/caddy/fake-caddy.ts +20 -2
  140. package/src/caddy/index.ts +3 -8
  141. package/src/caddy/local-admin.test.ts +35 -21
  142. package/src/caddy/local-admin.ts +60 -167
  143. package/src/caddy/providers.test.ts +14 -6
  144. package/src/caddy/providers.ts +20 -4
  145. package/src/github/declare-repo-baseline-apply-order.test.ts +86 -0
  146. package/src/github/declare-repo-baseline.ts +40 -7
  147. package/src/github/index.ts +8 -4
  148. package/src/github/repo-apply-order-fake-providers.ts +119 -0
  149. package/src/github/repo-apply-order-fake-stack.ts +74 -0
  150. package/src/github/repo-auto-merge-gate.ts +57 -0
  151. package/src/github/repo-baseline-data.test.ts +13 -0
  152. package/src/github/repo-baseline-data.ts +11 -2
  153. package/src/github/repo-policy-apply-order.test.ts +72 -0
  154. package/src/github/repo-policy-stack.test.ts +59 -0
  155. package/src/github/repo-policy.ts +31 -3
  156. package/src/github/repository-ruleset-errors.ts +6 -4
  157. package/src/github/repository-ruleset-exact-adopt-k1.test.ts +109 -0
  158. package/src/github/repository-ruleset-exact-adopt.test.ts +14 -9
  159. package/src/github/repository-ruleset-form.ts +7 -3
  160. package/src/github/repository-ruleset-guards.ts +3 -2
  161. package/src/github/repository-ruleset-octokit.test.ts +141 -0
  162. package/src/github/repository-ruleset-octokit.ts +121 -30
  163. package/src/github/repository-ruleset-probe.ts +2 -1
  164. package/src/github/repository-ruleset-props.ts +7 -4
  165. package/src/github/repository-ruleset-providers.ts +46 -0
  166. package/src/github/repository-ruleset-wire.test.ts +38 -27
  167. package/src/github/repository-ruleset.ts +27 -3
  168. package/src/litellm/fake-litellm.ts +83 -78
  169. package/src/litellm/fake-stack.ts +13 -5
  170. package/src/litellm/index.ts +7 -21
  171. package/src/litellm/operations.ts +149 -0
  172. package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +17 -20
  173. package/src/litellm/pass-through-endpoint-replace.test.ts +3 -4
  174. package/src/litellm/pass-through-endpoint.test.ts +8 -8
  175. package/src/litellm/pass-through-endpoint.ts +21 -31
  176. package/src/litellm/pass-through-errors.ts +37 -0
  177. package/src/litellm/pass-through-form.ts +20 -10
  178. package/src/litellm/providers.ts +8 -7
  179. package/src/paperless/custom-field.test.ts +49 -40
  180. package/src/paperless/custom-field.ts +57 -33
  181. package/src/paperless/document-type.test.ts +48 -0
  182. package/src/paperless/document-type.ts +51 -22
  183. package/src/paperless/fake-paperless.ts +63 -72
  184. package/src/paperless/index.ts +7 -13
  185. package/src/paperless/matching-locate.test.ts +102 -0
  186. package/src/paperless/matching-locate.ts +80 -86
  187. package/src/paperless/matching-types.ts +49 -16
  188. package/src/paperless/matching.ts +77 -90
  189. package/src/paperless/providers.ts +14 -10
  190. package/src/paperless/storage-path.test.ts +56 -0
  191. package/src/paperless/storage-path.ts +53 -24
  192. package/src/paperless/tag-identity.test.ts +127 -0
  193. package/src/paperless/tag.test.ts +100 -0
  194. package/src/paperless/tag.ts +62 -32
  195. package/src/proxmox/acl.test.ts +145 -0
  196. package/src/proxmox/acl.ts +128 -150
  197. package/src/proxmox/distilled-pve.test.ts +231 -0
  198. package/src/proxmox/distilled-pve.ts +147 -0
  199. package/src/proxmox/members-timeout.test.ts +161 -0
  200. package/src/proxmox/members.test.ts +9 -0
  201. package/src/proxmox/members.ts +36 -2
  202. package/dist/litellm/client.d.ts +0 -76
  203. package/dist/litellm/client.d.ts.map +0 -1
  204. package/dist/litellm/credentials.d.ts +0 -48
  205. package/dist/litellm/credentials.d.ts.map +0 -1
  206. package/dist/litellm/generated/pass-through.d.ts +0 -71
  207. package/dist/litellm/generated/pass-through.d.ts.map +0 -1
  208. package/dist/paperless/client.d.ts +0 -37
  209. package/dist/paperless/client.d.ts.map +0 -1
  210. package/dist/paperless/credentials.d.ts +0 -40
  211. package/dist/paperless/credentials.d.ts.map +0 -1
  212. package/dist/paperless/errors.d.ts +0 -49
  213. package/dist/paperless/errors.d.ts.map +0 -1
  214. package/src/litellm/client.ts +0 -195
  215. package/src/litellm/credentials.ts +0 -73
  216. package/src/litellm/generated/pass-through.ts +0 -73
  217. package/src/paperless/client.ts +0 -88
  218. package/src/paperless/credentials.ts +0 -69
  219. package/src/paperless/errors.ts +0 -62
  220. package/src/paperless/matching-identity.test.ts +0 -194
  221. package/src/paperless/matching.test.ts +0 -128
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Running one `@distilled.cloud/proxmox` typed operation the way `client.ts`'s `pve()` ran a raw
3
+ * HTTP call — same credential lease reuse (lease-cache.ts) and the same cluster-member failover
4
+ * safety rule (members.ts), now handing the request itself to the SDK instead of building one.
5
+ *
6
+ * ★ THIS IS THE SEAM A DISTILLED-BACKED FAMILY CALLS INSTEAD OF `pve()`. `client.ts` stays for
7
+ * every family not yet migrated — see acl.ts, the first one, for what a migrated family looks
8
+ * like. A family migrates by calling its own `@distilled.cloud/proxmox/<segment>` typed
9
+ * operations through `runPve` below, never through a generic method+path+form call: that is
10
+ * what "no hand-rolled client" means in practice (upstream Alchemy PR 1425).
11
+ *
12
+ * ⚠️ THE WHOLE OPERATION IS RE-RUN PER MEMBER, NOT JUST ONE HTTP CALL. `executeOnCluster` in
13
+ * members.ts retries a single already-built `HttpClientRequest`; a distilled operation builds
14
+ * its OWN request internally from whatever `Credentials` it is given, so there is no request to
15
+ * hand between attempts. Failover here instead re-provides `Credentials` with the next member's
16
+ * `apiBaseUrl` and re-runs the same operation value. This is safe under the identical rule
17
+ * `executeOnCluster` documents and members.ts's classifiers still enforce: a write may repeat
18
+ * only on a PROVABLY pre-send transport failure (connection refused, DNS, a refused
19
+ * certificate) — never on a timeout after connect, and never on an HTTP answer of any kind,
20
+ * because an answer (even a typed error the SDK decoded) proves the request reached a live
21
+ * member and must not be sent again elsewhere. Reads may fail over on any transport failure.
22
+ *
23
+ * ⛔ A TYPED ANSWER-ERROR IS NEVER A REASON TO TRY ANOTHER MEMBER. `Forbidden`,
24
+ * `ParameterVerificationFailed`, `ClusterNodeUnreachable`, … all mean the request reached a
25
+ * member and that member answered — `isTransportFailure`/`isPreSendTransport` (members.ts)
26
+ * return `false` for all of them, exactly as they do for the `HttpClientResponse` case in
27
+ * `executeOnCluster`, since neither function inspects anything but a transport-level
28
+ * `HttpClientError`. Such an error propagates from here UNCHANGED — not wrapped, not retried —
29
+ * which is what lets a caller `catchTag` it directly.
30
+ *
31
+ * TRAP FOUND WHILE WRITING THE FAILOVER TEST: `Retry.none` on every attempt, and it is not
32
+ * optional. `@distilled.cloud/core`'s DEFAULT policy retries any error in its `ServerError`
33
+ * category automatically, with no caller opt-in (protocol.ts's own header names the mechanism:
34
+ * `core/category.ts`'s `isTransientError` via `makeDefault`) -- and that category covers
35
+ * transport failures too, not just 5xx answers: MEASURED -- a plain `listAccessAcl({})` against
36
+ * a member that refuses the connection hung past a 5-second test timeout, backing off inside
37
+ * the SDK before this file's own failover loop ever saw a failure to classify. Left enabled, a
38
+ * WRITE could be resent to the SAME member several times before `runPveWith` gets a result at
39
+ * all -- silently reintroducing, inside one member, the exact risk `executeOnCluster`
40
+ * (members.ts) exists to rule out across members ("a request that may have reached a member
41
+ * must not be sent again"). The hand-rolled client never auto-retried anything; member failover
42
+ * was its only resilience. `Retry.none` below keeps that property true here too.
43
+ *
44
+ * ⛔ GAP FOUND ON REVIEW, FIXED 2026-09-24: `Retry.none` only stops the SDK re-sending to the SAME
45
+ * member -- it added no bound on how long ONE attempt may wait. A member that accepts the TCP
46
+ * connection but never answers hung this whole function forever, since the for-loop below never
47
+ * got a result to classify and so never reached a healthy member. `MEMBER_TIMEOUT` (members.ts,
48
+ * shared with `client.ts`'s `executeOnCluster`) now bounds each attempt; `isTransportFailure`
49
+ * treats the resulting `Cause.TimeoutError` as failover-eligible for a read, and
50
+ * `isPreSendTransport` still refuses it for a write, for the same reason a post-send timeout
51
+ * always was refused.
52
+ */
53
+ import type * as Cause from 'effect/Cause';
54
+ import type * as Duration from 'effect/Duration';
55
+ import * as Effect from 'effect/Effect';
56
+ import * as Result from 'effect/Result';
57
+ import type * as HttpClient from 'effect/unstable/http/HttpClient';
58
+ import { credentials as distilledPveCredentials } from '@distilled.cloud/proxmox/Credentials';
59
+ import type { ProxmoxOpContext } from '@distilled.cloud/proxmox/Protocol';
60
+ import * as ProxmoxRetry from '@distilled.cloud/proxmox/Retry';
61
+ import type { PveCredential, PveRole, PveTarget } from './credentials.ts';
62
+ import { leased } from './lease-cache.ts';
63
+ import {
64
+ MEMBER_TIMEOUT,
65
+ type MemberAttempt,
66
+ describeTransport,
67
+ isPreSendTransport,
68
+ isTransportFailure,
69
+ noteGoodMember,
70
+ orderedMembers,
71
+ pveApiBase,
72
+ } from './members.ts';
73
+
74
+ /** Every member answered nothing usable — the direct successor to `client.ts`'s `clusterExhausted`. */
75
+ export class PveClusterExhausted extends Error {
76
+ constructor(readonly attempts: readonly MemberAttempt[]) {
77
+ super(`all members failed: ${attempts.map((a) => `${a.member} (${a.why})`).join('; ')}`);
78
+ this.name = 'PveClusterExhausted';
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Run `op` against `target`'s cluster on an ALREADY-HELD credential — the direct successor to
84
+ * `client.ts`'s `pveWith`, for a caller reasoning about identity rather than cost (or a test that
85
+ * wants a fixed, explicit credential rather than a live OpenBao mint — see distilled-pve.test.ts).
86
+ *
87
+ * @param isWrite Whether `op` may have a side effect once sent — decides which transport
88
+ * failures may retry on the next member. Pass `true` for anything that is not a plain read.
89
+ * @param timeout Bound on ONE member's attempt — defaults to `MEMBER_TIMEOUT` (members.ts). A
90
+ * test passes a short one to prove failover/bounding without waiting out the real bound;
91
+ * production code never overrides it. A member that answers past this is failed over (a read)
92
+ * or failed outright (a write, since the request may already have landed) — see
93
+ * `isTransportFailure`/`isPreSendTransport`.
94
+ */
95
+ export const runPveWith = <A, E>(
96
+ target: PveTarget,
97
+ credential: PveCredential,
98
+ isWrite: boolean,
99
+ op: Effect.Effect<A, E, ProxmoxOpContext>,
100
+ timeout: Duration.Input = MEMBER_TIMEOUT,
101
+ ): Effect.Effect<A, E | Cause.TimeoutError | PveClusterExhausted, HttpClient.HttpClient> =>
102
+ Effect.gen(function* () {
103
+ const attempts: MemberAttempt[] = [];
104
+ for (const member of orderedMembers(target)) {
105
+ const credLayer = distilledPveCredentials({
106
+ baseUrl: pveApiBase(member),
107
+ secret: credential.secret,
108
+ tokenId: credential.tokenId,
109
+ });
110
+ const outcome = yield* op.pipe(
111
+ ProxmoxRetry.none,
112
+ Effect.provide(credLayer),
113
+ Effect.timeout(timeout),
114
+ Effect.result,
115
+ );
116
+ if (Result.isSuccess(outcome)) {
117
+ noteGoodMember(target, member);
118
+ return outcome.success;
119
+ }
120
+ const mayRetry = isWrite
121
+ ? isPreSendTransport(outcome.failure)
122
+ : isTransportFailure(outcome.failure);
123
+ if (!mayRetry) return yield* Effect.fail(outcome.failure);
124
+ attempts.push({ member, why: describeTransport(outcome.failure) });
125
+ }
126
+ return yield* Effect.fail(new PveClusterExhausted(attempts));
127
+ });
128
+
129
+ /**
130
+ * `runPveWith`, minting/reusing a `role` lease exactly as `pve()` did — the seam a migrated
131
+ * resource file calls.
132
+ *
133
+ * ⚠️ `| Error` IN THE RETURN TYPE IS `leased()`'s OWN FAILURE, NOT A NEW ONE THIS FILE ADDS.
134
+ * `credentials.ts`'s `mint` fails with a plain `Error` (an OpenBao refusal — sealed, denied, no
135
+ * such role) and was never typed more narrowly there; `pve()` carried the same untyped
136
+ * possibility before this file existed, just without a signature that said so.
137
+ */
138
+ export const runPve = <A, E>(
139
+ target: PveTarget,
140
+ role: PveRole,
141
+ isWrite: boolean,
142
+ op: Effect.Effect<A, E, ProxmoxOpContext>,
143
+ ): Effect.Effect<A, E | Cause.TimeoutError | PveClusterExhausted | Error, HttpClient.HttpClient> =>
144
+ Effect.gen(function* () {
145
+ const credential = yield* leased(target, role);
146
+ return yield* runPveWith(target, credential, isWrite, op);
147
+ });
@@ -0,0 +1,161 @@
1
+ /**
2
+ * `MEMBER_TIMEOUT` bounds one cluster-member attempt — proved directly against `executeOnCluster`
3
+ * with a SHORT injected bound, not against `pveWith`/`runPveWith` with an outer `Effect.timeout`.
4
+ *
5
+ * ⛔ WHY A SEPARATE FILE, NOT MORE CASES IN client.test.ts's EXISTING "transport timeout" TEST:
6
+ * that test (and distilled-pve.test.ts's twin) wraps the WHOLE call in `Effect.timeout('50
7
+ * millis')` from the OUTSIDE. That proves a write is never resent once the outer timeout wins
8
+ * the race — but it proves nothing about whether `executeOnCluster`/`runPveWith` bound a single
9
+ * attempt THEMSELVES: before this file existed, nothing did, and those tests still passed,
10
+ * because the outer wrapper always won first. A broken fix here hangs node:test's own per-test
11
+ * timeout instead of quietly passing.
12
+ */
13
+ import assert from 'node:assert/strict';
14
+ import { describe, it } from 'node:test';
15
+ import * as Effect from 'effect/Effect';
16
+ import * as Layer from 'effect/Layer';
17
+ import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
18
+ import type * as HttpClient from 'effect/unstable/http/HttpClient';
19
+ import * as HttpClientRequest from 'effect/unstable/http/HttpClientRequest';
20
+ import type { PveTarget } from './credentials.ts';
21
+ import { executeOnCluster, resetLastGoodForTest } from './members.ts';
22
+
23
+ type ServeOptions = {
24
+ fetch(request: Request): Response | Promise<Response>;
25
+ hostname: string;
26
+ port: number;
27
+ };
28
+ const { Bun } = globalThis as unknown as {
29
+ Bun: { serve(options: ServeOptions): { port: number; stop(closeActive: boolean): void } };
30
+ };
31
+
32
+ const target = (members: readonly string[]): PveTarget => ({
33
+ members,
34
+ mount: 'proxmox-c1-test',
35
+ scheme: 'pve',
36
+ });
37
+
38
+ /** Same remap as client.test.ts: https://member:8006 -> http://127.0.0.1:<port>. Never a live host. */
39
+ const remapFetch = (ports: Record<string, number>): typeof fetch =>
40
+ Object.assign(
41
+ (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {
42
+ const url = new URL(String(input instanceof Request ? input.url : input));
43
+ const port = ports[url.hostname];
44
+ if (port === undefined) {
45
+ return Promise.reject(Object.assign(new Error('unmapped host'), { code: 'ENOTFOUND' }));
46
+ }
47
+ url.protocol = 'http:';
48
+ url.hostname = '127.0.0.1';
49
+ url.port = String(port);
50
+ return globalThis.fetch(url, init);
51
+ },
52
+ { preconnect: globalThis.fetch.preconnect },
53
+ );
54
+
55
+ const run = <A, E>(
56
+ effect: Effect.Effect<A, E, HttpClient.HttpClient>,
57
+ ports: Record<string, number>,
58
+ ) =>
59
+ Effect.runPromise(
60
+ effect.pipe(
61
+ Effect.provide(
62
+ FetchHttpClient.layer.pipe(
63
+ Layer.provideMerge(Layer.succeed(FetchHttpClient.Fetch, remapFetch(ports))),
64
+ ),
65
+ ),
66
+ ),
67
+ );
68
+
69
+ const counting = (body: unknown) => {
70
+ let hits = 0;
71
+ const server = Bun.serve({
72
+ fetch: () => {
73
+ hits += 1;
74
+ return Response.json({ data: body });
75
+ },
76
+ hostname: '127.0.0.1',
77
+ port: 0,
78
+ });
79
+ return { hits: () => hits, port: server.port, stop: () => server.stop(true) };
80
+ };
81
+
82
+ const hanging = () =>
83
+ Bun.serve({
84
+ fetch: () => new Promise<Response>(() => {}),
85
+ hostname: '127.0.0.1',
86
+ port: 0,
87
+ });
88
+
89
+ const A = 'pve-a.test';
90
+ const B = 'pve-b.test';
91
+ const BOUND = '100 millis';
92
+
93
+ describe('MEMBER_TIMEOUT bounds a single attempt', () => {
94
+ /**
95
+ * ⚠️ EXPLICIT `{ timeout: 15000 }`: bun's default per-test kill is 5000ms (MEASURED via a
96
+ * throwaway probe), which is too close to this shared, often-loaded dev machine's own
97
+ * scheduling jitter to trust — a slow tick could kill the test via the FRAMEWORK's timeout
98
+ * before the 100ms `BOUND` or the assertion below ever gets a say, which reads as this fix
99
+ * being broken when it is not. 15s leaves wide headroom while staying far under this file's
100
+ * own 10s assertion, so a genuine "never bounded" regression still fails it either way.
101
+ */
102
+ it(
103
+ 'fails a hung read over to the next member, within the injected bound',
104
+ { timeout: 15000 },
105
+ async () => {
106
+ resetLastGoodForTest();
107
+ const hang = hanging();
108
+ const good = counting(['ok']);
109
+ try {
110
+ const started = Date.now();
111
+ const response = await run(
112
+ executeOnCluster(
113
+ target([A, B]),
114
+ 'GET',
115
+ 'pools',
116
+ (apiBase) => HttpClientRequest.get(`${apiBase}/pools`),
117
+ BOUND,
118
+ ),
119
+ { [A]: hang.port, [B]: good.port },
120
+ );
121
+ assert.equal(response.status, 200);
122
+ assert.equal(good.hits(), 1);
123
+ // ⚠️ Loose on purpose (machine jitter) — the real separation is "did not hang for the
124
+ // whole per-test timeout above", which a regression here would.
125
+ assert.ok(Date.now() - started < 10000);
126
+ } finally {
127
+ hang.stop(true);
128
+ good.stop();
129
+ }
130
+ },
131
+ );
132
+
133
+ it(
134
+ 'fails a hung write outright, never resending it, within the injected bound',
135
+ { timeout: 15000 },
136
+ async () => {
137
+ resetLastGoodForTest();
138
+ const hang = hanging();
139
+ const backup = counting('UPID:timeout-test');
140
+ try {
141
+ const started = Date.now();
142
+ await run(
143
+ executeOnCluster(
144
+ target([A, B]),
145
+ 'PUT',
146
+ 'nodes/node-c/network',
147
+ (apiBase) => HttpClientRequest.put(`${apiBase}/nodes/node-c/network`),
148
+ BOUND,
149
+ ).pipe(Effect.flip),
150
+ { [A]: hang.port, [B]: backup.port },
151
+ );
152
+ // ⛔ B must never receive the PUT — a post-connect timeout is not pre-send failover.
153
+ assert.equal(backup.hits(), 0);
154
+ assert.ok(Date.now() - started < 10000);
155
+ } finally {
156
+ hang.stop(true);
157
+ backup.stop();
158
+ }
159
+ },
160
+ );
161
+ });
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import assert from 'node:assert/strict';
10
10
  import { describe, it } from 'node:test';
11
+ import * as Cause from 'effect/Cause';
11
12
  import * as HttpClientError from 'effect/unstable/http/HttpClientError';
12
13
  import { isPreSendTransport, isTransportFailure } from './members.ts';
13
14
 
@@ -51,4 +52,12 @@ describe('transport classification', () => {
51
52
  assert.equal(isTransportFailure(new Error('500')), false);
52
53
  assert.equal(isPreSendTransport(new Error('ConnectionRefused')), false);
53
54
  });
55
+
56
+ // ★ `MEMBER_TIMEOUT` produces this via `Effect.timeout` — see members-timeout.test.ts for the
57
+ // live-server proof that it actually fires and bounds a hung attempt.
58
+ it('treats a bounded-attempt timeout as transport for a read, never pre-send for a write', () => {
59
+ const timedOut = new Cause.TimeoutError();
60
+ assert.equal(isTransportFailure(timedOut), true);
61
+ assert.equal(isPreSendTransport(timedOut), false);
62
+ });
54
63
  });
@@ -6,6 +6,8 @@
6
6
  *
7
7
  * ⛔ NEVER cluster-c1.example.com: it resolves to 192.0.2.250, which nothing serves.
8
8
  */
9
+ import * as Cause from 'effect/Cause';
10
+ import type * as Duration from 'effect/Duration';
9
11
  import * as Effect from 'effect/Effect';
10
12
  import * as Result from 'effect/Result';
11
13
  import * as HttpClient from 'effect/unstable/http/HttpClient';
@@ -19,6 +21,22 @@ export const PVE_API_PORT = 8006;
19
21
  /** Strict TLS, always :8006 — each node's certificate carries its mgmt name as a SAN. */
20
22
  export const pveApiBase = (member: string) => `https://${member}:${String(PVE_API_PORT)}/api2/json`;
21
23
 
24
+ /**
25
+ * Bound on ONE member attempt, shared by `client.ts`'s `executeOnCluster` and
26
+ * `distilled-pve.ts`'s `runPveWith` — both loop over `orderedMembers` and both call this file's
27
+ * classifiers, so one constant keeps them in the same failure-mode boundary.
28
+ *
29
+ * ⛔ MEASURED GAP, FIXED 2026-09-24: before this, NOTHING bounded a single attempt. A member that
30
+ * accepted the TCP connection but never answered hung the whole call forever — the for-loop
31
+ * never produced a failure for `isTransportFailure`/`isPreSendTransport` to classify, so it
32
+ * never advanced to a healthy member. `isTimeoutCause` below existed already (written for this
33
+ * exact case, per its own comment: "no answer after connect") but nothing ever called
34
+ * `Effect.timeout` to produce one.
35
+ * ★ TB4 (the live 3-member cluster, `homeflare-proxmox/src/target.ts`) waits at most 3× this
36
+ * before `PveClusterExhausted` — well under S26's ~90-second ceiling on a bounded wait.
37
+ */
38
+ export const MEMBER_TIMEOUT: Duration.Input = '20 seconds';
39
+
22
40
  /** Process-scoped last member that answered, keyed by OpenBao mount (= one PVE cluster). */
23
41
  const lastGoodByMount = new Map<string, string>();
24
42
 
@@ -82,14 +100,22 @@ const isPreSendCause = (cause: unknown): boolean => {
82
100
  * ★ TransportError | InvalidUrlError | EncodeError occur before a response exists; StatusCodeError
83
101
  * and kin include a response. client.execute does NOT filter status, so HTTP answers are never
84
102
  * TransportError — only the transport layer is.
103
+ * ★ `Cause.TimeoutError` (from `MEMBER_TIMEOUT` below, via `Effect.timeout`) counts too — a read
104
+ * with no side effect to duplicate fails over on ANY transport failure, a hang included.
85
105
  */
86
106
  export const isTransportFailure = (cause: unknown): boolean => {
107
+ if (Cause.isTimeoutError(cause)) return true;
87
108
  if (!HttpClientError.isHttpClientError(cause)) return false;
88
109
  const tag = cause.reason._tag;
89
110
  return tag === 'TransportError' || tag === 'InvalidUrlError' || tag === 'EncodeError';
90
111
  };
91
112
 
92
- /** Pre-send only: connection refused, DNS, a certificate refused — never a timeout after connect. */
113
+ /**
114
+ * Pre-send only: connection refused, DNS, a certificate refused — never a timeout after connect.
115
+ * ⛔ `Cause.TimeoutError` (MEMBER_TIMEOUT firing) falls through to `false` here on purpose, same
116
+ * as any other non-`HttpClientError` cause: the request may already have reached the member, so
117
+ * a write must not repeat it elsewhere.
118
+ */
93
119
  export const isPreSendTransport = (cause: unknown): boolean => {
94
120
  if (!HttpClientError.isHttpClientError(cause)) return false;
95
121
  const { reason } = cause;
@@ -102,6 +128,7 @@ export const isPreSendTransport = (cause: unknown): boolean => {
102
128
  };
103
129
 
104
130
  export const describeTransport = (cause: unknown): string => {
131
+ if (Cause.isTimeoutError(cause)) return `timed out after ${String(MEMBER_TIMEOUT)}`;
105
132
  if (HttpClientError.isHttpClientError(cause)) return cause.message;
106
133
  return String(cause);
107
134
  };
@@ -122,12 +149,17 @@ export type PveMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
122
149
  * ★ Reads fail over on any transport failure, including timeout — no side effect to duplicate.
123
150
  * MEASURED 2026-09-14: full C1 plans stayed all-noop with the first member refused (127.0.0.1)
124
151
  * and with it unresolvable (n9.invalid).
152
+ *
153
+ * @param timeout Bound on ONE member's attempt — defaults to `MEMBER_TIMEOUT`. A test passes a
154
+ * short one to prove failover happens without waiting out the real bound; production code never
155
+ * overrides it.
125
156
  */
126
157
  export const executeOnCluster = (
127
158
  target: PveTarget,
128
159
  method: PveMethod,
129
160
  path: string,
130
161
  build: (apiBase: string) => HttpClientRequest.HttpClientRequest,
162
+ timeout: Duration.Input = MEMBER_TIMEOUT,
131
163
  ): Effect.Effect<
132
164
  HttpClientResponse.HttpClientResponse,
133
165
  {
@@ -156,7 +188,9 @@ export const executeOnCluster = (
156
188
  const isWrite = method !== 'GET';
157
189
  const attempts: MemberAttempt[] = [];
158
190
  for (const member of orderedMembers(target)) {
159
- const outcome = yield* client.execute(build(pveApiBase(member))).pipe(Effect.result);
191
+ const outcome = yield* client
192
+ .execute(build(pveApiBase(member)))
193
+ .pipe(Effect.timeout(timeout), Effect.result);
160
194
  if (Result.isSuccess(outcome)) {
161
195
  noteGoodMember(target, member);
162
196
  return outcome.success;
@@ -1,76 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,48 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,71 +0,0 @@
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
@@ -1 +0,0 @@
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"}