cortico 0.1.0

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 (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,247 @@
1
+ /**
2
+ * llama-server's own model endpoints. In router mode `GET /models` lists everything it can
3
+ * serve with a status per model, `POST /models` pulls one from HuggingFace into `LLAMA_CACHE`,
4
+ * and `/models/load` / `/models/unload` move one in and out of memory (unload also cancels a
5
+ * download). A single-model server answers the listing without statuses.
6
+ */
7
+ import type { CatalogModel } from '../openai-responses-compat/native.ts';
8
+ import { EventDecoder } from '../transport/response-http.ts';
9
+
10
+ export type RouterModelStatus = 'loaded' | 'loading' | 'unloaded' | 'downloading' | 'sleeping' | 'failed' | 'unknown';
11
+
12
+ export interface RouterModel {
13
+ id: string;
14
+ status: RouterModelStatus;
15
+ path: string | null;
16
+ /** From `architecture.input_modalities`; null when the server does not state it. */
17
+ inputModalities: string[] | null;
18
+ /** Bytes done / total across the files of a running download. */
19
+ progress: { done: number; total: number } | null;
20
+ }
21
+
22
+ export interface RouterEndpoint {
23
+ baseUrl: string;
24
+ apiKey?: string;
25
+ }
26
+
27
+ /** `/props` re-probe interval; a reloaded model may run with a different `-c`. */
28
+ const PROPS_REFRESH_MS = 60_000;
29
+
30
+ export class RouterCatalog {
31
+ private readonly fetchImpl: typeof fetch;
32
+ private readonly refreshMs: number;
33
+ private last: RouterModel[] = [];
34
+ private readonly windows = new Map<string, { value: number | undefined; probedAt: number; inFlight: Promise<void> | null }>();
35
+ /** Byte progress per downloading model, fed by `/models/sse`; the listing itself carries none. */
36
+ private readonly progress = new Map<string, { done: number; total: number }>();
37
+ private sse: AbortController | null = null;
38
+
39
+ constructor(
40
+ private readonly endpoint: () => RouterEndpoint,
41
+ opts: { fetchImpl?: typeof fetch; refreshMs?: number } = {},
42
+ ) {
43
+ this.fetchImpl = opts.fetchImpl ?? fetch;
44
+ this.refreshMs = opts.refreshMs ?? PROPS_REFRESH_MS;
45
+ }
46
+
47
+ private origin(): string {
48
+ return new URL(this.endpoint().baseUrl).origin;
49
+ }
50
+
51
+ private headers(): Record<string, string> {
52
+ const { apiKey } = this.endpoint();
53
+ return { 'Content-Type': 'application/json', ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}) };
54
+ }
55
+
56
+ private async post(path: string, body: Record<string, unknown>): Promise<void> {
57
+ const res = await this.fetchImpl(`${this.origin()}${path}`, {
58
+ method: 'POST',
59
+ headers: this.headers(),
60
+ body: JSON.stringify(body),
61
+ signal: AbortSignal.timeout(15_000),
62
+ });
63
+ if (!res.ok) throw new Error(`POST ${path} ${res.status}: ${(await res.text()).slice(0, 300)}`);
64
+ }
65
+
66
+ async list(reload = false): Promise<RouterModel[]> {
67
+ const res = await this.fetchImpl(`${this.origin()}/models${reload ? '?reload=1' : ''}`, {
68
+ headers: this.headers(),
69
+ signal: AbortSignal.timeout(15_000),
70
+ });
71
+ if (!res.ok) throw new Error(`GET /models ${res.status}: ${(await res.text()).slice(0, 300)}`);
72
+ const json = (await res.json()) as { data?: unknown };
73
+ if (!Array.isArray(json.data)) throw new Error('GET /models returned no data array');
74
+ const models: RouterModel[] = [];
75
+ for (const row of json.data) {
76
+ if (!row || typeof row !== 'object' || typeof (row as { id?: unknown }).id !== 'string') continue;
77
+ const model = normalizeModel(row as Record<string, unknown>);
78
+ if (model.status === 'downloading') {
79
+ model.progress ??= this.progress.get(model.id) ?? null;
80
+ this.watchDownloads();
81
+ }
82
+ models.push(model);
83
+ }
84
+ this.last = models.sort((a, b) => a.id.localeCompare(b.id));
85
+ return this.last;
86
+ }
87
+
88
+ /** Triggers a HuggingFace pull; `list()` then shows the model as `downloading` with byte progress. */
89
+ async download(model: string): Promise<void> {
90
+ await this.post('/models', { model });
91
+ this.watchDownloads();
92
+ }
93
+
94
+ /** Stops the progress subscription (tests and shutdown); it also ends itself when nothing downloads. */
95
+ stop(): void {
96
+ this.sse?.abort();
97
+ this.sse = null;
98
+ }
99
+
100
+ /**
101
+ * One `/models/sse` subscription while a download runs: `download_progress` frames carry
102
+ * per-file byte counts, `download_finished` / `download_failed` end the model's entry, and
103
+ * the connection closes once no model is downloading.
104
+ */
105
+ private watchDownloads(): void {
106
+ if (this.sse) return;
107
+ const controller = new AbortController();
108
+ this.sse = controller;
109
+ void (async () => {
110
+ try {
111
+ const res = await this.fetchImpl(`${this.origin()}/models/sse`, { headers: this.headers(), signal: controller.signal });
112
+ if (!res.ok || !res.body) return;
113
+ const decoder = new EventDecoder();
114
+ const text = new TextDecoder();
115
+ const reader = res.body.getReader();
116
+ for (;;) {
117
+ const { value, done } = await reader.read();
118
+ if (done) break;
119
+ for (const raw of decoder.feed(text.decode(value, { stream: true }))) this.onDownloadEvent(raw);
120
+ if (this.progress.size === 0 && !this.last.some((model) => model.status === 'downloading')) break;
121
+ }
122
+ } catch {
123
+ // Aborted or unreachable: the next listing restarts the subscription if a download is still running.
124
+ } finally {
125
+ if (this.sse === controller) this.sse = null;
126
+ controller.abort();
127
+ }
128
+ })();
129
+ }
130
+
131
+ private onDownloadEvent(raw: string): void {
132
+ let frame: { model?: unknown; event?: unknown; data?: unknown };
133
+ try {
134
+ frame = JSON.parse(raw) as typeof frame;
135
+ } catch {
136
+ return;
137
+ }
138
+ if (typeof frame.model !== 'string') return;
139
+ if (frame.event === 'download_progress' && frame.data && typeof frame.data === 'object') {
140
+ let done = 0;
141
+ let total = 0;
142
+ for (const file of Object.values(frame.data as Record<string, { done?: unknown; total?: unknown }>)) {
143
+ if (typeof file?.done === 'number') done += file.done;
144
+ if (typeof file?.total === 'number') total += file.total;
145
+ }
146
+ this.progress.set(frame.model, { done, total });
147
+ const listed = this.last.find((model) => model.id === frame.model);
148
+ if (listed) listed.progress = { done, total };
149
+ } else if (frame.event === 'download_finished' || frame.event === 'download_failed' || frame.event === 'model_remove') {
150
+ this.progress.delete(frame.model);
151
+ }
152
+ }
153
+
154
+ load(model: string): Promise<void> {
155
+ return this.post('/models/load', { model });
156
+ }
157
+
158
+ /** Unloads a loaded model, or cancels its download. */
159
+ unload(model: string): Promise<void> {
160
+ return this.post('/models/unload', { model });
161
+ }
162
+
163
+ /** The `ProviderInstance.listModels` view: ids, plus the window when already probed. */
164
+ async listModels(): Promise<CatalogModel[]> {
165
+ const models = await this.list();
166
+ return models.map((model) => {
167
+ const window = this.windows.get(model.id)?.value;
168
+ return window ? { id: model.id, contextWindow: window } : { id: model.id };
169
+ });
170
+ }
171
+
172
+ /** Last listing's answer on images for this model; undefined when never listed or not stated. */
173
+ acceptsImage(model: string): boolean | undefined {
174
+ const found = this.last.find((row) => row.id === model);
175
+ if (!found?.inputModalities) return undefined;
176
+ return found.inputModalities.includes('image');
177
+ }
178
+
179
+ /**
180
+ * `default_generation_settings.n_ctx` from `/props?model=`: the per-slot context the server
181
+ * enforces. Never blocks; refreshes in the background and keeps the last value while the
182
+ * server is away.
183
+ */
184
+ contextWindow(model: string): number | undefined {
185
+ const entry = this.windows.get(model) ?? { value: undefined, probedAt: 0, inFlight: null };
186
+ this.windows.set(model, entry);
187
+ if (!entry.inFlight && Date.now() - entry.probedAt >= this.refreshMs) {
188
+ entry.inFlight = this.probeProps(model)
189
+ .then((value) => {
190
+ if (value !== undefined) entry.value = value;
191
+ })
192
+ .finally(() => {
193
+ entry.inFlight = null;
194
+ entry.probedAt = Date.now();
195
+ });
196
+ }
197
+ return entry.value;
198
+ }
199
+
200
+ /** Resolves once the probe started by the last lookup has settled (tests). */
201
+ settled(model: string): Promise<void> {
202
+ return this.windows.get(model)?.inFlight ?? Promise.resolve();
203
+ }
204
+
205
+ private async probeProps(model: string): Promise<number | undefined> {
206
+ try {
207
+ const res = await this.fetchImpl(`${this.origin()}/props?model=${encodeURIComponent(model)}`, {
208
+ headers: this.headers(),
209
+ signal: AbortSignal.timeout(2000),
210
+ });
211
+ if (!res.ok) return undefined;
212
+ const json = (await res.json()) as { default_generation_settings?: { n_ctx?: unknown } };
213
+ const n = json.default_generation_settings?.n_ctx;
214
+ return typeof n === 'number' && Number.isInteger(n) && n > 0 ? n : undefined;
215
+ } catch {
216
+ return undefined;
217
+ }
218
+ }
219
+ }
220
+
221
+ function normalizeModel(row: Record<string, unknown>): RouterModel {
222
+ const status = row.status as Record<string, unknown> | undefined;
223
+ const value = typeof status?.value === 'string' ? status.value : null;
224
+ let progress: RouterModel['progress'] = null;
225
+ if (status?.progress && typeof status.progress === 'object' && value === 'downloading') {
226
+ let done = 0;
227
+ let total = 0;
228
+ for (const file of Object.values(status.progress as Record<string, { done?: unknown; total?: unknown }>)) {
229
+ if (typeof file?.done === 'number') done += file.done;
230
+ if (typeof file?.total === 'number') total += file.total;
231
+ }
232
+ progress = { done, total };
233
+ }
234
+ const architecture = row.architecture as { input_modalities?: unknown } | undefined;
235
+ const modalities = Array.isArray(architecture?.input_modalities)
236
+ ? architecture.input_modalities.filter((item): item is string => typeof item === 'string')
237
+ : null;
238
+ return {
239
+ id: row.id as string,
240
+ status: status?.failed === true ? 'failed'
241
+ : value === 'loaded' || value === 'loading' || value === 'unloaded' || value === 'downloading' || value === 'sleeping' ? value
242
+ : 'unknown',
243
+ path: typeof row.path === 'string' ? row.path : null,
244
+ inputModalities: modalities,
245
+ progress,
246
+ };
247
+ }
@@ -0,0 +1,36 @@
1
+ import type { ConfigGroup, ConfigProperty } from '../../core/config-schema.ts';
2
+ import type { Language } from '../../core/language.ts';
3
+ import type { LLMProviderEntry } from '../../core/types.ts';
4
+ import { backendChoices, llamacppOptions } from './options.ts';
5
+ import { panel } from './strings.ts';
6
+
7
+ export function runtimeConfig(name: string, entry: LLMProviderEntry, language: Language): ConfigGroup[] {
8
+ if (!llamacppOptions(entry).runtime) return [];
9
+ const S = panel[language];
10
+ const group = (id: string, title: string, properties: Record<string, ConfigProperty>): ConfigGroup => ({
11
+ id: `llm.llamacpp.${name}.${id}`,
12
+ owner: 'provider:llamacpp',
13
+ schema: {
14
+ type: 'object', title,
15
+ properties: Object.fromEntries(Object.entries(properties).map(([path, property]) => [
16
+ `providers.${name}.options.${path}`, { ...property, 'x-hot': true },
17
+ ])),
18
+ },
19
+ });
20
+ return [
21
+ group('runtime', S.runtimeSection, {
22
+ 'runtime.release': { type: 'string', title: S.release, description: S.releaseHint },
23
+ 'runtime.backend': { type: 'string', title: S.backend, enum: backendChoices() },
24
+ 'runtime.runtimeDir': {
25
+ type: 'string', title: S.runtimeDir, description: S.runtimeDirHint, 'x-path': { kind: 'directory' },
26
+ },
27
+ }),
28
+ group('launch', S.launchSection, {
29
+ 'launch.contextSize': { type: 'integer', title: S.contextSize, minimum: 1, description: S.launchSectionDesc },
30
+ 'launch.nGpuLayers': { type: 'integer', title: S.nGpuLayers, minimum: 0 },
31
+ 'launch.parallel': { type: 'integer', title: S.parallel, minimum: 1 },
32
+ 'launch.extraArgs': { type: 'string', title: S.extraArgs, description: S.extraArgsHint },
33
+ autoStart: { type: 'boolean', title: S.autoStart },
34
+ }),
35
+ ];
36
+ }
@@ -0,0 +1,6 @@
1
+ import type { ConsoleClientBundle } from '../../../web/shared/client-panel.ts';
2
+ import { modelsPanel } from './models-panel.ts';
3
+ import { runtimePanel } from './runtime-panel.ts';
4
+
5
+ // The endpoint table (`builtin: 'llm-settings'`) is the console's own; only these two ship here.
6
+ export default { panels: { runtime: runtimePanel, models: modelsPanel } } satisfies ConsoleClientBundle;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Model section of one endpoint: what llama-server knows, and pulling more.
3
+ * The endpoint comes from `ctx.scope.instance`.
4
+ */
5
+ import type { ConsolePanel, ConsolePanelContext } from '../../../web/shared/client-panel.ts';
6
+ import type { ModelsState } from './server.ts';
7
+ import { panel } from '../strings.ts';
8
+
9
+ const POLL_MS = 2_000;
10
+
11
+ function bytes(n: number): string {
12
+ if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(2)} GB`;
13
+ if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
14
+ return `${Math.round(n / 1024)} KB`;
15
+ }
16
+
17
+ export const modelsPanel: ConsolePanel = {
18
+ mount: async (ctx: ConsolePanelContext) => {
19
+ const { ui, root } = ctx;
20
+ const S = ctx.language === 'en' ? panel.en : panel.zh;
21
+ const name = ctx.scope.instance;
22
+ const card = ui.sheet({ title: S.modelsTitle });
23
+ const message = ui.msgline();
24
+ root.append(card.el, message);
25
+ /** The pull input survives a re-render: the operator types while the poll redraws the card. */
26
+ let draft = '';
27
+ let lastSnapshot = '';
28
+
29
+ async function act(method: string, extra: Record<string, unknown> = {}): Promise<void> {
30
+ message.textContent = '';
31
+ try {
32
+ await ctx.invoke(method, [{ name, ...extra }]);
33
+ } catch (error) {
34
+ message.textContent = String(error);
35
+ }
36
+ await load(true);
37
+ }
38
+
39
+ function render(state: ModelsState, body: HTMLElement): void {
40
+ body.append(ui.kv([
41
+ { k: S.cacheDir, v: state.cacheDir },
42
+ { k: S.localDir, v: state.localModelsDir },
43
+ ]));
44
+ if (!state.reachable) {
45
+ body.append(ui.placeholder(S.serverDown));
46
+ return;
47
+ }
48
+ const bar = ui.rowbar();
49
+ const pull = ui.input({
50
+ placeholder: S.pullPlaceholder,
51
+ cls: 'mono',
52
+ value: draft,
53
+ onInput: (value) => { draft = value; },
54
+ onCommit: (value) => void submit(value),
55
+ });
56
+ const submit = async (value: string): Promise<void> => {
57
+ const model = value.trim();
58
+ if (!model) return;
59
+ draft = '';
60
+ await act('pull', { model });
61
+ };
62
+ bar.append(
63
+ pull,
64
+ ui.button(S.pull, { variant: 'primary', onClick: () => void submit(pull.value) }),
65
+ ui.button(S.reload, { onClick: () => void act('reload') }),
66
+ );
67
+ body.append(ui.field(S.pull, bar));
68
+ const table = ui.table({ head: [S.modelId, S.modelStatus, S.modality, S.path, S.actions] });
69
+ if (state.models.length === 0) table.clear(S.noModels);
70
+ for (const model of state.models) {
71
+ const status = ui.h('div');
72
+ status.append(ui.pill(S.modelStatusLabel[model.status] ?? model.status,
73
+ model.status === 'loaded' ? 'on' : model.status === 'failed' ? 'off' : 'plain'));
74
+ if (model.status === 'downloading' && model.progress) {
75
+ const progress = ui.progress({
76
+ value: model.progress.done,
77
+ max: model.progress.total || 1,
78
+ format: (value, max) => (model.progress?.total ? `${bytes(value)} / ${bytes(max)}` : bytes(value)),
79
+ });
80
+ status.append(progress.el);
81
+ }
82
+ const actions = ui.h('div');
83
+ if (model.status === 'downloading')
84
+ actions.append(ui.button(S.cancel, { size: 'sm', onClick: () => void act('cancel', { model: model.id }) }));
85
+ else if (model.status === 'loaded' || model.status === 'sleeping')
86
+ actions.append(ui.button(S.unload, { size: 'sm', onClick: () => void act('unload', { model: model.id }) }));
87
+ else if (model.status === 'unloaded' || model.status === 'failed')
88
+ actions.append(ui.button(S.load, { size: 'sm', onClick: () => void act('load', { model: model.id }) }));
89
+ table.addRow([
90
+ { text: model.id, cls: 'mono' },
91
+ status,
92
+ model.inputModalities ? model.inputModalities.join(' + ') : '',
93
+ { text: model.path ?? '', cls: 'mono' },
94
+ actions,
95
+ ]);
96
+ }
97
+ body.append(table.el);
98
+ }
99
+
100
+ async function load(force = false): Promise<void> {
101
+ let state: ModelsState;
102
+ try {
103
+ state = await ctx.invoke<ModelsState>('state', [{ name }]);
104
+ } catch (error) {
105
+ message.textContent = String(error);
106
+ return;
107
+ }
108
+ if (ctx.signal.aborted) return;
109
+ const snapshot = JSON.stringify(state);
110
+ const focused = card.el.ownerDocument.activeElement;
111
+ if (!force && (snapshot === lastSnapshot || (focused !== null && card.el.contains(focused)))) return;
112
+ lastSnapshot = snapshot;
113
+ card.body.replaceChildren();
114
+ render(state, card.body);
115
+ }
116
+
117
+ await load(true);
118
+ ctx.interval(() => void load(), POLL_MS);
119
+ },
120
+ };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Runtime section of one endpoint: the binary, its launch parameters and the llama-server process.
3
+ * The endpoint comes from `ctx.scope.instance`; edits are written on change.
4
+ */
5
+ import type { ConsolePanel, ConsolePanelContext } from '../../../web/shared/client-panel.ts';
6
+ import type { ConfigValues } from '../../../core/config-schema.ts';
7
+ import { configField } from '../../../web/client/features/config/view.ts';
8
+ import type { RuntimePanelState } from './server.ts';
9
+ import { panel } from '../strings.ts';
10
+
11
+ type Row = RuntimePanelState;
12
+
13
+ const POLL_MS = 2_000;
14
+
15
+ function bytes(n: number): string {
16
+ if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(2)} GB`;
17
+ if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
18
+ return `${Math.round(n / 1024)} KB`;
19
+ }
20
+
21
+ export const runtimePanel: ConsolePanel = {
22
+ mount: async (ctx: ConsolePanelContext) => {
23
+ const { ui, root } = ctx;
24
+ const S = ctx.language === 'en' ? panel.en : panel.zh;
25
+ const name = ctx.scope.instance;
26
+ const card = ui.sheet({ title: S.runtimeTitle });
27
+ const message = ui.msgline();
28
+ root.append(card.el, message);
29
+ let busy = false;
30
+ let lastSnapshot = '';
31
+
32
+ async function act(method: string, extra: Record<string, unknown> = {}): Promise<void> {
33
+ if (busy) return;
34
+ busy = true;
35
+ message.textContent = '';
36
+ try {
37
+ await ctx.invoke(method, [{ name, ...extra }]);
38
+ } catch (error) {
39
+ message.textContent = String(error);
40
+ } finally {
41
+ busy = false;
42
+ }
43
+ await load(true);
44
+ }
45
+
46
+ async function saveConfig(groupId: string, values: ConfigValues): Promise<void> {
47
+ if (busy) return;
48
+ busy = true;
49
+ message.textContent = '';
50
+ try {
51
+ await ctx.setConfig(groupId, values);
52
+ } catch (error) {
53
+ message.textContent = String(error);
54
+ } finally {
55
+ busy = false;
56
+ }
57
+ await load(true);
58
+ }
59
+
60
+ function renderConfig(row: Row, index: number, body: HTMLElement): void {
61
+ const { group, values } = row.config[index];
62
+ body.append(ui.section(group.schema.title));
63
+ for (const [path, property] of Object.entries(group.schema.properties)) {
64
+ const field = configField(ui, property, values[path], () => {
65
+ if (field.read) void saveConfig(group.id, { [path]: field.read() });
66
+ }, ctx.signal);
67
+ field.node.setAttribute('aria-label', property.title);
68
+ body.append(ui.field(property.title, field.node));
69
+ if (property.description) body.append(ui.msgline(property.description));
70
+ }
71
+ }
72
+
73
+ function renderUnmanaged(row: Row, body: HTMLElement): void {
74
+ body.append(ui.msgline(S.managedOff));
75
+ const bar = ui.rowbar();
76
+ const backend = ui.select({ options: row.backendChoices });
77
+ bar.append(ui.field(S.backend, backend), ui.button(S.enable, {
78
+ variant: 'primary',
79
+ onClick: () => void act('enable', { backend: backend.value }),
80
+ }));
81
+ body.append(bar);
82
+ }
83
+
84
+ function renderManaged(row: Row, body: HTMLElement): void {
85
+ const install = row.install;
86
+ const installPill = install.phase === 'installed' ? ui.pill(S.installed, 'on')
87
+ : install.phase === 'downloading' ? ui.pill(S.downloading, 'plain')
88
+ : install.phase === 'extracting' ? ui.pill(S.extracting, 'plain')
89
+ : ui.pill(S.absent, 'off');
90
+
91
+ renderConfig(row, 0, body);
92
+ body.append(ui.kv([{ k: S.installStatus, v: installPill }]));
93
+ if (row.smartAppControl === 1) body.append(ui.msgline(S.sacWarning, true));
94
+ if (install.phase === 'downloading' || install.phase === 'extracting') {
95
+ const progress = ui.progress({
96
+ label: `${install.phase === 'downloading' ? S.downloading : S.extracting} ${install.file ?? ''}`,
97
+ value: install.done,
98
+ max: install.total ?? 1,
99
+ format: (value, max) => (install.total ? `${bytes(value)} / ${bytes(max)}` : bytes(value)),
100
+ });
101
+ body.append(progress.el);
102
+ }
103
+ if (install.detail) body.append(ui.msgline(install.detail, true));
104
+ const installBar = ui.rowbar();
105
+ if (!row.own) {
106
+ const button = ui.button(install.phase === 'installed' ? S.reinstall : S.install, {
107
+ variant: install.phase === 'installed' ? 'plain' : 'primary',
108
+ onClick: () => void act('install'),
109
+ });
110
+ button.disabled = !row.supported || install.phase === 'downloading' || install.phase === 'extracting';
111
+ installBar.append(button);
112
+ }
113
+ installBar.append(ui.h('span', 'grow'), ui.button(S.disable, { onClick: () => void act('disable') }));
114
+ body.append(installBar);
115
+
116
+ renderConfig(row, 1, body);
117
+
118
+ body.append(ui.section(S.serverSection, S.serverSectionDesc));
119
+ const server = row.server;
120
+ if (server) {
121
+ const endpointRow = ui.rowbar();
122
+ const epText = ui.h('span', 'mono', server.baseUrl);
123
+ const epPill = ui.pill(server.reachable ? S.reachable : S.unreachable, server.reachable ? 'on' : 'off');
124
+ const copyBtn = ui.copyButton(server.baseUrl, { size: 'sm' });
125
+ endpointRow.append(epText, epPill, copyBtn);
126
+
127
+ body.append(ui.kv([
128
+ { k: S.status, v: ui.pill(S.phase[server.phase] ?? server.phase, server.phase === 'running' ? 'on' : server.phase === 'error' ? 'off' : 'plain') },
129
+ { k: S.endpoint, v: endpointRow },
130
+ { k: S.pid, v: server.pid === null ? '' : String(server.pid) },
131
+ ]));
132
+ if (server.configurationPending) body.append(ui.msgline(S.pendingNote));
133
+ if (server.detail) body.append(ui.msgline(server.detail, server.phase === 'error'));
134
+ }
135
+ const serverBar = ui.rowbar();
136
+ const start = ui.button(S.start, { variant: 'primary', onClick: () => void act('start') });
137
+ start.disabled = install.phase !== 'installed' || server?.phase === 'running' || server?.phase === 'starting';
138
+ const stop = ui.button(S.stop, { onClick: () => void act('stop') });
139
+ stop.disabled = !server || server.phase === 'stopped';
140
+ serverBar.append(start, stop);
141
+ body.append(serverBar);
142
+ if (install.phase !== 'installed') {
143
+ body.append(ui.msgline(S.installRequired));
144
+ }
145
+ }
146
+
147
+ /** The poll redraws the card, so it stands down while the operator is in one of its fields. */
148
+ function editing(): boolean {
149
+ const focused = card.el.ownerDocument.activeElement;
150
+ return focused !== null && card.el.contains(focused);
151
+ }
152
+
153
+ async function load(force = false): Promise<void> {
154
+ let row: Row;
155
+ try {
156
+ row = await ctx.invoke<Row>('state', [{ name }]);
157
+ } catch (error) {
158
+ message.textContent = String(error);
159
+ return;
160
+ }
161
+ if (ctx.signal.aborted) return;
162
+ const snapshot = JSON.stringify(row);
163
+ if (!force && (snapshot === lastSnapshot || editing())) return;
164
+ lastSnapshot = snapshot;
165
+ card.body.replaceChildren();
166
+ if (row.managed) renderManaged(row, card.body);
167
+ else renderUnmanaged(row, card.body);
168
+ }
169
+
170
+ await load(true);
171
+ ctx.interval(() => void load(), POLL_MS);
172
+ },
173
+ };