slidev-addon-agent 0.0.1
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/LICENSE +21 -0
- package/README.md +54 -0
- package/agent/constants.ts +119 -0
- package/agent/deck-context.ts +67 -0
- package/agent/index.ts +201 -0
- package/agent/middleware.ts +163 -0
- package/agent/skills/slidev/README.md +61 -0
- package/agent/skills/slidev/SKILL.md +189 -0
- package/agent/skills/slidev/references/animation-click-marker.md +37 -0
- package/agent/skills/slidev/references/animation-drawing.md +68 -0
- package/agent/skills/slidev/references/animation-rough-marker.md +53 -0
- package/agent/skills/slidev/references/api-slide-hooks.md +37 -0
- package/agent/skills/slidev/references/build-og-image.md +36 -0
- package/agent/skills/slidev/references/build-pdf.md +40 -0
- package/agent/skills/slidev/references/build-remote-assets.md +34 -0
- package/agent/skills/slidev/references/build-seo-meta.md +43 -0
- package/agent/skills/slidev/references/code-groups.md +64 -0
- package/agent/skills/slidev/references/code-import-snippet.md +55 -0
- package/agent/skills/slidev/references/code-line-highlighting.md +50 -0
- package/agent/skills/slidev/references/code-line-numbers.md +46 -0
- package/agent/skills/slidev/references/code-magic-move.md +57 -0
- package/agent/skills/slidev/references/code-max-height.md +37 -0
- package/agent/skills/slidev/references/code-twoslash.md +42 -0
- package/agent/skills/slidev/references/core-animations.md +196 -0
- package/agent/skills/slidev/references/core-cli.md +140 -0
- package/agent/skills/slidev/references/core-components.md +197 -0
- package/agent/skills/slidev/references/core-exporting.md +148 -0
- package/agent/skills/slidev/references/core-frontmatter.md +195 -0
- package/agent/skills/slidev/references/core-global-context.md +155 -0
- package/agent/skills/slidev/references/core-headmatter.md +188 -0
- package/agent/skills/slidev/references/core-hosting.md +152 -0
- package/agent/skills/slidev/references/core-layouts.md +286 -0
- package/agent/skills/slidev/references/core-syntax.md +155 -0
- package/agent/skills/slidev/references/diagram-latex.md +55 -0
- package/agent/skills/slidev/references/diagram-mermaid.md +44 -0
- package/agent/skills/slidev/references/diagram-plantuml.md +45 -0
- package/agent/skills/slidev/references/editor-monaco-run.md +44 -0
- package/agent/skills/slidev/references/editor-monaco-write.md +24 -0
- package/agent/skills/slidev/references/editor-monaco.md +50 -0
- package/agent/skills/slidev/references/editor-prettier.md +40 -0
- package/agent/skills/slidev/references/editor-side.md +23 -0
- package/agent/skills/slidev/references/editor-vscode.md +55 -0
- package/agent/skills/slidev/references/layout-canvas-size.md +25 -0
- package/agent/skills/slidev/references/layout-draggable.md +57 -0
- package/agent/skills/slidev/references/layout-global-layers.md +50 -0
- package/agent/skills/slidev/references/layout-slots.md +75 -0
- package/agent/skills/slidev/references/layout-transform.md +33 -0
- package/agent/skills/slidev/references/layout-zoom.md +39 -0
- package/agent/skills/slidev/references/presenter-notes-ruby.md +35 -0
- package/agent/skills/slidev/references/presenter-recording.md +30 -0
- package/agent/skills/slidev/references/presenter-remote.md +40 -0
- package/agent/skills/slidev/references/presenter-timer.md +34 -0
- package/agent/skills/slidev/references/style-direction.md +34 -0
- package/agent/skills/slidev/references/style-icons.md +46 -0
- package/agent/skills/slidev/references/style-scoped.md +50 -0
- package/agent/skills/slidev/references/syntax-block-frontmatter.md +39 -0
- package/agent/skills/slidev/references/syntax-frontmatter-merging.md +49 -0
- package/agent/skills/slidev/references/syntax-importing-slides.md +60 -0
- package/agent/skills/slidev/references/syntax-mdc.md +51 -0
- package/agent/skills/slidev/references/tool-eject-theme.md +27 -0
- package/agent/tools/export-tool.ts +216 -0
- package/agent/tools/review-tool.ts +136 -0
- package/app/index.ts +124 -0
- package/components/MessageItem.vue +231 -0
- package/components/SlidevAgentNavButton.vue +48 -0
- package/components/SlidevAgentSidebar.vue +766 -0
- package/components/SubagentCard.vue +184 -0
- package/components/TypingDots.vue +62 -0
- package/dist/agent/constants.js +117 -0
- package/dist/agent/deck-context.js +47 -0
- package/dist/agent/index.js +167 -0
- package/dist/agent/middleware.js +134 -0
- package/dist/agent/slide-preview-tool.js +257 -0
- package/dist/agent/tools/export-tool.js +167 -0
- package/dist/agent/tools/review-tool.js +111 -0
- package/dist/app/index.js +101 -0
- package/dist/bin/slidev-agent.js +155 -0
- package/dist/lib/bridge.js +151 -0
- package/dist/lib/env.js +17 -0
- package/dist/lib/headless-tools.js +10 -0
- package/dist/lib/langgraph-init.js +59 -0
- package/dist/lib/review-tool.js +98 -0
- package/lib/bridge.ts +212 -0
- package/lib/config.ts +79 -0
- package/lib/env.ts +38 -0
- package/lib/headless-tool-impl.ts +26 -0
- package/lib/headless-tools.ts +11 -0
- package/lib/langgraph-init.ts +79 -0
- package/lib/messages.ts +169 -0
- package/lib/render-chat-markdown.ts +19 -0
- package/lib/sidebar.ts +573 -0
- package/lib/state.ts +44 -0
- package/package.json +65 -0
- package/public/deepagents.svg +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christian Bromann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# `slidev-addon-agent`
|
|
2
|
+
|
|
3
|
+
Slidev addon components and a wrapper CLI for agent-powered slide authoring.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
- Slidev: `@slidev/cli` and `vue` (peer dependencies).
|
|
10
|
+
- LangGraph: `@langchain/langgraph` for the deep agent runtime.
|
|
11
|
+
- Chat model: install one of `@langchain/openai`, `@langchain/anthropic`, or `@langchain/google`, and set the matching API key in the environment your LangGraph server uses.
|
|
12
|
+
- Optional screenshot verification: install `playwright-chromium` in the Slidev project so the agent can export PNGs and visually review rendered slides.
|
|
13
|
+
|
|
14
|
+
### Install
|
|
15
|
+
|
|
16
|
+
In your Slidev project (or LangGraph app that bundles the agent):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# base setup
|
|
20
|
+
pnpm add slidev-addon-agent @slidev/cli vue @langchain/langgraph
|
|
21
|
+
# LLM provider
|
|
22
|
+
pnpm add @langchain/openai # or @langchain/anthropic / @langchain/google
|
|
23
|
+
# optional: PNG screenshot verification during slide review
|
|
24
|
+
pnpm add -D playwright-chromium
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Add the addon to your Slidev project and render the components from root layer files:
|
|
28
|
+
|
|
29
|
+
```vue
|
|
30
|
+
<!-- global-top.vue -->
|
|
31
|
+
<template>
|
|
32
|
+
<SlidevAgentSidebar v-if="!$nav.isPresenter" />
|
|
33
|
+
</template>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```vue
|
|
37
|
+
<!-- custom-nav-controls.vue -->
|
|
38
|
+
<template>
|
|
39
|
+
<SlidevAgentNavButton v-if="!$nav.isPresenter" />
|
|
40
|
+
</template>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Agent environment variables
|
|
44
|
+
|
|
45
|
+
The deep agent uses your project directory as the filesystem root (virtualMode) and bundled Slidev skills when those paths are reachable. Set these in `.env` (or your LangGraph deployment) for the **server** that runs the graph:
|
|
46
|
+
|
|
47
|
+
| Variable | Purpose |
|
|
48
|
+
| ---------- | --------- |
|
|
49
|
+
| `SLIDEV_AGENT_MODEL` | Model id, e.g. `openai:gpt-5.4`, `anthropic:claude-sonnet-4-6`. |
|
|
50
|
+
| `SLIDEV_AGENT_REVIEW_MODEL` | Optional override for screenshot review; defaults to `SLIDEV_AGENT_MODEL` and should be a vision-capable model. |
|
|
51
|
+
| `SLIDEV_AGENT_SYSTEM_PROMPT` | Replaces the default system prompt when set. |
|
|
52
|
+
| `SLIDEV_AGENT_ROOT_DIR` | Filesystem root for deck files (defaults to `process.cwd()`). |
|
|
53
|
+
| `SLIDEV_AGENT_SKILLS_PATH` | Comma-separated extra skill directories (POSIX paths relative to the backend root). |
|
|
54
|
+
| `SLIDEV_AGENT_DISABLE_LANGGRAPH` | Set to `1` to skip spawning `langgraph dev` alongside Slidev when using `slidev-agent dev`. |
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default system prompt for the Slidev orchestrator agent.
|
|
3
|
+
* Overridden entirely when `SLIDEV_AGENT_SYSTEM_PROMPT` is set.
|
|
4
|
+
*/
|
|
5
|
+
export const SLIDEV_AGENT_DEFAULT_SYSTEM_PROMPT = `
|
|
6
|
+
You are Slidev Agent, an expert presentation author for Slidev decks.
|
|
7
|
+
|
|
8
|
+
## Skill usage
|
|
9
|
+
- You have a **slidev** skill in your skills library (metadata is injected below).
|
|
10
|
+
- For non-trivial slides or Slidev features (animations, code blocks, diagrams, layouts, export), read that skill's SKILL.md with read_file first, then open specific files under its references/ folder as needed instead of guessing syntax.
|
|
11
|
+
|
|
12
|
+
## Core operating model
|
|
13
|
+
- Your job is to create and revise deck files directly in the local Slidev project filesystem.
|
|
14
|
+
- Treat the filesystem as the source of truth for deck content.
|
|
15
|
+
- When asked to make deck changes, inspect current files first.
|
|
16
|
+
|
|
17
|
+
## Path rule
|
|
18
|
+
- Filesystem tools such as \`read_file\`, \`write_file\`, and \`edit_file\` are rooted at the agent project root, not the host machine root.
|
|
19
|
+
- Use project-root-relative paths like \`/slides.md\`, \`/example/slides.md\`, or \`/pages/foo.md\`.
|
|
20
|
+
- Never pass host OS paths like \`/Users/christian/.../slides.md\` to those file tools.
|
|
21
|
+
|
|
22
|
+
## Authoring rules
|
|
23
|
+
- Prefer editing existing files over creating many new files when possible, but do not force large amounts of content into a single giant slide file.
|
|
24
|
+
- Use Slidev-compatible markdown, frontmatter, layouts, and Vue component syntax.
|
|
25
|
+
- Prefer native markdown syntax over raw HTML whenever possible.
|
|
26
|
+
- Only use HTML or custom Vue markup when markdown or built-in Slidev features cannot express the layout, because ad-hoc HTML is prone to missing closing tags and other render errors.
|
|
27
|
+
|
|
28
|
+
## Deck shell vs. slide content
|
|
29
|
+
- You edit the **deck shell** yourself, such as \`/slides.md\` imports, front matter, \`/components\`, \`/snippets\`, and shared assets.
|
|
30
|
+
- You **never** write or edit slide **content** files yourself.
|
|
31
|
+
- **Always** use the \`task\` tool with \`slide_generator\` to create or change slide content.
|
|
32
|
+
- Any new slide file, any new slide under \`/pages\` (or similar), or any change to the markdown body of an existing slide file must go through the subagent.
|
|
33
|
+
- Do not use \`write_file\`, \`edit_file\`, or equivalent on those slide bodies yourself.
|
|
34
|
+
- Spawn one \`slide_generator\` task per slide file, and parallelize when the user asks for multiple slides.
|
|
35
|
+
|
|
36
|
+
## Subagent coordination
|
|
37
|
+
- When you spin up subagents or describe that work to the user, keep it brief but playful: warm and a little theatrical (for example, assembling a tiny slide squad, handing off to specialists, or cueing the next slide), not stiff or purely procedural.
|
|
38
|
+
- Keep deck structure coherent, with a main entry file such as \`/slides.md\` and supporting slide files under \`/pages\`.
|
|
39
|
+
- Use Slidev's importing-slides pattern (\`src: ./pages/file.md\`) to stitch subagent output into the main deck instead of streaming everything into one file.
|
|
40
|
+
- The \`slide_generator\` subagent creates exactly one slide file per task, typically in \`/pages\`.
|
|
41
|
+
- After subagents finish, you are responsible for updating \`/slides.md\` (or the entry deck) to import or embed those files correctly.
|
|
42
|
+
- When multiple slides are needed, launch several \`slide_generator\` tasks in parallel rather than one long write.
|
|
43
|
+
|
|
44
|
+
## Verification and navigation
|
|
45
|
+
- After importing a new slide into \`slides.md\`, include its 1-based slide URL index in each \`slide_generator\` task so that the subagent can export a PNG screenshot of the rendered slide, visually review that image, and fix only clearly broken layout or readability issues before returning.
|
|
46
|
+
- When revising an existing slide file that is already imported into the deck, keep its existing 1-based slide index. Editing a slide does not create a new index.
|
|
47
|
+
- Only include a slide index in a \`slide_generator\` task when you know it from the fully assembled deck after imports and ordering are finalized.
|
|
48
|
+
- If there is any uncertainty about the current deck length, recount from the fully assembled deck instead of guessing or clamping the index.
|
|
49
|
+
- Keep that verification loop short: the \`slide_generator\` should do at most 2 screenshot review passes total, then stop and report any remaining limitation.
|
|
50
|
+
- Verification is intentionally lenient: minor spacing, alignment, or stylistic polish issues are acceptable. Only treat the slide as failing review when it looks broken, unreadable, or clearly misrendered.
|
|
51
|
+
- You do not run those checks yourself; render verification is the \`slide_generator\`'s responsibility.
|
|
52
|
+
- When you create a new slide and know its final 1-based index in the deck, call the tool \`slidev_go_to_slide\` so the user's Slidev preview automatically jumps to that slide.
|
|
53
|
+
- Only call \`slidev_go_to_slide\` after imports and ordering are finalized so you navigate to the correct page.
|
|
54
|
+
|
|
55
|
+
## Final response
|
|
56
|
+
- Explain major file changes briefly to the user after you finish.
|
|
57
|
+
`.trim()
|
|
58
|
+
|
|
59
|
+
/** Shown to the model for \`task\` tool selection (slide_generator). */
|
|
60
|
+
export const SLIDEV_SLIDE_GENERATOR_SUBAGENT_DESCRIPTION = `Create or redesign exactly one Slidev slide file at a requested path so the orchestrator can parallelize multi-slide work. When the slide's URL index is known, export a PNG screenshot with Slidev and use a screenshot review tool to catch clearly broken rendering or readability issues.`
|
|
61
|
+
|
|
62
|
+
/** System prompt for the slide_generator subagent. */
|
|
63
|
+
export const SLIDEV_SLIDE_GENERATOR_SYSTEM_PROMPT = `
|
|
64
|
+
You are a specialized Slidev slide generation subagent.
|
|
65
|
+
|
|
66
|
+
## Skill usage
|
|
67
|
+
- Use the **slidev** skill from your skills list: read its SKILL.md, then \`references/*.md\` for the features you implement (animations, code, diagrams, layouts).
|
|
68
|
+
|
|
69
|
+
## Scope
|
|
70
|
+
- Your responsibility is to create or revise exactly one slide file at the path requested by the orchestrator.
|
|
71
|
+
- Produce one focused slide or imported slide fragment, not the entire deck.
|
|
72
|
+
- Do not update the main deck entry file such as \`/slides.md\` unless explicitly asked; the orchestrator will stitch your slide into the deck.
|
|
73
|
+
|
|
74
|
+
## Path rule
|
|
75
|
+
- Filesystem tool paths are project-root-relative.
|
|
76
|
+
- For \`read_file\`, \`write_file\`, and \`edit_file\`, use paths like \`/example/slides.md\` or \`/pages/foo.md\`, never host OS paths like \`/Users/...\`.
|
|
77
|
+
|
|
78
|
+
## Slide design rules
|
|
79
|
+
- Slide space is limited: each slide is only a fixed viewport, so packing too much onto one frame causes clutter, tiny text, or overflow.
|
|
80
|
+
- Prefer native markdown over raw HTML for headings, lists, emphasis, tables, blockquotes, and simple layouts.
|
|
81
|
+
- Prefer a small number of ideas per slide.
|
|
82
|
+
- When you have lots of bullets, diagrams, code, or tables, split the material across multiple slides or use Slidev subslides / click-step patterns (\`v-click\` and similar) instead of one overloaded slide.
|
|
83
|
+
- Avoid cramming diagrams, dense grids, and long bullet lists onto a single slide; add more slides or subslides rather than shrinking everything to fit.
|
|
84
|
+
- Keep the output self-contained so it can be imported cleanly from the main deck with \`src: ./pages/...\`.
|
|
85
|
+
- Use valid Slidev markdown, frontmatter, layouts, components, and animations when they improve clarity.
|
|
86
|
+
- Reach for HTML or Vue markup only when markdown and built-in Slidev features are insufficient, since raw HTML often introduces missing closing tags and other brittle syntax mistakes.
|
|
87
|
+
|
|
88
|
+
## Screenshot verification
|
|
89
|
+
|
|
90
|
+
- When the task gives a 1-based slide index, you MUST verify the rendered slide before finishing. Do not skip only because the markdown looks fine.
|
|
91
|
+
- If you are revising an existing slide file that is already imported into the deck, its slide index usually stays the same.
|
|
92
|
+
- Prefer Slidev's PNG export flow over browser automation. This does not require the dev server to be open in a browser tab.
|
|
93
|
+
- You have a dedicated tool named \`slidev_export_screenshot\`, which exports the requested slide to a PNG from the correct deck directory. Use that tool as the default export path instead of composing shell commands yourself.
|
|
94
|
+
- You also have the tool \`slidev_review_screenshot\`, which sends the exported image to a multimodal model for actual visual review. Use that tool instead of claiming you inspected a local PNG directly in text.
|
|
95
|
+
- You also have the standard \`execute\` tool. Use \`execute\` for screenshot export only as a debugging fallback if \`slidev_export_screenshot\` itself fails.
|
|
96
|
+
- **Prerequisites:** \`slidev-agent\` / Slidev CLI available from the project root, and \`playwright-chromium\` installed where Slidev can resolve it. If PNG export fails because Playwright is missing, say that explicitly and ask for \`pnpm add -D playwright-chromium\`.
|
|
97
|
+
- This review is a coarse quality gate, not pixel-perfect design critique. Ignore minor spacing, alignment, or aesthetic polish issues unless they make the slide look broken or hard to read.
|
|
98
|
+
- Only fail verification for obvious breakage such as clipped or missing content, severe overflow, overlapping elements, unreadably tiny text, broken wrapping, or other rendering problems that materially hurt readability.
|
|
99
|
+
|
|
100
|
+
Typical sequence:
|
|
101
|
+
|
|
102
|
+
1. Call \`slidev_export_screenshot\` with the slide index. Omit \`outputDir\` unless you need a special project-local folder.
|
|
103
|
+
2. Pass the returned PNG path to \`slidev_review_screenshot\`.
|
|
104
|
+
3. Treat the review tool's result as the visual review. Focus on obvious breakage such as clipped content, severe overflow, overlapping elements, unreadably tiny text, broken wrapping, missing content, or contrast problems that make the slide hard to read.
|
|
105
|
+
4. If the tool reports only minor stylistic issues, accept the slide as passing. If it reports clear breakage, revise the slide and re-run export and screenshot review at most once. Do no more than 2 screenshot review passes total, then stop and explain any remaining limitation instead of looping further.
|
|
106
|
+
|
|
107
|
+
## Verification edge cases
|
|
108
|
+
- Counting rule: the screenshot tool expects the slide's final 1-based URL index in the fully assembled deck. Recount after imports or ordering changes; do not assume a new file automatically means "the next slide number".
|
|
109
|
+
- If \`slidev_export_screenshot\` reports that the deck has fewer slides than requested, or that it produced no PNGs, stop and report an index/import mismatch instead of retrying the same export loop.
|
|
110
|
+
- Important path rule for debugging: filesystem tools and shell paths are different. For file tools, \`/pages/foo.md\` or \`/slides.md\` means a path relative to the agent project root. For \`execute\`, those same strings are interpreted as host filesystem paths and are usually wrong. If you must use \`execute\`, stay in the current deck working directory, use cwd-relative paths like \`./.slidev-agent-artifacts/...\`, and never use \`/.slidev-agent-artifacts\`, \`/pages\`, or \`pnpm --prefix /\` unless the project truly lives at filesystem root.
|
|
111
|
+
- If the screenshot review tool fails because no review model is configured or the model cannot accept images, still generate the PNG and say that screenshot export succeeded but AI visual review was unavailable in this environment.
|
|
112
|
+
- If the slide is not yet imported into \`slides.md\` or no slide number is given, skip screenshot verification and say so; ask the orchestrator to import it and re-invoke you with the 1-based index.
|
|
113
|
+
|
|
114
|
+
## Final report
|
|
115
|
+
- When finished, report the file path you touched, a concise description of the slide, and whether screenshot verification passed, was skipped, or only exported an image without visual review.
|
|
116
|
+
- If you stopped because the 2-pass review limit was reached, say that explicitly.
|
|
117
|
+
- If verification ran, report which slide index you checked and whether it passed.
|
|
118
|
+
- If you know the index but skipped verification, say that explicitly instead of inventing a preview link.
|
|
119
|
+
`.trim()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
import { env } from "../lib/env.js"
|
|
5
|
+
|
|
6
|
+
type PackageJsonWithScripts = {
|
|
7
|
+
scripts?: Record<string, unknown>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function normalizeProjectRelativePath(filePath: string) {
|
|
11
|
+
const normalized = filePath.replaceAll(path.sep, "/").replace(/^\/+/, "")
|
|
12
|
+
return normalized || "."
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readPackageJson(packageJsonPath: string): PackageJsonWithScripts | null {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as PackageJsonWithScripts
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readPackageScript(rootDir: string, scriptName: string) {
|
|
25
|
+
const packageJson = readPackageJson(path.join(rootDir, "package.json"))
|
|
26
|
+
const script = packageJson?.scripts?.[scriptName]
|
|
27
|
+
return typeof script === "string" && script.trim() ? script.trim() : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function extractWorkingDirFromScript(script: string) {
|
|
31
|
+
const pnpmDirMatch = script.match(/(?:^|\s)pnpm\s+(?:--dir|-C)\s+(?:"([^"]+)"|'([^']+)'|([^\s]+))/)
|
|
32
|
+
const cdMatch = script.match(/(?:^|\s)cd\s+(?:"([^"]+)"|'([^']+)'|([^\s;&]+))/)
|
|
33
|
+
const workingDir = pnpmDirMatch?.[1] || pnpmDirMatch?.[2] || pnpmDirMatch?.[3] || cdMatch?.[1] || cdMatch?.[2] || cdMatch?.[3]
|
|
34
|
+
return workingDir ? normalizeProjectRelativePath(workingDir) : null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type SlidevDeckExecutionContext = {
|
|
38
|
+
rootDir: string
|
|
39
|
+
configuredEntry: string
|
|
40
|
+
deckDir: string
|
|
41
|
+
deckHostDir: string
|
|
42
|
+
entryPath: string
|
|
43
|
+
rootExportScript: string | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveDeckExecutionContext(rootDir: string): SlidevDeckExecutionContext {
|
|
47
|
+
const configuredEntry = normalizeProjectRelativePath(env(process.env, "SLIDEV_AGENT_ENTRY", "slides.md"))
|
|
48
|
+
const configuredEntryDir = path.posix.dirname(configuredEntry)
|
|
49
|
+
const rootExportScript = readPackageScript(rootDir, "export")
|
|
50
|
+
const inferredDeckDir = rootExportScript ? extractWorkingDirFromScript(rootExportScript) : null
|
|
51
|
+
const deckDir = configuredEntryDir !== "." ? configuredEntryDir : inferredDeckDir || "."
|
|
52
|
+
const deckHostDir = deckDir === "."
|
|
53
|
+
? rootDir
|
|
54
|
+
: path.resolve(rootDir, deckDir)
|
|
55
|
+
const entryPath = configuredEntryDir !== "."
|
|
56
|
+
? path.resolve(rootDir, configuredEntry)
|
|
57
|
+
: path.resolve(deckHostDir, configuredEntry)
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
rootDir,
|
|
61
|
+
configuredEntry,
|
|
62
|
+
deckDir,
|
|
63
|
+
deckHostDir,
|
|
64
|
+
entryPath,
|
|
65
|
+
rootExportScript,
|
|
66
|
+
}
|
|
67
|
+
}
|
package/agent/index.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
|
|
5
|
+
import { createDeepAgent, LocalShellBackend } from "deepagents"
|
|
6
|
+
import { dynamicSystemPromptMiddleware } from "langchain"
|
|
7
|
+
import { z } from "zod"
|
|
8
|
+
|
|
9
|
+
import { createFilesystemPathGuardMiddleware } from "./middleware.js"
|
|
10
|
+
import { model, env } from "../lib/env.js"
|
|
11
|
+
import { slidevGoToSlide } from "../lib/headless-tools.js"
|
|
12
|
+
|
|
13
|
+
import { resolveDeckExecutionContext } from "./deck-context.js"
|
|
14
|
+
import { createSlidevExportScreenshotTool } from "./tools/export-tool.js"
|
|
15
|
+
import { createSlidevReviewScreenshotTool } from "./tools/review-tool.js"
|
|
16
|
+
import {
|
|
17
|
+
SLIDEV_AGENT_DEFAULT_SYSTEM_PROMPT,
|
|
18
|
+
SLIDEV_SLIDE_GENERATOR_SUBAGENT_DESCRIPTION,
|
|
19
|
+
SLIDEV_SLIDE_GENERATOR_SYSTEM_PROMPT,
|
|
20
|
+
} from "./constants.js"
|
|
21
|
+
|
|
22
|
+
type SlidevDeepAgentOptions = {
|
|
23
|
+
model?: string
|
|
24
|
+
systemPrompt?: string
|
|
25
|
+
rootDir?: string
|
|
26
|
+
virtualMode?: boolean
|
|
27
|
+
skills?: string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const slidevAgentContextSchema = z.object({
|
|
31
|
+
currentSlide: z.object({
|
|
32
|
+
page: z.number().optional(),
|
|
33
|
+
layout: z.string().optional(),
|
|
34
|
+
route: z.string().optional(),
|
|
35
|
+
title: z.string().optional(),
|
|
36
|
+
}).optional(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
function resolveSystemPrompt(): string {
|
|
40
|
+
const customPrompt = env(process.env, "SLIDEV_AGENT_SYSTEM_PROMPT")
|
|
41
|
+
if (customPrompt)
|
|
42
|
+
return customPrompt
|
|
43
|
+
|
|
44
|
+
return SLIDEV_AGENT_DEFAULT_SYSTEM_PROMPT
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeSkillPath(skillPath: string): string {
|
|
48
|
+
if (!skillPath)
|
|
49
|
+
return skillPath
|
|
50
|
+
|
|
51
|
+
const normalized = skillPath.replaceAll(path.sep, "/")
|
|
52
|
+
return normalized.startsWith("/") ? normalized : `/${normalized}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveSlidevSkillPaths(rootDir: string): string[] {
|
|
56
|
+
const explicitSkillsPath = env(process.env, "SLIDEV_AGENT_SKILLS_PATH")
|
|
57
|
+
if (explicitSkillsPath) {
|
|
58
|
+
return explicitSkillsPath
|
|
59
|
+
.split(",")
|
|
60
|
+
.map(entry => entry.trim())
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.map(normalizeSkillPath)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const packagedSkillsDir = fileURLToPath(new URL("./skills", import.meta.url))
|
|
66
|
+
const relativeSkillsDir = path.relative(rootDir, packagedSkillsDir)
|
|
67
|
+
|
|
68
|
+
if (relativeSkillsDir.startsWith(".."))
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
return [normalizeSkillPath(relativeSkillsDir)]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildSlideGeneratorRuntimePrompt(rootDir: string) {
|
|
75
|
+
const deckContext = resolveDeckExecutionContext(rootDir)
|
|
76
|
+
const deckHostDir = deckContext.deckHostDir
|
|
77
|
+
const entryPath = deckContext.entryPath
|
|
78
|
+
const artifactDir = path.join(deckHostDir, ".slidev-agent-artifacts", "verify-<slideIndex>")
|
|
79
|
+
const generatedImagePath = path.join(artifactDir, "<generated-file>.png")
|
|
80
|
+
const reviewImageRelativePath = deckContext.deckDir === "."
|
|
81
|
+
? `.slidev-agent-artifacts/verify-<slideIndex>/<generated-file>.png`
|
|
82
|
+
: `${deckContext.deckDir}/.slidev-agent-artifacts/verify-<slideIndex>/<generated-file>.png`
|
|
83
|
+
const directExportCommand = [
|
|
84
|
+
`cd "${deckHostDir}"`,
|
|
85
|
+
"mkdir -p .slidev-agent-artifacts/verify-<slideIndex> &&",
|
|
86
|
+
"pnpm exec slidev-agent export",
|
|
87
|
+
"--format png",
|
|
88
|
+
"--range <slideIndex>",
|
|
89
|
+
"--per-slide",
|
|
90
|
+
"--output ./.slidev-agent-artifacts/verify-<slideIndex>",
|
|
91
|
+
"--timeout 60000",
|
|
92
|
+
"--wait 500",
|
|
93
|
+
"--scale 1",
|
|
94
|
+
].join(" ")
|
|
95
|
+
const rootScriptExportCommand = deckContext.rootExportScript
|
|
96
|
+
? [
|
|
97
|
+
`cd "${rootDir}"`,
|
|
98
|
+
"pnpm export --",
|
|
99
|
+
"--format png",
|
|
100
|
+
"--range <slideIndex>",
|
|
101
|
+
"--per-slide",
|
|
102
|
+
"--output ./.slidev-agent-artifacts/verify-<slideIndex>",
|
|
103
|
+
"--timeout 60000",
|
|
104
|
+
"--wait 500",
|
|
105
|
+
"--scale 1",
|
|
106
|
+
].join(" ")
|
|
107
|
+
: null
|
|
108
|
+
|
|
109
|
+
const lines = [
|
|
110
|
+
"## Runtime deck execution context",
|
|
111
|
+
"Filesystem tools are rooted at the agent project root. Use project-relative paths like `/example/slides.md` there, never host paths like `/Users/.../example/slides.md`.",
|
|
112
|
+
`Inside \`execute\`, paths like \`/\` refer to the real host filesystem root. Do not treat \`/\` as a virtual project root.`,
|
|
113
|
+
`Agent root for this run: \`${rootDir}\`.`,
|
|
114
|
+
`Configured Slidev entry for this run: \`${entryPath}\`.`,
|
|
115
|
+
`Runnable deck package directory for exports: \`${deckHostDir}\`. Always run export there unless you are intentionally invoking a wrapper script from \`${rootDir}\`.`,
|
|
116
|
+
"For screenshot export, use the dedicated `slidev_export_screenshot` tool first instead of hand-writing shell commands.",
|
|
117
|
+
"If there is any uncertainty about the current assembled deck length after imports or reordering, recount from the fully assembled deck before choosing a validation index. Do not guess or clamp the index.",
|
|
118
|
+
"When revising an existing slide file that is already imported into the deck, its validation index usually stays the same. Do not add 1 just because you edited the file.",
|
|
119
|
+
`If you need to debug manually, this is the known-good fallback command: \`${directExportCommand}\`.`,
|
|
120
|
+
rootScriptExportCommand
|
|
121
|
+
? `Optional root-package wrapper command: \`${rootScriptExportCommand}\`. Use it only if you specifically want the root package script.`
|
|
122
|
+
: "",
|
|
123
|
+
"The `execute` shell starts in the deck package directory already. Keep export commands cwd-relative and do not `cd /` before exporting.",
|
|
124
|
+
`Write export artifacts under \`${artifactDir}\` using a relative path like \`./.slidev-agent-artifacts/...\` or that full host path.`,
|
|
125
|
+
"Never use `/.slidev-agent-artifacts`, `pnpm --prefix /`, or any other host-root path unless the project itself actually lives at filesystem root.",
|
|
126
|
+
"Logical project paths such as `/pages/foo.md` or `/slides.md` are for filesystem tools, not shell paths. For `execute`, strip the leading slash and use cwd-relative paths instead.",
|
|
127
|
+
`When calling \`slidev_review_screenshot\`, prefer the real host PNG path \`${generatedImagePath}\`. A root-relative alternative within the agent root is \`${reviewImageRelativePath}\`.`,
|
|
128
|
+
"If screenshot export says the deck has fewer slides than requested, or says it produced no PNGs, your slide index is wrong or the slide is not imported into the final deck yet. Fix the deck/import/index mismatch before retrying.",
|
|
129
|
+
"Do not scan `/Users`, `/node_modules`, or the whole filesystem to locate Slidev once the deck context above is available.",
|
|
130
|
+
].filter(Boolean)
|
|
131
|
+
|
|
132
|
+
return lines.join("\n")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function createSlidevDeepAgent(options: SlidevDeepAgentOptions = {}) {
|
|
136
|
+
const systemPrompt = options.systemPrompt || resolveSystemPrompt()
|
|
137
|
+
const rootDir = options.rootDir
|
|
138
|
+
? path.resolve(options.rootDir)
|
|
139
|
+
: path.resolve(env(process.env, "SLIDEV_AGENT_ROOT_DIR") || process.cwd())
|
|
140
|
+
|
|
141
|
+
const skills = options.skills || resolveSlidevSkillPaths(rootDir)
|
|
142
|
+
const filesystemPathGuardMiddleware = createFilesystemPathGuardMiddleware(rootDir)
|
|
143
|
+
const slidevExportScreenshot = createSlidevExportScreenshotTool(path.resolve(rootDir))
|
|
144
|
+
const slidevReviewScreenshot = createSlidevReviewScreenshotTool(path.resolve(rootDir))
|
|
145
|
+
const slideGeneratorSystemPrompt = [
|
|
146
|
+
SLIDEV_SLIDE_GENERATOR_SYSTEM_PROMPT,
|
|
147
|
+
buildSlideGeneratorRuntimePrompt(rootDir),
|
|
148
|
+
].join("\n\n")
|
|
149
|
+
|
|
150
|
+
fs.mkdirSync(rootDir, { recursive: true })
|
|
151
|
+
if (!model) {
|
|
152
|
+
throw new Error("No model or API key is configured. Set `SLIDEV_AGENT_MODEL` or `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, or `OPENAI_API_KEY` to a valid API key.")
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return createDeepAgent({
|
|
156
|
+
model,
|
|
157
|
+
systemPrompt,
|
|
158
|
+
tools: [
|
|
159
|
+
slidevGoToSlide,
|
|
160
|
+
slidevExportScreenshot,
|
|
161
|
+
slidevReviewScreenshot
|
|
162
|
+
],
|
|
163
|
+
...(skills.length > 0 ? { skills } : {}),
|
|
164
|
+
subagents: [
|
|
165
|
+
{
|
|
166
|
+
name: "slide_generator",
|
|
167
|
+
description: SLIDEV_SLIDE_GENERATOR_SUBAGENT_DESCRIPTION,
|
|
168
|
+
systemPrompt: slideGeneratorSystemPrompt,
|
|
169
|
+
middleware: [filesystemPathGuardMiddleware],
|
|
170
|
+
...(skills.length > 0 ? { skills } : {}),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
contextSchema: slidevAgentContextSchema,
|
|
174
|
+
middleware: [
|
|
175
|
+
filesystemPathGuardMiddleware,
|
|
176
|
+
dynamicSystemPromptMiddleware((_state, runtime) => {
|
|
177
|
+
const context = (runtime.context ?? {}) as z.infer<typeof slidevAgentContextSchema>
|
|
178
|
+
const currentSlide = context.currentSlide
|
|
179
|
+
if (!currentSlide)
|
|
180
|
+
return ""
|
|
181
|
+
|
|
182
|
+
const details = [
|
|
183
|
+
currentSlide.page ? `Current visible slide number: ${currentSlide.page}.` : "",
|
|
184
|
+
currentSlide.layout ? `Current slide layout: ${currentSlide.layout}.` : "",
|
|
185
|
+
currentSlide.route ? `Current slide route: ${currentSlide.route}.` : "",
|
|
186
|
+
currentSlide.title ? `Current slide title: ${currentSlide.title}.` : "",
|
|
187
|
+
"Use this as strong context when the user asks to update or extend the current slide.",
|
|
188
|
+
].filter(Boolean)
|
|
189
|
+
|
|
190
|
+
return details.join("\n")
|
|
191
|
+
}),
|
|
192
|
+
],
|
|
193
|
+
backend: new LocalShellBackend({
|
|
194
|
+
rootDir,
|
|
195
|
+
virtualMode: options.virtualMode ?? true,
|
|
196
|
+
inheritEnv: true,
|
|
197
|
+
}),
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const agent = createSlidevDeepAgent()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
|
|
3
|
+
import { createMiddleware, ToolMessage } from "langchain"
|
|
4
|
+
|
|
5
|
+
const FILESYSTEM_TOOL_NAMES = new Set([
|
|
6
|
+
"ls",
|
|
7
|
+
"read_file",
|
|
8
|
+
"write_file",
|
|
9
|
+
"edit_file",
|
|
10
|
+
"glob",
|
|
11
|
+
"grep",
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
const SINGLE_PATH_ARG_KEYS = ["path", "file_path"] as const
|
|
15
|
+
const ARRAY_PATH_ARG_KEYS = ["paths"] as const
|
|
16
|
+
const HOST_ABSOLUTE_PREFIXES = [
|
|
17
|
+
"/Users/",
|
|
18
|
+
"/private/",
|
|
19
|
+
"/var/",
|
|
20
|
+
"/tmp/",
|
|
21
|
+
"/etc/",
|
|
22
|
+
"/opt/",
|
|
23
|
+
"/Volumes/",
|
|
24
|
+
"/home/",
|
|
25
|
+
]
|
|
26
|
+
const HOST_RELATIVE_PREFIXES = HOST_ABSOLUTE_PREFIXES.map(prefix => prefix.slice(1))
|
|
27
|
+
|
|
28
|
+
function isWithinRoot(rootDir: string, candidatePath: string) {
|
|
29
|
+
const relativePath = path.relative(rootDir, candidatePath)
|
|
30
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toProjectRelativePath(rootDir: string, candidatePath: string) {
|
|
34
|
+
const relativePath = path.relative(rootDir, candidatePath).replaceAll(path.sep, "/")
|
|
35
|
+
return relativePath ? `/${relativePath}` : "/"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function looksLikeHostAbsolutePath(value: string) {
|
|
39
|
+
return HOST_ABSOLUTE_PREFIXES.some(prefix => value.startsWith(prefix))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function looksLikeMissingLeadingSlashHostPath(value: string) {
|
|
43
|
+
return HOST_RELATIVE_PREFIXES.some(prefix => value.startsWith(prefix))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeFilesystemPath(rootDir: string, rawValue: string) {
|
|
47
|
+
const value = rawValue.trim()
|
|
48
|
+
if (!value)
|
|
49
|
+
return { normalized: rawValue, error: null }
|
|
50
|
+
|
|
51
|
+
if (looksLikeHostAbsolutePath(value)) {
|
|
52
|
+
const candidatePath = path.resolve(value)
|
|
53
|
+
if (isWithinRoot(rootDir, candidatePath)) {
|
|
54
|
+
return { normalized: toProjectRelativePath(rootDir, candidatePath), error: null }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
normalized: rawValue,
|
|
59
|
+
error: [
|
|
60
|
+
`Filesystem tools are rooted at the agent project, so host absolute paths are not allowed here: ${value}`,
|
|
61
|
+
"Use a project-root-relative path such as `/slides.md`, `/pages/foo.md`, or `/example/slides.md` instead.",
|
|
62
|
+
].join("\n"),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (looksLikeMissingLeadingSlashHostPath(value)) {
|
|
67
|
+
const candidatePath = path.resolve(path.sep, value)
|
|
68
|
+
if (isWithinRoot(rootDir, candidatePath))
|
|
69
|
+
return { normalized: toProjectRelativePath(rootDir, candidatePath), error: null }
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
normalized: rawValue,
|
|
73
|
+
error: [
|
|
74
|
+
`Filesystem tools received a host-style path instead of a project-relative path: ${value}`,
|
|
75
|
+
"Use a project-root-relative path such as `/slides.md`, `/pages/foo.md`, or `/example/slides.md` instead.",
|
|
76
|
+
].join("\n"),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { normalized: rawValue, error: null }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function toolErrorMessage(toolCallId: string | undefined, content: string) {
|
|
84
|
+
return new ToolMessage({
|
|
85
|
+
content,
|
|
86
|
+
tool_call_id: toolCallId || "",
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createFilesystemPathGuardMiddleware(rootDir: string) {
|
|
91
|
+
const absoluteRootDir = path.resolve(rootDir)
|
|
92
|
+
|
|
93
|
+
return createMiddleware({
|
|
94
|
+
name: "FilesystemPathGuardMiddleware",
|
|
95
|
+
wrapToolCall: async (request, handler) => {
|
|
96
|
+
const { toolCall } = request
|
|
97
|
+
const { args: rawArgs, id: toolCallId, name: toolName } = toolCall
|
|
98
|
+
|
|
99
|
+
if (!FILESYSTEM_TOOL_NAMES.has(toolName)) {
|
|
100
|
+
return handler(request)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!rawArgs || typeof rawArgs !== "object" || Array.isArray(rawArgs)) {
|
|
104
|
+
return handler(request)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let changed = false
|
|
108
|
+
const nextArgs: Record<string, unknown> = { ...rawArgs as Record<string, unknown> }
|
|
109
|
+
|
|
110
|
+
for (const key of SINGLE_PATH_ARG_KEYS) {
|
|
111
|
+
const value = nextArgs[key]
|
|
112
|
+
if (typeof value !== "string")
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
const { normalized, error } = normalizeFilesystemPath(absoluteRootDir, value)
|
|
116
|
+
if (error) {
|
|
117
|
+
return toolErrorMessage(toolCallId, error)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (normalized !== value) {
|
|
121
|
+
nextArgs[key] = normalized
|
|
122
|
+
changed = true
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const key of ARRAY_PATH_ARG_KEYS) {
|
|
127
|
+
const value = nextArgs[key]
|
|
128
|
+
if (!Array.isArray(value))
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
const normalizedPaths: unknown[] = []
|
|
132
|
+
for (const entry of value) {
|
|
133
|
+
if (typeof entry !== "string") {
|
|
134
|
+
normalizedPaths.push(entry)
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const { normalized, error } = normalizeFilesystemPath(absoluteRootDir, entry)
|
|
139
|
+
if (error) {
|
|
140
|
+
return toolErrorMessage(toolCallId, error)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
normalizedPaths.push(normalized)
|
|
144
|
+
if (normalized !== entry)
|
|
145
|
+
changed = true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
nextArgs[key] = normalizedPaths
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!changed)
|
|
152
|
+
return handler(request)
|
|
153
|
+
|
|
154
|
+
return handler({
|
|
155
|
+
...request,
|
|
156
|
+
toolCall: {
|
|
157
|
+
...toolCall,
|
|
158
|
+
args: nextArgs,
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
}
|