@evomap/evolver-proxy 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/dist/bin/envFile.d.ts +10 -0
- package/dist/bin/envFile.js +68 -0
- package/dist/bin/evolver-llm-proxy.d.ts +2 -0
- package/dist/bin/evolver-llm-proxy.js +111 -0
- package/dist/bin/evolver-proxy.d.ts +83 -0
- package/dist/bin/evolver-proxy.js +511 -0
- package/dist/bin/proxySettings.d.ts +15 -0
- package/dist/bin/proxySettings.js +84 -0
- package/dist/bin/proxyStorePath.d.ts +1 -0
- package/dist/bin/proxyStorePath.js +16 -0
- package/dist/daemon/atpConsent.d.ts +13 -0
- package/dist/daemon/atpConsent.js +60 -0
- package/dist/daemon/ipcConfig.d.ts +1 -0
- package/dist/daemon/ipcConfig.js +13 -0
- package/dist/daemon/proxyDaemon.d.ts +191 -0
- package/dist/daemon/proxyDaemon.js +1015 -0
- package/dist/daemon/selectHub.d.ts +16 -0
- package/dist/daemon/selectHub.js +30 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/lifecycle/deployGuard.d.ts +46 -0
- package/dist/lifecycle/deployGuard.js +53 -0
- package/dist/lifecycle/legacyNodeId.d.ts +96 -0
- package/dist/lifecycle/legacyNodeId.js +163 -0
- package/dist/lifecycle/manager.d.ts +106 -0
- package/dist/lifecycle/manager.js +390 -0
- package/dist/llm/bodyCapture.d.ts +31 -0
- package/dist/llm/bodyCapture.js +293 -0
- package/dist/llm/index.d.ts +3 -0
- package/dist/llm/index.js +3 -0
- package/dist/llm/server.d.ts +35 -0
- package/dist/llm/server.js +359 -0
- package/dist/llm/traceBackfill.d.ts +53 -0
- package/dist/llm/traceBackfill.js +525 -0
- package/dist/llm/traceConfig.d.ts +7 -0
- package/dist/llm/traceConfig.js +44 -0
- package/dist/llm/traceControl.d.ts +16 -0
- package/dist/llm/traceControl.js +85 -0
- package/dist/llm/traceEnvelope.d.ts +75 -0
- package/dist/llm/traceEnvelope.js +286 -0
- package/dist/llm/traceSink.d.ts +61 -0
- package/dist/llm/traceSink.js +278 -0
- package/dist/llm/traceUploadPayload.d.ts +14 -0
- package/dist/llm/traceUploadPayload.js +27 -0
- package/dist/llm/upstream.d.ts +68 -0
- package/dist/llm/upstream.js +491 -0
- package/dist/private/adapterLoader.d.ts +46 -0
- package/dist/private/adapterLoader.js +62 -0
- package/dist/private/privateRuntimeSmokeOptions.d.ts +15 -0
- package/dist/private/privateRuntimeSmokeOptions.js +132 -0
- package/dist/router/cachePassthrough.d.ts +3 -0
- package/dist/router/cachePassthrough.js +13 -0
- package/dist/router/features.d.ts +9 -0
- package/dist/router/features.js +52 -0
- package/dist/router/index.d.ts +6 -0
- package/dist/router/index.js +6 -0
- package/dist/router/messagesRoute.d.ts +138 -0
- package/dist/router/messagesRoute.js +753 -0
- package/dist/router/modelRouter.d.ts +49 -0
- package/dist/router/modelRouter.js +66 -0
- package/dist/router/providerRoutes.d.ts +42 -0
- package/dist/router/providerRoutes.js +1579 -0
- package/dist/router/sseScan.d.ts +56 -0
- package/dist/router/sseScan.js +543 -0
- package/dist/selfUpdate/executor.d.ts +90 -0
- package/dist/selfUpdate/executor.js +179 -0
- package/dist/selfUpdate/failureCodes.d.ts +33 -0
- package/dist/selfUpdate/failureCodes.js +84 -0
- package/dist/selfUpdate/index.d.ts +5 -0
- package/dist/selfUpdate/index.js +5 -0
- package/dist/selfUpdate/lastUpdate.d.ts +43 -0
- package/dist/selfUpdate/lastUpdate.js +195 -0
- package/dist/selfUpdate/policy.d.ts +3 -0
- package/dist/selfUpdate/policy.js +9 -0
- package/dist/selfUpdate/releaseBinary.d.ts +56 -0
- package/dist/selfUpdate/releaseBinary.js +498 -0
- package/dist/selfUpdate/version.d.ts +2 -0
- package/dist/selfUpdate/version.js +14 -0
- package/dist/sync/engine.d.ts +65 -0
- package/dist/sync/engine.js +461 -0
- package/package.json +41 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { expandHomePath, parseEnvFile } from '../bin/envFile.js';
|
|
4
|
+
import { resolvePrivateEnterpriseToken } from './adapterLoader.js';
|
|
5
|
+
const CONFIG_FILES = ['.mcp.json', join('.claude', 'settings.json'), join('.codex', 'config.toml')];
|
|
6
|
+
const POINTER_RE = /EVOLVER_ENV_FILE"?\s*[:=]\s*"([^"]+)"/;
|
|
7
|
+
export function resolvePrivateRuntimeSmokeOptions(sourceEnv = process.env, readEnvFileOrDeps) {
|
|
8
|
+
const deps = normalizeDeps(readEnvFileOrDeps);
|
|
9
|
+
const env = { ...sourceEnv };
|
|
10
|
+
const envFilePath = resolveEnvFilePath(env, deps);
|
|
11
|
+
if (envFilePath) {
|
|
12
|
+
env['EVOLVER_ENV_FILE'] = envFilePath;
|
|
13
|
+
try {
|
|
14
|
+
const parsed = parseEnvFile(readEnvFile(envFilePath, deps));
|
|
15
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
16
|
+
if (key !== 'EVOLVER_ENV_FILE')
|
|
17
|
+
env[key] = value;
|
|
18
|
+
}
|
|
19
|
+
const insecureMode = insecureSecretFileMode(Object.keys(parsed), readStatMode(envFilePath, deps));
|
|
20
|
+
if (env['EVOLVER_PRIVATE_SMOKE'] === '1' && insecureMode) {
|
|
21
|
+
throw new Error(`EVOLVER_ENV_FILE mode ${insecureMode.modeText} exposes secret-like keys [${insecureMode.keys.join(', ')}]; run chmod 600 on the credential store`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (env['EVOLVER_PRIVATE_SMOKE'] === '1') {
|
|
26
|
+
throw new Error(`failed to load EVOLVER_ENV_FILE for private runtime smoke: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const run = env['EVOLVER_PRIVATE_SMOKE'] === '1';
|
|
31
|
+
const runSearch = env['EVOLVER_PRIVATE_SMOKE_SEARCH'] === '1';
|
|
32
|
+
const runReuseResult = env['EVOLVER_PRIVATE_SMOKE_REUSE_RESULT'] === '1';
|
|
33
|
+
const runPublish = env['EVOLVER_PRIVATE_SMOKE_PUBLISH'] === '1';
|
|
34
|
+
const hubUrl = env['EVOMAP_HUB_URL']?.trim();
|
|
35
|
+
const assetId = env['EVOLVER_PRIVATE_SMOKE_ASSET_ID']?.trim();
|
|
36
|
+
if (run && env['EVOMAP_HUB_MODE']?.trim().toLowerCase() !== 'private') {
|
|
37
|
+
throw new Error('EVOLVER_PRIVATE_SMOKE requires EVOMAP_HUB_MODE=private');
|
|
38
|
+
}
|
|
39
|
+
if (run && !hubUrl) {
|
|
40
|
+
throw new Error('EVOLVER_PRIVATE_SMOKE requires EVOMAP_HUB_URL pointing at a controlled PHub test-env');
|
|
41
|
+
}
|
|
42
|
+
if (run && !resolvePrivateEnterpriseToken(env)) {
|
|
43
|
+
throw new Error('EVOLVER_PRIVATE_SMOKE requires an enterprise token alias in EVOLVER_ENV_FILE or process env');
|
|
44
|
+
}
|
|
45
|
+
if (run && isProductionLikeHubUrl(hubUrl)) {
|
|
46
|
+
throw new Error('EVOLVER_PRIVATE_SMOKE refuses production-like PHub URL; use a test/staging/dev/local PHub URL');
|
|
47
|
+
}
|
|
48
|
+
if (run && runReuseResult && !runSearch && !assetId) {
|
|
49
|
+
throw new Error('EVOLVER_PRIVATE_SMOKE_REUSE_RESULT=1 requires EVOLVER_PRIVATE_SMOKE_ASSET_ID or EVOLVER_PRIVATE_SMOKE_SEARCH=1');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
env,
|
|
53
|
+
run,
|
|
54
|
+
runSearch,
|
|
55
|
+
runReuseResult,
|
|
56
|
+
runPublish,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function normalizeDeps(readEnvFileOrDeps) {
|
|
60
|
+
if (typeof readEnvFileOrDeps === 'function')
|
|
61
|
+
return { readEnvFile: readEnvFileOrDeps };
|
|
62
|
+
return readEnvFileOrDeps ?? {};
|
|
63
|
+
}
|
|
64
|
+
function resolveEnvFilePath(env, deps) {
|
|
65
|
+
const fromEnv = env['EVOLVER_ENV_FILE']?.trim();
|
|
66
|
+
if (fromEnv)
|
|
67
|
+
return fromEnv;
|
|
68
|
+
const root = deps.configRoot ?? process.cwd();
|
|
69
|
+
const exists = deps.exists ?? existsSync;
|
|
70
|
+
const readConfig = deps.readConfigFile ?? ((path) => readFileSync(path, 'utf8'));
|
|
71
|
+
for (const rel of CONFIG_FILES) {
|
|
72
|
+
const path = join(root, rel);
|
|
73
|
+
if (!exists(path))
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const match = POINTER_RE.exec(readConfig(path));
|
|
77
|
+
if (match?.[1])
|
|
78
|
+
return match[1];
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Ignore unreadable runtime config candidates; the env file itself remains the authoritative smoke input.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
function readEnvFile(path, deps) {
|
|
87
|
+
if (deps.readEnvFile)
|
|
88
|
+
return deps.readEnvFile(path);
|
|
89
|
+
return readFileSync(expandHomePath(path), 'utf8');
|
|
90
|
+
}
|
|
91
|
+
function readStatMode(path, deps) {
|
|
92
|
+
if (deps.statMode)
|
|
93
|
+
return deps.statMode(path);
|
|
94
|
+
if (deps.readEnvFile)
|
|
95
|
+
return undefined;
|
|
96
|
+
try {
|
|
97
|
+
return statSync(expandHomePath(path)).mode;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function insecureSecretFileMode(keys, mode) {
|
|
104
|
+
if (mode === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
if ((mode & 0o077) === 0)
|
|
107
|
+
return undefined;
|
|
108
|
+
const secretKeys = keys.filter(isSecretKeyName);
|
|
109
|
+
return secretKeys.length > 0 ? { modeText: (mode & 0o777).toString(8).padStart(3, '0'), keys: secretKeys } : undefined;
|
|
110
|
+
}
|
|
111
|
+
function isSecretKeyName(key) {
|
|
112
|
+
return /(^|_)(SECRET|TOKEN|PASSWORD|PRIVATE_KEY|API_KEY|NODE_SECRET|IPC_TOKEN)(_|$)/.test(key);
|
|
113
|
+
}
|
|
114
|
+
function isProductionLikeHubUrl(raw) {
|
|
115
|
+
if (!raw)
|
|
116
|
+
return false;
|
|
117
|
+
try {
|
|
118
|
+
const host = new URL(raw).hostname.toLowerCase();
|
|
119
|
+
if (isLocalHubHost(host))
|
|
120
|
+
return false;
|
|
121
|
+
return !/(^|[-.])(test|staging|stage|dev|local)([-.]|$)/.test(host);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function isLocalHubHost(host) {
|
|
128
|
+
return host === 'localhost'
|
|
129
|
+
|| host === '::1'
|
|
130
|
+
|| host === '[::1]'
|
|
131
|
+
|| /^127(?:\.\d{1,3}){3}$/.test(host);
|
|
132
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Cache-preserving model rewrite (ported from v1 proxy/router/cache_passthrough.js). Swaps ONLY the top-level
|
|
2
|
+
// `model` string on an Anthropic /v1/messages body, never the `messages`/`system`/`tools` arrays — so every
|
|
3
|
+
// `cache_control: { type: "ephemeral" }` breakpoint the client placed stays intact and the prompt-cache prefix
|
|
4
|
+
// the client established with prior turns keeps hitting. Pure: returns a shallow clone, never mutates input.
|
|
5
|
+
export function rewriteModel(body, newModel) {
|
|
6
|
+
if (!body || typeof body !== 'object')
|
|
7
|
+
return body;
|
|
8
|
+
if (!newModel || typeof newModel !== 'string')
|
|
9
|
+
return body;
|
|
10
|
+
if (body.model === newModel)
|
|
11
|
+
return body;
|
|
12
|
+
return { ...body, model: newModel };
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RouterFeatures } from './modelRouter.js';
|
|
2
|
+
declare const PLAN_RE: RegExp;
|
|
3
|
+
declare const SIMPLE_LOOKUP_MAX_CHARS = 80;
|
|
4
|
+
/** Loose by design — the handler passes an arbitrary request body; we validate `messages` is an array at use. */
|
|
5
|
+
interface MessagesBody {
|
|
6
|
+
messages?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function extractFeatures(body: MessagesBody | null | undefined): RouterFeatures;
|
|
9
|
+
export { PLAN_RE, SIMPLE_LOOKUP_MAX_CHARS };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const PLAN_RE = /\b(plan|design|architect|brainstorm|outline|think through|let'?s think)\b/i;
|
|
2
|
+
const SIMPLE_LOOKUP_MAX_CHARS = 80;
|
|
3
|
+
function lastMessageOfRole(messages, role) {
|
|
4
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5
|
+
const m = messages[i];
|
|
6
|
+
if (m && m.role === role)
|
|
7
|
+
return m;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function blocksOf(msg) {
|
|
12
|
+
if (!msg)
|
|
13
|
+
return [];
|
|
14
|
+
const c = msg.content;
|
|
15
|
+
if (typeof c === 'string')
|
|
16
|
+
return [{ type: 'text', text: c }];
|
|
17
|
+
return Array.isArray(c) ? c : [];
|
|
18
|
+
}
|
|
19
|
+
function tailUserText(messages) {
|
|
20
|
+
const tail = messages[messages.length - 1];
|
|
21
|
+
if (!tail || tail.role !== 'user')
|
|
22
|
+
return '';
|
|
23
|
+
return blocksOf(tail)
|
|
24
|
+
.filter((b) => b && b.type === 'text' && typeof b.text === 'string')
|
|
25
|
+
.map((b) => b.text)
|
|
26
|
+
.join('\n');
|
|
27
|
+
}
|
|
28
|
+
export function extractFeatures(body) {
|
|
29
|
+
const messages = Array.isArray(body?.messages) ? body.messages : [];
|
|
30
|
+
const lastAssistant = lastMessageOfRole(messages, 'assistant');
|
|
31
|
+
const toolCallCount = blocksOf(lastAssistant).filter((b) => b && b.type === 'tool_use').length;
|
|
32
|
+
const tail = messages[messages.length - 1];
|
|
33
|
+
const tailIsUser = !!(tail && tail.role === 'user');
|
|
34
|
+
const tailBlocks = blocksOf(tail ?? null);
|
|
35
|
+
const lastUserIsToolResultOnly = tailIsUser && tailBlocks.length > 0 && tailBlocks.every((b) => b && b.type === 'tool_result');
|
|
36
|
+
const userText = tailUserText(messages);
|
|
37
|
+
const userRequestedPlanning = userText.length > 0 && PLAN_RE.test(userText);
|
|
38
|
+
const userSimpleLookup = userText.length > 0 && !lastUserIsToolResultOnly && !userRequestedPlanning && userText.trim().length <= SIMPLE_LOOKUP_MAX_CHARS;
|
|
39
|
+
// We don't have the real Anthropic response on the request side, but the last assistant message's shape
|
|
40
|
+
// reconstructs stop_reason deterministically: any tool_use block ⇒ it ended with 'ToolUse'; text-only ⇒ 'Stop'.
|
|
41
|
+
const stopReason = lastAssistant ? (toolCallCount > 0 ? 'ToolUse' : 'Stop') : null;
|
|
42
|
+
return {
|
|
43
|
+
last_assistant_tool_call_count: toolCallCount,
|
|
44
|
+
last_assistant_had_tool_call: toolCallCount > 0,
|
|
45
|
+
last_user_is_tool_result_only: lastUserIsToolResultOnly,
|
|
46
|
+
user_requested_planning: userRequestedPlanning,
|
|
47
|
+
user_simple_lookup: userSimpleLookup,
|
|
48
|
+
last_assistant_output_tokens: 0,
|
|
49
|
+
last_assistant_stop_reason: stopReason,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export { PLAN_RE, SIMPLE_LOOKUP_MAX_CHARS };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { type Tier, type RouterFeatures } from './modelRouter.js';
|
|
2
|
+
import { type SseUsage } from './sseScan.js';
|
|
3
|
+
export type LlmRoute = '/v1/messages' | '/v1/responses' | '/v1/chat/completions';
|
|
4
|
+
export type LlmWireApi = 'anthropic_messages' | 'openai_responses' | 'openai_chat_completions' | 'gemini_generate_content' | 'ollama_api' | 'vertex_gemini';
|
|
5
|
+
export interface UpstreamResult {
|
|
6
|
+
status: number;
|
|
7
|
+
headers?: Record<string, string | undefined>;
|
|
8
|
+
stream?: unknown | null;
|
|
9
|
+
text?: () => Promise<string> | string;
|
|
10
|
+
traceRequestBody?: unknown;
|
|
11
|
+
transportMetadata?: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface UpstreamCallOptions {
|
|
14
|
+
inboundHeaders: Record<string, string | undefined>;
|
|
15
|
+
upstreamMode: string;
|
|
16
|
+
method?: string;
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
export type AnthropicProxy = (path: string, body: unknown, opts: UpstreamCallOptions) => Promise<UpstreamResult>;
|
|
20
|
+
export interface RouterLogger {
|
|
21
|
+
log?: (line: string) => void;
|
|
22
|
+
warn?: (line: string) => void;
|
|
23
|
+
}
|
|
24
|
+
export type LlmUsage = SseUsage;
|
|
25
|
+
export interface LlmTraceAttempt {
|
|
26
|
+
attempt_index: number;
|
|
27
|
+
model: string | null;
|
|
28
|
+
provider: string;
|
|
29
|
+
upstream_mode: string;
|
|
30
|
+
status: number | null;
|
|
31
|
+
error?: string;
|
|
32
|
+
requestBody?: string;
|
|
33
|
+
responseBody?: string;
|
|
34
|
+
body_truncated?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** One record per /v1/messages turn — the trace material the proxy exists to capture. METADATA ONLY: routing
|
|
37
|
+
* decision, status, latency, token usage, stop_reason. Prompt/completion content never enters a record. */
|
|
38
|
+
export interface LlmTurnTrace {
|
|
39
|
+
ts: string;
|
|
40
|
+
event: 'llm_turn';
|
|
41
|
+
id: string;
|
|
42
|
+
request_id: string | null;
|
|
43
|
+
route: string;
|
|
44
|
+
provider: string;
|
|
45
|
+
wire_api: LlmWireApi;
|
|
46
|
+
client: 'claude-code' | 'cursor' | 'codex' | 'unknown';
|
|
47
|
+
/** Raw inbound User-Agent header (clipped), kept as a structured field so buyers can fingerprint the client
|
|
48
|
+
* beyond the coarse `client` bucket. Omitted when absent. (FIX-10) */
|
|
49
|
+
user_agent?: string;
|
|
50
|
+
/** Stable account hash derived from request metadata.user_id/user. For Claude-style
|
|
51
|
+
* `user_...__session_<uuid>` values this strips the volatile session suffix. */
|
|
52
|
+
user_id_hash?: string;
|
|
53
|
+
/** Normalized reasoning/thinking effort for this turn, derived from the request body across providers:
|
|
54
|
+
* Anthropic `thinking` (type/budget_tokens), OpenAI `reasoning.effort`, `output_config.effort`, or metadata.
|
|
55
|
+
* Shape: { effort?: 'minimal'|'low'|'medium'|'high'|string, budget_tokens?: number, type?: string }. (FIX-9) */
|
|
56
|
+
thinking_effort?: ThinkingEffort;
|
|
57
|
+
session_id: string | null;
|
|
58
|
+
response_id?: string;
|
|
59
|
+
previous_response_id?: string;
|
|
60
|
+
original_model: string | null;
|
|
61
|
+
chosen_model: string | null;
|
|
62
|
+
tier: Tier | null;
|
|
63
|
+
reason: string | null;
|
|
64
|
+
fallback: string | null;
|
|
65
|
+
router_enabled: boolean;
|
|
66
|
+
upstream_mode: string;
|
|
67
|
+
status: number | null;
|
|
68
|
+
stream: boolean;
|
|
69
|
+
/** ms from request arrival to upstream response headers (null if upstream was never reached). */
|
|
70
|
+
ttfb_ms: number | null;
|
|
71
|
+
/** ms from request arrival to trace emission (stream end for streams, body parse for JSON). */
|
|
72
|
+
latency_ms: number;
|
|
73
|
+
features?: RouterFeatures;
|
|
74
|
+
usage?: LlmUsage;
|
|
75
|
+
stop_reason?: string | null;
|
|
76
|
+
error?: string;
|
|
77
|
+
requestBody?: string;
|
|
78
|
+
responseBody?: string;
|
|
79
|
+
redaction?: string;
|
|
80
|
+
body_truncated?: boolean;
|
|
81
|
+
request_headers?: unknown;
|
|
82
|
+
response_headers?: unknown;
|
|
83
|
+
transport_metadata?: unknown;
|
|
84
|
+
attempts?: LlmTraceAttempt[];
|
|
85
|
+
}
|
|
86
|
+
export type TraceSink = (record: LlmTurnTrace) => void;
|
|
87
|
+
export interface MessagesHandlerOptions {
|
|
88
|
+
anthropicProxy: AnthropicProxy;
|
|
89
|
+
logger?: RouterLogger;
|
|
90
|
+
/** Explicit override wins (hermetic tests); else read from env EVOMAP_ROUTER_ENABLED==='1' at construction. */
|
|
91
|
+
routerEnabled?: boolean;
|
|
92
|
+
/** Injected env (testability). Default process.env. */
|
|
93
|
+
env?: NodeJS.ProcessEnv;
|
|
94
|
+
/** Trace-capture seam: called exactly once per turn. Sink errors are swallowed — capture never breaks serving. */
|
|
95
|
+
onTrace?: TraceSink;
|
|
96
|
+
/** Injected clock for latency fields (testability). Default Date.now. */
|
|
97
|
+
clock?: () => number;
|
|
98
|
+
}
|
|
99
|
+
export interface MessagesRequest {
|
|
100
|
+
route?: LlmRoute;
|
|
101
|
+
body: {
|
|
102
|
+
model?: unknown;
|
|
103
|
+
messages?: unknown;
|
|
104
|
+
} & Record<string, unknown>;
|
|
105
|
+
headers?: Record<string, string | undefined>;
|
|
106
|
+
}
|
|
107
|
+
export interface MessagesResponse {
|
|
108
|
+
status: number;
|
|
109
|
+
body?: unknown;
|
|
110
|
+
stream?: unknown;
|
|
111
|
+
headers?: Record<string, string>;
|
|
112
|
+
}
|
|
113
|
+
export declare function captureTraceMetadata(value: unknown, env?: NodeJS.ProcessEnv): unknown;
|
|
114
|
+
export declare function resolveTierModels(env?: NodeJS.ProcessEnv): Partial<Record<Tier, string>>;
|
|
115
|
+
interface ClaudeId {
|
|
116
|
+
family: string;
|
|
117
|
+
major: number;
|
|
118
|
+
minor: number;
|
|
119
|
+
}
|
|
120
|
+
export declare function parseClaudeId(modelId: unknown): ClaudeId | null;
|
|
121
|
+
/** Block an intra-family generational DOWNGRADE (opus-4-7 → opus-4-1). Cross-family (opus→haiku) is allowed. */
|
|
122
|
+
export declare function isIntraFamilyDowngrade(chosen: unknown, original: unknown): boolean;
|
|
123
|
+
export declare function resolveBedrockAliases(env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
124
|
+
/** Canonicalize a short Claude ID to its operator-configured Bedrock alias. Unmapped/unknown → unchanged. */
|
|
125
|
+
export declare function canonicalizeForBedrock(modelId: unknown, aliases: Record<string, string>): unknown;
|
|
126
|
+
export declare function supportsAdaptiveThinking(modelId: unknown): boolean;
|
|
127
|
+
export interface ThinkingEffort {
|
|
128
|
+
effort?: string;
|
|
129
|
+
budget_tokens?: number;
|
|
130
|
+
type?: string;
|
|
131
|
+
}
|
|
132
|
+
export declare function extractThinkingEffort(body: unknown): ThinkingEffort | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* Build the /v1/messages handler. `enabled` (env EVOMAP_ROUTER_ENABLED, or the explicit override) gates the
|
|
135
|
+
* whole router — when off, the body forwards unmodified (a pure passthrough). Returns {status, body|stream}.
|
|
136
|
+
*/
|
|
137
|
+
export declare function buildMessagesHandler(opts: MessagesHandlerOptions): (req: MessagesRequest) => Promise<MessagesResponse>;
|
|
138
|
+
export {};
|