@cjhyy/code-shell-core 0.5.0-rc.0 → 0.5.0-rc.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/dist/agent/agent-definition-registry.d.ts +29 -0
- package/dist/agent/agent-definition-registry.js +74 -0
- package/dist/agent/agent-definition.d.ts +37 -0
- package/dist/agent/agent-definition.js +59 -0
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +2 -4
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.js +36 -3
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +31 -0
- package/dist/automation/cron-expr.js +151 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +67 -0
- package/dist/automation/runner.js +56 -0
- package/dist/automation/scheduler.d.ts +156 -0
- package/dist/automation/scheduler.js +546 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +119 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +49 -0
- package/dist/capability-control/overlay.js +89 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cli/agent-server-stdio.d.ts +12 -1
- package/dist/cli/agent-server-stdio.js +118 -18
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +120 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.js +23 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +0 -37
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/engine.d.ts +256 -16
- package/dist/engine/engine.js +856 -200
- package/dist/engine/goal.d.ts +42 -0
- package/dist/engine/goal.js +52 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +152 -0
- package/dist/engine/image-policy.js +165 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/runtime.js +7 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/turn-loop.d.ts +29 -8
- package/dist/engine/turn-loop.js +498 -316
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +57 -26
- package/dist/git/worktree.js +33 -18
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +30 -0
- package/dist/hooks/goal-stop-hook.js +102 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +7 -0
- package/dist/hooks/registry.js +38 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +43 -2
- package/dist/index.d.ts +32 -7
- package/dist/index.js +30 -3
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +33 -3
- package/dist/llm/capabilities/types.d.ts +22 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +41 -4
- package/dist/llm/client-base.js +67 -13
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-pool.d.ts +15 -11
- package/dist/llm/model-pool.js +23 -20
- package/dist/llm/provider-catalog.d.ts +2 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +147 -8
- package/dist/llm/providers/openai.d.ts +11 -2
- package/dist/llm/providers/openai.js +253 -52
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +32 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/types.d.ts +5 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +90 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.js +2 -1
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/onboarding.js +18 -11
- package/dist/plugins/gitOps.d.ts +34 -0
- package/dist/plugins/gitOps.js +59 -3
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
- package/dist/plugins/installer/codex/convertMcp.js +45 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +99 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +45 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +23 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
- package/dist/plugins/installer/loadPluginMcp.js +74 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/types.d.ts +49 -0
- package/dist/plugins/installer/types.js +27 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +14 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +65 -0
- package/dist/plugins/loadPluginHooks.d.ts +8 -1
- package/dist/plugins/loadPluginHooks.js +11 -1
- package/dist/plugins/marketplaceManager.d.ts +8 -1
- package/dist/plugins/marketplaceManager.js +38 -4
- package/dist/plugins/pluginCommandHook.js +34 -0
- package/dist/plugins/pluginInstaller.d.ts +21 -0
- package/dist/plugins/pluginInstaller.js +128 -29
- package/dist/plugins/schemas.js +40 -6
- package/dist/plugins/types.d.ts +4 -0
- package/dist/preset/index.js +16 -0
- package/dist/prompt/composer.d.ts +10 -0
- package/dist/prompt/composer.js +29 -4
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -1
- package/dist/protocol/chat-session-manager.js +10 -0
- package/dist/protocol/chat-session.d.ts +17 -0
- package/dist/protocol/chat-session.js +28 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +41 -2
- package/dist/protocol/server.js +231 -33
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/types.d.ts +23 -0
- package/dist/protocol/types.js +6 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +32 -8
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.js +51 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +7 -0
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/memory-orchestrator.js +18 -0
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +8 -9
- package/dist/session/file-history.js +10 -3
- package/dist/session/session-manager.d.ts +23 -1
- package/dist/session/session-manager.js +73 -2
- package/dist/settings/disk-defaults.d.ts +35 -0
- package/dist/settings/disk-defaults.js +24 -0
- package/dist/settings/manager.d.ts +45 -1
- package/dist/settings/manager.js +121 -17
- package/dist/settings/manager.test.d.ts +1 -0
- package/dist/settings/manager.test.js +73 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema.d.ts +1163 -126
- package/dist/settings/schema.js +126 -10
- package/dist/skills/scanner.d.ts +13 -6
- package/dist/skills/scanner.js +23 -4
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
- package/dist/tool-system/builtin/agent-registry.js +14 -4
- package/dist/tool-system/builtin/agent.d.ts +55 -0
- package/dist/tool-system/builtin/agent.js +165 -17
- package/dist/tool-system/builtin/apply-patch/index.js +22 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/cron.js +53 -9
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +6 -1
- package/dist/tool-system/builtin/generate-image.d.ts +25 -0
- package/dist/tool-system/builtin/generate-image.js +146 -0
- package/dist/tool-system/builtin/glob.js +7 -0
- package/dist/tool-system/builtin/grep.js +6 -0
- package/dist/tool-system/builtin/index.d.ts +18 -1
- package/dist/tool-system/builtin/index.js +62 -1
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +8 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +9 -1
- package/dist/tool-system/builtin/skill.js +23 -5
- package/dist/tool-system/builtin/sleep.js +9 -3
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +36 -3
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +7 -1
- package/dist/tool-system/context.d.ts +43 -0
- package/dist/tool-system/executor.d.ts +0 -1
- package/dist/tool-system/executor.js +34 -40
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +19 -3
- package/dist/tool-system/mcp-manager.d.ts +36 -12
- package/dist/tool-system/mcp-manager.js +173 -10
- package/dist/tool-system/path-policy.d.ts +94 -0
- package/dist/tool-system/path-policy.js +279 -0
- package/dist/tool-system/permission.js +43 -50
- package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
- package/dist/tool-system/plan-mode-allowlist.js +43 -0
- package/dist/tool-system/registry.js +12 -1
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +90 -15
- package/dist/utils/format.js +4 -2
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +6 -0
- package/dist/utils/theme.js +4 -2
- package/package.json +2 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in GenerateImage tool — text-to-image via the OpenAI Images API
|
|
3
|
+
* (`gpt-image-2`).
|
|
4
|
+
*
|
|
5
|
+
* Credentials come from config, not env: the tool finds the `kind: "openai"`
|
|
6
|
+
* entry in `settings.providers[]` and uses its `apiKey` + `baseUrl`. No
|
|
7
|
+
* `OPENAI_API_KEY` required, no new config — an existing OpenAI provider is
|
|
8
|
+
* reused as-is.
|
|
9
|
+
*
|
|
10
|
+
* The generated PNG is decoded from the API's base64 payload and written to
|
|
11
|
+
* `<cwd>/.code-shell/generated_images/<timestamp>.png`; the tool returns the
|
|
12
|
+
* absolute path so the model can Read or reference it on later turns. This
|
|
13
|
+
* mirrors codex's imagegen CLI fallback (`scripts/image_gen.py`): call API →
|
|
14
|
+
* take b64_json → decode → write → report path.
|
|
15
|
+
*
|
|
16
|
+
* Scope is deliberately narrow (text-to-image only). Edit/mask/transparent/
|
|
17
|
+
* batch are out of scope — see the design doc.
|
|
18
|
+
*
|
|
19
|
+
* Uses the native `fetch` (codeshell does not depend on the `openai` SDK).
|
|
20
|
+
*/
|
|
21
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { SettingsManager } from "../../settings/manager.js";
|
|
24
|
+
const DEFAULT_SIZE = "1024x1024";
|
|
25
|
+
const DEFAULT_QUALITY = "auto";
|
|
26
|
+
const MODEL = "gpt-image-2";
|
|
27
|
+
export const generateImageToolDef = {
|
|
28
|
+
name: "GenerateImage",
|
|
29
|
+
description: "Generate an image from a text prompt using the OpenAI Images API (gpt-image-2). " +
|
|
30
|
+
"Saves a PNG into the workspace and returns its absolute path; read or reference " +
|
|
31
|
+
"that path on later turns. Requires an OpenAI provider (kind: \"openai\") configured " +
|
|
32
|
+
"in settings. For raster assets (photos, illustrations, mockups, textures) — not for " +
|
|
33
|
+
"SVG/vector icons or anything better built directly in code.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
prompt: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Description of the image to generate",
|
|
40
|
+
},
|
|
41
|
+
size: {
|
|
42
|
+
type: "string",
|
|
43
|
+
enum: ["1024x1024", "1536x1024", "1024x1536", "auto"],
|
|
44
|
+
description: "Image dimensions (default 1024x1024)",
|
|
45
|
+
},
|
|
46
|
+
quality: {
|
|
47
|
+
type: "string",
|
|
48
|
+
enum: ["low", "medium", "high", "auto"],
|
|
49
|
+
description: "Render quality (default auto). Use low for drafts, high for final assets.",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ["prompt"],
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
/** Resolve the OpenAI provider's apiKey + baseUrl from settings, or null. */
|
|
56
|
+
function resolveOpenAIProvider(cwd) {
|
|
57
|
+
const settings = new SettingsManager(cwd, "full").get();
|
|
58
|
+
const provider = settings.providers.find((p) => p.kind === "openai");
|
|
59
|
+
if (!provider || !provider.apiKey)
|
|
60
|
+
return null;
|
|
61
|
+
return { baseUrl: provider.baseUrl, apiKey: provider.apiKey };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Tool-visibility guard: GenerateImage needs a kind:"openai" provider with a
|
|
65
|
+
* key. resolveOpenAIProvider returns null when none is configured.
|
|
66
|
+
*
|
|
67
|
+
* Cached per-cwd with a short TTL: the guard runs on EVERY message's toolDefs
|
|
68
|
+
* assembly (engine.ts), and resolveOpenAIProvider reads + parses settings from
|
|
69
|
+
* disk each call. The 1s TTL collapses the back-to-back reads (same message,
|
|
70
|
+
* rapid turns) while still picking up a newly-configured key well within the
|
|
71
|
+
* gap before the user's next message. The execution path
|
|
72
|
+
* (resolveOpenAIProvider) is intentionally NOT cached — only this boolean.
|
|
73
|
+
*/
|
|
74
|
+
const availCache = new Map();
|
|
75
|
+
const AVAIL_TTL_MS = 1000;
|
|
76
|
+
export function isGenerateImageAvailable(cwd = process.cwd(), nowMs = Date.now()) {
|
|
77
|
+
const hit = availCache.get(cwd);
|
|
78
|
+
if (hit && nowMs - hit.at < AVAIL_TTL_MS)
|
|
79
|
+
return hit.value;
|
|
80
|
+
let value = false;
|
|
81
|
+
try {
|
|
82
|
+
value = resolveOpenAIProvider(cwd) !== null;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
value = false;
|
|
86
|
+
}
|
|
87
|
+
availCache.set(cwd, { at: nowMs, value });
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
export async function generateImageTool(args, ctx) {
|
|
91
|
+
const prompt = args.prompt;
|
|
92
|
+
if (typeof prompt !== "string" || !prompt.trim()) {
|
|
93
|
+
return "Error: prompt is required";
|
|
94
|
+
}
|
|
95
|
+
const size = typeof args.size === "string" ? args.size : DEFAULT_SIZE;
|
|
96
|
+
const quality = typeof args.quality === "string" ? args.quality : DEFAULT_QUALITY;
|
|
97
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
98
|
+
const provider = resolveOpenAIProvider(cwd);
|
|
99
|
+
if (!provider) {
|
|
100
|
+
return ('Error: no OpenAI provider available. Configure a provider with kind: "openai" ' +
|
|
101
|
+
"(including an apiKey) in your code-shell settings to use GenerateImage.");
|
|
102
|
+
}
|
|
103
|
+
// Trim a trailing slash so `${baseUrl}/images/generations` doesn't double up.
|
|
104
|
+
const baseUrl = provider.baseUrl.replace(/\/+$/, "");
|
|
105
|
+
let resp;
|
|
106
|
+
try {
|
|
107
|
+
resp = await fetch(`${baseUrl}/images/generations`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
Authorization: `Bearer ${provider.apiKey}`,
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({ model: MODEL, prompt, size, quality, n: 1 }),
|
|
114
|
+
signal: ctx?.signal,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return `Error generating image: ${err.message}`;
|
|
119
|
+
}
|
|
120
|
+
if (!resp.ok) {
|
|
121
|
+
const body = (await resp.text().catch(() => "")).slice(0, 500);
|
|
122
|
+
return `Error: image API returned ${resp.status}: ${body}`;
|
|
123
|
+
}
|
|
124
|
+
let json;
|
|
125
|
+
try {
|
|
126
|
+
json = await resp.json();
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
return `Error: could not parse image API response: ${err.message}`;
|
|
130
|
+
}
|
|
131
|
+
const b64 = json?.data?.[0]?.b64_json;
|
|
132
|
+
if (typeof b64 !== "string" || !b64) {
|
|
133
|
+
const preview = JSON.stringify(json).slice(0, 500);
|
|
134
|
+
return `Error: no image in response: ${preview}`;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const dir = join(cwd, ".code-shell", "generated_images");
|
|
138
|
+
await mkdir(dir, { recursive: true });
|
|
139
|
+
const path = join(dir, `${Date.now()}.png`);
|
|
140
|
+
await writeFile(path, Buffer.from(b64, "base64"));
|
|
141
|
+
return `Generated image saved to ${path}`;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
return `Error saving generated image: ${err.message}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { glob } from "glob";
|
|
5
5
|
import { stat } from "node:fs/promises";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
+
import { enforcePathPolicy } from "../path-policy.js";
|
|
7
8
|
export const globToolDef = {
|
|
8
9
|
name: "Glob",
|
|
9
10
|
description: "Fast file pattern matching. Returns matching file paths sorted by modification time. " +
|
|
@@ -31,6 +32,12 @@ export async function globTool(args, ctx) {
|
|
|
31
32
|
? argPath
|
|
32
33
|
: path.resolve(baseDir, argPath)
|
|
33
34
|
: baseDir;
|
|
35
|
+
// Path policy: refuse to glob a search root the policy classifies as
|
|
36
|
+
// ask/deny for read. Stops a Glob("**/*", "/home/user/.ssh") from
|
|
37
|
+
// enumerating the keys directory under the existence-check radar.
|
|
38
|
+
const blocked = enforcePathPolicy(cwd, "read", ctx?.cwd);
|
|
39
|
+
if (blocked)
|
|
40
|
+
return blocked;
|
|
34
41
|
try {
|
|
35
42
|
const matches = await glob(pattern, {
|
|
36
43
|
cwd,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { resolve, sep, isAbsolute } from "node:path";
|
|
7
|
+
import { enforcePathPolicy } from "../path-policy.js";
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
/**
|
|
9
10
|
* Strip the cwd prefix from each line so results display as relative
|
|
@@ -58,6 +59,11 @@ export async function grepTool(args, ctx) {
|
|
|
58
59
|
? argPath
|
|
59
60
|
: resolve(baseDir, argPath)
|
|
60
61
|
: baseDir;
|
|
62
|
+
// Path policy: reading file contents under ~/.ssh, ~/.aws etc. by way
|
|
63
|
+
// of grep is the same exfiltration risk as Read. Gate the search root.
|
|
64
|
+
const blocked = enforcePathPolicy(searchPath, "read", ctx?.cwd);
|
|
65
|
+
if (blocked)
|
|
66
|
+
return blocked;
|
|
61
67
|
const fileGlob = args.glob;
|
|
62
68
|
const context = args.context || 0;
|
|
63
69
|
const maxResults = args.max_results || 50;
|
|
@@ -12,9 +12,26 @@ import type { RegisteredTool } from "../../types.js";
|
|
|
12
12
|
* ctx is optional in the signature so legacy call sites (and tests) that
|
|
13
13
|
* pass only args still type-check; ToolRegistry always passes ctx at runtime.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* 内置工具返回值:大多数返回纯文本字符串。需要回传图片(或其它结构化
|
|
17
|
+
* 内容块)的工具(view_image)可改为返回 `{ contentBlocks }`;此时
|
|
18
|
+
* registry 会把它放进 ToolResult.contentBlocks。可选的 `result` 字段是给
|
|
19
|
+
* transcript / 摘要用的纯文本镜像。
|
|
20
|
+
*/
|
|
21
|
+
export type BuiltinToolResult = string | {
|
|
22
|
+
contentBlocks: import("../../types.js").ContentBlock[];
|
|
23
|
+
result?: string;
|
|
24
|
+
};
|
|
25
|
+
export type BuiltinToolFn = (args: Record<string, unknown>, ctx?: import("../context.js").ToolContext) => Promise<BuiltinToolResult>;
|
|
16
26
|
export interface BuiltinTool {
|
|
17
27
|
definition: RegisteredTool;
|
|
18
28
|
execute: BuiltinToolFn;
|
|
19
29
|
}
|
|
20
30
|
export declare const BUILTIN_TOOLS: BuiltinTool[];
|
|
31
|
+
/**
|
|
32
|
+
* Per-tool availability predicates. A tool listed here is filtered OUT of the
|
|
33
|
+
* exposed toolDefs when its predicate returns false for the active cwd (see
|
|
34
|
+
* engine.ts toolDefs assembly). Tools NOT listed here are always visible.
|
|
35
|
+
* Keyed by the tool's `name` (must match the toolDef name).
|
|
36
|
+
*/
|
|
37
|
+
export declare const BUILTIN_TOOL_GUARDS: Map<string, (cwd: string) => boolean>;
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { readToolDef, readTool } from "./read.js";
|
|
5
5
|
import { writeToolDef, writeTool } from "./write.js";
|
|
6
|
+
import { generateImageToolDef, generateImageTool, isGenerateImageAvailable } from "./generate-image.js";
|
|
7
|
+
import { viewImageToolDef, viewImageTool } from "./view-image.js";
|
|
6
8
|
import { editToolDef, editTool } from "./edit.js";
|
|
7
9
|
import { applyPatchToolDef, applyPatchTool } from "./apply-patch/index.js";
|
|
8
10
|
import { globToolDef, globTool } from "./glob.js";
|
|
9
11
|
import { grepToolDef, grepTool } from "./grep.js";
|
|
10
12
|
import { bashToolDef, bashTool } from "./bash.js";
|
|
11
|
-
import { webSearchToolDef, webSearchTool } from "./web-search.js";
|
|
13
|
+
import { webSearchToolDef, webSearchTool, isWebSearchAvailable } from "./web-search.js";
|
|
12
14
|
import { webFetchToolDef, webFetchTool } from "./web-fetch.js";
|
|
13
15
|
import { askUserToolDef, askUserTool } from "./ask-user.js";
|
|
14
16
|
import { agentToolDef, agentTool, agentStatusToolDef, agentStatusTool, agentCancelToolDef, agentCancelTool, } from "./agent.js";
|
|
@@ -30,6 +32,8 @@ import { briefToolDef, briefTool } from "./brief.js";
|
|
|
30
32
|
import { powershellToolDef, powershellTool } from "./powershell.js";
|
|
31
33
|
import { arenaToolDef, arenaTool } from "./arena.js";
|
|
32
34
|
import { memoryListToolDef, memoryListTool, memoryReadToolDef, memoryReadTool, memorySaveToolDef, memorySaveTool, memoryDeleteToolDef, memoryDeleteTool, } from "./memory.js";
|
|
35
|
+
import { completeGoalToolDef, completeGoalTool } from "./complete-goal.js";
|
|
36
|
+
import { addMarketplaceToolDef, addMarketplaceTool } from "./add-marketplace.js";
|
|
33
37
|
export const BUILTIN_TOOLS = [
|
|
34
38
|
{
|
|
35
39
|
definition: {
|
|
@@ -51,6 +55,27 @@ export const BUILTIN_TOOLS = [
|
|
|
51
55
|
},
|
|
52
56
|
execute: writeTool,
|
|
53
57
|
},
|
|
58
|
+
{
|
|
59
|
+
definition: {
|
|
60
|
+
...generateImageToolDef,
|
|
61
|
+
source: "builtin",
|
|
62
|
+
permissionDefault: "ask",
|
|
63
|
+
isReadOnly: false,
|
|
64
|
+
isConcurrencySafe: false,
|
|
65
|
+
timeoutMs: 600_000, // 10min — high-quality / large image generation routinely exceeds the 120s default; give slow renders ample room while still bounding a hung request (Stop / ctx.signal cancels sooner)
|
|
66
|
+
},
|
|
67
|
+
execute: generateImageTool,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
definition: {
|
|
71
|
+
...viewImageToolDef,
|
|
72
|
+
source: "builtin",
|
|
73
|
+
permissionDefault: "allow",
|
|
74
|
+
isReadOnly: true,
|
|
75
|
+
isConcurrencySafe: true,
|
|
76
|
+
},
|
|
77
|
+
execute: viewImageTool,
|
|
78
|
+
},
|
|
54
79
|
{
|
|
55
80
|
definition: {
|
|
56
81
|
...editToolDef,
|
|
@@ -446,4 +471,40 @@ export const BUILTIN_TOOLS = [
|
|
|
446
471
|
},
|
|
447
472
|
execute: memoryDeleteTool,
|
|
448
473
|
},
|
|
474
|
+
// ─── Goal mode: model-declared completion ──────────────────────
|
|
475
|
+
// Lets the model explicitly declare the active goal complete. The
|
|
476
|
+
// turn-loop short-circuits on this call; the tool itself only records an
|
|
477
|
+
// acknowledgement string, so it's read-only and concurrency-safe.
|
|
478
|
+
{
|
|
479
|
+
definition: {
|
|
480
|
+
...completeGoalToolDef,
|
|
481
|
+
source: "builtin",
|
|
482
|
+
permissionDefault: "allow",
|
|
483
|
+
isReadOnly: true,
|
|
484
|
+
isConcurrencySafe: true,
|
|
485
|
+
},
|
|
486
|
+
execute: completeGoalTool,
|
|
487
|
+
},
|
|
488
|
+
// ─── Plugin marketplace: model-driven source registration ──────
|
|
489
|
+
{
|
|
490
|
+
definition: {
|
|
491
|
+
...addMarketplaceToolDef,
|
|
492
|
+
source: "builtin",
|
|
493
|
+
permissionDefault: "ask",
|
|
494
|
+
isReadOnly: false,
|
|
495
|
+
isConcurrencySafe: false,
|
|
496
|
+
timeoutMs: 120_000, // git clone over network
|
|
497
|
+
},
|
|
498
|
+
execute: addMarketplaceTool,
|
|
499
|
+
},
|
|
449
500
|
];
|
|
501
|
+
/**
|
|
502
|
+
* Per-tool availability predicates. A tool listed here is filtered OUT of the
|
|
503
|
+
* exposed toolDefs when its predicate returns false for the active cwd (see
|
|
504
|
+
* engine.ts toolDefs assembly). Tools NOT listed here are always visible.
|
|
505
|
+
* Keyed by the tool's `name` (must match the toolDef name).
|
|
506
|
+
*/
|
|
507
|
+
export const BUILTIN_TOOL_GUARDS = new Map([
|
|
508
|
+
[webSearchToolDef.name, isWebSearchAvailable],
|
|
509
|
+
[generateImageToolDef.name, isGenerateImageAvailable],
|
|
510
|
+
]);
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* NotebookEditTool — read and edit Jupyter notebook (.ipynb) cells.
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolDefinition } from "../../types.js";
|
|
5
|
+
import type { ToolContext } from "../context.js";
|
|
5
6
|
export declare const notebookEditToolDef: ToolDefinition;
|
|
6
|
-
export declare function notebookEditTool(args: Record<string, unknown
|
|
7
|
+
export declare function notebookEditTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* NotebookEditTool — read and edit Jupyter notebook (.ipynb) cells.
|
|
3
3
|
*/
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { enforcePathPolicy } from "../path-policy.js";
|
|
5
6
|
export const notebookEditToolDef = {
|
|
6
7
|
name: "NotebookEdit",
|
|
7
8
|
description: "Edit a Jupyter notebook (.ipynb) file. Supports actions: " +
|
|
@@ -36,13 +37,19 @@ export const notebookEditToolDef = {
|
|
|
36
37
|
required: ["file_path", "action"],
|
|
37
38
|
},
|
|
38
39
|
};
|
|
39
|
-
export async function notebookEditTool(args) {
|
|
40
|
+
export async function notebookEditTool(args, ctx) {
|
|
40
41
|
const filePath = args.file_path;
|
|
41
42
|
const action = args.action;
|
|
42
43
|
if (!filePath)
|
|
43
44
|
return "Error: file_path is required";
|
|
44
45
|
if (!filePath.endsWith(".ipynb"))
|
|
45
46
|
return "Error: file must be a .ipynb file";
|
|
47
|
+
// Path policy: 'read' goes through the read-side rules (sensitive paths
|
|
48
|
+
// still ask), all other actions are writes and use the stricter rules.
|
|
49
|
+
const op = action === "read" ? "read" : "write";
|
|
50
|
+
const blocked = enforcePathPolicy(filePath, op, ctx?.cwd);
|
|
51
|
+
if (blocked)
|
|
52
|
+
return blocked;
|
|
46
53
|
if (action === "read") {
|
|
47
54
|
if (!existsSync(filePath))
|
|
48
55
|
return `Error: File not found: ${filePath}`;
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* Built-in Read file tool.
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolDefinition } from "../../types.js";
|
|
5
|
+
import type { ToolContext } from "../context.js";
|
|
5
6
|
export declare const readToolDef: ToolDefinition;
|
|
6
|
-
export declare function readTool(args: Record<string, unknown
|
|
7
|
+
export declare function readTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readFile, stat } from "node:fs/promises";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { fileCache } from "./file-cache.js";
|
|
7
|
+
import { enforcePathPolicy } from "../path-policy.js";
|
|
7
8
|
export const readToolDef = {
|
|
8
9
|
name: "Read",
|
|
9
10
|
description: "Read a file from the local filesystem. Returns the file content with line numbers. " +
|
|
@@ -26,10 +27,17 @@ export const readToolDef = {
|
|
|
26
27
|
},
|
|
27
28
|
};
|
|
28
29
|
const MAX_CONTENT_CHARS = 200_000;
|
|
29
|
-
export async function readTool(args) {
|
|
30
|
+
export async function readTool(args, ctx) {
|
|
30
31
|
const filePath = args.file_path;
|
|
31
32
|
if (!filePath)
|
|
32
33
|
return "Error: file_path is required";
|
|
34
|
+
// Path policy: in-workspace reads pass through; sensitive paths (~/.ssh,
|
|
35
|
+
// .env, etc.) and outside-workspace reads are refused with an explanatory
|
|
36
|
+
// error. existsSync check stays AFTER the policy gate — we don't want to
|
|
37
|
+
// probe sensitive paths' existence as a side-channel.
|
|
38
|
+
const blocked = enforcePathPolicy(filePath, "read", ctx?.cwd);
|
|
39
|
+
if (blocked)
|
|
40
|
+
return blocked;
|
|
33
41
|
if (!existsSync(filePath))
|
|
34
42
|
return `Error: File not found: ${filePath}`;
|
|
35
43
|
try {
|
|
@@ -31,16 +31,34 @@ export async function skillTool(args, ctx) {
|
|
|
31
31
|
if (!skillName) {
|
|
32
32
|
return "Error: skill name is required.";
|
|
33
33
|
}
|
|
34
|
-
// Reject disabled skills before scanning so the user gets a
|
|
35
|
-
// message that distinguishes "disabled" from "not found" —
|
|
36
|
-
// the UI's toggle semantics. The scanner would also filter
|
|
37
|
-
// out, which alone would produce a misleading "not
|
|
34
|
+
// Reject disabled skills/plugins before scanning so the user gets a
|
|
35
|
+
// clear message that distinguishes "disabled" from "not found" —
|
|
36
|
+
// matches the UI's toggle semantics. The scanner would also filter
|
|
37
|
+
// these entries out, which alone would produce a misleading "not
|
|
38
|
+
// found" reply.
|
|
39
|
+
//
|
|
40
|
+
// Ordering: per-skill check fires BEFORE plugin-level check so the
|
|
41
|
+
// more-specific match wins. Both produce distinct error strings so
|
|
42
|
+
// ordering is observable but not load-bearing.
|
|
38
43
|
const disabledSkills = ctx?.disabledSkills;
|
|
44
|
+
const disabledPlugins = ctx?.disabledPlugins;
|
|
39
45
|
if (disabledSkills && disabledSkills.includes(skillName)) {
|
|
40
46
|
return `Skill "${skillName}" is disabled. Enable it in Customize or remove it from settings.disabledSkills.`;
|
|
41
47
|
}
|
|
48
|
+
if (disabledPlugins && disabledPlugins.length > 0) {
|
|
49
|
+
const colon = skillName.indexOf(":");
|
|
50
|
+
if (colon > 0) {
|
|
51
|
+
const pluginName = skillName.slice(0, colon);
|
|
52
|
+
if (disabledPlugins.includes(pluginName)) {
|
|
53
|
+
return `Skill "${skillName}" is in disabled plugin "${pluginName}". Enable the plugin in Customize or remove "${pluginName}" from settings.disabledPlugins.`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
42
57
|
// A4: scan skills from the Engine's cwd, not the host process cwd.
|
|
43
|
-
const skills = scanSkills(ctx?.cwd ?? process.cwd(), {
|
|
58
|
+
const skills = scanSkills(ctx?.cwd ?? process.cwd(), {
|
|
59
|
+
disabledSkills,
|
|
60
|
+
disabledPlugins,
|
|
61
|
+
});
|
|
44
62
|
const found = skills.find((s) => s.name === skillName);
|
|
45
63
|
if (!found) {
|
|
46
64
|
return `Skill "${skillName}" not found. Run /skills to list available skills.`;
|
|
@@ -23,11 +23,17 @@ export async function sleepTool(args) {
|
|
|
23
23
|
if (signal?.aborted)
|
|
24
24
|
return "Sleep aborted.";
|
|
25
25
|
await new Promise((resolve, reject) => {
|
|
26
|
-
const
|
|
27
|
-
signal?.addEventListener("abort", () => {
|
|
26
|
+
const onAbort = () => {
|
|
28
27
|
clearTimeout(timer);
|
|
29
28
|
reject(new Error("Sleep aborted"));
|
|
30
|
-
}
|
|
29
|
+
};
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
// Remove the abort listener on normal completion — otherwise every Sleep
|
|
32
|
+
// call leaks a listener on the (shared, per-turn) signal.
|
|
33
|
+
signal?.removeEventListener("abort", onAbort);
|
|
34
|
+
resolve();
|
|
35
|
+
}, seconds * 1000);
|
|
36
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
31
37
|
}).catch(() => { });
|
|
32
38
|
return `Slept for ${seconds} seconds.`;
|
|
33
39
|
}
|
|
@@ -30,7 +30,10 @@ export const todoWriteToolDef = {
|
|
|
30
30
|
name: "TodoWrite",
|
|
31
31
|
description: "Manage the session todo list. Pass the COMPLETE list each call — there's no per-item update API, you rewrite the entire snapshot. " +
|
|
32
32
|
"Use proactively for tasks with 3+ steps. Exactly one item should be in_progress at a time. " +
|
|
33
|
-
"Always provide both `content` (imperative) and `activeForm` (present continuous) so the UI can show what's happening right now."
|
|
33
|
+
"Always provide both `content` (imperative) and `activeForm` (present continuous) so the UI can show what's happening right now. " +
|
|
34
|
+
"Skip the todo list for straightforward tasks (roughly the easiest 25%) and don't make single-step lists. " +
|
|
35
|
+
"Update the list immediately after finishing a sub-task — mark it completed in the same turn you completed it, don't batch updates across turns. " +
|
|
36
|
+
"If you change direction and a planned item will no longer be done, rewrite the list with that item dropped rather than leaving it pending; the list must reflect what you're actually going to do next.",
|
|
34
37
|
inputSchema: {
|
|
35
38
|
type: "object",
|
|
36
39
|
properties: {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpdateAutomationMemory — lets an automation (cron/scheduled) run persist a
|
|
3
|
+
* one-paragraph summary of what it did, so the NEXT run starts with context.
|
|
4
|
+
*
|
|
5
|
+
* This is a FACTORY: core must not depend on desktop's filesystem layer, so the
|
|
6
|
+
* actual persistence is supplied by an injected `sink` callback. A later task
|
|
7
|
+
* (the desktop automation runner) injects a sink that appends to the task's
|
|
8
|
+
* memory.md; this module only builds the tool + validates input.
|
|
9
|
+
*/
|
|
10
|
+
import type { BuiltinTool } from "./index.js";
|
|
11
|
+
import type { ToolDefinition } from "../../types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Bare tool definition (name/description/inputSchema) — mirrors the export
|
|
14
|
+
* style of the other builtin tool defs (e.g. cron.ts). The factory below wraps
|
|
15
|
+
* this into a registry-ready RegisteredTool.
|
|
16
|
+
*/
|
|
17
|
+
export declare const updateAutomationMemoryToolDef: ToolDefinition;
|
|
18
|
+
/**
|
|
19
|
+
* Build an UpdateAutomationMemory tool bound to a persistence `sink`.
|
|
20
|
+
*
|
|
21
|
+
* @param sink Called with the trimmed summary when a non-empty summary is given.
|
|
22
|
+
* The caller decides where/how to persist it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function makeUpdateAutomationMemoryTool(sink: (summary: string) => void): BuiltinTool;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpdateAutomationMemory — lets an automation (cron/scheduled) run persist a
|
|
3
|
+
* one-paragraph summary of what it did, so the NEXT run starts with context.
|
|
4
|
+
*
|
|
5
|
+
* This is a FACTORY: core must not depend on desktop's filesystem layer, so the
|
|
6
|
+
* actual persistence is supplied by an injected `sink` callback. A later task
|
|
7
|
+
* (the desktop automation runner) injects a sink that appends to the task's
|
|
8
|
+
* memory.md; this module only builds the tool + validates input.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Bare tool definition (name/description/inputSchema) — mirrors the export
|
|
12
|
+
* style of the other builtin tool defs (e.g. cron.ts). The factory below wraps
|
|
13
|
+
* this into a registry-ready RegisteredTool.
|
|
14
|
+
*/
|
|
15
|
+
export const updateAutomationMemoryToolDef = {
|
|
16
|
+
name: "UpdateAutomationMemory",
|
|
17
|
+
description: "Persist a short memory note for this automation task so your NEXT scheduled run " +
|
|
18
|
+
"starts with context. Call this EXACTLY ONCE, at the very end of the run, with a " +
|
|
19
|
+
"concise one-paragraph summary of what you did, what you found, and anything the " +
|
|
20
|
+
"next run should know or continue (e.g. items processed, last cursor/timestamp, " +
|
|
21
|
+
"open issues). Keep it to a few sentences — it is prepended to your future prompts.",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
summary: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "A concise one-paragraph summary of this run for the next run to build on.",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ["summary"],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Build an UpdateAutomationMemory tool bound to a persistence `sink`.
|
|
35
|
+
*
|
|
36
|
+
* @param sink Called with the trimmed summary when a non-empty summary is given.
|
|
37
|
+
* The caller decides where/how to persist it.
|
|
38
|
+
*/
|
|
39
|
+
export function makeUpdateAutomationMemoryTool(sink) {
|
|
40
|
+
return {
|
|
41
|
+
definition: {
|
|
42
|
+
...updateAutomationMemoryToolDef,
|
|
43
|
+
source: "builtin",
|
|
44
|
+
// The summary is automation-internal bookkeeping (not user code); no prompt.
|
|
45
|
+
permissionDefault: "allow",
|
|
46
|
+
// Writes (via the injected sink), so not read-only / not concurrency-safe.
|
|
47
|
+
isReadOnly: false,
|
|
48
|
+
isConcurrencySafe: false,
|
|
49
|
+
},
|
|
50
|
+
execute: async (args) => {
|
|
51
|
+
const raw = typeof args.summary === "string" ? args.summary : "";
|
|
52
|
+
const summary = raw.trim();
|
|
53
|
+
if (!summary) {
|
|
54
|
+
return "Error: summary is required and must not be empty";
|
|
55
|
+
}
|
|
56
|
+
sink(summary);
|
|
57
|
+
return "Automation memory saved for the next run.";
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in view_image tool — 把一个本地图片文件以 base64 image ContentBlock
|
|
3
|
+
* 回传进上下文,让 vision 模型「看」它(对照 codex 的 view_image)。
|
|
4
|
+
*
|
|
5
|
+
* 典型用法:模型先写 SVG/Mermaid 并用 shell 转成 PNG,再调 view_image(png)
|
|
6
|
+
* 检查图画对没有(标签是否重叠、文字是否溢出),不对就改源再重转。
|
|
7
|
+
*
|
|
8
|
+
* 三道闸门,避免污染上下文 / 浪费 token:
|
|
9
|
+
* 1. vision gate —— 当前模型不支持视觉时不读文件,只回文字占位。
|
|
10
|
+
* 2. 格式 gate —— 只支持 png/jpeg/gif/webp(provider image 块能吃的);
|
|
11
|
+
* svg/pdf 等回文字提示「先转 PNG」。
|
|
12
|
+
* 3. 大小 gate —— 超过 MAX_BYTES 回文字提示,不读进 base64。
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolDefinition } from "../../types.js";
|
|
15
|
+
import type { ToolContext } from "../context.js";
|
|
16
|
+
import type { BuiltinToolResult } from "./index.js";
|
|
17
|
+
export declare const viewImageToolDef: ToolDefinition;
|
|
18
|
+
export declare function viewImageTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<BuiltinToolResult>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in view_image tool — 把一个本地图片文件以 base64 image ContentBlock
|
|
3
|
+
* 回传进上下文,让 vision 模型「看」它(对照 codex 的 view_image)。
|
|
4
|
+
*
|
|
5
|
+
* 典型用法:模型先写 SVG/Mermaid 并用 shell 转成 PNG,再调 view_image(png)
|
|
6
|
+
* 检查图画对没有(标签是否重叠、文字是否溢出),不对就改源再重转。
|
|
7
|
+
*
|
|
8
|
+
* 三道闸门,避免污染上下文 / 浪费 token:
|
|
9
|
+
* 1. vision gate —— 当前模型不支持视觉时不读文件,只回文字占位。
|
|
10
|
+
* 2. 格式 gate —— 只支持 png/jpeg/gif/webp(provider image 块能吃的);
|
|
11
|
+
* svg/pdf 等回文字提示「先转 PNG」。
|
|
12
|
+
* 3. 大小 gate —— 超过 MAX_BYTES 回文字提示,不读进 base64。
|
|
13
|
+
*/
|
|
14
|
+
import { readFile, stat } from "node:fs/promises";
|
|
15
|
+
import { extname, isAbsolute, resolve } from "node:path";
|
|
16
|
+
import { capabilitiesFor } from "../../llm/capabilities/index.js";
|
|
17
|
+
const MAX_BYTES = 5 * 1024 * 1024; // 5MB —— vision 模型按 tile 计 token,大图无益且撑大请求
|
|
18
|
+
const MEDIA_TYPES = {
|
|
19
|
+
".png": "image/png",
|
|
20
|
+
".jpg": "image/jpeg",
|
|
21
|
+
".jpeg": "image/jpeg",
|
|
22
|
+
".gif": "image/gif",
|
|
23
|
+
".webp": "image/webp",
|
|
24
|
+
};
|
|
25
|
+
export const viewImageToolDef = {
|
|
26
|
+
name: "view_image",
|
|
27
|
+
description: "Load a local image file into the conversation so you can SEE it (vision). " +
|
|
28
|
+
"Use after generating or rendering an image (e.g. SVG/Mermaid → PNG) to verify it " +
|
|
29
|
+
"looks right — check for overlapping labels, clipped text, wrong layout — then fix the " +
|
|
30
|
+
"source and re-render if needed. Supports PNG/JPEG/GIF/WebP only; convert SVG/PDF to PNG " +
|
|
31
|
+
"first. Requires a vision-capable model; otherwise the image is skipped.",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
path: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Path to the image file (absolute, or relative to the working directory).",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ["path"],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export async function viewImageTool(args, ctx) {
|
|
44
|
+
const rawPath = args.path;
|
|
45
|
+
if (typeof rawPath !== "string" || !rawPath.trim()) {
|
|
46
|
+
return "Error: path is required";
|
|
47
|
+
}
|
|
48
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
49
|
+
const abs = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
|
|
50
|
+
// 闸门 1:vision gate —— 不支持视觉就不读文件。
|
|
51
|
+
// 缺 llmConfig 时按「非视觉」处理(fail closed),对齐 DEFAULT_CAPABILITY
|
|
52
|
+
// 的保守姿态:运行时 ToolContext.llmConfig 必有,缺失只发生在测试 / 异常
|
|
53
|
+
// 装配下,此时绝不把 base64 读进上下文。
|
|
54
|
+
const supportsVision = (() => {
|
|
55
|
+
if (!ctx?.llmConfig)
|
|
56
|
+
return false;
|
|
57
|
+
const kind = (ctx.llmConfig.providerKind ?? ctx.llmConfig.provider);
|
|
58
|
+
return capabilitiesFor(kind, ctx.llmConfig.model).supportsVision;
|
|
59
|
+
})();
|
|
60
|
+
if (!supportsVision) {
|
|
61
|
+
return `[图片未加载: ${abs} —— 当前模型不支持视觉输入,已跳过。切换到 vision 模型后再 view_image。]`;
|
|
62
|
+
}
|
|
63
|
+
// 闸门 2:格式 gate
|
|
64
|
+
const ext = extname(abs).toLowerCase();
|
|
65
|
+
const mediaType = MEDIA_TYPES[ext];
|
|
66
|
+
if (!mediaType) {
|
|
67
|
+
return `[图片未加载: ${abs} —— 格式 ${ext || "(无扩展名)"} 不支持视觉预览,请先转成 PNG/JPEG。]`;
|
|
68
|
+
}
|
|
69
|
+
// 闸门 3:大小 gate(读 stat,不读全文件)
|
|
70
|
+
let size;
|
|
71
|
+
try {
|
|
72
|
+
size = (await stat(abs)).size;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
return `Error: 无法读取 ${abs}: ${err.message}`;
|
|
76
|
+
}
|
|
77
|
+
if (size > MAX_BYTES) {
|
|
78
|
+
const mb = (size / 1024 / 1024).toFixed(1);
|
|
79
|
+
return `[图片未加载: ${abs} —— 文件过大 (${mb} MB > 5 MB),请先压缩或缩放再 view_image。]`;
|
|
80
|
+
}
|
|
81
|
+
let buf;
|
|
82
|
+
try {
|
|
83
|
+
buf = await readFile(abs);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
return `Error: 无法读取 ${abs}: ${err.message}`;
|
|
87
|
+
}
|
|
88
|
+
const data = buf.toString("base64");
|
|
89
|
+
const kb = Math.round(buf.length / 1024);
|
|
90
|
+
return {
|
|
91
|
+
contentBlocks: [
|
|
92
|
+
{ type: "image", source: { type: "base64", media_type: mediaType, data } },
|
|
93
|
+
],
|
|
94
|
+
result: `[已加载图片: ${abs} (${mediaType}, ${kb} KB)]`,
|
|
95
|
+
};
|
|
96
|
+
}
|