@aliyunrds/ctxdb 1.0.7 → 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.internal.md +59 -0
- package/README.md +162 -12
- package/dist/{chunk-X2BX6LR6.js → chunk-3B7J5VUT.js} +1 -1
- package/dist/{chunk-S5W4FQ7M.js → chunk-KYW76CL7.js} +970 -771
- package/dist/{chunk-R2QS6ACP.js → chunk-LTAO6USK.js} +1 -1
- package/dist/{chunk-DPLMBHLU.js → chunk-NF5KFV2P.js} +5 -5
- package/dist/{chunk-QVXDI77N.js → chunk-NFAPLK4C.js} +1 -1
- package/dist/chunk-R67JELM7.js +20 -0
- package/dist/{chunk-DC5NWGDW.js → chunk-SGRMPSZU.js} +2 -2
- package/dist/{chunk-NFTGG6WK.js → chunk-TYW6ABNS.js} +2 -2
- package/dist/{chunk-Z6OHSEF2.js → chunk-ULAWTBBC.js} +34 -16
- package/dist/chunk-VIG4SYLU.js +8 -0
- package/dist/cli/main.js +1526 -73
- package/dist/hooks/hermes-post-llm-call.js +8 -6
- package/dist/hooks/hermes-pre-llm-call.js +11 -9
- package/dist/hooks/pre-tool-use.js +2 -1
- package/dist/hooks/session-start.js +10 -8
- package/dist/hooks/stop.js +8 -6
- package/dist/hooks/user-prompt-submit.js +10 -8
- package/dist/opencode/index.js +58 -2
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +4 -4
- package/dist/setup/skills/contextdb-memory/SKILL.md +4 -4
- package/dist/workers/version-check.js +3 -1
- package/package.json +8 -2
- /package/dist/{chunk-UEKR2Z3S.js → chunk-5ML4VSXN.js} +0 -0
|
@@ -1,877 +1,1069 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DISTRIBUTION_MANIFEST
|
|
4
|
+
} from "./chunk-R67JELM7.js";
|
|
2
5
|
|
|
3
|
-
// src/lib/
|
|
4
|
-
import { appendFileSync, mkdirSync } from "fs";
|
|
6
|
+
// src/lib/agents.ts
|
|
5
7
|
import { homedir } from "os";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
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);
|
|
9
15
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
_enabled = enabled;
|
|
16
|
+
function isAgentSlug(v) {
|
|
17
|
+
return v === "default" || isBuiltinAgent(v);
|
|
13
18
|
}
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
}
|
|
16
39
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
};
|
|
32
58
|
}
|
|
33
|
-
|
|
34
|
-
|
|
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) {
|
|
35
68
|
try {
|
|
36
|
-
|
|
37
|
-
appendFileSync(p, line, "utf-8");
|
|
69
|
+
return existsSync(path);
|
|
38
70
|
} catch {
|
|
71
|
+
return false;
|
|
39
72
|
}
|
|
40
73
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
45
|
-
import { fileURLToPath } from "url";
|
|
46
|
-
function findPackageVersion() {
|
|
47
|
-
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
48
|
-
for (let i = 0; i < 5; i++) {
|
|
49
|
-
try {
|
|
50
|
-
const pkg = JSON.parse(readFileSync(join2(dir, "package.json"), "utf-8"));
|
|
51
|
-
if (pkg.name === "@aliyunrds/ctxdb" && typeof pkg.version === "string") {
|
|
52
|
-
return pkg.version;
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
}
|
|
56
|
-
dir = dirname2(dir);
|
|
57
|
-
}
|
|
58
|
-
return "0.0.0";
|
|
59
|
-
}
|
|
60
|
-
var PACKAGE_VERSION = findPackageVersion();
|
|
61
|
-
|
|
62
|
-
// src/lib/http-client.ts
|
|
63
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
64
|
-
var CtxdbError = class extends Error {
|
|
65
|
-
constructor(message) {
|
|
66
|
-
super(message);
|
|
67
|
-
this.name = "CtxdbError";
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
var AuthError = class extends CtxdbError {
|
|
71
|
-
status = 401;
|
|
72
|
-
errorCode;
|
|
73
|
-
errorMessage;
|
|
74
|
-
data;
|
|
75
|
-
responseBody;
|
|
76
|
-
constructor(message = "Unauthorized (HTTP 401) \u2014 check api_key", fields = {}) {
|
|
77
|
-
super(message);
|
|
78
|
-
this.name = "AuthError";
|
|
79
|
-
this.errorCode = fields.errorCode;
|
|
80
|
-
this.errorMessage = fields.errorMessage;
|
|
81
|
-
this.data = fields.data;
|
|
82
|
-
this.responseBody = fields.responseBody;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
var NotFoundError = class extends CtxdbError {
|
|
86
|
-
status = 404;
|
|
87
|
-
path;
|
|
88
|
-
errorCode;
|
|
89
|
-
errorMessage;
|
|
90
|
-
data;
|
|
91
|
-
responseBody;
|
|
92
|
-
constructor(path, fields = {}) {
|
|
93
|
-
super(`Not found: ${path}`);
|
|
94
|
-
this.name = "NotFoundError";
|
|
95
|
-
this.path = path;
|
|
96
|
-
this.errorCode = fields.errorCode;
|
|
97
|
-
this.errorMessage = fields.errorMessage;
|
|
98
|
-
this.data = fields.data;
|
|
99
|
-
this.responseBody = fields.responseBody;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
var APIError = class extends CtxdbError {
|
|
103
|
-
status = 400;
|
|
104
|
-
path;
|
|
105
|
-
detail;
|
|
106
|
-
errorCode;
|
|
107
|
-
errorMessage;
|
|
108
|
-
data;
|
|
109
|
-
responseBody;
|
|
110
|
-
constructor(path, detail, fields = {}) {
|
|
111
|
-
super(`API error at ${path}: ${detail}`);
|
|
112
|
-
this.name = "APIError";
|
|
113
|
-
this.path = path;
|
|
114
|
-
this.detail = detail;
|
|
115
|
-
this.errorCode = fields.errorCode;
|
|
116
|
-
this.errorMessage = fields.errorMessage;
|
|
117
|
-
this.data = fields.data;
|
|
118
|
-
this.responseBody = fields.responseBody;
|
|
119
|
-
}
|
|
74
|
+
var EXECUTABLE_AGENT_NAMES = {
|
|
75
|
+
opencode: "opencode",
|
|
76
|
+
hermes: "hermes"
|
|
120
77
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
data;
|
|
127
|
-
responseBody;
|
|
128
|
-
constructor(status, detail, fields = {}) {
|
|
129
|
-
super(`HTTP ${status}: ${detail}`);
|
|
130
|
-
this.name = "CtxdbHttpError";
|
|
131
|
-
this.status = status;
|
|
132
|
-
this.detail = detail;
|
|
133
|
-
this.errorCode = fields.errorCode;
|
|
134
|
-
this.errorMessage = fields.errorMessage;
|
|
135
|
-
this.data = fields.data;
|
|
136
|
-
this.responseBody = fields.responseBody;
|
|
78
|
+
function isDirectoryAdapter(path) {
|
|
79
|
+
try {
|
|
80
|
+
return statSync(path).isDirectory();
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
137
83
|
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
constructor(opts) {
|
|
147
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
148
|
-
this.apiKey = opts.apiKey;
|
|
149
|
-
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
150
|
-
this.userAgent = opts.userAgent ?? `ctxdb-cli/${PACKAGE_VERSION}`;
|
|
151
|
-
this.extraHeaders = { ...opts.extraHeaders ?? {} };
|
|
152
|
-
const f = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
153
|
-
this.fetchImpl = f;
|
|
84
|
+
}
|
|
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;
|
|
154
92
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
+
}
|
|
165
113
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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) });
|
|
172
131
|
}
|
|
173
|
-
const s = qs.toString();
|
|
174
|
-
if (s) url = `${url}?${s}`;
|
|
175
132
|
}
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
}
|
|
182
143
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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 });
|
|
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
|
+
}
|
|
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
|
|
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);
|
|
240
|
+
}
|
|
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;
|
|
198
261
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return {};
|
|
262
|
+
} else {
|
|
263
|
+
fs.copy(target, backup);
|
|
264
|
+
fs.chmod(backup, 384);
|
|
265
|
+
fs.rename(temp, target);
|
|
266
|
+
tempExists = false;
|
|
205
267
|
}
|
|
206
|
-
|
|
268
|
+
} else {
|
|
269
|
+
fs.rename(temp, target);
|
|
270
|
+
tempExists = false;
|
|
271
|
+
}
|
|
272
|
+
} finally {
|
|
273
|
+
if (fd !== null) {
|
|
207
274
|
try {
|
|
208
|
-
|
|
209
|
-
} catch
|
|
210
|
-
if (err?.name === "AbortError") {
|
|
211
|
-
if (dbg) debug("http", `\u2717 ${method} ${path} body-read timeout after ${Date.now() - t0}ms`);
|
|
212
|
-
throw new CtxdbError(`request timeout after ${effectiveTimeout}ms (body read): ${url}`);
|
|
213
|
-
}
|
|
214
|
-
if (dbg) debug("http", `\u2717 ${method} ${path} body-read error after ${Date.now() - t0}ms: ${err?.message ?? err}`);
|
|
215
|
-
throw new CtxdbError(`network error reading body from ${url}: ${err?.message ?? err}`);
|
|
275
|
+
fs.close(fd);
|
|
276
|
+
} catch {
|
|
216
277
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
if (resp.status === 404) {
|
|
224
|
-
throw new NotFoundError(path, parsedError);
|
|
225
|
-
}
|
|
226
|
-
if (resp.status === 400) {
|
|
227
|
-
throw new APIError(path, parsedError.detail, parsedError);
|
|
228
|
-
}
|
|
229
|
-
throw new CtxdbHttpError(resp.status, parsedError.detail, parsedError);
|
|
278
|
+
}
|
|
279
|
+
if (tempExists && fs.exists(temp)) {
|
|
280
|
+
try {
|
|
281
|
+
fs.unlink(temp);
|
|
282
|
+
} catch {
|
|
230
283
|
}
|
|
231
|
-
if (dbg) debug("http", `\u2190 ${method} ${path} ${resp.status} (${Date.now() - t0}ms) body=${text.length}B`);
|
|
232
|
-
if (!text) return {};
|
|
233
|
-
return maybeJson(text);
|
|
234
|
-
} finally {
|
|
235
|
-
clearTimeout(timer);
|
|
236
284
|
}
|
|
237
285
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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`);
|
|
241
322
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
timeoutMs: options.timeoutMs,
|
|
248
|
-
headers: options.headers
|
|
249
|
-
});
|
|
323
|
+
let url;
|
|
324
|
+
try {
|
|
325
|
+
url = new URL(value);
|
|
326
|
+
} catch {
|
|
327
|
+
throw new Error(`credentials: ${field} must be a valid URL`);
|
|
250
328
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
body: JSON.stringify(body),
|
|
254
|
-
contentType: "application/json",
|
|
255
|
-
params,
|
|
256
|
-
timeoutMs: options.timeoutMs,
|
|
257
|
-
headers: options.headers
|
|
258
|
-
});
|
|
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`);
|
|
259
331
|
}
|
|
260
|
-
|
|
261
|
-
|
|
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");
|
|
262
337
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
* `fields` are appended as text values; `files` are appended as Blob
|
|
267
|
-
* with a filename. We intentionally do NOT pass a `Content-Type`
|
|
268
|
-
* header — when fetch sees a FormData body it sets the multipart
|
|
269
|
-
* boundary itself.
|
|
270
|
-
*/
|
|
271
|
-
postMultipart(path, fields = {}, files = {}, options = {}) {
|
|
272
|
-
const fd = buildMultipartForm(fields, files);
|
|
273
|
-
return this.doRequest("POST", path, {
|
|
274
|
-
body: fd,
|
|
275
|
-
params: options.params,
|
|
276
|
-
timeoutMs: options.timeoutMs,
|
|
277
|
-
headers: options.headers
|
|
278
|
-
});
|
|
338
|
+
const raw = value;
|
|
339
|
+
if (raw.kind !== "api-key") {
|
|
340
|
+
throw new Error(`credentials: unsupported record kind ${String(raw.kind)}`);
|
|
279
341
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
body: fd,
|
|
285
|
-
params: options.params,
|
|
286
|
-
timeoutMs: options.timeoutMs,
|
|
287
|
-
headers: options.headers
|
|
288
|
-
});
|
|
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");
|
|
289
346
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const fd = new FormData();
|
|
293
|
-
for (const [name, value] of Object.entries(fields)) {
|
|
294
|
-
fd.append(name, value);
|
|
347
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
348
|
+
throw new Error("credentials: api-key metadata must be an object");
|
|
295
349
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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");
|
|
299
354
|
}
|
|
300
|
-
|
|
355
|
+
if (meta.authorization_method !== "browser-loopback" && meta.authorization_method !== "device-code") {
|
|
356
|
+
throw new Error("credentials: authorization_method is invalid");
|
|
357
|
+
}
|
|
358
|
+
if (typeof meta.issued_at !== "string" || !Number.isFinite(Date.parse(meta.issued_at))) {
|
|
359
|
+
throw new Error("credentials: issued_at is invalid");
|
|
360
|
+
}
|
|
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
|
+
};
|
|
301
373
|
}
|
|
302
|
-
function
|
|
374
|
+
function parseDocument(text) {
|
|
375
|
+
let raw;
|
|
303
376
|
try {
|
|
304
|
-
|
|
377
|
+
raw = JSON.parse(text);
|
|
305
378
|
} catch {
|
|
306
|
-
|
|
379
|
+
throw new Error("credentials: credentials.json is not valid JSON");
|
|
307
380
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (!text) return { detail: fallback };
|
|
311
|
-
let parsed;
|
|
312
|
-
try {
|
|
313
|
-
parsed = JSON.parse(text);
|
|
314
|
-
} catch {
|
|
315
|
-
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");
|
|
316
383
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
for (const k of ["errorMessage", "detail", "message", "error"]) {
|
|
321
|
-
const v = obj[k];
|
|
322
|
-
if (typeof v === "string" && v) {
|
|
323
|
-
detail = v;
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
const rawCode = obj.errorCode;
|
|
328
|
-
const numericCode = typeof rawCode === "number" ? rawCode : typeof rawCode === "string" && rawCode.trim() !== "" ? Number(rawCode) : void 0;
|
|
329
|
-
const errorCode = typeof numericCode === "number" && Number.isFinite(numericCode) ? numericCode : void 0;
|
|
330
|
-
const errorMessage = typeof obj.errorMessage === "string" && obj.errorMessage ? obj.errorMessage : void 0;
|
|
331
|
-
return {
|
|
332
|
-
detail: detail || JSON.stringify(parsed),
|
|
333
|
-
errorCode,
|
|
334
|
-
errorMessage,
|
|
335
|
-
data: Object.hasOwn(obj, "data") ? obj.data : void 0,
|
|
336
|
-
responseBody: parsed
|
|
337
|
-
};
|
|
384
|
+
const document = raw;
|
|
385
|
+
if (document.version !== DOCUMENT_VERSION) {
|
|
386
|
+
throw new Error(`credentials: unsupported document version ${String(document.version)}`);
|
|
338
387
|
}
|
|
339
|
-
|
|
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];
|
|
340
416
|
}
|
|
341
417
|
|
|
342
|
-
// src/lib/
|
|
343
|
-
import {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
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");
|
|
349
424
|
}
|
|
350
|
-
|
|
351
|
-
|
|
425
|
+
var DEFAULT_CONFIG_PATH = join3(homedir3(), ".ctxdb", "ctxdb.json");
|
|
426
|
+
function configDir() {
|
|
427
|
+
return join3(homedir3(), ".ctxdb");
|
|
352
428
|
}
|
|
353
|
-
var
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return join3(home, ".qwenwork");
|
|
362
|
-
case "codex":
|
|
363
|
-
return join3(home, ".codex");
|
|
364
|
-
case "claude":
|
|
365
|
-
return join3(home, ".claude");
|
|
366
|
-
case "opencode":
|
|
367
|
-
return join3(home, ".config", "opencode");
|
|
368
|
-
case "hermes":
|
|
369
|
-
return join3(home, ".hermes");
|
|
370
|
-
}
|
|
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);
|
|
371
437
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
qwenwork: [".qwenworkcn"]
|
|
376
|
-
};
|
|
377
|
-
function agentHomeDirs(agent, home = homedir2()) {
|
|
378
|
-
const primary = agentHomeDir(agent, home);
|
|
379
|
-
const variants = (AGENT_VARIANT_HOMES[agent] ?? []).map(
|
|
380
|
-
(name) => join3(home, name)
|
|
381
|
-
);
|
|
382
|
-
return [primary, ...variants];
|
|
438
|
+
function resolveConfigAgent(options = {}, preloadedRaw) {
|
|
439
|
+
if (isAgentSlug(options.agent)) return options.agent;
|
|
440
|
+
return agentFromEnv(options.env);
|
|
383
441
|
}
|
|
384
|
-
function
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
detail: "Hermes integration currently supports macOS/Linux only; Windows Hermes is not yet supported."
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
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;
|
|
392
446
|
}
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
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;
|
|
398
451
|
}
|
|
399
|
-
function
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
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);
|
|
405
456
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
};
|
|
410
|
-
function isDirectoryAdapter(path) {
|
|
411
|
-
try {
|
|
412
|
-
return statSync(path).isDirectory();
|
|
413
|
-
} catch {
|
|
414
|
-
return false;
|
|
457
|
+
function coerceKbCatalogInjection(v) {
|
|
458
|
+
if (v === "session_start" || v === "user_prompt_submit" || v === "off") {
|
|
459
|
+
return v;
|
|
415
460
|
}
|
|
461
|
+
return DEFAULT_KB_CATALOG_INJECTION;
|
|
416
462
|
}
|
|
417
|
-
function
|
|
463
|
+
function readRaw(path) {
|
|
464
|
+
if (!existsSync4(path)) return {};
|
|
418
465
|
try {
|
|
419
|
-
|
|
420
|
-
if (
|
|
421
|
-
|
|
466
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
467
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
468
|
+
return parsed;
|
|
469
|
+
}
|
|
422
470
|
} catch {
|
|
423
|
-
return false;
|
|
424
471
|
}
|
|
472
|
+
return {};
|
|
425
473
|
}
|
|
426
|
-
function
|
|
427
|
-
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
function resolveAgentExecutable(command, options = {}) {
|
|
433
|
-
const platform = options.platform ?? process.platform;
|
|
434
|
-
const env = options.env ?? process.env;
|
|
435
|
-
const isExecutable = options.isExecutable ?? isExecutableAdapter;
|
|
436
|
-
const pathValue = envValue(env, "PATH", platform) ?? "";
|
|
437
|
-
const pathSeparator = platform === "win32" ? ";" : delimiter;
|
|
438
|
-
const pathEntries = pathValue.split(pathSeparator).map((entry) => entry.trim().replace(/^"(.*)"$/, "$1")).filter(Boolean);
|
|
439
|
-
const suffixes = platform === "win32" ? (envValue(env, "PATHEXT", platform) ?? ".COM;.EXE;.BAT;.CMD").split(";").map((suffix) => suffix.trim()).filter(Boolean) : [""];
|
|
440
|
-
for (const directory of pathEntries) {
|
|
441
|
-
for (const suffix of suffixes) {
|
|
442
|
-
const candidate = join3(directory, `${command}${suffix}`);
|
|
443
|
-
if (isExecutable(candidate, platform)) return candidate;
|
|
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;
|
|
444
480
|
}
|
|
445
481
|
}
|
|
446
|
-
return
|
|
482
|
+
return {};
|
|
447
483
|
}
|
|
448
|
-
function
|
|
449
|
-
if (
|
|
450
|
-
return
|
|
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);
|
|
451
487
|
}
|
|
452
|
-
function
|
|
453
|
-
const
|
|
454
|
-
|
|
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
|
+
});
|
|
509
|
+
}
|
|
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;
|
|
517
|
+
}
|
|
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();
|
|
455
528
|
const env = options.env ?? process.env;
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
const executableName = EXECUTABLE_AGENT_NAMES[agent];
|
|
466
|
-
if (executableName) {
|
|
467
|
-
const executable = resolveAgentExecutable(executableName, {
|
|
468
|
-
platform,
|
|
469
|
-
env,
|
|
470
|
-
isExecutable: options.isExecutable
|
|
471
|
-
});
|
|
472
|
-
if (executable) {
|
|
473
|
-
evidence.push({ kind: "executable", value: executableName });
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (evidence.length === 0) {
|
|
477
|
-
observations.push({
|
|
478
|
-
agent,
|
|
479
|
-
selected: false,
|
|
480
|
-
reason: "not-detected",
|
|
481
|
-
evidence
|
|
482
|
-
});
|
|
483
|
-
continue;
|
|
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 {
|
|
484
538
|
}
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
detail: support.detail
|
|
493
|
-
});
|
|
494
|
-
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;
|
|
495
546
|
}
|
|
496
|
-
|
|
547
|
+
return applyEnv(cfg2, env);
|
|
497
548
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
for (let i = 0; i < argv.length; i++) {
|
|
508
|
-
const tok = argv[i];
|
|
509
|
-
if (tok === "--agent" && isAgentSlug(argv[i + 1])) {
|
|
510
|
-
return { agent: argv[i + 1], fellBack: false };
|
|
511
|
-
}
|
|
512
|
-
if (tok.startsWith("--agent=")) {
|
|
513
|
-
const raw = tok.slice("--agent=".length);
|
|
514
|
-
if (isAgentSlug(raw)) return { agent: raw, fellBack: false };
|
|
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
|
+
}
|
|
515
558
|
}
|
|
516
559
|
}
|
|
517
|
-
|
|
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);
|
|
518
569
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
writeFileSync
|
|
537
|
-
} from "fs";
|
|
538
|
-
import { randomBytes } from "crypto";
|
|
539
|
-
import { dirname as dirname3 } from "path";
|
|
540
|
-
var nodeSecureFileSystem = {
|
|
541
|
-
exists: existsSync2,
|
|
542
|
-
mkdir: (path, options) => {
|
|
543
|
-
mkdirSync2(path, options);
|
|
544
|
-
},
|
|
545
|
-
chmod: chmodSync,
|
|
546
|
-
open: openSync,
|
|
547
|
-
write: (fd, data) => {
|
|
548
|
-
writeFileSync(fd, data);
|
|
549
|
-
},
|
|
550
|
-
fsync: fsyncSync,
|
|
551
|
-
close: closeSync,
|
|
552
|
-
copy: copyFileSync,
|
|
553
|
-
rename: renameSync,
|
|
554
|
-
unlink: unlinkSync
|
|
555
|
-
};
|
|
556
|
-
function configBackupPath(target) {
|
|
557
|
-
return `${target}.bak`;
|
|
570
|
+
function configToDisk(cfg) {
|
|
571
|
+
return {
|
|
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
|
|
586
|
+
};
|
|
558
587
|
}
|
|
559
|
-
function
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const dir = dirname3(target);
|
|
564
|
-
const backup = configBackupPath(target);
|
|
565
|
-
const suffix = options.tempSuffix ?? `${process.pid}.${randomBytes(6).toString("hex")}`;
|
|
566
|
-
const temp = `${target}.${suffix}.tmp`;
|
|
567
|
-
const hadTarget = fs.exists(target);
|
|
568
|
-
fs.mkdir(dir, { recursive: true, mode: 448 });
|
|
569
|
-
if (posix) {
|
|
570
|
-
fs.chmod(dir, 448);
|
|
571
|
-
if (hadTarget) fs.chmod(target, 384);
|
|
588
|
+
function removeAgent(agent, path, options = {}) {
|
|
589
|
+
const target = path ?? defaultConfigPath();
|
|
590
|
+
if (!existsSync4(target)) {
|
|
591
|
+
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
572
592
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
throw error;
|
|
593
|
-
}
|
|
594
|
-
} else {
|
|
595
|
-
fs.copy(target, backup);
|
|
596
|
-
fs.chmod(backup, 384);
|
|
597
|
-
fs.rename(temp, target);
|
|
598
|
-
tempExists = false;
|
|
599
|
-
}
|
|
600
|
-
} else {
|
|
601
|
-
fs.rename(temp, target);
|
|
602
|
-
tempExists = false;
|
|
603
|
-
}
|
|
604
|
-
} finally {
|
|
605
|
-
if (fd !== null) {
|
|
606
|
-
try {
|
|
607
|
-
fs.close(fd);
|
|
608
|
-
} catch {
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
if (tempExists && fs.exists(temp)) {
|
|
612
|
-
try {
|
|
613
|
-
fs.unlink(temp);
|
|
614
|
-
} catch {
|
|
615
|
-
}
|
|
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 {
|
|
616
612
|
}
|
|
617
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
618
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
delete onDisk.default_agent;
|
|
636
|
+
secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
|
|
630
637
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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);
|
|
634
643
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
+
}
|
|
649
|
+
const profile = raw.agents[agent];
|
|
650
|
+
return Boolean(
|
|
651
|
+
profile && typeof profile === "object" && !Array.isArray(profile)
|
|
652
|
+
);
|
|
643
653
|
}
|
|
644
|
-
function
|
|
645
|
-
|
|
646
|
-
|
|
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;
|
|
659
|
+
}
|
|
660
|
+
const agents = { ...raw.agents };
|
|
661
|
+
const profile = agents[agent];
|
|
662
|
+
if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
agents[agent] = { ...profile, debug: configured };
|
|
666
|
+
secureAtomicWrite(
|
|
667
|
+
target,
|
|
668
|
+
JSON.stringify({ ...raw, agents }, null, 2) + "\n"
|
|
669
|
+
);
|
|
670
|
+
return true;
|
|
647
671
|
}
|
|
648
|
-
function
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
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");
|
|
652
677
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
678
|
+
|
|
679
|
+
// src/lib/logger.ts
|
|
680
|
+
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
681
|
+
import { homedir as homedir4 } from "os";
|
|
682
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
683
|
+
function logPath() {
|
|
684
|
+
return join4(homedir4(), ".ctxdb", "logs", "ctxdb.log");
|
|
657
685
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
return Boolean(v);
|
|
686
|
+
var _enabled = null;
|
|
687
|
+
function setDebug(enabled) {
|
|
688
|
+
_enabled = enabled;
|
|
662
689
|
}
|
|
663
|
-
function
|
|
664
|
-
|
|
665
|
-
return v;
|
|
666
|
-
}
|
|
667
|
-
return DEFAULT_KB_CATALOG_INJECTION;
|
|
690
|
+
function isDebug() {
|
|
691
|
+
return _enabled === true;
|
|
668
692
|
}
|
|
669
|
-
function
|
|
670
|
-
if (
|
|
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}`;
|
|
708
|
+
}
|
|
709
|
+
line += "\n";
|
|
710
|
+
const p = logPath();
|
|
671
711
|
try {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
return parsed;
|
|
675
|
-
}
|
|
712
|
+
mkdirSync3(dirname4(p), { recursive: true });
|
|
713
|
+
appendFileSync(p, line, "utf-8");
|
|
676
714
|
} catch {
|
|
677
715
|
}
|
|
678
|
-
return {};
|
|
679
716
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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 {
|
|
686
735
|
}
|
|
736
|
+
dir = dirname5(dir);
|
|
687
737
|
}
|
|
688
|
-
return {};
|
|
738
|
+
return { name: "@aliyunrds/ctxdb", version: "0.0.0" };
|
|
689
739
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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";
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
var AuthError = class extends CtxdbError {
|
|
753
|
+
status = 401;
|
|
754
|
+
errorCode;
|
|
755
|
+
errorMessage;
|
|
756
|
+
data;
|
|
757
|
+
responseBody;
|
|
758
|
+
constructor(message = "Unauthorized (HTTP 401) \u2014 check api_key", fields = {}) {
|
|
759
|
+
super(message);
|
|
760
|
+
this.name = "AuthError";
|
|
761
|
+
this.errorCode = fields.errorCode;
|
|
762
|
+
this.errorMessage = fields.errorMessage;
|
|
763
|
+
this.data = fields.data;
|
|
764
|
+
this.responseBody = fields.responseBody;
|
|
765
|
+
}
|
|
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;
|
|
782
|
+
}
|
|
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;
|
|
801
|
+
}
|
|
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;
|
|
819
|
+
}
|
|
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");
|
|
744
833
|
}
|
|
745
|
-
|
|
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;
|
|
746
841
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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;
|
|
852
|
+
}
|
|
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}`;
|
|
868
|
+
}
|
|
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)`);
|
|
875
|
+
}
|
|
876
|
+
const controller = new AbortController();
|
|
877
|
+
const timer = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
878
|
+
try {
|
|
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);
|
|
754
925
|
}
|
|
926
|
+
throw new CtxdbHttpError(resp.status, parsedError.detail, parsedError);
|
|
755
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);
|
|
756
933
|
}
|
|
757
934
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
return {
|
|
762
|
-
api_key: cfg.apiKey,
|
|
763
|
-
base_url: cfg.baseUrl,
|
|
764
|
-
user_id: cfg.userId,
|
|
765
|
-
agent_id: cfg.agentId,
|
|
766
|
-
app_id: cfg.appId,
|
|
767
|
-
auto_capture: cfg.autoCapture,
|
|
768
|
-
auto_recall: cfg.autoRecall,
|
|
769
|
-
warmup_recall: cfg.warmupRecall,
|
|
770
|
-
recall_knowledge: cfg.recallKnowledge,
|
|
771
|
-
top_k: cfg.topK,
|
|
772
|
-
threshold: cfg.threshold,
|
|
773
|
-
knowledge_top_k: cfg.knowledgeTopK,
|
|
774
|
-
debug: cfg.debugConfigured,
|
|
775
|
-
kb_catalog_injection: cfg.kbCatalogInjection
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
function removeAgent(agent, path, options = {}) {
|
|
779
|
-
const target = path ?? defaultConfigPath();
|
|
780
|
-
if (!existsSync3(target)) {
|
|
781
|
-
return { removed: false, remainingAgents: [], fileDeleted: false };
|
|
935
|
+
// ---------- Convenience verbs ----------
|
|
936
|
+
get(path, params) {
|
|
937
|
+
return this.doRequest("GET", path, { params });
|
|
782
938
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
+
});
|
|
786
947
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
+
});
|
|
794
956
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
}
|
|
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
|
+
});
|
|
803
965
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
+
});
|
|
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;
|
|
831
1022
|
}
|
|
832
|
-
function
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1023
|
+
function maybeJson(text) {
|
|
1024
|
+
try {
|
|
1025
|
+
return JSON.parse(text);
|
|
1026
|
+
} catch {
|
|
1027
|
+
return text;
|
|
836
1028
|
}
|
|
837
|
-
const profile = raw.agents[agent];
|
|
838
|
-
return Boolean(
|
|
839
|
-
profile && typeof profile === "object" && !Array.isArray(profile)
|
|
840
|
-
);
|
|
841
1029
|
}
|
|
842
|
-
function
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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 };
|
|
847
1037
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
+
};
|
|
852
1059
|
}
|
|
853
|
-
|
|
854
|
-
secureAtomicWrite(
|
|
855
|
-
target,
|
|
856
|
-
JSON.stringify({ ...raw, agents }, null, 2) + "\n"
|
|
857
|
-
);
|
|
858
|
-
return true;
|
|
859
|
-
}
|
|
860
|
-
function writeInstalledPkgVersion(version, path) {
|
|
861
|
-
const target = path ?? defaultConfigPath();
|
|
862
|
-
const raw = readRaw(target);
|
|
863
|
-
const updated = { ...raw, installed_pkg_version: version };
|
|
864
|
-
secureAtomicWrite(target, JSON.stringify(updated, null, 2) + "\n");
|
|
1060
|
+
return { detail: String(parsed), responseBody: parsed };
|
|
865
1061
|
}
|
|
866
1062
|
|
|
867
1063
|
export {
|
|
868
|
-
setDebug,
|
|
869
|
-
isDebug,
|
|
870
|
-
debug,
|
|
871
|
-
PACKAGE_VERSION,
|
|
872
|
-
CtxdbError,
|
|
873
|
-
HttpClient,
|
|
874
1064
|
SUPPORTED_AGENTS,
|
|
1065
|
+
SUPPORTED_AGENT_CHOICES,
|
|
1066
|
+
AGENT_CHOICES,
|
|
875
1067
|
isBuiltinAgent,
|
|
876
1068
|
isAgentSlug,
|
|
877
1069
|
agentHomeDir,
|
|
@@ -891,5 +1083,12 @@ export {
|
|
|
891
1083
|
configuredAgents,
|
|
892
1084
|
hasConfiguredAgent,
|
|
893
1085
|
updateConfiguredDebug,
|
|
894
|
-
writeInstalledPkgVersion
|
|
1086
|
+
writeInstalledPkgVersion,
|
|
1087
|
+
setDebug,
|
|
1088
|
+
isDebug,
|
|
1089
|
+
debug,
|
|
1090
|
+
PACKAGE_NAME,
|
|
1091
|
+
PACKAGE_VERSION,
|
|
1092
|
+
CtxdbError,
|
|
1093
|
+
HttpClient
|
|
895
1094
|
};
|