@mastra/factory 0.0.0 → 0.1.0-alpha.3
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 +23 -0
- package/LICENSE.md +30 -0
- package/dist/auth.d.ts +181 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +370 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities/connection.d.ts +9 -0
- package/dist/capabilities/connection.d.ts.map +1 -0
- package/dist/capabilities/connection.js +1 -0
- package/dist/capabilities/connection.js.map +1 -0
- package/dist/capabilities/intake.d.ts +93 -0
- package/dist/capabilities/intake.d.ts.map +1 -0
- package/dist/capabilities/intake.js +1 -0
- package/dist/capabilities/intake.js.map +1 -0
- package/dist/capabilities/version-control.d.ts +239 -0
- package/dist/capabilities/version-control.d.ts.map +1 -0
- package/dist/capabilities/version-control.js +1 -0
- package/dist/capabilities/version-control.js.map +1 -0
- package/dist/factory.d.ts +145 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +38245 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38245 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/base.d.ts +197 -0
- package/dist/integrations/base.d.ts.map +1 -0
- package/dist/integrations/base.js +1 -0
- package/dist/integrations/base.js.map +1 -0
- package/dist/integrations/github/config.d.ts +60 -0
- package/dist/integrations/github/config.d.ts.map +1 -0
- package/dist/integrations/github/config.js +28 -0
- package/dist/integrations/github/config.js.map +1 -0
- package/dist/integrations/github/factory-session.d.ts +12 -0
- package/dist/integrations/github/factory-session.d.ts.map +1 -0
- package/dist/integrations/github/factory-session.js +41 -0
- package/dist/integrations/github/factory-session.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +191 -0
- package/dist/integrations/github/integration.d.ts.map +1 -0
- package/dist/integrations/github/integration.js +3001 -0
- package/dist/integrations/github/integration.js.map +1 -0
- package/dist/integrations/github/issue-triage.d.ts +27 -0
- package/dist/integrations/github/issue-triage.d.ts.map +1 -0
- package/dist/integrations/github/issue-triage.js +82 -0
- package/dist/integrations/github/issue-triage.js.map +1 -0
- package/dist/integrations/github/project-lock.d.ts +78 -0
- package/dist/integrations/github/project-lock.d.ts.map +1 -0
- package/dist/integrations/github/project-lock.js +70 -0
- package/dist/integrations/github/project-lock.js.map +1 -0
- package/dist/integrations/github/provenance.d.ts +27 -0
- package/dist/integrations/github/provenance.d.ts.map +1 -0
- package/dist/integrations/github/provenance.js +104 -0
- package/dist/integrations/github/provenance.js.map +1 -0
- package/dist/integrations/github/routes.d.ts +72 -0
- package/dist/integrations/github/routes.d.ts.map +1 -0
- package/dist/integrations/github/routes.js +1834 -0
- package/dist/integrations/github/routes.js.map +1 -0
- package/dist/integrations/github/sandbox.d.ts +268 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -0
- package/dist/integrations/github/sandbox.js +381 -0
- package/dist/integrations/github/sandbox.js.map +1 -0
- package/dist/integrations/github/session-subscriptions.d.ts +23 -0
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/session-subscriptions.js +190 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -0
- package/dist/integrations/github/subscriptions.d.ts +54 -0
- package/dist/integrations/github/subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/subscriptions.js +70 -0
- package/dist/integrations/github/subscriptions.js.map +1 -0
- package/dist/integrations/github/webhook.d.ts +87 -0
- package/dist/integrations/github/webhook.d.ts.map +1 -0
- package/dist/integrations/github/webhook.js +381 -0
- package/dist/integrations/github/webhook.js.map +1 -0
- package/dist/integrations/linear/agent-tools.d.ts +34 -0
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
- package/dist/integrations/linear/agent-tools.js +108 -0
- package/dist/integrations/linear/agent-tools.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +233 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -0
- package/dist/integrations/linear/integration.js +962 -0
- package/dist/integrations/linear/integration.js.map +1 -0
- package/dist/integrations/linear/routes.d.ts +61 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -0
- package/dist/integrations/linear/routes.js +242 -0
- package/dist/integrations/linear/routes.js.map +1 -0
- package/dist/integrations/linear/storage.d.ts +65 -0
- package/dist/integrations/linear/storage.d.ts.map +1 -0
- package/dist/integrations/linear/storage.js +1 -0
- package/dist/integrations/linear/storage.js.map +1 -0
- package/dist/integrations/platform/api-client.d.ts +24 -0
- package/dist/integrations/platform/api-client.d.ts.map +1 -0
- package/dist/integrations/platform/api-client.js +153 -0
- package/dist/integrations/platform/api-client.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +44 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +585 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -0
- package/dist/integrations/platform/github/integration.d.ts +36 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -0
- package/dist/integrations/platform/github/integration.js +3253 -0
- package/dist/integrations/platform/github/integration.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +46 -0
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
- package/dist/integrations/platform/linear/integration.js +891 -0
- package/dist/integrations/platform/linear/integration.js.map +1 -0
- package/dist/integrations/workos/integration.d.ts +39 -0
- package/dist/integrations/workos/integration.d.ts.map +1 -0
- package/dist/integrations/workos/integration.js +96 -0
- package/dist/integrations/workos/integration.js.map +1 -0
- package/dist/routes/config.d.ts +220 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +770 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/fs.d.ts +105 -0
- package/dist/routes/fs.d.ts.map +1 -0
- package/dist/routes/fs.js +294 -0
- package/dist/routes/fs.js.map +1 -0
- package/dist/routes/intake.d.ts +23 -0
- package/dist/routes/intake.d.ts.map +1 -0
- package/dist/routes/intake.js +179 -0
- package/dist/routes/intake.js.map +1 -0
- package/dist/routes/oauth.d.ts +43 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +25058 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/projects.d.ts +19 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +395 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/provider-credentials.d.ts +81 -0
- package/dist/routes/provider-credentials.d.ts.map +1 -0
- package/dist/routes/provider-credentials.js +68 -0
- package/dist/routes/provider-credentials.js.map +1 -0
- package/dist/routes/route.d.ts +48 -0
- package/dist/routes/route.d.ts.map +1 -0
- package/dist/routes/route.js +11 -0
- package/dist/routes/route.js.map +1 -0
- package/dist/routes/skills.d.ts +30 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +181 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/surface.d.ts +83 -0
- package/dist/routes/surface.d.ts.map +1 -0
- package/dist/routes/surface.js +28519 -0
- package/dist/routes/surface.js.map +1 -0
- package/dist/routes/tenant-credentials.d.ts +74 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -0
- package/dist/routes/tenant-credentials.js +181 -0
- package/dist/routes/tenant-credentials.js.map +1 -0
- package/dist/routes/work-items.d.ts +40 -0
- package/dist/routes/work-items.d.ts.map +1 -0
- package/dist/routes/work-items.js +902 -0
- package/dist/routes/work-items.js.map +1 -0
- package/dist/rules/binding-context.d.ts +5 -0
- package/dist/rules/binding-context.d.ts.map +1 -0
- package/dist/rules/binding-context.js +40 -0
- package/dist/rules/binding-context.js.map +1 -0
- package/dist/rules/defaults.d.ts +9 -0
- package/dist/rules/defaults.d.ts.map +1 -0
- package/dist/rules/defaults.js +334 -0
- package/dist/rules/defaults.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +40 -0
- package/dist/rules/dispatcher.d.ts.map +1 -0
- package/dist/rules/dispatcher.js +695 -0
- package/dist/rules/dispatcher.js.map +1 -0
- package/dist/rules/github-service.d.ts +29 -0
- package/dist/rules/github-service.d.ts.map +1 -0
- package/dist/rules/github-service.js +474 -0
- package/dist/rules/github-service.js.map +1 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +583 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/linear-service.d.ts +27 -0
- package/dist/rules/linear-service.d.ts.map +1 -0
- package/dist/rules/linear-service.js +343 -0
- package/dist/rules/linear-service.js.map +1 -0
- package/dist/rules/processor.d.ts +54 -0
- package/dist/rules/processor.d.ts.map +1 -0
- package/dist/rules/processor.js +629 -0
- package/dist/rules/processor.js.map +1 -0
- package/dist/rules/resolve.d.ts +17 -0
- package/dist/rules/resolve.d.ts.map +1 -0
- package/dist/rules/resolve.js +27 -0
- package/dist/rules/resolve.js.map +1 -0
- package/dist/rules/start-coordinator.d.ts +58 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -0
- package/dist/rules/start-coordinator.js +136 -0
- package/dist/rules/start-coordinator.js.map +1 -0
- package/dist/rules/tools.d.ts +10 -0
- package/dist/rules/tools.d.ts.map +1 -0
- package/dist/rules/tools.js +93 -0
- package/dist/rules/tools.js.map +1 -0
- package/dist/rules/transition-service.d.ts +32 -0
- package/dist/rules/transition-service.d.ts.map +1 -0
- package/dist/rules/transition-service.js +474 -0
- package/dist/rules/transition-service.js.map +1 -0
- package/dist/rules/types.d.ts +225 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +33 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/validation.d.ts +11 -0
- package/dist/rules/validation.d.ts.map +1 -0
- package/dist/rules/validation.js +310 -0
- package/dist/rules/validation.js.map +1 -0
- package/dist/sandbox/fleet.d.ts +208 -0
- package/dist/sandbox/fleet.d.ts.map +1 -0
- package/dist/sandbox/fleet.js +259 -0
- package/dist/sandbox/fleet.js.map +1 -0
- package/dist/sandbox/reattach.d.ts +3 -0
- package/dist/sandbox/reattach.d.ts.map +1 -0
- package/dist/sandbox/reattach.js +9 -0
- package/dist/sandbox/reattach.js.map +1 -0
- package/dist/server-error.d.ts +12 -0
- package/dist/server-error.d.ts.map +1 -0
- package/dist/server-error.js +20 -0
- package/dist/server-error.js.map +1 -0
- package/dist/skills/service.d.ts +48 -0
- package/dist/skills/service.d.ts.map +1 -0
- package/dist/skills/service.js +45 -0
- package/dist/skills/service.js.map +1 -0
- package/dist/spa-static.d.ts +34 -0
- package/dist/spa-static.d.ts.map +1 -0
- package/dist/spa-static.js +73 -0
- package/dist/spa-static.js.map +1 -0
- package/dist/state-signing.d.ts +48 -0
- package/dist/state-signing.d.ts.map +1 -0
- package/dist/state-signing.js +48 -0
- package/dist/state-signing.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
- package/dist/storage/domains/audit/agent-audit.js +65 -0
- package/dist/storage/domains/audit/agent-audit.js.map +1 -0
- package/dist/storage/domains/audit/base.d.ts +140 -0
- package/dist/storage/domains/audit/base.d.ts.map +1 -0
- package/dist/storage/domains/audit/base.js +127 -0
- package/dist/storage/domains/audit/base.js.map +1 -0
- package/dist/storage/domains/audit/domain.d.ts +69 -0
- package/dist/storage/domains/audit/domain.d.ts.map +1 -0
- package/dist/storage/domains/audit/domain.js +165 -0
- package/dist/storage/domains/audit/domain.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +134 -0
- package/dist/storage/domains/credentials/base.d.ts.map +1 -0
- package/dist/storage/domains/credentials/base.js +257 -0
- package/dist/storage/domains/credentials/base.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +38 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -0
- package/dist/storage/domains/intake/base.js +62 -0
- package/dist/storage/domains/intake/base.js.map +1 -0
- package/dist/storage/domains/integrations/base.d.ts +126 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -0
- package/dist/storage/domains/integrations/base.js +252 -0
- package/dist/storage/domains/integrations/base.js.map +1 -0
- package/dist/storage/domains/model-packs/base.d.ts +49 -0
- package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
- package/dist/storage/domains/model-packs/base.js +95 -0
- package/dist/storage/domains/model-packs/base.js.map +1 -0
- package/dist/storage/domains/projects/base.d.ts +55 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -0
- package/dist/storage/domains/projects/base.js +99 -0
- package/dist/storage/domains/projects/base.js.map +1 -0
- package/dist/storage/domains/queue-health/base.d.ts +52 -0
- package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
- package/dist/storage/domains/queue-health/base.js +87 -0
- package/dist/storage/domains/queue-health/base.js.map +1 -0
- package/dist/storage/domains/source-control/base.d.ts +288 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -0
- package/dist/storage/domains/source-control/base.js +722 -0
- package/dist/storage/domains/source-control/base.js.map +1 -0
- package/dist/storage/domains/source-control/inmemory.d.ts +158 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/source-control/inmemory.js +348 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -0
- package/dist/storage/domains/work-items/base.d.ts +412 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -0
- package/dist/storage/domains/work-items/base.js +1174 -0
- package/dist/storage/domains/work-items/base.js.map +1 -0
- package/dist/storage/domains/work-items/metrics.d.ts +101 -0
- package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
- package/dist/storage/domains/work-items/metrics.js +203 -0
- package/dist/storage/domains/work-items/metrics.js.map +1 -0
- package/dist/workspace.d.ts +20 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +364 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +19 -19
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
// src/routes/config.ts
|
|
2
|
+
import { removeCustomPackFromSettings } from "@mastra/code-sdk/onboarding/custom-packs";
|
|
3
|
+
import {
|
|
4
|
+
removeCustomProviderFromSettings,
|
|
5
|
+
upsertCustomProviderInSettings
|
|
6
|
+
} from "@mastra/code-sdk/onboarding/custom-providers";
|
|
7
|
+
import { applyOmRoleOverride, persistOmObserveAttachments } from "@mastra/code-sdk/onboarding/om-settings";
|
|
8
|
+
import { getAvailableModePacks } from "@mastra/code-sdk/onboarding/packs";
|
|
9
|
+
import {
|
|
10
|
+
getCustomProviderId,
|
|
11
|
+
loadSettings,
|
|
12
|
+
saveSettings,
|
|
13
|
+
THREAD_ACTIVE_MODEL_PACK_ID_KEY
|
|
14
|
+
} from "@mastra/code-sdk/onboarding/settings";
|
|
15
|
+
import { registerApiRoute } from "@mastra/core/server";
|
|
16
|
+
|
|
17
|
+
// src/routes/provider-credentials.ts
|
|
18
|
+
function getAuthProviderId(provider) {
|
|
19
|
+
return provider === "openai" ? "openai-codex" : provider;
|
|
20
|
+
}
|
|
21
|
+
var WEB_OAUTH_FLOW_KINDS = {
|
|
22
|
+
anthropic: "paste-code",
|
|
23
|
+
openai: "device-code",
|
|
24
|
+
"github-copilot": "device-code",
|
|
25
|
+
xai: "device-code"
|
|
26
|
+
};
|
|
27
|
+
async function getTenantCredentialsStorage(credentials) {
|
|
28
|
+
if (!credentials) return void 0;
|
|
29
|
+
try {
|
|
30
|
+
await credentials.ensureReady();
|
|
31
|
+
} catch {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
return credentials;
|
|
35
|
+
}
|
|
36
|
+
function tenantOrgId(tenant) {
|
|
37
|
+
return tenant.orgId ?? `user:${tenant.userId}`;
|
|
38
|
+
}
|
|
39
|
+
async function resolveCredentialContext({
|
|
40
|
+
c,
|
|
41
|
+
auth,
|
|
42
|
+
credentials
|
|
43
|
+
}) {
|
|
44
|
+
await auth.ensureUser(c);
|
|
45
|
+
const tenant = auth.tenant(c);
|
|
46
|
+
if (!tenant) {
|
|
47
|
+
if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
|
|
48
|
+
return { mode: "local" };
|
|
49
|
+
}
|
|
50
|
+
const storage = await getTenantCredentialsStorage(credentials);
|
|
51
|
+
if (!storage) {
|
|
52
|
+
return {
|
|
53
|
+
response: c.json(
|
|
54
|
+
{
|
|
55
|
+
error: "credentials_unavailable",
|
|
56
|
+
message: "Tenant credential storage is unavailable \u2014 the app database is not configured or failed to start."
|
|
57
|
+
},
|
|
58
|
+
503
|
|
59
|
+
)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
|
|
63
|
+
}
|
|
64
|
+
async function listTenantCredentialsForRequest({
|
|
65
|
+
c,
|
|
66
|
+
auth,
|
|
67
|
+
credentials
|
|
68
|
+
}) {
|
|
69
|
+
await auth.ensureUser(c);
|
|
70
|
+
const tenant = auth.tenant(c);
|
|
71
|
+
if (!tenant) return auth.enabled() ? [] : void 0;
|
|
72
|
+
const storage = await getTenantCredentialsStorage(credentials);
|
|
73
|
+
if (!storage) return [];
|
|
74
|
+
return storage.listCredentials(tenantOrgId(tenant), tenant.userId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/routes/route.ts
|
|
78
|
+
var Route = class {
|
|
79
|
+
deps;
|
|
80
|
+
constructor(deps) {
|
|
81
|
+
this.deps = deps;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/routes/config.ts
|
|
86
|
+
function loose(c) {
|
|
87
|
+
return c;
|
|
88
|
+
}
|
|
89
|
+
async function listProviders({
|
|
90
|
+
controller,
|
|
91
|
+
authStorage,
|
|
92
|
+
tenantCredentials
|
|
93
|
+
}) {
|
|
94
|
+
const models = await controller.listAvailableModels();
|
|
95
|
+
const seen = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const model of models) {
|
|
97
|
+
if (seen.has(model.provider)) continue;
|
|
98
|
+
const authProviderId = getAuthProviderId(model.provider);
|
|
99
|
+
let source = "none";
|
|
100
|
+
if (tenantCredentials) {
|
|
101
|
+
const userRec = tenantCredentials.find((r) => r.scope === "user" && r.provider === authProviderId);
|
|
102
|
+
const orgRec = tenantCredentials.find((r) => r.scope === "org" && r.provider === authProviderId);
|
|
103
|
+
if (userRec?.credential.type === "oauth") {
|
|
104
|
+
source = "oauth-user";
|
|
105
|
+
} else if (userRec?.credential.type === "api_key") {
|
|
106
|
+
source = "stored-user";
|
|
107
|
+
} else if (orgRec?.credential.type === "api_key") {
|
|
108
|
+
source = "stored-org";
|
|
109
|
+
}
|
|
110
|
+
} else if (authStorage?.isLoggedIn(authProviderId)) {
|
|
111
|
+
source = "oauth";
|
|
112
|
+
} else if (authStorage?.hasStoredApiKey(model.provider)) {
|
|
113
|
+
source = "stored";
|
|
114
|
+
} else if (model.apiKeyEnvVar && process.env[model.apiKeyEnvVar]) {
|
|
115
|
+
source = "env";
|
|
116
|
+
} else if (model.hasApiKey) {
|
|
117
|
+
source = "env";
|
|
118
|
+
}
|
|
119
|
+
const flowKind = WEB_OAUTH_FLOW_KINDS[model.provider];
|
|
120
|
+
seen.set(model.provider, {
|
|
121
|
+
provider: model.provider,
|
|
122
|
+
envVar: model.apiKeyEnvVar,
|
|
123
|
+
source,
|
|
124
|
+
...flowKind ? { oauth: { supported: true, modes: [flowKind] } } : {}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return Array.from(seen.values()).sort((a, b) => a.provider.localeCompare(b.provider));
|
|
128
|
+
}
|
|
129
|
+
function listCustomProviders() {
|
|
130
|
+
const settings = loadSettings();
|
|
131
|
+
return settings.customProviders.map((p) => ({
|
|
132
|
+
id: getCustomProviderId(p.name),
|
|
133
|
+
name: p.name,
|
|
134
|
+
url: p.url,
|
|
135
|
+
hasApiKey: Boolean(p.apiKey),
|
|
136
|
+
models: p.models
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
function parseCustomProviderBody(body) {
|
|
140
|
+
if (!body || typeof body !== "object") return { error: "Invalid JSON body" };
|
|
141
|
+
const b = body;
|
|
142
|
+
const name = typeof b.name === "string" ? b.name.trim() : "";
|
|
143
|
+
if (!name) return { error: "Missing required field: name" };
|
|
144
|
+
const url = typeof b.url === "string" ? b.url.trim() : "";
|
|
145
|
+
if (!url) return { error: "Missing required field: url" };
|
|
146
|
+
try {
|
|
147
|
+
const parsed = new URL(url);
|
|
148
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
149
|
+
return { error: "url must be an http(s) URL" };
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
return { error: "url must be a valid URL" };
|
|
153
|
+
}
|
|
154
|
+
const apiKey = typeof b.apiKey === "string" && b.apiKey.trim() ? b.apiKey.trim() : void 0;
|
|
155
|
+
const models = Array.isArray(b.models) ? b.models.filter((m) => typeof m === "string" && m.trim().length > 0).map((m) => m.trim()) : [];
|
|
156
|
+
return { name, url, apiKey, models };
|
|
157
|
+
}
|
|
158
|
+
async function buildProviderAccess({
|
|
159
|
+
controller,
|
|
160
|
+
authStorage,
|
|
161
|
+
tenantCredentials
|
|
162
|
+
}) {
|
|
163
|
+
const models = await controller.listAvailableModels();
|
|
164
|
+
const hasModelKey = (provider) => models.some((m) => m.provider === provider && m.hasApiKey);
|
|
165
|
+
const accessLevel = (provider) => {
|
|
166
|
+
const authProviderId = getAuthProviderId(provider);
|
|
167
|
+
if (tenantCredentials) {
|
|
168
|
+
const userRec = tenantCredentials.find((r) => r.scope === "user" && r.provider === authProviderId);
|
|
169
|
+
const orgRec = tenantCredentials.find((r) => r.scope === "org" && r.provider === authProviderId);
|
|
170
|
+
const credential = userRec?.credential ?? orgRec?.credential;
|
|
171
|
+
if (credential?.type === "oauth") return "oauth";
|
|
172
|
+
if (credential?.type === "api_key" && credential.key.trim().length > 0) return "apikey";
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const oauthCredential = authStorage?.get(authProviderId);
|
|
176
|
+
if (oauthCredential?.type === "oauth") return "oauth";
|
|
177
|
+
if (authStorage?.hasStoredApiKey(provider)) return "apikey";
|
|
178
|
+
const directCredential = authStorage?.get(provider);
|
|
179
|
+
if (directCredential?.type === "api_key" && directCredential.key.trim().length > 0) return "apikey";
|
|
180
|
+
return hasModelKey(provider) ? "apikey" : false;
|
|
181
|
+
};
|
|
182
|
+
const access = {
|
|
183
|
+
anthropic: accessLevel("anthropic"),
|
|
184
|
+
openai: accessLevel("openai"),
|
|
185
|
+
cerebras: accessLevel("cerebras"),
|
|
186
|
+
google: accessLevel("google"),
|
|
187
|
+
deepseek: accessLevel("deepseek"),
|
|
188
|
+
"github-copilot": accessLevel("github-copilot")
|
|
189
|
+
};
|
|
190
|
+
const seen = new Set(Object.keys(access));
|
|
191
|
+
for (const m of models) {
|
|
192
|
+
if (!seen.has(m.provider)) {
|
|
193
|
+
access[m.provider] = accessLevel(m.provider);
|
|
194
|
+
seen.add(m.provider);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return access;
|
|
198
|
+
}
|
|
199
|
+
function canUseModelProvider(access, provider) {
|
|
200
|
+
return Boolean(access[provider]);
|
|
201
|
+
}
|
|
202
|
+
async function getTenantModelPacksStorage(modelPacks) {
|
|
203
|
+
if (!modelPacks) return void 0;
|
|
204
|
+
try {
|
|
205
|
+
await modelPacks.ensureReady();
|
|
206
|
+
} catch {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
return modelPacks;
|
|
210
|
+
}
|
|
211
|
+
async function resolvePackContext({
|
|
212
|
+
c,
|
|
213
|
+
auth,
|
|
214
|
+
modelPacks
|
|
215
|
+
}) {
|
|
216
|
+
await auth.ensureUser(c);
|
|
217
|
+
const tenant = auth.tenant(c);
|
|
218
|
+
if (!tenant) {
|
|
219
|
+
if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
|
|
220
|
+
return { mode: "local" };
|
|
221
|
+
}
|
|
222
|
+
const storage = await getTenantModelPacksStorage(modelPacks);
|
|
223
|
+
if (!storage) {
|
|
224
|
+
return {
|
|
225
|
+
response: c.json(
|
|
226
|
+
{
|
|
227
|
+
error: "model_packs_unavailable",
|
|
228
|
+
message: "Model pack storage is unavailable \u2014 the app database is not configured or failed to start."
|
|
229
|
+
},
|
|
230
|
+
503
|
|
231
|
+
)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
|
|
235
|
+
}
|
|
236
|
+
function recordToModePack(record) {
|
|
237
|
+
return { id: `custom:${record.id}`, name: record.name, description: "Saved custom pack", models: record.models };
|
|
238
|
+
}
|
|
239
|
+
async function listModelPacks({
|
|
240
|
+
controller,
|
|
241
|
+
authStorage,
|
|
242
|
+
tenantCredentials,
|
|
243
|
+
packContext,
|
|
244
|
+
activePackId
|
|
245
|
+
}) {
|
|
246
|
+
const access = await buildProviderAccess({ controller, authStorage, tenantCredentials });
|
|
247
|
+
const packs = packContext.mode === "local" ? getAvailableModePacks(access, loadSettings().customModelPacks) : [
|
|
248
|
+
...getAvailableModePacks(access),
|
|
249
|
+
...(await packContext.storage.list({ orgId: packContext.orgId })).map(recordToModePack)
|
|
250
|
+
];
|
|
251
|
+
return packs.filter((p) => p.id !== "custom").map((p) => ({
|
|
252
|
+
...p,
|
|
253
|
+
custom: p.id.startsWith("custom:"),
|
|
254
|
+
active: activePackId != null && p.id === activePackId
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
async function resolveActivePackId(session) {
|
|
258
|
+
if (!session) return null;
|
|
259
|
+
const threadId = session.thread.getId();
|
|
260
|
+
if (!threadId) return null;
|
|
261
|
+
const thread = (await session.thread.list()).find((t) => t.id === threadId);
|
|
262
|
+
const value = thread?.metadata?.[THREAD_ACTIVE_MODEL_PACK_ID_KEY];
|
|
263
|
+
return typeof value === "string" ? value : null;
|
|
264
|
+
}
|
|
265
|
+
async function applyPackToSession({
|
|
266
|
+
controller,
|
|
267
|
+
session,
|
|
268
|
+
pack
|
|
269
|
+
}) {
|
|
270
|
+
const modes = controller.listModes?.() ?? [];
|
|
271
|
+
const packModels = pack.models;
|
|
272
|
+
for (const mode of modes) {
|
|
273
|
+
const modelId = packModels[mode.id];
|
|
274
|
+
if (modelId) {
|
|
275
|
+
mode.defaultModelId = modelId;
|
|
276
|
+
await session.thread.setSetting({ key: `modeModelId_${mode.id}`, value: modelId });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const currentModeModel = packModels[session.mode.get()];
|
|
280
|
+
if (currentModeModel) {
|
|
281
|
+
await session.model.switch({ modelId: currentModeModel });
|
|
282
|
+
}
|
|
283
|
+
const subagentModeMap = { explore: "fast", plan: "plan", execute: "build" };
|
|
284
|
+
for (const [agentType, modeId] of Object.entries(subagentModeMap)) {
|
|
285
|
+
const saModelId = packModels[modeId];
|
|
286
|
+
if (saModelId) {
|
|
287
|
+
await session.subagents.model.set({ modelId: saModelId, agentType });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
await session.thread.setSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY, value: pack.id });
|
|
291
|
+
}
|
|
292
|
+
var DEFAULT_OBSERVATION_THRESHOLD = 3e4;
|
|
293
|
+
var DEFAULT_REFLECTION_THRESHOLD = 4e4;
|
|
294
|
+
function readOMConfig(session) {
|
|
295
|
+
const state = session.state.get() ?? {};
|
|
296
|
+
const observeAttachments = state.observeAttachments;
|
|
297
|
+
return {
|
|
298
|
+
observerModelId: session.om.observer.modelId() ?? "",
|
|
299
|
+
reflectorModelId: session.om.reflector.modelId() ?? "",
|
|
300
|
+
observationThreshold: session.om.observer.threshold() ?? DEFAULT_OBSERVATION_THRESHOLD,
|
|
301
|
+
reflectionThreshold: session.om.reflector.threshold() ?? DEFAULT_REFLECTION_THRESHOLD,
|
|
302
|
+
observeAttachments: observeAttachments === true || observeAttachments === false ? observeAttachments : "auto"
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function persistOmThreshold({ role, value }) {
|
|
306
|
+
const settings = loadSettings();
|
|
307
|
+
if (role === "observation") settings.models.omObservationThreshold = value;
|
|
308
|
+
else settings.models.omReflectionThreshold = value;
|
|
309
|
+
saveSettings(settings);
|
|
310
|
+
}
|
|
311
|
+
function persistOmRoleOverride({
|
|
312
|
+
role,
|
|
313
|
+
modelId,
|
|
314
|
+
otherRoleCurrentModelId
|
|
315
|
+
}) {
|
|
316
|
+
const settings = loadSettings();
|
|
317
|
+
applyOmRoleOverride(settings, role, modelId, otherRoleCurrentModelId);
|
|
318
|
+
saveSettings(settings);
|
|
319
|
+
}
|
|
320
|
+
var ConfigRoutes = class extends Route {
|
|
321
|
+
routes() {
|
|
322
|
+
const options = this.deps;
|
|
323
|
+
const { controller, authStorage, auth } = options;
|
|
324
|
+
const onCredentialsChanged = options.onCredentialsChanged ?? (() => {
|
|
325
|
+
});
|
|
326
|
+
return [
|
|
327
|
+
registerApiRoute("/web/config/providers", {
|
|
328
|
+
method: "GET",
|
|
329
|
+
requiresAuth: false,
|
|
330
|
+
handler: async (c) => {
|
|
331
|
+
try {
|
|
332
|
+
const tenantCredentials = await listTenantCredentialsForRequest({
|
|
333
|
+
c: loose(c),
|
|
334
|
+
auth,
|
|
335
|
+
credentials: options.modelCredentials
|
|
336
|
+
});
|
|
337
|
+
return c.json({
|
|
338
|
+
providers: await listProviders({
|
|
339
|
+
controller,
|
|
340
|
+
authStorage: tenantCredentials ? void 0 : authStorage,
|
|
341
|
+
tenantCredentials
|
|
342
|
+
})
|
|
343
|
+
});
|
|
344
|
+
} catch (error) {
|
|
345
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}),
|
|
349
|
+
registerApiRoute("/web/config/providers/:provider/key", {
|
|
350
|
+
method: "PUT",
|
|
351
|
+
requiresAuth: false,
|
|
352
|
+
handler: async (c) => {
|
|
353
|
+
const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: options.modelCredentials });
|
|
354
|
+
if ("response" in ctx) return ctx.response;
|
|
355
|
+
const provider = c.req.param("provider");
|
|
356
|
+
let body;
|
|
357
|
+
try {
|
|
358
|
+
body = await c.req.json();
|
|
359
|
+
} catch {
|
|
360
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
361
|
+
}
|
|
362
|
+
const key = typeof body.key === "string" ? body.key.trim() : "";
|
|
363
|
+
if (!key) return c.json({ error: "Missing required field: key" }, 400);
|
|
364
|
+
const envVar = typeof body.envVar === "string" ? body.envVar : void 0;
|
|
365
|
+
const scope = body.scope === "org" ? "org" : "user";
|
|
366
|
+
try {
|
|
367
|
+
if (ctx.mode === "tenant") {
|
|
368
|
+
if (scope === "org" && !await auth.isOrganizationAdmin(loose(c), ctx.orgId)) {
|
|
369
|
+
return c.json({ error: "organization_admin_required" }, 403);
|
|
370
|
+
}
|
|
371
|
+
const tenant = scope === "org" ? { orgId: ctx.orgId } : { orgId: ctx.orgId, userId: ctx.userId };
|
|
372
|
+
await ctx.storage.setCredential(tenant, getAuthProviderId(provider), { type: "api_key", key });
|
|
373
|
+
onCredentialsChanged(tenant);
|
|
374
|
+
const records = await ctx.storage.listCredentials(ctx.orgId, ctx.userId);
|
|
375
|
+
const providers2 = await listProviders({ controller, tenantCredentials: records });
|
|
376
|
+
return c.json({ ok: true, provider: providers2.find((p) => p.provider === provider) });
|
|
377
|
+
}
|
|
378
|
+
if (!authStorage) return c.json({ error: "Credential storage is not available" }, 503);
|
|
379
|
+
authStorage.setStoredApiKey(provider, key, envVar);
|
|
380
|
+
const providers = await listProviders({ controller, authStorage });
|
|
381
|
+
return c.json({ ok: true, provider: providers.find((p) => p.provider === provider) });
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}),
|
|
387
|
+
registerApiRoute("/web/config/providers/:provider/key", {
|
|
388
|
+
method: "DELETE",
|
|
389
|
+
requiresAuth: false,
|
|
390
|
+
handler: async (c) => {
|
|
391
|
+
const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: options.modelCredentials });
|
|
392
|
+
if ("response" in ctx) return ctx.response;
|
|
393
|
+
const provider = c.req.param("provider");
|
|
394
|
+
const scope = c.req.query("scope") === "org" ? "org" : "user";
|
|
395
|
+
try {
|
|
396
|
+
if (ctx.mode === "tenant") {
|
|
397
|
+
if (scope === "org" && !await auth.isOrganizationAdmin(loose(c), ctx.orgId)) {
|
|
398
|
+
return c.json({ error: "organization_admin_required" }, 403);
|
|
399
|
+
}
|
|
400
|
+
const tenant = scope === "org" ? { orgId: ctx.orgId } : { orgId: ctx.orgId, userId: ctx.userId };
|
|
401
|
+
await ctx.storage.removeCredential(tenant, getAuthProviderId(provider));
|
|
402
|
+
onCredentialsChanged(tenant);
|
|
403
|
+
const records = await ctx.storage.listCredentials(ctx.orgId, ctx.userId);
|
|
404
|
+
const providers2 = await listProviders({ controller, tenantCredentials: records });
|
|
405
|
+
return c.json({ ok: true, provider: providers2.find((p) => p.provider === provider) });
|
|
406
|
+
}
|
|
407
|
+
if (!authStorage) return c.json({ error: "Credential storage is not available" }, 503);
|
|
408
|
+
authStorage.remove(`apikey:${provider}`);
|
|
409
|
+
const providers = await listProviders({ controller, authStorage });
|
|
410
|
+
return c.json({ ok: true, provider: providers.find((p) => p.provider === provider) });
|
|
411
|
+
} catch (error) {
|
|
412
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}),
|
|
416
|
+
// ── Custom providers (OpenAI-compatible endpoints) ──────────────────────
|
|
417
|
+
// Mirrors the TUI's /custom-providers command. Backed by GlobalSettings
|
|
418
|
+
// (settings.json), not session state — these are user-global definitions.
|
|
419
|
+
registerApiRoute("/web/config/custom-providers", {
|
|
420
|
+
method: "GET",
|
|
421
|
+
requiresAuth: false,
|
|
422
|
+
handler: (c) => {
|
|
423
|
+
try {
|
|
424
|
+
return c.json({ providers: listCustomProviders() });
|
|
425
|
+
} catch (error) {
|
|
426
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}),
|
|
430
|
+
registerApiRoute("/web/config/custom-providers", {
|
|
431
|
+
method: "POST",
|
|
432
|
+
requiresAuth: false,
|
|
433
|
+
handler: async (c) => {
|
|
434
|
+
let body;
|
|
435
|
+
try {
|
|
436
|
+
body = await c.req.json();
|
|
437
|
+
} catch {
|
|
438
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
439
|
+
}
|
|
440
|
+
const parsed = parseCustomProviderBody(body);
|
|
441
|
+
if ("error" in parsed) return c.json({ error: parsed.error }, 400);
|
|
442
|
+
const previousId = body && typeof body === "object" && typeof body.previousId === "string" ? body.previousId : void 0;
|
|
443
|
+
try {
|
|
444
|
+
const settings = loadSettings();
|
|
445
|
+
upsertCustomProviderInSettings(settings, parsed, previousId);
|
|
446
|
+
saveSettings(settings);
|
|
447
|
+
const id = getCustomProviderId(parsed.name);
|
|
448
|
+
return c.json({ ok: true, provider: listCustomProviders().find((p) => p.id === id) });
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}),
|
|
454
|
+
registerApiRoute("/web/config/custom-providers/:id", {
|
|
455
|
+
method: "DELETE",
|
|
456
|
+
requiresAuth: false,
|
|
457
|
+
handler: (c) => {
|
|
458
|
+
const id = c.req.param("id");
|
|
459
|
+
try {
|
|
460
|
+
const settings = loadSettings();
|
|
461
|
+
removeCustomProviderFromSettings(settings, id);
|
|
462
|
+
saveSettings(settings);
|
|
463
|
+
return c.json({ ok: true });
|
|
464
|
+
} catch (error) {
|
|
465
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}),
|
|
469
|
+
// ── Available models ────────────────────────────────────────────────────
|
|
470
|
+
// Session-independent model catalog for settings pickers (Factory default
|
|
471
|
+
// model, pack editors). Only models whose provider has a credential are
|
|
472
|
+
// returned — the same filter the session-scoped hook applies client-side.
|
|
473
|
+
registerApiRoute("/web/config/models", {
|
|
474
|
+
method: "GET",
|
|
475
|
+
requiresAuth: false,
|
|
476
|
+
handler: async (c) => {
|
|
477
|
+
try {
|
|
478
|
+
const tenantCredentials = await listTenantCredentialsForRequest({
|
|
479
|
+
c: loose(c),
|
|
480
|
+
auth,
|
|
481
|
+
credentials: options.modelCredentials
|
|
482
|
+
});
|
|
483
|
+
const [models, access] = await Promise.all([
|
|
484
|
+
controller.listAvailableModels(),
|
|
485
|
+
buildProviderAccess({
|
|
486
|
+
controller,
|
|
487
|
+
authStorage: tenantCredentials ? void 0 : authStorage,
|
|
488
|
+
tenantCredentials
|
|
489
|
+
})
|
|
490
|
+
]);
|
|
491
|
+
return c.json({
|
|
492
|
+
models: models.filter((m) => canUseModelProvider(access, m.provider) && typeof m.id === "string").map((m) => ({ id: m.id, provider: m.provider, modelName: m.modelName, hasApiKey: true })).sort(
|
|
493
|
+
(a, b) => a.provider === b.provider ? a.id.localeCompare(b.id) : a.provider.localeCompare(b.provider)
|
|
494
|
+
)
|
|
495
|
+
});
|
|
496
|
+
} catch (error) {
|
|
497
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}),
|
|
501
|
+
// ── Model packs ─────────────────────────────────────────────────────────
|
|
502
|
+
// Mirrors the TUI's /models-pack command. Custom-pack CRUD is DB-backed in
|
|
503
|
+
// tenant mode (model-packs storage domain, org-scoped) and settings.json in
|
|
504
|
+
// local mode; activation is session-scoped and resolves the session from
|
|
505
|
+
// the controller registry by resourceId.
|
|
506
|
+
registerApiRoute("/web/config/model-packs", {
|
|
507
|
+
method: "GET",
|
|
508
|
+
requiresAuth: false,
|
|
509
|
+
handler: async (c) => {
|
|
510
|
+
const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });
|
|
511
|
+
if ("response" in packContext) return packContext.response;
|
|
512
|
+
const resourceId = c.req.query("resourceId");
|
|
513
|
+
const scope = c.req.query("scope") || void 0;
|
|
514
|
+
try {
|
|
515
|
+
const session = resourceId ? await controller.getSessionByResource?.(resourceId, scope) : void 0;
|
|
516
|
+
const activePackId = await resolveActivePackId(session);
|
|
517
|
+
const tenantCredentials = await listTenantCredentialsForRequest({
|
|
518
|
+
c: loose(c),
|
|
519
|
+
auth,
|
|
520
|
+
credentials: options.modelCredentials
|
|
521
|
+
});
|
|
522
|
+
return c.json({
|
|
523
|
+
packs: await listModelPacks({
|
|
524
|
+
controller,
|
|
525
|
+
authStorage: tenantCredentials ? void 0 : authStorage,
|
|
526
|
+
tenantCredentials,
|
|
527
|
+
packContext,
|
|
528
|
+
activePackId
|
|
529
|
+
}),
|
|
530
|
+
activePackId
|
|
531
|
+
});
|
|
532
|
+
} catch (error) {
|
|
533
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}),
|
|
537
|
+
registerApiRoute("/web/config/model-packs", {
|
|
538
|
+
method: "POST",
|
|
539
|
+
requiresAuth: false,
|
|
540
|
+
handler: async (c) => {
|
|
541
|
+
const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });
|
|
542
|
+
if ("response" in packContext) return packContext.response;
|
|
543
|
+
let body;
|
|
544
|
+
try {
|
|
545
|
+
body = await c.req.json();
|
|
546
|
+
} catch {
|
|
547
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
548
|
+
}
|
|
549
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
550
|
+
if (!name) return c.json({ error: "Missing required field: name" }, 400);
|
|
551
|
+
const m = body.models ?? {};
|
|
552
|
+
const build = typeof m.build === "string" ? m.build.trim() : "";
|
|
553
|
+
const plan = typeof m.plan === "string" ? m.plan.trim() : "";
|
|
554
|
+
const fast = typeof m.fast === "string" ? m.fast.trim() : "";
|
|
555
|
+
if (!build || !plan || !fast) {
|
|
556
|
+
return c.json({ error: "models.build, models.plan and models.fast are required" }, 400);
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
if (packContext.mode === "tenant") {
|
|
560
|
+
const record = await packContext.storage.upsert({
|
|
561
|
+
orgId: packContext.orgId,
|
|
562
|
+
userId: packContext.userId,
|
|
563
|
+
input: { name, models: { build, plan, fast } }
|
|
564
|
+
});
|
|
565
|
+
return c.json({ ok: true, pack: recordToModePack(record) });
|
|
566
|
+
}
|
|
567
|
+
const settings = loadSettings();
|
|
568
|
+
const entry = { name, models: { build, plan, fast }, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
569
|
+
const idx = settings.customModelPacks.findIndex((p) => p.name === name);
|
|
570
|
+
if (idx >= 0) settings.customModelPacks[idx] = entry;
|
|
571
|
+
else settings.customModelPacks.push(entry);
|
|
572
|
+
saveSettings(settings);
|
|
573
|
+
return c.json({ ok: true, pack: { id: `custom:${name}`, name, models: { build, plan, fast } } });
|
|
574
|
+
} catch (error) {
|
|
575
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}),
|
|
579
|
+
registerApiRoute("/web/config/model-packs/:id", {
|
|
580
|
+
method: "DELETE",
|
|
581
|
+
requiresAuth: false,
|
|
582
|
+
handler: async (c) => {
|
|
583
|
+
const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });
|
|
584
|
+
if ("response" in packContext) return packContext.response;
|
|
585
|
+
const id = decodeURIComponent(c.req.param("id"));
|
|
586
|
+
try {
|
|
587
|
+
if (packContext.mode === "tenant") {
|
|
588
|
+
const recordId = id.startsWith("custom:") ? id.slice("custom:".length) : id;
|
|
589
|
+
const deleted = await packContext.storage.delete({ orgId: packContext.orgId, id: recordId });
|
|
590
|
+
return deleted ? c.json({ ok: true }) : c.json({ error: `Unknown pack "${id}"` }, 404);
|
|
591
|
+
}
|
|
592
|
+
const settings = loadSettings();
|
|
593
|
+
removeCustomPackFromSettings(settings, id);
|
|
594
|
+
saveSettings(settings);
|
|
595
|
+
return c.json({ ok: true });
|
|
596
|
+
} catch (error) {
|
|
597
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}),
|
|
601
|
+
registerApiRoute("/web/config/model-packs/:id/activate", {
|
|
602
|
+
method: "POST",
|
|
603
|
+
requiresAuth: false,
|
|
604
|
+
handler: async (c) => {
|
|
605
|
+
const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });
|
|
606
|
+
if ("response" in packContext) return packContext.response;
|
|
607
|
+
const id = decodeURIComponent(c.req.param("id"));
|
|
608
|
+
let body;
|
|
609
|
+
try {
|
|
610
|
+
body = await c.req.json();
|
|
611
|
+
} catch {
|
|
612
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
613
|
+
}
|
|
614
|
+
const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
|
|
615
|
+
const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
|
|
616
|
+
if (!resourceId) return c.json({ error: "Missing required field: resourceId" }, 400);
|
|
617
|
+
try {
|
|
618
|
+
const session = await controller.getSessionByResource?.(resourceId, scope);
|
|
619
|
+
if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
|
|
620
|
+
const tenantCredentials = await listTenantCredentialsForRequest({
|
|
621
|
+
c: loose(c),
|
|
622
|
+
auth,
|
|
623
|
+
credentials: options.modelCredentials
|
|
624
|
+
});
|
|
625
|
+
const packs = await listModelPacks({
|
|
626
|
+
controller,
|
|
627
|
+
authStorage: tenantCredentials ? void 0 : authStorage,
|
|
628
|
+
tenantCredentials,
|
|
629
|
+
packContext
|
|
630
|
+
});
|
|
631
|
+
const pack = packs.find((p) => p.id === id);
|
|
632
|
+
if (!pack) return c.json({ error: `Unknown pack "${id}"` }, 404);
|
|
633
|
+
await applyPackToSession({ controller, session, pack });
|
|
634
|
+
return c.json({ ok: true, activePackId: pack.id });
|
|
635
|
+
} catch (error) {
|
|
636
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}),
|
|
640
|
+
// ── Observational memory ──────────────────────────────────────────────────
|
|
641
|
+
// Mirrors the TUI's /om command. All five knobs are session-scoped (resolved
|
|
642
|
+
// from the session, persisted to its state + thread setting) plus written to
|
|
643
|
+
// GlobalSettings so the choice survives restarts and stays in sync with the TUI.
|
|
644
|
+
registerApiRoute("/web/config/om", {
|
|
645
|
+
method: "GET",
|
|
646
|
+
requiresAuth: false,
|
|
647
|
+
handler: async (c) => {
|
|
648
|
+
const resourceId = c.req.query("resourceId");
|
|
649
|
+
const scope = c.req.query("scope") || void 0;
|
|
650
|
+
if (!resourceId) return c.json({ error: "Missing required query param: resourceId" }, 400);
|
|
651
|
+
try {
|
|
652
|
+
const session = await controller.getSessionByResource?.(resourceId, scope);
|
|
653
|
+
if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
|
|
654
|
+
return c.json({ config: readOMConfig(session) });
|
|
655
|
+
} catch (error) {
|
|
656
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}),
|
|
660
|
+
registerApiRoute("/web/config/om/:role/model", {
|
|
661
|
+
method: "PUT",
|
|
662
|
+
requiresAuth: false,
|
|
663
|
+
handler: async (c) => {
|
|
664
|
+
const role = c.req.param("role");
|
|
665
|
+
if (role !== "observer" && role !== "reflector") {
|
|
666
|
+
return c.json({ error: `Unknown OM role "${role}"` }, 400);
|
|
667
|
+
}
|
|
668
|
+
let body;
|
|
669
|
+
try {
|
|
670
|
+
body = await c.req.json();
|
|
671
|
+
} catch {
|
|
672
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
673
|
+
}
|
|
674
|
+
const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
|
|
675
|
+
const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
|
|
676
|
+
const modelId = typeof body.modelId === "string" ? body.modelId.trim() : "";
|
|
677
|
+
if (!resourceId) return c.json({ error: "Missing required field: resourceId" }, 400);
|
|
678
|
+
if (!modelId) return c.json({ error: "Missing required field: modelId" }, 400);
|
|
679
|
+
try {
|
|
680
|
+
const session = await controller.getSessionByResource?.(resourceId, scope);
|
|
681
|
+
if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
|
|
682
|
+
const otherRole = role === "observer" ? session.om.reflector : session.om.observer;
|
|
683
|
+
const otherRoleCurrentModelId = otherRole.modelId() ?? null;
|
|
684
|
+
await session.om[role].switchModel({ modelId });
|
|
685
|
+
persistOmRoleOverride({ role, modelId, otherRoleCurrentModelId });
|
|
686
|
+
return c.json({ ok: true, config: readOMConfig(session) });
|
|
687
|
+
} catch (error) {
|
|
688
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}),
|
|
692
|
+
registerApiRoute("/web/config/om/thresholds", {
|
|
693
|
+
method: "PUT",
|
|
694
|
+
requiresAuth: false,
|
|
695
|
+
handler: async (c) => {
|
|
696
|
+
let body;
|
|
697
|
+
try {
|
|
698
|
+
body = await c.req.json();
|
|
699
|
+
} catch {
|
|
700
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
701
|
+
}
|
|
702
|
+
const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
|
|
703
|
+
const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
|
|
704
|
+
if (!resourceId) return c.json({ error: "Missing required field: resourceId" }, 400);
|
|
705
|
+
const observation = typeof body.observationThreshold === "number" && body.observationThreshold > 0 ? Math.round(body.observationThreshold) : void 0;
|
|
706
|
+
const reflection = typeof body.reflectionThreshold === "number" && body.reflectionThreshold > 0 ? Math.round(body.reflectionThreshold) : void 0;
|
|
707
|
+
if (observation === void 0 && reflection === void 0) {
|
|
708
|
+
return c.json({ error: "Provide observationThreshold and/or reflectionThreshold (positive numbers)" }, 400);
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
const session = await controller.getSessionByResource?.(resourceId, scope);
|
|
712
|
+
if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
|
|
713
|
+
if (observation !== void 0) {
|
|
714
|
+
await session.state.set({ observationThreshold: observation });
|
|
715
|
+
await session.thread.setSetting({ key: "observationThreshold", value: observation });
|
|
716
|
+
persistOmThreshold({ role: "observation", value: observation });
|
|
717
|
+
}
|
|
718
|
+
if (reflection !== void 0) {
|
|
719
|
+
await session.state.set({ reflectionThreshold: reflection });
|
|
720
|
+
await session.thread.setSetting({ key: "reflectionThreshold", value: reflection });
|
|
721
|
+
persistOmThreshold({ role: "reflection", value: reflection });
|
|
722
|
+
}
|
|
723
|
+
return c.json({ ok: true, config: readOMConfig(session) });
|
|
724
|
+
} catch (error) {
|
|
725
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}),
|
|
729
|
+
registerApiRoute("/web/config/om/observe-attachments", {
|
|
730
|
+
method: "PUT",
|
|
731
|
+
requiresAuth: false,
|
|
732
|
+
handler: async (c) => {
|
|
733
|
+
let body;
|
|
734
|
+
try {
|
|
735
|
+
body = await c.req.json();
|
|
736
|
+
} catch {
|
|
737
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
738
|
+
}
|
|
739
|
+
const resourceId = typeof body.resourceId === "string" ? body.resourceId : "";
|
|
740
|
+
const scope = typeof body.scope === "string" && body.scope ? body.scope : void 0;
|
|
741
|
+
if (!resourceId) return c.json({ error: "Missing required field: resourceId" }, 400);
|
|
742
|
+
const raw = body.value;
|
|
743
|
+
const value = raw === "auto" || raw === true || raw === false ? raw : "auto";
|
|
744
|
+
if (raw !== "auto" && raw !== true && raw !== false) {
|
|
745
|
+
return c.json({ error: "value must be 'auto', true, or false" }, 400);
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
const session = await controller.getSessionByResource?.(resourceId, scope);
|
|
749
|
+
if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
|
|
750
|
+
await session.state.set({ observeAttachments: value });
|
|
751
|
+
await session.thread.setSetting({ key: "observeAttachments", value });
|
|
752
|
+
persistOmObserveAttachments(value);
|
|
753
|
+
return c.json({ ok: true, config: readOMConfig(session) });
|
|
754
|
+
} catch (error) {
|
|
755
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
})
|
|
759
|
+
];
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
export {
|
|
763
|
+
ConfigRoutes,
|
|
764
|
+
buildProviderAccess,
|
|
765
|
+
listCustomProviders,
|
|
766
|
+
listModelPacks,
|
|
767
|
+
listProviders,
|
|
768
|
+
readOMConfig
|
|
769
|
+
};
|
|
770
|
+
//# sourceMappingURL=config.js.map
|