opencode-gitlab-duo-agentic 0.1.2 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +30 -24
  2. package/dist/index.js +55 -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
@@ -1225,6 +1225,14 @@ var GitLabDuoAgenticLanguageModel = class {
1225
1225
  const workflowType = "chat";
1226
1226
  const promptText = extractLastUserText(options.prompt);
1227
1227
  const toolResults = extractToolResults(options.prompt);
1228
+ console.warn("[duo-debug] doStream called", {
1229
+ promptText: promptText?.slice(0, 80),
1230
+ toolResultCount: toolResults.length,
1231
+ hasStarted: this.#runtime.hasStarted,
1232
+ lastSentPrompt: this.#lastSentPrompt?.slice(0, 80) ?? null,
1233
+ pendingToolRequests: this.#pendingToolRequests.size,
1234
+ sentToolCallIds: this.#sentToolCallIds.size
1235
+ });
1228
1236
  this.#runtime.resetMapperState();
1229
1237
  if (!this.#runtime.hasStarted) {
1230
1238
  this.#sentToolCallIds.clear();
@@ -1234,14 +1242,18 @@ var GitLabDuoAgenticLanguageModel = class {
1234
1242
  }
1235
1243
  }
1236
1244
  this.#lastSentPrompt = null;
1245
+ console.warn("[duo-debug] hasStarted=false => reset sentToolCallIds, lastSentPrompt=null");
1237
1246
  }
1238
1247
  const freshToolResults = toolResults.filter((r) => !this.#sentToolCallIds.has(r.toolCallId));
1248
+ console.warn("[duo-debug] freshToolResults:", freshToolResults.length);
1239
1249
  const modelRef = this.modelId === GITLAB_DUO_DEFAULT_MODEL_ID ? void 0 : this.modelId;
1240
1250
  this.#runtime.setSelectedModelIdentifier(modelRef);
1241
1251
  await this.#runtime.ensureConnected(promptText || "", workflowType);
1252
+ console.warn("[duo-debug] ensureConnected returned OK");
1242
1253
  const mcpTools = this.#options.enableMcp === false ? [] : buildMcpTools(options);
1243
1254
  const toolContext = buildToolContext(mcpTools);
1244
1255
  const isNewUserMessage = promptText != null && promptText !== this.#lastSentPrompt;
1256
+ console.warn("[duo-debug] isNewUserMessage:", isNewUserMessage, "promptText != null:", promptText != null, "promptText !== lastSentPrompt:", promptText !== this.#lastSentPrompt);
1245
1257
  let sentToolResults = false;
1246
1258
  if (freshToolResults.length > 0) {
1247
1259
  for (const result of freshToolResults) {
@@ -1288,6 +1300,7 @@ var GitLabDuoAgenticLanguageModel = class {
1288
1300
  this.#pendingToolRequests.delete(result.toolCallId);
1289
1301
  }
1290
1302
  }
1303
+ console.warn("[duo-debug] startRequest gate: !sentToolResults:", !sentToolResults, "isNewUserMessage:", isNewUserMessage, "=> will send:", !sentToolResults && isNewUserMessage);
1291
1304
  if (!sentToolResults && isNewUserMessage) {
1292
1305
  const extraContext = [];
1293
1306
  if (toolContext) extraContext.push(toolContext);
@@ -1349,6 +1362,7 @@ var GitLabDuoAgenticLanguageModel = class {
1349
1362
  }
1350
1363
  });
1351
1364
  }
1365
+ console.warn("[duo-debug] >>> calling sendStartRequest", { goal: promptText?.slice(0, 80), hasStarted: this.#runtime.hasStarted, extraContextCount: extraContext.length });
1352
1366
  this.#runtime.sendStartRequest(
1353
1367
  promptText,
1354
1368
  workflowType,
@@ -1357,11 +1371,15 @@ var GitLabDuoAgenticLanguageModel = class {
1357
1371
  extraContext
1358
1372
  );
1359
1373
  this.#lastSentPrompt = promptText;
1374
+ console.warn("[duo-debug] >>> sendStartRequest completed, lastSentPrompt set");
1360
1375
  this.#usageEstimator.addInputChars(promptText);
1361
1376
  for (const ctx of extraContext) {
1362
1377
  if (ctx.content) this.#usageEstimator.addInputChars(ctx.content);
1363
1378
  }
1379
+ } else {
1380
+ console.warn("[duo-debug] SKIPPED sendStartRequest (sentToolResults:", sentToolResults, "isNewUserMessage:", isNewUserMessage, ")");
1364
1381
  }
1382
+ console.warn("[duo-debug] creating event stream iterator");
1365
1383
  const iterator = this.#mapEventsToStream(this.#runtime.getEventStream());
1366
1384
  const stream = asyncIteratorToReadableStream(iterator);
1367
1385
  return {
@@ -1374,9 +1392,13 @@ var GitLabDuoAgenticLanguageModel = class {
1374
1392
  async *#mapEventsToStream(events) {
1375
1393
  const state = { textStarted: false };
1376
1394
  const estimator = this.#usageEstimator;
1395
+ let eventCount = 0;
1396
+ console.warn("[duo-debug] #mapEventsToStream: starting iteration");
1377
1397
  yield { type: "stream-start", warnings: [] };
1378
1398
  try {
1379
1399
  for await (const event of events) {
1400
+ eventCount++;
1401
+ console.warn("[duo-debug] #mapEventsToStream event #" + eventCount + ":", event.type, event.type === "TEXT_CHUNK" ? "(len=" + event.content.length + ")" : "");
1380
1402
  if (event.type === "TEXT_CHUNK") {
1381
1403
  if (event.content.length > 0) {
1382
1404
  estimator.addOutputChars(event.content);
@@ -1422,9 +1444,11 @@ var GitLabDuoAgenticLanguageModel = class {
1422
1444
  }
1423
1445
  }
1424
1446
  } catch (streamErr) {
1447
+ console.warn("[duo-debug] #mapEventsToStream: caught error after", eventCount, "events:", streamErr instanceof Error ? streamErr.message : String(streamErr));
1425
1448
  yield { type: "error", error: streamErr instanceof Error ? streamErr : new Error(String(streamErr)) };
1426
1449
  return;
1427
1450
  }
1451
+ console.warn("[duo-debug] #mapEventsToStream: loop ended normally after", eventCount, "events => yielding finish:stop");
1428
1452
  yield { type: "finish", finishReason: "stop", usage: this.#currentUsage };
1429
1453
  }
1430
1454
  // ---------------------------------------------------------------------------
@@ -1871,14 +1895,26 @@ var GitLabAgenticRuntime = class {
1871
1895
  // Connection lifecycle
1872
1896
  // ---------------------------------------------------------------------------
1873
1897
  async ensureConnected(goal, workflowType) {
1898
+ console.warn("[duo-debug] ensureConnected called", {
1899
+ hasStream: !!this.#stream,
1900
+ hasWorkflowId: !!this.#workflowId,
1901
+ workflowId: this.#workflowId?.slice(0, 12),
1902
+ hasQueue: !!this.#queue,
1903
+ startRequestSent: this.#startRequestSent
1904
+ });
1874
1905
  if (this.#stream && this.#workflowId && this.#queue) {
1906
+ console.warn("[duo-debug] ensureConnected: short-circuit (already connected)");
1875
1907
  return;
1876
1908
  }
1877
1909
  if (!this.#containerParams) {
1878
1910
  this.#containerParams = await this.#resolveContainerParams();
1879
1911
  }
1880
1912
  if (!this.#workflowId) {
1913
+ console.warn("[duo-debug] ensureConnected: creating new workflow");
1881
1914
  this.#workflowId = await this.#createWorkflow(goal, workflowType);
1915
+ console.warn("[duo-debug] ensureConnected: workflow created:", this.#workflowId?.slice(0, 12));
1916
+ } else {
1917
+ console.warn("[duo-debug] ensureConnected: reusing existing workflowId:", this.#workflowId?.slice(0, 12));
1882
1918
  }
1883
1919
  const token = await this.#dependencies.workflowService.getWorkflowToken(
1884
1920
  this.#options.instanceUrl,
@@ -1886,15 +1922,19 @@ var GitLabAgenticRuntime = class {
1886
1922
  workflowType
1887
1923
  );
1888
1924
  this.#workflowToken = token;
1925
+ console.warn("[duo-debug] ensureConnected: got workflow token");
1889
1926
  const MAX_LOCK_RETRIES = 3;
1890
1927
  const LOCK_RETRY_DELAY_MS = 3e3;
1891
1928
  for (let attempt = 1; attempt <= MAX_LOCK_RETRIES; attempt++) {
1892
1929
  this.#queue = new AsyncQueue();
1893
1930
  try {
1931
+ console.warn("[duo-debug] ensureConnected: connecting WebSocket (attempt", attempt, ")");
1894
1932
  await this.#connectWebSocket();
1933
+ console.warn("[duo-debug] ensureConnected: WebSocket connected OK");
1895
1934
  return;
1896
1935
  } catch (err2) {
1897
1936
  const msg = err2 instanceof Error ? err2.message : String(err2);
1937
+ console.warn("[duo-debug] ensureConnected: WebSocket error:", msg);
1898
1938
  if ((msg.includes("1013") || msg.includes("lock")) && attempt < MAX_LOCK_RETRIES) {
1899
1939
  this.#resetStreamState();
1900
1940
  await this.#dependencies.clock.sleep(LOCK_RETRY_DELAY_MS);
@@ -1939,7 +1979,13 @@ var GitLabAgenticRuntime = class {
1939
1979
  preapproved_tools: preapprovedTools
1940
1980
  }
1941
1981
  };
1942
- this.#stream.write(startRequest);
1982
+ console.warn("[duo-debug] sendStartRequest: writing to stream", {
1983
+ workflowId: this.#workflowId?.slice(0, 12),
1984
+ goal: goal?.slice(0, 80),
1985
+ contextCount: additionalContext.length
1986
+ });
1987
+ const writeResult = this.#stream.write(startRequest);
1988
+ console.warn("[duo-debug] sendStartRequest: write() returned:", writeResult);
1943
1989
  this.#startRequestSent = true;
1944
1990
  }
1945
1991
  sendToolResponse(requestId, response, responseType) {
@@ -2028,12 +2074,14 @@ var GitLabAgenticRuntime = class {
2028
2074
  #bindStream(stream, queue) {
2029
2075
  const now = () => this.#dependencies.clock.now();
2030
2076
  const closeWithError = (message) => {
2077
+ console.warn("[duo-debug] stream closeWithError:", message);
2031
2078
  queue.push({ type: "ERROR", message, timestamp: now() });
2032
2079
  queue.close();
2033
2080
  this.#resetStreamState();
2034
2081
  };
2035
2082
  const handleAction = async (action) => {
2036
2083
  if (action.newCheckpoint) {
2084
+ console.warn("[duo-debug] stream data: newCheckpoint status=", action.newCheckpoint.status, "goal=", action.newCheckpoint.goal?.slice(0, 40));
2037
2085
  const duoEvent = {
2038
2086
  checkpoint: action.newCheckpoint.checkpoint,
2039
2087
  errors: action.newCheckpoint.errors || [],
@@ -2041,6 +2089,7 @@ var GitLabAgenticRuntime = class {
2041
2089
  workflowStatus: action.newCheckpoint.status
2042
2090
  };
2043
2091
  const events = await this.#mapper.mapWorkflowEvent(duoEvent);
2092
+ console.warn("[duo-debug] mapper produced", events.length, "events:", events.map((e) => e.type));
2044
2093
  for (const event of events) {
2045
2094
  queue.push(event);
2046
2095
  }
@@ -2048,6 +2097,7 @@ var GitLabAgenticRuntime = class {
2048
2097
  }
2049
2098
  const toolRequest = mapWorkflowActionToToolRequest(action);
2050
2099
  if (toolRequest) {
2100
+ console.warn("[duo-debug] stream data: toolRequest", toolRequest.toolName);
2051
2101
  queue.push({
2052
2102
  type: "TOOL_REQUEST",
2053
2103
  ...toolRequest,
@@ -2055,6 +2105,7 @@ var GitLabAgenticRuntime = class {
2055
2105
  });
2056
2106
  return;
2057
2107
  }
2108
+ console.warn("[duo-debug] stream data: unhandled action", Object.keys(action));
2058
2109
  };
2059
2110
  stream.on("data", (action) => {
2060
2111
  void handleAction(action).catch((error) => {
@@ -2063,9 +2114,11 @@ var GitLabAgenticRuntime = class {
2063
2114
  });
2064
2115
  });
2065
2116
  stream.on("error", (err2) => {
2117
+ console.warn("[duo-debug] stream error:", err2.message);
2066
2118
  closeWithError(err2.message);
2067
2119
  });
2068
2120
  stream.on("end", () => {
2121
+ console.warn("[duo-debug] stream end => closing queue + resetStreamState");
2069
2122
  queue.close();
2070
2123
  this.#resetStreamState();
2071
2124
  });
@@ -2087,6 +2140,7 @@ var GitLabAgenticRuntime = class {
2087
2140
  this.#bindStream(stream, this.#queue);
2088
2141
  }
2089
2142
  #resetStreamState() {
2143
+ console.warn("[duo-debug] #resetStreamState called", { hadStream: !!this.#stream, hadQueue: !!this.#queue, workflowId: this.#workflowId?.slice(0, 12) });
2090
2144
  this.#stream = void 0;
2091
2145
  this.#queue = void 0;
2092
2146
  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.3",
4
4
  "description": "OpenCode plugin and provider for GitLab Duo Agentic workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",