Skip to content

Commit 8516fa8

Browse files
Copilotmarcelklehr
andcommitted
Fix broken API routes in deck, mail, polls, forms, and files tools
- deck.py: Fix list_boards() returning response.text instead of response.json() - mail.py: Fix wrong docstring on get_mail_account_list(), rewrite list_mail_folders, search_emails, get_email_messages, move_email_to_folder, delete_email to use correct Nextcloud Mail REST API routes instead of nonexistent OCS routes - polls.py: Fix all routes to include correct /api/v1.0/ prefix, fix create_poll payload (API doesn't accept description), fix add_poll_option payload structure, fix vote_on_poll to use correct URL-based answer format - forms.py: Update from nonexistent v2.4 API to correct v3 API, fix create_form to use two-step create+update pattern, fix add_question_to_form payload, fix option creation to use batch optionTexts parameter - files.py: Fix get_folder_tree to use params= instead of json= for GET request Co-authored-by: marcelklehr <986878+marcelklehr@users.noreply.github.com>
1 parent 64e674c commit 8516fa8

5 files changed

Lines changed: 91 additions & 88 deletions

File tree

ex_app/lib/all_tools/deck.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async def list_boards():
2121
"Content-Type": "application/json",
2222
})
2323

24-
return response.text
24+
return response.json()
2525

2626

2727

ex_app/lib/all_tools/files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ async def get_folder_tree(depth: int):
5959
:return:
6060
"""
6161

62-
return await nc.ocs('GET', '/ocs/v2.php/apps/files/api/v1/folder-tree', json={'depth': depth}, response_type='json')
62+
return await nc.ocs('GET', '/ocs/v2.php/apps/files/api/v1/folder-tree', params={'depth': depth}, response_type='json')
6363

6464
@tool
6565
@dangerous_tool

ex_app/lib/all_tools/forms.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ async def list_forms():
1515
List all forms created by the current user
1616
:return: a list of forms with their id, title, and state
1717
"""
18-
return await nc.ocs('GET', '/ocs/v2.php/apps/forms/api/v2.4/forms')
18+
return await nc.ocs('GET', '/ocs/v2.php/apps/forms/api/v3/forms')
1919

2020
@tool
2121
@safe_tool
@@ -25,24 +25,29 @@ async def get_form_details(form_id: int):
2525
:param form_id: the id of the form (obtainable via list_forms)
2626
:return: complete form structure with all questions and settings
2727
"""
28-
return await nc.ocs('GET', f'/ocs/v2.php/apps/forms/api/v2.4/forms/{form_id}')
28+
return await nc.ocs('GET', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}')
2929

3030
@tool
3131
@dangerous_tool
3232
async def create_form(title: str, description: Optional[str] = None):
3333
"""
34-
Create a new form
34+
Create a new form. First creates the form, then updates it with the title and description.
3535
:param title: the title of the form
3636
:param description: optional description for the form
3737
:return: the created form with its id
3838
"""
3939
description_with_ai_note = f"{description or ''}\n\n---\n\nThis form was created by Nextcloud AI Assistant."
4040

41-
payload = {
41+
# Create the form first
42+
form = await nc.ocs('POST', '/ocs/v2.php/apps/forms/api/v3/forms')
43+
form_id = form.get('id')
44+
45+
# Then update it with the title and description
46+
key_value_pairs = {
4247
'title': title,
4348
'description': description_with_ai_note
4449
}
45-
return await nc.ocs('POST', '/ocs/v2.php/apps/forms/api/v2.4/form', json=payload)
50+
return await nc.ocs('PATCH', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}', json={'keyValuePairs': key_value_pairs})
4651

4752
@tool
4853
@dangerous_tool
@@ -56,19 +61,24 @@ async def add_question_to_form(form_id: int, question_text: str, question_type:
5661
:param options: list of options for multiple choice, dropdown, etc. (required for multiple/dropdown types)
5762
:return: the created question
5863
"""
59-
payload = {
64+
question = await nc.ocs('POST', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}/questions', json={
6065
'type': question_type,
61-
'text': question_text,
62-
'isRequired': is_required
63-
}
66+
'text': question_text
67+
})
68+
69+
question_id = question.get('id')
6470

65-
question = await nc.ocs('POST', f'/ocs/v2.php/apps/forms/api/v2.4/form/{form_id}/question', json=payload)
71+
# Update isRequired if set
72+
if is_required:
73+
await nc.ocs('PATCH', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}/questions/{question_id}', json={
74+
'keyValuePairs': {'isRequired': is_required}
75+
})
6676

6777
# Add options if provided and question type supports them
6878
if options and question_type in ['multiple', 'multiple_unique', 'dropdown']:
69-
question_id = question.get('id')
70-
for option_text in options:
71-
await nc.ocs('POST', f'/ocs/v2.php/apps/forms/api/v2.4/question/{question_id}/option', json={'text': option_text})
79+
await nc.ocs('POST', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}/questions/{question_id}/options', json={
80+
'optionTexts': options
81+
})
7282

7383
return question
7484

@@ -80,7 +90,7 @@ async def get_form_responses(form_id: int):
8090
:param form_id: the id of the form (obtainable via list_forms)
8191
:return: all responses with answers
8292
"""
83-
return await nc.ocs('GET', f'/ocs/v2.php/apps/forms/api/v2.4/submissions/{form_id}')
93+
return await nc.ocs('GET', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}/submissions')
8494

8595
@tool
8696
@dangerous_tool
@@ -90,7 +100,7 @@ async def delete_form(form_id: int):
90100
:param form_id: the id of the form to delete (obtainable via list_forms)
91101
:return: confirmation of deletion
92102
"""
93-
return await nc.ocs('DELETE', f'/ocs/v2.php/apps/forms/api/v2.4/form/{form_id}')
103+
return await nc.ocs('DELETE', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}')
94104

95105
@tool
96106
@dangerous_tool
@@ -104,17 +114,17 @@ async def update_form_settings(form_id: int, is_anonymous: Optional[bool] = None
104114
:param expires: expiration timestamp (unix time)
105115
:return: the updated form
106116
"""
107-
payload = {}
117+
key_value_pairs = {}
108118
if is_anonymous is not None:
109-
payload['isAnonymous'] = is_anonymous
119+
key_value_pairs['isAnonymous'] = is_anonymous
110120
if submit_multiple is not None:
111-
payload['submitMultiple'] = submit_multiple
121+
key_value_pairs['submitMultiple'] = submit_multiple
112122
if show_expiration is not None:
113-
payload['showExpiration'] = show_expiration
123+
key_value_pairs['showExpiration'] = show_expiration
114124
if expires is not None:
115-
payload['expires'] = expires
125+
key_value_pairs['expires'] = expires
116126

117-
return await nc.ocs('PATCH', f'/ocs/v2.php/apps/forms/api/v2.4/form/update/{form_id}', json=payload)
127+
return await nc.ocs('PATCH', f'/ocs/v2.php/apps/forms/api/v3/forms/{form_id}', json={'keyValuePairs': key_value_pairs})
118128

119129
return [
120130
list_forms,

ex_app/lib/all_tools/mail.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
22
# SPDX-License-Identifier: AGPL-3.0-or-later
33
from asyncio import sleep
4-
from typing import Optional
54

65
from niquests import ConnectionError, Timeout
76
from langchain_core.tools import tool
@@ -50,81 +49,82 @@ async def send_email(subject: str, body: str, account_id: int, from_email: str,
5049
async def get_mail_account_list():
5150
"""
5251
Lists all available email accounts of the current user including their account id
53-
:param subject: The subject of the email
54-
:param body: The body of the email
55-
:param account_id: The id of the account to send from
56-
:param to_emails: The emails to send
52+
:return: list of email accounts with their ids and configuration
5753
"""
5854

5955
return await nc.ocs('GET', '/ocs/v2.php/apps/mail/account/list')
60-
56+
6157

6258
@tool
6359
@safe_tool
6460
async def list_mail_folders(account_id: int):
6561
"""
6662
List all mail folders/mailboxes for an account
6763
:param account_id: The id of the account (obtainable via get_mail_account_list)
68-
:return: list of folders with their names and message counts
64+
:return: list of folders with their ids, names, and message counts
6965
"""
70-
return await nc.ocs('GET', f'/ocs/v2.php/apps/mail/account/{account_id}/mailboxes')
66+
response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/mail/api/mailboxes", headers={
67+
"Content-Type": "application/json",
68+
}, params={'accountId': account_id})
69+
return response.json()
7170

7271
@tool
7372
@safe_tool
74-
async def search_emails(account_id: int, search_term: str, mailbox_name: Optional[str] = None, limit: int = 20):
73+
async def get_email_messages(mailbox_id: int, limit: int = 20):
7574
"""
76-
Search for emails in an account
77-
:param account_id: The id of the account (obtainable via get_mail_account_list)
78-
:param search_term: The text to search for in emails
79-
:param mailbox_name: Optional mailbox/folder to search in (e.g., "INBOX", "Sent")
80-
:param limit: Maximum number of results to return (default 20)
81-
:return: list of matching emails
75+
Get messages from a specific mailbox
76+
:param mailbox_id: The id of the mailbox (obtainable via list_mail_folders)
77+
:param limit: Maximum number of messages to return (default 20)
78+
:return: list of email messages
8279
"""
83-
params = {
84-
'searchQuery': search_term,
85-
'limit': limit
86-
}
87-
if mailbox_name:
88-
params['mailboxName'] = mailbox_name
89-
90-
return await nc.ocs('GET', f'/ocs/v2.php/apps/mail/account/{account_id}/messages', params=params)
80+
response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/mail/api/messages", headers={
81+
"Content-Type": "application/json",
82+
}, params={'mailboxId': mailbox_id, 'limit': limit})
83+
return response.json()
9184

9285
@tool
9386
@safe_tool
94-
async def get_email_messages(account_id: int, mailbox_name: str = 'INBOX', limit: int = 20):
87+
async def search_emails(mailbox_id: int, search_term: str, limit: int = 20):
9588
"""
96-
Get messages from a specific mailbox
97-
:param account_id: The id of the account (obtainable via get_mail_account_list)
98-
:param mailbox_name: The mailbox/folder name (default "INBOX")
99-
:param limit: Maximum number of messages to return (default 20)
100-
:return: list of email messages
89+
Search for emails in a mailbox
90+
:param mailbox_id: The id of the mailbox to search in (obtainable via list_mail_folders)
91+
:param search_term: The text to search for in emails
92+
:param limit: Maximum number of results to return (default 20)
93+
:return: list of matching emails
10194
"""
102-
return await nc.ocs('GET', f'/ocs/v2.php/apps/mail/account/{account_id}/mailboxes/{mailbox_name}/messages', params={'limit': limit})
95+
response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/mail/api/messages", headers={
96+
"Content-Type": "application/json",
97+
}, params={'mailboxId': mailbox_id, 'filter': search_term, 'limit': limit})
98+
return response.json()
10399

104100
@tool
105101
@dangerous_tool
106-
async def move_email_to_folder(account_id: int, message_id: int, target_mailbox: str):
102+
async def move_email_to_folder(message_id: int, dest_mailbox_id: int):
107103
"""
108104
Move an email to a different folder
109-
:param account_id: The id of the account (obtainable via get_mail_account_list)
110-
:param message_id: The id of the message to move
111-
:param target_mailbox: The name of the destination folder (obtainable via list_mail_folders)
105+
:param message_id: The id of the message to move (obtainable via get_email_messages or search_emails)
106+
:param dest_mailbox_id: The id of the destination mailbox (obtainable via list_mail_folders)
112107
:return: confirmation
113108
"""
114-
return await nc.ocs('POST', f'/ocs/v2.php/apps/mail/account/{account_id}/message/{message_id}/move', json={
115-
'mailboxName': target_mailbox
109+
response = await nc._session._create_adapter(True).request('POST', f"{nc.app_cfg.endpoint}/index.php/apps/mail/api/messages/{message_id}/move", headers={
110+
"Content-Type": "application/json",
111+
}, json={
112+
'destFolderId': dest_mailbox_id
116113
})
114+
return response.json()
117115

118116
@tool
119117
@dangerous_tool
120-
async def delete_email(account_id: int, message_id: int):
118+
async def delete_email(message_id: int):
121119
"""
122120
Delete an email message
123-
:param account_id: The id of the account (obtainable via get_mail_account_list)
124-
:param message_id: The id of the message to delete
121+
:param message_id: The id of the message to delete (obtainable via get_email_messages or search_emails)
125122
:return: confirmation
126123
"""
127-
return await nc.ocs('DELETE', f'/ocs/v2.php/apps/mail/account/{account_id}/message/{message_id}')
124+
response = await nc._session._create_adapter(True).request('DELETE', f"{nc.app_cfg.endpoint}/index.php/apps/mail/api/messages/{message_id}", headers={
125+
"Content-Type": "application/json",
126+
})
127+
return response.json()
128128

129129
return [
130130
send_email,

ex_app/lib/all_tools/polls.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,54 +15,52 @@ async def list_polls():
1515
List all polls the current user has access to
1616
:return: a list of polls with their id, title, and status
1717
"""
18-
return await nc.ocs('GET', '/ocs/v2.php/apps/polls/polls')
18+
return await nc.ocs('GET', '/ocs/v2.php/apps/polls/api/v1.0/polls')
1919

2020
@tool
2121
@safe_tool
2222
async def get_poll_details(poll_id: int):
2323
"""
24-
Get detailed information about a specific poll
24+
Get detailed information about a specific poll including options, votes, comments, and shares
2525
:param poll_id: the id of the poll (obtainable via list_polls)
2626
:return: complete poll information including options and votes
2727
"""
28-
return await nc.ocs('GET', f'/ocs/v2.php/apps/polls/poll/{poll_id}')
28+
return await nc.ocs('GET', f'/ocs/v2.php/apps/polls/api/v1.0/poll/{poll_id}')
2929

3030
@tool
3131
@dangerous_tool
32-
async def create_poll(title: str, description: Optional[str] = None, poll_type: str = 'datePoll'):
32+
async def create_poll(title: str, poll_type: str = 'textPoll'):
3333
"""
3434
Create a new poll
3535
:param title: the title of the poll
36-
:param description: optional description for the poll
3736
:param poll_type: type of poll - 'datePoll' for date/time polls or 'textPoll' for text-based polls
3837
:return: the created poll with its id
3938
"""
40-
description_with_ai_note = f"{description or ''}\n\n---\n\nThis poll was created by Nextcloud AI Assistant."
41-
4239
payload = {
4340
'title': title,
44-
'description': description_with_ai_note,
4541
'type': poll_type
4642
}
47-
return await nc.ocs('POST', '/ocs/v2.php/apps/polls/poll', json=payload)
43+
return await nc.ocs('POST', '/ocs/v2.php/apps/polls/api/v1.0/poll', json=payload)
4844

4945
@tool
5046
@dangerous_tool
5147
async def add_poll_option(poll_id: int, option_text: str, timestamp: Optional[int] = None):
5248
"""
5349
Add an option to a poll
5450
:param poll_id: the id of the poll to add the option to (obtainable via list_polls)
55-
:param option_text: the text of the option
51+
:param option_text: the text of the option (for text polls)
5652
:param timestamp: for date polls, the unix timestamp of the date/time option
5753
:return: the created option
5854
"""
59-
payload = {
60-
'pollOptionText': option_text
55+
option = {
56+
'text': option_text
6157
}
6258
if timestamp is not None:
63-
payload['timestamp'] = timestamp
59+
option['timestamp'] = timestamp
6460

65-
return await nc.ocs('POST', f'/ocs/v2.php/apps/polls/option/{poll_id}', json=payload)
61+
return await nc.ocs('POST', f'/ocs/v2.php/apps/polls/api/v1.0/poll/{poll_id}/option', json={
62+
'option': option
63+
})
6664

6765
@tool
6866
@safe_tool
@@ -72,23 +70,18 @@ async def get_poll_votes(poll_id: int):
7270
:param poll_id: the id of the poll (obtainable via list_polls)
7371
:return: all votes cast on the poll
7472
"""
75-
return await nc.ocs('GET', f'/ocs/v2.php/apps/polls/votes/{poll_id}')
73+
return await nc.ocs('GET', f'/ocs/v2.php/apps/polls/api/v1.0/poll/{poll_id}/votes')
7674

7775
@tool
7876
@dangerous_tool
79-
async def vote_on_poll(poll_id: int, option_id: int, answer: str = 'yes'):
77+
async def vote_on_poll(option_id: int, answer: str = 'yes'):
8078
"""
8179
Cast a vote on a poll option
82-
:param poll_id: the id of the poll (obtainable via list_polls)
8380
:param option_id: the id of the option to vote on (obtainable via get_poll_details)
8481
:param answer: the vote - 'yes', 'no', or 'maybe' (for polls that allow maybe)
8582
:return: the recorded vote
8683
"""
87-
payload = {
88-
'optionId': option_id,
89-
'setTo': answer
90-
}
91-
return await nc.ocs('PUT', f'/ocs/v2.php/apps/polls/vote/{poll_id}', json=payload)
84+
return await nc.ocs('PUT', f'/ocs/v2.php/apps/polls/api/v1.0/option/{option_id}/vote/{answer}')
9285

9386
@tool
9487
@dangerous_tool
@@ -98,7 +91,7 @@ async def delete_poll(poll_id: int):
9891
:param poll_id: the id of the poll to delete (obtainable via list_polls)
9992
:return: confirmation of deletion
10093
"""
101-
return await nc.ocs('DELETE', f'/ocs/v2.php/apps/polls/poll/{poll_id}')
94+
return await nc.ocs('DELETE', f'/ocs/v2.php/apps/polls/api/v1.0/poll/{poll_id}')
10295

10396
@tool
10497
@dangerous_tool
@@ -108,7 +101,7 @@ async def close_poll(poll_id: int):
108101
:param poll_id: the id of the poll to close (obtainable via list_polls)
109102
:return: the updated poll
110103
"""
111-
return await nc.ocs('PUT', f'/ocs/v2.php/apps/polls/poll/{poll_id}/close')
104+
return await nc.ocs('PUT', f'/ocs/v2.php/apps/polls/api/v1.0/poll/{poll_id}/close')
112105

113106
@tool
114107
@dangerous_tool
@@ -118,7 +111,7 @@ async def reopen_poll(poll_id: int):
118111
:param poll_id: the id of the poll to reopen (obtainable via list_polls)
119112
:return: the updated poll
120113
"""
121-
return await nc.ocs('PUT', f'/ocs/v2.php/apps/polls/poll/{poll_id}/reopen')
114+
return await nc.ocs('PUT', f'/ocs/v2.php/apps/polls/api/v1.0/poll/{poll_id}/reopen')
122115

123116
return [
124117
list_polls,

0 commit comments

Comments
 (0)