opencode-gitlab-duo-agentic 0.2.6 → 0.2.7

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 +169 -397
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -135,8 +135,8 @@ async function resolveRootNamespaceId(client, namespaceId) {
135
135
  }
136
136
  async function readGitConfig(cwd) {
137
137
  const gitPath = path.join(cwd, ".git");
138
- const stat2 = await fs.stat(gitPath);
139
- if (stat2.isDirectory()) {
138
+ const stat = await fs.stat(gitPath);
139
+ if (stat.isDirectory()) {
140
140
  return fs.readFile(path.join(gitPath, "config"), "utf8");
141
141
  }
142
142
  const content = await fs.readFile(gitPath, "utf8");
@@ -415,7 +415,7 @@ var AsyncQueue = class {
415
415
  const value = this.#values.shift();
416
416
  if (value !== void 0) return Promise.resolve(value);
417
417
  if (this.#closed) return Promise.resolve(null);
418
- return new Promise((resolve2) => this.#waiters.push(resolve2));
418
+ return new Promise((resolve) => this.#waiters.push(resolve));
419
419
  }
420
420
  close() {
421
421
  this.#closed = true;
@@ -572,7 +572,7 @@ var WorkflowWebSocketClient = class {
572
572
  async connect(url, headers) {
573
573
  const socket = new WebSocket(url, { headers });
574
574
  this.#socket = socket;
575
- await new Promise((resolve2, reject) => {
575
+ await new Promise((resolve, reject) => {
576
576
  const timeout = setTimeout(() => {
577
577
  cleanup();
578
578
  socket.close(1e3);
@@ -585,7 +585,7 @@ var WorkflowWebSocketClient = class {
585
585
  };
586
586
  const onOpen = () => {
587
587
  cleanup();
588
- resolve2();
588
+ resolve();
589
589
  };
590
590
  const onError = (error) => {
591
591
  cleanup();
@@ -650,375 +650,131 @@ function decodeSocketMessage(data) {
650
650
  return void 0;
651
651
  }
652
652
 
653
- // src/workflow/tool-executor.ts
654
- import { readFile, readdir, writeFile, mkdir as fsMkdir, stat } from "fs/promises";
655
- import { exec } from "child_process";
656
- import { resolve, dirname } from "path";
657
- function asString(value) {
658
- return typeof value === "string" ? value : void 0;
659
- }
660
- function asNumber(value) {
661
- return typeof value === "number" ? value : void 0;
662
- }
663
- function asStringArray(value) {
664
- if (!Array.isArray(value)) return [];
665
- return value.filter((v) => typeof v === "string");
666
- }
667
- var ToolExecutor = class {
668
- #cwd;
669
- #client;
670
- constructor(cwd, client) {
671
- this.#cwd = cwd;
672
- this.#client = client;
673
- }
674
- /**
675
- * Execute a tool request using Duo tool names and arg formats.
676
- * This handles checkpoint-based tool requests where tool_info contains
677
- * Duo-native names (read_file, edit_file, etc.) and Duo-native arg keys
678
- * (file_path, old_str, new_str, etc.).
679
- */
680
- async executeDuoTool(toolName, args) {
681
- try {
682
- switch (toolName) {
683
- case "read_file": {
684
- const filePath = asString(args.file_path) ?? asString(args.filepath) ?? asString(args.filePath) ?? asString(args.path);
685
- if (!filePath) return { response: "", error: "read_file: missing file_path" };
686
- return this.#read({ filePath, offset: asNumber(args.offset), limit: asNumber(args.limit) });
687
- }
688
- case "read_files": {
689
- const paths = asStringArray(args.file_paths);
690
- if (paths.length === 0) return { response: "", error: "read_files: missing file_paths" };
691
- return this.#readMultiple(paths);
692
- }
693
- case "create_file_with_contents": {
694
- const filePath = asString(args.file_path);
695
- const content = asString(args.contents);
696
- if (!filePath || content === void 0) return { response: "", error: "create_file: missing file_path or contents" };
697
- return this.#write({ filePath, content });
698
- }
699
- case "edit_file": {
700
- const filePath = asString(args.file_path);
701
- const oldString = asString(args.old_str);
702
- const newString = asString(args.new_str);
703
- if (!filePath || oldString === void 0 || newString === void 0) {
704
- return { response: "", error: "edit_file: missing file_path, old_str or new_str" };
705
- }
706
- return this.#edit({ filePath, oldString, newString });
707
- }
708
- case "list_dir": {
709
- const directory = asString(args.directory) ?? ".";
710
- return this.#read({ filePath: directory });
711
- }
712
- case "find_files": {
713
- const pattern = asString(args.name_pattern);
714
- if (!pattern) return { response: "", error: "find_files: missing name_pattern" };
715
- return this.#glob({ pattern });
716
- }
717
- case "grep": {
718
- const pattern = asString(args.pattern);
719
- if (!pattern) return { response: "", error: "grep: missing pattern" };
720
- return this.#grep({
721
- pattern,
722
- path: asString(args.search_directory),
723
- caseInsensitive: Boolean(args.case_insensitive)
724
- });
725
- }
726
- case "mkdir": {
727
- const dir = asString(args.directory_path);
728
- if (!dir) return { response: "", error: "mkdir: missing directory_path" };
729
- return this.#mkdir(dir);
730
- }
731
- case "shell_command": {
732
- const command = asString(args.command);
733
- if (!command) return { response: "", error: "shell_command: missing command" };
734
- return this.#bash({ command });
735
- }
736
- case "run_command": {
737
- const program = asString(args.program);
738
- if (!program) return { response: "", error: "run_command: missing program" };
739
- const parts = [program];
740
- const flags = args.flags;
741
- if (Array.isArray(flags)) parts.push(...flags.map(String));
742
- const cmdArgs = args.arguments;
743
- if (Array.isArray(cmdArgs)) parts.push(...cmdArgs.map(String));
744
- return this.#bash({ command: parts.join(" ") });
745
- }
746
- case "run_git_command": {
747
- const command = asString(args.command);
748
- if (!command) return { response: "", error: "run_git_command: missing command" };
749
- const extra = Array.isArray(args.args) ? args.args.map(String).join(" ") : asString(args.args);
750
- const gitCmd = extra ? `git ${command} ${extra}` : `git ${command}`;
751
- return this.#bash({ command: gitCmd });
752
- }
753
- case "gitlab_api_request": {
754
- const method = asString(args.method) ?? "GET";
755
- const path3 = asString(args.path);
756
- if (!path3) return { response: "", error: "gitlab_api_request: missing path" };
757
- return this.#httpRequest({ method, path: path3, body: asString(args.body) });
758
- }
759
- default: {
760
- console.error(`[tool-executor] unknown Duo tool: ${toolName}, args: ${JSON.stringify(args).slice(0, 300)}`);
761
- return { response: "", error: `Unknown Duo tool: ${toolName}` };
762
- }
653
+ // src/workflow/action-mapper.ts
654
+ function mapActionToToolRequest(action) {
655
+ const requestId = action.requestID;
656
+ if (!requestId) return null;
657
+ if (action.runMCPTool) {
658
+ let parsedArgs;
659
+ if (typeof action.runMCPTool.args === "string") {
660
+ try {
661
+ parsedArgs = JSON.parse(action.runMCPTool.args);
662
+ } catch {
663
+ parsedArgs = {};
763
664
  }
764
- } catch (err) {
765
- console.error(`[tool-executor] executeDuoTool error:`, err);
766
- return { response: "", error: err instanceof Error ? err.message : String(err) };
665
+ } else {
666
+ parsedArgs = {};
767
667
  }
668
+ return { requestId, toolName: action.runMCPTool.name, args: parsedArgs };
768
669
  }
769
- /**
770
- * Route a Duo WorkflowToolAction to the appropriate local handler.
771
- * This handles standalone WebSocket actions (non-checkpoint path).
772
- */
773
- async executeAction(action) {
774
- try {
775
- if (action.runReadFile) {
776
- return this.#read({
777
- filePath: action.runReadFile.filepath,
778
- offset: action.runReadFile.offset,
779
- limit: action.runReadFile.limit
780
- });
781
- }
782
- if (action.runReadFiles) {
783
- return this.#readMultiple(action.runReadFiles.filepaths);
784
- }
785
- if (action.runWriteFile) {
786
- return this.#write({
787
- filePath: action.runWriteFile.filepath,
788
- content: action.runWriteFile.contents
789
- });
790
- }
791
- if (action.runEditFile) {
792
- return this.#edit({
793
- filePath: action.runEditFile.filepath,
794
- oldString: action.runEditFile.oldString,
795
- newString: action.runEditFile.newString
796
- });
797
- }
798
- if (action.runShellCommand) {
799
- return this.#bash({ command: action.runShellCommand.command });
800
- }
801
- if (action.runCommand) {
802
- const parts = [action.runCommand.program];
803
- if (action.runCommand.flags) parts.push(...action.runCommand.flags);
804
- if (action.runCommand.arguments) parts.push(...action.runCommand.arguments);
805
- return this.#bash({ command: parts.join(" ") });
806
- }
807
- if (action.runGitCommand) {
808
- const cmd = action.runGitCommand.arguments ? `git ${action.runGitCommand.command} ${action.runGitCommand.arguments}` : `git ${action.runGitCommand.command}`;
809
- return this.#bash({ command: cmd });
810
- }
811
- if (action.listDirectory) {
812
- return this.#read({ filePath: action.listDirectory.directory });
813
- }
814
- if (action.grep) {
815
- return this.#grep({
816
- pattern: action.grep.pattern,
817
- path: action.grep.search_directory,
818
- caseInsensitive: action.grep.case_insensitive
819
- });
820
- }
821
- if (action.findFiles) {
822
- return this.#glob({ pattern: action.findFiles.name_pattern });
823
- }
824
- if (action.mkdir) {
825
- return this.#mkdir(action.mkdir.directory_path);
826
- }
827
- if (action.runMCPTool) {
828
- return this.#executeMcpTool(action.runMCPTool.name, action.runMCPTool.args);
829
- }
830
- if (action.runHTTPRequest) {
831
- return this.#httpRequest(action.runHTTPRequest);
670
+ if (action.runReadFile) {
671
+ return {
672
+ requestId,
673
+ toolName: "read_file",
674
+ args: {
675
+ file_path: action.runReadFile.filepath,
676
+ offset: action.runReadFile.offset,
677
+ limit: action.runReadFile.limit
832
678
  }
833
- const keys = Object.keys(action).filter((k) => k !== "requestID");
834
- console.error(`[tool-executor] unhandled action, keys: ${JSON.stringify(keys)}, raw: ${JSON.stringify(action).slice(0, 500)}`);
835
- return { response: "", error: `Unknown tool action (keys: ${keys.join(", ")})` };
836
- } catch (err) {
837
- console.error(`[tool-executor] execution error:`, err);
838
- return { response: "", error: err instanceof Error ? err.message : String(err) };
839
- }
840
- }
841
- /**
842
- * Execute an MCP tool by name (for runMCPTool actions).
843
- * Routes to the appropriate handler based on tool name.
844
- */
845
- async #executeMcpTool(name, argsJson) {
846
- const args = argsJson ? JSON.parse(argsJson) : {};
847
- switch (name) {
848
- case "bash":
849
- return this.#bash(args);
850
- case "read":
851
- return this.#read(args);
852
- case "edit":
853
- return this.#edit(args);
854
- case "write":
855
- return this.#write(args);
856
- case "glob":
857
- return this.#glob(args);
858
- case "grep":
859
- return this.#grep(args);
860
- default:
861
- return { response: "", error: `Unknown MCP tool: ${name}` };
862
- }
679
+ };
863
680
  }
864
- // ── Handlers ──────────────────────────────────────────────────────
865
- async #bash(args) {
866
- const cwd = args.workdir ? resolve(this.#cwd, args.workdir) : this.#cwd;
867
- const timeout = args.timeout ?? 12e4;
868
- return new Promise((res) => {
869
- exec(args.command, { cwd, timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
870
- const output = [stdout, stderr].filter(Boolean).join("\n");
871
- if (error) {
872
- res({ response: output, error: error.message });
873
- return;
874
- }
875
- res({ response: output });
876
- });
877
- });
681
+ if (action.runReadFiles) {
682
+ return {
683
+ requestId,
684
+ toolName: "read_files",
685
+ args: { file_paths: action.runReadFiles.filepaths ?? [] }
686
+ };
878
687
  }
879
- async #read(args) {
880
- try {
881
- const filepath = resolve(this.#cwd, args.filePath);
882
- const info = await stat(filepath);
883
- if (info.isDirectory()) {
884
- const entries = await readdir(filepath, { withFileTypes: true });
885
- const lines = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name);
886
- return { response: lines.join("\n") };
688
+ if (action.runWriteFile) {
689
+ return {
690
+ requestId,
691
+ toolName: "create_file_with_contents",
692
+ args: {
693
+ file_path: action.runWriteFile.filepath,
694
+ contents: action.runWriteFile.contents
887
695
  }
888
- const content = await readFile(filepath, "utf-8");
889
- const allLines = content.split("\n");
890
- const offset = Math.max((args.offset ?? 1) - 1, 0);
891
- const limit = args.limit ?? 2e3;
892
- const slice = allLines.slice(offset, offset + limit);
893
- const numbered = slice.map((line, i) => `${offset + i + 1}: ${line}`);
894
- return { response: numbered.join("\n") };
895
- } catch (err) {
896
- return { response: "", error: err instanceof Error ? err.message : String(err) };
897
- }
696
+ };
898
697
  }
899
- async #readMultiple(filepaths) {
900
- const results = [];
901
- for (const fp of filepaths) {
902
- const result = await this.#read({ filePath: fp });
903
- if (result.error) {
904
- results.push(`--- ${fp} ---
905
- ERROR: ${result.error}`);
906
- } else {
907
- results.push(`--- ${fp} ---
908
- ${result.response}`);
698
+ if (action.runEditFile) {
699
+ return {
700
+ requestId,
701
+ toolName: "edit_file",
702
+ args: {
703
+ file_path: action.runEditFile.filepath,
704
+ old_str: action.runEditFile.oldString,
705
+ new_str: action.runEditFile.newString
909
706
  }
910
- }
911
- return { response: results.join("\n\n") };
707
+ };
912
708
  }
913
- async #edit(args) {
914
- try {
915
- const filepath = resolve(this.#cwd, args.filePath);
916
- let content = await readFile(filepath, "utf-8");
917
- if (args.replaceAll) {
918
- if (!content.includes(args.oldString)) {
919
- return { response: "", error: "oldString not found in content" };
920
- }
921
- content = content.replaceAll(args.oldString, args.newString);
922
- } else {
923
- const idx = content.indexOf(args.oldString);
924
- if (idx === -1) {
925
- return { response: "", error: "oldString not found in content" };
926
- }
927
- const secondIdx = content.indexOf(args.oldString, idx + 1);
928
- if (secondIdx !== -1) {
929
- return { response: "", error: "Found multiple matches for oldString. Provide more surrounding lines to identify the correct match." };
930
- }
931
- content = content.slice(0, idx) + args.newString + content.slice(idx + args.oldString.length);
932
- }
933
- await writeFile(filepath, content, "utf-8");
934
- return { response: `Successfully edited ${args.filePath}` };
935
- } catch (err) {
936
- return { response: "", error: err instanceof Error ? err.message : String(err) };
937
- }
709
+ if (action.findFiles) {
710
+ return {
711
+ requestId,
712
+ toolName: "find_files",
713
+ args: { name_pattern: action.findFiles.name_pattern }
714
+ };
938
715
  }
939
- async #write(args) {
940
- try {
941
- const filepath = resolve(this.#cwd, args.filePath);
942
- await fsMkdir(dirname(filepath), { recursive: true });
943
- await writeFile(filepath, args.content, "utf-8");
944
- return { response: `Successfully wrote ${args.filePath}` };
945
- } catch (err) {
946
- return { response: "", error: err instanceof Error ? err.message : String(err) };
947
- }
716
+ if (action.listDirectory) {
717
+ return {
718
+ requestId,
719
+ toolName: "list_dir",
720
+ args: { directory: action.listDirectory.directory }
721
+ };
948
722
  }
949
- async #glob(args) {
950
- const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
951
- return new Promise((res) => {
952
- const command = process.platform === "win32" ? `dir /s /b "${args.pattern}"` : `find . -path "./${args.pattern}" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -200`;
953
- exec(command, { cwd, timeout: 3e4 }, (error, stdout) => {
954
- if (error && !stdout) {
955
- exec(`ls -1 ${args.pattern} 2>/dev/null | head -200`, { cwd, timeout: 3e4 }, (_err, out) => {
956
- res({ response: out.trim() || "No matches found" });
957
- });
958
- return;
959
- }
960
- const lines = stdout.trim().split("\n").filter(Boolean);
961
- res({ response: lines.length > 0 ? lines.join("\n") : "No matches found" });
962
- });
963
- });
723
+ if (action.grep) {
724
+ const args = { pattern: action.grep.pattern };
725
+ if (action.grep.search_directory) args.search_directory = action.grep.search_directory;
726
+ if (action.grep.case_insensitive !== void 0) args.case_insensitive = action.grep.case_insensitive;
727
+ return { requestId, toolName: "grep", args };
964
728
  }
965
- async #grep(args) {
966
- const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
967
- const caseFlag = args.caseInsensitive ? "--ignore-case" : "";
968
- return new Promise((res) => {
969
- const includeFlag = args.include ? `--glob '${args.include}'` : "";
970
- const escapedPattern = args.pattern.replace(/'/g, "'\\''");
971
- const rgCommand = `rg --line-number --no-heading ${caseFlag} ${includeFlag} '${escapedPattern}' . 2>/dev/null | head -500`;
972
- exec(rgCommand, { cwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (_error, stdout) => {
973
- if (stdout.trim()) {
974
- res({ response: stdout.trim() });
975
- return;
976
- }
977
- const grepCaseFlag = args.caseInsensitive ? "-i" : "";
978
- const grepInclude = args.include ? `--include='${args.include}'` : "";
979
- const grepCommand = `grep -rn ${grepCaseFlag} ${grepInclude} '${escapedPattern}' . 2>/dev/null | head -500`;
980
- exec(grepCommand, { cwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (_err, out) => {
981
- res({ response: out.trim() || "No matches found" });
982
- });
983
- });
984
- });
729
+ if (action.mkdir) {
730
+ return {
731
+ requestId,
732
+ toolName: "mkdir",
733
+ args: { directory_path: action.mkdir.directory_path }
734
+ };
985
735
  }
986
- async #mkdir(directoryPath) {
987
- try {
988
- const filepath = resolve(this.#cwd, directoryPath);
989
- await fsMkdir(filepath, { recursive: true });
990
- return { response: `Created directory ${directoryPath}` };
991
- } catch (err) {
992
- return { response: "", error: err instanceof Error ? err.message : String(err) };
993
- }
736
+ if (action.runShellCommand) {
737
+ return {
738
+ requestId,
739
+ toolName: "shell_command",
740
+ args: { command: action.runShellCommand.command }
741
+ };
994
742
  }
995
- async #httpRequest(args) {
996
- if (!this.#client) {
997
- return { response: "", error: "HTTP requests require GitLab client credentials" };
998
- }
999
- try {
1000
- const url = `${this.#client.instanceUrl}/api/v4/${args.path}`;
1001
- const headers = {
1002
- authorization: `Bearer ${this.#client.token}`
1003
- };
1004
- if (args.body) {
1005
- headers["content-type"] = "application/json";
743
+ if (action.runCommand) {
744
+ return {
745
+ requestId,
746
+ toolName: "run_command",
747
+ args: {
748
+ program: action.runCommand.program,
749
+ flags: action.runCommand.flags,
750
+ arguments: action.runCommand.arguments
1006
751
  }
1007
- const response = await fetch(url, {
1008
- method: args.method,
1009
- headers,
1010
- body: args.body ?? void 0
1011
- });
1012
- const text2 = await response.text();
1013
- if (!response.ok) {
1014
- return { response: text2, error: `HTTP ${response.status}: ${response.statusText}` };
752
+ };
753
+ }
754
+ if (action.runGitCommand) {
755
+ return {
756
+ requestId,
757
+ toolName: "run_git_command",
758
+ args: {
759
+ repository_url: action.runGitCommand.repository_url ?? "",
760
+ command: action.runGitCommand.command,
761
+ args: action.runGitCommand.arguments
1015
762
  }
1016
- return { response: text2 };
1017
- } catch (err) {
1018
- return { response: "", error: err instanceof Error ? err.message : String(err) };
1019
- }
763
+ };
1020
764
  }
1021
- };
765
+ if (action.runHTTPRequest) {
766
+ return {
767
+ requestId,
768
+ toolName: "gitlab_api_request",
769
+ args: {
770
+ method: action.runHTTPRequest.method,
771
+ path: action.runHTTPRequest.path,
772
+ body: action.runHTTPRequest.body
773
+ }
774
+ };
775
+ }
776
+ return null;
777
+ }
1022
778
 
1023
779
  // src/workflow/session.ts
1024
780
  var WorkflowSession = class {
@@ -1031,7 +787,6 @@ var WorkflowSession = class {
1031
787
  #rootNamespaceId;
1032
788
  #checkpoint = createCheckpointState();
1033
789
  #toolsConfig;
1034
- #toolExecutor;
1035
790
  #socket;
1036
791
  #queue;
1037
792
  #startRequestSent = false;
@@ -1040,7 +795,6 @@ var WorkflowSession = class {
1040
795
  this.#tokenService = new WorkflowTokenService(client);
1041
796
  this.#modelId = modelId;
1042
797
  this.#cwd = cwd;
1043
- this.#toolExecutor = new ToolExecutor(cwd, client);
1044
798
  }
1045
799
  /**
1046
800
  * Opt-in: override the server-side system prompt and/or register MCP tools.
@@ -1171,24 +925,18 @@ var WorkflowSession = class {
1171
925
  }
1172
926
  return;
1173
927
  }
1174
- if (action.requestID) {
1175
- console.error(`[duo-workflow] ws tool action: keys=${JSON.stringify(Object.keys(action).filter((k) => k !== "requestID"))}`);
1176
- this.#executeStandaloneAction(action);
928
+ const toolAction = action;
929
+ const mapped = mapActionToToolRequest(toolAction);
930
+ if (mapped) {
931
+ console.error(`[duo-workflow] ws tool request: ${mapped.toolName} requestId=${mapped.requestId}`);
932
+ queue.push({
933
+ type: "tool-request",
934
+ requestId: mapped.requestId,
935
+ toolName: mapped.toolName,
936
+ args: mapped.args
937
+ });
1177
938
  }
1178
939
  }
1179
- async #executeStandaloneAction(action) {
1180
- if (!action.requestID || !this.#socket) return;
1181
- const result = await this.#toolExecutor.executeAction(action);
1182
- this.#socket.send({
1183
- actionResponse: {
1184
- requestID: action.requestID,
1185
- plainTextResponse: {
1186
- response: result.response,
1187
- error: result.error ?? ""
1188
- }
1189
- }
1190
- });
1191
- }
1192
940
  // ---------------------------------------------------------------------------
1193
941
  // Private: connection management
1194
942
  // ---------------------------------------------------------------------------
@@ -1270,21 +1018,21 @@ function extractToolResults(prompt) {
1270
1018
  const toolName = String(p.toolName ?? "");
1271
1019
  if (!toolCallId) continue;
1272
1020
  const { output, error } = parseToolResultOutput(p);
1273
- const finalError = error ?? asString2(p.error) ?? asString2(p.errorText);
1021
+ const finalError = error ?? asString(p.error) ?? asString(p.errorText);
1274
1022
  results.push({ toolCallId, toolName, output, error: finalError });
1275
1023
  }
1276
1024
  if (p.type === "tool-error") {
1277
1025
  const toolCallId = String(p.toolCallId ?? "");
1278
1026
  const toolName = String(p.toolName ?? "");
1279
1027
  const errorValue = p.error ?? p.errorText ?? p.message;
1280
- const error = asString2(errorValue) ?? String(errorValue ?? "");
1028
+ const error = asString(errorValue) ?? String(errorValue ?? "");
1281
1029
  results.push({ toolCallId, toolName, output: "", error });
1282
1030
  }
1283
1031
  }
1284
1032
  }
1285
1033
  return results;
1286
1034
  }
1287
- function asString2(value) {
1035
+ function asString(value) {
1288
1036
  return typeof value === "string" ? value : void 0;
1289
1037
  }
1290
1038
  function parseToolResultOutput(part) {
@@ -1309,7 +1057,7 @@ function parseToolResultOutput(part) {
1309
1057
  }
1310
1058
  if (resultField !== void 0) {
1311
1059
  const output = typeof resultField === "string" ? resultField : JSON.stringify(resultField);
1312
- const error = isPlainObject(resultField) ? asString2(resultField.error) : void 0;
1060
+ const error = isPlainObject(resultField) ? asString(resultField.error) : void 0;
1313
1061
  return { output, error };
1314
1062
  }
1315
1063
  return { output: "" };
@@ -1377,14 +1125,14 @@ function isPlainObject(value) {
1377
1125
  function mapDuoToolRequest(toolName, args) {
1378
1126
  switch (toolName) {
1379
1127
  case "list_dir": {
1380
- const directory = asString3(args.directory) ?? ".";
1128
+ const directory = asString2(args.directory) ?? ".";
1381
1129
  return {
1382
1130
  toolName: "bash",
1383
1131
  args: { command: `ls -la ${shellQuote(directory)}`, description: "List directory contents", workdir: "." }
1384
1132
  };
1385
1133
  }
1386
1134
  case "read_file": {
1387
- const filePath = asString3(args.file_path) ?? asString3(args.filepath) ?? asString3(args.filePath) ?? asString3(args.path);
1135
+ const filePath = asString2(args.file_path) ?? asString2(args.filepath) ?? asString2(args.filePath) ?? asString2(args.path);
1388
1136
  if (!filePath) return { toolName, args };
1389
1137
  const mapped = { filePath };
1390
1138
  if (typeof args.offset === "number") mapped.offset = args.offset;
@@ -1392,32 +1140,32 @@ function mapDuoToolRequest(toolName, args) {
1392
1140
  return { toolName: "read", args: mapped };
1393
1141
  }
1394
1142
  case "read_files": {
1395
- const filePaths = asStringArray2(args.file_paths);
1143
+ const filePaths = asStringArray(args.file_paths);
1396
1144
  if (filePaths.length === 0) return { toolName, args };
1397
1145
  return filePaths.map((fp) => ({ toolName: "read", args: { filePath: fp } }));
1398
1146
  }
1399
1147
  case "create_file_with_contents": {
1400
- const filePath = asString3(args.file_path);
1401
- const content = asString3(args.contents);
1148
+ const filePath = asString2(args.file_path);
1149
+ const content = asString2(args.contents);
1402
1150
  if (!filePath || content === void 0) return { toolName, args };
1403
1151
  return { toolName: "write", args: { filePath, content } };
1404
1152
  }
1405
1153
  case "edit_file": {
1406
- const filePath = asString3(args.file_path);
1407
- const oldString = asString3(args.old_str);
1408
- const newString = asString3(args.new_str);
1154
+ const filePath = asString2(args.file_path);
1155
+ const oldString = asString2(args.old_str);
1156
+ const newString = asString2(args.new_str);
1409
1157
  if (!filePath || oldString === void 0 || newString === void 0) return { toolName, args };
1410
1158
  return { toolName: "edit", args: { filePath, oldString, newString } };
1411
1159
  }
1412
1160
  case "find_files": {
1413
- const pattern = asString3(args.name_pattern);
1161
+ const pattern = asString2(args.name_pattern);
1414
1162
  if (!pattern) return { toolName, args };
1415
1163
  return { toolName: "glob", args: { pattern } };
1416
1164
  }
1417
1165
  case "grep": {
1418
- const pattern = asString3(args.pattern);
1166
+ const pattern = asString2(args.pattern);
1419
1167
  if (!pattern) return { toolName, args };
1420
- const searchDir = asString3(args.search_directory);
1168
+ const searchDir = asString2(args.search_directory);
1421
1169
  const caseInsensitive = Boolean(args.case_insensitive);
1422
1170
  const normalizedPattern = caseInsensitive && !pattern.startsWith("(?i)") ? `(?i)${pattern}` : pattern;
1423
1171
  const mapped = { pattern: normalizedPattern };
@@ -1425,43 +1173,67 @@ function mapDuoToolRequest(toolName, args) {
1425
1173
  return { toolName: "grep", args: mapped };
1426
1174
  }
1427
1175
  case "mkdir": {
1428
- const directory = asString3(args.directory_path);
1176
+ const directory = asString2(args.directory_path);
1429
1177
  if (!directory) return { toolName, args };
1430
1178
  return { toolName: "bash", args: { command: `mkdir -p ${shellQuote(directory)}`, description: "Create directory", workdir: "." } };
1431
1179
  }
1432
1180
  case "shell_command": {
1433
- const command = asString3(args.command);
1181
+ const command = asString2(args.command);
1434
1182
  if (!command) return { toolName, args };
1435
1183
  return { toolName: "bash", args: { command, description: "Run shell command", workdir: "." } };
1436
1184
  }
1437
1185
  case "run_command": {
1438
- const program = asString3(args.program);
1186
+ const program = asString2(args.program);
1439
1187
  if (program) {
1440
1188
  const parts = [shellQuote(program)];
1441
1189
  if (Array.isArray(args.flags)) parts.push(...args.flags.map((f) => shellQuote(String(f))));
1442
1190
  if (Array.isArray(args.arguments)) parts.push(...args.arguments.map((a) => shellQuote(String(a))));
1443
1191
  return { toolName: "bash", args: { command: parts.join(" "), description: "Run command", workdir: "." } };
1444
1192
  }
1445
- const command = asString3(args.command);
1193
+ const command = asString2(args.command);
1446
1194
  if (!command) return { toolName, args };
1447
1195
  return { toolName: "bash", args: { command, description: "Run command", workdir: "." } };
1448
1196
  }
1449
1197
  case "run_git_command": {
1450
- const command = asString3(args.command);
1198
+ const command = asString2(args.command);
1451
1199
  if (!command) return { toolName, args };
1452
1200
  const rawArgs = args.args;
1453
- const extraArgs = Array.isArray(rawArgs) ? rawArgs.map((v) => shellQuote(String(v))).join(" ") : asString3(rawArgs);
1201
+ const extraArgs = Array.isArray(rawArgs) ? rawArgs.map((v) => shellQuote(String(v))).join(" ") : asString2(rawArgs);
1454
1202
  const gitCmd = extraArgs ? `git ${shellQuote(command)} ${extraArgs}` : `git ${shellQuote(command)}`;
1455
1203
  return { toolName: "bash", args: { command: gitCmd, description: "Run git command", workdir: "." } };
1456
1204
  }
1205
+ case "gitlab_api_request": {
1206
+ const method = asString2(args.method) ?? "GET";
1207
+ const apiPath = asString2(args.path);
1208
+ if (!apiPath) return { toolName, args };
1209
+ const body = asString2(args.body);
1210
+ const curlParts = [
1211
+ "curl",
1212
+ "-s",
1213
+ "-X",
1214
+ method,
1215
+ "-H",
1216
+ "'Authorization: Bearer $GITLAB_TOKEN'",
1217
+ "-H",
1218
+ "'Content-Type: application/json'"
1219
+ ];
1220
+ if (body) {
1221
+ curlParts.push("-d", shellQuote(body));
1222
+ }
1223
+ curlParts.push(shellQuote(`$GITLAB_INSTANCE_URL/api/v4/${apiPath}`));
1224
+ return {
1225
+ toolName: "bash",
1226
+ args: { command: curlParts.join(" "), description: `GitLab API: ${method} ${apiPath}`, workdir: "." }
1227
+ };
1228
+ }
1457
1229
  default:
1458
1230
  return { toolName, args };
1459
1231
  }
1460
1232
  }
1461
- function asString3(value) {
1233
+ function asString2(value) {
1462
1234
  return typeof value === "string" ? value : void 0;
1463
1235
  }
1464
- function asStringArray2(value) {
1236
+ function asStringArray(value) {
1465
1237
  if (!Array.isArray(value)) return [];
1466
1238
  return value.filter((v) => typeof v === "string");
1467
1239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitlab-duo-agentic",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "OpenCode plugin and provider for GitLab Duo Agentic workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",