@semiont/api-client 0.4.20 → 0.4.22
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/README.md +49 -79
- package/dist/index.d.ts +124 -1278
- package/dist/index.js +549 -2144
- package/dist/index.js.map +1 -1
- package/package.json +3 -7
- package/dist/utils/index.d.ts +0 -584
- package/dist/utils/index.js +0 -646
- package/dist/utils/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,1044 +1,279 @@
|
|
|
1
1
|
import ky, { HTTPError } from 'ky';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import { Subject, BehaviorSubject } from 'rxjs';
|
|
3
|
+
import { RESOURCE_BROADCAST_TYPES, PERSISTED_EVENT_TYPES, BRIDGED_CHANNELS, busLog } from '@semiont/core';
|
|
4
|
+
import { getActiveTraceparent, recordBusEmit, withSpan, SpanKind, extractTraceparent, withTraceparent } from '@semiont/observability';
|
|
5
|
+
import { share, filter, map } from 'rxjs/operators';
|
|
6
6
|
|
|
7
|
-
// src/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
// src/transport/http-transport.ts
|
|
8
|
+
var DEGRADED_THRESHOLD_MS = 3e3;
|
|
9
|
+
var ALLOWED_TRANSITIONS = {
|
|
10
|
+
initial: ["connecting", "closed"],
|
|
11
|
+
connecting: ["open", "reconnecting", "closed"],
|
|
12
|
+
open: ["reconnecting", "closed"],
|
|
13
|
+
reconnecting: ["connecting", "degraded", "closed"],
|
|
14
|
+
degraded: ["connecting", "closed"],
|
|
15
|
+
closed: []
|
|
16
|
+
};
|
|
17
|
+
function createActorVM(options) {
|
|
18
|
+
const { baseUrl, token: tokenOrGetter, channels: initialChannels, scope: initialScope, reconnectMs = 5e3 } = options;
|
|
19
|
+
const getToken = typeof tokenOrGetter === "function" ? tokenOrGetter : () => tokenOrGetter;
|
|
20
|
+
const g = globalThis;
|
|
21
|
+
g.__SEMIONT_ACTOR_INSTANCES__ = (g.__SEMIONT_ACTOR_INSTANCES__ ?? 0) + 1;
|
|
22
|
+
const actorSerial = g.__SEMIONT_ACTOR_INSTANCES__;
|
|
23
|
+
console.debug(`[diag] ActorVM #${actorSerial} constructed (baseUrl=${baseUrl})`);
|
|
24
|
+
const globalChannels = new Set(initialChannels);
|
|
25
|
+
const scopedChannels = /* @__PURE__ */ new Set();
|
|
26
|
+
let activeScope = initialScope;
|
|
27
|
+
const events$ = new Subject();
|
|
28
|
+
const state$ = new BehaviorSubject("initial");
|
|
29
|
+
let currentState = "initial";
|
|
30
|
+
let degradedTimer = null;
|
|
31
|
+
const transition = (next) => {
|
|
32
|
+
if (currentState === next) return;
|
|
33
|
+
const allowed = ALLOWED_TRANSITIONS[currentState];
|
|
34
|
+
if (!allowed.includes(next)) {
|
|
35
|
+
throw new Error(`Invalid connection state transition: ${currentState} \u2192 ${next}`);
|
|
36
|
+
}
|
|
37
|
+
const prev = currentState;
|
|
38
|
+
currentState = next;
|
|
39
|
+
if (next === "reconnecting" && prev !== "reconnecting") {
|
|
40
|
+
if (degradedTimer) clearTimeout(degradedTimer);
|
|
41
|
+
degradedTimer = setTimeout(() => {
|
|
42
|
+
if (currentState === "reconnecting") transition("degraded");
|
|
43
|
+
}, DEGRADED_THRESHOLD_MS);
|
|
44
|
+
}
|
|
45
|
+
if (prev === "reconnecting" && next !== "reconnecting") {
|
|
46
|
+
if (degradedTimer) {
|
|
47
|
+
clearTimeout(degradedTimer);
|
|
48
|
+
degradedTimer = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
state$.next(next);
|
|
52
|
+
};
|
|
53
|
+
let running = false;
|
|
54
|
+
const inflightControllers = /* @__PURE__ */ new Set();
|
|
55
|
+
let reconnectTimer = null;
|
|
13
56
|
let lastEventId = null;
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
57
|
+
const shared$ = events$.pipe(share());
|
|
58
|
+
const disconnect = () => {
|
|
59
|
+
for (const c of inflightControllers) {
|
|
60
|
+
try {
|
|
61
|
+
c.abort();
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
inflightControllers.clear();
|
|
66
|
+
if (reconnectTimer) {
|
|
67
|
+
clearTimeout(reconnectTimer);
|
|
68
|
+
reconnectTimer = null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
17
71
|
const connect = async () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
timestamp: Date.now()
|
|
24
|
-
});
|
|
25
|
-
const headers = {
|
|
26
|
-
...fetchOptions.headers,
|
|
27
|
-
"Accept": "text/event-stream"
|
|
28
|
-
};
|
|
29
|
-
if (lastEventId !== null) {
|
|
30
|
-
headers["Last-Event-ID"] = lastEventId;
|
|
72
|
+
transition("connecting");
|
|
73
|
+
for (const c of inflightControllers) {
|
|
74
|
+
try {
|
|
75
|
+
c.abort();
|
|
76
|
+
} catch {
|
|
31
77
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
url,
|
|
43
|
-
error: error.message,
|
|
44
|
-
status: response.status,
|
|
45
|
-
phase: "connect"
|
|
46
|
-
});
|
|
47
|
-
throw error;
|
|
78
|
+
}
|
|
79
|
+
inflightControllers.clear();
|
|
80
|
+
const params = new URLSearchParams();
|
|
81
|
+
for (const ch of globalChannels) {
|
|
82
|
+
params.append("channel", ch);
|
|
83
|
+
}
|
|
84
|
+
if (activeScope && scopedChannels.size > 0) {
|
|
85
|
+
params.append("scope", activeScope);
|
|
86
|
+
for (const ch of scopedChannels) {
|
|
87
|
+
params.append("scoped", ch);
|
|
48
88
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
}
|
|
90
|
+
const url = `${baseUrl}/bus/subscribe?${params.toString()}`;
|
|
91
|
+
const controller = new AbortController();
|
|
92
|
+
inflightControllers.add(controller);
|
|
93
|
+
try {
|
|
94
|
+
const headers = { Authorization: `Bearer ${getToken()}` };
|
|
95
|
+
if (lastEventId) headers["Last-Event-ID"] = lastEventId;
|
|
96
|
+
const response = await fetch(url, { headers, signal: controller.signal });
|
|
97
|
+
if (!response.ok || !response.body) {
|
|
98
|
+
throw new Error(`SSE connect failed: ${response.status}`);
|
|
58
99
|
}
|
|
59
|
-
|
|
60
|
-
type: "sse_connected",
|
|
61
|
-
url,
|
|
62
|
-
status: response.status,
|
|
63
|
-
contentType: response.headers.get("content-type") || "unknown"
|
|
64
|
-
});
|
|
100
|
+
transition("open");
|
|
65
101
|
const reader = response.body.getReader();
|
|
66
102
|
const decoder = new TextDecoder();
|
|
67
103
|
let buffer = "";
|
|
68
|
-
let
|
|
69
|
-
let
|
|
70
|
-
let
|
|
71
|
-
while (
|
|
104
|
+
let currentEvent = "";
|
|
105
|
+
let currentData = "";
|
|
106
|
+
let currentId;
|
|
107
|
+
while (running && inflightControllers.has(controller)) {
|
|
72
108
|
const { done, value } = await reader.read();
|
|
73
|
-
if (done
|
|
74
|
-
|
|
75
|
-
buffer += chunk;
|
|
109
|
+
if (done) break;
|
|
110
|
+
buffer += decoder.decode(value, { stream: true });
|
|
76
111
|
const lines = buffer.split("\n");
|
|
77
|
-
buffer = lines.pop()
|
|
112
|
+
buffer = lines.pop() ?? "";
|
|
78
113
|
for (const line of lines) {
|
|
79
|
-
if (line.startsWith("event:")) {
|
|
80
|
-
|
|
81
|
-
} else if (line.startsWith("data:")) {
|
|
82
|
-
|
|
83
|
-
} else if (line.startsWith("id:")) {
|
|
84
|
-
|
|
114
|
+
if (line.startsWith("event: ")) {
|
|
115
|
+
currentEvent = line.slice(7);
|
|
116
|
+
} else if (line.startsWith("data: ")) {
|
|
117
|
+
currentData = line.slice(6);
|
|
118
|
+
} else if (line.startsWith("id: ")) {
|
|
119
|
+
currentId = line.slice(4);
|
|
85
120
|
} else if (line === "") {
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
121
|
+
if (currentEvent === "bus-event" && currentData) {
|
|
122
|
+
if (currentId !== void 0) lastEventId = currentId;
|
|
123
|
+
const parsed = JSON.parse(currentData);
|
|
124
|
+
busLog("RECV", parsed.channel, parsed.payload, parsed.scope);
|
|
125
|
+
const carrier = extractTraceparent(
|
|
126
|
+
parsed.payload
|
|
127
|
+
);
|
|
128
|
+
await withTraceparent(
|
|
129
|
+
carrier,
|
|
130
|
+
() => withSpan(
|
|
131
|
+
`bus.recv:${parsed.channel}`,
|
|
132
|
+
() => {
|
|
133
|
+
events$.next(parsed);
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
kind: SpanKind.CONSUMER,
|
|
137
|
+
attrs: {
|
|
138
|
+
"bus.channel": parsed.channel,
|
|
139
|
+
...parsed.scope ? { "bus.scope": parsed.scope } : {}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
);
|
|
92
144
|
}
|
|
145
|
+
currentEvent = "";
|
|
146
|
+
currentData = "";
|
|
147
|
+
currentId = void 0;
|
|
93
148
|
}
|
|
94
149
|
}
|
|
95
|
-
if (closed) break;
|
|
96
|
-
}
|
|
97
|
-
logger?.info("SSE Stream Closed", {
|
|
98
|
-
type: "sse_closed",
|
|
99
|
-
url,
|
|
100
|
-
reason: "complete"
|
|
101
|
-
});
|
|
102
|
-
} catch (error) {
|
|
103
|
-
if (error instanceof Error && error.name !== "AbortError") {
|
|
104
|
-
logger?.error("SSE Stream Error", {
|
|
105
|
-
type: "sse_error",
|
|
106
|
-
url,
|
|
107
|
-
error: error.message,
|
|
108
|
-
phase: "stream"
|
|
109
|
-
});
|
|
110
|
-
} else if (error instanceof Error && error.name === "AbortError") {
|
|
111
|
-
logger?.info("SSE Stream Closed", {
|
|
112
|
-
type: "sse_closed",
|
|
113
|
-
url,
|
|
114
|
-
reason: "abort"
|
|
115
|
-
});
|
|
116
150
|
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (err.name === "AbortError") return;
|
|
153
|
+
} finally {
|
|
154
|
+
inflightControllers.delete(controller);
|
|
117
155
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (id) {
|
|
124
|
-
lastEventId = id;
|
|
125
|
-
}
|
|
126
|
-
try {
|
|
127
|
-
const parsed = JSON.parse(data);
|
|
128
|
-
logger?.debug("SSE Event Received", {
|
|
129
|
-
type: "sse_event",
|
|
130
|
-
url,
|
|
131
|
-
event: eventType || "message",
|
|
132
|
-
hasData: !!data
|
|
133
|
-
});
|
|
134
|
-
if (typeof parsed === "object" && parsed !== null && "metadata" in parsed) {
|
|
135
|
-
config.eventBus.get(eventType).next(parsed);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
config.eventBus.get(eventType).next(parsed);
|
|
139
|
-
if (config.completeEvent && eventType === config.completeEvent) {
|
|
140
|
-
closed = true;
|
|
141
|
-
abortController.abort();
|
|
142
|
-
} else if (config.errorEvent && eventType === config.errorEvent) {
|
|
143
|
-
closed = true;
|
|
144
|
-
abortController.abort();
|
|
145
|
-
}
|
|
146
|
-
} catch (error) {
|
|
147
|
-
logger?.error("SSE Failed to parse event data", { error, eventType, data });
|
|
156
|
+
if (running) {
|
|
157
|
+
transition("reconnecting");
|
|
158
|
+
reconnectTimer = setTimeout(() => {
|
|
159
|
+
if (running) connect();
|
|
160
|
+
}, reconnectMs);
|
|
148
161
|
}
|
|
149
162
|
};
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
while (!closed) {
|
|
155
|
-
abortController = new AbortController();
|
|
156
|
-
try {
|
|
157
|
-
await connect();
|
|
158
|
-
if (closed) return;
|
|
159
|
-
logger?.info("SSE Stream ended cleanly; reconnecting", { url });
|
|
160
|
-
} catch (error) {
|
|
161
|
-
if (closed) return;
|
|
162
|
-
if (error instanceof Error && error.name === "AbortError") return;
|
|
163
|
-
logger?.warn("SSE Stream errored; reconnecting", {
|
|
164
|
-
url,
|
|
165
|
-
error: error instanceof Error ? error.message : String(error),
|
|
166
|
-
delayMs: reconnectDelayMs
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
await new Promise((resolve) => setTimeout(resolve, reconnectDelayMs));
|
|
170
|
-
reconnectDelayMs = Math.min(reconnectDelayMs * 2, RECONNECT_MAX_MS);
|
|
163
|
+
const reconnect = () => {
|
|
164
|
+
if (!running) return;
|
|
165
|
+
if (currentState === "open" || currentState === "connecting" || currentState === "degraded") {
|
|
166
|
+
transition("reconnecting");
|
|
171
167
|
}
|
|
168
|
+
disconnect();
|
|
169
|
+
connect();
|
|
172
170
|
};
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
let reconnectTimer2 = null;
|
|
172
|
+
const RECONNECT_DEBOUNCE_MS = 100;
|
|
173
|
+
const scheduleReconnect = () => {
|
|
174
|
+
if (reconnectTimer2) clearTimeout(reconnectTimer2);
|
|
175
|
+
reconnectTimer2 = setTimeout(() => {
|
|
176
|
+
reconnectTimer2 = null;
|
|
177
|
+
reconnect();
|
|
178
|
+
}, RECONNECT_DEBOUNCE_MS);
|
|
179
179
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
baseUrl;
|
|
186
|
-
logger;
|
|
187
|
-
constructor(config) {
|
|
188
|
-
this.baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl.slice(0, -1) : config.baseUrl;
|
|
189
|
-
this.logger = config.logger;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Get common headers for SSE requests
|
|
193
|
-
*/
|
|
194
|
-
getHeaders(auth) {
|
|
195
|
-
const headers = {
|
|
196
|
-
"Content-Type": "application/json"
|
|
197
|
-
};
|
|
198
|
-
if (auth) {
|
|
199
|
-
headers["Authorization"] = `Bearer ${auth}`;
|
|
200
|
-
}
|
|
201
|
-
return headers;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Subscribe to resource events (long-lived stream)
|
|
205
|
-
*
|
|
206
|
-
* Opens a long-lived SSE connection to receive real-time events for a resource.
|
|
207
|
-
* Used for collaborative editing - see events from other users as they happen.
|
|
208
|
-
*
|
|
209
|
-
* This stream does NOT have a complete event - it stays open until explicitly closed.
|
|
210
|
-
*
|
|
211
|
-
* @param resourceId - Resource URI or ID to subscribe to
|
|
212
|
-
* @param options - Request options (auth token)
|
|
213
|
-
* @returns SSE stream controller with event callback
|
|
214
|
-
*
|
|
215
|
-
* @example
|
|
216
|
-
* ```typescript
|
|
217
|
-
* const stream = sseClient.resourceEvents(
|
|
218
|
-
* 'http://localhost:4000/resources/doc-123',
|
|
219
|
-
* { auth: 'your-token' }
|
|
220
|
-
* );
|
|
221
|
-
*
|
|
222
|
-
* stream.onProgress((event) => {
|
|
223
|
-
* console.log(`Event: ${event.type}`);
|
|
224
|
-
* console.log(`User: ${event.userId}`);
|
|
225
|
-
* console.log(`Sequence: ${event.metadata.sequenceNumber}`);
|
|
226
|
-
* console.log(`Payload:`, event.payload);
|
|
227
|
-
* });
|
|
228
|
-
*
|
|
229
|
-
* stream.onError((error) => {
|
|
230
|
-
* console.error('Stream error:', error.message);
|
|
231
|
-
* });
|
|
232
|
-
*
|
|
233
|
-
* // Close when no longer needed (e.g., component unmount)
|
|
234
|
-
* stream.close();
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
resourceEvents(resourceId, options) {
|
|
238
|
-
const url = `${this.baseUrl}/resources/${resourceId}/events/stream`;
|
|
239
|
-
const stream = createSSEStream(
|
|
240
|
-
url,
|
|
241
|
-
{
|
|
242
|
-
method: "GET",
|
|
243
|
-
headers: this.getHeaders(options.auth)
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
progressEvents: ["*"],
|
|
247
|
-
// Accept all event types (long-lived stream)
|
|
248
|
-
completeEvent: null,
|
|
249
|
-
// Never completes (long-lived)
|
|
250
|
-
errorEvent: null,
|
|
251
|
-
// No error event (errors throw)
|
|
252
|
-
eventBus: options.eventBus,
|
|
253
|
-
reconnect: true
|
|
254
|
-
},
|
|
255
|
-
this.logger
|
|
256
|
-
);
|
|
257
|
-
if (options.onConnected) {
|
|
258
|
-
const sub = options.eventBus.get(SSE_STREAM_CONNECTED).subscribe(() => {
|
|
259
|
-
options.onConnected();
|
|
260
|
-
sub.unsubscribe();
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
return stream;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Subscribe to global system events (long-lived stream)
|
|
267
|
-
*
|
|
268
|
-
* Opens a long-lived SSE connection to receive system-level domain events
|
|
269
|
-
* (entity type additions, etc.) that are not scoped to a specific resource.
|
|
270
|
-
*
|
|
271
|
-
* @param options - Request options (auth token, eventBus)
|
|
272
|
-
* @returns SSE stream controller
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* ```typescript
|
|
276
|
-
* const stream = sseClient.globalEvents({ auth: 'your-token', eventBus });
|
|
277
|
-
*
|
|
278
|
-
* // Events auto-emit to EventBus typed channels — subscribe there
|
|
279
|
-
* eventBus.get('mark:entity-type-added').subscribe((stored) => {
|
|
280
|
-
* // Invalidate entity types query
|
|
281
|
-
* });
|
|
282
|
-
*
|
|
283
|
-
* // Close when no longer needed
|
|
284
|
-
* stream.close();
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
globalEvents(options) {
|
|
288
|
-
const url = `${this.baseUrl}/api/events/stream`;
|
|
289
|
-
const stream = createSSEStream(
|
|
290
|
-
url,
|
|
291
|
-
{
|
|
292
|
-
method: "GET",
|
|
293
|
-
headers: this.getHeaders(options.auth)
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
progressEvents: ["*"],
|
|
297
|
-
completeEvent: null,
|
|
298
|
-
errorEvent: null,
|
|
299
|
-
eventBus: options.eventBus,
|
|
300
|
-
reconnect: true
|
|
301
|
-
},
|
|
302
|
-
this.logger
|
|
303
|
-
);
|
|
304
|
-
if (options.onConnected) {
|
|
305
|
-
const sub = options.eventBus.get(SSE_STREAM_CONNECTED).subscribe(() => {
|
|
306
|
-
options.onConnected();
|
|
307
|
-
sub.unsubscribe();
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
return stream;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Subscribe to participant attention stream (long-lived stream)
|
|
314
|
-
*
|
|
315
|
-
* Opens a participant-scoped SSE connection to receive cross-participant
|
|
316
|
-
* beckon signals. Signals are delivered as 'beckon:focus' events routed
|
|
317
|
-
* to the EventBus — the existing scroll/highlight machinery handles them.
|
|
318
|
-
*
|
|
319
|
-
* Signals are ephemeral — delivered if connected, dropped if not.
|
|
320
|
-
*
|
|
321
|
-
* @param options - Request options (auth token, eventBus)
|
|
322
|
-
* @returns SSE stream controller
|
|
323
|
-
*/
|
|
324
|
-
attentionStream(options) {
|
|
325
|
-
const url = `${this.baseUrl}/api/participants/me/attention-stream`;
|
|
326
|
-
const stream = createSSEStream(
|
|
327
|
-
url,
|
|
328
|
-
{
|
|
329
|
-
method: "GET",
|
|
330
|
-
headers: this.getHeaders(options.auth)
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
progressEvents: ["*"],
|
|
334
|
-
completeEvent: null,
|
|
335
|
-
errorEvent: null,
|
|
336
|
-
eventBus: options.eventBus,
|
|
337
|
-
reconnect: true
|
|
338
|
-
},
|
|
339
|
-
this.logger
|
|
340
|
-
);
|
|
341
|
-
if (options.onConnected) {
|
|
342
|
-
const sub = options.eventBus.get(SSE_STREAM_CONNECTED).subscribe(() => {
|
|
343
|
-
options.onConnected();
|
|
344
|
-
sub.unsubscribe();
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
return stream;
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
var BrowseNamespace = class {
|
|
351
|
-
constructor(http, eventBus, getToken) {
|
|
352
|
-
this.http = http;
|
|
353
|
-
this.eventBus = eventBus;
|
|
354
|
-
this.getToken = getToken;
|
|
355
|
-
this.subscribeToEvents();
|
|
356
|
-
}
|
|
357
|
-
// ── Annotation list cache ───────────────────────────────────────────────
|
|
358
|
-
annotationList$ = new BehaviorSubject(/* @__PURE__ */ new Map());
|
|
359
|
-
fetchingAnnotationList = /* @__PURE__ */ new Set();
|
|
360
|
-
annotationListObs$ = /* @__PURE__ */ new Map();
|
|
361
|
-
// ── Annotation detail cache ─────────────────────────────────────────────
|
|
362
|
-
annotationDetail$ = new BehaviorSubject(/* @__PURE__ */ new Map());
|
|
363
|
-
fetchingAnnotationDetail = /* @__PURE__ */ new Set();
|
|
364
|
-
annotationDetailObs$ = /* @__PURE__ */ new Map();
|
|
365
|
-
// ── Resource detail cache ───────────────────────────────────────────────
|
|
366
|
-
resourceDetail$ = new BehaviorSubject(/* @__PURE__ */ new Map());
|
|
367
|
-
fetchingResourceDetail = /* @__PURE__ */ new Set();
|
|
368
|
-
resourceDetailObs$ = /* @__PURE__ */ new Map();
|
|
369
|
-
// ── Resource list cache ─────────────────────────────────────────────────
|
|
370
|
-
resourceList$ = new BehaviorSubject(/* @__PURE__ */ new Map());
|
|
371
|
-
fetchingResourceList = /* @__PURE__ */ new Set();
|
|
372
|
-
resourceListObs$ = /* @__PURE__ */ new Map();
|
|
373
|
-
// ── Entity types cache ──────────────────────────────────────────────────
|
|
374
|
-
entityTypes$ = new BehaviorSubject(void 0);
|
|
375
|
-
fetchingEntityTypes = false;
|
|
376
|
-
// ── Referenced-by cache ─────────────────────────────────────────────────
|
|
377
|
-
referencedBy$ = new BehaviorSubject(/* @__PURE__ */ new Map());
|
|
378
|
-
fetchingReferencedBy = /* @__PURE__ */ new Set();
|
|
379
|
-
referencedByObs$ = /* @__PURE__ */ new Map();
|
|
380
|
-
getToken;
|
|
381
|
-
// ── Live queries ────────────────────────────────────────────────────────
|
|
382
|
-
resource(resourceId) {
|
|
383
|
-
if (!this.resourceDetail$.value.has(resourceId) && !this.fetchingResourceDetail.has(resourceId)) {
|
|
384
|
-
this.fetchResourceDetail(resourceId);
|
|
385
|
-
}
|
|
386
|
-
let obs = this.resourceDetailObs$.get(resourceId);
|
|
387
|
-
if (!obs) {
|
|
388
|
-
obs = this.resourceDetail$.pipe(map((m) => m.get(resourceId)), distinctUntilChanged());
|
|
389
|
-
this.resourceDetailObs$.set(resourceId, obs);
|
|
390
|
-
}
|
|
391
|
-
return obs;
|
|
392
|
-
}
|
|
393
|
-
resources(filters) {
|
|
394
|
-
const key = JSON.stringify(filters ?? {});
|
|
395
|
-
if (!this.resourceList$.value.has(key) && !this.fetchingResourceList.has(key)) {
|
|
396
|
-
this.fetchResourceList(key, filters);
|
|
397
|
-
}
|
|
398
|
-
let obs = this.resourceListObs$.get(key);
|
|
399
|
-
if (!obs) {
|
|
400
|
-
obs = this.resourceList$.pipe(map((m) => m.get(key)), distinctUntilChanged());
|
|
401
|
-
this.resourceListObs$.set(key, obs);
|
|
402
|
-
}
|
|
403
|
-
return obs;
|
|
404
|
-
}
|
|
405
|
-
annotations(resourceId) {
|
|
406
|
-
if (!this.annotationList$.value.has(resourceId) && !this.fetchingAnnotationList.has(resourceId)) {
|
|
407
|
-
this.fetchAnnotationList(resourceId);
|
|
408
|
-
}
|
|
409
|
-
let obs = this.annotationListObs$.get(resourceId);
|
|
410
|
-
if (!obs) {
|
|
411
|
-
obs = this.annotationList$.pipe(
|
|
412
|
-
map((m) => m.get(resourceId)?.annotations),
|
|
413
|
-
distinctUntilChanged()
|
|
180
|
+
return {
|
|
181
|
+
on$(channel) {
|
|
182
|
+
return shared$.pipe(
|
|
183
|
+
filter((e) => e.channel === channel),
|
|
184
|
+
map((e) => e.payload)
|
|
414
185
|
);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
return obs;
|
|
429
|
-
}
|
|
430
|
-
entityTypes() {
|
|
431
|
-
if (this.entityTypes$.value === void 0 && !this.fetchingEntityTypes) {
|
|
432
|
-
this.fetchEntityTypes();
|
|
433
|
-
}
|
|
434
|
-
return this.entityTypes$.asObservable();
|
|
435
|
-
}
|
|
436
|
-
referencedBy(resourceId) {
|
|
437
|
-
if (!this.referencedBy$.value.has(resourceId) && !this.fetchingReferencedBy.has(resourceId)) {
|
|
438
|
-
this.fetchReferencedBy(resourceId);
|
|
439
|
-
}
|
|
440
|
-
let obs = this.referencedByObs$.get(resourceId);
|
|
441
|
-
if (!obs) {
|
|
442
|
-
obs = this.referencedBy$.pipe(map((m) => m.get(resourceId)), distinctUntilChanged());
|
|
443
|
-
this.referencedByObs$.set(resourceId, obs);
|
|
444
|
-
}
|
|
445
|
-
return obs;
|
|
446
|
-
}
|
|
447
|
-
// ── One-shot reads ──────────────────────────────────────────────────────
|
|
448
|
-
async resourceContent(resourceId) {
|
|
449
|
-
const result = await this.http.getResourceRepresentation(resourceId, {
|
|
450
|
-
accept: "text/plain",
|
|
451
|
-
auth: this.getToken()
|
|
452
|
-
});
|
|
453
|
-
const decoder = new TextDecoder();
|
|
454
|
-
return decoder.decode(result.data);
|
|
455
|
-
}
|
|
456
|
-
async resourceRepresentation(resourceId, options) {
|
|
457
|
-
return this.http.getResourceRepresentation(resourceId, {
|
|
458
|
-
accept: options?.accept,
|
|
459
|
-
auth: this.getToken()
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
async resourceRepresentationStream(resourceId, options) {
|
|
463
|
-
return this.http.getResourceRepresentationStream(resourceId, {
|
|
464
|
-
accept: options?.accept,
|
|
465
|
-
auth: this.getToken()
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
async resourceEvents(resourceId) {
|
|
469
|
-
const result = await this.http.getResourceEvents(resourceId, { auth: this.getToken() });
|
|
470
|
-
return result.events;
|
|
471
|
-
}
|
|
472
|
-
async annotationHistory(resourceId, annotationId) {
|
|
473
|
-
return this.http.getAnnotationHistory(resourceId, annotationId, { auth: this.getToken() });
|
|
474
|
-
}
|
|
475
|
-
async connections(_resourceId) {
|
|
476
|
-
throw new Error("Not implemented: connections endpoint does not exist yet");
|
|
477
|
-
}
|
|
478
|
-
async backlinks(_resourceId) {
|
|
479
|
-
throw new Error("Not implemented: backlinks endpoint does not exist yet");
|
|
480
|
-
}
|
|
481
|
-
async resourcesByName(_query, _limit) {
|
|
482
|
-
throw new Error("Not implemented: resourcesByName endpoint does not exist yet");
|
|
483
|
-
}
|
|
484
|
-
async files(dirPath, sort) {
|
|
485
|
-
return this.http.browseFiles(dirPath, sort, { auth: this.getToken() });
|
|
486
|
-
}
|
|
487
|
-
// ── Invalidation (exposed for other namespaces) ─────────────────────────
|
|
488
|
-
invalidateAnnotationList(resourceId) {
|
|
489
|
-
const next = new Map(this.annotationList$.value);
|
|
490
|
-
next.delete(resourceId);
|
|
491
|
-
this.annotationList$.next(next);
|
|
492
|
-
this.fetchAnnotationList(resourceId);
|
|
493
|
-
}
|
|
494
|
-
invalidateAnnotationDetail(annotationId) {
|
|
495
|
-
const next = new Map(this.annotationDetail$.value);
|
|
496
|
-
next.delete(annotationId);
|
|
497
|
-
this.annotationDetail$.next(next);
|
|
498
|
-
}
|
|
499
|
-
invalidateResourceDetail(id) {
|
|
500
|
-
const next = new Map(this.resourceDetail$.value);
|
|
501
|
-
next.delete(id);
|
|
502
|
-
this.resourceDetail$.next(next);
|
|
503
|
-
this.fetchResourceDetail(id);
|
|
504
|
-
}
|
|
505
|
-
invalidateResourceLists() {
|
|
506
|
-
this.resourceList$.next(/* @__PURE__ */ new Map());
|
|
507
|
-
}
|
|
508
|
-
invalidateEntityTypes() {
|
|
509
|
-
this.entityTypes$.next(void 0);
|
|
510
|
-
this.fetchEntityTypes();
|
|
511
|
-
}
|
|
512
|
-
invalidateReferencedBy(resourceId) {
|
|
513
|
-
const next = new Map(this.referencedBy$.value);
|
|
514
|
-
next.delete(resourceId);
|
|
515
|
-
this.referencedBy$.next(next);
|
|
516
|
-
this.fetchReferencedBy(resourceId);
|
|
517
|
-
}
|
|
518
|
-
updateAnnotationInPlace(resourceId, annotation) {
|
|
519
|
-
const currentList = this.annotationList$.value.get(resourceId);
|
|
520
|
-
if (!currentList) return;
|
|
521
|
-
const existingIdx = currentList.annotations.findIndex((a) => a.id === annotation.id);
|
|
522
|
-
const nextAnnotations = existingIdx >= 0 ? currentList.annotations.map((a, i) => i === existingIdx ? annotation : a) : [...currentList.annotations, annotation];
|
|
523
|
-
const nextList = { ...currentList, annotations: nextAnnotations };
|
|
524
|
-
const nextMap = new Map(this.annotationList$.value);
|
|
525
|
-
nextMap.set(resourceId, nextList);
|
|
526
|
-
this.annotationList$.next(nextMap);
|
|
527
|
-
}
|
|
528
|
-
// ── EventBus subscriptions ──────────────────────────────────────────────
|
|
529
|
-
subscribeToEvents() {
|
|
530
|
-
const bus = this.eventBus;
|
|
531
|
-
bus.get("mark:delete-ok").subscribe((event) => {
|
|
532
|
-
this.invalidateAnnotationDetail(annotationId(event.annotationId));
|
|
533
|
-
});
|
|
534
|
-
bus.get("mark:added").subscribe((stored) => {
|
|
535
|
-
if (stored.resourceId) {
|
|
536
|
-
this.invalidateAnnotationList(stored.resourceId);
|
|
186
|
+
},
|
|
187
|
+
emit: async (channel, payload, emitScope) => {
|
|
188
|
+
const body = { channel, payload };
|
|
189
|
+
if (emitScope) body.scope = emitScope;
|
|
190
|
+
const headers = {
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
Authorization: `Bearer ${getToken()}`
|
|
193
|
+
};
|
|
194
|
+
const trace = getActiveTraceparent();
|
|
195
|
+
if (trace) {
|
|
196
|
+
headers["traceparent"] = trace.traceparent;
|
|
197
|
+
if (trace.tracestate) headers["tracestate"] = trace.tracestate;
|
|
537
198
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
199
|
+
await fetch(`${baseUrl}/bus/emit`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers,
|
|
202
|
+
body: JSON.stringify(body)
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
state$: state$.asObservable(),
|
|
206
|
+
addChannels: (channels, scope) => {
|
|
207
|
+
let changed = false;
|
|
208
|
+
if (scope !== void 0) {
|
|
209
|
+
for (const ch of channels) {
|
|
210
|
+
if (!scopedChannels.has(ch)) {
|
|
211
|
+
scopedChannels.add(ch);
|
|
212
|
+
changed = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (scope !== activeScope) {
|
|
216
|
+
activeScope = scope;
|
|
217
|
+
changed = true;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
for (const ch of channels) {
|
|
221
|
+
if (!globalChannels.has(ch)) {
|
|
222
|
+
globalChannels.add(ch);
|
|
223
|
+
changed = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
542
226
|
}
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
});
|
|
551
|
-
bus.get("mark:entity-tag-added").subscribe((stored) => {
|
|
552
|
-
if (stored.resourceId) {
|
|
553
|
-
this.invalidateAnnotationList(stored.resourceId);
|
|
554
|
-
this.invalidateResourceDetail(stored.resourceId);
|
|
227
|
+
if (changed) scheduleReconnect();
|
|
228
|
+
},
|
|
229
|
+
removeChannels: (channels) => {
|
|
230
|
+
let changed = false;
|
|
231
|
+
for (const ch of channels) {
|
|
232
|
+
if (scopedChannels.delete(ch)) changed = true;
|
|
233
|
+
if (globalChannels.delete(ch)) changed = true;
|
|
555
234
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
235
|
+
if (scopedChannels.size === 0) activeScope = void 0;
|
|
236
|
+
if (changed) scheduleReconnect();
|
|
237
|
+
},
|
|
238
|
+
start: () => {
|
|
239
|
+
if (running) return;
|
|
240
|
+
running = true;
|
|
241
|
+
connect();
|
|
242
|
+
},
|
|
243
|
+
stop: () => {
|
|
244
|
+
running = false;
|
|
245
|
+
if (currentState !== "closed") transition("closed");
|
|
246
|
+
if (reconnectTimer2) {
|
|
247
|
+
clearTimeout(reconnectTimer2);
|
|
248
|
+
reconnectTimer2 = null;
|
|
561
249
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
this.invalidateAnnotationList(event.resourceId);
|
|
250
|
+
if (degradedTimer) {
|
|
251
|
+
clearTimeout(degradedTimer);
|
|
252
|
+
degradedTimer = null;
|
|
566
253
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
});
|
|
576
|
-
bus.get("mark:archived").subscribe((stored) => {
|
|
577
|
-
if (stored.resourceId) {
|
|
578
|
-
this.invalidateResourceDetail(stored.resourceId);
|
|
579
|
-
this.invalidateResourceLists();
|
|
254
|
+
disconnect();
|
|
255
|
+
},
|
|
256
|
+
dispose: () => {
|
|
257
|
+
running = false;
|
|
258
|
+
if (currentState !== "closed") transition("closed");
|
|
259
|
+
if (reconnectTimer2) {
|
|
260
|
+
clearTimeout(reconnectTimer2);
|
|
261
|
+
reconnectTimer2 = null;
|
|
580
262
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
this.invalidateResourceDetail(stored.resourceId);
|
|
585
|
-
this.invalidateResourceLists();
|
|
263
|
+
if (degradedTimer) {
|
|
264
|
+
clearTimeout(degradedTimer);
|
|
265
|
+
degradedTimer = null;
|
|
586
266
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
// ── Fetch helpers ───────────────────────────────────────────────────────
|
|
593
|
-
async fetchAnnotationList(resourceId) {
|
|
594
|
-
if (this.fetchingAnnotationList.has(resourceId)) return;
|
|
595
|
-
this.fetchingAnnotationList.add(resourceId);
|
|
596
|
-
try {
|
|
597
|
-
const result = await this.http.browseAnnotations(resourceId, void 0, { auth: this.getToken() });
|
|
598
|
-
const next = new Map(this.annotationList$.value);
|
|
599
|
-
next.set(resourceId, result);
|
|
600
|
-
this.annotationList$.next(next);
|
|
601
|
-
} catch {
|
|
602
|
-
} finally {
|
|
603
|
-
this.fetchingAnnotationList.delete(resourceId);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
async fetchAnnotationDetail(resourceId, annotationId) {
|
|
607
|
-
if (this.fetchingAnnotationDetail.has(annotationId)) return;
|
|
608
|
-
this.fetchingAnnotationDetail.add(annotationId);
|
|
609
|
-
try {
|
|
610
|
-
const result = await this.http.browseAnnotation(resourceId, annotationId, { auth: this.getToken() });
|
|
611
|
-
const next = new Map(this.annotationDetail$.value);
|
|
612
|
-
next.set(annotationId, result.annotation);
|
|
613
|
-
this.annotationDetail$.next(next);
|
|
614
|
-
} catch {
|
|
615
|
-
} finally {
|
|
616
|
-
this.fetchingAnnotationDetail.delete(annotationId);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
async fetchResourceDetail(id) {
|
|
620
|
-
if (this.fetchingResourceDetail.has(id)) return;
|
|
621
|
-
this.fetchingResourceDetail.add(id);
|
|
622
|
-
try {
|
|
623
|
-
const result = await this.http.browseResource(id, { auth: this.getToken() });
|
|
624
|
-
const next = new Map(this.resourceDetail$.value);
|
|
625
|
-
next.set(id, result.resource);
|
|
626
|
-
this.resourceDetail$.next(next);
|
|
627
|
-
} catch {
|
|
628
|
-
} finally {
|
|
629
|
-
this.fetchingResourceDetail.delete(id);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
async fetchResourceList(key, filters) {
|
|
633
|
-
if (this.fetchingResourceList.has(key)) return;
|
|
634
|
-
this.fetchingResourceList.add(key);
|
|
635
|
-
try {
|
|
636
|
-
const search = filters?.search ? searchQuery(filters.search) : void 0;
|
|
637
|
-
const result = await this.http.browseResources(filters?.limit, filters?.archived, search, { auth: this.getToken() });
|
|
638
|
-
const next = new Map(this.resourceList$.value);
|
|
639
|
-
next.set(key, result.resources);
|
|
640
|
-
this.resourceList$.next(next);
|
|
641
|
-
} catch {
|
|
642
|
-
} finally {
|
|
643
|
-
this.fetchingResourceList.delete(key);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
async fetchEntityTypes() {
|
|
647
|
-
if (this.fetchingEntityTypes) return;
|
|
648
|
-
this.fetchingEntityTypes = true;
|
|
649
|
-
try {
|
|
650
|
-
const result = await this.http.listEntityTypes({ auth: this.getToken() });
|
|
651
|
-
this.entityTypes$.next(result.entityTypes);
|
|
652
|
-
} catch {
|
|
653
|
-
} finally {
|
|
654
|
-
this.fetchingEntityTypes = false;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
async fetchReferencedBy(resourceId) {
|
|
658
|
-
if (this.fetchingReferencedBy.has(resourceId)) return;
|
|
659
|
-
this.fetchingReferencedBy.add(resourceId);
|
|
660
|
-
try {
|
|
661
|
-
const result = await this.http.browseReferences(resourceId, { auth: this.getToken() });
|
|
662
|
-
const next = new Map(this.referencedBy$.value);
|
|
663
|
-
next.set(resourceId, result.referencedBy);
|
|
664
|
-
this.referencedBy$.next(next);
|
|
665
|
-
} catch {
|
|
666
|
-
} finally {
|
|
667
|
-
this.fetchingReferencedBy.delete(resourceId);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
|
-
var MarkNamespace = class {
|
|
672
|
-
constructor(http, eventBus, getToken) {
|
|
673
|
-
this.http = http;
|
|
674
|
-
this.eventBus = eventBus;
|
|
675
|
-
this.getToken = getToken;
|
|
676
|
-
}
|
|
677
|
-
async annotation(resourceId, input) {
|
|
678
|
-
return this.http.markAnnotation(resourceId, input, { auth: this.getToken() });
|
|
679
|
-
}
|
|
680
|
-
async delete(resourceId, annotationId) {
|
|
681
|
-
return this.http.deleteAnnotation(resourceId, annotationId, { auth: this.getToken() });
|
|
682
|
-
}
|
|
683
|
-
async entityType(type) {
|
|
684
|
-
return this.http.addEntityType(type, { auth: this.getToken() });
|
|
685
|
-
}
|
|
686
|
-
async entityTypes(types) {
|
|
687
|
-
return this.http.addEntityTypesBulk(types, { auth: this.getToken() });
|
|
688
|
-
}
|
|
689
|
-
async updateResource(resourceId, data) {
|
|
690
|
-
return this.http.updateResource(resourceId, data, { auth: this.getToken() });
|
|
691
|
-
}
|
|
692
|
-
async archive(resourceId) {
|
|
693
|
-
return this.http.updateResource(resourceId, { archived: true }, { auth: this.getToken() });
|
|
694
|
-
}
|
|
695
|
-
async unarchive(resourceId) {
|
|
696
|
-
return this.http.updateResource(resourceId, { archived: false }, { auth: this.getToken() });
|
|
697
|
-
}
|
|
698
|
-
assist(resourceId, motivation, options) {
|
|
699
|
-
return new Observable((subscriber) => {
|
|
700
|
-
const progress$ = this.eventBus.get("mark:progress").pipe(
|
|
701
|
-
filter((e) => e.resourceId === resourceId)
|
|
702
|
-
);
|
|
703
|
-
const finished$ = this.eventBus.get("mark:assist-finished").pipe(
|
|
704
|
-
filter((e) => e.resourceId === resourceId && e.motivation === motivation)
|
|
705
|
-
);
|
|
706
|
-
const failed$ = this.eventBus.get("mark:assist-failed").pipe(
|
|
707
|
-
filter((e) => e.resourceId === resourceId)
|
|
708
|
-
);
|
|
709
|
-
const progressSub = progress$.pipe(takeUntil(merge(finished$, failed$))).subscribe((e) => subscriber.next(e));
|
|
710
|
-
const finishedSub = finished$.subscribe((e) => {
|
|
711
|
-
subscriber.next(e);
|
|
712
|
-
subscriber.complete();
|
|
713
|
-
});
|
|
714
|
-
const failedSub = failed$.subscribe((e) => {
|
|
715
|
-
subscriber.error(new Error(e.message));
|
|
716
|
-
});
|
|
717
|
-
const auth = this.getToken();
|
|
718
|
-
const postPromise = this.dispatchAssist(resourceId, motivation, options, auth);
|
|
719
|
-
postPromise.catch((error) => {
|
|
720
|
-
subscriber.error(error);
|
|
721
|
-
});
|
|
722
|
-
return () => {
|
|
723
|
-
progressSub.unsubscribe();
|
|
724
|
-
finishedSub.unsubscribe();
|
|
725
|
-
failedSub.unsubscribe();
|
|
726
|
-
};
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
async dispatchAssist(resourceId, motivation, options, auth) {
|
|
730
|
-
if (motivation === "tagging") {
|
|
731
|
-
const { schemaId, categories } = options;
|
|
732
|
-
if (!schemaId || !categories?.length) throw new Error("Tag assist requires schemaId and categories");
|
|
733
|
-
await this.http.annotateTags(resourceId, { schemaId, categories }, { auth });
|
|
734
|
-
} else if (motivation === "linking") {
|
|
735
|
-
const { entityTypes, includeDescriptiveReferences } = options;
|
|
736
|
-
if (!entityTypes?.length) throw new Error("Reference assist requires entityTypes");
|
|
737
|
-
await this.http.annotateReferences(resourceId, {
|
|
738
|
-
entityTypes,
|
|
739
|
-
includeDescriptiveReferences: includeDescriptiveReferences ?? false
|
|
740
|
-
}, { auth });
|
|
741
|
-
} else if (motivation === "highlighting") {
|
|
742
|
-
await this.http.annotateHighlights(resourceId, {
|
|
743
|
-
instructions: options.instructions,
|
|
744
|
-
density: options.density
|
|
745
|
-
}, { auth });
|
|
746
|
-
} else if (motivation === "assessing") {
|
|
747
|
-
await this.http.annotateAssessments(resourceId, {
|
|
748
|
-
instructions: options.instructions,
|
|
749
|
-
tone: options.tone,
|
|
750
|
-
density: options.density,
|
|
751
|
-
language: options.language
|
|
752
|
-
}, { auth });
|
|
753
|
-
} else if (motivation === "commenting") {
|
|
754
|
-
await this.http.annotateComments(resourceId, {
|
|
755
|
-
instructions: options.instructions,
|
|
756
|
-
tone: options.tone,
|
|
757
|
-
density: options.density,
|
|
758
|
-
language: options.language
|
|
759
|
-
}, { auth });
|
|
267
|
+
disconnect();
|
|
268
|
+
events$.complete();
|
|
269
|
+
state$.complete();
|
|
760
270
|
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
this.http = http;
|
|
768
|
-
this.getToken = getToken;
|
|
769
|
-
}
|
|
770
|
-
async body(resourceId, annotationId, operations) {
|
|
771
|
-
await this.http.bindAnnotation(resourceId, annotationId, { operations }, { auth: this.getToken() });
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
var GatherNamespace = class {
|
|
775
|
-
constructor(http, eventBus, getToken) {
|
|
776
|
-
this.http = http;
|
|
777
|
-
this.eventBus = eventBus;
|
|
778
|
-
this.getToken = getToken;
|
|
779
|
-
}
|
|
780
|
-
annotation(annotationId$1, resourceId, options) {
|
|
781
|
-
return new Observable((subscriber) => {
|
|
782
|
-
const correlationId = crypto.randomUUID();
|
|
783
|
-
const complete$ = this.eventBus.get("gather:complete").pipe(
|
|
784
|
-
filter((e) => e.correlationId === correlationId)
|
|
785
|
-
);
|
|
786
|
-
const failed$ = this.eventBus.get("gather:failed").pipe(
|
|
787
|
-
filter((e) => e.correlationId === correlationId)
|
|
788
|
-
);
|
|
789
|
-
const sub = merge(
|
|
790
|
-
this.eventBus.get("gather:annotation-progress").pipe(
|
|
791
|
-
// Progress events don't carry correlationId, so match by annotationId
|
|
792
|
-
filter((e) => e.annotationId === annotationId$1),
|
|
793
|
-
map((e) => e)
|
|
794
|
-
),
|
|
795
|
-
complete$.pipe(map((e) => e))
|
|
796
|
-
).pipe(takeUntil(merge(complete$, failed$))).subscribe({
|
|
797
|
-
next: (v) => subscriber.next(v),
|
|
798
|
-
error: (e) => subscriber.error(e)
|
|
799
|
-
});
|
|
800
|
-
const completeSub = complete$.subscribe((e) => {
|
|
801
|
-
subscriber.next(e);
|
|
802
|
-
subscriber.complete();
|
|
803
|
-
});
|
|
804
|
-
const failedSub = failed$.subscribe((e) => {
|
|
805
|
-
subscriber.error(new Error(e.message));
|
|
806
|
-
});
|
|
807
|
-
this.http.gatherAnnotationContext(
|
|
808
|
-
resourceId,
|
|
809
|
-
annotationId(annotationId$1),
|
|
810
|
-
{ correlationId, contextWindow: options?.contextWindow ?? 2e3 },
|
|
811
|
-
{ auth: this.getToken() }
|
|
812
|
-
).catch((error) => {
|
|
813
|
-
subscriber.error(error);
|
|
814
|
-
});
|
|
815
|
-
return () => {
|
|
816
|
-
sub.unsubscribe();
|
|
817
|
-
completeSub.unsubscribe();
|
|
818
|
-
failedSub.unsubscribe();
|
|
819
|
-
};
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
resource(_resourceId, _options) {
|
|
823
|
-
throw new Error("Not implemented: gather.resource() \u2014 no backend route yet");
|
|
824
|
-
}
|
|
825
|
-
};
|
|
826
|
-
var MatchNamespace = class {
|
|
827
|
-
constructor(http, eventBus, getToken) {
|
|
828
|
-
this.http = http;
|
|
829
|
-
this.eventBus = eventBus;
|
|
830
|
-
this.getToken = getToken;
|
|
831
|
-
}
|
|
832
|
-
search(resourceId, referenceId, context, options) {
|
|
833
|
-
return new Observable((subscriber) => {
|
|
834
|
-
const correlationId = crypto.randomUUID();
|
|
835
|
-
const result$ = this.eventBus.get("match:search-results").pipe(
|
|
836
|
-
filter((e) => e.correlationId === correlationId)
|
|
837
|
-
);
|
|
838
|
-
const failed$ = this.eventBus.get("match:search-failed").pipe(
|
|
839
|
-
filter((e) => e.correlationId === correlationId)
|
|
840
|
-
);
|
|
841
|
-
const resultSub = result$.subscribe((e) => {
|
|
842
|
-
subscriber.next(e);
|
|
843
|
-
subscriber.complete();
|
|
844
|
-
});
|
|
845
|
-
const failedSub = failed$.subscribe((e) => {
|
|
846
|
-
subscriber.error(new Error(e.error));
|
|
847
|
-
});
|
|
848
|
-
this.http.matchSearch(
|
|
849
|
-
resourceId,
|
|
850
|
-
{
|
|
851
|
-
correlationId,
|
|
852
|
-
referenceId,
|
|
853
|
-
context,
|
|
854
|
-
limit: options?.limit,
|
|
855
|
-
useSemanticScoring: options?.useSemanticScoring
|
|
856
|
-
},
|
|
857
|
-
{ auth: this.getToken() }
|
|
858
|
-
).catch((error) => {
|
|
859
|
-
subscriber.error(error);
|
|
860
|
-
});
|
|
861
|
-
return () => {
|
|
862
|
-
resultSub.unsubscribe();
|
|
863
|
-
failedSub.unsubscribe();
|
|
864
|
-
};
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
var YieldNamespace = class {
|
|
869
|
-
constructor(http, eventBus, getToken) {
|
|
870
|
-
this.http = http;
|
|
871
|
-
this.eventBus = eventBus;
|
|
872
|
-
this.getToken = getToken;
|
|
873
|
-
}
|
|
874
|
-
async resource(data) {
|
|
875
|
-
return this.http.yieldResource(data, { auth: this.getToken() });
|
|
876
|
-
}
|
|
877
|
-
fromAnnotation(resourceId$1, annotationId$1, options) {
|
|
878
|
-
return new Observable((subscriber) => {
|
|
879
|
-
const progress$ = this.eventBus.get("yield:progress").pipe(
|
|
880
|
-
filter((e) => e.referenceId === annotationId$1)
|
|
881
|
-
);
|
|
882
|
-
const finished$ = this.eventBus.get("yield:finished").pipe(
|
|
883
|
-
filter((e) => e.referenceId === annotationId$1)
|
|
884
|
-
);
|
|
885
|
-
const failed$ = this.eventBus.get("yield:failed").pipe(
|
|
886
|
-
filter((e) => e.referenceId === annotationId$1)
|
|
887
|
-
);
|
|
888
|
-
const progressSub = progress$.pipe(takeUntil(merge(finished$, failed$))).subscribe((e) => subscriber.next(e));
|
|
889
|
-
const finishedSub = finished$.subscribe((event) => {
|
|
890
|
-
subscriber.next(event);
|
|
891
|
-
subscriber.complete();
|
|
892
|
-
if (event.resourceId && event.referenceId && event.sourceResourceId) {
|
|
893
|
-
this.eventBus.get("bind:update-body").next({
|
|
894
|
-
correlationId: crypto.randomUUID(),
|
|
895
|
-
annotationId: annotationId(event.referenceId),
|
|
896
|
-
resourceId: resourceId(event.sourceResourceId),
|
|
897
|
-
operations: [{ op: "add", item: { type: "SpecificResource", source: event.resourceId } }]
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
const failedSub = failed$.subscribe((e) => {
|
|
902
|
-
subscriber.error(new Error(e.error ?? e.message ?? "Generation failed"));
|
|
903
|
-
});
|
|
904
|
-
this.http.yieldResourceFromAnnotation(
|
|
905
|
-
resourceId$1,
|
|
906
|
-
annotationId$1,
|
|
907
|
-
options,
|
|
908
|
-
{ auth: this.getToken() }
|
|
909
|
-
).catch((error) => {
|
|
910
|
-
subscriber.error(error);
|
|
911
|
-
});
|
|
912
|
-
return () => {
|
|
913
|
-
progressSub.unsubscribe();
|
|
914
|
-
finishedSub.unsubscribe();
|
|
915
|
-
failedSub.unsubscribe();
|
|
916
|
-
};
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
async cloneToken(resourceId) {
|
|
920
|
-
const result = await this.http.generateCloneToken(resourceId, { auth: this.getToken() });
|
|
921
|
-
return result;
|
|
922
|
-
}
|
|
923
|
-
async fromToken(token) {
|
|
924
|
-
const result = await this.http.getResourceByToken(token, { auth: this.getToken() });
|
|
925
|
-
return result.sourceResource;
|
|
926
|
-
}
|
|
927
|
-
async createFromToken(options) {
|
|
928
|
-
return this.http.createResourceFromToken(options, { auth: this.getToken() });
|
|
929
|
-
}
|
|
930
|
-
};
|
|
931
|
-
|
|
932
|
-
// src/namespaces/beckon.ts
|
|
933
|
-
var BeckonNamespace = class {
|
|
934
|
-
constructor(http, getToken) {
|
|
935
|
-
this.http = http;
|
|
936
|
-
this.getToken = getToken;
|
|
937
|
-
}
|
|
938
|
-
attention(annotationId, resourceId) {
|
|
939
|
-
this.http.beckonAttention(
|
|
940
|
-
"me",
|
|
941
|
-
// participantId — always 'me' for self-identification
|
|
942
|
-
{ annotationId, resourceId },
|
|
943
|
-
{ auth: this.getToken() }
|
|
944
|
-
).catch(() => {
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
// src/namespaces/job.ts
|
|
950
|
-
var JobNamespace = class {
|
|
951
|
-
constructor(http, getToken) {
|
|
952
|
-
this.http = http;
|
|
953
|
-
this.getToken = getToken;
|
|
954
|
-
}
|
|
955
|
-
async status(jobId) {
|
|
956
|
-
return this.http.getJobStatus(jobId, { auth: this.getToken() });
|
|
957
|
-
}
|
|
958
|
-
async pollUntilComplete(jobId, options) {
|
|
959
|
-
return this.http.pollJobUntilComplete(jobId, {
|
|
960
|
-
interval: options?.interval,
|
|
961
|
-
timeout: options?.timeout,
|
|
962
|
-
onProgress: options?.onProgress,
|
|
963
|
-
auth: this.getToken()
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
async cancel(jobId, type) {
|
|
967
|
-
throw new Error(`Not implemented: job.cancel(${jobId}, ${type}) \u2014 needs EventBus wiring`);
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
var AuthNamespace = class {
|
|
971
|
-
constructor(http, getToken) {
|
|
972
|
-
this.http = http;
|
|
973
|
-
this.getToken = getToken;
|
|
974
|
-
}
|
|
975
|
-
async password(emailStr, passwordStr) {
|
|
976
|
-
return this.http.authenticatePassword(email(emailStr), passwordStr);
|
|
977
|
-
}
|
|
978
|
-
async google(credential) {
|
|
979
|
-
return this.http.authenticateGoogle(googleCredential(credential));
|
|
980
|
-
}
|
|
981
|
-
async refresh(token) {
|
|
982
|
-
return this.http.refreshToken(refreshToken(token));
|
|
983
|
-
}
|
|
984
|
-
async logout() {
|
|
985
|
-
await this.http.logout({ auth: this.getToken() });
|
|
986
|
-
}
|
|
987
|
-
async me() {
|
|
988
|
-
return this.http.getMe({ auth: this.getToken() });
|
|
989
|
-
}
|
|
990
|
-
async acceptTerms() {
|
|
991
|
-
await this.http.acceptTerms({ auth: this.getToken() });
|
|
992
|
-
}
|
|
993
|
-
async mcpToken() {
|
|
994
|
-
return this.http.generateMCPToken({ auth: this.getToken() });
|
|
995
|
-
}
|
|
996
|
-
async mediaToken(resourceId) {
|
|
997
|
-
return this.http.getMediaToken(resourceId, { auth: this.getToken() });
|
|
998
|
-
}
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
// src/namespaces/admin.ts
|
|
1002
|
-
var AdminNamespace = class {
|
|
1003
|
-
constructor(http, getToken) {
|
|
1004
|
-
this.http = http;
|
|
1005
|
-
this.getToken = getToken;
|
|
1006
|
-
}
|
|
1007
|
-
async users() {
|
|
1008
|
-
const result = await this.http.listUsers({ auth: this.getToken() });
|
|
1009
|
-
return result.users;
|
|
1010
|
-
}
|
|
1011
|
-
async userStats() {
|
|
1012
|
-
return this.http.getUserStats({ auth: this.getToken() });
|
|
1013
|
-
}
|
|
1014
|
-
async updateUser(userId, data) {
|
|
1015
|
-
const result = await this.http.updateUser(userId, data, { auth: this.getToken() });
|
|
1016
|
-
return result.user;
|
|
1017
|
-
}
|
|
1018
|
-
async oauthConfig() {
|
|
1019
|
-
return this.http.getOAuthConfig({ auth: this.getToken() });
|
|
1020
|
-
}
|
|
1021
|
-
async healthCheck() {
|
|
1022
|
-
return this.http.healthCheck({ auth: this.getToken() });
|
|
1023
|
-
}
|
|
1024
|
-
async status() {
|
|
1025
|
-
return this.http.getStatus({ auth: this.getToken() });
|
|
1026
|
-
}
|
|
1027
|
-
async backup() {
|
|
1028
|
-
return this.http.backupKnowledgeBase({ auth: this.getToken() });
|
|
1029
|
-
}
|
|
1030
|
-
async restore(file, onProgress) {
|
|
1031
|
-
return this.http.restoreKnowledgeBase(file, { auth: this.getToken(), onProgress });
|
|
1032
|
-
}
|
|
1033
|
-
async exportKnowledgeBase(params) {
|
|
1034
|
-
return this.http.exportKnowledgeBase(params, { auth: this.getToken() });
|
|
1035
|
-
}
|
|
1036
|
-
async importKnowledgeBase(file, onProgress) {
|
|
1037
|
-
return this.http.importKnowledgeBase(file, { auth: this.getToken(), onProgress });
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
|
-
// src/client.ts
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
var RESOURCE_SCOPED_CHANNELS = [
|
|
274
|
+
...PERSISTED_EVENT_TYPES.filter((t) => t !== "mark:entity-type-added"),
|
|
275
|
+
...RESOURCE_BROADCAST_TYPES
|
|
276
|
+
];
|
|
1042
277
|
var APIError = class extends Error {
|
|
1043
278
|
constructor(message, status, statusText, details) {
|
|
1044
279
|
super(message);
|
|
@@ -1048,40 +283,22 @@ var APIError = class extends Error {
|
|
|
1048
283
|
this.name = "APIError";
|
|
1049
284
|
}
|
|
1050
285
|
};
|
|
1051
|
-
var
|
|
1052
|
-
http;
|
|
286
|
+
var HttpTransport = class {
|
|
1053
287
|
baseUrl;
|
|
1054
|
-
|
|
1055
|
-
|
|
288
|
+
http;
|
|
289
|
+
token$;
|
|
1056
290
|
logger;
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
* SSE streaming client for real-time operations
|
|
1064
|
-
*
|
|
1065
|
-
* Separate from the main HTTP client to clearly mark streaming endpoints.
|
|
1066
|
-
* Uses native fetch() instead of ky for SSE support.
|
|
1067
|
-
*/
|
|
1068
|
-
sse;
|
|
1069
|
-
// ── Verb-oriented namespace API ──────────────────────────────────────────
|
|
1070
|
-
browse;
|
|
1071
|
-
mark;
|
|
1072
|
-
bind;
|
|
1073
|
-
gather;
|
|
1074
|
-
match;
|
|
1075
|
-
yield;
|
|
1076
|
-
beckon;
|
|
1077
|
-
job;
|
|
1078
|
-
auth;
|
|
1079
|
-
admin;
|
|
291
|
+
_actor = null;
|
|
292
|
+
_actorStarted = false;
|
|
293
|
+
disposed = false;
|
|
294
|
+
activeResource = null;
|
|
295
|
+
/** Buses we've been asked to bridge wire events into. */
|
|
296
|
+
bridges = [];
|
|
1080
297
|
constructor(config) {
|
|
1081
|
-
const { baseUrl,
|
|
1082
|
-
this.eventBus = eventBus;
|
|
1083
|
-
this.logger = logger;
|
|
298
|
+
const { baseUrl, timeout = 3e4, retry = 2, logger, tokenRefresher } = config;
|
|
1084
299
|
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
300
|
+
this.token$ = config.token$ ?? new BehaviorSubject(null);
|
|
301
|
+
this.logger = logger;
|
|
1085
302
|
const retryConfig = tokenRefresher ? {
|
|
1086
303
|
limit: 1,
|
|
1087
304
|
methods: ["get", "post", "put", "patch", "delete", "head", "options"],
|
|
@@ -1161,462 +378,234 @@ var SemiontApiClient = class {
|
|
|
1161
378
|
]
|
|
1162
379
|
}
|
|
1163
380
|
});
|
|
1164
|
-
this.
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
if (config.getToken) this._getToken = config.getToken;
|
|
1169
|
-
const getToken = () => this._getToken();
|
|
1170
|
-
this.browse = new BrowseNamespace(this, this.eventBus, getToken);
|
|
1171
|
-
this.mark = new MarkNamespace(this, this.eventBus, getToken);
|
|
1172
|
-
this.bind = new BindNamespace(this, getToken);
|
|
1173
|
-
this.gather = new GatherNamespace(this, this.eventBus, getToken);
|
|
1174
|
-
this.match = new MatchNamespace(this, this.eventBus, getToken);
|
|
1175
|
-
this.yield = new YieldNamespace(this, this.eventBus, getToken);
|
|
1176
|
-
this.beckon = new BeckonNamespace(this, getToken);
|
|
1177
|
-
this.job = new JobNamespace(this, getToken);
|
|
1178
|
-
this.auth = new AuthNamespace(this, getToken);
|
|
1179
|
-
this.admin = new AdminNamespace(this, getToken);
|
|
1180
|
-
}
|
|
1181
|
-
/**
|
|
1182
|
-
* Update the token getter for all verb namespaces.
|
|
1183
|
-
* Called from the React auth layer when the token changes.
|
|
1184
|
-
* All namespaces share this getter via closure — no per-namespace sync needed.
|
|
1185
|
-
*/
|
|
1186
|
-
setTokenGetter(getter) {
|
|
1187
|
-
this._getToken = getter;
|
|
1188
|
-
}
|
|
1189
|
-
authHeaders(options) {
|
|
1190
|
-
return options?.auth ? { Authorization: `Bearer ${options.auth}` } : {};
|
|
1191
|
-
}
|
|
1192
|
-
// ============================================================================
|
|
1193
|
-
// AUTHENTICATION
|
|
1194
|
-
// ============================================================================
|
|
1195
|
-
async authenticatePassword(email, password, options) {
|
|
1196
|
-
return this.http.post(`${this.baseUrl}/api/tokens/password`, {
|
|
1197
|
-
json: { email, password },
|
|
1198
|
-
headers: this.authHeaders(options)
|
|
1199
|
-
}).json();
|
|
1200
|
-
}
|
|
1201
|
-
async refreshToken(token, options) {
|
|
1202
|
-
return this.http.post(`${this.baseUrl}/api/tokens/refresh`, {
|
|
1203
|
-
json: { refreshToken: token },
|
|
1204
|
-
headers: this.authHeaders(options)
|
|
1205
|
-
}).json();
|
|
1206
|
-
}
|
|
1207
|
-
async authenticateGoogle(credential, options) {
|
|
1208
|
-
return this.http.post(`${this.baseUrl}/api/tokens/google`, {
|
|
1209
|
-
json: { credential },
|
|
1210
|
-
headers: this.authHeaders(options)
|
|
1211
|
-
}).json();
|
|
1212
|
-
}
|
|
1213
|
-
async generateMCPToken(options) {
|
|
1214
|
-
return this.http.post(`${this.baseUrl}/api/tokens/mcp-generate`, {
|
|
1215
|
-
headers: this.authHeaders(options)
|
|
1216
|
-
}).json();
|
|
1217
|
-
}
|
|
1218
|
-
async getMediaToken(resourceId, options) {
|
|
1219
|
-
return this.http.post(`${this.baseUrl}/api/tokens/media`, {
|
|
1220
|
-
json: { resourceId },
|
|
1221
|
-
headers: this.authHeaders(options)
|
|
1222
|
-
}).json();
|
|
1223
|
-
}
|
|
1224
|
-
// ============================================================================
|
|
1225
|
-
// USERS
|
|
1226
|
-
// ============================================================================
|
|
1227
|
-
async getMe(options) {
|
|
1228
|
-
return this.http.get(`${this.baseUrl}/api/users/me`, {
|
|
1229
|
-
headers: this.authHeaders(options)
|
|
1230
|
-
}).json();
|
|
1231
|
-
}
|
|
1232
|
-
async acceptTerms(options) {
|
|
1233
|
-
return this.http.post(`${this.baseUrl}/api/users/accept-terms`, {
|
|
1234
|
-
headers: this.authHeaders(options)
|
|
1235
|
-
}).json();
|
|
1236
|
-
}
|
|
1237
|
-
async logout(options) {
|
|
1238
|
-
return this.http.post(`${this.baseUrl}/api/users/logout`, {
|
|
1239
|
-
headers: this.authHeaders(options)
|
|
1240
|
-
}).json();
|
|
1241
|
-
}
|
|
1242
|
-
// ============================================================================
|
|
1243
|
-
// RESOURCES
|
|
1244
|
-
// ============================================================================
|
|
1245
|
-
/**
|
|
1246
|
-
* Create a new resource with binary content support
|
|
1247
|
-
*
|
|
1248
|
-
* @param data - Resource creation data
|
|
1249
|
-
* @param data.name - Resource name
|
|
1250
|
-
* @param data.file - File object or Buffer with binary content
|
|
1251
|
-
* @param data.format - MIME type (e.g., 'text/markdown', 'image/png')
|
|
1252
|
-
* @param data.entityTypes - Optional array of entity types
|
|
1253
|
-
* @param data.language - Optional ISO 639-1 language code
|
|
1254
|
-
* @param data.creationMethod - Optional creation method
|
|
1255
|
-
* @param data.sourceAnnotationId - Optional source annotation ID
|
|
1256
|
-
* @param data.sourceResourceId - Optional source resource ID
|
|
1257
|
-
* @param options - Request options including auth
|
|
1258
|
-
*/
|
|
1259
|
-
async yieldResource(data, options) {
|
|
1260
|
-
const formData = new FormData();
|
|
1261
|
-
formData.append("name", data.name);
|
|
1262
|
-
formData.append("format", data.format);
|
|
1263
|
-
formData.append("storageUri", data.storageUri);
|
|
1264
|
-
if (data.file instanceof File) {
|
|
1265
|
-
formData.append("file", data.file);
|
|
1266
|
-
} else if (Buffer.isBuffer(data.file)) {
|
|
1267
|
-
const blob = new Blob([new Uint8Array(data.file)], { type: data.format });
|
|
1268
|
-
formData.append("file", blob, data.name);
|
|
1269
|
-
} else {
|
|
1270
|
-
throw new Error("file must be a File or Buffer");
|
|
1271
|
-
}
|
|
1272
|
-
if (data.entityTypes && data.entityTypes.length > 0) {
|
|
1273
|
-
formData.append("entityTypes", JSON.stringify(data.entityTypes));
|
|
1274
|
-
}
|
|
1275
|
-
if (data.language) {
|
|
1276
|
-
formData.append("language", data.language);
|
|
1277
|
-
}
|
|
1278
|
-
if (data.creationMethod) {
|
|
1279
|
-
formData.append("creationMethod", data.creationMethod);
|
|
1280
|
-
}
|
|
1281
|
-
if (data.sourceAnnotationId) {
|
|
1282
|
-
formData.append("sourceAnnotationId", data.sourceAnnotationId);
|
|
1283
|
-
}
|
|
1284
|
-
if (data.sourceResourceId) {
|
|
1285
|
-
formData.append("sourceResourceId", data.sourceResourceId);
|
|
1286
|
-
}
|
|
1287
|
-
return this.http.post(`${this.baseUrl}/resources`, {
|
|
1288
|
-
body: formData,
|
|
1289
|
-
headers: this.authHeaders(options)
|
|
1290
|
-
}).json();
|
|
1291
|
-
}
|
|
1292
|
-
async browseResource(id, options) {
|
|
1293
|
-
return this.http.get(`${this.baseUrl}/resources/${id}`, {
|
|
1294
|
-
headers: this.authHeaders(options)
|
|
1295
|
-
}).json();
|
|
1296
|
-
}
|
|
1297
|
-
/**
|
|
1298
|
-
* Get resource representation using W3C content negotiation
|
|
1299
|
-
* Returns raw binary content (images, PDFs, text, etc.) with content type
|
|
1300
|
-
*
|
|
1301
|
-
* @param resourceUri - Full resource URI
|
|
1302
|
-
* @param options - Options including Accept header for content negotiation and auth
|
|
1303
|
-
* @returns Object with data (ArrayBuffer) and contentType (string)
|
|
1304
|
-
*
|
|
1305
|
-
* @example
|
|
1306
|
-
* ```typescript
|
|
1307
|
-
* // Get markdown representation
|
|
1308
|
-
* const { data, contentType } = await client.getResourceRepresentation(rUri, { accept: 'text/markdown', auth: token });
|
|
1309
|
-
* const markdown = new TextDecoder().decode(data);
|
|
1310
|
-
*
|
|
1311
|
-
* // Get image representation
|
|
1312
|
-
* const { data, contentType } = await client.getResourceRepresentation(rUri, { accept: 'image/png', auth: token });
|
|
1313
|
-
* const blob = new Blob([data], { type: contentType });
|
|
1314
|
-
*
|
|
1315
|
-
* // Get PDF representation
|
|
1316
|
-
* const { data, contentType } = await client.getResourceRepresentation(rUri, { accept: 'application/pdf', auth: token });
|
|
1317
|
-
* ```
|
|
1318
|
-
*/
|
|
1319
|
-
async getResourceRepresentation(id, options) {
|
|
1320
|
-
const response = await this.http.get(`${this.baseUrl}/resources/${id}`, {
|
|
1321
|
-
headers: {
|
|
1322
|
-
Accept: options?.accept || "text/plain",
|
|
1323
|
-
...this.authHeaders(options)
|
|
1324
|
-
}
|
|
1325
|
-
});
|
|
1326
|
-
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
1327
|
-
const data = await response.arrayBuffer();
|
|
1328
|
-
return { data, contentType };
|
|
1329
|
-
}
|
|
1330
|
-
/**
|
|
1331
|
-
* Get resource representation as a stream using W3C content negotiation
|
|
1332
|
-
* Returns streaming binary content (for large files: videos, large PDFs, etc.)
|
|
1333
|
-
*
|
|
1334
|
-
* Use this for large files to avoid loading entire content into memory.
|
|
1335
|
-
* The stream is consumed incrementally and the backend connection stays open
|
|
1336
|
-
* until the stream is fully consumed or closed.
|
|
1337
|
-
*
|
|
1338
|
-
* @param resourceUri - Full resource URI
|
|
1339
|
-
* @param options - Options including Accept header for content negotiation and auth
|
|
1340
|
-
* @returns Object with stream (ReadableStream) and contentType (string)
|
|
1341
|
-
*
|
|
1342
|
-
* @example
|
|
1343
|
-
* ```typescript
|
|
1344
|
-
* // Stream large file
|
|
1345
|
-
* const { stream, contentType } = await client.getResourceRepresentationStream(rUri, {
|
|
1346
|
-
* accept: 'video/mp4',
|
|
1347
|
-
* auth: token
|
|
1348
|
-
* });
|
|
1349
|
-
*
|
|
1350
|
-
* // Consume stream chunk by chunk (never loads entire file into memory)
|
|
1351
|
-
* for await (const chunk of stream) {
|
|
1352
|
-
* // Process chunk
|
|
1353
|
-
* console.log(`Received ${chunk.length} bytes`);
|
|
1354
|
-
* }
|
|
1355
|
-
*
|
|
1356
|
-
* // Or pipe to a file in Node.js
|
|
1357
|
-
* const fileStream = fs.createWriteStream('output.mp4');
|
|
1358
|
-
* const reader = stream.getReader();
|
|
1359
|
-
* while (true) {
|
|
1360
|
-
* const { done, value } = await reader.read();
|
|
1361
|
-
* if (done) break;
|
|
1362
|
-
* fileStream.write(value);
|
|
1363
|
-
* }
|
|
1364
|
-
* ```
|
|
1365
|
-
*/
|
|
1366
|
-
async getResourceRepresentationStream(id, options) {
|
|
1367
|
-
const response = await this.http.get(`${this.baseUrl}/resources/${id}`, {
|
|
1368
|
-
headers: {
|
|
1369
|
-
Accept: options?.accept || "text/plain",
|
|
1370
|
-
...this.authHeaders(options)
|
|
381
|
+
this.token$.subscribe((token) => {
|
|
382
|
+
if (token && !this._actorStarted && !this.disposed) {
|
|
383
|
+
this._actorStarted = true;
|
|
384
|
+
this.actor.start();
|
|
1371
385
|
}
|
|
1372
386
|
});
|
|
1373
|
-
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
1374
|
-
if (!response.body) {
|
|
1375
|
-
throw new Error("Response body is null - cannot create stream");
|
|
1376
|
-
}
|
|
1377
|
-
return { stream: response.body, contentType };
|
|
1378
|
-
}
|
|
1379
|
-
async browseResources(limit, archived, query, options) {
|
|
1380
|
-
const searchParams = new URLSearchParams();
|
|
1381
|
-
if (limit) searchParams.append("limit", limit.toString());
|
|
1382
|
-
if (archived !== void 0) searchParams.append("archived", archived.toString());
|
|
1383
|
-
if (query) searchParams.append("q", query);
|
|
1384
|
-
return this.http.get(`${this.baseUrl}/resources`, {
|
|
1385
|
-
searchParams,
|
|
1386
|
-
headers: this.authHeaders(options)
|
|
1387
|
-
}).json();
|
|
1388
|
-
}
|
|
1389
|
-
async updateResource(id, data, options) {
|
|
1390
|
-
await this.http.patch(`${this.baseUrl}/resources/${id}`, {
|
|
1391
|
-
json: data,
|
|
1392
|
-
headers: this.authHeaders(options)
|
|
1393
|
-
}).text();
|
|
1394
|
-
}
|
|
1395
|
-
async getResourceEvents(id, options) {
|
|
1396
|
-
return this.http.get(`${this.baseUrl}/resources/${id}/events`, {
|
|
1397
|
-
headers: this.authHeaders(options)
|
|
1398
|
-
}).json();
|
|
1399
|
-
}
|
|
1400
|
-
async browseReferences(id, options) {
|
|
1401
|
-
return this.http.get(`${this.baseUrl}/resources/${id}/referenced-by`, {
|
|
1402
|
-
headers: this.authHeaders(options)
|
|
1403
|
-
}).json();
|
|
1404
|
-
}
|
|
1405
|
-
async generateCloneToken(id, options) {
|
|
1406
|
-
return this.http.post(`${this.baseUrl}/resources/${id}/clone-with-token`, {
|
|
1407
|
-
headers: this.authHeaders(options)
|
|
1408
|
-
}).json();
|
|
1409
|
-
}
|
|
1410
|
-
async getResourceByToken(token, options) {
|
|
1411
|
-
return this.http.get(`${this.baseUrl}/api/clone-tokens/${token}`, {
|
|
1412
|
-
headers: this.authHeaders(options)
|
|
1413
|
-
}).json();
|
|
1414
|
-
}
|
|
1415
|
-
async createResourceFromToken(data, options) {
|
|
1416
|
-
return this.http.post(`${this.baseUrl}/api/clone-tokens/create-resource`, {
|
|
1417
|
-
json: data,
|
|
1418
|
-
headers: this.authHeaders(options)
|
|
1419
|
-
}).json();
|
|
1420
|
-
}
|
|
1421
|
-
// ============================================================================
|
|
1422
|
-
// ANNOTATIONS
|
|
1423
|
-
// ============================================================================
|
|
1424
|
-
async markAnnotation(id, data, options) {
|
|
1425
|
-
return this.http.post(`${this.baseUrl}/resources/${id}/annotations`, {
|
|
1426
|
-
json: data,
|
|
1427
|
-
headers: this.authHeaders(options)
|
|
1428
|
-
}).json();
|
|
1429
|
-
}
|
|
1430
|
-
async getAnnotation(id, options) {
|
|
1431
|
-
return this.http.get(`${this.baseUrl}/annotations/${id}`, {
|
|
1432
|
-
headers: this.authHeaders(options)
|
|
1433
|
-
}).json();
|
|
1434
387
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
388
|
+
// ── Lazy actor construction + per-channel fan-in to bridges ───────────
|
|
389
|
+
//
|
|
390
|
+
// `actor` is exposed so the legacy `SemiontClient` can keep `.actor`
|
|
391
|
+
// pointing at the same ActorVM during the transport-abstraction
|
|
392
|
+
// migration. Once SemiontClient is removed, this should be made
|
|
393
|
+
// private again — external callers should use emit/on/stream/state$.
|
|
394
|
+
get actor() {
|
|
395
|
+
if (!this._actor) {
|
|
396
|
+
this._actor = createActorVM({
|
|
397
|
+
baseUrl: this.baseUrl,
|
|
398
|
+
token: () => this.token$.getValue() ?? "",
|
|
399
|
+
channels: [...BRIDGED_CHANNELS]
|
|
400
|
+
});
|
|
401
|
+
for (const channel of BRIDGED_CHANNELS) {
|
|
402
|
+
this._actor.on$(channel).subscribe((payload) => {
|
|
403
|
+
for (const bus of this.bridges) {
|
|
404
|
+
bus.get(channel).next(payload);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return this._actor;
|
|
410
|
+
}
|
|
411
|
+
// ── ITransport — bus primitives ───────────────────────────────────────
|
|
412
|
+
async emit(channel, payload, resourceScope) {
|
|
413
|
+
busLog("EMIT", channel, payload, resourceScope);
|
|
414
|
+
recordBusEmit(channel, resourceScope);
|
|
415
|
+
await withSpan(
|
|
416
|
+
`bus.emit:${channel}`,
|
|
417
|
+
async () => {
|
|
418
|
+
if (resourceScope !== void 0) {
|
|
419
|
+
await this.actor.emit(
|
|
420
|
+
channel,
|
|
421
|
+
payload,
|
|
422
|
+
resourceScope
|
|
423
|
+
);
|
|
424
|
+
} else {
|
|
425
|
+
await this.actor.emit(
|
|
426
|
+
channel,
|
|
427
|
+
payload
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
kind: SpanKind.PRODUCER,
|
|
433
|
+
attrs: {
|
|
434
|
+
"bus.channel": channel,
|
|
435
|
+
...resourceScope ? { "bus.scope": resourceScope } : {}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
);
|
|
1439
439
|
}
|
|
1440
|
-
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
return this.http.get(`${this.baseUrl}/resources/${id}/annotations`, {
|
|
1444
|
-
searchParams,
|
|
1445
|
-
headers: this.authHeaders(options)
|
|
1446
|
-
}).json();
|
|
440
|
+
on(channel, handler) {
|
|
441
|
+
const sub = this.actor.on$(channel).subscribe(handler);
|
|
442
|
+
return () => sub.unsubscribe();
|
|
1447
443
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
headers: this.authHeaders(options)
|
|
1451
|
-
});
|
|
444
|
+
stream(channel) {
|
|
445
|
+
return this.actor.on$(channel);
|
|
1452
446
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Wire this transport's SSE fan-in into the given bus. Every channel
|
|
449
|
+
* in `BRIDGED_CHANNELS` (and subsequently per-resource scoped channels
|
|
450
|
+
* opened by `subscribeToResource`) is published on the bus. Safe to
|
|
451
|
+
* call multiple times — each bus is added to the fan-out list.
|
|
452
|
+
*/
|
|
453
|
+
bridgeInto(bus) {
|
|
454
|
+
this.bridges.push(bus);
|
|
455
|
+
}
|
|
456
|
+
subscribeToResource(resourceId) {
|
|
457
|
+
if (this.activeResource) {
|
|
458
|
+
if (this.activeResource.resourceId !== resourceId) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`HttpTransport already subscribed to resource ${this.activeResource.resourceId}; call the unsubscribe returned from the previous subscribeToResource before subscribing to ${resourceId}.`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
this.activeResource.refCount++;
|
|
464
|
+
return this.makeUnsubscriber();
|
|
465
|
+
}
|
|
466
|
+
this.actor.addChannels([...RESOURCE_SCOPED_CHANNELS], resourceId);
|
|
467
|
+
const bridgeSubs = [];
|
|
468
|
+
for (const channel of RESOURCE_SCOPED_CHANNELS) {
|
|
469
|
+
bridgeSubs.push(
|
|
470
|
+
this.actor.on$(channel).subscribe((payload) => {
|
|
471
|
+
for (const bus of this.bridges) {
|
|
472
|
+
bus.get(channel).next(payload);
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
this.activeResource = { resourceId, refCount: 1, bridgeSubs };
|
|
478
|
+
return this.makeUnsubscriber();
|
|
479
|
+
}
|
|
480
|
+
makeUnsubscriber() {
|
|
481
|
+
let called = false;
|
|
482
|
+
return () => {
|
|
483
|
+
if (called) return;
|
|
484
|
+
called = true;
|
|
485
|
+
if (!this.activeResource) return;
|
|
486
|
+
this.activeResource.refCount--;
|
|
487
|
+
if (this.activeResource.refCount > 0) return;
|
|
488
|
+
for (const sub of this.activeResource.bridgeSubs) sub.unsubscribe();
|
|
489
|
+
this.actor.removeChannels([...RESOURCE_SCOPED_CHANNELS]);
|
|
490
|
+
this.activeResource = null;
|
|
491
|
+
};
|
|
1458
492
|
}
|
|
1459
|
-
|
|
1460
|
-
return this.
|
|
1461
|
-
headers: this.authHeaders(options)
|
|
1462
|
-
}).json();
|
|
493
|
+
get state$() {
|
|
494
|
+
return this.actor.state$;
|
|
1463
495
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
496
|
+
dispose() {
|
|
497
|
+
if (this.disposed) return;
|
|
498
|
+
this.disposed = true;
|
|
499
|
+
if (this.activeResource) {
|
|
500
|
+
for (const sub of this.activeResource.bridgeSubs) sub.unsubscribe();
|
|
501
|
+
this.activeResource = null;
|
|
502
|
+
}
|
|
503
|
+
if (this._actor) {
|
|
504
|
+
this._actor.dispose();
|
|
505
|
+
this._actor = null;
|
|
506
|
+
}
|
|
1469
507
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
}).json();
|
|
508
|
+
// ── Auth ──────────────────────────────────────────────────────────────
|
|
509
|
+
authHeaders() {
|
|
510
|
+
const token = this.token$.getValue() ?? void 0;
|
|
511
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
1475
512
|
}
|
|
1476
|
-
async
|
|
1477
|
-
return this.http.post(`${this.baseUrl}/
|
|
1478
|
-
json:
|
|
1479
|
-
headers: this.authHeaders(
|
|
513
|
+
async authenticatePassword(email, password) {
|
|
514
|
+
return this.http.post(`${this.baseUrl}/api/tokens/password`, {
|
|
515
|
+
json: { email, password },
|
|
516
|
+
headers: this.authHeaders()
|
|
1480
517
|
}).json();
|
|
1481
518
|
}
|
|
1482
|
-
async
|
|
1483
|
-
return this.http.post(`${this.baseUrl}/
|
|
1484
|
-
json:
|
|
1485
|
-
headers: this.authHeaders(
|
|
519
|
+
async authenticateGoogle(credential) {
|
|
520
|
+
return this.http.post(`${this.baseUrl}/api/tokens/google`, {
|
|
521
|
+
json: { credential },
|
|
522
|
+
headers: this.authHeaders()
|
|
1486
523
|
}).json();
|
|
1487
524
|
}
|
|
1488
|
-
async
|
|
1489
|
-
return this.http.post(`${this.baseUrl}/
|
|
1490
|
-
json:
|
|
1491
|
-
headers: this.authHeaders(
|
|
525
|
+
async refreshAccessToken(token) {
|
|
526
|
+
return this.http.post(`${this.baseUrl}/api/tokens/refresh`, {
|
|
527
|
+
json: { refreshToken: token },
|
|
528
|
+
headers: this.authHeaders()
|
|
1492
529
|
}).json();
|
|
1493
530
|
}
|
|
1494
|
-
async
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
headers: this.authHeaders(options)
|
|
531
|
+
async logout() {
|
|
532
|
+
await this.http.post(`${this.baseUrl}/api/users/logout`, {
|
|
533
|
+
headers: this.authHeaders()
|
|
1498
534
|
}).json();
|
|
1499
535
|
}
|
|
1500
|
-
async
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
headers: this.authHeaders(options)
|
|
536
|
+
async acceptTerms() {
|
|
537
|
+
await this.http.post(`${this.baseUrl}/api/users/accept-terms`, {
|
|
538
|
+
headers: this.authHeaders()
|
|
1504
539
|
}).json();
|
|
1505
540
|
}
|
|
1506
|
-
async
|
|
1507
|
-
return this.http.
|
|
1508
|
-
|
|
1509
|
-
headers: this.authHeaders(options)
|
|
541
|
+
async getCurrentUser() {
|
|
542
|
+
return this.http.get(`${this.baseUrl}/api/users/me`, {
|
|
543
|
+
headers: this.authHeaders()
|
|
1510
544
|
}).json();
|
|
1511
545
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
async addEntityType(type, options) {
|
|
1516
|
-
await this.http.post(`${this.baseUrl}/api/entity-types`, {
|
|
1517
|
-
json: { tag: type },
|
|
1518
|
-
headers: this.authHeaders(options)
|
|
1519
|
-
});
|
|
1520
|
-
}
|
|
1521
|
-
async addEntityTypesBulk(types, options) {
|
|
1522
|
-
await this.http.post(`${this.baseUrl}/api/entity-types/bulk`, {
|
|
1523
|
-
json: { tags: types },
|
|
1524
|
-
headers: this.authHeaders(options)
|
|
1525
|
-
});
|
|
1526
|
-
}
|
|
1527
|
-
async listEntityTypes(options) {
|
|
1528
|
-
return this.http.get(`${this.baseUrl}/api/entity-types`, {
|
|
1529
|
-
headers: this.authHeaders(options)
|
|
546
|
+
async generateMcpToken() {
|
|
547
|
+
return this.http.post(`${this.baseUrl}/api/tokens/mcp-generate`, {
|
|
548
|
+
headers: this.authHeaders()
|
|
1530
549
|
}).json();
|
|
1531
550
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
return this.http.post(`${this.baseUrl}/api/participants/${participantId}/attention`, {
|
|
1537
|
-
json: data,
|
|
1538
|
-
headers: this.authHeaders(options)
|
|
551
|
+
async getMediaToken(resourceId) {
|
|
552
|
+
return this.http.post(`${this.baseUrl}/api/tokens/media`, {
|
|
553
|
+
json: { resourceId },
|
|
554
|
+
headers: this.authHeaders()
|
|
1539
555
|
}).json();
|
|
1540
556
|
}
|
|
1541
|
-
//
|
|
1542
|
-
|
|
1543
|
-
// ============================================================================
|
|
1544
|
-
async listUsers(options) {
|
|
557
|
+
// ── Admin ─────────────────────────────────────────────────────────────
|
|
558
|
+
async listUsers() {
|
|
1545
559
|
return this.http.get(`${this.baseUrl}/api/admin/users`, {
|
|
1546
|
-
headers: this.authHeaders(
|
|
560
|
+
headers: this.authHeaders()
|
|
1547
561
|
}).json();
|
|
1548
562
|
}
|
|
1549
|
-
async getUserStats(
|
|
563
|
+
async getUserStats() {
|
|
1550
564
|
return this.http.get(`${this.baseUrl}/api/admin/users/stats`, {
|
|
1551
|
-
headers: this.authHeaders(
|
|
565
|
+
headers: this.authHeaders()
|
|
1552
566
|
}).json();
|
|
1553
567
|
}
|
|
1554
|
-
|
|
1555
|
-
* Update a user by ID
|
|
1556
|
-
* Note: Users use DID identifiers (did:web:domain:users:id), not HTTP URIs.
|
|
1557
|
-
*/
|
|
1558
|
-
async updateUser(id, data, options) {
|
|
568
|
+
async updateUser(id, data) {
|
|
1559
569
|
return this.http.patch(`${this.baseUrl}/api/admin/users/${id}`, {
|
|
1560
570
|
json: data,
|
|
1561
|
-
headers: this.authHeaders(
|
|
571
|
+
headers: this.authHeaders()
|
|
1562
572
|
}).json();
|
|
1563
573
|
}
|
|
1564
|
-
async getOAuthConfig(
|
|
574
|
+
async getOAuthConfig() {
|
|
1565
575
|
return this.http.get(`${this.baseUrl}/api/admin/oauth/config`, {
|
|
1566
|
-
headers: this.authHeaders(
|
|
576
|
+
headers: this.authHeaders()
|
|
1567
577
|
}).json();
|
|
1568
578
|
}
|
|
1569
|
-
//
|
|
1570
|
-
|
|
1571
|
-
// ============================================================================
|
|
1572
|
-
/**
|
|
1573
|
-
* Create a backup of the knowledge base. Returns raw Response for streaming download.
|
|
1574
|
-
* Caller should use response.blob() to trigger a file download.
|
|
1575
|
-
*/
|
|
1576
|
-
async backupKnowledgeBase(options) {
|
|
579
|
+
// ── Exchange (backup/restore/export/import) ───────────────────────────
|
|
580
|
+
async backupKnowledgeBase() {
|
|
1577
581
|
return this.http.post(`${this.baseUrl}/api/admin/exchange/backup`, {
|
|
1578
|
-
headers: this.authHeaders(
|
|
582
|
+
headers: this.authHeaders()
|
|
1579
583
|
});
|
|
1580
584
|
}
|
|
1581
|
-
|
|
1582
|
-
* Restore knowledge base from a backup file. Parses SSE progress events and calls onProgress.
|
|
1583
|
-
* Returns the final SSE event (phase: 'complete' or 'error').
|
|
1584
|
-
*/
|
|
1585
|
-
async restoreKnowledgeBase(file, options) {
|
|
585
|
+
async restoreKnowledgeBase(file, onProgress) {
|
|
1586
586
|
const formData = new FormData();
|
|
1587
587
|
formData.append("file", file);
|
|
1588
588
|
const response = await this.http.post(`${this.baseUrl}/api/admin/exchange/restore`, {
|
|
1589
589
|
body: formData,
|
|
1590
|
-
headers: this.authHeaders(
|
|
590
|
+
headers: this.authHeaders()
|
|
1591
591
|
});
|
|
1592
|
-
return this.parseSSEStream(response,
|
|
592
|
+
return this.parseSSEStream(response, onProgress);
|
|
1593
593
|
}
|
|
1594
|
-
|
|
1595
|
-
// ADMIN — EXCHANGE (Linked Data Export/Import)
|
|
1596
|
-
// ============================================================================
|
|
1597
|
-
/**
|
|
1598
|
-
* Export the knowledge base as a JSON-LD Linked Data archive. Returns raw Response for streaming download.
|
|
1599
|
-
* Caller should use response.blob() to trigger a file download.
|
|
1600
|
-
*/
|
|
1601
|
-
async exportKnowledgeBase(params, options) {
|
|
594
|
+
async exportKnowledgeBase(params) {
|
|
1602
595
|
const searchParams = params?.includeArchived ? new URLSearchParams({ includeArchived: "true" }) : void 0;
|
|
1603
596
|
return this.http.post(`${this.baseUrl}/api/moderate/exchange/export`, {
|
|
1604
|
-
headers: this.authHeaders(
|
|
597
|
+
headers: this.authHeaders(),
|
|
1605
598
|
...searchParams ? { searchParams } : {}
|
|
1606
599
|
});
|
|
1607
600
|
}
|
|
1608
|
-
|
|
1609
|
-
* Import a JSON-LD Linked Data archive into the knowledge base. Parses SSE progress events and calls onProgress.
|
|
1610
|
-
* Returns the final SSE event (phase: 'complete' or 'error').
|
|
1611
|
-
*/
|
|
1612
|
-
async importKnowledgeBase(file, options) {
|
|
601
|
+
async importKnowledgeBase(file, onProgress) {
|
|
1613
602
|
const formData = new FormData();
|
|
1614
603
|
formData.append("file", file);
|
|
1615
604
|
const response = await this.http.post(`${this.baseUrl}/api/moderate/exchange/import`, {
|
|
1616
605
|
body: formData,
|
|
1617
|
-
headers: this.authHeaders(
|
|
606
|
+
headers: this.authHeaders()
|
|
1618
607
|
});
|
|
1619
|
-
return this.parseSSEStream(response,
|
|
608
|
+
return this.parseSSEStream(response, onProgress);
|
|
1620
609
|
}
|
|
1621
610
|
async parseSSEStream(response, onProgress) {
|
|
1622
611
|
const reader = response.body.getReader();
|
|
@@ -1633,737 +622,153 @@ var SemiontApiClient = class {
|
|
|
1633
622
|
if (line.startsWith("data: ")) {
|
|
1634
623
|
const event = JSON.parse(line.slice(6));
|
|
1635
624
|
onProgress?.(event);
|
|
1636
|
-
|
|
625
|
+
if (event.phase === "complete" || event.phase === "error" || event.phase === "failed") {
|
|
626
|
+
finalResult = event;
|
|
627
|
+
}
|
|
1637
628
|
}
|
|
1638
629
|
}
|
|
1639
630
|
}
|
|
1640
631
|
return finalResult;
|
|
1641
632
|
}
|
|
1642
|
-
//
|
|
1643
|
-
|
|
1644
|
-
// ============================================================================
|
|
1645
|
-
async getJobStatus(id, options) {
|
|
1646
|
-
return this.http.get(`${this.baseUrl}/api/jobs/${id}`, {
|
|
1647
|
-
headers: this.authHeaders(options)
|
|
1648
|
-
}).json();
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Poll a job until it completes or fails
|
|
1652
|
-
* @param id - The job ID to poll
|
|
1653
|
-
* @param options - Polling options
|
|
1654
|
-
* @returns The final job status
|
|
1655
|
-
*/
|
|
1656
|
-
async pollJobUntilComplete(id, options) {
|
|
1657
|
-
const interval = options?.interval ?? 1e3;
|
|
1658
|
-
const timeout = options?.timeout ?? 6e4;
|
|
1659
|
-
const startTime = Date.now();
|
|
1660
|
-
while (true) {
|
|
1661
|
-
const status = await this.getJobStatus(id, { auth: options?.auth });
|
|
1662
|
-
if (options?.onProgress) {
|
|
1663
|
-
options.onProgress(status);
|
|
1664
|
-
}
|
|
1665
|
-
if (status.status === "complete" || status.status === "failed" || status.status === "cancelled") {
|
|
1666
|
-
return status;
|
|
1667
|
-
}
|
|
1668
|
-
if (Date.now() - startTime > timeout) {
|
|
1669
|
-
throw new Error(`Job polling timeout after ${timeout}ms`);
|
|
1670
|
-
}
|
|
1671
|
-
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
// ============================================================================
|
|
1675
|
-
// SYSTEM STATUS
|
|
1676
|
-
// ============================================================================
|
|
1677
|
-
async healthCheck(options) {
|
|
633
|
+
// ── System status ─────────────────────────────────────────────────────
|
|
634
|
+
async healthCheck() {
|
|
1678
635
|
return this.http.get(`${this.baseUrl}/api/health`, {
|
|
1679
|
-
headers: this.authHeaders(
|
|
636
|
+
headers: this.authHeaders()
|
|
1680
637
|
}).json();
|
|
1681
638
|
}
|
|
1682
|
-
async getStatus(
|
|
639
|
+
async getStatus() {
|
|
1683
640
|
return this.http.get(`${this.baseUrl}/api/status`, {
|
|
1684
|
-
headers: this.authHeaders(
|
|
641
|
+
headers: this.authHeaders()
|
|
1685
642
|
}).json();
|
|
1686
643
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
644
|
+
// ── Internal: ky accessor for legacy passthroughs (temporary) ─────────
|
|
645
|
+
/**
|
|
646
|
+
* Temporary escape hatch for the ongoing transport migration: namespaces
|
|
647
|
+
* that still need to issue ad-hoc HTTP calls (e.g. legacy browse/mark
|
|
648
|
+
* HTTP fallbacks) can borrow the configured `ky` instance here. Will be
|
|
649
|
+
* deleted once all namespaces route through bus channels or through
|
|
650
|
+
* typed methods on this transport.
|
|
651
|
+
*/
|
|
652
|
+
get rawHttp() {
|
|
653
|
+
return this.http;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Current access token (synchronously read from the BehaviorSubject).
|
|
657
|
+
* Used by content-transport and legacy namespace HTTP fallbacks that
|
|
658
|
+
* need to pass `auth: token` through some code paths.
|
|
659
|
+
*/
|
|
660
|
+
getToken() {
|
|
661
|
+
return this.token$.getValue() ?? void 0;
|
|
1695
662
|
}
|
|
1696
663
|
};
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
664
|
+
var HttpContentTransport = class {
|
|
665
|
+
constructor(transport) {
|
|
666
|
+
this.transport = transport;
|
|
667
|
+
}
|
|
668
|
+
async putBinary(request, options) {
|
|
669
|
+
const sizeBytes = request.file instanceof File ? request.file.size : request.file.length;
|
|
670
|
+
busLog("PUT", "content", {
|
|
671
|
+
name: request.name,
|
|
672
|
+
format: request.format,
|
|
673
|
+
storageUri: request.storageUri,
|
|
674
|
+
sizeBytes
|
|
675
|
+
});
|
|
676
|
+
return withSpan(
|
|
677
|
+
"content.put",
|
|
678
|
+
async () => {
|
|
679
|
+
const formData = new FormData();
|
|
680
|
+
formData.append("name", request.name);
|
|
681
|
+
formData.append("format", request.format);
|
|
682
|
+
formData.append("storageUri", request.storageUri);
|
|
683
|
+
if (request.file instanceof File) {
|
|
684
|
+
formData.append("file", request.file);
|
|
685
|
+
} else if (Buffer.isBuffer(request.file)) {
|
|
686
|
+
const blob = new Blob([new Uint8Array(request.file)], { type: request.format });
|
|
687
|
+
formData.append("file", blob, request.name);
|
|
688
|
+
} else {
|
|
689
|
+
throw new Error("file must be a File or Buffer");
|
|
690
|
+
}
|
|
691
|
+
if (request.entityTypes && request.entityTypes.length > 0) {
|
|
692
|
+
formData.append("entityTypes", JSON.stringify(request.entityTypes));
|
|
693
|
+
}
|
|
694
|
+
if (request.language) formData.append("language", request.language);
|
|
695
|
+
if (request.creationMethod) formData.append("creationMethod", String(request.creationMethod));
|
|
696
|
+
if (request.sourceAnnotationId) formData.append("sourceAnnotationId", String(request.sourceAnnotationId));
|
|
697
|
+
if (request.sourceResourceId) formData.append("sourceResourceId", String(request.sourceResourceId));
|
|
698
|
+
if (request.generationPrompt) formData.append("generationPrompt", request.generationPrompt);
|
|
699
|
+
if (request.generator) formData.append("generator", JSON.stringify(request.generator));
|
|
700
|
+
if (request.isDraft !== void 0) formData.append("isDraft", String(request.isDraft));
|
|
701
|
+
const result = await this.transport.rawHttp.post(`${this.transport.baseUrl}/resources`, {
|
|
702
|
+
body: formData,
|
|
703
|
+
headers: this.requestHeaders(options?.auth)
|
|
704
|
+
}).json();
|
|
705
|
+
return { resourceId: result.resourceId };
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
kind: SpanKind.CLIENT,
|
|
709
|
+
attrs: {
|
|
710
|
+
"content.format": request.format,
|
|
711
|
+
"content.size_bytes": sizeBytes
|
|
1705
712
|
}
|
|
1706
713
|
}
|
|
1707
|
-
|
|
1708
|
-
return null;
|
|
1709
|
-
}
|
|
1710
|
-
if (typeof body === "object" && body !== null && "type" in body && "source" in body) {
|
|
1711
|
-
const bodyType = body.type;
|
|
1712
|
-
const bodySource = body.source;
|
|
1713
|
-
if (bodyType === "SpecificResource" && typeof bodySource === "string") {
|
|
1714
|
-
return bodySource;
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
return null;
|
|
1718
|
-
}
|
|
1719
|
-
function getBodyType(body) {
|
|
1720
|
-
if (Array.isArray(body)) {
|
|
1721
|
-
if (body.length === 0) {
|
|
1722
|
-
return null;
|
|
1723
|
-
}
|
|
1724
|
-
if (typeof body[0] === "object" && body[0] !== null && "type" in body[0]) {
|
|
1725
|
-
const firstType = body[0].type;
|
|
1726
|
-
if (firstType === "TextualBody" || firstType === "SpecificResource") {
|
|
1727
|
-
return firstType;
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
return null;
|
|
1731
|
-
}
|
|
1732
|
-
if (typeof body === "object" && body !== null && "type" in body) {
|
|
1733
|
-
const bodyType = body.type;
|
|
1734
|
-
if (bodyType === "TextualBody" || bodyType === "SpecificResource") {
|
|
1735
|
-
return bodyType;
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
return null;
|
|
1739
|
-
}
|
|
1740
|
-
function isBodyResolved(body) {
|
|
1741
|
-
return getBodySource(body) !== null;
|
|
1742
|
-
}
|
|
1743
|
-
function getTargetSource(target) {
|
|
1744
|
-
if (typeof target === "string") {
|
|
1745
|
-
return target;
|
|
1746
|
-
}
|
|
1747
|
-
return target.source;
|
|
1748
|
-
}
|
|
1749
|
-
function getTargetSelector(target) {
|
|
1750
|
-
if (typeof target === "string") {
|
|
1751
|
-
return void 0;
|
|
1752
|
-
}
|
|
1753
|
-
return target.selector;
|
|
1754
|
-
}
|
|
1755
|
-
function hasTargetSelector(target) {
|
|
1756
|
-
return typeof target !== "string" && target.selector !== void 0;
|
|
1757
|
-
}
|
|
1758
|
-
function isHighlight(annotation) {
|
|
1759
|
-
return annotation.motivation === "highlighting";
|
|
1760
|
-
}
|
|
1761
|
-
function isReference(annotation) {
|
|
1762
|
-
return annotation.motivation === "linking";
|
|
1763
|
-
}
|
|
1764
|
-
function isAssessment(annotation) {
|
|
1765
|
-
return annotation.motivation === "assessing";
|
|
1766
|
-
}
|
|
1767
|
-
function isComment(annotation) {
|
|
1768
|
-
return annotation.motivation === "commenting";
|
|
1769
|
-
}
|
|
1770
|
-
function isTag(annotation) {
|
|
1771
|
-
return annotation.motivation === "tagging";
|
|
1772
|
-
}
|
|
1773
|
-
function getCommentText(annotation) {
|
|
1774
|
-
if (!isComment(annotation)) return void 0;
|
|
1775
|
-
const body = Array.isArray(annotation.body) ? annotation.body[0] : annotation.body;
|
|
1776
|
-
if (body && "value" in body) {
|
|
1777
|
-
return body.value;
|
|
1778
|
-
}
|
|
1779
|
-
return void 0;
|
|
1780
|
-
}
|
|
1781
|
-
function isStubReference(annotation) {
|
|
1782
|
-
return isReference(annotation) && !isBodyResolved(annotation.body);
|
|
1783
|
-
}
|
|
1784
|
-
function isResolvedReference(annotation) {
|
|
1785
|
-
return isReference(annotation) && isBodyResolved(annotation.body);
|
|
1786
|
-
}
|
|
1787
|
-
function getExactText(selector) {
|
|
1788
|
-
if (!selector) {
|
|
1789
|
-
return "";
|
|
1790
|
-
}
|
|
1791
|
-
const selectors = Array.isArray(selector) ? selector : [selector];
|
|
1792
|
-
const quoteSelector = selectors.find((s) => s.type === "TextQuoteSelector");
|
|
1793
|
-
if (quoteSelector) {
|
|
1794
|
-
return quoteSelector.exact;
|
|
1795
|
-
}
|
|
1796
|
-
return "";
|
|
1797
|
-
}
|
|
1798
|
-
function getAnnotationExactText(annotation) {
|
|
1799
|
-
const selector = getTargetSelector(annotation.target);
|
|
1800
|
-
return getExactText(selector);
|
|
1801
|
-
}
|
|
1802
|
-
function getPrimarySelector(selector) {
|
|
1803
|
-
if (Array.isArray(selector)) {
|
|
1804
|
-
if (selector.length === 0) {
|
|
1805
|
-
throw new Error("Empty selector array");
|
|
1806
|
-
}
|
|
1807
|
-
const first = selector[0];
|
|
1808
|
-
if (!first) {
|
|
1809
|
-
throw new Error("Invalid selector array");
|
|
1810
|
-
}
|
|
1811
|
-
return first;
|
|
1812
|
-
}
|
|
1813
|
-
return selector;
|
|
1814
|
-
}
|
|
1815
|
-
function getTextQuoteSelector(selector) {
|
|
1816
|
-
const selectors = Array.isArray(selector) ? selector : [selector];
|
|
1817
|
-
const found = selectors.find((s) => s.type === "TextQuoteSelector");
|
|
1818
|
-
if (!found) return null;
|
|
1819
|
-
return found.type === "TextQuoteSelector" ? found : null;
|
|
1820
|
-
}
|
|
1821
|
-
function extractBoundingBox(svg) {
|
|
1822
|
-
const viewBoxMatch = svg.match(/<svg[^>]*viewBox="([^"]+)"/);
|
|
1823
|
-
if (viewBoxMatch) {
|
|
1824
|
-
const values = viewBoxMatch[1].split(/\s+/).map(parseFloat);
|
|
1825
|
-
if (values.length === 4 && values.every((v) => !isNaN(v))) {
|
|
1826
|
-
return {
|
|
1827
|
-
x: values[0],
|
|
1828
|
-
y: values[1],
|
|
1829
|
-
width: values[2],
|
|
1830
|
-
height: values[3]
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
const svgTagMatch = svg.match(/<svg[^>]*>/);
|
|
1835
|
-
if (svgTagMatch) {
|
|
1836
|
-
const svgTag = svgTagMatch[0];
|
|
1837
|
-
const widthMatch = svgTag.match(/width="([^"]+)"/);
|
|
1838
|
-
const heightMatch = svgTag.match(/height="([^"]+)"/);
|
|
1839
|
-
if (widthMatch && heightMatch) {
|
|
1840
|
-
const width = parseFloat(widthMatch[1]);
|
|
1841
|
-
const height = parseFloat(heightMatch[1]);
|
|
1842
|
-
if (!isNaN(width) && !isNaN(height)) {
|
|
1843
|
-
return { x: 0, y: 0, width, height };
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
return null;
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
// src/utils/fuzzy-anchor.ts
|
|
1851
|
-
function normalizeText(text) {
|
|
1852
|
-
return text.replace(/\s+/g, " ").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"').replace(/\u2014/g, "--").replace(/\u2013/g, "-").trim();
|
|
1853
|
-
}
|
|
1854
|
-
function levenshteinDistance(str1, str2) {
|
|
1855
|
-
const len1 = str1.length;
|
|
1856
|
-
const len2 = str2.length;
|
|
1857
|
-
const matrix = [];
|
|
1858
|
-
for (let i = 0; i <= len1; i++) {
|
|
1859
|
-
matrix[i] = [i];
|
|
1860
|
-
}
|
|
1861
|
-
for (let j = 0; j <= len2; j++) {
|
|
1862
|
-
matrix[0][j] = j;
|
|
1863
|
-
}
|
|
1864
|
-
for (let i = 1; i <= len1; i++) {
|
|
1865
|
-
for (let j = 1; j <= len2; j++) {
|
|
1866
|
-
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
1867
|
-
const deletion = matrix[i - 1][j] + 1;
|
|
1868
|
-
const insertion = matrix[i][j - 1] + 1;
|
|
1869
|
-
const substitution = matrix[i - 1][j - 1] + cost;
|
|
1870
|
-
matrix[i][j] = Math.min(deletion, insertion, substitution);
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
return matrix[len1][len2];
|
|
1874
|
-
}
|
|
1875
|
-
function buildContentCache(content) {
|
|
1876
|
-
return {
|
|
1877
|
-
normalizedContent: normalizeText(content),
|
|
1878
|
-
lowerContent: content.toLowerCase()
|
|
1879
|
-
};
|
|
1880
|
-
}
|
|
1881
|
-
function findBestTextMatch(content, searchText, positionHint, cache) {
|
|
1882
|
-
const maxFuzzyDistance = Math.max(5, Math.floor(searchText.length * 0.05));
|
|
1883
|
-
const exactIndex = content.indexOf(searchText);
|
|
1884
|
-
if (exactIndex !== -1) {
|
|
1885
|
-
return {
|
|
1886
|
-
start: exactIndex,
|
|
1887
|
-
end: exactIndex + searchText.length,
|
|
1888
|
-
matchQuality: "exact"
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
const normalizedSearch = normalizeText(searchText);
|
|
1892
|
-
const normalizedIndex = cache.normalizedContent.indexOf(normalizedSearch);
|
|
1893
|
-
if (normalizedIndex !== -1) {
|
|
1894
|
-
let actualPos = 0;
|
|
1895
|
-
let normalizedPos = 0;
|
|
1896
|
-
while (normalizedPos < normalizedIndex && actualPos < content.length) {
|
|
1897
|
-
const char = content[actualPos];
|
|
1898
|
-
const normalizedChar = normalizeText(char);
|
|
1899
|
-
if (normalizedChar) {
|
|
1900
|
-
normalizedPos += normalizedChar.length;
|
|
1901
|
-
}
|
|
1902
|
-
actualPos++;
|
|
1903
|
-
}
|
|
1904
|
-
return {
|
|
1905
|
-
start: actualPos,
|
|
1906
|
-
end: actualPos + searchText.length,
|
|
1907
|
-
matchQuality: "normalized"
|
|
1908
|
-
};
|
|
1909
|
-
}
|
|
1910
|
-
const lowerSearch = searchText.toLowerCase();
|
|
1911
|
-
const caseInsensitiveIndex = cache.lowerContent.indexOf(lowerSearch);
|
|
1912
|
-
if (caseInsensitiveIndex !== -1) {
|
|
1913
|
-
return {
|
|
1914
|
-
start: caseInsensitiveIndex,
|
|
1915
|
-
end: caseInsensitiveIndex + searchText.length,
|
|
1916
|
-
matchQuality: "case-insensitive"
|
|
1917
|
-
};
|
|
1918
|
-
}
|
|
1919
|
-
const windowSize = searchText.length;
|
|
1920
|
-
const searchRadius = Math.min(500, content.length);
|
|
1921
|
-
const searchStart = positionHint !== void 0 ? Math.max(0, positionHint - searchRadius) : 0;
|
|
1922
|
-
const searchEnd = positionHint !== void 0 ? Math.min(content.length, positionHint + searchRadius) : content.length;
|
|
1923
|
-
let bestMatch = null;
|
|
1924
|
-
for (let i = searchStart; i <= searchEnd - windowSize; i++) {
|
|
1925
|
-
const candidate = content.substring(i, i + windowSize);
|
|
1926
|
-
const distance = levenshteinDistance(searchText, candidate);
|
|
1927
|
-
if (distance <= maxFuzzyDistance) {
|
|
1928
|
-
if (!bestMatch || distance < bestMatch.distance) {
|
|
1929
|
-
bestMatch = { start: i, distance };
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
if (bestMatch) {
|
|
1934
|
-
return {
|
|
1935
|
-
start: bestMatch.start,
|
|
1936
|
-
end: bestMatch.start + windowSize,
|
|
1937
|
-
matchQuality: "fuzzy"
|
|
1938
|
-
};
|
|
1939
|
-
}
|
|
1940
|
-
return null;
|
|
1941
|
-
}
|
|
1942
|
-
function findTextWithContext(content, exact, prefix, suffix, positionHint, cache) {
|
|
1943
|
-
if (!exact) return null;
|
|
1944
|
-
if (positionHint !== void 0 && positionHint >= 0 && positionHint + exact.length <= content.length) {
|
|
1945
|
-
if (content.substring(positionHint, positionHint + exact.length) === exact) {
|
|
1946
|
-
return { start: positionHint, end: positionHint + exact.length };
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
const occurrences = [];
|
|
1950
|
-
let index = content.indexOf(exact);
|
|
1951
|
-
while (index !== -1) {
|
|
1952
|
-
occurrences.push(index);
|
|
1953
|
-
index = content.indexOf(exact, index + 1);
|
|
1954
|
-
}
|
|
1955
|
-
if (occurrences.length === 0) {
|
|
1956
|
-
const fuzzyMatch = findBestTextMatch(content, exact, positionHint, cache);
|
|
1957
|
-
if (fuzzyMatch) {
|
|
1958
|
-
return { start: fuzzyMatch.start, end: fuzzyMatch.end };
|
|
1959
|
-
}
|
|
1960
|
-
return null;
|
|
1961
|
-
}
|
|
1962
|
-
if (occurrences.length === 1) {
|
|
1963
|
-
const pos2 = occurrences[0];
|
|
1964
|
-
return { start: pos2, end: pos2 + exact.length };
|
|
1965
|
-
}
|
|
1966
|
-
if (prefix || suffix) {
|
|
1967
|
-
for (const pos2 of occurrences) {
|
|
1968
|
-
const actualPrefixStart = Math.max(0, pos2 - (prefix?.length || 0));
|
|
1969
|
-
const actualPrefix = content.substring(actualPrefixStart, pos2);
|
|
1970
|
-
const actualSuffixEnd = Math.min(content.length, pos2 + exact.length + (suffix?.length || 0));
|
|
1971
|
-
const actualSuffix = content.substring(pos2 + exact.length, actualSuffixEnd);
|
|
1972
|
-
const prefixMatch = !prefix || actualPrefix.endsWith(prefix);
|
|
1973
|
-
const suffixMatch = !suffix || actualSuffix.startsWith(suffix);
|
|
1974
|
-
if (prefixMatch && suffixMatch) {
|
|
1975
|
-
return { start: pos2, end: pos2 + exact.length };
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
for (const pos2 of occurrences) {
|
|
1979
|
-
const actualPrefix = content.substring(Math.max(0, pos2 - (prefix?.length || 0)), pos2);
|
|
1980
|
-
const actualSuffix = content.substring(pos2 + exact.length, pos2 + exact.length + (suffix?.length || 0));
|
|
1981
|
-
const fuzzyPrefixMatch = !prefix || actualPrefix.includes(prefix.trim());
|
|
1982
|
-
const fuzzySuffixMatch = !suffix || actualSuffix.includes(suffix.trim());
|
|
1983
|
-
if (fuzzyPrefixMatch && fuzzySuffixMatch) {
|
|
1984
|
-
return { start: pos2, end: pos2 + exact.length };
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
const pos = occurrences[0];
|
|
1989
|
-
return { start: pos, end: pos + exact.length };
|
|
1990
|
-
}
|
|
1991
|
-
function verifyPosition(content, position, expectedExact) {
|
|
1992
|
-
const actualText = content.substring(position.start, position.end);
|
|
1993
|
-
return actualText === expectedExact;
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
// src/utils/locales.ts
|
|
1997
|
-
var LOCALES = [
|
|
1998
|
-
{ code: "ar", nativeName: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", englishName: "Arabic" },
|
|
1999
|
-
{ code: "bn", nativeName: "\u09AC\u09BE\u0982\u09B2\u09BE", englishName: "Bengali" },
|
|
2000
|
-
{ code: "cs", nativeName: "\u010Ce\u0161tina", englishName: "Czech" },
|
|
2001
|
-
{ code: "da", nativeName: "Dansk", englishName: "Danish" },
|
|
2002
|
-
{ code: "de", nativeName: "Deutsch", englishName: "German" },
|
|
2003
|
-
{ code: "el", nativeName: "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC", englishName: "Greek" },
|
|
2004
|
-
{ code: "en", nativeName: "English", englishName: "English" },
|
|
2005
|
-
{ code: "es", nativeName: "Espa\xF1ol", englishName: "Spanish" },
|
|
2006
|
-
{ code: "fa", nativeName: "\u0641\u0627\u0631\u0633\u06CC", englishName: "Persian" },
|
|
2007
|
-
{ code: "fi", nativeName: "Suomi", englishName: "Finnish" },
|
|
2008
|
-
{ code: "fr", nativeName: "Fran\xE7ais", englishName: "French" },
|
|
2009
|
-
{ code: "he", nativeName: "\u05E2\u05D1\u05E8\u05D9\u05EA", englishName: "Hebrew" },
|
|
2010
|
-
{ code: "hi", nativeName: "\u0939\u093F\u0928\u094D\u0926\u0940", englishName: "Hindi" },
|
|
2011
|
-
{ code: "id", nativeName: "Bahasa Indonesia", englishName: "Indonesian" },
|
|
2012
|
-
{ code: "it", nativeName: "Italiano", englishName: "Italian" },
|
|
2013
|
-
{ code: "ja", nativeName: "\u65E5\u672C\u8A9E", englishName: "Japanese" },
|
|
2014
|
-
{ code: "ko", nativeName: "\uD55C\uAD6D\uC5B4", englishName: "Korean" },
|
|
2015
|
-
{ code: "ms", nativeName: "Bahasa Melayu", englishName: "Malay" },
|
|
2016
|
-
{ code: "nl", nativeName: "Nederlands", englishName: "Dutch" },
|
|
2017
|
-
{ code: "no", nativeName: "Norsk", englishName: "Norwegian" },
|
|
2018
|
-
{ code: "pl", nativeName: "Polski", englishName: "Polish" },
|
|
2019
|
-
{ code: "pt", nativeName: "Portugu\xEAs", englishName: "Portuguese" },
|
|
2020
|
-
{ code: "ro", nativeName: "Rom\xE2n\u0103", englishName: "Romanian" },
|
|
2021
|
-
{ code: "sv", nativeName: "Svenska", englishName: "Swedish" },
|
|
2022
|
-
{ code: "th", nativeName: "\u0E44\u0E17\u0E22", englishName: "Thai" },
|
|
2023
|
-
{ code: "tr", nativeName: "T\xFCrk\xE7e", englishName: "Turkish" },
|
|
2024
|
-
{ code: "uk", nativeName: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", englishName: "Ukrainian" },
|
|
2025
|
-
{ code: "vi", nativeName: "Ti\u1EBFng Vi\u1EC7t", englishName: "Vietnamese" },
|
|
2026
|
-
{ code: "zh", nativeName: "\u4E2D\u6587", englishName: "Chinese" }
|
|
2027
|
-
];
|
|
2028
|
-
var localeByCode = new Map(
|
|
2029
|
-
LOCALES.map((locale) => [locale.code.toLowerCase(), locale])
|
|
2030
|
-
);
|
|
2031
|
-
function getLocaleInfo(code) {
|
|
2032
|
-
if (!code) return void 0;
|
|
2033
|
-
return localeByCode.get(code.toLowerCase());
|
|
2034
|
-
}
|
|
2035
|
-
function getLocaleNativeName(code) {
|
|
2036
|
-
return getLocaleInfo(code)?.nativeName;
|
|
2037
|
-
}
|
|
2038
|
-
function getLocaleEnglishName(code) {
|
|
2039
|
-
return getLocaleInfo(code)?.englishName;
|
|
2040
|
-
}
|
|
2041
|
-
function formatLocaleDisplay(code) {
|
|
2042
|
-
if (!code) return void 0;
|
|
2043
|
-
const info = getLocaleInfo(code);
|
|
2044
|
-
if (!info) return code;
|
|
2045
|
-
return `${info.nativeName} (${code.toLowerCase()})`;
|
|
2046
|
-
}
|
|
2047
|
-
function getAllLocaleCodes() {
|
|
2048
|
-
return LOCALES.map((l) => l.code);
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// src/utils/resources.ts
|
|
2052
|
-
function getResourceId(resource) {
|
|
2053
|
-
if (!resource) return void 0;
|
|
2054
|
-
return resource["@id"] || void 0;
|
|
2055
|
-
}
|
|
2056
|
-
function getPrimaryRepresentation(resource) {
|
|
2057
|
-
if (!resource?.representations) return void 0;
|
|
2058
|
-
const reps = Array.isArray(resource.representations) ? resource.representations : [resource.representations];
|
|
2059
|
-
return reps[0];
|
|
2060
|
-
}
|
|
2061
|
-
function getPrimaryMediaType(resource) {
|
|
2062
|
-
return getPrimaryRepresentation(resource)?.mediaType;
|
|
2063
|
-
}
|
|
2064
|
-
function getChecksum(resource) {
|
|
2065
|
-
return getPrimaryRepresentation(resource)?.checksum;
|
|
2066
|
-
}
|
|
2067
|
-
function getLanguage(resource) {
|
|
2068
|
-
return getPrimaryRepresentation(resource)?.language;
|
|
2069
|
-
}
|
|
2070
|
-
function getStorageUri(resource) {
|
|
2071
|
-
return getPrimaryRepresentation(resource)?.storageUri;
|
|
2072
|
-
}
|
|
2073
|
-
function getCreator(resource) {
|
|
2074
|
-
if (!resource?.wasAttributedTo) return void 0;
|
|
2075
|
-
return Array.isArray(resource.wasAttributedTo) ? resource.wasAttributedTo[0] : resource.wasAttributedTo;
|
|
2076
|
-
}
|
|
2077
|
-
function getDerivedFrom(resource) {
|
|
2078
|
-
if (!resource?.wasDerivedFrom) return void 0;
|
|
2079
|
-
return Array.isArray(resource.wasDerivedFrom) ? resource.wasDerivedFrom[0] : resource.wasDerivedFrom;
|
|
2080
|
-
}
|
|
2081
|
-
function isArchived(resource) {
|
|
2082
|
-
return resource?.archived === true;
|
|
2083
|
-
}
|
|
2084
|
-
function getResourceEntityTypes(resource) {
|
|
2085
|
-
return resource?.entityTypes || [];
|
|
2086
|
-
}
|
|
2087
|
-
function isDraft(resource) {
|
|
2088
|
-
return resource?.isDraft === true;
|
|
2089
|
-
}
|
|
2090
|
-
function getNodeEncoding(charset) {
|
|
2091
|
-
const normalized = charset.toLowerCase().replace(/[-_]/g, "");
|
|
2092
|
-
const charsetMap = {
|
|
2093
|
-
"utf8": "utf8",
|
|
2094
|
-
"iso88591": "latin1",
|
|
2095
|
-
"latin1": "latin1",
|
|
2096
|
-
"ascii": "ascii",
|
|
2097
|
-
"usascii": "ascii",
|
|
2098
|
-
"utf16le": "utf16le",
|
|
2099
|
-
"ucs2": "ucs2",
|
|
2100
|
-
"binary": "binary",
|
|
2101
|
-
"windows1252": "latin1",
|
|
2102
|
-
// Windows-1252 is a superset of Latin-1
|
|
2103
|
-
"cp1252": "latin1"
|
|
2104
|
-
};
|
|
2105
|
-
return charsetMap[normalized] || "utf8";
|
|
2106
|
-
}
|
|
2107
|
-
function decodeRepresentation(buffer, mediaType) {
|
|
2108
|
-
const charsetMatch = mediaType.match(/charset=([^\s;]+)/i);
|
|
2109
|
-
const charset = (charsetMatch?.[1] || "utf-8").toLowerCase();
|
|
2110
|
-
const encoding = getNodeEncoding(charset);
|
|
2111
|
-
return buffer.toString(encoding);
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
// src/utils/svg-utils.ts
|
|
2115
|
-
function createRectangleSvg(start, end) {
|
|
2116
|
-
const x = Math.min(start.x, end.x);
|
|
2117
|
-
const y = Math.min(start.y, end.y);
|
|
2118
|
-
const width = Math.abs(end.x - start.x);
|
|
2119
|
-
const height = Math.abs(end.y - start.y);
|
|
2120
|
-
return `<svg xmlns="http://www.w3.org/2000/svg"><rect x="${x}" y="${y}" width="${width}" height="${height}"/></svg>`;
|
|
2121
|
-
}
|
|
2122
|
-
function createPolygonSvg(points) {
|
|
2123
|
-
if (points.length < 3) {
|
|
2124
|
-
throw new Error("Polygon requires at least 3 points");
|
|
2125
|
-
}
|
|
2126
|
-
const pointsStr = points.map((p) => `${p.x},${p.y}`).join(" ");
|
|
2127
|
-
return `<svg xmlns="http://www.w3.org/2000/svg"><polygon points="${pointsStr}"/></svg>`;
|
|
2128
|
-
}
|
|
2129
|
-
function createCircleSvg(center, radius) {
|
|
2130
|
-
if (radius <= 0) {
|
|
2131
|
-
throw new Error("Circle radius must be positive");
|
|
2132
|
-
}
|
|
2133
|
-
return `<svg xmlns="http://www.w3.org/2000/svg"><circle cx="${center.x}" cy="${center.y}" r="${radius}"/></svg>`;
|
|
2134
|
-
}
|
|
2135
|
-
function parseSvgSelector(svg) {
|
|
2136
|
-
const rectMatch = svg.match(/<rect\s+([^>]+)\/>/);
|
|
2137
|
-
if (rectMatch && rectMatch[1]) {
|
|
2138
|
-
const attrs = rectMatch[1];
|
|
2139
|
-
const x = parseFloat(attrs.match(/x="([^"]+)"/)?.[1] || "0");
|
|
2140
|
-
const y = parseFloat(attrs.match(/y="([^"]+)"/)?.[1] || "0");
|
|
2141
|
-
const width = parseFloat(attrs.match(/width="([^"]+)"/)?.[1] || "0");
|
|
2142
|
-
const height = parseFloat(attrs.match(/height="([^"]+)"/)?.[1] || "0");
|
|
2143
|
-
return {
|
|
2144
|
-
type: "rect",
|
|
2145
|
-
data: { x, y, width, height }
|
|
2146
|
-
};
|
|
2147
|
-
}
|
|
2148
|
-
const polygonMatch = svg.match(/<polygon\s+points="([^"]+)"/);
|
|
2149
|
-
if (polygonMatch && polygonMatch[1]) {
|
|
2150
|
-
const pointsStr = polygonMatch[1];
|
|
2151
|
-
const points = pointsStr.split(/\s+/).map((pair) => {
|
|
2152
|
-
const [x, y] = pair.split(",").map(parseFloat);
|
|
2153
|
-
return { x, y };
|
|
2154
|
-
});
|
|
2155
|
-
return {
|
|
2156
|
-
type: "polygon",
|
|
2157
|
-
data: { points }
|
|
2158
|
-
};
|
|
2159
|
-
}
|
|
2160
|
-
const circleMatch = svg.match(/<circle\s+([^>]+)\/>/);
|
|
2161
|
-
if (circleMatch && circleMatch[1]) {
|
|
2162
|
-
const attrs = circleMatch[1];
|
|
2163
|
-
const cx = parseFloat(attrs.match(/cx="([^"]+)"/)?.[1] || "0");
|
|
2164
|
-
const cy = parseFloat(attrs.match(/cy="([^"]+)"/)?.[1] || "0");
|
|
2165
|
-
const r = parseFloat(attrs.match(/r="([^"]+)"/)?.[1] || "0");
|
|
2166
|
-
return {
|
|
2167
|
-
type: "circle",
|
|
2168
|
-
data: { cx, cy, r }
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
return null;
|
|
2172
|
-
}
|
|
2173
|
-
function normalizeCoordinates(point, displayWidth, displayHeight, imageWidth, imageHeight) {
|
|
2174
|
-
return {
|
|
2175
|
-
x: point.x / displayWidth * imageWidth,
|
|
2176
|
-
y: point.y / displayHeight * imageHeight
|
|
2177
|
-
};
|
|
2178
|
-
}
|
|
2179
|
-
function scaleSvgToNative(svg, displayWidth, displayHeight, imageWidth, imageHeight) {
|
|
2180
|
-
const parsed = parseSvgSelector(svg);
|
|
2181
|
-
if (!parsed) return svg;
|
|
2182
|
-
const scaleX = imageWidth / displayWidth;
|
|
2183
|
-
const scaleY = imageHeight / displayHeight;
|
|
2184
|
-
switch (parsed.type) {
|
|
2185
|
-
case "rect": {
|
|
2186
|
-
const { x, y, width, height } = parsed.data;
|
|
2187
|
-
return createRectangleSvg(
|
|
2188
|
-
{ x: x * scaleX, y: y * scaleY },
|
|
2189
|
-
{ x: (x + width) * scaleX, y: (y + height) * scaleY }
|
|
2190
|
-
);
|
|
2191
|
-
}
|
|
2192
|
-
case "circle": {
|
|
2193
|
-
const { cx, cy, r } = parsed.data;
|
|
2194
|
-
return createCircleSvg(
|
|
2195
|
-
{ x: cx * scaleX, y: cy * scaleY },
|
|
2196
|
-
r * Math.min(scaleX, scaleY)
|
|
2197
|
-
);
|
|
2198
|
-
}
|
|
2199
|
-
case "polygon": {
|
|
2200
|
-
const points = parsed.data.points.map((p) => ({
|
|
2201
|
-
x: p.x * scaleX,
|
|
2202
|
-
y: p.y * scaleY
|
|
2203
|
-
}));
|
|
2204
|
-
return createPolygonSvg(points);
|
|
2205
|
-
}
|
|
714
|
+
);
|
|
2206
715
|
}
|
|
2207
|
-
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
extensionCount++;
|
|
2225
|
-
}
|
|
2226
|
-
prefix = content.substring(prefixStart, start);
|
|
716
|
+
async getBinary(resourceId, options) {
|
|
717
|
+
busLog("GET", "content", { resourceId, accept: options?.accept });
|
|
718
|
+
return withSpan(
|
|
719
|
+
"content.get",
|
|
720
|
+
async () => {
|
|
721
|
+
const response = await this.transport.rawHttp.get(`${this.transport.baseUrl}/resources/${resourceId}`, {
|
|
722
|
+
headers: {
|
|
723
|
+
Accept: options?.accept ?? "text/plain",
|
|
724
|
+
...this.requestHeaders(options?.auth)
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
728
|
+
const data = await response.arrayBuffer();
|
|
729
|
+
return { data, contentType };
|
|
730
|
+
},
|
|
731
|
+
{ kind: SpanKind.CLIENT, attrs: { "resource.id": resourceId } }
|
|
732
|
+
);
|
|
2227
733
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
734
|
+
async getBinaryStream(resourceId, options) {
|
|
735
|
+
busLog("GET", "content", { resourceId, accept: options?.accept, stream: true });
|
|
736
|
+
return withSpan(
|
|
737
|
+
"content.get",
|
|
738
|
+
async () => {
|
|
739
|
+
const response = await this.transport.rawHttp.get(`${this.transport.baseUrl}/resources/${resourceId}`, {
|
|
740
|
+
headers: {
|
|
741
|
+
Accept: options?.accept ?? "text/plain",
|
|
742
|
+
...this.requestHeaders(options?.auth)
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
746
|
+
if (!response.body) {
|
|
747
|
+
throw new Error("Response body is null - cannot create stream");
|
|
748
|
+
}
|
|
749
|
+
return { stream: response.body, contentType };
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
kind: SpanKind.CLIENT,
|
|
753
|
+
attrs: { "resource.id": resourceId, "content.stream": true }
|
|
2236
754
|
}
|
|
2237
|
-
suffixEnd++;
|
|
2238
|
-
extensionCount++;
|
|
2239
|
-
}
|
|
2240
|
-
suffix = content.substring(end, suffixEnd);
|
|
2241
|
-
}
|
|
2242
|
-
return { prefix, suffix };
|
|
2243
|
-
}
|
|
2244
|
-
function validateAndCorrectOffsets(content, aiStart, aiEnd, exact) {
|
|
2245
|
-
const textAtOffset = content.substring(aiStart, aiEnd);
|
|
2246
|
-
if (textAtOffset === exact) {
|
|
2247
|
-
const context2 = extractContext(content, aiStart, aiEnd);
|
|
2248
|
-
return {
|
|
2249
|
-
start: aiStart,
|
|
2250
|
-
end: aiEnd,
|
|
2251
|
-
exact,
|
|
2252
|
-
prefix: context2.prefix,
|
|
2253
|
-
suffix: context2.suffix,
|
|
2254
|
-
corrected: false,
|
|
2255
|
-
matchQuality: "exact"
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2258
|
-
const cache = buildContentCache(content);
|
|
2259
|
-
const match = findBestTextMatch(content, exact, aiStart, cache);
|
|
2260
|
-
if (!match) {
|
|
2261
|
-
throw new Error(
|
|
2262
|
-
"Cannot find acceptable match for text in content. All search strategies failed. Text may be hallucinated."
|
|
2263
755
|
);
|
|
2264
756
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
fuzzyMatched: match.matchQuality !== "exact",
|
|
2276
|
-
matchQuality: match.matchQuality
|
|
2277
|
-
};
|
|
2278
|
-
}
|
|
2279
|
-
|
|
2280
|
-
// src/utils/text-encoding.ts
|
|
2281
|
-
function extractCharset(mediaType) {
|
|
2282
|
-
const charsetMatch = mediaType.match(/charset=([^\s;]+)/i);
|
|
2283
|
-
return (charsetMatch?.[1] || "utf-8").toLowerCase();
|
|
2284
|
-
}
|
|
2285
|
-
function decodeWithCharset(buffer, mediaType) {
|
|
2286
|
-
const charset = extractCharset(mediaType);
|
|
2287
|
-
const decoder = new TextDecoder(charset);
|
|
2288
|
-
return decoder.decode(buffer);
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
// src/utils/validation.ts
|
|
2292
|
-
var JWTTokenSchema = {
|
|
2293
|
-
parse(token) {
|
|
2294
|
-
if (typeof token !== "string") {
|
|
2295
|
-
throw new Error("Token must be a string");
|
|
2296
|
-
}
|
|
2297
|
-
if (!token || token.length === 0) {
|
|
2298
|
-
throw new Error("Token is required");
|
|
2299
|
-
}
|
|
2300
|
-
const jwtRegex = /^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*$/;
|
|
2301
|
-
if (!jwtRegex.test(token)) {
|
|
2302
|
-
throw new Error("Invalid JWT token format");
|
|
2303
|
-
}
|
|
2304
|
-
return token;
|
|
2305
|
-
},
|
|
2306
|
-
safeParse(token) {
|
|
2307
|
-
try {
|
|
2308
|
-
const validated = this.parse(token);
|
|
2309
|
-
return { success: true, data: validated };
|
|
2310
|
-
} catch (error) {
|
|
2311
|
-
return {
|
|
2312
|
-
success: false,
|
|
2313
|
-
error: error instanceof Error ? error.message : "Invalid JWT token"
|
|
2314
|
-
};
|
|
757
|
+
dispose() {
|
|
758
|
+
}
|
|
759
|
+
/** Auth header + W3C trace propagation for the active span. */
|
|
760
|
+
requestHeaders(override) {
|
|
761
|
+
const token = override ?? this.transport.getToken();
|
|
762
|
+
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
|
763
|
+
const trace = getActiveTraceparent();
|
|
764
|
+
if (trace) {
|
|
765
|
+
headers["traceparent"] = trace.traceparent;
|
|
766
|
+
if (trace.tracestate) headers["tracestate"] = trace.tracestate;
|
|
2315
767
|
}
|
|
768
|
+
return headers;
|
|
2316
769
|
}
|
|
2317
770
|
};
|
|
2318
|
-
function validateData(schema, data) {
|
|
2319
|
-
try {
|
|
2320
|
-
const validated = schema.parse(data);
|
|
2321
|
-
return { success: true, data: validated };
|
|
2322
|
-
} catch (error) {
|
|
2323
|
-
return {
|
|
2324
|
-
success: false,
|
|
2325
|
-
error: error instanceof Error ? error.message : "Validation failed"
|
|
2326
|
-
};
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
function isValidEmail(email) {
|
|
2330
|
-
if (email.length < 1 || email.length > 255) {
|
|
2331
|
-
return false;
|
|
2332
|
-
}
|
|
2333
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2334
|
-
return emailRegex.test(email);
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
// src/mime-utils.ts
|
|
2338
|
-
function getExtensionForMimeType(mimeType) {
|
|
2339
|
-
const map3 = {
|
|
2340
|
-
"text/plain": "txt",
|
|
2341
|
-
"text/markdown": "md",
|
|
2342
|
-
"image/png": "png",
|
|
2343
|
-
"image/jpeg": "jpg",
|
|
2344
|
-
"application/pdf": "pdf"
|
|
2345
|
-
};
|
|
2346
|
-
return map3[mimeType] || "dat";
|
|
2347
|
-
}
|
|
2348
|
-
function isImageMimeType(mimeType) {
|
|
2349
|
-
return mimeType === "image/png" || mimeType === "image/jpeg";
|
|
2350
|
-
}
|
|
2351
|
-
function isTextMimeType(mimeType) {
|
|
2352
|
-
return mimeType === "text/plain" || mimeType === "text/markdown";
|
|
2353
|
-
}
|
|
2354
|
-
function isPdfMimeType(mimeType) {
|
|
2355
|
-
return mimeType === "application/pdf";
|
|
2356
|
-
}
|
|
2357
|
-
function getMimeCategory(mimeType) {
|
|
2358
|
-
if (isTextMimeType(mimeType)) {
|
|
2359
|
-
return "text";
|
|
2360
|
-
}
|
|
2361
|
-
if (isImageMimeType(mimeType) || isPdfMimeType(mimeType)) {
|
|
2362
|
-
return "image";
|
|
2363
|
-
}
|
|
2364
|
-
return "unsupported";
|
|
2365
|
-
}
|
|
2366
771
|
|
|
2367
|
-
export { APIError,
|
|
772
|
+
export { APIError, DEGRADED_THRESHOLD_MS, HttpContentTransport, HttpTransport, createActorVM };
|
|
2368
773
|
//# sourceMappingURL=index.js.map
|
|
2369
774
|
//# sourceMappingURL=index.js.map
|