@evomap/evolver 1.89.8 → 1.89.9
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/package.json +1 -1
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/antiAbuseTelemetry.js +1 -1
- package/src/gep/autoDistillConv.js +1 -1
- package/src/gep/autoDistillLlm.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/contentHash.js +1 -1
- package/src/gep/conversationSniffer.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/execBridge.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hubFetch.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/openPRRegistry.js +1 -1
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallInject.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/savingsCore.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/tokenSavings.js +1 -1
- package/src/gep/workspaceKeychain.js +1 -1
- package/src/proxy/extensions/traceControl.js +1 -1
- package/src/proxy/index.js +101 -1
- package/src/proxy/inject.js +1 -1
- package/src/proxy/router/gemini_route.js +154 -0
- package/src/proxy/router/responses_route.js +14 -3
- package/src/proxy/server/routes.js +11 -0
- package/src/proxy/trace/extractor.js +1 -1
- package/src/proxy/trace/usage.js +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Gemini passthrough handler (format-aware routing, NO translation). A Gemini-shaped request — Google's native
|
|
4
|
+
// `/v1beta/models/<model>:generateContent` | `:streamGenerateContent` path, body `{contents, generationConfig,
|
|
5
|
+
// systemInstruction, tools}` — is forwarded verbatim to the Gemini upstream. The model + action live in the
|
|
6
|
+
// PATH (not the body), so we reconstruct the path (+ query like ?alt=sse) and pass it through. Trace capture
|
|
7
|
+
// mirrors the other providers (usage/finish/stream tee). Point the Gemini CLI/SDK's base URL at the proxy and
|
|
8
|
+
// it works unmodified — no Anthropic/OpenAI conversion (lossy translation is deliberately avoided).
|
|
9
|
+
|
|
10
|
+
const { createProxyTrace } = require('../trace/extractor');
|
|
11
|
+
|
|
12
|
+
const GEMINI_RESPONSE_HEADER_ALLOWLIST = new Set([
|
|
13
|
+
'content-type',
|
|
14
|
+
'retry-after',
|
|
15
|
+
'x-request-id',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
function hasGeminiUpstreamCredential() {
|
|
19
|
+
return !!(process.env.EVOMAP_GEMINI_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function upstreamStatus(err, fallback = 502) {
|
|
23
|
+
const status = Number(err && err.statusCode);
|
|
24
|
+
return Number.isFinite(status) ? status : fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function asUpstreamError(err, fallback = 502) {
|
|
28
|
+
if (err && err.statusCode && /^gemini upstream /.test(err.message || '')) return err;
|
|
29
|
+
const out = new Error('gemini upstream request failed');
|
|
30
|
+
out.statusCode = upstreamStatus(err, fallback);
|
|
31
|
+
out.cause = err;
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function responseToBody(raw, status, headers, log) {
|
|
36
|
+
if (!raw) return {};
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(raw);
|
|
39
|
+
} catch {
|
|
40
|
+
log.warn?.(JSON.stringify({
|
|
41
|
+
event: 'gemini_fallback',
|
|
42
|
+
reason: 'upstream_non_json',
|
|
43
|
+
upstream_status: status,
|
|
44
|
+
content_type: (headers && headers['content-type']) || '',
|
|
45
|
+
response_bytes: Buffer.byteLength(raw),
|
|
46
|
+
}));
|
|
47
|
+
return { error: raw };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function copyGeminiResponseHeaders(headers = {}) {
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const [name, value] of Object.entries(headers || {})) {
|
|
54
|
+
const lower = String(name || '').toLowerCase();
|
|
55
|
+
if (!GEMINI_RESPONSE_HEADER_ALLOWLIST.has(lower) && !lower.startsWith('x-goog-')) continue;
|
|
56
|
+
if (value === undefined || value === null) continue;
|
|
57
|
+
const headerValue = Array.isArray(value) ? value.join(', ') : String(value);
|
|
58
|
+
if (/[\r\n]/.test(headerValue)) continue;
|
|
59
|
+
out[lower] = headerValue;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// `<model>:<action>` — the model can contain dots/dashes; the action is the part after the LAST colon
|
|
65
|
+
// (generateContent | streamGenerateContent | countTokens | ...). Returns {model, action} (action '' if absent).
|
|
66
|
+
function parseModelAction(modelAction) {
|
|
67
|
+
const s = String(modelAction || '');
|
|
68
|
+
const idx = s.lastIndexOf(':');
|
|
69
|
+
if (idx === -1) return { model: s, action: '' };
|
|
70
|
+
return { model: s.slice(0, idx), action: s.slice(idx + 1) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildGeminiHandler({ geminiProxy, logger, traceStore, onTraceQueued } = {}) {
|
|
74
|
+
if (typeof geminiProxy !== 'function') {
|
|
75
|
+
throw new Error('buildGeminiHandler requires geminiProxy(path, body, opts)');
|
|
76
|
+
}
|
|
77
|
+
const log = logger || console;
|
|
78
|
+
|
|
79
|
+
return async ({ body, headers, params, query }) => {
|
|
80
|
+
const inboundHeaders = headers || {};
|
|
81
|
+
if (!hasGeminiUpstreamCredential()) {
|
|
82
|
+
throw Object.assign(new Error('gemini api key required'), { statusCode: 401 });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const modelAction = (params && params.modelAction) || '';
|
|
86
|
+
const { model, action } = parseModelAction(modelAction);
|
|
87
|
+
// Reconstruct the native Gemini path + query (e.g. ?alt=sse for streaming) and forward verbatim.
|
|
88
|
+
const qs = query && Object.keys(query).length ? '?' + new URLSearchParams(query).toString() : '';
|
|
89
|
+
const reqPath = `/v1beta/models/${modelAction}${qs}`;
|
|
90
|
+
|
|
91
|
+
let trace = null;
|
|
92
|
+
try {
|
|
93
|
+
trace = createProxyTrace({
|
|
94
|
+
route: `POST /v1beta/models/${modelAction}`,
|
|
95
|
+
headers: inboundHeaders,
|
|
96
|
+
body,
|
|
97
|
+
upstreamMode: 'gemini',
|
|
98
|
+
originalModel: model,
|
|
99
|
+
chosenModel: model,
|
|
100
|
+
store: traceStore,
|
|
101
|
+
logger: traceStore ? log : null,
|
|
102
|
+
onTraceQueued,
|
|
103
|
+
});
|
|
104
|
+
} catch (_) { /* best-effort trace; never break the request */ }
|
|
105
|
+
|
|
106
|
+
let upstream;
|
|
107
|
+
try {
|
|
108
|
+
upstream = await geminiProxy(reqPath, body, { inboundHeaders, upstreamMode: 'gemini' });
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const wrapped = asUpstreamError(err, upstreamStatus(err));
|
|
111
|
+
trace?.record({ status: wrapped.statusCode, error: wrapped, upstreamMode: 'gemini', model });
|
|
112
|
+
throw wrapped;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (upstream.stream) {
|
|
116
|
+
const forwardHeaders = copyGeminiResponseHeaders(upstream.headers);
|
|
117
|
+
const ct = upstream.headers && upstream.headers['content-type'];
|
|
118
|
+
if (ct) forwardHeaders['Content-Type'] = ct;
|
|
119
|
+
trace?.recordStreamStart({ status: upstream.status, upstreamMode: 'gemini', model, headers: forwardHeaders });
|
|
120
|
+
return {
|
|
121
|
+
status: upstream.status,
|
|
122
|
+
// Tee the Gemini SSE body so the deferred trace captures usageMetadata + finishReason. Bytes unchanged.
|
|
123
|
+
stream: trace ? trace.observeStream(upstream.stream) : upstream.stream,
|
|
124
|
+
headers: forwardHeaders,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let raw = '';
|
|
129
|
+
if (upstream.text) {
|
|
130
|
+
try {
|
|
131
|
+
raw = await upstream.text();
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const wrapped = asUpstreamError(err, upstreamStatus(err));
|
|
134
|
+
trace?.record({ status: wrapped.statusCode, error: wrapped, upstreamMode: 'gemini', model });
|
|
135
|
+
throw wrapped;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const respBody = responseToBody(raw, upstream.status, upstream.headers, log);
|
|
139
|
+
trace?.record({ status: upstream.status, responseBody: respBody, upstreamMode: 'gemini', model, headers: upstream.headers });
|
|
140
|
+
return {
|
|
141
|
+
status: upstream.status,
|
|
142
|
+
body: respBody,
|
|
143
|
+
headers: copyGeminiResponseHeaders(upstream.headers),
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
buildGeminiHandler,
|
|
150
|
+
copyGeminiResponseHeaders,
|
|
151
|
+
hasGeminiUpstreamCredential,
|
|
152
|
+
responseToBody,
|
|
153
|
+
parseModelAction,
|
|
154
|
+
};
|
|
@@ -66,7 +66,11 @@ function copyOpenAIResponseHeaders(headers = {}) {
|
|
|
66
66
|
return out;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// Generic OpenAI passthrough handler. `upstreamPath` selects the OpenAI endpoint (/responses for codex's
|
|
70
|
+
// Responses API, /chat/completions for the Chat Completions API used by cursor's OpenAI mode + generic OpenAI
|
|
71
|
+
// clients). Both share the same upstream, auth, header allow-list, trace, and stream tee — the only difference
|
|
72
|
+
// is the path + the trace route label. No translation: each OpenAI dialect goes to its native OpenAI endpoint.
|
|
73
|
+
function buildResponsesHandler({ openAIProxy, logger, traceStore, onTraceQueued, upstreamPath = '/responses', traceRoute = 'POST /v1/responses' } = {}) {
|
|
70
74
|
if (typeof openAIProxy !== 'function') {
|
|
71
75
|
throw new Error('buildResponsesHandler requires openAIProxy(path, body, opts)');
|
|
72
76
|
}
|
|
@@ -82,7 +86,7 @@ function buildResponsesHandler({ openAIProxy, logger, traceStore, onTraceQueued
|
|
|
82
86
|
let trace = null;
|
|
83
87
|
try {
|
|
84
88
|
trace = createProxyTrace({
|
|
85
|
-
route:
|
|
89
|
+
route: traceRoute,
|
|
86
90
|
headers: inboundHeaders,
|
|
87
91
|
body,
|
|
88
92
|
upstreamMode: 'openai',
|
|
@@ -96,7 +100,7 @@ function buildResponsesHandler({ openAIProxy, logger, traceStore, onTraceQueued
|
|
|
96
100
|
|
|
97
101
|
let upstream;
|
|
98
102
|
try {
|
|
99
|
-
upstream = await openAIProxy(
|
|
103
|
+
upstream = await openAIProxy(upstreamPath, body, {
|
|
100
104
|
inboundHeaders,
|
|
101
105
|
upstreamMode: 'openai',
|
|
102
106
|
});
|
|
@@ -151,8 +155,15 @@ function buildResponsesHandler({ openAIProxy, logger, traceStore, onTraceQueued
|
|
|
151
155
|
};
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
// OpenAI Chat Completions ingress (cursor's OpenAI mode + generic OpenAI clients). Same OpenAI upstream as the
|
|
159
|
+
// Responses handler, just the /chat/completions endpoint — point an OpenAI-Chat client's base URL at the proxy.
|
|
160
|
+
function buildChatCompletionsHandler(opts = {}) {
|
|
161
|
+
return buildResponsesHandler({ ...opts, upstreamPath: '/chat/completions', traceRoute: 'POST /v1/chat/completions' });
|
|
162
|
+
}
|
|
163
|
+
|
|
154
164
|
module.exports = {
|
|
155
165
|
buildResponsesHandler,
|
|
166
|
+
buildChatCompletionsHandler,
|
|
156
167
|
copyOpenAIResponseHeaders,
|
|
157
168
|
hasOpenAIUpstreamCredential,
|
|
158
169
|
responseToBody,
|
|
@@ -10,6 +10,8 @@ function buildRoutes(store, proxyHandlers, taskMonitor, extensions) {
|
|
|
10
10
|
sessionHandler,
|
|
11
11
|
messagesHandler,
|
|
12
12
|
responsesHandler,
|
|
13
|
+
geminiHandler,
|
|
14
|
+
chatCompletionsHandler,
|
|
13
15
|
} = extensions || {};
|
|
14
16
|
const routes = {
|
|
15
17
|
// -- Mailbox --
|
|
@@ -476,6 +478,15 @@ function buildRoutes(store, proxyHandlers, taskMonitor, extensions) {
|
|
|
476
478
|
if (responsesHandler) {
|
|
477
479
|
routes['POST /v1/responses'] = responsesHandler;
|
|
478
480
|
}
|
|
481
|
+
if (geminiHandler) {
|
|
482
|
+
// Native Gemini path: model + action (generateContent | streamGenerateContent) are one path segment
|
|
483
|
+
// (`<model>:<action>`), matched as :modelAction and split by the handler.
|
|
484
|
+
routes['POST /v1beta/models/:modelAction'] = geminiHandler;
|
|
485
|
+
}
|
|
486
|
+
if (chatCompletionsHandler) {
|
|
487
|
+
// OpenAI Chat Completions ingress (cursor's OpenAI mode + generic OpenAI clients) → OpenAI upstream.
|
|
488
|
+
routes['POST /v1/chat/completions'] = chatCompletionsHandler;
|
|
489
|
+
}
|
|
479
490
|
|
|
480
491
|
return routes;
|
|
481
492
|
}
|