@squadbase/vite-server 0.1.17-dev.a9ddcfa → 0.1.17-dev.d4fff69
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/dist/cli/index.js +4299 -821
- package/dist/connectors/airtable-oauth.js +48 -8
- package/dist/connectors/airtable.js +44 -8
- package/dist/connectors/amplitude.js +8 -8
- package/dist/connectors/anthropic.js +2 -2
- package/dist/connectors/asana.js +37 -10
- package/dist/connectors/attio.js +30 -13
- package/dist/connectors/aws-billing.js +8 -8
- package/dist/connectors/azure-sql.js +47 -10
- package/dist/connectors/backlog-api-key.js +40 -15
- package/dist/connectors/clickup.js +50 -10
- package/dist/connectors/cosmosdb.js +12 -12
- package/dist/connectors/customerio.js +8 -8
- package/dist/connectors/dbt.js +686 -25
- package/dist/connectors/freshdesk.js +82 -8
- package/dist/connectors/freshsales.js +8 -8
- package/dist/connectors/freshservice.js +8 -8
- package/dist/connectors/gamma.js +15 -15
- package/dist/connectors/gemini.js +2 -2
- package/dist/connectors/github.js +12 -12
- package/dist/connectors/gmail-oauth.js +10 -10
- package/dist/connectors/gmail.js +4 -4
- package/dist/connectors/google-ads.js +8 -8
- package/dist/connectors/google-analytics-oauth.js +152 -25
- package/dist/connectors/google-analytics.js +490 -96
- package/dist/connectors/google-audit-log.js +4 -4
- package/dist/connectors/google-calendar-oauth.js +61 -15
- package/dist/connectors/google-calendar.js +61 -11
- package/dist/connectors/google-docs.js +10 -10
- package/dist/connectors/google-drive.js +32 -10
- package/dist/connectors/google-search-console-oauth.js +126 -17
- package/dist/connectors/google-sheets.js +6 -6
- package/dist/connectors/google-slides.js +10 -10
- package/dist/connectors/grafana.js +45 -10
- package/dist/connectors/hackernews.d.ts +5 -0
- package/dist/connectors/hackernews.js +890 -0
- package/dist/connectors/hubspot-oauth.js +41 -9
- package/dist/connectors/hubspot.js +25 -9
- package/dist/connectors/influxdb.js +8 -8
- package/dist/connectors/intercom-oauth.js +72 -12
- package/dist/connectors/intercom.js +12 -12
- package/dist/connectors/jdbc.js +37 -10
- package/dist/connectors/jira-api-key.js +68 -11
- package/dist/connectors/kintone-api-token.js +66 -18
- package/dist/connectors/kintone.js +54 -11
- package/dist/connectors/linear.js +54 -12
- package/dist/connectors/linkedin-ads.js +41 -14
- package/dist/connectors/mailchimp-oauth.js +6 -6
- package/dist/connectors/mailchimp.js +6 -6
- package/dist/connectors/meta-ads-oauth.js +33 -14
- package/dist/connectors/meta-ads.js +35 -14
- package/dist/connectors/mixpanel.js +8 -8
- package/dist/connectors/monday.js +9 -9
- package/dist/connectors/mongodb.js +8 -8
- package/dist/connectors/notion-oauth.js +60 -11
- package/dist/connectors/notion.js +60 -11
- package/dist/connectors/openai.js +2 -2
- package/dist/connectors/oracle.js +39 -11
- package/dist/connectors/outlook-oauth.js +21 -21
- package/dist/connectors/powerbi-oauth.js +13 -13
- package/dist/connectors/salesforce.js +42 -9
- package/dist/connectors/semrush.js +6 -6
- package/dist/connectors/sentry.js +36 -10
- package/dist/connectors/shopify-oauth.js +43 -10
- package/dist/connectors/shopify.js +8 -8
- package/dist/connectors/sqlserver.js +47 -10
- package/dist/connectors/stripe-api-key.js +66 -15
- package/dist/connectors/stripe-oauth.js +70 -19
- package/dist/connectors/supabase.js +31 -6
- package/dist/connectors/tableau.js +15 -15
- package/dist/connectors/tiktok-ads.js +37 -16
- package/dist/connectors/wix-store.js +8 -8
- package/dist/connectors/x.d.ts +5 -0
- package/dist/connectors/x.js +927 -0
- package/dist/connectors/zendesk-oauth.js +55 -12
- package/dist/connectors/zendesk.js +12 -12
- package/dist/index.js +4317 -819
- package/dist/main.js +4317 -819
- package/dist/vite-plugin.js +4297 -819
- package/package.json +9 -1
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// ../connectors/src/parameter-definition.ts
|
|
7
|
+
var ParameterDefinition;
|
|
8
|
+
var init_parameter_definition = __esm({
|
|
9
|
+
"../connectors/src/parameter-definition.ts"() {
|
|
10
|
+
"use strict";
|
|
11
|
+
ParameterDefinition = class {
|
|
12
|
+
slug;
|
|
13
|
+
name;
|
|
14
|
+
description;
|
|
15
|
+
envVarBaseKey;
|
|
16
|
+
type;
|
|
17
|
+
secret;
|
|
18
|
+
required;
|
|
19
|
+
isDeprecated;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.slug = config.slug;
|
|
22
|
+
this.name = config.name;
|
|
23
|
+
this.description = config.description;
|
|
24
|
+
this.envVarBaseKey = config.envVarBaseKey;
|
|
25
|
+
this.type = config.type;
|
|
26
|
+
this.secret = config.secret;
|
|
27
|
+
this.required = config.required;
|
|
28
|
+
this.isDeprecated = config.isDeprecated ?? false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the parameter value from a ConnectorConnectionObject.
|
|
32
|
+
*/
|
|
33
|
+
getValue(connection2) {
|
|
34
|
+
const param = connection2.parameters.find(
|
|
35
|
+
(p) => p.parameterSlug === this.slug
|
|
36
|
+
);
|
|
37
|
+
if (!param || param.value == null) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return param.value;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Try to get the parameter value. Returns undefined if not found (for optional params).
|
|
46
|
+
*/
|
|
47
|
+
tryGetValue(connection2) {
|
|
48
|
+
const param = connection2.parameters.find(
|
|
49
|
+
(p) => p.parameterSlug === this.slug
|
|
50
|
+
);
|
|
51
|
+
if (!param || param.value == null) return void 0;
|
|
52
|
+
return param.value;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ../connectors/src/connectors/x/parameters.ts
|
|
59
|
+
init_parameter_definition();
|
|
60
|
+
var parameters = {
|
|
61
|
+
bearerToken: new ParameterDefinition({
|
|
62
|
+
slug: "bearer-token",
|
|
63
|
+
name: "X API Bearer Token",
|
|
64
|
+
description: "The X API Bearer Token for app-only authentication. Create it in the X Developer Console for your app.",
|
|
65
|
+
envVarBaseKey: "X_BEARER_TOKEN",
|
|
66
|
+
type: "text",
|
|
67
|
+
secret: true,
|
|
68
|
+
required: true
|
|
69
|
+
})
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// ../connectors/src/connectors/x/sdk/index.ts
|
|
73
|
+
var BASE_URL = "https://api.x.com";
|
|
74
|
+
function createClient(params) {
|
|
75
|
+
const bearerToken = params[parameters.bearerToken.slug];
|
|
76
|
+
if (!bearerToken) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`x: missing required parameter: ${parameters.bearerToken.slug}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function buildUrl(path2, query) {
|
|
82
|
+
if (/^https?:\/\//i.test(path2)) {
|
|
83
|
+
throw new Error("x: absolute URLs are not allowed");
|
|
84
|
+
}
|
|
85
|
+
const url = new URL(`${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`);
|
|
86
|
+
if (query) {
|
|
87
|
+
for (const [k, v] of Object.entries(query)) {
|
|
88
|
+
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return url.toString();
|
|
92
|
+
}
|
|
93
|
+
async function request(path2, init) {
|
|
94
|
+
const { query, headers: initHeaders, ...rest } = init ?? {};
|
|
95
|
+
const headers = new Headers(initHeaders);
|
|
96
|
+
headers.set("Authorization", `Bearer ${bearerToken}`);
|
|
97
|
+
headers.set("Accept", "application/json");
|
|
98
|
+
if (rest.body && !headers.has("Content-Type")) {
|
|
99
|
+
headers.set("Content-Type", "application/json");
|
|
100
|
+
}
|
|
101
|
+
return fetch(buildUrl(path2, query), { ...rest, headers });
|
|
102
|
+
}
|
|
103
|
+
async function json(path2, query) {
|
|
104
|
+
const res = await request(path2, { method: "GET", query });
|
|
105
|
+
const text = await res.text();
|
|
106
|
+
const data = text ? JSON.parse(text) : {};
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
throw new Error(`x: ${res.status} ${res.statusText}: ${text}`);
|
|
109
|
+
}
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
request,
|
|
114
|
+
getUserByUsername(username, options) {
|
|
115
|
+
return json(
|
|
116
|
+
`/2/users/by/username/${encodeURIComponent(username)}`,
|
|
117
|
+
options?.query
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
getUser(id, options) {
|
|
121
|
+
return json(`/2/users/${encodeURIComponent(id)}`, options?.query);
|
|
122
|
+
},
|
|
123
|
+
getTweet(id, options) {
|
|
124
|
+
return json(`/2/tweets/${encodeURIComponent(id)}`, options?.query);
|
|
125
|
+
},
|
|
126
|
+
getUserTweets(userId, options) {
|
|
127
|
+
return json(
|
|
128
|
+
`/2/users/${encodeURIComponent(userId)}/tweets`,
|
|
129
|
+
options?.query
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
searchRecent(query, options) {
|
|
133
|
+
return json("/2/tweets/search/recent", {
|
|
134
|
+
query,
|
|
135
|
+
...options?.query ?? {}
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
searchAll(query, options) {
|
|
139
|
+
return json("/2/tweets/search/all", {
|
|
140
|
+
query,
|
|
141
|
+
...options?.query ?? {}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ../connectors/src/connector-onboarding.ts
|
|
148
|
+
var ConnectorOnboarding = class {
|
|
149
|
+
/** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
|
|
150
|
+
connectionSetupInstructions;
|
|
151
|
+
/** Phase 2: Data overview instructions */
|
|
152
|
+
dataOverviewInstructions;
|
|
153
|
+
constructor(config) {
|
|
154
|
+
this.connectionSetupInstructions = config.connectionSetupInstructions;
|
|
155
|
+
this.dataOverviewInstructions = config.dataOverviewInstructions;
|
|
156
|
+
}
|
|
157
|
+
getConnectionSetupPrompt(language) {
|
|
158
|
+
return this.connectionSetupInstructions?.[language] ?? null;
|
|
159
|
+
}
|
|
160
|
+
getDataOverviewInstructions(language) {
|
|
161
|
+
return this.dataOverviewInstructions[language];
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// ../connectors/src/connector-tool.ts
|
|
166
|
+
var ConnectorTool = class {
|
|
167
|
+
name;
|
|
168
|
+
description;
|
|
169
|
+
inputSchema;
|
|
170
|
+
outputSchema;
|
|
171
|
+
_execute;
|
|
172
|
+
constructor(config) {
|
|
173
|
+
this.name = config.name;
|
|
174
|
+
this.description = config.description;
|
|
175
|
+
this.inputSchema = config.inputSchema;
|
|
176
|
+
this.outputSchema = config.outputSchema;
|
|
177
|
+
this._execute = config.execute;
|
|
178
|
+
}
|
|
179
|
+
createTool(connections, config) {
|
|
180
|
+
return {
|
|
181
|
+
description: this.description,
|
|
182
|
+
inputSchema: this.inputSchema,
|
|
183
|
+
outputSchema: this.outputSchema,
|
|
184
|
+
execute: (input) => this._execute(input, connections, config)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// ../connectors/src/connector-plugin.ts
|
|
190
|
+
var ConnectorPlugin = class _ConnectorPlugin {
|
|
191
|
+
slug;
|
|
192
|
+
authType;
|
|
193
|
+
name;
|
|
194
|
+
description;
|
|
195
|
+
iconUrl;
|
|
196
|
+
parameters;
|
|
197
|
+
releaseFlag;
|
|
198
|
+
proxyPolicy;
|
|
199
|
+
experimentalAttributes;
|
|
200
|
+
categories;
|
|
201
|
+
onboarding;
|
|
202
|
+
systemPrompt;
|
|
203
|
+
tools;
|
|
204
|
+
query;
|
|
205
|
+
checkConnection;
|
|
206
|
+
/**
|
|
207
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
208
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
209
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
210
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
211
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
212
|
+
*/
|
|
213
|
+
setup;
|
|
214
|
+
/**
|
|
215
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
216
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
217
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
218
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
219
|
+
*
|
|
220
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
221
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
222
|
+
* connectionId, which doesn't exist until the row is saved
|
|
223
|
+
*
|
|
224
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
225
|
+
* the default verify-on-create behavior without opt-in.
|
|
226
|
+
*/
|
|
227
|
+
skipConnectionCheckOnCreate;
|
|
228
|
+
constructor(config) {
|
|
229
|
+
this.slug = config.slug;
|
|
230
|
+
this.authType = config.authType;
|
|
231
|
+
this.name = config.name;
|
|
232
|
+
this.description = config.description;
|
|
233
|
+
this.iconUrl = config.iconUrl;
|
|
234
|
+
this.parameters = config.parameters;
|
|
235
|
+
this.releaseFlag = config.releaseFlag;
|
|
236
|
+
this.proxyPolicy = config.proxyPolicy;
|
|
237
|
+
this.experimentalAttributes = config.experimentalAttributes;
|
|
238
|
+
this.categories = config.categories ?? [];
|
|
239
|
+
this.onboarding = config.onboarding;
|
|
240
|
+
this.systemPrompt = config.systemPrompt;
|
|
241
|
+
this.tools = config.tools;
|
|
242
|
+
this.query = config.query;
|
|
243
|
+
this.checkConnection = config.checkConnection;
|
|
244
|
+
this.setup = config.setup;
|
|
245
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
246
|
+
}
|
|
247
|
+
get connectorKey() {
|
|
248
|
+
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Create tools for connections that belong to this connector.
|
|
252
|
+
* Filters connections by connectorKey internally.
|
|
253
|
+
* Returns tools keyed as `connector_${connectorKey}_${toolName}`.
|
|
254
|
+
*/
|
|
255
|
+
createTools(connections, config, opts) {
|
|
256
|
+
const myConnections = connections.filter(
|
|
257
|
+
(c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
|
|
258
|
+
);
|
|
259
|
+
const result = {};
|
|
260
|
+
for (const t of Object.values(this.tools)) {
|
|
261
|
+
const tool = t.createTool(myConnections, config);
|
|
262
|
+
const originalToModelOutput = tool.toModelOutput;
|
|
263
|
+
result[`connector_${this.connectorKey}_${t.name}`] = {
|
|
264
|
+
...tool,
|
|
265
|
+
toModelOutput: async (options) => {
|
|
266
|
+
if (!originalToModelOutput) {
|
|
267
|
+
return opts.truncateOutput(options.output);
|
|
268
|
+
}
|
|
269
|
+
const modelOutput = await originalToModelOutput(options);
|
|
270
|
+
if (modelOutput.type === "text" || modelOutput.type === "json") {
|
|
271
|
+
return opts.truncateOutput(modelOutput.value);
|
|
272
|
+
}
|
|
273
|
+
return modelOutput;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
static deriveKey(slug, authType) {
|
|
280
|
+
if (authType) return `${slug}-${authType}`;
|
|
281
|
+
const LEGACY_NULL_AUTH_TYPE_MAP = {
|
|
282
|
+
// user-password
|
|
283
|
+
"postgresql": "user-password",
|
|
284
|
+
"mysql": "user-password",
|
|
285
|
+
"clickhouse": "user-password",
|
|
286
|
+
"kintone": "user-password",
|
|
287
|
+
"squadbase-db": "user-password",
|
|
288
|
+
// service-account
|
|
289
|
+
"snowflake": "service-account",
|
|
290
|
+
"bigquery": "service-account",
|
|
291
|
+
"google-analytics": "service-account",
|
|
292
|
+
"google-calendar": "service-account",
|
|
293
|
+
"aws-athena": "service-account",
|
|
294
|
+
"redshift": "service-account",
|
|
295
|
+
// api-key
|
|
296
|
+
"databricks": "api-key",
|
|
297
|
+
"dbt": "api-key",
|
|
298
|
+
"airtable": "api-key",
|
|
299
|
+
"openai": "api-key",
|
|
300
|
+
"gemini": "api-key",
|
|
301
|
+
"anthropic": "api-key",
|
|
302
|
+
"wix-store": "api-key"
|
|
303
|
+
};
|
|
304
|
+
const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
|
|
305
|
+
if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
|
|
306
|
+
return slug;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// ../connectors/src/setup-flow.ts
|
|
311
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
312
|
+
const runtime = {
|
|
313
|
+
params,
|
|
314
|
+
language: ctx.language,
|
|
315
|
+
config
|
|
316
|
+
};
|
|
317
|
+
let state = flow.initialState();
|
|
318
|
+
let answerIdx = 0;
|
|
319
|
+
const pendingParameterUpdates = [];
|
|
320
|
+
for (const step of flow.steps) {
|
|
321
|
+
const ans = ctx.answers[answerIdx];
|
|
322
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
323
|
+
state = step.applyAnswer(state, ans.answer);
|
|
324
|
+
if (step.toParameterUpdates) {
|
|
325
|
+
pendingParameterUpdates.push(...step.toParameterUpdates(state));
|
|
326
|
+
}
|
|
327
|
+
answerIdx += 1;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
|
|
331
|
+
if (step.type === "text") {
|
|
332
|
+
if (step.fetchOptions) {
|
|
333
|
+
const options2 = await step.fetchOptions(state, runtime);
|
|
334
|
+
if (options2.length === 0) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
type: "nextQuestion",
|
|
340
|
+
questionSlug: step.slug,
|
|
341
|
+
question: step.question[ctx.language],
|
|
342
|
+
questionType: "text",
|
|
343
|
+
allowFreeText: resolvedAllowFreeText,
|
|
344
|
+
...pendingParameterUpdates.length > 0 && {
|
|
345
|
+
parameterUpdates: pendingParameterUpdates
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
350
|
+
if (options.length === 0) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
type: "nextQuestion",
|
|
355
|
+
questionSlug: step.slug,
|
|
356
|
+
question: step.question[ctx.language],
|
|
357
|
+
questionType: step.type,
|
|
358
|
+
options,
|
|
359
|
+
allowFreeText: resolvedAllowFreeText,
|
|
360
|
+
...pendingParameterUpdates.length > 0 && {
|
|
361
|
+
parameterUpdates: pendingParameterUpdates
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
366
|
+
return {
|
|
367
|
+
type: "fulfilled",
|
|
368
|
+
dataInvestigationResult,
|
|
369
|
+
...pendingParameterUpdates.length > 0 && {
|
|
370
|
+
parameterUpdates: pendingParameterUpdates
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ../connectors/src/auth-types.ts
|
|
376
|
+
var AUTH_TYPES = {
|
|
377
|
+
OAUTH: "oauth",
|
|
378
|
+
API_KEY: "api-key",
|
|
379
|
+
JWT: "jwt",
|
|
380
|
+
SERVICE_ACCOUNT: "service-account",
|
|
381
|
+
PAT: "pat",
|
|
382
|
+
USER_PASSWORD: "user-password"
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// ../connectors/src/connectors/x/tools/request.ts
|
|
386
|
+
import { z } from "zod";
|
|
387
|
+
var BASE_URL2 = "https://api.x.com";
|
|
388
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
389
|
+
function readRateLimit(headers) {
|
|
390
|
+
const limit = headers.get("x-rate-limit-limit");
|
|
391
|
+
const remaining = headers.get("x-rate-limit-remaining");
|
|
392
|
+
const reset = headers.get("x-rate-limit-reset");
|
|
393
|
+
return {
|
|
394
|
+
limit: limit ? Number(limit) : void 0,
|
|
395
|
+
remaining: remaining ? Number(remaining) : void 0,
|
|
396
|
+
reset: reset ? Number(reset) : void 0
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
var inputSchema = z.object({
|
|
400
|
+
toolUseIntent: z.string().optional().describe(
|
|
401
|
+
"Brief description of what you intend to accomplish with this tool call"
|
|
402
|
+
),
|
|
403
|
+
connectionId: z.string().describe("ID of the X connection to use"),
|
|
404
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe(
|
|
405
|
+
"HTTP method. Use GET for dashboard data reads. Use POST/PUT/DELETE only when the user explicitly asks for mutations supported by their X API access level."
|
|
406
|
+
),
|
|
407
|
+
path: z.string().describe(
|
|
408
|
+
"API path appended to https://api.x.com. Use '/2/...' paths such as '/2/users/by/username/xdevelopers', '/2/users/{id}/tweets', or '/2/tweets/search/recent'. Absolute URLs are not allowed."
|
|
409
|
+
),
|
|
410
|
+
queryParams: z.record(z.string(), z.string()).optional().describe(
|
|
411
|
+
"Query parameters to append to the request URL. Examples: tweet.fields=created_at,public_metrics, user.fields=created_at,public_metrics, max_results=10, query=from:xdevelopers. Keep max_results small for dashboard exploration."
|
|
412
|
+
),
|
|
413
|
+
body: z.record(z.string(), z.unknown()).optional().describe("JSON request body for POST/PUT requests.")
|
|
414
|
+
});
|
|
415
|
+
var outputSchema = z.discriminatedUnion("success", [
|
|
416
|
+
z.object({
|
|
417
|
+
success: z.literal(true),
|
|
418
|
+
status: z.number(),
|
|
419
|
+
rateLimit: z.object({
|
|
420
|
+
limit: z.number().optional(),
|
|
421
|
+
remaining: z.number().optional(),
|
|
422
|
+
reset: z.number().optional()
|
|
423
|
+
}),
|
|
424
|
+
data: z.unknown()
|
|
425
|
+
}),
|
|
426
|
+
z.object({
|
|
427
|
+
success: z.literal(false),
|
|
428
|
+
status: z.number().optional(),
|
|
429
|
+
rateLimit: z.object({
|
|
430
|
+
limit: z.number().optional(),
|
|
431
|
+
remaining: z.number().optional(),
|
|
432
|
+
reset: z.number().optional()
|
|
433
|
+
}).optional(),
|
|
434
|
+
error: z.string()
|
|
435
|
+
})
|
|
436
|
+
]);
|
|
437
|
+
var requestTool = new ConnectorTool({
|
|
438
|
+
name: "request",
|
|
439
|
+
description: `Send authenticated requests to the X API (https://api.x.com).
|
|
440
|
+
Use this tool for X API v2 reads such as looking up users, fetching posts, retrieving timelines, and performing recent search. Authentication is handled automatically with the connection's Bearer Token; never include Authorization headers yourself.
|
|
441
|
+
|
|
442
|
+
X rate limits are endpoint-specific and commonly reset on fixed windows. This tool returns x-rate-limit-limit, x-rate-limit-remaining, and x-rate-limit-reset when X sends them. Before making repeated calls or adding pagination, inspect these values and keep dashboard requests small. Do not use this tool for frequent polling; if a request returns 429, wait until the reset timestamp instead of retrying immediately.`,
|
|
443
|
+
inputSchema,
|
|
444
|
+
outputSchema,
|
|
445
|
+
async execute({ connectionId, method, path: path2, queryParams, body }, connections) {
|
|
446
|
+
const connection2 = connections.find((c) => c.id === connectionId);
|
|
447
|
+
if (!connection2) {
|
|
448
|
+
return {
|
|
449
|
+
success: false,
|
|
450
|
+
error: `Connection ${connectionId} not found`
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
console.log(`[connector-request] x/${connection2.name}: ${method} ${path2}`);
|
|
454
|
+
try {
|
|
455
|
+
if (/^https?:\/\//i.test(path2)) {
|
|
456
|
+
return {
|
|
457
|
+
success: false,
|
|
458
|
+
error: "Absolute URLs are not allowed. Pass a path such as /2/users/me."
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const bearerToken = parameters.bearerToken.getValue(connection2);
|
|
462
|
+
const url = new URL(
|
|
463
|
+
`${BASE_URL2}${path2.startsWith("/") ? "" : "/"}${path2}`
|
|
464
|
+
);
|
|
465
|
+
if (queryParams) {
|
|
466
|
+
for (const [k, v] of Object.entries(queryParams)) {
|
|
467
|
+
url.searchParams.set(k, v);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const controller = new AbortController();
|
|
471
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
472
|
+
try {
|
|
473
|
+
const response = await fetch(url.toString(), {
|
|
474
|
+
method,
|
|
475
|
+
headers: {
|
|
476
|
+
Authorization: `Bearer ${bearerToken}`,
|
|
477
|
+
Accept: "application/json",
|
|
478
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
479
|
+
},
|
|
480
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
481
|
+
signal: controller.signal
|
|
482
|
+
});
|
|
483
|
+
const rateLimit = readRateLimit(response.headers);
|
|
484
|
+
const text = await response.text();
|
|
485
|
+
let data = text;
|
|
486
|
+
if (text.length > 0) {
|
|
487
|
+
try {
|
|
488
|
+
data = JSON.parse(text);
|
|
489
|
+
} catch {
|
|
490
|
+
data = text;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (!response.ok) {
|
|
494
|
+
const error = typeof data === "object" && data != null ? JSON.stringify(data) : String(data || response.statusText);
|
|
495
|
+
return {
|
|
496
|
+
success: false,
|
|
497
|
+
status: response.status,
|
|
498
|
+
rateLimit,
|
|
499
|
+
error
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
success: true,
|
|
504
|
+
status: response.status,
|
|
505
|
+
rateLimit,
|
|
506
|
+
data
|
|
507
|
+
};
|
|
508
|
+
} finally {
|
|
509
|
+
clearTimeout(timeout);
|
|
510
|
+
}
|
|
511
|
+
} catch (err) {
|
|
512
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
513
|
+
return { success: false, error: msg };
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// ../connectors/src/connectors/x/setup.ts
|
|
519
|
+
var requestToolName = `x-api-key_${requestTool.name}`;
|
|
520
|
+
var xOnboarding = new ConnectorOnboarding({
|
|
521
|
+
connectionSetupInstructions: {
|
|
522
|
+
en: `Follow these steps to set up the X connection.
|
|
523
|
+
|
|
524
|
+
1. Call \`${requestToolName}\` to verify the Bearer Token:
|
|
525
|
+
- \`method\`: \`"GET"\`
|
|
526
|
+
- \`path\`: \`"/2/users/by/username/xdevelopers"\`
|
|
527
|
+
2. If an error is returned, ask the user to verify their Bearer Token (it must be a valid App-only Bearer Token with at least Basic access)
|
|
528
|
+
|
|
529
|
+
#### Constraints
|
|
530
|
+
- **Do NOT read post or user data beyond the single verification call above**
|
|
531
|
+
- Write only 1 sentence between tool calls, then immediately call the next tool. Skip unnecessary explanations and proceed efficiently`,
|
|
532
|
+
ja: `\u4EE5\u4E0B\u306E\u624B\u9806\u3067X\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
|
|
533
|
+
|
|
534
|
+
1. \`${requestToolName}\` \u3092\u547C\u3073\u51FA\u3057\u3066 Bearer Token \u3092\u691C\u8A3C\u3059\u308B:
|
|
535
|
+
- \`method\`: \`"GET"\`
|
|
536
|
+
- \`path\`: \`"/2/users/by/username/xdevelopers"\`
|
|
537
|
+
2. \u30A8\u30E9\u30FC\u304C\u8FD4\u3055\u308C\u305F\u5834\u5408\u3001\u30E6\u30FC\u30B6\u30FC\u306B Bearer Token \u306E\u78BA\u8A8D\u3092\u4F9D\u983C\u3059\u308B\uFF08Basic \u30A2\u30AF\u30BB\u30B9\u4EE5\u4E0A\u306E\u6A29\u9650\u3092\u6301\u3064\u6709\u52B9\u306A App-only Bearer Token \u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u308B\u304B\u78BA\u8A8D\uFF09
|
|
538
|
+
|
|
539
|
+
#### \u5236\u7D04
|
|
540
|
+
- **\u4E0A\u8A18\u306E\u691C\u8A3C\u547C\u3073\u51FA\u3057 1 \u56DE\u3092\u8D85\u3048\u3066\u6295\u7A3F\u3084\u30E6\u30FC\u30B6\u30FC\u30C7\u30FC\u30BF\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3053\u3068**
|
|
541
|
+
- \u30C4\u30FC\u30EB\u9593\u306F 1 \u6587\u3060\u3051\u66F8\u3044\u3066\u5373\u6B21\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3002\u4E0D\u8981\u306A\u8AAC\u660E\u306F\u7701\u7565\u3057\u3001\u52B9\u7387\u7684\u306B\u9032\u3081\u308B`
|
|
542
|
+
},
|
|
543
|
+
dataOverviewInstructions: {
|
|
544
|
+
en: `1. Use connector_x-api-key_request with GET /2/users/by/username/{username} to inspect target accounts
|
|
545
|
+
2. Use GET /2/users/{id}/tweets with a small max_results value to sample recent posts
|
|
546
|
+
3. Use GET /2/tweets/search/recent only when keyword search is required
|
|
547
|
+
4. Always check rateLimit.remaining and rateLimit.reset in tool results before issuing additional calls`,
|
|
548
|
+
ja: `1. connector_x-api-key_request \u3067 GET /2/users/by/username/{username} \u3092\u547C\u3073\u51FA\u3057\u3001\u5BFE\u8C61\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u78BA\u8A8D\u3057\u307E\u3059
|
|
549
|
+
2. GET /2/users/{id}/tweets \u3092\u5C0F\u3055\u306A max_results \u3067\u547C\u3073\u51FA\u3057\u3001\u6700\u8FD1\u306E\u6295\u7A3F\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3057\u307E\u3059
|
|
550
|
+
3. \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u304C\u5FC5\u8981\u306A\u5834\u5408\u306E\u307F GET /2/tweets/search/recent \u3092\u4F7F\u7528\u3057\u307E\u3059
|
|
551
|
+
4. \u8FFD\u52A0\u306E\u547C\u3073\u51FA\u3057\u524D\u306B\u3001\u30C4\u30FC\u30EB\u7D50\u679C\u306E rateLimit.remaining \u3068 rateLimit.reset \u3092\u5FC5\u305A\u78BA\u8A8D\u3057\u307E\u3059`
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// ../connectors/src/connectors/x/setup-flow.ts
|
|
556
|
+
var xSetupFlow = {
|
|
557
|
+
initialState: () => ({}),
|
|
558
|
+
steps: [],
|
|
559
|
+
async finalize(_state, rt) {
|
|
560
|
+
const sections = rt.language === "ja" ? [
|
|
561
|
+
"## X",
|
|
562
|
+
"",
|
|
563
|
+
"- \u63A5\u7D9A\u306F Bearer Token \u3067\u8A8D\u8A3C\u3055\u308C\u307E\u3059\u3002",
|
|
564
|
+
"- \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u5B9F\u88C5\u3067\u306F\u3001\u540C\u3058\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u306E\u77ED\u5468\u671F\u30DD\u30FC\u30EA\u30F3\u30B0\u3084\u5927\u91CF\u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u3092\u907F\u3051\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
565
|
+
"- X API \u306E rate limit \u306F\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u5358\u4F4D\u3067\u7BA1\u7406\u3055\u308C\u307E\u3059\u3002\u30EC\u30B9\u30DD\u30F3\u30B9\u30D8\u30C3\u30C0\u30FC\u306E `x-rate-limit-remaining` \u3068 `x-rate-limit-reset` \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
566
|
+
"- 429 \u304C\u8FD4\u3063\u305F\u5834\u5408\u306F\u3001reset \u6642\u523B\u307E\u3067\u518D\u8A66\u884C\u3092\u6B62\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
567
|
+
] : [
|
|
568
|
+
"## X",
|
|
569
|
+
"",
|
|
570
|
+
"- The connection authenticates with a Bearer Token.",
|
|
571
|
+
"- When building dashboards, avoid frequent polling and large pagination against the same endpoint.",
|
|
572
|
+
"- X API rate limits are endpoint-specific. Check the `x-rate-limit-remaining` and `x-rate-limit-reset` response headers.",
|
|
573
|
+
"- If a request returns 429, stop retrying until the reset time."
|
|
574
|
+
];
|
|
575
|
+
return sections.join("\n");
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// ../connectors/src/connectors/x/index.ts
|
|
580
|
+
var tools = { request: requestTool };
|
|
581
|
+
var xConnector = new ConnectorPlugin({
|
|
582
|
+
slug: "x",
|
|
583
|
+
authType: AUTH_TYPES.API_KEY,
|
|
584
|
+
name: "X",
|
|
585
|
+
description: "Connect to X API v2 for posts, users, search, timelines, trends, and related social media data using a user-provided Bearer Token.",
|
|
586
|
+
iconUrl: "https://cdn.simpleicons.org/x/000000",
|
|
587
|
+
parameters,
|
|
588
|
+
releaseFlag: { dev1: true, dev2: true, prod: false },
|
|
589
|
+
categories: ["social_media"],
|
|
590
|
+
onboarding: xOnboarding,
|
|
591
|
+
systemPrompt: {
|
|
592
|
+
en: `### Tools
|
|
593
|
+
|
|
594
|
+
- \`connector_x-api-key_request\`: Send authenticated requests to X API v2. Use it for reading users, posts, timelines, and recent search. Authentication is handled automatically with the connection's Bearer Token. The tool returns rate limit headers when available; inspect \`rateLimit.remaining\` and \`rateLimit.reset\` before making repeated calls.
|
|
595
|
+
|
|
596
|
+
### Business Logic
|
|
597
|
+
|
|
598
|
+
The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
|
|
599
|
+
|
|
600
|
+
SDK methods (client created via \`connection(connectionId)\`):
|
|
601
|
+
- \`client.request(path, init?)\` \u2014 low-level authenticated fetch against \`https://api.x.com\`
|
|
602
|
+
- \`client.getUserByUsername(username, options?)\` \u2014 get an X user by username
|
|
603
|
+
- \`client.getUser(id, options?)\` \u2014 get an X user by ID
|
|
604
|
+
- \`client.getTweet(id, options?)\` \u2014 get a post by ID
|
|
605
|
+
- \`client.getUserTweets(userId, options?)\` \u2014 get recent posts for a user
|
|
606
|
+
- \`client.searchRecent(query, options?)\` \u2014 search recent posts
|
|
607
|
+
- \`client.searchAll(query, options?)\` \u2014 search the full archive when the connected X API access tier supports it
|
|
608
|
+
|
|
609
|
+
When building dashboards, keep API calls small and cache or reuse responses in your own handler where possible. Avoid short-interval polling, avoid fetching many pages during rendering, and avoid repeated recent-search calls for the same query. If X returns HTTP 429, do not retry immediately; wait until the \`x-rate-limit-reset\` timestamp.
|
|
610
|
+
|
|
611
|
+
\`\`\`ts
|
|
612
|
+
import type { Context } from "hono";
|
|
613
|
+
import { connection } from "@squadbase/vite-server/connectors/x";
|
|
614
|
+
|
|
615
|
+
const x = connection("<connectionId>");
|
|
616
|
+
|
|
617
|
+
export default async function handler(c: Context) {
|
|
618
|
+
const { username = "xdevelopers", maxResults = 10 } = await c.req.json<{
|
|
619
|
+
username?: string;
|
|
620
|
+
maxResults?: number;
|
|
621
|
+
}>();
|
|
622
|
+
|
|
623
|
+
const user = await x.getUserByUsername(username, {
|
|
624
|
+
query: { "user.fields": "created_at,description,public_metrics" },
|
|
625
|
+
});
|
|
626
|
+
const userId = (user.data as { id: string }).id;
|
|
627
|
+
const tweets = await x.getUserTweets(userId, {
|
|
628
|
+
query: {
|
|
629
|
+
max_results: Math.min(maxResults, 25),
|
|
630
|
+
"tweet.fields": "created_at,public_metrics",
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
return c.json({ user: user.data, tweets: tweets.data ?? [] });
|
|
635
|
+
}
|
|
636
|
+
\`\`\`
|
|
637
|
+
|
|
638
|
+
### X API Reference
|
|
639
|
+
|
|
640
|
+
- Base URL: \`https://api.x.com\`
|
|
641
|
+
- Authentication: Bearer Token (handled automatically)
|
|
642
|
+
- Rate limits: endpoint-specific; response headers may include \`x-rate-limit-limit\`, \`x-rate-limit-remaining\`, and \`x-rate-limit-reset\`
|
|
643
|
+
|
|
644
|
+
#### Common Endpoints
|
|
645
|
+
- GET \`/2/users/by/username/{username}\` \u2014 Look up a user by username
|
|
646
|
+
- GET \`/2/users/{id}\` \u2014 Look up a user by ID
|
|
647
|
+
- GET \`/2/users/{id}/tweets\` \u2014 Get a user's recent posts
|
|
648
|
+
- GET \`/2/tweets/{id}\` \u2014 Look up a post by ID
|
|
649
|
+
- GET \`/2/tweets/search/recent\` \u2014 Search recent posts
|
|
650
|
+
- GET \`/2/tweets/search/all\` \u2014 Search the full archive (requires X API access that includes full-archive search)
|
|
651
|
+
- GET \`/2/tweets/counts/recent\` \u2014 Count recent posts matching a query
|
|
652
|
+
|
|
653
|
+
#### Tips
|
|
654
|
+
- Request only fields you need with \`tweet.fields\`, \`user.fields\`, and \`expansions\`
|
|
655
|
+
- Use small \`max_results\` values while exploring; dashboards rarely need hundreds of posts per render
|
|
656
|
+
- Prefer counts endpoints for trend charts when the user needs aggregate volume instead of individual posts`,
|
|
657
|
+
ja: `### \u30C4\u30FC\u30EB
|
|
658
|
+
|
|
659
|
+
- \`connector_x-api-key_request\`: X API v2 \u306B\u8A8D\u8A3C\u6E08\u307F\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002\u30E6\u30FC\u30B6\u30FC\u3001\u6295\u7A3F\u3001\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3001recent search \u306E\u8AAD\u307F\u53D6\u308A\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A8D\u8A3C\u306F\u63A5\u7D9A\u306E Bearer Token \u3067\u81EA\u52D5\u7684\u306B\u884C\u308F\u308C\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u5834\u5408\u306F rate limit \u30D8\u30C3\u30C0\u30FC\u3082\u8FD4\u3059\u305F\u3081\u3001\u7E70\u308A\u8FD4\u3057\u547C\u3073\u51FA\u3059\u524D\u306B \`rateLimit.remaining\` \u3068 \`rateLimit.reset\` \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
660
|
+
|
|
661
|
+
### Business Logic
|
|
662
|
+
|
|
663
|
+
\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
|
|
664
|
+
|
|
665
|
+
SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
|
|
666
|
+
- \`client.request(path, init?)\` \u2014 \`https://api.x.com\` \u306B\u5BFE\u3059\u308B\u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
|
|
667
|
+
- \`client.getUserByUsername(username, options?)\` \u2014 username \u3067 X \u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
|
|
668
|
+
- \`client.getUser(id, options?)\` \u2014 ID \u3067 X \u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
|
|
669
|
+
- \`client.getTweet(id, options?)\` \u2014 ID \u3067\u6295\u7A3F\u3092\u53D6\u5F97
|
|
670
|
+
- \`client.getUserTweets(userId, options?)\` \u2014 \u30E6\u30FC\u30B6\u30FC\u306E\u6700\u8FD1\u306E\u6295\u7A3F\u3092\u53D6\u5F97
|
|
671
|
+
- \`client.searchRecent(query, options?)\` \u2014 \u6700\u8FD1\u306E\u6295\u7A3F\u3092\u691C\u7D22
|
|
672
|
+
- \`client.searchAll(query, options?)\` \u2014 \u63A5\u7D9A\u4E2D\u306E X API access tier \u304C\u5BFE\u5FDC\u3057\u3066\u3044\u308B\u5834\u5408\u306B full archive \u3092\u691C\u7D22
|
|
673
|
+
|
|
674
|
+
\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3092\u4F5C\u308B\u6642\u306F API \u547C\u3073\u51FA\u3057\u3092\u5C0F\u3055\u304F\u3057\u3001\u53EF\u80FD\u3067\u3042\u308C\u3070\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u518D\u5229\u7528\u30FB\u30AD\u30E3\u30C3\u30B7\u30E5\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u77ED\u5468\u671F\u30DD\u30FC\u30EA\u30F3\u30B0\u3001\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u3054\u3068\u306E\u5927\u91CF\u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u3001\u540C\u3058\u30AF\u30A8\u30EA\u3067\u306E recent search \u9023\u6253\u306F\u907F\u3051\u3066\u304F\u3060\u3055\u3044\u3002X \u304C HTTP 429 \u3092\u8FD4\u3057\u305F\u5834\u5408\u306F\u5373\u6642\u30EA\u30C8\u30E9\u30A4\u305B\u305A\u3001\`x-rate-limit-reset\` \u306E\u6642\u523B\u307E\u3067\u5F85\u3063\u3066\u304F\u3060\u3055\u3044\u3002
|
|
675
|
+
|
|
676
|
+
\`\`\`ts
|
|
677
|
+
import type { Context } from "hono";
|
|
678
|
+
import { connection } from "@squadbase/vite-server/connectors/x";
|
|
679
|
+
|
|
680
|
+
const x = connection("<connectionId>");
|
|
681
|
+
|
|
682
|
+
export default async function handler(c: Context) {
|
|
683
|
+
const { username = "xdevelopers", maxResults = 10 } = await c.req.json<{
|
|
684
|
+
username?: string;
|
|
685
|
+
maxResults?: number;
|
|
686
|
+
}>();
|
|
687
|
+
|
|
688
|
+
const user = await x.getUserByUsername(username, {
|
|
689
|
+
query: { "user.fields": "created_at,description,public_metrics" },
|
|
690
|
+
});
|
|
691
|
+
const userId = (user.data as { id: string }).id;
|
|
692
|
+
const tweets = await x.getUserTweets(userId, {
|
|
693
|
+
query: {
|
|
694
|
+
max_results: Math.min(maxResults, 25),
|
|
695
|
+
"tweet.fields": "created_at,public_metrics",
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return c.json({ user: user.data, tweets: tweets.data ?? [] });
|
|
700
|
+
}
|
|
701
|
+
\`\`\`
|
|
702
|
+
|
|
703
|
+
### X API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
|
|
704
|
+
|
|
705
|
+
- \u30D9\u30FC\u30B9URL: \`https://api.x.com\`
|
|
706
|
+
- \u8A8D\u8A3C: Bearer Token\uFF08\u81EA\u52D5\u8A2D\u5B9A\uFF09
|
|
707
|
+
- Rate limit: \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u5358\u4F4D\u3002\u30EC\u30B9\u30DD\u30F3\u30B9\u30D8\u30C3\u30C0\u30FC\u306B \`x-rate-limit-limit\`, \`x-rate-limit-remaining\`, \`x-rate-limit-reset\` \u304C\u542B\u307E\u308C\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059
|
|
708
|
+
|
|
709
|
+
#### \u4E3B\u8981\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
|
|
710
|
+
- GET \`/2/users/by/username/{username}\` \u2014 username \u3067\u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
|
|
711
|
+
- GET \`/2/users/{id}\` \u2014 ID \u3067\u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
|
|
712
|
+
- GET \`/2/users/{id}/tweets\` \u2014 \u30E6\u30FC\u30B6\u30FC\u306E\u6700\u8FD1\u306E\u6295\u7A3F\u3092\u53D6\u5F97
|
|
713
|
+
- GET \`/2/tweets/{id}\` \u2014 ID \u3067\u6295\u7A3F\u3092\u53D6\u5F97
|
|
714
|
+
- GET \`/2/tweets/search/recent\` \u2014 \u6700\u8FD1\u306E\u6295\u7A3F\u3092\u691C\u7D22
|
|
715
|
+
- GET \`/2/tweets/search/all\` \u2014 full archive \u3092\u691C\u7D22\uFF08full-archive search \u3092\u542B\u3080 X API access \u304C\u5FC5\u8981\uFF09
|
|
716
|
+
- GET \`/2/tweets/counts/recent\` \u2014 \u30AF\u30A8\u30EA\u306B\u4E00\u81F4\u3059\u308B\u6700\u8FD1\u306E\u6295\u7A3F\u6570\u3092\u96C6\u8A08
|
|
717
|
+
|
|
718
|
+
#### \u30D2\u30F3\u30C8
|
|
719
|
+
- \`tweet.fields\`, \`user.fields\`, \`expansions\` \u3067\u5FC5\u8981\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044
|
|
720
|
+
- \u63A2\u7D22\u6642\u306E \`max_results\` \u306F\u5C0F\u3055\u304F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u591A\u304F\u306E\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3067\u306F1\u56DE\u306E\u63CF\u753B\u306B\u6570\u767E\u4EF6\u306E\u6295\u7A3F\u306F\u4E0D\u8981\u3067\u3059
|
|
721
|
+
- \u500B\u5225\u6295\u7A3F\u3067\u306F\u306A\u304F\u4EF6\u6570\u30C8\u30EC\u30F3\u30C9\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F counts endpoint \u3092\u512A\u5148\u3057\u3066\u304F\u3060\u3055\u3044`
|
|
722
|
+
},
|
|
723
|
+
tools,
|
|
724
|
+
setup: (params, ctx, config) => runSetupFlow(xSetupFlow, params, ctx, config),
|
|
725
|
+
async checkConnection(params) {
|
|
726
|
+
const bearerToken = params[parameters.bearerToken.slug];
|
|
727
|
+
if (!bearerToken) {
|
|
728
|
+
return {
|
|
729
|
+
success: false,
|
|
730
|
+
error: `Missing required parameter: ${parameters.bearerToken.slug}`
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
const res = await fetch(
|
|
735
|
+
"https://api.x.com/2/users/by/username/xdevelopers",
|
|
736
|
+
{
|
|
737
|
+
method: "GET",
|
|
738
|
+
headers: {
|
|
739
|
+
Authorization: `Bearer ${bearerToken}`,
|
|
740
|
+
Accept: "application/json"
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
);
|
|
744
|
+
if (!res.ok) {
|
|
745
|
+
const errorText = await res.text().catch(() => res.statusText);
|
|
746
|
+
return {
|
|
747
|
+
success: false,
|
|
748
|
+
error: `X API failed: HTTP ${res.status} ${errorText}`
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return { success: true };
|
|
752
|
+
} catch (error) {
|
|
753
|
+
return {
|
|
754
|
+
success: false,
|
|
755
|
+
error: error instanceof Error ? error.message : String(error)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
// src/connectors/create-connector-sdk.ts
|
|
762
|
+
import { readFileSync } from "fs";
|
|
763
|
+
import path from "path";
|
|
764
|
+
|
|
765
|
+
// src/connector-client/env.ts
|
|
766
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
767
|
+
const envVarName = entry.envVars[key];
|
|
768
|
+
if (!envVarName) {
|
|
769
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
770
|
+
}
|
|
771
|
+
const value = process.env[envVarName];
|
|
772
|
+
if (!value) {
|
|
773
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
774
|
+
}
|
|
775
|
+
return value;
|
|
776
|
+
}
|
|
777
|
+
function resolveEnvVarOptional(entry, key) {
|
|
778
|
+
const envVarName = entry.envVars[key];
|
|
779
|
+
if (!envVarName) return void 0;
|
|
780
|
+
return process.env[envVarName] || void 0;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/connector-client/proxy-fetch.ts
|
|
784
|
+
import { getContext } from "hono/context-storage";
|
|
785
|
+
import { getCookie } from "hono/cookie";
|
|
786
|
+
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
787
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
788
|
+
function normalizeHeaders(input) {
|
|
789
|
+
const out = {};
|
|
790
|
+
if (!input) return out;
|
|
791
|
+
new Headers(input).forEach((value, key) => {
|
|
792
|
+
out[key] = value;
|
|
793
|
+
});
|
|
794
|
+
return out;
|
|
795
|
+
}
|
|
796
|
+
function extractInputUrl(input) {
|
|
797
|
+
if (typeof input === "string") return input;
|
|
798
|
+
if (input instanceof URL) return input.href;
|
|
799
|
+
return input.url;
|
|
800
|
+
}
|
|
801
|
+
function createSandboxProxyFetch(connectionId) {
|
|
802
|
+
return async (input, init) => {
|
|
803
|
+
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
804
|
+
const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
|
|
805
|
+
if (!token || !sandboxId) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
"Connection proxy is not configured. Please check your deployment settings."
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
const originalUrl = extractInputUrl(input);
|
|
811
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
812
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
813
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
814
|
+
return fetch(sessionUrl, {
|
|
815
|
+
method: "POST",
|
|
816
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
const originalMethod = init?.method ?? "GET";
|
|
820
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
821
|
+
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
822
|
+
return fetch(proxyUrl, {
|
|
823
|
+
method: "POST",
|
|
824
|
+
headers: {
|
|
825
|
+
"Content-Type": "application/json",
|
|
826
|
+
Authorization: `Bearer ${token}`
|
|
827
|
+
},
|
|
828
|
+
body: JSON.stringify({
|
|
829
|
+
url: originalUrl,
|
|
830
|
+
method: originalMethod,
|
|
831
|
+
headers: normalizeHeaders(init?.headers),
|
|
832
|
+
body: originalBody
|
|
833
|
+
})
|
|
834
|
+
});
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function createDeployedAppProxyFetch(connectionId) {
|
|
838
|
+
const projectId = process.env["SQUADBASE_PROJECT_ID"];
|
|
839
|
+
if (!projectId) {
|
|
840
|
+
throw new Error(
|
|
841
|
+
"Connection proxy is not configured. Please check your deployment settings."
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
845
|
+
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
846
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
847
|
+
return async (input, init) => {
|
|
848
|
+
const originalUrl = extractInputUrl(input);
|
|
849
|
+
const c = getContext();
|
|
850
|
+
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
851
|
+
if (!appSession) {
|
|
852
|
+
throw new Error(
|
|
853
|
+
"No authentication method available for connection proxy."
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
857
|
+
return fetch(sessionUrl, {
|
|
858
|
+
method: "POST",
|
|
859
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
const originalMethod = init?.method ?? "GET";
|
|
863
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
864
|
+
return fetch(proxyUrl, {
|
|
865
|
+
method: "POST",
|
|
866
|
+
headers: {
|
|
867
|
+
"Content-Type": "application/json",
|
|
868
|
+
Authorization: `Bearer ${appSession}`
|
|
869
|
+
},
|
|
870
|
+
body: JSON.stringify({
|
|
871
|
+
url: originalUrl,
|
|
872
|
+
method: originalMethod,
|
|
873
|
+
headers: normalizeHeaders(init?.headers),
|
|
874
|
+
body: originalBody
|
|
875
|
+
})
|
|
876
|
+
});
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function createProxyFetch(connectionId) {
|
|
880
|
+
if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
|
|
881
|
+
return createSandboxProxyFetch(connectionId);
|
|
882
|
+
}
|
|
883
|
+
return createDeployedAppProxyFetch(connectionId);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// src/connectors/create-connector-sdk.ts
|
|
887
|
+
function loadConnectionsSync() {
|
|
888
|
+
const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
889
|
+
try {
|
|
890
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
891
|
+
return JSON.parse(raw);
|
|
892
|
+
} catch {
|
|
893
|
+
return {};
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function createConnectorSdk(plugin, createClient2) {
|
|
897
|
+
return (connectionId) => {
|
|
898
|
+
const connections = loadConnectionsSync();
|
|
899
|
+
const entry = connections[connectionId];
|
|
900
|
+
if (!entry) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Connection "${connectionId}" not found in .squadbase/connections.json`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
if (entry.connector.slug !== plugin.slug) {
|
|
906
|
+
throw new Error(
|
|
907
|
+
`Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
const params = {};
|
|
911
|
+
for (const param of Object.values(plugin.parameters)) {
|
|
912
|
+
if (param.required) {
|
|
913
|
+
params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
|
|
914
|
+
} else {
|
|
915
|
+
const val = resolveEnvVarOptional(entry, param.slug);
|
|
916
|
+
if (val !== void 0) params[param.slug] = val;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return createClient2(params, createProxyFetch(connectionId));
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// src/connectors/entries/x.ts
|
|
924
|
+
var connection = createConnectorSdk(xConnector, createClient);
|
|
925
|
+
export {
|
|
926
|
+
connection
|
|
927
|
+
};
|