@squadbase/vite-server 0.1.9-dev.d3c856d → 0.1.9-dev.e22f810

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,842 @@
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/freshdesk/parameters.ts
46
+ var parameters = {
47
+ domain: new ParameterDefinition({
48
+ slug: "domain",
49
+ name: "Freshdesk Domain",
50
+ description: "Your Freshdesk subdomain (e.g. `acme` for `https://acme.freshdesk.com`). Provide just the subdomain without `https://` or the rest of the URL.",
51
+ envVarBaseKey: "FRESHDESK_DOMAIN",
52
+ type: "text",
53
+ secret: false,
54
+ required: true
55
+ }),
56
+ apiKey: new ParameterDefinition({
57
+ slug: "api-key",
58
+ name: "Freshdesk API Key",
59
+ description: "API key for Freshdesk. Find it in Freshdesk \u2192 Profile (top-right) \u2192 Profile Settings \u2192 API Key. The key is sent via HTTP Basic Auth (api-key as username, any value as password).",
60
+ envVarBaseKey: "FRESHDESK_API_KEY",
61
+ type: "text",
62
+ secret: true,
63
+ required: true
64
+ })
65
+ };
66
+
67
+ // ../connectors/src/connectors/freshdesk/sdk/index.ts
68
+ function buildBaseUrl(domain) {
69
+ const trimmed = domain.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
70
+ const subdomain = trimmed.split(".")[0];
71
+ return `https://${subdomain}.freshdesk.com/api/v2`;
72
+ }
73
+ function basicAuthHeader(apiKey) {
74
+ return `Basic ${Buffer.from(`${apiKey}:X`).toString("base64")}`;
75
+ }
76
+ function createClient(params) {
77
+ const apiKey = params[parameters.apiKey.slug];
78
+ const domain = params[parameters.domain.slug];
79
+ if (!apiKey) {
80
+ throw new Error(
81
+ `freshdesk: missing required parameter: ${parameters.apiKey.slug}`
82
+ );
83
+ }
84
+ if (!domain) {
85
+ throw new Error(
86
+ `freshdesk: missing required parameter: ${parameters.domain.slug}`
87
+ );
88
+ }
89
+ const baseUrl = buildBaseUrl(domain);
90
+ const auth = basicAuthHeader(apiKey);
91
+ function authHeaders(extra) {
92
+ const headers = new Headers(extra);
93
+ headers.set("Authorization", auth);
94
+ headers.set("Content-Type", "application/json");
95
+ headers.set("Accept", "application/json");
96
+ return headers;
97
+ }
98
+ async function assertOk(res, label) {
99
+ if (!res.ok) {
100
+ const body = await res.text().catch(() => "(unreadable body)");
101
+ throw new Error(
102
+ `freshdesk ${label}: ${res.status} ${res.statusText} \u2014 ${body}`
103
+ );
104
+ }
105
+ }
106
+ function buildQuery(base) {
107
+ const search = new URLSearchParams();
108
+ for (const [key, value] of Object.entries(base)) {
109
+ if (value !== void 0) search.set(key, String(value));
110
+ }
111
+ const qs = search.toString();
112
+ return qs ? `?${qs}` : "";
113
+ }
114
+ return {
115
+ request(path2, init) {
116
+ const headers = new Headers(init?.headers);
117
+ headers.set("Authorization", auth);
118
+ if (!headers.has("Content-Type")) {
119
+ headers.set("Content-Type", "application/json");
120
+ }
121
+ if (!headers.has("Accept")) {
122
+ headers.set("Accept", "application/json");
123
+ }
124
+ return fetch(`${baseUrl}${path2}`, { ...init, headers });
125
+ },
126
+ async listTickets(options) {
127
+ const qs = buildQuery({
128
+ page: options?.page,
129
+ per_page: options?.per_page,
130
+ include: options?.include,
131
+ updated_since: options?.updated_since,
132
+ order_by: options?.order_by,
133
+ order_type: options?.order_type,
134
+ filter: options?.filter
135
+ });
136
+ const res = await fetch(`${baseUrl}/tickets${qs}`, {
137
+ method: "GET",
138
+ headers: authHeaders()
139
+ });
140
+ await assertOk(res, "listTickets");
141
+ return await res.json();
142
+ },
143
+ async getTicket(ticketId, options) {
144
+ const qs = buildQuery({ include: options?.include });
145
+ const res = await fetch(
146
+ `${baseUrl}/tickets/${encodeURIComponent(String(ticketId))}${qs}`,
147
+ { method: "GET", headers: authHeaders() }
148
+ );
149
+ await assertOk(res, "getTicket");
150
+ return await res.json();
151
+ },
152
+ async createTicket(ticketData) {
153
+ const res = await fetch(`${baseUrl}/tickets`, {
154
+ method: "POST",
155
+ headers: authHeaders(),
156
+ body: JSON.stringify(ticketData)
157
+ });
158
+ await assertOk(res, "createTicket");
159
+ return await res.json();
160
+ },
161
+ async updateTicket(ticketId, ticketData) {
162
+ const res = await fetch(
163
+ `${baseUrl}/tickets/${encodeURIComponent(String(ticketId))}`,
164
+ {
165
+ method: "PUT",
166
+ headers: authHeaders(),
167
+ body: JSON.stringify(ticketData)
168
+ }
169
+ );
170
+ await assertOk(res, "updateTicket");
171
+ return await res.json();
172
+ },
173
+ async searchTickets(query) {
174
+ const qs = `?query=${encodeURIComponent(`"${query}"`)}`;
175
+ const res = await fetch(`${baseUrl}/search/tickets${qs}`, {
176
+ method: "GET",
177
+ headers: authHeaders()
178
+ });
179
+ await assertOk(res, "searchTickets");
180
+ return await res.json();
181
+ },
182
+ async listContacts(options) {
183
+ const qs = buildQuery({
184
+ page: options?.page,
185
+ per_page: options?.per_page,
186
+ email: options?.email,
187
+ mobile: options?.mobile,
188
+ phone: options?.phone,
189
+ company_id: options?.company_id,
190
+ state: options?.state
191
+ });
192
+ const res = await fetch(`${baseUrl}/contacts${qs}`, {
193
+ method: "GET",
194
+ headers: authHeaders()
195
+ });
196
+ await assertOk(res, "listContacts");
197
+ return await res.json();
198
+ },
199
+ async listCompanies(options) {
200
+ const qs = buildQuery({
201
+ page: options?.page,
202
+ per_page: options?.per_page
203
+ });
204
+ const res = await fetch(`${baseUrl}/companies${qs}`, {
205
+ method: "GET",
206
+ headers: authHeaders()
207
+ });
208
+ await assertOk(res, "listCompanies");
209
+ return await res.json();
210
+ },
211
+ async listAgents(options) {
212
+ const qs = buildQuery({
213
+ page: options?.page,
214
+ per_page: options?.per_page,
215
+ email: options?.email,
216
+ state: options?.state
217
+ });
218
+ const res = await fetch(`${baseUrl}/agents${qs}`, {
219
+ method: "GET",
220
+ headers: authHeaders()
221
+ });
222
+ await assertOk(res, "listAgents");
223
+ return await res.json();
224
+ }
225
+ };
226
+ }
227
+
228
+ // ../connectors/src/connector-onboarding.ts
229
+ var ConnectorOnboarding = class {
230
+ /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
231
+ connectionSetupInstructions;
232
+ /** Phase 2: Data overview instructions */
233
+ dataOverviewInstructions;
234
+ constructor(config) {
235
+ this.connectionSetupInstructions = config.connectionSetupInstructions;
236
+ this.dataOverviewInstructions = config.dataOverviewInstructions;
237
+ }
238
+ getConnectionSetupPrompt(language) {
239
+ return this.connectionSetupInstructions?.[language] ?? null;
240
+ }
241
+ getDataOverviewInstructions(language) {
242
+ return this.dataOverviewInstructions[language];
243
+ }
244
+ };
245
+
246
+ // ../connectors/src/connector-tool.ts
247
+ var ConnectorTool = class {
248
+ name;
249
+ description;
250
+ inputSchema;
251
+ outputSchema;
252
+ _execute;
253
+ constructor(config) {
254
+ this.name = config.name;
255
+ this.description = config.description;
256
+ this.inputSchema = config.inputSchema;
257
+ this.outputSchema = config.outputSchema;
258
+ this._execute = config.execute;
259
+ }
260
+ createTool(connections, config) {
261
+ return {
262
+ description: this.description,
263
+ inputSchema: this.inputSchema,
264
+ outputSchema: this.outputSchema,
265
+ execute: (input) => this._execute(input, connections, config)
266
+ };
267
+ }
268
+ };
269
+
270
+ // ../connectors/src/connector-plugin.ts
271
+ var ConnectorPlugin = class _ConnectorPlugin {
272
+ slug;
273
+ authType;
274
+ name;
275
+ description;
276
+ iconUrl;
277
+ parameters;
278
+ releaseFlag;
279
+ proxyPolicy;
280
+ experimentalAttributes;
281
+ categories;
282
+ onboarding;
283
+ systemPrompt;
284
+ tools;
285
+ query;
286
+ checkConnection;
287
+ constructor(config) {
288
+ this.slug = config.slug;
289
+ this.authType = config.authType;
290
+ this.name = config.name;
291
+ this.description = config.description;
292
+ this.iconUrl = config.iconUrl;
293
+ this.parameters = config.parameters;
294
+ this.releaseFlag = config.releaseFlag;
295
+ this.proxyPolicy = config.proxyPolicy;
296
+ this.experimentalAttributes = config.experimentalAttributes;
297
+ this.categories = config.categories ?? [];
298
+ this.onboarding = config.onboarding;
299
+ this.systemPrompt = config.systemPrompt;
300
+ this.tools = config.tools;
301
+ this.query = config.query;
302
+ this.checkConnection = config.checkConnection;
303
+ }
304
+ get connectorKey() {
305
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
306
+ }
307
+ /**
308
+ * Create tools for connections that belong to this connector.
309
+ * Filters connections by connectorKey internally.
310
+ * Returns tools keyed as `${connectorKey}_${toolName}`.
311
+ */
312
+ createTools(connections, config, opts) {
313
+ const myConnections = connections.filter(
314
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
315
+ );
316
+ const result = {};
317
+ for (const t of Object.values(this.tools)) {
318
+ const tool = t.createTool(myConnections, config);
319
+ const originalToModelOutput = tool.toModelOutput;
320
+ result[`${this.connectorKey}_${t.name}`] = {
321
+ ...tool,
322
+ toModelOutput: async (options) => {
323
+ if (!originalToModelOutput) {
324
+ return opts.truncateOutput(options.output);
325
+ }
326
+ const modelOutput = await originalToModelOutput(options);
327
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
328
+ return opts.truncateOutput(modelOutput.value);
329
+ }
330
+ return modelOutput;
331
+ }
332
+ };
333
+ }
334
+ return result;
335
+ }
336
+ static deriveKey(slug, authType) {
337
+ if (authType) return `${slug}-${authType}`;
338
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
339
+ // user-password
340
+ "postgresql": "user-password",
341
+ "mysql": "user-password",
342
+ "clickhouse": "user-password",
343
+ "kintone": "user-password",
344
+ "squadbase-db": "user-password",
345
+ // service-account
346
+ "snowflake": "service-account",
347
+ "bigquery": "service-account",
348
+ "google-analytics": "service-account",
349
+ "google-calendar": "service-account",
350
+ "aws-athena": "service-account",
351
+ "redshift": "service-account",
352
+ // api-key
353
+ "databricks": "api-key",
354
+ "dbt": "api-key",
355
+ "airtable": "api-key",
356
+ "openai": "api-key",
357
+ "gemini": "api-key",
358
+ "anthropic": "api-key",
359
+ "wix-store": "api-key"
360
+ };
361
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
362
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
363
+ return slug;
364
+ }
365
+ };
366
+
367
+ // ../connectors/src/auth-types.ts
368
+ var AUTH_TYPES = {
369
+ OAUTH: "oauth",
370
+ API_KEY: "api-key",
371
+ JWT: "jwt",
372
+ SERVICE_ACCOUNT: "service-account",
373
+ PAT: "pat",
374
+ USER_PASSWORD: "user-password"
375
+ };
376
+
377
+ // ../connectors/src/lib/normalize-path.ts
378
+ function normalizeRequestPath(path2, basePathSegment) {
379
+ let p = path2.trim();
380
+ if (!p.startsWith("/")) p = "/" + p;
381
+ if (p === basePathSegment || p.startsWith(basePathSegment + "/")) {
382
+ p = p.slice(basePathSegment.length) || "/";
383
+ }
384
+ return p;
385
+ }
386
+
387
+ // ../connectors/src/connectors/freshdesk/setup.ts
388
+ var freshdeskOnboarding = new ConnectorOnboarding({
389
+ dataOverviewInstructions: {
390
+ en: `1. Call freshdesk_request with GET /agents/me to confirm credentials.
391
+ 2. Call freshdesk_request with GET /tickets?per_page=10&order_by=created_at&order_type=desc to sample recent tickets. Each ticket carries \`status\`, \`priority\`, \`source\`, \`requester_id\`, \`responder_id\`, and \`created_at\`.
392
+ 3. Call GET /contacts?per_page=10 to sample contacts (end users) and GET /companies?per_page=10 for companies.
393
+ 4. To enumerate agents and groups, call GET /agents and GET /groups.
394
+ 5. Pagination is page-based with \`page\` (1-indexed) and \`per_page\` (max 100). The \`Link\` response header carries the next-page URL when more results exist.`,
395
+ ja: `1. freshdesk_request \u3067 GET /agents/me \u3092\u547C\u3073\u51FA\u3057\u3001\u8A8D\u8A3C\u60C5\u5831\u3092\u78BA\u8A8D\u3057\u307E\u3059\u3002
396
+ 2. freshdesk_request \u3067 GET /tickets?per_page=10&order_by=created_at&order_type=desc \u3092\u547C\u3073\u51FA\u3057\u3066\u6700\u8FD1\u306E\u30C1\u30B1\u30C3\u30C8\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3057\u307E\u3059\u3002\u5404\u30C1\u30B1\u30C3\u30C8\u306B\u306F \`status\`, \`priority\`, \`source\`, \`requester_id\`, \`responder_id\`, \`created_at\` \u306A\u3069\u304C\u542B\u307E\u308C\u307E\u3059\u3002
397
+ 3. GET /contacts?per_page=10 \u3067\u9023\u7D61\u5148\uFF08\u30A8\u30F3\u30C9\u30E6\u30FC\u30B6\u30FC\uFF09\u3001GET /companies?per_page=10 \u3067\u4F1A\u793E\u60C5\u5831\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3057\u307E\u3059\u3002
398
+ 4. \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3084\u30B0\u30EB\u30FC\u30D7\u306F GET /agents\u3001GET /groups \u3067\u53D6\u5F97\u3057\u307E\u3059\u3002
399
+ 5. \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u306F1\u59CB\u307E\u308A\u306E \`page\` \u3068 \`per_page\`\uFF08\u6700\u5927100\uFF09\u3002\u6B21\u30DA\u30FC\u30B8\u304C\u5B58\u5728\u3059\u308B\u5834\u5408\u306F \`Link\` \u30EC\u30B9\u30DD\u30F3\u30B9\u30D8\u30C3\u30C0\u306B\u6B21URL\u304C\u542B\u307E\u308C\u307E\u3059\u3002`
400
+ }
401
+ });
402
+
403
+ // ../connectors/src/connectors/freshdesk/tools/request.ts
404
+ import { z } from "zod";
405
+ var BASE_PATH_SEGMENT = "/api/v2";
406
+ var REQUEST_TIMEOUT_MS = 6e4;
407
+ function buildBaseUrl2(domain) {
408
+ const trimmed = domain.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
409
+ const subdomain = trimmed.split(".")[0];
410
+ return `https://${subdomain}.freshdesk.com${BASE_PATH_SEGMENT}`;
411
+ }
412
+ function basicAuthHeader2(apiKey) {
413
+ return `Basic ${Buffer.from(`${apiKey}:X`).toString("base64")}`;
414
+ }
415
+ var inputSchema = z.object({
416
+ toolUseIntent: z.string().optional().describe(
417
+ "Brief description of what you intend to accomplish with this tool call"
418
+ ),
419
+ connectionId: z.string().describe("ID of the Freshdesk connection to use"),
420
+ method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe(
421
+ "HTTP method. GET for reading resources, POST for creating, PUT for updating, DELETE for removing."
422
+ ),
423
+ path: z.string().describe(
424
+ "API path (e.g., '/tickets', '/tickets/123', '/contacts'). Append query parameters such as '?per_page=100&page=1&order_by=created_at&order_type=desc'."
425
+ ),
426
+ body: z.record(z.string(), z.unknown()).optional().describe(
427
+ 'Request body (JSON) for POST/PUT requests. Example creating a ticket: { "subject": "...", "description": "...", "email": "...", "priority": 2, "status": 2, "source": 1 }.'
428
+ )
429
+ });
430
+ var outputSchema = z.discriminatedUnion("success", [
431
+ z.object({
432
+ success: z.literal(true),
433
+ status: z.number(),
434
+ data: z.union([
435
+ z.record(z.string(), z.unknown()),
436
+ z.array(z.unknown())
437
+ ])
438
+ }),
439
+ z.object({
440
+ success: z.literal(false),
441
+ error: z.string()
442
+ })
443
+ ]);
444
+ var requestTool = new ConnectorTool({
445
+ name: "request",
446
+ description: `Send authenticated requests to the Freshdesk REST API v2.
447
+ Authentication is HTTP Basic Auth using the API key as the username (handled automatically).
448
+ Provide the API path relative to the base URL (https://<domain>.freshdesk.com/api/v2).
449
+
450
+ Common endpoints:
451
+ - GET /tickets \u2014 List tickets (paginated). Use ?include=requester,company,stats to enrich responses.
452
+ - GET /tickets/{id} \u2014 Get a ticket
453
+ - POST /tickets \u2014 Create a ticket (body: { subject, description, email, priority, status, source })
454
+ - PUT /tickets/{id} \u2014 Update a ticket
455
+ - GET /tickets/{id}/conversations \u2014 Replies and notes on a ticket
456
+ - POST /tickets/{id}/reply \u2014 Reply to a ticket
457
+ - POST /tickets/{id}/notes \u2014 Add a private note
458
+ - GET /contacts \u2014 List contacts (end users)
459
+ - GET /companies \u2014 List companies
460
+ - GET /agents \u2014 List agents
461
+ - GET /agents/me \u2014 Get the calling agent (useful as auth probe)
462
+ - GET /groups \u2014 List agent groups
463
+ - GET /solutions/categories \u2014 Knowledge base categories
464
+ - GET /search/tickets?query="..." \u2014 Filter tickets (advanced search)
465
+
466
+ Pagination: ?page=1&per_page=100 (per_page max 100). Inspect the \`Link\` response header for the next-page URL.
467
+
468
+ Field codes (tickets):
469
+ - priority: 1 Low, 2 Medium, 3 High, 4 Urgent
470
+ - status: 2 Open, 3 Pending, 4 Resolved, 5 Closed
471
+ - source: 1 Email, 2 Portal, 3 Phone, 7 Chat, 9 Feedback widget, 10 Outbound email`,
472
+ inputSchema,
473
+ outputSchema,
474
+ async execute({ connectionId, method, path: path2, body }, connections) {
475
+ const connection2 = connections.find((c) => c.id === connectionId);
476
+ if (!connection2) {
477
+ return {
478
+ success: false,
479
+ error: `Connection ${connectionId} not found`
480
+ };
481
+ }
482
+ console.log(
483
+ `[connector-request] freshdesk/${connection2.name}: ${method} ${path2}`
484
+ );
485
+ try {
486
+ const apiKey = parameters.apiKey.getValue(connection2);
487
+ const domain = parameters.domain.getValue(connection2);
488
+ const baseUrl = buildBaseUrl2(domain);
489
+ const normalizedPath = normalizeRequestPath(path2, BASE_PATH_SEGMENT);
490
+ const url = `${baseUrl}${normalizedPath}`;
491
+ const controller = new AbortController();
492
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
493
+ try {
494
+ const response = await fetch(url, {
495
+ method,
496
+ headers: {
497
+ Authorization: basicAuthHeader2(apiKey),
498
+ "Content-Type": "application/json",
499
+ Accept: "application/json"
500
+ },
501
+ body: body ? JSON.stringify(body) : void 0,
502
+ signal: controller.signal
503
+ });
504
+ const text = await response.text();
505
+ const data = text ? JSON.parse(text) : {};
506
+ if (!response.ok) {
507
+ const errObj = !Array.isArray(data) ? data : {};
508
+ const description = typeof errObj.description === "string" && errObj.description || typeof errObj.message === "string" && errObj.message;
509
+ const errors = errObj.errors;
510
+ const fieldDetail = Array.isArray(errors) && errors.length > 0 ? ` \u2014 ${errors[0]?.message ?? ""}` : "";
511
+ const errorMessage = description ? `${description}${fieldDetail}` : `HTTP ${response.status} ${response.statusText}`;
512
+ return { success: false, error: String(errorMessage) };
513
+ }
514
+ return { success: true, status: response.status, data };
515
+ } finally {
516
+ clearTimeout(timeout);
517
+ }
518
+ } catch (err) {
519
+ const msg = err instanceof Error ? err.message : String(err);
520
+ return { success: false, error: msg };
521
+ }
522
+ }
523
+ });
524
+
525
+ // ../connectors/src/connectors/freshdesk/index.ts
526
+ var tools = { request: requestTool };
527
+ var freshdeskConnector = new ConnectorPlugin({
528
+ slug: "freshdesk",
529
+ authType: AUTH_TYPES.API_KEY,
530
+ name: "Freshdesk",
531
+ description: "Connect to Freshdesk for customer support ticket, contact, and company data via API key.",
532
+ iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/1PSjIfQJgTYmiWriNTx4uE/26905f4120713bda0afd0b23d02a154e/freshdesk-icon.png",
533
+ parameters,
534
+ releaseFlag: { dev1: true, dev2: true, prod: true },
535
+ categories: ["crm"],
536
+ onboarding: freshdeskOnboarding,
537
+ systemPrompt: {
538
+ en: `### Tools
539
+
540
+ - \`freshdesk_request\`: The only way to call the Freshdesk REST API v2. Use it to list tickets, contacts, companies, agents, groups, and more. Authentication (HTTP Basic Auth using the API key as username) and the base URL (https://<domain>.freshdesk.com/api/v2) are configured automatically.
541
+
542
+ ### Business Logic
543
+
544
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
545
+
546
+ SDK methods (client created via \`connection(connectionId)\`):
547
+ - \`client.request(path, init?)\` \u2014 low-level authenticated fetch
548
+ - \`client.listTickets(options?)\` \u2014 list tickets (paginated)
549
+ - \`client.getTicket(ticketId, options?)\` \u2014 get a single ticket
550
+ - \`client.createTicket(ticketData)\` \u2014 create a ticket
551
+ - \`client.updateTicket(ticketId, ticketData)\` \u2014 update a ticket
552
+ - \`client.searchTickets(query)\` \u2014 advanced search (e.g. \`status:2 AND priority:>2\`)
553
+ - \`client.listContacts(options?)\` \u2014 list contacts
554
+ - \`client.listCompanies(options?)\` \u2014 list companies
555
+ - \`client.listAgents(options?)\` \u2014 list agents
556
+
557
+ \`\`\`ts
558
+ import type { Context } from "hono";
559
+ import { connection } from "@squadbase/vite-server/connectors/freshdesk";
560
+
561
+ const fd = connection("<connectionId>");
562
+
563
+ export default async function handler(c: Context) {
564
+ const tickets = await fd.listTickets({
565
+ per_page: 100,
566
+ include: "requester,company",
567
+ order_by: "created_at",
568
+ order_type: "desc",
569
+ });
570
+ return c.json({ tickets });
571
+ }
572
+ \`\`\`
573
+
574
+ ### Freshdesk REST API v2 Reference
575
+
576
+ - Base URL: \`https://<domain>.freshdesk.com/api/v2\`
577
+ - Authentication: HTTP Basic Auth (\`<API_KEY>:X\`, base64-encoded; the password is ignored).
578
+ - Pagination: 1-indexed \`page\` + \`per_page\` (max 100). Tickets max 300 pages historically \u2014 use \`updated_since\` or \`/search/tickets\` for larger windows.
579
+ - Sorting: \`order_by\` \u2208 { created_at, updated_at, due_by, status }, \`order_type\` \u2208 { asc, desc }.
580
+
581
+ #### Resource Endpoints
582
+
583
+ **Tickets**
584
+ - GET \`/tickets\` \u2014 List tickets (use \`include=requester,company,stats\` to enrich)
585
+ - GET \`/tickets/{id}\` \u2014 Get a ticket
586
+ - POST \`/tickets\` \u2014 Create a ticket
587
+ - PUT \`/tickets/{id}\` \u2014 Update a ticket
588
+ - DELETE \`/tickets/{id}\` \u2014 Soft-delete a ticket
589
+ - GET \`/tickets/{id}/conversations\` \u2014 Replies and notes
590
+ - POST \`/tickets/{id}/reply\` \u2014 Reply to a ticket
591
+ - POST \`/tickets/{id}/notes\` \u2014 Add a private/public note
592
+ - GET \`/search/tickets?query="..."\` \u2014 Advanced search (e.g. \`"priority:>2 AND status:2"\`)
593
+
594
+ **Users & Companies**
595
+ - GET \`/contacts\` \u2014 List end-user contacts
596
+ - GET \`/contacts/{id}\` \u2014 Get a contact
597
+ - POST \`/contacts\` \u2014 Create a contact
598
+ - PUT \`/contacts/{id}\` \u2014 Update a contact
599
+ - GET \`/companies\` \u2014 List companies
600
+ - POST \`/companies\` \u2014 Create a company
601
+ - GET \`/agents\` \u2014 List agents
602
+ - GET \`/agents/me\` \u2014 Calling agent (auth probe)
603
+ - GET \`/groups\` \u2014 Agent groups
604
+
605
+ **Knowledge Base**
606
+ - GET \`/solutions/categories\` \u2014 Categories
607
+ - GET \`/solutions/categories/{id}/folders\` \u2014 Folders in a category
608
+ - GET \`/solutions/folders/{id}/articles\` \u2014 Articles in a folder
609
+
610
+ #### Field codes (tickets)
611
+
612
+ - \`priority\`: 1=Low, 2=Medium, 3=High, 4=Urgent
613
+ - \`status\`: 2=Open, 3=Pending, 4=Resolved, 5=Closed
614
+ - \`source\`: 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback widget, 10=Outbound email`,
615
+ ja: `### \u30C4\u30FC\u30EB
616
+
617
+ - \`freshdesk_request\`: Freshdesk REST API v2 \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30C1\u30B1\u30C3\u30C8\u3001\u9023\u7D61\u5148\u3001\u4F1A\u793E\u3001\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3001\u30B0\u30EB\u30FC\u30D7\u306A\u3069\u306E\u53D6\u5F97\u30FB\u66F4\u65B0\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A8D\u8A3C\uFF08API \u30AD\u30FC\u3092\u30E6\u30FC\u30B6\u30FC\u540D\u3068\u3057\u305F HTTP Basic Auth\uFF09\u3068\u30D9\u30FC\u30B9URL\uFF08https://<\u30C9\u30E1\u30A4\u30F3>.freshdesk.com/api/v2\uFF09\u306F\u81EA\u52D5\u3067\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002
618
+
619
+ ### Business Logic
620
+
621
+ \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\u30BFSDK\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
622
+
623
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
624
+ - \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
625
+ - \`client.listTickets(options?)\` \u2014 \u30C1\u30B1\u30C3\u30C8\u4E00\u89A7\uFF08\u30DA\u30FC\u30B8\u30F3\u30B0\uFF09
626
+ - \`client.getTicket(ticketId, options?)\` \u2014 \u5358\u4E00\u30C1\u30B1\u30C3\u30C8\u306E\u53D6\u5F97
627
+ - \`client.createTicket(ticketData)\` \u2014 \u30C1\u30B1\u30C3\u30C8\u306E\u4F5C\u6210
628
+ - \`client.updateTicket(ticketId, ticketData)\` \u2014 \u30C1\u30B1\u30C3\u30C8\u306E\u66F4\u65B0
629
+ - \`client.searchTickets(query)\` \u2014 \u9AD8\u5EA6\u306A\u691C\u7D22\uFF08\u4F8B: \`status:2 AND priority:>2\`\uFF09
630
+ - \`client.listContacts(options?)\` \u2014 \u9023\u7D61\u5148\u4E00\u89A7
631
+ - \`client.listCompanies(options?)\` \u2014 \u4F1A\u793E\u4E00\u89A7
632
+ - \`client.listAgents(options?)\` \u2014 \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u4E00\u89A7
633
+
634
+ \`\`\`ts
635
+ import type { Context } from "hono";
636
+ import { connection } from "@squadbase/vite-server/connectors/freshdesk";
637
+
638
+ const fd = connection("<connectionId>");
639
+
640
+ export default async function handler(c: Context) {
641
+ const tickets = await fd.listTickets({
642
+ per_page: 100,
643
+ include: "requester,company",
644
+ order_by: "created_at",
645
+ order_type: "desc",
646
+ });
647
+ return c.json({ tickets });
648
+ }
649
+ \`\`\`
650
+
651
+ ### Freshdesk REST API v2 \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
652
+
653
+ - \u30D9\u30FC\u30B9URL: \`https://<\u30C9\u30E1\u30A4\u30F3>.freshdesk.com/api/v2\`
654
+ - \u8A8D\u8A3C: HTTP Basic Auth\uFF08\`<API_KEY>:X\` \u3092 base64 \u30A8\u30F3\u30B3\u30FC\u30C9\u3001\u30D1\u30B9\u30EF\u30FC\u30C9\u90E8\u306F\u7121\u8996\u3055\u308C\u307E\u3059\uFF09
655
+ - \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3: 1\u59CB\u307E\u308A\u306E \`page\` \u3068 \`per_page\`\uFF08\u6700\u5927100\uFF09\u3002\u30C1\u30B1\u30C3\u30C8\u4E00\u89A7\u306F\u6700\u5927300\u30DA\u30FC\u30B8\u7A0B\u5EA6\u306E\u5C65\u6B74\u5236\u9650\u304C\u3042\u308B\u305F\u3081\u3001\u3088\u308A\u5E83\u3044\u671F\u9593\u3092\u53D6\u308A\u305F\u3044\u5834\u5408\u306F \`updated_since\` \u304B \`/search/tickets\` \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002
656
+ - \u30BD\u30FC\u30C8: \`order_by\` \u2208 { created_at, updated_at, due_by, status }, \`order_type\` \u2208 { asc, desc }\u3002
657
+
658
+ #### \u30EA\u30BD\u30FC\u30B9\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
659
+
660
+ **\u30C1\u30B1\u30C3\u30C8**
661
+ - GET \`/tickets\` \u2014 \u30C1\u30B1\u30C3\u30C8\u4E00\u89A7\uFF08\`include=requester,company,stats\` \u3067\u95A2\u9023\u60C5\u5831\u3092\u5C55\u958B\u53EF\u80FD\uFF09
662
+ - GET \`/tickets/{id}\` \u2014 \u30C1\u30B1\u30C3\u30C8\u306E\u53D6\u5F97
663
+ - POST \`/tickets\` \u2014 \u30C1\u30B1\u30C3\u30C8\u306E\u4F5C\u6210
664
+ - PUT \`/tickets/{id}\` \u2014 \u30C1\u30B1\u30C3\u30C8\u306E\u66F4\u65B0
665
+ - DELETE \`/tickets/{id}\` \u2014 \u30C1\u30B1\u30C3\u30C8\u306E\u8AD6\u7406\u524A\u9664
666
+ - GET \`/tickets/{id}/conversations\` \u2014 \u8FD4\u4FE1\u30FB\u30CE\u30FC\u30C8\u4E00\u89A7
667
+ - POST \`/tickets/{id}/reply\` \u2014 \u30C1\u30B1\u30C3\u30C8\u3078\u306E\u8FD4\u4FE1
668
+ - POST \`/tickets/{id}/notes\` \u2014 \u30D7\u30E9\u30A4\u30D9\u30FC\u30C8/\u30D1\u30D6\u30EA\u30C3\u30AF\u30CE\u30FC\u30C8\u306E\u8FFD\u52A0
669
+ - GET \`/search/tickets?query="..."\` \u2014 \u9AD8\u5EA6\u306A\u691C\u7D22\uFF08\u4F8B: \`"priority:>2 AND status:2"\`\uFF09
670
+
671
+ **\u30E6\u30FC\u30B6\u30FC & \u4F1A\u793E**
672
+ - GET \`/contacts\` \u2014 \u30A8\u30F3\u30C9\u30E6\u30FC\u30B6\u30FC\u9023\u7D61\u5148\u306E\u4E00\u89A7
673
+ - GET \`/contacts/{id}\` \u2014 \u9023\u7D61\u5148\u306E\u53D6\u5F97
674
+ - POST \`/contacts\` \u2014 \u9023\u7D61\u5148\u306E\u4F5C\u6210
675
+ - PUT \`/contacts/{id}\` \u2014 \u9023\u7D61\u5148\u306E\u66F4\u65B0
676
+ - GET \`/companies\` \u2014 \u4F1A\u793E\u4E00\u89A7
677
+ - POST \`/companies\` \u2014 \u4F1A\u793E\u306E\u4F5C\u6210
678
+ - GET \`/agents\` \u2014 \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u4E00\u89A7
679
+ - GET \`/agents/me\` \u2014 \u8A8D\u8A3C\u4E2D\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u53D6\u5F97
680
+ - GET \`/groups\` \u2014 \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u30B0\u30EB\u30FC\u30D7
681
+
682
+ **\u30CA\u30EC\u30C3\u30B8\u30D9\u30FC\u30B9**
683
+ - GET \`/solutions/categories\` \u2014 \u30AB\u30C6\u30B4\u30EA
684
+ - GET \`/solutions/categories/{id}/folders\` \u2014 \u30AB\u30C6\u30B4\u30EA\u5185\u306E\u30D5\u30A9\u30EB\u30C0
685
+ - GET \`/solutions/folders/{id}/articles\` \u2014 \u30D5\u30A9\u30EB\u30C0\u5185\u306E\u8A18\u4E8B
686
+
687
+ #### \u30D5\u30A3\u30FC\u30EB\u30C9\u30B3\u30FC\u30C9\uFF08\u30C1\u30B1\u30C3\u30C8\uFF09
688
+
689
+ - \`priority\`: 1=Low, 2=Medium, 3=High, 4=Urgent
690
+ - \`status\`: 2=Open, 3=Pending, 4=Resolved, 5=Closed
691
+ - \`source\`: 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback widget, 10=Outbound email`
692
+ },
693
+ tools
694
+ });
695
+
696
+ // src/connectors/create-connector-sdk.ts
697
+ import { readFileSync } from "fs";
698
+ import path from "path";
699
+
700
+ // src/connector-client/env.ts
701
+ function resolveEnvVar(entry, key, connectionId) {
702
+ const envVarName = entry.envVars[key];
703
+ if (!envVarName) {
704
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
705
+ }
706
+ const value = process.env[envVarName];
707
+ if (!value) {
708
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
709
+ }
710
+ return value;
711
+ }
712
+ function resolveEnvVarOptional(entry, key) {
713
+ const envVarName = entry.envVars[key];
714
+ if (!envVarName) return void 0;
715
+ return process.env[envVarName] || void 0;
716
+ }
717
+
718
+ // src/connector-client/proxy-fetch.ts
719
+ import { getContext } from "hono/context-storage";
720
+ import { getCookie } from "hono/cookie";
721
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
722
+ function normalizeHeaders(input) {
723
+ const out = {};
724
+ if (!input) return out;
725
+ new Headers(input).forEach((value, key) => {
726
+ out[key] = value;
727
+ });
728
+ return out;
729
+ }
730
+ function createSandboxProxyFetch(connectionId) {
731
+ return async (input, init) => {
732
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
733
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
734
+ if (!token || !sandboxId) {
735
+ throw new Error(
736
+ "Connection proxy is not configured. Please check your deployment settings."
737
+ );
738
+ }
739
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
740
+ const originalMethod = init?.method ?? "GET";
741
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
742
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
743
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
744
+ return fetch(proxyUrl, {
745
+ method: "POST",
746
+ headers: {
747
+ "Content-Type": "application/json",
748
+ Authorization: `Bearer ${token}`
749
+ },
750
+ body: JSON.stringify({
751
+ url: originalUrl,
752
+ method: originalMethod,
753
+ headers: normalizeHeaders(init?.headers),
754
+ body: originalBody
755
+ })
756
+ });
757
+ };
758
+ }
759
+ function createDeployedAppProxyFetch(connectionId) {
760
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
761
+ if (!projectId) {
762
+ throw new Error(
763
+ "Connection proxy is not configured. Please check your deployment settings."
764
+ );
765
+ }
766
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
767
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
768
+ return async (input, init) => {
769
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
770
+ const originalMethod = init?.method ?? "GET";
771
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
772
+ const c = getContext();
773
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
774
+ if (!appSession) {
775
+ throw new Error(
776
+ "No authentication method available for connection proxy."
777
+ );
778
+ }
779
+ return fetch(proxyUrl, {
780
+ method: "POST",
781
+ headers: {
782
+ "Content-Type": "application/json",
783
+ Authorization: `Bearer ${appSession}`
784
+ },
785
+ body: JSON.stringify({
786
+ url: originalUrl,
787
+ method: originalMethod,
788
+ headers: normalizeHeaders(init?.headers),
789
+ body: originalBody
790
+ })
791
+ });
792
+ };
793
+ }
794
+ function createProxyFetch(connectionId) {
795
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
796
+ return createSandboxProxyFetch(connectionId);
797
+ }
798
+ return createDeployedAppProxyFetch(connectionId);
799
+ }
800
+
801
+ // src/connectors/create-connector-sdk.ts
802
+ function loadConnectionsSync() {
803
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
804
+ try {
805
+ const raw = readFileSync(filePath, "utf-8");
806
+ return JSON.parse(raw);
807
+ } catch {
808
+ return {};
809
+ }
810
+ }
811
+ function createConnectorSdk(plugin, createClient2) {
812
+ return (connectionId) => {
813
+ const connections = loadConnectionsSync();
814
+ const entry = connections[connectionId];
815
+ if (!entry) {
816
+ throw new Error(
817
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
818
+ );
819
+ }
820
+ if (entry.connector.slug !== plugin.slug) {
821
+ throw new Error(
822
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
823
+ );
824
+ }
825
+ const params = {};
826
+ for (const param of Object.values(plugin.parameters)) {
827
+ if (param.required) {
828
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
829
+ } else {
830
+ const val = resolveEnvVarOptional(entry, param.slug);
831
+ if (val !== void 0) params[param.slug] = val;
832
+ }
833
+ }
834
+ return createClient2(params, createProxyFetch(connectionId));
835
+ };
836
+ }
837
+
838
+ // src/connectors/entries/freshdesk.ts
839
+ var connection = createConnectorSdk(freshdeskConnector, createClient);
840
+ export {
841
+ connection
842
+ };