dolibarr 25.0.0-alpha
execute_tool.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2026 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2026 Nick Fragoulis
4 * Copyright (C) 2026 Anthony Damhet <a.damhet@progiseize.fr>
5 * Copyright (C) 2026 Jose Martinez <jose.martinez@pichinov.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
27if (!defined('NOTOKENRENEWAL')) {
28 define('NOTOKENRENEWAL', 1);
29}
30if (!defined('NOREQUIREMENU')) {
31 define('NOREQUIREMENU', 1);
32}
33if (!defined('NOREQUIREHTML')) {
34 define('NOREQUIREHTML', 1);
35}
36if (!defined('NOREQUIREAJAX')) {
37 define('NOREQUIREAJAX', 1);
38}
39// The payload is read from the raw php://input body, so the CSRF token cannot be checked by
40// main.inc.php. It is checked explicitly below by aiCheckCsrfToken().
41if (!defined('NOCSRFCHECK')) {
42 define('NOCSRFCHECK', 1);
43}
44
45require '../../main.inc.php';
46require_once DOL_DOCUMENT_ROOT . '/ai/class/mcp.class.php';
47require_once DOL_DOCUMENT_ROOT . '/ai/lib/ai.lib.php';
48
49// Security check
50if (!isModEnabled('ai') || !getDolGlobalString('AI_ASSISTANT_ENABLED')) {
51 accessforbidden('Module or feature not allowed');
52}
53
54global $db, $user, $conf;
55
56// Per-user gate: same right as the assistant page and parse_intent.php
57if (!$user->hasRight('ai', 'assistant', 'use')) {
59}
60
61// This endpoint creates, updates and deletes documents, so it must not be reachable from
62// another site. Must stay after the login is done by main.inc.php (the session is needed).
63aiCheckCsrfToken('ai/assistant/execute_tool.php');
64
65top_httphead('application/json');
66
67
68try {
69 $raw = file_get_contents('php://input');
70 $input = json_decode($raw, true);
71
72 if (!$input || empty($input['tool'])) {
73 throw new Exception("Invalid Request: No tool specified.");
74 }
75
76 // Initialize Handler with the private assistant context so that the correct
77 // allow-list (AI_ASSISTANT_ALLOWED_TOOLS) is enforced on both schema and execution.
78 $mcp = new McpHandler($db, $user, $conf, McpHandler::CTX_ASSISTANT);
79 $mcp->loadTools();
80
81 $tStart = microtime(true);
82 $result = $mcp->executeTool($input['tool'], $input['arguments'] ?? []);
83
84 // A write answers with a confirmation request instead of running. This
85 // endpoint is only reached after the user pressed the confirmation button of
86 // the chat, on a CSRF-checked request, so the round trip is completed here
87 // rather than asking the same human twice. The state is still issued, bound
88 // to these arguments and consumed once, so the write leaves its trace in
89 // llx_ai_write_confirmation like any other.
90 if (is_array($result) && ($result['resultType'] ?? '') === 'input_required' && !empty($result['requestState'])) {
91 $confirmedargs = $input['arguments'] ?? [];
92 $confirmedargs['requestState'] = $result['requestState'];
93 $result = $mcp->executeTool($input['tool'], $confirmedargs);
94 }
95
96 // This endpoint runs the executions the user confirmed - the calls that actually
97 // create, update or delete data - so they must land in the audit table just like
98 // the parse rounds (parse_intent.php) and the MCP server calls already do.
99 $status = 'Success';
100 $errorMsg = '';
101 // Two failure conventions coexist: the write tools return success=false,
102 // everything else (missing record, unknown tool, bridge error, rights
103 // refusal) returns a bare non-empty 'error' key - both must log as Error.
104 if ((array_key_exists('success', $result) && empty($result['success'])) || !empty($result['error'])) {
105 $status = 'Error';
106 $errorMsg = isset($result['error']) ? (string) $result['error'] : '';
107 }
109 $db,
110 $user,
111 '[Assistant] '.$input['tool'].' '.aiTruncateForLog((string) json_encode($input['arguments'] ?? []), 20000),
112 ['tool' => (string) $input['tool']],
113 'assistant',
114 microtime(true) - $tStart,
115 1.0,
116 $status,
117 $errorMsg,
118 $raw,
119 (string) json_encode($result)
120 );
121
122 echo json_encode($result);
123} catch (Throwable $e) {
124 // Set HTTP response code to error (400 Bad Request)
125 http_response_code(400);
126 $toolName = (isset($input) && is_array($input) && !empty($input['tool'])) ? (string) $input['tool'] : '';
128 $db,
129 $user,
130 '[Assistant] '.($toolName !== '' ? $toolName : 'invalid_request'),
131 ['tool' => $toolName],
132 'assistant',
133 0.0,
134 0.0,
135 'Error',
136 $e->getMessage(),
137 isset($raw) && is_string($raw) ? $raw : '',
138 ''
139 );
140 echo json_encode(["error" => $e->getMessage()]);
141}
aiTruncateForLog($text, $max=60000)
Trim a payload for the request log, keeping the beginning and the end.
Definition ai.lib.php:383
ai_log_request($db, $user, $query, array $response, $provider, float $time, float $confidence, $status, $error='', $rawReq='', $rawRes='', array $context=array(), &$logId=null)
Log AI Request with Raw Payloads.
Definition ai.lib.php:416
aiCheckCsrfToken($context='')
Check the anti-CSRF token of a request sent to one of the AI Assistant endpoints.
Definition ai.lib.php:1065
Class to handle MCP (Model Context Protocol).
Definition mcp.class.php:40
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.