@ducci/jarvis 1.0.10 → 1.0.11
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/docs/agent.md +16 -2
- package/package.json +1 -1
- package/src/server/agent.js +50 -7
package/docs/agent.md
CHANGED
|
@@ -198,7 +198,7 @@ Seed tool included for sanity checks:
|
|
|
198
198
|
Jarvis uses the provider tool-calling API:
|
|
199
199
|
|
|
200
200
|
1. The model returns an assistant message containing a `tool_calls` array.
|
|
201
|
-
2. Jarvis
|
|
201
|
+
2. Jarvis normalizes each tool call before appending to the conversation history: if `function.arguments` is missing or empty, it is set to `"{}"`. Some models (especially smaller/free ones) omit `arguments` for no-arg tools. Storing a malformed tool call would cause the next API request to fail with a 400 validation error.
|
|
202
202
|
3. Jarvis executes those tools in order, serially.
|
|
203
203
|
4. Each tool result is appended to the conversation as a `role: "tool"` message with a matching `tool_call_id`.
|
|
204
204
|
5. Jarvis calls the model again with the updated conversation.
|
|
@@ -447,7 +447,13 @@ Tool inputs/outputs:
|
|
|
447
447
|
|
|
448
448
|
- Model call failures: try the selected model once, then one fallback model attempt. If both fail, end the run with a `500` error and a clear message.
|
|
449
449
|
- Tool failures: pass the error result back to the model and continue the loop. Best case would be that the next model response include another tool call to fix the previous tool call. All tool errors (especially `exec` failures) must be reported in the `logSummary` with enough detail for a human to understand the cause.
|
|
450
|
-
- Malformed JSON on final response:
|
|
450
|
+
- Malformed JSON on final response: attempt two recovery steps before giving up:
|
|
451
|
+
1. **Fallback model retry** — call the fallback model with the same conversation messages (the bad response is not saved to the session yet). If this produces valid JSON, use it and continue normally.
|
|
452
|
+
2. **Nudge retry** — if the fallback model also returns non-JSON, append a temporary nudge message to the conversation (not saved to the session) and call `callModelWithFallback` once more:
|
|
453
|
+
```
|
|
454
|
+
Your previous response was not valid JSON. Respond only with the required JSON object: {"response": "...", "logSummary": "..."}
|
|
455
|
+
```
|
|
456
|
+
3. **Give up** — if all three attempts fail, return `format_error` without pushing any assistant content to the session. The nudge message is never persisted regardless of outcome.
|
|
451
457
|
|
|
452
458
|
**Error Payload Structure**:
|
|
453
459
|
|
|
@@ -462,6 +468,14 @@ Tool inputs/outputs:
|
|
|
462
468
|
- Use `500 Internal Server Error` for API failures, tool runtime errors, or model communication issues.
|
|
463
469
|
- Always append a log entry on failure so the outcome is visible in the session log.
|
|
464
470
|
|
|
471
|
+
**Synthetic error note on failure**: when a run ends with `model_error` or `format_error`, a synthetic assistant message is appended to the session before saving:
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
[System: Previous run failed (model_error): <logSummary>. Error detail: <errorDetail JSON>]
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
The full `errorDetail` (provider error body, HTTP status, etc.) is included so the model has enough information to understand and potentially recover from the failure without needing to call `read_session_log`. Without this, the session would contain a dangling user message with no reply, and the model would have no way to understand or recover from the failure.
|
|
478
|
+
|
|
465
479
|
Model configuration:
|
|
466
480
|
|
|
467
481
|
- Selected model ID is stored in the same config file created during setup.
|
package/package.json
CHANGED
package/src/server/agent.js
CHANGED
|
@@ -6,6 +6,8 @@ import { loadTools, getToolDefinitions, executeTool } from './tools.js';
|
|
|
6
6
|
import { appendLog } from './logging.js';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
|
|
9
|
+
const FORMAT_NUDGE = 'Your previous response was not valid JSON. Respond only with the required JSON object: {"response": "...", "logSummary": "..."}';
|
|
10
|
+
|
|
9
11
|
const WRAP_UP_NOTE = `[System: You have reached the iteration limit. This is your final response for this run.
|
|
10
12
|
Respond with your normal JSON, but add a checkpoint field:
|
|
11
13
|
|
|
@@ -112,7 +114,13 @@ async function runAgentLoop(client, config, session, prepareMessages) {
|
|
|
112
114
|
session.messages.push({
|
|
113
115
|
role: 'assistant',
|
|
114
116
|
content: assistantMessage.content || null,
|
|
115
|
-
tool_calls: assistantMessage.tool_calls
|
|
117
|
+
tool_calls: assistantMessage.tool_calls.map(tc => ({
|
|
118
|
+
...tc,
|
|
119
|
+
function: {
|
|
120
|
+
...tc.function,
|
|
121
|
+
arguments: tc.function.arguments || '{}',
|
|
122
|
+
},
|
|
123
|
+
})),
|
|
116
124
|
});
|
|
117
125
|
|
|
118
126
|
let toolsModified = false;
|
|
@@ -158,20 +166,45 @@ async function runAgentLoop(client, config, session, prepareMessages) {
|
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
// No tool calls — final response
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
// Delay pushing to session until we have a valid response (recovery may replace it)
|
|
170
|
+
let content = assistantMessage.content || '';
|
|
171
|
+
let parsed = null;
|
|
163
172
|
|
|
164
173
|
try {
|
|
165
|
-
|
|
166
|
-
response = parsed.response || content;
|
|
167
|
-
logSummary = parsed.logSummary || '';
|
|
174
|
+
parsed = JSON.parse(content);
|
|
168
175
|
} catch {
|
|
176
|
+
// Step 1: retry with fallback model
|
|
177
|
+
try {
|
|
178
|
+
const fallbackResult = await callModel(client, config.fallbackModel, preparedMessages, toolDefs);
|
|
179
|
+
const fallbackContent = fallbackResult.choices[0]?.message?.content || '';
|
|
180
|
+
parsed = JSON.parse(fallbackContent);
|
|
181
|
+
content = fallbackContent;
|
|
182
|
+
} catch {
|
|
183
|
+
// Step 2: nudge retry via both models
|
|
184
|
+
try {
|
|
185
|
+
const nudgeMessages = [...preparedMessages, { role: 'user', content: FORMAT_NUDGE }];
|
|
186
|
+
const nudgeResult = await callModelWithFallback(client, config, nudgeMessages, toolDefs);
|
|
187
|
+
const nudgeContent = nudgeResult.choices[0]?.message?.content || '';
|
|
188
|
+
parsed = JSON.parse(nudgeContent);
|
|
189
|
+
content = nudgeContent;
|
|
190
|
+
} catch {
|
|
191
|
+
// Give up
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!parsed) {
|
|
197
|
+
// Don't push bad content — handleChat will inject a synthetic error note
|
|
169
198
|
response = content;
|
|
170
|
-
logSummary = 'Model returned non-JSON final response.';
|
|
199
|
+
logSummary = 'Model returned non-JSON final response after recovery attempts.';
|
|
171
200
|
status = 'format_error';
|
|
172
201
|
return { iteration, response, logSummary, status, runToolCalls, checkpoint: null, rawResponse: content };
|
|
173
202
|
}
|
|
174
203
|
|
|
204
|
+
session.messages.push({ role: 'assistant', content });
|
|
205
|
+
response = parsed.response || content;
|
|
206
|
+
logSummary = parsed.logSummary || '';
|
|
207
|
+
|
|
175
208
|
done = true;
|
|
176
209
|
break;
|
|
177
210
|
}
|
|
@@ -303,6 +336,16 @@ export async function handleChat(config, requestSessionId, userMessage) {
|
|
|
303
336
|
if (run.contextInfo) logEntry.contextInfo = run.contextInfo;
|
|
304
337
|
if (run.rawResponse) logEntry.rawResponse = run.rawResponse;
|
|
305
338
|
appendLog(sessionId, logEntry);
|
|
339
|
+
|
|
340
|
+
// Inject synthetic error note so the model has context on the next user turn
|
|
341
|
+
if (finalStatus === 'model_error' || finalStatus === 'format_error') {
|
|
342
|
+
const errorDetail = run.errorDetail ? ` Error detail: ${JSON.stringify(run.errorDetail)}` : '';
|
|
343
|
+
session.messages.push({
|
|
344
|
+
role: 'assistant',
|
|
345
|
+
content: `[System: Previous run failed (${finalStatus}): ${finalLogSummary}.${errorDetail}]`,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
306
349
|
break;
|
|
307
350
|
}
|
|
308
351
|
|