opencode-gitlab-duo-agentic-custom-tools 0.3.3 → 0.3.5

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 +286 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1264,8 +1264,12 @@ function isPlainObject(value) {
1264
1264
  // src/provider/tool-mapping.ts
1265
1265
  var TODO_WRITE_PROGRAM = "__todo_write__";
1266
1266
  var TODO_READ_PROGRAM = "__todo_read__";
1267
+ var WEBFETCH_PROGRAM = "__webfetch__";
1268
+ var QUESTION_PROGRAM = "__question__";
1269
+ var SKILL_PROGRAM = "__skill__";
1267
1270
  var TODO_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed", "cancelled"]);
1268
1271
  var TODO_PRIORITIES = /* @__PURE__ */ new Set(["high", "medium", "low"]);
1272
+ var WEBFETCH_FORMATS = /* @__PURE__ */ new Set(["text", "markdown", "html"]);
1269
1273
  function mapDuoToolRequest(toolName, args) {
1270
1274
  switch (toolName) {
1271
1275
  case "list_dir": {
@@ -1321,14 +1325,14 @@ function mapDuoToolRequest(toolName, args) {
1321
1325
  case "shell_command": {
1322
1326
  const command = asString2(args.command);
1323
1327
  if (!command) return { toolName, args };
1324
- const bridged = mapTodoBridgeCommand(command);
1328
+ const bridged = mapBridgeCommand(command);
1325
1329
  if (bridged) return bridged;
1326
1330
  return { toolName: "bash", args: { command, description: "Run shell command", workdir: "." } };
1327
1331
  }
1328
1332
  case "run_command": {
1329
1333
  const command = asString2(args.command);
1330
1334
  if (command) {
1331
- const bridged = mapTodoBridgeCommand(command);
1335
+ const bridged = mapBridgeCommand(command);
1332
1336
  if (bridged) return bridged;
1333
1337
  }
1334
1338
  const program = asString2(args.program);
@@ -1338,6 +1342,15 @@ function mapDuoToolRequest(toolName, args) {
1338
1342
  if (program === TODO_WRITE_PROGRAM) {
1339
1343
  return mapTodoWriteCall(args.arguments);
1340
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
+ }
1341
1354
  if (program) {
1342
1355
  const parts = [shellQuote(program)];
1343
1356
  if (Array.isArray(args.flags)) parts.push(...args.flags.map((f) => shellQuote(String(f))));
@@ -1386,28 +1399,76 @@ function mapDuoToolRequest(toolName, args) {
1386
1399
  function asString2(value) {
1387
1400
  return typeof value === "string" ? value : void 0;
1388
1401
  }
1389
- function mapTodoBridgeCommand(command) {
1402
+ function mapBridgeCommand(command) {
1390
1403
  const normalized = command.trim();
1391
1404
  if (normalized === TODO_READ_PROGRAM) {
1392
1405
  return { toolName: "todoread", args: {} };
1393
1406
  }
1407
+ if (normalized.startsWith(`${TODO_READ_PROGRAM} `)) {
1408
+ return invalidTool("todoread", `${TODO_READ_PROGRAM} does not accept a payload`);
1409
+ }
1394
1410
  if (normalized === TODO_WRITE_PROGRAM) {
1395
1411
  return invalidTool("todowrite", `${TODO_WRITE_PROGRAM} expects JSON payload after command prefix`);
1396
1412
  }
1397
- if (!normalized.startsWith(`${TODO_WRITE_PROGRAM} `)) {
1398
- return null;
1413
+ if (normalized === WEBFETCH_PROGRAM) {
1414
+ return invalidTool("webfetch", `${WEBFETCH_PROGRAM} expects JSON payload after command prefix`);
1399
1415
  }
1400
- const payload = normalized.slice(TODO_WRITE_PROGRAM.length).trim();
1401
- if (!payload) {
1402
- 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`);
1421
+ }
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);
1403
1449
  }
1404
- return mapTodoWritePayload(payload);
1450
+ return null;
1405
1451
  }
1406
1452
  function mapTodoWriteCall(rawArguments) {
1407
- const payloadResult = parseTodoPayloadFromArguments(rawArguments);
1453
+ const payloadResult = parsePayloadFromArguments(rawArguments, TODO_WRITE_PROGRAM);
1408
1454
  if ("error" in payloadResult) return invalidTool("todowrite", payloadResult.error);
1409
1455
  return mapTodoWritePayload(payloadResult.payload);
1410
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
+ }
1411
1472
  function mapTodoWritePayload(rawPayload) {
1412
1473
  const payloadResult = parseTodoPayload(rawPayload);
1413
1474
  if ("error" in payloadResult) return invalidTool("todowrite", payloadResult.error);
@@ -1420,35 +1481,181 @@ function mapTodoWritePayload(rawPayload) {
1420
1481
  }
1421
1482
  };
1422
1483
  }
1423
- 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) {
1424
1509
  if (!Array.isArray(rawArguments) || rawArguments.length === 0) {
1425
1510
  return {
1426
- error: `${TODO_WRITE_PROGRAM} expects JSON payload in arguments[0]`
1511
+ error: `${program} expects JSON payload in arguments[0]`
1427
1512
  };
1428
1513
  }
1429
1514
  const rawPayload = rawArguments[0];
1430
1515
  if (typeof rawPayload !== "string") {
1431
1516
  return {
1432
- error: `${TODO_WRITE_PROGRAM} expects arguments[0] to be a JSON string`
1517
+ error: `${program} expects arguments[0] to be a JSON string`
1433
1518
  };
1434
1519
  }
1435
1520
  return { payload: rawPayload };
1436
1521
  }
1437
1522
  function parseTodoPayload(rawPayload) {
1523
+ return parseObjectPayload(rawPayload, TODO_WRITE_PROGRAM);
1524
+ }
1525
+ function parseObjectPayload(rawPayload, program) {
1438
1526
  const normalized = unwrapWrappingQuotes(rawPayload);
1439
1527
  try {
1440
1528
  const parsed = JSON.parse(normalized);
1441
1529
  if (!isRecord(parsed)) {
1442
1530
  return {
1443
- error: `${TODO_WRITE_PROGRAM} payload must be a JSON object`
1531
+ error: `${program} payload must be a JSON object`
1444
1532
  };
1445
1533
  }
1446
1534
  return { payload: parsed };
1447
1535
  } catch {
1448
1536
  return {
1449
- 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`
1548
+ };
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`
1450
1578
  };
1451
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
+ };
1452
1659
  }
1453
1660
  function unwrapWrappingQuotes(value) {
1454
1661
  const trimmed = value.trim();
@@ -1666,17 +1873,73 @@ var TOOL_ALLOWLIST = [
1666
1873
  "mkdir",
1667
1874
  "run_command"
1668
1875
  ];
1669
- var TODO_BRIDGE_INSTRUCTIONS = [
1670
- "Todo bridge (required):",
1671
- "- For todo write/update, call run_command with command:",
1876
+ var BRIDGE_WHEN_TO_USE = [
1877
+ "Bridge tools \u2014 when to use:",
1878
+ "",
1879
+ "Todo (__todo_read__, __todo_write__):",
1880
+ "- Use for planning and tracking when the task has 3+ steps, multiple files, or multiple user requirements.",
1881
+ "- Keep exactly one todo in_progress at a time.",
1882
+ "- Mark tasks completed immediately after finishing each step.",
1883
+ "- Update the todo list when scope changes or new requirements appear.",
1884
+ "- Skip for trivial one-step requests.",
1885
+ "",
1886
+ "Web fetch (__webfetch__):",
1887
+ "- Use only when external URL content is needed and repo/local context is insufficient.",
1888
+ "- Prefer targeted URLs and the minimum needed format.",
1889
+ "- Do not use for local files or codebase exploration.",
1890
+ "",
1891
+ "Question (__question__):",
1892
+ "- Use only when blocked by missing user input that materially changes the implementation.",
1893
+ "- Complete all non-blocked work first, then ask.",
1894
+ "- Ask exactly one targeted question unless multiple answers are strictly required together.",
1895
+ "- If a safe low-risk default exists, proceed without asking and state the assumption.",
1896
+ "- Must ask for: credentials/secrets, destructive/irreversible actions, production/billing/security impact, ambiguity where different answers produce different implementations.",
1897
+ '- Do not ask for: permission-style prompts ("Should I proceed?"), trivial preferences, questions answerable from repo/docs/context.',
1898
+ "",
1899
+ "Skill (__skill__):",
1900
+ "- Use when the task clearly matches an available skill and specialized instructions are needed.",
1901
+ "- Load the matching skill once, then follow its workflow.",
1902
+ "- Do not use if no available skill clearly matches."
1903
+ ].join("\n");
1904
+ var BRIDGE_HOW_TO_USE = [
1905
+ "Bridge tools \u2014 how to use:",
1906
+ "",
1907
+ "All bridge tools are called via run_command with a single command string.",
1908
+ "",
1909
+ "Read todos:",
1910
+ " __todo_read__",
1911
+ "Write/update todos:",
1672
1912
  ' __todo_write__ {"todos":[{"content":"...","status":"pending|in_progress|completed|cancelled","priority":"high|medium|low"}]}',
1673
- "- For todo read, call run_command with command: __todo_read__",
1674
- "- Use strict JSON with double quotes.",
1675
- "- If validation fails, correct payload and retry.",
1676
- "- Do not use regular shell commands for todo operations."
1913
+ "Fetch web content:",
1914
+ ' __webfetch__ {"url":"https://example.com","format":"markdown","timeout":30}',
1915
+ "Ask user question(s):",
1916
+ ' __question__ {"questions":[{"question":"...","header":"...","options":[{"label":"Option A","description":"..."}],"multiple":false}]}',
1917
+ "Load a skill:",
1918
+ ' __skill__ {"name":"skill-name"}'
1919
+ ].join("\n");
1920
+ var BRIDGE_FORMATTING_RULES = [
1921
+ "Bridge tools \u2014 formatting and validation:",
1922
+ "",
1923
+ "Payloads:",
1924
+ "- Use strict JSON with double quotes in all payloads.",
1925
+ "- If validation fails, correct the JSON and retry.",
1926
+ "- Do not use regular shell commands for bridge operations.",
1927
+ "",
1928
+ "Question formatting:",
1929
+ "- Provide 2-5 concrete options.",
1930
+ "- Labels: 1-5 words, concise.",
1931
+ "- Header: max 30 chars.",
1932
+ '- Put recommended option first; append "(Recommended)" to its label.',
1933
+ "- Set multiple=true only when selecting more than one is valid.",
1934
+ '- Custom free-text is enabled by default; do not add generic "Other" options.'
1677
1935
  ].join("\n");
1678
1936
  function buildFlowConfig(systemPrompt) {
1679
- const bridgedPrompt = [systemPrompt.trim(), TODO_BRIDGE_INSTRUCTIONS].filter(Boolean).join("\n\n");
1937
+ const bridgedPrompt = [
1938
+ systemPrompt.trim(),
1939
+ BRIDGE_WHEN_TO_USE,
1940
+ BRIDGE_HOW_TO_USE,
1941
+ BRIDGE_FORMATTING_RULES
1942
+ ].filter(Boolean).join("\n\n");
1680
1943
  return {
1681
1944
  version: "v1",
1682
1945
  environment: "chat-partial",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitlab-duo-agentic-custom-tools",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "OpenCode plugin and provider for GitLab Duo Agentic workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",