super-dsh 0.1.6 → 0.1.10-e

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 (260) hide show
  1. package/README.md +25 -3
  2. package/agent-agy/cordis.patch.yml +7 -4
  3. package/agent-agy/dist/agent.d.ts +12 -11
  4. package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
  5. package/agent-agy/dist/agy-auth-gateway.js +233 -0
  6. package/agent-agy/dist/agy-auth.d.ts +153 -0
  7. package/agent-agy/dist/agy-auth.js +179 -0
  8. package/agent-agy/dist/agy-cli-client.d.ts +39 -9
  9. package/agent-agy/dist/agy-cli-client.js +82 -18
  10. package/agent-agy/dist/agy-store.d.ts +8 -0
  11. package/agent-agy/dist/index.d.ts +46 -16
  12. package/agent-agy/dist/index.js +82 -56
  13. package/agent-agy/dist/models.d.ts +70 -0
  14. package/agent-agy/dist/models.js +102 -3
  15. package/agent-agy/package.json +7 -5
  16. package/agent-agy-sdk/cordis.patch.yml +58 -0
  17. package/agent-agy-sdk/dist/adapter.d.ts +15 -0
  18. package/agent-agy-sdk/dist/adapter.js +42 -0
  19. package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
  20. package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
  21. package/agent-agy-sdk/dist/agent.d.ts +138 -0
  22. package/agent-agy-sdk/dist/agent.js +494 -0
  23. package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
  24. package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
  25. package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
  26. package/agent-agy-sdk/dist/agy-sessions.js +107 -0
  27. package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
  28. package/agent-agy-sdk/dist/agy-store.js +83 -0
  29. package/agent-agy-sdk/dist/inbox.d.ts +26 -0
  30. package/agent-agy-sdk/dist/inbox.js +53 -0
  31. package/agent-agy-sdk/dist/index.d.ts +93 -0
  32. package/agent-agy-sdk/dist/index.js +476 -0
  33. package/agent-agy-sdk/dist/models.d.ts +41 -0
  34. package/agent-agy-sdk/dist/models.js +74 -0
  35. package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
  36. package/agent-agy-sdk/dist/sdk-venv.js +173 -0
  37. package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
  38. package/agent-agy-sdk/dist/world-plugin.js +82 -0
  39. package/agent-agy-sdk/package.json +64 -0
  40. package/agent-claude/dist/adapter.js +106 -31
  41. package/agent-claude/dist/agent.js +154 -17
  42. package/agent-claude/dist/claude-client.js +84 -43
  43. package/agent-claude/dist/claude-launch.js +101 -0
  44. package/agent-claude/dist/claude-native-config.js +169 -0
  45. package/agent-claude/dist/claude-provider-gateway.js +340 -0
  46. package/agent-claude/dist/claude-providers.js +391 -0
  47. package/agent-claude/dist/claude-route.js +152 -0
  48. package/agent-claude/dist/index.js +214 -37
  49. package/agent-claude/dist/models.js +14 -0
  50. package/agent-claude/dist/sdk-runtime.js +320 -0
  51. package/agent-claude/package.json +6 -4
  52. package/agent-codex/README.md +141 -36
  53. package/agent-codex/cordis.patch.yml +14 -10
  54. package/agent-codex/dist/adapter.js +64 -28
  55. package/agent-codex/dist/agent.js +255 -81
  56. package/agent-codex/dist/app-server-approvals.js +165 -0
  57. package/agent-codex/dist/app-server-client.js +723 -0
  58. package/agent-codex/dist/app-server-events.js +432 -0
  59. package/agent-codex/dist/app-server-protocol.js +95 -0
  60. package/agent-codex/dist/app-server-wire.js +299 -0
  61. package/agent-codex/dist/codex-launch.js +78 -0
  62. package/agent-codex/dist/codex-native-config.js +392 -0
  63. package/agent-codex/dist/codex-provider-gateway.js +402 -0
  64. package/agent-codex/dist/codex-providers.js +376 -0
  65. package/agent-codex/dist/codex-route.js +86 -0
  66. package/agent-codex/dist/index.js +256 -115
  67. package/agent-codex/dist/permission.js +75 -1
  68. package/agent-codex/package.json +1 -2
  69. package/agent-codex-sdk/README.md +79 -0
  70. package/agent-codex-sdk/cordis.patch.yml +81 -0
  71. package/agent-codex-sdk/dist/adapter.js +91 -0
  72. package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
  73. package/agent-codex-sdk/dist/agent.js +1085 -0
  74. package/agent-codex-sdk/dist/codex-native-config.js +392 -0
  75. package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
  76. package/agent-codex-sdk/dist/codex-providers.js +376 -0
  77. package/agent-codex-sdk/dist/codex-route.js +86 -0
  78. package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
  79. package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
  80. package/agent-codex-sdk/dist/codex-store.js +180 -0
  81. package/agent-codex-sdk/dist/inbox.js +53 -0
  82. package/agent-codex-sdk/dist/index.js +739 -0
  83. package/agent-codex-sdk/dist/permission.js +71 -0
  84. package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
  85. package/agent-codex-sdk/dist/session-map.js +167 -0
  86. package/agent-codex-sdk/dist/world-plugin.js +93 -0
  87. package/agent-codex-sdk/package.json +79 -0
  88. package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
  89. package/agent-omp/README.md +72 -55
  90. package/agent-omp/dist/agent.js +36 -11
  91. package/agent-omp/dist/index.js +92 -52
  92. package/agent-omp/dist/lazy-rpc.js +11 -4
  93. package/agent-omp/dist/omp-cli.js +114 -97
  94. package/agent-omp/dist/omp-store.js +12 -4
  95. package/agent-omp/dist/permission.js +70 -5
  96. package/agent-omp/dist/rpc-client.js +409 -0
  97. package/agent-omp/dist/world-plugin.js +36 -3
  98. package/agent-omp/package.json +2 -6
  99. package/agent-omp-sdk/README.md +103 -0
  100. package/agent-omp-sdk/cordis.patch.yml +108 -0
  101. package/agent-omp-sdk/dist/adapter.d.ts +21 -0
  102. package/agent-omp-sdk/dist/adapter.js +36 -0
  103. package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
  104. package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
  105. package/agent-omp-sdk/dist/agent.d.ts +64 -0
  106. package/agent-omp-sdk/dist/agent.js +1178 -0
  107. package/agent-omp-sdk/dist/discovery.d.ts +57 -0
  108. package/agent-omp-sdk/dist/discovery.js +116 -0
  109. package/agent-omp-sdk/dist/inbox.d.ts +28 -0
  110. package/agent-omp-sdk/dist/inbox.js +53 -0
  111. package/agent-omp-sdk/dist/index.d.ts +60 -0
  112. package/agent-omp-sdk/dist/index.js +632 -0
  113. package/agent-omp-sdk/dist/knobs.d.ts +34 -0
  114. package/agent-omp-sdk/dist/knobs.js +50 -0
  115. package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
  116. package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
  117. package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
  118. package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
  119. package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
  120. package/agent-omp-sdk/dist/mobile-boot.js +38 -0
  121. package/agent-omp-sdk/dist/models.d.ts +39 -0
  122. package/agent-omp-sdk/dist/models.js +299 -0
  123. package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
  124. package/agent-omp-sdk/dist/omp-cli.js +164 -0
  125. package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
  126. package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
  127. package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
  128. package/agent-omp-sdk/dist/omp-executable.js +30 -0
  129. package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
  130. package/agent-omp-sdk/dist/omp-store.js +526 -0
  131. package/agent-omp-sdk/dist/pairing.d.ts +7 -0
  132. package/agent-omp-sdk/dist/pairing.js +45 -0
  133. package/agent-omp-sdk/dist/permission.d.ts +138 -0
  134. package/agent-omp-sdk/dist/permission.js +161 -0
  135. package/agent-omp-sdk/dist/protocol.d.ts +189 -0
  136. package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
  137. package/agent-omp-sdk/dist/rpc-types.js +1 -0
  138. package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
  139. package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
  140. package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
  141. package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
  142. package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
  143. package/agent-omp-sdk/dist/sidecar-client.js +197 -0
  144. package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
  145. package/agent-omp-sdk/dist/store/adapter.js +47 -0
  146. package/agent-omp-sdk/dist/store/db.d.ts +70 -0
  147. package/agent-omp-sdk/dist/store/db.js +210 -0
  148. package/agent-omp-sdk/dist/store/index.d.ts +23 -0
  149. package/agent-omp-sdk/dist/store/index.js +59 -0
  150. package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
  151. package/agent-omp-sdk/dist/store/reconcile.js +51 -0
  152. package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
  153. package/agent-omp-sdk/dist/store/schema.js +40 -0
  154. package/agent-omp-sdk/dist/transport.d.ts +49 -0
  155. package/agent-omp-sdk/dist/transport.js +35 -0
  156. package/agent-omp-sdk/dist/world-home.d.ts +4 -0
  157. package/agent-omp-sdk/dist/world-home.js +23 -0
  158. package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
  159. package/agent-omp-sdk/dist/world-plugin.js +137 -0
  160. package/agent-omp-sdk/package.json +87 -0
  161. package/client/index.js +1883 -287
  162. package/cordis.patch.yml +80 -0
  163. package/package.json +23 -9
  164. package/superd-hub/dist/a2a-delivery.d.ts +189 -0
  165. package/superd-hub/dist/a2a-delivery.js +240 -0
  166. package/superd-hub/dist/a2a-host.d.ts +76 -0
  167. package/superd-hub/dist/a2a-host.js +145 -0
  168. package/superd-hub/dist/a2a-ingest.d.ts +31 -0
  169. package/superd-hub/dist/a2a-ingest.js +636 -0
  170. package/superd-hub/dist/a2a-log.d.ts +17 -0
  171. package/superd-hub/dist/a2a-log.js +42 -0
  172. package/superd-hub/dist/a2a-row.d.ts +65 -0
  173. package/superd-hub/dist/a2a-row.js +162 -0
  174. package/superd-hub/dist/a2a-service.d.ts +357 -0
  175. package/superd-hub/dist/a2a-service.js +980 -0
  176. package/superd-hub/dist/a2a-wire.d.ts +220 -0
  177. package/superd-hub/dist/a2a-wire.js +305 -0
  178. package/superd-hub/dist/agent-ids.d.ts +10 -0
  179. package/superd-hub/dist/agent-ids.js +68 -0
  180. package/superd-hub/dist/agent-roster.d.ts +29 -18
  181. package/superd-hub/dist/agent-roster.js +54 -15
  182. package/superd-hub/dist/agent-status.d.ts +119 -0
  183. package/superd-hub/dist/agent-status.js +386 -0
  184. package/superd-hub/dist/client/agent-settings.d.ts +210 -0
  185. package/superd-hub/dist/client/agent-settings.js +300 -0
  186. package/superd-hub/dist/client/mesh-api.d.ts +134 -0
  187. package/superd-hub/dist/client/mesh-api.js +219 -0
  188. package/superd-hub/dist/client/mesh-groups.d.ts +63 -0
  189. package/superd-hub/dist/client/mesh-groups.js +66 -0
  190. package/superd-hub/dist/client/roster.d.ts +48 -0
  191. package/superd-hub/dist/client/roster.js +102 -0
  192. package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
  193. package/superd-hub/dist/client/runtime-settings-api.js +137 -0
  194. package/superd-hub/dist/client/switch.d.ts +165 -0
  195. package/superd-hub/dist/client/switch.js +295 -0
  196. package/superd-hub/dist/exec-pty-channel.d.ts +58 -0
  197. package/superd-hub/dist/exec-pty-channel.js +162 -0
  198. package/superd-hub/dist/exec-pty-framing.d.ts +32 -0
  199. package/superd-hub/dist/exec-pty-framing.js +81 -0
  200. package/superd-hub/dist/exec-pty-rpc.d.ts +21 -0
  201. package/superd-hub/dist/exec-pty-rpc.js +128 -0
  202. package/superd-hub/dist/exec-pty-session.d.ts +76 -0
  203. package/superd-hub/dist/exec-pty-session.js +234 -0
  204. package/superd-hub/dist/exec.d.ts +56 -0
  205. package/superd-hub/dist/exec.js +225 -0
  206. package/superd-hub/dist/gateway.d.ts +39 -2
  207. package/superd-hub/dist/gateway.js +62 -3
  208. package/superd-hub/dist/hub-tool.d.ts +174 -0
  209. package/superd-hub/dist/hub-tool.js +464 -0
  210. package/superd-hub/dist/index.d.ts +15 -1
  211. package/superd-hub/dist/index.js +16 -0
  212. package/superd-hub/dist/machines.d.ts +45 -0
  213. package/superd-hub/dist/machines.js +125 -0
  214. package/superd-hub/dist/mesh-federate.d.ts +138 -0
  215. package/superd-hub/dist/mesh-federate.js +223 -0
  216. package/superd-hub/dist/mesh-gateway.d.ts +67 -0
  217. package/superd-hub/dist/mesh-gateway.js +408 -0
  218. package/superd-hub/dist/mesh-link.d.ts +274 -0
  219. package/superd-hub/dist/mesh-link.js +441 -0
  220. package/superd-hub/dist/mesh-rpc.d.ts +123 -0
  221. package/superd-hub/dist/mesh-rpc.js +250 -0
  222. package/superd-hub/dist/mux-client.d.ts +71 -0
  223. package/superd-hub/dist/mux-client.js +281 -0
  224. package/superd-hub/dist/pack-version.d.ts +2 -0
  225. package/superd-hub/dist/pack-version.js +30 -0
  226. package/superd-hub/dist/roster.d.ts +6 -0
  227. package/superd-hub/dist/rpc.d.ts +18 -2
  228. package/superd-hub/dist/rpc.js +34 -4
  229. package/superd-hub/dist/spawn-world.js +3 -1
  230. package/superd-hub/dist/switch-core.d.ts +62 -25
  231. package/superd-hub/dist/switch-core.js +53 -30
  232. package/superd-hub/dist/system-head.d.ts +22 -12
  233. package/superd-hub/dist/system-head.js +100 -84
  234. package/superd-hub/dist/wire-endpoints.d.ts +264 -0
  235. package/superd-hub/dist/wire-endpoints.js +180 -0
  236. package/superd-hub/lib/client/index.js +1883 -287
  237. package/superd-hub/package.json +11 -2
  238. package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
  239. package/agent-agy/dist/agent-preset-projection.js +0 -33
  240. package/agent-claude/dist/agent-preset-projection.js +0 -30
  241. package/agent-codex/dist/agent-preset-projection.js +0 -37
  242. package/agent-codex/dist/app-home.js +0 -65
  243. package/agent-codex/dist/models.js +0 -152
  244. package/agent-codex/dist/spike.js +0 -46
  245. package/agent-hermes/dist/agent-preset-projection.js +0 -34
  246. package/agent-omp/dist/agent-preset-projection.js +0 -36
  247. package/agent-omp/dist/app-home.js +0 -86
  248. package/agent-omp/dist/sidecar-client.js +0 -195
  249. package/agent-pi/dist/agent-preset-projection.js +0 -29
  250. package/superd-hub/dist/client-shim.d.ts +0 -28
  251. package/superd-hub/dist/client-shim.js +0 -251
  252. package/superd-hub/dist/index-pass.d.ts +0 -22
  253. package/superd-hub/dist/index-pass.js +0 -47
  254. package/superd-hub/dist/labels.d.ts +0 -45
  255. package/superd-hub/dist/labels.js +0 -83
  256. /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
  257. /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
  258. /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
  259. /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
  260. /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
@@ -0,0 +1,391 @@
1
+ /**
2
+ * claude-providers.ts — the adapter-owned multi-provider store behind the Hub's
3
+ * Claude settings panel (2026-10-04 multi-provider ruling).
4
+ *
5
+ * Claude Code is a single-provider harness: its own `settings.json` env block
6
+ * names exactly ONE `ANTHROPIC_BASE_URL` with one key, so the LIST of
7
+ * configured providers has nowhere to live in Claude's home, and this module
8
+ * is where it lives instead: `<worldHome>/claude-providers.json` under the
9
+ * world's DSH home (`<dshHome>/agents/claude/`), the location dev-rules §5/§13
10
+ * already assigns to the adapter's own DSH state.
11
+ *
12
+ * This file is the CANONICAL provider list. The native `settings.json` env
13
+ * block is a SOURCE, not a peer: `syncNativeProviders` folds whatever it
14
+ * declares into this list, deduplicated by endpoint URL (the only identity a
15
+ * hand-written settings env reliably carries), and never deletes anything.
16
+ * There is no "selected" provider — which provider a session runs on is DSH's
17
+ * own per-session `model/selection`, and the native home is written by nobody.
18
+ *
19
+ * The file carries API keys, so it is written 0600 with the sibling
20
+ * `.credentials.yaml` posture, and reads are fail-soft: a corrupt or partial
21
+ * store must degrade to "no providers yet", never take the world down. A
22
+ * provider entry that does not survive validation is DROPPED rather than
23
+ * poisoning the whole list.
24
+ *
25
+ * @module agent-claude/claude-providers
26
+ */
27
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
28
+ import { dirname, join } from "node:path";
29
+ /**
30
+ * A provider id becomes one half of the composite model id and is shown in the
31
+ * panel, so the alphabet is deliberately narrow: lowercase alphanumerics and
32
+ * interior hyphens. (Covers `zai-plan`, `deepseek`, `glm`; rejects spaces,
33
+ * dots, quotes, the composite-key separator `/` and anything else a slug was
34
+ * never meant to carry.)
35
+ */
36
+ const PROVIDER_ID = /^[a-z0-9][a-z0-9-]*$/;
37
+ /** Longest generated id: the slug part plus its 7-character discriminator. */
38
+ const MAX_ID_LENGTH = 48;
39
+ /** Whether `value` may be used as a provider id. */
40
+ export function isProviderId(value) {
41
+ return typeof value === "string" && PROVIDER_ID.test(value);
42
+ }
43
+ /** Where the store lives: `<worldHome>/claude-providers.json`. */
44
+ export function providerStorePath(worldHome) {
45
+ return join(worldHome, "claude-providers.json");
46
+ }
47
+ /** The empty store — also the fail-soft answer for every unreadable input. */
48
+ function emptyStore() {
49
+ return { providers: [], removed: [] };
50
+ }
51
+ function str(value) {
52
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
53
+ }
54
+ function auth(value) {
55
+ if (value === null || typeof value !== "object")
56
+ return undefined;
57
+ const record = value;
58
+ const kind = record["kind"];
59
+ if (kind !== "authToken" && kind !== "apiKey")
60
+ return undefined;
61
+ // An EMPTY value is a real state, not a malformed entry: it means "this
62
+ // provider is keyed outside Super D", which is how a provider seeded from
63
+ // the user's own settings arrives. See ClaudeProviderAuth.
64
+ const secret = record["value"];
65
+ return typeof secret === "string" ? { kind, value: secret } : undefined;
66
+ }
67
+ function model(value) {
68
+ if (value === null || typeof value !== "object")
69
+ return undefined;
70
+ const record = value;
71
+ const id = str(record["id"]);
72
+ if (id === undefined)
73
+ return undefined;
74
+ const displayName = str(record["displayName"]);
75
+ const contextWindow = record["contextWindow"];
76
+ return {
77
+ id,
78
+ ...(displayName === undefined ? {} : { displayName }),
79
+ ...(typeof contextWindow === "number" && Number.isSafeInteger(contextWindow) && contextWindow > 0
80
+ ? { contextWindow }
81
+ : {}),
82
+ };
83
+ }
84
+ function aliases(value) {
85
+ if (value === null || typeof value !== "object")
86
+ return undefined;
87
+ const record = value;
88
+ const sonnet = str(record["sonnet"]);
89
+ const opus = str(record["opus"]);
90
+ const haiku = str(record["haiku"]);
91
+ if (sonnet === undefined && opus === undefined && haiku === undefined)
92
+ return undefined;
93
+ return {
94
+ ...(sonnet === undefined ? {} : { sonnet }),
95
+ ...(opus === undefined ? {} : { opus }),
96
+ ...(haiku === undefined ? {} : { haiku }),
97
+ };
98
+ }
99
+ /**
100
+ * Validate one raw entry. Returns `undefined` for anything unusable — an id
101
+ * outside the alphabet, a missing endpoint, an auth shape this build does not
102
+ * understand — so the caller can drop it and keep the rest. An empty `models`
103
+ * list is VALID (see {@link ClaudeProviderEntry.models}).
104
+ */
105
+ export function providerEntry(value) {
106
+ if (value === null || typeof value !== "object")
107
+ return undefined;
108
+ const record = value;
109
+ const id = record["id"];
110
+ const label = str(record["label"]);
111
+ const baseUrl = str(record["baseUrl"]);
112
+ const credentials = auth(record["auth"]);
113
+ if (!isProviderId(id) || label === undefined || baseUrl === undefined || credentials === undefined) {
114
+ return undefined;
115
+ }
116
+ const rawModels = record["models"];
117
+ const models = (Array.isArray(rawModels) ? rawModels : [])
118
+ .map(model)
119
+ .filter((entry) => entry !== undefined);
120
+ const parsedAliases = aliases(record["aliases"]);
121
+ const origin = record["origin"] === "native" ? "native" : "user";
122
+ return {
123
+ id,
124
+ label,
125
+ baseUrl,
126
+ auth: credentials,
127
+ models,
128
+ ...(parsedAliases === undefined ? {} : { aliases: parsedAliases }),
129
+ origin,
130
+ };
131
+ }
132
+ /**
133
+ * Read the store. Fail-soft by construction: a missing file, unparsable JSON,
134
+ * a wrong top-level shape, or individual bad entries all degrade toward an
135
+ * empty (or shorter) list rather than throwing.
136
+ */
137
+ export function readProviderStore(worldHome) {
138
+ const path = providerStorePath(worldHome);
139
+ if (!existsSync(path))
140
+ return emptyStore();
141
+ let parsed;
142
+ try {
143
+ parsed = JSON.parse(readFileSync(path, "utf8"));
144
+ }
145
+ catch {
146
+ return emptyStore();
147
+ }
148
+ if (parsed === null || typeof parsed !== "object")
149
+ return emptyStore();
150
+ const record = parsed;
151
+ if (!Array.isArray(record["providers"]))
152
+ return emptyStore();
153
+ const providers = record["providers"]
154
+ .map(providerEntry)
155
+ .filter((entry) => entry !== undefined);
156
+ const rawRemoved = record["removed"];
157
+ const removed = (Array.isArray(rawRemoved) ? rawRemoved : [])
158
+ .filter((value) => typeof value === "string" && value.trim() !== "");
159
+ return { providers, removed };
160
+ }
161
+ /**
162
+ * Persist the store atomically (tmp + rename) and 0600 — the file holds API
163
+ * keys in the clear, exactly like the sibling `.credentials.yaml`, so it must
164
+ * never be group- or world-readable even for the instant before a rewrite.
165
+ */
166
+ export function writeProviderStore(worldHome, store) {
167
+ const path = providerStorePath(worldHome);
168
+ mkdirSync(dirname(path), { recursive: true });
169
+ const temp = `${path}.tmp-${process.pid}`;
170
+ writeFileSync(temp, `${JSON.stringify(store, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
171
+ renameSync(temp, path);
172
+ }
173
+ /**
174
+ * Insert or replace one provider by id, preserving list order.
175
+ *
176
+ * An explicit add also clears the endpoint's removal tombstone: re-adding a
177
+ * provider is the user overruling their own earlier removal, and leaving the
178
+ * tombstone would make the next sync skip it again.
179
+ */
180
+ export function upsertProvider(store, entry) {
181
+ const at = store.providers.findIndex((existing) => existing.id === entry.id);
182
+ const providers = at === -1
183
+ ? [...store.providers, entry]
184
+ : store.providers.map((existing, index) => (index === at ? entry : existing));
185
+ const key = endpointKey(entry.baseUrl);
186
+ // The endpoint's tombstone is FILTERED OUT, not carried forward: leaving it
187
+ // would make the next sync skip the endpoint the user just re-added.
188
+ const removed = store.removed.filter((candidate) => candidate !== key);
189
+ return { providers, removed };
190
+ }
191
+ /**
192
+ * Drop one provider, and TOMBSTONE its endpoint so the reconcile cannot bring
193
+ * it back: the user's own `settings.json` still declares it, and a list editor
194
+ * whose delete button undoes itself every ten minutes is worse than no button.
195
+ *
196
+ * Removing from this list is still not "unconfiguring Claude Code": nothing
197
+ * native is touched, and the user's own CLI keeps running whatever their file
198
+ * says.
199
+ */
200
+ export function removeProvider(store, id) {
201
+ const entry = store.providers.find((candidate) => candidate.id === id);
202
+ const providers = store.providers.filter((candidate) => candidate.id !== id);
203
+ if (entry === undefined)
204
+ return { providers, removed: store.removed };
205
+ const key = endpointKey(entry.baseUrl);
206
+ const removed = store.removed.includes(key) ? store.removed : [...store.removed, key];
207
+ return { providers, removed };
208
+ }
209
+ /**
210
+ * The comparison key for "the same endpoint". A settings env block has no
211
+ * identity beyond its `ANTHROPIC_BASE_URL`, and a hand-written one carries no
212
+ * other identity worth trusting — so the endpoint IS the dedupe key. Case, a
213
+ * trailing slash, and the default port are noise.
214
+ *
215
+ * @param baseUrl - a provider's `ANTHROPIC_BASE_URL` (any shape; unparsable input is compared verbatim).
216
+ * @returns a stable key.
217
+ */
218
+ export function endpointKey(baseUrl) {
219
+ const trimmed = baseUrl.trim();
220
+ let parsed;
221
+ try {
222
+ parsed = new URL(trimmed);
223
+ }
224
+ catch {
225
+ return trimmed.toLowerCase();
226
+ }
227
+ const path = parsed.pathname.replace(/\/+$/, "");
228
+ const port = parsed.port === "" || (parsed.protocol === "https:" && parsed.port === "443")
229
+ || (parsed.protocol === "http:" && parsed.port === "80")
230
+ ? ""
231
+ : `:${parsed.port}`;
232
+ return `${parsed.protocol}//${parsed.hostname.toLowerCase()}${port}${path}`;
233
+ }
234
+ /** Reduce arbitrary text to the provider-id alphabet, collapsing runs of junk. */
235
+ function slugify(value) {
236
+ const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
237
+ return slug === "" ? "provider" : slug.slice(0, MAX_ID_LENGTH - 8);
238
+ }
239
+ /** A short, stable discriminator so two endpoints sharing a host stay distinct. */
240
+ function discriminator(value) {
241
+ let hash = 0x811c9dc5;
242
+ for (let index = 0; index < value.length; index += 1) {
243
+ hash ^= value.charCodeAt(index);
244
+ hash = Math.imul(hash, 0x01000193) >>> 0;
245
+ }
246
+ return hash.toString(36).padStart(6, "0").slice(-6);
247
+ }
248
+ /**
249
+ * The provider id for an endpoint. A settings env block carries no provider
250
+ * name at all, so this is the path every folded provider takes, and the one a
251
+ * UI-added provider reuses.
252
+ *
253
+ * @param baseUrl - the provider's endpoint.
254
+ * @returns a slug that satisfies {@link isProviderId}.
255
+ */
256
+ export function providerIdFromEndpoint(baseUrl) {
257
+ const key = endpointKey(baseUrl);
258
+ let host = "provider";
259
+ let path = "";
260
+ try {
261
+ const parsed = new URL(key);
262
+ host = parsed.hostname;
263
+ path = parsed.pathname;
264
+ }
265
+ catch {
266
+ // keep the defaults
267
+ }
268
+ return `${slugify(`${host}${path}`)}-${discriminator(key)}`;
269
+ }
270
+ /**
271
+ * Make `wanted` usable and unique within `taken`.
272
+ *
273
+ * @param wanted - the preferred id (usually a generated slug).
274
+ * @param taken - ids already claimed by other entries.
275
+ * @returns a valid id not present in `taken`.
276
+ */
277
+ export function uniqueProviderId(wanted, taken) {
278
+ const used = new Set(taken);
279
+ const base = isProviderId(wanted) ? wanted : slugify(wanted);
280
+ if (!used.has(base))
281
+ return base;
282
+ const salted = `${base.slice(0, MAX_ID_LENGTH - 7)}-${discriminator(wanted)}`;
283
+ if (!used.has(salted))
284
+ return salted;
285
+ for (let index = 2;; index += 1) {
286
+ const candidate = `${base.slice(0, MAX_ID_LENGTH - 4)}-${index}`;
287
+ if (!used.has(candidate))
288
+ return candidate;
289
+ }
290
+ }
291
+ /** Append the models of `incoming` that `existing` does not already carry. */
292
+ function unionModels(existing, incoming) {
293
+ const known = new Set(existing.map((entry) => entry.id));
294
+ const additions = [];
295
+ for (const candidate of incoming) {
296
+ // Same validation a store read applies: junk in an import is skipped, not
297
+ // propagated — and a duplicate id (even twice within one import) is noise.
298
+ const parsed = model(candidate);
299
+ if (parsed === undefined || known.has(parsed.id))
300
+ continue;
301
+ known.add(parsed.id);
302
+ additions.push(parsed);
303
+ }
304
+ return additions.length === 0 ? existing : [...existing, ...additions];
305
+ }
306
+ /**
307
+ * Structural auth equality.
308
+ *
309
+ * Reference equality is wrong here: a native sync re-parses the settings on
310
+ * every tick, so the incoming auth object is a FRESH one each time and `!==`
311
+ * would report a change forever. That is not a cosmetic difference — it makes
312
+ * the "only write when something moved" contract fail, so every scheduled
313
+ * reconcile would rewrite a 0600 secrets file.
314
+ */
315
+ function sameAuth(left, right) {
316
+ return left.kind === right.kind && left.value === right.value;
317
+ }
318
+ /** Structural alias equality — same reasoning as {@link sameAuth}. */
319
+ function sameAliases(left, right) {
320
+ if (left === undefined || right === undefined)
321
+ return left === right;
322
+ return left.sonnet === right.sonnet && left.opus === right.opus && left.haiku === right.haiku;
323
+ }
324
+ /**
325
+ * Fold the providers a native settings env declares into the canonical store.
326
+ *
327
+ * Identity is the endpoint (see {@link endpointKey}); an import whose endpoint
328
+ * already matches an entry refreshes that entry's endpoint, model list, aliases
329
+ * and — only when the entry holds no secret of its own — its auth shape.
330
+ * Anything unmatched is appended, labelled with its endpoint URL, because that
331
+ * is the only name a settings env block gives it. Nothing is ever deleted
332
+ * here: a provider dropped from the settings may still be one the user uses
333
+ * through Super D. The one exception runs the other way — an endpoint the user
334
+ * REMOVED in the panel is skipped, so their removal is not undone by the next
335
+ * reconcile.
336
+ *
337
+ * @param store - the canonical list.
338
+ * @param imported - providers parsed from the native settings env, in file order.
339
+ * @returns the new store plus which ids were added and which were refreshed.
340
+ */
341
+ export function syncNativeProviders(store, imported) {
342
+ const providers = [...store.providers];
343
+ const added = [];
344
+ const updated = [];
345
+ for (const incoming of imported) {
346
+ const baseUrl = incoming.baseUrl.trim();
347
+ if (baseUrl === "")
348
+ continue;
349
+ const key = endpointKey(baseUrl);
350
+ if (store.removed.includes(key))
351
+ continue;
352
+ const at = providers.findIndex((entry) => endpointKey(entry.baseUrl) === key);
353
+ if (at === -1) {
354
+ const id = uniqueProviderId(providerIdFromEndpoint(baseUrl), providers.map((entry) => entry.id));
355
+ providers.push({
356
+ id,
357
+ label: baseUrl,
358
+ baseUrl,
359
+ auth: incoming.auth,
360
+ models: unionModels([], incoming.models ?? []),
361
+ ...(incoming.aliases === undefined ? {} : { aliases: incoming.aliases }),
362
+ origin: "native",
363
+ });
364
+ added.push(id);
365
+ continue;
366
+ }
367
+ const entry = providers[at];
368
+ const models = unionModels(entry.models, incoming.models ?? []);
369
+ // The native env block is the user's own statement about how Claude Code
370
+ // should authenticate, but a secret Super D already holds must not be
371
+ // dropped in favour of it — an EMPTY value is the only shape that defers
372
+ // ("Super D holds no copy"), so only that one follows the native file.
373
+ const adoptAuth = entry.auth.value === "";
374
+ const next = {
375
+ ...entry,
376
+ baseUrl,
377
+ ...(adoptAuth ? { auth: incoming.auth } : {}),
378
+ models,
379
+ ...(incoming.aliases === undefined ? {} : { aliases: incoming.aliases }),
380
+ };
381
+ const changed = next.baseUrl !== entry.baseUrl
382
+ || next.models !== entry.models
383
+ || !sameAuth(next.auth, entry.auth)
384
+ || !sameAliases(next.aliases, entry.aliases);
385
+ if (changed) {
386
+ providers[at] = next;
387
+ updated.push(entry.id);
388
+ }
389
+ }
390
+ return { store: { providers, removed: store.removed }, added, updated };
391
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * claude-route.ts — which provider/model a Claude turn must run
3
+ * (2026-10-04 multi-provider rework).
4
+ *
5
+ * This is the whole of Super D's model-selection policy, and the point is that
6
+ * there is almost none: the precedence below is the harness's own
7
+ * (`sessionController.selectionFor(agent).current`), so a session's route is
8
+ * decided by DSH exactly as it would be for a native agent, and Super D only
9
+ * has to read it.
10
+ *
11
+ * pending the durable `modelSelection` projection's `pending` — a switch
12
+ * made through `session/selectModel` that no `request/header` has
13
+ * retired yet. This is what makes a mid-turn switch land on the NEXT
14
+ * turn, for free: the harness records the intent and the next
15
+ * request consumes it.
16
+ * logged the last `request/header` snapshot — what the session ACTUALLY ran
17
+ * last. It is also what lets a resumed session name the provider
18
+ * that produced its transcript.
19
+ * preferred `ctx.agentDefaultModel` — the deployment default for a session
20
+ * that has never run.
21
+ * fallback the agent's own reconciled options — the pair the factory built
22
+ * from the provider store and the user's native `settings.json`.
23
+ *
24
+ * The Dash model selector, though, speaks one id per model, and with more than
25
+ * one provider a bare CLI alias (`glm-5.2`) is ambiguous. The catalog therefore
26
+ * mints every entry's id as `provider/model` (`compositeModelId`), and a
27
+ * selection's id splits back into its halves here (`parseCompositeModelId`) —
28
+ * on the FIRST slash, because a provider id is slug-alphabet and never carries
29
+ * one while a model id may.
30
+ *
31
+ * One shape predates the per-provider routes and is still in the wild: sessions
32
+ * created before the rework pinned the hardcoded `claude` route with the
33
+ * COMPOSITE id as their model half (`{provider: "claude", model: "glm/glm-5.3"}`).
34
+ * Read raw it names no store entry, so the session would silently relaunch on
35
+ * the native endpoint; {@link routeOf} revives it by splitting that one legacy
36
+ * shape, and only that one — a new-shape selection's raw model id may carry a
37
+ * slash of its own (`zai-plan` + `anthropic/claude-sonnet-4`).
38
+ *
39
+ * Kept out of `agent.ts` so the precedence can be pinned without standing up
40
+ * a Session, a Context and a client: it is the one piece of this feature whose
41
+ * mistakes are silent.
42
+ *
43
+ * @module agent-claude/claude-route
44
+ */
45
+ /**
46
+ * The route id every selection was pinned under BEFORE the per-provider-route
47
+ * rework. A local literal on purpose: importing `CLAUDE_PROVIDER_ID` from
48
+ * `adapter.ts` would close a module cycle (`adapter → models → claude-route`).
49
+ */
50
+ const LEGACY_ROUTE_ID = "claude";
51
+ /**
52
+ * Read a route out of one candidate value.
53
+ *
54
+ * Every candidate arrives as untyped projection/config data, so this validates
55
+ * structurally and answers `undefined` for anything incomplete. A half-filled
56
+ * route must never win the precedence: falling through to a complete one is
57
+ * always better than launching a child with an empty model.
58
+ *
59
+ * @param value - a candidate route (a `ModelSelection`-shaped object, or junk).
60
+ * @returns the route, or `undefined` when it does not name both halves.
61
+ */
62
+ export function routeOf(value) {
63
+ if (value === null || typeof value !== "object" || Array.isArray(value))
64
+ return undefined;
65
+ const record = value;
66
+ const provider = record["provider"];
67
+ const model = record["model"];
68
+ if (typeof provider !== "string" || provider === "" || typeof model !== "string" || model === "")
69
+ return undefined;
70
+ // Revive a selection from the pre-per-provider-route architecture: the route
71
+ // half was hardcoded `claude` and the model half was the COMPOSITE id, so a
72
+ // resumed session hands back `{provider: "claude", model: "glm/glm-5.3"}`.
73
+ // Read raw that pair misses the provider store, and the agent falls back to
74
+ // NATIVE_LAUNCH — the session quietly resumes on the native endpoint instead
75
+ // of its pinned provider until the user re-picks. Keyed on the provider half
76
+ // so a new-shape selection passes through untouched, even when its raw model
77
+ // id carries a slash of its own.
78
+ if (provider === LEGACY_ROUTE_ID)
79
+ return parseCompositeModelId(model) ?? { provider, model };
80
+ return { provider, model };
81
+ }
82
+ /**
83
+ * Resolve the route for the next request by the harness's own precedence.
84
+ *
85
+ * @param candidates - the candidates in descending priority: `pending`,
86
+ * `logged`, `preferred`, `fallback`. `preferred` is the harness default
87
+ * (`agentDefaultModel`); `fallback` is the factory's own options.
88
+ * @returns the first complete route, or `undefined` when none is usable.
89
+ */
90
+ export function resolveRoute(candidates) {
91
+ return routeOf(candidates.pending)
92
+ ?? routeOf(candidates.logged)
93
+ ?? routeOf(candidates.preferred)
94
+ ?? routeOf(candidates.fallback);
95
+ }
96
+ /**
97
+ * Mint the Dash-facing model id for one provider's model: `${provider}/${model}`.
98
+ *
99
+ * The catalog publishes this composite as the entry id because the selector
100
+ * picks by a single id and a bare CLI alias is ambiguous across providers. The
101
+ * provider half is a store id (slug alphabet — `isProviderId` in
102
+ * claude-providers.ts), so it can never itself contain the separator.
103
+ *
104
+ * @param provider - the store provider id.
105
+ * @param model - the real model id the provider serves.
106
+ */
107
+ export function compositeModelId(provider, model) {
108
+ return `${provider}/${model}`;
109
+ }
110
+ /**
111
+ * Split a composite model id back into its halves, on the FIRST `/` only.
112
+ *
113
+ * The split is anchored at the first slash because the MODEL half may carry
114
+ * slashes of its own while the provider half never does; any later split point
115
+ * would shred the model half and would not round-trip through
116
+ * {@link compositeModelId}.
117
+ *
118
+ * @param id - a composite model id (`provider/model`), e.g. a selection the
119
+ * catalog minted.
120
+ * @returns the halves, or `undefined` when there is no `/` at all or either
121
+ * half is empty — the same completeness rule `routeOf` enforces.
122
+ */
123
+ export function parseCompositeModelId(id) {
124
+ const slash = id.indexOf("/");
125
+ if (slash === -1)
126
+ return undefined;
127
+ const provider = id.slice(0, slash);
128
+ const model = id.slice(slash + 1);
129
+ if (provider === "" || model === "")
130
+ return undefined;
131
+ return { provider, model };
132
+ }
133
+ /**
134
+ * The route a brand-new session's log is SEEDED with.
135
+ *
136
+ * The single argument is deliberate. The factory's reconciled options are the
137
+ * only acceptable source, because they are already ONE pair: a first attempt
138
+ * at this (the codex line) built the seed from the native provider id and the
139
+ * deployment default model independently, which produced
140
+ * `zhipu-cn/deepseek-flash` — a route no provider serves, and which had to be
141
+ * caught by reading a live session's projection rather than by any unit test.
142
+ * Taking one object makes that mistake unrepresentable.
143
+ *
144
+ * @param options - the factory's reconciled agent options.
145
+ * @returns the seed. The declared input already names both halves, so the
146
+ * `routeOf` screen inside always answers a route for it; a pair that somehow
147
+ * arrives half-empty surfaces as `undefined` to the caller's own guard rather
148
+ * than as a broken seed.
149
+ */
150
+ export function routeSeed(options) {
151
+ return routeOf(options);
152
+ }