@ainyc/canonry 4.96.0 → 4.98.0

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 (31) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +8 -3
  2. package/assets/assets/{BacklinksPage-BiuaXwM3.js → BacklinksPage-CuWs7I1D.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-C7jCdApm.js → ChartPrimitives-bm37tprp.js} +1 -1
  4. package/assets/assets/ProjectPage-BksYe8FP.js +6 -0
  5. package/assets/assets/{RunRow-DLn-qaBh.js → RunRow-DzhQl87g.js} +1 -1
  6. package/assets/assets/{RunsPage-BArLwd1T.js → RunsPage-wOaewhbF.js} +1 -1
  7. package/assets/assets/{SettingsPage-BJmzcMsh.js → SettingsPage-CJdhhkkE.js} +1 -1
  8. package/assets/assets/{TrafficPage-DqG9hcNu.js → TrafficPage-BJBeN5Ph.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-yW5pdgrM.js → TrafficSourceDetailPage-D3PmH0B1.js} +1 -1
  10. package/assets/assets/{arrow-left-BUhA2bZu.js → arrow-left-BmITMI6O.js} +1 -1
  11. package/assets/assets/{extract-error-message-D2vzFSUQ.js → extract-error-message-BGwnyAfP.js} +1 -1
  12. package/assets/assets/index-BSX6r6ju.js +210 -0
  13. package/assets/assets/{index-DiykVUPr.css → index-BsZNCwg8.css} +1 -1
  14. package/assets/assets/{trash-2-C94CCTsF.js → trash-2-Bd2mkP_w.js} +1 -1
  15. package/assets/assets/{vendor-markdown-DK7fbRNb.js → vendor-markdown-6dwjPOKK.js} +1 -1
  16. package/assets/assets/{vendor-radix-B57xfQbP.js → vendor-radix-CQ77EO2y.js} +1 -1
  17. package/assets/assets/{vendor-recharts-ClRVR6aX.js → vendor-recharts-C9EZkgbP.js} +1 -1
  18. package/assets/assets/vendor-tanstack-Be8JdV5M.js +1 -0
  19. package/assets/index.html +6 -6
  20. package/dist/{chunk-IFB43PRW.js → chunk-3TH54726.js} +15 -7
  21. package/dist/{chunk-ROLTZPV7.js → chunk-7COFPNMH.js} +115 -11
  22. package/dist/{chunk-CEYDMSQO.js → chunk-CWFTMFEU.js} +58 -10
  23. package/dist/{chunk-CNTBEUVE.js → chunk-DJVUNURL.js} +140 -61
  24. package/dist/cli.js +19 -6
  25. package/dist/index.js +4 -4
  26. package/dist/{intelligence-service-SJKUVFZ6.js → intelligence-service-QHBXW4KI.js} +2 -2
  27. package/dist/mcp.js +2 -2
  28. package/package.json +9 -9
  29. package/assets/assets/ProjectPage-BHo6vIlj.js +0 -6
  30. package/assets/assets/index-afT0D_K8.js +0 -210
  31. package/assets/assets/vendor-tanstack-Dq7p98wZ.js +0 -1
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-IFB43PRW.js";
12
+ } from "./chunk-3TH54726.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -111,7 +111,7 @@ import {
111
111
  siteAuditPages,
112
112
  siteAuditSnapshots,
113
113
  usageCounters
114
- } from "./chunk-CNTBEUVE.js";
114
+ } from "./chunk-DJVUNURL.js";
115
115
  import {
116
116
  AGENT_MEMORY_VALUE_MAX_BYTES,
117
117
  AGENT_PROVIDER_IDS,
@@ -171,7 +171,7 @@ import {
171
171
  validationError,
172
172
  winnabilityClassLabel,
173
173
  withRetry
174
- } from "./chunk-CEYDMSQO.js";
174
+ } from "./chunk-CWFTMFEU.js";
175
175
 
176
176
  // src/telemetry.ts
177
177
  import crypto from "crypto";
@@ -4377,8 +4377,8 @@ function removeOpenAiAdsConnection(config, projectName) {
4377
4377
 
4378
4378
  // src/ads-sync.ts
4379
4379
  var log4 = createLogger("AdsSync");
4380
- var CAMPAIGN_INSIGHT_FIELDS = ["campaign.impressions", "campaign.clicks", "campaign.spend", "metadata.readable_time"];
4381
- var AD_GROUP_INSIGHT_FIELDS = ["ad_group.impressions", "ad_group.clicks", "ad_group.spend", "metadata.readable_time"];
4380
+ var CAMPAIGN_INSIGHT_FIELDS = ["campaign.impressions", "campaign.clicks", "campaign.spend", "campaign.conversions", "metadata.readable_time"];
4381
+ var AD_GROUP_INSIGHT_FIELDS = ["ad_group.impressions", "ad_group.clicks", "ad_group.spend", "ad_group.conversions", "metadata.readable_time"];
4382
4382
  function toInsightUpserts(level, entityId, rows) {
4383
4383
  const upserts = [];
4384
4384
  for (const row of rows) {
@@ -4392,7 +4392,8 @@ function toInsightUpserts(level, entityId, rows) {
4392
4392
  date: row.readable_time,
4393
4393
  impressions: row.impressions ?? 0,
4394
4394
  clicks: row.clicks ?? 0,
4395
- spendMicros: dollarsToMicros(row.spend ?? 0)
4395
+ spendMicros: dollarsToMicros(row.spend ?? 0),
4396
+ conversions: Math.round(row.conversions ?? 0)
4396
4397
  });
4397
4398
  }
4398
4399
  return upserts;
@@ -4443,6 +4444,9 @@ async function executeAdsSync(db, runId, projectId, opts) {
4443
4444
  log4.error("campaign.failed", { runId, campaignId: campaign.id, error: err instanceof Error ? err.message : String(err) });
4444
4445
  }
4445
4446
  }
4447
+ const conversionTrackingConfigured = campaigns.some(
4448
+ (c) => (c.conversion_event_setting_ids?.length ?? 0) > 0
4449
+ );
4446
4450
  const insertNow = (/* @__PURE__ */ new Date()).toISOString();
4447
4451
  db.transaction((tx) => {
4448
4452
  tx.delete(adsCampaigns).where(eq4(adsCampaigns.projectId, projectId)).run();
@@ -4505,6 +4509,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
4505
4509
  impressions: upsert.impressions,
4506
4510
  clicks: upsert.clicks,
4507
4511
  spendMicros: upsert.spendMicros,
4512
+ conversions: upsert.conversions,
4508
4513
  syncRunId: runId
4509
4514
  }
4510
4515
  }).run();
@@ -4515,6 +4520,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
4515
4520
  currencyCode: account.currency_code,
4516
4521
  timezone: account.timezone,
4517
4522
  status: account.status,
4523
+ conversionTrackingConfigured,
4518
4524
  lastSyncedAt: insertNow,
4519
4525
  updatedAt: insertNow
4520
4526
  }).where(eq4(adsConnections.projectId, projectId)).run();
@@ -6396,7 +6402,7 @@ function readStoredGroundingSources(rawResponse) {
6396
6402
  return result;
6397
6403
  }
6398
6404
  async function backfillInsightsCommand(project, opts) {
6399
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-SJKUVFZ6.js");
6405
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-QHBXW4KI.js");
6400
6406
  const config = loadConfig();
6401
6407
  const db = createClient(config.database);
6402
6408
  migrate(db);
@@ -7982,7 +7988,10 @@ import { Agent } from "@mariozechner/pi-agent-core";
7982
7988
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
7983
7989
 
7984
7990
  // src/agent/providers.ts
7985
- import { getEnvApiKey, getModel } from "@mariozechner/pi-ai";
7991
+ import {
7992
+ getEnvApiKey,
7993
+ getModel
7994
+ } from "@mariozechner/pi-ai";
7986
7995
  var PROVIDER_MODELS = {
7987
7996
  [AgentProviderIds.claude]: {
7988
7997
  [LlmCapabilities.agent]: "claude-opus-4-7",
@@ -8008,6 +8017,14 @@ var PROVIDER_MODELS = {
8008
8017
  [LlmCapabilities.agent]: "glm-5.1",
8009
8018
  [LlmCapabilities.analyze]: "glm-5-turbo",
8010
8019
  [LlmCapabilities.classify]: "glm-5-turbo"
8020
+ },
8021
+ [AgentProviderIds.deepinfra]: {
8022
+ // DeepInfra `org/Model` slugs. GLM-5.2 drives the agent loop; the much
8023
+ // cheaper DeepSeek-V4-Flash fills analyze + classify. Resolved into a
8024
+ // custom openai-completions model, not pi-ai's catalog.
8025
+ [LlmCapabilities.agent]: "zai-org/GLM-5.2",
8026
+ [LlmCapabilities.analyze]: "deepseek-ai/DeepSeek-V4-Flash",
8027
+ [LlmCapabilities.classify]: "deepseek-ai/DeepSeek-V4-Flash"
8011
8028
  }
8012
8029
  };
8013
8030
  var AGENT_PROVIDERS = {
@@ -8034,6 +8051,62 @@ var AGENT_PROVIDERS = {
8034
8051
  label: "Z.ai (GLM)",
8035
8052
  defaultModel: PROVIDER_MODELS[AgentProviderIds.zai][LlmCapabilities.agent],
8036
8053
  autoDetectPriority: 3
8054
+ },
8055
+ [AgentProviderIds.deepinfra]: {
8056
+ // `piAiProvider` is the canonical id here (not a pi-ai vendor) — it's the
8057
+ // `model.provider` string the agent loop hands to `getApiKey`, which
8058
+ // `resolveAgentId` maps straight back to the deepinfra config/env key.
8059
+ piAiProvider: AgentProviderIds.deepinfra,
8060
+ label: "DeepInfra (GLM / DeepSeek)",
8061
+ defaultModel: PROVIDER_MODELS[AgentProviderIds.deepinfra][LlmCapabilities.agent],
8062
+ autoDetectPriority: 4,
8063
+ openaiCompatible: {
8064
+ baseUrl: "https://api.deepinfra.com/v1/openai",
8065
+ baseUrlEnvVar: "DEEPINFRA_BASE_URL",
8066
+ apiKeyEnvVar: "DEEPINFRA_TOKEN",
8067
+ // DeepInfra serves open-weight models (GLM, DeepSeek) behind an
8068
+ // OpenAI-compatible vLLM endpoint. pi-ai's `detectCompat` has no rule
8069
+ // for api.deepinfra.com, so it would apply the strict OpenAI profile;
8070
+ // we set the same open-model profile pi-ai uses for deepseek.com /
8071
+ // cerebras / z.ai. `developer` role and `reasoning_effort` are
8072
+ // OpenAI-platform-isms these models reject, `store` is response
8073
+ // persistence DeepInfra doesn't implement, and DeepInfra documents
8074
+ // `max_tokens` (not `max_completion_tokens`) on its OpenAI route.
8075
+ compat: {
8076
+ supportsStore: false,
8077
+ supportsDeveloperRole: false,
8078
+ supportsReasoningEffort: false,
8079
+ maxTokensField: "max_tokens"
8080
+ },
8081
+ // Best-effort metadata. Costs are USD/1M tokens (DeepInfra published
8082
+ // rates: GLM-5.2 ~$0.95 in / $0.18 cached / $3.00 out; DeepSeek-V4-Flash
8083
+ // ~$0.10 in / $0.20 out). contextWindow is DeepInfra's 1M (fp4) serving
8084
+ // window for both models; it's descriptive (see OpenAiCompatibleModelMeta),
8085
+ // so it documents the real window rather than gating compaction.
8086
+ knownModels: {
8087
+ "zai-org/GLM-5.2": {
8088
+ contextWindow: 1048576,
8089
+ maxTokens: 98304,
8090
+ reasoning: true,
8091
+ cost: { input: 0.95, output: 3, cacheRead: 0.18, cacheWrite: 0 }
8092
+ },
8093
+ "deepseek-ai/DeepSeek-V4-Flash": {
8094
+ contextWindow: 1048576,
8095
+ maxTokens: 32768,
8096
+ reasoning: false,
8097
+ cost: { input: 0.1, output: 0.2, cacheRead: 0, cacheWrite: 0 }
8098
+ }
8099
+ },
8100
+ // Fallback for arbitrary `--model` slugs we don't ship as tiers. cost is
8101
+ // 0, so an unknown slug isn't cost-tracked by pi-ai's `calculateCost`;
8102
+ // contextWindow stays conservative since we can't know the slug's window.
8103
+ defaultModelMeta: {
8104
+ contextWindow: 131072,
8105
+ maxTokens: 32768,
8106
+ reasoning: false,
8107
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
8108
+ }
8109
+ }
8037
8110
  }
8038
8111
  };
8039
8112
  function agentProvidersByPriority() {
@@ -8055,6 +8128,9 @@ function resolveModelForProvider(provider, modelId) {
8055
8128
  function resolveModelForCapability(provider, capability, modelIdOverride) {
8056
8129
  const entry = AGENT_PROVIDERS[provider];
8057
8130
  const id = modelIdOverride ?? PROVIDER_MODELS[provider][capability];
8131
+ if (entry.openaiCompatible) {
8132
+ return buildOpenAiCompatibleModel(entry, id);
8133
+ }
8058
8134
  const model = getModel(entry.piAiProvider, id);
8059
8135
  if (!model) {
8060
8136
  throw new Error(
@@ -8063,6 +8139,27 @@ function resolveModelForCapability(provider, capability, modelIdOverride) {
8063
8139
  }
8064
8140
  return model;
8065
8141
  }
8142
+ function buildOpenAiCompatibleModel(entry, id) {
8143
+ const host = entry.openaiCompatible;
8144
+ if (!host) {
8145
+ throw new Error(`buildOpenAiCompatibleModel called for non-custom provider '${entry.piAiProvider}'`);
8146
+ }
8147
+ const meta = host.knownModels[id] ?? host.defaultModelMeta;
8148
+ const baseUrl = (host.baseUrlEnvVar ? process.env[host.baseUrlEnvVar] : void 0) || host.baseUrl;
8149
+ return {
8150
+ id,
8151
+ name: id,
8152
+ api: "openai-completions",
8153
+ provider: entry.piAiProvider,
8154
+ baseUrl,
8155
+ reasoning: meta.reasoning,
8156
+ input: ["text"],
8157
+ cost: meta.cost,
8158
+ contextWindow: meta.contextWindow,
8159
+ maxTokens: meta.maxTokens,
8160
+ ...host.compat ? { compat: host.compat } : {}
8161
+ };
8162
+ }
8066
8163
  function validateAgentProviderRegistry() {
8067
8164
  for (const provider of listAgentProviders()) {
8068
8165
  for (const capability of LLM_CAPABILITIES) {
@@ -8086,10 +8183,14 @@ function resolveApiKeySource(providerOrPiAi, config) {
8086
8183
  const entry = AGENT_PROVIDERS[id];
8087
8184
  const fromConfig = config.providers?.[id]?.apiKey;
8088
8185
  if (fromConfig) return { key: fromConfig, source: "config" };
8089
- const fromEnv = getEnvApiKey(entry.piAiProvider);
8186
+ const fromEnv = entry.openaiCompatible ? process.env[entry.openaiCompatible.apiKeyEnvVar] : getEnvApiKey(entry.piAiProvider);
8090
8187
  if (fromEnv) return { key: fromEnv, source: "env" };
8091
8188
  return void 0;
8092
8189
  }
8190
+ function agentProviderApiKeyEnvVar(id) {
8191
+ const entry = AGENT_PROVIDERS[id];
8192
+ return entry.openaiCompatible ? entry.openaiCompatible.apiKeyEnvVar : `${entry.piAiProvider.toUpperCase()}_API_KEY`;
8193
+ }
8093
8194
  function resolveAgentId(providerOrPiAi) {
8094
8195
  if (isAgentProviderId(providerOrPiAi)) return providerOrPiAi;
8095
8196
  for (const id of AGENT_PROVIDER_IDS) {
@@ -8313,7 +8414,7 @@ ${skillBody}`;
8313
8414
  }
8314
8415
  function missingProviderMessage() {
8315
8416
  const configHints = agentProvidersByPriority().join(", ");
8316
- const envHints = agentProvidersByPriority().map((p) => `${AGENT_PROVIDERS[p].piAiProvider.toUpperCase()}_API_KEY`).join(" / ");
8417
+ const envHints = agentProvidersByPriority().map(agentProviderApiKeyEnvVar).join(" / ");
8317
8418
  return `No agent LLM provider configured. Add an API key for one of: ${configHints} in ~/.canonry/config.yaml, or export ${envHints}.`;
8318
8419
  }
8319
8420
  function detectAgentProvider(config) {
@@ -9267,7 +9368,7 @@ function pickExplainProvider(config, providerOverride) {
9267
9368
  for (const provider of agentProvidersByPriority()) {
9268
9369
  if (resolveApiKeyFor(provider, config)) return provider;
9269
9370
  }
9270
- const hints = agentProvidersByPriority().map((p) => `${AGENT_PROVIDERS[p].piAiProvider.toUpperCase()}_API_KEY`).join(" / ");
9371
+ const hints = agentProvidersByPriority().map(agentProviderApiKeyEnvVar).join(" / ");
9271
9372
  throw providerError(
9272
9373
  `No LLM provider configured. Add an API key in ~/.canonry/config.yaml or set one of: ${hints}.`
9273
9374
  );
@@ -10886,6 +10987,9 @@ async function createServer(opts) {
10886
10987
  },
10887
10988
  getGoogleAuthConfig: () => getGoogleAuthConfig(opts.config),
10888
10989
  getPlacesConfig: () => getPlacesConfig(opts.config),
10990
+ // Resolved fresh each call so a key added at runtime (settings API) shows
10991
+ // up immediately in the `config.agent-providers` doctor check.
10992
+ getAgentProviderSummary: () => buildAgentProvidersResponse(opts.config).providers,
10889
10993
  googleConnectionStore,
10890
10994
  googleStateSecret,
10891
10995
  publicUrl: opts.config.publicUrl,
@@ -1434,7 +1434,8 @@ var ProviderIds = {
1434
1434
  perplexity: "perplexity",
1435
1435
  local: "local",
1436
1436
  cdpChatgpt: "cdp:chatgpt",
1437
- zai: "zai"
1437
+ zai: "zai",
1438
+ deepinfra: "deepinfra"
1438
1439
  };
1439
1440
  var PROVIDER_IDS = Object.values(ProviderIds);
1440
1441
  var SweepProviderIds = {
@@ -1450,7 +1451,8 @@ var AgentProviderIds = {
1450
1451
  claude: ProviderIds.claude,
1451
1452
  openai: ProviderIds.openai,
1452
1453
  gemini: ProviderIds.gemini,
1453
- zai: ProviderIds.zai
1454
+ zai: ProviderIds.zai,
1455
+ deepinfra: ProviderIds.deepinfra
1454
1456
  };
1455
1457
  var AGENT_PROVIDER_IDS = Object.values(AgentProviderIds);
1456
1458
  function isAgentProviderId(value) {
@@ -3027,7 +3029,7 @@ function escapeRegExp(value) {
3027
3029
 
3028
3030
  // ../contracts/src/agent.ts
3029
3031
  import { z as z22 } from "zod";
3030
- var agentProviderIdSchema = z22.enum(["claude", "openai", "gemini", "zai"]);
3032
+ var agentProviderIdSchema = z22.enum(["claude", "openai", "gemini", "zai", "deepinfra"]);
3031
3033
  var agentProviderOptionDtoSchema = z22.object({
3032
3034
  /** Stable identifier — what clients pass back as `provider` on the prompt endpoint. */
3033
3035
  id: agentProviderIdSchema,
@@ -3850,6 +3852,23 @@ function citationStateToCited(state) {
3850
3852
 
3851
3853
  // ../contracts/src/report.ts
3852
3854
  import { z as z28 } from "zod";
3855
+ var REPORT_PERIOD_OPTIONS = [7, 14, 30, 90];
3856
+ var REPORT_DEFAULT_PERIOD_DAYS = 30;
3857
+ var reportPeriodSchema = z28.union([z28.literal(7), z28.literal(14), z28.literal(30), z28.literal(90)]).describe("Report window in days (7, 14, 30, or 90). Defaults to 30 when omitted.");
3858
+ function isReportPeriodDays(n) {
3859
+ return REPORT_PERIOD_OPTIONS.includes(n);
3860
+ }
3861
+ function parseReportPeriodDays(value) {
3862
+ if (value === void 0 || value === null || value === "") return REPORT_DEFAULT_PERIOD_DAYS;
3863
+ const n = typeof value === "number" ? value : Number(value);
3864
+ if (!Number.isInteger(n) || !isReportPeriodDays(n)) {
3865
+ throw validationError(`"period" must be one of ${REPORT_PERIOD_OPTIONS.join(", ")}`);
3866
+ }
3867
+ return n;
3868
+ }
3869
+ function reportComparisonWindowDays(periodDays) {
3870
+ return Math.max(1, Math.floor(periodDays / 2));
3871
+ }
3853
3872
  var providerLocationTreatmentSchema = z28.enum([
3854
3873
  "prompt",
3855
3874
  "request-param",
@@ -3907,7 +3926,14 @@ var reportMetaSchema = z28.object({
3907
3926
  /** Earliest data point referenced by the report (ISO date). */
3908
3927
  periodStart: z28.string().nullable(),
3909
3928
  /** Latest data point referenced by the report (ISO date). */
3910
- periodEnd: z28.string().nullable()
3929
+ periodEnd: z28.string().nullable(),
3930
+ /**
3931
+ * The selected report window, in days (one of `REPORT_PERIOD_OPTIONS`).
3932
+ * Every time-windowed section scopes to this many days; renderers read it to
3933
+ * label the window ("Last 30 days", "(30d)"). Defaults to
3934
+ * `REPORT_DEFAULT_PERIOD_DAYS` when no `period` is requested.
3935
+ */
3936
+ periodDays: z28.number().int().positive()
3911
3937
  });
3912
3938
  var reportExecutiveSummarySchema = z28.object({
3913
3939
  /**
@@ -4331,16 +4357,25 @@ var whatsChangedSectionSchema = z28.object({
4331
4357
  /** Cited query count delta vs the prior completed run. Null when no prior run. */
4332
4358
  citedQueryCount: reportRateDeltaSchema.nullable(),
4333
4359
  /**
4334
- * GSC clicks delta — last 14 days of `gsc.trend` vs the 14 days before
4335
- * that. Null when GSC isn't connected or fewer than 28 trend points exist.
4360
+ * GSC clicks delta — the most recent `comparisonWindowDays` of `gsc.trend`
4361
+ * vs the `comparisonWindowDays` before that. Null when GSC isn't connected
4362
+ * or fewer than `comparisonWindowDays * 2` trend points exist.
4336
4363
  */
4337
4364
  gscClicksDelta: reportRateDeltaSchema.nullable(),
4338
4365
  /**
4339
- * AI referral sessions delta — last 14 days of `aiReferrals.trend` vs the
4340
- * 14 days before that. Null when AI referrals aren't tracked or fewer
4341
- * than 28 trend points exist.
4366
+ * AI referral sessions delta — the most recent `comparisonWindowDays` of
4367
+ * `aiReferrals.trend` vs the `comparisonWindowDays` before that. Null when
4368
+ * AI referrals aren't tracked or fewer than `comparisonWindowDays * 2`
4369
+ * trend points exist.
4342
4370
  */
4343
4371
  aiReferralsDelta: reportRateDeltaSchema.nullable(),
4372
+ /**
4373
+ * The period-over-period half-window in days used for `gscClicksDelta` and
4374
+ * `aiReferralsDelta` — `floor(meta.periodDays / 2)`. Renderers label those
4375
+ * deltas "vs prior {comparisonWindowDays} days" off this single value so the
4376
+ * SPA and HTML stay verbatim-identical.
4377
+ */
4378
+ comparisonWindowDays: z28.number().int().positive(),
4344
4379
  /**
4345
4380
  * Per-provider citation rate movements (latest run vs prior run). Empty
4346
4381
  * when no prior run. Sorted by |deltaAbs| desc — providers with the
@@ -5105,7 +5140,11 @@ var adsConnectionStatusDtoSchema = z31.object({
5105
5140
  currencyCode: z31.string().nullable().optional(),
5106
5141
  timezone: z31.string().nullable().optional(),
5107
5142
  status: z31.string().nullable().optional(),
5108
- lastSyncedAt: z31.string().nullable().optional()
5143
+ lastSyncedAt: z31.string().nullable().optional(),
5144
+ /** Whether the ad account has OpenAI conversion tracking (pixel or CAPI) configured,
5145
+ * detected from synced campaigns carrying conversion_event_setting_ids. Optional:
5146
+ * only present when connected. */
5147
+ conversionTrackingConfigured: z31.boolean().optional()
5109
5148
  });
5110
5149
  var adsDisconnectResponseSchema = z31.object({
5111
5150
  disconnected: z31.boolean()
@@ -5163,6 +5202,10 @@ var adsInsightRowDtoSchema = z31.object({
5163
5202
  impressions: z31.number().int(),
5164
5203
  clicks: z31.number().int(),
5165
5204
  spendMicros: z31.number().int(),
5205
+ /** Conversion count for the row. 0 when conversion tracking is not configured.
5206
+ * Conversion VALUE (for ROAS) is a deliberate follow-up: the upstream value
5207
+ * field is not yet captured against a live conversion-tracking account. */
5208
+ conversions: z31.number().int(),
5166
5209
  /** clicks / impressions; null when impressions is 0. */
5167
5210
  ctr: z31.number().nullable(),
5168
5211
  /** spendMicros / clicks, rounded to integer micros; null when clicks is 0. */
@@ -5177,6 +5220,7 @@ var adsTotalsDtoSchema = z31.object({
5177
5220
  impressions: z31.number().int(),
5178
5221
  clicks: z31.number().int(),
5179
5222
  spendMicros: z31.number().int(),
5223
+ conversions: z31.number().int(),
5180
5224
  ctr: z31.number().nullable(),
5181
5225
  cpcMicros: z31.number().int().nullable()
5182
5226
  });
@@ -5554,6 +5598,10 @@ export {
5554
5598
  citationVisibilityResponseSchema,
5555
5599
  emptyCitationVisibility,
5556
5600
  citationStateToCited,
5601
+ REPORT_PERIOD_OPTIONS,
5602
+ reportPeriodSchema,
5603
+ parseReportPeriodDays,
5604
+ reportComparisonWindowDays,
5557
5605
  reportActionTone,
5558
5606
  reportSeverityLabel,
5559
5607
  reportHorizonLabel,