@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,25 @@
|
|
|
1
|
+
import type { AnalyticsEngine } from '../../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Scriptable AnalyticsEngine fake for tool tests.
|
|
4
|
+
*
|
|
5
|
+
* Unlike the rules-side fake (single global queue), this fake supports
|
|
6
|
+
* per-SQL-substring row queues so multi-query tools (compare, activity)
|
|
7
|
+
* can script distinct responses per call.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const engine = createFakeEngine()
|
|
11
|
+
* engine.queueRows('FROM hrv', [{ day: '2026-07-08', avg_hrv: 45 }])
|
|
12
|
+
* engine.queueRows('FROM sleep_session', [...])
|
|
13
|
+
* // ...invoke tool...
|
|
14
|
+
* engine.calls.forEach(c => ...)
|
|
15
|
+
*/
|
|
16
|
+
export interface FakeEngine extends AnalyticsEngine {
|
|
17
|
+
readonly calls: {
|
|
18
|
+
sql: string;
|
|
19
|
+
params: Record<string, unknown>;
|
|
20
|
+
}[];
|
|
21
|
+
queueRows: (sqlSubstring: string, rows: unknown[]) => void;
|
|
22
|
+
setError: (err: Error | null) => void;
|
|
23
|
+
}
|
|
24
|
+
export declare function createFakeEngine(): FakeEngine;
|
|
25
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/tools/__fakes__/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEvD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,UAAW,SAAQ,eAAe;IACjD,QAAQ,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,CAAA;IAClE,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IAC1D,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,CAAA;CACtC;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAkC7C"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function createFakeEngine() {
|
|
2
|
+
const queues = [];
|
|
3
|
+
const calls = [];
|
|
4
|
+
let error = null;
|
|
5
|
+
const engine = {
|
|
6
|
+
async attach() { },
|
|
7
|
+
async detach() { },
|
|
8
|
+
async execute(sql, params) {
|
|
9
|
+
calls.push({ sql, params: params ?? {} });
|
|
10
|
+
if (error)
|
|
11
|
+
throw error;
|
|
12
|
+
const idx = queues.findIndex(q => sql.includes(q.substring));
|
|
13
|
+
if (idx === -1)
|
|
14
|
+
return [];
|
|
15
|
+
const [q] = queues.splice(idx, 1);
|
|
16
|
+
return q.rows;
|
|
17
|
+
},
|
|
18
|
+
stream() {
|
|
19
|
+
throw new Error('stream not implemented in fake');
|
|
20
|
+
},
|
|
21
|
+
createAppender() {
|
|
22
|
+
throw new Error('createAppender not implemented in fake');
|
|
23
|
+
},
|
|
24
|
+
state: 'attached',
|
|
25
|
+
lastError: null,
|
|
26
|
+
catalog: 'fake',
|
|
27
|
+
subscribe: () => () => { },
|
|
28
|
+
async setRetention() { },
|
|
29
|
+
async getLastAttach() { return null; },
|
|
30
|
+
async close() { },
|
|
31
|
+
calls,
|
|
32
|
+
queueRows(substring, rows) { queues.push({ substring, rows }); },
|
|
33
|
+
setError(err) { error = err; },
|
|
34
|
+
};
|
|
35
|
+
return engine;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/tools/__fakes__/engine.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,gBAAgB;IAC9B,MAAM,MAAM,GAA6C,EAAE,CAAA;IAC3D,MAAM,KAAK,GAAuD,EAAE,CAAA;IACpE,IAAI,KAAK,GAAiB,IAAI,CAAA;IAE9B,MAAM,MAAM,GAAe;QACzB,KAAK,CAAC,MAAM,KAAI,CAAC;QACjB,KAAK,CAAC,MAAM,KAAI,CAAC;QACjB,KAAK,CAAC,OAAO,CAAI,GAAW,EAAE,MAAgC;YAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC,CAAA;YACzC,IAAI,KAAK;gBAAE,MAAM,KAAK,CAAA;YACtB,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;YAC5D,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,OAAO,EAAS,CAAA;YAChC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;YACjC,OAAO,CAAC,CAAC,IAAW,CAAA;QACtB,CAAC;QACD,MAAM;YACJ,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;QACnD,CAAC;QACD,cAAc;YACZ,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QAC3D,CAAC;QACD,KAAK,EAAE,UAAU;QACjB,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,MAAM;QACf,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;QACzB,KAAK,CAAC,YAAY,KAAI,CAAC;QACvB,KAAK,CAAC,aAAa,KAAK,OAAO,IAAI,CAAA,CAAC,CAAC;QACrC,KAAK,CAAC,KAAK,KAAI,CAAC;QAChB,KAAK;QACL,SAAS,CAAC,SAAS,EAAE,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA,CAAC,CAAC;QAC/D,QAAQ,CAAC,GAAG,IAAI,KAAK,GAAG,GAAG,CAAA,CAAC,CAAC;KAC9B,CAAA;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batched writer for `tool_call_audit`.
|
|
3
|
+
*
|
|
4
|
+
* `record()` enqueues synchronously (non-blocking). The batch flushes
|
|
5
|
+
* whenever:
|
|
6
|
+
* - the pending queue reaches `batchSize` (default 10), or
|
|
7
|
+
* - the flush timer fires (`flushIntervalMs`, default 1000ms).
|
|
8
|
+
*
|
|
9
|
+
* `flush()` and `close()` are cooperative exit hooks — `close`
|
|
10
|
+
* additionally halts the timer.
|
|
11
|
+
*
|
|
12
|
+
* On flush failure the batch is retained and retried on the next
|
|
13
|
+
* tick. A defensive `MAX_CONSECUTIVE_FAILURES` cap drops the buffer
|
|
14
|
+
* after repeated errors to avoid unbounded growth on a stuck
|
|
15
|
+
* warehouse.
|
|
16
|
+
*
|
|
17
|
+
* SQL uses a single INSERT with a suffix-indexed VALUES tuple per
|
|
18
|
+
* row (`$p0_ts, $p0_brand, …, $p1_ts, …`). DuckDB honors named
|
|
19
|
+
* parameters in this form.
|
|
20
|
+
*/
|
|
21
|
+
import type { AnalyticsEngine } from '../core/types';
|
|
22
|
+
import type { AuditWriter, ToolsLogger } from './types';
|
|
23
|
+
export interface CreateAuditWriterConfig {
|
|
24
|
+
analytics: AnalyticsEngine;
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
batchSize?: number;
|
|
27
|
+
flushIntervalMs?: number;
|
|
28
|
+
logger?: ToolsLogger;
|
|
29
|
+
}
|
|
30
|
+
export declare function createAuditWriter(cfg: CreateAuditWriterConfig): AuditWriter;
|
|
31
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/tools/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAc,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAoBnE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,eAAe,CAAA;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,uBAAuB,GAAG,WAAW,CA6F3E"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batched writer for `tool_call_audit`.
|
|
3
|
+
*
|
|
4
|
+
* `record()` enqueues synchronously (non-blocking). The batch flushes
|
|
5
|
+
* whenever:
|
|
6
|
+
* - the pending queue reaches `batchSize` (default 10), or
|
|
7
|
+
* - the flush timer fires (`flushIntervalMs`, default 1000ms).
|
|
8
|
+
*
|
|
9
|
+
* `flush()` and `close()` are cooperative exit hooks — `close`
|
|
10
|
+
* additionally halts the timer.
|
|
11
|
+
*
|
|
12
|
+
* On flush failure the batch is retained and retried on the next
|
|
13
|
+
* tick. A defensive `MAX_CONSECUTIVE_FAILURES` cap drops the buffer
|
|
14
|
+
* after repeated errors to avoid unbounded growth on a stuck
|
|
15
|
+
* warehouse.
|
|
16
|
+
*
|
|
17
|
+
* SQL uses a single INSERT with a suffix-indexed VALUES tuple per
|
|
18
|
+
* row (`$p0_ts, $p0_brand, …, $p1_ts, …`). DuckDB honors named
|
|
19
|
+
* parameters in this form.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_BATCH_SIZE = 10;
|
|
22
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 1000;
|
|
23
|
+
const MAX_CONSECUTIVE_FAILURES = 5;
|
|
24
|
+
const COLUMNS = [
|
|
25
|
+
'ts',
|
|
26
|
+
'brand',
|
|
27
|
+
'family_id',
|
|
28
|
+
'requester_user_id',
|
|
29
|
+
'tool_name',
|
|
30
|
+
'args',
|
|
31
|
+
'result_bytes',
|
|
32
|
+
'result_row_count',
|
|
33
|
+
'latency_ms',
|
|
34
|
+
'outcome',
|
|
35
|
+
'error_message',
|
|
36
|
+
];
|
|
37
|
+
export function createAuditWriter(cfg) {
|
|
38
|
+
const enabled = cfg.enabled ?? true;
|
|
39
|
+
const batchSize = cfg.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
40
|
+
const flushIntervalMs = cfg.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
41
|
+
const logger = cfg.logger;
|
|
42
|
+
const pending = [];
|
|
43
|
+
let timer = null;
|
|
44
|
+
let consecutiveFailures = 0;
|
|
45
|
+
let inFlight = null;
|
|
46
|
+
let closed = false;
|
|
47
|
+
function ensureTimer() {
|
|
48
|
+
if (timer || closed)
|
|
49
|
+
return;
|
|
50
|
+
timer = setInterval(() => {
|
|
51
|
+
void doFlush();
|
|
52
|
+
}, flushIntervalMs);
|
|
53
|
+
// Node timers are keep-alive by default; unref so a lingering
|
|
54
|
+
// audit writer doesn't block process exit.
|
|
55
|
+
if (typeof timer.unref === 'function') {
|
|
56
|
+
timer.unref();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function clearFlushTimer() {
|
|
60
|
+
if (!timer)
|
|
61
|
+
return;
|
|
62
|
+
clearInterval(timer);
|
|
63
|
+
timer = null;
|
|
64
|
+
}
|
|
65
|
+
async function doFlush() {
|
|
66
|
+
if (inFlight)
|
|
67
|
+
return inFlight;
|
|
68
|
+
if (pending.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
const batch = pending.splice(0, pending.length);
|
|
71
|
+
inFlight = (async () => {
|
|
72
|
+
try {
|
|
73
|
+
await writeBatch(cfg.analytics, batch);
|
|
74
|
+
consecutiveFailures = 0;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
consecutiveFailures += 1;
|
|
78
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
79
|
+
logger?.warn('audit flush failed', {
|
|
80
|
+
err: errMsg,
|
|
81
|
+
batchSize: batch.length,
|
|
82
|
+
consecutiveFailures,
|
|
83
|
+
});
|
|
84
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
85
|
+
logger?.error('audit flush cap reached, dropping batch', {
|
|
86
|
+
droppedRows: batch.length,
|
|
87
|
+
});
|
|
88
|
+
consecutiveFailures = 0;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Retain: prepend so retry preserves order.
|
|
92
|
+
pending.unshift(...batch);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
inFlight = null;
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
return inFlight;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
record(entry) {
|
|
103
|
+
if (!enabled || closed)
|
|
104
|
+
return;
|
|
105
|
+
pending.push(entry);
|
|
106
|
+
ensureTimer();
|
|
107
|
+
if (pending.length >= batchSize) {
|
|
108
|
+
void doFlush();
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
async flush() {
|
|
112
|
+
if (!enabled)
|
|
113
|
+
return;
|
|
114
|
+
await doFlush();
|
|
115
|
+
// If the flush swallowed a failure and re-queued, drain once
|
|
116
|
+
// more so callers get a best-effort guarantee.
|
|
117
|
+
if (pending.length > 0) {
|
|
118
|
+
await doFlush();
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
async close() {
|
|
122
|
+
closed = true;
|
|
123
|
+
clearFlushTimer();
|
|
124
|
+
if (!enabled)
|
|
125
|
+
return;
|
|
126
|
+
await doFlush();
|
|
127
|
+
if (pending.length > 0) {
|
|
128
|
+
await doFlush();
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function writeBatch(analytics, batch) {
|
|
134
|
+
if (batch.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
const tuples = [];
|
|
137
|
+
const params = {};
|
|
138
|
+
batch.forEach((entry, i) => {
|
|
139
|
+
const prefix = `p${i}`;
|
|
140
|
+
const placeholders = COLUMNS.map(col => `$${prefix}_${col}`);
|
|
141
|
+
tuples.push(`(${placeholders.join(', ')})`);
|
|
142
|
+
params[`${prefix}_ts`] = entry.ts;
|
|
143
|
+
params[`${prefix}_brand`] = entry.brand;
|
|
144
|
+
params[`${prefix}_family_id`] = entry.familyId;
|
|
145
|
+
params[`${prefix}_requester_user_id`] = entry.requesterUserId;
|
|
146
|
+
params[`${prefix}_tool_name`] = entry.toolName;
|
|
147
|
+
params[`${prefix}_args`] = JSON.stringify(entry.args);
|
|
148
|
+
params[`${prefix}_result_bytes`] = entry.resultBytes;
|
|
149
|
+
params[`${prefix}_result_row_count`] = entry.resultRowCount;
|
|
150
|
+
params[`${prefix}_latency_ms`] = entry.latencyMs;
|
|
151
|
+
params[`${prefix}_outcome`] = entry.outcome;
|
|
152
|
+
params[`${prefix}_error_message`] = entry.errorMessage;
|
|
153
|
+
});
|
|
154
|
+
const sql = `INSERT INTO tool_call_audit (${COLUMNS.join(', ')}) VALUES ${tuples.join(', ')}`;
|
|
155
|
+
await analytics.execute(sql, params);
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/tools/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAC7B,MAAM,yBAAyB,GAAG,IAAI,CAAA;AACtC,MAAM,wBAAwB,GAAG,CAAC,CAAA;AAElC,MAAM,OAAO,GAAG;IACd,IAAI;IACJ,OAAO;IACP,WAAW;IACX,mBAAmB;IACnB,WAAW;IACX,MAAM;IACN,cAAc;IACd,kBAAkB;IAClB,YAAY;IACZ,SAAS;IACT,eAAe;CACP,CAAA;AAUV,MAAM,UAAU,iBAAiB,CAAC,GAA4B;IAC5D,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,IAAI,CAAA;IACnC,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,kBAAkB,CAAA;IACrD,MAAM,eAAe,GAAG,GAAG,CAAC,eAAe,IAAI,yBAAyB,CAAA;IACxE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAA;IAEzB,MAAM,OAAO,GAAiB,EAAE,CAAA;IAChC,IAAI,KAAK,GAA0C,IAAI,CAAA;IACvD,IAAI,mBAAmB,GAAG,CAAC,CAAA;IAC3B,IAAI,QAAQ,GAAyB,IAAI,CAAA;IACzC,IAAI,MAAM,GAAG,KAAK,CAAA;IAElB,SAAS,WAAW;QAClB,IAAI,KAAK,IAAI,MAAM;YAAE,OAAM;QAC3B,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YACvB,KAAK,OAAO,EAAE,CAAA;QAChB,CAAC,EAAE,eAAe,CAAC,CAAA;QACnB,8DAA8D;QAC9D,2CAA2C;QAC3C,IAAI,OAAQ,KAA2C,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC5E,KAA0C,CAAC,KAAK,EAAE,CAAA;QACrD,CAAC;IACH,CAAC;IAED,SAAS,eAAe;QACtB,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,aAAa,CAAC,KAAK,CAAC,CAAA;QACpB,KAAK,GAAG,IAAI,CAAA;IACd,CAAC;IAED,KAAK,UAAU,OAAO;QACpB,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAChC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;QAC/C,QAAQ,GAAG,CAAC,KAAK,IAAI,EAAE;YACrB,IAAI,CAAC;gBACH,MAAM,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;gBACtC,mBAAmB,GAAG,CAAC,CAAA;YACzB,CAAC;YACD,OAAO,GAAG,EAAE,CAAC;gBACX,mBAAmB,IAAI,CAAC,CAAA;gBACxB,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;gBAC/D,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE;oBACjC,GAAG,EAAE,MAAM;oBACX,SAAS,EAAE,KAAK,CAAC,MAAM;oBACvB,mBAAmB;iBACpB,CAAC,CAAA;gBACF,IAAI,mBAAmB,IAAI,wBAAwB,EAAE,CAAC;oBACpD,MAAM,EAAE,KAAK,CAAC,yCAAyC,EAAE;wBACvD,WAAW,EAAE,KAAK,CAAC,MAAM;qBAC1B,CAAC,CAAA;oBACF,mBAAmB,GAAG,CAAC,CAAA;gBACzB,CAAC;qBACI,CAAC;oBACJ,4CAA4C;oBAC5C,OAAO,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,CAAA;gBAC3B,CAAC;YACH,CAAC;oBACO,CAAC;gBACP,QAAQ,GAAG,IAAI,CAAA;YACjB,CAAC;QACH,CAAC,CAAC,EAAE,CAAA;QACJ,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,OAAO;QACL,MAAM,CAAC,KAAK;YACV,IAAI,CAAC,OAAO,IAAI,MAAM;gBAAE,OAAM;YAC9B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACnB,WAAW,EAAE,CAAA;YACb,IAAI,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;gBAChC,KAAK,OAAO,EAAE,CAAA;YAChB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK;YACT,IAAI,CAAC,OAAO;gBAAE,OAAM;YACpB,MAAM,OAAO,EAAE,CAAA;YACf,6DAA6D;YAC7D,+CAA+C;YAC/C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,OAAO,EAAE,CAAA;YACjB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK;YACT,MAAM,GAAG,IAAI,CAAA;YACb,eAAe,EAAE,CAAA;YACjB,IAAI,CAAC,OAAO;gBAAE,OAAM;YACpB,MAAM,OAAO,EAAE,CAAA;YACf,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,OAAO,EAAE,CAAA;YACjB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,SAA0B,EAC1B,KAAmB;IAEnB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAE9B,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,MAAM,MAAM,GAA4B,EAAE,CAAA;IAE1C,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAA;QACtB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC,CAAA;QAC5D,MAAM,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAE3C,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,GAAG,KAAK,CAAC,EAAE,CAAA;QACjC,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,GAAG,KAAK,CAAC,KAAK,CAAA;QACvC,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAA;QAC9C,MAAM,CAAC,GAAG,MAAM,oBAAoB,CAAC,GAAG,KAAK,CAAC,eAAe,CAAA;QAC7D,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAA;QAC9C,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACrD,MAAM,CAAC,GAAG,MAAM,eAAe,CAAC,GAAG,KAAK,CAAC,WAAW,CAAA;QACpD,MAAM,CAAC,GAAG,MAAM,mBAAmB,CAAC,GAAG,KAAK,CAAC,cAAc,CAAA;QAC3D,MAAM,CAAC,GAAG,MAAM,aAAa,CAAC,GAAG,KAAK,CAAC,SAAS,CAAA;QAChD,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,GAAG,KAAK,CAAC,OAAO,CAAA;QAC3C,MAAM,CAAC,GAAG,MAAM,gBAAgB,CAAC,GAAG,KAAK,CAAC,YAAY,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,MAAM,GAAG,GACL,gCAAgC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;IAErF,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AACtC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorize hooks for analytics tools.
|
|
3
|
+
*
|
|
4
|
+
* `familyScopeAuthorize` — grants access iff `args.userId` is either
|
|
5
|
+
* the requester themselves or another member of the requester's
|
|
6
|
+
* family. When `config.familyMembersProvider` is supplied (the
|
|
7
|
+
* factory path since T-11), membership resolves via that provider;
|
|
8
|
+
* otherwise the legacy SQL path against a `family_member` table is
|
|
9
|
+
* used (kept for standalone hook usage and backwards compat).
|
|
10
|
+
*
|
|
11
|
+
* Both hooks fail closed on any error — provider throws, engine
|
|
12
|
+
* throws, missing/malformed args — return `false` rather than
|
|
13
|
+
* surfacing the error to the LLM.
|
|
14
|
+
*/
|
|
15
|
+
import type { AnalyticsEngine, FamilyMembersProvider } from '../core/types';
|
|
16
|
+
import type { AuthorizeFn } from './types';
|
|
17
|
+
export interface AuthorizeConfig {
|
|
18
|
+
/**
|
|
19
|
+
* When present, membership is resolved via the provider instead of
|
|
20
|
+
* SQL. This is the path wired by `createAnalyticsTools`.
|
|
21
|
+
*/
|
|
22
|
+
familyMembersProvider?: FamilyMembersProvider;
|
|
23
|
+
}
|
|
24
|
+
export declare function familyScopeAuthorize(analytics: AnalyticsEngine, config?: AuthorizeConfig): AuthorizeFn;
|
|
25
|
+
/**
|
|
26
|
+
* `orgScopeAuthorize` — parity with family scope but against an
|
|
27
|
+
* `org_member` table. Orgs are not first-class in v0.1.0; the ctx
|
|
28
|
+
* `familyId` is treated as the org id until org membership is wired
|
|
29
|
+
* through `RequestContext`.
|
|
30
|
+
*
|
|
31
|
+
* When `familyMembersProvider` is supplied, the same provider is
|
|
32
|
+
* consulted (v0.1.0 shim — a dedicated `orgMembersProvider` will
|
|
33
|
+
* split out once orgs land).
|
|
34
|
+
*/
|
|
35
|
+
export declare function orgScopeAuthorize(analytics: AnalyticsEngine, config?: AuthorizeConfig): AuthorizeFn;
|
|
36
|
+
//# sourceMappingURL=authorize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/tools/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;CAC9C;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,eAAe,EAC1B,MAAM,GAAE,eAAoB,GAC3B,WAAW,CAmCb;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,eAAe,EAC1B,MAAM,GAAE,eAAoB,GAC3B,WAAW,CAmCb"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorize hooks for analytics tools.
|
|
3
|
+
*
|
|
4
|
+
* `familyScopeAuthorize` — grants access iff `args.userId` is either
|
|
5
|
+
* the requester themselves or another member of the requester's
|
|
6
|
+
* family. When `config.familyMembersProvider` is supplied (the
|
|
7
|
+
* factory path since T-11), membership resolves via that provider;
|
|
8
|
+
* otherwise the legacy SQL path against a `family_member` table is
|
|
9
|
+
* used (kept for standalone hook usage and backwards compat).
|
|
10
|
+
*
|
|
11
|
+
* Both hooks fail closed on any error — provider throws, engine
|
|
12
|
+
* throws, missing/malformed args — return `false` rather than
|
|
13
|
+
* surfacing the error to the LLM.
|
|
14
|
+
*/
|
|
15
|
+
export function familyScopeAuthorize(analytics, config = {}) {
|
|
16
|
+
return async (_toolName, args, ctx) => {
|
|
17
|
+
const targetUserId = args.userId;
|
|
18
|
+
if (typeof targetUserId !== 'string' || targetUserId.length === 0) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (targetUserId === ctx.requesterUserId) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (config.familyMembersProvider) {
|
|
25
|
+
try {
|
|
26
|
+
const members = await config.familyMembersProvider({
|
|
27
|
+
brand: ctx.brand,
|
|
28
|
+
familyId: ctx.familyId,
|
|
29
|
+
});
|
|
30
|
+
return members.includes(targetUserId);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const rows = await analytics.execute('SELECT 1 AS one FROM family_member '
|
|
38
|
+
+ 'WHERE family_id = $familyId AND user_id = $userId LIMIT 1', { familyId: ctx.familyId, userId: targetUserId });
|
|
39
|
+
return rows.length > 0;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `orgScopeAuthorize` — parity with family scope but against an
|
|
48
|
+
* `org_member` table. Orgs are not first-class in v0.1.0; the ctx
|
|
49
|
+
* `familyId` is treated as the org id until org membership is wired
|
|
50
|
+
* through `RequestContext`.
|
|
51
|
+
*
|
|
52
|
+
* When `familyMembersProvider` is supplied, the same provider is
|
|
53
|
+
* consulted (v0.1.0 shim — a dedicated `orgMembersProvider` will
|
|
54
|
+
* split out once orgs land).
|
|
55
|
+
*/
|
|
56
|
+
export function orgScopeAuthorize(analytics, config = {}) {
|
|
57
|
+
return async (_toolName, args, ctx) => {
|
|
58
|
+
const targetUserId = args.userId;
|
|
59
|
+
if (typeof targetUserId !== 'string' || targetUserId.length === 0) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (targetUserId === ctx.requesterUserId) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (config.familyMembersProvider) {
|
|
66
|
+
try {
|
|
67
|
+
const members = await config.familyMembersProvider({
|
|
68
|
+
brand: ctx.brand,
|
|
69
|
+
familyId: ctx.familyId,
|
|
70
|
+
});
|
|
71
|
+
return members.includes(targetUserId);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const rows = await analytics.execute('SELECT 1 AS one FROM org_member '
|
|
79
|
+
+ 'WHERE org_id = $orgId AND user_id = $userId LIMIT 1', { orgId: ctx.familyId, userId: targetUserId });
|
|
80
|
+
return rows.length > 0;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=authorize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/tools/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH,MAAM,UAAU,oBAAoB,CAClC,SAA0B,EAC1B,SAA0B,EAAE;IAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAA;QAChC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,YAAY,KAAK,GAAG,CAAC,eAAe,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC;oBACjD,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;iBACvB,CAAC,CAAA;gBACF,OAAO,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;YACvC,CAAC;YACD,MAAM,CAAC;gBACL,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAClC,qCAAqC;kBACnC,2DAA2D,EAC7D,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CACjD,CAAA;YACD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QACxB,CAAC;QACD,MAAM,CAAC;YACL,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAA0B,EAC1B,SAA0B,EAAE;IAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAA;QAChC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,YAAY,KAAK,GAAG,CAAC,eAAe,EAAE,CAAC;YACzC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC;oBACjD,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;iBACvB,CAAC,CAAA;gBACF,OAAO,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;YACvC,CAAC;YACD,MAAM,CAAC;gBACL,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAClC,kCAAkC;kBAChC,qDAAqD,EACvD,EAAE,KAAK,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CAC9C,CAAA;YACD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QACxB,CAAC;QACD,MAAM,CAAC;YACL,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output budget enforcement for tool results.
|
|
3
|
+
*
|
|
4
|
+
* `applyOutputBudget` truncates `ToolResult.text` when its UTF-8
|
|
5
|
+
* byte size exceeds `budget.maxBytes`, appending a `\n[truncated]`
|
|
6
|
+
* suffix. Truncation is UTF-8-safe: bytes are sliced and re-decoded
|
|
7
|
+
* with `fatal: false` so any partial multi-byte codepoint at the
|
|
8
|
+
* boundary is dropped rather than emitting invalid sequences.
|
|
9
|
+
*
|
|
10
|
+
* `budget.maxRows` is informational — tool impls limit row count
|
|
11
|
+
* via SQL LIMIT. This helper does not attempt row-level truncation.
|
|
12
|
+
*/
|
|
13
|
+
import type { OutputBudget, ToolResult } from './types';
|
|
14
|
+
export declare function applyOutputBudget(result: ToolResult, budget: OutputBudget): ToolResult;
|
|
15
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/tools/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAIvD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,YAAY,GACnB,UAAU,CAiBZ"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output budget enforcement for tool results.
|
|
3
|
+
*
|
|
4
|
+
* `applyOutputBudget` truncates `ToolResult.text` when its UTF-8
|
|
5
|
+
* byte size exceeds `budget.maxBytes`, appending a `\n[truncated]`
|
|
6
|
+
* suffix. Truncation is UTF-8-safe: bytes are sliced and re-decoded
|
|
7
|
+
* with `fatal: false` so any partial multi-byte codepoint at the
|
|
8
|
+
* boundary is dropped rather than emitting invalid sequences.
|
|
9
|
+
*
|
|
10
|
+
* `budget.maxRows` is informational — tool impls limit row count
|
|
11
|
+
* via SQL LIMIT. This helper does not attempt row-level truncation.
|
|
12
|
+
*/
|
|
13
|
+
const TRUNCATE_SUFFIX = '\n[truncated]';
|
|
14
|
+
export function applyOutputBudget(result, budget) {
|
|
15
|
+
if (result.bytes <= budget.maxBytes)
|
|
16
|
+
return result;
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
const suffixBytes = encoder.encode(TRUNCATE_SUFFIX).byteLength;
|
|
19
|
+
const targetBytes = Math.max(0, budget.maxBytes - suffixBytes);
|
|
20
|
+
const encoded = encoder.encode(result.text);
|
|
21
|
+
const safeEnd = utf8SafeBoundary(encoded, Math.min(targetBytes, encoded.byteLength));
|
|
22
|
+
const decoded = new TextDecoder('utf-8').decode(encoded.slice(0, safeEnd));
|
|
23
|
+
const truncated = `${decoded}${TRUNCATE_SUFFIX}`;
|
|
24
|
+
return {
|
|
25
|
+
text: truncated,
|
|
26
|
+
rowCount: result.rowCount,
|
|
27
|
+
bytes: encoder.encode(truncated).byteLength,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Walk `end` backward until it points to a valid UTF-8 codepoint
|
|
32
|
+
* boundary — i.e. either past-the-end of the array or at a byte
|
|
33
|
+
* whose top bits are not `10` (continuation). This guarantees that
|
|
34
|
+
* `bytes.slice(0, boundary)` decodes without emitting replacement
|
|
35
|
+
* characters for a partial trailing multibyte sequence.
|
|
36
|
+
*/
|
|
37
|
+
function utf8SafeBoundary(bytes, end) {
|
|
38
|
+
let boundary = end;
|
|
39
|
+
// A continuation byte matches 10xxxxxx (0x80..0xBF).
|
|
40
|
+
while (boundary > 0 && (bytes[boundary] & 0xC0) === 0x80) {
|
|
41
|
+
boundary -= 1;
|
|
42
|
+
}
|
|
43
|
+
// If boundary now points to a lead byte (top bit set), check
|
|
44
|
+
// whether the full sequence fits inside `end`. If not, drop the
|
|
45
|
+
// lead byte as well.
|
|
46
|
+
if (boundary > 0 && boundary < bytes.byteLength) {
|
|
47
|
+
const lead = bytes[boundary];
|
|
48
|
+
let seqLen = 0;
|
|
49
|
+
if ((lead & 0x80) === 0x00)
|
|
50
|
+
seqLen = 1;
|
|
51
|
+
else if ((lead & 0xE0) === 0xC0)
|
|
52
|
+
seqLen = 2;
|
|
53
|
+
else if ((lead & 0xF0) === 0xE0)
|
|
54
|
+
seqLen = 3;
|
|
55
|
+
else if ((lead & 0xF8) === 0xF0)
|
|
56
|
+
seqLen = 4;
|
|
57
|
+
if (seqLen > 1 && boundary + seqLen > end) {
|
|
58
|
+
// Partial sequence — drop lead byte.
|
|
59
|
+
return boundary;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return boundary;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/tools/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,eAAe,GAAG,eAAe,CAAA;AAEvC,MAAM,UAAU,iBAAiB,CAC/B,MAAkB,EAClB,MAAoB;IAEpB,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAA;IAElD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,UAAU,CAAA;IAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,WAAW,CAAC,CAAA;IAE9D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC3C,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAA;IACpF,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;IAC1E,MAAM,SAAS,GAAG,GAAG,OAAO,GAAG,eAAe,EAAE,CAAA;IAEhD,OAAO;QACL,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,UAAU;KAC5C,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,KAAiB,EAAE,GAAW;IACtD,IAAI,QAAQ,GAAG,GAAG,CAAA;IAClB,qDAAqD;IACrD,OAAO,QAAQ,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QACzD,QAAQ,IAAI,CAAC,CAAA;IACf,CAAC;IACD,6DAA6D;IAC7D,gEAAgE;IAChE,qBAAqB;IACrB,IAAI,QAAQ,GAAG,CAAC,IAAI,QAAQ,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC5B,IAAI,MAAM,GAAG,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI;YAAE,MAAM,GAAG,CAAC,CAAA;aACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI;YAAE,MAAM,GAAG,CAAC,CAAA;aACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI;YAAE,MAAM,GAAG,CAAC,CAAA;aACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI;YAAE,MAAM,GAAG,CAAC,CAAA;QAC3C,IAAI,MAAM,GAAG,CAAC,IAAI,QAAQ,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC;YAC1C,qCAAqC;YACrC,OAAO,QAAQ,CAAA;QACjB,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createAnalyticsTools` — assemble the 6 wrapped AI SDK tools into
|
|
3
|
+
* a single map ready to hand to `streamText({ tools })` / `generateText`.
|
|
4
|
+
*
|
|
5
|
+
* Wires:
|
|
6
|
+
* - rate limiter (or `null` if `rateLimit: false`)
|
|
7
|
+
* - authorize hook (default `familyScopeAuthorize`, optionally with
|
|
8
|
+
* an injected `familyMembersProvider`)
|
|
9
|
+
* - audit writer (batched, optional)
|
|
10
|
+
* - output budget (default 4096 bytes / 100 rows)
|
|
11
|
+
*
|
|
12
|
+
* Per-request context threading: the AI SDK's `execute(input)` has no
|
|
13
|
+
* ctx arg. We hold a mutable `currentCtx` container and expose
|
|
14
|
+
* `setContext(ctx)`. Consumers (zivaone_app) call `setContext` before
|
|
15
|
+
* each LLM turn — matching how `@mongrov/ai`'s `AIProvider` threads
|
|
16
|
+
* per-request scopes. This design keeps rate-limiter buckets + audit
|
|
17
|
+
* writer state shared across requests rather than rebuilding the
|
|
18
|
+
* whole tool map per turn.
|
|
19
|
+
*/
|
|
20
|
+
import type { AnalyticsToolsConfig, AuditWriter, ToolContext } from './types';
|
|
21
|
+
import { makeTool } from './wrap';
|
|
22
|
+
import { type GetActivityTotalInput } from './impls/activity';
|
|
23
|
+
import { type DetectAnomalyInput } from './impls/anomaly';
|
|
24
|
+
import { type CompareTrendInput } from './impls/compare';
|
|
25
|
+
import { type GetHRVInput } from './impls/hrv';
|
|
26
|
+
import { type GetInsightsInput } from './impls/insights';
|
|
27
|
+
import { type GetSleepSummaryInput } from './impls/sleep';
|
|
28
|
+
export interface AnalyticsToolMap {
|
|
29
|
+
getHRV: ReturnType<typeof makeTool<GetHRVInput>>;
|
|
30
|
+
getSleepSummary: ReturnType<typeof makeTool<GetSleepSummaryInput>>;
|
|
31
|
+
getActivityTotal: ReturnType<typeof makeTool<GetActivityTotalInput>>;
|
|
32
|
+
compareTrend: ReturnType<typeof makeTool<CompareTrendInput>>;
|
|
33
|
+
detectAnomaly: ReturnType<typeof makeTool<DetectAnomalyInput>>;
|
|
34
|
+
getInsights: ReturnType<typeof makeTool<GetInsightsInput>>;
|
|
35
|
+
}
|
|
36
|
+
export interface AnalyticsToolsHandle {
|
|
37
|
+
tools: AnalyticsToolMap;
|
|
38
|
+
setContext: (ctx: ToolContext | null) => void;
|
|
39
|
+
auditWriter: AuditWriter;
|
|
40
|
+
close: () => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createAnalyticsTools(config: AnalyticsToolsConfig): AnalyticsToolsHandle;
|
|
43
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/tools/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EACV,oBAAoB,EACpB,WAAW,EAEX,WAAW,EACZ,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AACzE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAA;AAoBtB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAA;IAChD,eAAe,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAA;IAClE,gBAAgB,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,qBAAqB,CAAC,CAAC,CAAA;IACpE,YAAY,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAA;IAC5D,aAAa,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAA;IAC9D,WAAW,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAA;CAC3D;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,CAAA;IACvB,UAAU,EAAE,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,GAC3B,oBAAoB,CAiGtB"}
|