@exulu/backend 1.48.2 → 1.49.0
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/dist/index.cjs +351 -42
- package/dist/index.d.cts +96 -1
- package/dist/index.d.ts +96 -1
- package/dist/index.js +340 -38
- package/ee/{markdown.ts → chunking/markdown.ts} +2 -2
- package/ee/python/README.md +295 -0
- package/ee/python/documents/processing/README.md +155 -0
- package/ee/{documents → python/documents}/processing/doc_processor.ts +25 -17
- package/ee/{documents/processing/pdf_to_markdown.py → python/documents/processing/document_to_markdown.py} +3 -10
- package/ee/python/setup.sh +180 -0
- package/package.json +14 -3
- package/scripts/postinstall.cjs +149 -0
- package/.agents/skills/mintlify/SKILL.md +0 -347
- package/.editorconfig +0 -15
- package/.eslintrc.json +0 -52
- package/.github/workflows/release-backend.yml +0 -38
- package/.husky/commit-msg +0 -1
- package/.jscpd.json +0 -18
- package/.mcp.json +0 -25
- package/.nvmrc +0 -1
- package/.prettierignore +0 -5
- package/.prettierrc.json +0 -12
- package/CHANGELOG.md +0 -8
- package/SECURITY.md +0 -5
- package/commitlint.config.js +0 -4
- package/devops/documentation/patch-older-releases.md +0 -42
- package/ee/documents/processing/build_pdf_processor.sh +0 -35
- package/ee/documents/processing/chunk_markdown.py +0 -263
- package/ee/documents/processing/pdf_processor.spec +0 -115
- package/eslint.config.js +0 -88
- package/jest.config.ts +0 -25
- package/mintlify-docs/.mintignore +0 -7
- package/mintlify-docs/AGENTS.md +0 -33
- package/mintlify-docs/CLAUDE.MD +0 -50
- package/mintlify-docs/CONTRIBUTING.md +0 -32
- package/mintlify-docs/LICENSE +0 -21
- package/mintlify-docs/README.md +0 -55
- package/mintlify-docs/ai-tools/claude-code.mdx +0 -43
- package/mintlify-docs/ai-tools/cursor.mdx +0 -39
- package/mintlify-docs/ai-tools/windsurf.mdx +0 -39
- package/mintlify-docs/api-reference/core-types/agent-types.mdx +0 -110
- package/mintlify-docs/api-reference/core-types/analytics-types.mdx +0 -95
- package/mintlify-docs/api-reference/core-types/configuration-types.mdx +0 -83
- package/mintlify-docs/api-reference/core-types/evaluation-types.mdx +0 -106
- package/mintlify-docs/api-reference/core-types/job-types.mdx +0 -135
- package/mintlify-docs/api-reference/core-types/overview.mdx +0 -73
- package/mintlify-docs/api-reference/core-types/prompt-types.mdx +0 -102
- package/mintlify-docs/api-reference/core-types/rbac-types.mdx +0 -163
- package/mintlify-docs/api-reference/core-types/session-types.mdx +0 -77
- package/mintlify-docs/api-reference/core-types/user-management.mdx +0 -112
- package/mintlify-docs/api-reference/core-types/workflow-types.mdx +0 -88
- package/mintlify-docs/api-reference/core-types.mdx +0 -585
- package/mintlify-docs/api-reference/dynamic-types.mdx +0 -851
- package/mintlify-docs/api-reference/endpoint/create.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/delete.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/get.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/webhook.mdx +0 -4
- package/mintlify-docs/api-reference/introduction.mdx +0 -661
- package/mintlify-docs/api-reference/mutations.mdx +0 -1012
- package/mintlify-docs/api-reference/openapi.json +0 -217
- package/mintlify-docs/api-reference/queries.mdx +0 -1154
- package/mintlify-docs/backend/introduction.mdx +0 -218
- package/mintlify-docs/changelog.mdx +0 -387
- package/mintlify-docs/community-edition.mdx +0 -304
- package/mintlify-docs/core/exulu-agent/api-reference.mdx +0 -894
- package/mintlify-docs/core/exulu-agent/configuration.mdx +0 -690
- package/mintlify-docs/core/exulu-agent/introduction.mdx +0 -552
- package/mintlify-docs/core/exulu-app/api-reference.mdx +0 -481
- package/mintlify-docs/core/exulu-app/configuration.mdx +0 -319
- package/mintlify-docs/core/exulu-app/introduction.mdx +0 -117
- package/mintlify-docs/core/exulu-authentication.mdx +0 -810
- package/mintlify-docs/core/exulu-chunkers/api-reference.mdx +0 -1011
- package/mintlify-docs/core/exulu-chunkers/configuration.mdx +0 -596
- package/mintlify-docs/core/exulu-chunkers/introduction.mdx +0 -403
- package/mintlify-docs/core/exulu-context/api-reference.mdx +0 -911
- package/mintlify-docs/core/exulu-context/configuration.mdx +0 -648
- package/mintlify-docs/core/exulu-context/introduction.mdx +0 -394
- package/mintlify-docs/core/exulu-database.mdx +0 -811
- package/mintlify-docs/core/exulu-default-agents.mdx +0 -545
- package/mintlify-docs/core/exulu-eval/api-reference.mdx +0 -772
- package/mintlify-docs/core/exulu-eval/configuration.mdx +0 -680
- package/mintlify-docs/core/exulu-eval/introduction.mdx +0 -459
- package/mintlify-docs/core/exulu-logging.mdx +0 -464
- package/mintlify-docs/core/exulu-otel.mdx +0 -670
- package/mintlify-docs/core/exulu-queues/api-reference.mdx +0 -648
- package/mintlify-docs/core/exulu-queues/configuration.mdx +0 -650
- package/mintlify-docs/core/exulu-queues/introduction.mdx +0 -474
- package/mintlify-docs/core/exulu-reranker/api-reference.mdx +0 -630
- package/mintlify-docs/core/exulu-reranker/configuration.mdx +0 -663
- package/mintlify-docs/core/exulu-reranker/introduction.mdx +0 -516
- package/mintlify-docs/core/exulu-tool/api-reference.mdx +0 -723
- package/mintlify-docs/core/exulu-tool/configuration.mdx +0 -805
- package/mintlify-docs/core/exulu-tool/introduction.mdx +0 -539
- package/mintlify-docs/core/exulu-variables/api-reference.mdx +0 -699
- package/mintlify-docs/core/exulu-variables/configuration.mdx +0 -736
- package/mintlify-docs/core/exulu-variables/introduction.mdx +0 -511
- package/mintlify-docs/development.mdx +0 -94
- package/mintlify-docs/docs.json +0 -248
- package/mintlify-docs/enterprise-edition.mdx +0 -538
- package/mintlify-docs/essentials/code.mdx +0 -35
- package/mintlify-docs/essentials/images.mdx +0 -59
- package/mintlify-docs/essentials/markdown.mdx +0 -88
- package/mintlify-docs/essentials/navigation.mdx +0 -87
- package/mintlify-docs/essentials/reusable-snippets.mdx +0 -110
- package/mintlify-docs/essentials/settings.mdx +0 -318
- package/mintlify-docs/favicon.svg +0 -3
- package/mintlify-docs/frontend/introduction.mdx +0 -39
- package/mintlify-docs/getting-started.mdx +0 -267
- package/mintlify-docs/guides/custom-agent.mdx +0 -608
- package/mintlify-docs/guides/first-agent.mdx +0 -315
- package/mintlify-docs/images/admin_ui.png +0 -0
- package/mintlify-docs/images/contexts.png +0 -0
- package/mintlify-docs/images/create_agents.png +0 -0
- package/mintlify-docs/images/evals.png +0 -0
- package/mintlify-docs/images/graphql.png +0 -0
- package/mintlify-docs/images/graphql_api.png +0 -0
- package/mintlify-docs/images/hero-dark.png +0 -0
- package/mintlify-docs/images/hero-light.png +0 -0
- package/mintlify-docs/images/hero.png +0 -0
- package/mintlify-docs/images/knowledge_sources.png +0 -0
- package/mintlify-docs/images/mcp.png +0 -0
- package/mintlify-docs/images/scaling.png +0 -0
- package/mintlify-docs/index.mdx +0 -411
- package/mintlify-docs/logo/dark.svg +0 -9
- package/mintlify-docs/logo/light.svg +0 -9
- package/mintlify-docs/partners.mdx +0 -558
- package/mintlify-docs/products.mdx +0 -77
- package/mintlify-docs/snippets/snippet-intro.mdx +0 -4
- package/mintlify-docs/styles.css +0 -207
- package/ngrok.bash +0 -1
- package/ngrok.md +0 -6
- package/ngrok.yml +0 -10
- package/release.config.cjs +0 -15
- package/skills-lock.json +0 -10
- package/types/context-processor.ts +0 -45
- package/types/enums/eval-types.ts +0 -5
- package/types/enums/field-types.ts +0 -1
- package/types/enums/jobs.ts +0 -11
- package/types/enums/statistics.ts +0 -13
- package/types/exulu-table-definition.ts +0 -79
- package/types/file-types.ts +0 -18
- package/types/models/agent-session.ts +0 -27
- package/types/models/agent.ts +0 -68
- package/types/models/context.ts +0 -53
- package/types/models/embedding.ts +0 -17
- package/types/models/eval-run.ts +0 -40
- package/types/models/exulu-agent-tool-config.ts +0 -11
- package/types/models/item.ts +0 -21
- package/types/models/job.ts +0 -8
- package/types/models/project.ts +0 -16
- package/types/models/rate-limiter-rules.ts +0 -7
- package/types/models/test-case.ts +0 -25
- package/types/models/tool.ts +0 -9
- package/types/models/user-role.ts +0 -12
- package/types/models/user.ts +0 -20
- package/types/models/variable.ts +0 -8
- package/types/models/vector-methods.ts +0 -7
- package/types/provider-config.ts +0 -21
- package/types/queue-config.ts +0 -16
- package/types/rbac-rights-modes.ts +0 -1
- package/types/statistics.ts +0 -20
- package/types/workflow.ts +0 -31
- /package/ee/{documents → python/documents}/THIRD_PARTY_LICENSES/docling.txt +0 -0
- /package/ee/{documents/processing → python}/requirements.txt +0 -0
|
@@ -1,911 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "API reference"
|
|
3
|
-
description: "Complete method and property reference for ExuluContext"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Constructor
|
|
7
|
-
|
|
8
|
-
```typescript
|
|
9
|
-
const context = new ExuluContext(options: ExuluContextOptions);
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
Creates a new ExuluContext instance. See the [configuration guide](/core/exulu-context/configuration) for all available options.
|
|
13
|
-
|
|
14
|
-
## Item management methods
|
|
15
|
-
|
|
16
|
-
### createItem()
|
|
17
|
-
|
|
18
|
-
Creates a new item in the context. Optionally triggers processor and embeddings generation.
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
async createItem(
|
|
22
|
-
item: Item,
|
|
23
|
-
config: ExuluConfig,
|
|
24
|
-
user?: number,
|
|
25
|
-
role?: string,
|
|
26
|
-
upsert?: boolean,
|
|
27
|
-
generateEmbeddingsOverwrite?: boolean
|
|
28
|
-
): Promise<{ item: Item; job?: string }>
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
<ParamField path="item" type="Item" required>
|
|
32
|
-
Item data matching the context's field schema
|
|
33
|
-
</ParamField>
|
|
34
|
-
|
|
35
|
-
<ParamField path="config" type="ExuluConfig" required>
|
|
36
|
-
ExuluApp configuration object
|
|
37
|
-
</ParamField>
|
|
38
|
-
|
|
39
|
-
<ParamField path="user" type="number">
|
|
40
|
-
User ID for access control and tracking
|
|
41
|
-
</ParamField>
|
|
42
|
-
|
|
43
|
-
<ParamField path="role" type="string">
|
|
44
|
-
Role ID for access control
|
|
45
|
-
</ParamField>
|
|
46
|
-
|
|
47
|
-
<ParamField path="upsert" type="boolean">
|
|
48
|
-
If `true`, update existing item if `id` or `external_id` matches (default: `false`)
|
|
49
|
-
</ParamField>
|
|
50
|
-
|
|
51
|
-
<ParamField path="generateEmbeddingsOverwrite" type="boolean">
|
|
52
|
-
Override the `calculateVectors` configuration for this operation
|
|
53
|
-
</ParamField>
|
|
54
|
-
|
|
55
|
-
<ResponseField name="item" type="Item">
|
|
56
|
-
The created item with generated `id`
|
|
57
|
-
</ResponseField>
|
|
58
|
-
|
|
59
|
-
<ResponseField name="job" type="string">
|
|
60
|
-
Comma-separated job IDs if processor or embeddings were queued
|
|
61
|
-
</ResponseField>
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
const { item, job } = await context.createItem(
|
|
65
|
-
{
|
|
66
|
-
external_id: "doc-123",
|
|
67
|
-
name: "Getting Started",
|
|
68
|
-
content: "Welcome to our platform...",
|
|
69
|
-
category: "guide"
|
|
70
|
-
},
|
|
71
|
-
config,
|
|
72
|
-
userId
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
console.log(`Created item ${item.id}`);
|
|
76
|
-
if (job) {
|
|
77
|
-
console.log(`Queued jobs: ${job}`);
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
<Info>
|
|
82
|
-
When `upsert: true`, the item is created if it doesn't exist, or updated if an item with the same `external_id` or `id` exists.
|
|
83
|
-
</Info>
|
|
84
|
-
|
|
85
|
-
### updateItem()
|
|
86
|
-
|
|
87
|
-
Updates an existing item. Optionally triggers processor and embeddings generation.
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
async updateItem(
|
|
91
|
-
item: Item,
|
|
92
|
-
config: ExuluConfig,
|
|
93
|
-
user?: number,
|
|
94
|
-
role?: string,
|
|
95
|
-
generateEmbeddingsOverwrite?: boolean
|
|
96
|
-
): Promise<{ item: Item; job?: string }>
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
<ParamField path="item" type="Item" required>
|
|
100
|
-
Item data with `id` field and fields to update
|
|
101
|
-
</ParamField>
|
|
102
|
-
|
|
103
|
-
<ParamField path="config" type="ExuluConfig" required>
|
|
104
|
-
ExuluApp configuration object
|
|
105
|
-
</ParamField>
|
|
106
|
-
|
|
107
|
-
<ParamField path="user" type="number">
|
|
108
|
-
User ID for access control and tracking
|
|
109
|
-
</ParamField>
|
|
110
|
-
|
|
111
|
-
<ParamField path="role" type="string">
|
|
112
|
-
Role ID for access control
|
|
113
|
-
</ParamField>
|
|
114
|
-
|
|
115
|
-
<ParamField path="generateEmbeddingsOverwrite" type="boolean">
|
|
116
|
-
Override the `calculateVectors` configuration
|
|
117
|
-
</ParamField>
|
|
118
|
-
|
|
119
|
-
<ResponseField name="item" type="Item">
|
|
120
|
-
The updated item
|
|
121
|
-
</ResponseField>
|
|
122
|
-
|
|
123
|
-
<ResponseField name="job" type="string">
|
|
124
|
-
Comma-separated job IDs if processor or embeddings were queued
|
|
125
|
-
</ResponseField>
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
const { item } = await context.updateItem(
|
|
129
|
-
{
|
|
130
|
-
id: "123e4567-e89b-12d3-a456-426614174000",
|
|
131
|
-
content: "Updated content...",
|
|
132
|
-
category: "reference"
|
|
133
|
-
},
|
|
134
|
-
config,
|
|
135
|
-
userId
|
|
136
|
-
);
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### deleteItem()
|
|
140
|
-
|
|
141
|
-
Deletes an item and its associated chunks.
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
async deleteItem(
|
|
145
|
-
item: Item,
|
|
146
|
-
user?: number,
|
|
147
|
-
role?: string
|
|
148
|
-
): Promise<{ id: string; job?: string }>
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
<ParamField path="item" type="Item" required>
|
|
152
|
-
Item with `id` or `external_id` to delete
|
|
153
|
-
</ParamField>
|
|
154
|
-
|
|
155
|
-
<ParamField path="user" type="number">
|
|
156
|
-
User ID for tracking
|
|
157
|
-
</ParamField>
|
|
158
|
-
|
|
159
|
-
<ParamField path="role" type="string">
|
|
160
|
-
Role ID for access control
|
|
161
|
-
</ParamField>
|
|
162
|
-
|
|
163
|
-
<ResponseField name="id" type="string">
|
|
164
|
-
ID of the deleted item
|
|
165
|
-
</ResponseField>
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
await context.deleteItem({
|
|
169
|
-
id: "123e4567-e89b-12d3-a456-426614174000"
|
|
170
|
-
});
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
<Warning>
|
|
174
|
-
This permanently deletes the item and all its embedding chunks. This operation cannot be undone.
|
|
175
|
-
</Warning>
|
|
176
|
-
|
|
177
|
-
### getItem()
|
|
178
|
-
|
|
179
|
-
Retrieves a single item by ID or external ID.
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
async getItem({ item }: { item: Item }): Promise<Item>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
<ParamField path="item" type="Item" required>
|
|
186
|
-
Object with `id` or `external_id` field
|
|
187
|
-
</ParamField>
|
|
188
|
-
|
|
189
|
-
<ResponseField name="return" type="Promise<Item>">
|
|
190
|
-
The item with all fields and `chunksCount` property
|
|
191
|
-
</ResponseField>
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
const item = await context.getItem({
|
|
195
|
-
item: { id: "123e4567-e89b-12d3-a456-426614174000" }
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// Or by external_id
|
|
199
|
-
const item = await context.getItem({
|
|
200
|
-
item: { external_id: "doc-123" }
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
console.log(`Item has ${item.chunksCount} chunks`);
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
<Note>
|
|
207
|
-
This method does not apply access control. You are responsible for implementing access control in your application.
|
|
208
|
-
</Note>
|
|
209
|
-
|
|
210
|
-
### getItems()
|
|
211
|
-
|
|
212
|
-
Retrieves multiple items with optional filters.
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
async getItems({
|
|
216
|
-
filters,
|
|
217
|
-
fields
|
|
218
|
-
}: {
|
|
219
|
-
filters?: any[];
|
|
220
|
-
fields?: string[];
|
|
221
|
-
}): Promise<Item[]>
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
<ParamField path="filters" type="any[]">
|
|
225
|
-
Array of filter conditions (field, operator, value)
|
|
226
|
-
</ParamField>
|
|
227
|
-
|
|
228
|
-
<ParamField path="fields" type="string[]">
|
|
229
|
-
Array of field names to return (default: all fields)
|
|
230
|
-
</ParamField>
|
|
231
|
-
|
|
232
|
-
<ResponseField name="return" type="Promise<Item[]>">
|
|
233
|
-
Array of items matching the filters
|
|
234
|
-
</ResponseField>
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
const items = await context.getItems({
|
|
238
|
-
filters: [
|
|
239
|
-
{ field: "category", operator: "=", value: "guide" },
|
|
240
|
-
{ field: "archived", operator: "=", value: false }
|
|
241
|
-
],
|
|
242
|
-
fields: ["id", "name", "category"]
|
|
243
|
-
});
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### deleteAll()
|
|
247
|
-
|
|
248
|
-
Deletes all items and chunks from the context.
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
async deleteAll(): Promise<{
|
|
252
|
-
count: number;
|
|
253
|
-
results: any;
|
|
254
|
-
errors?: string[];
|
|
255
|
-
}>
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
<ResponseField name="count" type="number">
|
|
259
|
-
Number of items deleted
|
|
260
|
-
</ResponseField>
|
|
261
|
-
|
|
262
|
-
<ResponseField name="results" type="any">
|
|
263
|
-
Deletion results
|
|
264
|
-
</ResponseField>
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
await context.deleteAll();
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
<Warning>
|
|
271
|
-
This is a destructive operation that deletes all data in the context. Use with extreme caution.
|
|
272
|
-
</Warning>
|
|
273
|
-
|
|
274
|
-
## Search methods
|
|
275
|
-
|
|
276
|
-
### search()
|
|
277
|
-
|
|
278
|
-
Performs semantic, keyword, or hybrid search on the context.
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
async search(options: SearchOptions): Promise<SearchResult>
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
<ParamField path="options.query" type="string">
|
|
285
|
-
Natural language search query
|
|
286
|
-
</ParamField>
|
|
287
|
-
|
|
288
|
-
<ParamField path="options.keywords" type="string[]">
|
|
289
|
-
Specific keywords to search for
|
|
290
|
-
</ParamField>
|
|
291
|
-
|
|
292
|
-
<ParamField path="options.method" type="VectorMethod" required>
|
|
293
|
-
Search method: `"cosineDistance"`, `"tsvector"`, or `"hybridSearch"`
|
|
294
|
-
</ParamField>
|
|
295
|
-
|
|
296
|
-
<ParamField path="options.itemFilters" type="SearchFilters" required>
|
|
297
|
-
Filters to apply to items table
|
|
298
|
-
</ParamField>
|
|
299
|
-
|
|
300
|
-
<ParamField path="options.chunkFilters" type="SearchFilters" required>
|
|
301
|
-
Filters to apply to chunks table
|
|
302
|
-
</ParamField>
|
|
303
|
-
|
|
304
|
-
<ParamField path="options.user" type="User">
|
|
305
|
-
User object for access control
|
|
306
|
-
</ParamField>
|
|
307
|
-
|
|
308
|
-
<ParamField path="options.role" type="string">
|
|
309
|
-
Role for access control
|
|
310
|
-
</ParamField>
|
|
311
|
-
|
|
312
|
-
<ParamField path="options.limit" type="number" required>
|
|
313
|
-
Maximum number of results to return
|
|
314
|
-
</ParamField>
|
|
315
|
-
|
|
316
|
-
<ParamField path="options.page" type="number" required>
|
|
317
|
-
Page number for pagination (1-indexed)
|
|
318
|
-
</ParamField>
|
|
319
|
-
|
|
320
|
-
<ParamField path="options.sort" type="any" required>
|
|
321
|
-
Sort configuration
|
|
322
|
-
</ParamField>
|
|
323
|
-
|
|
324
|
-
<ParamField path="options.trigger" type="STATISTICS_LABELS" required>
|
|
325
|
-
Trigger source for tracking: `"agent"`, `"api"`, `"processor"`, etc.
|
|
326
|
-
</ParamField>
|
|
327
|
-
|
|
328
|
-
<ParamField path="options.cutoffs" type="object">
|
|
329
|
-
Override default cutoff scores
|
|
330
|
-
</ParamField>
|
|
331
|
-
|
|
332
|
-
<ParamField path="options.expand" type="object">
|
|
333
|
-
Override default chunk expansion
|
|
334
|
-
</ParamField>
|
|
335
|
-
|
|
336
|
-
<ResponseField name="chunks" type="VectorSearchChunkResult[]">
|
|
337
|
-
Array of matching chunks with metadata
|
|
338
|
-
</ResponseField>
|
|
339
|
-
|
|
340
|
-
<ResponseField name="context" type="object">
|
|
341
|
-
Context metadata (name, id, embedder)
|
|
342
|
-
</ResponseField>
|
|
343
|
-
|
|
344
|
-
<ResponseField name="query" type="string">
|
|
345
|
-
The search query used
|
|
346
|
-
</ResponseField>
|
|
347
|
-
|
|
348
|
-
<ResponseField name="keywords" type="string[]">
|
|
349
|
-
The keywords used
|
|
350
|
-
</ResponseField>
|
|
351
|
-
|
|
352
|
-
<ResponseField name="method" type="VectorMethod">
|
|
353
|
-
The search method used
|
|
354
|
-
</ResponseField>
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
const results = await context.search({
|
|
358
|
-
query: "How do I configure authentication?",
|
|
359
|
-
keywords: ["auth", "config"],
|
|
360
|
-
method: "hybridSearch",
|
|
361
|
-
itemFilters: [
|
|
362
|
-
{ field: "category", operator: "=", value: "guide" }
|
|
363
|
-
],
|
|
364
|
-
chunkFilters: [],
|
|
365
|
-
user: currentUser,
|
|
366
|
-
limit: 10,
|
|
367
|
-
page: 1,
|
|
368
|
-
sort: undefined,
|
|
369
|
-
trigger: "api"
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
results.chunks.forEach(chunk => {
|
|
373
|
-
console.log(chunk.chunk_content);
|
|
374
|
-
console.log(chunk.item_name);
|
|
375
|
-
console.log(chunk.chunk_metadata);
|
|
376
|
-
});
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
<Tabs>
|
|
380
|
-
<Tab title="Hybrid search">
|
|
381
|
-
```typescript
|
|
382
|
-
method: "hybridSearch"
|
|
383
|
-
```
|
|
384
|
-
Combines semantic and keyword search. Best for most queries.
|
|
385
|
-
</Tab>
|
|
386
|
-
|
|
387
|
-
<Tab title="Semantic search">
|
|
388
|
-
```typescript
|
|
389
|
-
method: "cosineDistance"
|
|
390
|
-
```
|
|
391
|
-
Pure vector similarity search. Best for conceptual queries.
|
|
392
|
-
</Tab>
|
|
393
|
-
|
|
394
|
-
<Tab title="Keyword search">
|
|
395
|
-
```typescript
|
|
396
|
-
method: "tsvector"
|
|
397
|
-
```
|
|
398
|
-
Full-text keyword search. Best for exact terms and IDs.
|
|
399
|
-
</Tab>
|
|
400
|
-
</Tabs>
|
|
401
|
-
|
|
402
|
-
## Embeddings methods
|
|
403
|
-
|
|
404
|
-
### embeddings.generate.one()
|
|
405
|
-
|
|
406
|
-
Generates embeddings for a single item.
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
async embeddings.generate.one({
|
|
410
|
-
item,
|
|
411
|
-
user,
|
|
412
|
-
role,
|
|
413
|
-
trigger,
|
|
414
|
-
config
|
|
415
|
-
}: EmbeddingsGenerateOneOptions): Promise<{
|
|
416
|
-
id: string;
|
|
417
|
-
job?: string;
|
|
418
|
-
chunks?: number;
|
|
419
|
-
}>
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
<ParamField path="item" type="Item" required>
|
|
423
|
-
Item to generate embeddings for (must have `id`)
|
|
424
|
-
</ParamField>
|
|
425
|
-
|
|
426
|
-
<ParamField path="user" type="number">
|
|
427
|
-
User ID for tracking
|
|
428
|
-
</ParamField>
|
|
429
|
-
|
|
430
|
-
<ParamField path="role" type="string">
|
|
431
|
-
Role for tracking
|
|
432
|
-
</ParamField>
|
|
433
|
-
|
|
434
|
-
<ParamField path="trigger" type="STATISTICS_LABELS" required>
|
|
435
|
-
Trigger source: `"agent"`, `"api"`, `"processor"`, etc.
|
|
436
|
-
</ParamField>
|
|
437
|
-
|
|
438
|
-
<ParamField path="config" type="ExuluConfig" required>
|
|
439
|
-
ExuluApp configuration
|
|
440
|
-
</ParamField>
|
|
441
|
-
|
|
442
|
-
<ResponseField name="id" type="string">
|
|
443
|
-
Item ID
|
|
444
|
-
</ResponseField>
|
|
445
|
-
|
|
446
|
-
<ResponseField name="job" type="string">
|
|
447
|
-
Job ID if queued for background processing
|
|
448
|
-
</ResponseField>
|
|
449
|
-
|
|
450
|
-
<ResponseField name="chunks" type="number">
|
|
451
|
-
Number of chunks generated (if not queued)
|
|
452
|
-
</ResponseField>
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
const { id, job, chunks } = await context.embeddings.generate.one({
|
|
456
|
-
item: { id: "123e4567-e89b-12d3-a456-426614174000" },
|
|
457
|
-
user: userId,
|
|
458
|
-
trigger: "api",
|
|
459
|
-
config: config
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
if (job) {
|
|
463
|
-
console.log(`Embeddings queued as job ${job}`);
|
|
464
|
-
} else {
|
|
465
|
-
console.log(`Generated ${chunks} chunks`);
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### embeddings.generate.all()
|
|
470
|
-
|
|
471
|
-
Generates embeddings for all items in the context.
|
|
472
|
-
|
|
473
|
-
```typescript
|
|
474
|
-
async embeddings.generate.all(
|
|
475
|
-
config: ExuluConfig,
|
|
476
|
-
userId?: number,
|
|
477
|
-
roleId?: string,
|
|
478
|
-
limit?: number
|
|
479
|
-
): Promise<{
|
|
480
|
-
jobs: string[];
|
|
481
|
-
items: number;
|
|
482
|
-
}>
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
<ParamField path="config" type="ExuluConfig" required>
|
|
486
|
-
ExuluApp configuration
|
|
487
|
-
</ParamField>
|
|
488
|
-
|
|
489
|
-
<ParamField path="userId" type="number">
|
|
490
|
-
User ID for tracking
|
|
491
|
-
</ParamField>
|
|
492
|
-
|
|
493
|
-
<ParamField path="roleId" type="string">
|
|
494
|
-
Role ID for tracking
|
|
495
|
-
</ParamField>
|
|
496
|
-
|
|
497
|
-
<ParamField path="limit" type="number">
|
|
498
|
-
Maximum number of items to process
|
|
499
|
-
</ParamField>
|
|
500
|
-
|
|
501
|
-
<ResponseField name="jobs" type="string[]">
|
|
502
|
-
Array of job IDs if queued
|
|
503
|
-
</ResponseField>
|
|
504
|
-
|
|
505
|
-
<ResponseField name="items" type="number">
|
|
506
|
-
Number of items processed
|
|
507
|
-
</ResponseField>
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
const { jobs, items } = await context.embeddings.generate.all(
|
|
511
|
-
config,
|
|
512
|
-
userId,
|
|
513
|
-
undefined,
|
|
514
|
-
1000 // Process first 1000 items
|
|
515
|
-
);
|
|
516
|
-
|
|
517
|
-
console.log(`Processing ${items} items in ${jobs.length} jobs`);
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
<Warning>
|
|
521
|
-
Without a queue configured, this method can only process up to 2,000 items. For larger datasets, configure the embedder with a queue.
|
|
522
|
-
</Warning>
|
|
523
|
-
|
|
524
|
-
## Processor methods
|
|
525
|
-
|
|
526
|
-
### processField()
|
|
527
|
-
|
|
528
|
-
Processes an item using the configured processor.
|
|
529
|
-
|
|
530
|
-
```typescript
|
|
531
|
-
async processField(
|
|
532
|
-
trigger: STATISTICS_LABELS,
|
|
533
|
-
item: Item,
|
|
534
|
-
exuluConfig: ExuluConfig,
|
|
535
|
-
user?: number,
|
|
536
|
-
role?: string
|
|
537
|
-
): Promise<{
|
|
538
|
-
result: Item | undefined;
|
|
539
|
-
job?: string;
|
|
540
|
-
}>
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
<ParamField path="trigger" type="STATISTICS_LABELS" required>
|
|
544
|
-
Trigger source for tracking
|
|
545
|
-
</ParamField>
|
|
546
|
-
|
|
547
|
-
<ParamField path="item" type="Item" required>
|
|
548
|
-
Item to process
|
|
549
|
-
</ParamField>
|
|
550
|
-
|
|
551
|
-
<ParamField path="exuluConfig" type="ExuluConfig" required>
|
|
552
|
-
ExuluApp configuration
|
|
553
|
-
</ParamField>
|
|
554
|
-
|
|
555
|
-
<ParamField path="user" type="number">
|
|
556
|
-
User ID for tracking
|
|
557
|
-
</ParamField>
|
|
558
|
-
|
|
559
|
-
<ParamField path="role" type="string">
|
|
560
|
-
Role for access control
|
|
561
|
-
</ParamField>
|
|
562
|
-
|
|
563
|
-
<ResponseField name="result" type="Item | undefined">
|
|
564
|
-
Processed item (undefined if queued or filtered out)
|
|
565
|
-
</ResponseField>
|
|
566
|
-
|
|
567
|
-
<ResponseField name="job" type="string">
|
|
568
|
-
Job ID if queued for background processing
|
|
569
|
-
</ResponseField>
|
|
570
|
-
|
|
571
|
-
```typescript
|
|
572
|
-
const { result, job } = await context.processField(
|
|
573
|
-
"api",
|
|
574
|
-
item,
|
|
575
|
-
config,
|
|
576
|
-
userId
|
|
577
|
-
);
|
|
578
|
-
|
|
579
|
-
if (job) {
|
|
580
|
-
console.log(`Processing queued as job ${job}`);
|
|
581
|
-
} else {
|
|
582
|
-
console.log("Processed result:", result);
|
|
583
|
-
}
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
<Note>
|
|
587
|
-
This method is typically called automatically by `createItem()` and `updateItem()` when a processor is configured.
|
|
588
|
-
</Note>
|
|
589
|
-
|
|
590
|
-
## Source methods
|
|
591
|
-
|
|
592
|
-
### executeSource()
|
|
593
|
-
|
|
594
|
-
Executes a data source to fetch and return items.
|
|
595
|
-
|
|
596
|
-
```typescript
|
|
597
|
-
async executeSource(
|
|
598
|
-
source: ExuluContextSource,
|
|
599
|
-
inputs: any,
|
|
600
|
-
exuluConfig: ExuluConfig
|
|
601
|
-
): Promise<Item[]>
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
<ParamField path="source" type="ExuluContextSource" required>
|
|
605
|
-
Source configuration to execute
|
|
606
|
-
</ParamField>
|
|
607
|
-
|
|
608
|
-
<ParamField path="inputs" type="any" required>
|
|
609
|
-
Input parameters for the source
|
|
610
|
-
</ParamField>
|
|
611
|
-
|
|
612
|
-
<ParamField path="exuluConfig" type="ExuluConfig" required>
|
|
613
|
-
ExuluApp configuration
|
|
614
|
-
</ParamField>
|
|
615
|
-
|
|
616
|
-
<ResponseField name="return" type="Promise<Item[]>">
|
|
617
|
-
Array of items fetched from the source
|
|
618
|
-
</ResponseField>
|
|
619
|
-
|
|
620
|
-
```typescript
|
|
621
|
-
const source = context.sources[0];
|
|
622
|
-
const items = await context.executeSource(
|
|
623
|
-
source,
|
|
624
|
-
{ since: "2024-01-01" },
|
|
625
|
-
config
|
|
626
|
-
);
|
|
627
|
-
|
|
628
|
-
console.log(`Fetched ${items.length} items from ${source.name}`);
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
## Table management methods
|
|
632
|
-
|
|
633
|
-
### createItemsTable()
|
|
634
|
-
|
|
635
|
-
Creates the database table for storing items.
|
|
636
|
-
|
|
637
|
-
```typescript
|
|
638
|
-
async createItemsTable(): Promise<void>
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
```typescript
|
|
642
|
-
await context.createItemsTable();
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
<Info>
|
|
646
|
-
This is typically called automatically by `db.init()` when initializing the database. You rarely need to call it manually.
|
|
647
|
-
</Info>
|
|
648
|
-
|
|
649
|
-
### createChunksTable()
|
|
650
|
-
|
|
651
|
-
Creates the database table for storing embedding chunks.
|
|
652
|
-
|
|
653
|
-
```typescript
|
|
654
|
-
async createChunksTable(): Promise<void>
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
```typescript
|
|
658
|
-
await context.createChunksTable();
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
<Warning>
|
|
662
|
-
This method requires an embedder to be configured, as it uses the embedder's vector dimensions to create the table schema.
|
|
663
|
-
</Warning>
|
|
664
|
-
|
|
665
|
-
### tableExists()
|
|
666
|
-
|
|
667
|
-
Checks if the items table exists in the database.
|
|
668
|
-
|
|
669
|
-
```typescript
|
|
670
|
-
async tableExists(): Promise<boolean>
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
```typescript
|
|
674
|
-
const exists = await context.tableExists();
|
|
675
|
-
if (!exists) {
|
|
676
|
-
await context.createItemsTable();
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
### chunksTableExists()
|
|
681
|
-
|
|
682
|
-
Checks if the chunks table exists in the database.
|
|
683
|
-
|
|
684
|
-
```typescript
|
|
685
|
-
async chunksTableExists(): Promise<boolean>
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
```typescript
|
|
689
|
-
const exists = await context.chunksTableExists();
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
## Tool generation
|
|
693
|
-
|
|
694
|
-
### tool()
|
|
695
|
-
|
|
696
|
-
Exports the context as a tool that agents can use.
|
|
697
|
-
|
|
698
|
-
```typescript
|
|
699
|
-
tool(): ExuluTool | null
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
<ResponseField name="return" type="ExuluTool | null">
|
|
703
|
-
Tool instance or `null` if `enableAsTool: false`
|
|
704
|
-
</ResponseField>
|
|
705
|
-
|
|
706
|
-
```typescript
|
|
707
|
-
const tool = context.tool();
|
|
708
|
-
if (tool) {
|
|
709
|
-
console.log(tool.name); // "{context_name}_context_search"
|
|
710
|
-
console.log(tool.description);
|
|
711
|
-
}
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
<Note>
|
|
715
|
-
This method is called automatically by ExuluApp when registering contexts. The generated tool is added to the tools registry.
|
|
716
|
-
</Note>
|
|
717
|
-
|
|
718
|
-
## Internal methods
|
|
719
|
-
|
|
720
|
-
### createAndUpsertEmbeddings()
|
|
721
|
-
|
|
722
|
-
Internal method that generates embeddings and upserts chunks into the database.
|
|
723
|
-
|
|
724
|
-
```typescript
|
|
725
|
-
async createAndUpsertEmbeddings(
|
|
726
|
-
item: Item,
|
|
727
|
-
config: ExuluConfig,
|
|
728
|
-
user?: number,
|
|
729
|
-
statistics?: ExuluStatisticParams,
|
|
730
|
-
role?: string,
|
|
731
|
-
job?: string
|
|
732
|
-
): Promise<{
|
|
733
|
-
id: string;
|
|
734
|
-
chunks?: number;
|
|
735
|
-
job?: string;
|
|
736
|
-
}>
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
<Warning>
|
|
740
|
-
This is an internal method. Use `embeddings.generate.one()` instead.
|
|
741
|
-
</Warning>
|
|
742
|
-
|
|
743
|
-
## Properties
|
|
744
|
-
|
|
745
|
-
### id
|
|
746
|
-
|
|
747
|
-
<ResponseField name="id" type="string">
|
|
748
|
-
Unique context identifier
|
|
749
|
-
</ResponseField>
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
console.log(context.id); // "product_docs"
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
### name
|
|
756
|
-
|
|
757
|
-
<ResponseField name="name" type="string">
|
|
758
|
-
Human-readable context name
|
|
759
|
-
</ResponseField>
|
|
760
|
-
|
|
761
|
-
```typescript
|
|
762
|
-
console.log(context.name); // "Product Documentation"
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
### description
|
|
766
|
-
|
|
767
|
-
<ResponseField name="description" type="string">
|
|
768
|
-
Context description
|
|
769
|
-
</ResponseField>
|
|
770
|
-
|
|
771
|
-
### active
|
|
772
|
-
|
|
773
|
-
<ResponseField name="active" type="boolean">
|
|
774
|
-
Whether the context is active
|
|
775
|
-
</ResponseField>
|
|
776
|
-
|
|
777
|
-
### fields
|
|
778
|
-
|
|
779
|
-
<ResponseField name="fields" type="ExuluContextFieldDefinition[]">
|
|
780
|
-
Array of field definitions
|
|
781
|
-
</ResponseField>
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
context.fields.forEach(field => {
|
|
785
|
-
console.log(`${field.name}: ${field.type}`);
|
|
786
|
-
});
|
|
787
|
-
```
|
|
788
|
-
|
|
789
|
-
### embedder
|
|
790
|
-
|
|
791
|
-
<ResponseField name="embedder" type="ExuluEmbedder | undefined">
|
|
792
|
-
Configured embedder instance
|
|
793
|
-
</ResponseField>
|
|
794
|
-
|
|
795
|
-
### processor
|
|
796
|
-
|
|
797
|
-
<ResponseField name="processor" type="ExuluContextProcessor | undefined">
|
|
798
|
-
Configured processor
|
|
799
|
-
</ResponseField>
|
|
800
|
-
|
|
801
|
-
### sources
|
|
802
|
-
|
|
803
|
-
<ResponseField name="sources" type="ExuluContextSource[]">
|
|
804
|
-
Array of data sources
|
|
805
|
-
</ResponseField>
|
|
806
|
-
|
|
807
|
-
### configuration
|
|
808
|
-
|
|
809
|
-
<ResponseField name="configuration" type="object">
|
|
810
|
-
Context configuration object
|
|
811
|
-
</ResponseField>
|
|
812
|
-
|
|
813
|
-
```typescript
|
|
814
|
-
console.log(context.configuration.maxRetrievalResults);
|
|
815
|
-
console.log(context.configuration.calculateVectors);
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
## Usage examples
|
|
819
|
-
|
|
820
|
-
### Complete CRUD workflow
|
|
821
|
-
|
|
822
|
-
```typescript
|
|
823
|
-
// Create
|
|
824
|
-
const { item } = await context.createItem(
|
|
825
|
-
{
|
|
826
|
-
name: "Getting Started Guide",
|
|
827
|
-
content: "This guide helps you...",
|
|
828
|
-
category: "guide"
|
|
829
|
-
},
|
|
830
|
-
config,
|
|
831
|
-
userId
|
|
832
|
-
);
|
|
833
|
-
|
|
834
|
-
// Read
|
|
835
|
-
const retrieved = await context.getItem({ item: { id: item.id } });
|
|
836
|
-
|
|
837
|
-
// Update
|
|
838
|
-
await context.updateItem(
|
|
839
|
-
{
|
|
840
|
-
id: item.id,
|
|
841
|
-
content: "Updated content..."
|
|
842
|
-
},
|
|
843
|
-
config,
|
|
844
|
-
userId
|
|
845
|
-
);
|
|
846
|
-
|
|
847
|
-
// Search
|
|
848
|
-
const results = await context.search({
|
|
849
|
-
query: "getting started",
|
|
850
|
-
method: "hybridSearch",
|
|
851
|
-
itemFilters: [],
|
|
852
|
-
chunkFilters: [],
|
|
853
|
-
limit: 10,
|
|
854
|
-
page: 1,
|
|
855
|
-
sort: undefined,
|
|
856
|
-
trigger: "api"
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
// Delete
|
|
860
|
-
await context.deleteItem({ id: item.id });
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
### Bulk embeddings generation
|
|
864
|
-
|
|
865
|
-
```typescript
|
|
866
|
-
// Generate embeddings for specific items
|
|
867
|
-
const items = await context.getItems({
|
|
868
|
-
filters: [
|
|
869
|
-
{ field: "embeddings_updated_at", operator: "IS", value: null }
|
|
870
|
-
]
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
for (const item of items) {
|
|
874
|
-
await context.embeddings.generate.one({
|
|
875
|
-
item,
|
|
876
|
-
trigger: "api",
|
|
877
|
-
config
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// Or generate for all items
|
|
882
|
-
await context.embeddings.generate.all(config);
|
|
883
|
-
```
|
|
884
|
-
|
|
885
|
-
### Custom search with filters
|
|
886
|
-
|
|
887
|
-
```typescript
|
|
888
|
-
const results = await context.search({
|
|
889
|
-
query: "authentication configuration",
|
|
890
|
-
keywords: ["oauth", "jwt"],
|
|
891
|
-
method: "hybridSearch",
|
|
892
|
-
itemFilters: [
|
|
893
|
-
{ field: "category", operator: "=", value: "guide" },
|
|
894
|
-
{ field: "archived", operator: "=", value: false }
|
|
895
|
-
],
|
|
896
|
-
chunkFilters: [],
|
|
897
|
-
user: currentUser,
|
|
898
|
-
role: currentUser.role.id,
|
|
899
|
-
limit: 20,
|
|
900
|
-
page: 1,
|
|
901
|
-
sort: undefined,
|
|
902
|
-
trigger: "api",
|
|
903
|
-
cutoffs: {
|
|
904
|
-
hybrid: 0.7 // Higher relevance threshold
|
|
905
|
-
},
|
|
906
|
-
expand: {
|
|
907
|
-
before: 2,
|
|
908
|
-
after: 2 // More context
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
```
|