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