@agentrix/agentrix-run 0.2.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/index.mjs');
package/dist/index.cjs ADDED
@@ -0,0 +1,656 @@
1
+ 'use strict';
2
+
3
+ var node_fs = require('node:fs');
4
+ var shared = require('@agentrix/shared');
5
+
6
+ const DEFAULT_RESPONSE_MODE = "stream";
7
+ const DEFAULT_WAIT_TIMEOUT_SECONDS = 1800;
8
+ function usage() {
9
+ return [
10
+ "Usage: agentrix-run --agent <agent> --prompt <prompt> [options]",
11
+ "",
12
+ "Options:",
13
+ " --title <text>",
14
+ " --repo <json>",
15
+ " --output-schema <json>",
16
+ " --ref <ref>",
17
+ " --sha <sha>",
18
+ " --base-ref <ref>",
19
+ " --head-ref <ref>",
20
+ " --pr-number <number>",
21
+ " --issue-number <number>",
22
+ " --response-mode <stream|async>",
23
+ " --timeout <seconds>",
24
+ " --capability-profile <name>",
25
+ " --runner-id <cloud-id|machine-id>",
26
+ " --result-file <path>",
27
+ " --base-url <url>",
28
+ " --api-key <api-key>",
29
+ " --metadata <key=value>",
30
+ " --help"
31
+ ].join("\n");
32
+ }
33
+ function requireValue(args, index, flag) {
34
+ const value = args[index + 1];
35
+ if (!value || value.startsWith("--")) {
36
+ throw new Error(`Missing value for ${flag}`);
37
+ }
38
+ return value;
39
+ }
40
+ function parseRepo(value) {
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(value);
44
+ } catch (error) {
45
+ throw new Error(`Invalid --repo JSON: ${error instanceof Error ? error.message : "unknown error"}`);
46
+ }
47
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
48
+ throw new Error("--repo must be a JSON object");
49
+ }
50
+ return parsed;
51
+ }
52
+ function isPlainObject(value) {
53
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
54
+ }
55
+ function formatSchemaPath(path) {
56
+ return path.length > 0 ? path.join(".") : "$";
57
+ }
58
+ function normalizeStrictStructuredOutputSchemaNode(value, path, warnings) {
59
+ if (Array.isArray(value)) {
60
+ return value.map(
61
+ (item, index) => normalizeStrictStructuredOutputSchemaNode(item, [...path, String(index)], warnings)
62
+ );
63
+ }
64
+ if (!isPlainObject(value)) {
65
+ return value;
66
+ }
67
+ const node = {};
68
+ for (const [key, child] of Object.entries(value)) {
69
+ node[key] = normalizeStrictStructuredOutputSchemaNode(child, [...path, key], warnings);
70
+ }
71
+ const hasProperties = isPlainObject(node.properties);
72
+ const typeValue = node.type;
73
+ const isObjectSchema = typeValue === "object" || Array.isArray(typeValue) && typeValue.includes("object") || hasProperties;
74
+ if (isObjectSchema) {
75
+ if (node.additionalProperties !== false) {
76
+ node.additionalProperties = false;
77
+ warnings.push(`${formatSchemaPath(path)}: set additionalProperties=false`);
78
+ }
79
+ if (hasProperties) {
80
+ const properties = node.properties;
81
+ const propertyNames = Object.keys(properties);
82
+ const requiredValue = node.required;
83
+ if (requiredValue !== void 0 && !Array.isArray(requiredValue)) {
84
+ throw new Error(`${formatSchemaPath(path)}.required must be an array of property names`);
85
+ }
86
+ if (Array.isArray(requiredValue)) {
87
+ const unknownNames = requiredValue.filter(
88
+ (item) => typeof item !== "string" || !propertyNames.includes(item)
89
+ );
90
+ if (unknownNames.length > 0) {
91
+ throw new Error(
92
+ `${formatSchemaPath(path)}.required contains unknown properties: ${unknownNames.map(String).join(", ")}`
93
+ );
94
+ }
95
+ }
96
+ const normalizedRequired = [...propertyNames];
97
+ const currentRequired = Array.isArray(requiredValue) ? requiredValue : [];
98
+ const isSameRequired = currentRequired.length === normalizedRequired.length && currentRequired.every((item, index) => item === normalizedRequired[index]);
99
+ if (!isSameRequired) {
100
+ node.required = normalizedRequired;
101
+ warnings.push(`${formatSchemaPath(path)}: normalized required to all properties`);
102
+ }
103
+ }
104
+ }
105
+ return node;
106
+ }
107
+ function normalizeStrictStructuredOutputSchema(schema) {
108
+ const warnings = [];
109
+ const normalized = normalizeStrictStructuredOutputSchemaNode(schema, [], warnings);
110
+ if (!isPlainObject(normalized)) {
111
+ throw new Error("--output-schema must normalize to a JSON object");
112
+ }
113
+ return {
114
+ schema: normalized,
115
+ warnings
116
+ };
117
+ }
118
+ function printOutputSchemaWarnings(warnings) {
119
+ if (warnings.length === 0) {
120
+ return;
121
+ }
122
+ for (const warning of warnings) {
123
+ process.stderr.write(`[agentrix-run] normalized output schema: ${warning}
124
+ `);
125
+ }
126
+ }
127
+ function parseOutputSchema(value) {
128
+ let parsed;
129
+ try {
130
+ parsed = JSON.parse(value);
131
+ } catch (error) {
132
+ throw new Error(`Invalid --output-schema JSON: ${error instanceof Error ? error.message : "unknown error"}`);
133
+ }
134
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
135
+ throw new Error("--output-schema must be a JSON object");
136
+ }
137
+ const normalized = normalizeStrictStructuredOutputSchema(parsed);
138
+ printOutputSchemaWarnings(normalized.warnings);
139
+ return normalized.schema;
140
+ }
141
+ function parseMetadataEntry(value) {
142
+ const separatorIndex = value.indexOf("=");
143
+ if (separatorIndex <= 0) {
144
+ throw new Error(`Invalid --metadata value: ${value}`);
145
+ }
146
+ const key = value.slice(0, separatorIndex).trim();
147
+ const metadataValue = value.slice(separatorIndex + 1).trim();
148
+ if (!key) {
149
+ throw new Error(`Invalid --metadata key: ${value}`);
150
+ }
151
+ return [key, metadataValue];
152
+ }
153
+ function parseInteger(value, flag) {
154
+ const parsed = Number.parseInt(value, 10);
155
+ if (!Number.isFinite(parsed) || parsed <= 0) {
156
+ throw new Error(`${flag} must be a positive integer`);
157
+ }
158
+ return parsed;
159
+ }
160
+ function parseArgs(argv) {
161
+ const options = {
162
+ agent: "",
163
+ prompt: "",
164
+ responseMode: DEFAULT_RESPONSE_MODE,
165
+ metadata: {}
166
+ };
167
+ for (let index = 0; index < argv.length; index += 1) {
168
+ const arg = argv[index];
169
+ switch (arg) {
170
+ case "--help":
171
+ console.log(usage());
172
+ process.exit(0);
173
+ case "--agent":
174
+ options.agent = requireValue(argv, index, arg);
175
+ index += 1;
176
+ break;
177
+ case "--title":
178
+ options.title = requireValue(argv, index, arg);
179
+ index += 1;
180
+ break;
181
+ case "--prompt":
182
+ options.prompt = requireValue(argv, index, arg);
183
+ index += 1;
184
+ break;
185
+ case "--repo":
186
+ options.repo = parseRepo(requireValue(argv, index, arg));
187
+ index += 1;
188
+ break;
189
+ case "--output-schema":
190
+ options.outputSchema = parseOutputSchema(requireValue(argv, index, arg));
191
+ index += 1;
192
+ break;
193
+ case "--ref":
194
+ options.ref = requireValue(argv, index, arg);
195
+ index += 1;
196
+ break;
197
+ case "--sha":
198
+ options.sha = requireValue(argv, index, arg);
199
+ index += 1;
200
+ break;
201
+ case "--base-ref":
202
+ options.baseRef = requireValue(argv, index, arg);
203
+ index += 1;
204
+ break;
205
+ case "--head-ref":
206
+ options.headRef = requireValue(argv, index, arg);
207
+ index += 1;
208
+ break;
209
+ case "--pr-number":
210
+ options.prNumber = parseInteger(requireValue(argv, index, arg), arg);
211
+ index += 1;
212
+ break;
213
+ case "--issue-number":
214
+ options.issueNumber = parseInteger(requireValue(argv, index, arg), arg);
215
+ index += 1;
216
+ break;
217
+ case "--response-mode": {
218
+ const value = requireValue(argv, index, arg);
219
+ if (value !== "stream" && value !== "async") {
220
+ throw new Error(`Unsupported --response-mode: ${value}`);
221
+ }
222
+ options.responseMode = value;
223
+ index += 1;
224
+ break;
225
+ }
226
+ case "--timeout":
227
+ options.timeoutSeconds = parseInteger(requireValue(argv, index, arg), arg);
228
+ index += 1;
229
+ break;
230
+ case "--capability-profile":
231
+ options.capabilityProfile = requireValue(argv, index, arg);
232
+ index += 1;
233
+ break;
234
+ case "--runner-id":
235
+ options.runnerId = requireValue(argv, index, arg);
236
+ index += 1;
237
+ break;
238
+ case "--result-file":
239
+ options.resultFile = requireValue(argv, index, arg);
240
+ index += 1;
241
+ break;
242
+ case "--base-url":
243
+ options.baseUrl = requireValue(argv, index, arg);
244
+ index += 1;
245
+ break;
246
+ case "--api-key":
247
+ options.apiKey = requireValue(argv, index, arg);
248
+ index += 1;
249
+ break;
250
+ case "--metadata": {
251
+ const [key, value] = parseMetadataEntry(requireValue(argv, index, arg));
252
+ options.metadata[key] = value;
253
+ index += 1;
254
+ break;
255
+ }
256
+ default:
257
+ throw new Error(`Unknown argument: ${arg}`);
258
+ }
259
+ }
260
+ if (!options.agent.trim()) {
261
+ throw new Error("--agent is required");
262
+ }
263
+ if (!options.prompt.trim()) {
264
+ throw new Error("--prompt is required");
265
+ }
266
+ return options;
267
+ }
268
+ function normalizeRef(value) {
269
+ if (!value) {
270
+ return void 0;
271
+ }
272
+ return value.replace(/^refs\/heads\//, "");
273
+ }
274
+ function safeReadJsonFile(path) {
275
+ if (!path) {
276
+ return void 0;
277
+ }
278
+ try {
279
+ const content = node_fs.readFileSync(path, "utf8");
280
+ const parsed = JSON.parse(content);
281
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
282
+ return parsed;
283
+ }
284
+ } catch {
285
+ return void 0;
286
+ }
287
+ return void 0;
288
+ }
289
+ function splitRepositoryPath(value) {
290
+ if (!value) {
291
+ return {};
292
+ }
293
+ const parts = value.split("/").filter(Boolean);
294
+ if (parts.length < 2) {
295
+ return {};
296
+ }
297
+ return {
298
+ owner: parts.slice(0, -1).join("/"),
299
+ name: parts[parts.length - 1]
300
+ };
301
+ }
302
+ function optionalPositiveInt(value) {
303
+ if (!value) {
304
+ return void 0;
305
+ }
306
+ const parsed = Number.parseInt(value, 10);
307
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
308
+ }
309
+ function getNestedNumber(value, ...keys) {
310
+ let current = value;
311
+ for (const key of keys) {
312
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
313
+ return void 0;
314
+ }
315
+ current = current[key];
316
+ }
317
+ return typeof current === "number" ? current : void 0;
318
+ }
319
+ function getNestedString(value, ...keys) {
320
+ let current = value;
321
+ for (const key of keys) {
322
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
323
+ return void 0;
324
+ }
325
+ current = current[key];
326
+ }
327
+ return typeof current === "string" ? current : void 0;
328
+ }
329
+ function detectGithub() {
330
+ if (process.env.GITHUB_ACTIONS !== "true") {
331
+ return null;
332
+ }
333
+ const payload = safeReadJsonFile(process.env.GITHUB_EVENT_PATH);
334
+ const repository = splitRepositoryPath(process.env.GITHUB_REPOSITORY);
335
+ const runUrl = process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}` : void 0;
336
+ return {
337
+ repo: {
338
+ gitServerId: process.env.AGENTRIX_GIT_SERVER_ID || "github",
339
+ owner: repository.owner,
340
+ name: repository.name
341
+ },
342
+ git: {
343
+ ref: normalizeRef(process.env.GITHUB_REF),
344
+ sha: process.env.GITHUB_SHA,
345
+ baseRef: process.env.GITHUB_BASE_REF || getNestedString(payload, "pull_request", "base", "ref"),
346
+ headRef: process.env.GITHUB_HEAD_REF || getNestedString(payload, "pull_request", "head", "ref"),
347
+ prNumber: getNestedNumber(payload, "pull_request", "number"),
348
+ issueNumber: getNestedNumber(payload, "issue", "number")
349
+ },
350
+ context: {
351
+ ciProvider: "github_actions",
352
+ eventName: process.env.GITHUB_EVENT_NAME,
353
+ eventAction: getNestedString(payload, "action"),
354
+ runUrl,
355
+ payload
356
+ }
357
+ };
358
+ }
359
+ function detectGitlab() {
360
+ if (process.env.GITLAB_CI !== "true") {
361
+ return null;
362
+ }
363
+ const repository = splitRepositoryPath(process.env.CI_PROJECT_PATH);
364
+ return {
365
+ repo: {
366
+ gitServerId: process.env.AGENTRIX_GIT_SERVER_ID || "gitlab",
367
+ owner: repository.owner,
368
+ name: repository.name
369
+ },
370
+ git: {
371
+ ref: process.env.CI_COMMIT_REF_NAME,
372
+ sha: process.env.CI_COMMIT_SHA,
373
+ baseRef: process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME,
374
+ headRef: process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
375
+ prNumber: optionalPositiveInt(process.env.CI_MERGE_REQUEST_IID),
376
+ issueNumber: optionalPositiveInt(process.env.AGENTRIX_ISSUE_NUMBER)
377
+ },
378
+ context: {
379
+ ciProvider: "gitlab_ci",
380
+ eventName: process.env.AGENTRIX_EVENT_NAME || process.env.CI_PIPELINE_SOURCE,
381
+ eventAction: process.env.AGENTRIX_EVENT_ACTION,
382
+ jobUrl: process.env.CI_JOB_URL,
383
+ runUrl: process.env.CI_PIPELINE_URL
384
+ }
385
+ };
386
+ }
387
+ function detectCiContext() {
388
+ return detectGithub() ?? detectGitlab() ?? {};
389
+ }
390
+ function mergeRepo(explicitRepo, detectedRepo) {
391
+ if (!explicitRepo && !detectedRepo) {
392
+ return void 0;
393
+ }
394
+ return {
395
+ ...detectedRepo ?? {},
396
+ ...explicitRepo ?? {}
397
+ };
398
+ }
399
+ function buildRequest(options, detected) {
400
+ const repo = mergeRepo(options.repo, detected.repo);
401
+ const request = {
402
+ agent: options.agent,
403
+ title: options.title,
404
+ prompt: options.prompt,
405
+ outputSchema: options.outputSchema,
406
+ repo: {
407
+ gitServerId: repo?.gitServerId,
408
+ serverRepoId: repo?.serverRepoId,
409
+ owner: repo?.owner,
410
+ name: repo?.name
411
+ },
412
+ git: {
413
+ ref: options.ref ?? detected.git?.ref,
414
+ sha: options.sha ?? detected.git?.sha,
415
+ baseRef: options.baseRef ?? detected.git?.baseRef,
416
+ headRef: options.headRef ?? detected.git?.headRef,
417
+ prNumber: options.prNumber ?? detected.git?.prNumber,
418
+ issueNumber: options.issueNumber ?? detected.git?.issueNumber
419
+ },
420
+ execution: {
421
+ capabilityProfile: options.capabilityProfile,
422
+ runnerId: options.runnerId
423
+ },
424
+ context: detected.context?.ciProvider ? {
425
+ ciProvider: detected.context.ciProvider,
426
+ eventName: process.env.AGENTRIX_EVENT_NAME || detected.context.eventName,
427
+ eventAction: process.env.AGENTRIX_EVENT_ACTION || detected.context.eventAction,
428
+ jobUrl: process.env.AGENTRIX_JOB_URL || detected.context.jobUrl,
429
+ runUrl: process.env.AGENTRIX_RUN_URL || detected.context.runUrl,
430
+ payload: detected.context.payload
431
+ } : void 0,
432
+ metadata: Object.keys(options.metadata).length > 0 ? options.metadata : void 0
433
+ };
434
+ return shared.CreateCiRunRequestSchema.parse(request);
435
+ }
436
+ function resolveBaseUrl(options) {
437
+ const value = options.baseUrl || process.env.AGENTRIX_BASE_URL;
438
+ if (!value) {
439
+ throw new Error("Missing --base-url and AGENTRIX_BASE_URL");
440
+ }
441
+ return value.replace(/\/+$/, "");
442
+ }
443
+ function resolveApiKey(options) {
444
+ const value = options.apiKey || process.env.AGENTRIX_API_KEY;
445
+ if (!value) {
446
+ throw new Error("Missing --api-key and AGENTRIX_API_KEY");
447
+ }
448
+ return value;
449
+ }
450
+ async function requestJson(method, url, apiKey, body) {
451
+ const response = await fetch(url, {
452
+ method,
453
+ headers: {
454
+ Authorization: `Bearer ${apiKey}`,
455
+ "Content-Type": "application/json"
456
+ },
457
+ body: body === void 0 ? void 0 : JSON.stringify(body)
458
+ });
459
+ const text = await response.text();
460
+ const parsed = text ? JSON.parse(text) : void 0;
461
+ if (!response.ok) {
462
+ const message = parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
463
+ throw new Error(message);
464
+ }
465
+ return parsed;
466
+ }
467
+ async function createRun(baseUrl, apiKey, request) {
468
+ const response = await requestJson("POST", `${baseUrl}/v1/ci/runs`, apiKey, request);
469
+ return shared.CreateCiRunResponseSchema.parse(response);
470
+ }
471
+ function exitCodeForStatus(status) {
472
+ return status === "completed" ? 0 : 1;
473
+ }
474
+ function printStandardFields(payload) {
475
+ console.log(`run_id=${payload.runId}`);
476
+ console.log(`status=${payload.status}`);
477
+ console.log(`detail_url=${payload.detailUrl}`);
478
+ if (payload.result) {
479
+ console.log(`result=${payload.result}`);
480
+ }
481
+ }
482
+ function ensureWaitDeadline(options) {
483
+ const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_WAIT_TIMEOUT_SECONDS;
484
+ return Date.now() + timeoutSeconds * 1e3;
485
+ }
486
+ function writeJsonLine(value) {
487
+ process.stdout.write(`${JSON.stringify(value)}
488
+ `);
489
+ }
490
+ function writeStreamEvent(type, data) {
491
+ const record = {
492
+ type,
493
+ data
494
+ };
495
+ writeJsonLine(record);
496
+ }
497
+ function buildFinalResultRecord(status) {
498
+ return {
499
+ runId: status.runId,
500
+ status: status.status,
501
+ result: status.result,
502
+ structuredOutput: status.structuredOutput,
503
+ detailUrl: status.detailUrl
504
+ };
505
+ }
506
+ function writeResultFile(path, status) {
507
+ if (!path) {
508
+ return;
509
+ }
510
+ node_fs.writeFileSync(path, `${JSON.stringify(buildFinalResultRecord(status), null, 2)}
511
+ `, "utf8");
512
+ }
513
+ function writeAsyncResultFile(path, run) {
514
+ if (!path) {
515
+ return;
516
+ }
517
+ const record = {
518
+ runId: run.runId,
519
+ status: run.status,
520
+ result: "",
521
+ detailUrl: run.detailUrl
522
+ };
523
+ node_fs.writeFileSync(path, `${JSON.stringify(record, null, 2)}
524
+ `, "utf8");
525
+ }
526
+ function parseSseBlock(block) {
527
+ const normalized = block.replace(/\r/g, "");
528
+ const lines = normalized.split("\n");
529
+ let event = "message";
530
+ const dataLines = [];
531
+ for (const line of lines) {
532
+ if (!line) {
533
+ continue;
534
+ }
535
+ if (line.startsWith(":")) {
536
+ continue;
537
+ }
538
+ if (line.startsWith("event:")) {
539
+ event = line.slice("event:".length).trim();
540
+ continue;
541
+ }
542
+ if (line.startsWith("data:")) {
543
+ dataLines.push(line.slice("data:".length).trimStart());
544
+ }
545
+ }
546
+ if (dataLines.length === 0) {
547
+ return null;
548
+ }
549
+ const dataText = dataLines.join("\n");
550
+ return {
551
+ event,
552
+ data: JSON.parse(dataText)
553
+ };
554
+ }
555
+ async function createRunStream(baseUrl, apiKey, request, deadline) {
556
+ const timeoutMs = Math.max(deadline - Date.now(), 1);
557
+ const controller = new AbortController();
558
+ const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
559
+ try {
560
+ const response = await fetch(`${baseUrl}/v1/ci/runs`, {
561
+ method: "POST",
562
+ headers: {
563
+ Authorization: `Bearer ${apiKey}`,
564
+ "Content-Type": "application/json",
565
+ Accept: "text/event-stream"
566
+ },
567
+ body: JSON.stringify(request),
568
+ signal: controller.signal
569
+ });
570
+ if (!response.ok) {
571
+ const text = await response.text();
572
+ const parsed = text ? JSON.parse(text) : void 0;
573
+ const message = parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
574
+ throw new Error(message);
575
+ }
576
+ if (!response.body) {
577
+ throw new Error("Missing SSE response body");
578
+ }
579
+ const reader = response.body.getReader();
580
+ const decoder = new TextDecoder();
581
+ let buffer = "";
582
+ for (; ; ) {
583
+ const { value, done } = await reader.read();
584
+ if (done) {
585
+ break;
586
+ }
587
+ buffer += decoder.decode(value, { stream: true });
588
+ buffer = buffer.replace(/\r\n/g, "\n");
589
+ while (true) {
590
+ const delimiterIndex = buffer.indexOf("\n\n");
591
+ if (delimiterIndex === -1) {
592
+ break;
593
+ }
594
+ const block = buffer.slice(0, delimiterIndex);
595
+ buffer = buffer.slice(delimiterIndex + 2);
596
+ const parsed = parseSseBlock(block);
597
+ if (!parsed) {
598
+ continue;
599
+ }
600
+ if (parsed.event === "run.created" || parsed.event === "task.event") {
601
+ writeStreamEvent(parsed.event, parsed.data);
602
+ continue;
603
+ }
604
+ if (parsed.event === "run.completed" || parsed.event === "run.failed" || parsed.event === "run.canceled" || parsed.event === "run.timeout") {
605
+ const finalStatus = shared.CiRunStatusResponseSchema.parse(parsed.data);
606
+ writeStreamEvent(parsed.event, finalStatus);
607
+ return finalStatus;
608
+ }
609
+ if (parsed.event === "run.error") {
610
+ writeStreamEvent(parsed.event, parsed.data);
611
+ const error = parsed.data;
612
+ throw new Error(typeof error.message === "string" ? error.message : "CI run stream failed");
613
+ }
614
+ }
615
+ }
616
+ buffer += decoder.decode();
617
+ buffer = buffer.replace(/\r\n/g, "\n");
618
+ throw new Error("CI run stream ended before terminal event");
619
+ } catch (error) {
620
+ if (error instanceof Error && error.name === "AbortError") {
621
+ throw new Error("Timed out waiting for CI run terminal status");
622
+ }
623
+ throw error;
624
+ } finally {
625
+ clearTimeout(timeoutHandle);
626
+ }
627
+ }
628
+ async function main(argv = process.argv.slice(2)) {
629
+ const options = parseArgs(argv);
630
+ const detected = detectCiContext();
631
+ const baseUrl = resolveBaseUrl(options);
632
+ const apiKey = resolveApiKey(options);
633
+ const request = buildRequest(options, detected);
634
+ if (options.responseMode === "async") {
635
+ const run = await createRun(baseUrl, apiKey, request);
636
+ writeAsyncResultFile(options.resultFile, run);
637
+ printStandardFields({
638
+ runId: run.runId,
639
+ status: run.status,
640
+ detailUrl: run.detailUrl
641
+ });
642
+ return 0;
643
+ }
644
+ const deadline = ensureWaitDeadline(options);
645
+ const finalStatus = await createRunStream(baseUrl, apiKey, request, deadline);
646
+ writeResultFile(options.resultFile, finalStatus);
647
+ return exitCodeForStatus(finalStatus.status);
648
+ }
649
+
650
+ main().then((code) => {
651
+ process.exitCode = code;
652
+ }).catch((error) => {
653
+ const message = error instanceof Error ? error.message : String(error);
654
+ console.error(message);
655
+ process.exitCode = 1;
656
+ });
@@ -0,0 +1,2 @@
1
+
2
+ export { };
@@ -0,0 +1,2 @@
1
+
2
+ export { };
package/dist/index.mjs ADDED
@@ -0,0 +1,654 @@
1
+ import { writeFileSync, readFileSync } from 'node:fs';
2
+ import { CreateCiRunRequestSchema, CiRunStatusResponseSchema, CreateCiRunResponseSchema } from '@agentrix/shared';
3
+
4
+ const DEFAULT_RESPONSE_MODE = "stream";
5
+ const DEFAULT_WAIT_TIMEOUT_SECONDS = 1800;
6
+ function usage() {
7
+ return [
8
+ "Usage: agentrix-run --agent <agent> --prompt <prompt> [options]",
9
+ "",
10
+ "Options:",
11
+ " --title <text>",
12
+ " --repo <json>",
13
+ " --output-schema <json>",
14
+ " --ref <ref>",
15
+ " --sha <sha>",
16
+ " --base-ref <ref>",
17
+ " --head-ref <ref>",
18
+ " --pr-number <number>",
19
+ " --issue-number <number>",
20
+ " --response-mode <stream|async>",
21
+ " --timeout <seconds>",
22
+ " --capability-profile <name>",
23
+ " --runner-id <cloud-id|machine-id>",
24
+ " --result-file <path>",
25
+ " --base-url <url>",
26
+ " --api-key <api-key>",
27
+ " --metadata <key=value>",
28
+ " --help"
29
+ ].join("\n");
30
+ }
31
+ function requireValue(args, index, flag) {
32
+ const value = args[index + 1];
33
+ if (!value || value.startsWith("--")) {
34
+ throw new Error(`Missing value for ${flag}`);
35
+ }
36
+ return value;
37
+ }
38
+ function parseRepo(value) {
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(value);
42
+ } catch (error) {
43
+ throw new Error(`Invalid --repo JSON: ${error instanceof Error ? error.message : "unknown error"}`);
44
+ }
45
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
46
+ throw new Error("--repo must be a JSON object");
47
+ }
48
+ return parsed;
49
+ }
50
+ function isPlainObject(value) {
51
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
52
+ }
53
+ function formatSchemaPath(path) {
54
+ return path.length > 0 ? path.join(".") : "$";
55
+ }
56
+ function normalizeStrictStructuredOutputSchemaNode(value, path, warnings) {
57
+ if (Array.isArray(value)) {
58
+ return value.map(
59
+ (item, index) => normalizeStrictStructuredOutputSchemaNode(item, [...path, String(index)], warnings)
60
+ );
61
+ }
62
+ if (!isPlainObject(value)) {
63
+ return value;
64
+ }
65
+ const node = {};
66
+ for (const [key, child] of Object.entries(value)) {
67
+ node[key] = normalizeStrictStructuredOutputSchemaNode(child, [...path, key], warnings);
68
+ }
69
+ const hasProperties = isPlainObject(node.properties);
70
+ const typeValue = node.type;
71
+ const isObjectSchema = typeValue === "object" || Array.isArray(typeValue) && typeValue.includes("object") || hasProperties;
72
+ if (isObjectSchema) {
73
+ if (node.additionalProperties !== false) {
74
+ node.additionalProperties = false;
75
+ warnings.push(`${formatSchemaPath(path)}: set additionalProperties=false`);
76
+ }
77
+ if (hasProperties) {
78
+ const properties = node.properties;
79
+ const propertyNames = Object.keys(properties);
80
+ const requiredValue = node.required;
81
+ if (requiredValue !== void 0 && !Array.isArray(requiredValue)) {
82
+ throw new Error(`${formatSchemaPath(path)}.required must be an array of property names`);
83
+ }
84
+ if (Array.isArray(requiredValue)) {
85
+ const unknownNames = requiredValue.filter(
86
+ (item) => typeof item !== "string" || !propertyNames.includes(item)
87
+ );
88
+ if (unknownNames.length > 0) {
89
+ throw new Error(
90
+ `${formatSchemaPath(path)}.required contains unknown properties: ${unknownNames.map(String).join(", ")}`
91
+ );
92
+ }
93
+ }
94
+ const normalizedRequired = [...propertyNames];
95
+ const currentRequired = Array.isArray(requiredValue) ? requiredValue : [];
96
+ const isSameRequired = currentRequired.length === normalizedRequired.length && currentRequired.every((item, index) => item === normalizedRequired[index]);
97
+ if (!isSameRequired) {
98
+ node.required = normalizedRequired;
99
+ warnings.push(`${formatSchemaPath(path)}: normalized required to all properties`);
100
+ }
101
+ }
102
+ }
103
+ return node;
104
+ }
105
+ function normalizeStrictStructuredOutputSchema(schema) {
106
+ const warnings = [];
107
+ const normalized = normalizeStrictStructuredOutputSchemaNode(schema, [], warnings);
108
+ if (!isPlainObject(normalized)) {
109
+ throw new Error("--output-schema must normalize to a JSON object");
110
+ }
111
+ return {
112
+ schema: normalized,
113
+ warnings
114
+ };
115
+ }
116
+ function printOutputSchemaWarnings(warnings) {
117
+ if (warnings.length === 0) {
118
+ return;
119
+ }
120
+ for (const warning of warnings) {
121
+ process.stderr.write(`[agentrix-run] normalized output schema: ${warning}
122
+ `);
123
+ }
124
+ }
125
+ function parseOutputSchema(value) {
126
+ let parsed;
127
+ try {
128
+ parsed = JSON.parse(value);
129
+ } catch (error) {
130
+ throw new Error(`Invalid --output-schema JSON: ${error instanceof Error ? error.message : "unknown error"}`);
131
+ }
132
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
133
+ throw new Error("--output-schema must be a JSON object");
134
+ }
135
+ const normalized = normalizeStrictStructuredOutputSchema(parsed);
136
+ printOutputSchemaWarnings(normalized.warnings);
137
+ return normalized.schema;
138
+ }
139
+ function parseMetadataEntry(value) {
140
+ const separatorIndex = value.indexOf("=");
141
+ if (separatorIndex <= 0) {
142
+ throw new Error(`Invalid --metadata value: ${value}`);
143
+ }
144
+ const key = value.slice(0, separatorIndex).trim();
145
+ const metadataValue = value.slice(separatorIndex + 1).trim();
146
+ if (!key) {
147
+ throw new Error(`Invalid --metadata key: ${value}`);
148
+ }
149
+ return [key, metadataValue];
150
+ }
151
+ function parseInteger(value, flag) {
152
+ const parsed = Number.parseInt(value, 10);
153
+ if (!Number.isFinite(parsed) || parsed <= 0) {
154
+ throw new Error(`${flag} must be a positive integer`);
155
+ }
156
+ return parsed;
157
+ }
158
+ function parseArgs(argv) {
159
+ const options = {
160
+ agent: "",
161
+ prompt: "",
162
+ responseMode: DEFAULT_RESPONSE_MODE,
163
+ metadata: {}
164
+ };
165
+ for (let index = 0; index < argv.length; index += 1) {
166
+ const arg = argv[index];
167
+ switch (arg) {
168
+ case "--help":
169
+ console.log(usage());
170
+ process.exit(0);
171
+ case "--agent":
172
+ options.agent = requireValue(argv, index, arg);
173
+ index += 1;
174
+ break;
175
+ case "--title":
176
+ options.title = requireValue(argv, index, arg);
177
+ index += 1;
178
+ break;
179
+ case "--prompt":
180
+ options.prompt = requireValue(argv, index, arg);
181
+ index += 1;
182
+ break;
183
+ case "--repo":
184
+ options.repo = parseRepo(requireValue(argv, index, arg));
185
+ index += 1;
186
+ break;
187
+ case "--output-schema":
188
+ options.outputSchema = parseOutputSchema(requireValue(argv, index, arg));
189
+ index += 1;
190
+ break;
191
+ case "--ref":
192
+ options.ref = requireValue(argv, index, arg);
193
+ index += 1;
194
+ break;
195
+ case "--sha":
196
+ options.sha = requireValue(argv, index, arg);
197
+ index += 1;
198
+ break;
199
+ case "--base-ref":
200
+ options.baseRef = requireValue(argv, index, arg);
201
+ index += 1;
202
+ break;
203
+ case "--head-ref":
204
+ options.headRef = requireValue(argv, index, arg);
205
+ index += 1;
206
+ break;
207
+ case "--pr-number":
208
+ options.prNumber = parseInteger(requireValue(argv, index, arg), arg);
209
+ index += 1;
210
+ break;
211
+ case "--issue-number":
212
+ options.issueNumber = parseInteger(requireValue(argv, index, arg), arg);
213
+ index += 1;
214
+ break;
215
+ case "--response-mode": {
216
+ const value = requireValue(argv, index, arg);
217
+ if (value !== "stream" && value !== "async") {
218
+ throw new Error(`Unsupported --response-mode: ${value}`);
219
+ }
220
+ options.responseMode = value;
221
+ index += 1;
222
+ break;
223
+ }
224
+ case "--timeout":
225
+ options.timeoutSeconds = parseInteger(requireValue(argv, index, arg), arg);
226
+ index += 1;
227
+ break;
228
+ case "--capability-profile":
229
+ options.capabilityProfile = requireValue(argv, index, arg);
230
+ index += 1;
231
+ break;
232
+ case "--runner-id":
233
+ options.runnerId = requireValue(argv, index, arg);
234
+ index += 1;
235
+ break;
236
+ case "--result-file":
237
+ options.resultFile = requireValue(argv, index, arg);
238
+ index += 1;
239
+ break;
240
+ case "--base-url":
241
+ options.baseUrl = requireValue(argv, index, arg);
242
+ index += 1;
243
+ break;
244
+ case "--api-key":
245
+ options.apiKey = requireValue(argv, index, arg);
246
+ index += 1;
247
+ break;
248
+ case "--metadata": {
249
+ const [key, value] = parseMetadataEntry(requireValue(argv, index, arg));
250
+ options.metadata[key] = value;
251
+ index += 1;
252
+ break;
253
+ }
254
+ default:
255
+ throw new Error(`Unknown argument: ${arg}`);
256
+ }
257
+ }
258
+ if (!options.agent.trim()) {
259
+ throw new Error("--agent is required");
260
+ }
261
+ if (!options.prompt.trim()) {
262
+ throw new Error("--prompt is required");
263
+ }
264
+ return options;
265
+ }
266
+ function normalizeRef(value) {
267
+ if (!value) {
268
+ return void 0;
269
+ }
270
+ return value.replace(/^refs\/heads\//, "");
271
+ }
272
+ function safeReadJsonFile(path) {
273
+ if (!path) {
274
+ return void 0;
275
+ }
276
+ try {
277
+ const content = readFileSync(path, "utf8");
278
+ const parsed = JSON.parse(content);
279
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
280
+ return parsed;
281
+ }
282
+ } catch {
283
+ return void 0;
284
+ }
285
+ return void 0;
286
+ }
287
+ function splitRepositoryPath(value) {
288
+ if (!value) {
289
+ return {};
290
+ }
291
+ const parts = value.split("/").filter(Boolean);
292
+ if (parts.length < 2) {
293
+ return {};
294
+ }
295
+ return {
296
+ owner: parts.slice(0, -1).join("/"),
297
+ name: parts[parts.length - 1]
298
+ };
299
+ }
300
+ function optionalPositiveInt(value) {
301
+ if (!value) {
302
+ return void 0;
303
+ }
304
+ const parsed = Number.parseInt(value, 10);
305
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
306
+ }
307
+ function getNestedNumber(value, ...keys) {
308
+ let current = value;
309
+ for (const key of keys) {
310
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
311
+ return void 0;
312
+ }
313
+ current = current[key];
314
+ }
315
+ return typeof current === "number" ? current : void 0;
316
+ }
317
+ function getNestedString(value, ...keys) {
318
+ let current = value;
319
+ for (const key of keys) {
320
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
321
+ return void 0;
322
+ }
323
+ current = current[key];
324
+ }
325
+ return typeof current === "string" ? current : void 0;
326
+ }
327
+ function detectGithub() {
328
+ if (process.env.GITHUB_ACTIONS !== "true") {
329
+ return null;
330
+ }
331
+ const payload = safeReadJsonFile(process.env.GITHUB_EVENT_PATH);
332
+ const repository = splitRepositoryPath(process.env.GITHUB_REPOSITORY);
333
+ const runUrl = process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}` : void 0;
334
+ return {
335
+ repo: {
336
+ gitServerId: process.env.AGENTRIX_GIT_SERVER_ID || "github",
337
+ owner: repository.owner,
338
+ name: repository.name
339
+ },
340
+ git: {
341
+ ref: normalizeRef(process.env.GITHUB_REF),
342
+ sha: process.env.GITHUB_SHA,
343
+ baseRef: process.env.GITHUB_BASE_REF || getNestedString(payload, "pull_request", "base", "ref"),
344
+ headRef: process.env.GITHUB_HEAD_REF || getNestedString(payload, "pull_request", "head", "ref"),
345
+ prNumber: getNestedNumber(payload, "pull_request", "number"),
346
+ issueNumber: getNestedNumber(payload, "issue", "number")
347
+ },
348
+ context: {
349
+ ciProvider: "github_actions",
350
+ eventName: process.env.GITHUB_EVENT_NAME,
351
+ eventAction: getNestedString(payload, "action"),
352
+ runUrl,
353
+ payload
354
+ }
355
+ };
356
+ }
357
+ function detectGitlab() {
358
+ if (process.env.GITLAB_CI !== "true") {
359
+ return null;
360
+ }
361
+ const repository = splitRepositoryPath(process.env.CI_PROJECT_PATH);
362
+ return {
363
+ repo: {
364
+ gitServerId: process.env.AGENTRIX_GIT_SERVER_ID || "gitlab",
365
+ owner: repository.owner,
366
+ name: repository.name
367
+ },
368
+ git: {
369
+ ref: process.env.CI_COMMIT_REF_NAME,
370
+ sha: process.env.CI_COMMIT_SHA,
371
+ baseRef: process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME,
372
+ headRef: process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
373
+ prNumber: optionalPositiveInt(process.env.CI_MERGE_REQUEST_IID),
374
+ issueNumber: optionalPositiveInt(process.env.AGENTRIX_ISSUE_NUMBER)
375
+ },
376
+ context: {
377
+ ciProvider: "gitlab_ci",
378
+ eventName: process.env.AGENTRIX_EVENT_NAME || process.env.CI_PIPELINE_SOURCE,
379
+ eventAction: process.env.AGENTRIX_EVENT_ACTION,
380
+ jobUrl: process.env.CI_JOB_URL,
381
+ runUrl: process.env.CI_PIPELINE_URL
382
+ }
383
+ };
384
+ }
385
+ function detectCiContext() {
386
+ return detectGithub() ?? detectGitlab() ?? {};
387
+ }
388
+ function mergeRepo(explicitRepo, detectedRepo) {
389
+ if (!explicitRepo && !detectedRepo) {
390
+ return void 0;
391
+ }
392
+ return {
393
+ ...detectedRepo ?? {},
394
+ ...explicitRepo ?? {}
395
+ };
396
+ }
397
+ function buildRequest(options, detected) {
398
+ const repo = mergeRepo(options.repo, detected.repo);
399
+ const request = {
400
+ agent: options.agent,
401
+ title: options.title,
402
+ prompt: options.prompt,
403
+ outputSchema: options.outputSchema,
404
+ repo: {
405
+ gitServerId: repo?.gitServerId,
406
+ serverRepoId: repo?.serverRepoId,
407
+ owner: repo?.owner,
408
+ name: repo?.name
409
+ },
410
+ git: {
411
+ ref: options.ref ?? detected.git?.ref,
412
+ sha: options.sha ?? detected.git?.sha,
413
+ baseRef: options.baseRef ?? detected.git?.baseRef,
414
+ headRef: options.headRef ?? detected.git?.headRef,
415
+ prNumber: options.prNumber ?? detected.git?.prNumber,
416
+ issueNumber: options.issueNumber ?? detected.git?.issueNumber
417
+ },
418
+ execution: {
419
+ capabilityProfile: options.capabilityProfile,
420
+ runnerId: options.runnerId
421
+ },
422
+ context: detected.context?.ciProvider ? {
423
+ ciProvider: detected.context.ciProvider,
424
+ eventName: process.env.AGENTRIX_EVENT_NAME || detected.context.eventName,
425
+ eventAction: process.env.AGENTRIX_EVENT_ACTION || detected.context.eventAction,
426
+ jobUrl: process.env.AGENTRIX_JOB_URL || detected.context.jobUrl,
427
+ runUrl: process.env.AGENTRIX_RUN_URL || detected.context.runUrl,
428
+ payload: detected.context.payload
429
+ } : void 0,
430
+ metadata: Object.keys(options.metadata).length > 0 ? options.metadata : void 0
431
+ };
432
+ return CreateCiRunRequestSchema.parse(request);
433
+ }
434
+ function resolveBaseUrl(options) {
435
+ const value = options.baseUrl || process.env.AGENTRIX_BASE_URL;
436
+ if (!value) {
437
+ throw new Error("Missing --base-url and AGENTRIX_BASE_URL");
438
+ }
439
+ return value.replace(/\/+$/, "");
440
+ }
441
+ function resolveApiKey(options) {
442
+ const value = options.apiKey || process.env.AGENTRIX_API_KEY;
443
+ if (!value) {
444
+ throw new Error("Missing --api-key and AGENTRIX_API_KEY");
445
+ }
446
+ return value;
447
+ }
448
+ async function requestJson(method, url, apiKey, body) {
449
+ const response = await fetch(url, {
450
+ method,
451
+ headers: {
452
+ Authorization: `Bearer ${apiKey}`,
453
+ "Content-Type": "application/json"
454
+ },
455
+ body: body === void 0 ? void 0 : JSON.stringify(body)
456
+ });
457
+ const text = await response.text();
458
+ const parsed = text ? JSON.parse(text) : void 0;
459
+ if (!response.ok) {
460
+ const message = parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
461
+ throw new Error(message);
462
+ }
463
+ return parsed;
464
+ }
465
+ async function createRun(baseUrl, apiKey, request) {
466
+ const response = await requestJson("POST", `${baseUrl}/v1/ci/runs`, apiKey, request);
467
+ return CreateCiRunResponseSchema.parse(response);
468
+ }
469
+ function exitCodeForStatus(status) {
470
+ return status === "completed" ? 0 : 1;
471
+ }
472
+ function printStandardFields(payload) {
473
+ console.log(`run_id=${payload.runId}`);
474
+ console.log(`status=${payload.status}`);
475
+ console.log(`detail_url=${payload.detailUrl}`);
476
+ if (payload.result) {
477
+ console.log(`result=${payload.result}`);
478
+ }
479
+ }
480
+ function ensureWaitDeadline(options) {
481
+ const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_WAIT_TIMEOUT_SECONDS;
482
+ return Date.now() + timeoutSeconds * 1e3;
483
+ }
484
+ function writeJsonLine(value) {
485
+ process.stdout.write(`${JSON.stringify(value)}
486
+ `);
487
+ }
488
+ function writeStreamEvent(type, data) {
489
+ const record = {
490
+ type,
491
+ data
492
+ };
493
+ writeJsonLine(record);
494
+ }
495
+ function buildFinalResultRecord(status) {
496
+ return {
497
+ runId: status.runId,
498
+ status: status.status,
499
+ result: status.result,
500
+ structuredOutput: status.structuredOutput,
501
+ detailUrl: status.detailUrl
502
+ };
503
+ }
504
+ function writeResultFile(path, status) {
505
+ if (!path) {
506
+ return;
507
+ }
508
+ writeFileSync(path, `${JSON.stringify(buildFinalResultRecord(status), null, 2)}
509
+ `, "utf8");
510
+ }
511
+ function writeAsyncResultFile(path, run) {
512
+ if (!path) {
513
+ return;
514
+ }
515
+ const record = {
516
+ runId: run.runId,
517
+ status: run.status,
518
+ result: "",
519
+ detailUrl: run.detailUrl
520
+ };
521
+ writeFileSync(path, `${JSON.stringify(record, null, 2)}
522
+ `, "utf8");
523
+ }
524
+ function parseSseBlock(block) {
525
+ const normalized = block.replace(/\r/g, "");
526
+ const lines = normalized.split("\n");
527
+ let event = "message";
528
+ const dataLines = [];
529
+ for (const line of lines) {
530
+ if (!line) {
531
+ continue;
532
+ }
533
+ if (line.startsWith(":")) {
534
+ continue;
535
+ }
536
+ if (line.startsWith("event:")) {
537
+ event = line.slice("event:".length).trim();
538
+ continue;
539
+ }
540
+ if (line.startsWith("data:")) {
541
+ dataLines.push(line.slice("data:".length).trimStart());
542
+ }
543
+ }
544
+ if (dataLines.length === 0) {
545
+ return null;
546
+ }
547
+ const dataText = dataLines.join("\n");
548
+ return {
549
+ event,
550
+ data: JSON.parse(dataText)
551
+ };
552
+ }
553
+ async function createRunStream(baseUrl, apiKey, request, deadline) {
554
+ const timeoutMs = Math.max(deadline - Date.now(), 1);
555
+ const controller = new AbortController();
556
+ const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
557
+ try {
558
+ const response = await fetch(`${baseUrl}/v1/ci/runs`, {
559
+ method: "POST",
560
+ headers: {
561
+ Authorization: `Bearer ${apiKey}`,
562
+ "Content-Type": "application/json",
563
+ Accept: "text/event-stream"
564
+ },
565
+ body: JSON.stringify(request),
566
+ signal: controller.signal
567
+ });
568
+ if (!response.ok) {
569
+ const text = await response.text();
570
+ const parsed = text ? JSON.parse(text) : void 0;
571
+ const message = parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
572
+ throw new Error(message);
573
+ }
574
+ if (!response.body) {
575
+ throw new Error("Missing SSE response body");
576
+ }
577
+ const reader = response.body.getReader();
578
+ const decoder = new TextDecoder();
579
+ let buffer = "";
580
+ for (; ; ) {
581
+ const { value, done } = await reader.read();
582
+ if (done) {
583
+ break;
584
+ }
585
+ buffer += decoder.decode(value, { stream: true });
586
+ buffer = buffer.replace(/\r\n/g, "\n");
587
+ while (true) {
588
+ const delimiterIndex = buffer.indexOf("\n\n");
589
+ if (delimiterIndex === -1) {
590
+ break;
591
+ }
592
+ const block = buffer.slice(0, delimiterIndex);
593
+ buffer = buffer.slice(delimiterIndex + 2);
594
+ const parsed = parseSseBlock(block);
595
+ if (!parsed) {
596
+ continue;
597
+ }
598
+ if (parsed.event === "run.created" || parsed.event === "task.event") {
599
+ writeStreamEvent(parsed.event, parsed.data);
600
+ continue;
601
+ }
602
+ if (parsed.event === "run.completed" || parsed.event === "run.failed" || parsed.event === "run.canceled" || parsed.event === "run.timeout") {
603
+ const finalStatus = CiRunStatusResponseSchema.parse(parsed.data);
604
+ writeStreamEvent(parsed.event, finalStatus);
605
+ return finalStatus;
606
+ }
607
+ if (parsed.event === "run.error") {
608
+ writeStreamEvent(parsed.event, parsed.data);
609
+ const error = parsed.data;
610
+ throw new Error(typeof error.message === "string" ? error.message : "CI run stream failed");
611
+ }
612
+ }
613
+ }
614
+ buffer += decoder.decode();
615
+ buffer = buffer.replace(/\r\n/g, "\n");
616
+ throw new Error("CI run stream ended before terminal event");
617
+ } catch (error) {
618
+ if (error instanceof Error && error.name === "AbortError") {
619
+ throw new Error("Timed out waiting for CI run terminal status");
620
+ }
621
+ throw error;
622
+ } finally {
623
+ clearTimeout(timeoutHandle);
624
+ }
625
+ }
626
+ async function main(argv = process.argv.slice(2)) {
627
+ const options = parseArgs(argv);
628
+ const detected = detectCiContext();
629
+ const baseUrl = resolveBaseUrl(options);
630
+ const apiKey = resolveApiKey(options);
631
+ const request = buildRequest(options, detected);
632
+ if (options.responseMode === "async") {
633
+ const run = await createRun(baseUrl, apiKey, request);
634
+ writeAsyncResultFile(options.resultFile, run);
635
+ printStandardFields({
636
+ runId: run.runId,
637
+ status: run.status,
638
+ detailUrl: run.detailUrl
639
+ });
640
+ return 0;
641
+ }
642
+ const deadline = ensureWaitDeadline(options);
643
+ const finalStatus = await createRunStream(baseUrl, apiKey, request, deadline);
644
+ writeResultFile(options.resultFile, finalStatus);
645
+ return exitCodeForStatus(finalStatus.status);
646
+ }
647
+
648
+ main().then((code) => {
649
+ process.exitCode = code;
650
+ }).catch((error) => {
651
+ const message = error instanceof Error ? error.message : String(error);
652
+ console.error(message);
653
+ process.exitCode = 1;
654
+ });
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@agentrix/agentrix-run",
3
+ "version": "0.2.0",
4
+ "description": "Native CI submit-and-observe wrapper for Agentrix",
5
+ "type": "module",
6
+ "bin": {
7
+ "agentrix-run": "./bin/agentrix-run.mjs"
8
+ },
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.cts",
12
+ "exports": {
13
+ ".": {
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ },
18
+ "import": {
19
+ "types": "./dist/index.d.mts",
20
+ "default": "./dist/index.mjs"
21
+ }
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "bin",
27
+ "package.json"
28
+ ],
29
+ "scripts": {
30
+ "typecheck": "tsc --noEmit",
31
+ "build": "shx rm -rf dist && tsc --noEmit && pkgroll",
32
+ "prepublishOnly": "yarn build"
33
+ },
34
+ "dependencies": {
35
+ "@agentrix/shared": "^2.12.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": ">=20",
39
+ "pkgroll": "^2.14.2",
40
+ "shx": "^0.3.3",
41
+ "typescript": "^5"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.org"
46
+ },
47
+ "packageManager": "yarn@1.22.22"
48
+ }