@xandreed/plugin-models 0.13.0 → 0.15.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 (151) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/anthropicOAuth.d.ts +45 -11
  3. package/dist/auth/anthropicOAuth.js +23 -23
  4. package/dist/auth/auth-store.conformance.d.ts +63 -0
  5. package/dist/auth/auth-store.conformance.js +152 -0
  6. package/dist/auth/credential-input.entity.d.ts +52 -0
  7. package/dist/auth/credential-input.entity.functions.d.ts +18 -0
  8. package/dist/auth/credential-input.entity.functions.js +12 -0
  9. package/dist/auth/credential-input.entity.js +22 -0
  10. package/dist/auth/credential-refresh.entity.d.ts +17 -0
  11. package/dist/auth/credential-refresh.entity.functions.d.ts +57 -0
  12. package/dist/auth/credential-refresh.entity.functions.js +21 -0
  13. package/dist/auth/credential-refresh.entity.js +2 -0
  14. package/dist/auth/localAuth.d.ts +12 -2
  15. package/dist/auth/localAuth.js +58 -86
  16. package/dist/auth/memory-auth.adapter.d.ts +35 -0
  17. package/dist/auth/memory-auth.adapter.js +35 -0
  18. package/dist/auth/openAiCodexOAuth.d.ts +28 -6
  19. package/dist/auth/openAiCodexOAuth.js +22 -32
  20. package/dist/auth/token-response.entity.d.ts +21 -0
  21. package/dist/auth/token-response.entity.functions.d.ts +20 -0
  22. package/dist/auth/token-response.entity.functions.js +10 -0
  23. package/dist/auth/token-response.entity.js +10 -0
  24. package/dist/compat/index.d.ts +2 -0
  25. package/dist/compat/index.js +1 -0
  26. package/dist/conformance/index.d.ts +6 -0
  27. package/dist/conformance/index.js +3 -0
  28. package/dist/index.d.ts +30 -13
  29. package/dist/index.js +25 -50
  30. package/dist/llm/chat-wire.entity.d.ts +123 -0
  31. package/dist/llm/chat-wire.entity.functions.d.ts +28 -0
  32. package/dist/llm/chat-wire.entity.functions.js +10 -0
  33. package/dist/llm/chat-wire.entity.js +37 -0
  34. package/dist/llm/compat-telemetry.adapter.d.ts +6 -0
  35. package/dist/llm/compat-telemetry.adapter.js +21 -0
  36. package/dist/llm/compat.d.ts +84 -33
  37. package/dist/llm/compat.js +188 -121
  38. package/dist/llm/model-completion.adapter.d.ts +49 -0
  39. package/dist/llm/model-completion.adapter.js +77 -0
  40. package/dist/llm/model-completion.entity.d.ts +4 -0
  41. package/dist/llm/model-completion.entity.functions.d.ts +3 -0
  42. package/dist/llm/model-completion.entity.functions.js +7 -0
  43. package/dist/llm/model-completion.entity.js +7 -0
  44. package/dist/llm/model-retry-policy.entity.d.ts +37 -0
  45. package/dist/llm/model-retry-policy.entity.functions.d.ts +16 -0
  46. package/dist/llm/model-retry-policy.entity.functions.js +33 -0
  47. package/dist/llm/model-retry-policy.entity.js +23 -0
  48. package/dist/llm/model-settings.entity.d.ts +85 -0
  49. package/dist/llm/model-settings.entity.functions.d.ts +11 -0
  50. package/dist/llm/model-settings.entity.functions.js +49 -0
  51. package/dist/llm/model-settings.entity.js +28 -0
  52. package/dist/llm/model-usage.entity.d.ts +61 -0
  53. package/dist/llm/model-usage.entity.functions.d.ts +11 -0
  54. package/dist/llm/model-usage.entity.functions.js +24 -0
  55. package/dist/llm/model-usage.entity.js +19 -0
  56. package/dist/llm/modelCatalog.d.ts +47 -7
  57. package/dist/llm/modelCatalog.js +31 -57
  58. package/dist/llm/openAiCodex.d.ts +18 -7
  59. package/dist/llm/openAiCodex.js +57 -45
  60. package/dist/llm/openAiCodexWebSocket.d.ts +60 -7
  61. package/dist/llm/openAiCodexWebSocket.js +113 -105
  62. package/dist/llm/prompt-cache-frame.entity.d.ts +18 -0
  63. package/dist/llm/prompt-cache-frame.entity.js +10 -0
  64. package/dist/llm/prompt-cache.entity.d.ts +57 -0
  65. package/dist/llm/prompt-cache.entity.functions.d.ts +8 -0
  66. package/dist/llm/prompt-cache.entity.functions.js +73 -0
  67. package/dist/llm/prompt-cache.entity.js +27 -0
  68. package/dist/llm/provider-client.adapter.d.ts +9 -0
  69. package/dist/llm/provider-client.adapter.js +60 -0
  70. package/dist/llm/provider-failure.entity.d.ts +42 -0
  71. package/dist/llm/provider-failure.entity.functions.d.ts +8 -0
  72. package/dist/llm/provider-failure.entity.functions.js +67 -0
  73. package/dist/llm/provider-failure.entity.js +20 -0
  74. package/dist/llm/provider-profile.entity.d.ts +79 -0
  75. package/dist/llm/provider-profile.entity.functions.d.ts +23 -0
  76. package/dist/llm/provider-profile.entity.functions.js +83 -0
  77. package/dist/llm/provider-profile.entity.js +10 -0
  78. package/dist/llm/providers.d.ts +125 -29
  79. package/dist/llm/providers.js +311 -117
  80. package/dist/llm/retry.d.ts +11 -47
  81. package/dist/llm/retry.js +33 -52
  82. package/dist/llm/router.d.ts +56 -28
  83. package/dist/llm/router.js +125 -88
  84. package/dist/llm/sse.d.ts +58 -29
  85. package/dist/llm/sse.js +47 -39
  86. package/dist/llm/thinking-binding.entity.d.ts +49 -0
  87. package/dist/llm/thinking-binding.entity.functions.d.ts +21 -0
  88. package/dist/llm/thinking-binding.entity.functions.js +16 -0
  89. package/dist/llm/thinking-binding.entity.js +11 -0
  90. package/dist/llm/utilityLlm.d.ts +14 -5
  91. package/dist/llm/utilityLlm.js +18 -13
  92. package/dist/plugin.adapter.d.ts +154 -6
  93. package/dist/plugin.adapter.js +53 -10
  94. package/dist/ports/auth-store.port.d.ts +73 -0
  95. package/dist/ports/auth-store.port.js +24 -0
  96. package/dist/ports/host-credentials.port.d.ts +22 -0
  97. package/dist/ports/host-credentials.port.js +4 -0
  98. package/dist/ports/model-call-completion.port.d.ts +9 -0
  99. package/dist/ports/model-call-completion.port.js +2 -0
  100. package/dist/ports/model-endpoints.port.d.ts +66 -19
  101. package/dist/ports/model-endpoints.port.js +22 -4
  102. package/dist/ports/model-retry-policy.port.d.ts +17 -0
  103. package/dist/ports/model-retry-policy.port.js +3 -0
  104. package/dist/ports/model-settings.port.d.ts +32 -0
  105. package/dist/ports/model-settings.port.js +4 -0
  106. package/dist/ports/models-transport.port.d.ts +53 -0
  107. package/dist/ports/models-transport.port.js +7 -0
  108. package/dist/ports/prompt-cache-frame.port.d.ts +21 -0
  109. package/dist/ports/prompt-cache-frame.port.js +4 -0
  110. package/dist/ports/settings-store.port.d.ts +70 -0
  111. package/dist/ports/settings-store.port.js +33 -0
  112. package/dist/ports/thinking-binding.port.d.ts +7 -0
  113. package/dist/ports/thinking-binding.port.js +4 -0
  114. package/dist/settings/localSettings.d.ts +2 -1
  115. package/dist/settings/localSettings.js +18 -61
  116. package/dist/settings/memory-settings.adapter.d.ts +12 -0
  117. package/dist/settings/memory-settings.adapter.js +11 -0
  118. package/dist/settings/roleView.d.ts +3 -1
  119. package/dist/settings/roleView.js +1 -1
  120. package/dist/settings/settings-store.conformance.d.ts +47 -0
  121. package/dist/settings/settings-store.conformance.js +100 -0
  122. package/dist/settings/settings-value.entity.d.ts +15 -0
  123. package/dist/settings/settings-value.entity.functions.d.ts +51 -0
  124. package/dist/settings/settings-value.entity.functions.js +42 -0
  125. package/dist/settings/settings-value.entity.js +2 -0
  126. package/dist/storage/json-file.d.ts +18 -0
  127. package/dist/storage/json-file.js +26 -0
  128. package/dist/transport/http-proxy.entity.d.ts +104 -0
  129. package/dist/transport/http-proxy.entity.functions.d.ts +51 -0
  130. package/dist/transport/http-proxy.entity.functions.js +112 -0
  131. package/dist/transport/http-proxy.entity.js +42 -0
  132. package/dist/transport/model-request.conformance.d.ts +13 -0
  133. package/dist/transport/model-request.conformance.js +36 -0
  134. package/dist/transport/models-transport.adapter.d.ts +49 -0
  135. package/dist/transport/models-transport.adapter.js +99 -0
  136. package/dist/transport/resilient-http-client.adapter.d.ts +73 -0
  137. package/dist/transport/resilient-http-client.adapter.js +177 -0
  138. package/dist/transport/undici-fetch.adapter.d.ts +13 -0
  139. package/dist/transport/undici-fetch.adapter.js +1 -0
  140. package/package.json +13 -6
  141. package/dist/auth/anthropicOAuth.test.d.ts +0 -1
  142. package/dist/auth/localAuth.test.d.ts +0 -1
  143. package/dist/llm/compat.test.d.ts +0 -1
  144. package/dist/llm/modelCatalog.test.d.ts +0 -1
  145. package/dist/llm/providers.test.d.ts +0 -1
  146. package/dist/llm/retry.test.d.ts +0 -1
  147. package/dist/llm/router.test.d.ts +0 -1
  148. package/dist/llm/sse.test.d.ts +0 -1
  149. package/dist/plugin.adapter.test.d.ts +0 -1
  150. package/dist/settings/localSettings.test.d.ts +0 -1
  151. package/dist/settings/roleView.test.d.ts +0 -1
@@ -1,90 +1,62 @@
1
- import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
3
- import { Effect, Layer, Option, Redacted } from "effect";
4
- import { asJsonRecord, AuthError, AuthStore, parseJsonWarn } from "@xandreed/core";
5
- import { refreshAnthropicToken } from "./anthropicOAuth.js";
6
- import { refreshOpenAiCodexToken } from "./openAiCodexOAuth.js";
7
- const decodeCredential = (value) => {
8
- if (typeof value === "string")
9
- return Option.some({ type: "api_key", key: value });
10
- if (typeof value !== "object" || value === null)
11
- return Option.none();
12
- const v = value;
13
- if (v.type === "api_key" && typeof v.key === "string")
14
- return Option.some({ type: "api_key", key: v.key });
15
- if (v.type === "oauth" && typeof v.access === "string" && typeof v.refresh === "string" && typeof v.expires === "number")
16
- return Option.some({
17
- type: "oauth",
18
- access: v.access,
19
- refresh: v.refresh,
20
- expires: v.expires,
21
- ...typeof v.accountId === "string" ? { accountId: v.accountId } : {},
22
- ...typeof v.installationId === "string" ? { installationId: v.installationId } : {}
23
- });
24
- if (v.type === "local")
25
- return Option.some({
26
- type: "local",
27
- ...typeof v.baseUrl === "string" ? { baseUrl: v.baseUrl } : {}
28
- });
29
- return Option.none();
30
- }, readAuthFile = (path) => Effect.tryPromise({ try: () => readFile(path, "utf-8"), catch: () => "missing" }).pipe(Effect.flatMap((text) => parseJsonWarn(text, path)), Effect.map((parsed) => {
31
- const record = asJsonRecord(parsed), entries = new Map(Object.entries(record).flatMap(([provider, raw]) => Option.match(decodeCredential(raw), {
32
- onNone: () => [],
33
- onSome: (cred) => [[provider, cred]]
34
- })));
35
- return { path, entries };
36
- }), Effect.orElseSucceed(() => ({ path, entries: new Map }))), writeAuthFile = (path, entries) => Effect.tryPromise({
37
- try: async () => {
38
- await mkdir(dirname(path), { recursive: !0 });
39
- const tmp = `${path}.tmp-${process.pid}`;
40
- await writeFile(tmp, JSON.stringify(Object.fromEntries(entries), null, 2), "utf-8");
41
- await chmod(tmp, 384);
42
- await rename(tmp, path);
43
- },
44
- catch: (e) => new AuthError({ provider: "*", message: `auth.json write failed: ${String(e)}` })
45
- }), REFRESH_SKEW_MS = 60000;
1
+ import { join } from "node:path";
2
+ import { Effect, Layer, Option, Schema } from "effect";
3
+ import { AuthError, AuthStore, Credential } from "../ports/auth-store.port.js";
4
+ import { encodeCredential } from "./credential-input.entity.functions.js";
5
+ import { isSameToken, resolveSlotKey } from "./credential-refresh.entity.functions.js";
6
+ import { ModelsTransport } from "../ports/models-transport.port.js";
7
+ import { namedLock, readJsonObject, withFileLock, writeJsonObject } from "../storage/json-file.js";
8
+ const decodeStored = Schema.decodeUnknownOption(Credential), decodeLegacyKey = Schema.decodeUnknownOption(Schema.String), decodeCredential = (raw) => Option.orElse(decodeStored(raw), () => Option.flatMap(decodeLegacyKey(raw), (key) => decodeStored({ type: "api_key", key }))), corrupt = (path) => (reason) => new AuthError({
9
+ provider: "*",
10
+ code: "auth.corrupt",
11
+ message: `${path} ${reason}: fix or remove it; nothing was read from it or written to it`
12
+ }), readAuthFile = (path) => readJsonObject(path, corrupt(path)).pipe(Effect.map((record) => {
13
+ const decoded = Object.entries(Option.getOrElse(record, () => ({}))).map(([provider, raw]) => ({ provider, raw, credential: decodeCredential(raw) }));
14
+ return {
15
+ path,
16
+ entries: new Map(decoded.flatMap(({ provider, credential }) => Option.match(credential, {
17
+ onNone: () => [],
18
+ onSome: (found) => [[provider, found]]
19
+ }))),
20
+ unread: new Map(decoded.filter(({ credential }) => Option.isNone(credential)).map(({ provider, raw }) => [provider, raw]))
21
+ };
22
+ })), writeAuthFile = (file, provider, credential) => {
23
+ const others = (entries) => [...entries].filter(([key]) => key !== provider), credentials = [...others(file.entries), ...Option.toArray(Option.map(credential, (value) => [provider, value]))];
24
+ return Effect.forEach(credentials, ([key, value]) => Effect.map(encodeCredential(key, value), (encoded) => [key, encoded]), { concurrency: 1 }).pipe(Effect.flatMap((encoded) => {
25
+ const record = Object.fromEntries([...others(file.unread), ...encoded]);
26
+ return writeJsonObject(file.path, record, Option.some(384), (reason) => new AuthError({ provider: "*", message: `auth.json write failed: ${reason}` }));
27
+ }));
28
+ };
46
29
  export const authPaths = (cwd, home, directory = ".efferent") => [
47
30
  join(home, directory, "auth.json"),
48
31
  join(cwd, directory, "auth.json")
49
- ], LocalAuthStoreLive = (cwd, home, directory = ".efferent", fallbackDirectory) => {
50
- const paths = [...fallbackDirectory === void 0 ? [] : authPaths(cwd, home, fallbackDirectory), ...authPaths(cwd, home, directory)];
51
- return Layer.succeed(AuthStore, {
52
- all: Effect.gen(function* () {
53
- const files = yield* Effect.forEach(paths, readAuthFile);
54
- return new Map(files.flatMap((f) => [...f.entries]));
55
- }),
56
- get: (provider) => Effect.gen(function* () {
57
- const files = yield* Effect.forEach(paths, readAuthFile), merged = new Map(files.flatMap((f) => [...f.entries]));
58
- return Option.fromNullishOr(merged.get(provider));
59
- }),
60
- resolveKey: (provider) => Effect.gen(function* () {
61
- const holder = [...yield* Effect.forEach(paths, readAuthFile)].reverse().find((f) => f.entries.has(provider)), cred = holder?.entries.get(provider);
62
- if (cred === void 0)
63
- return Option.none();
64
- if (cred.type === "api_key")
65
- return Option.some(Redacted.make(cred.key));
66
- if (cred.type === "local")
67
- return Option.none();
68
- if (cred.expires > Date.now() + REFRESH_SKEW_MS)
69
- return Option.some(Redacted.make(cred.access));
70
- if (provider !== "anthropic" && provider !== "openai-codex")
71
- return yield* Effect.fail(new AuthError({
72
- provider,
73
- message: `the ${provider} OAuth token expired and its refresh flow isn't wired on the new line \u2014 re-login or use an API key`
74
- }));
75
- const fresh = yield* provider === "anthropic" ? refreshAnthropicToken(cred.refresh) : refreshOpenAiCodexToken(cred.refresh), updated = { ...cred, ...fresh }, target = holder !== void 0 && authPaths(cwd, home, directory).includes(holder.path) ? holder : yield* readAuthFile(authPaths(cwd, home, directory)[0] ?? "");
76
- yield* writeAuthFile(target.path, new Map([...target.entries, [provider, updated]]));
77
- return Option.some(Redacted.make(fresh.access));
78
- }),
79
- set: (provider, credential) => Effect.gen(function* () {
80
- const globalPath = authPaths(cwd, home, directory)[0] ?? "", file = yield* readAuthFile(globalPath);
81
- yield* writeAuthFile(globalPath, new Map([...file.entries, [provider, credential]]));
82
- }),
83
- remove: (provider) => Effect.forEach(paths, (path) => Effect.gen(function* () {
84
- const file = yield* readAuthFile(path);
85
- if (!file.entries.has(provider))
86
- return;
87
- yield* writeAuthFile(path, new Map([...file.entries].filter(([p]) => p !== provider)));
88
- })).pipe(Effect.asVoid)
89
- });
32
+ ];
33
+ const holderOf = (files, provider) => Option.firstSomeOf([...files].reverse().map((file) => Option.map(Option.fromNullishOr(file.entries.get(provider)), (credential) => ({ file, credential }))));
34
+ export const LocalAuthStoreLive = (cwd, home, directory = ".efferent", fallbackDirectory) => {
35
+ const writable = authPaths(cwd, home, directory), loginPath = writable[0] ?? join(home, directory, "auth.json"), paths = [...fallbackDirectory === void 0 ? [] : authPaths(cwd, home, fallbackDirectory), ...writable], readAll = Effect.forEach(paths, readAuthFile, { concurrency: 1 }), targetOf = (path) => writable.includes(path) ? path : loginPath;
36
+ return Layer.effect(AuthStore, Effect.map(Effect.serviceOption(ModelsTransport), (transport) => {
37
+ const tokens = Option.match(transport, { onNone: () => ({}), onSome: ({ fetch }) => ({ fetch }) }), slotOf = (provider) => ({
38
+ current: Effect.map(readAll, (files) => Option.map(holderOf(files, provider), ({ credential }) => credential)),
39
+ refreshLock: namedLock(loginPath, `refresh:${provider}`),
40
+ commit: (stale, fresh) => Effect.map(readAll, (files) => holderOf(files, provider)).pipe(Effect.flatMap(Option.match({
41
+ onNone: () => Effect.succeed(!1),
42
+ onSome: ({ file }) => {
43
+ const target = targetOf(file.path);
44
+ return withFileLock(target)(Effect.gen(function* () {
45
+ const now = holderOf(yield* readAll, provider);
46
+ if (!Option.exists(now, (held) => targetOf(held.file.path) === target && isSameToken(held.credential, stale)))
47
+ return !1;
48
+ yield* writeAuthFile(yield* readAuthFile(target), provider, Option.some(fresh));
49
+ return !0;
50
+ }));
51
+ }
52
+ })))
53
+ });
54
+ return {
55
+ all: Effect.map(readAll, (files) => new Map(files.flatMap((f) => [...f.entries]))),
56
+ get: (provider) => slotOf(provider).current,
57
+ resolveKey: (provider) => resolveSlotKey(provider, slotOf(provider), tokens),
58
+ set: (provider, credential) => withFileLock(loginPath)(readAuthFile(loginPath).pipe(Effect.flatMap((file) => writeAuthFile(file, provider, Option.some(credential))))),
59
+ remove: (provider) => readAll.pipe(Effect.andThen(Effect.forEach(paths, (path) => withFileLock(path)(readAuthFile(path).pipe(Effect.flatMap((file) => file.entries.has(provider) || file.unread.has(provider) ? writeAuthFile(file, provider, Option.none()) : Effect.void))), { concurrency: 1, discard: !0 })))
60
+ };
61
+ }));
90
62
  };
@@ -0,0 +1,35 @@
1
+ import { Layer } from "effect";
2
+ import { AuthStore } from "../ports/auth-store.port.js";
3
+ import type { AuthError, Credential } from "../ports/auth-store.port.js";
4
+ import type { ModelsFetch } from "../ports/models-transport.port.js";
5
+ /**
6
+ * How the in-memory store reaches the OAuth token endpoints.
7
+ *
8
+ * @public
9
+ */
10
+ export interface MemoryAuthOptions {
11
+ /** The fetch token refreshes go through; default: the `ModelsTransport`'s when the layer is built over one, else the global fetch. */
12
+ readonly fetch?: ModelsFetch;
13
+ }
14
+ /**
15
+ * `MemoryAuthStoreLive(seed?)` — an AuthStore that never touches disk.
16
+ * Credentials live in memory for the layer's lifetime: the host's
17
+ * `HostCredentials.load` (when the host provides that service), overlaid by
18
+ * `seed`. `resolveKey` refreshes a near-expiry anthropic or openai-codex
19
+ * OAuth token in memory, one refresh per provider at a time (through the
20
+ * `ModelsTransport` it was built over, so through the plugin's proxy), and
21
+ * hands the new token to `HostCredentials.save`. Secrets stay `Redacted`
22
+ * throughout; a `set` whose secrets are not (a plain string from untyped
23
+ * code) fails with an `AuthError`, as it does on the file store.
24
+ *
25
+ * A login or logout (`set`, `remove`) does not wait for a refresh's token
26
+ * request; it waits only while a refreshed token is committed, which
27
+ * includes the host's `save` of it, so a slow host store delays it that
28
+ * long. A refresh writes its token only if the provider still holds the
29
+ * credential it refreshed (compare-and-set under the provider's lock): a
30
+ * logout during a stalled token request stays a logout, and the host never
31
+ * receives the refreshed token.
32
+ *
33
+ * @public
34
+ */
35
+ export declare const MemoryAuthStoreLive: (seed?: ReadonlyMap<string, Credential>, options?: MemoryAuthOptions) => Layer.Layer<AuthStore, AuthError>;
@@ -0,0 +1,35 @@
1
+ import { Effect, Layer, Option, Ref, Semaphore, SynchronizedRef } from "effect";
2
+ import { AuthStore } from "../ports/auth-store.port.js";
3
+ import { HostCredentials } from "../ports/host-credentials.port.js";
4
+ import { ModelsTransport } from "../ports/models-transport.port.js";
5
+ import { encodeCredential, toCredentialInput } from "./credential-input.entity.functions.js";
6
+ import { isSameToken, resolveSlotKey } from "./credential-refresh.entity.functions.js";
7
+ export const MemoryAuthStoreLive = (seed = new Map, options = {}) => Layer.effect(AuthStore, Effect.gen(function* () {
8
+ const host = yield* Effect.serviceOption(HostCredentials), loaded = yield* Option.match(host, {
9
+ onNone: () => Effect.succeed(new Map),
10
+ onSome: (credentials) => credentials.load
11
+ }), state = yield* Ref.make(new Map([...loaded, ...seed])), locks = yield* SynchronizedRef.make(new Map), transport = yield* Effect.serviceOption(ModelsTransport), tokens = Option.match(Option.orElse(Option.fromNullishOr(options.fetch), () => Option.map(transport, (found) => found.fetch)), {
12
+ onNone: () => ({}),
13
+ onSome: (fetch) => ({ fetch })
14
+ }), locksOf = (provider) => SynchronizedRef.modifyEffect(locks, (all) => Option.match(Option.fromNullishOr(all.get(provider)), {
15
+ onSome: (found) => Effect.succeed([found, all]),
16
+ onNone: () => Effect.all({ commit: Semaphore.make(1), refresh: Semaphore.make(1) }, { concurrency: 1 }).pipe(Effect.map((made) => [made, new Map([...all, [provider, made]])]))
17
+ })), current = (provider) => Ref.get(state).pipe(Effect.map((all) => Option.fromNullishOr(all.get(provider)))), save = (provider, credential) => Option.match(host, {
18
+ onNone: () => Effect.void,
19
+ onSome: (credentials) => credentials.save(provider, Option.map(credential, toCredentialInput))
20
+ }), write = (provider, credential) => Ref.update(state, (all) => Option.match(credential, {
21
+ onNone: () => new Map([...all].filter(([key]) => key !== provider)),
22
+ onSome: (value) => new Map([...all, [provider, value]])
23
+ })), slotOf = (provider) => ({
24
+ current: current(provider),
25
+ refreshLock: Effect.map(locksOf(provider), (lock) => lock.refresh),
26
+ commit: (stale, fresh) => locksOf(provider).pipe(Effect.flatMap((lock) => lock.commit.withPermit(current(provider).pipe(Effect.flatMap((now) => !Option.exists(now, (held) => isSameToken(held, stale)) ? Effect.succeed(!1) : write(provider, Option.some(fresh)).pipe(Effect.andThen(save(provider, Option.some(fresh)).pipe(Effect.catch((error) => Effect.logWarning(`the refreshed ${provider} token is kept in memory, but the host did not save it: ${error.message}`)))), Effect.as(!0)))))))
27
+ });
28
+ return AuthStore.of({
29
+ all: Ref.get(state),
30
+ get: current,
31
+ resolveKey: (provider) => resolveSlotKey(provider, slotOf(provider), tokens),
32
+ set: (provider, credential) => encodeCredential(provider, credential).pipe(Effect.andThen(locksOf(provider)), Effect.flatMap((lock) => lock.commit.withPermit(save(provider, Option.some(credential)).pipe(Effect.andThen(write(provider, Option.some(credential))))))),
33
+ remove: (provider) => locksOf(provider).pipe(Effect.flatMap((lock) => lock.commit.withPermit(current(provider).pipe(Effect.flatMap((held) => Option.isNone(held) ? Effect.void : save(provider, Option.none()).pipe(Effect.andThen(write(provider, Option.none()))))))))
34
+ });
35
+ }));
@@ -1,16 +1,27 @@
1
- import { Effect, Option } from "effect";
2
- import { AuthError } from "@xandreed/core";
1
+ import { Effect, Option, Redacted } from "effect";
2
+ import { AuthError } from "../ports/auth-store.port.js";
3
+ import type { TokenRequestOptions } from "./anthropicOAuth.js";
3
4
  /** ChatGPT Plus/Pro OAuth protocol used by the OpenAI Codex subscription. */
4
5
  export declare const OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
6
+ /** The originator registered with `OPENAI_CODEX_CLIENT_ID`. The authorize
7
+ * request, the token and the subscription transports must all send it: the
8
+ * backend keys model entitlement to the client/originator pair. */
9
+ export declare const OPENAI_CODEX_ORIGINATOR = "pi";
5
10
  export declare const OPENAI_CODEX_CALLBACK_PORT = 1455;
6
11
  export declare const OPENAI_CODEX_CALLBACK_PATH = "/auth/callback";
7
12
  export declare const OPENAI_CODEX_REDIRECT_URI = "http://localhost:1455/auth/callback";
13
+ /**
14
+ * The tokens a sign-in or a refresh grants, `Redacted` from the token response on, and the account id read from the access token.
15
+ *
16
+ * @public
17
+ */
8
18
  export interface OpenAiCodexTokens {
9
- readonly access: string;
10
- readonly refresh: string;
19
+ readonly access: Redacted.Redacted<string>;
20
+ readonly refresh: Redacted.Redacted<string>;
11
21
  readonly expires: number;
12
22
  readonly accountId?: string;
13
23
  }
24
+ /** @public */
14
25
  export interface OpenAiCodexOAuthBegin {
15
26
  readonly verifier: string;
16
27
  readonly state: string;
@@ -20,6 +31,17 @@ export interface OpenAiCodexOAuthBegin {
20
31
  }
21
32
  /** Extract the subscription account lane without exposing the token. */
22
33
  export declare const openAiCodexAccountId: (accessToken: string) => Option.Option<string>;
34
+ /** @public */
23
35
  export declare const beginOpenAiCodexOAuth: Effect.Effect<OpenAiCodexOAuthBegin>;
24
- export declare const exchangeOpenAiCodexCode: (code: string, verifier: string) => Effect.Effect<OpenAiCodexTokens, AuthError>;
25
- export declare const refreshOpenAiCodexToken: (refreshToken: string) => Effect.Effect<OpenAiCodexTokens, AuthError>;
36
+ /**
37
+ * Exchange the authorization code for tokens, `Redacted` (ready for `AuthStore.set`).
38
+ *
39
+ * @public
40
+ */
41
+ export declare const exchangeOpenAiCodexCode: (code: string, verifier: string, options?: TokenRequestOptions) => Effect.Effect<OpenAiCodexTokens, AuthError>;
42
+ /**
43
+ * Refresh an expired (or near-expiry) access token; the refresh token is unwrapped only into the request form.
44
+ *
45
+ * @public
46
+ */
47
+ export declare const refreshOpenAiCodexToken: (refreshToken: Redacted.Redacted<string>, options?: TokenRequestOptions) => Effect.Effect<OpenAiCodexTokens, AuthError>;
@@ -1,26 +1,19 @@
1
- import { Effect, Result, Option } from "effect";
2
- import { AuthError } from "@xandreed/core";
3
- import { generatePkce } from "./anthropicOAuth.js";
4
- export const OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
1
+ import { Effect, Result, Option, Redacted, Schema } from "effect";
2
+ import { Base64Url } from "effect/encoding";
3
+ import { AuthError } from "../ports/auth-store.port.js";
4
+ import { generatePkce, tokenTime } from "./anthropicOAuth.js";
5
+ import { redactCredentials } from "../transport/http-proxy.entity.functions.js";
6
+ import { decodeTokenResponse, tokenRefusalText } from "./token-response.entity.functions.js";
7
+ export const OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", OPENAI_CODEX_ORIGINATOR = "pi";
5
8
  const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize", TOKEN_URL = "https://auth.openai.com/oauth/token";
6
9
  export const OPENAI_CODEX_CALLBACK_PORT = 1455, OPENAI_CODEX_CALLBACK_PATH = "/auth/callback", OPENAI_CODEX_REDIRECT_URI = `http://localhost:${OPENAI_CODEX_CALLBACK_PORT}${OPENAI_CODEX_CALLBACK_PATH}`;
7
- const SCOPE = "openid profile email offline_access", ACCOUNT_CLAIM = "https://api.openai.com/auth", oauthError = (stage, detail) => new AuthError({ provider: "openai-codex", message: `OAuth ${stage} failed: ${detail}` }), base64UrlJson = (segment) => {
8
- const normalized = segment.replace(/-/g, "+").replace(/_/g, "/"), padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
9
- return JSON.parse(atob(padded));
10
- };
11
- export const openAiCodexAccountId = (accessToken) => {
12
- const segment = accessToken.split(".")[1];
13
- return Option.flatMap(Option.fromNullishOr(segment), (encoded) => Option.flatMap(Result.getSuccess(Result.try(() => base64UrlJson(encoded))), (payload) => {
14
- const claim = payload[ACCOUNT_CLAIM];
15
- if (typeof claim !== "object" || claim === null)
16
- return Option.none();
17
- const accountId = claim.chatgpt_account_id;
18
- return Option.filter(Option.fromNullishOr(typeof accountId === "string" ? accountId : void 0), (value) => value.length > 0);
19
- }));
20
- };
21
- const postToken = (body, stage) => Effect.tryPromise({
10
+ const SCOPE = "openid profile email offline_access", ACCOUNT_CLAIM = "https://api.openai.com/auth", oauthError = (stage, detail) => new AuthError({ provider: "openai-codex", message: `OAuth ${stage} failed: ${detail}` }), AccessTokenClaims = Schema.Struct({
11
+ [ACCOUNT_CLAIM]: Schema.optionalKey(Schema.Struct({ chatgpt_account_id: Schema.optionalKey(Schema.NonEmptyString) }))
12
+ }), decodeClaims = Schema.decodeUnknownOption(Schema.fromJsonString(AccessTokenClaims)), claimsOf = (segment) => Option.flatMap(Result.getSuccess(Base64Url.decodeString(segment)), decodeClaims);
13
+ export const openAiCodexAccountId = (accessToken) => Option.flatMap(Option.flatMap(Option.fromNullishOr(accessToken.split(".")[1]), claimsOf), (claims) => Option.fromNullishOr(claims[ACCOUNT_CLAIM]?.chatgpt_account_id));
14
+ const postToken = (body, stage, options, previous) => Effect.tryPromise({
22
15
  try: async () => {
23
- const response = await fetch(TOKEN_URL, {
16
+ const response = await (options.fetch ?? fetch)(TOKEN_URL, {
24
17
  method: "POST",
25
18
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
26
19
  body: new URLSearchParams(body),
@@ -28,16 +21,13 @@ const postToken = (body, stage) => Effect.tryPromise({
28
21
  });
29
22
  return { ok: response.ok, status: response.status, text: await response.text() };
30
23
  },
31
- catch: (error) => oauthError(stage, String(error))
32
- }).pipe(Effect.filterOrFail((response) => response.ok, (response) => oauthError(stage, `status ${response.status}: ${response.text.slice(0, 200)}`)), Effect.flatMap((response) => Effect.try({
33
- try: () => JSON.parse(response.text),
34
- catch: () => oauthError(stage, "the token response was not JSON")
35
- })), Effect.filterOrFail((token) => typeof token.access_token === "string" && typeof token.refresh_token === "string" && typeof token.expires_in === "number", () => oauthError(stage, "the token response omitted required fields")), Effect.map((token) => ({
36
- access: token.access_token,
37
- refresh: token.refresh_token,
38
- expires: Date.now() + token.expires_in * 1000,
39
- ...Option.match(openAiCodexAccountId(token.access_token), { onNone: () => ({}), onSome: (accountId) => ({ accountId }) })
40
- })));
24
+ catch: (error) => oauthError(stage, redactCredentials(String(error)))
25
+ }).pipe(Effect.filterOrFail((response) => response.ok, (response) => oauthError(stage, tokenRefusalText(response.status, response.text, body))), Effect.flatMap((response) => decodeTokenResponse(response.text, previous).pipe(Effect.mapError((reason) => oauthError(stage, reason)))), Effect.flatMap((issued) => Effect.map(tokenTime(options), (now) => ({
26
+ access: issued.access,
27
+ refresh: issued.refresh,
28
+ expires: now + issued.lifetimeMs,
29
+ ...Option.match(openAiCodexAccountId(Redacted.value(issued.access)), { onNone: () => ({}), onSome: (accountId) => ({ accountId }) })
30
+ }))));
41
31
  export const beginOpenAiCodexOAuth = Effect.gen(function* () {
42
32
  const pkce = yield* generatePkce, stateBytes = new Uint8Array(16);
43
33
  crypto.getRandomValues(stateBytes);
@@ -52,7 +42,7 @@ export const beginOpenAiCodexOAuth = Effect.gen(function* () {
52
42
  state,
53
43
  id_token_add_organizations: "true",
54
44
  codex_cli_simplified_flow: "true",
55
- originator: "pi"
45
+ originator: OPENAI_CODEX_ORIGINATOR
56
46
  }).forEach(([key, value]) => url.searchParams.set(key, value));
57
47
  return { verifier: pkce.verifier, state, authorizeUrl: url.toString(), callbackPort: OPENAI_CODEX_CALLBACK_PORT, callbackPath: OPENAI_CODEX_CALLBACK_PATH };
58
- }), exchangeOpenAiCodexCode = (code, verifier) => postToken({ grant_type: "authorization_code", client_id: OPENAI_CODEX_CLIENT_ID, code, code_verifier: verifier, redirect_uri: OPENAI_CODEX_REDIRECT_URI }, "code exchange"), refreshOpenAiCodexToken = (refreshToken) => postToken({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: OPENAI_CODEX_CLIENT_ID }, "token refresh");
48
+ }), exchangeOpenAiCodexCode = (code, verifier, options = {}) => postToken({ grant_type: "authorization_code", client_id: OPENAI_CODEX_CLIENT_ID, code, code_verifier: verifier, redirect_uri: OPENAI_CODEX_REDIRECT_URI }, "code exchange", options, Option.none()), refreshOpenAiCodexToken = (refreshToken, options = {}) => postToken({ grant_type: "refresh_token", refresh_token: Redacted.value(refreshToken), client_id: OPENAI_CODEX_CLIENT_ID }, "token refresh", options, Option.some(refreshToken));
@@ -0,0 +1,21 @@
1
+ import { Schema } from "effect";
2
+ /**
3
+ * A token endpoint's answer, as both subscription providers send it: the
4
+ * access token, its lifetime in seconds, and a refresh token. A refresh may
5
+ * leave the refresh token out, when the provider does not rotate it. The
6
+ * tokens decode `Redacted`, so from the response on they print as
7
+ * `<redacted>` in errors, logs and fingerprints.
8
+ */
9
+ export declare const TokenResponse: Schema.Struct<{
10
+ readonly access_token: Schema.RedactedFromValue<Schema.NonEmptyString>;
11
+ readonly expires_in: Schema.Finite;
12
+ readonly refresh_token: Schema.OptionFromOptional<Schema.RedactedFromValue<Schema.String>>;
13
+ }>;
14
+ export type TokenResponse = typeof TokenResponse.Type;
15
+ /** The tokens a response grants, `Redacted`: the access token, the refresh token to keep, and the access token's lifetime in ms. */
16
+ export declare const IssuedTokens: Schema.Struct<{
17
+ readonly access: Schema.Redacted<Schema.String>;
18
+ readonly refresh: Schema.Redacted<Schema.String>;
19
+ readonly lifetimeMs: Schema.Number;
20
+ }>;
21
+ export type IssuedTokens = typeof IssuedTokens.Type;
@@ -0,0 +1,20 @@
1
+ import { Effect, Option, Redacted } from "effect";
2
+ import type { IssuedTokens } from "./token-response.entity.js";
3
+ /** How much of a refused token request's response body an error quotes. */
4
+ export declare const TOKEN_ERROR_BODY_CHARS = 200;
5
+ /**
6
+ * What an error says of a token endpoint's refusal: its status and the
7
+ * start of its body, every secret the request sent (`SECRET_FIELDS` of
8
+ * `sent`) replaced by `<redacted>` first, so an endpoint that echoes the
9
+ * request never puts a token into an error, a log or the model's view.
10
+ */
11
+ export declare const tokenRefusalText: (status: number, body: string, sent: Readonly<Record<string, string>>) => string;
12
+ /**
13
+ * Decode a token endpoint's answer, its tokens `Redacted`. `previous` is
14
+ * the refresh token a refresh sent: a response without one keeps it. A code
15
+ * exchange sends none, so its response must carry a refresh token. A
16
+ * response that is not JSON, has no access token or no lifetime above zero
17
+ * seconds fails with a reason that never quotes the response, which holds
18
+ * secrets.
19
+ */
20
+ export declare const decodeTokenResponse: (text: string, previous: Option.Option<Redacted.Redacted<string>>) => Effect.Effect<IssuedTokens, string>;
@@ -0,0 +1,10 @@
1
+ import { Effect, Option, Redacted, Schema } from "effect";
2
+ import { TokenResponse } from "./token-response.entity.js";
3
+ const decodeResponse = Schema.decodeUnknownEffect(Schema.fromJsonString(TokenResponse)), isNonEmpty = (token) => Redacted.value(token).length > 0, SECRET_FIELDS = ["refresh_token", "code", "code_verifier"];
4
+ export const TOKEN_ERROR_BODY_CHARS = 200, tokenRefusalText = (status, body, sent) => {
5
+ const redacted = SECRET_FIELDS.flatMap((field) => Option.toArray(Option.filter(Option.fromNullishOr(sent[field]), (value) => value.length > 0))).reduce((text, secret) => text.replaceAll(secret, "<redacted>"), body);
6
+ return `status ${status}: ${redacted.slice(0, TOKEN_ERROR_BODY_CHARS)}`;
7
+ }, decodeTokenResponse = (text, previous) => decodeResponse(text).pipe(Effect.mapError(() => "the token response did not hold an access token and its lifetime (expires_in, in seconds, above 0)"), Effect.flatMap((response) => Option.match(Option.orElse(Option.filter(response.refresh_token, isNonEmpty), () => previous), {
8
+ onNone: () => Effect.fail("the token response did not hold a refresh token"),
9
+ onSome: (refresh) => Effect.succeed({ access: response.access_token, refresh, lifetimeMs: response.expires_in * 1000 })
10
+ })));
@@ -0,0 +1,10 @@
1
+ import { Schema } from "effect";
2
+ export const TokenResponse = Schema.Struct({
3
+ access_token: Schema.RedactedFromValue(Schema.NonEmptyString),
4
+ expires_in: Schema.Finite.check(Schema.isGreaterThan(0)),
5
+ refresh_token: Schema.OptionFromOptional(Schema.RedactedFromValue(Schema.String))
6
+ }), IssuedTokens = Schema.Struct({
7
+ access: Schema.Redacted(Schema.String),
8
+ refresh: Schema.Redacted(Schema.String),
9
+ lifetimeMs: Schema.Number
10
+ });
@@ -0,0 +1,2 @@
1
+ export { makeCompatLanguageModel, promptWithImageLabels, toChatMessages } from "../llm/compat.js";
2
+ export type { ChatMessageOptions, CompatConfig } from "../llm/compat.js";
@@ -0,0 +1 @@
1
+ export { makeCompatLanguageModel, promptWithImageLabels, toChatMessages } from "../llm/compat.js";
@@ -0,0 +1,6 @@
1
+ export { authStoreConformance } from "../auth/auth-store.conformance.js";
2
+ export type { AuthStoreConformanceOptions, AuthStoreRefreshProbe } from "../auth/auth-store.conformance.js";
3
+ export { settingsStoreConformance } from "../settings/settings-store.conformance.js";
4
+ export type { SettingsStoreConformanceOptions } from "../settings/settings-store.conformance.js";
5
+ export { modelRequestConformance } from "../transport/model-request.conformance.js";
6
+ export type { ModelRequestSubject } from "../transport/model-request.conformance.js";
@@ -0,0 +1,3 @@
1
+ export { authStoreConformance } from "../auth/auth-store.conformance.js";
2
+ export { settingsStoreConformance } from "../settings/settings-store.conformance.js";
3
+ export { modelRequestConformance } from "../transport/model-request.conformance.js";
package/dist/index.d.ts CHANGED
@@ -1,20 +1,37 @@
1
- export { ANTHROPIC_OAUTH_BETA, beginAnthropicOAuth, CLAUDE_CODE_SYSTEM, exchangeAnthropicCode, generatePkce, parseAuthorizationInput, refreshAnthropicToken, } from "./auth/anthropicOAuth.js";
2
- export type { AuthRedirect, OAuthBegin, Pkce, RefreshedTokens } from "./auth/anthropicOAuth.js";
3
- export { beginOpenAiCodexOAuth, exchangeOpenAiCodexCode, OPENAI_CODEX_CALLBACK_PATH, OPENAI_CODEX_CALLBACK_PORT, OPENAI_CODEX_CLIENT_ID, OPENAI_CODEX_REDIRECT_URI, openAiCodexAccountId, refreshOpenAiCodexToken, } from "./auth/openAiCodexOAuth.js";
1
+ export { beginAnthropicOAuth, exchangeAnthropicCode, parseAuthorizationInput, refreshAnthropicToken } from "./auth/anthropicOAuth.js";
2
+ export type { AuthRedirect, OAuthBegin, RefreshedTokens, TokenRequestOptions } from "./auth/anthropicOAuth.js";
3
+ export { beginOpenAiCodexOAuth, exchangeOpenAiCodexCode, refreshOpenAiCodexToken } from "./auth/openAiCodexOAuth.js";
4
4
  export type { OpenAiCodexOAuthBegin, OpenAiCodexTokens } from "./auth/openAiCodexOAuth.js";
5
- export { authPaths, LocalAuthStoreLive } from "./auth/localAuth.js";
5
+ export { LocalAuthStoreLive } from "./auth/localAuth.js";
6
+ export { MemoryAuthStoreLive } from "./auth/memory-auth.adapter.js";
7
+ export type { MemoryAuthOptions } from "./auth/memory-auth.adapter.js";
8
+ export { AccountId, CredentialInput, CredentialInputs, InstallationId } from "./auth/credential-input.entity.js";
9
+ export { RefreshableProvider } from "./auth/credential-refresh.entity.js";
10
+ export type { OAuthCredential } from "./auth/credential-refresh.entity.js";
11
+ export { isUsableAt, REFRESH_SKEW_MS, refreshCredential } from "./auth/credential-refresh.entity.functions.js";
12
+ export { HostCredentials } from "./ports/host-credentials.port.js";
13
+ export { AuthError, AuthStore, Credential } from "./ports/auth-store.port.js";
14
+ export { EngineSettings, SETTINGS_KEYS, SettingsError, SettingsStore } from "./ports/settings-store.port.js";
15
+ export type { ModelRole, SettingsKey } from "./ports/settings-store.port.js";
6
16
  export { LocalSettingsStoreLive } from "./settings/localSettings.js";
17
+ export { MemorySettingsStoreLive } from "./settings/memory-settings.adapter.js";
18
+ export { RoleField, SettingValue } from "./settings/settings-value.entity.js";
19
+ export { applyRole, applySetting, coerceSettingValue, ROLE_FIELDS, roleSelection, settingsFromRecord } from "./settings/settings-value.entity.functions.js";
7
20
  export { roleModelView } from "./settings/roleView.js";
8
- export { fromChatCompletion, makeCompatLanguageModel, toChatMessages, toChatTools, } from "./llm/compat.js";
9
- export type { CompatConfig } from "./llm/compat.js";
10
- export { makeOpenAiCodexLanguageModel, OPENAI_CODEX_API_URL, toOpenAiCodexRequestBody } from "./llm/openAiCodex.js";
11
- export { normalizeOpenAiCodexWebSocketEvent, openAiCodexUuidV7, OpenAiCodexWebSocketHttpClient } from "./llm/openAiCodexWebSocket.js";
12
- export { buildProvider, chatCompletionsUrl, OPENCODE_CHAT_URL, OPENCODE_RESPONSES_API_URL, prependClaudeCode, usesOpenCodeResponses, withAnthropicCacheBreakpoints, } from "./llm/providers.js";
13
- export { classifyLlmError, LLM_REQUEST_TIMEOUT_MS, MAX_HONORED_RETRY_AFTER_MS, rejectEmptyResponse, retryableLlm, } from "./llm/retry.js";
14
- export type { ErrorClass } from "./llm/retry.js";
15
- export { generateWith, LanguageModelLive, LanguageModelSelectionLive } from "./llm/router.js";
16
- export { configuredModelCatalog, ConfiguredModelCatalogLive, reasoningEffortsFor, } from "./llm/modelCatalog.js";
21
+ export { makeOpenAiCodexWebSocketHttpClient } from "./llm/openAiCodexWebSocket.js";
22
+ export { LLM_CALL_BUDGET_MS, LLM_REQUEST_TIMEOUT_MS } from "./llm/retry.js";
23
+ export { ModelRetryPolicy, modelRetryDefaults } from "./llm/model-retry-policy.entity.js";
24
+ export { CurrentModelRetryPolicy } from "./ports/model-retry-policy.port.js";
25
+ export { LanguageModelLive, LanguageModelSelectionLive } from "./llm/router.js";
26
+ export { ConfiguredModelCatalogLive, imageInputFor, reasoningEffortsFor } from "./llm/modelCatalog.js";
17
27
  export type { ReasoningEffort } from "./llm/modelCatalog.js";
18
28
  export { UtilityLlmLive } from "./llm/utilityLlm.js";
19
29
  export { modelsPlugin, default } from "./plugin.adapter.js";
20
30
  export { CurrentModelEndpoints, ModelEndpoint, ModelEndpoints } from "./ports/model-endpoints.port.js";
31
+ export { ModelsTransport } from "./ports/models-transport.port.js";
32
+ export type { ModelsFetch, WebSocketProxy } from "./ports/models-transport.port.js";
33
+ export { ModelsTransportLive } from "./transport/models-transport.adapter.js";
34
+ export { HttpProxyOptions, ProxyInvalid, ProxyUnsupported, ProxyUrlOption } from "./transport/http-proxy.entity.js";
35
+ export { ModelThinking, ModelSettings, ModelSettingsMap, ModelSettingsError } from "./llm/model-settings.entity.js";
36
+ export { PromptCacheSettings } from "./llm/prompt-cache.entity.js";
37
+ export { CurrentModelSettings } from "./ports/model-settings.port.js";
package/dist/index.js CHANGED
@@ -1,55 +1,30 @@
1
- export {
2
- ANTHROPIC_OAUTH_BETA,
3
- beginAnthropicOAuth,
4
- CLAUDE_CODE_SYSTEM,
5
- exchangeAnthropicCode,
6
- generatePkce,
7
- parseAuthorizationInput,
8
- refreshAnthropicToken
9
- } from "./auth/anthropicOAuth.js";
10
- export {
11
- beginOpenAiCodexOAuth,
12
- exchangeOpenAiCodexCode,
13
- OPENAI_CODEX_CALLBACK_PATH,
14
- OPENAI_CODEX_CALLBACK_PORT,
15
- OPENAI_CODEX_CLIENT_ID,
16
- OPENAI_CODEX_REDIRECT_URI,
17
- openAiCodexAccountId,
18
- refreshOpenAiCodexToken
19
- } from "./auth/openAiCodexOAuth.js";
20
- export { authPaths, LocalAuthStoreLive } from "./auth/localAuth.js";
1
+ export { beginAnthropicOAuth, exchangeAnthropicCode, parseAuthorizationInput, refreshAnthropicToken } from "./auth/anthropicOAuth.js";
2
+ export { beginOpenAiCodexOAuth, exchangeOpenAiCodexCode, refreshOpenAiCodexToken } from "./auth/openAiCodexOAuth.js";
3
+ export { LocalAuthStoreLive } from "./auth/localAuth.js";
4
+ export { MemoryAuthStoreLive } from "./auth/memory-auth.adapter.js";
5
+ export { AccountId, CredentialInput, CredentialInputs, InstallationId } from "./auth/credential-input.entity.js";
6
+ export { RefreshableProvider } from "./auth/credential-refresh.entity.js";
7
+ export { isUsableAt, REFRESH_SKEW_MS, refreshCredential } from "./auth/credential-refresh.entity.functions.js";
8
+ export { HostCredentials } from "./ports/host-credentials.port.js";
9
+ export { AuthError, AuthStore, Credential } from "./ports/auth-store.port.js";
10
+ export { EngineSettings, SETTINGS_KEYS, SettingsError, SettingsStore } from "./ports/settings-store.port.js";
21
11
  export { LocalSettingsStoreLive } from "./settings/localSettings.js";
12
+ export { MemorySettingsStoreLive } from "./settings/memory-settings.adapter.js";
13
+ export { RoleField, SettingValue } from "./settings/settings-value.entity.js";
14
+ export { applyRole, applySetting, coerceSettingValue, ROLE_FIELDS, roleSelection, settingsFromRecord } from "./settings/settings-value.entity.functions.js";
22
15
  export { roleModelView } from "./settings/roleView.js";
23
- export {
24
- fromChatCompletion,
25
- makeCompatLanguageModel,
26
- toChatMessages,
27
- toChatTools
28
- } from "./llm/compat.js";
29
- export { makeOpenAiCodexLanguageModel, OPENAI_CODEX_API_URL, toOpenAiCodexRequestBody } from "./llm/openAiCodex.js";
30
- export { normalizeOpenAiCodexWebSocketEvent, openAiCodexUuidV7, OpenAiCodexWebSocketHttpClient } from "./llm/openAiCodexWebSocket.js";
31
- export {
32
- buildProvider,
33
- chatCompletionsUrl,
34
- OPENCODE_CHAT_URL,
35
- OPENCODE_RESPONSES_API_URL,
36
- prependClaudeCode,
37
- usesOpenCodeResponses,
38
- withAnthropicCacheBreakpoints
39
- } from "./llm/providers.js";
40
- export {
41
- classifyLlmError,
42
- LLM_REQUEST_TIMEOUT_MS,
43
- MAX_HONORED_RETRY_AFTER_MS,
44
- rejectEmptyResponse,
45
- retryableLlm
46
- } from "./llm/retry.js";
47
- export { generateWith, LanguageModelLive, LanguageModelSelectionLive } from "./llm/router.js";
48
- export {
49
- configuredModelCatalog,
50
- ConfiguredModelCatalogLive,
51
- reasoningEffortsFor
52
- } from "./llm/modelCatalog.js";
16
+ export { makeOpenAiCodexWebSocketHttpClient } from "./llm/openAiCodexWebSocket.js";
17
+ export { LLM_CALL_BUDGET_MS, LLM_REQUEST_TIMEOUT_MS } from "./llm/retry.js";
18
+ export { ModelRetryPolicy, modelRetryDefaults } from "./llm/model-retry-policy.entity.js";
19
+ export { CurrentModelRetryPolicy } from "./ports/model-retry-policy.port.js";
20
+ export { LanguageModelLive, LanguageModelSelectionLive } from "./llm/router.js";
21
+ export { ConfiguredModelCatalogLive, imageInputFor, reasoningEffortsFor } from "./llm/modelCatalog.js";
53
22
  export { UtilityLlmLive } from "./llm/utilityLlm.js";
54
23
  export { modelsPlugin, default } from "./plugin.adapter.js";
55
24
  export { CurrentModelEndpoints, ModelEndpoint, ModelEndpoints } from "./ports/model-endpoints.port.js";
25
+ export { ModelsTransport } from "./ports/models-transport.port.js";
26
+ export { ModelsTransportLive } from "./transport/models-transport.adapter.js";
27
+ export { HttpProxyOptions, ProxyInvalid, ProxyUnsupported, ProxyUrlOption } from "./transport/http-proxy.entity.js";
28
+ export { ModelThinking, ModelSettings, ModelSettingsMap, ModelSettingsError } from "./llm/model-settings.entity.js";
29
+ export { PromptCacheSettings } from "./llm/prompt-cache.entity.js";
30
+ export { CurrentModelSettings } from "./ports/model-settings.port.js";