@squadbase/vite-server 0.1.3-dev.13 → 0.1.3-dev.15

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.
@@ -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
 
@@ -357,50 +248,68 @@ var AUTH_TYPES = {
357
248
  USER_PASSWORD: "user-password"
358
249
  };
359
250
 
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
251
+ // ../connectors/src/connectors/google-ads/tools/list-customers.ts
371
252
  import { z } from "zod";
372
253
  var BASE_URL2 = "https://googleads.googleapis.com/v18/";
373
254
  var REQUEST_TIMEOUT_MS = 6e4;
255
+ var cachedToken = null;
256
+ async function getProxyToken(config) {
257
+ if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
258
+ return cachedToken.token;
259
+ }
260
+ const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
261
+ const res = await fetch(url, {
262
+ method: "POST",
263
+ headers: {
264
+ "Content-Type": "application/json",
265
+ "x-api-key": config.appApiKey,
266
+ "project-id": config.projectId
267
+ },
268
+ body: JSON.stringify({
269
+ sandboxId: config.sandboxId,
270
+ issuedBy: "coding-agent"
271
+ })
272
+ });
273
+ if (!res.ok) {
274
+ const errorText = await res.text().catch(() => res.statusText);
275
+ throw new Error(
276
+ `Failed to get proxy token: HTTP ${res.status} ${errorText}`
277
+ );
278
+ }
279
+ const data = await res.json();
280
+ cachedToken = {
281
+ token: data.token,
282
+ expiresAt: new Date(data.expiresAt).getTime()
283
+ };
284
+ return data.token;
285
+ }
374
286
  var inputSchema = z.object({
375
287
  toolUseIntent: z.string().optional().describe(
376
288
  "Brief description of what you intend to accomplish with this tool call"
377
289
  ),
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)")
290
+ connectionId: z.string().describe("ID of the Google Ads OAuth connection to use")
384
291
  });
385
292
  var outputSchema = z.discriminatedUnion("success", [
386
293
  z.object({
387
294
  success: z.literal(true),
388
- status: z.number(),
389
- data: z.unknown()
295
+ customers: z.array(
296
+ z.object({
297
+ customerId: z.string(),
298
+ descriptiveName: z.string()
299
+ })
300
+ )
390
301
  }),
391
302
  z.object({
392
303
  success: z.literal(false),
393
304
  error: z.string()
394
305
  })
395
306
  ]);
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).`,
307
+ var listCustomersTool = new ConnectorTool({
308
+ name: "listCustomers",
309
+ description: "List Google Ads customer accounts accessible with the current OAuth credentials.",
401
310
  inputSchema,
402
311
  outputSchema,
403
- async execute({ connectionId, method, path: path2, body }, connections) {
312
+ async execute({ connectionId }, connections, config) {
404
313
  const connection2 = connections.find((c) => c.id === connectionId);
405
314
  if (!connection2) {
406
315
  return {
@@ -409,57 +318,82 @@ Authentication is handled automatically using a service account.
409
318
  };
410
319
  }
411
320
  console.log(
412
- `[connector-request] google-ads/${connection2.name}: ${method} ${path2}`
321
+ `[connector-request] google-ads/${connection2.name}: listCustomers`
413
322
  );
414
323
  try {
415
- const { GoogleAuth } = await import("google-auth-library");
416
- const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
417
324
  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}`;
325
+ const token = await getProxyToken(config.oauthProxy);
326
+ const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
438
327
  const controller = new AbortController();
439
328
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
440
329
  try {
441
- const lid = loginCustomerId || customerId;
442
- const response = await fetch(url, {
443
- method,
330
+ const response = await fetch(proxyUrl, {
331
+ method: "POST",
444
332
  headers: {
445
- Authorization: `Bearer ${token}`,
446
333
  "Content-Type": "application/json",
447
- "developer-token": developerToken,
448
- ...lid ? { "login-customer-id": lid } : {}
334
+ Authorization: `Bearer ${token}`
449
335
  },
450
- body: method === "POST" && body ? JSON.stringify(body) : void 0,
336
+ body: JSON.stringify({
337
+ url: `${BASE_URL2}customers:listAccessibleCustomers`,
338
+ method: "GET",
339
+ headers: {
340
+ "developer-token": developerToken
341
+ }
342
+ }),
451
343
  signal: controller.signal
452
344
  });
453
345
  const data = await response.json();
454
346
  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
- };
347
+ const errorMessage = typeof data?.error === "string" ? data.error : typeof data?.message === "string" ? data.message : `HTTP ${response.status} ${response.statusText}`;
348
+ return { success: false, error: errorMessage };
461
349
  }
462
- return { success: true, status: response.status, data };
350
+ const customerIds = (data.resourceNames ?? []).map(
351
+ (rn) => rn.replace(/^customers\//, "")
352
+ );
353
+ const customers = [];
354
+ for (const cid of customerIds) {
355
+ try {
356
+ const detailResponse = await fetch(proxyUrl, {
357
+ method: "POST",
358
+ headers: {
359
+ "Content-Type": "application/json",
360
+ Authorization: `Bearer ${token}`
361
+ },
362
+ body: JSON.stringify({
363
+ url: `${BASE_URL2}customers/${cid}/googleAds:searchStream`,
364
+ method: "POST",
365
+ headers: {
366
+ "Content-Type": "application/json",
367
+ "developer-token": developerToken,
368
+ "login-customer-id": cid
369
+ },
370
+ body: JSON.stringify({
371
+ query: "SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1"
372
+ })
373
+ }),
374
+ signal: controller.signal
375
+ });
376
+ if (detailResponse.ok) {
377
+ const detailData = await detailResponse.json();
378
+ const customer = detailData?.[0]?.results?.[0]?.customer;
379
+ customers.push({
380
+ customerId: cid,
381
+ descriptiveName: customer?.descriptiveName ?? cid
382
+ });
383
+ } else {
384
+ customers.push({
385
+ customerId: cid,
386
+ descriptiveName: cid
387
+ });
388
+ }
389
+ } catch {
390
+ customers.push({
391
+ customerId: cid,
392
+ descriptiveName: cid
393
+ });
394
+ }
395
+ }
396
+ return { success: true, customers };
463
397
  } finally {
464
398
  clearTimeout(timeout);
465
399
  }
@@ -470,37 +404,122 @@ Authentication is handled automatically using a service account.
470
404
  }
471
405
  });
472
406
 
473
- // ../connectors/src/connectors/google-ads/tools/list-customers.ts
407
+ // ../connectors/src/connectors/google-ads/setup.ts
408
+ var listCustomersToolName = `google-ads_${listCustomersTool.name}`;
409
+ var googleAdsOnboarding = new ConnectorOnboarding({
410
+ connectionSetupInstructions: {
411
+ 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
412
+
413
+ 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
414
+ 2. \`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3059:
415
+ - \`parameterSlug\`: \`"developer-token"\`
416
+ - \`value\`: \u30E6\u30FC\u30B6\u30FC\u304C\u63D0\u4F9B\u3057\u305F Developer Token
417
+ 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
418
+ 4. \`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3059:
419
+ - \`parameterSlug\`: \`"customer-id"\`
420
+ - \`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
421
+ 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
422
+ 6. \`updateConnectionContext\` \u3092\u547C\u3073\u51FA\u3059:
423
+ - \`customer\`: \u9078\u629E\u3055\u308C\u305F\u30AB\u30B9\u30BF\u30DE\u30FC\u306E\u8868\u793A\u540D
424
+ - \`customerId\`: \u9078\u629E\u3055\u308C\u305F\u30AB\u30B9\u30BF\u30DE\u30FCID
425
+ - \`note\`: \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5185\u5BB9\u306E\u7C21\u5358\u306A\u8AAC\u660E
426
+
427
+ #### \u5236\u7D04
428
+ - **\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
429
+ - \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`,
430
+ en: `Follow these steps to set up the Google Ads (OAuth) connection.
431
+
432
+ 1. Ask the user to provide their Google Ads API Developer Token (available in Google Ads UI > Tools & Settings > API Center)
433
+ 2. Call \`updateConnectionParameters\`:
434
+ - \`parameterSlug\`: \`"developer-token"\`
435
+ - \`value\`: The Developer Token provided by the user
436
+ 3. Call \`${listCustomersToolName}\` to get the list of Google Ads customer accounts accessible with the OAuth credentials
437
+ 4. Call \`updateConnectionParameters\`:
438
+ - \`parameterSlug\`: \`"customer-id"\`
439
+ - \`options\`: The customer list. Each option's \`label\` should be \`Account Name (id: customerId)\`, \`value\` should be the customer ID
440
+ 5. The \`label\` of the user's selected customer will arrive as a message. Proceed to the next step
441
+ 6. Call \`updateConnectionContext\`:
442
+ - \`customer\`: The selected customer's display name
443
+ - \`customerId\`: The selected customer ID
444
+ - \`note\`: Brief description of the setup
445
+
446
+ #### Constraints
447
+ - **Do NOT fetch report data during setup**. Only the metadata requests specified in the steps above are allowed
448
+ - Write only 1 sentence between tool calls, then immediately call the next tool. Skip unnecessary explanations and proceed efficiently`
449
+ },
450
+ dataOverviewInstructions: {
451
+ 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
452
+ 2. Explore ad group and keyword data as needed to understand the data structure`,
453
+ 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
454
+ 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`
455
+ }
456
+ });
457
+
458
+ // ../connectors/src/connectors/google-ads/tools/request.ts
474
459
  import { z as z2 } from "zod";
475
460
  var BASE_URL3 = "https://googleads.googleapis.com/v18/";
476
461
  var REQUEST_TIMEOUT_MS2 = 6e4;
462
+ var cachedToken2 = null;
463
+ async function getProxyToken2(config) {
464
+ if (cachedToken2 && cachedToken2.expiresAt > Date.now() + 6e4) {
465
+ return cachedToken2.token;
466
+ }
467
+ const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
468
+ const res = await fetch(url, {
469
+ method: "POST",
470
+ headers: {
471
+ "Content-Type": "application/json",
472
+ "x-api-key": config.appApiKey,
473
+ "project-id": config.projectId
474
+ },
475
+ body: JSON.stringify({
476
+ sandboxId: config.sandboxId,
477
+ issuedBy: "coding-agent"
478
+ })
479
+ });
480
+ if (!res.ok) {
481
+ const errorText = await res.text().catch(() => res.statusText);
482
+ throw new Error(
483
+ `Failed to get proxy token: HTTP ${res.status} ${errorText}`
484
+ );
485
+ }
486
+ const data = await res.json();
487
+ cachedToken2 = {
488
+ token: data.token,
489
+ expiresAt: new Date(data.expiresAt).getTime()
490
+ };
491
+ return data.token;
492
+ }
477
493
  var inputSchema2 = z2.object({
478
494
  toolUseIntent: z2.string().optional().describe(
479
495
  "Brief description of what you intend to accomplish with this tool call"
480
496
  ),
481
- connectionId: z2.string().describe("ID of the Google Ads connection to use")
497
+ connectionId: z2.string().describe("ID of the Google Ads OAuth connection to use"),
498
+ method: z2.enum(["GET", "POST"]).describe("HTTP method"),
499
+ path: z2.string().describe(
500
+ "API path appended to https://googleads.googleapis.com/v18/ (e.g., 'customers/{customerId}/googleAds:searchStream'). {customerId} is automatically replaced."
501
+ ),
502
+ body: z2.record(z2.string(), z2.unknown()).optional().describe("POST request body (JSON)")
482
503
  });
483
504
  var outputSchema2 = z2.discriminatedUnion("success", [
484
505
  z2.object({
485
506
  success: z2.literal(true),
486
- customers: z2.array(
487
- z2.object({
488
- customerId: z2.string(),
489
- descriptiveName: z2.string()
490
- })
491
- )
507
+ status: z2.number(),
508
+ data: z2.unknown()
492
509
  }),
493
510
  z2.object({
494
511
  success: z2.literal(false),
495
512
  error: z2.string()
496
513
  })
497
514
  ]);
498
- var listCustomersTool = new ConnectorTool({
499
- name: "listCustomers",
500
- description: "List Google Ads customer accounts accessible with the service account credentials.",
515
+ var requestTool = new ConnectorTool({
516
+ name: "request",
517
+ description: `Send authenticated requests to the Google Ads API v18.
518
+ Authentication is handled automatically via OAuth proxy.
519
+ {customerId} in the path is automatically replaced with the connection's customer ID (hyphens removed).`,
501
520
  inputSchema: inputSchema2,
502
521
  outputSchema: outputSchema2,
503
- async execute({ connectionId }, connections) {
522
+ async execute({ connectionId, method, path: path2, body }, connections, config) {
504
523
  const connection2 = connections.find((c) => c.id === connectionId);
505
524
  if (!connection2) {
506
525
  return {
@@ -509,90 +528,44 @@ var listCustomersTool = new ConnectorTool({
509
528
  };
510
529
  }
511
530
  console.log(
512
- `[connector-request] google-ads/${connection2.name}: listCustomers`
531
+ `[connector-request] google-ads/${connection2.name}: ${method} ${path2}`
513
532
  );
514
533
  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
- }
534
+ const rawCustomerId = parameters.customerId.tryGetValue(connection2);
535
+ const customerId = rawCustomerId?.replace(/-/g, "") ?? "";
536
+ const resolvedPath = customerId ? path2.replace(/\{customerId\}/g, customerId) : path2;
537
+ const url = `${BASE_URL3}${resolvedPath}`;
538
+ const token = await getProxyToken2(config.oauthProxy);
539
+ const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
532
540
  const controller = new AbortController();
533
541
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
534
542
  try {
535
- const listResponse = await fetch(
536
- `${BASE_URL3}customers:listAccessibleCustomers`,
537
- {
538
- method: "GET",
543
+ const developerToken = parameters.developerToken.getValue(connection2);
544
+ const response = await fetch(proxyUrl, {
545
+ method: "POST",
546
+ headers: {
547
+ "Content-Type": "application/json",
548
+ Authorization: `Bearer ${token}`
549
+ },
550
+ body: JSON.stringify({
551
+ url,
552
+ method,
539
553
  headers: {
540
- Authorization: `Bearer ${token}`,
541
- "developer-token": developerToken
554
+ "Content-Type": "application/json",
555
+ "developer-token": developerToken,
556
+ ...customerId ? { "login-customer-id": customerId } : {}
542
557
  },
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
- }
558
+ ...method === "POST" && body ? { body: JSON.stringify(body) } : {}
559
+ }),
560
+ signal: controller.signal
561
+ });
562
+ const data = await response.json();
563
+ if (!response.ok) {
564
+ const dataObj = data;
565
+ const errorMessage = typeof dataObj?.error === "string" ? dataObj.error : typeof dataObj?.message === "string" ? dataObj.message : `HTTP ${response.status} ${response.statusText}`;
566
+ return { success: false, error: errorMessage };
594
567
  }
595
- return { success: true, customers };
568
+ return { success: true, status: response.status, data };
596
569
  } finally {
597
570
  clearTimeout(timeout);
598
571
  }
@@ -610,17 +583,25 @@ var tools = {
610
583
  };
611
584
  var googleAdsConnector = new ConnectorPlugin({
612
585
  slug: "google-ads",
613
- authType: AUTH_TYPES.SERVICE_ACCOUNT,
586
+ authType: AUTH_TYPES.OAUTH,
614
587
  name: "Google Ads",
615
- description: "Connect to Google Ads for advertising campaign data and reporting using a service account.",
588
+ description: "Connect to Google Ads for advertising campaign data and reporting using OAuth.",
616
589
  iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/1NGvmgvCxX7Tn11EST2N3N/a745fe7c63d360ed40a27ddaad3af168/google-ads.svg",
617
590
  parameters,
618
591
  releaseFlag: { dev1: true, dev2: false, prod: false },
619
592
  onboarding: googleAdsOnboarding,
593
+ proxyPolicy: {
594
+ allowlist: [
595
+ {
596
+ host: "googleads.googleapis.com",
597
+ methods: ["GET", "POST"]
598
+ }
599
+ ]
600
+ },
620
601
  systemPrompt: {
621
602
  en: `### Tools
622
603
 
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.
604
+ - \`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 and developer token are configured automatically.
624
605
  - \`google-ads_listCustomers\`: List accessible Google Ads customer accounts. Use this during setup to discover available accounts.
625
606
 
626
607
  ### Google Ads API Reference
@@ -673,7 +654,7 @@ const customerIds = await ads.listAccessibleCustomers();
673
654
  \`\`\``,
674
655
  ja: `### \u30C4\u30FC\u30EB
675
656
 
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
657
+ - \`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\u8A8D\u8A3C\u3068\u30C7\u30D9\u30ED\u30C3\u30D1\u30FC\u30C8\u30FC\u30AF\u30F3\u306F\u81EA\u52D5\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002
677
658
  - \`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
678
659
 
679
660
  ### Google Ads API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
@@ -725,7 +706,49 @@ rows.forEach(row => console.log(row));
725
706
  const customerIds = await ads.listAccessibleCustomers();
726
707
  \`\`\``
727
708
  },
728
- tools
709
+ tools,
710
+ async checkConnection(params, config) {
711
+ const { proxyFetch } = config;
712
+ const rawCustomerId = params[parameters.customerId.slug];
713
+ const customerId = rawCustomerId?.replace(/-/g, "");
714
+ if (!customerId) {
715
+ return { success: true };
716
+ }
717
+ const developerToken = params[parameters.developerToken.slug];
718
+ if (!developerToken) {
719
+ return {
720
+ success: false,
721
+ error: "Developer token is required"
722
+ };
723
+ }
724
+ const url = `https://googleads.googleapis.com/v18/customers/${customerId}/googleAds:searchStream`;
725
+ try {
726
+ const res = await proxyFetch(url, {
727
+ method: "POST",
728
+ headers: {
729
+ "Content-Type": "application/json",
730
+ "developer-token": developerToken,
731
+ "login-customer-id": customerId
732
+ },
733
+ body: JSON.stringify({
734
+ query: "SELECT customer.id FROM customer LIMIT 1"
735
+ })
736
+ });
737
+ if (!res.ok) {
738
+ const errorText = await res.text().catch(() => res.statusText);
739
+ return {
740
+ success: false,
741
+ error: `Google Ads API failed: HTTP ${res.status} ${errorText}`
742
+ };
743
+ }
744
+ return { success: true };
745
+ } catch (error) {
746
+ return {
747
+ success: false,
748
+ error: error instanceof Error ? error.message : String(error)
749
+ };
750
+ }
751
+ }
729
752
  });
730
753
 
731
754
  // src/connectors/create-connector-sdk.ts