@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.
- package/config/xpod.base.json +4 -0
- package/dist/api/chatkit/pod-store.d.ts +9 -0
- package/dist/api/chatkit/pod-store.js +229 -10
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/components/context.jsonld +6 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +2 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +65 -5
- package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -1
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +22 -0
- package/package.json +1 -1
package/config/xpod.base.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
779
|
-
|
|
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
|
|
802
|
-
|
|
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.
|
|
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)
|