1/* Guided source connection. 2 * 3 * Reading "connect the log source that already records AI crawler requests" and 4 * then hand-assembling a Cloudflare GraphQL call is not a flow a customer 5 * completes. This walks it: pick a source, say which account it is, copy the 6 * exact request we built, paste what came back, see what was extracted, commit. 7 * 8 * The copy-and-paste middle is not a shortcut we took. This service never holds 9 * a customer credential, so the request has to be run by the customer against 10 * their own account. Making that explicit is the honest shape of the flow, and 11 * the dialog says so rather than leaving the user to wonder why. 12 */ 13(function attachConnectSource(root) { 14 "use strict"; 15 16 const FIELDS = { 17 cloudflare_a1: [ 18 {name: "zone_id", type: "text", label: ["Cloudflare zone id", "Cloudflare zone id"], 19 hint: ["Found on the zone overview page in your Cloudflare dashboard.", 20 "å¨ Cloudflare æ§å¶å°ç zone 總覽é å¯ä»¥æ¾å°ã"]}, 21 ], 22 ga4_b1: [ 23 {name: "property_id", type: "text", label: ["GA4 property id", "GA4 property id"], 24 hint: ["The numeric id, without the properties/ prefix.", "ç´æ¸å idï¼ä¸ç¨å properties/ åç¶´ã"]}, 25 ], 26 crm_b1: [ 27 {name: "supplied_response", type: "json", label: ["Conversion rows", "è½æè³æ"], 28 hint: ["Paste the exported rows as JSON. Each row keeps its order receipt.", 29 "æå¯åºçè³æä»¥ JSON è²¼ä¸ãæ¯ä¸åé½è¦ä¿çè¨å®æ¶æã"]}, 30 ], 31 a2_provider_response: [ 32 {name: "provider", type: "text", label: ["Provider", "便å"], 33 hint: ["Which engine produced the answer.", "éååçæ¯åªå弿ç¢ççã"]}, 34 {name: "prompt_id", type: "text", label: ["Prompt id", "Prompt id"], 35 hint: ["The prompt this answer came from.", "éååçå°æç promptã"]}, 36 {name: "supplied_response", type: "json", label: ["The answer", "åçå §å®¹"], 37 hint: ["Paste the engine's response as JSON.", "æå¼æçåæä»¥ JSON è²¼ä¸ã"]}, 38 ], 39 a2_probe_run: [], 40 }; 41 42 const NEEDS_WINDOW = new Set(["cloudflare_a1", "ga4_b1", "crm_b1"]); 43 const PLAN_FIRST = new Set(["cloudflare_a1", "ga4_b1"]); 44 45 function createConnectSource(options) { 46 const request = options && options.request; 47 const localized = (options && options.localized) || ((en) => en); 48 if (typeof request !== "function") throw new TypeError("connect_source_request_required"); 49 50 const state = {host: "", connector: "", values: {}, plan: null, evidence: null}; 51 52 function reset(host, connector) { 53 state.host = String(host || ""); 54 state.connector = String(connector || ""); 55 state.values = {}; 56 state.plan = null; 57 state.evidence = null; 58 } 59 60 function fieldsFor(connector) { 61 return FIELDS[connector] || []; 62 } 63 64 function parseValue(field, raw) { 65 if (field.type !== "json") return String(raw || "").trim(); 66 const text = String(raw || "").trim(); 67 if (!text) throw new Error("value_required");
68 try { 69 return JSON.parse(text); 70 } catch (_error) { 71 const error = new Error("invalid_json"); 72 error.field = field.name; 73 throw error; 74 } 75 } 76 77 function collect(form) { 78 const values = {}; 79 for (const field of fieldsFor(state.connector)) { 80 values[field.name] = parseValue(field, form[field.name]); 81 } 82 if (NEEDS_WINDOW.has(state.connector)) { 83 const start = String(form.window_start || "").trim(); 84 const end = String(form.window_end || "").trim(); 85 if (!start || !end) throw new Error("window_required"); 86 values.window = {start, end}; 87 } 88 return values; 89 } 90 91 function body(extra) { 92 return {url: `https://${state.host}`, connector: state.connector, ...state.values, ...(extra || {})}; 93 } 94 95 async function plan(form) { 96 state.values = collect(form); 97 if (!PLAN_FIRST.has(state.connector)) return null; 98 const result = await request("/api/dashboard/connector/plan", {method: "POST", body: body()}); 99 state.plan = result && result.plan; 100 return state.plan; 101 } 102 103 async function normalize(pastedResponse) { 104 const supplied = pastedResponse === undefined 105 ? state.values.supplied_response 106 : parseValue({name: "supplied_response", type: "json"}, pastedResponse); 107 const result = await request("/api/dashboard/connector/normalize", { 108 method: "POST", body: body({supplied_response: supplied}), 109 }); 110 state.evidence = result && result.evidence; 111 return state.evidence; 112 } 113 114 async function submit() { 115 if (!state.evidence) throw new Error("nothing_to_submit"); 116 return request("/api/dashboard/connector/submit", { 117 method: "POST", 118 body: {url: `https://${state.host}`, kind: "connector", evidence: state.evidence}, 119 }); 120 } 121 122 /* What the copied request actually is. Kept as text rather than a fetch the 123 page could run, because running it needs the customer's credential and 124 this page must never see one. */ 125 function requestPreview() { 126 const p = state.plan; 127 if (!p) return ""; 128 if (p.query) { 129 return `POST ${p.endpoint}\nAuthorization: Bearer <your Cloudflare API token>\nContent-Type: application/json\n\n${JSON.stringify({query: p.query}, null, 2)}`; 130 } 131 return `${p.method || "POST"} ${p.endpoint}\nAuthorization: Bearer <your access token>\nContent-Type: application/json\n\n${JSON.stringify(p.body || {}, null, 2)}`; 132 } 133 134 function summary() { 135 const e = state.evidence; 136 if (!e || typeof e !== "object") return []; 137 const rows = []; 138 const push = (label, value) => { 139 if (value !== undefined && value !== null && value !== "") rows.push({label, value}); 140 }; 141 push(localized("Source", "便º"), e.provider || e.source || state.connector); 142 push(localized("Window", "æé"), e.window ? `${e.window.start || "?"} â ${e.window.end || "?"}` : ""); 143 push(localized("Observed rows", "è§æ¸¬çæ¸"), e.observed ?? e.total ?? e.rows); 144 push(localized("Receipt", "æ¶æ"), e.receipt_hash || e.content_hash); 145 return rows; 146 } 147 148 return { 149 reset, fieldsFor, plan, normalize, submit, requestPreview, summary, 150 needsPlan: (connector) => PLAN_FIRST.has(connector), 151 needsWindow: (connector) => NEEDS_WINDOW.has(connector), 152 state: () => ({...state}), 153 }; 154 } 155 156 const api = {createConnectSource, FIELDS, NEEDS_WINDOW, PLAN_FIRST}; 157 if (typeof module === "object" && module.exports) module.exports = api; 158 else root.AgentIdConnectSource = api; 159})(typeof globalThis === "object" ? globalThis : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.