@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.
@@ -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
+ }