scoutline 0.1.0 → 0.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.
Files changed (242) hide show
  1. package/README.md +394 -7
  2. package/bin/scoutline.js +36 -15
  3. package/dist/capabilities/diagnostics.d.ts +135 -0
  4. package/dist/capabilities/diagnostics.d.ts.map +1 -0
  5. package/dist/capabilities/diagnostics.js +139 -0
  6. package/dist/capabilities/diagnostics.js.map +1 -0
  7. package/dist/capabilities/quota.d.ts +99 -0
  8. package/dist/capabilities/quota.d.ts.map +1 -0
  9. package/dist/capabilities/quota.js +129 -0
  10. package/dist/capabilities/quota.js.map +1 -0
  11. package/dist/capabilities/reader.d.ts +227 -0
  12. package/dist/capabilities/reader.d.ts.map +1 -0
  13. package/dist/capabilities/reader.js +100 -0
  14. package/dist/capabilities/reader.js.map +1 -0
  15. package/dist/capabilities/repository.d.ts +221 -0
  16. package/dist/capabilities/repository.d.ts.map +1 -0
  17. package/dist/capabilities/repository.js +172 -0
  18. package/dist/capabilities/repository.js.map +1 -0
  19. package/dist/capabilities/search.d.ts +110 -0
  20. package/dist/capabilities/search.d.ts.map +1 -0
  21. package/dist/capabilities/search.js +21 -0
  22. package/dist/capabilities/search.js.map +1 -0
  23. package/dist/capabilities/vision.d.ts +156 -0
  24. package/dist/capabilities/vision.d.ts.map +1 -0
  25. package/dist/capabilities/vision.js +111 -0
  26. package/dist/capabilities/vision.js.map +1 -0
  27. package/dist/command-invocation.d.ts +58 -0
  28. package/dist/command-invocation.d.ts.map +1 -0
  29. package/dist/command-invocation.js +95 -0
  30. package/dist/command-invocation.js.map +1 -0
  31. package/dist/commands/cache.d.ts +106 -0
  32. package/dist/commands/cache.d.ts.map +1 -0
  33. package/dist/commands/cache.js +203 -0
  34. package/dist/commands/cache.js.map +1 -0
  35. package/dist/commands/code.d.ts +11 -4
  36. package/dist/commands/code.d.ts.map +1 -1
  37. package/dist/commands/code.js +16 -42
  38. package/dist/commands/code.js.map +1 -1
  39. package/dist/commands/doctor.d.ts +69 -3
  40. package/dist/commands/doctor.d.ts.map +1 -1
  41. package/dist/commands/doctor.js +179 -52
  42. package/dist/commands/doctor.js.map +1 -1
  43. package/dist/commands/quota.d.ts +45 -3
  44. package/dist/commands/quota.d.ts.map +1 -1
  45. package/dist/commands/quota.js +116 -60
  46. package/dist/commands/quota.js.map +1 -1
  47. package/dist/commands/read.d.ts +76 -2
  48. package/dist/commands/read.d.ts.map +1 -1
  49. package/dist/commands/read.js +260 -121
  50. package/dist/commands/read.js.map +1 -1
  51. package/dist/commands/repo.d.ts +55 -4
  52. package/dist/commands/repo.d.ts.map +1 -1
  53. package/dist/commands/repo.js +115 -172
  54. package/dist/commands/repo.js.map +1 -1
  55. package/dist/commands/repository-explorer.d.ts +147 -0
  56. package/dist/commands/repository-explorer.d.ts.map +1 -0
  57. package/dist/commands/repository-explorer.js +550 -0
  58. package/dist/commands/repository-explorer.js.map +1 -0
  59. package/dist/commands/search.d.ts +27 -2
  60. package/dist/commands/search.d.ts.map +1 -1
  61. package/dist/commands/search.js +95 -103
  62. package/dist/commands/search.js.map +1 -1
  63. package/dist/commands/tools.d.ts +9 -3
  64. package/dist/commands/tools.d.ts.map +1 -1
  65. package/dist/commands/tools.js +29 -59
  66. package/dist/commands/tools.js.map +1 -1
  67. package/dist/commands/vision.d.ts +35 -9
  68. package/dist/commands/vision.d.ts.map +1 -1
  69. package/dist/commands/vision.js +184 -131
  70. package/dist/commands/vision.js.map +1 -1
  71. package/dist/index.d.ts +62 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +580 -223
  74. package/dist/index.js.map +1 -1
  75. package/dist/lib/cache.d.ts +178 -12
  76. package/dist/lib/cache.d.ts.map +1 -1
  77. package/dist/lib/cache.js +377 -39
  78. package/dist/lib/cache.js.map +1 -1
  79. package/dist/lib/code-mode.d.ts +13 -0
  80. package/dist/lib/code-mode.d.ts.map +1 -1
  81. package/dist/lib/code-mode.js +26 -6
  82. package/dist/lib/code-mode.js.map +1 -1
  83. package/dist/lib/config.d.ts.map +1 -1
  84. package/dist/lib/config.js +15 -18
  85. package/dist/lib/config.js.map +1 -1
  86. package/dist/lib/errors.d.ts +89 -11
  87. package/dist/lib/errors.d.ts.map +1 -1
  88. package/dist/lib/errors.js +168 -21
  89. package/dist/lib/errors.js.map +1 -1
  90. package/dist/lib/execution.d.ts +190 -0
  91. package/dist/lib/execution.d.ts.map +1 -0
  92. package/dist/lib/execution.js +383 -0
  93. package/dist/lib/execution.js.map +1 -0
  94. package/dist/lib/index.d.ts +10 -2
  95. package/dist/lib/index.d.ts.map +1 -1
  96. package/dist/lib/index.js +10 -2
  97. package/dist/lib/index.js.map +1 -1
  98. package/dist/lib/mcp-client.d.ts +75 -16
  99. package/dist/lib/mcp-client.d.ts.map +1 -1
  100. package/dist/lib/mcp-client.js +173 -108
  101. package/dist/lib/mcp-client.js.map +1 -1
  102. package/dist/lib/mcp-config.d.ts +17 -0
  103. package/dist/lib/mcp-config.d.ts.map +1 -1
  104. package/dist/lib/mcp-config.js +29 -0
  105. package/dist/lib/mcp-config.js.map +1 -1
  106. package/dist/lib/monitor-client.d.ts +17 -3
  107. package/dist/lib/monitor-client.d.ts.map +1 -1
  108. package/dist/lib/monitor-client.js +23 -69
  109. package/dist/lib/monitor-client.js.map +1 -1
  110. package/dist/lib/output.d.ts +53 -7
  111. package/dist/lib/output.d.ts.map +1 -1
  112. package/dist/lib/output.js +126 -39
  113. package/dist/lib/output.js.map +1 -1
  114. package/dist/lib/redact.d.ts +62 -3
  115. package/dist/lib/redact.d.ts.map +1 -1
  116. package/dist/lib/redact.js +165 -31
  117. package/dist/lib/redact.js.map +1 -1
  118. package/dist/lib/tool-cache.d.ts +86 -0
  119. package/dist/lib/tool-cache.d.ts.map +1 -0
  120. package/dist/lib/tool-cache.js +123 -0
  121. package/dist/lib/tool-cache.js.map +1 -0
  122. package/dist/lib/tty.d.ts +7 -20
  123. package/dist/lib/tty.d.ts.map +1 -1
  124. package/dist/lib/tty.js +51 -30
  125. package/dist/lib/tty.js.map +1 -1
  126. package/dist/node-command-invocation-adapter.d.ts +28 -0
  127. package/dist/node-command-invocation-adapter.d.ts.map +1 -0
  128. package/dist/node-command-invocation-adapter.js +88 -0
  129. package/dist/node-command-invocation-adapter.js.map +1 -0
  130. package/dist/providers/minimax/adapter.d.ts +49 -0
  131. package/dist/providers/minimax/adapter.d.ts.map +1 -0
  132. package/dist/providers/minimax/adapter.js +450 -0
  133. package/dist/providers/minimax/adapter.js.map +1 -0
  134. package/dist/providers/minimax/coding-plan-client.d.ts +60 -0
  135. package/dist/providers/minimax/coding-plan-client.d.ts.map +1 -0
  136. package/dist/providers/minimax/coding-plan-client.js +204 -0
  137. package/dist/providers/minimax/coding-plan-client.js.map +1 -0
  138. package/dist/providers/minimax/config.d.ts +32 -0
  139. package/dist/providers/minimax/config.d.ts.map +1 -0
  140. package/dist/providers/minimax/config.js +84 -0
  141. package/dist/providers/minimax/config.js.map +1 -0
  142. package/dist/providers/minimax/media.d.ts +88 -0
  143. package/dist/providers/minimax/media.d.ts.map +1 -0
  144. package/dist/providers/minimax/media.js +232 -0
  145. package/dist/providers/minimax/media.js.map +1 -0
  146. package/dist/providers/minimax/quota-client.d.ts +44 -0
  147. package/dist/providers/minimax/quota-client.d.ts.map +1 -0
  148. package/dist/providers/minimax/quota-client.js +83 -0
  149. package/dist/providers/minimax/quota-client.js.map +1 -0
  150. package/dist/providers/minimax/quota.d.ts +48 -0
  151. package/dist/providers/minimax/quota.d.ts.map +1 -0
  152. package/dist/providers/minimax/quota.js +120 -0
  153. package/dist/providers/minimax/quota.js.map +1 -0
  154. package/dist/providers/minimax/sdk-client.d.ts +29 -0
  155. package/dist/providers/minimax/sdk-client.d.ts.map +1 -0
  156. package/dist/providers/minimax/sdk-client.js +50 -0
  157. package/dist/providers/minimax/sdk-client.js.map +1 -0
  158. package/dist/providers/minimax/vision-attestations.d.ts +52 -0
  159. package/dist/providers/minimax/vision-attestations.d.ts.map +1 -0
  160. package/dist/providers/minimax/vision-attestations.js +79 -0
  161. package/dist/providers/minimax/vision-attestations.js.map +1 -0
  162. package/dist/providers/minimax/vision-conformance.d.ts +186 -0
  163. package/dist/providers/minimax/vision-conformance.d.ts.map +1 -0
  164. package/dist/providers/minimax/vision-conformance.js +371 -0
  165. package/dist/providers/minimax/vision-conformance.js.map +1 -0
  166. package/dist/providers/minimax/vision-mappings/chart.d.ts +35 -0
  167. package/dist/providers/minimax/vision-mappings/chart.d.ts.map +1 -0
  168. package/dist/providers/minimax/vision-mappings/chart.js +55 -0
  169. package/dist/providers/minimax/vision-mappings/chart.js.map +1 -0
  170. package/dist/providers/minimax/vision-mappings/common.d.ts +60 -0
  171. package/dist/providers/minimax/vision-mappings/common.d.ts.map +1 -0
  172. package/dist/providers/minimax/vision-mappings/common.js +75 -0
  173. package/dist/providers/minimax/vision-mappings/common.js.map +1 -0
  174. package/dist/providers/minimax/vision-mappings/diagnose-error.d.ts +35 -0
  175. package/dist/providers/minimax/vision-mappings/diagnose-error.d.ts.map +1 -0
  176. package/dist/providers/minimax/vision-mappings/diagnose-error.js +55 -0
  177. package/dist/providers/minimax/vision-mappings/diagnose-error.js.map +1 -0
  178. package/dist/providers/minimax/vision-mappings/diagram.d.ts +35 -0
  179. package/dist/providers/minimax/vision-mappings/diagram.d.ts.map +1 -0
  180. package/dist/providers/minimax/vision-mappings/diagram.js +55 -0
  181. package/dist/providers/minimax/vision-mappings/diagram.js.map +1 -0
  182. package/dist/providers/minimax/vision-mappings/extract-text.d.ts +35 -0
  183. package/dist/providers/minimax/vision-mappings/extract-text.d.ts.map +1 -0
  184. package/dist/providers/minimax/vision-mappings/extract-text.js +55 -0
  185. package/dist/providers/minimax/vision-mappings/extract-text.js.map +1 -0
  186. package/dist/providers/minimax/vision-mappings/ui-artifact.d.ts +35 -0
  187. package/dist/providers/minimax/vision-mappings/ui-artifact.d.ts.map +1 -0
  188. package/dist/providers/minimax/vision-mappings/ui-artifact.js +69 -0
  189. package/dist/providers/minimax/vision-mappings/ui-artifact.js.map +1 -0
  190. package/dist/providers/minimax/vision-mappings.generated.d.ts +29 -0
  191. package/dist/providers/minimax/vision-mappings.generated.d.ts.map +1 -0
  192. package/dist/providers/minimax/vision-mappings.generated.js +29 -0
  193. package/dist/providers/minimax/vision-mappings.generated.js.map +1 -0
  194. package/dist/providers/minimax/vision-revisions.d.ts +29 -0
  195. package/dist/providers/minimax/vision-revisions.d.ts.map +1 -0
  196. package/dist/providers/minimax/vision-revisions.js +34 -0
  197. package/dist/providers/minimax/vision-revisions.js.map +1 -0
  198. package/dist/providers/registry.d.ts +36 -0
  199. package/dist/providers/registry.d.ts.map +1 -0
  200. package/dist/providers/registry.js +45 -0
  201. package/dist/providers/registry.js.map +1 -0
  202. package/dist/providers/selection.d.ts +44 -0
  203. package/dist/providers/selection.d.ts.map +1 -0
  204. package/dist/providers/selection.js +85 -0
  205. package/dist/providers/selection.js.map +1 -0
  206. package/dist/providers/types.d.ts +281 -0
  207. package/dist/providers/types.d.ts.map +1 -0
  208. package/dist/providers/types.js +127 -0
  209. package/dist/providers/types.js.map +1 -0
  210. package/dist/providers/zai/adapter.d.ts +35 -0
  211. package/dist/providers/zai/adapter.d.ts.map +1 -0
  212. package/dist/providers/zai/adapter.js +685 -0
  213. package/dist/providers/zai/adapter.js.map +1 -0
  214. package/dist/providers/zai/credentials.d.ts +38 -0
  215. package/dist/providers/zai/credentials.d.ts.map +1 -0
  216. package/dist/providers/zai/credentials.js +60 -0
  217. package/dist/providers/zai/credentials.js.map +1 -0
  218. package/dist/providers/zai/encoded-error.d.ts +90 -0
  219. package/dist/providers/zai/encoded-error.d.ts.map +1 -0
  220. package/dist/providers/zai/encoded-error.js +169 -0
  221. package/dist/providers/zai/encoded-error.js.map +1 -0
  222. package/dist/providers/zai/media.d.ts +38 -0
  223. package/dist/providers/zai/media.d.ts.map +1 -0
  224. package/dist/providers/zai/media.js +107 -0
  225. package/dist/providers/zai/media.js.map +1 -0
  226. package/dist/providers/zai/monitor-client.d.ts +53 -0
  227. package/dist/providers/zai/monitor-client.d.ts.map +1 -0
  228. package/dist/providers/zai/monitor-client.js +117 -0
  229. package/dist/providers/zai/monitor-client.js.map +1 -0
  230. package/dist/providers/zai/quota.d.ts +47 -0
  231. package/dist/providers/zai/quota.d.ts.map +1 -0
  232. package/dist/providers/zai/quota.js +127 -0
  233. package/dist/providers/zai/quota.js.map +1 -0
  234. package/dist/providers/zai/reader.d.ts +82 -0
  235. package/dist/providers/zai/reader.d.ts.map +1 -0
  236. package/dist/providers/zai/reader.js +490 -0
  237. package/dist/providers/zai/reader.js.map +1 -0
  238. package/dist/providers/zai/repository.d.ts +76 -0
  239. package/dist/providers/zai/repository.d.ts.map +1 -0
  240. package/dist/providers/zai/repository.js +715 -0
  241. package/dist/providers/zai/repository.js.map +1 -0
  242. package/package.json +11 -6
@@ -0,0 +1,685 @@
1
+ /**
2
+ * Z.AI Search Adapter (DESIGN.md §5, §7, §11 — P2-03).
3
+ *
4
+ * Implements the real Z.AI Provider Descriptor with Search support.
5
+ * The Adapter owns credentials, transport lifecycle, Provider field
6
+ * mapping, and failure normalization. Shared execution owns cache and
7
+ * retry policy, so Adapter invocations disable client-owned cache and
8
+ * retries (`noCache: true, disableRetry: true`).
9
+ *
10
+ * Boundary rules (ARCHITECTURE.md §2):
11
+ * - May import capability types, normalized errors, Provider-specific
12
+ * transport (ZaiMcpClient), and Provider identity types.
13
+ * - Must NOT import command presentation, output mode, or another
14
+ * Provider's Adapter.
15
+ *
16
+ * Field mapping (DESIGN.md §7):
17
+ * title -> title
18
+ * link -> url
19
+ * content -> summary
20
+ * media -> source
21
+ * publish_date -> date
22
+ *
23
+ * The Adapter sends only `search_query`, domain, recency, content size,
24
+ * and location to the Provider. It NEVER sends count.
25
+ */
26
+ import crypto from "node:crypto";
27
+ import { ApiError, AuthError, NetworkError, TimeoutError, ValidationError, } from "../../lib/errors.js";
28
+ import { getMcpToolName } from "../../lib/mcp-config.js";
29
+ import { ZaiMcpClient, } from "../../lib/mcp-client.js";
30
+ import { buildLegacyRepositoryCacheKey } from "../../lib/cache.js";
31
+ import { isZaiConfigured, requireZaiApiKey } from "./credentials.js";
32
+ import { resolveImageSource, resolveVideoSource } from "./media.js";
33
+ import { createZaiQuotaCapability } from "./quota.js";
34
+ import { createZaiRepositoryCapability } from "./repository.js";
35
+ import { createZaiReaderCapability } from "./reader.js";
36
+ const SEARCH_TOOL_PUBLIC_NAME = getMcpToolName("search", "web_search_prime");
37
+ const VISION_ANALYZE_TOOL_PUBLIC_NAME = getMcpToolName("vision", "analyze_image");
38
+ // ---------------------------------------------------------------------------
39
+ // Vision Capability — operations wired in this Adapter (P3-03, P3-04)
40
+ // ---------------------------------------------------------------------------
41
+ /**
42
+ * Public dotted MCP tool names for every Z.AI Vision operation. Each
43
+ * resolves internally through the P2-03 name-translation fix. The Adapter
44
+ * invokes only the raw tool path; it does NOT call the high-level
45
+ * `ZaiMcpClient` wrapper methods.
46
+ */
47
+ const VISION_UI_TO_ARTIFACT_TOOL_PUBLIC_NAME = getMcpToolName("vision", "ui_to_artifact");
48
+ const VISION_EXTRACT_TEXT_TOOL_PUBLIC_NAME = getMcpToolName("vision", "extract_text_from_screenshot");
49
+ const VISION_DIAGNOSE_ERROR_TOOL_PUBLIC_NAME = getMcpToolName("vision", "diagnose_error_screenshot");
50
+ const VISION_DIAGRAM_TOOL_PUBLIC_NAME = getMcpToolName("vision", "understand_technical_diagram");
51
+ const VISION_CHART_TOOL_PUBLIC_NAME = getMcpToolName("vision", "analyze_data_visualization");
52
+ const VISION_DIFF_TOOL_PUBLIC_NAME = getMcpToolName("vision", "ui_diff_check");
53
+ const VISION_VIDEO_TOOL_PUBLIC_NAME = getMcpToolName("vision", "analyze_video");
54
+ /**
55
+ * Vision operations the Z.AI Adapter implements. P3-03 wired the general
56
+ * single-image interpretation; P3-04 adds every specialized operation so
57
+ * selecting Z.AI preserves Phase 1 behaviour (DESIGN.md §8: "Z.AI maps
58
+ * all current operations to dedicated MCP operations"). The descriptor
59
+ * advertises exactly what this set contains.
60
+ */
61
+ const ZAI_VISION_OPERATIONS = new Set([
62
+ "interpret-image",
63
+ "ui-artifact",
64
+ "extract-text",
65
+ "diagnose-error",
66
+ "diagram",
67
+ "chart",
68
+ "diff",
69
+ "video",
70
+ ]);
71
+ // ---------------------------------------------------------------------------
72
+ // Provider-owned legacy cache candidate
73
+ // ---------------------------------------------------------------------------
74
+ /**
75
+ * Build the legacy Z.AI cache key for a request. The shape mirrors the
76
+ * pre-P2-05 `buildCacheKey` calls the search command made through
77
+ * `ZaiMcpClient.webSearch`. `legacyCount` reconstructs the old `count`
78
+ * argument so an existing on-disk entry can still be served.
79
+ *
80
+ * P6-08A: the already-resolved `apiKey` is consumed through the same
81
+ * pure explicit-key algorithm `buildLegacyRepositoryCacheKey` exposes
82
+ * for the Repository Capability. The two algorithms are byte-identical
83
+ * for the same `(apiKey, command, args)`:
84
+ *
85
+ * credentialPart = sha256(apiKey).hex.slice(0, 12)
86
+ * argumentPart = sha256(JSON.stringify({ command, args })).hex.slice(0, 24)
87
+ * key = `${command}.${credentialPart}.${argumentPart}.json`
88
+ *
89
+ * Switching to the pure helper removes the ambient `getApiKey()` read
90
+ * that the legacy `buildCacheKey` performed. An Adapter created with
91
+ * `{ env: { Z_AI_API_KEY: "injected-only" } }` no longer throws
92
+ * `CONFIGURATION_ERROR` from `search.cacheIdentity()` when ambient
93
+ * credentials are absent. The output bytes for a given credential
94
+ * are unchanged.
95
+ */
96
+ function buildLegacyZaiSearchKey(apiKey, request, legacyCount) {
97
+ const args = { search_query: request.query };
98
+ if (legacyCount !== undefined) {
99
+ args.count = legacyCount;
100
+ }
101
+ if (request.controls?.domain) {
102
+ args.search_domain_filter = request.controls.domain;
103
+ }
104
+ if (request.controls?.recency) {
105
+ args.search_recency_filter = request.controls.recency;
106
+ }
107
+ if (request.controls?.contentSize) {
108
+ args.content_size = request.controls.contentSize;
109
+ }
110
+ if (request.controls?.location) {
111
+ args.location = request.controls.location;
112
+ }
113
+ return buildLegacyRepositoryCacheKey(apiKey, SEARCH_TOOL_PUBLIC_NAME, args);
114
+ }
115
+ /**
116
+ * Decode a raw legacy Z.AI search entry into normalized `SearchSource[]`.
117
+ * Returns `null` for any shape that is not a `WebSearchResult[]` so the
118
+ * cache layer treats invalid legacy data as a miss.
119
+ */
120
+ function decodeLegacyZaiSearch(raw) {
121
+ if (!Array.isArray(raw))
122
+ return null;
123
+ const out = [];
124
+ for (const entry of raw) {
125
+ if (!entry || typeof entry !== "object")
126
+ return null;
127
+ const record = entry;
128
+ const title = typeof record.title === "string" ? record.title : "";
129
+ const link = typeof record.link === "string" ? record.link : "";
130
+ const content = typeof record.content === "string" ? record.content : "";
131
+ if (!title || !link || !content) {
132
+ // Invalid legacy entry — treat the whole candidate as a miss.
133
+ return null;
134
+ }
135
+ const source = { title, url: link, summary: content };
136
+ if (typeof record.media === "string" && record.media.length > 0) {
137
+ source.source = record.media;
138
+ }
139
+ if (typeof record.publish_date === "string" && record.publish_date.length > 0) {
140
+ source.date = record.publish_date;
141
+ }
142
+ out.push(source);
143
+ }
144
+ return out;
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Provider-owned credential fingerprint
148
+ // ---------------------------------------------------------------------------
149
+ function credentialFingerprint(apiKey) {
150
+ return crypto.createHash("sha256").update(apiKey).digest("hex");
151
+ }
152
+ function createZaiSearchCapability(options) {
153
+ const { env, clientFactory } = options;
154
+ // Credential resolution is shared (Fixup A — B4/B7): the alias
155
+ // `ZAI_API_KEY` is accepted and a missing key is a configuration
156
+ // failure (ConfigurationError, exit 3), not an auth failure.
157
+ function resolveApiKey() {
158
+ return requireZaiApiKey(env);
159
+ }
160
+ const capability = {
161
+ validate(request) {
162
+ if (!request || typeof request.query !== "string" || !request.query.trim()) {
163
+ throw new ValidationError("Search query must contain at least one non-whitespace character");
164
+ }
165
+ },
166
+ cacheIdentity(request, compatibility) {
167
+ const apiKey = resolveApiKey();
168
+ const legacyCandidates = [
169
+ {
170
+ key: buildLegacyZaiSearchKey(apiKey, request, compatibility?.legacyCount),
171
+ decode: decodeLegacyZaiSearch,
172
+ },
173
+ ];
174
+ // Mirror only `query` and Provider controls into identity. Count is
175
+ // never part of identity (it enters only the legacy key above).
176
+ const identityRequest = {
177
+ query: request.query,
178
+ };
179
+ if (request.controls) {
180
+ identityRequest.controls = request.controls;
181
+ }
182
+ return {
183
+ provider: "zai",
184
+ capability: "search",
185
+ credentialFingerprint: credentialFingerprint(apiKey),
186
+ request: identityRequest,
187
+ legacyCandidates,
188
+ };
189
+ },
190
+ async invoke(request) {
191
+ // Validate before any transport access.
192
+ capability.validate(request);
193
+ // Disable client-owned cache and retry so shared execution is the
194
+ // single policy owner.
195
+ const clientOptions = {
196
+ enableVision: false,
197
+ noCache: true,
198
+ disableRetry: true,
199
+ };
200
+ const client = clientFactory(clientOptions);
201
+ try {
202
+ const args = buildZaiSearchArgs(request);
203
+ const raw = await invokeZaiSearch(client, args);
204
+ return normalizeZaiSearchResults(raw);
205
+ }
206
+ finally {
207
+ await client.close().catch(() => { });
208
+ }
209
+ },
210
+ };
211
+ return capability;
212
+ }
213
+ /**
214
+ * Build the Z.AI Provider request arguments. The Adapter sends only
215
+ * `search_query`, domain, recency, content size, and location. Count is
216
+ * NEVER included — it remains command-local.
217
+ */
218
+ function buildZaiSearchArgs(request) {
219
+ const args = { search_query: request.query };
220
+ const controls = request.controls;
221
+ if (controls?.domain) {
222
+ args.search_domain_filter = controls.domain;
223
+ }
224
+ if (controls?.recency) {
225
+ args.search_recency_filter = controls.recency;
226
+ }
227
+ if (controls?.contentSize) {
228
+ args.content_size = controls.contentSize;
229
+ }
230
+ if (controls?.location) {
231
+ args.location = controls.location;
232
+ }
233
+ return args;
234
+ }
235
+ async function invokeZaiSearch(client, args) {
236
+ try {
237
+ const result = await client.callToolRaw(SEARCH_TOOL_PUBLIC_NAME, args);
238
+ if (!Array.isArray(result)) {
239
+ throw new ApiError("Z.AI search returned a non-array result", 500);
240
+ }
241
+ return result;
242
+ }
243
+ catch (error) {
244
+ throw normalizeZaiError(error);
245
+ }
246
+ }
247
+ /**
248
+ * Map a Provider failure into a normalized error. Numeric codes, raw
249
+ * response bodies, and UTCP stack data are discarded (NFR-006).
250
+ *
251
+ * Typed transport errors thrown by the lower-level client (AuthError,
252
+ * ApiError, NetworkError, TimeoutError) are re-wrapped with sanitized
253
+ * messages so a raw Provider response body embedded upstream never
254
+ * survives to public output. The `statusCode` is preserved so the
255
+ * shared execution layer can classify retryability (DESIGN.md §10).
256
+ * Our own `ValidationError` (from Capability `validate()`) carries a
257
+ * clean, human-authored message and is returned verbatim.
258
+ */
259
+ function normalizeZaiError(error) {
260
+ // Validation messages are authored by Scoutline and are safe to surface.
261
+ if (error instanceof ValidationError) {
262
+ return error;
263
+ }
264
+ // Re-wrap typed transport errors with clean messages. The raw message
265
+ // is discarded; only code + statusCode (retry signal) survive.
266
+ if (error instanceof AuthError) {
267
+ return new AuthError("Z.AI authentication failed");
268
+ }
269
+ if (error instanceof NetworkError) {
270
+ return new NetworkError("Z.AI network error");
271
+ }
272
+ if (error instanceof TimeoutError) {
273
+ // Fixup D: preserve the original error's duration instead of
274
+ // reconstructing it from an ambient process.env that may differ from
275
+ // the injected env. The rewrapped error carries the same duration.
276
+ return new TimeoutError(error.durationMs);
277
+ }
278
+ if (error instanceof ApiError) {
279
+ const statusCode = inferStatusCode("", error.statusCode);
280
+ return new ApiError("Z.AI request failed", statusCode);
281
+ }
282
+ const message = error instanceof Error ? error.message : String(error);
283
+ const lower = message.toLowerCase();
284
+ if (lower.includes("401") ||
285
+ lower.includes("403") ||
286
+ lower.includes("unauthorized") ||
287
+ lower.includes("forbidden")) {
288
+ return new AuthError("Z.AI authentication failed");
289
+ }
290
+ if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("etimedout")) {
291
+ return new TimeoutError(parseInt(process.env.Z_AI_TIMEOUT || "30000", 10));
292
+ }
293
+ if (lower.includes("econnrefused") ||
294
+ lower.includes("econnreset") ||
295
+ lower.includes("network") ||
296
+ lower.includes("enotfound") ||
297
+ lower.includes("fetch failed")) {
298
+ return new NetworkError("Z.AI network error");
299
+ }
300
+ // Default: API_ERROR with no stack or raw body.
301
+ return new ApiError("Z.AI search request failed", inferStatusCode(lower));
302
+ }
303
+ /**
304
+ * Resolve a stable HTTP-style status code for retry classification.
305
+ * Explicit client errors (404, 400, 410, 422) map to their real codes
306
+ * so the execution layer treats them as terminal (DESIGN.md §10).
307
+ * Transient failures (429 and any 5xx 500..599 inclusive) map to a
308
+ * representative status in that range so the shared execution retry
309
+ * classifier (DESIGN.md §18 / FR-090) treats them as retryable. An
310
+ * unknown failure defaults to 500 (transient), preserving shipped
311
+ * behaviour for genuine "unexpected system" conditions.
312
+ *
313
+ * When the caller already carries a numeric status (a typed ApiError),
314
+ * that status is honoured directly so retryability is never lost.
315
+ */
316
+ function inferStatusCode(lower, known) {
317
+ if (typeof known === "number" && Number.isFinite(known))
318
+ return known;
319
+ if (lower.includes("404") || lower.includes("not found"))
320
+ return 404;
321
+ if (lower.includes("400") || lower.includes("bad request"))
322
+ return 400;
323
+ if (lower.includes("410") || lower.includes("gone"))
324
+ return 410;
325
+ if (lower.includes("422") || lower.includes("unprocessable"))
326
+ return 422;
327
+ if (lower.includes("429") || lower.includes("rate limit"))
328
+ return 429;
329
+ if (lower.includes("500") || lower.includes("internal"))
330
+ return 500;
331
+ if (lower.includes("502") || lower.includes("bad gateway"))
332
+ return 502;
333
+ if (lower.includes("503") || lower.includes("service unavailable"))
334
+ return 503;
335
+ if (lower.includes("504") || lower.includes("gateway timeout"))
336
+ return 504;
337
+ return 500;
338
+ }
339
+ /**
340
+ * Map Provider response fields to normalized `SearchSource[]`. Unknown
341
+ * fields are discarded.
342
+ */
343
+ function normalizeZaiSearchResults(raw) {
344
+ const out = [];
345
+ for (const entry of raw) {
346
+ if (!entry || typeof entry !== "object")
347
+ continue;
348
+ const title = typeof entry.title === "string" ? entry.title : "";
349
+ const link = typeof entry.link === "string" ? entry.link : "";
350
+ const content = typeof entry.content === "string" ? entry.content : "";
351
+ const source = { title, url: link, summary: content };
352
+ if (typeof entry.media === "string" && entry.media.length > 0) {
353
+ source.source = entry.media;
354
+ }
355
+ if (typeof entry.publish_date === "string" && entry.publish_date.length > 0) {
356
+ source.date = entry.publish_date;
357
+ }
358
+ out.push(source);
359
+ }
360
+ return out;
361
+ }
362
+ /**
363
+ * Build the Z.AI Vision Capability. Maps `interpret-image` to the
364
+ * `vision.analyze_image` MCP operation through the same raw-tool path as
365
+ * Search. The validated image source maps to `image_source`; the
366
+ * instruction maps to `prompt`. Only a nonempty text result is normalized;
367
+ * Provider envelopes and error bodies stay inside the Adapter.
368
+ *
369
+ * Vision never uses the response cache (FR-022) and never retries inside
370
+ * the Adapter transport; shared execution owns the retry policy.
371
+ */
372
+ function createZaiVisionCapability(options) {
373
+ const { env, clientFactory } = options;
374
+ // Shared credential resolver (Fixup A — B4/B7).
375
+ function resolveApiKey() {
376
+ return requireZaiApiKey(env);
377
+ }
378
+ const capability = {
379
+ supports(operation) {
380
+ return ZAI_VISION_OPERATIONS.has(operation);
381
+ },
382
+ async invoke(request) {
383
+ // Credential resolved for the transport; media resolved inside
384
+ // `buildZaiVisionInvocation` to the validated Z.AI source (absolute
385
+ // path or HTTP(S) URL) — the media module never reads file content.
386
+ // Unsupported operations never reach here: the descriptor-level
387
+ // gate and `supports()` reject first (defence in depth).
388
+ resolveApiKey();
389
+ const { toolName, args } = buildZaiVisionInvocation(request);
390
+ // Disable client-owned cache and retry so shared execution is the
391
+ // single policy owner. Vision enables the Z.AI vision MCP server.
392
+ const clientOptions = {
393
+ enableVision: true,
394
+ noCache: true,
395
+ disableRetry: true,
396
+ };
397
+ const client = clientFactory(clientOptions);
398
+ try {
399
+ const raw = await invokeZaiVision(client, toolName, args);
400
+ return normalizeZaiVisionResult(raw);
401
+ }
402
+ finally {
403
+ await client.close().catch(() => { });
404
+ }
405
+ },
406
+ };
407
+ return capability;
408
+ }
409
+ /**
410
+ * Map a discriminated `VisionRequest` to its dedicated Z.AI MCP tool name
411
+ * and arguments, resolving media through the Z.AI media Module. Field
412
+ * names mirror the characterized transport schema (see `mcp-client.ts`
413
+ * and the live discovery fixtures). Optional fields are omitted when
414
+ * absent so the Provider receives the same request shape Phase 1 sent.
415
+ */
416
+ function buildZaiVisionInvocation(request) {
417
+ switch (request.operation) {
418
+ case "interpret-image":
419
+ return {
420
+ toolName: VISION_ANALYZE_TOOL_PUBLIC_NAME,
421
+ args: {
422
+ image_source: resolveImageSource(request.source),
423
+ prompt: request.instruction,
424
+ },
425
+ };
426
+ case "ui-artifact":
427
+ return {
428
+ toolName: VISION_UI_TO_ARTIFACT_TOOL_PUBLIC_NAME,
429
+ args: {
430
+ image_source: resolveImageSource(request.source),
431
+ output_type: request.outputType,
432
+ prompt: request.instruction,
433
+ },
434
+ };
435
+ case "extract-text": {
436
+ const args = {
437
+ image_source: resolveImageSource(request.source),
438
+ prompt: request.instruction,
439
+ };
440
+ if (request.programmingLanguage) {
441
+ args.programming_language = request.programmingLanguage;
442
+ }
443
+ return { toolName: VISION_EXTRACT_TEXT_TOOL_PUBLIC_NAME, args };
444
+ }
445
+ case "diagnose-error": {
446
+ const args = {
447
+ image_source: resolveImageSource(request.source),
448
+ prompt: request.instruction,
449
+ };
450
+ if (request.context) {
451
+ args.context = request.context;
452
+ }
453
+ return { toolName: VISION_DIAGNOSE_ERROR_TOOL_PUBLIC_NAME, args };
454
+ }
455
+ case "diagram": {
456
+ const args = {
457
+ image_source: resolveImageSource(request.source),
458
+ prompt: request.instruction,
459
+ };
460
+ if (request.diagramType) {
461
+ args.diagram_type = request.diagramType;
462
+ }
463
+ return { toolName: VISION_DIAGRAM_TOOL_PUBLIC_NAME, args };
464
+ }
465
+ case "chart": {
466
+ const args = {
467
+ image_source: resolveImageSource(request.source),
468
+ prompt: request.instruction,
469
+ };
470
+ if (request.focus) {
471
+ args.analysis_focus = request.focus;
472
+ }
473
+ return { toolName: VISION_CHART_TOOL_PUBLIC_NAME, args };
474
+ }
475
+ case "diff":
476
+ return {
477
+ toolName: VISION_DIFF_TOOL_PUBLIC_NAME,
478
+ args: {
479
+ expected_image_source: resolveImageSource(request.expectedSource),
480
+ actual_image_source: resolveImageSource(request.actualSource),
481
+ prompt: request.instruction,
482
+ },
483
+ };
484
+ case "video":
485
+ return {
486
+ toolName: VISION_VIDEO_TOOL_PUBLIC_NAME,
487
+ args: {
488
+ video_source: resolveVideoSource(request.source),
489
+ prompt: request.instruction,
490
+ },
491
+ };
492
+ }
493
+ }
494
+ async function invokeZaiVision(client, toolName, args) {
495
+ try {
496
+ return await client.callToolRaw(toolName, args);
497
+ }
498
+ catch (error) {
499
+ throw normalizeZaiError(error);
500
+ }
501
+ }
502
+ /**
503
+ * Normalize the Z.AI vision result to a nonempty text string. The
504
+ * `vision.analyze_image` MCP operation returns direct text; an empty,
505
+ * whitespace-only, or non-string value is a malformed result.
506
+ */
507
+ function normalizeZaiVisionResult(raw) {
508
+ if (typeof raw === "string" && raw.trim().length > 0) {
509
+ return raw;
510
+ }
511
+ throw new ApiError("Z.AI vision returned an empty or malformed result", 500);
512
+ }
513
+ /**
514
+ * Build the Z.AI DiagnosticsCapability. Z.AI connectivity is probed
515
+ * through tool discovery: a UTCP client is constructed and `listTools`
516
+ * is called once. The probe authenticates and verifies the MCP
517
+ * transport without a generative request. Shared execution wraps this
518
+ * in the retry policy; the Adapter transport performs one attempt.
519
+ */
520
+ function createZaiDiagnosticsCapability(options) {
521
+ const { env, clientFactory } = options;
522
+ return {
523
+ async invoke(diagOptions) {
524
+ if (!diagOptions.probe)
525
+ return;
526
+ // Shared credential resolver (Fixup A — B4/B7): missing key is a
527
+ // configuration failure (ConfigurationError, exit 3).
528
+ requireZaiApiKey(env);
529
+ // Disable client-owned cache and retry so shared execution is the
530
+ // single policy owner. Diagnostics needs no vision MCP server.
531
+ const clientOptions = {
532
+ enableVision: false,
533
+ noCache: true,
534
+ disableRetry: true,
535
+ };
536
+ const client = clientFactory(clientOptions);
537
+ try {
538
+ await client.listTools();
539
+ }
540
+ catch (error) {
541
+ throw normalizeZaiError(error);
542
+ }
543
+ finally {
544
+ await client.close().catch(() => { });
545
+ }
546
+ },
547
+ };
548
+ }
549
+ // ---------------------------------------------------------------------------
550
+ // Descriptor factory
551
+ // ---------------------------------------------------------------------------
552
+ /**
553
+ * Default Z.AI client factory bound to {@link ZaiMcpClient}. Tests inject
554
+ * a fake factory through {@link createZaiDescriptor}'s dependencies.
555
+ */
556
+ function defaultZaiClientFactory(options) {
557
+ // The Provider-types `ZaiMcpClientOptions` declares a narrower
558
+ // `utcpFactory` return type (`UtcpClientPort`) than the
559
+ // `ZaiMcpClient` constructor's local interface (`UtcpClient`). The
560
+ // runtime object is structurally compatible — the narrower port is a
561
+ // behavioural subset the client consumes via duck-typing — so we cast
562
+ // at the boundary instead of leaking the SDK type outward.
563
+ const client = new ZaiMcpClient(options);
564
+ // Adapt the rich ZaiMcpClient surface to the narrow
565
+ // ZaiAdapterClientPort the Z.AI Search Adapter needs.
566
+ return {
567
+ callToolRaw(name, args) {
568
+ return client.callToolRaw(name, args);
569
+ },
570
+ listTools() {
571
+ return client.listTools();
572
+ },
573
+ close() {
574
+ return client.close();
575
+ },
576
+ };
577
+ }
578
+ /**
579
+ * Build the Z.AI Provider Descriptor. The descriptor advertises the
580
+ * Search Capability and constructs an Adapter whose `search` Capability
581
+ * owns credentials, transport lifecycle, Provider field mapping, and
582
+ * failure normalization. Construction is side-effect-free; transport is
583
+ * built and torn down per invocation.
584
+ */
585
+ export function createZaiDescriptor(dependencies) {
586
+ const clientFactory = dependencies?.clientFactory ?? defaultZaiClientFactory;
587
+ // Quota-monitor transport injection (tests). Production uses the
588
+ // global fetch and timers resolved inside the monitor client.
589
+ const quotaTransport = {};
590
+ if (dependencies?.quotaFetch) {
591
+ quotaTransport.fetch = dependencies.quotaFetch;
592
+ }
593
+ if (dependencies?.quotaSetTimeout) {
594
+ quotaTransport.setTimeout = dependencies.quotaSetTimeout;
595
+ }
596
+ if (dependencies?.quotaClearTimeout) {
597
+ quotaTransport.clearTimeout = dependencies.quotaClearTimeout;
598
+ }
599
+ return {
600
+ id: "zai",
601
+ isConfigured(env) {
602
+ // Shared resolver honours the ZAI_API_KEY alias (Fixup A — B4).
603
+ return isZaiConfigured(env);
604
+ },
605
+ capabilities() {
606
+ // P6-06: advertise `repository-exploration`. The Adapter has
607
+ // supplied `adapter.repository` since P6-04; descriptor
608
+ // metadata now mirrors that fact so Provider selection and
609
+ // Doctor inventory derive from a single source of truth.
610
+ // MiniMax stays free of repository-exploration until it ships
611
+ // its own Adapter and conformance fixtures.
612
+ //
613
+ // Reader Migration Ticket 04: advertise `reader`. The Adapter
614
+ // has supplied `adapter.reader` since Ticket 03; descriptor
615
+ // metadata now mirrors that fact so Provider selection and
616
+ // Doctor inventory derive from a single source of truth.
617
+ // MiniMax stays free of reader until it ships its own Adapter
618
+ // and conformance fixtures.
619
+ return new Set([
620
+ "search",
621
+ "vision.interpret-image",
622
+ "vision.ui-artifact",
623
+ "vision.extract-text",
624
+ "vision.diagnose-error",
625
+ "vision.diagram",
626
+ "vision.chart",
627
+ "vision.diff",
628
+ "vision.video",
629
+ "quota",
630
+ "diagnostics",
631
+ "repository-exploration",
632
+ "reader",
633
+ ]);
634
+ },
635
+ create(context) {
636
+ const search = createZaiSearchCapability({
637
+ env: context.env,
638
+ clientFactory,
639
+ });
640
+ const vision = createZaiVisionCapability({
641
+ env: context.env,
642
+ clientFactory,
643
+ });
644
+ const quotaOptions = { env: context.env, ...quotaTransport };
645
+ const quota = createZaiQuotaCapability(quotaOptions);
646
+ const diagnostics = createZaiDiagnosticsCapability({
647
+ env: context.env,
648
+ clientFactory,
649
+ });
650
+ // P6-04: wire the Repository Capability so tests and the future
651
+ // Explorer layer (P6-05+) can reach the implementation through
652
+ // `adapter.repository`. P6-06 advertises
653
+ // `repository-exploration` in `capabilities()` so Provider
654
+ // selection and Doctor inventory derive from a single source of
655
+ // truth. P6-04A forwards the optional `repositoryCloseTimeoutMs`
656
+ // test seam; production leaves it undefined and the capability
657
+ // uses the documented 2000 ms default.
658
+ const repository = createZaiRepositoryCapability({
659
+ env: context.env,
660
+ clientFactory,
661
+ ...(dependencies?.repositoryCloseTimeoutMs !== undefined && {
662
+ closeTimeoutMs: dependencies.repositoryCloseTimeoutMs,
663
+ }),
664
+ });
665
+ // Reader Migration Ticket 03: wire the Reader Capability so tests
666
+ // and the Ticket 04 handler cutover can reach the implementation
667
+ // through `adapter.reader.fetch`. Ticket 04 advertised `reader`
668
+ // in `capabilities()` so Provider selection and Doctor inventory
669
+ // derive from a single source of truth, and cut
670
+ // `commands/read.ts` over to dispatch through this handle.
671
+ // The optional `readerCloseTimeoutMs` test seam mirrors the
672
+ // repository seam; production leaves it undefined and the
673
+ // capability uses the documented 2000 ms default.
674
+ const reader = createZaiReaderCapability({
675
+ env: context.env,
676
+ clientFactory,
677
+ ...(dependencies?.readerCloseTimeoutMs !== undefined && {
678
+ closeTimeoutMs: dependencies.readerCloseTimeoutMs,
679
+ }),
680
+ });
681
+ return { id: "zai", search, vision, quota, diagnostics, repository, reader };
682
+ },
683
+ };
684
+ }
685
+ //# sourceMappingURL=adapter.js.map