@canonry/canonry 4.117.2 → 4.123.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 (35) hide show
  1. package/README.md +9 -0
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +3 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +161 -2
  4. package/assets/assets/AuditHistoryPanel-BVQMDIhQ.js +1 -0
  5. package/assets/assets/{BacklinksPage-CschVQxC.js → BacklinksPage-nrHT9VMG.js} +1 -1
  6. package/assets/assets/ChartPrimitives-_8qLsQzI.js +1 -0
  7. package/assets/assets/HistoryPage-DqsKKdyy.js +1 -0
  8. package/assets/assets/ProjectPage-GyKoQUv_.js +7 -0
  9. package/assets/assets/{RunRow-ggNGlwdN.js → RunRow-BRZZcSjQ.js} +1 -1
  10. package/assets/assets/RunsPage-24aI8DKF.js +1 -0
  11. package/assets/assets/{SettingsPage-Cy9SlBJX.js → SettingsPage-BaNeTG1i.js} +1 -1
  12. package/assets/assets/{TrafficPage-1S2PzSHB.js → TrafficPage-ByvcLSjv.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-CqXCoaod.js → TrafficSourceDetailPage-B-siTXVg.js} +1 -1
  14. package/assets/assets/{arrow-left-kCK5M8Bp.js → arrow-left-CBh1cZNH.js} +1 -1
  15. package/assets/assets/{extract-error-message-C82LAW6q.js → extract-error-message-DJZCPudR.js} +1 -1
  16. package/assets/assets/index-jymsMBpZ.css +1 -0
  17. package/assets/assets/index-mzLganDP.js +210 -0
  18. package/assets/assets/{trash-2-8Pe4v1T0.js → trash-2-Bh-Yvkgc.js} +1 -1
  19. package/assets/assets/vendor-recharts-DSxTZhaH.js +36 -0
  20. package/assets/index.html +3 -3
  21. package/dist/{chunk-BMPIV35C.js → chunk-4AZ7VES6.js} +5726 -697
  22. package/dist/{chunk-4PMMEFME.js → chunk-HJIIJI7K.js} +816 -18
  23. package/dist/{chunk-F2SRZJMY.js → chunk-JYBNBK25.js} +627 -22
  24. package/dist/{chunk-EZME2J2G.js → chunk-MMFSI3OG.js} +3315 -2191
  25. package/dist/cli.js +861 -109
  26. package/dist/index.js +4 -4
  27. package/dist/{intelligence-service-GNOE4J35.js → intelligence-service-K35EN43I.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +12 -12
  30. package/assets/assets/ChartPrimitives-D4782Ifx.js +0 -1
  31. package/assets/assets/ProjectPage-BSGTpBUJ.js +0 -7
  32. package/assets/assets/RunsPage-Bfp0K6Ry.js +0 -1
  33. package/assets/assets/index-DrDNyUhv.css +0 -1
  34. package/assets/assets/index-i-t64RXw.js +0 -210
  35. package/assets/assets/vendor-recharts-C9EZkgbP.js +0 -36
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-4PMMEFME.js";
13
+ } from "./chunk-HJIIJI7K.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -115,12 +115,14 @@ import {
115
115
  siteAuditPages,
116
116
  siteAuditSnapshots,
117
117
  usageCounters
118
- } from "./chunk-BMPIV35C.js";
118
+ } from "./chunk-4AZ7VES6.js";
119
119
  import {
120
120
  AGENT_MEMORY_VALUE_MAX_BYTES,
121
121
  AGENT_PROVIDER_IDS,
122
122
  AI_ENGINE_DOMAINS,
123
123
  AI_ENGINE_SELF_DOMAINS,
124
+ AdsAdGroupBillingEventTypes,
125
+ AdsCampaignBiddingTypes,
124
126
  AgentProviderIds,
125
127
  BacklinkSources,
126
128
  CcReleaseSyncStatuses,
@@ -142,6 +144,10 @@ import {
142
144
  TrafficSourceTypes,
143
145
  VERTEX_AI_SEARCH_PROXY_DOMAIN,
144
146
  actionConfidenceLabel,
147
+ adsAccountDtoSchema,
148
+ adsConversionEventSettingListResponseSchema,
149
+ adsConversionPixelListResponseSchema,
150
+ adsGeoSearchResponseSchema,
145
151
  agentBusy,
146
152
  agentMemoryDeleteRequestSchema,
147
153
  agentMemoryUpsertRequestSchema,
@@ -166,6 +172,7 @@ import {
166
172
  isGhostTelemetryEvent,
167
173
  isRetryableHttpError,
168
174
  normalizeIdTokens,
175
+ normalizeServedModel,
169
176
  normalizeUrlPath,
170
177
  notFound,
171
178
  parseOriginList,
@@ -178,7 +185,7 @@ import {
178
185
  validationError,
179
186
  winnabilityClassLabel,
180
187
  withRetry
181
- } from "./chunk-EZME2J2G.js";
188
+ } from "./chunk-MMFSI3OG.js";
182
189
 
183
190
  // src/telemetry.ts
184
191
  import crypto from "crypto";
@@ -594,6 +601,7 @@ async function executeTrackedQuery(input) {
594
601
  provider: "gemini",
595
602
  rawResponse,
596
603
  model,
604
+ servedModel: extractServedModel(rawResponse),
597
605
  groundingSources: parsed.groundingSources,
598
606
  searchQueries: parsed.searchQueries
599
607
  };
@@ -619,6 +627,9 @@ function normalizeResult(raw) {
619
627
  function hasParsedResponseContent(rawResponse) {
620
628
  return Array.isArray(rawResponse.candidates) && rawResponse.candidates.length > 0;
621
629
  }
630
+ function extractServedModel(rawResponse) {
631
+ return normalizeServedModel(rawResponse.modelVersion);
632
+ }
622
633
  function reparseStoredResult(rawResponse) {
623
634
  const groundingSources = extractGroundingMetadataFromRaw(rawResponse);
624
635
  const searchQueries = extractSearchQueriesFromRaw(rawResponse);
@@ -767,7 +778,11 @@ function responseToRecord(response) {
767
778
  }));
768
779
  return {
769
780
  candidates: candidates ?? [],
770
- usageMetadata: response.usageMetadata ?? null
781
+ usageMetadata: response.usageMetadata ?? null,
782
+ // Gemini reports the identity it actually served here — the only place it
783
+ // appears. Preserved so `servedModel` can be read back off a stored snapshot.
784
+ modelVersion: response.modelVersion ?? null,
785
+ responseId: response.responseId ?? null
771
786
  };
772
787
  } catch {
773
788
  return { error: "failed to serialize response" };
@@ -884,6 +899,7 @@ var geminiAdapter = {
884
899
  provider: "gemini",
885
900
  rawResponse: raw.rawResponse,
886
901
  model: raw.model,
902
+ servedModel: raw.servedModel,
887
903
  groundingSources: raw.groundingSources,
888
904
  searchQueries: raw.searchQueries
889
905
  };
@@ -1006,6 +1022,7 @@ async function executeTrackedQuery2(input) {
1006
1022
  provider: "openai",
1007
1023
  rawResponse,
1008
1024
  model,
1025
+ servedModel: extractServedModel2(rawResponse),
1009
1026
  groundingSources: parsed.groundingSources,
1010
1027
  searchQueries: parsed.searchQueries
1011
1028
  };
@@ -1031,6 +1048,9 @@ function normalizeResult2(raw) {
1031
1048
  function hasParsedResponseContent2(rawResponse) {
1032
1049
  return Array.isArray(rawResponse.output) && rawResponse.output.length > 0;
1033
1050
  }
1051
+ function extractServedModel2(rawResponse) {
1052
+ return normalizeServedModel(rawResponse.model);
1053
+ }
1034
1054
  function reparseStoredResult2(rawResponse) {
1035
1055
  const groundingSources = extractGroundingSourcesFromRaw(rawResponse);
1036
1056
  const searchQueries = extractSearchQueriesFromRaw2(rawResponse);
@@ -1228,6 +1248,7 @@ var openaiAdapter = {
1228
1248
  provider: "openai",
1229
1249
  rawResponse: raw.rawResponse,
1230
1250
  model: raw.model,
1251
+ servedModel: raw.servedModel,
1231
1252
  groundingSources: raw.groundingSources,
1232
1253
  searchQueries: raw.searchQueries
1233
1254
  };
@@ -1365,6 +1386,7 @@ async function executeTrackedQuery3(input) {
1365
1386
  provider: "claude",
1366
1387
  rawResponse,
1367
1388
  model,
1389
+ servedModel: extractServedModel3(rawResponse),
1368
1390
  groundingSources: parsed.groundingSources,
1369
1391
  searchQueries: parsed.searchQueries
1370
1392
  };
@@ -1390,6 +1412,9 @@ function normalizeResult3(raw) {
1390
1412
  function hasParsedResponseContent3(rawResponse) {
1391
1413
  return Array.isArray(rawResponse.content) && rawResponse.content.length > 0;
1392
1414
  }
1415
+ function extractServedModel3(rawResponse) {
1416
+ return normalizeServedModel(rawResponse.model);
1417
+ }
1393
1418
  function reparseStoredResult3(rawResponse) {
1394
1419
  const groundingSources = extractGroundingSourcesFromRaw2(rawResponse);
1395
1420
  const searchQueries = extractSearchQueriesFromRaw3(rawResponse);
@@ -1589,6 +1614,7 @@ var claudeAdapter = {
1589
1614
  provider: "claude",
1590
1615
  rawResponse: raw.rawResponse,
1591
1616
  model: raw.model,
1617
+ servedModel: raw.servedModel,
1592
1618
  groundingSources: raw.groundingSources,
1593
1619
  searchQueries: raw.searchQueries
1594
1620
  };
@@ -1704,10 +1730,12 @@ async function executeTrackedQuery4(input) {
1704
1730
  ]
1705
1731
  })
1706
1732
  );
1733
+ const rawResponse = responseToRecord4(response);
1707
1734
  return {
1708
1735
  provider: "local",
1709
- rawResponse: responseToRecord4(response),
1736
+ rawResponse,
1710
1737
  model,
1738
+ servedModel: extractServedModel4(rawResponse),
1711
1739
  groundingSources: [],
1712
1740
  searchQueries: []
1713
1741
  };
@@ -1771,6 +1799,9 @@ function extractDomainMentions(text) {
1771
1799
  }
1772
1800
  return [...domains];
1773
1801
  }
1802
+ function extractServedModel4(rawResponse) {
1803
+ return normalizeServedModel(rawResponse.model);
1804
+ }
1774
1805
  function responseToRecord4(response) {
1775
1806
  try {
1776
1807
  return JSON.parse(JSON.stringify(response));
@@ -1830,6 +1861,7 @@ var localAdapter = {
1830
1861
  provider: "local",
1831
1862
  rawResponse: raw.rawResponse,
1832
1863
  model: raw.model,
1864
+ servedModel: raw.servedModel,
1833
1865
  groundingSources: raw.groundingSources,
1834
1866
  searchQueries: raw.searchQueries
1835
1867
  };
@@ -2505,6 +2537,7 @@ async function executeTrackedQuery5(input) {
2505
2537
  provider: "perplexity",
2506
2538
  rawResponse,
2507
2539
  model,
2540
+ servedModel: extractServedModel5(rawResponse),
2508
2541
  groundingSources: parsed.groundingSources,
2509
2542
  searchQueries: parsed.searchQueries
2510
2543
  };
@@ -2535,6 +2568,9 @@ function hasParsedResponseContent4(rawResponse) {
2535
2568
  if (!nestedResponse) return false;
2536
2569
  return Array.isArray(nestedResponse.choices) && nestedResponse.choices.length > 0 || Array.isArray(nestedResponse.search_results) && nestedResponse.search_results.length > 0 || Array.isArray(nestedResponse.citations) && nestedResponse.citations.length > 0;
2537
2570
  }
2571
+ function extractServedModel5(rawResponse) {
2572
+ return normalizeServedModel(rawResponse.model);
2573
+ }
2538
2574
  function reparseStoredResult4(rawResponse) {
2539
2575
  const groundingSources = extractGroundingSources(rawResponse);
2540
2576
  return {
@@ -2725,6 +2761,7 @@ var perplexityAdapter = {
2725
2761
  provider: "perplexity",
2726
2762
  rawResponse: raw.rawResponse,
2727
2763
  model: raw.model,
2764
+ servedModel: raw.servedModel,
2728
2765
  groundingSources: raw.groundingSources,
2729
2766
  searchQueries: raw.searchQueries
2730
2767
  };
@@ -3303,7 +3340,10 @@ var JobRunner = class {
3303
3340
  }
3304
3341
  }
3305
3342
  const projectProviders = providerOverride ?? project.providers;
3306
- activeProviders = this.registry.getForProject(projectProviders);
3343
+ activeProviders = this.registry.getForProject(projectProviders).map((entry) => {
3344
+ const model = project.providerModels[entry.adapter.name];
3345
+ return model === void 0 ? entry : { ...entry, config: { ...entry.config, model } };
3346
+ });
3307
3347
  if (activeProviders.length === 0) {
3308
3348
  throw new Error("No providers configured. Add at least one provider API key.");
3309
3349
  }
@@ -3406,6 +3446,7 @@ var JobRunner = class {
3406
3446
  queryText: q.query,
3407
3447
  provider: providerName,
3408
3448
  model: raw.model,
3449
+ servedModel: raw.servedModel ?? null,
3409
3450
  citationState,
3410
3451
  answerMentioned,
3411
3452
  answerText: normalized.answerText,
@@ -3416,6 +3457,7 @@ var JobRunner = class {
3416
3457
  screenshotPath: screenshotRelPath,
3417
3458
  rawResponse: JSON.stringify({
3418
3459
  model: raw.model,
3460
+ servedModel: raw.servedModel ?? null,
3419
3461
  groundingSources: normalized.groundingSources,
3420
3462
  searchQueries: normalized.searchQueries,
3421
3463
  apiResponse: raw.rawResponse
@@ -3430,6 +3472,7 @@ var JobRunner = class {
3430
3472
  queryText: q.query,
3431
3473
  provider: providerName,
3432
3474
  model: raw.model,
3475
+ servedModel: raw.servedModel ?? null,
3433
3476
  citationState,
3434
3477
  answerMentioned,
3435
3478
  answerText: normalized.answerText,
@@ -3439,6 +3482,7 @@ var JobRunner = class {
3439
3482
  location: runLocation?.label ?? null,
3440
3483
  rawResponse: JSON.stringify({
3441
3484
  model: raw.model,
3485
+ servedModel: raw.servedModel ?? null,
3442
3486
  groundingSources: normalized.groundingSources,
3443
3487
  searchQueries: normalized.searchQueries,
3444
3488
  apiResponse: raw.rawResponse
@@ -4241,6 +4285,15 @@ var OPENAI_ADS_REQUEST_TIMEOUT_MS = 3e4;
4241
4285
  var OPENAI_ADS_MAX_PAGES = 100;
4242
4286
 
4243
4287
  // ../integration-openai-ads/src/types.ts
4288
+ var OpenAiAdsWriteStatuses = {
4289
+ active: "active",
4290
+ paused: "paused"
4291
+ };
4292
+ var OpenAiAdsBiddingTypes = AdsCampaignBiddingTypes;
4293
+ var OpenAiAdsBillingEventTypes = AdsAdGroupBillingEventTypes;
4294
+ var OpenAiAdsCreativeTypes = {
4295
+ chatCard: "chat_card"
4296
+ };
4244
4297
  var OpenAiAdsApiError = class extends Error {
4245
4298
  status;
4246
4299
  code;
@@ -4264,6 +4317,18 @@ function parseErrorEnvelope(body) {
4264
4317
  }
4265
4318
 
4266
4319
  // ../integration-openai-ads/src/ads-client.ts
4320
+ var MIN_ENTITY_NAME_LENGTH = 3;
4321
+ var MAX_ENTITY_NAME_LENGTH = 1e3;
4322
+ var MIN_CAMPAIGN_TIMESTAMP = 946684800;
4323
+ var MAX_CAMPAIGN_TIMESTAMP = 4102444800;
4324
+ var MIN_LIFETIME_BUDGET_MICROS = 1e6;
4325
+ var MIN_BID_MICROS = 1;
4326
+ var MAX_BID_MICROS = 1e8;
4327
+ var MIN_AD_TITLE_LENGTH = 3;
4328
+ var MAX_AD_TITLE_LENGTH = 50;
4329
+ var MAX_AD_BODY_LENGTH = 100;
4330
+ var MIN_GEO_SEARCH_LIMIT = 1;
4331
+ var MAX_GEO_SEARCH_LIMIT = 500;
4267
4332
  function validateApiKey(apiKey) {
4268
4333
  if (!apiKey || typeof apiKey !== "string" || apiKey.trim().length === 0) {
4269
4334
  throw new OpenAiAdsApiError("API key is required and must be a non-empty string", 400);
@@ -4274,6 +4339,208 @@ function validateId(value, label) {
4274
4339
  throw new OpenAiAdsApiError(`${label} is required and must be a non-empty string`, 400);
4275
4340
  }
4276
4341
  }
4342
+ function validateGeoSearchLimit(limit) {
4343
+ if (limit === void 0) return;
4344
+ if (!Number.isInteger(limit) || limit < MIN_GEO_SEARCH_LIMIT || limit > MAX_GEO_SEARCH_LIMIT) {
4345
+ throw new OpenAiAdsApiError(
4346
+ `Geo search limit must be an integer between ${MIN_GEO_SEARCH_LIMIT} and ${MAX_GEO_SEARCH_LIMIT}`,
4347
+ 400
4348
+ );
4349
+ }
4350
+ }
4351
+ function isRecord(value) {
4352
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4353
+ }
4354
+ function validateRequestObject(value, label) {
4355
+ if (!isRecord(value)) {
4356
+ throw new OpenAiAdsApiError(`${label} must be a JSON object`, 400);
4357
+ }
4358
+ }
4359
+ function validateNonEmptyRequest(value, label) {
4360
+ validateRequestObject(value, label);
4361
+ if (Object.keys(value).length === 0) {
4362
+ throw new OpenAiAdsApiError(`${label} must include at least one field`, 400);
4363
+ }
4364
+ }
4365
+ function snapshotJsonRequest(value, label) {
4366
+ try {
4367
+ const serialized = JSON.stringify(value);
4368
+ return JSON.parse(serialized);
4369
+ } catch {
4370
+ throw new OpenAiAdsApiError(`${label} must be JSON-serializable`, 400);
4371
+ }
4372
+ }
4373
+ function validateEntityName(value, label) {
4374
+ if (typeof value !== "string" || value.trim().length < MIN_ENTITY_NAME_LENGTH || value.length > MAX_ENTITY_NAME_LENGTH) {
4375
+ throw new OpenAiAdsApiError(
4376
+ `${label} must be ${MIN_ENTITY_NAME_LENGTH}-${MAX_ENTITY_NAME_LENGTH} characters and include a non-space character`,
4377
+ 400
4378
+ );
4379
+ }
4380
+ }
4381
+ function validatePausedCreateStatus(value, label) {
4382
+ if (value !== OpenAiAdsWriteStatuses.paused) {
4383
+ throw new OpenAiAdsApiError(`${label} status must be paused`, 400);
4384
+ }
4385
+ }
4386
+ function validatePublicUpdateRequest(value, label) {
4387
+ validateNonEmptyRequest(value, label);
4388
+ if (Object.hasOwn(value, "status")) {
4389
+ throw new OpenAiAdsApiError(`${label} cannot include status; use an explicit lifecycle action`, 400);
4390
+ }
4391
+ }
4392
+ function validateCampaignTimestamp(value, label) {
4393
+ if (value === void 0 || value === null) return;
4394
+ if (!Number.isInteger(value) || value < MIN_CAMPAIGN_TIMESTAMP || value > MAX_CAMPAIGN_TIMESTAMP) {
4395
+ throw new OpenAiAdsApiError(
4396
+ `${label} must be a Unix timestamp between ${MIN_CAMPAIGN_TIMESTAMP} and ${MAX_CAMPAIGN_TIMESTAMP}`,
4397
+ 400
4398
+ );
4399
+ }
4400
+ }
4401
+ function validateCampaignBudget(value) {
4402
+ validateRequestObject(value, "Campaign budget");
4403
+ const limit = value.lifetime_spend_limit_micros;
4404
+ if (!Number.isInteger(limit) || limit < MIN_LIFETIME_BUDGET_MICROS) {
4405
+ throw new OpenAiAdsApiError(
4406
+ `Campaign budget lifetime_spend_limit_micros must be an integer of at least ${MIN_LIFETIME_BUDGET_MICROS}`,
4407
+ 400
4408
+ );
4409
+ }
4410
+ }
4411
+ function validateCampaignTargeting(value) {
4412
+ if (value === void 0) return;
4413
+ if (value === null) {
4414
+ throw new OpenAiAdsApiError("Campaign targeting cannot be null; omit it to preserve existing targeting", 400);
4415
+ }
4416
+ validateRequestObject(value, "Campaign targeting");
4417
+ validateRequestObject(value.locations, "Campaign targeting locations");
4418
+ const include = value.locations.include;
4419
+ if (!Array.isArray(include) || include.length === 0) {
4420
+ throw new OpenAiAdsApiError("Campaign targeting locations include must be a non-empty array", 400);
4421
+ }
4422
+ for (const target of include) {
4423
+ validateRequestObject(target, "Campaign location target");
4424
+ validateId(target.id, "Campaign location id");
4425
+ }
4426
+ }
4427
+ function validateCampaignBidding(request) {
4428
+ const biddingType = request.bidding_type;
4429
+ if (biddingType !== void 0 && biddingType !== OpenAiAdsBiddingTypes.impressions && biddingType !== OpenAiAdsBiddingTypes.clicks) {
4430
+ throw new OpenAiAdsApiError("Campaign bidding_type must be impressions or clicks", 400);
4431
+ }
4432
+ const conversionIds = request.conversion_event_setting_ids;
4433
+ if (conversionIds !== void 0) {
4434
+ if (!Array.isArray(conversionIds)) {
4435
+ throw new OpenAiAdsApiError("Campaign conversion_event_setting_ids must be an array of unique IDs", 400);
4436
+ }
4437
+ for (const conversionId of conversionIds) {
4438
+ validateId(conversionId, "Campaign conversion event setting id");
4439
+ }
4440
+ if (new Set(conversionIds).size !== conversionIds.length) {
4441
+ throw new OpenAiAdsApiError("Campaign conversion_event_setting_ids must contain unique IDs", 400);
4442
+ }
4443
+ }
4444
+ if (biddingType === OpenAiAdsBiddingTypes.clicks && (!Array.isArray(conversionIds) || conversionIds.length === 0)) {
4445
+ throw new OpenAiAdsApiError(
4446
+ "Click campaigns require at least one conversion_event_setting_id",
4447
+ 400
4448
+ );
4449
+ }
4450
+ }
4451
+ function validateBiddingConfig(value) {
4452
+ validateRequestObject(value, "Ad group bidding_config");
4453
+ if (value.billing_event_type !== OpenAiAdsBillingEventTypes.impression && value.billing_event_type !== OpenAiAdsBillingEventTypes.click) {
4454
+ throw new OpenAiAdsApiError("Ad group billing_event_type must be impression or click", 400);
4455
+ }
4456
+ const maxBid = value.max_bid_micros;
4457
+ if (!Number.isInteger(maxBid) || maxBid < MIN_BID_MICROS || maxBid > MAX_BID_MICROS) {
4458
+ throw new OpenAiAdsApiError(
4459
+ `Ad group max_bid_micros must be an integer between ${MIN_BID_MICROS} and ${MAX_BID_MICROS}`,
4460
+ 400
4461
+ );
4462
+ }
4463
+ }
4464
+ function validateContextHints(value) {
4465
+ if (value === void 0) return;
4466
+ if (!Array.isArray(value) || value.some((hint) => typeof hint !== "string" || hint.trim().length === 0)) {
4467
+ throw new OpenAiAdsApiError("Ad group context_hints must be an array of non-empty strings", 400);
4468
+ }
4469
+ }
4470
+ function validateHttpUrl(value, label) {
4471
+ if (typeof value !== "string" || value.trim().length === 0) {
4472
+ throw new OpenAiAdsApiError(`${label} is required and must be a non-empty URL`, 400);
4473
+ }
4474
+ try {
4475
+ const parsed = new URL(value);
4476
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4477
+ throw new Error("unsupported protocol");
4478
+ }
4479
+ } catch {
4480
+ throw new OpenAiAdsApiError(`${label} must be an absolute HTTP or HTTPS URL`, 400);
4481
+ }
4482
+ }
4483
+ function validateChatCardCreative(value) {
4484
+ validateRequestObject(value, "Ad creative");
4485
+ if (value.type !== OpenAiAdsCreativeTypes.chatCard) {
4486
+ throw new OpenAiAdsApiError("Ad creative type must be chat_card", 400);
4487
+ }
4488
+ if (typeof value.title !== "string" || value.title.trim().length < MIN_AD_TITLE_LENGTH || value.title.length > MAX_AD_TITLE_LENGTH) {
4489
+ throw new OpenAiAdsApiError(
4490
+ `Ad creative title must be ${MIN_AD_TITLE_LENGTH}-${MAX_AD_TITLE_LENGTH} characters`,
4491
+ 400
4492
+ );
4493
+ }
4494
+ if (typeof value.body !== "string" || value.body.trim().length === 0 || value.body.length > MAX_AD_BODY_LENGTH) {
4495
+ throw new OpenAiAdsApiError(`Ad creative body must be 1-${MAX_AD_BODY_LENGTH} characters`, 400);
4496
+ }
4497
+ validateId(value.file_id, "Ad creative file id");
4498
+ validateHttpUrl(value.target_url, "Ad creative target URL");
4499
+ }
4500
+ function validateCreateCampaignRequest(request) {
4501
+ validateRequestObject(request, "Campaign create request");
4502
+ validateEntityName(request.name, "Campaign name");
4503
+ validatePausedCreateStatus(request.status, "Campaign create request");
4504
+ validateCampaignBudget(request.budget);
4505
+ validateCampaignTimestamp(request.start_time, "Campaign start_time");
4506
+ validateCampaignTimestamp(request.end_time, "Campaign end_time");
4507
+ validateCampaignBidding(request);
4508
+ validateCampaignTargeting(request.targeting);
4509
+ }
4510
+ function validateUpdateCampaignRequest(request) {
4511
+ validatePublicUpdateRequest(request, "Campaign update request");
4512
+ if (request.name !== void 0) validateEntityName(request.name, "Campaign name");
4513
+ if (request.budget !== void 0) validateCampaignBudget(request.budget);
4514
+ validateCampaignTimestamp(request.start_time, "Campaign start_time");
4515
+ validateCampaignTimestamp(request.end_time, "Campaign end_time");
4516
+ validateCampaignTargeting(request.targeting);
4517
+ }
4518
+ function validateCreateAdGroupRequest(request) {
4519
+ validateRequestObject(request, "Ad group create request");
4520
+ validateId(request.campaign_id, "Campaign id");
4521
+ validateEntityName(request.name, "Ad group name");
4522
+ validatePausedCreateStatus(request.status, "Ad group create request");
4523
+ validateContextHints(request.context_hints);
4524
+ validateBiddingConfig(request.bidding_config);
4525
+ }
4526
+ function validateUpdateAdGroupRequest(request) {
4527
+ validatePublicUpdateRequest(request, "Ad group update request");
4528
+ if (request.name !== void 0) validateEntityName(request.name, "Ad group name");
4529
+ validateContextHints(request.context_hints);
4530
+ if (request.bidding_config !== void 0) validateBiddingConfig(request.bidding_config);
4531
+ }
4532
+ function validateCreateAdRequest(request) {
4533
+ validateRequestObject(request, "Ad create request");
4534
+ validateId(request.ad_group_id, "Ad group id");
4535
+ validateEntityName(request.name, "Ad name");
4536
+ validatePausedCreateStatus(request.status, "Ad create request");
4537
+ validateChatCardCreative(request.creative);
4538
+ }
4539
+ function validateUpdateAdRequest(request) {
4540
+ validatePublicUpdateRequest(request, "Ad update request");
4541
+ if (request.name !== void 0) validateEntityName(request.name, "Ad name");
4542
+ if (request.creative !== void 0) validateChatCardCreative(request.creative);
4543
+ }
4277
4544
  function adsClientLog(level, action, ctx) {
4278
4545
  const entry = {
4279
4546
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4290,14 +4557,15 @@ function buildUrl(path10, queryPairs) {
4290
4557
  const qs = queryPairs.join("&");
4291
4558
  return qs ? `${OPENAI_ADS_API_BASE}/${path10}?${qs}` : `${OPENAI_ADS_API_BASE}/${path10}`;
4292
4559
  }
4293
- async function adsFetch(apiKey, path10, queryPairs = []) {
4560
+ async function adsFetch(apiKey, path10, queryPairs = [], method = "GET", body) {
4294
4561
  const url = buildUrl(path10, queryPairs);
4295
4562
  const res = await fetch(url, {
4296
- method: "GET",
4563
+ method,
4297
4564
  headers: {
4298
4565
  Authorization: `Bearer ${apiKey}`,
4299
4566
  "Content-Type": "application/json"
4300
4567
  },
4568
+ body: body === void 0 ? void 0 : JSON.stringify(body),
4301
4569
  signal: AbortSignal.timeout(OPENAI_ADS_REQUEST_TIMEOUT_MS)
4302
4570
  });
4303
4571
  if (res.status === 401 || res.status === 403) {
@@ -4311,10 +4579,10 @@ async function adsFetch(apiKey, path10, queryPairs = []) {
4311
4579
  throw new OpenAiAdsApiError("OpenAI Ads API rate limit exceeded", 429, code);
4312
4580
  }
4313
4581
  if (!res.ok) {
4314
- const body = await res.text();
4315
- const { message, code } = parseErrorEnvelope(body);
4582
+ const body2 = await res.text();
4583
+ const { message, code } = parseErrorEnvelope(body2);
4316
4584
  adsClientLog("error", "http.error", { path: path10, httpStatus: res.status, code });
4317
- const detail = message ?? (body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`);
4585
+ const detail = message ?? (body2.length <= 500 ? body2 : `${body2.slice(0, 500)}... [truncated]`);
4318
4586
  throw new OpenAiAdsApiError(`OpenAI Ads API error (${res.status}): ${detail}`, res.status, code);
4319
4587
  }
4320
4588
  const text = await res.text();
@@ -4337,7 +4605,11 @@ async function fetchAllPages(apiKey, path10, queryPairs) {
4337
4605
  after = response.last_id;
4338
4606
  }
4339
4607
  adsClientLog("error", "pagination.cap-reached", { path: path10, pages: OPENAI_ADS_MAX_PAGES, items: items.length });
4340
- return items;
4608
+ throw new OpenAiAdsApiError(
4609
+ `OpenAI Ads API collection remained incomplete after the ${OPENAI_ADS_MAX_PAGES}-page safety cap`,
4610
+ 502,
4611
+ "pagination_limit_exceeded"
4612
+ );
4341
4613
  }
4342
4614
  function insightsPairs(opts) {
4343
4615
  return (opts?.fields ?? []).map((field) => `fields[]=${encodeURIComponent(field)}`);
@@ -4346,20 +4618,126 @@ async function getAdAccount(apiKey) {
4346
4618
  validateApiKey(apiKey);
4347
4619
  return adsFetch(apiKey, "ad_account");
4348
4620
  }
4621
+ async function searchGeoLocations(apiKey, query, limit) {
4622
+ validateApiKey(apiKey);
4623
+ validateId(query, "Geo search query");
4624
+ validateGeoSearchLimit(limit);
4625
+ const queryPairs = [`q=${encodeURIComponent(query)}`];
4626
+ if (limit !== void 0) queryPairs.push(`limit=${limit}`);
4627
+ return adsFetch(apiKey, "geo_lookup/search", queryPairs);
4628
+ }
4629
+ async function listConversionPixels(apiKey) {
4630
+ validateApiKey(apiKey);
4631
+ return fetchAllPages(apiKey, "conversions/pixels", []);
4632
+ }
4633
+ async function listConversionEventSettings(apiKey) {
4634
+ validateApiKey(apiKey);
4635
+ return fetchAllPages(apiKey, "conversions/event_settings", []);
4636
+ }
4349
4637
  async function listCampaigns(apiKey) {
4350
4638
  validateApiKey(apiKey);
4351
4639
  return fetchAllPages(apiKey, "campaigns", []);
4352
4640
  }
4641
+ async function getCampaign(apiKey, campaignId) {
4642
+ validateApiKey(apiKey);
4643
+ validateId(campaignId, "Campaign id");
4644
+ return adsFetch(apiKey, `campaigns/${encodeURIComponent(campaignId)}`);
4645
+ }
4646
+ async function createCampaign(apiKey, request) {
4647
+ validateApiKey(apiKey);
4648
+ const outbound = snapshotJsonRequest(request, "Campaign create request");
4649
+ validateCreateCampaignRequest(outbound);
4650
+ return adsFetch(apiKey, "campaigns", [], "POST", outbound);
4651
+ }
4652
+ async function updateCampaign(apiKey, campaignId, request) {
4653
+ validateApiKey(apiKey);
4654
+ validateId(campaignId, "Campaign id");
4655
+ const outbound = snapshotJsonRequest(request, "Campaign update request");
4656
+ validateUpdateCampaignRequest(outbound);
4657
+ return adsFetch(apiKey, `campaigns/${encodeURIComponent(campaignId)}`, [], "POST", outbound);
4658
+ }
4659
+ async function activateCampaign(apiKey, campaignId) {
4660
+ validateApiKey(apiKey);
4661
+ validateId(campaignId, "Campaign id");
4662
+ return adsFetch(apiKey, `campaigns/${encodeURIComponent(campaignId)}/activate`, [], "POST");
4663
+ }
4664
+ async function pauseCampaign(apiKey, campaignId) {
4665
+ validateApiKey(apiKey);
4666
+ validateId(campaignId, "Campaign id");
4667
+ return adsFetch(apiKey, `campaigns/${encodeURIComponent(campaignId)}/pause`, [], "POST");
4668
+ }
4353
4669
  async function listAdGroups(apiKey, campaignId) {
4354
4670
  validateApiKey(apiKey);
4355
4671
  validateId(campaignId, "Campaign id");
4356
4672
  return fetchAllPages(apiKey, "ad_groups", [`campaign_id=${encodeURIComponent(campaignId)}`]);
4357
4673
  }
4674
+ async function getAdGroup(apiKey, adGroupId) {
4675
+ validateApiKey(apiKey);
4676
+ validateId(adGroupId, "Ad group id");
4677
+ return adsFetch(apiKey, `ad_groups/${encodeURIComponent(adGroupId)}`);
4678
+ }
4679
+ async function createAdGroup(apiKey, request) {
4680
+ validateApiKey(apiKey);
4681
+ const outbound = snapshotJsonRequest(request, "Ad group create request");
4682
+ validateCreateAdGroupRequest(outbound);
4683
+ return adsFetch(apiKey, "ad_groups", [], "POST", outbound);
4684
+ }
4685
+ async function updateAdGroup(apiKey, adGroupId, request) {
4686
+ validateApiKey(apiKey);
4687
+ validateId(adGroupId, "Ad group id");
4688
+ const outbound = snapshotJsonRequest(request, "Ad group update request");
4689
+ validateUpdateAdGroupRequest(outbound);
4690
+ return adsFetch(apiKey, `ad_groups/${encodeURIComponent(adGroupId)}`, [], "POST", outbound);
4691
+ }
4692
+ async function activateAdGroup(apiKey, adGroupId) {
4693
+ validateApiKey(apiKey);
4694
+ validateId(adGroupId, "Ad group id");
4695
+ return adsFetch(apiKey, `ad_groups/${encodeURIComponent(adGroupId)}/activate`, [], "POST");
4696
+ }
4697
+ async function pauseAdGroup(apiKey, adGroupId) {
4698
+ validateApiKey(apiKey);
4699
+ validateId(adGroupId, "Ad group id");
4700
+ return adsFetch(apiKey, `ad_groups/${encodeURIComponent(adGroupId)}/pause`, [], "POST");
4701
+ }
4358
4702
  async function listAds(apiKey, adGroupId) {
4359
4703
  validateApiKey(apiKey);
4360
4704
  validateId(adGroupId, "Ad group id");
4361
4705
  return fetchAllPages(apiKey, "ads", [`ad_group_id=${encodeURIComponent(adGroupId)}`]);
4362
4706
  }
4707
+ async function getAd(apiKey, adId) {
4708
+ validateApiKey(apiKey);
4709
+ validateId(adId, "Ad id");
4710
+ return adsFetch(apiKey, `ads/${encodeURIComponent(adId)}`);
4711
+ }
4712
+ async function createAd(apiKey, request) {
4713
+ validateApiKey(apiKey);
4714
+ const outbound = snapshotJsonRequest(request, "Ad create request");
4715
+ validateCreateAdRequest(outbound);
4716
+ return adsFetch(apiKey, "ads", [], "POST", outbound);
4717
+ }
4718
+ async function updateAd(apiKey, adId, request) {
4719
+ validateApiKey(apiKey);
4720
+ validateId(adId, "Ad id");
4721
+ const outbound = snapshotJsonRequest(request, "Ad update request");
4722
+ validateUpdateAdRequest(outbound);
4723
+ return adsFetch(apiKey, `ads/${encodeURIComponent(adId)}`, [], "POST", outbound);
4724
+ }
4725
+ async function activateAd(apiKey, adId) {
4726
+ validateApiKey(apiKey);
4727
+ validateId(adId, "Ad id");
4728
+ return adsFetch(apiKey, `ads/${encodeURIComponent(adId)}/activate`, [], "POST");
4729
+ }
4730
+ async function pauseAd(apiKey, adId) {
4731
+ validateApiKey(apiKey);
4732
+ validateId(adId, "Ad id");
4733
+ return adsFetch(apiKey, `ads/${encodeURIComponent(adId)}/pause`, [], "POST");
4734
+ }
4735
+ async function uploadImageFromUrl(apiKey, imageUrl) {
4736
+ validateApiKey(apiKey);
4737
+ validateHttpUrl(imageUrl, "Image URL");
4738
+ const request = { image_url: imageUrl };
4739
+ return adsFetch(apiKey, "upload", [], "POST", request);
4740
+ }
4363
4741
  async function getCampaignInsights(apiKey, campaignId, opts) {
4364
4742
  validateApiKey(apiKey);
4365
4743
  validateId(campaignId, "Campaign id");
@@ -4491,10 +4869,14 @@ async function executeAdsSync(db, runId, projectId, opts) {
4491
4869
  id: campaign.id,
4492
4870
  projectId,
4493
4871
  name: campaign.name,
4872
+ description: campaign.description ?? null,
4494
4873
  status: campaign.status,
4874
+ startTime: campaign.start_time ?? null,
4875
+ endTime: campaign.end_time ?? null,
4495
4876
  biddingType: campaign.bidding_type,
4496
4877
  dailySpendLimitMicros: campaign.budget?.daily_spend_limit_micros ?? null,
4497
4878
  lifetimeSpendLimitMicros: campaign.budget?.lifetime_spend_limit_micros ?? null,
4879
+ conversionEventSettingIds: campaign.conversion_event_setting_ids ?? [],
4498
4880
  targeting: campaign.targeting,
4499
4881
  upstreamCreatedAt: campaign.created_at,
4500
4882
  upstreamUpdatedAt: campaign.updated_at,
@@ -4507,6 +4889,7 @@ async function executeAdsSync(db, runId, projectId, opts) {
4507
4889
  projectId,
4508
4890
  campaignId: campaign.id,
4509
4891
  name: group.name,
4892
+ description: group.description ?? null,
4510
4893
  status: group.status,
4511
4894
  billingEventType: group.bidding_config?.billing_event_type ?? null,
4512
4895
  maxBidMicros: group.bidding_config?.max_bid_micros ?? null,
@@ -4556,6 +4939,9 @@ async function executeAdsSync(db, runId, projectId, opts) {
4556
4939
  currencyCode: account.currency_code,
4557
4940
  timezone: account.timezone,
4558
4941
  status: account.status,
4942
+ reviewStatus: account.review?.status ?? null,
4943
+ integrityReviewStatus: account.account_integrity_review?.review?.status ?? null,
4944
+ integrityDecision: account.account_integrity_review?.details?.decision ?? null,
4559
4945
  conversionTrackingConfigured,
4560
4946
  lastSyncedAt: insertNow,
4561
4947
  updatedAt: insertNow
@@ -6515,7 +6901,7 @@ function readStoredGroundingSources(rawResponse) {
6515
6901
  return result;
6516
6902
  }
6517
6903
  async function backfillInsightsCommand(project, opts) {
6518
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-GNOE4J35.js");
6904
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-K35EN43I.js");
6519
6905
  const config = loadConfig();
6520
6906
  const db = createClient(config.database);
6521
6907
  migrate(db);
@@ -8602,9 +8988,28 @@ var AERO_ADS_OPERATOR_MCP_TOOL_NAMES = /* @__PURE__ */ new Set([
8602
8988
  CanonryMcpToolNames.canonry_memory_forget,
8603
8989
  CanonryMcpToolNames.canonry_run_trigger,
8604
8990
  CanonryMcpToolNames.canonry_ads_status,
8991
+ CanonryMcpToolNames.canonry_ads_account,
8992
+ CanonryMcpToolNames.canonry_ads_geo_search,
8993
+ CanonryMcpToolNames.canonry_ads_conversion_pixels,
8994
+ CanonryMcpToolNames.canonry_ads_conversion_event_settings,
8605
8995
  CanonryMcpToolNames.canonry_ads_campaigns,
8606
8996
  CanonryMcpToolNames.canonry_ads_insights,
8607
8997
  CanonryMcpToolNames.canonry_ads_summary,
8998
+ CanonryMcpToolNames.canonry_ads_operations_unresolved,
8999
+ CanonryMcpToolNames.canonry_ads_operation_get,
9000
+ CanonryMcpToolNames.canonry_ads_operation_reconcile,
9001
+ CanonryMcpToolNames.canonry_ads_operation_resume_activation,
9002
+ CanonryMcpToolNames.canonry_ads_image_upload,
9003
+ CanonryMcpToolNames.canonry_ads_campaign_create,
9004
+ CanonryMcpToolNames.canonry_ads_campaign_update,
9005
+ CanonryMcpToolNames.canonry_ads_campaign_pause,
9006
+ CanonryMcpToolNames.canonry_ads_campaign_activate_tree,
9007
+ CanonryMcpToolNames.canonry_ads_ad_group_create,
9008
+ CanonryMcpToolNames.canonry_ads_ad_group_update,
9009
+ CanonryMcpToolNames.canonry_ads_ad_group_pause,
9010
+ CanonryMcpToolNames.canonry_ads_ad_create,
9011
+ CanonryMcpToolNames.canonry_ads_ad_update,
9012
+ CanonryMcpToolNames.canonry_ads_ad_pause,
8608
9013
  CanonryMcpToolNames.canonry_ads_sync
8609
9014
  ]);
8610
9015
  function toolResult(details) {
@@ -8666,6 +9071,7 @@ function compactCampaigns(response, limit) {
8666
9071
  adGroups: campaign.adGroups.slice(0, 8).map((group) => ({
8667
9072
  id: group.id,
8668
9073
  name: group.name,
9074
+ description: group.description,
8669
9075
  status: group.status,
8670
9076
  billingEventType: group.billingEventType,
8671
9077
  maxBidMicros: group.maxBidMicros,
@@ -8677,14 +9083,25 @@ function compactCampaigns(response, limit) {
8677
9083
  name: ad.name,
8678
9084
  status: ad.status,
8679
9085
  reviewStatus: ad.reviewStatus,
9086
+ upstreamUpdatedAt: ad.upstreamUpdatedAt,
9087
+ syncedAt: ad.syncedAt,
8680
9088
  creative: ad.creative ? {
8681
9089
  type: ad.creative.type,
8682
9090
  title: ad.creative.title,
8683
9091
  body: ad.creative.body,
8684
- targetUrl: ad.creative.targetUrl
9092
+ targetUrl: ad.creative.targetUrl,
9093
+ fileId: ad.creative.fileId
8685
9094
  } : ad.creative
8686
- }))
8687
- }))
9095
+ })),
9096
+ upstreamUpdatedAt: group.upstreamUpdatedAt,
9097
+ syncedAt: group.syncedAt
9098
+ })),
9099
+ description: campaign.description,
9100
+ startTime: campaign.startTime,
9101
+ endTime: campaign.endTime,
9102
+ locationIds: campaign.locationIds ?? [],
9103
+ upstreamUpdatedAt: campaign.upstreamUpdatedAt,
9104
+ syncedAt: campaign.syncedAt
8688
9105
  }))
8689
9106
  };
8690
9107
  }
@@ -8845,11 +9262,11 @@ function recordLlmUsageEvent(args) {
8845
9262
  var AERO_MEMORY_SEPARATOR = "\n\n---\n\n";
8846
9263
  var AERO_MEMORY_BLOCK_PREFIX = `${AERO_MEMORY_SEPARATOR}<memory>
8847
9264
  `;
8848
- function isRecord(value) {
9265
+ function isRecord2(value) {
8849
9266
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
8850
9267
  }
8851
9268
  function isTextSystemBlock(value) {
8852
- return isRecord(value) && value.type === "text" && typeof value.text === "string";
9269
+ return isRecord2(value) && value.type === "text" && typeof value.text === "string";
8853
9270
  }
8854
9271
  function splitAeroHydratedSystemPrompt(systemPrompt) {
8855
9272
  const idx = systemPrompt.lastIndexOf(AERO_MEMORY_BLOCK_PREFIX);
@@ -8861,7 +9278,7 @@ function splitAeroHydratedSystemPrompt(systemPrompt) {
8861
9278
  }
8862
9279
  function splitAeroAnthropicSystemCachePayload(payload, model) {
8863
9280
  if (model.api !== "anthropic-messages") return void 0;
8864
- if (!isRecord(payload) || !Array.isArray(payload.system)) return void 0;
9281
+ if (!isRecord2(payload) || !Array.isArray(payload.system)) return void 0;
8865
9282
  let changed = false;
8866
9283
  const nextSystem = [];
8867
9284
  for (const block of payload.system) {
@@ -11361,16 +11778,199 @@ async function createServer(opts) {
11361
11778
  return removed;
11362
11779
  }
11363
11780
  };
11781
+ const normalizeAdsAccount = (account) => adsAccountDtoSchema.parse({
11782
+ id: account.id,
11783
+ name: account.name,
11784
+ status: account.status,
11785
+ currencyCode: account.currency_code ?? null,
11786
+ timezone: account.timezone ?? null,
11787
+ url: account.url ?? null,
11788
+ reviewStatus: account.review?.status ?? null,
11789
+ integrityReviewStatus: account.account_integrity_review?.review?.status ?? null,
11790
+ integrityDecision: account.account_integrity_review?.details?.decision ?? null
11791
+ });
11364
11792
  const verifyAdsAccount = async (apiKey) => {
11365
- const account = await getAdAccount(apiKey);
11793
+ const account = normalizeAdsAccount(await getAdAccount(apiKey));
11366
11794
  return {
11367
11795
  id: account.id,
11368
11796
  name: account.name,
11369
11797
  status: account.status,
11370
- currencyCode: account.currency_code ?? null,
11371
- timezone: account.timezone ?? null
11798
+ currencyCode: account.currencyCode,
11799
+ timezone: account.timezone,
11800
+ reviewStatus: account.reviewStatus,
11801
+ integrityReviewStatus: account.integrityReviewStatus,
11802
+ integrityDecision: account.integrityDecision
11372
11803
  };
11373
11804
  };
11805
+ const adsReader = {
11806
+ getAccount: async (apiKey) => normalizeAdsAccount(await getAdAccount(apiKey)),
11807
+ searchGeo: async (apiKey, input) => {
11808
+ const response = await searchGeoLocations(apiKey, input.q, input.limit);
11809
+ return adsGeoSearchResponseSchema.parse({
11810
+ count: response.count,
11811
+ query: response.query,
11812
+ results: response.results.map((location) => ({
11813
+ id: location.id,
11814
+ type: location.type,
11815
+ canonicalName: location.canonical_name,
11816
+ countryCode: location.country_code,
11817
+ name: location.name,
11818
+ regionCode: location.region_code
11819
+ }))
11820
+ });
11821
+ },
11822
+ listConversionPixels: async (apiKey) => {
11823
+ const pixels = await listConversionPixels(apiKey);
11824
+ return adsConversionPixelListResponseSchema.parse({
11825
+ pixels: pixels.map((pixel) => ({
11826
+ id: pixel.id,
11827
+ clientType: pixel.client_type,
11828
+ name: pixel.name,
11829
+ pixelId: pixel.pixel_id
11830
+ }))
11831
+ });
11832
+ },
11833
+ listConversionEventSettings: async (apiKey) => {
11834
+ const eventSettings = await listConversionEventSettings(apiKey);
11835
+ return adsConversionEventSettingListResponseSchema.parse({
11836
+ eventSettings: eventSettings.map((eventSetting) => ({
11837
+ id: eventSetting.id,
11838
+ name: eventSetting.name,
11839
+ eventType: eventSetting.event_type,
11840
+ customEventName: eventSetting.custom_event_name,
11841
+ attributionWindowDays: eventSetting.attribution_window_days,
11842
+ adAccountId: eventSetting.ad_account_id,
11843
+ sourceIds: eventSetting.source_ids,
11844
+ sources: eventSetting.sources,
11845
+ archived: eventSetting.archived,
11846
+ version: eventSetting.version
11847
+ }))
11848
+ });
11849
+ }
11850
+ };
11851
+ const adsEntityResult = (entity) => ({
11852
+ id: entity.id,
11853
+ name: entity.name,
11854
+ status: entity.status,
11855
+ updatedAt: entity.updated_at,
11856
+ reviewStatus: entity.review_status ?? null,
11857
+ creative: typeof entity.creative?.title === "string" && typeof entity.creative.body === "string" && typeof entity.creative.target_url === "string" && typeof entity.creative.file_id === "string" ? {
11858
+ title: entity.creative.title,
11859
+ body: entity.creative.body,
11860
+ targetUrl: entity.creative.target_url,
11861
+ fileId: entity.creative.file_id
11862
+ } : null
11863
+ });
11864
+ const adsCampaignEntityResult = (entity) => ({
11865
+ ...adsEntityResult(entity),
11866
+ description: entity.description,
11867
+ startTime: entity.start_time,
11868
+ endTime: entity.end_time,
11869
+ lifetimeSpendLimitMicros: entity.budget?.lifetime_spend_limit_micros ?? null,
11870
+ locationIds: entity.targeting?.locations?.include?.map((location) => location.id) ?? [],
11871
+ biddingType: entity.bidding_type,
11872
+ conversionEventSettingIds: entity.conversion_event_setting_ids
11873
+ });
11874
+ const adsAdGroupEntityResult = (entity, campaignId) => ({
11875
+ ...adsEntityResult(entity),
11876
+ description: entity.description,
11877
+ campaignId: campaignId ?? null,
11878
+ contextHints: entity.context_hints,
11879
+ maxBidMicros: entity.bidding_config?.max_bid_micros ?? null,
11880
+ billingEventType: entity.bidding_config?.billing_event_type ?? null
11881
+ });
11882
+ const adsAdEntityResult = (entity, adGroupId) => ({
11883
+ ...adsEntityResult(entity),
11884
+ adGroupId: adGroupId ?? null
11885
+ });
11886
+ const adsOperator = {
11887
+ uploadImage: async (apiKey, imageUrl) => {
11888
+ const result = await uploadImageFromUrl(apiKey, imageUrl);
11889
+ return { fileId: result.file_id };
11890
+ },
11891
+ getCampaign: async (apiKey, id) => adsCampaignEntityResult(await getCampaign(apiKey, id)),
11892
+ listCampaigns: async (apiKey) => (await listCampaigns(apiKey)).map(adsCampaignEntityResult),
11893
+ createCampaign: async (apiKey, input) => adsCampaignEntityResult(await createCampaign(apiKey, {
11894
+ name: input.name,
11895
+ description: input.description,
11896
+ start_time: input.startTime,
11897
+ end_time: input.endTime,
11898
+ status: OpenAiAdsWriteStatuses.paused,
11899
+ budget: { lifetime_spend_limit_micros: input.lifetimeSpendLimitMicros },
11900
+ bidding_type: input.biddingType,
11901
+ conversion_event_setting_ids: input.conversionEventSettingIds,
11902
+ targeting: { locations: { include: input.locationIds.map((id) => ({ id })) } }
11903
+ })),
11904
+ updateCampaign: async (apiKey, id, input) => adsCampaignEntityResult(await updateCampaign(apiKey, id, {
11905
+ name: input.name,
11906
+ description: input.description,
11907
+ start_time: input.startTime,
11908
+ end_time: input.endTime,
11909
+ budget: input.lifetimeSpendLimitMicros === void 0 ? void 0 : { lifetime_spend_limit_micros: input.lifetimeSpendLimitMicros },
11910
+ targeting: input.locationIds === void 0 ? void 0 : { locations: { include: input.locationIds.map((locationId) => ({ id: locationId })) } }
11911
+ })),
11912
+ activateCampaign: async (apiKey, id) => adsCampaignEntityResult(await activateCampaign(apiKey, id)),
11913
+ pauseCampaign: async (apiKey, id) => adsCampaignEntityResult(await pauseCampaign(apiKey, id)),
11914
+ getAdGroup: async (apiKey, id) => adsAdGroupEntityResult(await getAdGroup(apiKey, id)),
11915
+ listAdGroups: async (apiKey, campaignId) => (await listAdGroups(apiKey, campaignId)).map((entity) => adsAdGroupEntityResult(entity, campaignId)),
11916
+ createAdGroup: async (apiKey, input) => adsAdGroupEntityResult(await createAdGroup(apiKey, {
11917
+ campaign_id: input.campaignId,
11918
+ name: input.name,
11919
+ description: input.description,
11920
+ context_hints: input.contextHints,
11921
+ status: OpenAiAdsWriteStatuses.paused,
11922
+ bidding_config: {
11923
+ billing_event_type: input.billingEventType,
11924
+ max_bid_micros: input.maxBidMicros
11925
+ }
11926
+ })),
11927
+ updateAdGroup: async (apiKey, id, input) => {
11928
+ let biddingConfig;
11929
+ if (input.maxBidMicros !== void 0) {
11930
+ if (input.billingEventType === void 0) {
11931
+ throw new Error("Ad group max-bid updates require the current billing event type");
11932
+ }
11933
+ biddingConfig = {
11934
+ billing_event_type: input.billingEventType,
11935
+ max_bid_micros: input.maxBidMicros
11936
+ };
11937
+ }
11938
+ return adsAdGroupEntityResult(await updateAdGroup(apiKey, id, {
11939
+ name: input.name,
11940
+ description: input.description,
11941
+ context_hints: input.contextHints,
11942
+ bidding_config: biddingConfig
11943
+ }));
11944
+ },
11945
+ activateAdGroup: async (apiKey, id) => adsAdGroupEntityResult(await activateAdGroup(apiKey, id)),
11946
+ pauseAdGroup: async (apiKey, id) => adsAdGroupEntityResult(await pauseAdGroup(apiKey, id)),
11947
+ getAd: async (apiKey, id) => adsAdEntityResult(await getAd(apiKey, id)),
11948
+ listAds: async (apiKey, adGroupId) => (await listAds(apiKey, adGroupId)).map((entity) => adsAdEntityResult(entity, adGroupId)),
11949
+ createAd: async (apiKey, input) => adsAdEntityResult(await createAd(apiKey, {
11950
+ ad_group_id: input.adGroupId,
11951
+ name: input.name,
11952
+ status: OpenAiAdsWriteStatuses.paused,
11953
+ creative: {
11954
+ type: OpenAiAdsCreativeTypes.chatCard,
11955
+ title: input.creative.title,
11956
+ body: input.creative.body,
11957
+ target_url: input.creative.targetUrl,
11958
+ file_id: input.creative.fileId
11959
+ }
11960
+ })),
11961
+ updateAd: async (apiKey, id, input) => adsAdEntityResult(await updateAd(apiKey, id, {
11962
+ name: input.name,
11963
+ creative: input.creative ? {
11964
+ type: OpenAiAdsCreativeTypes.chatCard,
11965
+ title: input.creative.title,
11966
+ body: input.creative.body,
11967
+ target_url: input.creative.targetUrl,
11968
+ file_id: input.creative.fileId
11969
+ } : void 0
11970
+ })),
11971
+ activateAd: async (apiKey, id) => adsAdEntityResult(await activateAd(apiKey, id)),
11972
+ pauseAd: async (apiKey, id) => adsAdEntityResult(await pauseAd(apiKey, id))
11973
+ };
11374
11974
  const scheduler = new Scheduler(opts.db, {
11375
11975
  onRunCreated: (runId, projectId, providers2, location) => {
11376
11976
  jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
@@ -11963,6 +12563,8 @@ async function createServer(opts) {
11963
12563
  },
11964
12564
  adsCredentialStore,
11965
12565
  verifyAdsAccount,
12566
+ adsReader,
12567
+ adsOperator,
11966
12568
  onAdsSyncRequested: (runId, projectId) => {
11967
12569
  runAdsSync(runId, projectId);
11968
12570
  },
@@ -12106,6 +12708,9 @@ async function createServer(opts) {
12106
12708
  name: a.name,
12107
12709
  displayName: a.displayName,
12108
12710
  mode: a.mode,
12711
+ modelConfigurable: a.mode === "api",
12712
+ defaultModel: a.modelRegistry.defaultModel,
12713
+ knownModels: a.modelRegistry.knownModels,
12109
12714
  modelValidationPattern: a.modelRegistry.validationPattern,
12110
12715
  modelValidationHint: a.modelRegistry.validationHint
12111
12716
  })),