@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
@@ -0,0 +1,33 @@
1
+ import { Option } from "effect";
2
+ import { ModelRequestState } from "./model-retry-policy.entity.js";
3
+ const DECIMAL = /^(?:\d+(?:\.\d*)?|\.\d+)$/;
4
+ export const retryAfterMillis = (value, now) => {
5
+ const text = value.trim();
6
+ if (DECIMAL.test(text)) {
7
+ const millis = Number(text) * 1000;
8
+ return Number.isFinite(millis) ? Option.some(millis) : Option.none();
9
+ }
10
+ const date = /^[A-Za-z]{3,9}[ ,]/.test(text) ? Date.parse(text) : NaN;
11
+ return Number.isFinite(date) ? Option.some(Math.max(0, date - now)) : Option.none();
12
+ }, retryAfterMsMillis = (value) => {
13
+ const text = value.trim(), millis = DECIMAL.test(text) ? Number(text) : NaN;
14
+ return Number.isFinite(millis) ? Option.some(millis) : Option.none();
15
+ };
16
+ const headerText = (headers, name) => Option.filter(Option.fromNullishOr(Object.hasOwn(headers, name) ? headers[name] : void 0), (value) => typeof value === "string");
17
+ export const retryHintMillis = (headers, now) => Option.orElse(Option.flatMap(headerText(headers, "retry-after-ms"), retryAfterMsMillis), () => Option.flatMap(headerText(headers, "retry-after"), (value) => retryAfterMillis(value, now))), retryDelayMillis = (policy, attempt, unit, hint) => {
18
+ const base = Math.min(policy.backoff.maxMs, policy.backoff.initialMs * 2 ** Math.min(1023, Math.max(0, attempt - 1))), jittered = Math.min(policy.backoff.maxMs, Math.max(0, Math.round(base * (1 - policy.backoff.jitter + 2 * policy.backoff.jitter * unit))));
19
+ return Math.max(jittered, Math.min(policy.retryAfterMaxMs, Option.getOrElse(hint, () => 0)));
20
+ }, primaryAttemptLimit = (policy, reserveFallback) => reserveFallback && policy.maxAttempts >= 2 ? policy.maxAttempts - 1 : policy.maxAttempts, retryableModelFailure = (kind) => kind === "Transient" || kind === "Timeout" || kind === "RateLimit", startAttemptState = (state, policy, reserveFallback) => {
21
+ const start = () => state.attempts < (state.rung === "Primary" ? primaryAttemptLimit(policy, reserveFallback) : policy.maxAttempts) ? Option.some({ _tag: "Sending", rung: state.rung, attempts: state.attempts + 1 }) : Option.none();
22
+ return ModelRequestState.match(state, {
23
+ Ready: start,
24
+ Sending: () => Option.none(),
25
+ Rejected: (value) => retryableModelFailure(value.kind) && value.replayable ? start() : Option.none(),
26
+ Committed: () => Option.none()
27
+ });
28
+ }, enterFallbackState = (state, policy, reserveFallback) => ModelRequestState.match(state, {
29
+ Ready: () => Option.none(),
30
+ Sending: () => Option.none(),
31
+ Committed: () => Option.none(),
32
+ Rejected: (value) => reserveFallback && policy.maxAttempts >= 2 && value.rung === "Primary" && value.attempts >= primaryAttemptLimit(policy, !0) && value.attempts < policy.maxAttempts && value.replayable && retryableModelFailure(value.kind) ? Option.some({ _tag: "Ready", rung: "Fallback", attempts: value.attempts }) : Option.none()
33
+ });
@@ -0,0 +1,23 @@
1
+ import { Schema } from "effect";
2
+ import { ModelFailureKind } from "@xandreed/core";
3
+ const positive = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)), nonnegative = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
4
+ export const ModelRetryPolicy = Schema.Struct({
5
+ maxAttempts: positive,
6
+ requestTimeoutMs: positive,
7
+ bodyIdleTimeoutMs: positive,
8
+ totalTimeoutMs: positive,
9
+ retryAfterMaxMs: nonnegative,
10
+ backoff: Schema.Struct({ initialMs: nonnegative, maxMs: nonnegative, jitter: Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 })) }).check(Schema.makeFilter((value) => value.maxMs >= value.initialMs ? !0 : "maximum backoff must be at least its initial delay"))
11
+ }), modelRetryDefaults = {
12
+ maxAttempts: 4,
13
+ requestTimeoutMs: 300000,
14
+ bodyIdleTimeoutMs: 300000,
15
+ totalTimeoutMs: 600000,
16
+ retryAfterMaxMs: 60000,
17
+ backoff: { initialMs: 1000, maxMs: 4000, jitter: 0.2 }
18
+ }, ModelRequestRung = Schema.Literals(["Primary", "Fallback"]), ModelRequestState = Schema.Union([
19
+ Schema.TaggedStruct("Ready", { rung: ModelRequestRung, attempts: nonnegative }),
20
+ Schema.TaggedStruct("Sending", { rung: ModelRequestRung, attempts: positive }),
21
+ Schema.TaggedStruct("Rejected", { rung: ModelRequestRung, attempts: positive, kind: ModelFailureKind, replayable: Schema.Boolean, retryAt: Schema.optionalKey(nonnegative) }),
22
+ Schema.TaggedStruct("Committed", { rung: ModelRequestRung, attempts: positive })
23
+ ]).pipe(Schema.toTaggedUnion("_tag"));
@@ -0,0 +1,85 @@
1
+ import { Schema } from "effect";
2
+ /** The public reasoning summary returned with adaptive or manual thinking. @public */
3
+ export declare const ThinkingDisplay: Schema.Literals<readonly ["summarized", "omitted"]>;
4
+ export type ThinkingDisplay = typeof ThinkingDisplay.Type;
5
+ /** Model-specific thinking controls; modes carry only their supported companions. @public */
6
+ export declare const ModelThinking: Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Adaptive", {
7
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
8
+ }>, Schema.TaggedStruct<"Budget", {
9
+ readonly budgetTokens: Schema.Int;
10
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
11
+ }>, Schema.TaggedStruct<"BetweenTools", {}>, Schema.TaggedStruct<"Off", {}>]>;
12
+ export type ModelThinking = typeof ModelThinking.Type;
13
+ /** Static host settings for one full provider:model selection. @public */
14
+ export declare const ModelSettings: Schema.Struct<{
15
+ readonly thinking: Schema.optionalKey<Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Adaptive", {
16
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
17
+ }>, Schema.TaggedStruct<"Budget", {
18
+ readonly budgetTokens: Schema.Int;
19
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
20
+ }>, Schema.TaggedStruct<"BetweenTools", {}>, Schema.TaggedStruct<"Off", {}>]>>;
21
+ readonly effort: Schema.optionalKey<Schema.Literals<readonly ["none", "low", "medium", "high", "xhigh", "max"]>>;
22
+ readonly limits: Schema.optionalKey<Schema.Struct<{
23
+ readonly contextWindow: Schema.optionalKey<Schema.Int>;
24
+ readonly maxOutputTokens: Schema.optionalKey<Schema.Int>;
25
+ }>>;
26
+ readonly promptCache: Schema.optionalKey<Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Auto", {
27
+ readonly ttl: Schema.Literals<readonly ["5m", "1h"]>;
28
+ }>, Schema.TaggedStruct<"TopLevel", {
29
+ readonly ttl: Schema.Literals<readonly ["5m", "1h"]>;
30
+ }>, Schema.TaggedStruct<"Off", {}>]>>;
31
+ }>;
32
+ export type ModelSettings = typeof ModelSettings.Type;
33
+ /** Settings keyed by the complete provider:modelId selection, including gateway aliases. @public */
34
+ export declare const ModelSettingsMap: Schema.$Record<Schema.String, Schema.Struct<{
35
+ readonly thinking: Schema.optionalKey<Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Adaptive", {
36
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
37
+ }>, Schema.TaggedStruct<"Budget", {
38
+ readonly budgetTokens: Schema.Int;
39
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
40
+ }>, Schema.TaggedStruct<"BetweenTools", {}>, Schema.TaggedStruct<"Off", {}>]>>;
41
+ readonly effort: Schema.optionalKey<Schema.Literals<readonly ["none", "low", "medium", "high", "xhigh", "max"]>>;
42
+ readonly limits: Schema.optionalKey<Schema.Struct<{
43
+ readonly contextWindow: Schema.optionalKey<Schema.Int>;
44
+ readonly maxOutputTokens: Schema.optionalKey<Schema.Int>;
45
+ }>>;
46
+ readonly promptCache: Schema.optionalKey<Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Auto", {
47
+ readonly ttl: Schema.Literals<readonly ["5m", "1h"]>;
48
+ }>, Schema.TaggedStruct<"TopLevel", {
49
+ readonly ttl: Schema.Literals<readonly ["5m", "1h"]>;
50
+ }>, Schema.TaggedStruct<"Off", {}>]>>;
51
+ }>>;
52
+ export type ModelSettingsMap = typeof ModelSettingsMap.Type;
53
+ declare const ModelSettingsError_base: Schema.Class<ModelSettingsError, Schema.TaggedStruct<"ModelSettingsError", {
54
+ readonly reason: Schema.Literals<readonly ["Settings", "Selection", "OutputLimit", "Thinking", "Effort", "Budget", "ToolChoice", "Binding"]>;
55
+ readonly message: Schema.String;
56
+ }>, import("effect/Cause").YieldableError>;
57
+ /** Why configured model controls cannot form a valid Messages request. @public */
58
+ export declare class ModelSettingsError extends ModelSettingsError_base {
59
+ }
60
+ /** Anthropic's native vocabulary; neutral none is mapped to low by local adapter policy. */
61
+ export declare const AnthropicEffort: Schema.Literals<readonly ["low", "medium", "high", "xhigh", "max"]>;
62
+ export type AnthropicEffort = typeof AnthropicEffort.Type;
63
+ /** Effective public request settings; unknown thinking/effort remains absent. */
64
+ export declare const ResolvedAnthropicSettings: Schema.Struct<{
65
+ readonly selection: Schema.String;
66
+ readonly thinking: Schema.OptionFromNullOr<Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Adaptive", {
67
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
68
+ }>, Schema.TaggedStruct<"Budget", {
69
+ readonly budgetTokens: Schema.Int;
70
+ readonly display: Schema.optionalKey<Schema.Literals<readonly ["summarized", "omitted"]>>;
71
+ }>, Schema.TaggedStruct<"BetweenTools", {}>, Schema.TaggedStruct<"Off", {}>]>>;
72
+ readonly effort: Schema.OptionFromNullOr<Schema.Literals<readonly ["low", "medium", "high", "xhigh", "max"]>>;
73
+ readonly maxOutputTokens: Schema.Int;
74
+ readonly promptCache: Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Auto", {
75
+ readonly ttl: Schema.Literals<readonly ["5m", "1h"]>;
76
+ }>, Schema.TaggedStruct<"TopLevel", {
77
+ readonly ttl: Schema.Literals<readonly ["5m", "1h"]>;
78
+ }>, Schema.TaggedStruct<"Off", {}>]>;
79
+ readonly limits: Schema.optionalKey<Schema.Struct<{
80
+ readonly contextWindow: Schema.optionalKey<Schema.Int>;
81
+ readonly maxOutputTokens: Schema.optionalKey<Schema.Int>;
82
+ }>>;
83
+ }>;
84
+ export type ResolvedAnthropicSettings = typeof ResolvedAnthropicSettings.Type;
85
+ export {};
@@ -0,0 +1,11 @@
1
+ import { Option, Result } from "effect";
2
+ import type { ModelCallPolicy } from "@xandreed/core";
3
+ import { ModelSettingsError } from "./model-settings.entity.js";
4
+ import type { ModelSettingsMap, ResolvedAnthropicSettings } from "./model-settings.entity.js";
5
+ /** Resolve public settings without wire fields; an unknown model requires an explicit output cap. */
6
+ export declare const resolveAnthropicSettings: (selection: string, settings: ModelSettingsMap, policy: Option.Option<ModelCallPolicy>) => Result.Result<ResolvedAnthropicSettings, ModelSettingsError>;
7
+ /** Validate request-dependent controls after resolution, before any network I/O. */
8
+ export declare const validateAnthropicRequest: (settings: ResolvedAnthropicSettings, request: {
9
+ readonly forcedTool: boolean;
10
+ readonly binding: boolean;
11
+ }) => Result.Result<void, ModelSettingsError>;
@@ -0,0 +1,49 @@
1
+ import { Array as Arr, Option, Result, Schema } from "effect";
2
+ import { parseModelSelection } from "@xandreed/core";
3
+ import { anthropicProfileOf } from "./provider-profile.entity.functions.js";
4
+ import { AnthropicEffort, ModelSettings, ModelSettingsError, ModelThinking } from "./model-settings.entity.js";
5
+ const error = (reason, message) => new ModelSettingsError({ reason, message }), outputLimit = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)), EFFORT_ORDER = ["low", "medium", "high", "xhigh", "max"], nativeEffort = (value) => value === "none" ? "low" : value, nearestEffort = (requested, supported) => Option.orElse(Arr.findLast(supported, (level) => EFFORT_ORDER.indexOf(level) <= EFFORT_ORDER.indexOf(requested)), () => Arr.head(supported)), displayed = (thinking) => ModelThinking.match(thinking, {
6
+ Adaptive: (value) => ({ ...value, display: value.display ?? "summarized" }),
7
+ Budget: (value) => ({ ...value, display: value.display ?? "summarized" }),
8
+ BetweenTools: (value) => value,
9
+ Off: (value) => value
10
+ });
11
+ export const resolveAnthropicSettings = (selection, settings, policy) => {
12
+ const parsed = parseModelSelection(selection);
13
+ if (Option.isNone(parsed))
14
+ return Result.fail(error("Selection", "the model selection must name a provider and model"));
15
+ const decoded = Schema.decodeUnknownResult(ModelSettings)(Object.hasOwn(settings, selection) ? settings[selection] : {}, { onExcessProperty: "error" });
16
+ if (Result.isFailure(decoded))
17
+ return Result.fail(error("Settings", "the per-model settings do not decode; check thinking companions, effort and positive integer limits"));
18
+ const configured = decoded.success, profile = anthropicProfileOf(parsed.value.modelId), traits = profile.anthropic, limits = { ...profile.limits, ...configured.limits }, maximum = Option.orElse(Option.flatMap(policy, (value) => Option.fromNullishOr(value.maxOutputTokens)), () => Option.fromNullishOr(limits.maxOutputTokens));
19
+ if (Option.isNone(maximum))
20
+ return Result.fail(error("OutputLimit", "an unknown Messages model needs maxOutputTokens in its call policy or per-model limits"));
21
+ const output = Schema.decodeUnknownResult(outputLimit)(maximum.value);
22
+ if (Result.isFailure(output))
23
+ return Result.fail(error("OutputLimit", "maxOutputTokens must be a positive finite integer"));
24
+ const thinking = Option.map(Option.orElse(Option.fromNullishOr(configured.thinking), () => traits?.defaultThinking ?? Option.none()), displayed);
25
+ if (Option.exists(thinking, (value) => traits !== void 0 && !traits.modes.includes(value._tag)))
26
+ return Result.fail(error("Thinking", "the configured thinking mode is not supported by this Messages model"));
27
+ const noEffort = traits?.source === "documented" && profile.efforts.length === 0;
28
+ if (noEffort && configured.effort !== void 0)
29
+ return Result.fail(error("Effort", "this Messages model does not support per-model effort"));
30
+ const reduced = Option.exists(thinking, (value) => value._tag === "Off" || value._tag === "BetweenTools") ? traits?.reducedEfforts ?? Option.none() : Option.none(), supported = traits?.source === "documented" ? Option.some(EFFORT_ORDER.filter((level) => profile.efforts.includes(level) && Option.match(reduced, { onNone: () => !0, onSome: (allowed) => allowed.includes(level) }))) : Option.none(), explicit = Option.map(Option.fromNullishOr(configured.effort), nativeEffort);
31
+ if (Option.exists(explicit, (level) => traits?.source === "documented" && !profile.efforts.includes(level)))
32
+ return Result.fail(error("Effort", "the configured effort is not supported by this Messages model"));
33
+ if (Option.exists(explicit, (level) => Option.exists(reduced, (allowed) => !allowed.includes(level))))
34
+ return Result.fail(error("Effort", "this reduced-thinking mode requires low, medium or high effort"));
35
+ const ambient = Option.map(Option.map(policy, (value) => value.effort), nativeEffort), effort = noEffort ? Option.none() : Option.orElse(Option.flatMap(ambient, (level) => Option.match(supported, { onNone: () => Option.some(level), onSome: (levels) => nearestEffort(level, levels) })), () => Option.orElse(explicit, () => traits?.defaultEffort ?? Option.none()));
36
+ if (Option.exists(thinking, (value) => value._tag === "Budget" && value.budgetTokens >= output.success))
37
+ return Result.fail(error("Budget", "the thinking budget must be less than maxOutputTokens"));
38
+ return Result.succeed({ selection, thinking, effort, maxOutputTokens: output.success, promptCache: configured.promptCache ?? { _tag: "Auto", ttl: "5m" }, ...Object.keys(limits).length === 0 ? {} : { limits } });
39
+ }, validateAnthropicRequest = (settings, request) => {
40
+ const parsed = parseModelSelection(settings.selection);
41
+ if (Option.isNone(parsed))
42
+ return Result.fail(error("Selection", "the model selection must name a provider and model"));
43
+ const profile = anthropicProfileOf(parsed.value.modelId);
44
+ if (request.forcedTool && (Option.contains(profile.anthropic?.forcedTools ?? Option.none(), !1) || Option.exists(settings.thinking, (value) => value._tag === "Budget")))
45
+ return Result.fail(error("ToolChoice", "this Messages model and thinking mode do not support forced tool choice"));
46
+ if (request.binding && (!profile.bindsThinking || !Option.exists(settings.thinking, (value) => value._tag === "Adaptive")))
47
+ return Result.fail(error("Binding", "thinking binding requires a supported Messages model with adaptive thinking"));
48
+ return Result.succeed(void 0);
49
+ };
@@ -0,0 +1,28 @@
1
+ import { Option, Schema } from "effect";
2
+ import { ModelLimits, ReasoningEffort, parseModelSelection } from "@xandreed/core";
3
+ import { PromptCacheSettings } from "./prompt-cache.entity.js";
4
+ export const ThinkingDisplay = Schema.Literals(["summarized", "omitted"]), ModelThinking = Schema.Union([
5
+ Schema.TaggedStruct("Adaptive", { display: Schema.optionalKey(ThinkingDisplay) }),
6
+ Schema.TaggedStruct("Budget", { budgetTokens: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1024)), display: Schema.optionalKey(ThinkingDisplay) }),
7
+ Schema.TaggedStruct("BetweenTools", {}),
8
+ Schema.TaggedStruct("Off", {})
9
+ ]).pipe(Schema.toTaggedUnion("_tag")), ModelSettings = Schema.Struct({
10
+ thinking: Schema.optionalKey(ModelThinking),
11
+ effort: Schema.optionalKey(ReasoningEffort),
12
+ limits: Schema.optionalKey(ModelLimits),
13
+ promptCache: Schema.optionalKey(PromptCacheSettings)
14
+ }), ModelSettingsMap = Schema.Record(Schema.String, ModelSettings).check(Schema.makeFilter((settings) => Object.keys(settings).every((selection) => Option.isSome(parseModelSelection(selection))) ? !0 : "keys must be full provider:modelId selections"));
15
+
16
+ export class ModelSettingsError extends Schema.TaggedError()("ModelSettingsError", {
17
+ reason: Schema.Literals(["Settings", "Selection", "OutputLimit", "Thinking", "Effort", "Budget", "ToolChoice", "Binding"]),
18
+ message: Schema.String
19
+ }) {
20
+ }
21
+ export const AnthropicEffort = ReasoningEffort.pick(["low", "medium", "high", "xhigh", "max"]), ResolvedAnthropicSettings = Schema.Struct({
22
+ selection: Schema.String,
23
+ thinking: Schema.OptionFromNullOr(ModelThinking),
24
+ effort: Schema.OptionFromNullOr(AnthropicEffort),
25
+ maxOutputTokens: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
26
+ promptCache: PromptCacheSettings,
27
+ limits: Schema.optionalKey(ModelLimits)
28
+ });
@@ -0,0 +1,61 @@
1
+ import { Schema } from "effect";
2
+ /** Reported cumulative counts; an absent field is not a reported zero. */
3
+ export declare const ProviderUsageCounts: Schema.Struct<{
4
+ readonly inputTokens: Schema.optionalKey<Schema.Int>;
5
+ readonly outputTokens: Schema.optionalKey<Schema.Int>;
6
+ readonly cacheReadTokens: Schema.optionalKey<Schema.Int>;
7
+ readonly cacheWriteTokens: Schema.optionalKey<Schema.Int>;
8
+ readonly reasoningTokens: Schema.optionalKey<Schema.Int>;
9
+ }>;
10
+ export type ProviderUsageCounts = typeof ProviderUsageCounts.Type;
11
+ export declare const ProviderUsageObservation: Schema.toTaggedUnion<"_tag", readonly [Schema.TaggedStruct<"Collecting", {
12
+ readonly counts: Schema.Struct<{
13
+ readonly inputTokens: Schema.optionalKey<Schema.Int>;
14
+ readonly outputTokens: Schema.optionalKey<Schema.Int>;
15
+ readonly cacheReadTokens: Schema.optionalKey<Schema.Int>;
16
+ readonly cacheWriteTokens: Schema.optionalKey<Schema.Int>;
17
+ readonly reasoningTokens: Schema.optionalKey<Schema.Int>;
18
+ }>;
19
+ }>, Schema.TaggedStruct<"Final", {
20
+ readonly counts: Schema.Struct<{
21
+ readonly inputTokens: Schema.optionalKey<Schema.Int>;
22
+ readonly outputTokens: Schema.optionalKey<Schema.Int>;
23
+ readonly cacheReadTokens: Schema.optionalKey<Schema.Int>;
24
+ readonly cacheWriteTokens: Schema.optionalKey<Schema.Int>;
25
+ readonly reasoningTokens: Schema.optionalKey<Schema.Int>;
26
+ }>;
27
+ }>]>;
28
+ export type ProviderUsageObservation = typeof ProviderUsageObservation.Type;
29
+ export declare const noProviderUsage: ProviderUsageObservation;
30
+ export declare const AnthropicUsageCounts: Schema.Struct<{
31
+ readonly input_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
32
+ readonly output_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
33
+ readonly cache_read_input_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
34
+ readonly cache_creation_input_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
35
+ }>;
36
+ export declare const OpenAiUsageCounts: Schema.Struct<{
37
+ readonly input_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
38
+ readonly prompt_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
39
+ readonly output_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
40
+ readonly completion_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
41
+ readonly prompt_cache_hit_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
42
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
43
+ readonly input_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
44
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
45
+ readonly cache_write_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
46
+ }>>>;
47
+ readonly prompt_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
48
+ readonly cached_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
49
+ readonly cache_write_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
50
+ }>>>;
51
+ readonly output_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
52
+ readonly reasoning_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
53
+ }>>>;
54
+ readonly completion_tokens_details: Schema.optionalKey<Schema.NullOr<Schema.Struct<{
55
+ readonly reasoning_tokens: Schema.optionalKey<Schema.NullOr<Schema.Int>>;
56
+ }>>>;
57
+ }>;
58
+ /** The usage envelope is observed before downstream completion/schema acceptance. */
59
+ export declare const ProviderUsageBody: Schema.Struct<{
60
+ readonly usage: Schema.optionalKey<Schema.Unknown>;
61
+ }>;
@@ -0,0 +1,11 @@
1
+ import { Option } from "effect";
2
+ import type { ModelCallUsage } from "@xandreed/core";
3
+ import type { ProviderFailureProtocol } from "./provider-failure.entity.js";
4
+ import { ProviderUsageCounts } from "./model-usage.entity.js";
5
+ import type { ProviderUsageObservation } from "./model-usage.entity.js";
6
+ /** Read only the supported provider's token counters before absent fields get SDK defaults. */
7
+ export declare const providerUsageCounts: (protocol: ProviderFailureProtocol, value: unknown, reasoning?: boolean) => Option.Option<ProviderUsageCounts>;
8
+ /** Reported cumulative fields replace their earlier values; null/absent fields never erase facts. */
9
+ export declare const observeProviderUsage: (before: ProviderUsageObservation, counts: ProviderUsageCounts, final: boolean) => ProviderUsageObservation;
10
+ /** Normalize the observed subtotal while preserving coverage and finality separately. */
11
+ export declare const modelCallUsageOf: (protocol: ProviderFailureProtocol, observation: ProviderUsageObservation) => ModelCallUsage;
@@ -0,0 +1,24 @@
1
+ import { Option, Schema } from "effect";
2
+ import { tokenUsage } from "@xandreed/core";
3
+ import { AnthropicUsageCounts, OpenAiUsageCounts, ProviderUsageCounts } from "./model-usage.entity.js";
4
+ const present = (key, value) => value == null ? {} : { [key]: value };
5
+ export const providerUsageCounts = (protocol, value, reasoning = !0) => protocol === "anthropic" ? Option.map(Schema.decodeUnknownOption(AnthropicUsageCounts)(value), (usage) => ({
6
+ ...present("inputTokens", usage.input_tokens),
7
+ ...present("outputTokens", usage.output_tokens),
8
+ ...present("cacheReadTokens", usage.cache_read_input_tokens),
9
+ ...present("cacheWriteTokens", usage.cache_creation_input_tokens)
10
+ })) : Option.map(Schema.decodeUnknownOption(OpenAiUsageCounts)(value), (usage) => ({
11
+ ...present("inputTokens", usage.input_tokens ?? usage.prompt_tokens),
12
+ ...present("outputTokens", usage.output_tokens ?? usage.completion_tokens),
13
+ ...present("cacheReadTokens", usage.prompt_cache_hit_tokens ?? usage.cached_tokens ?? usage.prompt_tokens_details?.cached_tokens ?? usage.input_tokens_details?.cached_tokens),
14
+ ...present("cacheWriteTokens", usage.prompt_tokens_details?.cache_write_tokens ?? usage.input_tokens_details?.cache_write_tokens),
15
+ ...reasoning ? present("reasoningTokens", usage.output_tokens_details?.reasoning_tokens ?? usage.completion_tokens_details?.reasoning_tokens) : {}
16
+ })), observeProviderUsage = (before, counts, final) => ({ _tag: final && counts.outputTokens !== void 0 ? "Final" : before._tag, counts: { ...before.counts, ...counts } }), modelCallUsageOf = (protocol, observation) => {
17
+ const counts = observation.counts, reported = Object.keys(ProviderUsageCounts.fields).filter((key) => counts[key] !== void 0);
18
+ if (reported.length === 0)
19
+ return { _tag: "Unknown" };
20
+ const inputTokens = (counts.inputTokens ?? 0) + (protocol === "anthropic" ? (counts.cacheReadTokens ?? 0) + (counts.cacheWriteTokens ?? 0) : 0), usage = tokenUsage({ ...counts, inputTokens, outputTokens: counts.outputTokens ?? 0 });
21
+ if (Object.values(usage).some((count) => !Number.isSafeInteger(count)))
22
+ return { _tag: "Unknown" };
23
+ return observation._tag === "Final" && counts.inputTokens !== void 0 && counts.outputTokens !== void 0 ? { _tag: "Known", usage } : { _tag: "Partial", usage, reported };
24
+ };
@@ -0,0 +1,19 @@
1
+ import { Schema } from "effect";
2
+ const count = Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)));
3
+ export const ProviderUsageCounts = Schema.Struct({ inputTokens: count, outputTokens: count, cacheReadTokens: count, cacheWriteTokens: count, reasoningTokens: count }), ProviderUsageObservation = Schema.Union([
4
+ Schema.TaggedStruct("Collecting", { counts: ProviderUsageCounts }),
5
+ Schema.TaggedStruct("Final", { counts: ProviderUsageCounts })
6
+ ]).pipe(Schema.toTaggedUnion("_tag")), noProviderUsage = { _tag: "Collecting", counts: {} };
7
+ const wireCount = Schema.optionalKey(Schema.NullOr(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))));
8
+ export const AnthropicUsageCounts = Schema.Struct({ input_tokens: wireCount, output_tokens: wireCount, cache_read_input_tokens: wireCount, cache_creation_input_tokens: wireCount }), OpenAiUsageCounts = Schema.Struct({
9
+ input_tokens: wireCount,
10
+ prompt_tokens: wireCount,
11
+ output_tokens: wireCount,
12
+ completion_tokens: wireCount,
13
+ prompt_cache_hit_tokens: wireCount,
14
+ cached_tokens: wireCount,
15
+ input_tokens_details: Schema.optionalKey(Schema.NullOr(Schema.Struct({ cached_tokens: wireCount, cache_write_tokens: wireCount }))),
16
+ prompt_tokens_details: Schema.optionalKey(Schema.NullOr(Schema.Struct({ cached_tokens: wireCount, cache_write_tokens: wireCount }))),
17
+ output_tokens_details: Schema.optionalKey(Schema.NullOr(Schema.Struct({ reasoning_tokens: wireCount }))),
18
+ completion_tokens_details: Schema.optionalKey(Schema.NullOr(Schema.Struct({ reasoning_tokens: wireCount })))
19
+ }), ProviderUsageBody = Schema.Struct({ usage: Schema.optionalKey(Schema.Unknown) });
@@ -1,10 +1,50 @@
1
1
  import { Layer } from "effect";
2
- import { AuthStore, ModelCatalog } from "@xandreed/core";
3
- import type { Credential, ModelCatalogEntryType } from "@xandreed/core";
4
- export declare const configuredModelCatalog: (credentials: ReadonlyMap<string, Credential>) => ReadonlyArray<ModelCatalogEntryType>;
5
- export type ReasoningEffort = "none" | "low" | "medium" | "high" | "xhigh" | "max";
6
- /** The reasoning controls accepted by each routed model. `none` on the
7
- * gpt-5.6 subscription dialect is live-probed (2026-07-16: accepted,
8
- * reasoning_tokens 0; `minimal` rejected). */
2
+ import { ModelCatalog } from "@xandreed/core";
3
+ import { AuthStore } from "../ports/auth-store.port.js";
4
+ import type { ModelCatalogEntryType, ReasoningEffortType } from "@xandreed/core";
5
+ import type { Credential } from "../ports/auth-store.port.js";
6
+ import type { ModelEndpoints } from "../ports/model-endpoints.port.js";
7
+ import type { ModelSettingsMap } from "./model-settings.entity.js";
8
+ /** OpenCode serves its GPT models through the OpenAI Responses protocol, the others through chat completions. */
9
+ export declare const usesOpenCodeResponses: (modelId: string) => boolean;
10
+ /**
11
+ * Whether a built-in route sends a model images: Anthropic's Messages API,
12
+ * the OpenAI Responses routes (`openai`, `openai-codex` but its text-only
13
+ * Spark model, OpenCode's `gpt-*`) and Gemini's chat completions do;
14
+ * OpenCode's other models are text-only until each is verified. A
15
+ * configured endpoint says so itself (`ModelEndpoint.images`). Where it is
16
+ * false, a request's images are sent as label lines.
17
+ *
18
+ * @public
19
+ */
20
+ export declare const imageInputFor: (selection: string) => boolean;
21
+ /**
22
+ * The models of every provider with a credential. Each entry's `input` is
23
+ * what its route reads, as `buildProvider` routes it: `endpoints` (the
24
+ * models plugin's configured ones; none by default) replace a built-in
25
+ * route of their name.
26
+ */
27
+ export declare const configuredModelCatalog: (credentials: ReadonlyMap<string, Credential>, endpoints?: ModelEndpoints, settings?: ModelSettingsMap) => ReadonlyArray<ModelCatalogEntryType>;
28
+ /** @public */
29
+ export type ReasoningEffort = ReasoningEffortType;
30
+ /**
31
+ * A built-in model profile's reasoning controls. Configured route overrides are not inferred here.
32
+ *
33
+ * @public
34
+ */
9
35
  export declare const reasoningEffortsFor: (selection: string) => ReadonlyArray<ReasoningEffort>;
36
+ /** The efforts of the public OpenAI API's vocabulary, which the OpenAI Responses routes and the chat-completions gateways take. */
37
+ export type PublicReasoningEffort = "low" | "medium" | "high";
38
+ /**
39
+ * An effort in the public vocabulary: `xhigh` and `max` are `high`, `none`
40
+ * is `low`, the others themselves. Only the subscription dialect takes the
41
+ * whole vocabulary (and only it is probe-verified for `none`). Anthropic
42
+ * Messages uses its own native effort resolution instead of this clamp.
43
+ */
44
+ export declare const publicReasoningEffort: (effort: ReasoningEffort) => PublicReasoningEffort;
45
+ /**
46
+ * The catalog over the AuthStore's credentials and the configured endpoints (`CurrentModelEndpoints`).
47
+ *
48
+ * @public
49
+ */
10
50
  export declare const ConfiguredModelCatalogLive: Layer.Layer<ModelCatalog, never, AuthStore>;
@@ -1,58 +1,32 @@
1
- import { Effect, Layer } from "effect";
2
- import { AuthStore, ModelCatalog } from "@xandreed/core";
3
- const OPENCODE_MODELS = [
4
- "glm-5.2",
5
- "glm-5.1",
6
- "kimi-k2.7-code",
7
- "kimi-k2.6",
8
- "mimo-v2.5-pro",
9
- "mimo-v2.5",
10
- "qwen3.7-max",
11
- "qwen3.7-plus",
12
- "qwen3.6-plus",
13
- "minimax-m3",
14
- "minimax-m2.7",
15
- "deepseek-v4-pro",
16
- "deepseek-v4-flash"
17
- ], MODELS = {
18
- opencode: OPENCODE_MODELS,
19
- "openai-codex": [
20
- "gpt-5.3-codex-spark",
21
- "gpt-5.4",
22
- "gpt-5.4-mini",
23
- "gpt-5.5",
24
- "gpt-5.6-luna",
25
- "gpt-5.6-sol",
26
- "gpt-5.6-terra"
27
- ],
28
- openai: [
29
- "gpt-5",
30
- "gpt-5.1",
31
- "gpt-5.2",
32
- "gpt-5.4",
33
- "gpt-5.4-mini",
34
- "gpt-5.4-nano",
35
- "gpt-5.5",
36
- "gpt-5.6-luna",
37
- "gpt-5.6-sol",
38
- "gpt-5.6-terra"
39
- ],
40
- anthropic: ["claude-fable-5", "claude-haiku-4-5", "claude-opus-4-8", "claude-sonnet-5"],
41
- google: ["gemini-3-flash", "gemini-3.1-pro", "gemini-3.5-flash"]
1
+ import { Effect, Layer, Option } from "effect";
2
+ import { ModelCatalog } from "@xandreed/core";
3
+ import { AuthStore } from "../ports/auth-store.port.js";
4
+ import { CurrentModelEndpoints } from "../ports/model-endpoints.port.js";
5
+ import { CurrentModelSettings } from "../ports/model-settings.port.js";
6
+ import { anthropicProfileOf, modelProfileOf, profileOf, PROVIDER_PROFILES } from "./provider-profile.entity.functions.js";
7
+ export const usesOpenCodeResponses = (modelId) => PROVIDER_PROFILES.opencode.routeOf(modelId)._tag === "openai-responses", imageInputFor = (selection) => Option.exists(modelProfileOf(selection), (model) => model.images);
8
+ const routeReadsImages = (provider, model, credential, endpoints) => Option.match(Object.hasOwn(endpoints, provider) ? Option.fromNullishOr(endpoints[provider]) : Option.none(), {
9
+ onSome: (endpoint) => endpoint.images ?? (endpoint.api === "anthropic-messages" ? anthropicProfileOf(model.id).images : !1),
10
+ onNone: () => !(credential.type === "local" && credential.baseUrl !== void 0) && model.images
11
+ }), routeLimits = (provider, model, credential, endpoints, settings) => {
12
+ if (!Option.match(Object.hasOwn(endpoints, provider) ? Option.fromNullishOr(endpoints[provider]) : Option.none(), {
13
+ onSome: (endpoint) => endpoint.api === "anthropic-messages",
14
+ onNone: () => !(credential.type === "local" && credential.baseUrl !== void 0) && Option.exists(profileOf(provider), (profile) => profile.routeOf(model.id)._tag === "anthropic")
15
+ }))
16
+ return Option.none();
17
+ const selection = `${provider}:${model.id}`, configured = Object.hasOwn(settings, selection) ? settings[selection]?.limits : void 0, limits = { ...anthropicProfileOf(model.id).limits, ...configured };
18
+ return Object.keys(limits).length === 0 ? Option.none() : Option.some(limits);
42
19
  };
43
- export const configuredModelCatalog = (credentials) => [...credentials].flatMap(([provider, credential]) => (MODELS[provider] ?? []).map((model) => ({
44
- selection: `${provider}:${model}`,
45
- label: provider === "openai-codex" ? `OpenAI subscription \xB7 ${model}` : provider === "openai" ? `OpenAI API key \xB7 ${model}` : void 0,
46
- provider,
47
- credential: credential.type
48
- }))), reasoningEffortsFor = (selection) => {
49
- if (/^openai-codex:gpt-5\.6-(luna|sol|terra)$/.test(selection))
50
- return ["none", "low", "medium", "high", "xhigh", "max"];
51
- if (/^openai-codex:gpt-5/.test(selection))
52
- return ["low", "medium", "high", "xhigh"];
53
- if (/^openai:gpt-5/.test(selection))
54
- return ["low", "medium", "high"];
55
- return [];
56
- }, ConfiguredModelCatalogLive = Layer.effect(ModelCatalog, Effect.map(AuthStore, (auth) => ({
57
- list: auth.all.pipe(Effect.orElseSucceed(() => new Map), Effect.map(configuredModelCatalog))
58
- })));
20
+ export const configuredModelCatalog = (credentials, endpoints = {}, settings = {}) => [...credentials].flatMap(([provider, credential]) => Option.match(profileOf(provider), {
21
+ onNone: () => [],
22
+ onSome: (profile) => profile.models.map((model) => ({
23
+ selection: `${provider}:${model.id}`,
24
+ ...Option.match(profile.label, { onNone: () => ({}), onSome: (label) => ({ label: `${label} \xB7 ${model.id}` }) }),
25
+ provider,
26
+ credential: credential.type,
27
+ input: routeReadsImages(provider, model, credential, endpoints) ? ["text", "image"] : ["text"],
28
+ ...Option.match(routeLimits(provider, model, credential, endpoints, settings), { onNone: () => ({}), onSome: (limits) => ({ limits }) })
29
+ }))
30
+ })), reasoningEffortsFor = (selection) => Option.match(modelProfileOf(selection), { onNone: () => [], onSome: (model) => model.efforts }), publicReasoningEffort = (effort) => effort === "xhigh" || effort === "max" ? "high" : effort === "none" ? "low" : effort, ConfiguredModelCatalogLive = Layer.effect(ModelCatalog, Effect.all({ auth: AuthStore, endpoints: Effect.service(CurrentModelEndpoints), settings: Effect.service(CurrentModelSettings) }, { concurrency: 1 }).pipe(Effect.map(({ auth, endpoints, settings }) => ({
31
+ list: auth.all.pipe(Effect.tapError((error) => Effect.logWarning("the model catalog could not read the stored credentials; it lists none of them", error)), Effect.orElseSucceed(() => new Map), Effect.map((credentials) => configuredModelCatalog(credentials, endpoints, settings)))
32
+ }))));
@@ -1,14 +1,25 @@
1
- import { LanguageModel } from "effect/ai";
1
+ import { AiError, LanguageModel } from "effect/ai";
2
2
  import { HttpClient } from "effect/http";
3
- import { Effect, Redacted } from "effect";
3
+ import { Effect, Option, Redacted, Schema } from "effect";
4
+ import type { ReasoningEffortType } from "@xandreed/core";
5
+ import type { WebSocketProxy } from "../ports/models-transport.port.js";
4
6
  export declare const OPENAI_CODEX_API_URL = "https://chatgpt.com/backend-api/codex";
5
- type Json = Record<string, unknown>;
6
- /** Translate the standard Responses request into the Codex subscription dialect. */
7
- export declare const toOpenAiCodexRequestBody: (body: unknown, reasoningEffort?: "none" | "low" | "medium" | "high" | "xhigh" | "max") => Json;
8
- export declare const mapOpenAiCodexError: (model: string, error: unknown) => unknown;
7
+ /**
8
+ * Translate the standard Responses request into the Codex subscription
9
+ * dialect. A body that is not a Responses request object reads as an empty
10
+ * one.
11
+ */
12
+ export declare const toOpenAiCodexRequestBody: (body: unknown, reasoningEffort?: ReasoningEffortType) => Schema.JsonObject;
13
+ /**
14
+ * Refine structured subscription errors before the legacy protocol fallback.
15
+ * Keep concrete reason classes and HTTP/provider evidence; explicit semantic
16
+ * stamps prevent a protocol failure from becoming a model-output corrective.
17
+ */
18
+ export declare const mapOpenAiCodexError: <E>(_model: string, error: E) => E | AiError.AiError;
9
19
  export declare const makeOpenAiCodexLanguageModel: (args: {
10
20
  readonly model: string;
11
21
  readonly accessToken: Redacted.Redacted<string>;
12
22
  readonly accountId: string;
23
+ /** How the WebSocket reaches ChatGPT (`ModelsTransport.webSocketAgent`); default: directly. */
24
+ readonly webSocketProxy?: Option.Option<WebSocketProxy>;
13
25
  }) => Effect.Effect<LanguageModel.LanguageModel, never, HttpClient.HttpClient>;
14
- export {};