gitlab-ai-provider 6.9.2 → 6.10.0

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 CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
4
4
 
5
+ ## 6.10.0 (2026-07-01)
6
+
7
+ - Merge branch 'feature-add-claude-sonnet-5' into 'main' ([5294b5e](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/5294b5e))
8
+ - feat(models): add Claude Sonnet 5 model mappings ([85976b4](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/85976b4))
9
+ - chore(skill): add add-gitlab-model skill to automate model onboarding ([6bbfc4d](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/6bbfc4d))
10
+
11
+ ## <small>6.9.3 (2026-06-11)</small>
12
+
13
+ - Merge branch 'fix/concurrent-auth-token-race' into 'main' ([7306083](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/7306083))
14
+ - fix(auth): prevent concurrent auth failures from token races ([49ed5c7](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/49ed5c7))
15
+
5
16
  ## <small>6.9.2 (2026-06-10)</small>
6
17
 
7
18
  - Merge branch 'fix/auth-json-api-token-handling' into 'main' ([19a6e58](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/19a6e58))
package/dist/index.d.mts CHANGED
@@ -6,6 +6,11 @@ interface GitLabAnthropicConfig {
6
6
  instanceUrl: string;
7
7
  getHeaders: () => Record<string, string>;
8
8
  fetch?: typeof fetch;
9
+ /**
10
+ * Optional async hook that resolves once credentials are loaded, awaited
11
+ * before the synchronous getHeaders() is used for the direct-access request.
12
+ */
13
+ ensureApiKey?: () => Promise<void>;
9
14
  /**
10
15
  * Optional callback to refresh the API key when a 401 error occurs.
11
16
  * Should clear cached credentials and re-fetch from auth provider.
@@ -1073,6 +1078,7 @@ interface GitLabOpenAIConfig {
1073
1078
  instanceUrl: string;
1074
1079
  getHeaders: () => Record<string, string>;
1075
1080
  fetch?: typeof fetch;
1081
+ ensureApiKey?: () => Promise<void>;
1076
1082
  refreshApiKey?: () => Promise<void>;
1077
1083
  openaiModel?: string;
1078
1084
  maxTokens?: number;
@@ -1431,6 +1437,14 @@ declare const DEFAULT_AI_GATEWAY_URL = "https://cloud.gitlab.com";
1431
1437
  interface GitLabDirectAccessConfig {
1432
1438
  instanceUrl: string;
1433
1439
  getHeaders: () => Record<string, string>;
1440
+ /**
1441
+ * Optional async hook that resolves once credentials are loaded.
1442
+ * `getHeaders` is synchronous, but the API key is loaded asynchronously; the
1443
+ * client awaits this before building request headers so the first request on
1444
+ * a cold provider doesn't race the lazy key load (which would otherwise throw
1445
+ * "API key is missing" or send an empty Authorization header).
1446
+ */
1447
+ ensureApiKey?: () => Promise<void>;
1434
1448
  fetch?: typeof fetch;
1435
1449
  /**
1436
1450
  * Optional callback to refresh the API key when a 401 error occurs.
package/dist/index.d.ts CHANGED
@@ -6,6 +6,11 @@ interface GitLabAnthropicConfig {
6
6
  instanceUrl: string;
7
7
  getHeaders: () => Record<string, string>;
8
8
  fetch?: typeof fetch;
9
+ /**
10
+ * Optional async hook that resolves once credentials are loaded, awaited
11
+ * before the synchronous getHeaders() is used for the direct-access request.
12
+ */
13
+ ensureApiKey?: () => Promise<void>;
9
14
  /**
10
15
  * Optional callback to refresh the API key when a 401 error occurs.
11
16
  * Should clear cached credentials and re-fetch from auth provider.
@@ -1073,6 +1078,7 @@ interface GitLabOpenAIConfig {
1073
1078
  instanceUrl: string;
1074
1079
  getHeaders: () => Record<string, string>;
1075
1080
  fetch?: typeof fetch;
1081
+ ensureApiKey?: () => Promise<void>;
1076
1082
  refreshApiKey?: () => Promise<void>;
1077
1083
  openaiModel?: string;
1078
1084
  maxTokens?: number;
@@ -1431,6 +1437,14 @@ declare const DEFAULT_AI_GATEWAY_URL = "https://cloud.gitlab.com";
1431
1437
  interface GitLabDirectAccessConfig {
1432
1438
  instanceUrl: string;
1433
1439
  getHeaders: () => Record<string, string>;
1440
+ /**
1441
+ * Optional async hook that resolves once credentials are loaded.
1442
+ * `getHeaders` is synchronous, but the API key is loaded asynchronously; the
1443
+ * client awaits this before building request headers so the first request on
1444
+ * a cold provider doesn't race the lazy key load (which would otherwise throw
1445
+ * "API key is missing" or send an empty Authorization header).
1446
+ */
1447
+ ensureApiKey?: () => Promise<void>;
1434
1448
  fetch?: typeof fetch;
1435
1449
  /**
1436
1450
  * Optional callback to refresh the API key when a 401 error occurs.
package/dist/index.js CHANGED
@@ -727,6 +727,9 @@ var GitLabDirectAccessClient = class {
727
727
  if (this.config.featureFlags && Object.keys(this.config.featureFlags).length > 0) {
728
728
  requestBody.feature_flags = this.config.featureFlags;
729
729
  }
730
+ if (this.config.ensureApiKey) {
731
+ await this.config.ensureApiKey();
732
+ }
730
733
  try {
731
734
  const response = await this.fetchFn(url, {
732
735
  method: "POST",
@@ -822,6 +825,7 @@ var GitLabAnthropicLanguageModel = class {
822
825
  this.directAccessClient = new GitLabDirectAccessClient({
823
826
  instanceUrl: config.instanceUrl,
824
827
  getHeaders: config.getHeaders,
828
+ ensureApiKey: config.ensureApiKey,
825
829
  refreshApiKey: config.refreshApiKey,
826
830
  fetch: config.fetch,
827
831
  featureFlags: config.featureFlags,
@@ -1457,6 +1461,7 @@ var MODEL_MAPPINGS = {
1457
1461
  "duo-chat-opus-4-8": { provider: "anthropic", model: "claude-opus-4-8" },
1458
1462
  "duo-chat-opus-4-7": { provider: "anthropic", model: "claude-opus-4-7" },
1459
1463
  "duo-chat-opus-4-6": { provider: "anthropic", model: "claude-opus-4-6" },
1464
+ "duo-chat-sonnet-5": { provider: "anthropic", model: "claude-sonnet-5" },
1460
1465
  "duo-chat-sonnet-4-6": { provider: "anthropic", model: "claude-sonnet-4-6" },
1461
1466
  "duo-chat-opus-4-5": { provider: "anthropic", model: "claude-opus-4-5-20251101" },
1462
1467
  "duo-chat-sonnet-4-5": { provider: "anthropic", model: "claude-sonnet-4-5-20250929" },
@@ -1496,6 +1501,7 @@ var MODEL_MAPPINGS = {
1496
1501
  provider: "workflow",
1497
1502
  model: "anthropic/claude-sonnet-4-5-20250929"
1498
1503
  },
1504
+ "duo-workflow-sonnet-5": { provider: "workflow", model: "claude_sonnet_5" },
1499
1505
  "duo-workflow-sonnet-4-6": { provider: "workflow", model: "claude_sonnet_4_6" },
1500
1506
  "duo-workflow-opus-4-5": {
1501
1507
  provider: "workflow",
@@ -1555,6 +1561,7 @@ var GitLabOpenAILanguageModel = class {
1555
1561
  this.directAccessClient = new GitLabDirectAccessClient({
1556
1562
  instanceUrl: config.instanceUrl,
1557
1563
  getHeaders: config.getHeaders,
1564
+ ensureApiKey: config.ensureApiKey,
1558
1565
  refreshApiKey: config.refreshApiKey,
1559
1566
  fetch: config.fetch,
1560
1567
  featureFlags: config.featureFlags,
@@ -2496,7 +2503,7 @@ var import_node_async_hooks = require("async_hooks");
2496
2503
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
2497
2504
 
2498
2505
  // src/version.ts
2499
- var VERSION = true ? "6.9.1" : "0.0.0-dev";
2506
+ var VERSION = true ? "6.9.3" : "0.0.0-dev";
2500
2507
 
2501
2508
  // src/gitlab-workflow-client.ts
2502
2509
  var WS_CONNECT_TIMEOUT_MS = 3e4;
@@ -4833,9 +4840,10 @@ var TOKEN_EXPIRY_SKEW_MS = 5 * 60 * 1e3;
4833
4840
  var OAUTH_SCOPES = ["api"];
4834
4841
 
4835
4842
  // src/gitlab-oauth-manager.ts
4843
+ var globalInFlightRefreshes = /* @__PURE__ */ new Map();
4836
4844
  var GitLabOAuthManager = class {
4837
4845
  fetch;
4838
- inFlightRefreshes = /* @__PURE__ */ new Map();
4846
+ inFlightRefreshes = globalInFlightRefreshes;
4839
4847
  constructor(fetchImpl = fetch) {
4840
4848
  this.fetch = fetchImpl;
4841
4849
  }
@@ -5064,6 +5072,39 @@ async function loadOpenCodeAuth(instanceUrl) {
5064
5072
  return void 0;
5065
5073
  }
5066
5074
  }
5075
+ var inFlightOAuthRefreshes = /* @__PURE__ */ new Map();
5076
+ async function refreshOAuthToken(auth, instanceUrl, clientId) {
5077
+ const existing = inFlightOAuthRefreshes.get(instanceUrl);
5078
+ if (existing) {
5079
+ return existing;
5080
+ }
5081
+ const refreshPromise = (async () => {
5082
+ const oauthManager = new GitLabOAuthManager();
5083
+ const refreshed = await oauthManager.exchangeRefreshToken({
5084
+ instanceUrl,
5085
+ refreshToken: auth.refresh,
5086
+ clientId
5087
+ });
5088
+ const authPath = getOpenCodeAuthPath();
5089
+ const authData = JSON.parse(fs3.readFileSync(authPath, "utf-8"));
5090
+ authData.gitlab = {
5091
+ type: "oauth",
5092
+ refresh: refreshed.refreshToken,
5093
+ access: refreshed.accessToken,
5094
+ expires: refreshed.expiresAt,
5095
+ enterpriseUrl: instanceUrl
5096
+ // Use enterpriseUrl to match auth plugin format
5097
+ };
5098
+ fs3.writeFileSync(authPath, JSON.stringify(authData, null, 2), { mode: 384 });
5099
+ return refreshed.accessToken;
5100
+ })();
5101
+ inFlightOAuthRefreshes.set(instanceUrl, refreshPromise);
5102
+ try {
5103
+ return await refreshPromise;
5104
+ } finally {
5105
+ inFlightOAuthRefreshes.delete(instanceUrl);
5106
+ }
5107
+ }
5067
5108
  async function loadApiKey(options, instanceUrl, clientId) {
5068
5109
  if (options.apiKey) {
5069
5110
  return options.apiKey;
@@ -5076,23 +5117,7 @@ async function loadApiKey(options, instanceUrl, clientId) {
5076
5117
  const oauthManager = new GitLabOAuthManager();
5077
5118
  if (oauthManager.needsRefresh(auth.expires)) {
5078
5119
  try {
5079
- const refreshed = await oauthManager.exchangeRefreshToken({
5080
- instanceUrl,
5081
- refreshToken: auth.refresh,
5082
- clientId
5083
- });
5084
- const authPath = getOpenCodeAuthPath();
5085
- const authData = JSON.parse(fs3.readFileSync(authPath, "utf-8"));
5086
- authData.gitlab = {
5087
- type: "oauth",
5088
- refresh: refreshed.refreshToken,
5089
- access: refreshed.accessToken,
5090
- expires: refreshed.expiresAt,
5091
- enterpriseUrl: instanceUrl
5092
- // Use enterpriseUrl to match auth plugin format
5093
- };
5094
- fs3.writeFileSync(authPath, JSON.stringify(authData, null, 2), { mode: 384 });
5095
- return refreshed.accessToken;
5120
+ return await refreshOAuthToken(auth, instanceUrl, clientId);
5096
5121
  } catch (error) {
5097
5122
  const refreshErrorMsg = error instanceof Error ? error.message : String(error);
5098
5123
  const envApiKey = process.env[options.environmentVariableName];
@@ -5191,6 +5216,12 @@ function createGitLab(options = {}) {
5191
5216
  `ai-sdk-gitlab/${VERSION}`
5192
5217
  );
5193
5218
  };
5219
+ const ensureApiKey = async () => {
5220
+ try {
5221
+ await getApiKey();
5222
+ } catch {
5223
+ }
5224
+ };
5194
5225
  getApiKey().catch(() => {
5195
5226
  });
5196
5227
  const createAgenticChatModel = (modelId, agenticOptions) => {
@@ -5225,6 +5256,7 @@ function createGitLab(options = {}) {
5225
5256
  provider: `${providerName}.agentic`,
5226
5257
  instanceUrl,
5227
5258
  getHeaders,
5259
+ ensureApiKey,
5228
5260
  refreshApiKey,
5229
5261
  fetch: options.fetch,
5230
5262
  maxTokens: agenticOptions?.maxTokens,