scoutline 0.2.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.
- package/README.md +240 -18
- package/dist/capabilities/diagnostics.d.ts +70 -32
- package/dist/capabilities/diagnostics.d.ts.map +1 -1
- package/dist/capabilities/diagnostics.js +97 -46
- package/dist/capabilities/diagnostics.js.map +1 -1
- package/dist/capabilities/reader.d.ts +227 -0
- package/dist/capabilities/reader.d.ts.map +1 -0
- package/dist/capabilities/reader.js +100 -0
- package/dist/capabilities/reader.js.map +1 -0
- package/dist/capabilities/repository.d.ts +221 -0
- package/dist/capabilities/repository.d.ts.map +1 -0
- package/dist/capabilities/repository.js +172 -0
- package/dist/capabilities/repository.js.map +1 -0
- package/dist/commands/cache.d.ts +106 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +203 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/doctor.d.ts +17 -6
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +42 -17
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/read.d.ts +74 -14
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +257 -117
- package/dist/commands/read.js.map +1 -1
- package/dist/commands/repo.d.ts +53 -7
- package/dist/commands/repo.d.ts.map +1 -1
- package/dist/commands/repo.js +104 -123
- package/dist/commands/repo.js.map +1 -1
- package/dist/commands/repository-explorer.d.ts +147 -0
- package/dist/commands/repository-explorer.d.ts.map +1 -0
- package/dist/commands/repository-explorer.js +550 -0
- package/dist/commands/repository-explorer.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +209 -34
- package/dist/index.js.map +1 -1
- package/dist/lib/cache.d.ts +123 -18
- package/dist/lib/cache.d.ts.map +1 -1
- package/dist/lib/cache.js +324 -49
- package/dist/lib/cache.js.map +1 -1
- package/dist/lib/errors.d.ts +24 -1
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +33 -2
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/execution.d.ts +119 -5
- package/dist/lib/execution.d.ts.map +1 -1
- package/dist/lib/execution.js +216 -10
- package/dist/lib/execution.js.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/mcp-client.d.ts +29 -5
- package/dist/lib/mcp-client.d.ts.map +1 -1
- package/dist/lib/mcp-client.js +88 -88
- package/dist/lib/mcp-client.js.map +1 -1
- package/dist/lib/tool-cache.d.ts +86 -0
- package/dist/lib/tool-cache.d.ts.map +1 -0
- package/dist/lib/tool-cache.js +123 -0
- package/dist/lib/tool-cache.js.map +1 -0
- package/dist/providers/minimax/adapter.d.ts +6 -4
- package/dist/providers/minimax/adapter.d.ts.map +1 -1
- package/dist/providers/minimax/adapter.js +64 -57
- package/dist/providers/minimax/adapter.js.map +1 -1
- package/dist/providers/minimax/coding-plan-client.d.ts +60 -0
- package/dist/providers/minimax/coding-plan-client.d.ts.map +1 -0
- package/dist/providers/minimax/coding-plan-client.js +204 -0
- package/dist/providers/minimax/coding-plan-client.js.map +1 -0
- package/dist/providers/minimax/media.d.ts +60 -6
- package/dist/providers/minimax/media.d.ts.map +1 -1
- package/dist/providers/minimax/media.js +147 -7
- package/dist/providers/minimax/media.js.map +1 -1
- package/dist/providers/minimax/quota-client.d.ts +13 -6
- package/dist/providers/minimax/quota-client.d.ts.map +1 -1
- package/dist/providers/minimax/quota-client.js +5 -0
- package/dist/providers/minimax/quota-client.js.map +1 -1
- package/dist/providers/minimax/vision-attestations.d.ts +23 -0
- package/dist/providers/minimax/vision-attestations.d.ts.map +1 -1
- package/dist/providers/minimax/vision-attestations.js +35 -10
- package/dist/providers/minimax/vision-attestations.js.map +1 -1
- package/dist/providers/minimax/vision-conformance.d.ts +8 -6
- package/dist/providers/minimax/vision-conformance.d.ts.map +1 -1
- package/dist/providers/minimax/vision-conformance.js +8 -6
- package/dist/providers/minimax/vision-conformance.js.map +1 -1
- package/dist/providers/minimax/vision-revisions.d.ts +8 -1
- package/dist/providers/minimax/vision-revisions.d.ts.map +1 -1
- package/dist/providers/minimax/vision-revisions.js +13 -6
- package/dist/providers/minimax/vision-revisions.js.map +1 -1
- package/dist/providers/selection.d.ts +3 -3
- package/dist/providers/selection.js +3 -3
- package/dist/providers/types.d.ts +66 -32
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/zai/adapter.d.ts.map +1 -1
- package/dist/providers/zai/adapter.js +71 -5
- package/dist/providers/zai/adapter.js.map +1 -1
- package/dist/providers/zai/encoded-error.d.ts +90 -0
- package/dist/providers/zai/encoded-error.d.ts.map +1 -0
- package/dist/providers/zai/encoded-error.js +169 -0
- package/dist/providers/zai/encoded-error.js.map +1 -0
- package/dist/providers/zai/reader.d.ts +82 -0
- package/dist/providers/zai/reader.d.ts.map +1 -0
- package/dist/providers/zai/reader.js +490 -0
- package/dist/providers/zai/reader.js.map +1 -0
- package/dist/providers/zai/repository.d.ts +76 -0
- package/dist/providers/zai/repository.d.ts.map +1 -0
- package/dist/providers/zai/repository.js +715 -0
- package/dist/providers/zai/repository.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z.AI Repository Adapter (DESIGN.md §18, PRD FR-081, FR-087,
|
|
3
|
+
* FR-089–FR-091, FR-093; NFR-004, NFR-006).
|
|
4
|
+
*
|
|
5
|
+
* Owns the Provider-facing half of the provider-neutral Repository
|
|
6
|
+
* Capability defined in `src/capabilities/repository.ts`:
|
|
7
|
+
*
|
|
8
|
+
* - total parsers for the characterized ZRead Search, File, and
|
|
9
|
+
* Directory Listing responses (`<excerpt>`, `<file_content>`,
|
|
10
|
+
* `<structure>`);
|
|
11
|
+
* - per-operation Z.AI descriptors with `validate`, `cacheIdentity`,
|
|
12
|
+
* `decodeCached`, and `invoke`;
|
|
13
|
+
* - a single resolved-credential fingerprint per cache identity and
|
|
14
|
+
* exact per-operation legacy cache candidates/decoders;
|
|
15
|
+
* - encoded MCP error classification BEFORE success parsing
|
|
16
|
+
* (exhausted quota is terminal `QUOTA_ERROR`; the rest of the
|
|
17
|
+
* taxonomy uses the shared retry/terminal classification);
|
|
18
|
+
* - a fresh transport per invocation attempt and exactly one
|
|
19
|
+
* best-effort close in `finally`; close failure never replaces
|
|
20
|
+
* success nor masks the primary failure;
|
|
21
|
+
* - no leakage of raw ZRead response types outside this module.
|
|
22
|
+
*
|
|
23
|
+
* Boundary rules (ARCHITECTURE.md §2):
|
|
24
|
+
* - May import capability types, normalized errors, the Z.AI MCP
|
|
25
|
+
* tool-name helpers, the legacy cache-key helper, and the
|
|
26
|
+
* shared `ZaiAdapterClientPort` typed client port.
|
|
27
|
+
* - Must NOT import another Provider's Adapter, command
|
|
28
|
+
* presentation, BFS/Explorer logic, or path canonicalization.
|
|
29
|
+
*
|
|
30
|
+
* Scope:
|
|
31
|
+
* - implements Search, File, and Directory Listing only. Tree
|
|
32
|
+
* projection, BFS, depth/path policy, and `--max-chars` all
|
|
33
|
+
* belong to the Explorer layer (P6-05+) or the command layer.
|
|
34
|
+
* - descriptor metadata sequencing: P6-04 introduced this Adapter
|
|
35
|
+
* handle and wired it through `ProviderAdapter.repository`
|
|
36
|
+
* WITHOUT advertising `repository-exploration` on
|
|
37
|
+
* `createZaiDescriptor.capabilities()`; P6-06 then flipped the
|
|
38
|
+
* descriptor to advertise `repository-exploration` so Provider
|
|
39
|
+
* selection and Doctor inventory derive from a single source of
|
|
40
|
+
* truth. This module itself still owns no registry, selection,
|
|
41
|
+
* or command cutover — `commands/repo.ts` continues to dispatch
|
|
42
|
+
* through the legacy `ZReadMcpClient` until P6-07 exclusively
|
|
43
|
+
* owns that dispatch cutover.
|
|
44
|
+
*/
|
|
45
|
+
import crypto from "node:crypto";
|
|
46
|
+
import { ApiError, ScoutlineError, ValidationError } from "../../lib/errors.js";
|
|
47
|
+
import { getMcpToolName } from "../../lib/mcp-config.js";
|
|
48
|
+
import { buildLegacyRepositoryCacheKey } from "../../lib/cache.js";
|
|
49
|
+
import { requireZaiApiKey } from "./credentials.js";
|
|
50
|
+
import { classifyEncodedMcpError, looksLikeEncodedMcpError } from "./encoded-error.js";
|
|
51
|
+
import { decodeRepositoryDirectoryListing, decodeRepositoryFile, decodeRepositorySearch, } from "../../capabilities/repository.js";
|
|
52
|
+
/**
|
|
53
|
+
* Production close bound (ms). Matches the existing
|
|
54
|
+
* `ZaiMcpClient.close(timeoutMs = 2000)` semantic; the Adapter races
|
|
55
|
+
* the close against a 2 second timer that resolves silently so a stuck
|
|
56
|
+
* close cannot stall the attempt. Tests may inject a shorter bound via
|
|
57
|
+
* {@link ZaiRepositoryCapabilityOptions.closeTimeoutMs}.
|
|
58
|
+
*/
|
|
59
|
+
export const ZAI_REPOSITORY_CLOSE_BOUND_MS = 2000;
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Public dotted MCP tool names — the Adapter invokes through these so the
|
|
62
|
+
// `ZaiMcpClient.callToolRaw` path resolves the discovered internal identity
|
|
63
|
+
// on a miss, exactly as it does for Search/Vision/Reader.
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
const SEARCH_TOOL_PUBLIC_NAME = getMcpToolName("zread", "search_doc");
|
|
66
|
+
const FILE_TOOL_PUBLIC_NAME = getMcpToolName("zread", "read_file");
|
|
67
|
+
const DIRECTORY_TOOL_PUBLIC_NAME = getMcpToolName("zread", "get_repo_structure");
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Operation label passed to the shared encoded-error classifier. The label
|
|
70
|
+
// is the only operation-specific input — it becomes part of the sanitized
|
|
71
|
+
// outward ApiError / QuotaError message ("Z.AI repository request failed",
|
|
72
|
+
// "Z.AI repository quota has been exhausted"). Auth messages (401, 403) do
|
|
73
|
+
// not carry the label.
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
const ENCODED_ERROR_LABEL = "repository";
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Encoded MCP error envelope (DESIGN.md §18, ZRead response
|
|
78
|
+
// characterization). The Adapter recognises the string BEFORE parsing
|
|
79
|
+
// any success grammar. Raw Provider body, message, and help fields are
|
|
80
|
+
// discarded; outward messages are sanitized.
|
|
81
|
+
//
|
|
82
|
+
// The classification logic (status extraction, code extraction, phrase
|
|
83
|
+
// matching, status mapping) is centralized in
|
|
84
|
+
// `src/providers/zai/encoded-error.ts` so the Reader Adapter (Ticket 03)
|
|
85
|
+
// can reuse it without duplication. Each Adapter supplies an operation
|
|
86
|
+
// label that becomes part of the sanitized outward message.
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Total parsers for characterized ZRead responses (DESIGN.md §18, ZRead
|
|
90
|
+
// response characterization). Each parser is total over its expected
|
|
91
|
+
// shape: malformed responses throw a sanitized retryable ApiError 502.
|
|
92
|
+
//
|
|
93
|
+
// The parsers also accept a `request` so the normalized result can carry
|
|
94
|
+
// the validated request fields (`repository`, `query`, `language`, `path`).
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/**
|
|
97
|
+
* Parse a ZRead Search response into a normalized
|
|
98
|
+
* `RepositorySearchResult`. The grammar is balanced `<excerpt>` framing
|
|
99
|
+
* with at least one well-formed top-level block; arbitrary inner
|
|
100
|
+
* Markdown, code, and HTML-like markup is preserved verbatim inside
|
|
101
|
+
* each excerpt text.
|
|
102
|
+
*
|
|
103
|
+
* Malformed responses (no wrapper, unbalanced tags, non-string return)
|
|
104
|
+
* throw `ApiError` with status 502 — the outward retryable envelope.
|
|
105
|
+
*
|
|
106
|
+
* Framing hardening (P6-04A): equal tag counts cannot admit nested,
|
|
107
|
+
* reversed, or stray `<excerpt>` framing. The parser walks tokens in
|
|
108
|
+
* source order with depth tracking so it can reject:
|
|
109
|
+
* - nested openings (`<excerpt>...<excerpt>...</excerpt>...</excerpt>`);
|
|
110
|
+
* - reversed/reversed openings (`</excerpt>...<excerpt>...</excerpt>`);
|
|
111
|
+
* - stray closings (`<excerpt>...</excerpt></excerpt>`).
|
|
112
|
+
* Inner non-framing text between an open and its matching close is
|
|
113
|
+
* preserved verbatim; Provider order is preserved.
|
|
114
|
+
*/
|
|
115
|
+
function parseZaiSearch(raw, request) {
|
|
116
|
+
if (typeof raw !== "string") {
|
|
117
|
+
throw new ApiError("Z.AI search returned a malformed response", 502);
|
|
118
|
+
}
|
|
119
|
+
if (looksLikeEncodedMcpError(raw)) {
|
|
120
|
+
throw classifyEncodedMcpError(raw, ENCODED_ERROR_LABEL);
|
|
121
|
+
}
|
|
122
|
+
const openTag = "<excerpt>";
|
|
123
|
+
const closeTag = "</excerpt>";
|
|
124
|
+
const excerpts = [];
|
|
125
|
+
let depth = 0;
|
|
126
|
+
let pos = 0;
|
|
127
|
+
let sawOpening = false;
|
|
128
|
+
let originalTextLength = 0;
|
|
129
|
+
while (pos < raw.length) {
|
|
130
|
+
const nextOpen = raw.indexOf(openTag, pos);
|
|
131
|
+
const nextClose = raw.indexOf(closeTag, pos);
|
|
132
|
+
if (nextOpen === -1 && nextClose === -1)
|
|
133
|
+
break;
|
|
134
|
+
if (nextOpen !== -1 && (nextClose === -1 || nextOpen < nextClose)) {
|
|
135
|
+
// Next token is an opening tag. Reject if depth != 0 (nested or
|
|
136
|
+
// reversed framing).
|
|
137
|
+
if (depth !== 0) {
|
|
138
|
+
throw new ApiError("Z.AI search returned a malformed response", 502);
|
|
139
|
+
}
|
|
140
|
+
depth = 1;
|
|
141
|
+
sawOpening = true;
|
|
142
|
+
pos = nextOpen + openTag.length;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Next token is a closing tag. Reject if depth != 1 (stray
|
|
146
|
+
// closing before any open, or after a previous close).
|
|
147
|
+
if (depth !== 1) {
|
|
148
|
+
throw new ApiError("Z.AI search returned a malformed response", 502);
|
|
149
|
+
}
|
|
150
|
+
const innerText = raw.slice(pos, nextClose);
|
|
151
|
+
excerpts.push({ text: innerText });
|
|
152
|
+
originalTextLength += innerText.length;
|
|
153
|
+
depth = 0;
|
|
154
|
+
pos = nextClose + closeTag.length;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (depth !== 0 || !sawOpening) {
|
|
158
|
+
throw new ApiError("Z.AI search returned a malformed response", 502);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
schemaVersion: 1,
|
|
162
|
+
repository: request.repository,
|
|
163
|
+
query: request.query,
|
|
164
|
+
language: request.language,
|
|
165
|
+
excerpts,
|
|
166
|
+
truncated: false,
|
|
167
|
+
originalTextLength,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Count non-overlapping occurrences of a literal substring. Used by
|
|
172
|
+
* the File and Directory parsers to enforce exactly one outer
|
|
173
|
+
* Provider wrapper (P6-04B): duplicate or nested `<file_content>` /
|
|
174
|
+
* `<structure>` framing is malformed, not content/entry data.
|
|
175
|
+
*/
|
|
176
|
+
function countOccurrences(haystack, needle) {
|
|
177
|
+
let count = 0;
|
|
178
|
+
let from = 0;
|
|
179
|
+
while (true) {
|
|
180
|
+
const idx = haystack.indexOf(needle, from);
|
|
181
|
+
if (idx === -1)
|
|
182
|
+
return count;
|
|
183
|
+
count += 1;
|
|
184
|
+
from = idx + needle.length;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Parse a ZRead File response into a normalized `RepositoryFileResult`.
|
|
189
|
+
* The grammar is a single whole-response `<file_content>...</file_content>`
|
|
190
|
+
* wrapper; only characterized whitespace may appear outside the wrapper.
|
|
191
|
+
* Exactly one outer opening and one outer closing wrapper tag are
|
|
192
|
+
* required (P6-04B): duplicate or nested framing is malformed.
|
|
193
|
+
*/
|
|
194
|
+
function parseZaiFile(raw, request) {
|
|
195
|
+
if (typeof raw !== "string") {
|
|
196
|
+
throw new ApiError("Z.AI file returned a malformed response", 502);
|
|
197
|
+
}
|
|
198
|
+
if (looksLikeEncodedMcpError(raw)) {
|
|
199
|
+
throw classifyEncodedMcpError(raw, ENCODED_ERROR_LABEL);
|
|
200
|
+
}
|
|
201
|
+
// Enforce exactly one outer wrapper pair (P6-04B). Duplicate or
|
|
202
|
+
// nested `<file_content>` framing is malformed, not content data.
|
|
203
|
+
if (countOccurrences(raw, "<file_content>") !== 1 ||
|
|
204
|
+
countOccurrences(raw, "</file_content>") !== 1) {
|
|
205
|
+
throw new ApiError("Z.AI file returned a malformed response", 502);
|
|
206
|
+
}
|
|
207
|
+
const m = raw.match(/^\s*<file_content>([\s\S]*)<\/file_content>\s*$/);
|
|
208
|
+
if (!m) {
|
|
209
|
+
throw new ApiError("Z.AI file returned a malformed response", 502);
|
|
210
|
+
}
|
|
211
|
+
const content = m[1];
|
|
212
|
+
return {
|
|
213
|
+
schemaVersion: 1,
|
|
214
|
+
repository: request.repository,
|
|
215
|
+
path: request.path,
|
|
216
|
+
content,
|
|
217
|
+
truncated: false,
|
|
218
|
+
originalContentLength: content.length,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Parse a ZRead Directory Listing response into a normalized
|
|
223
|
+
* `RepositoryDirectoryListing`. The grammar is a single whole-response
|
|
224
|
+
* `<structure>...</structure>` wrapper. The FIRST non-blank line inside
|
|
225
|
+
* the wrapper MUST be a non-empty glyph-less root label (typically
|
|
226
|
+
* `owner-repo/`). Every SUBSEQUENT non-blank line is either:
|
|
227
|
+
*
|
|
228
|
+
* - a level-zero immediate entry: `├── name` or `└── name` (branch
|
|
229
|
+
* glyph at column 0, no leading whitespace); or
|
|
230
|
+
* - a well-formed nested descendant: one or more exact four-column
|
|
231
|
+
* indentation groups (`│ ` pipe+3spaces or ` ` 4 spaces),
|
|
232
|
+
* then `├── ` or `└── `, then a non-empty name.
|
|
233
|
+
*
|
|
234
|
+
* Only immediate entries are emitted. Valid descendants are silently
|
|
235
|
+
* skipped (P6-04B). Glyph-bearing lines with arbitrary or misaligned
|
|
236
|
+
* prefixes (e.g. `garbage├──`, `│ ├──` with only 2 spaces),
|
|
237
|
+
* glyph-less lines, and descendants with empty names are rejected
|
|
238
|
+
* (P6-04C).
|
|
239
|
+
*
|
|
240
|
+
* A trailing `/` on an entry marks a directory. Sibling order is
|
|
241
|
+
* preserved verbatim.
|
|
242
|
+
*
|
|
243
|
+
* Child paths are repository-relative (P6-04A): for a listing at
|
|
244
|
+
* `packages`, child `react/` yields
|
|
245
|
+
* `{ name: "react", path: "packages/react", kind: "directory" }`.
|
|
246
|
+
* Root listings (request.path === "") project the child name as the
|
|
247
|
+
* entry path. The Adapter performs only this deterministic
|
|
248
|
+
* parent/child projection — canonical safety validation and rejection
|
|
249
|
+
* remain the Explorer's responsibility (P6-05).
|
|
250
|
+
*/
|
|
251
|
+
function parseZaiDirectory(raw, request) {
|
|
252
|
+
if (typeof raw !== "string") {
|
|
253
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
254
|
+
}
|
|
255
|
+
if (looksLikeEncodedMcpError(raw)) {
|
|
256
|
+
throw classifyEncodedMcpError(raw, ENCODED_ERROR_LABEL);
|
|
257
|
+
}
|
|
258
|
+
// Enforce exactly one outer wrapper pair (P6-04B). Duplicate or
|
|
259
|
+
// nested `<structure>` framing is malformed, not entry data.
|
|
260
|
+
if (countOccurrences(raw, "<structure>") !== 1 || countOccurrences(raw, "</structure>") !== 1) {
|
|
261
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
262
|
+
}
|
|
263
|
+
const wrapper = raw.match(/^\s*<structure>([\s\S]*)<\/structure>\s*$/);
|
|
264
|
+
if (!wrapper) {
|
|
265
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
266
|
+
}
|
|
267
|
+
const body = wrapper[1];
|
|
268
|
+
// Level-0 immediate entries: branch glyph at column 0 (no leading
|
|
269
|
+
// whitespace).
|
|
270
|
+
const immediateEntryRe = /^[├└]──\s(.+)$/;
|
|
271
|
+
// Well-formed nested descendant: one or more exact four-column
|
|
272
|
+
// indentation groups (`│ ` pipe+3spaces or ` ` 4 spaces),
|
|
273
|
+
// then `├── `/`└── `, then the name (P6-04C). Misaligned prefixes
|
|
274
|
+
// (`│ ├──`, ` ├──`, `garbage├──`) do not match and are rejected.
|
|
275
|
+
const descendantRe = /^(?:│ | )+[├└]──\s(.+)$/;
|
|
276
|
+
const entries = [];
|
|
277
|
+
let sawImmediate = false;
|
|
278
|
+
let isFirstNonBlank = true;
|
|
279
|
+
for (const line of body.split("\n")) {
|
|
280
|
+
if (!line.trim())
|
|
281
|
+
continue;
|
|
282
|
+
if (isFirstNonBlank) {
|
|
283
|
+
// The first non-blank line MUST be a glyph-less root label
|
|
284
|
+
// (P6-04C). A glyph-bearing first line means the root label
|
|
285
|
+
// is missing and the response is malformed.
|
|
286
|
+
isFirstNonBlank = false;
|
|
287
|
+
if (/[├└]──/.test(line)) {
|
|
288
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const m = line.match(immediateEntryRe);
|
|
293
|
+
if (m) {
|
|
294
|
+
// Level-0 immediate entry — parse and emit.
|
|
295
|
+
const name = m[1].trim();
|
|
296
|
+
if (!name) {
|
|
297
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
298
|
+
}
|
|
299
|
+
sawImmediate = true;
|
|
300
|
+
const isDir = name.endsWith("/");
|
|
301
|
+
const cleanName = isDir ? name.slice(0, -1) : name;
|
|
302
|
+
if (!cleanName) {
|
|
303
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
304
|
+
}
|
|
305
|
+
// Repository-relative child path: root listings project the
|
|
306
|
+
// name unchanged; non-root listings join `request.path` and the
|
|
307
|
+
// entry name with a single `/`. The Explorer later applies
|
|
308
|
+
// canonical safety validation.
|
|
309
|
+
const childPath = request.path === "" ? cleanName : `${request.path}/${cleanName}`;
|
|
310
|
+
entries.push({ name: cleanName, path: childPath, kind: isDir ? "directory" : "file" });
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
// Not a level-0 entry. Check whether the line is a well-formed
|
|
314
|
+
// descendant with exact four-column indentation groups (P6-04C).
|
|
315
|
+
const d = line.match(descendantRe);
|
|
316
|
+
if (d) {
|
|
317
|
+
// Valid descendant indentation — but a descendant with an empty
|
|
318
|
+
// name is still malformed. Silently skip only if the name is
|
|
319
|
+
// non-empty; otherwise reject.
|
|
320
|
+
if (d[1].trim()) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Glyph-less, arbitrary/misaligned prefix, or empty descendant
|
|
325
|
+
// name — reject.
|
|
326
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
327
|
+
}
|
|
328
|
+
if (!sawImmediate) {
|
|
329
|
+
// A wrapper with no glyph-prefixed entries is malformed (the
|
|
330
|
+
// characterization requires at least one documented immediate
|
|
331
|
+
// entry). Reject uniformly rather than returning an empty list
|
|
332
|
+
// so the Explorer never sees a silently empty normalized result.
|
|
333
|
+
throw new ApiError("Z.AI directory returned a malformed response", 502);
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
repository: request.repository,
|
|
337
|
+
path: request.path,
|
|
338
|
+
entries,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Adapter-owned credential fingerprint (DESIGN.md §18). Identical
|
|
343
|
+
// algorithm to the Search Capability: full lowercase SHA-256 hex digest
|
|
344
|
+
// of the active credential; the cache key uses this verbatim.
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
function credentialFingerprint(apiKey) {
|
|
347
|
+
return crypto.createHash("sha256").update(apiKey).digest("hex");
|
|
348
|
+
}
|
|
349
|
+
function createZaiSearchOperation(options) {
|
|
350
|
+
const { env, clientFactory } = options;
|
|
351
|
+
function resolveApiKey() {
|
|
352
|
+
return requireZaiApiKey(env);
|
|
353
|
+
}
|
|
354
|
+
const operation = {
|
|
355
|
+
kind: "repository-search",
|
|
356
|
+
validate(request) {
|
|
357
|
+
assertRepository(request.repository);
|
|
358
|
+
assertQuery(request.query);
|
|
359
|
+
assertLanguage(request.language);
|
|
360
|
+
},
|
|
361
|
+
cacheIdentity(request) {
|
|
362
|
+
const apiKey = resolveApiKey();
|
|
363
|
+
// Legacy argument insertion order is fixed (DESIGN.md §18):
|
|
364
|
+
// Search -> repo_name, query, language
|
|
365
|
+
const legacyArgs = {
|
|
366
|
+
repo_name: request.repository,
|
|
367
|
+
query: request.query,
|
|
368
|
+
language: request.language,
|
|
369
|
+
};
|
|
370
|
+
return {
|
|
371
|
+
provider: "zai",
|
|
372
|
+
capability: "repository-exploration",
|
|
373
|
+
operation: "repository-search",
|
|
374
|
+
credentialFingerprint: credentialFingerprint(apiKey),
|
|
375
|
+
request,
|
|
376
|
+
legacyCandidates: [
|
|
377
|
+
{
|
|
378
|
+
key: buildLegacyRepositoryCacheKey(apiKey, SEARCH_TOOL_PUBLIC_NAME, legacyArgs),
|
|
379
|
+
decode: (raw) => decodeLegacyZaiSearch(raw, {
|
|
380
|
+
repository: request.repository,
|
|
381
|
+
query: request.query,
|
|
382
|
+
language: request.language,
|
|
383
|
+
}),
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
decodeCached(value) {
|
|
389
|
+
// Total normalized cache decoder: delegate to the shared P6-02
|
|
390
|
+
// `decodeRepositorySearch` (DESIGN.md §18). Shared decoder
|
|
391
|
+
// rejects fractional `originalTextLength` values, missing
|
|
392
|
+
// required scalars, malformed `excerpts`, and unknown
|
|
393
|
+
// `language` values. Any malformed shape is a cache miss.
|
|
394
|
+
return decodeRepositorySearch(value);
|
|
395
|
+
},
|
|
396
|
+
async invoke(request) {
|
|
397
|
+
// Validate before any transport access (DESIGN.md §18 / §2).
|
|
398
|
+
operation.validate(request);
|
|
399
|
+
const args = {
|
|
400
|
+
repo_name: request.repository,
|
|
401
|
+
query: request.query,
|
|
402
|
+
language: request.language,
|
|
403
|
+
};
|
|
404
|
+
return invokeRepositoryOperation(clientFactory, SEARCH_TOOL_PUBLIC_NAME, args, (raw) => parseZaiSearch(raw, request), options.closeTimeoutMs);
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
return operation;
|
|
408
|
+
}
|
|
409
|
+
// ---------------------------------------------------------------------------
|
|
410
|
+
// Operation factory — File
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
function createZaiReadFileOperation(options) {
|
|
413
|
+
const { env, clientFactory } = options;
|
|
414
|
+
function resolveApiKey() {
|
|
415
|
+
return requireZaiApiKey(env);
|
|
416
|
+
}
|
|
417
|
+
const operation = {
|
|
418
|
+
kind: "repository-read-file",
|
|
419
|
+
validate(request) {
|
|
420
|
+
assertRepository(request.repository);
|
|
421
|
+
assertNonRootPath(request.path, "File");
|
|
422
|
+
},
|
|
423
|
+
cacheIdentity(request) {
|
|
424
|
+
const apiKey = resolveApiKey();
|
|
425
|
+
// File uses a fixed insertion order (DESIGN.md §18):
|
|
426
|
+
// File -> repo_name, file_path
|
|
427
|
+
const legacyArgs = {
|
|
428
|
+
repo_name: request.repository,
|
|
429
|
+
file_path: request.path,
|
|
430
|
+
};
|
|
431
|
+
return {
|
|
432
|
+
provider: "zai",
|
|
433
|
+
capability: "repository-exploration",
|
|
434
|
+
operation: "repository-read-file",
|
|
435
|
+
credentialFingerprint: credentialFingerprint(apiKey),
|
|
436
|
+
request,
|
|
437
|
+
legacyCandidates: [
|
|
438
|
+
{
|
|
439
|
+
key: buildLegacyRepositoryCacheKey(apiKey, FILE_TOOL_PUBLIC_NAME, legacyArgs),
|
|
440
|
+
decode: (raw) => decodeLegacyZaiFile(raw, {
|
|
441
|
+
repository: request.repository,
|
|
442
|
+
path: request.path,
|
|
443
|
+
}),
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
},
|
|
448
|
+
decodeCached(value) {
|
|
449
|
+
// Total normalized cache decoder: delegate to the shared P6-02
|
|
450
|
+
// `decodeRepositoryFile`. Shared decoder rejects fractional
|
|
451
|
+
// `originalContentLength`, non-string `content`, empty `path`,
|
|
452
|
+
// and primitives/arrays at the top level.
|
|
453
|
+
return decodeRepositoryFile(value);
|
|
454
|
+
},
|
|
455
|
+
async invoke(request) {
|
|
456
|
+
operation.validate(request);
|
|
457
|
+
const args = {
|
|
458
|
+
repo_name: request.repository,
|
|
459
|
+
file_path: request.path,
|
|
460
|
+
};
|
|
461
|
+
return invokeRepositoryOperation(clientFactory, FILE_TOOL_PUBLIC_NAME, args, (raw) => parseZaiFile(raw, request), options.closeTimeoutMs);
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
return operation;
|
|
465
|
+
}
|
|
466
|
+
// ---------------------------------------------------------------------------
|
|
467
|
+
// Operation factory — Directory
|
|
468
|
+
// ---------------------------------------------------------------------------
|
|
469
|
+
function createZaiListDirectoryOperation(options) {
|
|
470
|
+
const { env, clientFactory } = options;
|
|
471
|
+
function resolveApiKey() {
|
|
472
|
+
return requireZaiApiKey(env);
|
|
473
|
+
}
|
|
474
|
+
const operation = {
|
|
475
|
+
kind: "repository-list-directory",
|
|
476
|
+
validate(request) {
|
|
477
|
+
assertRepository(request.repository);
|
|
478
|
+
assertDirectoryPath(request.path);
|
|
479
|
+
// Directory allows `path: ""` (root) AND non-root paths. The
|
|
480
|
+
// Adapter performs only the deterministic parent/child
|
|
481
|
+
// projection; canonical child-path safety validation belongs to
|
|
482
|
+
// the Explorer layer (P6-05).
|
|
483
|
+
},
|
|
484
|
+
cacheIdentity(request) {
|
|
485
|
+
const apiKey = resolveApiKey();
|
|
486
|
+
// Directory argument insertion order is fixed (DESIGN.md §18):
|
|
487
|
+
// root -> repo_name only
|
|
488
|
+
// non-root -> repo_name, dir_path
|
|
489
|
+
const legacyArgs = { repo_name: request.repository };
|
|
490
|
+
if (request.path !== "") {
|
|
491
|
+
legacyArgs.dir_path = request.path;
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
provider: "zai",
|
|
495
|
+
capability: "repository-exploration",
|
|
496
|
+
operation: "repository-list-directory",
|
|
497
|
+
credentialFingerprint: credentialFingerprint(apiKey),
|
|
498
|
+
request,
|
|
499
|
+
legacyCandidates: [
|
|
500
|
+
{
|
|
501
|
+
key: buildLegacyRepositoryCacheKey(apiKey, DIRECTORY_TOOL_PUBLIC_NAME, legacyArgs),
|
|
502
|
+
decode: (raw) => decodeLegacyZaiDirectory(raw, {
|
|
503
|
+
repository: request.repository,
|
|
504
|
+
path: request.path,
|
|
505
|
+
}),
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
};
|
|
509
|
+
},
|
|
510
|
+
decodeCached(value) {
|
|
511
|
+
// Total normalized cache decoder: delegate to the shared P6-02
|
|
512
|
+
// `decodeRepositoryDirectoryListing`. Shared decoder rejects
|
|
513
|
+
// malformed entries (empty name/path, unknown kind) and
|
|
514
|
+
// primitives/arrays at the top level.
|
|
515
|
+
return decodeRepositoryDirectoryListing(value);
|
|
516
|
+
},
|
|
517
|
+
async invoke(request) {
|
|
518
|
+
operation.validate(request);
|
|
519
|
+
const args = { repo_name: request.repository };
|
|
520
|
+
if (request.path !== "") {
|
|
521
|
+
args.dir_path = request.path;
|
|
522
|
+
}
|
|
523
|
+
return invokeRepositoryOperation(clientFactory, DIRECTORY_TOOL_PUBLIC_NAME, args, (raw) => parseZaiDirectory(raw, request), options.closeTimeoutMs);
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
return operation;
|
|
527
|
+
}
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
// Legacy decoder — total over `unknown`, returns normalized Result or
|
|
530
|
+
// `null`. Each legacy decoder runs the raw Provider string through the
|
|
531
|
+
// same production parser so the read-through path validates the same
|
|
532
|
+
// grammar the cache decoder does. `JSON.stringify(raw)` is used only
|
|
533
|
+
// when `raw` is already an object (the normalized shape); raw string
|
|
534
|
+
// values are passed through unchanged.
|
|
535
|
+
// ---------------------------------------------------------------------------
|
|
536
|
+
function decodeLegacyZaiSearch(raw, request) {
|
|
537
|
+
try {
|
|
538
|
+
return parseZaiSearch(raw, request);
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function decodeLegacyZaiFile(raw, request) {
|
|
545
|
+
try {
|
|
546
|
+
return parseZaiFile(raw, request);
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function decodeLegacyZaiDirectory(raw, request) {
|
|
553
|
+
try {
|
|
554
|
+
return parseZaiDirectory(raw, request);
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
// Validation helpers
|
|
562
|
+
//
|
|
563
|
+
// Request validation throws `ValidationError` (P6-04A) for invalid
|
|
564
|
+
// repository, query, language, File path, and Directory path types —
|
|
565
|
+
// distinct from parser/envelope failures, which remain retryable
|
|
566
|
+
// `ApiError` 502. `ValidationError` is terminal at the request layer
|
|
567
|
+
// (the retry classifier treats `VALIDATION_ERROR` as non-retryable)
|
|
568
|
+
// and never reaches a transport.
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
function assertRepository(repository) {
|
|
571
|
+
if (typeof repository !== "string" || !containsSlash(repository)) {
|
|
572
|
+
throw new ValidationError("Z.AI repository must be a string of the form 'owner/name'");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function containsSlash(value) {
|
|
576
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
577
|
+
if (value.charCodeAt(i) === 47 /* "/" */)
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
function assertQuery(query) {
|
|
583
|
+
if (typeof query !== "string" || query.trim().length === 0) {
|
|
584
|
+
throw new ValidationError("Z.AI search query must contain at least one non-whitespace character");
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function assertLanguage(language) {
|
|
588
|
+
if (language !== "en" && language !== "zh") {
|
|
589
|
+
throw new ValidationError("Z.AI search language must be 'en' or 'zh'");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function assertNonRootPath(path, label) {
|
|
593
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
594
|
+
throw new ValidationError(`Z.AI ${label} path must be a non-empty string`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function assertDirectoryPath(path) {
|
|
598
|
+
// Directory allows both `path: ""` (root) and a non-root repository-
|
|
599
|
+
// relative POSIX path. The Adapter only validates the type here;
|
|
600
|
+
// canonical child-path safety validation belongs to the Explorer.
|
|
601
|
+
if (typeof path !== "string") {
|
|
602
|
+
throw new ValidationError("Z.AI directory path must be a string");
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// ---------------------------------------------------------------------------
|
|
606
|
+
// Per-invocation transport lifecycle (DESIGN.md §18 "Transport close
|
|
607
|
+
// semantics"). Each uncached attempt constructs a fresh client and
|
|
608
|
+
// closes it exactly once in `finally`. Close rejection or timeout
|
|
609
|
+
// never replaces success and never masks the primary operation failure.
|
|
610
|
+
//
|
|
611
|
+
// The Adapter NEVER retries internally — shared execution owns retry
|
|
612
|
+
// policy. A retry constructs a fresh Adapter attempt and therefore a
|
|
613
|
+
// fresh client.
|
|
614
|
+
// ---------------------------------------------------------------------------
|
|
615
|
+
async function invokeRepositoryOperation(clientFactory, publicToolName, args, parse, closeTimeoutMs) {
|
|
616
|
+
const clientOptions = {
|
|
617
|
+
enableVision: false,
|
|
618
|
+
noCache: true,
|
|
619
|
+
disableRetry: true,
|
|
620
|
+
};
|
|
621
|
+
const client = clientFactory(clientOptions);
|
|
622
|
+
// The success and primary-failure paths must survive a close
|
|
623
|
+
// rejection or timeout. We capture both outcomes separately so a
|
|
624
|
+
// failing close cannot replace a successful result and cannot mask a
|
|
625
|
+
// primary Provider failure.
|
|
626
|
+
let primaryError;
|
|
627
|
+
let result;
|
|
628
|
+
try {
|
|
629
|
+
try {
|
|
630
|
+
// Invoke through the public dotted tool name so the underlying
|
|
631
|
+
// client resolves the discovered internal identity (P6-01A fix
|
|
632
|
+
// path). No retry is performed inside this Adapter attempt;
|
|
633
|
+
// shared execution owns the retry policy.
|
|
634
|
+
result = parse(await client.callToolRaw(publicToolName, args));
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
// Wrap any thrown MCP error into the normalized Adapter error.
|
|
638
|
+
primaryError = normalizeMcpInvokeError(error);
|
|
639
|
+
throw primaryError;
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
// Best-effort close. Matches the existing `ZaiMcpClient.close`
|
|
645
|
+
// semantic: race the close against a timeout that resolves
|
|
646
|
+
// silently so a stuck close cannot stall the Adapter attempt.
|
|
647
|
+
// Close rejection is also silently swallowed.
|
|
648
|
+
await closeWithBound(client, closeTimeoutMs);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Close the client with a bounded timeout that matches the existing
|
|
653
|
+
* `ZaiMcpClient.close(timeoutMs = 2000)` semantic. The timeout
|
|
654
|
+
* resolves silently (not rejects) so the close never throws and the
|
|
655
|
+
* Adapter attempt never stalls on a stuck close. Any close rejection
|
|
656
|
+
* is also silently swallowed.
|
|
657
|
+
*/
|
|
658
|
+
async function closeWithBound(client, timeoutMs) {
|
|
659
|
+
let timer;
|
|
660
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
661
|
+
timer = setTimeout(() => resolve(), timeoutMs);
|
|
662
|
+
if (timer && typeof timer === "object" && "unref" in timer) {
|
|
663
|
+
timer.unref();
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
try {
|
|
667
|
+
await Promise.race([client.close().catch(() => undefined), timeoutPromise]);
|
|
668
|
+
}
|
|
669
|
+
finally {
|
|
670
|
+
if (timer !== undefined)
|
|
671
|
+
clearTimeout(timer);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Normalize a Provider failure surfaced through `callToolRaw`. The
|
|
676
|
+
* underlying client already normalizes typed transport errors
|
|
677
|
+
* (`AuthError`, `NetworkError`, `TimeoutError`, `ApiError`,
|
|
678
|
+
* `QuotaError`); this function preserves ANY normalized
|
|
679
|
+
* `ScoutlineError` so the shared retry classifier sees the original
|
|
680
|
+
* `code` and `statusCode`. This base-class check also covers the
|
|
681
|
+
* repository-local `ScoutlineError` constructed for an encoded 403
|
|
682
|
+
* (which intentionally does not widen the global `AuthError`
|
|
683
|
+
* constructor). Untyped throwables surface as sanitized retryable
|
|
684
|
+
* `ApiError` 502 — the Adapter never embeds a raw Provider string
|
|
685
|
+
* into the public envelope.
|
|
686
|
+
*/
|
|
687
|
+
function normalizeMcpInvokeError(error) {
|
|
688
|
+
if (error instanceof ScoutlineError) {
|
|
689
|
+
return error;
|
|
690
|
+
}
|
|
691
|
+
// Defensive: never let a raw Provider string reach the public
|
|
692
|
+
// envelope. An untyped throwable is a sanitized 502-equivalent.
|
|
693
|
+
return new ApiError("Z.AI repository request failed", 502);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Build the Z.AI Repository Capability. The capability is composed of
|
|
697
|
+
* three typed `RepositoryOperation` descriptors; the Adapter owns
|
|
698
|
+
* credentials, transport lifecycle, raw request/response mapping, and
|
|
699
|
+
* error normalization. No transport, credential resolution, or I/O
|
|
700
|
+
* happens during construction.
|
|
701
|
+
*/
|
|
702
|
+
export function createZaiRepositoryCapability(options) {
|
|
703
|
+
const closeTimeoutMs = options.closeTimeoutMs ?? ZAI_REPOSITORY_CLOSE_BOUND_MS;
|
|
704
|
+
const sharedOptions = {
|
|
705
|
+
env: options.env,
|
|
706
|
+
clientFactory: options.clientFactory,
|
|
707
|
+
closeTimeoutMs,
|
|
708
|
+
};
|
|
709
|
+
return {
|
|
710
|
+
search: createZaiSearchOperation(sharedOptions),
|
|
711
|
+
readFile: createZaiReadFileOperation(sharedOptions),
|
|
712
|
+
listDirectory: createZaiListDirectoryOperation(sharedOptions),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
//# sourceMappingURL=repository.js.map
|