@aliyunrds/ctxdb 1.0.8-beta.4 → 1.0.8
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 +205 -32
- package/dist/{chunk-SVO6H62S.js → chunk-3FAQREIU.js} +285 -216
- package/dist/{chunk-OEZM2ZYA.js → chunk-3MEBQDGU.js} +5 -0
- package/dist/{chunk-WJTV7JNR.js → chunk-AAWG5SPO.js} +3 -3
- package/dist/{chunk-O6MKQ2I3.js → chunk-AH2QEUK4.js} +1 -1
- package/dist/{chunk-ZW7YXNJ6.js → chunk-LZD55CWE.js} +286 -11
- package/dist/{chunk-5DGRNP55.js → chunk-MWAFAK5M.js} +1 -1
- package/dist/{chunk-6MEWI73Y.js → chunk-RRDQCZJ4.js} +2 -2
- package/dist/{chunk-76AXXLVE.js → chunk-YTCXHO4F.js} +2 -2
- package/dist/cli/main.js +4151 -1926
- package/dist/hooks/hermes-post-llm-call.js +3 -3
- package/dist/hooks/hermes-pre-llm-call.js +6 -6
- package/dist/hooks/session-start.js +5 -5
- package/dist/hooks/stop.js +3 -3
- package/dist/hooks/user-prompt-submit.js +5 -5
- package/dist/opencode/index.js +725 -245
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +14 -3
- package/dist/setup/skills/contextdb-memory/SKILL.md +10 -3
- package/dist/workers/version-check.js +1 -1
- package/package.json +2 -2
package/dist/opencode/index.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
// <define:__CTXDB_DISTRIBUTION_MANIFEST__>
|
|
2
2
|
var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false, deviceFlow: true } };
|
|
3
3
|
|
|
4
|
-
// src/config.ts
|
|
5
|
-
import { readFileSync as readFileSync4, existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
6
|
-
import { homedir as homedir2 } from "os";
|
|
7
|
-
import { dirname as dirname3, join as join5 } from "path";
|
|
8
|
-
|
|
9
4
|
// ../shared/src/graph-context.ts
|
|
10
5
|
var GRAPH_CONTEXT_TAG = "graphrag";
|
|
11
6
|
var GRAPH_CONTEXT_SOURCE_LABEL = "Related content in Knowledge Graph";
|
|
@@ -589,6 +584,7 @@ function selectAndFormatRecalledMemories(memories, userId, config = {}) {
|
|
|
589
584
|
|
|
590
585
|
// ../shared/src/debug-policy.ts
|
|
591
586
|
var OFFICIAL_PRODUCTION_BASE_URLS = [
|
|
587
|
+
"https://api.cn-hangzhou.agentcontext.aliyuncs.com",
|
|
592
588
|
"https://context-database.aliyuncs.com"
|
|
593
589
|
];
|
|
594
590
|
function normalizeBaseUrl(value) {
|
|
@@ -861,16 +857,21 @@ var CoreDeviceClient = class {
|
|
|
861
857
|
baseUrl;
|
|
862
858
|
fetchImpl;
|
|
863
859
|
now;
|
|
860
|
+
requestTimeoutMs;
|
|
864
861
|
sleep;
|
|
865
862
|
constructor(baseUrl, options = {}) {
|
|
866
863
|
this.baseUrl = normalizeCoreOrigin(baseUrl);
|
|
867
864
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
868
865
|
this.now = options.now ?? Date.now;
|
|
866
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
|
|
869
867
|
this.sleep = options.sleep ?? ((ms, signal) => delay(ms, void 0, { signal }));
|
|
870
868
|
}
|
|
871
869
|
async login(options) {
|
|
872
870
|
const started = this.now();
|
|
873
|
-
const initial = await this.post("/v1/auth/device/authorize", {
|
|
871
|
+
const initial = await this.post("/v1/auth/device/authorize", {
|
|
872
|
+
client_id: clientId,
|
|
873
|
+
...options.initialAgent === void 0 ? {} : { initial_agent: options.initialAgent }
|
|
874
|
+
}, options.signal);
|
|
874
875
|
if (!initial.ok) throw new DeviceFlowError("request_rejected");
|
|
875
876
|
const data = initial.data;
|
|
876
877
|
const requestId = text(data.request_id, 32), userCode = text(data.user_code, 9), code = text(data.device_code, 43);
|
|
@@ -920,7 +921,7 @@ var CoreDeviceClient = class {
|
|
|
920
921
|
const controller = new AbortController();
|
|
921
922
|
const abort = () => controller.abort();
|
|
922
923
|
signal?.addEventListener("abort", abort, { once: true });
|
|
923
|
-
const timer = setTimeout(abort,
|
|
924
|
+
const timer = setTimeout(abort, this.requestTimeoutMs);
|
|
924
925
|
timer.unref?.();
|
|
925
926
|
try {
|
|
926
927
|
const response = await this.fetchImpl(this.baseUrl + path, {
|
|
@@ -1022,22 +1023,17 @@ async function boundedJson(response) {
|
|
|
1022
1023
|
}
|
|
1023
1024
|
|
|
1024
1025
|
// ../shared/src/oauth-credentials.ts
|
|
1025
|
-
import {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
fsyncSync,
|
|
1031
|
-
lstatSync,
|
|
1032
|
-
mkdirSync as mkdirSync2,
|
|
1033
|
-
openSync as openSync2,
|
|
1034
|
-
readFileSync as readFileSync2,
|
|
1035
|
-
renameSync as renameSync2,
|
|
1036
|
-
unlinkSync,
|
|
1037
|
-
writeFileSync
|
|
1038
|
-
} from "fs";
|
|
1039
|
-
import { randomBytes } from "crypto";
|
|
1026
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1027
|
+
|
|
1028
|
+
// ../shared/src/credentials/local-credential-provider.ts
|
|
1029
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
1030
|
+
import { homedir as homedir2 } from "os";
|
|
1040
1031
|
import { join as join3 } from "path";
|
|
1032
|
+
|
|
1033
|
+
// ../shared/src/credentials/storage.ts
|
|
1034
|
+
import { constants, closeSync as closeSync2, existsSync as existsSync2, fstatSync, fsyncSync, lstatSync, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
1035
|
+
import { randomBytes, createHash } from "crypto";
|
|
1036
|
+
import { dirname as dirname2 } from "path";
|
|
1041
1037
|
import { setTimeout as delay2 } from "timers/promises";
|
|
1042
1038
|
|
|
1043
1039
|
// ../shared/src/windows-private-storage.ts
|
|
@@ -1092,6 +1088,331 @@ function checkWindowsPrivateStorage(path, initialize, run = execFileSync) {
|
|
|
1092
1088
|
}
|
|
1093
1089
|
}
|
|
1094
1090
|
|
|
1091
|
+
// ../shared/src/credentials/storage.ts
|
|
1092
|
+
function privatePath(path, directory = false) {
|
|
1093
|
+
const s = lstatSync(path);
|
|
1094
|
+
if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) throw new Error("credentials: storage is not private or is invalid");
|
|
1095
|
+
if (process.platform === "win32") {
|
|
1096
|
+
checkWindowsPrivateStorage(path, false);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
if (s.mode & 63 || process.getuid && s.uid !== process.getuid()) throw new Error("credentials: storage must be private and owner-only (run chmod 600 on credentials.json)");
|
|
1100
|
+
}
|
|
1101
|
+
function prepareStorage(path) {
|
|
1102
|
+
const dir = dirname2(path);
|
|
1103
|
+
if (!existsSync2(dir)) {
|
|
1104
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
1105
|
+
if (process.platform === "win32") checkWindowsPrivateStorage(dir, true);
|
|
1106
|
+
}
|
|
1107
|
+
privatePath(dir, true);
|
|
1108
|
+
}
|
|
1109
|
+
function readPrivate(path) {
|
|
1110
|
+
if (!existsSync2(path)) return void 0;
|
|
1111
|
+
privatePath(dirname2(path), true);
|
|
1112
|
+
privatePath(path);
|
|
1113
|
+
const fd = openSync2(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
1114
|
+
try {
|
|
1115
|
+
const stat = fstatSync(fd);
|
|
1116
|
+
if (!stat.isFile() || stat.size > 16 * 1024 * 1024) throw new Error("credentials: invalid storage");
|
|
1117
|
+
return readFileSync2(fd, "utf8");
|
|
1118
|
+
} finally {
|
|
1119
|
+
closeSync2(fd);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function atomicCredentialWrite(path, value) {
|
|
1123
|
+
prepareStorage(path);
|
|
1124
|
+
if (existsSync2(path)) privatePath(path);
|
|
1125
|
+
const temp = path + "." + randomBytes(12).toString("hex") + ".tmp";
|
|
1126
|
+
let fd;
|
|
1127
|
+
try {
|
|
1128
|
+
fd = openSync2(temp, "wx", 384);
|
|
1129
|
+
writeFileSync(fd, JSON.stringify(value, null, 2) + "\n");
|
|
1130
|
+
fsyncSync(fd);
|
|
1131
|
+
closeSync2(fd);
|
|
1132
|
+
fd = void 0;
|
|
1133
|
+
renameSync2(temp, path);
|
|
1134
|
+
if (process.platform !== "win32") {
|
|
1135
|
+
try {
|
|
1136
|
+
const dir = openSync2(dirname2(path), "r");
|
|
1137
|
+
try {
|
|
1138
|
+
fsyncSync(dir);
|
|
1139
|
+
} finally {
|
|
1140
|
+
closeSync2(dir);
|
|
1141
|
+
}
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
} finally {
|
|
1146
|
+
if (fd !== void 0) closeSync2(fd);
|
|
1147
|
+
if (existsSync2(temp)) unlinkSync(temp);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
function withCredentialWriteLock(path, operation) {
|
|
1151
|
+
prepareStorage(path);
|
|
1152
|
+
const lock = path + ".lock", deadline = Date.now() + 1e4;
|
|
1153
|
+
let fd;
|
|
1154
|
+
while (true) {
|
|
1155
|
+
try {
|
|
1156
|
+
fd = openSync2(lock, "wx", 384);
|
|
1157
|
+
break;
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
if (error.code !== "EEXIST") throw error;
|
|
1160
|
+
if (Date.now() >= deadline) throw new Error("credentials: writer lock is busy; check the previous CLI process");
|
|
1161
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
try {
|
|
1165
|
+
return operation();
|
|
1166
|
+
} finally {
|
|
1167
|
+
closeSync2(fd);
|
|
1168
|
+
unlinkSync(lock);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function operationLockPath(path, key) {
|
|
1172
|
+
return path + "." + createHash("sha256").update(key).digest("hex") + ".lock";
|
|
1173
|
+
}
|
|
1174
|
+
async function withCredentialOperation(path, key, operation, options = {}) {
|
|
1175
|
+
prepareStorage(path);
|
|
1176
|
+
const lock = operationLockPath(path, key), deadline = Date.now() + (options.lockWaitMs ?? 3e4);
|
|
1177
|
+
while (true) {
|
|
1178
|
+
options.signal?.throwIfAborted();
|
|
1179
|
+
let fd;
|
|
1180
|
+
try {
|
|
1181
|
+
fd = openSync2(lock, "wx", 384);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
if (error.code !== "EEXIST") throw error;
|
|
1184
|
+
if (Date.now() >= deadline) throw new Error("credentials: credential_busy");
|
|
1185
|
+
await delay2(25, void 0, { signal: options.signal });
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
try {
|
|
1189
|
+
writeFileSync(fd, String(process.pid));
|
|
1190
|
+
} finally {
|
|
1191
|
+
closeSync2(fd);
|
|
1192
|
+
}
|
|
1193
|
+
try {
|
|
1194
|
+
return await operation();
|
|
1195
|
+
} finally {
|
|
1196
|
+
unlinkSync(lock);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// ../shared/src/credentials/types.ts
|
|
1202
|
+
var ACTIVE_CONTEXTDB_CREDENTIAL = "contextdb/active";
|
|
1203
|
+
|
|
1204
|
+
// ../shared/src/credentials/local-credential-provider.ts
|
|
1205
|
+
function defaultCredentialsPath() {
|
|
1206
|
+
return join3(homedir2(), ".ctxdb", "credentials.json");
|
|
1207
|
+
}
|
|
1208
|
+
var object2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
1209
|
+
var fail = (message) => {
|
|
1210
|
+
throw new Error("credentials: " + message);
|
|
1211
|
+
};
|
|
1212
|
+
function origin(value) {
|
|
1213
|
+
if (typeof value !== "string" || !value) return fail("invalid service URL");
|
|
1214
|
+
let u;
|
|
1215
|
+
try {
|
|
1216
|
+
u = new URL(value);
|
|
1217
|
+
} catch {
|
|
1218
|
+
return fail("invalid service URL");
|
|
1219
|
+
}
|
|
1220
|
+
if (!["http:", "https:"].includes(u.protocol) || u.username || u.password || u.search || u.hash) return fail("invalid service URL");
|
|
1221
|
+
return u.toString().replace(/\/$/, "");
|
|
1222
|
+
}
|
|
1223
|
+
function validateApi(raw, internal, v1 = false) {
|
|
1224
|
+
if (!object2(raw.payload) || typeof raw.payload.api_key !== "string" || !raw.payload.api_key) fail("invalid api-key payload");
|
|
1225
|
+
const metadata = v1 ? raw.payload : raw.metadata;
|
|
1226
|
+
origin(metadata.base_url);
|
|
1227
|
+
if (internal) {
|
|
1228
|
+
origin(metadata.login_server);
|
|
1229
|
+
if (!object2(raw.metadata) || !["browser-loopback", "device-code"].includes(raw.metadata.authorization_method) || typeof raw.metadata.issued_at !== "string" || !Number.isFinite(Date.parse(raw.metadata.issued_at))) fail("invalid api-key metadata");
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
function validate(key, raw) {
|
|
1233
|
+
if (!object2(raw) || !["api-key", "oauth-session"].includes(raw.kind) || !object2(raw.payload) || !object2(raw.metadata) || !object2(raw.metadata.owner) || !["ready", "refreshing", "reauth_required", "logged_out"].includes(raw.metadata.state) || typeof raw.metadata.generation !== "string" || !/^[a-f0-9]{32}$/.test(raw.metadata.generation) || !Number.isSafeInteger(raw.metadata.revision) || raw.metadata.revision < 1) fail("invalid credential record");
|
|
1234
|
+
if (key === ACTIVE_CONTEXTDB_CREDENTIAL) {
|
|
1235
|
+
if (raw.kind !== "api-key" || raw.metadata.owner.distribution !== "internal" || raw.metadata.state !== "ready") fail("invalid contextdb/active record");
|
|
1236
|
+
} else if (!/^[a-f0-9]{32}$/.test(key) || raw.metadata.owner.distribution !== "public" || typeof raw.metadata.owner.agent !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(raw.metadata.owner.agent) || raw.metadata.owner.loginId !== void 0 && (typeof raw.metadata.owner.loginId !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(raw.metadata.owner.loginId))) fail("invalid credential ownership");
|
|
1237
|
+
const fields = raw.kind === "api-key" ? ["api_key"] : ["ready", "refreshing"].includes(raw.metadata.state) ? ["accessToken", "refreshToken"] : [];
|
|
1238
|
+
if (Object.keys(raw.payload).length !== fields.length || fields.some((field) => typeof raw.payload[field] !== "string" || !raw.payload[field])) fail("invalid credential payload; only credential values are allowed");
|
|
1239
|
+
if (raw.kind === "api-key") validateApi(raw, key === ACTIVE_CONTEXTDB_CREDENTIAL);
|
|
1240
|
+
}
|
|
1241
|
+
function readDocument(path) {
|
|
1242
|
+
const bytes = readPrivate(path);
|
|
1243
|
+
if (bytes === void 0) return { version: 2, records: {} };
|
|
1244
|
+
let doc;
|
|
1245
|
+
try {
|
|
1246
|
+
doc = JSON.parse(bytes);
|
|
1247
|
+
} catch {
|
|
1248
|
+
return fail("credentials.json is not valid JSON");
|
|
1249
|
+
}
|
|
1250
|
+
if (!object2(doc) || ![1, 2].includes(doc.version) || !object2(doc.records)) return fail("unsupported credentials.json schema/version");
|
|
1251
|
+
if (doc.version === 1) {
|
|
1252
|
+
if (Object.keys(doc.records).some((k) => k !== ACTIVE_CONTEXTDB_CREDENTIAL)) fail("unsupported record key in v1 credentials");
|
|
1253
|
+
const active = doc.records[ACTIVE_CONTEXTDB_CREDENTIAL];
|
|
1254
|
+
if (active !== void 0) {
|
|
1255
|
+
if (active.kind !== "api-key") fail("unsupported record kind");
|
|
1256
|
+
validateApi(active, true, true);
|
|
1257
|
+
}
|
|
1258
|
+
} else for (const [key, record] of Object.entries(doc.records)) validate(key, record);
|
|
1259
|
+
return doc;
|
|
1260
|
+
}
|
|
1261
|
+
function upgrade(doc) {
|
|
1262
|
+
if (doc.version === 2) return doc;
|
|
1263
|
+
const record = doc.records[ACTIVE_CONTEXTDB_CREDENTIAL];
|
|
1264
|
+
return { ...doc, version: 2, records: record ? { [ACTIVE_CONTEXTDB_CREDENTIAL]: legacy(record) } : {} };
|
|
1265
|
+
}
|
|
1266
|
+
function legacy(record) {
|
|
1267
|
+
const { api_key, ...details } = record.payload;
|
|
1268
|
+
return { kind: record.kind, payload: { api_key }, metadata: {
|
|
1269
|
+
...details,
|
|
1270
|
+
...record.metadata,
|
|
1271
|
+
base_url: record.payload.base_url,
|
|
1272
|
+
login_server: record.payload.login_server,
|
|
1273
|
+
owner: { distribution: "internal" },
|
|
1274
|
+
state: "ready",
|
|
1275
|
+
revision: 1,
|
|
1276
|
+
generation: createHash2("sha256").update(JSON.stringify(record)).digest("hex").slice(0, 32)
|
|
1277
|
+
} };
|
|
1278
|
+
}
|
|
1279
|
+
function snapshot(doc, key) {
|
|
1280
|
+
const raw = Object.hasOwn(doc.records, key) ? doc.records[key] : void 0;
|
|
1281
|
+
const record = raw && (doc.version === 1 ? legacy(raw) : raw);
|
|
1282
|
+
return { key, record, fingerprint: createHash2("sha256").update(JSON.stringify(record) ?? "missing").digest("hex") };
|
|
1283
|
+
}
|
|
1284
|
+
function apiRecord(record) {
|
|
1285
|
+
if (!record) return void 0;
|
|
1286
|
+
if (record.kind !== "api-key" || record.metadata.owner.distribution !== "internal") return fail("invalid contextdb/active record");
|
|
1287
|
+
return {
|
|
1288
|
+
kind: "api-key",
|
|
1289
|
+
payload: { apiKey: record.payload.api_key },
|
|
1290
|
+
metadata: {
|
|
1291
|
+
baseUrl: origin(record.metadata.base_url),
|
|
1292
|
+
loginServer: origin(record.metadata.login_server),
|
|
1293
|
+
authorizationMethod: record.metadata.authorization_method,
|
|
1294
|
+
issuedAt: record.metadata.issued_at
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
function internalCandidate(record) {
|
|
1299
|
+
return {
|
|
1300
|
+
kind: "api-key",
|
|
1301
|
+
payload: { api_key: record.payload.apiKey },
|
|
1302
|
+
metadata: {
|
|
1303
|
+
base_url: record.metadata.baseUrl,
|
|
1304
|
+
login_server: record.metadata.loginServer,
|
|
1305
|
+
authorization_method: record.metadata.authorizationMethod,
|
|
1306
|
+
issued_at: record.metadata.issuedAt,
|
|
1307
|
+
owner: { distribution: "internal" },
|
|
1308
|
+
state: "ready"
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
var LocalCredentialProvider = class {
|
|
1313
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1314
|
+
path;
|
|
1315
|
+
constructor(path = defaultCredentialsPath()) {
|
|
1316
|
+
this.path = path;
|
|
1317
|
+
}
|
|
1318
|
+
snapshot(key) {
|
|
1319
|
+
return snapshot(readDocument(this.path), key);
|
|
1320
|
+
}
|
|
1321
|
+
readStoredRecord(key) {
|
|
1322
|
+
return this.snapshot(key).record;
|
|
1323
|
+
}
|
|
1324
|
+
async readRecord(key) {
|
|
1325
|
+
return apiRecord(this.readStoredRecord(key));
|
|
1326
|
+
}
|
|
1327
|
+
async describeRecord(key) {
|
|
1328
|
+
const r = this.readStoredRecord(key);
|
|
1329
|
+
return { configured: !!r && r.metadata.state === "ready", kind: r?.kind, writable: true };
|
|
1330
|
+
}
|
|
1331
|
+
onRecordUpdated(listener) {
|
|
1332
|
+
this.listeners.add(listener);
|
|
1333
|
+
return () => this.listeners.delete(listener);
|
|
1334
|
+
}
|
|
1335
|
+
/** Synchronous short commit; callbacks may acquire a config lock, never the reverse. */
|
|
1336
|
+
transaction(operation) {
|
|
1337
|
+
return withCredentialWriteLock(this.path, () => {
|
|
1338
|
+
let doc = readDocument(this.path);
|
|
1339
|
+
const read = (key) => snapshot(doc, key);
|
|
1340
|
+
const check = (expected) => {
|
|
1341
|
+
if (read(expected.key).fingerprint !== expected.fingerprint) fail("authentication changed; the newer credential was preserved, retry the command");
|
|
1342
|
+
};
|
|
1343
|
+
const save = () => {
|
|
1344
|
+
doc = upgrade(doc);
|
|
1345
|
+
atomicCredentialWrite(this.path, doc);
|
|
1346
|
+
};
|
|
1347
|
+
return operation({
|
|
1348
|
+
read,
|
|
1349
|
+
put: (key, candidate, expected) => {
|
|
1350
|
+
if (expected) {
|
|
1351
|
+
if (expected.key !== key) fail("invalid commit target");
|
|
1352
|
+
check(expected);
|
|
1353
|
+
}
|
|
1354
|
+
const before = read(key), previous = before.record;
|
|
1355
|
+
const next = { kind: candidate.kind, payload: candidate.payload, metadata: {
|
|
1356
|
+
...candidate.metadata,
|
|
1357
|
+
generation: previous?.metadata.generation ?? randomBytes2(16).toString("hex"),
|
|
1358
|
+
revision: (previous?.metadata.revision ?? 0) + 1
|
|
1359
|
+
} };
|
|
1360
|
+
validate(key, next);
|
|
1361
|
+
doc = upgrade(doc);
|
|
1362
|
+
doc.records[key] = next;
|
|
1363
|
+
save();
|
|
1364
|
+
return { before, after: read(key) };
|
|
1365
|
+
},
|
|
1366
|
+
remove: (expected) => {
|
|
1367
|
+
check(expected);
|
|
1368
|
+
if (!read(expected.key).record) return;
|
|
1369
|
+
doc = upgrade(doc);
|
|
1370
|
+
delete doc.records[expected.key];
|
|
1371
|
+
save();
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
commit(key, record, expected = this.snapshot(key)) {
|
|
1377
|
+
return this.transaction((tx) => tx.put(key, record, expected));
|
|
1378
|
+
}
|
|
1379
|
+
rollback(receipt) {
|
|
1380
|
+
return this.transaction((tx) => {
|
|
1381
|
+
if (tx.read(receipt.after.key).fingerprint !== receipt.after.fingerprint) return false;
|
|
1382
|
+
if (receipt.before.record) tx.put(receipt.after.key, receipt.before.record, receipt.after);
|
|
1383
|
+
else tx.remove(receipt.after);
|
|
1384
|
+
return true;
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
async modifyRecord(key, mutate, signal) {
|
|
1388
|
+
const before = this.snapshot(key), next = await mutate(apiRecord(before.record));
|
|
1389
|
+
signal?.throwIfAborted();
|
|
1390
|
+
if (next === void 0) return apiRecord(before.record);
|
|
1391
|
+
const receipt = this.commit(key, internalCandidate(next), before);
|
|
1392
|
+
for (const listener of this.listeners) {
|
|
1393
|
+
try {
|
|
1394
|
+
listener(key);
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return apiRecord(receipt.after.record);
|
|
1399
|
+
}
|
|
1400
|
+
async deleteRecord(key, signal) {
|
|
1401
|
+
signal?.throwIfAborted();
|
|
1402
|
+
const before = this.snapshot(key);
|
|
1403
|
+
this.transaction((tx) => tx.remove(before));
|
|
1404
|
+
for (const listener of this.listeners) {
|
|
1405
|
+
try {
|
|
1406
|
+
listener(key);
|
|
1407
|
+
} catch {
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
function readActiveCredentialSync(path = defaultCredentialsPath()) {
|
|
1413
|
+
return apiRecord(new LocalCredentialProvider(path).readStoredRecord(ACTIVE_CONTEXTDB_CREDENTIAL));
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1095
1416
|
// ../shared/src/oauth-credentials.ts
|
|
1096
1417
|
var OAuthCredentialError = class extends Error {
|
|
1097
1418
|
code;
|
|
@@ -1110,29 +1431,31 @@ var OAuthCredentialError = class extends Error {
|
|
|
1110
1431
|
var loginRequired = () => new OAuthCredentialError("login_required");
|
|
1111
1432
|
var unsafe = () => new OAuthCredentialError("unsafe_storage");
|
|
1112
1433
|
var OAuthSessionStore = class {
|
|
1113
|
-
|
|
1434
|
+
provider;
|
|
1114
1435
|
now;
|
|
1115
1436
|
lockWaitMs;
|
|
1116
|
-
|
|
1117
|
-
|
|
1437
|
+
path;
|
|
1438
|
+
constructor(path, options = {}) {
|
|
1439
|
+
this.path = path;
|
|
1440
|
+
this.provider = new LocalCredentialProvider(path);
|
|
1118
1441
|
this.now = options.now ?? Date.now;
|
|
1119
1442
|
this.lockWaitMs = options.lockWaitMs ?? 3e4;
|
|
1120
1443
|
}
|
|
1121
|
-
saveLogin(profile, session) {
|
|
1444
|
+
saveLogin(profile, session, loginId) {
|
|
1122
1445
|
requireProfile(profile);
|
|
1123
1446
|
validateSession(session);
|
|
1124
|
-
|
|
1125
|
-
const ref =
|
|
1126
|
-
this.write(ref, {
|
|
1447
|
+
if (loginId !== void 0) requireProfile(loginId);
|
|
1448
|
+
const ref = randomBytes3(16).toString("hex");
|
|
1449
|
+
this.write(ref, { profile, ...loginId ? { loginId } : {}, state: "ready", session });
|
|
1127
1450
|
return ref;
|
|
1128
1451
|
}
|
|
1129
|
-
status(ref,
|
|
1130
|
-
const record = this.read(ref,
|
|
1131
|
-
if (!record) return { state:
|
|
1452
|
+
status(ref, owner) {
|
|
1453
|
+
const record = this.read(ref, owner);
|
|
1454
|
+
if (!record) return { state: "missing" };
|
|
1132
1455
|
const s = record.session;
|
|
1133
1456
|
return {
|
|
1134
1457
|
state: record.state,
|
|
1135
|
-
profile,
|
|
1458
|
+
profile: record.profile,
|
|
1136
1459
|
baseUrl: s.baseUrl,
|
|
1137
1460
|
workspaceId: s.workspaceId,
|
|
1138
1461
|
memberId: s.memberId,
|
|
@@ -1143,12 +1466,12 @@ var OAuthSessionStore = class {
|
|
|
1143
1466
|
accessExpiresAt: s.expiresAt
|
|
1144
1467
|
};
|
|
1145
1468
|
}
|
|
1146
|
-
async resolve(ref,
|
|
1147
|
-
const first = this.read(ref,
|
|
1148
|
-
if (!first || first.state
|
|
1469
|
+
async resolve(ref, owner, transport) {
|
|
1470
|
+
const first = this.read(ref, owner);
|
|
1471
|
+
if (!first || ["reauth_required", "logged_out"].includes(first.state)) throw loginRequired();
|
|
1149
1472
|
if (first.state === "ready" && !this.needsRefresh(first.session)) return authorization(first.session);
|
|
1150
1473
|
return this.withLock(ref, async () => {
|
|
1151
|
-
const current = this.read(ref,
|
|
1474
|
+
const current = this.read(ref, owner);
|
|
1152
1475
|
if (!current || current.state !== "ready") throw loginRequired();
|
|
1153
1476
|
if (!this.needsRefresh(current.session)) return authorization(current.session);
|
|
1154
1477
|
const started = this.now();
|
|
@@ -1159,170 +1482,160 @@ var OAuthSessionStore = class {
|
|
|
1159
1482
|
if (next.expiresAt <= this.now()) throw loginRequired();
|
|
1160
1483
|
this.write(ref, { ...current, state: "ready", session: next });
|
|
1161
1484
|
return authorization(next);
|
|
1162
|
-
} catch {
|
|
1163
|
-
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
this.write(ref, { ...current, state: "reauth_required" });
|
|
1487
|
+
if (error instanceof OAuthCredentialError) throw error;
|
|
1164
1488
|
throw loginRequired();
|
|
1165
1489
|
}
|
|
1166
1490
|
});
|
|
1167
1491
|
}
|
|
1168
|
-
async
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
if (current)
|
|
1173
|
-
|
|
1174
|
-
|
|
1492
|
+
async share(ref, profile, loginId, commit) {
|
|
1493
|
+
requireProfile(loginId);
|
|
1494
|
+
return this.withLock(ref, async () => {
|
|
1495
|
+
const current = this.read(ref, profile);
|
|
1496
|
+
if (!current || current.state !== "ready") throw loginRequired();
|
|
1497
|
+
if (current.loginId && current.loginId !== loginId) throw new OAuthCredentialError("invalid_profile");
|
|
1498
|
+
const receipt = this.write(ref, { ...current, loginId });
|
|
1499
|
+
try {
|
|
1500
|
+
return commit();
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
this.provider.rollback(receipt);
|
|
1503
|
+
throw error;
|
|
1175
1504
|
}
|
|
1176
|
-
}
|
|
1177
|
-
} finally {
|
|
1178
|
-
this.prepareDirectory();
|
|
1179
|
-
this.atomicWrite(this.path(ref, ".logged-out"), "");
|
|
1180
|
-
remove(this.path(ref));
|
|
1181
|
-
}
|
|
1182
|
-
return { remoteRevoked };
|
|
1183
|
-
}
|
|
1184
|
-
needsRefresh(session) {
|
|
1185
|
-
const advance = Math.min(3e4, (session.expiresAt - session.issuedAt) / 2);
|
|
1186
|
-
return this.now() >= session.expiresAt - advance;
|
|
1505
|
+
});
|
|
1187
1506
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1507
|
+
/** Setup changes the DATA endpoint without replacing the identity or racing a refresh. */
|
|
1508
|
+
async changeBaseUrl(ref, owner, baseUrl, beforeWrite) {
|
|
1509
|
+
await this.withLock(ref, async () => {
|
|
1510
|
+
this.provider.transaction((tx) => {
|
|
1511
|
+
beforeWrite();
|
|
1512
|
+
const current = this.read(ref, owner);
|
|
1513
|
+
if (!current || current.state !== "ready") throw loginRequired();
|
|
1514
|
+
const session = { ...current.session, baseUrl };
|
|
1515
|
+
validateSession(session);
|
|
1516
|
+
const before = tx.read(ref), candidate = credentialForSession({ ...current, session });
|
|
1517
|
+
tx.put(ref, { ...candidate, metadata: { ...before.record?.metadata, ...candidate.metadata } }, before);
|
|
1518
|
+
});
|
|
1519
|
+
});
|
|
1191
1520
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1521
|
+
async logout(ref, owner, transport, beforeRevoke = () => {
|
|
1522
|
+
}) {
|
|
1523
|
+
return this.withLock(ref, async () => {
|
|
1524
|
+
const current = this.read(ref, owner);
|
|
1525
|
+
this.provider.transaction((tx) => {
|
|
1526
|
+
beforeRevoke();
|
|
1527
|
+
const before = tx.read(ref);
|
|
1528
|
+
if (current && before.record) tx.put(ref, { ...before.record, metadata: { ...before.record.metadata, state: "logged_out" }, payload: {} }, before);
|
|
1529
|
+
});
|
|
1530
|
+
let remoteRevoked = false;
|
|
1531
|
+
try {
|
|
1532
|
+
if (current?.session.refreshToken && current.state !== "logged_out") {
|
|
1533
|
+
await transport.revoke(current.session.refreshToken, current.session.baseUrl);
|
|
1534
|
+
remoteRevoked = true;
|
|
1535
|
+
}
|
|
1536
|
+
} catch {
|
|
1537
|
+
}
|
|
1538
|
+
return { remoteRevoked };
|
|
1539
|
+
});
|
|
1194
1540
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1541
|
+
/** Release config references under the write lock, then delete an unused session.
|
|
1542
|
+
* Waiting on the operation lock first lets an in-flight refresh supply its latest RT.
|
|
1543
|
+
* Returning false from release preserves a session that still has users.
|
|
1544
|
+
*/
|
|
1545
|
+
async retire(ref, owner, transport, release) {
|
|
1546
|
+
return this.withLock(ref, async () => {
|
|
1547
|
+
const current = this.read(ref, owner);
|
|
1548
|
+
const removed = this.provider.transaction((tx) => {
|
|
1549
|
+
if (!release()) return false;
|
|
1550
|
+
tx.remove(tx.read(ref));
|
|
1551
|
+
return true;
|
|
1552
|
+
});
|
|
1553
|
+
if (!removed) return { removed: false, remoteRevoked: false, remoteUnconfirmed: false };
|
|
1554
|
+
if (!current || current.state === "logged_out") return { removed: true, remoteRevoked: false, remoteUnconfirmed: false };
|
|
1198
1555
|
try {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1556
|
+
if (!current.session.refreshToken) throw loginRequired();
|
|
1557
|
+
await transport.revoke(current.session.refreshToken, current.session.baseUrl);
|
|
1558
|
+
return { removed: true, remoteRevoked: true, remoteUnconfirmed: false };
|
|
1559
|
+
} catch {
|
|
1560
|
+
return { removed: true, remoteRevoked: false, remoteUnconfirmed: true };
|
|
1203
1561
|
}
|
|
1204
|
-
}
|
|
1205
|
-
requirePrivate(this.directory, true);
|
|
1562
|
+
});
|
|
1206
1563
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
if (
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const fd = openSync2(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
1215
|
-
let text2;
|
|
1216
|
-
try {
|
|
1217
|
-
const stat = fstatSync(fd);
|
|
1218
|
-
if (stat.size > 65536 || !stat.isFile()) throw unsafe();
|
|
1219
|
-
text2 = readFileSync2(fd, "utf8");
|
|
1220
|
-
} finally {
|
|
1221
|
-
closeSync2(fd);
|
|
1222
|
-
}
|
|
1223
|
-
let doc;
|
|
1564
|
+
needsRefresh(session) {
|
|
1565
|
+
return this.now() >= session.expiresAt - Math.min(3e4, (session.expiresAt - session.issuedAt) / 2);
|
|
1566
|
+
}
|
|
1567
|
+
read(ref, owner) {
|
|
1568
|
+
if (!/^[a-f0-9]{32}$/.test(ref)) throw unsafe();
|
|
1569
|
+
requireProfile(typeof owner === "string" ? owner : owner.loginId);
|
|
1570
|
+
let stored;
|
|
1224
1571
|
try {
|
|
1225
|
-
|
|
1572
|
+
stored = this.provider.readStoredRecord(ref);
|
|
1226
1573
|
} catch {
|
|
1227
1574
|
throw unsafe();
|
|
1228
1575
|
}
|
|
1229
|
-
if (!
|
|
1230
|
-
if (
|
|
1231
|
-
|
|
1232
|
-
|
|
1576
|
+
if (!stored) return void 0;
|
|
1577
|
+
if (stored.kind !== "oauth-session" || stored.metadata.owner.distribution !== "public") throw new OAuthCredentialError("invalid_profile");
|
|
1578
|
+
const who = stored.metadata.owner;
|
|
1579
|
+
if (typeof owner === "string" ? who.agent !== owner : who.loginId !== owner.loginId) throw new OAuthCredentialError("invalid_profile");
|
|
1580
|
+
return { profile: who.agent, loginId: who.loginId, state: stored.metadata.state, session: oauthSessionFromCredential(stored) };
|
|
1233
1581
|
}
|
|
1234
1582
|
write(ref, doc) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
throw loginRequired();
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
atomicWrite(path, text2) {
|
|
1243
|
-
this.prepareDirectory();
|
|
1244
|
-
const temporary = path + "." + randomBytes(12).toString("hex") + ".tmp";
|
|
1245
|
-
let fd;
|
|
1246
|
-
try {
|
|
1247
|
-
fd = openSync2(temporary, "wx", 384);
|
|
1248
|
-
writeFileSync(fd, text2);
|
|
1249
|
-
fsyncSync(fd);
|
|
1250
|
-
closeSync2(fd);
|
|
1251
|
-
fd = void 0;
|
|
1252
|
-
renameSync2(temporary, path);
|
|
1253
|
-
if (process.platform !== "win32") {
|
|
1254
|
-
const dir = openSync2(this.directory, "r");
|
|
1255
|
-
try {
|
|
1256
|
-
fsyncSync(dir);
|
|
1257
|
-
} finally {
|
|
1258
|
-
closeSync2(dir);
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
} finally {
|
|
1262
|
-
if (fd !== void 0) closeSync2(fd);
|
|
1263
|
-
remove(temporary);
|
|
1264
|
-
}
|
|
1583
|
+
const before = this.provider.snapshot(ref);
|
|
1584
|
+
if (before.record && ["logged_out", "reauth_required"].includes(before.record.metadata.state)) throw loginRequired();
|
|
1585
|
+
const candidate = credentialForSession(doc);
|
|
1586
|
+
return this.provider.commit(ref, { ...candidate, metadata: { ...before.record?.metadata, ...candidate.metadata } }, before);
|
|
1265
1587
|
}
|
|
1266
1588
|
async withLock(ref, task) {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
fd = openSync2(path, "wx", 384);
|
|
1274
|
-
} catch (error) {
|
|
1275
|
-
if (error.code !== "EEXIST") throw unsafe();
|
|
1276
|
-
if (Date.now() >= deadline) throw new OAuthCredentialError("credential_busy");
|
|
1277
|
-
await delay2(25);
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
try {
|
|
1281
|
-
writeFileSync(fd, String(process.pid));
|
|
1282
|
-
fsyncSync(fd);
|
|
1283
|
-
} catch {
|
|
1284
|
-
closeSync2(fd);
|
|
1285
|
-
remove(path);
|
|
1286
|
-
throw unsafe();
|
|
1287
|
-
}
|
|
1288
|
-
closeSync2(fd);
|
|
1289
|
-
try {
|
|
1290
|
-
return await task();
|
|
1291
|
-
} finally {
|
|
1292
|
-
remove(path);
|
|
1293
|
-
}
|
|
1589
|
+
if (!/^[a-f0-9]{32}$/.test(ref)) throw unsafe();
|
|
1590
|
+
try {
|
|
1591
|
+
return await withCredentialOperation(this.path, ref, task, { lockWaitMs: this.lockWaitMs });
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
if (error instanceof Error && error.message === "credentials: credential_busy") throw new OAuthCredentialError("credential_busy");
|
|
1594
|
+
throw error;
|
|
1294
1595
|
}
|
|
1295
1596
|
}
|
|
1296
1597
|
};
|
|
1297
|
-
function
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
throw unsafe();
|
|
1598
|
+
function credentialForSession(doc) {
|
|
1599
|
+
const { accessToken, refreshToken, ...metadata } = doc.session;
|
|
1600
|
+
return {
|
|
1601
|
+
kind: "oauth-session",
|
|
1602
|
+
payload: ["ready", "refreshing"].includes(doc.state) ? { accessToken, refreshToken } : {},
|
|
1603
|
+
metadata: {
|
|
1604
|
+
...metadata,
|
|
1605
|
+
owner: { distribution: "public", agent: doc.profile, ...doc.loginId ? { loginId: doc.loginId } : {} },
|
|
1606
|
+
state: doc.state,
|
|
1607
|
+
authorization_method: "device-code"
|
|
1308
1608
|
}
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1311
|
-
if ((s.mode & 63) !== 0 || process.getuid && s.uid !== process.getuid()) throw unsafe();
|
|
1609
|
+
};
|
|
1312
1610
|
}
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1611
|
+
function oauthSessionFromCredential(record) {
|
|
1612
|
+
if (record.kind !== "oauth-session") throw unsafe();
|
|
1613
|
+
const m = record.metadata, p = record.payload;
|
|
1614
|
+
const session = {
|
|
1615
|
+
baseUrl: m.baseUrl,
|
|
1616
|
+
workspaceId: m.workspaceId,
|
|
1617
|
+
memberId: m.memberId,
|
|
1618
|
+
clientId: m.clientId,
|
|
1619
|
+
scope: m.scope,
|
|
1620
|
+
sessionId: m.sessionId,
|
|
1621
|
+
accountDisplay: m.accountDisplay,
|
|
1622
|
+
issuedAt: m.issuedAt,
|
|
1623
|
+
expiresAt: m.expiresAt,
|
|
1624
|
+
accessToken: p.accessToken,
|
|
1625
|
+
refreshToken: p.refreshToken
|
|
1626
|
+
};
|
|
1627
|
+
validateSession(session, ["ready", "refreshing"].includes(m.state));
|
|
1628
|
+
return session;
|
|
1319
1629
|
}
|
|
1320
|
-
function
|
|
1630
|
+
function requireProfile(profile) {
|
|
1631
|
+
if (typeof profile !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(profile)) throw new OAuthCredentialError("invalid_profile");
|
|
1632
|
+
}
|
|
1633
|
+
function validateSession(s, requireTokens = true) {
|
|
1321
1634
|
if (!s || typeof s !== "object" || s.clientId !== "ctxdb-cli" || !["read_only", "read_write", "default"].includes(s.scope) || !Number.isSafeInteger(s.issuedAt) || !Number.isSafeInteger(s.expiresAt) || s.expiresAt - s.issuedAt < 1e3 || s.expiresAt - s.issuedAt > 36e5) throw unsafe();
|
|
1322
1635
|
for (const value of [s.workspaceId, s.memberId, s.sessionId, s.accountDisplay]) {
|
|
1323
1636
|
if (typeof value !== "string" || !value || value.length > 256 || /[\r\n\x00-\x1f]/.test(value)) throw unsafe();
|
|
1324
1637
|
}
|
|
1325
|
-
for (const value of [s.accessToken, s.refreshToken]) {
|
|
1638
|
+
for (const value of requireTokens ? [s.accessToken, s.refreshToken] : []) {
|
|
1326
1639
|
if (typeof value !== "string" || !value || value.length > 8192 || /\s/.test(value)) throw unsafe();
|
|
1327
1640
|
}
|
|
1328
1641
|
let url;
|
|
@@ -1351,8 +1664,56 @@ function authorization(session) {
|
|
|
1351
1664
|
}
|
|
1352
1665
|
|
|
1353
1666
|
// ../shared/src/oauth-profile.ts
|
|
1354
|
-
import {
|
|
1355
|
-
|
|
1667
|
+
import { dirname as dirname4, join as join4, resolve } from "path";
|
|
1668
|
+
|
|
1669
|
+
// ../shared/src/auth-config.ts
|
|
1670
|
+
import { closeSync as closeSync3, existsSync as existsSync3, fsyncSync as fsyncSync2, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1671
|
+
import { createHash as createHash3, randomBytes as randomBytes4 } from "crypto";
|
|
1672
|
+
import { dirname as dirname3 } from "path";
|
|
1673
|
+
var AuthConfigError = class extends Error {
|
|
1674
|
+
constructor(message) {
|
|
1675
|
+
super(message);
|
|
1676
|
+
this.name = "AuthConfigError";
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
function readAuthConfig(path) {
|
|
1680
|
+
if (!existsSync3(path)) return { version: 2, agents: {} };
|
|
1681
|
+
let raw;
|
|
1682
|
+
try {
|
|
1683
|
+
raw = JSON.parse(readFileSync3(path, "utf8"));
|
|
1684
|
+
} catch {
|
|
1685
|
+
throw new AuthConfigError("Invalid ctxdb.json; repair the JSON before changing authentication.");
|
|
1686
|
+
}
|
|
1687
|
+
if (raw?.version !== 2 || !object3(raw.agents) || raw.logins !== void 0 && !object3(raw.logins)) {
|
|
1688
|
+
throw new AuthConfigError("Unsupported ctxdb configuration; authentication was not changed.");
|
|
1689
|
+
}
|
|
1690
|
+
return raw;
|
|
1691
|
+
}
|
|
1692
|
+
function object3(value) {
|
|
1693
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1694
|
+
}
|
|
1695
|
+
function ownAuthProfile(raw, agent) {
|
|
1696
|
+
const value = Object.hasOwn(raw.agents, agent) ? raw.agents[agent] : void 0;
|
|
1697
|
+
if (value !== void 0 && !object3(value)) throw new AuthConfigError("Invalid agent configuration; repair its section in ctxdb.json.");
|
|
1698
|
+
return value ?? {};
|
|
1699
|
+
}
|
|
1700
|
+
function requireLoginId(id) {
|
|
1701
|
+
if (typeof id !== "string" || id === "default" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(id) || ["__proto__", "constructor", "prototype"].includes(id)) throw new AuthConfigError("Invalid login ID; use an ID from ctxdb auth status --all.");
|
|
1702
|
+
}
|
|
1703
|
+
function validateLoginChoice(section) {
|
|
1704
|
+
if (Object.hasOwn(section, "credential_ref")) {
|
|
1705
|
+
if (typeof section.credential_ref !== "string" || !/^[a-f0-9]{32}$/.test(section.credential_ref)) throw new AuthConfigError("Invalid credential_ref; explicitly select --api-key or --login again.");
|
|
1706
|
+
if (Object.hasOwn(section, "access_credential") || section.api_key || section.oauth_credential_ref) throw new AuthConfigError("Conflicting credential_ref and access_credential/api_key/oauth_credential_ref. Keep one authentication choice.");
|
|
1707
|
+
}
|
|
1708
|
+
if (!Object.hasOwn(section, "access_credential")) return;
|
|
1709
|
+
if (section.access_credential === null) return;
|
|
1710
|
+
requireLoginId(section.access_credential);
|
|
1711
|
+
if (section.api_key || section.oauth_credential_ref) {
|
|
1712
|
+
throw new AuthConfigError("Conflicting access_credential and api_key/oauth_credential_ref in this Agent. Keep one authentication choice, or use setup --api-key/--login to replace it.");
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// ../shared/src/oauth-profile.ts
|
|
1356
1717
|
function oauthProfile(reference, configPath, profile, baseUrl) {
|
|
1357
1718
|
if (reference === void 0) return void 0;
|
|
1358
1719
|
if (typeof reference !== "string" || !/^[a-f0-9]{32}$/.test(reference) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(profile)) throw new OAuthCredentialError("unsafe_storage");
|
|
@@ -1361,33 +1722,149 @@ function oauthProfile(reference, configPath, profile, baseUrl) {
|
|
|
1361
1722
|
reference,
|
|
1362
1723
|
profile,
|
|
1363
1724
|
configPath: path,
|
|
1364
|
-
|
|
1725
|
+
credentialsPath: join4(dirname4(path), "credentials.json"),
|
|
1365
1726
|
baseUrl: normalizeCoreOrigin(baseUrl)
|
|
1366
1727
|
};
|
|
1367
1728
|
}
|
|
1729
|
+
function oauthProfileFromConfig(raw, path, agent, baseUrlOverride) {
|
|
1730
|
+
const section = ownAuthProfile(raw, agent);
|
|
1731
|
+
validateLoginChoice(section);
|
|
1732
|
+
if (section.access_credential === null) return void 0;
|
|
1733
|
+
if (Object.hasOwn(section, "access_credential")) {
|
|
1734
|
+
const id = section.access_credential;
|
|
1735
|
+
requireLoginId(id);
|
|
1736
|
+
const record = raw.logins && Object.hasOwn(raw.logins, id) ? raw.logins[id] : void 0;
|
|
1737
|
+
if (!record?.credential_ref) throw new AuthConfigError("access_credential points to a missing login. Use ctxdb auth status --all and explicitly bind a valid login or log in again.");
|
|
1738
|
+
return storedProfile(record.credential_ref, path, agent, id, baseUrlOverride);
|
|
1739
|
+
}
|
|
1740
|
+
return storedProfile(section.oauth_credential_ref, path, agent, void 0, baseUrlOverride);
|
|
1741
|
+
}
|
|
1742
|
+
function storedProfile(reference, path, agent, loginId, baseUrlOverride) {
|
|
1743
|
+
const descriptor = oauthProfile(reference, path, agent, "https://api.cn-hangzhou.agentcontext.aliyuncs.com");
|
|
1744
|
+
if (!descriptor) return void 0;
|
|
1745
|
+
const profile = { ...descriptor, ...loginId ? { loginId } : {} };
|
|
1746
|
+
const status = new OAuthSessionStore(profile.credentialsPath).status(profile.reference, oauthOwner(profile));
|
|
1747
|
+
if (!("baseUrl" in status) || typeof status.baseUrl !== "string") throw new OAuthCredentialError("login_required");
|
|
1748
|
+
profile.baseUrl = normalizeCoreOrigin(status.baseUrl);
|
|
1749
|
+
if (baseUrlOverride && normalizeCoreOrigin(baseUrlOverride) !== profile.baseUrl) {
|
|
1750
|
+
throw new AuthConfigError("Requested base URL does not match the login Core origin; no token was sent.");
|
|
1751
|
+
}
|
|
1752
|
+
return profile;
|
|
1753
|
+
}
|
|
1754
|
+
function oauthOwner(profile) {
|
|
1755
|
+
return profile.loginId ? { loginId: profile.loginId } : profile.profile;
|
|
1756
|
+
}
|
|
1757
|
+
function oauthProfileStatus(profile) {
|
|
1758
|
+
const status = new OAuthSessionStore(profile.credentialsPath).status(profile.reference, oauthOwner(profile));
|
|
1759
|
+
if (typeof status.baseUrl === "string" && normalizeCoreOrigin(status.baseUrl) !== profile.baseUrl) {
|
|
1760
|
+
throw new AuthConfigError("Requested base URL does not match the login Core origin; no token was sent.");
|
|
1761
|
+
}
|
|
1762
|
+
return status;
|
|
1763
|
+
}
|
|
1368
1764
|
var OAuthProfileProvider = class {
|
|
1369
1765
|
selected;
|
|
1370
1766
|
constructor(selected) {
|
|
1371
1767
|
this.selected = selected;
|
|
1372
1768
|
}
|
|
1373
|
-
async resolve() {
|
|
1769
|
+
async resolve(options = {}) {
|
|
1770
|
+
const timeoutMs = options.timeoutMs ?? 3e4, deadline = Date.now() + timeoutMs;
|
|
1374
1771
|
const selected = this.selected;
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
raw
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
}
|
|
1381
|
-
const profile = raw?.version === 2 ? raw.agents?.[selected.profile] : void 0;
|
|
1382
|
-
if (!profile || !Object.hasOwn(profile, "oauth_credential_ref")) throw new OAuthCredentialError("login_required");
|
|
1383
|
-
const current = oauthProfile(profile.oauth_credential_ref, selected.configPath, selected.profile, profile.base_url);
|
|
1772
|
+
const readCurrent = () => {
|
|
1773
|
+
const raw = readAuthConfig(selected.configPath);
|
|
1774
|
+
return oauthProfileFromConfig(raw, selected.configPath, selected.profile);
|
|
1775
|
+
};
|
|
1776
|
+
const current = readCurrent();
|
|
1384
1777
|
if (!current || current.baseUrl !== selected.baseUrl) throw new OAuthCredentialError("login_required");
|
|
1385
|
-
const store = new OAuthSessionStore(current.
|
|
1386
|
-
const status =
|
|
1778
|
+
const store = new OAuthSessionStore(current.credentialsPath, { lockWaitMs: timeoutMs });
|
|
1779
|
+
const status = oauthProfileStatus(current);
|
|
1387
1780
|
if (!("baseUrl" in status) || typeof status.baseUrl !== "string" || normalizeCoreOrigin(status.baseUrl) !== current.baseUrl) throw new OAuthCredentialError("login_required");
|
|
1388
|
-
|
|
1781
|
+
const authorization2 = await store.resolve(current.reference, oauthOwner(current), {
|
|
1782
|
+
refresh: (rt, url) => new CoreDeviceClient(url, { requestTimeoutMs: Math.max(1, deadline - Date.now()) }).refresh(rt, url)
|
|
1783
|
+
});
|
|
1784
|
+
const after = readCurrent();
|
|
1785
|
+
if (!after || after.reference !== current.reference || after.loginId !== current.loginId || after.baseUrl !== current.baseUrl || oauthProfileStatus(after).state !== "ready") throw new OAuthCredentialError("login_required");
|
|
1786
|
+
return authorization2;
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
// ../shared/src/login-bindings.ts
|
|
1791
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
1792
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
1793
|
+
|
|
1794
|
+
// ../shared/src/data-auth.ts
|
|
1795
|
+
import { dirname as dirname5, join as join5 } from "path";
|
|
1796
|
+
function selectDataApiCredential(cfg, env = process.env) {
|
|
1797
|
+
const accessToken = env.CTXDB_ACCESS_TOKEN?.trim();
|
|
1798
|
+
if (accessToken) return { type: "access-token", value: accessToken };
|
|
1799
|
+
if (env.CTXDB_API_KEY) return { type: "api-key", value: env.CTXDB_API_KEY };
|
|
1800
|
+
const context = cfg.authContext ? { context: cfg.authContext } : {};
|
|
1801
|
+
if (cfg.oauthCredential) return { type: "oauth-session", profile: cfg.oauthCredential, ...context };
|
|
1802
|
+
return cfg.apiKey ? { type: "api-key", value: cfg.apiKey, ...context } : null;
|
|
1803
|
+
}
|
|
1804
|
+
var DataApiAuthorizationProvider = class {
|
|
1805
|
+
context;
|
|
1806
|
+
constructor(context) {
|
|
1807
|
+
this.context = context;
|
|
1808
|
+
}
|
|
1809
|
+
async resolve(options = {}) {
|
|
1810
|
+
const c = this.context, raw = readAuthConfig(c.configPath), section = ownAuthProfile(raw, c.agent);
|
|
1811
|
+
const fallback = c.inheritDefaultKey && c.agent !== "default" ? ownAuthProfile(raw, "default") : {};
|
|
1812
|
+
if (c.managedCredentials) {
|
|
1813
|
+
const active = readActiveCredentialSync(c.credentialsPath ?? join5(dirname5(c.configPath), "credentials.json"));
|
|
1814
|
+
if (active) {
|
|
1815
|
+
const baseUrl2 = c.baseUrlOverride ?? active.metadata.baseUrl;
|
|
1816
|
+
if (baseUrl2 !== c.baseUrl) throw new OAuthCredentialError("login_required");
|
|
1817
|
+
return { scheme: "Token", value: active.payload.apiKey, baseUrl: baseUrl2, expiresAt: null };
|
|
1818
|
+
}
|
|
1819
|
+
throw new OAuthCredentialError("login_required");
|
|
1820
|
+
}
|
|
1821
|
+
const stored = publicAuthenticationFromConfig(raw, c.configPath, c.agent, c.baseUrlOverride);
|
|
1822
|
+
const baseUrl = stored?.baseUrl ?? String(c.baseUrlOverride || section.base_url || fallback.base_url || "https://api.cn-hangzhou.agentcontext.aliyuncs.com").replace(/\/+$/, "");
|
|
1823
|
+
if (baseUrl !== c.baseUrl) throw new OAuthCredentialError("login_required");
|
|
1824
|
+
if (stored?.apiKey) return { scheme: "Token", value: stored.apiKey, baseUrl, expiresAt: null };
|
|
1825
|
+
if (stored?.oauthCredential) return new OAuthProfileProvider(stored.oauthCredential).resolve(options);
|
|
1826
|
+
if (Object.hasOwn(section, "access_credential")) throw new OAuthCredentialError("login_required");
|
|
1827
|
+
const key = Object.hasOwn(section, "api_key") ? section.api_key : fallback.api_key;
|
|
1828
|
+
if (typeof key !== "string" || !key) throw new OAuthCredentialError("login_required");
|
|
1829
|
+
return { scheme: "Token", value: key, baseUrl, expiresAt: null };
|
|
1389
1830
|
}
|
|
1390
1831
|
};
|
|
1832
|
+
function publicAuthenticationFromConfig(raw, path, agent, baseUrlOverride) {
|
|
1833
|
+
const section = ownAuthProfile(raw, agent);
|
|
1834
|
+
validateLoginChoice(section);
|
|
1835
|
+
if (!Object.hasOwn(section, "credential_ref")) {
|
|
1836
|
+
const oauthCredential = oauthProfileFromConfig(raw, path, agent, baseUrlOverride);
|
|
1837
|
+
return oauthCredential ? { baseUrl: oauthCredential.baseUrl, oauthCredential } : void 0;
|
|
1838
|
+
}
|
|
1839
|
+
const r = new LocalCredentialProvider(join5(dirname5(path), "credentials.json")).readStoredRecord(section.credential_ref);
|
|
1840
|
+
if (!r || r.kind !== "api-key" || r.metadata.state !== "ready" || r.metadata.owner.distribution !== "public" || r.metadata.owner.agent !== agent || r.metadata.owner.loginId) {
|
|
1841
|
+
throw new AuthConfigError("credential_ref is missing, invalid or belongs to another Agent. Use setup --api-key/--login to choose a valid credential.");
|
|
1842
|
+
}
|
|
1843
|
+
const baseUrl = String(r.metadata.base_url).replace(/\/+$/, "");
|
|
1844
|
+
if (baseUrlOverride && baseUrl !== baseUrlOverride.replace(/\/+$/, "")) throw new AuthConfigError("Requested base URL does not match the credential origin; no credential was sent.");
|
|
1845
|
+
return { baseUrl, apiKey: r.payload.api_key };
|
|
1846
|
+
}
|
|
1847
|
+
function createRequestAuthorizationProvider(credential, baseUrl) {
|
|
1848
|
+
if (credential?.context) return new DataApiAuthorizationProvider(credential.context);
|
|
1849
|
+
if (credential?.type === "oauth-session") return new OAuthProfileProvider(credential.profile);
|
|
1850
|
+
return { async resolve(_options) {
|
|
1851
|
+
if (!credential) throw new OAuthCredentialError("login_required");
|
|
1852
|
+
return {
|
|
1853
|
+
scheme: credential.type === "access-token" ? "Bearer" : "Token",
|
|
1854
|
+
value: credential.value,
|
|
1855
|
+
baseUrl,
|
|
1856
|
+
expiresAt: null
|
|
1857
|
+
};
|
|
1858
|
+
} };
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// ../shared/src/authorization/public-target.ts
|
|
1862
|
+
import { dirname as dirname7, join as join7 } from "path";
|
|
1863
|
+
|
|
1864
|
+
// src/config.ts
|
|
1865
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1866
|
+
import { homedir as homedir3 } from "os";
|
|
1867
|
+
import { dirname as dirname8, join as join8 } from "path";
|
|
1391
1868
|
|
|
1392
1869
|
// src/distribution-capabilities.ts
|
|
1393
1870
|
var PUBLIC_DISTRIBUTION = {
|
|
@@ -1403,7 +1880,7 @@ var PUBLIC_DISTRIBUTION = {
|
|
|
1403
1880
|
var DISTRIBUTION_MANIFEST = typeof define_CTXDB_DISTRIBUTION_MANIFEST_default === "undefined" ? PUBLIC_DISTRIBUTION : define_CTXDB_DISTRIBUTION_MANIFEST_default;
|
|
1404
1881
|
|
|
1405
1882
|
// src/config.ts
|
|
1406
|
-
var DEFAULT_BASE_URL = "https://
|
|
1883
|
+
var DEFAULT_BASE_URL = "https://api.cn-hangzhou.agentcontext.aliyuncs.com";
|
|
1407
1884
|
var DEFAULT_USER_ID = "default";
|
|
1408
1885
|
var DEFAULT_TOP_K = 5;
|
|
1409
1886
|
var DEFAULT_THRESHOLD = 0.4;
|
|
@@ -1411,7 +1888,7 @@ var DEFAULT_KNOWLEDGE_TOP_K = 6;
|
|
|
1411
1888
|
var DEFAULT_KB_CATALOG_INJECTION = "session_start";
|
|
1412
1889
|
function defaultPath(env) {
|
|
1413
1890
|
if (env.CTXDB_CONFIG_PATH) return env.CTXDB_CONFIG_PATH;
|
|
1414
|
-
return
|
|
1891
|
+
return join8(homedir3(), ".ctxdb", "ctxdb.json");
|
|
1415
1892
|
}
|
|
1416
1893
|
function coerceInt(v, fallback) {
|
|
1417
1894
|
if (v === null || v === void 0 || v === "") return fallback;
|
|
@@ -1433,7 +1910,7 @@ function coerceKbCatalogInjection(v) {
|
|
|
1433
1910
|
return DEFAULT_KB_CATALOG_INJECTION;
|
|
1434
1911
|
}
|
|
1435
1912
|
function readRaw(path) {
|
|
1436
|
-
if (!
|
|
1913
|
+
if (!existsSync4(path)) return {};
|
|
1437
1914
|
try {
|
|
1438
1915
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
1439
1916
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
@@ -1470,40 +1947,6 @@ function applyDebugPolicy(cfg) {
|
|
|
1470
1947
|
cfg.debugReason = policy.debugReason;
|
|
1471
1948
|
return cfg;
|
|
1472
1949
|
}
|
|
1473
|
-
function managedCredential(path) {
|
|
1474
|
-
if (!existsSync3(path)) return null;
|
|
1475
|
-
if (process.platform !== "win32" && (statSync2(path).mode & 63) !== 0) {
|
|
1476
|
-
throw new Error(`credentials: ${path} must be owner-only (run chmod 600)`);
|
|
1477
|
-
}
|
|
1478
|
-
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
1479
|
-
if (raw.version !== 1 || !raw.records || typeof raw.records !== "object") {
|
|
1480
|
-
throw new Error("credentials: unsupported credentials.json schema");
|
|
1481
|
-
}
|
|
1482
|
-
const unknownKeys = Object.keys(raw.records).filter(
|
|
1483
|
-
(key) => key !== "contextdb/active"
|
|
1484
|
-
);
|
|
1485
|
-
if (unknownKeys.length > 0) {
|
|
1486
|
-
throw new Error(`credentials: unsupported record key ${unknownKeys[0]}`);
|
|
1487
|
-
}
|
|
1488
|
-
const record = raw.records["contextdb/active"];
|
|
1489
|
-
if (!record) return null;
|
|
1490
|
-
if (record.kind !== "api-key" || typeof record.payload?.api_key !== "string" || !record.payload.api_key || typeof record.payload?.base_url !== "string" || !record.payload.base_url) {
|
|
1491
|
-
throw new Error("credentials: invalid contextdb/active record");
|
|
1492
|
-
}
|
|
1493
|
-
let baseUrl;
|
|
1494
|
-
try {
|
|
1495
|
-
baseUrl = new URL(record.payload.base_url);
|
|
1496
|
-
} catch {
|
|
1497
|
-
throw new Error("credentials: invalid contextdb/active base_url");
|
|
1498
|
-
}
|
|
1499
|
-
if (!["https:", "http:"].includes(baseUrl.protocol) || baseUrl.username || baseUrl.password || baseUrl.search || baseUrl.hash) {
|
|
1500
|
-
throw new Error("credentials: invalid contextdb/active base_url");
|
|
1501
|
-
}
|
|
1502
|
-
return {
|
|
1503
|
-
apiKey: record.payload.api_key,
|
|
1504
|
-
baseUrl: baseUrl.toString().replace(/\/$/, "")
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
1950
|
function loadOpencodeConfig(options = {}) {
|
|
1508
1951
|
const env = options.env ?? process.env;
|
|
1509
1952
|
const path = options.path ?? defaultPath(env);
|
|
@@ -1529,16 +1972,41 @@ function loadOpencodeConfig(options = {}) {
|
|
|
1529
1972
|
debugReason: null,
|
|
1530
1973
|
kbCatalogInjection: coerceKbCatalogInjection(section.kb_catalog_injection)
|
|
1531
1974
|
};
|
|
1532
|
-
const managed = DISTRIBUTION_MANIFEST.capabilities.managedCredentials && options.managedCredentials !== false ?
|
|
1533
|
-
options.credentialsPath ??
|
|
1975
|
+
const managed = DISTRIBUTION_MANIFEST.capabilities.managedCredentials && options.managedCredentials !== false ? readActiveCredentialSync(
|
|
1976
|
+
options.credentialsPath ?? join8(dirname8(path), "credentials.json")
|
|
1534
1977
|
) : null;
|
|
1535
1978
|
if (managed) {
|
|
1536
|
-
cfg.apiKey = managed.apiKey;
|
|
1537
|
-
cfg.baseUrl = managed.baseUrl;
|
|
1979
|
+
cfg.apiKey = managed.payload.apiKey;
|
|
1980
|
+
cfg.baseUrl = managed.metadata.baseUrl;
|
|
1981
|
+
}
|
|
1982
|
+
if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
|
|
1983
|
+
validateLoginChoice(section);
|
|
1984
|
+
const stored = env.CTXDB_API_KEY || env.CTXDB_ACCESS_TOKEN?.trim() ? void 0 : publicAuthenticationFromConfig({ ...raw, version: 2, agents: raw.agents ?? {} }, path, "opencode", env.CTXDB_BASE_URL);
|
|
1985
|
+
if (stored) {
|
|
1986
|
+
cfg.baseUrl = stored.baseUrl;
|
|
1987
|
+
cfg.apiKey = stored.apiKey ?? null;
|
|
1988
|
+
cfg.oauthCredential = stored.oauthCredential;
|
|
1989
|
+
} else if (section.credential_ref) cfg.apiKey = null;
|
|
1538
1990
|
}
|
|
1539
1991
|
applyEnv(cfg, env);
|
|
1992
|
+
if (managed) cfg.authContext = {
|
|
1993
|
+
configPath: path,
|
|
1994
|
+
agent: "opencode",
|
|
1995
|
+
baseUrl: cfg.baseUrl,
|
|
1996
|
+
inheritDefaultKey: false,
|
|
1997
|
+
managedCredentials: true,
|
|
1998
|
+
credentialsPath: options.credentialsPath,
|
|
1999
|
+
...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
|
|
2000
|
+
};
|
|
1540
2001
|
if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
|
|
1541
|
-
cfg.
|
|
2002
|
+
cfg.authContext = {
|
|
2003
|
+
configPath: path,
|
|
2004
|
+
agent: "opencode",
|
|
2005
|
+
baseUrl: cfg.baseUrl,
|
|
2006
|
+
inheritDefaultKey: false,
|
|
2007
|
+
...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
|
|
2008
|
+
};
|
|
2009
|
+
if (Object.hasOwn(section, "access_credential") && !env.CTXDB_API_KEY) cfg.apiKey = null;
|
|
1542
2010
|
}
|
|
1543
2011
|
if (env.CTXDB_API_KEY || accessTokenFromEnv(env)) cfg.oauthCredential = void 0;
|
|
1544
2012
|
return cfg;
|
|
@@ -1566,25 +2034,28 @@ var HttpClient = class {
|
|
|
1566
2034
|
userAgent;
|
|
1567
2035
|
fetchImpl;
|
|
1568
2036
|
oauthProvider;
|
|
2037
|
+
credential;
|
|
1569
2038
|
constructor(opts) {
|
|
1570
2039
|
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
1571
2040
|
this.apiKey = opts.apiKey;
|
|
1572
2041
|
this.accessToken = opts.accessToken ?? null;
|
|
1573
|
-
this.
|
|
2042
|
+
this.credential = selectDataApiCredential(opts, { CTXDB_ACCESS_TOKEN: opts.accessToken ?? void 0, CTXDB_API_KEY: opts.environmentApiKey });
|
|
2043
|
+
this.oauthProvider = createRequestAuthorizationProvider(this.credential, this.baseUrl);
|
|
1574
2044
|
this.userAgent = opts.userAgent ?? "ctxdb-opencode-plugin/0.0.0";
|
|
1575
2045
|
this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
1576
2046
|
}
|
|
1577
|
-
async headers(contentType) {
|
|
2047
|
+
async headers(contentType, timeoutMs) {
|
|
1578
2048
|
const h = {
|
|
1579
2049
|
"User-Agent": this.userAgent,
|
|
1580
2050
|
Connection: "close"
|
|
1581
2051
|
};
|
|
1582
|
-
if (this.
|
|
1583
|
-
|
|
1584
|
-
const authorization2 = await this.oauthProvider.resolve();
|
|
2052
|
+
if (this.oauthProvider) {
|
|
2053
|
+
const authorization2 = await this.oauthProvider.resolve({ timeoutMs });
|
|
1585
2054
|
if (authorization2.baseUrl !== this.baseUrl) throw new Error("OAuth environment changed; reload this runtime.");
|
|
1586
|
-
h.Authorization =
|
|
1587
|
-
} else if (this.
|
|
2055
|
+
h.Authorization = `${authorization2.scheme} ${authorization2.value}`;
|
|
2056
|
+
} else if (this.credential && this.credential.type !== "oauth-session") {
|
|
2057
|
+
h.Authorization = `${this.credential.type === "access-token" ? "Bearer" : "Token"} ${this.credential.value}`;
|
|
2058
|
+
}
|
|
1588
2059
|
if (contentType) h["Content-Type"] = contentType;
|
|
1589
2060
|
return h;
|
|
1590
2061
|
}
|
|
@@ -1605,12 +2076,12 @@ var HttpClient = class {
|
|
|
1605
2076
|
return this.request("POST", url, path, JSON.stringify(body), "application/json", options.timeoutMs);
|
|
1606
2077
|
}
|
|
1607
2078
|
async request(method, url, path, body, contentType, timeoutMs) {
|
|
1608
|
-
const
|
|
1609
|
-
const
|
|
2079
|
+
const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS, started = Date.now();
|
|
2080
|
+
const headers = await this.headers(contentType, effectiveTimeout);
|
|
2081
|
+
const bearer = headers.Authorization?.replace(/^(Bearer|Token) /, "");
|
|
1610
2082
|
const safeText = (value) => bearer ? String(value).split(bearer).join("[REDACTED]") : String(value);
|
|
1611
|
-
const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1612
2083
|
const controller = new AbortController();
|
|
1613
|
-
const timer = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
2084
|
+
const timer = setTimeout(() => controller.abort(), Math.max(1, effectiveTimeout - (Date.now() - started)));
|
|
1614
2085
|
try {
|
|
1615
2086
|
let resp;
|
|
1616
2087
|
try {
|
|
@@ -1630,7 +2101,8 @@ var HttpClient = class {
|
|
|
1630
2101
|
if (resp.status === 204) return {};
|
|
1631
2102
|
let text2;
|
|
1632
2103
|
try {
|
|
1633
|
-
|
|
2104
|
+
const bodyText = await resp.text();
|
|
2105
|
+
text2 = headers.Authorization?.startsWith("Bearer ") || !resp.ok ? safeText(bodyText) : bodyText;
|
|
1634
2106
|
} catch (err) {
|
|
1635
2107
|
throw new CtxdbHttpError(path, resp.status, `body read failed: ${safeText(err?.message ?? err)}`);
|
|
1636
2108
|
}
|
|
@@ -1904,7 +2376,7 @@ function buildWarmupQuery(cwd, git) {
|
|
|
1904
2376
|
}
|
|
1905
2377
|
|
|
1906
2378
|
// src/capture.ts
|
|
1907
|
-
import { createHash } from "crypto";
|
|
2379
|
+
import { createHash as createHash4 } from "crypto";
|
|
1908
2380
|
var EMPTY2 = {
|
|
1909
2381
|
captured: false,
|
|
1910
2382
|
reason: "",
|
|
@@ -1971,7 +2443,7 @@ function toParsedMessage(m, index) {
|
|
|
1971
2443
|
return null;
|
|
1972
2444
|
}
|
|
1973
2445
|
function fingerprintMessages(messages) {
|
|
1974
|
-
const h =
|
|
2446
|
+
const h = createHash4("sha256");
|
|
1975
2447
|
for (const m of messages) {
|
|
1976
2448
|
h.update(m.role);
|
|
1977
2449
|
h.update("\0");
|
|
@@ -2045,6 +2517,8 @@ function buildRuntime(config, cwd, env = process.env) {
|
|
|
2045
2517
|
baseUrl: config.baseUrl,
|
|
2046
2518
|
apiKey: config.apiKey,
|
|
2047
2519
|
oauthCredential: config.oauthCredential,
|
|
2520
|
+
authContext: config.authContext,
|
|
2521
|
+
environmentApiKey: env.CTXDB_API_KEY,
|
|
2048
2522
|
accessToken: accessTokenFromEnv(env)
|
|
2049
2523
|
}),
|
|
2050
2524
|
sessionState: /* @__PURE__ */ new Map(),
|
|
@@ -2084,7 +2558,13 @@ function extractTextPrompt(parts) {
|
|
|
2084
2558
|
}
|
|
2085
2559
|
async function buildHooks(input) {
|
|
2086
2560
|
if (shouldSkipHooks()) return {};
|
|
2087
|
-
|
|
2561
|
+
let config;
|
|
2562
|
+
try {
|
|
2563
|
+
config = loadOpencodeConfig();
|
|
2564
|
+
} catch {
|
|
2565
|
+
process.stderr.write("[ctxdb] Invalid authentication configuration; OpenCode integration skipped. Run ctxdb auth status --all.\n");
|
|
2566
|
+
return {};
|
|
2567
|
+
}
|
|
2088
2568
|
if (!isConfigured(config)) {
|
|
2089
2569
|
logDebug(
|
|
2090
2570
|
config,
|