@nordsym/apiclaw 1.3.12 → 1.4.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/PRD-API-CHAINING.md +483 -0
- package/PRD-HARDEN-SHELL.md +278 -0
- package/README.md +72 -0
- package/convex/_generated/api.d.ts +4 -0
- package/convex/chains.ts +1095 -0
- package/convex/crons.ts +11 -0
- package/convex/logs.ts +107 -0
- package/convex/schema.ts +107 -0
- package/convex/spendAlerts.ts +442 -0
- package/convex/workspaces.ts +26 -0
- package/dist/chain-types.d.ts +187 -0
- package/dist/chain-types.d.ts.map +1 -0
- package/dist/chain-types.js +33 -0
- package/dist/chain-types.js.map +1 -0
- package/dist/chainExecutor.d.ts +122 -0
- package/dist/chainExecutor.d.ts.map +1 -0
- package/dist/chainExecutor.js +454 -0
- package/dist/chainExecutor.js.map +1 -0
- package/dist/chainResolver.d.ts +100 -0
- package/dist/chainResolver.d.ts.map +1 -0
- package/dist/chainResolver.js +519 -0
- package/dist/chainResolver.js.map +1 -0
- package/dist/chainResolver.test.d.ts +5 -0
- package/dist/chainResolver.test.d.ts.map +1 -0
- package/dist/chainResolver.test.js +201 -0
- package/dist/chainResolver.test.js.map +1 -0
- package/dist/execute.d.ts +5 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +207 -118
- package/dist/execute.js.map +1 -1
- package/dist/index.js +382 -2
- package/dist/index.js.map +1 -1
- package/landing/package-lock.json +29 -5
- package/landing/package.json +2 -1
- package/landing/public/logos/chattgpt.svg +1 -0
- package/landing/public/logos/claude.svg +1 -0
- package/landing/public/logos/gemini.svg +1 -0
- package/landing/public/logos/grok.svg +1 -0
- package/landing/src/app/page.tsx +11 -0
- package/landing/src/app/security/page.tsx +381 -0
- package/landing/src/app/workspace/chains/page.tsx +520 -0
- package/landing/src/components/AITestimonials.tsx +195 -0
- package/landing/src/components/ChainStepDetail.tsx +310 -0
- package/landing/src/components/ChainTrace.tsx +261 -0
- package/landing/src/lib/stats.json +1 -1
- package/package.json +1 -1
- package/src/chain-types.ts +270 -0
- package/src/chainExecutor.ts +730 -0
- package/src/chainResolver.test.ts +246 -0
- package/src/chainResolver.ts +658 -0
- package/src/execute.ts +273 -114
- package/src/index.ts +423 -2
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# APIClaw: Orchestration Layer
|
|
2
|
+
|
|
3
|
+
**Status:** SPEC
|
|
4
|
+
**Philosophy:** Agents don't call APIs. They express intent. APIClaw executes.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Core Truth
|
|
9
|
+
|
|
10
|
+
An agent that needs to:
|
|
11
|
+
1. Generate an image
|
|
12
|
+
2. Upload it somewhere
|
|
13
|
+
3. Email the link to someone
|
|
14
|
+
|
|
15
|
+
Should not think about this as three separate operations. It's one intent: *"Create and deliver an image."*
|
|
16
|
+
|
|
17
|
+
APIClaw is the layer that translates intent into execution.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## The Interface
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
call_api({
|
|
25
|
+
intent: "generate_and_deliver_image",
|
|
26
|
+
|
|
27
|
+
// Or explicit chain:
|
|
28
|
+
chain: [
|
|
29
|
+
{
|
|
30
|
+
id: "generate",
|
|
31
|
+
provider: "replicate",
|
|
32
|
+
action: "run",
|
|
33
|
+
params: {
|
|
34
|
+
model: "stability-ai/sdxl",
|
|
35
|
+
prompt: "A sunset over mountains"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "upload",
|
|
40
|
+
provider: "cloudinary",
|
|
41
|
+
action: "upload",
|
|
42
|
+
params: {
|
|
43
|
+
url: "$generate.url"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "notify",
|
|
48
|
+
provider: "resend",
|
|
49
|
+
action: "send",
|
|
50
|
+
params: {
|
|
51
|
+
to: "client@example.com",
|
|
52
|
+
subject: "Your image",
|
|
53
|
+
html: "<img src='$upload.secure_url' />"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
|
|
58
|
+
// Execution modifiers
|
|
59
|
+
parallel: ["generate_alt_1", "generate_alt_2"], // Run these in parallel
|
|
60
|
+
continueOnError: false, // Stop on first failure
|
|
61
|
+
timeout: 30000, // Max execution time
|
|
62
|
+
retry: { maxAttempts: 3, backoff: "exponential" } // Retry policy
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Reference System
|
|
69
|
+
|
|
70
|
+
Steps reference each other by ID. Full JSONPath support.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Direct reference
|
|
74
|
+
"$stepId.property"
|
|
75
|
+
|
|
76
|
+
// Nested access
|
|
77
|
+
"$generate.output.images[0].url"
|
|
78
|
+
|
|
79
|
+
// Previous step shorthand
|
|
80
|
+
"$prev.url"
|
|
81
|
+
|
|
82
|
+
// Array from parallel execution
|
|
83
|
+
"$parallel[0].url"
|
|
84
|
+
|
|
85
|
+
// Conditional reference
|
|
86
|
+
"$generate.success ? $generate.url : $fallback.url"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Built-in Variables
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
"$chain.startedAt" // ISO timestamp
|
|
93
|
+
"$chain.workspace" // Workspace ID
|
|
94
|
+
"$chain.index" // Current step index
|
|
95
|
+
"$env.CUSTOM_VAR" // Workspace environment variables
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Execution Modes
|
|
101
|
+
|
|
102
|
+
### Sequential (Default)
|
|
103
|
+
```typescript
|
|
104
|
+
chain: [A, B, C] // A → B → C
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Parallel
|
|
108
|
+
```typescript
|
|
109
|
+
chain: [
|
|
110
|
+
{
|
|
111
|
+
parallel: [
|
|
112
|
+
{ id: "img1", provider: "replicate", ... },
|
|
113
|
+
{ id: "img2", provider: "replicate", ... },
|
|
114
|
+
{ id: "img3", provider: "replicate", ... }
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "combine",
|
|
119
|
+
provider: "cloudinary",
|
|
120
|
+
action: "create_collage",
|
|
121
|
+
params: {
|
|
122
|
+
images: ["$img1.url", "$img2.url", "$img3.url"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Conditional
|
|
129
|
+
```typescript
|
|
130
|
+
chain: [
|
|
131
|
+
{ id: "analyze", provider: "openrouter", action: "chat", ... },
|
|
132
|
+
{
|
|
133
|
+
if: "$analyze.intent === 'image'",
|
|
134
|
+
then: { id: "gen", provider: "replicate", ... },
|
|
135
|
+
else: { id: "search", provider: "brave", ... }
|
|
136
|
+
},
|
|
137
|
+
{ id: "deliver", provider: "resend", params: { content: "$prev.result" } }
|
|
138
|
+
]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Loop
|
|
142
|
+
```typescript
|
|
143
|
+
chain: [
|
|
144
|
+
{ id: "get_urls", provider: "brave", action: "search", ... },
|
|
145
|
+
{
|
|
146
|
+
forEach: "$get_urls.results",
|
|
147
|
+
as: "result",
|
|
148
|
+
do: {
|
|
149
|
+
id: "scrape_$index",
|
|
150
|
+
provider: "firecrawl",
|
|
151
|
+
action: "scrape",
|
|
152
|
+
params: { url: "$result.url" }
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "summarize",
|
|
157
|
+
provider: "openrouter",
|
|
158
|
+
action: "chat",
|
|
159
|
+
params: { content: "$forEach.results" }
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Map-Reduce
|
|
165
|
+
```typescript
|
|
166
|
+
chain: [
|
|
167
|
+
{
|
|
168
|
+
map: {
|
|
169
|
+
input: "$urls",
|
|
170
|
+
fn: { provider: "firecrawl", action: "scrape", params: { url: "$item" } }
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
reduce: {
|
|
175
|
+
input: "$map.results",
|
|
176
|
+
fn: { provider: "openrouter", action: "chat", params: { summarize: "$items" } }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Error Handling
|
|
185
|
+
|
|
186
|
+
### Per-Step Configuration
|
|
187
|
+
```typescript
|
|
188
|
+
{
|
|
189
|
+
id: "critical_step",
|
|
190
|
+
provider: "stripe",
|
|
191
|
+
action: "charge",
|
|
192
|
+
params: { ... },
|
|
193
|
+
|
|
194
|
+
onError: {
|
|
195
|
+
retry: { attempts: 3, backoff: [1000, 2000, 4000] },
|
|
196
|
+
fallback: { id: "fallback_step", ... },
|
|
197
|
+
abort: false // Continue chain even if this fails
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Chain-Level Policies
|
|
203
|
+
```typescript
|
|
204
|
+
chain: [...],
|
|
205
|
+
errorPolicy: {
|
|
206
|
+
mode: "fail-fast" | "best-effort" | "transactional",
|
|
207
|
+
|
|
208
|
+
// Transactional mode: rollback on failure
|
|
209
|
+
rollback: [
|
|
210
|
+
{ if: "stripe.charge.success", do: { provider: "stripe", action: "refund", ... } }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Error Response
|
|
216
|
+
```typescript
|
|
217
|
+
{
|
|
218
|
+
success: false,
|
|
219
|
+
completedSteps: ["generate", "upload"],
|
|
220
|
+
failedStep: {
|
|
221
|
+
id: "notify",
|
|
222
|
+
error: "Rate limited",
|
|
223
|
+
code: "RATE_LIMITED",
|
|
224
|
+
retryAfter: 30
|
|
225
|
+
},
|
|
226
|
+
partialResults: {
|
|
227
|
+
generate: { url: "https://..." },
|
|
228
|
+
upload: { secure_url: "https://..." }
|
|
229
|
+
},
|
|
230
|
+
canResume: true,
|
|
231
|
+
resumeToken: "chain_abc123_step_3"
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Resumable Chains
|
|
238
|
+
|
|
239
|
+
Long-running or failed chains can be resumed:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
call_api({
|
|
243
|
+
resume: "chain_abc123_step_3",
|
|
244
|
+
// Optional: override params for the failed step
|
|
245
|
+
overrides: {
|
|
246
|
+
notify: { to: "different@email.com" }
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Stored Chains (Templates)
|
|
254
|
+
|
|
255
|
+
Save chains as reusable templates:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Define once
|
|
259
|
+
call_api({
|
|
260
|
+
defineChain: {
|
|
261
|
+
name: "content_pipeline",
|
|
262
|
+
description: "Generate, optimize, publish, notify",
|
|
263
|
+
inputs: {
|
|
264
|
+
prompt: { type: "string", required: true },
|
|
265
|
+
email: { type: "string", required: true }
|
|
266
|
+
},
|
|
267
|
+
chain: [
|
|
268
|
+
{ provider: "replicate", params: { prompt: "$inputs.prompt" } },
|
|
269
|
+
{ provider: "cloudinary", params: { url: "$prev.url" } },
|
|
270
|
+
{ provider: "ghost", params: { image: "$prev.secure_url" } },
|
|
271
|
+
{ provider: "resend", params: { to: "$inputs.email", content: "$prev.url" } }
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Use anywhere
|
|
277
|
+
call_api({
|
|
278
|
+
useChain: "content_pipeline",
|
|
279
|
+
inputs: {
|
|
280
|
+
prompt: "A cyberpunk cityscape",
|
|
281
|
+
email: "client@example.com"
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Webhooks & Async
|
|
289
|
+
|
|
290
|
+
For long-running chains:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
call_api({
|
|
294
|
+
chain: [...],
|
|
295
|
+
async: true,
|
|
296
|
+
webhook: "https://your-server.com/chain-complete",
|
|
297
|
+
// Or poll:
|
|
298
|
+
pollInterval: 5000
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
// Response:
|
|
302
|
+
{
|
|
303
|
+
chainId: "chain_xyz",
|
|
304
|
+
status: "running",
|
|
305
|
+
estimatedCompletion: "2026-03-02T01:15:00Z",
|
|
306
|
+
statusUrl: "https://apiclaw.com/api/chain/chain_xyz/status"
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Observability
|
|
313
|
+
|
|
314
|
+
Every chain execution is fully traced:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
{
|
|
318
|
+
chainId: "chain_xyz",
|
|
319
|
+
trace: [
|
|
320
|
+
{
|
|
321
|
+
stepId: "generate",
|
|
322
|
+
startedAt: "...",
|
|
323
|
+
completedAt: "...",
|
|
324
|
+
latencyMs: 2340,
|
|
325
|
+
provider: "replicate",
|
|
326
|
+
cost: { cents: 2 },
|
|
327
|
+
input: { ... },
|
|
328
|
+
output: { ... }
|
|
329
|
+
},
|
|
330
|
+
// ...
|
|
331
|
+
],
|
|
332
|
+
totalLatencyMs: 4521,
|
|
333
|
+
totalCost: { cents: 5 },
|
|
334
|
+
tokensSaved: 1600 // vs discrete calls
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Dashboard shows:
|
|
339
|
+
- Chain execution timeline (Gantt-style)
|
|
340
|
+
- Cost breakdown per step
|
|
341
|
+
- Error rates per provider
|
|
342
|
+
- Most-used chain templates
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Security
|
|
347
|
+
|
|
348
|
+
### Credential Isolation
|
|
349
|
+
Each step runs with only the credentials it needs. Step B cannot access Step A's provider credentials.
|
|
350
|
+
|
|
351
|
+
### Input Validation
|
|
352
|
+
References are validated before execution:
|
|
353
|
+
- `$nonexistent.url` → Error before any step runs
|
|
354
|
+
- Type mismatches caught early
|
|
355
|
+
|
|
356
|
+
### Rate Limiting
|
|
357
|
+
Chain-level rate limits prevent runaway execution:
|
|
358
|
+
```typescript
|
|
359
|
+
chain: [...],
|
|
360
|
+
limits: {
|
|
361
|
+
maxSteps: 20,
|
|
362
|
+
maxParallel: 5,
|
|
363
|
+
maxCost: { cents: 100 }
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Pricing
|
|
370
|
+
|
|
371
|
+
**Chain execution = sum of step costs.**
|
|
372
|
+
|
|
373
|
+
No orchestration fee. Chaining is a feature, not a product.
|
|
374
|
+
|
|
375
|
+
Why: Chains reduce our compute overhead (1 request vs N). We pass savings to users.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## The Complete Picture
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
383
|
+
│ AGENT │
|
|
384
|
+
│ │
|
|
385
|
+
│ "Generate 3 variations, pick the best, publish, notify team" │
|
|
386
|
+
│ │
|
|
387
|
+
└─────────────────────────┬───────────────────────────────────────┘
|
|
388
|
+
│
|
|
389
|
+
▼
|
|
390
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
391
|
+
│ call_api({ chain: [...] }) │
|
|
392
|
+
└─────────────────────────┬───────────────────────────────────────┘
|
|
393
|
+
│
|
|
394
|
+
▼
|
|
395
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
396
|
+
│ APICLAW ORCHESTRATOR │
|
|
397
|
+
│ │
|
|
398
|
+
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|
399
|
+
│ │ Parse │→ │Validate │→ │ Plan │ │
|
|
400
|
+
│ │ Chain │ │ Refs │ │ Exec │ │
|
|
401
|
+
│ └─────────┘ └─────────┘ └─────────┘ │
|
|
402
|
+
│ │ │
|
|
403
|
+
│ ▼ │
|
|
404
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
405
|
+
│ │ EXECUTION ENGINE │ │
|
|
406
|
+
│ │ │ │
|
|
407
|
+
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
|
|
408
|
+
│ │ │ A │───→│ B │───→│ C │ Sequential │ │
|
|
409
|
+
│ │ └─────┘ └─────┘ └─────┘ │ │
|
|
410
|
+
│ │ │ │
|
|
411
|
+
│ │ ┌─────┐ │ │
|
|
412
|
+
│ │ │ A │─┐ │ │
|
|
413
|
+
│ │ └─────┘ │ ┌─────┐ │ │
|
|
414
|
+
│ │ ┌─────┐ ├─→│ D │ Parallel + Join │ │
|
|
415
|
+
│ │ │ B │─┤ └─────┘ │ │
|
|
416
|
+
│ │ └─────┘ │ │ │
|
|
417
|
+
│ │ ┌─────┐ │ │ │
|
|
418
|
+
│ │ │ C │─┘ │ │
|
|
419
|
+
│ │ └─────┘ │ │
|
|
420
|
+
│ │ │ │
|
|
421
|
+
│ │ ┌─────┐ ┌─────┐ │ │
|
|
422
|
+
│ │ │ A │───→│ B? │───→ B₁ or B₂ Conditional │ │
|
|
423
|
+
│ │ └─────┘ └─────┘ │ │
|
|
424
|
+
│ │ │ │
|
|
425
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
426
|
+
│ │ │
|
|
427
|
+
│ ▼ │
|
|
428
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
429
|
+
│ │ Replicate │ Cloudinary │ Resend │ Stripe │ ... 18 more │ │
|
|
430
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
431
|
+
│ │
|
|
432
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
433
|
+
│
|
|
434
|
+
▼
|
|
435
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
436
|
+
│ AGENT │
|
|
437
|
+
│ │
|
|
438
|
+
│ { success: true, finalResult: { publishedUrl: "..." } } │
|
|
439
|
+
│ │
|
|
440
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## What This Enables
|
|
446
|
+
|
|
447
|
+
### Self-Orchestrating Agents
|
|
448
|
+
Agent receives a complex task → breaks it into chain → executes atomically → reports result.
|
|
449
|
+
|
|
450
|
+
No human writing glue code. No n8n. No Zapier. The agent IS the orchestrator, APIClaw is the executor.
|
|
451
|
+
|
|
452
|
+
### Compound Actions
|
|
453
|
+
"Research this company and draft an outreach email" becomes one chain:
|
|
454
|
+
```
|
|
455
|
+
Brave → Firecrawl (×3) → OpenRouter (analyze) → OpenRouter (draft) → Resend (preview)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Agent-to-Agent Workflows
|
|
459
|
+
Agent A triggers a chain that includes calling Agent B's endpoint, which triggers its own chain.
|
|
460
|
+
|
|
461
|
+
Recursive. Distributed. Autonomous.
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## The Positioning
|
|
466
|
+
|
|
467
|
+
> "APIClaw is not an API aggregator. It's the execution layer for autonomous AI."
|
|
468
|
+
|
|
469
|
+
> "Agents don't integrate APIs. They declare intent. APIClaw handles the rest."
|
|
470
|
+
|
|
471
|
+
> "From tool-calling to orchestration. From 10 round-trips to 1."
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
*"A chain of three call_api calls with no context switching."*
|
|
476
|
+
|
|
477
|
+
*Three? Try thirty. Try three hundred.*
|
|
478
|
+
|
|
479
|
+
*No switching. No waiting. No glue code.*
|
|
480
|
+
|
|
481
|
+
*Just execution.*
|
|
482
|
+
|
|
483
|
+
🦞
|