@undefineds.co/xpod 0.3.48 → 0.3.50

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 (128) hide show
  1. package/bin/xpod.js +0 -0
  2. package/dist/api/auth/MultiAuthenticator.js +2 -2
  3. package/dist/api/auth/MultiAuthenticator.js.map +1 -1
  4. package/dist/api/chatkit/pod-store.d.ts +16 -17
  5. package/dist/api/chatkit/pod-store.js +299 -231
  6. package/dist/api/chatkit/pod-store.js.map +1 -1
  7. package/dist/api/chatkit/schema.d.ts +1 -1
  8. package/dist/api/chatkit/service.js +13 -11
  9. package/dist/api/chatkit/service.js.map +1 -1
  10. package/dist/api/chatkit/store.d.ts +1 -0
  11. package/dist/api/chatkit/store.js +23 -11
  12. package/dist/api/chatkit/store.js.map +1 -1
  13. package/dist/api/chatkit/types.d.ts +17 -10
  14. package/dist/api/chatkit/types.js +97 -14
  15. package/dist/api/chatkit/types.js.map +1 -1
  16. package/dist/api/container/common.js +16 -2
  17. package/dist/api/container/common.js.map +1 -1
  18. package/dist/api/container/routes.js +3 -0
  19. package/dist/api/container/routes.js.map +1 -1
  20. package/dist/api/container/types.d.ts +3 -0
  21. package/dist/api/container/types.js.map +1 -1
  22. package/dist/api/handlers/ChatKitV1Handler.js +1 -2
  23. package/dist/api/handlers/ChatKitV1Handler.js.map +1 -1
  24. package/dist/api/handlers/CoordinationHandler.d.ts +6 -0
  25. package/dist/api/handlers/CoordinationHandler.js +115 -0
  26. package/dist/api/handlers/CoordinationHandler.js.map +1 -0
  27. package/dist/api/handlers/MatrixHandler.d.ts +11 -0
  28. package/dist/api/handlers/MatrixHandler.js +144 -2
  29. package/dist/api/handlers/MatrixHandler.js.map +1 -1
  30. package/dist/api/handlers/RunHandler.js +33 -15
  31. package/dist/api/handlers/RunHandler.js.map +1 -1
  32. package/dist/api/handlers/index.d.ts +1 -0
  33. package/dist/api/handlers/index.js +1 -0
  34. package/dist/api/handlers/index.js.map +1 -1
  35. package/dist/api/index.d.ts +1 -0
  36. package/dist/api/index.js +1 -0
  37. package/dist/api/index.js.map +1 -1
  38. package/dist/api/matrix/PodMatrixStore.d.ts +25 -1
  39. package/dist/api/matrix/PodMatrixStore.js +253 -41
  40. package/dist/api/matrix/PodMatrixStore.js.map +1 -1
  41. package/dist/api/matrix/index.d.ts +1 -1
  42. package/dist/api/matrix/index.js.map +1 -1
  43. package/dist/api/matrix/types.d.ts +25 -2
  44. package/dist/api/matrix/types.js.map +1 -1
  45. package/dist/api/middleware/AuthMiddleware.d.ts +1 -0
  46. package/dist/api/middleware/AuthMiddleware.js +13 -1
  47. package/dist/api/middleware/AuthMiddleware.js.map +1 -1
  48. package/dist/api/protocol-metadata.d.ts +4 -0
  49. package/dist/api/protocol-metadata.js +64 -0
  50. package/dist/api/protocol-metadata.js.map +1 -0
  51. package/dist/api/reconciler/ClientReconcilerCoordinator.d.ts +42 -0
  52. package/dist/api/reconciler/ClientReconcilerCoordinator.js +250 -0
  53. package/dist/api/reconciler/ClientReconcilerCoordinator.js.map +1 -0
  54. package/dist/api/reconciler/ClientReconcilerCoordinator.jsonld +186 -0
  55. package/dist/api/reconciler/ServerGroupReconcilerService.d.ts +39 -0
  56. package/dist/api/reconciler/ServerGroupReconcilerService.js +91 -0
  57. package/dist/api/reconciler/ServerGroupReconcilerService.js.map +1 -0
  58. package/dist/api/reconciler/ServerGroupReconcilerService.jsonld +146 -0
  59. package/dist/api/reconciler/WakeAgentQueue.d.ts +23 -0
  60. package/dist/api/reconciler/WakeAgentQueue.js +123 -0
  61. package/dist/api/reconciler/WakeAgentQueue.js.map +1 -0
  62. package/dist/api/reconciler/WakeAgentQueue.jsonld +91 -0
  63. package/dist/api/reconciler/coordination.d.ts +61 -0
  64. package/dist/api/reconciler/coordination.js +109 -0
  65. package/dist/api/reconciler/coordination.js.map +1 -0
  66. package/dist/api/reconciler/coordination.jsonld +186 -0
  67. package/dist/api/reconciler/index.d.ts +4 -0
  68. package/dist/api/reconciler/index.js +21 -0
  69. package/dist/api/reconciler/index.js.map +1 -0
  70. package/dist/api/runs/ManagedRunWorker.js +0 -5
  71. package/dist/api/runs/ManagedRunWorker.js.map +1 -1
  72. package/dist/api/runs/RunStateCenter.d.ts +1 -1
  73. package/dist/api/runs/RunStateCenter.js +12 -28
  74. package/dist/api/runs/RunStateCenter.js.map +1 -1
  75. package/dist/api/runs/store.d.ts +12 -15
  76. package/dist/api/runs/store.js +24 -15
  77. package/dist/api/runs/store.js.map +1 -1
  78. package/dist/api/tasks/TaskMaterializer.d.ts +1 -0
  79. package/dist/api/tasks/TaskMaterializer.js +10 -13
  80. package/dist/api/tasks/TaskMaterializer.js.map +1 -1
  81. package/dist/api/tasks/TaskService.d.ts +0 -2
  82. package/dist/api/tasks/TaskService.js +6 -16
  83. package/dist/api/tasks/TaskService.js.map +1 -1
  84. package/dist/api/tasks/store.d.ts +0 -2
  85. package/dist/api/tasks/store.js.map +1 -1
  86. package/dist/cli/commands/auth.d.ts +1 -1
  87. package/dist/cli/commands/auth.js +4 -5
  88. package/dist/cli/commands/auth.js.map +1 -1
  89. package/dist/cli/commands/backup.js +1 -1
  90. package/dist/cli/commands/backup.js.map +1 -1
  91. package/dist/cli/commands/login.js +1 -1
  92. package/dist/cli/commands/login.js.map +1 -1
  93. package/dist/cli/commands/pod.js +1 -1
  94. package/dist/cli/commands/pod.js.map +1 -1
  95. package/dist/cli/lib/auth-context.js +10 -7
  96. package/dist/cli/lib/auth-context.js.map +1 -1
  97. package/dist/cli/lib/auth-helper.d.ts +10 -6
  98. package/dist/cli/lib/auth-helper.js +10 -6
  99. package/dist/cli/lib/auth-helper.js.map +1 -1
  100. package/dist/cli/lib/credentials-store.d.ts +22 -4
  101. package/dist/cli/lib/credentials-store.js +68 -51
  102. package/dist/cli/lib/credentials-store.js.map +1 -1
  103. package/dist/cli/lib/oidc-auth.d.ts +4 -0
  104. package/dist/cli/lib/oidc-auth.js +90 -0
  105. package/dist/cli/lib/oidc-auth.js.map +1 -0
  106. package/dist/cli/lib/oidc-session-storage.d.ts +3 -0
  107. package/dist/cli/lib/oidc-session-storage.js +41 -0
  108. package/dist/cli/lib/oidc-session-storage.js.map +1 -0
  109. package/dist/components/components.jsonld +5 -1
  110. package/dist/components/context.jsonld +103 -0
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.js +15 -0
  113. package/dist/index.js.map +1 -1
  114. package/dist/provision/LocalPodProvisioningService.d.ts +1 -0
  115. package/dist/provision/LocalPodProvisioningService.js +9 -0
  116. package/dist/provision/LocalPodProvisioningService.js.map +1 -1
  117. package/dist/provision/LocalPodProvisioningService.jsonld +4 -0
  118. package/dist/runtime/Proxy.d.ts +1 -0
  119. package/dist/runtime/Proxy.js +8 -1
  120. package/dist/runtime/Proxy.js.map +1 -1
  121. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.d.ts.map +1 -1
  122. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +19 -9
  123. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js.map +1 -1
  124. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.d.ts.map +1 -1
  125. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +19 -9
  126. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js.map +1 -1
  127. package/node_modules/@undefineds.co/drizzle-solid/package.json +1 -1
  128. package/package.json +5 -4
@@ -7,10 +7,10 @@ exports.PodChatKitStore = void 0;
7
7
  * 将 ChatKit 数据存储到 Solid Pod。
8
8
  *
9
9
  * 存储结构:
10
- * /.data/{chat|task}/{surfaceId}/
10
+ * /.data/{chat|task}/{parent-derived-id}/
11
11
  * index.ttl
12
- * #this # Chat (meeting:LongChat)
13
- * #{threadId} # Thread (sioc:Thread)
12
+ * #this # Chat parent (meeting:LongChat)
13
+ * #{threadId} # Thread (sioc:Thread, sioc:has_parent)
14
14
  * {yyyy}/{MM}/{dd}/messages.ttl # Messages (meeting:Message)
15
15
  */
16
16
  const drizzle_solid_1 = require("@undefineds.co/drizzle-solid");
@@ -28,6 +28,8 @@ const model_1 = require("../../ai/schema/model");
28
28
  const tables_1 = require("../../credential/schema/tables");
29
29
  const types_2 = require("../../credential/schema/types");
30
30
  const models_1 = require("@undefineds.co/models");
31
+ const reconciler_1 = require("../reconciler");
32
+ const protocol_metadata_1 = require("../protocol-metadata");
31
33
  const schema = {
32
34
  chat: schema_1.Chat,
33
35
  thread: schema_1.Thread,
@@ -45,9 +47,8 @@ const schema = {
45
47
  * 数据模型映射:
46
48
  * - ChatKit thread = Thread (sioc:Thread)
47
49
  * - ChatKit thread item = Message (meeting:Message)
48
- * - ChatKit 协议里的 chat_id 在内部映射为 surfaceId
49
- *
50
- * 每个 Thread 属于一个 Chat 容器。默认使用 'default' Chat。
50
+ * - Thread 通过 sioc:has_parent 归属到 Chat 或 Task command surface。
51
+ * - ChatKit/API 的 opaque 字段不进入 Pod 持久 metadata;内部存储路径从 parent/id 派生。
51
52
  */
52
53
  class PodChatKitStore {
53
54
  constructor(options) {
@@ -55,6 +56,7 @@ class PodChatKitStore {
55
56
  // Track recently created message IDs to avoid SELECT cache timing issues
56
57
  this.recentlyCreatedIds = new Set();
57
58
  this.tokenEndpoint = options.tokenEndpoint;
59
+ this.serverGroupReconcilerService = options.serverGroupReconcilerService;
58
60
  }
59
61
  // =========================================================================
60
62
  // Private Helpers
@@ -337,17 +339,9 @@ class PodChatKitStore {
337
339
  return { type: 'active' };
338
340
  }
339
341
  }
340
- getCommandKindFromMetadata(metadata) {
341
- return metadata?.commandKind === 'task' ? 'task' : 'chat';
342
- }
343
- getSurfaceIdFromMetadata(metadata) {
344
- if (metadata && typeof metadata.surface_id === 'string') {
345
- return metadata.surface_id;
346
- }
347
- if (metadata && typeof metadata.chat_id === 'string') {
348
- return metadata.chat_id;
349
- }
350
- return PodChatKitStore.DEFAULT_CHAT_ID;
342
+ readMetadataString(metadata, key) {
343
+ const value = metadata?.[key];
344
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
351
345
  }
352
346
  isBaseRelativeChatResourceId(value) {
353
347
  return typeof value === 'string'
@@ -361,6 +355,12 @@ class PodChatKitStore {
361
355
  }
362
356
  return `${chatId.replace(/^#/, '')}/index.ttl#this`;
363
357
  }
358
+ buildMessageParentResourceId(surface) {
359
+ if (surface.commandKind === 'task') {
360
+ return `task/index.ttl#${surface.surfaceId}`;
361
+ }
362
+ return `chat/${surface.surfaceId}/index.ttl#this`;
363
+ }
364
364
  chatSurfaceIdFromResourceId(chatId) {
365
365
  if (!chatId) {
366
366
  return undefined;
@@ -384,6 +384,136 @@ class PodChatKitStore {
384
384
  }
385
385
  return this.chatSurfaceIdFromResourceId(chat);
386
386
  }
387
+ commandSurfaceFromResourceRef(resource) {
388
+ if (!resource) {
389
+ return undefined;
390
+ }
391
+ const parseRef = (ref, hash) => {
392
+ const normalizedHash = hash?.startsWith('#') ? hash.slice(1) : hash;
393
+ const trimmed = ref.replace(/^\/+/, '');
394
+ const dataIndex = trimmed.indexOf('.data/');
395
+ const dataRef = dataIndex >= 0 ? trimmed.slice(dataIndex + '.data/'.length) : trimmed;
396
+ const [pathPart, fragmentPart] = dataRef.split('#', 2);
397
+ const fragment = decodeURIComponent(normalizedHash || fragmentPart || '');
398
+ let match = pathPart.match(/^chat\/([^/]+)\/index\.ttl$/);
399
+ if (match && (!fragment || fragment === 'this')) {
400
+ return {
401
+ commandKind: 'chat',
402
+ surfaceId: decodeURIComponent(match[1]),
403
+ };
404
+ }
405
+ match = pathPart.match(/^task\/index\.ttl$/);
406
+ if (match && fragment) {
407
+ return {
408
+ commandKind: 'task',
409
+ surfaceId: fragment,
410
+ };
411
+ }
412
+ match = pathPart.match(/^(chat|task)\/([^/]+)(?:\/|$)/);
413
+ if (match) {
414
+ return {
415
+ commandKind: match[1] === 'task' ? 'task' : 'chat',
416
+ surfaceId: decodeURIComponent(match[2]),
417
+ };
418
+ }
419
+ return undefined;
420
+ };
421
+ try {
422
+ const url = new URL(resource);
423
+ return parseRef(url.pathname, url.hash);
424
+ }
425
+ catch {
426
+ return parseRef(resource);
427
+ }
428
+ }
429
+ resolveTaskParentResource(taskRef, context) {
430
+ if (/^https?:\/\//.test(taskRef)) {
431
+ return taskRef;
432
+ }
433
+ if (taskRef.startsWith('task/')) {
434
+ return this.resolveDataResource(taskRef, context);
435
+ }
436
+ const podBaseUrl = this.ensurePodBaseUrlCache(context);
437
+ if (!podBaseUrl) {
438
+ throw new Error(`Cannot resolve Pod base URL for task parent: ${taskRef}`);
439
+ }
440
+ return (0, store_2.resolveTaskResource)(podBaseUrl, (0, store_2.buildTaskResourceId)(taskRef));
441
+ }
442
+ resolveExplicitThreadParentResource(parentRef, commandKind, context) {
443
+ if (/^https?:\/\//.test(parentRef)) {
444
+ return parentRef;
445
+ }
446
+ if (parentRef.startsWith('chat/') || parentRef.startsWith('task/')) {
447
+ return this.resolveDataResource(parentRef, context);
448
+ }
449
+ if (commandKind === 'task') {
450
+ return this.resolveTaskParentResource(parentRef, context);
451
+ }
452
+ return this.resolveDataResource(`chat/${parentRef}/index.ttl#this`, context);
453
+ }
454
+ resolveThreadParent(thread, context) {
455
+ const metadata = thread.metadata;
456
+ const explicitParent = thread.parent ?? this.readMetadataString(metadata, 'parent');
457
+ const taskRef = this.readMetadataString(metadata, 'task') ?? this.readMetadataString(metadata, 'taskId');
458
+ if (explicitParent) {
459
+ const derived = (0, types_1.getThreadParent)({ id: thread.id, parent: explicitParent });
460
+ const requestedCommandKind = derived?.kind ?? (taskRef ? 'task' : 'chat');
461
+ const parent = this.resolveExplicitThreadParentResource(explicitParent, requestedCommandKind, context);
462
+ const surface = this.commandSurfaceFromResourceRef(parent) ?? (derived
463
+ ? { commandKind: derived.kind, surfaceId: derived.key }
464
+ : undefined);
465
+ if (surface) {
466
+ return { ...surface, parent };
467
+ }
468
+ return {
469
+ commandKind: requestedCommandKind,
470
+ surfaceId: PodChatKitStore.DEFAULT_CHAT_ID,
471
+ parent,
472
+ };
473
+ }
474
+ if (taskRef) {
475
+ const parent = this.resolveTaskParentResource(taskRef, context);
476
+ const surface = this.commandSurfaceFromResourceRef(parent);
477
+ return {
478
+ commandKind: 'task',
479
+ surfaceId: surface?.surfaceId ?? (0, store_1.extractResourceLocalId)(taskRef),
480
+ parent,
481
+ };
482
+ }
483
+ const derived = (0, types_1.getThreadParent)(thread);
484
+ if (derived) {
485
+ const parent = this.resolveExplicitThreadParentResource(derived.parent, derived.kind, context);
486
+ const surface = this.commandSurfaceFromResourceRef(parent);
487
+ return {
488
+ commandKind: surface?.commandKind ?? derived.kind,
489
+ surfaceId: surface?.surfaceId ?? derived.key,
490
+ parent,
491
+ };
492
+ }
493
+ const parent = this.resolveDataResource(`chat/${PodChatKitStore.DEFAULT_CHAT_ID}/index.ttl#this`, context);
494
+ return {
495
+ commandKind: 'chat',
496
+ surfaceId: PodChatKitStore.DEFAULT_CHAT_ID,
497
+ parent,
498
+ };
499
+ }
500
+ threadStoredMetadata(metadata) {
501
+ if (!metadata) {
502
+ return undefined;
503
+ }
504
+ const stored = { ...metadata };
505
+ delete stored.parent;
506
+ delete stored.chat_id;
507
+ delete stored.commandKind;
508
+ delete stored.surface_id;
509
+ delete stored.conversationKind;
510
+ return Object.keys(stored).length > 0 ? stored : undefined;
511
+ }
512
+ mentionsFromUserMessageContent(content) {
513
+ return (0, reconciler_1.normalizeAgentUris)(content
514
+ .filter((entry) => entry.type === 'input_tag')
515
+ .map((entry) => entry.tag));
516
+ }
387
517
  /**
388
518
  * 确保 Chat 容器存在,如果不存在则创建
389
519
  */
@@ -405,6 +535,7 @@ class PodChatKitStore {
405
535
  title: surfaceId === PodChatKitStore.DEFAULT_CHAT_ID ? 'Default Chat' : surfaceId,
406
536
  author: webId || null,
407
537
  status: 'active',
538
+ metadata: (0, reconciler_1.reconcilerCoordinationMetadata)('client'),
408
539
  createdAt: now,
409
540
  updatedAt: now,
410
541
  });
@@ -412,29 +543,28 @@ class PodChatKitStore {
412
543
  }
413
544
  }
414
545
  /**
415
- * 将 ThreadRecord 转为 ThreadMetadata
416
- * ChatKit 边界继续暴露 metadata.chat_id;内部同一值叫 surface_id。
546
+ * 将 ThreadRecord 转为 ThreadMetadata
547
+ * Pod 中以 thread.parent 作为权威归属关系,metadata 只返回业务扩展字段。
417
548
  */
418
- threadRecordToMetadata(record, chatResourceMap) {
419
- const extra = this.parseJsonObject(record.metadata);
420
- const commandKind = record.commandKind === 'task' || extra?.commandKind === 'task' ? 'task' : 'chat';
421
- const surfaceId = record.surfaceId
422
- || (typeof extra?.surface_id === 'string' ? extra.surface_id : undefined)
423
- || (typeof extra?.chat_id === 'string' ? extra.chat_id : undefined)
424
- || this.resolveChatSurfaceFromResource(record.chat, chatResourceMap, PodChatKitStore.DEFAULT_CHAT_ID);
549
+ threadRecordToMetadata(record) {
550
+ const extra = this.threadStoredMetadata(this.parseJsonObject(record.metadata));
551
+ const parent = record.parent
552
+ ?? (0, types_1.getThreadParent)({ id: record.id })?.parent
553
+ ?? (this.chatSurfaceIdFromResource(record.chat)
554
+ ? `chat/${this.chatSurfaceIdFromResource(record.chat)}/index.ttl#this`
555
+ : undefined);
556
+ const reconcilerOwner = (0, reconciler_1.normalizeReconcilerOwner)(extra?.reconcilerOwner, 'client');
557
+ const coordination = (0, reconciler_1.reconcilerCoordinationMetadata)(reconcilerOwner);
425
558
  return {
426
559
  id: record.id,
560
+ parent,
427
561
  title: record.title || undefined,
428
562
  status: this.stringToStatus(record.status),
429
563
  workspace: record.workspace || undefined,
564
+ ...coordination,
430
565
  created_at: record.createdAt ? Math.floor(new Date(record.createdAt).getTime() / 1000) : (0, types_1.nowTimestamp)(),
431
566
  updated_at: record.updatedAt ? Math.floor(new Date(record.updatedAt).getTime() / 1000) : (0, types_1.nowTimestamp)(),
432
- metadata: {
433
- ...(extra ?? {}),
434
- chat_id: surfaceId,
435
- commandKind,
436
- surface_id: surfaceId,
437
- },
567
+ metadata: extra,
438
568
  };
439
569
  }
440
570
  timestampToIso(timestamp) {
@@ -628,14 +758,11 @@ class PodChatKitStore {
628
758
  }
629
759
  runRecordToData(record) {
630
760
  const metadata = this.parseJsonObject(record.metadata);
631
- const xpod = this.getXpodMetadata(metadata);
632
761
  return {
633
762
  id: record.id || '',
634
- surfaceId: record.surfaceId || (typeof xpod?.surfaceId === 'string' ? xpod.surfaceId : 'default'),
635
763
  task: record.task || undefined,
636
764
  thread: record.thread || '',
637
765
  workspace: record.workspace || '',
638
- commandKind: record.commandKind === 'task' || xpod?.commandKind === 'task' ? 'task' : 'chat',
639
766
  status: (record.status || 'queued'),
640
767
  runner: record.runner || '',
641
768
  prompt: record.prompt || undefined,
@@ -652,14 +779,13 @@ class PodChatKitStore {
652
779
  updatedAt: this.isoToTimestamp(record.updatedAt) ?? (0, types_1.nowTimestamp)(),
653
780
  };
654
781
  }
655
- runStepRecordToData(record) {
782
+ runStepRecordToData(record, context) {
656
783
  const payload = this.parseJsonObject(record.payload) ?? this.parseJsonObject(record.data);
657
- const xpod = this.getXpodMetadata(payload);
784
+ const runId = record.runId
785
+ || (record.run ? this.baseRelativeIdFromResource(record.run, context) : '');
658
786
  return {
659
787
  id: record.id || '',
660
- commandKind: record.commandKind === 'task' || xpod?.commandKind === 'task' ? 'task' : 'chat',
661
- surfaceId: record.surfaceId || (typeof xpod?.surfaceId === 'string' ? xpod.surfaceId : 'default'),
662
- runId: record.runId || (typeof xpod?.runId === 'string' ? xpod.runId : ''),
788
+ runId,
663
789
  run: record.run || '',
664
790
  type: record.type || record.stepType || 'runtime.event',
665
791
  message: record.message || undefined,
@@ -672,7 +798,6 @@ class PodChatKitStore {
672
798
  const xpod = this.getXpodMetadata(metadata);
673
799
  return {
674
800
  id: record.id || '',
675
- surfaceId: record.surfaceId || (typeof xpod?.surfaceId === 'string' ? xpod.surfaceId : 'default'),
676
801
  title: record.title || undefined,
677
802
  prompt: record.prompt || '',
678
803
  thread: record.thread || '',
@@ -734,48 +859,6 @@ class PodChatKitStore {
734
859
  };
735
860
  }
736
861
  }
737
- /**
738
- * 获取或构建 Chat resource -> surface id 的映射缓存。
739
- * drizzle-solid 的 link(Chat) 字段返回完整资源引用,通过 Chat 的 @id 比对还原路径槽位。
740
- */
741
- async getChatResourceMap(context) {
742
- if (context._chatResourceMap) {
743
- return context._chatResourceMap;
744
- }
745
- const db = await this.getDb(context);
746
- const map = new Map();
747
- if (db) {
748
- try {
749
- const chats = await db.select().from(schema_1.Chat);
750
- for (const c of chats) {
751
- const uri = c['@id'];
752
- const surfaceId = this.chatSurfaceIdFromResourceId(c.id) ?? c.id;
753
- if (uri)
754
- map.set(uri, surfaceId);
755
- }
756
- }
757
- catch {
758
- // ignore
759
- }
760
- }
761
- context._chatResourceMap = map;
762
- return map;
763
- }
764
- /**
765
- * 从 Chat resource 还原 surface id。
766
- * 优先通过 @id 映射;若调用方本来给的是裸 ID,则原样使用。
767
- */
768
- resolveChatSurfaceFromResource(chat, chatResourceMap, defaultSurfaceId) {
769
- if (!chat)
770
- return defaultSurfaceId;
771
- const bare = chatResourceMap.get(chat);
772
- if (bare)
773
- return bare;
774
- const parsed = this.chatSurfaceIdFromResource(chat);
775
- if (parsed)
776
- return parsed;
777
- return chat.includes('/') ? defaultSurfaceId : chat;
778
- }
779
862
  isAbsoluteHttpResource(value) {
780
863
  try {
781
864
  const url = new URL(value);
@@ -841,24 +924,7 @@ class PodChatKitStore {
841
924
  thread: threadResource,
842
925
  };
843
926
  }
844
- if (!('chat_id' in thread) || !thread.chat_id) {
845
- throw new Error(`chat_id is required when thread_id "${threadRef}" is not a full thread resource id`);
846
- }
847
- const surfaceId = thread.chat_id;
848
- const commandKind = 'chat';
849
- const threadId = this.generateThreadResourceId({
850
- key: threadRef,
851
- commandKind,
852
- surfaceId,
853
- });
854
- const threadResource = this.resolveDataResource(threadId, context);
855
- this.cacheThreadSurfaceId(context, threadId, surfaceId);
856
- return {
857
- threadId,
858
- commandKind,
859
- surfaceId,
860
- thread: threadResource,
861
- };
927
+ throw new Error(`complete thread resource id is required, got "${threadRef}"`);
862
928
  }
863
929
  /**
864
930
  * 缓存 Thread -> surfaceId 映射
@@ -903,65 +969,27 @@ class PodChatKitStore {
903
969
  return binding?.[key]?.value ?? null;
904
970
  }
905
971
  async selectMessagesForThread(thread, context) {
906
- await this.getDb(context);
907
- const cachedFetch = this.getCachedFetch(context);
908
- const podBaseUrl = this.getCachedPodBaseUrl(context);
909
- if (!cachedFetch || !podBaseUrl) {
972
+ const db = await this.getDb(context);
973
+ if (!db) {
910
974
  return [];
911
975
  }
912
976
  const resolvedThread = await this.resolveThreadRef(thread, context);
913
- const endpoint = `${podBaseUrl}/.data/chat/-/sparql`;
914
- const query = `
915
- PREFIX meeting: <http://www.w3.org/ns/pim/meeting#>
916
- PREFIX sioc: <http://rdfs.org/sioc/ns#>
917
- PREFIX foaf: <http://xmlns.com/foaf/0.1/>
918
- PREFIX dcterms: <http://purl.org/dc/terms/>
919
- PREFIX udfs: <https://undefineds.co/ns#>
920
- SELECT ?msg ?maker ?messageType ?legacyRole ?content ?messageStatus ?legacyStatus ?createdAt ?legacyCreatedAt ?toolName ?toolCallId ?metadata
921
- WHERE {
922
- <${resolvedThread.thread}> sioc:has_member ?msg .
923
- ?msg a meeting:Message .
924
- OPTIONAL { ?msg foaf:maker ?maker . }
925
- OPTIONAL { ?msg udfs:messageType ?messageType . }
926
- OPTIONAL { ?msg udfs:role ?legacyRole . }
927
- OPTIONAL { ?msg sioc:content ?content . }
928
- OPTIONAL { ?msg udfs:messageStatus ?messageStatus . }
929
- OPTIONAL { ?msg udfs:status ?legacyStatus . }
930
- OPTIONAL { ?msg dcterms:created ?createdAt . }
931
- OPTIONAL { ?msg udfs:createdAt ?legacyCreatedAt . }
932
- OPTIONAL { ?msg udfs:toolName ?toolName . }
933
- OPTIONAL { ?msg udfs:toolCallId ?toolCallId . }
934
- OPTIONAL { ?msg udfs:metadata ?metadata . }
935
- }
936
- ORDER BY ?createdAt
937
- `.trim();
938
- const response = await cachedFetch(endpoint, {
939
- method: 'POST',
940
- headers: {
941
- 'Content-Type': 'application/sparql-query',
942
- Accept: 'application/sparql-results+json',
943
- },
944
- body: query,
945
- });
946
- if (!response.ok) {
947
- const text = await response.text().catch(() => '');
948
- throw new Error(`Failed to query thread messages: ${response.status} ${response.statusText} - ${text}`);
949
- }
950
- const json = await response.json();
951
- const bindings = json.results?.bindings ?? [];
952
- return bindings.map((binding) => ({
953
- id: this.baseRelativeIdFromResource(this.parseSparqlBindingValue(binding, 'msg') ?? '', context),
954
- chat: null,
977
+ const records = await models_1.messageRepository.list(db, {
955
978
  thread: resolvedThread.thread,
956
- maker: this.parseSparqlBindingValue(binding, 'maker'),
957
- role: this.parseSparqlBindingValue(binding, 'messageType') ?? this.parseSparqlBindingValue(binding, 'legacyRole'),
958
- content: this.parseSparqlBindingValue(binding, 'content'),
959
- status: this.parseSparqlBindingValue(binding, 'messageStatus') ?? this.parseSparqlBindingValue(binding, 'legacyStatus'),
960
- createdAt: this.parseSparqlBindingValue(binding, 'createdAt') ?? this.parseSparqlBindingValue(binding, 'legacyCreatedAt'),
961
- toolName: this.parseSparqlBindingValue(binding, 'toolName'),
962
- toolCallId: this.parseSparqlBindingValue(binding, 'toolCallId'),
963
- metadata: this.parseSparqlBindingValue(binding, 'metadata'),
964
- resource: this.parseSparqlBindingValue(binding, 'msg'),
979
+ });
980
+ return records.map((record) => ({
981
+ id: record.id || '',
982
+ chat: record.chat || null,
983
+ thread: record.thread || resolvedThread.thread,
984
+ maker: record.maker || null,
985
+ role: record.role || null,
986
+ content: record.content || null,
987
+ status: record.status || null,
988
+ createdAt: record.createdAt || null,
989
+ toolName: record.toolName || null,
990
+ toolCallId: record.toolCallId || null,
991
+ metadata: record.metadata || null,
992
+ resource: record.id ? this.resolveDataResource(record.id, context) : null,
965
993
  }));
966
994
  }
967
995
  datePathFromTimestamp(timestamp) {
@@ -983,12 +1011,13 @@ class PodChatKitStore {
983
1011
  });
984
1012
  }
985
1013
  generateItemId(itemType, thread, _context) {
986
- const commandKind = thread.metadata?.commandKind === 'task' ? 'task' : 'chat';
987
- const surfaceId = this.getSurfaceIdFromMetadata(thread.metadata);
1014
+ const surface = this.commandSurfaceFromResourceRef(thread.parent)
1015
+ ?? this.commandSurfaceFromResourceRef(thread.id)
1016
+ ?? { commandKind: 'chat', surfaceId: PodChatKitStore.DEFAULT_CHAT_ID };
988
1017
  return this.generateMessageResourceId({
989
1018
  key: (0, types_1.generateId)(itemType.replace('_', '-')),
990
- commandKind,
991
- surfaceId,
1019
+ commandKind: surface.commandKind,
1020
+ surfaceId: surface.surfaceId,
992
1021
  });
993
1022
  }
994
1023
  // =========================================================================
@@ -1010,11 +1039,13 @@ class PodChatKitStore {
1010
1039
  if (!threadRecord) {
1011
1040
  throw new Error(`Thread not found: ${resolvedThread.threadId}`);
1012
1041
  }
1013
- const chatResourceMap = await this.getChatResourceMap(context);
1014
- const metadata = this.threadRecordToMetadata(threadRecord, chatResourceMap);
1042
+ const metadata = this.threadRecordToMetadata(threadRecord);
1015
1043
  // 缓存结果
1016
1044
  this.cacheThreadMetadata(context, metadata);
1017
- this.cacheThreadSurfaceId(context, metadata.id, resolvedThread.surfaceId);
1045
+ const surface = this.commandSurfaceFromResourceRef(metadata.parent) ?? this.commandSurfaceFromResourceRef(metadata.id);
1046
+ if (surface) {
1047
+ this.cacheThreadSurfaceId(context, metadata.id, surface.surfaceId);
1048
+ }
1018
1049
  return metadata;
1019
1050
  }
1020
1051
  async saveThread(thread, context) {
@@ -1023,15 +1054,16 @@ class PodChatKitStore {
1023
1054
  throw new Error('Cannot access Pod: invalid credentials');
1024
1055
  }
1025
1056
  const now = new Date().toISOString();
1026
- const commandKind = this.getCommandKindFromMetadata(thread.metadata);
1027
- const surfaceId = this.getSurfaceIdFromMetadata(thread.metadata);
1028
- thread.metadata = {
1029
- ...(thread.metadata ?? {}),
1030
- commandKind,
1031
- surface_id: surfaceId,
1032
- chat_id: surfaceId,
1033
- };
1034
- const metadataObject = this.jsonObjectOrNull(thread.metadata);
1057
+ const parent = this.resolveThreadParent(thread, context);
1058
+ const commandKind = parent.commandKind;
1059
+ const surfaceId = parent.surfaceId;
1060
+ const reconcilerOwner = (0, reconciler_1.normalizeReconcilerOwner)(thread.metadata?.reconcilerOwner ?? thread.reconcilerOwner, 'client');
1061
+ const coordination = (0, reconciler_1.reconcilerCoordinationMetadata)(reconcilerOwner);
1062
+ const storedMetadata = (0, reconciler_1.withReconcilerCoordinationMetadata)(this.threadStoredMetadata(thread.metadata), reconcilerOwner);
1063
+ thread.parent = parent.parent;
1064
+ thread.reconcilerOwner = coordination.reconcilerOwner;
1065
+ thread.metadata = storedMetadata;
1066
+ const metadataObject = this.jsonObjectOrNull(storedMetadata);
1035
1067
  if (commandKind === 'chat') {
1036
1068
  await this.ensureChat(surfaceId, context);
1037
1069
  }
@@ -1048,8 +1080,7 @@ class PodChatKitStore {
1048
1080
  if (existing) {
1049
1081
  // Update
1050
1082
  await db.updateByIri(schema_1.Thread, threadResource, {
1051
- chat: commandKind === 'chat' ? this.buildChatResourceId(surfaceId) : null,
1052
- task: commandKind === 'task' ? (0, store_2.buildTaskResourceId)(`index.ttl#${surfaceId}`) : null,
1083
+ parent: parent.parent,
1053
1084
  title: thread.title || null,
1054
1085
  status: this.statusToString(thread.status),
1055
1086
  workspace: thread.workspace || null,
@@ -1061,8 +1092,7 @@ class PodChatKitStore {
1061
1092
  // Insert
1062
1093
  await db.insert(schema_1.Thread).values({
1063
1094
  id: threadResourceId,
1064
- chat: commandKind === 'chat' ? this.buildChatResourceId(surfaceId) : null,
1065
- task: commandKind === 'task' ? (0, store_2.buildTaskResourceId)(`index.ttl#${surfaceId}`) : null,
1095
+ parent: parent.parent,
1066
1096
  title: thread.title || null,
1067
1097
  status: this.statusToString(thread.status),
1068
1098
  workspace: thread.workspace || null,
@@ -1071,15 +1101,12 @@ class PodChatKitStore {
1071
1101
  updatedAt: now,
1072
1102
  });
1073
1103
  }
1074
- // 缓存完整的 Thread metadata,确保 ChatKit metadata.chat_id 包含正确的 surface。
1104
+ // 缓存完整的 Thread metadatametadata 不包含协议兼容字段。
1075
1105
  const threadMetadata = {
1076
1106
  ...thread,
1077
- metadata: {
1078
- ...(thread.metadata ?? {}),
1079
- commandKind,
1080
- surface_id: surfaceId,
1081
- chat_id: surfaceId,
1082
- },
1107
+ parent: parent.parent,
1108
+ ...coordination,
1109
+ metadata: storedMetadata,
1083
1110
  };
1084
1111
  this.cacheThreadMetadata(context, threadMetadata);
1085
1112
  }
@@ -1089,11 +1116,10 @@ class PodChatKitStore {
1089
1116
  return { data: [], has_more: false };
1090
1117
  }
1091
1118
  try {
1092
- const threads = await db.select().from(schema_1.Thread);
1093
- const chatResourceMap = await this.getChatResourceMap(context);
1119
+ const threads = await models_1.threadRepository.list(db);
1094
1120
  const metadataById = new Map();
1095
1121
  for (const thread of threads) {
1096
- const metadata = this.threadRecordToMetadata(thread, chatResourceMap);
1122
+ const metadata = this.threadRecordToMetadata(thread);
1097
1123
  metadataById.set(metadata.id, metadata);
1098
1124
  }
1099
1125
  for (const cached of this.getCachedThreadMetadataList(context)) {
@@ -1211,17 +1237,22 @@ class PodChatKitStore {
1211
1237
  const webId = this.getWebId(context);
1212
1238
  let content = '';
1213
1239
  let role = schema_1.MessageRole.USER;
1214
- let status = null;
1240
+ let status = schema_1.MessageStatus.COMPLETED;
1215
1241
  let toolName = null;
1216
1242
  let toolCallId = null;
1217
1243
  let metadata = null;
1244
+ let mentions = [];
1245
+ const threadMetadata = await this.loadThread({ thread_id: resolvedThread.threadId }, context).catch(() => undefined);
1246
+ const reconcilerOwner = (0, reconciler_1.normalizeReconcilerOwner)(threadMetadata?.metadata?.reconcilerOwner ?? threadMetadata?.reconcilerOwner, 'client');
1218
1247
  if (item.type === 'user_message') {
1219
1248
  const userItem = item;
1220
1249
  content = userItem.content
1221
1250
  .filter((c) => c.type === 'input_text')
1222
1251
  .map((c) => c.text)
1223
1252
  .join('\n');
1253
+ mentions = this.mentionsFromUserMessageContent(userItem.content);
1224
1254
  role = schema_1.MessageRole.USER;
1255
+ status = schema_1.MessageStatus.SENT;
1225
1256
  }
1226
1257
  else if (item.type === 'assistant_message') {
1227
1258
  const assistantItem = item;
@@ -1249,9 +1280,23 @@ class PodChatKitStore {
1249
1280
  // 其他类型暂时存储为 JSON
1250
1281
  content = JSON.stringify(item);
1251
1282
  role = schema_1.MessageRole.SYSTEM;
1252
- }
1283
+ status = schema_1.MessageStatus.COMPLETED;
1284
+ }
1285
+ const durableMessageMetadata = (0, protocol_metadata_1.withProtocolMetadata)((0, reconciler_1.withReconcilerCoordinationMetadata)((0, protocol_metadata_1.withoutProtocolProjectionKeys)(metadata ?? undefined, [
1286
+ 'chat_id',
1287
+ 'thread_id',
1288
+ 'item_id',
1289
+ 'commandKind',
1290
+ 'surface_id',
1291
+ 'conversationKind',
1292
+ ]), reconcilerOwner), 'chatkit', {
1293
+ chat_id: resolvedThread.surfaceId,
1294
+ thread_id: resolvedThread.threadId,
1295
+ item_id: itemResourceId,
1296
+ });
1253
1297
  const messageRecord = {
1254
1298
  id: itemResourceId,
1299
+ parent: this.resolveDataResource(this.buildMessageParentResourceId(resolvedThread), context),
1255
1300
  chat: resolvedThread.commandKind === 'chat' ? this.buildChatResourceId(resolvedThread.surfaceId) : null,
1256
1301
  thread: resolvedThread.thread,
1257
1302
  maker: role === schema_1.MessageRole.USER ? webId : null,
@@ -1260,18 +1305,41 @@ class PodChatKitStore {
1260
1305
  status,
1261
1306
  toolName,
1262
1307
  toolCallId,
1263
- metadata: this.jsonObjectOrNull({
1264
- ...(metadata ?? {}),
1265
- commandKind: resolvedThread.commandKind,
1266
- surface_id: resolvedThread.surfaceId,
1267
- chat_id: resolvedThread.surfaceId,
1268
- }),
1308
+ metadata: this.jsonObjectOrNull(durableMessageMetadata),
1269
1309
  createdAt: new Date(item.created_at * 1000).toISOString(),
1270
1310
  };
1271
1311
  await db.insert(schema_1.Message).values(messageRecord);
1312
+ await this.reconcileGroupUserMessage({
1313
+ thread: resolvedThread.thread,
1314
+ triggerMessage: this.resolveDataResource(itemResourceId, context),
1315
+ actor: String(webId ?? context.userId),
1316
+ role,
1317
+ content,
1318
+ reconcilerOwner,
1319
+ mentions,
1320
+ });
1272
1321
  // Track this ID to avoid cache timing issues in saveItem
1273
1322
  this.recentlyCreatedIds.add(item.id);
1274
1323
  }
1324
+ async reconcileGroupUserMessage(input) {
1325
+ if (!this.serverGroupReconcilerService || input.role !== schema_1.MessageRole.USER) {
1326
+ return;
1327
+ }
1328
+ try {
1329
+ await this.serverGroupReconcilerService.reconcileThreadMessage({
1330
+ thread: input.thread,
1331
+ triggerMessage: input.triggerMessage,
1332
+ actor: input.actor,
1333
+ role: 'user',
1334
+ content: input.content,
1335
+ reconcilerOwner: input.reconcilerOwner,
1336
+ mentions: input.mentions,
1337
+ });
1338
+ }
1339
+ catch (error) {
1340
+ this.logger.warn(`Failed to enqueue ChatKit group Reconciler wake: ${error}`);
1341
+ }
1342
+ }
1275
1343
  async saveItem(thread, item, context) {
1276
1344
  const db = await this.getDb(context);
1277
1345
  if (!db) {
@@ -1463,10 +1531,6 @@ WHERE { ${deletePatterns.join(' ')} }
1463
1531
  }
1464
1532
  run.id = (0, store_1.buildRunResourceId)(run);
1465
1533
  const existing = await db.findById(schema_2.Run, run.id);
1466
- const metadata = this.withXpodMetadata(run.metadata, {
1467
- commandKind: run.commandKind,
1468
- surfaceId: run.surfaceId,
1469
- });
1470
1534
  const values = {
1471
1535
  task: run.task || null,
1472
1536
  thread: run.thread,
@@ -1480,7 +1544,7 @@ WHERE { ${deletePatterns.join(' ')} }
1480
1544
  heartbeatAt: this.timestampToIso(run.heartbeatAt),
1481
1545
  cancelRequestedAt: this.timestampToIso(run.cancelRequestedAt),
1482
1546
  error: run.error || null,
1483
- metadata: this.jsonObjectOrNull(metadata),
1547
+ metadata: this.jsonObjectOrNull(run.metadata),
1484
1548
  createdAt: this.timestampToIso(run.createdAt) ?? new Date().toISOString(),
1485
1549
  startedAt: this.timestampToIso(run.startedAt),
1486
1550
  completedAt: this.timestampToIso(run.completedAt),
@@ -1528,10 +1592,7 @@ WHERE { ${deletePatterns.join(' ')} }
1528
1592
  const records = conditions.length > 0
1529
1593
  ? await query.where((0, drizzle_solid_1.and)(...conditions))
1530
1594
  : await query;
1531
- let runs = records.map((record) => this.runRecordToData(record));
1532
- if (options.commandKind) {
1533
- runs = runs.filter((run) => run.commandKind === options.commandKind);
1534
- }
1595
+ const runs = records.map((record) => this.runRecordToData(record));
1535
1596
  return runs
1536
1597
  .sort((a, b) => b.createdAt - a.createdAt || b.id.localeCompare(a.id))
1537
1598
  .slice(0, options.limit ?? runs.length);
@@ -1545,16 +1606,13 @@ WHERE { ${deletePatterns.join(' ')} }
1545
1606
  throw new Error(`RunStep runId must be a complete Run resource id: ${event.runId}`);
1546
1607
  }
1547
1608
  event.id = (0, store_1.buildRunStepResourceId)(event);
1609
+ const runResource = event.run || this.resolveDataResource(event.runId, context);
1548
1610
  await db.insert(schema_2.RunStep).values({
1549
1611
  id: event.id,
1550
- run: event.run,
1551
1612
  stepType: event.type,
1613
+ run: runResource,
1552
1614
  message: event.message || null,
1553
- payload: this.jsonObjectOrNull(this.withXpodMetadata(event.data, {
1554
- commandKind: event.commandKind,
1555
- surfaceId: event.surfaceId,
1556
- runId: event.runId,
1557
- })),
1615
+ payload: this.jsonObjectOrNull(event.data),
1558
1616
  createdAt: this.timestampToIso(event.createdAt) ?? new Date().toISOString(),
1559
1617
  });
1560
1618
  }
@@ -1566,9 +1624,10 @@ WHERE { ${deletePatterns.join(' ')} }
1566
1624
  if (!(0, store_1.isRunResourceId)(runId)) {
1567
1625
  throw new Error(`loadRunSteps requires a base-relative Run id: ${runId}`);
1568
1626
  }
1569
- const records = await db.select().from(schema_2.RunStep).where((0, drizzle_solid_1.eq)(schema_2.RunStep.run, this.resolveDataResource(runId, context)));
1627
+ const resolvedRun = this.resolveDataResource(runId, context);
1628
+ const records = await db.select().from(schema_2.RunStep).where((0, drizzle_solid_1.eq)(schema_2.RunStep.run, resolvedRun));
1570
1629
  return records
1571
- .map((record) => this.runStepRecordToData(record))
1630
+ .map((record) => this.runStepRecordToData(record, context))
1572
1631
  .sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
1573
1632
  }
1574
1633
  async claimRun(input, context) {
@@ -1595,7 +1654,6 @@ WHERE { ${deletePatterns.join(' ')} }
1595
1654
  task.id = (0, store_2.buildTaskResourceId)(task.id);
1596
1655
  const existing = await db.findById(schema_3.Task, task.id);
1597
1656
  const metadata = this.withXpodMetadata(this.withTaskAuthBindingMetadata(task.metadata, task.authBinding), {
1598
- surfaceId: task.surfaceId,
1599
1657
  runner: task.runner,
1600
1658
  triggerKind: task.triggerKind,
1601
1659
  cron: task.cron ?? null,
@@ -1859,21 +1917,24 @@ WHERE { ${deletePatterns.join(' ')} }
1859
1917
  }
1860
1918
  const endpoint = `${podBaseUrl.replace(/\/$/, '')}/settings/-/sparql`;
1861
1919
  const query = `
1920
+ PREFIX cred: <https://vocab.xpod.dev/credential#>
1921
+ PREFIX ai: <https://vocab.xpod.dev/ai#>
1862
1922
  PREFIX udfs: <https://undefineds.co/ns#>
1863
- SELECT ?cred ?provider ?apiKey ?isDefault ?lastUsedAt ?failCount ?baseUrl ?proxyUrl ?defaultModel ?hasModel
1923
+ SELECT ?cred ?provider ?apiKey ?isDefault ?lastUsedAt ?failCount ?providerBaseUrl ?credentialBaseUrl ?providerProxyUrl ?credentialProxyUrl ?defaultModel ?hasModel
1864
1924
  WHERE {
1865
- ?cred a udfs:Credential ;
1866
- udfs:service "ai" ;
1867
- udfs:status "active" ;
1868
- udfs:apiKey ?apiKey .
1869
- OPTIONAL { ?cred udfs:provider ?provider . }
1870
- OPTIONAL { ?cred udfs:isDefault ?isDefault . }
1871
- OPTIONAL { ?cred udfs:lastUsedAt ?lastUsedAt . }
1872
- OPTIONAL { ?cred udfs:failCount ?failCount . }
1873
- OPTIONAL { ?provider udfs:baseUrl ?baseUrl . }
1874
- OPTIONAL { ?provider udfs:proxyUrl ?proxyUrl . }
1875
- OPTIONAL { ?provider udfs:defaultModel ?defaultModel . }
1876
- OPTIONAL { ?provider udfs:hasModel ?hasModel . }
1925
+ ?cred (cred:service|udfs:service) "ai" ;
1926
+ (cred:status|udfs:status) "active" ;
1927
+ (cred:apiKey|udfs:apiKey) ?apiKey .
1928
+ OPTIONAL { ?cred (cred:provider|udfs:provider) ?provider . }
1929
+ OPTIONAL { ?cred (cred:isDefault|udfs:isDefault) ?isDefault . }
1930
+ OPTIONAL { ?cred (cred:lastUsedAt|udfs:lastUsedAt) ?lastUsedAt . }
1931
+ OPTIONAL { ?cred (cred:failCount|udfs:failCount) ?failCount . }
1932
+ OPTIONAL { ?cred (cred:baseUrl|udfs:baseUrl) ?credentialBaseUrl . }
1933
+ OPTIONAL { ?cred (cred:proxyUrl|udfs:proxyUrl) ?credentialProxyUrl . }
1934
+ OPTIONAL { ?provider (ai:baseUrl|udfs:baseUrl) ?providerBaseUrl . }
1935
+ OPTIONAL { ?provider (ai:proxyUrl|udfs:proxyUrl) ?providerProxyUrl . }
1936
+ OPTIONAL { ?provider (ai:defaultModel|udfs:defaultModel) ?defaultModel . }
1937
+ OPTIONAL { ?provider (ai:hasModel|udfs:hasModel) ?hasModel . }
1877
1938
  }
1878
1939
  `.trim();
1879
1940
  const response = await cachedFetch(endpoint, {
@@ -1900,8 +1961,10 @@ WHERE { ${deletePatterns.join(' ')} }
1900
1961
  isDefault: this.parseSparqlBindingValue(binding, 'isDefault'),
1901
1962
  lastUsedAt: this.parseSparqlBindingValue(binding, 'lastUsedAt'),
1902
1963
  failCount: this.parseIntegerValue(this.parseSparqlBindingValue(binding, 'failCount')),
1903
- baseUrl: this.parseSparqlBindingValue(binding, 'baseUrl'),
1904
- proxyUrl: this.parseSparqlBindingValue(binding, 'proxyUrl'),
1964
+ baseUrl: this.parseSparqlBindingValue(binding, 'providerBaseUrl')
1965
+ ?? this.parseSparqlBindingValue(binding, 'credentialBaseUrl'),
1966
+ proxyUrl: this.parseSparqlBindingValue(binding, 'providerProxyUrl')
1967
+ ?? this.parseSparqlBindingValue(binding, 'credentialProxyUrl'),
1905
1968
  defaultModel: this.parseSparqlBindingValue(binding, 'defaultModel')
1906
1969
  ?? this.parseSparqlBindingValue(binding, 'hasModel'),
1907
1970
  };
@@ -1933,9 +1996,14 @@ WHERE { ${deletePatterns.join(' ')} }
1933
1996
  }
1934
1997
  this.ensurePodBaseUrlCache(context, db);
1935
1998
  try {
1936
- const sparqlConfig = await this.queryAiConfigFromSettingsSparql(context);
1937
- if (sparqlConfig !== null) {
1938
- return sparqlConfig ?? undefined;
1999
+ try {
2000
+ const sparqlConfig = await this.queryAiConfigFromSettingsSparql(context);
2001
+ if (sparqlConfig !== null) {
2002
+ return sparqlConfig ?? undefined;
2003
+ }
2004
+ }
2005
+ catch (error) {
2006
+ this.logger.warn(`Failed to read AI config via settings SPARQL, falling back to model tables: ${error}`);
1939
2007
  }
1940
2008
  // 查询活跃的 AI 凭据
1941
2009
  const credentials = await db.select()
@@ -2035,7 +2103,7 @@ WHERE { ${deletePatterns.join(' ')} }
2035
2103
  catch (error) {
2036
2104
  this.logger.warn(`Failed to load Pod models for ${config.providerId}: ${error}`);
2037
2105
  }
2038
- if (config.defaultModel) {
2106
+ if (config.defaultModel && !seenModelIds.has(config.defaultModel)) {
2039
2107
  this.pushAvailableModel(models, seenModelIds, {
2040
2108
  id: config.defaultModel,
2041
2109
  name: config.defaultModel,