@adhd/sox-embedding-provider 0.5.1 → 0.5.3
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/README.md +44 -3
- package/dist/embedHostConfig.d.ts +161 -0
- package/dist/embedHostConfig.d.ts.map +1 -0
- package/dist/embedHostConfig.js +234 -0
- package/dist/embedHostConfig.js.map +1 -0
- package/dist/embedHostMain.d.ts +59 -0
- package/dist/embedHostMain.d.ts.map +1 -0
- package/dist/embedHostMain.js +223 -0
- package/dist/embedHostMain.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/fastembed.d.ts.map +1 -1
- package/dist/fastembed.js +3 -0
- package/dist/fastembed.js.map +1 -1
- package/dist/fastembedProcessHost.d.ts.map +1 -1
- package/dist/fastembedProcessHost.js +11 -10
- package/dist/fastembedProcessHost.js.map +1 -1
- package/dist/funnelClient.d.ts +97 -0
- package/dist/funnelClient.d.ts.map +1 -0
- package/dist/funnelClient.js +320 -0
- package/dist/funnelClient.js.map +1 -0
- package/dist/index.d.ts +35 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -39
- package/dist/index.js.map +1 -1
- package/dist/onnxStderrFilter.d.ts +2 -1
- package/dist/onnxStderrFilter.d.ts.map +1 -1
- package/dist/onnxStderrFilter.js +2 -1
- package/dist/onnxStderrFilter.js.map +1 -1
- package/dist/package.json +8 -2
- package/dist/sharedFastembedProcess.d.ts +59 -32
- package/dist/sharedFastembedProcess.d.ts.map +1 -1
- package/dist/sharedFastembedProcess.js +100 -41
- package/dist/sharedFastembedProcess.js.map +1 -1
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -43,6 +43,8 @@ interface EmbeddingProviderConfig {
|
|
|
43
43
|
type: string; // 'fastembed' | 'remote'
|
|
44
44
|
model: string;
|
|
45
45
|
options?: Record<string, unknown>;
|
|
46
|
+
host?: 'shared' | 'private'; // default 'shared' — see "Host posture" below
|
|
47
|
+
idleGraceMs?: number; // how long an idle shared host lingers (default 30000)
|
|
46
48
|
}
|
|
47
49
|
```
|
|
48
50
|
|
|
@@ -107,9 +109,48 @@ Built-in models (`modelId` → dimensions):
|
|
|
107
109
|
| `bge-m3` | 1024 | 8192 | 100+ languages, long context, ~570M params |
|
|
108
110
|
| `codexembed-400m` | 1024 | 8192 | code-only, ~1.6GB RAM, long context |
|
|
109
111
|
|
|
110
|
-
fastembed inference is routed through
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
fastembed inference is routed through **one machine-wide, peer-spawned, self-reaping host
|
|
113
|
+
process** per `(model, execution-provider, cacheDir)` — not one ONNX host per consumer process.
|
|
114
|
+
The first consumer to need it peer-spawns the host through the service-proxy's `ensureBackend()`
|
|
115
|
+
singleton spawn-lock; every other consumer on the box dials that same host over a Unix domain
|
|
116
|
+
socket. The host is compute-only (it holds no store connection) and it **reaps itself**: a
|
|
117
|
+
debounced, ref-counted teardown retires it `idleGraceMs` after the last client disconnects and its
|
|
118
|
+
last in-flight request drains. There is no supervised service and no daemon.
|
|
119
|
+
|
|
120
|
+
#### Host posture
|
|
121
|
+
|
|
122
|
+
`EmbeddingProviderConfig.host` is a typed closed union (default `'shared'`), applied
|
|
123
|
+
process-wide before the accessor is constructed and reported in `health().host`:
|
|
124
|
+
|
|
125
|
+
- `'shared'` — funnel through the machine-wide host above. `getSharedFastembedProcess()` returns a
|
|
126
|
+
`FunneledFastembedClient`; its `terminate()` is a **no-op** (a consumer must never kill a host
|
|
127
|
+
other consumers are using).
|
|
128
|
+
- `'private'` — the pre-funnel per-process fork (CI/diagnostics). `getSharedFastembedProcess()`
|
|
129
|
+
returns the private pool directly.
|
|
130
|
+
|
|
131
|
+
`EmbeddingProviderConfig.idleGraceMs` (typed config, default `DEFAULT_EMBED_HOST_IDLE_GRACE_MS` =
|
|
132
|
+
30 s) sets how long a zero-client host lingers before it reaps itself. A host that fails to come up
|
|
133
|
+
throws a typed `TransientEmbeddingError` naming the socket — it never silently falls back to a
|
|
134
|
+
private host.
|
|
135
|
+
|
|
136
|
+
#### Lifecycle helpers
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import {
|
|
140
|
+
getPrivateFastembedProcess, // the PRIVATE (un-funneled) per-process pool
|
|
141
|
+
resetSharedFastembedHost, // heal: ask the live shared host to re-fork its private pool
|
|
142
|
+
getSharedFastembedProcess, // the host-aware accessor (shared by default)
|
|
143
|
+
FunneledFastembedClient, // what getSharedFastembedProcess() returns under 'shared'
|
|
144
|
+
} from '@adhd/sox-embedding-provider';
|
|
145
|
+
|
|
146
|
+
const client = getSharedFastembedProcess(); // FunneledFastembedClient under the default
|
|
147
|
+
const vec = await client.request({ type: 'embed', text: 'hello' });
|
|
148
|
+
console.log(client.started, client.pendingCount, client.hostSocketPath);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`resetSharedFastembedHost()` is the recovery path for a wedged private child: under `'shared'` it
|
|
152
|
+
asks the live host to tear down and re-fork its private ONNX pool (without killing the host other
|
|
153
|
+
consumers share); under `'private'` the accessor's `terminate()` path is unchanged.
|
|
113
154
|
|
|
114
155
|
### remote — call an HTTP embedding endpoint
|
|
115
156
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* embedHostConfig.ts — config + stable socket/singleton-key resolution for the
|
|
3
|
+
* embedding funnel (SPEC-EMBEDDING-FUNNEL.md §B).
|
|
4
|
+
*
|
|
5
|
+
* The funnel collapses N consumer processes onto ONE peer-spawned, self-reaping
|
|
6
|
+
* ONNX host per `(model, execution-provider, cacheDir)`. That requires a
|
|
7
|
+
* deterministic, machine-wide identity for "the one host for this workload":
|
|
8
|
+
*
|
|
9
|
+
* - {@link embedHostSingletonKey} — the [def:singleton-key] every consumer
|
|
10
|
+
* computes identically, so `ensureBackend()`'s O_EXCL spawn-lock collapses a
|
|
11
|
+
* thundering herd to one spawn.
|
|
12
|
+
* - {@link embedHostSocketPath} — the UDS path derived from that key via
|
|
13
|
+
* `backendSocketPath()` (the SAME derivation the service-proxy uses, so
|
|
14
|
+
* there is never a port-selection problem and never a clash).
|
|
15
|
+
*
|
|
16
|
+
* ── Host selection AND the idle bound are typed config, never env toggles ──────
|
|
17
|
+
*
|
|
18
|
+
* `host` is a closed union (`'shared' | 'private'`) carried on
|
|
19
|
+
* `EmbeddingProviderConfig.host` and reported in `health()`. `idleGraceMs` is the
|
|
20
|
+
* same shape: a typed field on `EmbeddingProviderConfig.idleGraceMs` (applied by
|
|
21
|
+
* `createEmbeddingProvider()`), reported in the host's `embedding.health`, and
|
|
22
|
+
* carried on the resolved `EmbedHostConfig` the SPAWNER reads and forwards to the
|
|
23
|
+
* spawned host. There is deliberately NO `SOX_EMBED_HOST=shared|private`
|
|
24
|
+
* variable: a behavior switch must be visible, validated, and auditable, not an
|
|
25
|
+
* ambient string.
|
|
26
|
+
*
|
|
27
|
+
* `SOX_EMBED_HOST_IDLE_GRACE_MS` still exists, but it is the INTERNAL
|
|
28
|
+
* cross-process transport between the spawner and the spawned host — the spawner
|
|
29
|
+
* writes the resolved `EmbedHostConfig.idleGraceMs` into the child's env and the
|
|
30
|
+
* host consumes it. It is not the public configuration surface; set the typed
|
|
31
|
+
* `idleGraceMs` (or `configureEmbedHostIdleGraceMs()`) instead. (Owner directive:
|
|
32
|
+
* "the time bound should be configurable" — typed config, not an ADR-0013 D3 env
|
|
33
|
+
* tuning constant. This supersedes ADR-0020 D2's env-tuning framing.)
|
|
34
|
+
*
|
|
35
|
+
* The remaining env reads are ADR-0013 D5 shapes:
|
|
36
|
+
*
|
|
37
|
+
* - `SOX_ECOSYSTEM_HOME` — host config (D5): where the socket lives.
|
|
38
|
+
* - `SOX_EMBED_HOST_MAIN` / `SOX_EMBED_HOST_SOCKET` — host-injected transport
|
|
39
|
+
* config (D5) / test seams; they select a path, they never enable a feature.
|
|
40
|
+
*
|
|
41
|
+
* Leaf module — node builtins only (fs, os, path, url, crypto, module) plus
|
|
42
|
+
* `backendSocketPath` from `@adhd/sox-service-proxy`.
|
|
43
|
+
*/
|
|
44
|
+
/** The host-selection closed union (ADR-0013). `'shared'` is the default. */
|
|
45
|
+
export type EmbedHostMode = 'shared' | 'private';
|
|
46
|
+
/** Resolved funnel configuration. */
|
|
47
|
+
export interface EmbedHostConfig {
|
|
48
|
+
/** `'shared'` (default): funnel through the peer-spawned host. `'private'`: the pre-funnel per-process fork (CI/diagnostics). */
|
|
49
|
+
host: EmbedHostMode;
|
|
50
|
+
/** The directory the host UDS is created under (ADR-0004: `$SOX_ECOSYSTEM_HOME/run`). */
|
|
51
|
+
socketDir: string;
|
|
52
|
+
/**
|
|
53
|
+
* How long the host lingers with zero clients and zero in-flight work before
|
|
54
|
+
* it reaps itself. Typed config (owner directive: "the time bound should be
|
|
55
|
+
* configurable"): the SPAWNER reads this resolved value and forwards it to the
|
|
56
|
+
* spawned host, which consumes it. Set it via
|
|
57
|
+
* `EmbeddingProviderConfig.idleGraceMs` / `configureEmbedHostIdleGraceMs()`.
|
|
58
|
+
*/
|
|
59
|
+
idleGraceMs: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Bumped whenever the host's wire protocol or behavior changes incompatibly.
|
|
63
|
+
*
|
|
64
|
+
* Open question #4 in SPEC-EMBEDDING-FUNNEL.md ("a running host is an old
|
|
65
|
+
* build") is resolved with this constant rather than the npm package version.
|
|
66
|
+
* Rationale: the real hazard is a host whose `embedding.*` contract the new
|
|
67
|
+
* consumer cannot speak — a *protocol* change — not every cosmetic patch. Keying
|
|
68
|
+
* on the npm version would spawn a brand-new host on every patch bump (briefly
|
|
69
|
+
* two hosts, defeating the funnel across an upgrade window) while the explicit
|
|
70
|
+
* protocol version forces a new socket exactly when compatibility actually
|
|
71
|
+
* breaks. Bump this with any change to the `embedding.*` method set, the payload
|
|
72
|
+
* shape, or the response shape in `embedHostMain.ts`.
|
|
73
|
+
*/
|
|
74
|
+
export declare const EMBED_HOST_PROTOCOL_VERSION = 1;
|
|
75
|
+
/** Default grace before a zero-client host reaps itself. Sized to the short-lived-CLI arrival cadence. */
|
|
76
|
+
export declare const DEFAULT_EMBED_HOST_IDLE_GRACE_MS = 30000;
|
|
77
|
+
/**
|
|
78
|
+
* The internal cross-process transport variable the spawner uses to hand the
|
|
79
|
+
* resolved `EmbedHostConfig.idleGraceMs` to the spawned host. Not a public knob
|
|
80
|
+
* — set the typed `idleGraceMs` instead.
|
|
81
|
+
*/
|
|
82
|
+
export declare const EMBED_HOST_IDLE_GRACE_ENV = "SOX_EMBED_HOST_IDLE_GRACE_MS";
|
|
83
|
+
/**
|
|
84
|
+
* Apply the typed `host` field from an `EmbeddingProviderConfig`. Passing
|
|
85
|
+
* `undefined` leaves the current posture unchanged (so a `remote` provider
|
|
86
|
+
* without a `host` field never resets a deliberate `'private'` selection).
|
|
87
|
+
*/
|
|
88
|
+
export declare function configureEmbedHostHost(host: EmbedHostMode | undefined): void;
|
|
89
|
+
/**
|
|
90
|
+
* Apply the typed `idleGraceMs` field from an `EmbeddingProviderConfig`. Passing
|
|
91
|
+
* `undefined` leaves the current value unchanged (so a provider without the field
|
|
92
|
+
* never resets a deliberate selection). A non-positive or non-finite value is
|
|
93
|
+
* rejected loudly — never silently treated as "off".
|
|
94
|
+
*/
|
|
95
|
+
export declare function configureEmbedHostIdleGraceMs(idleGraceMs: number | undefined): void;
|
|
96
|
+
/** TEST-ONLY: clear the process-wide host + idle-grace overrides back to defaults. */
|
|
97
|
+
export declare function __resetEmbedHostConfigForTests(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Resolve the funnel configuration. `host` defaults to `'shared'`; `idleGraceMs`
|
|
100
|
+
* is typed config (with the transport env / hard default as fallbacks). The
|
|
101
|
+
* SPAWNER reads the resolved `idleGraceMs` and forwards it to the spawned host —
|
|
102
|
+
* see `funnelClient.ts`'s `doEnsure()` — which is what makes this field the
|
|
103
|
+
* consumed surface rather than a dead declaration.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveEmbedHostConfig(): EmbedHostConfig;
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the idle grace, in priority order:
|
|
108
|
+
*
|
|
109
|
+
* 1. the typed override (`configureEmbedHostIdleGraceMs()` /
|
|
110
|
+
* `EmbeddingProviderConfig.idleGraceMs`) — the public surface;
|
|
111
|
+
* 2. the internal transport env (`SOX_EMBED_HOST_IDLE_GRACE_ENV`) the spawner
|
|
112
|
+
* writes for the spawned host;
|
|
113
|
+
* 3. `DEFAULT_EMBED_HOST_IDLE_GRACE_MS`.
|
|
114
|
+
*
|
|
115
|
+
* A non-positive or non-numeric transport value is rejected loudly (never
|
|
116
|
+
* silently treated as "off").
|
|
117
|
+
*/
|
|
118
|
+
export declare function resolveEmbedHostIdleGraceMs(): number;
|
|
119
|
+
/**
|
|
120
|
+
* ADR-0004 D5: the host socket lives under `$SOX_ECOSYSTEM_HOME/run` (the user
|
|
121
|
+
* data root's `run/` dir). Mirrors `@adhd/sox-host-runtime`'s `runDir()` without
|
|
122
|
+
* importing it — this package is a low-tier leaf and must not gain a
|
|
123
|
+
* host-runtime dependency. `SOX_ECOSYSTEM_HOME` defaults to `~/.adhd/sox-ecosystem`
|
|
124
|
+
* exactly as `data-paths.ts`'s `DATA_SUBDIR` does.
|
|
125
|
+
*/
|
|
126
|
+
export declare function resolveEmbedHostSocketDir(): string;
|
|
127
|
+
/**
|
|
128
|
+
* The machine-wide [def:singleton-key] for the host serving `(modelId, ep,
|
|
129
|
+
* cacheDir)`. Identical inputs ⇒ identical key ⇒ identical socket ⇒ one host,
|
|
130
|
+
* across every consumer process on the box.
|
|
131
|
+
*
|
|
132
|
+
* `cacheDirDigest` is a short sha256 of the ABSOLUTE cache dir, so two stores
|
|
133
|
+
* pointed at different model caches never collide onto one host. The protocol
|
|
134
|
+
* version is appended (see {@link EMBED_HOST_PROTOCOL_VERSION}).
|
|
135
|
+
*/
|
|
136
|
+
export declare function embedHostSingletonKey(modelId: string, ep: string, cacheDir: string): string;
|
|
137
|
+
/** Derive the host UDS path from the resolved config and singleton key. */
|
|
138
|
+
export declare function embedHostSocketPath(cfg: EmbedHostConfig, key: string): string;
|
|
139
|
+
/**
|
|
140
|
+
* Resolve the runnable host entrypoint to spawn.
|
|
141
|
+
*
|
|
142
|
+
* Order:
|
|
143
|
+
* 1. `SOX_EMBED_HOST_MAIN` (D5/test seam) — a path injection, never a feature
|
|
144
|
+
* toggle. Used by the funnel teeth suite to run the host from source via a
|
|
145
|
+
* tsx shim without a prior build.
|
|
146
|
+
* 2. A `__dirname` sibling (`dist/embedHostMain.js`) — the npm AND bundled
|
|
147
|
+
* case. The literal `join(__dirname, 'embedHostMain.js')` is load-bearing:
|
|
148
|
+
* `bundle-extension.cjs`'s `verifySidecarReferences()` scans emitted files
|
|
149
|
+
* for exactly this shape, so the sidecar can never ship missing (BL-259).
|
|
150
|
+
* 3. `src/../dist/embedHostMain.js` — vitest's `src/`-resident `__dirname`.
|
|
151
|
+
* 4. `require.resolve('@adhd/sox-embedding-provider/embed-host')` — the
|
|
152
|
+
* package `exports` subpath, when self-reference is available.
|
|
153
|
+
*/
|
|
154
|
+
export declare function resolveEmbedHostMainPath(): string;
|
|
155
|
+
/**
|
|
156
|
+
* Where the detached host's stderr is redirected (its stdout/stdin are severed).
|
|
157
|
+
* Kept beside the socket so a wedged host's startup diagnostics are greppable
|
|
158
|
+
* without inheriting any caller fd ([inv:no-fd-inherit], BL-67).
|
|
159
|
+
*/
|
|
160
|
+
export declare function resolveEmbedHostStderrLogPath(cfg: EmbedHostConfig): string;
|
|
161
|
+
//# sourceMappingURL=embedHostConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedHostConfig.d.ts","sourceRoot":"","sources":["../src/embedHostConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAYH,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEjD,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,iIAAiI;IACjI,IAAI,EAAE,aAAa,CAAC;IACpB,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,0GAA0G;AAC1G,eAAO,MAAM,gCAAgC,QAAS,CAAC;AAwBvD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,iCAAiC,CAAC;AAExE;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI,CAM5E;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQnF;AAED,sFAAsF;AACtF,wBAAgB,8BAA8B,IAAI,IAAI,CAGrD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,eAAe,CAMxD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAWpD;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAIlD;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAgBjD;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAE1E"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* embedHostConfig.ts — config + stable socket/singleton-key resolution for the
|
|
3
|
+
* embedding funnel (SPEC-EMBEDDING-FUNNEL.md §B).
|
|
4
|
+
*
|
|
5
|
+
* The funnel collapses N consumer processes onto ONE peer-spawned, self-reaping
|
|
6
|
+
* ONNX host per `(model, execution-provider, cacheDir)`. That requires a
|
|
7
|
+
* deterministic, machine-wide identity for "the one host for this workload":
|
|
8
|
+
*
|
|
9
|
+
* - {@link embedHostSingletonKey} — the [def:singleton-key] every consumer
|
|
10
|
+
* computes identically, so `ensureBackend()`'s O_EXCL spawn-lock collapses a
|
|
11
|
+
* thundering herd to one spawn.
|
|
12
|
+
* - {@link embedHostSocketPath} — the UDS path derived from that key via
|
|
13
|
+
* `backendSocketPath()` (the SAME derivation the service-proxy uses, so
|
|
14
|
+
* there is never a port-selection problem and never a clash).
|
|
15
|
+
*
|
|
16
|
+
* ── Host selection AND the idle bound are typed config, never env toggles ──────
|
|
17
|
+
*
|
|
18
|
+
* `host` is a closed union (`'shared' | 'private'`) carried on
|
|
19
|
+
* `EmbeddingProviderConfig.host` and reported in `health()`. `idleGraceMs` is the
|
|
20
|
+
* same shape: a typed field on `EmbeddingProviderConfig.idleGraceMs` (applied by
|
|
21
|
+
* `createEmbeddingProvider()`), reported in the host's `embedding.health`, and
|
|
22
|
+
* carried on the resolved `EmbedHostConfig` the SPAWNER reads and forwards to the
|
|
23
|
+
* spawned host. There is deliberately NO `SOX_EMBED_HOST=shared|private`
|
|
24
|
+
* variable: a behavior switch must be visible, validated, and auditable, not an
|
|
25
|
+
* ambient string.
|
|
26
|
+
*
|
|
27
|
+
* `SOX_EMBED_HOST_IDLE_GRACE_MS` still exists, but it is the INTERNAL
|
|
28
|
+
* cross-process transport between the spawner and the spawned host — the spawner
|
|
29
|
+
* writes the resolved `EmbedHostConfig.idleGraceMs` into the child's env and the
|
|
30
|
+
* host consumes it. It is not the public configuration surface; set the typed
|
|
31
|
+
* `idleGraceMs` (or `configureEmbedHostIdleGraceMs()`) instead. (Owner directive:
|
|
32
|
+
* "the time bound should be configurable" — typed config, not an ADR-0013 D3 env
|
|
33
|
+
* tuning constant. This supersedes ADR-0020 D2's env-tuning framing.)
|
|
34
|
+
*
|
|
35
|
+
* The remaining env reads are ADR-0013 D5 shapes:
|
|
36
|
+
*
|
|
37
|
+
* - `SOX_ECOSYSTEM_HOME` — host config (D5): where the socket lives.
|
|
38
|
+
* - `SOX_EMBED_HOST_MAIN` / `SOX_EMBED_HOST_SOCKET` — host-injected transport
|
|
39
|
+
* config (D5) / test seams; they select a path, they never enable a feature.
|
|
40
|
+
*
|
|
41
|
+
* Leaf module — node builtins only (fs, os, path, url, crypto, module) plus
|
|
42
|
+
* `backendSocketPath` from `@adhd/sox-service-proxy`.
|
|
43
|
+
*/
|
|
44
|
+
import { existsSync } from 'node:fs';
|
|
45
|
+
import { createHash } from 'node:crypto';
|
|
46
|
+
import { createRequire } from 'node:module';
|
|
47
|
+
import { homedir } from 'node:os';
|
|
48
|
+
import { dirname, join } from 'node:path';
|
|
49
|
+
import { fileURLToPath } from 'node:url';
|
|
50
|
+
import { backendSocketPath } from '@adhd/sox-service-proxy';
|
|
51
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
52
|
+
/**
|
|
53
|
+
* Bumped whenever the host's wire protocol or behavior changes incompatibly.
|
|
54
|
+
*
|
|
55
|
+
* Open question #4 in SPEC-EMBEDDING-FUNNEL.md ("a running host is an old
|
|
56
|
+
* build") is resolved with this constant rather than the npm package version.
|
|
57
|
+
* Rationale: the real hazard is a host whose `embedding.*` contract the new
|
|
58
|
+
* consumer cannot speak — a *protocol* change — not every cosmetic patch. Keying
|
|
59
|
+
* on the npm version would spawn a brand-new host on every patch bump (briefly
|
|
60
|
+
* two hosts, defeating the funnel across an upgrade window) while the explicit
|
|
61
|
+
* protocol version forces a new socket exactly when compatibility actually
|
|
62
|
+
* breaks. Bump this with any change to the `embedding.*` method set, the payload
|
|
63
|
+
* shape, or the response shape in `embedHostMain.ts`.
|
|
64
|
+
*/
|
|
65
|
+
export const EMBED_HOST_PROTOCOL_VERSION = 1;
|
|
66
|
+
/** Default grace before a zero-client host reaps itself. Sized to the short-lived-CLI arrival cadence. */
|
|
67
|
+
export const DEFAULT_EMBED_HOST_IDLE_GRACE_MS = 30_000;
|
|
68
|
+
/**
|
|
69
|
+
* Process-wide host-selection override. `null` ⇒ `'shared'`.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately NOT an env var (ADR-0013): the typed
|
|
72
|
+
* `EmbeddingProviderConfig.host` field is applied here by
|
|
73
|
+
* `createEmbeddingProvider()` before the accessor singleton is constructed.
|
|
74
|
+
* Host selection is a process-wide posture — once a consumer has resolved its
|
|
75
|
+
* accessor, changing this does not retroactively re-point it (documented, not
|
|
76
|
+
* hidden).
|
|
77
|
+
*/
|
|
78
|
+
let _hostOverride = null;
|
|
79
|
+
/**
|
|
80
|
+
* Process-wide idle-grace override. `null` ⇒ resolve from the transport env (or
|
|
81
|
+
* the default). This is the TYPED public surface for the time bound: the owner
|
|
82
|
+
* directive is that the bound be configurable as config, not as an ambient
|
|
83
|
+
* ADR-0013 D3 env constant. Set via `configureEmbedHostIdleGraceMs()` (which
|
|
84
|
+
* `createEmbeddingProvider()` calls with `EmbeddingProviderConfig.idleGraceMs`)
|
|
85
|
+
* before the host is spawned.
|
|
86
|
+
*/
|
|
87
|
+
let _idleGraceOverride = null;
|
|
88
|
+
/**
|
|
89
|
+
* The internal cross-process transport variable the spawner uses to hand the
|
|
90
|
+
* resolved `EmbedHostConfig.idleGraceMs` to the spawned host. Not a public knob
|
|
91
|
+
* — set the typed `idleGraceMs` instead.
|
|
92
|
+
*/
|
|
93
|
+
export const EMBED_HOST_IDLE_GRACE_ENV = 'SOX_EMBED_HOST_IDLE_GRACE_MS';
|
|
94
|
+
/**
|
|
95
|
+
* Apply the typed `host` field from an `EmbeddingProviderConfig`. Passing
|
|
96
|
+
* `undefined` leaves the current posture unchanged (so a `remote` provider
|
|
97
|
+
* without a `host` field never resets a deliberate `'private'` selection).
|
|
98
|
+
*/
|
|
99
|
+
export function configureEmbedHostHost(host) {
|
|
100
|
+
if (host === undefined)
|
|
101
|
+
return;
|
|
102
|
+
if (host !== 'shared' && host !== 'private') {
|
|
103
|
+
throw new TypeError(`EmbedHostMode must be 'shared' or 'private', got ${String(host)}`);
|
|
104
|
+
}
|
|
105
|
+
_hostOverride = host;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Apply the typed `idleGraceMs` field from an `EmbeddingProviderConfig`. Passing
|
|
109
|
+
* `undefined` leaves the current value unchanged (so a provider without the field
|
|
110
|
+
* never resets a deliberate selection). A non-positive or non-finite value is
|
|
111
|
+
* rejected loudly — never silently treated as "off".
|
|
112
|
+
*/
|
|
113
|
+
export function configureEmbedHostIdleGraceMs(idleGraceMs) {
|
|
114
|
+
if (idleGraceMs === undefined)
|
|
115
|
+
return;
|
|
116
|
+
if (!Number.isFinite(idleGraceMs) || idleGraceMs <= 0) {
|
|
117
|
+
throw new TypeError(`EmbedHostConfig.idleGraceMs must be a positive number, got ${String(idleGraceMs)}`);
|
|
118
|
+
}
|
|
119
|
+
_idleGraceOverride = idleGraceMs;
|
|
120
|
+
}
|
|
121
|
+
/** TEST-ONLY: clear the process-wide host + idle-grace overrides back to defaults. */
|
|
122
|
+
export function __resetEmbedHostConfigForTests() {
|
|
123
|
+
_hostOverride = null;
|
|
124
|
+
_idleGraceOverride = null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve the funnel configuration. `host` defaults to `'shared'`; `idleGraceMs`
|
|
128
|
+
* is typed config (with the transport env / hard default as fallbacks). The
|
|
129
|
+
* SPAWNER reads the resolved `idleGraceMs` and forwards it to the spawned host —
|
|
130
|
+
* see `funnelClient.ts`'s `doEnsure()` — which is what makes this field the
|
|
131
|
+
* consumed surface rather than a dead declaration.
|
|
132
|
+
*/
|
|
133
|
+
export function resolveEmbedHostConfig() {
|
|
134
|
+
return {
|
|
135
|
+
host: _hostOverride ?? 'shared',
|
|
136
|
+
socketDir: resolveEmbedHostSocketDir(),
|
|
137
|
+
idleGraceMs: resolveEmbedHostIdleGraceMs(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Resolve the idle grace, in priority order:
|
|
142
|
+
*
|
|
143
|
+
* 1. the typed override (`configureEmbedHostIdleGraceMs()` /
|
|
144
|
+
* `EmbeddingProviderConfig.idleGraceMs`) — the public surface;
|
|
145
|
+
* 2. the internal transport env (`SOX_EMBED_HOST_IDLE_GRACE_ENV`) the spawner
|
|
146
|
+
* writes for the spawned host;
|
|
147
|
+
* 3. `DEFAULT_EMBED_HOST_IDLE_GRACE_MS`.
|
|
148
|
+
*
|
|
149
|
+
* A non-positive or non-numeric transport value is rejected loudly (never
|
|
150
|
+
* silently treated as "off").
|
|
151
|
+
*/
|
|
152
|
+
export function resolveEmbedHostIdleGraceMs() {
|
|
153
|
+
if (_idleGraceOverride !== null)
|
|
154
|
+
return _idleGraceOverride;
|
|
155
|
+
const raw = process.env[EMBED_HOST_IDLE_GRACE_ENV];
|
|
156
|
+
if (raw === undefined || raw === '')
|
|
157
|
+
return DEFAULT_EMBED_HOST_IDLE_GRACE_MS;
|
|
158
|
+
const parsed = Number(raw);
|
|
159
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
160
|
+
throw new TypeError(`${EMBED_HOST_IDLE_GRACE_ENV} must be a positive number, got ${JSON.stringify(raw)}`);
|
|
161
|
+
}
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* ADR-0004 D5: the host socket lives under `$SOX_ECOSYSTEM_HOME/run` (the user
|
|
166
|
+
* data root's `run/` dir). Mirrors `@adhd/sox-host-runtime`'s `runDir()` without
|
|
167
|
+
* importing it — this package is a low-tier leaf and must not gain a
|
|
168
|
+
* host-runtime dependency. `SOX_ECOSYSTEM_HOME` defaults to `~/.adhd/sox-ecosystem`
|
|
169
|
+
* exactly as `data-paths.ts`'s `DATA_SUBDIR` does.
|
|
170
|
+
*/
|
|
171
|
+
export function resolveEmbedHostSocketDir() {
|
|
172
|
+
const home = process.env['SOX_ECOSYSTEM_HOME'];
|
|
173
|
+
const root = home !== undefined && home !== '' ? home : join(homedir(), '.adhd', 'sox-ecosystem');
|
|
174
|
+
return join(root, 'run');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The machine-wide [def:singleton-key] for the host serving `(modelId, ep,
|
|
178
|
+
* cacheDir)`. Identical inputs ⇒ identical key ⇒ identical socket ⇒ one host,
|
|
179
|
+
* across every consumer process on the box.
|
|
180
|
+
*
|
|
181
|
+
* `cacheDirDigest` is a short sha256 of the ABSOLUTE cache dir, so two stores
|
|
182
|
+
* pointed at different model caches never collide onto one host. The protocol
|
|
183
|
+
* version is appended (see {@link EMBED_HOST_PROTOCOL_VERSION}).
|
|
184
|
+
*/
|
|
185
|
+
export function embedHostSingletonKey(modelId, ep, cacheDir) {
|
|
186
|
+
const digest = createHash('sha256').update(cacheDir, 'utf8').digest('hex').slice(0, 12);
|
|
187
|
+
return `embedding-host:v${EMBED_HOST_PROTOCOL_VERSION}:${modelId}:${ep}:${digest}`;
|
|
188
|
+
}
|
|
189
|
+
/** Derive the host UDS path from the resolved config and singleton key. */
|
|
190
|
+
export function embedHostSocketPath(cfg, key) {
|
|
191
|
+
return backendSocketPath(cfg.socketDir, key);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Resolve the runnable host entrypoint to spawn.
|
|
195
|
+
*
|
|
196
|
+
* Order:
|
|
197
|
+
* 1. `SOX_EMBED_HOST_MAIN` (D5/test seam) — a path injection, never a feature
|
|
198
|
+
* toggle. Used by the funnel teeth suite to run the host from source via a
|
|
199
|
+
* tsx shim without a prior build.
|
|
200
|
+
* 2. A `__dirname` sibling (`dist/embedHostMain.js`) — the npm AND bundled
|
|
201
|
+
* case. The literal `join(__dirname, 'embedHostMain.js')` is load-bearing:
|
|
202
|
+
* `bundle-extension.cjs`'s `verifySidecarReferences()` scans emitted files
|
|
203
|
+
* for exactly this shape, so the sidecar can never ship missing (BL-259).
|
|
204
|
+
* 3. `src/../dist/embedHostMain.js` — vitest's `src/`-resident `__dirname`.
|
|
205
|
+
* 4. `require.resolve('@adhd/sox-embedding-provider/embed-host')` — the
|
|
206
|
+
* package `exports` subpath, when self-reference is available.
|
|
207
|
+
*/
|
|
208
|
+
export function resolveEmbedHostMainPath() {
|
|
209
|
+
const override = process.env['SOX_EMBED_HOST_MAIN'];
|
|
210
|
+
if (override !== undefined && override !== '')
|
|
211
|
+
return override;
|
|
212
|
+
const sibling = join(__dirname, 'embedHostMain.js');
|
|
213
|
+
if (existsSync(sibling))
|
|
214
|
+
return sibling;
|
|
215
|
+
const distSibling = join(__dirname, '..', 'dist', 'embedHostMain.js');
|
|
216
|
+
if (existsSync(distSibling))
|
|
217
|
+
return distSibling;
|
|
218
|
+
try {
|
|
219
|
+
return createRequire(import.meta.url).resolve('@adhd/sox-embedding-provider/embed-host');
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Last resort: name the path that was actually attempted in any later error.
|
|
223
|
+
return sibling;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Where the detached host's stderr is redirected (its stdout/stdin are severed).
|
|
228
|
+
* Kept beside the socket so a wedged host's startup diagnostics are greppable
|
|
229
|
+
* without inheriting any caller fd ([inv:no-fd-inherit], BL-67).
|
|
230
|
+
*/
|
|
231
|
+
export function resolveEmbedHostStderrLogPath(cfg) {
|
|
232
|
+
return join(cfg.socketDir, 'logs', 'embed-host.stderr.log');
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=embedHostConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedHostConfig.js","sourceRoot":"","sources":["../src/embedHostConfig.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* embedHostMain.ts — the peer-spawned, self-reaping embedding host process
|
|
3
|
+
* (SPEC-EMBEDDING-FUNNEL.md §C).
|
|
4
|
+
*
|
|
5
|
+
* This is the compute backend of the funnel: a plain, DETACHED Node process,
|
|
6
|
+
* spawned on demand by the first consumer through `ensureBackend()`'s O_EXCL
|
|
7
|
+
* singleton spawn-lock. It is **never supervised** (no launchd/KeepAlive) and it
|
|
8
|
+
* **reaps itself** — a debounced, ref-counted teardown retires it
|
|
9
|
+
* `idleGraceMs` after the last cross-process client disconnects and in-flight
|
|
10
|
+
* work drains.
|
|
11
|
+
*
|
|
12
|
+
* ── Compute-only (ADR-0012) ────────────────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The host holds NO store connection. It forwards `embedding.*` payloads 1:1 to
|
|
15
|
+
* its private ONNX pool and never opens a database. It therefore cannot
|
|
16
|
+
* serialize store access and must never be described as single-writer or as a
|
|
17
|
+
* store serialization point; concurrent store writers are unaffected.
|
|
18
|
+
*
|
|
19
|
+
* ── No recursion ───────────────────────────────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* The handler forwards to `getPrivateFastembedProcess()` — the PRIVATE pool — and
|
|
22
|
+
* NEVER to `getSharedFastembedProcess()`, which under `host: 'shared'` would
|
|
23
|
+
* return a `FunneledFastembedClient` and dial this very host (infinite
|
|
24
|
+
* recursion). The import graph is arranged so that mistake is a type error, not
|
|
25
|
+
* a runtime hang.
|
|
26
|
+
*
|
|
27
|
+
* ── Teardown inputs are CROSS-PROCESS ─────────────────────────────────────────
|
|
28
|
+
*
|
|
29
|
+
* - `activeClients` — live UDS client connections, from `serveBackend`'s
|
|
30
|
+
* `onClientCountChange` hook (the cross-process half);
|
|
31
|
+
* - `inFlight` — THIS host's own request depth, incremented synchronously at
|
|
32
|
+
* the top of every handler invocation and decremented in its `finally` (the
|
|
33
|
+
* in-process half). It is deliberately NOT the private pool's
|
|
34
|
+
* `pendingCount`: that counter is incremented only AFTER `ensureProcess()`
|
|
35
|
+
* (the fork) resolves, so it reads 0 during a cold-start fork and would arm
|
|
36
|
+
* a reap over live work. `inFlight` is the synchronous truth.
|
|
37
|
+
*
|
|
38
|
+
* `activeClients === 0 && inFlight === 0` arms the grace timer; a new client
|
|
39
|
+
* or request cancels it. On expiry the private pool is terminated, the listener
|
|
40
|
+
* closed (which unlinks the socket), and the process exits 0. The private pool's
|
|
41
|
+
* ONNX child is forked `detached: false`, so it dies with the host — no orphans.
|
|
42
|
+
*
|
|
43
|
+
* ── The accessor is resolved at EVERY use, never captured ──────────────────────
|
|
44
|
+
*
|
|
45
|
+
* `embedding.reset` terminates AND nulls the private singleton
|
|
46
|
+
* (`resetPrivateFastembedProcess()`); a host that captured the accessor once
|
|
47
|
+
* would keep forwarding through the terminated reference and answer every later
|
|
48
|
+
* request with `shared fastembed process terminated`. Every use
|
|
49
|
+
* (the request handler, `armIfIdle`, `teardown`, `health`) therefore calls
|
|
50
|
+
* `getPrivateFastembedProcess()` fresh.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Start the host: bind the UDS, serve `embedding.*`, and arm the debounced
|
|
54
|
+
* self-reap. Resolves once the listener is up (so a caller/test can await
|
|
55
|
+
* readiness); the process stays alive until the teardown fires or a signal
|
|
56
|
+
* arrives.
|
|
57
|
+
*/
|
|
58
|
+
export declare function runEmbedHost(): Promise<void>;
|
|
59
|
+
//# sourceMappingURL=embedHostMain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedHostMain.d.ts","sourceRoot":"","sources":["../src/embedHostMain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAyBH;;;;;GAKG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA4HlD"}
|