@undefineds.co/xpod 0.3.39 → 0.3.41

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.
@@ -118,6 +118,10 @@
118
118
  "providerFactory": {
119
119
  "@id": "urn:solid-server:default:IdentityProviderFactory"
120
120
  },
121
+ "storageBaseUrl": {
122
+ "@id": "urn:solid-server:default:variable:baseUrl",
123
+ "@type": "Variable"
124
+ },
121
125
  "provisionBaseUrl": {
122
126
  "@id": "urn:solid-server:default:variable:oidcIssuer",
123
127
  "@type": "Variable"
@@ -34,6 +34,11 @@ export declare class PodChatKitStore implements ChatKitStore<StoreContext>, RunS
34
34
  */
35
35
  private getWebId;
36
36
  private derivePodBaseUrl;
37
+ private normalizePodBaseUrl;
38
+ private readPodUrlFromRuntimeSource;
39
+ private readPodUrlFromDatabase;
40
+ private readExplicitPodBaseUrl;
41
+ private ensurePodBaseUrlCache;
37
42
  /**
38
43
  * 将 ThreadStatus 对象转为字符串
39
44
  */
@@ -76,6 +81,7 @@ export declare class PodChatKitStore implements ChatKitStore<StoreContext>, RunS
76
81
  private generateMessageResourceId;
77
82
  private resolveDataResource;
78
83
  private baseRelativeIdFromResource;
84
+ private baseRelativeIdFromPodPath;
79
85
  private runRecordToData;
80
86
  private runStepRecordToData;
81
87
  private taskRecordToData;
@@ -197,6 +203,9 @@ export declare class PodChatKitStore implements ChatKitStore<StoreContext>, RunS
197
203
  private sortAiCredentialCandidates;
198
204
  private isTruthyValue;
199
205
  private timestampValue;
206
+ private parseIntegerValue;
207
+ private extractModelId;
208
+ private queryAiConfigFromSettingsSparql;
200
209
  getAiConfig(context: StoreContext): Promise<{
201
210
  providerId: string;
202
211
  baseUrl: string;
@@ -93,6 +93,7 @@ class PodChatKitStore {
93
93
  context._cachedDb = db;
94
94
  context._cachedFetch = authFetch;
95
95
  context._cachedWebId = auth.webId;
96
+ this.ensurePodBaseUrlCache(context, db, auth.webId);
96
97
  return db;
97
98
  }
98
99
  catch (error) {
@@ -126,6 +127,7 @@ class PodChatKitStore {
126
127
  context._cachedDb = db;
127
128
  context._cachedFetch = authFetch;
128
129
  context._cachedWebId = webId;
130
+ this.ensurePodBaseUrlCache(context, db, webId);
129
131
  context._cachedAccessToken = token.accessToken;
130
132
  context._cachedTokenType = token.tokenType;
131
133
  return db;
@@ -218,6 +220,104 @@ class PodChatKitStore {
218
220
  return podBase.endsWith('/') ? podBase.slice(0, -1) : podBase;
219
221
  }
220
222
  }
223
+ normalizePodBaseUrl(value) {
224
+ if (typeof value !== 'string') {
225
+ return undefined;
226
+ }
227
+ const trimmed = value.trim();
228
+ if (!trimmed) {
229
+ return undefined;
230
+ }
231
+ return trimmed.replace(/\/+$/, '');
232
+ }
233
+ readPodUrlFromRuntimeSource(source) {
234
+ if (!source || typeof source !== 'object') {
235
+ return undefined;
236
+ }
237
+ const record = source;
238
+ const getPodUrl = record.getPodUrl;
239
+ if (typeof getPodUrl === 'function') {
240
+ try {
241
+ const value = getPodUrl.call(source);
242
+ const normalized = this.normalizePodBaseUrl(value);
243
+ if (normalized) {
244
+ return normalized;
245
+ }
246
+ }
247
+ catch {
248
+ // Ignore and continue with direct properties.
249
+ }
250
+ }
251
+ for (const key of ['podUrl', 'podBaseUrl', 'storageUrl', 'storageProviderUrl']) {
252
+ const normalized = this.normalizePodBaseUrl(record[key]);
253
+ if (normalized) {
254
+ return normalized;
255
+ }
256
+ }
257
+ const runtime = record.runtime;
258
+ if (runtime && runtime !== source) {
259
+ return this.readPodUrlFromRuntimeSource(runtime);
260
+ }
261
+ return undefined;
262
+ }
263
+ readPodUrlFromDatabase(db) {
264
+ if (!db || typeof db !== 'object') {
265
+ return undefined;
266
+ }
267
+ const record = db;
268
+ const getDialect = record.getDialect;
269
+ if (typeof getDialect === 'function') {
270
+ try {
271
+ const value = this.readPodUrlFromRuntimeSource(getDialect.call(db));
272
+ if (value) {
273
+ return value;
274
+ }
275
+ }
276
+ catch {
277
+ // Ignore and continue with session.
278
+ }
279
+ }
280
+ const getSession = record.getSession;
281
+ if (typeof getSession === 'function') {
282
+ try {
283
+ const value = this.readPodUrlFromRuntimeSource(getSession.call(db));
284
+ if (value) {
285
+ return value;
286
+ }
287
+ }
288
+ catch {
289
+ // Ignore and continue with direct session property.
290
+ }
291
+ }
292
+ return this.readPodUrlFromRuntimeSource(record.session);
293
+ }
294
+ readExplicitPodBaseUrl(context) {
295
+ const record = context;
296
+ for (const key of ['podBaseUrl', 'podUrl', 'storageUrl', 'storageProviderUrl']) {
297
+ const normalized = this.normalizePodBaseUrl(record[key]);
298
+ if (normalized) {
299
+ return normalized;
300
+ }
301
+ }
302
+ return undefined;
303
+ }
304
+ ensurePodBaseUrlCache(context, db, fallbackWebId) {
305
+ const authoritativePodBaseUrl = this.readExplicitPodBaseUrl(context)
306
+ ?? this.readPodUrlFromDatabase(db);
307
+ if (authoritativePodBaseUrl) {
308
+ context._cachedPodBaseUrl = authoritativePodBaseUrl;
309
+ return authoritativePodBaseUrl;
310
+ }
311
+ const cached = this.getCachedPodBaseUrl(context);
312
+ if (cached) {
313
+ return cached;
314
+ }
315
+ const podBaseUrl = this.derivePodBaseUrl(fallbackWebId ?? this.getWebId(context));
316
+ if (podBaseUrl) {
317
+ context._cachedPodBaseUrl = podBaseUrl;
318
+ }
319
+ return podBaseUrl;
320
+ }
221
321
  /**
222
322
  * 将 ThreadStatus 对象转为字符串
223
323
  */
@@ -489,16 +589,14 @@ class PodChatKitStore {
489
589
  if (/^https?:\/\//.test(resourceId)) {
490
590
  return resourceId;
491
591
  }
492
- const podBaseUrl = this.getCachedPodBaseUrl(context)
493
- ?? this.derivePodBaseUrl(this.getWebId(context));
592
+ const podBaseUrl = this.ensurePodBaseUrlCache(context);
494
593
  if (!podBaseUrl) {
495
594
  throw new Error(`Cannot resolve Pod base URL for resource id: ${resourceId}`);
496
595
  }
497
596
  return (0, store_1.resolveDataResource)(podBaseUrl, resourceId);
498
597
  }
499
598
  baseRelativeIdFromResource(resource, context) {
500
- const podBaseUrl = this.getCachedPodBaseUrl(context)
501
- ?? this.derivePodBaseUrl(this.getWebId(context));
599
+ const podBaseUrl = this.ensurePodBaseUrlCache(context);
502
600
  if (podBaseUrl) {
503
601
  const dataPrefix = `${podBaseUrl.replace(/\/$/, '')}/.data/`;
504
602
  if (resource.startsWith(dataPrefix)) {
@@ -512,6 +610,22 @@ class PodChatKitStore {
512
610
  }
513
611
  return resource;
514
612
  }
613
+ baseRelativeIdFromPodPath(resource, context, podPath) {
614
+ const normalizedPath = podPath.replace(/^\/+|\/+$/g, '');
615
+ const podBaseUrl = this.ensurePodBaseUrlCache(context);
616
+ if (podBaseUrl) {
617
+ const prefix = `${podBaseUrl.replace(/\/$/, '')}/${normalizedPath}/`;
618
+ if (resource.startsWith(prefix)) {
619
+ return resource.slice(prefix.length);
620
+ }
621
+ }
622
+ const marker = `/${normalizedPath}/`;
623
+ const markerIndex = resource.indexOf(marker);
624
+ if (markerIndex >= 0) {
625
+ return resource.slice(markerIndex + marker.length);
626
+ }
627
+ return resource;
628
+ }
515
629
  runRecordToData(record) {
516
630
  const metadata = this.parseJsonObject(record.metadata);
517
631
  const xpod = this.getXpodMetadata(metadata);
@@ -775,8 +889,11 @@ class PodChatKitStore {
775
889
  return context._cachedFetch;
776
890
  }
777
891
  getCachedPodBaseUrl(context) {
778
- const cachedWebId = context._cachedWebId;
779
- return this.derivePodBaseUrl(cachedWebId);
892
+ const cachedPodBaseUrl = context._cachedPodBaseUrl;
893
+ if (cachedPodBaseUrl) {
894
+ return cachedPodBaseUrl.replace(/\/$/, '');
895
+ }
896
+ return undefined;
780
897
  }
781
898
  parseSparqlBindingValue(binding, key) {
782
899
  return binding?.[key]?.value ?? null;
@@ -798,8 +915,8 @@ class PodChatKitStore {
798
915
  PREFIX udfs: <https://undefineds.co/ns#>
799
916
  SELECT ?msg ?maker ?messageType ?legacyRole ?content ?messageStatus ?legacyStatus ?createdAt ?legacyCreatedAt ?toolName ?toolCallId ?metadata
800
917
  WHERE {
801
- ?msg a meeting:Message ;
802
- sioc:has_container <${resolvedThread.thread}> .
918
+ <${resolvedThread.thread}> sioc:has_member ?msg .
919
+ ?msg a meeting:Message .
803
920
  OPTIONAL { ?msg foaf:maker ?maker . }
804
921
  OPTIONAL { ?msg udfs:messageType ?messageType . }
805
922
  OPTIONAL { ?msg udfs:role ?legacyRole . }
@@ -1628,8 +1745,7 @@ WHERE { ${deletePatterns.join(' ')} }
1628
1745
  if (/^https?:\/\//.test(resource)) {
1629
1746
  return resource;
1630
1747
  }
1631
- const podBaseUrl = this.getCachedPodBaseUrl(context)
1632
- ?? this.derivePodBaseUrl(this.getWebId(context));
1748
+ const podBaseUrl = this.ensurePodBaseUrlCache(context);
1633
1749
  if (!podBaseUrl) {
1634
1750
  throw new Error(`Cannot resolve Pod base URL for resource: ${resource}`);
1635
1751
  }
@@ -1698,12 +1814,115 @@ WHERE { ${deletePatterns.join(' ')} }
1698
1814
  }
1699
1815
  return 0;
1700
1816
  }
1817
+ parseIntegerValue(value) {
1818
+ if (typeof value === 'number' && Number.isFinite(value)) {
1819
+ return value;
1820
+ }
1821
+ if (typeof value === 'string' && value.trim()) {
1822
+ const parsed = Number.parseInt(value, 10);
1823
+ return Number.isFinite(parsed) ? parsed : null;
1824
+ }
1825
+ return null;
1826
+ }
1827
+ extractModelId(model) {
1828
+ if (!model) {
1829
+ return undefined;
1830
+ }
1831
+ const hashIndex = model.lastIndexOf('#');
1832
+ if (hashIndex >= 0 && hashIndex < model.length - 1) {
1833
+ return model.slice(hashIndex + 1) || undefined;
1834
+ }
1835
+ const clean = model.replace(/\/+$/, '');
1836
+ const slashIndex = clean.lastIndexOf('/');
1837
+ const tail = slashIndex >= 0 ? clean.slice(slashIndex + 1) : clean;
1838
+ return tail || undefined;
1839
+ }
1840
+ async queryAiConfigFromSettingsSparql(context) {
1841
+ const cachedFetch = this.getCachedFetch(context);
1842
+ const podBaseUrl = this.ensurePodBaseUrlCache(context);
1843
+ if (!cachedFetch || !podBaseUrl) {
1844
+ return null;
1845
+ }
1846
+ const endpoint = `${podBaseUrl.replace(/\/$/, '')}/settings/-/sparql`;
1847
+ const query = `
1848
+ PREFIX udfs: <https://undefineds.co/ns#>
1849
+ SELECT ?cred ?provider ?apiKey ?isDefault ?lastUsedAt ?failCount ?baseUrl ?proxyUrl ?defaultModel ?hasModel
1850
+ WHERE {
1851
+ ?cred a udfs:Credential ;
1852
+ udfs:service "ai" ;
1853
+ udfs:status "active" ;
1854
+ udfs:apiKey ?apiKey .
1855
+ OPTIONAL { ?cred udfs:provider ?provider . }
1856
+ OPTIONAL { ?cred udfs:isDefault ?isDefault . }
1857
+ OPTIONAL { ?cred udfs:lastUsedAt ?lastUsedAt . }
1858
+ OPTIONAL { ?cred udfs:failCount ?failCount . }
1859
+ OPTIONAL { ?provider udfs:baseUrl ?baseUrl . }
1860
+ OPTIONAL { ?provider udfs:proxyUrl ?proxyUrl . }
1861
+ OPTIONAL { ?provider udfs:defaultModel ?defaultModel . }
1862
+ OPTIONAL { ?provider udfs:hasModel ?hasModel . }
1863
+ }
1864
+ `.trim();
1865
+ const response = await cachedFetch(endpoint, {
1866
+ method: 'POST',
1867
+ headers: {
1868
+ 'Content-Type': 'application/sparql-query',
1869
+ Accept: 'application/sparql-results+json',
1870
+ },
1871
+ body: query,
1872
+ });
1873
+ if (!response.ok) {
1874
+ const text = await response.text().catch(() => '');
1875
+ throw new Error(`Failed to query settings AI config: ${response.status} ${response.statusText} - ${text}`);
1876
+ }
1877
+ const json = await response.json();
1878
+ const credentials = (json.results?.bindings ?? []).map((binding) => {
1879
+ const credentialIri = this.parseSparqlBindingValue(binding, 'cred') ?? '';
1880
+ const providerRef = this.parseSparqlBindingValue(binding, 'provider');
1881
+ return {
1882
+ id: this.baseRelativeIdFromPodPath(credentialIri, context, '/settings/'),
1883
+ provider: providerRef,
1884
+ providerId: providerRef ? this.extractProviderId(providerRef) : null,
1885
+ apiKey: this.parseSparqlBindingValue(binding, 'apiKey'),
1886
+ isDefault: this.parseSparqlBindingValue(binding, 'isDefault'),
1887
+ lastUsedAt: this.parseSparqlBindingValue(binding, 'lastUsedAt'),
1888
+ failCount: this.parseIntegerValue(this.parseSparqlBindingValue(binding, 'failCount')),
1889
+ baseUrl: this.parseSparqlBindingValue(binding, 'baseUrl'),
1890
+ proxyUrl: this.parseSparqlBindingValue(binding, 'proxyUrl'),
1891
+ defaultModel: this.parseSparqlBindingValue(binding, 'defaultModel')
1892
+ ?? this.parseSparqlBindingValue(binding, 'hasModel'),
1893
+ };
1894
+ });
1895
+ for (const cred of this.sortAiCredentialCandidates(credentials)) {
1896
+ if (!cred.provider || !cred.apiKey || !cred.baseUrl) {
1897
+ continue;
1898
+ }
1899
+ const providerId = cred.providerId || this.extractProviderId(cred.provider);
1900
+ if (!providerId) {
1901
+ continue;
1902
+ }
1903
+ this.logger.debug(`Using credential ${cred.id} with provider ${providerId}`);
1904
+ return {
1905
+ providerId,
1906
+ baseUrl: cred.baseUrl,
1907
+ proxyUrl: cred.proxyUrl || undefined,
1908
+ defaultModel: this.extractModelId(cred.defaultModel),
1909
+ apiKey: cred.apiKey,
1910
+ credentialId: cred.id,
1911
+ };
1912
+ }
1913
+ return undefined;
1914
+ }
1701
1915
  async getAiConfig(context) {
1702
1916
  const db = await this.getDb(context);
1703
1917
  if (!db) {
1704
1918
  return undefined;
1705
1919
  }
1920
+ this.ensurePodBaseUrlCache(context, db);
1706
1921
  try {
1922
+ const sparqlConfig = await this.queryAiConfigFromSettingsSparql(context);
1923
+ if (sparqlConfig !== null) {
1924
+ return sparqlConfig ?? undefined;
1925
+ }
1707
1926
  // 查询活跃的 AI 凭据
1708
1927
  const credentials = await db.select()
1709
1928
  .from(tables_1.Credential)