@ixo/common 1.1.38 → 1.1.39
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/.turbo/turbo-build.log +1 -1
- package/dist/services/memory-engine/memory-engine.service.d.ts +9 -10
- package/dist/services/memory-engine/memory-engine.service.d.ts.map +1 -1
- package/dist/services/memory-engine/memory-engine.service.js +104 -89
- package/dist/services/memory-engine/memory-engine.service.js.map +1 -1
- package/dist/services/memory-engine/types.d.ts +16 -0
- package/dist/services/memory-engine/types.d.ts.map +1 -1
- package/dist/services/memory-engine/types.js +4 -1
- package/dist/services/memory-engine/types.js.map +1 -1
- package/package.json +3 -3
- package/src/services/memory-engine/memory-engine.service.ts +160 -209
- package/src/services/memory-engine/types.ts +34 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Logger } from '@ixo/logger';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
isBatchErrorSlot,
|
|
4
|
+
type SearchEnhancedBatchRequest,
|
|
5
|
+
type SearchEnhancedBatchResponse,
|
|
6
|
+
type SearchEnhancedRequest,
|
|
7
|
+
type SearchEnhancedResponse,
|
|
8
|
+
type UserContextData,
|
|
6
9
|
} from './types.js';
|
|
7
10
|
|
|
8
11
|
interface MemoryEngineAuthHeaders {
|
|
@@ -15,7 +18,9 @@ interface MemoryEngineAuthHeaders {
|
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export class MemoryEngineService {
|
|
18
|
-
|
|
21
|
+
// Batch covers 6 queries running in parallel server-side. Bound by the
|
|
22
|
+
// slowest query, not 6× — but we leave headroom for cold caches.
|
|
23
|
+
private readonly BATCH_TIMEOUT_MS = 15000;
|
|
19
24
|
|
|
20
25
|
constructor(private readonly memoryEngineUrl: string) {}
|
|
21
26
|
|
|
@@ -44,22 +49,6 @@ export class MemoryEngineService {
|
|
|
44
49
|
};
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
/**
|
|
48
|
-
* Wraps a promise with a timeout, returning fallback value if timeout is exceeded
|
|
49
|
-
*/
|
|
50
|
-
private async withTimeout<T>(
|
|
51
|
-
promise: Promise<T>,
|
|
52
|
-
timeoutMs: number,
|
|
53
|
-
fallback: T,
|
|
54
|
-
): Promise<T> {
|
|
55
|
-
return Promise.race([
|
|
56
|
-
promise,
|
|
57
|
-
new Promise<T>((resolve) =>
|
|
58
|
-
setTimeout(() => resolve(fallback), timeoutMs),
|
|
59
|
-
),
|
|
60
|
-
]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
52
|
/**
|
|
64
53
|
* Gather user context from Memory Engine by executing 6 parallel queries
|
|
65
54
|
*/
|
|
@@ -87,100 +76,156 @@ export class MemoryEngineService {
|
|
|
87
76
|
`[MemoryEngineService] Gathering user context for oracle: ${oracleDid}, room: ${roomId}`,
|
|
88
77
|
);
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
ucanInvocation,
|
|
98
|
-
};
|
|
79
|
+
const authHeaders: MemoryEngineAuthHeaders = {
|
|
80
|
+
oracleToken,
|
|
81
|
+
userToken,
|
|
82
|
+
oracleHomeServer,
|
|
83
|
+
userHomeServer,
|
|
84
|
+
ucanInvocation,
|
|
85
|
+
};
|
|
99
86
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
results[0].status === 'fulfilled' ? results[0].value : undefined;
|
|
136
|
-
const work =
|
|
137
|
-
results[1].status === 'fulfilled' ? results[1].value : undefined;
|
|
138
|
-
const goals =
|
|
139
|
-
results[2].status === 'fulfilled' ? results[2].value : undefined;
|
|
140
|
-
const interests =
|
|
141
|
-
results[3].status === 'fulfilled' ? results[3].value : undefined;
|
|
142
|
-
const relationships =
|
|
143
|
-
results[4].status === 'fulfilled' ? results[4].value : undefined;
|
|
144
|
-
const recent =
|
|
145
|
-
results[5].status === 'fulfilled' ? results[5].value : undefined;
|
|
146
|
-
|
|
147
|
-
// Log any failures
|
|
148
|
-
results.forEach((result, index) => {
|
|
149
|
-
if (result.status === 'rejected') {
|
|
87
|
+
// The 6 queries that make up userContext. Order matters: it determines
|
|
88
|
+
// how we map batch result slots back to UserContextData fields.
|
|
89
|
+
const labels = [
|
|
90
|
+
'identity',
|
|
91
|
+
'work',
|
|
92
|
+
'goals',
|
|
93
|
+
'interests',
|
|
94
|
+
'relationships',
|
|
95
|
+
'recent',
|
|
96
|
+
] as const;
|
|
97
|
+
const requests: SearchEnhancedRequest[] = [
|
|
98
|
+
this.buildIdentityRequest(oracleDid),
|
|
99
|
+
this.buildWorkRequest(oracleDid),
|
|
100
|
+
this.buildGoalsRequest(oracleDid),
|
|
101
|
+
this.buildInterestsRequest(oracleDid),
|
|
102
|
+
this.buildRelationshipsRequest(oracleDid),
|
|
103
|
+
this.buildRecentRequest(oracleDid),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
const gatherStart = Date.now();
|
|
107
|
+
const batch = await this.executeBatch(requests, roomId, authHeaders);
|
|
108
|
+
const gatherElapsed = Date.now() - gatherStart;
|
|
109
|
+
|
|
110
|
+
if (!batch) {
|
|
111
|
+
Logger.error(
|
|
112
|
+
`[MemoryEngineService] gatherUserContext failed after ${gatherElapsed}ms — returning empty context`,
|
|
113
|
+
);
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Map each slot back to the labelled field. Error slots become undefined.
|
|
118
|
+
const fields: (SearchEnhancedResponse | undefined)[] = batch.results.map(
|
|
119
|
+
(slot, index) => {
|
|
120
|
+
const label = labels[index];
|
|
121
|
+
if (isBatchErrorSlot(slot)) {
|
|
150
122
|
Logger.warn(
|
|
151
|
-
`[MemoryEngineService]
|
|
152
|
-
result.reason,
|
|
123
|
+
`[MemoryEngineService] Batch slot "${label}" failed (${slot.error.status_code}): ${slot.error.detail}`,
|
|
153
124
|
);
|
|
125
|
+
return undefined;
|
|
154
126
|
}
|
|
155
|
-
|
|
127
|
+
return slot;
|
|
128
|
+
},
|
|
129
|
+
);
|
|
156
130
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
goals,
|
|
161
|
-
interests,
|
|
162
|
-
relationships,
|
|
163
|
-
recent,
|
|
164
|
-
};
|
|
165
|
-
} catch (error) {
|
|
166
|
-
Logger.error(
|
|
167
|
-
'[MemoryEngineService] Failed to gather user context:',
|
|
168
|
-
error,
|
|
131
|
+
if (batch.results.length !== labels.length) {
|
|
132
|
+
Logger.warn(
|
|
133
|
+
`[MemoryEngineService] Batch length mismatch: expected ${labels.length}, got ${batch.results.length}`,
|
|
169
134
|
);
|
|
170
|
-
// Return empty context on error
|
|
171
|
-
return {};
|
|
172
135
|
}
|
|
136
|
+
|
|
137
|
+
const summary = labels.map((label, index) => {
|
|
138
|
+
const value = fields[index];
|
|
139
|
+
if (value === undefined) return `${label}=missing`;
|
|
140
|
+
return `${label}=ok(f${value.total_results.facts}/e${value.total_results.entities})`;
|
|
141
|
+
});
|
|
142
|
+
Logger.info(
|
|
143
|
+
`[MemoryEngineService] gatherUserContext completed in ${gatherElapsed}ms (batch) — ${summary.join(', ')}`,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
identity: fields[0],
|
|
148
|
+
work: fields[1],
|
|
149
|
+
goals: fields[2],
|
|
150
|
+
interests: fields[3],
|
|
151
|
+
relationships: fields[4],
|
|
152
|
+
recent: fields[5],
|
|
153
|
+
};
|
|
173
154
|
}
|
|
174
155
|
|
|
175
156
|
/**
|
|
176
|
-
*
|
|
157
|
+
* POST /search-enhanced-batch — single round-trip for N parallel queries.
|
|
158
|
+
* Returns undefined on transport/HTTP failure; a partially-failed batch
|
|
159
|
+
* still resolves with per-slot error markers (handled by caller via
|
|
160
|
+
* `isBatchErrorSlot`).
|
|
177
161
|
*/
|
|
178
|
-
private async
|
|
179
|
-
|
|
162
|
+
private async executeBatch(
|
|
163
|
+
queries: SearchEnhancedRequest[],
|
|
180
164
|
roomId: string,
|
|
181
165
|
auth: MemoryEngineAuthHeaders,
|
|
182
|
-
): Promise<
|
|
183
|
-
|
|
166
|
+
): Promise<SearchEnhancedBatchResponse | undefined> {
|
|
167
|
+
if (!roomId) {
|
|
168
|
+
Logger.warn(
|
|
169
|
+
`[MemoryEngineService] No room id provided, skipping batch search`,
|
|
170
|
+
);
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
if (!auth.ucanInvocation && (!auth.oracleToken || !auth.userToken)) {
|
|
174
|
+
Logger.warn(
|
|
175
|
+
`[MemoryEngineService] Missing auth (no UCAN and no Matrix tokens), skipping batch search`,
|
|
176
|
+
);
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const body: SearchEnhancedBatchRequest = { queries };
|
|
181
|
+
|
|
182
|
+
const controller = new AbortController();
|
|
183
|
+
const timer = setTimeout(
|
|
184
|
+
() => controller.abort(),
|
|
185
|
+
this.BATCH_TIMEOUT_MS,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const response = await fetch(
|
|
190
|
+
`${this.memoryEngineUrl}/search-enhanced-batch`,
|
|
191
|
+
{
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: this.buildHeaders(auth, roomId),
|
|
194
|
+
body: JSON.stringify(body),
|
|
195
|
+
signal: controller.signal,
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
const errorText = await response.text();
|
|
201
|
+
Logger.warn(
|
|
202
|
+
`[MemoryEngineService] Batch search failed (${response.status}): ${errorText}`,
|
|
203
|
+
);
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (await response.json()) as SearchEnhancedBatchResponse;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if ((error as Error).name === 'AbortError') {
|
|
210
|
+
Logger.warn(
|
|
211
|
+
`[MemoryEngineService] Batch search aborted after ${this.BATCH_TIMEOUT_MS}ms`,
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
Logger.error(`[MemoryEngineService] Batch search threw:`, error);
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
} finally {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Per-query request builders ────────────────────────────────────────────
|
|
223
|
+
// These produce SearchEnhancedRequest payloads consumed by gatherUserContext
|
|
224
|
+
// via the batch endpoint. Order matches the labels array in
|
|
225
|
+
// gatherUserContext — keep the two in sync.
|
|
226
|
+
|
|
227
|
+
private buildIdentityRequest(oracleDid: string): SearchEnhancedRequest {
|
|
228
|
+
return {
|
|
184
229
|
oracle_dids: [oracleDid],
|
|
185
230
|
query:
|
|
186
231
|
'username and nickname and age user identity traits values personality characteristics communication style beliefs preferences',
|
|
@@ -204,19 +249,10 @@ export class MemoryEngineService {
|
|
|
204
249
|
invalid_at: [[{ date: null, comparison_operator: 'IS NULL' }]],
|
|
205
250
|
},
|
|
206
251
|
};
|
|
207
|
-
|
|
208
|
-
return this.executeQuery(request, roomId, auth);
|
|
209
252
|
}
|
|
210
253
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*/
|
|
214
|
-
private async queryWork(
|
|
215
|
-
oracleDid: string,
|
|
216
|
-
roomId: string,
|
|
217
|
-
auth: MemoryEngineAuthHeaders,
|
|
218
|
-
): Promise<SearchEnhancedResponse | undefined> {
|
|
219
|
-
const request: SearchEnhancedRequest = {
|
|
254
|
+
private buildWorkRequest(oracleDid: string): SearchEnhancedRequest {
|
|
255
|
+
return {
|
|
220
256
|
oracle_dids: [oracleDid],
|
|
221
257
|
query:
|
|
222
258
|
'work job career projects skills organization employment role responsibilities expertise',
|
|
@@ -247,19 +283,10 @@ export class MemoryEngineService {
|
|
|
247
283
|
invalid_at: [[{ date: null, comparison_operator: 'IS NULL' }]],
|
|
248
284
|
},
|
|
249
285
|
};
|
|
250
|
-
|
|
251
|
-
return this.executeQuery(request, roomId, auth);
|
|
252
286
|
}
|
|
253
287
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
*/
|
|
257
|
-
private async queryGoals(
|
|
258
|
-
oracleDid: string,
|
|
259
|
-
roomId: string,
|
|
260
|
-
auth: MemoryEngineAuthHeaders,
|
|
261
|
-
): Promise<SearchEnhancedResponse | undefined> {
|
|
262
|
-
const request: SearchEnhancedRequest = {
|
|
288
|
+
private buildGoalsRequest(oracleDid: string): SearchEnhancedRequest {
|
|
289
|
+
return {
|
|
263
290
|
oracle_dids: [oracleDid],
|
|
264
291
|
query:
|
|
265
292
|
'goals aspirations objectives milestones habits routines patterns achievements progress',
|
|
@@ -282,19 +309,10 @@ export class MemoryEngineService {
|
|
|
282
309
|
invalid_at: [[{ date: null, comparison_operator: 'IS NULL' }]],
|
|
283
310
|
},
|
|
284
311
|
};
|
|
285
|
-
|
|
286
|
-
return this.executeQuery(request, roomId, auth);
|
|
287
312
|
}
|
|
288
313
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
*/
|
|
292
|
-
private async queryInterests(
|
|
293
|
-
oracleDid: string,
|
|
294
|
-
roomId: string,
|
|
295
|
-
auth: MemoryEngineAuthHeaders,
|
|
296
|
-
): Promise<SearchEnhancedResponse | undefined> {
|
|
297
|
-
const request: SearchEnhancedRequest = {
|
|
314
|
+
private buildInterestsRequest(oracleDid: string): SearchEnhancedRequest {
|
|
315
|
+
return {
|
|
298
316
|
oracle_dids: [oracleDid],
|
|
299
317
|
query:
|
|
300
318
|
'interests hobbies passions preferences likes dislikes expertise topics content',
|
|
@@ -324,19 +342,10 @@ export class MemoryEngineService {
|
|
|
324
342
|
invalid_at: [[{ date: null, comparison_operator: 'IS NULL' }]],
|
|
325
343
|
},
|
|
326
344
|
};
|
|
327
|
-
|
|
328
|
-
return this.executeQuery(request, roomId, auth);
|
|
329
345
|
}
|
|
330
346
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
*/
|
|
334
|
-
private async queryRelationships(
|
|
335
|
-
oracleDid: string,
|
|
336
|
-
roomId: string,
|
|
337
|
-
auth: MemoryEngineAuthHeaders,
|
|
338
|
-
): Promise<SearchEnhancedResponse | undefined> {
|
|
339
|
-
const request: SearchEnhancedRequest = {
|
|
347
|
+
private buildRelationshipsRequest(oracleDid: string): SearchEnhancedRequest {
|
|
348
|
+
return {
|
|
340
349
|
oracle_dids: [oracleDid],
|
|
341
350
|
query:
|
|
342
351
|
'relationships people connections social network colleagues friends family contacts',
|
|
@@ -359,24 +368,17 @@ export class MemoryEngineService {
|
|
|
359
368
|
invalid_at: [[{ date: null, comparison_operator: 'IS NULL' }]],
|
|
360
369
|
},
|
|
361
370
|
};
|
|
362
|
-
|
|
363
|
-
return this.executeQuery(request, roomId, auth);
|
|
364
371
|
}
|
|
365
372
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
oracleDid: string,
|
|
371
|
-
roomId: string,
|
|
372
|
-
auth: MemoryEngineAuthHeaders,
|
|
373
|
-
): Promise<SearchEnhancedResponse | undefined> {
|
|
374
|
-
// Calculate date 90 days ago for recent context
|
|
373
|
+
private buildRecentRequest(oracleDid: string): SearchEnhancedRequest {
|
|
374
|
+
// Server-side `recent_memory` strategy auto-injects a created_at >= now-90d
|
|
375
|
+
// filter. We still pass it explicitly as defense-in-depth — the server's
|
|
376
|
+
// merge logic respects an existing lower bound and won't double-apply.
|
|
375
377
|
const ninetyDaysAgo = new Date();
|
|
376
378
|
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
|
|
377
379
|
const dateString = ninetyDaysAgo.toISOString();
|
|
378
380
|
|
|
379
|
-
|
|
381
|
+
return {
|
|
380
382
|
oracle_dids: [oracleDid],
|
|
381
383
|
query:
|
|
382
384
|
'recent conversations messages discussions activities updates interactions',
|
|
@@ -390,8 +392,6 @@ export class MemoryEngineService {
|
|
|
390
392
|
created_at: [[{ date: dateString, comparison_operator: '>=' }]],
|
|
391
393
|
},
|
|
392
394
|
};
|
|
393
|
-
|
|
394
|
-
return this.executeQuery(request, roomId, auth);
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
/**
|
|
@@ -475,53 +475,4 @@ export class MemoryEngineService {
|
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
/**
|
|
479
|
-
* Execute a search query against the Memory Engine API
|
|
480
|
-
*/
|
|
481
|
-
private async executeQuery(
|
|
482
|
-
request: SearchEnhancedRequest,
|
|
483
|
-
roomId: string,
|
|
484
|
-
auth: MemoryEngineAuthHeaders,
|
|
485
|
-
): Promise<SearchEnhancedResponse | undefined> {
|
|
486
|
-
if (!roomId) {
|
|
487
|
-
Logger.warn(
|
|
488
|
-
`[MemoryEngineService] No room id provided, skipping query "${request.query}"`,
|
|
489
|
-
);
|
|
490
|
-
return undefined;
|
|
491
|
-
}
|
|
492
|
-
if (!auth.ucanInvocation && (!auth.oracleToken || !auth.userToken)) {
|
|
493
|
-
Logger.warn(
|
|
494
|
-
`[MemoryEngineService] Missing auth (no UCAN and no Matrix tokens), skipping query "${request.query}"`,
|
|
495
|
-
);
|
|
496
|
-
return undefined;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
try {
|
|
500
|
-
const response = await fetch(`${this.memoryEngineUrl}/search-enhanced`, {
|
|
501
|
-
method: 'POST',
|
|
502
|
-
headers: this.buildHeaders(auth, roomId),
|
|
503
|
-
body: JSON.stringify(request),
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
if (!response.ok) {
|
|
507
|
-
const errorText = await response.text();
|
|
508
|
-
Logger.warn(
|
|
509
|
-
`[MemoryEngineService] Memory Engine query failed (${response.status}): ${errorText}`,
|
|
510
|
-
);
|
|
511
|
-
return undefined;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const result = (await response.json()) as SearchEnhancedResponse;
|
|
515
|
-
Logger.info(
|
|
516
|
-
`[MemoryEngineService] Query "${request.query}" returned ${result.total_results.facts} facts, ${result.total_results.entities} entities`,
|
|
517
|
-
);
|
|
518
|
-
return result;
|
|
519
|
-
} catch (error) {
|
|
520
|
-
Logger.error(
|
|
521
|
-
`[MemoryEngineService] Failed to execute query "${request.query}":`,
|
|
522
|
-
error,
|
|
523
|
-
);
|
|
524
|
-
return undefined;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
478
|
}
|
|
@@ -206,3 +206,37 @@ export interface UserContextData {
|
|
|
206
206
|
relationships?: SearchEnhancedResponse;
|
|
207
207
|
recent?: SearchEnhancedResponse;
|
|
208
208
|
}
|
|
209
|
+
|
|
210
|
+
// Batch search types — backend endpoint POST /search-enhanced-batch
|
|
211
|
+
export interface SearchEnhancedBatchRequest {
|
|
212
|
+
queries: SearchEnhancedRequest[];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// A failed slot in the batch response. The server returns this in place of
|
|
216
|
+
// SearchEnhancedResponse when a single query fails — the rest of the batch
|
|
217
|
+
// still completes.
|
|
218
|
+
export interface SearchEnhancedBatchErrorSlot {
|
|
219
|
+
error: {
|
|
220
|
+
status_code: number;
|
|
221
|
+
detail: string;
|
|
222
|
+
};
|
|
223
|
+
query: string;
|
|
224
|
+
strategy_used: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export type SearchEnhancedBatchSlot =
|
|
228
|
+
| SearchEnhancedResponse
|
|
229
|
+
| SearchEnhancedBatchErrorSlot;
|
|
230
|
+
|
|
231
|
+
export interface SearchEnhancedBatchResponse {
|
|
232
|
+
results: SearchEnhancedBatchSlot[];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function isBatchErrorSlot(
|
|
236
|
+
slot: SearchEnhancedBatchSlot,
|
|
237
|
+
): slot is SearchEnhancedBatchErrorSlot {
|
|
238
|
+
return (
|
|
239
|
+
typeof (slot as SearchEnhancedBatchErrorSlot).error === 'object' &&
|
|
240
|
+
(slot as SearchEnhancedBatchErrorSlot).error !== null
|
|
241
|
+
);
|
|
242
|
+
}
|