Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,6 @@ jobs:

- name: SDK tests
run: npm test --workspace=@keyvaluesystems/agent-opfor-sdk

- name: SDK e2e tests
run: npm run test:e2e:sdk
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ third_party/
core/dist/
runners/cli/dist/
runners/mcp/dist/
runners/sdk/.tsup/
runners/extension/catalog.json
.opfor/
*.json
Expand Down
44 changes: 28 additions & 16 deletions docs/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@ npm install @keyvaluesystems/agent-opfor-sdk

## Quick Start

### Runnable examples (monorepo)

If you’re working in this repository, see the runnable cookbook at [`runners/sdk/examples/README.md`](../runners/sdk/examples/README.md).

### Class-based API

```typescript
import { Opfor } from "@keyvaluesystems/agent-opfor-sdk";

const opfor = new Opfor({
apiKey: process.env.ANTHROPIC_API_KEY,
apiKey: process.env.ANTHROPIC_API_KEY, // or your own secret manager / config
});

const results = await opfor.run({
target: {
url: "https://api.example.com/chat",
apiKeyEnv: "TARGET_API_KEY",
apiKey: process.env.TARGET_API_KEY,
},

suite: "owasp-llm-top10",
Expand All @@ -42,13 +46,21 @@ import { run, report } from "@keyvaluesystems/agent-opfor-sdk";
const results = await run({
target: {
url: "https://api.example.com/chat",
apiKeyEnv: "TARGET_API_KEY",
apiKey: process.env.TARGET_API_KEY,
},

suite: "owasp-llm-top10",

// Pass API key directly in options
apiKey: process.env.ANTHROPIC_API_KEY,
attackerModel: {
provider: "anthropic",
model: "claude-sonnet-4",
apiKey: process.env.ANTHROPIC_API_KEY,
},
judgeModel: {
provider: "anthropic",
model: "claude-sonnet-4",
apiKey: process.env.ANTHROPIC_API_KEY,
},
});

console.log(results.score);
Expand All @@ -65,7 +77,7 @@ Run adversarial tests against a target.
const results = await opfor.run({
target: {
url: "https://api.example.com/chat",
apiKeyEnv: "TARGET_API_KEY",
apiKey: process.env.TARGET_API_KEY,
model: "gpt-4o",
},

Expand Down Expand Up @@ -162,7 +174,7 @@ const results = await opfor.run({
name: "My Chatbot",
description: "Customer support chatbot with access to order database",

apiKeyEnv: "TARGET_API_KEY",
apiKey: process.env.TARGET_API_KEY,
model: "gpt-4o",

headers: {
Expand Down Expand Up @@ -318,33 +330,33 @@ const results = await opfor.run({

```typescript
// OpenAI
attackerModel: { provider: "openai", model: "gpt-4o", apiKeyEnv: "OPENAI_API_KEY" }
attackerModel: { provider: "openai", model: "gpt-4o", apiKey: process.env.OPENAI_API_KEY }

// Anthropic
attackerModel: { provider: "anthropic", model: "claude-sonnet-4", apiKeyEnv: "ANTHROPIC_API_KEY" }
attackerModel: { provider: "anthropic", model: "claude-sonnet-4", apiKey: process.env.ANTHROPIC_API_KEY }

// Google
attackerModel: { provider: "google", model: "gemini-2.0-flash", apiKeyEnv: "GOOGLE_GENERATIVE_AI_API_KEY" }
attackerModel: { provider: "google", model: "gemini-2.0-flash", apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY }

// Groq
attackerModel: { provider: "groq", model: "llama-3.3-70b", apiKeyEnv: "GROQ_API_KEY" }
attackerModel: { provider: "groq", model: "llama-3.3-70b", apiKey: process.env.GROQ_API_KEY }

// DeepSeek
attackerModel: { provider: "deepseek", model: "deepseek-chat", apiKeyEnv: "DEEPSEEK_API_KEY" }
attackerModel: { provider: "deepseek", model: "deepseek-chat", apiKey: process.env.DEEPSEEK_API_KEY }

// Azure OpenAI
attackerModel: {
provider: "azure",
model: "gpt-4o",
apiKeyEnv: "AZURE_OPENAI_API_KEY",
apiKey: process.env.AZURE_OPENAI_API_KEY,
baseUrl: "https://my-resource.openai.azure.com"
}

// OpenAI-compatible (LiteLLM, Ollama, etc.)
attackerModel: {
provider: "openai-compatible",
model: "llama-3.3-70b",
apiKeyEnv: "CUSTOM_API_KEY",
apiKey: process.env.CUSTOM_API_KEY,
baseUrl: "http://localhost:4000"
}
```
Expand Down Expand Up @@ -589,7 +601,7 @@ import { run } from "@keyvaluesystems/agent-opfor-sdk";
const results = await run({
target: {
url: process.env.TARGET_URL!,
apiKeyEnv: "TARGET_API_KEY",
apiKey: process.env.TARGET_API_KEY,
},
suite: "owasp-llm-top10",
apiKey: process.env.ANTHROPIC_API_KEY,
Expand Down Expand Up @@ -620,7 +632,7 @@ const opfor = new Opfor({
const results = await opfor.run({
target: {
url: process.env.TARGET_URL!,
apiKeyEnv: "TARGET_API_KEY",
apiKey: process.env.TARGET_API_KEY,
},
suite: "owasp-llm-top10",
});
Expand Down
19 changes: 18 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"runners/cli",
"runners/mcp",
"runners/extension",
"runners/sdk"
"runners/sdk",
"tests/e2e/sdk"
],
"files": [
"evaluators/",
Expand All @@ -28,6 +29,7 @@
"clean": "rm -rf core/dist runners/*/dist core/*.tsbuildinfo runners/*/*.tsbuildinfo",
"install:cli": "npm run build && npm install -g ./runners/cli",
"test": "npm test --workspace=core",
"test:e2e:sdk": "npm test --workspace=@keyvaluesystems/agent-opfor-e2e-sdk",
"typecheck": "tsc -b core && npm run typecheck -w runners/cli && npm run typecheck -w runners/mcp && npm run typecheck -w runners/sdk",
"extension:catalog": "npm run build:catalog --workspace=@keyvaluesystems/agent-opfor-extension",
"lint": "eslint .",
Expand Down
51 changes: 51 additions & 0 deletions runners/sdk/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# SDK Examples (in-repo)

Runnable examples for `@keyvaluesystems/agent-opfor-sdk` from the monorepo.

## Prereqs

- Node.js 20+
- Install deps once at repo root:

```bash
npm install
```

## Quickstart (mock lane, no real keys)

These examples start an in-process mock target + mock OpenAI-compatible LLM. They pass a fake `apiKey` in code.

```bash
node --import tsx/esm runners/sdk/examples/run-suite.functional.ts
node --import tsx/esm runners/sdk/examples/run-with-progress-and-listeners.ts
node --import tsx/esm runners/sdk/examples/report-json-html.ts
node --import tsx/esm runners/sdk/examples/catalog-list.ts
```

## Real target lane (needs keys)

When pointing at real LLM providers / targets, pass the API keys in the `attackerModel` / `judgeModel` configs (or via the top-level `apiKey` fallback).

- **LiteLLM / OpenAI-compatible**: `LITELLM_BASE_URL` + `LITELLM_API_KEY` (see `run-suite.real.ts`)
- **Anthropic**: `apiKey` in model config
- **OpenAI**: `apiKey` in model config
- **Groq / Google / DeepSeek / Azure**: pass `apiKey` and optionally `baseUrl`

```bash
# Terminal 1 — start the test agent
cd tests/e2e/agents/vanilla-chat
cp .env.example .env # set PROVIDER=openai, BASE_URL, OPENAI_API_KEY
npm run dev

# Terminal 2 — real SDK run (from repo root)
export LITELLM_BASE_URL=https://llm.keyvalue.systems/v1
export LITELLM_API_KEY=...
node --import tsx/esm runners/sdk/examples/run-suite.real.ts
```

Examples that are templates (require extra setup): MCP targets and autonomous `hunt()`.

## Notes

- Examples are **repo-only** (not shipped in the npm package).
- For `run()`, prefer model auth via `apiKey` values in code.
Loading
Loading