@xandreed/plugin-models 0.11.0 → 0.12.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.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # @xandreed/plugin-models
2
2
 
3
- Efferent composable agent framework, 0.11.0. Node 24 or Bun.
3
+ Efferent composable agent framework, 0.12.0. Node 24 or Bun.
4
4
 
5
5
  Documentation: https://github.com/xandreeddev/efferent
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { fromChatCompletion, makeCompatLanguageModel, toChatMessages, toChatTool
9
9
  export type { CompatConfig } from "./llm/compat.js";
10
10
  export { makeOpenAiCodexLanguageModel, OPENAI_CODEX_API_URL, toOpenAiCodexRequestBody } from "./llm/openAiCodex.js";
11
11
  export { normalizeOpenAiCodexWebSocketEvent, openAiCodexUuidV7, OpenAiCodexWebSocketHttpClient } from "./llm/openAiCodexWebSocket.js";
12
- export { buildProvider, OPENCODE_CHAT_URL, OPENCODE_RESPONSES_API_URL, prependClaudeCode, usesOpenCodeResponses, withAnthropicCacheBreakpoints, } from "./llm/providers.js";
12
+ export { buildProvider, chatCompletionsUrl, OPENCODE_CHAT_URL, OPENCODE_RESPONSES_API_URL, prependClaudeCode, usesOpenCodeResponses, withAnthropicCacheBreakpoints, } from "./llm/providers.js";
13
13
  export { classifyLlmError, LLM_REQUEST_TIMEOUT_MS, MAX_HONORED_RETRY_AFTER_MS, rejectEmptyResponse, retryableLlm, } from "./llm/retry.js";
14
14
  export type { ErrorClass } from "./llm/retry.js";
15
15
  export { generateWith, LanguageModelLive, LanguageModelSelectionLive } from "./llm/router.js";
@@ -17,3 +17,4 @@ export { configuredModelCatalog, ConfiguredModelCatalogLive, reasoningEffortsFor
17
17
  export type { ReasoningEffort } from "./llm/modelCatalog.js";
18
18
  export { UtilityLlmLive } from "./llm/utilityLlm.js";
19
19
  export { modelsPlugin, default } from "./plugin.adapter.js";
20
+ export { CurrentModelEndpoints, ModelEndpoint, ModelEndpoints } from "./ports/model-endpoints.port.js";
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ export { makeOpenAiCodexLanguageModel, OPENAI_CODEX_API_URL, toOpenAiCodexReques
30
30
  export { normalizeOpenAiCodexWebSocketEvent, openAiCodexUuidV7, OpenAiCodexWebSocketHttpClient } from "./llm/openAiCodexWebSocket.js";
31
31
  export {
32
32
  buildProvider,
33
+ chatCompletionsUrl,
33
34
  OPENCODE_CHAT_URL,
34
35
  OPENCODE_RESPONSES_API_URL,
35
36
  prependClaudeCode,
@@ -51,3 +52,4 @@ export {
51
52
  } from "./llm/modelCatalog.js";
52
53
  export { UtilityLlmLive } from "./llm/utilityLlm.js";
53
54
  export { modelsPlugin, default } from "./plugin.adapter.js";
55
+ export { CurrentModelEndpoints, ModelEndpoint, ModelEndpoints } from "./ports/model-endpoints.port.js";
@@ -19,8 +19,8 @@ export interface CompatConfig {
19
19
  readonly moduleName: string;
20
20
  /** Full chat-completions endpoint. */
21
21
  readonly chatUrl: string;
22
- /** Bearer key (already resolved from the AuthStore). */
23
- readonly apiKey: string;
22
+ /** Bearer key (already resolved). Omit for keyless endpoints such as a local server. */
23
+ readonly apiKey?: string;
24
24
  /** Provider-native model id. */
25
25
  readonly model: string;
26
26
  /** Host-selected thinking mode. Omit to retain family defaults. */
@@ -142,7 +142,7 @@ const requestSettings = (config, cacheKey, policy) => ({
142
142
  method: "POST",
143
143
  headers: {
144
144
  "content-type": "application/json",
145
- authorization: `Bearer ${config.apiKey}`
145
+ ...config.apiKey === void 0 || config.apiKey.length === 0 ? {} : { authorization: `Bearer ${config.apiKey}` }
146
146
  },
147
147
  body: JSON.stringify(body)
148
148
  });
@@ -11,8 +11,9 @@ import type { Credential, ModelSelection } from "@xandreed/core";
11
11
  *
12
12
  * v1 providers: opencode (OpenAI-compatible gateway — the default), google
13
13
  * (Gemini's OpenAI-compatible endpoint), anthropic (api key or subscription
14
- * OAuth), openai (api key). OpenAI-OAuth
15
- * (Codex) and ollama are deferred until an agent needs them.
14
+ * OAuth), openai (api key), openai-codex (subscription OAuth). A configured
15
+ * endpoint, or a `local` credential with a `baseUrl`, sends its provider's
16
+ * models to that OpenAI-compatible URL, with or without a key.
16
17
  */
17
18
  export declare const OPENCODE_CHAT_URL = "https://opencode.ai/zen/go/v1/chat/completions";
18
19
  /** OpenCode serves GPT models through the OpenAI Responses protocol. */
@@ -37,5 +38,7 @@ export declare const prependClaudeCode: (options: unknown) => unknown;
37
38
  * alone.
38
39
  */
39
40
  export declare const withAnthropicCacheBreakpoints: (options: unknown) => unknown;
41
+ /** `<url>/chat/completions` of an OpenAI-compatible base URL. */
42
+ export declare const chatCompletionsUrl: (baseUrl: string) => string;
40
43
  /** Preserve adapter descriptors and describe the public configuration of SDK-backed models. */
41
44
  export declare const buildProvider: (selection: ModelSelection, credential: Credential | undefined, key: Redacted.Redacted<string> | undefined) => Effect.Effect<BuiltProvider, AuthError, HttpClient.HttpClient | Scope.Scope>;
@@ -8,6 +8,7 @@ import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM } from "../auth/anthropicOAuth
8
8
  import { makeCompatLanguageModel } from "./compat.js";
9
9
  import { makeOpenAiCodexLanguageModel } from "./openAiCodex.js";
10
10
  import { openAiCodexAccountId } from "../auth/openAiCodexOAuth.js";
11
+ import { CurrentModelEndpoints } from "../ports/model-endpoints.port.js";
11
12
  export const OPENCODE_CHAT_URL = "https://opencode.ai/zen/go/v1/chat/completions", OPENCODE_RESPONSES_API_URL = "https://opencode.ai/zen/v1", GOOGLE_CHAT_URL = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", usesOpenCodeResponses = (modelId) => /^gpt-/i.test(modelId);
12
13
  const anthropicOAuthTransform = (access) => (client) => client.pipe(HttpClient.mapRequest((req) => req.pipe(HttpClientRequest.setHeaders({
13
14
  Authorization: `Bearer ${Redacted.value(access)}`,
@@ -57,7 +58,22 @@ const missingKey = (selection) => new AuthError({
57
58
  }
58
59
  })
59
60
  })
60
- }), buildProviderService = (selection, credential, key) => {
61
+ });
62
+ export const chatCompletionsUrl = (baseUrl) => `${baseUrl.replace(/\/+$/, "")}/chat/completions`;
63
+ const endpointKey = (endpoint, stored) => Option.fromNullishOr(endpoint.apiKey).pipe(Option.orElse(() => Option.flatMap(Option.fromNullishOr(endpoint.apiKeyEnv), (name) => Option.fromNullishOr(process.env[name]))), Option.orElse(() => Option.map(Option.fromNullishOr(stored), Redacted.value)), Option.filter((value) => value.length > 0)), endpointModel = (selection, url, key, standardOnly) => makeCompatLanguageModel({
64
+ moduleName: selection.provider,
65
+ chatUrl: chatCompletionsUrl(url),
66
+ model: selection.modelId,
67
+ standardOnly,
68
+ ...Option.match(key, { onNone: () => ({}), onSome: (apiKey) => ({ apiKey }) })
69
+ }).pipe(Effect.map((svc) => ({ svc, prependClaudeCode: !1 }))), buildProviderService = (selection, credential, key) => Effect.flatMap(Effect.service(CurrentModelEndpoints), (endpoints) => {
70
+ const endpoint = Option.fromNullishOr(endpoints[selection.provider]);
71
+ if (Option.isSome(endpoint))
72
+ return endpointModel(selection, endpoint.value.url, endpointKey(endpoint.value, key), endpoint.value.standardOnly ?? !0);
73
+ if (credential?.type === "local" && credential.baseUrl !== void 0)
74
+ return endpointModel(selection, credential.baseUrl, Option.none(), !0);
75
+ return buildBuiltInProvider(selection, credential, key);
76
+ }), buildBuiltInProvider = (selection, credential, key) => {
61
77
  const oauth = credential?.type === "oauth";
62
78
  if (selection.provider === "opencode") {
63
79
  if (key === void 0)
@@ -110,7 +126,7 @@ const missingKey = (selection) => new AuthError({
110
126
  }).pipe(Effect.provideService(OpenAiClient.OpenAiClient, client))), Effect.map((svc) => ({ svc, prependClaudeCode: !1 }))));
111
127
  return Effect.fail(new AuthError({
112
128
  provider: selection.provider,
113
- message: `provider "${selection.provider}" is not wired on the new line (v1: opencode, google, anthropic, openai, openai-codex)`
129
+ message: `provider "${selection.provider}" is not wired (built in: opencode, google, anthropic, openai, openai-codex; add others as models plugin endpoints)`
114
130
  }));
115
131
  };
116
132
  export const buildProvider = (selection, credential, key) => buildProviderService(selection, credential, key).pipe(Effect.flatMap((built) => Effect.gen(function* () {
@@ -5,10 +5,26 @@ export declare const modelsPlugin: import("@xandreed/core").TypedPlugin<{
5
5
  readonly fastModel: string;
6
6
  readonly fallbackModel: string;
7
7
  readonly inheritPrevious: boolean;
8
+ readonly endpoints: {
9
+ readonly [x: string]: {
10
+ readonly url: string;
11
+ readonly apiKey?: string;
12
+ readonly apiKeyEnv?: string;
13
+ readonly standardOnly?: boolean;
14
+ };
15
+ };
8
16
  }, {
9
17
  readonly model: string;
10
18
  readonly fastModel: string;
11
19
  readonly fallbackModel: string;
12
20
  readonly inheritPrevious: boolean;
21
+ readonly endpoints: {
22
+ readonly [x: string]: {
23
+ readonly url: string;
24
+ readonly apiKey?: string;
25
+ readonly apiKeyEnv?: string;
26
+ readonly standardOnly?: boolean;
27
+ };
28
+ };
13
29
  }, SettingsStore | AuthStore | ModelCatalog | UtilityLlm | LanguageModel.LanguageModel, SettingsError, SessionEnvironment>;
14
30
  export default modelsPlugin;
@@ -7,12 +7,13 @@ import { LanguageModelLive } from "./llm/router.js";
7
7
  import { UtilityLlmLive } from "./llm/utilityLlm.js";
8
8
  import { ConfiguredModelCatalogLive } from "./llm/modelCatalog.js";
9
9
  import { LocalSettingsStoreLive } from "./settings/localSettings.js";
10
- const Config = Schema.Struct({ model: Schema.String, fastModel: Schema.String, fallbackModel: Schema.String, inheritPrevious: Schema.Boolean });
10
+ import { CurrentModelEndpoints, ModelEndpoints } from "./ports/model-endpoints.port.js";
11
+ const Config = Schema.Struct({ model: Schema.String, fastModel: Schema.String, fallbackModel: Schema.String, inheritPrevious: Schema.Boolean, endpoints: ModelEndpoints });
11
12
  export const modelsPlugin = definePlugin({
12
13
  id: "@xandreed/plugin-models",
13
- version: "0.11.0",
14
+ version: "0.12.0",
14
15
  config: Config,
15
- defaults: { model: "", fastModel: "", fallbackModel: "", inheritPrevious: !0 },
16
+ defaults: { model: "", fastModel: "", fallbackModel: "", inheritPrevious: !0, endpoints: {} },
16
17
  requires: [SessionEnvironment],
17
18
  provides: [LanguageModel.LanguageModel, UtilityLlm, AuthStore, SettingsStore, ModelCatalog],
18
19
  layer: (config) => Layer.unwrap(Effect.gen(function* () {
@@ -20,7 +21,7 @@ export const modelsPlugin = definePlugin({
20
21
  load: Effect.succeed(new EngineSettings({ ...previous, model: Option.orElse(optional(config.model), () => previous.model), fastModel: Option.orElse(optional(config.fastModel), () => previous.fastModel), fallbackModel: Option.orElse(optional(config.fallbackModel), () => previous.fallbackModel) })),
21
22
  setRole: () => Effect.fail(new SettingsError({ message: "Edit the models plugin configuration to change a model" })),
22
23
  set: () => Effect.fail(new SettingsError({ message: "Edit the models plugin configuration to change settings" }))
23
- }), base = Layer.merge(settings, LocalAuthStoreLive(workspace, homedir(), ".efferent/runtime", config.inheritPrevious ? ".efferent" : void 0));
24
+ }), base = Layer.mergeAll(settings, LocalAuthStoreLive(workspace, homedir(), ".efferent/runtime", config.inheritPrevious ? ".efferent" : void 0), Layer.succeed(CurrentModelEndpoints, config.endpoints));
24
25
  return Layer.mergeAll(LanguageModelLive, UtilityLlmLive, ConfiguredModelCatalogLive).pipe(Layer.provideMerge(base));
25
26
  }))
26
27
  });
@@ -0,0 +1,33 @@
1
+ import { Context, Schema } from "effect";
2
+ /**
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
+ * `standardOnly` (default true) sends only standard chat-completions fields.
9
+ */
10
+ export declare const ModelEndpoint: Schema.Struct<{
11
+ readonly url: Schema.String;
12
+ readonly apiKey: Schema.optionalKey<Schema.String>;
13
+ readonly apiKeyEnv: Schema.optionalKey<Schema.String>;
14
+ readonly standardOnly: Schema.optionalKey<Schema.Boolean>;
15
+ }>;
16
+ export type ModelEndpoint = typeof ModelEndpoint.Type;
17
+ /** Endpoints by provider name; a name that matches a built-in provider replaces it. */
18
+ export declare const ModelEndpoints: Schema.$Record<Schema.String, Schema.Struct<{
19
+ readonly url: Schema.String;
20
+ readonly apiKey: Schema.optionalKey<Schema.String>;
21
+ readonly apiKeyEnv: Schema.optionalKey<Schema.String>;
22
+ readonly standardOnly: Schema.optionalKey<Schema.Boolean>;
23
+ }>>;
24
+ export type ModelEndpoints = typeof ModelEndpoints.Type;
25
+ /** The endpoints the models plugin was configured with; none by default. */
26
+ export declare const CurrentModelEndpoints: Context.Reference<{
27
+ readonly [x: string]: {
28
+ readonly url: string;
29
+ readonly apiKey?: string;
30
+ readonly apiKeyEnv?: string;
31
+ readonly standardOnly?: boolean;
32
+ };
33
+ }>;
@@ -0,0 +1,9 @@
1
+ import { Context, Schema } from "effect";
2
+ export const ModelEndpoint = Schema.Struct({
3
+ url: Schema.String,
4
+ apiKey: Schema.optionalKey(Schema.String),
5
+ apiKeyEnv: Schema.optionalKey(Schema.String),
6
+ standardOnly: Schema.optionalKey(Schema.Boolean)
7
+ }), ModelEndpoints = Schema.Record(Schema.String, ModelEndpoint), CurrentModelEndpoints = Context.Reference("@xandreed/plugin-models/CurrentModelEndpoints", {
8
+ defaultValue: () => ({})
9
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xandreed/plugin-models",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -14,7 +14,7 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@xandreed/core": "0.11.0",
17
+ "@xandreed/core": "0.12.0",
18
18
  "effect": "4.0.0-rc.118",
19
19
  "@effect/ai-anthropic": "4.0.0-rc.118",
20
20
  "@effect/ai-openai": "4.0.0-rc.118",