opencode-gitlab-duo-agentic 0.2.2 → 0.2.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.
- package/dist/index.js +159 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -391,10 +391,10 @@ function isGitLabProvider(model) {
|
|
|
391
391
|
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
392
392
|
|
|
393
393
|
// src/provider/duo-workflow-model.ts
|
|
394
|
-
import { randomUUID as
|
|
394
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
395
395
|
|
|
396
396
|
// src/workflow/session.ts
|
|
397
|
-
import { randomUUID } from "crypto";
|
|
397
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
398
398
|
|
|
399
399
|
// src/utils/async-queue.ts
|
|
400
400
|
var AsyncQueue = class {
|
|
@@ -416,9 +416,11 @@ var AsyncQueue = class {
|
|
|
416
416
|
};
|
|
417
417
|
|
|
418
418
|
// src/workflow/checkpoint.ts
|
|
419
|
+
import { randomUUID } from "crypto";
|
|
419
420
|
function createCheckpointState() {
|
|
420
421
|
return {
|
|
421
|
-
uiChatLog: []
|
|
422
|
+
uiChatLog: [],
|
|
423
|
+
processedRequestIndices: /* @__PURE__ */ new Set()
|
|
422
424
|
};
|
|
423
425
|
}
|
|
424
426
|
function extractAgentTextDeltas(checkpoint, state) {
|
|
@@ -443,6 +445,23 @@ function extractAgentTextDeltas(checkpoint, state) {
|
|
|
443
445
|
state.uiChatLog = next;
|
|
444
446
|
return out;
|
|
445
447
|
}
|
|
448
|
+
function extractToolRequests(checkpoint, state) {
|
|
449
|
+
const next = parseCheckpoint(checkpoint);
|
|
450
|
+
const requests = [];
|
|
451
|
+
for (let i = 0; i < next.length; i++) {
|
|
452
|
+
const item = next[i];
|
|
453
|
+
if (item.message_type !== "request") continue;
|
|
454
|
+
if (!item.tool_info) continue;
|
|
455
|
+
if (state.processedRequestIndices.has(i)) continue;
|
|
456
|
+
state.processedRequestIndices.add(i);
|
|
457
|
+
requests.push({
|
|
458
|
+
requestId: item.correlation_id ?? randomUUID(),
|
|
459
|
+
toolName: item.tool_info.name,
|
|
460
|
+
args: item.tool_info.args ?? {}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
return requests;
|
|
464
|
+
}
|
|
446
465
|
function parseCheckpoint(raw) {
|
|
447
466
|
if (!raw) return [];
|
|
448
467
|
try {
|
|
@@ -624,6 +643,16 @@ function decodeSocketMessage(data) {
|
|
|
624
643
|
import { readFile, readdir, writeFile, mkdir as fsMkdir, stat } from "fs/promises";
|
|
625
644
|
import { exec } from "child_process";
|
|
626
645
|
import { resolve, dirname } from "path";
|
|
646
|
+
function asString(value) {
|
|
647
|
+
return typeof value === "string" ? value : void 0;
|
|
648
|
+
}
|
|
649
|
+
function asNumber(value) {
|
|
650
|
+
return typeof value === "number" ? value : void 0;
|
|
651
|
+
}
|
|
652
|
+
function asStringArray(value) {
|
|
653
|
+
if (!Array.isArray(value)) return [];
|
|
654
|
+
return value.filter((v) => typeof v === "string");
|
|
655
|
+
}
|
|
627
656
|
var ToolExecutor = class {
|
|
628
657
|
#cwd;
|
|
629
658
|
#client;
|
|
@@ -631,9 +660,104 @@ var ToolExecutor = class {
|
|
|
631
660
|
this.#cwd = cwd;
|
|
632
661
|
this.#client = client;
|
|
633
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* Execute a tool request using Duo tool names and arg formats.
|
|
665
|
+
* This handles checkpoint-based tool requests where tool_info contains
|
|
666
|
+
* Duo-native names (read_file, edit_file, etc.) and Duo-native arg keys
|
|
667
|
+
* (file_path, old_str, new_str, etc.).
|
|
668
|
+
*/
|
|
669
|
+
async executeDuoTool(toolName, args) {
|
|
670
|
+
try {
|
|
671
|
+
switch (toolName) {
|
|
672
|
+
case "read_file": {
|
|
673
|
+
const filePath = asString(args.file_path) ?? asString(args.filepath) ?? asString(args.filePath) ?? asString(args.path);
|
|
674
|
+
if (!filePath) return { response: "", error: "read_file: missing file_path" };
|
|
675
|
+
return this.#read({ filePath, offset: asNumber(args.offset), limit: asNumber(args.limit) });
|
|
676
|
+
}
|
|
677
|
+
case "read_files": {
|
|
678
|
+
const paths = asStringArray(args.file_paths);
|
|
679
|
+
if (paths.length === 0) return { response: "", error: "read_files: missing file_paths" };
|
|
680
|
+
return this.#readMultiple(paths);
|
|
681
|
+
}
|
|
682
|
+
case "create_file_with_contents": {
|
|
683
|
+
const filePath = asString(args.file_path);
|
|
684
|
+
const content = asString(args.contents);
|
|
685
|
+
if (!filePath || content === void 0) return { response: "", error: "create_file: missing file_path or contents" };
|
|
686
|
+
return this.#write({ filePath, content });
|
|
687
|
+
}
|
|
688
|
+
case "edit_file": {
|
|
689
|
+
const filePath = asString(args.file_path);
|
|
690
|
+
const oldString = asString(args.old_str);
|
|
691
|
+
const newString = asString(args.new_str);
|
|
692
|
+
if (!filePath || oldString === void 0 || newString === void 0) {
|
|
693
|
+
return { response: "", error: "edit_file: missing file_path, old_str or new_str" };
|
|
694
|
+
}
|
|
695
|
+
return this.#edit({ filePath, oldString, newString });
|
|
696
|
+
}
|
|
697
|
+
case "list_dir": {
|
|
698
|
+
const directory = asString(args.directory) ?? ".";
|
|
699
|
+
return this.#read({ filePath: directory });
|
|
700
|
+
}
|
|
701
|
+
case "find_files": {
|
|
702
|
+
const pattern = asString(args.name_pattern);
|
|
703
|
+
if (!pattern) return { response: "", error: "find_files: missing name_pattern" };
|
|
704
|
+
return this.#glob({ pattern });
|
|
705
|
+
}
|
|
706
|
+
case "grep": {
|
|
707
|
+
const pattern = asString(args.pattern);
|
|
708
|
+
if (!pattern) return { response: "", error: "grep: missing pattern" };
|
|
709
|
+
return this.#grep({
|
|
710
|
+
pattern,
|
|
711
|
+
path: asString(args.search_directory),
|
|
712
|
+
caseInsensitive: Boolean(args.case_insensitive)
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
case "mkdir": {
|
|
716
|
+
const dir = asString(args.directory_path);
|
|
717
|
+
if (!dir) return { response: "", error: "mkdir: missing directory_path" };
|
|
718
|
+
return this.#mkdir(dir);
|
|
719
|
+
}
|
|
720
|
+
case "shell_command": {
|
|
721
|
+
const command = asString(args.command);
|
|
722
|
+
if (!command) return { response: "", error: "shell_command: missing command" };
|
|
723
|
+
return this.#bash({ command });
|
|
724
|
+
}
|
|
725
|
+
case "run_command": {
|
|
726
|
+
const program = asString(args.program);
|
|
727
|
+
if (!program) return { response: "", error: "run_command: missing program" };
|
|
728
|
+
const parts = [program];
|
|
729
|
+
const flags = args.flags;
|
|
730
|
+
if (Array.isArray(flags)) parts.push(...flags.map(String));
|
|
731
|
+
const cmdArgs = args.arguments;
|
|
732
|
+
if (Array.isArray(cmdArgs)) parts.push(...cmdArgs.map(String));
|
|
733
|
+
return this.#bash({ command: parts.join(" ") });
|
|
734
|
+
}
|
|
735
|
+
case "run_git_command": {
|
|
736
|
+
const command = asString(args.command);
|
|
737
|
+
if (!command) return { response: "", error: "run_git_command: missing command" };
|
|
738
|
+
const extra = Array.isArray(args.args) ? args.args.map(String).join(" ") : asString(args.args);
|
|
739
|
+
const gitCmd = extra ? `git ${command} ${extra}` : `git ${command}`;
|
|
740
|
+
return this.#bash({ command: gitCmd });
|
|
741
|
+
}
|
|
742
|
+
case "gitlab_api_request": {
|
|
743
|
+
const method = asString(args.method) ?? "GET";
|
|
744
|
+
const path3 = asString(args.path);
|
|
745
|
+
if (!path3) return { response: "", error: "gitlab_api_request: missing path" };
|
|
746
|
+
return this.#httpRequest({ method, path: path3, body: asString(args.body) });
|
|
747
|
+
}
|
|
748
|
+
default: {
|
|
749
|
+
console.error(`[tool-executor] unknown Duo tool: ${toolName}, args: ${JSON.stringify(args).slice(0, 300)}`);
|
|
750
|
+
return { response: "", error: `Unknown Duo tool: ${toolName}` };
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
} catch (err) {
|
|
754
|
+
console.error(`[tool-executor] executeDuoTool error:`, err);
|
|
755
|
+
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
756
|
+
}
|
|
757
|
+
}
|
|
634
758
|
/**
|
|
635
759
|
* Route a Duo WorkflowToolAction to the appropriate local handler.
|
|
636
|
-
* This
|
|
760
|
+
* This handles standalone WebSocket actions (non-checkpoint path).
|
|
637
761
|
*/
|
|
638
762
|
async executeAction(action) {
|
|
639
763
|
try {
|
|
@@ -695,8 +819,11 @@ var ToolExecutor = class {
|
|
|
695
819
|
if (action.runHTTPRequest) {
|
|
696
820
|
return this.#httpRequest(action.runHTTPRequest);
|
|
697
821
|
}
|
|
698
|
-
|
|
822
|
+
const keys = Object.keys(action).filter((k) => k !== "requestID");
|
|
823
|
+
console.error(`[tool-executor] unhandled action, keys: ${JSON.stringify(keys)}, raw: ${JSON.stringify(action).slice(0, 500)}`);
|
|
824
|
+
return { response: "", error: `Unknown tool action (keys: ${keys.join(", ")})` };
|
|
699
825
|
} catch (err) {
|
|
826
|
+
console.error(`[tool-executor] execution error:`, err);
|
|
700
827
|
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
701
828
|
}
|
|
702
829
|
}
|
|
@@ -946,7 +1073,7 @@ var WorkflowSession = class {
|
|
|
946
1073
|
await socket.connect(url, {
|
|
947
1074
|
authorization: `Bearer ${this.#client.token}`,
|
|
948
1075
|
origin: new URL(this.#client.instanceUrl).origin,
|
|
949
|
-
"x-request-id":
|
|
1076
|
+
"x-request-id": randomUUID2(),
|
|
950
1077
|
"x-gitlab-client-type": "node-websocket"
|
|
951
1078
|
});
|
|
952
1079
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -961,7 +1088,7 @@ var WorkflowSession = class {
|
|
|
961
1088
|
workflowMetadata: JSON.stringify({
|
|
962
1089
|
extended_logging: access?.workflow_metadata?.extended_logging ?? false
|
|
963
1090
|
}),
|
|
964
|
-
clientCapabilities: [],
|
|
1091
|
+
clientCapabilities: ["shell_command"],
|
|
965
1092
|
mcpTools,
|
|
966
1093
|
additional_context: [],
|
|
967
1094
|
preapproved_tools: preapprovedTools,
|
|
@@ -981,20 +1108,42 @@ var WorkflowSession = class {
|
|
|
981
1108
|
throw new Error(`workflow websocket closed abnormally (${event.code}): ${event.reason}`);
|
|
982
1109
|
}
|
|
983
1110
|
if (isCheckpointAction(event.action)) {
|
|
984
|
-
const
|
|
1111
|
+
const ckpt = event.action.newCheckpoint.checkpoint;
|
|
1112
|
+
const deltas = extractAgentTextDeltas(ckpt, this.#checkpoint);
|
|
985
1113
|
for (const delta of deltas) {
|
|
986
1114
|
yield {
|
|
987
1115
|
type: "text-delta",
|
|
988
1116
|
value: delta
|
|
989
1117
|
};
|
|
990
1118
|
}
|
|
1119
|
+
const toolRequests = extractToolRequests(ckpt, this.#checkpoint);
|
|
1120
|
+
for (const req of toolRequests) {
|
|
1121
|
+
console.error(`[duo-workflow] checkpoint tool request: ${req.toolName} requestId=${req.requestId} args=${JSON.stringify(req.args).slice(0, 200)}`);
|
|
1122
|
+
const result2 = await this.#toolExecutor.executeDuoTool(req.toolName, req.args);
|
|
1123
|
+
console.error(`[duo-workflow] checkpoint tool result: ${result2.response.length} bytes, error=${result2.error ?? "none"}`);
|
|
1124
|
+
socket.send({
|
|
1125
|
+
actionResponse: {
|
|
1126
|
+
requestID: req.requestId,
|
|
1127
|
+
plainTextResponse: {
|
|
1128
|
+
response: result2.response,
|
|
1129
|
+
error: result2.error ?? ""
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
991
1134
|
if (isTurnComplete(event.action.newCheckpoint.status)) {
|
|
992
1135
|
socket.close();
|
|
993
1136
|
}
|
|
994
1137
|
continue;
|
|
995
1138
|
}
|
|
996
|
-
|
|
1139
|
+
const actionKeys = Object.keys(event.action).filter((k) => k !== "requestID");
|
|
1140
|
+
console.error(`[duo-workflow] tool action received: keys=${JSON.stringify(actionKeys)} requestID=${event.action.requestID ?? "MISSING"}`);
|
|
1141
|
+
if (!event.action.requestID) {
|
|
1142
|
+
console.error("[duo-workflow] skipping action without requestID");
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
997
1145
|
const result = await this.#toolExecutor.executeAction(event.action);
|
|
1146
|
+
console.error(`[duo-workflow] tool result: response=${result.response.length} bytes, error=${result.error ?? "none"}`);
|
|
998
1147
|
socket.send({
|
|
999
1148
|
actionResponse: {
|
|
1000
1149
|
requestID: event.action.requestID,
|
|
@@ -1150,7 +1299,7 @@ var DuoWorkflowModel = class {
|
|
|
1150
1299
|
const goal = extractGoal(options.prompt);
|
|
1151
1300
|
if (!goal) throw new Error("missing user message content");
|
|
1152
1301
|
const session = this.#resolveSession(sessionID);
|
|
1153
|
-
const textId =
|
|
1302
|
+
const textId = randomUUID3();
|
|
1154
1303
|
return {
|
|
1155
1304
|
stream: new ReadableStream({
|
|
1156
1305
|
start: async (controller) => {
|