@musnows/scriverse 0.6.3 → 0.6.5

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.
@@ -0,0 +1,155 @@
1
+ import { createSign } from "node:crypto";
2
+ import { AppError } from "./errors.js";
3
+ export const GOOGLE_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
4
+ export const GOOGLE_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
5
+ const TOKEN_EXPIRY_SKEW_MS = 60_000;
6
+ const JWT_LIFETIME_SECONDS = 3_600;
7
+ const GOOGLE_VERTEX_REGIONAL_HOST_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?-aiplatform\.googleapis\.com$/u;
8
+ export function isOfficialGoogleVertexBaseUrl(raw) {
9
+ try {
10
+ const url = new URL(raw);
11
+ const hostname = url.hostname.toLowerCase();
12
+ return url.protocol === "https:"
13
+ && !url.username
14
+ && !url.password
15
+ && (url.port === "" || url.port === "443")
16
+ && (hostname === "aiplatform.googleapis.com" || GOOGLE_VERTEX_REGIONAL_HOST_PATTERN.test(hostname));
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ export function assertOfficialGoogleVertexBaseUrl(raw) {
23
+ if (!isOfficialGoogleVertexBaseUrl(raw)) {
24
+ throw new AppError(400, "INVALID_VERTEX_BASE_URL", "Google Vertex 接口地址必须使用 aiplatform.googleapis.com 或官方区域 *-aiplatform.googleapis.com 域名");
25
+ }
26
+ }
27
+ function base64UrlEncode(value) {
28
+ const buffer = typeof value === "string" ? Buffer.from(value, "utf8") : value;
29
+ return buffer.toString("base64url");
30
+ }
31
+ function readNonEmptyString(record, key) {
32
+ const value = record[key];
33
+ if (typeof value !== "string" || !value.trim()) {
34
+ throw new AppError(400, "INVALID_SERVICE_ACCOUNT", `服务账号 JSON 缺少有效字段:${key}`);
35
+ }
36
+ return value.trim();
37
+ }
38
+ export function parseGoogleServiceAccount(raw) {
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(raw);
42
+ }
43
+ catch {
44
+ throw new AppError(400, "INVALID_SERVICE_ACCOUNT", "服务账号 JSON 无法解析");
45
+ }
46
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
47
+ throw new AppError(400, "INVALID_SERVICE_ACCOUNT", "服务账号 JSON 必须是对象");
48
+ }
49
+ const record = parsed;
50
+ if (record.type !== "service_account") {
51
+ throw new AppError(400, "INVALID_SERVICE_ACCOUNT", "服务账号 JSON 的 type 必须为 service_account");
52
+ }
53
+ const clientEmail = readNonEmptyString(record, "client_email");
54
+ const privateKey = readNonEmptyString(record, "private_key");
55
+ const projectId = typeof record.project_id === "string" && record.project_id.trim()
56
+ ? record.project_id.trim()
57
+ : undefined;
58
+ return {
59
+ type: "service_account",
60
+ client_email: clientEmail,
61
+ private_key: privateKey,
62
+ ...(projectId ? { project_id: projectId } : {})
63
+ };
64
+ }
65
+ export function maskServiceAccountHint(account) {
66
+ return `sa:${account.client_email}`;
67
+ }
68
+ export function createGoogleServiceAccountJwt(account, nowSeconds = Math.floor(Date.now() / 1000)) {
69
+ const header = base64UrlEncode(JSON.stringify({ alg: "RS256", typ: "JWT" }));
70
+ const payload = base64UrlEncode(JSON.stringify({
71
+ iss: account.client_email,
72
+ sub: account.client_email,
73
+ aud: GOOGLE_OAUTH_TOKEN_URL,
74
+ iat: nowSeconds,
75
+ exp: nowSeconds + JWT_LIFETIME_SECONDS,
76
+ scope: GOOGLE_CLOUD_PLATFORM_SCOPE
77
+ }));
78
+ const unsigned = `${header}.${payload}`;
79
+ const signer = createSign("RSA-SHA256");
80
+ signer.update(unsigned);
81
+ signer.end();
82
+ let signature;
83
+ try {
84
+ signature = signer.sign(account.private_key);
85
+ }
86
+ catch {
87
+ throw new AppError(400, "INVALID_SERVICE_ACCOUNT", "服务账号私钥无法用于签名");
88
+ }
89
+ return `${unsigned}.${base64UrlEncode(signature)}`;
90
+ }
91
+ export async function exchangeGoogleServiceAccountToken(account, fetchToken, nowSeconds = Math.floor(Date.now() / 1000)) {
92
+ const jwt = createGoogleServiceAccountJwt(account, nowSeconds);
93
+ return fetchToken(jwt);
94
+ }
95
+ export class GoogleVertexTokenCache {
96
+ tokens = new Map();
97
+ clear(cacheKey) {
98
+ if (cacheKey)
99
+ this.tokens.delete(cacheKey);
100
+ else
101
+ this.tokens.clear();
102
+ }
103
+ async getAccessToken(cacheKey, account, fetchToken, nowMs = Date.now()) {
104
+ const cached = this.tokens.get(cacheKey);
105
+ if (cached && cached.expiresAtMs - TOKEN_EXPIRY_SKEW_MS > nowMs) {
106
+ return cached.accessToken;
107
+ }
108
+ const exchanged = await exchangeGoogleServiceAccountToken(account, fetchToken, Math.floor(nowMs / 1000));
109
+ const expiresInSeconds = Number.isFinite(exchanged.expiresInSeconds) && exchanged.expiresInSeconds > 0
110
+ ? exchanged.expiresInSeconds
111
+ : JWT_LIFETIME_SECONDS;
112
+ this.tokens.set(cacheKey, {
113
+ accessToken: exchanged.accessToken,
114
+ expiresAtMs: nowMs + expiresInSeconds * 1_000
115
+ });
116
+ return exchanged.accessToken;
117
+ }
118
+ }
119
+ export async function fetchGoogleOAuthAccessToken(jwt, outboundFetch) {
120
+ const response = await outboundFetch(GOOGLE_OAUTH_TOKEN_URL, {
121
+ method: "POST",
122
+ headers: {
123
+ "Content-Type": "application/x-www-form-urlencoded",
124
+ Accept: "application/json"
125
+ },
126
+ body: new URLSearchParams({
127
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
128
+ assertion: jwt
129
+ }).toString()
130
+ });
131
+ const body = await response.text();
132
+ if (!response.ok) {
133
+ throw new AppError(502, "VERTEX_TOKEN_EXCHANGE_FAILED", `Google OAuth 换票失败:HTTP ${response.status}`);
134
+ }
135
+ let payload;
136
+ try {
137
+ payload = JSON.parse(body);
138
+ }
139
+ catch {
140
+ throw new AppError(502, "VERTEX_TOKEN_EXCHANGE_FAILED", "Google OAuth 换票返回了无效 JSON");
141
+ }
142
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
143
+ throw new AppError(502, "VERTEX_TOKEN_EXCHANGE_FAILED", "Google OAuth 换票返回了无效响应");
144
+ }
145
+ const record = payload;
146
+ const accessToken = typeof record.access_token === "string" ? record.access_token.trim() : "";
147
+ if (!accessToken) {
148
+ throw new AppError(502, "VERTEX_TOKEN_EXCHANGE_FAILED", "Google OAuth 换票响应缺少 access_token");
149
+ }
150
+ const expiresInSeconds = typeof record.expires_in === "number" && Number.isFinite(record.expires_in)
151
+ ? record.expires_in
152
+ : JWT_LIFETIME_SECONDS;
153
+ return { accessToken, expiresInSeconds };
154
+ }
155
+ //# sourceMappingURL=google-vertex-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-vertex-auth.js","sourceRoot":"","sources":["../src/google-vertex-auth.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ export function findAiMention(value, cursor = value.length) {
12
12
  export function listAiMentionOptions(characters, settings, chapters, query = "", limit = 12) {
13
13
  const keyword = String(query).trim().toLocaleLowerCase("zh-CN");
14
14
  const groups = [
15
+ [{ kind: "context-settings", kindLabel: "能力", id: "include-setting-info", name: "注入上下文设定" }],
15
16
  characters.map((item) => ({ kind: "character", kindLabel: "角色", id: String(item.id), name: String(item.name) })),
16
17
  settings.map((item) => ({ kind: "setting", kindLabel: "设定", id: String(item.id), name: String(item.title) })),
17
18
  chapters.map((item) => ({
@@ -47,9 +48,22 @@ export function buildAiReferenceScope(references) {
47
48
  const chapterIds = [...new Set(references.filter((item) => item.kind === "chapter").map((item) => item.id))];
48
49
  const characterIds = [...new Set(references.filter((item) => item.kind === "character").map((item) => item.id))];
49
50
  const settingIds = [...new Set(references.filter((item) => item.kind === "setting").map((item) => item.id))];
51
+ const includeSettingInfo = references.some((item) => item.kind === "context-settings" && item.id === "include-setting-info");
50
52
  return {
51
53
  ...(chapterIds.length ? { chapterIds } : {}),
52
54
  ...(characterIds.length ? { characterIds } : {}),
53
- ...(settingIds.length ? { settingIds } : {})
55
+ ...(settingIds.length ? { settingIds } : {}),
56
+ ...(includeSettingInfo ? { includeSettingInfo: true } : {})
54
57
  };
55
58
  }
59
+
60
+ export function mergeAiReferenceScope(scope, references) {
61
+ const merged = { ...scope };
62
+ const referenceScope = buildAiReferenceScope(references);
63
+ for (const key of ["chapterIds", "characterIds", "settingIds"]) {
64
+ const values = [...new Set([...(Array.isArray(scope?.[key]) ? scope[key] : []), ...(referenceScope[key] ?? [])])];
65
+ if (values.length) merged[key] = values;
66
+ }
67
+ if (referenceScope.includeSettingInfo === true) merged.includeSettingInfo = true;
68
+ return merged;
69
+ }