feat: add pagination support for CI job logs to prevent context window flooding
- Add optional `limit` and `offset` parameters to get_pipeline_job_output - Default limit is 1000 lines when pagination is used - Returns lines from the end of the log, with configurable offset - Includes truncation metadata showing what was skipped - Maintains backward compatibility (no parameters = full log) This prevents large CI logs from overwhelming AI tooling context windows while still providing access to the most recent and relevant log output.
This commit is contained in:
40
index.ts
40
index.ts
@ -518,7 +518,7 @@ const allTools = [
|
||||
},
|
||||
{
|
||||
name: "get_pipeline_job_output",
|
||||
description: "Get the output/trace of a GitLab pipeline job number",
|
||||
description: "Get the output/trace of a GitLab pipeline job with optional pagination to limit context window usage",
|
||||
inputSchema: zodToJsonSchema(GetPipelineJobOutputSchema),
|
||||
},
|
||||
{
|
||||
@ -2606,9 +2606,11 @@ async function getPipelineJob(projectId: string, jobId: number): Promise<GitLabP
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} jobId - The ID of the job
|
||||
* @param {number} limit - Maximum number of lines to return from the end (default: 1000)
|
||||
* @param {number} offset - Number of lines to skip from the end (default: 0)
|
||||
* @returns {Promise<string>} The job output/trace
|
||||
*/
|
||||
async function getPipelineJobOutput(projectId: string, jobId: number): Promise<string> {
|
||||
async function getPipelineJobOutput(projectId: string, jobId: number, limit?: number, offset?: number): Promise<string> {
|
||||
projectId = decodeURIComponent(projectId); // Decode project ID
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/trace`
|
||||
@ -2627,7 +2629,35 @@ async function getPipelineJobOutput(projectId: string, jobId: number): Promise<s
|
||||
}
|
||||
|
||||
await handleGitLabError(response);
|
||||
return await response.text();
|
||||
const fullTrace = await response.text();
|
||||
|
||||
// Apply client-side pagination to limit context window usage
|
||||
if (limit !== undefined || offset !== undefined) {
|
||||
const lines = fullTrace.split('\n');
|
||||
const startOffset = offset || 0;
|
||||
const maxLines = limit || 1000;
|
||||
|
||||
// Return lines from the end, skipping offset lines and limiting to maxLines
|
||||
const startIndex = Math.max(0, lines.length - startOffset - maxLines);
|
||||
const endIndex = lines.length - startOffset;
|
||||
|
||||
const selectedLines = lines.slice(startIndex, endIndex);
|
||||
const result = selectedLines.join('\n');
|
||||
|
||||
// Add metadata about truncation
|
||||
if (startIndex > 0 || endIndex < lines.length) {
|
||||
const totalLines = lines.length;
|
||||
const shownLines = selectedLines.length;
|
||||
const skippedFromStart = startIndex;
|
||||
const skippedFromEnd = startOffset;
|
||||
|
||||
return `[Log truncated: showing ${shownLines} of ${totalLines} lines, skipped ${skippedFromStart} from start, ${skippedFromEnd} from end]\n\n${result}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return fullTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3722,8 +3752,8 @@ server.setRequestHandler(CallToolRequestSchema, async request => {
|
||||
}
|
||||
|
||||
case "get_pipeline_job_output": {
|
||||
const { project_id, job_id } = GetPipelineJobOutputSchema.parse(request.params.arguments);
|
||||
const jobOutput = await getPipelineJobOutput(project_id, job_id);
|
||||
const { project_id, job_id, limit, offset } = GetPipelineJobOutputSchema.parse(request.params.arguments);
|
||||
const jobOutput = await getPipelineJobOutput(project_id, job_id, limit, offset);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
|
@ -191,6 +191,8 @@ export const CancelPipelineSchema = z.object({
|
||||
export const GetPipelineJobOutputSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"),
|
||||
job_id: z.number().describe("The ID of the job"),
|
||||
limit: z.number().optional().describe("Maximum number of lines to return from the end of the log (default: 1000)"),
|
||||
offset: z.number().optional().describe("Number of lines to skip from the end of the log (default: 0)"),
|
||||
});
|
||||
|
||||
// User schemas
|
||||
|
Reference in New Issue
Block a user