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