@qwen-code/arena-agent 0.0.10-dev.8-test.0 → 0.0.10-test.0
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/.env.example +61 -10
- package/CHANGELOG.md +148 -17
- package/README.md +309 -46
- package/dist/agent.d.ts +20 -6
- package/dist/agent.js +353 -72
- package/dist/agent.js.map +1 -1
- package/dist/anthropic.d.ts +1 -1
- package/dist/anthropic.js +46 -57
- package/dist/anthropic.js.map +1 -1
- package/dist/arena-history.d.ts +12 -0
- package/dist/arena-history.js +119 -0
- package/dist/arena-history.js.map +1 -0
- package/dist/ask-user.js +44 -2
- package/dist/ask-user.js.map +1 -1
- package/dist/attachment-limits.d.ts +4 -0
- package/dist/attachment-limits.js +10 -0
- package/dist/attachment-limits.js.map +1 -0
- package/dist/attachments.d.ts +4 -2
- package/dist/attachments.js +9 -11
- package/dist/attachments.js.map +1 -1
- package/dist/brave-search.js +11 -7
- package/dist/brave-search.js.map +1 -1
- package/dist/canonical-data.d.ts +9 -0
- package/dist/canonical-data.js +12 -3
- package/dist/canonical-data.js.map +1 -1
- package/dist/cli.js +140 -40
- package/dist/cli.js.map +1 -1
- package/dist/code-tool-schema.d.ts +3 -0
- package/dist/code-tool-schema.js +92 -0
- package/dist/code-tool-schema.js.map +1 -0
- package/dist/code-tools.d.ts +2 -1
- package/dist/code-tools.js +103 -31
- package/dist/code-tools.js.map +1 -1
- package/dist/compaction-threshold.d.ts +3 -0
- package/dist/compaction-threshold.js +6 -0
- package/dist/compaction-threshold.js.map +1 -0
- package/dist/compaction.d.ts +24 -7
- package/dist/compaction.js +242 -66
- package/dist/compaction.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.js +30 -5
- package/dist/config.js.map +1 -1
- package/dist/connector-errors.d.ts +16 -0
- package/dist/connector-errors.js +69 -0
- package/dist/connector-errors.js.map +1 -0
- package/dist/connector-files.d.ts +31 -0
- package/dist/connector-files.js +249 -0
- package/dist/connector-files.js.map +1 -0
- package/dist/connector-input.d.ts +10 -0
- package/dist/connector-input.js +79 -0
- package/dist/connector-input.js.map +1 -0
- package/dist/connector-mcp.d.ts +60 -0
- package/dist/connector-mcp.js +474 -0
- package/dist/connector-mcp.js.map +1 -0
- package/dist/connectors.d.ts +13 -0
- package/dist/connectors.js +69 -0
- package/dist/connectors.js.map +1 -0
- package/dist/fetch-page.js +2 -2
- package/dist/fetch-page.js.map +1 -1
- package/dist/history.d.ts +16 -5
- package/dist/history.js +73 -14
- package/dist/history.js.map +1 -1
- package/dist/image-choice.d.ts +23 -0
- package/dist/image-choice.js +190 -0
- package/dist/image-choice.js.map +1 -0
- package/dist/image-input-fallback.js +5 -0
- package/dist/image-input-fallback.js.map +1 -1
- package/dist/image-model.d.ts +6 -0
- package/dist/image-model.js +32 -0
- package/dist/image-model.js.map +1 -0
- package/dist/midturn-checkpoint-hook.d.ts +14 -1
- package/dist/midturn-checkpoint-hook.js.map +1 -1
- package/dist/mode.js +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/model-capabilities.js +18 -8
- package/dist/model-capabilities.js.map +1 -1
- package/dist/model-router-media.js +55 -3
- package/dist/model-router-media.js.map +1 -1
- package/dist/model-stream.d.ts +16 -0
- package/dist/model-stream.js +129 -0
- package/dist/model-stream.js.map +1 -0
- package/dist/openai-responses.d.ts +2 -1
- package/dist/openai-responses.js +85 -88
- package/dist/openai-responses.js.map +1 -1
- package/dist/process-manager.js +9 -9
- package/dist/process-manager.js.map +1 -1
- package/dist/prompts.d.ts +0 -2
- package/dist/prompts.js +10 -60
- package/dist/prompts.js.map +1 -1
- package/dist/protocol.d.ts +5 -1
- package/dist/protocol.js +23 -2
- package/dist/protocol.js.map +1 -1
- package/dist/provider.d.ts +18 -0
- package/dist/provider.js +120 -51
- package/dist/provider.js.map +1 -1
- package/dist/repo.d.ts +1 -1
- package/dist/repo.js +26 -4
- package/dist/repo.js.map +1 -1
- package/dist/routify-web-fetch.js +2 -2
- package/dist/routify-web-fetch.js.map +1 -1
- package/dist/server.js +55 -27
- package/dist/server.js.map +1 -1
- package/dist/session-memory-prompts.d.ts +3 -1
- package/dist/session-memory-prompts.js +4 -2
- package/dist/session-memory-prompts.js.map +1 -1
- package/dist/side-query.js +5 -0
- package/dist/side-query.js.map +1 -1
- package/dist/spec.d.ts +5 -4
- package/dist/spec.js +31 -23
- package/dist/spec.js.map +1 -1
- package/dist/startup-config.d.ts +39 -0
- package/dist/startup-config.js +347 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/startup-main-config.d.ts +63 -0
- package/dist/startup-main-config.js +94 -0
- package/dist/startup-main-config.js.map +1 -0
- package/dist/startup-tool-config.d.ts +8 -0
- package/dist/startup-tool-config.js +116 -0
- package/dist/startup-tool-config.js.map +1 -0
- package/dist/summary-compaction.d.ts +29 -0
- package/dist/{legacy-compaction.js → summary-compaction.js} +79 -14
- package/dist/summary-compaction.js.map +1 -0
- package/dist/tools.d.ts +5 -1
- package/dist/tools.js +195 -68
- package/dist/tools.js.map +1 -1
- package/dist/tui.js +26 -6
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/web/app.js +30 -5
- package/dist/web/app.js.map +1 -1
- package/dist/workspace.js +61 -13
- package/dist/workspace.js.map +1 -1
- package/docs/BINARY_DISTRIBUTION.md +44 -8
- package/docs/MODES_AND_RELEASES.md +79 -6
- package/docs/SDK.md +1 -0
- package/modes/code-arena/capture-provenance.json +57 -0
- package/modes/code-arena/full-stack/dependencies.lock.json +8670 -0
- package/modes/code-arena/full-stack/system-prompt.txt +1 -1
- package/modes/code-arena/full-stack/template/eslint.config.mjs +3 -4
- package/modes/code-arena/full-stack/template/postcss.config.mjs +2 -2
- package/modes/code-arena/full-stack/tools.json +186 -186
- package/modes/code-arena/html/system-prompt.txt +1 -1
- package/modes/code-arena/html/tools.json +123 -123
- package/modes/code-arena/react/dependencies.lock.json +2707 -0
- package/modes/code-arena/react/system-prompt.txt +4 -3
- package/modes/code-arena/react/tools.json +178 -184
- package/package.json +7 -2
- package/scripts/README.md +23 -4
- package/web/styles.css +22 -0
- package/dist/legacy-compaction.d.ts +0 -16
- package/dist/legacy-compaction.js.map +0 -1
package/dist/tools.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
3
3
|
import { open, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
5
6
|
import { Ajv } from "ajv";
|
|
6
7
|
import addFormats from "ajv-formats";
|
|
7
8
|
import { detect as detectEncoding } from "chardet";
|
|
@@ -10,12 +11,16 @@ import mime from "mime/lite";
|
|
|
10
11
|
import { AskUserHandlerError } from "./ask-user.js";
|
|
11
12
|
import { executeBraveImageSearch, executeBraveWebSearch, } from "./brave-search.js";
|
|
12
13
|
import { CodeToolExecutor } from "./code-tools.js";
|
|
14
|
+
import { schemaOrderedValue } from "./arena-history.js";
|
|
15
|
+
import { ConnectorTools, connectorFailureText } from "./connector-mcp.js";
|
|
13
16
|
import { supportsImageInput } from "./config.js";
|
|
14
17
|
import { extractStructuredDocument, isStructuredDocument, } from "./document-reader.js";
|
|
15
18
|
import { FetchPageReader } from "./fetch-page.js";
|
|
16
19
|
import { replaceFirstText } from "./file-edit.js";
|
|
17
20
|
import { generatedImageBytesForExtension, prepareImageForModel, } from "./image.js";
|
|
18
21
|
import { imageTextFromFallbackModel } from "./image-input-fallback.js";
|
|
22
|
+
import { offerGeneratedImages } from "./image-choice.js";
|
|
23
|
+
import { arenaImageModel, imageOptionModels } from "./image-model.js";
|
|
19
24
|
import { isCodeArenaMode } from "./mode.js";
|
|
20
25
|
import { generateImageWithModelRouter, generateSpeechWithModelRouter, } from "./model-router-media.js";
|
|
21
26
|
import { processManagerForWorkspace, } from "./process-manager.js";
|
|
@@ -424,12 +429,22 @@ function receivedType(value) {
|
|
|
424
429
|
return "array";
|
|
425
430
|
return typeof value;
|
|
426
431
|
}
|
|
432
|
+
function arenaEnumIssue(values, pathParts) {
|
|
433
|
+
return {
|
|
434
|
+
code: "invalid_value", values, path: pathParts,
|
|
435
|
+
message: `Invalid option: expected one of ${values.map((item) => JSON.stringify(item)).join("|")}`,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
427
438
|
function arenaValidationIssue(error, value) {
|
|
428
439
|
const pathParts = jsonPointerPath(error.instancePath);
|
|
429
440
|
if (error.keyword === "required") {
|
|
430
441
|
const missingProperty = String(error.params.missingProperty);
|
|
431
442
|
const parentSchema = error.parentSchema;
|
|
432
|
-
const
|
|
443
|
+
const missingSchema = parentSchema?.properties?.[missingProperty];
|
|
444
|
+
if (missingSchema?.enum) {
|
|
445
|
+
return arenaEnumIssue(missingSchema.enum, [...pathParts, missingProperty]);
|
|
446
|
+
}
|
|
447
|
+
const expected = zodTypeName(missingSchema?.type || "unknown");
|
|
433
448
|
return {
|
|
434
449
|
expected,
|
|
435
450
|
code: "invalid_type",
|
|
@@ -490,19 +505,11 @@ function arenaValidationIssue(error, value) {
|
|
|
490
505
|
};
|
|
491
506
|
}
|
|
492
507
|
if (error.keyword === "enum") {
|
|
493
|
-
|
|
494
|
-
return {
|
|
495
|
-
code: "invalid_value",
|
|
496
|
-
values,
|
|
497
|
-
path: pathParts,
|
|
498
|
-
message: `Invalid option: expected one of ${values
|
|
499
|
-
.map((item) => JSON.stringify(item))
|
|
500
|
-
.join("|")}`,
|
|
501
|
-
};
|
|
508
|
+
return arenaEnumIssue(error.params.allowedValues, pathParts);
|
|
502
509
|
}
|
|
503
510
|
if (error.keyword === "format") {
|
|
504
511
|
return {
|
|
505
|
-
origin: "string",
|
|
512
|
+
...(error.params.format === "uri" ? {} : { origin: "string" }),
|
|
506
513
|
code: "invalid_format",
|
|
507
514
|
format: error.params.format === "uri" ? "url" : error.params.format,
|
|
508
515
|
path: pathParts,
|
|
@@ -548,8 +555,8 @@ function arenaValidationIssues(error, value) {
|
|
|
548
555
|
}
|
|
549
556
|
return issues;
|
|
550
557
|
}
|
|
551
|
-
function arenaValidationError(toolName, value, errors) {
|
|
552
|
-
const issues = (errors || []).flatMap((error) => arenaValidationIssues(error, value));
|
|
558
|
+
function arenaValidationError(toolName, value, errors, customIssues = []) {
|
|
559
|
+
const issues = (errors || []).flatMap((error) => arenaValidationIssues(error, value)).concat(customIssues);
|
|
553
560
|
return (`Invalid input for tool ${toolName}: Type validation failed: Value: ` +
|
|
554
561
|
`${JSON.stringify(value)}.\nError message: ${JSON.stringify(issues, null, 2)}`);
|
|
555
562
|
}
|
|
@@ -593,11 +600,14 @@ async function responseBytes(response, limit = 4 * 1024 * 1024) {
|
|
|
593
600
|
}
|
|
594
601
|
class ArenaToolExecutor {
|
|
595
602
|
config;
|
|
603
|
+
interactionBoundaries = new Set();
|
|
604
|
+
interactionQueue = Promise.resolve();
|
|
596
605
|
workspace;
|
|
597
606
|
workspaceReal;
|
|
598
607
|
tmpReal;
|
|
599
608
|
varTmpReal;
|
|
600
609
|
validators = new Map();
|
|
610
|
+
connectors;
|
|
601
611
|
pendingVisualContent = new Map();
|
|
602
612
|
askUserHandler;
|
|
603
613
|
imageInputFallbackClient;
|
|
@@ -615,6 +625,7 @@ class ArenaToolExecutor {
|
|
|
615
625
|
// The captured runtime uses Zod object parsing: unknown object keys are
|
|
616
626
|
// stripped before dispatch instead of being emitted as validation issues.
|
|
617
627
|
removeAdditional: "all",
|
|
628
|
+
useDefaults: true,
|
|
618
629
|
});
|
|
619
630
|
// ask_user needs a handler: TUI/WebUI collect real user input, while
|
|
620
631
|
// headless `-p` may delegate to a configured remote User Agent. add_voice
|
|
@@ -634,7 +645,18 @@ class ArenaToolExecutor {
|
|
|
634
645
|
this.varTmpReal = realpathSync("/var/tmp");
|
|
635
646
|
this.processManager = processManagerForWorkspace(this.workspaceReal);
|
|
636
647
|
this.voiceRegistry = voiceRegistryForWorkspace(this.workspaceReal);
|
|
637
|
-
|
|
648
|
+
const askUserHandler = options.askUserHandler;
|
|
649
|
+
this.askUserHandler = askUserHandler ? (callId, questions, context) => {
|
|
650
|
+
// TUI/WebUI own one active question panel. Parallel tools can prepare
|
|
651
|
+
// auditions concurrently, but must not overwrite another pending answer.
|
|
652
|
+
const response = this.interactionQueue.then(() => {
|
|
653
|
+
(this.signal || context?.signal)?.throwIfAborted();
|
|
654
|
+
this.interactionBoundaries.add(callId);
|
|
655
|
+
return askUserHandler(callId, questions, context);
|
|
656
|
+
});
|
|
657
|
+
this.interactionQueue = response.then(() => { }, () => { });
|
|
658
|
+
return response;
|
|
659
|
+
} : undefined;
|
|
638
660
|
this.askUserEnabled = Boolean(options.askUserHandler);
|
|
639
661
|
if (config.imageInputFallbackModel?.trim()) {
|
|
640
662
|
const connection = imageInputFallbackConnection(config);
|
|
@@ -646,6 +668,9 @@ class ArenaToolExecutor {
|
|
|
646
668
|
}
|
|
647
669
|
this.signal = options.signal;
|
|
648
670
|
this.workspaceContext = options.workspaceContext;
|
|
671
|
+
if ((config.connectors?.length || config.connectedConnectors !== undefined) && !config.disallowedTools?.includes("list_connector_tools")) {
|
|
672
|
+
this.connectors = new ConnectorTools(config.connectors || [], config.mergeConnectors, config.disallowedTools, options.signal, { workspace: this.workspace, connected: config.connectedConnectors, resolvePath: raw => this.confinedPath(raw) });
|
|
673
|
+
}
|
|
649
674
|
this.pageReader = new FetchPageReader({ signal: options.signal });
|
|
650
675
|
this.routifyPageReader = new RoutifyFetchPageReader({
|
|
651
676
|
signal: options.signal,
|
|
@@ -658,28 +683,66 @@ class ArenaToolExecutor {
|
|
|
658
683
|
type: "string",
|
|
659
684
|
validate: (value) => URL.canParse(value),
|
|
660
685
|
});
|
|
661
|
-
for (const definition of toolDefinitions(this.workspaceContext)) {
|
|
662
|
-
|
|
686
|
+
for (const definition of toolDefinitions(this.workspaceContext, "arena", config.connectors, config.connectedConnectors)) {
|
|
687
|
+
const parameters = structuredClone(definition.function.parameters);
|
|
688
|
+
// The latest wire schema moves this limit into the description. Keep the
|
|
689
|
+
// documented command bound enforced internally without changing that schema.
|
|
690
|
+
if (definition.function.name === "bash" || definition.function.name === "start_process") {
|
|
691
|
+
record(record(parameters.properties).command).maxLength = 64_000;
|
|
692
|
+
}
|
|
693
|
+
this.validators.set(definition.function.name, this.ajv.compile(parameters));
|
|
663
694
|
}
|
|
664
695
|
}
|
|
665
696
|
// The tool schemas offered to the model for this session. Identical to the
|
|
666
697
|
// canonical trace set, adjusted for the selected workspace runtime and minus
|
|
667
698
|
// ask_user when no handler is wired. add_voice stays available headlessly.
|
|
668
699
|
availableTools() {
|
|
669
|
-
let definitions =
|
|
700
|
+
let definitions = this.allDefinitions();
|
|
670
701
|
if (!this.askUserEnabled) {
|
|
671
702
|
definitions = definitions.filter((definition) => definition.function.name !== "ask_user");
|
|
672
703
|
}
|
|
673
704
|
const disallowed = new Set(this.config.disallowedTools || []);
|
|
674
705
|
return definitions.filter((definition) => !disallowed.has(definition.function.name));
|
|
675
706
|
}
|
|
707
|
+
allDefinitions() {
|
|
708
|
+
return [...toolDefinitions(this.workspaceContext, "arena", this.config.connectors, this.config.connectedConnectors),
|
|
709
|
+
...(this.connectors?.definitions() || [])];
|
|
710
|
+
}
|
|
711
|
+
async beginConnectorTurn(history) {
|
|
712
|
+
await this.connectors?.beginTurn(history);
|
|
713
|
+
}
|
|
714
|
+
async closeConnectors() { await this.connectors?.close(); }
|
|
715
|
+
historyCall(call) {
|
|
716
|
+
if (call.function.arguments === "")
|
|
717
|
+
return call;
|
|
718
|
+
const parsed = this.parseArguments(call);
|
|
719
|
+
if (parsed.error !== undefined)
|
|
720
|
+
return call;
|
|
721
|
+
const name = call.function.name;
|
|
722
|
+
let value = parsed.value;
|
|
723
|
+
const validate = this.validators.get(name) || this.connectors?.validator(name);
|
|
724
|
+
const schema = this.allDefinitions().find(definition => definition.function.name === name)?.function.parameters;
|
|
725
|
+
if (validate && schema) {
|
|
726
|
+
const checked = this.validateArguments(name, value, validate);
|
|
727
|
+
if (checked.validationMessage === undefined)
|
|
728
|
+
value = this.connectors?.has(name)
|
|
729
|
+
? this.connectors.historyValue(name, checked.value) : schemaOrderedValue(checked.value, schema);
|
|
730
|
+
}
|
|
731
|
+
const argumentsText = JSON.stringify(value);
|
|
732
|
+
return argumentsText === call.function.arguments ? call : { ...call, function: { ...call.function, arguments: argumentsText } };
|
|
733
|
+
}
|
|
734
|
+
takeInteractionBoundary(callId) {
|
|
735
|
+
return this.interactionBoundaries.delete(callId);
|
|
736
|
+
}
|
|
676
737
|
parseArguments(call) {
|
|
677
738
|
const rawArguments = typeof call.function?.arguments === "string"
|
|
678
739
|
? call.function.arguments
|
|
679
740
|
: "{}";
|
|
680
741
|
let value;
|
|
681
742
|
try {
|
|
682
|
-
|
|
743
|
+
// Captured empty arguments validate as an empty object, while whitespace
|
|
744
|
+
// and other incomplete JSON still produce parsing errors.
|
|
745
|
+
value = JSON.parse(rawArguments === "" ? "{}" : rawArguments);
|
|
683
746
|
}
|
|
684
747
|
catch (error) {
|
|
685
748
|
return { rawArguments, invalidJsonText: rawArguments, error };
|
|
@@ -688,6 +751,8 @@ class ArenaToolExecutor {
|
|
|
688
751
|
? value.__malformedToolArguments
|
|
689
752
|
: undefined;
|
|
690
753
|
if (typeof malformedArguments === "string") {
|
|
754
|
+
if (malformedArguments === "")
|
|
755
|
+
return { rawArguments, value: {} };
|
|
691
756
|
try {
|
|
692
757
|
JSON.parse(malformedArguments);
|
|
693
758
|
}
|
|
@@ -698,12 +763,29 @@ class ArenaToolExecutor {
|
|
|
698
763
|
return { rawArguments, value };
|
|
699
764
|
}
|
|
700
765
|
validateArguments(name, value, validate) {
|
|
701
|
-
|
|
766
|
+
let validatedArguments;
|
|
767
|
+
try {
|
|
768
|
+
validatedArguments = (this.connectors?.has(name) ? this.connectors.normalizeArguments(name, value) : structuredClone(value));
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
return { validationMessage: connectorFailureText };
|
|
772
|
+
}
|
|
702
773
|
if (!validate(validatedArguments)) {
|
|
703
774
|
return {
|
|
704
|
-
validationMessage: arenaValidationError(name, value, validate.errors),
|
|
775
|
+
validationMessage: this.connectors?.has(name) ? connectorFailureText : arenaValidationError(name, value, validate.errors),
|
|
705
776
|
};
|
|
706
777
|
}
|
|
778
|
+
if (name === "ask_user") {
|
|
779
|
+
const issues = validatedArguments.questions.flatMap((question, index) => {
|
|
780
|
+
const ids = question.options.map((option) => option.id);
|
|
781
|
+
return new Set(ids).size === ids.length ? [] : [{
|
|
782
|
+
code: "custom", path: ["questions", index, "options"],
|
|
783
|
+
message: "Option IDs must be unique within a question",
|
|
784
|
+
}];
|
|
785
|
+
});
|
|
786
|
+
if (issues.length)
|
|
787
|
+
return { validationMessage: arenaValidationError(name, value, [], issues) };
|
|
788
|
+
}
|
|
707
789
|
return { value: validatedArguments };
|
|
708
790
|
}
|
|
709
791
|
inspect(call) {
|
|
@@ -713,7 +795,7 @@ class ArenaToolExecutor {
|
|
|
713
795
|
? call.function.arguments
|
|
714
796
|
: "{}";
|
|
715
797
|
const parsed = this.parseArguments(call);
|
|
716
|
-
const validate = this.validators.get(name);
|
|
798
|
+
const validate = this.validators.get(name) || this.connectors?.validator(name);
|
|
717
799
|
if (this.config.disallowedTools?.includes(name) || !validate) {
|
|
718
800
|
return {
|
|
719
801
|
id,
|
|
@@ -723,7 +805,7 @@ class ArenaToolExecutor {
|
|
|
723
805
|
validation: "unknown_tool",
|
|
724
806
|
validationMessage: this.config.disallowedTools?.includes(name)
|
|
725
807
|
? `tool is disallowed: ${name}`
|
|
726
|
-
: unavailableToolError(name,
|
|
808
|
+
: this.connectors?.recognizes(name) ? connectorFailureText : unavailableToolError(name, this.allDefinitions().map((definition) => definition.function.name)),
|
|
727
809
|
};
|
|
728
810
|
}
|
|
729
811
|
if (parsed.error !== undefined) {
|
|
@@ -732,7 +814,7 @@ class ArenaToolExecutor {
|
|
|
732
814
|
name,
|
|
733
815
|
rawArguments: parsed.rawArguments,
|
|
734
816
|
validation: "invalid_json",
|
|
735
|
-
validationMessage: arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
817
|
+
validationMessage: this.connectors?.has(name) ? connectorFailureText : arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
736
818
|
};
|
|
737
819
|
}
|
|
738
820
|
const validated = this.validateArguments(name, parsed.value, validate);
|
|
@@ -771,12 +853,12 @@ class ArenaToolExecutor {
|
|
|
771
853
|
if (this.config.disallowedTools?.includes(name)) {
|
|
772
854
|
throw new Error(`tool is disallowed: ${name}`);
|
|
773
855
|
}
|
|
774
|
-
const validate = this.validators.get(name);
|
|
856
|
+
const validate = this.validators.get(name) || this.connectors?.validator(name);
|
|
775
857
|
if (!validate) {
|
|
776
858
|
return {
|
|
777
859
|
role: "tool",
|
|
778
860
|
tool_call_id: callId,
|
|
779
|
-
content: unavailableToolError(name,
|
|
861
|
+
content: this.connectors?.recognizes(name) ? connectorFailureText : unavailableToolError(name, this.allDefinitions().map((definition) => definition.function.name)),
|
|
780
862
|
};
|
|
781
863
|
}
|
|
782
864
|
const parsed = this.parseArguments(call);
|
|
@@ -784,7 +866,7 @@ class ArenaToolExecutor {
|
|
|
784
866
|
return {
|
|
785
867
|
role: "tool",
|
|
786
868
|
tool_call_id: callId,
|
|
787
|
-
content: arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
869
|
+
content: this.connectors?.has(name) ? connectorFailureText : arenaJsonParsingError(name, parsed.invalidJsonText, parsed.error),
|
|
788
870
|
};
|
|
789
871
|
}
|
|
790
872
|
const argumentsValue = parsed.value;
|
|
@@ -843,7 +925,7 @@ class ArenaToolExecutor {
|
|
|
843
925
|
return {
|
|
844
926
|
role: "tool",
|
|
845
927
|
tool_call_id: callId,
|
|
846
|
-
content: json(this.runtimeFailureResult(name, validatedArguments, error, startedAt)),
|
|
928
|
+
content: json(this.runtimeFailureResult(name, validatedArguments, error, startedAt, name === "generate_image" ? context?.signal : undefined)),
|
|
847
929
|
};
|
|
848
930
|
}
|
|
849
931
|
const content = typeof result === "string" ? result : json(result);
|
|
@@ -876,20 +958,20 @@ class ArenaToolExecutor {
|
|
|
876
958
|
};
|
|
877
959
|
}
|
|
878
960
|
}
|
|
879
|
-
runtimeFailureResult(name, values, error, startedAt) {
|
|
880
|
-
if (this.signal?.aborted)
|
|
961
|
+
runtimeFailureResult(name, values, error, startedAt, contextSignal) {
|
|
962
|
+
if ((this.signal || contextSignal)?.aborted)
|
|
881
963
|
return { status: "aborted" };
|
|
882
964
|
const message = errorMessage(error);
|
|
883
965
|
const durationMs = Date.now() - startedAt;
|
|
884
966
|
if (name === "bash") {
|
|
885
967
|
return {
|
|
886
|
-
status: "shell_error",
|
|
887
|
-
stderr: message,
|
|
888
968
|
stdout: "",
|
|
969
|
+
stdout_truncated: false,
|
|
970
|
+
stderr: message,
|
|
971
|
+
stderr_truncated: false,
|
|
889
972
|
exit_code: null,
|
|
973
|
+
status: "shell_error",
|
|
890
974
|
duration_ms: durationMs,
|
|
891
|
-
stderr_truncated: false,
|
|
892
|
-
stdout_truncated: false,
|
|
893
975
|
};
|
|
894
976
|
}
|
|
895
977
|
if (name === "start_process") {
|
|
@@ -912,8 +994,8 @@ class ArenaToolExecutor {
|
|
|
912
994
|
if (name === "get_process_output") {
|
|
913
995
|
return {
|
|
914
996
|
status: "shell_error",
|
|
915
|
-
log_tail: message,
|
|
916
997
|
exit_code: null,
|
|
998
|
+
log_tail: message,
|
|
917
999
|
listening_ports: [],
|
|
918
1000
|
};
|
|
919
1001
|
}
|
|
@@ -970,6 +1052,11 @@ class ArenaToolExecutor {
|
|
|
970
1052
|
};
|
|
971
1053
|
}
|
|
972
1054
|
async dispatch(name, values, toolCallId, context) {
|
|
1055
|
+
if (name === "list_connector_tools" && this.connectors) {
|
|
1056
|
+
return this.connectors.discover(String(values.service));
|
|
1057
|
+
}
|
|
1058
|
+
if (this.connectors?.has(name))
|
|
1059
|
+
return this.connectors.call(name, values);
|
|
973
1060
|
// Canned replay responses are served in execute() before schema
|
|
974
1061
|
// validation (the lookup consumes entries — single serving point).
|
|
975
1062
|
switch (name) {
|
|
@@ -985,7 +1072,7 @@ class ArenaToolExecutor {
|
|
|
985
1072
|
case "edit_file":
|
|
986
1073
|
return this.editFile(String(values.path), String(values.old_text), String(values.new_text));
|
|
987
1074
|
case "read_file":
|
|
988
|
-
return this.readWorkspaceFile(String(values.path), toolCallId);
|
|
1075
|
+
return this.readWorkspaceFile(String(values.path), toolCallId, values.offset, values.limit);
|
|
989
1076
|
case "fetch_page":
|
|
990
1077
|
return routifyWebFetchEnabled()
|
|
991
1078
|
? this.routifyPageReader.read(String(values.url), Number(values.chunkIndex || 0))
|
|
@@ -1006,7 +1093,7 @@ class ArenaToolExecutor {
|
|
|
1006
1093
|
? path.resolve(this.workspaceContext.cwd)
|
|
1007
1094
|
: "/home/user")), Number(values.startup_wait || 5));
|
|
1008
1095
|
case "generate_image":
|
|
1009
|
-
return this.generateImage(String(values.file_path), String(values.prompt), values.images);
|
|
1096
|
+
return this.generateImage(String(values.file_path), String(values.prompt), values.images, values.offer_options === true, toolCallId, context);
|
|
1010
1097
|
case "generate_speech":
|
|
1011
1098
|
return this.generateSpeech(String(values.file_path), String(values.text), String(values.voice_id), values.language === undefined ? undefined : String(values.language));
|
|
1012
1099
|
case "get_process_output":
|
|
@@ -1163,9 +1250,8 @@ class ArenaToolExecutor {
|
|
|
1163
1250
|
}
|
|
1164
1251
|
return new Promise((resolve, reject) => {
|
|
1165
1252
|
const env = this.sandboxEnvironment();
|
|
1166
|
-
//
|
|
1167
|
-
//
|
|
1168
|
-
// 必须杀整组,并在宽限期后强制结算兜底。
|
|
1253
|
+
// A process group lets cancellation stop descendants too. A wait timeout
|
|
1254
|
+
// is different: the captured contract permits the command to continue.
|
|
1169
1255
|
const child = spawn("/bin/bash", ["-lc", command], {
|
|
1170
1256
|
cwd: workdir,
|
|
1171
1257
|
env,
|
|
@@ -1202,10 +1288,8 @@ class ArenaToolExecutor {
|
|
|
1202
1288
|
clearTimeout(timeout);
|
|
1203
1289
|
if (forceSettle)
|
|
1204
1290
|
clearTimeout(forceSettle);
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
// servers belong in start_process.
|
|
1208
|
-
killTree("SIGKILL");
|
|
1291
|
+
if (!timedOut || this.signal?.aborted)
|
|
1292
|
+
killTree("SIGKILL");
|
|
1209
1293
|
this.signal?.removeEventListener("abort", abort);
|
|
1210
1294
|
if (this.signal?.aborted) {
|
|
1211
1295
|
reject(this.signal.reason || new Error("operation cancelled"));
|
|
@@ -1214,13 +1298,13 @@ class ArenaToolExecutor {
|
|
|
1214
1298
|
const out = stdout.result();
|
|
1215
1299
|
const err = stderr.result();
|
|
1216
1300
|
resolve({
|
|
1217
|
-
status: timedOut ? "timeout" : signal ? "killed" : "completed",
|
|
1218
|
-
stderr: err.text,
|
|
1219
1301
|
stdout: out.text,
|
|
1302
|
+
stdout_truncated: out.truncated,
|
|
1303
|
+
stderr: err.text,
|
|
1304
|
+
stderr_truncated: err.truncated,
|
|
1220
1305
|
exit_code: timedOut ? null : code,
|
|
1306
|
+
status: timedOut ? "timeout" : signal ? "killed" : "completed",
|
|
1221
1307
|
duration_ms: Date.now() - startedAt,
|
|
1222
|
-
stderr_truncated: err.truncated,
|
|
1223
|
-
stdout_truncated: out.truncated,
|
|
1224
1308
|
});
|
|
1225
1309
|
};
|
|
1226
1310
|
// 宽限期结束时升级为组级 SIGKILL 再结算:abort 只发 SIGTERM,
|
|
@@ -1233,8 +1317,12 @@ class ArenaToolExecutor {
|
|
|
1233
1317
|
};
|
|
1234
1318
|
const timeout = setTimeout(() => {
|
|
1235
1319
|
timedOut = true;
|
|
1236
|
-
|
|
1237
|
-
|
|
1320
|
+
finish(null, null);
|
|
1321
|
+
// Keep draining the pipes so a continuing command cannot block or get
|
|
1322
|
+
// SIGPIPE. Do not keep the host CLI alive just to collect discarded output.
|
|
1323
|
+
child.unref();
|
|
1324
|
+
child.stdout.unref?.();
|
|
1325
|
+
child.stderr.unref?.();
|
|
1238
1326
|
}, timeoutSeconds * 1000);
|
|
1239
1327
|
const abort = () => {
|
|
1240
1328
|
killTree("SIGTERM");
|
|
@@ -1242,10 +1330,12 @@ class ArenaToolExecutor {
|
|
|
1242
1330
|
};
|
|
1243
1331
|
this.signal?.addEventListener("abort", abort, { once: true });
|
|
1244
1332
|
child.stdout.on("data", (chunk) => {
|
|
1245
|
-
|
|
1333
|
+
if (!settled)
|
|
1334
|
+
stdout.append(chunk);
|
|
1246
1335
|
});
|
|
1247
1336
|
child.stderr.on("data", (chunk) => {
|
|
1248
|
-
|
|
1337
|
+
if (!settled)
|
|
1338
|
+
stderr.append(chunk);
|
|
1249
1339
|
});
|
|
1250
1340
|
child.once("error", (error) => {
|
|
1251
1341
|
if (settled)
|
|
@@ -1389,7 +1479,7 @@ class ArenaToolExecutor {
|
|
|
1389
1479
|
}
|
|
1390
1480
|
return { status: "success", message: `Edited ${rawPath}.` };
|
|
1391
1481
|
}
|
|
1392
|
-
async readWorkspaceFile(rawPath, toolCallId) {
|
|
1482
|
+
async readWorkspaceFile(rawPath, toolCallId, offset, limit) {
|
|
1393
1483
|
let target;
|
|
1394
1484
|
let fileSize;
|
|
1395
1485
|
let sample;
|
|
@@ -1472,7 +1562,10 @@ class ArenaToolExecutor {
|
|
|
1472
1562
|
return result;
|
|
1473
1563
|
}
|
|
1474
1564
|
if (kind === "text") {
|
|
1475
|
-
const
|
|
1565
|
+
const start = (offset ?? 1) - 1;
|
|
1566
|
+
const selected = offset === undefined && limit === undefined ? data
|
|
1567
|
+
: Buffer.from(decodeText(data).split("\n").slice(start, limit === undefined ? undefined : start + limit).join("\n"), "utf8");
|
|
1568
|
+
const text = readTextResult(selected);
|
|
1476
1569
|
return {
|
|
1477
1570
|
kind: "text",
|
|
1478
1571
|
lines: data.reduce((count, byte) => count + (byte === 0x0a ? 1 : 0), 1),
|
|
@@ -1554,7 +1647,15 @@ class ArenaToolExecutor {
|
|
|
1554
1647
|
return { status: "success" };
|
|
1555
1648
|
}
|
|
1556
1649
|
async webSearch(query, depth) {
|
|
1557
|
-
|
|
1650
|
+
const result = await executeBraveWebSearch(query, depth, { signal: this.signal });
|
|
1651
|
+
// Keep the shared provider/Code Arena envelope unchanged. Agent Arena's
|
|
1652
|
+
// live history emits title before URL; restored history has its own order.
|
|
1653
|
+
if (!Array.isArray(result.results))
|
|
1654
|
+
return result;
|
|
1655
|
+
return { ...result, results: result.results.map(value => {
|
|
1656
|
+
const { id, title, url, ...rest } = record(value);
|
|
1657
|
+
return { id, title, url, ...rest };
|
|
1658
|
+
}) };
|
|
1558
1659
|
}
|
|
1559
1660
|
async imageSearch(query, count) {
|
|
1560
1661
|
const payload = await executeBraveImageSearch(query, count, {
|
|
@@ -1602,10 +1703,10 @@ class ArenaToolExecutor {
|
|
|
1602
1703
|
mkdirSync(path.dirname(target), { recursive: true });
|
|
1603
1704
|
await writeFile(target, await responseBytes(imageResponse));
|
|
1604
1705
|
saved.push({
|
|
1605
|
-
title: stringValue(item.title),
|
|
1606
1706
|
file_path: this.relativePath(target),
|
|
1607
|
-
source_url: sourceUrl,
|
|
1608
1707
|
thumbnail_url: thumbnailUrl,
|
|
1708
|
+
title: stringValue(item.title),
|
|
1709
|
+
source_url: sourceUrl,
|
|
1609
1710
|
});
|
|
1610
1711
|
break;
|
|
1611
1712
|
}
|
|
@@ -1630,13 +1731,13 @@ class ArenaToolExecutor {
|
|
|
1630
1731
|
const target = await this.confinedPath(rawPath);
|
|
1631
1732
|
if (!(await stat(target)).isFile())
|
|
1632
1733
|
throw new Error(rawPath);
|
|
1633
|
-
return {
|
|
1734
|
+
return { status: "success", path: rawPath };
|
|
1634
1735
|
}
|
|
1635
1736
|
catch {
|
|
1636
1737
|
return { status: "error", message: "Error calling present file tool" };
|
|
1637
1738
|
}
|
|
1638
1739
|
}
|
|
1639
|
-
async generateImage(rawPath, prompt, images) {
|
|
1740
|
+
async generateImage(rawPath, prompt, images, offerOptions = false, toolCallId = "generate_image", context) {
|
|
1640
1741
|
if (!rawPath) {
|
|
1641
1742
|
return { status: "error", message: "File path cannot be empty." };
|
|
1642
1743
|
}
|
|
@@ -1648,8 +1749,8 @@ class ArenaToolExecutor {
|
|
|
1648
1749
|
message: "File path must end in .jpg, .jpeg, or .png.",
|
|
1649
1750
|
};
|
|
1650
1751
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1752
|
+
if (!offerOptions)
|
|
1753
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
1653
1754
|
const references = [];
|
|
1654
1755
|
for (const imagePath of images || []) {
|
|
1655
1756
|
const source = await this.confinedPath(imagePath);
|
|
@@ -1674,14 +1775,26 @@ class ArenaToolExecutor {
|
|
|
1674
1775
|
mimeType,
|
|
1675
1776
|
});
|
|
1676
1777
|
}
|
|
1677
|
-
const
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1778
|
+
const size = process.env.ARENA_IMAGE_SIZE || "1024*1024";
|
|
1779
|
+
const generate = async (imageModel) => {
|
|
1780
|
+
const generated = await generateImageWithModelRouter({
|
|
1781
|
+
images: references, model: imageModel, prompt, signal: this.signal || context?.signal, size,
|
|
1782
|
+
});
|
|
1783
|
+
return generatedImageBytesForExtension(generated.bytes, generated.mimeType, extension);
|
|
1784
|
+
};
|
|
1785
|
+
if (offerOptions) {
|
|
1786
|
+
const models = imageOptionModels();
|
|
1787
|
+
return offerGeneratedImages({
|
|
1788
|
+
workspace: this.workspaceReal, stateRoot: this.config.stateRoot,
|
|
1789
|
+
target, rawPath, callId: toolCallId, context,
|
|
1790
|
+
signal: this.signal || context?.signal, models, generate,
|
|
1791
|
+
fingerprint: JSON.stringify({ target, rawPath, prompt, size, models,
|
|
1792
|
+
images: references.map(image => ({ mimeType: image.mimeType,
|
|
1793
|
+
sha256: createHash("sha256").update(image.bytes).digest("hex") })) }),
|
|
1794
|
+
askUser: this.askUserHandler, confinedPath: filePath => this.confinedPath(filePath),
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
await writeFile(target, await generate(arenaImageModel()));
|
|
1685
1798
|
return {
|
|
1686
1799
|
status: "success",
|
|
1687
1800
|
file_path: rawPath,
|
|
@@ -1746,6 +1859,20 @@ export class ToolExecutor {
|
|
|
1746
1859
|
availableTools() {
|
|
1747
1860
|
return this.implementation.availableTools();
|
|
1748
1861
|
}
|
|
1862
|
+
historyCall(call) {
|
|
1863
|
+
return this.implementation instanceof ArenaToolExecutor ? this.implementation.historyCall(call) : call;
|
|
1864
|
+
}
|
|
1865
|
+
takeInteractionBoundary(callId) {
|
|
1866
|
+
return this.implementation instanceof ArenaToolExecutor && this.implementation.takeInteractionBoundary(callId);
|
|
1867
|
+
}
|
|
1868
|
+
async beginConnectorTurn(history) {
|
|
1869
|
+
if (this.implementation instanceof ArenaToolExecutor)
|
|
1870
|
+
await this.implementation.beginConnectorTurn(history);
|
|
1871
|
+
}
|
|
1872
|
+
async closeConnectors() {
|
|
1873
|
+
if (this.implementation instanceof ArenaToolExecutor)
|
|
1874
|
+
await this.implementation.closeConnectors();
|
|
1875
|
+
}
|
|
1749
1876
|
inspect(call) {
|
|
1750
1877
|
return this.implementation.inspect(call);
|
|
1751
1878
|
}
|