deepagentsdk 0.9.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 +21 -0
- package/README.md +159 -0
- package/package.json +95 -0
- package/src/agent.ts +1230 -0
- package/src/backends/composite.ts +273 -0
- package/src/backends/filesystem.ts +692 -0
- package/src/backends/index.ts +22 -0
- package/src/backends/local-sandbox.ts +175 -0
- package/src/backends/persistent.ts +593 -0
- package/src/backends/sandbox.ts +510 -0
- package/src/backends/state.ts +244 -0
- package/src/backends/utils.ts +287 -0
- package/src/checkpointer/file-saver.ts +98 -0
- package/src/checkpointer/index.ts +5 -0
- package/src/checkpointer/kv-saver.ts +82 -0
- package/src/checkpointer/memory-saver.ts +82 -0
- package/src/checkpointer/types.ts +125 -0
- package/src/cli/components/ApiKeyInput.tsx +300 -0
- package/src/cli/components/FilePreview.tsx +237 -0
- package/src/cli/components/Input.tsx +277 -0
- package/src/cli/components/Message.tsx +93 -0
- package/src/cli/components/ModelSelection.tsx +338 -0
- package/src/cli/components/SlashMenu.tsx +101 -0
- package/src/cli/components/StatusBar.tsx +89 -0
- package/src/cli/components/Subagent.tsx +91 -0
- package/src/cli/components/TodoList.tsx +133 -0
- package/src/cli/components/ToolApproval.tsx +70 -0
- package/src/cli/components/ToolCall.tsx +144 -0
- package/src/cli/components/ToolCallSummary.tsx +175 -0
- package/src/cli/components/Welcome.tsx +75 -0
- package/src/cli/components/index.ts +24 -0
- package/src/cli/hooks/index.ts +12 -0
- package/src/cli/hooks/useAgent.ts +933 -0
- package/src/cli/index.tsx +1066 -0
- package/src/cli/theme.ts +205 -0
- package/src/cli/utils/model-list.ts +365 -0
- package/src/constants/errors.ts +29 -0
- package/src/constants/limits.ts +195 -0
- package/src/index.ts +176 -0
- package/src/middleware/agent-memory.ts +330 -0
- package/src/prompts.ts +196 -0
- package/src/skills/index.ts +2 -0
- package/src/skills/load.ts +191 -0
- package/src/skills/types.ts +53 -0
- package/src/tools/execute.ts +167 -0
- package/src/tools/filesystem.ts +418 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/subagent.ts +443 -0
- package/src/tools/todos.ts +101 -0
- package/src/tools/web.ts +567 -0
- package/src/types/backend.ts +177 -0
- package/src/types/core.ts +220 -0
- package/src/types/events.ts +429 -0
- package/src/types/index.ts +94 -0
- package/src/types/structured-output.ts +43 -0
- package/src/types/subagent.ts +96 -0
- package/src/types.ts +22 -0
- package/src/utils/approval.ts +213 -0
- package/src/utils/events.ts +416 -0
- package/src/utils/eviction.ts +181 -0
- package/src/utils/index.ts +34 -0
- package/src/utils/model-parser.ts +38 -0
- package/src/utils/patch-tool-calls.ts +233 -0
- package/src/utils/project-detection.ts +32 -0
- package/src/utils/summarization.ts +254 -0
|
@@ -0,0 +1,933 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for managing agent streaming and events.
|
|
3
|
+
*/
|
|
4
|
+
import { useState, useCallback, useRef } from "react";
|
|
5
|
+
import type {
|
|
6
|
+
DeepAgentState,
|
|
7
|
+
DeepAgentEvent,
|
|
8
|
+
TodoItem,
|
|
9
|
+
ModelMessage,
|
|
10
|
+
SummarizationConfig,
|
|
11
|
+
InterruptOnConfig,
|
|
12
|
+
} from "../../types.js";
|
|
13
|
+
import type { BaseCheckpointSaver } from "../../checkpointer/types.js";
|
|
14
|
+
import { createDeepAgent } from "../../agent.js";
|
|
15
|
+
import { parseModelString } from "../../utils/model-parser.js";
|
|
16
|
+
import type { SandboxBackendProtocol } from "../../types.js";
|
|
17
|
+
import type { ToolCallData } from "../components/Message.js";
|
|
18
|
+
import { useEffect } from "react";
|
|
19
|
+
|
|
20
|
+
export type AgentStatus =
|
|
21
|
+
| "idle"
|
|
22
|
+
| "thinking"
|
|
23
|
+
| "streaming"
|
|
24
|
+
| "tool-call"
|
|
25
|
+
| "subagent"
|
|
26
|
+
| "done"
|
|
27
|
+
| "error";
|
|
28
|
+
|
|
29
|
+
export interface AgentEventLog {
|
|
30
|
+
id: string;
|
|
31
|
+
type: DeepAgentEvent["type"] | "text-segment";
|
|
32
|
+
event: DeepAgentEvent | { type: "text-segment"; text: string };
|
|
33
|
+
timestamp: Date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface UseAgentOptions {
|
|
37
|
+
model: string;
|
|
38
|
+
maxSteps: number;
|
|
39
|
+
systemPrompt?: string;
|
|
40
|
+
backend: SandboxBackendProtocol;
|
|
41
|
+
/** Enable Anthropic prompt caching */
|
|
42
|
+
enablePromptCaching?: boolean;
|
|
43
|
+
/** Token limit before evicting large tool results */
|
|
44
|
+
toolResultEvictionLimit?: number;
|
|
45
|
+
/** Summarization configuration */
|
|
46
|
+
summarization?: SummarizationConfig;
|
|
47
|
+
/**
|
|
48
|
+
* Default interruptOn config for CLI.
|
|
49
|
+
* Default: { execute: true, write_file: true, edit_file: true }
|
|
50
|
+
*/
|
|
51
|
+
interruptOn?: InterruptOnConfig;
|
|
52
|
+
/** Session ID for checkpoint persistence */
|
|
53
|
+
sessionId?: string;
|
|
54
|
+
/** Checkpoint saver for session persistence */
|
|
55
|
+
checkpointer?: BaseCheckpointSaver;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface UseAgentReturn {
|
|
59
|
+
/** Current agent status */
|
|
60
|
+
status: AgentStatus;
|
|
61
|
+
/** Current streaming text */
|
|
62
|
+
streamingText: string;
|
|
63
|
+
/** Final text from the last completed generation */
|
|
64
|
+
lastCompletedText: string;
|
|
65
|
+
/** Event log for rendering */
|
|
66
|
+
events: AgentEventLog[];
|
|
67
|
+
/** Current state (todos, files) */
|
|
68
|
+
state: DeepAgentState;
|
|
69
|
+
/** Conversation history */
|
|
70
|
+
messages: ModelMessage[];
|
|
71
|
+
/** Tool calls from the current/last generation */
|
|
72
|
+
toolCalls: ToolCallData[];
|
|
73
|
+
/** Current error if any */
|
|
74
|
+
error: Error | null;
|
|
75
|
+
/** Send a prompt to the agent, returns the final text and tool calls */
|
|
76
|
+
sendPrompt: (prompt: string) => Promise<{ text: string; toolCalls: ToolCallData[] }>;
|
|
77
|
+
/** Abort current generation */
|
|
78
|
+
abort: () => void;
|
|
79
|
+
/** Clear events, messages, and reset */
|
|
80
|
+
clear: () => void;
|
|
81
|
+
/** Clear only the streaming text (after saving to messages) */
|
|
82
|
+
clearStreamingText: () => void;
|
|
83
|
+
/** Clear only the events (after saving to messages) */
|
|
84
|
+
clearEvents: () => void;
|
|
85
|
+
/** Update model */
|
|
86
|
+
setModel: (model: string) => void;
|
|
87
|
+
/** Current model */
|
|
88
|
+
currentModel: string;
|
|
89
|
+
/** Feature flags */
|
|
90
|
+
features: {
|
|
91
|
+
promptCaching: boolean;
|
|
92
|
+
eviction: boolean;
|
|
93
|
+
summarization: boolean;
|
|
94
|
+
};
|
|
95
|
+
/** Toggle prompt caching */
|
|
96
|
+
setPromptCaching: (enabled: boolean) => void;
|
|
97
|
+
/** Toggle eviction */
|
|
98
|
+
setEviction: (enabled: boolean) => void;
|
|
99
|
+
/** Toggle summarization */
|
|
100
|
+
setSummarization: (enabled: boolean) => void;
|
|
101
|
+
/** Current approval request if any */
|
|
102
|
+
pendingApproval: {
|
|
103
|
+
approvalId: string;
|
|
104
|
+
toolName: string;
|
|
105
|
+
args: unknown;
|
|
106
|
+
} | null;
|
|
107
|
+
/** Respond to approval request */
|
|
108
|
+
respondToApproval: (approved: boolean) => void;
|
|
109
|
+
/** Whether auto-approve mode is enabled */
|
|
110
|
+
autoApproveEnabled: boolean;
|
|
111
|
+
/** Toggle auto-approve mode */
|
|
112
|
+
setAutoApprove: (enabled: boolean) => void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let eventCounter = 0;
|
|
116
|
+
|
|
117
|
+
function createEventId(): string {
|
|
118
|
+
return `event-${++eventCounter}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default interruptOn config for CLI - safe defaults
|
|
122
|
+
// Based on LangChain DeepAgents approval pattern
|
|
123
|
+
const DEFAULT_CLI_INTERRUPT_ON: InterruptOnConfig = {
|
|
124
|
+
execute: true,
|
|
125
|
+
write_file: true,
|
|
126
|
+
edit_file: true,
|
|
127
|
+
web_search: true,
|
|
128
|
+
fetch_url: true,
|
|
129
|
+
// Note: http_request does NOT require approval per LangChain pattern
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export function useAgent(options: UseAgentOptions): UseAgentReturn {
|
|
133
|
+
const [status, setStatus] = useState<AgentStatus>("idle");
|
|
134
|
+
const [streamingText, setStreamingText] = useState("");
|
|
135
|
+
const [lastCompletedText, setLastCompletedText] = useState("");
|
|
136
|
+
const [events, setEvents] = useState<AgentEventLog[]>([]);
|
|
137
|
+
const [state, setState] = useState<DeepAgentState>({
|
|
138
|
+
todos: [],
|
|
139
|
+
files: {},
|
|
140
|
+
});
|
|
141
|
+
const [messages, setMessages] = useState<ModelMessage[]>([]);
|
|
142
|
+
const [toolCalls, setToolCalls] = useState<ToolCallData[]>([]);
|
|
143
|
+
const [error, setError] = useState<Error | null>(null);
|
|
144
|
+
const [currentModel, setCurrentModel] = useState(options.model);
|
|
145
|
+
|
|
146
|
+
// Load session on mount if sessionId and checkpointer are provided
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const loadSession = async () => {
|
|
149
|
+
if (!options.sessionId || !options.checkpointer) return;
|
|
150
|
+
|
|
151
|
+
const checkpoint = await options.checkpointer.load(options.sessionId);
|
|
152
|
+
if (checkpoint) {
|
|
153
|
+
setState(checkpoint.state);
|
|
154
|
+
setMessages(checkpoint.messages);
|
|
155
|
+
messagesRef.current = checkpoint.messages;
|
|
156
|
+
// Show restore message via addEvent
|
|
157
|
+
addEvent({
|
|
158
|
+
type: "checkpoint-loaded",
|
|
159
|
+
threadId: options.sessionId,
|
|
160
|
+
step: checkpoint.step,
|
|
161
|
+
messagesCount: checkpoint.messages.length,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
loadSession().catch(console.error);
|
|
167
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
|
+
}, [options.sessionId]);
|
|
169
|
+
|
|
170
|
+
// Feature flag states (can be toggled at runtime)
|
|
171
|
+
const [promptCachingEnabled, setPromptCachingEnabled] = useState(options.enablePromptCaching ?? false);
|
|
172
|
+
const [evictionLimit, setEvictionLimit] = useState(options.toolResultEvictionLimit ?? 0);
|
|
173
|
+
const [summarizationEnabled, setSummarizationEnabled] = useState(options.summarization?.enabled ?? false);
|
|
174
|
+
const [summarizationConfig, setSummarizationConfig] = useState(options.summarization);
|
|
175
|
+
|
|
176
|
+
// Auto-approve mode state
|
|
177
|
+
const [autoApproveEnabled, setAutoApproveEnabled] = useState(false);
|
|
178
|
+
|
|
179
|
+
// Pending approval state
|
|
180
|
+
const [pendingApproval, setPendingApproval] = useState<{
|
|
181
|
+
approvalId: string;
|
|
182
|
+
toolName: string;
|
|
183
|
+
args: unknown;
|
|
184
|
+
} | null>(null);
|
|
185
|
+
const approvalResolverRef = useRef<((approved: boolean) => void) | null>(null);
|
|
186
|
+
|
|
187
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
188
|
+
// Use a ref to track accumulated text during streaming (current segment, gets flushed)
|
|
189
|
+
const accumulatedTextRef = useRef("");
|
|
190
|
+
// Use a ref to track total text for return value (never reset mid-generation)
|
|
191
|
+
const totalTextRef = useRef("");
|
|
192
|
+
// Use a ref to track messages during streaming (to pass to agent)
|
|
193
|
+
const messagesRef = useRef<ModelMessage[]>([]);
|
|
194
|
+
// Use a ref to track tool calls during streaming
|
|
195
|
+
const toolCallsRef = useRef<ToolCallData[]>([]);
|
|
196
|
+
// Map to track pending tool calls by ID
|
|
197
|
+
const pendingToolCallsRef = useRef<Map<string, ToolCallData>>(new Map());
|
|
198
|
+
|
|
199
|
+
// Track feature flags (derived from state)
|
|
200
|
+
const features = {
|
|
201
|
+
promptCaching: promptCachingEnabled,
|
|
202
|
+
eviction: evictionLimit > 0,
|
|
203
|
+
summarization: summarizationEnabled,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const agentRef = useRef(
|
|
207
|
+
createDeepAgent({
|
|
208
|
+
model: parseModelString(currentModel),
|
|
209
|
+
maxSteps: options.maxSteps,
|
|
210
|
+
systemPrompt: options.systemPrompt,
|
|
211
|
+
backend: options.backend,
|
|
212
|
+
enablePromptCaching: promptCachingEnabled,
|
|
213
|
+
toolResultEvictionLimit: evictionLimit,
|
|
214
|
+
summarization: summarizationConfig,
|
|
215
|
+
interruptOn: autoApproveEnabled ? undefined : (options.interruptOn ?? DEFAULT_CLI_INTERRUPT_ON),
|
|
216
|
+
checkpointer: options.checkpointer,
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const addEvent = useCallback((event: DeepAgentEvent | { type: "text-segment"; text: string }) => {
|
|
221
|
+
setEvents((prev) => [
|
|
222
|
+
...prev,
|
|
223
|
+
{
|
|
224
|
+
id: createEventId(),
|
|
225
|
+
type: event.type,
|
|
226
|
+
event,
|
|
227
|
+
timestamp: new Date(),
|
|
228
|
+
},
|
|
229
|
+
]);
|
|
230
|
+
}, []);
|
|
231
|
+
|
|
232
|
+
// Flush accumulated text as a text-segment event
|
|
233
|
+
const flushTextSegment = useCallback(() => {
|
|
234
|
+
if (accumulatedTextRef.current.trim()) {
|
|
235
|
+
addEvent({
|
|
236
|
+
type: "text-segment",
|
|
237
|
+
text: accumulatedTextRef.current,
|
|
238
|
+
});
|
|
239
|
+
accumulatedTextRef.current = "";
|
|
240
|
+
setStreamingText("");
|
|
241
|
+
}
|
|
242
|
+
}, [addEvent]);
|
|
243
|
+
|
|
244
|
+
// ============================================================================
|
|
245
|
+
// Event Handler Context
|
|
246
|
+
// ============================================================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Context object shared by all event handlers.
|
|
250
|
+
* Contains all refs and setters needed for event processing.
|
|
251
|
+
*/
|
|
252
|
+
interface EventHandlerContext {
|
|
253
|
+
setStatus: (status: AgentStatus) => void;
|
|
254
|
+
setState: React.Dispatch<React.SetStateAction<DeepAgentState>>;
|
|
255
|
+
setMessages: React.Dispatch<React.SetStateAction<ModelMessage[]>>;
|
|
256
|
+
setToolCalls: React.Dispatch<React.SetStateAction<ToolCallData[]>>;
|
|
257
|
+
setError: React.Dispatch<React.SetStateAction<Error | null>>;
|
|
258
|
+
addEvent: (event: DeepAgentEvent | { type: "text-segment"; text: string }) => void;
|
|
259
|
+
flushTextSegment: () => void;
|
|
260
|
+
accumulatedTextRef: React.MutableRefObject<string>;
|
|
261
|
+
totalTextRef: React.MutableRefObject<string>;
|
|
262
|
+
toolCallsRef: React.MutableRefObject<ToolCallData[]>;
|
|
263
|
+
pendingToolCallsRef: React.MutableRefObject<Map<string, ToolCallData>>;
|
|
264
|
+
messagesRef: React.MutableRefObject<ModelMessage[]>;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Helper Functions for Common Patterns
|
|
269
|
+
// ============================================================================
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Common pattern: flush text, set status to "tool-call", add event
|
|
273
|
+
*/
|
|
274
|
+
const handleToolEvent = (
|
|
275
|
+
event: DeepAgentEvent,
|
|
276
|
+
ctx: EventHandlerContext
|
|
277
|
+
) => {
|
|
278
|
+
ctx.flushTextSegment();
|
|
279
|
+
ctx.setStatus("tool-call");
|
|
280
|
+
ctx.addEvent(event);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// ============================================================================
|
|
284
|
+
// Event Handlers
|
|
285
|
+
// ============================================================================
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Handle text streaming events.
|
|
289
|
+
* Accumulates text and updates streaming display.
|
|
290
|
+
*/
|
|
291
|
+
const handleTextEvent = (
|
|
292
|
+
event: DeepAgentEvent,
|
|
293
|
+
ctx: EventHandlerContext
|
|
294
|
+
) => {
|
|
295
|
+
if (event.type !== "text") return;
|
|
296
|
+
ctx.setStatus("streaming");
|
|
297
|
+
ctx.accumulatedTextRef.current += event.text;
|
|
298
|
+
ctx.totalTextRef.current += event.text;
|
|
299
|
+
setStreamingText(ctx.accumulatedTextRef.current);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Handle step-start events.
|
|
304
|
+
* Marks beginning of a new reasoning step.
|
|
305
|
+
*/
|
|
306
|
+
const handleStepStartEvent = (
|
|
307
|
+
event: DeepAgentEvent,
|
|
308
|
+
ctx: EventHandlerContext
|
|
309
|
+
) => {
|
|
310
|
+
if (event.type !== "step-start") return;
|
|
311
|
+
// Don't flush here - steps are just markers, text comes after tool results
|
|
312
|
+
if (event.stepNumber > 1) {
|
|
313
|
+
ctx.addEvent(event);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Handle tool-call events.
|
|
319
|
+
* Tracks pending tool calls until results arrive.
|
|
320
|
+
*/
|
|
321
|
+
const handleToolCallEvent = (
|
|
322
|
+
event: DeepAgentEvent,
|
|
323
|
+
ctx: EventHandlerContext
|
|
324
|
+
) => {
|
|
325
|
+
if (event.type !== "tool-call") return;
|
|
326
|
+
ctx.flushTextSegment();
|
|
327
|
+
ctx.setStatus("tool-call");
|
|
328
|
+
const pendingToolCall: ToolCallData = {
|
|
329
|
+
toolName: event.toolName,
|
|
330
|
+
args: event.args,
|
|
331
|
+
status: "success", // Will be updated on result
|
|
332
|
+
};
|
|
333
|
+
ctx.pendingToolCallsRef.current.set(event.toolCallId, pendingToolCall);
|
|
334
|
+
ctx.addEvent(event);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Handle tool-result events.
|
|
339
|
+
* Updates pending tool call with result and moves to completed list.
|
|
340
|
+
*/
|
|
341
|
+
const handleToolResultEvent = (
|
|
342
|
+
event: DeepAgentEvent,
|
|
343
|
+
ctx: EventHandlerContext
|
|
344
|
+
) => {
|
|
345
|
+
if (event.type !== "tool-result") return;
|
|
346
|
+
const completedToolCall = ctx.pendingToolCallsRef.current.get(
|
|
347
|
+
event.toolCallId
|
|
348
|
+
);
|
|
349
|
+
if (completedToolCall) {
|
|
350
|
+
completedToolCall.result = event.result;
|
|
351
|
+
// Move from pending to completed
|
|
352
|
+
ctx.toolCallsRef.current.push(completedToolCall);
|
|
353
|
+
ctx.setToolCalls([...ctx.toolCallsRef.current]);
|
|
354
|
+
ctx.pendingToolCallsRef.current.delete(event.toolCallId);
|
|
355
|
+
}
|
|
356
|
+
ctx.addEvent(event);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Handle todos-changed events.
|
|
361
|
+
* Updates the todos list in state.
|
|
362
|
+
*/
|
|
363
|
+
const handleTodosChangedEvent = (
|
|
364
|
+
event: DeepAgentEvent,
|
|
365
|
+
ctx: EventHandlerContext
|
|
366
|
+
) => {
|
|
367
|
+
if (event.type !== "todos-changed") return;
|
|
368
|
+
ctx.flushTextSegment();
|
|
369
|
+
ctx.setStatus("tool-call");
|
|
370
|
+
ctx.setState((prev) => ({ ...prev, todos: event.todos }));
|
|
371
|
+
ctx.addEvent(event);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Handle file-write-start events.
|
|
376
|
+
*/
|
|
377
|
+
const handleFileWriteStartEvent = (
|
|
378
|
+
event: DeepAgentEvent,
|
|
379
|
+
ctx: EventHandlerContext
|
|
380
|
+
) => {
|
|
381
|
+
if (event.type !== "file-write-start") return;
|
|
382
|
+
handleToolEvent(event, ctx);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Handle file-written events.
|
|
387
|
+
*/
|
|
388
|
+
const handleFileWrittenEvent = (
|
|
389
|
+
event: DeepAgentEvent,
|
|
390
|
+
ctx: EventHandlerContext
|
|
391
|
+
) => {
|
|
392
|
+
if (event.type !== "file-written") return;
|
|
393
|
+
ctx.setStatus("tool-call");
|
|
394
|
+
ctx.addEvent(event);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Handle file-edited events.
|
|
399
|
+
*/
|
|
400
|
+
const handleFileEditedEvent = (
|
|
401
|
+
event: DeepAgentEvent,
|
|
402
|
+
ctx: EventHandlerContext
|
|
403
|
+
) => {
|
|
404
|
+
if (event.type !== "file-edited") return;
|
|
405
|
+
ctx.setStatus("tool-call");
|
|
406
|
+
ctx.addEvent(event);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Handle file-read events.
|
|
411
|
+
*/
|
|
412
|
+
const handleFileReadEvent = (
|
|
413
|
+
event: DeepAgentEvent,
|
|
414
|
+
ctx: EventHandlerContext
|
|
415
|
+
) => {
|
|
416
|
+
if (event.type !== "file-read") return;
|
|
417
|
+
handleToolEvent(event, ctx);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Handle ls events.
|
|
422
|
+
*/
|
|
423
|
+
const handleLsEvent = (event: DeepAgentEvent, ctx: EventHandlerContext) => {
|
|
424
|
+
if (event.type !== "ls") return;
|
|
425
|
+
handleToolEvent(event, ctx);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Handle glob events.
|
|
430
|
+
*/
|
|
431
|
+
const handleGlobEvent = (
|
|
432
|
+
event: DeepAgentEvent,
|
|
433
|
+
ctx: EventHandlerContext
|
|
434
|
+
) => {
|
|
435
|
+
if (event.type !== "glob") return;
|
|
436
|
+
handleToolEvent(event, ctx);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Handle grep events.
|
|
441
|
+
*/
|
|
442
|
+
const handleGrepEvent = (
|
|
443
|
+
event: DeepAgentEvent,
|
|
444
|
+
ctx: EventHandlerContext
|
|
445
|
+
) => {
|
|
446
|
+
if (event.type !== "grep") return;
|
|
447
|
+
handleToolEvent(event, ctx);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Handle web-search-start events.
|
|
452
|
+
*/
|
|
453
|
+
const handleWebSearchStartEvent = (
|
|
454
|
+
event: DeepAgentEvent,
|
|
455
|
+
ctx: EventHandlerContext
|
|
456
|
+
) => {
|
|
457
|
+
if (event.type !== "web-search-start") return;
|
|
458
|
+
handleToolEvent(event, ctx);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Handle web-search-finish events.
|
|
463
|
+
*/
|
|
464
|
+
const handleWebSearchFinishEvent = (
|
|
465
|
+
event: DeepAgentEvent,
|
|
466
|
+
ctx: EventHandlerContext
|
|
467
|
+
) => {
|
|
468
|
+
if (event.type !== "web-search-finish") return;
|
|
469
|
+
ctx.setStatus("tool-call");
|
|
470
|
+
ctx.addEvent(event);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Handle http-request-start events.
|
|
475
|
+
*/
|
|
476
|
+
const handleHttpRequestStartEvent = (
|
|
477
|
+
event: DeepAgentEvent,
|
|
478
|
+
ctx: EventHandlerContext
|
|
479
|
+
) => {
|
|
480
|
+
if (event.type !== "http-request-start") return;
|
|
481
|
+
handleToolEvent(event, ctx);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Handle http-request-finish events.
|
|
486
|
+
*/
|
|
487
|
+
const handleHttpRequestFinishEvent = (
|
|
488
|
+
event: DeepAgentEvent,
|
|
489
|
+
ctx: EventHandlerContext
|
|
490
|
+
) => {
|
|
491
|
+
if (event.type !== "http-request-finish") return;
|
|
492
|
+
ctx.setStatus("tool-call");
|
|
493
|
+
ctx.addEvent(event);
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Handle fetch-url-start events.
|
|
498
|
+
*/
|
|
499
|
+
const handleFetchUrlStartEvent = (
|
|
500
|
+
event: DeepAgentEvent,
|
|
501
|
+
ctx: EventHandlerContext
|
|
502
|
+
) => {
|
|
503
|
+
if (event.type !== "fetch-url-start") return;
|
|
504
|
+
handleToolEvent(event, ctx);
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Handle fetch-url-finish events.
|
|
509
|
+
*/
|
|
510
|
+
const handleFetchUrlFinishEvent = (
|
|
511
|
+
event: DeepAgentEvent,
|
|
512
|
+
ctx: EventHandlerContext
|
|
513
|
+
) => {
|
|
514
|
+
if (event.type !== "fetch-url-finish") return;
|
|
515
|
+
ctx.setStatus("tool-call");
|
|
516
|
+
ctx.addEvent(event);
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Handle subagent-start events.
|
|
521
|
+
*/
|
|
522
|
+
const handleSubagentStartEvent = (
|
|
523
|
+
event: DeepAgentEvent,
|
|
524
|
+
ctx: EventHandlerContext
|
|
525
|
+
) => {
|
|
526
|
+
if (event.type !== "subagent-start") return;
|
|
527
|
+
ctx.setStatus("subagent");
|
|
528
|
+
ctx.addEvent(event);
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Handle subagent-finish events.
|
|
533
|
+
*/
|
|
534
|
+
const handleSubagentFinishEvent = (
|
|
535
|
+
event: DeepAgentEvent,
|
|
536
|
+
ctx: EventHandlerContext
|
|
537
|
+
) => {
|
|
538
|
+
if (event.type !== "subagent-finish") return;
|
|
539
|
+
ctx.addEvent(event);
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Handle approval-requested events.
|
|
544
|
+
* Already handled in onApprovalRequest callback, so no-op here.
|
|
545
|
+
*/
|
|
546
|
+
const handleApprovalRequestedEvent = (
|
|
547
|
+
event: DeepAgentEvent,
|
|
548
|
+
ctx: EventHandlerContext
|
|
549
|
+
) => {
|
|
550
|
+
// Approval request is handled in onApprovalRequest callback
|
|
551
|
+
// Event is already emitted there, no need to duplicate
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Handle approval-response events.
|
|
556
|
+
* Already handled in respondToApproval callback, so no-op here.
|
|
557
|
+
*/
|
|
558
|
+
const handleApprovalResponseEvent = (
|
|
559
|
+
event: DeepAgentEvent,
|
|
560
|
+
ctx: EventHandlerContext
|
|
561
|
+
) => {
|
|
562
|
+
// Approval response is handled in respondToApproval callback
|
|
563
|
+
// Event is already emitted there, no need to duplicate
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Handle done events.
|
|
568
|
+
* Flushes remaining text and updates final state.
|
|
569
|
+
*/
|
|
570
|
+
const handleDoneEvent = (
|
|
571
|
+
event: DeepAgentEvent,
|
|
572
|
+
ctx: EventHandlerContext
|
|
573
|
+
) => {
|
|
574
|
+
if (event.type !== "done") return;
|
|
575
|
+
// Flush any remaining text as a final text-segment
|
|
576
|
+
ctx.flushTextSegment();
|
|
577
|
+
ctx.setStatus("done");
|
|
578
|
+
ctx.setState(event.state);
|
|
579
|
+
// Update messages with the new conversation history
|
|
580
|
+
if (event.messages) {
|
|
581
|
+
ctx.setMessages(event.messages);
|
|
582
|
+
ctx.messagesRef.current = event.messages;
|
|
583
|
+
}
|
|
584
|
+
ctx.addEvent(event);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Handle error events.
|
|
589
|
+
* Flushes remaining text and marks pending tool calls as failed.
|
|
590
|
+
*/
|
|
591
|
+
const handleErrorEvent = (
|
|
592
|
+
event: DeepAgentEvent,
|
|
593
|
+
ctx: EventHandlerContext
|
|
594
|
+
) => {
|
|
595
|
+
if (event.type !== "error") return;
|
|
596
|
+
// Flush any remaining text before showing error
|
|
597
|
+
ctx.flushTextSegment();
|
|
598
|
+
ctx.setStatus("error");
|
|
599
|
+
ctx.setError(event.error);
|
|
600
|
+
// Mark any pending tool calls as failed
|
|
601
|
+
for (const [id, tc] of ctx.pendingToolCallsRef.current) {
|
|
602
|
+
tc.status = "error";
|
|
603
|
+
ctx.toolCallsRef.current.push(tc);
|
|
604
|
+
}
|
|
605
|
+
ctx.pendingToolCallsRef.current.clear();
|
|
606
|
+
ctx.setToolCalls([...ctx.toolCallsRef.current]);
|
|
607
|
+
ctx.addEvent(event);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Type-safe event handler function.
|
|
612
|
+
*/
|
|
613
|
+
type EventHandler = (event: DeepAgentEvent, ctx: EventHandlerContext) => void;
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Event handler map.
|
|
617
|
+
* Maps event types to their handler functions.
|
|
618
|
+
*/
|
|
619
|
+
const EVENT_HANDLERS: Record<string, EventHandler> = {
|
|
620
|
+
"text": handleTextEvent,
|
|
621
|
+
"step-start": handleStepStartEvent,
|
|
622
|
+
"tool-call": handleToolCallEvent,
|
|
623
|
+
"tool-result": handleToolResultEvent,
|
|
624
|
+
"todos-changed": handleTodosChangedEvent,
|
|
625
|
+
"file-write-start": handleFileWriteStartEvent,
|
|
626
|
+
"file-written": handleFileWrittenEvent,
|
|
627
|
+
"file-edited": handleFileEditedEvent,
|
|
628
|
+
"file-read": handleFileReadEvent,
|
|
629
|
+
"ls": handleLsEvent,
|
|
630
|
+
"glob": handleGlobEvent,
|
|
631
|
+
"grep": handleGrepEvent,
|
|
632
|
+
"web-search-start": handleWebSearchStartEvent,
|
|
633
|
+
"web-search-finish": handleWebSearchFinishEvent,
|
|
634
|
+
"http-request-start": handleHttpRequestStartEvent,
|
|
635
|
+
"http-request-finish": handleHttpRequestFinishEvent,
|
|
636
|
+
"fetch-url-start": handleFetchUrlStartEvent,
|
|
637
|
+
"fetch-url-finish": handleFetchUrlFinishEvent,
|
|
638
|
+
"subagent-start": handleSubagentStartEvent,
|
|
639
|
+
"subagent-finish": handleSubagentFinishEvent,
|
|
640
|
+
"approval-requested": handleApprovalRequestedEvent,
|
|
641
|
+
"approval-response": handleApprovalResponseEvent,
|
|
642
|
+
"done": handleDoneEvent,
|
|
643
|
+
"error": handleErrorEvent,
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// ============================================================================
|
|
647
|
+
// Main sendPrompt Function
|
|
648
|
+
// ============================================================================
|
|
649
|
+
|
|
650
|
+
const sendPrompt = useCallback(
|
|
651
|
+
async (prompt: string): Promise<{ text: string; toolCalls: ToolCallData[] }> => {
|
|
652
|
+
// Reset for new generation - but keep events for history
|
|
653
|
+
setStatus("thinking");
|
|
654
|
+
setStreamingText("");
|
|
655
|
+
// Don't clear events - they serve as conversation history
|
|
656
|
+
setToolCalls([]);
|
|
657
|
+
setError(null);
|
|
658
|
+
accumulatedTextRef.current = "";
|
|
659
|
+
totalTextRef.current = "";
|
|
660
|
+
toolCallsRef.current = [];
|
|
661
|
+
pendingToolCallsRef.current.clear();
|
|
662
|
+
|
|
663
|
+
// Add user message to events for history
|
|
664
|
+
addEvent({ type: "user-message", content: prompt });
|
|
665
|
+
|
|
666
|
+
// Sync messages ref with current state
|
|
667
|
+
messagesRef.current = messages;
|
|
668
|
+
|
|
669
|
+
// Create new abort controller
|
|
670
|
+
abortControllerRef.current = new AbortController();
|
|
671
|
+
|
|
672
|
+
// Build the input messages array for streamWithEvents
|
|
673
|
+
// Append the new prompt to the conversation history (if any exists)
|
|
674
|
+
const inputMessages = messagesRef.current.length > 0
|
|
675
|
+
? [
|
|
676
|
+
...messagesRef.current,
|
|
677
|
+
{ role: "user", content: prompt } as ModelMessage,
|
|
678
|
+
]
|
|
679
|
+
: [{ role: "user", content: prompt } as ModelMessage]; // First message: just the prompt
|
|
680
|
+
|
|
681
|
+
try {
|
|
682
|
+
for await (const event of agentRef.current.streamWithEvents({
|
|
683
|
+
messages: inputMessages, // Always use messages parameter (built with or without history)
|
|
684
|
+
state,
|
|
685
|
+
threadId: options.sessionId,
|
|
686
|
+
abortSignal: abortControllerRef.current.signal,
|
|
687
|
+
// Approval callback - auto-approve or prompt user
|
|
688
|
+
onApprovalRequest: async (request) => {
|
|
689
|
+
// If auto-approve is enabled, immediately approve
|
|
690
|
+
if (autoApproveEnabled) {
|
|
691
|
+
addEvent({
|
|
692
|
+
type: "approval-requested",
|
|
693
|
+
...request,
|
|
694
|
+
});
|
|
695
|
+
addEvent({
|
|
696
|
+
type: "approval-response",
|
|
697
|
+
approvalId: request.approvalId,
|
|
698
|
+
approved: true
|
|
699
|
+
});
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Otherwise, show approval UI and wait for user response
|
|
704
|
+
setPendingApproval({
|
|
705
|
+
approvalId: request.approvalId,
|
|
706
|
+
toolName: request.toolName,
|
|
707
|
+
args: request.args,
|
|
708
|
+
});
|
|
709
|
+
addEvent({ type: "approval-requested", ...request });
|
|
710
|
+
|
|
711
|
+
// Return a promise that resolves when user responds
|
|
712
|
+
return new Promise<boolean>((resolve) => {
|
|
713
|
+
approvalResolverRef.current = resolve;
|
|
714
|
+
});
|
|
715
|
+
},
|
|
716
|
+
})) {
|
|
717
|
+
// Create event handler context
|
|
718
|
+
const eventHandlerContext: EventHandlerContext = {
|
|
719
|
+
setStatus,
|
|
720
|
+
setState,
|
|
721
|
+
setMessages,
|
|
722
|
+
setToolCalls,
|
|
723
|
+
setError,
|
|
724
|
+
addEvent,
|
|
725
|
+
flushTextSegment,
|
|
726
|
+
accumulatedTextRef,
|
|
727
|
+
totalTextRef,
|
|
728
|
+
toolCallsRef,
|
|
729
|
+
pendingToolCallsRef,
|
|
730
|
+
messagesRef,
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
// Get the handler for this event type
|
|
734
|
+
const handler = EVENT_HANDLERS[event.type];
|
|
735
|
+
if (handler) {
|
|
736
|
+
handler(event, eventHandlerContext);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Save the final text and tool calls before resetting
|
|
741
|
+
const finalText = totalTextRef.current;
|
|
742
|
+
const finalToolCalls = [...toolCallsRef.current];
|
|
743
|
+
setLastCompletedText(finalText);
|
|
744
|
+
setStatus("idle");
|
|
745
|
+
return { text: finalText, toolCalls: finalToolCalls };
|
|
746
|
+
} catch (err) {
|
|
747
|
+
if ((err as Error).name === "AbortError") {
|
|
748
|
+
// Flush remaining text before aborting
|
|
749
|
+
flushTextSegment();
|
|
750
|
+
setStatus("idle");
|
|
751
|
+
return { text: totalTextRef.current, toolCalls: toolCallsRef.current };
|
|
752
|
+
} else {
|
|
753
|
+
// Flush remaining text before showing error
|
|
754
|
+
flushTextSegment();
|
|
755
|
+
setStatus("error");
|
|
756
|
+
setError(err as Error);
|
|
757
|
+
return { text: "", toolCalls: [] };
|
|
758
|
+
}
|
|
759
|
+
} finally {
|
|
760
|
+
abortControllerRef.current = null;
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
[state, messages, addEvent, flushTextSegment, autoApproveEnabled]
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
const abort = useCallback(() => {
|
|
768
|
+
if (abortControllerRef.current) {
|
|
769
|
+
abortControllerRef.current.abort();
|
|
770
|
+
setStatus("idle");
|
|
771
|
+
}
|
|
772
|
+
}, []);
|
|
773
|
+
|
|
774
|
+
const clear = useCallback(() => {
|
|
775
|
+
setEvents([]);
|
|
776
|
+
setStreamingText("");
|
|
777
|
+
setLastCompletedText("");
|
|
778
|
+
setMessages([]);
|
|
779
|
+
setToolCalls([]);
|
|
780
|
+
messagesRef.current = [];
|
|
781
|
+
toolCallsRef.current = [];
|
|
782
|
+
pendingToolCallsRef.current.clear();
|
|
783
|
+
setError(null);
|
|
784
|
+
setStatus("idle");
|
|
785
|
+
}, []);
|
|
786
|
+
|
|
787
|
+
const clearStreamingText = useCallback(() => {
|
|
788
|
+
setStreamingText("");
|
|
789
|
+
setEvents([]);
|
|
790
|
+
}, []);
|
|
791
|
+
|
|
792
|
+
const clearEvents = useCallback(() => {
|
|
793
|
+
setEvents([]);
|
|
794
|
+
}, []);
|
|
795
|
+
|
|
796
|
+
// Helper to recreate the agent with current settings
|
|
797
|
+
const recreateAgent = useCallback(
|
|
798
|
+
(overrides: {
|
|
799
|
+
model?: string;
|
|
800
|
+
promptCaching?: boolean;
|
|
801
|
+
evictionLimit?: number;
|
|
802
|
+
summarization?: SummarizationConfig;
|
|
803
|
+
interruptOn?: InterruptOnConfig | null; // null means explicitly no interruptOn (auto-approve)
|
|
804
|
+
} = {}) => {
|
|
805
|
+
const newModel = overrides.model ?? currentModel;
|
|
806
|
+
const newPromptCaching = overrides.promptCaching ?? promptCachingEnabled;
|
|
807
|
+
const newEvictionLimit = overrides.evictionLimit ?? evictionLimit;
|
|
808
|
+
const newSummarization = overrides.summarization ?? summarizationConfig;
|
|
809
|
+
|
|
810
|
+
// Handle interruptOn: null = explicit no approval, undefined = use current state
|
|
811
|
+
let newInterruptOn: InterruptOnConfig | undefined;
|
|
812
|
+
if (overrides.interruptOn === null) {
|
|
813
|
+
// Explicitly disable approval (auto-approve mode)
|
|
814
|
+
newInterruptOn = undefined;
|
|
815
|
+
} else if (overrides.interruptOn !== undefined) {
|
|
816
|
+
// Use the provided config
|
|
817
|
+
newInterruptOn = overrides.interruptOn;
|
|
818
|
+
} else {
|
|
819
|
+
// Use current state to determine
|
|
820
|
+
newInterruptOn = autoApproveEnabled ? undefined : (options.interruptOn ?? DEFAULT_CLI_INTERRUPT_ON);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
agentRef.current = createDeepAgent({
|
|
824
|
+
model: parseModelString(newModel),
|
|
825
|
+
maxSteps: options.maxSteps,
|
|
826
|
+
systemPrompt: options.systemPrompt,
|
|
827
|
+
backend: options.backend,
|
|
828
|
+
enablePromptCaching: newPromptCaching,
|
|
829
|
+
toolResultEvictionLimit: newEvictionLimit,
|
|
830
|
+
summarization: newSummarization,
|
|
831
|
+
interruptOn: newInterruptOn,
|
|
832
|
+
});
|
|
833
|
+
},
|
|
834
|
+
[currentModel, promptCachingEnabled, evictionLimit, summarizationConfig, autoApproveEnabled, options.maxSteps, options.systemPrompt, options.backend, options.interruptOn]
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
const setModel = useCallback(
|
|
838
|
+
(model: string) => {
|
|
839
|
+
setCurrentModel(model);
|
|
840
|
+
recreateAgent({ model });
|
|
841
|
+
},
|
|
842
|
+
[recreateAgent]
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
const setPromptCaching = useCallback(
|
|
846
|
+
(enabled: boolean) => {
|
|
847
|
+
setPromptCachingEnabled(enabled);
|
|
848
|
+
recreateAgent({ promptCaching: enabled });
|
|
849
|
+
},
|
|
850
|
+
[recreateAgent]
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
const setEviction = useCallback(
|
|
854
|
+
(enabled: boolean) => {
|
|
855
|
+
const newLimit = enabled ? (options.toolResultEvictionLimit || 20000) : 0;
|
|
856
|
+
setEvictionLimit(newLimit);
|
|
857
|
+
recreateAgent({ evictionLimit: newLimit });
|
|
858
|
+
},
|
|
859
|
+
[recreateAgent, options.toolResultEvictionLimit]
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
const setSummarization = useCallback(
|
|
863
|
+
(enabled: boolean) => {
|
|
864
|
+
setSummarizationEnabled(enabled);
|
|
865
|
+
const newConfig = enabled
|
|
866
|
+
? { enabled: true, tokenThreshold: options.summarization?.tokenThreshold, keepMessages: options.summarization?.keepMessages }
|
|
867
|
+
: undefined;
|
|
868
|
+
setSummarizationConfig(newConfig);
|
|
869
|
+
recreateAgent({ summarization: newConfig });
|
|
870
|
+
},
|
|
871
|
+
[recreateAgent, options.summarization]
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
// Respond to approval request
|
|
875
|
+
const respondToApproval = useCallback((approved: boolean) => {
|
|
876
|
+
if (approvalResolverRef.current) {
|
|
877
|
+
approvalResolverRef.current(approved);
|
|
878
|
+
approvalResolverRef.current = null;
|
|
879
|
+
const currentApproval = pendingApproval;
|
|
880
|
+
setPendingApproval(null);
|
|
881
|
+
if (currentApproval) {
|
|
882
|
+
addEvent({
|
|
883
|
+
type: "approval-response",
|
|
884
|
+
approvalId: currentApproval.approvalId,
|
|
885
|
+
approved
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}, [addEvent]);
|
|
890
|
+
|
|
891
|
+
// Toggle auto-approve and recreate agent
|
|
892
|
+
const setAutoApprove = useCallback((enabled: boolean) => {
|
|
893
|
+
setAutoApproveEnabled(enabled);
|
|
894
|
+
|
|
895
|
+
// When enabling auto-approve, immediately approve any pending request
|
|
896
|
+
if (enabled && approvalResolverRef.current) {
|
|
897
|
+
respondToApproval(true);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Recreate agent with/without interruptOn config
|
|
901
|
+
// Use null to explicitly disable approval, or the config to enable it
|
|
902
|
+
recreateAgent({
|
|
903
|
+
interruptOn: enabled ? null : (options.interruptOn ?? DEFAULT_CLI_INTERRUPT_ON)
|
|
904
|
+
});
|
|
905
|
+
}, [recreateAgent, options.interruptOn, respondToApproval]);
|
|
906
|
+
|
|
907
|
+
return {
|
|
908
|
+
status,
|
|
909
|
+
streamingText,
|
|
910
|
+
lastCompletedText,
|
|
911
|
+
events,
|
|
912
|
+
state,
|
|
913
|
+
messages,
|
|
914
|
+
toolCalls,
|
|
915
|
+
error,
|
|
916
|
+
sendPrompt,
|
|
917
|
+
abort,
|
|
918
|
+
clear,
|
|
919
|
+
clearStreamingText,
|
|
920
|
+
clearEvents,
|
|
921
|
+
setModel,
|
|
922
|
+
currentModel,
|
|
923
|
+
features,
|
|
924
|
+
setPromptCaching,
|
|
925
|
+
setEviction,
|
|
926
|
+
setSummarization,
|
|
927
|
+
pendingApproval,
|
|
928
|
+
respondToApproval,
|
|
929
|
+
autoApproveEnabled,
|
|
930
|
+
setAutoApprove,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|