@lazyingart/agintiflow 0.8.12 → 0.9.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/README.md +6 -0
- package/docs/auxiliary-image-generation.md +62 -0
- package/package.json +4 -2
- package/public/app.js +8 -2
- package/public/index.html +6 -0
- package/scripts/smoke-auxiliary-tools.js +95 -0
- package/src/agent-runner.js +81 -0
- package/src/auxiliary-tools.js +344 -0
- package/src/capabilities.js +14 -1
- package/src/cli.js +31 -7
- package/src/config.js +4 -0
- package/src/guardrails.js +15 -0
- package/src/interactive-cli.js +58 -8
- package/src/model-client.js +54 -0
- package/src/project.js +35 -13
- package/src/redaction.js +1 -1
- package/src/task-profiles.js +7 -0
- package/src/workspace-tools.js +2 -0
- package/web.js +4 -0
package/README.md
CHANGED
|
@@ -53,6 +53,10 @@ aginti login deepseek
|
|
|
53
53
|
# inside chat, use /login or /auth
|
|
54
54
|
# or non-interactively:
|
|
55
55
|
printf '%s' "$DEEPSEEK_API_KEY" | aginti keys set deepseek --stdin
|
|
56
|
+
|
|
57
|
+
# optional image-generation auxiliary skill:
|
|
58
|
+
aginti login grsai
|
|
59
|
+
# inside chat, use /auxilliary grsai or /auxiliary grsai
|
|
56
60
|
```
|
|
57
61
|
|
|
58
62
|
Start an interactive Codex-style CLI chat from any project folder:
|
|
@@ -67,6 +71,8 @@ Inside chat, type normal requests such as `write a small Python CLI app with tes
|
|
|
67
71
|
|
|
68
72
|
For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
|
|
69
73
|
|
|
74
|
+
For raster image work, AgInTiFlow has an optional `image_generation` skill backed by the `generate_image` tool and a local `GRSAI` key. The skill tells DeepSeek when image generation is appropriate; the tool calls GRS AI Nano Banana, saves manifests/images under `artifacts/images`, and sends the result to the canvas. See [docs/auxiliary-image-generation.md](docs/auxiliary-image-generation.md).
|
|
75
|
+
|
|
70
76
|
Launch the local web UI from an installed package:
|
|
71
77
|
|
|
72
78
|
```bash
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Auxiliary Image Generation
|
|
2
|
+
|
|
3
|
+
AgInTiFlow separates **skills** from **tools**:
|
|
4
|
+
|
|
5
|
+
- A skill is instruction and routing context that teaches the agent when a capability is useful.
|
|
6
|
+
- A tool is the deterministic callable function that performs the action and writes auditable artifacts.
|
|
7
|
+
|
|
8
|
+
The optional image-generation skill is `image_generation`. Its tool is `generate_image`.
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
Store the GRS AI key project-locally:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
aginti login grsai
|
|
16
|
+
# or
|
|
17
|
+
printf '%s' "$GRSAI" | aginti keys set grsai --stdin
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Inside interactive chat, use either spelling:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
/auxilliary grsai
|
|
24
|
+
/auxiliary grsai
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The key is saved in `.aginti/.env` as `GRSAI` with `0600` permissions. The CLI and web app only report whether the key exists; they never return the raw value.
|
|
28
|
+
|
|
29
|
+
## Runtime Flow
|
|
30
|
+
|
|
31
|
+
For image, cover, poster, illustration, photo, or logo-concept requests, the model can call:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"prompt": "A polished cyan robot painting a circuit-board river, high-end product illustration",
|
|
36
|
+
"outputDir": "artifacts/images/robot-cover",
|
|
37
|
+
"outputStem": "robot-cover",
|
|
38
|
+
"aspectRatio": "1:1",
|
|
39
|
+
"imageSize": "2K"
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The tool uses the GRS AI Nano Banana API:
|
|
44
|
+
|
|
45
|
+
- `POST https://grsaiapi.com/v1/draw/nano-banana`
|
|
46
|
+
- `POST https://grsaiapi.com/v1/draw/result`
|
|
47
|
+
- `Authorization: Bearer <GRSAI>`
|
|
48
|
+
|
|
49
|
+
Saved workspace artifacts:
|
|
50
|
+
|
|
51
|
+
- `prompt.txt`
|
|
52
|
+
- `request_payload.redacted.json`
|
|
53
|
+
- `submit_response.json`
|
|
54
|
+
- `result_response.json`
|
|
55
|
+
- `task_manifest.json`
|
|
56
|
+
- generated image files, for example `image.png`
|
|
57
|
+
|
|
58
|
+
Generated images are sent to the canvas automatically when available.
|
|
59
|
+
|
|
60
|
+
## Guardrails
|
|
61
|
+
|
|
62
|
+
Output paths must stay inside the project workspace. Secret paths, `.git`, `node_modules` writes, and oversized reference images are blocked. Reference images may be workspace files, HTTPS URLs, or data URLs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"scripts/install-docker-ubuntu.sh",
|
|
42
42
|
"scripts/setup-agent-toolchain-docker.sh",
|
|
43
43
|
"scripts/real-deepseek-capabilities.js",
|
|
44
|
+
"scripts/smoke-auxiliary-tools.js",
|
|
44
45
|
"scripts/smoke-cli-chat.js",
|
|
45
46
|
"scripts/smoke-coding-tools.js",
|
|
46
47
|
"scripts/smoke-capabilities.js",
|
|
@@ -61,12 +62,13 @@
|
|
|
61
62
|
"check": "node --check run.js && node --check web.js && node --check bin/aginti-cli.js && node --check src/*.js",
|
|
62
63
|
"setup:toolchain-docker": "scripts/setup-agent-toolchain-docker.sh",
|
|
63
64
|
"smoke:coding-tools": "node scripts/smoke-coding-tools.js",
|
|
65
|
+
"smoke:auxiliary-tools": "node scripts/smoke-auxiliary-tools.js",
|
|
64
66
|
"smoke:cli-chat": "node scripts/smoke-cli-chat.js",
|
|
65
67
|
"smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
|
|
66
68
|
"smoke:inbox": "node scripts/smoke-inbox.js",
|
|
67
69
|
"smoke:web-api": "node scripts/smoke-web-api.js",
|
|
68
70
|
"real:deepseek": "node scripts/real-deepseek-capabilities.js",
|
|
69
|
-
"test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:capabilities && npm run smoke:cli-chat && npm run smoke:inbox",
|
|
71
|
+
"test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:capabilities && npm run smoke:cli-chat && npm run smoke:inbox",
|
|
70
72
|
"pack:dry-run": "npm pack --dry-run",
|
|
71
73
|
"smoke:capabilities": "node scripts/smoke-capabilities.js"
|
|
72
74
|
},
|
package/public/app.js
CHANGED
|
@@ -24,7 +24,7 @@ const translations = {
|
|
|
24
24
|
setupHelp:
|
|
25
25
|
"DeepSeek/OpenAI keys are missing. Use mock mode, export an env var, or save a project-local DeepSeek key.",
|
|
26
26
|
setupEnvHelp:
|
|
27
|
-
"Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY,
|
|
27
|
+
"Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, LLM_API_KEY, and optional GRSAI for image generation. Mock mode remains available for local tests.",
|
|
28
28
|
setupProviderLabel: "Provider",
|
|
29
29
|
setupKeyLabel: "API key",
|
|
30
30
|
saveKeyButton: "Save local key",
|
|
@@ -68,6 +68,7 @@ const translations = {
|
|
|
68
68
|
headlessLabel: "Headless browser",
|
|
69
69
|
shellToolLabel: "Enable shell tool",
|
|
70
70
|
fileToolLabel: "Enable file tools",
|
|
71
|
+
auxiliaryToolLabel: "Enable auxiliary skills",
|
|
71
72
|
wrapperToolLabel: "Enable agent wrappers",
|
|
72
73
|
preferredWrapperLabel: "Preferred wrapper",
|
|
73
74
|
dockerSandboxLabel: "Use Docker sandbox",
|
|
@@ -648,6 +649,7 @@ const logsEl = document.querySelector("#logs");
|
|
|
648
649
|
const runMetaEl = document.querySelector("#run-meta");
|
|
649
650
|
const stopRunButton = document.querySelector("#stop-run");
|
|
650
651
|
const keyStatusEl = document.querySelector("#key-status");
|
|
652
|
+
const allowAuxiliaryToolsField = document.querySelector("#allowAuxiliaryTools");
|
|
651
653
|
const allowWrapperToolsField = document.querySelector("#allowWrapperTools");
|
|
652
654
|
const preferredWrapperField = document.querySelector("#preferredWrapper");
|
|
653
655
|
const wrapperStatusEl = document.querySelector("#wrapper-status");
|
|
@@ -744,7 +746,9 @@ function renderKeyStatus(status = lastKeyStatus) {
|
|
|
744
746
|
if (!status) return;
|
|
745
747
|
keyStatusEl.textContent = `${t("keysLabel")}: OpenAI ${
|
|
746
748
|
status.openai ? t("availableLabel") : t("missingLabel")
|
|
747
|
-
} · DeepSeek ${status.deepseek ? t("availableLabel") : t("missingLabel")} ·
|
|
749
|
+
} · DeepSeek ${status.deepseek ? t("availableLabel") : t("missingLabel")} · GRS AI ${
|
|
750
|
+
status.grsai ? t("availableLabel") : t("missingLabel")
|
|
751
|
+
} · ${t("mockLabel")} ${
|
|
748
752
|
status.mock ? t("availableLabel") : t("missingLabel")
|
|
749
753
|
}`;
|
|
750
754
|
if (setupCardEl) setupCardEl.hidden = Boolean(status.openai || status.deepseek);
|
|
@@ -979,6 +983,7 @@ function formPayload() {
|
|
|
979
983
|
headless: document.querySelector("#headless").checked,
|
|
980
984
|
allowShellTool: document.querySelector("#allowShellTool").checked,
|
|
981
985
|
allowFileTools: document.querySelector("#allowFileTools").checked,
|
|
986
|
+
allowAuxiliaryTools: allowAuxiliaryToolsField?.checked ?? true,
|
|
982
987
|
allowWrapperTools: allowWrapperToolsField.checked,
|
|
983
988
|
preferredWrapper: preferredWrapperField.value,
|
|
984
989
|
taskProfile: taskProfileField?.value || "auto",
|
|
@@ -2167,6 +2172,7 @@ async function loadConfig() {
|
|
|
2167
2172
|
packageInstallPolicyField.value = prefs.packageInstallPolicy || "allow";
|
|
2168
2173
|
document.querySelector("#allowShellTool").checked = prefs.allowShellTool ?? true;
|
|
2169
2174
|
document.querySelector("#allowFileTools").checked = prefs.allowFileTools ?? true;
|
|
2175
|
+
if (allowAuxiliaryToolsField) allowAuxiliaryToolsField.checked = prefs.allowAuxiliaryTools ?? true;
|
|
2170
2176
|
allowWrapperToolsField.checked = prefs.allowWrapperTools ?? false;
|
|
2171
2177
|
preferredWrapperField.value = prefs.preferredWrapper || "codex";
|
|
2172
2178
|
document.querySelector("#dockerSandboxImage").value = prefs.dockerSandboxImage || "agintiflow-sandbox:latest";
|
package/public/index.html
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
<select id="setup-provider">
|
|
58
58
|
<option value="deepseek">DeepSeek</option>
|
|
59
59
|
<option value="openai">OpenAI</option>
|
|
60
|
+
<option value="grsai">GRS AI image</option>
|
|
60
61
|
</select>
|
|
61
62
|
</label>
|
|
62
63
|
<label>
|
|
@@ -175,6 +176,11 @@
|
|
|
175
176
|
<span class="switch" aria-hidden="true"></span>
|
|
176
177
|
<span data-i18n="fileToolLabel">Enable file tools</span>
|
|
177
178
|
</label>
|
|
179
|
+
<label class="switch-label">
|
|
180
|
+
<input id="allowAuxiliaryTools" name="allowAuxiliaryTools" type="checkbox" checked />
|
|
181
|
+
<span class="switch" aria-hidden="true"></span>
|
|
182
|
+
<span data-i18n="auxiliaryToolLabel">Enable auxiliary skills</span>
|
|
183
|
+
</label>
|
|
178
184
|
<label class="switch-label">
|
|
179
185
|
<input id="allowPasswords" name="allowPasswords" type="checkbox" />
|
|
180
186
|
<span class="switch" aria-hidden="true"></span>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { runAgent } from "../src/agent-runner.js";
|
|
7
|
+
import { generateImage, listAuxiliarySkills } from "../src/auxiliary-tools.js";
|
|
8
|
+
import { resolveRuntimeConfig } from "../src/config.js";
|
|
9
|
+
import { providerKeyStatus, setProviderKey } from "../src/project.js";
|
|
10
|
+
import { SessionStore } from "../src/session-store.js";
|
|
11
|
+
|
|
12
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-auxiliary-"));
|
|
14
|
+
const runtimeDir = path.join(tempRoot, "runtime");
|
|
15
|
+
const workspace = path.join(tempRoot, "workspace");
|
|
16
|
+
await fs.mkdir(workspace, { recursive: true });
|
|
17
|
+
|
|
18
|
+
function assert(condition, message) {
|
|
19
|
+
if (!condition) throw new Error(message);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await setProviderKey(workspace, "grsai", "test-grsai-key");
|
|
24
|
+
const keyStatus = providerKeyStatus(workspace);
|
|
25
|
+
assert(keyStatus.grsai, "GRSAI key status was not detected");
|
|
26
|
+
assert(keyStatus.envVars.grsai.includes("GRSAI"), "GRSAI env var name was not reported");
|
|
27
|
+
assert(listAuxiliarySkills().some((skill) => skill.id === "image_generation"), "image_generation skill missing");
|
|
28
|
+
|
|
29
|
+
const dryRun = await generateImage(
|
|
30
|
+
{
|
|
31
|
+
prompt: "A small cyan robot holding a paintbrush, clean bright product illustration.",
|
|
32
|
+
outputDir: "artifacts/images/dry-run",
|
|
33
|
+
outputStem: "robot",
|
|
34
|
+
dryRun: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
commandCwd: workspace,
|
|
38
|
+
allowFileTools: true,
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
assert(dryRun.ok && dryRun.dryRun, "generate_image dry run failed");
|
|
42
|
+
await fs.access(path.join(workspace, "artifacts/images/dry-run/task_manifest.json"));
|
|
43
|
+
const payloadText = await fs.readFile(path.join(workspace, "artifacts/images/dry-run/request_payload.redacted.json"), "utf8");
|
|
44
|
+
assert(payloadText.includes("nano-banana-2"), "redacted image payload was not written");
|
|
45
|
+
|
|
46
|
+
const blocked = await generateImage(
|
|
47
|
+
{
|
|
48
|
+
prompt: "blocked",
|
|
49
|
+
outputDir: ".env/images",
|
|
50
|
+
dryRun: true,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
commandCwd: workspace,
|
|
54
|
+
allowFileTools: true,
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
assert(blocked.blocked, "generate_image did not block sensitive output path");
|
|
58
|
+
|
|
59
|
+
const config = resolveRuntimeConfig(
|
|
60
|
+
{
|
|
61
|
+
provider: "mock",
|
|
62
|
+
routingMode: "manual",
|
|
63
|
+
model: "mock-agent",
|
|
64
|
+
goal: "Generate an image of a panda astronaut.",
|
|
65
|
+
commandCwd: workspace,
|
|
66
|
+
allowFileTools: true,
|
|
67
|
+
allowAuxiliaryTools: true,
|
|
68
|
+
maxSteps: 4,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
baseDir: runtimeDir,
|
|
72
|
+
packageDir: repoRoot,
|
|
73
|
+
provider: "mock",
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
const run = await runAgent(config);
|
|
77
|
+
const store = new SessionStore(config.sessionsDir, run.sessionId);
|
|
78
|
+
const events = await store.loadEvents();
|
|
79
|
+
assert(events.some((event) => event.type === "tool.completed" && event.data?.toolName === "generate_image"), "mock run did not call generate_image");
|
|
80
|
+
await fs.access(path.join(workspace, "artifacts/images/mock-image/task_manifest.json"));
|
|
81
|
+
|
|
82
|
+
console.log(
|
|
83
|
+
JSON.stringify(
|
|
84
|
+
{
|
|
85
|
+
ok: true,
|
|
86
|
+
workspace,
|
|
87
|
+
checks: ["grsai_key_status", "image_skill_listed", "generate_image_dry_run", "generate_image_guardrail", "mock_agent_image_tool"],
|
|
88
|
+
},
|
|
89
|
+
null,
|
|
90
|
+
2
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
} finally {
|
|
94
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
95
|
+
}
|
package/src/agent-runner.js
CHANGED
|
@@ -17,6 +17,7 @@ import { redactSensitiveText, redactValue } from "./redaction.js";
|
|
|
17
17
|
import { executeWorkspaceTool, resolveWorkspacePath, summarizeWorkspaceTools, WORKSPACE_TOOL_NAMES } from "./workspace-tools.js";
|
|
18
18
|
import { normalizeCanvasPayload } from "./artifact-tunnel.js";
|
|
19
19
|
import { getTaskProfile } from "./task-profiles.js";
|
|
20
|
+
import { generateImage, listAuxiliarySkills } from "./auxiliary-tools.js";
|
|
20
21
|
|
|
21
22
|
const exec = promisify(execCallback);
|
|
22
23
|
const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
|
|
@@ -276,6 +277,11 @@ function createInitialState(config, sessionId) {
|
|
|
276
277
|
config.allowWrapperTools
|
|
277
278
|
? `External coding-agent wrappers are available as advisory tools only. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Wrapper status: ${wrapperStatusText()}.`
|
|
278
279
|
: "External coding-agent wrappers are disabled.",
|
|
280
|
+
config.allowAuxiliaryTools
|
|
281
|
+
? `Auxiliary skills are available: ${listAuxiliarySkills()
|
|
282
|
+
.map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
|
|
283
|
+
.join(", ")}. Use generate_image for real raster image/photo/illustration/cover/poster/logo requests when appropriate; if the key is missing, ask the user to run /auxilliary grsai or aginti login grsai.`
|
|
284
|
+
: "Auxiliary skills are disabled for this run.",
|
|
279
285
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
280
286
|
"A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. It is optional and ordinary final text can still go directly to finish.",
|
|
281
287
|
"For visual-output requests such as draw, plot, graph, chart, diagram, figure, image, or visualization, proactively publish a canvas artifact even when the user does not mention canvas. If workspace file tools are enabled, prefer creating a small SVG or markdown artifact and call send_to_canvas with selected=true.",
|
|
@@ -309,6 +315,11 @@ function createInitialState(config, sessionId) {
|
|
|
309
315
|
config.allowWrapperTools
|
|
310
316
|
? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
|
|
311
317
|
: "",
|
|
318
|
+
config.allowAuxiliaryTools
|
|
319
|
+
? `Auxiliary skills: ${listAuxiliarySkills()
|
|
320
|
+
.map((skill) => `${skill.id}:${skill.available ? "available" : "missing-key"}`)
|
|
321
|
+
.join(" ")}`
|
|
322
|
+
: "",
|
|
312
323
|
`Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
|
|
313
324
|
"Canvas/artifacts tunnel: available through send_to_canvas for optional frontend rendering.",
|
|
314
325
|
"Visual-output requests should produce a canvas artifact without requiring the user to ask for canvas explicitly.",
|
|
@@ -504,6 +515,15 @@ function sanitizeToolArgs(toolName, args) {
|
|
|
504
515
|
replace: typeof args.replace === "string" ? redactSensitiveText(args.replace).slice(0, 160) : safeArgs.replace,
|
|
505
516
|
};
|
|
506
517
|
}
|
|
518
|
+
if (toolName === "generate_image") {
|
|
519
|
+
return {
|
|
520
|
+
...safeArgs,
|
|
521
|
+
prompt: typeof args.prompt === "string" ? `[${Buffer.byteLength(args.prompt, "utf8")} bytes sha256=${hashForLog(args.prompt)}]` : safeArgs.prompt,
|
|
522
|
+
referenceImages: Array.isArray(args.referenceImages)
|
|
523
|
+
? args.referenceImages.map((item) => (String(item || "").startsWith("data:") ? `[data-uri ${String(item).length} chars]` : redactSensitiveText(item)))
|
|
524
|
+
: safeArgs.referenceImages,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
507
527
|
return safeArgs;
|
|
508
528
|
}
|
|
509
529
|
|
|
@@ -874,6 +894,67 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
874
894
|
observers.event("tool.completed", result);
|
|
875
895
|
return result;
|
|
876
896
|
}
|
|
897
|
+
case "generate_image": {
|
|
898
|
+
const imageResult = await generateImage(args, config);
|
|
899
|
+
const result = {
|
|
900
|
+
ok: Boolean(imageResult.ok),
|
|
901
|
+
toolName: "generate_image",
|
|
902
|
+
args: safeArgs,
|
|
903
|
+
...imageResult,
|
|
904
|
+
};
|
|
905
|
+
const eventResult = sanitizeToolResult(result);
|
|
906
|
+
await store.appendEvent("tool.completed", eventResult);
|
|
907
|
+
observers.event("tool.completed", eventResult);
|
|
908
|
+
|
|
909
|
+
if (result.ok) {
|
|
910
|
+
const generated = {
|
|
911
|
+
path: result.path,
|
|
912
|
+
imagePaths: result.imagePaths || [],
|
|
913
|
+
manifestPath: result.manifestPath || "",
|
|
914
|
+
promptPath: result.promptPath || "",
|
|
915
|
+
requestPayloadPath: result.requestPayloadPath || "",
|
|
916
|
+
commandCwd: config.commandCwd,
|
|
917
|
+
};
|
|
918
|
+
await store.appendEvent("image.generated", generated);
|
|
919
|
+
observers.event("image.generated", generated);
|
|
920
|
+
|
|
921
|
+
const selectedPath = result.imagePaths?.[0] || result.manifestPath || "";
|
|
922
|
+
if (selectedPath) {
|
|
923
|
+
const normalized = normalizeCanvasPayload(
|
|
924
|
+
{
|
|
925
|
+
title: result.imagePaths?.length ? "Generated image" : "Image generation payload",
|
|
926
|
+
kind: result.imagePaths?.length ? "image" : "json",
|
|
927
|
+
path: selectedPath,
|
|
928
|
+
note: result.summary || "Generated image artifact.",
|
|
929
|
+
selected: Boolean(result.imagePaths?.length),
|
|
930
|
+
},
|
|
931
|
+
config
|
|
932
|
+
);
|
|
933
|
+
if (normalized.ok) {
|
|
934
|
+
const canvasItem = {
|
|
935
|
+
...normalized.payload,
|
|
936
|
+
toolName: "generate_image",
|
|
937
|
+
commandCwd: config.commandCwd,
|
|
938
|
+
};
|
|
939
|
+
await store.appendEvent("canvas.item", canvasItem);
|
|
940
|
+
observers.event("canvas.item", canvasItem);
|
|
941
|
+
if (canvasItem.selected) {
|
|
942
|
+
await store.appendEvent("canvas.selected", {
|
|
943
|
+
artifactId: canvasItem.artifactId,
|
|
944
|
+
title: canvasItem.title,
|
|
945
|
+
source: "generate_image",
|
|
946
|
+
});
|
|
947
|
+
observers.event("canvas.selected", {
|
|
948
|
+
artifactId: canvasItem.artifactId,
|
|
949
|
+
title: canvasItem.title,
|
|
950
|
+
source: "generate_image",
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return result;
|
|
957
|
+
}
|
|
877
958
|
case "send_to_canvas": {
|
|
878
959
|
const normalized = normalizeCanvasPayload(args, config);
|
|
879
960
|
if (!normalized.ok) {
|