@squadbase/vite-server 0.1.17-dev.a9ddcfa → 0.1.17-dev.e4768d1

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 (80) hide show
  1. package/dist/cli/index.js +8941 -3700
  2. package/dist/connectors/airtable-oauth.js +87 -23
  3. package/dist/connectors/airtable.js +130 -23
  4. package/dist/connectors/amplitude.js +47 -23
  5. package/dist/connectors/anthropic.js +41 -17
  6. package/dist/connectors/asana.js +76 -25
  7. package/dist/connectors/attio.js +69 -28
  8. package/dist/connectors/aws-billing.js +47 -23
  9. package/dist/connectors/azure-sql.js +322 -25
  10. package/dist/connectors/backlog-api-key.js +79 -30
  11. package/dist/connectors/clickup.js +89 -25
  12. package/dist/connectors/cosmosdb.js +51 -27
  13. package/dist/connectors/customerio.js +47 -23
  14. package/dist/connectors/dbt.js +725 -40
  15. package/dist/connectors/freshdesk.js +121 -23
  16. package/dist/connectors/freshsales.js +47 -23
  17. package/dist/connectors/freshservice.js +47 -23
  18. package/dist/connectors/gamma.js +54 -30
  19. package/dist/connectors/gemini.js +41 -17
  20. package/dist/connectors/github.js +51 -27
  21. package/dist/connectors/gmail-oauth.js +42 -23
  22. package/dist/connectors/gmail.js +43 -19
  23. package/dist/connectors/google-ads.js +47 -23
  24. package/dist/connectors/google-analytics-oauth.js +191 -40
  25. package/dist/connectors/google-analytics.js +529 -111
  26. package/dist/connectors/google-audit-log.js +43 -19
  27. package/dist/connectors/google-calendar-oauth.js +100 -30
  28. package/dist/connectors/google-calendar.js +100 -26
  29. package/dist/connectors/google-docs.js +42 -23
  30. package/dist/connectors/google-drive.js +64 -23
  31. package/dist/connectors/google-search-console-oauth.js +165 -32
  32. package/dist/connectors/google-sheets.js +142 -199
  33. package/dist/connectors/google-slides.js +42 -23
  34. package/dist/connectors/grafana.js +84 -25
  35. package/dist/connectors/hackernews.d.ts +5 -0
  36. package/dist/connectors/hackernews.js +909 -0
  37. package/dist/connectors/hubspot-oauth.js +73 -22
  38. package/dist/connectors/hubspot.js +64 -24
  39. package/dist/connectors/influxdb.js +47 -23
  40. package/dist/connectors/intercom-oauth.js +104 -25
  41. package/dist/connectors/intercom.js +51 -27
  42. package/dist/connectors/jdbc.js +76 -25
  43. package/dist/connectors/jira-api-key.js +107 -26
  44. package/dist/connectors/kintone-api-token.js +105 -33
  45. package/dist/connectors/kintone.js +182 -26
  46. package/dist/connectors/linear.js +93 -27
  47. package/dist/connectors/linkedin-ads.js +80 -29
  48. package/dist/connectors/mailchimp-oauth.js +45 -21
  49. package/dist/connectors/mailchimp.js +45 -21
  50. package/dist/connectors/meta-ads-oauth.js +72 -29
  51. package/dist/connectors/meta-ads.js +74 -29
  52. package/dist/connectors/mixpanel.js +47 -23
  53. package/dist/connectors/monday.js +48 -24
  54. package/dist/connectors/mongodb.js +47 -23
  55. package/dist/connectors/notion-oauth.js +92 -24
  56. package/dist/connectors/notion.js +144 -26
  57. package/dist/connectors/openai.js +41 -17
  58. package/dist/connectors/oracle.js +316 -26
  59. package/dist/connectors/outlook-oauth.js +53 -34
  60. package/dist/connectors/powerbi-oauth.js +45 -26
  61. package/dist/connectors/salesforce.js +150 -24
  62. package/dist/connectors/semrush.js +45 -21
  63. package/dist/connectors/sentry.js +75 -25
  64. package/dist/connectors/shopify-oauth.js +75 -23
  65. package/dist/connectors/shopify.js +47 -23
  66. package/dist/connectors/sqlserver.js +322 -25
  67. package/dist/connectors/stripe-api-key.js +105 -30
  68. package/dist/connectors/stripe-oauth.js +102 -32
  69. package/dist/connectors/supabase.js +315 -21
  70. package/dist/connectors/tableau.js +54 -30
  71. package/dist/connectors/tiktok-ads.js +76 -31
  72. package/dist/connectors/wix-store.js +47 -23
  73. package/dist/connectors/x.d.ts +5 -0
  74. package/dist/connectors/x.js +951 -0
  75. package/dist/connectors/zendesk-oauth.js +87 -25
  76. package/dist/connectors/zendesk.js +51 -27
  77. package/dist/index.js +9005 -3709
  78. package/dist/main.js +9005 -3709
  79. package/dist/vite-plugin.js +8939 -3698
  80. package/package.json +12 -4
@@ -1,6 +1,11 @@
1
1
  var __getOwnPropNames = Object.getOwnPropertyNames;
2
- var __esm = (fn, res) => function __init() {
3
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
2
+ var __esm = (fn, res, err) => function __init() {
3
+ if (err) throw err[0];
4
+ try {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ } catch (e) {
7
+ throw err = [e], e;
8
+ }
4
9
  };
5
10
 
6
11
  // ../connectors/src/parameter-definition.ts
@@ -311,6 +316,15 @@ var ConnectorPlugin = class _ConnectorPlugin {
311
316
  * `runSetupFlow` from `setup-flow.ts`.
312
317
  */
313
318
  setup;
319
+ /**
320
+ * 統一 introspection。SQL DB の `information_schema` / `SHOW` / `DESCRIBE` や SaaS の
321
+ * メタデータ API といった取得経路の差を吸収し、tables / columns / PK / FK を共通型
322
+ * {@link IntrospectedSchema} へ正規化して返す。取得経路は {@link IntrospectContext} で
323
+ * 抽象化され、SQL DB は `ctx.query`、SaaS は `ctx.proxyFetch` + `ctx.params` を使う。
324
+ * `ctx.profile === true` のときは L2 プロファイル(行数・NULL 率等)も best-effort で埋める。
325
+ * coding-agent の `DataContextIndex` がデータ地図を構築する際に使う。
326
+ */
327
+ introspect;
314
328
  /**
315
329
  * Opt-out of the default "verify before save" behavior on connection
316
330
  * creation. The backend invokes `checkConnection` synchronously while
@@ -325,6 +339,14 @@ var ConnectorPlugin = class _ConnectorPlugin {
325
339
  * the default verify-on-create behavior without opt-in.
326
340
  */
327
341
  skipConnectionCheckOnCreate;
342
+ /**
343
+ * 決定論的な「立ち上げ survey」レシピ(ルールベース prewarm 基盤)。実装した connector は、LLM を使わずに
344
+ * read-only で自分を survey する probe 列(テーブル/列メタデータ → 小サンプル)を宣言する。coding-agent の
345
+ * recon driver がこれを読み、connector ツールの `execute` を直接呼んで結果を seed にする。未実装の connector は
346
+ * driver 側で safely skip される(その connection は listConnections のみ seed)。標準 `information_schema` を
347
+ * 持つ SQL connector は `makeStandardSqlSurveyProbes` を使える。**probe は必ず read-only**。
348
+ */
349
+ surveyProbes;
328
350
  constructor(config) {
329
351
  this.slug = config.slug;
330
352
  this.authType = config.authType;
@@ -342,7 +364,9 @@ var ConnectorPlugin = class _ConnectorPlugin {
342
364
  this.query = config.query;
343
365
  this.checkConnection = config.checkConnection;
344
366
  this.setup = config.setup;
367
+ this.introspect = config.introspect;
345
368
  this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
369
+ this.surveyProbes = config.surveyProbes;
346
370
  }
347
371
  get connectorKey() {
348
372
  return _ConnectorPlugin.deriveKey(this.slug, this.authType);
@@ -350,7 +374,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
350
374
  /**
351
375
  * Create tools for connections that belong to this connector.
352
376
  * Filters connections by connectorKey internally.
353
- * Returns tools keyed as `${connectorKey}_${toolName}`.
377
+ * Returns tools keyed as `connector_${connectorKey}_${toolName}`.
354
378
  */
355
379
  createTools(connections, config, opts) {
356
380
  const myConnections = connections.filter(
@@ -360,7 +384,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
360
384
  for (const t of Object.values(this.tools)) {
361
385
  const tool = t.createTool(myConnections, config);
362
386
  const originalToModelOutput = tool.toModelOutput;
363
- result[`${this.connectorKey}_${t.name}`] = {
387
+ result[`connector_${this.connectorKey}_${t.name}`] = {
364
388
  ...tool,
365
389
  toModelOutput: async (options) => {
366
390
  if (!originalToModelOutput) {
@@ -380,25 +404,25 @@ var ConnectorPlugin = class _ConnectorPlugin {
380
404
  if (authType) return `${slug}-${authType}`;
381
405
  const LEGACY_NULL_AUTH_TYPE_MAP = {
382
406
  // user-password
383
- "postgresql": "user-password",
384
- "mysql": "user-password",
385
- "clickhouse": "user-password",
386
- "kintone": "user-password",
407
+ postgresql: "user-password",
408
+ mysql: "user-password",
409
+ clickhouse: "user-password",
410
+ kintone: "user-password",
387
411
  "squadbase-db": "user-password",
388
412
  // service-account
389
- "snowflake": "service-account",
390
- "bigquery": "service-account",
413
+ snowflake: "service-account",
414
+ bigquery: "service-account",
391
415
  "google-analytics": "service-account",
392
416
  "google-calendar": "service-account",
393
417
  "aws-athena": "service-account",
394
- "redshift": "service-account",
418
+ redshift: "service-account",
395
419
  // api-key
396
- "databricks": "api-key",
397
- "dbt": "api-key",
398
- "airtable": "api-key",
399
- "openai": "api-key",
400
- "gemini": "api-key",
401
- "anthropic": "api-key",
420
+ databricks: "api-key",
421
+ dbt: "api-key",
422
+ airtable: "api-key",
423
+ openai: "api-key",
424
+ gemini: "api-key",
425
+ anthropic: "api-key",
402
426
  "wix-store": "api-key"
403
427
  };
404
428
  const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
@@ -490,10 +514,10 @@ var AUTH_TYPES = {
490
514
  // ../connectors/src/connectors/google-analytics/setup.ts
491
515
  var googleAnalyticsOnboarding = new ConnectorOnboarding({
492
516
  dataOverviewInstructions: {
493
- en: `1. Call google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
494
- 2. Call google-analytics-service-account_request with POST properties/{propertyId}:runReport using a small date range and basic metrics to verify data availability`,
495
- ja: `1. google-analytics-service-account_request \u3067 GET properties/{propertyId}/metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3\u3068\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7\u3092\u53D6\u5F97
496
- 2. google-analytics-service-account_request \u3067 POST properties/{propertyId}:runReport \u3092\u77ED\u3044\u671F\u9593\u3068\u57FA\u672C\u30E1\u30C8\u30EA\u30AF\u30B9\u3067\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u306E\u53EF\u7528\u6027\u3092\u78BA\u8A8D`
517
+ en: `1. Call connector_google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
518
+ 2. Call connector_google-analytics-service-account_request with POST properties/{propertyId}:runReport using a small date range and basic metrics to verify data availability`,
519
+ ja: `1. connector_google-analytics-service-account_request \u3067 GET properties/{propertyId}/metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3\u3068\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7\u3092\u53D6\u5F97
520
+ 2. connector_google-analytics-service-account_request \u3067 POST properties/{propertyId}:runReport \u3092\u77ED\u3044\u671F\u9593\u3068\u57FA\u672C\u30E1\u30C8\u30EA\u30AF\u30B9\u3067\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u306E\u53EF\u7528\u6027\u3092\u78BA\u8A8D`
497
521
  }
498
522
  });
499
523
 
@@ -601,7 +625,17 @@ async function dataFetch(params, path2, init) {
601
625
  // ../connectors/src/connectors/google-analytics/setup-flow.ts
602
626
  var ALL_PROPERTIES = "__ALL_PROPERTIES__";
603
627
  var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
604
- var METADATA_DISPLAY_LIMIT = 30;
628
+ var MAX_PROPERTIES_TO_EXPLORE = 2;
629
+ var TOP_N_SOURCES = 8;
630
+ var TOP_N_COUNTRIES = 8;
631
+ var TOP_N_BROWSERS = 5;
632
+ var TOP_N_PAGES = 10;
633
+ var TOP_N_EVENTS = 10;
634
+ var STANDARD_METADATA_LIMIT = 25;
635
+ var CUSTOM_METADATA_LIMIT = 15;
636
+ var DESCRIPTION_MAX = 140;
637
+ var PAGE_PATH_MAX = 80;
638
+ var EVENT_NAME_MAX = 60;
605
639
  async function listAccountSummaries(params) {
606
640
  const summaries = [];
607
641
  let pageToken;
@@ -624,9 +658,13 @@ function propertyIdFromResource(resource) {
624
658
  return (resource ?? "").replace(/^properties\//, "");
625
659
  }
626
660
  async function getProperty(params, propertyId) {
627
- const res = await adminFetch(params, `/properties/${propertyId}`);
628
- if (!res.ok) return null;
629
- return await res.json();
661
+ try {
662
+ const res = await adminFetch(params, `/properties/${propertyId}`);
663
+ if (!res.ok) return null;
664
+ return await res.json();
665
+ } catch {
666
+ return null;
667
+ }
630
668
  }
631
669
  async function getMetadata(params, propertyId) {
632
670
  try {
@@ -634,41 +672,387 @@ async function getMetadata(params, propertyId) {
634
672
  params,
635
673
  `/properties/${propertyId}/metadata`
636
674
  );
637
- if (!res.ok) return { dimensions: [], metrics: [] };
638
- const data = await res.json();
675
+ if (!res.ok) return null;
676
+ return await res.json();
677
+ } catch {
678
+ return null;
679
+ }
680
+ }
681
+ async function probePropertyAccess(params, propertyId) {
682
+ try {
683
+ const res = await dataFetch(
684
+ params,
685
+ `/properties/${propertyId}:runReport`,
686
+ {
687
+ method: "POST",
688
+ headers: { "Content-Type": "application/json" },
689
+ body: JSON.stringify({
690
+ dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
691
+ metrics: [{ name: "activeUsers" }],
692
+ limit: 1
693
+ })
694
+ }
695
+ );
696
+ if (res.ok) return { ok: true };
697
+ const body = await res.text().catch(() => res.statusText);
698
+ return { ok: false, status: res.status, body };
699
+ } catch (err) {
639
700
  return {
640
- dimensions: data.dimensions ?? [],
641
- metrics: data.metrics ?? []
701
+ ok: false,
702
+ status: "network",
703
+ body: err instanceof Error ? err.message : String(err)
642
704
  };
705
+ }
706
+ }
707
+ async function batchRunReports(params, propertyId, requests) {
708
+ try {
709
+ const res = await dataFetch(
710
+ params,
711
+ `/properties/${propertyId}:batchRunReports`,
712
+ {
713
+ method: "POST",
714
+ headers: { "Content-Type": "application/json" },
715
+ body: JSON.stringify({ requests })
716
+ }
717
+ );
718
+ if (!res.ok) return requests.map(() => ({}));
719
+ const data = await res.json();
720
+ const reports = data.reports ?? [];
721
+ return requests.map((_, i) => reports[i] ?? {});
643
722
  } catch {
644
- return { dimensions: [], metrics: [] };
723
+ return requests.map(() => ({}));
724
+ }
725
+ }
726
+ var LAST_30D = { startDate: "30daysAgo", endDate: "today" };
727
+ var PREV_30D = { startDate: "60daysAgo", endDate: "31daysAgo" };
728
+ var HEADLINE_METRICS = [
729
+ "activeUsers",
730
+ "newUsers",
731
+ "sessions",
732
+ "screenPageViews",
733
+ "engagementRate",
734
+ "averageSessionDuration",
735
+ "keyEvents",
736
+ "totalRevenue"
737
+ ];
738
+ function buildHeadlineRequest() {
739
+ return {
740
+ dateRanges: [
741
+ { ...LAST_30D, name: "current" },
742
+ { ...PREV_30D, name: "previous" }
743
+ ],
744
+ metrics: HEADLINE_METRICS.map((name) => ({ name }))
745
+ };
746
+ }
747
+ function buildTopDimensionRequest(dimension, metric, limit) {
748
+ return {
749
+ dateRanges: [LAST_30D],
750
+ dimensions: [{ name: dimension }],
751
+ metrics: [{ name: metric }],
752
+ limit,
753
+ orderBys: [{ desc: true, metric: { metricName: metric } }]
754
+ };
755
+ }
756
+ function buildTopPagesRequest() {
757
+ return {
758
+ dateRanges: [LAST_30D],
759
+ dimensions: [{ name: "pagePath" }],
760
+ metrics: [
761
+ { name: "screenPageViews" },
762
+ { name: "activeUsers" },
763
+ { name: "averageSessionDuration" }
764
+ ],
765
+ limit: TOP_N_PAGES,
766
+ orderBys: [{ desc: true, metric: { metricName: "screenPageViews" } }]
767
+ };
768
+ }
769
+ function buildTopEventsRequest() {
770
+ return {
771
+ dateRanges: [LAST_30D],
772
+ dimensions: [{ name: "eventName" }],
773
+ metrics: [{ name: "eventCount" }, { name: "totalUsers" }],
774
+ limit: TOP_N_EVENTS,
775
+ orderBys: [{ desc: true, metric: { metricName: "eventCount" } }]
776
+ };
777
+ }
778
+ function escapePipe(s) {
779
+ return s.replace(/\|/g, "\\|");
780
+ }
781
+ function truncate(s, max) {
782
+ if (!s) return "";
783
+ if (s.length <= max) return s;
784
+ return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
785
+ }
786
+ function formatNumber(raw) {
787
+ if (raw == null || raw === "") return "-";
788
+ const n = Number(raw);
789
+ if (!Number.isFinite(n)) return raw;
790
+ if (Number.isInteger(n)) return n.toLocaleString("en-US");
791
+ return n.toLocaleString("en-US", { maximumFractionDigits: 2 });
792
+ }
793
+ function formatPercent(raw) {
794
+ if (raw == null || raw === "") return "-";
795
+ const n = Number(raw);
796
+ if (!Number.isFinite(n)) return raw;
797
+ return `${(n * 100).toFixed(1)}%`;
798
+ }
799
+ function formatDuration(rawSeconds) {
800
+ if (rawSeconds == null || rawSeconds === "") return "-";
801
+ const s = Math.round(Number(rawSeconds));
802
+ if (!Number.isFinite(s)) return rawSeconds;
803
+ if (s < 60) return `${s}s`;
804
+ const mins = Math.floor(s / 60);
805
+ const secs = s % 60;
806
+ return `${mins}m ${secs}s`;
807
+ }
808
+ function deltaArrow(curr, prev) {
809
+ if (!Number.isFinite(prev) || prev === 0) return "";
810
+ const pct = (curr - prev) / prev * 100;
811
+ if (Math.abs(pct) < 0.5) return " (\u2248 flat)";
812
+ const sign = pct > 0 ? "\u2191" : "\u2193";
813
+ return ` (${sign} ${Math.abs(pct).toFixed(1)}% vs prior 30d)`;
814
+ }
815
+ function pivotComparisonReport(report) {
816
+ const out = /* @__PURE__ */ new Map();
817
+ const headers = report.metricHeaders ?? [];
818
+ const rows = report.rows ?? [];
819
+ for (let i = 0; i < headers.length; i++) {
820
+ const name = headers[i]?.name;
821
+ if (!name) continue;
822
+ out.set(name, {
823
+ current: Number(rows[0]?.metricValues?.[i]?.value ?? "0"),
824
+ previous: Number(rows[1]?.metricValues?.[i]?.value ?? "0")
825
+ });
826
+ }
827
+ return out;
828
+ }
829
+ function renderHeadline(sections, report) {
830
+ const m = pivotComparisonReport(report);
831
+ if (m.size === 0) return;
832
+ sections.push("#### Headline metrics (last 30 days)", "");
833
+ const lines = [];
834
+ const push = (label, key, formatter) => {
835
+ const cell = m.get(key);
836
+ if (!cell) return;
837
+ const formatted = formatter(String(cell.current));
838
+ const delta = deltaArrow(cell.current, cell.previous);
839
+ lines.push([label, `${formatted}${delta}`]);
840
+ };
841
+ push("Active users", "activeUsers", formatNumber);
842
+ push("New users", "newUsers", formatNumber);
843
+ push("Sessions", "sessions", formatNumber);
844
+ push("Page / screen views", "screenPageViews", formatNumber);
845
+ push("Engagement rate", "engagementRate", formatPercent);
846
+ push("Avg session duration", "averageSessionDuration", formatDuration);
847
+ push("Key events (conversions)", "keyEvents", formatNumber);
848
+ const rev = m.get("totalRevenue");
849
+ if (rev && rev.current > 0) {
850
+ lines.push([
851
+ "Total revenue",
852
+ `${formatNumber(String(rev.current))}${deltaArrow(rev.current, rev.previous)}`
853
+ ]);
854
+ }
855
+ for (const [k, v] of lines) sections.push(`- **${k}**: ${v}`);
856
+ sections.push("");
857
+ }
858
+ function renderBreakdown(sections, title, blurb, report, topN, metricFormatter = formatNumber) {
859
+ const rows = report.rows ?? [];
860
+ if (rows.length === 0) return;
861
+ const dim = report.dimensionHeaders?.[0]?.name ?? "dimension";
862
+ const metricName = report.metricHeaders?.[0]?.name ?? "metric";
863
+ sections.push(`#### ${title}`, "", blurb, "");
864
+ for (const row of rows.slice(0, topN)) {
865
+ const label = row.dimensionValues?.[0]?.value || `(empty ${dim})`;
866
+ const value = metricFormatter(row.metricValues?.[0]?.value);
867
+ sections.push(`- ${label}: ${value} ${metricName}`);
868
+ }
869
+ sections.push("");
870
+ }
871
+ function renderTopPages(sections, report) {
872
+ const rows = report.rows ?? [];
873
+ if (rows.length === 0) return;
874
+ sections.push(
875
+ `#### Top pages by views (last 30 days)`,
876
+ "",
877
+ "_The most-visited pages \u2014 strong candidates for content-performance dashboards._",
878
+ ""
879
+ );
880
+ sections.push("| Path | Page views | Active users | Avg session duration |");
881
+ sections.push("|------|------------|--------------|----------------------|");
882
+ for (const row of rows.slice(0, TOP_N_PAGES)) {
883
+ const path2 = truncate(row.dimensionValues?.[0]?.value, PAGE_PATH_MAX) || "-";
884
+ sections.push(
885
+ `| ${escapePipe(path2)} | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
886
+ row.metricValues?.[1]?.value
887
+ )} | ${formatDuration(row.metricValues?.[2]?.value)} |`
888
+ );
889
+ }
890
+ sections.push("");
891
+ }
892
+ function renderTopEvents(sections, report) {
893
+ const rows = report.rows ?? [];
894
+ if (rows.length === 0) return;
895
+ sections.push(
896
+ `#### Top events (last 30 days)`,
897
+ "",
898
+ "_Standard auto-collected events (`page_view`, `session_start`, \u2026) plus any custom events the team has set up. The custom ones are usually the most interesting for funnel / conversion dashboards._",
899
+ ""
900
+ );
901
+ sections.push("| Event | Event count | Unique users |");
902
+ sections.push("|-------|-------------|--------------|");
903
+ for (const row of rows.slice(0, TOP_N_EVENTS)) {
904
+ const name = truncate(row.dimensionValues?.[0]?.value, EVENT_NAME_MAX) || "-";
905
+ sections.push(
906
+ `| \`${escapePipe(name)}\` | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
907
+ row.metricValues?.[1]?.value
908
+ )} |`
909
+ );
645
910
  }
911
+ sections.push("");
646
912
  }
647
- function appendMetadataSection(sections, dimensions, metrics) {
648
- if (dimensions.length > 0) {
649
- sections.push(`#### Dimensions (${dimensions.length})`, "");
650
- for (const d of dimensions.slice(0, METADATA_DISPLAY_LIMIT)) {
651
- sections.push(`- ${d.apiName ?? d.uiName ?? "(unknown)"}`);
913
+ function renderMetadata(sections, metadata) {
914
+ const dims = metadata.dimensions ?? [];
915
+ const mets = metadata.metrics ?? [];
916
+ if (dims.length === 0 && mets.length === 0) return;
917
+ const customDims = dims.filter((d) => d.customDefinition);
918
+ const customMets = mets.filter((m) => m.customDefinition);
919
+ const standardDims = dims.filter((d) => !d.customDefinition);
920
+ const standardMets = mets.filter((m) => !m.customDefinition);
921
+ sections.push("#### Available query surface", "");
922
+ sections.push(
923
+ "_Use these in custom `runReport` calls. Custom definitions reveal what the team specifically tracks beyond GA4's defaults._",
924
+ ""
925
+ );
926
+ if (customDims.length > 0 || customMets.length > 0) {
927
+ sections.push(`##### Custom definitions`, "");
928
+ for (const d of customDims.slice(0, CUSTOM_METADATA_LIMIT)) {
929
+ const desc = d.description ? ` \u2014 ${truncate(d.description, DESCRIPTION_MAX)}` : "";
930
+ sections.push(`- dimension \`${d.apiName ?? d.uiName}\`${desc}`);
652
931
  }
653
- if (dimensions.length > METADATA_DISPLAY_LIMIT) {
932
+ if (customDims.length > CUSTOM_METADATA_LIMIT) {
654
933
  sections.push(
655
- `- \u2026and ${dimensions.length - METADATA_DISPLAY_LIMIT} more`
934
+ `- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
656
935
  );
657
936
  }
658
- sections.push("");
659
- }
660
- if (metrics.length > 0) {
661
- sections.push(`#### Metrics (${metrics.length})`, "");
662
- for (const m of metrics.slice(0, METADATA_DISPLAY_LIMIT)) {
663
- sections.push(`- ${m.apiName ?? m.uiName ?? "(unknown)"}`);
937
+ for (const m of customMets.slice(0, CUSTOM_METADATA_LIMIT)) {
938
+ const desc = m.description ? ` \u2014 ${truncate(m.description, DESCRIPTION_MAX)}` : "";
939
+ sections.push(`- metric \`${m.apiName ?? m.uiName}\`${desc}`);
664
940
  }
665
- if (metrics.length > METADATA_DISPLAY_LIMIT) {
941
+ if (customMets.length > CUSTOM_METADATA_LIMIT) {
666
942
  sections.push(
667
- `- \u2026and ${metrics.length - METADATA_DISPLAY_LIMIT} more`
943
+ `- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
668
944
  );
669
945
  }
670
946
  sections.push("");
671
947
  }
948
+ if (standardDims.length > 0) {
949
+ sections.push(`##### Standard dimensions (${standardDims.length})`);
950
+ const names = standardDims.slice(0, STANDARD_METADATA_LIMIT).map((d) => `\`${d.apiName ?? d.uiName}\``).join(", ");
951
+ const overflow = standardDims.length > STANDARD_METADATA_LIMIT ? ` _(+${standardDims.length - STANDARD_METADATA_LIMIT} more)_` : "";
952
+ sections.push(names + overflow, "");
953
+ }
954
+ if (standardMets.length > 0) {
955
+ sections.push(`##### Standard metrics (${standardMets.length})`);
956
+ const names = standardMets.slice(0, STANDARD_METADATA_LIMIT).map((m) => `\`${m.apiName ?? m.uiName}\``).join(", ");
957
+ const overflow = standardMets.length > STANDARD_METADATA_LIMIT ? ` _(+${standardMets.length - STANDARD_METADATA_LIMIT} more)_` : "";
958
+ sections.push(names + overflow, "");
959
+ }
960
+ }
961
+ function renderProbeFailure(sections, propertyId, probe, serviceAccountEmail) {
962
+ sections.push(`#### Connection probe failed`, "");
963
+ if (probe.status === 403) {
964
+ sections.push(
965
+ `_The service account could not access GA4 property \`${propertyId}\`._`,
966
+ "",
967
+ `**Fix**: In GA4 (Admin > Property access management), grant **Viewer** role to the service account below, then re-run setup.`,
968
+ ""
969
+ );
970
+ if (serviceAccountEmail) {
971
+ sections.push(`\`${serviceAccountEmail}\``, "");
972
+ }
973
+ } else if (probe.status === 404) {
974
+ sections.push(
975
+ `_GA4 property \`${propertyId}\` was not found. Double-check the Property ID in **Admin > Property Settings**._`,
976
+ ""
977
+ );
978
+ } else if (probe.status === "network") {
979
+ sections.push(
980
+ `_Could not reach the GA4 Data API: ${probe.body}_`,
981
+ ""
982
+ );
983
+ } else {
984
+ sections.push(
985
+ `_GA4 Data API returned ${probe.status}: ${probe.body.slice(0, 400)}_`,
986
+ ""
987
+ );
988
+ }
989
+ }
990
+ async function exploreProperty(sections, params, propertyId, serviceAccountEmail) {
991
+ const probe = await probePropertyAccess(params, propertyId);
992
+ if (!probe.ok) {
993
+ renderProbeFailure(sections, propertyId, probe, serviceAccountEmail);
994
+ return;
995
+ }
996
+ const batch1Requests = [
997
+ buildHeadlineRequest(),
998
+ buildTopDimensionRequest("sessionSourceMedium", "sessions", TOP_N_SOURCES),
999
+ buildTopDimensionRequest("country", "activeUsers", TOP_N_COUNTRIES),
1000
+ buildTopDimensionRequest("deviceCategory", "sessions", 5),
1001
+ buildTopDimensionRequest("browser", "activeUsers", TOP_N_BROWSERS)
1002
+ ];
1003
+ const batch2Requests = [buildTopPagesRequest(), buildTopEventsRequest()];
1004
+ const [batch1, batch2, metadata] = await Promise.all([
1005
+ batchRunReports(params, propertyId, batch1Requests),
1006
+ batchRunReports(params, propertyId, batch2Requests),
1007
+ getMetadata(params, propertyId)
1008
+ ]);
1009
+ const [headline, sourceMedium, country, device, browser] = batch1;
1010
+ const [topPages, topEvents] = batch2;
1011
+ if (headline) renderHeadline(sections, headline);
1012
+ const hasTrafficSignal = (sourceMedium?.rows?.length ?? 0) + (country?.rows?.length ?? 0) + (device?.rows?.length ?? 0) + (browser?.rows?.length ?? 0) > 0;
1013
+ if (hasTrafficSignal) {
1014
+ sections.push(
1015
+ "#### Traffic mix (last 30 days)",
1016
+ "",
1017
+ "_Where users are coming from \u2014 useful for dashboards that segment by acquisition channel, geography, or device._",
1018
+ ""
1019
+ );
1020
+ if (sourceMedium)
1021
+ renderBreakdown(
1022
+ sections,
1023
+ "Top source / medium",
1024
+ "_Acquisition channels driving sessions._",
1025
+ sourceMedium,
1026
+ TOP_N_SOURCES
1027
+ );
1028
+ if (country)
1029
+ renderBreakdown(
1030
+ sections,
1031
+ "Top countries by active users",
1032
+ "_Geography \u2014 informs which date/time filters and locale segments to expose._",
1033
+ country,
1034
+ TOP_N_COUNTRIES
1035
+ );
1036
+ if (device)
1037
+ renderBreakdown(
1038
+ sections,
1039
+ "Device categories",
1040
+ "_Desktop / mobile / tablet split \u2014 informs responsive cuts._",
1041
+ device,
1042
+ 5
1043
+ );
1044
+ if (browser)
1045
+ renderBreakdown(
1046
+ sections,
1047
+ "Top browsers",
1048
+ "_Browser distribution \u2014 informs compatibility dashboards or QA filters._",
1049
+ browser,
1050
+ TOP_N_BROWSERS
1051
+ );
1052
+ }
1053
+ if (topPages) renderTopPages(sections, topPages);
1054
+ if (topEvents) renderTopEvents(sections, topEvents);
1055
+ if (metadata) renderMetadata(sections, metadata);
672
1056
  }
673
1057
  var googleAnalyticsSetupFlow = {
674
1058
  initialState: () => ({}),
@@ -737,8 +1121,8 @@ var googleAnalyticsSetupFlow = {
737
1121
  slug: "manualPropertyId",
738
1122
  type: "text",
739
1123
  question: {
740
- ja: "GA4 \u30D7\u30ED\u30D1\u30C6\u30A3 ID \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: 123456789\uFF09\u3002GA4 \u7BA1\u7406\u753B\u9762 > \u30D7\u30ED\u30D1\u30C6\u30A3\u8A2D\u5B9A\u3067\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002",
741
- en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings."
1124
+ ja: "GA4 \u30D7\u30ED\u30D1\u30C6\u30A3 ID \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: 123456789\uFF09\u3002GA4 \u7BA1\u7406\u753B\u9762 > \u30D7\u30ED\u30D1\u30C6\u30A3\u8A2D\u5B9A\u3067\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002\u5165\u529B\u5F8C\u3001\u30C7\u30FC\u30BF\u53D6\u5F97\u3092\u8A66\u3057\u3066\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u63D0\u6848\u7528\u306E\u30B5\u30DE\u30EA\u3092\u751F\u6210\u3057\u307E\u3059\u3002",
1125
+ en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings. After entry we'll probe the property via the Data API and assemble a dashboard-ready summary."
742
1126
  },
743
1127
  async fetchOptions(state) {
744
1128
  if (state.properties?.length) return [];
@@ -752,85 +1136,107 @@ var googleAnalyticsSetupFlow = {
752
1136
  ],
753
1137
  async finalize(state, rt) {
754
1138
  const sections = ["## Google Analytics", ""];
1139
+ let serviceAccountEmail = null;
1140
+ try {
1141
+ const keyJsonBase64 = rt.params[parameters.serviceAccountKeyJsonBase64.slug];
1142
+ if (keyJsonBase64) {
1143
+ serviceAccountEmail = decodeServiceAccount(keyJsonBase64).client_email ?? null;
1144
+ }
1145
+ } catch {
1146
+ }
1147
+ let targetPropertyIds = [];
1148
+ let accountLabel = null;
1149
+ let accountSummary;
755
1150
  if (state.account && state.properties) {
756
- let summaries = [];
757
1151
  try {
758
- summaries = await listAccountSummaries(rt.params);
1152
+ const summaries = await listAccountSummaries(rt.params);
1153
+ accountSummary = summaries.find(
1154
+ (s) => (s.account ?? s.name) === state.account
1155
+ );
1156
+ accountLabel = accountSummary?.displayName ?? state.account.replace(/^accounts\//, "");
759
1157
  } catch {
1158
+ accountLabel = state.account.replace(/^accounts\//, "");
760
1159
  }
761
- const account = summaries.find(
762
- (s) => (s.account ?? s.name) === state.account
763
- );
764
- const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
765
- const targetPropertyIds = await resolveSetupSelection({
1160
+ targetPropertyIds = await resolveSetupSelection({
766
1161
  selected: state.properties,
767
1162
  allSentinel: ALL_PROPERTIES,
768
- fetchAll: async () => (account?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
1163
+ fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
769
1164
  limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
770
1165
  });
771
- sections.push(`### Account: ${accountLabel}`, "");
772
- if (targetPropertyIds.length === 0) {
773
- sections.push("_No properties selected._", "");
774
- return sections.join("\n");
775
- }
776
- sections.push(
777
- "| Property ID | Display Name | Time Zone | Currency | Industry |"
778
- );
1166
+ } else if (state.manualPropertyId) {
1167
+ targetPropertyIds = [state.manualPropertyId];
1168
+ }
1169
+ if (targetPropertyIds.length === 0) {
779
1170
  sections.push(
780
- "|-------------|--------------|-----------|----------|----------|"
1171
+ "_Could not identify a target GA4 property. Either enable the Google Analytics Admin API on the service account's GCP project, or re-run setup and provide the Property ID manually._",
1172
+ ""
781
1173
  );
782
- for (const pid of targetPropertyIds) {
783
- const prop = await getProperty(rt.params, pid).catch(() => null);
784
- const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
785
- const timeZone = prop?.timeZone ?? "-";
786
- const currency = prop?.currencyCode ?? "-";
787
- const industry = (prop?.industryCategory ?? "-").replace(
788
- /\|/g,
789
- "\\|"
790
- );
1174
+ if (serviceAccountEmail) {
791
1175
  sections.push(
792
- `| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
793
- );
794
- }
795
- sections.push("");
796
- const firstPropertyId = targetPropertyIds[0];
797
- if (firstPropertyId) {
798
- const { dimensions, metrics } = await getMetadata(
799
- rt.params,
800
- firstPropertyId
1176
+ `_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
1177
+ ""
801
1178
  );
802
- appendMetadataSection(sections, dimensions, metrics);
803
1179
  }
804
1180
  return sections.join("\n");
805
1181
  }
806
- const propertyId = state.manualPropertyId;
807
- if (propertyId) {
808
- sections.push(`### Property: ${propertyId}`, "");
809
- const { dimensions, metrics } = await getMetadata(
810
- rt.params,
811
- propertyId
812
- );
813
- appendMetadataSection(sections, dimensions, metrics);
814
- return sections.join("\n");
815
- }
816
1182
  sections.push(
817
- "_Could not list GA4 accounts. Please enable the Google Analytics Admin API in your GCP project. Property ID can be specified per request at runtime._",
1183
+ "_The downstream coding agent should use this summary as the canonical baseline of the property's traffic shape for dashboard / app proposals. Headline metrics, traffic mix, top pages, top events, and custom dimensions/metrics together cover the most common dashboarding patterns; reach for the standard query surface for anything custom._",
818
1184
  ""
819
1185
  );
1186
+ if (accountLabel) sections.push(`### Account: ${accountLabel}`, "");
1187
+ sections.push("### Properties", "");
1188
+ sections.push(
1189
+ "| Property ID | Display Name | Time Zone | Currency | Industry |"
1190
+ );
1191
+ sections.push(
1192
+ "|-------------|--------------|-----------|----------|----------|"
1193
+ );
1194
+ const properties = await Promise.all(
1195
+ targetPropertyIds.map((id) => getProperty(rt.params, id))
1196
+ );
1197
+ for (let i = 0; i < targetPropertyIds.length; i++) {
1198
+ const pid = targetPropertyIds[i];
1199
+ const prop = properties[i];
1200
+ const displayName = escapePipe(prop?.displayName ?? "-");
1201
+ const timeZone = prop?.timeZone ?? "-";
1202
+ const currency = prop?.currencyCode ?? "-";
1203
+ const industry = escapePipe(prop?.industryCategory ?? "-");
1204
+ sections.push(
1205
+ `| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
1206
+ );
1207
+ }
1208
+ sections.push("");
1209
+ const propertiesToExplore = targetPropertyIds.slice(
1210
+ 0,
1211
+ MAX_PROPERTIES_TO_EXPLORE
1212
+ );
1213
+ for (let i = 0; i < propertiesToExplore.length; i++) {
1214
+ const pid = propertiesToExplore[i];
1215
+ const prop = properties[i];
1216
+ const heading = prop?.displayName && prop.displayName !== "-" ? `### Data exploration: ${prop.displayName} (${pid})` : `### Data exploration: property ${pid}`;
1217
+ sections.push(heading, "");
1218
+ await exploreProperty(sections, rt.params, pid, serviceAccountEmail);
1219
+ }
1220
+ if (targetPropertyIds.length > propertiesToExplore.length) {
1221
+ const skipped = targetPropertyIds.length - propertiesToExplore.length;
1222
+ sections.push(
1223
+ `_Deep exploration limited to the first ${propertiesToExplore.length} of ${targetPropertyIds.length} properties to keep this summary bounded. The remaining ${skipped} can be explored on demand via \`connector_google-analytics-service-account_request\`._`,
1224
+ ""
1225
+ );
1226
+ }
820
1227
  return sections.join("\n");
821
1228
  }
822
1229
  };
823
1230
 
824
1231
  // ../connectors/src/connectors/google-analytics/tools/request.ts
825
1232
  import { z } from "zod";
826
- var BASE_URL2 = "https://analyticsdata.googleapis.com/v1beta/";
1233
+ var BASE_URL2 = "https://analyticsdata.googleapis.com";
827
1234
  var REQUEST_TIMEOUT_MS = 6e4;
828
1235
  var inputSchema = z.object({
829
1236
  toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
830
1237
  connectionId: z.string().describe("ID of the Google Analytics connection to use"),
831
- propertyId: z.string().describe("GA4 property ID (e.g., '123456789')"),
832
1238
  method: z.enum(["GET", "POST"]).describe("HTTP method"),
833
- path: z.string().describe("API path (e.g., 'properties/{propertyId}:runReport'). {propertyId} is replaced with the propertyId parameter."),
1239
+ path: z.string().describe("Full request path including the API version prefix, appended to https://analyticsdata.googleapis.com. Use the same form as the GA4 Data API reference, e.g., '/v1beta/properties/123456789:runReport'."),
834
1240
  body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
835
1241
  });
836
1242
  var outputSchema = z.discriminatedUnion("success", [
@@ -847,11 +1253,10 @@ var outputSchema = z.discriminatedUnion("success", [
847
1253
  var requestTool = new ConnectorTool({
848
1254
  name: "request",
849
1255
  description: `Send authenticated requests to the Google Analytics Data API.
850
- Authentication is handled automatically using a service account.
851
- {propertyId} in the path is automatically replaced with the propertyId parameter.`,
1256
+ Authentication is handled automatically using a service account.`,
852
1257
  inputSchema,
853
1258
  outputSchema,
854
- async execute({ connectionId, propertyId, method, path: path2, body }, connections) {
1259
+ async execute({ connectionId, method, path: path2, body }, connections) {
855
1260
  const connection2 = connections.find((c) => c.id === connectionId);
856
1261
  if (!connection2) {
857
1262
  return { success: false, error: `Connection ${connectionId} not found` };
@@ -871,8 +1276,7 @@ Authentication is handled automatically using a service account.
871
1276
  if (!token) {
872
1277
  return { success: false, error: "Failed to obtain access token" };
873
1278
  }
874
- const resolvedPath = path2.replace(/\{propertyId\}/g, propertyId);
875
- const url = `${BASE_URL2}${resolvedPath}`;
1279
+ const url = `${BASE_URL2}${path2}`;
876
1280
  const controller = new AbortController();
877
1281
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
878
1282
  try {
@@ -919,7 +1323,7 @@ var googleAnalyticsConnector = new ConnectorPlugin({
919
1323
  systemPrompt: {
920
1324
  en: `### Tools
921
1325
 
922
- - \`google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. Requires a \`propertyId\` parameter. See the GA4 Data API Reference below for available endpoints and request bodies.
1326
+ - \`connector_google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. See the GA4 Data API Reference below for available endpoints and request bodies.
923
1327
 
924
1328
  ### Business Logic
925
1329
 
@@ -968,8 +1372,8 @@ export default async function handler(c: Context) {
968
1372
 
969
1373
  Common operations:
970
1374
 
971
- - **Get metadata** (list available dimensions/metrics for a property): \`GET properties/{propertyId}/metadata\`
972
- - **Run report** (query analytics data): \`POST properties/{propertyId}:runReport\` with a body like:
1375
+ - **Get metadata** (list available dimensions/metrics for a property): \`GET /v1beta/properties/{propertyId}/metadata\`
1376
+ - **Run report** (query analytics data): \`POST /v1beta/properties/{propertyId}:runReport\` with a body like:
973
1377
  \`\`\`json
974
1378
  {
975
1379
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -978,7 +1382,7 @@ Common operations:
978
1382
  "limit": 100
979
1383
  }
980
1384
  \`\`\`
981
- - **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
1385
+ - **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
982
1386
 
983
1387
  #### Common dimensions
984
1388
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
@@ -991,7 +1395,7 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
991
1395
  - Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
992
1396
  ja: `### \u30C4\u30FC\u30EB
993
1397
 
994
- - \`google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\`propertyId\` \u30D1\u30E9\u30E1\u30FC\u30BF\u30FC\u304C\u5FC5\u8981\u3067\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
1398
+ - \`connector_google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
995
1399
 
996
1400
  ### Business Logic
997
1401
 
@@ -1040,8 +1444,8 @@ export default async function handler(c: Context) {
1040
1444
 
1041
1445
  \u4E3B\u306A\u64CD\u4F5C:
1042
1446
 
1043
- - **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET properties/{propertyId}/metadata\`
1044
- - **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
1447
+ - **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET /v1beta/properties/{propertyId}/metadata\`
1448
+ - **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST /v1beta/properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
1045
1449
  \`\`\`json
1046
1450
  {
1047
1451
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -1050,7 +1454,7 @@ export default async function handler(c: Context) {
1050
1454
  "limit": 100
1051
1455
  }
1052
1456
  \`\`\`
1053
- - **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST properties/{propertyId}:runRealtimeReport\`
1457
+ - **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
1054
1458
 
1055
1459
  #### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
1056
1460
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
@@ -1072,8 +1476,22 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
1072
1476
  error: "google-analytics: missing service account key"
1073
1477
  };
1074
1478
  }
1479
+ const propertyId = params[parameters.propertyId.slug];
1480
+ if (!propertyId) {
1481
+ try {
1482
+ const sa = decodeServiceAccount(keyJsonBase64);
1483
+ await getAccessToken(sa);
1484
+ return { success: true };
1485
+ } catch (err) {
1486
+ const msg = err instanceof Error ? err.message : String(err);
1487
+ return { success: false, error: msg };
1488
+ }
1489
+ }
1075
1490
  try {
1076
- const res = await dataFetch(params, `/metadata`);
1491
+ const res = await dataFetch(
1492
+ params,
1493
+ `/properties/${propertyId}/metadata`
1494
+ );
1077
1495
  if (!res.ok) {
1078
1496
  const body = await res.text().catch(() => res.statusText);
1079
1497
  return {