@chnxthr/knowledge-mcp 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -2
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +18 -0
  5. package/dist/bootstrap.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +5 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/core/auth.d.ts +38 -0
  10. package/dist/core/auth.js +93 -0
  11. package/dist/core/auth.js.map +1 -0
  12. package/dist/core/config.d.ts +259 -0
  13. package/dist/core/config.js +172 -0
  14. package/dist/core/config.js.map +1 -0
  15. package/dist/core/context.d.ts +25 -0
  16. package/dist/core/context.js +37 -0
  17. package/dist/core/context.js.map +1 -0
  18. package/dist/core/env.d.ts +2 -0
  19. package/dist/core/env.js +5 -0
  20. package/dist/core/env.js.map +1 -0
  21. package/dist/core/log_mask.d.ts +10 -0
  22. package/dist/core/log_mask.js +88 -0
  23. package/dist/core/log_mask.js.map +1 -0
  24. package/dist/core/oauth.d.ts +46 -0
  25. package/dist/core/oauth.js +507 -0
  26. package/dist/core/oauth.js.map +1 -0
  27. package/dist/core/paths.d.ts +5 -0
  28. package/dist/core/paths.js +47 -0
  29. package/dist/core/paths.js.map +1 -0
  30. package/dist/core/scope.d.ts +28 -0
  31. package/dist/core/scope.js +54 -0
  32. package/dist/core/scope.js.map +1 -0
  33. package/dist/core/upstream.d.ts +30 -0
  34. package/dist/core/upstream.js +164 -0
  35. package/dist/core/upstream.js.map +1 -0
  36. package/dist/index.d.ts +23 -0
  37. package/dist/index.js +21 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/plugin/define.d.ts +144 -0
  40. package/dist/plugin/define.js +44 -0
  41. package/dist/plugin/define.js.map +1 -0
  42. package/dist/plugin/load.d.ts +2 -0
  43. package/dist/plugin/load.js +41 -0
  44. package/dist/plugin/load.js.map +1 -0
  45. package/dist/plugin/ping.d.ts +9 -0
  46. package/dist/plugin/ping.js +29 -0
  47. package/dist/plugin/ping.js.map +1 -0
  48. package/dist/plugin/registry.d.ts +62 -0
  49. package/dist/plugin/registry.js +248 -0
  50. package/dist/plugin/registry.js.map +1 -0
  51. package/dist/plugin/result.d.ts +20 -0
  52. package/dist/plugin/result.js +59 -0
  53. package/dist/plugin/result.js.map +1 -0
  54. package/dist/plugins/drive/chunker.d.ts +28 -0
  55. package/dist/plugins/drive/chunker.js +356 -0
  56. package/dist/plugins/drive/chunker.js.map +1 -0
  57. package/dist/plugins/drive/clean_text.d.ts +13 -0
  58. package/dist/plugins/drive/clean_text.js +31 -0
  59. package/dist/plugins/drive/clean_text.js.map +1 -0
  60. package/dist/plugins/drive/client.d.ts +94 -0
  61. package/dist/plugins/drive/client.js +220 -0
  62. package/dist/plugins/drive/client.js.map +1 -0
  63. package/dist/plugins/drive/embeddings.d.ts +15 -0
  64. package/dist/plugins/drive/embeddings.js +118 -0
  65. package/dist/plugins/drive/embeddings.js.map +1 -0
  66. package/dist/plugins/drive/exclude.d.ts +11 -0
  67. package/dist/plugins/drive/exclude.js +36 -0
  68. package/dist/plugins/drive/exclude.js.map +1 -0
  69. package/dist/plugins/drive/extract_text.d.ts +26 -0
  70. package/dist/plugins/drive/extract_text.js +95 -0
  71. package/dist/plugins/drive/extract_text.js.map +1 -0
  72. package/dist/plugins/drive/get_token.d.ts +5 -0
  73. package/dist/plugins/drive/get_token.js +128 -0
  74. package/dist/plugins/drive/get_token.js.map +1 -0
  75. package/dist/plugins/drive/index.d.ts +5 -0
  76. package/dist/plugins/drive/index.js +72 -0
  77. package/dist/plugins/drive/index.js.map +1 -0
  78. package/dist/plugins/drive/indexer.d.ts +69 -0
  79. package/dist/plugins/drive/indexer.js +204 -0
  80. package/dist/plugins/drive/indexer.js.map +1 -0
  81. package/dist/plugins/drive/mathpix.d.ts +4 -0
  82. package/dist/plugins/drive/mathpix.js +102 -0
  83. package/dist/plugins/drive/mathpix.js.map +1 -0
  84. package/dist/plugins/drive/ooxml.d.ts +17 -0
  85. package/dist/plugins/drive/ooxml.js +152 -0
  86. package/dist/plugins/drive/ooxml.js.map +1 -0
  87. package/dist/plugins/drive/path.d.ts +40 -0
  88. package/dist/plugins/drive/path.js +87 -0
  89. package/dist/plugins/drive/path.js.map +1 -0
  90. package/dist/plugins/drive/qdrant.d.ts +123 -0
  91. package/dist/plugins/drive/qdrant.js +148 -0
  92. package/dist/plugins/drive/qdrant.js.map +1 -0
  93. package/dist/plugins/drive/reindex.d.ts +2 -0
  94. package/dist/plugins/drive/reindex.js +263 -0
  95. package/dist/plugins/drive/reindex.js.map +1 -0
  96. package/dist/plugins/drive/settings.d.ts +11 -0
  97. package/dist/plugins/drive/settings.js +13 -0
  98. package/dist/plugins/drive/settings.js.map +1 -0
  99. package/dist/plugins/drive/sync.d.ts +16 -0
  100. package/dist/plugins/drive/sync.js +139 -0
  101. package/dist/plugins/drive/sync.js.map +1 -0
  102. package/dist/plugins/drive/sync_state.d.ts +3 -0
  103. package/dist/plugins/drive/sync_state.js +26 -0
  104. package/dist/plugins/drive/sync_state.js.map +1 -0
  105. package/dist/plugins/drive/tools/list_drive_folder.d.ts +104 -0
  106. package/dist/plugins/drive/tools/list_drive_folder.js +221 -0
  107. package/dist/plugins/drive/tools/list_drive_folder.js.map +1 -0
  108. package/dist/plugins/drive/tools/read_drive_file.d.ts +66 -0
  109. package/dist/plugins/drive/tools/read_drive_file.js +206 -0
  110. package/dist/plugins/drive/tools/read_drive_file.js.map +1 -0
  111. package/dist/plugins/drive/tools/search_drive.d.ts +52 -0
  112. package/dist/plugins/drive/tools/search_drive.js +78 -0
  113. package/dist/plugins/drive/tools/search_drive.js.map +1 -0
  114. package/dist/plugins/drive/tools/semantic_search_drive.d.ts +109 -0
  115. package/dist/plugins/drive/tools/semantic_search_drive.js +228 -0
  116. package/dist/plugins/drive/tools/semantic_search_drive.js.map +1 -0
  117. package/dist/plugins/github/index.d.ts +1 -0
  118. package/dist/plugins/github/index.js +27 -0
  119. package/dist/plugins/github/index.js.map +1 -0
  120. package/dist/plugins/github/repo_tree.d.ts +57 -0
  121. package/dist/plugins/github/repo_tree.js +123 -0
  122. package/dist/plugins/github/repo_tree.js.map +1 -0
  123. package/dist/plugins/index.d.ts +11 -0
  124. package/dist/plugins/index.js +7 -0
  125. package/dist/plugins/index.js.map +1 -0
  126. package/dist/plugins/railway/index.d.ts +1 -0
  127. package/dist/plugins/railway/index.js +21 -0
  128. package/dist/plugins/railway/index.js.map +1 -0
  129. package/dist/plugins/railway/tools.d.ts +114 -0
  130. package/dist/plugins/railway/tools.js +165 -0
  131. package/dist/plugins/railway/tools.js.map +1 -0
  132. package/dist/plugins/slack/api.d.ts +63 -0
  133. package/dist/plugins/slack/api.js +280 -0
  134. package/dist/plugins/slack/api.js.map +1 -0
  135. package/dist/plugins/slack/index.d.ts +1 -0
  136. package/dist/plugins/slack/index.js +80 -0
  137. package/dist/plugins/slack/index.js.map +1 -0
  138. package/dist/plugins/slack/tools/slack_files.d.ts +87 -0
  139. package/dist/plugins/slack/tools/slack_files.js +204 -0
  140. package/dist/plugins/slack/tools/slack_files.js.map +1 -0
  141. package/dist/plugins/slack/tools/slack_list_channels.d.ts +46 -0
  142. package/dist/plugins/slack/tools/slack_list_channels.js +197 -0
  143. package/dist/plugins/slack/tools/slack_list_channels.js.map +1 -0
  144. package/dist/plugins/slack/tools/slack_read_channel.d.ts +56 -0
  145. package/dist/plugins/slack/tools/slack_read_channel.js +150 -0
  146. package/dist/plugins/slack/tools/slack_read_channel.js.map +1 -0
  147. package/dist/plugins/slack/tools/slack_search.d.ts +53 -0
  148. package/dist/plugins/slack/tools/slack_search.js +132 -0
  149. package/dist/plugins/slack/tools/slack_search.js.map +1 -0
  150. package/dist/run.d.ts +8 -0
  151. package/dist/run.js +270 -0
  152. package/dist/run.js.map +1 -0
  153. package/dist/server/http.d.ts +5 -0
  154. package/dist/server/http.js +406 -0
  155. package/dist/server/http.js.map +1 -0
  156. package/dist/server/identity.d.ts +17 -0
  157. package/dist/server/identity.js +47 -0
  158. package/dist/server/identity.js.map +1 -0
  159. package/dist/server/mcp.d.ts +7 -0
  160. package/dist/server/mcp.js +55 -0
  161. package/dist/server/mcp.js.map +1 -0
  162. package/dist/server/stdio.d.ts +2 -0
  163. package/dist/server/stdio.js +40 -0
  164. package/dist/server/stdio.js.map +1 -0
  165. package/dist/store/index.d.ts +11 -0
  166. package/dist/store/index.js +31 -0
  167. package/dist/store/index.js.map +1 -0
  168. package/dist/store/keys.d.ts +9 -0
  169. package/dist/store/keys.js +32 -0
  170. package/dist/store/keys.js.map +1 -0
  171. package/dist/store/sqlite.d.ts +38 -0
  172. package/dist/store/sqlite.js +316 -0
  173. package/dist/store/sqlite.js.map +1 -0
  174. package/dist/store/supabase.d.ts +35 -0
  175. package/dist/store/supabase.js +269 -0
  176. package/dist/store/supabase.js.map +1 -0
  177. package/dist/store/supabase_client.d.ts +10 -0
  178. package/dist/store/supabase_client.js +31 -0
  179. package/dist/store/supabase_client.js.map +1 -0
  180. package/dist/store/tool_log.d.ts +12 -0
  181. package/dist/store/tool_log.js +8 -0
  182. package/dist/store/tool_log.js.map +1 -0
  183. package/dist/store/types.d.ts +96 -0
  184. package/dist/store/types.js +2 -0
  185. package/dist/store/types.js.map +1 -0
  186. package/dist/version.d.ts +1 -0
  187. package/dist/version.js +11 -0
  188. package/dist/version.js.map +1 -0
  189. package/package.json +80 -4
  190. package/sql/supabase.sql +185 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Google Drive API (v3, read-only) over plain HTTPS.
3
+ *
4
+ * 인증 옵션 (우선순위 순):
5
+ * 1. Service Account JSON (권장 — production-grade)
6
+ * - GOOGLE_SERVICE_ACCOUNT_KEY (inline JSON, Railway 친화)
7
+ * - GOOGLE_APPLICATION_CREDENTIALS (JSON 파일 path. service_account 또는 authorized_user)
8
+ *
9
+ * 2. OAuth Refresh Token (fallback, single-user)
10
+ * - GOOGLE_OAUTH_CLIENT_ID + GOOGLE_OAUTH_CLIENT_SECRET + GOOGLE_OAUTH_REFRESH_TOKEN
11
+ * - Legacy DRIVE_* fallback
12
+ *
13
+ * Service Account 사용 시:
14
+ * - Drive 폴더에 service account email (xxx@<project>.iam.gserviceaccount.com) viewer 공유 필수
15
+ * - refresh_token 만료·rotation 없음
16
+ *
17
+ * googleapis 대신 직접 부르는 이유: googleapis(google-auth-library → gaxios → node-fetch → fetch-blob)는
18
+ * 최신판도 폐기된 패키지(node-domexception, 옛 판은 uuid)를 끌어와 설치마다 경고를 남겼고, 불러오는 데만
19
+ * CPU 1초가 넘게 들었다. 이 서버가 쓰는 Drive 엔드포인트는 5개뿐이다. 호출 모양(drive.files.list 등,
20
+ * { data } 반환)은 googleapis 와 같게 둬서 도구 코드는 바꾸지 않았다.
21
+ */
22
+ import { readFileSync } from 'node:fs';
23
+ import { SignJWT, importPKCS8 } from 'jose';
24
+ const DRIVE_READONLY_SCOPE = 'https://www.googleapis.com/auth/drive.readonly';
25
+ const DEFAULT_TOKEN_URI = 'https://oauth2.googleapis.com/token';
26
+ const DRIVE_API = 'https://www.googleapis.com/drive/v3';
27
+ // Drive API 전역 timeout — 명시 timeout 이 없으면 매달린 호출 하나가 도구 호출 전체를 멈춘다.
28
+ const DRIVE_TIMEOUT_MS = Number(process.env.DRIVE_TIMEOUT_MS ?? 15_000);
29
+ const RETRIES = 3; // 429·5xx·네트워크 오류. GET 만 쓰므로 다시 보내도 안전하다
30
+ export class DriveApiError extends Error {
31
+ code;
32
+ constructor(message,
33
+ /** HTTP status (0 = network or timeout). googleapis put the same value in `code`. */
34
+ code) {
35
+ super(message);
36
+ this.code = code;
37
+ this.name = 'DriveApiError';
38
+ }
39
+ }
40
+ function credentialsFromJson(raw, source) {
41
+ let j;
42
+ try {
43
+ j = JSON.parse(raw);
44
+ }
45
+ catch (e) {
46
+ throw new Error(`[drive] ${source} is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
47
+ }
48
+ if (j.type === 'authorized_user') {
49
+ return {
50
+ kind: 'refresh_token',
51
+ clientId: String(j.client_id ?? ''),
52
+ clientSecret: String(j.client_secret ?? ''),
53
+ refreshToken: String(j.refresh_token ?? ''),
54
+ tokenUri: DEFAULT_TOKEN_URI,
55
+ };
56
+ }
57
+ if (typeof j.client_email !== 'string' || typeof j.private_key !== 'string') {
58
+ throw new Error(`[drive] ${source} must be a service account key (client_email, private_key) or an authorized_user file`);
59
+ }
60
+ return {
61
+ kind: 'service_account',
62
+ clientEmail: j.client_email,
63
+ privateKey: j.private_key,
64
+ tokenUri: typeof j.token_uri === 'string' ? j.token_uri : DEFAULT_TOKEN_URI,
65
+ };
66
+ }
67
+ /** Which credentials the env provides. Throws with the options when none are set. */
68
+ export function resolveCredentials(env = process.env) {
69
+ if (env.GOOGLE_SERVICE_ACCOUNT_KEY)
70
+ return credentialsFromJson(env.GOOGLE_SERVICE_ACCOUNT_KEY, 'GOOGLE_SERVICE_ACCOUNT_KEY');
71
+ if (env.GOOGLE_APPLICATION_CREDENTIALS) {
72
+ const path = env.GOOGLE_APPLICATION_CREDENTIALS;
73
+ let raw;
74
+ try {
75
+ raw = readFileSync(path, 'utf8');
76
+ }
77
+ catch (e) {
78
+ throw new Error(`[drive] cannot read GOOGLE_APPLICATION_CREDENTIALS (${path}): ${e instanceof Error ? e.message : String(e)}`);
79
+ }
80
+ return credentialsFromJson(raw, 'GOOGLE_APPLICATION_CREDENTIALS');
81
+ }
82
+ const clientId = env.GOOGLE_OAUTH_CLIENT_ID || env.DRIVE_CLIENT_ID;
83
+ const clientSecret = env.GOOGLE_OAUTH_CLIENT_SECRET || env.DRIVE_CLIENT_SECRET;
84
+ const refreshToken = env.GOOGLE_OAUTH_REFRESH_TOKEN || env.DRIVE_REFRESH_TOKEN;
85
+ if (clientId && clientSecret && refreshToken) {
86
+ return { kind: 'refresh_token', clientId, clientSecret, refreshToken, tokenUri: DEFAULT_TOKEN_URI };
87
+ }
88
+ throw new Error('[drive] Drive credentials missing. Use one of:\n' +
89
+ ' (1) GOOGLE_SERVICE_ACCOUNT_KEY (inline JSON) ← 권장\n' +
90
+ ' (2) GOOGLE_APPLICATION_CREDENTIALS (JSON file path) ← 권장\n' +
91
+ ' (3) GOOGLE_OAUTH_CLIENT_ID + _SECRET + _REFRESH_TOKEN ← fallback');
92
+ }
93
+ async function requestToken(creds) {
94
+ const form = new URLSearchParams();
95
+ if (creds.kind === 'service_account') {
96
+ // OAuth 2.0 JWT bearer grant (RFC 7523): 서비스 계정 키로 서명한 assertion 을 토큰으로 바꾼다.
97
+ const now = Math.floor(Date.now() / 1000);
98
+ const key = await importPKCS8(creds.privateKey, 'RS256');
99
+ const assertion = await new SignJWT({ scope: DRIVE_READONLY_SCOPE })
100
+ .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
101
+ .setIssuer(creds.clientEmail)
102
+ .setAudience(creds.tokenUri)
103
+ .setIssuedAt(now)
104
+ .setExpirationTime(now + 3600)
105
+ .sign(key);
106
+ form.set('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
107
+ form.set('assertion', assertion);
108
+ }
109
+ else {
110
+ form.set('grant_type', 'refresh_token');
111
+ form.set('client_id', creds.clientId);
112
+ form.set('client_secret', creds.clientSecret);
113
+ form.set('refresh_token', creds.refreshToken);
114
+ }
115
+ const res = await fetch(creds.tokenUri, {
116
+ method: 'POST',
117
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
118
+ body: form,
119
+ signal: AbortSignal.timeout(DRIVE_TIMEOUT_MS),
120
+ });
121
+ const body = (await res.json().catch(() => ({})));
122
+ if (!res.ok || !body.access_token) {
123
+ // Google 은 오류 본문에 토큰이나 키를 돌려주지 않는다. 오류 코드와 설명만 남긴다.
124
+ throw new DriveApiError(`[drive] token request failed ${res.status}: ${body.error ?? ''} ${body.error_description ?? ''}`.trim(), res.status);
125
+ }
126
+ return { token: body.access_token, expiresAt: Date.now() + (body.expires_in ?? 3600) * 1000 };
127
+ }
128
+ /** Builds a client. `apiBase` and credentials are injectable for tests; production uses env and Google URLs. */
129
+ export function createDriveClient(creds, apiBase = DRIVE_API) {
130
+ let cached = null;
131
+ let pending = null;
132
+ const accessToken = () => {
133
+ if (cached && cached.expiresAt - 60_000 > Date.now())
134
+ return Promise.resolve(cached.token);
135
+ pending ??= requestToken(creds)
136
+ .then((t) => {
137
+ cached = t;
138
+ return t.token;
139
+ })
140
+ .finally(() => {
141
+ pending = null;
142
+ });
143
+ return pending;
144
+ };
145
+ const get = async (path, params = {}, responseType = 'json') => {
146
+ const qs = new URLSearchParams();
147
+ for (const [k, v] of Object.entries(params))
148
+ if (v !== undefined)
149
+ qs.set(k, String(v));
150
+ const url = `${apiBase}${path}${qs.size ? `?${qs}` : ''}`;
151
+ for (let attempt = 0;; attempt++) {
152
+ let res;
153
+ try {
154
+ res = await fetch(url, {
155
+ headers: { Authorization: `Bearer ${await accessToken()}`, Accept: responseType === 'json' ? 'application/json' : '*/*' },
156
+ signal: AbortSignal.timeout(DRIVE_TIMEOUT_MS),
157
+ });
158
+ }
159
+ catch (e) {
160
+ if (e instanceof DriveApiError)
161
+ throw e; // token request failed
162
+ if (attempt + 1 < RETRIES) {
163
+ await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
164
+ continue;
165
+ }
166
+ const reason = e instanceof Error && e.name === 'TimeoutError' ? `timeout ${DRIVE_TIMEOUT_MS}ms` : String(e);
167
+ throw new DriveApiError(`[drive] GET ${path} failed: ${reason}`, 0);
168
+ }
169
+ if (res.status === 401 && attempt === 0) {
170
+ cached = null; // revoked or expired early: get a new token once
171
+ continue;
172
+ }
173
+ if ((res.status === 429 || res.status >= 500) && attempt + 1 < RETRIES) {
174
+ const after = Number(res.headers.get('retry-after'));
175
+ await res.arrayBuffer().catch(() => undefined);
176
+ await new Promise((r) => setTimeout(r, Number.isFinite(after) && after > 0 ? after * 1000 : 500 * 2 ** attempt));
177
+ continue;
178
+ }
179
+ if (!res.ok) {
180
+ const text = await res.text().catch(() => '');
181
+ let msg = text.slice(0, 300);
182
+ try {
183
+ msg = JSON.parse(text).error?.message ?? msg;
184
+ }
185
+ catch {
186
+ /* not JSON */
187
+ }
188
+ throw new DriveApiError(`[drive] ${res.status} ${path}: ${msg}`, res.status);
189
+ }
190
+ if (responseType === 'arraybuffer')
191
+ return { data: await res.arrayBuffer() };
192
+ if (responseType === 'text')
193
+ return { data: await res.text() };
194
+ return { data: await res.json() };
195
+ }
196
+ };
197
+ const fileId = (id) => encodeURIComponent(id);
198
+ return {
199
+ files: {
200
+ list: (params) => get('/files', params),
201
+ get: ({ fileId: id, ...params }, opts) => get(`/files/${fileId(id)}`, params, opts?.responseType ?? 'json'),
202
+ export: ({ fileId: id, ...params }, opts) => get(`/files/${fileId(id)}/export`, params, opts?.responseType ?? 'text'),
203
+ },
204
+ changes: {
205
+ getStartPageToken: (params) => get('/changes/startPageToken', params),
206
+ list: (params) => get('/changes', params),
207
+ },
208
+ };
209
+ }
210
+ let _client = null;
211
+ /** The process-wide Drive client, built from env on first use. */
212
+ export async function getDriveClient() {
213
+ _client ??= createDriveClient(resolveCredentials());
214
+ return _client;
215
+ }
216
+ /** Google Drive query language 의 single-quote escape. */
217
+ export function escapeDriveQuery(s) {
218
+ return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
219
+ }
220
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/plugins/drive/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export declare const EMBEDDING_MODEL = "text-embedding-3-small";
2
+ export declare const EMBEDDING_DIM = 1536;
3
+ interface EmbedPolicy {
4
+ maxRetries: number;
5
+ timeoutMs: number;
6
+ }
7
+ export type EmbedTaskType = 'RETRIEVAL_DOCUMENT' | 'RETRIEVAL_QUERY' | 'SEMANTIC_SIMILARITY' | 'CLASSIFICATION' | 'CLUSTERING';
8
+ /**
9
+ * 여러 텍스트 → embedding 벡터들 (input 순서 보장).
10
+ * BATCH_SIZE 씩 묶어 batch 호출. policy 기본 = 색인용(patient). 쿼리는 embedQuery 가 fail-fast 사용.
11
+ */
12
+ export declare function embedTexts(texts: string[], _taskType?: EmbedTaskType, policy?: EmbedPolicy): Promise<number[][]>;
13
+ /** 단일 query → 벡터. 인터랙티브 검색 경로라 fail-fast 정책(QUERY_POLICY) 사용. */
14
+ export declare function embedQuery(query: string): Promise<number[]>;
15
+ export {};
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Embedding 헬퍼 — OpenAI `text-embedding-3-small` (2026-05-28 비용 전환).
3
+ *
4
+ * 기존 Gemini `gemini-embedding-2` → OpenAI 3-small 전환 이유:
5
+ * - 비용 ~1/10 ($0.02/1M vs Gemini ~$0.20/1M)
6
+ * - 429 rate limit 여유 (Gemini free tier 한도 잦은 429 → drive-sync 에러 로그)
7
+ * - backend 가 이미 3-small 사용(한국어 KG 임베딩 검증됨)
8
+ * - 1536 dim 동일 → Qdrant collection 차원 유지 (벡터 공간만 다름 → 전환 시 전체 재색인 필수)
9
+ * - input 한도 8191 토큰 → Gemini 대비 큰 청크 Bad Request 완화
10
+ *
11
+ * 호출 패턴:
12
+ * OpenAI embeddings API 는 `input` 배열 batch 지원 → 한 호출에 여러 청크 (Gemini individual call 대비 빠르고 rate limit ↓).
13
+ * 429/5xx → exponential backoff + jitter retry. timeout 방어(무응답 hang).
14
+ */
15
+ const OPENAI_EMBEDDINGS_URL = 'https://api.openai.com/v1/embeddings';
16
+ export const EMBEDDING_MODEL = 'text-embedding-3-small';
17
+ export const EMBEDDING_DIM = 1536;
18
+ // 한 batch 당 input 수 (OpenAI 는 배열 input 지원). 토큰 한도 여유 위해 보수적 128.
19
+ const BATCH_SIZE = Number(process.env.EMBED_BATCH ?? 128);
20
+ const INDEX_POLICY = {
21
+ maxRetries: Number(process.env.EMBED_INDEX_RETRIES ?? 5),
22
+ timeoutMs: Number(process.env.EMBED_TIMEOUT_MS ?? 10_000),
23
+ };
24
+ const QUERY_POLICY = {
25
+ maxRetries: Number(process.env.EMBED_QUERY_RETRIES ?? 2),
26
+ timeoutMs: Number(process.env.EMBED_QUERY_TIMEOUT_MS ?? 5_000),
27
+ };
28
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
29
+ function withTimeout(p, ms, label) {
30
+ let t;
31
+ const timer = new Promise((_, reject) => {
32
+ t = setTimeout(() => reject(new Error(`${label}: 504 deadline (timeout ${ms}ms)`)), ms);
33
+ });
34
+ return Promise.race([p, timer]).finally(() => clearTimeout(t));
35
+ }
36
+ function getApiKey() {
37
+ const key = process.env.OPENAI_API_KEY;
38
+ if (!key)
39
+ throw new Error('[embeddings] OPENAI_API_KEY env missing');
40
+ return key;
41
+ }
42
+ /** 재시도 가능 여부 (429 rate limit 또는 5xx/timeout). */
43
+ function parseRetryable(status, msg) {
44
+ const is429 = status === 429 || /rate limit|quota|too many requests/i.test(msg);
45
+ const is5xx = (typeof status === 'number' && status >= 500 && status <= 599) ||
46
+ /\b(500|502|503|504)\b|timeout|deadline|ECONNRESET|ETIMEDOUT|fetch failed/i.test(msg);
47
+ return is429 || is5xx;
48
+ }
49
+ // lone surrogate 방어 — 깨진 이모지 반쪽이 들어오면 400. 빈 입력도 400 → 공백 fallback.
50
+ function sanitize(text) {
51
+ const s = text.replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, '');
52
+ return s.length > 0 ? s : ' ';
53
+ }
54
+ /** input 배열 → 임베딩 배열 (batch 1회 호출 + retry). 순서 보장. policy 로 재시도·timeout 경로별 분리. */
55
+ async function embedBatch(texts, policy) {
56
+ const apiKey = getApiKey();
57
+ const inputs = texts.map(sanitize);
58
+ for (let attempt = 0;; attempt++) {
59
+ try {
60
+ const resp = await withTimeout(fetch(OPENAI_EMBEDDINGS_URL, {
61
+ method: 'POST',
62
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
63
+ body: JSON.stringify({ model: EMBEDDING_MODEL, input: inputs, dimensions: EMBEDDING_DIM }),
64
+ }), policy.timeoutMs, '[embeddings] openai');
65
+ if (!resp.ok) {
66
+ const body = await resp.text();
67
+ const retryable = parseRetryable(resp.status, body);
68
+ if (!retryable || attempt >= policy.maxRetries) {
69
+ throw new Error(`OpenAI embeddings ${resp.status}: ${body.slice(0, 200)}`);
70
+ }
71
+ const m = body.match(/try again in ([\d.]+)s/i);
72
+ const base = m ? Math.ceil(parseFloat(m[1]) * 1000) : Math.min(60_000, 1000 * 2 ** attempt);
73
+ const wait = Math.round(base + base * 0.3 * Math.random());
74
+ console.error(`[embeddings] ${resp.status} retry ${attempt + 1}/${policy.maxRetries} — ${wait}ms 대기`);
75
+ await sleep(wait);
76
+ continue;
77
+ }
78
+ const j = (await resp.json());
79
+ // index 순 정렬 후 벡터만 (OpenAI 는 보통 정렬돼 오지만 방어)
80
+ const sorted = [...j.data].sort((a, b) => a.index - b.index);
81
+ const vecs = sorted.map((d) => d.embedding);
82
+ if (vecs.length !== inputs.length || vecs[0]?.length !== EMBEDDING_DIM) {
83
+ throw new Error(`[embeddings] 응답 불일치 — vecs=${vecs.length}/${inputs.length}, dim=${vecs[0]?.length}/${EMBEDDING_DIM}`);
84
+ }
85
+ return vecs;
86
+ }
87
+ catch (err) {
88
+ const msg = err instanceof Error ? err.message : String(err);
89
+ if (attempt >= policy.maxRetries || !parseRetryable(null, msg))
90
+ throw err;
91
+ const base = Math.min(60_000, 1000 * 2 ** attempt);
92
+ const wait = Math.round(base + base * 0.3 * Math.random());
93
+ console.error(`[embeddings] 예외 retry ${attempt + 1}/${policy.maxRetries} — ${wait}ms 대기 (${msg.slice(0, 80)})`);
94
+ await sleep(wait);
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * 여러 텍스트 → embedding 벡터들 (input 순서 보장).
100
+ * BATCH_SIZE 씩 묶어 batch 호출. policy 기본 = 색인용(patient). 쿼리는 embedQuery 가 fail-fast 사용.
101
+ */
102
+ export async function embedTexts(texts, _taskType = 'RETRIEVAL_DOCUMENT', policy = INDEX_POLICY) {
103
+ if (texts.length === 0)
104
+ return [];
105
+ const results = [];
106
+ for (let i = 0; i < texts.length; i += BATCH_SIZE) {
107
+ const slice = texts.slice(i, i + BATCH_SIZE);
108
+ const embeds = await embedBatch(slice, policy);
109
+ results.push(...embeds);
110
+ }
111
+ return results;
112
+ }
113
+ /** 단일 query → 벡터. 인터랙티브 검색 경로라 fail-fast 정책(QUERY_POLICY) 사용. */
114
+ export async function embedQuery(query) {
115
+ const [vec] = await embedTexts([query], 'RETRIEVAL_QUERY', QUERY_POLICY);
116
+ return vec;
117
+ }
118
+ //# sourceMappingURL=embeddings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../../src/plugins/drive/embeddings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** Normalized form used for every exclusion comparison. */
2
+ export declare function normalizeFolderName(name: string): string;
3
+ /** Merged exclusion set (normalized): built-in dev artifacts + configured sensitive folders. */
4
+ export declare const EXCLUDED_FOLDER_NAMES: Set<string>;
5
+ /** True if this single folder name is excluded. */
6
+ export declare function isExcludedName(name: string | null | undefined): boolean;
7
+ /**
8
+ * True if any path segment matches an excluded folder name.
9
+ * Segment-wise (not includes()) so e.g. "rebuild.md" doesn't match "build".
10
+ */
11
+ export declare function isExcludedPath(path: string): boolean;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Search-index folder exclusions — single source of truth.
3
+ *
4
+ * Shared by full reindex (reindex.ts), incremental sync (sync.ts) and the read/list/search
5
+ * tools (path.ts). Two buckets:
6
+ * 1. Built-in dev artifacts (node_modules, .git, build outputs) — always excluded so whole code
7
+ * projects in Drive don't flood the index with library-doc noise.
8
+ * 2. Configured sensitive folders (config.drive.excludeFolders) — your PII/contract/HR/backup folders.
9
+ *
10
+ * Names are compared after normalization (Unicode NFC, trimmed, lower-cased). Drive keeps whatever form the
11
+ * uploader sent, so a macOS-uploaded Korean folder name can arrive as NFD and "HR " or "hr" must still match.
12
+ */
13
+ import { getConfig } from '../../core/config.js';
14
+ const BUILTIN_EXCLUDES = [
15
+ 'node_modules', '.git', '.next', '.nuxt', '.svelte-kit',
16
+ 'dist', 'build', 'coverage', '.cache', '.turbo', '.parcel-cache',
17
+ '__pycache__', '.venv', 'venv', '.pytest_cache', '.mypy_cache',
18
+ ];
19
+ /** Normalized form used for every exclusion comparison. */
20
+ export function normalizeFolderName(name) {
21
+ return name.normalize('NFC').trim().toLowerCase();
22
+ }
23
+ /** Merged exclusion set (normalized): built-in dev artifacts + configured sensitive folders. */
24
+ export const EXCLUDED_FOLDER_NAMES = new Set([...BUILTIN_EXCLUDES, ...getConfig().drive.excludeFolders].map(normalizeFolderName));
25
+ /** True if this single folder name is excluded. */
26
+ export function isExcludedName(name) {
27
+ return !!name && EXCLUDED_FOLDER_NAMES.has(normalizeFolderName(name));
28
+ }
29
+ /**
30
+ * True if any path segment matches an excluded folder name.
31
+ * Segment-wise (not includes()) so e.g. "rebuild.md" doesn't match "build".
32
+ */
33
+ export function isExcludedPath(path) {
34
+ return path.split('/').some((seg) => isExcludedName(seg));
35
+ }
36
+ //# sourceMappingURL=exclude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclude.js","sourceRoot":"","sources":["../../../src/plugins/drive/exclude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,MAAM,gBAAgB,GAAG;IACvB,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa;IACvD,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe;IAChE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa;CAC/D,CAAC;AAEF,2DAA2D;AAC3D,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACpD,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAC1C,CAAC,GAAG,gBAAgB,EAAE,GAAG,SAAS,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,CACpF,CAAC;AAEF,mDAAmD;AACnD,MAAM,UAAU,cAAc,CAAC,IAA+B;IAC5D,OAAO,CAAC,CAAC,IAAI,IAAI,qBAAqB,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 문서 형식별 텍스트 추출 router. 벡터 색인 전처리.
3
+ *
4
+ * 무료 추출 (OCR 불필요):
5
+ * - text/markdown, text/plain → 그대로
6
+ * - Google Docs → Drive export text/markdown
7
+ * - Google Sheets → Drive export text/csv
8
+ * - Google Slides → Drive export text/plain
9
+ * - Word .docx → ooxml.ts (zip 안 XML 의 문단 텍스트)
10
+ * - PDF (digital) → pdf-parse (텍스트 레이어)
11
+ *
12
+ * OCR 필요 (후속 — Mathpix):
13
+ * - PDF (스캔본, 텍스트 부족) → Mathpix
14
+ * - .pptx / image → Mathpix / Gemini multimodal
15
+ *
16
+ * lazy import (pdf-parse, xlsx) — 필요 시에만 로드.
17
+ */
18
+ import type { DriveClient } from './client.js';
19
+ export interface ExtractResult {
20
+ text: string;
21
+ method: string;
22
+ needsOcr?: boolean;
23
+ meta?: Record<string, unknown>;
24
+ }
25
+ /** 추출 + 정제 (노이즈 cleanup). 색인·검색 진입점. */
26
+ export declare function extractText(drive: DriveClient, fileId: string, mimeType: string): Promise<ExtractResult>;
@@ -0,0 +1,95 @@
1
+ import { cleanExtractedText } from './clean_text.js';
2
+ /** 스캔본 판정 threshold — 페이지당 평균 글자 수 */
3
+ const MIN_CHARS_PER_PAGE = 80;
4
+ async function downloadText(drive, fileId) {
5
+ const r = await drive.files.get({ fileId, alt: 'media', supportsAllDrives: true }, { responseType: 'text' });
6
+ return r.data;
7
+ }
8
+ async function downloadBuffer(drive, fileId) {
9
+ const r = await drive.files.get({ fileId, alt: 'media', supportsAllDrives: true }, { responseType: 'arraybuffer' });
10
+ return Buffer.from(r.data);
11
+ }
12
+ async function exportGoogle(drive, fileId, mimeType) {
13
+ const r = await drive.files.export({ fileId, mimeType }, { responseType: 'text' });
14
+ return r.data;
15
+ }
16
+ /** 추출 + 정제 (노이즈 cleanup). 색인·검색 진입점. */
17
+ export async function extractText(drive, fileId, mimeType) {
18
+ const raw = await extractRaw(drive, fileId, mimeType);
19
+ return { ...raw, text: cleanExtractedText(raw.text) };
20
+ }
21
+ /** 형식별 원본 추출 (정제 전). */
22
+ async function extractRaw(drive, fileId, mimeType) {
23
+ // 1. Native text
24
+ if (mimeType === 'text/markdown' || mimeType === 'text/plain') {
25
+ return { text: await downloadText(drive, fileId), method: 'native' };
26
+ }
27
+ // 2. Google Workspace — Drive export (무료)
28
+ if (mimeType === 'application/vnd.google-apps.document') {
29
+ return { text: await exportGoogle(drive, fileId, 'text/markdown'), method: 'gdoc→md' };
30
+ }
31
+ if (mimeType === 'application/vnd.google-apps.spreadsheet') {
32
+ return { text: await exportGoogle(drive, fileId, 'text/csv'), method: 'gsheet→csv' };
33
+ }
34
+ if (mimeType === 'application/vnd.google-apps.presentation') {
35
+ return { text: await exportGoogle(drive, fileId, 'text/plain'), method: 'gslide→txt' };
36
+ }
37
+ // 3. Word .docx — 의존성 없는 zip/XML 읽기 (ooxml.ts). 문단 사이 빈 줄 — 예전 mammoth extractRawText 와 같은 모양
38
+ if (mimeType.includes('wordprocessingml')) {
39
+ const { docxText } = await import('./ooxml.js');
40
+ const buffer = await downloadBuffer(drive, fileId);
41
+ return { text: docxText(buffer), method: 'docx→ooxml' };
42
+ }
43
+ // 4. PDF — pdf-parse v2. digital 텍스트, 스캔본(<MIN) 은 Mathpix OCR fallback
44
+ if (mimeType === 'application/pdf') {
45
+ const buffer = await downloadBuffer(drive, fileId);
46
+ const { PDFParse } = (await import('pdf-parse'));
47
+ const parser = new PDFParse({ data: new Uint8Array(buffer) });
48
+ let text = '';
49
+ let pages = 1;
50
+ try {
51
+ const result = await parser.getText();
52
+ text = result.text ?? '';
53
+ pages = result.total ?? 1;
54
+ }
55
+ finally {
56
+ await parser.destroy();
57
+ }
58
+ const charsPerPage = text.replace(/\s/g, '').length / Math.max(pages, 1);
59
+ if (charsPerPage >= MIN_CHARS_PER_PAGE) {
60
+ return { text, method: 'pdf-parse', meta: { pages, charsPerPage: Math.round(charsPerPage) } };
61
+ }
62
+ // 스캔본 → Mathpix OCR
63
+ const { mathpixPdfToMarkdown } = await import('./mathpix.js');
64
+ const md = await mathpixPdfToMarkdown(buffer);
65
+ return {
66
+ text: md,
67
+ method: 'pdf→mathpix-ocr',
68
+ meta: { pages, charsPerPage: Math.round(charsPerPage), ocr: true },
69
+ };
70
+ }
71
+ // 5. PowerPoint .pptx → 슬라이드마다 "## Slide N" + 본문 + 발표자 노트 (ooxml.ts)
72
+ if (mimeType.includes('presentationml')) {
73
+ const { pptxText } = await import('./ooxml.js');
74
+ const buffer = await downloadBuffer(drive, fileId);
75
+ return { text: pptxText(buffer), method: 'pptx→ooxml' };
76
+ }
77
+ // 6. Excel .xlsx → SheetJS (시트별 csv)
78
+ if (mimeType.includes('spreadsheetml')) {
79
+ const XLSX = await import('xlsx');
80
+ const buffer = await downloadBuffer(drive, fileId);
81
+ const wb = XLSX.read(buffer, { type: 'buffer' });
82
+ const text = wb.SheetNames.map((n) => `## ${n}\n${XLSX.utils.sheet_to_csv(wb.Sheets[n])}`).join('\n\n');
83
+ return { text, method: 'xlsx→sheetjs' };
84
+ }
85
+ // 7. 이미지 → Mathpix OCR (v3/text, 즉시)
86
+ if (mimeType.startsWith('image/')) {
87
+ const buffer = await downloadBuffer(drive, fileId);
88
+ const { mathpixImageToMarkdown } = await import('./mathpix.js');
89
+ const md = await mathpixImageToMarkdown(buffer, mimeType);
90
+ return { text: md, method: 'image→mathpix' };
91
+ }
92
+ // 8. 미지원
93
+ return { text: '', method: `unsupported:${mimeType}`, needsOcr: true };
94
+ }
95
+ //# sourceMappingURL=extract_text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract_text.js","sourceRoot":"","sources":["../../../src/plugins/drive/extract_text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `knowledge-mcp drive get-token` — opens Google sign-in and prints a Drive refresh token.
3
+ * The token is written with process.stdout.write: console output is masked and would hide it.
4
+ */
5
+ export declare function runGetToken(): Promise<void>;