@osovv/vv-opencode 1.1.6 → 1.2.1

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +59 -2
  3. package/dist/commands/completion.js +1 -1
  4. package/dist/commands/completion.js.map +1 -1
  5. package/dist/commands/init.js +1 -1
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/install.js +7 -2
  8. package/dist/commands/install.js.map +1 -1
  9. package/dist/commands/patch-provider.d.ts +4 -4
  10. package/dist/commands/patch-provider.js +7 -6
  11. package/dist/commands/patch-provider.js.map +1 -1
  12. package/dist/commands/plugin-list.js +1 -1
  13. package/dist/commands/plugin-list.js.map +1 -1
  14. package/dist/commands/plugin-toggle.js +1 -1
  15. package/dist/commands/plugin-toggle.js.map +1 -1
  16. package/dist/commands/preset.js +1 -0
  17. package/dist/commands/preset.js.map +1 -1
  18. package/dist/commands/role.js +1 -1
  19. package/dist/commands/role.js.map +1 -1
  20. package/dist/commands/sync.js +7 -2
  21. package/dist/commands/sync.js.map +1 -1
  22. package/dist/commands/upgrade.js +1 -1
  23. package/dist/commands/upgrade.js.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +7 -5
  26. package/dist/index.js.map +1 -1
  27. package/dist/lib/agent-models.js +1 -1
  28. package/dist/lib/agent-models.js.map +1 -1
  29. package/dist/lib/managed-agents.js +1 -1
  30. package/dist/lib/managed-agents.js.map +1 -1
  31. package/dist/lib/managed-skills.js +1 -1
  32. package/dist/lib/managed-skills.js.map +1 -1
  33. package/dist/lib/model-roles.js +1 -1
  34. package/dist/lib/model-roles.js.map +1 -1
  35. package/dist/lib/opencode.js +22 -7
  36. package/dist/lib/opencode.js.map +1 -1
  37. package/dist/lib/orchestration.js +4 -2
  38. package/dist/lib/orchestration.js.map +1 -1
  39. package/dist/lib/plugin-toggle-config.d.ts +1 -1
  40. package/dist/lib/plugin-toggle-config.js +4 -2
  41. package/dist/lib/plugin-toggle-config.js.map +1 -1
  42. package/dist/lib/vvoc-config.d.ts +93 -0
  43. package/dist/lib/vvoc-config.js +87 -2
  44. package/dist/lib/vvoc-config.js.map +1 -1
  45. package/dist/lib/vvoc-paths.js +2 -3
  46. package/dist/lib/vvoc-paths.js.map +1 -1
  47. package/dist/lib/vvoc-preset-registry.js +1 -1
  48. package/dist/lib/vvoc-preset-registry.js.map +1 -1
  49. package/dist/plugins/guardian/index.js +44 -6
  50. package/dist/plugins/guardian/index.js.map +1 -1
  51. package/dist/plugins/hashline-edit/edit-text-normalization.js +6 -5
  52. package/dist/plugins/hashline-edit/edit-text-normalization.js.map +1 -1
  53. package/dist/plugins/hashline-edit/index.js +16 -7
  54. package/dist/plugins/hashline-edit/index.js.map +1 -1
  55. package/dist/plugins/hashline-edit/tool-description.d.ts +1 -1
  56. package/dist/plugins/hashline-edit/tool-description.js +3 -2
  57. package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
  58. package/dist/plugins/secrets-redaction/config.d.ts +6 -1
  59. package/dist/plugins/secrets-redaction/config.js +30 -5
  60. package/dist/plugins/secrets-redaction/config.js.map +1 -1
  61. package/dist/plugins/secrets-redaction/deep.js +1 -1
  62. package/dist/plugins/secrets-redaction/deep.js.map +1 -1
  63. package/dist/plugins/secrets-redaction/engine.js +10 -4
  64. package/dist/plugins/secrets-redaction/engine.js.map +1 -1
  65. package/dist/plugins/secrets-redaction/index.js +14 -21
  66. package/dist/plugins/secrets-redaction/index.js.map +1 -1
  67. package/dist/plugins/secrets-redaction/patterns.js +4 -4
  68. package/dist/plugins/secrets-redaction/patterns.js.map +1 -1
  69. package/dist/plugins/secrets-redaction/restore.js +1 -1
  70. package/dist/plugins/secrets-redaction/restore.js.map +1 -1
  71. package/dist/plugins/secrets-redaction/session.js +1 -2
  72. package/dist/plugins/secrets-redaction/session.js.map +1 -1
  73. package/dist/plugins/web-tools/config.d.ts +43 -0
  74. package/dist/plugins/web-tools/config.js +134 -0
  75. package/dist/plugins/web-tools/config.js.map +1 -0
  76. package/dist/plugins/web-tools/fetch-service.d.ts +14 -0
  77. package/dist/plugins/web-tools/fetch-service.js +169 -0
  78. package/dist/plugins/web-tools/fetch-service.js.map +1 -0
  79. package/dist/plugins/web-tools/html-markdown.d.ts +13 -0
  80. package/dist/plugins/web-tools/html-markdown.js +82 -0
  81. package/dist/plugins/web-tools/html-markdown.js.map +1 -0
  82. package/dist/plugins/web-tools/http.d.ts +34 -0
  83. package/dist/plugins/web-tools/http.js +154 -0
  84. package/dist/plugins/web-tools/http.js.map +1 -0
  85. package/dist/plugins/web-tools/index.d.ts +18 -0
  86. package/dist/plugins/web-tools/index.js +93 -0
  87. package/dist/plugins/web-tools/index.js.map +1 -0
  88. package/dist/plugins/web-tools/media-loader.d.ts +26 -0
  89. package/dist/plugins/web-tools/media-loader.js +178 -0
  90. package/dist/plugins/web-tools/media-loader.js.map +1 -0
  91. package/dist/plugins/web-tools/providers/brave.d.ts +3 -0
  92. package/dist/plugins/web-tools/providers/brave.js +101 -0
  93. package/dist/plugins/web-tools/providers/brave.js.map +1 -0
  94. package/dist/plugins/web-tools/providers/exa.d.ts +28 -0
  95. package/dist/plugins/web-tools/providers/exa.js +138 -0
  96. package/dist/plugins/web-tools/providers/exa.js.map +1 -0
  97. package/dist/plugins/web-tools/providers/native-fetch.d.ts +21 -0
  98. package/dist/plugins/web-tools/providers/native-fetch.js +112 -0
  99. package/dist/plugins/web-tools/providers/native-fetch.js.map +1 -0
  100. package/dist/plugins/web-tools/providers/spider.d.ts +25 -0
  101. package/dist/plugins/web-tools/providers/spider.js +104 -0
  102. package/dist/plugins/web-tools/providers/spider.js.map +1 -0
  103. package/dist/plugins/web-tools/providers/zai.d.ts +33 -0
  104. package/dist/plugins/web-tools/providers/zai.js +181 -0
  105. package/dist/plugins/web-tools/providers/zai.js.map +1 -0
  106. package/dist/plugins/web-tools/search-service.d.ts +18 -0
  107. package/dist/plugins/web-tools/search-service.js +110 -0
  108. package/dist/plugins/web-tools/search-service.js.map +1 -0
  109. package/dist/plugins/workflow/index.js +24 -5
  110. package/dist/plugins/workflow/index.js.map +1 -1
  111. package/dist/plugins/workflow/persistence.js +1 -1
  112. package/dist/plugins/workflow/persistence.js.map +1 -1
  113. package/dist/plugins/workflow/state.d.ts +14 -0
  114. package/dist/plugins/workflow/state.js +44 -1
  115. package/dist/plugins/workflow/state.js.map +1 -1
  116. package/dist/plugins/workflow/tooling.js +1 -1
  117. package/dist/plugins/workflow/tooling.js.map +1 -1
  118. package/dist/tui/context/analyze.js +11 -3
  119. package/dist/tui/context/analyze.js.map +1 -1
  120. package/dist/tui/context/collect.js +1 -1
  121. package/dist/tui/context/collect.js.map +1 -1
  122. package/dist/tui/context/plugin.js +3 -2
  123. package/dist/tui/context/plugin.js.map +1 -1
  124. package/dist/tui.js +1 -1
  125. package/package.json +15 -8
  126. package/schemas/vvoc/v3.json +43 -1
  127. package/templates/skills/vv-execute/SKILL.md +17 -12
@@ -0,0 +1,169 @@
1
+ // FILE: src/plugins/web-tools/fetch-service.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Build the provider-neutral web_fetch tool: input validation, permission request, provider dispatch, and structured text or attachment results.
5
+ // SCOPE: web_fetch ToolDefinition factory; delegates retrieval and conversion to the native, Spider, and direct Z.AI adapters.
6
+ // DEPENDS: [@opencode-ai/plugin, src/plugins/web-tools/config.ts, src/plugins/web-tools/providers/native-fetch.ts, src/plugins/web-tools/providers/spider.ts, src/plugins/web-tools/providers/zai.ts, src/plugins/web-tools/providers/exa.ts]
7
+ // LINKS: M-WEB-FETCH-SERVICE, M-WEB-NATIVE-FETCH, M-WEB-SPIDER, M-WEB-ZAI, M-WEB-MEDIA-LOADER, M-WEB-EXA, M-PLUGIN-WEB-TOOLS
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // WEB_FETCH_DEFAULT_TIMEOUT_SECONDS - Default per-call timeout in seconds.
14
+ // WEB_FETCH_MAX_TIMEOUT_SECONDS - Maximum model-configurable timeout in seconds.
15
+ // createWebFetchTool - Create the web_fetch ToolDefinition bound to a resolved fetch config.
16
+ // END_MODULE_MAP
17
+ //
18
+ // START_CHANGE_SUMMARY
19
+ // LAST_CHANGE: [DIRECT-FIX - Applied web_fetch format and timeout defaults inside execute when OpenCode omits schema defaults at runtime.]
20
+ // LAST_CHANGE: [C-ZAI-DIRECT-WEB-PROVIDERS - Routed canonical web_fetch calls through the explicit-region direct Z.AI reader adapter.]
21
+ // LAST_CHANGE: [v1.0.0 - Initial web_fetch tool service.]
22
+ // END_CHANGE_SUMMARY
23
+ import { tool, } from "@opencode-ai/plugin";
24
+ import { WebProviderError } from "./providers/exa.js";
25
+ import { fetchNative } from "./providers/native-fetch.js";
26
+ import { scrapeSpider } from "./providers/spider.js";
27
+ import { fetchZai } from "./providers/zai.js";
28
+ const z = tool.schema;
29
+ export const WEB_FETCH_DEFAULT_TIMEOUT_SECONDS = 30;
30
+ export const WEB_FETCH_MAX_TIMEOUT_SECONDS = 120;
31
+ // START_BLOCK_RESULT_MAPPING
32
+ function mediaSummary(attachment) {
33
+ const name = attachment.filename ? `\`${attachment.filename}\`` : "the requested resource";
34
+ return `Fetched ${name} as a ${attachment.mime} attachment.`;
35
+ }
36
+ function nativeResult(url, format, outcome) {
37
+ if (outcome.kind === "media") {
38
+ return {
39
+ title: `web_fetch: ${url}`,
40
+ output: mediaSummary(outcome.attachment),
41
+ attachments: [outcome.attachment],
42
+ metadata: { provider: "native", format, status: outcome.status },
43
+ };
44
+ }
45
+ return {
46
+ title: `web_fetch: ${url}`,
47
+ output: outcome.content,
48
+ metadata: { provider: "native", format, status: outcome.status },
49
+ };
50
+ }
51
+ function spiderResult(url, format, credentialSource, outcome) {
52
+ const metadata = {
53
+ provider: "spider",
54
+ format,
55
+ credentialSource,
56
+ ...outcome.metadata,
57
+ };
58
+ if (outcome.kind === "media") {
59
+ return {
60
+ title: `web_fetch: ${url}`,
61
+ output: mediaSummary(outcome.attachment),
62
+ attachments: [outcome.attachment],
63
+ metadata,
64
+ };
65
+ }
66
+ return { title: `web_fetch: ${url}`, output: outcome.content, metadata };
67
+ }
68
+ function zaiResult(url, format, region, credentialSource, outcome) {
69
+ const metadata = {
70
+ provider: "zai",
71
+ region,
72
+ format,
73
+ credentialSource,
74
+ ...outcome.metadata,
75
+ };
76
+ if (outcome.kind === "media") {
77
+ return {
78
+ title: `web_fetch: ${url}`,
79
+ output: mediaSummary(outcome.attachment),
80
+ attachments: [outcome.attachment],
81
+ metadata,
82
+ };
83
+ }
84
+ return { title: `web_fetch: ${url}`, output: outcome.content, metadata };
85
+ }
86
+ // END_BLOCK_RESULT_MAPPING
87
+ /**
88
+ * Create the web_fetch tool bound to the resolved fetch configuration.
89
+ * execute validates http or https schemes, asks permission key web_fetch with patterns [url],
90
+ * routes textual extraction to native, Spider, or direct Z.AI reader, returns media as attachments with a short
91
+ * Markdown summary in the same ToolResult, and reports metadata
92
+ * { provider, format, credentialSource?, status?, durationMs? }.
93
+ * Native fetch requires no credential; Spider and Z.AI validate credentials at execution time
94
+ * with actionable messages naming the environment variable and web.fetch.apiKey.
95
+ */
96
+ export function createWebFetchTool(resolved) {
97
+ return tool({
98
+ description: "Fetch a known HTTP or HTTPS URL using the configured provider. Returns Markdown, text, raw HTML, or an image/PDF attachment.",
99
+ args: {
100
+ url: z.string().min(1).describe("The HTTP or HTTPS URL to retrieve."),
101
+ format: z
102
+ .enum(["markdown", "text", "html"])
103
+ .default("markdown")
104
+ .describe("Output format for textual resources; defaults to markdown."),
105
+ timeout: z
106
+ .number()
107
+ .positive()
108
+ .max(WEB_FETCH_MAX_TIMEOUT_SECONDS)
109
+ .default(WEB_FETCH_DEFAULT_TIMEOUT_SECONDS)
110
+ .describe("Timeout in seconds, greater than 0 and at most 120."),
111
+ },
112
+ async execute(args, context) {
113
+ let parsedUrl;
114
+ try {
115
+ parsedUrl = new URL(args.url);
116
+ }
117
+ catch {
118
+ throw new Error("web_fetch requires a valid HTTP or HTTPS URL");
119
+ }
120
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
121
+ throw new Error(`web_fetch supports only HTTP and HTTPS URLs; received ${parsedUrl.protocol || "unknown scheme"}`);
122
+ }
123
+ const format = args.format ?? "markdown";
124
+ const timeoutSeconds = args.timeout ?? WEB_FETCH_DEFAULT_TIMEOUT_SECONDS;
125
+ await context.ask({
126
+ permission: "web_fetch",
127
+ patterns: [args.url],
128
+ always: [],
129
+ metadata: { provider: resolved.provider, format },
130
+ });
131
+ const timeoutMs = timeoutSeconds * 1000;
132
+ if (resolved.provider === "native") {
133
+ const outcome = await fetchNative({
134
+ url: args.url,
135
+ format,
136
+ abort: context.abort,
137
+ timeoutMs,
138
+ });
139
+ return nativeResult(args.url, format, outcome);
140
+ }
141
+ if (!resolved.credential) {
142
+ throw new WebProviderError(resolved.provider, "MISSING_CREDENTIAL", `missing credential for ${resolved.provider}: set ${resolved.envVar ?? "SPIDER_API_KEY"} or ${resolved.configField ?? "web.fetch.apiKey"}`);
143
+ }
144
+ if (resolved.provider === "zai") {
145
+ if (!resolved.region) {
146
+ throw new Error("web.fetch.region is required when provider is zai");
147
+ }
148
+ const outcome = await fetchZai({
149
+ url: args.url,
150
+ format,
151
+ region: resolved.region,
152
+ credential: resolved.credential,
153
+ abort: context.abort,
154
+ timeoutMs,
155
+ });
156
+ return zaiResult(args.url, format, resolved.region, resolved.credential.source, outcome);
157
+ }
158
+ const outcome = await scrapeSpider({
159
+ url: args.url,
160
+ format,
161
+ credential: resolved.credential,
162
+ abort: context.abort,
163
+ timeoutMs,
164
+ });
165
+ return spiderResult(args.url, format, resolved.credential.source, outcome);
166
+ },
167
+ });
168
+ }
169
+ //# sourceMappingURL=fetch-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-service.js","sourceRoot":"","sources":["../../../src/plugins/web-tools/fetch-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Convert an HTML document to GitHub-flavored Markdown.
3
+ * Removes script and style content, keeps links as [text](href),
4
+ * and preserves headings, lists, and code blocks.
5
+ * Returns an empty string for empty or whitespace-only input.
6
+ */
7
+ export declare function convertHtmlToMarkdown(html: string): string;
8
+ /**
9
+ * Extract visible text from an HTML document.
10
+ * Removes script and style content, decodes entities, and collapses whitespace runs.
11
+ * Returns an empty string for empty or whitespace-only input.
12
+ */
13
+ export declare function convertHtmlToText(html: string): string;
@@ -0,0 +1,82 @@
1
+ // FILE: src/plugins/web-tools/html-markdown.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Convert HTML locally to Markdown or plain text for the native fetch provider.
5
+ // SCOPE: Turndown-based Markdown conversion and htmlparser2-based text extraction, both stripping script and style content.
6
+ // DEPENDS: [turndown, htmlparser2]
7
+ // LINKS: [M-WEB-HTML-MARKDOWN, M-WEB-NATIVE-FETCH]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // convertHtmlToMarkdown - Convert an HTML document to GitHub-flavored Markdown.
14
+ // convertHtmlToText - Extract visible text from an HTML document.
15
+ // END_MODULE_MAP
16
+ //
17
+ // START_CHANGE_SUMMARY
18
+ // LAST_CHANGE: [v1.0.0 - Initial local HTML to Markdown and text conversion for the native fetch provider.]
19
+ // END_CHANGE_SUMMARY
20
+ import { Parser } from "htmlparser2";
21
+ import TurndownService from "turndown";
22
+ // START_BLOCK_MARKDOWN
23
+ let sharedTurndown;
24
+ function getTurndown() {
25
+ if (!sharedTurndown) {
26
+ sharedTurndown = new TurndownService({
27
+ headingStyle: "atx",
28
+ codeBlockStyle: "fenced",
29
+ bulletListMarker: "-",
30
+ });
31
+ sharedTurndown.remove(["script", "style"]);
32
+ }
33
+ return sharedTurndown;
34
+ }
35
+ /**
36
+ * Convert an HTML document to GitHub-flavored Markdown.
37
+ * Removes script and style content, keeps links as [text](href),
38
+ * and preserves headings, lists, and code blocks.
39
+ * Returns an empty string for empty or whitespace-only input.
40
+ */
41
+ export function convertHtmlToMarkdown(html) {
42
+ if (!html || html.trim() === "") {
43
+ return "";
44
+ }
45
+ return getTurndown().turndown(html).trim();
46
+ }
47
+ // END_BLOCK_MARKDOWN
48
+ // START_BLOCK_TEXT
49
+ /**
50
+ * Extract visible text from an HTML document.
51
+ * Removes script and style content, decodes entities, and collapses whitespace runs.
52
+ * Returns an empty string for empty or whitespace-only input.
53
+ */
54
+ export function convertHtmlToText(html) {
55
+ if (!html || html.trim() === "") {
56
+ return "";
57
+ }
58
+ let text = "";
59
+ let skipDepth = 0;
60
+ const parser = new Parser({
61
+ onopentag(name) {
62
+ if (name === "script" || name === "style") {
63
+ skipDepth += 1;
64
+ }
65
+ },
66
+ ontext(data) {
67
+ if (skipDepth === 0) {
68
+ text += data;
69
+ }
70
+ },
71
+ onclosetag(name) {
72
+ if (name === "script" || name === "style") {
73
+ skipDepth = Math.max(0, skipDepth - 1);
74
+ }
75
+ },
76
+ }, { decodeEntities: true });
77
+ parser.write(html);
78
+ parser.end();
79
+ return text.replace(/\s+/g, " ").trim();
80
+ }
81
+ // END_BLOCK_TEXT
82
+ //# sourceMappingURL=html-markdown.js.map
@@ -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>;