gitlab-ai-provider 6.9.1 → 6.9.3
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 +10 -0
- package/dist/gitlab-ai-provider-6.9.3.tgz +0 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +59 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/gitlab-ai-provider-6.9.1.tgz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
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
|
+
## <small>6.9.3 (2026-06-11)</small>
|
|
6
|
+
|
|
7
|
+
- Merge branch 'fix/concurrent-auth-token-race' into 'main' ([7306083](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/7306083))
|
|
8
|
+
- fix(auth): prevent concurrent auth failures from token races ([49ed5c7](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/49ed5c7))
|
|
9
|
+
|
|
10
|
+
## <small>6.9.2 (2026-06-10)</small>
|
|
11
|
+
|
|
12
|
+
- Merge branch 'fix/auth-json-api-token-handling' into 'main' ([19a6e58](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/19a6e58))
|
|
13
|
+
- fix(auth): use PAT and enterpriseUrl-less OAuth tokens from auth.json ([6feeb58](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/6feeb58))
|
|
14
|
+
|
|
5
15
|
## <small>6.9.1 (2026-06-10)</small>
|
|
6
16
|
|
|
7
17
|
- Merge branch 'fix-security-review-hardening' into 'main' ([81e627b](https://gitlab.com/vglafirov/gitlab-ai-provider/commit/81e627b))
|
|
Binary file
|
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,
|
|
@@ -1555,6 +1559,7 @@ var GitLabOpenAILanguageModel = class {
|
|
|
1555
1559
|
this.directAccessClient = new GitLabDirectAccessClient({
|
|
1556
1560
|
instanceUrl: config.instanceUrl,
|
|
1557
1561
|
getHeaders: config.getHeaders,
|
|
1562
|
+
ensureApiKey: config.ensureApiKey,
|
|
1558
1563
|
refreshApiKey: config.refreshApiKey,
|
|
1559
1564
|
fetch: config.fetch,
|
|
1560
1565
|
featureFlags: config.featureFlags,
|
|
@@ -2496,7 +2501,7 @@ var import_node_async_hooks = require("async_hooks");
|
|
|
2496
2501
|
var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
|
|
2497
2502
|
|
|
2498
2503
|
// src/version.ts
|
|
2499
|
-
var VERSION = true ? "6.9.
|
|
2504
|
+
var VERSION = true ? "6.9.2" : "0.0.0-dev";
|
|
2500
2505
|
|
|
2501
2506
|
// src/gitlab-workflow-client.ts
|
|
2502
2507
|
var WS_CONNECT_TIMEOUT_MS = 3e4;
|
|
@@ -4833,9 +4838,10 @@ var TOKEN_EXPIRY_SKEW_MS = 5 * 60 * 1e3;
|
|
|
4833
4838
|
var OAUTH_SCOPES = ["api"];
|
|
4834
4839
|
|
|
4835
4840
|
// src/gitlab-oauth-manager.ts
|
|
4841
|
+
var globalInFlightRefreshes = /* @__PURE__ */ new Map();
|
|
4836
4842
|
var GitLabOAuthManager = class {
|
|
4837
4843
|
fetch;
|
|
4838
|
-
inFlightRefreshes =
|
|
4844
|
+
inFlightRefreshes = globalInFlightRefreshes;
|
|
4839
4845
|
constructor(fetchImpl = fetch) {
|
|
4840
4846
|
this.fetch = fetchImpl;
|
|
4841
4847
|
}
|
|
@@ -5048,12 +5054,15 @@ async function loadOpenCodeAuth(instanceUrl) {
|
|
|
5048
5054
|
}
|
|
5049
5055
|
const authData = JSON.parse(fs3.readFileSync(authPath, "utf-8"));
|
|
5050
5056
|
const normalizedUrl = instanceUrl.replace(/\/$/, "");
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
const normalizedEnterpriseUrl = typeof gitlabAuth.enterpriseUrl === "string" ? gitlabAuth.enterpriseUrl.replace(/\/$/, "") :
|
|
5054
|
-
|
|
5057
|
+
const gitlabAuth = authData.gitlab;
|
|
5058
|
+
if (gitlabAuth?.type === "oauth") {
|
|
5059
|
+
const normalizedEnterpriseUrl = typeof gitlabAuth.enterpriseUrl === "string" ? gitlabAuth.enterpriseUrl.replace(/\/$/, "") : void 0;
|
|
5060
|
+
const matchesInstance = normalizedEnterpriseUrl === normalizedUrl || normalizedEnterpriseUrl === void 0 && normalizedUrl === GITLAB_COM_URL;
|
|
5061
|
+
if (matchesInstance) {
|
|
5055
5062
|
return gitlabAuth;
|
|
5056
5063
|
}
|
|
5064
|
+
} else if (gitlabAuth?.type === "api" && typeof gitlabAuth.key === "string") {
|
|
5065
|
+
return gitlabAuth;
|
|
5057
5066
|
}
|
|
5058
5067
|
const auth = authData[normalizedUrl] || authData[`${normalizedUrl}/`];
|
|
5059
5068
|
return auth;
|
|
@@ -5061,32 +5070,52 @@ async function loadOpenCodeAuth(instanceUrl) {
|
|
|
5061
5070
|
return void 0;
|
|
5062
5071
|
}
|
|
5063
5072
|
}
|
|
5073
|
+
var inFlightOAuthRefreshes = /* @__PURE__ */ new Map();
|
|
5074
|
+
async function refreshOAuthToken(auth, instanceUrl, clientId) {
|
|
5075
|
+
const existing = inFlightOAuthRefreshes.get(instanceUrl);
|
|
5076
|
+
if (existing) {
|
|
5077
|
+
return existing;
|
|
5078
|
+
}
|
|
5079
|
+
const refreshPromise = (async () => {
|
|
5080
|
+
const oauthManager = new GitLabOAuthManager();
|
|
5081
|
+
const refreshed = await oauthManager.exchangeRefreshToken({
|
|
5082
|
+
instanceUrl,
|
|
5083
|
+
refreshToken: auth.refresh,
|
|
5084
|
+
clientId
|
|
5085
|
+
});
|
|
5086
|
+
const authPath = getOpenCodeAuthPath();
|
|
5087
|
+
const authData = JSON.parse(fs3.readFileSync(authPath, "utf-8"));
|
|
5088
|
+
authData.gitlab = {
|
|
5089
|
+
type: "oauth",
|
|
5090
|
+
refresh: refreshed.refreshToken,
|
|
5091
|
+
access: refreshed.accessToken,
|
|
5092
|
+
expires: refreshed.expiresAt,
|
|
5093
|
+
enterpriseUrl: instanceUrl
|
|
5094
|
+
// Use enterpriseUrl to match auth plugin format
|
|
5095
|
+
};
|
|
5096
|
+
fs3.writeFileSync(authPath, JSON.stringify(authData, null, 2), { mode: 384 });
|
|
5097
|
+
return refreshed.accessToken;
|
|
5098
|
+
})();
|
|
5099
|
+
inFlightOAuthRefreshes.set(instanceUrl, refreshPromise);
|
|
5100
|
+
try {
|
|
5101
|
+
return await refreshPromise;
|
|
5102
|
+
} finally {
|
|
5103
|
+
inFlightOAuthRefreshes.delete(instanceUrl);
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5064
5106
|
async function loadApiKey(options, instanceUrl, clientId) {
|
|
5065
5107
|
if (options.apiKey) {
|
|
5066
5108
|
return options.apiKey;
|
|
5067
5109
|
}
|
|
5068
5110
|
const auth = await loadOpenCodeAuth(instanceUrl);
|
|
5111
|
+
if (auth?.type === "api") {
|
|
5112
|
+
return auth.key;
|
|
5113
|
+
}
|
|
5069
5114
|
if (auth?.type === "oauth") {
|
|
5070
5115
|
const oauthManager = new GitLabOAuthManager();
|
|
5071
5116
|
if (oauthManager.needsRefresh(auth.expires)) {
|
|
5072
5117
|
try {
|
|
5073
|
-
|
|
5074
|
-
instanceUrl,
|
|
5075
|
-
refreshToken: auth.refresh,
|
|
5076
|
-
clientId
|
|
5077
|
-
});
|
|
5078
|
-
const authPath = getOpenCodeAuthPath();
|
|
5079
|
-
const authData = JSON.parse(fs3.readFileSync(authPath, "utf-8"));
|
|
5080
|
-
authData.gitlab = {
|
|
5081
|
-
type: "oauth",
|
|
5082
|
-
refresh: refreshed.refreshToken,
|
|
5083
|
-
access: refreshed.accessToken,
|
|
5084
|
-
expires: refreshed.expiresAt,
|
|
5085
|
-
enterpriseUrl: instanceUrl
|
|
5086
|
-
// Use enterpriseUrl to match auth plugin format
|
|
5087
|
-
};
|
|
5088
|
-
fs3.writeFileSync(authPath, JSON.stringify(authData, null, 2), { mode: 384 });
|
|
5089
|
-
return refreshed.accessToken;
|
|
5118
|
+
return await refreshOAuthToken(auth, instanceUrl, clientId);
|
|
5090
5119
|
} catch (error) {
|
|
5091
5120
|
const refreshErrorMsg = error instanceof Error ? error.message : String(error);
|
|
5092
5121
|
const envApiKey = process.env[options.environmentVariableName];
|
|
@@ -5185,6 +5214,12 @@ function createGitLab(options = {}) {
|
|
|
5185
5214
|
`ai-sdk-gitlab/${VERSION}`
|
|
5186
5215
|
);
|
|
5187
5216
|
};
|
|
5217
|
+
const ensureApiKey = async () => {
|
|
5218
|
+
try {
|
|
5219
|
+
await getApiKey();
|
|
5220
|
+
} catch {
|
|
5221
|
+
}
|
|
5222
|
+
};
|
|
5188
5223
|
getApiKey().catch(() => {
|
|
5189
5224
|
});
|
|
5190
5225
|
const createAgenticChatModel = (modelId, agenticOptions) => {
|
|
@@ -5219,6 +5254,7 @@ function createGitLab(options = {}) {
|
|
|
5219
5254
|
provider: `${providerName}.agentic`,
|
|
5220
5255
|
instanceUrl,
|
|
5221
5256
|
getHeaders,
|
|
5257
|
+
ensureApiKey,
|
|
5222
5258
|
refreshApiKey,
|
|
5223
5259
|
fetch: options.fetch,
|
|
5224
5260
|
maxTokens: agenticOptions?.maxTokens,
|