@xandreed/plugin-models 0.3.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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/auth/anthropicOAuth.d.ts +50 -0
  4. package/dist/auth/anthropicOAuth.js +90 -0
  5. package/dist/auth/anthropicOAuth.test.d.ts +1 -0
  6. package/dist/auth/localAuth.d.ts +8 -0
  7. package/dist/auth/localAuth.js +90 -0
  8. package/dist/auth/localAuth.test.d.ts +1 -0
  9. package/dist/auth/openAiCodexOAuth.d.ts +25 -0
  10. package/dist/auth/openAiCodexOAuth.js +58 -0
  11. package/dist/index.d.ts +19 -0
  12. package/dist/index.js +53 -0
  13. package/dist/llm/compat.d.ts +77 -0
  14. package/dist/llm/compat.js +215 -0
  15. package/dist/llm/compat.test.d.ts +1 -0
  16. package/dist/llm/modelCatalog.d.ts +10 -0
  17. package/dist/llm/modelCatalog.js +58 -0
  18. package/dist/llm/modelCatalog.test.d.ts +1 -0
  19. package/dist/llm/openAiCodex.d.ts +18 -0
  20. package/dist/llm/openAiCodex.js +99 -0
  21. package/dist/llm/openAiCodexWebSocket.d.ts +14 -0
  22. package/dist/llm/openAiCodexWebSocket.js +126 -0
  23. package/dist/llm/providers.d.ts +37 -0
  24. package/dist/llm/providers.js +114 -0
  25. package/dist/llm/providers.test.d.ts +1 -0
  26. package/dist/llm/retry.d.ts +54 -0
  27. package/dist/llm/retry.js +61 -0
  28. package/dist/llm/retry.test.d.ts +1 -0
  29. package/dist/llm/router.d.ts +55 -0
  30. package/dist/llm/router.js +125 -0
  31. package/dist/llm/router.test.d.ts +1 -0
  32. package/dist/llm/sse.d.ts +45 -0
  33. package/dist/llm/sse.js +116 -0
  34. package/dist/llm/sse.test.d.ts +1 -0
  35. package/dist/llm/utilityLlm.d.ts +8 -0
  36. package/dist/llm/utilityLlm.js +33 -0
  37. package/dist/plugin.adapter.d.ts +2 -0
  38. package/dist/plugin.adapter.js +27 -0
  39. package/dist/plugin.adapter.test.d.ts +1 -0
  40. package/dist/settings/localSettings.d.ts +3 -0
  41. package/dist/settings/localSettings.js +72 -0
  42. package/dist/settings/localSettings.test.d.ts +1 -0
  43. package/dist/settings/roleView.d.ts +11 -0
  44. package/dist/settings/roleView.js +11 -0
  45. package/dist/settings/roleView.test.d.ts +1 -0
  46. package/package.json +39 -0
@@ -0,0 +1,27 @@
1
+ import { homedir } from "node:os";
2
+ import { LanguageModel } from "@effect/ai";
3
+ import { Effect, Layer, Option, Schema } from "effect";
4
+ import { AuthStore, definePlugin, EngineSettings, ModelCatalog, SessionEnvironment, SettingsError, SettingsStore, UtilityLlm } from "@xandreed/core";
5
+ import { LocalAuthStoreLive } from "./auth/localAuth.js";
6
+ import { LanguageModelLive } from "./llm/router.js";
7
+ import { UtilityLlmLive } from "./llm/utilityLlm.js";
8
+ import { ConfiguredModelCatalogLive } from "./llm/modelCatalog.js";
9
+ import { LocalSettingsStoreLive } from "./settings/localSettings.js";
10
+ const Config = Schema.Struct({ model: Schema.String, fastModel: Schema.String, fallbackModel: Schema.String, inheritPrevious: Schema.Boolean });
11
+ export const modelsPlugin = definePlugin({
12
+ id: "@xandreed/plugin-models",
13
+ version: "0.3.0",
14
+ config: Config,
15
+ defaults: { model: "", fastModel: "", fallbackModel: "", inheritPrevious: !0 },
16
+ requires: [SessionEnvironment],
17
+ provides: [LanguageModel.LanguageModel, UtilityLlm, AuthStore, SettingsStore, ModelCatalog],
18
+ layer: (config) => Layer.unwrapEffect(Effect.gen(function* () {
19
+ const { workspace } = yield* SessionEnvironment, previous = config.inheritPrevious ? yield* SettingsStore.pipe(Effect.flatMap((settings) => settings.load), Effect.provide(LocalSettingsStoreLive(workspace, homedir()))) : new EngineSettings({}), optional = (value) => value.length === 0 ? Option.none() : Option.some(value), settings = Layer.succeed(SettingsStore, {
20
+ 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
+ setRole: () => Effect.fail(new SettingsError({ message: "Edit the models plugin configuration to change a model" })),
22
+ 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
+ return Layer.mergeAll(LanguageModelLive, UtilityLlmLive, ConfiguredModelCatalogLive).pipe(Layer.provideMerge(base));
25
+ }))
26
+ });
27
+ export default modelsPlugin;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { Layer } from "effect";
2
+ import { SettingsStore } from "@xandreed/core";
3
+ export declare const LocalSettingsStoreLive: (cwd: string, home: string) => Layer.Layer<SettingsStore, never, never>;
@@ -0,0 +1,72 @@
1
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { Effect, Layer, Option } from "effect";
4
+ import { asJsonRecord, EngineSettings, parseJsonWarn, parseModelSelection, SettingsError, SettingsStore } from "@xandreed/core";
5
+ const configPaths = (cwd, home) => [
6
+ join(home, ".efferent", "config.json"),
7
+ join(cwd, ".efferent", "config.json")
8
+ ], readConfig = (path) => Effect.tryPromise({ try: () => readFile(path, "utf-8"), catch: () => "missing" }).pipe(Effect.flatMap((text) => parseJsonWarn(text, path)), Effect.map(asJsonRecord), Effect.orElseSucceed(() => ({}))), asString = (value) => typeof value === "string" && value.length > 0 ? Option.some(value) : Option.none(), asBoolean = (value) => typeof value === "boolean" ? Option.some(value) : Option.none(), asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? Option.some(value) : Option.none(), asReasoningEffort = (value) => value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? Option.some(value) : Option.none(), coerceSettingValue = (key, raw) => {
9
+ if (key === "fallbackModel")
10
+ return Option.match(parseModelSelection(raw), {
11
+ onNone: () => Effect.fail(new SettingsError({
12
+ message: `fallbackModel must be "<provider>:<modelId>" \u2014 got "${raw}"`
13
+ })),
14
+ onSome: () => Effect.succeed(raw)
15
+ });
16
+ if (key === "sandbox" || key === "viMode") {
17
+ if (raw === "true" || raw === "on")
18
+ return Effect.succeed(!0);
19
+ if (raw === "false" || raw === "off")
20
+ return Effect.succeed(!1);
21
+ return Effect.fail(new SettingsError({ message: `${key} must be true/false \u2014 got "${raw}"` }));
22
+ }
23
+ if (key === "reasoningEffort" || key === "codeReasoningEffort" || key === "fastReasoningEffort")
24
+ return Option.match(asReasoningEffort(raw), {
25
+ onNone: () => Effect.fail(new SettingsError({ message: `reasoningEffort is not supported: "${raw}"` })),
26
+ onSome: Effect.succeed
27
+ });
28
+ const parsed = Number(raw);
29
+ return Number.isInteger(parsed) && parsed >= 1 ? Effect.succeed(parsed) : Effect.fail(new SettingsError({ message: `${key} must be a positive integer \u2014 got "${raw}"` }));
30
+ }, writeKey = (cwd, key, value) => Effect.gen(function* () {
31
+ const path = join(cwd, ".efferent", "config.json"), current = yield* readConfig(path), next = Option.match(value, {
32
+ onNone: () => Object.fromEntries(Object.entries(current).filter(([k]) => k !== key)),
33
+ onSome: (v) => ({ ...current, [key]: v })
34
+ });
35
+ yield* writeConfigAtomic(path, next);
36
+ });
37
+ export const LocalSettingsStoreLive = (cwd, home) => Layer.succeed(SettingsStore, {
38
+ load: Effect.gen(function* () {
39
+ const merged = (yield* Effect.forEach(configPaths(cwd, home), readConfig)).reduce((acc, c) => ({ ...acc, ...c }), {});
40
+ return new EngineSettings({
41
+ model: asString(merged.model),
42
+ codeModel: asString(merged.codeModel),
43
+ fastModel: asString(merged.fastModel),
44
+ fallbackModel: asString(merged.fallbackModel),
45
+ sandbox: asBoolean(merged.sandbox),
46
+ viMode: asBoolean(merged.viMode),
47
+ maxAttempts: asNumber(merged.maxAttempts),
48
+ budgetMillis: asNumber(merged.budgetMillis),
49
+ reasoningEffort: Option.orElse(asReasoningEffort(merged.reasoningEffort), () => asReasoningEffort(merged.openAiReasoningEffort)),
50
+ codeReasoningEffort: asReasoningEffort(merged.codeReasoningEffort),
51
+ fastReasoningEffort: asReasoningEffort(merged.fastReasoningEffort)
52
+ });
53
+ }),
54
+ setRole: (role, selection) => writeKey(cwd, ROLE_KEYS[role], selection),
55
+ set: (key, value) => Option.match(value, {
56
+ onNone: () => writeKey(cwd, key, Option.none()),
57
+ onSome: (raw) => coerceSettingValue(key, raw).pipe(Effect.flatMap((coerced) => writeKey(cwd, key, Option.some(coerced))))
58
+ })
59
+ });
60
+ const ROLE_KEYS = {
61
+ general: "model",
62
+ code: "codeModel",
63
+ fast: "fastModel"
64
+ }, writeConfigAtomic = (path, config) => Effect.tryPromise({
65
+ try: async () => {
66
+ await mkdir(dirname(path), { recursive: !0 });
67
+ const tmp = `${path}.tmp-${process.pid}`;
68
+ await writeFile(tmp, JSON.stringify(config, null, 2), "utf-8");
69
+ await rename(tmp, path);
70
+ },
71
+ catch: (e) => new SettingsError({ message: `config.json write failed: ${String(e)}` })
72
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import { Layer } from "effect";
2
+ import { SettingsStore } from "@xandreed/core";
3
+ /**
4
+ * A role-scoped VIEW of the settings store: `load` re-maps the general
5
+ * `model` to the role's selection (falling back to `model` when the role is
6
+ * unset), so any consumer that routes on `settings.model` — the
7
+ * LanguageModel router above all — runs on that role WITHOUT knowing roles
8
+ * exist. Compose it under `LanguageModelLive` for the scope that should use
9
+ * the role (e.g. the forge implementor on "code"); writes pass through.
10
+ */
11
+ export declare const roleModelView: (role: "code" | "fast") => Layer.Layer<SettingsStore, never, SettingsStore>;
@@ -0,0 +1,11 @@
1
+ import { Effect, Layer, Option } from "effect";
2
+ import { EngineSettings, SettingsStore } from "@xandreed/core";
3
+ export const roleModelView = (role) => Layer.effect(SettingsStore, Effect.map(SettingsStore, (inner) => ({
4
+ load: Effect.map(inner.load, (s) => new EngineSettings({
5
+ ...s,
6
+ model: Option.orElse(role === "code" ? s.codeModel : s.fastModel, () => s.model),
7
+ reasoningEffort: Option.orElse(role === "code" ? s.codeReasoningEffort : s.fastReasoningEffort, () => s.reasoningEffort)
8
+ })),
9
+ setRole: inner.setRole,
10
+ set: inner.set
11
+ })));
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@xandreed/plugin-models",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ },
11
+ "./compat": {
12
+ "types": "./dist/llm/compat.d.ts",
13
+ "default": "./dist/llm/compat.js"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@xandreed/core": "0.3.0",
18
+ "effect": "3.21.4",
19
+ "@effect/ai": "0.35.0",
20
+ "@effect/ai-anthropic": "^0.25.0",
21
+ "@effect/ai-google": "^0.14.0",
22
+ "@effect/ai-openai": "^0.39.2",
23
+ "@effect/platform": "0.96.1",
24
+ "ws": "^8.21.0"
25
+ },
26
+ "private": false,
27
+ "types": "./dist/index.d.ts",
28
+ "files": [
29
+ "dist",
30
+ "skills",
31
+ "profiles",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "engines": {
36
+ "bun": ">=1.3.0"
37
+ },
38
+ "license": "MIT"
39
+ }