@camstack/addon-ai 0.1.1
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/assets/icon.svg +4 -0
- package/dist/addon.js +26487 -0
- package/dist/addon.mjs +26415 -0
- package/dist/ensure-llama-server-CwEkUUrD.js +0 -0
- package/dist/ensure-llama-server-Dmh3cZ41.mjs +0 -0
- package/dist/execute-plan-KbxnOqY1.mjs +41 -0
- package/dist/execute-plan-gwFcJjeV.js +51 -0
- package/dist/index.js +41 -0
- package/dist/index.mjs +5 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/usage-store-BVa_imkv.mjs +178 -0
- package/dist/usage-store-DK494_zK.js +200 -0
- package/package.json +74 -0
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
//#region src/http/execute-plan.ts
|
|
3
|
+
var execute_plan_exports = /* @__PURE__ */ __exportAll({ createFetchExecutor: () => createFetchExecutor });
|
|
4
|
+
var MAX_RESPONSE_BODY_CHARS = 4 * 1024 * 1024;
|
|
5
|
+
function createFetchExecutor(fetchImpl = fetch) {
|
|
6
|
+
return async (plan, timeoutMs) => {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
9
|
+
try {
|
|
10
|
+
const res = await fetchImpl(plan.url, {
|
|
11
|
+
method: plan.method ?? "POST",
|
|
12
|
+
headers: plan.headers,
|
|
13
|
+
body: plan.body === void 0 ? void 0 : JSON.stringify(plan.body),
|
|
14
|
+
signal: controller.signal
|
|
15
|
+
});
|
|
16
|
+
const bodyText = (await res.text()).slice(0, MAX_RESPONSE_BODY_CHARS);
|
|
17
|
+
const headers = {};
|
|
18
|
+
res.headers.forEach((value, key) => {
|
|
19
|
+
headers[key.toLowerCase()] = value;
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
kind: "response",
|
|
23
|
+
response: {
|
|
24
|
+
status: res.status,
|
|
25
|
+
headers,
|
|
26
|
+
bodyText
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err instanceof Error && err.name === "AbortError") return { kind: "timeout" };
|
|
31
|
+
return {
|
|
32
|
+
kind: "network",
|
|
33
|
+
message: err instanceof Error ? err.message : String(err)
|
|
34
|
+
};
|
|
35
|
+
} finally {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { execute_plan_exports as n, createFetchExecutor as t };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/http/execute-plan.ts
|
|
2
|
+
var execute_plan_exports = /* @__PURE__ */ require("./addon.js").__exportAll({ createFetchExecutor: () => createFetchExecutor });
|
|
3
|
+
var MAX_RESPONSE_BODY_CHARS = 4 * 1024 * 1024;
|
|
4
|
+
function createFetchExecutor(fetchImpl = fetch) {
|
|
5
|
+
return async (plan, timeoutMs) => {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetchImpl(plan.url, {
|
|
10
|
+
method: plan.method ?? "POST",
|
|
11
|
+
headers: plan.headers,
|
|
12
|
+
body: plan.body === void 0 ? void 0 : JSON.stringify(plan.body),
|
|
13
|
+
signal: controller.signal
|
|
14
|
+
});
|
|
15
|
+
const bodyText = (await res.text()).slice(0, MAX_RESPONSE_BODY_CHARS);
|
|
16
|
+
const headers = {};
|
|
17
|
+
res.headers.forEach((value, key) => {
|
|
18
|
+
headers[key.toLowerCase()] = value;
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
kind: "response",
|
|
22
|
+
response: {
|
|
23
|
+
status: res.status,
|
|
24
|
+
headers,
|
|
25
|
+
bodyText
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err instanceof Error && err.name === "AbortError") return { kind: "timeout" };
|
|
30
|
+
return {
|
|
31
|
+
kind: "network",
|
|
32
|
+
message: err instanceof Error ? err.message : String(err)
|
|
33
|
+
};
|
|
34
|
+
} finally {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
Object.defineProperty(exports, "createFetchExecutor", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
get: function() {
|
|
43
|
+
return createFetchExecutor;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(exports, "execute_plan_exports", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
get: function() {
|
|
49
|
+
return execute_plan_exports;
|
|
50
|
+
}
|
|
51
|
+
});
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
const require_addon = require("./addon.js");
|
|
6
|
+
const require_execute_plan = require("./execute-plan-gwFcJjeV.js");
|
|
7
|
+
const require_usage_store = require("./usage-store-DK494_zK.js");
|
|
8
|
+
const require_ensure_llama_server = require("./ensure-llama-server-CwEkUUrD.js");
|
|
9
|
+
exports.AI_ADDON_ID = require_addon.AI_ADDON_ID;
|
|
10
|
+
exports.AiAddon = require_addon.AiAddon;
|
|
11
|
+
exports.BUDGETS_COLLECTION = require_usage_store.BUDGETS_COLLECTION;
|
|
12
|
+
exports.DefaultsStore = require_addon.DefaultsStore;
|
|
13
|
+
exports.LLAMA_SERVER_VERSION = require_ensure_llama_server.LLAMA_SERVER_VERSION;
|
|
14
|
+
exports.LLM_MODEL_CATALOG = require_addon.LLM_MODEL_CATALOG;
|
|
15
|
+
exports.LlamaSupervisor = require_addon.LlamaSupervisor;
|
|
16
|
+
exports.PROFILES_COLLECTION = require_addon.PROFILES_COLLECTION;
|
|
17
|
+
exports.ProfileStore = require_addon.ProfileStore;
|
|
18
|
+
exports.REDACTED_MARKER = require_addon.REDACTED_MARKER;
|
|
19
|
+
exports.SEED_PROFILE_ID = require_addon.SEED_PROFILE_ID;
|
|
20
|
+
exports.USAGE_COLLECTION = require_usage_store.USAGE_COLLECTION;
|
|
21
|
+
exports.UsageStore = require_usage_store.UsageStore;
|
|
22
|
+
exports.anthropicAdapter = require_addon.anthropicAdapter;
|
|
23
|
+
exports.assembleAi = require_addon.assembleAi;
|
|
24
|
+
exports.catalogById = require_addon.catalogById;
|
|
25
|
+
exports.createApiSettingsStorePort = require_addon.createApiSettingsStorePort;
|
|
26
|
+
exports.createDefaultModelOps = require_addon.createDefaultModelOps;
|
|
27
|
+
exports.createFetchExecutor = require_execute_plan.createFetchExecutor;
|
|
28
|
+
exports.createLlmProvider = require_addon.createLlmProvider;
|
|
29
|
+
exports.createLlmRuntimeProvider = require_addon.createLlmRuntimeProvider;
|
|
30
|
+
exports.createMemorySettingsStorePort = require_addon.createMemorySettingsStorePort;
|
|
31
|
+
exports.createRuntimeClient = require_addon.createRuntimeClient;
|
|
32
|
+
exports.default = require_addon.AiAddon;
|
|
33
|
+
exports.ensureLlamaServer = require_ensure_llama_server.ensureLlamaServer;
|
|
34
|
+
exports.entryForRef = require_addon.entryForRef;
|
|
35
|
+
exports.googleAdapter = require_addon.googleAdapter;
|
|
36
|
+
exports.llamaServerAsset = require_ensure_llama_server.llamaServerAsset;
|
|
37
|
+
exports.mergeProfileSecrets = require_addon.mergeProfileSecrets;
|
|
38
|
+
exports.openAiAdapter = require_addon.openAiAdapter;
|
|
39
|
+
exports.openAiCompatibleAdapter = require_addon.openAiCompatibleAdapter;
|
|
40
|
+
exports.redactProfileForRead = require_addon.redactProfileForRead;
|
|
41
|
+
exports.resolveProfile = require_addon.resolveProfile;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AiAddon, C as googleAdapter, S as openAiAdapter, T as openAiCompatibleAdapter, _ as redactProfileForRead, a as createLlmRuntimeProvider, b as SEED_PROFILE_ID, c as LLM_MODEL_CATALOG, d as createRuntimeClient, f as AI_ADDON_ID, g as mergeProfileSecrets, h as REDACTED_MARKER, i as assembleAi, l as catalogById, m as resolveProfile, n as createMemorySettingsStorePort, p as createLlmProvider, r as LlamaSupervisor, s as createDefaultModelOps, t as createApiSettingsStorePort, u as entryForRef, v as PROFILES_COLLECTION, w as anthropicAdapter, x as DefaultsStore, y as ProfileStore } from "./addon.mjs";
|
|
2
|
+
import { t as createFetchExecutor } from "./execute-plan-KbxnOqY1.mjs";
|
|
3
|
+
import { n as USAGE_COLLECTION, r as UsageStore, t as BUDGETS_COLLECTION } from "./usage-store-BVa_imkv.mjs";
|
|
4
|
+
import { i as llamaServerAsset, n as ensureLlamaServer, t as LLAMA_SERVER_VERSION } from "./ensure-llama-server-Dmh3cZ41.mjs";
|
|
5
|
+
export { AI_ADDON_ID, AiAddon, AiAddon as default, BUDGETS_COLLECTION, DefaultsStore, LLAMA_SERVER_VERSION, LLM_MODEL_CATALOG, LlamaSupervisor, PROFILES_COLLECTION, ProfileStore, REDACTED_MARKER, SEED_PROFILE_ID, USAGE_COLLECTION, UsageStore, anthropicAdapter, assembleAi, catalogById, createApiSettingsStorePort, createDefaultModelOps, createFetchExecutor, createLlmProvider, createLlmRuntimeProvider, createMemorySettingsStorePort, createRuntimeClient, ensureLlamaServer, entryForRef, googleAdapter, llamaServerAsset, mergeProfileSecrets, openAiAdapter, openAiCompatibleAdapter, redactProfileForRead, resolveProfile };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { A as object, D as LlmProfileKindSchema, E as LlmErrorCodeSchema, O as boolean, j as string, k as number } from "./addon.mjs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
//#region src/usage/usage-store.ts
|
|
5
|
+
/**
|
|
6
|
+
* Usage store over declared settings-store collections (spec §6). The sqlite
|
|
7
|
+
* backend silently drops undeclared fields, so `init()` declares explicit
|
|
8
|
+
* columns for both `ai:usage` and `ai:budgets` (budget enforcement is v2 —
|
|
9
|
+
* schema only). Query-then-filter/group in JS is deliberate: LLM call volume is
|
|
10
|
+
* low and rows are pruned at 90d.
|
|
11
|
+
*/
|
|
12
|
+
var usage_store_exports = /* @__PURE__ */ __exportAll({
|
|
13
|
+
BUDGETS_COLLECTION: () => BUDGETS_COLLECTION,
|
|
14
|
+
USAGE_COLLECTION: () => USAGE_COLLECTION,
|
|
15
|
+
UsageStore: () => UsageStore
|
|
16
|
+
});
|
|
17
|
+
var USAGE_COLLECTION = "ai:usage";
|
|
18
|
+
var BUDGETS_COLLECTION = "ai:budgets";
|
|
19
|
+
var USAGE_COLUMNS = [
|
|
20
|
+
{
|
|
21
|
+
name: "ts",
|
|
22
|
+
type: "INTEGER",
|
|
23
|
+
notNull: true
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "consumer",
|
|
27
|
+
type: "TEXT",
|
|
28
|
+
notNull: true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "profileId",
|
|
32
|
+
type: "TEXT"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "kind",
|
|
36
|
+
type: "TEXT"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "model",
|
|
40
|
+
type: "TEXT"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "inputTokens",
|
|
44
|
+
type: "INTEGER"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "outputTokens",
|
|
48
|
+
type: "INTEGER"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "latencyMs",
|
|
52
|
+
type: "INTEGER"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "ok",
|
|
56
|
+
type: "BOOLEAN"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "errorCode",
|
|
60
|
+
type: "TEXT"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "imageCount",
|
|
64
|
+
type: "INTEGER"
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
var BUDGET_COLUMNS = [
|
|
68
|
+
{
|
|
69
|
+
name: "consumer",
|
|
70
|
+
type: "TEXT",
|
|
71
|
+
notNull: true
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "dailyTokenLimit",
|
|
75
|
+
type: "INTEGER"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "action",
|
|
79
|
+
type: "TEXT"
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
var UsageRowSchema = object({
|
|
83
|
+
ts: number(),
|
|
84
|
+
consumer: string(),
|
|
85
|
+
profileId: string().optional(),
|
|
86
|
+
kind: LlmProfileKindSchema.optional(),
|
|
87
|
+
model: string().optional(),
|
|
88
|
+
inputTokens: number(),
|
|
89
|
+
outputTokens: number(),
|
|
90
|
+
latencyMs: number(),
|
|
91
|
+
ok: boolean(),
|
|
92
|
+
errorCode: LlmErrorCodeSchema.optional(),
|
|
93
|
+
imageCount: number()
|
|
94
|
+
});
|
|
95
|
+
function dayKey(ts) {
|
|
96
|
+
return new Date(ts).toISOString().slice(0, 10);
|
|
97
|
+
}
|
|
98
|
+
var UsageStore = class {
|
|
99
|
+
store;
|
|
100
|
+
now;
|
|
101
|
+
constructor(store, now = Date.now) {
|
|
102
|
+
this.store = store;
|
|
103
|
+
this.now = now;
|
|
104
|
+
}
|
|
105
|
+
async init() {
|
|
106
|
+
await this.store.declareCollection(USAGE_COLLECTION, USAGE_COLUMNS);
|
|
107
|
+
await this.store.declareCollection(BUDGETS_COLLECTION, BUDGET_COLUMNS);
|
|
108
|
+
}
|
|
109
|
+
async record(row) {
|
|
110
|
+
await this.store.insert(USAGE_COLLECTION, {
|
|
111
|
+
id: randomUUID(),
|
|
112
|
+
data: { ...row }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async validRows() {
|
|
116
|
+
const rows = await this.store.query(USAGE_COLLECTION);
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const row of rows) {
|
|
119
|
+
const parsed = UsageRowSchema.safeParse(row.data);
|
|
120
|
+
if (parsed.success) out.push(parsed.data);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
async rollup(filter) {
|
|
125
|
+
const rows = (await this.validRows()).filter((r) => {
|
|
126
|
+
if (filter.since !== void 0 && r.ts < filter.since) return false;
|
|
127
|
+
if (filter.until !== void 0 && r.ts > filter.until) return false;
|
|
128
|
+
if (filter.consumer !== void 0 && r.consumer !== filter.consumer) return false;
|
|
129
|
+
if (filter.profileId !== void 0 && (r.profileId ?? "") !== filter.profileId) return false;
|
|
130
|
+
return true;
|
|
131
|
+
});
|
|
132
|
+
const groups = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const r of rows) {
|
|
134
|
+
const profileId = r.profileId ?? "";
|
|
135
|
+
const day = dayKey(r.ts);
|
|
136
|
+
const key = `${day}|${r.consumer}|${profileId}`;
|
|
137
|
+
const acc = groups.get(key) ?? {
|
|
138
|
+
day,
|
|
139
|
+
consumer: r.consumer,
|
|
140
|
+
profileId,
|
|
141
|
+
calls: 0,
|
|
142
|
+
okCalls: 0,
|
|
143
|
+
errorCalls: 0,
|
|
144
|
+
inputTokens: 0,
|
|
145
|
+
outputTokens: 0,
|
|
146
|
+
latencySum: 0
|
|
147
|
+
};
|
|
148
|
+
acc.calls += 1;
|
|
149
|
+
if (r.ok) acc.okCalls += 1;
|
|
150
|
+
else acc.errorCalls += 1;
|
|
151
|
+
acc.inputTokens += r.inputTokens;
|
|
152
|
+
acc.outputTokens += r.outputTokens;
|
|
153
|
+
acc.latencySum += r.latencyMs;
|
|
154
|
+
groups.set(key, acc);
|
|
155
|
+
}
|
|
156
|
+
return [...groups.values()].map((acc) => ({
|
|
157
|
+
day: acc.day,
|
|
158
|
+
consumer: acc.consumer,
|
|
159
|
+
profileId: acc.profileId,
|
|
160
|
+
calls: acc.calls,
|
|
161
|
+
okCalls: acc.okCalls,
|
|
162
|
+
errorCalls: acc.errorCalls,
|
|
163
|
+
inputTokens: acc.inputTokens,
|
|
164
|
+
outputTokens: acc.outputTokens,
|
|
165
|
+
avgLatencyMs: acc.calls > 0 ? acc.latencySum / acc.calls : 0
|
|
166
|
+
})).sort((a, b) => a.day < b.day ? -1 : a.day > b.day ? 1 : 0);
|
|
167
|
+
}
|
|
168
|
+
async prune(retentionDays = 90) {
|
|
169
|
+
const cutoff = this.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
170
|
+
const rows = await this.store.query(USAGE_COLLECTION);
|
|
171
|
+
for (const row of rows) {
|
|
172
|
+
const ts = row.data.ts;
|
|
173
|
+
if (typeof ts === "number" && ts < cutoff) await this.store.remove(USAGE_COLLECTION, row.id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
178
|
+
export { usage_store_exports as i, USAGE_COLLECTION as n, UsageStore as r, BUDGETS_COLLECTION as t };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const require_addon = require("./addon.js");
|
|
2
|
+
let node_crypto = require("node:crypto");
|
|
3
|
+
//#region src/usage/usage-store.ts
|
|
4
|
+
/**
|
|
5
|
+
* Usage store over declared settings-store collections (spec §6). The sqlite
|
|
6
|
+
* backend silently drops undeclared fields, so `init()` declares explicit
|
|
7
|
+
* columns for both `ai:usage` and `ai:budgets` (budget enforcement is v2 —
|
|
8
|
+
* schema only). Query-then-filter/group in JS is deliberate: LLM call volume is
|
|
9
|
+
* low and rows are pruned at 90d.
|
|
10
|
+
*/
|
|
11
|
+
var usage_store_exports = /* @__PURE__ */ require_addon.__exportAll({
|
|
12
|
+
BUDGETS_COLLECTION: () => BUDGETS_COLLECTION,
|
|
13
|
+
USAGE_COLLECTION: () => USAGE_COLLECTION,
|
|
14
|
+
UsageStore: () => UsageStore
|
|
15
|
+
});
|
|
16
|
+
var USAGE_COLLECTION = "ai:usage";
|
|
17
|
+
var BUDGETS_COLLECTION = "ai:budgets";
|
|
18
|
+
var USAGE_COLUMNS = [
|
|
19
|
+
{
|
|
20
|
+
name: "ts",
|
|
21
|
+
type: "INTEGER",
|
|
22
|
+
notNull: true
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "consumer",
|
|
26
|
+
type: "TEXT",
|
|
27
|
+
notNull: true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "profileId",
|
|
31
|
+
type: "TEXT"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "kind",
|
|
35
|
+
type: "TEXT"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "model",
|
|
39
|
+
type: "TEXT"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "inputTokens",
|
|
43
|
+
type: "INTEGER"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "outputTokens",
|
|
47
|
+
type: "INTEGER"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "latencyMs",
|
|
51
|
+
type: "INTEGER"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "ok",
|
|
55
|
+
type: "BOOLEAN"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "errorCode",
|
|
59
|
+
type: "TEXT"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "imageCount",
|
|
63
|
+
type: "INTEGER"
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
var BUDGET_COLUMNS = [
|
|
67
|
+
{
|
|
68
|
+
name: "consumer",
|
|
69
|
+
type: "TEXT",
|
|
70
|
+
notNull: true
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "dailyTokenLimit",
|
|
74
|
+
type: "INTEGER"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "action",
|
|
78
|
+
type: "TEXT"
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
var UsageRowSchema = require_addon.object({
|
|
82
|
+
ts: require_addon.number(),
|
|
83
|
+
consumer: require_addon.string(),
|
|
84
|
+
profileId: require_addon.string().optional(),
|
|
85
|
+
kind: require_addon.LlmProfileKindSchema.optional(),
|
|
86
|
+
model: require_addon.string().optional(),
|
|
87
|
+
inputTokens: require_addon.number(),
|
|
88
|
+
outputTokens: require_addon.number(),
|
|
89
|
+
latencyMs: require_addon.number(),
|
|
90
|
+
ok: require_addon.boolean(),
|
|
91
|
+
errorCode: require_addon.LlmErrorCodeSchema.optional(),
|
|
92
|
+
imageCount: require_addon.number()
|
|
93
|
+
});
|
|
94
|
+
function dayKey(ts) {
|
|
95
|
+
return new Date(ts).toISOString().slice(0, 10);
|
|
96
|
+
}
|
|
97
|
+
var UsageStore = class {
|
|
98
|
+
store;
|
|
99
|
+
now;
|
|
100
|
+
constructor(store, now = Date.now) {
|
|
101
|
+
this.store = store;
|
|
102
|
+
this.now = now;
|
|
103
|
+
}
|
|
104
|
+
async init() {
|
|
105
|
+
await this.store.declareCollection(USAGE_COLLECTION, USAGE_COLUMNS);
|
|
106
|
+
await this.store.declareCollection(BUDGETS_COLLECTION, BUDGET_COLUMNS);
|
|
107
|
+
}
|
|
108
|
+
async record(row) {
|
|
109
|
+
await this.store.insert(USAGE_COLLECTION, {
|
|
110
|
+
id: (0, node_crypto.randomUUID)(),
|
|
111
|
+
data: { ...row }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async validRows() {
|
|
115
|
+
const rows = await this.store.query(USAGE_COLLECTION);
|
|
116
|
+
const out = [];
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
const parsed = UsageRowSchema.safeParse(row.data);
|
|
119
|
+
if (parsed.success) out.push(parsed.data);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
async rollup(filter) {
|
|
124
|
+
const rows = (await this.validRows()).filter((r) => {
|
|
125
|
+
if (filter.since !== void 0 && r.ts < filter.since) return false;
|
|
126
|
+
if (filter.until !== void 0 && r.ts > filter.until) return false;
|
|
127
|
+
if (filter.consumer !== void 0 && r.consumer !== filter.consumer) return false;
|
|
128
|
+
if (filter.profileId !== void 0 && (r.profileId ?? "") !== filter.profileId) return false;
|
|
129
|
+
return true;
|
|
130
|
+
});
|
|
131
|
+
const groups = /* @__PURE__ */ new Map();
|
|
132
|
+
for (const r of rows) {
|
|
133
|
+
const profileId = r.profileId ?? "";
|
|
134
|
+
const day = dayKey(r.ts);
|
|
135
|
+
const key = `${day}|${r.consumer}|${profileId}`;
|
|
136
|
+
const acc = groups.get(key) ?? {
|
|
137
|
+
day,
|
|
138
|
+
consumer: r.consumer,
|
|
139
|
+
profileId,
|
|
140
|
+
calls: 0,
|
|
141
|
+
okCalls: 0,
|
|
142
|
+
errorCalls: 0,
|
|
143
|
+
inputTokens: 0,
|
|
144
|
+
outputTokens: 0,
|
|
145
|
+
latencySum: 0
|
|
146
|
+
};
|
|
147
|
+
acc.calls += 1;
|
|
148
|
+
if (r.ok) acc.okCalls += 1;
|
|
149
|
+
else acc.errorCalls += 1;
|
|
150
|
+
acc.inputTokens += r.inputTokens;
|
|
151
|
+
acc.outputTokens += r.outputTokens;
|
|
152
|
+
acc.latencySum += r.latencyMs;
|
|
153
|
+
groups.set(key, acc);
|
|
154
|
+
}
|
|
155
|
+
return [...groups.values()].map((acc) => ({
|
|
156
|
+
day: acc.day,
|
|
157
|
+
consumer: acc.consumer,
|
|
158
|
+
profileId: acc.profileId,
|
|
159
|
+
calls: acc.calls,
|
|
160
|
+
okCalls: acc.okCalls,
|
|
161
|
+
errorCalls: acc.errorCalls,
|
|
162
|
+
inputTokens: acc.inputTokens,
|
|
163
|
+
outputTokens: acc.outputTokens,
|
|
164
|
+
avgLatencyMs: acc.calls > 0 ? acc.latencySum / acc.calls : 0
|
|
165
|
+
})).sort((a, b) => a.day < b.day ? -1 : a.day > b.day ? 1 : 0);
|
|
166
|
+
}
|
|
167
|
+
async prune(retentionDays = 90) {
|
|
168
|
+
const cutoff = this.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
169
|
+
const rows = await this.store.query(USAGE_COLLECTION);
|
|
170
|
+
for (const row of rows) {
|
|
171
|
+
const ts = row.data.ts;
|
|
172
|
+
if (typeof ts === "number" && ts < cutoff) await this.store.remove(USAGE_COLLECTION, row.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
Object.defineProperty(exports, "BUDGETS_COLLECTION", {
|
|
178
|
+
enumerable: true,
|
|
179
|
+
get: function() {
|
|
180
|
+
return BUDGETS_COLLECTION;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
Object.defineProperty(exports, "USAGE_COLLECTION", {
|
|
184
|
+
enumerable: true,
|
|
185
|
+
get: function() {
|
|
186
|
+
return USAGE_COLLECTION;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
Object.defineProperty(exports, "UsageStore", {
|
|
190
|
+
enumerable: true,
|
|
191
|
+
get: function() {
|
|
192
|
+
return UsageStore;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
Object.defineProperty(exports, "usage_store_exports", {
|
|
196
|
+
enumerable: true,
|
|
197
|
+
get: function() {
|
|
198
|
+
return usage_store_exports;
|
|
199
|
+
}
|
|
200
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@camstack/addon-ai",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"camstack",
|
|
7
|
+
"addon",
|
|
8
|
+
"camstack-addon",
|
|
9
|
+
"llm",
|
|
10
|
+
"ai"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"require": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"camstack": {
|
|
25
|
+
"displayName": "AI",
|
|
26
|
+
"addons": [
|
|
27
|
+
{
|
|
28
|
+
"id": "ai",
|
|
29
|
+
"category": "analytics",
|
|
30
|
+
"name": "AI",
|
|
31
|
+
"description": "LLM profiles (cloud, LAN, managed local models) + per-node managed llama.cpp runtimes",
|
|
32
|
+
"version": "0.1.0",
|
|
33
|
+
"entry": "./dist/index.js",
|
|
34
|
+
"execution": {
|
|
35
|
+
"placement": "any-node"
|
|
36
|
+
},
|
|
37
|
+
"capabilities": [
|
|
38
|
+
{
|
|
39
|
+
"name": "llm"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "llm-runtime"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"icon": "assets/icon.svg",
|
|
46
|
+
"color": "#0ea5e9"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"assets"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "vite build",
|
|
56
|
+
"dev": "vite build --watch",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"publish": "npm publish --access public"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"@camstack/types": "*"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@camstack/system": "*",
|
|
66
|
+
"zod": "^4.3.6"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@camstack/types": "*",
|
|
70
|
+
"typescript": "~6.0.3",
|
|
71
|
+
"vite": "^8.0.11",
|
|
72
|
+
"vitest": "^3.0.0"
|
|
73
|
+
}
|
|
74
|
+
}
|