pi-gitlab-duo 0.1.0 → 0.1.2
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/README.md +16 -11
- package/index.ts +115 -168
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,14 +6,16 @@ Provides access to GitLab Duo AI models (Claude and GPT) through GitLab's AI Gat
|
|
|
6
6
|
|
|
7
7
|
## Models
|
|
8
8
|
|
|
9
|
-
| Model ID |
|
|
10
|
-
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
9
|
+
| Model ID | Name |
|
|
10
|
+
|----------|------|
|
|
11
|
+
| `claude-opus-4-5-20251101` | Claude Opus 4.5 |
|
|
12
|
+
| `claude-sonnet-4-5-20250929` | Claude Sonnet 4.5 |
|
|
13
|
+
| `claude-haiku-4-5-20251001` | Claude Haiku 4.5 |
|
|
14
|
+
| `gpt-5.1-2025-11-13` | GPT-5.1 |
|
|
15
|
+
| `gpt-5-mini-2025-08-07` | GPT-5 Mini |
|
|
16
|
+
| `gpt-5-codex` | GPT-5 Codex |
|
|
17
|
+
|
|
18
|
+
All models support extended thinking/reasoning.
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -38,17 +40,20 @@ Set the `GITLAB_TOKEN` environment variable:
|
|
|
38
40
|
|
|
39
41
|
```bash
|
|
40
42
|
export GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
|
|
41
|
-
pi --provider gitlab-duo --model
|
|
43
|
+
pi --provider gitlab-duo --model claude-sonnet-4-5-20250929
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
## Usage
|
|
45
47
|
|
|
46
48
|
```bash
|
|
47
49
|
# Interactive mode
|
|
48
|
-
pi --provider gitlab-duo --model
|
|
50
|
+
pi --provider gitlab-duo --model claude-sonnet-4-5-20250929
|
|
51
|
+
|
|
52
|
+
# With thinking enabled
|
|
53
|
+
pi --provider gitlab-duo --model claude-sonnet-4-5-20250929 --thinking medium
|
|
49
54
|
|
|
50
55
|
# Print mode
|
|
51
|
-
pi --provider gitlab-duo --model
|
|
56
|
+
pi --provider gitlab-duo --model claude-sonnet-4-5-20250929 -p "explain git rebase"
|
|
52
57
|
```
|
|
53
58
|
|
|
54
59
|
## Requirements
|
package/index.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GitLab Duo Provider Extension
|
|
2
|
+
* GitLab Duo Provider Extension
|
|
3
3
|
*
|
|
4
4
|
* Provides access to GitLab Duo AI models (Claude and GPT) through GitLab's AI Gateway.
|
|
5
5
|
* Delegates to pi-ai's built-in Anthropic and OpenAI streaming implementations.
|
|
6
6
|
*
|
|
7
|
-
* Installation:
|
|
8
|
-
* pi install npm:pi-gitlab-duo
|
|
9
|
-
*
|
|
10
7
|
* Usage:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* # With PAT
|
|
15
|
-
* GITLAB_TOKEN=glpat-... pi --provider gitlab-duo --model duo-chat-sonnet-4-5
|
|
8
|
+
* pi -e ./packages/coding-agent/examples/extensions/custom-provider-gitlab-duo
|
|
9
|
+
* # Then /login gitlab-duo, or set GITLAB_TOKEN=glpat-...
|
|
16
10
|
*/
|
|
17
11
|
|
|
18
12
|
import {
|
|
@@ -24,7 +18,8 @@ import {
|
|
|
24
18
|
type OAuthCredentials,
|
|
25
19
|
type OAuthLoginCallbacks,
|
|
26
20
|
type SimpleStreamOptions,
|
|
27
|
-
|
|
21
|
+
streamSimpleAnthropic,
|
|
22
|
+
streamSimpleOpenAIResponses,
|
|
28
23
|
} from "@mariozechner/pi-ai";
|
|
29
24
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
30
25
|
|
|
@@ -37,42 +32,101 @@ const AI_GATEWAY_URL = "https://cloud.gitlab.com";
|
|
|
37
32
|
const ANTHROPIC_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/anthropic/`;
|
|
38
33
|
const OPENAI_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/openai/v1`;
|
|
39
34
|
|
|
40
|
-
// Bundled OAuth client ID for gitlab.com (from opencode-gitlab-auth, registered with localhost redirect)
|
|
41
35
|
const BUNDLED_CLIENT_ID = "1d89f9fdb23ee96d4e603201f6861dab6e143c5c3c00469a018a2d94bdc03d4e";
|
|
42
36
|
const OAUTH_SCOPES = ["api"];
|
|
43
37
|
const REDIRECT_URI = "http://127.0.0.1:8080/callback";
|
|
44
|
-
|
|
45
|
-
// Direct access token cache (25 min, tokens expire after 30 min)
|
|
46
38
|
const DIRECT_ACCESS_TTL = 25 * 60 * 1000;
|
|
47
39
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Models - exported for use by tests
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
type Backend = "anthropic" | "openai";
|
|
45
|
+
|
|
46
|
+
interface GitLabModel {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
backend: Backend;
|
|
50
|
+
baseUrl: string;
|
|
51
|
+
reasoning: boolean;
|
|
52
|
+
input: ("text" | "image")[];
|
|
53
|
+
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
54
|
+
contextWindow: number;
|
|
55
|
+
maxTokens: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const MODELS: GitLabModel[] = [
|
|
59
|
+
// Anthropic
|
|
60
|
+
{
|
|
61
|
+
id: "claude-opus-4-5-20251101",
|
|
62
|
+
name: "Claude Opus 4.5",
|
|
63
|
+
backend: "anthropic",
|
|
56
64
|
baseUrl: ANTHROPIC_PROXY_URL,
|
|
65
|
+
reasoning: true,
|
|
66
|
+
input: ["text", "image"],
|
|
67
|
+
cost: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
68
|
+
contextWindow: 200000,
|
|
69
|
+
maxTokens: 32000,
|
|
57
70
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
{
|
|
72
|
+
id: "claude-sonnet-4-5-20250929",
|
|
73
|
+
name: "Claude Sonnet 4.5",
|
|
74
|
+
backend: "anthropic",
|
|
61
75
|
baseUrl: ANTHROPIC_PROXY_URL,
|
|
76
|
+
reasoning: true,
|
|
77
|
+
input: ["text", "image"],
|
|
78
|
+
cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
79
|
+
contextWindow: 200000,
|
|
80
|
+
maxTokens: 16384,
|
|
62
81
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
{
|
|
83
|
+
id: "claude-haiku-4-5-20251001",
|
|
84
|
+
name: "Claude Haiku 4.5",
|
|
85
|
+
backend: "anthropic",
|
|
66
86
|
baseUrl: ANTHROPIC_PROXY_URL,
|
|
87
|
+
reasoning: true,
|
|
88
|
+
input: ["text", "image"],
|
|
89
|
+
cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
90
|
+
contextWindow: 200000,
|
|
91
|
+
maxTokens: 8192,
|
|
92
|
+
},
|
|
93
|
+
// OpenAI (all use Responses API)
|
|
94
|
+
{
|
|
95
|
+
id: "gpt-5.1-2025-11-13",
|
|
96
|
+
name: "GPT-5.1",
|
|
97
|
+
backend: "openai",
|
|
98
|
+
baseUrl: OPENAI_PROXY_URL,
|
|
99
|
+
reasoning: true,
|
|
100
|
+
input: ["text", "image"],
|
|
101
|
+
cost: { input: 2.5, output: 10, cacheRead: 0, cacheWrite: 0 },
|
|
102
|
+
contextWindow: 128000,
|
|
103
|
+
maxTokens: 16384,
|
|
67
104
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
{
|
|
106
|
+
id: "gpt-5-mini-2025-08-07",
|
|
107
|
+
name: "GPT-5 Mini",
|
|
108
|
+
backend: "openai",
|
|
72
109
|
baseUrl: OPENAI_PROXY_URL,
|
|
110
|
+
reasoning: true,
|
|
111
|
+
input: ["text", "image"],
|
|
112
|
+
cost: { input: 0.15, output: 0.6, cacheRead: 0, cacheWrite: 0 },
|
|
113
|
+
contextWindow: 128000,
|
|
114
|
+
maxTokens: 16384,
|
|
73
115
|
},
|
|
74
|
-
|
|
75
|
-
|
|
116
|
+
{
|
|
117
|
+
id: "gpt-5-codex",
|
|
118
|
+
name: "GPT-5 Codex",
|
|
119
|
+
backend: "openai",
|
|
120
|
+
baseUrl: OPENAI_PROXY_URL,
|
|
121
|
+
reasoning: true,
|
|
122
|
+
input: ["text", "image"],
|
|
123
|
+
cost: { input: 2.5, output: 10, cacheRead: 0, cacheWrite: 0 },
|
|
124
|
+
contextWindow: 128000,
|
|
125
|
+
maxTokens: 16384,
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const MODEL_MAP = new Map(MODELS.map((m) => [m.id, m]));
|
|
76
130
|
|
|
77
131
|
// =============================================================================
|
|
78
132
|
// Direct Access Token Cache
|
|
@@ -92,13 +146,9 @@ async function getDirectAccessToken(gitlabAccessToken: string): Promise<DirectAc
|
|
|
92
146
|
return cachedDirectAccess;
|
|
93
147
|
}
|
|
94
148
|
|
|
95
|
-
const
|
|
96
|
-
const response = await fetch(url, {
|
|
149
|
+
const response = await fetch(`${GITLAB_COM_URL}/api/v4/ai/third_party_agents/direct_access`, {
|
|
97
150
|
method: "POST",
|
|
98
|
-
headers: {
|
|
99
|
-
Authorization: `Bearer ${gitlabAccessToken}`,
|
|
100
|
-
"Content-Type": "application/json",
|
|
101
|
-
},
|
|
151
|
+
headers: { Authorization: `Bearer ${gitlabAccessToken}`, "Content-Type": "application/json" },
|
|
102
152
|
body: JSON.stringify({ feature_flags: { DuoAgentPlatformNext: true } }),
|
|
103
153
|
});
|
|
104
154
|
|
|
@@ -113,11 +163,7 @@ async function getDirectAccessToken(gitlabAccessToken: string): Promise<DirectAc
|
|
|
113
163
|
}
|
|
114
164
|
|
|
115
165
|
const data = (await response.json()) as { token: string; headers: Record<string, string> };
|
|
116
|
-
cachedDirectAccess = {
|
|
117
|
-
token: data.token,
|
|
118
|
-
headers: data.headers,
|
|
119
|
-
expiresAt: now + DIRECT_ACCESS_TTL,
|
|
120
|
-
};
|
|
166
|
+
cachedDirectAccess = { token: data.token, headers: data.headers, expiresAt: now + DIRECT_ACCESS_TTL };
|
|
121
167
|
return cachedDirectAccess;
|
|
122
168
|
}
|
|
123
169
|
|
|
@@ -126,7 +172,7 @@ function invalidateDirectAccessToken() {
|
|
|
126
172
|
}
|
|
127
173
|
|
|
128
174
|
// =============================================================================
|
|
129
|
-
// OAuth
|
|
175
|
+
// OAuth
|
|
130
176
|
// =============================================================================
|
|
131
177
|
|
|
132
178
|
async function generatePKCE(): Promise<{ verifier: string; challenge: string }> {
|
|
@@ -136,21 +182,16 @@ async function generatePKCE(): Promise<{ verifier: string; challenge: string }>
|
|
|
136
182
|
.replace(/\+/g, "-")
|
|
137
183
|
.replace(/\//g, "_")
|
|
138
184
|
.replace(/=+$/, "");
|
|
139
|
-
|
|
140
|
-
const encoder = new TextEncoder();
|
|
141
|
-
const data = encoder.encode(verifier);
|
|
142
|
-
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
185
|
+
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
143
186
|
const challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
|
|
144
187
|
.replace(/\+/g, "-")
|
|
145
188
|
.replace(/\//g, "_")
|
|
146
189
|
.replace(/=+$/, "");
|
|
147
|
-
|
|
148
190
|
return { verifier, challenge };
|
|
149
191
|
}
|
|
150
192
|
|
|
151
193
|
async function loginGitLab(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
152
194
|
const { verifier, challenge } = await generatePKCE();
|
|
153
|
-
|
|
154
195
|
const authParams = new URLSearchParams({
|
|
155
196
|
client_id: BUNDLED_CLIENT_ID,
|
|
156
197
|
redirect_uri: REDIRECT_URI,
|
|
@@ -163,9 +204,7 @@ async function loginGitLab(callbacks: OAuthLoginCallbacks): Promise<OAuthCredent
|
|
|
163
204
|
|
|
164
205
|
callbacks.onAuth({ url: `${GITLAB_COM_URL}/oauth/authorize?${authParams.toString()}` });
|
|
165
206
|
const callbackUrl = await callbacks.onPrompt({ message: "Paste the callback URL:" });
|
|
166
|
-
|
|
167
|
-
const urlObj = new URL(callbackUrl);
|
|
168
|
-
const code = urlObj.searchParams.get("code");
|
|
207
|
+
const code = new URL(callbackUrl).searchParams.get("code");
|
|
169
208
|
if (!code) throw new Error("No authorization code found in callback URL");
|
|
170
209
|
|
|
171
210
|
const tokenResponse = await fetch(`${GITLAB_COM_URL}/oauth/token`, {
|
|
@@ -181,14 +220,12 @@ async function loginGitLab(callbacks: OAuthLoginCallbacks): Promise<OAuthCredent
|
|
|
181
220
|
});
|
|
182
221
|
|
|
183
222
|
if (!tokenResponse.ok) throw new Error(`Token exchange failed: ${await tokenResponse.text()}`);
|
|
184
|
-
|
|
185
223
|
const data = (await tokenResponse.json()) as {
|
|
186
224
|
access_token: string;
|
|
187
225
|
refresh_token: string;
|
|
188
226
|
expires_in: number;
|
|
189
227
|
created_at: number;
|
|
190
228
|
};
|
|
191
|
-
|
|
192
229
|
invalidateDirectAccessToken();
|
|
193
230
|
return {
|
|
194
231
|
refresh: data.refresh_token,
|
|
@@ -207,16 +244,13 @@ async function refreshGitLabToken(credentials: OAuthCredentials): Promise<OAuthC
|
|
|
207
244
|
refresh_token: credentials.refresh,
|
|
208
245
|
}).toString(),
|
|
209
246
|
});
|
|
210
|
-
|
|
211
247
|
if (!response.ok) throw new Error(`Token refresh failed: ${await response.text()}`);
|
|
212
|
-
|
|
213
248
|
const data = (await response.json()) as {
|
|
214
249
|
access_token: string;
|
|
215
250
|
refresh_token: string;
|
|
216
251
|
expires_in: number;
|
|
217
252
|
created_at: number;
|
|
218
253
|
};
|
|
219
|
-
|
|
220
254
|
invalidateDirectAccessToken();
|
|
221
255
|
return {
|
|
222
256
|
refresh: data.refresh_token,
|
|
@@ -226,10 +260,10 @@ async function refreshGitLabToken(credentials: OAuthCredentials): Promise<OAuthC
|
|
|
226
260
|
}
|
|
227
261
|
|
|
228
262
|
// =============================================================================
|
|
229
|
-
//
|
|
263
|
+
// Stream Function
|
|
230
264
|
// =============================================================================
|
|
231
265
|
|
|
232
|
-
function streamGitLabDuo(
|
|
266
|
+
export function streamGitLabDuo(
|
|
233
267
|
model: Model<Api>,
|
|
234
268
|
context: Context,
|
|
235
269
|
options?: SimpleStreamOptions,
|
|
@@ -239,57 +273,22 @@ function streamGitLabDuo(
|
|
|
239
273
|
(async () => {
|
|
240
274
|
try {
|
|
241
275
|
const gitlabAccessToken = options?.apiKey;
|
|
242
|
-
if (!gitlabAccessToken)
|
|
243
|
-
throw new Error("No GitLab access token. Run /login gitlab-duo or set GITLAB_TOKEN");
|
|
244
|
-
}
|
|
276
|
+
if (!gitlabAccessToken) throw new Error("No GitLab access token. Run /login gitlab-duo or set GITLAB_TOKEN");
|
|
245
277
|
|
|
246
|
-
const
|
|
247
|
-
if (!
|
|
278
|
+
const cfg = MODEL_MAP.get(model.id);
|
|
279
|
+
if (!cfg) throw new Error(`Unknown model: ${model.id}`);
|
|
248
280
|
|
|
249
|
-
// Get direct access token (cached)
|
|
250
281
|
const directAccess = await getDirectAccessToken(gitlabAccessToken);
|
|
282
|
+
const modelWithBaseUrl = { ...model, baseUrl: cfg.baseUrl };
|
|
283
|
+
const headers = { ...directAccess.headers, Authorization: `Bearer ${directAccess.token}` };
|
|
284
|
+
const streamOptions = { ...options, apiKey: "gitlab-duo", headers };
|
|
251
285
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
api: mapping.api,
|
|
257
|
-
baseUrl: mapping.baseUrl,
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// Merge GitLab headers with Authorization bearer token
|
|
261
|
-
const headers = {
|
|
262
|
-
...directAccess.headers,
|
|
263
|
-
Authorization: `Bearer ${directAccess.token}`,
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// Delegate to pi-ai's built-in streaming
|
|
267
|
-
const innerStream = streamSimple(proxyModel, context, {
|
|
268
|
-
...options,
|
|
269
|
-
apiKey: "gitlab-duo", // Dummy value to pass validation
|
|
270
|
-
headers,
|
|
271
|
-
});
|
|
286
|
+
const innerStream =
|
|
287
|
+
cfg.backend === "anthropic"
|
|
288
|
+
? streamSimpleAnthropic(modelWithBaseUrl as Model<"anthropic-messages">, context, streamOptions)
|
|
289
|
+
: streamSimpleOpenAIResponses(modelWithBaseUrl as Model<"openai-responses">, context, streamOptions);
|
|
272
290
|
|
|
273
|
-
|
|
274
|
-
for await (const event of innerStream) {
|
|
275
|
-
// Patch the model info back to gitlab-duo
|
|
276
|
-
if ("partial" in event && event.partial) {
|
|
277
|
-
event.partial.api = model.api;
|
|
278
|
-
event.partial.provider = model.provider;
|
|
279
|
-
event.partial.model = model.id;
|
|
280
|
-
}
|
|
281
|
-
if ("message" in event && event.message) {
|
|
282
|
-
event.message.api = model.api;
|
|
283
|
-
event.message.provider = model.provider;
|
|
284
|
-
event.message.model = model.id;
|
|
285
|
-
}
|
|
286
|
-
if ("error" in event && event.error) {
|
|
287
|
-
event.error.api = model.api;
|
|
288
|
-
event.error.provider = model.provider;
|
|
289
|
-
event.error.model = model.id;
|
|
290
|
-
}
|
|
291
|
-
stream.push(event);
|
|
292
|
-
}
|
|
291
|
+
for await (const event of innerStream) stream.push(event);
|
|
293
292
|
stream.end();
|
|
294
293
|
} catch (error) {
|
|
295
294
|
stream.push({
|
|
@@ -330,73 +329,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
330
329
|
baseUrl: AI_GATEWAY_URL,
|
|
331
330
|
apiKey: "GITLAB_TOKEN",
|
|
332
331
|
api: "gitlab-duo-api",
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
contextWindow: 200000,
|
|
343
|
-
maxTokens: 32000,
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
id: "duo-chat-sonnet-4-5",
|
|
347
|
-
name: "GitLab Duo Claude Sonnet 4.5",
|
|
348
|
-
reasoning: false,
|
|
349
|
-
input: ["text"],
|
|
350
|
-
cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
351
|
-
contextWindow: 200000,
|
|
352
|
-
maxTokens: 16384,
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
id: "duo-chat-haiku-4-5",
|
|
356
|
-
name: "GitLab Duo Claude Haiku 4.5",
|
|
357
|
-
reasoning: false,
|
|
358
|
-
input: ["text"],
|
|
359
|
-
cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
360
|
-
contextWindow: 200000,
|
|
361
|
-
maxTokens: 8192,
|
|
362
|
-
},
|
|
363
|
-
// OpenAI models
|
|
364
|
-
{
|
|
365
|
-
id: "duo-chat-gpt-5-1",
|
|
366
|
-
name: "GitLab Duo GPT-5.1",
|
|
367
|
-
reasoning: false,
|
|
368
|
-
input: ["text"],
|
|
369
|
-
cost: { input: 2.5, output: 10, cacheRead: 0, cacheWrite: 0 },
|
|
370
|
-
contextWindow: 128000,
|
|
371
|
-
maxTokens: 16384,
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
id: "duo-chat-gpt-5-mini",
|
|
375
|
-
name: "GitLab Duo GPT-5 Mini",
|
|
376
|
-
reasoning: false,
|
|
377
|
-
input: ["text"],
|
|
378
|
-
cost: { input: 0.15, output: 0.6, cacheRead: 0, cacheWrite: 0 },
|
|
379
|
-
contextWindow: 128000,
|
|
380
|
-
maxTokens: 16384,
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
id: "duo-chat-gpt-5-codex",
|
|
384
|
-
name: "GitLab Duo GPT-5 Codex",
|
|
385
|
-
reasoning: false,
|
|
386
|
-
input: ["text"],
|
|
387
|
-
cost: { input: 2.5, output: 10, cacheRead: 0, cacheWrite: 0 },
|
|
388
|
-
contextWindow: 128000,
|
|
389
|
-
maxTokens: 16384,
|
|
390
|
-
},
|
|
391
|
-
],
|
|
392
|
-
|
|
332
|
+
models: MODELS.map(({ id, name, reasoning, input, cost, contextWindow, maxTokens }) => ({
|
|
333
|
+
id,
|
|
334
|
+
name,
|
|
335
|
+
reasoning,
|
|
336
|
+
input,
|
|
337
|
+
cost,
|
|
338
|
+
contextWindow,
|
|
339
|
+
maxTokens,
|
|
340
|
+
})),
|
|
393
341
|
oauth: {
|
|
394
342
|
name: "GitLab Duo",
|
|
395
343
|
login: loginGitLab,
|
|
396
344
|
refreshToken: refreshGitLabToken,
|
|
397
345
|
getApiKey: (cred) => cred.access,
|
|
398
346
|
},
|
|
399
|
-
|
|
400
347
|
streamSimple: streamGitLabDuo,
|
|
401
348
|
});
|
|
402
349
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-gitlab-duo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "GitLab Duo provider extension for pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Mario Zechner",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/badlogic/pi-gitlab-duo.git"
|
|
10
|
+
"url": "git+https://github.com/badlogic/pi-gitlab-duo.git"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"pi",
|