@mastra/factory 0.2.2-alpha.1 → 0.2.2-alpha.2

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 (155) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/auth.js +401 -334
  3. package/dist/auth.js.map +1 -1
  4. package/dist/capabilities/connection.js +1 -1
  5. package/dist/capabilities/intake.js +1 -1
  6. package/dist/capabilities/version-control.js +1 -1
  7. package/dist/factory.js +441 -39622
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.js +2 -39633
  10. package/dist/integrations/base.js +1 -1
  11. package/dist/integrations/github/config.js +36 -22
  12. package/dist/integrations/github/config.js.map +1 -1
  13. package/dist/integrations/github/factory-session.js +33 -37
  14. package/dist/integrations/github/factory-session.js.map +1 -1
  15. package/dist/integrations/github/integration.js +1037 -3672
  16. package/dist/integrations/github/integration.js.map +1 -1
  17. package/dist/integrations/github/issue-triage.js +71 -71
  18. package/dist/integrations/github/issue-triage.js.map +1 -1
  19. package/dist/integrations/github/pat.js +47 -36
  20. package/dist/integrations/github/pat.js.map +1 -1
  21. package/dist/integrations/github/provenance.js +72 -101
  22. package/dist/integrations/github/provenance.js.map +1 -1
  23. package/dist/integrations/github/routes.js +1272 -1881
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/rules.js +203 -471
  26. package/dist/integrations/github/rules.js.map +1 -1
  27. package/dist/integrations/github/sandbox.js +400 -345
  28. package/dist/integrations/github/sandbox.js.map +1 -1
  29. package/dist/integrations/github/session-subscriptions.js +165 -223
  30. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  31. package/dist/integrations/github/subscriptions.js +43 -51
  32. package/dist/integrations/github/subscriptions.js.map +1 -1
  33. package/dist/integrations/github/token-refresh.js +15 -18
  34. package/dist/integrations/github/token-refresh.js.map +1 -1
  35. package/dist/integrations/github/webhook.js +359 -343
  36. package/dist/integrations/github/webhook.js.map +1 -1
  37. package/dist/integrations/linear/agent-tools.js +77 -100
  38. package/dist/integrations/linear/agent-tools.js.map +1 -1
  39. package/dist/integrations/linear/integration.js +611 -1309
  40. package/dist/integrations/linear/integration.js.map +1 -1
  41. package/dist/integrations/linear/routes.js +227 -230
  42. package/dist/integrations/linear/routes.js.map +1 -1
  43. package/dist/integrations/linear/rules.js +126 -346
  44. package/dist/integrations/linear/rules.js.map +1 -1
  45. package/dist/integrations/linear/storage.js +1 -1
  46. package/dist/integrations/platform/api-client.js +131 -135
  47. package/dist/integrations/platform/api-client.js.map +1 -1
  48. package/dist/integrations/platform/github/event-worker.js +275 -573
  49. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  50. package/dist/integrations/platform/github/integration.js +845 -4022
  51. package/dist/integrations/platform/github/integration.js.map +1 -1
  52. package/dist/integrations/platform/linear/integration.js +457 -1309
  53. package/dist/integrations/platform/linear/integration.js.map +1 -1
  54. package/dist/integrations/workos/integration.js +80 -85
  55. package/dist/integrations/workos/integration.js.map +1 -1
  56. package/dist/routes/config.d.ts.map +1 -1
  57. package/dist/routes/config.js +933 -912
  58. package/dist/routes/config.js.map +1 -1
  59. package/dist/routes/custom-provider-source.js +110 -93
  60. package/dist/routes/custom-provider-source.js.map +1 -1
  61. package/dist/routes/fs.js +401 -349
  62. package/dist/routes/fs.js.map +1 -1
  63. package/dist/routes/intake.js +165 -165
  64. package/dist/routes/intake.js.map +1 -1
  65. package/dist/routes/oauth.js +447 -25166
  66. package/dist/routes/oauth.js.map +1 -1
  67. package/dist/routes/projects.js +437 -376
  68. package/dist/routes/projects.js.map +1 -1
  69. package/dist/routes/provider-credentials.js +76 -58
  70. package/dist/routes/provider-credentials.js.map +1 -1
  71. package/dist/routes/route.js +12 -8
  72. package/dist/routes/route.js.map +1 -1
  73. package/dist/routes/skills.js +160 -173
  74. package/dist/routes/skills.js.map +1 -1
  75. package/dist/routes/surface.js +250 -28615
  76. package/dist/routes/surface.js.map +1 -1
  77. package/dist/routes/tenant-credentials.js +141 -166
  78. package/dist/routes/tenant-credentials.js.map +1 -1
  79. package/dist/routes/work-items.js +654 -862
  80. package/dist/routes/work-items.js.map +1 -1
  81. package/dist/rules/binding-context.js +23 -35
  82. package/dist/rules/binding-context.js.map +1 -1
  83. package/dist/rules/defaults.js +200 -309
  84. package/dist/rules/defaults.js.map +1 -1
  85. package/dist/rules/dispatcher.js +438 -683
  86. package/dist/rules/dispatcher.js.map +1 -1
  87. package/dist/rules/index.js +5 -599
  88. package/dist/rules/processor.js +344 -596
  89. package/dist/rules/processor.js.map +1 -1
  90. package/dist/rules/resolve.js +21 -18
  91. package/dist/rules/resolve.js.map +1 -1
  92. package/dist/rules/start-coordinator.js +168 -157
  93. package/dist/rules/start-coordinator.js.map +1 -1
  94. package/dist/rules/tools.js +50 -85
  95. package/dist/rules/tools.js.map +1 -1
  96. package/dist/rules/transition-service.js +183 -454
  97. package/dist/rules/transition-service.js.map +1 -1
  98. package/dist/rules/types.js +33 -29
  99. package/dist/rules/types.js.map +1 -1
  100. package/dist/rules/validation.js +266 -281
  101. package/dist/rules/validation.js.map +1 -1
  102. package/dist/sandbox/fleet.js +272 -250
  103. package/dist/sandbox/fleet.js.map +1 -1
  104. package/dist/sandbox/reattach.js +13 -6
  105. package/dist/sandbox/reattach.js.map +1 -1
  106. package/dist/server-error.js +12 -16
  107. package/dist/server-error.js.map +1 -1
  108. package/dist/skills/service.js +30 -35
  109. package/dist/skills/service.js.map +1 -1
  110. package/dist/spa-static.js +91 -65
  111. package/dist/spa-static.js.map +1 -1
  112. package/dist/state-signing.js +73 -44
  113. package/dist/state-signing.js.map +1 -1
  114. package/dist/storage/domains/audit/agent-audit.js +60 -59
  115. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  116. package/dist/storage/domains/audit/base.js +169 -112
  117. package/dist/storage/domains/audit/base.js.map +1 -1
  118. package/dist/storage/domains/audit/domain.js +155 -153
  119. package/dist/storage/domains/audit/domain.js.map +1 -1
  120. package/dist/storage/domains/credentials/base.js +259 -243
  121. package/dist/storage/domains/credentials/base.js.map +1 -1
  122. package/dist/storage/domains/custom-providers/base.js +138 -143
  123. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  124. package/dist/storage/domains/intake/base.js +75 -57
  125. package/dist/storage/domains/intake/base.js.map +1 -1
  126. package/dist/storage/domains/integrations/base.js +310 -245
  127. package/dist/storage/domains/integrations/base.js.map +1 -1
  128. package/dist/storage/domains/memory-settings/base.js +117 -102
  129. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  130. package/dist/storage/domains/model-packs/base.js +89 -88
  131. package/dist/storage/domains/model-packs/base.js.map +1 -1
  132. package/dist/storage/domains/projects/base.js +97 -92
  133. package/dist/storage/domains/projects/base.js.map +1 -1
  134. package/dist/storage/domains/queue-health/base.js +111 -76
  135. package/dist/storage/domains/queue-health/base.js.map +1 -1
  136. package/dist/storage/domains/source-control/base.js +777 -706
  137. package/dist/storage/domains/source-control/base.js.map +1 -1
  138. package/dist/storage/domains/source-control/inmemory.js +322 -348
  139. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  140. package/dist/storage/domains/work-items/base.js +1266 -1148
  141. package/dist/storage/domains/work-items/base.js.map +1 -1
  142. package/dist/storage/domains/work-items/metrics.js +202 -184
  143. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  144. package/dist/timing.js +29 -22
  145. package/dist/timing.js.map +1 -1
  146. package/dist/workspace.js +236 -477
  147. package/dist/workspace.js.map +1 -1
  148. package/package.json +8 -8
  149. package/dist/capabilities/connection.js.map +0 -1
  150. package/dist/capabilities/intake.js.map +0 -1
  151. package/dist/capabilities/version-control.js.map +0 -1
  152. package/dist/index.js.map +0 -1
  153. package/dist/integrations/base.js.map +0 -1
  154. package/dist/integrations/linear/storage.js.map +0 -1
  155. package/dist/rules/index.js.map +0 -1
@@ -1,107 +1,124 @@
1
- // src/routes/custom-provider-source.ts
1
+ import { tenantOrgId } from "./provider-credentials.js";
2
2
  import { setCustomProvidersSource } from "@mastra/code-sdk/agents/custom-provider-source";
3
-
4
- // src/routes/provider-credentials.ts
5
- function tenantOrgId(tenant) {
6
- return tenant.orgId ?? `user:${tenant.userId}`;
7
- }
8
-
9
- // src/routes/custom-provider-source.ts
10
- var SNAPSHOT_TTL_MS = 15e3;
11
- var MAX_CACHED_ORGS = 1e3;
12
- var LOCAL_ORG = "local";
3
+ //#region src/routes/custom-provider-source.ts
4
+ /**
5
+ * DB-backed custom providers source for model resolution (factory mode).
6
+ *
7
+ * The SDK's `resolveModel` and gateway catalog ask the registered
8
+ * `CustomProvidersSource` synchronously, so this module keeps a small per-org
9
+ * **snapshot** of custom provider rows hydrated from the `custom-providers`
10
+ * domain. Once registered the source is authoritative — the factory never
11
+ * reads `settings.json` custom providers, in either auth mode:
12
+ *
13
+ * - tenant mode: rows are scoped per org (`tenantOrgId`); calls without an
14
+ * authenticated tenant (boot-time catalog) resolve to an empty list.
15
+ * - no-auth/local mode: rows live under the sentinel `local` org, matching the
16
+ * custom provider routes.
17
+ *
18
+ * Snapshots are primed per request by `createCustomProvidersPrimer` (mounted
19
+ * after the web auth gate) and invalidated on writes via
20
+ * `invalidateCustomProvidersSnapshots` so changes are visible immediately.
21
+ */
22
+ /** How long a hydrated snapshot is considered fresh. */
23
+ const SNAPSHOT_TTL_MS = 15e3;
24
+ /** Cap on cached org snapshots; oldest-inserted evicted beyond this. */
25
+ const MAX_CACHED_ORGS = 1e3;
26
+ /** Sentinel org for no-auth mode — must match the custom provider routes. */
27
+ const LOCAL_ORG = "local";
13
28
  var OrgCustomProvidersSnapshot = class {
14
- #orgId;
15
- #storage;
16
- #snapshot = [];
17
- #fetchedAt = 0;
18
- #hydrating;
19
- constructor(orgId, storage) {
20
- this.#orgId = orgId;
21
- this.#storage = storage;
22
- }
23
- /** Hydrate the snapshot when stale; coalesces concurrent callers. */
24
- async ensureFresh(now = Date.now()) {
25
- if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;
26
- this.#hydrating ??= this.#hydrate().finally(() => {
27
- this.#hydrating = void 0;
28
- });
29
- await this.#hydrating;
30
- }
31
- async #hydrate() {
32
- await this.#storage.ensureReady();
33
- const records = await this.#storage.list({ orgId: this.#orgId });
34
- this.#snapshot = records.map((record) => ({
35
- name: record.name,
36
- url: record.url,
37
- apiKey: record.apiKey ?? void 0,
38
- models: record.models
39
- }));
40
- this.#fetchedAt = Date.now();
41
- }
42
- /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
43
- get() {
44
- if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {
45
- void this.ensureFresh().catch(() => {
46
- });
47
- }
48
- return this.#snapshot;
49
- }
29
+ #orgId;
30
+ #storage;
31
+ #snapshot = [];
32
+ #fetchedAt = 0;
33
+ #hydrating;
34
+ constructor(orgId, storage) {
35
+ this.#orgId = orgId;
36
+ this.#storage = storage;
37
+ }
38
+ /** Hydrate the snapshot when stale; coalesces concurrent callers. */
39
+ async ensureFresh(now = Date.now()) {
40
+ if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;
41
+ this.#hydrating ??= this.#hydrate().finally(() => {
42
+ this.#hydrating = void 0;
43
+ });
44
+ await this.#hydrating;
45
+ }
46
+ async #hydrate() {
47
+ await this.#storage.ensureReady();
48
+ const records = await this.#storage.list({ orgId: this.#orgId });
49
+ this.#snapshot = records.map((record) => ({
50
+ name: record.name,
51
+ url: record.url,
52
+ apiKey: record.apiKey ?? void 0,
53
+ models: record.models
54
+ }));
55
+ this.#fetchedAt = Date.now();
56
+ }
57
+ /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
58
+ get() {
59
+ if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) this.ensureFresh().catch(() => {});
60
+ return this.#snapshot;
61
+ }
50
62
  };
51
- var orgSnapshots = /* @__PURE__ */ new Map();
63
+ const orgSnapshots = /* @__PURE__ */ new Map();
52
64
  function snapshotFor(orgId, storage) {
53
- let snapshot = orgSnapshots.get(orgId);
54
- if (!snapshot) {
55
- if (orgSnapshots.size >= MAX_CACHED_ORGS) {
56
- const oldest = orgSnapshots.keys().next().value;
57
- if (oldest !== void 0) orgSnapshots.delete(oldest);
58
- }
59
- snapshot = new OrgCustomProvidersSnapshot(orgId, storage);
60
- orgSnapshots.set(orgId, snapshot);
61
- }
62
- return snapshot;
65
+ let snapshot = orgSnapshots.get(orgId);
66
+ if (!snapshot) {
67
+ if (orgSnapshots.size >= MAX_CACHED_ORGS) {
68
+ const oldest = orgSnapshots.keys().next().value;
69
+ if (oldest !== void 0) orgSnapshots.delete(oldest);
70
+ }
71
+ snapshot = new OrgCustomProvidersSnapshot(orgId, storage);
72
+ orgSnapshots.set(orgId, snapshot);
73
+ }
74
+ return snapshot;
63
75
  }
76
+ /** The org a call resolves to, or `undefined` to fail closed (tenant mode without identity). */
64
77
  function orgForTenant(tenant, authEnabled) {
65
- if (!authEnabled) return LOCAL_ORG;
66
- return tenant ? tenantOrgId(tenant) : void 0;
78
+ if (!authEnabled) return LOCAL_ORG;
79
+ return tenant ? tenantOrgId(tenant) : void 0;
67
80
  }
68
- function registerCustomProvidersSource({
69
- storage,
70
- authEnabled
71
- }) {
72
- setCustomProvidersSource((tenant) => {
73
- const orgId = orgForTenant(tenant, authEnabled);
74
- if (!orgId) return [];
75
- return snapshotFor(orgId, storage).get();
76
- });
81
+ /**
82
+ * Register the DB-backed custom providers source with the SDK. Called by the
83
+ * factory after storage init with the `custom-providers` domain handle; from
84
+ * then on model resolution and the gateway catalog never read settings.json
85
+ * custom providers.
86
+ */
87
+ function registerCustomProvidersSource({ storage, authEnabled }) {
88
+ setCustomProvidersSource((tenant) => {
89
+ const orgId = orgForTenant(tenant, authEnabled);
90
+ if (!orgId) return [];
91
+ return snapshotFor(orgId, storage).get();
92
+ });
77
93
  }
94
+ /**
95
+ * Drop the org's cached snapshot after a custom provider write so the change
96
+ * is visible to the next model call immediately instead of after the TTL.
97
+ */
78
98
  function invalidateCustomProvidersSnapshots(tenant) {
79
- orgSnapshots.delete(tenant.orgId);
99
+ orgSnapshots.delete(tenant.orgId);
80
100
  }
101
+ /** Test hook: clear registration and cached org snapshots. */
81
102
  function resetCustomProvidersSourceForTests() {
82
- setCustomProvidersSource(void 0);
83
- orgSnapshots.clear();
103
+ setCustomProvidersSource(void 0);
104
+ orgSnapshots.clear();
84
105
  }
85
- function createCustomProvidersPrimer({
86
- auth,
87
- storage,
88
- authEnabled
89
- }) {
90
- return async (c, next) => {
91
- const orgId = orgForTenant(auth.tenant(c), authEnabled);
92
- if (orgId) {
93
- try {
94
- await snapshotFor(orgId, storage).ensureFresh();
95
- } catch {
96
- }
97
- }
98
- await next();
99
- };
106
+ /**
107
+ * Middleware mounted after the web auth gate: primes the caller's org snapshot
108
+ * so the request's first model call sees their custom providers without an
109
+ * async seam in model resolution. Cheap when fresh (TTL check), best-effort
110
+ * when not — a failed hydrate serves the last snapshot, never blocks a request.
111
+ */
112
+ function createCustomProvidersPrimer({ auth, storage, authEnabled }) {
113
+ return async (c, next) => {
114
+ const orgId = orgForTenant(auth.tenant(c), authEnabled);
115
+ if (orgId) try {
116
+ await snapshotFor(orgId, storage).ensureFresh();
117
+ } catch {}
118
+ await next();
119
+ };
100
120
  }
101
- export {
102
- createCustomProvidersPrimer,
103
- invalidateCustomProvidersSnapshots,
104
- registerCustomProvidersSource,
105
- resetCustomProvidersSourceForTests
106
- };
121
+ //#endregion
122
+ export { createCustomProvidersPrimer, invalidateCustomProvidersSnapshots, registerCustomProvidersSource, resetCustomProvidersSourceForTests };
123
+
107
124
  //# sourceMappingURL=custom-provider-source.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/custom-provider-source.ts","../../src/routes/provider-credentials.ts"],"sourcesContent":["/**\n * DB-backed custom providers source for model resolution (factory mode).\n *\n * The SDK's `resolveModel` and gateway catalog ask the registered\n * `CustomProvidersSource` synchronously, so this module keeps a small per-org\n * **snapshot** of custom provider rows hydrated from the `custom-providers`\n * domain. Once registered the source is authoritative — the factory never\n * reads `settings.json` custom providers, in either auth mode:\n *\n * - tenant mode: rows are scoped per org (`tenantOrgId`); calls without an\n * authenticated tenant (boot-time catalog) resolve to an empty list.\n * - no-auth/local mode: rows live under the sentinel `local` org, matching the\n * custom provider routes.\n *\n * Snapshots are primed per request by `createCustomProvidersPrimer` (mounted\n * after the web auth gate) and invalidated on writes via\n * `invalidateCustomProvidersSnapshots` so changes are visible immediately.\n */\n\nimport { setCustomProvidersSource } from '@mastra/code-sdk/agents/custom-provider-source';\nimport type { MastraCodeCustomProvider } from '@mastra/code-sdk/agents/mastracode-gateway';\nimport type { MiddlewareHandler } from 'hono';\n\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport { tenantOrgId } from './provider-credentials.js';\nimport type { RouteAuth } from './route.js';\n\n/** How long a hydrated snapshot is considered fresh. */\nconst SNAPSHOT_TTL_MS = 15_000;\n\n/** Cap on cached org snapshots; oldest-inserted evicted beyond this. */\nconst MAX_CACHED_ORGS = 1000;\n\n/** Sentinel org for no-auth mode — must match the custom provider routes. */\nconst LOCAL_ORG = 'local';\n\nclass OrgCustomProvidersSnapshot {\n readonly #orgId: string;\n readonly #storage: CustomProvidersStorage;\n #snapshot: MastraCodeCustomProvider[] = [];\n #fetchedAt = 0;\n #hydrating: Promise<void> | undefined;\n\n constructor(orgId: string, storage: CustomProvidersStorage) {\n this.#orgId = orgId;\n this.#storage = storage;\n }\n\n /** Hydrate the snapshot when stale; coalesces concurrent callers. */\n async ensureFresh(now = Date.now()): Promise<void> {\n if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;\n this.#hydrating ??= this.#hydrate().finally(() => {\n this.#hydrating = undefined;\n });\n await this.#hydrating;\n }\n\n async #hydrate(): Promise<void> {\n await this.#storage.ensureReady();\n const records = await this.#storage.list({ orgId: this.#orgId });\n this.#snapshot = records.map(record => ({\n name: record.name,\n url: record.url,\n apiKey: record.apiKey ?? undefined,\n models: record.models,\n }));\n this.#fetchedAt = Date.now();\n }\n\n /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */\n get(): MastraCodeCustomProvider[] {\n if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {\n void this.ensureFresh().catch(() => {});\n }\n return this.#snapshot;\n }\n}\n\nconst orgSnapshots = new Map<string, OrgCustomProvidersSnapshot>();\n\nfunction snapshotFor(orgId: string, storage: CustomProvidersStorage): OrgCustomProvidersSnapshot {\n let snapshot = orgSnapshots.get(orgId);\n if (!snapshot) {\n if (orgSnapshots.size >= MAX_CACHED_ORGS) {\n const oldest = orgSnapshots.keys().next().value;\n if (oldest !== undefined) orgSnapshots.delete(oldest);\n }\n snapshot = new OrgCustomProvidersSnapshot(orgId, storage);\n orgSnapshots.set(orgId, snapshot);\n }\n return snapshot;\n}\n\n/** The org a call resolves to, or `undefined` to fail closed (tenant mode without identity). */\nfunction orgForTenant(\n tenant: { orgId?: string; userId: string } | undefined,\n authEnabled: boolean,\n): string | undefined {\n if (!authEnabled) return LOCAL_ORG;\n return tenant ? tenantOrgId(tenant) : undefined;\n}\n\n/**\n * Register the DB-backed custom providers source with the SDK. Called by the\n * factory after storage init with the `custom-providers` domain handle; from\n * then on model resolution and the gateway catalog never read settings.json\n * custom providers.\n */\nexport function registerCustomProvidersSource({\n storage,\n authEnabled,\n}: {\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): void {\n setCustomProvidersSource(tenant => {\n const orgId = orgForTenant(tenant, authEnabled);\n if (!orgId) return [];\n return snapshotFor(orgId, storage).get();\n });\n}\n\n/**\n * Drop the org's cached snapshot after a custom provider write so the change\n * is visible to the next model call immediately instead of after the TTL.\n */\nexport function invalidateCustomProvidersSnapshots(tenant: { orgId: string }): void {\n orgSnapshots.delete(tenant.orgId);\n}\n\n/** Test hook: clear registration and cached org snapshots. */\nexport function resetCustomProvidersSourceForTests(): void {\n setCustomProvidersSource(undefined);\n orgSnapshots.clear();\n}\n\n/**\n * Middleware mounted after the web auth gate: primes the caller's org snapshot\n * so the request's first model call sees their custom providers without an\n * async seam in model resolution. Cheap when fresh (TTL check), best-effort\n * when not — a failed hydrate serves the last snapshot, never blocks a request.\n */\nexport function createCustomProvidersPrimer({\n auth,\n storage,\n authEnabled,\n}: {\n auth: RouteAuth;\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): MiddlewareHandler {\n return async (c, next) => {\n const orgId = orgForTenant(auth.tenant(c), authEnabled);\n if (orgId) {\n try {\n await snapshotFor(orgId, storage).ensureFresh();\n } catch {\n // Fail open: model calls serve the last snapshot (or none).\n }\n }\n await next();\n };\n}\n","/**\n * Per-request model-provider credential context.\n *\n * The provider config/OAuth routes serve two deployment shapes:\n *\n * - **Local** (no web auth adapter): credentials live in the server-global\n * file-backed `AuthStorage` (`auth.json`) — unchanged TUI behavior.\n * - **Tenant** (auth adapter active): credentials live in the\n * `model-credentials` factory storage domain, scoped per `(orgId, userId)`\n * with optional org-wide rows (`userId` absent).\n *\n * `resolveCredentialContext` decides which shape a request is in: an\n * authenticated user always gets the tenant path; an unauthenticated request\n * is rejected when auth is enabled and falls back to local otherwise.\n */\n\nimport type { Context } from 'hono';\n\nimport type {\n CredentialRecord,\n LoginSessionKind,\n ModelCredentialsStorage,\n} from '../storage/domains/credentials/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * OAuth credentials are stored under the auth provider id, which differs from\n * the catalog provider id for OpenAI (stored as `openai-codex`). Tenant rows\n * use the same keying so one lookup serves both OAuth tokens and API keys.\n */\nexport function getAuthProviderId(provider: string): string {\n return provider === 'openai' ? 'openai-codex' : provider;\n}\n\n/**\n * Providers that support a browser-driven OAuth sign-in flow, keyed by\n * *catalog* provider id (what the routes and settings UI use). All flows need\n * no inbound connection to the server: Anthropic uses paste-code PKCE, the\n * rest use RFC 8628-style device codes.\n */\nexport const WEB_OAUTH_FLOW_KINDS: Readonly<Record<string, LoginSessionKind>> = {\n anthropic: 'paste-code',\n openai: 'device-code',\n 'github-copilot': 'device-code',\n xai: 'device-code',\n};\n\nexport type CredentialContext =\n | { mode: 'local' }\n | { mode: 'tenant'; storage: ModelCredentialsStorage; orgId: string; userId: string };\n\n/**\n * The tenant credentials domain, when registered and ready. `undefined` means\n * the factory never ran (no handle threaded in) or the domain's init failed\n * (fail-soft — callers report the feature unavailable instead of crashing).\n */\nexport async function getTenantCredentialsStorage(\n credentials: ModelCredentialsStorage | undefined,\n): Promise<ModelCredentialsStorage | undefined> {\n if (!credentials) return undefined;\n try {\n await credentials.ensureReady();\n } catch {\n return undefined;\n }\n return credentials;\n}\n\n/**\n * Org key for tenant rows. Personal accounts get an org bootstrapped by the\n * auth adapter (`ensureOrg`); if one still isn't present, scope rows under a\n * per-user synthetic org so credentials never become server-global.\n */\nexport function tenantOrgId(tenant: { orgId?: string; userId: string }): string {\n return tenant.orgId ?? `user:${tenant.userId}`;\n}\n\n/**\n * Resolve the credential context for a request, or a ready-to-return error\n * response. Mutating credential routes call this and hard-fail (401/503)\n * when the tenant path is required but unavailable.\n */\nexport async function resolveCredentialContext({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialContext | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) {\n // When an auth provider is active, credential operations always require a\n // signed-in caller — otherwise one anonymous request could write keys for\n // everyone. Without a provider this is a single-user local server.\n if (auth.enabled()) return { response: c.json({ error: 'unauthorized' }, 401) };\n return { mode: 'local' };\n }\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) {\n return {\n response: c.json(\n {\n error: 'credentials_unavailable',\n message: 'Tenant credential storage is unavailable — the app database is not configured or failed to start.',\n },\n 503,\n ),\n };\n }\n return { mode: 'tenant', storage, orgId: tenantOrgId(tenant), userId: tenant.userId };\n}\n\n/**\n * List the caller's tenant credential records for provider listing, or\n * `undefined` in local mode (caller should fall back to `AuthStorage`).\n * Read-only and degradation-friendly: an authenticated caller with the domain\n * unavailable gets an empty list (sources show `env`/`none`) instead of a 503\n * so the settings page still renders.\n */\nexport async function listTenantCredentialsForRequest({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialRecord[] | undefined> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return auth.enabled() ? [] : undefined;\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) return [];\n return storage.listCredentials(tenantOrgId(tenant), tenant.userId);\n}\n"],"mappings":";AAmBA,SAAS,gCAAgC;;;ACsDlC,SAAS,YAAY,QAAoD;AAC9E,SAAO,OAAO,SAAS,QAAQ,OAAO,MAAM;AAC9C;;;AD/CA,IAAM,kBAAkB;AAGxB,IAAM,kBAAkB;AAGxB,IAAM,YAAY;AAElB,IAAM,6BAAN,MAAiC;AAAA,EACtB;AAAA,EACA;AAAA,EACT,YAAwC,CAAC;AAAA,EACzC,aAAa;AAAA,EACb;AAAA,EAEA,YAAY,OAAe,SAAiC;AAC1D,SAAK,SAAS;AACd,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,YAAY,MAAM,KAAK,IAAI,GAAkB;AACjD,QAAI,MAAM,KAAK,aAAa,gBAAiB;AAC7C,SAAK,eAAe,KAAK,SAAS,EAAE,QAAQ,MAAM;AAChD,WAAK,aAAa;AAAA,IACpB,CAAC;AACD,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,SAAS,YAAY;AAChC,UAAM,UAAU,MAAM,KAAK,SAAS,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAC/D,SAAK,YAAY,QAAQ,IAAI,aAAW;AAAA,MACtC,MAAM,OAAO;AAAA,MACb,KAAK,OAAO;AAAA,MACZ,QAAQ,OAAO,UAAU;AAAA,MACzB,QAAQ,OAAO;AAAA,IACjB,EAAE;AACF,SAAK,aAAa,KAAK,IAAI;AAAA,EAC7B;AAAA;AAAA,EAGA,MAAkC;AAChC,QAAI,KAAK,IAAI,IAAI,KAAK,cAAc,iBAAiB;AACnD,WAAK,KAAK,YAAY,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACxC;AACA,WAAO,KAAK;AAAA,EACd;AACF;AAEA,IAAM,eAAe,oBAAI,IAAwC;AAEjE,SAAS,YAAY,OAAe,SAA6D;AAC/F,MAAI,WAAW,aAAa,IAAI,KAAK;AACrC,MAAI,CAAC,UAAU;AACb,QAAI,aAAa,QAAQ,iBAAiB;AACxC,YAAM,SAAS,aAAa,KAAK,EAAE,KAAK,EAAE;AAC1C,UAAI,WAAW,OAAW,cAAa,OAAO,MAAM;AAAA,IACtD;AACA,eAAW,IAAI,2BAA2B,OAAO,OAAO;AACxD,iBAAa,IAAI,OAAO,QAAQ;AAAA,EAClC;AACA,SAAO;AACT;AAGA,SAAS,aACP,QACA,aACoB;AACpB,MAAI,CAAC,YAAa,QAAO;AACzB,SAAO,SAAS,YAAY,MAAM,IAAI;AACxC;AAQO,SAAS,8BAA8B;AAAA,EAC5C;AAAA,EACA;AACF,GAGS;AACP,2BAAyB,YAAU;AACjC,UAAM,QAAQ,aAAa,QAAQ,WAAW;AAC9C,QAAI,CAAC,MAAO,QAAO,CAAC;AACpB,WAAO,YAAY,OAAO,OAAO,EAAE,IAAI;AAAA,EACzC,CAAC;AACH;AAMO,SAAS,mCAAmC,QAAiC;AAClF,eAAa,OAAO,OAAO,KAAK;AAClC;AAGO,SAAS,qCAA2C;AACzD,2BAAyB,MAAS;AAClC,eAAa,MAAM;AACrB;AAQO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AACF,GAIsB;AACpB,SAAO,OAAO,GAAG,SAAS;AACxB,UAAM,QAAQ,aAAa,KAAK,OAAO,CAAC,GAAG,WAAW;AACtD,QAAI,OAAO;AACT,UAAI;AACF,cAAM,YAAY,OAAO,OAAO,EAAE,YAAY;AAAA,MAChD,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
1
+ {"version":3,"file":"custom-provider-source.js","names":["#orgId","#storage","#fetchedAt","#hydrating","#hydrate","#snapshot"],"sources":["../../src/routes/custom-provider-source.ts"],"sourcesContent":["/**\n * DB-backed custom providers source for model resolution (factory mode).\n *\n * The SDK's `resolveModel` and gateway catalog ask the registered\n * `CustomProvidersSource` synchronously, so this module keeps a small per-org\n * **snapshot** of custom provider rows hydrated from the `custom-providers`\n * domain. Once registered the source is authoritative — the factory never\n * reads `settings.json` custom providers, in either auth mode:\n *\n * - tenant mode: rows are scoped per org (`tenantOrgId`); calls without an\n * authenticated tenant (boot-time catalog) resolve to an empty list.\n * - no-auth/local mode: rows live under the sentinel `local` org, matching the\n * custom provider routes.\n *\n * Snapshots are primed per request by `createCustomProvidersPrimer` (mounted\n * after the web auth gate) and invalidated on writes via\n * `invalidateCustomProvidersSnapshots` so changes are visible immediately.\n */\n\nimport { setCustomProvidersSource } from '@mastra/code-sdk/agents/custom-provider-source';\nimport type { MastraCodeCustomProvider } from '@mastra/code-sdk/agents/mastracode-gateway';\nimport type { MiddlewareHandler } from 'hono';\n\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport { tenantOrgId } from './provider-credentials.js';\nimport type { RouteAuth } from './route.js';\n\n/** How long a hydrated snapshot is considered fresh. */\nconst SNAPSHOT_TTL_MS = 15_000;\n\n/** Cap on cached org snapshots; oldest-inserted evicted beyond this. */\nconst MAX_CACHED_ORGS = 1000;\n\n/** Sentinel org for no-auth mode — must match the custom provider routes. */\nconst LOCAL_ORG = 'local';\n\nclass OrgCustomProvidersSnapshot {\n readonly #orgId: string;\n readonly #storage: CustomProvidersStorage;\n #snapshot: MastraCodeCustomProvider[] = [];\n #fetchedAt = 0;\n #hydrating: Promise<void> | undefined;\n\n constructor(orgId: string, storage: CustomProvidersStorage) {\n this.#orgId = orgId;\n this.#storage = storage;\n }\n\n /** Hydrate the snapshot when stale; coalesces concurrent callers. */\n async ensureFresh(now = Date.now()): Promise<void> {\n if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;\n this.#hydrating ??= this.#hydrate().finally(() => {\n this.#hydrating = undefined;\n });\n await this.#hydrating;\n }\n\n async #hydrate(): Promise<void> {\n await this.#storage.ensureReady();\n const records = await this.#storage.list({ orgId: this.#orgId });\n this.#snapshot = records.map(record => ({\n name: record.name,\n url: record.url,\n apiKey: record.apiKey ?? undefined,\n models: record.models,\n }));\n this.#fetchedAt = Date.now();\n }\n\n /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */\n get(): MastraCodeCustomProvider[] {\n if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {\n void this.ensureFresh().catch(() => {});\n }\n return this.#snapshot;\n }\n}\n\nconst orgSnapshots = new Map<string, OrgCustomProvidersSnapshot>();\n\nfunction snapshotFor(orgId: string, storage: CustomProvidersStorage): OrgCustomProvidersSnapshot {\n let snapshot = orgSnapshots.get(orgId);\n if (!snapshot) {\n if (orgSnapshots.size >= MAX_CACHED_ORGS) {\n const oldest = orgSnapshots.keys().next().value;\n if (oldest !== undefined) orgSnapshots.delete(oldest);\n }\n snapshot = new OrgCustomProvidersSnapshot(orgId, storage);\n orgSnapshots.set(orgId, snapshot);\n }\n return snapshot;\n}\n\n/** The org a call resolves to, or `undefined` to fail closed (tenant mode without identity). */\nfunction orgForTenant(\n tenant: { orgId?: string; userId: string } | undefined,\n authEnabled: boolean,\n): string | undefined {\n if (!authEnabled) return LOCAL_ORG;\n return tenant ? tenantOrgId(tenant) : undefined;\n}\n\n/**\n * Register the DB-backed custom providers source with the SDK. Called by the\n * factory after storage init with the `custom-providers` domain handle; from\n * then on model resolution and the gateway catalog never read settings.json\n * custom providers.\n */\nexport function registerCustomProvidersSource({\n storage,\n authEnabled,\n}: {\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): void {\n setCustomProvidersSource(tenant => {\n const orgId = orgForTenant(tenant, authEnabled);\n if (!orgId) return [];\n return snapshotFor(orgId, storage).get();\n });\n}\n\n/**\n * Drop the org's cached snapshot after a custom provider write so the change\n * is visible to the next model call immediately instead of after the TTL.\n */\nexport function invalidateCustomProvidersSnapshots(tenant: { orgId: string }): void {\n orgSnapshots.delete(tenant.orgId);\n}\n\n/** Test hook: clear registration and cached org snapshots. */\nexport function resetCustomProvidersSourceForTests(): void {\n setCustomProvidersSource(undefined);\n orgSnapshots.clear();\n}\n\n/**\n * Middleware mounted after the web auth gate: primes the caller's org snapshot\n * so the request's first model call sees their custom providers without an\n * async seam in model resolution. Cheap when fresh (TTL check), best-effort\n * when not — a failed hydrate serves the last snapshot, never blocks a request.\n */\nexport function createCustomProvidersPrimer({\n auth,\n storage,\n authEnabled,\n}: {\n auth: RouteAuth;\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): MiddlewareHandler {\n return async (c, next) => {\n const orgId = orgForTenant(auth.tenant(c), authEnabled);\n if (orgId) {\n try {\n await snapshotFor(orgId, storage).ensureFresh();\n } catch {\n // Fail open: model calls serve the last snapshot (or none).\n }\n }\n await next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAM,kBAAkB;;AAGxB,MAAM,kBAAkB;;AAGxB,MAAM,YAAY;AAElB,IAAM,6BAAN,MAAiC;CAC/B;CACA;CACA,YAAwC,CAAC;CACzC,aAAa;CACb;CAEA,YAAY,OAAe,SAAiC;EAC1D,KAAKA,SAAS;EACd,KAAKC,WAAW;CAClB;;CAGA,MAAM,YAAY,MAAM,KAAK,IAAI,GAAkB;EACjD,IAAI,MAAM,KAAKC,aAAa,iBAAiB;EAC7C,KAAKC,eAAe,KAAKC,SAAS,CAAC,CAAC,cAAc;GAChD,KAAKD,aAAa,KAAA;EACpB,CAAC;EACD,MAAM,KAAKA;CACb;CAEA,MAAMC,WAA0B;EAC9B,MAAM,KAAKH,SAAS,YAAY;EAChC,MAAM,UAAU,MAAM,KAAKA,SAAS,KAAK,EAAE,OAAO,KAAKD,OAAO,CAAC;EAC/D,KAAKK,YAAY,QAAQ,KAAI,YAAW;GACtC,MAAM,OAAO;GACb,KAAK,OAAO;GACZ,QAAQ,OAAO,UAAU,KAAA;GACzB,QAAQ,OAAO;EACjB,EAAE;EACF,KAAKH,aAAa,KAAK,IAAI;CAC7B;;CAGA,MAAkC;EAChC,IAAI,KAAK,IAAI,IAAI,KAAKA,cAAc,iBAClC,KAAU,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC;EAExC,OAAO,KAAKG;CACd;AACF;AAEA,MAAM,+BAAe,IAAI,IAAwC;AAEjE,SAAS,YAAY,OAAe,SAA6D;CAC/F,IAAI,WAAW,aAAa,IAAI,KAAK;CACrC,IAAI,CAAC,UAAU;EACb,IAAI,aAAa,QAAQ,iBAAiB;GACxC,MAAM,SAAS,aAAa,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GAC1C,IAAI,WAAW,KAAA,GAAW,aAAa,OAAO,MAAM;EACtD;EACA,WAAW,IAAI,2BAA2B,OAAO,OAAO;EACxD,aAAa,IAAI,OAAO,QAAQ;CAClC;CACA,OAAO;AACT;;AAGA,SAAS,aACP,QACA,aACoB;CACpB,IAAI,CAAC,aAAa,OAAO;CACzB,OAAO,SAAS,YAAY,MAAM,IAAI,KAAA;AACxC;;;;;;;AAQA,SAAgB,8BAA8B,EAC5C,SACA,eAIO;CACP,0BAAyB,WAAU;EACjC,MAAM,QAAQ,aAAa,QAAQ,WAAW;EAC9C,IAAI,CAAC,OAAO,OAAO,CAAC;EACpB,OAAO,YAAY,OAAO,OAAO,CAAC,CAAC,IAAI;CACzC,CAAC;AACH;;;;;AAMA,SAAgB,mCAAmC,QAAiC;CAClF,aAAa,OAAO,OAAO,KAAK;AAClC;;AAGA,SAAgB,qCAA2C;CACzD,yBAAyB,KAAA,CAAS;CAClC,aAAa,MAAM;AACrB;;;;;;;AAQA,SAAgB,4BAA4B,EAC1C,MACA,SACA,eAKoB;CACpB,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,QAAQ,aAAa,KAAK,OAAO,CAAC,GAAG,WAAW;EACtD,IAAI,OACF,IAAI;GACF,MAAM,YAAY,OAAO,OAAO,CAAC,CAAC,YAAY;EAChD,QAAQ,CAER;EAEF,MAAM,KAAK;CACb;AACF"}