@xandreed/plugin-models 0.14.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 +30 -10
  3. package/dist/auth/anthropicOAuth.js +15 -18
  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 +23 -5
  7. package/dist/auth/credential-input.entity.functions.d.ts +15 -5
  8. package/dist/auth/credential-input.entity.functions.js +12 -9
  9. package/dist/auth/credential-input.entity.js +5 -3
  10. package/dist/auth/credential-refresh.entity.d.ts +11 -3
  11. package/dist/auth/credential-refresh.entity.functions.d.ts +48 -6
  12. package/dist/auth/credential-refresh.entity.functions.js +12 -3
  13. package/dist/auth/localAuth.d.ts +10 -1
  14. package/dist/auth/localAuth.js +53 -79
  15. package/dist/auth/memory-auth.adapter.d.ts +12 -4
  16. package/dist/auth/memory-auth.adapter.js +13 -25
  17. package/dist/auth/openAiCodexOAuth.d.ts +26 -5
  18. package/dist/auth/openAiCodexOAuth.js +17 -28
  19. package/dist/auth/token-response.entity.d.ts +21 -0
  20. package/dist/auth/token-response.entity.functions.d.ts +20 -0
  21. package/dist/auth/token-response.entity.functions.js +10 -0
  22. package/dist/auth/token-response.entity.js +10 -0
  23. package/dist/compat/index.d.ts +2 -0
  24. package/dist/compat/index.js +1 -0
  25. package/dist/conformance/index.d.ts +6 -0
  26. package/dist/conformance/index.js +3 -0
  27. package/dist/index.d.ts +19 -19
  28. package/dist/index.js +17 -69
  29. package/dist/llm/chat-wire.entity.d.ts +123 -0
  30. package/dist/llm/chat-wire.entity.functions.d.ts +28 -0
  31. package/dist/llm/chat-wire.entity.functions.js +10 -0
  32. package/dist/llm/chat-wire.entity.js +37 -0
  33. package/dist/llm/compat-telemetry.adapter.d.ts +6 -0
  34. package/dist/llm/compat-telemetry.adapter.js +21 -0
  35. package/dist/llm/compat.d.ts +50 -30
  36. package/dist/llm/compat.js +149 -133
  37. package/dist/llm/model-completion.adapter.d.ts +49 -0
  38. package/dist/llm/model-completion.adapter.js +77 -0
  39. package/dist/llm/model-completion.entity.d.ts +4 -0
  40. package/dist/llm/model-completion.entity.functions.d.ts +3 -0
  41. package/dist/llm/model-completion.entity.functions.js +7 -0
  42. package/dist/llm/model-completion.entity.js +7 -0
  43. package/dist/llm/model-retry-policy.entity.d.ts +37 -0
  44. package/dist/llm/model-retry-policy.entity.functions.d.ts +16 -0
  45. package/dist/llm/model-retry-policy.entity.functions.js +33 -0
  46. package/dist/llm/model-retry-policy.entity.js +23 -0
  47. package/dist/llm/model-settings.entity.d.ts +85 -0
  48. package/dist/llm/model-settings.entity.functions.d.ts +11 -0
  49. package/dist/llm/model-settings.entity.functions.js +49 -0
  50. package/dist/llm/model-settings.entity.js +28 -0
  51. package/dist/llm/model-usage.entity.d.ts +61 -0
  52. package/dist/llm/model-usage.entity.functions.d.ts +11 -0
  53. package/dist/llm/model-usage.entity.functions.js +24 -0
  54. package/dist/llm/model-usage.entity.js +19 -0
  55. package/dist/llm/modelCatalog.d.ts +29 -8
  56. package/dist/llm/modelCatalog.js +28 -66
  57. package/dist/llm/openAiCodex.d.ts +15 -7
  58. package/dist/llm/openAiCodex.js +57 -45
  59. package/dist/llm/openAiCodexWebSocket.d.ts +48 -15
  60. package/dist/llm/openAiCodexWebSocket.js +101 -147
  61. package/dist/llm/prompt-cache-frame.entity.d.ts +18 -0
  62. package/dist/llm/prompt-cache-frame.entity.js +10 -0
  63. package/dist/llm/prompt-cache.entity.d.ts +57 -0
  64. package/dist/llm/prompt-cache.entity.functions.d.ts +8 -0
  65. package/dist/llm/prompt-cache.entity.functions.js +73 -0
  66. package/dist/llm/prompt-cache.entity.js +27 -0
  67. package/dist/llm/provider-client.adapter.d.ts +9 -0
  68. package/dist/llm/provider-client.adapter.js +60 -0
  69. package/dist/llm/provider-failure.entity.d.ts +42 -0
  70. package/dist/llm/provider-failure.entity.functions.d.ts +8 -0
  71. package/dist/llm/provider-failure.entity.functions.js +67 -0
  72. package/dist/llm/provider-failure.entity.js +20 -0
  73. package/dist/llm/provider-profile.entity.d.ts +79 -0
  74. package/dist/llm/provider-profile.entity.functions.d.ts +23 -0
  75. package/dist/llm/provider-profile.entity.functions.js +83 -0
  76. package/dist/llm/provider-profile.entity.js +10 -0
  77. package/dist/llm/providers.d.ts +116 -34
  78. package/dist/llm/providers.js +298 -129
  79. package/dist/llm/retry.d.ts +11 -47
  80. package/dist/llm/retry.js +33 -52
  81. package/dist/llm/router.d.ts +54 -36
  82. package/dist/llm/router.js +91 -78
  83. package/dist/llm/sse.d.ts +44 -37
  84. package/dist/llm/sse.js +43 -35
  85. package/dist/llm/thinking-binding.entity.d.ts +49 -0
  86. package/dist/llm/thinking-binding.entity.functions.d.ts +21 -0
  87. package/dist/llm/thinking-binding.entity.functions.js +16 -0
  88. package/dist/llm/thinking-binding.entity.js +11 -0
  89. package/dist/llm/utilityLlm.d.ts +14 -5
  90. package/dist/llm/utilityLlm.js +18 -13
  91. package/dist/plugin.adapter.d.ts +94 -6
  92. package/dist/plugin.adapter.js +18 -13
  93. package/dist/ports/auth-store.port.d.ts +73 -0
  94. package/dist/ports/auth-store.port.js +24 -0
  95. package/dist/ports/host-credentials.port.d.ts +2 -1
  96. package/dist/ports/model-call-completion.port.d.ts +9 -0
  97. package/dist/ports/model-call-completion.port.js +2 -0
  98. package/dist/ports/model-endpoints.port.d.ts +60 -26
  99. package/dist/ports/model-endpoints.port.js +17 -3
  100. package/dist/ports/model-retry-policy.port.d.ts +17 -0
  101. package/dist/ports/model-retry-policy.port.js +3 -0
  102. package/dist/ports/model-settings.port.d.ts +32 -0
  103. package/dist/ports/model-settings.port.js +4 -0
  104. package/dist/ports/models-transport.port.d.ts +9 -2
  105. package/dist/ports/models-transport.port.js +4 -1
  106. package/dist/ports/prompt-cache-frame.port.d.ts +21 -0
  107. package/dist/ports/prompt-cache-frame.port.js +4 -0
  108. package/dist/ports/settings-store.port.d.ts +70 -0
  109. package/dist/ports/settings-store.port.js +33 -0
  110. package/dist/ports/thinking-binding.port.d.ts +7 -0
  111. package/dist/ports/thinking-binding.port.js +4 -0
  112. package/dist/settings/localSettings.d.ts +2 -1
  113. package/dist/settings/localSettings.js +17 -20
  114. package/dist/settings/memory-settings.adapter.d.ts +3 -1
  115. package/dist/settings/memory-settings.adapter.js +1 -1
  116. package/dist/settings/roleView.d.ts +3 -1
  117. package/dist/settings/roleView.js +1 -1
  118. package/dist/settings/settings-store.conformance.d.ts +47 -0
  119. package/dist/settings/settings-store.conformance.js +100 -0
  120. package/dist/settings/settings-value.entity.d.ts +10 -2
  121. package/dist/settings/settings-value.entity.functions.d.ts +31 -9
  122. package/dist/settings/settings-value.entity.functions.js +5 -6
  123. package/dist/storage/json-file.d.ts +18 -0
  124. package/dist/storage/json-file.js +26 -0
  125. package/dist/transport/http-proxy.entity.d.ts +17 -3
  126. package/dist/transport/model-request.conformance.d.ts +13 -0
  127. package/dist/transport/model-request.conformance.js +36 -0
  128. package/dist/transport/models-transport.adapter.d.ts +18 -17
  129. package/dist/transport/models-transport.adapter.js +28 -31
  130. package/dist/transport/resilient-http-client.adapter.d.ts +73 -0
  131. package/dist/transport/resilient-http-client.adapter.js +177 -0
  132. package/package.json +11 -5
  133. package/dist/auth/anthropicOAuth.test.d.ts +0 -1
  134. package/dist/auth/credential-refresh.entity.functions.test.d.ts +0 -1
  135. package/dist/auth/localAuth.test.d.ts +0 -1
  136. package/dist/auth/memory-auth.adapter.test.d.ts +0 -1
  137. package/dist/llm/compat.test.d.ts +0 -1
  138. package/dist/llm/modelCatalog.test.d.ts +0 -1
  139. package/dist/llm/openAiCodexWebSocket.test.d.ts +0 -1
  140. package/dist/llm/providers.test.d.ts +0 -1
  141. package/dist/llm/retry.test.d.ts +0 -1
  142. package/dist/llm/router.test.d.ts +0 -1
  143. package/dist/llm/sse.test.d.ts +0 -1
  144. package/dist/plugin.adapter.test.d.ts +0 -1
  145. package/dist/settings/localSettings.test.d.ts +0 -1
  146. package/dist/settings/memory-settings.adapter.test.d.ts +0 -1
  147. package/dist/settings/roleView.test.d.ts +0 -1
  148. package/dist/settings/settings-value.entity.functions.test.d.ts +0 -1
  149. package/dist/transport/http-proxy.entity.functions.test.d.ts +0 -1
  150. package/dist/transport/models-transport.adapter.test.d.ts +0 -1
  151. package/dist/transport/recording-proxy.testing.d.ts +0 -33
@@ -1,6 +1,7 @@
1
1
  import { Context } from "effect";
2
2
  import type { Effect, Option } from "effect";
3
- import type { AuthError, ProviderId } from "@xandreed/core";
3
+ import type { ProviderId } from "@xandreed/core";
4
+ import type { AuthError } from "./auth-store.port.js";
4
5
  import type { CredentialInput } from "../auth/credential-input.entity.js";
5
6
  declare const HostCredentials_base: Context.ServiceClass<HostCredentials, "@xandreed/plugin-models/HostCredentials", {
6
7
  readonly load: Effect.Effect<ReadonlyMap<string, CredentialInput>, AuthError>;
@@ -0,0 +1,9 @@
1
+ import { Context, Option } from "effect";
2
+ import type { ModelSelection } from "@xandreed/core";
3
+ /** Private execution-time purpose; utilities provide Helper without recording another call. */
4
+ export declare const CurrentModelCallPurpose: Context.Reference<"Loop" | "Helper">;
5
+ /** Private invocation metadata; a routed object call can use a provider's text implementation. */
6
+ export declare const CurrentModelCallMethod: Context.Reference<"Text" | "Object" | "Stream">;
7
+ export declare const CurrentModelCallSelection: Context.Reference<Option.Option<ModelSelection>>;
8
+ /** Identifies which logical budget owns inherited invocation metadata. */
9
+ export declare const CurrentModelCallMetadataOwner: Context.Reference<Option.Option<string & import("effect/Brand").Brand<"ModelCallId">>>;
@@ -0,0 +1,2 @@
1
+ import { Context, Option } from "effect";
2
+ export const CurrentModelCallPurpose = Context.Reference("@xandreed/plugin-models/CurrentModelCallPurpose", { defaultValue: () => "Loop" }), CurrentModelCallMethod = Context.Reference("@xandreed/plugin-models/CurrentModelCallMethod", { defaultValue: () => "Text" }), CurrentModelCallSelection = Context.Reference("@xandreed/plugin-models/CurrentModelCallSelection", { defaultValue: () => Option.none() }), CurrentModelCallMetadataOwner = Context.Reference("@xandreed/plugin-models/CurrentModelCallMetadataOwner", { defaultValue: () => Option.none() });
@@ -1,46 +1,80 @@
1
1
  import { Context, Schema } from "effect";
2
+ /** A base URL using chat completions, or an explicit Anthropic Messages protocol. @public */
3
+ export declare const ModelEndpoint: Schema.Union<readonly [Schema.Struct<{
4
+ /** Absent keeps the existing chat-completions protocol. */
5
+ readonly api: Schema.optionalKey<Schema.Literal<"chat-completions">>;
6
+ readonly standardOnly: Schema.optionalKey<Schema.Boolean>;
7
+ readonly url: Schema.String;
8
+ readonly apiKey: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Redacted<Schema.String>]>>;
9
+ readonly apiKeyEnv: Schema.optionalKey<Schema.String>;
10
+ readonly images: Schema.optionalKey<Schema.Boolean>;
11
+ }>, Schema.Struct<{
12
+ readonly api: Schema.Literal<"anthropic-messages">;
13
+ /** The API key is opaque in either header; omission uses x-api-key. */
14
+ readonly auth: Schema.optionalKey<Schema.Literals<readonly ["x-api-key", "bearer"]>>;
15
+ /** False disables framework caching and refuses caller cache controls. */
16
+ readonly cacheControl: Schema.optionalKey<Schema.Boolean>;
17
+ /** Preserve the pinned SDK's version by default; an explicit version is sent verbatim. */
18
+ readonly apiVersion: Schema.optionalKey<Schema.String>;
19
+ /** Added to the SDK's own beta flags, in order with duplicates removed. */
20
+ readonly betas: Schema.optionalKey<Schema.$Array<Schema.String>>;
21
+ readonly url: Schema.String;
22
+ readonly apiKey: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Redacted<Schema.String>]>>;
23
+ readonly apiKeyEnv: Schema.optionalKey<Schema.String>;
24
+ readonly images: Schema.optionalKey<Schema.Boolean>;
25
+ }>]>;
26
+ export type ModelEndpoint = typeof ModelEndpoint.Type;
2
27
  /**
3
- * An OpenAI-compatible endpoint a host names in configuration: the model
4
- * `<name>:<model id>` is sent to `<url>/chat/completions`. The key is
5
- * optional — a local server or a private gateway may need none. It comes
6
- * from `apiKey`, then the environment variable `apiKeyEnv` names, then the
7
- * AuthStore's key for `<name>`; with none, no Authorization header is sent.
8
- * An endpoint with its own key (`apiKey`, or a set `apiKeyEnv`) never reads
9
- * the AuthStore; an empty one (`apiKey: ""`, or a variable set to "") sends
10
- * no key. A host's code passes `apiKey` as a `Redacted` string so it
11
- * prints as `<redacted>` in errors and graph fingerprints; JSON
12
- * configuration names an environment variable in `apiKeyEnv` instead.
13
- * `standardOnly` (default true) sends only standard chat-completions fields.
14
- * `images` (default false) says its models read images: a user's images are
15
- * then sent as `image_url` parts, and a tool result's in one user message
16
- * after the tool results; without it they are label lines.
17
- * The endpoint's usage is read in full: cache reads and writes from
18
- * `prompt_tokens_details`, reasoning from `completion_tokens_details`.
28
+ * Endpoints by provider name; a name that matches a built-in provider replaces it.
29
+ *
30
+ * @public
19
31
  */
20
- export declare const ModelEndpoint: Schema.Struct<{
32
+ export declare const ModelEndpoints: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.Struct<{
33
+ /** Absent keeps the existing chat-completions protocol. */
34
+ readonly api: Schema.optionalKey<Schema.Literal<"chat-completions">>;
35
+ readonly standardOnly: Schema.optionalKey<Schema.Boolean>;
21
36
  readonly url: Schema.String;
22
37
  readonly apiKey: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Redacted<Schema.String>]>>;
23
38
  readonly apiKeyEnv: Schema.optionalKey<Schema.String>;
24
- readonly standardOnly: Schema.optionalKey<Schema.Boolean>;
25
39
  readonly images: Schema.optionalKey<Schema.Boolean>;
26
- }>;
27
- export type ModelEndpoint = typeof ModelEndpoint.Type;
28
- /** Endpoints by provider name; a name that matches a built-in provider replaces it. */
29
- export declare const ModelEndpoints: Schema.$Record<Schema.String, Schema.Struct<{
40
+ }>, Schema.Struct<{
41
+ readonly api: Schema.Literal<"anthropic-messages">;
42
+ /** The API key is opaque in either header; omission uses x-api-key. */
43
+ readonly auth: Schema.optionalKey<Schema.Literals<readonly ["x-api-key", "bearer"]>>;
44
+ /** False disables framework caching and refuses caller cache controls. */
45
+ readonly cacheControl: Schema.optionalKey<Schema.Boolean>;
46
+ /** Preserve the pinned SDK's version by default; an explicit version is sent verbatim. */
47
+ readonly apiVersion: Schema.optionalKey<Schema.String>;
48
+ /** Added to the SDK's own beta flags, in order with duplicates removed. */
49
+ readonly betas: Schema.optionalKey<Schema.$Array<Schema.String>>;
30
50
  readonly url: Schema.String;
31
51
  readonly apiKey: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.Redacted<Schema.String>]>>;
32
52
  readonly apiKeyEnv: Schema.optionalKey<Schema.String>;
33
- readonly standardOnly: Schema.optionalKey<Schema.Boolean>;
34
53
  readonly images: Schema.optionalKey<Schema.Boolean>;
35
- }>>;
54
+ }>]>>;
36
55
  export type ModelEndpoints = typeof ModelEndpoints.Type;
37
- /** The endpoints the models plugin was configured with; none by default. */
56
+ /**
57
+ * The endpoints the models plugin was configured with; none by default.
58
+ *
59
+ * @public
60
+ */
38
61
  export declare const CurrentModelEndpoints: Context.Reference<{
39
62
  readonly [x: string]: {
40
63
  readonly url: string;
41
64
  readonly images?: boolean;
42
65
  readonly apiKey?: string | import("effect/Redacted").Redacted<string>;
43
- readonly apiKeyEnv?: string;
66
+ readonly api?: "chat-completions";
44
67
  readonly standardOnly?: boolean;
68
+ readonly apiKeyEnv?: string;
69
+ } | {
70
+ readonly url: string;
71
+ readonly api: "anthropic-messages";
72
+ readonly images?: boolean;
73
+ readonly apiKey?: string | import("effect/Redacted").Redacted<string>;
74
+ readonly auth?: "x-api-key" | "bearer";
75
+ readonly apiKeyEnv?: string;
76
+ readonly cacheControl?: boolean;
77
+ readonly apiVersion?: string;
78
+ readonly betas?: readonly string[];
45
79
  };
46
80
  }>;
@@ -1,13 +1,27 @@
1
1
  import { Context, Schema } from "effect";
2
- export const ModelEndpoint = Schema.Struct({
2
+ const endpointFields = {
3
3
  url: Schema.String,
4
4
  apiKey: Schema.optionalKey(Schema.Union([
5
5
  Schema.String,
6
6
  Schema.Redacted(Schema.String).annotate({ message: "apiKey is a string or a Redacted string" })
7
7
  ]).annotate({ message: "apiKey is a string or a Redacted string" })),
8
8
  apiKeyEnv: Schema.optionalKey(Schema.String),
9
- standardOnly: Schema.optionalKey(Schema.Boolean),
10
9
  images: Schema.optionalKey(Schema.Boolean)
11
- }).annotate({ messageUnexpectedKey: "not an option of a model endpoint" }), ModelEndpoints = Schema.Record(Schema.String, ModelEndpoint), CurrentModelEndpoints = Context.Reference("@xandreed/plugin-models/CurrentModelEndpoints", {
10
+ };
11
+ export const ModelEndpoint = Schema.Union([
12
+ Schema.Struct({
13
+ ...endpointFields,
14
+ api: Schema.optionalKey(Schema.Literal("chat-completions")),
15
+ standardOnly: Schema.optionalKey(Schema.Boolean)
16
+ }).annotate({ messageUnexpectedKey: "not an option of a chat-completions endpoint" }),
17
+ Schema.Struct({
18
+ ...endpointFields,
19
+ api: Schema.Literal("anthropic-messages"),
20
+ auth: Schema.optionalKey(Schema.Literals(["x-api-key", "bearer"])),
21
+ cacheControl: Schema.optionalKey(Schema.Boolean),
22
+ apiVersion: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}$/))),
23
+ betas: Schema.optionalKey(Schema.Array(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$/))))
24
+ }).annotate({ messageUnexpectedKey: "not an option of an anthropic-messages endpoint" })
25
+ ]).annotate({ messageUnexpectedKey: "not an option of a model endpoint" }), ModelEndpoints = Schema.Record(Schema.String, ModelEndpoint), CurrentModelEndpoints = Context.Reference("@xandreed/plugin-models/CurrentModelEndpoints", {
12
26
  defaultValue: () => ({})
13
27
  });
@@ -0,0 +1,17 @@
1
+ import { Context, Option } from "effect";
2
+ import type { ModelRequestCall } from "../transport/resilient-http-client.adapter.js";
3
+ /** Read when a logical model call executes; the complete policy overrides the defaults. @public */
4
+ export declare const CurrentModelRetryPolicy: Context.Reference<{
5
+ readonly maxAttempts: number;
6
+ readonly requestTimeoutMs: number;
7
+ readonly bodyIdleTimeoutMs: number;
8
+ readonly totalTimeoutMs: number;
9
+ readonly retryAfterMaxMs: number;
10
+ readonly backoff: {
11
+ readonly initialMs: number;
12
+ readonly maxMs: number;
13
+ readonly jitter: number;
14
+ };
15
+ }>;
16
+ /** Private per-call counter/deadline, shared only by its primary and fallback rung. */
17
+ export declare const CurrentModelRequestCall: Context.Reference<Option.Option<ModelRequestCall>>;
@@ -0,0 +1,3 @@
1
+ import { Context, Option } from "effect";
2
+ import { modelRetryDefaults } from "../llm/model-retry-policy.entity.js";
3
+ export const CurrentModelRetryPolicy = Context.Reference("@xandreed/plugin-models/CurrentModelRetryPolicy", { defaultValue: () => modelRetryDefaults }), CurrentModelRequestCall = Context.Reference("@xandreed/plugin-models/CurrentModelRequestCall", { defaultValue: () => Option.none() });
@@ -0,0 +1,32 @@
1
+ import { Context } from "effect";
2
+ /** Static per-model settings captured where the models layer is built. @public */
3
+ export declare const CurrentModelSettings: Context.Reference<{
4
+ readonly [x: string]: {
5
+ readonly effort?: "none" | "low" | "medium" | "high" | "xhigh" | "max";
6
+ readonly limits?: {
7
+ readonly maxOutputTokens?: number;
8
+ readonly contextWindow?: number;
9
+ };
10
+ readonly thinking?: {
11
+ readonly _tag: "Off";
12
+ } | {
13
+ readonly _tag: "Adaptive";
14
+ readonly display?: "omitted" | "summarized";
15
+ } | {
16
+ readonly _tag: "Budget";
17
+ readonly budgetTokens: number;
18
+ readonly display?: "omitted" | "summarized";
19
+ } | {
20
+ readonly _tag: "BetweenTools";
21
+ };
22
+ readonly promptCache?: {
23
+ readonly _tag: "Auto";
24
+ readonly ttl: "5m" | "1h";
25
+ } | {
26
+ readonly _tag: "TopLevel";
27
+ readonly ttl: "5m" | "1h";
28
+ } | {
29
+ readonly _tag: "Off";
30
+ };
31
+ };
32
+ }>;
@@ -0,0 +1,4 @@
1
+ import { Context } from "effect";
2
+ export const CurrentModelSettings = Context.Reference("@xandreed/plugin-models/CurrentModelSettings", {
3
+ defaultValue: () => ({})
4
+ });
@@ -1,5 +1,5 @@
1
- import { Context } from "effect";
2
- import type { Effect, Option, Redacted } from "effect";
1
+ import { Context, Option } from "effect";
2
+ import type { Effect, Redacted } from "effect";
3
3
  import type { Agent } from "node:http";
4
4
  import type { ProxyUnsupported } from "../transport/http-proxy.entity.js";
5
5
  /**
@@ -16,6 +16,8 @@ export type ModelsFetch = (input: string | URL, init?: RequestInit) => Promise<R
16
16
  * the runtime cannot use (`Proxy` outside Bun, `Agent` on Bun, whose `ws`
17
17
  * stand-in takes neither) fails the call rather than connecting past the
18
18
  * proxy.
19
+ *
20
+ * @public
19
21
  */
20
22
  export type WebSocketProxy = {
21
23
  readonly _tag: "Agent";
@@ -43,4 +45,9 @@ declare const ModelsTransport_base: Context.ServiceClass<ModelsTransport, "@xand
43
45
  */
44
46
  export declare class ModelsTransport extends ModelsTransport_base {
45
47
  }
48
+ /** Internal provenance: only this exact managed direct instance yields to an explicit call-local fetch. */
49
+ export declare const CurrentManagedModelsTransport: Context.Reference<Option.Option<{
50
+ readonly fetch: ModelsFetch;
51
+ readonly webSocketAgent?: (target: URL) => Effect.Effect<Option.Option<WebSocketProxy>, ProxyUnsupported>;
52
+ }>>;
46
53
  export {};
@@ -1,4 +1,7 @@
1
- import { Context } from "effect";
1
+ import { Context, Option } from "effect";
2
2
 
3
3
  export class ModelsTransport extends Context.Service()("@xandreed/plugin-models/ModelsTransport") {
4
4
  }
5
+ export const CurrentManagedModelsTransport = Context.Reference("@xandreed/plugin-models/CurrentManagedModelsTransport", {
6
+ defaultValue: Option.none
7
+ });
@@ -0,0 +1,21 @@
1
+ import { Context } from "effect";
2
+ /** A captured SDK client reads this immutable frame in the invoking call's fiber. */
3
+ export declare const CurrentPromptCacheFrame: Context.Reference<{
4
+ readonly candidates: readonly {
5
+ readonly control: {
6
+ readonly type: "ephemeral";
7
+ readonly ttl?: "5m" | "1h";
8
+ };
9
+ readonly priority: number;
10
+ }[];
11
+ readonly policy: {
12
+ readonly _tag: "Auto";
13
+ readonly ttl: "5m" | "1h";
14
+ } | {
15
+ readonly _tag: "TopLevel";
16
+ readonly ttl: "5m" | "1h";
17
+ } | {
18
+ readonly _tag: "Off";
19
+ };
20
+ readonly capability: boolean;
21
+ }>;
@@ -0,0 +1,4 @@
1
+ import { Context } from "effect";
2
+ export const CurrentPromptCacheFrame = Context.Reference("@xandreed/plugin-models/CurrentPromptCacheFrame", {
3
+ defaultValue: () => ({ candidates: [], policy: { _tag: "Off" }, capability: !0 })
4
+ });
@@ -0,0 +1,70 @@
1
+ import { Context, Effect, Option, Schema } from "effect";
2
+ declare const EngineSettings_base: Schema.Class<EngineSettings, Schema.Struct<{
3
+ readonly model: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<string, string, never, never>>>;
4
+ readonly codeModel: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<string, string, never, never>>>;
5
+ readonly fastModel: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<string, string, never, never>>>;
6
+ readonly fallbackModel: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<string, string, never, never>>>;
7
+ readonly maxAttempts: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<number, number, never, never>>>;
8
+ readonly budgetMillis: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<number, number, never, never>>>;
9
+ /** Selected after the model because the accepted levels are model-specific. */
10
+ readonly reasoningEffort: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<"none" | "low" | "medium" | "high" | "xhigh" | "max", "none" | "low" | "medium" | "high" | "xhigh" | "max", never, never>>>;
11
+ readonly codeReasoningEffort: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<"none" | "low" | "medium" | "high" | "xhigh" | "max", "none" | "low" | "medium" | "high" | "xhigh" | "max", never, never>>>;
12
+ readonly fastReasoningEffort: Schema.withConstructorDefault<Schema.OptionFromOptional<Schema.Codec<"none" | "low" | "medium" | "high" | "xhigh" | "max", "none" | "low" | "medium" | "high" | "xhigh" | "max", never, never>>>;
13
+ }>, {}>;
14
+ /** @public */
15
+ export declare class EngineSettings extends EngineSettings_base {
16
+ }
17
+ declare const SettingsError_base: Schema.Class<SettingsError, Schema.TaggedStruct<"SettingsError", {
18
+ readonly message: Schema.String;
19
+ /**
20
+ * `settings.corrupt`: the store's backing data could not be read or
21
+ * decoded. Nothing was read as defaults and nothing was written over it.
22
+ */
23
+ readonly code: Schema.optionalKey<Schema.Literal<"settings.corrupt">>;
24
+ }>, import("effect/Cause").YieldableError>;
25
+ /** @public */
26
+ export declare class SettingsError extends SettingsError_base {
27
+ }
28
+ /**
29
+ * The three model roles a selection can be persisted under.
30
+ *
31
+ * @public
32
+ */
33
+ export type ModelRole = "general" | "code" | "fast";
34
+ /** The non-role keys the keyed setter may write — a closed vocabulary, so a
35
+ * typo can never land a dead key in the user's config.
36
+ *
37
+ * @public
38
+ */
39
+ export declare const SETTINGS_KEYS: readonly ["fallbackModel", "maxAttempts", "budgetMillis", "reasoningEffort", "codeReasoningEffort", "fastReasoningEffort"];
40
+ /** @public */
41
+ export type SettingsKey = (typeof SETTINGS_KEYS)[number];
42
+ declare const SettingsStore_base: Context.ServiceClass<SettingsStore, "@xandreed/core/SettingsStore", {
43
+ readonly load: Effect.Effect<EngineSettings, SettingsError>;
44
+ /** Persist one role's model selection (a role choice); `None` clears
45
+ * the role so it falls back to its default again. A selection that is
46
+ * not `"<provider>:<modelId>"` fails with a `SettingsError` and writes
47
+ * nothing. */
48
+ readonly setRole: (role: ModelRole, selection: Option.Option<string>) => Effect.Effect<void, SettingsError>;
49
+ /** Persist one non-role key from its STRING form (a settings edit): the
50
+ * adapter validates + coerces per key ("true", "3", "provider:model")
51
+ * and rejects a value that doesn't parse with a `SettingsError`,
52
+ * writing nothing; `None` clears the key. */
53
+ readonly set: (key: SettingsKey, value: Option.Option<string>) => Effect.Effect<void, SettingsError>;
54
+ }>;
55
+ /**
56
+ * Read-through settings port. `load` re-reads the backing store on every call
57
+ * so a value changed mid-session takes effect on the next turn — callers must
58
+ * not cache the result across turns. A store may be read-only: then
59
+ * `setRole` and `set` fail with a `SettingsError` and write nothing. A store
60
+ * whose backing data cannot be read or decoded fails `load` and every write
61
+ * with a `SettingsError` of code `settings.corrupt` instead of reading it as
62
+ * defaults, so no write replaces settings it could not read.
63
+ * `settingsStoreConformance` (`@xandreed/plugin-models/conformance`) checks
64
+ * a store against this contract. Its key stays `@xandreed/core/SettingsStore`,
65
+ * the package it was first declared in: plugins' `provides` and hosts'
66
+ * bindings name it, so it never changes.
67
+ */
68
+ export declare class SettingsStore extends SettingsStore_base {
69
+ }
70
+ export {};
@@ -0,0 +1,33 @@
1
+ import { Context, Effect, Option, Schema } from "effect";
2
+ import { ReasoningEffort } from "@xandreed/core";
3
+ const optionField = (schema) => Schema.OptionFromOptional(schema).pipe(Schema.withConstructorDefault(Effect.succeed(Option.none())));
4
+
5
+ export class EngineSettings extends Schema.Class("EngineSettings")({
6
+ model: optionField(Schema.String),
7
+ codeModel: optionField(Schema.String),
8
+ fastModel: optionField(Schema.String),
9
+ fallbackModel: optionField(Schema.String),
10
+ maxAttempts: optionField(Schema.Number),
11
+ budgetMillis: optionField(Schema.Number),
12
+ reasoningEffort: optionField(ReasoningEffort),
13
+ codeReasoningEffort: optionField(ReasoningEffort),
14
+ fastReasoningEffort: optionField(ReasoningEffort)
15
+ }) {
16
+ }
17
+
18
+ export class SettingsError extends Schema.TaggedError()("SettingsError", {
19
+ message: Schema.String,
20
+ code: Schema.optionalKey(Schema.Literal("settings.corrupt"))
21
+ }) {
22
+ }
23
+ export const SETTINGS_KEYS = [
24
+ "fallbackModel",
25
+ "maxAttempts",
26
+ "budgetMillis",
27
+ "reasoningEffort",
28
+ "codeReasoningEffort",
29
+ "fastReasoningEffort"
30
+ ];
31
+
32
+ export class SettingsStore extends Context.Service()("@xandreed/core/SettingsStore") {
33
+ }
@@ -0,0 +1,7 @@
1
+ import { Context } from "effect";
2
+ /**
3
+ * What the models plugin was configured to tell a model that binds thinking
4
+ * to the conversation (`thinkingPrefixMismatch`): `drop_block` for adaptive thinking by default;
5
+ * `provider-default` opts out of the explicit binding field.
6
+ */
7
+ export declare const CurrentThinkingPrefixMismatch: Context.Reference<"error" | "provider-default" | "drop_block">;
@@ -0,0 +1,4 @@
1
+ import { Context } from "effect";
2
+ export const CurrentThinkingPrefixMismatch = Context.Reference("@xandreed/plugin-models/CurrentThinkingPrefixMismatch", {
3
+ defaultValue: () => "drop_block"
4
+ });
@@ -1,3 +1,4 @@
1
1
  import { Layer } from "effect";
2
- import { SettingsStore } from "@xandreed/core";
2
+ import { SettingsStore } from "../ports/settings-store.port.js";
3
+ /** @public */
3
4
  export declare const LocalSettingsStoreLive: (cwd: string, home: string) => Layer.Layer<SettingsStore, never, never>;
@@ -1,32 +1,29 @@
1
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
1
+ import { join } from "node:path";
3
2
  import { Effect, Layer, Option } from "effect";
4
- import { asJsonRecord, parseJsonWarn, SettingsError, SettingsStore } from "@xandreed/core";
3
+ import { SettingsError, SettingsStore } from "../ports/settings-store.port.js";
4
+ import { readJsonObject, withFileLock, writeJsonObject } from "../storage/json-file.js";
5
5
  import { coerceSettingValue, ROLE_FIELDS, roleSelection, settingsFromRecord } from "./settings-value.entity.functions.js";
6
6
  const configPaths = (cwd, home) => [
7
7
  join(home, ".efferent", "config.json"),
8
8
  join(cwd, ".efferent", "config.json")
9
- ], readConfig = (path) => Effect.tryPromise({ try: () => readFile(path, "utf-8"), catch: () => "missing" }).pipe(Effect.flatMap((text) => parseJsonWarn(text, path)), Effect.map(asJsonRecord), Effect.orElseSucceed(() => ({}))), writeKey = (cwd, key, value) => Effect.gen(function* () {
10
- const path = join(cwd, ".efferent", "config.json"), current = yield* readConfig(path), next = Option.match(value, {
11
- onNone: () => Object.fromEntries(Object.entries(current).filter(([k]) => k !== key)),
12
- onSome: (v) => ({ ...current, [key]: v })
13
- });
14
- yield* writeConfigAtomic(path, next);
15
- });
9
+ ], readConfig = (path) => readJsonObject(path, (reason) => new SettingsError({
10
+ code: "settings.corrupt",
11
+ message: `${path} ${reason}: fix or remove it; nothing was read from it or written to it`
12
+ })).pipe(Effect.map(Option.getOrElse(() => ({})))), writeKey = (cwd, key, value) => {
13
+ const path = join(cwd, ".efferent", "config.json");
14
+ return withFileLock(path)(Effect.gen(function* () {
15
+ const current = yield* readConfig(path), next = Option.match(value, {
16
+ onNone: () => Object.fromEntries(Object.entries(current).filter(([k]) => k !== key)),
17
+ onSome: (v) => ({ ...current, [key]: v })
18
+ });
19
+ yield* writeJsonObject(path, next, Option.none(), (reason) => new SettingsError({ message: `config.json write failed: ${reason}` }));
20
+ }));
21
+ };
16
22
  export const LocalSettingsStoreLive = (cwd, home) => Layer.succeed(SettingsStore, {
17
- load: Effect.forEach(configPaths(cwd, home), readConfig).pipe(Effect.map((configs) => settingsFromRecord(configs.reduce((acc, c) => ({ ...acc, ...c }), {})))),
23
+ load: Effect.forEach(configPaths(cwd, home), readConfig, { concurrency: 1 }).pipe(Effect.map((configs) => settingsFromRecord(configs.reduce((acc, c) => ({ ...acc, ...c }), {})))),
18
24
  setRole: (role, selection) => roleSelection(role, selection).pipe(Effect.flatMap((checked) => writeKey(cwd, ROLE_FIELDS[role], checked))),
19
25
  set: (key, value) => Option.match(value, {
20
26
  onNone: () => writeKey(cwd, key, Option.none()),
21
27
  onSome: (raw) => coerceSettingValue(key, raw).pipe(Effect.flatMap((coerced) => writeKey(cwd, key, Option.some(coerced))))
22
28
  })
23
29
  });
24
- const writeConfigAtomic = (path, config) => Effect.tryPromise({
25
- try: async () => {
26
- await mkdir(dirname(path), { recursive: !0 });
27
- const tmp = `${path}.tmp-${process.pid}`;
28
- await writeFile(tmp, JSON.stringify(config, null, 2), "utf-8");
29
- await rename(tmp, path);
30
- },
31
- catch: (e) => new SettingsError({ message: `config.json write failed: ${String(e)}` })
32
- });
@@ -1,10 +1,12 @@
1
1
  import { Layer } from "effect";
2
- import { EngineSettings, SettingsStore } from "@xandreed/core";
2
+ import { EngineSettings, SettingsStore } from "../ports/settings-store.port.js";
3
3
  /**
4
4
  * `MemorySettingsStoreLive(initial)` — a SettingsStore that never touches
5
5
  * disk: one writable value for the layer's lifetime, starting at `initial`.
6
6
  * `setRole` and `set` check their value the way the file store does
7
7
  * (a `"<provider>:<modelId>"` selection; each key's string form) and refuse
8
8
  * one that does not parse, leaving the settings as they were.
9
+ *
10
+ * @public
9
11
  */
10
12
  export declare const MemorySettingsStoreLive: (initial?: EngineSettings) => Layer.Layer<SettingsStore>;
@@ -1,5 +1,5 @@
1
1
  import { Effect, Layer, SynchronizedRef } from "effect";
2
- import { EngineSettings, SettingsStore } from "@xandreed/core";
2
+ import { EngineSettings, SettingsStore } from "../ports/settings-store.port.js";
3
3
  import { applyRole, applySetting } from "./settings-value.entity.functions.js";
4
4
  export const MemorySettingsStoreLive = (initial = new EngineSettings({})) => Layer.effect(SettingsStore, Effect.gen(function* () {
5
5
  const state = yield* SynchronizedRef.make(initial);
@@ -1,5 +1,5 @@
1
1
  import { Layer } from "effect";
2
- import { SettingsStore } from "@xandreed/core";
2
+ import { SettingsStore } from "../ports/settings-store.port.js";
3
3
  /**
4
4
  * A role-scoped VIEW of the settings store: `load` re-maps the general
5
5
  * `model` to the role's selection (falling back to `model` when the role is
@@ -7,5 +7,7 @@ import { SettingsStore } from "@xandreed/core";
7
7
  * LanguageModel router above all — runs on that role WITHOUT knowing roles
8
8
  * exist. Compose it under `LanguageModelLive` for the scope that should use
9
9
  * the role (e.g. the forge implementor on "code"); writes pass through.
10
+ *
11
+ * @public
10
12
  */
11
13
  export declare const roleModelView: (role: "code" | "fast") => Layer.Layer<SettingsStore, never, SettingsStore>;
@@ -1,5 +1,5 @@
1
1
  import { Effect, Layer, Option } from "effect";
2
- import { EngineSettings, SettingsStore } from "@xandreed/core";
2
+ import { EngineSettings, SettingsStore } from "../ports/settings-store.port.js";
3
3
  export const roleModelView = (role) => Layer.effect(SettingsStore, Effect.map(SettingsStore, (inner) => ({
4
4
  load: Effect.map(inner.load, (s) => new EngineSettings({
5
5
  ...s,
@@ -0,0 +1,47 @@
1
+ import { Effect } from "effect";
2
+ import type { Context, Scope } from "effect";
3
+ import type { ConformanceCheck } from "@xandreed/core/conformance";
4
+ import type { SettingsStore } from "../ports/settings-store.port.js";
5
+ type Store = Context.Service.Shape<typeof SettingsStore>;
6
+ /**
7
+ * What the kit is told about a store, and can do to it beyond calling it.
8
+ *
9
+ * @public
10
+ */
11
+ export interface SettingsStoreConformanceOptions {
12
+ /** Whether `setRole` and `set` write, or every write is refused. */
13
+ readonly writable: boolean;
14
+ /**
15
+ * Damage the store's backing data (a file that is not JSON) for the
16
+ * scope's lifetime, and repair it when the scope closes: the corrupt check.
17
+ * Damage what the store writes to, so a write that ignores the damage shows.
18
+ * A store with nothing to damage, such as one in memory, leaves it out.
19
+ */
20
+ readonly corrupt?: Effect.Effect<void, never, Scope.Scope>;
21
+ }
22
+ /**
23
+ * The SettingsStore contract. Every store: `load` is stable. A writable
24
+ * store: each role reads back what `setRole` wrote and clearing it returns
25
+ * the value before the set; `set` accepts each key's canonical string form
26
+ * and `load` gives it typed; `None` clears a key back to the value before the
27
+ * set; a value that does not parse (a selection that is not
28
+ * `"<provider>:<modelId>"`, a count that is not a whole number of at least
29
+ * 1, an unknown effort) fails with a `SettingsError` and changes
30
+ * nothing, through `set` and `setRole` alike. A read-only store refuses
31
+ * every write with a `SettingsError` and changes nothing.
32
+ *
33
+ * The kit writes the store's shared keys (settings have no per-check ids),
34
+ * and restores each key it touches; do not run it against settings that are
35
+ * in use. A key it would touch that holds a value the store must refuse (a
36
+ * selection that is not `"<provider>:<modelId>"`, a count below 1, left by
37
+ * an older, unchecked write) could not be put back, so the check that would
38
+ * touch it fails before writing anything.
39
+ *
40
+ * With `options.corrupt`, a store whose backing data cannot be read fails
41
+ * `load` (and, writable, `setRole` and `set`) with a `SettingsError` of code
42
+ * `settings.corrupt` instead of reading it as defaults.
43
+ *
44
+ * @public
45
+ */
46
+ export declare const settingsStoreConformance: (store: Store, options: SettingsStoreConformanceOptions) => ReadonlyArray<ConformanceCheck>;
47
+ export {};