@oh-my-pi/pi-coding-agent 15.5.13 → 15.6.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 (192) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/types/cli/classify-install-target.d.ts +0 -10
  3. package/dist/types/cli/initial-message.d.ts +1 -1
  4. package/dist/types/cli/tiny-models-cli.d.ts +9 -0
  5. package/dist/types/commands/tiny-models.d.ts +22 -0
  6. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  7. package/dist/types/commit/analysis/summary.d.ts +1 -1
  8. package/dist/types/commit/changelog/generate.d.ts +1 -1
  9. package/dist/types/commit/changelog/index.d.ts +2 -2
  10. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  11. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  12. package/dist/types/config/model-id-affixes.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +1 -1
  14. package/dist/types/config/models-config-schema.d.ts +2 -0
  15. package/dist/types/config/settings-schema.d.ts +233 -17
  16. package/dist/types/discovery/helpers.d.ts +1 -1
  17. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  18. package/dist/types/eval/__tests__/llm-bridge.test.d.ts +1 -0
  19. package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
  20. package/dist/types/eval/llm-bridge.d.ts +25 -0
  21. package/dist/types/export/html/template.generated.d.ts +1 -1
  22. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +15 -0
  23. package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
  24. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
  25. package/dist/types/internal-urls/local-protocol.d.ts +2 -1
  26. package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
  27. package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
  28. package/dist/types/internal-urls/router.d.ts +8 -1
  29. package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
  30. package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
  31. package/dist/types/internal-urls/types.d.ts +26 -0
  32. package/dist/types/memory-backend/index.d.ts +1 -0
  33. package/dist/types/memory-backend/resolve.d.ts +2 -1
  34. package/dist/types/memory-backend/types.d.ts +7 -1
  35. package/dist/types/mnemosyne/backend.d.ts +4 -0
  36. package/dist/types/mnemosyne/config.d.ts +29 -0
  37. package/dist/types/mnemosyne/index.d.ts +3 -0
  38. package/dist/types/mnemosyne/state.d.ts +72 -0
  39. package/dist/types/modes/components/custom-editor.d.ts +2 -3
  40. package/dist/types/modes/components/hook-selector.d.ts +27 -0
  41. package/dist/types/modes/components/index.d.ts +1 -0
  42. package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
  43. package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
  44. package/dist/types/modes/components/welcome.d.ts +1 -0
  45. package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
  46. package/dist/types/modes/gradient-highlight.d.ts +23 -0
  47. package/dist/types/modes/interactive-mode.d.ts +4 -2
  48. package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
  49. package/dist/types/modes/orchestrate.d.ts +10 -0
  50. package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
  51. package/dist/types/modes/theme/theme.d.ts +2 -1
  52. package/dist/types/modes/ultrathink.d.ts +3 -3
  53. package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
  54. package/dist/types/sdk.d.ts +3 -0
  55. package/dist/types/session/agent-session.d.ts +35 -0
  56. package/dist/types/system-prompt.d.ts +2 -0
  57. package/dist/types/task/executor.d.ts +2 -0
  58. package/dist/types/task/render.d.ts +5 -1
  59. package/dist/types/tiny/models.d.ts +185 -0
  60. package/dist/types/tiny/text.d.ts +4 -0
  61. package/dist/types/tiny/title-client.d.ts +24 -0
  62. package/dist/types/tiny/title-protocol.d.ts +74 -0
  63. package/dist/types/tiny/worker.d.ts +2 -0
  64. package/dist/types/tools/bash.d.ts +3 -1
  65. package/dist/types/tools/index.d.ts +7 -4
  66. package/dist/types/tools/memory-edit.d.ts +40 -0
  67. package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
  68. package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
  69. package/dist/types/tools/memory-render.d.ts +60 -0
  70. package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
  71. package/dist/types/tools/todo-write.d.ts +8 -0
  72. package/dist/types/tools/tool-result.d.ts +2 -0
  73. package/dist/types/utils/title-generator.d.ts +3 -0
  74. package/package.json +18 -14
  75. package/scripts/build-binary.ts +1 -0
  76. package/src/cli/tiny-models-cli.ts +127 -0
  77. package/src/cli-commands.ts +1 -0
  78. package/src/cli.ts +8 -8
  79. package/src/commands/tiny-models.ts +36 -0
  80. package/src/config/model-equivalence.ts +43 -2
  81. package/src/config/model-id-affixes.ts +64 -0
  82. package/src/config/model-registry.ts +166 -8
  83. package/src/config/models-config-schema.ts +1 -1
  84. package/src/config/settings-schema.ts +206 -14
  85. package/src/edit/hashline/diff.ts +5 -7
  86. package/src/eval/__tests__/llm-bridge.test.ts +297 -0
  87. package/src/eval/__tests__/shared-executors.test.ts +36 -0
  88. package/src/eval/js/shared/local-module-loader.ts +13 -1
  89. package/src/eval/js/shared/prelude.txt +8 -0
  90. package/src/eval/js/shared/rewrite-imports.ts +31 -26
  91. package/src/eval/js/tool-bridge.ts +4 -0
  92. package/src/eval/llm-bridge.ts +181 -0
  93. package/src/eval/py/prelude.py +52 -31
  94. package/src/export/html/template.generated.ts +1 -1
  95. package/src/export/html/template.js +0 -13
  96. package/src/extensibility/plugins/legacy-pi-compat.ts +60 -23
  97. package/src/internal-urls/agent-protocol.ts +18 -1
  98. package/src/internal-urls/artifact-protocol.ts +19 -1
  99. package/src/internal-urls/docs-index.generated.ts +5 -4
  100. package/src/internal-urls/local-protocol.ts +14 -1
  101. package/src/internal-urls/memory-protocol.ts +6 -1
  102. package/src/internal-urls/omp-protocol.ts +5 -1
  103. package/src/internal-urls/router.ts +20 -1
  104. package/src/internal-urls/rule-protocol.ts +8 -1
  105. package/src/internal-urls/skill-protocol.ts +8 -1
  106. package/src/internal-urls/types.ts +27 -0
  107. package/src/lsp/render.ts +1 -1
  108. package/src/main.ts +4 -0
  109. package/src/mcp/oauth-flow.ts +2 -2
  110. package/src/memory-backend/index.ts +1 -0
  111. package/src/memory-backend/resolve.ts +4 -1
  112. package/src/memory-backend/types.ts +8 -1
  113. package/src/mnemosyne/backend.ts +374 -0
  114. package/src/mnemosyne/config.ts +160 -0
  115. package/src/mnemosyne/index.ts +3 -0
  116. package/src/mnemosyne/state.ts +548 -0
  117. package/src/modes/acp/acp-agent.ts +11 -6
  118. package/src/modes/components/agent-dashboard.ts +4 -4
  119. package/src/modes/components/custom-editor.ts +3 -2
  120. package/src/modes/components/diff.ts +2 -2
  121. package/src/modes/components/extensions/extension-list.ts +3 -2
  122. package/src/modes/components/footer.ts +5 -6
  123. package/src/modes/components/history-search.ts +3 -3
  124. package/src/modes/components/hook-selector.ts +94 -8
  125. package/src/modes/components/index.ts +1 -0
  126. package/src/modes/components/mcp-add-wizard.ts +3 -3
  127. package/src/modes/components/model-selector.ts +124 -26
  128. package/src/modes/components/oauth-selector.ts +3 -3
  129. package/src/modes/components/session-observer-overlay.ts +19 -13
  130. package/src/modes/components/session-selector.ts +3 -3
  131. package/src/modes/components/settings-defs.ts +7 -0
  132. package/src/modes/components/status-line/context-thresholds.ts +11 -0
  133. package/src/modes/components/status-line/presets.ts +1 -0
  134. package/src/modes/components/status-line/segments.ts +25 -2
  135. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  136. package/src/modes/components/tips.txt +12 -0
  137. package/src/modes/components/tool-execution.ts +67 -3
  138. package/src/modes/components/tree-selector.ts +3 -3
  139. package/src/modes/components/user-message-selector.ts +3 -3
  140. package/src/modes/components/welcome.ts +55 -1
  141. package/src/modes/controllers/command-controller.ts +16 -1
  142. package/src/modes/controllers/extension-ui-controller.ts +3 -1
  143. package/src/modes/controllers/input-controller.ts +57 -0
  144. package/src/modes/gradient-highlight.ts +70 -0
  145. package/src/modes/interactive-mode.ts +80 -196
  146. package/src/modes/internal-url-autocomplete.ts +143 -0
  147. package/src/modes/orchestrate.ts +36 -0
  148. package/src/modes/prompt-action-autocomplete.ts +12 -0
  149. package/src/modes/theme/theme.ts +7 -0
  150. package/src/modes/ultrathink.ts +9 -53
  151. package/src/modes/utils/keybinding-matchers.ts +11 -0
  152. package/src/prompts/system/memory-consolidation-system.md +8 -0
  153. package/src/prompts/system/memory-extraction-system.md +26 -0
  154. package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +5 -16
  155. package/src/prompts/system/system-prompt.md +2 -0
  156. package/src/prompts/system/tiny-title-system.md +8 -0
  157. package/src/prompts/tools/eval.md +2 -0
  158. package/src/prompts/tools/memory-edit.md +8 -0
  159. package/src/prompts/tools/task.md +4 -7
  160. package/src/sdk.ts +8 -6
  161. package/src/session/agent-session.ts +147 -44
  162. package/src/session/session-manager.ts +47 -0
  163. package/src/slash-commands/builtin-registry.ts +10 -1
  164. package/src/system-prompt.ts +4 -0
  165. package/src/task/commands.ts +1 -5
  166. package/src/task/executor.ts +8 -0
  167. package/src/task/index.ts +2 -0
  168. package/src/task/render.ts +69 -26
  169. package/src/tiny/models.ts +217 -0
  170. package/src/tiny/text.ts +19 -0
  171. package/src/tiny/title-client.ts +340 -0
  172. package/src/tiny/title-protocol.ts +51 -0
  173. package/src/tiny/worker.ts +523 -0
  174. package/src/tools/bash.ts +58 -16
  175. package/src/tools/browser/tab-worker.ts +1 -1
  176. package/src/tools/eval.ts +24 -48
  177. package/src/tools/index.ts +17 -15
  178. package/src/tools/memory-edit.ts +59 -0
  179. package/src/tools/memory-recall.ts +100 -0
  180. package/src/tools/memory-reflect.ts +88 -0
  181. package/src/tools/memory-render.ts +185 -0
  182. package/src/tools/memory-retain.ts +91 -0
  183. package/src/tools/renderers.ts +4 -2
  184. package/src/tools/todo-write.ts +128 -29
  185. package/src/tools/tool-result.ts +8 -0
  186. package/src/utils/title-generator.ts +115 -13
  187. package/dist/types/tools/calculator.d.ts +0 -77
  188. package/src/prompts/tools/calculator.md +0 -10
  189. package/src/tools/calculator.ts +0 -541
  190. package/src/tools/hindsight-recall.ts +0 -69
  191. package/src/tools/hindsight-reflect.ts +0 -58
  192. package/src/tools/hindsight-retain.ts +0 -57
@@ -0,0 +1,340 @@
1
+ import { isCompiledBinary, logger } from "@oh-my-pi/pi-utils";
2
+ import {
3
+ isTinyLocalModelKey,
4
+ isTinyMemoryLocalModelKey,
5
+ isTinyTitleLocalModelKey,
6
+ type TinyLocalModelKey,
7
+ type TinyMemoryLocalModelKey,
8
+ type TinyTitleLocalModelKey,
9
+ } from "./models";
10
+ import type { TinyTitleProgressEvent, TinyTitleWorkerInbound, TinyTitleWorkerOutbound } from "./title-protocol";
11
+
12
+ interface WorkerHandle {
13
+ send(message: TinyTitleWorkerInbound): void;
14
+ onMessage(handler: (message: TinyTitleWorkerOutbound) => void): () => void;
15
+ onError(handler: (error: Error) => void): () => void;
16
+ terminate(): Promise<void>;
17
+ }
18
+
19
+ type PendingRequest =
20
+ | { kind: "generate"; modelKey: TinyTitleLocalModelKey; resolve: (title: string | null) => void }
21
+ | { kind: "complete"; modelKey: TinyMemoryLocalModelKey; resolve: (text: string | null) => void }
22
+ | { kind: "download"; modelKey: TinyLocalModelKey; resolve: (ok: boolean) => void };
23
+
24
+ export interface TinyTitleDownloadOptions {
25
+ signal?: AbortSignal;
26
+ onProgress?: (event: TinyTitleProgressEvent) => void;
27
+ }
28
+
29
+ const SMOKE_TEST_TIMEOUT_MS = 5_000;
30
+
31
+ export function createTinyTitleWorker(): Worker {
32
+ return isCompiledBinary()
33
+ ? new Worker("./packages/coding-agent/src/tiny/worker.ts", { type: "module" })
34
+ : new Worker(new URL("./worker.ts", import.meta.url).href, { type: "module" });
35
+ }
36
+
37
+ function wrapBunWorker(worker: Worker): WorkerHandle {
38
+ (worker as Worker & { unref?: () => void }).unref?.();
39
+ return {
40
+ send(message) {
41
+ worker.postMessage(message);
42
+ },
43
+ onMessage(handler) {
44
+ const wrap = (event: MessageEvent): void => handler(event.data as TinyTitleWorkerOutbound);
45
+ worker.addEventListener("message", wrap);
46
+ return () => worker.removeEventListener("message", wrap);
47
+ },
48
+ onError(handler) {
49
+ const wrap = (event: ErrorEvent): void => {
50
+ handler(event.error instanceof Error ? event.error : new Error(event.message || "tiny title worker error"));
51
+ };
52
+ worker.addEventListener("error", wrap);
53
+ return () => worker.removeEventListener("error", wrap);
54
+ },
55
+ async terminate() {
56
+ worker.terminate();
57
+ },
58
+ };
59
+ }
60
+
61
+ function spawnInlineUnavailableWorker(error: unknown): WorkerHandle {
62
+ const listeners = new Set<(message: TinyTitleWorkerOutbound) => void>();
63
+ const errorMessage = error instanceof Error ? error.message : String(error);
64
+ const emit = (message: TinyTitleWorkerOutbound): void => {
65
+ for (const listener of listeners) listener(message);
66
+ };
67
+ return {
68
+ send(message) {
69
+ queueMicrotask(() => {
70
+ if (message.type === "ping") {
71
+ emit({ type: "pong", id: message.id });
72
+ return;
73
+ }
74
+ if (message.type === "close") {
75
+ emit({ type: "closed" });
76
+ return;
77
+ }
78
+ emit({ type: "error", id: message.id, error: errorMessage });
79
+ });
80
+ },
81
+ onMessage(handler) {
82
+ listeners.add(handler);
83
+ return () => listeners.delete(handler);
84
+ },
85
+ onError() {
86
+ return () => {};
87
+ },
88
+ async terminate() {
89
+ listeners.clear();
90
+ },
91
+ };
92
+ }
93
+
94
+ function spawnTinyTitleWorker(): WorkerHandle {
95
+ try {
96
+ return wrapBunWorker(createTinyTitleWorker());
97
+ } catch (error) {
98
+ logger.warn("Tiny title Worker spawn failed; local titles disabled", {
99
+ error: error instanceof Error ? error.message : String(error),
100
+ });
101
+ return spawnInlineUnavailableWorker(error);
102
+ }
103
+ }
104
+
105
+ function logWorkerMessage(message: Extract<TinyTitleWorkerOutbound, { type: "log" }>): void {
106
+ if (message.level === "debug") logger.debug(message.msg, message.meta);
107
+ else if (message.level === "warn") logger.warn(message.msg, message.meta);
108
+ else logger.error(message.msg, message.meta);
109
+ }
110
+
111
+ export class TinyTitleClient {
112
+ #worker: WorkerHandle | null = null;
113
+ #unsubscribeMessage: (() => void) | null = null;
114
+ #unsubscribeError: (() => void) | null = null;
115
+ #pending = new Map<string, PendingRequest>();
116
+ #progressListeners = new Set<(event: TinyTitleProgressEvent) => void>();
117
+ #nextRequestId = 0;
118
+
119
+ onProgress(listener: (event: TinyTitleProgressEvent) => void): () => void {
120
+ this.#progressListeners.add(listener);
121
+ return () => this.#progressListeners.delete(listener);
122
+ }
123
+
124
+ async generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null> {
125
+ if (!isTinyTitleLocalModelKey(modelKey)) return null;
126
+ if (signal?.aborted) return null;
127
+
128
+ try {
129
+ const worker = this.#ensureWorker();
130
+ const id = String(++this.#nextRequestId);
131
+ const { promise, resolve } = Promise.withResolvers<string | null>();
132
+ this.#pending.set(id, { kind: "generate", modelKey, resolve });
133
+ const abort = (): void => {
134
+ const pending = this.#pending.get(id);
135
+ if (pending?.kind !== "generate") return;
136
+ this.#pending.delete(id);
137
+ pending.resolve(null);
138
+ };
139
+ signal?.addEventListener("abort", abort, { once: true });
140
+ try {
141
+ worker.send({ type: "generate", id, modelKey, message });
142
+ return await promise;
143
+ } finally {
144
+ signal?.removeEventListener("abort", abort);
145
+ this.#pending.delete(id);
146
+ }
147
+ } catch (error) {
148
+ logger.debug("tiny-title: local generation failed", {
149
+ modelKey,
150
+ error: error instanceof Error ? error.message : String(error),
151
+ });
152
+ return null;
153
+ }
154
+ }
155
+
156
+ async complete(
157
+ modelKey: string,
158
+ prompt: string,
159
+ options: { maxTokens?: number; signal?: AbortSignal } = {},
160
+ ): Promise<string | null> {
161
+ if (!isTinyMemoryLocalModelKey(modelKey)) return null;
162
+ if (options.signal?.aborted) return null;
163
+
164
+ try {
165
+ const worker = this.#ensureWorker();
166
+ const id = String(++this.#nextRequestId);
167
+ const { promise, resolve } = Promise.withResolvers<string | null>();
168
+ this.#pending.set(id, { kind: "complete", modelKey, resolve });
169
+ const abort = (): void => {
170
+ const pending = this.#pending.get(id);
171
+ if (pending?.kind !== "complete") return;
172
+ this.#pending.delete(id);
173
+ pending.resolve(null);
174
+ };
175
+ options.signal?.addEventListener("abort", abort, { once: true });
176
+ try {
177
+ worker.send({ type: "complete", id, modelKey, prompt, maxTokens: options.maxTokens });
178
+ return await promise;
179
+ } finally {
180
+ options.signal?.removeEventListener("abort", abort);
181
+ this.#pending.delete(id);
182
+ }
183
+ } catch (error) {
184
+ logger.debug("tiny-model: local completion failed", {
185
+ modelKey,
186
+ error: error instanceof Error ? error.message : String(error),
187
+ });
188
+ return null;
189
+ }
190
+ }
191
+
192
+ async downloadModel(modelKey: string, options: TinyTitleDownloadOptions = {}): Promise<boolean> {
193
+ if (!isTinyLocalModelKey(modelKey)) return false;
194
+ if (options.signal?.aborted) return false;
195
+
196
+ const unsubscribe = options.onProgress ? this.onProgress(options.onProgress) : undefined;
197
+ try {
198
+ const worker = this.#ensureWorker();
199
+ const id = String(++this.#nextRequestId);
200
+ const { promise, resolve } = Promise.withResolvers<boolean>();
201
+ this.#pending.set(id, { kind: "download", modelKey, resolve });
202
+ const abort = (): void => {
203
+ const pending = this.#pending.get(id);
204
+ if (pending?.kind !== "download") return;
205
+ this.#pending.delete(id);
206
+ pending.resolve(false);
207
+ };
208
+ options.signal?.addEventListener("abort", abort, { once: true });
209
+ try {
210
+ worker.send({ type: "download", id, modelKey });
211
+ return await promise;
212
+ } finally {
213
+ options.signal?.removeEventListener("abort", abort);
214
+ this.#pending.delete(id);
215
+ }
216
+ } catch (error) {
217
+ logger.debug("tiny-title: local model download failed", {
218
+ modelKey,
219
+ error: error instanceof Error ? error.message : String(error),
220
+ });
221
+ return false;
222
+ } finally {
223
+ unsubscribe?.();
224
+ }
225
+ }
226
+
227
+ async terminate(): Promise<void> {
228
+ const worker = this.#worker;
229
+ this.#worker = null;
230
+ this.#unsubscribeMessage?.();
231
+ this.#unsubscribeMessage = null;
232
+ this.#unsubscribeError?.();
233
+ this.#unsubscribeError = null;
234
+ for (const pending of this.#pending.values()) {
235
+ this.#emitProgress({ modelKey: pending.modelKey, status: "error" });
236
+ if (pending.kind === "generate" || pending.kind === "complete") pending.resolve(null);
237
+ else pending.resolve(false);
238
+ }
239
+ this.#pending.clear();
240
+ try {
241
+ worker?.send({ type: "close" });
242
+ } catch {
243
+ // Worker may already be gone.
244
+ }
245
+ }
246
+
247
+ #ensureWorker(): WorkerHandle {
248
+ if (this.#worker) return this.#worker;
249
+ const worker = spawnTinyTitleWorker();
250
+ this.#worker = worker;
251
+ this.#unsubscribeMessage = worker.onMessage(message => this.#handleMessage(message));
252
+ this.#unsubscribeError = worker.onError(error => this.#handleWorkerError(error));
253
+ return worker;
254
+ }
255
+
256
+ #handleMessage(message: TinyTitleWorkerOutbound): void {
257
+ if (message.type === "log") {
258
+ logWorkerMessage(message);
259
+ return;
260
+ }
261
+ if (message.type === "progress") {
262
+ this.#emitProgress(message.event);
263
+ return;
264
+ }
265
+ if (message.type === "closed") return;
266
+ if (message.type === "pong") return;
267
+
268
+ const pending = this.#pending.get(message.id);
269
+ if (!pending) return;
270
+ this.#pending.delete(message.id);
271
+ if (message.type === "title") {
272
+ if (pending.kind === "generate") pending.resolve(message.title);
273
+ return;
274
+ }
275
+ if (message.type === "downloaded") {
276
+ if (pending.kind === "download") pending.resolve(true);
277
+ return;
278
+ }
279
+ if (message.type === "completion") {
280
+ if (pending.kind === "complete") pending.resolve(message.text);
281
+ return;
282
+ }
283
+ logger.debug("tiny-title: worker returned error", { error: message.error });
284
+ this.#emitProgress({ modelKey: pending.modelKey, status: "error" });
285
+ if (pending.kind === "generate" || pending.kind === "complete") pending.resolve(null);
286
+ else pending.resolve(false);
287
+ }
288
+
289
+ #emitProgress(event: TinyTitleProgressEvent): void {
290
+ for (const listener of this.#progressListeners) listener(event);
291
+ }
292
+
293
+ #handleWorkerError(error: Error): void {
294
+ logger.warn("tiny-title: worker error", { error: error.message });
295
+ for (const pending of this.#pending.values()) {
296
+ this.#emitProgress({ modelKey: pending.modelKey, status: "error" });
297
+ if (pending.kind === "generate" || pending.kind === "complete") pending.resolve(null);
298
+ else pending.resolve(false);
299
+ }
300
+ this.#pending.clear();
301
+ void this.terminate();
302
+ }
303
+ }
304
+
305
+ export const tinyTitleClient = new TinyTitleClient();
306
+
307
+ /** Alias for the shared tiny-model worker client (titles + memory completions). */
308
+ export const tinyModelClient = tinyTitleClient;
309
+
310
+ export async function shutdownTinyTitleClient(): Promise<void> {
311
+ await tinyTitleClient.terminate();
312
+ }
313
+
314
+ export async function smokeTestTinyTitleWorker({
315
+ timeoutMs = SMOKE_TEST_TIMEOUT_MS,
316
+ }: {
317
+ timeoutMs?: number;
318
+ } = {}): Promise<void> {
319
+ const worker = createTinyTitleWorker();
320
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
321
+ const timer = setTimeout(() => reject(new Error(`tiny title worker did not pong within ${timeoutMs}ms`)), timeoutMs);
322
+ worker.onmessage = (event: MessageEvent<TinyTitleWorkerOutbound>) => {
323
+ const message = event.data;
324
+ if (message.type === "pong") {
325
+ resolve();
326
+ return;
327
+ }
328
+ reject(new Error(`tiny title worker: expected pong, got ${JSON.stringify(message)}`));
329
+ };
330
+ worker.onerror = (event: ErrorEvent) => {
331
+ reject(event.error instanceof Error ? event.error : new Error(event.message || "tiny title worker error"));
332
+ };
333
+ try {
334
+ worker.postMessage({ type: "ping", id: "smoke" } satisfies TinyTitleWorkerInbound);
335
+ await promise;
336
+ } finally {
337
+ clearTimeout(timer);
338
+ worker.terminate();
339
+ }
340
+ }
@@ -0,0 +1,51 @@
1
+ import type { TinyLocalModelKey, TinyTitleLocalModelKey } from "./models";
2
+
3
+ export type TinyTitleProgressStatus =
4
+ | "initiate"
5
+ | "download"
6
+ | "progress"
7
+ | "progress_total"
8
+ | "done"
9
+ | "ready"
10
+ | "error";
11
+
12
+ export interface TinyTitleProgressFileState {
13
+ loaded: number;
14
+ total: number;
15
+ }
16
+
17
+ export interface TinyTitleProgressEvent {
18
+ modelKey: TinyLocalModelKey;
19
+ status: TinyTitleProgressStatus;
20
+ name?: string;
21
+ file?: string;
22
+ progress?: number;
23
+ loaded?: number;
24
+ total?: number;
25
+ files?: Record<string, TinyTitleProgressFileState>;
26
+ task?: string;
27
+ model?: string;
28
+ }
29
+
30
+ export type TinyTitleWorkerInbound =
31
+ | { type: "ping"; id: string }
32
+ | { type: "generate"; id: string; modelKey: TinyTitleLocalModelKey; message: string }
33
+ | { type: "complete"; id: string; modelKey: TinyLocalModelKey; prompt: string; maxTokens?: number }
34
+ | { type: "download"; id: string; modelKey: TinyLocalModelKey }
35
+ | { type: "close" };
36
+
37
+ export type TinyTitleWorkerOutbound =
38
+ | { type: "pong"; id: string }
39
+ | { type: "title"; id: string; title: string | null }
40
+ | { type: "completion"; id: string; text: string | null }
41
+ | { type: "downloaded"; id: string }
42
+ | { type: "error"; id: string; error: string }
43
+ | { type: "progress"; id: string; event: TinyTitleProgressEvent }
44
+ | { type: "log"; level: "debug" | "warn" | "error"; msg: string; meta?: Record<string, unknown> }
45
+ | { type: "closed" };
46
+
47
+ export interface TinyTitleTransport {
48
+ send(message: TinyTitleWorkerOutbound): void;
49
+ onMessage(handler: (message: TinyTitleWorkerInbound) => void): () => void;
50
+ close(): void;
51
+ }