ampcode-connector 0.1.4 → 0.1.6
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/package.json +5 -3
- package/src/auth/auto-refresh.ts +44 -0
- package/src/auth/callback-server.ts +1 -1
- package/src/auth/discovery.ts +1 -1
- package/src/auth/oauth.ts +11 -7
- package/src/auth/store.ts +73 -40
- package/src/cli/setup.ts +4 -4
- package/src/cli/tui.ts +1 -1
- package/src/config/config.ts +11 -11
- package/src/index.ts +27 -1
- package/src/providers/antigravity.ts +1 -4
- package/src/providers/base.ts +1 -4
- package/src/providers/codex.ts +1 -1
- package/src/proxy/rewriter.ts +12 -11
- package/src/proxy/upstream.ts +1 -4
- package/src/routing/affinity.ts +54 -19
- package/src/routing/cooldown.ts +6 -6
- package/src/routing/retry.ts +83 -0
- package/src/routing/router.ts +49 -33
- package/src/server/body.ts +12 -11
- package/src/server/server.ts +79 -92
- package/src/tools/internal.ts +69 -46
- package/src/tools/web-read.ts +336 -0
- package/src/tools/web-search.ts +33 -26
- package/src/utils/code-assist.ts +1 -1
- package/src/utils/logger.ts +31 -2
- package/src/utils/path.ts +9 -4
- package/src/utils/stats.ts +69 -0
- package/src/utils/streaming.ts +10 -11
- package/tsconfig.json +3 -3
- package/src/tools/web-extract.ts +0 -137
package/src/tools/web-extract.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/** Local handler for extractWebPageContent — fetches a URL and converts HTML to Markdown. */
|
|
2
|
-
|
|
3
|
-
import type { JsPreprocessingPreset } from "@kreuzberg/html-to-markdown";
|
|
4
|
-
import { convertWithOptionsHandle, createConversionOptionsHandle } from "@kreuzberg/html-to-markdown";
|
|
5
|
-
import { logger } from "../utils/logger.ts";
|
|
6
|
-
|
|
7
|
-
const FETCH_TIMEOUT_MS = 30_000;
|
|
8
|
-
const MAX_CONTENT_BYTES = 262_144; // 256 KB — matches CLI truncation limit
|
|
9
|
-
|
|
10
|
-
const conversionHandle = createConversionOptionsHandle({
|
|
11
|
-
skipImages: true,
|
|
12
|
-
preprocessing: { enabled: true, preset: "Aggressive" as JsPreprocessingPreset },
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export interface ExtractParams {
|
|
16
|
-
url: string;
|
|
17
|
-
objective?: string;
|
|
18
|
-
forceRefetch?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type ExtractResult =
|
|
22
|
-
| { ok: true; result: { excerpts: string[] } }
|
|
23
|
-
| { ok: true; result: { fullContent: string } }
|
|
24
|
-
| { ok: false; error: { code: string; message: string } };
|
|
25
|
-
|
|
26
|
-
export async function handleExtract(params: ExtractParams): Promise<ExtractResult> {
|
|
27
|
-
const { url, objective } = params;
|
|
28
|
-
|
|
29
|
-
let response: Response;
|
|
30
|
-
try {
|
|
31
|
-
response = await fetch(url, {
|
|
32
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
33
|
-
redirect: "follow",
|
|
34
|
-
headers: { "User-Agent": "Mozilla/5.0 (compatible; AmpBot/1.0)" },
|
|
35
|
-
});
|
|
36
|
-
} catch (err) {
|
|
37
|
-
logger.warn("extractWebPageContent fetch failed", { url, error: String(err) });
|
|
38
|
-
return { ok: false, error: { code: "fetch-error", message: `Failed to fetch ${url}: ${String(err)}` } };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!response.ok) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
error: { code: "fetch-error", message: `HTTP ${response.status} from ${url}` },
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const raw = await response.text();
|
|
49
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
50
|
-
const markdown = toMarkdown(raw, contentType);
|
|
51
|
-
|
|
52
|
-
if (objective) {
|
|
53
|
-
const excerpts = extractExcerpts(markdown, objective);
|
|
54
|
-
return { ok: true, result: { excerpts } };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return { ok: true, result: { fullContent: truncate(markdown) } };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Convert raw response body to Markdown based on content type. */
|
|
61
|
-
function toMarkdown(raw: string, contentType: string): string {
|
|
62
|
-
if (contentType.includes("text/html") || contentType.includes("application/xhtml")) {
|
|
63
|
-
return convertWithOptionsHandle(raw, conversionHandle);
|
|
64
|
-
}
|
|
65
|
-
if (contentType.includes("application/json")) {
|
|
66
|
-
try {
|
|
67
|
-
return `\`\`\`json\n${JSON.stringify(JSON.parse(raw), null, 2)}\n\`\`\``;
|
|
68
|
-
} catch {
|
|
69
|
-
return raw;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return raw;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Split markdown into paragraphs, score by keyword overlap with objective, return top excerpts. */
|
|
76
|
-
function extractExcerpts(markdown: string, objective: string): string[] {
|
|
77
|
-
const paragraphs = markdown
|
|
78
|
-
.split(/\n{2,}/)
|
|
79
|
-
.map((p) => p.trim())
|
|
80
|
-
.filter((p) => p.length > 0);
|
|
81
|
-
|
|
82
|
-
if (paragraphs.length === 0) return [truncate(markdown)];
|
|
83
|
-
|
|
84
|
-
const keywords = objective
|
|
85
|
-
.toLowerCase()
|
|
86
|
-
.split(/\W+/)
|
|
87
|
-
.filter((w) => w.length > 2);
|
|
88
|
-
|
|
89
|
-
if (keywords.length === 0) return [truncate(markdown)];
|
|
90
|
-
|
|
91
|
-
const scored = paragraphs.map((p, index) => {
|
|
92
|
-
const lower = p.toLowerCase();
|
|
93
|
-
const score = keywords.reduce((s, kw) => s + (lower.includes(kw) ? 1 : 0), 0);
|
|
94
|
-
return { text: p, score, index };
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const matched = scored.filter((s) => s.score > 0);
|
|
98
|
-
|
|
99
|
-
// If nothing matched, return full content as single excerpt
|
|
100
|
-
if (matched.length === 0) return [truncate(markdown)];
|
|
101
|
-
|
|
102
|
-
// Sort by score desc, take top entries, then restore original order
|
|
103
|
-
matched.sort((a, b) => b.score - a.score || a.index - b.index);
|
|
104
|
-
const top = matched.slice(0, 20);
|
|
105
|
-
top.sort((a, b) => a.index - b.index);
|
|
106
|
-
|
|
107
|
-
const joined = top.map((s) => s.text);
|
|
108
|
-
return truncateExcerpts(joined);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Truncate a string to MAX_CONTENT_BYTES. */
|
|
112
|
-
function truncate(text: string): string {
|
|
113
|
-
const encoder = new TextEncoder();
|
|
114
|
-
const bytes = encoder.encode(text);
|
|
115
|
-
if (bytes.length <= MAX_CONTENT_BYTES) return text;
|
|
116
|
-
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
117
|
-
return decoder.decode(bytes.slice(0, MAX_CONTENT_BYTES));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Truncate excerpt array so total joined size stays within limit. */
|
|
121
|
-
function truncateExcerpts(excerpts: string[]): string[] {
|
|
122
|
-
const encoder = new TextEncoder();
|
|
123
|
-
let total = 0;
|
|
124
|
-
const result: string[] = [];
|
|
125
|
-
for (const e of excerpts) {
|
|
126
|
-
const len = encoder.encode(e).length + 2; // +2 for \n\n join
|
|
127
|
-
if (total + len > MAX_CONTENT_BYTES) {
|
|
128
|
-
// Add truncated last excerpt if there's room
|
|
129
|
-
const remaining = MAX_CONTENT_BYTES - total;
|
|
130
|
-
if (remaining > 100) result.push(truncate(e));
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
result.push(e);
|
|
134
|
-
total += len;
|
|
135
|
-
}
|
|
136
|
-
return result.length > 0 ? result : [truncate(excerpts[0]!)];
|
|
137
|
-
}
|