dtu-github-actions 0.0.0

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/README.md ADDED
@@ -0,0 +1 @@
1
+ # DTU
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ declare const configSchema: z.ZodObject<{
3
+ /**
4
+ * The secret used to sign GitHub webhooks.
5
+ * Hardcoded for local-only mock usage.
6
+ */
7
+ GITHUB_WEBHOOK_SECRET: z.ZodDefault<z.ZodString>;
8
+ /**
9
+ * The internal URL where the DTU Mock Server is reachable.
10
+ * Simulation scripts seed this server.
11
+ */
12
+ DTU_URL: z.ZodDefault<z.ZodString>;
13
+ /**
14
+ * The port the DTU Mock Server listens on.
15
+ */
16
+ DTU_PORT: z.ZodDefault<z.ZodNumber>;
17
+ /**
18
+ * Directory where cache archives should be stored.
19
+ */
20
+ DTU_CACHE_DIR: z.ZodDefault<z.ZodString>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ GITHUB_WEBHOOK_SECRET: string;
23
+ DTU_URL: string;
24
+ DTU_PORT: number;
25
+ DTU_CACHE_DIR: string;
26
+ }, {
27
+ GITHUB_WEBHOOK_SECRET?: string | undefined;
28
+ DTU_URL?: string | undefined;
29
+ DTU_PORT?: number | undefined;
30
+ DTU_CACHE_DIR?: string | undefined;
31
+ }>;
32
+ export type Config = z.infer<typeof configSchema>;
33
+ export declare const config: {
34
+ GITHUB_WEBHOOK_SECRET: string;
35
+ DTU_URL: string;
36
+ DTU_PORT: number;
37
+ DTU_CACHE_DIR: string;
38
+ };
39
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ const configSchema = z.object({
5
+ /**
6
+ * The secret used to sign GitHub webhooks.
7
+ * Hardcoded for local-only mock usage.
8
+ */
9
+ GITHUB_WEBHOOK_SECRET: z.string().min(1).default("agent-ci-local"),
10
+ /**
11
+ * The internal URL where the DTU Mock Server is reachable.
12
+ * Simulation scripts seed this server.
13
+ */
14
+ DTU_URL: z.string().url().default("http://localhost:8910"),
15
+ /**
16
+ * The port the DTU Mock Server listens on.
17
+ */
18
+ DTU_PORT: z.coerce.number().default(8910),
19
+ /**
20
+ * Directory where cache archives should be stored.
21
+ */
22
+ DTU_CACHE_DIR: z.string().default(() => path.join(os.tmpdir(), "dtu_cache")),
23
+ });
24
+ export const config = configSchema.parse({
25
+ GITHUB_WEBHOOK_SECRET: process.env.GITHUB_WEBHOOK_SECRET,
26
+ DTU_URL: process.env.DTU_URL,
27
+ DTU_PORT: process.env.DTU_PORT,
28
+ DTU_CACHE_DIR: process.env.DTU_CACHE_DIR,
29
+ });
@@ -0,0 +1,16 @@
1
+ export interface EphemeralDtu {
2
+ /** Full URL including port, e.g. "http://127.0.0.1:49823" */
3
+ url: string;
4
+ port: number;
5
+ /** Shut down the ephemeral DTU server. */
6
+ close(): Promise<void>;
7
+ }
8
+ /**
9
+ * Start an ephemeral in-process DTU server on a random OS-assigned port.
10
+ *
11
+ * Each call creates an independent server instance — no shared state between
12
+ * calls. Typical startup overhead is ~50ms.
13
+ *
14
+ * @param cacheDir Where cache archives should be stored (e.g. `os.tmpdir()/agent-ci/<repo>/cache/dtu`).
15
+ */
16
+ export declare function startEphemeralDtu(cacheDir: string): Promise<EphemeralDtu>;
@@ -0,0 +1,48 @@
1
+ import http from "node:http";
2
+ import { setCacheDir } from "./server/store.js";
3
+ import { bootstrapAndReturnApp } from "./server/index.js";
4
+ /**
5
+ * Start an ephemeral in-process DTU server on a random OS-assigned port.
6
+ *
7
+ * Each call creates an independent server instance — no shared state between
8
+ * calls. Typical startup overhead is ~50ms.
9
+ *
10
+ * @param cacheDir Where cache archives should be stored (e.g. `os.tmpdir()/agent-ci/<repo>/cache/dtu`).
11
+ */
12
+ export async function startEphemeralDtu(cacheDir) {
13
+ // Override the cache directory before bootstrapping so the store writes
14
+ // archives to the repo-scoped path rather than the global tmp dir.
15
+ setCacheDir(cacheDir);
16
+ // Build the Polka app with all routes registered.
17
+ const app = await bootstrapAndReturnApp({ reset: false });
18
+ // Wrap the Polka request handler in a plain Node.js HTTP server so we can
19
+ // bind to port 0 (OS-assigned) and get back the actual port.
20
+ const server = http.createServer((req, res) => {
21
+ // Polka exposes its composed handler as `app.handler`.
22
+ app.handler(req, res);
23
+ });
24
+ const port = await new Promise((resolve, reject) => {
25
+ server.listen(0, "0.0.0.0", () => {
26
+ const addr = server.address();
27
+ if (!addr || typeof addr === "string") {
28
+ return reject(new Error("Unexpected server address type"));
29
+ }
30
+ resolve(addr.port);
31
+ });
32
+ server.on("error", reject);
33
+ });
34
+ const url = `http://127.0.0.1:${port}`;
35
+ return {
36
+ url,
37
+ port,
38
+ close() {
39
+ return new Promise((resolve) => {
40
+ // Force-close all existing connections (HTTP keep-alive etc.)
41
+ // so the server shuts down immediately instead of waiting for
42
+ // idle connections to drain.
43
+ server.closeAllConnections();
44
+ server.close(() => resolve());
45
+ });
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,4 @@
1
+ import polka from "polka";
2
+ export declare function bootstrapAndReturnApp(options?: {
3
+ reset?: boolean;
4
+ }): Promise<polka.Polka>;
@@ -0,0 +1,326 @@
1
+ import polka from "polka";
2
+ import bodyParser from "body-parser";
3
+ import { execa } from "execa";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { config } from "../config.js";
7
+ import { state } from "./store.js";
8
+ import { setupDtuLogging, getDtuLogPath } from "./logger.js";
9
+ // Routes
10
+ import { registerDtuRoutes } from "./routes/dtu.js";
11
+ import { registerGithubRoutes } from "./routes/github.js";
12
+ import { registerActionRoutes } from "./routes/actions/index.js";
13
+ import { registerCacheRoutes } from "./routes/cache.js";
14
+ import { registerArtifactRoutes } from "./routes/artifacts.js";
15
+ async function terminateOldProcess() {
16
+ // Kill existing process on DTU port
17
+ try {
18
+ await execa("kill", ["-9", `$(lsof -t -i:${config.DTU_PORT})`], { shell: true, reject: false });
19
+ }
20
+ catch {
21
+ // Ignore error if no process found
22
+ }
23
+ }
24
+ export async function bootstrapAndReturnApp(options) {
25
+ const shouldReset = options?.reset ?? true;
26
+ setupDtuLogging();
27
+ if (shouldReset) {
28
+ state.reset();
29
+ await terminateOldProcess();
30
+ }
31
+ const app = polka();
32
+ // Polka's listen() does: server.on('request', this.handler). So wrapping app.handler
33
+ // is the correct place to normalize double-slashes BEFORE polka parses req.url into req.path.
34
+ // ACTIONS_CACHE_URL ends with '/' and routes start with '/' — producing '//_apis/...' paths.
35
+ const originalHandler = app.handler.bind(app);
36
+ app.handler = (req, res, info) => {
37
+ if (req.url?.includes("//")) {
38
+ req.url = req.url.replace(/\/{2,}/g, "/");
39
+ }
40
+ originalHandler(req, res, info);
41
+ };
42
+ // Request timing middleware
43
+ app.use((req, res, next) => {
44
+ const start = Date.now();
45
+ const origEnd = res.end.bind(res);
46
+ res.end = (...args) => {
47
+ const ms = Date.now() - start;
48
+ const url = req.url || "";
49
+ if (!url.includes("/logs/") && !url.includes("/feed") && !url.includes("/lines")) {
50
+ console.log(`[DTU] ${req.method} ${url} (${ms}ms)`);
51
+ }
52
+ return origEnd(...args);
53
+ };
54
+ next();
55
+ });
56
+ app.use(bodyParser.json({ limit: "50mb" }));
57
+ // Raw parsers for logs and cache uploads
58
+ app.use(bodyParser.text({ type: ["text/plain"], limit: "50mb" }));
59
+ app.use(bodyParser.raw({
60
+ type: ["application/octet-stream", "application/zip", "application/xml", "text/xml"],
61
+ limit: "500mb",
62
+ }));
63
+ // Routes
64
+ registerDtuRoutes(app);
65
+ registerGithubRoutes(app);
66
+ registerCacheRoutes(app);
67
+ registerArtifactRoutes(app);
68
+ registerActionRoutes(app);
69
+ app.post("/_apis/distributedtask/hubs/:hub/plans/:planId/logs/:logId", (req, res) => {
70
+ let text = "";
71
+ if (typeof req.body === "string") {
72
+ text = req.body;
73
+ }
74
+ else if (Buffer.isBuffer(req.body)) {
75
+ text = req.body.toString("utf-8");
76
+ }
77
+ if (text) {
78
+ const planId = req.params.planId;
79
+ const logDir = state.planToLogDir.get(planId);
80
+ if (logDir) {
81
+ let content = "";
82
+ for (const rawLine of text.split("\n")) {
83
+ const line = rawLine.trimEnd();
84
+ if (!line) {
85
+ content += "\n";
86
+ continue;
87
+ }
88
+ const stripped = line
89
+ .replace(/^\uFEFF?\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*/, "")
90
+ .replace(/^\uFEFF/, "");
91
+ if (!stripped || stripped.startsWith("##[") || stripped.startsWith("[command]")) {
92
+ continue;
93
+ }
94
+ content += stripped + "\n";
95
+ }
96
+ if (content) {
97
+ try {
98
+ const stepName = state.recordToStepName.get(String(req.params.logId)) || req.params.logId;
99
+ const stepsDir = path.join(logDir, "steps");
100
+ fs.mkdirSync(stepsDir, { recursive: true });
101
+ fs.appendFileSync(path.join(stepsDir, `${stepName}.log`), content);
102
+ }
103
+ catch {
104
+ /* best-effort */
105
+ }
106
+ }
107
+ }
108
+ }
109
+ const lineCount = text ? text.split("\n").filter((l) => l.trim()).length : 0;
110
+ res.writeHead(200, { "Content-Type": "application/json" });
111
+ res.end(JSON.stringify({
112
+ id: parseInt(req.params.logId),
113
+ path: `logs/${req.params.logId}`,
114
+ lineCount,
115
+ createdOn: new Date().toISOString(),
116
+ }));
117
+ });
118
+ app.put("/_apis/distributedtask/hubs/:hub/plans/:planId/logs/:logId", (req, res) => {
119
+ let text = "";
120
+ if (typeof req.body === "string") {
121
+ text = req.body;
122
+ }
123
+ else if (Buffer.isBuffer(req.body)) {
124
+ text = req.body.toString("utf-8");
125
+ }
126
+ if (text) {
127
+ const planId = req.params.planId;
128
+ const logDir = state.planToLogDir.get(planId);
129
+ if (logDir) {
130
+ let content = "";
131
+ for (const rawLine of text.split("\n")) {
132
+ const line = rawLine.trimEnd();
133
+ if (!line) {
134
+ content += "\n";
135
+ continue;
136
+ }
137
+ const stripped = line
138
+ .replace(/^\uFEFF?\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*/, "")
139
+ .replace(/^\uFEFF/, "");
140
+ if (!stripped || stripped.startsWith("##[") || stripped.startsWith("[command]")) {
141
+ continue;
142
+ }
143
+ content += stripped + "\n";
144
+ }
145
+ if (content) {
146
+ try {
147
+ const stepName = state.recordToStepName.get(String(req.params.logId)) || req.params.logId;
148
+ const stepsDir = path.join(logDir, "steps");
149
+ fs.mkdirSync(stepsDir, { recursive: true });
150
+ fs.appendFileSync(path.join(stepsDir, `${stepName}.log`), content);
151
+ }
152
+ catch {
153
+ /* best-effort */
154
+ }
155
+ }
156
+ }
157
+ }
158
+ const lineCount = text ? text.split("\n").filter((l) => l.trim()).length : 0;
159
+ res.writeHead(200, { "Content-Type": "application/json" });
160
+ res.end(JSON.stringify({
161
+ id: parseInt(req.params.logId),
162
+ path: `logs/${req.params.logId}`,
163
+ lineCount,
164
+ createdOn: new Date().toISOString(),
165
+ }));
166
+ });
167
+ // Global OPTIONS (CORS & Discovery)
168
+ app.options("/*", (req, res) => {
169
+ res.writeHead(200, {
170
+ "Access-Control-Allow-Origin": "*",
171
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PATCH, PUT, DELETE",
172
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-TFS-FedAuthRedirect, X-VSS-E2EID, X-TFS-Session",
173
+ "Content-Type": "application/json",
174
+ });
175
+ const responseValue = [
176
+ {
177
+ id: "A8C47E17-4D56-4A56-92BB-DE7EA7DC65BE",
178
+ area: "distributedtask",
179
+ resourceName: "pools",
180
+ routeTemplate: "_apis/distributedtask/pools/{poolId}",
181
+ resourceVersion: 1,
182
+ minVersion: "1.0",
183
+ maxVersion: "9.0",
184
+ releasedVersion: "9.0",
185
+ },
186
+ {
187
+ id: "E298EF32-5878-4CAB-993C-043836571F42",
188
+ area: "distributedtask",
189
+ resourceName: "agents",
190
+ routeTemplate: "_apis/distributedtask/pools/{poolId}/agents/{agentId}",
191
+ resourceVersion: 1,
192
+ minVersion: "1.0",
193
+ maxVersion: "9.0",
194
+ releasedVersion: "9.0",
195
+ },
196
+ {
197
+ id: "C3A054F6-7A8A-49C0-944E-3A8E5D7ADFD7",
198
+ area: "distributedtask",
199
+ resourceName: "messages",
200
+ routeTemplate: "_apis/distributedtask/pools/{poolId}/messages",
201
+ resourceVersion: 1,
202
+ minVersion: "1.0",
203
+ maxVersion: "9.0",
204
+ releasedVersion: "9.0",
205
+ },
206
+ {
207
+ id: "134E239E-2DF3-4794-A6F6-24F1F19EC8DC",
208
+ area: "distributedtask",
209
+ resourceName: "sessions",
210
+ routeTemplate: "_apis/distributedtask/pools/{poolId}/sessions/{sessionId}",
211
+ resourceVersion: 1,
212
+ minVersion: "1.0",
213
+ maxVersion: "9.0",
214
+ },
215
+ {
216
+ id: "83597576-CC2C-453C-BEA6-2882AE6A1653",
217
+ area: "distributedtask",
218
+ resourceName: "timelines",
219
+ routeTemplate: "_apis/distributedtask/timelines/{timelineId}",
220
+ resourceVersion: 1,
221
+ minVersion: "1.0",
222
+ maxVersion: "9.0",
223
+ releasedVersion: "9.0",
224
+ },
225
+ {
226
+ id: "27d7f831-88c1-4719-8ca1-6a061dad90eb",
227
+ area: "distributedtask",
228
+ resourceName: "actiondownloadinfo",
229
+ routeTemplate: "_apis/distributedtask/hubs/{hubName}/plans/{planId}/actiondownloadinfo",
230
+ resourceVersion: 1,
231
+ minVersion: "1.0",
232
+ maxVersion: "6.0",
233
+ releasedVersion: "6.0",
234
+ },
235
+ {
236
+ id: "858983e4-19bd-4c5e-864c-507b59b58b12",
237
+ area: "distributedtask",
238
+ resourceName: "feed",
239
+ routeTemplate: "_apis/distributedtask/hubs/{hubName}/plans/{planId}/timelines/{timelineId}/records/{recordId}/feed",
240
+ resourceVersion: 1,
241
+ minVersion: "1.0",
242
+ maxVersion: "9.0",
243
+ releasedVersion: "9.0",
244
+ },
245
+ {
246
+ id: "46f5667d-263a-4684-91b1-dff7fdcf64e2",
247
+ area: "distributedtask",
248
+ resourceName: "logs",
249
+ routeTemplate: "_apis/distributedtask/hubs/{hubName}/plans/{planId}/logs/{logId}",
250
+ resourceVersion: 1,
251
+ minVersion: "1.0",
252
+ maxVersion: "9.0",
253
+ releasedVersion: "9.0",
254
+ },
255
+ {
256
+ id: "8893BC5B-35B2-4BE7-83CB-99E683551DB4",
257
+ area: "distributedtask",
258
+ resourceName: "records",
259
+ routeTemplate: "_apis/distributedtask/timelines/{timelineId}/records/{recordId}",
260
+ resourceVersion: 1,
261
+ minVersion: "1.0",
262
+ maxVersion: "9.0",
263
+ releasedVersion: "9.0",
264
+ },
265
+ {
266
+ id: "FC825784-C92A-4299-9221-998A02D1B54F",
267
+ area: "distributedtask",
268
+ resourceName: "jobrequests",
269
+ routeTemplate: "_apis/distributedtask/jobrequests/{jobId}",
270
+ resourceVersion: 1,
271
+ minVersion: "1.0",
272
+ maxVersion: "9.0",
273
+ releasedVersion: "9.0",
274
+ },
275
+ {
276
+ id: "0A1EFD25-ABDA-43BD-9629-6C7BDD2E0D60",
277
+ area: "distributedtask",
278
+ resourceName: "jobinstances",
279
+ routeTemplate: "_apis/distributedtask/jobinstances/{jobId}",
280
+ resourceVersion: 1,
281
+ minVersion: "1.0",
282
+ maxVersion: "9.0",
283
+ releasedVersion: "9.0",
284
+ },
285
+ ];
286
+ res.end(JSON.stringify({ count: responseValue.length, value: responseValue }));
287
+ });
288
+ // Health and root APIs discovery
289
+ app.get("/_apis", (req, res) => {
290
+ res.writeHead(200, { "Content-Type": "application/json" });
291
+ res.end(JSON.stringify({ value: [] }));
292
+ });
293
+ app.get("/", (req, res) => {
294
+ res.writeHead(200, { "Content-Type": "application/json" });
295
+ res.end(JSON.stringify({ status: "online", seededJobs: state.jobs.size }));
296
+ });
297
+ app.head("/", (req, res) => {
298
+ res.writeHead(200);
299
+ res.end();
300
+ });
301
+ // Catch-all 404 with payload dumping
302
+ app.all("/*", (req, res) => {
303
+ console.log(`[DTU] 404 Not Found: ${req.method} ${req.url} (Details in 404.log)`);
304
+ let logContent = `\\n--- [${new Date().toISOString()}] 404 Not Found: ${req.method} ${req.url} ---\\n`;
305
+ logContent += `Headers: ${JSON.stringify(req.headers, null, 2)}\\n`;
306
+ if (req.body && Object.keys(req.body).length > 0) {
307
+ logContent += `Body (parsed JSON): ${JSON.stringify(req.body, null, 2)}\\n`;
308
+ }
309
+ else if (typeof req.body === "string" && req.body.length > 0) {
310
+ logContent += `Body (raw text): ${req.body.substring(0, 500)}${req.body.length > 500 ? "..." : ""}\\n`;
311
+ }
312
+ try {
313
+ const logDir = path.dirname(getDtuLogPath());
314
+ if (!fs.existsSync(logDir)) {
315
+ fs.mkdirSync(logDir, { recursive: true });
316
+ }
317
+ fs.appendFileSync(path.join(logDir, "404.log"), logContent);
318
+ }
319
+ catch {
320
+ /* best-effort */
321
+ }
322
+ res.writeHead(404);
323
+ res.end("Not Found (DTU Mock)");
324
+ });
325
+ return app;
326
+ }
@@ -0,0 +1,4 @@
1
+ export declare const DTU_ROOT: string;
2
+ export declare function setWorkingDirectory(dir: string): void;
3
+ export declare function getDtuLogPath(): string;
4
+ export declare function setupDtuLogging(): void;
@@ -0,0 +1,56 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { minimatch } from "minimatch";
5
+ export const DTU_ROOT = path.resolve(fileURLToPath(import.meta.url), "..", "..", "..", "..");
6
+ let dtuLogsDir = process.env.DTU_LOGS_DIR ?? path.join(DTU_ROOT, "_", "logs");
7
+ let dtuLogPath = path.join(dtuLogsDir, "dtu-server.log");
8
+ export function setWorkingDirectory(dir) {
9
+ dtuLogsDir = path.join(dir, "logs");
10
+ dtuLogPath = path.join(dtuLogsDir, "dtu-server.log");
11
+ }
12
+ export function getDtuLogPath() {
13
+ return dtuLogPath;
14
+ }
15
+ let logStream = null;
16
+ const _origLog = console.log.bind(console);
17
+ const _origWarn = console.warn.bind(console);
18
+ const _origError = console.error.bind(console);
19
+ /** Check if DTU debug output should appear on the terminal. */
20
+ function isDtuDebugEnabled() {
21
+ const patterns = (process.env.DEBUG || "")
22
+ .split(",")
23
+ .map((s) => s.trim())
24
+ .filter(Boolean);
25
+ return patterns.some((p) => minimatch("agent-ci:dtu", p) || minimatch("agent-ci:*", p));
26
+ }
27
+ export function setupDtuLogging() {
28
+ fs.mkdirSync(dtuLogsDir, { recursive: true });
29
+ logStream = fs.createWriteStream(dtuLogPath, { flags: "a" });
30
+ const dtuDebug = isDtuDebugEnabled();
31
+ // console.log/warn: always write to log file, only show in terminal when debug is on
32
+ console.log = (...args) => {
33
+ if (dtuDebug) {
34
+ _origLog(...args);
35
+ }
36
+ writeToLog(...args);
37
+ };
38
+ console.warn = (...args) => {
39
+ if (dtuDebug) {
40
+ _origWarn(...args);
41
+ }
42
+ writeToLog("[WARN]", ...args);
43
+ };
44
+ // console.error: always show in terminal (real errors)
45
+ console.error = (...args) => {
46
+ _origError(...args);
47
+ writeToLog("[ERROR]", ...args);
48
+ };
49
+ }
50
+ function writeToLog(...args) {
51
+ if (!logStream) {
52
+ return;
53
+ }
54
+ const line = args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" ");
55
+ logStream.write(`${new Date().toISOString()} ${line}\n`);
56
+ }
@@ -0,0 +1,25 @@
1
+ import { MessageResponse } from "../../../types.js";
2
+ export declare function toContextData(obj: any): any;
3
+ export declare function toTemplateTokenMapping(obj: {
4
+ [key: string]: string;
5
+ }): object;
6
+ /**
7
+ * Convert a container definition { image, env?, ports?, volumes?, options? }
8
+ * into a TemplateToken MappingToken that the runner's EvaluateJobContainer expects.
9
+ *
10
+ * Format:
11
+ * { type: 2, map: [{ Key: "image", Value: "alpine:3.19" }, ...] }
12
+ *
13
+ * Nested:
14
+ * env → MappingToken (type 2)
15
+ * ports/volumes → SequenceToken (type 1) of StringTokens
16
+ * options → StringToken (bare string)
17
+ */
18
+ export declare function toContainerTemplateToken(container: {
19
+ image: string;
20
+ env?: Record<string, string>;
21
+ ports?: string[];
22
+ volumes?: string[];
23
+ options?: string;
24
+ }): object;
25
+ export declare function createJobResponse(jobId: string, payload: any, baseUrl: string, planId: string): MessageResponse;