opencode-gitlab-duo-agentic-custom-tools 0.3.2 → 0.3.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 (2) hide show
  1. package/dist/index.js +246 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -359,16 +359,12 @@ async function createPluginHooks(input) {
359
359
  always: ["*"],
360
360
  metadata: {}
361
361
  });
362
- const url = new URL(`/session/${encodeURIComponent(ctx.sessionID)}/todo`, input.serverUrl);
363
- const response = await fetch(url);
364
- if (!response.ok) {
365
- throw new Error(`Failed to read todos: ${response.status} ${response.statusText}`);
366
- }
367
- const payload = await response.json();
368
- if (Array.isArray(payload)) {
369
- return JSON.stringify(payload, null, 2);
370
- }
371
- return "[]";
362
+ const response = await input.client.session.todo({
363
+ path: { id: ctx.sessionID },
364
+ throwOnError: true
365
+ });
366
+ const payload = response.data ?? [];
367
+ return JSON.stringify(payload, null, 2);
372
368
  }
373
369
  })
374
370
  },
@@ -1268,8 +1264,12 @@ function isPlainObject(value) {
1268
1264
  // src/provider/tool-mapping.ts
1269
1265
  var TODO_WRITE_PROGRAM = "__todo_write__";
1270
1266
  var TODO_READ_PROGRAM = "__todo_read__";
1267
+ var WEBFETCH_PROGRAM = "__webfetch__";
1268
+ var QUESTION_PROGRAM = "__question__";
1269
+ var SKILL_PROGRAM = "__skill__";
1271
1270
  var TODO_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed", "cancelled"]);
1272
1271
  var TODO_PRIORITIES = /* @__PURE__ */ new Set(["high", "medium", "low"]);
1272
+ var WEBFETCH_FORMATS = /* @__PURE__ */ new Set(["text", "markdown", "html"]);
1273
1273
  function mapDuoToolRequest(toolName, args) {
1274
1274
  switch (toolName) {
1275
1275
  case "list_dir": {
@@ -1325,14 +1325,14 @@ function mapDuoToolRequest(toolName, args) {
1325
1325
  case "shell_command": {
1326
1326
  const command = asString2(args.command);
1327
1327
  if (!command) return { toolName, args };
1328
- const bridged = mapTodoBridgeCommand(command);
1328
+ const bridged = mapBridgeCommand(command);
1329
1329
  if (bridged) return bridged;
1330
1330
  return { toolName: "bash", args: { command, description: "Run shell command", workdir: "." } };
1331
1331
  }
1332
1332
  case "run_command": {
1333
1333
  const command = asString2(args.command);
1334
1334
  if (command) {
1335
- const bridged = mapTodoBridgeCommand(command);
1335
+ const bridged = mapBridgeCommand(command);
1336
1336
  if (bridged) return bridged;
1337
1337
  }
1338
1338
  const program = asString2(args.program);
@@ -1342,6 +1342,15 @@ function mapDuoToolRequest(toolName, args) {
1342
1342
  if (program === TODO_WRITE_PROGRAM) {
1343
1343
  return mapTodoWriteCall(args.arguments);
1344
1344
  }
1345
+ if (program === WEBFETCH_PROGRAM) {
1346
+ return mapWebfetchCall(args.arguments);
1347
+ }
1348
+ if (program === QUESTION_PROGRAM) {
1349
+ return mapQuestionCall(args.arguments);
1350
+ }
1351
+ if (program === SKILL_PROGRAM) {
1352
+ return mapSkillCall(args.arguments);
1353
+ }
1345
1354
  if (program) {
1346
1355
  const parts = [shellQuote(program)];
1347
1356
  if (Array.isArray(args.flags)) parts.push(...args.flags.map((f) => shellQuote(String(f))));
@@ -1390,28 +1399,76 @@ function mapDuoToolRequest(toolName, args) {
1390
1399
  function asString2(value) {
1391
1400
  return typeof value === "string" ? value : void 0;
1392
1401
  }
1393
- function mapTodoBridgeCommand(command) {
1402
+ function mapBridgeCommand(command) {
1394
1403
  const normalized = command.trim();
1395
1404
  if (normalized === TODO_READ_PROGRAM) {
1396
1405
  return { toolName: "todoread", args: {} };
1397
1406
  }
1407
+ if (normalized.startsWith(`${TODO_READ_PROGRAM} `)) {
1408
+ return invalidTool("todoread", `${TODO_READ_PROGRAM} does not accept a payload`);
1409
+ }
1398
1410
  if (normalized === TODO_WRITE_PROGRAM) {
1399
1411
  return invalidTool("todowrite", `${TODO_WRITE_PROGRAM} expects JSON payload after command prefix`);
1400
1412
  }
1401
- if (!normalized.startsWith(`${TODO_WRITE_PROGRAM} `)) {
1402
- return null;
1413
+ if (normalized === WEBFETCH_PROGRAM) {
1414
+ return invalidTool("webfetch", `${WEBFETCH_PROGRAM} expects JSON payload after command prefix`);
1403
1415
  }
1404
- const payload = normalized.slice(TODO_WRITE_PROGRAM.length).trim();
1405
- if (!payload) {
1406
- return invalidTool("todowrite", `${TODO_WRITE_PROGRAM} expects JSON payload after command prefix`);
1416
+ if (normalized === QUESTION_PROGRAM) {
1417
+ return invalidTool("question", `${QUESTION_PROGRAM} expects JSON payload after command prefix`);
1418
+ }
1419
+ if (normalized === SKILL_PROGRAM) {
1420
+ return invalidTool("skill", `${SKILL_PROGRAM} expects JSON payload after command prefix`);
1407
1421
  }
1408
- return mapTodoWritePayload(payload);
1422
+ if (normalized.startsWith(`${TODO_WRITE_PROGRAM} `)) {
1423
+ const payload = normalized.slice(TODO_WRITE_PROGRAM.length).trim();
1424
+ if (!payload) {
1425
+ return invalidTool("todowrite", `${TODO_WRITE_PROGRAM} expects JSON payload after command prefix`);
1426
+ }
1427
+ return mapTodoWritePayload(payload);
1428
+ }
1429
+ if (normalized.startsWith(`${WEBFETCH_PROGRAM} `)) {
1430
+ const payload = normalized.slice(WEBFETCH_PROGRAM.length).trim();
1431
+ if (!payload) {
1432
+ return invalidTool("webfetch", `${WEBFETCH_PROGRAM} expects JSON payload after command prefix`);
1433
+ }
1434
+ return mapWebfetchPayload(payload);
1435
+ }
1436
+ if (normalized.startsWith(`${QUESTION_PROGRAM} `)) {
1437
+ const payload = normalized.slice(QUESTION_PROGRAM.length).trim();
1438
+ if (!payload) {
1439
+ return invalidTool("question", `${QUESTION_PROGRAM} expects JSON payload after command prefix`);
1440
+ }
1441
+ return mapQuestionPayload(payload);
1442
+ }
1443
+ if (normalized.startsWith(`${SKILL_PROGRAM} `)) {
1444
+ const payload = normalized.slice(SKILL_PROGRAM.length).trim();
1445
+ if (!payload) {
1446
+ return invalidTool("skill", `${SKILL_PROGRAM} expects JSON payload after command prefix`);
1447
+ }
1448
+ return mapSkillPayload(payload);
1449
+ }
1450
+ return null;
1409
1451
  }
1410
1452
  function mapTodoWriteCall(rawArguments) {
1411
- const payloadResult = parseTodoPayloadFromArguments(rawArguments);
1453
+ const payloadResult = parsePayloadFromArguments(rawArguments, TODO_WRITE_PROGRAM);
1412
1454
  if ("error" in payloadResult) return invalidTool("todowrite", payloadResult.error);
1413
1455
  return mapTodoWritePayload(payloadResult.payload);
1414
1456
  }
1457
+ function mapWebfetchCall(rawArguments) {
1458
+ const payloadResult = parsePayloadFromArguments(rawArguments, WEBFETCH_PROGRAM);
1459
+ if ("error" in payloadResult) return invalidTool("webfetch", payloadResult.error);
1460
+ return mapWebfetchPayload(payloadResult.payload);
1461
+ }
1462
+ function mapQuestionCall(rawArguments) {
1463
+ const payloadResult = parsePayloadFromArguments(rawArguments, QUESTION_PROGRAM);
1464
+ if ("error" in payloadResult) return invalidTool("question", payloadResult.error);
1465
+ return mapQuestionPayload(payloadResult.payload);
1466
+ }
1467
+ function mapSkillCall(rawArguments) {
1468
+ const payloadResult = parsePayloadFromArguments(rawArguments, SKILL_PROGRAM);
1469
+ if ("error" in payloadResult) return invalidTool("skill", payloadResult.error);
1470
+ return mapSkillPayload(payloadResult.payload);
1471
+ }
1415
1472
  function mapTodoWritePayload(rawPayload) {
1416
1473
  const payloadResult = parseTodoPayload(rawPayload);
1417
1474
  if ("error" in payloadResult) return invalidTool("todowrite", payloadResult.error);
@@ -1424,35 +1481,181 @@ function mapTodoWritePayload(rawPayload) {
1424
1481
  }
1425
1482
  };
1426
1483
  }
1427
- function parseTodoPayloadFromArguments(rawArguments) {
1484
+ function mapWebfetchPayload(rawPayload) {
1485
+ const payloadResult = parseWebfetchPayload(rawPayload);
1486
+ if ("error" in payloadResult) return invalidTool("webfetch", payloadResult.error);
1487
+ return {
1488
+ toolName: "webfetch",
1489
+ args: payloadResult.args
1490
+ };
1491
+ }
1492
+ function mapQuestionPayload(rawPayload) {
1493
+ const payloadResult = parseQuestionPayload(rawPayload);
1494
+ if ("error" in payloadResult) return invalidTool("question", payloadResult.error);
1495
+ return {
1496
+ toolName: "question",
1497
+ args: payloadResult.args
1498
+ };
1499
+ }
1500
+ function mapSkillPayload(rawPayload) {
1501
+ const payloadResult = parseSkillPayload(rawPayload);
1502
+ if ("error" in payloadResult) return invalidTool("skill", payloadResult.error);
1503
+ return {
1504
+ toolName: "skill",
1505
+ args: payloadResult.args
1506
+ };
1507
+ }
1508
+ function parsePayloadFromArguments(rawArguments, program) {
1428
1509
  if (!Array.isArray(rawArguments) || rawArguments.length === 0) {
1429
1510
  return {
1430
- error: `${TODO_WRITE_PROGRAM} expects JSON payload in arguments[0]`
1511
+ error: `${program} expects JSON payload in arguments[0]`
1431
1512
  };
1432
1513
  }
1433
1514
  const rawPayload = rawArguments[0];
1434
1515
  if (typeof rawPayload !== "string") {
1435
1516
  return {
1436
- error: `${TODO_WRITE_PROGRAM} expects arguments[0] to be a JSON string`
1517
+ error: `${program} expects arguments[0] to be a JSON string`
1437
1518
  };
1438
1519
  }
1439
1520
  return { payload: rawPayload };
1440
1521
  }
1441
1522
  function parseTodoPayload(rawPayload) {
1523
+ return parseObjectPayload(rawPayload, TODO_WRITE_PROGRAM);
1524
+ }
1525
+ function parseObjectPayload(rawPayload, program) {
1442
1526
  const normalized = unwrapWrappingQuotes(rawPayload);
1443
1527
  try {
1444
1528
  const parsed = JSON.parse(normalized);
1445
1529
  if (!isRecord(parsed)) {
1446
1530
  return {
1447
- error: `${TODO_WRITE_PROGRAM} payload must be a JSON object`
1531
+ error: `${program} payload must be a JSON object`
1448
1532
  };
1449
1533
  }
1450
1534
  return { payload: parsed };
1451
1535
  } catch {
1452
1536
  return {
1453
- error: `${TODO_WRITE_PROGRAM} payload is not valid JSON`
1537
+ error: `${program} payload is not valid JSON`
1538
+ };
1539
+ }
1540
+ }
1541
+ function parseWebfetchPayload(rawPayload) {
1542
+ const payloadResult = parseObjectPayload(rawPayload, WEBFETCH_PROGRAM);
1543
+ if ("error" in payloadResult) return payloadResult;
1544
+ const url = asString2(payloadResult.payload.url);
1545
+ if (!url) {
1546
+ return {
1547
+ error: `${WEBFETCH_PROGRAM} payload must include a url string`
1454
1548
  };
1455
1549
  }
1550
+ const args = { url };
1551
+ const format = asString2(payloadResult.payload.format);
1552
+ if (format !== void 0) {
1553
+ if (!WEBFETCH_FORMATS.has(format)) {
1554
+ return {
1555
+ error: `${WEBFETCH_PROGRAM} format must be one of: text, markdown, html`
1556
+ };
1557
+ }
1558
+ args.format = format;
1559
+ }
1560
+ const timeout = payloadResult.payload.timeout;
1561
+ if (timeout !== void 0) {
1562
+ if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) {
1563
+ return {
1564
+ error: `${WEBFETCH_PROGRAM} timeout must be a positive number`
1565
+ };
1566
+ }
1567
+ args.timeout = timeout;
1568
+ }
1569
+ return { args };
1570
+ }
1571
+ function parseQuestionPayload(rawPayload) {
1572
+ const payloadResult = parseObjectPayload(rawPayload, QUESTION_PROGRAM);
1573
+ if ("error" in payloadResult) return payloadResult;
1574
+ const rawQuestions = payloadResult.payload.questions;
1575
+ if (!Array.isArray(rawQuestions) || rawQuestions.length === 0) {
1576
+ return {
1577
+ error: `${QUESTION_PROGRAM} payload must include a non-empty questions array`
1578
+ };
1579
+ }
1580
+ const questions = [];
1581
+ for (let i = 0; i < rawQuestions.length; i += 1) {
1582
+ const rawQuestion = rawQuestions[i];
1583
+ if (!isRecord(rawQuestion)) {
1584
+ return {
1585
+ error: `${QUESTION_PROGRAM} questions[${i}] must be an object`
1586
+ };
1587
+ }
1588
+ const question = asString2(rawQuestion.question);
1589
+ if (!question) {
1590
+ return {
1591
+ error: `${QUESTION_PROGRAM} questions[${i}].question must be a string`
1592
+ };
1593
+ }
1594
+ const header = asString2(rawQuestion.header);
1595
+ if (!header) {
1596
+ return {
1597
+ error: `${QUESTION_PROGRAM} questions[${i}].header must be a string`
1598
+ };
1599
+ }
1600
+ const rawOptions = rawQuestion.options;
1601
+ if (!Array.isArray(rawOptions) || rawOptions.length === 0) {
1602
+ return {
1603
+ error: `${QUESTION_PROGRAM} questions[${i}].options must be a non-empty array`
1604
+ };
1605
+ }
1606
+ const options = [];
1607
+ for (let j = 0; j < rawOptions.length; j += 1) {
1608
+ const rawOption = rawOptions[j];
1609
+ if (!isRecord(rawOption)) {
1610
+ return {
1611
+ error: `${QUESTION_PROGRAM} questions[${i}].options[${j}] must be an object`
1612
+ };
1613
+ }
1614
+ const label = asString2(rawOption.label);
1615
+ if (!label) {
1616
+ return {
1617
+ error: `${QUESTION_PROGRAM} questions[${i}].options[${j}].label must be a string`
1618
+ };
1619
+ }
1620
+ const description = asString2(rawOption.description);
1621
+ if (!description) {
1622
+ return {
1623
+ error: `${QUESTION_PROGRAM} questions[${i}].options[${j}].description must be a string`
1624
+ };
1625
+ }
1626
+ options.push({ label, description });
1627
+ }
1628
+ const mappedQuestion = { question, header, options };
1629
+ if (rawQuestion.multiple !== void 0) {
1630
+ if (typeof rawQuestion.multiple !== "boolean") {
1631
+ return {
1632
+ error: `${QUESTION_PROGRAM} questions[${i}].multiple must be a boolean`
1633
+ };
1634
+ }
1635
+ mappedQuestion.multiple = rawQuestion.multiple;
1636
+ }
1637
+ questions.push(mappedQuestion);
1638
+ }
1639
+ return {
1640
+ args: {
1641
+ questions
1642
+ }
1643
+ };
1644
+ }
1645
+ function parseSkillPayload(rawPayload) {
1646
+ const payloadResult = parseObjectPayload(rawPayload, SKILL_PROGRAM);
1647
+ if ("error" in payloadResult) return payloadResult;
1648
+ const name = asString2(payloadResult.payload.name)?.trim();
1649
+ if (!name) {
1650
+ return {
1651
+ error: `${SKILL_PROGRAM} payload must include a name string`
1652
+ };
1653
+ }
1654
+ return {
1655
+ args: {
1656
+ name
1657
+ }
1658
+ };
1456
1659
  }
1457
1660
  function unwrapWrappingQuotes(value) {
1458
1661
  const trimmed = value.trim();
@@ -1671,13 +1874,25 @@ var TOOL_ALLOWLIST = [
1671
1874
  "run_command"
1672
1875
  ];
1673
1876
  var TODO_BRIDGE_INSTRUCTIONS = [
1674
- "Todo bridge (required):",
1675
- "- For todo write/update, call run_command with command:",
1877
+ "Todo planning and execution (required):",
1878
+ "- For complex or multi-step work (3+ steps), create and maintain a todo list.",
1879
+ "- Keep exactly one task as in_progress at a time.",
1880
+ "- Mark tasks completed immediately after finishing them.",
1881
+ "- Update the todo list whenever scope changes or new requirements appear.",
1882
+ "- Skip todo planning for trivial one-step requests.",
1883
+ "- To read the current todo list, call run_command with command: __todo_read__",
1884
+ "- To create or update the todo list, call run_command with command:",
1676
1885
  ' __todo_write__ {"todos":[{"content":"...","status":"pending|in_progress|completed|cancelled","priority":"high|medium|low"}]}',
1677
- "- For todo read, call run_command with command: __todo_read__",
1678
- "- Use strict JSON with double quotes.",
1679
- "- If validation fails, correct payload and retry.",
1680
- "- Do not use regular shell commands for todo operations."
1886
+ "- To fetch web content, call run_command with command:",
1887
+ ' __webfetch__ {"url":"https://example.com","format":"markdown","timeout":30}',
1888
+ "- To ask the user questions, call run_command with command:",
1889
+ ' __question__ {"questions":[{"question":"...","header":"...","options":[{"label":"Option A","description":"..."}],"multiple":false}]}',
1890
+ "- To load a skill, call run_command with command:",
1891
+ ' __skill__ {"name":"skill-name"}',
1892
+ "- Use strict JSON with double quotes in all bridge payloads.",
1893
+ "- For __question__, custom is enabled by default; do not add generic Other options.",
1894
+ "- If payload validation fails, correct the JSON and retry.",
1895
+ "- For todo, webfetch, question, and skill operations, only use __todo_read__, __todo_write__, __webfetch__, __question__, and __skill__."
1681
1896
  ].join("\n");
1682
1897
  function buildFlowConfig(systemPrompt) {
1683
1898
  const bridgedPrompt = [systemPrompt.trim(), TODO_BRIDGE_INSTRUCTIONS].filter(Boolean).join("\n\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitlab-duo-agentic-custom-tools",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "OpenCode plugin and provider for GitLab Duo Agentic workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",