neoagent 2.1.18-beta.10 → 2.1.18-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/services/ai/engine.js +17 -3
- package/server/services/ai/systemPrompt.js +4 -0
- package/server/services/ai/taskAnalysis.js +3 -0
- package/server/services/ai/toolResult.js +30 -1
- package/server/services/integrations/figma/provider.js +207 -3
- package/server/services/integrations/google/calendar.js +26 -1
- package/server/services/integrations/google/common.js +39 -0
- package/server/services/integrations/google/docs.js +26 -0
- package/server/services/integrations/google/drive.js +31 -1
- package/server/services/integrations/google/gmail.js +26 -0
- package/server/services/integrations/google/provider.js +1 -1
- package/server/services/integrations/google/sheets.js +26 -0
- package/server/services/integrations/microsoft/provider.js +287 -4
- package/server/services/integrations/notion/provider.js +280 -0
- package/server/services/integrations/oauth_provider.js +91 -14
- package/server/services/integrations/slack/provider.js +219 -29
package/package.json
CHANGED
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"425cfb54d01a9472b3e81d9e76fd63a4a44cfb
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "126115739" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -258,7 +258,7 @@ function parseMaybeJson(value, fallback = null) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
function classifyToolExecution(toolName, result, errorMessage = '') {
|
|
261
|
+
function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '') {
|
|
262
262
|
const name = String(toolName || '');
|
|
263
263
|
const evidenceRelevantPrefixes = ['browser_', 'android_'];
|
|
264
264
|
const evidenceRelevantExact = new Set([
|
|
@@ -339,7 +339,7 @@ function classifyToolExecution(toolName, result, errorMessage = '') {
|
|
|
339
339
|
evidenceRelevant,
|
|
340
340
|
stateChanged,
|
|
341
341
|
dependsOnOutput: true,
|
|
342
|
-
summary: compactToolResult(name,
|
|
342
|
+
summary: compactToolResult(name, toolArgs, result || { error: errorMessage || 'Tool failed' }, {
|
|
343
343
|
softLimit: 500,
|
|
344
344
|
hardLimit: 900,
|
|
345
345
|
}),
|
|
@@ -1396,7 +1396,7 @@ class AgentEngine {
|
|
|
1396
1396
|
);
|
|
1397
1397
|
}
|
|
1398
1398
|
|
|
1399
|
-
toolExecutions.push(classifyToolExecution(toolName, toolResult, toolErrorMessage));
|
|
1399
|
+
toolExecutions.push(classifyToolExecution(toolName, toolArgs, toolResult, toolErrorMessage));
|
|
1400
1400
|
this.persistRunMetadata(runId, {
|
|
1401
1401
|
evidenceSources: [...new Set(toolExecutions.map((item) => item.evidenceSource).filter(Boolean))],
|
|
1402
1402
|
subagentState: this.listSubagents(runId),
|
|
@@ -1427,6 +1427,20 @@ class AgentEngine {
|
|
|
1427
1427
|
});
|
|
1428
1428
|
}
|
|
1429
1429
|
|
|
1430
|
+
if (
|
|
1431
|
+
toolName === 'execute_command'
|
|
1432
|
+
&& !toolErrorMessage
|
|
1433
|
+
&& !toolResult?.timedOut
|
|
1434
|
+
&& !toolResult?.killed
|
|
1435
|
+
&& toolResult?.exitCode !== undefined
|
|
1436
|
+
&& toolResult.exitCode !== 0
|
|
1437
|
+
) {
|
|
1438
|
+
messages.push({
|
|
1439
|
+
role: 'system',
|
|
1440
|
+
content: 'The previous shell command exited non-zero. Treat its output as partial evidence only. If it chained multiple shell segments, later segments may not have run. Do not summarize missing sections as observed facts; rerun or verify them separately first.'
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1430
1444
|
if (conversationId) {
|
|
1431
1445
|
db.prepare('INSERT INTO conversation_messages (conversation_id, role, content, tool_call_id, name) VALUES (?, ?, ?, ?, ?)')
|
|
1432
1446
|
.run(conversationId, 'tool', toolMessage.content, toolCall.id, toolName);
|
|
@@ -89,8 +89,12 @@ When the system context gives app-level official integration status, trust it ov
|
|
|
89
89
|
|
|
90
90
|
SHELL COMMANDS
|
|
91
91
|
When you use execute_command, treat timed out or killed commands as unfinished work, not success. For installs, updates, restarts, config changes, or other state-changing shell actions, verify the outcome with a follow-up command before telling the user it is done.
|
|
92
|
+
When execute_command exits non-zero, treat the output as partial evidence only. If the command chained multiple shell segments, later segments may not have run at all, so do not summarize them as observed facts unless you verified them separately.
|
|
92
93
|
If you restart or stop the NeoAgent service, this run ends immediately. Warn the user before doing it and say you cannot continue the current run after the restart.
|
|
93
94
|
|
|
95
|
+
MESSAGING CLAIMS
|
|
96
|
+
Do not claim a messaging platform is blocked, disconnected, receive-only, or unable to send unless a messaging tool or capability check in this run actually showed that failure. If send_message succeeded, do not describe outbound delivery as blocked.
|
|
97
|
+
|
|
94
98
|
SKILLS
|
|
95
99
|
Create or improve a skill only when it is clearly reusable, polished, and likely to matter again. Most completed tasks should not become skills.
|
|
96
100
|
|
|
@@ -286,6 +286,9 @@ function buildVerifierPrompt({ analysis, toolExecutionSummary, evidenceSources,
|
|
|
286
286
|
'Return JSON only. No markdown, no prose, no code fences.',
|
|
287
287
|
'Verify whether the draft final reply is adequately supported by the gathered evidence.',
|
|
288
288
|
'If the evidence is insufficient, revise the reply so it states the uncertainty clearly instead of guessing.',
|
|
289
|
+
'Cross-check every concrete claim against tool status and output. Remove or rewrite claims that are contradicted by the evidence.',
|
|
290
|
+
'A non-zero execute_command exit code means partial or failed shell evidence. Do not treat later sections of a chained shell command as observed unless they were verified separately.',
|
|
291
|
+
'A successful send_message or make_call means outbound delivery succeeded in this run unless a later messaging tool failed.',
|
|
289
292
|
`Freshness risk: ${analysis.freshness_risk}`,
|
|
290
293
|
`Verification need: ${analysis.verification_need}`,
|
|
291
294
|
evidenceSources?.length ? `Evidence sources used: ${evidenceSources.join(', ')}` : 'Evidence sources used: none',
|
|
@@ -47,14 +47,22 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
|
|
|
47
47
|
envelope = trimObject({
|
|
48
48
|
tool: toolName,
|
|
49
49
|
status: toolResult?.timedOut ? 'timed_out' : (toolResult?.exitCode === 0 ? 'ok' : 'error'),
|
|
50
|
+
command: clampText(toolArgs.command || '', Math.floor(softLimit * 0.28)),
|
|
50
51
|
exitCode: toolResult?.exitCode,
|
|
51
52
|
cwd: toolResult?.cwd || toolArgs.cwd,
|
|
52
53
|
killed: toolResult?.killed || false,
|
|
53
54
|
timedOut: toolResult?.timedOut || false,
|
|
54
55
|
signal: toolResult?.signal,
|
|
55
56
|
durationMs: toolResult?.durationMs,
|
|
57
|
+
note: toolResult?.timedOut
|
|
58
|
+
? 'Command timed out. Treat the output as partial.'
|
|
59
|
+
: toolResult?.killed
|
|
60
|
+
? 'Command was killed. Treat the output as partial.'
|
|
61
|
+
: (toolResult?.exitCode !== undefined && toolResult?.exitCode !== 0)
|
|
62
|
+
? 'Command exited non-zero. Output may be partial; later segments of a chained shell command may not have run.'
|
|
63
|
+
: '',
|
|
56
64
|
stdout: lineExcerpt(toolResult?.stdout, 12, Math.floor(softLimit * 0.45)),
|
|
57
|
-
stderr: lineExcerpt(toolResult?.stderr,
|
|
65
|
+
stderr: lineExcerpt(toolResult?.stderr, 10, Math.floor(softLimit * 0.35))
|
|
58
66
|
});
|
|
59
67
|
break;
|
|
60
68
|
|
|
@@ -156,6 +164,27 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
|
|
|
156
164
|
|
|
157
165
|
case 'send_message':
|
|
158
166
|
case 'make_call':
|
|
167
|
+
envelope = trimObject({
|
|
168
|
+
tool: toolName,
|
|
169
|
+
status: toolResult?.skipped
|
|
170
|
+
? 'skipped'
|
|
171
|
+
: (toolResult?.success === false || toolResult?.error ? 'error' : 'ok'),
|
|
172
|
+
platform: toolArgs.platform,
|
|
173
|
+
to: toolArgs.to,
|
|
174
|
+
success: typeof toolResult?.success === 'boolean' ? toolResult.success : undefined,
|
|
175
|
+
skipped: toolResult?.skipped === true ? true : undefined,
|
|
176
|
+
sent: typeof toolResult?.sent === 'boolean' ? toolResult.sent : undefined,
|
|
177
|
+
suppressed: toolResult?.suppressed === true ? true : undefined,
|
|
178
|
+
message: clampText(toolResult?.message || toolResult?.reason || toolResult?.error || '', Math.floor(softLimit * 0.45)),
|
|
179
|
+
result: clampText(JSON.stringify(trimObject({
|
|
180
|
+
id: toolResult?.id,
|
|
181
|
+
key: toolResult?.key,
|
|
182
|
+
deleted: toolResult?.deleted,
|
|
183
|
+
count: Array.isArray(toolResult?.results) ? toolResult.results.length : undefined
|
|
184
|
+
})), Math.floor(softLimit * 0.3))
|
|
185
|
+
});
|
|
186
|
+
break;
|
|
187
|
+
|
|
159
188
|
case 'memory_save':
|
|
160
189
|
case 'memory_recall':
|
|
161
190
|
case 'memory_update_core':
|
|
@@ -13,7 +13,109 @@ const FIGMA_APPS = [
|
|
|
13
13
|
id: 'figma',
|
|
14
14
|
label: 'Figma',
|
|
15
15
|
description: 'Connect a Figma account for future official design and file tools.',
|
|
16
|
-
scopes: [
|
|
16
|
+
scopes: [
|
|
17
|
+
'current_user:read',
|
|
18
|
+
'file_content:read',
|
|
19
|
+
'file_metadata:read',
|
|
20
|
+
'file_comments:read',
|
|
21
|
+
'file_comments:write',
|
|
22
|
+
'library_content:read',
|
|
23
|
+
'library_assets:read',
|
|
24
|
+
'file_dev_resources:read',
|
|
25
|
+
'file_dev_resources:write',
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const figmaToolDefinitions = [
|
|
31
|
+
{
|
|
32
|
+
appId: 'figma',
|
|
33
|
+
name: 'figma_get_me',
|
|
34
|
+
description: 'Get the current Figma user.',
|
|
35
|
+
parameters: { type: 'object', properties: {} },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
appId: 'figma',
|
|
39
|
+
name: 'figma_get_file',
|
|
40
|
+
description: 'Read a Figma file JSON document.',
|
|
41
|
+
parameters: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
file_key: { type: 'string', description: 'Figma file key.' },
|
|
45
|
+
ids: { type: 'string', description: 'Optional comma-separated node IDs.' },
|
|
46
|
+
depth: { type: 'number', description: 'Optional traversal depth.' },
|
|
47
|
+
},
|
|
48
|
+
required: ['file_key'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
appId: 'figma',
|
|
53
|
+
name: 'figma_get_file_nodes',
|
|
54
|
+
description: 'Read specific nodes from a Figma file.',
|
|
55
|
+
parameters: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
file_key: { type: 'string', description: 'Figma file key.' },
|
|
59
|
+
ids: { type: 'string', description: 'Comma-separated node IDs.' },
|
|
60
|
+
},
|
|
61
|
+
required: ['file_key', 'ids'],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
appId: 'figma',
|
|
66
|
+
name: 'figma_get_file_images',
|
|
67
|
+
description: 'Render Figma nodes to image URLs.',
|
|
68
|
+
parameters: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
file_key: { type: 'string', description: 'Figma file key.' },
|
|
72
|
+
ids: { type: 'string', description: 'Comma-separated node IDs.' },
|
|
73
|
+
format: { type: 'string', description: 'jpg, png, svg, or pdf. Default png.' },
|
|
74
|
+
scale: { type: 'number', description: 'Optional image scale.' },
|
|
75
|
+
},
|
|
76
|
+
required: ['file_key', 'ids'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
appId: 'figma',
|
|
81
|
+
name: 'figma_get_comments',
|
|
82
|
+
description: 'List comments on a Figma file.',
|
|
83
|
+
parameters: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
file_key: { type: 'string', description: 'Figma file key.' },
|
|
87
|
+
},
|
|
88
|
+
required: ['file_key'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
appId: 'figma',
|
|
93
|
+
name: 'figma_post_comment',
|
|
94
|
+
description: 'Post a Figma file comment.',
|
|
95
|
+
parameters: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
file_key: { type: 'string', description: 'Figma file key.' },
|
|
99
|
+
message: { type: 'string', description: 'Comment message.' },
|
|
100
|
+
client_meta: { type: 'object', description: 'Optional Figma comment position metadata.' },
|
|
101
|
+
},
|
|
102
|
+
required: ['file_key', 'message'],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
appId: 'figma',
|
|
107
|
+
name: 'figma_api_request',
|
|
108
|
+
description: 'Make an authenticated Figma REST API request for advanced file, comment, library, variable, webhook, and dev resource operations.',
|
|
109
|
+
parameters: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
method: { type: 'string', description: 'HTTP method: GET, POST, PUT, PATCH, or DELETE.' },
|
|
113
|
+
path: { type: 'string', description: 'Figma API path, e.g. /v1/files/{key}.' },
|
|
114
|
+
query: { type: 'object', description: 'Optional query parameters.' },
|
|
115
|
+
body: { type: 'object', description: 'Optional JSON body.' },
|
|
116
|
+
},
|
|
117
|
+
required: ['method', 'path'],
|
|
118
|
+
},
|
|
17
119
|
},
|
|
18
120
|
];
|
|
19
121
|
|
|
@@ -28,6 +130,102 @@ function normalizeFigmaUser(profile) {
|
|
|
28
130
|
};
|
|
29
131
|
}
|
|
30
132
|
|
|
133
|
+
function requireText(value, label) {
|
|
134
|
+
const text = String(value || '').trim();
|
|
135
|
+
if (!text) throw new Error(`${label} is required.`);
|
|
136
|
+
return text;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function figmaUrl(path, query) {
|
|
140
|
+
const url = new URL(
|
|
141
|
+
String(path || '').startsWith('http')
|
|
142
|
+
? String(path)
|
|
143
|
+
: `https://api.figma.com${String(path || '').startsWith('/') ? '' : '/'}${path}`,
|
|
144
|
+
);
|
|
145
|
+
if (url.hostname !== 'api.figma.com') {
|
|
146
|
+
throw new Error('Figma API request URL must target api.figma.com.');
|
|
147
|
+
}
|
|
148
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
149
|
+
if (value !== undefined && value !== null) url.searchParams.set(key, String(value));
|
|
150
|
+
}
|
|
151
|
+
return url.toString();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function figmaRequest(credentials, { method = 'GET', path, query, body }) {
|
|
155
|
+
return fetchJson(
|
|
156
|
+
figmaUrl(path, query),
|
|
157
|
+
{
|
|
158
|
+
method: String(method || 'GET').toUpperCase(),
|
|
159
|
+
headers: { Authorization: `Bearer ${credentials.access_token}` },
|
|
160
|
+
...(body === undefined ? {} : { json: body }),
|
|
161
|
+
},
|
|
162
|
+
{ serviceName: 'Figma' },
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function executeFigmaTool(toolName, args, { credentials }) {
|
|
167
|
+
switch (toolName) {
|
|
168
|
+
case 'figma_get_me':
|
|
169
|
+
return { result: await figmaRequest(credentials, { path: '/v1/me' }) };
|
|
170
|
+
case 'figma_get_file':
|
|
171
|
+
return {
|
|
172
|
+
result: await figmaRequest(credentials, {
|
|
173
|
+
path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}`,
|
|
174
|
+
query: {
|
|
175
|
+
ids: args.ids || undefined,
|
|
176
|
+
depth: args.depth || undefined,
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
};
|
|
180
|
+
case 'figma_get_file_nodes':
|
|
181
|
+
return {
|
|
182
|
+
result: await figmaRequest(credentials, {
|
|
183
|
+
path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}/nodes`,
|
|
184
|
+
query: { ids: requireText(args.ids, 'ids') },
|
|
185
|
+
}),
|
|
186
|
+
};
|
|
187
|
+
case 'figma_get_file_images':
|
|
188
|
+
return {
|
|
189
|
+
result: await figmaRequest(credentials, {
|
|
190
|
+
path: `/v1/images/${encodeURIComponent(requireText(args.file_key, 'file_key'))}`,
|
|
191
|
+
query: {
|
|
192
|
+
ids: requireText(args.ids, 'ids'),
|
|
193
|
+
format: String(args.format || 'png'),
|
|
194
|
+
scale: args.scale || undefined,
|
|
195
|
+
},
|
|
196
|
+
}),
|
|
197
|
+
};
|
|
198
|
+
case 'figma_get_comments':
|
|
199
|
+
return {
|
|
200
|
+
result: await figmaRequest(credentials, {
|
|
201
|
+
path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}/comments`,
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
case 'figma_post_comment':
|
|
205
|
+
return {
|
|
206
|
+
result: await figmaRequest(credentials, {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
path: `/v1/files/${encodeURIComponent(requireText(args.file_key, 'file_key'))}/comments`,
|
|
209
|
+
body: {
|
|
210
|
+
message: requireText(args.message, 'message'),
|
|
211
|
+
client_meta: args.client_meta || undefined,
|
|
212
|
+
},
|
|
213
|
+
}),
|
|
214
|
+
};
|
|
215
|
+
case 'figma_api_request':
|
|
216
|
+
return {
|
|
217
|
+
result: await figmaRequest(credentials, {
|
|
218
|
+
method: args.method,
|
|
219
|
+
path: requireText(args.path, 'path'),
|
|
220
|
+
query: args.query,
|
|
221
|
+
body: args.body,
|
|
222
|
+
}),
|
|
223
|
+
};
|
|
224
|
+
default:
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
31
229
|
function createFigmaProvider() {
|
|
32
230
|
return createOAuthProvider({
|
|
33
231
|
key: 'figma',
|
|
@@ -36,6 +234,7 @@ function createFigmaProvider() {
|
|
|
36
234
|
'Official Figma OAuth account connections for future design file and collaboration workflows.',
|
|
37
235
|
icon: 'figma',
|
|
38
236
|
apps: FIGMA_APPS,
|
|
237
|
+
toolDefinitions: figmaToolDefinitions,
|
|
39
238
|
connectPrompt:
|
|
40
239
|
'This enables the official Figma account layer now. Native Figma tools are not shipped yet in this run.',
|
|
41
240
|
getEnvStatus() {
|
|
@@ -58,13 +257,17 @@ function createFigmaProvider() {
|
|
|
58
257
|
},
|
|
59
258
|
async finishOAuth({ code, app }) {
|
|
60
259
|
const config = resolveFigmaOAuthConfig();
|
|
260
|
+
const basic = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString(
|
|
261
|
+
'base64',
|
|
262
|
+
);
|
|
61
263
|
const token = await fetchJson(
|
|
62
264
|
'https://api.figma.com/v1/oauth/token',
|
|
63
265
|
{
|
|
64
266
|
method: 'POST',
|
|
267
|
+
headers: {
|
|
268
|
+
Authorization: `Basic ${basic}`,
|
|
269
|
+
},
|
|
65
270
|
form: {
|
|
66
|
-
client_id: config.clientId,
|
|
67
|
-
client_secret: config.clientSecret,
|
|
68
271
|
redirect_uri: config.redirectUri,
|
|
69
272
|
code,
|
|
70
273
|
grant_type: 'authorization_code',
|
|
@@ -109,6 +312,7 @@ function createFigmaProvider() {
|
|
|
109
312
|
},
|
|
110
313
|
};
|
|
111
314
|
},
|
|
315
|
+
executeTool: executeFigmaTool,
|
|
112
316
|
});
|
|
113
317
|
}
|
|
114
318
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { google } = require('googleapis');
|
|
4
|
-
const { coerceStringList } = require('./common');
|
|
4
|
+
const { coerceStringList, executeGoogleApiRequest } = require('./common');
|
|
5
5
|
|
|
6
6
|
const calendarToolDefinitions = [
|
|
7
7
|
{
|
|
@@ -117,6 +117,25 @@ const calendarToolDefinitions = [
|
|
|
117
117
|
required: ['time_min', 'time_max'],
|
|
118
118
|
},
|
|
119
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: 'google_workspace_calendar_api_request',
|
|
122
|
+
description:
|
|
123
|
+
'Make an authenticated Google Calendar API request for advanced calendar operations not covered by the dedicated tools.',
|
|
124
|
+
parameters: {
|
|
125
|
+
type: 'object',
|
|
126
|
+
properties: {
|
|
127
|
+
method: { type: 'string', description: 'HTTP method: GET, POST, PUT, PATCH, or DELETE.' },
|
|
128
|
+
path: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
description:
|
|
131
|
+
'Calendar API path or URL, e.g. /calendar/v3/users/me/calendarList.',
|
|
132
|
+
},
|
|
133
|
+
query: { type: 'object', description: 'Optional query parameters.' },
|
|
134
|
+
body: { type: 'object', description: 'Optional JSON request body.' },
|
|
135
|
+
},
|
|
136
|
+
required: ['method', 'path'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
120
139
|
];
|
|
121
140
|
|
|
122
141
|
function summarizeEvent(event) {
|
|
@@ -241,6 +260,12 @@ async function executeCalendarTool(toolName, args, auth) {
|
|
|
241
260
|
return { calendars: response.data.calendars || {} };
|
|
242
261
|
}
|
|
243
262
|
|
|
263
|
+
case 'google_workspace_calendar_api_request': {
|
|
264
|
+
return executeGoogleApiRequest(auth, args, {
|
|
265
|
+
baseUrl: 'https://www.googleapis.com',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
244
269
|
default:
|
|
245
270
|
return null;
|
|
246
271
|
}
|
|
@@ -96,9 +96,48 @@ function summarizeFile(file) {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function requireGoogleApiUrl(pathOrUrl, defaultBaseUrl) {
|
|
100
|
+
const raw = String(pathOrUrl || '').trim();
|
|
101
|
+
if (!raw) throw new Error('path is required.');
|
|
102
|
+
const base = String(defaultBaseUrl || 'https://www.googleapis.com').replace(/\/$/, '');
|
|
103
|
+
const url = raw.startsWith('http://') || raw.startsWith('https://')
|
|
104
|
+
? new URL(raw)
|
|
105
|
+
: new URL(raw.startsWith('/') ? raw : `/${raw}`, base);
|
|
106
|
+
if (!url.hostname.endsWith('googleapis.com')) {
|
|
107
|
+
throw new Error('Google API request URL must target a googleapis.com host.');
|
|
108
|
+
}
|
|
109
|
+
return url.toString();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function executeGoogleApiRequest(auth, args, options = {}) {
|
|
113
|
+
const method = String(args.method || 'GET').trim().toUpperCase();
|
|
114
|
+
const allowedMethods = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
115
|
+
if (!allowedMethods.has(method)) {
|
|
116
|
+
throw new Error('method must be one of GET, POST, PUT, PATCH, DELETE.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const response = await auth.request({
|
|
120
|
+
url: requireGoogleApiUrl(args.path || args.url, options.baseUrl),
|
|
121
|
+
method,
|
|
122
|
+
params: args.query && typeof args.query === 'object' ? args.query : undefined,
|
|
123
|
+
data: args.body === undefined ? undefined : args.body,
|
|
124
|
+
headers: args.headers && typeof args.headers === 'object' ? args.headers : undefined,
|
|
125
|
+
responseType: args.response_type === 'arraybuffer' ? 'arraybuffer' : undefined,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
status: response.status,
|
|
130
|
+
statusText: response.statusText,
|
|
131
|
+
data: Buffer.isBuffer(response.data)
|
|
132
|
+
? response.data.toString('base64')
|
|
133
|
+
: response.data,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
99
137
|
module.exports = {
|
|
100
138
|
coerceStringList,
|
|
101
139
|
ensureParentDir,
|
|
140
|
+
executeGoogleApiRequest,
|
|
102
141
|
extractMessageBody,
|
|
103
142
|
getHeader,
|
|
104
143
|
stringToBase64Url,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { google } = require('googleapis');
|
|
4
|
+
const { executeGoogleApiRequest } = require('./common');
|
|
4
5
|
|
|
5
6
|
const docsToolDefinitions = [
|
|
6
7
|
{
|
|
@@ -51,6 +52,25 @@ const docsToolDefinitions = [
|
|
|
51
52
|
required: ['document_id', 'search_text', 'replace_text'],
|
|
52
53
|
},
|
|
53
54
|
},
|
|
55
|
+
{
|
|
56
|
+
name: 'google_workspace_docs_api_request',
|
|
57
|
+
description:
|
|
58
|
+
'Make an authenticated Google Docs API request for advanced document batchUpdate operations not covered by the dedicated tools.',
|
|
59
|
+
parameters: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
method: { type: 'string', description: 'HTTP method: GET, POST, PUT, PATCH, or DELETE.' },
|
|
63
|
+
path: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description:
|
|
66
|
+
'Docs API path or URL, e.g. /v1/documents/{documentId}:batchUpdate.',
|
|
67
|
+
},
|
|
68
|
+
query: { type: 'object', description: 'Optional query parameters.' },
|
|
69
|
+
body: { type: 'object', description: 'Optional JSON request body.' },
|
|
70
|
+
},
|
|
71
|
+
required: ['method', 'path'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
54
74
|
];
|
|
55
75
|
|
|
56
76
|
function documentToText(document) {
|
|
@@ -176,6 +196,12 @@ async function executeDocsTool(toolName, args, auth) {
|
|
|
176
196
|
};
|
|
177
197
|
}
|
|
178
198
|
|
|
199
|
+
case 'google_workspace_docs_api_request': {
|
|
200
|
+
return executeGoogleApiRequest(auth, args, {
|
|
201
|
+
baseUrl: 'https://docs.googleapis.com',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
179
205
|
default:
|
|
180
206
|
return null;
|
|
181
207
|
}
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { google } = require('googleapis');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
coerceStringList,
|
|
8
|
+
ensureParentDir,
|
|
9
|
+
executeGoogleApiRequest,
|
|
10
|
+
summarizeFile,
|
|
11
|
+
} = require('./common');
|
|
7
12
|
|
|
8
13
|
const driveToolDefinitions = [
|
|
9
14
|
{
|
|
@@ -101,6 +106,25 @@ const driveToolDefinitions = [
|
|
|
101
106
|
required: ['file_id'],
|
|
102
107
|
},
|
|
103
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'google_workspace_drive_api_request',
|
|
111
|
+
description:
|
|
112
|
+
'Make an authenticated Google Drive API request for advanced file, folder, permission, comment, revision, and shared drive operations.',
|
|
113
|
+
parameters: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
method: { type: 'string', description: 'HTTP method: GET, POST, PUT, PATCH, or DELETE.' },
|
|
117
|
+
path: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description:
|
|
120
|
+
'Drive API path or URL, e.g. /drive/v3/files or /drive/v3/files/{fileId}/comments.',
|
|
121
|
+
},
|
|
122
|
+
query: { type: 'object', description: 'Optional query parameters.' },
|
|
123
|
+
body: { type: 'object', description: 'Optional JSON request body.' },
|
|
124
|
+
},
|
|
125
|
+
required: ['method', 'path'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
104
128
|
];
|
|
105
129
|
|
|
106
130
|
async function validateExistingReadableFilePath(filePath) {
|
|
@@ -235,6 +259,12 @@ async function executeDriveTool(toolName, args, auth) {
|
|
|
235
259
|
return summarizeFile(fileResponse.data);
|
|
236
260
|
}
|
|
237
261
|
|
|
262
|
+
case 'google_workspace_drive_api_request': {
|
|
263
|
+
return executeGoogleApiRequest(auth, args, {
|
|
264
|
+
baseUrl: 'https://www.googleapis.com',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
238
268
|
default:
|
|
239
269
|
return null;
|
|
240
270
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { google } = require('googleapis');
|
|
4
4
|
const {
|
|
5
5
|
coerceStringList,
|
|
6
|
+
executeGoogleApiRequest,
|
|
6
7
|
extractMessageBody,
|
|
7
8
|
getHeader,
|
|
8
9
|
stringToBase64Url,
|
|
@@ -113,6 +114,25 @@ const gmailToolDefinitions = [
|
|
|
113
114
|
required: ['thread_id'],
|
|
114
115
|
},
|
|
115
116
|
},
|
|
117
|
+
{
|
|
118
|
+
name: 'google_workspace_gmail_api_request',
|
|
119
|
+
description:
|
|
120
|
+
'Make an authenticated Gmail API request for advanced Gmail operations not covered by the dedicated Gmail tools.',
|
|
121
|
+
parameters: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
method: { type: 'string', description: 'HTTP method: GET, POST, PUT, PATCH, or DELETE.' },
|
|
125
|
+
path: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
description:
|
|
128
|
+
'Gmail API path or URL, e.g. /gmail/v1/users/me/settings/sendAs.',
|
|
129
|
+
},
|
|
130
|
+
query: { type: 'object', description: 'Optional query parameters.' },
|
|
131
|
+
body: { type: 'object', description: 'Optional JSON request body.' },
|
|
132
|
+
},
|
|
133
|
+
required: ['method', 'path'],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
116
136
|
];
|
|
117
137
|
|
|
118
138
|
function summarizeMessage(message) {
|
|
@@ -294,6 +314,12 @@ async function executeGmailTool(toolName, args, auth) {
|
|
|
294
314
|
};
|
|
295
315
|
}
|
|
296
316
|
|
|
317
|
+
case 'google_workspace_gmail_api_request': {
|
|
318
|
+
return executeGoogleApiRequest(auth, args, {
|
|
319
|
+
baseUrl: 'https://gmail.googleapis.com',
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
297
323
|
default:
|
|
298
324
|
return null;
|
|
299
325
|
}
|
|
@@ -21,7 +21,7 @@ const GOOGLE_WORKSPACE_APPS = [
|
|
|
21
21
|
id: 'gmail',
|
|
22
22
|
label: 'Gmail',
|
|
23
23
|
description: 'Search threads, read messages, send mail, and manage labels.',
|
|
24
|
-
scopes: ['https://
|
|
24
|
+
scopes: ['https://mail.google.com/'],
|
|
25
25
|
toolDefinitions: gmailToolDefinitions,
|
|
26
26
|
executor: executeGmailTool,
|
|
27
27
|
},
|