opencode-gitlab-duo-agentic 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +30 -24
  2. package/dist/index.js +46 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # opencode-gitlab-duo-agentic
2
2
 
3
- OpenCode plugin and provider for GitLab Duo Agentic workflows, published as a single npm package.
3
+ OpenCode plugin for GitLab Duo Agentic. It registers the provider, discovers models from the GitLab API, and exposes file-reading tools.
4
4
 
5
- ## Install
5
+ ## Setup
6
6
 
7
- Add the plugin to your OpenCode config:
7
+ 1. Export your GitLab token:
8
+
9
+ ```bash
10
+ export GITLAB_TOKEN=glpat-...
11
+ ```
12
+
13
+ 2. Add the plugin to `opencode.json`:
8
14
 
9
15
  ```json
10
16
  {
@@ -13,40 +19,40 @@ Add the plugin to your OpenCode config:
13
19
  }
14
20
  ```
15
21
 
16
- The plugin registers the provider `gitlab-duo-agentic` automatically and resolves it from the same module entrypoint as the plugin.
22
+ 3. Run `opencode`. The provider, models, and tools are registered automatically.
17
23
 
18
- ## Required environment variables
24
+ `GITLAB_INSTANCE_URL` defaults to `https://gitlab.com`. Set it only for self-managed GitLab.
19
25
 
20
- ```bash
21
- export GITLAB_TOKEN=glpat-...
22
- export GITLAB_INSTANCE_URL=https://gitlab.com
23
- ```
24
-
25
- `GITLAB_INSTANCE_URL` is optional and defaults to `https://gitlab.com`.
26
+ ## Provider options
26
27
 
27
- ## Optional model configuration
28
+ Override defaults in `opencode.json` under `provider.gitlab-duo-agentic.options`.
28
29
 
29
- The plugin discovers models in this order:
30
+ | Option | Type | Default | Description |
31
+ |--------|------|---------|-------------|
32
+ | `instanceUrl` | string | `GITLAB_INSTANCE_URL` or `https://gitlab.com` | GitLab instance URL |
33
+ | `apiKey` | string | `GITLAB_TOKEN` | Personal access token |
34
+ | `sendSystemContext` | boolean | `true` | Send system context to Duo |
35
+ | `enableMcp` | boolean | `true` | Enable MCP tools |
36
+ | `systemRules` | string | `""` | Inline system rules |
37
+ | `systemRulesPath` | string | `""` | Path to a system rules file |
30
38
 
31
- 1. Live GitLab API discovery with cache (TTL default: `86400` seconds)
32
- 2. `models.json` file resolution:
33
- - `options.modelsPath` in provider config
34
- - `GITLAB_DUO_MODELS_PATH`
35
- - `models.json` found by walking upward from `process.cwd()`
36
- 3. Fallback default model `duo-agentic`
39
+ ## Model discovery
37
40
 
38
- Cache settings:
41
+ Models are discovered in this order:
39
42
 
40
- - Location: `~/.cache/opencode/gitlab-duo-models-*.json`
41
- - TTL override: `GITLAB_DUO_MODELS_CACHE_TTL` (seconds)
43
+ 1. Local cache (TTL: 24h)
44
+ 2. Live fetch from GitLab GraphQL API
45
+ 3. Stale cache (if live fetch fails)
46
+ 4. `models.json` on disk
47
+ 5. Default `duo-agentic` model
42
48
 
43
- You can still generate a local `models.json` manually using `src/scripts/fetch_models.ts`.
49
+ Cache is stored in `~/.cache/opencode/` (or `XDG_CACHE_HOME`). Override TTL with `GITLAB_DUO_MODELS_CACHE_TTL` (seconds).
44
50
 
45
51
  ## Development
46
52
 
47
53
  ```bash
48
54
  npm install
49
- npm run typecheck
50
55
  npm run build
56
+ npm run typecheck
51
57
  npm run pack:check
52
58
  ```
package/dist/index.js CHANGED
@@ -1179,6 +1179,26 @@ var TokenUsageEstimator = class {
1179
1179
  }
1180
1180
  };
1181
1181
 
1182
+ // src/provider/core/debug_log.ts
1183
+ import { appendFileSync, writeFileSync } from "fs";
1184
+ var LOG_PATH = "/tmp/duo-debug.log";
1185
+ function duoLog(...args) {
1186
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
1187
+ const line = ts + " " + args.map(
1188
+ (a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)
1189
+ ).join(" ");
1190
+ try {
1191
+ appendFileSync(LOG_PATH, line + "\n");
1192
+ } catch {
1193
+ }
1194
+ }
1195
+ function duoLogReset() {
1196
+ try {
1197
+ writeFileSync(LOG_PATH, "");
1198
+ } catch {
1199
+ }
1200
+ }
1201
+
1182
1202
  // src/provider/application/model.ts
1183
1203
  var GitLabDuoAgenticLanguageModel = class {
1184
1204
  specificationVersion = "v2";
@@ -1225,6 +1245,8 @@ var GitLabDuoAgenticLanguageModel = class {
1225
1245
  const workflowType = "chat";
1226
1246
  const promptText = extractLastUserText(options.prompt);
1227
1247
  const toolResults = extractToolResults(options.prompt);
1248
+ duoLogReset();
1249
+ duoLog("doStream", "hasStarted=" + this.#runtime.hasStarted, "prompt=" + (promptText?.slice(0, 60) ?? "null"), "toolResults=" + toolResults.length);
1228
1250
  this.#runtime.resetMapperState();
1229
1251
  if (!this.#runtime.hasStarted) {
1230
1252
  this.#sentToolCallIds.clear();
@@ -1242,6 +1264,7 @@ var GitLabDuoAgenticLanguageModel = class {
1242
1264
  const mcpTools = this.#options.enableMcp === false ? [] : buildMcpTools(options);
1243
1265
  const toolContext = buildToolContext(mcpTools);
1244
1266
  const isNewUserMessage = promptText != null && promptText !== this.#lastSentPrompt;
1267
+ duoLog("gate", "isNewUserMessage=" + isNewUserMessage, "freshTools=" + freshToolResults.length, "lastSent=" + (this.#lastSentPrompt?.slice(0, 40) ?? "null"));
1245
1268
  let sentToolResults = false;
1246
1269
  if (freshToolResults.length > 0) {
1247
1270
  for (const result of freshToolResults) {
@@ -1349,6 +1372,7 @@ var GitLabDuoAgenticLanguageModel = class {
1349
1372
  }
1350
1373
  });
1351
1374
  }
1375
+ duoLog("sendStartRequest", "hasStarted=" + this.#runtime.hasStarted);
1352
1376
  this.#runtime.sendStartRequest(
1353
1377
  promptText,
1354
1378
  workflowType,
@@ -1361,6 +1385,8 @@ var GitLabDuoAgenticLanguageModel = class {
1361
1385
  for (const ctx of extraContext) {
1362
1386
  if (ctx.content) this.#usageEstimator.addInputChars(ctx.content);
1363
1387
  }
1388
+ } else {
1389
+ duoLog("SKIP startRequest", "sentToolResults=" + sentToolResults, "isNewUserMessage=" + isNewUserMessage);
1364
1390
  }
1365
1391
  const iterator = this.#mapEventsToStream(this.#runtime.getEventStream());
1366
1392
  const stream = asyncIteratorToReadableStream(iterator);
@@ -1374,9 +1400,12 @@ var GitLabDuoAgenticLanguageModel = class {
1374
1400
  async *#mapEventsToStream(events) {
1375
1401
  const state = { textStarted: false };
1376
1402
  const estimator = this.#usageEstimator;
1403
+ let eventCount = 0;
1377
1404
  yield { type: "stream-start", warnings: [] };
1378
1405
  try {
1379
1406
  for await (const event of events) {
1407
+ eventCount++;
1408
+ duoLog("evt", event.type, eventCount);
1380
1409
  if (event.type === "TEXT_CHUNK") {
1381
1410
  if (event.content.length > 0) {
1382
1411
  estimator.addOutputChars(event.content);
@@ -1422,9 +1451,11 @@ var GitLabDuoAgenticLanguageModel = class {
1422
1451
  }
1423
1452
  }
1424
1453
  } catch (streamErr) {
1454
+ duoLog("streamErr", streamErr instanceof Error ? streamErr.message : String(streamErr));
1425
1455
  yield { type: "error", error: streamErr instanceof Error ? streamErr : new Error(String(streamErr)) };
1426
1456
  return;
1427
1457
  }
1458
+ duoLog("finish", "events=" + eventCount);
1428
1459
  yield { type: "finish", finishReason: "stop", usage: this.#currentUsage };
1429
1460
  }
1430
1461
  // ---------------------------------------------------------------------------
@@ -1871,7 +1902,9 @@ var GitLabAgenticRuntime = class {
1871
1902
  // Connection lifecycle
1872
1903
  // ---------------------------------------------------------------------------
1873
1904
  async ensureConnected(goal, workflowType) {
1905
+ duoLog("ensureConnected", "stream=" + !!this.#stream, "wfId=" + !!this.#workflowId, "queue=" + !!this.#queue);
1874
1906
  if (this.#stream && this.#workflowId && this.#queue) {
1907
+ duoLog("ensureConnected", "skip (connected)");
1875
1908
  return;
1876
1909
  }
1877
1910
  if (!this.#containerParams) {
@@ -1879,6 +1912,9 @@ var GitLabAgenticRuntime = class {
1879
1912
  }
1880
1913
  if (!this.#workflowId) {
1881
1914
  this.#workflowId = await this.#createWorkflow(goal, workflowType);
1915
+ duoLog("workflow created", this.#workflowId);
1916
+ } else {
1917
+ duoLog("workflow reuse", this.#workflowId);
1882
1918
  }
1883
1919
  const token = await this.#dependencies.workflowService.getWorkflowToken(
1884
1920
  this.#options.instanceUrl,
@@ -1892,9 +1928,11 @@ var GitLabAgenticRuntime = class {
1892
1928
  this.#queue = new AsyncQueue();
1893
1929
  try {
1894
1930
  await this.#connectWebSocket();
1931
+ duoLog("ws connected", "attempt=" + attempt);
1895
1932
  return;
1896
1933
  } catch (err2) {
1897
1934
  const msg = err2 instanceof Error ? err2.message : String(err2);
1935
+ duoLog("ws error", msg);
1898
1936
  if ((msg.includes("1013") || msg.includes("lock")) && attempt < MAX_LOCK_RETRIES) {
1899
1937
  this.#resetStreamState();
1900
1938
  await this.#dependencies.clock.sleep(LOCK_RETRY_DELAY_MS);
@@ -1939,7 +1977,8 @@ var GitLabAgenticRuntime = class {
1939
1977
  preapproved_tools: preapprovedTools
1940
1978
  }
1941
1979
  };
1942
- this.#stream.write(startRequest);
1980
+ const ok2 = this.#stream.write(startRequest);
1981
+ duoLog("startReq write=" + ok2, "wf=" + this.#workflowId);
1943
1982
  this.#startRequestSent = true;
1944
1983
  }
1945
1984
  sendToolResponse(requestId, response, responseType) {
@@ -2028,12 +2067,14 @@ var GitLabAgenticRuntime = class {
2028
2067
  #bindStream(stream, queue) {
2029
2068
  const now = () => this.#dependencies.clock.now();
2030
2069
  const closeWithError = (message) => {
2070
+ duoLog("streamErr", message);
2031
2071
  queue.push({ type: "ERROR", message, timestamp: now() });
2032
2072
  queue.close();
2033
2073
  this.#resetStreamState();
2034
2074
  };
2035
2075
  const handleAction = async (action) => {
2036
2076
  if (action.newCheckpoint) {
2077
+ duoLog("checkpoint", action.newCheckpoint.status);
2037
2078
  const duoEvent = {
2038
2079
  checkpoint: action.newCheckpoint.checkpoint,
2039
2080
  errors: action.newCheckpoint.errors || [],
@@ -2041,6 +2082,7 @@ var GitLabAgenticRuntime = class {
2041
2082
  workflowStatus: action.newCheckpoint.status
2042
2083
  };
2043
2084
  const events = await this.#mapper.mapWorkflowEvent(duoEvent);
2085
+ duoLog("mapped", events.length, events.map((e) => e.type).join(","));
2044
2086
  for (const event of events) {
2045
2087
  queue.push(event);
2046
2088
  }
@@ -2048,6 +2090,7 @@ var GitLabAgenticRuntime = class {
2048
2090
  }
2049
2091
  const toolRequest = mapWorkflowActionToToolRequest(action);
2050
2092
  if (toolRequest) {
2093
+ duoLog("toolReq", toolRequest.toolName);
2051
2094
  queue.push({
2052
2095
  type: "TOOL_REQUEST",
2053
2096
  ...toolRequest,
@@ -2066,6 +2109,7 @@ var GitLabAgenticRuntime = class {
2066
2109
  closeWithError(err2.message);
2067
2110
  });
2068
2111
  stream.on("end", () => {
2112
+ duoLog("stream end");
2069
2113
  queue.close();
2070
2114
  this.#resetStreamState();
2071
2115
  });
@@ -2087,6 +2131,7 @@ var GitLabAgenticRuntime = class {
2087
2131
  this.#bindStream(stream, this.#queue);
2088
2132
  }
2089
2133
  #resetStreamState() {
2134
+ duoLog("reset", "wf=" + this.#workflowId);
2090
2135
  this.#stream = void 0;
2091
2136
  this.#queue = void 0;
2092
2137
  this.#startRequestSent = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitlab-duo-agentic",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "OpenCode plugin and provider for GitLab Duo Agentic workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",