返回 CodeWhale
stock-adapters.mjs
根目录 / crates / tui / extension-host / dist / stock-adapters.mjs
1 // src/builtin/shared/web-adapters.ts
2 var own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
3 function row(value) {
4 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid captured web record");
5 return value;
6 }
7 function text(value) {
8 if (typeof value !== "string") throw new Error("invalid captured web text");
9 return value;
10 }
11 function string(value) {
12 return typeof value === "string" ? value : void 0;
13 }
14 function array(value) {
15 return Array.isArray(value) ? value : [];
16 }
17 function count(value, max = 10) {
18 if (!Number.isSafeInteger(value) || value < 0 || value > max) throw new Error("invalid captured web count");
19 return value;
20 }
21 function rustTrim(value) {
22 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, "");
23 }
24 function firstPresent(value, keys) {
25 for (const key of keys) if (own(value, key)) return value[key];
26 return void 0;
27 }
28 function firstText(value, keys) {
29 for (const key of keys) {
30 const found = string(value[key]);
31 if (found !== void 0) {
32 const trimmed = rustTrim(found);
33 if (trimmed) return trimmed;
34 }
35 }
36 return void 0;
37 }
38 function integer(facts, path) {
39 const value = facts[path];
40 if (value === void 0) return void 0;
41 const number2 = row(value);
42 if (number2.i64 === null || number2.i64 === void 0) return void 0;
43 const result3 = text(number2.i64);
44 if (!/^-?\d{1,19}$/.test(result3) || BigInt(result3) < -9223372036854775808n || BigInt(result3) > 9223372036854775807n) throw new Error("invalid captured web integer");
45 return result3;
46 }
47 function number(facts, path) {
48 const value = facts[path];
49 if (value === void 0) return 0;
50 const result3 = Number(text(row(value).score));
51 if (!Number.isFinite(result3)) throw new Error("invalid captured web score");
52 return result3;
53 }
54 function result(metadata) {
55 return { ok: true, result: { success: true, content: "", metadata } };
56 }
57 function filters(input) {
58 const recency = input.recency_days === null || input.recency_days === void 0 ? void 0 : count(input.recency_days, 3650);
59 const locale = input.locale === null || input.locale === void 0 ? void 0 : rustTrim(text(input.locale));
60 const parts = locale?.split(/[-_]/);
61 const language = parts?.[0] !== void 0 && /^[A-Za-z]{2,3}$/.test(parts[0]) ? parts[0].toLowerCase() : null;
62 const region = parts?.[1] !== void 0 && /^[A-Za-z]{2}$/.test(parts[1]) ? parts[1].toUpperCase() : null;
63 const window = recency === void 0 ? null : recency <= 1 ? "day" : recency <= 7 ? "week" : recency <= 31 ? "month" : "year";
64 return { kind: "web_filters", window, language, region };
65 }
66 function validJson(value) {
67 const pending = [{ value, depth: 0 }];
68 while (pending.length) {
69 const item = pending.pop();
70 if (typeof item.value === "number" && !Number.isFinite(item.value)) return false;
71 if (typeof item.value === "string" && [...item.value].some((ch) => ch.length === 1 && ch.charCodeAt(0) >= 55296 && ch.charCodeAt(0) <= 57343)) return false;
72 if (item.value && typeof item.value === "object") {
73 if (item.depth >= 128) return false;
74 for (const child of Object.values(item.value)) pending.push({ value: child, depth: item.depth + 1 });
75 }
76 }
77 return true;
78 }
79 function provider(input) {
80 const backend = text(input.backend), limit = count(input.max_results), parsed = row(input.parsed), facts = row(input.number_facts);
81 let entries2 = [], error = null;
82 let items = [], titleKeys = ["title"], urlKeys = ["url"], snippetKeys = ["content", "snippet"], trim2 = true, capSnippet = false, scorePath;
83 switch (backend) {
84 case "tavily":
85 items = array(parsed.results);
86 break;
87 case "firecrawl": {
88 const data2 = parsed.data;
89 items = array(data2 && typeof data2 === "object" && !Array.isArray(data2) && own(row(data2), "web") ? row(data2).web : data2);
90 snippetKeys = ["description", "markdown", "content"];
91 capSnippet = true;
92 if (parsed.success === false) error = `Firecrawl search failed: ${firstText(parsed, ["error", "message"]) ?? "unknown API error"}`;
93 break;
94 }
95 case "metaso": {
96 items = array(parsed.webpages);
97 urlKeys = ["link"];
98 snippetKeys = ["snippet", "summary"];
99 const code = integer(facts, "/code");
100 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"})`;
101 break;
102 }
103 case "bocha": {
104 const data2 = parsed.data && typeof parsed.data === "object" && !Array.isArray(parsed.data) ? row(parsed.data) : void 0;
105 let pages;
106 if (data2) {
107 const web = data2.webPages && typeof data2.webPages === "object" && !Array.isArray(data2.webPages) ? row(data2.webPages) : void 0;
108 pages = web && own(web, "value") ? web.value : data2.pages;
109 }
110 if (pages === void 0) pages = parsed.pages;
111 items = array(pages);
112 titleKeys = ["name", "title"];
113 urlKeys = ["url", "link"];
114 snippetKeys = ["summary", "snippet", "description"];
115 const code = integer(facts, "/code");
116 if (code !== void 0 && code !== "0" && code !== "200") error = `Bocha search API error (code ${code}: ${string(firstPresent(parsed, ["msg", "message"])) ?? "unknown error"})`;
117 break;
118 }
119 case "baidu": {
120 items = array(parsed.references);
121 titleKeys = ["title", "name"];
122 urlKeys = ["url", "link"];
123 snippetKeys = ["content", "snippet", "summary"];
124 const key = own(parsed, "error_code") ? "error_code" : "code", code = integer(facts, `/${key}`);
125 if (code !== void 0 && code !== "0") error = `Baidu search API error (code ${code}: ${string(firstPresent(parsed, ["error_msg", "message"])) ?? "unknown error"})`;
126 break;
127 }
128 case "searxng":
129 items = array(parsed.results);
130 scorePath = "/results";
131 break;
132 case "sofya":
133 items = array(parsed.results);
134 snippetKeys = ["content", "description"];
135 trim2 = false;
136 break;
137 case "serply":
138 items = array(parsed.results);
139 urlKeys = ["link"];
140 snippetKeys = ["description", "snippet"];
141 trim2 = false;
142 break;
143 case "volcengine": {
144 if (own(parsed, "error")) {
145 const value = parsed.error && typeof parsed.error === "object" && !Array.isArray(parsed.error) ? row(parsed.error) : {};
146 error = `Volcengine API error (code ${string(value.code) ?? "unknown"}: ${string(value.message) ?? "no details"})`;
147 }
148 let response;
149 const message = array(parsed.output).slice().reverse().find((value) => value && typeof value === "object" && !Array.isArray(value) && row(value).type === "message");
150 if (message) {
151 const content = array(row(message).content).find((value) => value && typeof value === "object" && !Array.isArray(value) && typeof row(value).text === "string");
152 if (content) response = text(row(content).text);
153 }
154 if (response === void 0 && error === null) error = "Volcengine response contains no output text";
155 if (response !== void 0) {
156 let body = response;
157 const fence2 = response.indexOf("```json");
158 if (fence2 >= 0) {
159 const rest = response.slice(fence2 + 7), end = rest.indexOf("```");
160 if (end >= 0) body = rustTrim(rest.slice(0, end));
161 else {
162 const first = response.indexOf("{"), last = response.lastIndexOf("}");
163 if (first >= 0 && last >= first) body = response.slice(first, last + 1);
164 }
165 } else {
166 const first = response.indexOf("{"), last = response.lastIndexOf("}");
167 if (first >= 0 && last >= first) body = response.slice(first, last + 1);
168 }
169 try {
170 const value = JSON.parse(body);
171 if (!validJson(value)) throw new Error("model JSON failed Core-compatible scalar/depth guard");
172 if (value && typeof value === "object" && !Array.isArray(value)) items = array(row(value).results);
173 } catch {
174 items = [];
175 }
176 }
177 snippetKeys = ["snippet"];
178 break;
179 }
180 default:
181 throw new Error("unadmitted web provider");
182 }
183 const scored = [];
184 for (const [index, value] of items.entries()) {
185 if (!value || typeof value !== "object" || Array.isArray(value)) continue;
186 const item = row(value), rawTitle = string(firstPresent(item, titleKeys)), rawUrl = string(firstPresent(item, urlKeys));
187 if (rawTitle === void 0 || rawUrl === void 0) continue;
188 const title = trim2 ? rustTrim(rawTitle) : rawTitle, url = trim2 ? rustTrim(rawUrl) : rawUrl;
189 if (trim2 && (!title || !url)) continue;
190 const presentSnippet = ["bocha", "baidu", "volcengine"].includes(backend);
191 const selected = presentSnippet ? string(firstPresent(item, snippetKeys)) : void 0;
192 const snippet = presentSnippet ? selected === void 0 ? void 0 : rustTrim(selected) || void 0 : firstText(item, snippetKeys);
193 const entry = { title, url, ...snippet === void 0 ? {} : { snippet: capSnippet ? [...snippet].slice(0, 1e3).join("") : snippet } };
194 scored.push({ entry, score: scorePath === void 0 ? 0 : number(facts, `${scorePath}/${index}/score`) });
195 if (scorePath === void 0 && scored.length === limit) break;
196 }
197 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);
198 entries2 = scored.slice(0, limit).map((value) => value.entry);
199 return { kind: "web_provider", entries: entries2, error };
200 }
201 function extraction(input) {
202 if (!Array.isArray(input.candidates) || input.candidates.length > 3) throw new Error("invalid captured web regions");
203 const facts = [];
204 let previous = -1;
205 for (const value of input.candidates) {
206 const candidate = row(value), id = count(candidate.id, 2);
207 if (Object.keys(candidate).some((key) => !["id", "non_whitespace", "words"].includes(key)) || id <= previous) throw new Error("invalid ordered captured web region");
208 previous = id;
209 facts.push({ id, chars: count(candidate.non_whitespace, 1e8), words: count(candidate.words, 1e8) });
210 }
211 return { kind: "web_extract", candidate: facts.find((value) => value.chars >= 32 && value.words >= 5)?.id ?? null };
212 }
213 function images(input) {
214 const limit = count(input.max_results), parsed = row(input.parsed);
215 if (!Array.isArray(parsed.results)) throw new Error("invalid captured image results");
216 const entries2 = [];
217 for (const value of parsed.results) {
218 const entry = row(value), image = text(entry.image);
219 for (const key of ["thumbnail", "title", "url", "source"]) if (entry[key] !== null && entry[key] !== void 0) text(entry[key]);
220 for (const key of ["width", "height"]) if (entry[key] !== null && entry[key] !== void 0) count(entry[key], 4294967295);
221 if (!rustTrim(image)) continue;
222 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]])) });
223 }
224 return { kind: "web_images", entries: entries2, max_results: limit };
225 }
226 function request(input) {
227 const backend = text(input.backend), query = text(input.query), max = count(input.max_results);
228 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);
229 const locale = input.locale === null || input.locale === void 0 ? void 0 : text(input.locale);
230 let payload = null, pairs = [];
231 switch (backend) {
232 case "firecrawl":
233 payload = { query, limit: max, sources: [{ type: "web" }], ...window === void 0 ? {} : { tbs: `qdr:${window[0]}` }, ...region === void 0 ? {} : { country: region } };
234 break;
235 case "tavily":
236 payload = { query, search_depth: "basic", max_results: max, ...window === void 0 ? {} : { time_range: window } };
237 break;
238 case "bocha":
239 payload = { query, freshness: "noLimit", count: max };
240 break;
241 case "metaso":
242 payload = { q: query, scope: "webpage", size: Math.max(1, Math.min(100, max)) };
243 break;
244 case "sofya":
245 payload = { query, max_results: max };
246 break;
247 case "baidu":
248 payload = { messages: [{ role: "user", content: query }], search_source: "baidu_search_v2", resource_type_filter: [{ type: "web", top_k: max }] };
249 break;
250 case "volcengine":
251 payload = { input: [{ role: "user", content: [{ type: "input_text", text: `Search the web for: ${query}
252
253 CRITICAL: Respond ONLY with a valid JSON object. No markdown, no explanation.
254 Schema: {"results":[{"title":"...","url":"https://...","snippet":"..."}]}
255 - results: 1-${max} most relevant pages
256 - title: page title (required)
257 - url: full URL starting with https:// (required)
258 - snippet: 1-2 sentence factual summary (required)
259 - If zero results: {"results":[]}
260 - Your entire response must be valid, parseable JSON.` }] }] };
261 break;
262 case "serply":
263 pairs = [["q", query], ["num", String(max)], ...language === void 0 ? [] : [["hl", language]], ...region === void 0 ? [] : [["gl", region.toLowerCase()]]];
264 break;
265 case "searxng":
266 pairs = [["q", query], ["format", "json"], ...window === void 0 ? [] : [["time_range", window]], ...locale === void 0 ? [] : [["language", locale]]];
267 break;
268 case "bing":
269 case "duckduckgo":
270 pairs = [["q", query]];
271 break;
272 default:
273 throw new Error("unadmitted web request provider");
274 }
275 return { kind: "web_request", payload, pairs };
276 }
277 function entries(input) {
278 if (!Array.isArray(input.entries)) throw new Error("invalid captured web candidates");
279 const values = input.entries.map((value) => {
280 const item = row(value);
281 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) } };
282 });
283 return { kind: "web_entries", entries: values };
284 }
285 function finalization(input) {
286 const requested = row(input.requested), capabilities = row(input.capabilities), counted = count(input.count);
287 if (!Array.isArray(input.degraded)) throw new Error("invalid captured web receipt");
288 const degraded = input.degraded.map((value) => row(value));
289 const ignored = (knob) => degraded.some((value) => row(value).kind === "knob_ignored" && row(value).knob === knob);
290 const honored = { max_results: capabilities.max_results === "supported", recency: false, domains: requested.domains === true, locale: false };
291 for (const knob of ["recency", "locale"]) {
292 if (knob === "locale") {
293 if (!Array.isArray(input.domain_extra)) throw new Error("invalid captured domain receipt");
294 degraded.push(...input.domain_extra.map((value) => row(value)));
295 }
296 if (requested[knob] === true && !ignored(knob)) {
297 if (capabilities[knob] === "supported") honored[knob] = true;
298 else degraded.push({ kind: "knob_ignored", knob });
299 }
300 }
301 const hasNote = input.has_note === true;
302 const prefix = counted === 0 ? hasNote ? "No results found. " : "No results found" : `Found ${counted} result(s)${hasNote ? ". " : ""}`;
303 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]" : "";
304 return { kind: "web_finalize", honored, degraded, prefix, suffix };
305 }
306 function transformWebSnapshot(operation, value) {
307 const input = row(value);
308 switch (operation) {
309 case "web_request":
310 return result(request(input));
311 case "web_entries":
312 return result(entries(input));
313 case "web_finalize":
314 return result(finalization(input));
315 case "web_filters":
316 return result(filters(input));
317 case "web_provider":
318 return result(provider(input));
319 case "web_extract":
320 return result(extraction(input));
321 case "web_images":
322 return result(images(input));
323 default:
324 throw new Error("unadmitted web adapter operation");
325 }
326 }
327
328 // src/builtin/shared/github-adapter.ts
329 var REPOSITORY = "Hmbown/CodeWhale";
330 function row2(value) {
331 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid GitHub snapshot");
332 return value;
333 }
334 function text2(value) {
335 if (typeof value !== "string") throw new Error("invalid GitHub text");
336 return value;
337 }
338 function strings(value) {
339 if (!Array.isArray(value)) throw new Error("invalid GitHub list");
340 return value.map(text2);
341 }
342 function githubSummary(value, limit) {
343 let out = "", index = 0;
344 for (const char of value) {
345 if (index++ >= Math.max(0, limit - 3)) return `${out}...`;
346 const code = char.codePointAt(0);
347 if ((code <= 31 || code >= 127 && code <= 159) && char !== "\n" && char !== " ") continue;
348 out += char;
349 }
350 return out;
351 }
352 function renderIssueReview(value) {
353 const report2 = row2(value), fields = row2(report2.fields), id = text2(report2.id);
354 let out = `# ${text2(fields.title)}
355
356 Draft: ${id}
357 Status: ready for review
358 Publication: unavailable
359 Duplicate search: not performed
360 Destination: ${REPOSITORY}
361
362 Review the contents before sharing. Redaction does not guarantee privacy.
363 `;
364 if (report2.revises !== null) out += `Revises: ${text2(report2.revises)}
365 `;
366 for (const [heading, key] of [["Expected behavior", "expected"], ["Actual behavior", "actual"], ["Impact", "impact"]]) out += `
367 ## ${heading}
368
369 ${text2(fields[key])}
370 `;
371 for (const [heading, key] of [["Steps to reproduce (agent reported)", "steps"], ["Observed by the agent", "observed"], ["Inferences (not verified)", "inferred"]]) {
372 const items = strings(fields[key]);
373 out += `
374 ## ${heading}
375
376 `;
377 out += items.length ? items.map((item) => `- ${item}
378 `).join("") : "None recorded.\n";
379 }
380 out += `
381 ## Runtime context
382
383 - Codewhale: ${text2(report2.version)}
384 - Platform: ${text2(report2.platform)}
385 - Active model: ${text2(report2.model)}
386 `;
387 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])}
388 `;
389 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");
390 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})
391 `).join("");
392 const redactions = strings(report2.redactions);
393 if (redactions.length) out += `
394 Redacted categories: ${redactions.join(", ")}
395 `;
396 return out + `
397 Review: \`/feedback review ${id}\`
398 Revise: \`/feedback edit ${id} <change>\`
399 `;
400 }
401 function transformGithubSnapshot(value) {
402 const snapshot = row2(value), action = text2(snapshot.action);
403 if (action === "report_draft" || action === "report_read" || action === "report_review") {
404 const report2 = row2(snapshot.report), id = text2(report2.id);
405 const review = renderIssueReview(report2);
406 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` });
407 return { ok: true, result: { success: true, content: content2, metadata: null } };
408 }
409 const number2 = text2(snapshot.number);
410 if (!/^[0-9]{1,20}$/.test(number2)) throw new Error("invalid captured issue number");
411 if (action === "issue_context" || action === "pr_context") {
412 const raw = structuredClone(row2(snapshot.raw)), kind = action === "issue_context" ? "issue" : "pr";
413 if (snapshot.large_body === true) {
414 const body = text2(raw.body);
415 raw.body_summary = githubSummary(body, 900);
416 raw.body_artifact = snapshot.body_artifact;
417 raw.body = githubSummary(body, 1200);
418 }
419 if (action === "pr_context" && snapshot.diff !== null) {
420 raw.diff_summary = githubSummary(text2(snapshot.diff), 900);
421 raw.diff_artifact = snapshot.diff_artifact;
422 }
423 const subject2 = kind === "issue" ? "Issue" : "PR";
424 return { ok: true, result: { success: true, metadata: null, content: JSON.stringify({ summary: `${subject2} #${number2}: ${typeof raw.title === "string" ? raw.title : ""}`, [kind]: raw }) } };
425 }
426 const target = text2(snapshot.target);
427 if (!["issue", "pr"].includes(target)) throw new Error("invalid captured GitHub target");
428 const subject = target === "issue" ? "issue" : "PR";
429 let content;
430 if (snapshot.dirty === true) content = `Refusing to close ${subject}: worktree is dirty and allow_dirty was false.`;
431 else if (action === "comment") content = snapshot.dry_run === true ? `Dry run: would comment on ${target} #${number2}.` : `Commented on ${target} #${number2}.`;
432 else if (action === "close_issue" || action === "close_pr") content = snapshot.dry_run === true ? `Dry run: would close ${subject} #${number2}.` : `Closed ${subject} #${number2}.`;
433 else throw new Error("unknown captured GitHub operation");
434 return { ok: true, result: { success: snapshot.dirty !== true, content, metadata: null } };
435 }
436
437 // src/json.ts
438 function isJson(value, depth = 0) {
439 if (depth > 64) return false;
440 if (value === null) return true;
441 switch (typeof value) {
442 case "boolean":
443 case "string":
444 return true;
445 case "number":
446 return Number.isFinite(value);
447 case "object": {
448 const array2 = Array.isArray(value);
449 if (!array2 && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
450 const keys = Reflect.ownKeys(value);
451 if (array2 && keys.length !== value.length + 1) return false;
452 for (const key of keys) {
453 if (array2 && key === "length") continue;
454 if (typeof key !== "string") return false;
455 if (array2 && (!/^(0|[1-9]\d*)$/u.test(key) || Number(key) >= value.length)) return false;
456 const descriptor = Object.getOwnPropertyDescriptor(value, key);
457 if (!descriptor.enumerable || !("value" in descriptor) || !isJson(descriptor.value, depth + 1)) return false;
458 }
459 return true;
460 }
461 default:
462 return false;
463 }
464 }
465
466 // src/builtin/shared/review-adapter.ts
467 var REVIEW_LIMIT = 16 * 1024 * 1024;
468 var OPERATIONS = ["review_source_prompt", "review_pass_prompt", "review_interactive_pr", "review_report"];
469 function isReviewOperation(value) {
470 return typeof value === "string" && OPERATIONS.includes(value);
471 }
472 function reviewEnvelopeBytes(value) {
473 return 8 + Buffer.byteLength(`{"jsonrpc":"2.0","id":18446744073709551615,"result":${JSON.stringify(value)}}`);
474 }
475 function row3(value) {
476 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid Core review snapshot");
477 return value;
478 }
479 function text3(value) {
480 if (typeof value !== "string") throw new Error("invalid Core review text");
481 return value;
482 }
483 function bool(value) {
484 if (typeof value !== "boolean") throw new Error("invalid Core review flag");
485 return value;
486 }
487 function integer2(value) {
488 if (!Number.isSafeInteger(value) || value < 0) throw new Error("invalid Core review count");
489 return value;
490 }
491 function list(value) {
492 if (!Array.isArray(value)) throw new Error("invalid Core review list");
493 return value;
494 }
495 function optional(value) {
496 return value === null || value === void 0 ? void 0 : text3(value);
497 }
498 function trim(value) {
499 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, "");
500 }
501 function lines(value) {
502 if (!value.length) return [];
503 const out = value.split("\n");
504 if (value.endsWith("\n")) out.pop();
505 return out.map((line, i) => (i < out.length - 1 || value.endsWith("\n")) && line.endsWith("\r") ? line.slice(0, -1) : line);
506 }
507 function skipped(value) {
508 return list(value).map((value2) => {
509 const skip = row3(value2);
510 return `${text3(skip.file)} (${integer2(skip.chars)} chars; ${text3(skip.reason)})`;
511 }).join(", ");
512 }
513 function ordered(value) {
514 if (Array.isArray(value)) return value.map(ordered);
515 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)]));
516 return value;
517 }
518 function source(input) {
519 const kind = text3(input.kind);
520 if (kind === "file") {
521 const content = lines(text3(input.content)).map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
522 return `Review the following file and provide feedback.
523 Path: ${text3(input.display)}
524
525 ${content}
526
527 End of file.`;
528 }
529 if (kind === "diff") return `Review the following ${text3(input.label)} and provide feedback.
530
531 ${text3(input.diff)}
532
533 End of diff.`;
534 if (kind === "cli_diff") return `Review the following diff and provide feedback:
535
536 ${text3(input.diff)}
537
538 End of diff.`;
539 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.
540
541 ${text3(input.diff)}
542
543 End of diff.`;
544 throw new Error("unadmitted review source");
545 }
546 function pass(input) {
547 const manifest = row3(input.manifest), part = row3(input.pass), view = row3(input.view);
548 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.`;
549 if (!isJson(input.context)) throw new Error("invalid Core review context");
550 const prompt = {
551 task,
552 untrusted_repository_data: true,
553 pull_request: { number: integer2(input.number), title: text3(view.title), description: text3(view.body) },
554 manifest,
555 pass: part,
556 diff: text3(input.diff),
557 repository_context: input.context,
558 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."
559 };
560 return JSON.stringify(bool(input.sort_keys) ? ordered(prompt) : prompt);
561 }
562 function interactive(input) {
563 const view = row3(input.view), number2 = integer2(input.number);
564 const base = text3(view.base), head = text3(view.head);
565 const branches = base && head ? `${base} ← ${head}` : base || head || "(unknown)";
566 return `Review PR #${number2} — ${trim(text3(view.title)) || `(PR #${number2})`}
567
568 URL: ${text3(view.url) || "(unavailable)"}
569 Branches: ${branches}
570 Revision: ${text3(view.head_sha)} (base ${text3(view.base_sha)}); ${integer2(view.changed_files)} file patches.
571 Binary changes are represented by metadata; their contents are not semantically inspected. Exact binary object IDs remain in the review evidence.
572
573 ## Description
574
575 ${trim(text3(view.body)) || "(no description)"}
576
577 ## Diff
578
579 \`\`\`diff
580 ${text3(input.diff)}
581 \`\`\`
582 `;
583 }
584 function fence(value) {
585 let longest = 0, run = 0;
586 for (const char of value) {
587 run = char === "`" ? run + 1 : 0;
588 longest = Math.max(longest, run);
589 }
590 return "`".repeat(Math.max(3, longest + 1));
591 }
592 function location(value) {
593 const path = optional(value.path);
594 return path === void 0 ? "" : value.line === null || value.line === void 0 ? `\`${trim(path)}\`` : `\`${trim(path)}:${integer2(value.line)}\``;
595 }
596 function report(input) {
597 if (input.review === null) return text3(input.output);
598 const review = row3(input.review), posted = bool(input.posted);
599 let out = "## Codewhale review\n\n";
600 const summary2 = text3(review.summary), assessment = text3(review.overall_assessment);
601 if (summary2.length) out += trim(summary2) + "\n\n";
602 const issues = list(review.issues), suggestions = list(review.suggestions);
603 if (issues.length) {
604 out += "### Findings\n\n";
605 for (const item of issues) {
606 const issue = row3(item), at = location(issue);
607 out += `- **[${text3(issue.severity).toUpperCase()}] ${text3(issue.title)}**${at ? ` (${at})` : ""}
608 `;
609 const description = text3(issue.description);
610 if (description.length) out += ` ${description}
611 `;
612 }
613 out += "\n";
614 }
615 if (suggestions.length) {
616 out += "### Suggestions\n\n";
617 for (const item of suggestions) {
618 const suggestion = row3(item), at = location(suggestion);
619 out += `- ${at ? `${at} — ` : ""}${text3(suggestion.suggestion)}
620 `;
621 const replacement = optional(suggestion.replacement);
622 if (replacement !== void 0 && trim(replacement).length) {
623 const codeFence = fence(replacement);
624 out += `
625 ${codeFence}${posted ? "text" : "suggestion"}
626 `;
627 for (const line of replacement.split("\n")) out += ` ${line}
628 `;
629 out += ` ${codeFence}
630 `;
631 }
632 }
633 out += "\n";
634 }
635 if (assessment.length) out += "### Assessment\n\n" + trim(assessment) + "\n\n";
636 return out;
637 }
638 function transformReviewSnapshot(operation, value) {
639 if (!isReviewOperation(operation)) throw new Error("unadmitted review operation");
640 const input = row3(value);
641 const content = operation === "review_source_prompt" ? source(input) : operation === "review_pass_prompt" ? pass(input) : operation === "review_interactive_pr" ? interactive(input) : report(input);
642 const result3 = { ok: true, result: { content, success: true, metadata: null } };
643 if (reviewEnvelopeBytes(result3) > REVIEW_LIMIT) throw new Error("serialized review result exceeds 16 MiB");
644 return result3;
645 }
646
647 // src/builtin/shared/stock-adapters.ts
648 var MAX_SNAPSHOT = 1024 * 1024;
649 function row4(value) {
650 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid adapter snapshot");
651 return value;
652 }
653 function text4(value) {
654 if (typeof value !== "string") throw new Error("invalid adapter text");
655 return value;
656 }
657 function optionalText(value) {
658 return value === null || value === void 0 ? void 0 : text4(value);
659 }
660 function optionalNumber(value) {
661 if (value === null || value === void 0) return void 0;
662 if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("invalid captured numeric field");
663 return value;
664 }
665 function float(value) {
666 if (Object.is(value, -0)) return "-0";
667 if (value === Infinity) return "inf";
668 if (value === -Infinity) return "-inf";
669 return String(value);
670 }
671 function timestamp(value) {
672 const time = optionalText(value);
673 if (time === void 0) return void 0;
674 if (!/^-?\d{1,19}$/.test(time)) throw new Error("invalid captured timestamp");
675 const integer3 = BigInt(time);
676 if (integer3 < -9223372036854775808n || integer3 > 9223372036854775807n) throw new Error("captured timestamp exceeds i64");
677 return time;
678 }
679 function result2(success, metadata, content = "") {
680 return { ok: true, result: { success, content, metadata } };
681 }
682 function failure(endpoint, kind, detail) {
683 return result2(false, { endpoint, kind, detail });
684 }
685 function asciiLower(value) {
686 return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
687 }
688 function finance(operation, input) {
689 const request2 = row4(input.request);
690 const requested = text4(request2.requested_ticker);
691 const symbol = text4(request2.resolved_symbol);
692 const parsed = row4(input.parsed);
693 const chart = operation === "finance_chart";
694 const source2 = chart ? "yahoo_chart" : "yahoo_quote";
695 let quote;
696 if (chart) {
697 const body = row4(parsed.chart);
698 if (body.error !== null && body.error !== void 0) {
699 const error = row4(body.error);
700 const description = optionalText(error.description) ?? "chart endpoint returned an error";
701 const code = optionalText(error.code);
702 const notFound = (code === void 0 ? void 0 : asciiLower(code)) === "not found" || asciiLower(description).includes("not found") || asciiLower(description).includes("symbol may be delisted");
703 return failure(source2, notFound ? "not_found" : "upstream", description);
704 }
705 if (body.result !== null && body.result !== void 0 && !Array.isArray(body.result)) throw new Error("invalid captured chart results");
706 const entries2 = body.result;
707 if (!entries2?.length) return failure(source2, "not_found", `no chart data for symbol '${symbol}'`);
708 quote = row4(row4(entries2[0]).meta);
709 } else {
710 const body = row4(parsed.quoteResponse);
711 if (!Array.isArray(body.result)) throw new Error("invalid captured quote results");
712 const candidate = body.result.map(row4).find((value2) => asciiLower(text4(value2.symbol)) === asciiLower(symbol));
713 if (!candidate) return failure(source2, "not_found", `no result for symbol '${symbol}'`);
714 quote = candidate;
715 }
716 const price = optionalNumber(quote.regularMarketPrice);
717 if (price === void 0) return failure(source2, "upstream", "response missing regularMarketPrice");
718 const previous = chart ? optionalNumber(quote.chartPreviousClose) ?? optionalNumber(quote.previousClose) : optionalNumber(quote.regularMarketPreviousClose);
719 const computedChange = previous === void 0 ? void 0 : price - previous;
720 const computedPercent = previous === void 0 || Math.abs(previous) < Number.EPSILON ? void 0 : (price - previous) / previous * 100;
721 const change = chart ? computedChange : optionalNumber(quote.regularMarketChange) ?? computedChange;
722 const percent = chart ? computedPercent : optionalNumber(quote.regularMarketChangePercent) ?? computedPercent;
723 const name = optionalText(quote.longName) ?? optionalText(quote.shortName);
724 const currency = optionalText(quote.currency);
725 const state = chart ? void 0 : optionalText(quote.marketState);
726 const type = optionalText(chart ? quote.instrumentType : quote.quoteType);
727 const exchange = optionalText(quote.fullExchangeName) ?? optionalText(chart ? quote.exchangeName : quote.exchange);
728 const time = timestamp(quote.regularMarketTime);
729 const value = {
730 requested_ticker: requested,
731 ticker: text4(quote.symbol),
732 price: float(price),
733 source: source2,
734 fallback_used: chart,
735 ...name === void 0 ? {} : { name },
736 ...currency === void 0 ? {} : { currency },
737 ...change === void 0 ? {} : { change: float(change) },
738 ...percent === void 0 ? {} : { change_percent: float(percent) },
739 ...previous === void 0 ? {} : { previous_close: float(previous) },
740 ...state === void 0 ? {} : { market_state: state },
741 ...type === void 0 ? {} : { quote_type: type },
742 ...exchange === void 0 ? {} : { exchange },
743 ...time === void 0 ? {} : { market_time: time }
744 };
745 return result2(true, value);
746 }
747 function summary(value, format) {
748 const descriptor = row4(value);
749 const kind = text4(descriptor.kind);
750 const accepted = format === "json" ? ["object", "array", "string", "number", "boolean", "null"] : ["table", "array", "string", "integer", "float", "boolean", "datetime"];
751 if (!accepted.includes(kind)) throw new Error("invalid captured parser kind");
752 if (kind === "object" || kind === "table") {
753 if (!Array.isArray(descriptor.keys) || descriptor.keys.some((key) => typeof key !== "string")) throw new Error("invalid captured parser keys");
754 const keys = descriptor.keys;
755 if (new Set(keys).size !== keys.length) throw new Error("duplicate captured parser keys");
756 return { top_level: kind, entries: keys.length, keys_preview: keys.slice(0, 10) };
757 }
758 if (kind === "array") {
759 if (!Number.isSafeInteger(descriptor.entries) || descriptor.entries < 0) throw new Error("invalid captured array count");
760 return { top_level: kind, entries: descriptor.entries };
761 }
762 return { top_level: kind };
763 }
764 function data(input) {
765 const format = text4(input.format);
766 if (!["auto", "json", "toml"].includes(format)) throw new Error("invalid captured data format");
767 const source2 = text4(input.source);
768 const extension = optionalText(input.extension);
769 const requested = format === "auto" && (extension === "json" || extension === "toml") ? extension : format;
770 const json = row4(input.json);
771 const toml = row4(input.toml);
772 if (typeof json.ok !== "boolean" || typeof toml.ok !== "boolean") throw new Error("invalid captured parser result");
773 const use = (parser, selected) => {
774 if (parser.ok) return result2(true, { valid: true, format: selected, source: source2, summary: summary(parser.value, selected) });
775 const error = text4(parser.error);
776 return result2(false, { valid: false, format: selected, source: source2, error }, `Invalid ${selected.toUpperCase()}: ${error}`);
777 };
778 if (requested === "json") return use(json, "json");
779 if (requested === "toml") return use(toml, "toml");
780 if (json.ok) return use(json, "json");
781 if (toml.ok) return use(toml, "toml");
782 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.");
783 }
784 function rustTrim2(value) {
785 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, "");
786 }
787 function bool2(value) {
788 if (typeof value !== "boolean") throw new Error("invalid captured boolean");
789 return value;
790 }
791 function speech(operation, input) {
792 const surface = text4(input.surface);
793 if (surface !== "tool" && surface !== "cli") throw new Error("invalid speech surface");
794 const tool = surface === "tool";
795 const invalid = (error) => result2(false, { error });
796 if (operation === "speech_format") {
797 const raw = text4(input.format);
798 const format = asciiLower(rustTrim2(raw));
799 if (format === "wav" || format === "mp3" || format === "pcm16" || format === "pcm") return result2(true, { format: format === "pcm" ? "pcm16" : format });
800 return invalid(tool ? `unsupported speech format '${raw}' (allowed: wav, mp3, pcm16)` : `Unsupported speech format '${raw}' (allowed: wav, mp3, pcm16)`);
801 }
802 const modelHint = optionalText(input.model);
803 const hasClone = bool2(input.has_clone_path);
804 const hasVoice = bool2(input.has_voice);
805 const nonemptyVoice = bool2(input.voice_nonempty);
806 const dataUri = bool2(input.voice_is_data_uri);
807 if ((nonemptyVoice || dataUri) && !hasVoice) throw new Error("inconsistent captured voice presence");
808 const instructionInput = optionalText(input.instruction);
809 const promptInput = optionalText(input.voice_prompt);
810 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");
811 const model = modelHint ?? (hasClone || dataUri ? "mimo-v2.5-tts-voiceclone" : promptInput !== void 0 ? "mimo-v2.5-tts-voicedesign" : "mimo-v2.5-tts");
812 const lower = asciiLower(model);
813 if (!lower.includes("tts")) {
814 const examples = "mimo-v2.5-tts, mimo-v2.5-tts-voicedesign, mimo-v2.5-tts-voiceclone, mimo-v2-tts";
815 return invalid(tool ? `speech tool requires a TTS model (examples: ${examples}), got '${model}'` : `speech requires a TTS model (examples: ${examples}); got ${model}`);
816 }
817 const parts = [promptInput, instructionInput].map((value) => value === void 0 ? void 0 : rustTrim2(value)).filter((value) => value !== void 0 && value !== "");
818 const instruction = parts.length ? parts.join("\n\n") : null;
819 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");
820 let voice;
821 if (hasClone) voice = "clone";
822 else if (lower.includes("voicedesign")) voice = "omit";
823 else if (nonemptyVoice) voice = "raw";
824 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>");
825 else voice = "default";
826 return result2(true, { model, instruction, voice });
827 }
828 function pdfProjection(input) {
829 const state = text4(input.state);
830 const decision = (code2, message) => result2(true, { kind: "pdf_decision", code: code2, ...message === void 0 ? {} : { message } });
831 if (state !== "complete") {
832 if (!["binary_unavailable", "cancelled", "timed_out", "execution"].includes(state)) throw new Error("invalid PDF process state");
833 return decision(state, text4(input.message));
834 }
835 const success = bool2(input.success);
836 const stdoutOverflow = bool2(input.stdout_truncated);
837 const stderrOverflow = bool2(input.stderr_truncated);
838 const stderr = text4(input.stderr);
839 const code = input.exit_code;
840 if (code !== null && (!Number.isInteger(code) || code < -2147483648 || code > 2147483647)) throw new Error("invalid PDF exit code");
841 if (stdoutOverflow) return decision("execution", "pdftotext output exceeded the 16777216 byte safety limit");
842 if (!success) return decision("execution", `pdftotext failed (exit ${code === null ? "None" : `Some(${code})`}): ${stderr || "no diagnostic output"}${stderrOverflow ? " [truncated]" : ""}`);
843 return decision("success");
844 }
845 function ocrProjection(input) {
846 const decision = (code2, trim_end = false, message) => result2(true, { kind: "ocr_decision", code: code2, trim_end, ...message === void 0 ? {} : { message } });
847 const state = text4(input.state), status = text4(input.status);
848 if (state === "native") {
849 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");
850 const fallback = bool2(input.can_fallback);
851 if (status === "success") {
852 if (fallback || input.next_ticket !== void 0) throw new Error("successful OCR Native step cannot launch fallback");
853 return decision("native_success");
854 }
855 if (fallback) {
856 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");
857 return decision("fallback");
858 }
859 if (input.next_ticket !== void 0) throw new Error("OCR fallback is not admitted");
860 if (status === "error") return decision("native_error");
861 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.");
862 }
863 if (state !== "tesseract") throw new Error("invalid OCR process stage");
864 if (status === "fault") {
865 if (Object.keys(input).some((key) => !["kind", "state", "status"].includes(key))) throw new Error("invalid OCR fault projection");
866 return decision("fault");
867 }
868 if (status !== "complete" || Object.keys(input).some((key) => !["kind", "state", "status", "success", "exit_code"].includes(key))) throw new Error("invalid OCR Tesseract projection");
869 const success = bool2(input.success), code = input.exit_code;
870 if (code !== null && (!Number.isInteger(code) || code < -2147483648 || code > 2147483647)) throw new Error("invalid OCR exit code");
871 return success ? decision("tesseract_success", true) : decision("execution", false, `tesseract failed (exit ${code === null ? "None" : `Some(${code})`}): `);
872 }
873 function transformStockSnapshot(value) {
874 const snapshot = row4(value);
875 const review = snapshot.kind === "stock_adapter" && isReviewOperation(snapshot.operation);
876 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");
877 if (snapshot.kind === "ocr_process") return ocrProjection(snapshot);
878 if (snapshot.kind === "pdf_process") return pdfProjection(snapshot);
879 if (snapshot.kind !== "stock_adapter") throw new Error("invalid adapter projection");
880 const input = row4(snapshot.input);
881 if (review) return transformReviewSnapshot(snapshot.operation, input);
882 switch (snapshot.operation) {
883 case "web_filters":
884 case "web_request":
885 case "web_provider":
886 case "web_entries":
887 case "web_finalize":
888 case "web_extract":
889 case "web_images":
890 return transformWebSnapshot(snapshot.operation, input);
891 case "github_result":
892 return transformGithubSnapshot(input);
893 case "finance_quote":
894 case "finance_chart":
895 return finance(snapshot.operation, input);
896 case "validate_data":
897 return data(input);
898 case "speech_options":
899 case "speech_format":
900 return speech(snapshot.operation, input);
901 default:
902 throw new Error("unadmitted adapter operation");
903 }
904 }
905 export {
906 transformStockSnapshot
907 };
908
908 lines Plain Text