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/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