@analitikisler/ai-tag-debugger 0.11.0 → 0.11.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.2 (2026-10-16)
4
+
5
+ - Fix sentence requests (`aitd "check my GA tags"`) failing with a 400 "Invalid schema" error from the Anthropic API. The request schema now writes optional values in a form Anthropic, OpenAI and Gemini all accept.
6
+ - When `AI_GATEWAY_URL` is set, the first line of a run says so: `Using anthropic (claude-opus-5-5) via Cloudflare AI Gateway`.
7
+
8
+ ## 0.11.1 (2026-10-16)
9
+
10
+ - Set `AI_GATEWAY_URL` (and `AI_GATEWAY_TOKEN` if the gateway has authentication on) to send Anthropic, OpenAI and Gemini calls through Cloudflare AI Gateway, for logs and cost tracking. Unset, nothing changes.
11
+
3
12
  ## 0.11.0 (2026-10-16)
4
13
 
5
14
  - Every saved run is compared with the site's previous run. The report opens with a "Changed since last run" section (`2 events fixed, 1 new problem`) that lists what was fixed and what is new, by plan coverage when both runs used `--against` and by health findings otherwise. The first run says there is nothing to compare yet. No new flag; the result is also in `capture.json` under `changes`.
package/README.md CHANGED
@@ -99,6 +99,17 @@ ai-tag-debugger run --site https://yourshop.com --journey add_to_cart \
99
99
 
100
100
  Use the model id from your provider's documentation. You can also set `OPENAI_COMPATIBLE_BASE_URL` and `OPENAI_COMPATIBLE_MODEL` instead of the flags, or `"provider"`, `"base_url"` and `"model"` in the config. When the API can't return strict JSON schemas, the explanations fall back to plain JSON mode.
101
101
 
102
+ ### Cloudflare AI Gateway
103
+
104
+ To see logs, costs and rate limits for every AI call, send them through [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/). Your provider key stays the same:
105
+
106
+ ```bash
107
+ export AI_GATEWAY_URL=https://gateway.ai.cloudflare.com/v1/<account id>/default
108
+ export AI_GATEWAY_TOKEN=... # only if the gateway has authentication on
109
+ ```
110
+
111
+ This works with `anthropic`, `openai` and `gemini`. Leave caching off in the gateway so each run sees your live site. Cloudflare Workers AI models also work through the OpenAI-compatible provider, with `--base-url https://api.cloudflare.com/client/v4/accounts/<account id>/ai/v1`.
112
+
102
113
  **A word on model choice.** The AI browses by calling tools, one step at a time, for 5 to 30 steps. Models differ a lot in how reliably they do that: smaller and local models may click the wrong thing, loop, or stop early. The checks stay correct whatever the model does, because they come from the captured hits, but a journey that doesn't finish can't show its later events. If a model struggles, try a larger one, a ready-made journey with a short goal, or `--max-steps`. The Anthropic, OpenAI and Gemini defaults are the tested ones.
103
114
 
104
115
  Adding a native provider means implementing two functions in [`src/providers.js`](src/providers.js): a tool-calling conversation and a structured JSON call.
package/README.tr.md CHANGED
@@ -99,6 +99,17 @@ ai-tag-debugger run --site https://yourshop.com --journey add_to_cart \
99
99
 
100
100
  Model kimliğini sağlayıcınızın dokümantasyonundan alın. Bayrakların yerine `OPENAI_COMPATIBLE_BASE_URL` ve `OPENAI_COMPATIBLE_MODEL` değişkenlerini ya da ayar dosyasında `"provider"`, `"base_url"` ve `"model"` alanlarını da kullanabilirsiniz. API katı JSON şemalarını döndüremezse açıklamalar düz JSON moduna geçer.
101
101
 
102
+ ### Cloudflare AI Gateway
103
+
104
+ Her AI çağrısının kayıtlarını, maliyetini ve limitlerini görmek için çağrıları [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/) üzerinden gönderin. Sağlayıcı anahtarınız aynı kalır:
105
+
106
+ ```bash
107
+ export AI_GATEWAY_URL=https://gateway.ai.cloudflare.com/v1/<hesap kimliği>/default
108
+ export AI_GATEWAY_TOKEN=... # yalnızca gateway'de kimlik doğrulama açıksa
109
+ ```
110
+
111
+ `anthropic`, `openai` ve `gemini` ile çalışır. Her çalıştırmanın canlı sitenizi görmesi için gateway'de önbelleği kapalı bırakın. Cloudflare Workers AI modelleri de OpenAI uyumlu sağlayıcıyla `--base-url https://api.cloudflare.com/client/v4/accounts/<hesap kimliği>/ai/v1` kullanılarak çalışır.
112
+
102
113
  **Model seçimi hakkında bir not.** Yapay zekâ, 5 ila 30 adım boyunca araç çağırarak, her seferinde bir adım gezinir. Modeller bunu ne kadar güvenilir yaptıklarında büyük farklılık gösterir: daha küçük ve yerel modeller yanlış yere tıklayabilir, döngüye girebilir ya da erken durabilir. Kontroller model ne yaparsa yapsın doğrudur, çünkü yakalanan isteklerden gelirler; ama bitmeyen bir yolculuk, sonraki event'lerini gösteremez. Bir model zorlanıyorsa daha büyük bir model, kısa hedefli hazır bir yolculuk ya da `--max-steps` deneyin. Anthropic, OpenAI ve Gemini varsayılanları test edilmiş olanlardır.
103
114
 
104
115
  Yerel bir sağlayıcı eklemek için [`src/providers.js`](src/providers.js) içinde iki fonksiyon yazmanız gerekir: araç çağıran bir konuşma ve yapılandırılmış bir JSON çağrısı.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@analitikisler/ai-tag-debugger",
3
3
  "mcpName": "io.github.analitikisler/ai-tag-debugger",
4
- "version": "0.11.0",
4
+ "version": "0.11.2",
5
5
  "description": "Open-source AI agent that QA-tests your website's analytics, ad and consent tracking (GA4, GTM, Google Ads, Meta, TikTok). Bring your own model (Claude, OpenAI, Gemini) or use it from your chat app over MCP.",
6
6
  "type": "module",
7
7
  "bin": {
package/src/ask.js CHANGED
@@ -7,19 +7,21 @@ const PLATFORM_KEYS = Object.keys(PLATFORMS);
7
7
  // The same values as plan --topic (TOPICS in discover.js, kept in step by a test).
8
8
  export const TOPIC_KEYS = ["custom-events", "ecommerce", "forms", "consent"];
9
9
 
10
+ // Optional values use anyOf with a null branch, not type: ["string", "null"]: the Anthropic API
11
+ // rejects an enum next to a type list, and anyOf works for OpenAI and Gemini too.
10
12
  export const INTENT_SCHEMA = {
11
13
  type: "object",
12
14
  additionalProperties: false,
13
15
  required: ["action", "site", "against", "platforms", "goal", "journey", "topic", "question"],
14
16
  properties: {
15
17
  action: { type: "string", enum: ["plan", "run", "report", "unclear"], description: "plan drafts what a site should track; run audits what the site sends; report re-reads an existing report; unclear when you cannot tell plan from run." },
16
- site: { type: ["string", "null"], description: "The site's domain or URL if the sentence names one." },
17
- against: { type: ["string", "null"], enum: ["plan", "latest", null], description: "run only: compare with the saved plan, or with the latest run (\"what changed\", \"since the last run\", \"after the deploy\")." },
18
+ site: { anyOf: [{ type: "string" }, { type: "null" }], description: "The site's domain or URL if the sentence names one." },
19
+ against: { anyOf: [{ type: "string", enum: ["plan", "latest"] }, { type: "null" }], description: "run only: compare with the saved plan, or with the latest run (\"what changed\", \"since the last run\", \"after the deploy\")." },
18
20
  platforms: { type: "array", items: { type: "string", enum: PLATFORM_KEYS }, description: "Only the tracking platforms the sentence names; empty for all." },
19
- goal: { type: ["string", "null"], description: "run only: a short user journey to try, in plain words, e.g. \"submit the contact form\"." },
20
- journey: { type: ["string", "null"], enum: ["purchase", null], description: "run only: purchase when the sentence is about the purchase event or revenue." },
21
- topic: { type: ["string", "null"], enum: [...TOPIC_KEYS, null], description: "plan only: limit the plan to one kind of event when the sentence asks about it." },
22
- question: { type: ["string", "null"], description: "One short question to ask back when the sentence is ambiguous; otherwise null." },
21
+ goal: { anyOf: [{ type: "string" }, { type: "null" }], description: "run only: a short user journey to try, in plain words, e.g. \"submit the contact form\"." },
22
+ journey: { anyOf: [{ type: "string", enum: ["purchase"] }, { type: "null" }], description: "run only: purchase when the sentence is about the purchase event or revenue." },
23
+ topic: { anyOf: [{ type: "string", enum: TOPIC_KEYS }, { type: "null" }], description: "plan only: limit the plan to one kind of event when the sentence asks about it." },
24
+ question: { anyOf: [{ type: "string" }, { type: "null" }], description: "One short question to ask back when the sentence is ambiguous; otherwise null." },
23
25
  },
24
26
  };
25
27
 
package/src/audit.js CHANGED
@@ -102,7 +102,7 @@ export async function runAudit(opts) {
102
102
  }
103
103
 
104
104
  const provider = useAi ? await createProvider({ name: opts.provider ?? config.provider, model: opts.model ?? config.model, baseUrl: opts.baseUrl ?? config.base_url }) : null;
105
- if (provider) log(`Using ${provider.name} (${provider.model})`);
105
+ if (provider) log(`Using ${provider.name} (${provider.model})${provider.via ? ` via ${provider.via}` : ""}`);
106
106
 
107
107
  if (opts.saveSite) await mkdir(opts.saveSite, { recursive: true });
108
108
  const runs = [];
package/src/providers.js CHANGED
@@ -57,11 +57,36 @@ export async function createProvider(opts = {}, env = process.env) {
57
57
  return openaiProvider(client, model, { compatible: true, label: new URL(baseURL).host });
58
58
  }
59
59
  const model = opts.model ?? PROVIDERS[name].defaultModel;
60
- if (name === "anthropic") return anthropicProvider(opts.client ?? new (await import("@anthropic-ai/sdk")).default(), model);
61
- if (name === "openai") return openaiProvider(opts.client ?? new (await import("openai")).default(), model);
60
+ const gateway = gatewayOptions(name, env);
61
+ const provider = await builtInProvider(name, model, gateway, opts.client, env);
62
+ // Shown in the "Using ..." line so a run says whether its calls went through the gateway.
63
+ if (gateway.baseURL && !opts.client) provider.via = "Cloudflare AI Gateway";
64
+ return provider;
65
+ }
66
+
67
+ async function builtInProvider(name, model, gateway, client, env) {
68
+ if (name === "anthropic") return anthropicProvider(client ?? new (await import("@anthropic-ai/sdk")).default(gateway), model);
69
+ if (name === "openai") return openaiProvider(client ?? new (await import("openai")).default(gateway), model);
62
70
  const { GoogleGenAI } = await import("@google/genai");
63
71
  const apiKey = env.GEMINI_API_KEY ?? env.GOOGLE_API_KEY;
64
- return geminiProvider(opts.client ?? new GoogleGenAI(apiKey ? { apiKey } : {}), model);
72
+ const httpOptions = gateway.baseURL ? { httpOptions: { baseUrl: gateway.baseURL, headers: gateway.defaultHeaders } } : {};
73
+ return geminiProvider(client ?? new GoogleGenAI({ ...(apiKey ? { apiKey } : {}), ...httpOptions }), model);
74
+ }
75
+
76
+ // Cloudflare AI Gateway path for each provider, appended to AI_GATEWAY_URL.
77
+ const GATEWAY_PATHS = { anthropic: "anthropic", openai: "openai", gemini: "google-ai-studio" };
78
+
79
+ /**
80
+ * SDK client options that send a provider's calls through Cloudflare AI Gateway, for logs,
81
+ * cost tracking and optional caching. AI_GATEWAY_URL is https://gateway.ai.cloudflare.com/v1/<account>/<gateway>;
82
+ * AI_GATEWAY_TOKEN is only needed when the gateway has authentication on. Empty when unset.
83
+ */
84
+ export function gatewayOptions(name, env = process.env) {
85
+ const base = env.AI_GATEWAY_URL?.trim().replace(/\/+$/, "");
86
+ if (!base || !GATEWAY_PATHS[name]) return {};
87
+ const options = { baseURL: `${base}/${GATEWAY_PATHS[name]}` };
88
+ if (env.AI_GATEWAY_TOKEN) options.defaultHeaders = { "cf-aig-authorization": `Bearer ${env.AI_GATEWAY_TOKEN}` };
89
+ return options;
65
90
  }
66
91
 
67
92
  // --- Anthropic ---------------------------------------------------------------