gitlab-ai-provider 5.0.0 → 5.1.1

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/dist/index.mjs CHANGED
@@ -289,7 +289,8 @@ var GitLabAnthropicLanguageModel = class {
289
289
  const messages = [];
290
290
  for (const message of prompt) {
291
291
  if (message.role === "system") {
292
- systemMessage = message.content;
292
+ systemMessage = systemMessage ? `${systemMessage}
293
+ ${message.content}` : message.content;
293
294
  continue;
294
295
  }
295
296
  if (message.role === "user") {
@@ -1505,7 +1506,7 @@ var GitLabOpenAILanguageModel = class {
1505
1506
  import WebSocket from "isomorphic-ws";
1506
1507
 
1507
1508
  // src/version.ts
1508
- var VERSION = true ? "3.6.0" : "0.0.0-dev";
1509
+ var VERSION = true ? "5.1.0" : "0.0.0-dev";
1509
1510
 
1510
1511
  // src/gitlab-workflow-types.ts
1511
1512
  var WorkflowType = /* @__PURE__ */ ((WorkflowType2) => {
@@ -1842,7 +1843,8 @@ function sanitizeErrorMessage(message) {
1842
1843
  if (!message) return "";
1843
1844
  return message.replace(/\bBearer\s+[A-Za-z0-9\-_.~+/]+=*/gi, "Bearer [REDACTED]").replace(/\bgl(?:pat|oat|cbt|dt|oas|rt|soat|ffct|sapat)-[A-Za-z0-9_-]+/g, "[REDACTED]").replace(/([?&](?:private_token|access_token|token)=)[^&\s"']*/gi, "$1[REDACTED]").replace(/:\/\/([^:@/\s]+):([^@/\s]+)@/g, "://$1:[REDACTED]@");
1844
1845
  }
1845
- function mapBuiltinTool(dwsToolName, data) {
1846
+ function mapBuiltinTool(dwsToolName, data, availableTools) {
1847
+ const has = (name) => !availableTools || availableTools.has(name);
1846
1848
  switch (dwsToolName) {
1847
1849
  case "runReadFile":
1848
1850
  return { toolName: "read", args: { filePath: data.filepath } };
@@ -1856,20 +1858,51 @@ function mapBuiltinTool(dwsToolName, data) {
1856
1858
  args: { filePaths: paths }
1857
1859
  };
1858
1860
  }
1859
- case "runWriteFile":
1860
- return {
1861
- toolName: "write",
1862
- args: { filePath: data.filepath, content: data.contents }
1863
- };
1864
- case "runEditFile":
1865
- return {
1866
- toolName: "edit",
1867
- args: {
1868
- filePath: data.filepath,
1869
- oldString: data.oldString ?? data.old_string,
1870
- newString: data.newString ?? data.new_string
1871
- }
1872
- };
1861
+ case "runWriteFile": {
1862
+ if (has("write")) {
1863
+ return {
1864
+ toolName: "write",
1865
+ args: { filePath: data.filepath, content: data.contents }
1866
+ };
1867
+ }
1868
+ const filePath = String(data.filepath ?? "");
1869
+ const content = String(data.contents ?? "");
1870
+ const patchLines = [
1871
+ "*** Begin Patch",
1872
+ `*** Add File: ${filePath}`,
1873
+ ...content.split("\n").map((l) => `+${l}`),
1874
+ "*** End Patch"
1875
+ ].join("\n");
1876
+ return { toolName: "apply_patch", args: { patchText: patchLines } };
1877
+ }
1878
+ case "runEditFile": {
1879
+ const editOldString = String(data.oldString ?? data.old_string ?? "");
1880
+ const editNewString = String(data.newString ?? data.new_string ?? "");
1881
+ if (has("edit")) {
1882
+ return {
1883
+ toolName: "edit",
1884
+ args: {
1885
+ filePath: data.filepath,
1886
+ oldString: editOldString,
1887
+ newString: editNewString
1888
+ }
1889
+ };
1890
+ }
1891
+ const editPath = String(data.filepath ?? "");
1892
+ const oldStr = editOldString;
1893
+ const newStr = editNewString;
1894
+ const oldLines = oldStr.split("\n");
1895
+ const newLines = newStr.split("\n");
1896
+ const patchContent = [
1897
+ "*** Begin Patch",
1898
+ `*** Update File: ${editPath}`,
1899
+ "@@",
1900
+ ...oldLines.map((l) => `-${l}`),
1901
+ ...newLines.map((l) => `+${l}`),
1902
+ "*** End Patch"
1903
+ ].join("\n");
1904
+ return { toolName: "apply_patch", args: { patchText: patchContent } };
1905
+ }
1873
1906
  case "runShellCommand": {
1874
1907
  const command = data.command;
1875
1908
  if (!command || typeof command !== "string") {
@@ -1992,6 +2025,87 @@ function mapBuiltinTool(dwsToolName, data) {
1992
2025
  return { toolName: dwsToolName, args: data };
1993
2026
  }
1994
2027
  }
2028
+ function validateSafePath(filePath) {
2029
+ if (!filePath) {
2030
+ throw new Error("filePath is required");
2031
+ }
2032
+ if (filePath.includes("\0")) {
2033
+ throw new Error("filePath contains null bytes");
2034
+ }
2035
+ const path5 = __require("path");
2036
+ const resolved = path5.resolve(filePath);
2037
+ const cwd = process.cwd();
2038
+ if (!resolved.startsWith(cwd + path5.sep) && resolved !== cwd) {
2039
+ throw new Error(`filePath resolves outside the working directory: ${filePath}`);
2040
+ }
2041
+ return resolved;
2042
+ }
2043
+ function executeBuiltinFallback(toolName, argsJson) {
2044
+ if (toolName !== "edit" && toolName !== "write") {
2045
+ return null;
2046
+ }
2047
+ const fs4 = __require("fs");
2048
+ let args;
2049
+ try {
2050
+ args = JSON.parse(argsJson);
2051
+ } catch {
2052
+ return { result: "", error: `${toolName} fallback: invalid JSON arguments` };
2053
+ }
2054
+ try {
2055
+ if (toolName === "write") {
2056
+ const filePath2 = String(args.filePath ?? "");
2057
+ if (!filePath2) {
2058
+ return { result: "", error: "write fallback: filePath is required" };
2059
+ }
2060
+ const safePath2 = validateSafePath(filePath2);
2061
+ const content2 = String(args.content ?? "");
2062
+ fs4.writeFileSync(safePath2, content2, "utf-8");
2063
+ return {
2064
+ result: "File written successfully.",
2065
+ title: filePath2,
2066
+ metadata: { output: "File written successfully." }
2067
+ };
2068
+ }
2069
+ const filePath = String(args.filePath ?? "");
2070
+ const oldString = String(args.oldString ?? "");
2071
+ const newString = String(args.newString ?? "");
2072
+ if (!filePath) {
2073
+ return { result: "", error: "edit fallback: filePath is required" };
2074
+ }
2075
+ if (!oldString && !newString) {
2076
+ return { result: "", error: "edit fallback: oldString and newString are both empty" };
2077
+ }
2078
+ const safePath = validateSafePath(filePath);
2079
+ let content;
2080
+ try {
2081
+ content = fs4.readFileSync(safePath, "utf-8");
2082
+ } catch {
2083
+ content = "";
2084
+ }
2085
+ if (oldString === "") {
2086
+ fs4.writeFileSync(safePath, newString, "utf-8");
2087
+ return {
2088
+ result: "Edit applied successfully.",
2089
+ title: filePath,
2090
+ metadata: { output: "Edit applied successfully." }
2091
+ };
2092
+ }
2093
+ const idx = content.indexOf(oldString);
2094
+ if (idx === -1) {
2095
+ return { result: "", error: `edit fallback: could not find oldString in ${filePath}` };
2096
+ }
2097
+ const newContent = content.substring(0, idx) + newString + content.substring(idx + oldString.length);
2098
+ fs4.writeFileSync(safePath, newContent, "utf-8");
2099
+ return {
2100
+ result: "Edit applied successfully.",
2101
+ title: filePath,
2102
+ metadata: { output: "Edit applied successfully." }
2103
+ };
2104
+ } catch (e) {
2105
+ const msg = e instanceof Error ? e.message : String(e);
2106
+ return { result: "", error: sanitizeErrorMessage(msg) };
2107
+ }
2108
+ }
1995
2109
 
1996
2110
  // src/gitlab-workflow-token-client.ts
1997
2111
  var TOKEN_CACHE_DURATION_MS = 25 * 60 * 1e3;
@@ -3082,6 +3196,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3082
3196
  const preapprovedTools = this.workflowOptions.preapprovedTools ?? mcpTools.map((t) => t.name);
3083
3197
  const additionalContext = this.buildAdditionalContext(options.prompt);
3084
3198
  const toolExecutor = this.toolExecutor ?? null;
3199
+ const availableToolNames = new Set(options.tools?.map((t) => t.name) ?? []);
3085
3200
  await this.tokenClient.getToken(
3086
3201
  this.workflowOptions.workflowDefinition ?? DEFAULT_WORKFLOW_DEFINITION,
3087
3202
  this.workflowOptions.rootNamespaceId
@@ -3142,7 +3257,8 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3142
3257
  controller,
3143
3258
  wsClient,
3144
3259
  toolExecutor,
3145
- () => `text-${textBlockCounter++}`
3260
+ () => `text-${textBlockCounter++}`,
3261
+ availableToolNames
3146
3262
  );
3147
3263
  }
3148
3264
  );
@@ -3223,7 +3339,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3223
3339
  // ---------------------------------------------------------------------------
3224
3340
  // Event handling
3225
3341
  // ---------------------------------------------------------------------------
3226
- handleWorkflowEvent(ss, event, controller, wsClient, toolExecutor, nextTextId) {
3342
+ handleWorkflowEvent(ss, event, controller, wsClient, toolExecutor, nextTextId, availableToolNames) {
3227
3343
  if (ss.streamClosed) {
3228
3344
  return;
3229
3345
  }
@@ -3280,7 +3396,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3280
3396
  break;
3281
3397
  }
3282
3398
  case "builtin-tool-request": {
3283
- const mapped = mapBuiltinTool(event.toolName, event.data);
3399
+ const mapped = mapBuiltinTool(event.toolName, event.data, availableToolNames);
3284
3400
  const mappedArgs = JSON.stringify(mapped.args);
3285
3401
  if (ss.activeTextBlockId) {
3286
3402
  controller.enqueue({ type: "text-end", id: ss.activeTextBlockId });
@@ -3480,37 +3596,19 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3480
3596
  };
3481
3597
  try {
3482
3598
  if (toolExecutor) {
3483
- const result = await toolExecutor(toolName, argsJson, requestID);
3599
+ let result = await toolExecutor(toolName, argsJson, requestID);
3600
+ if (result.error && /^Unknown tool:/.test(result.error)) {
3601
+ const fallback = executeBuiltinFallback(toolName, argsJson);
3602
+ if (fallback) {
3603
+ result = fallback;
3604
+ }
3605
+ }
3484
3606
  wsClient.sendActionResponse(requestID, result.result, result.error);
3485
3607
  ss.streamedInputChars += argsJson.length;
3486
3608
  ss.streamedOutputChars += result.result.length;
3487
- let toolOutput = result.result;
3488
- let toolTitle = `${toolName} result`;
3489
- let toolMetadata = { output: result.result };
3490
- try {
3491
- const parsed = JSON.parse(result.result);
3492
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3493
- if (typeof parsed.output === "string") {
3494
- toolOutput = parsed.output;
3495
- } else if (parsed.output != null) {
3496
- toolOutput = JSON.stringify(parsed.output);
3497
- }
3498
- if (typeof parsed.title === "string") toolTitle = parsed.title;
3499
- if (parsed.metadata && typeof parsed.metadata === "object") {
3500
- toolMetadata = {};
3501
- for (const [k, v] of Object.entries(parsed.metadata)) {
3502
- toolMetadata[k] = typeof v === "string" ? v : JSON.stringify(v);
3503
- }
3504
- if (!("output" in toolMetadata)) {
3505
- toolMetadata.output = toolOutput;
3506
- }
3507
- }
3508
- } else if (Array.isArray(parsed)) {
3509
- toolOutput = JSON.stringify(parsed);
3510
- toolMetadata = { output: toolOutput };
3511
- }
3512
- } catch {
3513
- }
3609
+ const toolOutput = result.result;
3610
+ const toolTitle = result.title ?? `${toolName} result`;
3611
+ const toolMetadata = result.metadata ?? { output: result.result };
3514
3612
  if (result.error) {
3515
3613
  let errorText;
3516
3614
  if (typeof result.error === "string") {
@@ -3520,16 +3618,11 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3520
3618
  } else {
3521
3619
  errorText = String(result.error);
3522
3620
  }
3523
- const errorOutput = toolOutput || errorText;
3524
3621
  safeEnqueue({
3525
3622
  type: "tool-result",
3526
3623
  toolCallId: requestID,
3527
3624
  toolName,
3528
- result: {
3529
- output: errorOutput,
3530
- title: toolTitle,
3531
- metadata: { ...toolMetadata, error: errorText }
3532
- },
3625
+ result: errorText,
3533
3626
  isError: true,
3534
3627
  providerExecuted: true
3535
3628
  });
@@ -3554,11 +3647,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3554
3647
  type: "tool-result",
3555
3648
  toolCallId: requestID,
3556
3649
  toolName,
3557
- result: {
3558
- output: errorMsg,
3559
- title: `${toolName} error`,
3560
- metadata: { output: errorMsg }
3561
- },
3650
+ result: errorMsg,
3562
3651
  isError: true,
3563
3652
  providerExecuted: true
3564
3653
  });
@@ -3571,11 +3660,7 @@ var GitLabWorkflowLanguageModel = class _GitLabWorkflowLanguageModel {
3571
3660
  type: "tool-result",
3572
3661
  toolCallId: requestID,
3573
3662
  toolName,
3574
- result: {
3575
- output: errorMsg,
3576
- title: `${toolName} error`,
3577
- metadata: { output: errorMsg }
3578
- },
3663
+ result: errorMsg,
3579
3664
  isError: true,
3580
3665
  providerExecuted: true
3581
3666
  });
@@ -4174,6 +4259,170 @@ function createGitLab(options = {}) {
4174
4259
  return provider;
4175
4260
  }
4176
4261
  var gitlab = createGitLab();
4262
+
4263
+ // src/gitlab-model-config.ts
4264
+ import * as fs3 from "fs";
4265
+ import * as path4 from "path";
4266
+ import * as os3 from "os";
4267
+ var DEFAULT_MODELS_YML_URL = "https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/raw/main/ai_gateway/model_selection/models.yml";
4268
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
4269
+ var DEFAULT_CONTEXT = 2e5;
4270
+ var DEFAULT_OUTPUT = 64e3;
4271
+ function getCacheFilePath2() {
4272
+ const cacheHome = process.env.XDG_CACHE_HOME || path4.join(os3.homedir(), ".cache");
4273
+ return path4.join(cacheHome, "opencode", "gitlab-model-configs.json");
4274
+ }
4275
+ function readCacheFile() {
4276
+ try {
4277
+ const filePath = getCacheFilePath2();
4278
+ if (!fs3.existsSync(filePath)) return null;
4279
+ const raw = fs3.readFileSync(filePath, "utf-8");
4280
+ return JSON.parse(raw);
4281
+ } catch {
4282
+ return null;
4283
+ }
4284
+ }
4285
+ function writeCacheFile(configs) {
4286
+ try {
4287
+ const filePath = getCacheFilePath2();
4288
+ const dir = path4.dirname(filePath);
4289
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
4290
+ const data = {
4291
+ configs: Object.fromEntries(configs),
4292
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4293
+ };
4294
+ fs3.writeFileSync(filePath, JSON.stringify(data, null, 2), { mode: 384 });
4295
+ } catch {
4296
+ }
4297
+ }
4298
+ function loadCacheFile(ttlMs) {
4299
+ const cached = readCacheFile();
4300
+ if (!cached) return null;
4301
+ const writtenAt = new Date(cached.updatedAt).getTime();
4302
+ const age = Date.now() - writtenAt;
4303
+ if (age > ttlMs) return null;
4304
+ return { configs: new Map(Object.entries(cached.configs)), writtenAt };
4305
+ }
4306
+ var GitLabModelConfigRegistry = class {
4307
+ url;
4308
+ ttlMs;
4309
+ fetchFn;
4310
+ memCache = null;
4311
+ memExpiresAt = 0;
4312
+ pending = null;
4313
+ constructor(options) {
4314
+ this.url = options?.url ?? DEFAULT_MODELS_YML_URL;
4315
+ this.ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
4316
+ this.fetchFn = options?.fetch ?? fetch;
4317
+ }
4318
+ /**
4319
+ * Get model configs, fetching and caching as needed.
4320
+ * Returns a Map keyed by `gitlab_identifier` (the discovery `ref`).
4321
+ */
4322
+ async getConfigs() {
4323
+ if (this.memCache && Date.now() < this.memExpiresAt) {
4324
+ return this.memCache;
4325
+ }
4326
+ const fileCached = loadCacheFile(this.ttlMs);
4327
+ if (fileCached) {
4328
+ this.memCache = fileCached.configs;
4329
+ this.memExpiresAt = fileCached.writtenAt + this.ttlMs;
4330
+ return fileCached.configs;
4331
+ }
4332
+ if (this.pending) return this.pending;
4333
+ this.pending = this.fetchConfigs();
4334
+ try {
4335
+ return await this.pending;
4336
+ } finally {
4337
+ this.pending = null;
4338
+ }
4339
+ }
4340
+ /**
4341
+ * Look up config for a single model ref.
4342
+ * Returns defaults if the ref is not found or fetch fails.
4343
+ */
4344
+ async getConfig(ref) {
4345
+ const configs = await this.getConfigs();
4346
+ return configs.get(ref) ?? { context: DEFAULT_CONTEXT, output: DEFAULT_OUTPUT };
4347
+ }
4348
+ /** Invalidate both in-memory and file caches. */
4349
+ invalidateCache() {
4350
+ this.memCache = null;
4351
+ this.memExpiresAt = 0;
4352
+ try {
4353
+ const filePath = getCacheFilePath2();
4354
+ if (fs3.existsSync(filePath)) {
4355
+ fs3.unlinkSync(filePath);
4356
+ }
4357
+ } catch {
4358
+ }
4359
+ }
4360
+ async fetchConfigs() {
4361
+ try {
4362
+ const res = await this.fetchFn(this.url);
4363
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
4364
+ const text = await res.text();
4365
+ const configs = parseModelsYml(text);
4366
+ this.memCache = configs;
4367
+ this.memExpiresAt = Date.now() + this.ttlMs;
4368
+ writeCacheFile(configs);
4369
+ return configs;
4370
+ } catch {
4371
+ return this.memCache ?? loadCacheFile(Infinity)?.configs ?? /* @__PURE__ */ new Map();
4372
+ }
4373
+ }
4374
+ };
4375
+ function parseModelsYml(text) {
4376
+ const configs = /* @__PURE__ */ new Map();
4377
+ let currentIdentifier = null;
4378
+ let currentContext = 0;
4379
+ let currentOutput = 0;
4380
+ let inParams = false;
4381
+ let paramsIndent = -1;
4382
+ for (const line of text.split("\n")) {
4383
+ const idMatch = line.match(/^\s*gitlab_identifier:\s*"?([^"#\s]+)"?/);
4384
+ if (idMatch) {
4385
+ if (currentIdentifier) {
4386
+ configs.set(currentIdentifier, {
4387
+ context: currentContext || DEFAULT_CONTEXT,
4388
+ output: currentOutput || DEFAULT_OUTPUT
4389
+ });
4390
+ }
4391
+ currentIdentifier = idMatch[1];
4392
+ currentContext = 0;
4393
+ currentOutput = 0;
4394
+ inParams = false;
4395
+ paramsIndent = -1;
4396
+ }
4397
+ const ctxMatch = line.match(/^\s*max_context_tokens:\s*([0-9_]+)/);
4398
+ if (ctxMatch && currentIdentifier) {
4399
+ currentContext = parseInt(ctxMatch[1].replace(/_/g, ""), 10);
4400
+ }
4401
+ if (/^\s*params:\s*$/.test(line)) {
4402
+ inParams = true;
4403
+ paramsIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
4404
+ } else if (inParams && /^\s*\S+:/.test(line)) {
4405
+ const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
4406
+ if (indent <= paramsIndent) {
4407
+ inParams = false;
4408
+ paramsIndent = -1;
4409
+ }
4410
+ }
4411
+ if (inParams) {
4412
+ const maxTokensMatch = line.match(/^\s*max_tokens:\s*([0-9_]+)/);
4413
+ if (maxTokensMatch && currentIdentifier) {
4414
+ currentOutput = parseInt(maxTokensMatch[1].replace(/_/g, ""), 10);
4415
+ }
4416
+ }
4417
+ }
4418
+ if (currentIdentifier) {
4419
+ configs.set(currentIdentifier, {
4420
+ context: currentContext || DEFAULT_CONTEXT,
4421
+ output: currentOutput || DEFAULT_OUTPUT
4422
+ });
4423
+ }
4424
+ return configs;
4425
+ }
4177
4426
  export {
4178
4427
  AGENT_PRIVILEGES,
4179
4428
  BUNDLED_CLIENT_ID,
@@ -4187,6 +4436,7 @@ export {
4187
4436
  GitLabDirectAccessClient,
4188
4437
  GitLabError,
4189
4438
  GitLabModelCache,
4439
+ GitLabModelConfigRegistry,
4190
4440
  GitLabModelDiscovery,
4191
4441
  GitLabOAuthManager,
4192
4442
  GitLabOpenAILanguageModel,
@@ -4215,6 +4465,7 @@ export {
4215
4465
  getWorkflowModelRef,
4216
4466
  gitlab,
4217
4467
  isResponsesApiModel,
4218
- isWorkflowModel
4468
+ isWorkflowModel,
4469
+ parseModelsYml
4219
4470
  };
4220
4471
  //# sourceMappingURL=index.mjs.map