import { requestJSON } from './http-client.mjs';
try {
const model = process.env.API_MODEL;
const mode = process.env.API_GEMINI_TOOL || 'search';
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(model || '')
|| !['search', 'url', 'code'].includes(mode)) throw new Error('Invalid configuration.');
let prompt = 'Search the web for the latest Node.js LTS release and cite your sources.';
let target;
if (mode === 'url') {
target = new URL(process.env.API_CONTEXT_URL || 'invalid:');
if (target.protocol !== 'https:' || target.username || target.password || target.hash) throw new Error('Invalid URL.');
prompt = `Use URL context to summarize this public page: ${target.href}`;
} else if (mode === 'code') {
prompt = 'Use code execution to calculate the sum of the squares of integers 1 through 20. Explain the result.';
}
const tool = { search: 'googleSearch', url: 'urlContext', code: 'codeExecution' }[mode];
const result = await requestJSON(`/v1beta/models/${model}:generateContent`, {
contents: [{ role: 'user', parts: [{ text: prompt }] }], tools: [{ [tool]: {} }],
});
const candidate = result.candidates?.[0];
if (result.promptFeedback?.blockReason || !Array.isArray(result.candidates) || result.candidates.length !== 1
|| (candidate.index !== undefined && candidate.index !== 0) || candidate.finishReason !== 'STOP'
|| candidate.content?.role !== 'model' || !Array.isArray(candidate.content.parts)) throw new Error('Incomplete candidate.');
const textParts = [];
const executions = [];
let pendingCode;
for (const [partIndex, part] of candidate.content.parts.entries()) {
if (!part || typeof part !== 'object' || Array.isArray(part)) throw new Error('Invalid part.');
if (part.thought === true) continue;
if (part.functionCall || part.functionResponse || part.inlineData || part.fileData) throw new Error('Unexpected content.');
const kinds = ['text', 'executableCode', 'codeExecutionResult'].filter((key) => part[key] !== undefined);
if (kinds.length === 0 && typeof part.thoughtSignature === 'string') continue;
if (kinds.length !== 1) throw new Error('Unsupported part.');
if (part.text !== undefined) {
if (typeof part.text !== 'string') throw new Error('Invalid text.');
textParts.push({ partIndex, text: part.text });
} else if (mode !== 'code') throw new Error('Unexpected execution.');
else if (part.executableCode) {
const code = part.executableCode;
if (pendingCode || code.language !== 'PYTHON' || typeof code.code !== 'string' || !code.code.trim()) throw new Error('Invalid code.');
pendingCode = { partIndex, language: code.language, code: code.code };
} else {
const execution = part.codeExecutionResult;
if (!pendingCode || execution?.outcome !== 'OUTCOME_OK' || typeof (execution.output ?? '') !== 'string') throw new Error('Execution failed.');
executions.push({ ...pendingCode, resultPartIndex: partIndex, outcome: execution.outcome, output: execution.output ?? '' });
pendingCode = undefined;
}
}
if (!textParts.some((part) => part.text.trim()) || pendingCode) throw new Error('Incomplete output.');
let evidence;
if (mode === 'search') {
const grounding = candidate.groundingMetadata;
if (!Array.isArray(grounding?.groundingChunks) || !grounding.groundingChunks.length
|| !Array.isArray(grounding.groundingSupports) || !grounding.groundingSupports.length) throw new Error('No grounded answer.');
const sources = grounding.groundingChunks.map((chunk) => {
const uri = new URL(chunk?.web?.uri || 'invalid:');
if (!['https:', 'http:'].includes(uri.protocol) || uri.username || uri.password) throw new Error('Invalid source.');
return { uri: uri.href, title: typeof chunk.web.title === 'string' ? chunk.web.title : '' };
});
const supports = grounding.groundingSupports.map((support) => {
const segment = support?.segment;
const partIndex = segment?.partIndex ?? 0;
const startIndex = segment?.startIndex ?? 0;
const endIndex = segment?.endIndex;
const part = textParts.find((item) => item.partIndex === partIndex);
const bytes = Buffer.from(part?.text || '', 'utf8');
if (!Number.isSafeInteger(partIndex) || !part || !Number.isSafeInteger(startIndex) || startIndex < 0
|| !Number.isSafeInteger(endIndex) || endIndex <= startIndex || endIndex > bytes.length
|| !Array.isArray(support.groundingChunkIndices) || !support.groundingChunkIndices.length
|| support.groundingChunkIndices.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= sources.length)) throw new Error('Invalid grounding.');
// Native offsets are UTF-8 bytes, not JavaScript UTF-16 string positions.
new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, startIndex));
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(startIndex, endIndex));
if (segment.text !== undefined && segment.text !== text) throw new Error('Mismatched segment.');
return { partIndex, startIndex, endIndex, text, groundingChunkIndices: support.groundingChunkIndices };
});
evidence = { sources, supports };
} else if (mode === 'url') {
const urls = candidate.urlContextMetadata?.urlMetadata;
if (!Array.isArray(urls) || !urls.length) throw new Error('Missing retrieval status.');
evidence = urls.map((item) => {
if (item?.urlRetrievalStatus !== 'URL_RETRIEVAL_STATUS_SUCCESS'
|| new URL(item.retrievedUrl).href !== target.href) throw new Error('Retrieval did not confirm the requested URL.');
return { retrievedUrl: item.retrievedUrl, status: item.urlRetrievalStatus };
});
} else {
if (!executions.length) throw new Error('No code executed.');
evidence = executions;
}
// JSON terminal output only: no HTML rendering, local code execution, or URL fetching.
console.log(JSON.stringify({ mode, textParts, evidence }));
} catch {
console.error('Gemini built-in example failed. Check native tool support, completion, and tool evidence. No automatic retry.');
process.exitCode = 1;
}