@desplega.ai/agent-swarm 1.103.0 → 1.104.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 -0
- package/dist/{actions-7txktrdj.js → actions-2expzt47.js} +6 -6
- package/dist/{app-xm3xsdye.js → app-dr8hwx7z.js} +3 -3
- package/dist/{assistant-qpv63wk6.js → assistant-9rhgsw7v.js} +9 -10
- package/dist/{boot-reembed-chxhrwv8.js → boot-reembed-3q1aywnz.js} +5 -4
- package/dist/{boot-reembed-9cxqka3e.js → boot-reembed-9kt97g1z.js} +4 -3
- package/dist/{boot-scrub-logs-8qtfkcz8.js → boot-scrub-logs-vskq108e.js} +2 -2
- package/dist/{cli-fedn86nz.js → cli-0jw6j755.js} +8 -3
- package/dist/{cli-yeemwevj.js → cli-23zk7xqy.js} +20 -16
- package/dist/{cli-v9283nma.js → cli-2g9jf9ek.js} +1 -1
- package/dist/{cli-rvae030h.js → cli-2hap19dv.js} +3 -3
- package/dist/{cli-wjyyv518.js → cli-3j01d33e.js} +9 -14
- package/dist/{cli-8hyv6c2v.js → cli-54h5daha.js} +41 -23
- package/dist/{cli-fygaw191.js → cli-5pf0b84v.js} +2 -2
- package/dist/{cli-m8f6qzck.js → cli-7v4y3hyd.js} +1 -1
- package/dist/{cli-fwxdt7kt.js → cli-d4z1csms.js} +30 -5
- package/dist/{cli-jt9d9fjw.js → cli-e7bwjntj.js} +1 -1
- package/dist/{cli-s14sb64w.js → cli-etvgz5x8.js} +5 -7
- package/dist/{cli-2qynerth.js → cli-gj2n2ec1.js} +1 -1
- package/dist/{cli-dz3d6zjg.js → cli-nzc0h54v.js} +121 -2
- package/dist/{cli-8w95v19d.js → cli-p4cj4vhc.js} +351 -24
- package/dist/{cli-xmtsan9k.js → cli-q5tshxd6.js} +1 -1
- package/dist/{http-zts4haq7.js → cli-qvzgncd8.js} +83966 -76422
- package/dist/{cli-4fnp2tc3.js → cli-s42f54ja.js} +1 -1
- package/dist/{cli-krvxq8bc.js → cli-t9v14cnd.js} +23 -4
- package/dist/{cli-gzepjz54.js → cli-v1syqkcb.js} +900 -352
- package/dist/{cli-ksq38x4n.js → cli-w1nfskkg.js} +2 -2
- package/dist/{cli-ns0r7mkr.js → cli-y9p3e94h.js} +3 -3
- package/dist/{cli-85pf4z38.js → cli-yjt8a36k.js} +3 -3
- package/dist/{cli-yfwwjeft.js → cli-yvektv79.js} +1 -1
- package/dist/{cli-kk51f3tf.js → cli-zaxksbgj.js} +5 -5
- package/dist/{cli-rttgde5f.js → cli-zghef0em.js} +1 -1
- package/dist/cli.js +10 -10
- package/dist/{commands-aqn63mcw.js → commands-wfrkef57.js} +2 -2
- package/dist/{db-azrvwsnj.js → db-c42pxt7h.js} +2 -2
- package/dist/{handlers-hf61har3.js → handlers-n36zkfvx.js} +10 -12
- package/dist/{hook-306p3yz4.js → hook-gw3vhv6s.js} +7 -5
- package/dist/http-59ygpn2h.js +17263 -0
- package/dist/{index-h2yqjyk0.js → index-881c1baf.js} +9 -8
- package/dist/{index-29vg51x6.js → index-jca7jdpt.js} +23 -10
- package/dist/{index-bfh9hgek.js → index-xf6fp4y1.js} +5 -5
- package/dist/{index-3c651yfk.js → index-xz8vaa5c.js} +14 -11
- package/dist/{keepalive-a0dg5pds.js → keepalive-mj4302vw.js} +4 -4
- package/dist/{lead-7mcxedkc.js → lead-qm0xjvkv.js} +19 -19
- package/dist/{maintenance-mrzqd53t.js → maintenance-xppxbe4t.js} +5 -4
- package/dist/{onboard-4dkpv9wa.js → onboard-ejx6yxh8.js} +2 -2
- package/dist/{otel-impl-jt7gpyp9.js → otel-impl-zs5exkpk.js} +1 -1
- package/dist/{pricing-refresh-xqnzh8dt.js → pricing-refresh-nqyf8bkb.js} +4 -4
- package/dist/{seed-pricing-56tjm1fj.js → seed-pricing-w3x550cj.js} +3 -3
- package/dist/{setup-tbwk8j10.js → setup-7729ktfj.js} +2 -2
- package/dist/{worker-e03za92t.js → worker-4ttemwhw.js} +19 -19
- package/openapi.json +90 -4
- package/package.json +3 -3
- package/src/be/memory/constants.ts +6 -0
- package/src/be/memory/providers/sqlite-store.ts +468 -71
- package/src/be/memory/raters/retrieval.ts +4 -2
- package/src/be/memory/reranker.ts +5 -2
- package/src/be/memory/retrieval-store.ts +2 -0
- package/src/be/memory/types.ts +46 -0
- package/src/be/migrations/099_memory_structured_key_versioning.sql +41 -0
- package/src/be/migrations/100_memory_retrieval_source.sql +8 -0
- package/src/be/migrations/101_script_connections.sql +67 -0
- package/src/be/script-connections.ts +744 -0
- package/src/be/script-credential-broker.ts +38 -0
- package/src/be/scripts/typecheck.ts +23 -2
- package/src/be/seed-scripts/catalog/boot-triage.inline.ts +16 -4
- package/src/be/seed-scripts/catalog/boot-triage.ts +16 -4
- package/src/commands/runner.ts +8 -6
- package/src/heartbeat/heartbeat.ts +42 -1
- package/src/heartbeat/index.ts +7 -1
- package/src/http/memory.ts +127 -14
- package/src/http/scripts.ts +15 -14
- package/src/scripts-runtime/api-client.ts +100 -0
- package/src/scripts-runtime/api-types.ts +45 -0
- package/src/scripts-runtime/credential-broker/README.md +40 -0
- package/src/scripts-runtime/credential-broker/broker.ts +51 -0
- package/src/scripts-runtime/credential-broker/default-bindings.ts +19 -0
- package/src/scripts-runtime/credential-broker/fetch-patch.ts +102 -0
- package/src/scripts-runtime/credential-broker/index.ts +17 -0
- package/src/scripts-runtime/credential-broker/store.ts +72 -0
- package/src/scripts-runtime/credential-broker/types.ts +54 -0
- package/src/scripts-runtime/ctx.ts +11 -1
- package/src/scripts-runtime/egress-secrets.ts +14 -76
- package/src/scripts-runtime/eval-harness.ts +1 -1
- package/src/scripts-runtime/executors/types.ts +5 -5
- package/src/scripts-runtime/loader.ts +6 -1
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +11 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +11 -0
- package/src/server.ts +6 -0
- package/src/tests/credential-broker.test.ts +281 -0
- package/src/tests/heartbeat.test.ts +239 -0
- package/src/tests/memory-edit.test.ts +177 -0
- package/src/tests/memory-hybrid.test.ts +212 -0
- package/src/tests/runner-repo-autostash.test.ts +39 -8
- package/src/tests/script-connections.test.ts +360 -0
- package/src/tests/scripts-http.test.ts +16 -10
- package/src/tests/scripts-runtime-secret-egress.test.ts +26 -5
- package/src/tests/swarm-config-reserved-keys.test.ts +23 -0
- package/src/tests/update-schedule-mcp-tool.test.ts +83 -4
- package/src/tools/credential-bindings/index.ts +1 -0
- package/src/tools/credential-bindings/tool.ts +238 -0
- package/src/tools/memory-edit.ts +148 -0
- package/src/tools/memory-search.ts +17 -10
- package/src/tools/schedules/delete-schedule.ts +18 -8
- package/src/tools/schedules/update-schedule.ts +4 -9
- package/src/tools/script-connections/index.ts +1 -0
- package/src/tools/script-connections/tool.ts +167 -0
- package/src/tools/swarm-config/delete-config.ts +5 -0
- package/src/tools/swarm-config/set-config.ts +23 -1
- package/src/tools/tool-config.ts +6 -1
- package/src/types.ts +6 -0
- package/src/workflows/executors/swarm-script.ts +2 -0
- package/templates/community/dora-metrics/PLAYBOOK.md +294 -0
- package/templates/community/dora-metrics/README.md +39 -0
- package/templates/community/dora-metrics/lead-prompt.md +56 -0
- package/templates/community/dora-metrics/report.mjs +393 -0
- package/templates/community/dora-metrics/run.sh +113 -0
- package/templates/schedules/weekly-dora-metrics/config.json +13 -0
- package/templates/schedules/weekly-dora-metrics/content.md +61 -0
- package/dist/cli-2307phk1.js +0 -24223
- package/dist/cli-2deg0cen.js +0 -120
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
import { getDb, getSwarmConfigs } from "@/be/db";
|
|
2
|
+
import type {
|
|
3
|
+
ScriptApiConnectionDescriptor,
|
|
4
|
+
ScriptApiJsonSchema,
|
|
5
|
+
ScriptApiJsonValue,
|
|
6
|
+
ScriptApiOperationDescriptor,
|
|
7
|
+
} from "@/scripts-runtime/api-types";
|
|
8
|
+
import {
|
|
9
|
+
CREDENTIAL_BINDINGS_CONFIG_KEY,
|
|
10
|
+
type CredentialBinding,
|
|
11
|
+
normalizeCredentialBindingsDocument,
|
|
12
|
+
} from "@/scripts-runtime/credential-broker";
|
|
13
|
+
|
|
14
|
+
export type ScriptConnectionScope = "global" | "agent" | "repo";
|
|
15
|
+
export type ScriptConnectionKind = "raw" | "openapi" | "mcp";
|
|
16
|
+
|
|
17
|
+
export type ScriptCredentialBindingRecord = CredentialBinding & {
|
|
18
|
+
id: string;
|
|
19
|
+
source: "default" | "user" | "migration";
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
createdBy: string | null;
|
|
23
|
+
updatedBy: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ScriptConnectionRecord = {
|
|
27
|
+
id: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
displayName: string | null;
|
|
30
|
+
kind: ScriptConnectionKind;
|
|
31
|
+
scope: ScriptConnectionScope;
|
|
32
|
+
scopeId: string | null;
|
|
33
|
+
baseUrl: string | null;
|
|
34
|
+
allowedHosts: string[];
|
|
35
|
+
credentialBindingId: string | null;
|
|
36
|
+
openapiSpecSourceKind: "url" | "inline" | "agent_fs" | null;
|
|
37
|
+
openapiSpecSource: string | null;
|
|
38
|
+
openapiSpecJson: string | null;
|
|
39
|
+
openapiSpecEtag: string | null;
|
|
40
|
+
openapiSpecFetchedAt: string | null;
|
|
41
|
+
mcpServerId: string | null;
|
|
42
|
+
generatedTypes: string | null;
|
|
43
|
+
generatedRuntimeJson: string | null;
|
|
44
|
+
generatedAt: string | null;
|
|
45
|
+
generationError: string | null;
|
|
46
|
+
enabled: boolean;
|
|
47
|
+
version: number;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
updatedAt: string;
|
|
50
|
+
createdBy: string | null;
|
|
51
|
+
updatedBy: string | null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type BindingRow = {
|
|
55
|
+
id: string;
|
|
56
|
+
config_key: string;
|
|
57
|
+
allowed_hosts_json: string;
|
|
58
|
+
header_template: string | null;
|
|
59
|
+
query_template: string | null;
|
|
60
|
+
scope: string;
|
|
61
|
+
scope_id: string | null;
|
|
62
|
+
active: number;
|
|
63
|
+
source: string;
|
|
64
|
+
created_at: string;
|
|
65
|
+
updated_at: string;
|
|
66
|
+
created_by: string | null;
|
|
67
|
+
updated_by: string | null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type ConnectionRow = {
|
|
71
|
+
id: string;
|
|
72
|
+
slug: string;
|
|
73
|
+
display_name: string | null;
|
|
74
|
+
kind: string;
|
|
75
|
+
scope: string;
|
|
76
|
+
scope_id: string | null;
|
|
77
|
+
base_url: string | null;
|
|
78
|
+
allowed_hosts_json: string;
|
|
79
|
+
credential_binding_id: string | null;
|
|
80
|
+
openapi_spec_source_kind: string | null;
|
|
81
|
+
openapi_spec_source: string | null;
|
|
82
|
+
openapi_spec_json: string | null;
|
|
83
|
+
openapi_spec_etag: string | null;
|
|
84
|
+
openapi_spec_fetched_at: string | null;
|
|
85
|
+
mcp_server_id: string | null;
|
|
86
|
+
generated_types: string | null;
|
|
87
|
+
generated_runtime_json: string | null;
|
|
88
|
+
generated_at: string | null;
|
|
89
|
+
generation_error: string | null;
|
|
90
|
+
enabled: number;
|
|
91
|
+
version: number;
|
|
92
|
+
created_at: string;
|
|
93
|
+
updated_at: string;
|
|
94
|
+
created_by: string | null;
|
|
95
|
+
updated_by: string | null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function parseJsonArray(value: string): string[] {
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(value);
|
|
101
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
102
|
+
} catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function bindingFromRow(row: BindingRow): ScriptCredentialBindingRecord {
|
|
108
|
+
return {
|
|
109
|
+
id: row.id,
|
|
110
|
+
configKey: row.config_key,
|
|
111
|
+
allowedHosts: parseJsonArray(row.allowed_hosts_json),
|
|
112
|
+
headerTemplate: row.header_template ?? undefined,
|
|
113
|
+
queryTemplate: row.query_template ?? undefined,
|
|
114
|
+
scope: row.scope as ScriptConnectionScope,
|
|
115
|
+
scopeId: row.scope_id,
|
|
116
|
+
active: row.active === 1,
|
|
117
|
+
source: row.source as ScriptCredentialBindingRecord["source"],
|
|
118
|
+
createdAt: row.created_at,
|
|
119
|
+
updatedAt: row.updated_at,
|
|
120
|
+
createdBy: row.created_by,
|
|
121
|
+
updatedBy: row.updated_by,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function connectionFromRow(row: ConnectionRow): ScriptConnectionRecord {
|
|
126
|
+
return {
|
|
127
|
+
id: row.id,
|
|
128
|
+
slug: row.slug,
|
|
129
|
+
displayName: row.display_name,
|
|
130
|
+
kind: row.kind as ScriptConnectionKind,
|
|
131
|
+
scope: row.scope as ScriptConnectionScope,
|
|
132
|
+
scopeId: row.scope_id,
|
|
133
|
+
baseUrl: row.base_url,
|
|
134
|
+
allowedHosts: parseJsonArray(row.allowed_hosts_json),
|
|
135
|
+
credentialBindingId: row.credential_binding_id,
|
|
136
|
+
openapiSpecSourceKind:
|
|
137
|
+
row.openapi_spec_source_kind as ScriptConnectionRecord["openapiSpecSourceKind"],
|
|
138
|
+
openapiSpecSource: row.openapi_spec_source,
|
|
139
|
+
openapiSpecJson: row.openapi_spec_json,
|
|
140
|
+
openapiSpecEtag: row.openapi_spec_etag,
|
|
141
|
+
openapiSpecFetchedAt: row.openapi_spec_fetched_at,
|
|
142
|
+
mcpServerId: row.mcp_server_id,
|
|
143
|
+
generatedTypes: row.generated_types,
|
|
144
|
+
generatedRuntimeJson: row.generated_runtime_json,
|
|
145
|
+
generatedAt: row.generated_at,
|
|
146
|
+
generationError: row.generation_error,
|
|
147
|
+
enabled: row.enabled === 1,
|
|
148
|
+
version: row.version,
|
|
149
|
+
createdAt: row.created_at,
|
|
150
|
+
updatedAt: row.updated_at,
|
|
151
|
+
createdBy: row.created_by,
|
|
152
|
+
updatedBy: row.updated_by,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function applies(
|
|
157
|
+
scope: ScriptConnectionScope,
|
|
158
|
+
scopeId: string | null,
|
|
159
|
+
context: { agentId?: string; repoId?: string },
|
|
160
|
+
) {
|
|
161
|
+
if (scope === "global") return true;
|
|
162
|
+
if (scope === "agent") return Boolean(context.agentId && scopeId === context.agentId);
|
|
163
|
+
return Boolean(context.repoId && scopeId === context.repoId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeSlug(slug: string): string {
|
|
167
|
+
const cleaned = slug
|
|
168
|
+
.trim()
|
|
169
|
+
.replace(/[^A-Za-z0-9]+(.)/g, (_m, chr: string) => chr.toUpperCase())
|
|
170
|
+
.replace(/^[^A-Za-z_]+/, "")
|
|
171
|
+
.replace(/[^A-Za-z0-9_]/g, "");
|
|
172
|
+
if (!cleaned) throw new Error("slug must contain at least one letter");
|
|
173
|
+
return `${cleaned[0]?.toLowerCase()}${cleaned.slice(1)}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function pascal(name: string): string {
|
|
177
|
+
const camel = normalizeSlug(name);
|
|
178
|
+
return `${camel[0]?.toUpperCase()}${camel.slice(1)}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function listRelationalCredentialBindings(context?: {
|
|
182
|
+
agentId?: string;
|
|
183
|
+
repoId?: string;
|
|
184
|
+
includeInactive?: boolean;
|
|
185
|
+
}): ScriptCredentialBindingRecord[] {
|
|
186
|
+
const rows = getDb()
|
|
187
|
+
.prepare<BindingRow, []>("SELECT * FROM script_credential_bindings ORDER BY config_key ASC")
|
|
188
|
+
.all();
|
|
189
|
+
return rows
|
|
190
|
+
.map(bindingFromRow)
|
|
191
|
+
.filter((binding) => context?.includeInactive || binding.active !== false)
|
|
192
|
+
.filter((binding) => !context || applies(binding.scope, binding.scopeId ?? null, context));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getCredentialBindingById(id: string): ScriptCredentialBindingRecord | null {
|
|
196
|
+
const row = getDb()
|
|
197
|
+
.prepare<BindingRow, [string]>("SELECT * FROM script_credential_bindings WHERE id = ?")
|
|
198
|
+
.get(id);
|
|
199
|
+
return row ? bindingFromRow(row) : null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function findCredentialBindingByIdentity(data: {
|
|
203
|
+
configKey: string;
|
|
204
|
+
scope: ScriptConnectionScope;
|
|
205
|
+
scopeId: string | null;
|
|
206
|
+
headerTemplate?: string | null;
|
|
207
|
+
queryTemplate?: string | null;
|
|
208
|
+
}): ScriptCredentialBindingRecord | null {
|
|
209
|
+
const row = getDb()
|
|
210
|
+
.prepare<BindingRow, [string, string, string, string, string]>(
|
|
211
|
+
`SELECT * FROM script_credential_bindings
|
|
212
|
+
WHERE config_key = ?
|
|
213
|
+
AND scope = ?
|
|
214
|
+
AND COALESCE(scope_id, '') = ?
|
|
215
|
+
AND COALESCE(header_template, '') = ?
|
|
216
|
+
AND COALESCE(query_template, '') = ?`,
|
|
217
|
+
)
|
|
218
|
+
.get(
|
|
219
|
+
data.configKey,
|
|
220
|
+
data.scope,
|
|
221
|
+
data.scopeId ?? "",
|
|
222
|
+
data.headerTemplate ?? "",
|
|
223
|
+
data.queryTemplate ?? "",
|
|
224
|
+
);
|
|
225
|
+
return row ? bindingFromRow(row) : null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function upsertCredentialBinding(data: {
|
|
229
|
+
id?: string;
|
|
230
|
+
configKey: string;
|
|
231
|
+
allowedHosts: string[];
|
|
232
|
+
headerTemplate?: string | null;
|
|
233
|
+
queryTemplate?: string | null;
|
|
234
|
+
scope?: ScriptConnectionScope;
|
|
235
|
+
scopeId?: string | null;
|
|
236
|
+
active?: boolean;
|
|
237
|
+
source?: "default" | "user" | "migration";
|
|
238
|
+
userId?: string | null;
|
|
239
|
+
}): ScriptCredentialBindingRecord {
|
|
240
|
+
const now = new Date().toISOString();
|
|
241
|
+
const id = data.id ?? crypto.randomUUID();
|
|
242
|
+
const scope = data.scope ?? "global";
|
|
243
|
+
const scopeId = scope === "global" ? null : (data.scopeId ?? null);
|
|
244
|
+
const active = data.active === false ? 0 : 1;
|
|
245
|
+
const source = data.source ?? "user";
|
|
246
|
+
const existing =
|
|
247
|
+
(data.id ? getCredentialBindingById(data.id) : null) ??
|
|
248
|
+
findCredentialBindingByIdentity({
|
|
249
|
+
configKey: data.configKey,
|
|
250
|
+
scope,
|
|
251
|
+
scopeId,
|
|
252
|
+
headerTemplate: data.headerTemplate,
|
|
253
|
+
queryTemplate: data.queryTemplate,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (existing) {
|
|
257
|
+
const targetId = existing.id;
|
|
258
|
+
const row = getDb()
|
|
259
|
+
.prepare<
|
|
260
|
+
BindingRow,
|
|
261
|
+
[
|
|
262
|
+
string,
|
|
263
|
+
string,
|
|
264
|
+
string | null,
|
|
265
|
+
string | null,
|
|
266
|
+
string,
|
|
267
|
+
string | null,
|
|
268
|
+
number,
|
|
269
|
+
string,
|
|
270
|
+
string | null,
|
|
271
|
+
string,
|
|
272
|
+
string,
|
|
273
|
+
]
|
|
274
|
+
>(
|
|
275
|
+
`UPDATE script_credential_bindings
|
|
276
|
+
SET config_key = ?, allowed_hosts_json = ?, header_template = ?, query_template = ?,
|
|
277
|
+
scope = ?, scope_id = ?, active = ?, source = ?, updated_by = ?, updated_at = ?
|
|
278
|
+
WHERE id = ? RETURNING *`,
|
|
279
|
+
)
|
|
280
|
+
.get(
|
|
281
|
+
data.configKey,
|
|
282
|
+
JSON.stringify(data.allowedHosts),
|
|
283
|
+
data.headerTemplate ?? null,
|
|
284
|
+
data.queryTemplate ?? null,
|
|
285
|
+
scope,
|
|
286
|
+
scopeId,
|
|
287
|
+
active,
|
|
288
|
+
source,
|
|
289
|
+
data.userId ?? null,
|
|
290
|
+
now,
|
|
291
|
+
targetId,
|
|
292
|
+
);
|
|
293
|
+
if (!row) throw new Error("Failed to update credential binding");
|
|
294
|
+
return bindingFromRow(row);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const row = getDb()
|
|
298
|
+
.prepare<
|
|
299
|
+
BindingRow,
|
|
300
|
+
[
|
|
301
|
+
string,
|
|
302
|
+
string,
|
|
303
|
+
string,
|
|
304
|
+
string | null,
|
|
305
|
+
string | null,
|
|
306
|
+
string,
|
|
307
|
+
string | null,
|
|
308
|
+
number,
|
|
309
|
+
string,
|
|
310
|
+
string,
|
|
311
|
+
string,
|
|
312
|
+
string | null,
|
|
313
|
+
string | null,
|
|
314
|
+
]
|
|
315
|
+
>(
|
|
316
|
+
`INSERT INTO script_credential_bindings
|
|
317
|
+
(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 *`,
|
|
320
|
+
)
|
|
321
|
+
.get(
|
|
322
|
+
id,
|
|
323
|
+
data.configKey,
|
|
324
|
+
JSON.stringify(data.allowedHosts),
|
|
325
|
+
data.headerTemplate ?? null,
|
|
326
|
+
data.queryTemplate ?? null,
|
|
327
|
+
scope,
|
|
328
|
+
scopeId,
|
|
329
|
+
active,
|
|
330
|
+
source,
|
|
331
|
+
now,
|
|
332
|
+
now,
|
|
333
|
+
data.userId ?? null,
|
|
334
|
+
data.userId ?? null,
|
|
335
|
+
);
|
|
336
|
+
if (!row) throw new Error("Failed to create credential binding");
|
|
337
|
+
return bindingFromRow(row);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function disableCredentialBinding(
|
|
341
|
+
id: string,
|
|
342
|
+
userId?: string | null,
|
|
343
|
+
): ScriptCredentialBindingRecord | null {
|
|
344
|
+
const row = getDb()
|
|
345
|
+
.prepare<BindingRow, [string, string | null, string]>(
|
|
346
|
+
`UPDATE script_credential_bindings SET active = 0, updated_at = ?, updated_by = ? WHERE id = ? RETURNING *`,
|
|
347
|
+
)
|
|
348
|
+
.get(new Date().toISOString(), userId ?? null, id);
|
|
349
|
+
return row ? bindingFromRow(row) : null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function importLegacyCredentialBindings(userId?: string | null): number {
|
|
353
|
+
let imported = 0;
|
|
354
|
+
for (const config of getSwarmConfigs({ key: CREDENTIAL_BINDINGS_CONFIG_KEY })) {
|
|
355
|
+
let bindings: CredentialBinding[];
|
|
356
|
+
try {
|
|
357
|
+
bindings = normalizeCredentialBindingsDocument(JSON.parse(config.value));
|
|
358
|
+
} catch {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
for (const binding of bindings) {
|
|
362
|
+
const scope = binding.scope ?? (config.scope as ScriptConnectionScope);
|
|
363
|
+
upsertCredentialBinding({
|
|
364
|
+
configKey: binding.configKey,
|
|
365
|
+
allowedHosts: binding.allowedHosts,
|
|
366
|
+
headerTemplate: binding.headerTemplate,
|
|
367
|
+
queryTemplate: binding.queryTemplate,
|
|
368
|
+
scope,
|
|
369
|
+
scopeId: binding.scopeId ?? config.scopeId ?? null,
|
|
370
|
+
active: binding.active !== false,
|
|
371
|
+
source: "migration",
|
|
372
|
+
userId,
|
|
373
|
+
});
|
|
374
|
+
imported += 1;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return imported;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function listScriptConnections(context?: {
|
|
381
|
+
agentId?: string;
|
|
382
|
+
repoId?: string;
|
|
383
|
+
kind?: ScriptConnectionKind;
|
|
384
|
+
includeDisabled?: boolean;
|
|
385
|
+
}): ScriptConnectionRecord[] {
|
|
386
|
+
const rows = getDb()
|
|
387
|
+
.prepare<ConnectionRow, []>("SELECT * FROM script_connections ORDER BY slug ASC")
|
|
388
|
+
.all();
|
|
389
|
+
return rows
|
|
390
|
+
.map(connectionFromRow)
|
|
391
|
+
.filter((connection) => !context?.kind || connection.kind === context.kind)
|
|
392
|
+
.filter((connection) => context?.includeDisabled || connection.enabled)
|
|
393
|
+
.filter((connection) => !context || applies(connection.scope, connection.scopeId, context));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function schemaToTs(schema: unknown): string {
|
|
397
|
+
if (!schema || typeof schema !== "object") return "JsonValue";
|
|
398
|
+
const s = schema as Record<string, unknown>;
|
|
399
|
+
if (typeof s.$ref === "string") return "JsonValue";
|
|
400
|
+
if (Array.isArray(s.enum)) return s.enum.map((v) => JSON.stringify(v)).join(" | ") || "JsonValue";
|
|
401
|
+
const type = s.type;
|
|
402
|
+
if (type === "string") return "string";
|
|
403
|
+
if (type === "integer" || type === "number") return "number";
|
|
404
|
+
if (type === "boolean") return "boolean";
|
|
405
|
+
if (type === "array") return `${schemaToTs(s.items)}[]`;
|
|
406
|
+
if (type === "object" || s.properties) {
|
|
407
|
+
const required = new Set(
|
|
408
|
+
Array.isArray(s.required) ? s.required.filter((v): v is string => typeof v === "string") : [],
|
|
409
|
+
);
|
|
410
|
+
const props =
|
|
411
|
+
s.properties && typeof s.properties === "object"
|
|
412
|
+
? (s.properties as Record<string, unknown>)
|
|
413
|
+
: {};
|
|
414
|
+
const entries = Object.entries(props);
|
|
415
|
+
if (entries.length === 0) return "{ [key: string]: JsonValue }";
|
|
416
|
+
return `{ ${entries.map(([key, value]) => `${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaToTs(value)}`).join("; ")} }`;
|
|
417
|
+
}
|
|
418
|
+
return "JsonValue";
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function toJsonValue(value: unknown): ScriptApiJsonValue | undefined {
|
|
422
|
+
if (value === null) return null;
|
|
423
|
+
if (["boolean", "number", "string"].includes(typeof value)) return value as ScriptApiJsonValue;
|
|
424
|
+
if (Array.isArray(value)) {
|
|
425
|
+
return value
|
|
426
|
+
.map((item) => toJsonValue(item))
|
|
427
|
+
.filter((item): item is ScriptApiJsonValue => item !== undefined);
|
|
428
|
+
}
|
|
429
|
+
if (value && typeof value === "object") {
|
|
430
|
+
return Object.fromEntries(
|
|
431
|
+
Object.entries(value as Record<string, unknown>)
|
|
432
|
+
.map(([key, item]) => [key, toJsonValue(item)] as const)
|
|
433
|
+
.filter((entry): entry is readonly [string, ScriptApiJsonValue] => entry[1] !== undefined),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function jsonSchema(schema: unknown): ScriptApiJsonSchema {
|
|
440
|
+
if (typeof schema === "boolean") return schema;
|
|
441
|
+
const value = toJsonValue(schema);
|
|
442
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function methodName(operationId: string | undefined, method: string, path: string): string {
|
|
446
|
+
if (operationId) return normalizeSlug(operationId);
|
|
447
|
+
return normalizeSlug(`${method}_${path.replace(/[{}]/g, "").replace(/\//g, "_")}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function extractOperations(
|
|
451
|
+
spec: unknown,
|
|
452
|
+
slug: string,
|
|
453
|
+
): { operations: ScriptApiOperationDescriptor[]; types: string } {
|
|
454
|
+
if (!spec || typeof spec !== "object") throw new Error("OpenAPI spec must be an object");
|
|
455
|
+
const root = spec as Record<string, unknown>;
|
|
456
|
+
const paths = root.paths;
|
|
457
|
+
if (!paths || typeof paths !== "object") throw new Error("OpenAPI spec must contain paths");
|
|
458
|
+
const operations: ScriptApiOperationDescriptor[] = [];
|
|
459
|
+
const typeBlocks: string[] = [];
|
|
460
|
+
const verbs = new Set(["get", "post", "put", "patch", "delete"]);
|
|
461
|
+
|
|
462
|
+
for (const [path, pathItem] of Object.entries(paths as Record<string, unknown>)) {
|
|
463
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
464
|
+
const inheritedParams = Array.isArray((pathItem as { parameters?: unknown }).parameters)
|
|
465
|
+
? (pathItem as { parameters: unknown[] }).parameters
|
|
466
|
+
: [];
|
|
467
|
+
for (const [method, op] of Object.entries(pathItem as Record<string, unknown>)) {
|
|
468
|
+
if (!verbs.has(method) || !op || typeof op !== "object") continue;
|
|
469
|
+
const operation = op as Record<string, unknown>;
|
|
470
|
+
const name = methodName(
|
|
471
|
+
typeof operation.operationId === "string" ? operation.operationId : undefined,
|
|
472
|
+
method,
|
|
473
|
+
path,
|
|
474
|
+
);
|
|
475
|
+
const typeBase = `${pascal(slug)}${pascal(name)}`;
|
|
476
|
+
const parameters = [
|
|
477
|
+
...inheritedParams,
|
|
478
|
+
...(Array.isArray(operation.parameters) ? operation.parameters : []),
|
|
479
|
+
]
|
|
480
|
+
.filter((param): param is Record<string, unknown> =>
|
|
481
|
+
Boolean(
|
|
482
|
+
param &&
|
|
483
|
+
typeof param === "object" &&
|
|
484
|
+
typeof (param as Record<string, unknown>).name === "string",
|
|
485
|
+
),
|
|
486
|
+
)
|
|
487
|
+
.map((param) => ({
|
|
488
|
+
name: String(param.name),
|
|
489
|
+
in: (["path", "query", "header"].includes(String(param.in))
|
|
490
|
+
? String(param.in)
|
|
491
|
+
: "query") as "path" | "query" | "header",
|
|
492
|
+
required: param.required === true,
|
|
493
|
+
type: schemaToTs(param.schema),
|
|
494
|
+
schema: jsonSchema(param.schema),
|
|
495
|
+
}));
|
|
496
|
+
const requestBody =
|
|
497
|
+
operation.requestBody && typeof operation.requestBody === "object"
|
|
498
|
+
? (operation.requestBody as Record<string, unknown>)
|
|
499
|
+
: null;
|
|
500
|
+
const jsonContent =
|
|
501
|
+
requestBody && typeof requestBody.content === "object"
|
|
502
|
+
? (requestBody.content as Record<string, { schema?: unknown }>)["application/json"]
|
|
503
|
+
: undefined;
|
|
504
|
+
const requestBodySchema = jsonContent?.schema ? jsonSchema(jsonContent.schema) : undefined;
|
|
505
|
+
const bodyType = jsonContent?.schema ? schemaToTs(jsonContent.schema) : "JsonValue";
|
|
506
|
+
const responses =
|
|
507
|
+
operation.responses && typeof operation.responses === "object"
|
|
508
|
+
? (operation.responses as Record<string, unknown>)
|
|
509
|
+
: {};
|
|
510
|
+
const successStatus =
|
|
511
|
+
Object.keys(responses).find((status) => /^2\d\d$/.test(status)) ?? "default";
|
|
512
|
+
const response = responses[successStatus];
|
|
513
|
+
const responseContent =
|
|
514
|
+
response &&
|
|
515
|
+
typeof response === "object" &&
|
|
516
|
+
typeof (response as { content?: unknown }).content === "object"
|
|
517
|
+
? (response as { content: Record<string, { schema?: unknown }> }).content[
|
|
518
|
+
"application/json"
|
|
519
|
+
]
|
|
520
|
+
: undefined;
|
|
521
|
+
const responseType = responseContent?.schema
|
|
522
|
+
? schemaToTs(responseContent.schema)
|
|
523
|
+
: "JsonValue";
|
|
524
|
+
const responseSchema = responseContent?.schema ? jsonSchema(responseContent.schema) : {};
|
|
525
|
+
const paramsByPlace = (place: "path" | "query" | "header") =>
|
|
526
|
+
parameters
|
|
527
|
+
.filter((param) => param.in === place)
|
|
528
|
+
.map(
|
|
529
|
+
(param) => `${JSON.stringify(param.name)}${param.required ? "" : "?"}: ${param.type}`,
|
|
530
|
+
)
|
|
531
|
+
.join("; ");
|
|
532
|
+
const hasBody = Boolean(jsonContent?.schema || requestBody);
|
|
533
|
+
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
|
+
const responseDecl = `export type ${typeBase}Response = ${responseType};`;
|
|
535
|
+
typeBlocks.push(requestType, responseDecl);
|
|
536
|
+
operations.push({
|
|
537
|
+
name,
|
|
538
|
+
method: method.toUpperCase(),
|
|
539
|
+
path,
|
|
540
|
+
parameters: parameters.map(({ name, in: where, required, schema }) => ({
|
|
541
|
+
name,
|
|
542
|
+
in: where,
|
|
543
|
+
required,
|
|
544
|
+
schema,
|
|
545
|
+
})),
|
|
546
|
+
hasBody,
|
|
547
|
+
successStatus,
|
|
548
|
+
requestBodySchema,
|
|
549
|
+
responseSchema,
|
|
550
|
+
requestType: `${typeBase}Args`,
|
|
551
|
+
responseType: `${typeBase}Response`,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (operations.length === 0) throw new Error("OpenAPI spec did not contain supported operations");
|
|
557
|
+
return { operations, types: typeBlocks.join("\n") };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function buildGeneratedArtifacts(input: {
|
|
561
|
+
slug: string;
|
|
562
|
+
baseUrl: string;
|
|
563
|
+
credentialBinding: ScriptCredentialBindingRecord | null;
|
|
564
|
+
openapiSpec: unknown;
|
|
565
|
+
}): { generatedTypes: string; generatedRuntimeJson: string } {
|
|
566
|
+
const slug = normalizeSlug(input.slug);
|
|
567
|
+
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;
|
|
575
|
+
const descriptor: ScriptApiConnectionDescriptor = {
|
|
576
|
+
slug,
|
|
577
|
+
baseUrl: input.baseUrl,
|
|
578
|
+
credential,
|
|
579
|
+
operations,
|
|
580
|
+
};
|
|
581
|
+
const generatedTypes = [
|
|
582
|
+
types,
|
|
583
|
+
`export interface ${pascal(slug)}Api {`,
|
|
584
|
+
...operations.map(
|
|
585
|
+
(operation) =>
|
|
586
|
+
` ${operation.name}(args: ${operation.requestType}): Promise<${operation.responseType}>;`,
|
|
587
|
+
),
|
|
588
|
+
"}",
|
|
589
|
+
].join("\n");
|
|
590
|
+
return { generatedTypes, generatedRuntimeJson: JSON.stringify(descriptor) };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export function upsertScriptConnection(data: {
|
|
594
|
+
id?: string;
|
|
595
|
+
slug: string;
|
|
596
|
+
displayName?: string | null;
|
|
597
|
+
kind: ScriptConnectionKind;
|
|
598
|
+
scope?: ScriptConnectionScope;
|
|
599
|
+
scopeId?: string | null;
|
|
600
|
+
baseUrl?: string | null;
|
|
601
|
+
allowedHosts?: string[];
|
|
602
|
+
credentialBindingId?: string | null;
|
|
603
|
+
openapiSpecSourceKind?: "url" | "inline" | "agent_fs" | null;
|
|
604
|
+
openapiSpecSource?: string | null;
|
|
605
|
+
openapiSpecJson?: string | null;
|
|
606
|
+
mcpServerId?: string | null;
|
|
607
|
+
enabled?: boolean;
|
|
608
|
+
userId?: string | null;
|
|
609
|
+
}): ScriptConnectionRecord {
|
|
610
|
+
const now = new Date().toISOString();
|
|
611
|
+
const id = data.id ?? crypto.randomUUID();
|
|
612
|
+
const scope = data.scope ?? "global";
|
|
613
|
+
const scopeId = scope === "global" ? null : (data.scopeId ?? null);
|
|
614
|
+
let generatedTypes: string | null = null;
|
|
615
|
+
let generatedRuntimeJson: string | null = null;
|
|
616
|
+
let generationError: string | null = null;
|
|
617
|
+
let generatedAt: string | null = null;
|
|
618
|
+
|
|
619
|
+
if (data.kind === "openapi") {
|
|
620
|
+
try {
|
|
621
|
+
const spec = JSON.parse(data.openapiSpecJson ?? "{}");
|
|
622
|
+
const binding = data.credentialBindingId
|
|
623
|
+
? getCredentialBindingById(data.credentialBindingId)
|
|
624
|
+
: null;
|
|
625
|
+
const artifacts = buildGeneratedArtifacts({
|
|
626
|
+
slug: data.slug,
|
|
627
|
+
baseUrl: data.baseUrl ?? "",
|
|
628
|
+
credentialBinding: binding,
|
|
629
|
+
openapiSpec: spec,
|
|
630
|
+
});
|
|
631
|
+
generatedTypes = artifacts.generatedTypes;
|
|
632
|
+
generatedRuntimeJson = artifacts.generatedRuntimeJson;
|
|
633
|
+
generatedAt = now;
|
|
634
|
+
} catch (err) {
|
|
635
|
+
generationError = err instanceof Error ? err.message : String(err);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const existing = data.id
|
|
640
|
+
? getDb()
|
|
641
|
+
.prepare<{ id: string; version: number }, [string]>(
|
|
642
|
+
"SELECT id, version FROM script_connections WHERE id = ?",
|
|
643
|
+
)
|
|
644
|
+
.get(data.id)
|
|
645
|
+
: getDb()
|
|
646
|
+
.prepare<{ id: string; version: number }, [string, string, string | null]>(
|
|
647
|
+
"SELECT id, version FROM script_connections WHERE slug = ? AND scope = ? AND COALESCE(scope_id, '') = COALESCE(?, '')",
|
|
648
|
+
)
|
|
649
|
+
.get(normalizeSlug(data.slug), scope, scopeId);
|
|
650
|
+
|
|
651
|
+
const params = [
|
|
652
|
+
normalizeSlug(data.slug),
|
|
653
|
+
data.displayName ?? null,
|
|
654
|
+
data.kind,
|
|
655
|
+
scope,
|
|
656
|
+
scopeId,
|
|
657
|
+
data.baseUrl ?? null,
|
|
658
|
+
JSON.stringify(data.allowedHosts ?? []),
|
|
659
|
+
data.credentialBindingId ?? null,
|
|
660
|
+
data.openapiSpecSourceKind ?? (data.kind === "openapi" ? "inline" : null),
|
|
661
|
+
data.openapiSpecSource ?? null,
|
|
662
|
+
data.openapiSpecJson ?? null,
|
|
663
|
+
data.mcpServerId ?? null,
|
|
664
|
+
generatedTypes,
|
|
665
|
+
generatedRuntimeJson,
|
|
666
|
+
generatedAt,
|
|
667
|
+
generationError,
|
|
668
|
+
data.enabled === false ? 0 : 1,
|
|
669
|
+
] as const;
|
|
670
|
+
|
|
671
|
+
if (existing) {
|
|
672
|
+
const row = getDb()
|
|
673
|
+
.prepare<ConnectionRow, [...typeof params, string, string | null, number, string]>(
|
|
674
|
+
`UPDATE script_connections SET
|
|
675
|
+
slug = ?, display_name = ?, kind = ?, scope = ?, scope_id = ?, base_url = ?,
|
|
676
|
+
allowed_hosts_json = ?, credential_binding_id = ?, openapi_spec_source_kind = ?,
|
|
677
|
+
openapi_spec_source = ?, openapi_spec_json = ?, mcp_server_id = ?, generated_types = ?,
|
|
678
|
+
generated_runtime_json = ?, generated_at = ?, generation_error = ?, enabled = ?,
|
|
679
|
+
updated_at = ?, updated_by = ?, version = ?
|
|
680
|
+
WHERE id = ? RETURNING *`,
|
|
681
|
+
)
|
|
682
|
+
.get(...params, now, data.userId ?? null, existing.version + 1, existing.id);
|
|
683
|
+
if (!row) throw new Error("Failed to update script connection");
|
|
684
|
+
return connectionFromRow(row);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const row = getDb()
|
|
688
|
+
.prepare<
|
|
689
|
+
ConnectionRow,
|
|
690
|
+
[string, ...typeof params, string, string, string | null, string | null]
|
|
691
|
+
>(
|
|
692
|
+
`INSERT INTO script_connections
|
|
693
|
+
(id, slug, display_name, kind, scope, scope_id, base_url, allowed_hosts_json,
|
|
694
|
+
credential_binding_id, openapi_spec_source_kind, openapi_spec_source, openapi_spec_json,
|
|
695
|
+
mcp_server_id, generated_types, generated_runtime_json, generated_at, generation_error,
|
|
696
|
+
enabled, created_at, updated_at, created_by, updated_by)
|
|
697
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
698
|
+
)
|
|
699
|
+
.get(id, ...params, now, now, data.userId ?? null, data.userId ?? null);
|
|
700
|
+
if (!row) throw new Error("Failed to create script connection");
|
|
701
|
+
return connectionFromRow(row);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export function setScriptConnectionEnabled(
|
|
705
|
+
id: string,
|
|
706
|
+
enabled: boolean,
|
|
707
|
+
userId?: string | null,
|
|
708
|
+
): ScriptConnectionRecord | null {
|
|
709
|
+
const row = getDb()
|
|
710
|
+
.prepare<ConnectionRow, [number, string, string | null, string]>(
|
|
711
|
+
`UPDATE script_connections
|
|
712
|
+
SET enabled = ?, updated_at = ?, updated_by = ?, version = version + 1
|
|
713
|
+
WHERE id = ? RETURNING *`,
|
|
714
|
+
)
|
|
715
|
+
.get(enabled ? 1 : 0, new Date().toISOString(), userId ?? null, id);
|
|
716
|
+
return row ? connectionFromRow(row) : null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
export function getScriptApiConnectionDescriptors(
|
|
720
|
+
context: { agentId?: string; repoId?: string } = {},
|
|
721
|
+
): ScriptApiConnectionDescriptor[] {
|
|
722
|
+
return listScriptConnections({ ...context, kind: "openapi" })
|
|
723
|
+
.map((connection) => {
|
|
724
|
+
if (!connection.generatedRuntimeJson) return null;
|
|
725
|
+
try {
|
|
726
|
+
return JSON.parse(connection.generatedRuntimeJson) as ScriptApiConnectionDescriptor;
|
|
727
|
+
} catch {
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
})
|
|
731
|
+
.filter((descriptor): descriptor is ScriptApiConnectionDescriptor => Boolean(descriptor));
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export function getScriptApiTypes(context: { agentId?: string; repoId?: string } = {}): string {
|
|
735
|
+
const connections = listScriptConnections({ ...context, kind: "openapi" });
|
|
736
|
+
const blocks = connections
|
|
737
|
+
.filter((connection) => connection.generatedTypes)
|
|
738
|
+
.map((connection) => connection.generatedTypes as string);
|
|
739
|
+
if (blocks.length === 0) return "export interface ScriptApiRegistry {}\n";
|
|
740
|
+
const members = connections
|
|
741
|
+
.filter((connection) => connection.generatedTypes)
|
|
742
|
+
.map((connection) => ` ${connection.slug}: ${pascal(connection.slug)}Api;`);
|
|
743
|
+
return `${blocks.join("\n\n")}\n\nexport interface ScriptApiRegistry {\n${members.join("\n")}\n}\n`;
|
|
744
|
+
}
|