linkque-cli-v2 1.0.5 → 1.0.7

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 (51) hide show
  1. package/README.md +1 -1
  2. package/linkquejs.js +23 -2212
  3. package/package.json +4 -1
  4. package/vendor/linkque-agent-appwrite-integrate/dist/esm/anthropic-messages-B5T6TFD2.js +7172 -0
  5. package/vendor/linkque-agent-appwrite-integrate/dist/esm/azure-openai-responses-S7HNQOSE.js +229 -0
  6. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-2NGJGNYB.js +1765 -0
  7. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-3S3TOEBZ.js +608 -0
  8. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-46SEH6ER.js +78 -0
  9. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-6MHDOBWO.js +12 -0
  10. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-6WHNFJT4.js +31278 -0
  11. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-7BQRUS4C.js +671 -0
  12. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-7QWAKKTY.js +7054 -0
  13. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-CT4G24PS.js +8 -0
  14. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-DYEQRE6N.js +343 -0
  15. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-E2P6TIFG.js +31 -0
  16. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-FHNQ6DPF.js +14 -0
  17. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-FJ2BZPIM.js +60308 -0
  18. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-HTG7JBF2.js +38 -0
  19. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-I64V3ICJ.js +11166 -0
  20. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-IAT272TH.js +12121 -0
  21. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-IKZ25GJD.js +497 -0
  22. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-MKS245OU.js +3570 -0
  23. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-NKEOCGYU.js +76 -0
  24. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-OXGCUIKV.js +33 -0
  25. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-RYONFGPP.js +317 -0
  26. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-UEVQXSKG.js +4765 -0
  27. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-UFVAKOTZ.js +17 -0
  28. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-VS2QQHAK.js +46 -0
  29. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-XMVFOHGJ.js +31 -0
  30. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-XUFLSZM4.js +5319 -0
  31. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-Y6UTJIT4.js +23 -0
  32. package/vendor/linkque-agent-appwrite-integrate/dist/esm/chunk-YDKZZ6EH.js +3584 -0
  33. package/vendor/linkque-agent-appwrite-integrate/dist/esm/dist-QD3CV4KC.js +1224 -0
  34. package/vendor/linkque-agent-appwrite-integrate/dist/esm/google-generative-ai-IOXNKFEV.js +412 -0
  35. package/vendor/linkque-agent-appwrite-integrate/dist/esm/google-vertex-U4ZV5IXZ.js +461 -0
  36. package/vendor/linkque-agent-appwrite-integrate/dist/esm/mistral-conversations-G3K7SSWR.js +30344 -0
  37. package/vendor/linkque-agent-appwrite-integrate/dist/esm/multipart-parser-2VJ7LVQD.js +170 -0
  38. package/vendor/linkque-agent-appwrite-integrate/dist/esm/multipart-parser-AJMV6U6H.js +178 -0
  39. package/vendor/linkque-agent-appwrite-integrate/dist/esm/multipart-parser-PULXGNYO.js +368 -0
  40. package/vendor/linkque-agent-appwrite-integrate/dist/esm/openai-codex-responses-4JJLPTCS.js +1308 -0
  41. package/vendor/linkque-agent-appwrite-integrate/dist/esm/openai-completions-PH4WW6E7.js +977 -0
  42. package/vendor/linkque-agent-appwrite-integrate/dist/esm/openai-responses-KGWAX6HR.js +264 -0
  43. package/vendor/linkque-agent-appwrite-integrate/dist/esm/openrouter-images-LMBZZQ76.js +138 -0
  44. package/vendor/linkque-agent-appwrite-integrate/dist/esm/otel-7GSG7QQO.js +6657 -0
  45. package/vendor/linkque-agent-appwrite-integrate/dist/esm/pi-messages-ZX3KUWSZ.js +309 -0
  46. package/vendor/linkque-agent-appwrite-integrate/dist/esm/runtime/handler/chat.js +2348 -0
  47. package/vendor/linkque-agent-appwrite-integrate/dist/esm/runtime/handler/clientCapability.js +16 -0
  48. package/vendor/linkque-agent-appwrite-integrate/dist/esm/runtime/protocol.js +17 -0
  49. package/vendor/linkque-agent-appwrite-integrate/dist/esm/src-NGZW4JXI.js +1396 -0
  50. package/vendor/linkque-agent-appwrite-integrate/package.json +1 -0
  51. package/worker.js +16 -6
@@ -0,0 +1,2348 @@
1
+ import {
2
+ LinkqueGatewayAuthError,
3
+ LinkqueGatewayAuthUnconfiguredError,
4
+ Query,
5
+ createAppBaseClient,
6
+ createClientCapabilityStore,
7
+ describeRuntimeEnv,
8
+ isDebugLogLevel,
9
+ jsonError,
10
+ parseStoredClientCapabilityResult,
11
+ readAppwriteApiKey,
12
+ readAuthenticatedUserId,
13
+ readEnvSelector,
14
+ readTraceId,
15
+ resolveBodyJSON,
16
+ resolveGatewayAuthContext,
17
+ resolveLinkqueGatewayAuth,
18
+ resolveRunStorage,
19
+ toAgentRunMessages
20
+ } from "../../chunk-IAT272TH.js";
21
+ import "../../chunk-MKS245OU.js";
22
+ import "../../chunk-YDKZZ6EH.js";
23
+ import "../../chunk-2NGJGNYB.js";
24
+ import {
25
+ AgentRuntime,
26
+ CANCEL_CONVERSATION_RUN_OPERATION,
27
+ CONVERSATION_HISTORY_VERSION,
28
+ ConversationControlValidationError,
29
+ ConversationHistoryValidationError,
30
+ ConversationProtocolValidationError,
31
+ DEFAULT_CONVERSATION_LIST_LIMIT,
32
+ DEFAULT_CONVERSATION_RUN_LIST_LIMIT,
33
+ DEFAULT_CONVERSATION_SEARCH_LIMIT,
34
+ EResourceType,
35
+ ESkillUrlScheme,
36
+ EventType,
37
+ FOLLOW_UP_CONTEXT_MAX_HISTORY_RUNS,
38
+ LinkqueKnowledgeSearchPort,
39
+ LinkquegwEventHookExecutor,
40
+ RemoteGatewayMcpServerProvider,
41
+ WebSkillLoader,
42
+ encodeConversationSseEvent,
43
+ executeHomeData,
44
+ getBakedProtocol,
45
+ parseCancelConversationRunRequest,
46
+ parseConversationFollowUpQuestions,
47
+ parseConversationHistoryRequest,
48
+ parseConversationRunRequest,
49
+ parseSkillFile,
50
+ streamToConversationEvents
51
+ } from "../../chunk-FJ2BZPIM.js";
52
+ import "../../chunk-XUFLSZM4.js";
53
+ import "../../chunk-I64V3ICJ.js";
54
+ import "../../chunk-6MHDOBWO.js";
55
+ import "../../chunk-E2P6TIFG.js";
56
+ import "../../chunk-3S3TOEBZ.js";
57
+ import "../../chunk-OXGCUIKV.js";
58
+ import "../../chunk-DYEQRE6N.js";
59
+ import "../../chunk-46SEH6ER.js";
60
+ import "../../chunk-VS2QQHAK.js";
61
+
62
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/chat.js
63
+ import { randomUUID as randomUUID3 } from "node:crypto";
64
+
65
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/ports/appbaseSessionManager.js
66
+ var AppbaseSessionManager = class {
67
+ store;
68
+ constructor(store) {
69
+ this.store = store;
70
+ }
71
+ async load(sessionId) {
72
+ const row = await this.store.read(sessionId);
73
+ if (!row)
74
+ return [];
75
+ return row.turns.map((json) => safeParseTurn(json)).filter((t) => t !== void 0);
76
+ }
77
+ async append(sessionId, turn) {
78
+ const existing = await this.store.read(sessionId);
79
+ if (!existing) {
80
+ await this.store.create(sessionId);
81
+ }
82
+ await this.store.append(sessionId, JSON.stringify(turn));
83
+ }
84
+ async appendMany(sessionId, turns) {
85
+ const serialized = turns.map((turn) => JSON.stringify(turn));
86
+ if (this.store.appendMany) {
87
+ await this.store.appendMany(sessionId, serialized);
88
+ return;
89
+ }
90
+ for (const turn of turns) {
91
+ await this.append(sessionId, turn);
92
+ }
93
+ }
94
+ };
95
+ function createAppbaseSessionManager(store) {
96
+ return new AppbaseSessionManager(store);
97
+ }
98
+ function safeParseTurn(json) {
99
+ try {
100
+ const value = JSON.parse(json);
101
+ if (value && typeof value === "object" && "role" in value)
102
+ return value;
103
+ return void 0;
104
+ } catch {
105
+ return void 0;
106
+ }
107
+ }
108
+ function createAppBaseSessionStore(databases, databaseId, tableId) {
109
+ return {
110
+ read: async (sessionId) => storesRead(databases, databaseId, tableId, sessionId),
111
+ create: async (sessionId) => {
112
+ await databases.createDocument(databaseId, tableId, sessionId, {
113
+ sessionId,
114
+ turns: []
115
+ });
116
+ return { sessionId, turns: [] };
117
+ },
118
+ append: async (sessionId, turnJson) => {
119
+ await appendSerializedTurns(databases, databaseId, tableId, sessionId, [
120
+ turnJson
121
+ ]);
122
+ },
123
+ appendMany: async (sessionId, turnJsons) => {
124
+ await appendSerializedTurns(databases, databaseId, tableId, sessionId, turnJsons);
125
+ }
126
+ };
127
+ }
128
+ async function appendSerializedTurns(databases, databaseId, tableId, sessionId, turnJsons) {
129
+ const row = await storesRead(databases, databaseId, tableId, sessionId);
130
+ const turns = row ? [...row.turns, ...turnJsons] : [...turnJsons];
131
+ if (row) {
132
+ await databases.updateDocument(databaseId, tableId, sessionId, { turns });
133
+ } else {
134
+ await databases.createDocument(databaseId, tableId, sessionId, {
135
+ sessionId,
136
+ turns
137
+ });
138
+ }
139
+ }
140
+ async function storesRead(databases, databaseId, tableId, sessionId) {
141
+ try {
142
+ const doc = await databases.getDocument(databaseId, tableId, sessionId);
143
+ const turns = doc.turns;
144
+ if (!Array.isArray(turns))
145
+ return void 0;
146
+ return { sessionId, turns };
147
+ } catch (err) {
148
+ if (isNotFound(err))
149
+ return void 0;
150
+ throw err;
151
+ }
152
+ }
153
+ function isNotFound(err) {
154
+ if (!err)
155
+ return false;
156
+ const e = err;
157
+ if (e.code === 404)
158
+ return true;
159
+ return /not found|404/i.test(e.message ?? "");
160
+ }
161
+
162
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/ports/appbaseSkillLoader.js
163
+ var AppbaseSkillLoader = class {
164
+ resolveContent;
165
+ constructor(options = {}) {
166
+ this.resolveContent = options.resolveContent;
167
+ }
168
+ async load(resources) {
169
+ if (!this.resolveContent || resources.length === 0)
170
+ return [];
171
+ const raws = await Promise.all(resources.map((r) => this.resolveContent(r)));
172
+ const records = [];
173
+ for (let i = 0; i < raws.length; i += 1) {
174
+ const raw = raws[i];
175
+ if (typeof raw !== "string" || raw.length === 0)
176
+ continue;
177
+ const record = parseSkillFile(raw, resources[i]?.resourceId ?? "skill");
178
+ if (record)
179
+ records.push(record);
180
+ }
181
+ return records;
182
+ }
183
+ };
184
+ function createAppbaseSkillLoader(options = {}) {
185
+ if (options.resolveContent) {
186
+ return new AppbaseSkillLoader(options);
187
+ }
188
+ return new WebSkillLoader({
189
+ allowedSchemes: [ESkillUrlScheme.HTTP, ESkillUrlScheme.HTTPS],
190
+ ...options.debugLog ? { debugLog: options.debugLog } : {}
191
+ });
192
+ }
193
+
194
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/ports/appbaseMcpProvider.js
195
+ var DEFAULT_OPEN_MCP_BASE_URL = "https://opengw.alipay.com";
196
+ function createAppbaseMcpConfigResolver(options) {
197
+ return (resource) => ({
198
+ remoteBaseUrl: DEFAULT_OPEN_MCP_BASE_URL,
199
+ sseEndpoint: `api/v1/open/mcps/${resource.config.serverName}/sse`,
200
+ agentId: options.agentId,
201
+ ...options.signEndpoint ? { signEndpoint: options.signEndpoint } : {}
202
+ });
203
+ }
204
+ function createAppbaseMcpProvider(options = {}) {
205
+ if (!options.resolveConfig) {
206
+ return {
207
+ async servers() {
208
+ return [];
209
+ }
210
+ };
211
+ }
212
+ return new RemoteGatewayMcpServerProvider({
213
+ resolveConfig: options.resolveConfig,
214
+ ...options.getAuthorizationHeader ? { getAuthorizationHeader: options.getAuthorizationHeader } : {},
215
+ ...options.debugLog ? { debugLog: options.debugLog } : {}
216
+ });
217
+ }
218
+
219
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/runtimeFactory.js
220
+ function resolveSessionLeaseStorage(env = process.env) {
221
+ const databaseId = env.SESSION_LEASE_DATABASE_ID ?? env.SESSION_DATABASE_ID;
222
+ const tableId = env.SESSION_LEASE_TABLE_ID;
223
+ if (!databaseId || !tableId)
224
+ return void 0;
225
+ return { databaseId, tableId };
226
+ }
227
+ function resolveSessionStorage(env = process.env) {
228
+ const databaseId = env.SESSION_DATABASE_ID ?? env.RUN_DATABASE_ID;
229
+ const tableId = env.SESSION_TABLE_ID ?? env.RUN_TABLE_ID;
230
+ if (!databaseId || !tableId)
231
+ return void 0;
232
+ return { databaseId, tableId };
233
+ }
234
+ function trimEnvVar(v) {
235
+ const t = v?.trim();
236
+ return t ? t : void 0;
237
+ }
238
+ function resolveLinkqueEndpoints(env, envSelector) {
239
+ const isPre = envSelector === "PRE";
240
+ return {
241
+ linkqueBaseUrl: trimEnvVar(isPre ? env.LINKQUE_BASE_URL_PRE : env.LINKQUE_BASE_URL),
242
+ knowledgeBaseUrl: trimEnvVar(isPre ? env.LINKQUEGW_KNOWLEDGE_BASE_URL_PRE : env.LINKQUEGW_KNOWLEDGE_BASE_URL),
243
+ mcpSignEndpoint: trimEnvVar(isPre ? env.LINKQUEGW_MCP_SIGN_ENDPOINT_PRE : env.LINKQUEGW_MCP_SIGN_ENDPOINT)
244
+ };
245
+ }
246
+ function createAppBaseRuntime(protocol, env = process.env, ports = {}, logCtx) {
247
+ const log = (...a) => logCtx?.log?.("[runtime-factory]", ...a);
248
+ const debugLog = logCtx?.log ? (line, scope) => {
249
+ const prefix = scope ? `[${scope}] ` : "";
250
+ logCtx.log?.(prefix + line);
251
+ } : void 0;
252
+ const runtimePorts = {};
253
+ if (ports.linkqueBaseUrl) {
254
+ runtimePorts.linkqueBaseUrl = ports.linkqueBaseUrl;
255
+ log("linkqueBaseUrl: ->", ports.linkqueBaseUrl);
256
+ }
257
+ if (ports.sessionManager) {
258
+ runtimePorts.sessionManager = ports.sessionManager;
259
+ log("sessionManager: \u4F7F\u7528\u6CE8\u5165\u5B9E\u73B0");
260
+ } else {
261
+ const sessionCfg = resolveSessionStorage(env);
262
+ if (sessionCfg) {
263
+ try {
264
+ const handle = createAppBaseClient(env, logCtx, ports.appBaseApiKey);
265
+ const store = createAppBaseSessionStore(handle.databases, sessionCfg.databaseId, sessionCfg.tableId);
266
+ runtimePorts.sessionManager = createAppbaseSessionManager(store);
267
+ log("sessionManager: AppBase \u6301\u4E45\u5316, databaseId=", sessionCfg.databaseId, "tableId=", sessionCfg.tableId);
268
+ } catch (err) {
269
+ log("sessionManager: AppBase \u51ED\u636E\u7F3A\u5931,\u56DE\u9000 InMemory, \u539F\u56E0=", err instanceof Error ? err.message : err);
270
+ }
271
+ } else {
272
+ log("sessionManager: \u65E0 session table \u914D\u7F6E,\u56DE\u9000 InMemory");
273
+ }
274
+ }
275
+ runtimePorts.skillLoader = ports.skillLoader ?? createAppbaseSkillLoader({ debugLog });
276
+ log("skillLoader:", ports.skillLoader ? "\u4F7F\u7528\u6CE8\u5165\u5B9E\u73B0" : "AppbaseSkillLoader(\u9ED8\u8BA4)");
277
+ runtimePorts.mcpServerProvider = ports.mcpServerProvider ?? createAppbaseMcpProvider({
278
+ ...ports.mcpConfigResolver ? { resolveConfig: ports.mcpConfigResolver } : {},
279
+ ...ports.mcpAuthorizationHeaderProvider ? { getAuthorizationHeader: ports.mcpAuthorizationHeaderProvider } : {},
280
+ ...debugLog ? { debugLog } : {}
281
+ });
282
+ log("mcpServerProvider:", ports.mcpServerProvider ? "\u4F7F\u7528\u6CE8\u5165\u5B9E\u73B0" : ports.mcpConfigResolver ? "RemoteGateway(\u9ED8\u8BA4)" : "AppbaseMcpProvider noop(\u9ED8\u8BA4)");
283
+ const mcpAuthHeader = ports.mcpAuthorizationHeaderProvider;
284
+ runtimePorts.knowledgeSearchPort = ports.knowledgeSearchPort ?? (mcpAuthHeader ? new LinkqueKnowledgeSearchPort({
285
+ baseUrl: ports.knowledgeBaseUrl,
286
+ getAuthHeaders: async () => ({
287
+ authorization: await mcpAuthHeader(),
288
+ ...ports.gatewayAgentId ? { "x-linkque-agent-id": ports.gatewayAgentId } : {}
289
+ }),
290
+ ...debugLog ? { debugLog } : {}
291
+ }) : void 0);
292
+ log("knowledgeSearchPort:", ports.knowledgeSearchPort ? "\u4F7F\u7528\u6CE8\u5165\u5B9E\u73B0" : mcpAuthHeader ? "LinkqueKnowledgeSearchPort(\u9274\u6743\u5934\u6A21\u5F0F,\u9ED8\u8BA4\u6D3E\u751F)" : "Noop(\u9ED8\u8BA4,\u65E0\u9274\u6743\u5934\u6E90)");
293
+ runtimePorts.hookExecutor = ports.hookExecutor ?? (mcpAuthHeader ? new LinkquegwEventHookExecutor({
294
+ baseUrl: ports.knowledgeBaseUrl,
295
+ getAuthHeaders: async () => ({
296
+ authorization: await mcpAuthHeader(),
297
+ ...ports.gatewayAgentId ? { "x-linkque-agent-id": ports.gatewayAgentId } : {}
298
+ }),
299
+ ...debugLog ? { debugLog } : {}
300
+ }) : void 0);
301
+ log("hookExecutor:", ports.hookExecutor ? "\u4F7F\u7528\u6CE8\u5165\u5B9E\u73B0" : mcpAuthHeader ? "LinkquegwEventHookExecutor(\u9274\u6743\u5934\u6A21\u5F0F,\u9ED8\u8BA4\u6D3E\u751F)" : "Noop(\u9ED8\u8BA4,\u65E0\u9274\u6743\u5934\u6E90)");
302
+ if (debugLog) {
303
+ runtimePorts.debugLog = debugLog;
304
+ log("debugLog: \u6CE8\u5165 runtime(\u7EDF\u4E00 ctx.log sink)");
305
+ }
306
+ log("AgentRuntime \u88C5\u914D\u5B8C\u6210, agentId=", protocol.agentId, "model=", protocol.agent.model.defaultModel);
307
+ return AgentRuntime.create(protocol, runtimePorts);
308
+ }
309
+
310
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/asr.js
311
+ var ASR_CREATE_CREDENTIAL_OPERATION = "asr.createCredential";
312
+ async function runAsrCredential(method, env, authenticatedUserId, agentId, fetchImpl = fetch, envSelector, gatewayAuth, logCtx) {
313
+ if (method !== "POST")
314
+ return jsonError(405, "method_not_allowed", "\u4EC5\u652F\u6301 POST");
315
+ if (!authenticatedUserId) {
316
+ return jsonError(401, "authentication_required", "\u7F3A\u5C11 Appwrite \u8BA4\u8BC1\u7528\u6237");
317
+ }
318
+ let resolvedAuth;
319
+ try {
320
+ resolvedAuth = resolveGatewayAuthContext(gatewayAuth, env, logCtx, "asr");
321
+ } catch {
322
+ return jsonError(500, "auth_unconfigured", "linkquegw AAK \u672A\u914D\u7F6E");
323
+ }
324
+ const gatewayBaseUrl = resolveLinkqueEndpoints(env, envSelector).knowledgeBaseUrl;
325
+ if (!gatewayBaseUrl) {
326
+ return jsonError(500, "gateway_unconfigured", "linkquegw \u5730\u5740\u672A\u914D\u7F6E");
327
+ }
328
+ let response;
329
+ try {
330
+ response = await fetchImpl(`${gatewayBaseUrl}/api/asr/credential`, {
331
+ method: "POST",
332
+ headers: {
333
+ Authorization: resolvedAuth.authorizationHeader,
334
+ "x-linkque-agent-id": agentId
335
+ }
336
+ });
337
+ } catch {
338
+ return jsonError(502, "asr_gateway_unavailable", "\u5B9E\u65F6\u8BED\u97F3\u7B7E\u540D\u670D\u52A1\u4E0D\u53EF\u7528");
339
+ }
340
+ const payload = await response.json().catch(() => void 0);
341
+ const credential = payload?.data;
342
+ if (!response.ok || payload?.success !== true || typeof credential?.appKey !== "string" || typeof credential.digest !== "string" || typeof credential.timestamp !== "string") {
343
+ const message = typeof payload?.errorMessage === "string" ? payload.errorMessage : `\u5B9E\u65F6\u8BED\u97F3\u7B7E\u540D\u670D\u52A1\u8FD4\u56DE\u5F02\u5E38\uFF08${response.status}\uFF09`;
344
+ return jsonError(response.status >= 500 ? 502 : response.status, "asr_credential_failed", message);
345
+ }
346
+ return {
347
+ status: 200,
348
+ body: {
349
+ operation: ASR_CREATE_CREDENTIAL_OPERATION,
350
+ credential: {
351
+ appKey: credential.appKey,
352
+ digest: credential.digest,
353
+ timestamp: credential.timestamp
354
+ }
355
+ }
356
+ };
357
+ }
358
+
359
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/attachment.js
360
+ var ATTACHMENT_CREATE_UPLOAD_TOKEN_OPERATION = "attachment.createUploadToken";
361
+ async function runAttachmentUploadToken(method, env, authenticatedUserId, agentId, fetchImpl = fetch, envSelector, gatewayAuth, logCtx) {
362
+ if (method !== "POST") {
363
+ return jsonError(405, "method_not_allowed", "\u4EC5\u652F\u6301 POST");
364
+ }
365
+ if (!authenticatedUserId) {
366
+ return jsonError(401, "authentication_required", "\u7F3A\u5C11 Appwrite \u8BA4\u8BC1\u7528\u6237");
367
+ }
368
+ let resolvedAuth;
369
+ try {
370
+ resolvedAuth = resolveGatewayAuthContext(gatewayAuth, env, logCtx, "attachment");
371
+ } catch {
372
+ return jsonError(500, "auth_unconfigured", "linkquegw AAK \u672A\u914D\u7F6E");
373
+ }
374
+ const gatewayBaseUrl = resolveLinkqueEndpoints(env, envSelector).knowledgeBaseUrl;
375
+ if (!gatewayBaseUrl) {
376
+ return jsonError(500, "gateway_unconfigured", "linkquegw \u5730\u5740\u672A\u914D\u7F6E");
377
+ }
378
+ let response;
379
+ try {
380
+ response = await fetchImpl(`${gatewayBaseUrl}/api/mass/token`, {
381
+ method: "POST",
382
+ headers: {
383
+ Authorization: resolvedAuth.authorizationHeader,
384
+ "content-type": "application/json",
385
+ "x-linkque-agent-id": agentId
386
+ },
387
+ body: JSON.stringify({ massType: 3 })
388
+ });
389
+ } catch {
390
+ return jsonError(502, "attachment_gateway_unavailable", "\u56FE\u7247\u4E0A\u4F20\u51ED\u8BC1\u670D\u52A1\u4E0D\u53EF\u7528");
391
+ }
392
+ const payload = await response.json().catch(() => void 0);
393
+ const massToken = payload?.data?.massToken;
394
+ if (!response.ok || payload?.success !== true || typeof massToken !== "string" || !massToken.trim()) {
395
+ const message = typeof payload?.errorMessage === "string" ? payload.errorMessage : `\u56FE\u7247\u4E0A\u4F20\u51ED\u8BC1\u670D\u52A1\u8FD4\u56DE\u5F02\u5E38\uFF08${response.status}\uFF09`;
396
+ return jsonError(response.ok ? 502 : response.status >= 500 ? 502 : response.status, "attachment_upload_token_failed", message);
397
+ }
398
+ return {
399
+ status: 200,
400
+ body: { operation: ATTACHMENT_CREATE_UPLOAD_TOKEN_OPERATION, massToken }
401
+ };
402
+ }
403
+
404
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/chatRequest.js
405
+ function guardChatRequest(method, bodyJSON, logCtx) {
406
+ const log = (...a) => logCtx?.log?.("[chat]", ...a);
407
+ const logError = (...a) => logCtx?.error?.("[chat]", ...a);
408
+ log("\u6536\u5230\u8BF7\u6C42, method=", method);
409
+ if (method !== "POST") {
410
+ return jsonError(405, "method_not_allowed", "\u4EC5\u652F\u6301 POST");
411
+ }
412
+ let request;
413
+ try {
414
+ request = parseConversationRunRequest(bodyJSON);
415
+ } catch (err) {
416
+ const message = err instanceof ConversationProtocolValidationError ? err.issues.join("; ") : "\u8BF7\u6C42\u4F53\u65E0\u6CD5\u89E3\u6790\u4E3A ConversationRunRequest";
417
+ logError("\u8BF7\u6C42\u89E3\u6790\u5931\u8D25:", message);
418
+ return jsonError(400, "protocol_invalid", message);
419
+ }
420
+ log("\u8BF7\u6C42\u89E3\u6790\u6210\u529F, threadId=", request.threadId, "runId=", request.runId);
421
+ return request;
422
+ }
423
+
424
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/conversationHistoryStore.js
425
+ import { randomUUID } from "node:crypto";
426
+
427
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/conversationSearchStore.js
428
+ import { createHash } from "node:crypto";
429
+ var SEARCH_MESSAGE_SEGMENT_LENGTH = 4096;
430
+ var SEARCH_MESSAGE_SEGMENT_OVERLAP = 63;
431
+ var SEARCH_QUERY_BATCH_LIMIT = 50;
432
+ var SEARCH_SNIPPET_LENGTH = 120;
433
+ var CJK_KEYWORD_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u;
434
+ async function indexConversationSearchMessages(databases, config, messages, ownerId = "anonymous") {
435
+ for (const message of messages) {
436
+ const content = normalizeSearchContent(message.content);
437
+ if (!content)
438
+ continue;
439
+ const segments = splitSearchContent(content);
440
+ for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex += 1) {
441
+ const segment = segments[segmentIndex];
442
+ if (!segment)
443
+ continue;
444
+ const data = {
445
+ ownerId,
446
+ agentId: message.agentId,
447
+ historyVersion: CONVERSATION_HISTORY_VERSION,
448
+ threadId: message.threadId,
449
+ runId: message.runId,
450
+ itemId: message.itemId,
451
+ role: message.role,
452
+ content: segment,
453
+ segmentIndex,
454
+ createdAt: message.createdAt,
455
+ sequence: message.sequence
456
+ };
457
+ const documentId = createSearchDocumentId(ownerId, message, segmentIndex);
458
+ try {
459
+ await databases.createDocument(config.databaseId, config.tableId, documentId, data);
460
+ } catch (error) {
461
+ if (!isConflict(error))
462
+ throw error;
463
+ await databases.updateDocument(config.databaseId, config.tableId, documentId, data);
464
+ }
465
+ }
466
+ }
467
+ }
468
+ async function searchConversationMessages(databases, config, agentId, keyword, cursor, limit, ownerId = "anonymous") {
469
+ const normalizedKeyword = keyword.trim();
470
+ const decodedCursor = decodeSearchCursor(cursor);
471
+ const items = [];
472
+ const emittedItemIds = /* @__PURE__ */ new Set();
473
+ if (decodedCursor.boundaryItemId) {
474
+ emittedItemIds.add(decodedCursor.boundaryItemId);
475
+ }
476
+ let documentCursor = decodedCursor.documentId || void 0;
477
+ let lastScannedDocumentId;
478
+ let boundaryItemId;
479
+ let hasMore = false;
480
+ while (items.length < limit) {
481
+ const queries = [
482
+ Query.equal("ownerId", ownerId),
483
+ Query.equal("agentId", agentId),
484
+ Query.equal("historyVersion", CONVERSATION_HISTORY_VERSION),
485
+ // AppBase 当前全文分词器会把连续中文整段视为单个 token,例如索引内容
486
+ // “天气搜索部署验证成功”时 Query.search(content, "天气") 无法命中。中文
487
+ // 关键词改用 contains 保证子串语义;英文等有明确分词边界的内容仍走全文索引。
488
+ CJK_KEYWORD_PATTERN.test(normalizedKeyword) ? Query.contains("content", normalizedKeyword) : Query.search("content", normalizedKeyword),
489
+ Query.orderDesc("createdAt"),
490
+ Query.orderDesc("sequence"),
491
+ Query.orderAsc("itemId"),
492
+ Query.orderAsc("segmentIndex"),
493
+ Query.limit(SEARCH_QUERY_BATCH_LIMIT + 1)
494
+ ];
495
+ if (documentCursor)
496
+ queries.push(Query.cursorAfter(documentCursor));
497
+ const result = await databases.listDocuments(config.databaseId, config.tableId, queries);
498
+ const rows = result.documents;
499
+ const scanRows = rows.slice(0, SEARCH_QUERY_BATCH_LIMIT);
500
+ hasMore = rows.length > SEARCH_QUERY_BATCH_LIMIT;
501
+ for (const row of scanRows) {
502
+ lastScannedDocumentId = row.$id;
503
+ const itemId = readString(row.itemId);
504
+ if (!itemId || emittedItemIds.has(itemId))
505
+ continue;
506
+ const hit = readSearchHit(row, normalizedKeyword);
507
+ if (!hit)
508
+ continue;
509
+ emittedItemIds.add(itemId);
510
+ items.push(hit);
511
+ boundaryItemId = itemId;
512
+ if (items.length >= limit) {
513
+ hasMore = true;
514
+ break;
515
+ }
516
+ }
517
+ if (items.length >= limit || !hasMore || !lastScannedDocumentId)
518
+ break;
519
+ documentCursor = lastScannedDocumentId;
520
+ }
521
+ return {
522
+ items,
523
+ nextCursor: hasMore && lastScannedDocumentId ? encodeSearchCursor({
524
+ documentId: lastScannedDocumentId,
525
+ boundaryItemId
526
+ }) : void 0
527
+ };
528
+ }
529
+ function splitSearchContent(content) {
530
+ if (content.length <= SEARCH_MESSAGE_SEGMENT_LENGTH)
531
+ return [content];
532
+ const step = SEARCH_MESSAGE_SEGMENT_LENGTH - SEARCH_MESSAGE_SEGMENT_OVERLAP;
533
+ const segments = [];
534
+ for (let start = 0; start < content.length; start += step) {
535
+ segments.push(content.slice(start, start + SEARCH_MESSAGE_SEGMENT_LENGTH));
536
+ if (start + SEARCH_MESSAGE_SEGMENT_LENGTH >= content.length)
537
+ break;
538
+ }
539
+ return segments;
540
+ }
541
+ function readSearchHit(row, keyword) {
542
+ const threadId = readString(row.threadId);
543
+ const runId = readString(row.runId);
544
+ const itemId = readString(row.itemId);
545
+ const role = row.role === "user" || row.role === "assistant" ? row.role : void 0;
546
+ const content = readString(row.content);
547
+ const createdAt = readNonNegativeInteger(row.createdAt);
548
+ if (!threadId || !runId || !itemId || !role || !content)
549
+ return void 0;
550
+ const { snippet, highlightRanges } = createSearchSnippet(content, keyword);
551
+ return { threadId, runId, itemId, role, snippet, highlightRanges, createdAt };
552
+ }
553
+ function createSearchSnippet(content, keyword) {
554
+ const normalizedContent = normalizeSearchContent(content);
555
+ const lowerContent = normalizedContent.toLowerCase();
556
+ const lowerKeyword = keyword.toLowerCase();
557
+ const firstMatch = lowerContent.indexOf(lowerKeyword);
558
+ const contentStart = firstMatch < 0 ? 0 : Math.max(0, Math.min(firstMatch - 40, normalizedContent.length - SEARCH_SNIPPET_LENGTH));
559
+ const rawSnippet = normalizedContent.slice(contentStart, contentStart + SEARCH_SNIPPET_LENGTH);
560
+ const prefix = contentStart > 0 ? "\u2026" : "";
561
+ const suffix = contentStart + rawSnippet.length < normalizedContent.length ? "\u2026" : "";
562
+ const snippet = `${prefix}${rawSnippet}${suffix}`;
563
+ const highlightRanges = [];
564
+ if (lowerKeyword) {
565
+ const lowerSnippet = rawSnippet.toLowerCase();
566
+ let from = 0;
567
+ while (from < lowerSnippet.length) {
568
+ const start = lowerSnippet.indexOf(lowerKeyword, from);
569
+ if (start < 0)
570
+ break;
571
+ highlightRanges.push({
572
+ start: prefix.length + start,
573
+ length: keyword.length
574
+ });
575
+ from = start + Math.max(1, lowerKeyword.length);
576
+ }
577
+ }
578
+ return { snippet, highlightRanges };
579
+ }
580
+ function normalizeSearchContent(value) {
581
+ return value.replace(/\s+/g, " ").trim();
582
+ }
583
+ function createSearchDocumentId(ownerId, message, segmentIndex) {
584
+ return createHash("sha256").update([
585
+ ownerId,
586
+ message.agentId,
587
+ message.threadId,
588
+ message.runId,
589
+ message.itemId,
590
+ String(segmentIndex)
591
+ ].join("\0")).digest("hex").slice(0, 36);
592
+ }
593
+ function encodeSearchCursor(cursor) {
594
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
595
+ }
596
+ function decodeSearchCursor(value) {
597
+ if (!value)
598
+ return { documentId: "" };
599
+ try {
600
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
601
+ if (typeof parsed.documentId !== "string" || !parsed.documentId) {
602
+ throw new Error("missing documentId");
603
+ }
604
+ return {
605
+ documentId: parsed.documentId,
606
+ boundaryItemId: typeof parsed.boundaryItemId === "string" ? parsed.boundaryItemId : void 0
607
+ };
608
+ } catch {
609
+ throw new Error("\u641C\u7D22 cursor \u65E0\u6548");
610
+ }
611
+ }
612
+ function readString(value) {
613
+ return typeof value === "string" && value.trim() ? value : void 0;
614
+ }
615
+ function readNonNegativeInteger(value) {
616
+ return Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : 0;
617
+ }
618
+ function isConflict(error) {
619
+ const value = error;
620
+ return value?.code === 409 || /conflict|already exists/i.test(value?.message ?? "");
621
+ }
622
+
623
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/rowSecurity.js
624
+ function rowPermissions(ownerId) {
625
+ if (!ownerId)
626
+ return void 0;
627
+ const role = `user:${ownerId}`;
628
+ return [`read("${role}")`, `update("${role}")`, `delete("${role}")`];
629
+ }
630
+
631
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/conversationHistoryStore.js
632
+ var DEFAULT_HISTORY_OWNER_ID = "anonymous";
633
+ var RUN_CHUNK_QUERY_LIMIT = 5e3;
634
+ var ConversationHistoryNotFoundError = class extends Error {
635
+ constructor(message = "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D Agent") {
636
+ super(message);
637
+ this.name = "ConversationHistoryNotFoundError";
638
+ }
639
+ };
640
+ function resolveConversationHistoryStorage(env = process.env) {
641
+ return {
642
+ runDatabaseId: requiredEnv(env, "RUN_DATABASE_ID"),
643
+ runTableId: requiredEnv(env, "RUN_TABLE_ID"),
644
+ sessionDatabaseId: requiredEnv(env, "SESSION_DATABASE_ID"),
645
+ sessionTableId: requiredEnv(env, "SESSION_TABLE_ID"),
646
+ runIndexDatabaseId: requiredEnv(env, "RUN_INDEX_DATABASE_ID"),
647
+ runIndexTableId: requiredEnv(env, "RUN_INDEX_TABLE_ID"),
648
+ searchDatabaseId: requiredEnv(env, "SEARCH_DATABASE_ID"),
649
+ searchTableId: requiredEnv(env, "SEARCH_TABLE_ID")
650
+ };
651
+ }
652
+ function createConversationHistoryStore(databases, config, ownerId = DEFAULT_HISTORY_OWNER_ID) {
653
+ return {
654
+ async beginRun(request, agentId) {
655
+ const startedAt = Date.now();
656
+ const existingSession = await readDocument(databases, config.sessionDatabaseId, config.sessionTableId, request.threadId);
657
+ if (existingSession && !isOwnedConversation(existingSession, ownerId, agentId)) {
658
+ throw new ConversationHistoryNotFoundError();
659
+ }
660
+ const existingRun = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, request.runId);
661
+ if (existingRun && (existingRun.ownerId !== ownerId || existingRun.agentId !== agentId || existingRun.threadId !== request.threadId)) {
662
+ throw new ConversationHistoryNotFoundError("Run \u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237");
663
+ }
664
+ await upsertDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, request.runId, {
665
+ runId: request.runId,
666
+ threadId: request.threadId,
667
+ ownerId,
668
+ agentId,
669
+ historyVersion: CONVERSATION_HISTORY_VERSION,
670
+ requestJson: JSON.stringify(createStoredHistoryRequest(request)),
671
+ status: "running",
672
+ startedAt,
673
+ totalChunks: 0,
674
+ followUpQuestionsJson: null
675
+ }, rowPermissions(ownerId));
676
+ const copy = deriveConversationCopy(request);
677
+ const existingRunCount = readNonNegativeInteger2(existingSession?.runCount);
678
+ const sessionAlreadyContainsRun = existingSession?.lastRunId === request.runId;
679
+ const metadata = {
680
+ sessionId: request.threadId,
681
+ turns: readStringArray(existingSession?.turns),
682
+ ownerId,
683
+ agentId,
684
+ historyVersion: CONVERSATION_HISTORY_VERSION,
685
+ title: existingRunCount > 0 ? readNonEmptyString(existingSession?.title) ?? copy.title : copy.title,
686
+ preview: copy.preview,
687
+ runCount: sessionAlreadyContainsRun ? existingRunCount : existingRunCount + 1,
688
+ createdAt: readNonNegativeInteger2(existingSession?.createdAt) || startedAt,
689
+ lastMessageAt: startedAt,
690
+ lastActiveAt: startedAt,
691
+ lastRunId: request.runId,
692
+ lastRunStatus: "running"
693
+ };
694
+ await upsertDocument(databases, config.sessionDatabaseId, config.sessionTableId, request.threadId, metadata);
695
+ },
696
+ async completeRun(completion) {
697
+ const completedAt = completion.completedAt ?? Date.now();
698
+ const run = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, completion.runId);
699
+ if (!run || run.ownerId !== ownerId || run.threadId !== completion.threadId) {
700
+ throw new ConversationHistoryNotFoundError("Run \u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237");
701
+ }
702
+ const persistedStatus = readRunStatus(run.status);
703
+ const status = persistedStatus === "cancelling" || persistedStatus === "cancelled" ? "cancelled" : completion.status;
704
+ await databases.updateDocument(config.runIndexDatabaseId, config.runIndexTableId, completion.runId, {
705
+ status,
706
+ completedAt,
707
+ totalChunks: completion.totalChunks,
708
+ ...status === "cancelled" ? { cancelledAt: completedAt } : {}
709
+ });
710
+ const session = await readDocument(databases, config.sessionDatabaseId, config.sessionTableId, completion.threadId);
711
+ if (session?.ownerId === ownerId && session.lastRunId === completion.runId) {
712
+ await databases.updateDocument(config.sessionDatabaseId, config.sessionTableId, completion.threadId, { lastRunStatus: status });
713
+ }
714
+ },
715
+ async saveFollowUpQuestions(runId, threadId, value) {
716
+ const normalized = parseConversationFollowUpQuestions(value);
717
+ if (!normalized?.questions.length)
718
+ return;
719
+ const run = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, runId);
720
+ if (!run || run.ownerId !== ownerId || run.threadId !== threadId || readRunStatus(run.status) !== "done") {
721
+ throw new ConversationHistoryNotFoundError("Run \u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237\u6216\u5C1A\u672A\u6210\u529F\u7ED3\u675F");
722
+ }
723
+ await databases.updateDocument(config.runIndexDatabaseId, config.runIndexTableId, runId, { followUpQuestionsJson: JSON.stringify(normalized) });
724
+ },
725
+ async getRecentFollowUpQuestionContext(agentId, threadId, currentRunId, limit) {
726
+ const boundedLimit = Math.max(0, Math.min(limit, 2));
727
+ if (boundedLimit === 0)
728
+ return [];
729
+ const currentRun = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, currentRunId);
730
+ if (!currentRun || currentRun.ownerId !== ownerId || currentRun.agentId !== agentId || currentRun.threadId !== threadId) {
731
+ throw new ConversationHistoryNotFoundError("Run \u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237");
732
+ }
733
+ const result = await databases.listDocuments(config.runIndexDatabaseId, config.runIndexTableId, [
734
+ Query.equal("ownerId", ownerId),
735
+ Query.equal("agentId", agentId),
736
+ Query.equal("threadId", threadId),
737
+ Query.equal("historyVersion", CONVERSATION_HISTORY_VERSION),
738
+ Query.orderDesc("startedAt"),
739
+ Query.limit(25)
740
+ ]);
741
+ const entries = [];
742
+ for (const row of result.documents) {
743
+ if (row.runId === currentRunId || readRunStatus(row.status) !== "done") {
744
+ continue;
745
+ }
746
+ if (row.blockedQuestion === true || row.blockedAnswer === true)
747
+ continue;
748
+ const userMessage = readStoredRunUserMessage(row.requestJson);
749
+ if (!userMessage)
750
+ continue;
751
+ const followUpQuestions = readStoredFollowUpQuestions(row.followUpQuestionsJson)?.questions.map((question) => question.prompt) ?? [];
752
+ entries.push({ userMessage, followUpQuestions });
753
+ if (entries.length === boundedLimit)
754
+ break;
755
+ }
756
+ const chronological = [];
757
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
758
+ chronological.push(entries[index]);
759
+ }
760
+ return chronological;
761
+ },
762
+ async requestRunCancellation(agentId, runId) {
763
+ const run = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, runId);
764
+ if (!run || run.ownerId !== ownerId || run.agentId !== agentId) {
765
+ return "not_found";
766
+ }
767
+ const status = readRunStatus(run.status);
768
+ if (status === "done" || status === "error" || status === "cancelled") {
769
+ return status;
770
+ }
771
+ if (status !== "cancelling") {
772
+ await databases.updateDocument(config.runIndexDatabaseId, config.runIndexTableId, runId, { status: "cancelling", cancelRequestedAt: Date.now() });
773
+ }
774
+ return "cancelling";
775
+ },
776
+ async isRunCancellationRequested(agentId, runId) {
777
+ const run = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, runId);
778
+ if (!run || run.ownerId !== ownerId || run.agentId !== agentId) {
779
+ return false;
780
+ }
781
+ const status = readRunStatus(run.status);
782
+ return status === "cancelling" || status === "cancelled" || readNonNegativeInteger2(run.cancelRequestedAt) > 0;
783
+ },
784
+ async setRunBlocked(params) {
785
+ const existing = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, params.runId);
786
+ if (!existing) {
787
+ throw new ConversationHistoryNotFoundError("Run \u4E0D\u5B58\u5728");
788
+ }
789
+ const prevBlockedQuestion = existing.blockedQuestion === true;
790
+ const prevBlockedAnswer = existing.blockedAnswer === true;
791
+ const prevQuestionReplacement = readNonEmptyString(existing.questionReplacement);
792
+ const prevAnswerReplacement = readNonEmptyString(existing.answerReplacement);
793
+ const blockedQuestion = params.blockQuestion ?? prevBlockedQuestion;
794
+ const blockedAnswer = params.blockAnswer ?? prevBlockedAnswer;
795
+ const questionReplacement = params.blockQuestion === true ? params.questionReplacement ?? "" : params.blockQuestion === false ? null : prevQuestionReplacement ?? null;
796
+ const answerReplacement = params.blockAnswer === true ? params.answerReplacement ?? "" : params.blockAnswer === false ? null : prevAnswerReplacement ?? null;
797
+ const blockedAt = Date.now();
798
+ const blockedBy = readNonEmptyString(params.blockedBy);
799
+ await databases.updateDocument(config.runIndexDatabaseId, config.runIndexTableId, params.runId, {
800
+ blockedQuestion,
801
+ blockedAnswer,
802
+ questionReplacement,
803
+ answerReplacement,
804
+ blockedAt,
805
+ blockedBy: blockedBy ?? null
806
+ });
807
+ return {
808
+ runId: params.runId,
809
+ threadId: readNonEmptyString(existing.threadId) ?? params.threadId,
810
+ blockedQuestion,
811
+ blockedAnswer,
812
+ questionReplacement: readNonEmptyString(questionReplacement),
813
+ answerReplacement: readNonEmptyString(answerReplacement),
814
+ blockedAt,
815
+ blockedBy
816
+ };
817
+ },
818
+ async bootstrap(agentId, limit) {
819
+ const page = await listConversationPage(databases, config, agentId, ownerId, void 0, limit);
820
+ let activeConversation = await findActiveSession(databases, config, agentId, ownerId);
821
+ if (!activeConversation && page.items.length) {
822
+ const fallbackThreadId = page.items[0].threadId;
823
+ await activateSessionDocument(databases, config, fallbackThreadId);
824
+ activeConversation = await readDocument(databases, config.sessionDatabaseId, config.sessionTableId, fallbackThreadId);
825
+ }
826
+ const activeThreadId = readNonEmptyString(activeConversation?.sessionId) ?? await createSessionDocument(databases, config, agentId, ownerId);
827
+ return {
828
+ activeThreadId,
829
+ activeConversationExists: readNonNegativeInteger2(activeConversation?.runCount) > 0,
830
+ conversations: page.items,
831
+ nextCursor: page.nextCursor
832
+ };
833
+ },
834
+ listConversations: (agentId, cursor, limit) => listConversationPage(databases, config, agentId, ownerId, cursor, limit),
835
+ async getConversationRuns(agentId, threadId, cursor, limit, direction = "older") {
836
+ await assertConversationExists(databases, config, agentId, ownerId, threadId);
837
+ const queries = [
838
+ Query.equal("ownerId", ownerId),
839
+ Query.equal("agentId", agentId),
840
+ Query.equal("threadId", threadId),
841
+ Query.equal("historyVersion", CONVERSATION_HISTORY_VERSION),
842
+ direction === "newer" ? Query.orderAsc("startedAt") : Query.orderDesc("startedAt"),
843
+ Query.limit(limit + 1)
844
+ ];
845
+ if (cursor)
846
+ queries.push(Query.cursorAfter(cursor));
847
+ const result = await databases.listDocuments(config.runIndexDatabaseId, config.runIndexTableId, queries);
848
+ const pageRows = result.documents.slice(0, limit);
849
+ const runs = await Promise.all(pageRows.map((row) => readConversationRun(databases, config, row)));
850
+ return {
851
+ items: runs,
852
+ nextCursor: result.documents.length > limit ? readNonEmptyString(pageRows.at(-1)?.$id) : void 0
853
+ };
854
+ },
855
+ async getConversationRunWindow(agentId, threadId, runId, beforeLimit, afterLimit) {
856
+ await assertConversationExists(databases, config, agentId, ownerId, threadId);
857
+ const anchor = await readDocument(databases, config.runIndexDatabaseId, config.runIndexTableId, runId);
858
+ if (!anchor || anchor.threadId !== threadId || anchor.agentId !== agentId || anchor.ownerId !== ownerId || anchor.historyVersion !== CONVERSATION_HISTORY_VERSION) {
859
+ throw new ConversationHistoryNotFoundError("\u76EE\u6807\u5386\u53F2\u6D88\u606F\u4E0D\u5B58\u5728");
860
+ }
861
+ const baseQueries = [
862
+ Query.equal("ownerId", ownerId),
863
+ Query.equal("agentId", agentId),
864
+ Query.equal("threadId", threadId),
865
+ Query.equal("historyVersion", CONVERSATION_HISTORY_VERSION)
866
+ ];
867
+ const [olderResult, newerResult] = await Promise.all([
868
+ databases.listDocuments(config.runIndexDatabaseId, config.runIndexTableId, [
869
+ ...baseQueries,
870
+ Query.orderDesc("startedAt"),
871
+ Query.cursorAfter(runId),
872
+ Query.limit(beforeLimit + 1)
873
+ ]),
874
+ databases.listDocuments(config.runIndexDatabaseId, config.runIndexTableId, [
875
+ ...baseQueries,
876
+ Query.orderAsc("startedAt"),
877
+ Query.cursorAfter(runId),
878
+ Query.limit(afterLimit + 1)
879
+ ])
880
+ ]);
881
+ const olderRows = olderResult.documents.slice(0, beforeLimit);
882
+ const newerRows = newerResult.documents.slice(0, afterLimit);
883
+ const rows = [...olderRows, anchor, ...newerRows];
884
+ return {
885
+ items: await Promise.all(rows.map((row) => readConversationRun(databases, config, row))),
886
+ olderCursor: olderResult.documents.length > beforeLimit ? readNonEmptyString(olderRows.at(-1)?.$id) : void 0,
887
+ newerCursor: newerResult.documents.length > afterLimit ? readNonEmptyString(newerRows.at(-1)?.$id) : void 0
888
+ };
889
+ },
890
+ indexSearchMessages: (messages) => indexConversationSearchMessages(databases, { databaseId: config.searchDatabaseId, tableId: config.searchTableId }, messages, ownerId),
891
+ searchConversationMessages: (agentId, keyword, cursor, limit) => searchConversationMessages(databases, { databaseId: config.searchDatabaseId, tableId: config.searchTableId }, agentId, keyword, cursor, limit, ownerId),
892
+ createSession: (agentId) => createSessionDocument(databases, config, agentId, ownerId),
893
+ async activateConversation(agentId, threadId) {
894
+ await assertConversationExists(databases, config, agentId, ownerId, threadId);
895
+ await activateSessionDocument(databases, config, threadId);
896
+ }
897
+ };
898
+ }
899
+ async function listConversationPage(databases, config, agentId, ownerId, cursor, limit) {
900
+ const queries = [
901
+ Query.equal("ownerId", ownerId),
902
+ Query.equal("agentId", agentId),
903
+ Query.equal("historyVersion", CONVERSATION_HISTORY_VERSION),
904
+ Query.greaterThan("runCount", 0),
905
+ Query.orderDesc("lastMessageAt"),
906
+ Query.limit(limit + 1)
907
+ ];
908
+ if (cursor)
909
+ queries.push(Query.cursorAfter(cursor));
910
+ const result = await databases.listDocuments(config.sessionDatabaseId, config.sessionTableId, queries);
911
+ const rows = result.documents.slice(0, limit);
912
+ return {
913
+ items: rows.map(readConversationSummary),
914
+ nextCursor: result.documents.length > limit ? readNonEmptyString(rows.at(-1)?.$id) : void 0
915
+ };
916
+ }
917
+ async function readConversationRun(databases, config, row) {
918
+ const runId = requireRowString(row, "runId");
919
+ const threadId = requireRowString(row, "threadId");
920
+ const requestJson = requireRowString(row, "requestJson");
921
+ const chunks = await databases.listDocuments(config.runDatabaseId, config.runTableId, [
922
+ Query.equal("runId", runId),
923
+ Query.orderAsc("seqIndex"),
924
+ Query.limit(RUN_CHUNK_QUERY_LIMIT)
925
+ ]);
926
+ const chunkRows = chunks.documents;
927
+ const status = readRunStatus(row.status);
928
+ const expectedChunks = readNonNegativeInteger2(row.totalChunks);
929
+ if (chunks.total > chunkRows.length || status !== "running" && expectedChunks > chunkRows.length) {
930
+ throw new Error(`run ${runId} \u5206\u7247\u4E0D\u5B8C\u6574\uFF0C\u5DF2\u8BFB\u53D6 ${chunkRows.length} / ${Math.max(chunks.total, expectedChunks)}`);
931
+ }
932
+ return {
933
+ runId,
934
+ threadId,
935
+ request: parseConversationRunRequest(JSON.parse(requestJson)),
936
+ lines: chunkRows.flatMap((chunk) => readStringArray(chunk.lines)),
937
+ status,
938
+ startedAt: readNonNegativeInteger2(row.startedAt),
939
+ completedAt: row.completedAt === null || row.completedAt === void 0 ? void 0 : readNonNegativeInteger2(row.completedAt),
940
+ blockedQuestion: row.blockedQuestion === true,
941
+ blockedAnswer: row.blockedAnswer === true,
942
+ questionReplacement: readNonEmptyString(row.questionReplacement),
943
+ answerReplacement: readNonEmptyString(row.answerReplacement),
944
+ blockedAt: row.blockedAt === null || row.blockedAt === void 0 ? void 0 : readNonNegativeInteger2(row.blockedAt),
945
+ blockedBy: readNonEmptyString(row.blockedBy),
946
+ followUpQuestions: readStoredFollowUpQuestions(row.followUpQuestionsJson)
947
+ };
948
+ }
949
+ function readStoredFollowUpQuestions(value) {
950
+ if (typeof value !== "string" || !value.trim())
951
+ return void 0;
952
+ try {
953
+ const parsed = parseConversationFollowUpQuestions(JSON.parse(value));
954
+ return parsed?.questions.length ? parsed : void 0;
955
+ } catch {
956
+ return void 0;
957
+ }
958
+ }
959
+ function readStoredRunUserMessage(value) {
960
+ if (typeof value !== "string" || !value.trim())
961
+ return void 0;
962
+ try {
963
+ const request = parseConversationRunRequest(JSON.parse(value));
964
+ for (let index = request.messages.length - 1; index >= 0; index -= 1) {
965
+ const message = request.messages[index];
966
+ if (message.role !== "user")
967
+ continue;
968
+ return typeof message.content === "string" ? readNonEmptyString(message.content.replace(/\s+/gu, " ")) : void 0;
969
+ }
970
+ return void 0;
971
+ } catch {
972
+ return void 0;
973
+ }
974
+ }
975
+ function readConversationSummary(row) {
976
+ return {
977
+ threadId: requireRowString(row, "sessionId"),
978
+ title: readNonEmptyString(row.title) ?? "\u65B0\u4F1A\u8BDD",
979
+ preview: typeof row.preview === "string" ? row.preview : "",
980
+ runCount: readNonNegativeInteger2(row.runCount),
981
+ createdAt: readNonNegativeInteger2(row.createdAt),
982
+ updatedAt: readNonNegativeInteger2(row.lastMessageAt),
983
+ lastRunStatus: readRunStatus(row.lastRunStatus)
984
+ };
985
+ }
986
+ async function assertConversationExists(databases, config, agentId, ownerId, threadId) {
987
+ const row = await readDocument(databases, config.sessionDatabaseId, config.sessionTableId, threadId);
988
+ if (!isOwnedConversation(row, ownerId, agentId)) {
989
+ throw new ConversationHistoryNotFoundError();
990
+ }
991
+ }
992
+ async function findActiveSession(databases, config, agentId, ownerId) {
993
+ const result = await databases.listDocuments(config.sessionDatabaseId, config.sessionTableId, [
994
+ Query.equal("ownerId", ownerId),
995
+ Query.equal("agentId", agentId),
996
+ Query.equal("historyVersion", CONVERSATION_HISTORY_VERSION),
997
+ Query.orderDesc("lastActiveAt"),
998
+ Query.limit(1)
999
+ ]);
1000
+ return result.documents[0];
1001
+ }
1002
+ async function createSessionDocument(databases, config, agentId, ownerId) {
1003
+ const threadId = createConversationThreadId();
1004
+ const now = Date.now();
1005
+ await databases.createDocument(config.sessionDatabaseId, config.sessionTableId, threadId, {
1006
+ sessionId: threadId,
1007
+ turns: [],
1008
+ ownerId,
1009
+ agentId,
1010
+ historyVersion: CONVERSATION_HISTORY_VERSION,
1011
+ title: "\u65B0\u4F1A\u8BDD",
1012
+ preview: "",
1013
+ runCount: 0,
1014
+ createdAt: now,
1015
+ lastActiveAt: now
1016
+ });
1017
+ return threadId;
1018
+ }
1019
+ function isOwnedConversation(row, ownerId, agentId) {
1020
+ return Boolean(row && row.ownerId === ownerId && row.agentId === agentId && row.historyVersion === CONVERSATION_HISTORY_VERSION);
1021
+ }
1022
+ async function activateSessionDocument(databases, config, threadId) {
1023
+ await databases.updateDocument(config.sessionDatabaseId, config.sessionTableId, threadId, { lastActiveAt: Date.now() });
1024
+ }
1025
+ async function upsertDocument(databases, databaseId, tableId, documentId, data, permissions) {
1026
+ const existing = await readDocument(databases, databaseId, tableId, documentId);
1027
+ if (existing) {
1028
+ await databases.updateDocument(databaseId, tableId, documentId, data);
1029
+ } else {
1030
+ await databases.createDocument(databaseId, tableId, documentId, data, permissions);
1031
+ }
1032
+ }
1033
+ async function readDocument(databases, databaseId, tableId, documentId) {
1034
+ try {
1035
+ return await databases.getDocument(databaseId, tableId, documentId);
1036
+ } catch (error) {
1037
+ if (isNotFound2(error))
1038
+ return void 0;
1039
+ throw error;
1040
+ }
1041
+ }
1042
+ function deriveConversationCopy(request) {
1043
+ const content = request.messages.at(-1)?.content;
1044
+ let visibleText = "";
1045
+ if (typeof content === "string") {
1046
+ visibleText = content.trim();
1047
+ } else if (content?.type === "a2ui.event") {
1048
+ visibleText = content.displayText?.trim() || content.event.name;
1049
+ }
1050
+ if (!visibleText && request.attachments.length)
1051
+ visibleText = "[\u56FE\u7247]";
1052
+ if (!visibleText)
1053
+ visibleText = "\u65B0\u4F1A\u8BDD";
1054
+ return {
1055
+ title: visibleText.slice(0, 40),
1056
+ preview: visibleText.slice(0, 160)
1057
+ };
1058
+ }
1059
+ function createStoredHistoryRequest(request) {
1060
+ return {
1061
+ ...request,
1062
+ messages: request.messages.map((message) => ({ ...message })),
1063
+ attachments: request.attachments.map((attachment) => {
1064
+ const hasPersistentTarget = Boolean(attachment.resourceId || attachment.url);
1065
+ return {
1066
+ id: attachment.id,
1067
+ resourceId: attachment.resourceId,
1068
+ name: attachment.name,
1069
+ mimeType: attachment.mimeType,
1070
+ size: attachment.size,
1071
+ url: hasPersistentTarget ? attachment.url : "appwrite://image-placeholder",
1072
+ metadata: hasPersistentTarget ? attachment.metadata : { ...attachment.metadata, uploadPending: true }
1073
+ };
1074
+ }),
1075
+ metadata: { ...request.metadata }
1076
+ };
1077
+ }
1078
+ function createConversationThreadId() {
1079
+ return `thread-${Date.now()}-${randomUUID().replaceAll("-", "").slice(0, 12)}`;
1080
+ }
1081
+ function readRunStatus(value) {
1082
+ return value === "done" || value === "error" || value === "cancelling" || value === "cancelled" ? value : "running";
1083
+ }
1084
+ function readStringArray(value) {
1085
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
1086
+ }
1087
+ function readNonNegativeInteger2(value) {
1088
+ return Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : 0;
1089
+ }
1090
+ function readNonEmptyString(value) {
1091
+ return typeof value === "string" && value.trim() ? value : void 0;
1092
+ }
1093
+ function requireRowString(row, field) {
1094
+ const value = readNonEmptyString(row[field]);
1095
+ if (!value)
1096
+ throw new Error(`\u5386\u53F2\u6570\u636E\u7F3A\u5C11\u5B57\u6BB5 ${field}`);
1097
+ return value;
1098
+ }
1099
+ function requiredEnv(env, name) {
1100
+ const value = env[name]?.trim();
1101
+ if (!value)
1102
+ throw new Error(`${name} \u672A\u914D\u7F6E`);
1103
+ return value;
1104
+ }
1105
+ function isNotFound2(error) {
1106
+ if (!error)
1107
+ return false;
1108
+ const value = error;
1109
+ return value.code === 404 || /not found|404/i.test(value.message ?? "");
1110
+ }
1111
+
1112
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/cancel.js
1113
+ async function runCancelConversation(method, bodyJSON, env, deps, logCtx, ownerId, appwriteApiKey) {
1114
+ if (method !== "POST") {
1115
+ return controlError(405, "method_not_allowed", "\u4EC5\u652F\u6301 POST");
1116
+ }
1117
+ let request;
1118
+ try {
1119
+ request = parseCancelConversationRunRequest(bodyJSON);
1120
+ } catch (error) {
1121
+ const message = error instanceof ConversationControlValidationError ? error.issues.join("; ") : "\u8BF7\u6C42\u4F53\u65E0\u6CD5\u89E3\u6790\u4E3A CancelConversationRunRequest";
1122
+ return controlError(400, "control_protocol_invalid", message);
1123
+ }
1124
+ const protocol = deps?.protocol ?? getBakedProtocol();
1125
+ let store = deps?.store;
1126
+ if (!store) {
1127
+ try {
1128
+ const handle = createAppBaseClient(env, logCtx, appwriteApiKey);
1129
+ store = createConversationHistoryStore(handle.databases, resolveConversationHistoryStorage(env), ownerId);
1130
+ } catch (error) {
1131
+ logCtx?.error?.("[cancel] \u5B58\u50A8\u914D\u7F6E\u5931\u8D25:", error);
1132
+ return controlError(500, "cancel_storage_unconfigured", error instanceof Error ? error.message : "\u53D6\u6D88\u72B6\u6001\u5B58\u50A8\u672A\u914D\u7F6E");
1133
+ }
1134
+ }
1135
+ if (!store.requestRunCancellation) {
1136
+ return controlError(501, "cancel_unsupported", "\u5F53\u524D\u90E8\u7F72\u4E0D\u652F\u6301\u670D\u52A1\u7AEF\u53D6\u6D88");
1137
+ }
1138
+ try {
1139
+ const status = await store.requestRunCancellation(protocol.agentId, request.runId);
1140
+ const response = {
1141
+ operation: CANCEL_CONVERSATION_RUN_OPERATION,
1142
+ runId: request.runId,
1143
+ accepted: status === "cancelling" || status === "cancelled",
1144
+ status
1145
+ };
1146
+ return { status: 200, body: response };
1147
+ } catch (error) {
1148
+ logCtx?.error?.("[cancel] \u8BF7\u6C42\u6267\u884C\u5931\u8D25:", error);
1149
+ return controlError(500, "cancel_request_failed", error instanceof Error ? error.message : "\u53D6\u6D88\u8BF7\u6C42\u6267\u884C\u5931\u8D25");
1150
+ }
1151
+ }
1152
+ function controlError(status, code, message) {
1153
+ return { status, body: { error: { code, message, status } } };
1154
+ }
1155
+
1156
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/appbaseStore.js
1157
+ function createAppBaseStore(databases, databaseId, tableId, ownerId) {
1158
+ const permissions = rowPermissions(ownerId);
1159
+ return {
1160
+ create: async (row) => {
1161
+ const created = await databases.createDocument(
1162
+ databaseId,
1163
+ tableId,
1164
+ "unique()",
1165
+ // 让 AppBase 生成唯一 rowId
1166
+ {
1167
+ runId: row.runId,
1168
+ threadId: row.threadId,
1169
+ seqIndex: row.seqIndex,
1170
+ status: row.status,
1171
+ lines: row.lines,
1172
+ ...ownerId ? { ownerId } : {}
1173
+ },
1174
+ permissions
1175
+ );
1176
+ return created.$id;
1177
+ }
1178
+ };
1179
+ }
1180
+
1181
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/chunkSink.js
1182
+ var ChunkSink = class {
1183
+ storage;
1184
+ chunkSize;
1185
+ flushIntervalMs;
1186
+ ctx;
1187
+ /** 已 create 的 chunk 数(含 chunk#0);下次 seqIndex = chunkCount。 */
1188
+ chunkCount = 0;
1189
+ pending = [];
1190
+ firstId;
1191
+ flushTimer;
1192
+ operationQueue = Promise.resolve();
1193
+ deferredError;
1194
+ closed = false;
1195
+ constructor(storage, ctx, options = {}) {
1196
+ this.storage = storage;
1197
+ this.chunkSize = options.chunkSize ?? 30;
1198
+ this.flushIntervalMs = options.flushIntervalMs ?? 250;
1199
+ this.ctx = ctx;
1200
+ }
1201
+ /** 建 chunk#0(running,空)。必须最先调用。返回 chunk#0 的 $id。 */
1202
+ async open() {
1203
+ await this.enqueue(async () => {
1204
+ this.firstId = await this.createChunk(0, "running", []);
1205
+ });
1206
+ return this.id;
1207
+ }
1208
+ /** 已建立的 chunk#0 id。 */
1209
+ get id() {
1210
+ if (this.firstId === void 0)
1211
+ throw new Error("ChunkSink.open() \u672A\u8C03\u7528");
1212
+ return this.firstId;
1213
+ }
1214
+ /** 已 create 的 chunk 总数(含 chunk#0)。 */
1215
+ get totalChunks() {
1216
+ return this.chunkCount;
1217
+ }
1218
+ /**
1219
+ * 喂入一段 SSE 编码文本,按行追加并按 chunkSize 切块 create。
1220
+ * chunk 通常等于 `encodeConversationSseEvent(ev)` 的产物(以 \n\n 结尾)。
1221
+ */
1222
+ async writeChunk(chunk, options = {}) {
1223
+ if (this.closed)
1224
+ throw new Error("ChunkSink \u5DF2\u7ED3\u675F\uFF0C\u4E0D\u80FD\u7EE7\u7EED\u5199\u5165");
1225
+ const newLines = chunk.split("\n");
1226
+ if (newLines.length && newLines[newLines.length - 1] === "")
1227
+ newLines.pop();
1228
+ if (!newLines.length)
1229
+ return;
1230
+ await this.enqueue(async () => {
1231
+ this.pending.push(...newLines);
1232
+ await this.flushFullChunks();
1233
+ if (options.flush) {
1234
+ await this.flushPending();
1235
+ } else {
1236
+ this.scheduleFlush();
1237
+ }
1238
+ });
1239
+ }
1240
+ /** 终结流:把剩余 pending 作为末 chunk create,带终态 status。 */
1241
+ async finalize(status) {
1242
+ this.closed = true;
1243
+ this.clearFlushTimer();
1244
+ await this.enqueue(async () => {
1245
+ await this.createChunk(this.chunkCount, status, this.pending);
1246
+ this.pending = [];
1247
+ });
1248
+ }
1249
+ /** 当 pending 行数 ≥ chunkSize 时,循环切出 running chunk。 */
1250
+ async flushFullChunks() {
1251
+ while (this.pending.length >= this.chunkSize) {
1252
+ const lines = this.pending.splice(0, this.chunkSize);
1253
+ await this.createChunk(this.chunkCount, "running", lines);
1254
+ }
1255
+ if (!this.pending.length)
1256
+ this.clearFlushTimer();
1257
+ }
1258
+ /** 把不足阈值的 pending 作为 running chunk 写出。 */
1259
+ async flushPending() {
1260
+ this.clearFlushTimer();
1261
+ if (!this.pending.length)
1262
+ return;
1263
+ const lines = [...this.pending];
1264
+ await this.createChunk(this.chunkCount, "running", lines);
1265
+ this.pending = [];
1266
+ }
1267
+ /** 从首批 pending 开始计时,不因后续 delta 重置,保证最大等待时间有上界。 */
1268
+ scheduleFlush() {
1269
+ if (!this.pending.length || this.flushTimer || this.closed)
1270
+ return;
1271
+ this.flushTimer = setTimeout(() => {
1272
+ this.flushTimer = void 0;
1273
+ void this.enqueue(async () => {
1274
+ if (this.closed)
1275
+ return;
1276
+ await this.flushPending();
1277
+ }).catch(() => void 0);
1278
+ }, this.flushIntervalMs);
1279
+ }
1280
+ clearFlushTimer() {
1281
+ if (!this.flushTimer)
1282
+ return;
1283
+ clearTimeout(this.flushTimer);
1284
+ this.flushTimer = void 0;
1285
+ }
1286
+ /** 串行化 timer/write/finalize,保持 seqIndex 严格递增并把后台 timer 错误传给主流程。 */
1287
+ enqueue(operation) {
1288
+ const next = this.operationQueue.then(async () => {
1289
+ if (this.deferredError)
1290
+ throw this.deferredError;
1291
+ await operation();
1292
+ });
1293
+ this.operationQueue = next.catch((error) => {
1294
+ this.deferredError ??= error;
1295
+ });
1296
+ return next;
1297
+ }
1298
+ /** create 一个 chunk;seqIndex 取当前 chunkCount,create 后 chunkCount 自增。 */
1299
+ async createChunk(seqIndex, status, lines) {
1300
+ const id = await this.storage.create({
1301
+ runId: this.ctx.runId,
1302
+ threadId: this.ctx.threadId,
1303
+ seqIndex,
1304
+ status,
1305
+ lines
1306
+ });
1307
+ this.chunkCount += 1;
1308
+ return id;
1309
+ }
1310
+ };
1311
+
1312
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/ports/appbaseSessionLease.js
1313
+ import { createHash as createHash2 } from "node:crypto";
1314
+ var DEFAULT_LEASE_TTL_MS = 20 * 60 * 1e3;
1315
+ var TRANSACTION_TTL_SECONDS = 60;
1316
+ function createSessionLease(persistence, options = {}) {
1317
+ const ttlMs = options.ttlMs ?? DEFAULT_LEASE_TTL_MS;
1318
+ const now = options.now ?? Date.now;
1319
+ return {
1320
+ acquire: async (threadId, ownerId) => {
1321
+ const leaseId = createLeaseId(threadId);
1322
+ if (await tryCreate(persistence, leaseId, ownerId))
1323
+ return true;
1324
+ const current = await persistence.read(leaseId);
1325
+ if (current && now() - current.updatedAt <= ttlMs)
1326
+ return false;
1327
+ if (!current)
1328
+ return tryCreate(persistence, leaseId, ownerId);
1329
+ return persistence.replaceIfOwner(leaseId, current.ownerId, ownerId);
1330
+ },
1331
+ release: async (threadId, ownerId) => {
1332
+ const leaseId = createLeaseId(threadId);
1333
+ await persistence.deleteIfOwner(leaseId, ownerId);
1334
+ }
1335
+ };
1336
+ }
1337
+ function createAppBaseSessionLease(databases, databaseId, tableId, appbaseUserId, options = {}) {
1338
+ const permissions = rowPermissions(appbaseUserId);
1339
+ const persistence = {
1340
+ create: async (leaseId, ownerId) => {
1341
+ await databases.createDocument(databaseId, tableId, leaseId, {
1342
+ sessionId: leaseId,
1343
+ turns: [ownerId],
1344
+ ...appbaseUserId ? { ownerId: appbaseUserId } : {}
1345
+ }, permissions);
1346
+ },
1347
+ read: async (leaseId) => {
1348
+ return readLeaseRecord(databases, databaseId, tableId, leaseId);
1349
+ },
1350
+ replaceIfOwner: async (leaseId, expectedOwnerId, nextOwnerId) => {
1351
+ return mutateIfOwner(databases, databaseId, tableId, leaseId, expectedOwnerId, async (transactionId) => {
1352
+ await databases.updateDocument({
1353
+ databaseId,
1354
+ collectionId: tableId,
1355
+ documentId: leaseId,
1356
+ data: { turns: [nextOwnerId] },
1357
+ transactionId
1358
+ });
1359
+ });
1360
+ },
1361
+ deleteIfOwner: async (leaseId, expectedOwnerId) => {
1362
+ return mutateIfOwner(databases, databaseId, tableId, leaseId, expectedOwnerId, async (transactionId) => {
1363
+ await databases.deleteDocument({
1364
+ databaseId,
1365
+ collectionId: tableId,
1366
+ documentId: leaseId,
1367
+ transactionId
1368
+ });
1369
+ });
1370
+ }
1371
+ };
1372
+ return createSessionLease(persistence, options);
1373
+ }
1374
+ async function tryCreate(persistence, leaseId, ownerId) {
1375
+ try {
1376
+ await persistence.create(leaseId, ownerId);
1377
+ return true;
1378
+ } catch (err) {
1379
+ if (isConflict2(err))
1380
+ return false;
1381
+ throw err;
1382
+ }
1383
+ }
1384
+ function createLeaseId(threadId) {
1385
+ const digest = createHash2("sha256").update(threadId).digest("hex");
1386
+ return `lease_${digest.slice(0, 30)}`;
1387
+ }
1388
+ async function mutateIfOwner(databases, databaseId, tableId, leaseId, expectedOwnerId, stageMutation) {
1389
+ const transaction = await databases.createTransaction({
1390
+ ttl: TRANSACTION_TTL_SECONDS
1391
+ });
1392
+ try {
1393
+ const current = await readLeaseRecord(databases, databaseId, tableId, leaseId, transaction.$id);
1394
+ if (!current || current.ownerId !== expectedOwnerId) {
1395
+ await rollbackTransaction(databases, transaction.$id);
1396
+ return false;
1397
+ }
1398
+ await stageMutation(transaction.$id);
1399
+ await databases.updateTransaction({
1400
+ transactionId: transaction.$id,
1401
+ commit: true
1402
+ });
1403
+ return true;
1404
+ } catch (err) {
1405
+ await rollbackTransaction(databases, transaction.$id);
1406
+ if (isConflict2(err) || isNotFound3(err))
1407
+ return false;
1408
+ throw err;
1409
+ }
1410
+ }
1411
+ async function readLeaseRecord(databases, databaseId, tableId, leaseId, transactionId) {
1412
+ try {
1413
+ const doc = await databases.getDocument({
1414
+ databaseId,
1415
+ collectionId: tableId,
1416
+ documentId: leaseId,
1417
+ transactionId
1418
+ });
1419
+ const raw = doc;
1420
+ const ownerId = Array.isArray(raw.turns) && typeof raw.turns[0] === "string" ? raw.turns[0] : void 0;
1421
+ if (!ownerId)
1422
+ return void 0;
1423
+ const timestamp = typeof raw.$updatedAt === "string" ? raw.$updatedAt : raw.$createdAt;
1424
+ const parsedUpdatedAt = typeof timestamp === "string" ? Date.parse(timestamp) : Number.NaN;
1425
+ return {
1426
+ ownerId,
1427
+ updatedAt: Number.isFinite(parsedUpdatedAt) ? parsedUpdatedAt : Date.now()
1428
+ };
1429
+ } catch (err) {
1430
+ if (isNotFound3(err))
1431
+ return void 0;
1432
+ throw err;
1433
+ }
1434
+ }
1435
+ async function rollbackTransaction(databases, transactionId) {
1436
+ await databases.updateTransaction({ transactionId, rollback: true }).catch(() => void 0);
1437
+ }
1438
+ function isConflict2(err) {
1439
+ if (!err)
1440
+ return false;
1441
+ const value = err;
1442
+ return value.code === 409 || /already exists|conflict|duplicate|409/i.test(value.message ?? "");
1443
+ }
1444
+ function isNotFound3(err) {
1445
+ if (!err)
1446
+ return false;
1447
+ const value = err;
1448
+ return value.code === 404 || /not found|404/i.test(value.message ?? "");
1449
+ }
1450
+
1451
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/history.js
1452
+ async function runHistory(method, bodyJSON, env, deps, logCtx, historyOwnerId, appwriteApiKey) {
1453
+ if (method !== "POST") {
1454
+ return historyError(405, "method_not_allowed", "\u4EC5\u652F\u6301 POST");
1455
+ }
1456
+ let request;
1457
+ try {
1458
+ request = parseConversationHistoryRequest(bodyJSON);
1459
+ } catch (error) {
1460
+ const message = error instanceof ConversationHistoryValidationError ? error.issues.join("; ") : "\u8BF7\u6C42\u4F53\u65E0\u6CD5\u89E3\u6790\u4E3A ConversationHistoryRequest";
1461
+ return historyError(400, "history_protocol_invalid", message);
1462
+ }
1463
+ const protocol = deps?.protocol ?? getBakedProtocol();
1464
+ if (request.agentId !== protocol.agentId) {
1465
+ return historyError(403, "agent_mismatch", "\u8BF7\u6C42 Agent \u4E0E\u90E8\u7F72\u7248\u672C\u4E0D\u5339\u914D");
1466
+ }
1467
+ let store = deps?.store;
1468
+ if (!store) {
1469
+ try {
1470
+ const handle = createAppBaseClient(env, logCtx, appwriteApiKey);
1471
+ store = createConversationHistoryStore(handle.databases, resolveConversationHistoryStorage(env), historyOwnerId);
1472
+ } catch (error) {
1473
+ logCtx?.error?.("[history] \u5B58\u50A8\u914D\u7F6E\u5931\u8D25:", error);
1474
+ return historyError(500, "history_storage_unconfigured", error instanceof Error ? error.message : "\u4F1A\u8BDD\u5386\u53F2\u5B58\u50A8\u672A\u914D\u7F6E");
1475
+ }
1476
+ }
1477
+ try {
1478
+ switch (request.operation) {
1479
+ case "bootstrap": {
1480
+ const result = await store.bootstrap(request.agentId, request.limit ?? DEFAULT_CONVERSATION_LIST_LIMIT);
1481
+ return {
1482
+ status: 200,
1483
+ body: { operation: request.operation, ...result }
1484
+ };
1485
+ }
1486
+ case "listConversations": {
1487
+ const page = await store.listConversations(request.agentId, request.cursor, request.limit ?? DEFAULT_CONVERSATION_LIST_LIMIT);
1488
+ return {
1489
+ status: 200,
1490
+ body: {
1491
+ operation: request.operation,
1492
+ conversations: page.items,
1493
+ nextCursor: page.nextCursor
1494
+ }
1495
+ };
1496
+ }
1497
+ case "getConversationRuns": {
1498
+ const page = await store.getConversationRuns(request.agentId, request.threadId, request.cursor, request.limit ?? DEFAULT_CONVERSATION_RUN_LIST_LIMIT, request.direction);
1499
+ return {
1500
+ status: 200,
1501
+ body: {
1502
+ operation: request.operation,
1503
+ threadId: request.threadId,
1504
+ runs: page.items,
1505
+ nextCursor: page.nextCursor
1506
+ }
1507
+ };
1508
+ }
1509
+ case "getConversationRunWindow": {
1510
+ const window = await store.getConversationRunWindow(request.agentId, request.threadId, request.runId, request.beforeLimit ?? 5, request.afterLimit ?? 5);
1511
+ return {
1512
+ status: 200,
1513
+ body: {
1514
+ operation: request.operation,
1515
+ threadId: request.threadId,
1516
+ anchorRunId: request.runId,
1517
+ runs: window.items,
1518
+ olderCursor: window.olderCursor,
1519
+ newerCursor: window.newerCursor
1520
+ }
1521
+ };
1522
+ }
1523
+ case "searchConversationMessages": {
1524
+ const page = await store.searchConversationMessages(request.agentId, request.keyword.trim(), request.cursor, request.limit ?? DEFAULT_CONVERSATION_SEARCH_LIMIT);
1525
+ return {
1526
+ status: 200,
1527
+ body: {
1528
+ operation: request.operation,
1529
+ keyword: request.keyword.trim(),
1530
+ results: page.items,
1531
+ nextCursor: page.nextCursor
1532
+ }
1533
+ };
1534
+ }
1535
+ case "createSession":
1536
+ case "createConversation": {
1537
+ const threadId = await store.createSession(request.agentId);
1538
+ return {
1539
+ status: 200,
1540
+ body: { operation: request.operation, threadId }
1541
+ };
1542
+ }
1543
+ case "activateConversation": {
1544
+ await store.activateConversation(request.agentId, request.threadId);
1545
+ return {
1546
+ status: 200,
1547
+ body: { operation: request.operation, threadId: request.threadId }
1548
+ };
1549
+ }
1550
+ case "setRunBlocked": {
1551
+ const result = await store.setRunBlocked({
1552
+ runId: request.runId,
1553
+ threadId: request.threadId,
1554
+ blockQuestion: request.blockQuestion,
1555
+ blockAnswer: request.blockAnswer,
1556
+ questionReplacement: request.questionReplacement,
1557
+ answerReplacement: request.answerReplacement,
1558
+ blockedBy: historyOwnerId
1559
+ });
1560
+ return {
1561
+ status: 200,
1562
+ body: {
1563
+ operation: request.operation,
1564
+ runId: result.runId,
1565
+ threadId: result.threadId,
1566
+ blockedQuestion: result.blockedQuestion,
1567
+ blockedAnswer: result.blockedAnswer,
1568
+ questionReplacement: result.questionReplacement,
1569
+ answerReplacement: result.answerReplacement,
1570
+ blockedAt: result.blockedAt,
1571
+ blockedBy: result.blockedBy
1572
+ }
1573
+ };
1574
+ }
1575
+ }
1576
+ } catch (error) {
1577
+ logCtx?.error?.("[history] \u8BF7\u6C42\u6267\u884C\u5931\u8D25:", error);
1578
+ if (error instanceof ConversationHistoryNotFoundError) {
1579
+ return historyError(404, "conversation_not_found", error.message);
1580
+ }
1581
+ return historyError(500, "history_query_failed", error instanceof Error ? error.message : "\u4F1A\u8BDD\u5386\u53F2\u67E5\u8BE2\u5931\u8D25");
1582
+ }
1583
+ }
1584
+ function historyError(status, code, message) {
1585
+ return { status, body: { error: { code, message, status } } };
1586
+ }
1587
+
1588
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/homeData.js
1589
+ var HOME_DATA_REQUEST_OPERATION = "homeData.request";
1590
+ async function runHomeData(method, body, userId, deps = {}, env = process.env, envSelector = "PROD", gatewayAuth, logCtx) {
1591
+ if (method.toUpperCase() !== "POST") {
1592
+ return jsonError(405, "method_not_allowed", "\u4EC5\u652F\u6301 POST");
1593
+ }
1594
+ if (!userId) {
1595
+ return jsonError(401, "authentication_required", "\u7F3A\u5C11 Appwrite \u8BA4\u8BC1\u7528\u6237");
1596
+ }
1597
+ const protocol = deps.protocol ?? getBakedProtocol();
1598
+ let provider = deps.mcpServerProvider;
1599
+ if (!provider) {
1600
+ let resolvedAuth;
1601
+ try {
1602
+ resolvedAuth = resolveGatewayAuthContext(gatewayAuth, env, logCtx, "home-data");
1603
+ } catch {
1604
+ return jsonError(500, "auth_unconfigured", "LINKQUE_AGENT_ACCESS_KEY \u672A\u914D\u7F6E");
1605
+ }
1606
+ const { mcpSignEndpoint } = resolveLinkqueEndpoints(env, envSelector);
1607
+ provider = createAppbaseMcpProvider({
1608
+ resolveConfig: createAppbaseMcpConfigResolver({
1609
+ agentId: protocol.agentId,
1610
+ ...mcpSignEndpoint ? { signEndpoint: mcpSignEndpoint } : {}
1611
+ }),
1612
+ getAuthorizationHeader: async () => resolvedAuth.authorizationHeader
1613
+ });
1614
+ }
1615
+ const response = await executeHomeData({
1616
+ request: body,
1617
+ protocol,
1618
+ userId,
1619
+ mcpServerProvider: provider
1620
+ });
1621
+ return { status: 200, body: response };
1622
+ }
1623
+
1624
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/chatConfig.js
1625
+ function resolveChatDeps(env, deps, logCtx, historyOwnerId, appwriteApiKey, envSelector, gatewayAuth) {
1626
+ const log = (...a) => logCtx?.log?.("[chat]", ...a);
1627
+ const logError = (...a) => logCtx?.error?.("[chat]", ...a);
1628
+ const protocol = deps?.protocol ?? getBakedProtocol();
1629
+ log("protocol \u5DF2\u52A0\u8F7D, agentId=", protocol.agentId, "model=", protocol.agent.model.defaultModel);
1630
+ for (const line of describeRuntimeEnv(env, Boolean(appwriteApiKey))) {
1631
+ log("env:", line);
1632
+ }
1633
+ let resolved;
1634
+ try {
1635
+ resolved = resolveGatewayAuthContext(gatewayAuth, env, logCtx, "chat");
1636
+ log("\u8BA4\u8BC1\u89E3\u6790\u6210\u529F, provider=", resolved.auth.provider, "mode=", resolved.mode, "model=", protocol.agent.model.defaultModel ?? "default");
1637
+ } catch (err) {
1638
+ logError("\u8BA4\u8BC1\u914D\u7F6E\u7F3A\u5931:", err);
1639
+ return jsonError(500, "auth_unconfigured", err instanceof Error ? err.message : "\u8BA4\u8BC1\u914D\u7F6E\u7F3A\u5931");
1640
+ }
1641
+ let store = deps?.store;
1642
+ let sessionLease = deps?.sessionLease;
1643
+ let historyStore = deps?.historyStore;
1644
+ let clientCapabilityStore = deps?.clientCapabilityStore;
1645
+ let chunkSize = 30;
1646
+ if (!store) {
1647
+ let storage;
1648
+ try {
1649
+ storage = resolveRunStorage(env, logCtx);
1650
+ } catch (err) {
1651
+ logError("\u843D\u5E93\u914D\u7F6E\u7F3A\u5931:", err);
1652
+ return jsonError(500, "storage_unconfigured", err instanceof Error ? err.message : "\u843D\u5E93\u914D\u7F6E\u7F3A\u5931");
1653
+ }
1654
+ let handle;
1655
+ try {
1656
+ handle = createAppBaseClient(env, logCtx, appwriteApiKey);
1657
+ } catch (err) {
1658
+ logError("AppBase \u51ED\u636E\u7F3A\u5931:", err);
1659
+ return jsonError(500, "appbase_unconfigured", err instanceof Error ? err.message : "AppBase \u51ED\u636E\u7F3A\u5931");
1660
+ }
1661
+ store = createAppBaseStore(handle.databases, storage.databaseId, storage.tableId, historyOwnerId);
1662
+ if (env.CLIENT_CAPABILITY_DATABASE_ID && env.CLIENT_CAPABILITY_TABLE_ID) {
1663
+ clientCapabilityStore ??= createClientCapabilityStore(handle.databases, env.CLIENT_CAPABILITY_DATABASE_ID, env.CLIENT_CAPABILITY_TABLE_ID);
1664
+ }
1665
+ try {
1666
+ historyStore ??= createConversationHistoryStore(handle.databases, resolveConversationHistoryStorage(env), historyOwnerId);
1667
+ } catch (err) {
1668
+ logError("\u4F1A\u8BDD\u5386\u53F2\u5B58\u50A8\u914D\u7F6E\u7F3A\u5931:", err);
1669
+ return jsonError(500, "history_storage_unconfigured", err instanceof Error ? err.message : "\u4F1A\u8BDD\u5386\u53F2\u5B58\u50A8\u914D\u7F6E\u7F3A\u5931");
1670
+ }
1671
+ const sessionStorage = resolveSessionStorage(env);
1672
+ const sessionLeaseStorage = resolveSessionLeaseStorage(env);
1673
+ if (sessionStorage && !sessionLeaseStorage && !sessionLease) {
1674
+ return jsonError(500, "session_lock_failed", "\u5DF2\u542F\u7528\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u4F46\u672A\u914D\u7F6E SESSION_LEASE_TABLE_ID");
1675
+ }
1676
+ if (!sessionLease && sessionLeaseStorage) {
1677
+ sessionLease = createAppBaseSessionLease(handle.databases, sessionLeaseStorage.databaseId, sessionLeaseStorage.tableId, historyOwnerId);
1678
+ }
1679
+ chunkSize = storage.chunkSize;
1680
+ }
1681
+ const mcpAuthorizationHeaderProvider = async () => resolved.authorizationHeader;
1682
+ const { linkqueBaseUrl, knowledgeBaseUrl, mcpSignEndpoint } = resolveLinkqueEndpoints(env, envSelector);
1683
+ const mcpConfigResolver = createAppbaseMcpConfigResolver({
1684
+ agentId: protocol.agentId,
1685
+ ...mcpSignEndpoint ? { signEndpoint: mcpSignEndpoint } : {}
1686
+ });
1687
+ const runtime = deps?.runtime ?? createAppBaseRuntime(protocol, env, {
1688
+ ...appwriteApiKey ? { appBaseApiKey: appwriteApiKey } : {},
1689
+ mcpConfigResolver,
1690
+ mcpAuthorizationHeaderProvider,
1691
+ gatewayAgentId: protocol.agentId,
1692
+ ...linkqueBaseUrl ? { linkqueBaseUrl } : {},
1693
+ ...knowledgeBaseUrl ? { knowledgeBaseUrl } : {}
1694
+ }, logCtx);
1695
+ return {
1696
+ auth: resolved.auth,
1697
+ // 模型取自 bundler 已 bake-in 的 protocol 默认模型(历史来自 env AGENT_MODEL,已移除)。
1698
+ model: protocol.agent.model.defaultModel || void 0,
1699
+ protocol,
1700
+ runtime,
1701
+ store,
1702
+ sessionLease,
1703
+ historyStore,
1704
+ clientCapabilityStore,
1705
+ chunkSize
1706
+ };
1707
+ }
1708
+
1709
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/chatDebug.js
1710
+ var DEBUG_PREVIEW_MAX = 60;
1711
+ function preview(value, max = DEBUG_PREVIEW_MAX) {
1712
+ let str;
1713
+ if (typeof value === "string") {
1714
+ str = value;
1715
+ } else {
1716
+ try {
1717
+ str = JSON.stringify(value) ?? "<unserializable>";
1718
+ } catch {
1719
+ str = "<unserializable>";
1720
+ }
1721
+ }
1722
+ str = str.replace(/\s+/g, " ").trim();
1723
+ return str.length > max ? `${str.slice(0, max)}\u2026` : str;
1724
+ }
1725
+ async function* debugLogAgentStream(stream, debugLog) {
1726
+ const start = Date.now();
1727
+ let firstResponseLogged = false;
1728
+ const counts = { delta: 0, thinking: 0, tool: 0, card: 0 };
1729
+ const summary = () => `\u4E8B\u4EF6\u603B\u6570=${counts.delta + counts.thinking + counts.tool + counts.card}(delta=${counts.delta},thinking=${counts.thinking},tool=${counts.tool},card=${counts.card})`;
1730
+ for await (const ev of stream) {
1731
+ switch (ev.type) {
1732
+ case "delta":
1733
+ counts.delta += 1;
1734
+ if (!firstResponseLogged) {
1735
+ firstResponseLogged = true;
1736
+ debugLog("provider \u9996\u54CD\u5E94", `${Date.now() - start}ms`, "event=delta");
1737
+ }
1738
+ debugLog("agent \u4E8B\u4EF6 delta #", counts.delta, "len=", ev.content.length);
1739
+ break;
1740
+ case "thinking":
1741
+ counts.thinking += 1;
1742
+ if (!firstResponseLogged) {
1743
+ firstResponseLogged = true;
1744
+ debugLog("provider \u9996\u54CD\u5E94", `${Date.now() - start}ms`, "event=thinking");
1745
+ }
1746
+ debugLog("agent \u4E8B\u4EF6 thinking #", counts.thinking, "\u9884\u89C8=", preview(ev.content));
1747
+ break;
1748
+ case "tool_start":
1749
+ counts.tool += 1;
1750
+ debugLog("agent \u4E8B\u4EF6 tool_start tool=", ev.toolName, "args=", preview(ev.args));
1751
+ break;
1752
+ case "tool_end":
1753
+ debugLog("agent \u4E8B\u4EF6 tool_end tool=", ev.toolName, "isError=", ev.isError, "result=", preview(ev.result));
1754
+ break;
1755
+ case "a2ui_card":
1756
+ counts.card += 1;
1757
+ debugLog("agent \u4E8B\u4EF6 a2ui_card cardRef=", ev.metadata.cardRef, "cardInstanceId=", ev.metadata.cardInstanceId);
1758
+ break;
1759
+ case "error":
1760
+ debugLog("agent \u4E8B\u4EF6 error code=", ev.error.code ?? "AGENT_PROVIDER_ERROR", "msg=", preview(ev.error.message), "|", summary());
1761
+ break;
1762
+ case "done":
1763
+ debugLog("agent \u4E8B\u4EF6 done willRetry=", ev.willRetry, "|", summary());
1764
+ break;
1765
+ }
1766
+ yield ev;
1767
+ }
1768
+ }
1769
+
1770
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/clientCapabilityTool.js
1771
+ import { randomUUID as randomUUID2 } from "node:crypto";
1772
+ var WAIT_TIMEOUT_MS = 2e4;
1773
+ var POLL_INTERVAL_MS = 500;
1774
+ var ROW_TTL_MS = 3 * 6e4;
1775
+ var LOCATION_INPUT = Object.freeze({
1776
+ type: 0,
1777
+ cacheTimeout: 30,
1778
+ coordinateSystemType: "GCJ02"
1779
+ });
1780
+ function createClientCapabilityTool(input) {
1781
+ const resource = input.protocol.agent.resources.find((candidate) => candidate.resourceType === EResourceType.CLIENT_CAPABILITY);
1782
+ if (!resource || !input.ownerId || !input.store || !resource.config.allowedCapabilities.includes("location") || !input.request.clientCapabilities?.includes("location"))
1783
+ return void 0;
1784
+ const ownerId = input.ownerId;
1785
+ const store = input.store;
1786
+ let pending = false;
1787
+ return {
1788
+ name: "client_capability",
1789
+ modelName: "client_capability",
1790
+ resourceId: resource.resourceId,
1791
+ label: "\u83B7\u53D6\u7528\u6237\u5F53\u524D\u4F4D\u7F6E",
1792
+ description: "\u5F53\u56DE\u7B54\u786E\u5B9E\u9700\u8981\u7528\u6237\u5F53\u524D\u5730\u7406\u4F4D\u7F6E\u65F6\u8C03\u7528\u3002\u80FD\u529B\u7531\u7528\u6237\u5F53\u524D\u8BBE\u5907\u6267\u884C\uFF0C\u5931\u8D25\u65F6\u8BF7\u7EE7\u7EED\u7ED9\u51FA\u53EF\u884C\u8BF4\u660E\u3002",
1793
+ inputSchema: {
1794
+ type: "object",
1795
+ additionalProperties: false,
1796
+ required: ["capability", "reason"],
1797
+ properties: {
1798
+ capability: { const: "location" },
1799
+ reason: { type: "string", minLength: 1, maxLength: 200 }
1800
+ }
1801
+ },
1802
+ execute: async (args, signal, context) => {
1803
+ if (!context)
1804
+ return failure("CLIENT_CAPABILITY_CONTEXT_MISSING", "\u7AEF\u4FA7\u8C03\u7528\u4E0A\u4E0B\u6587\u4E0D\u53EF\u7528");
1805
+ if (pending)
1806
+ return failure("CLIENT_CAPABILITY_BUSY", "\u5F53\u524D\u5DF2\u6709\u4E00\u4E2A\u7AEF\u4FA7\u80FD\u529B\u8C03\u7528\u6B63\u5728\u7B49\u5F85");
1807
+ const parsed = readArgs(args);
1808
+ if (!parsed)
1809
+ return failure("CLIENT_CAPABILITY_INVALID_ARGS", "\u7AEF\u4FA7\u5B9A\u4F4D\u53C2\u6570\u4E0D\u5408\u6CD5");
1810
+ pending = true;
1811
+ const invocationId = randomUUID2();
1812
+ const now = Date.now();
1813
+ const acceptUntil = new Date(now + WAIT_TIMEOUT_MS).toISOString();
1814
+ const expiresAt = new Date(now + ROW_TTL_MS).toISOString();
1815
+ let created = false;
1816
+ try {
1817
+ await store.create({
1818
+ invocationId,
1819
+ ownerId,
1820
+ agentId: input.protocol.agentId,
1821
+ threadId: input.request.threadId,
1822
+ runId: input.request.runId,
1823
+ toolCallId: context.toolCallId,
1824
+ capability: "location",
1825
+ status: "pending",
1826
+ input: JSON.stringify(LOCATION_INPUT),
1827
+ createdAt: new Date(now).toISOString(),
1828
+ acceptUntil,
1829
+ expiresAt
1830
+ });
1831
+ created = true;
1832
+ context.emit({
1833
+ type: "client_capability_request",
1834
+ value: {
1835
+ version: "1.0",
1836
+ invocationId,
1837
+ runId: input.request.runId,
1838
+ toolCallId: context.toolCallId,
1839
+ capability: "location",
1840
+ reason: parsed.reason,
1841
+ input: LOCATION_INPUT,
1842
+ expiresAt: acceptUntil
1843
+ }
1844
+ });
1845
+ while (Date.now() < Date.parse(acceptUntil)) {
1846
+ if (signal?.aborted)
1847
+ throw abortError();
1848
+ const row = await store.get(invocationId);
1849
+ if (row?.status === "succeeded" || row?.status === "failed") {
1850
+ const result = parseStoredClientCapabilityResult(row.result);
1851
+ if (!result)
1852
+ return failure("CLIENT_CAPABILITY_INVALID_RESULT", "\u7AEF\u4FA7\u8FD4\u56DE\u5185\u5BB9\u65E0\u6CD5\u89E3\u6790");
1853
+ await store.consume(invocationId);
1854
+ return result;
1855
+ }
1856
+ await wait(POLL_INTERVAL_MS, signal);
1857
+ }
1858
+ await store.expire(invocationId);
1859
+ return failure("CLIENT_CAPABILITY_TIMEOUT", "\u7B49\u5F85\u7AEF\u4FA7\u5B9A\u4F4D\u7ED3\u679C\u8D85\u65F6", true);
1860
+ } catch (error) {
1861
+ if (created && signal?.aborted)
1862
+ await store.expire(invocationId);
1863
+ throw error;
1864
+ } finally {
1865
+ pending = false;
1866
+ }
1867
+ }
1868
+ };
1869
+ }
1870
+ function readArgs(value) {
1871
+ if (!value || typeof value !== "object" || Array.isArray(value))
1872
+ return void 0;
1873
+ const args = value;
1874
+ if (args.capability !== "location" || typeof args.reason !== "string")
1875
+ return void 0;
1876
+ const reason = args.reason.trim();
1877
+ return reason && reason.length <= 200 ? { reason } : void 0;
1878
+ }
1879
+ function failure(code, message, retriable = false) {
1880
+ return { ok: false, error: { code, message, retriable } };
1881
+ }
1882
+ function wait(ms, signal) {
1883
+ return new Promise((resolve, reject) => {
1884
+ if (signal?.aborted)
1885
+ return reject(abortError());
1886
+ const onAbort = () => {
1887
+ clearTimeout(timer);
1888
+ reject(abortError());
1889
+ };
1890
+ const timer = setTimeout(() => {
1891
+ signal?.removeEventListener("abort", onAbort);
1892
+ resolve();
1893
+ }, ms);
1894
+ signal?.addEventListener("abort", onAbort, { once: true });
1895
+ });
1896
+ }
1897
+ function abortError() {
1898
+ const error = new Error("client capability call aborted");
1899
+ error.name = "AbortError";
1900
+ return error;
1901
+ }
1902
+
1903
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/chatExecution.js
1904
+ async function runAgentToChunks(deps, request, env, logCtx, authenticatedUserId, signal, traceId) {
1905
+ const log = (...a) => logCtx?.log?.("[chat]", ...a);
1906
+ const logError = (...a) => logCtx?.error?.("[chat]", ...a);
1907
+ const debug = isDebugLogLevel(env);
1908
+ const debugLog = (...args) => {
1909
+ if (debug)
1910
+ logCtx?.log?.("[chat][debug]", ...args);
1911
+ };
1912
+ const sink = new ChunkSink(deps.store, { runId: request.runId, threadId: request.threadId }, { chunkSize: deps.chunkSize });
1913
+ let firstChunkId;
1914
+ try {
1915
+ firstChunkId = await sink.open();
1916
+ log("chunk#0 \u521B\u5EFA\u6210\u529F, id=", firstChunkId);
1917
+ } catch (err) {
1918
+ logError("chunk#0 \u521B\u5EFA\u5931\u8D25:", err);
1919
+ return {
1920
+ response: jsonError(500, "chunk_create_failed", err instanceof Error ? err.message : "chunk#0 \u521B\u5EFA\u5931\u8D25"),
1921
+ runStatus: "error",
1922
+ totalChunks: 0,
1923
+ searchMessages: []
1924
+ };
1925
+ }
1926
+ const runOptions = {
1927
+ runtimeContext: {
1928
+ agentId: deps.protocol.agentId,
1929
+ threadId: request.threadId,
1930
+ ...authenticatedUserId ? { userId: authenticatedUserId } : {}
1931
+ },
1932
+ // hook 公共参数 host 注入:traceId 来自请求头(x-trace-id);runIndex 不注入,
1933
+ // 由 runtime 按已加载会话历史用户轮次 + 1 强准确推导(会话维度历史本就由本包维护)。
1934
+ ...traceId ? { hookContext: { traceId } } : {}
1935
+ };
1936
+ if (request.executionScope !== "presentation") {
1937
+ runOptions.sessionId = request.threadId;
1938
+ }
1939
+ if (deps.model)
1940
+ runOptions.model = deps.model;
1941
+ const clientCapabilityTool = createClientCapabilityTool({
1942
+ protocol: deps.protocol,
1943
+ request,
1944
+ ownerId: authenticatedUserId,
1945
+ store: deps.clientCapabilityStore
1946
+ });
1947
+ if (clientCapabilityTool)
1948
+ runOptions.additionalTools = [clientCapabilityTool];
1949
+ if (signal)
1950
+ runOptions.signal = signal;
1951
+ const agentReq = {
1952
+ threadId: request.threadId,
1953
+ runId: request.runId,
1954
+ messages: toAgentRunMessages(request),
1955
+ clientCapabilities: request.clientCapabilities
1956
+ };
1957
+ log("\u5F00\u59CB\u8FD0\u884C agent conversation...");
1958
+ debugLog("\u8C03\u7528 provider, provider=", deps.auth.provider, "model=", deps.model ?? deps.protocol.agent.model.defaultModel, "sessionId=", request.threadId);
1959
+ let status = "done";
1960
+ let loopErrorSeen = false;
1961
+ const assistantMessages = /* @__PURE__ */ new Map();
1962
+ try {
1963
+ const rawStream = deps.runtime.runAgentLoop(agentReq, deps.auth, runOptions);
1964
+ const agentStream = debug ? debugLogAgentStream(rawStream, debugLog) : rawStream;
1965
+ const sseStream = streamToConversationEvents(agentStream, { threadId: request.threadId, runId: request.runId }, signal ? { signal } : {});
1966
+ for await (const ev of sseStream) {
1967
+ if (ev.event === "transport-error") {
1968
+ loopErrorSeen = true;
1969
+ logError("agent loop \u5931\u8D25\u4E8B\u4EF6, code=", ev.data.code, "message=", ev.data.message);
1970
+ status = ev.data.code === "RUN_CANCELLED" ? "cancelled" : "error";
1971
+ }
1972
+ if (ev.event === "ag-ui" && ev.data.type === EventType.TEXT_MESSAGE_CONTENT) {
1973
+ const textEvent = ev.data;
1974
+ const existing = assistantMessages.get(textEvent.messageId);
1975
+ assistantMessages.set(textEvent.messageId, {
1976
+ content: `${existing?.content ?? ""}${textEvent.delta}`,
1977
+ sequence: existing?.sequence ?? assistantMessages.size + 1
1978
+ });
1979
+ }
1980
+ const chunk = encodeConversationSseEvent(ev);
1981
+ await sink.writeChunk(chunk, { flush: shouldFlushConversationEvent(ev) });
1982
+ }
1983
+ if (loopErrorSeen) {
1984
+ log("agent \u6D41\u7ED3\u675F(\u542B loop \u5931\u8D25\u4E8B\u4EF6), \u5DF2\u5EFA chunk \u6570=", sink.totalChunks);
1985
+ } else {
1986
+ log("agent \u6D41\u6B63\u5E38\u7ED3\u675F, \u5DF2\u5EFA chunk \u6570=", sink.totalChunks);
1987
+ }
1988
+ } catch (err) {
1989
+ const cancelled = signal?.aborted === true;
1990
+ const message = cancelled ? "\u7528\u6237\u5DF2\u505C\u6B62\u672C\u6B21\u56DE\u590D\u3002" : err instanceof Error ? err.message : "unknown gateway error";
1991
+ logError("agent \u6D41\u8FD0\u884C\u9519\u8BEF:", message);
1992
+ status = cancelled ? "cancelled" : "error";
1993
+ await sink.writeChunk(encodeConversationSseEvent({
1994
+ event: "transport-error",
1995
+ data: {
1996
+ code: cancelled ? "RUN_CANCELLED" : "GATEWAY_ERROR",
1997
+ message,
1998
+ retriable: false
1999
+ }
2000
+ }), { flush: true });
2001
+ } finally {
2002
+ try {
2003
+ await sink.finalize(status);
2004
+ log("chunk \u5DF2 finalize, status=", status, "\u603B chunk \u6570=", sink.totalChunks);
2005
+ } catch (err) {
2006
+ logError("finalize \u5931\u8D25:", err);
2007
+ }
2008
+ }
2009
+ return {
2010
+ response: {
2011
+ status: 201,
2012
+ body: { runId: request.runId, threadId: request.threadId }
2013
+ },
2014
+ runStatus: status,
2015
+ totalChunks: sink.totalChunks,
2016
+ searchMessages: [...assistantMessages.entries()].flatMap(([itemId, message]) => message.content.trim() ? [{ itemId, content: message.content, sequence: message.sequence }] : [])
2017
+ };
2018
+ }
2019
+ function shouldFlushConversationEvent(event) {
2020
+ if (event.event === "transport-error")
2021
+ return true;
2022
+ if (event.event !== "ag-ui")
2023
+ return false;
2024
+ return event.data.type === EventType.TEXT_MESSAGE_END || event.data.type === EventType.TOOL_CALL_START || event.data.type === EventType.TOOL_CALL_END || event.data.type === EventType.CUSTOM || event.data.type === EventType.RUN_FINISHED;
2025
+ }
2026
+
2027
+ // ../linkque-agent-appwrite-integrate/dist/esm/runtime/handler/chat.js
2028
+ async function runChat(method, bodyJSON, env, deps, logCtx, historyOwnerId, appwriteApiKey, envSelector, traceId, gatewayAuth) {
2029
+ const log = (...args) => logCtx?.log?.("[chat]", ...args);
2030
+ const logError = (...args) => logCtx?.error?.("[chat]", ...args);
2031
+ const guarded = guardChatRequest(method, bodyJSON, logCtx);
2032
+ if ("status" in guarded)
2033
+ return guarded;
2034
+ const request = guarded;
2035
+ const isPresentationRun = request.executionScope === "presentation";
2036
+ const cfg = resolveChatDeps(env, deps, logCtx, historyOwnerId, appwriteApiKey, envSelector, gatewayAuth);
2037
+ if ("status" in cfg)
2038
+ return cfg;
2039
+ const leaseOwnerId = randomUUID3();
2040
+ let leaseAcquired = false;
2041
+ let leaseReleased = false;
2042
+ if (cfg.sessionLease && !isPresentationRun) {
2043
+ try {
2044
+ leaseAcquired = await cfg.sessionLease.acquire(request.threadId, leaseOwnerId);
2045
+ } catch (err) {
2046
+ logError("session \u79DF\u7EA6\u83B7\u53D6\u5931\u8D25:", err);
2047
+ return jsonError(500, "session_lock_failed", err instanceof Error ? err.message : "\u4F1A\u8BDD\u79DF\u7EA6\u83B7\u53D6\u5931\u8D25");
2048
+ }
2049
+ if (!leaseAcquired) {
2050
+ return jsonError(409, "thread_busy", "\u8BE5\u4F1A\u8BDD\u5DF2\u6709\u6B63\u5728\u8FD0\u884C\u7684\u8BF7\u6C42");
2051
+ }
2052
+ }
2053
+ const releaseLease = async () => {
2054
+ if (!leaseAcquired || !cfg.sessionLease)
2055
+ return true;
2056
+ if (leaseReleased)
2057
+ return true;
2058
+ try {
2059
+ await cfg.sessionLease.release(request.threadId, leaseOwnerId);
2060
+ leaseReleased = true;
2061
+ return true;
2062
+ } catch (err) {
2063
+ logError("session \u79DF\u7EA6\u91CA\u653E\u5931\u8D25:", err);
2064
+ return false;
2065
+ }
2066
+ };
2067
+ try {
2068
+ if (cfg.historyStore && !isPresentationRun) {
2069
+ try {
2070
+ await cfg.historyStore.beginRun(request, cfg.protocol.agentId);
2071
+ } catch (err) {
2072
+ logError("\u4F1A\u8BDD\u76EE\u5F55\u521D\u59CB\u5316\u5931\u8D25:", err);
2073
+ return jsonError(500, "history_begin_failed", err instanceof Error ? err.message : "\u4F1A\u8BDD\u76EE\u5F55\u521D\u59CB\u5316\u5931\u8D25");
2074
+ }
2075
+ const userSearchContent = readSearchableUserContent(request);
2076
+ if (userSearchContent) {
2077
+ try {
2078
+ await cfg.historyStore.indexSearchMessages([
2079
+ {
2080
+ agentId: cfg.protocol.agentId,
2081
+ threadId: request.threadId,
2082
+ runId: request.runId,
2083
+ itemId: `${request.runId}:user`,
2084
+ role: "user",
2085
+ content: userSearchContent,
2086
+ createdAt: Date.now(),
2087
+ sequence: 0
2088
+ }
2089
+ ]);
2090
+ } catch (err) {
2091
+ logError("\u7528\u6237\u6D88\u606F\u641C\u7D22\u7D22\u5F15\u5199\u5165\u5931\u8D25:", err);
2092
+ }
2093
+ }
2094
+ }
2095
+ const runAbortController = new AbortController();
2096
+ const stopCancellationObserver = startRunCancellationObserver(isPresentationRun ? void 0 : cfg.historyStore, cfg.protocol.agentId, request.runId, runAbortController, env, logCtx);
2097
+ let execution;
2098
+ try {
2099
+ execution = await runAgentToChunks(cfg, request, env, logCtx, historyOwnerId, runAbortController.signal, traceId);
2100
+ } catch (err) {
2101
+ logError("Agent \u6267\u884C\u5757\u5F02\u5E38\u9000\u51FA:", err);
2102
+ if (cfg.historyStore && !isPresentationRun) {
2103
+ try {
2104
+ await cfg.historyStore.completeRun({
2105
+ runId: request.runId,
2106
+ threadId: request.threadId,
2107
+ status: "error",
2108
+ totalChunks: 0
2109
+ });
2110
+ } catch (historyError2) {
2111
+ logError("\u5F02\u5E38 run \u7D22\u5F15\u7EC8\u6001\u66F4\u65B0\u5931\u8D25:", historyError2);
2112
+ }
2113
+ }
2114
+ return jsonError(500, "run_execution_failed", err instanceof Error ? err.message : "Agent \u6267\u884C\u5931\u8D25");
2115
+ } finally {
2116
+ await stopCancellationObserver();
2117
+ }
2118
+ if (cfg.historyStore && !isPresentationRun) {
2119
+ try {
2120
+ await cfg.historyStore.completeRun({
2121
+ runId: request.runId,
2122
+ threadId: request.threadId,
2123
+ status: execution.runStatus,
2124
+ totalChunks: execution.totalChunks
2125
+ });
2126
+ } catch (err) {
2127
+ logError("\u4F1A\u8BDD run \u7D22\u5F15\u7EC8\u6001\u66F4\u65B0\u5931\u8D25:", err);
2128
+ }
2129
+ if (execution.searchMessages.length) {
2130
+ const createdAt = Date.now();
2131
+ try {
2132
+ await cfg.historyStore.indexSearchMessages(execution.searchMessages.map((message) => ({
2133
+ agentId: cfg.protocol.agentId,
2134
+ threadId: request.threadId,
2135
+ runId: request.runId,
2136
+ itemId: message.itemId,
2137
+ role: "assistant",
2138
+ content: message.content,
2139
+ createdAt,
2140
+ sequence: message.sequence
2141
+ })));
2142
+ } catch (err) {
2143
+ logError("\u667A\u80FD\u4F53\u6D88\u606F\u641C\u7D22\u7D22\u5F15\u5199\u5165\u5931\u8D25:", err);
2144
+ }
2145
+ }
2146
+ }
2147
+ let preparedFollowUpContext;
2148
+ log("\u8FFD\u95EE\u89E6\u53D1\u68C0\u67E5:", {
2149
+ runStatus: execution.runStatus,
2150
+ assistantMessageCount: execution.searchMessages.length,
2151
+ hasHistoryWriter: Boolean(cfg.historyStore?.saveFollowUpQuestions),
2152
+ hasContextPreparer: Boolean(cfg.runtime.prepareFollowUpQuestionContext),
2153
+ hasGenerator: Boolean(cfg.runtime.generateFollowUpQuestions)
2154
+ });
2155
+ if (!isPresentationRun && execution.runStatus === "done" && execution.searchMessages.length > 0 && cfg.historyStore?.saveFollowUpQuestions && cfg.runtime.prepareFollowUpQuestionContext && cfg.runtime.generateFollowUpQuestions) {
2156
+ let followUpHistory = [];
2157
+ if (cfg.historyStore.getRecentFollowUpQuestionContext) {
2158
+ try {
2159
+ followUpHistory = await cfg.historyStore.getRecentFollowUpQuestionContext(cfg.protocol.agentId, request.threadId, request.runId, FOLLOW_UP_CONTEXT_MAX_HISTORY_RUNS);
2160
+ } catch (err) {
2161
+ logError("\u8FFD\u95EE\u5386\u53F2\u53C2\u8003\u52A0\u8F7D\u5931\u8D25:", err);
2162
+ }
2163
+ }
2164
+ try {
2165
+ preparedFollowUpContext = await cfg.runtime.prepareFollowUpQuestionContext(request.threadId, followUpHistory);
2166
+ log("\u8FFD\u95EE\u4E0A\u4E0B\u6587\u5FEB\u7167\u5B8C\u6210:", {
2167
+ prepared: Boolean(preparedFollowUpContext),
2168
+ messageCount: preparedFollowUpContext?.messages.length ?? 0,
2169
+ historyCount: preparedFollowUpContext?.history?.length ?? 0
2170
+ });
2171
+ } catch (err) {
2172
+ logError("\u8FFD\u95EE\u4E0A\u4E0B\u6587\u5FEB\u7167\u5931\u8D25:", err);
2173
+ }
2174
+ }
2175
+ const released = await releaseLease();
2176
+ if (released && preparedFollowUpContext && cfg.historyStore?.saveFollowUpQuestions && cfg.runtime.generateFollowUpQuestions) {
2177
+ try {
2178
+ const questions = await cfg.runtime.generateFollowUpQuestions(preparedFollowUpContext, cfg.auth, {
2179
+ runId: request.runId,
2180
+ runtimeContext: {
2181
+ agentId: cfg.protocol.agentId,
2182
+ threadId: request.threadId,
2183
+ ...historyOwnerId ? { userId: historyOwnerId } : {}
2184
+ }
2185
+ });
2186
+ log("\u8FFD\u95EE\u751F\u6210\u5B8C\u6210:", { questionCount: questions.length });
2187
+ if (questions.length > 0) {
2188
+ await cfg.historyStore.saveFollowUpQuestions(request.runId, request.threadId, {
2189
+ questions: questions.map((question) => ({
2190
+ label: question,
2191
+ prompt: question
2192
+ }))
2193
+ });
2194
+ }
2195
+ } catch (err) {
2196
+ logError("\u8FFD\u95EE\u751F\u6210\u6216\u5199\u5165\u5931\u8D25:", err);
2197
+ }
2198
+ }
2199
+ return execution.response;
2200
+ } finally {
2201
+ await releaseLease();
2202
+ }
2203
+ }
2204
+ function readSearchableUserContent(request) {
2205
+ const content = request.messages.at(-1)?.content;
2206
+ if (typeof content === "string")
2207
+ return content.trim();
2208
+ return "";
2209
+ }
2210
+ var DEFAULT_CANCEL_POLL_INTERVAL_MS = 500;
2211
+ function startRunCancellationObserver(store, agentId, runId, runAbortController, env, logCtx) {
2212
+ if (!store?.isRunCancellationRequested)
2213
+ return async () => void 0;
2214
+ const observerAbortController = new AbortController();
2215
+ const intervalMs = readCancelPollInterval(env);
2216
+ let queryErrorLogged = false;
2217
+ const task = (async () => {
2218
+ while (!observerAbortController.signal.aborted && !runAbortController.signal.aborted) {
2219
+ try {
2220
+ const requested = await store.isRunCancellationRequested?.(agentId, runId);
2221
+ queryErrorLogged = false;
2222
+ if (requested) {
2223
+ logCtx?.log?.("[chat] \u6536\u5230 run \u53D6\u6D88\u8BF7\u6C42, runId=", runId);
2224
+ runAbortController.abort();
2225
+ return;
2226
+ }
2227
+ } catch (error) {
2228
+ if (!queryErrorLogged) {
2229
+ queryErrorLogged = true;
2230
+ logCtx?.error?.("[chat] \u67E5\u8BE2 run \u53D6\u6D88\u72B6\u6001\u5931\u8D25\uFF0C\u5C06\u7EE7\u7EED\u91CD\u8BD5:", error);
2231
+ }
2232
+ }
2233
+ await waitForCancellationPoll(intervalMs, observerAbortController.signal);
2234
+ }
2235
+ })();
2236
+ return async () => {
2237
+ observerAbortController.abort();
2238
+ await task.catch(() => void 0);
2239
+ };
2240
+ }
2241
+ function readCancelPollInterval(env) {
2242
+ const configured = Number(env.RUN_CANCEL_POLL_INTERVAL_MS);
2243
+ return Number.isSafeInteger(configured) && configured >= 100 ? Math.min(configured, 5e3) : DEFAULT_CANCEL_POLL_INTERVAL_MS;
2244
+ }
2245
+ function waitForCancellationPoll(delayMs, signal) {
2246
+ if (signal.aborted)
2247
+ return Promise.resolve();
2248
+ return new Promise((resolve) => {
2249
+ const timer = setTimeout(finish, delayMs);
2250
+ signal.addEventListener("abort", finish, { once: true });
2251
+ function finish() {
2252
+ clearTimeout(timer);
2253
+ signal.removeEventListener("abort", finish);
2254
+ resolve();
2255
+ }
2256
+ });
2257
+ }
2258
+ async function runGateway(method, bodyJSON, env, deps, logCtx, historyOwnerId, appwriteApiKey, envSelector, traceId, gatewayAuth) {
2259
+ const operation = readOperation(bodyJSON);
2260
+ if (operation === "cancelConversationRun") {
2261
+ return runCancelConversation(method, bodyJSON, env, deps ? { protocol: deps.protocol, store: deps.historyStore } : void 0, logCtx, historyOwnerId, appwriteApiKey);
2262
+ }
2263
+ if (operation === ASR_CREATE_CREDENTIAL_OPERATION) {
2264
+ const agentId = deps?.protocol?.agentId ?? getBakedProtocol().agentId;
2265
+ return runAsrCredential(method, env, historyOwnerId, agentId, void 0, envSelector, gatewayAuth, logCtx);
2266
+ }
2267
+ if (operation === ATTACHMENT_CREATE_UPLOAD_TOKEN_OPERATION) {
2268
+ const agentId = deps?.protocol?.agentId ?? getBakedProtocol().agentId;
2269
+ return runAttachmentUploadToken(method, env, historyOwnerId, agentId, void 0, envSelector, gatewayAuth, logCtx);
2270
+ }
2271
+ if (operation === HOME_DATA_REQUEST_OPERATION) {
2272
+ const request = readOperationPayload(bodyJSON);
2273
+ return runHomeData(method, request, historyOwnerId, {
2274
+ ...deps?.protocol ? { protocol: deps.protocol } : {},
2275
+ ...deps?.mcpServerProvider ? { mcpServerProvider: deps.mcpServerProvider } : {}
2276
+ }, env, envSelector === "PRE" ? "PRE" : "PROD");
2277
+ }
2278
+ if (operation && HISTORY_OPERATIONS.has(operation)) {
2279
+ return runHistory(method, bodyJSON, env, deps ? { protocol: deps.protocol, store: deps.historyStore } : void 0, logCtx, historyOwnerId, appwriteApiKey);
2280
+ }
2281
+ if (operation?.startsWith("attachment.")) {
2282
+ return jsonError(501, "operation_not_implemented", "\u9644\u4EF6 operation \u5DF2\u9884\u7559\uFF0C\u5F53\u524D\u7248\u672C\u672A\u5B9E\u73B0");
2283
+ }
2284
+ if (operation) {
2285
+ return jsonError(400, "operation_unsupported", `\u4E0D\u652F\u6301\u7684 operation: ${operation}`);
2286
+ }
2287
+ return runChat(method, bodyJSON, env, deps, logCtx, historyOwnerId, appwriteApiKey, envSelector, traceId, gatewayAuth);
2288
+ }
2289
+ async function chat(ctx) {
2290
+ let result;
2291
+ try {
2292
+ const bodyJSON = resolveBodyJSON(ctx.req);
2293
+ const historyOwnerId = readAuthenticatedUserId(ctx.req.headers);
2294
+ const appwriteApiKey = readAppwriteApiKey(ctx.req.headers);
2295
+ const envSelector = readEnvSelector(ctx.req.headers);
2296
+ const traceId = readTraceId(ctx.req.headers);
2297
+ const gatewayAuth = historyOwnerId ? resolveLinkqueGatewayAuth(ctx.req.headers, process.env, {
2298
+ log: ctx.log,
2299
+ error: ctx.error
2300
+ }) : void 0;
2301
+ result = historyOwnerId ? await runGateway(ctx.req.method, bodyJSON, process.env, void 0, { log: ctx.log, error: ctx.error }, historyOwnerId, appwriteApiKey, envSelector, traceId, gatewayAuth) : jsonError(401, "authentication_required", "\u7F3A\u5C11 Appwrite \u8BA4\u8BC1\u7528\u6237");
2302
+ } catch (err) {
2303
+ ctx.error?.(err);
2304
+ result = err instanceof LinkqueGatewayAuthError || err instanceof LinkqueGatewayAuthUnconfiguredError ? jsonError(err.status, err.code, err.message) : {
2305
+ status: 500,
2306
+ body: {
2307
+ error: {
2308
+ code: "INTERNAL_ERROR",
2309
+ message: err instanceof Error ? err.message : "unknown error"
2310
+ }
2311
+ }
2312
+ };
2313
+ }
2314
+ if (result.body === void 0) {
2315
+ return ctx.res.empty?.();
2316
+ }
2317
+ return ctx.res.json?.(result.body, result.status);
2318
+ }
2319
+ var HISTORY_OPERATIONS = /* @__PURE__ */ new Set([
2320
+ "activateConversation",
2321
+ "bootstrap",
2322
+ "createConversation",
2323
+ "createSession",
2324
+ "getConversationRuns",
2325
+ "getConversationRunWindow",
2326
+ "listConversations",
2327
+ "searchConversationMessages",
2328
+ "setRunBlocked"
2329
+ ]);
2330
+ function readOperation(value) {
2331
+ if (!value || typeof value !== "object" || Array.isArray(value))
2332
+ return void 0;
2333
+ const operation = value.operation;
2334
+ return typeof operation === "string" && operation ? operation : void 0;
2335
+ }
2336
+ function readOperationPayload(value) {
2337
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2338
+ return void 0;
2339
+ }
2340
+ const payload = { ...value };
2341
+ delete payload.operation;
2342
+ return payload;
2343
+ }
2344
+ export {
2345
+ chat as default,
2346
+ runChat,
2347
+ runGateway
2348
+ };