opencode-gitlab-duo-agentic 0.2.4 → 0.2.6
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 +650 -167
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -400,7 +400,9 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
400
400
|
var AsyncQueue = class {
|
|
401
401
|
#values = [];
|
|
402
402
|
#waiters = [];
|
|
403
|
+
#closed = false;
|
|
403
404
|
push(value) {
|
|
405
|
+
if (this.#closed) return;
|
|
404
406
|
const waiter = this.#waiters.shift();
|
|
405
407
|
if (waiter) {
|
|
406
408
|
waiter(value);
|
|
@@ -408,11 +410,20 @@ var AsyncQueue = class {
|
|
|
408
410
|
}
|
|
409
411
|
this.#values.push(value);
|
|
410
412
|
}
|
|
413
|
+
/** Returns null when closed and no buffered values remain. */
|
|
411
414
|
shift() {
|
|
412
415
|
const value = this.#values.shift();
|
|
413
416
|
if (value !== void 0) return Promise.resolve(value);
|
|
417
|
+
if (this.#closed) return Promise.resolve(null);
|
|
414
418
|
return new Promise((resolve2) => this.#waiters.push(resolve2));
|
|
415
419
|
}
|
|
420
|
+
close() {
|
|
421
|
+
this.#closed = true;
|
|
422
|
+
for (const waiter of this.#waiters) {
|
|
423
|
+
waiter(null);
|
|
424
|
+
}
|
|
425
|
+
this.#waiters = [];
|
|
426
|
+
}
|
|
416
427
|
};
|
|
417
428
|
|
|
418
429
|
// src/workflow/checkpoint.ts
|
|
@@ -1021,8 +1032,9 @@ var WorkflowSession = class {
|
|
|
1021
1032
|
#checkpoint = createCheckpointState();
|
|
1022
1033
|
#toolsConfig;
|
|
1023
1034
|
#toolExecutor;
|
|
1024
|
-
|
|
1025
|
-
#
|
|
1035
|
+
#socket;
|
|
1036
|
+
#queue;
|
|
1037
|
+
#startRequestSent = false;
|
|
1026
1038
|
constructor(client, modelId, cwd) {
|
|
1027
1039
|
this.#client = client;
|
|
1028
1040
|
this.#tokenService = new WorkflowTokenService(client);
|
|
@@ -1032,7 +1044,6 @@ var WorkflowSession = class {
|
|
|
1032
1044
|
}
|
|
1033
1045
|
/**
|
|
1034
1046
|
* Opt-in: override the server-side system prompt and/or register MCP tools.
|
|
1035
|
-
* When not called, the server uses its default prompt and built-in tools.
|
|
1036
1047
|
*/
|
|
1037
1048
|
setToolsConfig(config) {
|
|
1038
1049
|
this.#toolsConfig = config;
|
|
@@ -1040,126 +1051,153 @@ var WorkflowSession = class {
|
|
|
1040
1051
|
get workflowId() {
|
|
1041
1052
|
return this.#workflowId;
|
|
1042
1053
|
}
|
|
1054
|
+
get hasStarted() {
|
|
1055
|
+
return this.#startRequestSent;
|
|
1056
|
+
}
|
|
1043
1057
|
reset() {
|
|
1044
1058
|
this.#workflowId = void 0;
|
|
1045
1059
|
this.#checkpoint = createCheckpointState();
|
|
1046
1060
|
this.#tokenService.clear();
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1061
|
+
this.#closeConnection();
|
|
1062
|
+
this.#startRequestSent = false;
|
|
1063
|
+
}
|
|
1064
|
+
// ---------------------------------------------------------------------------
|
|
1065
|
+
// Connection lifecycle (persistent)
|
|
1066
|
+
// ---------------------------------------------------------------------------
|
|
1067
|
+
async ensureConnected(goal) {
|
|
1068
|
+
if (this.#socket && this.#queue) return;
|
|
1069
|
+
if (!this.#workflowId) {
|
|
1070
|
+
this.#workflowId = await this.#createWorkflow(goal);
|
|
1071
|
+
}
|
|
1072
|
+
await this.#tokenService.get(this.#rootNamespaceId);
|
|
1073
|
+
this.#queue = new AsyncQueue();
|
|
1074
|
+
const queue = this.#queue;
|
|
1055
1075
|
const socket = new WorkflowWebSocketClient({
|
|
1056
|
-
action: (action) =>
|
|
1057
|
-
error: (error) => queue.push({ type: "error", error }),
|
|
1058
|
-
close: (
|
|
1076
|
+
action: (action) => this.#handleAction(action, queue),
|
|
1077
|
+
error: (error) => queue.push({ type: "error", message: error.message }),
|
|
1078
|
+
close: (_code, _reason) => queue.close()
|
|
1059
1079
|
});
|
|
1060
|
-
const
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
if (isCheckpointAction(event.action)) {
|
|
1111
|
-
const ckpt = event.action.newCheckpoint.checkpoint;
|
|
1112
|
-
const deltas = extractAgentTextDeltas(ckpt, this.#checkpoint);
|
|
1113
|
-
for (const delta of deltas) {
|
|
1114
|
-
yield {
|
|
1115
|
-
type: "text-delta",
|
|
1116
|
-
value: delta
|
|
1117
|
-
};
|
|
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
|
-
}
|
|
1134
|
-
if (isTurnComplete(event.action.newCheckpoint.status)) {
|
|
1135
|
-
socket.close();
|
|
1136
|
-
}
|
|
1137
|
-
continue;
|
|
1138
|
-
}
|
|
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;
|
|
1080
|
+
const url = buildWebSocketUrl(this.#client.instanceUrl, this.#modelId);
|
|
1081
|
+
await socket.connect(url, {
|
|
1082
|
+
authorization: `Bearer ${this.#client.token}`,
|
|
1083
|
+
origin: new URL(this.#client.instanceUrl).origin,
|
|
1084
|
+
"x-request-id": randomUUID2(),
|
|
1085
|
+
"x-gitlab-client-type": "node-websocket"
|
|
1086
|
+
});
|
|
1087
|
+
this.#socket = socket;
|
|
1088
|
+
}
|
|
1089
|
+
// ---------------------------------------------------------------------------
|
|
1090
|
+
// Messaging
|
|
1091
|
+
// ---------------------------------------------------------------------------
|
|
1092
|
+
sendStartRequest(goal, additionalContext = []) {
|
|
1093
|
+
if (!this.#socket || !this.#workflowId) throw new Error("Not connected");
|
|
1094
|
+
const mcpTools = this.#toolsConfig?.mcpTools ?? [];
|
|
1095
|
+
const preapprovedTools = mcpTools.map((t) => t.name);
|
|
1096
|
+
this.#socket.send({
|
|
1097
|
+
startRequest: {
|
|
1098
|
+
workflowID: this.#workflowId,
|
|
1099
|
+
clientVersion: WORKFLOW_CLIENT_VERSION,
|
|
1100
|
+
workflowDefinition: WORKFLOW_DEFINITION,
|
|
1101
|
+
goal,
|
|
1102
|
+
workflowMetadata: JSON.stringify({
|
|
1103
|
+
extended_logging: false
|
|
1104
|
+
}),
|
|
1105
|
+
clientCapabilities: ["shell_command"],
|
|
1106
|
+
mcpTools,
|
|
1107
|
+
additional_context: additionalContext,
|
|
1108
|
+
preapproved_tools: preapprovedTools,
|
|
1109
|
+
...this.#toolsConfig?.flowConfig ? {
|
|
1110
|
+
flowConfig: this.#toolsConfig.flowConfig,
|
|
1111
|
+
flowConfigSchemaVersion: this.#toolsConfig.flowConfigSchemaVersion ?? "v1"
|
|
1112
|
+
} : {}
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
this.#startRequestSent = true;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Send a tool result back to DWS on the existing connection.
|
|
1119
|
+
*/
|
|
1120
|
+
sendToolResult(requestId, output, error) {
|
|
1121
|
+
if (!this.#socket) throw new Error("Not connected");
|
|
1122
|
+
console.error(`[duo-workflow] sendToolResult: requestId=${requestId} output=${output.length} bytes, error=${error ?? "none"}`);
|
|
1123
|
+
this.#socket.send({
|
|
1124
|
+
actionResponse: {
|
|
1125
|
+
requestID: requestId,
|
|
1126
|
+
plainTextResponse: {
|
|
1127
|
+
response: output,
|
|
1128
|
+
error: error ?? ""
|
|
1144
1129
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Wait for the next event from the session.
|
|
1135
|
+
* Returns null when the stream is closed (turn complete or connection lost).
|
|
1136
|
+
*/
|
|
1137
|
+
async waitForEvent() {
|
|
1138
|
+
if (!this.#queue) return null;
|
|
1139
|
+
return this.#queue.shift();
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Send an abort signal to DWS and close the connection.
|
|
1143
|
+
*/
|
|
1144
|
+
abort() {
|
|
1145
|
+
this.#socket?.send({ stopWorkflow: { reason: "ABORTED" } });
|
|
1146
|
+
this.#closeConnection();
|
|
1147
|
+
}
|
|
1148
|
+
// ---------------------------------------------------------------------------
|
|
1149
|
+
// Private: action handling
|
|
1150
|
+
// ---------------------------------------------------------------------------
|
|
1151
|
+
#handleAction(action, queue) {
|
|
1152
|
+
if (isCheckpointAction(action)) {
|
|
1153
|
+
const ckpt = action.newCheckpoint.checkpoint;
|
|
1154
|
+
const deltas = extractAgentTextDeltas(ckpt, this.#checkpoint);
|
|
1155
|
+
for (const delta of deltas) {
|
|
1156
|
+
queue.push({ type: "text-delta", value: delta });
|
|
1157
|
+
}
|
|
1158
|
+
const toolRequests = extractToolRequests(ckpt, this.#checkpoint);
|
|
1159
|
+
for (const req of toolRequests) {
|
|
1160
|
+
console.error(`[duo-workflow] checkpoint tool request: ${req.toolName} requestId=${req.requestId}`);
|
|
1161
|
+
queue.push({
|
|
1162
|
+
type: "tool-request",
|
|
1163
|
+
requestId: req.requestId,
|
|
1164
|
+
toolName: req.toolName,
|
|
1165
|
+
args: req.args
|
|
1155
1166
|
});
|
|
1156
1167
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1168
|
+
if (isTurnComplete(action.newCheckpoint.status)) {
|
|
1169
|
+
queue.close();
|
|
1170
|
+
this.#closeConnection();
|
|
1171
|
+
}
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
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);
|
|
1161
1177
|
}
|
|
1162
1178
|
}
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
1193
|
+
// Private: connection management
|
|
1194
|
+
// ---------------------------------------------------------------------------
|
|
1195
|
+
#closeConnection() {
|
|
1196
|
+
this.#socket?.close();
|
|
1197
|
+
this.#socket = void 0;
|
|
1198
|
+
this.#queue = void 0;
|
|
1199
|
+
this.#startRequestSent = false;
|
|
1200
|
+
}
|
|
1163
1201
|
async #createWorkflow(goal) {
|
|
1164
1202
|
await this.#loadProjectContext();
|
|
1165
1203
|
const body = {
|
|
@@ -1167,9 +1205,7 @@ var WorkflowSession = class {
|
|
|
1167
1205
|
workflow_definition: WORKFLOW_DEFINITION,
|
|
1168
1206
|
environment: WORKFLOW_ENVIRONMENT,
|
|
1169
1207
|
allow_agent_to_request_user: true,
|
|
1170
|
-
...this.#projectPath ? {
|
|
1171
|
-
project_id: this.#projectPath
|
|
1172
|
-
} : {}
|
|
1208
|
+
...this.#projectPath ? { project_id: this.#projectPath } : {}
|
|
1173
1209
|
};
|
|
1174
1210
|
const created = await post(this.#client, "ai/duo_workflows/workflows", body);
|
|
1175
1211
|
if (created.id === void 0 || created.id === null) {
|
|
@@ -1220,6 +1256,220 @@ function stripSystemReminders(value) {
|
|
|
1220
1256
|
}).trim();
|
|
1221
1257
|
}
|
|
1222
1258
|
|
|
1259
|
+
// src/provider/prompt-utils.ts
|
|
1260
|
+
function extractToolResults(prompt) {
|
|
1261
|
+
if (!Array.isArray(prompt)) return [];
|
|
1262
|
+
const results = [];
|
|
1263
|
+
for (const message of prompt) {
|
|
1264
|
+
const content = message.content;
|
|
1265
|
+
if (!Array.isArray(content)) continue;
|
|
1266
|
+
for (const part of content) {
|
|
1267
|
+
const p = part;
|
|
1268
|
+
if (p.type === "tool-result") {
|
|
1269
|
+
const toolCallId = String(p.toolCallId ?? "");
|
|
1270
|
+
const toolName = String(p.toolName ?? "");
|
|
1271
|
+
if (!toolCallId) continue;
|
|
1272
|
+
const { output, error } = parseToolResultOutput(p);
|
|
1273
|
+
const finalError = error ?? asString2(p.error) ?? asString2(p.errorText);
|
|
1274
|
+
results.push({ toolCallId, toolName, output, error: finalError });
|
|
1275
|
+
}
|
|
1276
|
+
if (p.type === "tool-error") {
|
|
1277
|
+
const toolCallId = String(p.toolCallId ?? "");
|
|
1278
|
+
const toolName = String(p.toolName ?? "");
|
|
1279
|
+
const errorValue = p.error ?? p.errorText ?? p.message;
|
|
1280
|
+
const error = asString2(errorValue) ?? String(errorValue ?? "");
|
|
1281
|
+
results.push({ toolCallId, toolName, output: "", error });
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return results;
|
|
1286
|
+
}
|
|
1287
|
+
function asString2(value) {
|
|
1288
|
+
return typeof value === "string" ? value : void 0;
|
|
1289
|
+
}
|
|
1290
|
+
function parseToolResultOutput(part) {
|
|
1291
|
+
const outputField = part.output;
|
|
1292
|
+
const resultField = part.result;
|
|
1293
|
+
if (isPlainObject(outputField) && "type" in outputField) {
|
|
1294
|
+
const outputType = String(outputField.type);
|
|
1295
|
+
const outputValue = outputField.value;
|
|
1296
|
+
if (outputType === "text" || outputType === "json") {
|
|
1297
|
+
return { output: typeof outputValue === "string" ? outputValue : JSON.stringify(outputValue ?? "") };
|
|
1298
|
+
}
|
|
1299
|
+
if (outputType === "error-text" || outputType === "error-json") {
|
|
1300
|
+
return { output: "", error: typeof outputValue === "string" ? outputValue : JSON.stringify(outputValue ?? "") };
|
|
1301
|
+
}
|
|
1302
|
+
if (outputType === "content" && Array.isArray(outputValue)) {
|
|
1303
|
+
const text2 = outputValue.filter((v) => v.type === "text").map((v) => String(v.text ?? "")).join("\n");
|
|
1304
|
+
return { output: text2 };
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (outputField !== void 0) {
|
|
1308
|
+
return { output: typeof outputField === "string" ? outputField : JSON.stringify(outputField) };
|
|
1309
|
+
}
|
|
1310
|
+
if (resultField !== void 0) {
|
|
1311
|
+
const output = typeof resultField === "string" ? resultField : JSON.stringify(resultField);
|
|
1312
|
+
const error = isPlainObject(resultField) ? asString2(resultField.error) : void 0;
|
|
1313
|
+
return { output, error };
|
|
1314
|
+
}
|
|
1315
|
+
return { output: "" };
|
|
1316
|
+
}
|
|
1317
|
+
function extractSystemPrompt(prompt) {
|
|
1318
|
+
if (!Array.isArray(prompt)) return null;
|
|
1319
|
+
const parts = [];
|
|
1320
|
+
for (const message of prompt) {
|
|
1321
|
+
const msg = message;
|
|
1322
|
+
if (msg.role === "system" && typeof msg.content === "string" && msg.content.trim()) {
|
|
1323
|
+
parts.push(msg.content);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
1327
|
+
}
|
|
1328
|
+
function sanitizeSystemPrompt(prompt) {
|
|
1329
|
+
let result = prompt;
|
|
1330
|
+
result = result.replace(/^You are [Oo]pen[Cc]ode[,.].*$/gm, "");
|
|
1331
|
+
result = result.replace(/^Your name is opencode\s*$/gm, "");
|
|
1332
|
+
result = result.replace(
|
|
1333
|
+
/If the user asks for help or wants to give feedback[\s\S]*?https:\/\/github\.com\/anomalyco\/opencode\s*/g,
|
|
1334
|
+
""
|
|
1335
|
+
);
|
|
1336
|
+
result = result.replace(
|
|
1337
|
+
/When the user directly asks about OpenCode[\s\S]*?https:\/\/opencode\.ai\/docs\s*/g,
|
|
1338
|
+
""
|
|
1339
|
+
);
|
|
1340
|
+
result = result.replace(/https:\/\/github\.com\/anomalyco\/opencode\S*/g, "");
|
|
1341
|
+
result = result.replace(/https:\/\/opencode\.ai\S*/g, "");
|
|
1342
|
+
result = result.replace(/\bOpenCode\b/g, "GitLab Duo");
|
|
1343
|
+
result = result.replace(/\bopencode\b/g, "GitLab Duo");
|
|
1344
|
+
result = result.replace(/The exact model ID is GitLab Duo\//g, "The exact model ID is ");
|
|
1345
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
1346
|
+
return result.trim();
|
|
1347
|
+
}
|
|
1348
|
+
function extractAgentReminders(prompt) {
|
|
1349
|
+
if (!Array.isArray(prompt)) return [];
|
|
1350
|
+
let textParts = [];
|
|
1351
|
+
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
1352
|
+
const message = prompt[i];
|
|
1353
|
+
if (message?.role !== "user" || !Array.isArray(message.content)) continue;
|
|
1354
|
+
textParts = message.content.filter((p) => p.type === "text");
|
|
1355
|
+
if (textParts.length > 0) break;
|
|
1356
|
+
}
|
|
1357
|
+
if (textParts.length === 0) return [];
|
|
1358
|
+
const reminders = [];
|
|
1359
|
+
for (const part of textParts) {
|
|
1360
|
+
if (!part.text) continue;
|
|
1361
|
+
const text2 = String(part.text);
|
|
1362
|
+
if (part.synthetic) {
|
|
1363
|
+
const trimmed = text2.trim();
|
|
1364
|
+
if (trimmed.length > 0) reminders.push(trimmed);
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
const matches = text2.match(/<system-reminder>[\s\S]*?<\/system-reminder>/g);
|
|
1368
|
+
if (matches) reminders.push(...matches);
|
|
1369
|
+
}
|
|
1370
|
+
return reminders;
|
|
1371
|
+
}
|
|
1372
|
+
function isPlainObject(value) {
|
|
1373
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// src/provider/tool-mapping.ts
|
|
1377
|
+
function mapDuoToolRequest(toolName, args) {
|
|
1378
|
+
switch (toolName) {
|
|
1379
|
+
case "list_dir": {
|
|
1380
|
+
const directory = asString3(args.directory) ?? ".";
|
|
1381
|
+
return {
|
|
1382
|
+
toolName: "bash",
|
|
1383
|
+
args: { command: `ls -la ${shellQuote(directory)}`, description: "List directory contents", workdir: "." }
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
case "read_file": {
|
|
1387
|
+
const filePath = asString3(args.file_path) ?? asString3(args.filepath) ?? asString3(args.filePath) ?? asString3(args.path);
|
|
1388
|
+
if (!filePath) return { toolName, args };
|
|
1389
|
+
const mapped = { filePath };
|
|
1390
|
+
if (typeof args.offset === "number") mapped.offset = args.offset;
|
|
1391
|
+
if (typeof args.limit === "number") mapped.limit = args.limit;
|
|
1392
|
+
return { toolName: "read", args: mapped };
|
|
1393
|
+
}
|
|
1394
|
+
case "read_files": {
|
|
1395
|
+
const filePaths = asStringArray2(args.file_paths);
|
|
1396
|
+
if (filePaths.length === 0) return { toolName, args };
|
|
1397
|
+
return filePaths.map((fp) => ({ toolName: "read", args: { filePath: fp } }));
|
|
1398
|
+
}
|
|
1399
|
+
case "create_file_with_contents": {
|
|
1400
|
+
const filePath = asString3(args.file_path);
|
|
1401
|
+
const content = asString3(args.contents);
|
|
1402
|
+
if (!filePath || content === void 0) return { toolName, args };
|
|
1403
|
+
return { toolName: "write", args: { filePath, content } };
|
|
1404
|
+
}
|
|
1405
|
+
case "edit_file": {
|
|
1406
|
+
const filePath = asString3(args.file_path);
|
|
1407
|
+
const oldString = asString3(args.old_str);
|
|
1408
|
+
const newString = asString3(args.new_str);
|
|
1409
|
+
if (!filePath || oldString === void 0 || newString === void 0) return { toolName, args };
|
|
1410
|
+
return { toolName: "edit", args: { filePath, oldString, newString } };
|
|
1411
|
+
}
|
|
1412
|
+
case "find_files": {
|
|
1413
|
+
const pattern = asString3(args.name_pattern);
|
|
1414
|
+
if (!pattern) return { toolName, args };
|
|
1415
|
+
return { toolName: "glob", args: { pattern } };
|
|
1416
|
+
}
|
|
1417
|
+
case "grep": {
|
|
1418
|
+
const pattern = asString3(args.pattern);
|
|
1419
|
+
if (!pattern) return { toolName, args };
|
|
1420
|
+
const searchDir = asString3(args.search_directory);
|
|
1421
|
+
const caseInsensitive = Boolean(args.case_insensitive);
|
|
1422
|
+
const normalizedPattern = caseInsensitive && !pattern.startsWith("(?i)") ? `(?i)${pattern}` : pattern;
|
|
1423
|
+
const mapped = { pattern: normalizedPattern };
|
|
1424
|
+
if (searchDir) mapped.path = searchDir;
|
|
1425
|
+
return { toolName: "grep", args: mapped };
|
|
1426
|
+
}
|
|
1427
|
+
case "mkdir": {
|
|
1428
|
+
const directory = asString3(args.directory_path);
|
|
1429
|
+
if (!directory) return { toolName, args };
|
|
1430
|
+
return { toolName: "bash", args: { command: `mkdir -p ${shellQuote(directory)}`, description: "Create directory", workdir: "." } };
|
|
1431
|
+
}
|
|
1432
|
+
case "shell_command": {
|
|
1433
|
+
const command = asString3(args.command);
|
|
1434
|
+
if (!command) return { toolName, args };
|
|
1435
|
+
return { toolName: "bash", args: { command, description: "Run shell command", workdir: "." } };
|
|
1436
|
+
}
|
|
1437
|
+
case "run_command": {
|
|
1438
|
+
const program = asString3(args.program);
|
|
1439
|
+
if (program) {
|
|
1440
|
+
const parts = [shellQuote(program)];
|
|
1441
|
+
if (Array.isArray(args.flags)) parts.push(...args.flags.map((f) => shellQuote(String(f))));
|
|
1442
|
+
if (Array.isArray(args.arguments)) parts.push(...args.arguments.map((a) => shellQuote(String(a))));
|
|
1443
|
+
return { toolName: "bash", args: { command: parts.join(" "), description: "Run command", workdir: "." } };
|
|
1444
|
+
}
|
|
1445
|
+
const command = asString3(args.command);
|
|
1446
|
+
if (!command) return { toolName, args };
|
|
1447
|
+
return { toolName: "bash", args: { command, description: "Run command", workdir: "." } };
|
|
1448
|
+
}
|
|
1449
|
+
case "run_git_command": {
|
|
1450
|
+
const command = asString3(args.command);
|
|
1451
|
+
if (!command) return { toolName, args };
|
|
1452
|
+
const rawArgs = args.args;
|
|
1453
|
+
const extraArgs = Array.isArray(rawArgs) ? rawArgs.map((v) => shellQuote(String(v))).join(" ") : asString3(rawArgs);
|
|
1454
|
+
const gitCmd = extraArgs ? `git ${shellQuote(command)} ${extraArgs}` : `git ${shellQuote(command)}`;
|
|
1455
|
+
return { toolName: "bash", args: { command: gitCmd, description: "Run git command", workdir: "." } };
|
|
1456
|
+
}
|
|
1457
|
+
default:
|
|
1458
|
+
return { toolName, args };
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
function asString3(value) {
|
|
1462
|
+
return typeof value === "string" ? value : void 0;
|
|
1463
|
+
}
|
|
1464
|
+
function asStringArray2(value) {
|
|
1465
|
+
if (!Array.isArray(value)) return [];
|
|
1466
|
+
return value.filter((v) => typeof v === "string");
|
|
1467
|
+
}
|
|
1468
|
+
function shellQuote(s) {
|
|
1469
|
+
if (/^[a-zA-Z0-9_\-./=:@]+$/.test(s)) return s;
|
|
1470
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1223
1473
|
// src/provider/session-context.ts
|
|
1224
1474
|
function readSessionID(options) {
|
|
1225
1475
|
const providerBlock = readProviderBlock(options);
|
|
@@ -1240,6 +1490,85 @@ function readProviderBlock(options) {
|
|
|
1240
1490
|
return void 0;
|
|
1241
1491
|
}
|
|
1242
1492
|
|
|
1493
|
+
// src/provider/system-context.ts
|
|
1494
|
+
import os2 from "os";
|
|
1495
|
+
function buildSystemContext() {
|
|
1496
|
+
const platform = os2.platform();
|
|
1497
|
+
const arch = os2.arch();
|
|
1498
|
+
return [
|
|
1499
|
+
{
|
|
1500
|
+
category: "os_information",
|
|
1501
|
+
content: `<os><platform>${platform}</platform><architecture>${arch}</architecture></os>`,
|
|
1502
|
+
id: "os_information",
|
|
1503
|
+
metadata: JSON.stringify({
|
|
1504
|
+
title: "Operating System",
|
|
1505
|
+
enabled: true,
|
|
1506
|
+
subType: "os"
|
|
1507
|
+
})
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
category: "user_rule",
|
|
1511
|
+
content: SYSTEM_RULES,
|
|
1512
|
+
id: "user_rules",
|
|
1513
|
+
metadata: JSON.stringify({
|
|
1514
|
+
title: "System Rules",
|
|
1515
|
+
enabled: true,
|
|
1516
|
+
subType: "user_rule"
|
|
1517
|
+
})
|
|
1518
|
+
}
|
|
1519
|
+
];
|
|
1520
|
+
}
|
|
1521
|
+
var SYSTEM_RULES = `<system-reminder>
|
|
1522
|
+
You MUST follow ALL the rules in this block strictly.
|
|
1523
|
+
|
|
1524
|
+
<tool_orchestration>
|
|
1525
|
+
PARALLEL EXECUTION:
|
|
1526
|
+
- When gathering information, plan all needed searches upfront and execute
|
|
1527
|
+
them together using multiple tool calls in the same turn where possible.
|
|
1528
|
+
- Read multiple related files together rather than one at a time.
|
|
1529
|
+
- Patterns: grep + find_files together, read_file for multiple files together.
|
|
1530
|
+
|
|
1531
|
+
SEQUENTIAL EXECUTION (only when output depends on previous step):
|
|
1532
|
+
- Read a file BEFORE editing it (always).
|
|
1533
|
+
- Check dependencies BEFORE importing them.
|
|
1534
|
+
- Run tests AFTER making changes.
|
|
1535
|
+
|
|
1536
|
+
READ BEFORE WRITE:
|
|
1537
|
+
- Always read existing files before modifying them to understand context.
|
|
1538
|
+
- Check for existing patterns (naming, imports, error handling) and match them.
|
|
1539
|
+
- Verify the exact content to replace when using edit_file.
|
|
1540
|
+
|
|
1541
|
+
ERROR HANDLING:
|
|
1542
|
+
- If a tool fails, analyze the error before retrying.
|
|
1543
|
+
- If a shell command fails, check the error output and adapt.
|
|
1544
|
+
- Do not repeat the same failing operation without changes.
|
|
1545
|
+
</tool_orchestration>
|
|
1546
|
+
|
|
1547
|
+
<development_workflow>
|
|
1548
|
+
For software development tasks, follow this workflow:
|
|
1549
|
+
|
|
1550
|
+
1. UNDERSTAND: Read relevant files, explore the codebase structure
|
|
1551
|
+
2. PLAN: Break down the task into clear steps
|
|
1552
|
+
3. IMPLEMENT: Make changes methodically, one step at a time
|
|
1553
|
+
4. VERIFY: Run tests, type-checking, or build to validate changes
|
|
1554
|
+
5. COMPLETE: Summarize what was accomplished
|
|
1555
|
+
|
|
1556
|
+
CODE QUALITY:
|
|
1557
|
+
- Match existing code style and patterns in the project
|
|
1558
|
+
- Write immediately executable code (no TODOs or placeholders)
|
|
1559
|
+
- Prefer editing existing files over creating new ones
|
|
1560
|
+
- Use the project's established error handling patterns
|
|
1561
|
+
</development_workflow>
|
|
1562
|
+
|
|
1563
|
+
<communication>
|
|
1564
|
+
- Be concise and direct. Responses appear in a chat panel.
|
|
1565
|
+
- Focus on practical solutions over theoretical discussion.
|
|
1566
|
+
- When unable to complete a request, explain the limitation briefly and
|
|
1567
|
+
provide alternatives.
|
|
1568
|
+
- Use active language: "Analyzing...", "Searching..." instead of "Let me..."
|
|
1569
|
+
</communication>
|
|
1570
|
+
</system-reminder>`;
|
|
1571
|
+
|
|
1243
1572
|
// src/provider/duo-workflow-model.ts
|
|
1244
1573
|
var sessions = /* @__PURE__ */ new Map();
|
|
1245
1574
|
var UNKNOWN_USAGE = {
|
|
@@ -1255,6 +1584,14 @@ var DuoWorkflowModel = class {
|
|
|
1255
1584
|
#client;
|
|
1256
1585
|
#cwd;
|
|
1257
1586
|
#toolsConfig;
|
|
1587
|
+
// Tool tracking state (per model instance, reset on session change)
|
|
1588
|
+
#pendingToolRequests = /* @__PURE__ */ new Map();
|
|
1589
|
+
#multiCallGroups = /* @__PURE__ */ new Map();
|
|
1590
|
+
#sentToolCallIds = /* @__PURE__ */ new Set();
|
|
1591
|
+
#lastSentGoal = null;
|
|
1592
|
+
#stateSessionId;
|
|
1593
|
+
#agentMode;
|
|
1594
|
+
#agentModeReminder;
|
|
1258
1595
|
constructor(modelId, client, cwd) {
|
|
1259
1596
|
this.modelId = modelId;
|
|
1260
1597
|
this.#client = client;
|
|
@@ -1262,8 +1599,6 @@ var DuoWorkflowModel = class {
|
|
|
1262
1599
|
}
|
|
1263
1600
|
/**
|
|
1264
1601
|
* Opt-in: override the server-side system prompt and/or register MCP tools.
|
|
1265
|
-
* When not called, the server uses its default prompt and built-in tools.
|
|
1266
|
-
* Tool execution is always bridged locally regardless of this setting.
|
|
1267
1602
|
*/
|
|
1268
1603
|
setToolsConfig(config) {
|
|
1269
1604
|
this.#toolsConfig = config;
|
|
@@ -1272,22 +1607,13 @@ var DuoWorkflowModel = class {
|
|
|
1272
1607
|
}
|
|
1273
1608
|
}
|
|
1274
1609
|
async doGenerate(options) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
const session = this.#resolveSession(sessionID);
|
|
1280
|
-
const chunks = [];
|
|
1281
|
-
for await (const item of session.runTurn(goal, options.abortSignal)) {
|
|
1282
|
-
if (item.type === "text-delta") chunks.push(item.value);
|
|
1610
|
+
let text2 = "";
|
|
1611
|
+
const { stream } = await this.doStream(options);
|
|
1612
|
+
for await (const part of stream) {
|
|
1613
|
+
if (part.type === "text-delta") text2 += part.delta;
|
|
1283
1614
|
}
|
|
1284
1615
|
return {
|
|
1285
|
-
content: [
|
|
1286
|
-
{
|
|
1287
|
-
type: "text",
|
|
1288
|
-
text: chunks.join("")
|
|
1289
|
-
}
|
|
1290
|
-
],
|
|
1616
|
+
content: [{ type: "text", text: text2 }],
|
|
1291
1617
|
finishReason: "stop",
|
|
1292
1618
|
usage: UNKNOWN_USAGE,
|
|
1293
1619
|
warnings: []
|
|
@@ -1297,57 +1623,188 @@ var DuoWorkflowModel = class {
|
|
|
1297
1623
|
const sessionID = readSessionID(options);
|
|
1298
1624
|
if (!sessionID) throw new Error("missing workflow session ID");
|
|
1299
1625
|
const goal = extractGoal(options.prompt);
|
|
1300
|
-
|
|
1626
|
+
const toolResults = extractToolResults(options.prompt);
|
|
1301
1627
|
const session = this.#resolveSession(sessionID);
|
|
1302
1628
|
const textId = randomUUID3();
|
|
1629
|
+
if (sessionID !== this.#stateSessionId) {
|
|
1630
|
+
this.#pendingToolRequests.clear();
|
|
1631
|
+
this.#multiCallGroups.clear();
|
|
1632
|
+
this.#sentToolCallIds.clear();
|
|
1633
|
+
this.#lastSentGoal = null;
|
|
1634
|
+
this.#agentMode = void 0;
|
|
1635
|
+
this.#agentModeReminder = void 0;
|
|
1636
|
+
this.#stateSessionId = sessionID;
|
|
1637
|
+
}
|
|
1638
|
+
const model = this;
|
|
1303
1639
|
return {
|
|
1304
1640
|
stream: new ReadableStream({
|
|
1305
1641
|
start: async (controller) => {
|
|
1306
|
-
controller.enqueue({
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
});
|
|
1310
|
-
let hasText = false;
|
|
1642
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
1643
|
+
const onAbort = () => session.abort();
|
|
1644
|
+
options.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1311
1645
|
try {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1646
|
+
if (!session.hasStarted) {
|
|
1647
|
+
model.#sentToolCallIds.clear();
|
|
1648
|
+
for (const r of toolResults) {
|
|
1649
|
+
if (!model.#pendingToolRequests.has(r.toolCallId)) {
|
|
1650
|
+
model.#sentToolCallIds.add(r.toolCallId);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
model.#lastSentGoal = null;
|
|
1654
|
+
}
|
|
1655
|
+
const freshResults = toolResults.filter(
|
|
1656
|
+
(r) => !model.#sentToolCallIds.has(r.toolCallId)
|
|
1657
|
+
);
|
|
1658
|
+
let sentToolResults = false;
|
|
1659
|
+
for (const result of freshResults) {
|
|
1660
|
+
const hashIdx = result.toolCallId.indexOf("#");
|
|
1661
|
+
if (hashIdx !== -1) {
|
|
1662
|
+
const originalId = result.toolCallId.substring(0, hashIdx);
|
|
1663
|
+
const group = model.#multiCallGroups.get(originalId);
|
|
1664
|
+
if (!group) {
|
|
1665
|
+
model.#sentToolCallIds.add(result.toolCallId);
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
group.collected.set(result.toolCallId, result.error ?? result.output);
|
|
1669
|
+
model.#sentToolCallIds.add(result.toolCallId);
|
|
1670
|
+
model.#pendingToolRequests.delete(result.toolCallId);
|
|
1671
|
+
if (group.collected.size === group.subIds.length) {
|
|
1672
|
+
const aggregated = group.subIds.map((id) => group.collected.get(id) ?? "").join("\n");
|
|
1673
|
+
session.sendToolResult(originalId, aggregated);
|
|
1674
|
+
model.#multiCallGroups.delete(originalId);
|
|
1675
|
+
model.#pendingToolRequests.delete(originalId);
|
|
1676
|
+
sentToolResults = true;
|
|
1677
|
+
}
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
const pending = model.#pendingToolRequests.get(result.toolCallId);
|
|
1681
|
+
if (!pending) {
|
|
1682
|
+
model.#sentToolCallIds.add(result.toolCallId);
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
session.sendToolResult(result.toolCallId, result.output, result.error);
|
|
1686
|
+
sentToolResults = true;
|
|
1687
|
+
model.#sentToolCallIds.add(result.toolCallId);
|
|
1688
|
+
model.#pendingToolRequests.delete(result.toolCallId);
|
|
1689
|
+
}
|
|
1690
|
+
const isNewGoal = goal && goal !== model.#lastSentGoal;
|
|
1691
|
+
if (!sentToolResults && isNewGoal) {
|
|
1692
|
+
await session.ensureConnected(goal);
|
|
1693
|
+
if (!session.hasStarted) {
|
|
1694
|
+
const extraContext = [];
|
|
1695
|
+
extraContext.push(...buildSystemContext());
|
|
1696
|
+
const systemPrompt = extractSystemPrompt(options.prompt);
|
|
1697
|
+
if (systemPrompt) {
|
|
1698
|
+
extraContext.push({
|
|
1699
|
+
category: "agent_context",
|
|
1700
|
+
content: sanitizeSystemPrompt(systemPrompt),
|
|
1701
|
+
id: "agent_system_prompt",
|
|
1702
|
+
metadata: JSON.stringify({
|
|
1703
|
+
title: "Agent System Prompt",
|
|
1704
|
+
enabled: true,
|
|
1705
|
+
subType: "system_prompt"
|
|
1706
|
+
})
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
const agentReminders = extractAgentReminders(options.prompt);
|
|
1710
|
+
const modeReminder = detectLatestModeReminder(agentReminders);
|
|
1711
|
+
if (modeReminder) {
|
|
1712
|
+
model.#agentMode = modeReminder.mode;
|
|
1713
|
+
model.#agentModeReminder = modeReminder.reminder;
|
|
1714
|
+
}
|
|
1715
|
+
const remindersForContext = buildReminderContext(agentReminders, model.#agentModeReminder);
|
|
1716
|
+
if (remindersForContext.length > 0) {
|
|
1717
|
+
extraContext.push({
|
|
1718
|
+
category: "agent_context",
|
|
1719
|
+
content: sanitizeSystemPrompt(remindersForContext.join("\n\n")),
|
|
1720
|
+
id: "agent_reminders",
|
|
1721
|
+
metadata: JSON.stringify({
|
|
1722
|
+
title: "Agent Reminders",
|
|
1723
|
+
enabled: true,
|
|
1724
|
+
subType: "agent_reminders"
|
|
1725
|
+
})
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
session.sendStartRequest(goal, extraContext);
|
|
1729
|
+
}
|
|
1730
|
+
model.#lastSentGoal = goal;
|
|
1731
|
+
}
|
|
1732
|
+
let hasText = false;
|
|
1733
|
+
while (true) {
|
|
1734
|
+
const event = await session.waitForEvent();
|
|
1735
|
+
if (!event) break;
|
|
1736
|
+
if (event.type === "text-delta") {
|
|
1737
|
+
if (!event.value) continue;
|
|
1738
|
+
if (!hasText) {
|
|
1739
|
+
hasText = true;
|
|
1740
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
1741
|
+
}
|
|
1742
|
+
controller.enqueue({ type: "text-delta", id: textId, delta: event.value });
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
if (event.type === "tool-request") {
|
|
1746
|
+
let mapped;
|
|
1747
|
+
try {
|
|
1748
|
+
mapped = mapDuoToolRequest(event.toolName, event.args);
|
|
1749
|
+
} catch {
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (hasText) {
|
|
1753
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
1754
|
+
}
|
|
1755
|
+
if (Array.isArray(mapped)) {
|
|
1756
|
+
const subIds = mapped.map((_, i) => `${event.requestId}#${i}`);
|
|
1757
|
+
model.#multiCallGroups.set(event.requestId, {
|
|
1758
|
+
subIds,
|
|
1759
|
+
collected: /* @__PURE__ */ new Map()
|
|
1760
|
+
});
|
|
1761
|
+
model.#pendingToolRequests.set(event.requestId, {});
|
|
1762
|
+
for (const subId of subIds) {
|
|
1763
|
+
model.#pendingToolRequests.set(subId, {});
|
|
1764
|
+
}
|
|
1765
|
+
for (let i = 0; i < mapped.length; i++) {
|
|
1766
|
+
controller.enqueue({
|
|
1767
|
+
type: "tool-call",
|
|
1768
|
+
toolCallId: subIds[i],
|
|
1769
|
+
toolName: mapped[i].toolName,
|
|
1770
|
+
input: JSON.stringify(mapped[i].args)
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
} else {
|
|
1774
|
+
model.#pendingToolRequests.set(event.requestId, {});
|
|
1775
|
+
controller.enqueue({
|
|
1776
|
+
type: "tool-call",
|
|
1777
|
+
toolCallId: event.requestId,
|
|
1778
|
+
toolName: mapped.toolName,
|
|
1779
|
+
input: JSON.stringify(mapped.args)
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1317
1782
|
controller.enqueue({
|
|
1318
|
-
type: "
|
|
1319
|
-
|
|
1783
|
+
type: "finish",
|
|
1784
|
+
finishReason: "tool-calls",
|
|
1785
|
+
usage: UNKNOWN_USAGE
|
|
1320
1786
|
});
|
|
1787
|
+
controller.close();
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
if (event.type === "error") {
|
|
1791
|
+
controller.enqueue({ type: "error", error: new Error(event.message) });
|
|
1792
|
+
controller.enqueue({ type: "finish", finishReason: "error", usage: UNKNOWN_USAGE });
|
|
1793
|
+
controller.close();
|
|
1794
|
+
return;
|
|
1321
1795
|
}
|
|
1322
|
-
controller.enqueue({
|
|
1323
|
-
type: "text-delta",
|
|
1324
|
-
id: textId,
|
|
1325
|
-
delta: item.value
|
|
1326
|
-
});
|
|
1327
1796
|
}
|
|
1328
1797
|
if (hasText) {
|
|
1329
|
-
controller.enqueue({
|
|
1330
|
-
type: "text-end",
|
|
1331
|
-
id: textId
|
|
1332
|
-
});
|
|
1798
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
1333
1799
|
}
|
|
1334
|
-
controller.enqueue({
|
|
1335
|
-
type: "finish",
|
|
1336
|
-
finishReason: "stop",
|
|
1337
|
-
usage: UNKNOWN_USAGE
|
|
1338
|
-
});
|
|
1800
|
+
controller.enqueue({ type: "finish", finishReason: "stop", usage: UNKNOWN_USAGE });
|
|
1339
1801
|
controller.close();
|
|
1340
1802
|
} catch (error) {
|
|
1341
|
-
controller.enqueue({
|
|
1342
|
-
|
|
1343
|
-
error
|
|
1344
|
-
});
|
|
1345
|
-
controller.enqueue({
|
|
1346
|
-
type: "finish",
|
|
1347
|
-
finishReason: "error",
|
|
1348
|
-
usage: UNKNOWN_USAGE
|
|
1349
|
-
});
|
|
1803
|
+
controller.enqueue({ type: "error", error });
|
|
1804
|
+
controller.enqueue({ type: "finish", finishReason: "error", usage: UNKNOWN_USAGE });
|
|
1350
1805
|
controller.close();
|
|
1806
|
+
} finally {
|
|
1807
|
+
options.abortSignal?.removeEventListener("abort", onAbort);
|
|
1351
1808
|
}
|
|
1352
1809
|
}
|
|
1353
1810
|
}),
|
|
@@ -1376,6 +1833,32 @@ var DuoWorkflowModel = class {
|
|
|
1376
1833
|
function sessionKey(instanceUrl, modelId, sessionID) {
|
|
1377
1834
|
return `${instanceUrl}::${modelId}::${sessionID}`;
|
|
1378
1835
|
}
|
|
1836
|
+
function detectLatestModeReminder(reminders) {
|
|
1837
|
+
let latest;
|
|
1838
|
+
for (const reminder of reminders) {
|
|
1839
|
+
const classification = classifyModeReminder(reminder);
|
|
1840
|
+
if (classification === "other") continue;
|
|
1841
|
+
latest = { mode: classification, reminder };
|
|
1842
|
+
}
|
|
1843
|
+
return latest;
|
|
1844
|
+
}
|
|
1845
|
+
function buildReminderContext(reminders, modeReminder) {
|
|
1846
|
+
const nonModeReminders = reminders.filter(
|
|
1847
|
+
(r) => classifyModeReminder(r) === "other"
|
|
1848
|
+
);
|
|
1849
|
+
if (!modeReminder) return nonModeReminders;
|
|
1850
|
+
return [...nonModeReminders, modeReminder];
|
|
1851
|
+
}
|
|
1852
|
+
function classifyModeReminder(reminder) {
|
|
1853
|
+
const text2 = reminder.toLowerCase();
|
|
1854
|
+
if (text2.includes("operational mode has changed from build to plan")) return "plan";
|
|
1855
|
+
if (text2.includes("operational mode has changed from plan to build")) return "build";
|
|
1856
|
+
if (text2.includes("you are no longer in read-only mode")) return "build";
|
|
1857
|
+
if (text2.includes("you are now in read-only mode")) return "plan";
|
|
1858
|
+
if (text2.includes("you are in read-only mode")) return "plan";
|
|
1859
|
+
if (text2.includes("you are permitted to make file changes")) return "build";
|
|
1860
|
+
return "other";
|
|
1861
|
+
}
|
|
1379
1862
|
|
|
1380
1863
|
// src/provider/index.ts
|
|
1381
1864
|
function createFallbackProvider(input = {}) {
|