kuzushi 0.1.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/LICENSE +21 -0
- package/README.md +410 -0
- package/dist/agent-runtime/claude.d.ts +8 -0
- package/dist/agent-runtime/claude.js +124 -0
- package/dist/agent-runtime/claude.js.map +1 -0
- package/dist/agent-runtime/index.d.ts +9 -0
- package/dist/agent-runtime/index.js +31 -0
- package/dist/agent-runtime/index.js.map +1 -0
- package/dist/agent-runtime/model-spec.d.ts +8 -0
- package/dist/agent-runtime/model-spec.js +17 -0
- package/dist/agent-runtime/model-spec.js.map +1 -0
- package/dist/agent-runtime/pi-ai.d.ts +8 -0
- package/dist/agent-runtime/pi-ai.js +365 -0
- package/dist/agent-runtime/pi-ai.js.map +1 -0
- package/dist/agent-runtime/tools.d.ts +3 -0
- package/dist/agent-runtime/tools.js +330 -0
- package/dist/agent-runtime/tools.js.map +1 -0
- package/dist/agent-runtime/types.d.ts +72 -0
- package/dist/agent-runtime/types.js +2 -0
- package/dist/agent-runtime/types.js.map +1 -0
- package/dist/agents/index.d.ts +7 -0
- package/dist/agents/index.js +24 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/registry.d.ts +14 -0
- package/dist/agents/registry.js +97 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/scanner-adapter.d.ts +5 -0
- package/dist/agents/scanner-adapter.js +18 -0
- package/dist/agents/scanner-adapter.js.map +1 -0
- package/dist/agents/tasks/augur-analyze.d.ts +22 -0
- package/dist/agents/tasks/augur-analyze.js +418 -0
- package/dist/agents/tasks/augur-analyze.js.map +1 -0
- package/dist/agents/tasks/augur-extraction-agent.d.ts +44 -0
- package/dist/agents/tasks/augur-extraction-agent.js +507 -0
- package/dist/agents/tasks/augur-extraction-agent.js.map +1 -0
- package/dist/agents/tasks/augur-label.d.ts +21 -0
- package/dist/agents/tasks/augur-label.js +627 -0
- package/dist/agents/tasks/augur-label.js.map +1 -0
- package/dist/agents/tasks/augur-preflight.d.ts +36 -0
- package/dist/agents/tasks/augur-preflight.js +471 -0
- package/dist/agents/tasks/augur-preflight.js.map +1 -0
- package/dist/agents/tasks/augur-types.d.ts +111 -0
- package/dist/agents/tasks/augur-types.js +169 -0
- package/dist/agents/tasks/augur-types.js.map +1 -0
- package/dist/agents/tasks/context-gatherer.d.ts +6 -0
- package/dist/agents/tasks/context-gatherer.js +320 -0
- package/dist/agents/tasks/context-gatherer.js.map +1 -0
- package/dist/agents/types.d.ts +28 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/bus/adapters/google-pubsub.d.ts +13 -0
- package/dist/bus/adapters/google-pubsub.js +26 -0
- package/dist/bus/adapters/google-pubsub.js.map +1 -0
- package/dist/bus/adapters/in-process.d.ts +12 -0
- package/dist/bus/adapters/in-process.js +118 -0
- package/dist/bus/adapters/in-process.js.map +1 -0
- package/dist/bus/adapters/index.d.ts +6 -0
- package/dist/bus/adapters/index.js +22 -0
- package/dist/bus/adapters/index.js.map +1 -0
- package/dist/bus/adapters/nats.d.ts +13 -0
- package/dist/bus/adapters/nats.js +26 -0
- package/dist/bus/adapters/nats.js.map +1 -0
- package/dist/bus/adapters/redis.d.ts +13 -0
- package/dist/bus/adapters/redis.js +26 -0
- package/dist/bus/adapters/redis.js.map +1 -0
- package/dist/bus/events.d.ts +295 -0
- package/dist/bus/events.js +2 -0
- package/dist/bus/events.js.map +1 -0
- package/dist/bus/helpers.d.ts +7 -0
- package/dist/bus/helpers.js +16 -0
- package/dist/bus/helpers.js.map +1 -0
- package/dist/bus/index.d.ts +30 -0
- package/dist/bus/index.js +66 -0
- package/dist/bus/index.js.map +1 -0
- package/dist/bus/orchestrator.d.ts +51 -0
- package/dist/bus/orchestrator.js +1350 -0
- package/dist/bus/orchestrator.js.map +1 -0
- package/dist/bus/types.d.ts +23 -0
- package/dist/bus/types.js +2 -0
- package/dist/bus/types.js.map +1 -0
- package/dist/bus/workers/audit-worker.d.ts +9 -0
- package/dist/bus/workers/audit-worker.js +125 -0
- package/dist/bus/workers/audit-worker.js.map +1 -0
- package/dist/bus/workers/poc-harness-worker.d.ts +9 -0
- package/dist/bus/workers/poc-harness-worker.js +96 -0
- package/dist/bus/workers/poc-harness-worker.js.map +1 -0
- package/dist/bus/workers/report-worker.d.ts +11 -0
- package/dist/bus/workers/report-worker.js +235 -0
- package/dist/bus/workers/report-worker.js.map +1 -0
- package/dist/bus/workers/scan-worker.d.ts +13 -0
- package/dist/bus/workers/scan-worker.js +223 -0
- package/dist/bus/workers/scan-worker.js.map +1 -0
- package/dist/bus/workers/store-worker.d.ts +10 -0
- package/dist/bus/workers/store-worker.js +62 -0
- package/dist/bus/workers/store-worker.js.map +1 -0
- package/dist/bus/workers/triage-worker.d.ts +13 -0
- package/dist/bus/workers/triage-worker.js +129 -0
- package/dist/bus/workers/triage-worker.js.map +1 -0
- package/dist/bus/workers/verification-worker.d.ts +9 -0
- package/dist/bus/workers/verification-worker.js +91 -0
- package/dist/bus/workers/verification-worker.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +513 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +866 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +5 -0
- package/dist/context.js +26 -0
- package/dist/context.js.map +1 -0
- package/dist/deps.d.ts +15 -0
- package/dist/deps.js +18 -0
- package/dist/deps.js.map +1 -0
- package/dist/llm/anthropic.d.ts +3 -0
- package/dist/llm/anthropic.js +21 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/poc-harness.d.ts +49 -0
- package/dist/poc-harness.js +420 -0
- package/dist/poc-harness.js.map +1 -0
- package/dist/report-markdown.d.ts +3 -0
- package/dist/report-markdown.js +197 -0
- package/dist/report-markdown.js.map +1 -0
- package/dist/report-sarif.d.ts +3 -0
- package/dist/report-sarif.js +253 -0
- package/dist/report-sarif.js.map +1 -0
- package/dist/report.d.ts +18 -0
- package/dist/report.js +146 -0
- package/dist/report.js.map +1 -0
- package/dist/retry.d.ts +6 -0
- package/dist/retry.js +25 -0
- package/dist/retry.js.map +1 -0
- package/dist/scanner/claude-adk.d.ts +26 -0
- package/dist/scanner/claude-adk.js +265 -0
- package/dist/scanner/claude-adk.js.map +1 -0
- package/dist/scanner/resolve.d.ts +10 -0
- package/dist/scanner/resolve.js +99 -0
- package/dist/scanner/resolve.js.map +1 -0
- package/dist/scanner.d.ts +47 -0
- package/dist/scanner.js +123 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scanners/agentic.d.ts +11 -0
- package/dist/scanners/agentic.js +71 -0
- package/dist/scanners/agentic.js.map +1 -0
- package/dist/scanners/claude-adk.d.ts +11 -0
- package/dist/scanners/claude-adk.js +74 -0
- package/dist/scanners/claude-adk.js.map +1 -0
- package/dist/scanners/codeql.d.ts +15 -0
- package/dist/scanners/codeql.js +97 -0
- package/dist/scanners/codeql.js.map +1 -0
- package/dist/scanners/finding-selection.d.ts +7 -0
- package/dist/scanners/finding-selection.js +120 -0
- package/dist/scanners/finding-selection.js.map +1 -0
- package/dist/scanners/index.d.ts +4 -0
- package/dist/scanners/index.js +14 -0
- package/dist/scanners/index.js.map +1 -0
- package/dist/scanners/registry.d.ts +12 -0
- package/dist/scanners/registry.js +70 -0
- package/dist/scanners/registry.js.map +1 -0
- package/dist/scanners/resolve-codeql.d.ts +9 -0
- package/dist/scanners/resolve-codeql.js +38 -0
- package/dist/scanners/resolve-codeql.js.map +1 -0
- package/dist/scanners/resolve-semgrep.d.ts +10 -0
- package/dist/scanners/resolve-semgrep.js +96 -0
- package/dist/scanners/resolve-semgrep.js.map +1 -0
- package/dist/scanners/run-agentic.d.ts +33 -0
- package/dist/scanners/run-agentic.js +267 -0
- package/dist/scanners/run-agentic.js.map +1 -0
- package/dist/scanners/run-claude-adk.d.ts +33 -0
- package/dist/scanners/run-claude-adk.js +267 -0
- package/dist/scanners/run-claude-adk.js.map +1 -0
- package/dist/scanners/run-codeql.d.ts +100 -0
- package/dist/scanners/run-codeql.js +538 -0
- package/dist/scanners/run-codeql.js.map +1 -0
- package/dist/scanners/run-semgrep.d.ts +41 -0
- package/dist/scanners/run-semgrep.js +115 -0
- package/dist/scanners/run-semgrep.js.map +1 -0
- package/dist/scanners/scoring.d.ts +7 -0
- package/dist/scanners/scoring.js +14 -0
- package/dist/scanners/scoring.js.map +1 -0
- package/dist/scanners/semgrep.d.ts +11 -0
- package/dist/scanners/semgrep.js +64 -0
- package/dist/scanners/semgrep.js.map +1 -0
- package/dist/scanners/types.d.ts +36 -0
- package/dist/scanners/types.js +2 -0
- package/dist/scanners/types.js.map +1 -0
- package/dist/scanners.d.ts +25 -0
- package/dist/scanners.js +88 -0
- package/dist/scanners.js.map +1 -0
- package/dist/store.d.ts +106 -0
- package/dist/store.js +609 -0
- package/dist/store.js.map +1 -0
- package/dist/triage.d.ts +74 -0
- package/dist/triage.js +314 -0
- package/dist/triage.js.map +1 -0
- package/dist/types.d.ts +166 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +19 -0
- package/dist/utils.js.map +1 -0
- package/dist/verify.d.ts +56 -0
- package/dist/verify.js +298 -0
- package/dist/verify.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shayaun Nejad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
<img src="kuzushi.png" alt="Kuzushi" width="200" />
|
|
2
|
+
|
|
3
|
+
# Kuzushi — Agentic SAST Orchestrator
|
|
4
|
+
|
|
5
|
+
Agentic SAST orchestrator. Runs security analysis tasks — scanners, AI triage, exploit verification, and more — as a dependency graph on an event-driven pipeline, then tells you what's actually dangerous.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
Prereqs: Node 22+, an API key for at least one supported LLM provider.
|
|
10
|
+
|
|
11
|
+
**With Anthropic (default):**
|
|
12
|
+
|
|
13
|
+
1. Get an API key at https://console.anthropic.com/
|
|
14
|
+
2. Set it: `export ANTHROPIC_API_KEY=sk-ant-...`
|
|
15
|
+
3. Scan: `npx kuzushi /path/to/your/repo`
|
|
16
|
+
|
|
17
|
+
**With OpenAI, Google, or any pi-ai-supported provider:**
|
|
18
|
+
|
|
19
|
+
1. Set the provider key: `export OPENAI_API_KEY=sk-...` (or `GEMINI_API_KEY`, etc.)
|
|
20
|
+
2. Scan: `npx kuzushi /path/to/repo --agent-runtime pi-ai --model openai:gpt-4o`
|
|
21
|
+
|
|
22
|
+
That's it. Kuzushi auto-downloads Opengrep if you don't have a scanner installed. To add CodeQL, see [CodeQL Setup](#codeql-setup).
|
|
23
|
+
|
|
24
|
+
## What It Does
|
|
25
|
+
|
|
26
|
+
- **Runs Opengrep/Semgrep** with severity-ranked rule matching
|
|
27
|
+
- **Runs configurable scanners** (`semgrep`, `agentic`, `codeql`) in one orchestration flow
|
|
28
|
+
- **Gathers repo context** — auto-detects language, frameworks, auth patterns, ORMs, and sanitization libraries to enrich AI analysis
|
|
29
|
+
- **Scores and deduplicates** findings by severity, likelihood, impact, and subcategory — cross-scanner normalization merges equivalent findings from different scanners at the same location
|
|
30
|
+
- **AI-triages selected findings** — after dedupe/resume/max filters, agent investigates with repo tools, assigns tp/fp/needs_review with confidence and rationale
|
|
31
|
+
- **Verifies exploitability** — optional post-triage phase constructs concrete proof-of-concept payloads for true positives (e.g., SQL injection strings, XSS vectors)
|
|
32
|
+
- **Generates PoC harnesses** — optional post-verification phase produces runnable exploit scripts (TypeScript, Python, etc.) for verified-exploitable findings
|
|
33
|
+
- **Vendor-agnostic LLM runtime** — swap between Anthropic, OpenAI, Google, and 15+ other providers via the `pi-ai` backend with zero consumer-code changes
|
|
34
|
+
- **Augur integration** — multi-pass CodeQL-based source/sink labeling pipeline with LLM-assisted classification, checkpoint gating, and deterministic library generation
|
|
35
|
+
- **Tracks cost** — per-finding triage, verification, and PoC harness costs are persisted and displayed in the summary
|
|
36
|
+
- **Event-driven pipeline** — pluggable message bus interface (in-process backend implemented; Redis/Google Pub/Sub/NATS adapters are scaffolded)
|
|
37
|
+
- **DAG-based task orchestration** — tasks declare dependencies, run in parallel groups, pass outputs downstream
|
|
38
|
+
- **Extensible agent framework** — `AgentTask` interface for adding new analysis types (threat modeling, binary analysis, etc.)
|
|
39
|
+
- **Persists results** in SQLite — resume interrupted scans, skip already-triaged findings
|
|
40
|
+
- **Resumable runs** — checkpoint pipeline state to SQLite; `--resume` picks up where a crashed or interrupted scan left off
|
|
41
|
+
- **Retry with backoff** — transient agent failures are retried automatically with exponential backoff
|
|
42
|
+
- **Audit logging** — optional JSONL audit trail of every agent decision for debugging and accountability
|
|
43
|
+
- **Markdown reports** — export a shareable `.md` report for CI pipelines and team review
|
|
44
|
+
- **Prints a styled report** showing only what matters: true positives, needs-review items, and verified exploits with PoC payloads
|
|
45
|
+
|
|
46
|
+
## How It Works
|
|
47
|
+
|
|
48
|
+
Semgrep/Opengrep catches syntactic patterns but can't verify data flow or intent. LLMs can reason about code but hallucinate when scanning from scratch (95%+ false positive rate). Kuzushi combines both: SAST signal narrows the search space, LLM reasoning eliminates false positives. This hybrid approach matches human researcher agreement rates.
|
|
49
|
+
|
|
50
|
+
Under the hood, Kuzushi uses an event-driven architecture with a DAG-based task orchestrator:
|
|
51
|
+
|
|
52
|
+
1. **Context gathering** (optional, enabled by default) — the context-gatherer task analyzes the repo structure (package.json, go.mod, etc.) to identify the tech stack, frameworks, and security-relevant libraries.
|
|
53
|
+
2. **Pipeline starts** — the orchestrator resolves enabled tasks into a dependency graph and groups them into parallel stages.
|
|
54
|
+
3. **Scanners run** — scanner tasks (Semgrep, CodeQL, Agentic, etc.) execute concurrently within their stage, emitting findings as typed events on the message bus.
|
|
55
|
+
4. **Results gate downstream tasks** — the orchestrator waits for each stage to complete before starting dependent stages. Upstream outputs are forwarded to dependent tasks via `TaskContext`.
|
|
56
|
+
5. **Triage stage** — findings are deduplicated (fingerprint + cross-scanner location/CWE/rule normalization), ranked, and sent to an LLM for semantic verification with configurable concurrency. The repo context from step 1 enriches every triage prompt.
|
|
57
|
+
6. **Verification stage** (optional) — triaged findings that pass verification gates (`verifyVerdicts`, scanner-level `scannerConfig.<id>.verify`, and `verifyMinConfidence`) are sent to a verification agent that attempts to construct concrete PoC exploit payloads.
|
|
58
|
+
7. **PoC harness generation** (optional) — verified-exploitable findings are sent to a harness generator that produces runnable exploit scripts with syntax validation.
|
|
59
|
+
8. **Report** — final results are persisted, rendered to terminal, and optionally exported as markdown.
|
|
60
|
+
|
|
61
|
+
All communication happens through a transport-agnostic `MessageBus` interface. The default in-process bus works out of the box; distributed adapters (Redis, Google Pub/Sub, NATS) are planned and scaffolded behind the same interface.
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
### Scan (default)
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
kuzushi <repo> # scan with defaults
|
|
69
|
+
kuzushi <repo> --scanners codeql
|
|
70
|
+
kuzushi <repo> --scanners semgrep,codeql
|
|
71
|
+
kuzushi <repo> --scanners semgrep,agentic
|
|
72
|
+
kuzushi <repo> --severity ERROR # only ERROR-level findings
|
|
73
|
+
kuzushi <repo> --max 20 # triage top 20 findings only
|
|
74
|
+
kuzushi <repo> --model claude-opus-4-20250514 # use a different model
|
|
75
|
+
kuzushi <repo> --triage-model claude-opus-4-20250514 # separate model for triage
|
|
76
|
+
kuzushi <repo> --triage-max-turns 15 # triage agent turn budget
|
|
77
|
+
kuzushi <repo> --api-key sk-ant-... --base-url https://basecamp.stark.rubrik.com/
|
|
78
|
+
kuzushi <repo> --fresh # clear prior results, re-triage everything
|
|
79
|
+
kuzushi <repo> --db ./my.sqlite3 # custom database path
|
|
80
|
+
kuzushi <repo> --resume # resume the most recent interrupted run
|
|
81
|
+
kuzushi <repo> --resume <run-id> # resume a specific run by ID
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Vendor-Agnostic Runtime
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
kuzushi <repo> --agent-runtime pi-ai --model openai:gpt-4o
|
|
88
|
+
kuzushi <repo> --agent-runtime pi-ai --model google:gemini-2.0-flash
|
|
89
|
+
kuzushi <repo> --agent-runtime pi-ai --model anthropic:claude-sonnet-4-20250514
|
|
90
|
+
kuzushi config set agentRuntimeBackend pi-ai
|
|
91
|
+
kuzushi config set model openai:gpt-4o
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
When `agentRuntimeBackend` is `pi-ai`, model strings use `provider:modelId` format. The pi-ai backend implements its own agentic tool-calling loop with local Read/Glob/Grep tools, structured output enforcement, budget tracking, and abort support. All consumer code (triage, verify, PoC harness, scanners) works unchanged — the `AgentRuntime` abstraction handles it.
|
|
95
|
+
|
|
96
|
+
### Verification
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
kuzushi <repo> --verify # enable exploit verification for TPs
|
|
100
|
+
kuzushi <repo> --verify --verify-model claude-haiku-4-5-20251001 # cheaper model for verification
|
|
101
|
+
kuzushi <repo> --verify --verify-max-turns 20
|
|
102
|
+
kuzushi <repo> --verify --verify-concurrency 3
|
|
103
|
+
kuzushi <repo> --verify --verify-min-confidence 0.7 # skip low-confidence TPs
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### PoC Harness Generation
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
kuzushi <repo> --verify --poc-harness # generate exploit scripts for verified findings
|
|
110
|
+
kuzushi <repo> --verify --poc-harness --poc-harness-model claude-haiku-4-5-20251001
|
|
111
|
+
kuzushi <repo> --verify --poc-harness --poc-harness-max-turns 25
|
|
112
|
+
kuzushi <repo> --verify --poc-harness --poc-harness-concurrency 2
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Output & Observability
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
kuzushi <repo> --output report.md # export markdown report
|
|
119
|
+
kuzushi <repo> --sarif results.sarif # export SARIF v2.1.0
|
|
120
|
+
kuzushi <repo> --audit-log # write agent activity to .kuzushi/runs/{runId}/
|
|
121
|
+
kuzushi <repo> --no-context # disable repo context gathering
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Retry
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
kuzushi <repo> --max-triage-retries 3 # retry failed triage calls (default: 2)
|
|
128
|
+
kuzushi <repo> --max-verify-retries 3 # retry failed verification calls (default: 2)
|
|
129
|
+
kuzushi <repo> --retry-backoff-ms 10000 # initial backoff delay (default: 5000)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Config
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
kuzushi config get # show all config
|
|
136
|
+
kuzushi config get model # show one key
|
|
137
|
+
kuzushi config set model claude-opus-4-20250514
|
|
138
|
+
kuzushi config set scanners semgrep,agentic
|
|
139
|
+
kuzushi config set scannerConfig.codeql.dbPath ./codeql-db
|
|
140
|
+
kuzushi config set scannerConfig.codeql.suite javascript-security-extended
|
|
141
|
+
kuzushi config set scannerConfig.semgrep.binary opengrep
|
|
142
|
+
kuzushi config set scannerConfig.semgrep.configFlag auto
|
|
143
|
+
kuzushi config set scannerConfig.agentic.model claude-sonnet-4-20250514
|
|
144
|
+
kuzushi config set scannerConfig.agentic.maxFindings 25
|
|
145
|
+
kuzushi config set severity ERROR,WARNING,INFO
|
|
146
|
+
kuzushi config set verify true
|
|
147
|
+
kuzushi config set verifyMinConfidence 0.7
|
|
148
|
+
kuzushi config set auditLog true
|
|
149
|
+
kuzushi config unset model # reset to default
|
|
150
|
+
kuzushi config path # print config file location
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Global config lives at `~/.kuzushi/config.json`. Optional project overrides can live at `<repo>/.kuzushi/config.json`. CLI flags override config values.
|
|
154
|
+
|
|
155
|
+
Security note: `agentRuntimeConfig.apiKey` is stored in plaintext in config files. Prefer `--api-key` for one-off runs or `ANTHROPIC_API_KEY` from your shell/secret manager.
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
| Key | Default | Description |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| `model` | `claude-sonnet-4-20250514` | LLM model for scanners and default triage model |
|
|
162
|
+
| `triageModel` | _(uses `model`)_ | Override model used by the triage agent |
|
|
163
|
+
| `triageMaxTurns` | `10` | Max agentic turns per triage call |
|
|
164
|
+
| `scanners` | `["semgrep"]` | Scanner plugins to run, in order |
|
|
165
|
+
| `severity` | `["ERROR","WARNING"]` | Semgrep severity filter |
|
|
166
|
+
| `excludePatterns` | `["test","tests","node_modules",...]` | Directories/globs to skip |
|
|
167
|
+
| `scannerConfig` | `{ semgrep: {...}, agentic: {...}, codeql: {...} }` | Per-scanner config blocks keyed by scanner id |
|
|
168
|
+
| `busBackend` | `"in-process"` | Message bus transport (`in-process`, future: `redis`, `google-pubsub`, `nats`) |
|
|
169
|
+
| `triageConcurrency` | `1` | Parallel LLM triage calls |
|
|
170
|
+
| `scanMode` | `"sequential"` | Scanner execution mode (`sequential` or `concurrent`) |
|
|
171
|
+
| `enabledTasks` | `[]` | Additional agent tasks beyond scanners |
|
|
172
|
+
| `agentRuntimeBackend` | `"claude-sdk"` | Agent runtime backend (`claude-sdk`, `pi-ai`, future: `acp`) |
|
|
173
|
+
| `verify` | `false` | Enable proof-of-exploitability verification |
|
|
174
|
+
| `verifyModel` | _(uses `triageModel` or `model`)_ | Override model for verification agent |
|
|
175
|
+
| `verifyMaxTurns` | `15` | Max turns for verification agent |
|
|
176
|
+
| `verifyConcurrency` | `1` | Parallel verification calls |
|
|
177
|
+
| `verifyVerdicts` | `["tp"]` | Which triage verdicts to verify |
|
|
178
|
+
| `verifyMinConfidence` | `0` | Minimum triage confidence to trigger verification (0-1) |
|
|
179
|
+
| `pocHarness` | `false` | Enable post-verification PoC harness generation (requires `--verify`) |
|
|
180
|
+
| `pocHarnessModel` | _(uses `triageModel` or `model`)_ | Override model for PoC harness agent |
|
|
181
|
+
| `pocHarnessMaxTurns` | `20` | Max turns for PoC harness agent |
|
|
182
|
+
| `pocHarnessConcurrency` | `1` | Parallel PoC harness generation calls |
|
|
183
|
+
| `enableContextGathering` | `true` | Run repo context analysis before triage |
|
|
184
|
+
| `auditLog` | `false` | Write agent activity to JSONL audit files |
|
|
185
|
+
| `reportOutput` | _(unset)_ | Write markdown report output to this path |
|
|
186
|
+
| `sarifOutput` | _(unset)_ | Write SARIF v2.1.0 output to this path |
|
|
187
|
+
| `maxTriageRetries` | `2` | Retry failed triage calls |
|
|
188
|
+
| `maxVerifyRetries` | `2` | Retry failed verification calls |
|
|
189
|
+
| `maxPocHarnessRetries` | `2` | Retry failed PoC harness generation calls |
|
|
190
|
+
| `retryBackoffMs` | `5000` | Initial retry backoff delay in ms |
|
|
191
|
+
| `retryBackoffMultiplier` | `2` | Exponential backoff multiplier |
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"scanners": ["semgrep", "codeql", "agentic"],
|
|
198
|
+
"scanMode": "concurrent",
|
|
199
|
+
"triageConcurrency": 3,
|
|
200
|
+
"verify": true,
|
|
201
|
+
"verifyMinConfidence": 0.7,
|
|
202
|
+
"auditLog": true,
|
|
203
|
+
"enabledTasks": [],
|
|
204
|
+
"scannerConfig": {
|
|
205
|
+
"codeql": { "dbPath": "./codeql-db", "suite": "javascript-security-extended" },
|
|
206
|
+
"semgrep": { "binary": "opengrep", "configFlag": "auto" },
|
|
207
|
+
"agentic": { "model": "claude-sonnet-4-20250514", "maxFindings": 20 }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Environment Variables
|
|
213
|
+
|
|
214
|
+
| Variable | Required | Description |
|
|
215
|
+
| --- | --- | --- |
|
|
216
|
+
| `ANTHROPIC_API_KEY` | Yes (claude-sdk backend) | Anthropic API key — required when `agentRuntimeBackend` is `claude-sdk` |
|
|
217
|
+
| `OPENAI_API_KEY` | When using `openai:*` models | OpenAI API key for pi-ai backend |
|
|
218
|
+
| `GEMINI_API_KEY` / `GOOGLE_API_KEY` | When using `google:*` models | Google API key for pi-ai backend |
|
|
219
|
+
|
|
220
|
+
## Scanner Plugins
|
|
221
|
+
|
|
222
|
+
- `semgrep`: traditional SAST via Opengrep/Semgrep binary
|
|
223
|
+
- `codeql`: semantic dataflow/taint analysis via GitHub CodeQL CLI (SARIF output)
|
|
224
|
+
- `agentic`: AI-driven agentic scanner — LLM with read-only repo tools via any supported runtime
|
|
225
|
+
- `augur`: multi-pass CodeQL source/sink labeling pipeline — runs preflight (database creation, candidate extraction), LLM-assisted labeling with human-in-the-loop checkpoint, and deterministic library/query generation + analysis
|
|
226
|
+
|
|
227
|
+
## Semgrep Resolution
|
|
228
|
+
|
|
229
|
+
For the `semgrep` plugin, Kuzushi finds a scanner binary in this order:
|
|
230
|
+
|
|
231
|
+
1. `opengrep` on your PATH
|
|
232
|
+
2. `semgrep` on your PATH
|
|
233
|
+
3. Previously downloaded binary at `~/.kuzushi/bin/opengrep`
|
|
234
|
+
4. Auto-downloads Opengrep from GitHub releases (~40 MB, cached for future runs)
|
|
235
|
+
|
|
236
|
+
No pip, no brew, no manual install needed.
|
|
237
|
+
|
|
238
|
+
## CodeQL Setup
|
|
239
|
+
|
|
240
|
+
The `codeql` scanner requires the [CodeQL CLI](https://github.com/github/codeql-cli-binaries/releases) to be installed separately. Unlike Semgrep, it is **not auto-downloaded** (the CLI is ~500 MB and requires accepting GitHub's license).
|
|
241
|
+
|
|
242
|
+
Install it:
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
# Via GitHub CLI (recommended):
|
|
246
|
+
gh extension install github/gh-codeql && gh codeql install-stub
|
|
247
|
+
|
|
248
|
+
# Or download directly from:
|
|
249
|
+
# https://github.com/github/codeql-cli-binaries/releases
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Kuzushi finds the CodeQL binary in this order:
|
|
253
|
+
|
|
254
|
+
1. `codeql` on your PATH
|
|
255
|
+
2. Previously placed binary at `~/.kuzushi/bin/codeql`
|
|
256
|
+
3. Fails with install instructions if not found
|
|
257
|
+
|
|
258
|
+
CodeQL is **opt-in** — the default scanner list is `["semgrep"]`. To enable it:
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
kuzushi <repo> --scanners codeql # CodeQL only
|
|
262
|
+
kuzushi <repo> --scanners semgrep,codeql # both scanners
|
|
263
|
+
kuzushi config set scanners semgrep,codeql # persist as default
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
CodeQL builds a database from your source code before running queries. You can skip this step by pointing to a pre-built database:
|
|
267
|
+
|
|
268
|
+
```sh
|
|
269
|
+
kuzushi config set scannerConfig.codeql.dbPath ./codeql-db
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Pi-AI Runtime
|
|
273
|
+
|
|
274
|
+
The `pi-ai` backend uses `@mariozechner/pi-ai` to provide vendor-agnostic LLM access. It supports 15+ providers (Anthropic, OpenAI, Google, Groq, Mistral, etc.) through a single interface.
|
|
275
|
+
|
|
276
|
+
Unlike the Claude SDK backend (which has a built-in agentic loop), the pi-ai backend implements its own:
|
|
277
|
+
|
|
278
|
+
1. **Tool-calling loop** — call model, parse tool calls, execute tools, feed results back, repeat until stop or max turns
|
|
279
|
+
2. **Local tool implementations** — Read (file reader with line numbers), Glob (Node 22+ `globSync`), Grep (regex search across files)
|
|
280
|
+
3. **Structured output** — system prompt injection + post-hoc JSON extraction from fenced code blocks or raw text
|
|
281
|
+
4. **Safety controls** — max turns, budget enforcement, abort signal, permission gating via `canUseTool`
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
# Use with any supported provider:
|
|
285
|
+
OPENAI_API_KEY=... kuzushi <repo> --agent-runtime pi-ai --model openai:gpt-4o
|
|
286
|
+
GEMINI_API_KEY=... kuzushi <repo> --agent-runtime pi-ai --model google:gemini-2.0-flash
|
|
287
|
+
ANTHROPIC_API_KEY=... kuzushi <repo> --agent-runtime pi-ai --model anthropic:claude-sonnet-4-20250514
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Augur Setup
|
|
291
|
+
|
|
292
|
+
The `augur` scanner is a multi-pass CodeQL-based pipeline that uses LLM-assisted classification to label sources, sinks, sanitizers, and summaries. It requires:
|
|
293
|
+
|
|
294
|
+
1. **CodeQL CLI** — same requirement as the `codeql` scanner
|
|
295
|
+
2. **Python 3** — used by Augur's scripts for query generation
|
|
296
|
+
|
|
297
|
+
Augur's templates, references, and scripts are bundled as the [`@kuzushi/augur`](https://www.npmjs.com/package/@kuzushi/augur) npm package and installed automatically with `pnpm install`. No manual clone or `AUGUR_PATH` setup needed.
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
kuzushi <repo> --scanners augur
|
|
301
|
+
kuzushi <repo> --scanners augur --approve-checkpoint # auto-approve label review
|
|
302
|
+
kuzushi config set scannerConfig.augur.labelingModel claude-sonnet-4-20250514
|
|
303
|
+
kuzushi config set scannerConfig.augur.passes "[1,2,3,4,5,6]"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
To override the bundled augur assets (e.g., for local development), set `AUGUR_PATH` or `scannerConfig.augur.augurPath`:
|
|
307
|
+
|
|
308
|
+
```sh
|
|
309
|
+
export AUGUR_PATH=/path/to/local/augur
|
|
310
|
+
kuzushi config set scannerConfig.augur.augurPath /path/to/local/augur
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Augur runs in three DAG-ordered stages: **preflight** (database creation, candidate extraction), **label** (LLM classification with checkpoint gate), and **analyze** (library generation, query execution, finding extraction). A human-in-the-loop checkpoint pauses after labeling for review — use `--approve-checkpoint` to auto-approve in CI.
|
|
314
|
+
|
|
315
|
+
## Output
|
|
316
|
+
|
|
317
|
+
Results are stored in SQLite at `<repo>/.kuzushi/findings.sqlite3`. Each finding includes:
|
|
318
|
+
|
|
319
|
+
- **verdict**: `tp` (true positive), `fp` (false positive), or `needs_review`
|
|
320
|
+
- **confidence**: 0.0-1.0
|
|
321
|
+
- **rationale**: why the LLM reached that verdict, referencing specific code
|
|
322
|
+
- **verification_steps**: 2-6 steps a human reviewer can follow
|
|
323
|
+
- **fix_patch**: suggested fix (when applicable)
|
|
324
|
+
- **exploitability** (with `--verify`): whether a concrete exploit was constructed, PoC payload, attack vector, and preconditions
|
|
325
|
+
- **cost**: per-finding triage and verification cost in USD
|
|
326
|
+
|
|
327
|
+
The terminal report shows true positives first, then needs-review items. False positives are counted but hidden. Verified exploitable findings are highlighted with their PoC payloads.
|
|
328
|
+
|
|
329
|
+
Use `--output report.md` to export a shareable markdown report.
|
|
330
|
+
Use `--sarif results.sarif` to export SARIF v2.1.0 for code scanning platforms.
|
|
331
|
+
|
|
332
|
+
## SARIF / GitHub Code Scanning
|
|
333
|
+
|
|
334
|
+
Kuzushi can emit SARIF v2.1.0 directly. GitHub Code Scanning ingests SARIF and creates inline annotations.
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
kuzushi <repo> --sarif results.sarif
|
|
338
|
+
|
|
339
|
+
gh api \
|
|
340
|
+
-X POST \
|
|
341
|
+
repos/OWNER/REPO/code-scanning/sarifs \
|
|
342
|
+
-f commit_sha="$(git rev-parse HEAD)" \
|
|
343
|
+
-f ref="refs/heads/$(git rev-parse --abbrev-ref HEAD)" \
|
|
344
|
+
-f sarif="$(gzip -c results.sarif | base64 | tr -d '\n')"
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Resume Support
|
|
348
|
+
|
|
349
|
+
Kuzushi fingerprints every finding (content-based SHA-256 that survives line shifts). Re-running a scan skips already-triaged findings automatically. Use `--fresh` to start over.
|
|
350
|
+
|
|
351
|
+
For interrupted runs, use `--resume` to pick up where the pipeline left off. Kuzushi checkpoints pipeline state (scan findings, triage progress, verification progress) to SQLite. On resume, completed phases are skipped and only remaining work is executed.
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
kuzushi <repo> --resume # resume most recent interrupted run
|
|
355
|
+
kuzushi <repo> --resume abc-123 # resume a specific run by ID
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Audit Logging
|
|
359
|
+
|
|
360
|
+
With `--audit-log`, Kuzushi writes a structured audit trail to `.kuzushi/runs/{runId}/`:
|
|
361
|
+
|
|
362
|
+
- `triage.jsonl` — every tool call, reasoning step, and verdict from triage agents
|
|
363
|
+
- `verify.jsonl` — same for verification agents
|
|
364
|
+
- `run.json` — run config and scan options
|
|
365
|
+
- `stats.json` — final pipeline statistics
|
|
366
|
+
|
|
367
|
+
Use this to debug verdicts, review agent reasoning, or build compliance records.
|
|
368
|
+
|
|
369
|
+
## Architecture
|
|
370
|
+
|
|
371
|
+
Kuzushi is built on three core abstractions:
|
|
372
|
+
|
|
373
|
+
**Message Bus** — A transport-agnostic `MessageBus` interface (`publish`, `subscribe`, `waitFor`) that decouples pipeline stages. The default in-process implementation uses an `EventEmitter`; the interface supports swapping in Redis, Google Pub/Sub, or NATS for distributed setups.
|
|
374
|
+
|
|
375
|
+
**AgentTask + DAG** — Every unit of work (context gatherer, scanner, future threat modeler, etc.) implements the `AgentTask` interface: an `id`, `dependsOn` list, `outputKind`, and a `run()` method. The `TaskRegistry` resolves enabled tasks into a DAG, groups them into parallel stages, detects cycles, and hands execution to the `PipelineOrchestrator`. Upstream task outputs are forwarded to dependents automatically.
|
|
376
|
+
|
|
377
|
+
**Pipeline Phases** — After the DAG completes, the orchestrator drives three sequential phases: triage (classify findings), verification (construct PoC exploits), and report (display results). Each phase has its own concurrency control, cost tracking, and checkpoint support.
|
|
378
|
+
|
|
379
|
+
Existing `ScannerPlugin` implementations (Semgrep, Agentic) are adapted into `AgentTask` via `adaptScannerPlugin()`, so the scanner plugin API remains stable.
|
|
380
|
+
|
|
381
|
+
See [AGENTS.md](AGENTS.md) for the full developer guide on adding new agent tasks.
|
|
382
|
+
|
|
383
|
+
## Development
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
pnpm install # install deps
|
|
387
|
+
pnpm dev -- /path/to/repo # run in dev mode
|
|
388
|
+
pnpm typecheck # type check
|
|
389
|
+
pnpm test # run tests (214 tests across 31 files)
|
|
390
|
+
pnpm test:coverage # tests + coverage (70% threshold)
|
|
391
|
+
pnpm build # compile to dist/
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Tests are organized by subsystem: `tests/bus/` for orchestrator, workers, and event bus tests, `tests/agents/` for DAG, task registry, and context-gatherer tests, and `tests/` for scanners, triage, verification, store, config, retry, and report.
|
|
395
|
+
|
|
396
|
+
## Troubleshooting
|
|
397
|
+
|
|
398
|
+
- **"Error: ANTHROPIC_API_KEY environment variable is required."**: Export your key — `export ANTHROPIC_API_KEY=sk-ant-...` (only required for `claude-sdk` backend; use `--agent-runtime pi-ai` with other providers)
|
|
399
|
+
- **"No findings from scanner. Code looks clean."**: Your code is clean, or try `--severity ERROR,WARNING,INFO` to include lower-severity rules
|
|
400
|
+
- **Scan interrupted**: Re-run the same command (already-triaged findings are skipped), or use `--resume` to continue from the exact checkpoint
|
|
401
|
+
- **Wrong model**: `kuzushi config set model claude-opus-4-20250514` or pass `--model` per-scan
|
|
402
|
+
- **Scanner download fails**: Install Opengrep or Semgrep manually, ensure it's on your PATH
|
|
403
|
+
- **High triage cost**: Use `--triage-model claude-haiku-4-5-20251001` for cheaper triage, or `--max 10` to limit findings
|
|
404
|
+
- **Verification too expensive**: Use `--verify-min-confidence 0.8` to only verify high-confidence TPs, or `--verify-model claude-haiku-4-5-20251001`
|
|
405
|
+
- **pi-ai model not found**: Ensure the model string uses `provider:modelId` format (e.g., `openai:gpt-4o`, not just `gpt-4o`)
|
|
406
|
+
- **Augur checkpoint blocks CI**: Pass `--approve-checkpoint` to auto-approve label review in non-interactive environments
|
|
407
|
+
|
|
408
|
+
## License
|
|
409
|
+
|
|
410
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentRuntimeConfig } from "../types.js";
|
|
2
|
+
import type { AgentMessage, AgentQueryRequest, AgentRuntime } from "./types.js";
|
|
3
|
+
export declare class ClaudeSdkAgentRuntime implements AgentRuntime {
|
|
4
|
+
readonly id = "claude-sdk";
|
|
5
|
+
private readonly runtimeConfig?;
|
|
6
|
+
constructor(runtimeConfig?: AgentRuntimeConfig);
|
|
7
|
+
query(request: AgentQueryRequest): AsyncIterable<AgentMessage>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { query, } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { isRecord } from "../utils.js";
|
|
3
|
+
export class ClaudeSdkAgentRuntime {
|
|
4
|
+
id = "claude-sdk";
|
|
5
|
+
runtimeConfig;
|
|
6
|
+
constructor(runtimeConfig) {
|
|
7
|
+
this.runtimeConfig = runtimeConfig;
|
|
8
|
+
}
|
|
9
|
+
async *query(request) {
|
|
10
|
+
const canUseTool = request.options.canUseTool
|
|
11
|
+
? adaptCanUseTool(request.options.canUseTool)
|
|
12
|
+
: undefined;
|
|
13
|
+
const runtimeEnv = buildRuntimeEnv(this.runtimeConfig);
|
|
14
|
+
for await (const message of query({
|
|
15
|
+
prompt: request.prompt,
|
|
16
|
+
options: {
|
|
17
|
+
cwd: request.options.cwd,
|
|
18
|
+
model: request.options.model,
|
|
19
|
+
maxTurns: request.options.maxTurns,
|
|
20
|
+
allowedTools: request.options.allowedTools,
|
|
21
|
+
disallowedTools: request.options.disallowedTools,
|
|
22
|
+
permissionMode: request.options.permissionMode,
|
|
23
|
+
maxBudgetUsd: request.options.maxBudgetUsd,
|
|
24
|
+
abortController: request.options.abortController,
|
|
25
|
+
outputFormat: request.options.outputFormat
|
|
26
|
+
? {
|
|
27
|
+
type: "json_schema",
|
|
28
|
+
schema: request.options.outputFormat.schema,
|
|
29
|
+
}
|
|
30
|
+
: undefined,
|
|
31
|
+
canUseTool,
|
|
32
|
+
env: runtimeEnv,
|
|
33
|
+
},
|
|
34
|
+
})) {
|
|
35
|
+
yield mapMessage(message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function buildRuntimeEnv(runtimeConfig) {
|
|
40
|
+
if (!runtimeConfig?.apiKey && !runtimeConfig?.baseUrl) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
...process.env,
|
|
45
|
+
ANTHROPIC_API_KEY: runtimeConfig.apiKey ?? process.env["ANTHROPIC_API_KEY"],
|
|
46
|
+
ANTHROPIC_BASE_URL: runtimeConfig.baseUrl ?? process.env["ANTHROPIC_BASE_URL"],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mapMessage(message) {
|
|
50
|
+
if (message.type === "result") {
|
|
51
|
+
if (message.subtype === "success") {
|
|
52
|
+
return {
|
|
53
|
+
type: "result",
|
|
54
|
+
subtype: "success",
|
|
55
|
+
result: message.result,
|
|
56
|
+
structuredOutput: message.structured_output,
|
|
57
|
+
durationMs: message.duration_ms,
|
|
58
|
+
numTurns: message.num_turns,
|
|
59
|
+
totalCostUsd: message.total_cost_usd,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
type: "result",
|
|
64
|
+
subtype: message.subtype,
|
|
65
|
+
errors: [...message.errors],
|
|
66
|
+
durationMs: message.duration_ms,
|
|
67
|
+
numTurns: message.num_turns,
|
|
68
|
+
totalCostUsd: message.total_cost_usd,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (message.type === "assistant") {
|
|
72
|
+
const content = Array.isArray(message.message.content)
|
|
73
|
+
? message.message.content.filter(isRecord)
|
|
74
|
+
: [];
|
|
75
|
+
return {
|
|
76
|
+
type: "assistant",
|
|
77
|
+
content,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (message.type === "tool_progress") {
|
|
81
|
+
return {
|
|
82
|
+
type: "tool-progress",
|
|
83
|
+
toolUseId: message.tool_use_id,
|
|
84
|
+
toolName: message.tool_name,
|
|
85
|
+
elapsedSeconds: message.elapsed_time_seconds,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (message.type === "tool_use_summary") {
|
|
89
|
+
return {
|
|
90
|
+
type: "tool-summary",
|
|
91
|
+
summary: message.summary,
|
|
92
|
+
precedingToolUseIds: [...message.preceding_tool_use_ids],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
type: "unknown",
|
|
97
|
+
raw: message,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function adaptCanUseTool(handler) {
|
|
101
|
+
return async (toolName, input, options) => {
|
|
102
|
+
const decision = await handler(toolName, input, {
|
|
103
|
+
signal: options.signal,
|
|
104
|
+
toolUseId: options.toolUseID,
|
|
105
|
+
blockedPath: options.blockedPath,
|
|
106
|
+
decisionReason: options.decisionReason,
|
|
107
|
+
});
|
|
108
|
+
return toPermissionResult(decision);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function toPermissionResult(decision) {
|
|
112
|
+
if (decision.behavior === "allow") {
|
|
113
|
+
return {
|
|
114
|
+
behavior: "allow",
|
|
115
|
+
updatedInput: undefined,
|
|
116
|
+
updatedPermissions: undefined,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
behavior: "deny",
|
|
121
|
+
message: decision.message ?? "Permission denied",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/agent-runtime/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KuzushiConfig } from "../types.js";
|
|
2
|
+
import { ClaudeSdkAgentRuntime } from "./claude.js";
|
|
3
|
+
import { PiAiAgentRuntime } from "./pi-ai.js";
|
|
4
|
+
import type { AgentRuntime } from "./types.js";
|
|
5
|
+
export declare function getAgentRuntime(): AgentRuntime;
|
|
6
|
+
export declare function setAgentRuntime(runtime: AgentRuntime): void;
|
|
7
|
+
export declare function configureAgentRuntime(config: KuzushiConfig): AgentRuntime;
|
|
8
|
+
export * from "./types.js";
|
|
9
|
+
export { ClaudeSdkAgentRuntime, PiAiAgentRuntime };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ClaudeSdkAgentRuntime } from "./claude.js";
|
|
2
|
+
import { PiAiAgentRuntime } from "./pi-ai.js";
|
|
3
|
+
const DEFAULT_BACKEND = "claude-sdk";
|
|
4
|
+
let currentRuntime = new ClaudeSdkAgentRuntime();
|
|
5
|
+
export function getAgentRuntime() {
|
|
6
|
+
return currentRuntime;
|
|
7
|
+
}
|
|
8
|
+
export function setAgentRuntime(runtime) {
|
|
9
|
+
currentRuntime = runtime;
|
|
10
|
+
}
|
|
11
|
+
export function configureAgentRuntime(config) {
|
|
12
|
+
const backend = config.agentRuntimeBackend ?? DEFAULT_BACKEND;
|
|
13
|
+
const runtimeConfig = config.agentRuntimeConfig;
|
|
14
|
+
if (backend === "claude-sdk") {
|
|
15
|
+
currentRuntime = new ClaudeSdkAgentRuntime(runtimeConfig);
|
|
16
|
+
return currentRuntime;
|
|
17
|
+
}
|
|
18
|
+
if (backend === "pi-ai") {
|
|
19
|
+
currentRuntime = new PiAiAgentRuntime(runtimeConfig);
|
|
20
|
+
return currentRuntime;
|
|
21
|
+
}
|
|
22
|
+
if (backend === "acp") {
|
|
23
|
+
throw new Error("agentRuntimeBackend=acp is not available yet. "
|
|
24
|
+
+ "Use agentRuntimeBackend=claude-sdk or agentRuntimeBackend=pi-ai until ACP runtime adapter is implemented.");
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`Unknown agent runtime backend: ${String(backend)}. `
|
|
27
|
+
+ "Expected one of: claude-sdk, pi-ai, acp.");
|
|
28
|
+
}
|
|
29
|
+
export * from "./types.js";
|
|
30
|
+
export { ClaudeSdkAgentRuntime, PiAiAgentRuntime };
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent-runtime/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC,IAAI,cAAc,GAAiB,IAAI,qBAAqB,EAAE,CAAC;AAE/D,MAAM,UAAU,eAAe;IAC7B,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAqB;IACnD,cAAc,GAAG,OAAO,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAqB;IACzD,MAAM,OAAO,GAAG,MAAM,CAAC,mBAAmB,IAAI,eAAe,CAAC;IAC9D,MAAM,aAAa,GAAG,MAAM,CAAC,kBAAkB,CAAC;IAEhD,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,cAAc,GAAG,IAAI,qBAAqB,CAAC,aAAa,CAAC,CAAC;QAC1D,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,cAAc,GAAG,IAAI,gBAAgB,CAAC,aAAa,CAAC,CAAC;QACrD,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,gDAAgD;cAC9C,2GAA2G,CAC9G,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,KAAK,CACb,kCAAkC,MAAM,CAAC,OAAO,CAAC,IAAI;UACnD,0CAA0C,CAC7C,CAAC;AACJ,CAAC;AAED,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a pi-ai model identifier in provider:modelId format.
|
|
3
|
+
*/
|
|
4
|
+
export function parsePiAiModelSpec(model) {
|
|
5
|
+
const value = model.trim();
|
|
6
|
+
const separatorIndex = value.indexOf(":");
|
|
7
|
+
if (separatorIndex === -1) {
|
|
8
|
+
throw new Error(`pi-ai model "${model}" is invalid. Expected format: provider:modelId`);
|
|
9
|
+
}
|
|
10
|
+
const provider = value.slice(0, separatorIndex).trim();
|
|
11
|
+
const modelId = value.slice(separatorIndex + 1).trim();
|
|
12
|
+
if (!provider || !modelId) {
|
|
13
|
+
throw new Error(`pi-ai model "${model}" is invalid. Expected format: provider:modelId`);
|
|
14
|
+
}
|
|
15
|
+
return { provider, modelId };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=model-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-spec.js","sourceRoot":"","sources":["../../src/agent-runtime/model-spec.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC3B,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAE1C,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,gBAAgB,KAAK,iDAAiD,CACvE,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEvD,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,gBAAgB,KAAK,iDAAiD,CACvE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentRuntimeConfig } from "../types.js";
|
|
2
|
+
import type { AgentMessage, AgentQueryRequest, AgentRuntime } from "./types.js";
|
|
3
|
+
export declare class PiAiAgentRuntime implements AgentRuntime {
|
|
4
|
+
readonly id = "pi-ai";
|
|
5
|
+
private readonly runtimeConfig?;
|
|
6
|
+
constructor(runtimeConfig?: AgentRuntimeConfig);
|
|
7
|
+
query(request: AgentQueryRequest): AsyncIterable<AgentMessage>;
|
|
8
|
+
}
|