opencode-gitlab-dap 0.1.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/LICENSE +21 -0
- package/README.md +235 -0
- package/dist/index.cjs +212 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +189 -0
- package/dist/index.js.map +1 -0
- package/package.json +95 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GitLab Community
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# opencode-gitlab-dap
|
|
2
|
+
|
|
3
|
+
OpenCode plugin for GitLab Duo Agent Platform (DAP) workflow model discovery and selection.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This plugin connects OpenCode to the GitLab Duo Agent Platform so users can discover and select workflow models configured for their GitLab namespace. It detects the current GitLab project, queries the DAP API for available models, caches the user's choice per project, and surfaces a TUI selection prompt when multiple models are available.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```mermaid
|
|
16
|
+
sequenceDiagram
|
|
17
|
+
participant TUI as OpenCode TUI
|
|
18
|
+
participant Server as OpenCode Server
|
|
19
|
+
participant Plugin as gitlab-dap Plugin
|
|
20
|
+
participant Cache as GitLabModelCache
|
|
21
|
+
participant API as GitLab DAP API
|
|
22
|
+
|
|
23
|
+
TUI->>Server: POST /plugin/gitlab/discover
|
|
24
|
+
Server->>Plugin: route handler
|
|
25
|
+
Plugin->>Cache: check cached selection
|
|
26
|
+
alt cache hit
|
|
27
|
+
Plugin-->>Server: { status: "cached", model }
|
|
28
|
+
else no cache
|
|
29
|
+
Plugin->>API: detect project + discover models
|
|
30
|
+
API-->>Plugin: DiscoveredModels
|
|
31
|
+
alt pinned model
|
|
32
|
+
Plugin-->>Server: { status: "pinned", model }
|
|
33
|
+
else multiple models
|
|
34
|
+
Plugin->>Server: askUser via plugin-select bus
|
|
35
|
+
Server->>TUI: render selection prompt
|
|
36
|
+
TUI-->>Server: user picks model
|
|
37
|
+
Server-->>Plugin: selected ref
|
|
38
|
+
Plugin->>Cache: save selection
|
|
39
|
+
Plugin-->>Server: { status: "selected", model }
|
|
40
|
+
else default only
|
|
41
|
+
Plugin-->>Server: { status: "default", model }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
Server-->>TUI: JSON response
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
This plugin is bundled internally with OpenCode. It is not installed as a standalone package. The plugin entry is registered automatically and exposes routes under the `/plugin/gitlab/` prefix.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Configure
|
|
56
|
+
|
|
57
|
+
Authentication is resolved in this order:
|
|
58
|
+
|
|
59
|
+
1. Request headers (`x-plugin-auth-token`, `x-plugin-auth-instance`)
|
|
60
|
+
2. OpenCode auth store via `input.getAuth("gitlab")` (OAuth or API key)
|
|
61
|
+
3. Environment variables as fallback
|
|
62
|
+
|
|
63
|
+
| Variable | Description | Default |
|
|
64
|
+
| ------------------------ | ------------------------------------------------------- | -------------------- |
|
|
65
|
+
| `GITLAB_INSTANCE_URL` | Base URL of your GitLab instance | `https://gitlab.com` |
|
|
66
|
+
| `GITLAB_TOKEN` | Personal or project access token (when not using OAuth) | -- |
|
|
67
|
+
| `GITLAB_OAUTH_CLIENT_ID` | OAuth application ID for the OAuth flow | -- |
|
|
68
|
+
|
|
69
|
+
The plugin also reads `x-plugin-directory` from request headers to scope cache and project detection to a specific working directory.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Routes
|
|
74
|
+
|
|
75
|
+
All routes are prefixed with `/plugin/gitlab`.
|
|
76
|
+
|
|
77
|
+
### POST /discover
|
|
78
|
+
|
|
79
|
+
Detect the GitLab project, query DAP for available models, and return the resolved model or prompt the user to pick one.
|
|
80
|
+
|
|
81
|
+
**Request:**
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{ "fresh": true }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Set `fresh: true` to clear the cache and re-discover. The body is optional.
|
|
88
|
+
|
|
89
|
+
**Response (pinned):**
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"status": "pinned",
|
|
94
|
+
"model": { "name": "Claude 4", "ref": "claude_4" }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Response (asked then selected):**
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"status": "selected",
|
|
103
|
+
"model": { "name": "GPT-5", "ref": "gpt_5" }
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Response (no auth):**
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"status": "no_provider",
|
|
112
|
+
"error": "No auth token found"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Possible `status` values: `pinned`, `cached`, `default`, `selected`, `dismissed`, `no_provider`, `no_models`, `error`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### POST /reply
|
|
121
|
+
|
|
122
|
+
Save a model selection directly, bypassing the interactive prompt.
|
|
123
|
+
|
|
124
|
+
**Request:**
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{ "ref": "claude_4" }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Response:**
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{ "ref": "claude_4", "name": "Claude 4" }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### POST /clear
|
|
139
|
+
|
|
140
|
+
Clear the cached discovery and selection for the current project.
|
|
141
|
+
|
|
142
|
+
**Response:**
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
true
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### GET /models
|
|
151
|
+
|
|
152
|
+
Return the current discovery state without triggering a new discovery.
|
|
153
|
+
|
|
154
|
+
**Response:**
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"models": [
|
|
159
|
+
{ "name": "Claude 4", "ref": "claude_4" },
|
|
160
|
+
{ "name": "GPT-5", "ref": "gpt_5" }
|
|
161
|
+
],
|
|
162
|
+
"pinned": null,
|
|
163
|
+
"default": { "name": "Claude 4", "ref": "claude_4" },
|
|
164
|
+
"switching": true
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Discovery flow
|
|
171
|
+
|
|
172
|
+
The `discover` function resolves models through a priority chain:
|
|
173
|
+
|
|
174
|
+
1. **No token** -- returns `no_provider` immediately.
|
|
175
|
+
2. **Project detection** -- uses `GitLabProjectDetector` to find the namespace from the working directory. Fails with `no_provider` if no project is found.
|
|
176
|
+
3. **DAP query** -- calls `GitLabModelDiscovery.discover()` with the namespace GID. Returns `no_models` if the API returns nothing.
|
|
177
|
+
4. **Pinned** -- if the namespace has a pinned model, it is saved to cache and returned.
|
|
178
|
+
5. **Cached** -- if a previous selection exists in `GitLabModelCache`, it is returned.
|
|
179
|
+
6. **Asked** -- if selectable models exist and nothing is cached, the list is returned with `status: "asked"` so the route handler can prompt the user.
|
|
180
|
+
7. **Default** -- if only a default model exists (no selectable list), it is saved and returned.
|
|
181
|
+
8. **No models** -- fallthrough when none of the above match.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Selection flow
|
|
186
|
+
|
|
187
|
+
When discovery returns `status: "asked"`, the route handler triggers an interactive prompt:
|
|
188
|
+
|
|
189
|
+
1. The `/discover` route calls `askUser()` with the list of models.
|
|
190
|
+
2. `askUser` sends a `POST` to the internal `plugin-select/ask` endpoint on the OpenCode server.
|
|
191
|
+
3. The server forwards the selection prompt to the TUI.
|
|
192
|
+
4. When the user picks a model, the response flows back through the same path.
|
|
193
|
+
5. The selected `ref` is saved via `saveSelection()` into `GitLabModelCache`.
|
|
194
|
+
6. Subsequent `/discover` calls return `status: "cached"` without prompting again.
|
|
195
|
+
|
|
196
|
+
If the user dismisses the prompt, the response is `{ "status": "dismissed" }`.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Develop
|
|
201
|
+
|
|
202
|
+
### Build
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
npm run build
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Uses [tsup](https://tsup.egoist.dev) to produce CJS, ESM, and `.d.ts` outputs in `dist/`.
|
|
209
|
+
|
|
210
|
+
### Test
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
npm test
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Runs [Vitest](https://vitest.dev) against `test/`. Watch mode is available with `npm run test:watch`.
|
|
217
|
+
|
|
218
|
+
### Lint and type-check
|
|
219
|
+
|
|
220
|
+
```sh
|
|
221
|
+
npm run lint
|
|
222
|
+
npm run type-check
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Clean
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
npm run clean
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
[MIT](./LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
default: () => index_default,
|
|
24
|
+
plugin: () => plugin
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/select.ts
|
|
29
|
+
var import_gitlab_ai_provider = require("gitlab-ai-provider");
|
|
30
|
+
var discovery = null;
|
|
31
|
+
function getDiscovery() {
|
|
32
|
+
return discovery;
|
|
33
|
+
}
|
|
34
|
+
async function discover(opts) {
|
|
35
|
+
if (!opts.token) return { status: "no_provider" };
|
|
36
|
+
if (opts.fresh) {
|
|
37
|
+
const c = new import_gitlab_ai_provider.GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
38
|
+
c.clear();
|
|
39
|
+
}
|
|
40
|
+
const headers = () => ({ Authorization: `Bearer ${opts.token}` });
|
|
41
|
+
const detector = new import_gitlab_ai_provider.GitLabProjectDetector({
|
|
42
|
+
instanceUrl: opts.instanceUrl,
|
|
43
|
+
getHeaders: headers
|
|
44
|
+
});
|
|
45
|
+
const project = await detector.detectProject(opts.directory).catch(() => null);
|
|
46
|
+
if (!project?.namespaceId)
|
|
47
|
+
return { status: "no_provider", error: "Could not detect GitLab project" };
|
|
48
|
+
const nsid = `gid://gitlab/Group/${project.namespaceId}`;
|
|
49
|
+
const disc = new import_gitlab_ai_provider.GitLabModelDiscovery({
|
|
50
|
+
instanceUrl: opts.instanceUrl,
|
|
51
|
+
getHeaders: headers
|
|
52
|
+
});
|
|
53
|
+
const result = await disc.discover(nsid).catch(() => null);
|
|
54
|
+
if (!result) return { status: "no_models" };
|
|
55
|
+
discovery = result;
|
|
56
|
+
const cache = new import_gitlab_ai_provider.GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
57
|
+
cache.saveDiscovery(result);
|
|
58
|
+
if (result.pinnedModel) {
|
|
59
|
+
cache.saveSelection(result.pinnedModel.ref, result.pinnedModel.name);
|
|
60
|
+
return { status: "pinned", model: result.pinnedModel };
|
|
61
|
+
}
|
|
62
|
+
const cached = cache.load();
|
|
63
|
+
if (cached?.selectedModelRef) {
|
|
64
|
+
const match = result.selectableModels.find((m) => m.ref === cached.selectedModelRef);
|
|
65
|
+
if (match) return { status: "cached", model: match };
|
|
66
|
+
return {
|
|
67
|
+
status: "cached",
|
|
68
|
+
model: { name: cached.selectedModelRef, ref: cached.selectedModelRef }
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (result.selectableModels.length > 0) {
|
|
72
|
+
const models = result.selectableModels.map((m) => ({
|
|
73
|
+
name: m.name,
|
|
74
|
+
ref: m.ref,
|
|
75
|
+
isDefault: result.defaultModel?.ref === m.ref
|
|
76
|
+
}));
|
|
77
|
+
return { status: "asked", models };
|
|
78
|
+
}
|
|
79
|
+
if (result.defaultModel) {
|
|
80
|
+
cache.saveSelection(result.defaultModel.ref, result.defaultModel.name);
|
|
81
|
+
return { status: "default", model: result.defaultModel };
|
|
82
|
+
}
|
|
83
|
+
return { status: "no_models" };
|
|
84
|
+
}
|
|
85
|
+
function saveSelection(opts) {
|
|
86
|
+
const cache = new import_gitlab_ai_provider.GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
87
|
+
const model = discovery?.selectableModels.find((m) => m.ref === opts.ref);
|
|
88
|
+
cache.saveSelection(opts.ref, model?.name ?? opts.ref);
|
|
89
|
+
return { ref: opts.ref, name: model?.name ?? opts.ref };
|
|
90
|
+
}
|
|
91
|
+
function clearCache(opts) {
|
|
92
|
+
const cache = new import_gitlab_ai_provider.GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
93
|
+
cache.clear();
|
|
94
|
+
discovery = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/routes.ts
|
|
98
|
+
function auth(c) {
|
|
99
|
+
return {
|
|
100
|
+
token: c.req.header("x-plugin-auth-token") || "",
|
|
101
|
+
instanceUrl: c.req.header("x-plugin-auth-instance") || process.env.GITLAB_INSTANCE_URL || "https://gitlab.com",
|
|
102
|
+
directory: c.req.header("x-plugin-directory") || process.cwd()
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function routes(ctx) {
|
|
106
|
+
return (app) => {
|
|
107
|
+
app.post("/discover", async (c) => {
|
|
108
|
+
try {
|
|
109
|
+
const hdr = auth(c);
|
|
110
|
+
const body = await c.req.json().catch(() => ({}));
|
|
111
|
+
const cred = hdr.token ? hdr : await ctx.creds();
|
|
112
|
+
if (!cred?.token)
|
|
113
|
+
return c.json({
|
|
114
|
+
status: "no_provider",
|
|
115
|
+
error: "No auth token found"
|
|
116
|
+
});
|
|
117
|
+
const result = await discover({
|
|
118
|
+
token: cred.token,
|
|
119
|
+
instanceUrl: cred.instanceUrl,
|
|
120
|
+
directory: hdr.directory,
|
|
121
|
+
fresh: body.fresh
|
|
122
|
+
});
|
|
123
|
+
if (result.status === "asked" && result.models) {
|
|
124
|
+
const value = await ctx.askUser(result.models);
|
|
125
|
+
if (value) {
|
|
126
|
+
saveSelection({
|
|
127
|
+
ref: value,
|
|
128
|
+
directory: hdr.directory,
|
|
129
|
+
instanceUrl: cred.instanceUrl
|
|
130
|
+
});
|
|
131
|
+
const match = result.models.find((m) => m.ref === value);
|
|
132
|
+
return c.json({
|
|
133
|
+
status: "selected",
|
|
134
|
+
model: { name: match?.name ?? value, ref: value }
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return c.json({ status: "dismissed" });
|
|
138
|
+
}
|
|
139
|
+
return c.json(result);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
return c.json({ status: "error", error: e?.message ?? String(e) });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
app.post("/reply", async (c) => {
|
|
145
|
+
const body = await c.req.json();
|
|
146
|
+
const hdr = auth(c);
|
|
147
|
+
const result = saveSelection({
|
|
148
|
+
ref: body.ref,
|
|
149
|
+
directory: hdr.directory,
|
|
150
|
+
instanceUrl: hdr.instanceUrl
|
|
151
|
+
});
|
|
152
|
+
return c.json(result);
|
|
153
|
+
});
|
|
154
|
+
app.post("/clear", async (c) => {
|
|
155
|
+
const hdr = auth(c);
|
|
156
|
+
clearCache({ directory: hdr.directory, instanceUrl: hdr.instanceUrl });
|
|
157
|
+
return c.json(true);
|
|
158
|
+
});
|
|
159
|
+
app.get("/models", async (c) => {
|
|
160
|
+
const disc = getDiscovery();
|
|
161
|
+
return c.json({
|
|
162
|
+
models: disc?.selectableModels || [],
|
|
163
|
+
pinned: disc?.pinnedModel || null,
|
|
164
|
+
default: disc?.defaultModel || null,
|
|
165
|
+
switching: disc?.modelSwitchingEnabled || false
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/index.ts
|
|
172
|
+
var plugin = async (input) => {
|
|
173
|
+
const client = input.client;
|
|
174
|
+
const serverFetch = client._client.getConfig().fetch ?? fetch;
|
|
175
|
+
async function creds() {
|
|
176
|
+
const auth2 = await input.getAuth("gitlab");
|
|
177
|
+
if (!auth2) return null;
|
|
178
|
+
const token = auth2.type === "oauth" ? auth2.access : auth2.type === "api" ? auth2.key : "";
|
|
179
|
+
const instanceUrl = auth2.enterpriseUrl ?? process.env.GITLAB_INSTANCE_URL ?? "https://gitlab.com";
|
|
180
|
+
return { token, instanceUrl };
|
|
181
|
+
}
|
|
182
|
+
async function askUser(models) {
|
|
183
|
+
const res = await serverFetch(
|
|
184
|
+
new Request("http://localhost:4096/plugin-select/ask", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
title: "Select GitLab DAP model",
|
|
189
|
+
options: models.map((m) => ({
|
|
190
|
+
label: m.name,
|
|
191
|
+
value: m.ref,
|
|
192
|
+
isDefault: m.isDefault
|
|
193
|
+
}))
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
return data?.value ?? null;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
route: {
|
|
202
|
+
prefix: "gitlab",
|
|
203
|
+
handler: routes({ creds, askUser })
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
var index_default = plugin;
|
|
208
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
209
|
+
0 && (module.exports = {
|
|
210
|
+
plugin
|
|
211
|
+
});
|
|
212
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/select.ts","../src/routes.ts"],"sourcesContent":["import type { Plugin } from \"@opencode-ai/plugin\";\nimport { routes } from \"./routes\";\n\nexport const plugin: Plugin = async (input) => {\n const client = input.client as any;\n const serverFetch: typeof fetch = client._client.getConfig().fetch ?? fetch;\n\n async function creds() {\n const auth = await input.getAuth(\"gitlab\");\n if (!auth) return null;\n const token =\n auth.type === \"oauth\" ? (auth as any).access : auth.type === \"api\" ? (auth as any).key : \"\";\n const instanceUrl =\n (auth as any).enterpriseUrl ?? process.env.GITLAB_INSTANCE_URL ?? \"https://gitlab.com\";\n return { token: token as string, instanceUrl: instanceUrl as string };\n }\n\n async function askUser(models: { name: string; ref: string; isDefault?: boolean }[]) {\n const res = await serverFetch(\n new Request(\"http://localhost:4096/plugin-select/ask\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n title: \"Select GitLab DAP model\",\n options: models.map((m: any) => ({\n label: m.name,\n value: m.ref,\n isDefault: m.isDefault,\n })),\n }),\n })\n );\n const data = (await res.json()) as any;\n return (data?.value ?? null) as string | null;\n }\n\n return {\n route: {\n prefix: \"gitlab\",\n handler: routes({ creds, askUser }),\n },\n };\n};\n\nexport default plugin;\n","import {\n GitLabModelDiscovery,\n GitLabModelCache,\n GitLabProjectDetector,\n type DiscoveredModels,\n} from \"gitlab-ai-provider\";\n\nexport type DiscoverResult = {\n status: \"pinned\" | \"cached\" | \"default\" | \"asked\" | \"no_provider\" | \"no_models\" | \"error\";\n model?: { name: string; ref: string };\n models?: { name: string; ref: string; isDefault?: boolean }[];\n error?: string;\n};\n\nlet discovery: DiscoveredModels | null = null;\n\nexport function getDiscovery() {\n return discovery;\n}\n\nexport async function discover(opts: {\n token: string;\n instanceUrl: string;\n directory: string;\n fresh?: boolean;\n}): Promise<DiscoverResult> {\n if (!opts.token) return { status: \"no_provider\" };\n\n if (opts.fresh) {\n const c = new GitLabModelCache(opts.directory, opts.instanceUrl);\n c.clear();\n }\n\n const headers = () => ({ Authorization: `Bearer ${opts.token}` });\n\n const detector = new GitLabProjectDetector({\n instanceUrl: opts.instanceUrl,\n getHeaders: headers,\n });\n const project = await detector.detectProject(opts.directory).catch(() => null);\n if (!project?.namespaceId)\n return { status: \"no_provider\", error: \"Could not detect GitLab project\" };\n\n const nsid = `gid://gitlab/Group/${project.namespaceId}`;\n const disc = new GitLabModelDiscovery({\n instanceUrl: opts.instanceUrl,\n getHeaders: headers,\n });\n const result = await disc.discover(nsid).catch(() => null);\n if (!result) return { status: \"no_models\" };\n\n discovery = result;\n const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);\n cache.saveDiscovery(result);\n\n if (result.pinnedModel) {\n cache.saveSelection(result.pinnedModel.ref, result.pinnedModel.name);\n return { status: \"pinned\", model: result.pinnedModel };\n }\n\n const cached = cache.load();\n if (cached?.selectedModelRef) {\n const match = result.selectableModels.find((m) => m.ref === cached.selectedModelRef);\n if (match) return { status: \"cached\", model: match };\n return {\n status: \"cached\",\n model: { name: cached.selectedModelRef, ref: cached.selectedModelRef },\n };\n }\n\n if (result.selectableModels.length > 0) {\n const models = result.selectableModels.map((m) => ({\n name: m.name,\n ref: m.ref,\n isDefault: result.defaultModel?.ref === m.ref,\n }));\n return { status: \"asked\", models };\n }\n\n if (result.defaultModel) {\n cache.saveSelection(result.defaultModel.ref, result.defaultModel.name);\n return { status: \"default\", model: result.defaultModel };\n }\n\n return { status: \"no_models\" };\n}\n\nexport function saveSelection(opts: { ref: string; directory: string; instanceUrl: string }) {\n const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);\n const model = discovery?.selectableModels.find((m) => m.ref === opts.ref);\n cache.saveSelection(opts.ref, model?.name ?? opts.ref);\n return { ref: opts.ref, name: model?.name ?? opts.ref };\n}\n\nexport function clearCache(opts: { directory: string; instanceUrl: string }) {\n const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);\n cache.clear();\n discovery = null;\n}\n","import { discover, saveSelection, clearCache, getDiscovery } from \"./select\";\n\ntype Context = {\n creds: () => Promise<{ token: string; instanceUrl: string } | null>;\n askUser: (models: { name: string; ref: string; isDefault?: boolean }[]) => Promise<string | null>;\n};\n\nfunction auth(c: any) {\n return {\n token: c.req.header(\"x-plugin-auth-token\") || \"\",\n instanceUrl:\n c.req.header(\"x-plugin-auth-instance\") ||\n process.env.GITLAB_INSTANCE_URL ||\n \"https://gitlab.com\",\n directory: c.req.header(\"x-plugin-directory\") || process.cwd(),\n };\n}\n\nexport function routes(ctx: Context) {\n return (app: any) => {\n app.post(\"/discover\", async (c: any) => {\n try {\n const hdr = auth(c);\n const body = await c.req.json().catch(() => ({}));\n\n const cred = hdr.token ? hdr : await ctx.creds();\n if (!cred?.token)\n return c.json({\n status: \"no_provider\",\n error: \"No auth token found\",\n });\n\n const result = await discover({\n token: cred.token,\n instanceUrl: cred.instanceUrl,\n directory: hdr.directory,\n fresh: body.fresh,\n });\n\n if (result.status === \"asked\" && result.models) {\n const value = await ctx.askUser(result.models);\n if (value) {\n saveSelection({\n ref: value,\n directory: hdr.directory,\n instanceUrl: cred.instanceUrl,\n });\n const match = result.models.find((m) => m.ref === value);\n return c.json({\n status: \"selected\",\n model: { name: match?.name ?? value, ref: value },\n });\n }\n return c.json({ status: \"dismissed\" });\n }\n\n return c.json(result);\n } catch (e: any) {\n return c.json({ status: \"error\", error: e?.message ?? String(e) });\n }\n });\n\n app.post(\"/reply\", async (c: any) => {\n const body = await c.req.json();\n const hdr = auth(c);\n const result = saveSelection({\n ref: body.ref,\n directory: hdr.directory,\n instanceUrl: hdr.instanceUrl,\n });\n return c.json(result);\n });\n\n app.post(\"/clear\", async (c: any) => {\n const hdr = auth(c);\n clearCache({ directory: hdr.directory, instanceUrl: hdr.instanceUrl });\n return c.json(true);\n });\n\n app.get(\"/models\", async (c: any) => {\n const disc = getDiscovery();\n return c.json({\n models: disc?.selectableModels || [],\n pinned: disc?.pinnedModel || null,\n default: disc?.defaultModel || null,\n switching: disc?.modelSwitchingEnabled || false,\n });\n });\n };\n}\n"],"mappings":"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","names":["auth"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/select.ts
|
|
2
|
+
import {
|
|
3
|
+
GitLabModelDiscovery,
|
|
4
|
+
GitLabModelCache,
|
|
5
|
+
GitLabProjectDetector
|
|
6
|
+
} from "gitlab-ai-provider";
|
|
7
|
+
var discovery = null;
|
|
8
|
+
function getDiscovery() {
|
|
9
|
+
return discovery;
|
|
10
|
+
}
|
|
11
|
+
async function discover(opts) {
|
|
12
|
+
if (!opts.token) return { status: "no_provider" };
|
|
13
|
+
if (opts.fresh) {
|
|
14
|
+
const c = new GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
15
|
+
c.clear();
|
|
16
|
+
}
|
|
17
|
+
const headers = () => ({ Authorization: `Bearer ${opts.token}` });
|
|
18
|
+
const detector = new GitLabProjectDetector({
|
|
19
|
+
instanceUrl: opts.instanceUrl,
|
|
20
|
+
getHeaders: headers
|
|
21
|
+
});
|
|
22
|
+
const project = await detector.detectProject(opts.directory).catch(() => null);
|
|
23
|
+
if (!project?.namespaceId)
|
|
24
|
+
return { status: "no_provider", error: "Could not detect GitLab project" };
|
|
25
|
+
const nsid = `gid://gitlab/Group/${project.namespaceId}`;
|
|
26
|
+
const disc = new GitLabModelDiscovery({
|
|
27
|
+
instanceUrl: opts.instanceUrl,
|
|
28
|
+
getHeaders: headers
|
|
29
|
+
});
|
|
30
|
+
const result = await disc.discover(nsid).catch(() => null);
|
|
31
|
+
if (!result) return { status: "no_models" };
|
|
32
|
+
discovery = result;
|
|
33
|
+
const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
34
|
+
cache.saveDiscovery(result);
|
|
35
|
+
if (result.pinnedModel) {
|
|
36
|
+
cache.saveSelection(result.pinnedModel.ref, result.pinnedModel.name);
|
|
37
|
+
return { status: "pinned", model: result.pinnedModel };
|
|
38
|
+
}
|
|
39
|
+
const cached = cache.load();
|
|
40
|
+
if (cached?.selectedModelRef) {
|
|
41
|
+
const match = result.selectableModels.find((m) => m.ref === cached.selectedModelRef);
|
|
42
|
+
if (match) return { status: "cached", model: match };
|
|
43
|
+
return {
|
|
44
|
+
status: "cached",
|
|
45
|
+
model: { name: cached.selectedModelRef, ref: cached.selectedModelRef }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (result.selectableModels.length > 0) {
|
|
49
|
+
const models = result.selectableModels.map((m) => ({
|
|
50
|
+
name: m.name,
|
|
51
|
+
ref: m.ref,
|
|
52
|
+
isDefault: result.defaultModel?.ref === m.ref
|
|
53
|
+
}));
|
|
54
|
+
return { status: "asked", models };
|
|
55
|
+
}
|
|
56
|
+
if (result.defaultModel) {
|
|
57
|
+
cache.saveSelection(result.defaultModel.ref, result.defaultModel.name);
|
|
58
|
+
return { status: "default", model: result.defaultModel };
|
|
59
|
+
}
|
|
60
|
+
return { status: "no_models" };
|
|
61
|
+
}
|
|
62
|
+
function saveSelection(opts) {
|
|
63
|
+
const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
64
|
+
const model = discovery?.selectableModels.find((m) => m.ref === opts.ref);
|
|
65
|
+
cache.saveSelection(opts.ref, model?.name ?? opts.ref);
|
|
66
|
+
return { ref: opts.ref, name: model?.name ?? opts.ref };
|
|
67
|
+
}
|
|
68
|
+
function clearCache(opts) {
|
|
69
|
+
const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);
|
|
70
|
+
cache.clear();
|
|
71
|
+
discovery = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/routes.ts
|
|
75
|
+
function auth(c) {
|
|
76
|
+
return {
|
|
77
|
+
token: c.req.header("x-plugin-auth-token") || "",
|
|
78
|
+
instanceUrl: c.req.header("x-plugin-auth-instance") || process.env.GITLAB_INSTANCE_URL || "https://gitlab.com",
|
|
79
|
+
directory: c.req.header("x-plugin-directory") || process.cwd()
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function routes(ctx) {
|
|
83
|
+
return (app) => {
|
|
84
|
+
app.post("/discover", async (c) => {
|
|
85
|
+
try {
|
|
86
|
+
const hdr = auth(c);
|
|
87
|
+
const body = await c.req.json().catch(() => ({}));
|
|
88
|
+
const cred = hdr.token ? hdr : await ctx.creds();
|
|
89
|
+
if (!cred?.token)
|
|
90
|
+
return c.json({
|
|
91
|
+
status: "no_provider",
|
|
92
|
+
error: "No auth token found"
|
|
93
|
+
});
|
|
94
|
+
const result = await discover({
|
|
95
|
+
token: cred.token,
|
|
96
|
+
instanceUrl: cred.instanceUrl,
|
|
97
|
+
directory: hdr.directory,
|
|
98
|
+
fresh: body.fresh
|
|
99
|
+
});
|
|
100
|
+
if (result.status === "asked" && result.models) {
|
|
101
|
+
const value = await ctx.askUser(result.models);
|
|
102
|
+
if (value) {
|
|
103
|
+
saveSelection({
|
|
104
|
+
ref: value,
|
|
105
|
+
directory: hdr.directory,
|
|
106
|
+
instanceUrl: cred.instanceUrl
|
|
107
|
+
});
|
|
108
|
+
const match = result.models.find((m) => m.ref === value);
|
|
109
|
+
return c.json({
|
|
110
|
+
status: "selected",
|
|
111
|
+
model: { name: match?.name ?? value, ref: value }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return c.json({ status: "dismissed" });
|
|
115
|
+
}
|
|
116
|
+
return c.json(result);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return c.json({ status: "error", error: e?.message ?? String(e) });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
app.post("/reply", async (c) => {
|
|
122
|
+
const body = await c.req.json();
|
|
123
|
+
const hdr = auth(c);
|
|
124
|
+
const result = saveSelection({
|
|
125
|
+
ref: body.ref,
|
|
126
|
+
directory: hdr.directory,
|
|
127
|
+
instanceUrl: hdr.instanceUrl
|
|
128
|
+
});
|
|
129
|
+
return c.json(result);
|
|
130
|
+
});
|
|
131
|
+
app.post("/clear", async (c) => {
|
|
132
|
+
const hdr = auth(c);
|
|
133
|
+
clearCache({ directory: hdr.directory, instanceUrl: hdr.instanceUrl });
|
|
134
|
+
return c.json(true);
|
|
135
|
+
});
|
|
136
|
+
app.get("/models", async (c) => {
|
|
137
|
+
const disc = getDiscovery();
|
|
138
|
+
return c.json({
|
|
139
|
+
models: disc?.selectableModels || [],
|
|
140
|
+
pinned: disc?.pinnedModel || null,
|
|
141
|
+
default: disc?.defaultModel || null,
|
|
142
|
+
switching: disc?.modelSwitchingEnabled || false
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/index.ts
|
|
149
|
+
var plugin = async (input) => {
|
|
150
|
+
const client = input.client;
|
|
151
|
+
const serverFetch = client._client.getConfig().fetch ?? fetch;
|
|
152
|
+
async function creds() {
|
|
153
|
+
const auth2 = await input.getAuth("gitlab");
|
|
154
|
+
if (!auth2) return null;
|
|
155
|
+
const token = auth2.type === "oauth" ? auth2.access : auth2.type === "api" ? auth2.key : "";
|
|
156
|
+
const instanceUrl = auth2.enterpriseUrl ?? process.env.GITLAB_INSTANCE_URL ?? "https://gitlab.com";
|
|
157
|
+
return { token, instanceUrl };
|
|
158
|
+
}
|
|
159
|
+
async function askUser(models) {
|
|
160
|
+
const res = await serverFetch(
|
|
161
|
+
new Request("http://localhost:4096/plugin-select/ask", {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: { "Content-Type": "application/json" },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
title: "Select GitLab DAP model",
|
|
166
|
+
options: models.map((m) => ({
|
|
167
|
+
label: m.name,
|
|
168
|
+
value: m.ref,
|
|
169
|
+
isDefault: m.isDefault
|
|
170
|
+
}))
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
const data = await res.json();
|
|
175
|
+
return data?.value ?? null;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
route: {
|
|
179
|
+
prefix: "gitlab",
|
|
180
|
+
handler: routes({ creds, askUser })
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
var index_default = plugin;
|
|
185
|
+
export {
|
|
186
|
+
index_default as default,
|
|
187
|
+
plugin
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/select.ts","../src/routes.ts","../src/index.ts"],"sourcesContent":["import {\n GitLabModelDiscovery,\n GitLabModelCache,\n GitLabProjectDetector,\n type DiscoveredModels,\n} from \"gitlab-ai-provider\";\n\nexport type DiscoverResult = {\n status: \"pinned\" | \"cached\" | \"default\" | \"asked\" | \"no_provider\" | \"no_models\" | \"error\";\n model?: { name: string; ref: string };\n models?: { name: string; ref: string; isDefault?: boolean }[];\n error?: string;\n};\n\nlet discovery: DiscoveredModels | null = null;\n\nexport function getDiscovery() {\n return discovery;\n}\n\nexport async function discover(opts: {\n token: string;\n instanceUrl: string;\n directory: string;\n fresh?: boolean;\n}): Promise<DiscoverResult> {\n if (!opts.token) return { status: \"no_provider\" };\n\n if (opts.fresh) {\n const c = new GitLabModelCache(opts.directory, opts.instanceUrl);\n c.clear();\n }\n\n const headers = () => ({ Authorization: `Bearer ${opts.token}` });\n\n const detector = new GitLabProjectDetector({\n instanceUrl: opts.instanceUrl,\n getHeaders: headers,\n });\n const project = await detector.detectProject(opts.directory).catch(() => null);\n if (!project?.namespaceId)\n return { status: \"no_provider\", error: \"Could not detect GitLab project\" };\n\n const nsid = `gid://gitlab/Group/${project.namespaceId}`;\n const disc = new GitLabModelDiscovery({\n instanceUrl: opts.instanceUrl,\n getHeaders: headers,\n });\n const result = await disc.discover(nsid).catch(() => null);\n if (!result) return { status: \"no_models\" };\n\n discovery = result;\n const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);\n cache.saveDiscovery(result);\n\n if (result.pinnedModel) {\n cache.saveSelection(result.pinnedModel.ref, result.pinnedModel.name);\n return { status: \"pinned\", model: result.pinnedModel };\n }\n\n const cached = cache.load();\n if (cached?.selectedModelRef) {\n const match = result.selectableModels.find((m) => m.ref === cached.selectedModelRef);\n if (match) return { status: \"cached\", model: match };\n return {\n status: \"cached\",\n model: { name: cached.selectedModelRef, ref: cached.selectedModelRef },\n };\n }\n\n if (result.selectableModels.length > 0) {\n const models = result.selectableModels.map((m) => ({\n name: m.name,\n ref: m.ref,\n isDefault: result.defaultModel?.ref === m.ref,\n }));\n return { status: \"asked\", models };\n }\n\n if (result.defaultModel) {\n cache.saveSelection(result.defaultModel.ref, result.defaultModel.name);\n return { status: \"default\", model: result.defaultModel };\n }\n\n return { status: \"no_models\" };\n}\n\nexport function saveSelection(opts: { ref: string; directory: string; instanceUrl: string }) {\n const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);\n const model = discovery?.selectableModels.find((m) => m.ref === opts.ref);\n cache.saveSelection(opts.ref, model?.name ?? opts.ref);\n return { ref: opts.ref, name: model?.name ?? opts.ref };\n}\n\nexport function clearCache(opts: { directory: string; instanceUrl: string }) {\n const cache = new GitLabModelCache(opts.directory, opts.instanceUrl);\n cache.clear();\n discovery = null;\n}\n","import { discover, saveSelection, clearCache, getDiscovery } from \"./select\";\n\ntype Context = {\n creds: () => Promise<{ token: string; instanceUrl: string } | null>;\n askUser: (models: { name: string; ref: string; isDefault?: boolean }[]) => Promise<string | null>;\n};\n\nfunction auth(c: any) {\n return {\n token: c.req.header(\"x-plugin-auth-token\") || \"\",\n instanceUrl:\n c.req.header(\"x-plugin-auth-instance\") ||\n process.env.GITLAB_INSTANCE_URL ||\n \"https://gitlab.com\",\n directory: c.req.header(\"x-plugin-directory\") || process.cwd(),\n };\n}\n\nexport function routes(ctx: Context) {\n return (app: any) => {\n app.post(\"/discover\", async (c: any) => {\n try {\n const hdr = auth(c);\n const body = await c.req.json().catch(() => ({}));\n\n const cred = hdr.token ? hdr : await ctx.creds();\n if (!cred?.token)\n return c.json({\n status: \"no_provider\",\n error: \"No auth token found\",\n });\n\n const result = await discover({\n token: cred.token,\n instanceUrl: cred.instanceUrl,\n directory: hdr.directory,\n fresh: body.fresh,\n });\n\n if (result.status === \"asked\" && result.models) {\n const value = await ctx.askUser(result.models);\n if (value) {\n saveSelection({\n ref: value,\n directory: hdr.directory,\n instanceUrl: cred.instanceUrl,\n });\n const match = result.models.find((m) => m.ref === value);\n return c.json({\n status: \"selected\",\n model: { name: match?.name ?? value, ref: value },\n });\n }\n return c.json({ status: \"dismissed\" });\n }\n\n return c.json(result);\n } catch (e: any) {\n return c.json({ status: \"error\", error: e?.message ?? String(e) });\n }\n });\n\n app.post(\"/reply\", async (c: any) => {\n const body = await c.req.json();\n const hdr = auth(c);\n const result = saveSelection({\n ref: body.ref,\n directory: hdr.directory,\n instanceUrl: hdr.instanceUrl,\n });\n return c.json(result);\n });\n\n app.post(\"/clear\", async (c: any) => {\n const hdr = auth(c);\n clearCache({ directory: hdr.directory, instanceUrl: hdr.instanceUrl });\n return c.json(true);\n });\n\n app.get(\"/models\", async (c: any) => {\n const disc = getDiscovery();\n return c.json({\n models: disc?.selectableModels || [],\n pinned: disc?.pinnedModel || null,\n default: disc?.defaultModel || null,\n switching: disc?.modelSwitchingEnabled || false,\n });\n });\n };\n}\n","import type { Plugin } from \"@opencode-ai/plugin\";\nimport { routes } from \"./routes\";\n\nexport const plugin: Plugin = async (input) => {\n const client = input.client as any;\n const serverFetch: typeof fetch = client._client.getConfig().fetch ?? fetch;\n\n async function creds() {\n const auth = await input.getAuth(\"gitlab\");\n if (!auth) return null;\n const token =\n auth.type === \"oauth\" ? (auth as any).access : auth.type === \"api\" ? (auth as any).key : \"\";\n const instanceUrl =\n (auth as any).enterpriseUrl ?? process.env.GITLAB_INSTANCE_URL ?? \"https://gitlab.com\";\n return { token: token as string, instanceUrl: instanceUrl as string };\n }\n\n async function askUser(models: { name: string; ref: string; isDefault?: boolean }[]) {\n const res = await serverFetch(\n new Request(\"http://localhost:4096/plugin-select/ask\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n title: \"Select GitLab DAP model\",\n options: models.map((m: any) => ({\n label: m.name,\n value: m.ref,\n isDefault: m.isDefault,\n })),\n }),\n })\n );\n const data = (await res.json()) as any;\n return (data?.value ?? null) as string | null;\n }\n\n return {\n route: {\n prefix: \"gitlab\",\n handler: routes({ creds, askUser }),\n },\n };\n};\n\nexport default plugin;\n"],"mappings":"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","names":["auth"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-gitlab-dap",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode plugin for GitLab Duo Agent Platform (DAP) workflow model discovery and selection",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.cjs",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"bun": "./src/index.ts",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://gitlab.com/vglafirov/opencode-gitlab-dap.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://gitlab.com/vglafirov/opencode-gitlab-dap/-/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://gitlab.com/vglafirov/opencode-gitlab-dap#readme",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
37
|
+
"rebuild": "npm run clean && npm run build",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"test:coverage": "vitest run --coverage",
|
|
41
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
42
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
43
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
44
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
45
|
+
"type-check": "tsc --noEmit",
|
|
46
|
+
"prepublishOnly": "npm run build",
|
|
47
|
+
"semantic-release": "semantic-release"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"gitlab-ai-provider": "^5.0.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@opencode-ai/plugin": ">=1.2.24"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@commitlint/cli": "^18.4.3",
|
|
57
|
+
"@commitlint/config-conventional": "^18.4.3",
|
|
58
|
+
"@opencode-ai/plugin": ">=1.2.24",
|
|
59
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
60
|
+
"@semantic-release/git": "^10.0.1",
|
|
61
|
+
"@semantic-release/gitlab": "^13.0.3",
|
|
62
|
+
"@semantic-release/npm": "^11.0.2",
|
|
63
|
+
"@types/node": "^20.17.24",
|
|
64
|
+
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
|
65
|
+
"@typescript-eslint/parser": "^6.15.0",
|
|
66
|
+
"eslint": "^8.56.0",
|
|
67
|
+
"eslint-config-prettier": "^9.1.0",
|
|
68
|
+
"prettier": "^3.1.1",
|
|
69
|
+
"semantic-release": "^22.0.12",
|
|
70
|
+
"tsup": "^8.0.0",
|
|
71
|
+
"typescript": "^5.8.3",
|
|
72
|
+
"vitest": "^4.0.16"
|
|
73
|
+
},
|
|
74
|
+
"author": {
|
|
75
|
+
"name": "Vladimir Glafirov",
|
|
76
|
+
"email": "vglafirov@gitlab.com",
|
|
77
|
+
"url": "https://gitlab.com/vglafirov"
|
|
78
|
+
},
|
|
79
|
+
"keywords": [
|
|
80
|
+
"opencode",
|
|
81
|
+
"opencode-plugin",
|
|
82
|
+
"gitlab",
|
|
83
|
+
"gitlab-duo",
|
|
84
|
+
"dap",
|
|
85
|
+
"duo-agent-platform",
|
|
86
|
+
"workflow",
|
|
87
|
+
"model-discovery",
|
|
88
|
+
"plugin",
|
|
89
|
+
"ai",
|
|
90
|
+
"typescript"
|
|
91
|
+
],
|
|
92
|
+
"engines": {
|
|
93
|
+
"node": ">=18.0.0"
|
|
94
|
+
}
|
|
95
|
+
}
|