@gitlab/gitlab-ai-provider 1.0.5
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/CHANGELOG.md +251 -0
- package/LICENSE +28 -0
- package/README.md +432 -0
- package/dist/index.d.mts +525 -0
- package/dist/index.d.ts +572 -0
- package/dist/index.js +2401 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2350 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +99 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2401 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var index_exports = {};
|
|
31
|
+
__export(index_exports, {
|
|
32
|
+
ANTHROPIC_TOOLS: () => ANTHROPIC_TOOLS,
|
|
33
|
+
AnthropicToolExecutor: () => AnthropicToolExecutor,
|
|
34
|
+
BUNDLED_CLIENT_ID: () => BUNDLED_CLIENT_ID,
|
|
35
|
+
GITLAB_API_TOOLS: () => GITLAB_API_TOOLS,
|
|
36
|
+
GITLAB_COM_URL: () => GITLAB_COM_URL,
|
|
37
|
+
GitLabAgenticLanguageModel: () => GitLabAgenticLanguageModel,
|
|
38
|
+
GitLabApiToolExecutor: () => GitLabApiToolExecutor,
|
|
39
|
+
GitLabError: () => GitLabError,
|
|
40
|
+
GitLabOAuthManager: () => GitLabOAuthManager,
|
|
41
|
+
GitLabProjectCache: () => GitLabProjectCache,
|
|
42
|
+
GitLabProjectDetector: () => GitLabProjectDetector,
|
|
43
|
+
OAUTH_SCOPES: () => OAUTH_SCOPES,
|
|
44
|
+
TOKEN_EXPIRY_SKEW_MS: () => TOKEN_EXPIRY_SKEW_MS,
|
|
45
|
+
createGitLab: () => createGitLab,
|
|
46
|
+
gitlab: () => gitlab,
|
|
47
|
+
isGitLabApiTool: () => isGitLabApiTool
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(index_exports);
|
|
50
|
+
|
|
51
|
+
// src/gitlab-agentic-language-model.ts
|
|
52
|
+
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
53
|
+
|
|
54
|
+
// src/gitlab-direct-access.ts
|
|
55
|
+
var import_zod = require("zod");
|
|
56
|
+
|
|
57
|
+
// src/gitlab-error.ts
|
|
58
|
+
var GitLabError = class _GitLabError extends Error {
|
|
59
|
+
statusCode;
|
|
60
|
+
responseBody;
|
|
61
|
+
cause;
|
|
62
|
+
constructor(options) {
|
|
63
|
+
super(options.message);
|
|
64
|
+
this.name = "GitLabError";
|
|
65
|
+
this.statusCode = options.statusCode;
|
|
66
|
+
this.responseBody = options.responseBody;
|
|
67
|
+
this.cause = options.cause;
|
|
68
|
+
if (Error.captureStackTrace) {
|
|
69
|
+
Error.captureStackTrace(this, _GitLabError);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
static fromResponse(response, body) {
|
|
73
|
+
return new _GitLabError({
|
|
74
|
+
message: `GitLab API error: ${response.status} ${response.statusText}`,
|
|
75
|
+
statusCode: response.status,
|
|
76
|
+
responseBody: body
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
isAuthError() {
|
|
80
|
+
return this.statusCode === 401;
|
|
81
|
+
}
|
|
82
|
+
isRateLimitError() {
|
|
83
|
+
return this.statusCode === 429;
|
|
84
|
+
}
|
|
85
|
+
isForbiddenError() {
|
|
86
|
+
return this.statusCode === 403;
|
|
87
|
+
}
|
|
88
|
+
isServerError() {
|
|
89
|
+
return this.statusCode !== void 0 && this.statusCode >= 500;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/gitlab-direct-access.ts
|
|
94
|
+
var directAccessTokenSchema = import_zod.z.object({
|
|
95
|
+
headers: import_zod.z.record(import_zod.z.string()),
|
|
96
|
+
token: import_zod.z.string()
|
|
97
|
+
});
|
|
98
|
+
var GitLabDirectAccessClient = class {
|
|
99
|
+
config;
|
|
100
|
+
fetchFn;
|
|
101
|
+
cachedToken = null;
|
|
102
|
+
tokenExpiresAt = 0;
|
|
103
|
+
constructor(config) {
|
|
104
|
+
this.config = config;
|
|
105
|
+
this.fetchFn = config.fetch ?? fetch;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get a direct access token for the Anthropic proxy.
|
|
109
|
+
* Tokens are cached for 25 minutes (they expire after 30 minutes).
|
|
110
|
+
* @param forceRefresh - If true, ignores the cache and fetches a new token
|
|
111
|
+
*/
|
|
112
|
+
async getDirectAccessToken(forceRefresh = false) {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
if (!forceRefresh && this.cachedToken && this.tokenExpiresAt > now) {
|
|
115
|
+
return this.cachedToken;
|
|
116
|
+
}
|
|
117
|
+
if (forceRefresh) {
|
|
118
|
+
this.invalidateToken();
|
|
119
|
+
}
|
|
120
|
+
const url = `${this.config.instanceUrl}/api/v4/ai/third_party_agents/direct_access`;
|
|
121
|
+
const requestBody = {};
|
|
122
|
+
if (this.config.featureFlags && Object.keys(this.config.featureFlags).length > 0) {
|
|
123
|
+
requestBody.feature_flags = this.config.featureFlags;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const response = await this.fetchFn(url, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
...this.config.getHeaders(),
|
|
130
|
+
"Content-Type": "application/json"
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(requestBody)
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const errorText = await response.text();
|
|
136
|
+
if (response.status === 401 && this.config.refreshApiKey && !forceRefresh) {
|
|
137
|
+
try {
|
|
138
|
+
await this.config.refreshApiKey();
|
|
139
|
+
return await this.getDirectAccessToken(true);
|
|
140
|
+
} catch (refreshError) {
|
|
141
|
+
throw new GitLabError({
|
|
142
|
+
message: `Failed to get direct access token: ${response.status} ${response.statusText} - ${errorText}`
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
throw new GitLabError({
|
|
147
|
+
message: `Failed to get direct access token: ${response.status} ${response.statusText} - ${errorText}`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
const token = directAccessTokenSchema.parse(data);
|
|
152
|
+
this.cachedToken = token;
|
|
153
|
+
this.tokenExpiresAt = now + 25 * 60 * 1e3;
|
|
154
|
+
return token;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof GitLabError) {
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
throw new GitLabError({
|
|
160
|
+
message: `Failed to get direct access token: ${error}`,
|
|
161
|
+
cause: error
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the Anthropic proxy base URL
|
|
167
|
+
*/
|
|
168
|
+
getAnthropicProxyUrl() {
|
|
169
|
+
return "https://cloud.gitlab.com/ai/v1/proxy/anthropic/";
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Invalidate the cached token
|
|
173
|
+
*/
|
|
174
|
+
invalidateToken() {
|
|
175
|
+
this.cachedToken = null;
|
|
176
|
+
this.tokenExpiresAt = 0;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/gitlab-agentic-language-model.ts
|
|
181
|
+
var GitLabAgenticLanguageModel = class {
|
|
182
|
+
specificationVersion = "v2";
|
|
183
|
+
modelId;
|
|
184
|
+
supportedUrls = {};
|
|
185
|
+
config;
|
|
186
|
+
directAccessClient;
|
|
187
|
+
anthropicClient = null;
|
|
188
|
+
constructor(modelId, config) {
|
|
189
|
+
this.modelId = modelId;
|
|
190
|
+
this.config = config;
|
|
191
|
+
this.directAccessClient = new GitLabDirectAccessClient({
|
|
192
|
+
instanceUrl: config.instanceUrl,
|
|
193
|
+
getHeaders: config.getHeaders,
|
|
194
|
+
refreshApiKey: config.refreshApiKey,
|
|
195
|
+
fetch: config.fetch,
|
|
196
|
+
featureFlags: config.featureFlags
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
get provider() {
|
|
200
|
+
return this.config.provider;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get or create an Anthropic client with valid credentials
|
|
204
|
+
* @param forceRefresh - If true, forces a token refresh before creating the client
|
|
205
|
+
*/
|
|
206
|
+
async getAnthropicClient(forceRefresh = false) {
|
|
207
|
+
const tokenData = await this.directAccessClient.getDirectAccessToken(forceRefresh);
|
|
208
|
+
this.anthropicClient = new import_sdk.default({
|
|
209
|
+
authToken: tokenData.token,
|
|
210
|
+
baseURL: this.directAccessClient.getAnthropicProxyUrl(),
|
|
211
|
+
defaultHeaders: tokenData.headers
|
|
212
|
+
});
|
|
213
|
+
return this.anthropicClient;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Check if an error is a token-related authentication error that can be retried
|
|
217
|
+
*/
|
|
218
|
+
isTokenError(error) {
|
|
219
|
+
if (error instanceof import_sdk.default.APIError) {
|
|
220
|
+
if (error.status === 401) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
const message = error.message?.toLowerCase() || "";
|
|
224
|
+
if (message.includes("token") && (message.includes("expired") || message.includes("revoked") || message.includes("invalid"))) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Convert AI SDK tools to Anthropic tool format
|
|
232
|
+
*/
|
|
233
|
+
convertTools(tools) {
|
|
234
|
+
if (!tools || tools.length === 0) {
|
|
235
|
+
return void 0;
|
|
236
|
+
}
|
|
237
|
+
return tools.filter((tool) => tool.type === "function").map((tool) => {
|
|
238
|
+
const schema = tool.inputSchema;
|
|
239
|
+
return {
|
|
240
|
+
name: tool.name,
|
|
241
|
+
description: tool.description || "",
|
|
242
|
+
input_schema: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: schema?.properties || {},
|
|
245
|
+
required: schema?.required || []
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Convert AI SDK tool choice to Anthropic format
|
|
252
|
+
*/
|
|
253
|
+
convertToolChoice(toolChoice) {
|
|
254
|
+
if (!toolChoice) {
|
|
255
|
+
return void 0;
|
|
256
|
+
}
|
|
257
|
+
switch (toolChoice.type) {
|
|
258
|
+
case "auto":
|
|
259
|
+
return { type: "auto" };
|
|
260
|
+
case "none":
|
|
261
|
+
return void 0;
|
|
262
|
+
case "required":
|
|
263
|
+
return { type: "any" };
|
|
264
|
+
case "tool":
|
|
265
|
+
return { type: "tool", name: toolChoice.toolName };
|
|
266
|
+
default:
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Convert AI SDK prompt to Anthropic messages format
|
|
272
|
+
*/
|
|
273
|
+
convertPrompt(prompt) {
|
|
274
|
+
let systemMessage;
|
|
275
|
+
const messages = [];
|
|
276
|
+
for (const message of prompt) {
|
|
277
|
+
if (message.role === "system") {
|
|
278
|
+
systemMessage = message.content;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (message.role === "user") {
|
|
282
|
+
const content = [];
|
|
283
|
+
for (const part of message.content) {
|
|
284
|
+
if (part.type === "text") {
|
|
285
|
+
content.push({ type: "text", text: part.text });
|
|
286
|
+
} else if (part.type === "file") {
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (content.length > 0) {
|
|
290
|
+
messages.push({ role: "user", content });
|
|
291
|
+
}
|
|
292
|
+
} else if (message.role === "assistant") {
|
|
293
|
+
const content = [];
|
|
294
|
+
for (const part of message.content) {
|
|
295
|
+
if (part.type === "text") {
|
|
296
|
+
content.push({ type: "text", text: part.text });
|
|
297
|
+
} else if (part.type === "tool-call") {
|
|
298
|
+
content.push({
|
|
299
|
+
type: "tool_use",
|
|
300
|
+
id: part.toolCallId,
|
|
301
|
+
name: part.toolName,
|
|
302
|
+
input: typeof part.input === "string" ? JSON.parse(part.input) : part.input
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (content.length > 0) {
|
|
307
|
+
messages.push({ role: "assistant", content });
|
|
308
|
+
}
|
|
309
|
+
} else if (message.role === "tool") {
|
|
310
|
+
const content = [];
|
|
311
|
+
for (const part of message.content) {
|
|
312
|
+
if (part.type === "tool-result") {
|
|
313
|
+
let resultContent;
|
|
314
|
+
if (part.output.type === "text") {
|
|
315
|
+
resultContent = part.output.value;
|
|
316
|
+
} else if (part.output.type === "json") {
|
|
317
|
+
resultContent = JSON.stringify(part.output.value);
|
|
318
|
+
} else if (part.output.type === "error-text") {
|
|
319
|
+
resultContent = part.output.value;
|
|
320
|
+
} else if (part.output.type === "error-json") {
|
|
321
|
+
resultContent = JSON.stringify(part.output.value);
|
|
322
|
+
} else {
|
|
323
|
+
resultContent = JSON.stringify(part.output);
|
|
324
|
+
}
|
|
325
|
+
content.push({
|
|
326
|
+
type: "tool_result",
|
|
327
|
+
tool_use_id: part.toolCallId,
|
|
328
|
+
content: resultContent,
|
|
329
|
+
is_error: part.output.type.startsWith("error")
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (content.length > 0) {
|
|
334
|
+
messages.push({ role: "user", content });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { system: systemMessage, messages };
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Convert Anthropic finish reason to AI SDK format
|
|
342
|
+
*/
|
|
343
|
+
convertFinishReason(stopReason) {
|
|
344
|
+
switch (stopReason) {
|
|
345
|
+
case "end_turn":
|
|
346
|
+
return "stop";
|
|
347
|
+
case "stop_sequence":
|
|
348
|
+
return "stop";
|
|
349
|
+
case "max_tokens":
|
|
350
|
+
return "length";
|
|
351
|
+
case "tool_use":
|
|
352
|
+
return "tool-calls";
|
|
353
|
+
default:
|
|
354
|
+
return "unknown";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async doGenerate(options) {
|
|
358
|
+
return this.doGenerateWithRetry(options, false);
|
|
359
|
+
}
|
|
360
|
+
async doGenerateWithRetry(options, isRetry) {
|
|
361
|
+
const client = await this.getAnthropicClient(isRetry);
|
|
362
|
+
const { system, messages } = this.convertPrompt(options.prompt);
|
|
363
|
+
const tools = this.convertTools(options.tools);
|
|
364
|
+
const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
|
|
365
|
+
const anthropicModel = this.config.anthropicModel || "claude-sonnet-4-5-20250929";
|
|
366
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
367
|
+
try {
|
|
368
|
+
const response = await client.messages.create({
|
|
369
|
+
model: anthropicModel,
|
|
370
|
+
max_tokens: maxTokens,
|
|
371
|
+
system,
|
|
372
|
+
messages,
|
|
373
|
+
tools,
|
|
374
|
+
tool_choice: tools ? toolChoice : void 0,
|
|
375
|
+
temperature: options.temperature,
|
|
376
|
+
top_p: options.topP,
|
|
377
|
+
stop_sequences: options.stopSequences
|
|
378
|
+
});
|
|
379
|
+
const content = [];
|
|
380
|
+
for (const block of response.content) {
|
|
381
|
+
if (block.type === "text") {
|
|
382
|
+
content.push({
|
|
383
|
+
type: "text",
|
|
384
|
+
text: block.text
|
|
385
|
+
});
|
|
386
|
+
} else if (block.type === "tool_use") {
|
|
387
|
+
content.push({
|
|
388
|
+
type: "tool-call",
|
|
389
|
+
toolCallId: block.id,
|
|
390
|
+
toolName: block.name,
|
|
391
|
+
input: JSON.stringify(block.input)
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const usage = {
|
|
396
|
+
inputTokens: response.usage.input_tokens,
|
|
397
|
+
outputTokens: response.usage.output_tokens,
|
|
398
|
+
totalTokens: response.usage.input_tokens + response.usage.output_tokens
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
content,
|
|
402
|
+
finishReason: this.convertFinishReason(response.stop_reason),
|
|
403
|
+
usage,
|
|
404
|
+
warnings: []
|
|
405
|
+
};
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (!isRetry && this.isTokenError(error)) {
|
|
408
|
+
this.directAccessClient.invalidateToken();
|
|
409
|
+
return this.doGenerateWithRetry(options, true);
|
|
410
|
+
}
|
|
411
|
+
if (error instanceof import_sdk.default.APIError) {
|
|
412
|
+
throw new GitLabError({
|
|
413
|
+
message: `Anthropic API error: ${error.message}`,
|
|
414
|
+
cause: error
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async doStream(options) {
|
|
421
|
+
return this.doStreamWithRetry(options, false);
|
|
422
|
+
}
|
|
423
|
+
async doStreamWithRetry(options, isRetry) {
|
|
424
|
+
const client = await this.getAnthropicClient(isRetry);
|
|
425
|
+
const { system, messages } = this.convertPrompt(options.prompt);
|
|
426
|
+
const tools = this.convertTools(options.tools);
|
|
427
|
+
const toolChoice = options.toolChoice?.type !== "none" ? this.convertToolChoice(options.toolChoice) : void 0;
|
|
428
|
+
const anthropicModel = this.config.anthropicModel || "claude-sonnet-4-5-20250929";
|
|
429
|
+
const maxTokens = options.maxOutputTokens || this.config.maxTokens || 8192;
|
|
430
|
+
const requestBody = {
|
|
431
|
+
model: anthropicModel,
|
|
432
|
+
max_tokens: maxTokens,
|
|
433
|
+
system,
|
|
434
|
+
messages,
|
|
435
|
+
tools,
|
|
436
|
+
tool_choice: tools ? toolChoice : void 0,
|
|
437
|
+
temperature: options.temperature,
|
|
438
|
+
top_p: options.topP,
|
|
439
|
+
stop_sequences: options.stopSequences,
|
|
440
|
+
stream: true
|
|
441
|
+
};
|
|
442
|
+
const self = this;
|
|
443
|
+
const stream = new ReadableStream({
|
|
444
|
+
start: async (controller) => {
|
|
445
|
+
try {
|
|
446
|
+
const anthropicStream = client.messages.stream(requestBody);
|
|
447
|
+
let currentTextBlockId = null;
|
|
448
|
+
let currentToolBlockId = null;
|
|
449
|
+
let currentToolName = null;
|
|
450
|
+
const usage = {
|
|
451
|
+
inputTokens: 0,
|
|
452
|
+
outputTokens: 0,
|
|
453
|
+
totalTokens: 0
|
|
454
|
+
};
|
|
455
|
+
let finishReason = "unknown";
|
|
456
|
+
controller.enqueue({
|
|
457
|
+
type: "stream-start",
|
|
458
|
+
warnings: []
|
|
459
|
+
});
|
|
460
|
+
for await (const event of anthropicStream) {
|
|
461
|
+
switch (event.type) {
|
|
462
|
+
case "message_start":
|
|
463
|
+
if (event.message.usage) {
|
|
464
|
+
usage.inputTokens = event.message.usage.input_tokens;
|
|
465
|
+
}
|
|
466
|
+
controller.enqueue({
|
|
467
|
+
type: "response-metadata",
|
|
468
|
+
id: event.message.id,
|
|
469
|
+
modelId: event.message.model
|
|
470
|
+
});
|
|
471
|
+
break;
|
|
472
|
+
case "content_block_start":
|
|
473
|
+
if (event.content_block.type === "text") {
|
|
474
|
+
currentTextBlockId = `text-${event.index}`;
|
|
475
|
+
controller.enqueue({
|
|
476
|
+
type: "text-start",
|
|
477
|
+
id: currentTextBlockId
|
|
478
|
+
});
|
|
479
|
+
} else if (event.content_block.type === "tool_use") {
|
|
480
|
+
currentToolBlockId = event.content_block.id;
|
|
481
|
+
currentToolName = event.content_block.name;
|
|
482
|
+
controller.enqueue({
|
|
483
|
+
type: "tool-input-start",
|
|
484
|
+
id: currentToolBlockId,
|
|
485
|
+
toolName: currentToolName
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
break;
|
|
489
|
+
case "content_block_delta":
|
|
490
|
+
if (event.delta.type === "text_delta" && currentTextBlockId) {
|
|
491
|
+
controller.enqueue({
|
|
492
|
+
type: "text-delta",
|
|
493
|
+
id: currentTextBlockId,
|
|
494
|
+
delta: event.delta.text
|
|
495
|
+
});
|
|
496
|
+
} else if (event.delta.type === "input_json_delta" && currentToolBlockId) {
|
|
497
|
+
controller.enqueue({
|
|
498
|
+
type: "tool-input-delta",
|
|
499
|
+
id: currentToolBlockId,
|
|
500
|
+
delta: event.delta.partial_json
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
case "content_block_stop":
|
|
505
|
+
if (currentTextBlockId) {
|
|
506
|
+
controller.enqueue({
|
|
507
|
+
type: "text-end",
|
|
508
|
+
id: currentTextBlockId
|
|
509
|
+
});
|
|
510
|
+
currentTextBlockId = null;
|
|
511
|
+
}
|
|
512
|
+
if (currentToolBlockId) {
|
|
513
|
+
controller.enqueue({
|
|
514
|
+
type: "tool-input-end",
|
|
515
|
+
id: currentToolBlockId
|
|
516
|
+
});
|
|
517
|
+
currentToolBlockId = null;
|
|
518
|
+
currentToolName = null;
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
case "message_delta":
|
|
522
|
+
if (event.usage) {
|
|
523
|
+
usage.outputTokens = event.usage.output_tokens;
|
|
524
|
+
usage.totalTokens = (usage.inputTokens || 0) + event.usage.output_tokens;
|
|
525
|
+
}
|
|
526
|
+
if (event.delta.stop_reason) {
|
|
527
|
+
finishReason = self.convertFinishReason(event.delta.stop_reason);
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
case "message_stop": {
|
|
531
|
+
const finalMessage = await anthropicStream.finalMessage();
|
|
532
|
+
for (const block of finalMessage.content) {
|
|
533
|
+
if (block.type === "tool_use") {
|
|
534
|
+
controller.enqueue({
|
|
535
|
+
type: "tool-call",
|
|
536
|
+
toolCallId: block.id,
|
|
537
|
+
toolName: block.name,
|
|
538
|
+
input: JSON.stringify(block.input)
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
controller.enqueue({
|
|
543
|
+
type: "finish",
|
|
544
|
+
finishReason,
|
|
545
|
+
usage
|
|
546
|
+
});
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
controller.close();
|
|
552
|
+
} catch (error) {
|
|
553
|
+
if (!isRetry && self.isTokenError(error)) {
|
|
554
|
+
self.directAccessClient.invalidateToken();
|
|
555
|
+
controller.enqueue({
|
|
556
|
+
type: "error",
|
|
557
|
+
error: new GitLabError({
|
|
558
|
+
message: "TOKEN_REFRESH_NEEDED",
|
|
559
|
+
cause: error
|
|
560
|
+
})
|
|
561
|
+
});
|
|
562
|
+
controller.close();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (error instanceof import_sdk.default.APIError) {
|
|
566
|
+
controller.enqueue({
|
|
567
|
+
type: "error",
|
|
568
|
+
error: new GitLabError({
|
|
569
|
+
message: `Anthropic API error: ${error.message}`,
|
|
570
|
+
cause: error
|
|
571
|
+
})
|
|
572
|
+
});
|
|
573
|
+
} else {
|
|
574
|
+
controller.enqueue({
|
|
575
|
+
type: "error",
|
|
576
|
+
error
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
controller.close();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
return {
|
|
584
|
+
stream,
|
|
585
|
+
request: { body: requestBody }
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
// src/gitlab-oauth-types.ts
|
|
591
|
+
var BUNDLED_CLIENT_ID = "36f2a70cddeb5a0889d4fd8295c241b7e9848e89cf9e599d0eed2d8e5350fbf5";
|
|
592
|
+
var GITLAB_COM_URL = "https://gitlab.com";
|
|
593
|
+
var TOKEN_EXPIRY_SKEW_MS = 5 * 60 * 1e3;
|
|
594
|
+
var OAUTH_SCOPES = ["api"];
|
|
595
|
+
|
|
596
|
+
// src/gitlab-oauth-manager.ts
|
|
597
|
+
var GitLabOAuthManager = class {
|
|
598
|
+
fetch;
|
|
599
|
+
constructor(fetchImpl = fetch) {
|
|
600
|
+
this.fetch = fetchImpl;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Check if a token is expired
|
|
604
|
+
*/
|
|
605
|
+
isTokenExpired(expiresAt) {
|
|
606
|
+
return Date.now() >= expiresAt;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Check if a token needs refresh (within skew window)
|
|
610
|
+
*/
|
|
611
|
+
needsRefresh(expiresAt) {
|
|
612
|
+
return Date.now() >= expiresAt - TOKEN_EXPIRY_SKEW_MS;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Refresh tokens if needed
|
|
616
|
+
* Returns the same tokens if refresh is not needed, or new tokens if refreshed
|
|
617
|
+
*/
|
|
618
|
+
async refreshIfNeeded(tokens, clientId) {
|
|
619
|
+
if (!this.needsRefresh(tokens.expiresAt)) {
|
|
620
|
+
return tokens;
|
|
621
|
+
}
|
|
622
|
+
if (this.isTokenExpired(tokens.expiresAt)) {
|
|
623
|
+
throw new GitLabError({
|
|
624
|
+
message: "OAuth token has expired and cannot be used"
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return this.exchangeRefreshToken({
|
|
628
|
+
instanceUrl: tokens.instanceUrl,
|
|
629
|
+
refreshToken: tokens.refreshToken,
|
|
630
|
+
clientId
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Exchange authorization code for tokens
|
|
635
|
+
* Based on gitlab-vscode-extension createOAuthAccountFromCode
|
|
636
|
+
*/
|
|
637
|
+
async exchangeAuthorizationCode(params) {
|
|
638
|
+
const { instanceUrl, code, codeVerifier, clientId, redirectUri } = params;
|
|
639
|
+
const tokenResponse = await this.exchangeToken({
|
|
640
|
+
instanceUrl,
|
|
641
|
+
grantType: "authorization_code",
|
|
642
|
+
code,
|
|
643
|
+
codeVerifier,
|
|
644
|
+
clientId: clientId || this.getClientId(instanceUrl),
|
|
645
|
+
redirectUri
|
|
646
|
+
});
|
|
647
|
+
return this.createTokensFromResponse(tokenResponse, instanceUrl);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Exchange refresh token for new tokens
|
|
651
|
+
* Based on gitlab-vscode-extension TokenExchangeService
|
|
652
|
+
*/
|
|
653
|
+
async exchangeRefreshToken(params) {
|
|
654
|
+
const { instanceUrl, refreshToken, clientId } = params;
|
|
655
|
+
const tokenResponse = await this.exchangeToken({
|
|
656
|
+
instanceUrl,
|
|
657
|
+
grantType: "refresh_token",
|
|
658
|
+
refreshToken,
|
|
659
|
+
clientId: clientId || this.getClientId(instanceUrl)
|
|
660
|
+
});
|
|
661
|
+
return this.createTokensFromResponse(tokenResponse, instanceUrl);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Get the OAuth client ID for an instance
|
|
665
|
+
*/
|
|
666
|
+
getClientId(instanceUrl) {
|
|
667
|
+
if (instanceUrl === GITLAB_COM_URL) {
|
|
668
|
+
return BUNDLED_CLIENT_ID;
|
|
669
|
+
}
|
|
670
|
+
throw new GitLabError({
|
|
671
|
+
message: `No OAuth client ID configured for instance ${instanceUrl}. Please provide a clientId parameter.`
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Exchange token with GitLab OAuth endpoint
|
|
676
|
+
* Based on gitlab-vscode-extension GitLabService.exchangeToken
|
|
677
|
+
*/
|
|
678
|
+
async exchangeToken(params) {
|
|
679
|
+
const { instanceUrl, grantType, code, codeVerifier, refreshToken, clientId, redirectUri } = params;
|
|
680
|
+
const body = {
|
|
681
|
+
client_id: clientId,
|
|
682
|
+
grant_type: grantType
|
|
683
|
+
};
|
|
684
|
+
if (grantType === "authorization_code") {
|
|
685
|
+
if (!code || !codeVerifier || !redirectUri) {
|
|
686
|
+
throw new GitLabError({
|
|
687
|
+
message: "Authorization code, code verifier, and redirect URI are required for authorization_code grant"
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
body.code = code;
|
|
691
|
+
body.code_verifier = codeVerifier;
|
|
692
|
+
body.redirect_uri = redirectUri;
|
|
693
|
+
} else if (grantType === "refresh_token") {
|
|
694
|
+
if (!refreshToken) {
|
|
695
|
+
throw new GitLabError({
|
|
696
|
+
message: "Refresh token is required for refresh_token grant"
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
body.refresh_token = refreshToken;
|
|
700
|
+
}
|
|
701
|
+
const url = `${instanceUrl}/oauth/token`;
|
|
702
|
+
try {
|
|
703
|
+
const response = await this.fetch(url, {
|
|
704
|
+
method: "POST",
|
|
705
|
+
headers: {
|
|
706
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
707
|
+
},
|
|
708
|
+
body: new URLSearchParams(body).toString()
|
|
709
|
+
});
|
|
710
|
+
if (!response.ok) {
|
|
711
|
+
const errorText = await response.text();
|
|
712
|
+
throw new GitLabError({
|
|
713
|
+
message: `OAuth token exchange failed: ${response.status} ${response.statusText}`,
|
|
714
|
+
cause: new Error(errorText)
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const data = await response.json();
|
|
718
|
+
return data;
|
|
719
|
+
} catch (error) {
|
|
720
|
+
if (error instanceof GitLabError) {
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
throw new GitLabError({
|
|
724
|
+
message: `Failed to exchange OAuth token: ${error instanceof Error ? error.message : String(error)}`,
|
|
725
|
+
cause: error instanceof Error ? error : void 0
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Create GitLabOAuthTokens from token response
|
|
731
|
+
*/
|
|
732
|
+
createTokensFromResponse(response, instanceUrl) {
|
|
733
|
+
const expiresAt = this.createExpiresTimestamp(response);
|
|
734
|
+
return {
|
|
735
|
+
accessToken: response.access_token,
|
|
736
|
+
refreshToken: response.refresh_token || "",
|
|
737
|
+
expiresAt,
|
|
738
|
+
instanceUrl
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Create expiry timestamp from token response
|
|
743
|
+
* Based on gitlab-vscode-extension createExpiresTimestamp
|
|
744
|
+
*/
|
|
745
|
+
createExpiresTimestamp(response) {
|
|
746
|
+
const createdAt = response.created_at * 1e3;
|
|
747
|
+
const expiresIn = response.expires_in * 1e3;
|
|
748
|
+
return createdAt + expiresIn;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
// src/gitlab-provider.ts
|
|
753
|
+
var fs = __toESM(require("fs"));
|
|
754
|
+
var path = __toESM(require("path"));
|
|
755
|
+
var os = __toESM(require("os"));
|
|
756
|
+
var VERSION = "0.0.1";
|
|
757
|
+
function getOpenCodeAuthPath() {
|
|
758
|
+
const homeDir = os.homedir();
|
|
759
|
+
const xdgDataHome = process.env.XDG_DATA_HOME;
|
|
760
|
+
if (xdgDataHome) {
|
|
761
|
+
return path.join(xdgDataHome, "opencode", "auth.json");
|
|
762
|
+
}
|
|
763
|
+
if (process.platform !== "win32") {
|
|
764
|
+
return path.join(homeDir, ".local", "share", "opencode", "auth.json");
|
|
765
|
+
}
|
|
766
|
+
return path.join(homeDir, ".opencode", "auth.json");
|
|
767
|
+
}
|
|
768
|
+
async function loadOpenCodeAuth(instanceUrl) {
|
|
769
|
+
try {
|
|
770
|
+
const authPath = getOpenCodeAuthPath();
|
|
771
|
+
if (!fs.existsSync(authPath)) {
|
|
772
|
+
return void 0;
|
|
773
|
+
}
|
|
774
|
+
const authData = JSON.parse(fs.readFileSync(authPath, "utf-8"));
|
|
775
|
+
if (authData.gitlab?.type === "oauth") {
|
|
776
|
+
const gitlabAuth = authData.gitlab;
|
|
777
|
+
if (gitlabAuth.enterpriseUrl === instanceUrl || gitlabAuth.enterpriseUrl === instanceUrl.replace(/\/$/, "")) {
|
|
778
|
+
return gitlabAuth;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const normalizedUrl = instanceUrl.replace(/\/$/, "");
|
|
782
|
+
const auth = authData[normalizedUrl] || authData[`${normalizedUrl}/`];
|
|
783
|
+
return auth;
|
|
784
|
+
} catch (error) {
|
|
785
|
+
return void 0;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async function loadApiKey(options, instanceUrl, clientId) {
|
|
789
|
+
if (options.apiKey) {
|
|
790
|
+
return options.apiKey;
|
|
791
|
+
}
|
|
792
|
+
const auth = await loadOpenCodeAuth(instanceUrl);
|
|
793
|
+
if (auth?.type === "oauth") {
|
|
794
|
+
const oauthManager = new GitLabOAuthManager();
|
|
795
|
+
if (oauthManager.needsRefresh(auth.expires)) {
|
|
796
|
+
try {
|
|
797
|
+
const refreshed = await oauthManager.exchangeRefreshToken({
|
|
798
|
+
instanceUrl,
|
|
799
|
+
refreshToken: auth.refresh,
|
|
800
|
+
clientId
|
|
801
|
+
});
|
|
802
|
+
const authPath = getOpenCodeAuthPath();
|
|
803
|
+
const authData = JSON.parse(fs.readFileSync(authPath, "utf-8"));
|
|
804
|
+
const normalizedUrl = instanceUrl.replace(/\/$/, "");
|
|
805
|
+
authData[normalizedUrl] = {
|
|
806
|
+
type: "oauth",
|
|
807
|
+
refresh: refreshed.refreshToken,
|
|
808
|
+
access: refreshed.accessToken,
|
|
809
|
+
expires: refreshed.expiresAt,
|
|
810
|
+
instanceUrl
|
|
811
|
+
};
|
|
812
|
+
fs.writeFileSync(authPath, JSON.stringify(authData, null, 2));
|
|
813
|
+
return refreshed.accessToken;
|
|
814
|
+
} catch (error) {
|
|
815
|
+
console.warn(
|
|
816
|
+
`Failed to refresh OAuth token: ${error instanceof Error ? error.message : String(error)}`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
} else {
|
|
820
|
+
return auth.access;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const apiKey = process.env[options.environmentVariableName];
|
|
824
|
+
if (!apiKey) {
|
|
825
|
+
throw new GitLabError({
|
|
826
|
+
message: `${options.description} API key is missing. Pass it as the 'apiKey' parameter, set the ${options.environmentVariableName} environment variable, or authenticate with 'opencode auth login gitlab'.`
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
return apiKey;
|
|
830
|
+
}
|
|
831
|
+
function withUserAgentSuffix(headers, suffix) {
|
|
832
|
+
const userAgent = headers["User-Agent"];
|
|
833
|
+
return {
|
|
834
|
+
...headers,
|
|
835
|
+
"User-Agent": userAgent ? `${userAgent} ${suffix}` : suffix
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function createGitLab(options = {}) {
|
|
839
|
+
const instanceUrl = options.instanceUrl ?? "https://gitlab.com";
|
|
840
|
+
const providerName = options.name ?? "gitlab";
|
|
841
|
+
let cachedApiKey;
|
|
842
|
+
let apiKeyPromise;
|
|
843
|
+
const getApiKey = async () => {
|
|
844
|
+
if (cachedApiKey) {
|
|
845
|
+
return cachedApiKey;
|
|
846
|
+
}
|
|
847
|
+
if (apiKeyPromise) {
|
|
848
|
+
return apiKeyPromise;
|
|
849
|
+
}
|
|
850
|
+
apiKeyPromise = loadApiKey(
|
|
851
|
+
{
|
|
852
|
+
apiKey: options.apiKey,
|
|
853
|
+
environmentVariableName: "GITLAB_TOKEN",
|
|
854
|
+
description: "GitLab"
|
|
855
|
+
},
|
|
856
|
+
instanceUrl,
|
|
857
|
+
options.clientId
|
|
858
|
+
);
|
|
859
|
+
cachedApiKey = await apiKeyPromise;
|
|
860
|
+
apiKeyPromise = void 0;
|
|
861
|
+
return cachedApiKey;
|
|
862
|
+
};
|
|
863
|
+
const refreshApiKey = async () => {
|
|
864
|
+
cachedApiKey = void 0;
|
|
865
|
+
apiKeyPromise = void 0;
|
|
866
|
+
cachedApiKey = await getApiKey();
|
|
867
|
+
};
|
|
868
|
+
const getHeaders = () => {
|
|
869
|
+
const apiKey = cachedApiKey || options.apiKey || process.env["GITLAB_TOKEN"] || "";
|
|
870
|
+
if (!apiKey) {
|
|
871
|
+
throw new GitLabError({
|
|
872
|
+
message: "GitLab API key is missing. Pass it as the 'apiKey' parameter, set the GITLAB_TOKEN environment variable, or authenticate with 'opencode auth login gitlab'."
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
return withUserAgentSuffix(
|
|
876
|
+
{
|
|
877
|
+
Authorization: `Bearer ${apiKey}`,
|
|
878
|
+
"Content-Type": "application/json",
|
|
879
|
+
...options.headers
|
|
880
|
+
},
|
|
881
|
+
`ai-sdk-gitlab/${VERSION}`
|
|
882
|
+
);
|
|
883
|
+
};
|
|
884
|
+
getApiKey().catch(() => {
|
|
885
|
+
});
|
|
886
|
+
const createAgenticChatModel = (modelId, agenticOptions) => {
|
|
887
|
+
const featureFlags = {
|
|
888
|
+
DuoAgentPlatformNext: true,
|
|
889
|
+
...options.featureFlags,
|
|
890
|
+
...agenticOptions?.featureFlags
|
|
891
|
+
};
|
|
892
|
+
return new GitLabAgenticLanguageModel(modelId, {
|
|
893
|
+
provider: `${providerName}.agentic`,
|
|
894
|
+
instanceUrl,
|
|
895
|
+
getHeaders,
|
|
896
|
+
refreshApiKey,
|
|
897
|
+
fetch: options.fetch,
|
|
898
|
+
anthropicModel: agenticOptions?.anthropicModel,
|
|
899
|
+
maxTokens: agenticOptions?.maxTokens,
|
|
900
|
+
featureFlags
|
|
901
|
+
});
|
|
902
|
+
};
|
|
903
|
+
const createDefaultModel = (modelId) => {
|
|
904
|
+
return createAgenticChatModel(modelId);
|
|
905
|
+
};
|
|
906
|
+
const provider = Object.assign((modelId) => createDefaultModel(modelId), {
|
|
907
|
+
specificationVersion: "v2",
|
|
908
|
+
languageModel: createDefaultModel,
|
|
909
|
+
chat: createDefaultModel,
|
|
910
|
+
agenticChat: createAgenticChatModel
|
|
911
|
+
});
|
|
912
|
+
provider.textEmbeddingModel = (modelId) => {
|
|
913
|
+
throw new GitLabError({
|
|
914
|
+
message: `GitLab provider does not support text embedding models. Model ID: ${modelId}`
|
|
915
|
+
});
|
|
916
|
+
};
|
|
917
|
+
provider.imageModel = (modelId) => {
|
|
918
|
+
throw new GitLabError({
|
|
919
|
+
message: `GitLab provider does not support image models. Model ID: ${modelId}`
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
return provider;
|
|
923
|
+
}
|
|
924
|
+
var gitlab = createGitLab();
|
|
925
|
+
|
|
926
|
+
// src/gitlab-api-tools.ts
|
|
927
|
+
var GITLAB_API_TOOLS = [
|
|
928
|
+
// Merge Request Tools
|
|
929
|
+
{
|
|
930
|
+
name: "gitlab_get_merge_request",
|
|
931
|
+
description: `Get details of a specific merge request by project and MR IID.
|
|
932
|
+
Returns: title, description, state, author, assignees, reviewers, labels, diff stats, and discussion notes.`,
|
|
933
|
+
input_schema: {
|
|
934
|
+
type: "object",
|
|
935
|
+
properties: {
|
|
936
|
+
project_id: {
|
|
937
|
+
type: "string",
|
|
938
|
+
description: 'The project ID or URL-encoded path (e.g., "gitlab-org/gitlab" or "123")'
|
|
939
|
+
},
|
|
940
|
+
mr_iid: {
|
|
941
|
+
type: "number",
|
|
942
|
+
description: "The internal ID of the merge request within the project"
|
|
943
|
+
},
|
|
944
|
+
include_changes: {
|
|
945
|
+
type: "boolean",
|
|
946
|
+
description: "Whether to include the list of changed files (default: false)"
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
required: ["project_id", "mr_iid"]
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
name: "gitlab_list_merge_requests",
|
|
954
|
+
description: `List merge requests for a project or search globally.
|
|
955
|
+
Can filter by state (opened, closed, merged, all), scope (assigned_to_me, created_by_me), and labels.`,
|
|
956
|
+
input_schema: {
|
|
957
|
+
type: "object",
|
|
958
|
+
properties: {
|
|
959
|
+
project_id: {
|
|
960
|
+
type: "string",
|
|
961
|
+
description: "The project ID or path. If not provided, searches globally."
|
|
962
|
+
},
|
|
963
|
+
state: {
|
|
964
|
+
type: "string",
|
|
965
|
+
enum: ["opened", "closed", "merged", "all"],
|
|
966
|
+
description: "Filter by MR state (default: opened)"
|
|
967
|
+
},
|
|
968
|
+
scope: {
|
|
969
|
+
type: "string",
|
|
970
|
+
enum: ["assigned_to_me", "created_by_me", "all"],
|
|
971
|
+
description: "Filter by scope"
|
|
972
|
+
},
|
|
973
|
+
search: {
|
|
974
|
+
type: "string",
|
|
975
|
+
description: "Search MRs by title or description"
|
|
976
|
+
},
|
|
977
|
+
labels: {
|
|
978
|
+
type: "string",
|
|
979
|
+
description: "Comma-separated list of labels to filter by"
|
|
980
|
+
},
|
|
981
|
+
limit: {
|
|
982
|
+
type: "number",
|
|
983
|
+
description: "Maximum number of results (default: 20)"
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
required: []
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
name: "gitlab_get_mr_changes",
|
|
991
|
+
description: `Get the file changes (diff) for a merge request.
|
|
992
|
+
Returns the list of files changed with their diffs.`,
|
|
993
|
+
input_schema: {
|
|
994
|
+
type: "object",
|
|
995
|
+
properties: {
|
|
996
|
+
project_id: {
|
|
997
|
+
type: "string",
|
|
998
|
+
description: "The project ID or URL-encoded path"
|
|
999
|
+
},
|
|
1000
|
+
mr_iid: {
|
|
1001
|
+
type: "number",
|
|
1002
|
+
description: "The internal ID of the merge request"
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
required: ["project_id", "mr_iid"]
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: "gitlab_list_mr_discussions",
|
|
1010
|
+
description: `List discussions (comments/threads) on a merge request.
|
|
1011
|
+
Returns all discussion threads including resolved status.`,
|
|
1012
|
+
input_schema: {
|
|
1013
|
+
type: "object",
|
|
1014
|
+
properties: {
|
|
1015
|
+
project_id: {
|
|
1016
|
+
type: "string",
|
|
1017
|
+
description: "The project ID or URL-encoded path"
|
|
1018
|
+
},
|
|
1019
|
+
mr_iid: {
|
|
1020
|
+
type: "number",
|
|
1021
|
+
description: "The internal ID of the merge request"
|
|
1022
|
+
}
|
|
1023
|
+
},
|
|
1024
|
+
required: ["project_id", "mr_iid"]
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
name: "gitlab_create_mr_note",
|
|
1029
|
+
description: `Add a comment/note to a merge request.`,
|
|
1030
|
+
input_schema: {
|
|
1031
|
+
type: "object",
|
|
1032
|
+
properties: {
|
|
1033
|
+
project_id: {
|
|
1034
|
+
type: "string",
|
|
1035
|
+
description: "The project ID or URL-encoded path"
|
|
1036
|
+
},
|
|
1037
|
+
mr_iid: {
|
|
1038
|
+
type: "number",
|
|
1039
|
+
description: "The internal ID of the merge request"
|
|
1040
|
+
},
|
|
1041
|
+
body: {
|
|
1042
|
+
type: "string",
|
|
1043
|
+
description: "The content of the note/comment (supports Markdown)"
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
required: ["project_id", "mr_iid", "body"]
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
// Issue Tools
|
|
1050
|
+
{
|
|
1051
|
+
name: "gitlab_get_issue",
|
|
1052
|
+
description: `Get details of a specific issue by project and issue IID.
|
|
1053
|
+
Returns: title, description, state, author, assignees, labels, milestone, weight, and comments.`,
|
|
1054
|
+
input_schema: {
|
|
1055
|
+
type: "object",
|
|
1056
|
+
properties: {
|
|
1057
|
+
project_id: {
|
|
1058
|
+
type: "string",
|
|
1059
|
+
description: "The project ID or URL-encoded path"
|
|
1060
|
+
},
|
|
1061
|
+
issue_iid: {
|
|
1062
|
+
type: "number",
|
|
1063
|
+
description: "The internal ID of the issue within the project"
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
required: ["project_id", "issue_iid"]
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
name: "gitlab_list_issues",
|
|
1071
|
+
description: `List issues for a project or search globally.
|
|
1072
|
+
Can filter by state, labels, assignee, milestone.`,
|
|
1073
|
+
input_schema: {
|
|
1074
|
+
type: "object",
|
|
1075
|
+
properties: {
|
|
1076
|
+
project_id: {
|
|
1077
|
+
type: "string",
|
|
1078
|
+
description: "The project ID or path. If not provided, searches globally."
|
|
1079
|
+
},
|
|
1080
|
+
state: {
|
|
1081
|
+
type: "string",
|
|
1082
|
+
enum: ["opened", "closed", "all"],
|
|
1083
|
+
description: "Filter by issue state (default: opened)"
|
|
1084
|
+
},
|
|
1085
|
+
scope: {
|
|
1086
|
+
type: "string",
|
|
1087
|
+
enum: ["assigned_to_me", "created_by_me", "all"],
|
|
1088
|
+
description: "Filter by scope"
|
|
1089
|
+
},
|
|
1090
|
+
search: {
|
|
1091
|
+
type: "string",
|
|
1092
|
+
description: "Search issues by title or description"
|
|
1093
|
+
},
|
|
1094
|
+
labels: {
|
|
1095
|
+
type: "string",
|
|
1096
|
+
description: "Comma-separated list of labels to filter by"
|
|
1097
|
+
},
|
|
1098
|
+
milestone: {
|
|
1099
|
+
type: "string",
|
|
1100
|
+
description: "Filter by milestone title"
|
|
1101
|
+
},
|
|
1102
|
+
limit: {
|
|
1103
|
+
type: "number",
|
|
1104
|
+
description: "Maximum number of results (default: 20)"
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
required: []
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
name: "gitlab_create_issue_note",
|
|
1112
|
+
description: `Add a comment/note to an issue.`,
|
|
1113
|
+
input_schema: {
|
|
1114
|
+
type: "object",
|
|
1115
|
+
properties: {
|
|
1116
|
+
project_id: {
|
|
1117
|
+
type: "string",
|
|
1118
|
+
description: "The project ID or URL-encoded path"
|
|
1119
|
+
},
|
|
1120
|
+
issue_iid: {
|
|
1121
|
+
type: "number",
|
|
1122
|
+
description: "The internal ID of the issue"
|
|
1123
|
+
},
|
|
1124
|
+
body: {
|
|
1125
|
+
type: "string",
|
|
1126
|
+
description: "The content of the note/comment (supports Markdown)"
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
required: ["project_id", "issue_iid", "body"]
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
// Pipeline/CI Tools
|
|
1133
|
+
{
|
|
1134
|
+
name: "gitlab_list_pipelines",
|
|
1135
|
+
description: `List pipelines for a project.
|
|
1136
|
+
Can filter by status, ref (branch/tag), username.`,
|
|
1137
|
+
input_schema: {
|
|
1138
|
+
type: "object",
|
|
1139
|
+
properties: {
|
|
1140
|
+
project_id: {
|
|
1141
|
+
type: "string",
|
|
1142
|
+
description: "The project ID or URL-encoded path"
|
|
1143
|
+
},
|
|
1144
|
+
status: {
|
|
1145
|
+
type: "string",
|
|
1146
|
+
enum: ["running", "pending", "success", "failed", "canceled", "skipped", "manual"],
|
|
1147
|
+
description: "Filter by pipeline status"
|
|
1148
|
+
},
|
|
1149
|
+
ref: {
|
|
1150
|
+
type: "string",
|
|
1151
|
+
description: "Filter by branch or tag name"
|
|
1152
|
+
},
|
|
1153
|
+
limit: {
|
|
1154
|
+
type: "number",
|
|
1155
|
+
description: "Maximum number of results (default: 20)"
|
|
1156
|
+
}
|
|
1157
|
+
},
|
|
1158
|
+
required: ["project_id"]
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
name: "gitlab_get_pipeline",
|
|
1163
|
+
description: `Get details of a specific pipeline including its jobs.`,
|
|
1164
|
+
input_schema: {
|
|
1165
|
+
type: "object",
|
|
1166
|
+
properties: {
|
|
1167
|
+
project_id: {
|
|
1168
|
+
type: "string",
|
|
1169
|
+
description: "The project ID or URL-encoded path"
|
|
1170
|
+
},
|
|
1171
|
+
pipeline_id: {
|
|
1172
|
+
type: "number",
|
|
1173
|
+
description: "The ID of the pipeline"
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
required: ["project_id", "pipeline_id"]
|
|
1177
|
+
}
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: "gitlab_list_pipeline_jobs",
|
|
1181
|
+
description: `List jobs for a pipeline, optionally filter by scope (failed, success, etc).`,
|
|
1182
|
+
input_schema: {
|
|
1183
|
+
type: "object",
|
|
1184
|
+
properties: {
|
|
1185
|
+
project_id: {
|
|
1186
|
+
type: "string",
|
|
1187
|
+
description: "The project ID or URL-encoded path"
|
|
1188
|
+
},
|
|
1189
|
+
pipeline_id: {
|
|
1190
|
+
type: "number",
|
|
1191
|
+
description: "The ID of the pipeline"
|
|
1192
|
+
},
|
|
1193
|
+
scope: {
|
|
1194
|
+
type: "string",
|
|
1195
|
+
enum: [
|
|
1196
|
+
"created",
|
|
1197
|
+
"pending",
|
|
1198
|
+
"running",
|
|
1199
|
+
"failed",
|
|
1200
|
+
"success",
|
|
1201
|
+
"canceled",
|
|
1202
|
+
"skipped",
|
|
1203
|
+
"manual"
|
|
1204
|
+
],
|
|
1205
|
+
description: "Filter jobs by scope/status"
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
required: ["project_id", "pipeline_id"]
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
name: "gitlab_get_job_log",
|
|
1213
|
+
description: `Get the log/trace output of a specific CI job.`,
|
|
1214
|
+
input_schema: {
|
|
1215
|
+
type: "object",
|
|
1216
|
+
properties: {
|
|
1217
|
+
project_id: {
|
|
1218
|
+
type: "string",
|
|
1219
|
+
description: "The project ID or URL-encoded path"
|
|
1220
|
+
},
|
|
1221
|
+
job_id: {
|
|
1222
|
+
type: "number",
|
|
1223
|
+
description: "The ID of the job"
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
required: ["project_id", "job_id"]
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
name: "gitlab_retry_job",
|
|
1231
|
+
description: `Retry a failed or canceled CI job.`,
|
|
1232
|
+
input_schema: {
|
|
1233
|
+
type: "object",
|
|
1234
|
+
properties: {
|
|
1235
|
+
project_id: {
|
|
1236
|
+
type: "string",
|
|
1237
|
+
description: "The project ID or URL-encoded path"
|
|
1238
|
+
},
|
|
1239
|
+
job_id: {
|
|
1240
|
+
type: "number",
|
|
1241
|
+
description: "The ID of the job to retry"
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
required: ["project_id", "job_id"]
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
// Repository Tools
|
|
1248
|
+
{
|
|
1249
|
+
name: "gitlab_get_file",
|
|
1250
|
+
description: `Get the contents of a file from a repository.`,
|
|
1251
|
+
input_schema: {
|
|
1252
|
+
type: "object",
|
|
1253
|
+
properties: {
|
|
1254
|
+
project_id: {
|
|
1255
|
+
type: "string",
|
|
1256
|
+
description: "The project ID or URL-encoded path"
|
|
1257
|
+
},
|
|
1258
|
+
file_path: {
|
|
1259
|
+
type: "string",
|
|
1260
|
+
description: "Path to the file in the repository"
|
|
1261
|
+
},
|
|
1262
|
+
ref: {
|
|
1263
|
+
type: "string",
|
|
1264
|
+
description: "Branch, tag, or commit SHA (default: default branch)"
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
required: ["project_id", "file_path"]
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
name: "gitlab_list_commits",
|
|
1272
|
+
description: `List commits in a repository. Can filter by branch/ref and path.`,
|
|
1273
|
+
input_schema: {
|
|
1274
|
+
type: "object",
|
|
1275
|
+
properties: {
|
|
1276
|
+
project_id: {
|
|
1277
|
+
type: "string",
|
|
1278
|
+
description: "The project ID or URL-encoded path"
|
|
1279
|
+
},
|
|
1280
|
+
ref: {
|
|
1281
|
+
type: "string",
|
|
1282
|
+
description: "Branch or tag name"
|
|
1283
|
+
},
|
|
1284
|
+
path: {
|
|
1285
|
+
type: "string",
|
|
1286
|
+
description: "File or directory path to filter commits"
|
|
1287
|
+
},
|
|
1288
|
+
since: {
|
|
1289
|
+
type: "string",
|
|
1290
|
+
description: "Only commits after this date (ISO 8601 format)"
|
|
1291
|
+
},
|
|
1292
|
+
until: {
|
|
1293
|
+
type: "string",
|
|
1294
|
+
description: "Only commits before this date (ISO 8601 format)"
|
|
1295
|
+
},
|
|
1296
|
+
limit: {
|
|
1297
|
+
type: "number",
|
|
1298
|
+
description: "Maximum number of results (default: 20)"
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
required: ["project_id"]
|
|
1302
|
+
}
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
name: "gitlab_get_commit_diff",
|
|
1306
|
+
description: `Get the diff for a specific commit.`,
|
|
1307
|
+
input_schema: {
|
|
1308
|
+
type: "object",
|
|
1309
|
+
properties: {
|
|
1310
|
+
project_id: {
|
|
1311
|
+
type: "string",
|
|
1312
|
+
description: "The project ID or URL-encoded path"
|
|
1313
|
+
},
|
|
1314
|
+
sha: {
|
|
1315
|
+
type: "string",
|
|
1316
|
+
description: "The commit SHA"
|
|
1317
|
+
}
|
|
1318
|
+
},
|
|
1319
|
+
required: ["project_id", "sha"]
|
|
1320
|
+
}
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
name: "gitlab_list_branches",
|
|
1324
|
+
description: `List branches in a repository.`,
|
|
1325
|
+
input_schema: {
|
|
1326
|
+
type: "object",
|
|
1327
|
+
properties: {
|
|
1328
|
+
project_id: {
|
|
1329
|
+
type: "string",
|
|
1330
|
+
description: "The project ID or URL-encoded path"
|
|
1331
|
+
},
|
|
1332
|
+
search: {
|
|
1333
|
+
type: "string",
|
|
1334
|
+
description: "Search branches by name"
|
|
1335
|
+
}
|
|
1336
|
+
},
|
|
1337
|
+
required: ["project_id"]
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
// Search Tools
|
|
1341
|
+
{
|
|
1342
|
+
name: "gitlab_search",
|
|
1343
|
+
description: `Search across GitLab for various resources.
|
|
1344
|
+
Scopes: projects, issues, merge_requests, milestones, users, blobs (code), commits, notes, wiki_blobs`,
|
|
1345
|
+
input_schema: {
|
|
1346
|
+
type: "object",
|
|
1347
|
+
properties: {
|
|
1348
|
+
scope: {
|
|
1349
|
+
type: "string",
|
|
1350
|
+
enum: [
|
|
1351
|
+
"projects",
|
|
1352
|
+
"issues",
|
|
1353
|
+
"merge_requests",
|
|
1354
|
+
"milestones",
|
|
1355
|
+
"users",
|
|
1356
|
+
"blobs",
|
|
1357
|
+
"commits",
|
|
1358
|
+
"notes",
|
|
1359
|
+
"wiki_blobs"
|
|
1360
|
+
],
|
|
1361
|
+
description: "The scope of the search"
|
|
1362
|
+
},
|
|
1363
|
+
search: {
|
|
1364
|
+
type: "string",
|
|
1365
|
+
description: "The search query"
|
|
1366
|
+
},
|
|
1367
|
+
project_id: {
|
|
1368
|
+
type: "string",
|
|
1369
|
+
description: "Limit search to a specific project (optional)"
|
|
1370
|
+
},
|
|
1371
|
+
limit: {
|
|
1372
|
+
type: "number",
|
|
1373
|
+
description: "Maximum number of results (default: 20)"
|
|
1374
|
+
}
|
|
1375
|
+
},
|
|
1376
|
+
required: ["scope", "search"]
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
// Project Tools
|
|
1380
|
+
{
|
|
1381
|
+
name: "gitlab_get_project",
|
|
1382
|
+
description: `Get details of a specific project.`,
|
|
1383
|
+
input_schema: {
|
|
1384
|
+
type: "object",
|
|
1385
|
+
properties: {
|
|
1386
|
+
project_id: {
|
|
1387
|
+
type: "string",
|
|
1388
|
+
description: 'The project ID or URL-encoded path (e.g., "gitlab-org/gitlab")'
|
|
1389
|
+
}
|
|
1390
|
+
},
|
|
1391
|
+
required: ["project_id"]
|
|
1392
|
+
}
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
name: "gitlab_list_project_members",
|
|
1396
|
+
description: `List members of a project.`,
|
|
1397
|
+
input_schema: {
|
|
1398
|
+
type: "object",
|
|
1399
|
+
properties: {
|
|
1400
|
+
project_id: {
|
|
1401
|
+
type: "string",
|
|
1402
|
+
description: "The project ID or URL-encoded path"
|
|
1403
|
+
}
|
|
1404
|
+
},
|
|
1405
|
+
required: ["project_id"]
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
];
|
|
1409
|
+
var GitLabApiToolExecutor = class {
|
|
1410
|
+
config;
|
|
1411
|
+
constructor(config) {
|
|
1412
|
+
this.config = config;
|
|
1413
|
+
}
|
|
1414
|
+
get headers() {
|
|
1415
|
+
return {
|
|
1416
|
+
Authorization: `Bearer ${this.config.token}`,
|
|
1417
|
+
"Content-Type": "application/json"
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
async fetchApi(method, path4, body) {
|
|
1421
|
+
const url = `${this.config.instanceUrl}/api/v4${path4}`;
|
|
1422
|
+
const fetchFn = this.config.fetch || fetch;
|
|
1423
|
+
const response = await fetchFn(url, {
|
|
1424
|
+
method,
|
|
1425
|
+
headers: this.headers,
|
|
1426
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1427
|
+
});
|
|
1428
|
+
if (!response.ok) {
|
|
1429
|
+
const errorText = await response.text();
|
|
1430
|
+
throw new Error(`GitLab API error ${response.status}: ${errorText}`);
|
|
1431
|
+
}
|
|
1432
|
+
const text = await response.text();
|
|
1433
|
+
if (!text) {
|
|
1434
|
+
return {};
|
|
1435
|
+
}
|
|
1436
|
+
return JSON.parse(text);
|
|
1437
|
+
}
|
|
1438
|
+
encodeProjectId(projectId) {
|
|
1439
|
+
if (projectId.includes("/")) {
|
|
1440
|
+
return encodeURIComponent(projectId);
|
|
1441
|
+
}
|
|
1442
|
+
return projectId;
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Execute a GitLab API tool by name
|
|
1446
|
+
*/
|
|
1447
|
+
async execute(toolName, input) {
|
|
1448
|
+
try {
|
|
1449
|
+
switch (toolName) {
|
|
1450
|
+
// Merge Request tools
|
|
1451
|
+
case "gitlab_get_merge_request":
|
|
1452
|
+
return this.getMergeRequest(input);
|
|
1453
|
+
case "gitlab_list_merge_requests":
|
|
1454
|
+
return this.listMergeRequests(input);
|
|
1455
|
+
case "gitlab_get_mr_changes":
|
|
1456
|
+
return this.getMrChanges(input);
|
|
1457
|
+
case "gitlab_list_mr_discussions":
|
|
1458
|
+
return this.listMrDiscussions(input);
|
|
1459
|
+
case "gitlab_create_mr_note":
|
|
1460
|
+
return this.createMrNote(input);
|
|
1461
|
+
// Issue tools
|
|
1462
|
+
case "gitlab_get_issue":
|
|
1463
|
+
return this.getIssue(input);
|
|
1464
|
+
case "gitlab_list_issues":
|
|
1465
|
+
return this.listIssues(input);
|
|
1466
|
+
case "gitlab_create_issue_note":
|
|
1467
|
+
return this.createIssueNote(input);
|
|
1468
|
+
// Pipeline tools
|
|
1469
|
+
case "gitlab_list_pipelines":
|
|
1470
|
+
return this.listPipelines(input);
|
|
1471
|
+
case "gitlab_get_pipeline":
|
|
1472
|
+
return this.getPipeline(input);
|
|
1473
|
+
case "gitlab_list_pipeline_jobs":
|
|
1474
|
+
return this.listPipelineJobs(input);
|
|
1475
|
+
case "gitlab_get_job_log":
|
|
1476
|
+
return this.getJobLog(input);
|
|
1477
|
+
case "gitlab_retry_job":
|
|
1478
|
+
return this.retryJob(input);
|
|
1479
|
+
// Repository tools
|
|
1480
|
+
case "gitlab_get_file":
|
|
1481
|
+
return this.getFile(input);
|
|
1482
|
+
case "gitlab_list_commits":
|
|
1483
|
+
return this.listCommits(input);
|
|
1484
|
+
case "gitlab_get_commit_diff":
|
|
1485
|
+
return this.getCommitDiff(input);
|
|
1486
|
+
case "gitlab_list_branches":
|
|
1487
|
+
return this.listBranches(input);
|
|
1488
|
+
// Search tools
|
|
1489
|
+
case "gitlab_search":
|
|
1490
|
+
return this.search(input);
|
|
1491
|
+
// Project tools
|
|
1492
|
+
case "gitlab_get_project":
|
|
1493
|
+
return this.getProject(input);
|
|
1494
|
+
case "gitlab_list_project_members":
|
|
1495
|
+
return this.listProjectMembers(input);
|
|
1496
|
+
default:
|
|
1497
|
+
return { result: "", error: `Unknown GitLab tool: ${toolName}` };
|
|
1498
|
+
}
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
return {
|
|
1501
|
+
result: "",
|
|
1502
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
// ========== Merge Request Tools ==========
|
|
1507
|
+
async getMergeRequest(input) {
|
|
1508
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1509
|
+
const mrIid = input.mr_iid;
|
|
1510
|
+
const includeChanges = input.include_changes;
|
|
1511
|
+
let path4 = `/projects/${projectId}/merge_requests/${mrIid}`;
|
|
1512
|
+
if (includeChanges) {
|
|
1513
|
+
path4 += "?include_diverged_commits_count=true";
|
|
1514
|
+
}
|
|
1515
|
+
const mr = await this.fetchApi("GET", path4);
|
|
1516
|
+
return { result: JSON.stringify(mr, null, 2) };
|
|
1517
|
+
}
|
|
1518
|
+
async listMergeRequests(input) {
|
|
1519
|
+
const params = new URLSearchParams();
|
|
1520
|
+
params.set("per_page", String(input.limit || 20));
|
|
1521
|
+
if (input.state) params.set("state", input.state);
|
|
1522
|
+
if (input.scope) params.set("scope", input.scope);
|
|
1523
|
+
if (input.search) params.set("search", input.search);
|
|
1524
|
+
if (input.labels) params.set("labels", input.labels);
|
|
1525
|
+
let path4;
|
|
1526
|
+
if (input.project_id) {
|
|
1527
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1528
|
+
path4 = `/projects/${projectId}/merge_requests?${params}`;
|
|
1529
|
+
} else {
|
|
1530
|
+
path4 = `/merge_requests?${params}`;
|
|
1531
|
+
}
|
|
1532
|
+
const mrs = await this.fetchApi("GET", path4);
|
|
1533
|
+
return { result: JSON.stringify(mrs, null, 2) };
|
|
1534
|
+
}
|
|
1535
|
+
async getMrChanges(input) {
|
|
1536
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1537
|
+
const mrIid = input.mr_iid;
|
|
1538
|
+
const changes = await this.fetchApi(
|
|
1539
|
+
"GET",
|
|
1540
|
+
`/projects/${projectId}/merge_requests/${mrIid}/changes`
|
|
1541
|
+
);
|
|
1542
|
+
return { result: JSON.stringify(changes, null, 2) };
|
|
1543
|
+
}
|
|
1544
|
+
async listMrDiscussions(input) {
|
|
1545
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1546
|
+
const mrIid = input.mr_iid;
|
|
1547
|
+
const discussions = await this.fetchApi(
|
|
1548
|
+
"GET",
|
|
1549
|
+
`/projects/${projectId}/merge_requests/${mrIid}/discussions`
|
|
1550
|
+
);
|
|
1551
|
+
return { result: JSON.stringify(discussions, null, 2) };
|
|
1552
|
+
}
|
|
1553
|
+
async createMrNote(input) {
|
|
1554
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1555
|
+
const mrIid = input.mr_iid;
|
|
1556
|
+
const body = input.body;
|
|
1557
|
+
const note = await this.fetchApi(
|
|
1558
|
+
"POST",
|
|
1559
|
+
`/projects/${projectId}/merge_requests/${mrIid}/notes`,
|
|
1560
|
+
{ body }
|
|
1561
|
+
);
|
|
1562
|
+
return { result: JSON.stringify(note, null, 2) };
|
|
1563
|
+
}
|
|
1564
|
+
// ========== Issue Tools ==========
|
|
1565
|
+
async getIssue(input) {
|
|
1566
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1567
|
+
const issueIid = input.issue_iid;
|
|
1568
|
+
const issue = await this.fetchApi(
|
|
1569
|
+
"GET",
|
|
1570
|
+
`/projects/${projectId}/issues/${issueIid}`
|
|
1571
|
+
);
|
|
1572
|
+
return { result: JSON.stringify(issue, null, 2) };
|
|
1573
|
+
}
|
|
1574
|
+
async listIssues(input) {
|
|
1575
|
+
const params = new URLSearchParams();
|
|
1576
|
+
params.set("per_page", String(input.limit || 20));
|
|
1577
|
+
if (input.state) params.set("state", input.state);
|
|
1578
|
+
if (input.scope) params.set("scope", input.scope);
|
|
1579
|
+
if (input.search) params.set("search", input.search);
|
|
1580
|
+
if (input.labels) params.set("labels", input.labels);
|
|
1581
|
+
if (input.milestone) params.set("milestone", input.milestone);
|
|
1582
|
+
let path4;
|
|
1583
|
+
if (input.project_id) {
|
|
1584
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1585
|
+
path4 = `/projects/${projectId}/issues?${params}`;
|
|
1586
|
+
} else {
|
|
1587
|
+
path4 = `/issues?${params}`;
|
|
1588
|
+
}
|
|
1589
|
+
const issues = await this.fetchApi("GET", path4);
|
|
1590
|
+
return { result: JSON.stringify(issues, null, 2) };
|
|
1591
|
+
}
|
|
1592
|
+
async createIssueNote(input) {
|
|
1593
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1594
|
+
const issueIid = input.issue_iid;
|
|
1595
|
+
const body = input.body;
|
|
1596
|
+
const note = await this.fetchApi(
|
|
1597
|
+
"POST",
|
|
1598
|
+
`/projects/${projectId}/issues/${issueIid}/notes`,
|
|
1599
|
+
{ body }
|
|
1600
|
+
);
|
|
1601
|
+
return { result: JSON.stringify(note, null, 2) };
|
|
1602
|
+
}
|
|
1603
|
+
// ========== Pipeline Tools ==========
|
|
1604
|
+
async listPipelines(input) {
|
|
1605
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1606
|
+
const params = new URLSearchParams();
|
|
1607
|
+
params.set("per_page", String(input.limit || 20));
|
|
1608
|
+
if (input.status) params.set("status", input.status);
|
|
1609
|
+
if (input.ref) params.set("ref", input.ref);
|
|
1610
|
+
const pipelines = await this.fetchApi(
|
|
1611
|
+
"GET",
|
|
1612
|
+
`/projects/${projectId}/pipelines?${params}`
|
|
1613
|
+
);
|
|
1614
|
+
return { result: JSON.stringify(pipelines, null, 2) };
|
|
1615
|
+
}
|
|
1616
|
+
async getPipeline(input) {
|
|
1617
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1618
|
+
const pipelineId = input.pipeline_id;
|
|
1619
|
+
const pipeline = await this.fetchApi(
|
|
1620
|
+
"GET",
|
|
1621
|
+
`/projects/${projectId}/pipelines/${pipelineId}`
|
|
1622
|
+
);
|
|
1623
|
+
return { result: JSON.stringify(pipeline, null, 2) };
|
|
1624
|
+
}
|
|
1625
|
+
async listPipelineJobs(input) {
|
|
1626
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1627
|
+
const pipelineId = input.pipeline_id;
|
|
1628
|
+
const params = new URLSearchParams();
|
|
1629
|
+
if (input.scope) params.set("scope[]", input.scope);
|
|
1630
|
+
const jobs = await this.fetchApi(
|
|
1631
|
+
"GET",
|
|
1632
|
+
`/projects/${projectId}/pipelines/${pipelineId}/jobs?${params}`
|
|
1633
|
+
);
|
|
1634
|
+
return { result: JSON.stringify(jobs, null, 2) };
|
|
1635
|
+
}
|
|
1636
|
+
async getJobLog(input) {
|
|
1637
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1638
|
+
const jobId = input.job_id;
|
|
1639
|
+
const url = `${this.config.instanceUrl}/api/v4/projects/${projectId}/jobs/${jobId}/trace`;
|
|
1640
|
+
const fetchFn = this.config.fetch || fetch;
|
|
1641
|
+
const response = await fetchFn(url, {
|
|
1642
|
+
method: "GET",
|
|
1643
|
+
headers: this.headers
|
|
1644
|
+
});
|
|
1645
|
+
if (!response.ok) {
|
|
1646
|
+
const errorText = await response.text();
|
|
1647
|
+
throw new Error(`GitLab API error ${response.status}: ${errorText}`);
|
|
1648
|
+
}
|
|
1649
|
+
const log = await response.text();
|
|
1650
|
+
const maxLength = 5e4;
|
|
1651
|
+
if (log.length > maxLength) {
|
|
1652
|
+
return {
|
|
1653
|
+
result: `[Log truncated, showing last ${maxLength} characters]
|
|
1654
|
+
|
|
1655
|
+
${log.slice(-maxLength)}`
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
return { result: log };
|
|
1659
|
+
}
|
|
1660
|
+
async retryJob(input) {
|
|
1661
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1662
|
+
const jobId = input.job_id;
|
|
1663
|
+
const job = await this.fetchApi(
|
|
1664
|
+
"POST",
|
|
1665
|
+
`/projects/${projectId}/jobs/${jobId}/retry`
|
|
1666
|
+
);
|
|
1667
|
+
return { result: JSON.stringify(job, null, 2) };
|
|
1668
|
+
}
|
|
1669
|
+
// ========== Repository Tools ==========
|
|
1670
|
+
async getFile(input) {
|
|
1671
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1672
|
+
const filePath = encodeURIComponent(input.file_path);
|
|
1673
|
+
const ref = input.ref || "HEAD";
|
|
1674
|
+
const file = await this.fetchApi(
|
|
1675
|
+
"GET",
|
|
1676
|
+
`/projects/${projectId}/repository/files/${filePath}?ref=${encodeURIComponent(ref)}`
|
|
1677
|
+
);
|
|
1678
|
+
if (file.encoding === "base64") {
|
|
1679
|
+
const decoded = Buffer.from(file.content, "base64").toString("utf-8");
|
|
1680
|
+
return { result: decoded };
|
|
1681
|
+
}
|
|
1682
|
+
return { result: file.content };
|
|
1683
|
+
}
|
|
1684
|
+
async listCommits(input) {
|
|
1685
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1686
|
+
const params = new URLSearchParams();
|
|
1687
|
+
params.set("per_page", String(input.limit || 20));
|
|
1688
|
+
if (input.ref) params.set("ref_name", input.ref);
|
|
1689
|
+
if (input.path) params.set("path", input.path);
|
|
1690
|
+
if (input.since) params.set("since", input.since);
|
|
1691
|
+
if (input.until) params.set("until", input.until);
|
|
1692
|
+
const commits = await this.fetchApi(
|
|
1693
|
+
"GET",
|
|
1694
|
+
`/projects/${projectId}/repository/commits?${params}`
|
|
1695
|
+
);
|
|
1696
|
+
return { result: JSON.stringify(commits, null, 2) };
|
|
1697
|
+
}
|
|
1698
|
+
async getCommitDiff(input) {
|
|
1699
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1700
|
+
const sha = input.sha;
|
|
1701
|
+
const diff = await this.fetchApi(
|
|
1702
|
+
"GET",
|
|
1703
|
+
`/projects/${projectId}/repository/commits/${sha}/diff`
|
|
1704
|
+
);
|
|
1705
|
+
return { result: JSON.stringify(diff, null, 2) };
|
|
1706
|
+
}
|
|
1707
|
+
async listBranches(input) {
|
|
1708
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1709
|
+
const params = new URLSearchParams();
|
|
1710
|
+
if (input.search) params.set("search", input.search);
|
|
1711
|
+
const branches = await this.fetchApi(
|
|
1712
|
+
"GET",
|
|
1713
|
+
`/projects/${projectId}/repository/branches?${params}`
|
|
1714
|
+
);
|
|
1715
|
+
return { result: JSON.stringify(branches, null, 2) };
|
|
1716
|
+
}
|
|
1717
|
+
// ========== Search Tools ==========
|
|
1718
|
+
async search(input) {
|
|
1719
|
+
const scope = input.scope;
|
|
1720
|
+
const searchQuery = input.search;
|
|
1721
|
+
const params = new URLSearchParams();
|
|
1722
|
+
params.set("scope", scope);
|
|
1723
|
+
params.set("search", searchQuery);
|
|
1724
|
+
params.set("per_page", String(input.limit || 20));
|
|
1725
|
+
let path4;
|
|
1726
|
+
if (input.project_id) {
|
|
1727
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1728
|
+
path4 = `/projects/${projectId}/search?${params}`;
|
|
1729
|
+
} else {
|
|
1730
|
+
path4 = `/search?${params}`;
|
|
1731
|
+
}
|
|
1732
|
+
const results = await this.fetchApi("GET", path4);
|
|
1733
|
+
return { result: JSON.stringify(results, null, 2) };
|
|
1734
|
+
}
|
|
1735
|
+
// ========== Project Tools ==========
|
|
1736
|
+
async getProject(input) {
|
|
1737
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1738
|
+
const project = await this.fetchApi("GET", `/projects/${projectId}`);
|
|
1739
|
+
return { result: JSON.stringify(project, null, 2) };
|
|
1740
|
+
}
|
|
1741
|
+
async listProjectMembers(input) {
|
|
1742
|
+
const projectId = this.encodeProjectId(input.project_id);
|
|
1743
|
+
const members = await this.fetchApi(
|
|
1744
|
+
"GET",
|
|
1745
|
+
`/projects/${projectId}/members`
|
|
1746
|
+
);
|
|
1747
|
+
return { result: JSON.stringify(members, null, 2) };
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
function isGitLabApiTool(toolName) {
|
|
1751
|
+
return toolName.startsWith("gitlab_");
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// src/gitlab-anthropic-tools.ts
|
|
1755
|
+
var fs2 = __toESM(require("fs/promises"));
|
|
1756
|
+
var path2 = __toESM(require("path"));
|
|
1757
|
+
var import_child_process = require("child_process");
|
|
1758
|
+
var ANTHROPIC_TOOLS = [
|
|
1759
|
+
{
|
|
1760
|
+
name: "list_dir",
|
|
1761
|
+
description: `List directory contents. Shows files and subdirectories relative to the working directory.`,
|
|
1762
|
+
input_schema: {
|
|
1763
|
+
type: "object",
|
|
1764
|
+
properties: {
|
|
1765
|
+
directory: {
|
|
1766
|
+
type: "string",
|
|
1767
|
+
description: "Directory path relative to the working directory"
|
|
1768
|
+
}
|
|
1769
|
+
},
|
|
1770
|
+
required: ["directory"]
|
|
1771
|
+
}
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
name: "read_file",
|
|
1775
|
+
description: `Read the contents of a file.`,
|
|
1776
|
+
input_schema: {
|
|
1777
|
+
type: "object",
|
|
1778
|
+
properties: {
|
|
1779
|
+
file_path: {
|
|
1780
|
+
type: "string",
|
|
1781
|
+
description: "The file path to read"
|
|
1782
|
+
}
|
|
1783
|
+
},
|
|
1784
|
+
required: ["file_path"]
|
|
1785
|
+
}
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
name: "create_file_with_contents",
|
|
1789
|
+
description: `Create and write contents to a file.`,
|
|
1790
|
+
input_schema: {
|
|
1791
|
+
type: "object",
|
|
1792
|
+
properties: {
|
|
1793
|
+
file_path: {
|
|
1794
|
+
type: "string",
|
|
1795
|
+
description: "The file path to write to"
|
|
1796
|
+
},
|
|
1797
|
+
contents: {
|
|
1798
|
+
type: "string",
|
|
1799
|
+
description: "The contents to write"
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
required: ["file_path", "contents"]
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
name: "edit_file",
|
|
1807
|
+
description: `Edit an existing file by replacing a string with a new string.`,
|
|
1808
|
+
input_schema: {
|
|
1809
|
+
type: "object",
|
|
1810
|
+
properties: {
|
|
1811
|
+
file_path: {
|
|
1812
|
+
type: "string",
|
|
1813
|
+
description: "The path of the file to edit"
|
|
1814
|
+
},
|
|
1815
|
+
old_str: {
|
|
1816
|
+
type: "string",
|
|
1817
|
+
description: "The string to replace (include context for uniqueness)"
|
|
1818
|
+
},
|
|
1819
|
+
new_str: {
|
|
1820
|
+
type: "string",
|
|
1821
|
+
description: "The new string value"
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
1824
|
+
required: ["file_path", "old_str", "new_str"]
|
|
1825
|
+
}
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
name: "find_files",
|
|
1829
|
+
description: `Find files by name pattern. Uses glob-like matching.`,
|
|
1830
|
+
input_schema: {
|
|
1831
|
+
type: "object",
|
|
1832
|
+
properties: {
|
|
1833
|
+
name_pattern: {
|
|
1834
|
+
type: "string",
|
|
1835
|
+
description: 'The pattern to search for (e.g., "*.py", "test_*.js")'
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
required: ["name_pattern"]
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
name: "mkdir",
|
|
1843
|
+
description: `Create a new directory.`,
|
|
1844
|
+
input_schema: {
|
|
1845
|
+
type: "object",
|
|
1846
|
+
properties: {
|
|
1847
|
+
directory_path: {
|
|
1848
|
+
type: "string",
|
|
1849
|
+
description: "The directory path to create"
|
|
1850
|
+
}
|
|
1851
|
+
},
|
|
1852
|
+
required: ["directory_path"]
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
{
|
|
1856
|
+
name: "grep",
|
|
1857
|
+
description: `Search for text patterns within files.`,
|
|
1858
|
+
input_schema: {
|
|
1859
|
+
type: "object",
|
|
1860
|
+
properties: {
|
|
1861
|
+
pattern: {
|
|
1862
|
+
type: "string",
|
|
1863
|
+
description: "The text pattern to search for"
|
|
1864
|
+
},
|
|
1865
|
+
search_directory: {
|
|
1866
|
+
type: "string",
|
|
1867
|
+
description: 'The directory to search in (default: ".")'
|
|
1868
|
+
},
|
|
1869
|
+
case_insensitive: {
|
|
1870
|
+
type: "boolean",
|
|
1871
|
+
description: "Whether to ignore case (default: false)"
|
|
1872
|
+
}
|
|
1873
|
+
},
|
|
1874
|
+
required: ["pattern"]
|
|
1875
|
+
}
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
name: "run_command",
|
|
1879
|
+
description: `Run a shell command. Note: git commands should use run_git_command instead.`,
|
|
1880
|
+
input_schema: {
|
|
1881
|
+
type: "object",
|
|
1882
|
+
properties: {
|
|
1883
|
+
program: {
|
|
1884
|
+
type: "string",
|
|
1885
|
+
description: 'The program to execute (e.g., "npm", "python")'
|
|
1886
|
+
},
|
|
1887
|
+
args: {
|
|
1888
|
+
type: "string",
|
|
1889
|
+
description: "Arguments as a single string"
|
|
1890
|
+
}
|
|
1891
|
+
},
|
|
1892
|
+
required: ["program"]
|
|
1893
|
+
}
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
name: "run_git_command",
|
|
1897
|
+
description: `Run a git command in the repository.`,
|
|
1898
|
+
input_schema: {
|
|
1899
|
+
type: "object",
|
|
1900
|
+
properties: {
|
|
1901
|
+
command: {
|
|
1902
|
+
type: "string",
|
|
1903
|
+
description: 'Git command (e.g., "status", "log", "diff")'
|
|
1904
|
+
},
|
|
1905
|
+
args: {
|
|
1906
|
+
type: "string",
|
|
1907
|
+
description: "Git command arguments"
|
|
1908
|
+
}
|
|
1909
|
+
},
|
|
1910
|
+
required: ["command"]
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
];
|
|
1914
|
+
var AnthropicToolExecutor = class {
|
|
1915
|
+
workingDirectory;
|
|
1916
|
+
constructor(workingDirectory) {
|
|
1917
|
+
this.workingDirectory = workingDirectory;
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Execute a tool by name with given input
|
|
1921
|
+
*/
|
|
1922
|
+
async execute(toolName, input) {
|
|
1923
|
+
try {
|
|
1924
|
+
switch (toolName) {
|
|
1925
|
+
case "list_dir":
|
|
1926
|
+
return this.listDir(input.directory);
|
|
1927
|
+
case "read_file":
|
|
1928
|
+
return this.readFile(input.file_path);
|
|
1929
|
+
case "create_file_with_contents":
|
|
1930
|
+
return this.writeFile(input.file_path, input.contents);
|
|
1931
|
+
case "edit_file":
|
|
1932
|
+
return this.editFile(
|
|
1933
|
+
input.file_path,
|
|
1934
|
+
input.old_str,
|
|
1935
|
+
input.new_str
|
|
1936
|
+
);
|
|
1937
|
+
case "find_files":
|
|
1938
|
+
return this.findFiles(input.name_pattern);
|
|
1939
|
+
case "mkdir":
|
|
1940
|
+
return this.mkdir(input.directory_path);
|
|
1941
|
+
case "grep":
|
|
1942
|
+
return this.grep(
|
|
1943
|
+
input.pattern,
|
|
1944
|
+
input.search_directory,
|
|
1945
|
+
input.case_insensitive
|
|
1946
|
+
);
|
|
1947
|
+
case "run_command":
|
|
1948
|
+
return this.runCommand(input.program, input.args);
|
|
1949
|
+
case "run_git_command":
|
|
1950
|
+
return this.runGitCommand(input.command, input.args);
|
|
1951
|
+
default:
|
|
1952
|
+
return { result: "", error: `Unknown tool: ${toolName}` };
|
|
1953
|
+
}
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
return {
|
|
1956
|
+
result: "",
|
|
1957
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
resolvePath(filePath) {
|
|
1962
|
+
if (path2.isAbsolute(filePath)) {
|
|
1963
|
+
return filePath;
|
|
1964
|
+
}
|
|
1965
|
+
return path2.resolve(this.workingDirectory, filePath);
|
|
1966
|
+
}
|
|
1967
|
+
async listDir(directory) {
|
|
1968
|
+
const dirPath = this.resolvePath(directory || ".");
|
|
1969
|
+
const entries = await fs2.readdir(dirPath, { withFileTypes: true });
|
|
1970
|
+
const result = entries.map((entry) => {
|
|
1971
|
+
const type = entry.isDirectory() ? "d" : "-";
|
|
1972
|
+
return `${type} ${entry.name}`;
|
|
1973
|
+
}).join("\n");
|
|
1974
|
+
return { result };
|
|
1975
|
+
}
|
|
1976
|
+
async readFile(filePath) {
|
|
1977
|
+
const fullPath = this.resolvePath(filePath);
|
|
1978
|
+
const content = await fs2.readFile(fullPath, "utf-8");
|
|
1979
|
+
return { result: content };
|
|
1980
|
+
}
|
|
1981
|
+
async writeFile(filePath, contents) {
|
|
1982
|
+
const fullPath = this.resolvePath(filePath);
|
|
1983
|
+
await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
|
|
1984
|
+
await fs2.writeFile(fullPath, contents, "utf-8");
|
|
1985
|
+
return { result: `File written successfully: ${filePath}` };
|
|
1986
|
+
}
|
|
1987
|
+
async editFile(filePath, oldStr, newStr) {
|
|
1988
|
+
const fullPath = this.resolvePath(filePath);
|
|
1989
|
+
const content = await fs2.readFile(fullPath, "utf-8");
|
|
1990
|
+
if (!content.includes(oldStr)) {
|
|
1991
|
+
return { result: "", error: `String not found in file: "${oldStr.substring(0, 50)}..."` };
|
|
1992
|
+
}
|
|
1993
|
+
const newContent = content.replace(oldStr, newStr);
|
|
1994
|
+
await fs2.writeFile(fullPath, newContent, "utf-8");
|
|
1995
|
+
return { result: `File edited successfully: ${filePath}` };
|
|
1996
|
+
}
|
|
1997
|
+
async findFiles(namePattern) {
|
|
1998
|
+
const results = [];
|
|
1999
|
+
const regex = new RegExp("^" + namePattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
|
|
2000
|
+
const search = async (dir, relativePath = "") => {
|
|
2001
|
+
try {
|
|
2002
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
2003
|
+
for (const entry of entries) {
|
|
2004
|
+
const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2005
|
+
if (entry.isDirectory()) {
|
|
2006
|
+
if (!["node_modules", ".git", "dist", "build", "__pycache__"].includes(entry.name)) {
|
|
2007
|
+
await search(path2.join(dir, entry.name), entryRelativePath);
|
|
2008
|
+
}
|
|
2009
|
+
} else if (regex.test(entry.name)) {
|
|
2010
|
+
results.push(entryRelativePath);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
} catch {
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
await search(this.workingDirectory);
|
|
2017
|
+
return { result: results.join("\n") || "No files found" };
|
|
2018
|
+
}
|
|
2019
|
+
async mkdir(directoryPath) {
|
|
2020
|
+
const fullPath = this.resolvePath(directoryPath);
|
|
2021
|
+
await fs2.mkdir(fullPath, { recursive: true });
|
|
2022
|
+
return { result: `Directory created: ${directoryPath}` };
|
|
2023
|
+
}
|
|
2024
|
+
async grep(pattern, searchDirectory, caseInsensitive) {
|
|
2025
|
+
const results = [];
|
|
2026
|
+
const flags = caseInsensitive ? "gi" : "g";
|
|
2027
|
+
const regex = new RegExp(pattern, flags);
|
|
2028
|
+
const searchDir = this.resolvePath(searchDirectory || ".");
|
|
2029
|
+
const search = async (dir, relativePath = "") => {
|
|
2030
|
+
try {
|
|
2031
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
2032
|
+
for (const entry of entries) {
|
|
2033
|
+
const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
2034
|
+
const fullPath = path2.join(dir, entry.name);
|
|
2035
|
+
if (entry.isDirectory()) {
|
|
2036
|
+
if (!["node_modules", ".git", "dist", "build", "__pycache__"].includes(entry.name)) {
|
|
2037
|
+
await search(fullPath, entryRelativePath);
|
|
2038
|
+
}
|
|
2039
|
+
} else {
|
|
2040
|
+
try {
|
|
2041
|
+
const content = await fs2.readFile(fullPath, "utf-8");
|
|
2042
|
+
const lines = content.split("\n");
|
|
2043
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2044
|
+
if (regex.test(lines[i])) {
|
|
2045
|
+
results.push(`${entryRelativePath}:${i + 1}: ${lines[i].trim()}`);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
} catch {
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
} catch {
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
await search(searchDir);
|
|
2056
|
+
return { result: results.slice(0, 100).join("\n") || "No matches found" };
|
|
2057
|
+
}
|
|
2058
|
+
runCommand(program, args) {
|
|
2059
|
+
if (program === "git") {
|
|
2060
|
+
return Promise.resolve({
|
|
2061
|
+
result: "",
|
|
2062
|
+
error: "Use run_git_command for git operations"
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
const parsedArgs = args ? args.match(/(?:[^\s"]+|"[^"]*")+/g) || [] : [];
|
|
2066
|
+
const cleanedArgs = parsedArgs.map((arg) => arg.replace(/^"(.*)"$/, "$1"));
|
|
2067
|
+
return this.executeCommand(program, cleanedArgs);
|
|
2068
|
+
}
|
|
2069
|
+
runGitCommand(command, args) {
|
|
2070
|
+
const gitArgs = [command];
|
|
2071
|
+
if (args) {
|
|
2072
|
+
const parsedArgs = args.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
|
|
2073
|
+
gitArgs.push(...parsedArgs.map((arg) => arg.replace(/^"(.*)"$/, "$1")));
|
|
2074
|
+
}
|
|
2075
|
+
return this.executeCommand("git", gitArgs);
|
|
2076
|
+
}
|
|
2077
|
+
executeCommand(program, args) {
|
|
2078
|
+
return new Promise((resolve3) => {
|
|
2079
|
+
const child = (0, import_child_process.spawn)(program, args, {
|
|
2080
|
+
cwd: this.workingDirectory,
|
|
2081
|
+
timeout: 3e4
|
|
2082
|
+
});
|
|
2083
|
+
let stdout = "";
|
|
2084
|
+
let stderr = "";
|
|
2085
|
+
child.stdout?.on("data", (data) => {
|
|
2086
|
+
stdout += data.toString();
|
|
2087
|
+
});
|
|
2088
|
+
child.stderr?.on("data", (data) => {
|
|
2089
|
+
stderr += data.toString();
|
|
2090
|
+
});
|
|
2091
|
+
child.on("error", (error) => {
|
|
2092
|
+
resolve3({
|
|
2093
|
+
result: "",
|
|
2094
|
+
error: `Command failed: ${error.message}`
|
|
2095
|
+
});
|
|
2096
|
+
});
|
|
2097
|
+
child.on("close", (exitCode) => {
|
|
2098
|
+
if (exitCode !== 0 && stderr) {
|
|
2099
|
+
resolve3({
|
|
2100
|
+
result: stdout,
|
|
2101
|
+
error: stderr
|
|
2102
|
+
});
|
|
2103
|
+
} else {
|
|
2104
|
+
resolve3({
|
|
2105
|
+
result: stdout || stderr || `Command completed with exit code ${exitCode}`
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
|
|
2113
|
+
// src/gitlab-project-detector.ts
|
|
2114
|
+
var import_child_process2 = require("child_process");
|
|
2115
|
+
var path3 = __toESM(require("path"));
|
|
2116
|
+
|
|
2117
|
+
// src/gitlab-project-cache.ts
|
|
2118
|
+
var GitLabProjectCache = class {
|
|
2119
|
+
cache = /* @__PURE__ */ new Map();
|
|
2120
|
+
defaultTTL;
|
|
2121
|
+
/**
|
|
2122
|
+
* Create a new project cache
|
|
2123
|
+
* @param defaultTTL - Default time-to-live in milliseconds (default: 5 minutes)
|
|
2124
|
+
*/
|
|
2125
|
+
constructor(defaultTTL = 5 * 60 * 1e3) {
|
|
2126
|
+
this.defaultTTL = defaultTTL;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Get a cached project by key
|
|
2130
|
+
* @param key - Cache key (typically the working directory path)
|
|
2131
|
+
* @returns The cached project or null if not found or expired
|
|
2132
|
+
*/
|
|
2133
|
+
get(key) {
|
|
2134
|
+
const entry = this.cache.get(key);
|
|
2135
|
+
if (!entry) {
|
|
2136
|
+
return null;
|
|
2137
|
+
}
|
|
2138
|
+
if (Date.now() > entry.expiresAt) {
|
|
2139
|
+
this.cache.delete(key);
|
|
2140
|
+
return null;
|
|
2141
|
+
}
|
|
2142
|
+
return entry.project;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Store a project in the cache
|
|
2146
|
+
* @param key - Cache key (typically the working directory path)
|
|
2147
|
+
* @param project - The project to cache
|
|
2148
|
+
* @param ttl - Optional custom TTL in milliseconds
|
|
2149
|
+
*/
|
|
2150
|
+
set(key, project, ttl) {
|
|
2151
|
+
this.cache.set(key, {
|
|
2152
|
+
project,
|
|
2153
|
+
expiresAt: Date.now() + (ttl ?? this.defaultTTL)
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Check if a key exists in the cache (and is not expired)
|
|
2158
|
+
* @param key - Cache key to check
|
|
2159
|
+
* @returns true if the key exists and is not expired
|
|
2160
|
+
*/
|
|
2161
|
+
has(key) {
|
|
2162
|
+
return this.get(key) !== null;
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Remove a specific entry from the cache
|
|
2166
|
+
* @param key - Cache key to remove
|
|
2167
|
+
*/
|
|
2168
|
+
delete(key) {
|
|
2169
|
+
this.cache.delete(key);
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Clear all entries from the cache
|
|
2173
|
+
*/
|
|
2174
|
+
clear() {
|
|
2175
|
+
this.cache.clear();
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Get the number of entries in the cache (including expired ones)
|
|
2179
|
+
*/
|
|
2180
|
+
get size() {
|
|
2181
|
+
return this.cache.size;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Clean up expired entries from the cache
|
|
2185
|
+
* This is useful for long-running processes to prevent memory leaks
|
|
2186
|
+
*/
|
|
2187
|
+
cleanup() {
|
|
2188
|
+
const now = Date.now();
|
|
2189
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
2190
|
+
if (now > entry.expiresAt) {
|
|
2191
|
+
this.cache.delete(key);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
};
|
|
2196
|
+
|
|
2197
|
+
// src/gitlab-project-detector.ts
|
|
2198
|
+
var debugLog = (..._args) => {
|
|
2199
|
+
};
|
|
2200
|
+
var GitLabProjectDetector = class {
|
|
2201
|
+
config;
|
|
2202
|
+
fetchFn;
|
|
2203
|
+
cache;
|
|
2204
|
+
constructor(config) {
|
|
2205
|
+
this.config = {
|
|
2206
|
+
gitTimeout: 5e3,
|
|
2207
|
+
// 5 seconds default
|
|
2208
|
+
...config
|
|
2209
|
+
};
|
|
2210
|
+
this.fetchFn = config.fetch ?? fetch;
|
|
2211
|
+
this.cache = config.cache ?? new GitLabProjectCache();
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Auto-detect GitLab project from git remote in the working directory
|
|
2215
|
+
*
|
|
2216
|
+
* @param workingDirectory - The directory to check for git remote
|
|
2217
|
+
* @param remoteName - The git remote name to use (default: 'origin')
|
|
2218
|
+
* @returns The detected project or null if detection fails
|
|
2219
|
+
*/
|
|
2220
|
+
async detectProject(workingDirectory, remoteName = "origin") {
|
|
2221
|
+
const cacheKey = path3.resolve(workingDirectory);
|
|
2222
|
+
const cached = this.cache.get(cacheKey);
|
|
2223
|
+
if (cached) {
|
|
2224
|
+
return cached;
|
|
2225
|
+
}
|
|
2226
|
+
try {
|
|
2227
|
+
debugLog(`[GitLabProjectDetector] Getting git remote URL from: ${workingDirectory}`);
|
|
2228
|
+
const remoteUrl = await this.getGitRemoteUrl(workingDirectory, remoteName);
|
|
2229
|
+
if (!remoteUrl) {
|
|
2230
|
+
debugLog(`[GitLabProjectDetector] No git remote URL found`);
|
|
2231
|
+
return null;
|
|
2232
|
+
}
|
|
2233
|
+
debugLog(`[GitLabProjectDetector] Git remote URL: ${remoteUrl}`);
|
|
2234
|
+
debugLog(
|
|
2235
|
+
`[GitLabProjectDetector] Parsing project path from URL (instance: ${this.config.instanceUrl})`
|
|
2236
|
+
);
|
|
2237
|
+
const projectPath = this.parseGitRemoteUrl(remoteUrl, this.config.instanceUrl);
|
|
2238
|
+
if (!projectPath) {
|
|
2239
|
+
debugLog(
|
|
2240
|
+
`[GitLabProjectDetector] Could not parse project path from URL (remote doesn't match instance)`
|
|
2241
|
+
);
|
|
2242
|
+
return null;
|
|
2243
|
+
}
|
|
2244
|
+
debugLog(`[GitLabProjectDetector] Parsed project path: ${projectPath}`);
|
|
2245
|
+
debugLog(`[GitLabProjectDetector] Fetching project from GitLab API: ${projectPath}`);
|
|
2246
|
+
const project = await this.getProjectByPath(projectPath);
|
|
2247
|
+
debugLog(`[GitLabProjectDetector] \u2713 Project fetched successfully:`, project);
|
|
2248
|
+
this.cache.set(cacheKey, project);
|
|
2249
|
+
return project;
|
|
2250
|
+
} catch (error) {
|
|
2251
|
+
if (error instanceof GitLabError) {
|
|
2252
|
+
debugLog(`[GitLabProjectDetector] GitLab API error:`, error.message || error);
|
|
2253
|
+
return null;
|
|
2254
|
+
}
|
|
2255
|
+
debugLog(`[GitLabProjectDetector] Unexpected error:`, error);
|
|
2256
|
+
console.warn(`Failed to auto-detect GitLab project: ${error}`);
|
|
2257
|
+
return null;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Parse a git remote URL to extract the project path
|
|
2262
|
+
*
|
|
2263
|
+
* Supports:
|
|
2264
|
+
* - SSH: git@gitlab.com:namespace/project.git
|
|
2265
|
+
* - HTTPS: https://gitlab.com/namespace/project.git
|
|
2266
|
+
* - HTTP: http://gitlab.local/namespace/project.git
|
|
2267
|
+
* - Custom domains and ports
|
|
2268
|
+
*
|
|
2269
|
+
* @param remoteUrl - The git remote URL
|
|
2270
|
+
* @param instanceUrl - The GitLab instance URL to match against
|
|
2271
|
+
* @returns The project path (e.g., "namespace/project") or null if parsing fails
|
|
2272
|
+
*/
|
|
2273
|
+
parseGitRemoteUrl(remoteUrl, instanceUrl) {
|
|
2274
|
+
try {
|
|
2275
|
+
const instanceHost = new URL(instanceUrl).hostname;
|
|
2276
|
+
const sshMatch = remoteUrl.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
|
|
2277
|
+
if (sshMatch) {
|
|
2278
|
+
const [, host, pathPart] = sshMatch;
|
|
2279
|
+
const hostWithoutPort = host.split(":")[0];
|
|
2280
|
+
if (hostWithoutPort === instanceHost) {
|
|
2281
|
+
const cleanPath = pathPart.replace(/^\d+\//, "");
|
|
2282
|
+
return cleanPath.endsWith(".git") ? cleanPath.slice(0, -4) : cleanPath;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
const httpsMatch = remoteUrl.match(/^(https?):\/\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
2286
|
+
if (httpsMatch) {
|
|
2287
|
+
const [, , hostWithPort, pathPart] = httpsMatch;
|
|
2288
|
+
const host = hostWithPort.split(":")[0];
|
|
2289
|
+
if (host === instanceHost) {
|
|
2290
|
+
return pathPart.endsWith(".git") ? pathPart.slice(0, -4) : pathPart;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
return null;
|
|
2294
|
+
} catch (error) {
|
|
2295
|
+
return null;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Get the git remote URL from a working directory
|
|
2300
|
+
*
|
|
2301
|
+
* @param workingDirectory - The directory to check
|
|
2302
|
+
* @param remoteName - The git remote name (default: 'origin')
|
|
2303
|
+
* @returns The remote URL or null if not found
|
|
2304
|
+
*/
|
|
2305
|
+
async getGitRemoteUrl(workingDirectory, remoteName = "origin") {
|
|
2306
|
+
return new Promise((resolve3) => {
|
|
2307
|
+
const child = (0, import_child_process2.spawn)("git", ["config", "--get", `remote.${remoteName}.url`], {
|
|
2308
|
+
cwd: workingDirectory,
|
|
2309
|
+
timeout: this.config.gitTimeout
|
|
2310
|
+
});
|
|
2311
|
+
let stdout = "";
|
|
2312
|
+
let _stderr = "";
|
|
2313
|
+
child.stdout?.on("data", (data) => {
|
|
2314
|
+
stdout += data.toString();
|
|
2315
|
+
});
|
|
2316
|
+
child.stderr?.on("data", (data) => {
|
|
2317
|
+
_stderr += data.toString();
|
|
2318
|
+
});
|
|
2319
|
+
child.on("close", (exitCode) => {
|
|
2320
|
+
if (exitCode === 0 && stdout.trim()) {
|
|
2321
|
+
resolve3(stdout.trim());
|
|
2322
|
+
} else {
|
|
2323
|
+
resolve3(null);
|
|
2324
|
+
}
|
|
2325
|
+
});
|
|
2326
|
+
child.on("error", () => {
|
|
2327
|
+
resolve3(null);
|
|
2328
|
+
});
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Fetch project details from GitLab API by project path
|
|
2333
|
+
*
|
|
2334
|
+
* @param projectPath - The project path (e.g., "namespace/project")
|
|
2335
|
+
* @returns The project details
|
|
2336
|
+
* @throws GitLabError if the API call fails
|
|
2337
|
+
*/
|
|
2338
|
+
async getProjectByPath(projectPath) {
|
|
2339
|
+
const encodedPath = encodeURIComponent(projectPath);
|
|
2340
|
+
const url = `${this.config.instanceUrl}/api/v4/projects/${encodedPath}`;
|
|
2341
|
+
try {
|
|
2342
|
+
const response = await this.fetchFn(url, {
|
|
2343
|
+
method: "GET",
|
|
2344
|
+
headers: this.config.getHeaders()
|
|
2345
|
+
});
|
|
2346
|
+
if (!response.ok) {
|
|
2347
|
+
throw new GitLabError({
|
|
2348
|
+
message: `Failed to fetch project '${projectPath}': ${response.status} ${response.statusText}`
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
const data = await response.json();
|
|
2352
|
+
return {
|
|
2353
|
+
id: data.id,
|
|
2354
|
+
path: data.path,
|
|
2355
|
+
pathWithNamespace: data.path_with_namespace,
|
|
2356
|
+
name: data.name,
|
|
2357
|
+
namespaceId: data.namespace?.id
|
|
2358
|
+
};
|
|
2359
|
+
} catch (error) {
|
|
2360
|
+
if (error instanceof GitLabError) {
|
|
2361
|
+
throw error;
|
|
2362
|
+
}
|
|
2363
|
+
throw new GitLabError({
|
|
2364
|
+
message: `Failed to fetch project '${projectPath}': ${error}`,
|
|
2365
|
+
cause: error
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Clear the project cache
|
|
2371
|
+
*/
|
|
2372
|
+
clearCache() {
|
|
2373
|
+
this.cache.clear();
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Get the cache instance (useful for testing)
|
|
2377
|
+
*/
|
|
2378
|
+
getCache() {
|
|
2379
|
+
return this.cache;
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2383
|
+
0 && (module.exports = {
|
|
2384
|
+
ANTHROPIC_TOOLS,
|
|
2385
|
+
AnthropicToolExecutor,
|
|
2386
|
+
BUNDLED_CLIENT_ID,
|
|
2387
|
+
GITLAB_API_TOOLS,
|
|
2388
|
+
GITLAB_COM_URL,
|
|
2389
|
+
GitLabAgenticLanguageModel,
|
|
2390
|
+
GitLabApiToolExecutor,
|
|
2391
|
+
GitLabError,
|
|
2392
|
+
GitLabOAuthManager,
|
|
2393
|
+
GitLabProjectCache,
|
|
2394
|
+
GitLabProjectDetector,
|
|
2395
|
+
OAUTH_SCOPES,
|
|
2396
|
+
TOKEN_EXPIRY_SKEW_MS,
|
|
2397
|
+
createGitLab,
|
|
2398
|
+
gitlab,
|
|
2399
|
+
isGitLabApiTool
|
|
2400
|
+
});
|
|
2401
|
+
//# sourceMappingURL=index.js.map
|