@aliyunrds/ctxdb 1.0.8-beta.1 → 1.0.8-beta.3
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 +14 -10
- package/dist/{chunk-NFAPLK4C.js → chunk-3NJ37TEY.js} +1 -1
- package/dist/{chunk-NF5KFV2P.js → chunk-7OX6UVPB.js} +15 -8
- package/dist/{chunk-LTAO6USK.js → chunk-AUBVVYQL.js} +1 -1
- package/dist/{chunk-KYW76CL7.js → chunk-BUK4SZC2.js} +980 -958
- package/dist/{chunk-3B7J5VUT.js → chunk-EI63DQX3.js} +1 -1
- package/dist/{chunk-TYW6ABNS.js → chunk-LZ2LOWZL.js} +2 -2
- package/dist/{chunk-SGRMPSZU.js → chunk-ZFS6OMWE.js} +4 -3
- package/dist/cli/main.js +29 -17
- package/dist/hooks/hermes-post-llm-call.js +10 -8
- package/dist/hooks/hermes-pre-llm-call.js +13 -12
- package/dist/hooks/session-start.js +12 -11
- package/dist/hooks/stop.js +10 -8
- package/dist/hooks/user-prompt-submit.js +12 -11
- package/dist/opencode/index.js +21 -7
- package/package.json +1 -1
|
@@ -3,1064 +3,1094 @@ import {
|
|
|
3
3
|
DISTRIBUTION_MANIFEST
|
|
4
4
|
} from "./chunk-R67JELM7.js";
|
|
5
5
|
|
|
6
|
-
// src/lib/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { accessSync, constants, existsSync, statSync } from "fs";
|
|
10
|
-
var SUPPORTED_AGENTS = ["qoder", "qoderwork", "qwenwork", "codex", "claude", "opencode", "hermes", "workbuddy"];
|
|
11
|
-
var SUPPORTED_AGENT_CHOICES = SUPPORTED_AGENTS.join("|");
|
|
12
|
-
var AGENT_CHOICES = [...SUPPORTED_AGENTS, "default"].join("|");
|
|
13
|
-
function isBuiltinAgent(v) {
|
|
14
|
-
return typeof v === "string" && SUPPORTED_AGENTS.includes(v);
|
|
6
|
+
// src/lib/credentials.ts
|
|
7
|
+
function apiKeyCredential(apiKey) {
|
|
8
|
+
return { type: "api-key", value: apiKey };
|
|
15
9
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function agentHomeDir(agent, home = homedir()) {
|
|
21
|
-
switch (agent) {
|
|
22
|
-
case "qoder":
|
|
23
|
-
return join(home, ".qoder");
|
|
24
|
-
case "qoderwork":
|
|
25
|
-
return join(home, ".qoderwork");
|
|
26
|
-
case "qwenwork":
|
|
27
|
-
return join(home, ".qwenwork");
|
|
28
|
-
case "codex":
|
|
29
|
-
return join(home, ".codex");
|
|
30
|
-
case "claude":
|
|
31
|
-
return join(home, ".claude");
|
|
32
|
-
case "opencode":
|
|
33
|
-
return join(home, ".config", "opencode");
|
|
34
|
-
case "hermes":
|
|
35
|
-
return join(home, ".hermes");
|
|
36
|
-
case "workbuddy":
|
|
37
|
-
return join(home, ".workbuddy");
|
|
38
|
-
}
|
|
10
|
+
function resolveDataApiCredential(cfg, env = process.env) {
|
|
11
|
+
const accessToken = env.CTXDB_ACCESS_TOKEN?.trim();
|
|
12
|
+
if (accessToken) return { type: "access-token", value: accessToken };
|
|
13
|
+
return cfg.apiKey ? apiKeyCredential(cfg.apiKey) : null;
|
|
39
14
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
qoderwork: [".qoderworkcn"],
|
|
43
|
-
qwenwork: [".qwenworkcn"]
|
|
44
|
-
};
|
|
45
|
-
function agentHomeDirs(agent, home = homedir()) {
|
|
46
|
-
const primary = agentHomeDir(agent, home);
|
|
47
|
-
const variants = (AGENT_VARIANT_HOMES[agent] ?? []).map(
|
|
48
|
-
(name) => join(home, name)
|
|
49
|
-
);
|
|
50
|
-
return [primary, ...variants];
|
|
15
|
+
function isDataApiReady(cfg, env = process.env) {
|
|
16
|
+
return Boolean(cfg.baseUrl && resolveDataApiCredential(cfg, env));
|
|
51
17
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return { supported: true, detail: `${agent} is supported on ${platform}.` };
|
|
18
|
+
|
|
19
|
+
// src/lib/logger.ts
|
|
20
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
21
|
+
import { homedir } from "os";
|
|
22
|
+
import { dirname, join } from "path";
|
|
23
|
+
function logPath() {
|
|
24
|
+
return join(homedir(), ".ctxdb", "logs", "ctxdb.log");
|
|
60
25
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const existing = candidates.filter(exists);
|
|
65
|
-
return { exists: existing.length > 0, existing, candidates };
|
|
26
|
+
var _enabled = null;
|
|
27
|
+
function setDebug(enabled) {
|
|
28
|
+
_enabled = enabled;
|
|
66
29
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
return existsSync(path);
|
|
70
|
-
} catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
30
|
+
function isDebug() {
|
|
31
|
+
return _enabled === true;
|
|
73
32
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
33
|
+
function debug(tag, msg, data) {
|
|
34
|
+
if (_enabled !== true) return;
|
|
35
|
+
const now = /* @__PURE__ */ new Date();
|
|
36
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
37
|
+
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
38
|
+
const tz = -now.getTimezoneOffset();
|
|
39
|
+
const tzSign = tz >= 0 ? "+" : "-";
|
|
40
|
+
const tzH = pad(Math.floor(Math.abs(tz) / 60));
|
|
41
|
+
const tzM = pad(Math.abs(tz) % 60);
|
|
42
|
+
const ts = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${ms}${tzSign}${tzH}:${tzM}`;
|
|
43
|
+
let line = `${ts} [${tag}] ${msg}`;
|
|
44
|
+
if (data !== void 0) {
|
|
45
|
+
const s = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
46
|
+
line += `
|
|
47
|
+
${s}`;
|
|
83
48
|
}
|
|
84
|
-
|
|
85
|
-
|
|
49
|
+
line += "\n";
|
|
50
|
+
const p = logPath();
|
|
86
51
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return true;
|
|
52
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
53
|
+
appendFileSync(p, line, "utf-8");
|
|
90
54
|
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function envValue(env, name, platform) {
|
|
95
|
-
if (env[name] !== void 0) return env[name];
|
|
96
|
-
if (platform !== "win32") return void 0;
|
|
97
|
-
const entry = Object.entries(env).find(([key]) => key.toLowerCase() === name.toLowerCase());
|
|
98
|
-
return entry?.[1];
|
|
99
|
-
}
|
|
100
|
-
function resolveAgentExecutable(command, options = {}) {
|
|
101
|
-
const platform = options.platform ?? process.platform;
|
|
102
|
-
const env = options.env ?? process.env;
|
|
103
|
-
const isExecutable = options.isExecutable ?? isExecutableAdapter;
|
|
104
|
-
const pathValue = envValue(env, "PATH", platform) ?? "";
|
|
105
|
-
const pathSeparator = platform === "win32" ? ";" : delimiter;
|
|
106
|
-
const pathEntries = pathValue.split(pathSeparator).map((entry) => entry.trim().replace(/^"(.*)"$/, "$1")).filter(Boolean);
|
|
107
|
-
const suffixes = platform === "win32" ? (envValue(env, "PATHEXT", platform) ?? ".COM;.EXE;.BAT;.CMD").split(";").map((suffix) => suffix.trim()).filter(Boolean) : [""];
|
|
108
|
-
for (const directory of pathEntries) {
|
|
109
|
-
for (const suffix of suffixes) {
|
|
110
|
-
const candidate = join(directory, `${command}${suffix}`);
|
|
111
|
-
if (isExecutable(candidate, platform)) return candidate;
|
|
112
|
-
}
|
|
113
55
|
}
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
116
|
-
function displayHomePath(home, path) {
|
|
117
|
-
if (path === home) return "~";
|
|
118
|
-
return path.startsWith(`${home}${sep}`) ? `~${path.slice(home.length)}` : path;
|
|
119
56
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
platform,
|
|
137
|
-
env,
|
|
138
|
-
isExecutable: options.isExecutable
|
|
139
|
-
});
|
|
140
|
-
if (executable) {
|
|
141
|
-
evidence.push({ kind: "executable", value: executableName });
|
|
57
|
+
|
|
58
|
+
// src/lib/package-version.ts
|
|
59
|
+
import { readFileSync } from "fs";
|
|
60
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
61
|
+
import { fileURLToPath } from "url";
|
|
62
|
+
var PACKAGE_NAMES = /* @__PURE__ */ new Set([
|
|
63
|
+
"@aliyunrds/ctxdb",
|
|
64
|
+
"@ali/ctxdb-internal"
|
|
65
|
+
]);
|
|
66
|
+
function findPackageIdentity() {
|
|
67
|
+
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
68
|
+
for (let i = 0; i < 5; i++) {
|
|
69
|
+
try {
|
|
70
|
+
const pkg = JSON.parse(readFileSync(join2(dir, "package.json"), "utf-8"));
|
|
71
|
+
if (PACKAGE_NAMES.has(pkg.name) && typeof pkg.version === "string") {
|
|
72
|
+
return { name: pkg.name, version: pkg.version };
|
|
142
73
|
}
|
|
74
|
+
} catch {
|
|
143
75
|
}
|
|
144
|
-
|
|
145
|
-
observations.push({
|
|
146
|
-
agent,
|
|
147
|
-
selected: false,
|
|
148
|
-
reason: "not-detected",
|
|
149
|
-
evidence
|
|
150
|
-
});
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const support = agentPlatformSupport(agent, platform);
|
|
154
|
-
if (!support.supported) {
|
|
155
|
-
observations.push({
|
|
156
|
-
agent,
|
|
157
|
-
selected: false,
|
|
158
|
-
reason: "unsupported-platform",
|
|
159
|
-
evidence,
|
|
160
|
-
detail: support.detail
|
|
161
|
-
});
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
observations.push({ agent, selected: true, reason: "detected", evidence });
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
selected: observations.filter((item) => item.selected).map((item) => item.agent),
|
|
168
|
-
observations
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
function agentFromEnv(env = process.env) {
|
|
172
|
-
return isAgentSlug(env.CTXDB_AGENT) ? env.CTXDB_AGENT : "default";
|
|
173
|
-
}
|
|
174
|
-
function agentFromArgvWithFallback(argv = process.argv.slice(2), env = process.env) {
|
|
175
|
-
for (let i = 0; i < argv.length; i++) {
|
|
176
|
-
const tok = argv[i];
|
|
177
|
-
if (tok === "--agent" && isAgentSlug(argv[i + 1])) {
|
|
178
|
-
return { agent: argv[i + 1], fellBack: false };
|
|
179
|
-
}
|
|
180
|
-
if (tok.startsWith("--agent=")) {
|
|
181
|
-
const raw = tok.slice("--agent=".length);
|
|
182
|
-
if (isAgentSlug(raw)) return { agent: raw, fellBack: false };
|
|
183
|
-
}
|
|
76
|
+
dir = dirname2(dir);
|
|
184
77
|
}
|
|
185
|
-
return {
|
|
78
|
+
return { name: "@aliyunrds/ctxdb", version: "0.0.0" };
|
|
186
79
|
}
|
|
80
|
+
var PACKAGE_IDENTITY = findPackageIdentity();
|
|
81
|
+
var PACKAGE_NAME = PACKAGE_IDENTITY.name;
|
|
82
|
+
var PACKAGE_VERSION = PACKAGE_IDENTITY.version;
|
|
187
83
|
|
|
188
|
-
// src/lib/
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
import {
|
|
195
|
-
chmodSync,
|
|
196
|
-
closeSync,
|
|
197
|
-
copyFileSync,
|
|
198
|
-
existsSync as existsSync2,
|
|
199
|
-
fsyncSync,
|
|
200
|
-
mkdirSync,
|
|
201
|
-
openSync,
|
|
202
|
-
renameSync,
|
|
203
|
-
unlinkSync,
|
|
204
|
-
writeFileSync
|
|
205
|
-
} from "fs";
|
|
206
|
-
import { randomBytes } from "crypto";
|
|
207
|
-
import { dirname } from "path";
|
|
208
|
-
var nodeSecureFileSystem = {
|
|
209
|
-
exists: existsSync2,
|
|
210
|
-
mkdir: (path, options) => {
|
|
211
|
-
mkdirSync(path, options);
|
|
212
|
-
},
|
|
213
|
-
chmod: chmodSync,
|
|
214
|
-
open: openSync,
|
|
215
|
-
write: (fd, data) => {
|
|
216
|
-
writeFileSync(fd, data);
|
|
217
|
-
},
|
|
218
|
-
fsync: fsyncSync,
|
|
219
|
-
close: closeSync,
|
|
220
|
-
copy: copyFileSync,
|
|
221
|
-
rename: renameSync,
|
|
222
|
-
unlink: unlinkSync
|
|
223
|
-
};
|
|
224
|
-
function configBackupPath(target) {
|
|
225
|
-
return `${target}.bak`;
|
|
226
|
-
}
|
|
227
|
-
function secureAtomicWrite(target, content, options = {}) {
|
|
228
|
-
const fs = options.fs ?? nodeSecureFileSystem;
|
|
229
|
-
const platform = options.platform ?? process.platform;
|
|
230
|
-
const posix = platform !== "win32";
|
|
231
|
-
const dir = dirname(target);
|
|
232
|
-
const backup = configBackupPath(target);
|
|
233
|
-
const suffix = options.tempSuffix ?? `${process.pid}.${randomBytes(6).toString("hex")}`;
|
|
234
|
-
const temp = `${target}.${suffix}.tmp`;
|
|
235
|
-
const hadTarget = fs.exists(target);
|
|
236
|
-
fs.mkdir(dir, { recursive: true, mode: 448 });
|
|
237
|
-
if (posix) {
|
|
238
|
-
fs.chmod(dir, 448);
|
|
239
|
-
if (hadTarget) fs.chmod(target, 384);
|
|
84
|
+
// src/lib/http-client.ts
|
|
85
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
86
|
+
var CtxdbError = class extends Error {
|
|
87
|
+
constructor(message) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.name = "CtxdbError";
|
|
240
90
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
91
|
+
};
|
|
92
|
+
var AuthError = class extends CtxdbError {
|
|
93
|
+
status = 401;
|
|
94
|
+
errorCode;
|
|
95
|
+
errorMessage;
|
|
96
|
+
data;
|
|
97
|
+
responseBody;
|
|
98
|
+
constructor(message = "Unauthorized (HTTP 401) \u2014 check Data API credential", fields = {}) {
|
|
99
|
+
super(message);
|
|
100
|
+
this.name = "AuthError";
|
|
101
|
+
this.errorCode = fields.errorCode;
|
|
102
|
+
this.errorMessage = fields.errorMessage;
|
|
103
|
+
this.data = fields.data;
|
|
104
|
+
this.responseBody = fields.responseBody;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var NotFoundError = class extends CtxdbError {
|
|
108
|
+
status = 404;
|
|
109
|
+
path;
|
|
110
|
+
errorCode;
|
|
111
|
+
errorMessage;
|
|
112
|
+
data;
|
|
113
|
+
responseBody;
|
|
114
|
+
constructor(path, fields = {}) {
|
|
115
|
+
super(`Not found: ${path}`);
|
|
116
|
+
this.name = "NotFoundError";
|
|
117
|
+
this.path = path;
|
|
118
|
+
this.errorCode = fields.errorCode;
|
|
119
|
+
this.errorMessage = fields.errorMessage;
|
|
120
|
+
this.data = fields.data;
|
|
121
|
+
this.responseBody = fields.responseBody;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var APIError = class extends CtxdbError {
|
|
125
|
+
status = 400;
|
|
126
|
+
path;
|
|
127
|
+
detail;
|
|
128
|
+
errorCode;
|
|
129
|
+
errorMessage;
|
|
130
|
+
data;
|
|
131
|
+
responseBody;
|
|
132
|
+
constructor(path, detail, fields = {}) {
|
|
133
|
+
super(`API error at ${path}: ${detail}`);
|
|
134
|
+
this.name = "APIError";
|
|
135
|
+
this.path = path;
|
|
136
|
+
this.detail = detail;
|
|
137
|
+
this.errorCode = fields.errorCode;
|
|
138
|
+
this.errorMessage = fields.errorMessage;
|
|
139
|
+
this.data = fields.data;
|
|
140
|
+
this.responseBody = fields.responseBody;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var CtxdbHttpError = class extends CtxdbError {
|
|
144
|
+
status;
|
|
145
|
+
detail;
|
|
146
|
+
errorCode;
|
|
147
|
+
errorMessage;
|
|
148
|
+
data;
|
|
149
|
+
responseBody;
|
|
150
|
+
constructor(status, detail, fields = {}) {
|
|
151
|
+
super(`HTTP ${status}: ${detail}`);
|
|
152
|
+
this.name = "CtxdbHttpError";
|
|
153
|
+
this.status = status;
|
|
154
|
+
this.detail = detail;
|
|
155
|
+
this.errorCode = fields.errorCode;
|
|
156
|
+
this.errorMessage = fields.errorMessage;
|
|
157
|
+
this.data = fields.data;
|
|
158
|
+
this.responseBody = fields.responseBody;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
var HttpClient = class {
|
|
162
|
+
baseUrl;
|
|
163
|
+
credential;
|
|
164
|
+
apiKey;
|
|
165
|
+
timeoutMs;
|
|
166
|
+
userAgent;
|
|
167
|
+
extraHeaders;
|
|
168
|
+
fetchImpl;
|
|
169
|
+
authorizationProvider;
|
|
170
|
+
constructor(opts) {
|
|
171
|
+
const credential = opts.credential ?? (opts.apiKey ? apiKeyCredential(opts.apiKey) : null);
|
|
172
|
+
if (!credential && !opts.authorizationProvider) {
|
|
173
|
+
throw new CtxdbError("DATA API credential is required");
|
|
174
|
+
}
|
|
175
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
176
|
+
this.credential = credential;
|
|
177
|
+
this.apiKey = opts.apiKey ?? "";
|
|
178
|
+
this.authorizationProvider = opts.authorizationProvider;
|
|
179
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
180
|
+
this.userAgent = opts.userAgent ?? `ctxdb-cli/${PACKAGE_VERSION}`;
|
|
181
|
+
this.extraHeaders = { ...opts.extraHeaders ?? {} };
|
|
182
|
+
const f = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
183
|
+
this.fetchImpl = f;
|
|
184
|
+
}
|
|
185
|
+
async buildHeaders(authorization, contentType, requestHeaders = {}) {
|
|
186
|
+
const h = {
|
|
187
|
+
Authorization: `${authorization.scheme} ${authorization.value}`,
|
|
188
|
+
"User-Agent": this.userAgent,
|
|
189
|
+
Connection: "close",
|
|
190
|
+
...this.extraHeaders,
|
|
191
|
+
...requestHeaders
|
|
192
|
+
};
|
|
193
|
+
if (contentType) h["Content-Type"] = contentType;
|
|
194
|
+
return h;
|
|
195
|
+
}
|
|
196
|
+
async doRequest(method, path, init = {}) {
|
|
197
|
+
let authorization;
|
|
198
|
+
if (this.authorizationProvider) {
|
|
199
|
+
authorization = await this.authorizationProvider.resolve();
|
|
268
200
|
} else {
|
|
269
|
-
|
|
270
|
-
|
|
201
|
+
const credential = this.credential;
|
|
202
|
+
if (!credential) throw new CtxdbError("DATA API credential is required");
|
|
203
|
+
authorization = {
|
|
204
|
+
scheme: credential.type === "access-token" ? "Bearer" : "Token",
|
|
205
|
+
value: credential.value,
|
|
206
|
+
baseUrl: this.baseUrl,
|
|
207
|
+
expiresAt: null
|
|
208
|
+
};
|
|
271
209
|
}
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
210
|
+
let url = `${authorization.baseUrl.replace(/\/+$/, "")}${path}`;
|
|
211
|
+
if (init.params) {
|
|
212
|
+
const qs = new URLSearchParams();
|
|
213
|
+
for (const [k, v] of Object.entries(init.params)) {
|
|
214
|
+
if (v !== void 0 && v !== null) qs.append(k, String(v));
|
|
277
215
|
}
|
|
216
|
+
const s = qs.toString();
|
|
217
|
+
if (s) url = `${url}?${s}`;
|
|
278
218
|
}
|
|
279
|
-
|
|
219
|
+
const effectiveTimeout = init.timeoutMs ?? this.timeoutMs;
|
|
220
|
+
const dbg = isDebug();
|
|
221
|
+
let t0 = 0;
|
|
222
|
+
if (dbg) {
|
|
223
|
+
t0 = Date.now();
|
|
224
|
+
debug("http", `\u2192 ${method} ${url} (timeout=${effectiveTimeout}ms)`);
|
|
225
|
+
}
|
|
226
|
+
const controller = new AbortController();
|
|
227
|
+
const timer = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
228
|
+
try {
|
|
229
|
+
let resp;
|
|
280
230
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
231
|
+
resp = await this.fetchImpl(url, {
|
|
232
|
+
method,
|
|
233
|
+
headers: await this.buildHeaders(
|
|
234
|
+
authorization,
|
|
235
|
+
init.contentType,
|
|
236
|
+
init.headers
|
|
237
|
+
),
|
|
238
|
+
body: init.body,
|
|
239
|
+
signal: controller.signal
|
|
240
|
+
});
|
|
241
|
+
} catch (err) {
|
|
242
|
+
if (err?.name === "AbortError") {
|
|
243
|
+
if (dbg) debug("http", `\u2717 ${method} ${path} timeout after ${Date.now() - t0}ms`);
|
|
244
|
+
throw new CtxdbError(`request timeout after ${effectiveTimeout}ms: ${url}`);
|
|
245
|
+
}
|
|
246
|
+
if (dbg) debug("http", `\u2717 ${method} ${path} network error after ${Date.now() - t0}ms: ${err?.message ?? err}`);
|
|
247
|
+
throw new CtxdbError(`network error contacting ${url}: ${err?.message ?? err}`);
|
|
248
|
+
}
|
|
249
|
+
if (resp.status === 204) {
|
|
250
|
+
if (dbg) debug("http", `\u2190 ${method} ${path} 204 (${Date.now() - t0}ms)`);
|
|
251
|
+
return {};
|
|
252
|
+
}
|
|
253
|
+
let text;
|
|
254
|
+
try {
|
|
255
|
+
text = await resp.text();
|
|
256
|
+
} catch (err) {
|
|
257
|
+
if (err?.name === "AbortError") {
|
|
258
|
+
if (dbg) debug("http", `\u2717 ${method} ${path} body-read timeout after ${Date.now() - t0}ms`);
|
|
259
|
+
throw new CtxdbError(`request timeout after ${effectiveTimeout}ms (body read): ${url}`);
|
|
260
|
+
}
|
|
261
|
+
if (dbg) debug("http", `\u2717 ${method} ${path} body-read error after ${Date.now() - t0}ms: ${err?.message ?? err}`);
|
|
262
|
+
throw new CtxdbError(`network error reading body from ${url}: ${err?.message ?? err}`);
|
|
263
|
+
}
|
|
264
|
+
if (!resp.ok) {
|
|
265
|
+
const parsedError = parseErrorResponse(text, `HTTP ${resp.status}`);
|
|
266
|
+
if (dbg) debug("http", `\u2190 ${method} ${path} ${resp.status} (${Date.now() - t0}ms) ${parsedError.detail}`);
|
|
267
|
+
if (resp.status === 401) {
|
|
268
|
+
throw new AuthError(void 0, parsedError);
|
|
269
|
+
}
|
|
270
|
+
if (resp.status === 404) {
|
|
271
|
+
throw new NotFoundError(path, parsedError);
|
|
272
|
+
}
|
|
273
|
+
if (resp.status === 400) {
|
|
274
|
+
throw new APIError(path, parsedError.detail, parsedError);
|
|
275
|
+
}
|
|
276
|
+
throw new CtxdbHttpError(resp.status, parsedError.detail, parsedError);
|
|
283
277
|
}
|
|
278
|
+
if (dbg) debug("http", `\u2190 ${method} ${path} ${resp.status} (${Date.now() - t0}ms) body=${text.length}B`);
|
|
279
|
+
if (!text) return {};
|
|
280
|
+
return maybeJson(text);
|
|
281
|
+
} finally {
|
|
282
|
+
clearTimeout(timer);
|
|
284
283
|
}
|
|
285
284
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
import { createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
290
|
-
var PROCESS_FINGERPRINT_KEY = randomBytes2(32);
|
|
291
|
-
|
|
292
|
-
// src/credentials/local-credential-provider.ts
|
|
293
|
-
import {
|
|
294
|
-
closeSync as closeSync2,
|
|
295
|
-
existsSync as existsSync3,
|
|
296
|
-
mkdirSync as mkdirSync2,
|
|
297
|
-
openSync as openSync2,
|
|
298
|
-
readFileSync,
|
|
299
|
-
statSync as statSync2,
|
|
300
|
-
unlinkSync as unlinkSync2,
|
|
301
|
-
writeFileSync as writeFileSync2
|
|
302
|
-
} from "fs";
|
|
303
|
-
import { homedir as homedir2 } from "os";
|
|
304
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
305
|
-
import { setTimeout as delay } from "timers/promises";
|
|
306
|
-
|
|
307
|
-
// src/credentials/types.ts
|
|
308
|
-
var ACTIVE_CONTEXTDB_CREDENTIAL = "contextdb/active";
|
|
309
|
-
|
|
310
|
-
// src/credentials/local-credential-provider.ts
|
|
311
|
-
var DOCUMENT_VERSION = 1;
|
|
312
|
-
var STALE_LOCK_MS = 10 * 60 * 1e3;
|
|
313
|
-
function defaultCredentialsPath() {
|
|
314
|
-
return join2(homedir2(), ".ctxdb", "credentials.json");
|
|
315
|
-
}
|
|
316
|
-
function emptyDocument() {
|
|
317
|
-
return { version: DOCUMENT_VERSION, records: {} };
|
|
318
|
-
}
|
|
319
|
-
function normalizeOrigin(value, field) {
|
|
320
|
-
if (typeof value !== "string" || !value) {
|
|
321
|
-
throw new Error(`credentials: ${field} must be a non-empty URL`);
|
|
322
|
-
}
|
|
323
|
-
let url;
|
|
324
|
-
try {
|
|
325
|
-
url = new URL(value);
|
|
326
|
-
} catch {
|
|
327
|
-
throw new Error(`credentials: ${field} must be a valid URL`);
|
|
285
|
+
// ---------- Convenience verbs ----------
|
|
286
|
+
get(path, params) {
|
|
287
|
+
return this.doRequest("GET", path, { params });
|
|
328
288
|
}
|
|
329
|
-
|
|
330
|
-
|
|
289
|
+
postJson(path, body, params, options = {}) {
|
|
290
|
+
return this.doRequest("POST", path, {
|
|
291
|
+
body: JSON.stringify(body ?? {}),
|
|
292
|
+
contentType: "application/json",
|
|
293
|
+
params,
|
|
294
|
+
timeoutMs: options.timeoutMs,
|
|
295
|
+
headers: options.headers
|
|
296
|
+
});
|
|
331
297
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
298
|
+
putJson(path, body, params, options = {}) {
|
|
299
|
+
return this.doRequest("PUT", path, {
|
|
300
|
+
body: JSON.stringify(body),
|
|
301
|
+
contentType: "application/json",
|
|
302
|
+
params,
|
|
303
|
+
timeoutMs: options.timeoutMs,
|
|
304
|
+
headers: options.headers
|
|
305
|
+
});
|
|
337
306
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
307
|
+
patchJson(path, body, params, options = {}) {
|
|
308
|
+
return this.doRequest("PATCH", path, {
|
|
309
|
+
body: JSON.stringify(body),
|
|
310
|
+
contentType: "application/json",
|
|
311
|
+
params,
|
|
312
|
+
timeoutMs: options.timeoutMs,
|
|
313
|
+
headers: options.headers
|
|
314
|
+
});
|
|
341
315
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
345
|
-
throw new Error("credentials: api-key payload must be an object");
|
|
316
|
+
delete(path, params) {
|
|
317
|
+
return this.doRequest("DELETE", path, { params });
|
|
346
318
|
}
|
|
347
|
-
|
|
348
|
-
|
|
319
|
+
/**
|
|
320
|
+
* DELETE with a JSON body. ContextDB batch deletes use the
|
|
321
|
+
* `DELETE /v1/{resource}/batch` shape with an `{"ids": [...]}` body
|
|
322
|
+
* (server contract) — the plain `delete()` above only supports query
|
|
323
|
+
* params.
|
|
324
|
+
*/
|
|
325
|
+
deleteJson(path, body, params, options = {}) {
|
|
326
|
+
return this.doRequest("DELETE", path, {
|
|
327
|
+
body: JSON.stringify(body),
|
|
328
|
+
contentType: "application/json",
|
|
329
|
+
params,
|
|
330
|
+
timeoutMs: options.timeoutMs,
|
|
331
|
+
headers: options.headers
|
|
332
|
+
});
|
|
349
333
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
334
|
+
/**
|
|
335
|
+
* POST multipart/form-data using runtime-native FormData.
|
|
336
|
+
*
|
|
337
|
+
* `fields` are appended as text values; `files` are appended as Blob
|
|
338
|
+
* with a filename. We intentionally do NOT pass a `Content-Type`
|
|
339
|
+
* header — when fetch sees a FormData body it sets the multipart
|
|
340
|
+
* boundary itself.
|
|
341
|
+
*/
|
|
342
|
+
postMultipart(path, fields = {}, files = {}, options = {}) {
|
|
343
|
+
const fd = buildMultipartForm(fields, files);
|
|
344
|
+
return this.doRequest("POST", path, {
|
|
345
|
+
body: fd,
|
|
346
|
+
params: options.params,
|
|
347
|
+
timeoutMs: options.timeoutMs,
|
|
348
|
+
headers: options.headers
|
|
349
|
+
});
|
|
354
350
|
}
|
|
355
|
-
|
|
356
|
-
|
|
351
|
+
/** PUT multipart/form-data with the same runtime-owned boundary as POST. */
|
|
352
|
+
putMultipart(path, fields = {}, files = {}, options = {}) {
|
|
353
|
+
const fd = buildMultipartForm(fields, files);
|
|
354
|
+
return this.doRequest("PUT", path, {
|
|
355
|
+
body: fd,
|
|
356
|
+
params: options.params,
|
|
357
|
+
timeoutMs: options.timeoutMs,
|
|
358
|
+
headers: options.headers
|
|
359
|
+
});
|
|
357
360
|
}
|
|
358
|
-
|
|
359
|
-
|
|
361
|
+
};
|
|
362
|
+
function buildMultipartForm(fields, files) {
|
|
363
|
+
const fd = new FormData();
|
|
364
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
365
|
+
fd.append(name, value);
|
|
360
366
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
loginServer: normalizeOrigin(body.login_server, "login_server")
|
|
367
|
-
},
|
|
368
|
-
metadata: {
|
|
369
|
-
authorizationMethod: meta.authorization_method,
|
|
370
|
-
issuedAt: meta.issued_at
|
|
371
|
-
}
|
|
372
|
-
};
|
|
367
|
+
for (const [name, part] of Object.entries(files)) {
|
|
368
|
+
const blob = part.content instanceof Blob ? part.content : new Blob([part.content], { type: part.mimeType });
|
|
369
|
+
fd.append(name, blob, part.filename);
|
|
370
|
+
}
|
|
371
|
+
return fd;
|
|
373
372
|
}
|
|
374
|
-
function
|
|
375
|
-
let raw;
|
|
373
|
+
function maybeJson(text) {
|
|
376
374
|
try {
|
|
377
|
-
|
|
375
|
+
return JSON.parse(text);
|
|
378
376
|
} catch {
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
382
|
-
throw new Error("credentials: document must be an object");
|
|
383
|
-
}
|
|
384
|
-
const document = raw;
|
|
385
|
-
if (document.version !== DOCUMENT_VERSION) {
|
|
386
|
-
throw new Error(`credentials: unsupported document version ${String(document.version)}`);
|
|
387
|
-
}
|
|
388
|
-
if (!document.records || typeof document.records !== "object" || Array.isArray(document.records)) {
|
|
389
|
-
throw new Error("credentials: records must be an object");
|
|
390
|
-
}
|
|
391
|
-
const records = document.records;
|
|
392
|
-
const unknown = Object.keys(records).filter((key) => key !== ACTIVE_CONTEXTDB_CREDENTIAL);
|
|
393
|
-
if (unknown.length > 0) {
|
|
394
|
-
throw new Error(`credentials: unsupported record key ${unknown[0]}`);
|
|
377
|
+
return text;
|
|
395
378
|
}
|
|
396
|
-
const active = records[ACTIVE_CONTEXTDB_CREDENTIAL];
|
|
397
|
-
return {
|
|
398
|
-
version: DOCUMENT_VERSION,
|
|
399
|
-
records: active === void 0 ? {} : { [ACTIVE_CONTEXTDB_CREDENTIAL]: parseRecord(active) }
|
|
400
|
-
};
|
|
401
379
|
}
|
|
402
|
-
function
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
380
|
+
function parseErrorResponse(text, fallback) {
|
|
381
|
+
if (!text) return { detail: fallback };
|
|
382
|
+
let parsed;
|
|
383
|
+
try {
|
|
384
|
+
parsed = JSON.parse(text);
|
|
385
|
+
} catch {
|
|
386
|
+
return { detail: text || fallback, responseBody: text || void 0 };
|
|
407
387
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
388
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
389
|
+
const obj = parsed;
|
|
390
|
+
let detail = "";
|
|
391
|
+
for (const k of ["errorMessage", "detail", "message", "error"]) {
|
|
392
|
+
const v = obj[k];
|
|
393
|
+
if (typeof v === "string" && v) {
|
|
394
|
+
detail = v;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const rawCode = obj.errorCode;
|
|
399
|
+
const numericCode = typeof rawCode === "number" ? rawCode : typeof rawCode === "string" && rawCode.trim() !== "" ? Number(rawCode) : void 0;
|
|
400
|
+
const errorCode = typeof numericCode === "number" && Number.isFinite(numericCode) ? numericCode : void 0;
|
|
401
|
+
const errorMessage = typeof obj.errorMessage === "string" && obj.errorMessage ? obj.errorMessage : void 0;
|
|
402
|
+
return {
|
|
403
|
+
detail: detail || JSON.stringify(parsed),
|
|
404
|
+
errorCode,
|
|
405
|
+
errorMessage,
|
|
406
|
+
data: Object.hasOwn(obj, "data") ? obj.data : void 0,
|
|
407
|
+
responseBody: parsed
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return { detail: String(parsed), responseBody: parsed };
|
|
416
411
|
}
|
|
417
412
|
|
|
418
|
-
// src/lib/
|
|
419
|
-
import {
|
|
420
|
-
|
|
421
|
-
} from "
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
return join3(homedir3(), ".ctxdb");
|
|
428
|
-
}
|
|
429
|
-
var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
|
|
430
|
-
var DEFAULT_USER_ID = "default";
|
|
431
|
-
var DEFAULT_TOP_K = 5;
|
|
432
|
-
var DEFAULT_THRESHOLD = 0.4;
|
|
433
|
-
var DEFAULT_KNOWLEDGE_TOP_K = 6;
|
|
434
|
-
var DEFAULT_KB_CATALOG_INJECTION = "session_start";
|
|
435
|
-
function isComplete(cfg) {
|
|
436
|
-
return Boolean(cfg.apiKey && cfg.baseUrl);
|
|
437
|
-
}
|
|
438
|
-
function resolveConfigAgent(options = {}, preloadedRaw) {
|
|
439
|
-
if (isAgentSlug(options.agent)) return options.agent;
|
|
440
|
-
return agentFromEnv(options.env);
|
|
413
|
+
// src/lib/agents.ts
|
|
414
|
+
import { homedir as homedir2 } from "os";
|
|
415
|
+
import { delimiter, join as join3, sep } from "path";
|
|
416
|
+
import { accessSync, constants, existsSync, statSync } from "fs";
|
|
417
|
+
var SUPPORTED_AGENTS = ["qoder", "qoderwork", "qwenwork", "codex", "claude", "opencode", "hermes", "workbuddy"];
|
|
418
|
+
var SUPPORTED_AGENT_CHOICES = SUPPORTED_AGENTS.join("|");
|
|
419
|
+
var AGENT_CHOICES = [...SUPPORTED_AGENTS, "default"].join("|");
|
|
420
|
+
function isBuiltinAgent(v) {
|
|
421
|
+
return typeof v === "string" && SUPPORTED_AGENTS.includes(v);
|
|
441
422
|
}
|
|
442
|
-
function
|
|
443
|
-
|
|
444
|
-
const n = typeof v === "number" ? v : Number(v);
|
|
445
|
-
return Number.isFinite(n) ? Math.trunc(n) : fallback;
|
|
423
|
+
function isAgentSlug(v) {
|
|
424
|
+
return v === "default" || isBuiltinAgent(v);
|
|
446
425
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
426
|
+
var isAgent = isAgentSlug;
|
|
427
|
+
function agentHomeDir(agent, home = homedir2()) {
|
|
428
|
+
switch (agent) {
|
|
429
|
+
case "qoder":
|
|
430
|
+
return join3(home, ".qoder");
|
|
431
|
+
case "qoderwork":
|
|
432
|
+
return join3(home, ".qoderwork");
|
|
433
|
+
case "qwenwork":
|
|
434
|
+
return join3(home, ".qwenwork");
|
|
435
|
+
case "codex":
|
|
436
|
+
return join3(home, ".codex");
|
|
437
|
+
case "claude":
|
|
438
|
+
return join3(home, ".claude");
|
|
439
|
+
case "opencode":
|
|
440
|
+
return join3(home, ".config", "opencode");
|
|
441
|
+
case "hermes":
|
|
442
|
+
return join3(home, ".hermes");
|
|
443
|
+
case "workbuddy":
|
|
444
|
+
return join3(home, ".workbuddy-ai");
|
|
445
|
+
}
|
|
451
446
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
447
|
+
var AGENT_VARIANT_HOMES = {
|
|
448
|
+
qoder: [".qoder-cn"],
|
|
449
|
+
qoderwork: [".qoderworkcn"],
|
|
450
|
+
qwenwork: [".qwenworkcn"]
|
|
451
|
+
};
|
|
452
|
+
function agentHomeDirs(agent, home = homedir2()) {
|
|
453
|
+
const primary = agentHomeDir(agent, home);
|
|
454
|
+
const variants = (AGENT_VARIANT_HOMES[agent] ?? []).map(
|
|
455
|
+
(name) => join3(home, name)
|
|
456
|
+
);
|
|
457
|
+
return [primary, ...variants];
|
|
456
458
|
}
|
|
457
|
-
function
|
|
458
|
-
if (
|
|
459
|
-
return
|
|
459
|
+
function agentPlatformSupport(agent, platform = process.platform) {
|
|
460
|
+
if (agent === "hermes" && platform === "win32") {
|
|
461
|
+
return {
|
|
462
|
+
supported: false,
|
|
463
|
+
detail: "Hermes integration currently supports macOS/Linux only; Windows Hermes is not yet supported."
|
|
464
|
+
};
|
|
460
465
|
}
|
|
461
|
-
return
|
|
466
|
+
return { supported: true, detail: `${agent} is supported on ${platform}.` };
|
|
462
467
|
}
|
|
463
|
-
function
|
|
464
|
-
|
|
468
|
+
function inspectAgentHomes(agent, options = {}) {
|
|
469
|
+
const exists = options.exists ?? existsSyncAdapter;
|
|
470
|
+
const candidates = agentHomeDirs(agent, options.home);
|
|
471
|
+
const existing = candidates.filter(exists);
|
|
472
|
+
return { exists: existing.length > 0, existing, candidates };
|
|
473
|
+
}
|
|
474
|
+
function existsSyncAdapter(path) {
|
|
465
475
|
try {
|
|
466
|
-
|
|
467
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
468
|
-
return parsed;
|
|
469
|
-
}
|
|
476
|
+
return existsSync(path);
|
|
470
477
|
} catch {
|
|
478
|
+
return false;
|
|
471
479
|
}
|
|
472
|
-
return {};
|
|
473
480
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
+
var EXECUTABLE_AGENT_NAMES = {
|
|
482
|
+
opencode: "opencode",
|
|
483
|
+
hermes: "hermes"
|
|
484
|
+
};
|
|
485
|
+
function isDirectoryAdapter(path) {
|
|
486
|
+
try {
|
|
487
|
+
return statSync(path).isDirectory();
|
|
488
|
+
} catch {
|
|
489
|
+
return false;
|
|
481
490
|
}
|
|
482
|
-
return {};
|
|
483
491
|
}
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
492
|
+
function isExecutableAdapter(path, platform) {
|
|
493
|
+
try {
|
|
494
|
+
if (!statSync(path).isFile()) return false;
|
|
495
|
+
if (platform !== "win32") accessSync(path, constants.X_OK);
|
|
496
|
+
return true;
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
487
500
|
}
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
return
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
userId: typeof raw.user_id === "string" && raw.user_id ? raw.user_id : DEFAULT_USER_ID,
|
|
494
|
-
agentId: typeof raw.agent_id === "string" && raw.agent_id ? raw.agent_id : null,
|
|
495
|
-
appId: typeof raw.app_id === "string" && raw.app_id ? raw.app_id : null,
|
|
496
|
-
autoCapture: coerceBool(raw.auto_capture, true),
|
|
497
|
-
autoRecall: coerceBool(raw.auto_recall, true),
|
|
498
|
-
warmupRecall: coerceBool(raw.warmup_recall, false),
|
|
499
|
-
recallKnowledge: coerceBool(raw.recall_knowledge, false),
|
|
500
|
-
topK: coerceInt(raw.top_k, DEFAULT_TOP_K),
|
|
501
|
-
threshold: coerceFloat(raw.threshold, DEFAULT_THRESHOLD),
|
|
502
|
-
knowledgeTopK: coerceInt(raw.knowledge_top_k, DEFAULT_KNOWLEDGE_TOP_K),
|
|
503
|
-
debug: debugConfigured,
|
|
504
|
-
debugConfigured,
|
|
505
|
-
debugForced: false,
|
|
506
|
-
debugReason: null,
|
|
507
|
-
kbCatalogInjection: coerceKbCatalogInjection(raw.kb_catalog_injection)
|
|
508
|
-
});
|
|
501
|
+
function envValue(env, name, platform) {
|
|
502
|
+
if (env[name] !== void 0) return env[name];
|
|
503
|
+
if (platform !== "win32") return void 0;
|
|
504
|
+
const entry = Object.entries(env).find(([key]) => key.toLowerCase() === name.toLowerCase());
|
|
505
|
+
return entry?.[1];
|
|
509
506
|
}
|
|
510
|
-
function
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
507
|
+
function resolveAgentExecutable(command, options = {}) {
|
|
508
|
+
const platform = options.platform ?? process.platform;
|
|
509
|
+
const env = options.env ?? process.env;
|
|
510
|
+
const isExecutable = options.isExecutable ?? isExecutableAdapter;
|
|
511
|
+
const pathValue = envValue(env, "PATH", platform) ?? "";
|
|
512
|
+
const pathSeparator = platform === "win32" ? ";" : delimiter;
|
|
513
|
+
const pathEntries = pathValue.split(pathSeparator).map((entry) => entry.trim().replace(/^"(.*)"$/, "$1")).filter(Boolean);
|
|
514
|
+
const suffixes = platform === "win32" ? (envValue(env, "PATHEXT", platform) ?? ".COM;.EXE;.BAT;.CMD").split(";").map((suffix) => suffix.trim()).filter(Boolean) : [""];
|
|
515
|
+
for (const directory of pathEntries) {
|
|
516
|
+
for (const suffix of suffixes) {
|
|
517
|
+
const candidate = join3(directory, `${command}${suffix}`);
|
|
518
|
+
if (isExecutable(candidate, platform)) return candidate;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return void 0;
|
|
517
522
|
}
|
|
518
|
-
function
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
if (env.CTXDB_USER_ID) cfg.userId = env.CTXDB_USER_ID;
|
|
522
|
-
if (env.CTXDB_AGENT_ID) cfg.agentId = env.CTXDB_AGENT_ID;
|
|
523
|
-
if (env.CTXDB_APP_ID) cfg.appId = env.CTXDB_APP_ID;
|
|
524
|
-
return applyDebugPolicy(cfg);
|
|
523
|
+
function displayHomePath(home, path) {
|
|
524
|
+
if (path === home) return "~";
|
|
525
|
+
return path.startsWith(`${home}${sep}`) ? `~${path.slice(home.length)}` : path;
|
|
525
526
|
}
|
|
526
|
-
function
|
|
527
|
-
const
|
|
527
|
+
function detectInstalledAgents(options = {}) {
|
|
528
|
+
const home = options.home ?? homedir2();
|
|
529
|
+
const platform = options.platform ?? process.platform;
|
|
528
530
|
const env = options.env ?? process.env;
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
} catch {
|
|
538
|
-
}
|
|
539
|
-
const cfg2 = configFromDisk({});
|
|
540
|
-
const managed2 = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
|
|
541
|
-
options.credentialsPath ?? join3(dirname3(path), "credentials.json")
|
|
542
|
-
);
|
|
543
|
-
if (managed2) {
|
|
544
|
-
cfg2.apiKey = managed2.payload.apiKey;
|
|
545
|
-
cfg2.baseUrl = managed2.payload.baseUrl;
|
|
531
|
+
const isDirectory = options.isDirectory ?? isDirectoryAdapter;
|
|
532
|
+
const observations = [];
|
|
533
|
+
for (const agent of SUPPORTED_AGENTS) {
|
|
534
|
+
const evidence = [];
|
|
535
|
+
for (const candidate of agentHomeDirs(agent, home)) {
|
|
536
|
+
if (isDirectory(candidate)) {
|
|
537
|
+
evidence.push({ kind: "home", value: displayHomePath(home, candidate) });
|
|
538
|
+
}
|
|
546
539
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
540
|
+
const executableName = EXECUTABLE_AGENT_NAMES[agent];
|
|
541
|
+
if (executableName) {
|
|
542
|
+
const executable = resolveAgentExecutable(executableName, {
|
|
543
|
+
platform,
|
|
544
|
+
env,
|
|
545
|
+
isExecutable: options.isExecutable
|
|
546
|
+
});
|
|
547
|
+
if (executable) {
|
|
548
|
+
evidence.push({ kind: "executable", value: executableName });
|
|
557
549
|
}
|
|
558
550
|
}
|
|
551
|
+
if (evidence.length === 0) {
|
|
552
|
+
observations.push({
|
|
553
|
+
agent,
|
|
554
|
+
selected: false,
|
|
555
|
+
reason: "not-detected",
|
|
556
|
+
evidence
|
|
557
|
+
});
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const support = agentPlatformSupport(agent, platform);
|
|
561
|
+
if (!support.supported) {
|
|
562
|
+
observations.push({
|
|
563
|
+
agent,
|
|
564
|
+
selected: false,
|
|
565
|
+
reason: "unsupported-platform",
|
|
566
|
+
evidence,
|
|
567
|
+
detail: support.detail
|
|
568
|
+
});
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
observations.push({ agent, selected: true, reason: "detected", evidence });
|
|
559
572
|
}
|
|
560
|
-
const cfg = configFromDisk(agentRaw);
|
|
561
|
-
const managed = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
|
|
562
|
-
options.credentialsPath ?? join3(dirname3(path), "credentials.json")
|
|
563
|
-
);
|
|
564
|
-
if (managed) {
|
|
565
|
-
cfg.apiKey = managed.payload.apiKey;
|
|
566
|
-
cfg.baseUrl = managed.payload.baseUrl;
|
|
567
|
-
}
|
|
568
|
-
return applyEnv(cfg, env);
|
|
569
|
-
}
|
|
570
|
-
function configToDisk(cfg) {
|
|
571
573
|
return {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
user_id: cfg.userId,
|
|
575
|
-
agent_id: cfg.agentId,
|
|
576
|
-
app_id: cfg.appId,
|
|
577
|
-
auto_capture: cfg.autoCapture,
|
|
578
|
-
auto_recall: cfg.autoRecall,
|
|
579
|
-
warmup_recall: cfg.warmupRecall,
|
|
580
|
-
recall_knowledge: cfg.recallKnowledge,
|
|
581
|
-
top_k: cfg.topK,
|
|
582
|
-
threshold: cfg.threshold,
|
|
583
|
-
knowledge_top_k: cfg.knowledgeTopK,
|
|
584
|
-
debug: cfg.debugConfigured,
|
|
585
|
-
kb_catalog_injection: cfg.kbCatalogInjection
|
|
574
|
+
selected: observations.filter((item) => item.selected).map((item) => item.agent),
|
|
575
|
+
observations
|
|
586
576
|
};
|
|
587
577
|
}
|
|
588
|
-
function
|
|
589
|
-
|
|
590
|
-
if (!existsSync4(target)) {
|
|
591
|
-
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
592
|
-
}
|
|
593
|
-
const raw = readRaw(target);
|
|
594
|
-
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
595
|
-
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
596
|
-
}
|
|
597
|
-
const agents = { ...raw.agents };
|
|
598
|
-
if (!(agent in agents)) {
|
|
599
|
-
return {
|
|
600
|
-
removed: false,
|
|
601
|
-
remainingAgents: Object.keys(agents),
|
|
602
|
-
fileDeleted: false
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
delete agents[agent];
|
|
606
|
-
const remaining = Object.keys(agents);
|
|
607
|
-
if (remaining.length === 0 && !options.keepEmptyShell) {
|
|
608
|
-
try {
|
|
609
|
-
unlinkSync3(target);
|
|
610
|
-
return { removed: true, remainingAgents: [], fileDeleted: true };
|
|
611
|
-
} catch {
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
const onDisk = { ...raw, version: 2, agents };
|
|
615
|
-
delete onDisk.default_agent;
|
|
616
|
-
secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
|
|
617
|
-
return { removed: true, remainingAgents: remaining, fileDeleted: false };
|
|
578
|
+
function agentFromEnv(env = process.env) {
|
|
579
|
+
return isAgentSlug(env.CTXDB_AGENT) ? env.CTXDB_AGENT : "default";
|
|
618
580
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
...validRaw,
|
|
629
|
-
version: 2,
|
|
630
|
-
agents: {
|
|
631
|
-
...existingAgents,
|
|
632
|
-
[agent]: serialized
|
|
581
|
+
function agentFromArgvWithFallback(argv = process.argv.slice(2), env = process.env) {
|
|
582
|
+
for (let i = 0; i < argv.length; i++) {
|
|
583
|
+
const tok = argv[i];
|
|
584
|
+
if (tok === "--agent" && isAgentSlug(argv[i + 1])) {
|
|
585
|
+
return { agent: argv[i + 1], fellBack: false };
|
|
586
|
+
}
|
|
587
|
+
if (tok.startsWith("--agent=")) {
|
|
588
|
+
const raw = tok.slice("--agent=".length);
|
|
589
|
+
if (isAgentSlug(raw)) return { agent: raw, fellBack: false };
|
|
633
590
|
}
|
|
634
|
-
};
|
|
635
|
-
delete onDisk.default_agent;
|
|
636
|
-
secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
|
|
637
|
-
}
|
|
638
|
-
function configuredAgents(path) {
|
|
639
|
-
const target = path ?? defaultConfigPath();
|
|
640
|
-
const raw = readRaw(target);
|
|
641
|
-
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object") return [];
|
|
642
|
-
return Object.keys(raw.agents).filter(isAgent);
|
|
643
|
-
}
|
|
644
|
-
function hasConfiguredAgent(agent, path) {
|
|
645
|
-
const raw = readRaw(path ?? defaultConfigPath());
|
|
646
|
-
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
647
|
-
return false;
|
|
648
591
|
}
|
|
649
|
-
|
|
650
|
-
return Boolean(
|
|
651
|
-
profile && typeof profile === "object" && !Array.isArray(profile)
|
|
652
|
-
);
|
|
592
|
+
return { agent: agentFromEnv(env), fellBack: true };
|
|
653
593
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
594
|
+
|
|
595
|
+
// src/lib/config.ts
|
|
596
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
597
|
+
import { homedir as homedir4 } from "os";
|
|
598
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
599
|
+
|
|
600
|
+
// src/lib/secure-file.ts
|
|
601
|
+
import {
|
|
602
|
+
chmodSync,
|
|
603
|
+
closeSync,
|
|
604
|
+
copyFileSync,
|
|
605
|
+
existsSync as existsSync2,
|
|
606
|
+
fsyncSync,
|
|
607
|
+
mkdirSync as mkdirSync2,
|
|
608
|
+
openSync,
|
|
609
|
+
renameSync,
|
|
610
|
+
unlinkSync,
|
|
611
|
+
writeFileSync
|
|
612
|
+
} from "fs";
|
|
613
|
+
import { randomBytes } from "crypto";
|
|
614
|
+
import { dirname as dirname3 } from "path";
|
|
615
|
+
var nodeSecureFileSystem = {
|
|
616
|
+
exists: existsSync2,
|
|
617
|
+
mkdir: (path, options) => {
|
|
618
|
+
mkdirSync2(path, options);
|
|
619
|
+
},
|
|
620
|
+
chmod: chmodSync,
|
|
621
|
+
open: openSync,
|
|
622
|
+
write: (fd, data) => {
|
|
623
|
+
writeFileSync(fd, data);
|
|
624
|
+
},
|
|
625
|
+
fsync: fsyncSync,
|
|
626
|
+
close: closeSync,
|
|
627
|
+
copy: copyFileSync,
|
|
628
|
+
rename: renameSync,
|
|
629
|
+
unlink: unlinkSync
|
|
630
|
+
};
|
|
631
|
+
function configBackupPath(target) {
|
|
632
|
+
return `${target}.bak`;
|
|
671
633
|
}
|
|
672
|
-
function
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
const
|
|
676
|
-
|
|
634
|
+
function secureAtomicWrite(target, content, options = {}) {
|
|
635
|
+
const fs = options.fs ?? nodeSecureFileSystem;
|
|
636
|
+
const platform = options.platform ?? process.platform;
|
|
637
|
+
const posix = platform !== "win32";
|
|
638
|
+
const dir = dirname3(target);
|
|
639
|
+
const backup = configBackupPath(target);
|
|
640
|
+
const suffix = options.tempSuffix ?? `${process.pid}.${randomBytes(6).toString("hex")}`;
|
|
641
|
+
const temp = `${target}.${suffix}.tmp`;
|
|
642
|
+
const hadTarget = fs.exists(target);
|
|
643
|
+
fs.mkdir(dir, { recursive: true, mode: 448 });
|
|
644
|
+
if (posix) {
|
|
645
|
+
fs.chmod(dir, 448);
|
|
646
|
+
if (hadTarget) fs.chmod(target, 384);
|
|
647
|
+
}
|
|
648
|
+
let fd = null;
|
|
649
|
+
let tempExists = false;
|
|
650
|
+
try {
|
|
651
|
+
fd = fs.open(temp, "wx", 384);
|
|
652
|
+
tempExists = true;
|
|
653
|
+
fs.write(fd, Buffer.from(content, "utf-8"));
|
|
654
|
+
fs.fsync(fd);
|
|
655
|
+
fs.close(fd);
|
|
656
|
+
fd = null;
|
|
657
|
+
if (posix) fs.chmod(temp, 384);
|
|
658
|
+
if (hadTarget && options.backup !== false) {
|
|
659
|
+
if (platform === "win32") {
|
|
660
|
+
if (fs.exists(backup)) fs.unlink(backup);
|
|
661
|
+
fs.rename(target, backup);
|
|
662
|
+
try {
|
|
663
|
+
fs.rename(temp, target);
|
|
664
|
+
tempExists = false;
|
|
665
|
+
} catch (error) {
|
|
666
|
+
fs.rename(backup, target);
|
|
667
|
+
throw error;
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
fs.copy(target, backup);
|
|
671
|
+
fs.chmod(backup, 384);
|
|
672
|
+
fs.rename(temp, target);
|
|
673
|
+
tempExists = false;
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
fs.rename(temp, target);
|
|
677
|
+
tempExists = false;
|
|
678
|
+
}
|
|
679
|
+
} finally {
|
|
680
|
+
if (fd !== null) {
|
|
681
|
+
try {
|
|
682
|
+
fs.close(fd);
|
|
683
|
+
} catch {
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (tempExists && fs.exists(temp)) {
|
|
687
|
+
try {
|
|
688
|
+
fs.unlink(temp);
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
677
693
|
}
|
|
678
694
|
|
|
679
|
-
// src/lib/
|
|
680
|
-
import {
|
|
681
|
-
|
|
695
|
+
// src/lib/secrets.ts
|
|
696
|
+
import { createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
697
|
+
var PROCESS_FINGERPRINT_KEY = randomBytes2(32);
|
|
698
|
+
|
|
699
|
+
// src/credentials/local-credential-provider.ts
|
|
700
|
+
import {
|
|
701
|
+
closeSync as closeSync2,
|
|
702
|
+
existsSync as existsSync3,
|
|
703
|
+
mkdirSync as mkdirSync3,
|
|
704
|
+
openSync as openSync2,
|
|
705
|
+
readFileSync as readFileSync2,
|
|
706
|
+
statSync as statSync2,
|
|
707
|
+
unlinkSync as unlinkSync2,
|
|
708
|
+
writeFileSync as writeFileSync2
|
|
709
|
+
} from "fs";
|
|
710
|
+
import { homedir as homedir3 } from "os";
|
|
682
711
|
import { dirname as dirname4, join as join4 } from "path";
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
var
|
|
687
|
-
|
|
688
|
-
|
|
712
|
+
import { setTimeout as delay } from "timers/promises";
|
|
713
|
+
|
|
714
|
+
// src/credentials/types.ts
|
|
715
|
+
var ACTIVE_CONTEXTDB_CREDENTIAL = "contextdb/active";
|
|
716
|
+
|
|
717
|
+
// src/credentials/local-credential-provider.ts
|
|
718
|
+
var DOCUMENT_VERSION = 1;
|
|
719
|
+
var STALE_LOCK_MS = 10 * 60 * 1e3;
|
|
720
|
+
function defaultCredentialsPath() {
|
|
721
|
+
return join4(homedir3(), ".ctxdb", "credentials.json");
|
|
689
722
|
}
|
|
690
|
-
function
|
|
691
|
-
return
|
|
723
|
+
function emptyDocument() {
|
|
724
|
+
return { version: DOCUMENT_VERSION, records: {} };
|
|
692
725
|
}
|
|
693
|
-
function
|
|
694
|
-
if (
|
|
695
|
-
|
|
696
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
697
|
-
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
698
|
-
const tz = -now.getTimezoneOffset();
|
|
699
|
-
const tzSign = tz >= 0 ? "+" : "-";
|
|
700
|
-
const tzH = pad(Math.floor(Math.abs(tz) / 60));
|
|
701
|
-
const tzM = pad(Math.abs(tz) % 60);
|
|
702
|
-
const ts = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${ms}${tzSign}${tzH}:${tzM}`;
|
|
703
|
-
let line = `${ts} [${tag}] ${msg}`;
|
|
704
|
-
if (data !== void 0) {
|
|
705
|
-
const s = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
706
|
-
line += `
|
|
707
|
-
${s}`;
|
|
726
|
+
function normalizeOrigin(value, field) {
|
|
727
|
+
if (typeof value !== "string" || !value) {
|
|
728
|
+
throw new Error(`credentials: ${field} must be a non-empty URL`);
|
|
708
729
|
}
|
|
709
|
-
|
|
710
|
-
const p = logPath();
|
|
730
|
+
let url;
|
|
711
731
|
try {
|
|
712
|
-
|
|
713
|
-
appendFileSync(p, line, "utf-8");
|
|
732
|
+
url = new URL(value);
|
|
714
733
|
} catch {
|
|
734
|
+
throw new Error(`credentials: ${field} must be a valid URL`);
|
|
715
735
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
// src/lib/package-version.ts
|
|
719
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
720
|
-
import { dirname as dirname5, join as join5 } from "path";
|
|
721
|
-
import { fileURLToPath } from "url";
|
|
722
|
-
var PACKAGE_NAMES = /* @__PURE__ */ new Set([
|
|
723
|
-
"@aliyunrds/ctxdb",
|
|
724
|
-
"@ali/ctxdb-internal"
|
|
725
|
-
]);
|
|
726
|
-
function findPackageIdentity() {
|
|
727
|
-
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
728
|
-
for (let i = 0; i < 5; i++) {
|
|
729
|
-
try {
|
|
730
|
-
const pkg = JSON.parse(readFileSync3(join5(dir, "package.json"), "utf-8"));
|
|
731
|
-
if (PACKAGE_NAMES.has(pkg.name) && typeof pkg.version === "string") {
|
|
732
|
-
return { name: pkg.name, version: pkg.version };
|
|
733
|
-
}
|
|
734
|
-
} catch {
|
|
735
|
-
}
|
|
736
|
-
dir = dirname5(dir);
|
|
736
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
737
|
+
throw new Error(`credentials: ${field} must be an HTTP(S) origin`);
|
|
737
738
|
}
|
|
738
|
-
return
|
|
739
|
+
return url.toString().replace(/\/$/, "");
|
|
739
740
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
// src/lib/http-client.ts
|
|
745
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
746
|
-
var CtxdbError = class extends Error {
|
|
747
|
-
constructor(message) {
|
|
748
|
-
super(message);
|
|
749
|
-
this.name = "CtxdbError";
|
|
750
|
-
}
|
|
751
|
-
};
|
|
752
|
-
var AuthError = class extends CtxdbError {
|
|
753
|
-
status = 401;
|
|
754
|
-
errorCode;
|
|
755
|
-
errorMessage;
|
|
756
|
-
data;
|
|
757
|
-
responseBody;
|
|
758
|
-
constructor(message = "Unauthorized (HTTP 401) \u2014 check api_key", fields = {}) {
|
|
759
|
-
super(message);
|
|
760
|
-
this.name = "AuthError";
|
|
761
|
-
this.errorCode = fields.errorCode;
|
|
762
|
-
this.errorMessage = fields.errorMessage;
|
|
763
|
-
this.data = fields.data;
|
|
764
|
-
this.responseBody = fields.responseBody;
|
|
741
|
+
function parseRecord(value) {
|
|
742
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
743
|
+
throw new Error("credentials: record must be an object");
|
|
765
744
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
path;
|
|
770
|
-
errorCode;
|
|
771
|
-
errorMessage;
|
|
772
|
-
data;
|
|
773
|
-
responseBody;
|
|
774
|
-
constructor(path, fields = {}) {
|
|
775
|
-
super(`Not found: ${path}`);
|
|
776
|
-
this.name = "NotFoundError";
|
|
777
|
-
this.path = path;
|
|
778
|
-
this.errorCode = fields.errorCode;
|
|
779
|
-
this.errorMessage = fields.errorMessage;
|
|
780
|
-
this.data = fields.data;
|
|
781
|
-
this.responseBody = fields.responseBody;
|
|
745
|
+
const raw = value;
|
|
746
|
+
if (raw.kind !== "api-key") {
|
|
747
|
+
throw new Error(`credentials: unsupported record kind ${String(raw.kind)}`);
|
|
782
748
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
detail;
|
|
788
|
-
errorCode;
|
|
789
|
-
errorMessage;
|
|
790
|
-
data;
|
|
791
|
-
responseBody;
|
|
792
|
-
constructor(path, detail, fields = {}) {
|
|
793
|
-
super(`API error at ${path}: ${detail}`);
|
|
794
|
-
this.name = "APIError";
|
|
795
|
-
this.path = path;
|
|
796
|
-
this.detail = detail;
|
|
797
|
-
this.errorCode = fields.errorCode;
|
|
798
|
-
this.errorMessage = fields.errorMessage;
|
|
799
|
-
this.data = fields.data;
|
|
800
|
-
this.responseBody = fields.responseBody;
|
|
749
|
+
const payload = raw.payload;
|
|
750
|
+
const metadata = raw.metadata;
|
|
751
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
752
|
+
throw new Error("credentials: api-key payload must be an object");
|
|
801
753
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
status;
|
|
805
|
-
detail;
|
|
806
|
-
errorCode;
|
|
807
|
-
errorMessage;
|
|
808
|
-
data;
|
|
809
|
-
responseBody;
|
|
810
|
-
constructor(status, detail, fields = {}) {
|
|
811
|
-
super(`HTTP ${status}: ${detail}`);
|
|
812
|
-
this.name = "CtxdbHttpError";
|
|
813
|
-
this.status = status;
|
|
814
|
-
this.detail = detail;
|
|
815
|
-
this.errorCode = fields.errorCode;
|
|
816
|
-
this.errorMessage = fields.errorMessage;
|
|
817
|
-
this.data = fields.data;
|
|
818
|
-
this.responseBody = fields.responseBody;
|
|
754
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
755
|
+
throw new Error("credentials: api-key metadata must be an object");
|
|
819
756
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
757
|
+
const body = payload;
|
|
758
|
+
const meta = metadata;
|
|
759
|
+
if (typeof body.api_key !== "string" || !body.api_key) {
|
|
760
|
+
throw new Error("credentials: api_key must be a non-empty string");
|
|
761
|
+
}
|
|
762
|
+
if (meta.authorization_method !== "browser-loopback" && meta.authorization_method !== "device-code") {
|
|
763
|
+
throw new Error("credentials: authorization_method is invalid");
|
|
764
|
+
}
|
|
765
|
+
if (typeof meta.issued_at !== "string" || !Number.isFinite(Date.parse(meta.issued_at))) {
|
|
766
|
+
throw new Error("credentials: issued_at is invalid");
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
kind: "api-key",
|
|
770
|
+
payload: {
|
|
771
|
+
apiKey: body.api_key,
|
|
772
|
+
baseUrl: normalizeOrigin(body.base_url, "base_url"),
|
|
773
|
+
loginServer: normalizeOrigin(body.login_server, "login_server")
|
|
774
|
+
},
|
|
775
|
+
metadata: {
|
|
776
|
+
authorizationMethod: meta.authorization_method,
|
|
777
|
+
issuedAt: meta.issued_at
|
|
833
778
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function parseDocument(text) {
|
|
782
|
+
let raw;
|
|
783
|
+
try {
|
|
784
|
+
raw = JSON.parse(text);
|
|
785
|
+
} catch {
|
|
786
|
+
throw new Error("credentials: credentials.json is not valid JSON");
|
|
841
787
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
Authorization: `${authorization.scheme} ${authorization.value}`,
|
|
845
|
-
"User-Agent": this.userAgent,
|
|
846
|
-
Connection: "close",
|
|
847
|
-
...this.extraHeaders,
|
|
848
|
-
...requestHeaders
|
|
849
|
-
};
|
|
850
|
-
if (contentType) h["Content-Type"] = contentType;
|
|
851
|
-
return h;
|
|
788
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
789
|
+
throw new Error("credentials: document must be an object");
|
|
852
790
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
791
|
+
const document = raw;
|
|
792
|
+
if (document.version !== DOCUMENT_VERSION) {
|
|
793
|
+
throw new Error(`credentials: unsupported document version ${String(document.version)}`);
|
|
794
|
+
}
|
|
795
|
+
if (!document.records || typeof document.records !== "object" || Array.isArray(document.records)) {
|
|
796
|
+
throw new Error("credentials: records must be an object");
|
|
797
|
+
}
|
|
798
|
+
const records = document.records;
|
|
799
|
+
const unknown = Object.keys(records).filter((key) => key !== ACTIVE_CONTEXTDB_CREDENTIAL);
|
|
800
|
+
if (unknown.length > 0) {
|
|
801
|
+
throw new Error(`credentials: unsupported record key ${unknown[0]}`);
|
|
802
|
+
}
|
|
803
|
+
const active = records[ACTIVE_CONTEXTDB_CREDENTIAL];
|
|
804
|
+
return {
|
|
805
|
+
version: DOCUMENT_VERSION,
|
|
806
|
+
records: active === void 0 ? {} : { [ACTIVE_CONTEXTDB_CREDENTIAL]: parseRecord(active) }
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function assertOwnerOnly(path) {
|
|
810
|
+
if (process.platform === "win32" || !existsSync3(path)) return;
|
|
811
|
+
const mode = statSync2(path).mode & 511;
|
|
812
|
+
if ((mode & 63) !== 0) {
|
|
813
|
+
throw new Error(`credentials: ${path} must be owner-only (run chmod 600)`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function readDocument(path) {
|
|
817
|
+
if (!existsSync3(path)) return emptyDocument();
|
|
818
|
+
assertOwnerOnly(path);
|
|
819
|
+
return parseDocument(readFileSync2(path, "utf8"));
|
|
820
|
+
}
|
|
821
|
+
function readActiveCredentialSync(path = defaultCredentialsPath()) {
|
|
822
|
+
return readDocument(path).records[ACTIVE_CONTEXTDB_CREDENTIAL];
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/lib/config.ts
|
|
826
|
+
import {
|
|
827
|
+
resolveDebugPolicy
|
|
828
|
+
} from "@aliyunrds/ctxdb-shared";
|
|
829
|
+
function defaultConfigPath() {
|
|
830
|
+
return join5(homedir4(), ".ctxdb", "ctxdb.json");
|
|
831
|
+
}
|
|
832
|
+
var DEFAULT_CONFIG_PATH = join5(homedir4(), ".ctxdb", "ctxdb.json");
|
|
833
|
+
function configDir() {
|
|
834
|
+
return join5(homedir4(), ".ctxdb");
|
|
835
|
+
}
|
|
836
|
+
var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
|
|
837
|
+
var DEFAULT_USER_ID = "default";
|
|
838
|
+
var DEFAULT_TOP_K = 5;
|
|
839
|
+
var DEFAULT_THRESHOLD = 0.4;
|
|
840
|
+
var DEFAULT_KNOWLEDGE_TOP_K = 6;
|
|
841
|
+
var DEFAULT_KB_CATALOG_INJECTION = "session_start";
|
|
842
|
+
function resolveConfigAgent(options = {}, preloadedRaw) {
|
|
843
|
+
if (isAgentSlug(options.agent)) return options.agent;
|
|
844
|
+
return agentFromEnv(options.env);
|
|
845
|
+
}
|
|
846
|
+
function coerceInt(v, fallback) {
|
|
847
|
+
if (v === null || v === void 0 || v === "") return fallback;
|
|
848
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
849
|
+
return Number.isFinite(n) ? Math.trunc(n) : fallback;
|
|
850
|
+
}
|
|
851
|
+
function coerceFloat(v, fallback) {
|
|
852
|
+
if (v === null || v === void 0 || v === "") return fallback;
|
|
853
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
854
|
+
return Number.isFinite(n) ? n : fallback;
|
|
855
|
+
}
|
|
856
|
+
function coerceBool(v, fallback) {
|
|
857
|
+
if (typeof v === "boolean") return v;
|
|
858
|
+
if (v === void 0 || v === null) return fallback;
|
|
859
|
+
return Boolean(v);
|
|
860
|
+
}
|
|
861
|
+
function coerceKbCatalogInjection(v) {
|
|
862
|
+
if (v === "session_start" || v === "user_prompt_submit" || v === "off") {
|
|
863
|
+
return v;
|
|
864
|
+
}
|
|
865
|
+
return DEFAULT_KB_CATALOG_INJECTION;
|
|
866
|
+
}
|
|
867
|
+
function readRaw(path) {
|
|
868
|
+
if (!existsSync4(path)) return {};
|
|
869
|
+
try {
|
|
870
|
+
const parsed = JSON.parse(readFileSync3(path, "utf-8"));
|
|
871
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
872
|
+
return parsed;
|
|
868
873
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
return {};
|
|
877
|
+
}
|
|
878
|
+
function agentRawFromFile(raw, agent) {
|
|
879
|
+
const agents = raw.agents;
|
|
880
|
+
if (agents && typeof agents === "object" && !Array.isArray(agents)) {
|
|
881
|
+
const candidate = agents[agent];
|
|
882
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
883
|
+
return candidate;
|
|
875
884
|
}
|
|
876
|
-
|
|
877
|
-
|
|
885
|
+
}
|
|
886
|
+
return {};
|
|
887
|
+
}
|
|
888
|
+
function isV2Schema(raw) {
|
|
889
|
+
if (Object.keys(raw).length === 0) return true;
|
|
890
|
+
return raw.version === 2 && raw.agents !== null && typeof raw.agents === "object" && !Array.isArray(raw.agents);
|
|
891
|
+
}
|
|
892
|
+
function configFromDisk(raw) {
|
|
893
|
+
const debugConfigured = coerceBool(raw.debug, false);
|
|
894
|
+
return applyDebugPolicy({
|
|
895
|
+
apiKey: typeof raw.api_key === "string" && raw.api_key ? raw.api_key : null,
|
|
896
|
+
baseUrl: typeof raw.base_url === "string" && raw.base_url ? String(raw.base_url).replace(/\/+$/, "") : DEFAULT_BASE_URL,
|
|
897
|
+
userId: typeof raw.user_id === "string" && raw.user_id ? raw.user_id : DEFAULT_USER_ID,
|
|
898
|
+
agentId: typeof raw.agent_id === "string" && raw.agent_id ? raw.agent_id : null,
|
|
899
|
+
appId: typeof raw.app_id === "string" && raw.app_id ? raw.app_id : null,
|
|
900
|
+
autoCapture: coerceBool(raw.auto_capture, true),
|
|
901
|
+
autoRecall: coerceBool(raw.auto_recall, true),
|
|
902
|
+
warmupRecall: coerceBool(raw.warmup_recall, false),
|
|
903
|
+
recallKnowledge: coerceBool(raw.recall_knowledge, false),
|
|
904
|
+
topK: coerceInt(raw.top_k, DEFAULT_TOP_K),
|
|
905
|
+
threshold: coerceFloat(raw.threshold, DEFAULT_THRESHOLD),
|
|
906
|
+
knowledgeTopK: coerceInt(raw.knowledge_top_k, DEFAULT_KNOWLEDGE_TOP_K),
|
|
907
|
+
debug: debugConfigured,
|
|
908
|
+
debugConfigured,
|
|
909
|
+
debugForced: false,
|
|
910
|
+
debugReason: null,
|
|
911
|
+
kbCatalogInjection: coerceKbCatalogInjection(raw.kb_catalog_injection)
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
function applyDebugPolicy(cfg) {
|
|
915
|
+
const policy = resolveDebugPolicy(cfg.debugConfigured, cfg.baseUrl);
|
|
916
|
+
cfg.debug = policy.debug;
|
|
917
|
+
cfg.debugConfigured = policy.debugConfigured;
|
|
918
|
+
cfg.debugForced = policy.debugForced;
|
|
919
|
+
cfg.debugReason = policy.debugReason;
|
|
920
|
+
return cfg;
|
|
921
|
+
}
|
|
922
|
+
function applyEnv(cfg, env) {
|
|
923
|
+
if (env.CTXDB_API_KEY) cfg.apiKey = env.CTXDB_API_KEY;
|
|
924
|
+
if (env.CTXDB_BASE_URL) cfg.baseUrl = env.CTXDB_BASE_URL.replace(/\/+$/, "");
|
|
925
|
+
if (env.CTXDB_USER_ID) cfg.userId = env.CTXDB_USER_ID;
|
|
926
|
+
if (env.CTXDB_AGENT_ID) cfg.agentId = env.CTXDB_AGENT_ID;
|
|
927
|
+
if (env.CTXDB_APP_ID) cfg.appId = env.CTXDB_APP_ID;
|
|
928
|
+
return applyDebugPolicy(cfg);
|
|
929
|
+
}
|
|
930
|
+
function load(options = {}) {
|
|
931
|
+
const path = options.path ?? defaultConfigPath();
|
|
932
|
+
const env = options.env ?? process.env;
|
|
933
|
+
const raw = readRaw(path);
|
|
934
|
+
const agent = resolveConfigAgent({ ...options, path, env }, raw);
|
|
935
|
+
if (!isV2Schema(raw)) {
|
|
878
936
|
try {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (
|
|
901
|
-
|
|
902
|
-
}
|
|
903
|
-
let text;
|
|
904
|
-
try {
|
|
905
|
-
text = await resp.text();
|
|
906
|
-
} catch (err) {
|
|
907
|
-
if (err?.name === "AbortError") {
|
|
908
|
-
if (dbg) debug("http", `\u2717 ${method} ${path} body-read timeout after ${Date.now() - t0}ms`);
|
|
909
|
-
throw new CtxdbError(`request timeout after ${effectiveTimeout}ms (body read): ${url}`);
|
|
910
|
-
}
|
|
911
|
-
if (dbg) debug("http", `\u2717 ${method} ${path} body-read error after ${Date.now() - t0}ms: ${err?.message ?? err}`);
|
|
912
|
-
throw new CtxdbError(`network error reading body from ${url}: ${err?.message ?? err}`);
|
|
913
|
-
}
|
|
914
|
-
if (!resp.ok) {
|
|
915
|
-
const parsedError = parseErrorResponse(text, `HTTP ${resp.status}`);
|
|
916
|
-
if (dbg) debug("http", `\u2190 ${method} ${path} ${resp.status} (${Date.now() - t0}ms) ${parsedError.detail}`);
|
|
917
|
-
if (resp.status === 401) {
|
|
918
|
-
throw new AuthError(void 0, parsedError);
|
|
919
|
-
}
|
|
920
|
-
if (resp.status === 404) {
|
|
921
|
-
throw new NotFoundError(path, parsedError);
|
|
922
|
-
}
|
|
923
|
-
if (resp.status === 400) {
|
|
924
|
-
throw new APIError(path, parsedError.detail, parsedError);
|
|
937
|
+
process.stderr.write(
|
|
938
|
+
`[ctxdb] config schema invalid at ${path}, treating as first-time install
|
|
939
|
+
`
|
|
940
|
+
);
|
|
941
|
+
} catch {
|
|
942
|
+
}
|
|
943
|
+
const cfg2 = configFromDisk({});
|
|
944
|
+
const managed2 = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
|
|
945
|
+
options.credentialsPath ?? join5(dirname5(path), "credentials.json")
|
|
946
|
+
);
|
|
947
|
+
if (managed2) {
|
|
948
|
+
cfg2.apiKey = managed2.payload.apiKey;
|
|
949
|
+
cfg2.baseUrl = managed2.payload.baseUrl;
|
|
950
|
+
}
|
|
951
|
+
return applyEnv(cfg2, env);
|
|
952
|
+
}
|
|
953
|
+
const agentRaw = agentRawFromFile(raw, agent);
|
|
954
|
+
if (agent !== "default") {
|
|
955
|
+
const defaultRaw = agentRawFromFile(raw, "default");
|
|
956
|
+
if (Object.keys(defaultRaw).length > 0) {
|
|
957
|
+
for (const [k, v] of Object.entries(defaultRaw)) {
|
|
958
|
+
if (!(k in agentRaw)) {
|
|
959
|
+
agentRaw[k] = v;
|
|
925
960
|
}
|
|
926
|
-
throw new CtxdbHttpError(resp.status, parsedError.detail, parsedError);
|
|
927
961
|
}
|
|
928
|
-
if (dbg) debug("http", `\u2190 ${method} ${path} ${resp.status} (${Date.now() - t0}ms) body=${text.length}B`);
|
|
929
|
-
if (!text) return {};
|
|
930
|
-
return maybeJson(text);
|
|
931
|
-
} finally {
|
|
932
|
-
clearTimeout(timer);
|
|
933
962
|
}
|
|
934
963
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
contentType: "application/json",
|
|
943
|
-
params,
|
|
944
|
-
timeoutMs: options.timeoutMs,
|
|
945
|
-
headers: options.headers
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
putJson(path, body, params, options = {}) {
|
|
949
|
-
return this.doRequest("PUT", path, {
|
|
950
|
-
body: JSON.stringify(body),
|
|
951
|
-
contentType: "application/json",
|
|
952
|
-
params,
|
|
953
|
-
timeoutMs: options.timeoutMs,
|
|
954
|
-
headers: options.headers
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
patchJson(path, body, params, options = {}) {
|
|
958
|
-
return this.doRequest("PATCH", path, {
|
|
959
|
-
body: JSON.stringify(body),
|
|
960
|
-
contentType: "application/json",
|
|
961
|
-
params,
|
|
962
|
-
timeoutMs: options.timeoutMs,
|
|
963
|
-
headers: options.headers
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
delete(path, params) {
|
|
967
|
-
return this.doRequest("DELETE", path, { params });
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* DELETE with a JSON body. ContextDB batch deletes use the
|
|
971
|
-
* `DELETE /v1/{resource}/batch` shape with an `{"ids": [...]}` body
|
|
972
|
-
* (server contract) — the plain `delete()` above only supports query
|
|
973
|
-
* params.
|
|
974
|
-
*/
|
|
975
|
-
deleteJson(path, body, params, options = {}) {
|
|
976
|
-
return this.doRequest("DELETE", path, {
|
|
977
|
-
body: JSON.stringify(body),
|
|
978
|
-
contentType: "application/json",
|
|
979
|
-
params,
|
|
980
|
-
timeoutMs: options.timeoutMs,
|
|
981
|
-
headers: options.headers
|
|
982
|
-
});
|
|
964
|
+
const cfg = configFromDisk(agentRaw);
|
|
965
|
+
const managed = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
|
|
966
|
+
options.credentialsPath ?? join5(dirname5(path), "credentials.json")
|
|
967
|
+
);
|
|
968
|
+
if (managed) {
|
|
969
|
+
cfg.apiKey = managed.payload.apiKey;
|
|
970
|
+
cfg.baseUrl = managed.payload.baseUrl;
|
|
983
971
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
972
|
+
return applyEnv(cfg, env);
|
|
973
|
+
}
|
|
974
|
+
function configToDisk(cfg) {
|
|
975
|
+
return {
|
|
976
|
+
api_key: cfg.apiKey,
|
|
977
|
+
base_url: cfg.baseUrl,
|
|
978
|
+
user_id: cfg.userId,
|
|
979
|
+
agent_id: cfg.agentId,
|
|
980
|
+
app_id: cfg.appId,
|
|
981
|
+
auto_capture: cfg.autoCapture,
|
|
982
|
+
auto_recall: cfg.autoRecall,
|
|
983
|
+
warmup_recall: cfg.warmupRecall,
|
|
984
|
+
recall_knowledge: cfg.recallKnowledge,
|
|
985
|
+
top_k: cfg.topK,
|
|
986
|
+
threshold: cfg.threshold,
|
|
987
|
+
knowledge_top_k: cfg.knowledgeTopK,
|
|
988
|
+
debug: cfg.debugConfigured,
|
|
989
|
+
kb_catalog_injection: cfg.kbCatalogInjection
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function removeAgent(agent, path, options = {}) {
|
|
993
|
+
const target = path ?? defaultConfigPath();
|
|
994
|
+
if (!existsSync4(target)) {
|
|
995
|
+
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
1000
996
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
return this.doRequest("PUT", path, {
|
|
1005
|
-
body: fd,
|
|
1006
|
-
params: options.params,
|
|
1007
|
-
timeoutMs: options.timeoutMs,
|
|
1008
|
-
headers: options.headers
|
|
1009
|
-
});
|
|
997
|
+
const raw = readRaw(target);
|
|
998
|
+
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
999
|
+
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
1010
1000
|
}
|
|
1011
|
-
};
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1001
|
+
const agents = { ...raw.agents };
|
|
1002
|
+
if (!(agent in agents)) {
|
|
1003
|
+
return {
|
|
1004
|
+
removed: false,
|
|
1005
|
+
remainingAgents: Object.keys(agents),
|
|
1006
|
+
fileDeleted: false
|
|
1007
|
+
};
|
|
1016
1008
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1009
|
+
delete agents[agent];
|
|
1010
|
+
const remaining = Object.keys(agents);
|
|
1011
|
+
if (remaining.length === 0 && !options.keepEmptyShell) {
|
|
1012
|
+
try {
|
|
1013
|
+
unlinkSync3(target);
|
|
1014
|
+
return { removed: true, remainingAgents: [], fileDeleted: true };
|
|
1015
|
+
} catch {
|
|
1016
|
+
}
|
|
1020
1017
|
}
|
|
1021
|
-
|
|
1018
|
+
const onDisk = { ...raw, version: 2, agents };
|
|
1019
|
+
delete onDisk.default_agent;
|
|
1020
|
+
secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
|
|
1021
|
+
return { removed: true, remainingAgents: remaining, fileDeleted: false };
|
|
1022
1022
|
}
|
|
1023
|
-
function
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1023
|
+
function save(cfg, path, options = {}) {
|
|
1024
|
+
const target = path ?? defaultConfigPath();
|
|
1025
|
+
const agent = resolveConfigAgent(options);
|
|
1026
|
+
const raw = readRaw(target);
|
|
1027
|
+
const validRaw = isV2Schema(raw) ? raw : {};
|
|
1028
|
+
const existingAgents = validRaw.agents && typeof validRaw.agents === "object" && !Array.isArray(validRaw.agents) ? { ...validRaw.agents } : {};
|
|
1029
|
+
const serialized = configToDisk(cfg);
|
|
1030
|
+
if (options.omitApiKey) delete serialized.api_key;
|
|
1031
|
+
const onDisk = {
|
|
1032
|
+
...validRaw,
|
|
1033
|
+
version: 2,
|
|
1034
|
+
agents: {
|
|
1035
|
+
...existingAgents,
|
|
1036
|
+
[agent]: serialized
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
delete onDisk.default_agent;
|
|
1040
|
+
secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
|
|
1041
|
+
}
|
|
1042
|
+
function configuredAgents(path) {
|
|
1043
|
+
const target = path ?? defaultConfigPath();
|
|
1044
|
+
const raw = readRaw(target);
|
|
1045
|
+
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object") return [];
|
|
1046
|
+
return Object.keys(raw.agents).filter(isAgent);
|
|
1047
|
+
}
|
|
1048
|
+
function hasConfiguredAgent(agent, path) {
|
|
1049
|
+
const raw = readRaw(path ?? defaultConfigPath());
|
|
1050
|
+
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
1051
|
+
return false;
|
|
1028
1052
|
}
|
|
1053
|
+
const profile = raw.agents[agent];
|
|
1054
|
+
return Boolean(
|
|
1055
|
+
profile && typeof profile === "object" && !Array.isArray(profile)
|
|
1056
|
+
);
|
|
1029
1057
|
}
|
|
1030
|
-
function
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
} catch {
|
|
1036
|
-
return { detail: text || fallback, responseBody: text || void 0 };
|
|
1058
|
+
function updateConfiguredDebug(agent, configured, path) {
|
|
1059
|
+
const target = path ?? defaultConfigPath();
|
|
1060
|
+
const raw = readRaw(target);
|
|
1061
|
+
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
|
|
1062
|
+
return false;
|
|
1037
1063
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const v = obj[k];
|
|
1043
|
-
if (typeof v === "string" && v) {
|
|
1044
|
-
detail = v;
|
|
1045
|
-
break;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
const rawCode = obj.errorCode;
|
|
1049
|
-
const numericCode = typeof rawCode === "number" ? rawCode : typeof rawCode === "string" && rawCode.trim() !== "" ? Number(rawCode) : void 0;
|
|
1050
|
-
const errorCode = typeof numericCode === "number" && Number.isFinite(numericCode) ? numericCode : void 0;
|
|
1051
|
-
const errorMessage = typeof obj.errorMessage === "string" && obj.errorMessage ? obj.errorMessage : void 0;
|
|
1052
|
-
return {
|
|
1053
|
-
detail: detail || JSON.stringify(parsed),
|
|
1054
|
-
errorCode,
|
|
1055
|
-
errorMessage,
|
|
1056
|
-
data: Object.hasOwn(obj, "data") ? obj.data : void 0,
|
|
1057
|
-
responseBody: parsed
|
|
1058
|
-
};
|
|
1064
|
+
const agents = { ...raw.agents };
|
|
1065
|
+
const profile = agents[agent];
|
|
1066
|
+
if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
|
|
1067
|
+
return false;
|
|
1059
1068
|
}
|
|
1060
|
-
|
|
1069
|
+
agents[agent] = { ...profile, debug: configured };
|
|
1070
|
+
secureAtomicWrite(
|
|
1071
|
+
target,
|
|
1072
|
+
JSON.stringify({ ...raw, agents }, null, 2) + "\n"
|
|
1073
|
+
);
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1076
|
+
function writeInstalledPkgVersion(version, path) {
|
|
1077
|
+
const target = path ?? defaultConfigPath();
|
|
1078
|
+
const raw = readRaw(target);
|
|
1079
|
+
const updated = { ...raw, installed_pkg_version: version };
|
|
1080
|
+
secureAtomicWrite(target, JSON.stringify(updated, null, 2) + "\n");
|
|
1061
1081
|
}
|
|
1062
1082
|
|
|
1063
1083
|
export {
|
|
1084
|
+
apiKeyCredential,
|
|
1085
|
+
resolveDataApiCredential,
|
|
1086
|
+
isDataApiReady,
|
|
1087
|
+
setDebug,
|
|
1088
|
+
isDebug,
|
|
1089
|
+
debug,
|
|
1090
|
+
PACKAGE_NAME,
|
|
1091
|
+
PACKAGE_VERSION,
|
|
1092
|
+
CtxdbError,
|
|
1093
|
+
HttpClient,
|
|
1064
1094
|
SUPPORTED_AGENTS,
|
|
1065
1095
|
SUPPORTED_AGENT_CHOICES,
|
|
1066
1096
|
AGENT_CHOICES,
|
|
@@ -1076,19 +1106,11 @@ export {
|
|
|
1076
1106
|
configDir,
|
|
1077
1107
|
DEFAULT_BASE_URL,
|
|
1078
1108
|
DEFAULT_USER_ID,
|
|
1079
|
-
isComplete,
|
|
1080
1109
|
load,
|
|
1081
1110
|
removeAgent,
|
|
1082
1111
|
save,
|
|
1083
1112
|
configuredAgents,
|
|
1084
1113
|
hasConfiguredAgent,
|
|
1085
1114
|
updateConfiguredDebug,
|
|
1086
|
-
writeInstalledPkgVersion
|
|
1087
|
-
setDebug,
|
|
1088
|
-
isDebug,
|
|
1089
|
-
debug,
|
|
1090
|
-
PACKAGE_NAME,
|
|
1091
|
-
PACKAGE_VERSION,
|
|
1092
|
-
CtxdbError,
|
|
1093
|
-
HttpClient
|
|
1115
|
+
writeInstalledPkgVersion
|
|
1094
1116
|
};
|