@squadbase/vite-server 0.1.10-dev.5aa0720 → 0.1.10-dev.5b0c0a8

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.
@@ -109,7 +109,7 @@ function createClient(params) {
109
109
  queryOptions.partitionKey = options.partitionKey;
110
110
  }
111
111
  const iterator = cont.items.query(sql, queryOptions);
112
- const { resources } = await iterator.fetchNext();
112
+ const { resources } = await iterator.fetchAll();
113
113
  return resources ?? [];
114
114
  } catch (err) {
115
115
  const msg = err instanceof Error ? err.message : String(err);
@@ -445,7 +445,7 @@ Use \`TOP n\` (not \`LIMIT n\`) to bound the result. Cross-partition queries are
445
445
  }
446
446
  }
447
447
  const iterator = cont.items.query(sql, queryOptions);
448
- const { resources, requestCharge } = await iterator.fetchNext();
448
+ const { resources, requestCharge } = await iterator.fetchAll();
449
449
  const documents = resources ?? [];
450
450
  const truncated = documents.length > MAX_DOCUMENTS;
451
451
  return {
@@ -486,7 +486,7 @@ var cosmosdbConnector = new ConnectorPlugin({
486
486
 
487
487
  ### Business Logic
488
488
 
489
- 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.
489
+ 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 \`import { CosmosClient } from "@azure/cosmos"\` in handler code \u2014 the \`@azure/cosmos\` driver is not part of the user dashboard's runtime, only of the connector SDK.
490
490
 
491
491
  \u26A0\uFE0F **The client returned by \`connection(connectionId)\` is NOT the raw \`@azure/cosmos\` \`CosmosClient\`.** It is a thin wrapper exposing only two methods:
492
492
  - \`client.query<T>(container, sql, options?) => Promise<T[]>\` \u2014 runs a Cosmos SQL query against the named container and returns the **already-unwrapped** items array (no \`{ resources }\` wrapper). \`options\` accepts \`{ maxItemCount?: number; partitionKey?: unknown }\`.
@@ -522,7 +522,7 @@ export default async function handler(_c: Context) {
522
522
  - Queries always target a single container and reference items via the alias \`c\` (e.g. \`SELECT c.id, c.name FROM c WHERE c.status = 'active'\`).
523
523
  - Row limiting: use \`TOP n\` (e.g. \`SELECT TOP 100 * FROM c\`). \`LIMIT\` is **not** supported. \`OFFSET m LIMIT n\` is supported on newer accounts but \`TOP\` is the safe default.
524
524
  - System fields are prefixed with an underscore (\`c.id\`, \`c._ts\` for the modification timestamp, \`c._etag\`).
525
- - Aggregates: \`COUNT(1)\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\`, with \`GROUP BY\` (e.g. \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`).
525
+ - Aggregates: \`COUNT(1)\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\`, with \`GROUP BY\` (e.g. \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`). Cross-partition \`GROUP BY\` is fully supported \u2014 the connector drains all result pages internally, so trust the returned rows even for queries that span many partitions. Do **not** work around perceived missing rows by re-fetching all items and aggregating in TypeScript.
526
526
  - Joins: only **intra-document** joins via \`JOIN\` over arrays inside the same item; there is no cross-container/cross-document join.
527
527
  - Always bound results with \`TOP n\` and prefer scoped queries with a \`partitionKey\` value when possible \u2014 cross-partition queries cost more RUs.`,
528
528
  ja: `### \u30C4\u30FC\u30EB
@@ -532,7 +532,7 @@ export default async function handler(_c: Context) {
532
532
 
533
533
  ### Business Logic
534
534
 
535
- \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
535
+ \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\u305F\u308A\u3001\u30CF\u30F3\u30C9\u30E9\u30B3\u30FC\u30C9\u3067 \`import { CosmosClient } from "@azure/cosmos"\` \u3092\u884C\u3063\u305F\u308A\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044 \u2014 \`@azure/cosmos\` \u30C9\u30E9\u30A4\u30D0\u306F\u30E6\u30FC\u30B6\u30FC\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u306E\u30E9\u30F3\u30BF\u30A4\u30E0\u306B\u306F\u542B\u307E\u308C\u305A\u3001\u30B3\u30CD\u30AF\u30BFSDK\u5185\u90E8\u306B\u306E\u307F\u5B58\u5728\u3057\u307E\u3059\u3002
536
536
 
537
537
  \u26A0\uFE0F **\`connection(connectionId)\` \u304C\u8FD4\u3059\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306F \`@azure/cosmos\` \u306E \`CosmosClient\` \u305D\u306E\u3082\u306E\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002** \u4EE5\u4E0B\u306E2\u30E1\u30BD\u30C3\u30C9\u306E\u307F\u3092\u516C\u958B\u3059\u308B\u8584\u3044\u30E9\u30C3\u30D1\u30FC\u3067\u3059\uFF1A
538
538
  - \`client.query<T>(container, sql, options?) => Promise<T[]>\` \u2014 \u6307\u5B9A\u30B3\u30F3\u30C6\u30CA\u306B\u5BFE\u3057\u3066 Cosmos SQL \u3092\u5B9F\u884C\u3057\u3001**\u3059\u3067\u306B\u5C55\u958B\u6E08\u307F\u306E\u30A2\u30A4\u30C6\u30E0\u914D\u5217**\u3092\u8FD4\u3059\uFF08\`{ resources }\` \u3067\u30E9\u30C3\u30D7\u3055\u308C\u3066\u3044\u306A\u3044\uFF09\u3002\`options\` \u306F \`{ maxItemCount?: number; partitionKey?: unknown }\`\u3002
@@ -568,7 +568,7 @@ export default async function handler(_c: Context) {
568
568
  - \u30AF\u30A8\u30EA\u306F\u5E38\u306B\u5358\u4E00\u30B3\u30F3\u30C6\u30CA\u3092\u5BFE\u8C61\u3068\u3057\u3001\u30A2\u30A4\u30C6\u30E0\u306F\u5225\u540D \`c\` \u3067\u53C2\u7167\u3057\u307E\u3059\uFF08\u4F8B: \`SELECT c.id, c.name FROM c WHERE c.status = 'active'\`\uFF09\u3002
569
569
  - \u884C\u6570\u5236\u9650: \`TOP n\` \u3092\u4F7F\u7528\u3057\u307E\u3059\uFF08\u4F8B: \`SELECT TOP 100 * FROM c\`\uFF09\u3002\`LIMIT\` \u306F **\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093**\u3002\u65B0\u3057\u3044\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u306F \`OFFSET m LIMIT n\` \u304C\u4F7F\u3048\u307E\u3059\u304C\u3001\u5B89\u5168\u5074\u306E\u65E2\u5B9A\u306F \`TOP\` \u3067\u3059\u3002
570
570
  - \u30B7\u30B9\u30C6\u30E0\u30D5\u30A3\u30FC\u30EB\u30C9\u306F\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u59CB\u307E\u308A\uFF08\`c.id\`\u3001\u5909\u66F4\u6642\u523B\u306E \`c._ts\`\u3001\`c._etag\`\uFF09\u3002
571
- - \u96C6\u7D04: \`COUNT(1)\`\u3001\`SUM\`\u3001\`AVG\`\u3001\`MIN\`\u3001\`MAX\` \u3092 \`GROUP BY\` \u3068\u7D44\u307F\u5408\u308F\u305B\u307E\u3059\uFF08\u4F8B: \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`\uFF09\u3002
571
+ - \u96C6\u7D04: \`COUNT(1)\`\u3001\`SUM\`\u3001\`AVG\`\u3001\`MIN\`\u3001\`MAX\` \u3092 \`GROUP BY\` \u3068\u7D44\u307F\u5408\u308F\u305B\u307E\u3059\uFF08\u4F8B: \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`\uFF09\u3002\u30AF\u30ED\u30B9\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u306E \`GROUP BY\` \u3082\u5B8C\u5168\u306B\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u304A\u308A\u3001\u30B3\u30CD\u30AF\u30BF\u304C\u5185\u90E8\u3067\u5168\u7D50\u679C\u30DA\u30FC\u30B8\u3092\u30C9\u30EC\u30A4\u30F3\u3059\u308B\u305F\u3081\u3001\u591A\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u306B\u307E\u305F\u304C\u308B\u30AF\u30A8\u30EA\u3067\u3082\u8FD4\u3063\u3066\u304D\u305F\u884C\u6570\u3092\u305D\u306E\u307E\u307E\u4FE1\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002**\u300C\u7D50\u679C\u304C\u6B20\u3051\u3066\u3044\u308B\u3088\u3046\u306B\u898B\u3048\u308B\u300D\u304B\u3089\u3068\u3044\u3063\u3066\u5168\u4EF6\u53D6\u5F97\u2192TS \u5074\u3067\u96C6\u8A08\u3059\u308B\u8FC2\u56DE\u306F\u3057\u306A\u3044\u3053\u3068\u3002**
572
572
  - \u7D50\u5408: \u540C\u4E00\u30A2\u30A4\u30C6\u30E0\u5185\u306E\u914D\u5217\u306B\u5BFE\u3059\u308B **\u30A4\u30F3\u30C8\u30E9\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8** \`JOIN\` \u306E\u307F\u3067\u3001\u30B3\u30F3\u30C6\u30CA\u9593\uFF0F\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u9593\u306E\u7D50\u5408\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u307E\u305B\u3093\u3002
573
573
  - \u7D50\u679C\u4EF6\u6570\u306F\u5FC5\u305A \`TOP n\` \u3067\u5236\u9650\u3057\u3001\u53EF\u80FD\u3067\u3042\u308C\u3070 \`partitionKey\` \u3067\u5358\u4E00\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u306B\u30B9\u30B3\u30FC\u30D7\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30AF\u30ED\u30B9\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u30AF\u30A8\u30EA\u306F RU \u3092\u591A\u304F\u6D88\u8CBB\u3057\u307E\u3059\uFF09\u3002`
574
574
  },
@@ -42,6 +42,39 @@ var ParameterDefinition = class {
42
42
  }
43
43
  };
44
44
 
45
+ // ../connectors/src/connectors/google-ads/constants.ts
46
+ var VERSION_FALLBACK_ORDER = ["v22", "v23", "v24"];
47
+ var cachedWorkingVersion = VERSION_FALLBACK_ORDER[0];
48
+ var looksSunset = (contentType) => {
49
+ return contentType != null && contentType.toLowerCase().includes("text/html");
50
+ };
51
+ async function fetchGoogleAdsWithVersionFallback(attempt) {
52
+ const order = [
53
+ cachedWorkingVersion,
54
+ ...VERSION_FALLBACK_ORDER.filter((v) => v !== cachedWorkingVersion)
55
+ ];
56
+ let lastResponse = null;
57
+ for (let i = 0; i < order.length; i++) {
58
+ const version = order[i];
59
+ const baseUrl = `https://googleads.googleapis.com/${version}/`;
60
+ const response = await attempt(baseUrl);
61
+ const contentType = response.headers.get("content-type");
62
+ if (looksSunset(contentType) && i < order.length - 1) {
63
+ console.warn(
64
+ `[google-ads] version ${version} returned a sunset/HTML response (status ${response.status}); retrying with newer version`
65
+ );
66
+ lastResponse = response;
67
+ continue;
68
+ }
69
+ cachedWorkingVersion = version;
70
+ return response;
71
+ }
72
+ return lastResponse ?? new Response(null, {
73
+ status: 502,
74
+ statusText: "All Google Ads API versions returned sunset responses"
75
+ });
76
+ }
77
+
45
78
  // ../connectors/src/connectors/google-ads/parameters.ts
46
79
  var parameters = {
47
80
  customerId: new ParameterDefinition({
@@ -52,29 +85,13 @@ var parameters = {
52
85
  type: "text",
53
86
  secret: false,
54
87
  required: false
55
- }),
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",
61
- type: "text",
62
- secret: true,
63
- required: true
64
88
  })
65
89
  };
66
90
 
67
91
  // ../connectors/src/connectors/google-ads/sdk/index.ts
68
- var BASE_URL = "https://googleads.googleapis.com/v18/";
69
92
  function createClient(params, fetchFn = fetch) {
70
93
  const rawCustomerId = params[parameters.customerId.slug];
71
94
  const defaultCustomerId = rawCustomerId?.replace(/-/g, "") ?? "";
72
- const developerToken = params[parameters.developerToken.slug];
73
- if (!developerToken) {
74
- throw new Error(
75
- `google-ads: missing required parameter: ${parameters.developerToken.slug}`
76
- );
77
- }
78
95
  function resolveCustomerId(override) {
79
96
  const id = override?.replace(/-/g, "") ?? defaultCustomerId;
80
97
  if (!id) {
@@ -86,26 +103,26 @@ function createClient(params, fetchFn = fetch) {
86
103
  }
87
104
  function request(path2, init) {
88
105
  const resolvedPath = defaultCustomerId ? path2.replace(/\{customerId\}/g, defaultCustomerId) : path2;
89
- const url = `${BASE_URL}${resolvedPath}`;
90
106
  const headers = new Headers(init?.headers);
91
- headers.set("developer-token", developerToken);
92
107
  if (defaultCustomerId) {
93
108
  headers.set("login-customer-id", defaultCustomerId);
94
109
  }
95
- return fetchFn(url, { ...init, headers });
110
+ return fetchGoogleAdsWithVersionFallback(
111
+ (baseUrl) => fetchFn(`${baseUrl}${resolvedPath}`, { ...init, headers })
112
+ );
96
113
  }
97
114
  async function search(query, customerId) {
98
115
  const cid = resolveCustomerId(customerId);
99
- const url = `${BASE_URL}customers/${cid}/googleAds:searchStream`;
100
116
  const headers = new Headers();
101
117
  headers.set("Content-Type", "application/json");
102
- headers.set("developer-token", developerToken);
103
118
  headers.set("login-customer-id", cid);
104
- const response = await fetchFn(url, {
105
- method: "POST",
106
- headers,
107
- body: JSON.stringify({ query })
108
- });
119
+ const response = await fetchGoogleAdsWithVersionFallback(
120
+ (baseUrl) => fetchFn(`${baseUrl}customers/${cid}/googleAds:searchStream`, {
121
+ method: "POST",
122
+ headers,
123
+ body: JSON.stringify({ query })
124
+ })
125
+ );
109
126
  if (!response.ok) {
110
127
  const body = await response.text();
111
128
  throw new Error(
@@ -116,10 +133,9 @@ function createClient(params, fetchFn = fetch) {
116
133
  return data.flatMap((chunk) => chunk.results ?? []);
117
134
  }
118
135
  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 });
136
+ const response = await fetchGoogleAdsWithVersionFallback(
137
+ (baseUrl) => fetchFn(`${baseUrl}customers:listAccessibleCustomers`, { method: "GET" })
138
+ );
123
139
  if (!response.ok) {
124
140
  const body = await response.text();
125
141
  throw new Error(
@@ -289,7 +305,65 @@ var AUTH_TYPES = {
289
305
 
290
306
  // ../connectors/src/connectors/google-ads/tools/list-customers.ts
291
307
  import { z } from "zod";
292
- var BASE_URL2 = "https://googleads.googleapis.com/v18/";
308
+
309
+ // ../connectors/src/connectors/google-ads/error.ts
310
+ var parseJsonOrNull = (text) => {
311
+ if (text.length === 0) return null;
312
+ try {
313
+ return JSON.parse(text);
314
+ } catch {
315
+ return null;
316
+ }
317
+ };
318
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
319
+ var collectGoogleAdsErrorCodes = (details) => {
320
+ if (!Array.isArray(details)) return [];
321
+ const codes = [];
322
+ for (const detail of details) {
323
+ if (!isRecord(detail)) continue;
324
+ const errors = detail.errors;
325
+ if (!Array.isArray(errors)) continue;
326
+ for (const e of errors) {
327
+ if (!isRecord(e)) continue;
328
+ const errorCode = e.errorCode;
329
+ if (!isRecord(errorCode)) continue;
330
+ for (const [k, v] of Object.entries(errorCode)) {
331
+ if (typeof v === "string") codes.push(`${k}=${v}`);
332
+ }
333
+ }
334
+ }
335
+ return codes;
336
+ };
337
+ var extractGoogleAdsErrorMessage = (status, statusText, parsedBody, rawText) => {
338
+ const fallback = `HTTP ${status} ${statusText}`;
339
+ if (isRecord(parsedBody)) {
340
+ const errorField = parsedBody.error;
341
+ if (typeof errorField === "string") return errorField;
342
+ if (isRecord(errorField)) {
343
+ const message = typeof errorField.message === "string" ? errorField.message : null;
344
+ const errorStatus = typeof errorField.status === "string" ? errorField.status : null;
345
+ const codes = collectGoogleAdsErrorCodes(errorField.details);
346
+ const parts = [
347
+ fallback,
348
+ errorStatus,
349
+ message,
350
+ codes.length > 0 ? `[${codes.join(", ")}]` : null
351
+ ].filter((p) => p != null && p.length > 0);
352
+ if (parts.length > 1) return parts.join(": ");
353
+ }
354
+ if (typeof parsedBody.message === "string") {
355
+ return `${fallback}: ${parsedBody.message}`;
356
+ }
357
+ }
358
+ if (rawText.length > 0) {
359
+ const trimmed = rawText.trim();
360
+ const preview = trimmed.length > 500 ? `${trimmed.slice(0, 500)}\u2026` : trimmed;
361
+ return `${fallback}: ${preview}`;
362
+ }
363
+ return fallback;
364
+ };
365
+
366
+ // ../connectors/src/connectors/google-ads/tools/list-customers.ts
293
367
  var REQUEST_TIMEOUT_MS = 6e4;
294
368
  var cachedToken = null;
295
369
  async function getProxyToken(config) {
@@ -360,58 +434,65 @@ var listCustomersTool = new ConnectorTool({
360
434
  `[connector-request] google-ads/${connection2.name}: listCustomers`
361
435
  );
362
436
  try {
363
- const developerToken = parameters.developerToken.getValue(connection2);
364
437
  const token = await getProxyToken(config.oauthProxy);
365
438
  const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
366
439
  const controller = new AbortController();
367
440
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
368
441
  try {
369
- const response = await fetch(proxyUrl, {
370
- method: "POST",
371
- headers: {
372
- "Content-Type": "application/json",
373
- Authorization: `Bearer ${token}`
374
- },
375
- body: JSON.stringify({
376
- url: `${BASE_URL2}customers:listAccessibleCustomers`,
377
- method: "GET",
442
+ const response = await fetchGoogleAdsWithVersionFallback(
443
+ (baseUrl) => fetch(proxyUrl, {
444
+ method: "POST",
378
445
  headers: {
379
- "developer-token": developerToken
380
- }
381
- }),
382
- signal: controller.signal
383
- });
384
- const data = await response.json();
446
+ "Content-Type": "application/json",
447
+ Authorization: `Bearer ${token}`
448
+ },
449
+ body: JSON.stringify({
450
+ url: `${baseUrl}customers:listAccessibleCustomers`,
451
+ method: "GET"
452
+ }),
453
+ signal: controller.signal
454
+ })
455
+ );
456
+ const rawText = await response.text();
457
+ const data = parseJsonOrNull(rawText);
385
458
  if (!response.ok) {
386
- const errorMessage = typeof data?.error === "string" ? data.error : typeof data?.message === "string" ? data.message : `HTTP ${response.status} ${response.statusText}`;
387
- return { success: false, error: errorMessage };
459
+ return {
460
+ success: false,
461
+ error: extractGoogleAdsErrorMessage(
462
+ response.status,
463
+ response.statusText,
464
+ data,
465
+ rawText
466
+ )
467
+ };
388
468
  }
389
- const customerIds = (data.resourceNames ?? []).map(
469
+ const customerIds = (data?.resourceNames ?? []).map(
390
470
  (rn) => rn.replace(/^customers\//, "")
391
471
  );
392
472
  const customers = [];
393
473
  for (const cid of customerIds) {
394
474
  try {
395
- const detailResponse = await fetch(proxyUrl, {
396
- method: "POST",
397
- headers: {
398
- "Content-Type": "application/json",
399
- Authorization: `Bearer ${token}`
400
- },
401
- body: JSON.stringify({
402
- url: `${BASE_URL2}customers/${cid}/googleAds:searchStream`,
475
+ const detailResponse = await fetchGoogleAdsWithVersionFallback(
476
+ (baseUrl) => fetch(proxyUrl, {
403
477
  method: "POST",
404
478
  headers: {
405
479
  "Content-Type": "application/json",
406
- "developer-token": developerToken,
407
- "login-customer-id": cid
480
+ Authorization: `Bearer ${token}`
408
481
  },
409
482
  body: JSON.stringify({
410
- query: "SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1"
411
- })
412
- }),
413
- signal: controller.signal
414
- });
483
+ url: `${baseUrl}customers/${cid}/googleAds:searchStream`,
484
+ method: "POST",
485
+ headers: {
486
+ "Content-Type": "application/json",
487
+ "login-customer-id": cid
488
+ },
489
+ body: JSON.stringify({
490
+ query: "SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1"
491
+ })
492
+ }),
493
+ signal: controller.signal
494
+ })
495
+ );
415
496
  if (detailResponse.ok) {
416
497
  const detailData = await detailResponse.json();
417
498
  const customer = detailData?.[0]?.results?.[0]?.customer;
@@ -449,30 +530,24 @@ var googleAdsOnboarding = new ConnectorOnboarding({
449
530
  connectionSetupInstructions: {
450
531
  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
451
532
 
452
- 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
533
+ 1. \`${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
453
534
  2. \`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3059:
454
- - \`parameterSlug\`: \`"developer-token"\`
455
- - \`value\`: \u30E6\u30FC\u30B6\u30FC\u304C\u63D0\u4F9B\u3057\u305F Developer Token
456
- 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
457
- 4. \`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3059:
458
535
  - \`parameterSlug\`: \`"customer-id"\`
459
536
  - \`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
460
- 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
537
+ - \u30AB\u30B9\u30BF\u30DE\u30FC\u304C **0\u4EF6** \u306E\u5834\u5408\u306F\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u4E2D\u65AD\u3057\u3001\u30E6\u30FC\u30B6\u30FC\u306B\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u306A\u3044\u65E8\u3092\u4F1D\u3048\u308B
538
+ 3. \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
461
539
 
462
540
  #### \u5236\u7D04
463
541
  - **\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
464
542
  - \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`,
465
543
  en: `Follow these steps to set up the Google Ads (OAuth) connection.
466
544
 
467
- 1. Ask the user to provide their Google Ads API Developer Token (available in Google Ads UI > Tools & Settings > API Center)
545
+ 1. Call \`${listCustomersToolName}\` to get the list of Google Ads customer accounts accessible with the OAuth credentials
468
546
  2. Call \`updateConnectionParameters\`:
469
- - \`parameterSlug\`: \`"developer-token"\`
470
- - \`value\`: The Developer Token provided by the user
471
- 3. Call \`${listCustomersToolName}\` to get the list of Google Ads customer accounts accessible with the OAuth credentials
472
- 4. Call \`updateConnectionParameters\`:
473
547
  - \`parameterSlug\`: \`"customer-id"\`
474
548
  - \`options\`: The customer list. Each option's \`label\` should be \`Account Name (id: customerId)\`, \`value\` should be the customer ID
475
- 5. The \`label\` of the user's selected customer will arrive as a message. Proceed to the next step
549
+ - If **0 customers** are returned, abort setup and inform the user that no accessible accounts are available
550
+ 3. The \`label\` of the user's selected customer will arrive as a message. Proceed to the next step
476
551
 
477
552
  #### Constraints
478
553
  - **Do NOT fetch report data during setup**. Only the metadata requests specified in the steps above are allowed
@@ -488,7 +563,6 @@ var googleAdsOnboarding = new ConnectorOnboarding({
488
563
 
489
564
  // ../connectors/src/connectors/google-ads/tools/request.ts
490
565
  import { z as z2 } from "zod";
491
- var BASE_URL3 = "https://googleads.googleapis.com/v18/";
492
566
  var REQUEST_TIMEOUT_MS2 = 6e4;
493
567
  var cachedToken2 = null;
494
568
  async function getProxyToken2(config) {
@@ -528,7 +602,7 @@ var inputSchema2 = z2.object({
528
602
  connectionId: z2.string().describe("ID of the Google Ads OAuth connection to use"),
529
603
  method: z2.enum(["GET", "POST"]).describe("HTTP method"),
530
604
  path: z2.string().describe(
531
- "API path appended to https://googleads.googleapis.com/v18/ (e.g., 'customers/{customerId}/googleAds:searchStream'). {customerId} is automatically replaced."
605
+ "API path appended to https://googleads.googleapis.com/<version>/ (e.g., 'customers/{customerId}/googleAds:searchStream'). The API version is auto-managed and {customerId} is automatically replaced."
532
606
  ),
533
607
  body: z2.record(z2.string(), z2.unknown()).optional().describe("POST request body (JSON)")
534
608
  });
@@ -545,7 +619,7 @@ var outputSchema2 = z2.discriminatedUnion("success", [
545
619
  ]);
546
620
  var requestTool = new ConnectorTool({
547
621
  name: "request",
548
- description: `Send authenticated requests to the Google Ads API v18.
622
+ description: `Send authenticated requests to the Google Ads API.
549
623
  Authentication is handled automatically via OAuth proxy.
550
624
  {customerId} in the path is automatically replaced with the connection's customer ID (hyphens removed).`,
551
625
  inputSchema: inputSchema2,
@@ -565,36 +639,42 @@ Authentication is handled automatically via OAuth proxy.
565
639
  const rawCustomerId = parameters.customerId.tryGetValue(connection2);
566
640
  const customerId = rawCustomerId?.replace(/-/g, "") ?? "";
567
641
  const resolvedPath = customerId ? path2.replace(/\{customerId\}/g, customerId) : path2;
568
- const url = `${BASE_URL3}${resolvedPath}`;
569
642
  const token = await getProxyToken2(config.oauthProxy);
570
643
  const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
571
644
  const controller = new AbortController();
572
645
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
573
646
  try {
574
- const developerToken = parameters.developerToken.getValue(connection2);
575
- const response = await fetch(proxyUrl, {
576
- method: "POST",
577
- headers: {
578
- "Content-Type": "application/json",
579
- Authorization: `Bearer ${token}`
580
- },
581
- body: JSON.stringify({
582
- url,
583
- method,
647
+ const response = await fetchGoogleAdsWithVersionFallback(
648
+ (baseUrl) => fetch(proxyUrl, {
649
+ method: "POST",
584
650
  headers: {
585
651
  "Content-Type": "application/json",
586
- "developer-token": developerToken,
587
- ...customerId ? { "login-customer-id": customerId } : {}
652
+ Authorization: `Bearer ${token}`
588
653
  },
589
- ...method === "POST" && body ? { body: JSON.stringify(body) } : {}
590
- }),
591
- signal: controller.signal
592
- });
593
- const data = await response.json();
654
+ body: JSON.stringify({
655
+ url: `${baseUrl}${resolvedPath}`,
656
+ method,
657
+ headers: {
658
+ "Content-Type": "application/json",
659
+ ...customerId ? { "login-customer-id": customerId } : {}
660
+ },
661
+ ...method === "POST" && body ? { body: JSON.stringify(body) } : {}
662
+ }),
663
+ signal: controller.signal
664
+ })
665
+ );
666
+ const rawText = await response.text();
667
+ const data = parseJsonOrNull(rawText);
594
668
  if (!response.ok) {
595
- const dataObj = data;
596
- const errorMessage = typeof dataObj?.error === "string" ? dataObj.error : typeof dataObj?.message === "string" ? dataObj.message : `HTTP ${response.status} ${response.statusText}`;
597
- return { success: false, error: errorMessage };
669
+ return {
670
+ success: false,
671
+ error: extractGoogleAdsErrorMessage(
672
+ response.status,
673
+ response.statusText,
674
+ data,
675
+ rawText
676
+ )
677
+ };
598
678
  }
599
679
  return { success: true, status: response.status, data };
600
680
  } finally {
@@ -764,26 +844,22 @@ const customerIds = await ads.listAccessibleCustomers();
764
844
  if (!customerId) {
765
845
  return { success: true };
766
846
  }
767
- const developerToken = params[parameters.developerToken.slug];
768
- if (!developerToken) {
769
- return {
770
- success: false,
771
- error: "Developer token is required"
772
- };
773
- }
774
- const url = `https://googleads.googleapis.com/v18/customers/${customerId}/googleAds:searchStream`;
775
847
  try {
776
- const res = await proxyFetch(url, {
777
- method: "POST",
778
- headers: {
779
- "Content-Type": "application/json",
780
- "developer-token": developerToken,
781
- "login-customer-id": customerId
782
- },
783
- body: JSON.stringify({
784
- query: "SELECT customer.id FROM customer LIMIT 1"
785
- })
786
- });
848
+ const res = await fetchGoogleAdsWithVersionFallback(
849
+ (baseUrl) => proxyFetch(
850
+ `${baseUrl}customers/${customerId}/googleAds:searchStream`,
851
+ {
852
+ method: "POST",
853
+ headers: {
854
+ "Content-Type": "application/json",
855
+ "login-customer-id": customerId
856
+ },
857
+ body: JSON.stringify({
858
+ query: "SELECT customer.id FROM customer LIMIT 1"
859
+ })
860
+ }
861
+ )
862
+ );
787
863
  if (!res.ok) {
788
864
  const errorText = await res.text().catch(() => res.statusText);
789
865
  return {