ralph-cli-sandboxed 0.4.1 → 0.4.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 +30 -0
- package/dist/commands/action.js +9 -9
- package/dist/commands/chat.js +13 -12
- package/dist/commands/config.js +2 -1
- package/dist/commands/daemon.js +4 -3
- package/dist/commands/docker.js +102 -66
- package/dist/commands/fix-config.js +2 -1
- package/dist/commands/fix-prd.js +2 -2
- package/dist/commands/init.js +78 -17
- package/dist/commands/listen.js +3 -1
- package/dist/commands/notify.js +1 -1
- package/dist/commands/once.js +17 -9
- package/dist/commands/prd.js +4 -1
- package/dist/commands/run.js +40 -25
- package/dist/commands/slack.js +2 -2
- package/dist/config/responder-presets.json +69 -0
- package/dist/index.js +1 -1
- package/dist/providers/discord.d.ts +28 -0
- package/dist/providers/discord.js +227 -14
- package/dist/providers/slack.d.ts +41 -1
- package/dist/providers/slack.js +389 -8
- package/dist/providers/telegram.d.ts +30 -0
- package/dist/providers/telegram.js +185 -5
- package/dist/responders/claude-code-responder.d.ts +48 -0
- package/dist/responders/claude-code-responder.js +203 -0
- package/dist/responders/cli-responder.d.ts +62 -0
- package/dist/responders/cli-responder.js +298 -0
- package/dist/responders/llm-responder.d.ts +135 -0
- package/dist/responders/llm-responder.js +582 -0
- package/dist/templates/macos-scripts.js +2 -4
- package/dist/templates/prompts.js +4 -2
- package/dist/tui/ConfigEditor.js +19 -5
- package/dist/tui/components/ArrayEditor.js +1 -1
- package/dist/tui/components/EditorPanel.js +10 -6
- package/dist/tui/components/HelpPanel.d.ts +1 -1
- package/dist/tui/components/HelpPanel.js +1 -1
- package/dist/tui/components/JsonSnippetEditor.js +8 -5
- package/dist/tui/components/KeyValueEditor.js +54 -9
- package/dist/tui/components/LLMProvidersEditor.d.ts +22 -0
- package/dist/tui/components/LLMProvidersEditor.js +357 -0
- package/dist/tui/components/ObjectEditor.js +1 -1
- package/dist/tui/components/Preview.js +1 -1
- package/dist/tui/components/RespondersEditor.d.ts +22 -0
- package/dist/tui/components/RespondersEditor.js +437 -0
- package/dist/tui/components/SectionNav.js +27 -3
- package/dist/utils/chat-client.d.ts +4 -0
- package/dist/utils/chat-client.js +12 -5
- package/dist/utils/config.d.ts +84 -0
- package/dist/utils/config.js +78 -1
- package/dist/utils/daemon-client.d.ts +21 -0
- package/dist/utils/daemon-client.js +28 -1
- package/dist/utils/llm-client.d.ts +82 -0
- package/dist/utils/llm-client.js +185 -0
- package/dist/utils/message-queue.js +6 -6
- package/dist/utils/notification.d.ts +6 -1
- package/dist/utils/notification.js +103 -2
- package/dist/utils/prd-validator.js +60 -19
- package/dist/utils/prompt.js +22 -12
- package/dist/utils/responder-logger.d.ts +47 -0
- package/dist/utils/responder-logger.js +129 -0
- package/dist/utils/responder-presets.d.ts +92 -0
- package/dist/utils/responder-presets.js +156 -0
- package/dist/utils/responder.d.ts +88 -0
- package/dist/utils/responder.js +207 -0
- package/dist/utils/stream-json.js +6 -6
- package/docs/CHAT-RESPONDERS.md +785 -0
- package/docs/DEVELOPMENT.md +25 -0
- package/docs/chat-architecture.md +251 -0
- package/package.json +11 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { RespondersConfig } from "../../utils/config.js";
|
|
3
|
+
export interface RespondersEditorProps {
|
|
4
|
+
/** The label to display for this field */
|
|
5
|
+
label: string;
|
|
6
|
+
/** The current responders config */
|
|
7
|
+
responders: RespondersConfig;
|
|
8
|
+
/** Called when the user confirms the edit */
|
|
9
|
+
onConfirm: (newResponders: RespondersConfig) => void;
|
|
10
|
+
/** Called when the user cancels the edit (Esc) */
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
/** Whether this editor has focus */
|
|
13
|
+
isFocused?: boolean;
|
|
14
|
+
/** Maximum height for the list (for scrolling) */
|
|
15
|
+
maxHeight?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* RespondersEditor component for editing chat responder configurations.
|
|
19
|
+
* Provides a user-friendly interface for adding, editing, and removing responders.
|
|
20
|
+
*/
|
|
21
|
+
export declare function RespondersEditor({ label, responders, onConfirm, onCancel, isFocused, maxHeight, }: RespondersEditorProps): React.ReactElement;
|
|
22
|
+
export default RespondersEditor;
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState, useCallback, useMemo } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
/**
|
|
6
|
+
* Responder type options for dropdown.
|
|
7
|
+
*/
|
|
8
|
+
const RESPONDER_TYPES = ["llm", "claude-code", "cli"];
|
|
9
|
+
/**
|
|
10
|
+
* Type descriptions for display.
|
|
11
|
+
*/
|
|
12
|
+
const TYPE_DESCRIPTIONS = {
|
|
13
|
+
llm: "LLM provider",
|
|
14
|
+
"claude-code": "Claude Code agent",
|
|
15
|
+
cli: "CLI command",
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Default timeouts by responder type.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_TIMEOUTS = {
|
|
21
|
+
llm: 60000,
|
|
22
|
+
"claude-code": 300000,
|
|
23
|
+
cli: 60000,
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Suggested responder names/presets.
|
|
27
|
+
*/
|
|
28
|
+
const SUGGESTED_NAMES = ["default", "qa", "reviewer", "code", "lint"];
|
|
29
|
+
/**
|
|
30
|
+
* RespondersEditor component for editing chat responder configurations.
|
|
31
|
+
* Provides a user-friendly interface for adding, editing, and removing responders.
|
|
32
|
+
*/
|
|
33
|
+
export function RespondersEditor({ label, responders, onConfirm, onCancel, isFocused = true, maxHeight = 15, }) {
|
|
34
|
+
const [editResponders, setEditResponders] = useState({ ...responders });
|
|
35
|
+
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
36
|
+
const [mode, setMode] = useState("list");
|
|
37
|
+
const [editText, setEditText] = useState("");
|
|
38
|
+
const [editingResponder, setEditingResponder] = useState(null);
|
|
39
|
+
const [typeIndex, setTypeIndex] = useState(0);
|
|
40
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
41
|
+
// Get sorted responder names
|
|
42
|
+
const responderNames = useMemo(() => Object.keys(editResponders).sort(), [editResponders]);
|
|
43
|
+
// Total options includes all responders plus "+ Add responder" option
|
|
44
|
+
const totalOptions = responderNames.length + 1;
|
|
45
|
+
// Calculate visible range for scrolling
|
|
46
|
+
const visibleCount = Math.min(maxHeight - 6, totalOptions); // Reserve lines for header, footer, hints
|
|
47
|
+
const visibleResponders = useMemo(() => {
|
|
48
|
+
const endIndex = Math.min(scrollOffset + visibleCount, responderNames.length);
|
|
49
|
+
return responderNames.slice(scrollOffset, endIndex);
|
|
50
|
+
}, [scrollOffset, visibleCount, responderNames]);
|
|
51
|
+
// Auto-scroll to keep highlighted item visible
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
if (highlightedIndex < scrollOffset) {
|
|
54
|
+
setScrollOffset(highlightedIndex);
|
|
55
|
+
}
|
|
56
|
+
else if (highlightedIndex >= scrollOffset + visibleCount) {
|
|
57
|
+
setScrollOffset(Math.max(0, highlightedIndex - visibleCount + 1));
|
|
58
|
+
}
|
|
59
|
+
}, [highlightedIndex, scrollOffset, visibleCount]);
|
|
60
|
+
// Navigation handlers
|
|
61
|
+
const handleNavigateUp = useCallback(() => {
|
|
62
|
+
setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : totalOptions - 1));
|
|
63
|
+
}, [totalOptions]);
|
|
64
|
+
const handleNavigateDown = useCallback(() => {
|
|
65
|
+
setHighlightedIndex((prev) => (prev < totalOptions - 1 ? prev + 1 : 0));
|
|
66
|
+
}, [totalOptions]);
|
|
67
|
+
// Delete the highlighted responder
|
|
68
|
+
const handleDelete = useCallback(() => {
|
|
69
|
+
if (highlightedIndex < responderNames.length) {
|
|
70
|
+
const nameToDelete = responderNames[highlightedIndex];
|
|
71
|
+
const newResponders = { ...editResponders };
|
|
72
|
+
delete newResponders[nameToDelete];
|
|
73
|
+
setEditResponders(newResponders);
|
|
74
|
+
// Adjust highlighted index if needed
|
|
75
|
+
const newNames = Object.keys(newResponders);
|
|
76
|
+
if (highlightedIndex >= newNames.length && newNames.length > 0) {
|
|
77
|
+
setHighlightedIndex(newNames.length - 1);
|
|
78
|
+
}
|
|
79
|
+
else if (newNames.length === 0) {
|
|
80
|
+
setHighlightedIndex(0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}, [highlightedIndex, responderNames, editResponders]);
|
|
84
|
+
// Start editing or adding a responder
|
|
85
|
+
const handleStartEdit = useCallback(() => {
|
|
86
|
+
if (highlightedIndex < responderNames.length) {
|
|
87
|
+
// Edit existing responder
|
|
88
|
+
const name = responderNames[highlightedIndex];
|
|
89
|
+
const config = editResponders[name];
|
|
90
|
+
setEditingResponder({ name, config: { ...config } });
|
|
91
|
+
setMode("edit-responder");
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Add new responder - start with name input
|
|
95
|
+
setEditText("");
|
|
96
|
+
setMode("add-name");
|
|
97
|
+
}
|
|
98
|
+
}, [highlightedIndex, responderNames, editResponders]);
|
|
99
|
+
// Handle name submission when adding new responder
|
|
100
|
+
const handleNameSubmit = useCallback(() => {
|
|
101
|
+
const trimmedName = editText.trim();
|
|
102
|
+
if (trimmedName) {
|
|
103
|
+
// Check if name already exists
|
|
104
|
+
if (editResponders[trimmedName]) {
|
|
105
|
+
// Edit existing instead
|
|
106
|
+
const config = editResponders[trimmedName];
|
|
107
|
+
setEditingResponder({ name: trimmedName, config: { ...config } });
|
|
108
|
+
setMode("edit-responder");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Create new responder with default values
|
|
112
|
+
const defaultTrigger = trimmedName === "default" ? undefined : `@${trimmedName}`;
|
|
113
|
+
setEditingResponder({
|
|
114
|
+
name: trimmedName,
|
|
115
|
+
config: {
|
|
116
|
+
type: "llm",
|
|
117
|
+
trigger: defaultTrigger,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
setTypeIndex(0);
|
|
121
|
+
setMode("select-type");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
setMode("list");
|
|
126
|
+
}
|
|
127
|
+
setEditText("");
|
|
128
|
+
}, [editText, editResponders]);
|
|
129
|
+
// Handle type selection
|
|
130
|
+
const handleTypeSelect = useCallback(() => {
|
|
131
|
+
if (editingResponder) {
|
|
132
|
+
const selectedType = RESPONDER_TYPES[typeIndex];
|
|
133
|
+
setEditingResponder({
|
|
134
|
+
...editingResponder,
|
|
135
|
+
config: {
|
|
136
|
+
...editingResponder.config,
|
|
137
|
+
type: selectedType,
|
|
138
|
+
timeout: DEFAULT_TIMEOUTS[selectedType],
|
|
139
|
+
maxLength: 2000,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
setMode("edit-responder");
|
|
143
|
+
}
|
|
144
|
+
}, [editingResponder, typeIndex]);
|
|
145
|
+
// Save current editing responder
|
|
146
|
+
const saveEditingResponder = useCallback(() => {
|
|
147
|
+
if (editingResponder) {
|
|
148
|
+
const newResponders = {
|
|
149
|
+
...editResponders,
|
|
150
|
+
[editingResponder.name]: { ...editingResponder.config },
|
|
151
|
+
};
|
|
152
|
+
// Clean up undefined values
|
|
153
|
+
const config = newResponders[editingResponder.name];
|
|
154
|
+
if (!config.trigger)
|
|
155
|
+
delete config.trigger;
|
|
156
|
+
if (!config.provider)
|
|
157
|
+
delete config.provider;
|
|
158
|
+
if (!config.systemPrompt)
|
|
159
|
+
delete config.systemPrompt;
|
|
160
|
+
if (!config.command)
|
|
161
|
+
delete config.command;
|
|
162
|
+
if (!config.timeout)
|
|
163
|
+
delete config.timeout;
|
|
164
|
+
if (!config.maxLength)
|
|
165
|
+
delete config.maxLength;
|
|
166
|
+
setEditResponders(newResponders);
|
|
167
|
+
setEditingResponder(null);
|
|
168
|
+
setMode("list");
|
|
169
|
+
setEditText("");
|
|
170
|
+
// Update highlighted index to the new/edited responder
|
|
171
|
+
const sortedNames = Object.keys(newResponders).sort();
|
|
172
|
+
const newIndex = sortedNames.indexOf(editingResponder.name);
|
|
173
|
+
if (newIndex >= 0) {
|
|
174
|
+
setHighlightedIndex(newIndex);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}, [editingResponder, editResponders]);
|
|
178
|
+
// Cancel editing
|
|
179
|
+
const handleCancel = useCallback(() => {
|
|
180
|
+
setMode("list");
|
|
181
|
+
setEditText("");
|
|
182
|
+
setEditingResponder(null);
|
|
183
|
+
}, []);
|
|
184
|
+
// Handle text field submission
|
|
185
|
+
const handleTextSubmit = useCallback(() => {
|
|
186
|
+
if (!editingResponder)
|
|
187
|
+
return;
|
|
188
|
+
const trimmedValue = editText.trim();
|
|
189
|
+
switch (mode) {
|
|
190
|
+
case "edit-trigger":
|
|
191
|
+
setEditingResponder({
|
|
192
|
+
...editingResponder,
|
|
193
|
+
config: {
|
|
194
|
+
...editingResponder.config,
|
|
195
|
+
trigger: trimmedValue || undefined,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
break;
|
|
199
|
+
case "edit-provider":
|
|
200
|
+
setEditingResponder({
|
|
201
|
+
...editingResponder,
|
|
202
|
+
config: {
|
|
203
|
+
...editingResponder.config,
|
|
204
|
+
provider: trimmedValue || undefined,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
break;
|
|
208
|
+
case "edit-system":
|
|
209
|
+
setEditingResponder({
|
|
210
|
+
...editingResponder,
|
|
211
|
+
config: {
|
|
212
|
+
...editingResponder.config,
|
|
213
|
+
systemPrompt: trimmedValue || undefined,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
break;
|
|
217
|
+
case "edit-command":
|
|
218
|
+
setEditingResponder({
|
|
219
|
+
...editingResponder,
|
|
220
|
+
config: {
|
|
221
|
+
...editingResponder.config,
|
|
222
|
+
command: trimmedValue || undefined,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
break;
|
|
226
|
+
case "edit-timeout":
|
|
227
|
+
setEditingResponder({
|
|
228
|
+
...editingResponder,
|
|
229
|
+
config: {
|
|
230
|
+
...editingResponder.config,
|
|
231
|
+
timeout: trimmedValue ? parseInt(trimmedValue, 10) || undefined : undefined,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
break;
|
|
235
|
+
case "edit-maxlength":
|
|
236
|
+
setEditingResponder({
|
|
237
|
+
...editingResponder,
|
|
238
|
+
config: {
|
|
239
|
+
...editingResponder.config,
|
|
240
|
+
maxLength: trimmedValue ? parseInt(trimmedValue, 10) || undefined : undefined,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
setMode("edit-responder");
|
|
246
|
+
setEditText("");
|
|
247
|
+
}, [editingResponder, editText, mode]);
|
|
248
|
+
// Handle keyboard input for list mode
|
|
249
|
+
useInput((input, key) => {
|
|
250
|
+
if (!isFocused || mode !== "list")
|
|
251
|
+
return;
|
|
252
|
+
if (input === "j" || key.downArrow) {
|
|
253
|
+
handleNavigateDown();
|
|
254
|
+
}
|
|
255
|
+
else if (input === "k" || key.upArrow) {
|
|
256
|
+
handleNavigateUp();
|
|
257
|
+
}
|
|
258
|
+
else if (key.return || input === "e") {
|
|
259
|
+
handleStartEdit();
|
|
260
|
+
}
|
|
261
|
+
else if (input === "d" || key.delete) {
|
|
262
|
+
handleDelete();
|
|
263
|
+
}
|
|
264
|
+
else if (key.escape) {
|
|
265
|
+
onCancel();
|
|
266
|
+
}
|
|
267
|
+
else if (input === "s" || input === "S") {
|
|
268
|
+
onConfirm(editResponders);
|
|
269
|
+
}
|
|
270
|
+
}, { isActive: isFocused && mode === "list" });
|
|
271
|
+
// Handle keyboard input for type selection
|
|
272
|
+
useInput((input, key) => {
|
|
273
|
+
if (!isFocused || mode !== "select-type")
|
|
274
|
+
return;
|
|
275
|
+
if (input === "j" || key.downArrow) {
|
|
276
|
+
setTypeIndex((prev) => (prev < RESPONDER_TYPES.length - 1 ? prev + 1 : 0));
|
|
277
|
+
}
|
|
278
|
+
else if (input === "k" || key.upArrow) {
|
|
279
|
+
setTypeIndex((prev) => (prev > 0 ? prev - 1 : RESPONDER_TYPES.length - 1));
|
|
280
|
+
}
|
|
281
|
+
else if (key.return) {
|
|
282
|
+
handleTypeSelect();
|
|
283
|
+
}
|
|
284
|
+
else if (key.escape) {
|
|
285
|
+
handleCancel();
|
|
286
|
+
}
|
|
287
|
+
}, { isActive: isFocused && mode === "select-type" });
|
|
288
|
+
// Handle keyboard input for text editing modes
|
|
289
|
+
useInput((_input, key) => {
|
|
290
|
+
const textModes = [
|
|
291
|
+
"add-name",
|
|
292
|
+
"edit-trigger",
|
|
293
|
+
"edit-provider",
|
|
294
|
+
"edit-system",
|
|
295
|
+
"edit-command",
|
|
296
|
+
"edit-timeout",
|
|
297
|
+
"edit-maxlength",
|
|
298
|
+
];
|
|
299
|
+
if (!isFocused || !textModes.includes(mode))
|
|
300
|
+
return;
|
|
301
|
+
if (key.escape) {
|
|
302
|
+
if (mode === "add-name") {
|
|
303
|
+
handleCancel();
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
setMode("edit-responder");
|
|
307
|
+
setEditText("");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}, {
|
|
311
|
+
isActive: isFocused &&
|
|
312
|
+
[
|
|
313
|
+
"add-name",
|
|
314
|
+
"edit-trigger",
|
|
315
|
+
"edit-provider",
|
|
316
|
+
"edit-system",
|
|
317
|
+
"edit-command",
|
|
318
|
+
"edit-timeout",
|
|
319
|
+
"edit-maxlength",
|
|
320
|
+
].includes(mode),
|
|
321
|
+
});
|
|
322
|
+
// Handle keyboard input for edit-responder mode (viewing a responder)
|
|
323
|
+
useInput((input, key) => {
|
|
324
|
+
if (!isFocused || mode !== "edit-responder" || !editingResponder)
|
|
325
|
+
return;
|
|
326
|
+
if (key.escape) {
|
|
327
|
+
handleCancel();
|
|
328
|
+
}
|
|
329
|
+
else if (input === "t" || input === "T") {
|
|
330
|
+
// Edit type
|
|
331
|
+
setTypeIndex(RESPONDER_TYPES.indexOf(editingResponder.config.type));
|
|
332
|
+
setMode("select-type");
|
|
333
|
+
}
|
|
334
|
+
else if (input === "g" || input === "G") {
|
|
335
|
+
// Edit trigger
|
|
336
|
+
setEditText(editingResponder.config.trigger || "");
|
|
337
|
+
setMode("edit-trigger");
|
|
338
|
+
}
|
|
339
|
+
else if (input === "p" || input === "P") {
|
|
340
|
+
// Edit provider (only for llm type)
|
|
341
|
+
if (editingResponder.config.type === "llm") {
|
|
342
|
+
setEditText(editingResponder.config.provider || "");
|
|
343
|
+
setMode("edit-provider");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else if (input === "y" || input === "Y") {
|
|
347
|
+
// Edit system prompt (only for llm type)
|
|
348
|
+
if (editingResponder.config.type === "llm") {
|
|
349
|
+
setEditText(editingResponder.config.systemPrompt || "");
|
|
350
|
+
setMode("edit-system");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else if (input === "c" || input === "C") {
|
|
354
|
+
// Edit command (only for cli type)
|
|
355
|
+
if (editingResponder.config.type === "cli") {
|
|
356
|
+
setEditText(editingResponder.config.command || "");
|
|
357
|
+
setMode("edit-command");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else if (input === "o" || input === "O") {
|
|
361
|
+
// Edit timeout
|
|
362
|
+
setEditText(editingResponder.config.timeout?.toString() || "");
|
|
363
|
+
setMode("edit-timeout");
|
|
364
|
+
}
|
|
365
|
+
else if (input === "l" || input === "L") {
|
|
366
|
+
// Edit max length
|
|
367
|
+
setEditText(editingResponder.config.maxLength?.toString() || "");
|
|
368
|
+
setMode("edit-maxlength");
|
|
369
|
+
}
|
|
370
|
+
else if (input === "s" || input === "S") {
|
|
371
|
+
// Save and close
|
|
372
|
+
saveEditingResponder();
|
|
373
|
+
}
|
|
374
|
+
}, { isActive: isFocused && mode === "edit-responder" });
|
|
375
|
+
// Render type selection mode
|
|
376
|
+
if (mode === "select-type") {
|
|
377
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Select Responder Type" }), editingResponder && _jsxs(Text, { dimColor: true, children: [" for \"", editingResponder.name, "\""] })] }), RESPONDER_TYPES.map((type, index) => {
|
|
378
|
+
const isHighlighted = index === typeIndex;
|
|
379
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? "cyan" : undefined, children: isHighlighted ? "▸ " : " " }), _jsx(Text, { bold: isHighlighted, color: isHighlighted ? "cyan" : undefined, inverse: isHighlighted, children: type }), _jsxs(Text, { dimColor: true, children: [" - ", TYPE_DESCRIPTIONS[type]] })] }, type));
|
|
380
|
+
}), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "j/k: navigate | Enter: select | Esc: cancel" }) })] }));
|
|
381
|
+
}
|
|
382
|
+
// Render name input mode
|
|
383
|
+
if (mode === "add-name") {
|
|
384
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Add New Responder" }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Name: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleNameSubmit, focus: isFocused, placeholder: "e.g., qa, reviewer, code" })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Suggested: ", SUGGESTED_NAMES.join(", ")] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: next | Esc: cancel" }) })] }));
|
|
385
|
+
}
|
|
386
|
+
// Render trigger input mode
|
|
387
|
+
if (mode === "edit-trigger" && editingResponder) {
|
|
388
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Edit Trigger Pattern" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Use @name for mentions, leave empty for default handler" }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Trigger: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleTextSubmit, focus: isFocused, placeholder: "e.g., @qa, @review (or empty for default)" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save | Esc: cancel" }) })] }));
|
|
389
|
+
}
|
|
390
|
+
// Render provider input mode
|
|
391
|
+
if (mode === "edit-provider" && editingResponder) {
|
|
392
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Edit LLM Provider" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Name from llmProviders config (e.g., anthropic, openai)" }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Provider: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleTextSubmit, focus: isFocused, placeholder: "e.g., anthropic" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save | Esc: cancel" }) })] }));
|
|
393
|
+
}
|
|
394
|
+
// Render system prompt input mode
|
|
395
|
+
if (mode === "edit-system" && editingResponder) {
|
|
396
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Edit System Prompt" }) }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Supports ", "{{project}}", " placeholder for project context"] }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "System: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleTextSubmit, focus: isFocused, placeholder: "You are a helpful assistant..." })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save | Esc: cancel" }) })] }));
|
|
397
|
+
}
|
|
398
|
+
// Render command input mode
|
|
399
|
+
if (mode === "edit-command" && editingResponder) {
|
|
400
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Edit CLI Command" }) }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Supports ", "{{message}}", " placeholder for the user message"] }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Command: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleTextSubmit, focus: isFocused, placeholder: "e.g., npm run lint" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save | Esc: cancel" }) })] }));
|
|
401
|
+
}
|
|
402
|
+
// Render timeout input mode
|
|
403
|
+
if (mode === "edit-timeout" && editingResponder) {
|
|
404
|
+
const defaultTimeout = DEFAULT_TIMEOUTS[editingResponder.config.type];
|
|
405
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Edit Timeout" }) }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Timeout in milliseconds (default: ", defaultTimeout, ")"] }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Timeout: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleTextSubmit, focus: isFocused, placeholder: defaultTimeout.toString() })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save | Esc: cancel" }) })] }));
|
|
406
|
+
}
|
|
407
|
+
// Render max length input mode
|
|
408
|
+
if (mode === "edit-maxlength" && editingResponder) {
|
|
409
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Edit Max Response Length" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Maximum characters to send back to chat (default: 2000)" }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Max Length: " }), _jsx(TextInput, { value: editText, onChange: setEditText, onSubmit: handleTextSubmit, focus: isFocused, placeholder: "2000" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: save | Esc: cancel" }) })] }));
|
|
410
|
+
}
|
|
411
|
+
// Render edit-responder mode (viewing/editing a single responder)
|
|
412
|
+
if (mode === "edit-responder" && editingResponder) {
|
|
413
|
+
const config = editingResponder.config;
|
|
414
|
+
const isLLM = config.type === "llm";
|
|
415
|
+
const isCLI = config.type === "cli";
|
|
416
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: ["Edit Responder: ", editingResponder.name] }) }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[T] Type: " }), _jsx(Text, { children: config.type })] }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[G] Trigger: " }), _jsx(Text, { dimColor: !config.trigger, children: config.trigger || "(default handler)" })] }), isLLM && (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[P] Provider: " }), _jsx(Text, { dimColor: !config.provider, children: config.provider || "(not set)" })] }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[Y] System: " }), _jsx(Text, { dimColor: !config.systemPrompt, children: config.systemPrompt
|
|
417
|
+
? config.systemPrompt.length > 40
|
|
418
|
+
? config.systemPrompt.substring(0, 40) + "..."
|
|
419
|
+
: config.systemPrompt
|
|
420
|
+
: "(not set)" })] })] })), isCLI && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[C] Command: " }), _jsx(Text, { dimColor: !config.command, children: config.command || "(not set)" })] })), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[O] Timeout: " }), _jsxs(Text, { dimColor: !config.timeout, children: [config.timeout || DEFAULT_TIMEOUTS[config.type], "ms"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "[L] Max Length: " }), _jsx(Text, { dimColor: !config.maxLength, children: config.maxLength || 2000 })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["[T]ype [G]trigger", isLLM ? " [P]rovider [Y]system" : "", isCLI ? " [C]ommand" : "", " [O]timeout [L]ength"] }), _jsx(Text, { dimColor: true, children: "S: save | Esc: cancel" })] })] }));
|
|
421
|
+
}
|
|
422
|
+
// Calculate scroll indicators
|
|
423
|
+
const canScrollUp = scrollOffset > 0;
|
|
424
|
+
const canScrollDown = scrollOffset + visibleCount < responderNames.length;
|
|
425
|
+
const hasOverflow = responderNames.length > visibleCount;
|
|
426
|
+
// Render list mode
|
|
427
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: label }), _jsxs(Text, { dimColor: true, children: [" (", responderNames.length, " responders)"] })] }), hasOverflow && (_jsx(Box, { children: _jsx(Text, { color: canScrollUp ? "cyan" : "gray", dimColor: !canScrollUp, children: canScrollUp ? " ▲ more" : "" }) })), responderNames.length === 0 ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, italic: true, children: "No responders configured" }) })) : (visibleResponders.map((name) => {
|
|
428
|
+
const actualIndex = responderNames.indexOf(name);
|
|
429
|
+
const isHighlighted = actualIndex === highlightedIndex;
|
|
430
|
+
const config = editResponders[name];
|
|
431
|
+
// Format display: type abbreviation and trigger
|
|
432
|
+
const typeAbbrev = config.type === "claude-code" ? "claude" : config.type;
|
|
433
|
+
const trigger = config.trigger || "(default)";
|
|
434
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? "cyan" : undefined, children: isHighlighted ? "▸ " : " " }), _jsx(Text, { bold: isHighlighted, color: isHighlighted ? "cyan" : "yellow", inverse: isHighlighted, children: name.padEnd(12) }), _jsx(Text, { color: "magenta", children: typeAbbrev.padEnd(8) }), _jsx(Text, { dimColor: true, children: trigger })] }, name));
|
|
435
|
+
})), hasOverflow && (_jsx(Box, { children: _jsx(Text, { color: canScrollDown ? "cyan" : "gray", dimColor: !canScrollDown, children: canScrollDown ? " ▼ more" : "" }) })), _jsxs(Box, { children: [_jsx(Text, { color: highlightedIndex === responderNames.length ? "green" : undefined, children: highlightedIndex === responderNames.length ? "▸ " : " " }), _jsx(Text, { bold: highlightedIndex === responderNames.length, color: highlightedIndex === responderNames.length ? "green" : "gray", inverse: highlightedIndex === responderNames.length, children: "+ Add responder" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "j/k: navigate | Enter/e: edit | d: delete" }), _jsx(Text, { dimColor: true, children: "s: save all | Esc: cancel" })] })] }));
|
|
436
|
+
}
|
|
437
|
+
export default RespondersEditor;
|
|
@@ -16,7 +16,18 @@ export const CONFIG_SECTIONS = [
|
|
|
16
16
|
id: "docker",
|
|
17
17
|
label: "Docker",
|
|
18
18
|
icon: "🐳",
|
|
19
|
-
fields: [
|
|
19
|
+
fields: [
|
|
20
|
+
"docker.ports",
|
|
21
|
+
"docker.volumes",
|
|
22
|
+
"docker.environment",
|
|
23
|
+
"docker.packages",
|
|
24
|
+
"docker.git",
|
|
25
|
+
"docker.buildCommands",
|
|
26
|
+
"docker.startCommand",
|
|
27
|
+
"docker.firewall",
|
|
28
|
+
"docker.autoStart",
|
|
29
|
+
"docker.restartCount",
|
|
30
|
+
],
|
|
20
31
|
},
|
|
21
32
|
{
|
|
22
33
|
id: "daemon",
|
|
@@ -34,13 +45,26 @@ export const CONFIG_SECTIONS = [
|
|
|
34
45
|
id: "chat",
|
|
35
46
|
label: "Chat",
|
|
36
47
|
icon: "💬",
|
|
37
|
-
fields: ["chat.enabled", "chat.provider", "chat.telegram", "chat.slack", "chat.discord"],
|
|
48
|
+
fields: ["chat.enabled", "chat.provider", "chat.telegram", "chat.slack", "chat.discord", "chat.responders"],
|
|
38
49
|
},
|
|
39
50
|
{
|
|
40
51
|
id: "notifications",
|
|
41
52
|
label: "Notifications",
|
|
42
53
|
icon: "🔔",
|
|
43
|
-
fields: [
|
|
54
|
+
fields: [
|
|
55
|
+
"notifications.provider",
|
|
56
|
+
"notifications.ntfy",
|
|
57
|
+
"notifications.pushover",
|
|
58
|
+
"notifications.gotify",
|
|
59
|
+
"notifications.command",
|
|
60
|
+
"notifyCommand",
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "llm",
|
|
65
|
+
label: "LLM Providers",
|
|
66
|
+
icon: "🧠",
|
|
67
|
+
fields: ["llmProviders"],
|
|
44
68
|
},
|
|
45
69
|
];
|
|
46
70
|
/**
|
|
@@ -73,6 +73,10 @@ export type ChatMessageHandler = (message: ChatMessage) => Promise<void>;
|
|
|
73
73
|
export interface SendMessageOptions {
|
|
74
74
|
/** Inline keyboard buttons (Telegram-specific) */
|
|
75
75
|
inlineKeyboard?: InlineButton[][];
|
|
76
|
+
/** Thread timestamp for reply context (Slack-specific) */
|
|
77
|
+
threadTs?: string;
|
|
78
|
+
/** Message ID to reply to (Telegram-specific) */
|
|
79
|
+
replyToMessageId?: number;
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
78
82
|
* Inline button for chat messages.
|
|
@@ -28,7 +28,17 @@ export function parseCommand(text, message) {
|
|
|
28
28
|
if (!trimmed)
|
|
29
29
|
return null;
|
|
30
30
|
// Valid commands
|
|
31
|
-
const validCommands = [
|
|
31
|
+
const validCommands = [
|
|
32
|
+
"run",
|
|
33
|
+
"status",
|
|
34
|
+
"add",
|
|
35
|
+
"exec",
|
|
36
|
+
"stop",
|
|
37
|
+
"help",
|
|
38
|
+
"start",
|
|
39
|
+
"action",
|
|
40
|
+
"claude",
|
|
41
|
+
];
|
|
32
42
|
// Check for slash command format: /command [args...]
|
|
33
43
|
if (trimmed.startsWith("/")) {
|
|
34
44
|
const parts = trimmed.slice(1).split(/\s+/);
|
|
@@ -64,10 +74,7 @@ export function parseCommand(text, message) {
|
|
|
64
74
|
* would otherwise be interpreted as HTML tags and cause API errors.
|
|
65
75
|
*/
|
|
66
76
|
export function escapeHtml(text) {
|
|
67
|
-
return text
|
|
68
|
-
.replace(/&/g, "&")
|
|
69
|
-
.replace(/</g, "<")
|
|
70
|
-
.replace(/>/g, ">");
|
|
77
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
71
78
|
}
|
|
72
79
|
/**
|
|
73
80
|
* Strip ANSI escape codes from a string.
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -6,6 +6,34 @@ export interface CliConfig {
|
|
|
6
6
|
modelArgs?: string[];
|
|
7
7
|
fileArgs?: string[];
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* LLM Provider Types
|
|
11
|
+
* - anthropic: Anthropic Claude API
|
|
12
|
+
* - openai: OpenAI API (GPT models)
|
|
13
|
+
* - ollama: Local Ollama server
|
|
14
|
+
*/
|
|
15
|
+
export type LLMProviderType = "anthropic" | "openai" | "ollama";
|
|
16
|
+
/**
|
|
17
|
+
* Configuration for a single LLM provider.
|
|
18
|
+
*/
|
|
19
|
+
export interface LLMProviderConfig {
|
|
20
|
+
type: LLMProviderType;
|
|
21
|
+
model: string;
|
|
22
|
+
apiKey?: string;
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Named LLM providers configuration.
|
|
27
|
+
* Providers can be referenced by name in responder configs.
|
|
28
|
+
*
|
|
29
|
+
* Example:
|
|
30
|
+
* {
|
|
31
|
+
* "claude": { "type": "anthropic", "model": "claude-sonnet-4-20250514" },
|
|
32
|
+
* "gpt4": { "type": "openai", "model": "gpt-4o" },
|
|
33
|
+
* "local": { "type": "ollama", "model": "llama3", "baseUrl": "http://localhost:11434" }
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
export type LLMProvidersConfig = Record<string, LLMProviderConfig>;
|
|
9
37
|
export interface McpServerConfig {
|
|
10
38
|
command: string;
|
|
11
39
|
args?: string[];
|
|
@@ -82,12 +110,46 @@ export interface DiscordChatSettings {
|
|
|
82
110
|
allowedGuildIds?: string[];
|
|
83
111
|
allowedChannelIds?: string[];
|
|
84
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Chat responder types.
|
|
115
|
+
* - llm: Send message to an LLM provider and return response
|
|
116
|
+
* - claude-code: Run Claude Code CLI with the message as prompt
|
|
117
|
+
* - cli: Execute a custom CLI command with the message
|
|
118
|
+
*/
|
|
119
|
+
export type ResponderType = "llm" | "claude-code" | "cli";
|
|
120
|
+
/**
|
|
121
|
+
* Configuration for a single chat responder.
|
|
122
|
+
* Responders handle incoming chat messages based on trigger patterns.
|
|
123
|
+
*/
|
|
124
|
+
export interface ResponderConfig {
|
|
125
|
+
type: ResponderType;
|
|
126
|
+
trigger?: string;
|
|
127
|
+
provider?: string;
|
|
128
|
+
systemPrompt?: string;
|
|
129
|
+
command?: string;
|
|
130
|
+
timeout?: number;
|
|
131
|
+
maxLength?: number;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Named responders configuration.
|
|
135
|
+
* The special name "default" handles messages that don't match any trigger.
|
|
136
|
+
*
|
|
137
|
+
* Example:
|
|
138
|
+
* {
|
|
139
|
+
* "default": { "type": "llm", "provider": "anthropic", "systemPrompt": "You are a helpful assistant." },
|
|
140
|
+
* "qa": { "type": "llm", "trigger": "@qa", "provider": "anthropic", "systemPrompt": "Answer questions about {{project}}." },
|
|
141
|
+
* "code": { "type": "claude-code", "trigger": "@code" },
|
|
142
|
+
* "lint": { "type": "cli", "trigger": "!lint", "command": "npm run lint" }
|
|
143
|
+
* }
|
|
144
|
+
*/
|
|
145
|
+
export type RespondersConfig = Record<string, ResponderConfig>;
|
|
85
146
|
export interface ChatConfig {
|
|
86
147
|
enabled?: boolean;
|
|
87
148
|
provider?: "telegram" | "slack" | "discord";
|
|
88
149
|
telegram?: TelegramChatSettings;
|
|
89
150
|
slack?: SlackChatSettings;
|
|
90
151
|
discord?: DiscordChatSettings;
|
|
152
|
+
responders?: RespondersConfig;
|
|
91
153
|
}
|
|
92
154
|
export interface RalphConfig {
|
|
93
155
|
language: string;
|
|
@@ -100,6 +162,7 @@ export interface RalphConfig {
|
|
|
100
162
|
javaVersion?: number;
|
|
101
163
|
cli?: CliConfig;
|
|
102
164
|
cliProvider?: string;
|
|
165
|
+
llmProviders?: LLMProvidersConfig;
|
|
103
166
|
docker?: {
|
|
104
167
|
ports?: string[];
|
|
105
168
|
volumes?: string[];
|
|
@@ -150,3 +213,24 @@ export declare function isRunningInContainer(): boolean;
|
|
|
150
213
|
* Require container execution. Exits with error if not in container.
|
|
151
214
|
*/
|
|
152
215
|
export declare function requireContainer(commandName: string): void;
|
|
216
|
+
/**
|
|
217
|
+
* Default LLM provider configurations.
|
|
218
|
+
* These are used when llmProviders is not specified in config.
|
|
219
|
+
* API keys are resolved from environment variables at runtime.
|
|
220
|
+
*/
|
|
221
|
+
export declare const DEFAULT_LLM_PROVIDERS: LLMProvidersConfig;
|
|
222
|
+
/**
|
|
223
|
+
* Get the API key for an LLM provider.
|
|
224
|
+
* First checks the provider config, then falls back to environment variables.
|
|
225
|
+
*/
|
|
226
|
+
export declare function getLLMProviderApiKey(provider: LLMProviderConfig): string | undefined;
|
|
227
|
+
/**
|
|
228
|
+
* Get the base URL for an LLM provider.
|
|
229
|
+
* Returns the configured baseUrl or the default for the provider type.
|
|
230
|
+
*/
|
|
231
|
+
export declare function getLLMProviderBaseUrl(provider: LLMProviderConfig): string;
|
|
232
|
+
/**
|
|
233
|
+
* Get LLM providers from config, merging with defaults.
|
|
234
|
+
* User-defined providers override defaults with the same name.
|
|
235
|
+
*/
|
|
236
|
+
export declare function getLLMProviders(config: RalphConfig): LLMProvidersConfig;
|