-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(ai): add tool use (function calling) to AI assistant #165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c128676
e3a4058
e5dc625
afc8d32
1cc0044
f65830b
4d7f7a5
38dd2cf
a992d06
10588b7
2951ebe
2781424
42fb7e5
30e9268
e26efbc
b40fe5f
71c2b31
2f0837c
f803742
b504cd5
3629486
349b7de
ede2504
c8eb00c
dc62cfd
28ea1ef
53cf70c
e45f442
6e1a026
0f502d2
2111ea0
2fed12a
e349b3f
833ecf9
e46c74f
caf0c56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,140 @@ | ||||||||||||||||||||||||||
| // apps/desktop/src/main/ai/built-in-tools.ts | ||||||||||||||||||||||||||
| import type { ToolRegistry } from '@readied/ai-core'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Register built-in AI tools for note operations. | ||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||
| * Read tools (auto-execute): search_notes, read_note, list_notebooks | ||||||||||||||||||||||||||
| * Write tools (require confirmation): create_note, insert_text, replace_selection | ||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||
| * insert_text and replace_selection execute in the renderer (they need editor access). | ||||||||||||||||||||||||||
| * They delegate via IPC: main → renderer → main. | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| export function registerBuiltInTools( | ||||||||||||||||||||||||||
| registry: ToolRegistry, | ||||||||||||||||||||||||||
| deps: { | ||||||||||||||||||||||||||
| searchNotes: ( | ||||||||||||||||||||||||||
| query: string, | ||||||||||||||||||||||||||
| limit?: number | ||||||||||||||||||||||||||
| ) => Promise<Array<{ id: string; title: string; snippet: string }>>; | ||||||||||||||||||||||||||
| readNote: (id: string) => Promise<{ id: string; title: string; content: string } | null>; | ||||||||||||||||||||||||||
| listNotebooks: () => Promise<Array<{ id: string; name: string; noteCount: number }>>; | ||||||||||||||||||||||||||
| createNote: (title: string, content: string, notebookId?: string) => Promise<{ id: string }>; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| ): void { | ||||||||||||||||||||||||||
| registry.register({ | ||||||||||||||||||||||||||
| name: 'search_notes', | ||||||||||||||||||||||||||
| description: 'Search notes by keyword query. Returns matching note IDs, titles, and snippets.', | ||||||||||||||||||||||||||
| parameters: { | ||||||||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||||||||
| properties: { | ||||||||||||||||||||||||||
| query: { type: 'string', description: 'Search query' }, | ||||||||||||||||||||||||||
| limit: { type: 'number', description: 'Max results (default 10)' }, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| required: ['query'], | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| requiresConfirmation: false, | ||||||||||||||||||||||||||
| execute: async args => { | ||||||||||||||||||||||||||
| const results = await deps.searchNotes(args.query as string, (args.limit as number) ?? 10); | ||||||||||||||||||||||||||
| return { ok: true, content: JSON.stringify(results) }; | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
Comment on lines
+37
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider adding runtime validation for tool arguments. The type assertions ( 💡 Suggested defensive validation execute: async args => {
+ const query = typeof args.query === 'string' ? args.query : String(args.query ?? '');
+ const limit = typeof args.limit === 'number' ? args.limit : 10;
- const results = await deps.searchNotes(args.query as string, (args.limit as number) ?? 10);
+ const results = await deps.searchNotes(query, limit);
return { ok: true, content: JSON.stringify(results) };
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged — valid nitpick. Tool args are schema-constrained by the provider. Will consider runtime validation in a follow-up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| registry.register({ | ||||||||||||||||||||||||||
| name: 'read_note', | ||||||||||||||||||||||||||
| description: 'Read the full content of a note by its ID.', | ||||||||||||||||||||||||||
| parameters: { | ||||||||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||||||||
| properties: { | ||||||||||||||||||||||||||
| id: { type: 'string', description: 'Note ID' }, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| required: ['id'], | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| requiresConfirmation: false, | ||||||||||||||||||||||||||
| execute: async args => { | ||||||||||||||||||||||||||
| const note = await deps.readNote(args.id as string); | ||||||||||||||||||||||||||
| if (!note) return { ok: false, content: 'Note not found', error: 'Note not found' }; | ||||||||||||||||||||||||||
| return { ok: true, content: JSON.stringify(note) }; | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| registry.register({ | ||||||||||||||||||||||||||
| name: 'list_notebooks', | ||||||||||||||||||||||||||
| description: 'List all notebooks with their names and note counts.', | ||||||||||||||||||||||||||
| parameters: { | ||||||||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||||||||
| properties: {}, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| requiresConfirmation: false, | ||||||||||||||||||||||||||
| execute: async () => { | ||||||||||||||||||||||||||
| const notebooks = await deps.listNotebooks(); | ||||||||||||||||||||||||||
| return { ok: true, content: JSON.stringify(notebooks) }; | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| registry.register({ | ||||||||||||||||||||||||||
| name: 'create_note', | ||||||||||||||||||||||||||
| description: 'Create a new note in a notebook.', | ||||||||||||||||||||||||||
| parameters: { | ||||||||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||||||||
| properties: { | ||||||||||||||||||||||||||
| title: { type: 'string', description: 'Note title' }, | ||||||||||||||||||||||||||
| content: { type: 'string', description: 'Note content in markdown' }, | ||||||||||||||||||||||||||
| notebookId: { | ||||||||||||||||||||||||||
| type: 'string', | ||||||||||||||||||||||||||
| description: 'Target notebook ID (optional, uses default)', | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| required: ['title', 'content'], | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| requiresConfirmation: true, | ||||||||||||||||||||||||||
| execute: async args => { | ||||||||||||||||||||||||||
| const result = await deps.createNote( | ||||||||||||||||||||||||||
| args.title as string, | ||||||||||||||||||||||||||
| args.content as string, | ||||||||||||||||||||||||||
| args.notebookId as string | undefined | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| return { ok: true, content: JSON.stringify(result) }; | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| registry.register({ | ||||||||||||||||||||||||||
| name: 'insert_text', | ||||||||||||||||||||||||||
| description: 'Insert text into the current note at the cursor position or at the end.', | ||||||||||||||||||||||||||
| parameters: { | ||||||||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||||||||
| properties: { | ||||||||||||||||||||||||||
| text: { type: 'string', description: 'Text to insert' }, | ||||||||||||||||||||||||||
| position: { | ||||||||||||||||||||||||||
| type: 'string', | ||||||||||||||||||||||||||
| description: "Where to insert: 'cursor' (default) or 'end'", | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| required: ['text'], | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| requiresConfirmation: true, | ||||||||||||||||||||||||||
| rendererOnly: true, | ||||||||||||||||||||||||||
| execute: async () => { | ||||||||||||||||||||||||||
| // Execution handled by ipc-ai.ts via executeToolInRenderer | ||||||||||||||||||||||||||
| return { ok: false, content: 'Not reachable', error: 'Renderer tool called without IPC' }; | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| registry.register({ | ||||||||||||||||||||||||||
| name: 'replace_selection', | ||||||||||||||||||||||||||
| description: 'Replace the currently selected text in the editor.', | ||||||||||||||||||||||||||
| parameters: { | ||||||||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||||||||
| properties: { | ||||||||||||||||||||||||||
| text: { type: 'string', description: 'Replacement text' }, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| required: ['text'], | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| requiresConfirmation: true, | ||||||||||||||||||||||||||
| rendererOnly: true, | ||||||||||||||||||||||||||
| execute: async () => { | ||||||||||||||||||||||||||
| // Execution handled by ipc-ai.ts via executeToolInRenderer | ||||||||||||||||||||||||||
| return { ok: false, content: 'Not reachable', error: 'Renderer tool called without IPC' }; | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a language to the fenced code block.
The fence opened at Line 265 has no language identifier (MD040).
📝 Proposed fix
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 265-265: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged — cosmetic, will fix separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rate Limit Exceeded
@tomymaritanohave exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 51 seconds before sending another message.