axonium 0.0.0-stage → 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +157 -2
  3. package/dist/cjs/auth.cjs +298 -0
  4. package/dist/cjs/auth.js.map +1 -0
  5. package/dist/cjs/client.cjs +235 -0
  6. package/dist/cjs/client.js.map +1 -0
  7. package/dist/cjs/config.cjs +133 -0
  8. package/dist/cjs/config.js.map +1 -0
  9. package/dist/cjs/errors.cjs +482 -0
  10. package/dist/cjs/errors.js.map +1 -0
  11. package/dist/cjs/index.cjs +87 -0
  12. package/dist/cjs/index.js.map +1 -0
  13. package/dist/cjs/resources.cjs +181 -0
  14. package/dist/cjs/resources.js.map +1 -0
  15. package/dist/cjs/retry.cjs +102 -0
  16. package/dist/cjs/retry.js.map +1 -0
  17. package/dist/cjs/sse.cjs +317 -0
  18. package/dist/cjs/sse.js.map +1 -0
  19. package/dist/cjs/transport.cjs +306 -0
  20. package/dist/cjs/transport.js.map +1 -0
  21. package/dist/cjs/version.cjs +16 -0
  22. package/dist/cjs/version.js.map +1 -0
  23. package/dist/esm/auth.d.ts +77 -0
  24. package/dist/esm/auth.d.ts.map +1 -0
  25. package/dist/esm/auth.js +293 -0
  26. package/dist/esm/auth.js.map +1 -0
  27. package/dist/esm/client.d.ts +202 -0
  28. package/dist/esm/client.d.ts.map +1 -0
  29. package/dist/esm/client.js +229 -0
  30. package/dist/esm/client.js.map +1 -0
  31. package/dist/esm/config.d.ts +75 -0
  32. package/dist/esm/config.d.ts.map +1 -0
  33. package/dist/esm/config.js +129 -0
  34. package/dist/esm/config.js.map +1 -0
  35. package/dist/esm/errors.d.ts +389 -0
  36. package/dist/esm/errors.d.ts.map +1 -0
  37. package/dist/esm/errors.js +430 -0
  38. package/dist/esm/errors.js.map +1 -0
  39. package/dist/esm/index.d.ts +24 -0
  40. package/dist/esm/index.d.ts.map +1 -0
  41. package/dist/esm/index.js +16 -0
  42. package/dist/esm/index.js.map +1 -0
  43. package/dist/esm/resources.d.ts +167 -0
  44. package/dist/esm/resources.d.ts.map +1 -0
  45. package/dist/esm/resources.js +170 -0
  46. package/dist/esm/resources.js.map +1 -0
  47. package/dist/esm/retry.d.ts +72 -0
  48. package/dist/esm/retry.d.ts.map +1 -0
  49. package/dist/esm/retry.js +96 -0
  50. package/dist/esm/retry.js.map +1 -0
  51. package/dist/esm/sse.d.ts +83 -0
  52. package/dist/esm/sse.d.ts.map +1 -0
  53. package/dist/esm/sse.js +311 -0
  54. package/dist/esm/sse.js.map +1 -0
  55. package/dist/esm/transport.d.ts +91 -0
  56. package/dist/esm/transport.d.ts.map +1 -0
  57. package/dist/esm/transport.js +299 -0
  58. package/dist/esm/transport.js.map +1 -0
  59. package/dist/esm/version.d.ts +13 -0
  60. package/dist/esm/version.d.ts.map +1 -0
  61. package/dist/esm/version.js +13 -0
  62. package/dist/esm/version.js.map +1 -0
  63. package/docs/api.md +263 -0
  64. package/docs/migrating-from-python.md +172 -0
  65. package/package.json +57 -4
  66. package/src/auth.ts +359 -0
  67. package/src/client.ts +365 -0
  68. package/src/config.ts +237 -0
  69. package/src/errors.ts +581 -0
  70. package/src/index.ts +106 -0
  71. package/src/resources.ts +349 -0
  72. package/src/retry.ts +128 -0
  73. package/src/sse.ts +333 -0
  74. package/src/transport.ts +379 -0
  75. package/src/version.ts +13 -0
package/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emeric Espiritu Santiago
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ Additional Terms for Enterprise Use:
24
+
25
+ For commercial or enterprise use, please contact the copyright holder at
26
+ emericespiritusantiago@gmail.com to discuss licensing terms and potential
27
+ remuneration.
package/README.md CHANGED
@@ -1,3 +1,158 @@
1
- # Temporary Holding Version
1
+ # Axonium for TypeScript
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Axonium SDK for the Prometheus inference platform, for Node, Bun, Deno and edge runtimes.
4
+
5
+ > **Status: `0.1.0`, ready to publish and not yet published.** Every resource, streaming, vision, tool
6
+ > calling and structured output; **49 of 49** shared contract cases; 77 unit tests and 16 integration
7
+ > tests against a live gateway; five runnable [examples](examples/) and an
8
+ > [API reference](docs/api.md). **9.5 KB** minified and gzipped, against a 50 KB budget.
9
+ >
10
+ > Not here yet: OpenTelemetry and a logging hook (`0.2.0`), `usage.export` (in no SDK of this family),
11
+ > and `X-Prometheus-Ignored-Parameters` on a success (held until all five SDKs agree on the shape). Bun,
12
+ > Deno and edge are **untested** — only Node 20, 22 and 24 are in CI, and claiming the rest because this
13
+ > only uses `fetch` would be the kind of unmeasured assertion this repository keeps catching.
14
+ >
15
+ > Requested in
16
+ > `apeiron_axonium_prometheus/solicitud-axonium-sdk-typescript.md`; see that channel for scope and
17
+ > dates.
18
+
19
+ ```ts
20
+ import { Transport, resolveConfig } from "axonium";
21
+
22
+ const transport = new Transport(resolveConfig()); // reads AXONIUM_GATEWAY_BASE_URL, …
23
+
24
+ const catalog = await transport.sendJSON("GET", "/v1/models", {});
25
+ const answer = await transport.sendJSON("POST", "/v1/chat/completions", {
26
+ model: "qwen3-0.6b",
27
+ body: { model: "qwen3-0.6b", messages: [{ role: "user", content: "hola" }] },
28
+ });
29
+ ```
30
+
31
+ `Transport` is the layer the resource methods will sit on; it is exported because it is useful on its
32
+ own and because nothing about it should be a surprise later.
33
+
34
+ ## What is decided, and why
35
+
36
+ **Zero runtime dependencies, and a test that says so.** `fetch` is the platform's. A transitive
37
+ dependency is how a 50 KB budget becomes 400 KB and how an edge build breaks, and both get discovered
38
+ by the consumer rather than here — so `test/package.test.ts` fails if `dependencies` is ever
39
+ non-empty, and if anything under `src/` imports a `node:` module.
40
+
41
+ **`fetch` is injected, not patched.** `options.fetch` exists so the contract corpus can be replayed
42
+ against a function. That is why this package has no HTTP-mocking dev dependency, and a test refuses
43
+ one: with a mocking library, what the suite exercises stops being the real transport.
44
+
45
+ **The source is erasable TypeScript.** `erasableSyntaxOnly` is on, so no parameter properties, enums
46
+ or decorators — nothing that compiles to code. The reason is concrete rather than stylistic: Node
47
+ runs this package's own `.ts` with `--experimental-strip-types`, so the whole suite needs no build
48
+ step and no test runner. Writing one parameter property is what revealed this, and a compiler flag is
49
+ cheaper than remembering.
50
+
51
+ **A credential is refused where this cannot establish it is on a server.** Not via
52
+ `typeof window !== "undefined"` — jsdom defines `window`, so that fires in anyone's vitest suite, and
53
+ a guard with false positives in CI is a guard somebody disables. It looks for positive evidence of a
54
+ server runtime instead, so an environment nobody anticipated reads as unknown rather than as safe.
55
+
56
+ The rule it enforces is about **whose** credential it is, not where the code runs. A `client_id` is
57
+ the principal that model grants and every usage row are keyed to, so an integrator's credential must
58
+ never reach a machine its users control — while an end client's **own** credential may live on their
59
+ own device, which the platform supports explicitly. A bundle cannot tell those apart, so the default
60
+ refuses and `allowInsecureCredential` is how a caller states which case theirs is.
61
+
62
+ **`request` defaults to 600 s, deliberately.** The gateway allows its backends 600 s and some image
63
+ backends take 2–8 minutes. A client timeout shorter than the server's, combined with a retry, is a
64
+ known failure mode: the backend keeps computing and the retry queues a second expensive generation.
65
+ **An edge runtime cannot wait that long** — that is a real conflict rather than a tuning question, and
66
+ a deployment that cannot afford it should say so instead of meeting it as a truncated request.
67
+
68
+ ## What the transport does
69
+
70
+ **Retries only what the contract says reached no model.** This API deduplicates nothing server-side,
71
+ so a retried generation is a _new billable one_ rather than a replay — which makes an over-eager
72
+ policy expensive rather than merely noisy. Three attempts, 1s base, 60s cap, full jitter. An
73
+ `idempotencyKey` is what turns a repeat into a replay, and every call accepts one.
74
+
75
+ **A client timeout is never retried.** The backend is probably still generating, so a retry queues a
76
+ second expensive generation on top of the first rather than resuming it.
77
+
78
+ **`Retry-After` is honoured verbatim**, because the gateway computes it from the real reset time or
79
+ the breaker's expected recovery. It is still capped: a wait longer than `maxBackoff` is handed to the
80
+ caller rather than slept through, since blocking for ten minutes is a scheduling decision that belongs
81
+ to the application.
82
+
83
+ **Cooldowns are keyed by model, not by gateway.** `backend-unavailable` means every replica of _that_
84
+ model is out while others on the same gateway keep serving.
85
+
86
+ **A 401 refreshes the token once, then gives up.** Bounded deliberately: the condition compares the
87
+ rejected token with the one just used, which is false again after every refresh, so without a counter
88
+ a gateway answering 401 to everything loops forever.
89
+
90
+ **Correlation ids are read from the body and then the headers.** The fallback is load-bearing: a body
91
+ that is not an envelope at all still arrives — an HTML page from a proxy that never reached the
92
+ gateway — and there the ids exist only in the headers, if anywhere.
93
+
94
+ ## The error taxonomy
95
+
96
+ One class per catalogued `type` suffix, dispatched on the **last path segment** of the problem-details
97
+ `type` URI. An unknown suffix falls back by status rather than throwing: the catalogue grows, and an
98
+ error documented on a Tuesday must not become a parse failure in a version already published.
99
+
100
+ ```ts
101
+ try {
102
+ /* … */
103
+ } catch (err) {
104
+ if (err instanceof ForbiddenError) console.log(err.detail, err.meta.requestId);
105
+ }
106
+ ```
107
+
108
+ Two deliberate shapes:
109
+
110
+ - **`OAuthError` does not extend `APIError`.** The token endpoint answers RFC 6749
111
+ `{error, error_description}`, not problem+json, and the remedies differ: an inference failure may be
112
+ worth retrying with the same credential, a credential failure never is. Both extend `AxoniumError`,
113
+ so one `catch` still covers the package.
114
+ - **`PredictBackendRejectedError.retryable` is derived from the status**, because the pass-through
115
+ route keeps the _engine's_ status: one suffix covers a `422` that will never succeed and a `429`
116
+ that will.
117
+
118
+ ## Documentation
119
+
120
+ - **[`docs/api.md`](docs/api.md)** — every export, including all 34 error classes against their `type`
121
+ suffix. A test asserts that nothing is exported without an entry.
122
+ - **[`docs/migrating-from-python.md`](docs/migrating-from-python.md)** — call by call, including the three
123
+ things Python has that this does not and why.
124
+ - **[`examples/`](examples/)** — chat, streaming from a Next.js Route Handler, vision, tool calling, and
125
+ governed mode. They import `axonium` by name, so they typecheck against the **published** `.d.ts`
126
+ rather than against `src/`.
127
+
128
+ ## A custom certificate authority
129
+
130
+ There is no `caBundle` option and there cannot be one: `fetch` has no option for a CA, and reaching one
131
+ means either an `undici` dependency or a `node:` import — each breaking a requirement this package was
132
+ built to. Use the runtime's own mechanism:
133
+
134
+ ```bash
135
+ NODE_EXTRA_CA_CERTS=/path/to/ca.pem node app.js # Node, Bun
136
+ deno run --cert /path/to/ca.pem app.ts # Deno
137
+ ```
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ npm install
143
+ npm test # node --test, no runner, no build step (needs Node 22+)
144
+ npm run build # ESM + CJS + .d.ts
145
+ npm run lint # prettier --check, plus tsc on the package and on the examples
146
+
147
+ AXONIUM_INTEGRATION=1 AXONIUM_GATEWAY_BASE_URL=… AXONIUM_CLIENT_ID=… AXONIUM_CLIENT_SECRET=… \
148
+ npm run test:integration
149
+ ```
150
+
151
+ The integration suite is **skipped without credentials and never runs in CI** — it spends real
152
+ inference, so a contributor who has not opted in must not pay for it. It picks models by **modality**
153
+ from the live catalogue rather than hardcoding ids, because a fixed id fails on every deployment that
154
+ does not serve it and that failure reads as a broken SDK.
155
+
156
+ The published package supports Node 20+. Node 20 cannot strip types, so the suite above proves
157
+ nothing about it — CI runs the **built** artifact there instead, which is what a consumer on 20
158
+ installs.
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenManager = void 0;
4
+ exports.decodeClaims = decodeClaims;
5
+ const errors_ts_1 = require("./errors.cjs");
6
+ const version_ts_1 = require("./version.cjs");
7
+ /** Refresh once this much of the token's life has gone. */
8
+ const REFRESH_AHEAD_RATIO = 0.8;
9
+ /** …or this little is left, whichever comes first. */
10
+ const REFRESH_AHEAD_MIN_SECONDS = 30;
11
+ /**
12
+ * A ceiling on any advertised lifetime.
13
+ *
14
+ * An `expires_in` of a year is a misconfiguration rather than a gift, and honouring it would mean
15
+ * holding a token that the gateway has long since forgotten, failing every request until the process
16
+ * restarts.
17
+ */
18
+ const MAX_PLAUSIBLE_TTL_SECONDS = 86_400;
19
+ /**
20
+ * Decodes a JWT's payload without verifying it.
21
+ *
22
+ * **Not verification, and it must not be used as such.** The signature is the gateway's to check; this
23
+ * reads the claims so a caller can see which `client_id` and scopes they are operating as, and so the
24
+ * token's own `exp` can be used as a second reading of its lifetime.
25
+ *
26
+ * Done by hand because the alternative is a dependency, and because all that is needed is one
27
+ * base64url segment. A malformed token yields empty claims rather than throwing: the gateway decides
28
+ * whether a token is good, and a decode failure here must not become the reason a request never left.
29
+ */
30
+ function decodeClaims(accessToken) {
31
+ const empty = {
32
+ subject: undefined,
33
+ authorizedParty: undefined,
34
+ scope: [],
35
+ expiresAt: undefined,
36
+ issuedAt: undefined,
37
+ raw: {},
38
+ };
39
+ const parts = accessToken.split(".");
40
+ if (parts.length !== 3)
41
+ return empty;
42
+ const payload = parts[1];
43
+ if (!payload)
44
+ return empty;
45
+ let json;
46
+ try {
47
+ const padded = payload.replace(/-/g, "+").replace(/_/g, "/");
48
+ const binary = atob(padded + "=".repeat((4 - (padded.length % 4)) % 4));
49
+ // atob gives latin1; a claim with a non-ASCII character would otherwise come back mangled.
50
+ json = new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)));
51
+ }
52
+ catch {
53
+ return empty;
54
+ }
55
+ let raw;
56
+ try {
57
+ const parsed = JSON.parse(json);
58
+ if (typeof parsed !== "object" || parsed === null)
59
+ return empty;
60
+ raw = parsed;
61
+ }
62
+ catch {
63
+ return empty;
64
+ }
65
+ const scope = raw["scope"];
66
+ const text = (key) => typeof raw[key] === "string" ? raw[key] : undefined;
67
+ return {
68
+ subject: text("sub"),
69
+ authorizedParty: text("azp"),
70
+ scope: typeof scope === "string" ? scope.split(" ").filter(Boolean) : [],
71
+ expiresAt: typeof raw["exp"] === "number" ? raw["exp"] : undefined,
72
+ issuedAt: typeof raw["iat"] === "number" ? raw["iat"] : undefined,
73
+ raw,
74
+ };
75
+ }
76
+ /**
77
+ * How long a token is really good for, in seconds.
78
+ *
79
+ * `expires_in` is the server's own answer and is normally used as-is. When the response's `Date`
80
+ * header and the token's own `exp` are both present, their difference is a **second, independent
81
+ * reading of the same lifetime** — and a skew-free one, because both come from the server's clock
82
+ * rather than being compared against ours. The platform confirmed this is the right anchor.
83
+ *
84
+ * The shorter wins. Disagreement should not happen, and treating a token as expiring sooner only
85
+ * costs an early refresh, while treating it as living longer costs a request.
86
+ */
87
+ function effectiveLifetime(headers, accessToken, expiresIn) {
88
+ const date = headers.get("Date");
89
+ const exp = decodeClaims(accessToken).expiresAt;
90
+ if (!date || exp === undefined)
91
+ return expiresIn;
92
+ const serverNow = Date.parse(date);
93
+ if (Number.isNaN(serverNow))
94
+ return expiresIn;
95
+ const serverRemaining = exp - serverNow / 1000;
96
+ // Already expired by the server's own reckoning: fail fast rather than spend a request finding out.
97
+ if (serverRemaining <= 0)
98
+ return 0;
99
+ return Math.min(expiresIn, serverRemaining);
100
+ }
101
+ /**
102
+ * Holds the credential and hands out tokens.
103
+ *
104
+ * **Concurrent callers share one in-flight refresh.** A promise is kept rather than a lock, which is
105
+ * the JavaScript shape of double-checked locking: the second caller awaits the first one's request
106
+ * instead of starting its own. Without it, a cold client answering ten simultaneous requests would
107
+ * send ten token requests, and nine of them would be charged against the rate-limit budget for
108
+ * nothing.
109
+ */
110
+ class TokenManager {
111
+ config;
112
+ provider;
113
+ current;
114
+ inFlight;
115
+ /** The last token a provider gave us, so a 401 can tell the provider *which* one was rejected. */
116
+ lastProvided;
117
+ constructor(config) {
118
+ this.config = config;
119
+ this.provider = config.tokenProvider;
120
+ }
121
+ /** The claims of the token currently held, or `undefined` before the first one is obtained. */
122
+ get claims() {
123
+ const token = this.current?.accessToken ?? this.lastProvided;
124
+ return token === undefined ? undefined : decodeClaims(token);
125
+ }
126
+ /** The scope the gateway granted, which may be narrower than the one requested. */
127
+ get grantedScope() {
128
+ return this.current?.scope ?? this.claims?.scope ?? [];
129
+ }
130
+ /** A token to send now, fetching or refreshing if the one held is spent. */
131
+ async token() {
132
+ if (this.provider) {
133
+ const token = await this.provider.token();
134
+ this.lastProvided = token;
135
+ return token;
136
+ }
137
+ if (this.current && !this.needsRefresh(this.current))
138
+ return this.current.accessToken;
139
+ return (await this.obtain()).accessToken;
140
+ }
141
+ /**
142
+ * Discards the token the gateway just rejected and gets another, exactly once per rejection.
143
+ *
144
+ * Compares against what was rejected rather than refreshing unconditionally: when several
145
+ * concurrent requests are all rejected with the same stale token, the first one's replacement
146
+ * serves them all, and the rest must not each throw away a token that is already good.
147
+ */
148
+ async refreshAfterRejection(rejected) {
149
+ if (this.provider) {
150
+ const token = await this.provider.refresh(rejected);
151
+ if (token === rejected) {
152
+ throw new errors_ts_1.ConfigurationError("The tokenProvider returned the same token the gateway had just rejected, so retrying " +
153
+ "would fail identically. refresh() must obtain a new one.");
154
+ }
155
+ this.lastProvided = token;
156
+ return token;
157
+ }
158
+ if (this.current && this.current.accessToken !== rejected) {
159
+ return this.current.accessToken;
160
+ }
161
+ this.current = undefined;
162
+ return (await this.obtain()).accessToken;
163
+ }
164
+ needsRefresh(token) {
165
+ const remaining = (token.expiresAt - performance.now()) / 1000;
166
+ return (remaining <= Math.max(token.expiresIn * (1 - REFRESH_AHEAD_RATIO), 0) ||
167
+ remaining <= REFRESH_AHEAD_MIN_SECONDS);
168
+ }
169
+ async obtain() {
170
+ // The second caller joins the first one's request instead of starting another.
171
+ if (this.inFlight)
172
+ return this.inFlight;
173
+ const attempt = this.fetchToken()
174
+ .then((token) => {
175
+ this.current = token;
176
+ return token;
177
+ })
178
+ .finally(() => {
179
+ this.inFlight = undefined;
180
+ });
181
+ this.inFlight = attempt;
182
+ return attempt;
183
+ }
184
+ async fetchToken() {
185
+ const body = new URLSearchParams({
186
+ grant_type: "client_credentials",
187
+ client_id: this.config.clientId,
188
+ client_secret: this.config.clientSecret,
189
+ });
190
+ // Omitted rather than sent empty: an empty `scope` is a request for no scopes at all, while
191
+ // omitting it asks for everything the account holds.
192
+ if (this.config.scope)
193
+ body.set("scope", this.config.scope);
194
+ // Captured before the request so the round trip is charged against the token's life rather than
195
+ // granted as extra margin. performance.now() rather than Date.now() so a system clock adjustment
196
+ // mid-flight cannot move the deadline.
197
+ const issuedAt = performance.now();
198
+ const signal = AbortSignal.timeout(this.config.timeouts.connect + this.config.timeouts.request);
199
+ let response;
200
+ try {
201
+ response = await this.config.fetch(`${this.config.gatewayBaseURL}/oauth2/token`, {
202
+ method: "POST",
203
+ headers: {
204
+ "Content-Type": "application/x-www-form-urlencoded",
205
+ Accept: "application/json",
206
+ "User-Agent": version_ts_1.USER_AGENT,
207
+ },
208
+ body,
209
+ signal,
210
+ });
211
+ }
212
+ catch (cause) {
213
+ throw new errors_ts_1.TransportError(`Could not reach the token endpoint at ${this.config.gatewayBaseURL}/oauth2/token.`, { cause });
214
+ }
215
+ const text = await response.text();
216
+ let parsed;
217
+ try {
218
+ parsed = text ? JSON.parse(text) : undefined;
219
+ }
220
+ catch {
221
+ parsed = undefined;
222
+ }
223
+ const payload = (typeof parsed === "object" && parsed !== null ? parsed : {});
224
+ if (!response.ok)
225
+ throw tokenError(response, payload, text);
226
+ const accessToken = payload["access_token"];
227
+ const expiresIn = payload["expires_in"];
228
+ if (typeof accessToken !== "string" || !accessToken) {
229
+ throw new errors_ts_1.TransportError("The token response carried no access_token.");
230
+ }
231
+ if (typeof expiresIn !== "number" || !(expiresIn > 0)) {
232
+ throw new errors_ts_1.TransportError(`The auth-service returned an unusable expires_in: ${JSON.stringify(expiresIn)}.`);
233
+ }
234
+ // The GRANTED scope, never the requested one. Asking for a subset is honoured, so what came back
235
+ // is what this token can actually do -- and a caller that assumed otherwise would diagnose a 403
236
+ // as a platform fault.
237
+ const granted = payload["scope"];
238
+ const scope = typeof granted === "string" ? granted.split(" ").filter(Boolean) : [];
239
+ const lifetime = Math.min(effectiveLifetime(response.headers, accessToken, expiresIn), MAX_PLAUSIBLE_TTL_SECONDS);
240
+ return {
241
+ accessToken,
242
+ expiresAt: issuedAt + lifetime * 1000,
243
+ expiresIn: lifetime,
244
+ scope,
245
+ };
246
+ }
247
+ }
248
+ exports.TokenManager = TokenManager;
249
+ /**
250
+ * Types a failed token response, which arrives in either of two envelopes.
251
+ *
252
+ * A `4xx` is an RFC 6749 outcome — wrong credentials, a scope the account does not hold — and is
253
+ * never worth retrying. A `5xx` is the gateway failing to do its job and comes back as problem+json,
254
+ * so it maps into the gateway taxonomy where `upstream-unavailable` is retryable.
255
+ */
256
+ function tokenError(response, payload, text) {
257
+ if (response.status >= 500) {
258
+ const type = typeof payload["type"] === "string" ? payload["type"] : "";
259
+ return (0, errors_ts_1.errorFromProblem)({
260
+ status: response.status,
261
+ typeSuffix: type.split("/").pop() ?? "",
262
+ title: typeof payload["title"] === "string" ? payload["title"] : "",
263
+ detail: typeof payload["detail"] === "string"
264
+ ? payload["detail"]
265
+ : `The token endpoint answered ${response.status}.`,
266
+ instance: typeof payload["instance"] === "string" ? payload["instance"] : "",
267
+ retryAfter: undefined,
268
+ meta: {
269
+ // Body first, then the headers. Same fallback as every other error path, and load-bearing for
270
+ // the same reason: these envelopes carry the ids in the body and some non-gateway answers
271
+ // carry them only in the headers.
272
+ requestId: (typeof payload["request_id"] === "string" ? payload["request_id"] : undefined) ??
273
+ response.headers.get("X-Request-ID") ??
274
+ undefined,
275
+ traceId: (typeof payload["trace_id"] === "string" ? payload["trace_id"] : undefined) ??
276
+ response.headers.get("X-Trace-ID") ??
277
+ undefined,
278
+ instance: undefined,
279
+ instanceId: undefined,
280
+ idempotentReplay: false,
281
+ idempotentReplayOf: undefined,
282
+ rateLimit: undefined,
283
+ ignoredParameters: undefined,
284
+ },
285
+ raw: payload,
286
+ });
287
+ }
288
+ const code = typeof payload["error"] === "string" ? payload["error"] : "";
289
+ if (!code) {
290
+ // A 4xx that is not RFC 6749 at all. Said plainly, because the usual cause is that something
291
+ // other than the gateway answered -- a proxy, a login page -- and the status alone misleads.
292
+ return new errors_ts_1.TransportError(`The token endpoint answered ${response.status} without an RFC 6749 error code. ` +
293
+ `Body: ${text.slice(0, 200)}`);
294
+ }
295
+ const description = typeof payload["error_description"] === "string" ? payload["error_description"] : "";
296
+ return (0, errors_ts_1.errorFromOAuth)(response.status, code, description);
297
+ }
298
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"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"}