@mastra/factory 0.0.0-fix-thread-subscription-multi-instance-redis-streams-20260730204150

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 (313) hide show
  1. package/CHANGELOG.md +695 -0
  2. package/LICENSE.md +30 -0
  3. package/README.md +27 -0
  4. package/dist/auth.d.ts +181 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/dist/auth.js +505 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/capabilities/connection.d.ts +9 -0
  9. package/dist/capabilities/connection.d.ts.map +1 -0
  10. package/dist/capabilities/connection.js +1 -0
  11. package/dist/capabilities/intake.d.ts +140 -0
  12. package/dist/capabilities/intake.d.ts.map +1 -0
  13. package/dist/capabilities/intake.js +1 -0
  14. package/dist/capabilities/version-control.d.ts +245 -0
  15. package/dist/capabilities/version-control.d.ts.map +1 -0
  16. package/dist/capabilities/version-control.js +1 -0
  17. package/dist/factory.d.ts +156 -0
  18. package/dist/factory.d.ts.map +1 -0
  19. package/dist/factory.js +483 -0
  20. package/dist/factory.js.map +1 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +7 -0
  24. package/dist/integrations/base.d.ts +210 -0
  25. package/dist/integrations/base.d.ts.map +1 -0
  26. package/dist/integrations/base.js +1 -0
  27. package/dist/integrations/github/config.d.ts +60 -0
  28. package/dist/integrations/github/config.d.ts.map +1 -0
  29. package/dist/integrations/github/config.js +42 -0
  30. package/dist/integrations/github/config.js.map +1 -0
  31. package/dist/integrations/github/factory-session.d.ts +12 -0
  32. package/dist/integrations/github/factory-session.d.ts.map +1 -0
  33. package/dist/integrations/github/factory-session.js +37 -0
  34. package/dist/integrations/github/factory-session.js.map +1 -0
  35. package/dist/integrations/github/integration.d.ts +191 -0
  36. package/dist/integrations/github/integration.d.ts.map +1 -0
  37. package/dist/integrations/github/integration.js +1077 -0
  38. package/dist/integrations/github/integration.js.map +1 -0
  39. package/dist/integrations/github/issue-triage.d.ts +27 -0
  40. package/dist/integrations/github/issue-triage.d.ts.map +1 -0
  41. package/dist/integrations/github/issue-triage.js +82 -0
  42. package/dist/integrations/github/issue-triage.js.map +1 -0
  43. package/dist/integrations/github/pat.d.ts +38 -0
  44. package/dist/integrations/github/pat.d.ts.map +1 -0
  45. package/dist/integrations/github/pat.js +59 -0
  46. package/dist/integrations/github/pat.js.map +1 -0
  47. package/dist/integrations/github/provenance.d.ts +27 -0
  48. package/dist/integrations/github/provenance.d.ts.map +1 -0
  49. package/dist/integrations/github/provenance.js +75 -0
  50. package/dist/integrations/github/provenance.js.map +1 -0
  51. package/dist/integrations/github/routes.d.ts +69 -0
  52. package/dist/integrations/github/routes.d.ts.map +1 -0
  53. package/dist/integrations/github/routes.js +1324 -0
  54. package/dist/integrations/github/routes.js.map +1 -0
  55. package/dist/integrations/github/rules.d.ts +78 -0
  56. package/dist/integrations/github/rules.d.ts.map +1 -0
  57. package/dist/integrations/github/rules.js +362 -0
  58. package/dist/integrations/github/rules.js.map +1 -0
  59. package/dist/integrations/github/sandbox-release.d.ts +38 -0
  60. package/dist/integrations/github/sandbox-release.d.ts.map +1 -0
  61. package/dist/integrations/github/sandbox-release.js +74 -0
  62. package/dist/integrations/github/sandbox-release.js.map +1 -0
  63. package/dist/integrations/github/sandbox.d.ts +281 -0
  64. package/dist/integrations/github/sandbox.d.ts.map +1 -0
  65. package/dist/integrations/github/sandbox.js +547 -0
  66. package/dist/integrations/github/sandbox.js.map +1 -0
  67. package/dist/integrations/github/session-subscriptions.d.ts +26 -0
  68. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
  69. package/dist/integrations/github/session-subscriptions.js +192 -0
  70. package/dist/integrations/github/session-subscriptions.js.map +1 -0
  71. package/dist/integrations/github/subscriptions.d.ts +54 -0
  72. package/dist/integrations/github/subscriptions.d.ts.map +1 -0
  73. package/dist/integrations/github/subscriptions.js +62 -0
  74. package/dist/integrations/github/subscriptions.js.map +1 -0
  75. package/dist/integrations/github/token-refresh.d.ts +12 -0
  76. package/dist/integrations/github/token-refresh.d.ts.map +1 -0
  77. package/dist/integrations/github/token-refresh.js +24 -0
  78. package/dist/integrations/github/token-refresh.js.map +1 -0
  79. package/dist/integrations/github/webhook.d.ts +87 -0
  80. package/dist/integrations/github/webhook.d.ts.map +1 -0
  81. package/dist/integrations/github/webhook.js +397 -0
  82. package/dist/integrations/github/webhook.js.map +1 -0
  83. package/dist/integrations/linear/agent-tools.d.ts +34 -0
  84. package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
  85. package/dist/integrations/linear/agent-tools.js +85 -0
  86. package/dist/integrations/linear/agent-tools.js.map +1 -0
  87. package/dist/integrations/linear/integration.d.ts +233 -0
  88. package/dist/integrations/linear/integration.d.ts.map +1 -0
  89. package/dist/integrations/linear/integration.js +672 -0
  90. package/dist/integrations/linear/integration.js.map +1 -0
  91. package/dist/integrations/linear/routes.d.ts +61 -0
  92. package/dist/integrations/linear/routes.d.ts.map +1 -0
  93. package/dist/integrations/linear/routes.js +239 -0
  94. package/dist/integrations/linear/routes.js.map +1 -0
  95. package/dist/integrations/linear/rules.d.ts +42 -0
  96. package/dist/integrations/linear/rules.d.ts.map +1 -0
  97. package/dist/integrations/linear/rules.js +133 -0
  98. package/dist/integrations/linear/rules.js.map +1 -0
  99. package/dist/integrations/linear/storage.d.ts +65 -0
  100. package/dist/integrations/linear/storage.d.ts.map +1 -0
  101. package/dist/integrations/linear/storage.js +1 -0
  102. package/dist/integrations/platform/api-client.d.ts +26 -0
  103. package/dist/integrations/platform/api-client.d.ts.map +1 -0
  104. package/dist/integrations/platform/api-client.js +156 -0
  105. package/dist/integrations/platform/api-client.js.map +1 -0
  106. package/dist/integrations/platform/github/event-worker.d.ts +49 -0
  107. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
  108. package/dist/integrations/platform/github/event-worker.js +348 -0
  109. package/dist/integrations/platform/github/event-worker.js.map +1 -0
  110. package/dist/integrations/platform/github/integration.d.ts +50 -0
  111. package/dist/integrations/platform/github/integration.d.ts.map +1 -0
  112. package/dist/integrations/platform/github/integration.js +961 -0
  113. package/dist/integrations/platform/github/integration.js.map +1 -0
  114. package/dist/integrations/platform/linear/integration.d.ts +46 -0
  115. package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
  116. package/dist/integrations/platform/linear/integration.js +483 -0
  117. package/dist/integrations/platform/linear/integration.js.map +1 -0
  118. package/dist/integrations/workos/integration.d.ts +39 -0
  119. package/dist/integrations/workos/integration.d.ts.map +1 -0
  120. package/dist/integrations/workos/integration.js +91 -0
  121. package/dist/integrations/workos/integration.js.map +1 -0
  122. package/dist/routes/config.d.ts +235 -0
  123. package/dist/routes/config.d.ts.map +1 -0
  124. package/dist/routes/config.js +972 -0
  125. package/dist/routes/config.js.map +1 -0
  126. package/dist/routes/custom-provider-source.d.ts +52 -0
  127. package/dist/routes/custom-provider-source.d.ts.map +1 -0
  128. package/dist/routes/custom-provider-source.js +124 -0
  129. package/dist/routes/custom-provider-source.js.map +1 -0
  130. package/dist/routes/fs.d.ts +151 -0
  131. package/dist/routes/fs.d.ts.map +1 -0
  132. package/dist/routes/fs.js +708 -0
  133. package/dist/routes/fs.js.map +1 -0
  134. package/dist/routes/intake.d.ts +23 -0
  135. package/dist/routes/intake.d.ts.map +1 -0
  136. package/dist/routes/intake.js +179 -0
  137. package/dist/routes/intake.js.map +1 -0
  138. package/dist/routes/oauth.d.ts +43 -0
  139. package/dist/routes/oauth.d.ts.map +1 -0
  140. package/dist/routes/oauth.js +463 -0
  141. package/dist/routes/oauth.js.map +1 -0
  142. package/dist/routes/projects.d.ts +19 -0
  143. package/dist/routes/projects.d.ts.map +1 -0
  144. package/dist/routes/projects.js +461 -0
  145. package/dist/routes/projects.js.map +1 -0
  146. package/dist/routes/provider-credentials.d.ts +81 -0
  147. package/dist/routes/provider-credentials.d.ts.map +1 -0
  148. package/dist/routes/provider-credentials.js +86 -0
  149. package/dist/routes/provider-credentials.js.map +1 -0
  150. package/dist/routes/route.d.ts +48 -0
  151. package/dist/routes/route.d.ts.map +1 -0
  152. package/dist/routes/route.js +15 -0
  153. package/dist/routes/route.js.map +1 -0
  154. package/dist/routes/skills.d.ts +30 -0
  155. package/dist/routes/skills.d.ts.map +1 -0
  156. package/dist/routes/skills.js +168 -0
  157. package/dist/routes/skills.js.map +1 -0
  158. package/dist/routes/surface.d.ts +91 -0
  159. package/dist/routes/surface.d.ts.map +1 -0
  160. package/dist/routes/surface.js +260 -0
  161. package/dist/routes/surface.js.map +1 -0
  162. package/dist/routes/tenant-credentials.d.ts +74 -0
  163. package/dist/routes/tenant-credentials.d.ts.map +1 -0
  164. package/dist/routes/tenant-credentials.js +156 -0
  165. package/dist/routes/tenant-credentials.js.map +1 -0
  166. package/dist/routes/work-items.d.ts +40 -0
  167. package/dist/routes/work-items.d.ts.map +1 -0
  168. package/dist/routes/work-items.js +695 -0
  169. package/dist/routes/work-items.js.map +1 -0
  170. package/dist/rules/binding-context.d.ts +5 -0
  171. package/dist/rules/binding-context.d.ts.map +1 -0
  172. package/dist/rules/binding-context.js +28 -0
  173. package/dist/rules/binding-context.js.map +1 -0
  174. package/dist/rules/defaults.d.ts +9 -0
  175. package/dist/rules/defaults.d.ts.map +1 -0
  176. package/dist/rules/defaults.js +260 -0
  177. package/dist/rules/defaults.js.map +1 -0
  178. package/dist/rules/dispatcher.d.ts +42 -0
  179. package/dist/rules/dispatcher.d.ts.map +1 -0
  180. package/dist/rules/dispatcher.js +518 -0
  181. package/dist/rules/dispatcher.js.map +1 -0
  182. package/dist/rules/index.d.ts +7 -0
  183. package/dist/rules/index.d.ts.map +1 -0
  184. package/dist/rules/index.js +5 -0
  185. package/dist/rules/processor.d.ts +54 -0
  186. package/dist/rules/processor.d.ts.map +1 -0
  187. package/dist/rules/processor.js +378 -0
  188. package/dist/rules/processor.js.map +1 -0
  189. package/dist/rules/resolve.d.ts +17 -0
  190. package/dist/rules/resolve.d.ts.map +1 -0
  191. package/dist/rules/resolve.js +30 -0
  192. package/dist/rules/resolve.js.map +1 -0
  193. package/dist/rules/start-coordinator.d.ts +60 -0
  194. package/dist/rules/start-coordinator.d.ts.map +1 -0
  195. package/dist/rules/start-coordinator.js +194 -0
  196. package/dist/rules/start-coordinator.js.map +1 -0
  197. package/dist/rules/tools.d.ts +10 -0
  198. package/dist/rules/tools.d.ts.map +1 -0
  199. package/dist/rules/tools.js +58 -0
  200. package/dist/rules/tools.js.map +1 -0
  201. package/dist/rules/transition-service.d.ts +49 -0
  202. package/dist/rules/transition-service.d.ts.map +1 -0
  203. package/dist/rules/transition-service.js +232 -0
  204. package/dist/rules/transition-service.js.map +1 -0
  205. package/dist/rules/types.d.ts +234 -0
  206. package/dist/rules/types.d.ts.map +1 -0
  207. package/dist/rules/types.js +38 -0
  208. package/dist/rules/types.js.map +1 -0
  209. package/dist/rules/validation.d.ts +11 -0
  210. package/dist/rules/validation.d.ts.map +1 -0
  211. package/dist/rules/validation.js +308 -0
  212. package/dist/rules/validation.js.map +1 -0
  213. package/dist/sandbox/fleet.d.ts +222 -0
  214. package/dist/sandbox/fleet.d.ts.map +1 -0
  215. package/dist/sandbox/fleet.js +322 -0
  216. package/dist/sandbox/fleet.js.map +1 -0
  217. package/dist/sandbox/reattach.d.ts +3 -0
  218. package/dist/sandbox/reattach.d.ts.map +1 -0
  219. package/dist/sandbox/reattach.js +16 -0
  220. package/dist/sandbox/reattach.js.map +1 -0
  221. package/dist/server-error.d.ts +12 -0
  222. package/dist/server-error.d.ts.map +1 -0
  223. package/dist/server-error.js +16 -0
  224. package/dist/server-error.js.map +1 -0
  225. package/dist/skills/service.d.ts +48 -0
  226. package/dist/skills/service.d.ts.map +1 -0
  227. package/dist/skills/service.js +40 -0
  228. package/dist/skills/service.js.map +1 -0
  229. package/dist/spa-static.d.ts +33 -0
  230. package/dist/spa-static.d.ts.map +1 -0
  231. package/dist/spa-static.js +104 -0
  232. package/dist/spa-static.js.map +1 -0
  233. package/dist/state-signing.d.ts +59 -0
  234. package/dist/state-signing.d.ts.map +1 -0
  235. package/dist/state-signing.js +83 -0
  236. package/dist/state-signing.js.map +1 -0
  237. package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
  238. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
  239. package/dist/storage/domains/audit/agent-audit.js +66 -0
  240. package/dist/storage/domains/audit/agent-audit.js.map +1 -0
  241. package/dist/storage/domains/audit/base.d.ts +140 -0
  242. package/dist/storage/domains/audit/base.d.ts.map +1 -0
  243. package/dist/storage/domains/audit/base.js +184 -0
  244. package/dist/storage/domains/audit/base.js.map +1 -0
  245. package/dist/storage/domains/audit/domain.d.ts +69 -0
  246. package/dist/storage/domains/audit/domain.d.ts.map +1 -0
  247. package/dist/storage/domains/audit/domain.js +167 -0
  248. package/dist/storage/domains/audit/domain.js.map +1 -0
  249. package/dist/storage/domains/channel-identity/base.d.ts +96 -0
  250. package/dist/storage/domains/channel-identity/base.d.ts.map +1 -0
  251. package/dist/storage/domains/channel-identity/base.js +149 -0
  252. package/dist/storage/domains/channel-identity/base.js.map +1 -0
  253. package/dist/storage/domains/credentials/base.d.ts +134 -0
  254. package/dist/storage/domains/credentials/base.d.ts.map +1 -0
  255. package/dist/storage/domains/credentials/base.js +273 -0
  256. package/dist/storage/domains/credentials/base.js.map +1 -0
  257. package/dist/storage/domains/custom-providers/base.d.ts +57 -0
  258. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
  259. package/dist/storage/domains/custom-providers/base.js +145 -0
  260. package/dist/storage/domains/custom-providers/base.js.map +1 -0
  261. package/dist/storage/domains/intake/base.d.ts +38 -0
  262. package/dist/storage/domains/intake/base.d.ts.map +1 -0
  263. package/dist/storage/domains/intake/base.js +80 -0
  264. package/dist/storage/domains/intake/base.js.map +1 -0
  265. package/dist/storage/domains/integrations/base.d.ts +126 -0
  266. package/dist/storage/domains/integrations/base.d.ts.map +1 -0
  267. package/dist/storage/domains/integrations/base.js +317 -0
  268. package/dist/storage/domains/integrations/base.js.map +1 -0
  269. package/dist/storage/domains/memory-settings/base.d.ts +62 -0
  270. package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
  271. package/dist/storage/domains/memory-settings/base.js +126 -0
  272. package/dist/storage/domains/memory-settings/base.js.map +1 -0
  273. package/dist/storage/domains/model-packs/base.d.ts +49 -0
  274. package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
  275. package/dist/storage/domains/model-packs/base.js +96 -0
  276. package/dist/storage/domains/model-packs/base.js.map +1 -0
  277. package/dist/storage/domains/projects/base.d.ts +58 -0
  278. package/dist/storage/domains/projects/base.d.ts.map +1 -0
  279. package/dist/storage/domains/projects/base.js +111 -0
  280. package/dist/storage/domains/projects/base.js.map +1 -0
  281. package/dist/storage/domains/queue-health/base.d.ts +52 -0
  282. package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
  283. package/dist/storage/domains/queue-health/base.js +122 -0
  284. package/dist/storage/domains/queue-health/base.js.map +1 -0
  285. package/dist/storage/domains/source-control/base.d.ts +358 -0
  286. package/dist/storage/domains/source-control/base.d.ts.map +1 -0
  287. package/dist/storage/domains/source-control/base.js +908 -0
  288. package/dist/storage/domains/source-control/base.js.map +1 -0
  289. package/dist/storage/domains/source-control/inmemory.d.ts +175 -0
  290. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
  291. package/dist/storage/domains/source-control/inmemory.js +377 -0
  292. package/dist/storage/domains/source-control/inmemory.js.map +1 -0
  293. package/dist/storage/domains/work-items/base.d.ts +412 -0
  294. package/dist/storage/domains/work-items/base.d.ts.map +1 -0
  295. package/dist/storage/domains/work-items/base.js +1309 -0
  296. package/dist/storage/domains/work-items/base.js.map +1 -0
  297. package/dist/storage/domains/work-items/metrics.d.ts +101 -0
  298. package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
  299. package/dist/storage/domains/work-items/metrics.js +221 -0
  300. package/dist/storage/domains/work-items/metrics.js.map +1 -0
  301. package/dist/timing.d.ts +15 -0
  302. package/dist/timing.d.ts.map +1 -0
  303. package/dist/timing.js +34 -0
  304. package/dist/timing.js.map +1 -0
  305. package/dist/workspace.d.ts +24 -0
  306. package/dist/workspace.d.ts.map +1 -0
  307. package/dist/workspace.js +286 -0
  308. package/dist/workspace.js.map +1 -0
  309. package/factory-skills/configure-factory-rules/SKILL.md +67 -0
  310. package/factory-skills/factory-plan/SKILL.md +61 -0
  311. package/factory-skills/factory-review/SKILL.md +155 -0
  312. package/factory-skills/factory-triage/SKILL.md +77 -0
  313. package/package.json +81 -0
@@ -0,0 +1,972 @@
1
+ import { WEB_OAUTH_FLOW_KINDS, getAuthProviderId, listTenantCredentialsForRequest, resolveCredentialContext, tenantOrgId } from "./provider-credentials.js";
2
+ import { Route } from "./route.js";
3
+ import { registerApiRoute } from "@mastra/core/server";
4
+ import { DEFAULT_OM_MODEL_ID } from "@mastra/code-sdk/constants";
5
+ import { getAvailableModePacks, resolveProviderOMDefault } from "@mastra/code-sdk/onboarding/packs";
6
+ import { THREAD_ACTIVE_MODEL_PACK_ID_KEY, getCustomProviderId } from "@mastra/code-sdk/onboarding/settings";
7
+ //#region src/routes/config.ts
8
+ /** Widen a route-local Hono context to the plain `Context` the auth helpers take. */
9
+ function loose(c) {
10
+ return c;
11
+ }
12
+ /**
13
+ * Build a deduplicated, sorted list of providers from the model catalog,
14
+ * annotated with where each provider's credential currently comes from.
15
+ * Mirrors the TUI's `/api-keys` provider list.
16
+ *
17
+ * When `tenantCredentials` is given (deployed mode), sources reflect the
18
+ * *caller's* tenant rows with user > org precedence and the server-global
19
+ * `authStorage` is ignored; otherwise the local `auth.json` view is reported.
20
+ */
21
+ async function listProviders({ controller, authStorage, tenantCredentials }) {
22
+ const models = await controller.listAvailableModels();
23
+ const seen = /* @__PURE__ */ new Map();
24
+ for (const model of models) {
25
+ if (seen.has(model.provider)) continue;
26
+ const authProviderId = getAuthProviderId(model.provider);
27
+ let source = "none";
28
+ let orgKey;
29
+ if (tenantCredentials) {
30
+ const userRec = tenantCredentials.find((r) => r.scope === "user" && r.provider === authProviderId);
31
+ const orgRec = tenantCredentials.find((r) => r.scope === "org" && r.provider === authProviderId);
32
+ orgKey = orgRec?.credential.type === "api_key";
33
+ if (userRec?.credential.type === "oauth") source = "oauth-user";
34
+ else if (userRec?.credential.type === "api_key") source = "stored-user";
35
+ else if (orgRec?.credential.type === "api_key") source = "stored-org";
36
+ } else if (authStorage?.isLoggedIn(authProviderId)) source = "oauth";
37
+ else if (authStorage?.hasStoredApiKey(model.provider)) source = "stored";
38
+ else if (model.apiKeyEnvVar && process.env[model.apiKeyEnvVar]) source = "env";
39
+ else if (model.hasApiKey) source = "env";
40
+ const flowKind = WEB_OAUTH_FLOW_KINDS[model.provider];
41
+ seen.set(model.provider, {
42
+ provider: model.provider,
43
+ envVar: model.apiKeyEnvVar,
44
+ source,
45
+ ...orgKey !== void 0 ? { orgKey } : {},
46
+ ...flowKind ? { oauth: {
47
+ supported: true,
48
+ modes: [flowKind]
49
+ } } : {}
50
+ });
51
+ }
52
+ return Array.from(seen.values()).sort((a, b) => a.provider.localeCompare(b.provider));
53
+ }
54
+ /** Redact a stored custom-provider row for the client (key presence only). */
55
+ function toCustomProviderInfo(record) {
56
+ return {
57
+ id: record.providerId,
58
+ name: record.name,
59
+ url: record.url,
60
+ hasApiKey: Boolean(record.apiKey),
61
+ models: record.models
62
+ };
63
+ }
64
+ /**
65
+ * Resolve the custom-providers context for a request, or a ready-to-return
66
+ * error response. Same posture as memory settings: tenant rows in deployed
67
+ * mode, a sentinel `local` org in no-auth mode — never settings.json.
68
+ */
69
+ async function resolveCustomProvidersContext({ c, auth, customProviders }) {
70
+ await auth.ensureUser(c);
71
+ const tenant = auth.tenant(c);
72
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
73
+ if (customProviders) try {
74
+ await customProviders.ensureReady();
75
+ return tenant ? {
76
+ storage: customProviders,
77
+ orgId: tenantOrgId(tenant),
78
+ userId: tenant.userId
79
+ } : {
80
+ storage: customProviders,
81
+ orgId: "local",
82
+ userId: "local"
83
+ };
84
+ } catch {}
85
+ return { response: c.json({
86
+ error: "custom_providers_unavailable",
87
+ message: "Custom provider storage is unavailable — the app database is not configured or failed to start."
88
+ }, 503) };
89
+ }
90
+ /** Validate + coerce a request body into a CustomProviderSetting. */
91
+ function parseCustomProviderBody(body) {
92
+ if (!body || typeof body !== "object") return { error: "Invalid JSON body" };
93
+ const b = body;
94
+ const name = typeof b.name === "string" ? b.name.trim() : "";
95
+ if (!name) return { error: "Missing required field: name" };
96
+ const url = typeof b.url === "string" ? b.url.trim() : "";
97
+ if (!url) return { error: "Missing required field: url" };
98
+ try {
99
+ const parsed = new URL(url);
100
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return { error: "url must be an http(s) URL" };
101
+ } catch {
102
+ return { error: "url must be a valid URL" };
103
+ }
104
+ return {
105
+ name,
106
+ url,
107
+ apiKey: typeof b.apiKey === "string" && b.apiKey.trim() ? b.apiKey.trim() : void 0,
108
+ models: Array.isArray(b.models) ? b.models.filter((m) => typeof m === "string" && m.trim().length > 0).map((m) => m.trim()) : []
109
+ };
110
+ }
111
+ /**
112
+ * Compute which providers the user can reach, mirroring the TUI's
113
+ * `/models-pack` access derivation: OAuth/api-key from the credential store for
114
+ * the named providers, plus any other provider that has a usable key.
115
+ */
116
+ async function buildProviderAccess({ controller, authStorage, tenantCredentials }) {
117
+ const models = await controller.listAvailableModels();
118
+ const hasModelKey = (provider) => models.some((m) => m.provider === provider && m.hasApiKey);
119
+ const accessLevel = (provider) => {
120
+ const authProviderId = getAuthProviderId(provider);
121
+ if (tenantCredentials) {
122
+ const userRec = tenantCredentials.find((r) => r.scope === "user" && r.provider === authProviderId);
123
+ const orgRec = tenantCredentials.find((r) => r.scope === "org" && r.provider === authProviderId);
124
+ const credential = userRec?.credential ?? orgRec?.credential;
125
+ if (credential?.type === "oauth") return "oauth";
126
+ if (credential?.type === "api_key" && credential.key.trim().length > 0) return "apikey";
127
+ return false;
128
+ }
129
+ if ((authStorage?.get(authProviderId))?.type === "oauth") return "oauth";
130
+ if (authStorage?.hasStoredApiKey(provider)) return "apikey";
131
+ const directCredential = authStorage?.get(provider);
132
+ if (directCredential?.type === "api_key" && directCredential.key.trim().length > 0) return "apikey";
133
+ return hasModelKey(provider) ? "apikey" : false;
134
+ };
135
+ const access = {
136
+ anthropic: accessLevel("anthropic"),
137
+ openai: accessLevel("openai"),
138
+ cerebras: accessLevel("cerebras"),
139
+ google: accessLevel("google"),
140
+ deepseek: accessLevel("deepseek"),
141
+ "github-copilot": accessLevel("github-copilot")
142
+ };
143
+ const seen = new Set(Object.keys(access));
144
+ for (const m of models) if (!seen.has(m.provider)) {
145
+ access[m.provider] = accessLevel(m.provider);
146
+ seen.add(m.provider);
147
+ }
148
+ return access;
149
+ }
150
+ function canUseModelProvider(access, provider) {
151
+ return Boolean(access[provider]);
152
+ }
153
+ /** Resolve the pack context for a request, or a ready-to-return error response. */
154
+ async function resolvePackContext({ c, auth, modelPacks }) {
155
+ await auth.ensureUser(c);
156
+ const tenant = auth.tenant(c);
157
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
158
+ if (modelPacks) try {
159
+ await modelPacks.ensureReady();
160
+ return tenant ? {
161
+ storage: modelPacks,
162
+ orgId: tenantOrgId(tenant),
163
+ userId: tenant.userId
164
+ } : {
165
+ storage: modelPacks,
166
+ orgId: "local",
167
+ userId: "local"
168
+ };
169
+ } catch {}
170
+ return { response: c.json({
171
+ error: "model_packs_unavailable",
172
+ message: "Model pack storage is unavailable — the app database is not configured or failed to start."
173
+ }, 503) };
174
+ }
175
+ /** DB row → the `ModePack` shape the packs list and activation flow consume. */
176
+ function recordToModePack(record) {
177
+ return {
178
+ id: `custom:${record.id}`,
179
+ name: record.name,
180
+ description: "Saved custom pack",
181
+ models: record.models
182
+ };
183
+ }
184
+ /**
185
+ * List available model packs (built-in, gated by provider access, plus saved
186
+ * custom packs from the request's pack context). Drops the synthetic
187
+ * "New Custom" placeholder — the web client has its own create flow. `active`
188
+ * is set from the given session's thread when a resourceId is supplied.
189
+ */
190
+ async function listModelPacks({ controller, authStorage, tenantCredentials, packContext, activePackId }) {
191
+ return [...getAvailableModePacks(await buildProviderAccess({
192
+ controller,
193
+ authStorage,
194
+ tenantCredentials
195
+ })), ...(await packContext.storage.list({ orgId: packContext.orgId })).map(recordToModePack)].filter((p) => p.id !== "custom").map((p) => ({
196
+ ...p,
197
+ custom: p.id.startsWith("custom:"),
198
+ active: activePackId != null && p.id === activePackId
199
+ }));
200
+ }
201
+ /** Resolve the active pack id for a session by reading its current thread. */
202
+ async function resolveActivePackId(session) {
203
+ if (!session) return null;
204
+ const threadId = session.thread.getId();
205
+ if (!threadId) return null;
206
+ const value = (await session.thread.list()).find((t) => t.id === threadId)?.metadata?.[THREAD_ACTIVE_MODEL_PACK_ID_KEY];
207
+ return typeof value === "string" ? value : null;
208
+ }
209
+ /**
210
+ * Apply a pack to a session: seed each mode's default model, switch the current
211
+ * mode's model, set per-subagent models, and tag the thread with the active
212
+ * pack id. Mirrors the TUI `applyPack` orchestration.
213
+ */
214
+ async function applyPackToSession({ controller, session, pack }) {
215
+ const modes = controller.listModes?.() ?? [];
216
+ const packModels = pack.models;
217
+ for (const mode of modes) {
218
+ const modelId = packModels[mode.id];
219
+ if (modelId) {
220
+ mode.defaultModelId = modelId;
221
+ await session.thread.setSetting({
222
+ key: `modeModelId_${mode.id}`,
223
+ value: modelId
224
+ });
225
+ }
226
+ }
227
+ const currentModeModel = packModels[session.mode.get()];
228
+ if (currentModeModel) await session.model.switch({ modelId: currentModeModel });
229
+ for (const [agentType, modeId] of Object.entries({
230
+ explore: "fast",
231
+ plan: "plan",
232
+ execute: "build"
233
+ })) {
234
+ const saModelId = packModels[modeId];
235
+ if (saModelId) await session.subagents.model.set({
236
+ modelId: saModelId,
237
+ agentType
238
+ });
239
+ }
240
+ await session.thread.setSetting({
241
+ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY,
242
+ value: pack.id
243
+ });
244
+ }
245
+ /** Default thresholds mirror the TUI `/om` fallbacks. */
246
+ const DEFAULT_OBSERVATION_THRESHOLD = 3e4;
247
+ const DEFAULT_REFLECTION_THRESHOLD = 4e4;
248
+ function readOMConfig(session) {
249
+ const observeAttachments = (session.state.get() ?? {}).observeAttachments;
250
+ return {
251
+ observerModelId: session.om.observer.modelId() ?? "",
252
+ reflectorModelId: session.om.reflector.modelId() ?? "",
253
+ observationThreshold: session.om.observer.threshold() ?? DEFAULT_OBSERVATION_THRESHOLD,
254
+ reflectionThreshold: session.om.reflector.threshold() ?? DEFAULT_REFLECTION_THRESHOLD,
255
+ observeAttachments: observeAttachments === true || observeAttachments === false ? observeAttachments : "auto"
256
+ };
257
+ }
258
+ function readStoredOMConfig(record) {
259
+ return {
260
+ observerModelId: record?.observerModelId ?? DEFAULT_OM_MODEL_ID,
261
+ reflectorModelId: record?.reflectorModelId ?? DEFAULT_OM_MODEL_ID,
262
+ observationThreshold: record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD,
263
+ reflectionThreshold: record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD,
264
+ observeAttachments: record?.observeAttachments ?? "auto"
265
+ };
266
+ }
267
+ /** Resolve the memory-settings context for a request, or a ready-to-return error response. */
268
+ async function resolveMemorySettingsContext({ c, auth, memorySettings }) {
269
+ await auth.ensureUser(c);
270
+ const tenant = auth.tenant(c);
271
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
272
+ if (memorySettings) try {
273
+ await memorySettings.ensureReady();
274
+ return tenant ? {
275
+ storage: memorySettings,
276
+ orgId: tenantOrgId(tenant),
277
+ userId: tenant.userId
278
+ } : {
279
+ storage: memorySettings,
280
+ orgId: "local",
281
+ userId: "local"
282
+ };
283
+ } catch {}
284
+ return { response: c.json({
285
+ error: "memory_settings_unavailable",
286
+ message: "Memory settings storage is unavailable — the app database is not configured or failed to start."
287
+ }, 503) };
288
+ }
289
+ /** Persist an OM knob change to the caller's memory-settings row. */
290
+ async function persistMemorySettings(context, patch, fillIfUnset) {
291
+ await context.storage.patch({
292
+ orgId: context.orgId,
293
+ userId: context.userId,
294
+ patch,
295
+ fillIfUnset
296
+ });
297
+ }
298
+ /**
299
+ * Apply the stored memory-settings row onto the session, so the DB — not
300
+ * whatever happens to sit in persisted session state (e.g. a stale boot-time
301
+ * seed from before memory settings moved to the DB) — is what the web surface
302
+ * reads and what the session's OM actually runs with. The row is authoritative:
303
+ * knobs without a stored value reset to the built-in defaults.
304
+ */
305
+ async function hydrateSessionMemorySettings(session, record) {
306
+ for (const role of ["observer", "reflector"]) {
307
+ const target = (role === "observer" ? record?.observerModelId : record?.reflectorModelId) ?? DEFAULT_OM_MODEL_ID;
308
+ if (session.om[role].modelId() !== target) await session.om[role].switchModel({ modelId: target });
309
+ }
310
+ const state = session.state.get() ?? {};
311
+ const updates = {};
312
+ const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;
313
+ if (state.observationThreshold !== observationThreshold) updates.observationThreshold = observationThreshold;
314
+ const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;
315
+ if (state.reflectionThreshold !== reflectionThreshold) updates.reflectionThreshold = reflectionThreshold;
316
+ const observeAttachments = record?.observeAttachments ?? "auto";
317
+ if ((state.observeAttachments ?? "auto") !== observeAttachments) updates.observeAttachments = observeAttachments;
318
+ if (Object.keys(updates).length > 0) await session.state.set(updates);
319
+ }
320
+ /**
321
+ * The web config routes as Mastra `apiRoutes`:
322
+ * - `GET /web/config/providers` — list providers + key source
323
+ * - `PUT /web/config/providers/:provider/key` — set/update a provider's API key
324
+ * - `DELETE /web/config/providers/:provider/key` — remove a stored API key
325
+ * - `GET /web/config/models` — list available models (credentialed providers)
326
+ * - `GET /web/config/custom-providers` — list custom OpenAI-compatible providers
327
+ * - `POST /web/config/custom-providers` — create/update a custom provider
328
+ * - `DELETE /web/config/custom-providers/:id` — remove a custom provider
329
+ * - `GET /web/config/om` — read OM models/thresholds/observe-attachments
330
+ * - `PUT /web/config/om/:role/model` — switch observer/reflector model
331
+ * - `PUT /web/config/om/thresholds` — set observation/reflection thresholds
332
+ * - `PUT /web/config/om/observe-attachments` — set observe-attachments (auto/on/off)
333
+ */
334
+ var ConfigRoutes = class extends Route {
335
+ routes() {
336
+ const options = this.deps;
337
+ const { controller, authStorage, auth } = options;
338
+ const onCredentialsChanged = options.onCredentialsChanged ?? (() => {});
339
+ const onCustomProvidersChanged = options.onCustomProvidersChanged ?? (() => {});
340
+ return [
341
+ registerApiRoute("/web/config/providers", {
342
+ method: "GET",
343
+ requiresAuth: false,
344
+ handler: async (c) => {
345
+ try {
346
+ const tenantCredentials = await listTenantCredentialsForRequest({
347
+ c: loose(c),
348
+ auth,
349
+ credentials: options.modelCredentials
350
+ });
351
+ const tenant = auth.tenant(loose(c));
352
+ const orgKeyAdmin = tenant ? await auth.isOrganizationAdmin(loose(c), tenantOrgId(tenant)) : void 0;
353
+ return c.json({
354
+ providers: await listProviders({
355
+ controller,
356
+ authStorage: tenantCredentials ? void 0 : authStorage,
357
+ tenantCredentials
358
+ }),
359
+ ...orgKeyAdmin !== void 0 ? { orgKeyAdmin } : {}
360
+ });
361
+ } catch (error) {
362
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
363
+ }
364
+ }
365
+ }),
366
+ registerApiRoute("/web/config/providers/:provider/key", {
367
+ method: "PUT",
368
+ requiresAuth: false,
369
+ handler: async (c) => {
370
+ const ctx = await resolveCredentialContext({
371
+ c: loose(c),
372
+ auth,
373
+ credentials: options.modelCredentials
374
+ });
375
+ if ("response" in ctx) return ctx.response;
376
+ const provider = c.req.param("provider");
377
+ let body;
378
+ try {
379
+ body = await c.req.json();
380
+ } catch {
381
+ return c.json({ error: "Invalid JSON body" }, 400);
382
+ }
383
+ const key = typeof body.key === "string" ? body.key.trim() : "";
384
+ if (!key) return c.json({ error: "Missing required field: key" }, 400);
385
+ const envVar = typeof body.envVar === "string" ? body.envVar : void 0;
386
+ const scope = body.scope === "org" ? "org" : "user";
387
+ try {
388
+ if (ctx.mode === "tenant") {
389
+ if (scope === "org" && !await auth.isOrganizationAdmin(loose(c), ctx.orgId)) return c.json({ error: "organization_admin_required" }, 403);
390
+ const tenant = scope === "org" ? { orgId: ctx.orgId } : {
391
+ orgId: ctx.orgId,
392
+ userId: ctx.userId
393
+ };
394
+ await ctx.storage.setCredential(tenant, getAuthProviderId(provider), {
395
+ type: "api_key",
396
+ key
397
+ });
398
+ onCredentialsChanged(tenant);
399
+ const records = await ctx.storage.listCredentials(ctx.orgId, ctx.userId);
400
+ const providers = await listProviders({
401
+ controller,
402
+ tenantCredentials: records
403
+ });
404
+ return c.json({
405
+ ok: true,
406
+ provider: providers.find((p) => p.provider === provider)
407
+ });
408
+ }
409
+ if (!authStorage) return c.json({ error: "Credential storage is not available" }, 503);
410
+ authStorage.setStoredApiKey(provider, key, envVar);
411
+ const providers = await listProviders({
412
+ controller,
413
+ authStorage
414
+ });
415
+ return c.json({
416
+ ok: true,
417
+ provider: providers.find((p) => p.provider === provider)
418
+ });
419
+ } catch (error) {
420
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
421
+ }
422
+ }
423
+ }),
424
+ registerApiRoute("/web/config/providers/:provider/key", {
425
+ method: "DELETE",
426
+ requiresAuth: false,
427
+ handler: async (c) => {
428
+ const ctx = await resolveCredentialContext({
429
+ c: loose(c),
430
+ auth,
431
+ credentials: options.modelCredentials
432
+ });
433
+ if ("response" in ctx) return ctx.response;
434
+ const provider = c.req.param("provider");
435
+ const scope = c.req.query("scope") === "org" ? "org" : "user";
436
+ try {
437
+ if (ctx.mode === "tenant") {
438
+ if (scope === "org" && !await auth.isOrganizationAdmin(loose(c), ctx.orgId)) return c.json({ error: "organization_admin_required" }, 403);
439
+ const tenant = scope === "org" ? { orgId: ctx.orgId } : {
440
+ orgId: ctx.orgId,
441
+ userId: ctx.userId
442
+ };
443
+ await ctx.storage.removeCredential(tenant, getAuthProviderId(provider));
444
+ onCredentialsChanged(tenant);
445
+ const records = await ctx.storage.listCredentials(ctx.orgId, ctx.userId);
446
+ const providers = await listProviders({
447
+ controller,
448
+ tenantCredentials: records
449
+ });
450
+ return c.json({
451
+ ok: true,
452
+ provider: providers.find((p) => p.provider === provider)
453
+ });
454
+ }
455
+ if (!authStorage) return c.json({ error: "Credential storage is not available" }, 503);
456
+ authStorage.remove(`apikey:${provider}`);
457
+ const providers = await listProviders({
458
+ controller,
459
+ authStorage
460
+ });
461
+ return c.json({
462
+ ok: true,
463
+ provider: providers.find((p) => p.provider === provider)
464
+ });
465
+ } catch (error) {
466
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
467
+ }
468
+ }
469
+ }),
470
+ registerApiRoute("/web/config/custom-providers", {
471
+ method: "GET",
472
+ requiresAuth: false,
473
+ handler: async (c) => {
474
+ const ctx = await resolveCustomProvidersContext({
475
+ c: loose(c),
476
+ auth,
477
+ customProviders: options.customProviders
478
+ });
479
+ if ("response" in ctx) return ctx.response;
480
+ try {
481
+ const records = await ctx.storage.list({ orgId: ctx.orgId });
482
+ return c.json({ providers: records.map(toCustomProviderInfo) });
483
+ } catch (error) {
484
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
485
+ }
486
+ }
487
+ }),
488
+ registerApiRoute("/web/config/custom-providers", {
489
+ method: "POST",
490
+ requiresAuth: false,
491
+ handler: async (c) => {
492
+ const ctx = await resolveCustomProvidersContext({
493
+ c: loose(c),
494
+ auth,
495
+ customProviders: options.customProviders
496
+ });
497
+ if ("response" in ctx) return ctx.response;
498
+ let body;
499
+ try {
500
+ body = await c.req.json();
501
+ } catch {
502
+ return c.json({ error: "Invalid JSON body" }, 400);
503
+ }
504
+ const parsed = parseCustomProviderBody(body);
505
+ if ("error" in parsed) return c.json({ error: parsed.error }, 400);
506
+ const previousId = body && typeof body === "object" && typeof body.previousId === "string" ? body.previousId : void 0;
507
+ try {
508
+ const record = await ctx.storage.upsert({
509
+ orgId: ctx.orgId,
510
+ userId: ctx.userId,
511
+ input: {
512
+ providerId: getCustomProviderId(parsed.name),
513
+ name: parsed.name,
514
+ url: parsed.url,
515
+ apiKey: parsed.apiKey,
516
+ models: parsed.models
517
+ },
518
+ previousProviderId: previousId
519
+ });
520
+ onCustomProvidersChanged({ orgId: ctx.orgId });
521
+ return c.json({
522
+ ok: true,
523
+ provider: toCustomProviderInfo(record)
524
+ });
525
+ } catch (error) {
526
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
527
+ }
528
+ }
529
+ }),
530
+ registerApiRoute("/web/config/custom-providers/:id", {
531
+ method: "DELETE",
532
+ requiresAuth: false,
533
+ handler: async (c) => {
534
+ const ctx = await resolveCustomProvidersContext({
535
+ c: loose(c),
536
+ auth,
537
+ customProviders: options.customProviders
538
+ });
539
+ if ("response" in ctx) return ctx.response;
540
+ const id = c.req.param("id");
541
+ try {
542
+ await ctx.storage.delete({
543
+ orgId: ctx.orgId,
544
+ providerId: id
545
+ });
546
+ onCustomProvidersChanged({ orgId: ctx.orgId });
547
+ return c.json({ ok: true });
548
+ } catch (error) {
549
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
550
+ }
551
+ }
552
+ }),
553
+ registerApiRoute("/web/config/models", {
554
+ method: "GET",
555
+ requiresAuth: false,
556
+ handler: async (c) => {
557
+ try {
558
+ const tenantCredentials = await listTenantCredentialsForRequest({
559
+ c: loose(c),
560
+ auth,
561
+ credentials: options.modelCredentials
562
+ });
563
+ const [models, access] = await Promise.all([controller.listAvailableModels(), buildProviderAccess({
564
+ controller,
565
+ authStorage: tenantCredentials ? void 0 : authStorage,
566
+ tenantCredentials
567
+ })]);
568
+ const catalog = models.filter((m) => canUseModelProvider(access, m.provider) && typeof m.id === "string").map((m) => ({
569
+ id: m.id,
570
+ provider: m.provider,
571
+ modelName: m.modelName,
572
+ hasApiKey: true
573
+ }));
574
+ if (options.customProviders) try {
575
+ const ctx = await resolveCustomProvidersContext({
576
+ c: loose(c),
577
+ auth,
578
+ customProviders: options.customProviders
579
+ });
580
+ if (!("response" in ctx)) {
581
+ const known = new Set(catalog.map((m) => m.id));
582
+ for (const record of await ctx.storage.list({ orgId: ctx.orgId })) for (const model of record.models) {
583
+ const id = `${record.providerId}/${model}`;
584
+ if (known.has(id)) continue;
585
+ known.add(id);
586
+ catalog.push({
587
+ id,
588
+ provider: record.providerId,
589
+ modelName: model,
590
+ hasApiKey: true
591
+ });
592
+ }
593
+ }
594
+ } catch {}
595
+ return c.json({ models: catalog.sort((a, b) => a.provider === b.provider ? a.id.localeCompare(b.id) : a.provider.localeCompare(b.provider)) });
596
+ } catch (error) {
597
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
598
+ }
599
+ }
600
+ }),
601
+ registerApiRoute("/web/config/model-packs", {
602
+ method: "GET",
603
+ requiresAuth: false,
604
+ handler: async (c) => {
605
+ const packContext = await resolvePackContext({
606
+ c: loose(c),
607
+ auth,
608
+ modelPacks: options.modelPacks
609
+ });
610
+ if ("response" in packContext) return packContext.response;
611
+ const resourceId = c.req.query("resourceId");
612
+ const scope = c.req.query("scope") || void 0;
613
+ try {
614
+ const activePackId = await resolveActivePackId(resourceId ? await controller.getSessionByResource?.(resourceId, scope) : void 0);
615
+ const tenantCredentials = await listTenantCredentialsForRequest({
616
+ c: loose(c),
617
+ auth,
618
+ credentials: options.modelCredentials
619
+ });
620
+ return c.json({
621
+ packs: await listModelPacks({
622
+ controller,
623
+ authStorage: tenantCredentials ? void 0 : authStorage,
624
+ tenantCredentials,
625
+ packContext,
626
+ activePackId
627
+ }),
628
+ activePackId
629
+ });
630
+ } catch (error) {
631
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
632
+ }
633
+ }
634
+ }),
635
+ registerApiRoute("/web/config/model-packs", {
636
+ method: "POST",
637
+ requiresAuth: false,
638
+ handler: async (c) => {
639
+ const packContext = await resolvePackContext({
640
+ c: loose(c),
641
+ auth,
642
+ modelPacks: options.modelPacks
643
+ });
644
+ if ("response" in packContext) return packContext.response;
645
+ let body;
646
+ try {
647
+ body = await c.req.json();
648
+ } catch {
649
+ return c.json({ error: "Invalid JSON body" }, 400);
650
+ }
651
+ const name = typeof body.name === "string" ? body.name.trim() : "";
652
+ if (!name) return c.json({ error: "Missing required field: name" }, 400);
653
+ const m = body.models ?? {};
654
+ const build = typeof m.build === "string" ? m.build.trim() : "";
655
+ const plan = typeof m.plan === "string" ? m.plan.trim() : "";
656
+ const fast = typeof m.fast === "string" ? m.fast.trim() : "";
657
+ if (!build || !plan || !fast) return c.json({ error: "models.build, models.plan and models.fast are required" }, 400);
658
+ try {
659
+ const record = await packContext.storage.upsert({
660
+ orgId: packContext.orgId,
661
+ userId: packContext.userId,
662
+ input: {
663
+ name,
664
+ models: {
665
+ build,
666
+ plan,
667
+ fast
668
+ }
669
+ }
670
+ });
671
+ return c.json({
672
+ ok: true,
673
+ pack: recordToModePack(record)
674
+ });
675
+ } catch (error) {
676
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
677
+ }
678
+ }
679
+ }),
680
+ registerApiRoute("/web/config/model-packs/:id", {
681
+ method: "DELETE",
682
+ requiresAuth: false,
683
+ handler: async (c) => {
684
+ const packContext = await resolvePackContext({
685
+ c: loose(c),
686
+ auth,
687
+ modelPacks: options.modelPacks
688
+ });
689
+ if ("response" in packContext) return packContext.response;
690
+ const id = decodeURIComponent(c.req.param("id"));
691
+ try {
692
+ const recordId = id.startsWith("custom:") ? id.slice(7) : id;
693
+ return await packContext.storage.delete({
694
+ orgId: packContext.orgId,
695
+ id: recordId
696
+ }) ? c.json({ ok: true }) : c.json({ error: `Unknown pack "${id}"` }, 404);
697
+ } catch (error) {
698
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
699
+ }
700
+ }
701
+ }),
702
+ registerApiRoute("/web/config/model-packs/:id/activate", {
703
+ method: "POST",
704
+ requiresAuth: false,
705
+ handler: async (c) => {
706
+ const packContext = await resolvePackContext({
707
+ c: loose(c),
708
+ auth,
709
+ modelPacks: options.modelPacks
710
+ });
711
+ if ("response" in packContext) return packContext.response;
712
+ const id = decodeURIComponent(c.req.param("id"));
713
+ let body;
714
+ try {
715
+ body = await c.req.json();
716
+ } catch {
717
+ return c.json({ error: "Invalid JSON body" }, 400);
718
+ }
719
+ const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
720
+ const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
721
+ if (!resourceId) return c.json({ error: "Missing required field: resourceId" }, 400);
722
+ try {
723
+ const session = await controller.getSessionByResource?.(resourceId, scope);
724
+ if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
725
+ const tenantCredentials = await listTenantCredentialsForRequest({
726
+ c: loose(c),
727
+ auth,
728
+ credentials: options.modelCredentials
729
+ });
730
+ const pack = (await listModelPacks({
731
+ controller,
732
+ authStorage: tenantCredentials ? void 0 : authStorage,
733
+ tenantCredentials,
734
+ packContext
735
+ })).find((p) => p.id === id);
736
+ if (!pack) return c.json({ error: `Unknown pack "${id}"` }, 404);
737
+ await applyPackToSession({
738
+ controller,
739
+ session,
740
+ pack
741
+ });
742
+ return c.json({
743
+ ok: true,
744
+ activePackId: pack.id
745
+ });
746
+ } catch (error) {
747
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
748
+ }
749
+ }
750
+ }),
751
+ registerApiRoute("/web/config/om/provider-defaults", {
752
+ method: "POST",
753
+ requiresAuth: false,
754
+ handler: async (c) => {
755
+ let body;
756
+ try {
757
+ body = await c.req.json();
758
+ } catch {
759
+ return c.json({ error: "Invalid JSON body" }, 400);
760
+ }
761
+ const providerId = typeof body.providerId === "string" ? body.providerId.trim() : "";
762
+ const factoryModelId = typeof body.factoryModelId === "string" ? body.factoryModelId.trim() : "";
763
+ if (!providerId) return c.json({ error: "Missing required field: providerId" }, 400);
764
+ const context = await resolveMemorySettingsContext({
765
+ c: loose(c),
766
+ auth,
767
+ memorySettings: options.memorySettings
768
+ });
769
+ if ("response" in context) return context.response;
770
+ try {
771
+ const tenantCredentials = await listTenantCredentialsForRequest({
772
+ c: loose(c),
773
+ auth,
774
+ credentials: options.modelCredentials
775
+ });
776
+ if (!(await buildProviderAccess({
777
+ controller,
778
+ authStorage: tenantCredentials ? void 0 : authStorage,
779
+ tenantCredentials
780
+ }))[providerId]) return c.json({ error: `Provider "${providerId}" is not configured` }, 400);
781
+ const modelId = resolveProviderOMDefault(providerId, factoryModelId).modelId;
782
+ const record = await context.storage.patch({
783
+ orgId: context.orgId,
784
+ userId: context.userId,
785
+ patch: {},
786
+ fillIfUnset: {
787
+ observerModelId: modelId,
788
+ reflectorModelId: modelId
789
+ }
790
+ });
791
+ return c.json({
792
+ ok: true,
793
+ config: readStoredOMConfig(record)
794
+ });
795
+ } catch (error) {
796
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
797
+ }
798
+ }
799
+ }),
800
+ registerApiRoute("/web/config/om", {
801
+ method: "GET",
802
+ requiresAuth: false,
803
+ handler: async (c) => {
804
+ const resourceId = c.req.query("resourceId");
805
+ const scope = c.req.query("scope") || void 0;
806
+ const context = await resolveMemorySettingsContext({
807
+ c: loose(c),
808
+ auth,
809
+ memorySettings: options.memorySettings
810
+ });
811
+ if ("response" in context) return context.response;
812
+ try {
813
+ const record = await context.storage.get({
814
+ orgId: context.orgId,
815
+ userId: context.userId
816
+ });
817
+ if (!resourceId) return c.json({ config: readStoredOMConfig(record) });
818
+ const session = await controller.getSessionByResource?.(resourceId, scope);
819
+ if (!session) return c.json({ config: readStoredOMConfig(record) });
820
+ await hydrateSessionMemorySettings(session, record);
821
+ return c.json({ config: readOMConfig(session) });
822
+ } catch (error) {
823
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
824
+ }
825
+ }
826
+ }),
827
+ registerApiRoute("/web/config/om/:role/model", {
828
+ method: "PUT",
829
+ requiresAuth: false,
830
+ handler: async (c) => {
831
+ const role = c.req.param("role");
832
+ if (role !== "observer" && role !== "reflector") return c.json({ error: `Unknown OM role "${role}"` }, 400);
833
+ let body;
834
+ try {
835
+ body = await c.req.json();
836
+ } catch {
837
+ return c.json({ error: "Invalid JSON body" }, 400);
838
+ }
839
+ const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
840
+ const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
841
+ const modelId = typeof body.modelId === "string" ? body.modelId.trim() : "";
842
+ if (!modelId) return c.json({ error: "Missing required field: modelId" }, 400);
843
+ const context = await resolveMemorySettingsContext({
844
+ c: loose(c),
845
+ auth,
846
+ memorySettings: options.memorySettings
847
+ });
848
+ if ("response" in context) return context.response;
849
+ try {
850
+ const session = resourceId ? await controller.getSessionByResource?.(resourceId, scope) : void 0;
851
+ const otherRoleCurrentModelId = (session ? role === "observer" ? session.om.reflector : session.om.observer : void 0)?.modelId() ?? null;
852
+ await session?.om[role].switchModel({ modelId });
853
+ const otherKey = role === "observer" ? "reflectorModelId" : "observerModelId";
854
+ await persistMemorySettings(context, { [role === "observer" ? "observerModelId" : "reflectorModelId"]: modelId }, otherRoleCurrentModelId ? { [otherKey]: otherRoleCurrentModelId } : void 0);
855
+ const config = session ? readOMConfig(session) : readStoredOMConfig(await context.storage.get({
856
+ orgId: context.orgId,
857
+ userId: context.userId
858
+ }));
859
+ return c.json({
860
+ ok: true,
861
+ config
862
+ });
863
+ } catch (error) {
864
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
865
+ }
866
+ }
867
+ }),
868
+ registerApiRoute("/web/config/om/thresholds", {
869
+ method: "PUT",
870
+ requiresAuth: false,
871
+ handler: async (c) => {
872
+ let body;
873
+ try {
874
+ body = await c.req.json();
875
+ } catch {
876
+ return c.json({ error: "Invalid JSON body" }, 400);
877
+ }
878
+ const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
879
+ const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
880
+ const observation = typeof body.observationThreshold === "number" && body.observationThreshold > 0 ? Math.round(body.observationThreshold) : void 0;
881
+ const reflection = typeof body.reflectionThreshold === "number" && body.reflectionThreshold > 0 ? Math.round(body.reflectionThreshold) : void 0;
882
+ if (observation === void 0 && reflection === void 0) return c.json({ error: "Provide observationThreshold and/or reflectionThreshold (positive numbers)" }, 400);
883
+ const context = await resolveMemorySettingsContext({
884
+ c: loose(c),
885
+ auth,
886
+ memorySettings: options.memorySettings
887
+ });
888
+ if ("response" in context) return context.response;
889
+ try {
890
+ const session = resourceId ? await controller.getSessionByResource?.(resourceId, scope) : void 0;
891
+ if (observation !== void 0 && session) {
892
+ await session.state.set({ observationThreshold: observation });
893
+ await session.thread.setSetting({
894
+ key: "observationThreshold",
895
+ value: observation
896
+ });
897
+ }
898
+ if (reflection !== void 0 && session) {
899
+ await session.state.set({ reflectionThreshold: reflection });
900
+ await session.thread.setSetting({
901
+ key: "reflectionThreshold",
902
+ value: reflection
903
+ });
904
+ }
905
+ await persistMemorySettings(context, {
906
+ ...observation !== void 0 ? { observationThreshold: observation } : {},
907
+ ...reflection !== void 0 ? { reflectionThreshold: reflection } : {}
908
+ });
909
+ const config = session ? readOMConfig(session) : readStoredOMConfig(await context.storage.get({
910
+ orgId: context.orgId,
911
+ userId: context.userId
912
+ }));
913
+ return c.json({
914
+ ok: true,
915
+ config
916
+ });
917
+ } catch (error) {
918
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
919
+ }
920
+ }
921
+ }),
922
+ registerApiRoute("/web/config/om/observe-attachments", {
923
+ method: "PUT",
924
+ requiresAuth: false,
925
+ handler: async (c) => {
926
+ let body;
927
+ try {
928
+ body = await c.req.json();
929
+ } catch {
930
+ return c.json({ error: "Invalid JSON body" }, 400);
931
+ }
932
+ const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
933
+ const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
934
+ const raw = body.value;
935
+ const value = raw === "auto" || raw === true || raw === false ? raw : "auto";
936
+ if (raw !== "auto" && raw !== true && raw !== false) return c.json({ error: "value must be 'auto', true, or false" }, 400);
937
+ const context = await resolveMemorySettingsContext({
938
+ c: loose(c),
939
+ auth,
940
+ memorySettings: options.memorySettings
941
+ });
942
+ if ("response" in context) return context.response;
943
+ try {
944
+ const session = resourceId ? await controller.getSessionByResource?.(resourceId, scope) : void 0;
945
+ if (session) {
946
+ await session.state.set({ observeAttachments: value });
947
+ await session.thread.setSetting({
948
+ key: "observeAttachments",
949
+ value
950
+ });
951
+ }
952
+ await persistMemorySettings(context, { observeAttachments: value });
953
+ const config = session ? readOMConfig(session) : readStoredOMConfig(await context.storage.get({
954
+ orgId: context.orgId,
955
+ userId: context.userId
956
+ }));
957
+ return c.json({
958
+ ok: true,
959
+ config
960
+ });
961
+ } catch (error) {
962
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
963
+ }
964
+ }
965
+ })
966
+ ];
967
+ }
968
+ };
969
+ //#endregion
970
+ export { ConfigRoutes, buildProviderAccess, listModelPacks, listProviders, readOMConfig };
971
+
972
+ //# sourceMappingURL=config.js.map