@j0hanz/code-assistant
Advanced tools
| import { HarmBlockThreshold, HarmCategory, ThinkingLevel } from '@google/genai'; | ||
| export declare const DEFAULT_MODEL = "gemini-3-flash-preview"; | ||
| export declare const MODEL_FALLBACK_TARGET = "gemini-2.5-flash"; | ||
| export declare function getDefaultModel(): string; | ||
@@ -9,2 +8,4 @@ /** Test-only: reset cached model so env changes take effect. */ | ||
| export declare const DEFAULT_TIMEOUT_MS = 90000; | ||
| export declare const DEFAULT_TOP_K = 40; | ||
| export declare const DEFAULT_TOP_P = 0.95; | ||
| export declare const CANCELLED_REQUEST_MESSAGE = "Gemini request was cancelled."; | ||
@@ -11,0 +12,0 @@ declare const UNKNOWN_REQUEST_CONTEXT_VALUE_STR = "unknown"; |
@@ -9,3 +9,2 @@ import { HarmBlockThreshold, HarmCategory, ThinkingLevel } from '@google/genai'; | ||
| export const DEFAULT_MODEL = 'gemini-3-flash-preview'; | ||
| export const MODEL_FALLBACK_TARGET = 'gemini-2.5-flash'; | ||
| const GEMINI_MODEL_ENV_VAR = 'GEMINI_MODEL'; | ||
@@ -25,2 +24,4 @@ export function getDefaultModel() { | ||
| export const DEFAULT_TIMEOUT_MS = 90_000; | ||
| export const DEFAULT_TOP_K = 40; | ||
| export const DEFAULT_TOP_P = 0.95; | ||
| export const CANCELLED_REQUEST_MESSAGE = 'Gemini request was cancelled.'; | ||
@@ -27,0 +28,0 @@ const UNKNOWN_REQUEST_CONTEXT_VALUE_STR = 'unknown'; |
@@ -9,4 +9,4 @@ import { performance } from 'node:perf_hooks'; | ||
| import { emitGeminiLog, geminiContext, getClient, nextRequestId, safeCallOnLog, } from './client.js'; | ||
| import { batchPollIntervalMsConfig, batchTimeoutMsConfig, CANCELLED_REQUEST_MESSAGE, concurrencyWaitMsConfig, DEFAULT_MAX_RETRIES, DEFAULT_MODEL, DEFAULT_TIMEOUT_MS, getDefaultBatchMode, getDefaultIncludeThoughts, getDefaultModel, getSafetySettings, getSafetyThreshold, getThinkingConfig, maxConcurrentBatchCallsConfig, maxConcurrentCallsConfig, MODEL_FALLBACK_TARGET, } from './config.js'; | ||
| import { canRetryAttempt, getNumericErrorCode, getRetryDelayMs, toUpperStringCode, } from './retry.js'; | ||
| import { batchPollIntervalMsConfig, batchTimeoutMsConfig, CANCELLED_REQUEST_MESSAGE, concurrencyWaitMsConfig, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, DEFAULT_TOP_K, DEFAULT_TOP_P, getDefaultBatchMode, getDefaultIncludeThoughts, getDefaultModel, getSafetySettings, getSafetyThreshold, getThinkingConfig, maxConcurrentBatchCallsConfig, maxConcurrentCallsConfig, } from './config.js'; | ||
| import { canRetryAttempt, getRetryDelayMs, toUpperStringCode, } from './retry.js'; | ||
| // --------------------------------------------------------------------------- | ||
@@ -17,2 +17,3 @@ // Constants | ||
| const JSON_CODE_BLOCK_PATTERN = /```(?:json)?\n?([\s\S]*?)(?=\n?```)/u; | ||
| const MAX_BATCH_POLL_RETRIES = 2; | ||
| // --------------------------------------------------------------------------- | ||
@@ -46,2 +47,4 @@ // Concurrency limiters | ||
| temperature: request.temperature ?? 1.0, | ||
| topK: request.topK ?? DEFAULT_TOP_K, | ||
| topP: request.topP ?? DEFAULT_TOP_P, | ||
| maxOutputTokens: request.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS, | ||
@@ -222,2 +225,21 @@ safetySettings: getSafetySettings(getSafetyThreshold()), | ||
| // --------------------------------------------------------------------------- | ||
| function handleExecutionResponse(request, response) { | ||
| if (request.useCodeExecution) { | ||
| return extractCodeExecutionResponse(response); | ||
| } | ||
| if (request.useGrounding) { | ||
| return { | ||
| text: response.text, | ||
| groundingMetadata: response.candidates?.[0]?.groundingMetadata, | ||
| }; | ||
| } | ||
| if (request.fileSearchStoreNames && request.fileSearchStoreNames.length > 0) { | ||
| const parts = (response.candidates?.[0]?.content?.parts ?? []); | ||
| return { | ||
| text: response.text ?? '', | ||
| parts, | ||
| }; | ||
| } | ||
| return parseStructuredResponse(response.text); | ||
| } | ||
| async function executeAttempt(request, model, timeoutMs, attempt, onLog) { | ||
@@ -243,19 +265,3 @@ const startedAt = performance.now(); | ||
| } | ||
| if (request.useCodeExecution) { | ||
| return extractCodeExecutionResponse(response); | ||
| } | ||
| if (request.useGrounding) { | ||
| return { | ||
| text: response.text, | ||
| groundingMetadata: response.candidates?.[0]?.groundingMetadata, | ||
| }; | ||
| } | ||
| if (request.fileSearchStoreNames && request.fileSearchStoreNames.length > 0) { | ||
| const parts = (response.candidates?.[0]?.content?.parts ?? []); | ||
| return { | ||
| text: response.text ?? '', | ||
| parts, | ||
| }; | ||
| } | ||
| return parseStructuredResponse(response.text); | ||
| return handleExecutionResponse(request, response); | ||
| } | ||
@@ -296,33 +302,2 @@ // --------------------------------------------------------------------------- | ||
| } | ||
| // --------------------------------------------------------------------------- | ||
| // Model fallback | ||
| // --------------------------------------------------------------------------- | ||
| function shouldUseModelFallback(error, model) { | ||
| return getNumericErrorCode(error) === 404 && model === DEFAULT_MODEL; | ||
| } | ||
| function omitThinkingLevel(request) { | ||
| const copy = { ...request }; | ||
| Reflect.deleteProperty(copy, 'thinkingLevel'); | ||
| return copy; | ||
| } | ||
| async function applyModelFallback(request, onLog, reason) { | ||
| await emitGeminiLog(onLog, 'warning', { | ||
| event: 'gemini_model_fallback', | ||
| details: { | ||
| from: DEFAULT_MODEL, | ||
| to: MODEL_FALLBACK_TARGET, | ||
| reason, | ||
| }, | ||
| }); | ||
| return { | ||
| model: MODEL_FALLBACK_TARGET, | ||
| request: omitThinkingLevel(request), | ||
| }; | ||
| } | ||
| async function tryApplyModelFallback(error, model, request, onLog, reason) { | ||
| if (!shouldUseModelFallback(error, model)) { | ||
| return undefined; | ||
| } | ||
| return applyModelFallback(request, onLog, reason); | ||
| } | ||
| function countAttemptsMade(attempt) { | ||
@@ -333,16 +308,8 @@ return attempt + 1; | ||
| let lastError; | ||
| let currentModel = model; | ||
| let effectiveRequest = request; | ||
| for (let attempt = 0; attempt <= maxRetries; attempt += 1) { | ||
| try { | ||
| return await executeAttempt(effectiveRequest, currentModel, timeoutMs, attempt, onLog); | ||
| return await executeAttempt(request, model, timeoutMs, attempt, onLog); | ||
| } | ||
| catch (error) { | ||
| lastError = error; | ||
| const fallback = await tryApplyModelFallback(error, currentModel, request, onLog, 'Model not found (404)'); | ||
| if (fallback) { | ||
| currentModel = fallback.model; | ||
| effectiveRequest = fallback.request; | ||
| continue; | ||
| } | ||
| if (!canRetryAttempt(attempt, maxRetries, error)) { | ||
@@ -454,4 +421,3 @@ return throwGeminiFailure(countAttemptsMade(attempt), lastError, onLog); | ||
| async function pollBatchStatusWithRetries(batches, batchName, onLog, requestSignal) { | ||
| const maxPollRetries = 2; | ||
| for (let attempt = 0; attempt <= maxPollRetries; attempt += 1) { | ||
| for (let attempt = 0; attempt <= MAX_BATCH_POLL_RETRIES; attempt += 1) { | ||
| try { | ||
@@ -461,3 +427,3 @@ return await batches.get({ name: batchName }); | ||
| catch (error) { | ||
| if (!canRetryAttempt(attempt, maxPollRetries, error)) { | ||
| if (!canRetryAttempt(attempt, MAX_BATCH_POLL_RETRIES, error)) { | ||
| throw error; | ||
@@ -495,32 +461,13 @@ } | ||
| } | ||
| async function createBatchJobWithFallback(request, batches, model, onLog) { | ||
| let currentModel = model; | ||
| let effectiveRequest = request; | ||
| const createSignal = request.signal; | ||
| for (let attempt = 0; attempt <= 1; attempt += 1) { | ||
| try { | ||
| const createPayload = { | ||
| model: currentModel, | ||
| src: [ | ||
| { | ||
| contents: [ | ||
| { role: 'user', parts: [{ text: effectiveRequest.prompt }] }, | ||
| ], | ||
| config: buildGenerationConfig(effectiveRequest, createSignal), | ||
| }, | ||
| ], | ||
| }; | ||
| return await batches.create(createPayload); | ||
| } | ||
| catch (error) { | ||
| if (attempt === 0 && shouldUseModelFallback(error, currentModel)) { | ||
| const fallback = await applyModelFallback(request, onLog, 'Model not found (404) during batch create'); | ||
| currentModel = fallback.model; | ||
| effectiveRequest = fallback.request; | ||
| continue; | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
| throw new Error('Unexpected state: batch creation loop exited without returning or throwing.'); | ||
| async function createBatchJob(request, batches, model) { | ||
| const createPayload = { | ||
| model, | ||
| src: [ | ||
| { | ||
| contents: [{ role: 'user', parts: [{ text: request.prompt }] }], | ||
| config: buildGenerationConfig(request, request.signal), | ||
| }, | ||
| ], | ||
| }; | ||
| return await batches.create(createPayload); | ||
| } | ||
@@ -559,3 +506,3 @@ async function pollBatchForCompletion(batches, batchName, onLog, requestSignal) { | ||
| try { | ||
| const createdJob = await createBatchJobWithFallback(request, batches, model, onLog); | ||
| const createdJob = await createBatchJob(request, batches, model); | ||
| const createdRecord = toRecord(createdJob); | ||
@@ -562,0 +509,0 @@ batchName = |
@@ -8,2 +8,4 @@ export type JsonObject = Record<string, unknown>; | ||
| temperature?: number; | ||
| topK?: number; | ||
| topP?: number; | ||
| maxOutputTokens?: number; | ||
@@ -10,0 +12,0 @@ thinkingLevel?: GeminiThinkingLevel; |
+1
-1
| { | ||
| "name": "@j0hanz/code-assistant", | ||
| "version": "0.9.3", | ||
| "version": "1.0.0", | ||
| "mcpName": "io.github.j0hanz/code-assistant", | ||
@@ -5,0 +5,0 @@ "description": "Gemini-powered MCP server for code analysis.", |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
-100%295276
-0.69%6537
-0.74%