@moxxy/sdk 0.14.4 → 0.14.5
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 +2 -0
- package/dist/compactor-helpers.d.ts +59 -0
- package/dist/compactor-helpers.d.ts.map +1 -1
- package/dist/compactor-helpers.js +72 -1
- package/dist/compactor-helpers.js.map +1 -1
- package/dist/elision-helpers.d.ts.map +1 -1
- package/dist/elision-helpers.js +12 -5
- package/dist/elision-helpers.js.map +1 -1
- package/dist/elision-state.d.ts +4 -0
- package/dist/elision-state.d.ts.map +1 -1
- package/dist/elision-state.js +41 -0
- package/dist/elision-state.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/mode/project-messages.d.ts +9 -0
- package/dist/mode/project-messages.d.ts.map +1 -1
- package/dist/mode/project-messages.js +4 -1
- package/dist/mode/project-messages.js.map +1 -1
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +21 -0
- package/dist/server.js.map +1 -0
- package/dist/transcriber.d.ts +13 -0
- package/dist/transcriber.d.ts.map +1 -1
- package/dist/transcriber.js +13 -1
- package/dist/transcriber.js.map +1 -1
- package/package.json +9 -1
- package/src/compactor-helpers.test.ts +106 -0
- package/src/compactor-helpers.ts +125 -1
- package/src/elision-helpers.ts +14 -5
- package/src/elision-state.test.ts +78 -0
- package/src/elision-state.ts +42 -0
- package/src/index.ts +13 -19
- package/src/mode/project-messages.ts +13 -1
- package/src/package-root.test.ts +37 -0
- package/src/server.ts +34 -0
- package/src/transcriber.test.ts +12 -0
- package/src/transcriber.ts +14 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-runtime-only surface of @moxxy/sdk.
|
|
3
|
+
*
|
|
4
|
+
* Importing `@moxxy/sdk/server` pulls in the value helpers that statically
|
|
5
|
+
* depend on Node builtins (`node:child_process`, `node:fs`, `node:os`,
|
|
6
|
+
* `node:http`, `node:crypto`, `node:path`). The MAIN barrel (`@moxxy/sdk`) is
|
|
7
|
+
* deliberately kept free of these so a browser/React-Native bundle can value-
|
|
8
|
+
* import from it (and from `@moxxy/sdk/tool-display`) without dragging a Node
|
|
9
|
+
* builtin into the bundle — Metro cannot polyfill `node:child_process`.
|
|
10
|
+
*
|
|
11
|
+
* Node-side consumers (cli, runner, desktop-host, channel/oauth/webhooks
|
|
12
|
+
* plugins, …) import these helpers from here. The corresponding *type* exports
|
|
13
|
+
* (e.g. `TunnelHandle`, `WriteFileAtomicOptions`, `ChannelTokenOptions`) remain
|
|
14
|
+
* on the main barrel because types are erased at build time and never reach a
|
|
15
|
+
* bundle.
|
|
16
|
+
*/
|
|
17
|
+
export { spawnCliTunnel, isCliTunnelAvailable } from './tunnel.js';
|
|
18
|
+
export { writeFileAtomic, writeFileAtomicSync, moxxyHome, moxxyPath, } from './fs-utils.js';
|
|
19
|
+
export { readRequestBody, bearerTokenMatches } from './http-utils.js';
|
|
20
|
+
export { resolveChannelToken, rotateChannelToken, bearerGuard, encodeWsBearerProtocol, tokenFromWsProtocolHeader, MOXXY_WS_SUBPROTOCOL, MOXXY_WS_BEARER_PROTOCOL_PREFIX, } from './channel-auth.js';
|
|
21
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,+BAA+B,GAChC,MAAM,mBAAmB,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-runtime-only surface of @moxxy/sdk.
|
|
3
|
+
*
|
|
4
|
+
* Importing `@moxxy/sdk/server` pulls in the value helpers that statically
|
|
5
|
+
* depend on Node builtins (`node:child_process`, `node:fs`, `node:os`,
|
|
6
|
+
* `node:http`, `node:crypto`, `node:path`). The MAIN barrel (`@moxxy/sdk`) is
|
|
7
|
+
* deliberately kept free of these so a browser/React-Native bundle can value-
|
|
8
|
+
* import from it (and from `@moxxy/sdk/tool-display`) without dragging a Node
|
|
9
|
+
* builtin into the bundle — Metro cannot polyfill `node:child_process`.
|
|
10
|
+
*
|
|
11
|
+
* Node-side consumers (cli, runner, desktop-host, channel/oauth/webhooks
|
|
12
|
+
* plugins, …) import these helpers from here. The corresponding *type* exports
|
|
13
|
+
* (e.g. `TunnelHandle`, `WriteFileAtomicOptions`, `ChannelTokenOptions`) remain
|
|
14
|
+
* on the main barrel because types are erased at build time and never reach a
|
|
15
|
+
* bundle.
|
|
16
|
+
*/
|
|
17
|
+
export { spawnCliTunnel, isCliTunnelAvailable } from './tunnel.js';
|
|
18
|
+
export { writeFileAtomic, writeFileAtomicSync, moxxyHome, moxxyPath, } from './fs-utils.js';
|
|
19
|
+
export { readRequestBody, bearerTokenMatches } from './http-utils.js';
|
|
20
|
+
export { resolveChannelToken, rotateChannelToken, bearerGuard, encodeWsBearerProtocol, tokenFromWsProtocolHeader, MOXXY_WS_SUBPROTOCOL, MOXXY_WS_BEARER_PROTOCOL_PREFIX, } from './channel-auth.js';
|
|
21
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,+BAA+B,GAChC,MAAM,mBAAmB,CAAC"}
|
package/dist/transcriber.d.ts
CHANGED
|
@@ -17,6 +17,19 @@
|
|
|
17
17
|
* - `ContentBlock` with `type: 'audio'` — provider-level wire format, only
|
|
18
18
|
* used by models that advertise `supportsAudio: true`.
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Wire MIME tag for raw 16-bit little-endian PCM mono at 24 kHz — the lossless
|
|
22
|
+
* format the desktop/web mic capture path produces and that the Codex
|
|
23
|
+
* transcriber keys on to wrap the bytes in a WAV header before upload.
|
|
24
|
+
*
|
|
25
|
+
* This is a cross-package PROTOCOL value: client-platform-web stamps it onto
|
|
26
|
+
* the captured blob, plugin-cli forwards it as the attachment `mimeType`, and
|
|
27
|
+
* plugin-stt-whisper switches on it. It lived as an independently-redeclared
|
|
28
|
+
* literal in all three (silent transcription breakage if any drifted), so it is
|
|
29
|
+
* hoisted here to the SDK's single zero-dep typed surface as the source of
|
|
30
|
+
* truth. Consumers should import this rather than re-spell the literal.
|
|
31
|
+
*/
|
|
32
|
+
export declare const MOXXY_PCM16_24KHZ_MIME = "audio/x-moxxy-pcm16-24khz";
|
|
20
33
|
export interface TranscriptionSegment {
|
|
21
34
|
/** Segment start, in seconds from the start of the clip. */
|
|
22
35
|
readonly start: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcriber.d.ts","sourceRoot":"","sources":["../src/transcriber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,UAAU,CACR,KAAK,EAAE,UAAU,GAAG,WAAW,EAC/B,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC;CAC5D"}
|
|
1
|
+
{"version":3,"file":"transcriber.d.ts","sourceRoot":"","sources":["../src/transcriber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,8BAA8B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,UAAU,CACR,KAAK,EAAE,UAAU,GAAG,WAAW,EAC/B,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC;CAC5D"}
|
package/dist/transcriber.js
CHANGED
|
@@ -17,5 +17,17 @@
|
|
|
17
17
|
* - `ContentBlock` with `type: 'audio'` — provider-level wire format, only
|
|
18
18
|
* used by models that advertise `supportsAudio: true`.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Wire MIME tag for raw 16-bit little-endian PCM mono at 24 kHz — the lossless
|
|
22
|
+
* format the desktop/web mic capture path produces and that the Codex
|
|
23
|
+
* transcriber keys on to wrap the bytes in a WAV header before upload.
|
|
24
|
+
*
|
|
25
|
+
* This is a cross-package PROTOCOL value: client-platform-web stamps it onto
|
|
26
|
+
* the captured blob, plugin-cli forwards it as the attachment `mimeType`, and
|
|
27
|
+
* plugin-stt-whisper switches on it. It lived as an independently-redeclared
|
|
28
|
+
* literal in all three (silent transcription breakage if any drifted), so it is
|
|
29
|
+
* hoisted here to the SDK's single zero-dep typed surface as the source of
|
|
30
|
+
* truth. Consumers should import this rather than re-spell the literal.
|
|
31
|
+
*/
|
|
32
|
+
export const MOXXY_PCM16_24KHZ_MIME = 'audio/x-moxxy-pcm16-24khz';
|
|
21
33
|
//# sourceMappingURL=transcriber.js.map
|
package/dist/transcriber.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcriber.js","sourceRoot":"","sources":["../src/transcriber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}
|
|
1
|
+
{"version":3,"file":"transcriber.js","sourceRoot":"","sources":["../src/transcriber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,2BAA2B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxxy/sdk",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.5",
|
|
4
4
|
"description": "Typed public surface for the moxxy framework: event types, define* factories, lifecycle hook signatures.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"moxxy",
|
|
@@ -45,9 +45,17 @@
|
|
|
45
45
|
"types": "./dist/index.d.ts",
|
|
46
46
|
"import": "./dist/index.js"
|
|
47
47
|
},
|
|
48
|
+
"./server": {
|
|
49
|
+
"types": "./dist/server.d.ts",
|
|
50
|
+
"import": "./dist/server.js"
|
|
51
|
+
},
|
|
48
52
|
"./tool-display": {
|
|
49
53
|
"types": "./dist/tool-display.d.ts",
|
|
50
54
|
"import": "./dist/tool-display.js"
|
|
55
|
+
},
|
|
56
|
+
"./transcriber": {
|
|
57
|
+
"types": "./dist/transcriber.d.ts",
|
|
58
|
+
"import": "./dist/transcriber.js"
|
|
51
59
|
}
|
|
52
60
|
},
|
|
53
61
|
"files": [
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isContextOverflowError,
|
|
10
10
|
runCompactionIfNeeded,
|
|
11
11
|
runElisionIfNeeded,
|
|
12
|
+
runManualCompaction,
|
|
12
13
|
type CompactorDef,
|
|
13
14
|
type EmittedEvent,
|
|
14
15
|
type EventLogReader,
|
|
@@ -298,6 +299,111 @@ describe('runCompactionIfNeeded', () => {
|
|
|
298
299
|
});
|
|
299
300
|
});
|
|
300
301
|
|
|
302
|
+
describe('runManualCompaction', () => {
|
|
303
|
+
// An appendable reader matching ManualCompactionInput.log — records the
|
|
304
|
+
// appended event so we can assert defensive identity-fill.
|
|
305
|
+
function appendableLog(seed: ReadonlyArray<MoxxyEvent>): EventLogReader & {
|
|
306
|
+
append(event: EmittedEvent): Promise<MoxxyEvent>;
|
|
307
|
+
appended: EmittedEvent[];
|
|
308
|
+
} {
|
|
309
|
+
const events = [...seed];
|
|
310
|
+
const appended: EmittedEvent[] = [];
|
|
311
|
+
return {
|
|
312
|
+
...reader(events),
|
|
313
|
+
appended,
|
|
314
|
+
append: async (e: EmittedEvent) => {
|
|
315
|
+
appended.push(e);
|
|
316
|
+
const mat = { ...e, id: asEventId(`a${appended.length}`), seq: events.length, ts: events.length, sessionId: sid } as MoxxyEvent;
|
|
317
|
+
events.push(mat);
|
|
318
|
+
return mat;
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const seed: MoxxyEvent[] = [
|
|
324
|
+
event(0, { type: 'user_prompt', turnId: tid, source: 'user', text: 'x'.repeat(400) }),
|
|
325
|
+
event(1, { type: 'assistant_message', turnId: tid, source: 'model', content: 'y'.repeat(400), stopReason: 'end_turn' }),
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
it('returns the no-op result for an empty log', async () => {
|
|
329
|
+
const log = appendableLog([]);
|
|
330
|
+
const compactor: CompactorDef = {
|
|
331
|
+
name: 'never',
|
|
332
|
+
shouldCompact: () => false,
|
|
333
|
+
compact: async () => { throw new Error('should not run'); },
|
|
334
|
+
};
|
|
335
|
+
const res = await runManualCompaction({ compactor, log });
|
|
336
|
+
expect(res).toEqual({ compacted: false, tokensSaved: 0, eventsCompacted: 0 });
|
|
337
|
+
expect(log.appended).toHaveLength(0);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('compacts unconditionally (no shouldCompact gate) and reports counts', async () => {
|
|
341
|
+
const log = appendableLog(seed);
|
|
342
|
+
const shouldCompact = vi.fn().mockReturnValue(false); // would block the auto path
|
|
343
|
+
const compactor: CompactorDef = {
|
|
344
|
+
name: 'fake',
|
|
345
|
+
shouldCompact,
|
|
346
|
+
compact: async () => ({
|
|
347
|
+
type: 'compaction',
|
|
348
|
+
compactor: 'fake',
|
|
349
|
+
replacedRange: [0, 1],
|
|
350
|
+
summary: 'compressed',
|
|
351
|
+
tokensSaved: 123,
|
|
352
|
+
}),
|
|
353
|
+
};
|
|
354
|
+
const res = await runManualCompaction({ compactor, log });
|
|
355
|
+
// shouldCompact is NEVER consulted — manual compaction always runs.
|
|
356
|
+
expect(shouldCompact).not.toHaveBeenCalled();
|
|
357
|
+
expect(res).toEqual({ compacted: true, tokensSaved: 123, eventsCompacted: 2 });
|
|
358
|
+
expect(log.appended).toHaveLength(1);
|
|
359
|
+
// Defensive identity fill from the log tail + source=compactor.
|
|
360
|
+
expect(log.appended[0]).toMatchObject({ type: 'compaction', source: 'compactor', sessionId: sid, turnId: tid });
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('counts only events inside the inclusive replacedRange, not the seq span', async () => {
|
|
364
|
+
// A range [0, 999] spanning a gap: only the two real events fall inside.
|
|
365
|
+
const log = appendableLog([
|
|
366
|
+
event(0, { type: 'user_prompt', turnId: tid, source: 'user', text: 'x'.repeat(400) }),
|
|
367
|
+
event(500, { type: 'assistant_message', turnId: tid, source: 'model', content: 'z'.repeat(400), stopReason: 'end_turn' }),
|
|
368
|
+
]);
|
|
369
|
+
const compactor: CompactorDef = {
|
|
370
|
+
name: 'wide',
|
|
371
|
+
shouldCompact: () => false,
|
|
372
|
+
compact: async () => ({
|
|
373
|
+
type: 'compaction', compactor: 'wide', replacedRange: [0, 999], summary: 's', tokensSaved: 50,
|
|
374
|
+
}),
|
|
375
|
+
};
|
|
376
|
+
const res = await runManualCompaction({ compactor, log });
|
|
377
|
+
expect(res.eventsCompacted).toBe(2);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('reports no-op (no append) when the summary is empty / tokensSaved <= 0', async () => {
|
|
381
|
+
const log = appendableLog(seed);
|
|
382
|
+
const compactor: CompactorDef = {
|
|
383
|
+
name: 'empty',
|
|
384
|
+
shouldCompact: () => true,
|
|
385
|
+
compact: async () => ({
|
|
386
|
+
type: 'compaction', compactor: 'empty', replacedRange: [0, 1], summary: ' ', tokensSaved: 0,
|
|
387
|
+
}),
|
|
388
|
+
};
|
|
389
|
+
const res = await runManualCompaction({ compactor, log });
|
|
390
|
+
expect(res.compacted).toBe(false);
|
|
391
|
+
expect(log.appended).toHaveLength(0);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('caller-supplied sessionId/turnId override the log-tail fallback', async () => {
|
|
395
|
+
const log = appendableLog(seed);
|
|
396
|
+
const compactor: CompactorDef = {
|
|
397
|
+
name: 'fake', shouldCompact: () => false,
|
|
398
|
+
compact: async () => ({ type: 'compaction', compactor: 'fake', replacedRange: [0, 1], summary: 'ok', tokensSaved: 10 }),
|
|
399
|
+
};
|
|
400
|
+
await runManualCompaction({
|
|
401
|
+
compactor, log, sessionId: asSessionId('other'), turnId: asTurnId('explicit'),
|
|
402
|
+
});
|
|
403
|
+
expect(log.appended[0]).toMatchObject({ sessionId: 'other', turnId: 'explicit' });
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
301
407
|
describe('isContextOverflowError', () => {
|
|
302
408
|
it('matches common provider context-overflow phrasings', () => {
|
|
303
409
|
for (const msg of [
|
package/src/compactor-helpers.ts
CHANGED
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
toolResultStubbed,
|
|
8
8
|
type ElisionState,
|
|
9
9
|
} from './elision-state.js';
|
|
10
|
+
import type { CompactorDef, TokenBudget } from './compactor.js';
|
|
10
11
|
import type { EmittedEvent, MoxxyEvent } from './events.js';
|
|
11
12
|
import type { EventLogReader } from './log.js';
|
|
12
13
|
import type { ModeContext } from './mode.js';
|
|
14
|
+
import type { LLMProvider } from './provider.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Cheap, no-network estimate of how many tokens the current event log
|
|
@@ -168,9 +170,15 @@ export async function runCompactionIfNeeded(
|
|
|
168
170
|
const events = ctx.log.slice();
|
|
169
171
|
if (events.length === 0) return false;
|
|
170
172
|
|
|
173
|
+
// Derive the elision state ONCE for this snapshot and thread it into the
|
|
174
|
+
// estimate — `computeElisionState` is memoized on the log version, but
|
|
175
|
+
// threading skips even the memo lookup and guarantees the estimate folds the
|
|
176
|
+
// exact same state the rest of this iteration uses.
|
|
177
|
+
const elisionState = computeElisionState(events);
|
|
178
|
+
|
|
171
179
|
const budget = {
|
|
172
180
|
contextWindow: resolved?.contextWindow ?? Number.MAX_SAFE_INTEGER,
|
|
173
|
-
estimatedTokens: estimateContextTokens(ctx.log),
|
|
181
|
+
estimatedTokens: estimateContextTokens(ctx.log, elisionState),
|
|
174
182
|
reserveForOutput: resolved?.reserveForOutput ?? 0,
|
|
175
183
|
} as const;
|
|
176
184
|
|
|
@@ -233,6 +241,122 @@ export async function runCompactionIfNeeded(
|
|
|
233
241
|
}
|
|
234
242
|
}
|
|
235
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Outcome of {@link runManualCompaction}. `compacted` is false (with the other
|
|
246
|
+
* counts at 0) whenever there was nothing to compact — empty log, no usable
|
|
247
|
+
* summary, or `tokensSaved <= 0` — so a caller can format "nothing to compact"
|
|
248
|
+
* vs "compacted N events" without re-deriving the gate.
|
|
249
|
+
*/
|
|
250
|
+
export interface ManualCompactionResult {
|
|
251
|
+
/** Did a CompactionEvent get appended? */
|
|
252
|
+
readonly compacted: boolean;
|
|
253
|
+
/** Estimated tokens the summary saves vs the replaced range. */
|
|
254
|
+
readonly tokensSaved: number;
|
|
255
|
+
/** Count of events whose `seq` fell inside the (inclusive) replaced range. */
|
|
256
|
+
readonly eventsCompacted: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const NO_COMPACTION: ManualCompactionResult = {
|
|
260
|
+
compacted: false,
|
|
261
|
+
tokensSaved: 0,
|
|
262
|
+
eventsCompacted: 0,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Shape a manual `/compact` needs from the session, kept structural so callers
|
|
267
|
+
* (plugin-commands) stay free of a `@moxxy/core` dependency — the host always
|
|
268
|
+
* passes a real Session that satisfies it. Mirrors the fields
|
|
269
|
+
* {@link runCompactionIfNeeded} reads off `ModeContext`, but log-first because
|
|
270
|
+
* a manual compaction has no live turn/mode context.
|
|
271
|
+
*/
|
|
272
|
+
export interface ManualCompactionInput {
|
|
273
|
+
readonly compactor: CompactorDef;
|
|
274
|
+
/** Authoring log the CompactionEvent is appended to. */
|
|
275
|
+
readonly log: EventLogReader & {
|
|
276
|
+
append(event: EmittedEvent): Promise<MoxxyEvent>;
|
|
277
|
+
};
|
|
278
|
+
/** Active provider/model so the default compactor writes a real summary. */
|
|
279
|
+
readonly provider?: LLMProvider;
|
|
280
|
+
readonly model?: string;
|
|
281
|
+
/** Active model's resolved context window (for `shouldCompact`). */
|
|
282
|
+
readonly contextWindow?: number;
|
|
283
|
+
readonly reserveForOutput?: number;
|
|
284
|
+
/** Cancellation signal; a fresh one is used when omitted. */
|
|
285
|
+
readonly signal?: AbortSignal;
|
|
286
|
+
/** Override the appended event's sessionId/turnId (else taken from the log tail). */
|
|
287
|
+
readonly sessionId?: string;
|
|
288
|
+
readonly turnId?: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Run ONE compaction now, unconditionally (the `/compact` command's force
|
|
293
|
+
* semantics — the threshold gate is skipped). The single shared implementation
|
|
294
|
+
* of the manual-compaction flow that `compactSession` in `@moxxy/plugin-commands`
|
|
295
|
+
* used to hand-roll: build the {@link TokenBudget} (with the same `estimate`
|
|
296
|
+
* + context-window fallback as the auto path), call `compactor.compact`, guard
|
|
297
|
+
* on an empty/zero-saving result, defensively fill the event's
|
|
298
|
+
* sessionId/turnId/source, append it, and report `{ compacted, tokensSaved,
|
|
299
|
+
* eventsCompacted }` so the caller only formats the message.
|
|
300
|
+
*
|
|
301
|
+
* Distinct from {@link runCompactionIfNeeded}, which is the per-iteration
|
|
302
|
+
* auto-compaction hook bound to a live `ModeContext` and gated by
|
|
303
|
+
* `shouldCompact` (unless forced). This one is log-first and always runs,
|
|
304
|
+
* matching how a user-invoked `/compact` works. Errors propagate to the caller
|
|
305
|
+
* (the command formats them); a no-op returns {@link NO_COMPACTION}.
|
|
306
|
+
*/
|
|
307
|
+
export async function runManualCompaction(
|
|
308
|
+
input: ManualCompactionInput,
|
|
309
|
+
): Promise<ManualCompactionResult> {
|
|
310
|
+
const { compactor, log } = input;
|
|
311
|
+
const events = log.slice();
|
|
312
|
+
if (events.length === 0) return NO_COMPACTION;
|
|
313
|
+
|
|
314
|
+
// Match the auto path's window fallback: an unresolved window degrades to
|
|
315
|
+
// MAX_SAFE_INTEGER (manual compaction ignores the threshold anyway).
|
|
316
|
+
const contextWindow =
|
|
317
|
+
input.contextWindow && input.contextWindow > 0
|
|
318
|
+
? input.contextWindow
|
|
319
|
+
: Number.MAX_SAFE_INTEGER;
|
|
320
|
+
const budget: TokenBudget = {
|
|
321
|
+
contextWindow,
|
|
322
|
+
estimatedTokens: estimateContextTokens(log),
|
|
323
|
+
reserveForOutput: input.reserveForOutput ?? 0,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const result = await compactor.compact(events, {
|
|
327
|
+
log,
|
|
328
|
+
budget,
|
|
329
|
+
signal: input.signal ?? new AbortController().signal,
|
|
330
|
+
...(input.provider ? { provider: input.provider } : {}),
|
|
331
|
+
...(input.model !== undefined ? { model: input.model } : {}),
|
|
332
|
+
});
|
|
333
|
+
if (result.tokensSaved <= 0 || result.summary.trim().length === 0) {
|
|
334
|
+
return NO_COMPACTION;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Defensive-fill identity fields a spec-compliant compactor may omit
|
|
338
|
+
// (`compact` declares `Omit<CompactionEvent, keyof EventBase>`). The
|
|
339
|
+
// compactor's own values win (result spreads last). Mirrors
|
|
340
|
+
// `runCompactionIfNeeded`.
|
|
341
|
+
const lastEvent = events[events.length - 1];
|
|
342
|
+
const emittable: EmittedEvent = {
|
|
343
|
+
sessionId: input.sessionId ?? lastEvent?.sessionId,
|
|
344
|
+
turnId: input.turnId ?? lastEvent?.turnId,
|
|
345
|
+
source: 'compactor',
|
|
346
|
+
...result,
|
|
347
|
+
} as EmittedEvent;
|
|
348
|
+
await log.append(emittable);
|
|
349
|
+
|
|
350
|
+
// `replacedRange` is an INCLUSIVE [fromSeq, toSeq] of event `seq` VALUES, not
|
|
351
|
+
// array indices — and `seq === arrayIndex` is not guaranteed for
|
|
352
|
+
// mirrors/partial views. Count the events actually inside the range rather
|
|
353
|
+
// than differencing the seqs (which would overstate across any seq gap).
|
|
354
|
+
const [fromSeq, toSeq] = result.replacedRange;
|
|
355
|
+
const eventsCompacted = events.filter((e) => e.seq >= fromSeq && e.seq <= toSeq).length;
|
|
356
|
+
|
|
357
|
+
return { compacted: true, tokensSaved: result.tokensSaved, eventsCompacted };
|
|
358
|
+
}
|
|
359
|
+
|
|
236
360
|
/**
|
|
237
361
|
* Heuristic: does this provider error mean "the request was too big for the
|
|
238
362
|
* model's context window"? Providers phrase it many ways (OpenAI "maximum
|
package/src/elision-helpers.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { estimateContextTokens, resolveModelContext } from './compactor-helpers.js';
|
|
2
|
-
import { toolResultBytes } from './elision-state.js';
|
|
2
|
+
import { computeElisionState, toolResultBytes } from './elision-state.js';
|
|
3
3
|
import type { EmittedEvent, MoxxyEvent } from './events.js';
|
|
4
4
|
import type { ElisionSettings, ModeContext } from './mode.js';
|
|
5
5
|
|
|
@@ -67,13 +67,22 @@ export async function runElisionIfNeeded(ctx: ModeContext): Promise<void> {
|
|
|
67
67
|
if (!resolved) return;
|
|
68
68
|
const contextWindow = resolved.contextWindow;
|
|
69
69
|
|
|
70
|
-
// Gate: below this fill the whole history fits comfortably — eliding would
|
|
71
|
-
// only add missed-context risk for no token benefit.
|
|
72
|
-
if (estimateContextTokens(ctx.log) < s.minContextRatioToElide * contextWindow) return;
|
|
73
|
-
|
|
74
70
|
const events = ctx.log.slice();
|
|
75
71
|
if (events.length === 0) return;
|
|
76
72
|
|
|
73
|
+
// Derive the elision state ONCE for this snapshot and thread it into the
|
|
74
|
+
// gate's estimate. The same `computeElisionState` fold otherwise runs again
|
|
75
|
+
// inside `estimateContextTokens` (and a third time in projection this
|
|
76
|
+
// iteration); threading shares the one fold. Order vs the slice is
|
|
77
|
+
// irrelevant — both read the same immutable snapshot.
|
|
78
|
+
const elisionState = computeElisionState(events);
|
|
79
|
+
|
|
80
|
+
// Gate: below this fill the whole history fits comfortably — eliding would
|
|
81
|
+
// only add missed-context risk for no token benefit.
|
|
82
|
+
if (estimateContextTokens(ctx.log, elisionState) < s.minContextRatioToElide * contextWindow) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
77
86
|
const priorHWM = events.reduce(
|
|
78
87
|
(max, e) => (e.type === 'elision' ? Math.max(max, e.elidedThrough) : max),
|
|
79
88
|
-1,
|
|
@@ -255,3 +255,81 @@ describe('computeElisionState (golden: fused == 4-pass reference)', () => {
|
|
|
255
255
|
assertSameState(state, computeElisionStateOld(events));
|
|
256
256
|
});
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
260
|
+
// Memo correctness (complexity-hotspots-7 / u122-2): the single-slot memo keyed
|
|
261
|
+
// on the input array's IDENTITY must (a) return the cached state for the same
|
|
262
|
+
// immutable snapshot (a cache HIT — the identical reference), and (b) RECOMPUTE
|
|
263
|
+
// — never serve a stale state — for any different array, including a new event
|
|
264
|
+
// appended past the HWM or a re-config that reuses the same ids/seqs.
|
|
265
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
266
|
+
describe('computeElisionState (memo correctness)', () => {
|
|
267
|
+
const baseLog = (): MoxxyEvent[] => [
|
|
268
|
+
event(0, { type: 'user_prompt', turnId: t1, source: 'user', text: 'the task' }),
|
|
269
|
+
event(1, { type: 'tool_call_requested', turnId: t1, source: 'model', callId: asToolCallId('ra'), name: 'recall', input: { callId: 'x' } }),
|
|
270
|
+
event(2, { type: 'tool_result', turnId: t1, source: 'tool', callId: asToolCallId('ra'), ok: true, output: 'A'.repeat(300) }),
|
|
271
|
+
event(3, {
|
|
272
|
+
type: 'elision', turnId: t2, source: 'system', elidedThrough: 2, stubbedRanges: [[0, 2]],
|
|
273
|
+
elideConversational: true, conversationalRecallThreshold: 4, maxRecallBytes: 1000, neverElideTools: [], tokensSaved: 100,
|
|
274
|
+
}),
|
|
275
|
+
event(4, { type: 'user_prompt', turnId: t2, source: 'user', text: 'recent' }),
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
it('returns the cached state for the same snapshot, equal to a fresh fold', () => {
|
|
279
|
+
const events = baseLog();
|
|
280
|
+
const a = computeElisionState(events);
|
|
281
|
+
// Same array reference → memo HIT → the identical cached reference.
|
|
282
|
+
expect(computeElisionState(events)).toBe(a);
|
|
283
|
+
// …and it equals an uncached fresh fold of the same snapshot.
|
|
284
|
+
assertSameState(a, computeElisionStateOld(events));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('invalidates (recomputes) when a new event is appended past the HWM', () => {
|
|
288
|
+
const events = baseLog();
|
|
289
|
+
const before = computeElisionState(events);
|
|
290
|
+
expect(before.hwm).toBe(2);
|
|
291
|
+
// A NEW snapshot array with one more tail event must NOT serve `before`.
|
|
292
|
+
const grown = [
|
|
293
|
+
...events,
|
|
294
|
+
event(5, { type: 'tool_call_requested', turnId: t2, source: 'model', callId: asToolCallId('rb'), name: 'recall', input: { callId: 'y' } }),
|
|
295
|
+
];
|
|
296
|
+
const after = computeElisionState(grown);
|
|
297
|
+
expect(after).not.toBe(before); // recomputed, not the cached ref
|
|
298
|
+
// `rb` only exists in the grown log → its presence proves invalidation.
|
|
299
|
+
expect(after.recallResultCallIds.has('rb')).toBe(true);
|
|
300
|
+
expect(before.recallResultCallIds.has('rb')).toBe(false);
|
|
301
|
+
assertSameState(after, computeElisionStateOld(grown));
|
|
302
|
+
// A new ElisionEvent that advances the HWM also invalidates.
|
|
303
|
+
const reElided = [
|
|
304
|
+
...grown,
|
|
305
|
+
event(6, { type: 'tool_result', turnId: t2, source: 'tool', callId: asToolCallId('rb'), ok: true, output: 'B'.repeat(50) }),
|
|
306
|
+
event(7, {
|
|
307
|
+
type: 'elision', turnId: t2, source: 'system', elidedThrough: 6, stubbedRanges: [[3, 6]],
|
|
308
|
+
elideConversational: true, conversationalRecallThreshold: 4, maxRecallBytes: 1000, neverElideTools: [], tokensSaved: 200,
|
|
309
|
+
}),
|
|
310
|
+
];
|
|
311
|
+
const advanced = computeElisionState(reElided);
|
|
312
|
+
expect(advanced.hwm).toBe(6);
|
|
313
|
+
assertSameState(advanced, computeElisionStateOld(reElided));
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('never serves a stale state for a re-config that reuses the same ids/seqs', () => {
|
|
317
|
+
// Two logically-distinct logs with byte-identical ids/seqs but different
|
|
318
|
+
// elision config — the exact case a content hash of id+seq would collide on
|
|
319
|
+
// and serve stale. Distinct array instances → distinct memo keys → correct.
|
|
320
|
+
const mk = (threshold: number): MoxxyEvent[] => [
|
|
321
|
+
event(0, { type: 'user_prompt', turnId: t1, source: 'user', text: 'the task' }),
|
|
322
|
+
event(1, { type: 'tool_call_requested', turnId: t1, source: 'model', callId: asToolCallId('s1'), name: 'recall', input: { seq: 0 } }),
|
|
323
|
+
event(2, { type: 'tool_call_requested', turnId: t1, source: 'model', callId: asToolCallId('s2'), name: 'recall', input: { seq: 0 } }),
|
|
324
|
+
event(3, {
|
|
325
|
+
type: 'elision', turnId: t2, source: 'system', elidedThrough: 2, stubbedRanges: [[0, 2]],
|
|
326
|
+
elideConversational: true, conversationalRecallThreshold: threshold, maxRecallBytes: 1000, neverElideTools: [], tokensSaved: 10,
|
|
327
|
+
}),
|
|
328
|
+
event(4, { type: 'user_prompt', turnId: t2, source: 'user', text: 'next' }),
|
|
329
|
+
];
|
|
330
|
+
const on = computeElisionState(mk(3)); // threshold 3 > 2 seq-recalls → ON
|
|
331
|
+
const off = computeElisionState(mk(2)); // threshold 2 == 2 → OFF
|
|
332
|
+
expect(on.effectiveElideConversational).toBe(true);
|
|
333
|
+
expect(off.effectiveElideConversational).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
});
|
package/src/elision-state.ts
CHANGED
|
@@ -70,12 +70,54 @@ const EMPTY_STATE: ElisionState = {
|
|
|
70
70
|
firstUserPromptSeq: -1,
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Single-slot memo of the most recent {@link computeElisionState} result,
|
|
75
|
+
* keyed on the IDENTITY of the events array it was folded from.
|
|
76
|
+
*
|
|
77
|
+
* The event log is append-only and every held event is immutable (invariant
|
|
78
|
+
* #6: events at/below the HWM never change), so a given snapshot array is a
|
|
79
|
+
* stable, never-mutated value: the same array reference always denotes the
|
|
80
|
+
* same content and therefore the same state. A new turn produces a NEW snapshot
|
|
81
|
+
* array (the live `log.slice()` returns a fresh array each call), so the memo
|
|
82
|
+
* self-invalidates the moment the log changes — there is no way for it to serve
|
|
83
|
+
* a state that is stale for the array it is keyed on.
|
|
84
|
+
*
|
|
85
|
+
* Keying on identity (not a content hash of `id`/`seq`/payload) is the only
|
|
86
|
+
* SOUND single-slot choice for a pure fold over an arbitrary array: two
|
|
87
|
+
* logically-different logs can share ids/seqs (e.g. a test that rebuilds the
|
|
88
|
+
* same prefix with different payload, or a re-config), and a content hash that
|
|
89
|
+
* missed a payload field would serve a stale state — a correctness bug. Array
|
|
90
|
+
* identity can never collide across distinct values.
|
|
91
|
+
*
|
|
92
|
+
* The win: callers that re-ask over the SAME snapshot within an iteration
|
|
93
|
+
* (e.g. the elision/compaction gates and the estimate, when they thread the
|
|
94
|
+
* one `log.slice()` array — see `estimateContextTokens`) fold it only once;
|
|
95
|
+
* `WeakMap` would also help a held snapshot survive GC pressure, but a single
|
|
96
|
+
* slot keeps it allocation-free and matches the "one live snapshot at a time"
|
|
97
|
+
* access pattern. Threading a precomputed state is the explicit zero-cost fast
|
|
98
|
+
* path; this memo covers callers that re-pass the same array but can't thread.
|
|
99
|
+
*/
|
|
100
|
+
let memoEvents: ReadonlyArray<MoxxyEvent> | null = null;
|
|
101
|
+
let memoState: ElisionState | null = null;
|
|
102
|
+
|
|
73
103
|
/**
|
|
74
104
|
* Derive elision state purely from the log: the active high-water mark + flags
|
|
75
105
|
* (from the latest ElisionEvent), the callId→tool map, recall bookkeeping, the
|
|
76
106
|
* adaptive conversational auto-disable, and which pinned recalls exceed the cap.
|
|
107
|
+
*
|
|
108
|
+
* Memoized on the input array's identity (see above) so repeated calls over the
|
|
109
|
+
* same immutable snapshot fold it only once. The returned state is identical
|
|
110
|
+
* (and `===` on a cache hit) to a fresh fold.
|
|
77
111
|
*/
|
|
78
112
|
export function computeElisionState(events: ReadonlyArray<MoxxyEvent>): ElisionState {
|
|
113
|
+
if (memoEvents === events && memoState !== null) return memoState;
|
|
114
|
+
const state = computeElisionStateUncached(events);
|
|
115
|
+
memoEvents = events;
|
|
116
|
+
memoState = state;
|
|
117
|
+
return state;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function computeElisionStateUncached(events: ReadonlyArray<MoxxyEvent>): ElisionState {
|
|
79
121
|
let hwm = -1;
|
|
80
122
|
let elideConversational = false;
|
|
81
123
|
let conversationalRecallThreshold = Number.POSITIVE_INFINITY;
|
package/src/index.ts
CHANGED
|
@@ -181,15 +181,12 @@ export type {
|
|
|
181
181
|
SpawnCliTunnelOptions,
|
|
182
182
|
CliTunnelHandle,
|
|
183
183
|
} from './tunnel.js';
|
|
184
|
-
|
|
184
|
+
// Node-runtime helpers (spawnCliTunnel/isCliTunnelAvailable, writeFileAtomic*,
|
|
185
|
+
// moxxyHome/moxxyPath, readRequestBody/bearerTokenMatches, channel-auth) are
|
|
186
|
+
// exported from the './server' subpath, NOT the main barrel — they statically
|
|
187
|
+
// reach node:* builtins and would break a browser/RN bundle. See ./server.ts.
|
|
185
188
|
export { isRetryableError, toFriendlyError, zodToJsonSchema, estimateTextTokens, type StopReason } from './provider-utils.js';
|
|
186
|
-
export {
|
|
187
|
-
writeFileAtomic,
|
|
188
|
-
writeFileAtomicSync,
|
|
189
|
-
moxxyHome,
|
|
190
|
-
moxxyPath,
|
|
191
|
-
type WriteFileAtomicOptions,
|
|
192
|
-
} from './fs-utils.js';
|
|
189
|
+
export type { WriteFileAtomicOptions } from './fs-utils.js';
|
|
193
190
|
export { createMutex, type Mutex } from './mutex.js';
|
|
194
191
|
export {
|
|
195
192
|
createJsonFileStore,
|
|
@@ -198,17 +195,9 @@ export {
|
|
|
198
195
|
} from './json-file-store.js';
|
|
199
196
|
export { assertNever } from './assert.js';
|
|
200
197
|
export { compareSemver, parseSemverCore } from './semver.js';
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
rotateChannelToken,
|
|
205
|
-
bearerGuard,
|
|
206
|
-
encodeWsBearerProtocol,
|
|
207
|
-
tokenFromWsProtocolHeader,
|
|
208
|
-
MOXXY_WS_SUBPROTOCOL,
|
|
209
|
-
MOXXY_WS_BEARER_PROTOCOL_PREFIX,
|
|
210
|
-
type ChannelTokenOptions,
|
|
211
|
-
} from './channel-auth.js';
|
|
198
|
+
// readRequestBody/bearerTokenMatches (http-utils) and the channel-auth value
|
|
199
|
+
// helpers live on the './server' subpath — they reach node:http/crypto/fs/path.
|
|
200
|
+
export type { ChannelTokenOptions } from './channel-auth.js';
|
|
212
201
|
export {
|
|
213
202
|
autoAllowResolver,
|
|
214
203
|
denyByDefaultResolver,
|
|
@@ -254,7 +243,11 @@ export type { TokenBudget, CompactContext, CompactorDef } from './compactor.js';
|
|
|
254
243
|
export {
|
|
255
244
|
estimateContextTokens,
|
|
256
245
|
runCompactionIfNeeded,
|
|
246
|
+
runManualCompaction,
|
|
247
|
+
resolveModelContext,
|
|
257
248
|
isContextOverflowError,
|
|
249
|
+
type ManualCompactionInput,
|
|
250
|
+
type ManualCompactionResult,
|
|
258
251
|
} from './compactor-helpers.js';
|
|
259
252
|
export {
|
|
260
253
|
runElisionIfNeeded,
|
|
@@ -403,6 +396,7 @@ export type {
|
|
|
403
396
|
TranscriptionSegment,
|
|
404
397
|
TranscribeOptions,
|
|
405
398
|
} from './transcriber.js';
|
|
399
|
+
export { MOXXY_PCM16_24KHZ_MIME } from './transcriber.js';
|
|
406
400
|
|
|
407
401
|
export type {
|
|
408
402
|
Synthesizer,
|