cyrus-simple-agent-runner 0.0.2
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 +674 -0
- package/README.md +322 -0
- package/dist/SimpleAgentRunner.d.ts +52 -0
- package/dist/SimpleAgentRunner.d.ts.map +1 -0
- package/dist/SimpleAgentRunner.js +120 -0
- package/dist/SimpleAgentRunner.js.map +1 -0
- package/dist/SimpleClaudeRunner.d.ts +28 -0
- package/dist/SimpleClaudeRunner.d.ts.map +1 -0
- package/dist/SimpleClaudeRunner.js +150 -0
- package/dist/SimpleClaudeRunner.js.map +1 -0
- package/dist/errors.d.ts +72 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +111 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# cyrus-simple-agent-runner
|
|
2
|
+
|
|
3
|
+
A simple, type-safe abstraction for agent interactions that return enumerated responses.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`simple-agent-runner` provides a clean API for running agent queries where you need the agent to select from a predefined set of responses (e.g., "yes"/"no", "approve"/"reject"/"abstain"). It handles:
|
|
8
|
+
|
|
9
|
+
- **Type-safe response validation** - Generic types ensure compile-time checking
|
|
10
|
+
- **Comprehensive error handling** - Structured errors with codes and context
|
|
11
|
+
- **Progress tracking** - Optional callbacks for observability
|
|
12
|
+
- **Multiple backends** - Extensible architecture supports different agent SDKs
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add cyrus-simple-agent-runner
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { SimpleClaudeRunner } from "cyrus-simple-agent-runner";
|
|
24
|
+
|
|
25
|
+
// Define valid responses as a const array for type safety
|
|
26
|
+
const VALID_RESPONSES = ["yes", "no"] as const;
|
|
27
|
+
type YesNoResponse = typeof VALID_RESPONSES[number]; // "yes" | "no"
|
|
28
|
+
|
|
29
|
+
// Create runner
|
|
30
|
+
const runner = new SimpleClaudeRunner<YesNoResponse>({
|
|
31
|
+
validResponses: VALID_RESPONSES,
|
|
32
|
+
cyrusHome: "/Users/me/.cyrus",
|
|
33
|
+
maxTurns: 3,
|
|
34
|
+
timeoutMs: 30000, // 30 seconds
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Execute query
|
|
38
|
+
const result = await runner.query(
|
|
39
|
+
"Is TypeScript better than JavaScript for large projects?"
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
console.log(result.response); // "yes" or "no" (type-safe!)
|
|
43
|
+
console.log(result.durationMs); // Execution time
|
|
44
|
+
console.log(result.costUSD); // Cost (if available)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API Reference
|
|
48
|
+
|
|
49
|
+
### `SimpleClaudeRunner<T>`
|
|
50
|
+
|
|
51
|
+
The concrete implementation using Claude Agent SDK.
|
|
52
|
+
|
|
53
|
+
#### Constructor Options
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
interface SimpleAgentRunnerConfig<T extends string> {
|
|
57
|
+
// Required
|
|
58
|
+
validResponses: readonly T[]; // Valid response options
|
|
59
|
+
cyrusHome: string; // Cyrus home directory
|
|
60
|
+
|
|
61
|
+
// Optional
|
|
62
|
+
systemPrompt?: string; // Custom system prompt
|
|
63
|
+
maxTurns?: number; // Max turns before timeout
|
|
64
|
+
timeoutMs?: number; // Overall timeout in ms
|
|
65
|
+
model?: string; // Model to use
|
|
66
|
+
fallbackModel?: string; // Fallback model
|
|
67
|
+
workingDirectory?: string; // Working directory
|
|
68
|
+
onProgress?: (event) => void; // Progress callback
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Methods
|
|
73
|
+
|
|
74
|
+
##### `query(prompt: string, options?: SimpleAgentQueryOptions): Promise<SimpleAgentResult<T>>`
|
|
75
|
+
|
|
76
|
+
Execute a query and return a validated response.
|
|
77
|
+
|
|
78
|
+
**Options:**
|
|
79
|
+
```typescript
|
|
80
|
+
interface SimpleAgentQueryOptions {
|
|
81
|
+
context?: string; // Additional context
|
|
82
|
+
allowFileReading?: boolean; // Allow file operations
|
|
83
|
+
allowedDirectories?: string[]; // Allowed file paths
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Returns:**
|
|
88
|
+
```typescript
|
|
89
|
+
interface SimpleAgentResult<T extends string> {
|
|
90
|
+
response: T; // Validated response
|
|
91
|
+
messages: SDKMessage[]; // All SDK messages
|
|
92
|
+
sessionId: string | null; // Session ID
|
|
93
|
+
durationMs: number; // Execution time
|
|
94
|
+
costUSD?: number; // Cost (if available)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Error Handling
|
|
99
|
+
|
|
100
|
+
All errors extend `SimpleAgentError` and include error codes:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import {
|
|
104
|
+
InvalidResponseError,
|
|
105
|
+
TimeoutError,
|
|
106
|
+
NoResponseError,
|
|
107
|
+
MaxTurnsExceededError,
|
|
108
|
+
SessionError,
|
|
109
|
+
SimpleAgentErrorCode,
|
|
110
|
+
} from "cyrus-simple-agent-runner";
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await runner.query("Should we deploy to production?");
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof InvalidResponseError) {
|
|
116
|
+
console.error("Agent returned:", error.receivedResponse);
|
|
117
|
+
console.error("Valid options:", error.validResponses);
|
|
118
|
+
} else if (error instanceof TimeoutError) {
|
|
119
|
+
console.error("Timeout after:", error.timeoutMs);
|
|
120
|
+
} else if (error instanceof NoResponseError) {
|
|
121
|
+
console.error("No response produced");
|
|
122
|
+
} else if (error instanceof MaxTurnsExceededError) {
|
|
123
|
+
console.error("Max turns exceeded:", error.maxTurns);
|
|
124
|
+
} else if (error instanceof SessionError) {
|
|
125
|
+
console.error("Session error:", error.cause);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Error Codes
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
enum SimpleAgentErrorCode {
|
|
134
|
+
INVALID_RESPONSE = "INVALID_RESPONSE",
|
|
135
|
+
TIMEOUT = "TIMEOUT",
|
|
136
|
+
NO_RESPONSE = "NO_RESPONSE",
|
|
137
|
+
SESSION_ERROR = "SESSION_ERROR",
|
|
138
|
+
INVALID_CONFIG = "INVALID_CONFIG",
|
|
139
|
+
ABORTED = "ABORTED",
|
|
140
|
+
MAX_TURNS_EXCEEDED = "MAX_TURNS_EXCEEDED",
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Examples
|
|
145
|
+
|
|
146
|
+
### Yes/No Questions
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const VALID_RESPONSES = ["yes", "no"] as const;
|
|
150
|
+
type YesNo = typeof VALID_RESPONSES[number];
|
|
151
|
+
|
|
152
|
+
const runner = new SimpleClaudeRunner<YesNo>({
|
|
153
|
+
validResponses: VALID_RESPONSES,
|
|
154
|
+
cyrusHome: process.env.CYRUS_HOME!,
|
|
155
|
+
systemPrompt: "You are a helpful assistant. Answer questions concisely.",
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const result = await runner.query(
|
|
159
|
+
"Does this code follow best practices?"
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (result.response === "yes") {
|
|
163
|
+
console.log("✅ Code looks good!");
|
|
164
|
+
} else {
|
|
165
|
+
console.log("❌ Code needs improvements");
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Approval Workflow
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const APPROVAL_OPTIONS = ["approve", "reject", "abstain"] as const;
|
|
173
|
+
type ApprovalDecision = typeof APPROVAL_OPTIONS[number];
|
|
174
|
+
|
|
175
|
+
const approvalRunner = new SimpleClaudeRunner<ApprovalDecision>({
|
|
176
|
+
validResponses: APPROVAL_OPTIONS,
|
|
177
|
+
cyrusHome: process.env.CYRUS_HOME!,
|
|
178
|
+
systemPrompt: "You are a code reviewer. Review PRs carefully.",
|
|
179
|
+
maxTurns: 5,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const result = await approvalRunner.query(
|
|
183
|
+
"Review this pull request and decide: approve, reject, or abstain",
|
|
184
|
+
{ context: prDiff, allowFileReading: true }
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
switch (result.response) {
|
|
188
|
+
case "approve":
|
|
189
|
+
await mergePR();
|
|
190
|
+
break;
|
|
191
|
+
case "reject":
|
|
192
|
+
await requestChanges();
|
|
193
|
+
break;
|
|
194
|
+
case "abstain":
|
|
195
|
+
await requestHumanReview();
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### With Progress Tracking
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const runner = new SimpleClaudeRunner({
|
|
204
|
+
validResponses: ["high", "medium", "low"] as const,
|
|
205
|
+
cyrusHome: process.env.CYRUS_HOME!,
|
|
206
|
+
onProgress: (event) => {
|
|
207
|
+
switch (event.type) {
|
|
208
|
+
case "started":
|
|
209
|
+
console.log("Session started:", event.sessionId);
|
|
210
|
+
break;
|
|
211
|
+
case "thinking":
|
|
212
|
+
console.log("Agent:", event.text);
|
|
213
|
+
break;
|
|
214
|
+
case "tool-use":
|
|
215
|
+
console.log("Using tool:", event.toolName);
|
|
216
|
+
break;
|
|
217
|
+
case "response-detected":
|
|
218
|
+
console.log("Candidate response:", event.candidateResponse);
|
|
219
|
+
break;
|
|
220
|
+
case "validating":
|
|
221
|
+
console.log("Validating response...");
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const result = await runner.query(
|
|
228
|
+
"Rate the priority of this bug: high, medium, or low"
|
|
229
|
+
);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Custom System Prompt
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const runner = new SimpleClaudeRunner({
|
|
236
|
+
validResponses: ["safe", "unsafe"] as const,
|
|
237
|
+
cyrusHome: process.env.CYRUS_HOME!,
|
|
238
|
+
systemPrompt: `You are a security analyzer.
|
|
239
|
+
Analyze code for security vulnerabilities.
|
|
240
|
+
Consider: injection attacks, authentication issues, data exposure.
|
|
241
|
+
Be conservative - mark as "unsafe" if you have any concerns.`,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const result = await runner.query(
|
|
245
|
+
"Analyze this function for security issues",
|
|
246
|
+
{ context: functionCode, allowFileReading: false }
|
|
247
|
+
);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Extending for Other Agent SDKs
|
|
251
|
+
|
|
252
|
+
To create implementations for other agent SDKs (e.g., OpenAI, Anthropic Direct API):
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { SimpleAgentRunner } from "cyrus-simple-agent-runner";
|
|
256
|
+
|
|
257
|
+
export class SimpleGPTRunner<T extends string> extends SimpleAgentRunner<T> {
|
|
258
|
+
protected async executeAgent(
|
|
259
|
+
prompt: string,
|
|
260
|
+
options?: SimpleAgentQueryOptions
|
|
261
|
+
): Promise<SDKMessage[]> {
|
|
262
|
+
// Your GPT implementation here
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
protected extractResponse(messages: SDKMessage[]): string {
|
|
266
|
+
// Your response extraction logic
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Architecture
|
|
272
|
+
|
|
273
|
+
The package has two layers:
|
|
274
|
+
|
|
275
|
+
1. **`SimpleAgentRunner`** (abstract base class)
|
|
276
|
+
- Handles configuration validation
|
|
277
|
+
- Manages response validation
|
|
278
|
+
- Provides timeout handling
|
|
279
|
+
- Emits progress events
|
|
280
|
+
- Defines the contract for implementations
|
|
281
|
+
|
|
282
|
+
2. **`SimpleClaudeRunner`** (concrete implementation)
|
|
283
|
+
- Uses `cyrus-claude-runner` for execution
|
|
284
|
+
- Handles message parsing
|
|
285
|
+
- Cleans and normalizes responses
|
|
286
|
+
- Manages tool restrictions
|
|
287
|
+
|
|
288
|
+
## Best Practices
|
|
289
|
+
|
|
290
|
+
1. **Use const arrays for valid responses:**
|
|
291
|
+
```typescript
|
|
292
|
+
const VALID = ["a", "b"] as const;
|
|
293
|
+
type Response = typeof VALID[number];
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
2. **Set reasonable timeouts:**
|
|
297
|
+
```typescript
|
|
298
|
+
{ timeoutMs: 30000, maxTurns: 5 }
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
3. **Handle all error types:**
|
|
302
|
+
```typescript
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (error instanceof InvalidResponseError) { /* ... */ }
|
|
305
|
+
else if (error instanceof TimeoutError) { /* ... */ }
|
|
306
|
+
// ... handle all types
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
4. **Use progress callbacks for observability:**
|
|
311
|
+
```typescript
|
|
312
|
+
{ onProgress: (e) => logger.info(e) }
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
5. **Restrict tools for simple queries:**
|
|
316
|
+
```typescript
|
|
317
|
+
{ allowFileReading: false } // Default behavior
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { AgentProgressEvent, SimpleAgentQueryOptions, SimpleAgentResult, SimpleAgentRunnerConfig } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for simple agent runners that return enumerated responses.
|
|
5
|
+
*
|
|
6
|
+
* This class provides the core validation and flow control logic, while
|
|
7
|
+
* concrete implementations provide the actual agent execution.
|
|
8
|
+
*/
|
|
9
|
+
export declare abstract class SimpleAgentRunner<T extends string> {
|
|
10
|
+
protected readonly config: SimpleAgentRunnerConfig<T>;
|
|
11
|
+
protected readonly validResponseSet: Set<T>;
|
|
12
|
+
constructor(config: SimpleAgentRunnerConfig<T>);
|
|
13
|
+
/**
|
|
14
|
+
* Execute the agent with the given prompt and return a validated response.
|
|
15
|
+
*
|
|
16
|
+
* @param prompt - The question or instruction for the agent
|
|
17
|
+
* @param options - Optional query configuration
|
|
18
|
+
* @returns A validated response from the enumerated set
|
|
19
|
+
* @throws {InvalidResponseError} If agent returns invalid response
|
|
20
|
+
* @throws {TimeoutError} If execution times out
|
|
21
|
+
* @throws {NoResponseError} If agent produces no response
|
|
22
|
+
* @throws {SessionError} If underlying session fails
|
|
23
|
+
*/
|
|
24
|
+
query(prompt: string, options?: SimpleAgentQueryOptions): Promise<SimpleAgentResult<T>>;
|
|
25
|
+
/**
|
|
26
|
+
* Validate the configuration
|
|
27
|
+
*/
|
|
28
|
+
private validateConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a response is valid
|
|
31
|
+
*/
|
|
32
|
+
protected isValidResponse(response: string): response is T;
|
|
33
|
+
/**
|
|
34
|
+
* Build the complete system prompt
|
|
35
|
+
*/
|
|
36
|
+
protected buildSystemPrompt(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Emit a progress event if callback is configured
|
|
39
|
+
*/
|
|
40
|
+
protected emitProgress(event: AgentProgressEvent): void;
|
|
41
|
+
/**
|
|
42
|
+
* Abstract method: Execute the agent and return messages.
|
|
43
|
+
* Concrete implementations must provide this.
|
|
44
|
+
*/
|
|
45
|
+
protected abstract executeAgent(prompt: string, options?: SimpleAgentQueryOptions): Promise<SDKMessage[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Abstract method: Extract the final response from messages.
|
|
48
|
+
* Concrete implementations must provide this.
|
|
49
|
+
*/
|
|
50
|
+
protected abstract extractResponse(messages: SDKMessage[]): string;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=SimpleAgentRunner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SimpleAgentRunner.d.ts","sourceRoot":"","sources":["../src/SimpleAgentRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAMjE,OAAO,KAAK,EACX,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,uBAAuB,EACvB,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,8BAAsB,iBAAiB,CAAC,CAAC,SAAS,MAAM;IACvD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC;IACtD,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBAEhC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC;IAM9C;;;;;;;;;;OAUG;IACG,KAAK,CACV,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,uBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAkEhC;;OAEG;IACH,OAAO,CAAC,cAAc;IAyBtB;;OAEG;IACH,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,IAAI,CAAC;IAI1D;;OAEG;IACH,SAAS,CAAC,iBAAiB,IAAI,MAAM;IAkBrC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAMvD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,uBAAuB,GAC/B,OAAO,CAAC,UAAU,EAAE,CAAC;IAExB;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM;CAClE"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { InvalidResponseError, SimpleAgentError, SimpleAgentErrorCode, } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for simple agent runners that return enumerated responses.
|
|
4
|
+
*
|
|
5
|
+
* This class provides the core validation and flow control logic, while
|
|
6
|
+
* concrete implementations provide the actual agent execution.
|
|
7
|
+
*/
|
|
8
|
+
export class SimpleAgentRunner {
|
|
9
|
+
config;
|
|
10
|
+
validResponseSet;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.validateConfig(config);
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.validResponseSet = new Set(config.validResponses);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Execute the agent with the given prompt and return a validated response.
|
|
18
|
+
*
|
|
19
|
+
* @param prompt - The question or instruction for the agent
|
|
20
|
+
* @param options - Optional query configuration
|
|
21
|
+
* @returns A validated response from the enumerated set
|
|
22
|
+
* @throws {InvalidResponseError} If agent returns invalid response
|
|
23
|
+
* @throws {TimeoutError} If execution times out
|
|
24
|
+
* @throws {NoResponseError} If agent produces no response
|
|
25
|
+
* @throws {SessionError} If underlying session fails
|
|
26
|
+
*/
|
|
27
|
+
async query(prompt, options) {
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
try {
|
|
30
|
+
// Set up timeout if configured
|
|
31
|
+
const timeoutPromise = this.config.timeoutMs
|
|
32
|
+
? new Promise((_, reject) => {
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
reject(new SimpleAgentError(SimpleAgentErrorCode.TIMEOUT, `Operation timed out after ${this.config.timeoutMs}ms`));
|
|
35
|
+
}, this.config.timeoutMs);
|
|
36
|
+
})
|
|
37
|
+
: null;
|
|
38
|
+
// Execute the agent (delegated to concrete implementation)
|
|
39
|
+
const executionPromise = this.executeAgent(prompt, options);
|
|
40
|
+
// Race between execution and timeout
|
|
41
|
+
const messages = timeoutPromise
|
|
42
|
+
? await Promise.race([executionPromise, timeoutPromise])
|
|
43
|
+
: await executionPromise;
|
|
44
|
+
// Extract and validate response
|
|
45
|
+
const response = this.extractResponse(messages);
|
|
46
|
+
if (!this.isValidResponse(response)) {
|
|
47
|
+
throw new InvalidResponseError(response, Array.from(this.validResponseSet));
|
|
48
|
+
}
|
|
49
|
+
const durationMs = Date.now() - startTime;
|
|
50
|
+
// Extract session metadata
|
|
51
|
+
const sessionId = messages[0]?.session_id || null;
|
|
52
|
+
const resultMessage = messages.find((m) => m.type === "result");
|
|
53
|
+
const costUSD = resultMessage?.type === "result"
|
|
54
|
+
? resultMessage.total_cost_usd
|
|
55
|
+
: undefined;
|
|
56
|
+
return {
|
|
57
|
+
response: response,
|
|
58
|
+
messages,
|
|
59
|
+
sessionId,
|
|
60
|
+
durationMs,
|
|
61
|
+
costUSD,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof SimpleAgentError) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
throw new SimpleAgentError(SimpleAgentErrorCode.SESSION_ERROR, error instanceof Error ? error.message : String(error), { originalError: error });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate the configuration
|
|
73
|
+
*/
|
|
74
|
+
validateConfig(config) {
|
|
75
|
+
if (!config.validResponses || config.validResponses.length === 0) {
|
|
76
|
+
throw new SimpleAgentError(SimpleAgentErrorCode.INVALID_CONFIG, "validResponses must be a non-empty array");
|
|
77
|
+
}
|
|
78
|
+
// Check for duplicates
|
|
79
|
+
const uniqueResponses = new Set(config.validResponses);
|
|
80
|
+
if (uniqueResponses.size !== config.validResponses.length) {
|
|
81
|
+
throw new SimpleAgentError(SimpleAgentErrorCode.INVALID_CONFIG, "validResponses contains duplicate values");
|
|
82
|
+
}
|
|
83
|
+
if (!config.cyrusHome) {
|
|
84
|
+
throw new SimpleAgentError(SimpleAgentErrorCode.INVALID_CONFIG, "cyrusHome is required");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a response is valid
|
|
89
|
+
*/
|
|
90
|
+
isValidResponse(response) {
|
|
91
|
+
return this.validResponseSet.has(response);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build the complete system prompt
|
|
95
|
+
*/
|
|
96
|
+
buildSystemPrompt() {
|
|
97
|
+
const basePrompt = this.config.systemPrompt || "";
|
|
98
|
+
const validResponsesStr = Array.from(this.validResponseSet)
|
|
99
|
+
.map((r) => `"${r}"`)
|
|
100
|
+
.join(", ");
|
|
101
|
+
const constraintPrompt = `
|
|
102
|
+
IMPORTANT: You must respond with EXACTLY one of the following values:
|
|
103
|
+
${validResponsesStr}
|
|
104
|
+
|
|
105
|
+
Your final response MUST be one of these exact strings, with no additional text, explanation, or formatting.
|
|
106
|
+
Do not use markdown, code blocks, or quotes around your response.
|
|
107
|
+
Simply output the chosen value as your final answer.
|
|
108
|
+
`;
|
|
109
|
+
return `${basePrompt}\n\n${constraintPrompt}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Emit a progress event if callback is configured
|
|
113
|
+
*/
|
|
114
|
+
emitProgress(event) {
|
|
115
|
+
if (this.config.onProgress) {
|
|
116
|
+
this.config.onProgress(event);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=SimpleAgentRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SimpleAgentRunner.js","sourceRoot":"","sources":["../src/SimpleAgentRunner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { SimpleAgentRunner } from "./SimpleAgentRunner.js";
|
|
3
|
+
import type { SimpleAgentQueryOptions } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Concrete implementation using ClaudeRunner from cyrus-claude-runner package.
|
|
6
|
+
*
|
|
7
|
+
* This implementation uses the Claude Agent SDK to execute queries and
|
|
8
|
+
* constrains the responses to an enumerated set.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SimpleClaudeRunner<T extends string> extends SimpleAgentRunner<T> {
|
|
11
|
+
/**
|
|
12
|
+
* Execute the agent using ClaudeRunner
|
|
13
|
+
*/
|
|
14
|
+
protected executeAgent(prompt: string, options?: SimpleAgentQueryOptions): Promise<SDKMessage[]>;
|
|
15
|
+
/**
|
|
16
|
+
* Extract the final response from the last assistant message
|
|
17
|
+
*/
|
|
18
|
+
protected extractResponse(messages: SDKMessage[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* Clean the response text to extract the actual value
|
|
21
|
+
*/
|
|
22
|
+
private cleanResponse;
|
|
23
|
+
/**
|
|
24
|
+
* Handle incoming messages for progress events
|
|
25
|
+
*/
|
|
26
|
+
private handleMessage;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=SimpleClaudeRunner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SimpleClaudeRunner.d.ts","sourceRoot":"","sources":["../src/SimpleClaudeRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAGjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;GAKG;AACH,qBAAa,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAE,SAAQ,iBAAiB,CAAC,CAAC,CAAC;IAC7E;;OAEG;cACa,YAAY,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,uBAAuB,GAC/B,OAAO,CAAC,UAAU,EAAE,CAAC;IA6DxB;;OAEG;IACH,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM;IAsCzD;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,aAAa;CA0BrB"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { ClaudeRunner } from "cyrus-claude-runner";
|
|
2
|
+
import { NoResponseError, SessionError } from "./errors.js";
|
|
3
|
+
import { SimpleAgentRunner } from "./SimpleAgentRunner.js";
|
|
4
|
+
/**
|
|
5
|
+
* Concrete implementation using ClaudeRunner from cyrus-claude-runner package.
|
|
6
|
+
*
|
|
7
|
+
* This implementation uses the Claude Agent SDK to execute queries and
|
|
8
|
+
* constrains the responses to an enumerated set.
|
|
9
|
+
*/
|
|
10
|
+
export class SimpleClaudeRunner extends SimpleAgentRunner {
|
|
11
|
+
/**
|
|
12
|
+
* Execute the agent using ClaudeRunner
|
|
13
|
+
*/
|
|
14
|
+
async executeAgent(prompt, options) {
|
|
15
|
+
const messages = [];
|
|
16
|
+
let sessionError = null;
|
|
17
|
+
// Build the full prompt with context if provided
|
|
18
|
+
const fullPrompt = options?.context
|
|
19
|
+
? `${options.context}\n\n${prompt}`
|
|
20
|
+
: prompt;
|
|
21
|
+
// Create ClaudeRunner with configuration
|
|
22
|
+
const runner = new ClaudeRunner({
|
|
23
|
+
workingDirectory: this.config.workingDirectory,
|
|
24
|
+
cyrusHome: this.config.cyrusHome,
|
|
25
|
+
model: this.config.model,
|
|
26
|
+
fallbackModel: this.config.fallbackModel,
|
|
27
|
+
maxTurns: this.config.maxTurns,
|
|
28
|
+
systemPrompt: this.buildSystemPrompt(),
|
|
29
|
+
// Limit tools for simple queries
|
|
30
|
+
disallowedTools: options?.allowFileReading
|
|
31
|
+
? []
|
|
32
|
+
: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
|
|
33
|
+
allowedDirectories: options?.allowedDirectories,
|
|
34
|
+
});
|
|
35
|
+
// Set up event handlers
|
|
36
|
+
runner.on("message", (message) => {
|
|
37
|
+
messages.push(message);
|
|
38
|
+
this.handleMessage(message);
|
|
39
|
+
});
|
|
40
|
+
runner.on("error", (error) => {
|
|
41
|
+
sessionError = error;
|
|
42
|
+
});
|
|
43
|
+
runner.on("complete", () => {
|
|
44
|
+
this.emitProgress({ type: "validating", response: "complete" });
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
this.emitProgress({ type: "started", sessionId: null });
|
|
48
|
+
await runner.start(fullPrompt);
|
|
49
|
+
// Update session ID in progress events
|
|
50
|
+
const sessionId = messages[0]?.session_id || null;
|
|
51
|
+
if (sessionId) {
|
|
52
|
+
this.emitProgress({ type: "started", sessionId });
|
|
53
|
+
}
|
|
54
|
+
if (sessionError) {
|
|
55
|
+
throw new SessionError(sessionError, messages);
|
|
56
|
+
}
|
|
57
|
+
return messages;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
throw new SessionError(new Error(String(error)), messages);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract the final response from the last assistant message
|
|
68
|
+
*/
|
|
69
|
+
extractResponse(messages) {
|
|
70
|
+
// Find the last assistant message with text content
|
|
71
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
72
|
+
const message = messages[i];
|
|
73
|
+
if (!message)
|
|
74
|
+
continue;
|
|
75
|
+
if (message.type === "assistant" &&
|
|
76
|
+
"message" in message &&
|
|
77
|
+
message.message &&
|
|
78
|
+
message.message.content) {
|
|
79
|
+
// Extract text from content blocks
|
|
80
|
+
for (const block of message.message.content) {
|
|
81
|
+
if (typeof block === "object" &&
|
|
82
|
+
block !== null &&
|
|
83
|
+
"type" in block &&
|
|
84
|
+
block.type === "text" &&
|
|
85
|
+
"text" in block) {
|
|
86
|
+
// Clean the response (remove whitespace, markdown, etc.)
|
|
87
|
+
const cleaned = this.cleanResponse(block.text);
|
|
88
|
+
if (cleaned) {
|
|
89
|
+
this.emitProgress({
|
|
90
|
+
type: "response-detected",
|
|
91
|
+
candidateResponse: cleaned,
|
|
92
|
+
});
|
|
93
|
+
return cleaned;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw new NoResponseError(messages);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Clean the response text to extract the actual value
|
|
103
|
+
*/
|
|
104
|
+
cleanResponse(text) {
|
|
105
|
+
// Remove markdown code blocks
|
|
106
|
+
let cleaned = text.replace(/```[\s\S]*?```/g, "");
|
|
107
|
+
// Remove inline code
|
|
108
|
+
cleaned = cleaned.replace(/`([^`]+)`/g, "$1");
|
|
109
|
+
// Remove quotes
|
|
110
|
+
cleaned = cleaned.replace(/^["']|["']$/g, "");
|
|
111
|
+
// Trim whitespace
|
|
112
|
+
cleaned = cleaned.trim();
|
|
113
|
+
// If the response is multi-line, try to find a valid response on any line
|
|
114
|
+
const lines = cleaned.split("\n").map((l) => l.trim());
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
if (this.isValidResponse(line)) {
|
|
117
|
+
return line;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Return the cleaned text (will be validated by caller)
|
|
121
|
+
return cleaned;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Handle incoming messages for progress events
|
|
125
|
+
*/
|
|
126
|
+
handleMessage(message) {
|
|
127
|
+
if (message.type === "assistant" &&
|
|
128
|
+
"message" in message &&
|
|
129
|
+
message.message &&
|
|
130
|
+
message.message.content) {
|
|
131
|
+
for (const block of message.message.content) {
|
|
132
|
+
if (typeof block === "object" && block !== null && "type" in block) {
|
|
133
|
+
if (block.type === "text" && "text" in block) {
|
|
134
|
+
this.emitProgress({ type: "thinking", text: block.text });
|
|
135
|
+
}
|
|
136
|
+
else if (block.type === "tool_use" &&
|
|
137
|
+
"name" in block &&
|
|
138
|
+
"input" in block) {
|
|
139
|
+
this.emitProgress({
|
|
140
|
+
type: "tool-use",
|
|
141
|
+
toolName: block.name,
|
|
142
|
+
input: block.input,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=SimpleClaudeRunner.js.map
|