@desplega.ai/agent-swarm 1.113.0 → 1.115.0
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/README.md +2 -2
- package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
- package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
- package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
- package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
- package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
- package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
- package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
- package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
- package/dist/cli-0pygq9r1.js +429 -0
- package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
- package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
- package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
- package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
- package/dist/cli-52fg7s7x.js +269 -0
- package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
- package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
- package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
- package/dist/cli-86fdnerz.js +405 -0
- package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
- package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
- package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
- package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
- package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
- package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
- package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
- package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
- package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
- package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
- package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
- package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
- package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
- package/dist/cli-jkhwmmgg.js +136 -0
- package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
- package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
- package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
- package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
- package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
- package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
- package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
- package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
- package/dist/cli-t0vqevnm.js +393 -0
- package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
- package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
- package/dist/cli-wh91zby6.js +187222 -0
- package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
- package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
- package/dist/cli.js +32 -15
- package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
- package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
- package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
- package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
- package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
- package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
- package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
- package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
- package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
- package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
- package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
- package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
- package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
- package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
- package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
- package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
- package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
- package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
- package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
- package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
- package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
- package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
- package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
- package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
- package/dist/rbac-roles-kfesjy9y.js +28 -0
- package/dist/rbac-roles-mwcwhbxc.js +283 -0
- package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
- package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
- package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
- package/openapi.json +1327 -20
- package/package.json +3 -3
- package/src/agentmail/handlers.ts +16 -6
- package/src/agentmail/templates.ts +25 -5
- package/src/be/audit-user.ts +28 -4
- package/src/be/db-queries/oauth.ts +42 -0
- package/src/be/db.ts +314 -28
- package/src/be/identity.ts +79 -0
- package/src/be/mcp-proxy.ts +132 -0
- package/src/be/migrations/109_rbac_roles.sql +79 -0
- package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
- package/src/be/migrations/112_script_connections_graphql.sql +96 -0
- package/src/be/migrations/113_task_routing_affinity.sql +9 -0
- package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
- package/src/be/modelsdev-cache.json +135 -0
- package/src/be/oauth-credential-bindings.ts +35 -0
- package/src/be/oauth-refresh-sweep.ts +159 -0
- package/src/be/rbac-audit.ts +52 -13
- package/src/be/rbac-roles.ts +424 -0
- package/src/be/script-connections.ts +459 -40
- package/src/be/script-credential-broker.ts +29 -3
- package/src/be/scripts/typecheck.ts +51 -7
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
- package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
- package/src/be/users.ts +24 -0
- package/src/cli.tsx +17 -0
- package/src/github/handlers.ts +10 -9
- package/src/github/templates.ts +45 -9
- package/src/gitlab/handlers.ts +20 -8
- package/src/gitlab/templates.ts +18 -6
- package/src/heartbeat/heartbeat.ts +143 -21
- package/src/http/all-routes.ts +3 -0
- package/src/http/approval-requests.ts +12 -0
- package/src/http/config.ts +44 -10
- package/src/http/core.ts +30 -3
- package/src/http/favorites.ts +1 -5
- package/src/http/index.ts +25 -1
- package/src/http/mcp-oauth.ts +2 -0
- package/src/http/mcp-servers.ts +44 -3
- package/src/http/oauth-generic.ts +105 -0
- package/src/http/poll.ts +20 -3
- package/src/http/script-connection-proxy.ts +116 -0
- package/src/http/script-connections.ts +1657 -0
- package/src/http/scripts.ts +27 -7
- package/src/http/skills.ts +11 -0
- package/src/http/tasks.ts +27 -13
- package/src/http/x.ts +6 -2
- package/src/integrations/kapso/inbound.ts +23 -6
- package/src/jira/sync.ts +111 -17
- package/src/jira/templates.ts +10 -2
- package/src/mcp-client/http-client.ts +194 -0
- package/src/oauth/app-validation.ts +16 -0
- package/src/oauth/ensure-token.ts +69 -14
- package/src/oauth/mcp-wrapper.ts +15 -0
- package/src/oauth/wrapper.ts +48 -20
- package/src/prompts/session-templates.ts +3 -3
- package/src/providers/codex-models.ts +100 -30
- package/src/providers/modelsdev-reasoning.json +51 -0
- package/src/providers/pi-mono-mcp-client.ts +2 -133
- package/src/providers/reasoning-effort.ts +11 -5
- package/src/rbac/admission.ts +68 -0
- package/src/rbac/index.ts +2 -0
- package/src/rbac/legacy-policy.ts +12 -0
- package/src/rbac/permissions.ts +48 -0
- package/src/scheduler/scheduler.ts +9 -1
- package/src/scripts-runtime/api-client.ts +151 -19
- package/src/scripts-runtime/api-types.ts +38 -6
- package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
- package/src/scripts-runtime/credential-broker/types.ts +8 -0
- package/src/scripts-runtime/ctx.ts +11 -1
- package/src/scripts-runtime/eval-harness.ts +5 -1
- package/src/scripts-runtime/executors/types.ts +2 -1
- package/src/scripts-runtime/loader.ts +3 -1
- package/src/scripts-runtime/mcp-client.ts +87 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +6 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
- package/src/server-user.ts +114 -59
- package/src/server.ts +12 -0
- package/src/slack/assistant.ts +14 -6
- package/src/slack/enrich.ts +17 -0
- package/src/slack/handlers.ts +8 -22
- package/src/slack/responses.ts +91 -1
- package/src/slack/thread-buffer.ts +6 -3
- package/src/slack/watcher.ts +9 -6
- package/src/tasks/worker-follow-up.ts +100 -3
- package/src/tests/agentmail-handlers.test.ts +32 -3
- package/src/tests/approval-requests.test.ts +61 -0
- package/src/tests/audit-user.test.ts +47 -1
- package/src/tests/codex-adapter.test.ts +46 -23
- package/src/tests/credential-broker.test.ts +5 -2
- package/src/tests/delete-page-tool.test.ts +197 -0
- package/src/tests/github-handlers.test.ts +28 -2
- package/src/tests/gitlab-handlers.test.ts +125 -0
- package/src/tests/heartbeat.test.ts +48 -3
- package/src/tests/identity.test.ts +117 -0
- package/src/tests/jira-sync.test.ts +99 -1
- package/src/tests/kapso-inbound.test.ts +7 -0
- package/src/tests/mcp-tools-user.test.ts +60 -6
- package/src/tests/model-control.test.ts +8 -2
- package/src/tests/oauth-credential-bindings.test.ts +490 -0
- package/src/tests/oauth-refresh-sweep.test.ts +160 -0
- package/src/tests/pool-affinity.test.ts +401 -0
- package/src/tests/prompt-template-remaining.test.ts +2 -2
- package/src/tests/rbac-admission-e2e.test.ts +386 -0
- package/src/tests/rbac-admission.test.ts +492 -0
- package/src/tests/rbac-e2e-helpers.ts +3 -0
- package/src/tests/rbac-engine.test.ts +50 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
- package/src/tests/rbac-roles.test.ts +328 -0
- package/src/tests/reasoning-effort.test.ts +15 -2
- package/src/tests/schedule-target-type.test.ts +50 -0
- package/src/tests/scheduled-tasks.test.ts +40 -0
- package/src/tests/script-apis-mcp.test.ts +37 -13
- package/src/tests/script-connections-http.test.ts +1158 -0
- package/src/tests/script-connections-mcp.test.ts +894 -0
- package/src/tests/script-connections.test.ts +754 -6
- package/src/tests/scripts-external-api.test.ts +49 -1
- package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
- package/src/tests/scripts-runtime.test.ts +291 -0
- package/src/tests/seed-scripts.test.ts +36 -0
- package/src/tests/slack-identity-resolution.test.ts +37 -3
- package/src/tests/slack-inline-output.test.ts +65 -0
- package/src/tests/slack-thread-buffer.test.ts +28 -0
- package/src/tests/tool-annotations.test.ts +1 -0
- package/src/tests/user-token-rest-auth.test.ts +140 -1
- package/src/tests/workflow-swarm-script.test.ts +44 -0
- package/src/tests/workflow-triggers-v2.test.ts +300 -1
- package/src/tools/credential-bindings/tool.ts +277 -18
- package/src/tools/delete-page.ts +145 -0
- package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
- package/src/tools/request-human-input.ts +2 -0
- package/src/tools/resolve-user.ts +77 -27
- package/src/tools/script-apis.ts +62 -3
- package/src/tools/script-connections/tool.ts +275 -31
- package/src/tools/send-task.ts +15 -1
- package/src/tools/task-action.ts +34 -2
- package/src/tools/templates.ts +53 -1
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +2 -0
- package/src/tools/workflows/create-workflow.ts +8 -1
- package/src/tools/workflows/update-workflow.ts +11 -2
- package/src/types.ts +90 -11
- package/src/utils/scoped-resource.ts +25 -0
- package/src/workflows/executors/human-in-the-loop.ts +1 -0
- package/src/workflows/executors/swarm-script.ts +7 -1
- package/src/workflows/triggers.ts +146 -18
- package/templates/skills/swarm-scripts/content.md +33 -8
- package/dist/cli-bgef578c.js +0 -771
- package/dist/cli-c9mtm09b.js +0 -43
- package/dist/cli-ceewqajw.js +0 -1698
- package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
- package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
- package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
- package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
- package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
- package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
- package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
- package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
- package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { getDb, getSwarmConfigs } from "@/be/db";
|
|
2
|
+
import { assertUrlSafe, publicEndpointSsrfOptions } from "@/oauth/mcp-wrapper";
|
|
2
3
|
import type {
|
|
3
4
|
ScriptApiConnectionDescriptor,
|
|
4
5
|
ScriptApiJsonSchema,
|
|
5
6
|
ScriptApiJsonValue,
|
|
6
7
|
ScriptApiOperationDescriptor,
|
|
8
|
+
ScriptMcpConnectionDescriptor,
|
|
9
|
+
ScriptMcpToolDescriptor,
|
|
7
10
|
} from "@/scripts-runtime/api-types";
|
|
8
11
|
import {
|
|
9
12
|
CREDENTIAL_BINDINGS_CONFIG_KEY,
|
|
10
13
|
type CredentialBinding,
|
|
11
14
|
normalizeCredentialBindingsDocument,
|
|
12
15
|
} from "@/scripts-runtime/credential-broker";
|
|
16
|
+
import { listMcpServerTools } from "./mcp-proxy";
|
|
13
17
|
|
|
14
18
|
export type ScriptConnectionScope = "global" | "agent" | "repo";
|
|
15
|
-
export type ScriptConnectionKind = "raw" | "openapi" | "mcp";
|
|
19
|
+
export type ScriptConnectionKind = "raw" | "openapi" | "mcp" | "graphql";
|
|
16
20
|
|
|
17
21
|
export type ScriptCredentialBindingRecord = CredentialBinding & {
|
|
18
22
|
id: string;
|
|
@@ -60,6 +64,8 @@ type BindingRow = {
|
|
|
60
64
|
scope: string;
|
|
61
65
|
scope_id: string | null;
|
|
62
66
|
active: number;
|
|
67
|
+
auth_kind: string;
|
|
68
|
+
oauth_provider: string | null;
|
|
63
69
|
source: string;
|
|
64
70
|
created_at: string;
|
|
65
71
|
updated_at: string;
|
|
@@ -114,6 +120,8 @@ function bindingFromRow(row: BindingRow): ScriptCredentialBindingRecord {
|
|
|
114
120
|
scope: row.scope as ScriptConnectionScope,
|
|
115
121
|
scopeId: row.scope_id,
|
|
116
122
|
active: row.active === 1,
|
|
123
|
+
authKind: row.auth_kind === "oauth" ? "oauth" : "config",
|
|
124
|
+
oauthProvider: row.oauth_provider ?? undefined,
|
|
117
125
|
source: row.source as ScriptCredentialBindingRecord["source"],
|
|
118
126
|
createdAt: row.created_at,
|
|
119
127
|
updatedAt: row.updated_at,
|
|
@@ -234,6 +242,8 @@ export function upsertCredentialBinding(data: {
|
|
|
234
242
|
scope?: ScriptConnectionScope;
|
|
235
243
|
scopeId?: string | null;
|
|
236
244
|
active?: boolean;
|
|
245
|
+
authKind?: CredentialBinding["authKind"];
|
|
246
|
+
oauthProvider?: string | null;
|
|
237
247
|
source?: "default" | "user" | "migration";
|
|
238
248
|
userId?: string | null;
|
|
239
249
|
}): ScriptCredentialBindingRecord {
|
|
@@ -242,6 +252,11 @@ export function upsertCredentialBinding(data: {
|
|
|
242
252
|
const scope = data.scope ?? "global";
|
|
243
253
|
const scopeId = scope === "global" ? null : (data.scopeId ?? null);
|
|
244
254
|
const active = data.active === false ? 0 : 1;
|
|
255
|
+
const authKind = data.authKind ?? "config";
|
|
256
|
+
if (authKind === "oauth" && !data.oauthProvider) {
|
|
257
|
+
throw new Error("oauthProvider is required for oauth credential bindings");
|
|
258
|
+
}
|
|
259
|
+
const oauthProvider = data.oauthProvider ?? null;
|
|
245
260
|
const source = data.source ?? "user";
|
|
246
261
|
const existing =
|
|
247
262
|
(data.id ? getCredentialBindingById(data.id) : null) ??
|
|
@@ -269,12 +284,15 @@ export function upsertCredentialBinding(data: {
|
|
|
269
284
|
string,
|
|
270
285
|
string | null,
|
|
271
286
|
string,
|
|
287
|
+
string | null,
|
|
288
|
+
string,
|
|
272
289
|
string,
|
|
273
290
|
]
|
|
274
291
|
>(
|
|
275
292
|
`UPDATE script_credential_bindings
|
|
276
293
|
SET config_key = ?, allowed_hosts_json = ?, header_template = ?, query_template = ?,
|
|
277
|
-
scope = ?, scope_id = ?, active = ?,
|
|
294
|
+
scope = ?, scope_id = ?, active = ?, auth_kind = ?, oauth_provider = ?,
|
|
295
|
+
source = ?, updated_by = ?, updated_at = ?
|
|
278
296
|
WHERE id = ? RETURNING *`,
|
|
279
297
|
)
|
|
280
298
|
.get(
|
|
@@ -285,6 +303,8 @@ export function upsertCredentialBinding(data: {
|
|
|
285
303
|
scope,
|
|
286
304
|
scopeId,
|
|
287
305
|
active,
|
|
306
|
+
authKind,
|
|
307
|
+
oauthProvider,
|
|
288
308
|
source,
|
|
289
309
|
data.userId ?? null,
|
|
290
310
|
now,
|
|
@@ -307,6 +327,8 @@ export function upsertCredentialBinding(data: {
|
|
|
307
327
|
string | null,
|
|
308
328
|
number,
|
|
309
329
|
string,
|
|
330
|
+
string | null,
|
|
331
|
+
string,
|
|
310
332
|
string,
|
|
311
333
|
string,
|
|
312
334
|
string | null,
|
|
@@ -315,8 +337,8 @@ export function upsertCredentialBinding(data: {
|
|
|
315
337
|
>(
|
|
316
338
|
`INSERT INTO script_credential_bindings
|
|
317
339
|
(id, config_key, allowed_hosts_json, header_template, query_template, scope, scope_id,
|
|
318
|
-
active, source, created_at, updated_at, created_by, updated_by)
|
|
319
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
340
|
+
active, auth_kind, oauth_provider, source, created_at, updated_at, created_by, updated_by)
|
|
341
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
320
342
|
)
|
|
321
343
|
.get(
|
|
322
344
|
id,
|
|
@@ -327,6 +349,8 @@ export function upsertCredentialBinding(data: {
|
|
|
327
349
|
scope,
|
|
328
350
|
scopeId,
|
|
329
351
|
active,
|
|
352
|
+
authKind,
|
|
353
|
+
oauthProvider,
|
|
330
354
|
source,
|
|
331
355
|
now,
|
|
332
356
|
now,
|
|
@@ -382,6 +406,7 @@ export function listScriptConnections(context?: {
|
|
|
382
406
|
repoId?: string;
|
|
383
407
|
kind?: ScriptConnectionKind;
|
|
384
408
|
includeDisabled?: boolean;
|
|
409
|
+
allScopes?: boolean;
|
|
385
410
|
}): ScriptConnectionRecord[] {
|
|
386
411
|
const rows = getDb()
|
|
387
412
|
.prepare<ConnectionRow, []>("SELECT * FROM script_connections ORDER BY slug ASC")
|
|
@@ -390,7 +415,19 @@ export function listScriptConnections(context?: {
|
|
|
390
415
|
.map(connectionFromRow)
|
|
391
416
|
.filter((connection) => !context?.kind || connection.kind === context.kind)
|
|
392
417
|
.filter((connection) => context?.includeDisabled || connection.enabled)
|
|
393
|
-
.filter(
|
|
418
|
+
.filter(
|
|
419
|
+
(connection) =>
|
|
420
|
+
!context ||
|
|
421
|
+
context.allScopes === true ||
|
|
422
|
+
applies(connection.scope, connection.scopeId, context),
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function getScriptConnectionById(id: string): ScriptConnectionRecord | null {
|
|
427
|
+
const row = getDb()
|
|
428
|
+
.prepare<ConnectionRow, [string]>("SELECT * FROM script_connections WHERE id = ?")
|
|
429
|
+
.get(id);
|
|
430
|
+
return row ? connectionFromRow(row) : null;
|
|
394
431
|
}
|
|
395
432
|
|
|
396
433
|
function schemaToTs(schema: unknown): string {
|
|
@@ -529,7 +566,10 @@ function extractOperations(
|
|
|
529
566
|
(param) => `${JSON.stringify(param.name)}${param.required ? "" : "?"}: ${param.type}`,
|
|
530
567
|
)
|
|
531
568
|
.join("; ");
|
|
532
|
-
|
|
569
|
+
// Some generated specs (e.g. readme.io exports) declare a requestBody on
|
|
570
|
+
// GET operations; fetch() rejects GET/HEAD bodies, so never generate one.
|
|
571
|
+
const methodAllowsBody = method !== "get" && method !== "head";
|
|
572
|
+
const hasBody = methodAllowsBody && Boolean(jsonContent?.schema || requestBody);
|
|
533
573
|
const requestType = `export type ${typeBase}Args = {${paramsByPlace("path") ? ` path: { ${paramsByPlace("path")} };` : ""}${paramsByPlace("query") ? ` query?: { ${paramsByPlace("query")} };` : ""}${paramsByPlace("header") ? ` header?: { ${paramsByPlace("header")} };` : ""}${hasBody ? ` body: ${bodyType};` : ""}};`;
|
|
534
574
|
const responseDecl = `export type ${typeBase}Response = ${responseType};`;
|
|
535
575
|
typeBlocks.push(requestType, responseDecl);
|
|
@@ -565,13 +605,7 @@ export function buildGeneratedArtifacts(input: {
|
|
|
565
605
|
}): { generatedTypes: string; generatedRuntimeJson: string } {
|
|
566
606
|
const slug = normalizeSlug(input.slug);
|
|
567
607
|
const { operations, types } = extractOperations(input.openapiSpec, slug);
|
|
568
|
-
const credential = input.credentialBinding
|
|
569
|
-
? {
|
|
570
|
-
configKey: input.credentialBinding.configKey,
|
|
571
|
-
headerTemplate: input.credentialBinding.headerTemplate,
|
|
572
|
-
queryTemplate: input.credentialBinding.queryTemplate,
|
|
573
|
-
}
|
|
574
|
-
: null;
|
|
608
|
+
const credential = credentialDescriptor(input.credentialBinding);
|
|
575
609
|
const descriptor: ScriptApiConnectionDescriptor = {
|
|
576
610
|
slug,
|
|
577
611
|
baseUrl: input.baseUrl,
|
|
@@ -581,16 +615,214 @@ export function buildGeneratedArtifacts(input: {
|
|
|
581
615
|
const generatedTypes = [
|
|
582
616
|
types,
|
|
583
617
|
`export interface ${pascal(slug)}Api {`,
|
|
584
|
-
...operations.
|
|
585
|
-
(operation)
|
|
586
|
-
|
|
618
|
+
...operations.flatMap((operation) => [
|
|
619
|
+
` ${operation.name}(args: ${operation.requestType}): Promise<${operation.responseType}>;`,
|
|
620
|
+
` ${operation.name}(args: ${operation.requestType}, options: ScriptApiRawOptions): Promise<ScriptApiRawResult>;`,
|
|
621
|
+
]),
|
|
622
|
+
"}",
|
|
623
|
+
].join("\n");
|
|
624
|
+
return { generatedTypes, generatedRuntimeJson: JSON.stringify(descriptor) };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function credentialDescriptor(binding: ScriptCredentialBindingRecord | null) {
|
|
628
|
+
return binding
|
|
629
|
+
? {
|
|
630
|
+
configKey: binding.configKey,
|
|
631
|
+
headerTemplate: binding.headerTemplate,
|
|
632
|
+
queryTemplate: binding.queryTemplate,
|
|
633
|
+
}
|
|
634
|
+
: null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function buildGraphqlGeneratedArtifacts(input: {
|
|
638
|
+
slug: string;
|
|
639
|
+
baseUrl: string;
|
|
640
|
+
credentialBinding: ScriptCredentialBindingRecord | null;
|
|
641
|
+
}): { generatedTypes: string; generatedRuntimeJson: string } {
|
|
642
|
+
const slug = normalizeSlug(input.slug);
|
|
643
|
+
const descriptor: ScriptApiConnectionDescriptor = {
|
|
644
|
+
slug,
|
|
645
|
+
kind: "graphql",
|
|
646
|
+
baseUrl: input.baseUrl,
|
|
647
|
+
credential: credentialDescriptor(input.credentialBinding),
|
|
648
|
+
};
|
|
649
|
+
const generatedTypes = [
|
|
650
|
+
`export interface ${pascal(slug)}Api {`,
|
|
651
|
+
" graphql<T = JsonValue>(query: string, variables?: Record<string, JsonValue>): Promise<T>;",
|
|
652
|
+
"}",
|
|
653
|
+
].join("\n");
|
|
654
|
+
return { generatedTypes, generatedRuntimeJson: JSON.stringify(descriptor) };
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
type OpenapiSpecFetchResult =
|
|
658
|
+
| {
|
|
659
|
+
status: "fetched";
|
|
660
|
+
spec: unknown;
|
|
661
|
+
specJson: string;
|
|
662
|
+
etag: string | null;
|
|
663
|
+
fetchedAt: string;
|
|
664
|
+
}
|
|
665
|
+
| {
|
|
666
|
+
status: "not_modified";
|
|
667
|
+
etag: string | null;
|
|
668
|
+
fetchedAt: string;
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
let openapiSpecFetchForTesting: typeof fetch | null = null;
|
|
672
|
+
|
|
673
|
+
export function setOpenapiSpecFetchForTesting(fetchImpl: typeof fetch | null): void {
|
|
674
|
+
openapiSpecFetchForTesting = fetchImpl;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function openapiSpecUrlOptions() {
|
|
678
|
+
return publicEndpointSsrfOptions();
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function isRedirectStatus(status: number): boolean {
|
|
682
|
+
return status >= 300 && status < 400 && status !== 304;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function mcpToolMethodName(name: string): string {
|
|
686
|
+
return normalizeSlug(name);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function mcpToolMethodNames<T extends { name: string }>(
|
|
690
|
+
tools: T[],
|
|
691
|
+
): Array<{ tool: T; methodName: string }> {
|
|
692
|
+
const used = new Set<string>();
|
|
693
|
+
const baseCounts = new Map<string, number>();
|
|
694
|
+
return tools.map((tool) => {
|
|
695
|
+
const base = mcpToolMethodName(tool.name);
|
|
696
|
+
const count = baseCounts.get(base) ?? 0;
|
|
697
|
+
baseCounts.set(base, count + 1);
|
|
698
|
+
let methodName = count === 0 ? base : `${base}${count + 1}`;
|
|
699
|
+
let suffix = count + 2;
|
|
700
|
+
while (used.has(methodName)) {
|
|
701
|
+
methodName = `${base}${suffix}`;
|
|
702
|
+
suffix += 1;
|
|
703
|
+
}
|
|
704
|
+
used.add(methodName);
|
|
705
|
+
return { tool, methodName };
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function mcpToolArgsType(inputSchema: unknown): string {
|
|
710
|
+
if (!inputSchema || typeof inputSchema !== "object") return "Record<string, JsonValue>";
|
|
711
|
+
const schema = inputSchema as Record<string, unknown>;
|
|
712
|
+
if (Object.keys(schema).length === 0) return "Record<string, JsonValue>";
|
|
713
|
+
const type = schemaToTs(schema);
|
|
714
|
+
return type === "JsonValue" ? "Record<string, JsonValue>" : type;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export function buildMcpGeneratedArtifacts(input: {
|
|
718
|
+
slug: string;
|
|
719
|
+
connectionId: string;
|
|
720
|
+
tools: Array<{ name: string; description?: string; inputSchema?: Record<string, unknown> }>;
|
|
721
|
+
}): { generatedTypes: string; generatedRuntimeJson: string } {
|
|
722
|
+
const slug = normalizeSlug(input.slug);
|
|
723
|
+
const methodNames = mcpToolMethodNames(input.tools);
|
|
724
|
+
const tools: ScriptMcpToolDescriptor[] = input.tools.map((tool) => ({
|
|
725
|
+
name: tool.name,
|
|
726
|
+
description: tool.description,
|
|
727
|
+
inputSchema: jsonSchema(tool.inputSchema ?? {}),
|
|
728
|
+
}));
|
|
729
|
+
const generatedTypes = [
|
|
730
|
+
`export interface ${pascal(slug)}Mcp {`,
|
|
731
|
+
...methodNames.map(
|
|
732
|
+
({ tool, methodName }) =>
|
|
733
|
+
` ${methodName}(args: ${mcpToolArgsType(tool.inputSchema)}): Promise<JsonValue>;`,
|
|
587
734
|
),
|
|
588
735
|
"}",
|
|
589
736
|
].join("\n");
|
|
737
|
+
const descriptor: ScriptMcpConnectionDescriptor = {
|
|
738
|
+
slug,
|
|
739
|
+
kind: "mcp",
|
|
740
|
+
connectionId: input.connectionId,
|
|
741
|
+
tools,
|
|
742
|
+
};
|
|
590
743
|
return { generatedTypes, generatedRuntimeJson: JSON.stringify(descriptor) };
|
|
591
744
|
}
|
|
592
745
|
|
|
593
|
-
|
|
746
|
+
function parseOpenapiSpecBody(body: string): unknown {
|
|
747
|
+
try {
|
|
748
|
+
return JSON.parse(body);
|
|
749
|
+
} catch (jsonErr) {
|
|
750
|
+
const yaml = (Bun as unknown as { YAML?: { parse(input: string): unknown } }).YAML;
|
|
751
|
+
if (!yaml) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
`OpenAPI spec response was not valid JSON. JSON specs only unless Bun.YAML.parse is available: ${
|
|
754
|
+
jsonErr instanceof Error ? jsonErr.message : String(jsonErr)
|
|
755
|
+
}`,
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
return yaml.parse(body);
|
|
760
|
+
} catch (yamlErr) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
`OpenAPI spec response was neither valid JSON nor valid YAML: ${
|
|
763
|
+
yamlErr instanceof Error ? yamlErr.message : String(yamlErr)
|
|
764
|
+
}`,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export async function fetchOpenapiSpec(
|
|
771
|
+
url: string,
|
|
772
|
+
opts: { etag?: string | null } = {},
|
|
773
|
+
): Promise<OpenapiSpecFetchResult> {
|
|
774
|
+
let parsed = assertUrlSafe(url, openapiSpecUrlOptions());
|
|
775
|
+
const controller = new AbortController();
|
|
776
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
777
|
+
try {
|
|
778
|
+
const headers = new Headers({
|
|
779
|
+
Accept: "application/json, application/yaml;q=0.9, text/yaml;q=0.8, */*;q=0.5",
|
|
780
|
+
});
|
|
781
|
+
if (opts.etag) headers.set("If-None-Match", opts.etag);
|
|
782
|
+
const fetchImpl = openapiSpecFetchForTesting ?? Bun.fetch;
|
|
783
|
+
let response: Response | null = null;
|
|
784
|
+
for (let hop = 0; hop <= 5; hop += 1) {
|
|
785
|
+
response = await fetchImpl(parsed, {
|
|
786
|
+
headers,
|
|
787
|
+
signal: controller.signal,
|
|
788
|
+
redirect: "manual",
|
|
789
|
+
});
|
|
790
|
+
if (!isRedirectStatus(response.status)) break;
|
|
791
|
+
const location = response.headers.get("location");
|
|
792
|
+
if (!location) {
|
|
793
|
+
throw new Error(`OpenAPI spec redirect missing Location header: HTTP ${response.status}`);
|
|
794
|
+
}
|
|
795
|
+
parsed = assertUrlSafe(new URL(location, parsed).toString(), openapiSpecUrlOptions());
|
|
796
|
+
if (hop === 5) throw new Error("OpenAPI spec fetch exceeded 5 redirects.");
|
|
797
|
+
}
|
|
798
|
+
if (!response) throw new Error("OpenAPI spec fetch failed before receiving a response.");
|
|
799
|
+
const fetchedAt = new Date().toISOString();
|
|
800
|
+
const etag = response.headers.get("etag");
|
|
801
|
+
if (response.status === 304) {
|
|
802
|
+
return { status: "not_modified", etag, fetchedAt };
|
|
803
|
+
}
|
|
804
|
+
if (!response.ok) {
|
|
805
|
+
throw new Error(`Failed to fetch OpenAPI spec: HTTP ${response.status}`);
|
|
806
|
+
}
|
|
807
|
+
const spec = parseOpenapiSpecBody(await response.text());
|
|
808
|
+
return {
|
|
809
|
+
status: "fetched",
|
|
810
|
+
spec,
|
|
811
|
+
specJson: JSON.stringify(spec),
|
|
812
|
+
etag,
|
|
813
|
+
fetchedAt,
|
|
814
|
+
};
|
|
815
|
+
} catch (err) {
|
|
816
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
817
|
+
throw new Error("Timed out fetching OpenAPI spec after 15s");
|
|
818
|
+
}
|
|
819
|
+
throw err;
|
|
820
|
+
} finally {
|
|
821
|
+
clearTimeout(timeout);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
export async function upsertScriptConnection(data: {
|
|
594
826
|
id?: string;
|
|
595
827
|
slug: string;
|
|
596
828
|
displayName?: string | null;
|
|
@@ -602,23 +834,67 @@ export function upsertScriptConnection(data: {
|
|
|
602
834
|
credentialBindingId?: string | null;
|
|
603
835
|
openapiSpecSourceKind?: "url" | "inline" | "agent_fs" | null;
|
|
604
836
|
openapiSpecSource?: string | null;
|
|
837
|
+
openapiSpecUrl?: string | null;
|
|
605
838
|
openapiSpecJson?: string | null;
|
|
839
|
+
openapiSpecEtag?: string | null;
|
|
840
|
+
openapiSpecFetchedAt?: string | null;
|
|
606
841
|
mcpServerId?: string | null;
|
|
607
842
|
enabled?: boolean;
|
|
843
|
+
agentId?: string;
|
|
608
844
|
userId?: string | null;
|
|
609
|
-
}): ScriptConnectionRecord {
|
|
845
|
+
}): Promise<ScriptConnectionRecord> {
|
|
610
846
|
const now = new Date().toISOString();
|
|
611
847
|
const id = data.id ?? crypto.randomUUID();
|
|
612
848
|
const scope = data.scope ?? "global";
|
|
613
849
|
const scopeId = scope === "global" ? null : (data.scopeId ?? null);
|
|
850
|
+
const normalizedSlug = normalizeSlug(data.slug);
|
|
614
851
|
let generatedTypes: string | null = null;
|
|
615
852
|
let generatedRuntimeJson: string | null = null;
|
|
616
853
|
let generationError: string | null = null;
|
|
617
854
|
let generatedAt: string | null = null;
|
|
855
|
+
let openapiSpecJson = data.openapiSpecJson ?? null;
|
|
856
|
+
let openapiSpecSourceKind =
|
|
857
|
+
data.openapiSpecSourceKind ??
|
|
858
|
+
(data.kind === "openapi" ? (data.openapiSpecUrl ? "url" : "inline") : null);
|
|
859
|
+
let openapiSpecSource = data.openapiSpecSource ?? data.openapiSpecUrl ?? null;
|
|
860
|
+
let openapiSpecEtag = data.openapiSpecEtag ?? null;
|
|
861
|
+
let openapiSpecFetchedAt = data.openapiSpecFetchedAt ?? null;
|
|
862
|
+
let openapiSpec: unknown;
|
|
863
|
+
|
|
864
|
+
const existing = data.id
|
|
865
|
+
? getDb()
|
|
866
|
+
.prepare<{ id: string; version: number }, [string]>(
|
|
867
|
+
"SELECT id, version FROM script_connections WHERE id = ?",
|
|
868
|
+
)
|
|
869
|
+
.get(data.id)
|
|
870
|
+
: getDb()
|
|
871
|
+
.prepare<{ id: string; version: number }, [string, string, string | null]>(
|
|
872
|
+
"SELECT id, version FROM script_connections WHERE slug = ? AND scope = ? AND COALESCE(scope_id, '') = COALESCE(?, '')",
|
|
873
|
+
)
|
|
874
|
+
.get(normalizedSlug, scope, scopeId);
|
|
875
|
+
const connectionId = existing?.id ?? id;
|
|
618
876
|
|
|
619
877
|
if (data.kind === "openapi") {
|
|
878
|
+
if (!openapiSpecJson && data.openapiSpecUrl) {
|
|
879
|
+
const fetched = await fetchOpenapiSpec(data.openapiSpecUrl);
|
|
880
|
+
if (fetched.status === "not_modified") {
|
|
881
|
+
throw new Error("OpenAPI spec URL returned 304 without an existing cached spec.");
|
|
882
|
+
}
|
|
883
|
+
openapiSpec = fetched.spec;
|
|
884
|
+
openapiSpecJson = fetched.specJson;
|
|
885
|
+
openapiSpecSourceKind = "url";
|
|
886
|
+
openapiSpecSource = data.openapiSpecUrl;
|
|
887
|
+
openapiSpecEtag = fetched.etag;
|
|
888
|
+
openapiSpecFetchedAt = fetched.fetchedAt;
|
|
889
|
+
}
|
|
620
890
|
try {
|
|
621
|
-
|
|
891
|
+
// Inline specs may be pasted as YAML too — parse with the same
|
|
892
|
+
// JSON-then-YAML fallback as fetched specs and store canonical JSON.
|
|
893
|
+
let spec = openapiSpec;
|
|
894
|
+
if (!spec) {
|
|
895
|
+
spec = parseOpenapiSpecBody(openapiSpecJson ?? "{}");
|
|
896
|
+
openapiSpecJson = JSON.stringify(spec);
|
|
897
|
+
}
|
|
622
898
|
const binding = data.credentialBindingId
|
|
623
899
|
? getCredentialBindingById(data.credentialBindingId)
|
|
624
900
|
: null;
|
|
@@ -636,20 +912,53 @@ export function upsertScriptConnection(data: {
|
|
|
636
912
|
}
|
|
637
913
|
}
|
|
638
914
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
915
|
+
if (data.kind === "mcp") {
|
|
916
|
+
try {
|
|
917
|
+
if (!data.mcpServerId) throw new Error("mcpServerId is required for MCP connections");
|
|
918
|
+
// Resolve MCP auth config in the scope the connection will RUN under:
|
|
919
|
+
// an agent-scoped connection may rely on that agent's scoped secrets,
|
|
920
|
+
// which the (lead) caller's own context cannot see.
|
|
921
|
+
const discoveryContext =
|
|
922
|
+
scope === "agent" && scopeId
|
|
923
|
+
? { agentId: scopeId }
|
|
924
|
+
: scope === "repo" && scopeId
|
|
925
|
+
? { agentId: data.agentId, repoId: scopeId }
|
|
926
|
+
: { agentId: data.agentId };
|
|
927
|
+
const tools = await listMcpServerTools(data.mcpServerId, discoveryContext);
|
|
928
|
+
const artifacts = buildMcpGeneratedArtifacts({
|
|
929
|
+
slug: data.slug,
|
|
930
|
+
connectionId,
|
|
931
|
+
tools,
|
|
932
|
+
});
|
|
933
|
+
generatedTypes = artifacts.generatedTypes;
|
|
934
|
+
generatedRuntimeJson = artifacts.generatedRuntimeJson;
|
|
935
|
+
generatedAt = now;
|
|
936
|
+
} catch (err) {
|
|
937
|
+
generationError = err instanceof Error ? err.message : String(err);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (data.kind === "graphql") {
|
|
942
|
+
if (!data.baseUrl) throw new Error("baseUrl is required for GraphQL connections");
|
|
943
|
+
if (!data.allowedHosts?.length) {
|
|
944
|
+
throw new Error("allowedHosts is required for GraphQL connections");
|
|
945
|
+
}
|
|
946
|
+
new URL(data.baseUrl);
|
|
947
|
+
const binding = data.credentialBindingId
|
|
948
|
+
? getCredentialBindingById(data.credentialBindingId)
|
|
949
|
+
: null;
|
|
950
|
+
const artifacts = buildGraphqlGeneratedArtifacts({
|
|
951
|
+
slug: data.slug,
|
|
952
|
+
baseUrl: data.baseUrl,
|
|
953
|
+
credentialBinding: binding,
|
|
954
|
+
});
|
|
955
|
+
generatedTypes = artifacts.generatedTypes;
|
|
956
|
+
generatedRuntimeJson = artifacts.generatedRuntimeJson;
|
|
957
|
+
generatedAt = now;
|
|
958
|
+
}
|
|
650
959
|
|
|
651
960
|
const params = [
|
|
652
|
-
|
|
961
|
+
normalizedSlug,
|
|
653
962
|
data.displayName ?? null,
|
|
654
963
|
data.kind,
|
|
655
964
|
scope,
|
|
@@ -657,9 +966,11 @@ export function upsertScriptConnection(data: {
|
|
|
657
966
|
data.baseUrl ?? null,
|
|
658
967
|
JSON.stringify(data.allowedHosts ?? []),
|
|
659
968
|
data.credentialBindingId ?? null,
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
969
|
+
openapiSpecSourceKind,
|
|
970
|
+
openapiSpecSource,
|
|
971
|
+
openapiSpecJson,
|
|
972
|
+
openapiSpecEtag,
|
|
973
|
+
openapiSpecFetchedAt,
|
|
663
974
|
data.mcpServerId ?? null,
|
|
664
975
|
generatedTypes,
|
|
665
976
|
generatedRuntimeJson,
|
|
@@ -674,7 +985,8 @@ export function upsertScriptConnection(data: {
|
|
|
674
985
|
`UPDATE script_connections SET
|
|
675
986
|
slug = ?, display_name = ?, kind = ?, scope = ?, scope_id = ?, base_url = ?,
|
|
676
987
|
allowed_hosts_json = ?, credential_binding_id = ?, openapi_spec_source_kind = ?,
|
|
677
|
-
openapi_spec_source = ?, openapi_spec_json = ?,
|
|
988
|
+
openapi_spec_source = ?, openapi_spec_json = ?, openapi_spec_etag = ?,
|
|
989
|
+
openapi_spec_fetched_at = ?, mcp_server_id = ?, generated_types = ?,
|
|
678
990
|
generated_runtime_json = ?, generated_at = ?, generation_error = ?, enabled = ?,
|
|
679
991
|
updated_at = ?, updated_by = ?, version = ?
|
|
680
992
|
WHERE id = ? RETURNING *`,
|
|
@@ -692,15 +1004,92 @@ export function upsertScriptConnection(data: {
|
|
|
692
1004
|
`INSERT INTO script_connections
|
|
693
1005
|
(id, slug, display_name, kind, scope, scope_id, base_url, allowed_hosts_json,
|
|
694
1006
|
credential_binding_id, openapi_spec_source_kind, openapi_spec_source, openapi_spec_json,
|
|
695
|
-
|
|
696
|
-
enabled, created_at, updated_at,
|
|
697
|
-
|
|
1007
|
+
openapi_spec_etag, openapi_spec_fetched_at, mcp_server_id, generated_types,
|
|
1008
|
+
generated_runtime_json, generated_at, generation_error, enabled, created_at, updated_at,
|
|
1009
|
+
created_by, updated_by)
|
|
1010
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
698
1011
|
)
|
|
699
1012
|
.get(id, ...params, now, now, data.userId ?? null, data.userId ?? null);
|
|
700
1013
|
if (!row) throw new Error("Failed to create script connection");
|
|
701
1014
|
return connectionFromRow(row);
|
|
702
1015
|
}
|
|
703
1016
|
|
|
1017
|
+
export async function refreshScriptConnection(
|
|
1018
|
+
id: string,
|
|
1019
|
+
userId?: string | null,
|
|
1020
|
+
callerAgentId?: string,
|
|
1021
|
+
): Promise<ScriptConnectionRecord | null> {
|
|
1022
|
+
const row = getDb()
|
|
1023
|
+
.prepare<ConnectionRow, [string]>("SELECT * FROM script_connections WHERE id = ?")
|
|
1024
|
+
.get(id);
|
|
1025
|
+
if (!row) return null;
|
|
1026
|
+
const connection = connectionFromRow(row);
|
|
1027
|
+
|
|
1028
|
+
// MCP refresh = re-run tool discovery (servers add/remove tools over time).
|
|
1029
|
+
// Route through the upsert path so discovery scoping, versioning, and
|
|
1030
|
+
// generation-error handling stay identical to registration.
|
|
1031
|
+
if (connection.kind === "mcp") {
|
|
1032
|
+
if (!connection.mcpServerId) {
|
|
1033
|
+
throw new Error("MCP connection has no mcpServerId to refresh from.");
|
|
1034
|
+
}
|
|
1035
|
+
return upsertScriptConnection({
|
|
1036
|
+
id: connection.id,
|
|
1037
|
+
slug: connection.slug,
|
|
1038
|
+
displayName: connection.displayName,
|
|
1039
|
+
kind: "mcp",
|
|
1040
|
+
scope: connection.scope,
|
|
1041
|
+
scopeId: connection.scopeId,
|
|
1042
|
+
allowedHosts: connection.allowedHosts,
|
|
1043
|
+
credentialBindingId: connection.credentialBindingId,
|
|
1044
|
+
mcpServerId: connection.mcpServerId,
|
|
1045
|
+
enabled: connection.enabled,
|
|
1046
|
+
agentId: callerAgentId,
|
|
1047
|
+
userId,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (connection.kind !== "openapi") {
|
|
1052
|
+
throw new Error("Only OpenAPI and MCP script connections can be refreshed.");
|
|
1053
|
+
}
|
|
1054
|
+
if (connection.openapiSpecSourceKind !== "url" || !connection.openapiSpecSource) {
|
|
1055
|
+
throw new Error("Only OpenAPI script connections registered by URL can be refreshed.");
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const fetched = await fetchOpenapiSpec(connection.openapiSpecSource, {
|
|
1059
|
+
etag: connection.openapiSpecEtag,
|
|
1060
|
+
});
|
|
1061
|
+
if (fetched.status === "not_modified") {
|
|
1062
|
+
const refreshed = getDb()
|
|
1063
|
+
.prepare<ConnectionRow, [string, string]>(
|
|
1064
|
+
`UPDATE script_connections
|
|
1065
|
+
SET openapi_spec_fetched_at = ?
|
|
1066
|
+
WHERE id = ? RETURNING *`,
|
|
1067
|
+
)
|
|
1068
|
+
.get(fetched.fetchedAt, id);
|
|
1069
|
+
return refreshed ? connectionFromRow(refreshed) : null;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
return upsertScriptConnection({
|
|
1073
|
+
id: connection.id,
|
|
1074
|
+
slug: connection.slug,
|
|
1075
|
+
displayName: connection.displayName,
|
|
1076
|
+
kind: "openapi",
|
|
1077
|
+
scope: connection.scope,
|
|
1078
|
+
scopeId: connection.scopeId,
|
|
1079
|
+
baseUrl: connection.baseUrl,
|
|
1080
|
+
allowedHosts: connection.allowedHosts,
|
|
1081
|
+
credentialBindingId: connection.credentialBindingId,
|
|
1082
|
+
openapiSpecSourceKind: "url",
|
|
1083
|
+
openapiSpecSource: connection.openapiSpecSource,
|
|
1084
|
+
openapiSpecJson: fetched.specJson,
|
|
1085
|
+
openapiSpecEtag: fetched.etag,
|
|
1086
|
+
openapiSpecFetchedAt: fetched.fetchedAt,
|
|
1087
|
+
mcpServerId: connection.mcpServerId,
|
|
1088
|
+
enabled: connection.enabled,
|
|
1089
|
+
userId,
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
|
|
704
1093
|
export function setScriptConnectionEnabled(
|
|
705
1094
|
id: string,
|
|
706
1095
|
enabled: boolean,
|
|
@@ -719,7 +1108,8 @@ export function setScriptConnectionEnabled(
|
|
|
719
1108
|
export function getScriptApiConnectionDescriptors(
|
|
720
1109
|
context: { agentId?: string; repoId?: string } = {},
|
|
721
1110
|
): ScriptApiConnectionDescriptor[] {
|
|
722
|
-
return listScriptConnections(
|
|
1111
|
+
return listScriptConnections(context)
|
|
1112
|
+
.filter((connection) => connection.kind === "openapi" || connection.kind === "graphql")
|
|
723
1113
|
.map((connection) => {
|
|
724
1114
|
if (!connection.generatedRuntimeJson) return null;
|
|
725
1115
|
try {
|
|
@@ -731,8 +1121,25 @@ export function getScriptApiConnectionDescriptors(
|
|
|
731
1121
|
.filter((descriptor): descriptor is ScriptApiConnectionDescriptor => Boolean(descriptor));
|
|
732
1122
|
}
|
|
733
1123
|
|
|
1124
|
+
export function getScriptMcpConnectionDescriptors(
|
|
1125
|
+
context: { agentId?: string; repoId?: string } = {},
|
|
1126
|
+
): ScriptMcpConnectionDescriptor[] {
|
|
1127
|
+
return listScriptConnections({ ...context, kind: "mcp" })
|
|
1128
|
+
.map((connection) => {
|
|
1129
|
+
if (!connection.generatedRuntimeJson) return null;
|
|
1130
|
+
try {
|
|
1131
|
+
return JSON.parse(connection.generatedRuntimeJson) as ScriptMcpConnectionDescriptor;
|
|
1132
|
+
} catch {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
})
|
|
1136
|
+
.filter((descriptor): descriptor is ScriptMcpConnectionDescriptor => Boolean(descriptor));
|
|
1137
|
+
}
|
|
1138
|
+
|
|
734
1139
|
export function getScriptApiTypes(context: { agentId?: string; repoId?: string } = {}): string {
|
|
735
|
-
const connections = listScriptConnections(
|
|
1140
|
+
const connections = listScriptConnections(context).filter(
|
|
1141
|
+
(connection) => connection.kind === "openapi" || connection.kind === "graphql",
|
|
1142
|
+
);
|
|
736
1143
|
const blocks = connections
|
|
737
1144
|
.filter((connection) => connection.generatedTypes)
|
|
738
1145
|
.map((connection) => connection.generatedTypes as string);
|
|
@@ -742,3 +1149,15 @@ export function getScriptApiTypes(context: { agentId?: string; repoId?: string }
|
|
|
742
1149
|
.map((connection) => ` ${connection.slug}: ${pascal(connection.slug)}Api;`);
|
|
743
1150
|
return `${blocks.join("\n\n")}\n\nexport interface ScriptApiRegistry {\n${members.join("\n")}\n}\n`;
|
|
744
1151
|
}
|
|
1152
|
+
|
|
1153
|
+
export function getScriptMcpTypes(context: { agentId?: string; repoId?: string } = {}): string {
|
|
1154
|
+
const connections = listScriptConnections({ ...context, kind: "mcp" });
|
|
1155
|
+
const blocks = connections
|
|
1156
|
+
.filter((connection) => connection.generatedTypes)
|
|
1157
|
+
.map((connection) => connection.generatedTypes as string);
|
|
1158
|
+
if (blocks.length === 0) return "export interface ScriptMcpRegistry {}\n";
|
|
1159
|
+
const members = connections
|
|
1160
|
+
.filter((connection) => connection.generatedTypes)
|
|
1161
|
+
.map((connection) => ` ${connection.slug}: ${pascal(connection.slug)}Mcp;`);
|
|
1162
|
+
return `${blocks.join("\n\n")}\n\nexport interface ScriptMcpRegistry {\n${members.join("\n")}\n}\n`;
|
|
1163
|
+
}
|