@oh-my-pi/pi-coding-agent 14.5.13 → 14.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/package.json +7 -7
- package/src/autoresearch/command-resume.md +5 -8
- package/src/autoresearch/git.ts +41 -51
- package/src/autoresearch/helpers.ts +43 -359
- package/src/autoresearch/index.ts +281 -273
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +52 -193
- package/src/autoresearch/resume-message.md +2 -8
- package/src/autoresearch/state.ts +59 -166
- package/src/autoresearch/storage.ts +687 -0
- package/src/autoresearch/tools/init-experiment.ts +201 -290
- package/src/autoresearch/tools/log-experiment.ts +304 -517
- package/src/autoresearch/tools/run-experiment.ts +117 -296
- package/src/autoresearch/tools/update-notes.ts +116 -0
- package/src/autoresearch/types.ts +16 -66
- package/src/commit/pipeline.ts +4 -3
- package/src/config/settings-schema.ts +1 -1
- package/src/config/settings.ts +20 -1
- package/src/config.ts +9 -6
- package/src/cursor.ts +1 -1
- package/src/edit/index.ts +9 -31
- package/src/edit/line-hash.ts +70 -43
- package/src/edit/modes/hashline.lark +26 -0
- package/src/edit/modes/hashline.ts +898 -1099
- package/src/edit/modes/patch.ts +0 -7
- package/src/edit/modes/replace.ts +0 -4
- package/src/edit/renderer.ts +22 -20
- package/src/edit/streaming.ts +8 -28
- package/src/eval/eval.lark +24 -30
- package/src/eval/js/context-manager.ts +5 -162
- package/src/eval/js/prelude.txt +0 -12
- package/src/eval/parse.ts +129 -129
- package/src/eval/py/kernel.ts +4 -4
- package/src/eval/py/prelude.py +1 -219
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +2 -2
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/main.ts +10 -0
- package/src/mcp/manager.ts +22 -0
- package/src/modes/components/session-observer-overlay.ts +5 -2
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tree-selector.ts +4 -5
- package/src/modes/components/welcome.ts +11 -1
- package/src/modes/controllers/command-controller.ts +2 -6
- package/src/modes/controllers/event-controller.ts +1 -2
- package/src/modes/controllers/extension-ui-controller.ts +3 -15
- package/src/modes/controllers/input-controller.ts +0 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -7
- package/src/modes/rpc/rpc-client.ts +9 -0
- package/src/modes/rpc/rpc-mode.ts +6 -0
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/prompts/system/system-prompt.md +14 -38
- package/src/prompts/tools/ast-edit.md +8 -8
- package/src/prompts/tools/ast-grep.md +10 -10
- package/src/prompts/tools/eval.md +13 -31
- package/src/prompts/tools/find.md +2 -1
- package/src/prompts/tools/hashline.md +66 -57
- package/src/prompts/tools/search.md +2 -2
- package/src/sdk.ts +19 -4
- package/src/session/agent-session.ts +110 -4
- package/src/session/session-manager.ts +17 -13
- package/src/task/agents.ts +4 -5
- package/src/tools/archive-reader.ts +9 -3
- package/src/tools/ast-edit.ts +141 -44
- package/src/tools/ast-grep.ts +112 -36
- package/src/tools/browser/readable.ts +11 -6
- package/src/tools/browser/tab-supervisor.ts +2 -2
- package/src/tools/browser.ts +5 -3
- package/src/tools/eval.ts +2 -53
- package/src/tools/find.ts +16 -15
- package/src/tools/image-gen.ts +2 -2
- package/src/tools/path-utils.ts +36 -196
- package/src/tools/search.ts +56 -35
- package/src/tools/write.ts +8 -1
- package/src/utils/edit-mode.ts +2 -11
- package/src/utils/file-display-mode.ts +1 -1
- package/src/utils/git.ts +17 -0
- package/src/utils/session-color.ts +0 -12
- package/src/utils/title-generator.ts +22 -38
- package/src/web/scrapers/crossref.ts +3 -3
- package/src/web/scrapers/devto.ts +1 -1
- package/src/web/scrapers/discourse.ts +5 -5
- package/src/web/scrapers/firefox-addons.ts +1 -1
- package/src/web/scrapers/flathub.ts +2 -2
- package/src/web/scrapers/gitlab.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
- package/src/web/scrapers/mastodon.ts +9 -9
- package/src/web/scrapers/mdn.ts +11 -7
- package/src/web/scrapers/pub-dev.ts +1 -1
- package/src/web/scrapers/rawg.ts +3 -3
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/spdx.ts +1 -1
- package/src/web/scrapers/stackoverflow.ts +2 -2
- package/src/web/scrapers/types.ts +53 -39
- package/src/web/scrapers/w3c.ts +1 -1
- package/src/web/search/providers/gemini.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +0 -24
- package/src/autoresearch/contract.ts +0 -288
- package/src/edit/modes/atom.lark +0 -29
- package/src/edit/modes/atom.ts +0 -1773
- package/src/prompts/tools/atom.md +0 -150
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
* Generate session titles using a smol, fast model.
|
|
3
3
|
*/
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
|
|
6
|
-
import type
|
|
7
|
-
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
5
|
+
|
|
6
|
+
import { type Api, completeSimple, type Model } from "@oh-my-pi/pi-ai";
|
|
8
7
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
9
8
|
import type { ModelRegistry } from "../config/model-registry";
|
|
10
9
|
import { resolveRoleSelection } from "../config/model-resolver";
|
|
11
10
|
import type { Settings } from "../config/settings";
|
|
12
11
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
13
|
-
import { toReasoningEffort } from "../thinking";
|
|
14
12
|
|
|
15
13
|
const TITLE_SYSTEM_PROMPT = prompt.render(titleSystemPrompt);
|
|
16
14
|
|
|
@@ -19,22 +17,14 @@ const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
|
19
17
|
|
|
20
18
|
const MAX_INPUT_CHARS = 2000;
|
|
21
19
|
|
|
22
|
-
function getTitleModel(
|
|
23
|
-
registry: ModelRegistry,
|
|
24
|
-
settings: Settings,
|
|
25
|
-
currentModel?: Model<Api>,
|
|
26
|
-
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
|
20
|
+
function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel?: Model<Api>): Model<Api> | undefined {
|
|
27
21
|
const availableModels = registry.getAvailable();
|
|
28
22
|
if (availableModels.length === 0) return undefined;
|
|
29
23
|
|
|
30
|
-
const titleModel = resolveRoleSelection(["commit", "smol"], settings, availableModels, registry);
|
|
31
|
-
if (titleModel)
|
|
32
|
-
return { model: titleModel.model, thinkingLevel: titleModel.thinkingLevel };
|
|
33
|
-
}
|
|
24
|
+
const titleModel = resolveRoleSelection(["commit", "smol"], settings, availableModels, registry)?.model;
|
|
25
|
+
if (titleModel) return titleModel;
|
|
34
26
|
|
|
35
|
-
if (currentModel)
|
|
36
|
-
return { model: currentModel };
|
|
37
|
-
}
|
|
27
|
+
if (currentModel) return currentModel;
|
|
38
28
|
|
|
39
29
|
return undefined;
|
|
40
30
|
}
|
|
@@ -44,7 +34,7 @@ function getTitleModel(
|
|
|
44
34
|
*
|
|
45
35
|
* @param firstMessage The first user message
|
|
46
36
|
* @param registry Model registry
|
|
47
|
-
* @param settings Settings used to resolve the smol role
|
|
37
|
+
* @param settings Settings used to resolve the smol role
|
|
48
38
|
* @param sessionId Optional session id for sticky API key selection
|
|
49
39
|
*/
|
|
50
40
|
export async function generateSessionTitle(
|
|
@@ -54,8 +44,8 @@ export async function generateSessionTitle(
|
|
|
54
44
|
sessionId?: string,
|
|
55
45
|
currentModel?: Model<Api>,
|
|
56
46
|
): Promise<string | null> {
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
47
|
+
const model = getTitleModel(registry, settings, currentModel);
|
|
48
|
+
if (!model) {
|
|
59
49
|
logger.debug("title-generator: no title model found");
|
|
60
50
|
return null;
|
|
61
51
|
}
|
|
@@ -67,17 +57,20 @@ export async function generateSessionTitle(
|
|
|
67
57
|
${truncatedMessage}
|
|
68
58
|
</user-message>`;
|
|
69
59
|
|
|
70
|
-
const apiKey = await registry.getApiKey(
|
|
60
|
+
const apiKey = await registry.getApiKey(model, sessionId);
|
|
71
61
|
if (!apiKey) {
|
|
72
62
|
logger.debug("title-generator: no API key for smol model", {
|
|
73
|
-
provider:
|
|
74
|
-
id:
|
|
63
|
+
provider: model.provider,
|
|
64
|
+
id: model.id,
|
|
75
65
|
});
|
|
76
66
|
return null;
|
|
77
67
|
}
|
|
78
68
|
|
|
69
|
+
// Title generation is a 3-6 word task; force reasoning off so reasoning models
|
|
70
|
+
// don't burn the entire output budget on internal thinking and return an empty
|
|
71
|
+
// string. With reasoning disabled, 30 tokens of output is plenty.
|
|
79
72
|
const request = {
|
|
80
|
-
model: `${
|
|
73
|
+
model: `${model.provider}/${model.id}`,
|
|
81
74
|
systemPrompt: TITLE_SYSTEM_PROMPT,
|
|
82
75
|
userMessage,
|
|
83
76
|
maxTokens: 30,
|
|
@@ -86,7 +79,7 @@ ${truncatedMessage}
|
|
|
86
79
|
|
|
87
80
|
try {
|
|
88
81
|
const response = await completeSimple(
|
|
89
|
-
|
|
82
|
+
model,
|
|
90
83
|
{
|
|
91
84
|
systemPrompt: request.systemPrompt,
|
|
92
85
|
messages: [{ role: "user", content: request.userMessage, timestamp: Date.now() }],
|
|
@@ -94,7 +87,7 @@ ${truncatedMessage}
|
|
|
94
87
|
{
|
|
95
88
|
apiKey,
|
|
96
89
|
maxTokens: 30,
|
|
97
|
-
|
|
90
|
+
disableReasoning: true,
|
|
98
91
|
},
|
|
99
92
|
);
|
|
100
93
|
|
|
@@ -153,13 +146,8 @@ function getFallbackTerminalTitle(cwd: string | undefined): string | undefined {
|
|
|
153
146
|
return sanitizeTerminalTitlePart(baseName);
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
export function formatSessionTerminalTitle(
|
|
157
|
-
sessionName
|
|
158
|
-
cwd?: string,
|
|
159
|
-
titleSource?: "auto" | "user" | undefined,
|
|
160
|
-
): string {
|
|
161
|
-
const label =
|
|
162
|
-
sanitizeTerminalTitlePart(titleSource === "auto" ? undefined : sessionName) ?? getFallbackTerminalTitle(cwd);
|
|
149
|
+
export function formatSessionTerminalTitle(sessionName: string | undefined, cwd?: string): string {
|
|
150
|
+
const label = sanitizeTerminalTitlePart(sessionName) ?? getFallbackTerminalTitle(cwd);
|
|
163
151
|
return label ? `${DEFAULT_TERMINAL_TITLE}: ${label}` : DEFAULT_TERMINAL_TITLE;
|
|
164
152
|
}
|
|
165
153
|
|
|
@@ -170,12 +158,8 @@ export function setTerminalTitle(title: string): void {
|
|
|
170
158
|
process.stdout.write(`\x1b]0;${sanitizeTerminalTitlePart(title) ?? DEFAULT_TERMINAL_TITLE}\x07`);
|
|
171
159
|
}
|
|
172
160
|
|
|
173
|
-
export function setSessionTerminalTitle(
|
|
174
|
-
sessionName
|
|
175
|
-
cwd?: string,
|
|
176
|
-
titleSource?: "auto" | "user" | undefined,
|
|
177
|
-
): void {
|
|
178
|
-
setTerminalTitle(formatSessionTerminalTitle(sessionName, cwd, titleSource));
|
|
161
|
+
export function setSessionTerminalTitle(sessionName: string | undefined, cwd?: string): void {
|
|
162
|
+
setTerminalTitle(formatSessionTerminalTitle(sessionName, cwd));
|
|
179
163
|
}
|
|
180
164
|
|
|
181
165
|
/**
|
|
@@ -66,10 +66,10 @@ function formatDate(date?: CrossrefDate): string | null {
|
|
|
66
66
|
return formatted.join("-");
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function formatAbstract(abstract?: string): string | null {
|
|
69
|
+
async function formatAbstract(abstract?: string): Promise<string | null> {
|
|
70
70
|
if (!abstract) return null;
|
|
71
71
|
const normalized = abstract.replace(/<\/?jats:p[^>]*>/g, match => (match.startsWith("</") ? "</p>" : "<p>"));
|
|
72
|
-
const markdown = htmlToBasicMarkdown(normalized);
|
|
72
|
+
const markdown = await htmlToBasicMarkdown(normalized);
|
|
73
73
|
return markdown.trim().length > 0 ? markdown : null;
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -114,7 +114,7 @@ export const handleCrossref: SpecialHandler = async (
|
|
|
114
114
|
formatDate(message.issued) ||
|
|
115
115
|
formatDate(message.created);
|
|
116
116
|
const doiValue = message.DOI || doi;
|
|
117
|
-
const abstract = formatAbstract(message.abstract);
|
|
117
|
+
const abstract = await formatAbstract(message.abstract);
|
|
118
118
|
const type = message.type?.replace(/-/g, " ");
|
|
119
119
|
|
|
120
120
|
let md = `# ${title}\n\n`;
|
|
@@ -133,7 +133,7 @@ export const handleDevTo: SpecialHandler = async (
|
|
|
133
133
|
if (article.body_markdown) {
|
|
134
134
|
md += article.body_markdown;
|
|
135
135
|
} else if (article.body_html) {
|
|
136
|
-
md += htmlToBasicMarkdown(article.body_html);
|
|
136
|
+
md += await htmlToBasicMarkdown(article.body_html);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
notes.push("Fetched via dev.to API");
|
|
@@ -77,12 +77,12 @@ function formatCategory(topic: DiscourseTopic): string | null {
|
|
|
77
77
|
return parts.length ? parts.join(" ") : null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function formatPostBody(post: DiscoursePost): string {
|
|
80
|
+
async function formatPostBody(post: DiscoursePost): Promise<string> {
|
|
81
81
|
const raw = post.raw?.trim();
|
|
82
82
|
if (raw) return raw;
|
|
83
83
|
const cooked = post.cooked?.trim();
|
|
84
84
|
if (!cooked) return "";
|
|
85
|
-
return htmlToBasicMarkdown(cooked);
|
|
85
|
+
return await htmlToBasicMarkdown(cooked);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function buildTopicUrl(baseUrl: string, topicId: string): string {
|
|
@@ -168,9 +168,9 @@ export const handleDiscourse: SpecialHandler = async (
|
|
|
168
168
|
md += "\n";
|
|
169
169
|
|
|
170
170
|
const description = topic.excerpt
|
|
171
|
-
? htmlToBasicMarkdown(topic.excerpt)
|
|
171
|
+
? await htmlToBasicMarkdown(topic.excerpt)
|
|
172
172
|
: posts.length
|
|
173
|
-
? formatPostBody(posts[0])
|
|
173
|
+
? await formatPostBody(posts[0])
|
|
174
174
|
: "";
|
|
175
175
|
if (description) {
|
|
176
176
|
md += `## Description\n\n${description}\n\n`;
|
|
@@ -182,7 +182,7 @@ export const handleDiscourse: SpecialHandler = async (
|
|
|
182
182
|
const author = formatAuthor({ name: post.name, username: post.username });
|
|
183
183
|
const date = formatIsoDate(post.created_at);
|
|
184
184
|
const likes = post.like_count ?? 0;
|
|
185
|
-
const content = formatPostBody(post);
|
|
185
|
+
const content = await formatPostBody(post);
|
|
186
186
|
const postLabel = post.post_number != null ? `Post ${post.post_number}` : `Post ${post.id}`;
|
|
187
187
|
|
|
188
188
|
md += `### ${postLabel} - ${author} - ${date} - Likes: ${likes}\n\n`;
|
|
@@ -112,7 +112,7 @@ export const handleFirefoxAddons: SpecialHandler = async (
|
|
|
112
112
|
const name = getLocalizedText(data.name, defaultLocale) ?? slug;
|
|
113
113
|
const summary = getLocalizedText(data.summary, defaultLocale);
|
|
114
114
|
const descriptionRaw = getLocalizedText(data.description, defaultLocale);
|
|
115
|
-
const description = descriptionRaw ? htmlToBasicMarkdown(descriptionRaw) : undefined;
|
|
115
|
+
const description = descriptionRaw ? await htmlToBasicMarkdown(descriptionRaw) : undefined;
|
|
116
116
|
|
|
117
117
|
const authors = (data.authors ?? [])
|
|
118
118
|
.map(author => author.name ?? "")
|
|
@@ -170,7 +170,7 @@ export const handleFlathub: SpecialHandler = async (
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
if (app.description) {
|
|
173
|
-
const description = htmlToBasicMarkdown(app.description);
|
|
173
|
+
const description = await htmlToBasicMarkdown(app.description);
|
|
174
174
|
if (description) md += `\n## Description\n\n${description}\n`;
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -204,7 +204,7 @@ export const handleFlathub: SpecialHandler = async (
|
|
|
204
204
|
md += `${line}\n`;
|
|
205
205
|
|
|
206
206
|
if (release.description) {
|
|
207
|
-
const releaseDesc = htmlToBasicMarkdown(release.description).replace(/\n+/g, " ").trim();
|
|
207
|
+
const releaseDesc = (await htmlToBasicMarkdown(release.description)).replace(/\n+/g, " ").trim();
|
|
208
208
|
if (releaseDesc) md += ` - ${releaseDesc}\n`;
|
|
209
209
|
}
|
|
210
210
|
}
|
|
@@ -259,7 +259,7 @@ async function renderGitLabIssue(
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
md += `\n---\n\n## Description\n\n`;
|
|
262
|
-
md += issue.description ? htmlToBasicMarkdown(issue.description) : "*No description*";
|
|
262
|
+
md += issue.description ? await htmlToBasicMarkdown(issue.description) : "*No description*";
|
|
263
263
|
|
|
264
264
|
return { content: md, ok: true };
|
|
265
265
|
}
|
|
@@ -159,7 +159,7 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
159
159
|
// Get overview paragraph
|
|
160
160
|
const overview = docSection.querySelector(".go-Message");
|
|
161
161
|
if (overview) {
|
|
162
|
-
const overviewMd = htmlToBasicMarkdown(overview.innerHTML);
|
|
162
|
+
const overviewMd = await htmlToBasicMarkdown(overview.innerHTML);
|
|
163
163
|
sections.push(overviewMd);
|
|
164
164
|
sections.push("");
|
|
165
165
|
}
|
|
@@ -172,7 +172,7 @@ export const handleGoPkg: SpecialHandler = async (
|
|
|
172
172
|
const docParts: string[] = [];
|
|
173
173
|
for (let i = 0; i < Math.min(3, paragraphs.length); i++) {
|
|
174
174
|
const p = paragraphs[i];
|
|
175
|
-
const text = htmlToBasicMarkdown(p.innerHTML).trim();
|
|
175
|
+
const text = (await htmlToBasicMarkdown(p.innerHTML)).trim();
|
|
176
176
|
if (text) {
|
|
177
177
|
docParts.push(text);
|
|
178
178
|
}
|
|
@@ -108,7 +108,7 @@ export const handleJetBrainsMarketplace: SpecialHandler = async (
|
|
|
108
108
|
|
|
109
109
|
const vendorName = plugin.vendor?.name ?? plugin.vendor?.publicName;
|
|
110
110
|
const descriptionSource = plugin.description ?? plugin.preview ?? "";
|
|
111
|
-
const description = descriptionSource ? htmlToBasicMarkdown(descriptionSource) : "";
|
|
111
|
+
const description = descriptionSource ? await htmlToBasicMarkdown(descriptionSource) : "";
|
|
112
112
|
const tags = (plugin.tags ?? []).map(tag => tag.name).filter(Boolean) as string[];
|
|
113
113
|
const rating = extractRating(plugin);
|
|
114
114
|
const buildCompatibility = update ? formatBuildCompatibility(update) : null;
|
|
@@ -89,11 +89,11 @@ function formatDate(isoDate: string): string {
|
|
|
89
89
|
/**
|
|
90
90
|
* Format a status/post as markdown
|
|
91
91
|
*/
|
|
92
|
-
function formatStatus(status: MastodonStatus, isReblog = false): string {
|
|
92
|
+
async function formatStatus(status: MastodonStatus, isReblog = false): Promise<string> {
|
|
93
93
|
// Handle reblogs (boosts)
|
|
94
94
|
if (status.reblog && !isReblog) {
|
|
95
95
|
let md = `🔁 **${status.account.display_name || status.account.username}** boosted:\n\n`;
|
|
96
|
-
md += formatStatus(status.reblog, true);
|
|
96
|
+
md += await formatStatus(status.reblog, true);
|
|
97
97
|
return md;
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -116,7 +116,7 @@ function formatStatus(status: MastodonStatus, isReblog = false): string {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Main content (convert HTML to markdown)
|
|
119
|
-
const content = htmlToBasicMarkdown(status.content);
|
|
119
|
+
const content = await htmlToBasicMarkdown(status.content);
|
|
120
120
|
md += `${content}\n\n`;
|
|
121
121
|
|
|
122
122
|
// Poll
|
|
@@ -152,7 +152,7 @@ function formatStatus(status: MastodonStatus, isReblog = false): string {
|
|
|
152
152
|
/**
|
|
153
153
|
* Format an account/profile as markdown
|
|
154
154
|
*/
|
|
155
|
-
function formatAccount(account: MastodonAccount): string {
|
|
155
|
+
async function formatAccount(account: MastodonAccount): Promise<string> {
|
|
156
156
|
let md = `# ${account.display_name || account.username}\n\n`;
|
|
157
157
|
|
|
158
158
|
md += `**@${account.acct}**`;
|
|
@@ -161,7 +161,7 @@ function formatAccount(account: MastodonAccount): string {
|
|
|
161
161
|
|
|
162
162
|
// Bio
|
|
163
163
|
if (account.note) {
|
|
164
|
-
const bio = htmlToBasicMarkdown(account.note);
|
|
164
|
+
const bio = await htmlToBasicMarkdown(account.note);
|
|
165
165
|
if (bio && bio !== account.display_name) {
|
|
166
166
|
md += `${bio}\n\n`;
|
|
167
167
|
}
|
|
@@ -179,7 +179,7 @@ function formatAccount(account: MastodonAccount): string {
|
|
|
179
179
|
if (account.fields && account.fields.length > 0) {
|
|
180
180
|
md += "\n**Profile Fields:**\n";
|
|
181
181
|
for (const field of account.fields) {
|
|
182
|
-
const value = htmlToBasicMarkdown(field.value);
|
|
182
|
+
const value = await htmlToBasicMarkdown(field.value);
|
|
183
183
|
md += `- **${field.name}:** ${value}\n`;
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -228,7 +228,7 @@ export const handleMastodon: SpecialHandler = async (
|
|
|
228
228
|
const status = tryParseJson<MastodonStatus>(result.content);
|
|
229
229
|
if (!status) return null;
|
|
230
230
|
|
|
231
|
-
const md = formatStatus(status);
|
|
231
|
+
const md = await formatStatus(status);
|
|
232
232
|
|
|
233
233
|
return buildResult(md, {
|
|
234
234
|
url,
|
|
@@ -263,7 +263,7 @@ export const handleMastodon: SpecialHandler = async (
|
|
|
263
263
|
signal,
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
-
let md = formatAccount(account);
|
|
266
|
+
let md = await formatAccount(account);
|
|
267
267
|
|
|
268
268
|
if (statusesResult.ok) {
|
|
269
269
|
const statuses = tryParseJson<MastodonStatus[]>(statusesResult.content);
|
|
@@ -271,7 +271,7 @@ export const handleMastodon: SpecialHandler = async (
|
|
|
271
271
|
md += "\n---\n\n## Recent Posts\n\n";
|
|
272
272
|
for (const status of statuses.slice(0, 5)) {
|
|
273
273
|
md += `### ${formatDate(status.created_at)}\n\n`;
|
|
274
|
-
const content = htmlToBasicMarkdown(status.content);
|
|
274
|
+
const content = await htmlToBasicMarkdown(status.content);
|
|
275
275
|
md += `${content}\n\n`;
|
|
276
276
|
md += `\uD83D\uDCAC ${status.replies_count} \u00B7 \uD83D\uDD01 ${status.reblogs_count} \u00B7 \u2B50 ${status.favourites_count}\n\n`;
|
|
277
277
|
}
|
package/src/web/scrapers/mdn.ts
CHANGED
|
@@ -29,7 +29,7 @@ interface MDNDoc {
|
|
|
29
29
|
/**
|
|
30
30
|
* Convert MDN body sections to markdown
|
|
31
31
|
*/
|
|
32
|
-
function convertMDNBody(sections: MDNSection[]): string {
|
|
32
|
+
async function convertMDNBody(sections: MDNSection[]): Promise<string> {
|
|
33
33
|
const parts: string[] = [];
|
|
34
34
|
|
|
35
35
|
for (const section of sections) {
|
|
@@ -38,7 +38,7 @@ function convertMDNBody(sections: MDNSection[]): string {
|
|
|
38
38
|
switch (type) {
|
|
39
39
|
case "prose":
|
|
40
40
|
if (value.content) {
|
|
41
|
-
const markdown = htmlToBasicMarkdown(value.content);
|
|
41
|
+
const markdown = await htmlToBasicMarkdown(value.content);
|
|
42
42
|
if (value.title) {
|
|
43
43
|
const level = value.isH3 ? "###" : "##";
|
|
44
44
|
parts.push(`${level} ${value.title}\n\n${markdown}`);
|
|
@@ -74,7 +74,7 @@ function convertMDNBody(sections: MDNSection[]): string {
|
|
|
74
74
|
if (value.items) {
|
|
75
75
|
for (const item of value.items) {
|
|
76
76
|
parts.push(`**${item.term}**`);
|
|
77
|
-
const desc = htmlToBasicMarkdown(item.description);
|
|
77
|
+
const desc = await htmlToBasicMarkdown(item.description);
|
|
78
78
|
parts.push(desc);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -83,9 +83,13 @@ function convertMDNBody(sections: MDNSection[]): string {
|
|
|
83
83
|
case "table":
|
|
84
84
|
if (value.rows && value.rows.length > 0) {
|
|
85
85
|
// Simple markdown table
|
|
86
|
-
const header = value.rows[0].map(cell => htmlToBasicMarkdown(cell)).join(" | ");
|
|
86
|
+
const header = (await Promise.all(value.rows[0].map(cell => htmlToBasicMarkdown(cell)))).join(" | ");
|
|
87
87
|
const separator = value.rows[0].map(() => "---").join(" | ");
|
|
88
|
-
const bodyRows =
|
|
88
|
+
const bodyRows = await Promise.all(
|
|
89
|
+
value.rows
|
|
90
|
+
.slice(1)
|
|
91
|
+
.map(async row => (await Promise.all(row.map(cell => htmlToBasicMarkdown(cell)))).join(" | ")),
|
|
92
|
+
);
|
|
89
93
|
|
|
90
94
|
parts.push(`| ${header} |`);
|
|
91
95
|
parts.push(`| ${separator} |`);
|
|
@@ -144,12 +148,12 @@ export const handleMDN: SpecialHandler = async (url: string, timeout: number, si
|
|
|
144
148
|
parts.push(`# ${doc.title}`);
|
|
145
149
|
|
|
146
150
|
if (doc.summary) {
|
|
147
|
-
const summary = htmlToBasicMarkdown(doc.summary);
|
|
151
|
+
const summary = await htmlToBasicMarkdown(doc.summary);
|
|
148
152
|
parts.push(summary);
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
if (doc.body && doc.body.length > 0) {
|
|
152
|
-
const bodyMarkdown = convertMDNBody(doc.body);
|
|
156
|
+
const bodyMarkdown = await convertMDNBody(doc.body);
|
|
153
157
|
parts.push(bodyMarkdown);
|
|
154
158
|
}
|
|
155
159
|
|
|
@@ -125,7 +125,7 @@ export const handlePubDev: SpecialHandler = async (url: string, timeout: number,
|
|
|
125
125
|
/<div[^>]*class="[^"]*markdown-body[^"]*"[^>]*>([\s\S]*?)<\/div>/i,
|
|
126
126
|
);
|
|
127
127
|
if (readmeMatch) {
|
|
128
|
-
const readme = htmlToBasicMarkdown(readmeMatch[1]);
|
|
128
|
+
const readme = await htmlToBasicMarkdown(readmeMatch[1]);
|
|
129
129
|
|
|
130
130
|
if (readme.length > 100) {
|
|
131
131
|
md += `## README\n\n${readme}\n`;
|
package/src/web/scrapers/rawg.ts
CHANGED
|
@@ -63,7 +63,7 @@ export const handleRawg: SpecialHandler = async (
|
|
|
63
63
|
md += `**RAWG:** https://rawg.io/games/${encodeURIComponent(slug)}\n`;
|
|
64
64
|
md += "\n";
|
|
65
65
|
|
|
66
|
-
const description = extractDescription(game);
|
|
66
|
+
const description = await extractDescription(game);
|
|
67
67
|
if (description) {
|
|
68
68
|
md += `## Description\n\n${description}\n`;
|
|
69
69
|
}
|
|
@@ -91,11 +91,11 @@ function requiresApiKey(game: RawgGameResponse): boolean {
|
|
|
91
91
|
return detail.includes("api key") || detail.includes("key is required") || detail.includes("apikey");
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
function extractDescription(game: RawgGameResponse): string | null {
|
|
94
|
+
async function extractDescription(game: RawgGameResponse): Promise<string | null> {
|
|
95
95
|
if (game.description_raw) return game.description_raw.trim();
|
|
96
96
|
if (!game.description) return null;
|
|
97
97
|
|
|
98
|
-
const markdown = htmlToBasicMarkdown(game.description).trim();
|
|
98
|
+
const markdown = (await htmlToBasicMarkdown(game.description)).trim();
|
|
99
99
|
return markdown || null;
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -101,7 +101,7 @@ export const handleReadTheDocs: SpecialHandler = async (
|
|
|
101
101
|
// If no raw source, convert HTML to markdown
|
|
102
102
|
if (!content && mainContent) {
|
|
103
103
|
const html = mainContent.innerHTML;
|
|
104
|
-
content = htmlToBasicMarkdown(html);
|
|
104
|
+
content = await htmlToBasicMarkdown(html);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
if (!content) {
|
package/src/web/scrapers/spdx.ts
CHANGED
|
@@ -94,7 +94,7 @@ export const handleSpdx: SpecialHandler = async (
|
|
|
94
94
|
const licenseText = license.licenseText
|
|
95
95
|
? license.licenseText
|
|
96
96
|
: license.licenseTextHtml
|
|
97
|
-
? htmlToBasicMarkdown(license.licenseTextHtml)
|
|
97
|
+
? await htmlToBasicMarkdown(license.licenseTextHtml)
|
|
98
98
|
: null;
|
|
99
99
|
|
|
100
100
|
if (licenseText) {
|
|
@@ -90,7 +90,7 @@ export const handleStackOverflow: SpecialHandler = async (
|
|
|
90
90
|
md += question.is_answered ? " (Answered)" : "";
|
|
91
91
|
md += `\n**Tags:** ${question.tags.join(", ")}\n`;
|
|
92
92
|
md += `**Asked by:** ${question.owner.display_name} · ${formatIsoDate(question.creation_date * 1000)}\n\n`;
|
|
93
|
-
md += `---\n\n## Question\n\n${htmlToBasicMarkdown(question.body)}\n\n`;
|
|
93
|
+
md += `---\n\n## Question\n\n${await htmlToBasicMarkdown(question.body)}\n\n`;
|
|
94
94
|
|
|
95
95
|
// Fetch answers
|
|
96
96
|
const aUrl = `https://api.stackexchange.com/2.3/questions/${questionId}/answers?order=desc&sort=votes&site=${site}&filter=withbody`;
|
|
@@ -103,7 +103,7 @@ export const handleStackOverflow: SpecialHandler = async (
|
|
|
103
103
|
for (const answer of aData.items.slice(0, 5)) {
|
|
104
104
|
const accepted = answer.is_accepted ? " (Accepted)" : "";
|
|
105
105
|
md += `### Score: ${answer.score}${accepted} · by ${answer.owner.display_name}\n\n`;
|
|
106
|
-
md += `${htmlToBasicMarkdown(answer.body)}\n\n---\n\n`;
|
|
106
|
+
md += `${await htmlToBasicMarkdown(answer.body)}\n\n---\n\n`;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Shared types and utilities for web-fetch handlers
|
|
3
3
|
*/
|
|
4
4
|
import { ptree } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import TurndownService from "turndown";
|
|
6
|
-
|
|
5
|
+
import type TurndownService from "turndown";
|
|
6
|
+
|
|
7
7
|
import { ToolAbortError } from "../../tools/tool-errors";
|
|
8
8
|
|
|
9
9
|
export { formatNumber } from "@oh-my-pi/pi-utils";
|
|
@@ -155,28 +155,8 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
|
|
|
155
155
|
return { content: "", contentType: "", finalUrl: url, ok: false };
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/** Module-level Turndown instance —
|
|
159
|
-
|
|
160
|
-
headingStyle: "atx",
|
|
161
|
-
codeBlockStyle: "fenced",
|
|
162
|
-
bulletListMarker: "-",
|
|
163
|
-
});
|
|
164
|
-
turndown.use(gfm);
|
|
165
|
-
turndown.addRule("strikethrough", {
|
|
166
|
-
filter: ["del", "s", "strike"],
|
|
167
|
-
replacement(content) {
|
|
168
|
-
return `~~${content}~~`;
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
turndown.addRule("heading", {
|
|
172
|
-
filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
|
|
173
|
-
replacement(content, node) {
|
|
174
|
-
const level = Number(node.nodeName.charAt(1));
|
|
175
|
-
const prefix = "#".repeat(level);
|
|
176
|
-
const cleaned = content.replace(/\\([.])/g, "$1").trim();
|
|
177
|
-
return `\n\n${prefix} ${cleaned}\n\n`;
|
|
178
|
-
},
|
|
179
|
-
});
|
|
158
|
+
/** Module-level Turndown instance — built lazily on first use. */
|
|
159
|
+
let turndownPromise: Promise<TurndownService> | undefined;
|
|
180
160
|
|
|
181
161
|
type TurndownListParent = {
|
|
182
162
|
nodeName: string;
|
|
@@ -184,27 +164,61 @@ type TurndownListParent = {
|
|
|
184
164
|
children: ArrayLike<unknown>;
|
|
185
165
|
};
|
|
186
166
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
167
|
+
function getTurndown(): Promise<TurndownService> {
|
|
168
|
+
turndownPromise ||= initTurndown();
|
|
169
|
+
return turndownPromise;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function initTurndown(): Promise<TurndownService> {
|
|
173
|
+
const [{ default: TurndownService }, { gfm }] = await Promise.all([
|
|
174
|
+
import("turndown"),
|
|
175
|
+
import("turndown-plugin-gfm"),
|
|
176
|
+
]);
|
|
177
|
+
const turndown = new TurndownService({
|
|
178
|
+
headingStyle: "atx",
|
|
179
|
+
codeBlockStyle: "fenced",
|
|
180
|
+
bulletListMarker: "-",
|
|
181
|
+
});
|
|
182
|
+
turndown.use(gfm);
|
|
183
|
+
turndown.addRule("strikethrough", {
|
|
184
|
+
filter: ["del", "s", "strike"],
|
|
185
|
+
replacement(content) {
|
|
186
|
+
return `~~${content}~~`;
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
turndown.addRule("heading", {
|
|
190
|
+
filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
|
|
191
|
+
replacement(content, node) {
|
|
192
|
+
const level = Number(node.nodeName.charAt(1));
|
|
193
|
+
const prefix = "#".repeat(level);
|
|
194
|
+
const cleaned = content.replace(/\\([.])/g, "$1").trim();
|
|
195
|
+
return `\n\n${prefix} ${cleaned}\n\n`;
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
turndown.addRule("listItem", {
|
|
199
|
+
filter: "li",
|
|
200
|
+
replacement(content, node, options) {
|
|
201
|
+
content = content.replace(/^\n+/, "").replace(/\n+$/, "\n").replace(/\n/gm, "\n ");
|
|
202
|
+
const parent = node.parentNode as unknown as TurndownListParent | null;
|
|
203
|
+
let prefix = `${options.bulletListMarker} `;
|
|
204
|
+
if (parent?.nodeName === "OL") {
|
|
205
|
+
const start = parent.getAttribute("start");
|
|
206
|
+
const index = Array.prototype.indexOf.call(parent.children, node);
|
|
207
|
+
prefix = `${(start ? Number(start) : 1) + index}. `;
|
|
208
|
+
}
|
|
209
|
+
return prefix + content + (node.nextSibling ? "\n" : "");
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
return turndown;
|
|
213
|
+
}
|
|
201
214
|
|
|
202
215
|
/**
|
|
203
216
|
* Convert HTML to markdown using Turndown with GFM support.
|
|
204
217
|
* Strips script/style tags before conversion.
|
|
205
218
|
*/
|
|
206
|
-
export function htmlToBasicMarkdown(html: string): string {
|
|
219
|
+
export async function htmlToBasicMarkdown(html: string): Promise<string> {
|
|
207
220
|
const cleaned = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
221
|
+
const turndown = await getTurndown();
|
|
208
222
|
return turndown.turndown(cleaned).trim();
|
|
209
223
|
}
|
|
210
224
|
|
package/src/web/scrapers/w3c.ts
CHANGED
|
@@ -100,7 +100,7 @@ export const handleW3c: SpecialHandler = async (
|
|
|
100
100
|
const title = getString(specPayload, "title");
|
|
101
101
|
const shortnameValue = getString(specPayload, "shortname") ?? shortname;
|
|
102
102
|
const description = getString(specPayload, "description") ?? getString(specPayload, "abstract");
|
|
103
|
-
const abstract = description ? htmlToBasicMarkdown(description) : undefined;
|
|
103
|
+
const abstract = description ? await htmlToBasicMarkdown(description) : undefined;
|
|
104
104
|
|
|
105
105
|
const latestVersionUrl =
|
|
106
106
|
getString(latestPayload, "uri") ??
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import {
|
|
9
9
|
ANTIGRAVITY_SYSTEM_INSTRUCTION,
|
|
10
10
|
extractRetryDelay,
|
|
11
|
-
|
|
11
|
+
getAntigravityUserAgent,
|
|
12
12
|
getGeminiCliHeaders,
|
|
13
13
|
} from "@oh-my-pi/pi-ai";
|
|
14
14
|
import { refreshAntigravityToken } from "@oh-my-pi/pi-ai/utils/oauth/google-antigravity";
|
|
@@ -248,7 +248,7 @@ async function callGeminiSearch(
|
|
|
248
248
|
usage?: { inputTokens: number; outputTokens: number; totalTokens: number };
|
|
249
249
|
}> {
|
|
250
250
|
const endpoints = auth.isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
|
|
251
|
-
const headers = auth.isAntigravity ?
|
|
251
|
+
const headers = auth.isAntigravity ? { "User-Agent": getAntigravityUserAgent() } : getGeminiCliHeaders();
|
|
252
252
|
|
|
253
253
|
const requestMetadata = auth.isAntigravity
|
|
254
254
|
? {
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { inferMetricUnitFromName } from "./helpers";
|
|
2
|
-
import type { AutoresearchContract, ExperimentState } from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Updates session fields from a validated `autoresearch.md` parse (same fields as `init_experiment`).
|
|
6
|
-
* Does not touch `name`, `currentSegment`, `results`, `bestMetric`, `confidence`, or `maxExperiments`.
|
|
7
|
-
*/
|
|
8
|
-
export function applyAutoresearchContractToExperimentState(
|
|
9
|
-
contract: AutoresearchContract,
|
|
10
|
-
state: ExperimentState,
|
|
11
|
-
): void {
|
|
12
|
-
const benchmarkContract = contract.benchmark;
|
|
13
|
-
state.metricName = benchmarkContract.primaryMetric ?? state.metricName;
|
|
14
|
-
state.metricUnit = benchmarkContract.metricUnit;
|
|
15
|
-
state.bestDirection = benchmarkContract.direction ?? "lower";
|
|
16
|
-
state.secondaryMetrics = benchmarkContract.secondaryMetrics.map(name => ({
|
|
17
|
-
name,
|
|
18
|
-
unit: inferMetricUnitFromName(name),
|
|
19
|
-
}));
|
|
20
|
-
state.benchmarkCommand = benchmarkContract.command?.trim() ?? state.benchmarkCommand;
|
|
21
|
-
state.scopePaths = [...contract.scopePaths];
|
|
22
|
-
state.offLimits = [...contract.offLimits];
|
|
23
|
-
state.constraints = [...contract.constraints];
|
|
24
|
-
}
|