Skip to content

Commit 850a714

Browse files
committed
docs: check only dev docs
Signed-off-by: Jan Pokorný <JenomPokorny@gmail.com>
1 parent 8ee2297 commit 850a714

File tree

3 files changed

+167
-2
lines changed

3 files changed

+167
-2
lines changed
File renamed without changes.
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
title: "Work with Canvas"
3+
description: "Handle artifact editing requests from users"
4+
---
5+
6+
The Canvas extension enables users to request edits to specific parts of artifacts (like code, documents, or other structured content) that your agent has generated. Instead of users describing what they want to change in text, they can select a portion of an artifact in the UI and request an edit, giving your agent precise information about what to modify.
7+
8+
## How Canvas Works
9+
10+
When a user selects a portion of an artifact and requests an edit:
11+
12+
1. The selection's **start and end indices** (character positions) are captured
13+
2. The user provides a **description** of what they want to change
14+
3. The **artifact ID** identifies which artifact to edit
15+
4. Your agent receives this structured information to process the edit request
16+
17+
This allows your agent to:
18+
19+
- Know exactly which part of the artifact the user wants to modify
20+
- Access the original artifact content from history
21+
- Make targeted edits based on the user's description
22+
- Generate a new artifact with the changes (using the same artifact_id to replace the previous version in the UI)
23+
24+
## Quickstart
25+
26+
<Steps>
27+
<Step title="Import the canvas extension">
28+
Import `CanvasExtensionServer` and `CanvasExtensionSpec` from `agentstack_sdk.a2a.extensions.ui.canvas`.
29+
</Step>
30+
31+
<Step title='Inject the extension'>
32+
Add a canvas parameter to your agent function using the `Annotated` type hint
33+
with `CanvasExtensionSpec()`.
34+
</Step>
35+
36+
<Step title='Parse edit requests'>
37+
Call `await canvas.parse_canvas_edit_request(message=message)` to check if the
38+
incoming message contains a canvas edit request. This returns `None` if no
39+
edit request is present, or a `CanvasEditRequest` object with: -
40+
`start_index`: The starting character position of the selected text -
41+
`end_index`: The ending character position of the selected text -
42+
`description`: The user's description of what they want to change -
43+
`artifact`: The full original artifact object from history
44+
</Step>
45+
46+
<Step title='Access the original content'>
47+
Extract the text from `artifact.parts[0].root.text` (for text artifacts) and
48+
use the start/end indices to get the selected portion: `selected_text =
49+
content[start_index:end_index]`.
50+
</Step>
51+
52+
<Step title="Return a new artifact">
53+
Create a new artifact with your changes.
54+
</Step>
55+
</Steps>
56+
57+
## Example: Canvas with LLM
58+
59+
Here's how to use canvas with an LLM, adapting your system prompt based on whether you're generating new content or editing existing content:
60+
61+
````python
62+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
63+
# SPDX-License-Identifier: Apache-2.0
64+
65+
import re
66+
from typing import Annotated
67+
68+
from a2a.types import Message, TextPart
69+
70+
from agentstack_sdk.a2a.extensions import LLMServiceExtensionServer, LLMServiceExtensionSpec
71+
from agentstack_sdk.a2a.extensions.ui.canvas import (
72+
CanvasExtensionServer,
73+
CanvasExtensionSpec,
74+
)
75+
from agentstack_sdk.a2a.types import AgentArtifact
76+
from agentstack_sdk.server import Server
77+
from agentstack_sdk.server.context import RunContext
78+
79+
server = Server()
80+
81+
BASE_PROMPT = """\
82+
You are a helpful coding assistant.
83+
Generate code enclosed in triple-backtick blocks tagged ```python.
84+
The first line should be a comment with the code's purpose.
85+
"""
86+
87+
EDIT_PROMPT = (
88+
BASE_PROMPT
89+
+ """
90+
You are editing existing code. The user selected this portion:
91+
```python
92+
{selected_code}
93+
```
94+
95+
They want: {description}
96+
97+
Respond with the FULL updated code. Only change the selected portion.
98+
"""
99+
)
100+
101+
102+
def get_system_prompt(canvas_edit):
103+
if not canvas_edit:
104+
return BASE_PROMPT
105+
106+
original_code = (
107+
canvas_edit.artifact.parts[0].root.text if isinstance(canvas_edit.artifact.parts[0].root, TextPart) else ""
108+
)
109+
selected = original_code[canvas_edit.start_index : canvas_edit.end_index]
110+
111+
return EDIT_PROMPT.format(selected_code=selected, description=canvas_edit.description)
112+
113+
114+
@server.agent()
115+
async def code_agent(
116+
message: Message,
117+
context: RunContext,
118+
llm: Annotated[LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand()],
119+
canvas: Annotated[CanvasExtensionServer, CanvasExtensionSpec()],
120+
):
121+
canvas_edit = await canvas.parse_canvas_edit_request(message=message)
122+
123+
# Adapt system prompt based on whether this is an edit or new generation
124+
system_prompt = get_system_prompt(canvas_edit)
125+
126+
# Call your LLM with the adapted prompt
127+
# (implementation depends on your LLM framework)
128+
response = await call_llm(llm, system_prompt, message)
129+
130+
# Extract code from response
131+
match = re.search(r"```python\n(.*?)\n```", response, re.DOTALL)
132+
if match:
133+
code = match.group(1).strip()
134+
first_line = code.split("\n", 1)[0]
135+
name = first_line.lstrip("# ").strip() if first_line.startswith("#") else "Python Script"
136+
137+
artifact = AgentArtifact(
138+
name=name,
139+
parts=[TextPart(text=code)],
140+
)
141+
yield artifact
142+
143+
# Note: Always create a new artifact. When canvas_edit exists, pass its artifact_id
144+
# to replace the previous version in the UI.
145+
146+
147+
if __name__ == "__main__":
148+
server.run()
149+
````
150+
151+
## How to work with Canvas
152+
153+
**Artifacts in history**: The extension automatically retrieves the original artifact from history using the `artifact_id`. If not found, a `ValueError` is raised.
154+
155+
**Text parts filtering**: The extension filters out fallback text messages (sent for agents that don't support canvas) so you only work with structured edit request data.
156+
157+
## Best Practices
158+
159+
**Adapt your system prompt**: Provide different instructions to your LLM depending on whether you're generating new content or editing existing content.
160+
161+
**Validate indices**: Ensure start and end indices are within bounds before slicing the artifact text.
162+
163+
```
164+
165+
```

docs/tasks.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ depends = ["docs:check:*"]
1313
["docs:check:pyright"]
1414
depends = ["agentstack-sdk-py:build"]
1515
dir = "{{config_root}}"
16-
sources = ["docs/**/*.md", "docs/**/*.mdx", "apps/agentstack-sdk-py/dist/agentstack_sdk-*.whl"]
16+
sources = ["docs/development/**/*.md", "docs/development/**/*.mdx", "apps/agentstack-sdk-py/dist/agentstack_sdk-*.whl"]
1717
outputs = { auto=true }
1818
run = """
1919
#!/usr/bin/env bash
@@ -39,7 +39,7 @@ while IFS= read -r -d '' md; do
3939
;;
4040
esac
4141
done
42-
done < <(find docs -type f \\( -name '*.md' -o -name '*.mdx' \\) -print0)
42+
done < <(find docs/development -type f \\( -name '*.md' -o -name '*.mdx' \\) -print0)
4343
4444
uv venv --quiet "$TMPDIR/venv"
4545
source "$TMPDIR/venv/bin/activate"

0 commit comments

Comments
 (0)