{"id":31786256,"url":"https://github.com/tldraw/agent-template","last_synced_at":"2025-10-10T12:50:38.142Z","repository":{"id":315582203,"uuid":"1058747538","full_name":"tldraw/agent-template","owner":"tldraw","description":"Enable AI agents to interpret and interact with canvas drawings and elements.","archived":false,"fork":false,"pushed_at":"2025-09-19T11:12:28.000Z","size":105,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-19T13:24:01.257Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://tldraw.dev/starter-kits/agent","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tldraw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-17T13:51:18.000Z","updated_at":"2025-09-19T11:12:32.000Z","dependencies_parsed_at":"2025-09-19T13:24:06.198Z","dependency_job_id":"ecd2e484-50cb-4c9b-b82e-71e45de8f139","html_url":"https://github.com/tldraw/agent-template","commit_stats":null,"previous_names":["tldraw/agent-template"],"tags_count":null,"template":true,"template_full_name":null,"purl":"pkg:github/tldraw/agent-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tldraw%2Fagent-template","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tldraw%2Fagent-template/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tldraw%2Fagent-template/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tldraw%2Fagent-template/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tldraw","download_url":"https://codeload.github.com/tldraw/agent-template/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tldraw%2Fagent-template/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279003890,"owners_count":26083641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-10T12:50:36.213Z","updated_at":"2025-10-10T12:50:38.137Z","avatar_url":"https://github.com/tldraw.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tldraw agent\n\nThis starter kit demonstrates how to build an AI agent that can manipulate the [tldraw](https://github.com/tldraw/tldraw) canvas.\n\nIt features a chat panel on the right-hand-side of the screen where the user can communicate with the agent, add context and see chat history.\n\n## Environment setup\n\nCreate a `.dev.vars` file in the root directory and add API keys for any model providers you want to use.\n\n```\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\nOPENAI_API_KEY=your_openai_api_key_here\n```\n\nWe recommend using Anthropic for best results. Get your API key from the [Anthropic dashboard](https://console.anthropic.com/settings/keys).\n\n## Local development\n\nInstall dependencies with `yarn` or `npm install`.\n\nRun the development server with `yarn dev` or `npm run dev`.\n\nOpen `http://localhost:5173/` in your browser to see the app.\n\n## Agent overview\n\nWith its default configuration, the agent can perform the following actions:\n\n- Create, update and delete shapes.\n- Draw freehand pen strokes.\n- Use higher-level operations on multiple shapes at once: Rotate, resize, align, distribute, stack and reorder shapes.\n- Write out its thinking and send messages to the user.\n- Keep track of its task by writing and updating a todo list.\n- Move its viewport to look at different parts of the canvas.\n- Schedule further work and reviews to be carried out in follow-up requests.\n- Call example external APIs: Looking up country information, retrieving a random Wikipedia article.\n\nTo make decisions on what to do, we send the agent information from various sources:\n\n- The user's message.\n- The user's current selection of shapes.\n- What the user can currently see on their screen.\n- Any additional context that the user has provided, such as specific shapes or a particular position or area on the canvas.\n- Actions the user has recently taken.\n- A screenshot of the agent's current view of the canvas.\n- A simplified format of all shapes within the agent's viewport.\n- Information on clusters of shapes outside the agent's viewport.\n- The history of the current session, including the user's messages and all the agent's actions.\n\n## Use the agent programmatically\n\nAside from using the chat panel UI, you can also prompt the agent programmatically.\n\nThe simplest way to do this is by calling the `prompt()` method to start an agentic loop. The agent will continue until it has finished the task you've given it.\n\n```ts\nconst agent = useTldrawAgent(editor)\nagent.prompt('Draw a cat')\n```\n\nYou can optionally specify further details about the request in the form of an `AgentInput` object:\n\n```ts\nagent.prompt({\n\tmessage: 'Draw a cat in this area',\n\tbounds: { x: 0, y: 0, w: 300, h: 400 },\n})\n```\n\nThere are more methods on the `TldrawAgent` class that can help when building an agentic app:\n\n- `agent.cancel()` - Cancel the agent's current task.\n- `agent.reset()` - Reset the agent's chat and memory.\n- `agent.request(input)` - Send a single request to the agent and handle its response _without_ entering into an agentic loop.\n\n## Customize the agent\n\nWe define the agent's behavior in the `AgentUtils.ts` file. In that file, there are two lists of utility classes:\n\n- `PROMPT_PART_UTILS` determine what the agent can **see**.\n- `AGENT_ACTION_UTILS` determine what the agent can **do**.\n\nAdd, edit or remove an entry in either list to change what the agent can see or do.\n\n## Change what the agent can see\n\n**Change what the agent can see by adding, editing or removing a `PromptPartUtil` within `AgentUtils.ts`.**\n\nPrompt part utils assemble and build the prompt that we give to the model, with each util adding a different piece of information. This includes the user's message, the model name, the system prompt, chat history and more.\n\nThis example shows how to let the model see what the current time is.\n\nFirst, define a prompt part:\n\n```ts\ninterface TimePart extends BasePromptPart\u003c'time'\u003e {\n\ttime: string\n}\n```\n\nThen, create a prompt part util:\n\n```ts\nexport class TimePartUtil extends PromptPartUtil\u003cTimePart\u003e {\n\tstatic override type = 'time' as const\n\n\toverride getPart(): TimePart {\n\t\treturn {\n\t\t\ttype: 'time',\n\t\t\ttime: new Date().toLocaleTimeString(),\n\t\t}\n\t}\n\n\toverride buildContent({ time }: TimePart) {\n\t\treturn [\"The user's current time is:\", time]\n\t}\n}\n```\n\nTo enable the prompt part, add its util to the `PROMPT_PART_UTILS` list in `AgentUtils.ts`. It will use its methods to assemble its data and send it to the model.\n\n- `getPart` - Gather any data needed to construct the prompt.\n- `buildContent` - Turn the data into messages to send to the model.\n\nThere are other methods available on the `PromptPartUtil` class that you can override for more granular control.\n\n- `getPriority` - Control where this prompt part will appear in the list of messages we send to the model. A lower value indicates higher priority, so we send it later on in the request.\n- `getModelName` - Determine which AI model to use.\n- `buildSystemPrompt` - Append a string to the system prompt.\n- `buildMessages` - Manually override how prompt messages are constructed from the prompt part.\n\n## Change what the agent can do\n\n**Change what the agent can do by adding, editing or removing an `AgentActionUtil` within `AgentUtils.ts`.**\n\nAgent action utils define the actions the agent can perform. Each `AgentActionUtil` adds a different capability.\n\nThis example shows how to allow the agent to clear the screen.\n\nFirst, define an agent action by creating a schema for it:\n\n```ts\nconst ClearAction = z\n\t// All agent actions must have a _type field\n\t// The underscore encourages the model to put this field first\n\t.object({\n\t\t_type: z.literal('clear'),\n\t})\n\t// A title and description tell the model what the action does\n\t.meta({\n\t\ttitle: 'Clear',\n\t\tdescription: 'The agent deletes all shapes on the canvas.',\n\t})\n\n// Infer the action's type\ntype ClearAction = z.infer\u003ctypeof ClearAction\u003e\n```\n\nCreate an agent action util:\n\n```ts\nexport class ClearActionUtil extends AgentActionUtil\u003cClearAction\u003e {\n\tstatic override type = 'clear' as const\n\n\toverride getSchema() {\n\t\treturn ClearAction\n\t}\n\n\toverride applyAction(action: Streaming\u003cClearAction\u003e) {\n\t\t// Don't do anything until the action has finished streaming\n\t\tif (!action.complete) return\n\n\t\tif (!this.agent) return\n\t\tconst { editor } = this.agent\n\n\t\t// Delete all shapes on the page\n\t\tconst shapes = editor.getCurrentPageShapes()\n\t\teditor.deleteShapes(shapes)\n\t}\n}\n```\n\nTo enable the agent action, add its util to the `AGENT_ACTION_UTILS` list in `AgentUtils.ts`. Its methods will be used to define and execute the action.\n\n- `getSchema` - Get the schema the model should follow to carry out the action.\n- `applyAction` - Execute the action.\n\nThere are other methods available on the `AgentActionUtil` class that you can override for more granular control.\n\n- `getInfo` - Determine how the action gets displayed in the chat panel UI.\n- `savesToHistory` - Control whether actions get saved to chat history or not.\n- `sanitizeAction` - Sanitize the action before saving it to history and applying it. More details on [sanitization](#sanitize-data-received-from-the-model) below.\n\n## Change how actions appear in chat history\n\nConfigure the icon and description of an action in the chat panel UI using the `getInfo()` method.\n\n```ts\noverride getInfo() {\n\treturn {\n\t\ticon: 'trash' as const,\n\t\tdescription: 'Cleared the canvas',\n\t}\n}\n```\n\nYou can make an action collapsible by adding a `summary` property.\n\n```ts\noverride getInfo() {\n\treturn {\n\t\tsummary: 'Cleared the canvas',\n\t\tdescription: 'After much consideration, the agent decided to clear the canvas',\n\t}\n}\n```\n\nTo customize an action's appearance via CSS, you can define style for the `agent-action-type-{TYPE}` class where `{TYPE}` is the type of the action.\n\n```css\n.agent-action-type-clear {\n\tcolor: red;\n}\n```\n\n## Schedule further work\n\nYou can let the agent work over multiple turns by scheduling further work using the `schedule` method as part of an action.\n\nThis example shows how to schedule an extra step for adding detail to the canvas.\n\n```ts\noverride applyAction(action: Streaming\u003cAddDetailAction\u003e) {\n\tif (!action.complete) return\n\tif (!this.agent) return\n\tthis.agent.schedule('Add more detail to the canvas.')\n}\n```\n\nAs with the `prompt` method, you can specify further details about the request.\n\n```ts\nagent.schedule((prev) =\u003e ({\n\tmessage: 'Add more detail in this area.',\n\tbounds: { x: 0, y: 0, w: 100, h: 100 },\n}))\n```\n\nYou can schedule multiple things by calling the `schedule` method more than once.\n\n```ts\nagent.schedule('Add more detail to the canvas.')\nagent.schedule('Check for spelling mistakes.')\n```\n\nYou can also schedule further work by adding to the agent's todo list. It won't stop working until all todos are resolved.\n\n```ts\noverride applyAction(action: Streaming\u003cAddDetailAction\u003e) {\n\tif (!action.complete) return\n\tif (!this.agent) return\n\n\tthis.agent.addTodo('Check for spelling mistakes.')\n}\n```\n\n## Retrieve data from an external API\n\nTo let the agent retrieve information from an external API, fetch the data within `applyAction` and schedule a follow-up request with any data you want the agent to have access to.\n\n```ts\noverride async applyAction(\n\taction: Streaming\u003cRandomWikipediaArticleAction\u003e\n) {\n\tif (!action.complete) return\n\tif (!this.agent) return\n\n\t// Fetch from the external API\n\tconst article = await fetchRandomWikipediaArticle()\n\n\t// Schedule a follow-up request with the data\n\tthis.agent.schedule({ data: [article] })\n}\n```\n\n## Sanitize data received from the model\n\nThe model can make mistakes. Sometimes this is due to hallucinations, and sometimes this is due to the canvas changing since the last time the model saw it. Either way, an incoming action might contain invalid data by the time we receive it.\n\nTo correct incoming mistakes, apply fixes in the `sanitizeAction` method of an action util. They'll get carried out before the action is applied to the editor or saved to chat history.\n\nFor example, ensure that a shape ID received from the model refers to an existing shape by using the `ensureShapeIdExists` method.\n\n```ts\noverride sanitizeAction(action: Streaming\u003cDeleteAction\u003e, helpers: AgentHelpers) {\n\tif (!action.complete) return action\n\n\t// Ensure the shape ID refers to an existing shape\n\taction.shapeId = helpers.ensureShapeIdExists(action.shapeId)\n\n\t// If the shape ID doesn't refer to an existing shape, cancel the action\n\tif (!action.shapeId) return null\n\n\treturn action\n}\n```\n\nThe `AgentHelpers` object contains more helpers for sanitizing data received from the model.\n\n- `ensureShapeIdExists` - Ensure that a shape ID refers to a real shape. Useful for interacting with existing shapes.\n- `ensureShapeIdIsUnique` - Ensure that a shape ID is unique. Useful for creating new shapes.\n- `ensureValueIsVec`, `ensureValueIsNumber` - Ensure that a value is a certain type. Useful for more complex actions where the model is more likely to make mistakes.\n\n## Send positions to and from the model\n\nBy default, every position sent to the model is offset by the starting position of the current chat.\n\nTo apply this offset to a position sent to the model, use the `applyOffsetToVec` method.\n\n```ts\noverride getPart(request: AgentRequest, helpers: AgentHelpers): ViewportCenterPart {\n\tif (!this.editor) return { part: 'user-viewport-center', center: null, }\n\n\t// Get the center of the user's viewport\n\tconst viewportCenter = this.editor.getViewportBounds().center\n\n\t// Apply the chat's offset to the vector\n\tconst offsetViewportCenter = helpers.applyOffsetToVec(viewportCenter)\n\n\t// Return the prompt part\n\treturn {\n\t\tpart: 'user-viewport-center',\n\t\tcenter: offsetViewportCenter,\n\t}\n}\n```\n\nTo remove the offset from a position received from the model, use the `removeOffsetFromVec` method.\n\n```ts\noverride applyAction(action: Streaming\u003cMoveAction\u003e, helpers: AgentHelpers) {\n\tif (!action.complete) return\n\n\t// Remove the offset from the position\n\tconst position = helpers.removeOffsetFromVec({ x: action.x, y: action.y })\n\n\t// Do something with the position...\n}\n```\n\nIt's a good idea to round numbers before sending them to the model. If you want to be able to restore the original number later, use the `roundAndSaveNumber` and `unroundAndRestoreNumber` methods.\n\n```ts\n// In `getPart`...\nconst roundedX = helpers.roundAndSaveNumber(x, 'my_key_x')\nconst roundedY = helpers.roundAndSaveNumber(y, 'my_key_y')\n\n// In `applyAction`...\nconst unroundedX = helpers.unroundAndRestoreNumber(x, 'my_key_x')\nconst unroundedY = helpers.unroundAndRestoreNumber(y, 'my_key_y')\n```\n\nTo round all the numbers on a shape, use the `roundShape` and `unroundShape` methods. See the [shapes](#send-shapes-to-the-model) section below for more details on sending shapes to the model.\n\n```ts\n// In `getPart`...\nconst roundedShape = helpers.roundShape(shape)\n\n// In `applyAction`...\nconst unroundedShape = helpers.unroundShape(roundedShape)\n```\n\n## Send shapes to the model\n\nBy default, the agent converts tldraw shapes to various simplified formats to improve the model's understanding and performance.\n\nThere are three main formats used in this starter:\n\n- `BlurryShape` - The format for shapes within the agent's viewport. It contains a shape's bounds, its id, its type, and any text it contains. The \"blurry\" name refers to the fact that the agent can't make out the details of shapes from this format. Instead, it gives the model an overview of what it's looking at.\n- `SimpleShape` - The format for shapes that the agent is focusing on, such as when it is reviewing a part of its work. The format contains most of a shape's properties, including color, fill, alignment, and any other shape-specific information. The \"simple\" name refers to how this format is still _simpler_ than the raw tldraw shape format.\n- `PeripheralShapeCluster` - The format for shapes outside the agent's viewport. Nearby shapes are grouped together into clusters, each with the group's bounds and a count of how many shapes are inside it. This is the least detailed format. Its role is to give the model an awareness of shapes that elsewhere on the page.\n\nTo send the model some shapes in one of these formats, use one of the conversion functions found within the `format` folder, such as `convertTldrawShapeToSimpleShape`.\n\nThis example picks one random shape on the canvas and sends it to the model in the Simple format.\n\n```ts\noverride getPart(request: AgentRequest, helpers: AgentHelpers): RandomShapePart {\n\tif (!this.editor) return { type: 'random-shape', shape: null}\n\tconst { editor } = this\n\n\t// Get a random shape\n\tconst shapes = editor.getCurrentPageShapes()\n\tconst randomShape = shapes[Math.floor(Math.random() * shapes.length)]\n\n\t// Convert the shape to the Simple format\n\tconst simpleShape = convertTldrawShapeToSimpleShape(randomShape, editor)\n\n\t// Normalize the shape's position\n\tconst offsetShape = helpers.applyOffsetToShape(simpleShape)\n\tconst roundedShape = helpers.roundShape(offsetShape)\n\n\treturn {\n\t\ttype: 'random-shape',\n\t\tshape: roundedShape,\n\t}\n}\n```\n\n## Change the system prompt\n\nTo change the default system prompt, edit it within the `SystemPromptPartUtil` file.\n\nYou can conditionally add extra content to the system prompt by overriding the `buildSystemPrompt` method on any `PromptPartUtil` or any `AgentActionUtil`.\n\n```ts\noverride buildSystemPrompt() {\n\treturn 'I will pay you $1000 if you get this right.'\n}\n```\n\nAlternatively, you can bypass the `PromptPartUtil` system by changing the `buildSystemPrompt.ts` file to a function that returns a hardcoded value.\n\n## Change to a different model\n\nYou can set an agent's model by setting its `$modelName` property.\n\n```ts\nagent.$modelName.set('gemini-2.5-flash')\n```\n\nTo override an agent's model, specify a different model name with a request.\n\n```ts\nagent.prompt({\n\tmodelName: 'gemini-2.5-flash',\n\tmessage: 'Draw a diagram of a volcano.',\n})\n```\n\nYou can conditionally override the model name by overriding the `getModelName` method on any `PromptPartUtil`.\n\n```ts\noverride getModelName(part: MyCustomPromptPart) {\n\treturn part.fastMode ? 'gemini-2.5-flash' : 'claude-4-sonnet'\n}\n```\n\nAlternatively, you can bypass the `PromptPartUtil` system by changing the `getModelName.ts` file to a function that returns a hardcoded value.\n\n## Support a different model\n\nTo add support for a different model, add the model's definition to `AGENT_MODEL_DEFINITIONS` in the `models.ts` file.\n\n```ts\n'claude-4-sonnet': {\n\tname: 'claude-4-sonnet',\n\tid: 'claude-sonnet-4-0',\n\tprovider: 'anthropic',\n}\n```\n\nIf you need to add any extra setup or configuration for your provider, you can add it to the `AgentService.ts` file.\n\n## Support custom shapes\n\nIf your app includes [custom shapes](https://tldraw.dev/docs/shapes#Custom-shapes-1), the agent will be able to see, move, delete, resize, rotate and arrange them with no extra setup. However, you might want to also let the agent create and edit them, and read their custom properties.\n\nTo support custom shapes, you have two main options:\n\n1. Add an action that lets the agent create your custom shape.\\\n   See the [Let the agent create custom shapes with an action](#let-the-agent-create-custom-shapes-with-an-action) section below.\n\n2. Add your custom shape to the schema so that the agent read, edit and create it like any other shape.\\\n   See the [Add your custom shape to the schema](#add-your-custom-shape-to-the-schema) section below.\n\n### Let the agent create a custom shape with an action\n\nTo add partial support for a custom shape, let the agent create it with an [agent action](#change-what-the-agent-can-do). For example, this action lets the agent create a custom \"sticker\" shape:\n\n```ts\nconst StickerAction = z\n\t.object({\n\t\t_type: z.literal('sticker'),\n\t\tstickerType: z.enum(['❤️', '⭐']),\n\t\tx: z.number(),\n\t\ty: z.number(),\n\t})\n\t.meta({\n\t\ttitle: 'Sticker',\n\t\tdescription: 'Add a sticker to the canvas.',\n\t})\n\ntype StickerAction = z.infer\u003ctypeof StickerAction\u003e\n```\n\nDefine how the action gets applied to the canvas by creating an action util:\n\n```ts\nexport class StickerActionUtil extends AgentActionUtil\u003cStickerAction\u003e {\n\tstatic override type = 'sticker' as const\n\n\t// Tell the model how to use the action\n\toverride getSchema() {\n\t\treturn StickerAction\n\t}\n\n\t// How to display the action in chat history\n\toverride getInfo(action: Streaming\u003cStickerAction\u003e) {\n\t\treturn {\n\t\t\ticon: 'pencil' as const,\n\t\t\tdescription: 'Added a sticker',\n\t\t}\n\t}\n\n\t// Execute the action\n\toverride applyAction(action: Streaming\u003cStickerAction\u003e, helpers: AgentHelpers) {\n\t\tif (!action.complete) return\n\t\tif (!this.agent) return\n\n\t\t// Normalize the position\n\t\tconst position = helpers.removeOffsetFromVec({ x: action.x, y: action.y })\n\n\t\t// Create the custom shape\n\t\tthis.agent.editor.createShape({\n\t\t\ttype: 'sticker',\n\t\t\tid: createShapeId(),\n\t\t\tx: position.x,\n\t\t\ty: position.y,\n\t\t\tprops: { stickerType: action.stickerType },\n\t\t})\n\t}\n}\n```\n\n### Add a custom shape to the schema\n\nTo let the agent see the custom properties of your custom shape, add it to the schema in `SimpleShape.ts`.\n\nFor example, here's a schema for a custom sticker shape.\n\n```ts\nconst SimpleStickerShape = z\n\t.object({\n\t\t// Required properties\n\t\t_type: z.literal('sticker'),\n\t\tnote: z.string(),\n\t\tshapeId: z.string(),\n\n\t\t// Custom properties\n\t\tstickerType: z.enum(['❤️', '⭐']),\n\t\tx: z.number(),\n\t\ty: z.number(),\n\t})\n\t.meta({\n\t\t// Information about the shape to give to the agent\n\t\ttitle: 'Sticker Shape',\n\t\tdescription:\n\t\t\t'A sticker shape is a small symbol stamped onto the canvas. There are two types of stickers: heart and star.',\n\t})\n```\n\nThe `_type` and `shapeId` properties are required so that the app can identify your shape. The `note` property is also required. The agent uses it to leave notes for itself.\n\nFor optional properties, it's worth considering how the agent should see your custom shape. You might want to leave out some properties and focus on showing the most important ones. It's also best to keep them in alphabetical order for better performance with Gemini models.\n\nEnable your custom shape schema by adding it to the list of `SIMPLE_SHAPES` in the same file.\n\n```ts\nconst SIMPLE_SHAPES = [\n\tSimpleDrawShape,\n\tSimpleGeoShape,\n\tSimpleLineShape,\n\tSimpleTextShape,\n\tSimpleArrowShape,\n\tSimpleNoteShape,\n\tSimpleUnknownShape,\n\n\t// Our custom shape\n\tSimpleStickerShape,\n] as const\n```\n\nTell the app how to convert your custom shape into the `SimpleShape` format by adding it as a case in `convertTldrawShapeToSimpleShape.ts`.\n\n```ts\nexport function convertTldrawShapeToSimpleShape(editor: Editor, shape: TLShape): SimpleShape {\n\tswitch (shape.type) {\n\t\t// ...\n\t\tcase 'sticker':\n\t\t\tconst bounds = getShapeBounds(shape)\n\t\t\treturn {\n\t\t\t\t_type: 'sticker',\n\t\t\t\tnote: (shape.meta.note as string) ?? '',\n\t\t\t\tshapeId: convertTldrawIdToSimpleId(shape.id),\n\t\t\t\tstickerType: shape.props.stickerType,\n\t\t\t\tx: bounds.x,\n\t\t\t\ty: bounds.y,\n\t\t\t}\n\t\t// ...\n\t}\n}\n```\n\nTo allow the agent to edit your custom shape's properties, tell the app how to convert your shape from the `SimpleShape` format that the model outputs to the actual format of your shape.\n\n```ts\nexport function convertSimpleShapeToTldrawShape(\n\teditor: Editor,\n\tsimpleShape: TLShape\n\t{ defaultShape }: { defaultShape: Partial\u003cTLShape\u003e }\n): {\n\tswitch (simpleShape.type) {\n\t\t// ...\n\t\tcase 'sticker':\n\t\t\tconst shapeId = convertSimpleIdToTldrawId(simpleShape.shapeId)\n\t\t\treturn {\n\t\t\t\tshape: {\n\t\t\t\t\tid: shapeId\n\t\t\t\t\tx: simpleShape.x,\n\t\t\t\t\ty: simpleShape.y\n\t\t\t\t\t// ...\n\t\t\t\t\tprops: {\n\t\t\t\t\t\t// ...\n\t\t\t\t\t\tstickerType: simpleShape.sitckerType\n\t\t\t\t\t},\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\tnote: simpleShape.note ?? ''\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t// ...\n\t}\n}\n```\n\n## License\n\nThis project is part of the tldraw SDK. It is provided under the [tldraw SDK license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md).\n\nYou can use the tldraw SDK in commercial or non-commercial projects so long as you preserve the \"Made with tldraw\" watermark on the canvas. To remove the watermark, you can purchase a [business license](https://tldraw.dev#pricing). Visit [tldraw.dev](https://tldraw.dev) to learn more.\n\n## Trademarks\n\nCopyright (c) 2025-present tldraw Inc. The tldraw name and logo are trademarks of tldraw. Please see our [trademark guidelines](https://github.com/tldraw/tldraw/blob/main/TRADEMARKS.md) for info on acceptable usage.\n\n## Distributions\n\nYou can find tldraw on npm [here](https://www.npmjs.com/package/@tldraw/tldraw?activeTab=versions).\n\n## Contribution\n\nFound a bug? Please [submit an issue](https://github.com/tldraw/tldraw/issues/new).\n\n## Community\n\nHave questions, comments or feedback? [Join our discord](https://discord.gg/rhsyWMUJxd). For the latest news and release notes, visit [tldraw.dev](https://tldraw.dev).\n\n## Contact\n\nFind us on Twitter/X at [@tldraw](https://twitter.com/tldraw) or email us at [mailto:hello@tldraw.com](hello@tldraw.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftldraw%2Fagent-template","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftldraw%2Fagent-template","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftldraw%2Fagent-template/lists"}