@squadbase/vite-server 0.1.3-dev.8 → 0.1.3

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.
Files changed (63) hide show
  1. package/dist/cli/index.js +14229 -29321
  2. package/dist/connectors/airtable-oauth.js +43 -6
  3. package/dist/connectors/airtable.js +43 -6
  4. package/dist/connectors/amplitude.js +43 -6
  5. package/dist/connectors/anthropic.js +43 -6
  6. package/dist/connectors/asana.js +43 -6
  7. package/dist/connectors/attio.js +43 -6
  8. package/dist/connectors/{google-ads-oauth.d.ts → backlog-api-key.d.ts} +1 -1
  9. package/dist/connectors/backlog-api-key.js +629 -0
  10. package/dist/connectors/customerio.js +43 -6
  11. package/dist/connectors/dbt.js +43 -6
  12. package/dist/connectors/{google-sheets-oauth.d.ts → gamma.d.ts} +1 -1
  13. package/dist/connectors/gamma.js +866 -0
  14. package/dist/connectors/gemini.js +43 -6
  15. package/dist/connectors/gmail-oauth.js +65 -8
  16. package/dist/connectors/gmail.js +104 -44
  17. package/dist/connectors/google-ads.d.ts +1 -1
  18. package/dist/connectors/google-ads.js +410 -332
  19. package/dist/connectors/google-analytics-oauth.js +61 -8
  20. package/dist/connectors/google-analytics.js +107 -292
  21. package/dist/connectors/google-calendar-oauth.js +61 -8
  22. package/dist/connectors/google-calendar.js +111 -58
  23. package/dist/connectors/{linkedin-ads-oauth.d.ts → google-docs.d.ts} +1 -1
  24. package/dist/connectors/google-docs.js +631 -0
  25. package/dist/connectors/google-drive.d.ts +5 -0
  26. package/dist/connectors/google-drive.js +875 -0
  27. package/dist/connectors/google-sheets.d.ts +1 -1
  28. package/dist/connectors/google-sheets.js +267 -285
  29. package/dist/connectors/google-slides.d.ts +5 -0
  30. package/dist/connectors/google-slides.js +663 -0
  31. package/dist/connectors/grafana.js +43 -6
  32. package/dist/connectors/hubspot-oauth.js +43 -6
  33. package/dist/connectors/hubspot.js +43 -6
  34. package/dist/connectors/intercom-oauth.js +43 -6
  35. package/dist/connectors/intercom.js +43 -6
  36. package/dist/connectors/jira-api-key.js +43 -6
  37. package/dist/connectors/kintone-api-token.js +256 -82
  38. package/dist/connectors/kintone.js +43 -6
  39. package/dist/connectors/linkedin-ads.js +188 -168
  40. package/dist/connectors/mailchimp-oauth.js +43 -6
  41. package/dist/connectors/mailchimp.js +43 -6
  42. package/dist/connectors/mixpanel.d.ts +5 -0
  43. package/dist/connectors/mixpanel.js +779 -0
  44. package/dist/connectors/notion-oauth.js +43 -6
  45. package/dist/connectors/notion.js +43 -6
  46. package/dist/connectors/openai.js +43 -6
  47. package/dist/connectors/sentry.d.ts +5 -0
  48. package/dist/connectors/sentry.js +761 -0
  49. package/dist/connectors/shopify-oauth.js +43 -6
  50. package/dist/connectors/shopify.js +43 -6
  51. package/dist/connectors/stripe-api-key.js +46 -7
  52. package/dist/connectors/stripe-oauth.js +43 -6
  53. package/dist/connectors/wix-store.js +43 -6
  54. package/dist/connectors/zendesk-oauth.js +43 -6
  55. package/dist/connectors/zendesk.js +43 -6
  56. package/dist/index.d.ts +1 -1
  57. package/dist/index.js +4419 -3855
  58. package/dist/main.js +5481 -4918
  59. package/dist/vite-plugin.js +4474 -3948
  60. package/package.json +30 -12
  61. package/dist/connectors/google-ads-oauth.js +0 -890
  62. package/dist/connectors/google-sheets-oauth.js +0 -718
  63. package/dist/connectors/linkedin-ads-oauth.js +0 -848
@@ -42,29 +42,8 @@ var ParameterDefinition = class {
42
42
  }
43
43
  };
44
44
 
45
- // ../connectors/src/connectors/google-ads/sdk/index.ts
46
- import * as crypto from "crypto";
47
-
48
45
  // ../connectors/src/connectors/google-ads/parameters.ts
49
46
  var parameters = {
50
- serviceAccountKeyJsonBase64: new ParameterDefinition({
51
- slug: "service-account-key-json-base64",
52
- name: "Google Cloud Service Account JSON",
53
- description: "The service account JSON key used to authenticate with Google Cloud Platform. Ensure that the service account has the necessary permissions to access Google Ads.",
54
- envVarBaseKey: "GOOGLE_ADS_SERVICE_ACCOUNT_JSON_BASE64",
55
- type: "base64EncodedJson",
56
- secret: true,
57
- required: true
58
- }),
59
- developerToken: new ParameterDefinition({
60
- slug: "developer-token",
61
- name: "Google Ads Developer Token",
62
- description: "The developer token for accessing the Google Ads API. Required for all API requests.",
63
- envVarBaseKey: "GOOGLE_ADS_DEVELOPER_TOKEN",
64
- type: "text",
65
- secret: true,
66
- required: true
67
- }),
68
47
  customerId: new ParameterDefinition({
69
48
  slug: "customer-id",
70
49
  name: "Google Ads Customer ID",
@@ -74,176 +53,88 @@ var parameters = {
74
53
  secret: false,
75
54
  required: false
76
55
  }),
77
- loginCustomerId: new ParameterDefinition({
78
- slug: "login-customer-id",
79
- name: "Login Customer ID (MCC)",
80
- description: "The manager account (MCC) customer ID used for login. Required when accessing client accounts through a manager account. If not set, the customer ID is used.",
81
- envVarBaseKey: "GOOGLE_ADS_LOGIN_CUSTOMER_ID",
56
+ developerToken: new ParameterDefinition({
57
+ slug: "developer-token",
58
+ name: "Google Ads Developer Token",
59
+ description: "The developer token for accessing the Google Ads API. Required for all API requests.",
60
+ envVarBaseKey: "GOOGLE_ADS_DEVELOPER_TOKEN",
82
61
  type: "text",
83
- secret: false,
84
- required: false
62
+ secret: true,
63
+ required: true
85
64
  })
86
65
  };
87
66
 
88
67
  // ../connectors/src/connectors/google-ads/sdk/index.ts
89
- var TOKEN_URL = "https://oauth2.googleapis.com/token";
90
68
  var BASE_URL = "https://googleads.googleapis.com/v18/";
91
- var SCOPE = "https://www.googleapis.com/auth/adwords";
92
- function base64url(input) {
93
- const buf = typeof input === "string" ? Buffer.from(input) : input;
94
- return buf.toString("base64url");
95
- }
96
- function buildJwt(clientEmail, privateKey, nowSec) {
97
- const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
98
- const payload = base64url(
99
- JSON.stringify({
100
- iss: clientEmail,
101
- scope: SCOPE,
102
- aud: TOKEN_URL,
103
- iat: nowSec,
104
- exp: nowSec + 3600
105
- })
106
- );
107
- const signingInput = `${header}.${payload}`;
108
- const sign = crypto.createSign("RSA-SHA256");
109
- sign.update(signingInput);
110
- sign.end();
111
- const signature = base64url(sign.sign(privateKey));
112
- return `${signingInput}.${signature}`;
113
- }
114
- function createClient(params) {
115
- const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
116
- const developerToken = params[parameters.developerToken.slug];
69
+ function createClient(params, fetchFn = fetch) {
117
70
  const rawCustomerId = params[parameters.customerId.slug];
118
71
  const defaultCustomerId = rawCustomerId?.replace(/-/g, "") ?? "";
119
- const rawLoginCustomerId = params[parameters.loginCustomerId.slug];
120
- const loginCustomerId = rawLoginCustomerId?.replace(/-/g, "") ?? "";
121
- if (!serviceAccountKeyJsonBase64 || !developerToken) {
122
- const required = [
123
- parameters.serviceAccountKeyJsonBase64.slug,
124
- parameters.developerToken.slug
125
- ];
126
- const missing = required.filter((s) => !params[s]);
127
- throw new Error(
128
- `google-ads: missing required parameters: ${missing.join(", ")}`
129
- );
130
- }
131
- let serviceAccountKey;
132
- try {
133
- const decoded = Buffer.from(
134
- serviceAccountKeyJsonBase64,
135
- "base64"
136
- ).toString("utf-8");
137
- serviceAccountKey = JSON.parse(decoded);
138
- } catch {
72
+ const developerToken = params[parameters.developerToken.slug];
73
+ if (!developerToken) {
139
74
  throw new Error(
140
- "google-ads: failed to decode service account key JSON from base64"
75
+ `google-ads: missing required parameter: ${parameters.developerToken.slug}`
141
76
  );
142
77
  }
143
- if (!serviceAccountKey.client_email || !serviceAccountKey.private_key) {
144
- throw new Error(
145
- "google-ads: service account key JSON must contain client_email and private_key"
146
- );
78
+ function resolveCustomerId(override) {
79
+ const id = override?.replace(/-/g, "") ?? defaultCustomerId;
80
+ if (!id) {
81
+ throw new Error(
82
+ "google-ads: customerId is required. Either configure a default or pass it explicitly."
83
+ );
84
+ }
85
+ return id;
147
86
  }
148
- let cachedToken = null;
149
- let tokenExpiresAt = 0;
150
- async function getAccessToken() {
151
- const nowSec = Math.floor(Date.now() / 1e3);
152
- if (cachedToken && nowSec < tokenExpiresAt - 60) {
153
- return cachedToken;
87
+ function request(path2, init) {
88
+ const resolvedPath = defaultCustomerId ? path2.replace(/\{customerId\}/g, defaultCustomerId) : path2;
89
+ const url = `${BASE_URL}${resolvedPath}`;
90
+ const headers = new Headers(init?.headers);
91
+ headers.set("developer-token", developerToken);
92
+ if (defaultCustomerId) {
93
+ headers.set("login-customer-id", defaultCustomerId);
154
94
  }
155
- const jwt = buildJwt(
156
- serviceAccountKey.client_email,
157
- serviceAccountKey.private_key,
158
- nowSec
159
- );
160
- const response = await fetch(TOKEN_URL, {
95
+ return fetchFn(url, { ...init, headers });
96
+ }
97
+ async function search(query, customerId) {
98
+ const cid = resolveCustomerId(customerId);
99
+ const url = `${BASE_URL}customers/${cid}/googleAds:searchStream`;
100
+ const headers = new Headers();
101
+ headers.set("Content-Type", "application/json");
102
+ headers.set("developer-token", developerToken);
103
+ headers.set("login-customer-id", cid);
104
+ const response = await fetchFn(url, {
161
105
  method: "POST",
162
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
163
- body: new URLSearchParams({
164
- grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
165
- assertion: jwt
166
- })
106
+ headers,
107
+ body: JSON.stringify({ query })
167
108
  });
168
109
  if (!response.ok) {
169
- const text = await response.text();
110
+ const body = await response.text();
170
111
  throw new Error(
171
- `google-ads: token exchange failed (${response.status}): ${text}`
112
+ `google-ads: search failed (${response.status}): ${body}`
172
113
  );
173
114
  }
174
115
  const data = await response.json();
175
- cachedToken = data.access_token;
176
- tokenExpiresAt = nowSec + data.expires_in;
177
- return cachedToken;
116
+ return data.flatMap((chunk) => chunk.results ?? []);
178
117
  }
179
- function resolveCustomerId(override) {
180
- const id = override?.replace(/-/g, "") ?? defaultCustomerId;
181
- if (!id) {
118
+ async function listAccessibleCustomers() {
119
+ const url = `${BASE_URL}customers:listAccessibleCustomers`;
120
+ const headers = new Headers();
121
+ headers.set("developer-token", developerToken);
122
+ const response = await fetchFn(url, { method: "GET", headers });
123
+ if (!response.ok) {
124
+ const body = await response.text();
182
125
  throw new Error(
183
- "google-ads: customerId is required. Either configure a default or pass it explicitly."
126
+ `google-ads: listAccessibleCustomers failed (${response.status}): ${body}`
184
127
  );
185
128
  }
186
- return id;
187
- }
188
- function getLoginCustomerId() {
189
- return loginCustomerId || defaultCustomerId;
129
+ const data = await response.json();
130
+ return (data.resourceNames ?? []).map(
131
+ (rn) => rn.replace(/^customers\//, "")
132
+ );
190
133
  }
191
134
  return {
192
- async request(path2, init) {
193
- const accessToken = await getAccessToken();
194
- const resolvedPath = defaultCustomerId ? path2.replace(/\{customerId\}/g, defaultCustomerId) : path2;
195
- const url = `${BASE_URL}${resolvedPath}`;
196
- const headers = new Headers(init?.headers);
197
- headers.set("Authorization", `Bearer ${accessToken}`);
198
- headers.set("developer-token", developerToken);
199
- const lid = getLoginCustomerId();
200
- if (lid) {
201
- headers.set("login-customer-id", lid);
202
- }
203
- return fetch(url, { ...init, headers });
204
- },
205
- async search(query, customerId) {
206
- const cid = resolveCustomerId(customerId);
207
- const accessToken = await getAccessToken();
208
- const url = `${BASE_URL}customers/${cid}/googleAds:searchStream`;
209
- const headers = new Headers();
210
- headers.set("Content-Type", "application/json");
211
- headers.set("Authorization", `Bearer ${accessToken}`);
212
- headers.set("developer-token", developerToken);
213
- const lid = loginCustomerId || cid;
214
- headers.set("login-customer-id", lid);
215
- const response = await fetch(url, {
216
- method: "POST",
217
- headers,
218
- body: JSON.stringify({ query })
219
- });
220
- if (!response.ok) {
221
- const body = await response.text();
222
- throw new Error(
223
- `google-ads: search failed (${response.status}): ${body}`
224
- );
225
- }
226
- const data = await response.json();
227
- return data.flatMap((chunk) => chunk.results ?? []);
228
- },
229
- async listAccessibleCustomers() {
230
- const accessToken = await getAccessToken();
231
- const url = `${BASE_URL}customers:listAccessibleCustomers`;
232
- const headers = new Headers();
233
- headers.set("Authorization", `Bearer ${accessToken}`);
234
- headers.set("developer-token", developerToken);
235
- const response = await fetch(url, { method: "GET", headers });
236
- if (!response.ok) {
237
- const body = await response.text();
238
- throw new Error(
239
- `google-ads: listAccessibleCustomers failed (${response.status}): ${body}`
240
- );
241
- }
242
- const data = await response.json();
243
- return (data.resourceNames ?? []).map(
244
- (rn) => rn.replace(/^customers\//, "")
245
- );
246
- }
135
+ request,
136
+ search,
137
+ listAccessibleCustomers
247
138
  };
248
139
  }
249
140
 
@@ -329,21 +220,58 @@ var ConnectorPlugin = class _ConnectorPlugin {
329
220
  * Filters connections by connectorKey internally.
330
221
  * Returns tools keyed as `${connectorKey}_${toolName}`.
331
222
  */
332
- createTools(connections, config) {
223
+ createTools(connections, config, opts) {
333
224
  const myConnections = connections.filter(
334
225
  (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
335
226
  );
336
227
  const result = {};
337
228
  for (const t of Object.values(this.tools)) {
338
- result[`${this.connectorKey}_${t.name}`] = t.createTool(
339
- myConnections,
340
- config
341
- );
229
+ const tool = t.createTool(myConnections, config);
230
+ const originalToModelOutput = tool.toModelOutput;
231
+ result[`${this.connectorKey}_${t.name}`] = {
232
+ ...tool,
233
+ toModelOutput: async (options) => {
234
+ if (!originalToModelOutput) {
235
+ return opts.truncateOutput(options.output);
236
+ }
237
+ const modelOutput = await originalToModelOutput(options);
238
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
239
+ return opts.truncateOutput(modelOutput.value);
240
+ }
241
+ return modelOutput;
242
+ }
243
+ };
342
244
  }
343
245
  return result;
344
246
  }
345
247
  static deriveKey(slug, authType) {
346
- return authType ? `${slug}-${authType}` : slug;
248
+ if (authType) return `${slug}-${authType}`;
249
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
250
+ // user-password
251
+ "postgresql": "user-password",
252
+ "mysql": "user-password",
253
+ "clickhouse": "user-password",
254
+ "kintone": "user-password",
255
+ "squadbase-db": "user-password",
256
+ // service-account
257
+ "snowflake": "service-account",
258
+ "bigquery": "service-account",
259
+ "google-analytics": "service-account",
260
+ "google-calendar": "service-account",
261
+ "aws-athena": "service-account",
262
+ "redshift": "service-account",
263
+ // api-key
264
+ "databricks": "api-key",
265
+ "dbt": "api-key",
266
+ "airtable": "api-key",
267
+ "openai": "api-key",
268
+ "gemini": "api-key",
269
+ "anthropic": "api-key",
270
+ "wix-store": "api-key"
271
+ };
272
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
273
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
274
+ return slug;
347
275
  }
348
276
  };
349
277
 
@@ -357,50 +285,68 @@ var AUTH_TYPES = {
357
285
  USER_PASSWORD: "user-password"
358
286
  };
359
287
 
360
- // ../connectors/src/connectors/google-ads/setup.ts
361
- var googleAdsOnboarding = new ConnectorOnboarding({
362
- dataOverviewInstructions: {
363
- en: `1. Call google-ads_request with POST customers/{customerId}/googleAds:searchStream to explore available campaign data using GAQL: SELECT campaign.id, campaign.name, campaign.status FROM campaign LIMIT 10
364
- 2. Explore ad group and keyword data as needed to understand the data structure`,
365
- ja: `1. google-ads_request \u3067 POST customers/{customerId}/googleAds:searchStream \u3092\u547C\u3073\u51FA\u3057\u3001GAQL\u3067\u30AD\u30E3\u30F3\u30DA\u30FC\u30F3\u30C7\u30FC\u30BF\u3092\u63A2\u7D22: SELECT campaign.id, campaign.name, campaign.status FROM campaign LIMIT 10
366
- 2. \u5FC5\u8981\u306B\u5FDC\u3058\u3066\u5E83\u544A\u30B0\u30EB\u30FC\u30D7\u3084\u30AD\u30FC\u30EF\u30FC\u30C9\u30C7\u30FC\u30BF\u3092\u63A2\u7D22\u3057\u3001\u30C7\u30FC\u30BF\u69CB\u9020\u3092\u628A\u63E1`
367
- }
368
- });
369
-
370
- // ../connectors/src/connectors/google-ads/tools/request.ts
288
+ // ../connectors/src/connectors/google-ads/tools/list-customers.ts
371
289
  import { z } from "zod";
372
290
  var BASE_URL2 = "https://googleads.googleapis.com/v18/";
373
291
  var REQUEST_TIMEOUT_MS = 6e4;
292
+ var cachedToken = null;
293
+ async function getProxyToken(config) {
294
+ if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
295
+ return cachedToken.token;
296
+ }
297
+ const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
298
+ const res = await fetch(url, {
299
+ method: "POST",
300
+ headers: {
301
+ "Content-Type": "application/json",
302
+ "x-api-key": config.appApiKey,
303
+ "project-id": config.projectId
304
+ },
305
+ body: JSON.stringify({
306
+ sandboxId: config.sandboxId,
307
+ issuedBy: "coding-agent"
308
+ })
309
+ });
310
+ if (!res.ok) {
311
+ const errorText = await res.text().catch(() => res.statusText);
312
+ throw new Error(
313
+ `Failed to get proxy token: HTTP ${res.status} ${errorText}`
314
+ );
315
+ }
316
+ const data = await res.json();
317
+ cachedToken = {
318
+ token: data.token,
319
+ expiresAt: new Date(data.expiresAt).getTime()
320
+ };
321
+ return data.token;
322
+ }
374
323
  var inputSchema = z.object({
375
324
  toolUseIntent: z.string().optional().describe(
376
325
  "Brief description of what you intend to accomplish with this tool call"
377
326
  ),
378
- connectionId: z.string().describe("ID of the Google Ads connection to use"),
379
- method: z.enum(["GET", "POST"]).describe("HTTP method"),
380
- path: z.string().describe(
381
- "API path appended to https://googleads.googleapis.com/v18/ (e.g., 'customers/{customerId}/googleAds:searchStream'). {customerId} is automatically replaced."
382
- ),
383
- body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
327
+ connectionId: z.string().describe("ID of the Google Ads OAuth connection to use")
384
328
  });
385
329
  var outputSchema = z.discriminatedUnion("success", [
386
330
  z.object({
387
331
  success: z.literal(true),
388
- status: z.number(),
389
- data: z.unknown()
332
+ customers: z.array(
333
+ z.object({
334
+ customerId: z.string(),
335
+ descriptiveName: z.string()
336
+ })
337
+ )
390
338
  }),
391
339
  z.object({
392
340
  success: z.literal(false),
393
341
  error: z.string()
394
342
  })
395
343
  ]);
396
- var requestTool = new ConnectorTool({
397
- name: "request",
398
- description: `Send authenticated requests to the Google Ads API v18.
399
- Authentication is handled automatically using a service account.
400
- {customerId} in the path is automatically replaced with the connection's customer ID (hyphens removed).`,
344
+ var listCustomersTool = new ConnectorTool({
345
+ name: "listCustomers",
346
+ description: "List Google Ads customer accounts accessible with the current OAuth credentials.",
401
347
  inputSchema,
402
348
  outputSchema,
403
- async execute({ connectionId, method, path: path2, body }, connections) {
349
+ async execute({ connectionId }, connections, config) {
404
350
  const connection2 = connections.find((c) => c.id === connectionId);
405
351
  if (!connection2) {
406
352
  return {
@@ -409,57 +355,82 @@ Authentication is handled automatically using a service account.
409
355
  };
410
356
  }
411
357
  console.log(
412
- `[connector-request] google-ads/${connection2.name}: ${method} ${path2}`
358
+ `[connector-request] google-ads/${connection2.name}: listCustomers`
413
359
  );
414
360
  try {
415
- const { GoogleAuth } = await import("google-auth-library");
416
- const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
417
361
  const developerToken = parameters.developerToken.getValue(connection2);
418
- const rawCustomerId = parameters.customerId.tryGetValue(connection2);
419
- const customerId = rawCustomerId?.replace(/-/g, "") ?? "";
420
- const rawLoginCustomerId = parameters.loginCustomerId.tryGetValue(connection2);
421
- const loginCustomerId = rawLoginCustomerId?.replace(/-/g, "") ?? "";
422
- const credentials = JSON.parse(
423
- Buffer.from(keyJsonBase64, "base64").toString("utf-8")
424
- );
425
- const auth = new GoogleAuth({
426
- credentials,
427
- scopes: ["https://www.googleapis.com/auth/adwords"]
428
- });
429
- const token = await auth.getAccessToken();
430
- if (!token) {
431
- return {
432
- success: false,
433
- error: "Failed to obtain access token"
434
- };
435
- }
436
- const resolvedPath = customerId ? path2.replace(/\{customerId\}/g, customerId) : path2;
437
- const url = `${BASE_URL2}${resolvedPath}`;
362
+ const token = await getProxyToken(config.oauthProxy);
363
+ const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
438
364
  const controller = new AbortController();
439
365
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
440
366
  try {
441
- const lid = loginCustomerId || customerId;
442
- const response = await fetch(url, {
443
- method,
367
+ const response = await fetch(proxyUrl, {
368
+ method: "POST",
444
369
  headers: {
445
- Authorization: `Bearer ${token}`,
446
370
  "Content-Type": "application/json",
447
- "developer-token": developerToken,
448
- ...lid ? { "login-customer-id": lid } : {}
371
+ Authorization: `Bearer ${token}`
449
372
  },
450
- body: method === "POST" && body ? JSON.stringify(body) : void 0,
373
+ body: JSON.stringify({
374
+ url: `${BASE_URL2}customers:listAccessibleCustomers`,
375
+ method: "GET",
376
+ headers: {
377
+ "developer-token": developerToken
378
+ }
379
+ }),
451
380
  signal: controller.signal
452
381
  });
453
382
  const data = await response.json();
454
383
  if (!response.ok) {
455
- const dataObj = data;
456
- const errorObj = dataObj?.error;
457
- return {
458
- success: false,
459
- error: errorObj?.message ?? `HTTP ${response.status} ${response.statusText}`
460
- };
384
+ const errorMessage = typeof data?.error === "string" ? data.error : typeof data?.message === "string" ? data.message : `HTTP ${response.status} ${response.statusText}`;
385
+ return { success: false, error: errorMessage };
461
386
  }
462
- return { success: true, status: response.status, data };
387
+ const customerIds = (data.resourceNames ?? []).map(
388
+ (rn) => rn.replace(/^customers\//, "")
389
+ );
390
+ const customers = [];
391
+ for (const cid of customerIds) {
392
+ try {
393
+ const detailResponse = await fetch(proxyUrl, {
394
+ method: "POST",
395
+ headers: {
396
+ "Content-Type": "application/json",
397
+ Authorization: `Bearer ${token}`
398
+ },
399
+ body: JSON.stringify({
400
+ url: `${BASE_URL2}customers/${cid}/googleAds:searchStream`,
401
+ method: "POST",
402
+ headers: {
403
+ "Content-Type": "application/json",
404
+ "developer-token": developerToken,
405
+ "login-customer-id": cid
406
+ },
407
+ body: JSON.stringify({
408
+ query: "SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1"
409
+ })
410
+ }),
411
+ signal: controller.signal
412
+ });
413
+ if (detailResponse.ok) {
414
+ const detailData = await detailResponse.json();
415
+ const customer = detailData?.[0]?.results?.[0]?.customer;
416
+ customers.push({
417
+ customerId: cid,
418
+ descriptiveName: customer?.descriptiveName ?? cid
419
+ });
420
+ } else {
421
+ customers.push({
422
+ customerId: cid,
423
+ descriptiveName: cid
424
+ });
425
+ }
426
+ } catch {
427
+ customers.push({
428
+ customerId: cid,
429
+ descriptiveName: cid
430
+ });
431
+ }
432
+ }
433
+ return { success: true, customers };
463
434
  } finally {
464
435
  clearTimeout(timeout);
465
436
  }
@@ -470,37 +441,122 @@ Authentication is handled automatically using a service account.
470
441
  }
471
442
  });
472
443
 
473
- // ../connectors/src/connectors/google-ads/tools/list-customers.ts
444
+ // ../connectors/src/connectors/google-ads/setup.ts
445
+ var listCustomersToolName = `google-ads-oauth_${listCustomersTool.name}`;
446
+ var googleAdsOnboarding = new ConnectorOnboarding({
447
+ connectionSetupInstructions: {
448
+ ja: `\u4EE5\u4E0B\u306E\u624B\u9806\u3067Google Ads (OAuth) \u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
449
+
450
+ 1. \u30E6\u30FC\u30B6\u30FC\u306B\u300CGoogle Ads API \u306E Developer Token \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08Google Ads \u7BA1\u7406\u753B\u9762 > \u30C4\u30FC\u30EB\u3068\u8A2D\u5B9A > API \u30BB\u30F3\u30BF\u30FC\u3067\u53D6\u5F97\u3067\u304D\u307E\u3059\uFF09\u300D\u3068\u4F1D\u3048\u308B
451
+ 2. \`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3059:
452
+ - \`parameterSlug\`: \`"developer-token"\`
453
+ - \`value\`: \u30E6\u30FC\u30B6\u30FC\u304C\u63D0\u4F9B\u3057\u305F Developer Token
454
+ 3. \`${listCustomersToolName}\` \u3092\u547C\u3073\u51FA\u3057\u3066\u3001OAuth\u3067\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306AGoogle Ads\u30AB\u30B9\u30BF\u30DE\u30FC\u30A2\u30AB\u30A6\u30F3\u30C8\u4E00\u89A7\u3092\u53D6\u5F97\u3059\u308B
455
+ 4. \`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3059:
456
+ - \`parameterSlug\`: \`"customer-id"\`
457
+ - \`options\`: \u30AB\u30B9\u30BF\u30DE\u30FC\u4E00\u89A7\u3002\u5404 option \u306E \`label\` \u306F \`\u30A2\u30AB\u30A6\u30F3\u30C8\u540D (id: \u30AB\u30B9\u30BF\u30DE\u30FCID)\` \u306E\u5F62\u5F0F\u3001\`value\` \u306F\u30AB\u30B9\u30BF\u30DE\u30FCID
458
+ 5. \u30E6\u30FC\u30B6\u30FC\u304C\u9078\u629E\u3057\u305F\u30AB\u30B9\u30BF\u30DE\u30FC\u306E \`label\` \u304C\u30E1\u30C3\u30BB\u30FC\u30B8\u3068\u3057\u3066\u5C4A\u304F\u306E\u3067\u3001\u6B21\u306E\u30B9\u30C6\u30C3\u30D7\u306B\u9032\u3080
459
+ 6. \`updateConnectionContext\` \u3092\u547C\u3073\u51FA\u3059:
460
+ - \`customer\`: \u9078\u629E\u3055\u308C\u305F\u30AB\u30B9\u30BF\u30DE\u30FC\u306E\u8868\u793A\u540D
461
+ - \`customerId\`: \u9078\u629E\u3055\u308C\u305F\u30AB\u30B9\u30BF\u30DE\u30FCID
462
+ - \`note\`: \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5185\u5BB9\u306E\u7C21\u5358\u306A\u8AAC\u660E
463
+
464
+ #### \u5236\u7D04
465
+ - **\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u4E2D\u306B\u30EC\u30DD\u30FC\u30C8\u30C7\u30FC\u30BF\u3092\u53D6\u5F97\u3057\u306A\u3044\u3053\u3068**\u3002\u5B9F\u884C\u3057\u3066\u3088\u3044\u306E\u306F\u4E0A\u8A18\u624B\u9806\u3067\u6307\u5B9A\u3055\u308C\u305F\u30E1\u30BF\u30C7\u30FC\u30BF\u53D6\u5F97\u306E\u307F
466
+ - \u30C4\u30FC\u30EB\u9593\u306F1\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`,
467
+ en: `Follow these steps to set up the Google Ads (OAuth) connection.
468
+
469
+ 1. Ask the user to provide their Google Ads API Developer Token (available in Google Ads UI > Tools & Settings > API Center)
470
+ 2. Call \`updateConnectionParameters\`:
471
+ - \`parameterSlug\`: \`"developer-token"\`
472
+ - \`value\`: The Developer Token provided by the user
473
+ 3. Call \`${listCustomersToolName}\` to get the list of Google Ads customer accounts accessible with the OAuth credentials
474
+ 4. Call \`updateConnectionParameters\`:
475
+ - \`parameterSlug\`: \`"customer-id"\`
476
+ - \`options\`: The customer list. Each option's \`label\` should be \`Account Name (id: customerId)\`, \`value\` should be the customer ID
477
+ 5. The \`label\` of the user's selected customer will arrive as a message. Proceed to the next step
478
+ 6. Call \`updateConnectionContext\`:
479
+ - \`customer\`: The selected customer's display name
480
+ - \`customerId\`: The selected customer ID
481
+ - \`note\`: Brief description of the setup
482
+
483
+ #### Constraints
484
+ - **Do NOT fetch report data during setup**. Only the metadata requests specified in the steps above are allowed
485
+ - Write only 1 sentence between tool calls, then immediately call the next tool. Skip unnecessary explanations and proceed efficiently`
486
+ },
487
+ dataOverviewInstructions: {
488
+ en: `1. Call google-ads-oauth_request with POST customers/{customerId}/googleAds:searchStream to explore available campaign data using GAQL: SELECT campaign.id, campaign.name, campaign.status FROM campaign LIMIT 10
489
+ 2. Explore ad group and keyword data as needed to understand the data structure`,
490
+ ja: `1. google-ads-oauth_request \u3067 POST customers/{customerId}/googleAds:searchStream \u3092\u547C\u3073\u51FA\u3057\u3001GAQL\u3067\u30AD\u30E3\u30F3\u30DA\u30FC\u30F3\u30C7\u30FC\u30BF\u3092\u63A2\u7D22: SELECT campaign.id, campaign.name, campaign.status FROM campaign LIMIT 10
491
+ 2. \u5FC5\u8981\u306B\u5FDC\u3058\u3066\u5E83\u544A\u30B0\u30EB\u30FC\u30D7\u3084\u30AD\u30FC\u30EF\u30FC\u30C9\u30C7\u30FC\u30BF\u3092\u63A2\u7D22\u3057\u3001\u30C7\u30FC\u30BF\u69CB\u9020\u3092\u628A\u63E1`
492
+ }
493
+ });
494
+
495
+ // ../connectors/src/connectors/google-ads/tools/request.ts
474
496
  import { z as z2 } from "zod";
475
497
  var BASE_URL3 = "https://googleads.googleapis.com/v18/";
476
498
  var REQUEST_TIMEOUT_MS2 = 6e4;
499
+ var cachedToken2 = null;
500
+ async function getProxyToken2(config) {
501
+ if (cachedToken2 && cachedToken2.expiresAt > Date.now() + 6e4) {
502
+ return cachedToken2.token;
503
+ }
504
+ const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
505
+ const res = await fetch(url, {
506
+ method: "POST",
507
+ headers: {
508
+ "Content-Type": "application/json",
509
+ "x-api-key": config.appApiKey,
510
+ "project-id": config.projectId
511
+ },
512
+ body: JSON.stringify({
513
+ sandboxId: config.sandboxId,
514
+ issuedBy: "coding-agent"
515
+ })
516
+ });
517
+ if (!res.ok) {
518
+ const errorText = await res.text().catch(() => res.statusText);
519
+ throw new Error(
520
+ `Failed to get proxy token: HTTP ${res.status} ${errorText}`
521
+ );
522
+ }
523
+ const data = await res.json();
524
+ cachedToken2 = {
525
+ token: data.token,
526
+ expiresAt: new Date(data.expiresAt).getTime()
527
+ };
528
+ return data.token;
529
+ }
477
530
  var inputSchema2 = z2.object({
478
531
  toolUseIntent: z2.string().optional().describe(
479
532
  "Brief description of what you intend to accomplish with this tool call"
480
533
  ),
481
- connectionId: z2.string().describe("ID of the Google Ads connection to use")
534
+ connectionId: z2.string().describe("ID of the Google Ads OAuth connection to use"),
535
+ method: z2.enum(["GET", "POST"]).describe("HTTP method"),
536
+ path: z2.string().describe(
537
+ "API path appended to https://googleads.googleapis.com/v18/ (e.g., 'customers/{customerId}/googleAds:searchStream'). {customerId} is automatically replaced."
538
+ ),
539
+ body: z2.record(z2.string(), z2.unknown()).optional().describe("POST request body (JSON)")
482
540
  });
483
541
  var outputSchema2 = z2.discriminatedUnion("success", [
484
542
  z2.object({
485
543
  success: z2.literal(true),
486
- customers: z2.array(
487
- z2.object({
488
- customerId: z2.string(),
489
- descriptiveName: z2.string()
490
- })
491
- )
544
+ status: z2.number(),
545
+ data: z2.unknown()
492
546
  }),
493
547
  z2.object({
494
548
  success: z2.literal(false),
495
549
  error: z2.string()
496
550
  })
497
551
  ]);
498
- var listCustomersTool = new ConnectorTool({
499
- name: "listCustomers",
500
- description: "List Google Ads customer accounts accessible with the service account credentials.",
552
+ var requestTool = new ConnectorTool({
553
+ name: "request",
554
+ description: `Send authenticated requests to the Google Ads API v18.
555
+ Authentication is handled automatically via OAuth proxy.
556
+ {customerId} in the path is automatically replaced with the connection's customer ID (hyphens removed).`,
501
557
  inputSchema: inputSchema2,
502
558
  outputSchema: outputSchema2,
503
- async execute({ connectionId }, connections) {
559
+ async execute({ connectionId, method, path: path2, body }, connections, config) {
504
560
  const connection2 = connections.find((c) => c.id === connectionId);
505
561
  if (!connection2) {
506
562
  return {
@@ -509,90 +565,44 @@ var listCustomersTool = new ConnectorTool({
509
565
  };
510
566
  }
511
567
  console.log(
512
- `[connector-request] google-ads/${connection2.name}: listCustomers`
568
+ `[connector-request] google-ads/${connection2.name}: ${method} ${path2}`
513
569
  );
514
570
  try {
515
- const { GoogleAuth } = await import("google-auth-library");
516
- const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
517
- const developerToken = parameters.developerToken.getValue(connection2);
518
- const credentials = JSON.parse(
519
- Buffer.from(keyJsonBase64, "base64").toString("utf-8")
520
- );
521
- const auth = new GoogleAuth({
522
- credentials,
523
- scopes: ["https://www.googleapis.com/auth/adwords"]
524
- });
525
- const token = await auth.getAccessToken();
526
- if (!token) {
527
- return {
528
- success: false,
529
- error: "Failed to obtain access token"
530
- };
531
- }
571
+ const rawCustomerId = parameters.customerId.tryGetValue(connection2);
572
+ const customerId = rawCustomerId?.replace(/-/g, "") ?? "";
573
+ const resolvedPath = customerId ? path2.replace(/\{customerId\}/g, customerId) : path2;
574
+ const url = `${BASE_URL3}${resolvedPath}`;
575
+ const token = await getProxyToken2(config.oauthProxy);
576
+ const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
532
577
  const controller = new AbortController();
533
578
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
534
579
  try {
535
- const listResponse = await fetch(
536
- `${BASE_URL3}customers:listAccessibleCustomers`,
537
- {
538
- method: "GET",
580
+ const developerToken = parameters.developerToken.getValue(connection2);
581
+ const response = await fetch(proxyUrl, {
582
+ method: "POST",
583
+ headers: {
584
+ "Content-Type": "application/json",
585
+ Authorization: `Bearer ${token}`
586
+ },
587
+ body: JSON.stringify({
588
+ url,
589
+ method,
539
590
  headers: {
540
- Authorization: `Bearer ${token}`,
541
- "developer-token": developerToken
591
+ "Content-Type": "application/json",
592
+ "developer-token": developerToken,
593
+ ...customerId ? { "login-customer-id": customerId } : {}
542
594
  },
543
- signal: controller.signal
544
- }
545
- );
546
- const listData = await listResponse.json();
547
- if (!listResponse.ok) {
548
- return {
549
- success: false,
550
- error: listData.error?.message ?? `HTTP ${listResponse.status} ${listResponse.statusText}`
551
- };
552
- }
553
- const customerIds = (listData.resourceNames ?? []).map(
554
- (rn) => rn.replace(/^customers\//, "")
555
- );
556
- const customers = [];
557
- for (const cid of customerIds) {
558
- try {
559
- const detailResponse = await fetch(
560
- `${BASE_URL3}customers/${cid}/googleAds:searchStream`,
561
- {
562
- method: "POST",
563
- headers: {
564
- Authorization: `Bearer ${token}`,
565
- "Content-Type": "application/json",
566
- "developer-token": developerToken,
567
- "login-customer-id": cid
568
- },
569
- body: JSON.stringify({
570
- query: "SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1"
571
- }),
572
- signal: controller.signal
573
- }
574
- );
575
- if (detailResponse.ok) {
576
- const detailData = await detailResponse.json();
577
- const customer = detailData?.[0]?.results?.[0]?.customer;
578
- customers.push({
579
- customerId: cid,
580
- descriptiveName: customer?.descriptiveName ?? cid
581
- });
582
- } else {
583
- customers.push({
584
- customerId: cid,
585
- descriptiveName: cid
586
- });
587
- }
588
- } catch {
589
- customers.push({
590
- customerId: cid,
591
- descriptiveName: cid
592
- });
593
- }
595
+ ...method === "POST" && body ? { body: JSON.stringify(body) } : {}
596
+ }),
597
+ signal: controller.signal
598
+ });
599
+ const data = await response.json();
600
+ if (!response.ok) {
601
+ const dataObj = data;
602
+ const errorMessage = typeof dataObj?.error === "string" ? dataObj.error : typeof dataObj?.message === "string" ? dataObj.message : `HTTP ${response.status} ${response.statusText}`;
603
+ return { success: false, error: errorMessage };
594
604
  }
595
- return { success: true, customers };
605
+ return { success: true, status: response.status, data };
596
606
  } finally {
597
607
  clearTimeout(timeout);
598
608
  }
@@ -610,18 +620,28 @@ var tools = {
610
620
  };
611
621
  var googleAdsConnector = new ConnectorPlugin({
612
622
  slug: "google-ads",
613
- authType: AUTH_TYPES.SERVICE_ACCOUNT,
623
+ authType: AUTH_TYPES.OAUTH,
614
624
  name: "Google Ads",
615
- description: "Connect to Google Ads for advertising campaign data and reporting using a service account.",
625
+ description: "Connect to Google Ads for advertising campaign data and reporting using OAuth.",
616
626
  iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/1NGvmgvCxX7Tn11EST2N3N/a745fe7c63d360ed40a27ddaad3af168/google-ads.svg",
617
627
  parameters,
618
628
  releaseFlag: { dev1: true, dev2: false, prod: false },
619
629
  onboarding: googleAdsOnboarding,
630
+ proxyPolicy: {
631
+ allowlist: [
632
+ {
633
+ host: "googleads.googleapis.com",
634
+ methods: ["GET", "POST"]
635
+ }
636
+ ]
637
+ },
620
638
  systemPrompt: {
621
- en: `### Tools
639
+ en: `### Tools (setup-time only)
640
+
641
+ - \`google-ads-oauth_request\`: Send authenticated requests to the Google Ads API during setup / data overview. Use it for GAQL queries via searchStream. The {customerId} placeholder in paths is automatically replaced (hyphens removed). Authentication and developer token are configured automatically.
642
+ - \`google-ads-oauth_listCustomers\`: List accessible Google Ads customer accounts. Use this during setup to discover available accounts.
622
643
 
623
- - \`google-ads_request\`: Send authenticated requests to the Google Ads API. Use it for GAQL queries via searchStream. The {customerId} placeholder in paths is automatically replaced (hyphens removed). Authentication via service account and developer token are configured automatically.
624
- - \`google-ads_listCustomers\`: List accessible Google Ads customer accounts. Use this during setup to discover available accounts.
644
+ > **Important**: These tools are only available at setup time. Inside server-logic handlers, use the SDK (\`connection(id).search\`, etc.) \u2014 the SDK's fetch is already wired through the OAuth proxy. **Do NOT** hand-roll HTTP calls to \`_sqcore/connections/*/request\` from a handler.
625
645
 
626
646
  ### Google Ads API Reference
627
647
 
@@ -653,7 +673,14 @@ segments.date, segments.device, segments.ad_network_type
653
673
 
654
674
  ### Business Logic
655
675
 
656
- The business logic type for this connector is "typescript". Write handler code using the connector SDK shown below. Do NOT access credentials directly from environment variables.
676
+ The business logic type for this connector is "typescript". Write handler code using the connector SDK shown below. Do NOT access credentials directly from environment variables and do NOT read \`INTERNAL_SQUADBASE_*\` env vars \u2014 the SDK takes care of OAuth.
677
+
678
+ SDK surface (client created via \`connection(connectionId)\`):
679
+ - \`client.request(path, init?)\` \u2014 low-level authenticated fetch. \`{customerId}\` placeholders in the path are auto-replaced with the configured customer id.
680
+ - \`client.search(query, customerId?)\` \u2014 execute a GAQL query via searchStream and get rows back.
681
+ - \`client.listAccessibleCustomers()\` \u2014 return the list of customer IDs the OAuth user can access.
682
+
683
+ If a handler test fails with \`Connection proxy is not configured\`, retry \u2014 the sandbox is still initializing. Do NOT abandon the SDK and construct OAuth proxy URLs manually.
657
684
 
658
685
  #### Example
659
686
 
@@ -671,10 +698,12 @@ rows.forEach(row => console.log(row));
671
698
  // List accessible customer accounts
672
699
  const customerIds = await ads.listAccessibleCustomers();
673
700
  \`\`\``,
674
- ja: `### \u30C4\u30FC\u30EB
701
+ ja: `### \u30C4\u30FC\u30EB\uFF08\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u6642\u306E\u307F\uFF09
675
702
 
676
- - \`google-ads_request\`: Google Ads API\u3078\u8A8D\u8A3C\u6E08\u307F\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002searchStream\u3092\u4F7F\u3063\u305FGAQL\u30AF\u30A8\u30EA\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306E{customerId}\u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u306F\u81EA\u52D5\u7684\u306B\u7F6E\u63DB\u3055\u308C\u307E\u3059\uFF08\u30CF\u30A4\u30D5\u30F3\u306F\u9664\u53BB\uFF09\u3002\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u3088\u308B\u8A8D\u8A3C\u3068\u30C7\u30D9\u30ED\u30C3\u30D1\u30FC\u30C8\u30FC\u30AF\u30F3\u306F\u81EA\u52D5\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002
677
- - \`google-ads_listCustomers\`: \u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306AGoogle Ads\u9867\u5BA2\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u4E00\u89A7\u3092\u53D6\u5F97\u3057\u307E\u3059\u3002\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u6642\u306B\u5229\u7528\u53EF\u80FD\u306A\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u78BA\u8A8D\u3059\u308B\u305F\u3081\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002
703
+ - \`google-ads-oauth_request\`: \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3084\u30C7\u30FC\u30BF\u6982\u8981\u628A\u63E1\u6642\u306B Google Ads API \u3078\u8A8D\u8A3C\u6E08\u307F\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002searchStream \u3092\u4F7F\u3063\u305F GAQL \u30AF\u30A8\u30EA\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306E {customerId} \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u306F\u81EA\u52D5\u7684\u306B\u7F6E\u63DB\u3055\u308C\u307E\u3059\uFF08\u30CF\u30A4\u30D5\u30F3\u306F\u9664\u53BB\uFF09\u3002\u8A8D\u8A3C\u3068\u30C7\u30D9\u30ED\u30C3\u30D1\u30FC\u30C8\u30FC\u30AF\u30F3\u306F\u81EA\u52D5\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002
704
+ - \`google-ads-oauth_listCustomers\`: \u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A Google Ads \u9867\u5BA2\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u4E00\u89A7\u3092\u53D6\u5F97\u3057\u307E\u3059\u3002\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u6642\u306B\u5229\u7528\u53EF\u80FD\u306A\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u78BA\u8A8D\u3059\u308B\u305F\u3081\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002
705
+
706
+ > **\u91CD\u8981**: \u3053\u308C\u3089\u306E\u30C4\u30FC\u30EB\u306F\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u6642\u306E\u307F\u5229\u7528\u53EF\u80FD\u3067\u3059\u3002\u30B5\u30FC\u30D0\u30FC\u30ED\u30B8\u30C3\u30AF\u306E\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u5FC5\u305A SDK\uFF08\`connection(id).search\` \u306A\u3069\uFF09\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002SDK \u306E fetch \u306F OAuth \u30D7\u30ED\u30AD\u30B7\u7D4C\u7531\u3067\u65E2\u306B\u914D\u7DDA\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u304B\u3089 \`_sqcore/connections/*/request\` \u3092\u624B\u66F8\u304D\u3067\u547C\u3073\u51FA\u3055\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
678
707
 
679
708
  ### Google Ads API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
680
709
 
@@ -706,7 +735,14 @@ segments.date, segments.device, segments.ad_network_type
706
735
 
707
736
  ### Business Logic
708
737
 
709
- \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u4EE5\u4E0B\u306B\u793A\u3059\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u30CF\u30F3\u30C9\u30E9\u30B3\u30FC\u30C9\u3092\u8A18\u8FF0\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u76F4\u63A5\u8A8D\u8A3C\u60C5\u5831\u306B\u30A2\u30AF\u30BB\u30B9\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
738
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u4EE5\u4E0B\u306B\u793A\u3059\u30B3\u30CD\u30AF\u30BF SDK \u3092\u4F7F\u7528\u3057\u3066\u30CF\u30F3\u30C9\u30E9\u30B3\u30FC\u30C9\u3092\u8A18\u8FF0\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u76F4\u63A5\u8A8D\u8A3C\u60C5\u5831\u306B\u30A2\u30AF\u30BB\u30B9\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\`INTERNAL_SQUADBASE_*\` \u306E\u74B0\u5883\u5909\u6570\u3092\u4F7F\u3063\u3066\u624B\u52D5\u3067 OAuth \u30D7\u30ED\u30AD\u30B7\u3092\u53E9\u304F\u3053\u3068\u3082\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044 \u2014 SDK \u304C OAuth \u3092\u51E6\u7406\u3057\u307E\u3059\u3002
739
+
740
+ SDK\uFF08\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\uFF09:
741
+ - \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304D fetch\u3002\u30D1\u30B9\u5185\u306E \`{customerId}\` \u306F\u81EA\u52D5\u7684\u306B\u8A2D\u5B9A\u6E08\u307F\u30AB\u30B9\u30BF\u30DE\u30FC ID \u306B\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002
742
+ - \`client.search(query, customerId?)\` \u2014 GAQL \u30AF\u30A8\u30EA\u3092 searchStream \u3067\u5B9F\u884C\u3057\u3001\u884C\u3092\u53D6\u5F97\u3002
743
+ - \`client.listAccessibleCustomers()\` \u2014 OAuth \u30E6\u30FC\u30B6\u30FC\u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30AB\u30B9\u30BF\u30DE\u30FC ID \u4E00\u89A7\u3092\u53D6\u5F97\u3002
744
+
745
+ \u30CF\u30F3\u30C9\u30E9\u306E\u30C6\u30B9\u30C8\u304C \`Connection proxy is not configured\` \u3067\u5931\u6557\u3059\u308B\u5834\u5408\u306F\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u901A\u5E38\u306F\u30B5\u30F3\u30C9\u30DC\u30C3\u30AF\u30B9\u306E\u521D\u671F\u5316\u4E2D\u306B\u8D77\u304D\u307E\u3059\u3002SDK \u3092\u8AE6\u3081\u3066 OAuth \u30D7\u30ED\u30AD\u30B7\u306E URL \u3092\u81EA\u5206\u3067\u7D44\u307F\u7ACB\u3066\u308B\u3053\u3068\u306F **\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044**\u3002
710
746
 
711
747
  #### Example
712
748
 
@@ -725,7 +761,49 @@ rows.forEach(row => console.log(row));
725
761
  const customerIds = await ads.listAccessibleCustomers();
726
762
  \`\`\``
727
763
  },
728
- tools
764
+ tools,
765
+ async checkConnection(params, config) {
766
+ const { proxyFetch } = config;
767
+ const rawCustomerId = params[parameters.customerId.slug];
768
+ const customerId = rawCustomerId?.replace(/-/g, "");
769
+ if (!customerId) {
770
+ return { success: true };
771
+ }
772
+ const developerToken = params[parameters.developerToken.slug];
773
+ if (!developerToken) {
774
+ return {
775
+ success: false,
776
+ error: "Developer token is required"
777
+ };
778
+ }
779
+ const url = `https://googleads.googleapis.com/v18/customers/${customerId}/googleAds:searchStream`;
780
+ try {
781
+ const res = await proxyFetch(url, {
782
+ method: "POST",
783
+ headers: {
784
+ "Content-Type": "application/json",
785
+ "developer-token": developerToken,
786
+ "login-customer-id": customerId
787
+ },
788
+ body: JSON.stringify({
789
+ query: "SELECT customer.id FROM customer LIMIT 1"
790
+ })
791
+ });
792
+ if (!res.ok) {
793
+ const errorText = await res.text().catch(() => res.statusText);
794
+ return {
795
+ success: false,
796
+ error: `Google Ads API failed: HTTP ${res.status} ${errorText}`
797
+ };
798
+ }
799
+ return { success: true };
800
+ } catch (error) {
801
+ return {
802
+ success: false,
803
+ error: error instanceof Error ? error.message : String(error)
804
+ };
805
+ }
806
+ }
729
807
  });
730
808
 
731
809
  // src/connectors/create-connector-sdk.ts