@qodo/sdk 0.7.0 → 0.9.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.
Files changed (148) hide show
  1. package/.claude/skills/qodo-agent/SKILL.md +268 -3
  2. package/.claude/skills/qodo-agent/assets/programmatic-agent.ts +90 -1
  3. package/.claude/skills/qodo-agent/references/common-issues.md +80 -0
  4. package/dist/api/agent.d.ts.map +1 -1
  5. package/dist/api/agent.js +13 -8
  6. package/dist/api/agent.js.map +1 -1
  7. package/dist/api/http.d.ts.map +1 -1
  8. package/dist/api/http.js +5 -2
  9. package/dist/api/http.js.map +1 -1
  10. package/dist/api/websocket.d.ts.map +1 -1
  11. package/dist/api/websocket.js +6 -3
  12. package/dist/api/websocket.js.map +1 -1
  13. package/dist/auth/index.d.ts +18 -0
  14. package/dist/auth/index.d.ts.map +1 -1
  15. package/dist/auth/index.js +69 -10
  16. package/dist/auth/index.js.map +1 -1
  17. package/dist/context/messageManager.d.ts +1 -1
  18. package/dist/context/messageManager.d.ts.map +1 -1
  19. package/dist/context/messageManager.js +1 -1
  20. package/dist/context/messageManager.js.map +1 -1
  21. package/dist/index.d.ts +21 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +18 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/mcp/MCPManager.js +2 -2
  26. package/dist/mcp/MCPManager.js.map +1 -1
  27. package/dist/mcp/baseServer.js +1 -1
  28. package/dist/mcp/baseServer.js.map +1 -1
  29. package/dist/mcp/servers/ripgrep.d.ts.map +1 -1
  30. package/dist/mcp/servers/ripgrep.js +6 -2
  31. package/dist/mcp/servers/ripgrep.js.map +1 -1
  32. package/dist/mcp/servers/shell.js +1 -1
  33. package/dist/mcp/servers/shell.js.map +1 -1
  34. package/dist/mcp/serversRegistry.d.ts.map +1 -1
  35. package/dist/mcp/serversRegistry.js +7 -1
  36. package/dist/mcp/serversRegistry.js.map +1 -1
  37. package/dist/mcp/toolProcessor.d.ts +44 -4
  38. package/dist/mcp/toolProcessor.d.ts.map +1 -1
  39. package/dist/mcp/toolProcessor.js +255 -20
  40. package/dist/mcp/toolProcessor.js.map +1 -1
  41. package/dist/messages/index.d.ts +8 -0
  42. package/dist/messages/index.d.ts.map +1 -0
  43. package/dist/messages/index.js +7 -0
  44. package/dist/messages/index.js.map +1 -0
  45. package/dist/messages/openai.d.ts +26 -0
  46. package/dist/messages/openai.d.ts.map +1 -0
  47. package/dist/messages/openai.js +55 -0
  48. package/dist/messages/openai.js.map +1 -0
  49. package/dist/messages/types.d.ts +73 -0
  50. package/dist/messages/types.d.ts.map +1 -0
  51. package/dist/messages/types.js +78 -0
  52. package/dist/messages/types.js.map +1 -0
  53. package/dist/parser/index.js +3 -3
  54. package/dist/parser/index.js.map +1 -1
  55. package/dist/sdk/QodoSDK.d.ts +30 -10
  56. package/dist/sdk/QodoSDK.d.ts.map +1 -1
  57. package/dist/sdk/QodoSDK.js +165 -33
  58. package/dist/sdk/QodoSDK.js.map +1 -1
  59. package/dist/sdk/artifacts.d.ts +156 -0
  60. package/dist/sdk/artifacts.d.ts.map +1 -0
  61. package/dist/sdk/artifacts.js +166 -0
  62. package/dist/sdk/artifacts.js.map +1 -0
  63. package/dist/sdk/bootstrap.d.ts.map +1 -1
  64. package/dist/sdk/bootstrap.js +11 -4
  65. package/dist/sdk/bootstrap.js.map +1 -1
  66. package/dist/sdk/discovery.js +1 -1
  67. package/dist/sdk/discovery.js.map +1 -1
  68. package/dist/sdk/events.d.ts +53 -1
  69. package/dist/sdk/events.d.ts.map +1 -1
  70. package/dist/sdk/events.js +7 -0
  71. package/dist/sdk/events.js.map +1 -1
  72. package/dist/sdk/middleware.d.ts +59 -0
  73. package/dist/sdk/middleware.d.ts.map +1 -0
  74. package/dist/sdk/middleware.js +69 -0
  75. package/dist/sdk/middleware.js.map +1 -0
  76. package/dist/sdk/pipeline/PipelineBuilder.d.ts +79 -0
  77. package/dist/sdk/pipeline/PipelineBuilder.d.ts.map +1 -0
  78. package/dist/sdk/pipeline/PipelineBuilder.js +129 -0
  79. package/dist/sdk/pipeline/PipelineBuilder.js.map +1 -0
  80. package/dist/sdk/pipeline/PipelineRunner.d.ts +28 -0
  81. package/dist/sdk/pipeline/PipelineRunner.d.ts.map +1 -0
  82. package/dist/sdk/pipeline/PipelineRunner.js +326 -0
  83. package/dist/sdk/pipeline/PipelineRunner.js.map +1 -0
  84. package/dist/sdk/pipeline/compiler.d.ts +24 -0
  85. package/dist/sdk/pipeline/compiler.d.ts.map +1 -0
  86. package/dist/sdk/pipeline/compiler.js +199 -0
  87. package/dist/sdk/pipeline/compiler.js.map +1 -0
  88. package/dist/sdk/pipeline/declarative.d.ts +34 -0
  89. package/dist/sdk/pipeline/declarative.d.ts.map +1 -0
  90. package/dist/sdk/pipeline/declarative.js +9 -0
  91. package/dist/sdk/pipeline/declarative.js.map +1 -0
  92. package/dist/sdk/pipeline/index.d.ts +20 -0
  93. package/dist/sdk/pipeline/index.d.ts.map +1 -0
  94. package/dist/sdk/pipeline/index.js +19 -0
  95. package/dist/sdk/pipeline/index.js.map +1 -0
  96. package/dist/sdk/pipeline/types.d.ts +93 -0
  97. package/dist/sdk/pipeline/types.d.ts.map +1 -0
  98. package/dist/sdk/pipeline/types.js +10 -0
  99. package/dist/sdk/pipeline/types.js.map +1 -0
  100. package/dist/sdk/policies.d.ts +163 -0
  101. package/dist/sdk/policies.d.ts.map +1 -0
  102. package/dist/sdk/policies.js +243 -0
  103. package/dist/sdk/policies.js.map +1 -0
  104. package/dist/sdk/runner/AgentRunner.js +5 -5
  105. package/dist/sdk/runner/AgentRunner.js.map +1 -1
  106. package/dist/sdk/runner/finalize.d.ts +47 -0
  107. package/dist/sdk/runner/finalize.d.ts.map +1 -1
  108. package/dist/sdk/runner/finalize.js +42 -2
  109. package/dist/sdk/runner/finalize.js.map +1 -1
  110. package/dist/sdk/runner/formats.d.ts +1 -1
  111. package/dist/sdk/runner/formats.d.ts.map +1 -1
  112. package/dist/sdk/runner/formats.js +22 -37
  113. package/dist/sdk/runner/formats.js.map +1 -1
  114. package/dist/sdk/runner/progress.d.ts +1 -1
  115. package/dist/sdk/runner/progress.d.ts.map +1 -1
  116. package/dist/sdk/runner/progress.js +1 -1
  117. package/dist/sdk/runner/progress.js.map +1 -1
  118. package/dist/session/SessionContext.d.ts +1 -0
  119. package/dist/session/SessionContext.d.ts.map +1 -1
  120. package/dist/session/SessionContext.js +3 -0
  121. package/dist/session/SessionContext.js.map +1 -1
  122. package/dist/session/environment.d.ts +16 -6
  123. package/dist/session/environment.d.ts.map +1 -1
  124. package/dist/session/environment.js.map +1 -1
  125. package/dist/session/serverData.d.ts.map +1 -1
  126. package/dist/session/serverData.js +21 -3
  127. package/dist/session/serverData.js.map +1 -1
  128. package/dist/tracing/PipelineTracer.d.ts +37 -0
  129. package/dist/tracing/PipelineTracer.d.ts.map +1 -0
  130. package/dist/tracing/PipelineTracer.js +80 -0
  131. package/dist/tracing/PipelineTracer.js.map +1 -0
  132. package/dist/tracing/SdkTracer.d.ts +91 -0
  133. package/dist/tracing/SdkTracer.d.ts.map +1 -0
  134. package/dist/tracing/SdkTracer.js +243 -0
  135. package/dist/tracing/SdkTracer.js.map +1 -0
  136. package/dist/tracing/index.d.ts +6 -0
  137. package/dist/tracing/index.d.ts.map +1 -0
  138. package/dist/tracing/index.js +3 -0
  139. package/dist/tracing/index.js.map +1 -0
  140. package/dist/tracing/pipelineHelpers.d.ts +29 -0
  141. package/dist/tracing/pipelineHelpers.d.ts.map +1 -0
  142. package/dist/tracing/pipelineHelpers.js +224 -0
  143. package/dist/tracing/pipelineHelpers.js.map +1 -0
  144. package/dist/tracing/types.d.ts +46 -0
  145. package/dist/tracing/types.d.ts.map +1 -0
  146. package/dist/tracing/types.js +9 -0
  147. package/dist/tracing/types.js.map +1 -0
  148. package/package.json +10 -7
@@ -39,6 +39,51 @@ 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
+ | `.step()` | Add a sequential step with typed state threading |
50
+ | `.parallel()` | Add a group of steps that execute concurrently |
51
+ | `.gate()` | Pipeline-level barrier — halts subsequent entries when false |
52
+ | `.branch()` | Pipeline-level if/else fork with sub-pipeline paths |
53
+
54
+ ### Tool Middleware
55
+
56
+ | Export | Purpose |
57
+ |--------|---------|
58
+ | `ToolMiddleware` (type) | Pre/post execution hook interface |
59
+ | `ToolMiddlewareError` | Middleware failure error |
60
+ | `MIDDLEWARE_TIMEOUT_MS` | Default hook timeout (10s) |
61
+ | `PROTECTED_TOOL_FIELDS` | Fields middleware cannot overwrite |
62
+
63
+ ### Tool Approval Policies
64
+
65
+ | Export | Purpose |
66
+ |--------|---------|
67
+ | `sdkPolicy()` | Fluent builder for declarative tool approval rules |
68
+ | `policyFromRules()` | Build a policy from a rule array |
69
+ | `globMatch()` | Glob pattern matching utility |
70
+
71
+ ### Artifacts
72
+
73
+ | Export | Purpose |
74
+ |--------|---------|
75
+ | `ArtifactStore` | Store/persist intermediate pipeline results |
76
+ | `withArtifacts()` | Wrap a pipeline step to auto-store output |
77
+ | `jsonSerializer` | Default JSON serializer |
78
+
79
+ ### OpenTelemetry Tracing
80
+
81
+ | Export | Purpose |
82
+ |--------|---------|
83
+ | `createSdkTracer()` | Create event-to-span tracer (duck-typed OTel API) |
84
+ | `tracePipeline()` | Trace pipeline execution with nested spans |
85
+ | `SdkTracer` | Tracer class for manual usage |
86
+
42
87
  ### Event Handling
43
88
 
44
89
  | Export | Purpose |
@@ -60,6 +105,9 @@ Use this skill when working with `@qodo/sdk` to build AI-powered agents. This sk
60
105
  | `QodoSchemaError` | Schema validation failures |
61
106
  | `QodoAuthError` | Authentication failures |
62
107
  | `QodoBackendBootstrapError` | Backend initialization failures |
108
+ | `QodoOutputValidationError` | Structured output didn't match Zod schema |
109
+ | `ToolMiddlewareError` | Tool middleware hook failure |
110
+ | `PipelineExecutionError` | Pipeline step failure |
63
111
 
64
112
  ### Built-in Tools
65
113
 
@@ -437,6 +485,209 @@ try {
437
485
  }
438
486
  ```
439
487
 
488
+ ### Pattern 8: Pipeline DSL (Multi-Step Workflows)
489
+
490
+ ```typescript
491
+ import { QodoSDK, sdkPipeline, runPipeline } from '@qodo/sdk';
492
+
493
+ const sdk = new QodoSDK({ autoApproveTools: true });
494
+
495
+ const pipeline = sdkPipeline<{ repo: string }>()
496
+ .step('lint', async ({ state, run }) => {
497
+ const r = await run('lint', { args: { path: state.repo } });
498
+ return { lintOutput: r.result.final_output };
499
+ })
500
+ // Gate: only continue review if lint produced output
501
+ .gate((state) => !!state.lintOutput)
502
+ .step('review', async ({ state, run }) => {
503
+ const r = await run('review', {
504
+ extraInstructions: `Lint results:\n${state.lintOutput}`,
505
+ });
506
+ return { reviewOutput: r.result.final_output };
507
+ })
508
+ .build();
509
+
510
+ const result = await runPipeline(sdk, pipeline, { repo: './src' });
511
+ console.log(result.state.reviewOutput);
512
+ console.log(result.steps); // Step logs with timing
513
+
514
+ await sdk.dispose();
515
+ ```
516
+
517
+ Features: typed state threading, `.parallel([...])`, `.gate()` (pipeline barriers), `.branch()` (if/else forks), error strategies (`'skip'`, `'throw'`, custom recovery).
518
+
519
+ ### Pattern 8b: Pipeline Branches
520
+
521
+ ```typescript
522
+ import { QodoSDK, sdkPipeline, runPipeline } from '@qodo/sdk';
523
+
524
+ const sdk = new QodoSDK({ autoApproveTools: true });
525
+
526
+ const pipeline = sdkPipeline<{ fileCount: number }>()
527
+ .branch(
528
+ (state) => state.fileCount > 20,
529
+ {
530
+ true: (b) => b.step('deep', async ({ run }) => {
531
+ const r = await run('analyze-deep');
532
+ return { analysis: r.result.final_output };
533
+ }),
534
+ false: (b) => b.step('quick', async ({ run }) => {
535
+ const r = await run('summarize');
536
+ return { analysis: r.result.final_output };
537
+ }),
538
+ },
539
+ { name: 'size-check' },
540
+ )
541
+ .step('report', async ({ state }) => ({
542
+ report: `${state.fileCount} files: ${state.analysis}`,
543
+ }))
544
+ .build();
545
+
546
+ await sdk.dispose();
547
+ ```
548
+
549
+ ### Pattern 9: Dry Run Mode
550
+
551
+ ```typescript
552
+ import { QodoSDK } from '@qodo/sdk';
553
+
554
+ const sdk = new QodoSDK({ autoApproveTools: true });
555
+
556
+ // Preview what the agent would do without executing tools
557
+ const result = await sdk.run('review', {
558
+ args: { path: './src' },
559
+ dryRun: true,
560
+ });
561
+
562
+ console.log(result.result.final_output); // Agent describes planned actions
563
+ console.log(result.meta.dry_run); // true
564
+
565
+ await sdk.dispose();
566
+ ```
567
+
568
+ ### Pattern 10: Tool Middleware (Auditing & Control)
569
+
570
+ ```typescript
571
+ import { QodoSDK, type ToolMiddleware } from '@qodo/sdk';
572
+
573
+ const auditLogger: ToolMiddleware = {
574
+ name: 'audit-logger',
575
+ preExecute(toolData) {
576
+ console.log(`[AUDIT] ${toolData.server_name}.${toolData.tool_name}`);
577
+ },
578
+ postExecute(toolData, result) {
579
+ console.log(`[AUDIT] Result: ${result.isError ? 'ERROR' : 'OK'}`);
580
+ },
581
+ };
582
+
583
+ const readOnly: ToolMiddleware = {
584
+ name: 'read-only',
585
+ preExecute(toolData) {
586
+ if (['write_file', 'edit_file', 'delete_files'].includes(toolData.tool_name)) {
587
+ throw new Error(`Blocked: ${toolData.tool_name}`);
588
+ }
589
+ },
590
+ };
591
+
592
+ const sdk = new QodoSDK({
593
+ autoApproveTools: true,
594
+ toolMiddleware: [auditLogger, readOnly],
595
+ });
596
+ ```
597
+
598
+ ### Pattern 11: Tool Approval Policy DSL
599
+
600
+ ```typescript
601
+ import { QodoSDK, sdkPolicy } from '@qodo/sdk';
602
+
603
+ const policy = sdkPolicy()
604
+ .approve({ tools: ['read_*', 'list_*', 'git_status', 'git_log'] }, 'allow-reads')
605
+ .deny({ servers: ['shell'] }, 'block-shell')
606
+ .deny({ tools: ['write_file', 'edit_file', 'delete_files'] }, 'block-writes')
607
+ .requireHuman({ tools: ['git_commit'] }, 'review-commits')
608
+ .default('deny')
609
+ .build();
610
+
611
+ const sdk = new QodoSDK({
612
+ autoApproveTools: false,
613
+ toolApproval: policy.evaluate,
614
+ });
615
+
616
+ // Inspect policy decisions:
617
+ const result = policy.evaluateDetailed({
618
+ tool_name: 'read_files', server_name: 'filesystem', tool_args: {},
619
+ });
620
+ console.log(result.decision, result.matchedRule?.name); // 'approve', 'allow-reads'
621
+ ```
622
+
623
+ ### Pattern 12: Execution Timeouts and Retries
624
+
625
+ ```typescript
626
+ import { QodoSDK } from '@qodo/sdk';
627
+
628
+ const sdk = new QodoSDK({ autoApproveTools: true });
629
+
630
+ const result = await sdk.run('review', {
631
+ args: { path: './src' },
632
+ timeout: 30000, // 30 second limit per attempt
633
+ maxRetries: 2, // Retry up to 2 times on failure
634
+ });
635
+
636
+ if (result.meta.timed_out) {
637
+ console.warn('Run timed out');
638
+ }
639
+
640
+ await sdk.dispose();
641
+ ```
642
+
643
+ ### Pattern 13: Artifacts (Pipeline Intermediate Results)
644
+
645
+ ```typescript
646
+ import { QodoSDK, sdkPipeline, runPipeline, ArtifactStore, withArtifacts } from '@qodo/sdk';
647
+ import fs from 'fs/promises';
648
+
649
+ const store = new ArtifactStore({
650
+ onPersist: async (artifact) => {
651
+ await fs.writeFile(`./out/${artifact.stepName}.json`, artifact.serialized);
652
+ },
653
+ });
654
+
655
+ const pipeline = sdkPipeline<{ repo: string }>()
656
+ .step('lint', withArtifacts(store, async ({ state, run }) => {
657
+ const r = await run('lint', { args: { path: state.repo } });
658
+ return { lintOutput: r.result.final_output };
659
+ }))
660
+ .build();
661
+
662
+ const result = await runPipeline(sdk, pipeline, { repo: './src' });
663
+ console.log(store.all()); // All artifacts
664
+ console.log(store.getErrors()); // Persistence failures (non-fatal)
665
+ ```
666
+
667
+ ### Pattern 14: OpenTelemetry Tracing
668
+
669
+ ```typescript
670
+ import * as otel from '@opentelemetry/api';
671
+ import { QodoSDK, createSdkTracer, SdkEventType, matchSdkEvent } from '@qodo/sdk';
672
+
673
+ const sdk = new QodoSDK({ autoApproveTools: true });
674
+ const tracer = createSdkTracer(otel, { tracerName: 'my-service' });
675
+
676
+ // Events flow through unchanged, spans created automatically
677
+ for await (const ev of tracer.wrapStream(sdk.stream('review'))) {
678
+ matchSdkEvent(ev, {
679
+ [SdkEventType.MessageDelta]: (e) => process.stdout.write(e.data.delta),
680
+ [SdkEventType.Final]: (e) => console.log('Done:', e.data.success),
681
+ default: () => {},
682
+ });
683
+ }
684
+
685
+ tracer.flush();
686
+ await sdk.dispose();
687
+ ```
688
+
689
+ No compile-time OTel dependency — install `@opentelemetry/api` as optional peer dep.
690
+
440
691
  ---
441
692
 
442
693
  ## Zod Schema Patterns
@@ -519,15 +770,15 @@ const args = z.object({
519
770
  | Event Type | When Emitted | Key Data Fields |
520
771
  |------------|--------------|-----------------|
521
772
  | `sdk.init` | SDK initialized | `sdk_version`, `backend.base_url`, `model` |
522
- | `sdk.run.started` | Run begins | `session_id`, `command`, `prompt_mode`, `cwd` |
773
+ | `sdk.run.started` | Run begins | `session_id`, `command`, `prompt_mode`, `cwd`, `dry_run?` |
523
774
  | `sdk.message.delta` | Streaming text | `delta`, `message_id`, `role` |
524
775
  | `sdk.message.full` | Full message update | `messages.langchain`, `messages.openai` |
525
776
  | `sdk.progress` | Progress update | `title`, `status`, `percent` |
526
777
  | `sdk.tool.requested` | Tool call requested | `tool_call_id`, `server_name`, `tool_name`, `tool_args`, `pending_approval` |
527
778
  | `sdk.tool.approved` | Tool approved/denied | `tool_call_id`, `approved`, `reason` |
528
- | `sdk.tool.executed` | Tool finished | `tool_call_id`, `result.isError`, `result.content` |
779
+ | `sdk.tool.executed` | Tool finished | `tool_call_id`, `result.isError`, `result.content`, `dry_run?` |
529
780
  | `sdk.error` | Error occurred | `message`, `cause` |
530
- | `sdk.final` | Run completed | `success`, `result.structured_output`, `result.final_output`, `messages` |
781
+ | `sdk.final` | Run completed | `success`, `result.structured_output`, `result.final_output`, `meta.dry_run?`, `meta.timed_out?` |
531
782
 
532
783
  ---
533
784
 
@@ -552,6 +803,7 @@ interface QodoSDKOptions {
552
803
  // Tool handling
553
804
  autoApproveTools?: boolean; // Auto-approve tools (default: true)
554
805
  toolApproval?: (req) => Promise<boolean> | boolean; // Custom approval
806
+ toolMiddleware?: ToolMiddleware[]; // Pre/post execution hooks
555
807
 
556
808
  // Session
557
809
  contextSessionIds?: string[]; // Previous sessions for context
@@ -654,3 +906,16 @@ z.object({
654
906
  - [assets/programmatic-agent.ts](assets/programmatic-agent.ts) - Complete working TypeScript agent template
655
907
  - [references/builtin-tools.md](references/builtin-tools.md) - Detailed guide to filesystem, git, ripgrep, and shell tools
656
908
  - [references/common-issues.md](references/common-issues.md) - Troubleshooting guide for common problems
909
+
910
+ ### Documentation (in `docs/`)
911
+
912
+ | Feature | Doc |
913
+ |---------|-----|
914
+ | Pipeline DSL | [docs/pipeline.md](../../../docs/pipeline.md) |
915
+ | Dry Run Mode | [docs/dry-run.md](../../../docs/dry-run.md) |
916
+ | Tool Middleware | [docs/tool-middleware.md](../../../docs/tool-middleware.md) |
917
+ | Structured Output | [docs/structured-output.md](../../../docs/structured-output.md) |
918
+ | Execution Timeouts | [docs/execution-timeouts.md](../../../docs/execution-timeouts.md) |
919
+ | OpenTelemetry | [docs/opentelemetry.md](../../../docs/opentelemetry.md) |
920
+ | Approval Policies | [docs/approval-policies.md](../../../docs/approval-policies.md) |
921
+ | 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
- z
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,70 @@ 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 with gates and branches)
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
+ // Gate: only continue if analysis produced results
332
+ .gate((state) => !!state.analysis, { name: 'has-analysis' })
333
+ // Branch: choose different summary strategies based on score
334
+ .branch(
335
+ (state) => (state.analysis as any)?.score > 80,
336
+ {
337
+ true: (b) => b.step('brief-summary', async ({ state, run }) => {
338
+ const r = await run('', {
339
+ extraInstructions: `High quality code (score: ${(state.analysis as any)?.score}). Brief summary: ${JSON.stringify(state.analysis)}`,
340
+ });
341
+ return { summary: r.result.final_output };
342
+ }),
343
+ false: (b) => b.step('detailed-summary', async ({ state, run }) => {
344
+ const r = await run('', {
345
+ extraInstructions: `Code needs improvement. Detailed summary with recommendations: ${JSON.stringify(state.analysis)}`,
346
+ });
347
+ return { summary: r.result.final_output };
348
+ }),
349
+ },
350
+ { name: 'quality-check' },
351
+ )
352
+ .build();
353
+
354
+ const pipelineResult = await runPipeline(sdk, pipeline, { path: './src' });
355
+ console.log('Pipeline summary:', pipelineResult.state.summary);
356
+ for (const log of pipelineResult.steps) {
357
+ const branch = log.branchTaken ? ` (branch: ${log.branchTaken})` : '';
358
+ console.log(` ${log.name}: ${log.status}${branch} (${log.durationMs}ms)`);
359
+ }
360
+
361
+ // -------------------------------------------------------------------------
362
+ // Example 5: Dry run mode (preview without side effects)
363
+ // -------------------------------------------------------------------------
364
+ console.log('\n\n--- Dry run example ---\n');
365
+
366
+ const dryResult = await sdk.run('analyze', {
367
+ args: { path: './src' },
368
+ dryRun: true,
369
+ });
370
+ console.log('Dry run output:', dryResult.result.final_output?.slice(0, 200));
371
+ console.log('Was dry run:', dryResult.meta.dry_run);
372
+
373
+ // -------------------------------------------------------------------------
374
+ // Example 6: Execution timeout with retry
375
+ // -------------------------------------------------------------------------
376
+ console.log('\n\n--- Timeout example ---\n');
377
+
378
+ const timedResult = await sdk.run('analyze', {
379
+ args: { path: './src' },
380
+ timeout: 60000, // 60 second limit
381
+ maxRetries: 1, // Retry once on failure
382
+ });
383
+ console.log('Timed out:', timedResult.meta.timed_out ?? false);
384
+
299
385
  } catch (error) {
300
386
  // Handle specific error types
301
387
  if (error instanceof QodoSchemaError) {
@@ -304,6 +390,9 @@ async function main() {
304
390
  } else if (error instanceof QodoAuthError) {
305
391
  console.error('Authentication error:', error.message);
306
392
  console.error('Check your QODO_API_KEY environment variable');
393
+ } else if (error instanceof QodoOutputValidationError) {
394
+ console.error('Output validation error:', error.issues);
395
+ console.error('Raw output:', error.rawOutput);
307
396
  } else {
308
397
  throw error;
309
398
  }
@@ -422,6 +422,86 @@ 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, or use gates to guard steps:
437
+
438
+ ```typescript
439
+ sdkPipeline()
440
+ .step('lint', lintFn, { onError: 'skip' }) // Skip on failure
441
+ .gate((state) => !!state.lintOutput) // Gate: halt if no lint output
442
+ .step('review', reviewFn, {
443
+ onError: (error, state) => ({ fallback: true }), // Custom recovery
444
+ })
445
+ .build();
446
+ ```
447
+
448
+ ---
449
+
450
+ ### Structured Output Validation Failed
451
+
452
+ **Error:**
453
+ ```
454
+ QodoOutputValidationError: Structured output validation failed
455
+ ```
456
+
457
+ **Cause:** The agent returned data that doesn't match the Zod output schema.
458
+
459
+ **Solution:** Check `error.issues` for details and `error.rawOutput` for what the agent actually returned:
460
+
461
+ ```typescript
462
+ import { QodoOutputValidationError } from '@qodo/sdk';
463
+
464
+ try {
465
+ await sdk.run('analyze');
466
+ } catch (e) {
467
+ if (e instanceof QodoOutputValidationError) {
468
+ console.error('Issues:', e.issues); // [{ path: ['score'], message: '...' }]
469
+ console.error('Raw:', e.rawOutput); // What the agent returned
470
+ }
471
+ }
472
+ ```
473
+
474
+ ---
475
+
476
+ ### Tool Middleware Error
477
+
478
+ **Error:**
479
+ ```
480
+ ToolMiddlewareError: Middleware "my-middleware" failed in preExecute
481
+ ```
482
+
483
+ **Cause:** A tool middleware hook threw or timed out (default: 10s).
484
+
485
+ **Solution:** Check the middleware implementation and handle errors gracefully:
486
+
487
+ ```typescript
488
+ import { ToolMiddlewareError, MIDDLEWARE_TIMEOUT_MS } from '@qodo/sdk';
489
+
490
+ // Middleware hooks should be fast and defensive
491
+ const safeMiddleware = {
492
+ name: 'safe-mw',
493
+ preExecute(toolData) {
494
+ try {
495
+ // Your logic here
496
+ } catch {
497
+ // Don't throw — return void to pass through
498
+ }
499
+ },
500
+ };
501
+ ```
502
+
503
+ ---
504
+
425
505
  ## Quick Debugging Checklist
426
506
 
427
507
  When something isn't working:
@@ -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;YAqDnB,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"}
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 { toolProcessorManager } from "../mcp/index.js";
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
- // Dry run tools if configured
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
- if (isAutoApprovedTool) {
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 toolProcessorManager.postProcessTool(toolData, response);
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 = "";