Skip to content

Commit fcd10cf

Browse files
authored
Add listMentionables, addAttachment and removeAttachment tools (commontoolsinc#1932)
* Add `listMentionables`, `addAttachment` and `removeAttachment` tools * Fix generated attachment id * Remove unused imports * Remove `getMentionable()` function * Inline schemifyWish helper * Guard against trying to remove invalid attachment
1 parent 8f776b3 commit fcd10cf

File tree

2 files changed

+89
-15
lines changed

2 files changed

+89
-15
lines changed

packages/patterns/chatbot-note-composed.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import {
2727

2828
import { type MentionableCharm } from "./backlinks-index.tsx";
2929

30+
function schemaifyWish<T>(path: string, def: Opaque<T>) {
31+
return derive<T, T>(wish<T>(path, def), (i) => i);
32+
}
33+
3034
type ChatbotNoteInput = {
3135
title: Default<string, "LLM Test">;
3236
messages: Default<Array<BuiltInLLMMessage>, []>;
@@ -165,10 +169,6 @@ type BacklinksIndex = {
165169
mentionable: MentionableCharm[];
166170
};
167171

168-
function schemaifyWish<T>(path: string, def: Opaque<T>) {
169-
return derive<T, T>(wish<T>(path, def), (i) => i);
170-
}
171-
172172
export default recipe<ChatbotNoteInput, ChatbotNoteResult>(
173173
"Chatbot + Note",
174174
({ title, messages }) => {

packages/patterns/chatbot.tsx

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
llmDialog,
1212
NAME,
1313
navigateTo,
14+
Opaque,
1415
recipe,
1516
Stream,
1617
UI,
@@ -19,6 +20,10 @@ import {
1920
} from "commontools";
2021
import { type MentionableCharm } from "./backlinks-index.tsx";
2122

23+
function schemaifyWish<T>(path: string, def: Opaque<T>) {
24+
return derive<T, T>(wish<T>(path, def), (i) => i);
25+
}
26+
2227
const addAttachment = handler<
2328
{
2429
detail: {
@@ -181,16 +186,6 @@ export const TitleGenerator = recipe<
181186
return title;
182187
});
183188

184-
function getMentionable() {
185-
return derive<Cell<MentionableCharm[]>, Cell<MentionableCharm[]>>(
186-
wish<MentionableCharm[]>(
187-
"/backlinksIndex/mentionable",
188-
[],
189-
) as unknown as Cell<MentionableCharm[]>,
190-
(i) => i,
191-
);
192-
}
193-
194189
const navigateToAttachment = handler<
195190
{ id: string },
196191
{ allAttachments: Array<PromptAttachment> }
@@ -211,12 +206,77 @@ const listAttachments = handler<
211206
}))));
212207
});
213208

209+
const addAttachmentTool = handler<
210+
{
211+
mentionableName: string;
212+
},
213+
{
214+
mentionable: Array<MentionableCharm>;
215+
allAttachments: Cell<Array<PromptAttachment>>;
216+
}
217+
>(({ mentionableName }, { mentionable, allAttachments }) => {
218+
const charm = mentionable.find((c) => c[NAME] === mentionableName);
219+
220+
// borrowed from `ct-prompt-input` to match
221+
const id = `attachment-${Date.now()}-${
222+
Math.random().toString(36).substring(2, 9)
223+
}`;
224+
225+
if (!charm) {
226+
throw new Error(
227+
`Unknown mentionable "${mentionableName}", cannot add attachment.`,
228+
);
229+
}
230+
231+
allAttachments.push({
232+
id,
233+
name: mentionableName,
234+
type: "mention",
235+
charm,
236+
});
237+
});
238+
239+
const removeAttachmentTool = handler<
240+
{
241+
mentionableName: string;
242+
},
243+
{
244+
allAttachments: Cell<Array<PromptAttachment>>;
245+
}
246+
>(({ mentionableName }, { allAttachments }) => {
247+
allAttachments.set(
248+
allAttachments.get().filter((attachment) =>
249+
attachment.name !== mentionableName
250+
),
251+
);
252+
});
253+
254+
const listMentionable = handler<
255+
{
256+
/** A cell to store the result text */
257+
result: Cell<string>;
258+
},
259+
{ mentionable: MentionableCharm[] }
260+
>(
261+
(args, state) => {
262+
try {
263+
const namesList = state.mentionable.map((charm) => charm[NAME]);
264+
args.result.set(JSON.stringify(namesList));
265+
} catch (error) {
266+
args.result.set(`Error: ${(error as any)?.message || "<error>"}`);
267+
}
268+
},
269+
);
270+
214271
export default recipe<ChatInput, ChatOutput>(
215272
"Chat",
216273
({ messages, tools, theme }) => {
217-
const mentionable = getMentionable();
218274
const model = cell<string>("anthropic:claude-sonnet-4-5");
219275
const allAttachments = cell<Array<PromptAttachment>>([]);
276+
const mentionable = schemaifyWish<MentionableCharm[]>(
277+
"/backlinksIndex/mentionable",
278+
[],
279+
);
220280

221281
// Derive tools from attachments
222282
const dynamicTools = derive(allAttachments, (attachments) => {
@@ -245,6 +305,20 @@ export default recipe<ChatInput, ChatOutput>(
245305
description: "List all attachments in the attachments array.",
246306
handler: listAttachments({ allAttachments }),
247307
},
308+
listMentionable: {
309+
description: "List all mentionable NAMEs in the space.",
310+
handler: listMentionable({ mentionable }),
311+
},
312+
addAttachment: {
313+
description:
314+
"Add a new attachment to the attachments array by its mentionable NAME.",
315+
handler: addAttachmentTool({ mentionable, allAttachments }),
316+
},
317+
removeAttachment: {
318+
description:
319+
"Remove an attachment from the attachments array by its mentionable NAME.",
320+
handler: removeAttachmentTool({ allAttachments }),
321+
},
248322
};
249323

250324
// Merge static and dynamic tools

0 commit comments

Comments
 (0)