@squadbase/vite-server 0.1.4 → 0.1.5-dev.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.
@@ -0,0 +1,862 @@
1
+ // ../connectors/src/parameter-definition.ts
2
+ var ParameterDefinition = class {
3
+ slug;
4
+ name;
5
+ description;
6
+ envVarBaseKey;
7
+ type;
8
+ secret;
9
+ required;
10
+ constructor(config) {
11
+ this.slug = config.slug;
12
+ this.name = config.name;
13
+ this.description = config.description;
14
+ this.envVarBaseKey = config.envVarBaseKey;
15
+ this.type = config.type;
16
+ this.secret = config.secret;
17
+ this.required = config.required;
18
+ }
19
+ /**
20
+ * Get the parameter value from a ConnectorConnectionObject.
21
+ */
22
+ getValue(connection2) {
23
+ const param = connection2.parameters.find(
24
+ (p) => p.parameterSlug === this.slug
25
+ );
26
+ if (!param || param.value == null) {
27
+ throw new Error(
28
+ `Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
29
+ );
30
+ }
31
+ return param.value;
32
+ }
33
+ /**
34
+ * Try to get the parameter value. Returns undefined if not found (for optional params).
35
+ */
36
+ tryGetValue(connection2) {
37
+ const param = connection2.parameters.find(
38
+ (p) => p.parameterSlug === this.slug
39
+ );
40
+ if (!param || param.value == null) return void 0;
41
+ return param.value;
42
+ }
43
+ };
44
+
45
+ // ../connectors/src/connectors/salesforce/parameters.ts
46
+ var parameters = {
47
+ username: new ParameterDefinition({
48
+ slug: "username",
49
+ name: "Username",
50
+ description: "Your Salesforce account username (the email you use to sign in).",
51
+ envVarBaseKey: "SALESFORCE_USERNAME",
52
+ type: "text",
53
+ secret: false,
54
+ required: true
55
+ }),
56
+ password: new ParameterDefinition({
57
+ slug: "password",
58
+ name: "Password",
59
+ description: "Your Salesforce account password concatenated with your security token (password + securityToken). The security token is emailed to you when you reset it from Settings \u2192 My Personal Information \u2192 Reset My Security Token.",
60
+ envVarBaseKey: "SALESFORCE_PASSWORD",
61
+ type: "text",
62
+ secret: true,
63
+ required: true
64
+ }),
65
+ clientId: new ParameterDefinition({
66
+ slug: "client-id",
67
+ name: "Consumer Key",
68
+ description: "The Consumer Key (client_id) of your Salesforce Connected App. Enable OAuth Settings and 'Allow OAuth Username-Password Flows' in your org's identity settings.",
69
+ envVarBaseKey: "SALESFORCE_CLIENT_ID",
70
+ type: "text",
71
+ secret: false,
72
+ required: true
73
+ }),
74
+ clientSecret: new ParameterDefinition({
75
+ slug: "client-secret",
76
+ name: "Consumer Secret",
77
+ description: "The Consumer Secret (client_secret) of your Salesforce Connected App.",
78
+ envVarBaseKey: "SALESFORCE_CLIENT_SECRET",
79
+ type: "text",
80
+ secret: true,
81
+ required: true
82
+ }),
83
+ isSandbox: new ParameterDefinition({
84
+ slug: "is-sandbox",
85
+ name: "Use Sandbox",
86
+ description: 'Set to "true" to authenticate against a Salesforce sandbox (test.salesforce.com) instead of production (login.salesforce.com). Defaults to "false".',
87
+ envVarBaseKey: "SALESFORCE_IS_SANDBOX",
88
+ type: "text",
89
+ secret: false,
90
+ required: false
91
+ })
92
+ };
93
+
94
+ // ../connectors/src/connectors/salesforce/sdk/index.ts
95
+ var DEFAULT_API_VERSION = "60.0";
96
+ async function fetchAccessToken(loginHost, clientId, clientSecret, username, password) {
97
+ const body = new URLSearchParams({
98
+ grant_type: "password",
99
+ client_id: clientId,
100
+ client_secret: clientSecret,
101
+ username,
102
+ password
103
+ });
104
+ const res = await fetch(`${loginHost}/services/oauth2/token`, {
105
+ method: "POST",
106
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
107
+ body: body.toString()
108
+ });
109
+ if (!res.ok) {
110
+ const errText = await res.text().catch(() => "(unreadable body)");
111
+ throw new Error(
112
+ `salesforce: failed to obtain access token: ${res.status} ${res.statusText} \u2014 ${errText}`
113
+ );
114
+ }
115
+ const json = await res.json();
116
+ if (!json.access_token || !json.instance_url) {
117
+ throw new Error(
118
+ "salesforce: access_token or instance_url not found in token response"
119
+ );
120
+ }
121
+ return { accessToken: json.access_token, instanceUrl: json.instance_url };
122
+ }
123
+ function createClient(params) {
124
+ const username = params[parameters.username.slug];
125
+ const password = params[parameters.password.slug];
126
+ const clientId = params[parameters.clientId.slug];
127
+ const clientSecret = params[parameters.clientSecret.slug];
128
+ const isSandbox = (params[parameters.isSandbox.slug] ?? "").toLowerCase() === "true";
129
+ for (const [slug, value] of [
130
+ [parameters.username.slug, username],
131
+ [parameters.password.slug, password],
132
+ [parameters.clientId.slug, clientId],
133
+ [parameters.clientSecret.slug, clientSecret]
134
+ ]) {
135
+ if (!value) {
136
+ throw new Error(`salesforce: missing required parameter: ${slug}`);
137
+ }
138
+ }
139
+ const loginHost = isSandbox ? "https://test.salesforce.com" : "https://login.salesforce.com";
140
+ async function getToken() {
141
+ return fetchAccessToken(
142
+ loginHost,
143
+ clientId,
144
+ clientSecret,
145
+ username,
146
+ password
147
+ );
148
+ }
149
+ async function authFetch(path2, init) {
150
+ const { accessToken, instanceUrl } = await getToken();
151
+ const url = path2.startsWith("http") ? path2 : `${instanceUrl}${path2.startsWith("/") ? "" : "/"}${path2}`;
152
+ const headers = new Headers(init?.headers);
153
+ headers.set("Authorization", `Bearer ${accessToken}`);
154
+ if (!headers.has("Content-Type") && init?.body) {
155
+ headers.set("Content-Type", "application/json");
156
+ }
157
+ return fetch(url, { ...init, headers });
158
+ }
159
+ async function assertOk(res, label) {
160
+ if (!res.ok) {
161
+ const body = await res.text().catch(() => "(unreadable body)");
162
+ throw new Error(
163
+ `salesforce ${label}: ${res.status} ${res.statusText} \u2014 ${body}`
164
+ );
165
+ }
166
+ }
167
+ const apiBase = `/services/data/v${DEFAULT_API_VERSION}`;
168
+ return {
169
+ request(path2, init) {
170
+ return authFetch(path2, init);
171
+ },
172
+ async query(soql) {
173
+ const res = await authFetch(
174
+ `${apiBase}/query?q=${encodeURIComponent(soql)}`,
175
+ { method: "GET" }
176
+ );
177
+ await assertOk(res, "query");
178
+ return await res.json();
179
+ },
180
+ async queryMore(nextRecordsUrl) {
181
+ const res = await authFetch(nextRecordsUrl, { method: "GET" });
182
+ await assertOk(res, "queryMore");
183
+ return await res.json();
184
+ },
185
+ async describeSObject(objectType) {
186
+ const res = await authFetch(
187
+ `${apiBase}/sobjects/${encodeURIComponent(objectType)}/describe`,
188
+ { method: "GET" }
189
+ );
190
+ await assertOk(res, "describeSObject");
191
+ return await res.json();
192
+ },
193
+ async getRecord(objectType, id, options) {
194
+ const qs = options?.fields?.length ? `?fields=${encodeURIComponent(options.fields.join(","))}` : "";
195
+ const res = await authFetch(
196
+ `${apiBase}/sobjects/${encodeURIComponent(objectType)}/${encodeURIComponent(id)}${qs}`,
197
+ { method: "GET" }
198
+ );
199
+ await assertOk(res, "getRecord");
200
+ return await res.json();
201
+ },
202
+ async createRecord(objectType, fields) {
203
+ const res = await authFetch(
204
+ `${apiBase}/sobjects/${encodeURIComponent(objectType)}`,
205
+ {
206
+ method: "POST",
207
+ headers: { "Content-Type": "application/json" },
208
+ body: JSON.stringify(fields)
209
+ }
210
+ );
211
+ await assertOk(res, "createRecord");
212
+ return await res.json();
213
+ },
214
+ async updateRecord(objectType, id, fields) {
215
+ const res = await authFetch(
216
+ `${apiBase}/sobjects/${encodeURIComponent(objectType)}/${encodeURIComponent(id)}`,
217
+ {
218
+ method: "PATCH",
219
+ headers: { "Content-Type": "application/json" },
220
+ body: JSON.stringify(fields)
221
+ }
222
+ );
223
+ await assertOk(res, "updateRecord");
224
+ },
225
+ async deleteRecord(objectType, id) {
226
+ const res = await authFetch(
227
+ `${apiBase}/sobjects/${encodeURIComponent(objectType)}/${encodeURIComponent(id)}`,
228
+ { method: "DELETE" }
229
+ );
230
+ await assertOk(res, "deleteRecord");
231
+ }
232
+ };
233
+ }
234
+
235
+ // ../connectors/src/connector-onboarding.ts
236
+ var ConnectorOnboarding = class {
237
+ /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
238
+ connectionSetupInstructions;
239
+ /** Phase 2: Data overview instructions */
240
+ dataOverviewInstructions;
241
+ constructor(config) {
242
+ this.connectionSetupInstructions = config.connectionSetupInstructions;
243
+ this.dataOverviewInstructions = config.dataOverviewInstructions;
244
+ }
245
+ getConnectionSetupPrompt(language) {
246
+ return this.connectionSetupInstructions?.[language] ?? null;
247
+ }
248
+ getDataOverviewInstructions(language) {
249
+ return this.dataOverviewInstructions[language];
250
+ }
251
+ };
252
+
253
+ // ../connectors/src/connector-tool.ts
254
+ var ConnectorTool = class {
255
+ name;
256
+ description;
257
+ inputSchema;
258
+ outputSchema;
259
+ _execute;
260
+ constructor(config) {
261
+ this.name = config.name;
262
+ this.description = config.description;
263
+ this.inputSchema = config.inputSchema;
264
+ this.outputSchema = config.outputSchema;
265
+ this._execute = config.execute;
266
+ }
267
+ createTool(connections, config) {
268
+ return {
269
+ description: this.description,
270
+ inputSchema: this.inputSchema,
271
+ outputSchema: this.outputSchema,
272
+ execute: (input) => this._execute(input, connections, config)
273
+ };
274
+ }
275
+ };
276
+
277
+ // ../connectors/src/connector-plugin.ts
278
+ var ConnectorPlugin = class _ConnectorPlugin {
279
+ slug;
280
+ authType;
281
+ name;
282
+ description;
283
+ iconUrl;
284
+ parameters;
285
+ releaseFlag;
286
+ proxyPolicy;
287
+ experimentalAttributes;
288
+ onboarding;
289
+ systemPrompt;
290
+ tools;
291
+ query;
292
+ checkConnection;
293
+ constructor(config) {
294
+ this.slug = config.slug;
295
+ this.authType = config.authType;
296
+ this.name = config.name;
297
+ this.description = config.description;
298
+ this.iconUrl = config.iconUrl;
299
+ this.parameters = config.parameters;
300
+ this.releaseFlag = config.releaseFlag;
301
+ this.proxyPolicy = config.proxyPolicy;
302
+ this.experimentalAttributes = config.experimentalAttributes;
303
+ this.onboarding = config.onboarding;
304
+ this.systemPrompt = config.systemPrompt;
305
+ this.tools = config.tools;
306
+ this.query = config.query;
307
+ this.checkConnection = config.checkConnection;
308
+ }
309
+ get connectorKey() {
310
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
311
+ }
312
+ /**
313
+ * Create tools for connections that belong to this connector.
314
+ * Filters connections by connectorKey internally.
315
+ * Returns tools keyed as `${connectorKey}_${toolName}`.
316
+ */
317
+ createTools(connections, config, opts) {
318
+ const myConnections = connections.filter(
319
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
320
+ );
321
+ const result = {};
322
+ for (const t of Object.values(this.tools)) {
323
+ const tool = t.createTool(myConnections, config);
324
+ const originalToModelOutput = tool.toModelOutput;
325
+ result[`${this.connectorKey}_${t.name}`] = {
326
+ ...tool,
327
+ toModelOutput: async (options) => {
328
+ if (!originalToModelOutput) {
329
+ return opts.truncateOutput(options.output);
330
+ }
331
+ const modelOutput = await originalToModelOutput(options);
332
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
333
+ return opts.truncateOutput(modelOutput.value);
334
+ }
335
+ return modelOutput;
336
+ }
337
+ };
338
+ }
339
+ return result;
340
+ }
341
+ static deriveKey(slug, authType) {
342
+ if (authType) return `${slug}-${authType}`;
343
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
344
+ // user-password
345
+ "postgresql": "user-password",
346
+ "mysql": "user-password",
347
+ "clickhouse": "user-password",
348
+ "kintone": "user-password",
349
+ "squadbase-db": "user-password",
350
+ // service-account
351
+ "snowflake": "service-account",
352
+ "bigquery": "service-account",
353
+ "google-analytics": "service-account",
354
+ "google-calendar": "service-account",
355
+ "aws-athena": "service-account",
356
+ "redshift": "service-account",
357
+ // api-key
358
+ "databricks": "api-key",
359
+ "dbt": "api-key",
360
+ "airtable": "api-key",
361
+ "openai": "api-key",
362
+ "gemini": "api-key",
363
+ "anthropic": "api-key",
364
+ "wix-store": "api-key"
365
+ };
366
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
367
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
368
+ return slug;
369
+ }
370
+ };
371
+
372
+ // ../connectors/src/auth-types.ts
373
+ var AUTH_TYPES = {
374
+ OAUTH: "oauth",
375
+ API_KEY: "api-key",
376
+ JWT: "jwt",
377
+ SERVICE_ACCOUNT: "service-account",
378
+ PAT: "pat",
379
+ USER_PASSWORD: "user-password"
380
+ };
381
+
382
+ // ../connectors/src/connectors/salesforce/setup.ts
383
+ var salesforceOnboarding = new ConnectorOnboarding({
384
+ connectionSetupInstructions: {
385
+ en: `#### Create a Connected App in Salesforce
386
+ 1. In Salesforce Setup, go to App Manager \u2192 New Connected App
387
+ 2. Under API (Enable OAuth Settings), check "Enable OAuth Settings"
388
+ 3. Add OAuth scopes: "Manage user data via APIs (api)" and "Perform requests at any time (refresh_token, offline_access)"
389
+ 4. Save and note the Consumer Key (client_id) and Consumer Secret (client_secret)
390
+
391
+ #### Allow Username-Password Flow
392
+ 1. Go to Setup \u2192 Identity \u2192 OAuth and OpenID Connect Settings
393
+ 2. Enable "Allow OAuth Username-Password Flows"
394
+
395
+ #### Reset your Security Token
396
+ 1. Go to Settings \u2192 My Personal Information \u2192 Reset My Security Token
397
+ 2. Salesforce emails you a new security token \u2014 append it to your password when entering the Password parameter (password + securityToken)
398
+
399
+ #### Sandbox vs Production
400
+ - Leave Use Sandbox as "false" (or empty) to connect to production (login.salesforce.com)
401
+ - Set Use Sandbox to "true" to connect to a sandbox (test.salesforce.com)`,
402
+ ja: `#### Salesforce \u3067 Connected App \u3092\u4F5C\u6210
403
+ 1. Setup \u2192 App Manager \u2192 New Connected App
404
+ 2. API (Enable OAuth Settings) \u30BB\u30AF\u30B7\u30E7\u30F3\u3067 "Enable OAuth Settings" \u3092\u6709\u52B9\u5316
405
+ 3. OAuth \u30B9\u30B3\u30FC\u30D7\u306B "Manage user data via APIs (api)" \u3068 "Perform requests at any time (refresh_token, offline_access)" \u3092\u8FFD\u52A0
406
+ 4. \u4FDD\u5B58\u5F8C\u3001Consumer Key (client_id) \u3068 Consumer Secret (client_secret) \u3092\u63A7\u3048\u308B
407
+
408
+ #### Username-Password Flow \u3092\u8A31\u53EF
409
+ 1. Setup \u2192 Identity \u2192 OAuth and OpenID Connect Settings
410
+ 2. "Allow OAuth Username-Password Flows" \u3092\u6709\u52B9\u5316
411
+
412
+ #### Security Token \u306E\u767A\u884C
413
+ 1. \u500B\u4EBA\u8A2D\u5B9A \u2192 My Personal Information \u2192 Reset My Security Token
414
+ 2. Salesforce \u304B\u3089\u9001\u3089\u308C\u308B\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30C8\u30FC\u30AF\u30F3\u3092\u30D1\u30B9\u30EF\u30FC\u30C9\u306B\u9023\u7D50\u3057\u3066\u5165\u529B\uFF08password + securityToken\uFF09
415
+
416
+ #### Sandbox / Production
417
+ - \u672C\u756A (login.salesforce.com) \u306E\u5834\u5408: Use Sandbox \u3092 "false" \u307E\u305F\u306F\u672A\u5165\u529B
418
+ - Sandbox (test.salesforce.com) \u306E\u5834\u5408: Use Sandbox \u3092 "true" \u306B\u8A2D\u5B9A`
419
+ },
420
+ dataOverviewInstructions: {
421
+ en: `1. Call salesforce_request with GET /services/data/v60.0/sobjects/ to list available sObjects (standard + custom)
422
+ 2. Call salesforce_request with GET /services/data/v60.0/sobjects/Account/describe to inspect Account fields; repeat for Contact, Opportunity, Lead as needed
423
+ 3. Run a sample SOQL query: GET /services/data/v60.0/query?q=SELECT+Id,Name,Industry+FROM+Account+LIMIT+5 to verify access and explore data`,
424
+ ja: `1. salesforce_request \u3067 GET /services/data/v60.0/sobjects/ \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A sObject\uFF08\u6A19\u6E96 + \u30AB\u30B9\u30BF\u30E0\uFF09\u3092\u4E00\u89A7\u53D6\u5F97
425
+ 2. salesforce_request \u3067 GET /services/data/v60.0/sobjects/Account/describe \u3092\u547C\u3073\u51FA\u3057 Account \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u78BA\u8A8D\u3002Contact / Opportunity / Lead \u306A\u3069\u5FC5\u8981\u306A sObject \u306B\u5BFE\u3057\u3066\u540C\u69D8\u306B\u5B9F\u884C
426
+ 3. \u30B5\u30F3\u30D7\u30EB SOQL \u3092\u5B9F\u884C: GET /services/data/v60.0/query?q=SELECT+Id,Name,Industry+FROM+Account+LIMIT+5 \u3067\u30A2\u30AF\u30BB\u30B9\u53EF\u5426\u3068\u30C7\u30FC\u30BF\u69CB\u9020\u3092\u78BA\u8A8D`
427
+ }
428
+ });
429
+
430
+ // ../connectors/src/connectors/salesforce/tools/request.ts
431
+ import { z } from "zod";
432
+ var REQUEST_TIMEOUT_MS = 6e4;
433
+ var inputSchema = z.object({
434
+ toolUseIntent: z.string().optional().describe(
435
+ "Brief description of what you intend to accomplish with this tool call"
436
+ ),
437
+ connectionId: z.string().describe("ID of the Salesforce connection to use"),
438
+ method: z.enum(["GET", "POST", "PATCH", "DELETE"]).describe(
439
+ "HTTP method. GET for reading resources and SOQL queries, POST for creating, PATCH for updating, DELETE for removing."
440
+ ),
441
+ path: z.string().describe(
442
+ "API path appended to the instance URL (e.g., '/services/data/v60.0/sobjects/Account', '/services/data/v60.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+5'). Always start the path with '/services/data/'."
443
+ ),
444
+ body: z.record(z.string(), z.unknown()).optional().describe("Request body (JSON) for POST/PATCH requests")
445
+ });
446
+ var outputSchema = z.discriminatedUnion("success", [
447
+ z.object({
448
+ success: z.literal(true),
449
+ status: z.number(),
450
+ data: z.record(z.string(), z.unknown())
451
+ }),
452
+ z.object({
453
+ success: z.literal(false),
454
+ error: z.string()
455
+ })
456
+ ]);
457
+ var requestTool = new ConnectorTool({
458
+ name: "request",
459
+ description: `Send authenticated requests to the Salesforce REST API.
460
+ Authentication is handled automatically using the OAuth 2.0 username-password flow (Connected App Consumer Key + Secret with the Salesforce account credentials). An access token and instance URL are obtained on each request, so the tool user only provides the API path.
461
+ Use this tool for all Salesforce interactions: describing sObjects, running SOQL queries (GET /services/data/vXX.X/query?q=...), reading/creating/updating standard (Account, Contact, Opportunity, Lead, Case) and custom objects.
462
+ Prefer SOQL via the /query endpoint for filtered, joined, or aggregated reads rather than paginating /sobjects/{Type} endpoints.`,
463
+ inputSchema,
464
+ outputSchema,
465
+ async execute({ connectionId, method, path: path2, body }, connections) {
466
+ const connection2 = connections.find((c) => c.id === connectionId);
467
+ if (!connection2) {
468
+ return {
469
+ success: false,
470
+ error: `Connection ${connectionId} not found`
471
+ };
472
+ }
473
+ console.log(
474
+ `[connector-request] salesforce/${connection2.name}: ${method} ${path2}`
475
+ );
476
+ try {
477
+ const username = parameters.username.getValue(connection2);
478
+ const password = parameters.password.getValue(connection2);
479
+ const clientId = parameters.clientId.getValue(connection2);
480
+ const clientSecret = parameters.clientSecret.getValue(connection2);
481
+ const isSandbox = parameters.isSandbox.tryGetValue(connection2)?.toLowerCase() === "true";
482
+ const loginHost = isSandbox ? "https://test.salesforce.com" : "https://login.salesforce.com";
483
+ const tokenBody = new URLSearchParams({
484
+ grant_type: "password",
485
+ client_id: clientId,
486
+ client_secret: clientSecret,
487
+ username,
488
+ password
489
+ });
490
+ const tokenRes = await fetch(`${loginHost}/services/oauth2/token`, {
491
+ method: "POST",
492
+ headers: {
493
+ "Content-Type": "application/x-www-form-urlencoded"
494
+ },
495
+ body: tokenBody.toString()
496
+ });
497
+ if (!tokenRes.ok) {
498
+ const errText = await tokenRes.text().catch(() => "(unreadable body)");
499
+ return {
500
+ success: false,
501
+ error: `Failed to obtain access token: ${tokenRes.status} ${tokenRes.statusText} \u2014 ${errText}`
502
+ };
503
+ }
504
+ const tokenJson = await tokenRes.json();
505
+ if (!tokenJson.access_token || !tokenJson.instance_url) {
506
+ return {
507
+ success: false,
508
+ error: "access_token or instance_url not found in token response"
509
+ };
510
+ }
511
+ const url = `${tokenJson.instance_url}${path2.startsWith("/") ? "" : "/"}${path2}`;
512
+ const controller = new AbortController();
513
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
514
+ try {
515
+ const response = await fetch(url, {
516
+ method,
517
+ headers: {
518
+ Authorization: `Bearer ${tokenJson.access_token}`,
519
+ "Content-Type": "application/json"
520
+ },
521
+ body: body ? JSON.stringify(body) : void 0,
522
+ signal: controller.signal
523
+ });
524
+ const text = await response.text();
525
+ let data;
526
+ try {
527
+ data = text ? JSON.parse(text) : {};
528
+ } catch {
529
+ data = { raw: text };
530
+ }
531
+ if (!response.ok) {
532
+ let errorMessage = `HTTP ${response.status} ${response.statusText}`;
533
+ if (Array.isArray(data) && data.length > 0) {
534
+ const first = data[0];
535
+ if (first.message) {
536
+ errorMessage = first.errorCode ? `${first.errorCode}: ${first.message}` : first.message;
537
+ }
538
+ } else if (typeof data.message === "string") {
539
+ errorMessage = data.message;
540
+ } else if (typeof data.error === "string") {
541
+ errorMessage = data.error;
542
+ }
543
+ return { success: false, error: errorMessage };
544
+ }
545
+ return { success: true, status: response.status, data };
546
+ } finally {
547
+ clearTimeout(timeout);
548
+ }
549
+ } catch (err) {
550
+ const msg = err instanceof Error ? err.message : String(err);
551
+ return { success: false, error: msg };
552
+ }
553
+ }
554
+ });
555
+
556
+ // ../connectors/src/connectors/salesforce/index.ts
557
+ var tools = { request: requestTool };
558
+ var salesforceConnector = new ConnectorPlugin({
559
+ slug: "salesforce",
560
+ authType: AUTH_TYPES.USER_PASSWORD,
561
+ name: "Salesforce",
562
+ description: "Connect to Salesforce CRM for accounts, contacts, opportunities, leads, cases, and custom objects via SOQL and the REST API.",
563
+ iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/6vZlbrUKhxXIiuvWJlb8YB/bbc5e08b88de46c8ed338a74c7d0abb3/salesforce-icon.png",
564
+ parameters,
565
+ releaseFlag: { dev1: true, dev2: false, prod: false },
566
+ onboarding: salesforceOnboarding,
567
+ systemPrompt: {
568
+ en: `### Tools
569
+
570
+ - \`salesforce_request\`: The only way to call the Salesforce REST API. Use it to run SOQL queries, describe sObjects, and read/create/update/delete standard (Account, Contact, Opportunity, Lead, Case) and custom objects. Authentication (OAuth 2.0 username-password flow against the Connected App) is configured automatically \u2014 an access token and the org's instance URL are resolved on each request. Prefer SOQL via \`GET /services/data/v60.0/query?q=...\` over paginating \`/sobjects/{Type}\` endpoints for filtered or joined reads.
571
+
572
+ ### Business Logic
573
+
574
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
575
+
576
+ SDK methods (client created via \`connection(connectionId)\`):
577
+ - \`client.request(path, init?)\` \u2014 low-level authenticated fetch against the Salesforce instance URL
578
+ - \`client.query(soql)\` \u2014 run a SOQL query and return \`{ totalSize, done, records, nextRecordsUrl? }\`
579
+ - \`client.queryMore(nextRecordsUrl)\` \u2014 fetch the next page of SOQL results
580
+ - \`client.describeSObject(objectType)\` \u2014 describe an sObject's metadata
581
+ - \`client.getRecord(objectType, id, options?)\` \u2014 fetch a single record (optionally restrict fields)
582
+ - \`client.createRecord(objectType, fields)\` \u2014 create a new record
583
+ - \`client.updateRecord(objectType, id, fields)\` \u2014 patch an existing record
584
+ - \`client.deleteRecord(objectType, id)\` \u2014 delete a record
585
+
586
+ \`\`\`ts
587
+ import type { Context } from "hono";
588
+ import { connection } from "@squadbase/vite-server/connectors/salesforce";
589
+
590
+ const salesforce = connection("<connectionId>");
591
+
592
+ export default async function handler(c: Context) {
593
+ const { industry, limit = 50 } = await c.req.json<{
594
+ industry?: string;
595
+ limit?: number;
596
+ }>();
597
+
598
+ const where = industry
599
+ ? \`WHERE Industry = '\${industry.replace(/'/g, "\\\\'")}'\`
600
+ : "";
601
+ const soql = \`SELECT Id, Name, Industry, AnnualRevenue FROM Account \${where} ORDER BY AnnualRevenue DESC NULLS LAST LIMIT \${limit}\`;
602
+
603
+ const { records } = await salesforce.query<{
604
+ Id: string;
605
+ Name: string;
606
+ Industry: string | null;
607
+ AnnualRevenue: number | null;
608
+ }>(soql);
609
+
610
+ return c.json({ accounts: records });
611
+ }
612
+ \`\`\`
613
+
614
+ ### Salesforce REST API Reference
615
+
616
+ - Login host: \`https://login.salesforce.com\` (production) or \`https://test.salesforce.com\` (sandbox)
617
+ - Token endpoint: \`POST /services/oauth2/token\` (grant_type=password + client_id/secret + username/password)
618
+ - Base path after login: \`{instance_url}/services/data/v60.0\`
619
+ - Authentication: Bearer token (handled automatically per request)
620
+ - Pagination (SOQL): follow \`nextRecordsUrl\` from the response (absolute path starting with \`/services/data/v60.0/query/...\`)
621
+
622
+ #### Common Endpoints
623
+ - GET \`/services/data/\` \u2014 List available API versions
624
+ - GET \`/services/data/v60.0/sobjects/\` \u2014 List all sObjects (standard + custom)
625
+ - GET \`/services/data/v60.0/sobjects/{Type}/describe\` \u2014 Describe an sObject (fields, relationships, picklists)
626
+ - GET \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 Get a record (supports \`?fields=...\`)
627
+ - POST \`/services/data/v60.0/sobjects/{Type}\` \u2014 Create a record
628
+ - PATCH \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 Update a record
629
+ - DELETE \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 Delete a record
630
+ - GET \`/services/data/v60.0/query?q={soql}\` \u2014 Run SOQL
631
+ - GET \`/services/data/v60.0/search?q={sosl}\` \u2014 Run SOSL
632
+
633
+ #### SOQL Reference
634
+ - SELECT column list is required (no \`SELECT *\`)
635
+ - Filter: \`WHERE\`, \`AND\` / \`OR\`, parent/child relationship fields (e.g., \`Account.Name\`)
636
+ - Sort: \`ORDER BY field [ASC|DESC] [NULLS FIRST|NULLS LAST]\`
637
+ - Paginate: \`LIMIT n\`, \`OFFSET m\`; for large result sets, follow \`nextRecordsUrl\` instead of OFFSET
638
+ - Aggregate: \`GROUP BY\`, \`HAVING\`, \`COUNT()\`, \`SUM()\`, \`AVG()\`, \`MIN()\`, \`MAX()\`
639
+ - Parent-to-child subquery: \`SELECT Id, Name, (SELECT Id, Email FROM Contacts) FROM Account\``,
640
+ ja: `### \u30C4\u30FC\u30EB
641
+
642
+ - \`salesforce_request\`: Salesforce REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002SOQL \u30AF\u30A8\u30EA\u306E\u5B9F\u884C\u3001sObject \u306E describe\u3001\u6A19\u6E96\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\uFF08Account, Contact, Opportunity, Lead, Case\uFF09\u3084\u30AB\u30B9\u30BF\u30E0\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306E\u8AAD\u307F\u66F8\u304D\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A8D\u8A3C\uFF08Connected App + OAuth 2.0 Username-Password Flow\uFF09\u306F\u81EA\u52D5\u3067\u884C\u308F\u308C\u3001\u30EA\u30AF\u30A8\u30B9\u30C8\u3054\u3068\u306B\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u3068\u7D44\u7E54\u306E instance URL \u304C\u89E3\u6C7A\u3055\u308C\u307E\u3059\u3002\u30D5\u30A3\u30EB\u30BF\u3084\u7D50\u5408\u306E\u3042\u308B\u8AAD\u307F\u53D6\u308A\u3067\u306F \`/sobjects/{Type}\` \u3092\u30DA\u30FC\u30B8\u30F3\u30B0\u3059\u308B\u306E\u3067\u306F\u306A\u304F\u3001\`GET /services/data/v60.0/query?q=...\` \u306E SOQL \u3092\u512A\u5148\u3057\u3066\u304F\u3060\u3055\u3044\u3002
643
+
644
+ ### Business Logic
645
+
646
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BF SDK \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
647
+
648
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
649
+ - \`client.request(path, init?)\` \u2014 Salesforce \u306E instance URL \u306B\u5BFE\u3059\u308B\u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304D fetch
650
+ - \`client.query(soql)\` \u2014 SOQL \u30AF\u30A8\u30EA\u3092\u5B9F\u884C\u3057 \`{ totalSize, done, records, nextRecordsUrl? }\` \u3092\u8FD4\u5374
651
+ - \`client.queryMore(nextRecordsUrl)\` \u2014 SOQL \u7D50\u679C\u306E\u6B21\u30DA\u30FC\u30B8\u3092\u53D6\u5F97
652
+ - \`client.describeSObject(objectType)\` \u2014 sObject \u306E\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97
653
+ - \`client.getRecord(objectType, id, options?)\` \u2014 1 \u4EF6\u306E\u30EC\u30B3\u30FC\u30C9\u3092\u53D6\u5F97\uFF08\u4EFB\u610F\u3067\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u6307\u5B9A\uFF09
654
+ - \`client.createRecord(objectType, fields)\` \u2014 \u30EC\u30B3\u30FC\u30C9\u3092\u65B0\u898F\u4F5C\u6210
655
+ - \`client.updateRecord(objectType, id, fields)\` \u2014 \u65E2\u5B58\u30EC\u30B3\u30FC\u30C9\u3092\u66F4\u65B0\uFF08PATCH\uFF09
656
+ - \`client.deleteRecord(objectType, id)\` \u2014 \u30EC\u30B3\u30FC\u30C9\u3092\u524A\u9664
657
+
658
+ \`\`\`ts
659
+ import type { Context } from "hono";
660
+ import { connection } from "@squadbase/vite-server/connectors/salesforce";
661
+
662
+ const salesforce = connection("<connectionId>");
663
+
664
+ export default async function handler(c: Context) {
665
+ const { industry, limit = 50 } = await c.req.json<{
666
+ industry?: string;
667
+ limit?: number;
668
+ }>();
669
+
670
+ const where = industry
671
+ ? \`WHERE Industry = '\${industry.replace(/'/g, "\\\\'")}'\`
672
+ : "";
673
+ const soql = \`SELECT Id, Name, Industry, AnnualRevenue FROM Account \${where} ORDER BY AnnualRevenue DESC NULLS LAST LIMIT \${limit}\`;
674
+
675
+ const { records } = await salesforce.query<{
676
+ Id: string;
677
+ Name: string;
678
+ Industry: string | null;
679
+ AnnualRevenue: number | null;
680
+ }>(soql);
681
+
682
+ return c.json({ accounts: records });
683
+ }
684
+ \`\`\`
685
+
686
+ ### Salesforce REST API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
687
+
688
+ - \u30ED\u30B0\u30A4\u30F3\u30DB\u30B9\u30C8: \`https://login.salesforce.com\`\uFF08\u672C\u756A\uFF09\u307E\u305F\u306F \`https://test.salesforce.com\`\uFF08Sandbox\uFF09
689
+ - \u30C8\u30FC\u30AF\u30F3\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8: \`POST /services/oauth2/token\`\uFF08grant_type=password + client_id/secret + username/password\uFF09
690
+ - \u30ED\u30B0\u30A4\u30F3\u5F8C\u306E\u30D9\u30FC\u30B9\u30D1\u30B9: \`{instance_url}/services/data/v60.0\`
691
+ - \u8A8D\u8A3C: Bearer \u30C8\u30FC\u30AF\u30F3\uFF08\u30EA\u30AF\u30A8\u30B9\u30C8\u3054\u3068\u306B\u81EA\u52D5\u8A2D\u5B9A\uFF09
692
+ - \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\uFF08SOQL\uFF09: \u30EC\u30B9\u30DD\u30F3\u30B9\u306E \`nextRecordsUrl\`\uFF08\`/services/data/v60.0/query/...\` \u304B\u3089\u59CB\u307E\u308B\u7D76\u5BFE\u30D1\u30B9\uFF09\u3092\u8FBF\u308B
693
+
694
+ #### \u4E3B\u8981\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
695
+ - GET \`/services/data/\` \u2014 \u5229\u7528\u53EF\u80FD\u306A API \u30D0\u30FC\u30B8\u30E7\u30F3\u4E00\u89A7
696
+ - GET \`/services/data/v60.0/sobjects/\` \u2014 sObject \u4E00\u89A7\uFF08\u6A19\u6E96 + \u30AB\u30B9\u30BF\u30E0\uFF09
697
+ - GET \`/services/data/v60.0/sobjects/{Type}/describe\` \u2014 sObject \u306E\u30D5\u30A3\u30FC\u30EB\u30C9\u30FB\u30EA\u30EC\u30FC\u30B7\u30E7\u30F3\u30FB\u30D4\u30C3\u30AF\u30EA\u30B9\u30C8\u3092\u53D6\u5F97
698
+ - GET \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u53D6\u5F97\uFF08\`?fields=...\` \u3067\u7D5E\u308A\u8FBC\u307F\u53EF\uFF09
699
+ - POST \`/services/data/v60.0/sobjects/{Type}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u4F5C\u6210
700
+ - PATCH \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u66F4\u65B0
701
+ - DELETE \`/services/data/v60.0/sobjects/{Type}/{id}\` \u2014 \u30EC\u30B3\u30FC\u30C9\u524A\u9664
702
+ - GET \`/services/data/v60.0/query?q={soql}\` \u2014 SOQL \u3092\u5B9F\u884C
703
+ - GET \`/services/data/v60.0/search?q={sosl}\` \u2014 SOSL \u3092\u5B9F\u884C
704
+
705
+ #### SOQL \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
706
+ - SELECT \u5217\u306E\u660E\u793A\u304C\u5FC5\u9808\uFF08\`SELECT *\` \u306F\u4E0D\u53EF\uFF09
707
+ - \u30D5\u30A3\u30EB\u30BF: \`WHERE\`\u3001\`AND\` / \`OR\`\u3001\u89AA\u5B50\u30EA\u30EC\u30FC\u30B7\u30E7\u30F3\u53C2\u7167\uFF08\u4F8B: \`Account.Name\`\uFF09
708
+ - \u4E26\u3073\u66FF\u3048: \`ORDER BY field [ASC|DESC] [NULLS FIRST|NULLS LAST]\`
709
+ - \u30DA\u30FC\u30B8\u30F3\u30B0: \`LIMIT n\`, \`OFFSET m\`\u3002\u5927\u91CF\u30C7\u30FC\u30BF\u3067\u306F OFFSET \u3067\u306F\u306A\u304F \`nextRecordsUrl\` \u3092\u5229\u7528\u3059\u308B
710
+ - \u96C6\u8A08: \`GROUP BY\`, \`HAVING\`, \`COUNT()\`, \`SUM()\`, \`AVG()\`, \`MIN()\`, \`MAX()\`
711
+ - \u89AA\u2192\u5B50\u30B5\u30D6\u30AF\u30A8\u30EA: \`SELECT Id, Name, (SELECT Id, Email FROM Contacts) FROM Account\``
712
+ },
713
+ tools
714
+ });
715
+
716
+ // src/connectors/create-connector-sdk.ts
717
+ import { readFileSync } from "fs";
718
+ import path from "path";
719
+
720
+ // src/connector-client/env.ts
721
+ function resolveEnvVar(entry, key, connectionId) {
722
+ const envVarName = entry.envVars[key];
723
+ if (!envVarName) {
724
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
725
+ }
726
+ const value = process.env[envVarName];
727
+ if (!value) {
728
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
729
+ }
730
+ return value;
731
+ }
732
+ function resolveEnvVarOptional(entry, key) {
733
+ const envVarName = entry.envVars[key];
734
+ if (!envVarName) return void 0;
735
+ return process.env[envVarName] || void 0;
736
+ }
737
+
738
+ // src/connector-client/proxy-fetch.ts
739
+ import { getContext } from "hono/context-storage";
740
+ import { getCookie } from "hono/cookie";
741
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
742
+ function normalizeHeaders(input) {
743
+ const out = {};
744
+ if (!input) return out;
745
+ new Headers(input).forEach((value, key) => {
746
+ out[key] = value;
747
+ });
748
+ return out;
749
+ }
750
+ function createSandboxProxyFetch(connectionId) {
751
+ return async (input, init) => {
752
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
753
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
754
+ if (!token || !sandboxId) {
755
+ throw new Error(
756
+ "Connection proxy is not configured. Please check your deployment settings."
757
+ );
758
+ }
759
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
760
+ const originalMethod = init?.method ?? "GET";
761
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
762
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
763
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
764
+ return fetch(proxyUrl, {
765
+ method: "POST",
766
+ headers: {
767
+ "Content-Type": "application/json",
768
+ Authorization: `Bearer ${token}`
769
+ },
770
+ body: JSON.stringify({
771
+ url: originalUrl,
772
+ method: originalMethod,
773
+ headers: normalizeHeaders(init?.headers),
774
+ body: originalBody
775
+ })
776
+ });
777
+ };
778
+ }
779
+ function createDeployedAppProxyFetch(connectionId) {
780
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
781
+ if (!projectId) {
782
+ throw new Error(
783
+ "Connection proxy is not configured. Please check your deployment settings."
784
+ );
785
+ }
786
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
787
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
788
+ return async (input, init) => {
789
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
790
+ const originalMethod = init?.method ?? "GET";
791
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
792
+ const c = getContext();
793
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
794
+ if (!appSession) {
795
+ throw new Error(
796
+ "No authentication method available for connection proxy."
797
+ );
798
+ }
799
+ return fetch(proxyUrl, {
800
+ method: "POST",
801
+ headers: {
802
+ "Content-Type": "application/json",
803
+ Authorization: `Bearer ${appSession}`
804
+ },
805
+ body: JSON.stringify({
806
+ url: originalUrl,
807
+ method: originalMethod,
808
+ headers: normalizeHeaders(init?.headers),
809
+ body: originalBody
810
+ })
811
+ });
812
+ };
813
+ }
814
+ function createProxyFetch(connectionId) {
815
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
816
+ return createSandboxProxyFetch(connectionId);
817
+ }
818
+ return createDeployedAppProxyFetch(connectionId);
819
+ }
820
+
821
+ // src/connectors/create-connector-sdk.ts
822
+ function loadConnectionsSync() {
823
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
824
+ try {
825
+ const raw = readFileSync(filePath, "utf-8");
826
+ return JSON.parse(raw);
827
+ } catch {
828
+ return {};
829
+ }
830
+ }
831
+ function createConnectorSdk(plugin, createClient2) {
832
+ return (connectionId) => {
833
+ const connections = loadConnectionsSync();
834
+ const entry = connections[connectionId];
835
+ if (!entry) {
836
+ throw new Error(
837
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
838
+ );
839
+ }
840
+ if (entry.connector.slug !== plugin.slug) {
841
+ throw new Error(
842
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
843
+ );
844
+ }
845
+ const params = {};
846
+ for (const param of Object.values(plugin.parameters)) {
847
+ if (param.required) {
848
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
849
+ } else {
850
+ const val = resolveEnvVarOptional(entry, param.slug);
851
+ if (val !== void 0) params[param.slug] = val;
852
+ }
853
+ }
854
+ return createClient2(params, createProxyFetch(connectionId));
855
+ };
856
+ }
857
+
858
+ // src/connectors/entries/salesforce.ts
859
+ var connection = createConnectorSdk(salesforceConnector, createClient);
860
+ export {
861
+ connection
862
+ };