| 1 | // codewhale built-in host module harness — generated by crates/tui/extension-host/build.mjs; do not edit. |
| 2 | |
| 3 | // src/dsh/upstream/hooks/hook-protocol/src/matcher.ts |
| 4 | function isMatchAll(matcher) { |
| 5 | return matcher === void 0 || matcher === "" || matcher === "*"; |
| 6 | } |
| 7 | var CLAUDE_LITERAL = /^[A-Za-z0-9_|]+$/; |
| 8 | function compileRegex(pattern) { |
| 9 | try { |
| 10 | return new RegExp(pattern); |
| 11 | } catch (_syntaxError) { |
| 12 | return void 0; |
| 13 | } |
| 14 | } |
| 15 | function matchesMatcher(matcher, query, mode) { |
| 16 | if (isMatchAll(matcher)) return true; |
| 17 | const pattern = matcher; |
| 18 | if (mode === "claude-code" && CLAUDE_LITERAL.test(pattern)) { |
| 19 | return pattern.split("|").includes(query); |
| 20 | } |
| 21 | return compileRegex(pattern)?.test(query) ?? false; |
| 22 | } |
| 23 | |
| 24 | // src/dsh/upstream/hooks/hook-protocol/src/codec.ts |
| 25 | var BLOCKING_EXIT_CODE = 2; |
| 26 | function str(obj2, key) { |
| 27 | const v = obj2[key]; |
| 28 | return typeof v === "string" ? v : void 0; |
| 29 | } |
| 30 | function bool(obj2, key) { |
| 31 | const v = obj2[key]; |
| 32 | return typeof v === "boolean" ? v : void 0; |
| 33 | } |
| 34 | function obj(value) { |
| 35 | return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0; |
| 36 | } |
| 37 | function topLevelDecisionOf(value) { |
| 38 | return value === "approve" || value === "block" ? value : void 0; |
| 39 | } |
| 40 | function permissionDecisionOf(value) { |
| 41 | return value === "allow" || value === "deny" || value === "ask" ? value : void 0; |
| 42 | } |
| 43 | function parseHookOutput(exitCode, stdout, stderr, expectedEventName) { |
| 44 | const trimmedErr = stderr.trim(); |
| 45 | const trimmedOut = stdout.trim(); |
| 46 | const output = { exitCode, stderr: trimmedErr, stdout: trimmedOut }; |
| 47 | if (exitCode === BLOCKING_EXIT_CODE) { |
| 48 | output.decision = "block"; |
| 49 | if (trimmedErr.length > 0) output.reason = trimmedErr; |
| 50 | } |
| 51 | if (exitCode === 0) { |
| 52 | if (trimmedOut.startsWith("{")) { |
| 53 | let parsed; |
| 54 | try { |
| 55 | parsed = obj(JSON.parse(trimmedOut)); |
| 56 | } catch { |
| 57 | parsed = void 0; |
| 58 | } |
| 59 | if (parsed) applyStructured(output, parsed, expectedEventName); |
| 60 | } |
| 61 | } |
| 62 | return output; |
| 63 | } |
| 64 | function applyStructured(output, parsed, expectedEventName) { |
| 65 | const cont = bool(parsed, "continue"); |
| 66 | if (cont !== void 0) output.continue = cont; |
| 67 | const stopReason = str(parsed, "stopReason"); |
| 68 | if (stopReason !== void 0) output.stopReason = stopReason; |
| 69 | const sysMsg = str(parsed, "systemMessage"); |
| 70 | if (sysMsg !== void 0) output.systemMessage = sysMsg; |
| 71 | const topDecision = topLevelDecisionOf(str(parsed, "decision")); |
| 72 | if (topDecision !== void 0) output.decision = topDecision; |
| 73 | const topReason = str(parsed, "reason"); |
| 74 | if (topReason !== void 0) output.reason = topReason; |
| 75 | const hso = obj(parsed.hookSpecificOutput); |
| 76 | if (hso) { |
| 77 | const eventName = str(hso, "hookEventName"); |
| 78 | if (eventName !== void 0) output.hookEventName = eventName; |
| 79 | if (expectedEventName !== void 0 && eventName !== expectedEventName) { |
| 80 | return; |
| 81 | } |
| 82 | const permission = permissionDecisionOf(str(hso, "permissionDecision")); |
| 83 | if (permission !== void 0) output.decision = permission; |
| 84 | const permissionReason = str(hso, "permissionDecisionReason"); |
| 85 | if (permissionReason !== void 0) output.reason = permissionReason; |
| 86 | const addCtx = str(hso, "additionalContext"); |
| 87 | if (addCtx !== void 0) output.additionalContext = addCtx; |
| 88 | const updated = obj(hso.updatedInput); |
| 89 | if (updated !== void 0) output.updatedInput = updated; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // src/dsh/upstream/hooks/hook-protocol/src/merge.ts |
| 94 | function rank(decision) { |
| 95 | switch (decision) { |
| 96 | case "deny": |
| 97 | case "block": |
| 98 | return 3; |
| 99 | case "ask": |
| 100 | return 2; |
| 101 | case "approve": |
| 102 | case "allow": |
| 103 | return 1; |
| 104 | default: |
| 105 | return 0; |
| 106 | } |
| 107 | } |
| 108 | function decisionForRank(maxRank) { |
| 109 | switch (maxRank) { |
| 110 | case 3: |
| 111 | return "deny"; |
| 112 | case 2: |
| 113 | return "ask"; |
| 114 | case 1: |
| 115 | return "allow"; |
| 116 | default: |
| 117 | return "none"; |
| 118 | } |
| 119 | } |
| 120 | function mergeHookOutputs(outputs) { |
| 121 | let maxRank = 0; |
| 122 | const reasonsByRank = /* @__PURE__ */ new Map(); |
| 123 | let stop = false; |
| 124 | let stopReason; |
| 125 | const additionalContext = []; |
| 126 | const systemMessages = []; |
| 127 | for (const out of outputs) { |
| 128 | const r = rank(out.decision); |
| 129 | if (r > maxRank) maxRank = r; |
| 130 | if ((r === 3 || r === 2) && out.reason !== void 0 && out.reason.length > 0) { |
| 131 | const list2 = reasonsByRank.get(r) ?? []; |
| 132 | list2.push(out.reason); |
| 133 | reasonsByRank.set(r, list2); |
| 134 | } |
| 135 | if (out.continue === false && !stop) { |
| 136 | stop = true; |
| 137 | if (out.stopReason !== void 0) stopReason = out.stopReason; |
| 138 | } |
| 139 | if (out.additionalContext !== void 0 && out.additionalContext.length > 0) { |
| 140 | additionalContext.push(out.additionalContext); |
| 141 | } |
| 142 | if (out.systemMessage !== void 0 && out.systemMessage.length > 0) { |
| 143 | systemMessages.push(out.systemMessage); |
| 144 | } |
| 145 | } |
| 146 | const reasons = reasonsByRank.get(maxRank) ?? []; |
| 147 | return { |
| 148 | decision: decisionForRank(maxRank), |
| 149 | ...reasons.length > 0 ? { reason: reasons.join("\n\n") } : {}, |
| 150 | stop, |
| 151 | ...stopReason !== void 0 ? { stopReason } : {}, |
| 152 | additionalContext, |
| 153 | systemMessages |
| 154 | }; |
| 155 | } |
| 156 | |
| 157 | // src/json.ts |
| 158 | function isJson(value, depth = 0) { |
| 159 | if (depth > 64) return false; |
| 160 | if (value === null) return true; |
| 161 | switch (typeof value) { |
| 162 | case "boolean": |
| 163 | case "string": |
| 164 | return true; |
| 165 | case "number": |
| 166 | return Number.isFinite(value); |
| 167 | case "object": { |
| 168 | const array2 = Array.isArray(value); |
| 169 | if (!array2 && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false; |
| 170 | const keys = Reflect.ownKeys(value); |
| 171 | if (array2 && keys.length !== value.length + 1) return false; |
| 172 | for (const key of keys) { |
| 173 | if (array2 && key === "length") continue; |
| 174 | if (typeof key !== "string") return false; |
| 175 | if (array2 && (!/^(0|[1-9]\d*)$/u.test(key) || Number(key) >= value.length)) return false; |
| 176 | const descriptor = Object.getOwnPropertyDescriptor(value, key); |
| 177 | if (!descriptor.enumerable || !("value" in descriptor) || !isJson(descriptor.value, depth + 1)) return false; |
| 178 | } |
| 179 | return true; |
| 180 | } |
| 181 | default: |
| 182 | return false; |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | // src/builtin/shared/web-adapters.ts |
| 187 | var own = (value, key) => Object.prototype.hasOwnProperty.call(value, key); |
| 188 | function row(value) { |
| 189 | if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid captured web record"); |
| 190 | return value; |
| 191 | } |
| 192 | function text(value) { |
| 193 | if (typeof value !== "string") throw new Error("invalid captured web text"); |
| 194 | return value; |
| 195 | } |
| 196 | function string(value) { |
| 197 | return typeof value === "string" ? value : void 0; |
| 198 | } |
| 199 | function array(value) { |
| 200 | return Array.isArray(value) ? value : []; |
| 201 | } |
| 202 | function count(value, max = 10) { |
| 203 | if (!Number.isSafeInteger(value) || value < 0 || value > max) throw new Error("invalid captured web count"); |
| 204 | return value; |
| 205 | } |
| 206 | function rustTrim(value) { |
| 207 | return value.replace(/^[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, ""); |
| 208 | } |
| 209 | function firstPresent(value, keys) { |
| 210 | for (const key of keys) if (own(value, key)) return value[key]; |
| 211 | return void 0; |
| 212 | } |
| 213 | function firstText(value, keys) { |
| 214 | for (const key of keys) { |
| 215 | const found = string(value[key]); |
| 216 | if (found !== void 0) { |
| 217 | const trimmed = rustTrim(found); |
| 218 | if (trimmed) return trimmed; |
| 219 | } |
| 220 | } |
| 221 | return void 0; |
| 222 | } |
| 223 | function integer(facts, path) { |
| 224 | const value = facts[path]; |
| 225 | if (value === void 0) return void 0; |
| 226 | const number2 = row(value); |
| 227 | if (number2.i64 === null || number2.i64 === void 0) return void 0; |
| 228 | const result3 = text(number2.i64); |
| 229 | if (!/^-?\d{1,19}$/.test(result3) || BigInt(result3) < -9223372036854775808n || BigInt(result3) > 9223372036854775807n) throw new Error("invalid captured web integer"); |
| 230 | return result3; |
| 231 | } |
| 232 | function number(facts, path) { |
| 233 | const value = facts[path]; |
| 234 | if (value === void 0) return 0; |
| 235 | const result3 = Number(text(row(value).score)); |
| 236 | if (!Number.isFinite(result3)) throw new Error("invalid captured web score"); |
| 237 | return result3; |
| 238 | } |
| 239 | function result(metadata) { |
| 240 | return { ok: true, result: { success: true, content: "", metadata } }; |
| 241 | } |
| 242 | function filters(input) { |
| 243 | const recency = input.recency_days === null || input.recency_days === void 0 ? void 0 : count(input.recency_days, 3650); |
| 244 | const locale = input.locale === null || input.locale === void 0 ? void 0 : rustTrim(text(input.locale)); |
| 245 | const parts = locale?.split(/[-_]/); |
| 246 | const language = parts?.[0] !== void 0 && /^[A-Za-z]{2,3}$/.test(parts[0]) ? parts[0].toLowerCase() : null; |
| 247 | const region = parts?.[1] !== void 0 && /^[A-Za-z]{2}$/.test(parts[1]) ? parts[1].toUpperCase() : null; |
| 248 | const window = recency === void 0 ? null : recency <= 1 ? "day" : recency <= 7 ? "week" : recency <= 31 ? "month" : "year"; |
| 249 | return { kind: "web_filters", window, language, region }; |
| 250 | } |
| 251 | function validJson(value) { |
| 252 | const pending = [{ value, depth: 0 }]; |
| 253 | while (pending.length) { |
| 254 | const item = pending.pop(); |
| 255 | if (typeof item.value === "number" && !Number.isFinite(item.value)) return false; |
| 256 | if (typeof item.value === "string" && [...item.value].some((ch) => ch.length === 1 && ch.charCodeAt(0) >= 55296 && ch.charCodeAt(0) <= 57343)) return false; |
| 257 | if (item.value && typeof item.value === "object") { |
| 258 | if (item.depth >= 128) return false; |
| 259 | for (const child of Object.values(item.value)) pending.push({ value: child, depth: item.depth + 1 }); |
| 260 | } |
| 261 | } |
| 262 | return true; |
| 263 | } |
| 264 | function provider(input) { |
| 265 | const backend = text(input.backend), limit = count(input.max_results), parsed = row(input.parsed), facts = row(input.number_facts); |
| 266 | let entries2 = [], error = null; |
| 267 | let items = [], titleKeys = ["title"], urlKeys = ["url"], snippetKeys = ["content", "snippet"], trim2 = true, capSnippet = false, scorePath; |
| 268 | switch (backend) { |
| 269 | case "tavily": |
| 270 | items = array(parsed.results); |
| 271 | break; |
| 272 | case "firecrawl": { |
| 273 | const data2 = parsed.data; |
| 274 | items = array(data2 && typeof data2 === "object" && !Array.isArray(data2) && own(row(data2), "web") ? row(data2).web : data2); |
| 275 | snippetKeys = ["description", "markdown", "content"]; |
| 276 | capSnippet = true; |
| 277 | if (parsed.success === false) error = `Firecrawl search failed: ${firstText(parsed, ["error", "message"]) ?? "unknown API error"}`; |
| 278 | break; |
| 279 | } |
| 280 | case "metaso": { |
| 281 | items = array(parsed.webpages); |
| 282 | urlKeys = ["link"]; |
| 283 | snippetKeys = ["snippet", "summary"]; |
| 284 | const code = integer(facts, "/code"); |
| 285 | if (code !== void 0 && code !== "0") error = code === "3003" ? "Metaso: daily search limit reached — set METASO_API_KEY or get one at https://metaso.cn/search-api/playground" : code === "2005" ? "Metaso API key rejected — check METASO_API_KEY or set `[search] api_key` in config.toml" : `Metaso API error (code ${code}: ${string(parsed.message) ?? "unknown error"})`; |
| 286 | break; |
| 287 | } |
| 288 | case "bocha": { |
| 289 | const data2 = parsed.data && typeof parsed.data === "object" && !Array.isArray(parsed.data) ? row(parsed.data) : void 0; |
| 290 | let pages; |
| 291 | if (data2) { |
| 292 | const web = data2.webPages && typeof data2.webPages === "object" && !Array.isArray(data2.webPages) ? row(data2.webPages) : void 0; |
| 293 | pages = web && own(web, "value") ? web.value : data2.pages; |
| 294 | } |
| 295 | if (pages === void 0) pages = parsed.pages; |
| 296 | items = array(pages); |
| 297 | titleKeys = ["name", "title"]; |
| 298 | urlKeys = ["url", "link"]; |
| 299 | snippetKeys = ["summary", "snippet", "description"]; |
| 300 | const code = integer(facts, "/code"); |
| 301 | if (code !== void 0 && code !== "0" && code !== "200") error = `Bocha search API error (code ${code}: ${string(firstPresent(parsed, ["msg", "message"])) ?? "unknown error"})`; |
| 302 | break; |
| 303 | } |
| 304 | case "baidu": { |
| 305 | items = array(parsed.references); |
| 306 | titleKeys = ["title", "name"]; |
| 307 | urlKeys = ["url", "link"]; |
| 308 | snippetKeys = ["content", "snippet", "summary"]; |
| 309 | const key = own(parsed, "error_code") ? "error_code" : "code", code = integer(facts, `/${key}`); |
| 310 | if (code !== void 0 && code !== "0") error = `Baidu search API error (code ${code}: ${string(firstPresent(parsed, ["error_msg", "message"])) ?? "unknown error"})`; |
| 311 | break; |
| 312 | } |
| 313 | case "searxng": |
| 314 | items = array(parsed.results); |
| 315 | scorePath = "/results"; |
| 316 | break; |
| 317 | case "sofya": |
| 318 | items = array(parsed.results); |
| 319 | snippetKeys = ["content", "description"]; |
| 320 | trim2 = false; |
| 321 | break; |
| 322 | case "serply": |
| 323 | items = array(parsed.results); |
| 324 | urlKeys = ["link"]; |
| 325 | snippetKeys = ["description", "snippet"]; |
| 326 | trim2 = false; |
| 327 | break; |
| 328 | case "volcengine": { |
| 329 | if (own(parsed, "error")) { |
| 330 | const value = parsed.error && typeof parsed.error === "object" && !Array.isArray(parsed.error) ? row(parsed.error) : {}; |
| 331 | error = `Volcengine API error (code ${string(value.code) ?? "unknown"}: ${string(value.message) ?? "no details"})`; |
| 332 | } |
| 333 | let response; |
| 334 | const message = array(parsed.output).slice().reverse().find((value) => value && typeof value === "object" && !Array.isArray(value) && row(value).type === "message"); |
| 335 | if (message) { |
| 336 | const content = array(row(message).content).find((value) => value && typeof value === "object" && !Array.isArray(value) && typeof row(value).text === "string"); |
| 337 | if (content) response = text(row(content).text); |
| 338 | } |
| 339 | if (response === void 0 && error === null) error = "Volcengine response contains no output text"; |
| 340 | if (response !== void 0) { |
| 341 | let body = response; |
| 342 | const fence2 = response.indexOf("```json"); |
| 343 | if (fence2 >= 0) { |
| 344 | const rest = response.slice(fence2 + 7), end = rest.indexOf("```"); |
| 345 | if (end >= 0) body = rustTrim(rest.slice(0, end)); |
| 346 | else { |
| 347 | const first = response.indexOf("{"), last = response.lastIndexOf("}"); |
| 348 | if (first >= 0 && last >= first) body = response.slice(first, last + 1); |
| 349 | } |
| 350 | } else { |
| 351 | const first = response.indexOf("{"), last = response.lastIndexOf("}"); |
| 352 | if (first >= 0 && last >= first) body = response.slice(first, last + 1); |
| 353 | } |
| 354 | try { |
| 355 | const value = JSON.parse(body); |
| 356 | if (!validJson(value)) throw new Error("model JSON failed Core-compatible scalar/depth guard"); |
| 357 | if (value && typeof value === "object" && !Array.isArray(value)) items = array(row(value).results); |
| 358 | } catch { |
| 359 | items = []; |
| 360 | } |
| 361 | } |
| 362 | snippetKeys = ["snippet"]; |
| 363 | break; |
| 364 | } |
| 365 | default: |
| 366 | throw new Error("unadmitted web provider"); |
| 367 | } |
| 368 | const scored = []; |
| 369 | for (const [index, value] of items.entries()) { |
| 370 | if (!value || typeof value !== "object" || Array.isArray(value)) continue; |
| 371 | const item = row(value), rawTitle = string(firstPresent(item, titleKeys)), rawUrl = string(firstPresent(item, urlKeys)); |
| 372 | if (rawTitle === void 0 || rawUrl === void 0) continue; |
| 373 | const title = trim2 ? rustTrim(rawTitle) : rawTitle, url = trim2 ? rustTrim(rawUrl) : rawUrl; |
| 374 | if (trim2 && (!title || !url)) continue; |
| 375 | const presentSnippet = ["bocha", "baidu", "volcengine"].includes(backend); |
| 376 | const selected = presentSnippet ? string(firstPresent(item, snippetKeys)) : void 0; |
| 377 | const snippet = presentSnippet ? selected === void 0 ? void 0 : rustTrim(selected) || void 0 : firstText(item, snippetKeys); |
| 378 | const entry = { title, url, ...snippet === void 0 ? {} : { snippet: capSnippet ? [...snippet].slice(0, 1e3).join("") : snippet } }; |
| 379 | scored.push({ entry, score: scorePath === void 0 ? 0 : number(facts, `${scorePath}/${index}/score`) }); |
| 380 | if (scorePath === void 0 && scored.length === limit) break; |
| 381 | } |
| 382 | if (scorePath !== void 0) scored.sort((a, b) => a.score === b.score ? Object.is(a.score, -0) === Object.is(b.score, -0) ? 0 : Object.is(a.score, -0) ? 1 : -1 : a.score > b.score ? -1 : 1); |
| 383 | entries2 = scored.slice(0, limit).map((value) => value.entry); |
| 384 | return { kind: "web_provider", entries: entries2, error }; |
| 385 | } |
| 386 | function extraction(input) { |
| 387 | if (!Array.isArray(input.candidates) || input.candidates.length > 3) throw new Error("invalid captured web regions"); |
| 388 | const facts = []; |
| 389 | let previous = -1; |
| 390 | for (const value of input.candidates) { |
| 391 | const candidate = row(value), id = count(candidate.id, 2); |
| 392 | if (Object.keys(candidate).some((key) => !["id", "non_whitespace", "words"].includes(key)) || id <= previous) throw new Error("invalid ordered captured web region"); |
| 393 | previous = id; |
| 394 | facts.push({ id, chars: count(candidate.non_whitespace, 1e8), words: count(candidate.words, 1e8) }); |
| 395 | } |
| 396 | return { kind: "web_extract", candidate: facts.find((value) => value.chars >= 32 && value.words >= 5)?.id ?? null }; |
| 397 | } |
| 398 | function images(input) { |
| 399 | const limit = count(input.max_results), parsed = row(input.parsed); |
| 400 | if (!Array.isArray(parsed.results)) throw new Error("invalid captured image results"); |
| 401 | const entries2 = []; |
| 402 | for (const value of parsed.results) { |
| 403 | const entry = row(value), image = text(entry.image); |
| 404 | for (const key of ["thumbnail", "title", "url", "source"]) if (entry[key] !== null && entry[key] !== void 0) text(entry[key]); |
| 405 | for (const key of ["width", "height"]) if (entry[key] !== null && entry[key] !== void 0) count(entry[key], 4294967295); |
| 406 | if (!rustTrim(image)) continue; |
| 407 | entries2.push({ image, ...Object.fromEntries(["thumbnail", "title", "url", "source", "width", "height"].filter((key) => entry[key] !== null && entry[key] !== void 0).map((key) => [key, entry[key]])) }); |
| 408 | } |
| 409 | return { kind: "web_images", entries: entries2, max_results: limit }; |
| 410 | } |
| 411 | function request(input) { |
| 412 | const backend = text(input.backend), query = text(input.query), max = count(input.max_results); |
| 413 | const f = row(input.filters), window = f.window === null ? void 0 : text(f.window), region = f.region === null ? void 0 : text(f.region), language = f.language === null ? void 0 : text(f.language); |
| 414 | const locale = input.locale === null || input.locale === void 0 ? void 0 : text(input.locale); |
| 415 | let payload = null, pairs = []; |
| 416 | switch (backend) { |
| 417 | case "firecrawl": |
| 418 | payload = { query, limit: max, sources: [{ type: "web" }], ...window === void 0 ? {} : { tbs: `qdr:${window[0]}` }, ...region === void 0 ? {} : { country: region } }; |
| 419 | break; |
| 420 | case "tavily": |
| 421 | payload = { query, search_depth: "basic", max_results: max, ...window === void 0 ? {} : { time_range: window } }; |
| 422 | break; |
| 423 | case "bocha": |
| 424 | payload = { query, freshness: "noLimit", count: max }; |
| 425 | break; |
| 426 | case "metaso": |
| 427 | payload = { q: query, scope: "webpage", size: Math.max(1, Math.min(100, max)) }; |
| 428 | break; |
| 429 | case "sofya": |
| 430 | payload = { query, max_results: max }; |
| 431 | break; |
| 432 | case "baidu": |
| 433 | payload = { messages: [{ role: "user", content: query }], search_source: "baidu_search_v2", resource_type_filter: [{ type: "web", top_k: max }] }; |
| 434 | break; |
| 435 | case "volcengine": |
| 436 | payload = { input: [{ role: "user", content: [{ type: "input_text", text: `Search the web for: ${query} |
| 437 | |
| 438 | CRITICAL: Respond ONLY with a valid JSON object. No markdown, no explanation. |
| 439 | Schema: {"results":[{"title":"...","url":"https://...","snippet":"..."}]} |
| 440 | - results: 1-${max} most relevant pages |
| 441 | - title: page title (required) |
| 442 | - url: full URL starting with https:// (required) |
| 443 | - snippet: 1-2 sentence factual summary (required) |
| 444 | - If zero results: {"results":[]} |
| 445 | - Your entire response must be valid, parseable JSON.` }] }] }; |
| 446 | break; |
| 447 | case "serply": |
| 448 | pairs = [["q", query], ["num", String(max)], ...language === void 0 ? [] : [["hl", language]], ...region === void 0 ? [] : [["gl", region.toLowerCase()]]]; |
| 449 | break; |
| 450 | case "searxng": |
| 451 | pairs = [["q", query], ["format", "json"], ...window === void 0 ? [] : [["time_range", window]], ...locale === void 0 ? [] : [["language", locale]]]; |
| 452 | break; |
| 453 | case "bing": |
| 454 | case "duckduckgo": |
| 455 | pairs = [["q", query]]; |
| 456 | break; |
| 457 | default: |
| 458 | throw new Error("unadmitted web request provider"); |
| 459 | } |
| 460 | return { kind: "web_request", payload, pairs }; |
| 461 | } |
| 462 | function entries(input) { |
| 463 | if (!Array.isArray(input.entries)) throw new Error("invalid captured web candidates"); |
| 464 | const values = input.entries.map((value) => { |
| 465 | const item = row(value); |
| 466 | return { title: text(item.title), url: text(item.url), ...item.snippet === null || item.snippet === void 0 ? {} : { snippet: text(item.snippet) }, ...item.published === null || item.published === void 0 ? {} : { published: text(item.published) } }; |
| 467 | }); |
| 468 | return { kind: "web_entries", entries: values }; |
| 469 | } |
| 470 | function finalization(input) { |
| 471 | const requested = row(input.requested), capabilities = row(input.capabilities), counted = count(input.count); |
| 472 | if (!Array.isArray(input.degraded)) throw new Error("invalid captured web receipt"); |
| 473 | const degraded = input.degraded.map((value) => row(value)); |
| 474 | const ignored = (knob) => degraded.some((value) => row(value).kind === "knob_ignored" && row(value).knob === knob); |
| 475 | const honored = { max_results: capabilities.max_results === "supported", recency: false, domains: requested.domains === true, locale: false }; |
| 476 | for (const knob of ["recency", "locale"]) { |
| 477 | if (knob === "locale") { |
| 478 | if (!Array.isArray(input.domain_extra)) throw new Error("invalid captured domain receipt"); |
| 479 | degraded.push(...input.domain_extra.map((value) => row(value))); |
| 480 | } |
| 481 | if (requested[knob] === true && !ignored(knob)) { |
| 482 | if (capabilities[knob] === "supported") honored[knob] = true; |
| 483 | else degraded.push({ kind: "knob_ignored", knob }); |
| 484 | } |
| 485 | } |
| 486 | const hasNote = input.has_note === true; |
| 487 | const prefix = counted === 0 ? hasNote ? "No results found. " : "No results found" : `Found ${counted} result(s)${hasNote ? ". " : ""}`; |
| 488 | const suffix = degraded.some((value) => row(value).kind === "answer_cut_by_provider") ? "\n[the provider stopped the search answer at its output limit; the answer is incomplete]" : ""; |
| 489 | return { kind: "web_finalize", honored, degraded, prefix, suffix }; |
| 490 | } |
| 491 | function transformWebSnapshot(operation, value) { |
| 492 | const input = row(value); |
| 493 | switch (operation) { |
| 494 | case "web_request": |
| 495 | return result(request(input)); |
| 496 | case "web_entries": |
| 497 | return result(entries(input)); |
| 498 | case "web_finalize": |
| 499 | return result(finalization(input)); |
| 500 | case "web_filters": |
| 501 | return result(filters(input)); |
| 502 | case "web_provider": |
| 503 | return result(provider(input)); |
| 504 | case "web_extract": |
| 505 | return result(extraction(input)); |
| 506 | case "web_images": |
| 507 | return result(images(input)); |
| 508 | default: |
| 509 | throw new Error("unadmitted web adapter operation"); |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | // src/builtin/shared/github-adapter.ts |
| 514 | var REPOSITORY = "Hmbown/CodeWhale"; |
| 515 | function row2(value) { |
| 516 | if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid GitHub snapshot"); |
| 517 | return value; |
| 518 | } |
| 519 | function text2(value) { |
| 520 | if (typeof value !== "string") throw new Error("invalid GitHub text"); |
| 521 | return value; |
| 522 | } |
| 523 | function strings(value) { |
| 524 | if (!Array.isArray(value)) throw new Error("invalid GitHub list"); |
| 525 | return value.map(text2); |
| 526 | } |
| 527 | function githubSummary(value, limit) { |
| 528 | let out = "", index = 0; |
| 529 | for (const char of value) { |
| 530 | if (index++ >= Math.max(0, limit - 3)) return `${out}...`; |
| 531 | const code = char.codePointAt(0); |
| 532 | if ((code <= 31 || code >= 127 && code <= 159) && char !== "\n" && char !== " ") continue; |
| 533 | out += char; |
| 534 | } |
| 535 | return out; |
| 536 | } |
| 537 | function renderIssueReview(value) { |
| 538 | const report2 = row2(value), fields = row2(report2.fields), id = text2(report2.id); |
| 539 | let out = `# ${text2(fields.title)} |
| 540 | |
| 541 | Draft: ${id} |
| 542 | Status: ready for review |
| 543 | Publication: unavailable |
| 544 | Duplicate search: not performed |
| 545 | Destination: ${REPOSITORY} |
| 546 | |
| 547 | Review the contents before sharing. Redaction does not guarantee privacy. |
| 548 | `; |
| 549 | if (report2.revises !== null) out += `Revises: ${text2(report2.revises)} |
| 550 | `; |
| 551 | for (const [heading, key] of [["Expected behavior", "expected"], ["Actual behavior", "actual"], ["Impact", "impact"]]) out += ` |
| 552 | ## ${heading} |
| 553 | |
| 554 | ${text2(fields[key])} |
| 555 | `; |
| 556 | for (const [heading, key] of [["Steps to reproduce (agent reported)", "steps"], ["Observed by the agent", "observed"], ["Inferences (not verified)", "inferred"]]) { |
| 557 | const items = strings(fields[key]); |
| 558 | out += ` |
| 559 | ## ${heading} |
| 560 | |
| 561 | `; |
| 562 | out += items.length ? items.map((item) => `- ${item} |
| 563 | `).join("") : "None recorded.\n"; |
| 564 | } |
| 565 | out += ` |
| 566 | ## Runtime context |
| 567 | |
| 568 | - Codewhale: ${text2(report2.version)} |
| 569 | - Platform: ${text2(report2.platform)} |
| 570 | - Active model: ${text2(report2.model)} |
| 571 | `; |
| 572 | for (const [label, key] of [["Provider", "reported_provider"], ["Tool", "reported_tool"], ["Terminal", "reported_terminal"]]) out += `- ${label} (agent reported): ${fields[key] === null ? "unknown" : text2(fields[key])} |
| 573 | `; |
| 574 | if (!Array.isArray(fields.related_issues) || fields.related_issues.some((value2) => !Number.isInteger(value2) || Number(value2) < 1 || Number(value2) > 4294967295)) throw new Error("invalid related issue"); |
| 575 | if (fields.related_issues.length) out += "\n## Related issues (agent supplied; not verified or searched)\n\n" + fields.related_issues.map((number2) => `- [#${number2}](https://github.com/${REPOSITORY}/issues/${number2}) |
| 576 | `).join(""); |
| 577 | const redactions = strings(report2.redactions); |
| 578 | if (redactions.length) out += ` |
| 579 | Redacted categories: ${redactions.join(", ")} |
| 580 | `; |
| 581 | return out + ` |
| 582 | Review: \`/feedback review ${id}\` |
| 583 | Revise: \`/feedback edit ${id} <change>\` |
| 584 | `; |
| 585 | } |
| 586 | function transformGithubSnapshot(value) { |
| 587 | const snapshot = row2(value), action = text2(snapshot.action); |
| 588 | if (action === "report_draft" || action === "report_read" || action === "report_review") { |
| 589 | const report2 = row2(snapshot.report), id = text2(report2.id); |
| 590 | const review = renderIssueReview(report2); |
| 591 | const content2 = action === "report_review" ? review : JSON.stringify({ report_id: id, revises: report2.revises, state: "ready_for_review", publication: "unavailable", duplicate_search: "not_performed", review, artifact: `artifacts/issue-reports/${id}.json` }); |
| 592 | return { ok: true, result: { success: true, content: content2, metadata: null } }; |
| 593 | } |
| 594 | const number2 = text2(snapshot.number); |
| 595 | if (!/^[0-9]{1,20}$/.test(number2)) throw new Error("invalid captured issue number"); |
| 596 | if (action === "issue_context" || action === "pr_context") { |
| 597 | const raw = structuredClone(row2(snapshot.raw)), kind = action === "issue_context" ? "issue" : "pr"; |
| 598 | if (snapshot.large_body === true) { |
| 599 | const body = text2(raw.body); |
| 600 | raw.body_summary = githubSummary(body, 900); |
| 601 | raw.body_artifact = snapshot.body_artifact; |
| 602 | raw.body = githubSummary(body, 1200); |
| 603 | } |
| 604 | if (action === "pr_context" && snapshot.diff !== null) { |
| 605 | raw.diff_summary = githubSummary(text2(snapshot.diff), 900); |
| 606 | raw.diff_artifact = snapshot.diff_artifact; |
| 607 | } |
| 608 | const subject2 = kind === "issue" ? "Issue" : "PR"; |
| 609 | return { ok: true, result: { success: true, metadata: null, content: JSON.stringify({ summary: `${subject2} #${number2}: ${typeof raw.title === "string" ? raw.title : ""}`, [kind]: raw }) } }; |
| 610 | } |
| 611 | const target = text2(snapshot.target); |
| 612 | if (!["issue", "pr"].includes(target)) throw new Error("invalid captured GitHub target"); |
| 613 | const subject = target === "issue" ? "issue" : "PR"; |
| 614 | let content; |
| 615 | if (snapshot.dirty === true) content = `Refusing to close ${subject}: worktree is dirty and allow_dirty was false.`; |
| 616 | else if (action === "comment") content = snapshot.dry_run === true ? `Dry run: would comment on ${target} #${number2}.` : `Commented on ${target} #${number2}.`; |
| 617 | else if (action === "close_issue" || action === "close_pr") content = snapshot.dry_run === true ? `Dry run: would close ${subject} #${number2}.` : `Closed ${subject} #${number2}.`; |
| 618 | else throw new Error("unknown captured GitHub operation"); |
| 619 | return { ok: true, result: { success: snapshot.dirty !== true, content, metadata: null } }; |
| 620 | } |
| 621 | |
| 622 | // src/builtin/shared/review-adapter.ts |
| 623 | var REVIEW_LIMIT = 16 * 1024 * 1024; |
| 624 | var OPERATIONS = ["review_source_prompt", "review_pass_prompt", "review_interactive_pr", "review_report"]; |
| 625 | function isReviewOperation(value) { |
| 626 | return typeof value === "string" && OPERATIONS.includes(value); |
| 627 | } |
| 628 | function reviewEnvelopeBytes(value) { |
| 629 | return 8 + Buffer.byteLength(`{"jsonrpc":"2.0","id":18446744073709551615,"result":${JSON.stringify(value)}}`); |
| 630 | } |
| 631 | function row3(value) { |
| 632 | if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid Core review snapshot"); |
| 633 | return value; |
| 634 | } |
| 635 | function text3(value) { |
| 636 | if (typeof value !== "string") throw new Error("invalid Core review text"); |
| 637 | return value; |
| 638 | } |
| 639 | function bool2(value) { |
| 640 | if (typeof value !== "boolean") throw new Error("invalid Core review flag"); |
| 641 | return value; |
| 642 | } |
| 643 | function integer2(value) { |
| 644 | if (!Number.isSafeInteger(value) || value < 0) throw new Error("invalid Core review count"); |
| 645 | return value; |
| 646 | } |
| 647 | function list(value) { |
| 648 | if (!Array.isArray(value)) throw new Error("invalid Core review list"); |
| 649 | return value; |
| 650 | } |
| 651 | function optional(value) { |
| 652 | return value === null || value === void 0 ? void 0 : text3(value); |
| 653 | } |
| 654 | function trim(value) { |
| 655 | return value.replace(/^[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, ""); |
| 656 | } |
| 657 | function lines(value) { |
| 658 | if (!value.length) return []; |
| 659 | const out = value.split("\n"); |
| 660 | if (value.endsWith("\n")) out.pop(); |
| 661 | return out.map((line, i) => (i < out.length - 1 || value.endsWith("\n")) && line.endsWith("\r") ? line.slice(0, -1) : line); |
| 662 | } |
| 663 | function skipped(value) { |
| 664 | return list(value).map((value2) => { |
| 665 | const skip = row3(value2); |
| 666 | return `${text3(skip.file)} (${integer2(skip.chars)} chars; ${text3(skip.reason)})`; |
| 667 | }).join(", "); |
| 668 | } |
| 669 | function ordered(value) { |
| 670 | if (Array.isArray(value)) return value.map(ordered); |
| 671 | if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, value2]) => [key, ordered(value2)])); |
| 672 | return value; |
| 673 | } |
| 674 | function source(input) { |
| 675 | const kind = text3(input.kind); |
| 676 | if (kind === "file") { |
| 677 | const content = lines(text3(input.content)).map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n"); |
| 678 | return `Review the following file and provide feedback. |
| 679 | Path: ${text3(input.display)} |
| 680 | |
| 681 | ${content} |
| 682 | |
| 683 | End of file.`; |
| 684 | } |
| 685 | if (kind === "diff") return `Review the following ${text3(input.label)} and provide feedback. |
| 686 | |
| 687 | ${text3(input.diff)} |
| 688 | |
| 689 | End of diff.`; |
| 690 | if (kind === "cli_diff") return `Review the following diff and provide feedback: |
| 691 | |
| 692 | ${text3(input.diff)} |
| 693 | |
| 694 | End of diff.`; |
| 695 | if (kind === "pr") return `Review the complete pull request diff (${text3(input.label)}) at head ${text3(input.head_sha)} and base ${text3(input.base_sha)}. Binary changes are represented by metadata; their contents are not semantically inspected. Exact binary object IDs remain in the review evidence. |
| 696 | |
| 697 | ${text3(input.diff)} |
| 698 | |
| 699 | End of diff.`; |
| 700 | throw new Error("unadmitted review source"); |
| 701 | } |
| 702 | function pass(input) { |
| 703 | const manifest = row3(input.manifest), part = row3(input.pass), view = row3(input.view); |
| 704 | const task = list(manifest.skipped_files).length === 0 ? "Review only defects introduced in this pass. Use supplementary source to check surrounding guards and declarations; it does not expand the commentable diff. Binary contents and omitted callers are not inspected. No build or tests have been run." : `Review only defects introduced in this pass. This is a partial review (pass ${integer2(part.number)} of ${list(manifest.passes).length}): the gate did not read ${skipped(manifest.skipped_files)}. Do not claim full coverage. Use supplementary source to check surrounding guards and declarations; it does not expand the commentable diff. Binary contents and omitted callers are not inspected. No build or tests have been run.`; |
| 705 | if (!isJson(input.context)) throw new Error("invalid Core review context"); |
| 706 | const prompt = { |
| 707 | task, |
| 708 | untrusted_repository_data: true, |
| 709 | pull_request: { number: integer2(input.number), title: text3(view.title), description: text3(view.body) }, |
| 710 | manifest, |
| 711 | pass: part, |
| 712 | diff: text3(input.diff), |
| 713 | repository_context: input.context, |
| 714 | context_limit: "Context is bounded supplementary excerpts from the exact head. Null means no source context could fit. Missing files or omitted lines are not evidence of a defect." |
| 715 | }; |
| 716 | return JSON.stringify(bool2(input.sort_keys) ? ordered(prompt) : prompt); |
| 717 | } |
| 718 | function interactive(input) { |
| 719 | const view = row3(input.view), number2 = integer2(input.number); |
| 720 | const base = text3(view.base), head = text3(view.head); |
| 721 | const branches = base && head ? `${base} ← ${head}` : base || head || "(unknown)"; |
| 722 | return `Review PR #${number2} — ${trim(text3(view.title)) || `(PR #${number2})`} |
| 723 | |
| 724 | URL: ${text3(view.url) || "(unavailable)"} |
| 725 | Branches: ${branches} |
| 726 | Revision: ${text3(view.head_sha)} (base ${text3(view.base_sha)}); ${integer2(view.changed_files)} file patches. |
| 727 | Binary changes are represented by metadata; their contents are not semantically inspected. Exact binary object IDs remain in the review evidence. |
| 728 | |
| 729 | ## Description |
| 730 | |
| 731 | ${trim(text3(view.body)) || "(no description)"} |
| 732 | |
| 733 | ## Diff |
| 734 | |
| 735 | \`\`\`diff |
| 736 | ${text3(input.diff)} |
| 737 | \`\`\` |
| 738 | `; |
| 739 | } |
| 740 | function fence(value) { |
| 741 | let longest = 0, run = 0; |
| 742 | for (const char of value) { |
| 743 | run = char === "`" ? run + 1 : 0; |
| 744 | longest = Math.max(longest, run); |
| 745 | } |
| 746 | return "`".repeat(Math.max(3, longest + 1)); |
| 747 | } |
| 748 | function location(value) { |
| 749 | const path = optional(value.path); |
| 750 | return path === void 0 ? "" : value.line === null || value.line === void 0 ? `\`${trim(path)}\`` : `\`${trim(path)}:${integer2(value.line)}\``; |
| 751 | } |
| 752 | function report(input) { |
| 753 | if (input.review === null) return text3(input.output); |
| 754 | const review = row3(input.review), posted = bool2(input.posted); |
| 755 | let out = "## Codewhale review\n\n"; |
| 756 | const summary2 = text3(review.summary), assessment = text3(review.overall_assessment); |
| 757 | if (summary2.length) out += trim(summary2) + "\n\n"; |
| 758 | const issues = list(review.issues), suggestions = list(review.suggestions); |
| 759 | if (issues.length) { |
| 760 | out += "### Findings\n\n"; |
| 761 | for (const item of issues) { |
| 762 | const issue = row3(item), at = location(issue); |
| 763 | out += `- **[${text3(issue.severity).toUpperCase()}] ${text3(issue.title)}**${at ? ` (${at})` : ""} |
| 764 | `; |
| 765 | const description = text3(issue.description); |
| 766 | if (description.length) out += ` ${description} |
| 767 | `; |
| 768 | } |
| 769 | out += "\n"; |
| 770 | } |
| 771 | if (suggestions.length) { |
| 772 | out += "### Suggestions\n\n"; |
| 773 | for (const item of suggestions) { |
| 774 | const suggestion = row3(item), at = location(suggestion); |
| 775 | out += `- ${at ? `${at} — ` : ""}${text3(suggestion.suggestion)} |
| 776 | `; |
| 777 | const replacement = optional(suggestion.replacement); |
| 778 | if (replacement !== void 0 && trim(replacement).length) { |
| 779 | const codeFence = fence(replacement); |
| 780 | out += ` |
| 781 | ${codeFence}${posted ? "text" : "suggestion"} |
| 782 | `; |
| 783 | for (const line of replacement.split("\n")) out += ` ${line} |
| 784 | `; |
| 785 | out += ` ${codeFence} |
| 786 | `; |
| 787 | } |
| 788 | } |
| 789 | out += "\n"; |
| 790 | } |
| 791 | if (assessment.length) out += "### Assessment\n\n" + trim(assessment) + "\n\n"; |
| 792 | return out; |
| 793 | } |
| 794 | function transformReviewSnapshot(operation, value) { |
| 795 | if (!isReviewOperation(operation)) throw new Error("unadmitted review operation"); |
| 796 | const input = row3(value); |
| 797 | const content = operation === "review_source_prompt" ? source(input) : operation === "review_pass_prompt" ? pass(input) : operation === "review_interactive_pr" ? interactive(input) : report(input); |
| 798 | const result3 = { ok: true, result: { content, success: true, metadata: null } }; |
| 799 | if (reviewEnvelopeBytes(result3) > REVIEW_LIMIT) throw new Error("serialized review result exceeds 16 MiB"); |
| 800 | return result3; |
| 801 | } |
| 802 | |
| 803 | // src/builtin/shared/stock-adapters.ts |
| 804 | var MAX_SNAPSHOT = 1024 * 1024; |
| 805 | function row4(value) { |
| 806 | if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid adapter snapshot"); |
| 807 | return value; |
| 808 | } |
| 809 | function text4(value) { |
| 810 | if (typeof value !== "string") throw new Error("invalid adapter text"); |
| 811 | return value; |
| 812 | } |
| 813 | function optionalText(value) { |
| 814 | return value === null || value === void 0 ? void 0 : text4(value); |
| 815 | } |
| 816 | function optionalNumber(value) { |
| 817 | if (value === null || value === void 0) return void 0; |
| 818 | if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("invalid captured numeric field"); |
| 819 | return value; |
| 820 | } |
| 821 | function float(value) { |
| 822 | if (Object.is(value, -0)) return "-0"; |
| 823 | if (value === Infinity) return "inf"; |
| 824 | if (value === -Infinity) return "-inf"; |
| 825 | return String(value); |
| 826 | } |
| 827 | function timestamp(value) { |
| 828 | const time = optionalText(value); |
| 829 | if (time === void 0) return void 0; |
| 830 | if (!/^-?\d{1,19}$/.test(time)) throw new Error("invalid captured timestamp"); |
| 831 | const integer3 = BigInt(time); |
| 832 | if (integer3 < -9223372036854775808n || integer3 > 9223372036854775807n) throw new Error("captured timestamp exceeds i64"); |
| 833 | return time; |
| 834 | } |
| 835 | function result2(success, metadata, content = "") { |
| 836 | return { ok: true, result: { success, content, metadata } }; |
| 837 | } |
| 838 | function failure(endpoint, kind, detail) { |
| 839 | return result2(false, { endpoint, kind, detail }); |
| 840 | } |
| 841 | function asciiLower(value) { |
| 842 | return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase()); |
| 843 | } |
| 844 | function finance(operation, input) { |
| 845 | const request2 = row4(input.request); |
| 846 | const requested = text4(request2.requested_ticker); |
| 847 | const symbol = text4(request2.resolved_symbol); |
| 848 | const parsed = row4(input.parsed); |
| 849 | const chart = operation === "finance_chart"; |
| 850 | const source2 = chart ? "yahoo_chart" : "yahoo_quote"; |
| 851 | let quote; |
| 852 | if (chart) { |
| 853 | const body = row4(parsed.chart); |
| 854 | if (body.error !== null && body.error !== void 0) { |
| 855 | const error = row4(body.error); |
| 856 | const description = optionalText(error.description) ?? "chart endpoint returned an error"; |
| 857 | const code = optionalText(error.code); |
| 858 | const notFound = (code === void 0 ? void 0 : asciiLower(code)) === "not found" || asciiLower(description).includes("not found") || asciiLower(description).includes("symbol may be delisted"); |
| 859 | return failure(source2, notFound ? "not_found" : "upstream", description); |
| 860 | } |
| 861 | if (body.result !== null && body.result !== void 0 && !Array.isArray(body.result)) throw new Error("invalid captured chart results"); |
| 862 | const entries2 = body.result; |
| 863 | if (!entries2?.length) return failure(source2, "not_found", `no chart data for symbol '${symbol}'`); |
| 864 | quote = row4(row4(entries2[0]).meta); |
| 865 | } else { |
| 866 | const body = row4(parsed.quoteResponse); |
| 867 | if (!Array.isArray(body.result)) throw new Error("invalid captured quote results"); |
| 868 | const candidate = body.result.map(row4).find((value2) => asciiLower(text4(value2.symbol)) === asciiLower(symbol)); |
| 869 | if (!candidate) return failure(source2, "not_found", `no result for symbol '${symbol}'`); |
| 870 | quote = candidate; |
| 871 | } |
| 872 | const price = optionalNumber(quote.regularMarketPrice); |
| 873 | if (price === void 0) return failure(source2, "upstream", "response missing regularMarketPrice"); |
| 874 | const previous = chart ? optionalNumber(quote.chartPreviousClose) ?? optionalNumber(quote.previousClose) : optionalNumber(quote.regularMarketPreviousClose); |
| 875 | const computedChange = previous === void 0 ? void 0 : price - previous; |
| 876 | const computedPercent = previous === void 0 || Math.abs(previous) < Number.EPSILON ? void 0 : (price - previous) / previous * 100; |
| 877 | const change = chart ? computedChange : optionalNumber(quote.regularMarketChange) ?? computedChange; |
| 878 | const percent = chart ? computedPercent : optionalNumber(quote.regularMarketChangePercent) ?? computedPercent; |
| 879 | const name = optionalText(quote.longName) ?? optionalText(quote.shortName); |
| 880 | const currency = optionalText(quote.currency); |
| 881 | const state = chart ? void 0 : optionalText(quote.marketState); |
| 882 | const type = optionalText(chart ? quote.instrumentType : quote.quoteType); |
| 883 | const exchange = optionalText(quote.fullExchangeName) ?? optionalText(chart ? quote.exchangeName : quote.exchange); |
| 884 | const time = timestamp(quote.regularMarketTime); |
| 885 | const value = { |
| 886 | requested_ticker: requested, |
| 887 | ticker: text4(quote.symbol), |
| 888 | price: float(price), |
| 889 | source: source2, |
| 890 | fallback_used: chart, |
| 891 | ...name === void 0 ? {} : { name }, |
| 892 | ...currency === void 0 ? {} : { currency }, |
| 893 | ...change === void 0 ? {} : { change: float(change) }, |
| 894 | ...percent === void 0 ? {} : { change_percent: float(percent) }, |
| 895 | ...previous === void 0 ? {} : { previous_close: float(previous) }, |
| 896 | ...state === void 0 ? {} : { market_state: state }, |
| 897 | ...type === void 0 ? {} : { quote_type: type }, |
| 898 | ...exchange === void 0 ? {} : { exchange }, |
| 899 | ...time === void 0 ? {} : { market_time: time } |
| 900 | }; |
| 901 | return result2(true, value); |
| 902 | } |
| 903 | function summary(value, format) { |
| 904 | const descriptor = row4(value); |
| 905 | const kind = text4(descriptor.kind); |
| 906 | const accepted = format === "json" ? ["object", "array", "string", "number", "boolean", "null"] : ["table", "array", "string", "integer", "float", "boolean", "datetime"]; |
| 907 | if (!accepted.includes(kind)) throw new Error("invalid captured parser kind"); |
| 908 | if (kind === "object" || kind === "table") { |
| 909 | if (!Array.isArray(descriptor.keys) || descriptor.keys.some((key) => typeof key !== "string")) throw new Error("invalid captured parser keys"); |
| 910 | const keys = descriptor.keys; |
| 911 | if (new Set(keys).size !== keys.length) throw new Error("duplicate captured parser keys"); |
| 912 | return { top_level: kind, entries: keys.length, keys_preview: keys.slice(0, 10) }; |
| 913 | } |
| 914 | if (kind === "array") { |
| 915 | if (!Number.isSafeInteger(descriptor.entries) || descriptor.entries < 0) throw new Error("invalid captured array count"); |
| 916 | return { top_level: kind, entries: descriptor.entries }; |
| 917 | } |
| 918 | return { top_level: kind }; |
| 919 | } |
| 920 | function data(input) { |
| 921 | const format = text4(input.format); |
| 922 | if (!["auto", "json", "toml"].includes(format)) throw new Error("invalid captured data format"); |
| 923 | const source2 = text4(input.source); |
| 924 | const extension = optionalText(input.extension); |
| 925 | const requested = format === "auto" && (extension === "json" || extension === "toml") ? extension : format; |
| 926 | const json = row4(input.json); |
| 927 | const toml = row4(input.toml); |
| 928 | if (typeof json.ok !== "boolean" || typeof toml.ok !== "boolean") throw new Error("invalid captured parser result"); |
| 929 | const use = (parser, selected) => { |
| 930 | if (parser.ok) return result2(true, { valid: true, format: selected, source: source2, summary: summary(parser.value, selected) }); |
| 931 | const error = text4(parser.error); |
| 932 | return result2(false, { valid: false, format: selected, source: source2, error }, `Invalid ${selected.toUpperCase()}: ${error}`); |
| 933 | }; |
| 934 | if (requested === "json") return use(json, "json"); |
| 935 | if (requested === "toml") return use(toml, "toml"); |
| 936 | if (json.ok) return use(json, "json"); |
| 937 | if (toml.ok) return use(toml, "toml"); |
| 938 | return result2(false, { valid: false, format: "auto", source: source2, json_error: text4(json.error), toml_error: text4(toml.error) }, "Validation failed in auto mode: content is neither valid JSON nor TOML."); |
| 939 | } |
| 940 | function rustTrim2(value) { |
| 941 | return value.replace(/^[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, ""); |
| 942 | } |
| 943 | function bool3(value) { |
| 944 | if (typeof value !== "boolean") throw new Error("invalid captured boolean"); |
| 945 | return value; |
| 946 | } |
| 947 | function speech(operation, input) { |
| 948 | const surface = text4(input.surface); |
| 949 | if (surface !== "tool" && surface !== "cli") throw new Error("invalid speech surface"); |
| 950 | const tool = surface === "tool"; |
| 951 | const invalid = (error) => result2(false, { error }); |
| 952 | if (operation === "speech_format") { |
| 953 | const raw = text4(input.format); |
| 954 | const format = asciiLower(rustTrim2(raw)); |
| 955 | if (format === "wav" || format === "mp3" || format === "pcm16" || format === "pcm") return result2(true, { format: format === "pcm" ? "pcm16" : format }); |
| 956 | return invalid(tool ? `unsupported speech format '${raw}' (allowed: wav, mp3, pcm16)` : `Unsupported speech format '${raw}' (allowed: wav, mp3, pcm16)`); |
| 957 | } |
| 958 | const modelHint = optionalText(input.model); |
| 959 | const hasClone = bool3(input.has_clone_path); |
| 960 | const hasVoice = bool3(input.has_voice); |
| 961 | const nonemptyVoice = bool3(input.voice_nonempty); |
| 962 | const dataUri = bool3(input.voice_is_data_uri); |
| 963 | if ((nonemptyVoice || dataUri) && !hasVoice) throw new Error("inconsistent captured voice presence"); |
| 964 | const instructionInput = optionalText(input.instruction); |
| 965 | const promptInput = optionalText(input.voice_prompt); |
| 966 | if (hasClone && hasVoice) return invalid(tool ? "use either clone_voice or voice for cloned voice data, not both" : "Use either --clone-voice or --voice for cloned voice data, not both"); |
| 967 | const model = modelHint ?? (hasClone || dataUri ? "mimo-v2.5-tts-voiceclone" : promptInput !== void 0 ? "mimo-v2.5-tts-voicedesign" : "mimo-v2.5-tts"); |
| 968 | const lower = asciiLower(model); |
| 969 | if (!lower.includes("tts")) { |
| 970 | const examples = "mimo-v2.5-tts, mimo-v2.5-tts-voicedesign, mimo-v2.5-tts-voiceclone, mimo-v2-tts"; |
| 971 | return invalid(tool ? `speech tool requires a TTS model (examples: ${examples}), got '${model}'` : `speech requires a TTS model (examples: ${examples}); got ${model}`); |
| 972 | } |
| 973 | const parts = [promptInput, instructionInput].map((value) => value === void 0 ? void 0 : rustTrim2(value)).filter((value) => value !== void 0 && value !== ""); |
| 974 | const instruction = parts.length ? parts.join("\n\n") : null; |
| 975 | if (lower.includes("voicedesign") && instruction === null) return invalid(tool ? "mimo-v2.5-tts-voicedesign requires voice_prompt or instruction" : "mimo-v2.5-tts-voicedesign requires --voice-prompt or --instruction to describe the voice"); |
| 976 | let voice; |
| 977 | if (hasClone) voice = "clone"; |
| 978 | else if (lower.includes("voicedesign")) voice = "omit"; |
| 979 | else if (nonemptyVoice) voice = "raw"; |
| 980 | else if (lower.includes("voiceclone")) return invalid(tool ? "mimo-v2.5-tts-voiceclone requires clone_voice <mp3|wav> or voice <data-uri>" : "mimo-v2.5-tts-voiceclone requires --clone-voice <mp3|wav> or --voice <data-uri>"); |
| 981 | else voice = "default"; |
| 982 | return result2(true, { model, instruction, voice }); |
| 983 | } |
| 984 | function pdfProjection(input) { |
| 985 | const state = text4(input.state); |
| 986 | const decision = (code2, message) => result2(true, { kind: "pdf_decision", code: code2, ...message === void 0 ? {} : { message } }); |
| 987 | if (state !== "complete") { |
| 988 | if (!["binary_unavailable", "cancelled", "timed_out", "execution"].includes(state)) throw new Error("invalid PDF process state"); |
| 989 | return decision(state, text4(input.message)); |
| 990 | } |
| 991 | const success = bool3(input.success); |
| 992 | const stdoutOverflow = bool3(input.stdout_truncated); |
| 993 | const stderrOverflow = bool3(input.stderr_truncated); |
| 994 | const stderr = text4(input.stderr); |
| 995 | const code = input.exit_code; |
| 996 | if (code !== null && (!Number.isInteger(code) || code < -2147483648 || code > 2147483647)) throw new Error("invalid PDF exit code"); |
| 997 | if (stdoutOverflow) return decision("execution", "pdftotext output exceeded the 16777216 byte safety limit"); |
| 998 | if (!success) return decision("execution", `pdftotext failed (exit ${code === null ? "None" : `Some(${code})`}): ${stderr || "no diagnostic output"}${stderrOverflow ? " [truncated]" : ""}`); |
| 999 | return decision("success"); |
| 1000 | } |
| 1001 | function ocrProjection(input) { |
| 1002 | const decision = (code2, trim_end = false, message) => result2(true, { kind: "ocr_decision", code: code2, trim_end, ...message === void 0 ? {} : { message } }); |
| 1003 | const state = text4(input.state), status = text4(input.status); |
| 1004 | if (state === "native") { |
| 1005 | if (Object.keys(input).some((key) => !["kind", "state", "status", "can_fallback", "next_ticket"].includes(key)) || !["success", "error", "unavailable"].includes(status)) throw new Error("invalid OCR Native projection"); |
| 1006 | const fallback = bool3(input.can_fallback); |
| 1007 | if (status === "success") { |
| 1008 | if (fallback || input.next_ticket !== void 0) throw new Error("successful OCR Native step cannot launch fallback"); |
| 1009 | return decision("native_success"); |
| 1010 | } |
| 1011 | if (fallback) { |
| 1012 | if (typeof input.next_ticket !== "string" || input.next_ticket.length < 1 || input.next_ticket.length > 256) throw new Error("OCR fallback has no exact continuation grant"); |
| 1013 | return decision("fallback"); |
| 1014 | } |
| 1015 | if (input.next_ticket !== void 0) throw new Error("OCR fallback is not admitted"); |
| 1016 | if (status === "error") return decision("native_error"); |
| 1017 | return decision("no_backend", false, "image_ocr: no local OCR backend is available. On macOS, update to a version with the Vision framework; on Linux/Windows install tesseract and restart codewhale."); |
| 1018 | } |
| 1019 | if (state !== "tesseract") throw new Error("invalid OCR process stage"); |
| 1020 | if (status === "fault") { |
| 1021 | if (Object.keys(input).some((key) => !["kind", "state", "status"].includes(key))) throw new Error("invalid OCR fault projection"); |
| 1022 | return decision("fault"); |
| 1023 | } |
| 1024 | if (status !== "complete" || Object.keys(input).some((key) => !["kind", "state", "status", "success", "exit_code"].includes(key))) throw new Error("invalid OCR Tesseract projection"); |
| 1025 | const success = bool3(input.success), code = input.exit_code; |
| 1026 | if (code !== null && (!Number.isInteger(code) || code < -2147483648 || code > 2147483647)) throw new Error("invalid OCR exit code"); |
| 1027 | return success ? decision("tesseract_success", true) : decision("execution", false, `tesseract failed (exit ${code === null ? "None" : `Some(${code})`}): `); |
| 1028 | } |
| 1029 | function transformStockSnapshot(value) { |
| 1030 | const snapshot = row4(value); |
| 1031 | const review = snapshot.kind === "stock_adapter" && isReviewOperation(snapshot.operation); |
| 1032 | if (review ? reviewEnvelopeBytes(snapshot) > REVIEW_LIMIT : Buffer.byteLength(JSON.stringify(value)) > MAX_SNAPSHOT) throw new Error(review ? "serialized review snapshot/envelope exceeds 16 MiB" : "adapter snapshot exceeds 1 MiB"); |
| 1033 | if (snapshot.kind === "ocr_process") return ocrProjection(snapshot); |
| 1034 | if (snapshot.kind === "pdf_process") return pdfProjection(snapshot); |
| 1035 | if (snapshot.kind !== "stock_adapter") throw new Error("invalid adapter projection"); |
| 1036 | const input = row4(snapshot.input); |
| 1037 | if (review) return transformReviewSnapshot(snapshot.operation, input); |
| 1038 | switch (snapshot.operation) { |
| 1039 | case "web_filters": |
| 1040 | case "web_request": |
| 1041 | case "web_provider": |
| 1042 | case "web_entries": |
| 1043 | case "web_finalize": |
| 1044 | case "web_extract": |
| 1045 | case "web_images": |
| 1046 | return transformWebSnapshot(snapshot.operation, input); |
| 1047 | case "github_result": |
| 1048 | return transformGithubSnapshot(input); |
| 1049 | case "finance_quote": |
| 1050 | case "finance_chart": |
| 1051 | return finance(snapshot.operation, input); |
| 1052 | case "validate_data": |
| 1053 | return data(input); |
| 1054 | case "speech_options": |
| 1055 | case "speech_format": |
| 1056 | return speech(snapshot.operation, input); |
| 1057 | default: |
| 1058 | throw new Error("unadmitted adapter operation"); |
| 1059 | } |
| 1060 | } |
| 1061 | |
| 1062 | // src/builtin/harness.ts |
| 1063 | var MAX_PENDING = 32; |
| 1064 | var MAX_STDOUT = 1024 * 1024; |
| 1065 | var MAX_STDERR = 64 * 1024; |
| 1066 | function normalizeOutput(value) { |
| 1067 | const out = value; |
| 1068 | if (!out || typeof out.success !== "boolean" || typeof out.stdout !== "string" || typeof out.stderr !== "string" || Buffer.byteLength(out.stdout) > MAX_STDOUT || Buffer.byteLength(out.stderr) > MAX_STDERR) throw new Error("invalid or oversized execution output"); |
| 1069 | if (!out.success) return { ok: false, error: out.stderr ? out.stdout ? `${out.stdout} |
| 1070 | ${out.stderr}` : out.stderr : out.stdout }; |
| 1071 | try { |
| 1072 | const result3 = JSON.parse(out.stdout); |
| 1073 | if (result3 && typeof result3.content === "string" && typeof result3.success === "boolean" && (result3.metadata === void 0 || isJson(result3.metadata))) { |
| 1074 | return { ok: true, result: { content: result3.content, success: result3.success, ...result3.metadata === void 0 ? {} : { metadata: result3.metadata } } }; |
| 1075 | } |
| 1076 | } catch { |
| 1077 | } |
| 1078 | return { ok: true, result: { content: out.stdout, success: true } }; |
| 1079 | } |
| 1080 | function createHarnessModule(rpc, reference) { |
| 1081 | if (reference.plugin_id !== "host:harness") throw new Error("execution runner requires its builtin owner"); |
| 1082 | const owner = Object.freeze(structuredClone(reference)); |
| 1083 | const stop = new AbortController(); |
| 1084 | const pending = /* @__PURE__ */ new Map(); |
| 1085 | let disposed = false; |
| 1086 | return { |
| 1087 | async run(params, signal) { |
| 1088 | if (disposed || signal.aborted || params.owner.plugin_id !== owner.plugin_id || params.owner.generation !== owner.generation || params.owner.owner_token !== owner.owner_token) throw new Error("execution runner owner is stale or cancelled"); |
| 1089 | if (!params.execution_id || !params.ticket || !Number.isSafeInteger(params.deadline_ms) || params.deadline_ms < 1 || params.deadline_ms > (params.hook ? 2147483647 : 12e4) || pending.size >= MAX_PENDING || pending.has(params.execution_id)) throw new Error("execution is not admitted"); |
| 1090 | if (params.hook) { |
| 1091 | const { event, dialect, point, matcher, query } = params.hook; |
| 1092 | if (typeof event !== "string" || typeof point !== "string" || typeof query !== "string" || query.length > 1024 || matcher !== void 0 && (typeof matcher !== "string" || matcher.length > 1024) || !["codewhale", "claude-code", "codex"].includes(dialect)) throw new Error("invalid hook projection"); |
| 1093 | if (dialect !== "codewhale" && !matchesMatcher(matcher, query, dialect)) return { ok: true, hook_skipped: true }; |
| 1094 | } |
| 1095 | const abort = new AbortController(); |
| 1096 | const cancel = () => abort.abort(); |
| 1097 | signal.addEventListener("abort", cancel, { once: true }); |
| 1098 | stop.signal.addEventListener("abort", cancel, { once: true }); |
| 1099 | const timer = setTimeout(cancel, params.deadline_ms); |
| 1100 | const done = (async () => { |
| 1101 | const cancelled = new Promise((_, reject) => { |
| 1102 | abort.signal.addEventListener("abort", () => reject(new Error("execution cancelled")), { once: true }); |
| 1103 | }); |
| 1104 | let value = await Promise.race([rpc.request("exec/redeem", { owner, execution_id: params.execution_id, ticket: params.ticket }, abort.signal), cancelled]); |
| 1105 | if (disposed || abort.signal.aborted) throw new Error("execution cancelled"); |
| 1106 | if (params.hook) { |
| 1107 | const out = value; |
| 1108 | if (!out || out.kind !== "hook" || out.event !== params.hook.event || typeof out.success !== "boolean" || out.exit_code !== null && !Number.isInteger(out.exit_code)) throw new Error("invalid hook completion projection"); |
| 1109 | if (params.hook.event === "shell_env") { |
| 1110 | if ("stdout" in out || "stderr" in out || Object.keys(out).some((key) => !["kind", "event", "success", "exit_code", "keys"].includes(key))) throw new Error("ShellEnv projection contains private process data"); |
| 1111 | return { ok: true, hook_completed: true }; |
| 1112 | } |
| 1113 | if (params.hook.dialect === "codewhale") return { ok: true, hook_completed: true }; |
| 1114 | if (typeof out.stdout !== "string" || typeof out.stderr !== "string" || Buffer.byteLength(out.stdout) > 65536 || Buffer.byteLength(out.stderr) > 65536) throw new Error("invalid dialect hook output"); |
| 1115 | const decoded = parseHookOutput(out.exit_code === null ? void 0 : out.exit_code, out.stdout, out.stderr, params.hook.point); |
| 1116 | const merged = mergeHookOutputs([decoded]); |
| 1117 | if (params.hook.event === "tool_call_before") return { ok: true, hook_completed: true, proposal: JSON.stringify({ ...merged.decision === "deny" ? { decision: "deny", reason: merged.reason ?? "Blocked by hook" } : merged.decision === "ask" && params.hook.dialect !== "codex" ? { decision: "ask", reason: merged.reason } : {}, ...merged.additionalContext.length ? { additional_context: merged.additionalContext.join("\n\n") } : {} }) }; |
| 1118 | if (params.hook.event === "message_submit" && (merged.stop || decoded.updatedInput !== void 0 || merged.additionalContext.length)) throw new Error("hook requested unsupported prompt steering"); |
| 1119 | if (params.hook.event === "message_submit") return { ok: true, hook_completed: true, proposal: JSON.stringify(merged.decision === "deny" ? { block: true, reason: merged.reason ?? "Blocked by hook" } : {}) }; |
| 1120 | if (merged.stop || merged.decision === "deny" || decoded.updatedInput !== void 0 || merged.additionalContext.length) throw new Error("hook requested steering unavailable at this observer boundary"); |
| 1121 | return { ok: true, hook_completed: true }; |
| 1122 | } |
| 1123 | if (value && typeof value === "object" && value.kind === "ocr_process") { |
| 1124 | if (value.state !== "native") throw new Error("OCR must begin with its admitted Native step"); |
| 1125 | const first = transformStockSnapshot(value); |
| 1126 | if (first.result.metadata.code !== "fallback") return first; |
| 1127 | const next = value.next_ticket; |
| 1128 | value = await Promise.race([rpc.request("exec/redeem", { owner, execution_id: params.execution_id, ticket: next }, abort.signal), cancelled]); |
| 1129 | if (disposed || abort.signal.aborted) throw new Error("execution cancelled"); |
| 1130 | if (!value || typeof value !== "object" || value.kind !== "ocr_process" || value.state !== "tesseract") throw new Error("OCR continuation returned the wrong stage"); |
| 1131 | return transformStockSnapshot(value); |
| 1132 | } |
| 1133 | if (value && typeof value === "object" && ["stock_adapter", "pdf_process"].includes(String(value.kind))) return transformStockSnapshot(value); |
| 1134 | return normalizeOutput(value); |
| 1135 | })(); |
| 1136 | pending.set(params.execution_id, { abort, done }); |
| 1137 | try { |
| 1138 | return await done; |
| 1139 | } finally { |
| 1140 | clearTimeout(timer); |
| 1141 | signal.removeEventListener("abort", cancel); |
| 1142 | stop.signal.removeEventListener("abort", cancel); |
| 1143 | pending.delete(params.execution_id); |
| 1144 | } |
| 1145 | }, |
| 1146 | async dispose() { |
| 1147 | if (disposed) return; |
| 1148 | disposed = true; |
| 1149 | stop.abort(); |
| 1150 | await Promise.allSettled([...pending.values()].map((row5) => row5.done)); |
| 1151 | } |
| 1152 | }; |
| 1153 | } |
| 1154 | export { |
| 1155 | createHarnessModule, |
| 1156 | normalizeOutput |
| 1157 | }; |
| 1158 |