@elizaos/app-core 2.0.0-beta.1 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/package.json +2 -2
  2. package/platforms/electrobun/native/macos/window-effects.mm +103 -0
  3. package/platforms/electrobun/package.json +9 -0
  4. package/platforms/electrobun/src/__stubs__/bun-ffi.ts +16 -0
  5. package/platforms/electrobun/src/libMacWindowEffects.dylib +0 -0
  6. package/platforms/electrobun/src/native/agent.ts +74 -3
  7. package/platforms/electrobun/src/native/desktop.ts +39 -6
  8. package/platforms/electrobun/src/native/mac-window-effects.ts +61 -1
  9. package/platforms/electrobun/src/native/permissions-shared.ts +3 -2
  10. package/platforms/electrobun/src/native/permissions.ts +11 -6
  11. package/platforms/electrobun/src/rpc-handlers.ts +7 -0
  12. package/platforms/electrobun/src/rpc-schema.ts +39 -4
  13. package/platforms/electrobun/src/runtime-permissions.ts +7 -1
  14. package/runtime/ensure-local-inference-handler.d.ts +1 -0
  15. package/runtime/ensure-local-inference-handler.d.ts.map +1 -1
  16. package/runtime/ensure-local-inference-handler.js +9 -0
  17. package/runtime/mode/remote-forwarder.d.ts.map +1 -1
  18. package/runtime/mode/remote-forwarder.js +1 -1
  19. package/runtime/mode/runtime-mode.d.ts +20 -2
  20. package/runtime/mode/runtime-mode.d.ts.map +1 -1
  21. package/runtime/mode/runtime-mode.js +69 -1
  22. package/scripts/aosp/stage-default-models.mjs +2 -2
  23. package/scripts/build-llama-cpp-dflash.mjs +75 -40
  24. package/scripts/kernel-patches/metal-kernels.mjs +357 -337
  25. package/scripts/lib/read-app-identity.mjs +5 -1
  26. package/services/local-inference/catalog.d.ts +2 -1
  27. package/services/local-inference/catalog.d.ts.map +1 -1
  28. package/services/local-inference/catalog.js +131 -12
  29. package/services/local-inference/downloader.d.ts +2 -0
  30. package/services/local-inference/downloader.d.ts.map +1 -1
  31. package/services/local-inference/downloader.js +300 -1
  32. package/services/local-inference/manifest/validator.d.ts.map +1 -1
  33. package/services/local-inference/manifest/validator.js +48 -0
  34. package/services/local-inference/providers.d.ts +1 -1
  35. package/services/local-inference/providers.js +6 -6
  36. package/services/local-inference/registry.d.ts.map +1 -1
  37. package/services/local-inference/registry.js +10 -1
  38. package/services/local-inference/types.d.ts +6 -0
  39. package/services/local-inference/types.d.ts.map +1 -1
  40. package/test/helpers/real-runtime.ts +21 -20
  41. package/platforms/electrobun/src/native/permissions-darwin.ts +0 -342
  42. package/platforms/electrobun/src/native/permissions-linux.ts +0 -34
  43. package/platforms/electrobun/src/native/permissions-win32.ts +0 -56
@@ -6,7 +6,8 @@ import path from "node:path";
6
6
  * via regex (no TS evaluation, so callers stay bun-import-free).
7
7
  *
8
8
  * Used by desktop and mobile build scripts to forward identity into
9
- * downstream env vars (`ELIZA_APP_NAME`, `ELIZA_APP_ID`, `ELIZA_URL_SCHEME`).
9
+ * downstream env vars (`ELIZA_APP_NAME`, `ELIZA_APP_ID`, `ELIZA_URL_SCHEME`,
10
+ * `ELIZA_NAMESPACE`).
10
11
  */
11
12
  export function readAppIdentity(appDir) {
12
13
  const cfgPath = path.join(appDir, "app.config.ts");
@@ -23,6 +24,7 @@ export function readAppIdentity(appDir) {
23
24
  /desktop\s*:\s*\{[\s\S]*?urlScheme\s*:\s*["']([^"']+)["']/,
24
25
  )?.[1];
25
26
  const topLevelUrlScheme = src.match(/urlScheme:\s*["']([^"']+)["']/)?.[1];
27
+ const namespace = src.match(/namespace:\s*["']([^"']+)["']/)?.[1];
26
28
  if (!appId || !appName) {
27
29
  throw new Error(
28
30
  `Could not parse appId/appName from ${cfgPath} (regex failed)`,
@@ -32,6 +34,7 @@ export function readAppIdentity(appDir) {
32
34
  appId: desktopBundleId ?? appId,
33
35
  appName,
34
36
  urlScheme: desktopUrlScheme ?? topLevelUrlScheme ?? appId,
37
+ namespace: namespace ?? "eliza",
35
38
  };
36
39
  }
37
40
 
@@ -46,5 +49,6 @@ export function appIdentityEnv(appDir, existing = process.env) {
46
49
  ELIZA_APP_NAME: existing.ELIZA_APP_NAME?.trim() || identity.appName,
47
50
  ELIZA_APP_ID: existing.ELIZA_APP_ID?.trim() || identity.appId,
48
51
  ELIZA_URL_SCHEME: existing.ELIZA_URL_SCHEME?.trim() || identity.urlScheme,
52
+ ELIZA_NAMESPACE: existing.ELIZA_NAMESPACE?.trim() || identity.namespace,
49
53
  };
50
54
  }
@@ -5,7 +5,7 @@
5
5
  * default per device tier (`lite-0_6b`, `mobile-1_7b`, `desktop-9b`,
6
6
  * `pro-27b`, `server-h200`). The recommendation engine picks one of
7
7
  * these tiers based on hardware. See
8
- * `/Users/shawwalters/eliza-workspace/milady/packages/inference/AGENTS.md`
8
+ * `/Users/shawwalters/eliza-workspace/milady/eliza/packages/inference/AGENTS.md`
9
9
  * §2 for the binding tier matrix.
10
10
  *
11
11
  * HF-search results from outside `elizalabs/eliza-1-*` MUST never be
@@ -52,5 +52,6 @@ export declare function findCatalogModel(id: string): CatalogModel | undefined;
52
52
  * downloader e2e test suite can redirect all downloads without touching
53
53
  * the catalog.
54
54
  */
55
+ export declare function buildHuggingFaceResolveUrlForPath(model: CatalogModel, filePath: string): string;
55
56
  export declare function buildHuggingFaceResolveUrl(model: CatalogModel): string;
56
57
  //# sourceMappingURL=catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../../../../src/services/local-inference/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,UAAU,EAAE,CAAC;AAEnD,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,YAAY,CAExD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,YAAoC,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,MAAM,CAE1D,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAEvD,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,YAAY,EA2FvC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAWtE"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../../../../src/services/local-inference/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAsB,MAAM,SAAS,CAAC;AAEhE;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,UAAU,EAAE,CAAC;AAEnD,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,YAAY,CAExD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,YAAoC,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,MAAM,CAE1D,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAEvD,CAAC;AA4EF,eAAO,MAAM,aAAa,EAAE,YAAY,EAsJvC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,GACf,MAAM,CAWR;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAEtE"}
@@ -5,7 +5,7 @@
5
5
  * default per device tier (`lite-0_6b`, `mobile-1_7b`, `desktop-9b`,
6
6
  * `pro-27b`, `server-h200`). The recommendation engine picks one of
7
7
  * these tiers based on hardware. See
8
- * `/Users/shawwalters/eliza-workspace/milady/packages/inference/AGENTS.md`
8
+ * `/Users/shawwalters/eliza-workspace/milady/eliza/packages/inference/AGENTS.md`
9
9
  * §2 for the binding tier matrix.
10
10
  *
11
11
  * HF-search results from outside `elizalabs/eliza-1-*` MUST never be
@@ -38,6 +38,62 @@ export function isDefaultEligibleId(id) {
38
38
  }
39
39
  /** Compatibility export for callers that need the Eliza-1 model id set. */
40
40
  export const ELIZA_1_PLACEHOLDER_IDS = new Set(ELIZA_1_TIER_IDS);
41
+ const BASE_REQUIRED_KERNELS = [
42
+ "dflash",
43
+ "turbo3",
44
+ "turbo4",
45
+ "qjl_full",
46
+ "polarquant",
47
+ ];
48
+ function requiredKernelsForContext(contextLength) {
49
+ return contextLength > 65536
50
+ ? [...BASE_REQUIRED_KERNELS, "turbo3_tcq"]
51
+ : [...BASE_REQUIRED_KERNELS];
52
+ }
53
+ function drafterId(id) {
54
+ return `${id}-drafter`;
55
+ }
56
+ function runtimeFor(id, contextLength) {
57
+ return {
58
+ preferredBackend: "llama-server",
59
+ optimizations: {
60
+ parallel: contextLength >= 131072 ? 8 : 4,
61
+ flashAttention: true,
62
+ mlock: contextLength >= 131072,
63
+ requiresKernel: requiredKernelsForContext(contextLength),
64
+ },
65
+ dflash: {
66
+ drafterModelId: drafterId(id),
67
+ specType: "dflash",
68
+ contextSize: contextLength,
69
+ draftContextSize: Math.min(contextLength, 65536),
70
+ draftMin: 2,
71
+ draftMax: contextLength >= 131072 ? 8 : 6,
72
+ gpuLayers: "auto",
73
+ draftGpuLayers: "auto",
74
+ disableThinking: true,
75
+ },
76
+ };
77
+ }
78
+ function drafterCompanion(args) {
79
+ return {
80
+ id: drafterId(args.id),
81
+ displayName: `${args.displayName} drafter`,
82
+ hfRepo: `elizalabs/${args.id}`,
83
+ ggufFile: args.ggufFile,
84
+ params: args.params,
85
+ quant: "Eliza-1 drafter companion",
86
+ sizeGb: args.sizeGb,
87
+ minRamGb: args.minRamGb,
88
+ category: "drafter",
89
+ bucket: args.bucket,
90
+ hiddenFromCatalog: true,
91
+ runtimeRole: "dflash-drafter",
92
+ companionForModelId: args.id,
93
+ tokenizerFamily: "eliza1",
94
+ blurb: `${args.displayName} drafter companion.`,
95
+ };
96
+ }
41
97
  export const MODEL_CATALOG = [
42
98
  // ─── Eliza-1 lite (low-RAM phones, CPU fallback) ────────────────────
43
99
  {
@@ -45,80 +101,140 @@ export const MODEL_CATALOG = [
45
101
  displayName: "Eliza-1 lite",
46
102
  hfRepo: "elizalabs/eliza-1-lite-0_6b",
47
103
  ggufFile: "text/eliza-1-lite-0_6b-32k.gguf",
104
+ bundleManifestFile: "eliza-1.manifest.json",
48
105
  params: "1B",
49
- quant: "TurboQuant Q3 + Polar Q4 KV",
106
+ quant: "Eliza-1 optimized local runtime",
50
107
  sizeGb: 0.5,
51
108
  minRamGb: 2,
52
109
  category: "chat",
53
110
  bucket: "small",
54
111
  contextLength: 32768,
55
112
  tokenizerFamily: "eliza1",
56
- blurb: "Eliza-1 lite — fits low-RAM phones and CPU-only fallback. Fused text + voice bundle with TurboQuant Q3 + Polar KV.",
113
+ companionModelIds: ["eliza-1-lite-0_6b-drafter"],
114
+ runtime: runtimeFor("eliza-1-lite-0_6b", 32768),
115
+ blurb: "Eliza-1 lite — fits low-RAM phones and CPU-only fallback with the optimized local runtime.",
57
116
  },
117
+ drafterCompanion({
118
+ id: "eliza-1-lite-0_6b",
119
+ displayName: "Eliza-1 lite",
120
+ ggufFile: "dflash/drafter-lite-0_6b.gguf",
121
+ params: "1B",
122
+ sizeGb: 0.25,
123
+ minRamGb: 2,
124
+ bucket: "small",
125
+ }),
58
126
  // ─── Eliza-1 mobile (modern phones) ─────────────────────────────────
59
127
  {
60
128
  id: "eliza-1-mobile-1_7b",
61
129
  displayName: "Eliza-1 mobile",
62
130
  hfRepo: "elizalabs/eliza-1-mobile-1_7b",
63
131
  ggufFile: "text/eliza-1-mobile-1_7b-32k.gguf",
132
+ bundleManifestFile: "eliza-1.manifest.json",
64
133
  params: "1.7B",
65
- quant: "TurboQuant Q3/Q4 + QJL K-cache",
134
+ quant: "Eliza-1 optimized local runtime",
66
135
  sizeGb: 1.2,
67
136
  minRamGb: 4,
68
137
  category: "chat",
69
138
  bucket: "small",
70
139
  contextLength: 32768,
71
140
  tokenizerFamily: "eliza1",
72
- blurb: "Eliza-1 mobile — modern phone default. Fused text + voice with TurboQuant Q3/Q4 and QJL K-cache.",
141
+ companionModelIds: ["eliza-1-mobile-1_7b-drafter"],
142
+ runtime: runtimeFor("eliza-1-mobile-1_7b", 32768),
143
+ blurb: "Eliza-1 mobile — modern phone default with text and voice prepared for the optimized local runtime.",
73
144
  },
145
+ drafterCompanion({
146
+ id: "eliza-1-mobile-1_7b",
147
+ displayName: "Eliza-1 mobile",
148
+ ggufFile: "dflash/drafter-mobile-1_7b.gguf",
149
+ params: "1.7B",
150
+ sizeGb: 0.35,
151
+ minRamGb: 4,
152
+ bucket: "small",
153
+ }),
74
154
  // ─── Eliza-1 desktop (laptops, 24GB phones, 48GB Mac) ───────────────
75
155
  {
76
156
  id: "eliza-1-desktop-9b",
77
157
  displayName: "Eliza-1 desktop",
78
158
  hfRepo: "elizalabs/eliza-1-desktop-9b",
79
159
  ggufFile: "text/eliza-1-desktop-9b-64k.gguf",
160
+ bundleManifestFile: "eliza-1.manifest.json",
80
161
  params: "9B",
81
- quant: "TurboQuant Q4 + QJL + Polar",
162
+ quant: "Eliza-1 optimized local runtime",
82
163
  sizeGb: 5.4,
83
164
  minRamGb: 12,
84
165
  category: "chat",
85
166
  bucket: "mid",
86
167
  contextLength: 65536,
87
168
  tokenizerFamily: "eliza1",
88
- blurb: "Eliza-1 desktop — laptop / 24 GB phone / 48 GB Mac default. Fused text + voice + vision with TurboQuant Q4, QJL, PolarQuant.",
169
+ companionModelIds: ["eliza-1-desktop-9b-drafter"],
170
+ runtime: runtimeFor("eliza-1-desktop-9b", 65536),
171
+ blurb: "Eliza-1 desktop — laptop / 24 GB phone / 48 GB Mac default with text, voice, and vision in the optimized local runtime.",
89
172
  },
173
+ drafterCompanion({
174
+ id: "eliza-1-desktop-9b",
175
+ displayName: "Eliza-1 desktop",
176
+ ggufFile: "dflash/drafter-desktop-9b.gguf",
177
+ params: "9B",
178
+ sizeGb: 0.8,
179
+ minRamGb: 12,
180
+ bucket: "mid",
181
+ }),
90
182
  // ─── Eliza-1 pro (96GB+ Mac, high-VRAM desktop) ─────────────────────
91
183
  {
92
184
  id: "eliza-1-pro-27b",
93
185
  displayName: "Eliza-1 pro",
94
186
  hfRepo: "elizalabs/eliza-1-pro-27b",
95
187
  ggufFile: "text/eliza-1-pro-27b-128k.gguf",
188
+ bundleManifestFile: "eliza-1.manifest.json",
96
189
  params: "27B",
97
- quant: "TurboQuant Q4 + QJL + Polar",
190
+ quant: "Eliza-1 optimized local runtime",
98
191
  sizeGb: 16.8,
99
192
  minRamGb: 32,
100
193
  category: "chat",
101
194
  bucket: "large",
102
195
  contextLength: 131072,
103
196
  tokenizerFamily: "eliza1",
197
+ companionModelIds: ["eliza-1-pro-27b-drafter"],
198
+ runtime: runtimeFor("eliza-1-pro-27b", 131072),
104
199
  blurb: "Eliza-1 pro — 96 GB+ Mac and high-VRAM desktop default. Fused text + voice + vision; longest-context Eliza-1 tier on workstation hardware.",
105
200
  },
201
+ drafterCompanion({
202
+ id: "eliza-1-pro-27b",
203
+ displayName: "Eliza-1 pro",
204
+ ggufFile: "dflash/drafter-pro-27b.gguf",
205
+ params: "9B",
206
+ sizeGb: 1.2,
207
+ minRamGb: 32,
208
+ bucket: "large",
209
+ }),
106
210
  // ─── Eliza-1 server (workstation / server) ──────────────────────────
107
211
  {
108
212
  id: "eliza-1-server-h200",
109
213
  displayName: "Eliza-1 server",
110
214
  hfRepo: "elizalabs/eliza-1-server-h200",
111
215
  ggufFile: "text/eliza-1-server-h200-256k.gguf",
216
+ bundleManifestFile: "eliza-1.manifest.json",
112
217
  params: "27B",
113
- quant: "CUDA TurboQuant + QJL + Polar",
218
+ quant: "Eliza-1 optimized local runtime",
114
219
  sizeGb: 16.8,
115
220
  minRamGb: 96,
116
221
  category: "chat",
117
222
  bucket: "large",
118
223
  contextLength: 262144,
119
224
  tokenizerFamily: "eliza1",
120
- blurb: "Eliza-1 server — H200-class workstation / server. CUDA TurboQuant + QJL + Polar with the largest context window in the line.",
225
+ companionModelIds: ["eliza-1-server-h200-drafter"],
226
+ runtime: runtimeFor("eliza-1-server-h200", 262144),
227
+ blurb: "Eliza-1 server — H200-class workstation / server tier with the largest context window in the line.",
121
228
  },
229
+ drafterCompanion({
230
+ id: "eliza-1-server-h200",
231
+ displayName: "Eliza-1 server",
232
+ ggufFile: "dflash/drafter-server-h200.gguf",
233
+ params: "9B",
234
+ sizeGb: 1.2,
235
+ minRamGb: 96,
236
+ bucket: "large",
237
+ }),
122
238
  ];
123
239
  export function findCatalogModel(id) {
124
240
  return MODEL_CATALOG.find((m) => m.id === id);
@@ -130,14 +246,17 @@ export function findCatalogModel(id) {
130
246
  * downloader e2e test suite can redirect all downloads without touching
131
247
  * the catalog.
132
248
  */
133
- export function buildHuggingFaceResolveUrl(model) {
249
+ export function buildHuggingFaceResolveUrlForPath(model, filePath) {
134
250
  const base = process.env.ELIZA_HF_BASE_URL?.trim().replace(/\/+$/, "") ||
135
251
  "https://huggingface.co";
136
252
  // Encode each path segment separately so nested bundle layouts like
137
253
  // `text/eliza-1-mobile-1_7b-32k.gguf` keep their slashes.
138
- const encodedPath = model.ggufFile
254
+ const encodedPath = filePath
139
255
  .split("/")
140
256
  .map((segment) => encodeURIComponent(segment))
141
257
  .join("/");
142
258
  return `${base}/${model.hfRepo}/resolve/main/${encodedPath}?download=true`;
143
259
  }
260
+ export function buildHuggingFaceResolveUrl(model) {
261
+ return buildHuggingFaceResolveUrlForPath(model, model.ggufFile);
262
+ }
@@ -40,6 +40,8 @@ export declare class Downloader {
40
40
  private clearTerminalDownload;
41
41
  private throttleEmit;
42
42
  private runJob;
43
+ private runBundleJob;
44
+ private downloadRemotePath;
43
45
  private loadHttpClient;
44
46
  }
45
47
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"downloader.d.ts","sourceRoot":"","sources":["../../../../../../src/services/local-inference/downloader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAgBH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EAGZ,MAAM,SAAS,CAAC;AAUjB,KAAK,gBAAgB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAwDvD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;;IAMtD,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAOjD,QAAQ,IAAI,WAAW,EAAE;IAUzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAQlC;;;;OAIG;IACG,KAAK,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IA2DvE,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAchC,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,wBAAwB;IAkBhC,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,YAAY;YAQN,MAAM;YAwJN,cAAc;CAkC7B"}
1
+ {"version":3,"file":"downloader.d.ts","sourceRoot":"","sources":["../../../../../../src/services/local-inference/downloader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAoBH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EAGZ,MAAM,SAAS,CAAC;AAUjB,KAAK,gBAAgB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAsPvD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;;IAMtD,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAOjD,QAAQ,IAAI,WAAW,EAAE;IAUzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAQlC;;;;OAIG;IACG,KAAK,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;IA2DvE,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAchC,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,wBAAwB;IAkBhC,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,YAAY;YAQN,MAAM;YAgKN,YAAY;YAgJZ,kBAAkB;YAiHlB,cAAc;CAkC7B"}
@@ -21,7 +21,7 @@ import path from "node:path";
21
21
  import { Readable } from "node:stream";
22
22
  import { pipeline } from "node:stream/promises";
23
23
  import { ensureDefaultAssignment } from "./assignments";
24
- import { buildHuggingFaceResolveUrl, findCatalogModel } from "./catalog";
24
+ import { buildHuggingFaceResolveUrl, buildHuggingFaceResolveUrlForPath, findCatalogModel, } from "./catalog";
25
25
  import { downloadsStagingDir, elizaModelsDir, localInferenceRoot, } from "./paths";
26
26
  import { upsertElizaModel } from "./registry";
27
27
  import { hashFile } from "./verify";
@@ -53,6 +53,126 @@ function finalFilename(model) {
53
53
  const safe = model.id.replace(/[^a-zA-Z0-9._-]/g, "_");
54
54
  return `${safe}.gguf`;
55
55
  }
56
+ function bundleDirname(modelId) {
57
+ const safe = modelId.replace(/[^a-zA-Z0-9._-]/g, "_");
58
+ return `${safe}.bundle`;
59
+ }
60
+ function bundleStagingFilename(modelId, filePath) {
61
+ const safePath = filePath.replace(/[^a-zA-Z0-9._-]/g, "_");
62
+ return stagingFilename(`${modelId}__${safePath}`);
63
+ }
64
+ function bundleTargetPath(root, filePath) {
65
+ if (!filePath ||
66
+ path.isAbsolute(filePath) ||
67
+ /^[a-zA-Z]:[\\/]/.test(filePath)) {
68
+ throw new Error(`Invalid bundle file path: ${filePath}`);
69
+ }
70
+ const resolvedRoot = path.resolve(root);
71
+ const target = path.resolve(resolvedRoot, filePath);
72
+ if (target !== resolvedRoot &&
73
+ !target.startsWith(`${resolvedRoot}${path.sep}`)) {
74
+ throw new Error(`Bundle file escapes install root: ${filePath}`);
75
+ }
76
+ return target;
77
+ }
78
+ function parseBundleFileEntry(value, kind) {
79
+ if (!value || typeof value !== "object") {
80
+ throw new Error(`Invalid Eliza-1 manifest file entry in files.${kind}`);
81
+ }
82
+ const entry = value;
83
+ if (typeof entry.path !== "string" || entry.path.length === 0) {
84
+ throw new Error(`Invalid Eliza-1 manifest file path in files.${kind}`);
85
+ }
86
+ if (typeof entry.sha256 !== "string" ||
87
+ !/^[a-f0-9]{64}$/.test(entry.sha256)) {
88
+ throw new Error(`Invalid Eliza-1 manifest sha256 for ${entry.path}`);
89
+ }
90
+ const parsed = {
91
+ path: entry.path,
92
+ sha256: entry.sha256,
93
+ };
94
+ if (typeof entry.ctx === "number")
95
+ parsed.ctx = entry.ctx;
96
+ return parsed;
97
+ }
98
+ function parseBundleManifestOrThrow(input, catalogEntry) {
99
+ if (!input || typeof input !== "object") {
100
+ throw new Error("Invalid Eliza-1 manifest: expected object");
101
+ }
102
+ const raw = input;
103
+ if (raw.id !== catalogEntry.id) {
104
+ throw new Error(`Invalid Eliza-1 manifest: id ${String(raw.id)} does not match ${catalogEntry.id}`);
105
+ }
106
+ if (typeof raw.version !== "string" || raw.version.length === 0) {
107
+ throw new Error("Invalid Eliza-1 manifest: missing version");
108
+ }
109
+ if (raw.defaultEligible !== true) {
110
+ throw new Error("Invalid Eliza-1 manifest: defaultEligible must be true");
111
+ }
112
+ if (!raw.files || typeof raw.files !== "object") {
113
+ throw new Error("Invalid Eliza-1 manifest: missing files");
114
+ }
115
+ const filesRaw = raw.files;
116
+ const files = {};
117
+ for (const kind of [
118
+ "text",
119
+ "voice",
120
+ "asr",
121
+ "vision",
122
+ "dflash",
123
+ "cache",
124
+ "embedding",
125
+ "vad",
126
+ "wakeword",
127
+ ]) {
128
+ const value = filesRaw[kind];
129
+ if (value === undefined &&
130
+ (kind === "embedding" || kind === "vad" || kind === "wakeword")) {
131
+ files[kind] = [];
132
+ continue;
133
+ }
134
+ if (!Array.isArray(value)) {
135
+ throw new Error(`Invalid Eliza-1 manifest: files.${kind} must be an array`);
136
+ }
137
+ files[kind] = value.map((entry) => parseBundleFileEntry(entry, kind));
138
+ }
139
+ for (const kind of ["text", "voice", "dflash", "cache"]) {
140
+ if (files[kind].length === 0) {
141
+ throw new Error(`Invalid Eliza-1 manifest: files.${kind} must be non-empty`);
142
+ }
143
+ }
144
+ if (!files.text.some((entry) => entry.path === catalogEntry.ggufFile)) {
145
+ throw new Error(`Invalid Eliza-1 manifest: primary text file ${catalogEntry.ggufFile} is missing`);
146
+ }
147
+ return {
148
+ id: catalogEntry.id,
149
+ version: raw.version,
150
+ files,
151
+ };
152
+ }
153
+ function collectBundleFiles(manifest) {
154
+ const seen = new Map();
155
+ for (const kind of [
156
+ "text",
157
+ "voice",
158
+ "asr",
159
+ "vision",
160
+ "dflash",
161
+ "cache",
162
+ "embedding",
163
+ "vad",
164
+ "wakeword",
165
+ ]) {
166
+ for (const entry of manifest.files[kind]) {
167
+ const current = seen.get(entry.path);
168
+ if (current && current.entry.sha256 !== entry.sha256) {
169
+ throw new Error(`Conflicting sha256 entries for bundle file ${entry.path}`);
170
+ }
171
+ seen.set(entry.path, { kind, entry });
172
+ }
173
+ }
174
+ return [...seen.values()];
175
+ }
56
176
  async function ensureDirs() {
57
177
  await fsp.mkdir(downloadsStagingDir(), { recursive: true });
58
178
  await fsp.mkdir(elizaModelsDir(), { recursive: true });
@@ -236,6 +356,11 @@ export class Downloader {
236
356
  async runJob(catalogEntry, record) {
237
357
  try {
238
358
  this.updateState(record, "downloading");
359
+ if (catalogEntry.bundleManifestFile &&
360
+ catalogEntry.runtimeRole !== "dflash-drafter") {
361
+ await this.runBundleJob(catalogEntry, record);
362
+ return;
363
+ }
239
364
  const url = buildHuggingFaceResolveUrl(catalogEntry);
240
365
  const httpClient = await this.loadHttpClient();
241
366
  const startByte = record.job.received;
@@ -365,6 +490,180 @@ export class Downloader {
365
490
  this.active.delete(record.job.modelId);
366
491
  }
367
492
  }
493
+ async runBundleJob(catalogEntry, record) {
494
+ if (!catalogEntry.bundleManifestFile) {
495
+ throw new Error(`[local-inference] ${catalogEntry.id} has no bundle manifest`);
496
+ }
497
+ const bundleRoot = path.join(elizaModelsDir(), bundleDirname(catalogEntry.id));
498
+ await fsp.mkdir(bundleRoot, { recursive: true });
499
+ const manifestPath = bundleTargetPath(bundleRoot, catalogEntry.bundleManifestFile);
500
+ const manifestDownloaded = await this.downloadRemotePath(catalogEntry, catalogEntry.bundleManifestFile, path.join(downloadsStagingDir(), bundleStagingFilename(catalogEntry.id, catalogEntry.bundleManifestFile)), manifestPath, record, 0);
501
+ const manifest = parseBundleManifestOrThrow(JSON.parse(await fsp.readFile(manifestPath, "utf8")), catalogEntry);
502
+ let completedBytes = manifestDownloaded.sizeBytes;
503
+ const downloaded = new Map();
504
+ for (const { entry } of collectBundleFiles(manifest)) {
505
+ const finalPath = bundleTargetPath(bundleRoot, entry.path);
506
+ const result = await this.downloadRemotePath(catalogEntry, entry.path, path.join(downloadsStagingDir(), bundleStagingFilename(catalogEntry.id, entry.path)), finalPath, record, completedBytes, entry.sha256);
507
+ downloaded.set(entry.path, result);
508
+ completedBytes += result.sizeBytes;
509
+ record.job.received = completedBytes;
510
+ record.job.total = Math.max(record.job.total, completedBytes);
511
+ this.throttleEmit(record);
512
+ }
513
+ const textEntry = manifest.files.text.find((entry) => entry.path === catalogEntry.ggufFile);
514
+ if (!textEntry) {
515
+ throw new Error(`[local-inference] Bundle missing primary text file ${catalogEntry.ggufFile}`);
516
+ }
517
+ const textFile = downloaded.get(textEntry.path);
518
+ if (!textFile) {
519
+ throw new Error(`[local-inference] Bundle did not install text file ${textEntry.path}`);
520
+ }
521
+ const now = new Date().toISOString();
522
+ const bundleMeta = {
523
+ bundleRoot,
524
+ manifestPath,
525
+ manifestSha256: manifestDownloaded.sha256,
526
+ bundleVersion: manifest.version,
527
+ bundleSizeBytes: completedBytes,
528
+ };
529
+ const installed = {
530
+ id: catalogEntry.id,
531
+ displayName: catalogEntry.displayName,
532
+ path: textFile.path,
533
+ sizeBytes: textFile.sizeBytes,
534
+ hfRepo: catalogEntry.hfRepo,
535
+ installedAt: now,
536
+ lastUsedAt: null,
537
+ source: "eliza-download",
538
+ sha256: textFile.sha256,
539
+ lastVerifiedAt: now,
540
+ ...bundleMeta,
541
+ };
542
+ await upsertElizaModel(installed);
543
+ const companionId = catalogEntry.runtime?.dflash?.drafterModelId ??
544
+ catalogEntry.companionModelIds?.[0];
545
+ const companion = companionId ? findCatalogModel(companionId) : undefined;
546
+ if (companion) {
547
+ const drafterEntry = manifest.files.dflash.find((entry) => entry.path === companion.ggufFile);
548
+ if (!drafterEntry) {
549
+ throw new Error(`[local-inference] Bundle missing DFlash companion ${companion.ggufFile}`);
550
+ }
551
+ const drafterFile = downloaded.get(drafterEntry.path);
552
+ if (!drafterFile) {
553
+ throw new Error(`[local-inference] Bundle did not install DFlash companion ${drafterEntry.path}`);
554
+ }
555
+ await upsertElizaModel({
556
+ id: companion.id,
557
+ displayName: companion.displayName,
558
+ path: drafterFile.path,
559
+ sizeBytes: drafterFile.sizeBytes,
560
+ hfRepo: companion.hfRepo,
561
+ installedAt: now,
562
+ lastUsedAt: null,
563
+ source: "eliza-download",
564
+ sha256: drafterFile.sha256,
565
+ lastVerifiedAt: now,
566
+ runtimeRole: "dflash-drafter",
567
+ companionFor: catalogEntry.id,
568
+ ...bundleMeta,
569
+ });
570
+ }
571
+ await ensureDefaultAssignment(installed.id);
572
+ this.updateState(record, "completed");
573
+ record.job.received = completedBytes;
574
+ record.job.total = completedBytes;
575
+ this.rememberTerminalDownload(record.job);
576
+ this.emit({ type: "completed", job: { ...record.job } });
577
+ }
578
+ async downloadRemotePath(catalogEntry, remotePath, stagingPath, finalPath, record, baseBytes, expectedSha256) {
579
+ if (expectedSha256) {
580
+ try {
581
+ const stat = await fsp.stat(finalPath);
582
+ if (stat.isFile()) {
583
+ const currentSha256 = await hashFile(finalPath);
584
+ if (currentSha256 === expectedSha256) {
585
+ record.job.received = baseBytes + stat.size;
586
+ return {
587
+ path: finalPath,
588
+ sizeBytes: stat.size,
589
+ sha256: currentSha256,
590
+ };
591
+ }
592
+ await fsp.rm(finalPath, { force: true });
593
+ }
594
+ }
595
+ catch {
596
+ // Missing files are downloaded below; unreadable stale files are
597
+ // treated as invalid and replaced by the fresh bundle artifact.
598
+ }
599
+ }
600
+ else {
601
+ await fsp.rm(stagingPath, { force: true }).catch(() => undefined);
602
+ }
603
+ await fsp.mkdir(path.dirname(finalPath), { recursive: true });
604
+ await fsp.mkdir(path.dirname(stagingPath), { recursive: true });
605
+ let startByte = expectedSha256 ? await partialSize(stagingPath) : 0;
606
+ record.job.received = baseBytes + startByte;
607
+ const headers = {
608
+ "user-agent": "Eliza-LocalInference/1.0",
609
+ };
610
+ if (startByte > 0) {
611
+ headers.range = `bytes=${startByte}-`;
612
+ }
613
+ const httpClient = await this.loadHttpClient();
614
+ const url = buildHuggingFaceResolveUrlForPath(catalogEntry, remotePath);
615
+ const response = await httpClient.request(url, {
616
+ method: "GET",
617
+ headers,
618
+ signal: record.abortController.signal,
619
+ });
620
+ if (response.statusCode >= 400) {
621
+ throw new Error(`HTTP ${response.statusCode} from HuggingFace for ${catalogEntry.hfRepo}/${remotePath}`);
622
+ }
623
+ if (startByte > 0 && response.statusCode !== 206) {
624
+ startByte = 0;
625
+ record.job.received = baseBytes;
626
+ }
627
+ const contentLengthHeader = response.headers["content-length"];
628
+ const contentLength = Array.isArray(contentLengthHeader)
629
+ ? Number.parseInt(contentLengthHeader[0] ?? "0", 10)
630
+ : Number.parseInt(contentLengthHeader ?? "0", 10);
631
+ if (Number.isFinite(contentLength) && contentLength > 0) {
632
+ record.job.total = Math.max(record.job.total, baseBytes + startByte + contentLength);
633
+ }
634
+ const writeStream = fs.createWriteStream(stagingPath, {
635
+ flags: startByte > 0 ? "a" : "w",
636
+ });
637
+ let lastSampleBytes = record.job.received;
638
+ let lastSampleAt = Date.now();
639
+ const bodyStream = Readable.from(response.body);
640
+ bodyStream.on("data", (chunk) => {
641
+ record.job.received += chunk.length;
642
+ const now = Date.now();
643
+ const elapsed = now - lastSampleAt;
644
+ if (elapsed >= 1000) {
645
+ record.job.bytesPerSec =
646
+ ((record.job.received - lastSampleBytes) * 1000) / elapsed;
647
+ record.job.etaMs =
648
+ record.job.bytesPerSec > 0
649
+ ? ((record.job.total - record.job.received) * 1000) /
650
+ record.job.bytesPerSec
651
+ : null;
652
+ lastSampleAt = now;
653
+ lastSampleBytes = record.job.received;
654
+ }
655
+ this.throttleEmit(record);
656
+ });
657
+ await pipeline(bodyStream, writeStream);
658
+ await fsp.rename(stagingPath, finalPath);
659
+ const stat = await fsp.stat(finalPath);
660
+ const sha256 = await hashFile(finalPath);
661
+ if (expectedSha256 && sha256 !== expectedSha256) {
662
+ await fsp.rm(finalPath, { force: true });
663
+ throw new Error(`SHA256 mismatch for bundle file ${remotePath}`);
664
+ }
665
+ return { path: finalPath, sizeBytes: stat.size, sha256 };
666
+ }
368
667
  async loadHttpClient() {
369
668
  const fetchImpl = globalThis.fetch;
370
669
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../../../../../src/services/local-inference/manifest/validator.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAEV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,UAAU,EACX,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,aAAa,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAgBjE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAQnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAeT;AAiED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,UAAU,EAChB,gBAAgB,EAAE,aAAa,CAAC,YAAY,CAAC,GAC5C,aAAa,CAAC,YAAY,CAAC,CAG7B"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../../../../../src/services/local-inference/manifest/validator.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAEV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,UAAU,EACX,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,aAAa,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAgBjE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAQnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAeT;AAoHD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,UAAU,EAChB,gBAAgB,EAAE,aAAa,CAAC,YAAY,CAAC,GAC5C,aAAa,CAAC,YAAY,CAAC,CAG7B"}