@aliyunrds/ctxdb 1.0.8-beta.3 → 1.0.8-beta.4
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 +39 -6
- package/dist/{chunk-EI63DQX3.js → chunk-5DGRNP55.js} +1 -1
- package/dist/{chunk-R67JELM7.js → chunk-5ITT5GSP.js} +3 -2
- package/dist/{chunk-LZ2LOWZL.js → chunk-6MEWI73Y.js} +2 -2
- package/dist/{chunk-ZFS6OMWE.js → chunk-76AXXLVE.js} +2 -2
- package/dist/{chunk-AUBVVYQL.js → chunk-O6MKQ2I3.js} +1 -1
- package/dist/{chunk-ULAWTBBC.js → chunk-OEZM2ZYA.js} +1 -1
- package/dist/{chunk-VIG4SYLU.js → chunk-PI2SUS3M.js} +1 -1
- package/dist/{chunk-BUK4SZC2.js → chunk-SVO6H62S.js} +36 -17
- package/dist/{chunk-7OX6UVPB.js → chunk-WJTV7JNR.js} +3 -3
- package/dist/{chunk-3NJ37TEY.js → chunk-ZW7YXNJ6.js} +1 -1
- package/dist/cli/main.js +282 -72
- package/dist/hooks/hermes-post-llm-call.js +5 -5
- package/dist/hooks/hermes-pre-llm-call.js +8 -8
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/hooks/session-start.js +7 -7
- package/dist/hooks/stop.js +5 -5
- package/dist/hooks/user-prompt-submit.js +7 -7
- package/dist/opencode/index.js +611 -36
- package/dist/workers/version-check.js +3 -3
- package/package.json +2 -2
package/dist/opencode/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// <define:__CTXDB_DISTRIBUTION_MANIFEST__>
|
|
2
|
-
var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false } };
|
|
2
|
+
var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false, deviceFlow: true } };
|
|
3
3
|
|
|
4
4
|
// src/config.ts
|
|
5
|
-
import { readFileSync as
|
|
5
|
+
import { readFileSync as readFileSync4, existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
6
6
|
import { homedir as homedir2 } from "os";
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
8
8
|
|
|
9
9
|
// ../shared/src/graph-context.ts
|
|
10
10
|
var GRAPH_CONTEXT_TAG = "graphrag";
|
|
@@ -435,8 +435,8 @@ function getMemoryImportance(memory) {
|
|
|
435
435
|
};
|
|
436
436
|
return defaults[cat] ?? 0.5;
|
|
437
437
|
}
|
|
438
|
-
function estimateTokens(
|
|
439
|
-
return Math.ceil(
|
|
438
|
+
function estimateTokens(text2) {
|
|
439
|
+
return Math.ceil(text2.length / CHARS_PER_TOKEN);
|
|
440
440
|
}
|
|
441
441
|
function rankMemories(memories, categoryOrder) {
|
|
442
442
|
const orderMap = new Map(categoryOrder.map((cat, i) => [cat, i]));
|
|
@@ -690,9 +690,9 @@ function sanitizeRecallFailureReason(reason, secrets = []) {
|
|
|
690
690
|
}
|
|
691
691
|
function sanitizeTraceString(value, secrets) {
|
|
692
692
|
let sanitized = value.replace(/authorization\s*[:=]\s*[^\r\n]*/gi, "Authorization: [REDACTED]").replace(/(api[_-]?key|access[_-]?token)\s*[:=]\s*[^\s,;]+/gi, "$1=[REDACTED]");
|
|
693
|
-
for (const
|
|
694
|
-
if (!
|
|
695
|
-
sanitized = sanitized.split(
|
|
693
|
+
for (const secret2 of secrets) {
|
|
694
|
+
if (!secret2) continue;
|
|
695
|
+
sanitized = sanitized.split(secret2).join("[REDACTED]");
|
|
696
696
|
}
|
|
697
697
|
return sanitized;
|
|
698
698
|
}
|
|
@@ -827,10 +827,568 @@ function isRecallTraceRecord(value) {
|
|
|
827
827
|
const baseValid = record.schema_version === RECALL_TRACE_SCHEMA_VERSION && (record.type === "recall.start" || record.type === "recall.finish") && isIsoTimestamp(record.timestamp) && typeof record.recall_event_id === "string" && record.recall_event_id.length > 0 && (typeof record.session_id === "string" || record.session_id === null) && isRecallTraceAgent(record.agent) && (record.kind === "prompt" || record.kind === "warmup") && typeof record.query === "string" && isRecallTraceRequest(record.request);
|
|
828
828
|
if (!baseValid) return false;
|
|
829
829
|
if (record.type === "recall.start") return true;
|
|
830
|
+
if (record.type !== "recall.finish") return false;
|
|
830
831
|
const selection = record.selection;
|
|
831
832
|
return isNonNegativeFinite(record.duration_ms) && typeof record.outcome === "string" && TRACE_OUTCOMES.has(record.outcome) && (typeof record.failure_reason === "string" || record.failure_reason === null) && Array.isArray(record.returned_memories) && Array.isArray(record.returned_knowledge) && Boolean(selection) && Array.isArray(selection?.selected) && Array.isArray(selection?.excluded) && isNonNegativeFinite(selection?.token_estimate) && typeof record.recall_context === "string";
|
|
832
833
|
}
|
|
833
834
|
|
|
835
|
+
// ../shared/src/device-flow.ts
|
|
836
|
+
import { setTimeout as delay } from "timers/promises";
|
|
837
|
+
var DeviceFlowError = class extends Error {
|
|
838
|
+
code;
|
|
839
|
+
constructor(code) {
|
|
840
|
+
super(code === "service_unavailable" ? "ContextDB authentication service is unavailable. No automatic retry was performed; start a new login later if authorization could not be completed." : `ContextDB device authorization: ${code}. Start a new login if authorization could not be completed.`);
|
|
841
|
+
this.name = "DeviceFlowError";
|
|
842
|
+
this.code = code;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
var invalid = () => new DeviceFlowError("invalid_response");
|
|
846
|
+
var scopes = ["read_only", "read_write", "default"];
|
|
847
|
+
var clientId = "ctxdb-cli";
|
|
848
|
+
var grantType = "urn:ietf:params:oauth:grant-type:device_code";
|
|
849
|
+
function normalizeCoreOrigin(value) {
|
|
850
|
+
let url;
|
|
851
|
+
try {
|
|
852
|
+
url = new URL(value);
|
|
853
|
+
} catch {
|
|
854
|
+
throw new Error("Invalid Core server origin");
|
|
855
|
+
}
|
|
856
|
+
const local = ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname);
|
|
857
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/" || !(url.protocol === "https:" || local && url.protocol === "http:")) throw new Error("Invalid Core server origin");
|
|
858
|
+
return url.origin;
|
|
859
|
+
}
|
|
860
|
+
var CoreDeviceClient = class {
|
|
861
|
+
baseUrl;
|
|
862
|
+
fetchImpl;
|
|
863
|
+
now;
|
|
864
|
+
sleep;
|
|
865
|
+
constructor(baseUrl, options = {}) {
|
|
866
|
+
this.baseUrl = normalizeCoreOrigin(baseUrl);
|
|
867
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
868
|
+
this.now = options.now ?? Date.now;
|
|
869
|
+
this.sleep = options.sleep ?? ((ms, signal) => delay(ms, void 0, { signal }));
|
|
870
|
+
}
|
|
871
|
+
async login(options) {
|
|
872
|
+
const started = this.now();
|
|
873
|
+
const initial = await this.post("/v1/auth/device/authorize", { client_id: clientId }, options.signal);
|
|
874
|
+
if (!initial.ok) throw new DeviceFlowError("request_rejected");
|
|
875
|
+
const data = initial.data;
|
|
876
|
+
const requestId = text(data.request_id, 32), userCode = text(data.user_code, 9), code = text(data.device_code, 43);
|
|
877
|
+
if (!/^[0-9a-f]{32}$/.test(requestId) || !/^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$/.test(userCode) || !/^[A-Za-z0-9_-]{43}$/.test(code)) throw invalid();
|
|
878
|
+
const expiresIn = integer(data.expires_in, 1, 3600);
|
|
879
|
+
let interval = integer(data.interval, 1, 3600);
|
|
880
|
+
const deadline = started + expiresIn * 1e3;
|
|
881
|
+
options.onPrompt({ requestId, userCode, expiresIn, interval });
|
|
882
|
+
while (true) {
|
|
883
|
+
if (options.signal?.aborted) throw new DeviceFlowError("cancelled");
|
|
884
|
+
if (this.now() >= deadline) throw new DeviceFlowError("expired_token");
|
|
885
|
+
try {
|
|
886
|
+
await this.sleep(Math.min(interval * 1e3, deadline - this.now()), options.signal);
|
|
887
|
+
} catch {
|
|
888
|
+
throw new DeviceFlowError("cancelled");
|
|
889
|
+
}
|
|
890
|
+
if (this.now() >= deadline) throw new DeviceFlowError("expired_token");
|
|
891
|
+
const issuedAt = this.now();
|
|
892
|
+
const result = await this.post("/v1/auth/device/token", { grant_type: grantType, client_id: clientId, device_code: code }, options.signal);
|
|
893
|
+
if (result.ok) return loginResult(result.data, this.baseUrl, issuedAt, this.now());
|
|
894
|
+
const error = result.data.error;
|
|
895
|
+
if (error === "authorization_pending") continue;
|
|
896
|
+
if (error === "slow_down") {
|
|
897
|
+
interval = Math.max(interval + 5, integer(result.data.interval, 1, 3605));
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
if (error === "access_denied" || error === "expired_token" || error === "invalid_grant") throw new DeviceFlowError(error);
|
|
901
|
+
throw new DeviceFlowError("request_rejected");
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async refresh(refreshToken, baseUrl) {
|
|
905
|
+
this.requireSameOrigin(baseUrl);
|
|
906
|
+
const result = await this.post("/v1/auth/token/refresh", { refresh_token: secret(refreshToken) });
|
|
907
|
+
if (!result.ok) throw new DeviceFlowError("request_rejected");
|
|
908
|
+
return credentialResult(result.data);
|
|
909
|
+
}
|
|
910
|
+
async revoke(refreshToken, baseUrl) {
|
|
911
|
+
this.requireSameOrigin(baseUrl);
|
|
912
|
+
const result = await this.post("/v1/auth/token/revoke", { refresh_token: secret(refreshToken) });
|
|
913
|
+
if (!result.ok) throw new DeviceFlowError("request_rejected");
|
|
914
|
+
}
|
|
915
|
+
requireSameOrigin(baseUrl) {
|
|
916
|
+
if (normalizeCoreOrigin(baseUrl) !== this.baseUrl) throw new Error("OAuth credential Core origin cannot change");
|
|
917
|
+
}
|
|
918
|
+
async post(path, body, signal) {
|
|
919
|
+
if (signal?.aborted) throw new DeviceFlowError("cancelled");
|
|
920
|
+
const controller = new AbortController();
|
|
921
|
+
const abort = () => controller.abort();
|
|
922
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
923
|
+
const timer = setTimeout(abort, 3e4);
|
|
924
|
+
timer.unref?.();
|
|
925
|
+
try {
|
|
926
|
+
const response = await this.fetchImpl(this.baseUrl + path, {
|
|
927
|
+
method: "POST",
|
|
928
|
+
body: JSON.stringify(body),
|
|
929
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
930
|
+
redirect: "error",
|
|
931
|
+
credentials: "omit",
|
|
932
|
+
cache: "no-store",
|
|
933
|
+
signal: controller.signal
|
|
934
|
+
});
|
|
935
|
+
const box = await boundedJson(response);
|
|
936
|
+
if (!Number.isInteger(box.errorCode)) throw invalid();
|
|
937
|
+
const ok = response.ok && box.errorCode === 0;
|
|
938
|
+
const data = (!ok || path === "/v1/auth/token/revoke") && box.data == null ? {} : object(box.data);
|
|
939
|
+
if (!ok && response.status >= 500) throw new DeviceFlowError("service_unavailable");
|
|
940
|
+
return { ok, data };
|
|
941
|
+
} catch (error) {
|
|
942
|
+
if (signal?.aborted) throw new DeviceFlowError("cancelled");
|
|
943
|
+
if (error instanceof DeviceFlowError) throw error;
|
|
944
|
+
throw new DeviceFlowError("network_error");
|
|
945
|
+
} finally {
|
|
946
|
+
clearTimeout(timer);
|
|
947
|
+
signal?.removeEventListener("abort", abort);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
function text(value, maximum) {
|
|
952
|
+
if (typeof value !== "string" || !value || value.length > maximum || /[\x00-\x1f\x7f]/.test(value)) throw invalid();
|
|
953
|
+
return value;
|
|
954
|
+
}
|
|
955
|
+
function integer(value, min, max) {
|
|
956
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) throw invalid();
|
|
957
|
+
return value;
|
|
958
|
+
}
|
|
959
|
+
function secret(value) {
|
|
960
|
+
const token = text(value, 8192);
|
|
961
|
+
if (/\s/.test(token)) throw invalid();
|
|
962
|
+
return token;
|
|
963
|
+
}
|
|
964
|
+
function object(value) {
|
|
965
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid();
|
|
966
|
+
return value;
|
|
967
|
+
}
|
|
968
|
+
function credentialResult(data) {
|
|
969
|
+
if (data.token_type !== "Bearer" || !scopes.includes(data.scope)) throw invalid();
|
|
970
|
+
return {
|
|
971
|
+
access_token: secret(data.access_token),
|
|
972
|
+
refresh_token: secret(data.refresh_token),
|
|
973
|
+
token_type: "Bearer",
|
|
974
|
+
expires_in: integer(data.expires_in, 1, 3600),
|
|
975
|
+
session_id: text(data.session_id, 64),
|
|
976
|
+
scope: data.scope
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function loginResult(data, baseUrl, issuedAt, now) {
|
|
980
|
+
const tokens = credentialResult(data), scope = tokens.scope;
|
|
981
|
+
if (data.client_id !== clientId || scope !== "default") throw invalid();
|
|
982
|
+
const expiresAt = issuedAt + tokens.expires_in * 1e3;
|
|
983
|
+
if (expiresAt <= now) throw invalid();
|
|
984
|
+
return {
|
|
985
|
+
baseUrl,
|
|
986
|
+
workspaceId: text(data.workspace_id, 64),
|
|
987
|
+
memberId: text(data.member_id, 64),
|
|
988
|
+
scope,
|
|
989
|
+
clientId,
|
|
990
|
+
sessionId: tokens.session_id,
|
|
991
|
+
accountDisplay: text(data.account_display, 256),
|
|
992
|
+
issuedAt,
|
|
993
|
+
expiresAt,
|
|
994
|
+
accessToken: tokens.access_token,
|
|
995
|
+
refreshToken: tokens.refresh_token
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
async function boundedJson(response) {
|
|
999
|
+
if (!response.body || Number(response.headers.get("content-length")) > 65536) throw invalid();
|
|
1000
|
+
const reader = response.body.getReader();
|
|
1001
|
+
const chunks = [];
|
|
1002
|
+
let size = 0;
|
|
1003
|
+
try {
|
|
1004
|
+
while (true) {
|
|
1005
|
+
const { done, value } = await reader.read();
|
|
1006
|
+
if (done) break;
|
|
1007
|
+
size += value.byteLength;
|
|
1008
|
+
if (size > 65536) {
|
|
1009
|
+
await reader.cancel();
|
|
1010
|
+
throw invalid();
|
|
1011
|
+
}
|
|
1012
|
+
chunks.push(value);
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
return object(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
1016
|
+
} catch {
|
|
1017
|
+
throw invalid();
|
|
1018
|
+
}
|
|
1019
|
+
} finally {
|
|
1020
|
+
reader.releaseLock();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// ../shared/src/oauth-credentials.ts
|
|
1025
|
+
import {
|
|
1026
|
+
constants,
|
|
1027
|
+
closeSync as closeSync2,
|
|
1028
|
+
existsSync as existsSync2,
|
|
1029
|
+
fstatSync,
|
|
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";
|
|
1040
|
+
import { join as join3 } from "path";
|
|
1041
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
1042
|
+
|
|
1043
|
+
// ../shared/src/windows-private-storage.ts
|
|
1044
|
+
import { execFileSync } from "child_process";
|
|
1045
|
+
import { join as join2 } from "path";
|
|
1046
|
+
var SCRIPT = `
|
|
1047
|
+
$ErrorActionPreference = 'Stop'
|
|
1048
|
+
$path = $env:CTXDB_PRIVATE_STORAGE_PATH
|
|
1049
|
+
$sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
1050
|
+
$item = Get-Item -LiteralPath $path -Force
|
|
1051
|
+
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'reparse' }
|
|
1052
|
+
if ($env:CTXDB_PRIVATE_STORAGE_INITIALIZE -eq '1') {
|
|
1053
|
+
if (-not $item.PSIsContainer) { throw 'not directory' }
|
|
1054
|
+
$acl = [System.Security.AccessControl.DirectorySecurity]::new()
|
|
1055
|
+
$acl.SetOwner($sid)
|
|
1056
|
+
$acl.SetAccessRuleProtection($true, $false)
|
|
1057
|
+
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new($sid, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
|
|
1058
|
+
$acl.AddAccessRule($rule)
|
|
1059
|
+
Set-Acl -LiteralPath $path -AclObject $acl
|
|
1060
|
+
}
|
|
1061
|
+
$acl = Get-Acl -LiteralPath $path
|
|
1062
|
+
if ($acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) { throw 'owner' }
|
|
1063
|
+
$self = $false
|
|
1064
|
+
foreach ($rule in $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])) {
|
|
1065
|
+
if ($rule.AccessControlType -eq 'Allow') {
|
|
1066
|
+
if ($rule.IdentityReference.Value -ne $sid.Value) { throw 'other principal' }
|
|
1067
|
+
if (($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl) { $self = $true }
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (-not $self) { throw 'missing owner access' }
|
|
1071
|
+
Write-Output 'private'
|
|
1072
|
+
`;
|
|
1073
|
+
function checkWindowsPrivateStorage(path, initialize, run = execFileSync) {
|
|
1074
|
+
const systemRoot = process.env.SystemRoot;
|
|
1075
|
+
if (!systemRoot) throw new Error("Private Windows credential storage could not be verified.");
|
|
1076
|
+
try {
|
|
1077
|
+
const result = run(
|
|
1078
|
+
join2(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
1079
|
+
["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(SCRIPT, "utf16le").toString("base64")],
|
|
1080
|
+
{
|
|
1081
|
+
env: { ...process.env, CTXDB_PRIVATE_STORAGE_PATH: path, CTXDB_PRIVATE_STORAGE_INITIALIZE: initialize ? "1" : "0" },
|
|
1082
|
+
encoding: "utf8",
|
|
1083
|
+
windowsHide: true,
|
|
1084
|
+
timeout: 1e4,
|
|
1085
|
+
maxBuffer: 4096,
|
|
1086
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1087
|
+
}
|
|
1088
|
+
);
|
|
1089
|
+
if (String(result).trim() !== "private") throw new Error();
|
|
1090
|
+
} catch {
|
|
1091
|
+
throw new Error("Private Windows credential storage could not be verified.");
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// ../shared/src/oauth-credentials.ts
|
|
1096
|
+
var OAuthCredentialError = class extends Error {
|
|
1097
|
+
code;
|
|
1098
|
+
constructor(code) {
|
|
1099
|
+
const messages = {
|
|
1100
|
+
login_required: "OAuth session needs login; run ctxdb login for this profile.",
|
|
1101
|
+
credential_busy: "OAuth credential is busy. If its previous process crashed, run ctxdb login for a new credential.",
|
|
1102
|
+
invalid_profile: "OAuth credential does not belong to this profile.",
|
|
1103
|
+
unsafe_storage: "OAuth credential storage is not private or is invalid."
|
|
1104
|
+
};
|
|
1105
|
+
super(messages[code]);
|
|
1106
|
+
this.name = "OAuthCredentialError";
|
|
1107
|
+
this.code = code;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
var loginRequired = () => new OAuthCredentialError("login_required");
|
|
1111
|
+
var unsafe = () => new OAuthCredentialError("unsafe_storage");
|
|
1112
|
+
var OAuthSessionStore = class {
|
|
1113
|
+
directory;
|
|
1114
|
+
now;
|
|
1115
|
+
lockWaitMs;
|
|
1116
|
+
constructor(directory, options = {}) {
|
|
1117
|
+
this.directory = directory;
|
|
1118
|
+
this.now = options.now ?? Date.now;
|
|
1119
|
+
this.lockWaitMs = options.lockWaitMs ?? 3e4;
|
|
1120
|
+
}
|
|
1121
|
+
saveLogin(profile, session) {
|
|
1122
|
+
requireProfile(profile);
|
|
1123
|
+
validateSession(session);
|
|
1124
|
+
this.prepareDirectory();
|
|
1125
|
+
const ref = randomBytes(16).toString("hex");
|
|
1126
|
+
this.write(ref, { version: 1, profile, state: "ready", session });
|
|
1127
|
+
return ref;
|
|
1128
|
+
}
|
|
1129
|
+
status(ref, profile) {
|
|
1130
|
+
const record = this.read(ref, profile);
|
|
1131
|
+
if (!record) return { state: this.isLoggedOut(ref) ? "logged_out" : "missing" };
|
|
1132
|
+
const s = record.session;
|
|
1133
|
+
return {
|
|
1134
|
+
state: record.state,
|
|
1135
|
+
profile,
|
|
1136
|
+
baseUrl: s.baseUrl,
|
|
1137
|
+
workspaceId: s.workspaceId,
|
|
1138
|
+
memberId: s.memberId,
|
|
1139
|
+
clientId: s.clientId,
|
|
1140
|
+
scope: s.scope,
|
|
1141
|
+
sessionId: s.sessionId,
|
|
1142
|
+
accountDisplay: s.accountDisplay,
|
|
1143
|
+
accessExpiresAt: s.expiresAt
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
async resolve(ref, profile, transport) {
|
|
1147
|
+
const first = this.read(ref, profile);
|
|
1148
|
+
if (!first || first.state === "reauth_required") throw loginRequired();
|
|
1149
|
+
if (first.state === "ready" && !this.needsRefresh(first.session)) return authorization(first.session);
|
|
1150
|
+
return this.withLock(ref, async () => {
|
|
1151
|
+
const current = this.read(ref, profile);
|
|
1152
|
+
if (!current || current.state !== "ready") throw loginRequired();
|
|
1153
|
+
if (!this.needsRefresh(current.session)) return authorization(current.session);
|
|
1154
|
+
const started = this.now();
|
|
1155
|
+
this.write(ref, { ...current, state: "refreshing" });
|
|
1156
|
+
try {
|
|
1157
|
+
const response = await transport.refresh(current.session.refreshToken, current.session.baseUrl);
|
|
1158
|
+
const next = rotated(current.session, response, started);
|
|
1159
|
+
if (next.expiresAt <= this.now()) throw loginRequired();
|
|
1160
|
+
this.write(ref, { ...current, state: "ready", session: next });
|
|
1161
|
+
return authorization(next);
|
|
1162
|
+
} catch {
|
|
1163
|
+
if (!this.isLoggedOut(ref)) this.write(ref, { ...current, state: "reauth_required" });
|
|
1164
|
+
throw loginRequired();
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async logout(ref, profile, transport) {
|
|
1169
|
+
const current = this.read(ref, profile);
|
|
1170
|
+
let remoteRevoked = false;
|
|
1171
|
+
try {
|
|
1172
|
+
if (current) {
|
|
1173
|
+
await transport.revoke(current.session.refreshToken, current.session.baseUrl);
|
|
1174
|
+
remoteRevoked = true;
|
|
1175
|
+
}
|
|
1176
|
+
} catch {
|
|
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;
|
|
1187
|
+
}
|
|
1188
|
+
path(ref, suffix = ".json") {
|
|
1189
|
+
if (!/^[a-f0-9]{32}$/.test(ref)) throw unsafe();
|
|
1190
|
+
return join3(this.directory, ref + suffix);
|
|
1191
|
+
}
|
|
1192
|
+
isLoggedOut(ref) {
|
|
1193
|
+
return existsSync2(this.path(ref, ".logged-out"));
|
|
1194
|
+
}
|
|
1195
|
+
prepareDirectory() {
|
|
1196
|
+
if (!existsSync2(this.directory)) {
|
|
1197
|
+
mkdirSync2(join3(this.directory, ".."), { recursive: true, mode: 448 });
|
|
1198
|
+
try {
|
|
1199
|
+
mkdirSync2(this.directory, { mode: 448 });
|
|
1200
|
+
if (process.platform === "win32") checkWindowsPrivateStorage(this.directory, true);
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
if (error.code !== "EEXIST") throw unsafe();
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
requirePrivate(this.directory, true);
|
|
1206
|
+
}
|
|
1207
|
+
read(ref, profile) {
|
|
1208
|
+
requireProfile(profile);
|
|
1209
|
+
if (this.isLoggedOut(ref)) return void 0;
|
|
1210
|
+
const path = this.path(ref);
|
|
1211
|
+
if (!existsSync2(path)) return void 0;
|
|
1212
|
+
requirePrivate(this.directory, true);
|
|
1213
|
+
requirePrivate(path, false);
|
|
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;
|
|
1224
|
+
try {
|
|
1225
|
+
doc = JSON.parse(text2);
|
|
1226
|
+
} catch {
|
|
1227
|
+
throw unsafe();
|
|
1228
|
+
}
|
|
1229
|
+
if (!doc || doc.version !== 1 || !["ready", "refreshing", "reauth_required"].includes(doc.state)) throw unsafe();
|
|
1230
|
+
if (doc.profile !== profile) throw new OAuthCredentialError("invalid_profile");
|
|
1231
|
+
validateSession(doc.session);
|
|
1232
|
+
return this.isLoggedOut(ref) ? void 0 : doc;
|
|
1233
|
+
}
|
|
1234
|
+
write(ref, doc) {
|
|
1235
|
+
if (this.isLoggedOut(ref)) throw loginRequired();
|
|
1236
|
+
this.atomicWrite(this.path(ref), JSON.stringify(doc));
|
|
1237
|
+
if (this.isLoggedOut(ref)) {
|
|
1238
|
+
remove(this.path(ref));
|
|
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
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
async withLock(ref, task) {
|
|
1267
|
+
const path = this.path(ref, ".lock");
|
|
1268
|
+
const deadline = Date.now() + this.lockWaitMs;
|
|
1269
|
+
this.prepareDirectory();
|
|
1270
|
+
while (true) {
|
|
1271
|
+
let fd;
|
|
1272
|
+
try {
|
|
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
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
function requireProfile(profile) {
|
|
1298
|
+
if (typeof profile !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(profile)) throw new OAuthCredentialError("invalid_profile");
|
|
1299
|
+
}
|
|
1300
|
+
function requirePrivate(path, directory) {
|
|
1301
|
+
const s = lstatSync(path);
|
|
1302
|
+
if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) throw unsafe();
|
|
1303
|
+
if (process.platform === "win32") {
|
|
1304
|
+
try {
|
|
1305
|
+
checkWindowsPrivateStorage(path, false);
|
|
1306
|
+
} catch {
|
|
1307
|
+
throw unsafe();
|
|
1308
|
+
}
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if ((s.mode & 63) !== 0 || process.getuid && s.uid !== process.getuid()) throw unsafe();
|
|
1312
|
+
}
|
|
1313
|
+
function remove(path) {
|
|
1314
|
+
try {
|
|
1315
|
+
unlinkSync(path);
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
if (error.code !== "ENOENT") throw unsafe();
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function validateSession(s) {
|
|
1321
|
+
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
|
+
for (const value of [s.workspaceId, s.memberId, s.sessionId, s.accountDisplay]) {
|
|
1323
|
+
if (typeof value !== "string" || !value || value.length > 256 || /[\r\n\x00-\x1f]/.test(value)) throw unsafe();
|
|
1324
|
+
}
|
|
1325
|
+
for (const value of [s.accessToken, s.refreshToken]) {
|
|
1326
|
+
if (typeof value !== "string" || !value || value.length > 8192 || /\s/.test(value)) throw unsafe();
|
|
1327
|
+
}
|
|
1328
|
+
let url;
|
|
1329
|
+
try {
|
|
1330
|
+
url = new URL(s.baseUrl);
|
|
1331
|
+
} catch {
|
|
1332
|
+
throw unsafe();
|
|
1333
|
+
}
|
|
1334
|
+
const loopback = ["127.0.0.1", "[::1]", "localhost"].includes(url.hostname);
|
|
1335
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/" || !(url.protocol === "https:" || loopback && url.protocol === "http:")) throw unsafe();
|
|
1336
|
+
}
|
|
1337
|
+
function rotated(current, response, issuedAt) {
|
|
1338
|
+
if (!response || response.token_type !== "Bearer" || response.session_id !== current.sessionId || response.scope !== current.scope || !Number.isSafeInteger(response.expires_in) || response.expires_in < 1 || response.expires_in > 3600 || response.refresh_token === current.refreshToken) throw loginRequired();
|
|
1339
|
+
const next = {
|
|
1340
|
+
...current,
|
|
1341
|
+
accessToken: response.access_token,
|
|
1342
|
+
refreshToken: response.refresh_token,
|
|
1343
|
+
issuedAt,
|
|
1344
|
+
expiresAt: issuedAt + response.expires_in * 1e3
|
|
1345
|
+
};
|
|
1346
|
+
validateSession(next);
|
|
1347
|
+
return next;
|
|
1348
|
+
}
|
|
1349
|
+
function authorization(session) {
|
|
1350
|
+
return { scheme: "Bearer", value: session.accessToken, baseUrl: session.baseUrl, expiresAt: session.expiresAt };
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// ../shared/src/oauth-profile.ts
|
|
1354
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1355
|
+
import { dirname as dirname2, join as join4, resolve } from "path";
|
|
1356
|
+
function oauthProfile(reference, configPath, profile, baseUrl) {
|
|
1357
|
+
if (reference === void 0) return void 0;
|
|
1358
|
+
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");
|
|
1359
|
+
const path = resolve(configPath);
|
|
1360
|
+
return {
|
|
1361
|
+
reference,
|
|
1362
|
+
profile,
|
|
1363
|
+
configPath: path,
|
|
1364
|
+
directory: join4(dirname2(path), "oauth-credentials"),
|
|
1365
|
+
baseUrl: normalizeCoreOrigin(baseUrl)
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
var OAuthProfileProvider = class {
|
|
1369
|
+
selected;
|
|
1370
|
+
constructor(selected) {
|
|
1371
|
+
this.selected = selected;
|
|
1372
|
+
}
|
|
1373
|
+
async resolve() {
|
|
1374
|
+
const selected = this.selected;
|
|
1375
|
+
let raw;
|
|
1376
|
+
try {
|
|
1377
|
+
raw = JSON.parse(readFileSync3(selected.configPath, "utf8"));
|
|
1378
|
+
} catch {
|
|
1379
|
+
throw new OAuthCredentialError("login_required");
|
|
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);
|
|
1384
|
+
if (!current || current.baseUrl !== selected.baseUrl) throw new OAuthCredentialError("login_required");
|
|
1385
|
+
const store = new OAuthSessionStore(current.directory);
|
|
1386
|
+
const status = store.status(current.reference, current.profile);
|
|
1387
|
+
if (!("baseUrl" in status) || typeof status.baseUrl !== "string" || normalizeCoreOrigin(status.baseUrl) !== current.baseUrl) throw new OAuthCredentialError("login_required");
|
|
1388
|
+
return store.resolve(current.reference, current.profile, new CoreDeviceClient(current.baseUrl));
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
|
|
834
1392
|
// src/distribution-capabilities.ts
|
|
835
1393
|
var PUBLIC_DISTRIBUTION = {
|
|
836
1394
|
id: "public",
|
|
@@ -838,7 +1396,8 @@ var PUBLIC_DISTRIBUTION = {
|
|
|
838
1396
|
packageRegistry: null,
|
|
839
1397
|
capabilities: {
|
|
840
1398
|
interactiveLogin: false,
|
|
841
|
-
managedCredentials: false
|
|
1399
|
+
managedCredentials: false,
|
|
1400
|
+
deviceFlow: true
|
|
842
1401
|
}
|
|
843
1402
|
};
|
|
844
1403
|
var DISTRIBUTION_MANIFEST = typeof define_CTXDB_DISTRIBUTION_MANIFEST_default === "undefined" ? PUBLIC_DISTRIBUTION : define_CTXDB_DISTRIBUTION_MANIFEST_default;
|
|
@@ -852,7 +1411,7 @@ var DEFAULT_KNOWLEDGE_TOP_K = 6;
|
|
|
852
1411
|
var DEFAULT_KB_CATALOG_INJECTION = "session_start";
|
|
853
1412
|
function defaultPath(env) {
|
|
854
1413
|
if (env.CTXDB_CONFIG_PATH) return env.CTXDB_CONFIG_PATH;
|
|
855
|
-
return
|
|
1414
|
+
return join5(homedir2(), ".ctxdb", "ctxdb.json");
|
|
856
1415
|
}
|
|
857
1416
|
function coerceInt(v, fallback) {
|
|
858
1417
|
if (v === null || v === void 0 || v === "") return fallback;
|
|
@@ -874,9 +1433,9 @@ function coerceKbCatalogInjection(v) {
|
|
|
874
1433
|
return DEFAULT_KB_CATALOG_INJECTION;
|
|
875
1434
|
}
|
|
876
1435
|
function readRaw(path) {
|
|
877
|
-
if (!
|
|
1436
|
+
if (!existsSync3(path)) return {};
|
|
878
1437
|
try {
|
|
879
|
-
const parsed = JSON.parse(
|
|
1438
|
+
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
880
1439
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
881
1440
|
return parsed;
|
|
882
1441
|
}
|
|
@@ -912,11 +1471,11 @@ function applyDebugPolicy(cfg) {
|
|
|
912
1471
|
return cfg;
|
|
913
1472
|
}
|
|
914
1473
|
function managedCredential(path) {
|
|
915
|
-
if (!
|
|
1474
|
+
if (!existsSync3(path)) return null;
|
|
916
1475
|
if (process.platform !== "win32" && (statSync2(path).mode & 63) !== 0) {
|
|
917
1476
|
throw new Error(`credentials: ${path} must be owner-only (run chmod 600)`);
|
|
918
1477
|
}
|
|
919
|
-
const raw = JSON.parse(
|
|
1478
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
920
1479
|
if (raw.version !== 1 || !raw.records || typeof raw.records !== "object") {
|
|
921
1480
|
throw new Error("credentials: unsupported credentials.json schema");
|
|
922
1481
|
}
|
|
@@ -971,16 +1530,21 @@ function loadOpencodeConfig(options = {}) {
|
|
|
971
1530
|
kbCatalogInjection: coerceKbCatalogInjection(section.kb_catalog_injection)
|
|
972
1531
|
};
|
|
973
1532
|
const managed = DISTRIBUTION_MANIFEST.capabilities.managedCredentials && options.managedCredentials !== false ? managedCredential(
|
|
974
|
-
options.credentialsPath ??
|
|
1533
|
+
options.credentialsPath ?? join5(dirname3(path), "credentials.json")
|
|
975
1534
|
) : null;
|
|
976
1535
|
if (managed) {
|
|
977
1536
|
cfg.apiKey = managed.apiKey;
|
|
978
1537
|
cfg.baseUrl = managed.baseUrl;
|
|
979
1538
|
}
|
|
980
|
-
|
|
1539
|
+
applyEnv(cfg, env);
|
|
1540
|
+
if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
|
|
1541
|
+
cfg.oauthCredential = oauthProfile(section.oauth_credential_ref, path, "opencode", cfg.baseUrl);
|
|
1542
|
+
}
|
|
1543
|
+
if (env.CTXDB_API_KEY || accessTokenFromEnv(env)) cfg.oauthCredential = void 0;
|
|
1544
|
+
return cfg;
|
|
981
1545
|
}
|
|
982
1546
|
function isConfigured(cfg, env = process.env) {
|
|
983
|
-
return Boolean((accessTokenFromEnv(env) || cfg.apiKey) && cfg.baseUrl);
|
|
1547
|
+
return Boolean((accessTokenFromEnv(env) || cfg.oauthCredential || cfg.apiKey) && cfg.baseUrl);
|
|
984
1548
|
}
|
|
985
1549
|
|
|
986
1550
|
// src/http-client.ts
|
|
@@ -1001,20 +1565,26 @@ var HttpClient = class {
|
|
|
1001
1565
|
accessToken;
|
|
1002
1566
|
userAgent;
|
|
1003
1567
|
fetchImpl;
|
|
1568
|
+
oauthProvider;
|
|
1004
1569
|
constructor(opts) {
|
|
1005
1570
|
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
1006
1571
|
this.apiKey = opts.apiKey;
|
|
1007
1572
|
this.accessToken = opts.accessToken ?? null;
|
|
1573
|
+
this.oauthProvider = opts.oauthCredential ? new OAuthProfileProvider(opts.oauthCredential) : void 0;
|
|
1008
1574
|
this.userAgent = opts.userAgent ?? "ctxdb-opencode-plugin/0.0.0";
|
|
1009
1575
|
this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
1010
1576
|
}
|
|
1011
|
-
headers(contentType) {
|
|
1577
|
+
async headers(contentType) {
|
|
1012
1578
|
const h = {
|
|
1013
1579
|
"User-Agent": this.userAgent,
|
|
1014
1580
|
Connection: "close"
|
|
1015
1581
|
};
|
|
1016
1582
|
if (this.accessToken) h.Authorization = `Bearer ${this.accessToken}`;
|
|
1017
|
-
else if (this.
|
|
1583
|
+
else if (this.oauthProvider) {
|
|
1584
|
+
const authorization2 = await this.oauthProvider.resolve();
|
|
1585
|
+
if (authorization2.baseUrl !== this.baseUrl) throw new Error("OAuth environment changed; reload this runtime.");
|
|
1586
|
+
h.Authorization = `Bearer ${authorization2.value}`;
|
|
1587
|
+
} else if (this.apiKey) h.Authorization = `Token ${this.apiKey}`;
|
|
1018
1588
|
if (contentType) h["Content-Type"] = contentType;
|
|
1019
1589
|
return h;
|
|
1020
1590
|
}
|
|
@@ -1035,6 +1605,9 @@ var HttpClient = class {
|
|
|
1035
1605
|
return this.request("POST", url, path, JSON.stringify(body), "application/json", options.timeoutMs);
|
|
1036
1606
|
}
|
|
1037
1607
|
async request(method, url, path, body, contentType, timeoutMs) {
|
|
1608
|
+
const headers = await this.headers(contentType);
|
|
1609
|
+
const bearer = headers.Authorization?.startsWith("Bearer ") ? headers.Authorization.slice(7) : void 0;
|
|
1610
|
+
const safeText = (value) => bearer ? String(value).split(bearer).join("[REDACTED]") : String(value);
|
|
1038
1611
|
const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1039
1612
|
const controller = new AbortController();
|
|
1040
1613
|
const timer = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
@@ -1043,41 +1616,42 @@ var HttpClient = class {
|
|
|
1043
1616
|
try {
|
|
1044
1617
|
resp = await this.fetchImpl(url, {
|
|
1045
1618
|
method,
|
|
1046
|
-
headers
|
|
1619
|
+
headers,
|
|
1047
1620
|
body,
|
|
1048
|
-
signal: controller.signal
|
|
1621
|
+
signal: controller.signal,
|
|
1622
|
+
...headers.Authorization?.startsWith("Bearer ") ? { redirect: "error" } : {}
|
|
1049
1623
|
});
|
|
1050
1624
|
} catch (err) {
|
|
1051
1625
|
if (err?.name === "AbortError") {
|
|
1052
1626
|
throw new CtxdbHttpError(path, null, `timeout after ${effectiveTimeout}ms`);
|
|
1053
1627
|
}
|
|
1054
|
-
throw new CtxdbHttpError(path, null, `network error: ${err?.message ?? err}`);
|
|
1628
|
+
throw new CtxdbHttpError(path, null, `network error: ${safeText(err?.message ?? err)}`);
|
|
1055
1629
|
}
|
|
1056
1630
|
if (resp.status === 204) return {};
|
|
1057
|
-
let
|
|
1631
|
+
let text2;
|
|
1058
1632
|
try {
|
|
1059
|
-
|
|
1633
|
+
text2 = safeText(await resp.text());
|
|
1060
1634
|
} catch (err) {
|
|
1061
|
-
throw new CtxdbHttpError(path, resp.status, `body read failed: ${err?.message ?? err}`);
|
|
1635
|
+
throw new CtxdbHttpError(path, resp.status, `body read failed: ${safeText(err?.message ?? err)}`);
|
|
1062
1636
|
}
|
|
1063
1637
|
if (!resp.ok) {
|
|
1064
|
-
throw new CtxdbHttpError(path, resp.status, extractDetail(
|
|
1638
|
+
throw new CtxdbHttpError(path, resp.status, extractDetail(text2) || `HTTP ${resp.status}`);
|
|
1065
1639
|
}
|
|
1066
|
-
if (!
|
|
1640
|
+
if (!text2) return {};
|
|
1067
1641
|
try {
|
|
1068
|
-
return JSON.parse(
|
|
1642
|
+
return JSON.parse(text2);
|
|
1069
1643
|
} catch {
|
|
1070
|
-
return
|
|
1644
|
+
return text2;
|
|
1071
1645
|
}
|
|
1072
1646
|
} finally {
|
|
1073
1647
|
clearTimeout(timer);
|
|
1074
1648
|
}
|
|
1075
1649
|
}
|
|
1076
1650
|
};
|
|
1077
|
-
function extractDetail(
|
|
1078
|
-
if (!
|
|
1651
|
+
function extractDetail(text2) {
|
|
1652
|
+
if (!text2) return "";
|
|
1079
1653
|
try {
|
|
1080
|
-
const parsed = JSON.parse(
|
|
1654
|
+
const parsed = JSON.parse(text2);
|
|
1081
1655
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1082
1656
|
for (const k of ["detail", "message", "error"]) {
|
|
1083
1657
|
const v = parsed[k];
|
|
@@ -1087,7 +1661,7 @@ function extractDetail(text) {
|
|
|
1087
1661
|
}
|
|
1088
1662
|
return String(parsed);
|
|
1089
1663
|
} catch {
|
|
1090
|
-
return
|
|
1664
|
+
return text2;
|
|
1091
1665
|
}
|
|
1092
1666
|
}
|
|
1093
1667
|
|
|
@@ -1470,6 +2044,7 @@ function buildRuntime(config, cwd, env = process.env) {
|
|
|
1470
2044
|
http: new HttpClient({
|
|
1471
2045
|
baseUrl: config.baseUrl,
|
|
1472
2046
|
apiKey: config.apiKey,
|
|
2047
|
+
oauthCredential: config.oauthCredential,
|
|
1473
2048
|
accessToken: accessTokenFromEnv(env)
|
|
1474
2049
|
}),
|
|
1475
2050
|
sessionState: /* @__PURE__ */ new Map(),
|
|
@@ -1523,10 +2098,10 @@ async function buildHooks(input) {
|
|
|
1523
2098
|
const hooks = {
|
|
1524
2099
|
"chat.message": async (input2, output) => {
|
|
1525
2100
|
try {
|
|
1526
|
-
const
|
|
1527
|
-
if (!
|
|
2101
|
+
const text2 = extractTextPrompt(output.parts);
|
|
2102
|
+
if (!text2) return;
|
|
1528
2103
|
const s = touchSession(rt, input2.sessionID);
|
|
1529
|
-
s.lastPrompt =
|
|
2104
|
+
s.lastPrompt = text2;
|
|
1530
2105
|
} catch (err) {
|
|
1531
2106
|
logError(rt.config, "chat.message", err);
|
|
1532
2107
|
}
|