@osovv/vv-opencode 1.1.6 → 1.2.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 +24 -0
- package/README.md +59 -2
- package/dist/commands/completion.js +1 -1
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.js +7 -2
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/patch-provider.d.ts +4 -4
- package/dist/commands/patch-provider.js +7 -6
- package/dist/commands/patch-provider.js.map +1 -1
- package/dist/commands/plugin-list.js +1 -1
- package/dist/commands/plugin-list.js.map +1 -1
- package/dist/commands/plugin-toggle.js +1 -1
- package/dist/commands/plugin-toggle.js.map +1 -1
- package/dist/commands/preset.js +1 -0
- package/dist/commands/preset.js.map +1 -1
- package/dist/commands/role.js +1 -1
- package/dist/commands/role.js.map +1 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/upgrade.js +1 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-models.js +1 -1
- package/dist/lib/agent-models.js.map +1 -1
- package/dist/lib/managed-agents.js +1 -1
- package/dist/lib/managed-agents.js.map +1 -1
- package/dist/lib/managed-skills.js +1 -1
- package/dist/lib/managed-skills.js.map +1 -1
- package/dist/lib/model-roles.js +1 -1
- package/dist/lib/model-roles.js.map +1 -1
- package/dist/lib/opencode.js +22 -7
- package/dist/lib/opencode.js.map +1 -1
- package/dist/lib/orchestration.js +4 -2
- package/dist/lib/orchestration.js.map +1 -1
- package/dist/lib/plugin-toggle-config.d.ts +1 -1
- package/dist/lib/plugin-toggle-config.js +4 -2
- package/dist/lib/plugin-toggle-config.js.map +1 -1
- package/dist/lib/vvoc-config.d.ts +93 -0
- package/dist/lib/vvoc-config.js +87 -2
- package/dist/lib/vvoc-config.js.map +1 -1
- package/dist/lib/vvoc-preset-registry.js +1 -1
- package/dist/lib/vvoc-preset-registry.js.map +1 -1
- package/dist/plugins/guardian/index.js +44 -6
- package/dist/plugins/guardian/index.js.map +1 -1
- package/dist/plugins/hashline-edit/edit-text-normalization.js +6 -5
- package/dist/plugins/hashline-edit/edit-text-normalization.js.map +1 -1
- package/dist/plugins/hashline-edit/index.js +16 -7
- package/dist/plugins/hashline-edit/index.js.map +1 -1
- package/dist/plugins/hashline-edit/tool-description.d.ts +1 -1
- package/dist/plugins/hashline-edit/tool-description.js +3 -2
- package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
- package/dist/plugins/secrets-redaction/config.d.ts +6 -1
- package/dist/plugins/secrets-redaction/config.js +29 -4
- package/dist/plugins/secrets-redaction/config.js.map +1 -1
- package/dist/plugins/secrets-redaction/engine.js +10 -4
- package/dist/plugins/secrets-redaction/engine.js.map +1 -1
- package/dist/plugins/secrets-redaction/index.js +14 -21
- package/dist/plugins/secrets-redaction/index.js.map +1 -1
- package/dist/plugins/secrets-redaction/patterns.js +3 -3
- package/dist/plugins/secrets-redaction/patterns.js.map +1 -1
- package/dist/plugins/secrets-redaction/session.js +0 -1
- package/dist/plugins/secrets-redaction/session.js.map +1 -1
- package/dist/plugins/web-tools/config.d.ts +43 -0
- package/dist/plugins/web-tools/config.js +134 -0
- package/dist/plugins/web-tools/config.js.map +1 -0
- package/dist/plugins/web-tools/fetch-service.d.ts +14 -0
- package/dist/plugins/web-tools/fetch-service.js +166 -0
- package/dist/plugins/web-tools/fetch-service.js.map +1 -0
- package/dist/plugins/web-tools/html-markdown.d.ts +13 -0
- package/dist/plugins/web-tools/html-markdown.js +82 -0
- package/dist/plugins/web-tools/html-markdown.js.map +1 -0
- package/dist/plugins/web-tools/http.d.ts +34 -0
- package/dist/plugins/web-tools/http.js +154 -0
- package/dist/plugins/web-tools/http.js.map +1 -0
- package/dist/plugins/web-tools/index.d.ts +18 -0
- package/dist/plugins/web-tools/index.js +93 -0
- package/dist/plugins/web-tools/index.js.map +1 -0
- package/dist/plugins/web-tools/media-loader.d.ts +26 -0
- package/dist/plugins/web-tools/media-loader.js +178 -0
- package/dist/plugins/web-tools/media-loader.js.map +1 -0
- package/dist/plugins/web-tools/providers/brave.d.ts +3 -0
- package/dist/plugins/web-tools/providers/brave.js +101 -0
- package/dist/plugins/web-tools/providers/brave.js.map +1 -0
- package/dist/plugins/web-tools/providers/exa.d.ts +28 -0
- package/dist/plugins/web-tools/providers/exa.js +138 -0
- package/dist/plugins/web-tools/providers/exa.js.map +1 -0
- package/dist/plugins/web-tools/providers/native-fetch.d.ts +21 -0
- package/dist/plugins/web-tools/providers/native-fetch.js +112 -0
- package/dist/plugins/web-tools/providers/native-fetch.js.map +1 -0
- package/dist/plugins/web-tools/providers/spider.d.ts +25 -0
- package/dist/plugins/web-tools/providers/spider.js +104 -0
- package/dist/plugins/web-tools/providers/spider.js.map +1 -0
- package/dist/plugins/web-tools/providers/zai.d.ts +33 -0
- package/dist/plugins/web-tools/providers/zai.js +181 -0
- package/dist/plugins/web-tools/providers/zai.js.map +1 -0
- package/dist/plugins/web-tools/search-service.d.ts +18 -0
- package/dist/plugins/web-tools/search-service.js +110 -0
- package/dist/plugins/web-tools/search-service.js.map +1 -0
- package/dist/plugins/workflow/index.js +24 -5
- package/dist/plugins/workflow/index.js.map +1 -1
- package/dist/plugins/workflow/persistence.js +1 -1
- package/dist/plugins/workflow/persistence.js.map +1 -1
- package/dist/plugins/workflow/state.d.ts +14 -0
- package/dist/plugins/workflow/state.js +44 -1
- package/dist/plugins/workflow/state.js.map +1 -1
- package/dist/plugins/workflow/tooling.js +1 -1
- package/dist/plugins/workflow/tooling.js.map +1 -1
- package/dist/tui/context/analyze.js +11 -3
- package/dist/tui/context/analyze.js.map +1 -1
- package/dist/tui/context/collect.js +1 -1
- package/dist/tui/context/collect.js.map +1 -1
- package/dist/tui/context/plugin.js +3 -2
- package/dist/tui/context/plugin.js.map +1 -1
- package/dist/tui.js +1 -1
- package/package.json +15 -8
- package/schemas/vvoc/v3.json +43 -1
- package/templates/skills/vv-execute/SKILL.md +17 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-markdown.js","sourceRoot":"","sources":["../../../src/plugins/web-tools/html-markdown.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,iBAAiB;AACjB,wBAAwB;AACxB,2FAA2F;AAC3F,8HAA8H;AAC9H,qCAAqC;AACrC,qDAAqD;AACrD,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,kFAAkF;AAClF,oEAAoE;AACpE,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,8GAA8G;AAC9G,qBAAqB;AAErB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,eAAe,MAAM,UAAU,CAAC;AAEvC,uBAAuB;AACvB,IAAI,cAA2C,CAAC;AAEhD,SAAS,WAAW;IAClB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,eAAe,CAAC;YACnC,YAAY,EAAE,KAAK;YACnB,cAAc,EAAE,QAAQ;YACxB,gBAAgB,EAAE,GAAG;SACtB,CAAC,CAAC;QACH,cAAc,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7C,CAAC;AACD,qBAAqB;AAErB,mBAAmB;AACnB;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,SAAS,CAAC,IAAI;YACZ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC1C,SAAS,IAAI,CAAC,CAAC;YACjB,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI;YACT,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;gBACpB,IAAI,IAAI,IAAI,CAAC;YACf,CAAC;QACH,CAAC;QACD,UAAU,CAAC,IAAI;YACb,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC1C,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;YACzC,CAAC;QACH,CAAC;KACF,EACD,EAAE,cAAc,EAAE,IAAI,EAAE,CACzB,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,MAAM,CAAC,GAAG,EAAE,CAAC;IACb,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AACD,iBAAiB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type WebHttpErrorCode = "NETWORK_ERROR" | "TIMEOUT" | "ABORTED" | "OVERSIZED" | "HTTP_ERROR";
|
|
2
|
+
/** Transport-level failure. status is set only for HTTP_ERROR. Messages never include credentials. */
|
|
3
|
+
export declare class WebHttpError extends Error {
|
|
4
|
+
readonly code: WebHttpErrorCode;
|
|
5
|
+
readonly status?: number;
|
|
6
|
+
constructor(code: WebHttpErrorCode, message: string, status?: number);
|
|
7
|
+
}
|
|
8
|
+
export type BoundedHttpRequest = {
|
|
9
|
+
url: string;
|
|
10
|
+
method?: "GET" | "POST";
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
body?: string;
|
|
13
|
+
/** Wall-clock timeout in milliseconds. */
|
|
14
|
+
timeoutMs: number;
|
|
15
|
+
/** Hard response body cap in bytes. */
|
|
16
|
+
maxBytes: number;
|
|
17
|
+
/** Optional caller abort signal; combined with the internal timeout controller. */
|
|
18
|
+
abort?: AbortSignal;
|
|
19
|
+
};
|
|
20
|
+
export type BoundedHttpResponse = {
|
|
21
|
+
status: number;
|
|
22
|
+
headers: Headers;
|
|
23
|
+
bytes: Uint8Array;
|
|
24
|
+
finalUrl: string;
|
|
25
|
+
};
|
|
26
|
+
/** Minimal fetch-compatible callable; avoids Bun-specific fetch properties for injectable mocks. */
|
|
27
|
+
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
28
|
+
/** Default cap for textual responses (5 MiB). */
|
|
29
|
+
export declare const DEFAULT_TEXT_MAX_BYTES: number;
|
|
30
|
+
/** Default cap for media responses (20 MiB). */
|
|
31
|
+
export declare const DEFAULT_MEDIA_MAX_BYTES: number;
|
|
32
|
+
/** Default request timeout (30 seconds). */
|
|
33
|
+
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
34
|
+
export declare function requestBounded(request: BoundedHttpRequest, fetchImpl?: FetchLike): Promise<BoundedHttpResponse>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// FILE: src/plugins/web-tools/http.ts
|
|
2
|
+
// VERSION: 1.0.0
|
|
3
|
+
// START_MODULE_CONTRACT
|
|
4
|
+
// PURPOSE: Provide shared bounded HTTP execution for the web tools providers and media loader.
|
|
5
|
+
// SCOPE: Timeout enforcement, caller abort propagation, a hard response-size cap, redirect following, and a transport error taxonomy.
|
|
6
|
+
// DEPENDS: [none]
|
|
7
|
+
// LINKS: [M-WEB-HTTP, M-WEB-EXA, M-WEB-BRAVE, M-WEB-NATIVE-FETCH, M-WEB-SPIDER, M-WEB-MEDIA-LOADER]
|
|
8
|
+
// ROLE: RUNTIME
|
|
9
|
+
// MAP_MODE: EXPORTS
|
|
10
|
+
// END_MODULE_CONTRACT
|
|
11
|
+
//
|
|
12
|
+
// START_MODULE_MAP
|
|
13
|
+
// WebHttpErrorCode - Transport error codes.
|
|
14
|
+
// WebHttpError - Transport-level error carrying a code and optional HTTP status.
|
|
15
|
+
// BoundedHttpRequest - Request input with timeout, size cap, and optional caller abort.
|
|
16
|
+
// BoundedHttpResponse - Response with status, headers, bytes, and final URL.
|
|
17
|
+
// FetchLike - Minimal fetch-compatible callable for injectable mocks.
|
|
18
|
+
// DEFAULT_TEXT_MAX_BYTES - Default cap for textual responses.
|
|
19
|
+
// DEFAULT_MEDIA_MAX_BYTES - Default cap for media responses.
|
|
20
|
+
// DEFAULT_REQUEST_TIMEOUT_MS - Default request timeout.
|
|
21
|
+
// requestBounded - Execute one bounded HTTP request.
|
|
22
|
+
// END_MODULE_MAP
|
|
23
|
+
//
|
|
24
|
+
// START_CHANGE_SUMMARY
|
|
25
|
+
// LAST_CHANGE: [v1.0.0 - Initial bounded HTTP transport and error taxonomy for the unified web tools.]
|
|
26
|
+
// END_CHANGE_SUMMARY
|
|
27
|
+
/** Transport-level failure. status is set only for HTTP_ERROR. Messages never include credentials. */
|
|
28
|
+
export class WebHttpError extends Error {
|
|
29
|
+
code;
|
|
30
|
+
status;
|
|
31
|
+
constructor(code, message, status) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "WebHttpError";
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.status = status;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Default cap for textual responses (5 MiB). */
|
|
39
|
+
export const DEFAULT_TEXT_MAX_BYTES = 5 * 1024 * 1024;
|
|
40
|
+
/** Default cap for media responses (20 MiB). */
|
|
41
|
+
export const DEFAULT_MEDIA_MAX_BYTES = 20 * 1024 * 1024;
|
|
42
|
+
/** Default request timeout (30 seconds). */
|
|
43
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
44
|
+
// START_BLOCK_HELPERS
|
|
45
|
+
function safeMessage(error) {
|
|
46
|
+
return error instanceof Error ? error.message : String(error);
|
|
47
|
+
}
|
|
48
|
+
function mapTransportError(error, controller, timedOut, timeoutMs) {
|
|
49
|
+
if (controller.signal.aborted) {
|
|
50
|
+
if (timedOut) {
|
|
51
|
+
return new WebHttpError("TIMEOUT", `request timed out after ${timeoutMs}ms`);
|
|
52
|
+
}
|
|
53
|
+
return new WebHttpError("ABORTED", "request aborted by caller");
|
|
54
|
+
}
|
|
55
|
+
return new WebHttpError("NETWORK_ERROR", `network error: ${safeMessage(error)}`);
|
|
56
|
+
}
|
|
57
|
+
async function readBodyWithCap(response, maxBytes, controller) {
|
|
58
|
+
const body = response.body;
|
|
59
|
+
if (!body) {
|
|
60
|
+
return new Uint8Array(0);
|
|
61
|
+
}
|
|
62
|
+
const reader = body.getReader();
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let total = 0;
|
|
65
|
+
try {
|
|
66
|
+
for (;;) {
|
|
67
|
+
const { done, value } = await reader.read();
|
|
68
|
+
if (done)
|
|
69
|
+
break;
|
|
70
|
+
if (!value)
|
|
71
|
+
continue;
|
|
72
|
+
total += value.byteLength;
|
|
73
|
+
if (total > maxBytes) {
|
|
74
|
+
controller.abort();
|
|
75
|
+
throw new WebHttpError("OVERSIZED", `response body exceeded ${maxBytes} bytes`);
|
|
76
|
+
}
|
|
77
|
+
chunks.push(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
reader.releaseLock();
|
|
82
|
+
}
|
|
83
|
+
const out = new Uint8Array(total);
|
|
84
|
+
let offset = 0;
|
|
85
|
+
for (const chunk of chunks) {
|
|
86
|
+
out.set(chunk, offset);
|
|
87
|
+
offset += chunk.byteLength;
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
// END_BLOCK_HELPERS
|
|
92
|
+
// START_CONTRACT: requestBounded
|
|
93
|
+
// PURPOSE: Execute one bounded HTTP request.
|
|
94
|
+
// INPUTS: { request: BoundedHttpRequest; fetchImpl: injectable fetch implementation for tests }
|
|
95
|
+
// OUTPUTS: { BoundedHttpResponse - status, headers, bytes, and finalUrl }
|
|
96
|
+
// SIDE_EFFECTS: performs network I/O through fetchImpl; rejects with WebHttpError on failure
|
|
97
|
+
// LINKS: M-WEB-EXA, M-WEB-BRAVE, M-WEB-NATIVE-FETCH, M-WEB-SPIDER, M-WEB-MEDIA-LOADER
|
|
98
|
+
// END_CONTRACT: requestBounded
|
|
99
|
+
export async function requestBounded(request, fetchImpl = fetch) {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
let timedOut = false;
|
|
102
|
+
const timeout = setTimeout(() => {
|
|
103
|
+
timedOut = true;
|
|
104
|
+
controller.abort();
|
|
105
|
+
}, request.timeoutMs);
|
|
106
|
+
const onCallerAbort = () => controller.abort();
|
|
107
|
+
if (request.abort) {
|
|
108
|
+
if (request.abort.aborted) {
|
|
109
|
+
controller.abort();
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
request.abort.addEventListener("abort", onCallerAbort, { once: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
let response;
|
|
117
|
+
try {
|
|
118
|
+
response = await fetchImpl(request.url, {
|
|
119
|
+
method: request.method ?? "GET",
|
|
120
|
+
headers: request.headers,
|
|
121
|
+
body: request.body,
|
|
122
|
+
signal: controller.signal,
|
|
123
|
+
redirect: "follow",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
throw mapTransportError(error, controller, timedOut, request.timeoutMs);
|
|
128
|
+
}
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
throw new WebHttpError("HTTP_ERROR", `HTTP ${response.status} for ${request.url}`, response.status);
|
|
131
|
+
}
|
|
132
|
+
let bytes;
|
|
133
|
+
try {
|
|
134
|
+
bytes = await readBodyWithCap(response, request.maxBytes, controller);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof WebHttpError) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
throw mapTransportError(error, controller, timedOut, request.timeoutMs);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
status: response.status,
|
|
144
|
+
headers: response.headers,
|
|
145
|
+
bytes,
|
|
146
|
+
finalUrl: response.url || request.url,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
request.abort?.removeEventListener("abort", onCallerAbort);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/plugins/web-tools/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Config, type Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
/** Built-in permission ids suppressed at runtime while web-tools is enabled. */
|
|
3
|
+
export declare const SUPPRESSED_BUILTIN_PERMISSIONS: readonly ["webfetch", "websearch"];
|
|
4
|
+
/**
|
|
5
|
+
* Hide the built-in web tools by denying their permission keys in the merged runtime config.
|
|
6
|
+
* Skips any key the user configured explicitly so explicit user policy always wins.
|
|
7
|
+
* Mutates only the in-memory config object; no files are written.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyBuiltinSuppression(config: Config): void;
|
|
10
|
+
/**
|
|
11
|
+
* WebToolsPlugin entry.
|
|
12
|
+
* Disabled toggle: returns empty hooks and injects nothing.
|
|
13
|
+
* Enabled: returns a config hook applying suppression and a tool map with exactly
|
|
14
|
+
* web_search and web_fetch. Startup logs include provider names and credential sources
|
|
15
|
+
* and never credential values; a git-tracked project config with an apiKey logs a warning
|
|
16
|
+
* naming the file without the value.
|
|
17
|
+
*/
|
|
18
|
+
export declare const WebToolsPlugin: Plugin;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// FILE: src/plugins/web-tools/index.ts
|
|
2
|
+
// VERSION: 1.0.0
|
|
3
|
+
// START_MODULE_CONTRACT
|
|
4
|
+
// PURPOSE: Register the canonical web_search and web_fetch tools and suppress OpenCode built-ins at runtime while the web-tools plugin is enabled.
|
|
5
|
+
// SCOPE: Startup vvoc snapshot use, plugin toggle handling, runtime permission suppression, tool registration, and credential-safe diagnostics.
|
|
6
|
+
// DEPENDS: [@opencode-ai/plugin, src/lib/config-layers.ts, src/lib/plugin-toggle-config.ts, src/plugins/web-tools/config.ts, src/plugins/web-tools/search-service.ts, src/plugins/web-tools/fetch-service.ts]
|
|
7
|
+
// LINKS: [M-PLUGIN-WEB-TOOLS, M-WEB-CONFIG, M-WEB-SEARCH-SERVICE, M-WEB-FETCH-SERVICE]
|
|
8
|
+
// ROLE: RUNTIME
|
|
9
|
+
// MAP_MODE: EXPORTS
|
|
10
|
+
// END_MODULE_CONTRACT
|
|
11
|
+
//
|
|
12
|
+
// START_MODULE_MAP
|
|
13
|
+
// SUPPRESSED_BUILTIN_PERMISSIONS - Built-in permission ids hidden while the plugin is enabled.
|
|
14
|
+
// applyBuiltinSuppression - Add runtime-only deny rules unless the user configured a key explicitly.
|
|
15
|
+
// WebToolsPlugin - Public plugin entry registering web_search and web_fetch.
|
|
16
|
+
// END_MODULE_MAP
|
|
17
|
+
//
|
|
18
|
+
// START_CHANGE_SUMMARY
|
|
19
|
+
// LAST_CHANGE: [C-ZAI-DIRECT-WEB-PROVIDERS - Added credential-safe direct Z.AI region diagnostics while preserving the canonical tool surface.]
|
|
20
|
+
// LAST_CHANGE: [v1.0.0 - Initial WebToolsPlugin with runtime built-in suppression and credential-safe startup diagnostics.]
|
|
21
|
+
// END_CHANGE_SUMMARY
|
|
22
|
+
import { loadVvocConfig } from "../../lib/config-layers.js";
|
|
23
|
+
import { isVvocPluginEnabled } from "../../lib/plugin-toggle-config.js";
|
|
24
|
+
import { resolveWebRuntimeConfig, warnIfSecretBearingProjectConfigTracked } from "./config.js";
|
|
25
|
+
import { createWebFetchTool } from "./fetch-service.js";
|
|
26
|
+
import { createWebSearchTool } from "./search-service.js";
|
|
27
|
+
/** Built-in permission ids suppressed at runtime while web-tools is enabled. */
|
|
28
|
+
export const SUPPRESSED_BUILTIN_PERMISSIONS = ["webfetch", "websearch"];
|
|
29
|
+
/**
|
|
30
|
+
* Hide the built-in web tools by denying their permission keys in the merged runtime config.
|
|
31
|
+
* Skips any key the user configured explicitly so explicit user policy always wins.
|
|
32
|
+
* Mutates only the in-memory config object; no files are written.
|
|
33
|
+
*/
|
|
34
|
+
export function applyBuiltinSuppression(config) {
|
|
35
|
+
config.permission ??= {};
|
|
36
|
+
const permission = config.permission;
|
|
37
|
+
for (const key of SUPPRESSED_BUILTIN_PERMISSIONS) {
|
|
38
|
+
if (permission[key] === undefined) {
|
|
39
|
+
permission[key] = "deny";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* WebToolsPlugin entry.
|
|
45
|
+
* Disabled toggle: returns empty hooks and injects nothing.
|
|
46
|
+
* Enabled: returns a config hook applying suppression and a tool map with exactly
|
|
47
|
+
* web_search and web_fetch. Startup logs include provider names and credential sources
|
|
48
|
+
* and never credential values; a git-tracked project config with an apiKey logs a warning
|
|
49
|
+
* naming the file without the value.
|
|
50
|
+
*/
|
|
51
|
+
export const WebToolsPlugin = async ({ client, directory }) => {
|
|
52
|
+
const vvoc = await loadVvocConfig({ cwd: directory });
|
|
53
|
+
if (!isVvocPluginEnabled(vvoc.config, "web-tools")) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
const resolved = resolveWebRuntimeConfig(vvoc);
|
|
57
|
+
await client.app.log({
|
|
58
|
+
body: {
|
|
59
|
+
service: "web-tools",
|
|
60
|
+
level: "info",
|
|
61
|
+
message: "web tools configuration loaded",
|
|
62
|
+
extra: {
|
|
63
|
+
searchProvider: resolved.search.provider,
|
|
64
|
+
...(resolved.search.provider === "zai" ? { searchRegion: resolved.search.region } : {}),
|
|
65
|
+
searchCredentialSource: resolved.search.credential?.source ?? "missing",
|
|
66
|
+
fetchProvider: resolved.fetch.provider,
|
|
67
|
+
...(resolved.fetch.provider === "zai" ? { fetchRegion: resolved.fetch.region } : {}),
|
|
68
|
+
fetchCredentialSource: resolved.fetch.provider === "native"
|
|
69
|
+
? "not-required"
|
|
70
|
+
: (resolved.fetch.credential?.source ?? "missing"),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
for (const warning of resolved.warnings) {
|
|
75
|
+
await client.app.log({
|
|
76
|
+
body: { service: "web-tools", level: "warn", message: warning },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const trackedConfigWarning = warnIfSecretBearingProjectConfigTracked(vvoc);
|
|
80
|
+
if (trackedConfigWarning) {
|
|
81
|
+
await client.app.log({
|
|
82
|
+
body: { service: "web-tools", level: "warn", message: trackedConfigWarning },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
config: async (config) => applyBuiltinSuppression(config),
|
|
87
|
+
tool: {
|
|
88
|
+
web_search: createWebSearchTool(resolved.search),
|
|
89
|
+
web_fetch: createWebFetchTool(resolved.fetch),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/web-tools/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ToolAttachment } from "@opencode-ai/plugin";
|
|
2
|
+
import { type FetchLike } from "./http.js";
|
|
3
|
+
export declare const SUPPORTED_MEDIA_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf"];
|
|
4
|
+
export type SupportedMediaMimeType = (typeof SUPPORTED_MEDIA_MIME_TYPES)[number];
|
|
5
|
+
/** Unsupported-media failure. The message names the detected or declared MIME type and never includes the payload. */
|
|
6
|
+
export declare class WebMediaError extends Error {
|
|
7
|
+
readonly code: "UNSUPPORTED";
|
|
8
|
+
readonly mime?: string;
|
|
9
|
+
constructor(message: string, mime?: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sniff the media MIME type from leading bytes.
|
|
13
|
+
* Returns undefined when the signature matches no supported format.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sniffMediaMimeType(bytes: Uint8Array): SupportedMediaMimeType | undefined;
|
|
16
|
+
export declare function mediaAttachmentFromBytes(bytes: Uint8Array, source: {
|
|
17
|
+
url: string;
|
|
18
|
+
declaredContentType?: string;
|
|
19
|
+
maxBytes?: number;
|
|
20
|
+
}): ToolAttachment;
|
|
21
|
+
export declare function loadMediaAttachment(input: {
|
|
22
|
+
url: string;
|
|
23
|
+
abort?: AbortSignal;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
maxBytes?: number;
|
|
26
|
+
}, fetchImpl?: FetchLike): Promise<ToolAttachment>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// FILE: src/plugins/web-tools/media-loader.ts
|
|
2
|
+
// VERSION: 1.0.0
|
|
3
|
+
// START_MODULE_CONTRACT
|
|
4
|
+
// PURPOSE: Load supported images and PDFs as base64 file attachments for the web tools fetch path.
|
|
5
|
+
// SCOPE: MIME and magic-byte identification, base64 data-URL attachment construction, filename derivation, and rejection of unsupported or oversized payloads.
|
|
6
|
+
// DEPENDS: [src/plugins/web-tools/http.ts, @opencode-ai/plugin]
|
|
7
|
+
// LINKS: [M-WEB-MEDIA-LOADER, M-WEB-HTTP, M-WEB-NATIVE-FETCH, M-WEB-SPIDER]
|
|
8
|
+
// ROLE: RUNTIME
|
|
9
|
+
// MAP_MODE: EXPORTS
|
|
10
|
+
// END_MODULE_CONTRACT
|
|
11
|
+
//
|
|
12
|
+
// START_MODULE_MAP
|
|
13
|
+
// SUPPORTED_MEDIA_MIME_TYPES - Tuple of supported media MIME types.
|
|
14
|
+
// SupportedMediaMimeType - Supported media MIME type union.
|
|
15
|
+
// WebMediaError - Unsupported-media error carrying the detected or declared MIME type.
|
|
16
|
+
// sniffMediaMimeType - Detect a supported MIME type from leading bytes.
|
|
17
|
+
// mediaAttachmentFromBytes - Build a base64 data-URL attachment from already-fetched bytes.
|
|
18
|
+
// loadMediaAttachment - Fetch a URL and return it as an attachment when supported.
|
|
19
|
+
// END_MODULE_MAP
|
|
20
|
+
//
|
|
21
|
+
// START_CHANGE_SUMMARY
|
|
22
|
+
// LAST_CHANGE: [v1.0.0 - Initial direct media attachment loader for the unified web tools.]
|
|
23
|
+
// END_CHANGE_SUMMARY
|
|
24
|
+
import { DEFAULT_MEDIA_MAX_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, requestBounded, WebHttpError, } from "./http.js";
|
|
25
|
+
export const SUPPORTED_MEDIA_MIME_TYPES = [
|
|
26
|
+
"image/jpeg",
|
|
27
|
+
"image/png",
|
|
28
|
+
"image/gif",
|
|
29
|
+
"image/webp",
|
|
30
|
+
"application/pdf",
|
|
31
|
+
];
|
|
32
|
+
/** Unsupported-media failure. The message names the detected or declared MIME type and never includes the payload. */
|
|
33
|
+
export class WebMediaError extends Error {
|
|
34
|
+
code;
|
|
35
|
+
mime;
|
|
36
|
+
constructor(message, mime) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "WebMediaError";
|
|
39
|
+
this.code = "UNSUPPORTED";
|
|
40
|
+
this.mime = mime;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const EXTENSION_BY_MIME = {
|
|
44
|
+
"image/jpeg": ".jpg",
|
|
45
|
+
"image/png": ".png",
|
|
46
|
+
"image/gif": ".gif",
|
|
47
|
+
"image/webp": ".webp",
|
|
48
|
+
"application/pdf": ".pdf",
|
|
49
|
+
};
|
|
50
|
+
// START_BLOCK_SNIFF
|
|
51
|
+
/**
|
|
52
|
+
* Sniff the media MIME type from leading bytes.
|
|
53
|
+
* Returns undefined when the signature matches no supported format.
|
|
54
|
+
*/
|
|
55
|
+
export function sniffMediaMimeType(bytes) {
|
|
56
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
57
|
+
return "image/jpeg";
|
|
58
|
+
}
|
|
59
|
+
if (bytes.length >= 4 &&
|
|
60
|
+
bytes[0] === 0x89 &&
|
|
61
|
+
bytes[1] === 0x50 &&
|
|
62
|
+
bytes[2] === 0x4e &&
|
|
63
|
+
bytes[3] === 0x47) {
|
|
64
|
+
return "image/png";
|
|
65
|
+
}
|
|
66
|
+
if (bytes.length >= 4 &&
|
|
67
|
+
bytes[0] === 0x47 &&
|
|
68
|
+
bytes[1] === 0x49 &&
|
|
69
|
+
bytes[2] === 0x46 &&
|
|
70
|
+
bytes[3] === 0x38) {
|
|
71
|
+
return "image/gif";
|
|
72
|
+
}
|
|
73
|
+
if (bytes.length >= 12 &&
|
|
74
|
+
bytes[0] === 0x52 &&
|
|
75
|
+
bytes[1] === 0x49 &&
|
|
76
|
+
bytes[2] === 0x46 &&
|
|
77
|
+
bytes[3] === 0x46 &&
|
|
78
|
+
bytes[8] === 0x57 &&
|
|
79
|
+
bytes[9] === 0x45 &&
|
|
80
|
+
bytes[10] === 0x42 &&
|
|
81
|
+
bytes[11] === 0x50) {
|
|
82
|
+
return "image/webp";
|
|
83
|
+
}
|
|
84
|
+
if (bytes.length >= 4 &&
|
|
85
|
+
bytes[0] === 0x25 &&
|
|
86
|
+
bytes[1] === 0x50 &&
|
|
87
|
+
bytes[2] === 0x44 &&
|
|
88
|
+
bytes[3] === 0x46) {
|
|
89
|
+
return "application/pdf";
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
// END_BLOCK_SNIFF
|
|
94
|
+
// START_BLOCK_HELPERS
|
|
95
|
+
function normalizeDeclaredContentType(declared) {
|
|
96
|
+
if (!declared) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const mime = declared.split(";")[0]?.trim().toLowerCase();
|
|
100
|
+
return mime || undefined;
|
|
101
|
+
}
|
|
102
|
+
function filenameFromUrl(url, mime) {
|
|
103
|
+
const extension = EXTENSION_BY_MIME[mime];
|
|
104
|
+
try {
|
|
105
|
+
const base = new URL(url).pathname.split("/").pop() ?? "";
|
|
106
|
+
if (base && base.includes(".")) {
|
|
107
|
+
return base;
|
|
108
|
+
}
|
|
109
|
+
if (base) {
|
|
110
|
+
return `${base}${extension}`;
|
|
111
|
+
}
|
|
112
|
+
return `resource${extension}`;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return `resource${extension}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function resolveMimeType(bytes, declaredContentType) {
|
|
119
|
+
const sniffed = sniffMediaMimeType(bytes);
|
|
120
|
+
if (sniffed) {
|
|
121
|
+
return sniffed;
|
|
122
|
+
}
|
|
123
|
+
const declared = normalizeDeclaredContentType(declaredContentType);
|
|
124
|
+
if (declared && SUPPORTED_MEDIA_MIME_TYPES.includes(declared)) {
|
|
125
|
+
return declared;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
// END_BLOCK_HELPERS
|
|
130
|
+
// START_CONTRACT: mediaAttachmentFromBytes
|
|
131
|
+
// PURPOSE: Build a base64 data-URL file attachment from already-fetched bytes.
|
|
132
|
+
// INPUTS: { bytes: payload; source: url, declaredContentType, optional maxBytes }
|
|
133
|
+
// OUTPUTS: { ToolAttachment - type file, mime, data URL, filename }
|
|
134
|
+
// SIDE_EFFECTS: none; performs no network I/O
|
|
135
|
+
// LINKS: M-WEB-NATIVE-FETCH, M-WEB-SPIDER
|
|
136
|
+
// END_CONTRACT: mediaAttachmentFromBytes
|
|
137
|
+
export function mediaAttachmentFromBytes(bytes, source) {
|
|
138
|
+
const maxBytes = source.maxBytes ?? DEFAULT_MEDIA_MAX_BYTES;
|
|
139
|
+
if (bytes.byteLength > maxBytes) {
|
|
140
|
+
throw new WebHttpError("OVERSIZED", `media payload exceeded ${maxBytes} bytes`);
|
|
141
|
+
}
|
|
142
|
+
const mime = resolveMimeType(bytes, source.declaredContentType);
|
|
143
|
+
if (!mime) {
|
|
144
|
+
const detail = sniffMediaMimeType(bytes) ??
|
|
145
|
+
normalizeDeclaredContentType(source.declaredContentType) ??
|
|
146
|
+
"unknown";
|
|
147
|
+
throw new WebMediaError(`unsupported media type: ${detail}`, detail === "unknown" ? undefined : detail);
|
|
148
|
+
}
|
|
149
|
+
const base64 = Buffer.from(bytes).toString("base64");
|
|
150
|
+
return {
|
|
151
|
+
type: "file",
|
|
152
|
+
mime,
|
|
153
|
+
url: `data:${mime};base64,${base64}`,
|
|
154
|
+
filename: filenameFromUrl(source.url, mime),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// START_CONTRACT: loadMediaAttachment
|
|
158
|
+
// PURPOSE: Fetch a URL through the bounded transport and return it as an attachment when supported.
|
|
159
|
+
// INPUTS: { input: url, optional abort, timeoutMs, maxBytes; fetchImpl: injectable fetch }
|
|
160
|
+
// OUTPUTS: { ToolAttachment }
|
|
161
|
+
// SIDE_EFFECTS: performs network I/O through fetchImpl
|
|
162
|
+
// LINKS: M-WEB-HTTP
|
|
163
|
+
// END_CONTRACT: loadMediaAttachment
|
|
164
|
+
export async function loadMediaAttachment(input, fetchImpl) {
|
|
165
|
+
const maxBytes = input.maxBytes ?? DEFAULT_MEDIA_MAX_BYTES;
|
|
166
|
+
const response = await requestBounded({
|
|
167
|
+
url: input.url,
|
|
168
|
+
timeoutMs: input.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
169
|
+
maxBytes,
|
|
170
|
+
abort: input.abort,
|
|
171
|
+
}, fetchImpl);
|
|
172
|
+
return mediaAttachmentFromBytes(response.bytes, {
|
|
173
|
+
url: response.finalUrl,
|
|
174
|
+
declaredContentType: response.headers.get("content-type") ?? undefined,
|
|
175
|
+
maxBytes,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=media-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-loader.js","sourceRoot":"","sources":["../../../src/plugins/web-tools/media-loader.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,iBAAiB;AACjB,wBAAwB;AACxB,qGAAqG;AACrG,iKAAiK;AACjK,kEAAkE;AAClE,8EAA8E;AAC9E,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,sEAAsE;AACtE,8DAA8D;AAC9D,yFAAyF;AACzF,0EAA0E;AAC1E,8FAA8F;AAC9F,qFAAqF;AACrF,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,8FAA8F;AAC9F,qBAAqB;AAGrB,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,cAAc,EACd,YAAY,GAEb,MAAM,WAAW,CAAC;AAEnB,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,YAAY;IACZ,WAAW;IACX,WAAW;IACX,YAAY;IACZ,iBAAiB;CACT,CAAC;AAIX,sHAAsH;AACtH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,IAAI,CAAgB;IACpB,IAAI,CAAU;IAEvB,YAAY,OAAe,EAAE,IAAa;QACxC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,iBAAiB,GAA2C;IAChE,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,YAAY,EAAE,OAAO;IACrB,iBAAiB,EAAE,MAAM;CAC1B,CAAC;AAEF,oBAAoB;AACpB;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAiB;IAClD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACrF,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,IACE,KAAK,CAAC,MAAM,IAAI,CAAC;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EACjB,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,IACE,KAAK,CAAC,MAAM,IAAI,CAAC;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EACjB,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,IACE,KAAK,CAAC,MAAM,IAAI,EAAE;QAClB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI;QAClB,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,EAClB,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,IACE,KAAK,CAAC,MAAM,IAAI,CAAC;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QACjB,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EACjB,CAAC;QACD,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AACD,kBAAkB;AAElB,sBAAsB;AACtB,SAAS,4BAA4B,CAAC,QAAiB;IACrD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1D,OAAO,IAAI,IAAI,SAAS,CAAC;AAC3B,CAAC;AAED,SAAS,eAAe,CAAC,GAAW,EAAE,IAA4B;IAChE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QAC1D,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC;QAC/B,CAAC;QACD,OAAO,WAAW,SAAS,EAAE,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,SAAS,EAAE,CAAC;IAChC,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CACtB,KAAiB,EACjB,mBAA4B;IAE5B,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,QAAQ,GAAG,4BAA4B,CAAC,mBAAmB,CAAC,CAAC;IACnE,IAAI,QAAQ,IAAK,0BAAgD,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrF,OAAO,QAAkC,CAAC;IAC5C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AACD,oBAAoB;AAEpB,2CAA2C;AAC3C,iFAAiF;AACjF,oFAAoF;AACpF,sEAAsE;AACtE,gDAAgD;AAChD,4CAA4C;AAC5C,yCAAyC;AACzC,MAAM,UAAU,wBAAwB,CACtC,KAAiB,EACjB,MAAwE;IAExE,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,uBAAuB,CAAC;IAC5D,IAAI,KAAK,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,0BAA0B,QAAQ,QAAQ,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAChE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,MAAM,GACV,kBAAkB,CAAC,KAAK,CAAC;YACzB,4BAA4B,CAAC,MAAM,CAAC,mBAAmB,CAAC;YACxD,SAAS,CAAC;QACZ,MAAM,IAAI,aAAa,CACrB,2BAA2B,MAAM,EAAE,EACnC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAC1C,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrD,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,IAAI;QACJ,GAAG,EAAE,QAAQ,IAAI,WAAW,MAAM,EAAE;QACpC,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC;KAC5C,CAAC;AACJ,CAAC;AAED,sCAAsC;AACtC,sGAAsG;AACtG,6FAA6F;AAC7F,gCAAgC;AAChC,yDAAyD;AACzD,sBAAsB;AACtB,oCAAoC;AACpC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAkF,EAClF,SAAqB;IAErB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,uBAAuB,CAAC;IAC3D,MAAM,QAAQ,GAAG,MAAM,cAAc,CACnC;QACE,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,0BAA0B;QACxD,QAAQ;QACR,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,EACD,SAAS,CACV,CAAC;IACF,OAAO,wBAAwB,CAAC,QAAQ,CAAC,KAAK,EAAE;QAC9C,GAAG,EAAE,QAAQ,CAAC,QAAQ;QACtB,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS;QACtE,QAAQ;KACT,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// FILE: src/plugins/web-tools/providers/brave.ts
|
|
2
|
+
// VERSION: 1.0.0
|
|
3
|
+
// START_MODULE_CONTRACT
|
|
4
|
+
// PURPOSE: Call the Brave Web Search endpoint directly and normalize web results for the web_search tool.
|
|
5
|
+
// SCOPE: Brave request construction with X-Subscription-Token auth, count and freshness mapping, moderate safe search, and web result normalization.
|
|
6
|
+
// DEPENDS: [src/plugins/web-tools/http.ts, src/plugins/web-tools/providers/exa.ts]
|
|
7
|
+
// LINKS: [M-WEB-BRAVE, M-WEB-HTTP, M-WEB-SEARCH-SERVICE]
|
|
8
|
+
// ROLE: RUNTIME
|
|
9
|
+
// MAP_MODE: EXPORTS
|
|
10
|
+
// END_MODULE_CONTRACT
|
|
11
|
+
//
|
|
12
|
+
// START_MODULE_MAP
|
|
13
|
+
// searchBrave - Execute one Brave Web Search.
|
|
14
|
+
// END_MODULE_MAP
|
|
15
|
+
//
|
|
16
|
+
// START_CHANGE_SUMMARY
|
|
17
|
+
// LAST_CHANGE: [v1.0.0 - Initial Brave Web Search adapter.]
|
|
18
|
+
// END_CHANGE_SUMMARY
|
|
19
|
+
import { DEFAULT_TEXT_MAX_BYTES, requestBounded, } from "../http.js";
|
|
20
|
+
import { mapHttpErrorToProviderError, WebProviderError, } from "./exa.js";
|
|
21
|
+
const BRAVE_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
22
|
+
const BRAVE_FRESHNESS = { day: "pd", week: "pw", month: "pm", year: "py" };
|
|
23
|
+
// START_BLOCK_HELPERS
|
|
24
|
+
function isPlainObject(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function normalizeBraveResult(item) {
|
|
28
|
+
if (!isPlainObject(item)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const url = typeof item.url === "string" ? item.url : undefined;
|
|
32
|
+
if (!url) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const title = typeof item.title === "string" && item.title.length > 0 ? item.title : url;
|
|
36
|
+
const snippet = typeof item.description === "string" && item.description.length > 0
|
|
37
|
+
? item.description
|
|
38
|
+
: undefined;
|
|
39
|
+
const publishedAt = typeof item.page_age === "string" ? item.page_age : undefined;
|
|
40
|
+
return {
|
|
41
|
+
title,
|
|
42
|
+
url,
|
|
43
|
+
...(snippet ? { snippet } : {}),
|
|
44
|
+
...(publishedAt ? { publishedAt } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function parseBraveResults(bytes) {
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new WebProviderError("brave", "BAD_RESPONSE", "malformed JSON response from brave");
|
|
54
|
+
}
|
|
55
|
+
const web = isPlainObject(parsed) ? parsed.web : undefined;
|
|
56
|
+
const results = isPlainObject(web) && Array.isArray(web.results) ? web.results : undefined;
|
|
57
|
+
if (!results) {
|
|
58
|
+
throw new WebProviderError("brave", "BAD_RESPONSE", "unexpected brave response envelope");
|
|
59
|
+
}
|
|
60
|
+
return results
|
|
61
|
+
.map(normalizeBraveResult)
|
|
62
|
+
.filter((result) => result !== undefined);
|
|
63
|
+
}
|
|
64
|
+
// END_BLOCK_HELPERS
|
|
65
|
+
// START_CONTRACT: searchBrave
|
|
66
|
+
// PURPOSE: GET the Brave Web Search endpoint with X-Subscription-Token auth and normalize web.results.
|
|
67
|
+
// INPUTS: { input: WebSearchInput; fetchImpl: injectable fetch }
|
|
68
|
+
// OUTPUTS: { WebSearchResult[] - ranked results, empty array when none }
|
|
69
|
+
// SIDE_EFFECTS: performs network I/O through fetchImpl; never logs the credential
|
|
70
|
+
// LINKS: M-WEB-SEARCH-SERVICE
|
|
71
|
+
// END_CONTRACT: searchBrave
|
|
72
|
+
export async function searchBrave(input, fetchImpl) {
|
|
73
|
+
const params = new URLSearchParams();
|
|
74
|
+
params.set("q", input.query);
|
|
75
|
+
params.set("count", String(input.count));
|
|
76
|
+
params.set("safesearch", "moderate");
|
|
77
|
+
if (input.freshness) {
|
|
78
|
+
params.set("freshness", BRAVE_FRESHNESS[input.freshness]);
|
|
79
|
+
}
|
|
80
|
+
const url = `${BRAVE_SEARCH_URL}?${params.toString()}`;
|
|
81
|
+
let response;
|
|
82
|
+
try {
|
|
83
|
+
response = await requestBounded({
|
|
84
|
+
url,
|
|
85
|
+
method: "GET",
|
|
86
|
+
headers: {
|
|
87
|
+
"X-Subscription-Token": input.credential.value,
|
|
88
|
+
accept: "application/json",
|
|
89
|
+
},
|
|
90
|
+
timeoutMs: input.timeoutMs,
|
|
91
|
+
maxBytes: DEFAULT_TEXT_MAX_BYTES,
|
|
92
|
+
abort: input.abort,
|
|
93
|
+
}, fetchImpl);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
mapHttpErrorToProviderError(error, "brave");
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return parseBraveResults(response.bytes);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=brave.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brave.js","sourceRoot":"","sources":["../../../../src/plugins/web-tools/providers/brave.ts"],"names":[],"mappings":"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"}
|