@nathapp/nax-ai 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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/auth/oauth-policy.d.ts +42 -0
  4. package/dist/auth/oauth-policy.d.ts.map +1 -0
  5. package/dist/auth/oauth-policy.js +61 -0
  6. package/dist/auth/oauth-policy.js.map +1 -0
  7. package/dist/auth/pi-auth.d.ts +12 -0
  8. package/dist/auth/pi-auth.d.ts.map +1 -0
  9. package/dist/auth/pi-auth.js +84 -0
  10. package/dist/auth/pi-auth.js.map +1 -0
  11. package/dist/auth/resolver.d.ts +30 -0
  12. package/dist/auth/resolver.d.ts.map +1 -0
  13. package/dist/auth/resolver.js +10 -0
  14. package/dist/auth/resolver.js.map +1 -0
  15. package/dist/client.d.ts +33 -0
  16. package/dist/client.d.ts.map +1 -0
  17. package/dist/client.js +68 -0
  18. package/dist/client.js.map +1 -0
  19. package/dist/index.d.ts +27 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +23 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/protocols/collect.d.ts +15 -0
  24. package/dist/protocols/collect.d.ts.map +1 -0
  25. package/dist/protocols/collect.js +65 -0
  26. package/dist/protocols/collect.js.map +1 -0
  27. package/dist/protocols/errors.d.ts +30 -0
  28. package/dist/protocols/errors.d.ts.map +1 -0
  29. package/dist/protocols/errors.js +59 -0
  30. package/dist/protocols/errors.js.map +1 -0
  31. package/dist/protocols/pi-client.d.ts +36 -0
  32. package/dist/protocols/pi-client.d.ts.map +1 -0
  33. package/dist/protocols/pi-client.js +348 -0
  34. package/dist/protocols/pi-client.js.map +1 -0
  35. package/dist/protocols/pi-protocols.d.ts +21 -0
  36. package/dist/protocols/pi-protocols.d.ts.map +1 -0
  37. package/dist/protocols/pi-protocols.js +29 -0
  38. package/dist/protocols/pi-protocols.js.map +1 -0
  39. package/dist/protocols/registry.d.ts +36 -0
  40. package/dist/protocols/registry.d.ts.map +1 -0
  41. package/dist/protocols/registry.js +85 -0
  42. package/dist/protocols/registry.js.map +1 -0
  43. package/dist/protocols/retry.d.ts +37 -0
  44. package/dist/protocols/retry.d.ts.map +1 -0
  45. package/dist/protocols/retry.js +96 -0
  46. package/dist/protocols/retry.js.map +1 -0
  47. package/dist/protocols/thinking.d.ts +11 -0
  48. package/dist/protocols/thinking.d.ts.map +1 -0
  49. package/dist/protocols/thinking.js +35 -0
  50. package/dist/protocols/thinking.js.map +1 -0
  51. package/dist/protocols/tool-args.d.ts +27 -0
  52. package/dist/protocols/tool-args.d.ts.map +1 -0
  53. package/dist/protocols/tool-args.js +38 -0
  54. package/dist/protocols/tool-args.js.map +1 -0
  55. package/dist/protocols/types.d.ts +142 -0
  56. package/dist/protocols/types.d.ts.map +1 -0
  57. package/dist/protocols/types.js +33 -0
  58. package/dist/protocols/types.js.map +1 -0
  59. package/dist/providers/catalog.d.ts +37 -0
  60. package/dist/providers/catalog.d.ts.map +1 -0
  61. package/dist/providers/catalog.js +72 -0
  62. package/dist/providers/catalog.js.map +1 -0
  63. package/dist/providers/pi-catalog.d.ts +14 -0
  64. package/dist/providers/pi-catalog.d.ts.map +1 -0
  65. package/dist/providers/pi-catalog.js +91 -0
  66. package/dist/providers/pi-catalog.js.map +1 -0
  67. package/dist/providers/types.d.ts +79 -0
  68. package/dist/providers/types.d.ts.map +1 -0
  69. package/dist/providers/types.js +10 -0
  70. package/dist/providers/types.js.map +1 -0
  71. package/dist/types.d.ts +94 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +10 -0
  74. package/dist/types.js.map +1 -0
  75. package/dist/usage.d.ts +39 -0
  76. package/dist/usage.d.ts.map +1 -0
  77. package/dist/usage.js +40 -0
  78. package/dist/usage.js.map +1 -0
  79. package/package.json +68 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 William Khoo
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.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @nathapp/nax-ai
2
+
3
+ Provider-agnostic LLM client: completions, streaming, tool calls, usage accounting and auth across API-key and OAuth providers.
4
+
5
+ > **Pre-1.0 — the API is unstable and will change without deprecation cycles.**
6
+ > Published under the `next` dist-tag. Pin an exact version; do not use a caret range.
7
+ >
8
+ > ```
9
+ > npm install @nathapp/nax-ai@next
10
+ > ```
11
+
12
+ ## Where to start
13
+
14
+ New to this repository, or picking the work up cold? **[`ROADMAP.md`](ROADMAP.md)** records the current milestone, what is next, and links to the design spec and the feasibility analysis behind it.
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import { createClient, piProtocols, piProviders } from "@nathapp/nax-ai";
20
+
21
+ const client = createClient({
22
+ providers: await piProviders(["deepseek", "anthropic"]),
23
+ protocols: piProtocols(),
24
+ });
25
+
26
+ const model = await client.model("deepseek", "deepseek-v4-flash");
27
+ const result = await client.complete(model, { messages: [{ role: "user", content: "hi" }] });
28
+ ```
29
+
30
+ Install from the `next` dist-tag while the API is unstable: `npm install @nathapp/nax-ai@next`.
31
+
32
+ ## Scope
33
+
34
+ This package speaks a generic LLM vocabulary — models, messages, tool calls, usage, credentials. It knows nothing about any consumer's domain concepts, and that direction is one-way by design: consumers map onto their own types at their own boundary.
35
+
36
+ That constraint is what lets the implementation underneath this surface be replaced, provider by provider, without consumers noticing. It is also why domain-specific abstractions do not belong here, however convenient they would be for the first consumer.
37
+
38
+ ## Requirements
39
+
40
+ - **Node >= 22.19** — the compatibility target. Runs unmodified on Bun and Deno.
41
+ - ESM only. There is no CommonJS build.
42
+
43
+ The package must not use runtime-specific APIs. `Bun.*` and `bun:` imports are rejected by a build gate (`scripts/check-no-bun-apis.ts`) because the primary consumer runs on Bun, so nothing would fail there — the breakage would surface only for someone else, on install.
44
+
45
+ ## OAuth policy
46
+
47
+ OAuth flows are governed by an explicit allowlist in `src/auth/oauth-policy.ts`, enforced by tests rather than convention.
48
+
49
+ **Anthropic subscription OAuth is prohibited and must never be added.** Using Pro/Max tokens outside the official Claude CLI is server-blocked and a Consumer ToS violation. This is not a broken path awaiting repair — route Claude subscription traffic through the official CLI instead.
50
+
51
+ The underlying client bundles Anthropic's flow beside permitted ones behind a shared lazy loader, which is precisely why the prohibition is a gate: "we simply won't call it" is not enforceable, and the environment that would notice the mistake is not the one running the tests.
52
+
53
+ ## Development
54
+
55
+ ```bash
56
+ bun install
57
+ bun run test # vitest
58
+ bun run typecheck # tsc --noEmit
59
+ bun run lint # biome + no-bun-apis gate
60
+ bun run build # tsc -p tsconfig.build.json → dist/
61
+ ```
62
+
63
+ **Vitest does not type-check.** It transpiles via esbuild, which strips types without verifying them, so a green suite proves nothing about compilation. `test` and `typecheck` cover disjoint ground and CI runs both.
64
+
65
+ Tests run on Node (the compatibility target) and the built package is smoke-tested on Bun (the primary consumer's runtime). Testing only on Bun would hide exactly the class of breakage the Node target exists to prevent.
66
+
67
+ ## Licence
68
+
69
+ MIT
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Which OAuth flows this package is permitted to register.
3
+ *
4
+ * The underlying client (pi-ai) bundles OAuth flows for several providers
5
+ * behind a single lazy loader, including Anthropic's. Reaching the Anthropic
6
+ * flow is therefore only ever one call away from reaching a permitted one,
7
+ * which is why this policy is an explicit allowlist enforced by a gate rather
8
+ * than a convention maintained by review.
9
+ *
10
+ * Anthropic subscription OAuth (Pro/Max tokens outside the official CLI) is
11
+ * both server-blocked and a Consumer ToS violation. It is not "broken and may
12
+ * come back" — it is prohibited, and must never be registered here regardless
13
+ * of whether it happens to work on a given day. Subscription traffic to Claude
14
+ * belongs on a path that uses the official CLI, not on this client.
15
+ */
16
+ import type { ProviderId } from "../types.ts";
17
+ /**
18
+ * OAuth flows this package may register.
19
+ *
20
+ * Additions are a policy decision, not a routine change: each entry is a
21
+ * provider whose terms permit third-party OAuth use, or whose flow is a
22
+ * first-party developer credential rather than a consumer subscription.
23
+ */
24
+ export declare const PERMITTED_OAUTH_FLOWS: readonly ProviderId[];
25
+ /**
26
+ * OAuth flows that must never be registered, with the reason recorded so a
27
+ * future reader does not "fix" the omission.
28
+ */
29
+ export declare const PROHIBITED_OAUTH_FLOWS: Readonly<Record<ProviderId, string>>;
30
+ export declare class OAuthFlowProhibitedError extends Error {
31
+ readonly providerId: ProviderId;
32
+ constructor(providerId: ProviderId, reason: string);
33
+ }
34
+ export declare function isOAuthFlowPermitted(providerId: ProviderId): boolean;
35
+ /**
36
+ * Throws if `providerId` names a flow this package must not register.
37
+ *
38
+ * Prohibited flows raise a specific error; merely-unknown flows raise a
39
+ * generic one, so a typo is distinguishable from a policy breach.
40
+ */
41
+ export declare function assertOAuthFlowPermitted(providerId: ProviderId): void;
42
+ //# sourceMappingURL=oauth-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-policy.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,UAAU,EAIrD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAGtE,CAAC;AAEH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAEhC,YAAY,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAIjD;CACF;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAEpE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAQrE"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Which OAuth flows this package is permitted to register.
3
+ *
4
+ * The underlying client (pi-ai) bundles OAuth flows for several providers
5
+ * behind a single lazy loader, including Anthropic's. Reaching the Anthropic
6
+ * flow is therefore only ever one call away from reaching a permitted one,
7
+ * which is why this policy is an explicit allowlist enforced by a gate rather
8
+ * than a convention maintained by review.
9
+ *
10
+ * Anthropic subscription OAuth (Pro/Max tokens outside the official CLI) is
11
+ * both server-blocked and a Consumer ToS violation. It is not "broken and may
12
+ * come back" — it is prohibited, and must never be registered here regardless
13
+ * of whether it happens to work on a given day. Subscription traffic to Claude
14
+ * belongs on a path that uses the official CLI, not on this client.
15
+ */
16
+ /**
17
+ * OAuth flows this package may register.
18
+ *
19
+ * Additions are a policy decision, not a routine change: each entry is a
20
+ * provider whose terms permit third-party OAuth use, or whose flow is a
21
+ * first-party developer credential rather than a consumer subscription.
22
+ */
23
+ export const PERMITTED_OAUTH_FLOWS = Object.freeze([
24
+ "openai-codex",
25
+ "github-copilot",
26
+ "openrouter",
27
+ ]);
28
+ /**
29
+ * OAuth flows that must never be registered, with the reason recorded so a
30
+ * future reader does not "fix" the omission.
31
+ */
32
+ export const PROHIBITED_OAUTH_FLOWS = Object.freeze({
33
+ anthropic: "Anthropic subscription OAuth outside the official Claude CLI is server-blocked and a Consumer ToS violation. Route Claude subscription traffic through the official CLI instead.",
34
+ });
35
+ export class OAuthFlowProhibitedError extends Error {
36
+ providerId;
37
+ constructor(providerId, reason) {
38
+ super(`OAuth flow for "${providerId}" is prohibited: ${reason}`);
39
+ this.name = "OAuthFlowProhibitedError";
40
+ this.providerId = providerId;
41
+ }
42
+ }
43
+ export function isOAuthFlowPermitted(providerId) {
44
+ return PERMITTED_OAUTH_FLOWS.includes(providerId);
45
+ }
46
+ /**
47
+ * Throws if `providerId` names a flow this package must not register.
48
+ *
49
+ * Prohibited flows raise a specific error; merely-unknown flows raise a
50
+ * generic one, so a typo is distinguishable from a policy breach.
51
+ */
52
+ export function assertOAuthFlowPermitted(providerId) {
53
+ const prohibitedReason = PROHIBITED_OAUTH_FLOWS[providerId];
54
+ if (prohibitedReason !== undefined) {
55
+ throw new OAuthFlowProhibitedError(providerId, prohibitedReason);
56
+ }
57
+ if (!isOAuthFlowPermitted(providerId)) {
58
+ throw new Error(`Unknown OAuth flow "${providerId}". Permitted flows: ${PERMITTED_OAUTH_FLOWS.join(", ")}.`);
59
+ }
60
+ }
61
+ //# sourceMappingURL=oauth-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-policy.js","sourceRoot":"","sources":["../../src/auth/oauth-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAA0B,MAAM,CAAC,MAAM,CAAC;IACxE,cAAc;IACd,gBAAgB;IAChB,YAAY;CACb,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAyC,MAAM,CAAC,MAAM,CAAC;IACxF,SAAS,EACP,kLAAkL;CACrL,CAAC,CAAC;AAEH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACxC,UAAU,CAAa;IAEhC,YAAY,UAAsB,EAAE,MAAc;QAChD,KAAK,CAAC,mBAAmB,UAAU,oBAAoB,MAAM,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,UAAU,oBAAoB,CAAC,UAAsB;IACzD,OAAO,qBAAqB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAsB;IAC7D,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IAC5D,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,IAAI,wBAAwB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,uBAAuB,UAAU,uBAAuB,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/G,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * pi-ai's side of auth: a credential-store adapter and an AuthResolver.
3
+ *
4
+ * On the ALLOWED list in scripts/check-pi-ai-imports.ts. Nothing here leaks a
5
+ * pi-ai type through an export a consumer sees.
6
+ */
7
+ import type { MutableModels, CredentialStore as PiCredentialStore } from "@earendil-works/pi-ai";
8
+ import type { CredentialStore } from "../types.ts";
9
+ import type { AuthResolver } from "./resolver.ts";
10
+ export declare function toPiCredentialStore(store: CredentialStore): PiCredentialStore;
11
+ export declare function createPiAuthResolver(models: MutableModels): AuthResolver;
12
+ //# sourceMappingURL=pi-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi-auth.d.ts","sourceRoot":"","sources":["../../src/auth/pi-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAc,aAAa,EAAE,eAAe,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC7G,OAAO,KAAK,EAAE,eAAe,EAA8B,MAAM,aAAa,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,eAAe,CAAC;AAoChE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,iBAAiB,CA8B7E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,CAkBxE"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * pi-ai's side of auth: a credential-store adapter and an AuthResolver.
3
+ *
4
+ * On the ALLOWED list in scripts/check-pi-ai-imports.ts. Nothing here leaks a
5
+ * pi-ai type through an export a consumer sees.
6
+ */
7
+ function toPi(credential) {
8
+ if (credential === undefined)
9
+ return undefined;
10
+ if (credential.kind === "api-key") {
11
+ return {
12
+ type: "api_key",
13
+ key: credential.key,
14
+ ...(credential.env !== undefined ? { env: { ...credential.env } } : {}),
15
+ };
16
+ }
17
+ return {
18
+ type: "oauth",
19
+ access: credential.access,
20
+ refresh: credential.refresh,
21
+ expires: credential.expires,
22
+ };
23
+ }
24
+ function fromPi(credential) {
25
+ if (credential === undefined)
26
+ return undefined;
27
+ if (credential.type === "api_key") {
28
+ return {
29
+ kind: "api-key",
30
+ key: credential.key ?? "",
31
+ ...(credential.env !== undefined ? { env: { ...credential.env } } : {}),
32
+ };
33
+ }
34
+ return {
35
+ kind: "oauth",
36
+ access: credential.access,
37
+ refresh: credential.refresh,
38
+ expires: credential.expires,
39
+ };
40
+ }
41
+ export function toPiCredentialStore(store) {
42
+ return {
43
+ read: async (providerId) => toPi(await store.read(providerId)),
44
+ // modify stays a single read-modify-write so the underlying store can hold
45
+ // a lock across the whole operation. pi-ai runs OAuth refresh inside it.
46
+ modify: async (providerId, fn) => toPi(await store.modify(providerId, async (current) => {
47
+ const next = await fn(toPi(current));
48
+ // pi's modify contract treats an undefined return as "leave the
49
+ // entry unchanged" — its OAuth refresh returns that when a
50
+ // concurrent caller already rotated the token. nax-ai's undefined
51
+ // means "remove", so the no-op is translated to the current
52
+ // credential, never propagated; removal goes through delete().
53
+ return next === undefined ? current : fromPi(next);
54
+ })),
55
+ delete: async (providerId) => {
56
+ await store.delete(providerId);
57
+ },
58
+ // pi-ai requires `list` for account and status enumeration. nax-ai's
59
+ // CredentialStore has no equivalent and nax-ai never enumerates accounts:
60
+ // that belongs to login and logout, which are out of scope for M2. An
61
+ // empty list is what "this store does not enumerate" looks like, and it is
62
+ // only ever read by UI that nax-ai does not have.
63
+ list: async () => [],
64
+ };
65
+ }
66
+ export function createPiAuthResolver(models) {
67
+ return {
68
+ async resolve(ref) {
69
+ const result = await models.getAuth(ref.provider);
70
+ const auth = result?.auth;
71
+ if (auth === undefined)
72
+ return {};
73
+ return {
74
+ ...(auth.apiKey !== undefined ? { apiKey: auth.apiKey } : {}),
75
+ ...(auth.headers !== undefined
76
+ ? {
77
+ headers: Object.fromEntries(Object.entries(auth.headers).filter((entry) => entry[1] !== null)),
78
+ }
79
+ : {}),
80
+ };
81
+ },
82
+ };
83
+ }
84
+ //# sourceMappingURL=pi-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi-auth.js","sourceRoot":"","sources":["../../src/auth/pi-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The auth port.
3
+ *
4
+ * pi-ai resolves credentials today, but a hand-written backend will need the
5
+ * same thing and must not import pi-ai to get it. Keeping the port here, in
6
+ * nax-ai's own vocabulary, is what stops "delete the pi backend" from also
7
+ * deleting credential handling.
8
+ */
9
+ import type { ModelRef } from "../types.ts";
10
+ /**
11
+ * Request auth for one call.
12
+ *
13
+ * There is deliberately no `baseUrl`: it belongs to the model, and the
14
+ * upstream request options have no per-request slot for it.
15
+ */
16
+ export interface ResolvedAuth {
17
+ readonly apiKey?: string;
18
+ readonly headers?: Readonly<Record<string, string>>;
19
+ }
20
+ export interface AuthResolver {
21
+ /**
22
+ * Refreshes an expired OAuth token as a side effect, under the store lock.
23
+ *
24
+ * Takes a `ModelRef` rather than a `ResolvedModel` so the pi protocol, which
25
+ * holds a pi-ai model, can call this without first round-tripping through
26
+ * the catalog.
27
+ */
28
+ resolve(ref: ModelRef): Promise<ResolvedAuth>;
29
+ }
30
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/auth/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC/C"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The auth port.
3
+ *
4
+ * pi-ai resolves credentials today, but a hand-written backend will need the
5
+ * same thing and must not import pi-ai to get it. Keeping the port here, in
6
+ * nax-ai's own vocabulary, is what stops "delete the pi backend" from also
7
+ * deleting credential handling.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/auth/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The client: the only thing a consumer constructs.
3
+ *
4
+ * It selects a protocol from `ResolvedModel.protocol`, so a consumer never
5
+ * names one. That indirection is what makes replacing a protocol's backend
6
+ * invisible to callers.
7
+ */
8
+ import { type BackendSelection, type ProtocolEntries } from "./protocols/registry.ts";
9
+ import type { ProtocolEvent, ProtocolRequest } from "./protocols/types.ts";
10
+ import { type RawProvider } from "./providers/catalog.ts";
11
+ import type { Pricing, ProviderOverride, ResolvedModel } from "./providers/types.ts";
12
+ import type { CompleteResult, CredentialStore } from "./types.ts";
13
+ export interface ClientOptions {
14
+ readonly providers: readonly RawProvider[];
15
+ readonly protocols: ProtocolEntries;
16
+ readonly backends?: BackendSelection;
17
+ readonly credentials?: CredentialStore;
18
+ readonly providerOverrides?: readonly ProviderOverride[];
19
+ /** Transport-fault retries before the first event. Default 2; 0 disables. */
20
+ readonly transportRetries?: number;
21
+ }
22
+ /** Everything on ProtocolRequest except `model`, which the client supplies. */
23
+ export type ClientRequest = Omit<ProtocolRequest, "model">;
24
+ export interface Client {
25
+ model(provider: string, model: string): Promise<ResolvedModel>;
26
+ listModels(provider?: string): Promise<readonly ResolvedModel[]>;
27
+ pricing(model: ResolvedModel): Pricing;
28
+ stream(model: ResolvedModel, req: ClientRequest): AsyncIterable<ProtocolEvent>;
29
+ complete(model: ResolvedModel, req: ClientRequest): Promise<CompleteResult>;
30
+ validate(): void;
31
+ }
32
+ export declare function createClient(options: ClientOptions): Client;
33
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,gBAAgB,EAAkB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAGtG,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,WAAW,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,eAAe,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACzD,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAE3D,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/D,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;IACjE,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;IACvC,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,aAAa,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IAC/E,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC5E,QAAQ,IAAI,IAAI,CAAC;CAClB;AASD,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA2D3D"}
package/dist/client.js ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The client: the only thing a consumer constructs.
3
+ *
4
+ * It selects a protocol from `ResolvedModel.protocol`, so a consumer never
5
+ * names one. That indirection is what makes replacing a protocol's backend
6
+ * invisible to callers.
7
+ */
8
+ import { collectStream } from "./protocols/collect.js";
9
+ import { createRegistry } from "./protocols/registry.js";
10
+ import { retryTransportFaults } from "./protocols/retry.js";
11
+ import { clampThinkingLevel } from "./protocols/thinking.js";
12
+ import { normaliseCatalog } from "./providers/catalog.js";
13
+ /** Matches the doc comment on `ClientOptions.transportRetries`. */
14
+ const DEFAULT_TRANSPORT_RETRIES = 2;
15
+ function realSleep(ms) {
16
+ return new Promise((resolve) => setTimeout(resolve, ms));
17
+ }
18
+ export function createClient(options) {
19
+ const catalog = normaliseCatalog(options.providers, options.providerOverrides ?? []);
20
+ const registry = createRegistry(options.protocols, options.backends ?? {});
21
+ // A negative value is a config bug, not a policy choice — clamping it to 0
22
+ // would silently disable retry instead of telling the caller their option
23
+ // is nonsense. Checked here, once, rather than per stream() call.
24
+ const transportRetries = options.transportRetries ?? DEFAULT_TRANSPORT_RETRIES;
25
+ if (transportRetries < 0) {
26
+ throw new Error(`transportRetries must be >= 0, got ${transportRetries}.`);
27
+ }
28
+ async function* streamFrom(model, req) {
29
+ const protocol = await registry.resolve(model.protocol);
30
+ const thinking = req.thinking !== undefined ? clampThinkingLevel(req.thinking, model.thinkingLevels) : undefined;
31
+ const protocolRequest = {
32
+ ...req,
33
+ model: model.id,
34
+ provider: model.provider,
35
+ ...(thinking !== undefined ? { thinking } : {}),
36
+ };
37
+ yield* retryTransportFaults(() => protocol.stream(protocolRequest), {
38
+ retries: transportRetries,
39
+ sleep: realSleep,
40
+ ...(req.signal !== undefined ? { signal: req.signal } : {}),
41
+ });
42
+ }
43
+ return {
44
+ async model(provider, model) {
45
+ const resolved = catalog.model(provider, model);
46
+ if (resolved === undefined) {
47
+ throw new Error(`Unknown model "${model}" for provider "${provider}".`);
48
+ }
49
+ return resolved;
50
+ },
51
+ async listModels(provider) {
52
+ return catalog.listModels(provider);
53
+ },
54
+ pricing(model) {
55
+ return model.pricing;
56
+ },
57
+ stream(model, req) {
58
+ return streamFrom(model, req);
59
+ },
60
+ complete(model, req) {
61
+ return collectStream(streamFrom(model, req));
62
+ },
63
+ validate() {
64
+ registry.validate();
65
+ },
66
+ };
67
+ }
68
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @nathapp/nax-ai — provider-agnostic LLM client.
3
+ *
4
+ * Status: pre-1.0, API unstable. Published under the `next` dist-tag.
5
+ *
6
+ * Scope boundary: this package speaks a generic LLM vocabulary — models,
7
+ * messages, tool calls, usage, credentials. It deliberately knows nothing
8
+ * about any consumer's domain concepts (stories, operations, sessions,
9
+ * permission policy). Consumers map onto their own types at their boundary.
10
+ * Keeping that direction one-way is what allows the implementation beneath
11
+ * this surface to be replaced — provider by provider — without consumers
12
+ * noticing.
13
+ */
14
+ export { assertOAuthFlowPermitted, isOAuthFlowPermitted, OAuthFlowProhibitedError, PERMITTED_OAUTH_FLOWS, PROHIBITED_OAUTH_FLOWS, } from "./auth/oauth-policy.ts";
15
+ export type { AuthResolver, ResolvedAuth } from "./auth/resolver.ts";
16
+ export { type Client, type ClientOptions, type ClientRequest, createClient } from "./client.ts";
17
+ export { collectStream, ProtocolStreamError } from "./protocols/collect.ts";
18
+ export { PI_PROTOCOL_NAMES, type PiProtocolName, type PiProtocolOptions, piProtocols, } from "./protocols/pi-protocols.ts";
19
+ export { type BackendId, type BackendSelection, createRegistry, type ProtocolBackends, type ProtocolEntries, UnknownProtocolError, UnregisteredBackendError, } from "./protocols/registry.ts";
20
+ export { clampThinkingLevel } from "./protocols/thinking.ts";
21
+ export type { CacheRetention, ConversationMessage, JsonSchema, Protocol, ProtocolError, ProtocolEvent, ProtocolRequest, ThinkingBlock, ThinkingLevel, ToolCall, ToolDefinition, } from "./protocols/types.ts";
22
+ export { type Catalog, normaliseCatalog, type RawModel, type RawProvider } from "./providers/catalog.ts";
23
+ export { piProviders } from "./providers/pi-catalog.ts";
24
+ export type { Pricing, PricingRates, PricingTier, ProviderAuth, ProviderOverride, ResolvedModel, ResolvedProvider, } from "./providers/types.ts";
25
+ export type { CompleteOptions, CompleteResult, CredentialStore, Message, MessageRole, ModelRef, ProviderId, StopReason, StoredCredential, TokenUsage, } from "./types.ts";
26
+ export { toTokenUsage, totalTokens, type UpstreamUsage } from "./usage.ts";
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EACL,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,WAAW,GACZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,UAAU,EACV,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,aAAa,EACb,aAAa,EACb,QAAQ,EACR,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,OAAO,EAAE,gBAAgB,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,YAAY,EACV,OAAO,EACP,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,eAAe,EACf,cAAc,EACd,eAAe,EACf,OAAO,EACP,WAAW,EACX,QAAQ,EACR,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @nathapp/nax-ai — provider-agnostic LLM client.
3
+ *
4
+ * Status: pre-1.0, API unstable. Published under the `next` dist-tag.
5
+ *
6
+ * Scope boundary: this package speaks a generic LLM vocabulary — models,
7
+ * messages, tool calls, usage, credentials. It deliberately knows nothing
8
+ * about any consumer's domain concepts (stories, operations, sessions,
9
+ * permission policy). Consumers map onto their own types at their boundary.
10
+ * Keeping that direction one-way is what allows the implementation beneath
11
+ * this surface to be replaced — provider by provider — without consumers
12
+ * noticing.
13
+ */
14
+ export { assertOAuthFlowPermitted, isOAuthFlowPermitted, OAuthFlowProhibitedError, PERMITTED_OAUTH_FLOWS, PROHIBITED_OAUTH_FLOWS, } from "./auth/oauth-policy.js";
15
+ export { createClient } from "./client.js";
16
+ export { collectStream, ProtocolStreamError } from "./protocols/collect.js";
17
+ export { PI_PROTOCOL_NAMES, piProtocols, } from "./protocols/pi-protocols.js";
18
+ export { createRegistry, UnknownProtocolError, UnregisteredBackendError, } from "./protocols/registry.js";
19
+ export { clampThinkingLevel } from "./protocols/thinking.js";
20
+ export { normaliseCatalog } from "./providers/catalog.js";
21
+ export { piProviders } from "./providers/pi-catalog.js";
22
+ export { toTokenUsage, totalTokens } from "./usage.js";
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAuD,YAAY,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EACL,iBAAiB,EAGjB,WAAW,GACZ,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGL,cAAc,EAGd,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAc7D,OAAO,EAAgB,gBAAgB,EAAmC,MAAM,wBAAwB,CAAC;AACzG,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAsBxD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAsB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Derives a single result from a protocol stream.
3
+ *
4
+ * Implemented once, here, rather than per protocol — that is why `Protocol`
5
+ * exposes only `stream`. A backend cannot let a request/response path drift
6
+ * from its streaming path if it never writes one.
7
+ */
8
+ import type { CompleteResult } from "../types.ts";
9
+ import type { ProtocolError, ProtocolEvent } from "./types.ts";
10
+ export declare class ProtocolStreamError extends Error {
11
+ readonly protocolError: ProtocolError;
12
+ constructor(protocolError: ProtocolError);
13
+ }
14
+ export declare function collectStream(events: AsyncIterable<ProtocolEvent>): Promise<CompleteResult>;
15
+ //# sourceMappingURL=collect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/protocols/collect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAA2B,MAAM,YAAY,CAAC;AAIxF,qBAAa,mBAAoB,SAAQ,KAAK;IAChC,QAAQ,CAAC,aAAa,EAAE,aAAa;IAAjD,YAAqB,aAAa,EAAE,aAAa,EAIhD;CACF;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAgDjG"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Derives a single result from a protocol stream.
3
+ *
4
+ * Implemented once, here, rather than per protocol — that is why `Protocol`
5
+ * exposes only `stream`. A backend cannot let a request/response path drift
6
+ * from its streaming path if it never writes one.
7
+ */
8
+ const EMPTY_USAGE = { inputTokens: 0, outputTokens: 0 };
9
+ export class ProtocolStreamError extends Error {
10
+ protocolError;
11
+ constructor(protocolError) {
12
+ super(protocolError.message);
13
+ this.protocolError = protocolError;
14
+ this.name = "ProtocolStreamError";
15
+ if (protocolError.cause !== undefined)
16
+ this.cause = protocolError.cause;
17
+ }
18
+ }
19
+ export async function collectStream(events) {
20
+ const text = [];
21
+ const toolCalls = [];
22
+ const thinking = [];
23
+ let usage;
24
+ let stopReason;
25
+ for await (const event of events) {
26
+ switch (event.type) {
27
+ case "text-delta":
28
+ text.push(event.text);
29
+ break;
30
+ case "tool-call":
31
+ toolCalls.push(event.call);
32
+ break;
33
+ case "thinking":
34
+ // Without this, complete() has no way to hand a caller the block
35
+ // needed to replay the next turn — the whole point of the fix.
36
+ thinking.push(event.block);
37
+ break;
38
+ case "usage":
39
+ usage = event.usage;
40
+ break;
41
+ case "error":
42
+ throw new ProtocolStreamError(event.error);
43
+ case "done":
44
+ stopReason = event.stopReason;
45
+ break;
46
+ // Thinking text is not part of the answer, and a partial tool call is
47
+ // superseded by the "tool-call" event that follows it.
48
+ case "thinking-delta":
49
+ case "tool-call-partial":
50
+ break;
51
+ }
52
+ }
53
+ if (stopReason === undefined) {
54
+ throw new Error("Protocol stream ended without a done event — the response was truncated.");
55
+ }
56
+ const result = {
57
+ text: text.join(""),
58
+ usage: usage ?? EMPTY_USAGE,
59
+ stopReason,
60
+ ...(toolCalls.length > 0 ? { toolCalls } : {}),
61
+ ...(thinking.length > 0 ? { thinking } : {}),
62
+ };
63
+ return result;
64
+ }
65
+ //# sourceMappingURL=collect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.js","sourceRoot":"","sources":["../../src/protocols/collect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,WAAW,GAAe,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;AAEpE,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACvB,aAAa;IAAlC,YAAqB,aAA4B;QAC/C,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;6BADV,aAAa;QAEhC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,aAAa,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC;IAC1E,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAoC;IACtE,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,IAAI,KAA6B,CAAC;IAClC,IAAI,UAAoD,CAAC;IAEzD,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,YAAY;gBACf,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACtB,MAAM;YACR,KAAK,WAAW;gBACd,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC3B,MAAM;YACR,KAAK,UAAU;gBACb,iEAAiE;gBACjE,+DAA+D;gBAC/D,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC3B,MAAM;YACR,KAAK,OAAO;gBACV,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;gBACpB,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,IAAI,mBAAmB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC7C,KAAK,MAAM;gBACT,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;gBAC9B,MAAM;YACR,sEAAsE;YACtE,uDAAuD;YACvD,KAAK,gBAAgB,CAAC;YACtB,KAAK,mBAAmB;gBACtB,MAAM;QACV,CAAC;IACH,CAAC;IAED,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,MAAM,GAAmB;QAC7B,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,KAAK,EAAE,KAAK,IAAI,WAAW;QAC3B,UAAU;QACV,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7C,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC"}