@oh-my-pi/pi-coding-agent 16.1.13 → 16.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/cli.js +5615 -2833
- package/dist/types/advisor/runtime.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +64 -14
- package/dist/types/eval/__tests__/julia-prelude.test.d.ts +1 -0
- package/dist/types/eval/backend-helpers.d.ts +23 -0
- package/dist/types/eval/executor-base.d.ts +118 -0
- package/dist/types/eval/index.d.ts +2 -0
- package/dist/types/eval/jl/executor.d.ts +44 -0
- package/dist/types/eval/jl/index.d.ts +11 -0
- package/dist/types/eval/jl/kernel.d.ts +28 -0
- package/dist/types/eval/jl/prelude.d.ts +1 -0
- package/dist/types/eval/jl/runtime.d.ts +22 -0
- package/dist/types/eval/kernel-base.d.ts +105 -0
- package/dist/types/eval/py/kernel.d.ts +3 -61
- package/dist/types/eval/rb/executor.d.ts +77 -0
- package/dist/types/eval/rb/index.d.ts +11 -0
- package/dist/types/eval/rb/kernel.d.ts +31 -0
- package/dist/types/eval/rb/prelude.d.ts +1 -0
- package/dist/types/eval/rb/runtime.d.ts +23 -0
- package/dist/types/eval/runtime-env.d.ts +24 -0
- package/dist/types/eval/types.d.ts +3 -3
- package/dist/types/export/share.d.ts +8 -1
- package/dist/types/extensibility/extensions/runner.d.ts +2 -15
- package/dist/types/extensibility/hooks/loader.d.ts +1 -25
- package/dist/types/extensibility/session-handler-types.d.ts +23 -0
- package/dist/types/jsonrpc/message-framing.d.ts +35 -0
- package/dist/types/mcp/transports/stdio.d.ts +12 -1
- package/dist/types/mnemopi/embed-client.d.ts +7 -24
- package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +11 -0
- package/dist/types/modes/components/selector-helpers.d.ts +53 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +4 -3
- package/dist/types/modes/components/status-line/types.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +8 -1
- package/dist/types/modes/types.d.ts +0 -2
- package/dist/types/modes/utils/copy-targets.d.ts +1 -1
- package/dist/types/modes/utils/interactive-context-helpers.d.ts +14 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +54 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +3 -3
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/stt/asr-client.d.ts +3 -29
- package/dist/types/subprocess/worker-client.d.ts +149 -0
- package/dist/types/subprocess/worker-runtime.d.ts +107 -0
- package/dist/types/tiny/title-client.d.ts +14 -34
- package/dist/types/tools/eval-backends.d.ts +6 -3
- package/dist/types/tools/eval-render.d.ts +6 -5
- package/dist/types/tools/eval.d.ts +13 -15
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tts/tts-client.d.ts +3 -28
- package/dist/types/tui/code-cell.d.ts +7 -0
- package/dist/types/utils/file-display-mode.d.ts +1 -1
- package/dist/types/utils/shell-snapshot.d.ts +10 -0
- package/dist/types/web/parallel.d.ts +6 -0
- package/dist/types/web/search/providers/perplexity.d.ts +17 -3
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +114 -0
- package/src/advisor/runtime.ts +129 -1
- package/src/config/model-registry.ts +12 -4
- package/src/config/settings-schema.ts +74 -18
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +13 -107
- package/src/eval/__tests__/julia-prelude.test.ts +77 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/executor-base.ts +425 -0
- package/src/eval/index.ts +2 -0
- package/src/eval/jl/executor.ts +540 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +235 -0
- package/src/eval/jl/prelude.jl +930 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +634 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/index.ts +3 -14
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/executor.ts +43 -252
- package/src/eval/py/index.ts +9 -20
- package/src/eval/py/kernel.ts +29 -544
- package/src/eval/rb/executor.ts +504 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +230 -0
- package/src/eval/rb/prelude.rb +721 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +474 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/types.ts +3 -3
- package/src/exec/bash-executor.ts +44 -0
- package/src/export/share.ts +51 -28
- package/src/extensibility/extensions/runner.ts +4 -11
- package/src/extensibility/hooks/loader.ts +3 -21
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/lsp/client.ts +13 -109
- package/src/mcp/transports/stdio.ts +20 -4
- package/src/mnemopi/embed-client.ts +43 -198
- package/src/modes/components/agent-dashboard.ts +17 -40
- package/src/modes/components/chat-transcript-builder.ts +18 -102
- package/src/modes/components/custom-editor.test.ts +22 -0
- package/src/modes/components/custom-editor.ts +29 -1
- package/src/modes/components/extensions/extension-dashboard.ts +4 -13
- package/src/modes/components/extensions/extension-list.ts +16 -44
- package/src/modes/components/footer.ts +4 -3
- package/src/modes/components/history-search.ts +4 -16
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/settings-selector.ts +7 -5
- package/src/modes/components/status-line/component.ts +5 -1
- package/src/modes/components/status-line/context-thresholds.ts +11 -3
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/tree-selector.ts +13 -18
- package/src/modes/controllers/command-controller.ts +1 -0
- package/src/modes/controllers/event-controller.ts +3 -9
- package/src/modes/controllers/input-controller.ts +32 -54
- package/src/modes/interactive-mode.ts +5 -7
- package/src/modes/theme/theme.ts +35 -0
- package/src/modes/types.ts +0 -2
- package/src/modes/utils/copy-targets.ts +3 -2
- package/src/modes/utils/interactive-context-helpers.ts +27 -0
- package/src/modes/utils/transcript-render-helpers.ts +157 -0
- package/src/modes/utils/ui-helpers.ts +21 -126
- package/src/prompts/system/system-prompt.md +5 -5
- package/src/prompts/tools/bash.md +2 -3
- package/src/prompts/tools/eval.md +6 -4
- package/src/prompts/tools/find.md +0 -4
- package/src/prompts/tools/read.md +1 -2
- package/src/prompts/tools/replace.md +1 -1
- package/src/sdk.ts +13 -7
- package/src/secrets/obfuscator.ts +3 -9
- package/src/session/agent-session.ts +42 -9
- package/src/slash-commands/builtin-registry.ts +2 -1
- package/src/stt/asr-client.ts +35 -215
- package/src/stt/asr-worker.ts +29 -181
- package/src/subprocess/worker-client.ts +297 -0
- package/src/subprocess/worker-runtime.ts +277 -0
- package/src/task/executor.ts +4 -4
- package/src/tiny/title-client.ts +53 -219
- package/src/tiny/worker.ts +29 -180
- package/src/tools/eval-backends.ts +10 -3
- package/src/tools/eval-render.ts +17 -8
- package/src/tools/eval.ts +187 -22
- package/src/tools/index.ts +51 -28
- package/src/tts/tts-client.ts +38 -206
- package/src/tts/tts-worker.ts +23 -97
- package/src/tui/code-cell.ts +12 -1
- package/src/utils/file-display-mode.ts +2 -3
- package/src/utils/shell-snapshot.ts +63 -1
- package/src/web/parallel.ts +43 -42
- package/src/web/search/providers/parallel.ts +10 -99
- package/src/web/search/providers/perplexity.ts +18 -6
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ProgressInfo } from "@huggingface/transformers";
|
|
4
|
+
import {
|
|
5
|
+
ensureRuntimeInstalled,
|
|
6
|
+
getTinyModelsCacheDir,
|
|
7
|
+
installRuntimeModuleResolver,
|
|
8
|
+
isCompiledBinary,
|
|
9
|
+
resolveRuntimeModule,
|
|
10
|
+
} from "@oh-my-pi/pi-utils";
|
|
11
|
+
import packageJson from "../../package.json" with { type: "json" };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Child-side scaffolding shared by the ONNX inference worker bodies
|
|
15
|
+
* (`stt/asr-worker`, `tiny/worker`, `tts/tts-worker`). These are the helpers
|
|
16
|
+
* that run inside the spawned subprocess: error serialization, structured log
|
|
17
|
+
* and progress reporting over the worker's typed transport, side-runtime
|
|
18
|
+
* install (sharp stubbing + module-resolver patch), once-per-process runtime
|
|
19
|
+
* memoization, and the Transformers.js runtime loader. The parent/client-side
|
|
20
|
+
* complement lives in `worker-client.ts`.
|
|
21
|
+
*
|
|
22
|
+
* Each worker keeps its own strongly-typed transport / model-key / progress
|
|
23
|
+
* event; the structural {@link WorkerLogTransport} / {@link WorkerProgressTransport}
|
|
24
|
+
* interfaces below are the minimal shapes these helpers need, and every worker's
|
|
25
|
+
* concrete transport satisfies them.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export const TRANSFORMERS_PACKAGE = "@huggingface/transformers";
|
|
29
|
+
const COMPILED_TRANSFORMERS_VERSION = process.env.PI_TINY_TRANSFORMERS_VERSION;
|
|
30
|
+
const sourceRequire = createRequire(import.meta.url);
|
|
31
|
+
|
|
32
|
+
// ── Error serialization ─────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export function errorText(error: unknown): string {
|
|
35
|
+
return error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function errorMessage(error: unknown): string {
|
|
39
|
+
return error instanceof Error ? error.message : String(error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Structured logging ──────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
export type WorkerLogLevel = "debug" | "warn" | "error";
|
|
45
|
+
|
|
46
|
+
/** Minimal transport surface a worker exposes for forwarding log lines. */
|
|
47
|
+
export interface WorkerLogTransport {
|
|
48
|
+
send(message: { type: "log"; level: WorkerLogLevel; msg: string; meta?: Record<string, unknown> }): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function sendLog(
|
|
52
|
+
transport: WorkerLogTransport,
|
|
53
|
+
level: WorkerLogLevel,
|
|
54
|
+
msg: string,
|
|
55
|
+
meta?: Record<string, unknown>,
|
|
56
|
+
): void {
|
|
57
|
+
transport.send({ type: "log", level, msg, meta });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Progress reporting ──────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generic worker progress event. Each worker's protocol declares an identical
|
|
64
|
+
* shape with its own `modelKey` type; this is the parameterized version the
|
|
65
|
+
* shared helpers emit, structurally assignable to each protocol's event.
|
|
66
|
+
*/
|
|
67
|
+
export interface WorkerProgressEvent<K> {
|
|
68
|
+
modelKey: K;
|
|
69
|
+
status: "initiate" | "download" | "progress" | "progress_total" | "done" | "ready" | "error";
|
|
70
|
+
name?: string;
|
|
71
|
+
file?: string;
|
|
72
|
+
progress?: number;
|
|
73
|
+
loaded?: number;
|
|
74
|
+
total?: number;
|
|
75
|
+
files?: Record<string, { loaded: number; total: number }>;
|
|
76
|
+
task?: string;
|
|
77
|
+
model?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Minimal transport surface a worker exposes for emitting progress events. */
|
|
81
|
+
export interface WorkerProgressTransport<K> {
|
|
82
|
+
send(message: { type: "progress"; id: string; event: WorkerProgressEvent<K> }): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Map a Transformers.js {@link ProgressInfo} onto the worker progress event. */
|
|
86
|
+
function toProgressEvent<K>(modelKey: K, info: ProgressInfo): WorkerProgressEvent<K> {
|
|
87
|
+
if (info.status === "ready") {
|
|
88
|
+
return { modelKey, status: info.status, task: info.task, model: info.model };
|
|
89
|
+
}
|
|
90
|
+
if (info.status === "progress_total") {
|
|
91
|
+
return {
|
|
92
|
+
modelKey,
|
|
93
|
+
status: info.status,
|
|
94
|
+
name: info.name,
|
|
95
|
+
progress: info.progress,
|
|
96
|
+
loaded: info.loaded,
|
|
97
|
+
total: info.total,
|
|
98
|
+
files: info.files,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (info.status === "progress") {
|
|
102
|
+
return {
|
|
103
|
+
modelKey,
|
|
104
|
+
status: info.status,
|
|
105
|
+
name: info.name,
|
|
106
|
+
file: info.file,
|
|
107
|
+
progress: info.progress,
|
|
108
|
+
loaded: info.loaded,
|
|
109
|
+
total: info.total,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { modelKey, status: info.status, name: info.name, file: info.file };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function sendProgress<K>(
|
|
116
|
+
transport: WorkerProgressTransport<K>,
|
|
117
|
+
id: string,
|
|
118
|
+
modelKey: K,
|
|
119
|
+
info: ProgressInfo,
|
|
120
|
+
): void {
|
|
121
|
+
transport.send({ type: "progress", id, event: toProgressEvent(modelKey, info) });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Model cache ─────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* If a model is already warming/warm in `cache`, replay a `ready` progress
|
|
128
|
+
* event for this request once it resolves and return the cached promise so the
|
|
129
|
+
* caller can short-circuit; otherwise return `undefined`.
|
|
130
|
+
*/
|
|
131
|
+
export function replayCachedReady<K, M>(
|
|
132
|
+
cache: Map<K, Promise<M>>,
|
|
133
|
+
modelKey: K,
|
|
134
|
+
transport: WorkerProgressTransport<K>,
|
|
135
|
+
requestId: string,
|
|
136
|
+
task: string,
|
|
137
|
+
model: string,
|
|
138
|
+
): Promise<M> | undefined {
|
|
139
|
+
const cached = cache.get(modelKey);
|
|
140
|
+
if (!cached) return undefined;
|
|
141
|
+
void cached
|
|
142
|
+
.then(() => {
|
|
143
|
+
transport.send({ type: "progress", id: requestId, event: { modelKey, status: "ready", task, model } });
|
|
144
|
+
})
|
|
145
|
+
.catch(() => undefined);
|
|
146
|
+
return cached;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Side-runtime install scaffolding ────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Stub `sharp` (the speech/text pipelines are not image codecs, so the native
|
|
153
|
+
* image dependency is dead weight) and patch the module resolver so a side
|
|
154
|
+
* runtime's bare requires resolve against its own `node_modules`. Returns the
|
|
155
|
+
* runtime's `node_modules` directory.
|
|
156
|
+
*/
|
|
157
|
+
export async function installSharpStubResolver(runtimeDir: string): Promise<string> {
|
|
158
|
+
const nodeModules = path.join(runtimeDir, "node_modules");
|
|
159
|
+
const sharpStub = path.join(runtimeDir, "omp-sharp-stub.cjs");
|
|
160
|
+
await Bun.write(sharpStub, "module.exports = {};\n");
|
|
161
|
+
installRuntimeModuleResolver({ runtimeNodeModules: nodeModules, stubs: { sharp: sharpStub } });
|
|
162
|
+
return nodeModules;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Prepare a freshly-installed compiled runtime for loading and return the
|
|
167
|
+
* absolute entrypoint of `packageName` to `require`.
|
|
168
|
+
*/
|
|
169
|
+
async function prepareCompiledRuntime(runtimeDir: string, packageName: string): Promise<string> {
|
|
170
|
+
const nodeModules = await installSharpStubResolver(runtimeDir);
|
|
171
|
+
const entry = resolveRuntimeModule(nodeModules, packageName);
|
|
172
|
+
if (!entry) throw new Error(`Unable to resolve ${packageName} in compiled runtime at ${nodeModules}`);
|
|
173
|
+
return entry;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Transformers version resolution ─────────────────────────────────
|
|
177
|
+
|
|
178
|
+
function resolveTransformersVersionSpec(): string {
|
|
179
|
+
const manifest = packageJson as {
|
|
180
|
+
optionalDependencies?: Record<string, string>;
|
|
181
|
+
dependencies?: Record<string, string>;
|
|
182
|
+
};
|
|
183
|
+
const versionSpec =
|
|
184
|
+
manifest.optionalDependencies?.[TRANSFORMERS_PACKAGE] ?? manifest.dependencies?.[TRANSFORMERS_PACKAGE];
|
|
185
|
+
if (!versionSpec) throw new Error(`${TRANSFORMERS_PACKAGE} is missing from package.json optionalDependencies`);
|
|
186
|
+
if (!versionSpec.startsWith("catalog:")) return versionSpec;
|
|
187
|
+
if (COMPILED_TRANSFORMERS_VERSION) return COMPILED_TRANSFORMERS_VERSION;
|
|
188
|
+
const installed = sourceRequire(`${TRANSFORMERS_PACKAGE}/package.json`) as { version: string };
|
|
189
|
+
return installed.version;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let cachedTransformersVersionSpec: string | undefined;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Lazily resolve (and memoize) the transformers version spec. In the `catalog:`
|
|
196
|
+
* case {@link resolveTransformersVersionSpec} `require`s the installed
|
|
197
|
+
* `@huggingface/transformers/package.json`, so it is only ever touched on the
|
|
198
|
+
* compiled-binary runtime-install path — loading a worker (smoke-test ping,
|
|
199
|
+
* online path) never triggers the transformers resolve/install dance.
|
|
200
|
+
*/
|
|
201
|
+
export function getTransformersVersionSpec(): string {
|
|
202
|
+
cachedTransformersVersionSpec ??= resolveTransformersVersionSpec();
|
|
203
|
+
return cachedTransformersVersionSpec;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Transformers runtime loader ─────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/** The subset of the Transformers.js module surface {@link configureTransformers} touches. */
|
|
209
|
+
interface ConfigurableTransformers {
|
|
210
|
+
env: { cacheDir?: string; allowLocalModels?: boolean; logLevel?: unknown };
|
|
211
|
+
LogLevel: { ERROR: unknown };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function configureTransformers<T extends ConfigurableTransformers>(transformers: T): T {
|
|
215
|
+
transformers.env.cacheDir = getTinyModelsCacheDir();
|
|
216
|
+
transformers.env.allowLocalModels = false;
|
|
217
|
+
transformers.env.logLevel = transformers.LogLevel.ERROR;
|
|
218
|
+
return transformers;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Memoize an async runtime load so it runs at most once per process, clearing
|
|
223
|
+
* the cache on failure so a later call can retry. Each worker holds one
|
|
224
|
+
* instance per runtime it loads.
|
|
225
|
+
*/
|
|
226
|
+
export class MemoizedRuntime<T> {
|
|
227
|
+
#promise: Promise<T> | null = null;
|
|
228
|
+
|
|
229
|
+
load(build: () => Promise<T>): Promise<T> {
|
|
230
|
+
if (this.#promise) return this.#promise;
|
|
231
|
+
const promise = build().catch(error => {
|
|
232
|
+
this.#promise = null;
|
|
233
|
+
throw error;
|
|
234
|
+
});
|
|
235
|
+
this.#promise = promise;
|
|
236
|
+
return promise;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Load the `@huggingface/transformers` runtime into `holder` (memoized): from
|
|
242
|
+
* the ambient install when running from source, or from a version-keyed side
|
|
243
|
+
* runtime (resolved lazily at `runtimeDir()`) when running as a compiled binary.
|
|
244
|
+
* The result is cast to the caller's concrete runtime type `T`.
|
|
245
|
+
*/
|
|
246
|
+
export function loadTransformersRuntime<T extends ConfigurableTransformers, K>(
|
|
247
|
+
holder: MemoizedRuntime<T>,
|
|
248
|
+
transport: WorkerProgressTransport<K>,
|
|
249
|
+
requestId: string,
|
|
250
|
+
modelKey: K,
|
|
251
|
+
runtimeDir: () => string,
|
|
252
|
+
): Promise<T> {
|
|
253
|
+
return holder.load(async () => {
|
|
254
|
+
if (!isCompiledBinary()) return configureTransformers(sourceRequire(TRANSFORMERS_PACKAGE) as T);
|
|
255
|
+
const installedDir = await ensureRuntimeInstalled({
|
|
256
|
+
runtimeDir: runtimeDir(),
|
|
257
|
+
install: {
|
|
258
|
+
dependencies: { [TRANSFORMERS_PACKAGE]: getTransformersVersionSpec() },
|
|
259
|
+
trustedDependencies: ["onnxruntime-node"],
|
|
260
|
+
},
|
|
261
|
+
probePackage: TRANSFORMERS_PACKAGE,
|
|
262
|
+
onPhase: phase =>
|
|
263
|
+
transport.send({
|
|
264
|
+
type: "progress",
|
|
265
|
+
id: requestId,
|
|
266
|
+
event: {
|
|
267
|
+
modelKey,
|
|
268
|
+
status: phase,
|
|
269
|
+
name: `${TRANSFORMERS_PACKAGE}@${getTransformersVersionSpec()}`,
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
273
|
+
const entry = await prepareCompiledRuntime(installedDir, TRANSFORMERS_PACKAGE);
|
|
274
|
+
const require_ = createRequire(entry);
|
|
275
|
+
return configureTransformers(require_(entry) as T);
|
|
276
|
+
});
|
|
277
|
+
}
|
package/src/task/executor.ts
CHANGED
|
@@ -41,7 +41,8 @@ import type { AuthStorage } from "../session/auth-storage";
|
|
|
41
41
|
import { SKILL_PROMPT_MESSAGE_TYPE, USER_INTERRUPT_LABEL } from "../session/messages";
|
|
42
42
|
import { SessionManager } from "../session/session-manager";
|
|
43
43
|
import { truncateTail } from "../session/streaming-output";
|
|
44
|
-
import type { ContextFileEntry } from "../tools";
|
|
44
|
+
import type { ContextFileEntry, ToolSession } from "../tools";
|
|
45
|
+
import { resolveEvalBackends } from "../tools/eval-backends";
|
|
45
46
|
import { isIrcEnabled } from "../tools/irc";
|
|
46
47
|
import { normalizeSchema } from "../tools/jtd-to-json-schema";
|
|
47
48
|
import {
|
|
@@ -1790,10 +1791,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1790
1791
|
toolNames = [...toolNames, "irc"];
|
|
1791
1792
|
}
|
|
1792
1793
|
if (toolNames?.includes("exec")) {
|
|
1793
|
-
const
|
|
1794
|
-
const allowEvalJs = settings.get("eval.js") ?? true;
|
|
1794
|
+
const backends = resolveEvalBackends({ settings } as ToolSession);
|
|
1795
1795
|
const expanded = toolNames.filter(name => name !== "exec");
|
|
1796
|
-
if (
|
|
1796
|
+
if (backends.python || backends.js || backends.ruby || backends.julia) expanded.push("eval");
|
|
1797
1797
|
expanded.push("bash");
|
|
1798
1798
|
toolNames = Array.from(new Set(expanded));
|
|
1799
1799
|
}
|
package/src/tiny/title-client.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { $env, isBunTestRuntime, isCompiledBinary, logger, workerHostEntry } from "@oh-my-pi/pi-utils";
|
|
3
|
-
import type { Subprocess } from "bun";
|
|
1
|
+
import { $env, logger } from "@oh-my-pi/pi-utils";
|
|
4
2
|
import { settings } from "../config/settings";
|
|
3
|
+
import {
|
|
4
|
+
createUnavailableWorker,
|
|
5
|
+
createWorkerHandle,
|
|
6
|
+
createWorkerSubprocess,
|
|
7
|
+
logWorkerMessage,
|
|
8
|
+
resolveWorkerSpawnCmd,
|
|
9
|
+
SMOKE_TEST_TIMEOUT_MS,
|
|
10
|
+
type SpawnedSubprocess,
|
|
11
|
+
smokeTestWorker,
|
|
12
|
+
spawnWorkerOrUnavailable,
|
|
13
|
+
type WorkerHandle,
|
|
14
|
+
workerEnvFromParent,
|
|
15
|
+
} from "../subprocess/worker-client";
|
|
5
16
|
import { safeSend } from "../utils/ipc";
|
|
6
17
|
import { tinyModelDeviceSettingToEnv } from "./device";
|
|
7
18
|
import { tinyModelDtypeSettingToEnv } from "./dtype";
|
|
@@ -15,21 +26,6 @@ import {
|
|
|
15
26
|
} from "./models";
|
|
16
27
|
import type { TinyTitleProgressEvent, TinyTitleWorkerInbound, TinyTitleWorkerOutbound } from "./title-protocol";
|
|
17
28
|
|
|
18
|
-
/**
|
|
19
|
-
* Abstraction over the tiny-model subprocess. Modelled as a worker interface
|
|
20
|
-
* so existing callers (titles, memory completions, downloads) compose the
|
|
21
|
-
* same way; the runtime implementation is a Bun child process so
|
|
22
|
-
* `onnxruntime-node`'s NAPI finalizer never runs inside the main agent
|
|
23
|
-
* address space — that destructor segfaults Bun on Windows during shutdown
|
|
24
|
-
* (issue #1606).
|
|
25
|
-
*/
|
|
26
|
-
interface WorkerHandle {
|
|
27
|
-
send(message: TinyTitleWorkerInbound): void;
|
|
28
|
-
onMessage(handler: (message: TinyTitleWorkerOutbound) => void): () => void;
|
|
29
|
-
onError(handler: (error: Error) => void): () => void;
|
|
30
|
-
terminate(): Promise<void>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
type PendingRequest =
|
|
34
30
|
| { kind: "generate"; modelKey: TinyTitleLocalModelKey; resolve: (title: string | null) => void }
|
|
35
31
|
| { kind: "complete"; modelKey: TinyMemoryLocalModelKey; resolve: (text: string | null) => void }
|
|
@@ -51,13 +47,6 @@ export interface TinyTitleGenerateOptions {
|
|
|
51
47
|
systemPrompt?: string;
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
// Cold-starting the worker subprocess from a compiled binary (decompress + module
|
|
55
|
-
// graph load) is slow on contended CI runners — the macos-15-intel release smoke
|
|
56
|
-
// blew past 5s while arm64/linux/win passed. The probe only needs to prove the
|
|
57
|
-
// worker spawns and ponges at all (a dead worker never ponges regardless), so a
|
|
58
|
-
// generous bound removes the flake without weakening the check.
|
|
59
|
-
const SMOKE_TEST_TIMEOUT_MS = 30_000;
|
|
60
|
-
|
|
61
50
|
function normalizeTinyTitleGenerateOptions(
|
|
62
51
|
options: AbortSignal | TinyTitleGenerateOptions | undefined,
|
|
63
52
|
): TinyTitleGenerateOptions {
|
|
@@ -107,201 +96,66 @@ export function tinyWorkerEnvOverlay(
|
|
|
107
96
|
}
|
|
108
97
|
|
|
109
98
|
/**
|
|
110
|
-
* Env handed to the tiny-model subprocess
|
|
111
|
-
*
|
|
112
|
-
* `
|
|
113
|
-
*
|
|
114
|
-
*
|
|
99
|
+
* Env handed to the tiny-model subprocess — and reused verbatim by the STT and
|
|
100
|
+
* TTS workers, which share the same device/dtype resolution. The
|
|
101
|
+
* `PI_TINY_DEVICE` / `PI_TINY_DTYPE` env vars win; otherwise the persisted
|
|
102
|
+
* `providers.tinyModelDevice` / `providers.tinyModelDtype` settings are mapped
|
|
103
|
+
* onto those vars so the subprocess's env-based resolution picks them up.
|
|
104
|
+
* Resolved once at spawn (pipelines are cached for the lifetime of the
|
|
105
|
+
* subprocess).
|
|
115
106
|
*/
|
|
116
|
-
function tinyWorkerEnv(): Record<string, string> {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
export function tinyWorkerEnv(): Record<string, string> {
|
|
108
|
+
return workerEnvFromParent(
|
|
109
|
+
tinyWorkerEnvOverlay(
|
|
110
|
+
$env,
|
|
111
|
+
readTinyModelSetting("providers.tinyModelDevice"),
|
|
112
|
+
readTinyModelSetting("providers.tinyModelDtype"),
|
|
113
|
+
),
|
|
121
114
|
);
|
|
122
|
-
const base = $env as Record<string, string | undefined>;
|
|
123
|
-
const merged: Record<string, string> = {};
|
|
124
|
-
for (const key in base) {
|
|
125
|
-
const value = base[key];
|
|
126
|
-
if (typeof value === "string") merged[key] = value;
|
|
127
|
-
}
|
|
128
|
-
for (const key in overlay) merged[key] = overlay[key];
|
|
129
|
-
return merged;
|
|
130
|
-
}
|
|
131
|
-
interface TinyWorkerSpawnCommand {
|
|
132
|
-
cmd: string[];
|
|
133
|
-
cwd?: string;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Resolve the command used to relaunch the agent CLI into tiny-worker mode.
|
|
138
|
-
* In a compiled binary the entry point is the binary itself (no script arg).
|
|
139
|
-
* Otherwise re-enter the declared worker-host entry (source cli.ts or
|
|
140
|
-
* npm-bundle cli.js) with a cwd-relative script path — Bun's subprocess IPC
|
|
141
|
-
* is more reliable that way than with an absolute `.ts` entry under
|
|
142
|
-
* `bun test` — and fall back to this package's own `src/cli.ts` when no host
|
|
143
|
-
* entry is declared (bun test, SDK embedding).
|
|
144
|
-
*/
|
|
145
|
-
function tinyWorkerSpawnCmd(): TinyWorkerSpawnCommand {
|
|
146
|
-
if (isCompiledBinary()) return { cmd: [process.execPath, TINY_WORKER_ARG] };
|
|
147
|
-
const hostEntry = workerHostEntry();
|
|
148
|
-
if (hostEntry) {
|
|
149
|
-
return { cmd: [process.execPath, path.basename(hostEntry), TINY_WORKER_ARG], cwd: path.dirname(hostEntry) };
|
|
150
|
-
}
|
|
151
|
-
const packageRoot = path.resolve(import.meta.dir, "..", "..");
|
|
152
|
-
return { cmd: [process.execPath, "src/cli.ts", TINY_WORKER_ARG], cwd: packageRoot };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
interface SpawnedSubprocess {
|
|
156
|
-
proc: Subprocess<"ignore", "ignore", "ignore">;
|
|
157
|
-
inbound: Set<(message: TinyTitleWorkerOutbound) => void>;
|
|
158
|
-
errors: Set<(error: Error) => void>;
|
|
159
|
-
/**
|
|
160
|
-
* Flipped to `true` by {@link wrapSubprocess}'s `terminate()` right
|
|
161
|
-
* before it SIGKILLs the child so `onExit` can distinguish the
|
|
162
|
-
* expected hard-kill from a crash/OOM/external signal. Only the
|
|
163
|
-
* latter is surfaced as a worker error.
|
|
164
|
-
*/
|
|
165
|
-
intentionalExit: { value: boolean };
|
|
166
115
|
}
|
|
167
116
|
|
|
168
117
|
/**
|
|
169
118
|
* Spawn the tiny-model worker as a subprocess. Exported for tests and the
|
|
170
|
-
* smoke probe; production callers go through {@link spawnTinyTitleWorker}
|
|
171
|
-
* which wraps the result in a {@link WorkerHandle}.
|
|
119
|
+
* smoke probe; production callers go through {@link spawnTinyTitleWorker}.
|
|
172
120
|
*/
|
|
173
|
-
export function createTinyTitleSubprocess(): SpawnedSubprocess {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const intentionalExit = { value: false };
|
|
177
|
-
const spawnCommand = tinyWorkerSpawnCmd();
|
|
178
|
-
const proc = Bun.spawn({
|
|
179
|
-
cmd: spawnCommand.cmd,
|
|
180
|
-
cwd: spawnCommand.cwd,
|
|
121
|
+
export function createTinyTitleSubprocess(): SpawnedSubprocess<TinyTitleWorkerOutbound> {
|
|
122
|
+
return createWorkerSubprocess<TinyTitleWorkerOutbound>({
|
|
123
|
+
spawnCommand: resolveWorkerSpawnCmd(TINY_WORKER_ARG),
|
|
181
124
|
env: tinyWorkerEnv(),
|
|
182
|
-
|
|
183
|
-
stdout: "ignore",
|
|
184
|
-
stderr: "ignore",
|
|
185
|
-
serialization: "advanced",
|
|
186
|
-
windowsHide: true,
|
|
187
|
-
// The worker is an implementation detail of the interactive TUI. Native
|
|
188
|
-
// model runtimes may print progress or decoded text directly; never let
|
|
189
|
-
// those bytes inherit the terminal and corrupt the chat scrollback.
|
|
190
|
-
ipc(message) {
|
|
191
|
-
for (const handler of inbound) handler(message as TinyTitleWorkerOutbound);
|
|
192
|
-
},
|
|
193
|
-
onExit(_proc, exitCode, signalCode) {
|
|
194
|
-
// Clean exit. The child only exits via SIGKILL in practice, but
|
|
195
|
-
// treat code 0 as a no-op for symmetry.
|
|
196
|
-
if (exitCode === 0) return;
|
|
197
|
-
// `exitCode === null` + non-null `signalCode` covers both the
|
|
198
|
-
// expected SIGKILL from `terminate()` AND external kills
|
|
199
|
-
// (SIGSEGV from a native crash, SIGKILL from the OOM killer, an
|
|
200
|
-
// operator `kill -9`, etc.). Swallow only the expected one;
|
|
201
|
-
// every other signal exit is a real worker death that must
|
|
202
|
-
// fault every in-flight request so callers don't await forever.
|
|
203
|
-
if (exitCode === null && intentionalExit.value) return;
|
|
204
|
-
const reason = exitCode !== null ? `code ${exitCode}` : `signal ${signalCode ?? "unknown"}`;
|
|
205
|
-
const err = new Error(`tiny model subprocess exited with ${reason}`);
|
|
206
|
-
for (const handler of errors) handler(err);
|
|
207
|
-
},
|
|
125
|
+
exitLabel: "tiny model subprocess",
|
|
208
126
|
});
|
|
209
|
-
// Don't keep the parent event loop alive on account of an idle worker; the
|
|
210
|
-
// agent dispose path calls `terminate()` explicitly when shutting down.
|
|
211
|
-
// Bun's test runner can starve IPC delivery for unref'd subprocesses, so
|
|
212
|
-
// keep it referenced only under tests that assert the ping/pong contract.
|
|
213
|
-
if (!isBunTestRuntime()) proc.unref();
|
|
214
|
-
return { proc, inbound, errors, intentionalExit };
|
|
215
127
|
}
|
|
216
128
|
|
|
217
|
-
function wrapSubprocess(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return () => inbound.delete(handler);
|
|
225
|
-
},
|
|
226
|
-
onError(handler) {
|
|
227
|
-
errors.add(handler);
|
|
228
|
-
return () => errors.delete(handler);
|
|
229
|
-
},
|
|
230
|
-
async terminate() {
|
|
231
|
-
// SIGKILL: the whole point of the subprocess isolation is that the
|
|
232
|
-
// parent never runs `onnxruntime-node`'s NAPI finalizer. A polite
|
|
233
|
-
// SIGTERM lets the subprocess try to clean up, which is exactly the
|
|
234
|
-
// codepath that crashes Bun on Windows. Hard-kill instead — the
|
|
235
|
-
// model lives in process memory and the OS reclaims everything.
|
|
236
|
-
// Flip the intentional-exit flag *before* killing so `onExit` can
|
|
237
|
-
// tell this apart from a crash or external SIGKILL.
|
|
238
|
-
intentionalExit.value = true;
|
|
239
|
-
try {
|
|
240
|
-
proc.kill("SIGKILL");
|
|
241
|
-
} catch {
|
|
242
|
-
// Already gone.
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function spawnInlineUnavailableWorker(error: unknown): WorkerHandle {
|
|
249
|
-
const listeners = new Set<(message: TinyTitleWorkerOutbound) => void>();
|
|
250
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
251
|
-
const emit = (message: TinyTitleWorkerOutbound): void => {
|
|
252
|
-
for (const listener of listeners) listener(message);
|
|
253
|
-
};
|
|
254
|
-
return {
|
|
255
|
-
send(message) {
|
|
256
|
-
queueMicrotask(() => {
|
|
257
|
-
if (message.type === "ping") {
|
|
258
|
-
emit({ type: "pong", id: message.id });
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
emit({ type: "error", id: message.id, error: errorMessage });
|
|
262
|
-
});
|
|
263
|
-
},
|
|
264
|
-
onMessage(handler) {
|
|
265
|
-
listeners.add(handler);
|
|
266
|
-
return () => listeners.delete(handler);
|
|
267
|
-
},
|
|
268
|
-
onError() {
|
|
269
|
-
return () => {};
|
|
270
|
-
},
|
|
271
|
-
async terminate() {
|
|
272
|
-
listeners.clear();
|
|
273
|
-
},
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function spawnTinyTitleWorker(): WorkerHandle {
|
|
278
|
-
try {
|
|
279
|
-
return wrapSubprocess(createTinyTitleSubprocess());
|
|
280
|
-
} catch (error) {
|
|
281
|
-
logger.warn("Tiny title worker spawn failed; local titles disabled", {
|
|
282
|
-
error: error instanceof Error ? error.message : String(error),
|
|
283
|
-
});
|
|
284
|
-
return spawnInlineUnavailableWorker(error);
|
|
285
|
-
}
|
|
129
|
+
function wrapSubprocess(
|
|
130
|
+
spawned: SpawnedSubprocess<TinyTitleWorkerOutbound>,
|
|
131
|
+
): WorkerHandle<TinyTitleWorkerInbound, TinyTitleWorkerOutbound> {
|
|
132
|
+
const { proc } = spawned;
|
|
133
|
+
return createWorkerHandle<TinyTitleWorkerInbound, TinyTitleWorkerOutbound>(spawned, message =>
|
|
134
|
+
safeSend(proc, message, "tiny-title"),
|
|
135
|
+
);
|
|
286
136
|
}
|
|
287
137
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
138
|
+
function spawnTinyTitleWorker(): WorkerHandle<TinyTitleWorkerInbound, TinyTitleWorkerOutbound> {
|
|
139
|
+
return spawnWorkerOrUnavailable(
|
|
140
|
+
() => wrapSubprocess(createTinyTitleSubprocess()),
|
|
141
|
+
createUnavailableWorker<TinyTitleWorkerInbound, TinyTitleWorkerOutbound>,
|
|
142
|
+
"Tiny title worker spawn failed; local titles disabled",
|
|
143
|
+
);
|
|
292
144
|
}
|
|
293
145
|
|
|
294
146
|
export class TinyTitleClient {
|
|
295
|
-
#worker: WorkerHandle | null = null;
|
|
147
|
+
#worker: WorkerHandle<TinyTitleWorkerInbound, TinyTitleWorkerOutbound> | null = null;
|
|
296
148
|
#unsubscribeMessage: (() => void) | null = null;
|
|
297
149
|
#unsubscribeError: (() => void) | null = null;
|
|
298
150
|
#pending = new Map<string, PendingRequest>();
|
|
299
151
|
#failedModels = new Set<TinyLocalModelKey>();
|
|
300
152
|
#progressListeners = new Set<(event: TinyTitleProgressEvent) => void>();
|
|
301
153
|
#nextRequestId = 0;
|
|
302
|
-
#spawnWorker: () => WorkerHandle
|
|
154
|
+
#spawnWorker: () => WorkerHandle<TinyTitleWorkerInbound, TinyTitleWorkerOutbound>;
|
|
303
155
|
|
|
304
|
-
constructor(
|
|
156
|
+
constructor(
|
|
157
|
+
spawnWorker: () => WorkerHandle<TinyTitleWorkerInbound, TinyTitleWorkerOutbound> = spawnTinyTitleWorker,
|
|
158
|
+
) {
|
|
305
159
|
this.#spawnWorker = spawnWorker;
|
|
306
160
|
}
|
|
307
161
|
|
|
@@ -443,7 +297,7 @@ export class TinyTitleClient {
|
|
|
443
297
|
}
|
|
444
298
|
}
|
|
445
299
|
|
|
446
|
-
#ensureWorker(): WorkerHandle {
|
|
300
|
+
#ensureWorker(): WorkerHandle<TinyTitleWorkerInbound, TinyTitleWorkerOutbound> {
|
|
447
301
|
if (this.#worker) return this.#worker;
|
|
448
302
|
const worker = this.#spawnWorker();
|
|
449
303
|
this.#worker = worker;
|
|
@@ -520,25 +374,5 @@ export async function smokeTestTinyTitleWorker({
|
|
|
520
374
|
}: {
|
|
521
375
|
timeoutMs?: number;
|
|
522
376
|
} = {}): Promise<void> {
|
|
523
|
-
|
|
524
|
-
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
525
|
-
const timer = setTimeout(() => reject(new Error(`tiny title worker did not pong within ${timeoutMs}ms`)), timeoutMs);
|
|
526
|
-
const unsubscribeMessage = handle.onMessage(message => {
|
|
527
|
-
if (message.type === "pong") {
|
|
528
|
-
resolve();
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
if (message.type === "log") return;
|
|
532
|
-
reject(new Error(`tiny title worker: expected pong, got ${JSON.stringify(message)}`));
|
|
533
|
-
});
|
|
534
|
-
const unsubscribeError = handle.onError(reject);
|
|
535
|
-
try {
|
|
536
|
-
handle.send({ type: "ping", id: "smoke" } satisfies TinyTitleWorkerInbound);
|
|
537
|
-
await promise;
|
|
538
|
-
} finally {
|
|
539
|
-
clearTimeout(timer);
|
|
540
|
-
unsubscribeMessage();
|
|
541
|
-
unsubscribeError();
|
|
542
|
-
await handle.terminate();
|
|
543
|
-
}
|
|
377
|
+
await smokeTestWorker(wrapSubprocess(createTinyTitleSubprocess()), "tiny title worker", timeoutMs);
|
|
544
378
|
}
|