@qodo/sdk 0.7.0 → 0.8.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/.claude/skills/qodo-agent/SKILL.md +233 -3
- package/.claude/skills/qodo-agent/assets/programmatic-agent.ts +74 -1
- package/.claude/skills/qodo-agent/references/common-issues.md +79 -0
- package/dist/api/agent.d.ts.map +1 -1
- package/dist/api/agent.js +13 -8
- package/dist/api/agent.js.map +1 -1
- package/dist/api/http.d.ts.map +1 -1
- package/dist/api/http.js +5 -2
- package/dist/api/http.js.map +1 -1
- package/dist/api/websocket.d.ts.map +1 -1
- package/dist/api/websocket.js +6 -3
- package/dist/api/websocket.js.map +1 -1
- package/dist/auth/index.d.ts +18 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +69 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/context/messageManager.d.ts +1 -1
- package/dist/context/messageManager.d.ts.map +1 -1
- package/dist/context/messageManager.js +1 -1
- package/dist/context/messageManager.js.map +1 -1
- package/dist/index.d.ts +21 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/MCPManager.js +2 -2
- package/dist/mcp/MCPManager.js.map +1 -1
- package/dist/mcp/baseServer.js +1 -1
- package/dist/mcp/baseServer.js.map +1 -1
- package/dist/mcp/servers/ripgrep.d.ts.map +1 -1
- package/dist/mcp/servers/ripgrep.js +6 -2
- package/dist/mcp/servers/ripgrep.js.map +1 -1
- package/dist/mcp/servers/shell.js +1 -1
- package/dist/mcp/servers/shell.js.map +1 -1
- package/dist/mcp/serversRegistry.d.ts.map +1 -1
- package/dist/mcp/serversRegistry.js +7 -1
- package/dist/mcp/serversRegistry.js.map +1 -1
- package/dist/mcp/toolProcessor.d.ts +44 -4
- package/dist/mcp/toolProcessor.d.ts.map +1 -1
- package/dist/mcp/toolProcessor.js +255 -20
- package/dist/mcp/toolProcessor.js.map +1 -1
- package/dist/messages/index.d.ts +8 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +7 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/openai.d.ts +26 -0
- package/dist/messages/openai.d.ts.map +1 -0
- package/dist/messages/openai.js +55 -0
- package/dist/messages/openai.js.map +1 -0
- package/dist/messages/types.d.ts +73 -0
- package/dist/messages/types.d.ts.map +1 -0
- package/dist/messages/types.js +78 -0
- package/dist/messages/types.js.map +1 -0
- package/dist/parser/index.js +3 -3
- package/dist/parser/index.js.map +1 -1
- package/dist/sdk/QodoSDK.d.ts +30 -10
- package/dist/sdk/QodoSDK.d.ts.map +1 -1
- package/dist/sdk/QodoSDK.js +165 -33
- package/dist/sdk/QodoSDK.js.map +1 -1
- package/dist/sdk/artifacts.d.ts +156 -0
- package/dist/sdk/artifacts.d.ts.map +1 -0
- package/dist/sdk/artifacts.js +166 -0
- package/dist/sdk/artifacts.js.map +1 -0
- package/dist/sdk/bootstrap.d.ts.map +1 -1
- package/dist/sdk/bootstrap.js +11 -4
- package/dist/sdk/bootstrap.js.map +1 -1
- package/dist/sdk/discovery.js +1 -1
- package/dist/sdk/discovery.js.map +1 -1
- package/dist/sdk/events.d.ts +46 -1
- package/dist/sdk/events.d.ts.map +1 -1
- package/dist/sdk/events.js +6 -0
- package/dist/sdk/events.js.map +1 -1
- package/dist/sdk/middleware.d.ts +59 -0
- package/dist/sdk/middleware.d.ts.map +1 -0
- package/dist/sdk/middleware.js +69 -0
- package/dist/sdk/middleware.js.map +1 -0
- package/dist/sdk/pipeline/PipelineBuilder.d.ts +61 -0
- package/dist/sdk/pipeline/PipelineBuilder.d.ts.map +1 -0
- package/dist/sdk/pipeline/PipelineBuilder.js +91 -0
- package/dist/sdk/pipeline/PipelineBuilder.js.map +1 -0
- package/dist/sdk/pipeline/PipelineRunner.d.ts +28 -0
- package/dist/sdk/pipeline/PipelineRunner.d.ts.map +1 -0
- package/dist/sdk/pipeline/PipelineRunner.js +210 -0
- package/dist/sdk/pipeline/PipelineRunner.js.map +1 -0
- package/dist/sdk/pipeline/compiler.d.ts +24 -0
- package/dist/sdk/pipeline/compiler.d.ts.map +1 -0
- package/dist/sdk/pipeline/compiler.js +197 -0
- package/dist/sdk/pipeline/compiler.js.map +1 -0
- package/dist/sdk/pipeline/declarative.d.ts +34 -0
- package/dist/sdk/pipeline/declarative.d.ts.map +1 -0
- package/dist/sdk/pipeline/declarative.js +9 -0
- package/dist/sdk/pipeline/declarative.js.map +1 -0
- package/dist/sdk/pipeline/index.d.ts +20 -0
- package/dist/sdk/pipeline/index.d.ts.map +1 -0
- package/dist/sdk/pipeline/index.js +19 -0
- package/dist/sdk/pipeline/index.js.map +1 -0
- package/dist/sdk/pipeline/types.d.ts +75 -0
- package/dist/sdk/pipeline/types.d.ts.map +1 -0
- package/dist/sdk/pipeline/types.js +10 -0
- package/dist/sdk/pipeline/types.js.map +1 -0
- package/dist/sdk/policies.d.ts +163 -0
- package/dist/sdk/policies.d.ts.map +1 -0
- package/dist/sdk/policies.js +243 -0
- package/dist/sdk/policies.js.map +1 -0
- package/dist/sdk/runner/AgentRunner.js +5 -5
- package/dist/sdk/runner/AgentRunner.js.map +1 -1
- package/dist/sdk/runner/finalize.d.ts +47 -0
- package/dist/sdk/runner/finalize.d.ts.map +1 -1
- package/dist/sdk/runner/finalize.js +42 -2
- package/dist/sdk/runner/finalize.js.map +1 -1
- package/dist/sdk/runner/formats.d.ts +1 -1
- package/dist/sdk/runner/formats.d.ts.map +1 -1
- package/dist/sdk/runner/formats.js +22 -37
- package/dist/sdk/runner/formats.js.map +1 -1
- package/dist/sdk/runner/progress.d.ts +1 -1
- package/dist/sdk/runner/progress.d.ts.map +1 -1
- package/dist/sdk/runner/progress.js +1 -1
- package/dist/sdk/runner/progress.js.map +1 -1
- package/dist/session/SessionContext.d.ts +1 -0
- package/dist/session/SessionContext.d.ts.map +1 -1
- package/dist/session/SessionContext.js +3 -0
- package/dist/session/SessionContext.js.map +1 -1
- package/dist/session/environment.d.ts +16 -6
- package/dist/session/environment.d.ts.map +1 -1
- package/dist/session/environment.js.map +1 -1
- package/dist/session/serverData.d.ts.map +1 -1
- package/dist/session/serverData.js +21 -3
- package/dist/session/serverData.js.map +1 -1
- package/dist/tracing/PipelineTracer.d.ts +37 -0
- package/dist/tracing/PipelineTracer.d.ts.map +1 -0
- package/dist/tracing/PipelineTracer.js +64 -0
- package/dist/tracing/PipelineTracer.js.map +1 -0
- package/dist/tracing/SdkTracer.d.ts +91 -0
- package/dist/tracing/SdkTracer.d.ts.map +1 -0
- package/dist/tracing/SdkTracer.js +243 -0
- package/dist/tracing/SdkTracer.js.map +1 -0
- package/dist/tracing/index.d.ts +6 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +3 -0
- package/dist/tracing/index.js.map +1 -0
- package/dist/tracing/pipelineHelpers.d.ts +20 -0
- package/dist/tracing/pipelineHelpers.d.ts.map +1 -0
- package/dist/tracing/pipelineHelpers.js +140 -0
- package/dist/tracing/pipelineHelpers.js.map +1 -0
- package/dist/tracing/types.d.ts +46 -0
- package/dist/tracing/types.d.ts.map +1 -0
- package/dist/tracing/types.js +9 -0
- package/dist/tracing/types.js.map +1 -0
- package/package.json +10 -7
|
@@ -39,6 +39,47 @@ Use this skill when working with `@qodo/sdk` to build AI-powered agents. This sk
|
|
|
39
39
|
| `parseArgsWithSchema()` | Validate args with Zod, get typed result |
|
|
40
40
|
| `QodoSchemaError` | Schema validation error class |
|
|
41
41
|
|
|
42
|
+
### Pipeline DSL
|
|
43
|
+
|
|
44
|
+
| Export | Purpose |
|
|
45
|
+
|--------|---------|
|
|
46
|
+
| `sdkPipeline()` | Build multi-step agent workflows |
|
|
47
|
+
| `runPipeline()` | Execute a pipeline with an SDK instance |
|
|
48
|
+
| `PipelineExecutionError` | Pipeline step failure error |
|
|
49
|
+
|
|
50
|
+
### Tool Middleware
|
|
51
|
+
|
|
52
|
+
| Export | Purpose |
|
|
53
|
+
|--------|---------|
|
|
54
|
+
| `ToolMiddleware` (type) | Pre/post execution hook interface |
|
|
55
|
+
| `ToolMiddlewareError` | Middleware failure error |
|
|
56
|
+
| `MIDDLEWARE_TIMEOUT_MS` | Default hook timeout (10s) |
|
|
57
|
+
| `PROTECTED_TOOL_FIELDS` | Fields middleware cannot overwrite |
|
|
58
|
+
|
|
59
|
+
### Tool Approval Policies
|
|
60
|
+
|
|
61
|
+
| Export | Purpose |
|
|
62
|
+
|--------|---------|
|
|
63
|
+
| `sdkPolicy()` | Fluent builder for declarative tool approval rules |
|
|
64
|
+
| `policyFromRules()` | Build a policy from a rule array |
|
|
65
|
+
| `globMatch()` | Glob pattern matching utility |
|
|
66
|
+
|
|
67
|
+
### Artifacts
|
|
68
|
+
|
|
69
|
+
| Export | Purpose |
|
|
70
|
+
|--------|---------|
|
|
71
|
+
| `ArtifactStore` | Store/persist intermediate pipeline results |
|
|
72
|
+
| `withArtifacts()` | Wrap a pipeline step to auto-store output |
|
|
73
|
+
| `jsonSerializer` | Default JSON serializer |
|
|
74
|
+
|
|
75
|
+
### OpenTelemetry Tracing
|
|
76
|
+
|
|
77
|
+
| Export | Purpose |
|
|
78
|
+
|--------|---------|
|
|
79
|
+
| `createSdkTracer()` | Create event-to-span tracer (duck-typed OTel API) |
|
|
80
|
+
| `tracePipeline()` | Trace pipeline execution with nested spans |
|
|
81
|
+
| `SdkTracer` | Tracer class for manual usage |
|
|
82
|
+
|
|
42
83
|
### Event Handling
|
|
43
84
|
|
|
44
85
|
| Export | Purpose |
|
|
@@ -60,6 +101,9 @@ Use this skill when working with `@qodo/sdk` to build AI-powered agents. This sk
|
|
|
60
101
|
| `QodoSchemaError` | Schema validation failures |
|
|
61
102
|
| `QodoAuthError` | Authentication failures |
|
|
62
103
|
| `QodoBackendBootstrapError` | Backend initialization failures |
|
|
104
|
+
| `QodoOutputValidationError` | Structured output didn't match Zod schema |
|
|
105
|
+
| `ToolMiddlewareError` | Tool middleware hook failure |
|
|
106
|
+
| `PipelineExecutionError` | Pipeline step failure |
|
|
63
107
|
|
|
64
108
|
### Built-in Tools
|
|
65
109
|
|
|
@@ -437,6 +481,178 @@ try {
|
|
|
437
481
|
}
|
|
438
482
|
```
|
|
439
483
|
|
|
484
|
+
### Pattern 8: Pipeline DSL (Multi-Step Workflows)
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { QodoSDK, sdkPipeline, runPipeline } from '@qodo/sdk';
|
|
488
|
+
|
|
489
|
+
const sdk = new QodoSDK({ autoApproveTools: true });
|
|
490
|
+
|
|
491
|
+
const pipeline = sdkPipeline<{ repo: string }>()
|
|
492
|
+
.step('lint', async ({ state, run }) => {
|
|
493
|
+
const r = await run('lint', { args: { path: state.repo } });
|
|
494
|
+
return { lintOutput: r.result.final_output };
|
|
495
|
+
})
|
|
496
|
+
.step('review', async ({ state, run }) => {
|
|
497
|
+
// TypeScript knows state has `repo` + `lintOutput`
|
|
498
|
+
const r = await run('review', {
|
|
499
|
+
extraInstructions: `Lint results:\n${state.lintOutput}`,
|
|
500
|
+
});
|
|
501
|
+
return { reviewOutput: r.result.final_output };
|
|
502
|
+
})
|
|
503
|
+
.build();
|
|
504
|
+
|
|
505
|
+
const result = await runPipeline(sdk, pipeline, { repo: './src' });
|
|
506
|
+
console.log(result.state.reviewOutput);
|
|
507
|
+
console.log(result.steps); // Step logs with timing
|
|
508
|
+
|
|
509
|
+
await sdk.dispose();
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Features: typed state threading, `.parallel([...])`, gates, error strategies (`'skip'`, `'throw'`, custom recovery).
|
|
513
|
+
|
|
514
|
+
### Pattern 9: Dry Run Mode
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import { QodoSDK } from '@qodo/sdk';
|
|
518
|
+
|
|
519
|
+
const sdk = new QodoSDK({ autoApproveTools: true });
|
|
520
|
+
|
|
521
|
+
// Preview what the agent would do without executing tools
|
|
522
|
+
const result = await sdk.run('review', {
|
|
523
|
+
args: { path: './src' },
|
|
524
|
+
dryRun: true,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
console.log(result.result.final_output); // Agent describes planned actions
|
|
528
|
+
console.log(result.meta.dry_run); // true
|
|
529
|
+
|
|
530
|
+
await sdk.dispose();
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Pattern 10: Tool Middleware (Auditing & Control)
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { QodoSDK, type ToolMiddleware } from '@qodo/sdk';
|
|
537
|
+
|
|
538
|
+
const auditLogger: ToolMiddleware = {
|
|
539
|
+
name: 'audit-logger',
|
|
540
|
+
preExecute(toolData) {
|
|
541
|
+
console.log(`[AUDIT] ${toolData.server_name}.${toolData.tool_name}`);
|
|
542
|
+
},
|
|
543
|
+
postExecute(toolData, result) {
|
|
544
|
+
console.log(`[AUDIT] Result: ${result.isError ? 'ERROR' : 'OK'}`);
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const readOnly: ToolMiddleware = {
|
|
549
|
+
name: 'read-only',
|
|
550
|
+
preExecute(toolData) {
|
|
551
|
+
if (['write_file', 'edit_file', 'delete_files'].includes(toolData.tool_name)) {
|
|
552
|
+
throw new Error(`Blocked: ${toolData.tool_name}`);
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const sdk = new QodoSDK({
|
|
558
|
+
autoApproveTools: true,
|
|
559
|
+
toolMiddleware: [auditLogger, readOnly],
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Pattern 11: Tool Approval Policy DSL
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
import { QodoSDK, sdkPolicy } from '@qodo/sdk';
|
|
567
|
+
|
|
568
|
+
const policy = sdkPolicy()
|
|
569
|
+
.approve({ tools: ['read_*', 'list_*', 'git_status', 'git_log'] }, 'allow-reads')
|
|
570
|
+
.deny({ servers: ['shell'] }, 'block-shell')
|
|
571
|
+
.deny({ tools: ['write_file', 'edit_file', 'delete_files'] }, 'block-writes')
|
|
572
|
+
.requireHuman({ tools: ['git_commit'] }, 'review-commits')
|
|
573
|
+
.default('deny')
|
|
574
|
+
.build();
|
|
575
|
+
|
|
576
|
+
const sdk = new QodoSDK({
|
|
577
|
+
autoApproveTools: false,
|
|
578
|
+
toolApproval: policy.evaluate,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Inspect policy decisions:
|
|
582
|
+
const result = policy.evaluateDetailed({
|
|
583
|
+
tool_name: 'read_files', server_name: 'filesystem', tool_args: {},
|
|
584
|
+
});
|
|
585
|
+
console.log(result.decision, result.matchedRule?.name); // 'approve', 'allow-reads'
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Pattern 12: Execution Timeouts and Retries
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
import { QodoSDK } from '@qodo/sdk';
|
|
592
|
+
|
|
593
|
+
const sdk = new QodoSDK({ autoApproveTools: true });
|
|
594
|
+
|
|
595
|
+
const result = await sdk.run('review', {
|
|
596
|
+
args: { path: './src' },
|
|
597
|
+
timeout: 30000, // 30 second limit per attempt
|
|
598
|
+
maxRetries: 2, // Retry up to 2 times on failure
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
if (result.meta.timed_out) {
|
|
602
|
+
console.warn('Run timed out');
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
await sdk.dispose();
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Pattern 13: Artifacts (Pipeline Intermediate Results)
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
import { QodoSDK, sdkPipeline, runPipeline, ArtifactStore, withArtifacts } from '@qodo/sdk';
|
|
612
|
+
import fs from 'fs/promises';
|
|
613
|
+
|
|
614
|
+
const store = new ArtifactStore({
|
|
615
|
+
onPersist: async (artifact) => {
|
|
616
|
+
await fs.writeFile(`./out/${artifact.stepName}.json`, artifact.serialized);
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const pipeline = sdkPipeline<{ repo: string }>()
|
|
621
|
+
.step('lint', withArtifacts(store, async ({ state, run }) => {
|
|
622
|
+
const r = await run('lint', { args: { path: state.repo } });
|
|
623
|
+
return { lintOutput: r.result.final_output };
|
|
624
|
+
}))
|
|
625
|
+
.build();
|
|
626
|
+
|
|
627
|
+
const result = await runPipeline(sdk, pipeline, { repo: './src' });
|
|
628
|
+
console.log(store.all()); // All artifacts
|
|
629
|
+
console.log(store.getErrors()); // Persistence failures (non-fatal)
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Pattern 14: OpenTelemetry Tracing
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
import * as otel from '@opentelemetry/api';
|
|
636
|
+
import { QodoSDK, createSdkTracer, SdkEventType, matchSdkEvent } from '@qodo/sdk';
|
|
637
|
+
|
|
638
|
+
const sdk = new QodoSDK({ autoApproveTools: true });
|
|
639
|
+
const tracer = createSdkTracer(otel, { tracerName: 'my-service' });
|
|
640
|
+
|
|
641
|
+
// Events flow through unchanged, spans created automatically
|
|
642
|
+
for await (const ev of tracer.wrapStream(sdk.stream('review'))) {
|
|
643
|
+
matchSdkEvent(ev, {
|
|
644
|
+
[SdkEventType.MessageDelta]: (e) => process.stdout.write(e.data.delta),
|
|
645
|
+
[SdkEventType.Final]: (e) => console.log('Done:', e.data.success),
|
|
646
|
+
default: () => {},
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
tracer.flush();
|
|
651
|
+
await sdk.dispose();
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
No compile-time OTel dependency — install `@opentelemetry/api` as optional peer dep.
|
|
655
|
+
|
|
440
656
|
---
|
|
441
657
|
|
|
442
658
|
## Zod Schema Patterns
|
|
@@ -519,15 +735,15 @@ const args = z.object({
|
|
|
519
735
|
| Event Type | When Emitted | Key Data Fields |
|
|
520
736
|
|------------|--------------|-----------------|
|
|
521
737
|
| `sdk.init` | SDK initialized | `sdk_version`, `backend.base_url`, `model` |
|
|
522
|
-
| `sdk.run.started` | Run begins | `session_id`, `command`, `prompt_mode`, `cwd` |
|
|
738
|
+
| `sdk.run.started` | Run begins | `session_id`, `command`, `prompt_mode`, `cwd`, `dry_run?` |
|
|
523
739
|
| `sdk.message.delta` | Streaming text | `delta`, `message_id`, `role` |
|
|
524
740
|
| `sdk.message.full` | Full message update | `messages.langchain`, `messages.openai` |
|
|
525
741
|
| `sdk.progress` | Progress update | `title`, `status`, `percent` |
|
|
526
742
|
| `sdk.tool.requested` | Tool call requested | `tool_call_id`, `server_name`, `tool_name`, `tool_args`, `pending_approval` |
|
|
527
743
|
| `sdk.tool.approved` | Tool approved/denied | `tool_call_id`, `approved`, `reason` |
|
|
528
|
-
| `sdk.tool.executed` | Tool finished | `tool_call_id`, `result.isError`, `result.content` |
|
|
744
|
+
| `sdk.tool.executed` | Tool finished | `tool_call_id`, `result.isError`, `result.content`, `dry_run?` |
|
|
529
745
|
| `sdk.error` | Error occurred | `message`, `cause` |
|
|
530
|
-
| `sdk.final` | Run completed | `success`, `result.structured_output`, `result.final_output`, `
|
|
746
|
+
| `sdk.final` | Run completed | `success`, `result.structured_output`, `result.final_output`, `meta.dry_run?`, `meta.timed_out?` |
|
|
531
747
|
|
|
532
748
|
---
|
|
533
749
|
|
|
@@ -552,6 +768,7 @@ interface QodoSDKOptions {
|
|
|
552
768
|
// Tool handling
|
|
553
769
|
autoApproveTools?: boolean; // Auto-approve tools (default: true)
|
|
554
770
|
toolApproval?: (req) => Promise<boolean> | boolean; // Custom approval
|
|
771
|
+
toolMiddleware?: ToolMiddleware[]; // Pre/post execution hooks
|
|
555
772
|
|
|
556
773
|
// Session
|
|
557
774
|
contextSessionIds?: string[]; // Previous sessions for context
|
|
@@ -654,3 +871,16 @@ z.object({
|
|
|
654
871
|
- [assets/programmatic-agent.ts](assets/programmatic-agent.ts) - Complete working TypeScript agent template
|
|
655
872
|
- [references/builtin-tools.md](references/builtin-tools.md) - Detailed guide to filesystem, git, ripgrep, and shell tools
|
|
656
873
|
- [references/common-issues.md](references/common-issues.md) - Troubleshooting guide for common problems
|
|
874
|
+
|
|
875
|
+
### Documentation (in `docs/`)
|
|
876
|
+
|
|
877
|
+
| Feature | Doc |
|
|
878
|
+
|---------|-----|
|
|
879
|
+
| Pipeline DSL | [docs/pipeline.md](../../../docs/pipeline.md) |
|
|
880
|
+
| Dry Run Mode | [docs/dry-run.md](../../../docs/dry-run.md) |
|
|
881
|
+
| Tool Middleware | [docs/tool-middleware.md](../../../docs/tool-middleware.md) |
|
|
882
|
+
| Structured Output | [docs/structured-output.md](../../../docs/structured-output.md) |
|
|
883
|
+
| Execution Timeouts | [docs/execution-timeouts.md](../../../docs/execution-timeouts.md) |
|
|
884
|
+
| OpenTelemetry | [docs/opentelemetry.md](../../../docs/opentelemetry.md) |
|
|
885
|
+
| Approval Policies | [docs/approval-policies.md](../../../docs/approval-policies.md) |
|
|
886
|
+
| Artifacts | [docs/artifacts.md](../../../docs/artifacts.md) |
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* - sdkAgent() and sdkCommand() builders
|
|
6
6
|
* - Zod schemas with proper descriptions
|
|
7
7
|
* - Event streaming with matchSdkEvent()
|
|
8
|
+
* - Pipeline DSL with typed state threading
|
|
9
|
+
* - Tool middleware for auditing
|
|
10
|
+
* - Dry run mode
|
|
11
|
+
* - Execution timeouts
|
|
8
12
|
* - Error handling
|
|
9
13
|
* - Proper cleanup with dispose()
|
|
10
14
|
*
|
|
@@ -23,7 +27,11 @@ import {
|
|
|
23
27
|
zJsonAny,
|
|
24
28
|
QodoSchemaError,
|
|
25
29
|
QodoAuthError,
|
|
26
|
-
|
|
30
|
+
QodoOutputValidationError,
|
|
31
|
+
sdkPipeline,
|
|
32
|
+
runPipeline,
|
|
33
|
+
z,
|
|
34
|
+
type ToolMiddleware,
|
|
27
35
|
} from '@qodo/sdk';
|
|
28
36
|
|
|
29
37
|
// =============================================================================
|
|
@@ -180,6 +188,17 @@ Guidelines:
|
|
|
180
188
|
// =============================================================================
|
|
181
189
|
|
|
182
190
|
async function main() {
|
|
191
|
+
// Optional: create tool middleware for auditing
|
|
192
|
+
const auditMiddleware: ToolMiddleware = {
|
|
193
|
+
name: 'audit',
|
|
194
|
+
preExecute(toolData) {
|
|
195
|
+
console.log(` [audit] >>> ${toolData.server_name}.${toolData.tool_name}`);
|
|
196
|
+
},
|
|
197
|
+
postExecute(toolData, result) {
|
|
198
|
+
console.log(` [audit] <<< ${result.isError ? 'ERROR' : 'OK'}`);
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
183
202
|
// Create SDK from the agent configuration
|
|
184
203
|
const sdk = QodoSDK.fromAgent(codeAssistantAgent, {
|
|
185
204
|
// Optional: override project path (defaults to cwd)
|
|
@@ -188,6 +207,9 @@ async function main() {
|
|
|
188
207
|
// Optional: enable debug logging
|
|
189
208
|
// debug: true,
|
|
190
209
|
|
|
210
|
+
// Optional: tool middleware for auditing, rate limiting, etc.
|
|
211
|
+
toolMiddleware: [auditMiddleware],
|
|
212
|
+
|
|
191
213
|
// Optional: custom tool approval (default: auto-approve)
|
|
192
214
|
// autoApproveTools: false,
|
|
193
215
|
// toolApproval: async ({ tool_name, tool_args }) => {
|
|
@@ -296,6 +318,54 @@ async function main() {
|
|
|
296
318
|
);
|
|
297
319
|
// @ts-ignore
|
|
298
320
|
console.log('Response:', promptResult.final_output);
|
|
321
|
+
// -------------------------------------------------------------------------
|
|
322
|
+
// Example 4: Pipeline (multi-step workflow)
|
|
323
|
+
// -------------------------------------------------------------------------
|
|
324
|
+
console.log('\n\n--- Pipeline example ---\n');
|
|
325
|
+
|
|
326
|
+
const pipeline = sdkPipeline<{ path: string }>()
|
|
327
|
+
.step('analyze', async ({ state, run }) => {
|
|
328
|
+
const r = await run('analyze', { args: { path: state.path } });
|
|
329
|
+
return { analysis: r.result.structured_output };
|
|
330
|
+
})
|
|
331
|
+
.step('summarize', async ({ state, run }) => {
|
|
332
|
+
const r = await run('', {
|
|
333
|
+
extraInstructions: `Summarize these findings in one paragraph: ${JSON.stringify(state.analysis)}`,
|
|
334
|
+
});
|
|
335
|
+
return { summary: r.result.final_output };
|
|
336
|
+
})
|
|
337
|
+
.build();
|
|
338
|
+
|
|
339
|
+
const pipelineResult = await runPipeline(sdk, pipeline, { path: './src' });
|
|
340
|
+
console.log('Pipeline summary:', pipelineResult.state.summary);
|
|
341
|
+
for (const log of pipelineResult.steps) {
|
|
342
|
+
console.log(` ${log.name}: ${log.status} (${log.durationMs}ms)`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// -------------------------------------------------------------------------
|
|
346
|
+
// Example 5: Dry run mode (preview without side effects)
|
|
347
|
+
// -------------------------------------------------------------------------
|
|
348
|
+
console.log('\n\n--- Dry run example ---\n');
|
|
349
|
+
|
|
350
|
+
const dryResult = await sdk.run('analyze', {
|
|
351
|
+
args: { path: './src' },
|
|
352
|
+
dryRun: true,
|
|
353
|
+
});
|
|
354
|
+
console.log('Dry run output:', dryResult.result.final_output?.slice(0, 200));
|
|
355
|
+
console.log('Was dry run:', dryResult.meta.dry_run);
|
|
356
|
+
|
|
357
|
+
// -------------------------------------------------------------------------
|
|
358
|
+
// Example 6: Execution timeout with retry
|
|
359
|
+
// -------------------------------------------------------------------------
|
|
360
|
+
console.log('\n\n--- Timeout example ---\n');
|
|
361
|
+
|
|
362
|
+
const timedResult = await sdk.run('analyze', {
|
|
363
|
+
args: { path: './src' },
|
|
364
|
+
timeout: 60000, // 60 second limit
|
|
365
|
+
maxRetries: 1, // Retry once on failure
|
|
366
|
+
});
|
|
367
|
+
console.log('Timed out:', timedResult.meta.timed_out ?? false);
|
|
368
|
+
|
|
299
369
|
} catch (error) {
|
|
300
370
|
// Handle specific error types
|
|
301
371
|
if (error instanceof QodoSchemaError) {
|
|
@@ -304,6 +374,9 @@ async function main() {
|
|
|
304
374
|
} else if (error instanceof QodoAuthError) {
|
|
305
375
|
console.error('Authentication error:', error.message);
|
|
306
376
|
console.error('Check your QODO_API_KEY environment variable');
|
|
377
|
+
} else if (error instanceof QodoOutputValidationError) {
|
|
378
|
+
console.error('Output validation error:', error.issues);
|
|
379
|
+
console.error('Raw output:', error.rawOutput);
|
|
307
380
|
} else {
|
|
308
381
|
throw error;
|
|
309
382
|
}
|
|
@@ -422,6 +422,85 @@ const sdk = new QodoSDK({
|
|
|
422
422
|
|
|
423
423
|
---
|
|
424
424
|
|
|
425
|
+
## Pipeline and Middleware Errors
|
|
426
|
+
|
|
427
|
+
### Pipeline Step Failed
|
|
428
|
+
|
|
429
|
+
**Error:**
|
|
430
|
+
```
|
|
431
|
+
PipelineExecutionError: Step "lint" failed: ...
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Cause:** A pipeline step threw an error and no error strategy was set.
|
|
435
|
+
|
|
436
|
+
**Solution:** Add error handling per step:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
sdkPipeline()
|
|
440
|
+
.step('lint', lintFn, { onError: 'skip' }) // Skip on failure
|
|
441
|
+
.step('review', reviewFn, {
|
|
442
|
+
onError: (error, state) => ({ fallback: true }), // Custom recovery
|
|
443
|
+
})
|
|
444
|
+
.build();
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
### Structured Output Validation Failed
|
|
450
|
+
|
|
451
|
+
**Error:**
|
|
452
|
+
```
|
|
453
|
+
QodoOutputValidationError: Structured output validation failed
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**Cause:** The agent returned data that doesn't match the Zod output schema.
|
|
457
|
+
|
|
458
|
+
**Solution:** Check `error.issues` for details and `error.rawOutput` for what the agent actually returned:
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import { QodoOutputValidationError } from '@qodo/sdk';
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
await sdk.run('analyze');
|
|
465
|
+
} catch (e) {
|
|
466
|
+
if (e instanceof QodoOutputValidationError) {
|
|
467
|
+
console.error('Issues:', e.issues); // [{ path: ['score'], message: '...' }]
|
|
468
|
+
console.error('Raw:', e.rawOutput); // What the agent returned
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
### Tool Middleware Error
|
|
476
|
+
|
|
477
|
+
**Error:**
|
|
478
|
+
```
|
|
479
|
+
ToolMiddlewareError: Middleware "my-middleware" failed in preExecute
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Cause:** A tool middleware hook threw or timed out (default: 10s).
|
|
483
|
+
|
|
484
|
+
**Solution:** Check the middleware implementation and handle errors gracefully:
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { ToolMiddlewareError, MIDDLEWARE_TIMEOUT_MS } from '@qodo/sdk';
|
|
488
|
+
|
|
489
|
+
// Middleware hooks should be fast and defensive
|
|
490
|
+
const safeMiddleware = {
|
|
491
|
+
name: 'safe-mw',
|
|
492
|
+
preExecute(toolData) {
|
|
493
|
+
try {
|
|
494
|
+
// Your logic here
|
|
495
|
+
} catch {
|
|
496
|
+
// Don't throw — return void to pass through
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
425
504
|
## Quick Debugging Checklist
|
|
426
505
|
|
|
427
506
|
When something isn't working:
|
package/dist/api/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/api/agent.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,YAAY,EAAC,MAAM,QAAQ,CAAC;AAGpC,OAAO,EAGL,YAAY,EAEb,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAK9C,OAAO,EAAkB,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAuBhE,qBAAa,QAAS,SAAQ,YAAY;IAkC5B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAjC5C,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,cAAc,CAAC,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAsC;IAI1E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,kBAAkB,CAAsB;IAChD,OAAO,CAAC,cAAc,CAAS;IAI/B,OAAO,CAAC,wBAAwB,CAA8B;gBAEjC,cAAc,CAAC,EAAE,cAAc,YAAA;YAuB9C,eAAe;IAyB7B,OAAO,CAAC,sBAAsB;IAkF9B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,oBAAoB;IAKrB,kBAAkB,IAAI,eAAe;IAIrC,uBAAuB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI;IAKtF,OAAO,CAAC,oBAAoB;YAUd,6BAA6B;IAkBpC,iBAAiB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;IAgB5C,kBAAkB,IAAI,IAAI;IAK3B,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBxE,OAAO,CAAC,yBAAyB;IAUpB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAmC1G,UAAU,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAarD,SAAS,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB7C,iBAAiB,CAAC,OAAO,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE,OAAO,CAAC,eAAe;YA4BT,cAAc;IAKrB,oBAAoB,IAAI,IAAI;IAInC,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,sBAAsB;YAUhB,iBAAiB;YASjB,kBAAkB;YAWlB,aAAa;YAqEb,sBAAsB;YAStB,cAAc;YAsCd,uBAAuB;YA0CvB,kBAAkB;IA4ChC,OAAO,CAAC,mBAAmB;YAIb,gBAAgB;YAWhB,aAAa;YAab,mBAAmB;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/api/agent.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,YAAY,EAAC,MAAM,QAAQ,CAAC;AAGpC,OAAO,EAGL,YAAY,EAEb,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAK9C,OAAO,EAAkB,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAuBhE,qBAAa,QAAS,SAAQ,YAAY;IAkC5B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAjC5C,OAAO,CAAC,QAAQ,CAAkB;IAElC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,cAAc,CAAC,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAsC;IAI1E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,kBAAkB,CAAsB;IAChD,OAAO,CAAC,cAAc,CAAS;IAI/B,OAAO,CAAC,wBAAwB,CAA8B;gBAEjC,cAAc,CAAC,EAAE,cAAc,YAAA;YAuB9C,eAAe;IAyB7B,OAAO,CAAC,sBAAsB;IAkF9B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,oBAAoB;IAKrB,kBAAkB,IAAI,eAAe;IAIrC,uBAAuB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI;IAKtF,OAAO,CAAC,oBAAoB;YAUd,6BAA6B;IAkBpC,iBAAiB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;IAgB5C,kBAAkB,IAAI,IAAI;IAK3B,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBxE,OAAO,CAAC,yBAAyB;IAUpB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAmC1G,UAAU,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAarD,SAAS,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB7C,iBAAiB,CAAC,OAAO,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE,OAAO,CAAC,eAAe;YA4BT,cAAc;IAKrB,oBAAoB,IAAI,IAAI;IAInC,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,sBAAsB;YAUhB,iBAAiB;YASjB,kBAAkB;YAWlB,aAAa;YAqEb,sBAAsB;YAStB,cAAc;YAsCd,uBAAuB;YA0CvB,kBAAkB;IA4ChC,OAAO,CAAC,mBAAmB;YAIb,gBAAgB;YAWhB,aAAa;YAab,mBAAmB;YA0DnB,iBAAiB;YAajB,QAAQ;YAiGR,gBAAgB;IAmD9B,OAAO,CAAC,kBAAkB;YAwBZ,oBAAoB;YAgBpB,mBAAmB;YAUnB,WAAW;IAkClB,kBAAkB,IAAI,IAAI;IAK1B,OAAO,IAAI,IAAI;IAUf,cAAc,IAAI,WAAW;CAGrC"}
|
package/dist/api/agent.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getUserData, getCurrentGitSha1 } from "./utils.js";
|
|
|
6
6
|
import { EndNode, UserResponse } from "../constants/tools.js";
|
|
7
7
|
import { SessionContext } from "../session/index.js";
|
|
8
8
|
import { TaskTracker } from "./taskTracking.js";
|
|
9
|
-
import {
|
|
9
|
+
import { ToolProcessorManager } from "../mcp/index.js";
|
|
10
10
|
import process from "node:process";
|
|
11
11
|
import { ServerData } from "../session/index.js";
|
|
12
12
|
import { httpRequest } from "./http.js";
|
|
@@ -166,7 +166,7 @@ export class AgentAPI extends EventEmitter {
|
|
|
166
166
|
await this.responseCallback({ forceStop: true });
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
-
catch { }
|
|
169
|
+
catch { /* best-effort: forceStop on reconnect should not crash the WS listener */ }
|
|
170
170
|
});
|
|
171
171
|
this.wsClient.on('checkpointRecovery', (info) => {
|
|
172
172
|
this.debug('[AgentAPI] Checkpoint recovery initiated', `| Reason: ${info.reason}`, info.checkpoint ? `| Checkpoint: ${info.checkpoint.substring(0, 8)}` : '');
|
|
@@ -639,10 +639,15 @@ export class AgentAPI extends EventEmitter {
|
|
|
639
639
|
return;
|
|
640
640
|
}
|
|
641
641
|
const isAutoApprovedTool = this.mcpManager.isAutoApprovedTool(toolData.server_name, toolData.tool, toolData.tool_args);
|
|
642
|
-
|
|
643
|
-
const processedToolData = await toolProcessorManager.preProcessTool(toolData, this.sessionContext);
|
|
642
|
+
const processedToolData = await ToolProcessorManager.getInstance().preProcessTool(toolData, this.sessionContext);
|
|
644
643
|
await this.processToolReasoning(processedToolData, isAutoApprovedTool);
|
|
645
|
-
|
|
644
|
+
// Dry run: simulate tool execution without side effects
|
|
645
|
+
if (this.sessionContext?.isDryRun()) {
|
|
646
|
+
const dryResult = await ToolProcessorManager.getInstance().dryRunTool(processedToolData, this.sessionContext);
|
|
647
|
+
await this.processToolResponse(processedToolData, dryResult);
|
|
648
|
+
await this.sendToolResponse(processedToolData, dryResult);
|
|
649
|
+
}
|
|
650
|
+
else if (isAutoApprovedTool) {
|
|
646
651
|
await this.callTool(processedToolData);
|
|
647
652
|
}
|
|
648
653
|
else {
|
|
@@ -701,7 +706,7 @@ export class AgentAPI extends EventEmitter {
|
|
|
701
706
|
}
|
|
702
707
|
}
|
|
703
708
|
}
|
|
704
|
-
catch { }
|
|
709
|
+
catch { /* best-effort: tool arg patching should not block tool execution */ }
|
|
705
710
|
if (!this.mcpManager) {
|
|
706
711
|
// Should not be reachable because we guard earlier, but keep a safe fallback
|
|
707
712
|
// that gracefully declines the tool call instead of throwing.
|
|
@@ -723,7 +728,7 @@ export class AgentAPI extends EventEmitter {
|
|
|
723
728
|
return;
|
|
724
729
|
}
|
|
725
730
|
// Post-process the tool result
|
|
726
|
-
const processedResponse = await
|
|
731
|
+
const processedResponse = await ToolProcessorManager.getInstance().postProcessTool(toolData, response);
|
|
727
732
|
await this.processToolResponse(toolData, processedResponse);
|
|
728
733
|
// Send tool response via WebSocket
|
|
729
734
|
await this.sendToolResponse(toolData, processedResponse);
|
|
@@ -870,7 +875,7 @@ export class AgentAPI extends EventEmitter {
|
|
|
870
875
|
try {
|
|
871
876
|
this.cleanupConnections();
|
|
872
877
|
}
|
|
873
|
-
catch { }
|
|
878
|
+
catch { /* cleanup: connection teardown must not throw */ }
|
|
874
879
|
this.pendingToolRequests.clear();
|
|
875
880
|
this.currentSession = undefined;
|
|
876
881
|
this.latestSessionId = "";
|