@mlx-node/server 0.0.0 → 0.0.8
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/dist/chat-session-warm-reuse.d.ts +51 -0
- package/dist/chat-session-warm-reuse.d.ts.map +1 -0
- package/dist/chat-session-warm-reuse.js +68 -0
- package/dist/endpoints/messages-count-tokens.d.ts +8 -0
- package/dist/endpoints/messages-count-tokens.d.ts.map +1 -0
- package/dist/endpoints/messages-count-tokens.js +121 -0
- package/dist/endpoints/messages.d.ts +57 -5
- package/dist/endpoints/messages.d.ts.map +1 -1
- package/dist/endpoints/messages.js +1043 -147
- package/dist/endpoints/models.d.ts +2 -1
- package/dist/endpoints/models.d.ts.map +1 -1
- package/dist/endpoints/models.js +2 -2
- package/dist/endpoints/responses.d.ts +20 -7
- package/dist/endpoints/responses.d.ts.map +1 -1
- package/dist/endpoints/responses.js +572 -82
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/handler.d.ts +42 -0
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +6 -1
- package/dist/idle-sweeper.d.ts +245 -0
- package/dist/idle-sweeper.d.ts.map +1 -0
- package/dist/idle-sweeper.js +408 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/mappers/anthropic-request.d.ts +24 -2
- package/dist/mappers/anthropic-request.d.ts.map +1 -1
- package/dist/mappers/anthropic-request.js +222 -24
- package/dist/mappers/anthropic-response.d.ts +29 -4
- package/dist/mappers/anthropic-response.d.ts.map +1 -1
- package/dist/mappers/anthropic-response.js +143 -21
- package/dist/mappers/request.d.ts +48 -0
- package/dist/mappers/request.d.ts.map +1 -1
- package/dist/mappers/request.js +211 -35
- package/dist/mappers/response.d.ts.map +1 -1
- package/dist/mappers/response.js +13 -1
- package/dist/model-work-coordinator.d.ts +70 -0
- package/dist/model-work-coordinator.d.ts.map +1 -0
- package/dist/model-work-coordinator.js +120 -0
- package/dist/pending-writes.d.ts.map +1 -1
- package/dist/presets.d.ts +82 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +98 -0
- package/dist/registry.d.ts +31 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +33 -5
- package/dist/router.d.ts +4 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +34 -4
- package/dist/server.d.ts +76 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +48 -1
- package/dist/session-registry.d.ts +272 -18
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +509 -37
- package/dist/stop-sequence-buffer.d.ts +58 -0
- package/dist/stop-sequence-buffer.d.ts.map +1 -0
- package/dist/stop-sequence-buffer.js +148 -0
- package/dist/text-recovery.d.ts +35 -0
- package/dist/text-recovery.d.ts.map +1 -0
- package/dist/text-recovery.js +41 -0
- package/dist/timing.d.ts +80 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +121 -0
- package/dist/tool-call-buffer.d.ts +5 -5
- package/dist/tool-call-buffer.d.ts.map +1 -1
- package/dist/tool-call-buffer.js +28 -8
- package/dist/types-anthropic.d.ts +161 -1
- package/dist/types-anthropic.d.ts.map +1 -1
- package/dist/types.d.ts +172 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-private `ChatSession` warm-reuse helper.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the `@mlx-node/lm` package exports entirely so downstream
|
|
5
|
+
* consumers cannot reach it: the module lives inside
|
|
6
|
+
* `@mlx-node/server`, its file path is not on the server's export map,
|
|
7
|
+
* and nothing re-exports it. Callers are the two server endpoints —
|
|
8
|
+
* `endpoints/responses.ts` (tier-1 / tier-2 HIT branch) and
|
|
9
|
+
* `endpoints/messages.ts` (`getOrCreateWarmAny` HIT branch) — each
|
|
10
|
+
* invoking the helper exclusively under a `SessionRegistry` HIT gate.
|
|
11
|
+
*
|
|
12
|
+
* Why this helper exists at all: `ChatSession.reset()` is the safe
|
|
13
|
+
* public wipe — it always calls `model.resetCaches()` because the
|
|
14
|
+
* underlying `SessionCapableModel` is shared across every session
|
|
15
|
+
* lifetime via the native `ModelRegistry`. A partial wipe that leaves
|
|
16
|
+
* the shared native KV cache intact without the HIT gate would leak a
|
|
17
|
+
* previous (unrelated) request's cached prefix into the next
|
|
18
|
+
* `chat_session_start_sync` call. The server's warm-lease replay path
|
|
19
|
+
* DOES have that HIT gate — the registry's own `getOrCreate` hit
|
|
20
|
+
* signal is the authoritative proof that the native cache genuinely
|
|
21
|
+
* belongs to this chain — so a JS-state-only reset that preserves the
|
|
22
|
+
* native cache is correct there and only there.
|
|
23
|
+
*
|
|
24
|
+
* Fields accessed: `inFlight`, `history`, `lastImagesKey`, `lastAudioKey`, `turnCount`,
|
|
25
|
+
* `unresolvedOkToolCallCount`, `needsFullReplay`. These are TypeScript `private` fields on
|
|
26
|
+
* `ChatSession` (compile-time only) — at runtime they are ordinary
|
|
27
|
+
* properties. The cast through {@link ChatSessionWarmReuseInternals}
|
|
28
|
+
* gives this helper a typed view of the instance without relaxing the
|
|
29
|
+
* class's `private` declarations. The field names MUST stay in sync
|
|
30
|
+
* with `packages/lm/src/chat-session.ts`; a mismatch would silently
|
|
31
|
+
* skip the intended state wipe and is covered by the chat-session
|
|
32
|
+
* unit tests that exercise this path through the endpoint handler.
|
|
33
|
+
*/
|
|
34
|
+
import type { ChatSession, SessionCapableModel } from '@mlx-node/lm';
|
|
35
|
+
/**
|
|
36
|
+
* JS-state-only reset that DELIBERATELY preserves the underlying
|
|
37
|
+
* model's native KV cache and `cached_token_history`.
|
|
38
|
+
*
|
|
39
|
+
* @internal server-private — used only by `SessionRegistry` HIT
|
|
40
|
+
* branches in `endpoints/responses.ts` and `endpoints/messages.ts`.
|
|
41
|
+
* Never export from this package's `index.ts`.
|
|
42
|
+
*
|
|
43
|
+
* Wipes ONLY the JS-side session state (history array, image key, turn
|
|
44
|
+
* counter, tool-call fan-out guard). With this function, the JS session
|
|
45
|
+
* is fresh enough for `ChatSession.primeHistory()` (which requires
|
|
46
|
+
* `turnCount === 0`) while the native prefix verifier can still recover
|
|
47
|
+
* the reused prefix on the next `chatSessionStart` and skip the
|
|
48
|
+
* corresponding re-prefill.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resetPreservingNativeCacheForWarmReuse<M extends SessionCapableModel>(session: ChatSession<M>): Promise<void>;
|
|
51
|
+
//# sourceMappingURL=chat-session-warm-reuse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-session-warm-reuse.d.ts","sourceRoot":"","sources":["../src/chat-session-warm-reuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAmBrE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,sCAAsC,CAAC,CAAC,SAAS,mBAAmB,EACxF,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-private `ChatSession` warm-reuse helper.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the `@mlx-node/lm` package exports entirely so downstream
|
|
5
|
+
* consumers cannot reach it: the module lives inside
|
|
6
|
+
* `@mlx-node/server`, its file path is not on the server's export map,
|
|
7
|
+
* and nothing re-exports it. Callers are the two server endpoints —
|
|
8
|
+
* `endpoints/responses.ts` (tier-1 / tier-2 HIT branch) and
|
|
9
|
+
* `endpoints/messages.ts` (`getOrCreateWarmAny` HIT branch) — each
|
|
10
|
+
* invoking the helper exclusively under a `SessionRegistry` HIT gate.
|
|
11
|
+
*
|
|
12
|
+
* Why this helper exists at all: `ChatSession.reset()` is the safe
|
|
13
|
+
* public wipe — it always calls `model.resetCaches()` because the
|
|
14
|
+
* underlying `SessionCapableModel` is shared across every session
|
|
15
|
+
* lifetime via the native `ModelRegistry`. A partial wipe that leaves
|
|
16
|
+
* the shared native KV cache intact without the HIT gate would leak a
|
|
17
|
+
* previous (unrelated) request's cached prefix into the next
|
|
18
|
+
* `chat_session_start_sync` call. The server's warm-lease replay path
|
|
19
|
+
* DOES have that HIT gate — the registry's own `getOrCreate` hit
|
|
20
|
+
* signal is the authoritative proof that the native cache genuinely
|
|
21
|
+
* belongs to this chain — so a JS-state-only reset that preserves the
|
|
22
|
+
* native cache is correct there and only there.
|
|
23
|
+
*
|
|
24
|
+
* Fields accessed: `inFlight`, `history`, `lastImagesKey`, `lastAudioKey`, `turnCount`,
|
|
25
|
+
* `unresolvedOkToolCallCount`, `needsFullReplay`. These are TypeScript `private` fields on
|
|
26
|
+
* `ChatSession` (compile-time only) — at runtime they are ordinary
|
|
27
|
+
* properties. The cast through {@link ChatSessionWarmReuseInternals}
|
|
28
|
+
* gives this helper a typed view of the instance without relaxing the
|
|
29
|
+
* class's `private` declarations. The field names MUST stay in sync
|
|
30
|
+
* with `packages/lm/src/chat-session.ts`; a mismatch would silently
|
|
31
|
+
* skip the intended state wipe and is covered by the chat-session
|
|
32
|
+
* unit tests that exercise this path through the endpoint handler.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* JS-state-only reset that DELIBERATELY preserves the underlying
|
|
36
|
+
* model's native KV cache and `cached_token_history`.
|
|
37
|
+
*
|
|
38
|
+
* @internal server-private — used only by `SessionRegistry` HIT
|
|
39
|
+
* branches in `endpoints/responses.ts` and `endpoints/messages.ts`.
|
|
40
|
+
* Never export from this package's `index.ts`.
|
|
41
|
+
*
|
|
42
|
+
* Wipes ONLY the JS-side session state (history array, image key, turn
|
|
43
|
+
* counter, tool-call fan-out guard). With this function, the JS session
|
|
44
|
+
* is fresh enough for `ChatSession.primeHistory()` (which requires
|
|
45
|
+
* `turnCount === 0`) while the native prefix verifier can still recover
|
|
46
|
+
* the reused prefix on the next `chatSessionStart` and skip the
|
|
47
|
+
* corresponding re-prefill.
|
|
48
|
+
*/
|
|
49
|
+
export async function resetPreservingNativeCacheForWarmReuse(session) {
|
|
50
|
+
// TypeScript `private` fields are only compile-time checks; at
|
|
51
|
+
// runtime they are ordinary properties. The cast through
|
|
52
|
+
// `ChatSessionWarmReuseInternals` preserves full static typing for
|
|
53
|
+
// this helper's mutations while bypassing the `private` gate — which
|
|
54
|
+
// is correct here because this helper is the designated server-side
|
|
55
|
+
// friend accessor. The cast is funneled through `unknown` because TS
|
|
56
|
+
// correctly rejects a direct `ChatSession → Internals` cast when the
|
|
57
|
+
// concrete class has other non-internals fields.
|
|
58
|
+
const internals = session;
|
|
59
|
+
if (internals.inFlight) {
|
|
60
|
+
throw new Error('ChatSession: cannot resetPreservingNativeCacheForWarmReuse() while a send() is in flight; await the previous call first');
|
|
61
|
+
}
|
|
62
|
+
internals.history = [];
|
|
63
|
+
internals.lastImagesKey = null;
|
|
64
|
+
internals.lastAudioKey = null;
|
|
65
|
+
internals.turnCount = 0;
|
|
66
|
+
internals.unresolvedOkToolCallCount = null;
|
|
67
|
+
internals.needsFullReplay = false;
|
|
68
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** POST /v1/messages/count_tokens — Anthropic Messages token-count endpoint. */
|
|
2
|
+
import type { ServerResponse } from 'node:http';
|
|
3
|
+
import type { IdleSweeper } from '../idle-sweeper.js';
|
|
4
|
+
import type { ModelWorkCoordinator } from '../model-work-coordinator.js';
|
|
5
|
+
import type { ModelRegistry } from '../registry.js';
|
|
6
|
+
import type { AnthropicCountTokensRequest } from '../types-anthropic.js';
|
|
7
|
+
export declare function handleCountMessageTokens(res: ServerResponse, body: AnthropicCountTokensRequest, registry: ModelRegistry, idleSweeper?: IdleSweeper | null, resolveModel?: (name: string) => Promise<void>, modelWorkCoordinator?: ModelWorkCoordinator): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=messages-count-tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages-count-tokens.d.ts","sourceRoot":"","sources":["../../src/endpoints/messages-count-tokens.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAUhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AACnE,OAAO,KAAK,EAAE,2BAA2B,EAAgC,MAAM,uBAAuB,CAAC;AAqBvG,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,2BAA2B,EACjC,QAAQ,EAAE,aAAa,EACvB,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,EAChC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAC9C,oBAAoB,CAAC,EAAE,oBAAoB,GAC1C,OAAO,CAAC,IAAI,CAAC,CAuHf"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** POST /v1/messages/count_tokens — Anthropic Messages token-count endpoint. */
|
|
2
|
+
import { sendAnthropicBadRequest, sendAnthropicInternalError, sendAnthropicNotFound, sendAnthropicNotImplemented, } from '../errors.js';
|
|
3
|
+
import { mapAnthropicRequest } from '../mappers/anthropic-request.js';
|
|
4
|
+
function getChatTemplateTokenCounter(model) {
|
|
5
|
+
const candidate = model;
|
|
6
|
+
return typeof candidate.applyChatTemplate === 'function' ? candidate : null;
|
|
7
|
+
}
|
|
8
|
+
function endJson(res, body) {
|
|
9
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
10
|
+
res.end(JSON.stringify(body));
|
|
11
|
+
}
|
|
12
|
+
export async function handleCountMessageTokens(res, body, registry, idleSweeper, resolveModel, modelWorkCoordinator) {
|
|
13
|
+
if (body == null || typeof body !== 'object') {
|
|
14
|
+
sendAnthropicBadRequest(res, 'Request body must be a JSON object');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (!body.model) {
|
|
18
|
+
sendAnthropicBadRequest(res, 'Missing required field: model');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!body.messages || !Array.isArray(body.messages) || body.messages.length === 0) {
|
|
22
|
+
sendAnthropicBadRequest(res, 'Missing required field: messages');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
for (const msg of body.messages) {
|
|
26
|
+
if (msg == null || typeof msg !== 'object') {
|
|
27
|
+
sendAnthropicBadRequest(res, 'Each message must be a non-null object');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let mapped;
|
|
32
|
+
try {
|
|
33
|
+
mapped = mapAnthropicRequest(body);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
sendAnthropicBadRequest(res, err instanceof Error ? err.message : 'Invalid request');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (resolveModel) {
|
|
40
|
+
try {
|
|
41
|
+
const runResolve = () => idleSweeper ? idleSweeper.withSuspendedDrains(() => resolveModel(body.model)) : resolveModel(body.model);
|
|
42
|
+
if (modelWorkCoordinator)
|
|
43
|
+
await modelWorkCoordinator.withModelLoad(runResolve);
|
|
44
|
+
else
|
|
45
|
+
await runResolve();
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
sendAnthropicInternalError(res, err instanceof Error ? err.message : 'Failed to resolve model');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const lease = registry.acquireDispatchLease(body.model);
|
|
53
|
+
if (!lease) {
|
|
54
|
+
if (registry.get(body.model) != null) {
|
|
55
|
+
sendAnthropicInternalError(res, 'session registry missing for registered model');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
sendAnthropicNotFound(res, `Model "${body.model}" not found`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const leaseModel = lease.model;
|
|
62
|
+
const sessionReg = lease.registry;
|
|
63
|
+
const preLockInstanceId = lease.instanceId;
|
|
64
|
+
try {
|
|
65
|
+
// Token counting is a pure-CPU tokenize-and-template operation; it must NOT
|
|
66
|
+
// queue behind the per-model generation FIFO (`sessionReg.withExclusive`)
|
|
67
|
+
// because that serializes against multi-minute decode passes and turns a
|
|
68
|
+
// millisecond call into a multi-hundred-second wait. The dispatch lease
|
|
69
|
+
// already pins the model object + binding for the duration of this call,
|
|
70
|
+
// and `withInference` (a shared reader lock against `withModelLoad`) is
|
|
71
|
+
// enough to keep the model from being swapped out mid-tokenize.
|
|
72
|
+
const bindingStillMatchesLease = () => {
|
|
73
|
+
const lockedSessionReg = registry.getSessionRegistry(body.model);
|
|
74
|
+
const lockedInstanceId = registry.getInstanceId(body.model);
|
|
75
|
+
return (lockedSessionReg !== undefined &&
|
|
76
|
+
lockedInstanceId !== undefined &&
|
|
77
|
+
lockedSessionReg === sessionReg &&
|
|
78
|
+
lockedInstanceId === preLockInstanceId);
|
|
79
|
+
};
|
|
80
|
+
const rejectChangedBinding = (phase) => {
|
|
81
|
+
sendAnthropicBadRequest(res, `Model "${body.model}" binding changed while the token-count request was ${phase}. ` +
|
|
82
|
+
`A concurrent register() re-pointed the name at a different model instance ` +
|
|
83
|
+
`(or released it entirely), so counting against the leased model would use ` +
|
|
84
|
+
`a stale model object. Retry the request — if the swap was intentional, the ` +
|
|
85
|
+
`new binding will service the retry cleanly.`);
|
|
86
|
+
};
|
|
87
|
+
const runCountWithModelRead = async () => {
|
|
88
|
+
if (!bindingStillMatchesLease()) {
|
|
89
|
+
rejectChangedBinding('waiting for the model-load reader gate');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const counter = getChatTemplateTokenCounter(leaseModel);
|
|
93
|
+
if (!counter) {
|
|
94
|
+
sendAnthropicNotImplemented(res, `Model "${body.model}" does not expose applyChatTemplate(); token counting requires a ` +
|
|
95
|
+
`non-generating chat-template tokenizer API on the registered model.`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const tokens = await counter.applyChatTemplate(mapped.messages, true, mapped.config.tools ?? null);
|
|
100
|
+
if (!bindingStillMatchesLease()) {
|
|
101
|
+
rejectChangedBinding('running');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
endJson(res, { input_tokens: tokens.length });
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
sendAnthropicInternalError(res, err instanceof Error ? err.message : 'Failed to count tokens');
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
if (modelWorkCoordinator)
|
|
111
|
+
await modelWorkCoordinator.withInference(runCountWithModelRead);
|
|
112
|
+
else
|
|
113
|
+
await runCountWithModelRead();
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
sendAnthropicInternalError(res, err instanceof Error ? err.message : 'Failed to count tokens');
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
registry.releaseDispatchLease(leaseModel);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,13 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* POST /v1/messages — stateless Anthropic Messages API.
|
|
3
3
|
*
|
|
4
|
-
* Every request carries the full conversation in `req.messages`.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Every request carries the full conversation in `req.messages`. The
|
|
5
|
+
* Anthropic Messages API is stateless on the wire: there is no
|
|
6
|
+
* `previous_response_id` to thread, and clients (e.g. Claude Code)
|
|
7
|
+
* also do NOT propagate `prompt_cache_key` back to the server. The
|
|
8
|
+
* cross-turn / cross-conversation prefix-reuse path is one of two
|
|
9
|
+
* mutually-exclusive mechanisms, picked at request time based on
|
|
10
|
+
* whether the underlying native model has the block-paged KV cache
|
|
11
|
+
* adapter active (`SessionCapableModel.hasBlockPagedCache?.()`):
|
|
12
|
+
*
|
|
13
|
+
* * **Paged-active path** (Qwen3 + LFM2 + Gemma4 are paged-active
|
|
14
|
+
* today; Qwen3.5 dense/MoE and Qianfan-OCR remain non-paged /
|
|
15
|
+
* default-off pending a perf decision and adapter wiring
|
|
16
|
+
* respectively). Each request allocates a fresh `ChatSession` via
|
|
17
|
+
* `SessionRegistry.createFreshSession()` and runs a full
|
|
18
|
+
* `session.reset()` + `primeHistory()` +
|
|
19
|
+
* `startFromHistory[Stream]()`. The JS-side warm slot is
|
|
20
|
+
* **not** consulted, **not** leased, and **not** adopted —
|
|
21
|
+
* cross-request prefix reuse is handled entirely by the native
|
|
22
|
+
* `BlockAllocator`'s content-addressed prefix-hash table, which
|
|
23
|
+
* refcounts SYS blocks shared across requests transparently
|
|
24
|
+
* (two parallel `/v1/messages` requests with the same system
|
|
25
|
+
* prompt run on distinct `ChatSession` objects but reference
|
|
26
|
+
* the same physical KV blocks). The non-streaming
|
|
27
|
+
* `X-Session-Cache` header is promoted from `fresh` to
|
|
28
|
+
* `prefix_hit` after dispatch when the engine reports
|
|
29
|
+
* `cachedTokens > 0`.
|
|
30
|
+
*
|
|
31
|
+
* * **Non-paged path** (Qwen3.5 dense + MoE — default-off pending a
|
|
32
|
+
* perf decision; the Qianfan-OCR VLM — no adapter wired). Each
|
|
33
|
+
* request looks up the warm slot via
|
|
34
|
+
* `SessionRegistry.getOrCreateWarmAny(requestedSystem)`. On a
|
|
35
|
+
* HIT we keep the underlying native KV cache alive
|
|
36
|
+
* (`resetPreservingNativeCacheForWarmReuse` wipes only JS-side
|
|
37
|
+
* session state) so the native `verify_cache_prefix_direct` can
|
|
38
|
+
* recognize the cached prefix and re-prefill only the new
|
|
39
|
+
* suffix. On a MISS we run a full `session.reset()` to wipe
|
|
40
|
+
* both JS and native state — a fresh JS session does NOT imply
|
|
41
|
+
* a fresh native cache (the underlying `SessionCapableModel` is
|
|
42
|
+
* shared and its native `cached_token_history` persists across
|
|
43
|
+
* requests). After the dispatch settles we adopt the session
|
|
44
|
+
* back under the sentinel id `'__msg_warm__'` (or drop on
|
|
45
|
+
* uncommitted streams / thrown errors) so the next turn can
|
|
46
|
+
* lease it. The sentinel is never produced by either the OpenAI
|
|
47
|
+
* or the Anthropic wire format, so cross-endpoint capture via
|
|
48
|
+
* tier-1 is impossible by construction. The `/v1/responses` and
|
|
49
|
+
* `/v1/messages` endpoints still SHARE the single warm slot
|
|
50
|
+
* under the registry's single-warm invariant on this path — a
|
|
51
|
+
* turn on one side can evict the other's slot.
|
|
52
|
+
*
|
|
53
|
+
* The `prompt_cache_key` request field is still NOT exposed on this
|
|
54
|
+
* endpoint. Cross-conversation block-level cache reuse on the
|
|
55
|
+
* paged path is now driven by native content-addressing instead of
|
|
56
|
+
* the JS warm slot, so adding the field is no longer a prerequisite
|
|
57
|
+
* for that use case.
|
|
8
58
|
*/
|
|
9
59
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
60
|
+
import type { IdleSweeper } from '../idle-sweeper.js';
|
|
61
|
+
import type { ModelWorkCoordinator } from '../model-work-coordinator.js';
|
|
10
62
|
import type { ModelRegistry } from '../registry.js';
|
|
11
63
|
import type { AnthropicMessagesRequest } from '../types-anthropic.js';
|
|
12
|
-
export declare function handleCreateMessage(res: ServerResponse, body: AnthropicMessagesRequest, registry: ModelRegistry, httpReq?: IncomingMessage): Promise<void>;
|
|
64
|
+
export declare function handleCreateMessage(res: ServerResponse, body: AnthropicMessagesRequest, registry: ModelRegistry, httpReq?: IncomingMessage, idleSweeper?: IdleSweeper | null, resolveModel?: (name: string) => Promise<void>, modelWorkCoordinator?: ModelWorkCoordinator): Promise<void>;
|
|
13
65
|
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/endpoints/messages.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/endpoints/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAajE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAgBtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAepD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAg3BtE,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,wBAAwB,EAC9B,QAAQ,EAAE,aAAa,EACvB,OAAO,CAAC,EAAE,eAAe,EACzB,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,EAChC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAC9C,oBAAoB,CAAC,EAAE,oBAAoB,GAC1C,OAAO,CAAC,IAAI,CAAC,CAioBf"}
|