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