Merge pull request #97 from huerlisi/feat/paginate-job-logs

feat: add pagination support for CI job logs to prevent context window flooding
This commit is contained in:
zereight
2025-06-12 21:20:25 +09:00
committed by GitHub
2 changed files with 37 additions and 5 deletions

View File

@ -523,7 +523,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),
},
{
@ -2611,9 +2611,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`
@ -2632,7 +2634,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;
}
/**
@ -3732,8 +3762,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: [
{