@fload-ai/mcp 0.2.1 → 0.2.2
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/README.md +1 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/api-client.d.ts +5 -4
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +59 -6
- package/dist/api-client.js.map +3 -3
- package/dist/bin.js +450 -184
- package/dist/bin.js.map +4 -4
- package/dist/index.js +450 -184
- package/dist/index.js.map +4 -4
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/public-errors.d.ts +2 -0
- package/dist/public-errors.d.ts.map +1 -0
- package/dist/tools/actions.d.ts +0 -3
- package/dist/tools/actions.d.ts.map +1 -1
- package/dist/tools/ads.d.ts +2 -2
- package/dist/tools/ads.d.ts.map +1 -1
- package/dist/tools/agents.d.ts +4 -4
- package/dist/tools/agents.d.ts.map +1 -1
- package/dist/tools/analytics.d.ts +2 -2
- package/dist/tools/analytics.d.ts.map +1 -1
- package/dist/tools/anomalies.d.ts +2 -2
- package/dist/tools/anomalies.d.ts.map +1 -1
- package/dist/tools/apps.d.ts +2 -2
- package/dist/tools/apps.d.ts.map +1 -1
- package/dist/tools/aso.d.ts +14 -5
- package/dist/tools/aso.d.ts.map +1 -1
- package/dist/tools/chat.d.ts +7 -9
- package/dist/tools/chat.d.ts.map +1 -1
- package/dist/tools/dashboard.d.ts.map +1 -1
- package/dist/tools/forecasting.d.ts +9 -3
- package/dist/tools/forecasting.d.ts.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +392 -178
- package/dist/tools/index.js.map +3 -3
- package/dist/tools/reviews.d.ts +19 -4
- package/dist/tools/reviews.d.ts.map +1 -1
- package/package.json +11 -13
package/dist/index.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
// src/public-errors.ts
|
|
2
|
+
var INTERNAL_ERROR_PATTERNS = [
|
|
3
|
+
/\b(stack|trace|at\s+[\w.$<>]+\s*\()/i,
|
|
4
|
+
/\b(sql|postgres|drizzle|database|constraint|duplicate key|violates|relation)\b/i,
|
|
5
|
+
/\b(fetch|undici|socket|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|HTTP\s+\d{3})\b/i,
|
|
6
|
+
/\b(token|secret|api[_-]?key|authorization|bearer|cookie|password)\b/i,
|
|
7
|
+
/\b[a-z0-9]+_(secret|token|key)\b/i,
|
|
8
|
+
/\b(secret|token|key)[_-][a-z0-9]+\b/i,
|
|
9
|
+
/\b(scrap|browser|patchright|playwright|chromium|page\.|locator|selector)\b/i,
|
|
10
|
+
/\b(redis|bullmq|queue|job id|worker)\b/i
|
|
11
|
+
];
|
|
12
|
+
function looksInternal(message) {
|
|
13
|
+
return INTERNAL_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
14
|
+
}
|
|
15
|
+
function publicMcpErrorMessage(message, fallbackMessage = "Fload could not complete the request. Please try again.") {
|
|
16
|
+
const raw = message instanceof Error ? message.message : typeof message === "string" ? message : "";
|
|
17
|
+
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
18
|
+
if (!normalized || normalized.length > 180 || looksInternal(normalized)) {
|
|
19
|
+
return fallbackMessage;
|
|
20
|
+
}
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
|
|
1
24
|
// src/api-client.ts
|
|
2
25
|
var FloadApiClient = class {
|
|
3
26
|
constructor(baseUrl, apiKey) {
|
|
@@ -6,17 +29,22 @@ var FloadApiClient = class {
|
|
|
6
29
|
}
|
|
7
30
|
async request(path, options = {}) {
|
|
8
31
|
const url = `${this.baseUrl}${path}`;
|
|
32
|
+
const headers = {
|
|
33
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
34
|
+
...options.body ? { "Content-Type": "application/json" } : {},
|
|
35
|
+
...options.headers
|
|
36
|
+
};
|
|
9
37
|
const response = await fetch(url, {
|
|
10
38
|
...options,
|
|
11
|
-
headers
|
|
12
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
13
|
-
"Content-Type": "application/json",
|
|
14
|
-
...options.headers
|
|
15
|
-
}
|
|
39
|
+
headers
|
|
16
40
|
});
|
|
17
41
|
if (!response.ok) {
|
|
18
42
|
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
19
|
-
|
|
43
|
+
const safeError = publicMcpErrorMessage(
|
|
44
|
+
error.publicMessage || error.message || response.statusText,
|
|
45
|
+
response.status >= 500 ? "Fload could not complete the request. Please try again." : "Fload rejected the request. Please check access and try again."
|
|
46
|
+
);
|
|
47
|
+
throw new Error(safeError);
|
|
20
48
|
}
|
|
21
49
|
return response.json();
|
|
22
50
|
}
|
|
@@ -46,6 +74,31 @@ var FloadApiClient = class {
|
|
|
46
74
|
async delete(path) {
|
|
47
75
|
return this.request(path, { method: "DELETE" });
|
|
48
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* POST to a streaming endpoint (SSE / AI SDK UI-message stream) and
|
|
79
|
+
* return the raw response body as text. Callers parse the event lines.
|
|
80
|
+
*/
|
|
81
|
+
async postStream(path, body) {
|
|
82
|
+
const url = `${this.baseUrl}${path}`;
|
|
83
|
+
const response = await fetch(url, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
Accept: "text/event-stream"
|
|
89
|
+
},
|
|
90
|
+
body: body ? JSON.stringify(body) : void 0
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
94
|
+
const safeError = publicMcpErrorMessage(
|
|
95
|
+
error.publicMessage || error.message || response.statusText,
|
|
96
|
+
response.status >= 500 ? "Fload could not complete the request. Please try again." : "Fload rejected the request. Please check access and try again."
|
|
97
|
+
);
|
|
98
|
+
throw new Error(safeError);
|
|
99
|
+
}
|
|
100
|
+
return response.text();
|
|
101
|
+
}
|
|
49
102
|
};
|
|
50
103
|
|
|
51
104
|
// src/server.ts
|
|
@@ -106,9 +159,12 @@ async function listApps(input, client) {
|
|
|
106
159
|
try {
|
|
107
160
|
const response = await client.get("/api/assets", {
|
|
108
161
|
limit: input.limit,
|
|
109
|
-
offset: 0
|
|
162
|
+
offset: 0,
|
|
163
|
+
// The listing hides assets without a valuation by default; "list all
|
|
164
|
+
// apps" must include apps that haven't been valued yet (FLO-734).
|
|
165
|
+
includeUnvalued: "true"
|
|
110
166
|
});
|
|
111
|
-
const apps = response.data
|
|
167
|
+
const apps = response.data ?? [];
|
|
112
168
|
const filtered = input.platform ? apps.filter((app) => {
|
|
113
169
|
if (input.platform === "ios")
|
|
114
170
|
return !!app.appleAppId;
|
|
@@ -152,8 +208,14 @@ async function getAppDetails(input, client) {
|
|
|
152
208
|
}
|
|
153
209
|
let assetId = input.assetId;
|
|
154
210
|
if (!assetId && input.bundleId) {
|
|
155
|
-
const allApps = await client.get("/api/assets", {
|
|
156
|
-
|
|
211
|
+
const allApps = await client.get("/api/assets", {
|
|
212
|
+
limit: 100,
|
|
213
|
+
offset: 0,
|
|
214
|
+
includeUnvalued: "true"
|
|
215
|
+
});
|
|
216
|
+
const match = (allApps.data ?? []).find(
|
|
217
|
+
(app2) => app2.bundleId === input.bundleId
|
|
218
|
+
);
|
|
157
219
|
if (!match) {
|
|
158
220
|
return {
|
|
159
221
|
content: [
|
|
@@ -167,8 +229,13 @@ async function getAppDetails(input, client) {
|
|
|
167
229
|
}
|
|
168
230
|
assetId = match.id;
|
|
169
231
|
}
|
|
170
|
-
const response = await client.get(
|
|
171
|
-
|
|
232
|
+
const response = await client.get(
|
|
233
|
+
`/api/assets/${assetId}`
|
|
234
|
+
);
|
|
235
|
+
const app = response.data;
|
|
236
|
+
if (!app) {
|
|
237
|
+
throw new Error("App details response did not include app data");
|
|
238
|
+
}
|
|
172
239
|
return {
|
|
173
240
|
content: [
|
|
174
241
|
{
|
|
@@ -225,14 +292,27 @@ var sendReviewReplySchema = z2.object({
|
|
|
225
292
|
});
|
|
226
293
|
var translateReviewSchema = z2.object({
|
|
227
294
|
reviewId: z2.string().describe("The review UUID to translate"),
|
|
228
|
-
assetId: z2.string().uuid().describe("The app UUID the review belongs to")
|
|
295
|
+
assetId: z2.string().uuid().describe("The app UUID the review belongs to"),
|
|
296
|
+
// The translate endpoint receives the review content in the request body
|
|
297
|
+
// (it does not re-fetch it). Pass the fields from a get_reviews result.
|
|
298
|
+
rating: z2.number().int().min(1).max(5).describe("The review star rating, from get_reviews"),
|
|
299
|
+
title: z2.string().default("").describe("The review title, from get_reviews"),
|
|
300
|
+
body: z2.string().describe("The review text to translate, from get_reviews"),
|
|
301
|
+
storeFront: z2.string().default("").describe('The review storefront (e.g. "SE"), from get_reviews'),
|
|
302
|
+
appVersionString: z2.string().default("").describe("The app version the review was left on, from get_reviews")
|
|
229
303
|
});
|
|
230
304
|
async function getReviews(input, client) {
|
|
231
305
|
try {
|
|
232
306
|
let assetId = input.assetId;
|
|
233
307
|
if (!assetId && input.bundleId) {
|
|
234
|
-
const allApps = await client.get("/api/assets", {
|
|
235
|
-
|
|
308
|
+
const allApps = await client.get("/api/assets", {
|
|
309
|
+
limit: 100,
|
|
310
|
+
offset: 0,
|
|
311
|
+
includeUnvalued: "true"
|
|
312
|
+
});
|
|
313
|
+
const match = (allApps.data ?? []).find(
|
|
314
|
+
(app) => app.bundleId === input.bundleId
|
|
315
|
+
);
|
|
236
316
|
if (!match) {
|
|
237
317
|
return {
|
|
238
318
|
content: [
|
|
@@ -246,12 +326,20 @@ async function getReviews(input, client) {
|
|
|
246
326
|
}
|
|
247
327
|
assetId = match.id;
|
|
248
328
|
}
|
|
329
|
+
if (!assetId) {
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: "Provide an assetId (or bundleId) to fetch reviews for. Use list_apps to find app IDs."
|
|
335
|
+
}
|
|
336
|
+
],
|
|
337
|
+
isError: true
|
|
338
|
+
};
|
|
339
|
+
}
|
|
249
340
|
const params = {
|
|
250
|
-
limit: input.limit
|
|
251
|
-
sortBy: input.sortBy
|
|
341
|
+
limit: input.limit
|
|
252
342
|
};
|
|
253
|
-
if (assetId)
|
|
254
|
-
params.assetId = assetId;
|
|
255
343
|
if (input.platform)
|
|
256
344
|
params.platform = input.platform;
|
|
257
345
|
if (input.rating !== void 0)
|
|
@@ -262,13 +350,25 @@ async function getReviews(input, client) {
|
|
|
262
350
|
params.startDate = input.startDate;
|
|
263
351
|
if (input.endDate)
|
|
264
352
|
params.endDate = input.endDate;
|
|
265
|
-
const response = await client.get(
|
|
266
|
-
|
|
353
|
+
const response = await client.get(
|
|
354
|
+
`/api/assets/${assetId}/reviews`,
|
|
355
|
+
params
|
|
356
|
+
);
|
|
357
|
+
let reviews = response.data?.reviews ?? [];
|
|
358
|
+
if (input.sortBy === "rating") {
|
|
359
|
+
reviews = [...reviews].sort(
|
|
360
|
+
(a, b) => Number(b.rating ?? 0) - Number(a.rating ?? 0)
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
const hasReply = (review) => !!(review.developerResponse || review.replySource || review.hasReply);
|
|
267
364
|
const summary = {
|
|
268
365
|
totalReviews: reviews.length,
|
|
269
|
-
averageRating: reviews.length > 0 ? (reviews.reduce((sum,
|
|
270
|
-
|
|
271
|
-
|
|
366
|
+
averageRating: reviews.length > 0 ? (reviews.reduce((sum, review) => {
|
|
367
|
+
const rating = Number(review.rating ?? 0);
|
|
368
|
+
return sum + (Number.isFinite(rating) ? rating : 0);
|
|
369
|
+
}, 0) / reviews.length).toFixed(2) : 0,
|
|
370
|
+
repliedCount: reviews.filter((review) => hasReply(review)).length,
|
|
371
|
+
unrepliedCount: reviews.filter((review) => !hasReply(review)).length
|
|
272
372
|
};
|
|
273
373
|
return {
|
|
274
374
|
content: [
|
|
@@ -276,19 +376,23 @@ async function getReviews(input, client) {
|
|
|
276
376
|
type: "text",
|
|
277
377
|
text: formatAsJson({
|
|
278
378
|
summary,
|
|
279
|
-
reviews: reviews.map((
|
|
280
|
-
id:
|
|
281
|
-
appId:
|
|
282
|
-
platform:
|
|
283
|
-
rating:
|
|
284
|
-
title:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
379
|
+
reviews: reviews.map((review) => ({
|
|
380
|
+
id: review.id,
|
|
381
|
+
appId: review.appId,
|
|
382
|
+
platform: review.platform,
|
|
383
|
+
rating: review.rating,
|
|
384
|
+
title: review.title,
|
|
385
|
+
// The handler exposes the review text as `review`.
|
|
386
|
+
body: review.review ?? review.body,
|
|
387
|
+
author: review.nickname || review.author,
|
|
388
|
+
// Epoch ms from the handler.
|
|
389
|
+
date: review.lastModified || review.date,
|
|
390
|
+
version: review.appVersionString || review.version,
|
|
391
|
+
storeFront: review.storeFront,
|
|
392
|
+
hasReply: hasReply(review),
|
|
393
|
+
replySource: review.replySource ?? null,
|
|
394
|
+
hasDraft: review.hasDraft ?? false,
|
|
395
|
+
reply: review.developerResponse ? typeof review.developerResponse === "object" ? review.developerResponse.response : review.developerResponse : review.reply || null
|
|
292
396
|
}))
|
|
293
397
|
})
|
|
294
398
|
}
|
|
@@ -308,9 +412,10 @@ async function getReviews(input, client) {
|
|
|
308
412
|
}
|
|
309
413
|
async function generateReviewReply(input, client) {
|
|
310
414
|
try {
|
|
311
|
-
const response = await client.post(
|
|
312
|
-
assetId
|
|
313
|
-
|
|
415
|
+
const response = await client.post(
|
|
416
|
+
`/api/assets/${input.assetId}/reviews/${input.reviewId}/generate-reply`,
|
|
417
|
+
void 0
|
|
418
|
+
);
|
|
314
419
|
return {
|
|
315
420
|
content: [
|
|
316
421
|
{
|
|
@@ -333,10 +438,11 @@ async function generateReviewReply(input, client) {
|
|
|
333
438
|
}
|
|
334
439
|
async function sendReviewReply(input, client) {
|
|
335
440
|
try {
|
|
336
|
-
const response = await client.post(
|
|
337
|
-
assetId
|
|
338
|
-
|
|
339
|
-
|
|
441
|
+
const response = await client.post(
|
|
442
|
+
`/api/assets/${input.assetId}/reviews/${input.reviewId}/respond`,
|
|
443
|
+
// The API handler reads `responseText` from the body.
|
|
444
|
+
{ responseText: input.response }
|
|
445
|
+
);
|
|
340
446
|
return {
|
|
341
447
|
content: [
|
|
342
448
|
{
|
|
@@ -359,9 +465,18 @@ async function sendReviewReply(input, client) {
|
|
|
359
465
|
}
|
|
360
466
|
async function translateReview(input, client) {
|
|
361
467
|
try {
|
|
362
|
-
const response = await client.post(
|
|
363
|
-
assetId
|
|
364
|
-
|
|
468
|
+
const response = await client.post(
|
|
469
|
+
`/api/assets/${input.assetId}/reviews/${input.reviewId}/translate`,
|
|
470
|
+
// The translate endpoint expects the review content in the body.
|
|
471
|
+
{
|
|
472
|
+
rating: input.rating,
|
|
473
|
+
title: input.title,
|
|
474
|
+
review: input.body,
|
|
475
|
+
storeFront: input.storeFront,
|
|
476
|
+
appVersionString: input.appVersionString,
|
|
477
|
+
lastModified: Date.now()
|
|
478
|
+
}
|
|
479
|
+
);
|
|
365
480
|
return {
|
|
366
481
|
content: [
|
|
367
482
|
{
|
|
@@ -441,9 +556,11 @@ var DIMENSION_INFO = {
|
|
|
441
556
|
};
|
|
442
557
|
async function discoverMetrics(input, client) {
|
|
443
558
|
try {
|
|
444
|
-
const response = await client.get(
|
|
445
|
-
|
|
446
|
-
|
|
559
|
+
const response = await client.get(
|
|
560
|
+
`/api/assets/${input.assetId}/metrics/availability`
|
|
561
|
+
);
|
|
562
|
+
const availableMetrics = response.data?.availableMetrics ?? [];
|
|
563
|
+
const metricGroups = response.data?.metricGroups ?? {};
|
|
447
564
|
const enriched = availableMetrics.map((metricName) => {
|
|
448
565
|
const info = METRIC_INFO[metricName];
|
|
449
566
|
return {
|
|
@@ -494,10 +611,13 @@ async function getMetrics(input, client) {
|
|
|
494
611
|
if (input.dimensionFilter && input.dimension) {
|
|
495
612
|
params[`filter.${input.dimension}`] = input.dimensionFilter;
|
|
496
613
|
}
|
|
497
|
-
const response = await client.get(
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
614
|
+
const response = await client.get(
|
|
615
|
+
`/api/assets/${input.assetId}/metrics/timeseries`,
|
|
616
|
+
params
|
|
617
|
+
);
|
|
618
|
+
const data = response.data ?? {};
|
|
619
|
+
const changes = response.changes ?? {};
|
|
620
|
+
const meta = response.meta ?? {};
|
|
501
621
|
const summaries = {};
|
|
502
622
|
for (const [metricName, timeseries] of Object.entries(data)) {
|
|
503
623
|
const points = timeseries;
|
|
@@ -553,17 +673,20 @@ async function discoverDimensions(input, client) {
|
|
|
553
673
|
assetId: input.assetId,
|
|
554
674
|
dimension: input.dimension,
|
|
555
675
|
displayName: DIMENSION_INFO[input.dimension] || input.dimension,
|
|
556
|
-
values: response2.data
|
|
676
|
+
values: response2.data ?? response2
|
|
557
677
|
})
|
|
558
678
|
}]
|
|
559
679
|
};
|
|
560
680
|
}
|
|
561
681
|
const response = await client.get(`/api/assets/${input.assetId}/metrics/dimensions`);
|
|
562
|
-
const dimensions = response.data
|
|
563
|
-
const enriched =
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
682
|
+
const dimensions = Array.isArray(response.data) ? response.data : [];
|
|
683
|
+
const enriched = dimensions.map((dim) => {
|
|
684
|
+
if (typeof dim === "string") {
|
|
685
|
+
return { name: dim, displayName: DIMENSION_INFO[dim] || dim };
|
|
686
|
+
}
|
|
687
|
+
const id = dim.id ?? "";
|
|
688
|
+
return { name: id, displayName: dim.label ?? DIMENSION_INFO[id] ?? id };
|
|
689
|
+
});
|
|
567
690
|
return {
|
|
568
691
|
content: [{
|
|
569
692
|
type: "text",
|
|
@@ -581,6 +704,9 @@ async function discoverDimensions(input, client) {
|
|
|
581
704
|
|
|
582
705
|
// src/tools/agents.ts
|
|
583
706
|
import { z as z4 } from "zod";
|
|
707
|
+
function asRecord(value) {
|
|
708
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
709
|
+
}
|
|
584
710
|
var AGENT_TYPES = [
|
|
585
711
|
"review",
|
|
586
712
|
"monitoring",
|
|
@@ -629,7 +755,7 @@ async function listAgents(_input, client) {
|
|
|
629
755
|
let apiAgents = null;
|
|
630
756
|
try {
|
|
631
757
|
const response = await client.get("/api/agents");
|
|
632
|
-
apiAgents = response.data
|
|
758
|
+
apiAgents = Array.isArray(response) ? response : response.data ?? response.agents ?? null;
|
|
633
759
|
} catch {
|
|
634
760
|
}
|
|
635
761
|
if (apiAgents && Array.isArray(apiAgents)) {
|
|
@@ -679,21 +805,26 @@ async function getAgentDetails(input, client) {
|
|
|
679
805
|
const params = {};
|
|
680
806
|
if (input.assetId)
|
|
681
807
|
params.assetId = input.assetId;
|
|
682
|
-
const response2 = await client.get(`/api/agents/${input.agentType}`, params);
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
808
|
+
const response2 = await client.get(`/api/agents/by-type/${input.agentType}`, params);
|
|
809
|
+
if (response2.agent) {
|
|
810
|
+
return {
|
|
811
|
+
content: [
|
|
812
|
+
{
|
|
813
|
+
type: "text",
|
|
814
|
+
text: formatAsJson({
|
|
815
|
+
agent: response2.agent,
|
|
816
|
+
recentRuns: response2.recentRuns ?? []
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
]
|
|
820
|
+
};
|
|
821
|
+
}
|
|
691
822
|
} catch {
|
|
692
823
|
}
|
|
693
824
|
const response = {
|
|
694
825
|
type: input.agentType,
|
|
695
826
|
description: AGENT_DESCRIPTIONS[input.agentType],
|
|
696
|
-
message:
|
|
827
|
+
message: `No ${input.agentType} agent is provisioned for this organization yet. Activate it from the Agents page or with trigger_agent_run once created.`
|
|
697
828
|
};
|
|
698
829
|
return {
|
|
699
830
|
content: [
|
|
@@ -717,33 +848,44 @@ async function getAgentDetails(input, client) {
|
|
|
717
848
|
}
|
|
718
849
|
async function getAgentRunHistory(input, client) {
|
|
719
850
|
try {
|
|
851
|
+
let agentId;
|
|
720
852
|
try {
|
|
721
|
-
const
|
|
722
|
-
limit: input.limit
|
|
723
|
-
};
|
|
853
|
+
const byTypeParams = {};
|
|
724
854
|
if (input.assetId)
|
|
725
|
-
|
|
726
|
-
const
|
|
855
|
+
byTypeParams.assetId = input.assetId;
|
|
856
|
+
const byType = await client.get(
|
|
857
|
+
`/api/agents/by-type/${input.agentType}`,
|
|
858
|
+
byTypeParams
|
|
859
|
+
);
|
|
860
|
+
agentId = byType.agent?.id;
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
if (!agentId) {
|
|
727
864
|
return {
|
|
728
865
|
content: [
|
|
729
866
|
{
|
|
730
867
|
type: "text",
|
|
731
868
|
text: formatAsJson({
|
|
732
869
|
agentType: input.agentType,
|
|
733
|
-
|
|
870
|
+
runs: [],
|
|
871
|
+
message: `No ${input.agentType} agent is provisioned for this organization yet, so there is no run history.`
|
|
734
872
|
})
|
|
735
873
|
}
|
|
736
874
|
]
|
|
737
875
|
};
|
|
738
|
-
} catch {
|
|
739
876
|
}
|
|
877
|
+
const response = await client.get(
|
|
878
|
+
`/api/agents/${agentId}/runs`,
|
|
879
|
+
{ limit: input.limit }
|
|
880
|
+
);
|
|
740
881
|
return {
|
|
741
882
|
content: [
|
|
742
883
|
{
|
|
743
884
|
type: "text",
|
|
744
885
|
text: formatAsJson({
|
|
745
886
|
agentType: input.agentType,
|
|
746
|
-
|
|
887
|
+
agentId,
|
|
888
|
+
...asRecord(response.data ?? response)
|
|
747
889
|
})
|
|
748
890
|
}
|
|
749
891
|
]
|
|
@@ -762,15 +904,16 @@ async function getAgentRunHistory(input, client) {
|
|
|
762
904
|
}
|
|
763
905
|
async function triggerAgentRun(input, client) {
|
|
764
906
|
try {
|
|
765
|
-
const body = {};
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
907
|
+
const body = input.assetId ? { triggerContext: { assetId: input.assetId } } : {};
|
|
908
|
+
const response = await client.post(
|
|
909
|
+
`/api/agents/${input.agentId}/run`,
|
|
910
|
+
body
|
|
911
|
+
);
|
|
769
912
|
return {
|
|
770
913
|
content: [
|
|
771
914
|
{
|
|
772
915
|
type: "text",
|
|
773
|
-
text: formatAsJson(response.data
|
|
916
|
+
text: formatAsJson(response.data ?? response)
|
|
774
917
|
}
|
|
775
918
|
]
|
|
776
919
|
};
|
|
@@ -788,12 +931,14 @@ async function triggerAgentRun(input, client) {
|
|
|
788
931
|
}
|
|
789
932
|
async function pauseAgent(input, client) {
|
|
790
933
|
try {
|
|
791
|
-
const response = await client.post(
|
|
934
|
+
const response = await client.post(
|
|
935
|
+
`/api/agents/${input.agentId}/pause`
|
|
936
|
+
);
|
|
792
937
|
return {
|
|
793
938
|
content: [
|
|
794
939
|
{
|
|
795
940
|
type: "text",
|
|
796
|
-
text: formatAsJson(response.data
|
|
941
|
+
text: formatAsJson(response.data ?? response)
|
|
797
942
|
}
|
|
798
943
|
]
|
|
799
944
|
};
|
|
@@ -811,12 +956,14 @@ async function pauseAgent(input, client) {
|
|
|
811
956
|
}
|
|
812
957
|
async function resumeAgent(input, client) {
|
|
813
958
|
try {
|
|
814
|
-
const response = await client.post(
|
|
959
|
+
const response = await client.post(
|
|
960
|
+
`/api/agents/${input.agentId}/resume`
|
|
961
|
+
);
|
|
815
962
|
return {
|
|
816
963
|
content: [
|
|
817
964
|
{
|
|
818
965
|
type: "text",
|
|
819
|
-
text: formatAsJson(response.data
|
|
966
|
+
text: formatAsJson(response.data ?? response)
|
|
820
967
|
}
|
|
821
968
|
]
|
|
822
969
|
};
|
|
@@ -834,12 +981,14 @@ async function resumeAgent(input, client) {
|
|
|
834
981
|
}
|
|
835
982
|
async function getAgentActivity(input, client) {
|
|
836
983
|
try {
|
|
837
|
-
const response = await client.get(
|
|
984
|
+
const response = await client.get(
|
|
985
|
+
`/api/agents/${input.agentId}/activity`
|
|
986
|
+
);
|
|
838
987
|
return {
|
|
839
988
|
content: [
|
|
840
989
|
{
|
|
841
990
|
type: "text",
|
|
842
|
-
text: formatAsJson(response.data
|
|
991
|
+
text: formatAsJson(response.data ?? response)
|
|
843
992
|
}
|
|
844
993
|
]
|
|
845
994
|
};
|
|
@@ -897,34 +1046,40 @@ async function getAnomalies(input, client) {
|
|
|
897
1046
|
params.fromDate = input.fromDate;
|
|
898
1047
|
if (input.toDate)
|
|
899
1048
|
params.toDate = input.toDate;
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1049
|
+
const response = await client.get(
|
|
1050
|
+
"/api/anomalies",
|
|
1051
|
+
params
|
|
1052
|
+
);
|
|
1053
|
+
let anomalies = response.data ?? response.anomalies ?? [];
|
|
1054
|
+
if (input.excludeDismissed && !input.status) {
|
|
1055
|
+
anomalies = anomalies.filter((a) => a.status !== "dismissed");
|
|
1056
|
+
}
|
|
904
1057
|
return {
|
|
905
1058
|
content: [
|
|
906
1059
|
{
|
|
907
1060
|
type: "text",
|
|
908
1061
|
text: formatAsJson({
|
|
909
1062
|
total: anomalies.length,
|
|
910
|
-
anomalies: anomalies.map((
|
|
911
|
-
id:
|
|
912
|
-
assetId:
|
|
913
|
-
assetName:
|
|
914
|
-
assetIcon:
|
|
915
|
-
anomalyDate:
|
|
916
|
-
metricName:
|
|
917
|
-
sourceType:
|
|
918
|
-
type:
|
|
919
|
-
severity:
|
|
920
|
-
actualValue: parseFloat(
|
|
921
|
-
expectedValue: parseFloat(
|
|
922
|
-
deviationPercent: parseFloat(
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1063
|
+
anomalies: anomalies.map((anomaly) => ({
|
|
1064
|
+
id: anomaly.id,
|
|
1065
|
+
assetId: anomaly.assetId,
|
|
1066
|
+
assetName: anomaly.assetName,
|
|
1067
|
+
assetIcon: anomaly.assetIcon,
|
|
1068
|
+
anomalyDate: anomaly.anomalyDate,
|
|
1069
|
+
metricName: anomaly.metricName,
|
|
1070
|
+
sourceType: anomaly.sourceType,
|
|
1071
|
+
type: anomaly.type,
|
|
1072
|
+
severity: anomaly.severity,
|
|
1073
|
+
actualValue: parseFloat(String(anomaly.actualValue || "0")),
|
|
1074
|
+
expectedValue: parseFloat(String(anomaly.expectedValue || "0")),
|
|
1075
|
+
deviationPercent: parseFloat(
|
|
1076
|
+
String(anomaly.deviationPercent || "0")
|
|
1077
|
+
),
|
|
1078
|
+
confidence: parseFloat(String(anomaly.confidence || "0")),
|
|
1079
|
+
explanation: anomaly.explanation,
|
|
1080
|
+
suggestedActions: anomaly.suggestedActions,
|
|
1081
|
+
status: anomaly.status,
|
|
1082
|
+
detectedAt: anomaly.detectedAt
|
|
928
1083
|
}))
|
|
929
1084
|
})
|
|
930
1085
|
}
|
|
@@ -1023,37 +1178,46 @@ var getAdsPerformanceSchema = z6.object({
|
|
|
1023
1178
|
});
|
|
1024
1179
|
async function getAdsPerformance(input, client) {
|
|
1025
1180
|
try {
|
|
1026
|
-
const params = {
|
|
1027
|
-
limit: input.limit
|
|
1028
|
-
};
|
|
1181
|
+
const params = {};
|
|
1029
1182
|
if (input.assetId)
|
|
1030
1183
|
params.assetId = input.assetId;
|
|
1031
|
-
if (input.platform)
|
|
1032
|
-
params.platform = input.platform;
|
|
1033
1184
|
if (input.fromDate)
|
|
1034
|
-
params.
|
|
1185
|
+
params.from = input.fromDate;
|
|
1035
1186
|
if (input.toDate)
|
|
1036
|
-
params.
|
|
1037
|
-
const response = await client.get(
|
|
1038
|
-
|
|
1187
|
+
params.to = input.toDate;
|
|
1188
|
+
const response = await client.get(
|
|
1189
|
+
"/api/ads-agent/campaigns",
|
|
1190
|
+
params
|
|
1191
|
+
);
|
|
1192
|
+
let campaigns = response.data ?? response.campaigns ?? [];
|
|
1193
|
+
if (input.platform) {
|
|
1194
|
+
campaigns = campaigns.filter((c) => c.platform === input.platform);
|
|
1195
|
+
}
|
|
1196
|
+
campaigns = campaigns.slice(0, input.limit);
|
|
1039
1197
|
return {
|
|
1040
1198
|
content: [
|
|
1041
1199
|
{
|
|
1042
1200
|
type: "text",
|
|
1043
1201
|
text: formatAsJson({
|
|
1044
1202
|
total: campaigns.length,
|
|
1045
|
-
campaigns: campaigns.map((
|
|
1046
|
-
id:
|
|
1047
|
-
assetId:
|
|
1048
|
-
assetName:
|
|
1049
|
-
platformCampaignId:
|
|
1050
|
-
name:
|
|
1051
|
-
status:
|
|
1052
|
-
objective:
|
|
1053
|
-
platform:
|
|
1054
|
-
linkSource:
|
|
1055
|
-
linkedAt:
|
|
1056
|
-
|
|
1203
|
+
campaigns: campaigns.map((campaign) => ({
|
|
1204
|
+
id: campaign.id,
|
|
1205
|
+
assetId: campaign.assetId,
|
|
1206
|
+
assetName: campaign.assetName,
|
|
1207
|
+
platformCampaignId: campaign.platformCampaignId,
|
|
1208
|
+
name: campaign.name,
|
|
1209
|
+
status: campaign.status,
|
|
1210
|
+
objective: campaign.objective,
|
|
1211
|
+
platform: campaign.platform,
|
|
1212
|
+
linkSource: campaign.linkSource,
|
|
1213
|
+
linkedAt: campaign.linkedAt,
|
|
1214
|
+
totalSpend: campaign.totalSpend ?? null,
|
|
1215
|
+
totalImpressions: campaign.totalImpressions ?? null,
|
|
1216
|
+
totalTaps: campaign.totalTaps ?? null,
|
|
1217
|
+
totalInstalls: campaign.totalInstalls ?? null,
|
|
1218
|
+
avgCPI: campaign.avgCPI ?? null,
|
|
1219
|
+
avgTTR: campaign.avgTTR ?? null,
|
|
1220
|
+
avgCVR: campaign.avgCVR ?? null
|
|
1057
1221
|
}))
|
|
1058
1222
|
})
|
|
1059
1223
|
}
|
|
@@ -1131,13 +1295,17 @@ async function getGrowthScore(input, client) {
|
|
|
1131
1295
|
import { z as z8 } from "zod";
|
|
1132
1296
|
var getForecastsSchema = z8.object({
|
|
1133
1297
|
assetId: z8.string().uuid().describe("App UUID to get forecasts for"),
|
|
1134
|
-
|
|
1298
|
+
metric: z8.enum(["proceeds", "total_revenue", "total_downloads", "active_subs", "new_trials"]).describe("The metric to forecast"),
|
|
1299
|
+
forecastWeeks: z8.number().int().min(4).max(156).optional().describe("Weeks to forecast forward (default 104)"),
|
|
1300
|
+
historicalWeeks: z8.number().int().min(12).max(156).optional().describe("Weeks of history to base the forecast on (default 104)")
|
|
1135
1301
|
});
|
|
1136
1302
|
async function getForecasts(input, client) {
|
|
1137
1303
|
try {
|
|
1138
1304
|
const response = await client.get("/api/forecasting/forecast", {
|
|
1139
1305
|
assetId: input.assetId,
|
|
1140
|
-
|
|
1306
|
+
metricName: input.metric,
|
|
1307
|
+
forecastWeeks: input.forecastWeeks,
|
|
1308
|
+
historicalWeeks: input.historicalWeeks
|
|
1141
1309
|
});
|
|
1142
1310
|
return {
|
|
1143
1311
|
content: [
|
|
@@ -1162,18 +1330,34 @@ async function getForecasts(input, client) {
|
|
|
1162
1330
|
|
|
1163
1331
|
// src/tools/dashboard.ts
|
|
1164
1332
|
import { z as z9 } from "zod";
|
|
1333
|
+
function parseDashboardNumber(value) {
|
|
1334
|
+
if (value === null || value === void 0 || value === "")
|
|
1335
|
+
return null;
|
|
1336
|
+
const parsed = typeof value === "number" ? value : Number.parseFloat(value);
|
|
1337
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1338
|
+
}
|
|
1339
|
+
function getSidebarAssets(response) {
|
|
1340
|
+
if (!response)
|
|
1341
|
+
return [];
|
|
1342
|
+
if (Array.isArray(response))
|
|
1343
|
+
return response;
|
|
1344
|
+
return response.data ?? [];
|
|
1345
|
+
}
|
|
1165
1346
|
var getDashboardOverviewSchema = z9.object({});
|
|
1166
1347
|
async function getDashboardOverview(_input, client) {
|
|
1167
1348
|
try {
|
|
1349
|
+
const overviewPromise = client.get("/api/dashboard/overview-metrics").catch(() => null);
|
|
1350
|
+
const sidebarPromise = client.get(
|
|
1351
|
+
"/api/dashboard/sidebar-assets"
|
|
1352
|
+
).catch(() => null);
|
|
1168
1353
|
const [overviewResponse, sidebarResponse] = await Promise.all([
|
|
1169
|
-
|
|
1170
|
-
|
|
1354
|
+
overviewPromise,
|
|
1355
|
+
sidebarPromise
|
|
1171
1356
|
]);
|
|
1172
|
-
const overview = overviewResponse?.data
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
return sum + parseFloat(a.currentValuation || "0");
|
|
1357
|
+
const overview = overviewResponse?.data ?? overviewResponse ?? {};
|
|
1358
|
+
const apps = getSidebarAssets(sidebarResponse);
|
|
1359
|
+
const totalValuation = apps.reduce((sum, app) => {
|
|
1360
|
+
return sum + (parseDashboardNumber(app.currentValuation) ?? 0);
|
|
1177
1361
|
}, 0);
|
|
1178
1362
|
return {
|
|
1179
1363
|
content: [
|
|
@@ -1183,16 +1367,16 @@ async function getDashboardOverview(_input, client) {
|
|
|
1183
1367
|
portfolio: {
|
|
1184
1368
|
totalApps: apps.length,
|
|
1185
1369
|
totalValuation: totalValuation > 0 ? totalValuation : null,
|
|
1186
|
-
apps: apps.map((
|
|
1187
|
-
id:
|
|
1188
|
-
name:
|
|
1189
|
-
bundleId:
|
|
1190
|
-
platform:
|
|
1191
|
-
currentValuation:
|
|
1192
|
-
rating:
|
|
1193
|
-
ratingCount:
|
|
1194
|
-
category:
|
|
1195
|
-
iconUrl:
|
|
1370
|
+
apps: apps.map((app) => ({
|
|
1371
|
+
id: app.id,
|
|
1372
|
+
name: app.name,
|
|
1373
|
+
bundleId: app.bundleId,
|
|
1374
|
+
platform: app.appleAppId ? "ios" : app.googleAppId ? "android" : "unknown",
|
|
1375
|
+
currentValuation: parseDashboardNumber(app.currentValuation),
|
|
1376
|
+
rating: parseDashboardNumber(app.rating),
|
|
1377
|
+
ratingCount: app.ratingCount ?? null,
|
|
1378
|
+
category: app.category ?? null,
|
|
1379
|
+
iconUrl: app.iconUrl ?? null
|
|
1196
1380
|
}))
|
|
1197
1381
|
},
|
|
1198
1382
|
overview
|
|
@@ -1215,13 +1399,15 @@ async function getDashboardOverview(_input, client) {
|
|
|
1215
1399
|
|
|
1216
1400
|
// src/tools/actions.ts
|
|
1217
1401
|
import { z as z10 } from "zod";
|
|
1402
|
+
function asRecord2(value) {
|
|
1403
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1404
|
+
}
|
|
1218
1405
|
var listPendingActionsSchema = z10.object({
|
|
1219
1406
|
assetId: z10.string().uuid().optional().describe("Filter by app UUID"),
|
|
1220
1407
|
limit: z10.number().int().min(1).max(100).default(50).describe("Maximum number of actions to return")
|
|
1221
1408
|
});
|
|
1222
1409
|
var approveActionSchema = z10.object({
|
|
1223
|
-
actionId: z10.string().describe("The
|
|
1224
|
-
editedReply: z10.string().optional().describe("Optionally modify the reply text before approving")
|
|
1410
|
+
actionId: z10.string().describe("The pending action ID to approve")
|
|
1225
1411
|
});
|
|
1226
1412
|
var rejectActionSchema = z10.object({
|
|
1227
1413
|
actionId: z10.string().describe("The draft reply ID to reject/delete")
|
|
@@ -1231,10 +1417,16 @@ async function listPendingActions(input, client) {
|
|
|
1231
1417
|
const params = {
|
|
1232
1418
|
limit: input.limit
|
|
1233
1419
|
};
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1420
|
+
const response = await client.get(
|
|
1421
|
+
"/api/pending-actions",
|
|
1422
|
+
params
|
|
1423
|
+
);
|
|
1424
|
+
let actions = response.data ?? response.actions ?? [];
|
|
1425
|
+
if (input.assetId) {
|
|
1426
|
+
actions = actions.filter(
|
|
1427
|
+
(action) => action.assetId === input.assetId
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1238
1430
|
return {
|
|
1239
1431
|
content: [
|
|
1240
1432
|
{
|
|
@@ -1260,10 +1452,10 @@ async function listPendingActions(input, client) {
|
|
|
1260
1452
|
}
|
|
1261
1453
|
async function approveAction(input, client) {
|
|
1262
1454
|
try {
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1455
|
+
const response = await client.post(
|
|
1456
|
+
`/api/pending-actions/${input.actionId}/approve`,
|
|
1457
|
+
{}
|
|
1458
|
+
);
|
|
1267
1459
|
return {
|
|
1268
1460
|
content: [
|
|
1269
1461
|
{
|
|
@@ -1273,7 +1465,7 @@ async function approveAction(input, client) {
|
|
|
1273
1465
|
actionId: input.actionId,
|
|
1274
1466
|
status: "approved",
|
|
1275
1467
|
message: "Reply has been approved and queued for sending",
|
|
1276
|
-
...response.data
|
|
1468
|
+
...asRecord2(response.data)
|
|
1277
1469
|
})
|
|
1278
1470
|
}
|
|
1279
1471
|
]
|
|
@@ -1292,7 +1484,9 @@ async function approveAction(input, client) {
|
|
|
1292
1484
|
}
|
|
1293
1485
|
async function rejectAction(input, client) {
|
|
1294
1486
|
try {
|
|
1295
|
-
const response = await client.post(
|
|
1487
|
+
const response = await client.post(
|
|
1488
|
+
`/api/pending-actions/${input.actionId}/reject`
|
|
1489
|
+
);
|
|
1296
1490
|
return {
|
|
1297
1491
|
content: [
|
|
1298
1492
|
{
|
|
@@ -1302,7 +1496,7 @@ async function rejectAction(input, client) {
|
|
|
1302
1496
|
actionId: input.actionId,
|
|
1303
1497
|
status: "rejected",
|
|
1304
1498
|
message: "Draft reply has been rejected and removed",
|
|
1305
|
-
...response.data
|
|
1499
|
+
...asRecord2(response.data)
|
|
1306
1500
|
})
|
|
1307
1501
|
}
|
|
1308
1502
|
]
|
|
@@ -1336,10 +1530,13 @@ var getAsoExperimentsSchema = z11.object({
|
|
|
1336
1530
|
assetId: z11.string().uuid().describe("App UUID to list ASO experiments for"),
|
|
1337
1531
|
status: z11.enum(["proposed", "approved", "applied", "measuring", "completed", "reverted"]).optional().describe("Filter experiments by status"),
|
|
1338
1532
|
limit: z11.number().int().min(1).max(100).default(20).describe("Maximum number of experiments to return"),
|
|
1339
|
-
|
|
1533
|
+
cursor: z11.string().optional().describe("Pagination cursor from a previous response (meta.nextCursor)")
|
|
1340
1534
|
});
|
|
1341
1535
|
var getAsoLocaleSnapshotsSchema = z11.object({
|
|
1342
|
-
assetId: z11.string().uuid().describe("App UUID to get locale snapshots for")
|
|
1536
|
+
assetId: z11.string().uuid().describe("App UUID to get locale snapshots for"),
|
|
1537
|
+
store: z11.enum(["ios", "android"]).default("ios").describe("Which store listing to read"),
|
|
1538
|
+
locale: z11.string().optional().describe('Filter to a single locale (e.g. "en-US"). Returns all locales if omitted.'),
|
|
1539
|
+
limit: z11.number().int().min(1).max(500).optional().describe("Maximum snapshot rows to return (newest first)")
|
|
1343
1540
|
});
|
|
1344
1541
|
var triggerAsoAnalysisSchema = z11.object({
|
|
1345
1542
|
assetId: z11.string().uuid().describe("App UUID to trigger ASO analysis for")
|
|
@@ -1420,7 +1617,7 @@ async function getAsoExperiments(input, client) {
|
|
|
1420
1617
|
try {
|
|
1421
1618
|
const params = {
|
|
1422
1619
|
limit: input.limit,
|
|
1423
|
-
|
|
1620
|
+
cursor: input.cursor
|
|
1424
1621
|
};
|
|
1425
1622
|
if (input.status)
|
|
1426
1623
|
params.status = input.status;
|
|
@@ -1447,7 +1644,14 @@ async function getAsoExperiments(input, client) {
|
|
|
1447
1644
|
}
|
|
1448
1645
|
async function getAsoLocaleSnapshots(input, client) {
|
|
1449
1646
|
try {
|
|
1450
|
-
const response = await client.get(
|
|
1647
|
+
const response = await client.get(
|
|
1648
|
+
`/api/assets/${input.assetId}/aso/listing-snapshots`,
|
|
1649
|
+
{
|
|
1650
|
+
store: input.store,
|
|
1651
|
+
locale: input.locale,
|
|
1652
|
+
limit: input.limit
|
|
1653
|
+
}
|
|
1654
|
+
);
|
|
1451
1655
|
return {
|
|
1452
1656
|
content: [
|
|
1453
1657
|
{
|
|
@@ -1470,7 +1674,10 @@ async function getAsoLocaleSnapshots(input, client) {
|
|
|
1470
1674
|
}
|
|
1471
1675
|
async function triggerAsoAnalysis(input, client) {
|
|
1472
1676
|
try {
|
|
1473
|
-
const response = await client.post(
|
|
1677
|
+
const response = await client.post(
|
|
1678
|
+
`/api/assets/${input.assetId}/aso/refetch`,
|
|
1679
|
+
{ force: true }
|
|
1680
|
+
);
|
|
1474
1681
|
return {
|
|
1475
1682
|
content: [
|
|
1476
1683
|
{
|
|
@@ -1493,6 +1700,7 @@ async function triggerAsoAnalysis(input, client) {
|
|
|
1493
1700
|
}
|
|
1494
1701
|
|
|
1495
1702
|
// src/tools/chat.ts
|
|
1703
|
+
import { randomUUID } from "node:crypto";
|
|
1496
1704
|
import { z as z12 } from "zod";
|
|
1497
1705
|
var listConversationsSchema = z12.object({
|
|
1498
1706
|
limit: z12.number().int().min(1).max(100).default(20).describe("Maximum number of conversations to return")
|
|
@@ -1562,23 +1770,81 @@ async function getConversationMessages(input, client) {
|
|
|
1562
1770
|
};
|
|
1563
1771
|
}
|
|
1564
1772
|
}
|
|
1773
|
+
function parseUiMessageStream(raw) {
|
|
1774
|
+
let reply = "";
|
|
1775
|
+
const toolsUsed = [];
|
|
1776
|
+
for (const line of raw.split("\n")) {
|
|
1777
|
+
if (!line.startsWith("data:"))
|
|
1778
|
+
continue;
|
|
1779
|
+
const payload = line.slice(5).trim();
|
|
1780
|
+
if (!payload || payload === "[DONE]")
|
|
1781
|
+
continue;
|
|
1782
|
+
try {
|
|
1783
|
+
const event = JSON.parse(payload);
|
|
1784
|
+
if (event.type === "text-delta") {
|
|
1785
|
+
reply += String(event.delta ?? event.textDelta ?? "");
|
|
1786
|
+
} else if (typeof event.type === "string" && event.type === "tool-input-start" && typeof event.toolName === "string") {
|
|
1787
|
+
toolsUsed.push(event.toolName);
|
|
1788
|
+
}
|
|
1789
|
+
} catch {
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return { reply, toolsUsed };
|
|
1793
|
+
}
|
|
1565
1794
|
async function sendChatMessage(input, client) {
|
|
1566
1795
|
try {
|
|
1796
|
+
const conversationId = input.conversationId ?? randomUUID();
|
|
1797
|
+
let priorMessages = [];
|
|
1798
|
+
if (input.conversationId) {
|
|
1799
|
+
const history = await client.get(`/api/chat/messages/${input.conversationId}`, { limit: 1e3 });
|
|
1800
|
+
for (const message of history.data ?? []) {
|
|
1801
|
+
const textParts = (message.parts ?? []).filter(
|
|
1802
|
+
(part) => part.type === "text" && typeof part.text === "string"
|
|
1803
|
+
);
|
|
1804
|
+
if (textParts.length === 0)
|
|
1805
|
+
continue;
|
|
1806
|
+
priorMessages.push({
|
|
1807
|
+
id: message.id,
|
|
1808
|
+
role: message.role,
|
|
1809
|
+
parts: textParts
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
priorMessages = priorMessages.slice(-100);
|
|
1813
|
+
}
|
|
1567
1814
|
const body = {
|
|
1568
|
-
|
|
1815
|
+
id: conversationId,
|
|
1816
|
+
messages: [
|
|
1817
|
+
...priorMessages,
|
|
1818
|
+
{
|
|
1819
|
+
id: randomUUID(),
|
|
1820
|
+
role: "user",
|
|
1821
|
+
parts: [{ type: "text", text: input.message }]
|
|
1822
|
+
}
|
|
1823
|
+
]
|
|
1569
1824
|
};
|
|
1570
|
-
if (input.
|
|
1571
|
-
body.
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1825
|
+
if (input.agentType) {
|
|
1826
|
+
body.agentContext = {
|
|
1827
|
+
agentId: null,
|
|
1828
|
+
agentType: input.agentType,
|
|
1829
|
+
agentName: input.agentType,
|
|
1830
|
+
surface: "mcp"
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const raw = await client.postStream("/api/chat", body);
|
|
1834
|
+
const { reply, toolsUsed } = parseUiMessageStream(raw);
|
|
1575
1835
|
return {
|
|
1576
1836
|
content: [
|
|
1577
1837
|
{
|
|
1578
1838
|
type: "text",
|
|
1579
|
-
text: formatAsJson(
|
|
1839
|
+
text: formatAsJson({
|
|
1840
|
+
conversationId,
|
|
1841
|
+
reply: reply || "The assistant returned no text response.",
|
|
1842
|
+
toolsUsed,
|
|
1843
|
+
tip: "Pass this conversationId to continue the conversation or to get_conversation_messages for the full transcript."
|
|
1844
|
+
})
|
|
1580
1845
|
}
|
|
1581
|
-
]
|
|
1846
|
+
],
|
|
1847
|
+
...reply ? {} : { isError: true }
|
|
1582
1848
|
};
|
|
1583
1849
|
} catch (error) {
|
|
1584
1850
|
return {
|
|
@@ -1596,8 +1862,8 @@ async function sendChatMessage(input, client) {
|
|
|
1596
1862
|
// src/tools/index.ts
|
|
1597
1863
|
function registerTools(server, client, options = {}) {
|
|
1598
1864
|
const rateLimiter = options.rateLimiter ?? new RateLimiter(100, 6e4);
|
|
1599
|
-
function wrapTool(
|
|
1600
|
-
|
|
1865
|
+
function wrapTool(handler) {
|
|
1866
|
+
const callback = async (input) => {
|
|
1601
1867
|
const rateCheck = rateLimiter.check("default");
|
|
1602
1868
|
if (!rateCheck.allowed) {
|
|
1603
1869
|
return {
|
|
@@ -1614,6 +1880,7 @@ function registerTools(server, client, options = {}) {
|
|
|
1614
1880
|
}
|
|
1615
1881
|
return handler(input, client);
|
|
1616
1882
|
};
|
|
1883
|
+
return callback;
|
|
1617
1884
|
}
|
|
1618
1885
|
function tool(name, title, description, schema, handler, annotations) {
|
|
1619
1886
|
server.registerTool(
|
|
@@ -1624,8 +1891,7 @@ function registerTools(server, client, options = {}) {
|
|
|
1624
1891
|
inputSchema: schema.shape,
|
|
1625
1892
|
annotations: { title, ...annotations }
|
|
1626
1893
|
},
|
|
1627
|
-
|
|
1628
|
-
wrapTool(name, handler)
|
|
1894
|
+
wrapTool(handler)
|
|
1629
1895
|
);
|
|
1630
1896
|
}
|
|
1631
1897
|
tool(
|
|
@@ -1671,7 +1937,7 @@ function registerTools(server, client, options = {}) {
|
|
|
1671
1937
|
tool(
|
|
1672
1938
|
"translate_review",
|
|
1673
1939
|
"Translate review",
|
|
1674
|
-
"Translate a review to English. Useful for reviews written in other languages. Returns the translated text. Does not modify the review in Fload.",
|
|
1940
|
+
"Translate a review to English. Useful for reviews written in other languages. Pass the review content fields (rating, title, body, storeFront, appVersionString) from a get_reviews result. Returns the translated text. Does not modify the review in Fload.",
|
|
1675
1941
|
translateReviewSchema,
|
|
1676
1942
|
translateReview,
|
|
1677
1943
|
{ readOnlyHint: true, openWorldHint: true }
|
|
@@ -1815,7 +2081,7 @@ function registerTools(server, client, options = {}) {
|
|
|
1815
2081
|
tool(
|
|
1816
2082
|
"get_forecasts",
|
|
1817
2083
|
"Get forecasts",
|
|
1818
|
-
"Get
|
|
2084
|
+
"Get a statistical forecast for an app metric (proceeds, total_revenue, total_downloads, active_subs, or new_trials). Returns weekly historical values plus a forward projection with confidence bounds.",
|
|
1819
2085
|
getForecastsSchema,
|
|
1820
2086
|
getForecasts,
|
|
1821
2087
|
{ readOnlyHint: true, openWorldHint: false }
|