Add the harness, envs, per-bot models and chat attachments
- Harness: providers, model catalog synced hourly, automatic free picks, cooldowns, tool loop with context trimming, MCP computer tools - Bots queue runs replies and tasks through the harness; jobs wait (attempt refunded) while every model is resting - Envs: Settings > Environment, scoped general/workspace/computer, written to computers and redacted from tool output - Models: Settings > Models, per-harness provider select defaulting to OpenRouter, and a models modal on each bot's chat (the coordinator's edits the defaults) - Chat: thinking row while a reply runs, file attachments (upload, paste, drop) that replies read as images, audio and document text - Fix media uploads failing after a user's first file, and private files being served from the public media path Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { z } from "zod";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import HarnessError from "../harness-error";
|
||||
import type { HarnessTool, HarnessToolCall } from "../types";
|
||||
|
||||
type Params = {
|
||||
call: HarnessToolCall;
|
||||
tools: HarnessTool[];
|
||||
signal: AbortSignal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs one tool call and returns the text the model gets back. Unknown
|
||||
* tools, bad arguments and tool failures come back as `Error: ...` so the
|
||||
* model can correct itself; only cancellation is thrown.
|
||||
*/
|
||||
export default async function runToolCall({ call, tools, signal }: Params) {
|
||||
const tool = tools.find((t) => t.name == call.name);
|
||||
|
||||
if (!tool) {
|
||||
return `Error: There is no tool called "${call.name}". Available: ${tools.map((t) => t.name).join(", ") || "none"}.`;
|
||||
}
|
||||
|
||||
let raw: unknown;
|
||||
|
||||
try {
|
||||
raw = call.arguments.trim() ? JSON.parse(call.arguments) : {};
|
||||
} catch {
|
||||
return `Error: The arguments for ${call.name} weren't valid JSON. Send a JSON object.`;
|
||||
}
|
||||
|
||||
if (!raw || typeof raw != "object" || Array.isArray(raw)) {
|
||||
return `Error: The arguments for ${call.name} must be a JSON object.`;
|
||||
}
|
||||
|
||||
const parsed = tool.schema?.safeParse(raw);
|
||||
|
||||
if (parsed && !parsed.success) {
|
||||
return `Error: Wrong arguments for ${call.name}.\n${z.prettifyError(parsed.error)}`;
|
||||
}
|
||||
|
||||
try {
|
||||
return truncate({
|
||||
text: await tool.run({ args: parsed ? parsed.data : raw, signal }),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (signal.aborted) {
|
||||
throw new HarnessError({ message: `Cancelled.`, code: "aborted" });
|
||||
}
|
||||
|
||||
return `Error: ${error instanceof Error ? error.message : String(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function truncate({ text }: { text: string }) {
|
||||
const max = AppData.HarnessToolResultMaxChars;
|
||||
|
||||
if (text.length <= max) return text;
|
||||
|
||||
return `${text.slice(0, max)}\n[Cut: ${text.length - max} more characters.]`;
|
||||
}
|
||||
Reference in New Issue
Block a user