@mongrov/analytics 0.1.0-alpha.8 → 0.1.0-alpha.9
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/dist/tools/__fakes__/engine.d.ts +25 -0
- package/dist/tools/__fakes__/engine.d.ts.map +1 -0
- package/dist/tools/__fakes__/engine.js +37 -0
- package/dist/tools/__fakes__/engine.js.map +1 -0
- package/dist/tools/audit.d.ts +31 -0
- package/dist/tools/audit.d.ts.map +1 -0
- package/dist/tools/audit.js +157 -0
- package/dist/tools/audit.js.map +1 -0
- package/dist/tools/authorize.d.ts +36 -0
- package/dist/tools/authorize.d.ts.map +1 -0
- package/dist/tools/authorize.js +87 -0
- package/dist/tools/authorize.js.map +1 -0
- package/dist/tools/budget.d.ts +15 -0
- package/dist/tools/budget.d.ts.map +1 -0
- package/dist/tools/budget.js +64 -0
- package/dist/tools/budget.js.map +1 -0
- package/dist/tools/factory.d.ts +43 -0
- package/dist/tools/factory.d.ts.map +1 -0
- package/dist/tools/factory.js +137 -0
- package/dist/tools/factory.js.map +1 -0
- package/dist/tools/formatters.d.ts +17 -0
- package/dist/tools/formatters.d.ts.map +1 -0
- package/dist/tools/formatters.js +30 -0
- package/dist/tools/formatters.js.map +1 -0
- package/dist/tools/impls/activity.d.ts +15 -0
- package/dist/tools/impls/activity.d.ts.map +1 -0
- package/dist/tools/impls/activity.js +61 -0
- package/dist/tools/impls/activity.js.map +1 -0
- package/dist/tools/impls/anomaly.d.ts +21 -0
- package/dist/tools/impls/anomaly.d.ts.map +1 -0
- package/dist/tools/impls/anomaly.js +70 -0
- package/dist/tools/impls/anomaly.js.map +1 -0
- package/dist/tools/impls/compare.d.ts +21 -0
- package/dist/tools/impls/compare.d.ts.map +1 -0
- package/dist/tools/impls/compare.js +68 -0
- package/dist/tools/impls/compare.js.map +1 -0
- package/dist/tools/impls/hrv.d.ts +15 -0
- package/dist/tools/impls/hrv.d.ts.map +1 -0
- package/dist/tools/impls/hrv.js +36 -0
- package/dist/tools/impls/hrv.js.map +1 -0
- package/dist/tools/impls/insights.d.ts +18 -0
- package/dist/tools/impls/insights.d.ts.map +1 -0
- package/dist/tools/impls/insights.js +41 -0
- package/dist/tools/impls/insights.js.map +1 -0
- package/dist/tools/impls/sleep.d.ts +15 -0
- package/dist/tools/impls/sleep.d.ts.map +1 -0
- package/dist/tools/impls/sleep.js +43 -0
- package/dist/tools/impls/sleep.js.map +1 -0
- package/dist/tools/index.d.ts +13 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +21 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/rate-limit.d.ts +26 -0
- package/dist/tools/rate-limit.d.ts.map +1 -0
- package/dist/tools/rate-limit.js +67 -0
- package/dist/tools/rate-limit.js.map +1 -0
- package/dist/tools/types.d.ts +121 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +16 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/wrap.d.ts +53 -0
- package/dist/tools/wrap.d.ts.map +1 -0
- package/dist/tools/wrap.js +135 -0
- package/dist/tools/wrap.js.map +1 -0
- package/package.json +18 -15
- package/LICENSE +0 -21
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ToolImpl } from '../types';
|
|
3
|
+
export declare const getSleepSummaryInputSchema: z.ZodObject<{
|
|
4
|
+
userId: z.ZodString;
|
|
5
|
+
days: z.ZodNumber;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
days: number;
|
|
8
|
+
userId: string;
|
|
9
|
+
}, {
|
|
10
|
+
days: number;
|
|
11
|
+
userId: string;
|
|
12
|
+
}>;
|
|
13
|
+
export type GetSleepSummaryInput = z.infer<typeof getSleepSummaryInputSchema>;
|
|
14
|
+
export declare const getSleepSummary: ToolImpl<GetSleepSummaryInput>;
|
|
15
|
+
//# sourceMappingURL=sleep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../../src/tools/impls/sleep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAEpD,eAAO,MAAM,0BAA0B;;;;;;;;;EAGrC,CAAA;AAEF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAU7E,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,oBAAoB,CA0C1D,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatBytes } from '../formatters';
|
|
3
|
+
export const getSleepSummaryInputSchema = z.object({
|
|
4
|
+
userId: z.string(),
|
|
5
|
+
days: z.number().int().min(1).max(90),
|
|
6
|
+
});
|
|
7
|
+
export const getSleepSummary = async (input, ctx) => {
|
|
8
|
+
const rows = await ctx.analytics.execute(`SELECT night_of::VARCHAR AS night_of,
|
|
9
|
+
SUM(total_minutes)::INTEGER AS total_minutes,
|
|
10
|
+
SUM(deep_minutes)::INTEGER AS deep_minutes,
|
|
11
|
+
SUM(rem_minutes)::INTEGER AS rem_minutes,
|
|
12
|
+
SUM(light_minutes)::INTEGER AS light_minutes
|
|
13
|
+
FROM sleep_session
|
|
14
|
+
WHERE user_id = $userId AND brand = $brand AND family_id = $familyId
|
|
15
|
+
AND ts_start >= now() - INTERVAL ($days) DAY
|
|
16
|
+
GROUP BY night_of ORDER BY night_of`, {
|
|
17
|
+
userId: input.userId,
|
|
18
|
+
brand: ctx.brand,
|
|
19
|
+
familyId: ctx.familyId,
|
|
20
|
+
days: input.days,
|
|
21
|
+
});
|
|
22
|
+
if (rows.length === 0) {
|
|
23
|
+
return finalize('No sleep sessions for the requested window.', 0);
|
|
24
|
+
}
|
|
25
|
+
const totalMean = rows.reduce((a, r) => a + r.total_minutes, 0) / rows.length;
|
|
26
|
+
const perNight = rows
|
|
27
|
+
.map((r) => {
|
|
28
|
+
const parts = [`${r.total_minutes}m total`];
|
|
29
|
+
if (r.deep_minutes != null)
|
|
30
|
+
parts.push(`${r.deep_minutes}m deep`);
|
|
31
|
+
if (r.rem_minutes != null)
|
|
32
|
+
parts.push(`${r.rem_minutes}m REM`);
|
|
33
|
+
return ` ${r.night_of}: ${parts.join(', ')}`;
|
|
34
|
+
})
|
|
35
|
+
.join('\n');
|
|
36
|
+
const text = `Sleep, last ${input.days} days:\n${perNight}\n`
|
|
37
|
+
+ ` ${rows.length}-night avg total: ${totalMean.toFixed(0)}m`;
|
|
38
|
+
return finalize(text, rows.length);
|
|
39
|
+
};
|
|
40
|
+
function finalize(text, rowCount) {
|
|
41
|
+
return { text, rowCount, bytes: formatBytes(text) };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=sleep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../../src/tools/impls/sleep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAG3C,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;CACtC,CAAC,CAAA;AAYF,MAAM,CAAC,MAAM,eAAe,GAAmC,KAAK,EAClE,KAAK,EACL,GAAG,EACH,EAAE;IACF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,OAAO,CACtC;;;;;;;;yCAQqC,EACrC;QACE,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CACF,CAAA;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,QAAQ,CAAC,6CAA6C,EAAE,CAAC,CAAC,CAAA;IACnE,CAAC;IAED,MAAM,SAAS,GACX,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAA;IAC/D,MAAM,QAAQ,GAAG,IAAI;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,KAAK,GAAa,CAAC,GAAG,CAAC,CAAC,aAAa,SAAS,CAAC,CAAA;QACrD,IAAI,CAAC,CAAC,YAAY,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,QAAQ,CAAC,CAAA;QACjE,IAAI,CAAC,CAAC,WAAW,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,OAAO,CAAC,CAAA;QAC9D,OAAO,KAAK,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;IAC/C,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,IAAI,GACN,eAAe,KAAK,CAAC,IAAI,WAAW,QAAQ,IAAI;UAChD,KAAK,IAAI,CAAC,MAAM,qBAAqB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;IAEhE,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC,CAAA;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAA;AACrD,CAAC"}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './audit';
|
|
2
|
+
export * from './authorize';
|
|
3
|
+
export * from './budget';
|
|
4
|
+
export * from './factory';
|
|
5
|
+
export * from './rate-limit';
|
|
6
|
+
export * from './types';
|
|
7
|
+
export * from './wrap';
|
|
8
|
+
export { getHRV, getHRVInputSchema, type GetHRVInput, } from './impls/hrv';
|
|
9
|
+
export { getSleepSummary, getSleepSummaryInputSchema, type GetSleepSummaryInput, } from './impls/sleep';
|
|
10
|
+
export { getActivityTotal, getActivityTotalInputSchema, type GetActivityTotalInput, } from './impls/activity';
|
|
11
|
+
export { compareTrend, compareTrendInputSchema, type CompareTrendInput, } from './impls/compare';
|
|
12
|
+
export { detectAnomaly, detectAnomalyInputSchema, type DetectAnomalyInput, } from './impls/anomaly';
|
|
13
|
+
export { getInsights, getInsightsInputSchema, type GetInsightsInput, } from './impls/insights';
|
|
2
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AASA,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AAEtB,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,KAAK,qBAAqB,GAC3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAA"}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,2 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
// @mongrov/analytics/tools — typed AI SDK tools + MCP.
|
|
2
|
+
//
|
|
3
|
+
// v0.1.0-alpha (T-01..T-12 landed): tool impls, formatters, rate
|
|
4
|
+
// limiter, authorize hooks (with optional familyMembersProvider),
|
|
5
|
+
// batched `tool_call_audit` writer, output-budget helper, `makeTool`
|
|
6
|
+
// wrapper composing rate → auth → execute → budget → audit, and the
|
|
7
|
+
// `createAnalyticsTools` factory returning an AI SDK tool map.
|
|
8
|
+
// MCP subpath (T-13/T-14) still pending.
|
|
9
|
+
export * from './audit';
|
|
10
|
+
export * from './authorize';
|
|
11
|
+
export * from './budget';
|
|
12
|
+
export * from './factory';
|
|
13
|
+
export * from './rate-limit';
|
|
14
|
+
export * from './types';
|
|
15
|
+
export * from './wrap';
|
|
16
|
+
export { getHRV, getHRVInputSchema, } from './impls/hrv';
|
|
17
|
+
export { getSleepSummary, getSleepSummaryInputSchema, } from './impls/sleep';
|
|
18
|
+
export { getActivityTotal, getActivityTotalInputSchema, } from './impls/activity';
|
|
19
|
+
export { compareTrend, compareTrendInputSchema, } from './impls/compare';
|
|
20
|
+
export { detectAnomaly, detectAnomalyInputSchema, } from './impls/anomaly';
|
|
21
|
+
export { getInsights, getInsightsInputSchema, } from './impls/insights';
|
|
2
22
|
//# sourceMappingURL=index.js.map
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,iEAAiE;AACjE,kEAAkE;AAClE,qEAAqE;AACrE,oEAAoE;AACpE,+DAA+D;AAC/D,yCAAyC;AAEzC,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AAEtB,OAAO,EACL,MAAM,EACN,iBAAiB,GAElB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,eAAe,EACf,0BAA0B,GAE3B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,gBAAgB,EAChB,2BAA2B,GAE5B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,YAAY,EACZ,uBAAuB,GAExB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,aAAa,EACb,wBAAwB,GAEzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,WAAW,EACX,sBAAsB,GAEvB,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory token-bucket rate limiter for analytics tools.
|
|
3
|
+
*
|
|
4
|
+
* Three independent buckets per `(toolName, userId)`:
|
|
5
|
+
* - per-tool-per-minute
|
|
6
|
+
* - per-tool-per-hour
|
|
7
|
+
* - per-user-per-minute (across all tools)
|
|
8
|
+
*
|
|
9
|
+
* `check(toolName, userId)` refills every bucket by elapsed time,
|
|
10
|
+
* then attempts to spend one token from all three. If any lacks a
|
|
11
|
+
* token, returns `false` **without** spending from the others.
|
|
12
|
+
* Otherwise deducts one from each and returns `true`.
|
|
13
|
+
*
|
|
14
|
+
* Persistence is out of scope for v0.1.0 — process restart resets
|
|
15
|
+
* buckets. Audit persistence (T-09) handles the durable trail.
|
|
16
|
+
*/
|
|
17
|
+
import { type RateLimitConfig } from './types';
|
|
18
|
+
export interface RateLimiter {
|
|
19
|
+
check: (toolName: string, userId: string) => boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface CreateRateLimiterConfig {
|
|
22
|
+
config?: RateLimitConfig;
|
|
23
|
+
clock?: () => number;
|
|
24
|
+
}
|
|
25
|
+
export declare function createRateLimiter(cfg?: CreateRateLimiterConfig): RateLimiter;
|
|
26
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/tools/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAUlE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;CACrD;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB;AAKD,wBAAgB,iBAAiB,CAC/B,GAAG,GAAE,uBAA4B,GAChC,WAAW,CA6Eb"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory token-bucket rate limiter for analytics tools.
|
|
3
|
+
*
|
|
4
|
+
* Three independent buckets per `(toolName, userId)`:
|
|
5
|
+
* - per-tool-per-minute
|
|
6
|
+
* - per-tool-per-hour
|
|
7
|
+
* - per-user-per-minute (across all tools)
|
|
8
|
+
*
|
|
9
|
+
* `check(toolName, userId)` refills every bucket by elapsed time,
|
|
10
|
+
* then attempts to spend one token from all three. If any lacks a
|
|
11
|
+
* token, returns `false` **without** spending from the others.
|
|
12
|
+
* Otherwise deducts one from each and returns `true`.
|
|
13
|
+
*
|
|
14
|
+
* Persistence is out of scope for v0.1.0 — process restart resets
|
|
15
|
+
* buckets. Audit persistence (T-09) handles the durable trail.
|
|
16
|
+
*/
|
|
17
|
+
import { DEFAULT_RATE_LIMIT } from './types';
|
|
18
|
+
const MS_PER_MINUTE = 60000;
|
|
19
|
+
const MS_PER_HOUR = 3600000;
|
|
20
|
+
export function createRateLimiter(cfg = {}) {
|
|
21
|
+
const rl = cfg.config ?? DEFAULT_RATE_LIMIT;
|
|
22
|
+
const clock = cfg.clock ?? Date.now;
|
|
23
|
+
const perToolMinute = new Map();
|
|
24
|
+
const perToolHour = new Map();
|
|
25
|
+
const perUserMinute = new Map();
|
|
26
|
+
function getOrCreate(map, key, capacity, windowMs, now) {
|
|
27
|
+
let b = map.get(key);
|
|
28
|
+
if (!b) {
|
|
29
|
+
b = {
|
|
30
|
+
tokens: capacity,
|
|
31
|
+
lastRefillMs: now,
|
|
32
|
+
capacity,
|
|
33
|
+
refillPerMs: capacity / windowMs,
|
|
34
|
+
};
|
|
35
|
+
map.set(key, b);
|
|
36
|
+
}
|
|
37
|
+
return b;
|
|
38
|
+
}
|
|
39
|
+
function refill(b, now) {
|
|
40
|
+
const elapsed = now - b.lastRefillMs;
|
|
41
|
+
if (elapsed <= 0)
|
|
42
|
+
return;
|
|
43
|
+
b.tokens = Math.min(b.capacity, b.tokens + elapsed * b.refillPerMs);
|
|
44
|
+
b.lastRefillMs = now;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
check(toolName, userId) {
|
|
48
|
+
const now = clock();
|
|
49
|
+
const toolKey = `${toolName}:${userId}`;
|
|
50
|
+
const userKey = userId;
|
|
51
|
+
const tm = getOrCreate(perToolMinute, toolKey, rl.perToolPerMinute, MS_PER_MINUTE, now);
|
|
52
|
+
const th = getOrCreate(perToolHour, toolKey, rl.perToolPerHour, MS_PER_HOUR, now);
|
|
53
|
+
const um = getOrCreate(perUserMinute, userKey, rl.perUserPerMinute, MS_PER_MINUTE, now);
|
|
54
|
+
refill(tm, now);
|
|
55
|
+
refill(th, now);
|
|
56
|
+
refill(um, now);
|
|
57
|
+
if (tm.tokens < 1 || th.tokens < 1 || um.tokens < 1) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
tm.tokens -= 1;
|
|
61
|
+
th.tokens -= 1;
|
|
62
|
+
um.tokens -= 1;
|
|
63
|
+
return true;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/tools/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,kBAAkB,EAAwB,MAAM,SAAS,CAAA;AAmBlE,MAAM,aAAa,GAAG,KAAM,CAAA;AAC5B,MAAM,WAAW,GAAG,OAAS,CAAA;AAE7B,MAAM,UAAU,iBAAiB,CAC/B,MAA+B,EAAE;IAEjC,MAAM,EAAE,GAAoB,GAAG,CAAC,MAAM,IAAI,kBAAkB,CAAA;IAC5D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAA;IAEnC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAA;IACpD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuB,CAAA;IAClD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAA;IAEpD,SAAS,WAAW,CAClB,GAA6B,EAC7B,GAAW,EACX,QAAgB,EAChB,QAAgB,EAChB,GAAW;QAEX,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACpB,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG;gBACF,MAAM,EAAE,QAAQ;gBAChB,YAAY,EAAE,GAAG;gBACjB,QAAQ;gBACR,WAAW,EAAE,QAAQ,GAAG,QAAQ;aACjC,CAAA;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC;IAED,SAAS,MAAM,CAAC,CAAc,EAAE,GAAW;QACzC,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,CAAC,YAAY,CAAA;QACpC,IAAI,OAAO,IAAI,CAAC;YAAE,OAAM;QACxB,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC,WAAW,CAAC,CAAA;QACnE,CAAC,CAAC,YAAY,GAAG,GAAG,CAAA;IACtB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,QAAQ,EAAE,MAAM;YACpB,MAAM,GAAG,GAAG,KAAK,EAAE,CAAA;YACnB,MAAM,OAAO,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,CAAA;YACvC,MAAM,OAAO,GAAG,MAAM,CAAA;YAEtB,MAAM,EAAE,GAAG,WAAW,CACpB,aAAa,EACb,OAAO,EACP,EAAE,CAAC,gBAAgB,EACnB,aAAa,EACb,GAAG,CACJ,CAAA;YACD,MAAM,EAAE,GAAG,WAAW,CACpB,WAAW,EACX,OAAO,EACP,EAAE,CAAC,cAAc,EACjB,WAAW,EACX,GAAG,CACJ,CAAA;YACD,MAAM,EAAE,GAAG,WAAW,CACpB,aAAa,EACb,OAAO,EACP,EAAE,CAAC,gBAAgB,EACnB,aAAa,EACb,GAAG,CACJ,CAAA;YAED,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YACf,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YACf,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YAEf,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpD,OAAO,KAAK,CAAA;YACd,CAAC;YAED,EAAE,CAAC,MAAM,IAAI,CAAC,CAAA;YACd,EAAE,CAAC,MAAM,IAAI,CAAC,CAAA;YACd,EAAE,CAAC,MAAM,IAAI,CAAC,CAAA;YACd,OAAO,IAAI,CAAA;QACb,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type surface for @mongrov/analytics/tools.
|
|
3
|
+
*
|
|
4
|
+
* Types shared across tool impls, rate limiter, authorize hooks,
|
|
5
|
+
* audit writer, wrapper, and the top-level factory.
|
|
6
|
+
*/
|
|
7
|
+
import type { AnalyticsEngine, FamilyMembersProvider } from '../core/types';
|
|
8
|
+
/** Runtime context threaded through every tool impl and authorize hook. */
|
|
9
|
+
export interface ToolContext {
|
|
10
|
+
readonly requesterUserId: string;
|
|
11
|
+
readonly brand: string;
|
|
12
|
+
readonly familyId: string;
|
|
13
|
+
readonly now?: () => Date;
|
|
14
|
+
}
|
|
15
|
+
/** Bounded text summary returned by every tool impl. */
|
|
16
|
+
export interface ToolResult {
|
|
17
|
+
readonly text: string;
|
|
18
|
+
readonly rowCount: number;
|
|
19
|
+
readonly bytes: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Authorization hook — invoked before every tool execute. `false`
|
|
23
|
+
* rejects the call before SQL runs. Toolname passes through so hooks
|
|
24
|
+
* can gate by tool identity in addition to args + ctx.
|
|
25
|
+
*/
|
|
26
|
+
export type AuthorizeFn = (toolName: string, args: Record<string, unknown>, ctx: ToolContext) => Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Token-bucket rate-limit ceilings. Defaults come from
|
|
29
|
+
* `analytics-ai-tools/spec.md` §Rate limiting.
|
|
30
|
+
*/
|
|
31
|
+
export interface RateLimitConfig {
|
|
32
|
+
perToolPerMinute: number;
|
|
33
|
+
perToolPerHour: number;
|
|
34
|
+
perUserPerMinute: number;
|
|
35
|
+
}
|
|
36
|
+
export declare const DEFAULT_RATE_LIMIT: RateLimitConfig;
|
|
37
|
+
export interface ToolsLogger {
|
|
38
|
+
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
39
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
40
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
41
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A tool impl. Takes a Zod-parsed input and the runtime context
|
|
45
|
+
* (augmented with the analytics engine), returns a bounded text
|
|
46
|
+
* result. `makeTool` (T-10) wraps this with rate → auth → execute →
|
|
47
|
+
* budget → audit chain to produce an AI SDK `tool()` handle.
|
|
48
|
+
*/
|
|
49
|
+
export type ToolImpl<Input> = (input: Input, ctx: ToolContext & {
|
|
50
|
+
analytics: AnalyticsEngine;
|
|
51
|
+
}) => Promise<ToolResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Outcome recorded in `tool_call_audit.outcome`. Enum discipline —
|
|
54
|
+
* every wrapper code path resolves to exactly one of these.
|
|
55
|
+
*/
|
|
56
|
+
export type ToolOutcome = 'success' | 'rate_limited' | 'authorized_reject' | 'error';
|
|
57
|
+
/**
|
|
58
|
+
* A single row destined for `tool_call_audit`. Column mapping (see
|
|
59
|
+
* `core/schemas.ts` line 189):
|
|
60
|
+
* ts, brand, family_id, requester_user_id, tool_name, args,
|
|
61
|
+
* result_bytes, result_row_count, latency_ms, outcome, error_message
|
|
62
|
+
*/
|
|
63
|
+
export interface AuditEntry {
|
|
64
|
+
readonly ts: Date;
|
|
65
|
+
readonly brand: string;
|
|
66
|
+
readonly familyId: string;
|
|
67
|
+
readonly requesterUserId: string;
|
|
68
|
+
readonly toolName: string;
|
|
69
|
+
readonly args: Record<string, unknown>;
|
|
70
|
+
readonly resultBytes: number | null;
|
|
71
|
+
readonly resultRowCount: number | null;
|
|
72
|
+
readonly latencyMs: number | null;
|
|
73
|
+
readonly outcome: ToolOutcome;
|
|
74
|
+
readonly errorMessage: string | null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Batched writer for `tool_call_audit`. `record` is non-blocking —
|
|
78
|
+
* entries flush on batch-size or timer. `flush` and `close` are test
|
|
79
|
+
* hooks / graceful-shutdown hooks respectively.
|
|
80
|
+
*/
|
|
81
|
+
export interface AuditWriter {
|
|
82
|
+
record: (entry: AuditEntry) => void;
|
|
83
|
+
flush: () => Promise<void>;
|
|
84
|
+
close: () => Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Per-tool output ceiling enforced by `applyOutputBudget`. Text
|
|
88
|
+
* exceeding `maxBytes` is UTF-8-safely truncated with a
|
|
89
|
+
* `\n[truncated]` suffix. `maxRows` is informational — impls limit
|
|
90
|
+
* row count via SQL.
|
|
91
|
+
*/
|
|
92
|
+
export interface OutputBudget {
|
|
93
|
+
maxBytes: number;
|
|
94
|
+
maxRows: number;
|
|
95
|
+
}
|
|
96
|
+
export declare const DEFAULT_OUTPUT_BUDGET: OutputBudget;
|
|
97
|
+
/**
|
|
98
|
+
* Config for `createAnalyticsTools` factory. All fields except
|
|
99
|
+
* `analytics` are optional with sane defaults from `spec.md`.
|
|
100
|
+
*
|
|
101
|
+
* - `rateLimit: false` disables the limiter entirely (tests only).
|
|
102
|
+
* - `authorize: undefined` defaults to `familyScopeAuthorize` wired
|
|
103
|
+
* with `familyMembersProvider` if supplied.
|
|
104
|
+
* - `audit.enabled: false` makes `record` a no-op.
|
|
105
|
+
*/
|
|
106
|
+
export interface AnalyticsToolsConfig {
|
|
107
|
+
analytics: AnalyticsEngine;
|
|
108
|
+
authorize?: AuthorizeFn;
|
|
109
|
+
rateLimit?: RateLimitConfig | false;
|
|
110
|
+
audit?: {
|
|
111
|
+
enabled?: boolean;
|
|
112
|
+
batchSize?: number;
|
|
113
|
+
flushIntervalMs?: number;
|
|
114
|
+
retentionDays?: number;
|
|
115
|
+
};
|
|
116
|
+
outputBudget?: Partial<OutputBudget>;
|
|
117
|
+
logger?: ToolsLogger;
|
|
118
|
+
familyMembersProvider?: FamilyMembersProvider;
|
|
119
|
+
clock?: () => number;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE3E,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,WAAW,KACb,OAAO,CAAC,OAAO,CAAC,CAAA;AAErB;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gBAAgB,EAAE,MAAM,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,eAAO,MAAM,kBAAkB,EAAE,eAIhC,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAChE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAC/D,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAC/D,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;CACjE;AAED;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,CAAC,KAAK,IAAI,CAC5B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,WAAW,GAAG;IAAE,SAAS,EAAE,eAAe,CAAA;CAAE,KAC9C,OAAO,CAAC,UAAU,CAAC,CAAA;AAExB;;;GAGG;AACH,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,cAAc,GACd,mBAAmB,GACnB,OAAO,CAAA;AAEX;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;IACnC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,qBAAqB,EAAE,YAGnC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,eAAe,CAAA;IAC1B,SAAS,CAAC,EAAE,WAAW,CAAA;IACvB,SAAS,CAAC,EAAE,eAAe,GAAG,KAAK,CAAA;IACnC,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;IACD,YAAY,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;IACpC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;IAC7C,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type surface for @mongrov/analytics/tools.
|
|
3
|
+
*
|
|
4
|
+
* Types shared across tool impls, rate limiter, authorize hooks,
|
|
5
|
+
* audit writer, wrapper, and the top-level factory.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_RATE_LIMIT = {
|
|
8
|
+
perToolPerMinute: 20,
|
|
9
|
+
perToolPerHour: 200,
|
|
10
|
+
perUserPerMinute: 60,
|
|
11
|
+
};
|
|
12
|
+
export const DEFAULT_OUTPUT_BUDGET = {
|
|
13
|
+
maxBytes: 4096,
|
|
14
|
+
maxRows: 100,
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwCH,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,gBAAgB,EAAE,EAAE;IACpB,cAAc,EAAE,GAAG;IACnB,gBAAgB,EAAE,EAAE;CACrB,CAAA;AAwED,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,GAAG;CACb,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `makeTool` — compose an AI SDK `tool()` from a raw impl plus the
|
|
3
|
+
* rate → auth → execute → budget → audit chain.
|
|
4
|
+
*
|
|
5
|
+
* The chain order is load-bearing:
|
|
6
|
+
* 1. Rate limit → cheap, no I/O; short-circuits abusive callers.
|
|
7
|
+
* 2. Authorize → may hit SQL/provider; runs after rate cap.
|
|
8
|
+
* 3. Execute → real tool work.
|
|
9
|
+
* 4. Budget → truncate oversized text before returning.
|
|
10
|
+
* 5. Audit → record every terminal state (success / reject
|
|
11
|
+
* / rate-limited / error).
|
|
12
|
+
*
|
|
13
|
+
* Every branch records exactly one audit entry. The wrapper never
|
|
14
|
+
* throws — errors resolve to a short LLM-friendly string. Because
|
|
15
|
+
* the AI SDK `execute(input)` signature has no per-call context arg,
|
|
16
|
+
* the caller supplies a `ctxProvider()` closure that returns the
|
|
17
|
+
* current `ToolContext` at invocation time (the factory backs this
|
|
18
|
+
* with a mutable container updated per request).
|
|
19
|
+
*/
|
|
20
|
+
import type { ZodTypeAny } from 'zod';
|
|
21
|
+
import type { AnalyticsEngine } from '../core/types';
|
|
22
|
+
import type { RateLimiter } from './rate-limit';
|
|
23
|
+
import type { AuditWriter, AuthorizeFn, OutputBudget, ToolContext, ToolImpl, ToolsLogger } from './types';
|
|
24
|
+
export interface MakeToolConfig<Input> {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
/**
|
|
28
|
+
* Accepts any Zod schema whose parsed output is assignable to
|
|
29
|
+
* `Input`. Widened to `ZodTypeAny` so schemas with `.default()` /
|
|
30
|
+
* `.optional()` fields (whose input types include `undefined`)
|
|
31
|
+
* still fit — the AI SDK's own `tool()` uses the same shape.
|
|
32
|
+
*/
|
|
33
|
+
inputSchema: ZodTypeAny;
|
|
34
|
+
impl: ToolImpl<Input>;
|
|
35
|
+
analytics: AnalyticsEngine;
|
|
36
|
+
authorize?: AuthorizeFn;
|
|
37
|
+
rateLimiter?: RateLimiter | null;
|
|
38
|
+
audit: AuditWriter;
|
|
39
|
+
budget: OutputBudget;
|
|
40
|
+
logger?: ToolsLogger;
|
|
41
|
+
/**
|
|
42
|
+
* Returns the current `ToolContext` at execute-time. The factory
|
|
43
|
+
* backs this with a mutable container so a single tool handle
|
|
44
|
+
* threads per-request scope. Returning `null` triggers a clean
|
|
45
|
+
* error branch (no ctx → audit `error` outcome).
|
|
46
|
+
*/
|
|
47
|
+
ctxProvider: () => ToolContext | null;
|
|
48
|
+
clock?: () => number;
|
|
49
|
+
}
|
|
50
|
+
export declare function makeTool<Input>(cfg: MakeToolConfig<Input>): import("ai").Tool<ZodTypeAny, string> & {
|
|
51
|
+
execute: (args: any, options: import("ai").ToolExecutionOptions) => PromiseLike<string>;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=wrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../src/tools/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,WAAW,EACZ,MAAM,SAAS,CAAA;AAOhB,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,WAAW,EAAE,UAAU,CAAA;IACvB,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;IACrB,SAAS,EAAE,eAAe,CAAA;IAC1B,SAAS,CAAC,EAAE,WAAW,CAAA;IACvB,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IAChC,KAAK,EAAE,WAAW,CAAA;IAClB,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC;;EAqHzD"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `makeTool` — compose an AI SDK `tool()` from a raw impl plus the
|
|
3
|
+
* rate → auth → execute → budget → audit chain.
|
|
4
|
+
*
|
|
5
|
+
* The chain order is load-bearing:
|
|
6
|
+
* 1. Rate limit → cheap, no I/O; short-circuits abusive callers.
|
|
7
|
+
* 2. Authorize → may hit SQL/provider; runs after rate cap.
|
|
8
|
+
* 3. Execute → real tool work.
|
|
9
|
+
* 4. Budget → truncate oversized text before returning.
|
|
10
|
+
* 5. Audit → record every terminal state (success / reject
|
|
11
|
+
* / rate-limited / error).
|
|
12
|
+
*
|
|
13
|
+
* Every branch records exactly one audit entry. The wrapper never
|
|
14
|
+
* throws — errors resolve to a short LLM-friendly string. Because
|
|
15
|
+
* the AI SDK `execute(input)` signature has no per-call context arg,
|
|
16
|
+
* the caller supplies a `ctxProvider()` closure that returns the
|
|
17
|
+
* current `ToolContext` at invocation time (the factory backs this
|
|
18
|
+
* with a mutable container updated per request).
|
|
19
|
+
*/
|
|
20
|
+
import { tool } from 'ai';
|
|
21
|
+
import { applyOutputBudget } from './budget';
|
|
22
|
+
const RATE_LIMIT_MESSAGE = 'Rate limit exceeded. Try again shortly.';
|
|
23
|
+
const AUTHZ_MESSAGE = 'Not authorized to access that user\'s data.';
|
|
24
|
+
const ERROR_MESSAGE = 'Tool call failed.';
|
|
25
|
+
const NO_CONTEXT_MESSAGE = 'Tool call failed: request context not set.';
|
|
26
|
+
export function makeTool(cfg) {
|
|
27
|
+
const clock = cfg.clock ?? Date.now;
|
|
28
|
+
return tool({
|
|
29
|
+
description: cfg.description,
|
|
30
|
+
parameters: cfg.inputSchema,
|
|
31
|
+
execute: async (input) => {
|
|
32
|
+
const startMs = clock();
|
|
33
|
+
const ctx = cfg.ctxProvider();
|
|
34
|
+
// 0. Ctx guard — audit as error, no rate/auth/execute performed.
|
|
35
|
+
if (!ctx) {
|
|
36
|
+
cfg.audit.record({
|
|
37
|
+
ts: new Date(clock()),
|
|
38
|
+
brand: '',
|
|
39
|
+
familyId: '',
|
|
40
|
+
requesterUserId: '',
|
|
41
|
+
toolName: cfg.name,
|
|
42
|
+
args: input,
|
|
43
|
+
resultBytes: null,
|
|
44
|
+
resultRowCount: null,
|
|
45
|
+
latencyMs: clock() - startMs,
|
|
46
|
+
outcome: 'error',
|
|
47
|
+
errorMessage: 'context not set',
|
|
48
|
+
});
|
|
49
|
+
cfg.logger?.warn('tool invoked without context', { toolName: cfg.name });
|
|
50
|
+
return NO_CONTEXT_MESSAGE;
|
|
51
|
+
}
|
|
52
|
+
// 1. Rate limit.
|
|
53
|
+
if (cfg.rateLimiter && !cfg.rateLimiter.check(cfg.name, ctx.requesterUserId)) {
|
|
54
|
+
cfg.audit.record({
|
|
55
|
+
ts: new Date(clock()),
|
|
56
|
+
brand: ctx.brand,
|
|
57
|
+
familyId: ctx.familyId,
|
|
58
|
+
requesterUserId: ctx.requesterUserId,
|
|
59
|
+
toolName: cfg.name,
|
|
60
|
+
args: input,
|
|
61
|
+
resultBytes: null,
|
|
62
|
+
resultRowCount: null,
|
|
63
|
+
latencyMs: clock() - startMs,
|
|
64
|
+
outcome: 'rate_limited',
|
|
65
|
+
errorMessage: null,
|
|
66
|
+
});
|
|
67
|
+
return RATE_LIMIT_MESSAGE;
|
|
68
|
+
}
|
|
69
|
+
// 2. Authorize.
|
|
70
|
+
if (cfg.authorize) {
|
|
71
|
+
const ok = await cfg.authorize(cfg.name, input, ctx);
|
|
72
|
+
if (!ok) {
|
|
73
|
+
cfg.audit.record({
|
|
74
|
+
ts: new Date(clock()),
|
|
75
|
+
brand: ctx.brand,
|
|
76
|
+
familyId: ctx.familyId,
|
|
77
|
+
requesterUserId: ctx.requesterUserId,
|
|
78
|
+
toolName: cfg.name,
|
|
79
|
+
args: input,
|
|
80
|
+
resultBytes: null,
|
|
81
|
+
resultRowCount: null,
|
|
82
|
+
latencyMs: clock() - startMs,
|
|
83
|
+
outcome: 'authorized_reject',
|
|
84
|
+
errorMessage: null,
|
|
85
|
+
});
|
|
86
|
+
return AUTHZ_MESSAGE;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 3. Execute + 4. Budget + 5. Audit success/error.
|
|
90
|
+
try {
|
|
91
|
+
const raw = await cfg.impl(input, {
|
|
92
|
+
...ctx,
|
|
93
|
+
analytics: cfg.analytics,
|
|
94
|
+
});
|
|
95
|
+
const capped = applyOutputBudget(raw, cfg.budget);
|
|
96
|
+
cfg.audit.record({
|
|
97
|
+
ts: new Date(clock()),
|
|
98
|
+
brand: ctx.brand,
|
|
99
|
+
familyId: ctx.familyId,
|
|
100
|
+
requesterUserId: ctx.requesterUserId,
|
|
101
|
+
toolName: cfg.name,
|
|
102
|
+
args: input,
|
|
103
|
+
resultBytes: capped.bytes,
|
|
104
|
+
resultRowCount: capped.rowCount,
|
|
105
|
+
latencyMs: clock() - startMs,
|
|
106
|
+
outcome: 'success',
|
|
107
|
+
errorMessage: null,
|
|
108
|
+
});
|
|
109
|
+
return capped.text;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
113
|
+
cfg.audit.record({
|
|
114
|
+
ts: new Date(clock()),
|
|
115
|
+
brand: ctx.brand,
|
|
116
|
+
familyId: ctx.familyId,
|
|
117
|
+
requesterUserId: ctx.requesterUserId,
|
|
118
|
+
toolName: cfg.name,
|
|
119
|
+
args: input,
|
|
120
|
+
resultBytes: null,
|
|
121
|
+
resultRowCount: null,
|
|
122
|
+
latencyMs: clock() - startMs,
|
|
123
|
+
outcome: 'error',
|
|
124
|
+
errorMessage,
|
|
125
|
+
});
|
|
126
|
+
cfg.logger?.warn('tool execution failed', {
|
|
127
|
+
toolName: cfg.name,
|
|
128
|
+
err: errorMessage,
|
|
129
|
+
});
|
|
130
|
+
return ERROR_MESSAGE;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=wrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.js","sourceRoot":"","sources":["../../src/tools/wrap.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongrov/analytics",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.9",
|
|
4
4
|
"description": "DuckDB + R2 Iceberg local analytics for @mongrov apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,12 +52,22 @@
|
|
|
52
52
|
"files": [
|
|
53
53
|
"dist"
|
|
54
54
|
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc -p tsconfig.build.json",
|
|
57
|
+
"clean": "rm -rf dist",
|
|
58
|
+
"prebuild": "pnpm run clean",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"prepublishOnly": "pnpm run build"
|
|
63
|
+
},
|
|
55
64
|
"peerDependencies": {
|
|
65
|
+
"@mongrov/db": "workspace:*",
|
|
56
66
|
"@mongrov/types": "^0.4.0",
|
|
67
|
+
"ai": ">=4",
|
|
57
68
|
"react": ">=19",
|
|
58
69
|
"react-native": ">=0.81",
|
|
59
|
-
"react-native-duckdb": ">=1.0.0"
|
|
60
|
-
"@mongrov/db": "0.3.0"
|
|
70
|
+
"react-native-duckdb": ">=1.0.0"
|
|
61
71
|
},
|
|
62
72
|
"peerDependenciesMeta": {
|
|
63
73
|
"react": {
|
|
@@ -77,25 +87,18 @@
|
|
|
77
87
|
"xstate": "^5.30.0"
|
|
78
88
|
},
|
|
79
89
|
"devDependencies": {
|
|
90
|
+
"@mongrov/db": "workspace:*",
|
|
91
|
+
"@mongrov/types": "workspace:*",
|
|
80
92
|
"@testing-library/react": "^14.3.1",
|
|
81
93
|
"@types/react": "^18.3.12",
|
|
82
94
|
"@types/react-dom": "^18.3.1",
|
|
95
|
+
"ai": "^4.3.0",
|
|
83
96
|
"fast-check": "^4.9.0",
|
|
84
97
|
"jsdom": "^25.0.1",
|
|
85
98
|
"react": "^18.3.1",
|
|
86
99
|
"react-dom": "^18.3.1",
|
|
87
100
|
"typescript": "^5.9.3",
|
|
88
101
|
"vitest": "^2.1.9",
|
|
89
|
-
"zod": "^3.23.8"
|
|
90
|
-
"@mongrov/db": "0.3.0",
|
|
91
|
-
"@mongrov/types": "0.4.0"
|
|
92
|
-
},
|
|
93
|
-
"scripts": {
|
|
94
|
-
"build": "tsc -p tsconfig.build.json",
|
|
95
|
-
"clean": "rm -rf dist",
|
|
96
|
-
"prebuild": "pnpm run clean",
|
|
97
|
-
"test": "vitest run",
|
|
98
|
-
"test:watch": "vitest",
|
|
99
|
-
"typecheck": "tsc --noEmit"
|
|
102
|
+
"zod": "^3.23.8"
|
|
100
103
|
}
|
|
101
|
-
}
|
|
104
|
+
}
|