@the-open-engine/zeroshot 6.28.0 → 6.29.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.
Files changed (47) hide show
  1. package/README.md +3 -3
  2. package/cli/index.js +21 -223
  3. package/docker/zeroshot-cluster/Dockerfile +2 -3
  4. package/docker/zeroshot-oecp/Cargo.toml +12 -0
  5. package/docker/zeroshot-oecp/Dockerfile +65 -0
  6. package/docker/zeroshot-oecp/src/main.rs +32 -0
  7. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  8. package/lib/agent-cli-provider/adapters/codex.js +1 -0
  9. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  10. package/lib/cluster-worker/engine-adapter.js +11 -15
  11. package/lib/cluster-worker/engine-input.js +14 -0
  12. package/lib/cluster-worker/profiles.js +42 -1
  13. package/lib/start-cluster.js +3 -3
  14. package/lib/target/bounded-json.d.ts +6 -0
  15. package/lib/target/bounded-json.js +43 -0
  16. package/lib/target/device-flow.d.ts +5 -1
  17. package/lib/target/device-flow.js +6 -1
  18. package/lib/target/discovery.d.ts +1 -0
  19. package/lib/target/discovery.js +12 -35
  20. package/lib/target/hosted-run/client.d.ts +26 -0
  21. package/lib/target/hosted-run/client.js +158 -0
  22. package/lib/target/hosted-run/commands.d.ts +4 -0
  23. package/lib/target/hosted-run/commands.js +113 -0
  24. package/lib/target/hosted-run/contracts.d.ts +49 -0
  25. package/lib/target/hosted-run/contracts.js +3 -0
  26. package/lib/target/hosted-run/input.d.ts +5 -0
  27. package/lib/target/hosted-run/input.js +200 -0
  28. package/lib/target/hosted-run.d.ts +4 -0
  29. package/lib/target/hosted-run.js +12 -0
  30. package/lib/target/target-session.d.ts +1 -0
  31. package/lib/target/target-session.js +2 -1
  32. package/package.json +16 -3
  33. package/scripts/audit-production-dependencies.js +150 -0
  34. package/scripts/opcore-agent-gate.js +159 -0
  35. package/scripts/opcore-agent-tool-overlays.js +154 -0
  36. package/scripts/opcore-introduced-check.js +290 -0
  37. package/src/agent-cli-provider/adapters/codex.ts +1 -0
  38. package/src/isolation-manager.js +16 -1
  39. package/src/target/bounded-json.ts +48 -0
  40. package/src/target/device-flow.ts +14 -3
  41. package/src/target/discovery.ts +17 -35
  42. package/src/target/hosted-run/client.ts +198 -0
  43. package/src/target/hosted-run/commands.ts +140 -0
  44. package/src/target/hosted-run/contracts.ts +53 -0
  45. package/src/target/hosted-run/input.ts +199 -0
  46. package/src/target/hosted-run.ts +8 -0
  47. package/src/target/target-session.ts +5 -1
@@ -0,0 +1,6 @@
1
+ interface BoundedJsonErrors {
2
+ readonly tooLarge: () => Error;
3
+ readonly invalid: () => Error;
4
+ }
5
+ export declare function readBoundedJson(response: Response, maxBytes: number, errors: BoundedJsonErrors): Promise<unknown>;
6
+ export {};
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readBoundedJson = readBoundedJson;
4
+ async function readBoundedJson(response, maxBytes, errors) {
5
+ const declared = response.headers.get('content-length');
6
+ if (declared !== null && Number(declared) > maxBytes)
7
+ throw errors.tooLarge();
8
+ const reader = response.body?.getReader();
9
+ if (!reader) {
10
+ const bytes = new Uint8Array(await response.arrayBuffer());
11
+ if (bytes.byteLength > maxBytes)
12
+ throw errors.tooLarge();
13
+ return parseJson(bytes, errors);
14
+ }
15
+ const chunks = [];
16
+ let total = 0;
17
+ for (;;) {
18
+ const { done, value } = await reader.read();
19
+ if (done)
20
+ break;
21
+ total += value.byteLength;
22
+ if (total > maxBytes) {
23
+ await reader.cancel();
24
+ throw errors.tooLarge();
25
+ }
26
+ chunks.push(value);
27
+ }
28
+ const bytes = new Uint8Array(total);
29
+ let offset = 0;
30
+ for (const chunk of chunks) {
31
+ bytes.set(chunk, offset);
32
+ offset += chunk.byteLength;
33
+ }
34
+ return parseJson(bytes, errors);
35
+ }
36
+ function parseJson(bytes, errors) {
37
+ try {
38
+ return JSON.parse(new TextDecoder().decode(bytes));
39
+ }
40
+ catch {
41
+ throw errors.invalid();
42
+ }
43
+ }
@@ -24,6 +24,10 @@ export interface HttpTransport {
24
24
  export interface Clock {
25
25
  now(): number;
26
26
  }
27
+ export interface DeviceIdentity {
28
+ readonly token: string;
29
+ readonly label: string;
30
+ }
27
31
  export declare class DeviceFlowDeniedError extends Error {
28
32
  constructor();
29
33
  }
@@ -35,4 +39,4 @@ export declare class UnboundSessionError extends Error {
35
39
  constructor(verificationUri: string);
36
40
  }
37
41
  export declare function requestDeviceCode(deviceAuthorizationEndpoint: string, clientId: string, http: HttpTransport, signal?: AbortSignal): Promise<DeviceCodeResponse>;
38
- export declare function pollForToken(tokenEndpoint: string, clientId: string, deviceCode: string, interval: number, expiresIn: number, http: HttpTransport, clock?: Clock, signal?: AbortSignal): Promise<TokenResponse>;
42
+ export declare function pollForToken(tokenEndpoint: string, clientId: string, deviceCode: string, interval: number, expiresIn: number, http: HttpTransport, clock?: Clock, signal?: AbortSignal, deviceIdentity?: DeviceIdentity): Promise<TokenResponse>;
@@ -60,7 +60,7 @@ async function requestDeviceCode(deviceAuthorizationEndpoint, clientId, http, si
60
60
  }
61
61
  return (await response.json());
62
62
  }
63
- async function pollForToken(tokenEndpoint, clientId, deviceCode, interval, expiresIn, http, clock = DEFAULT_CLOCK, signal) {
63
+ async function pollForToken(tokenEndpoint, clientId, deviceCode, interval, expiresIn, http, clock = DEFAULT_CLOCK, signal, deviceIdentity) {
64
64
  const deadline = clock.now() + expiresIn * 1000;
65
65
  let currentInterval = interval;
66
66
  while (clock.now() < deadline) {
@@ -73,6 +73,11 @@ async function pollForToken(tokenEndpoint, clientId, deviceCode, interval, expir
73
73
  device_code: deviceCode,
74
74
  client_id: clientId,
75
75
  });
76
+ if (deviceIdentity) {
77
+ body.set('audience', 'admin');
78
+ body.set('device_token', deviceIdentity.token);
79
+ body.set('device_label', deviceIdentity.label);
80
+ }
76
81
  const init = {
77
82
  method: 'POST',
78
83
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
@@ -4,6 +4,7 @@ export interface TargetSessionEndpoints {
4
4
  readonly tokenEndpoint: string;
5
5
  readonly revocationEndpoint?: string;
6
6
  readonly clientId: string;
7
+ readonly capsuleApiBaseUrl: string;
7
8
  }
8
9
  export declare class TargetDiscoveryError extends Error {
9
10
  constructor(message: string);
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TargetDiscoveryError = void 0;
4
4
  exports.discoverTargetSessionEndpoints = discoverTargetSessionEndpoints;
5
+ const bounded_json_ts_1 = require("./bounded-json.js");
5
6
  const DISCOVERY_PATH = '/.well-known/openengine-hosted-target';
6
7
  const MAX_DISCOVERY_BYTES = 64 * 1024;
7
8
  class TargetDiscoveryError extends Error {
@@ -43,40 +44,6 @@ function safeEndpoint(value, field, serviceOrigin) {
43
44
  }
44
45
  return endpoint.href;
45
46
  }
46
- async function readBoundedJson(response) {
47
- const declaredLength = response.headers.get('content-length');
48
- if (declaredLength !== null && Number(declaredLength) > MAX_DISCOVERY_BYTES) {
49
- throw new TargetDiscoveryError('response exceeds the size limit');
50
- }
51
- if (!response.body)
52
- return response.json();
53
- const reader = response.body.getReader();
54
- const chunks = [];
55
- let total = 0;
56
- while (true) {
57
- const { done, value } = await reader.read();
58
- if (done)
59
- break;
60
- total += value.byteLength;
61
- if (total > MAX_DISCOVERY_BYTES) {
62
- await reader.cancel();
63
- throw new TargetDiscoveryError('response exceeds the size limit');
64
- }
65
- chunks.push(value);
66
- }
67
- const bytes = new Uint8Array(total);
68
- let offset = 0;
69
- for (const chunk of chunks) {
70
- bytes.set(chunk, offset);
71
- offset += chunk.byteLength;
72
- }
73
- try {
74
- return JSON.parse(new TextDecoder().decode(bytes));
75
- }
76
- catch {
77
- throw new TargetDiscoveryError('response is not valid JSON');
78
- }
79
- }
80
47
  async function fetchDocument(http, url) {
81
48
  const response = await http.fetch(url, {
82
49
  method: 'GET',
@@ -86,7 +53,11 @@ async function fetchDocument(http, url) {
86
53
  if (!response.ok) {
87
54
  throw new TargetDiscoveryError(`request failed with status ${response.status}`);
88
55
  }
89
- return record(await readBoundedJson(response), 'response');
56
+ const body = await (0, bounded_json_ts_1.readBoundedJson)(response, MAX_DISCOVERY_BYTES, {
57
+ tooLarge: () => new TargetDiscoveryError('response exceeds the size limit'),
58
+ invalid: () => new TargetDiscoveryError('response is not valid JSON'),
59
+ });
60
+ return record(body, 'response');
90
61
  }
91
62
  async function discoverTargetSessionEndpoints(targetUrl, http) {
92
63
  const target = new URL(targetUrl);
@@ -98,6 +69,11 @@ async function discoverTargetSessionEndpoints(targetUrl, http) {
98
69
  throw new TargetDiscoveryError('unsupported organization binding');
99
70
  }
100
71
  const oauth = record(discovery.oauth, 'oauth');
72
+ const capsuleProtocol = record(discovery.capsule_protocol, 'capsule_protocol');
73
+ if (capsuleProtocol.name !== 'openengine.capsules/v1' || capsuleProtocol.major_version !== 1) {
74
+ throw new TargetDiscoveryError('unsupported capsule protocol');
75
+ }
76
+ const capsuleApiBaseUrl = safeEndpoint(capsuleProtocol.base_url, 'capsule_protocol.base_url', target.origin).replace(/\/$/, '');
101
77
  const metadataUrl = safeEndpoint(oauth.metadata_url, 'oauth.metadata_url', target.origin);
102
78
  const deviceEndpoint = safeEndpoint(oauth.device_authorization_endpoint, 'oauth.device_authorization_endpoint', target.origin);
103
79
  const tokenEndpoint = safeEndpoint(oauth.token_endpoint, 'oauth.token_endpoint', target.origin);
@@ -116,5 +92,6 @@ async function discoverTargetSessionEndpoints(targetUrl, http) {
116
92
  tokenEndpoint,
117
93
  ...(revocationEndpoint === undefined ? {} : { revocationEndpoint }),
118
94
  clientId,
95
+ capsuleApiBaseUrl,
119
96
  };
120
97
  }
@@ -0,0 +1,26 @@
1
+ import { type TargetAccessTokenProvider } from '../target-session.ts';
2
+ import type { HostedRunDependencies, HostedRunIntent } from './contracts.ts';
3
+ export declare const UUID: RegExp;
4
+ export declare const TERMINAL_STATES: Set<string>;
5
+ export interface HostedContext {
6
+ readonly targetName: string;
7
+ readonly organization: string;
8
+ readonly client: RunIntentClient;
9
+ }
10
+ export declare class HostedRunHttpError extends Error {
11
+ readonly status: number;
12
+ readonly code: string | null;
13
+ constructor(status: number, message: string, code?: string | null);
14
+ }
15
+ export declare class RunIntentClient {
16
+ private readonly baseUrl;
17
+ private readonly organization;
18
+ private readonly tokenProvider;
19
+ private readonly fetch;
20
+ constructor(baseUrl: string, organization: string, tokenProvider: TargetAccessTokenProvider, fetchImplementation: typeof globalThis.fetch);
21
+ submit(body: Record<string, unknown>, submissionKey: string): Promise<HostedRunIntent>;
22
+ get(intentId: string): Promise<HostedRunIntent>;
23
+ cancel(intentId: string): Promise<HostedRunIntent>;
24
+ private request;
25
+ }
26
+ export declare function context(targetName: string, deps: HostedRunDependencies): Promise<HostedContext>;
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RunIntentClient = exports.HostedRunHttpError = exports.TERMINAL_STATES = exports.UUID = void 0;
4
+ exports.context = context;
5
+ const credential_lock_ts_1 = require("../credential-lock.js");
6
+ const credential_store_ts_1 = require("../credential-store.js");
7
+ const discovery_ts_1 = require("../discovery.js");
8
+ const target_registry_ts_1 = require("../target-registry.js");
9
+ const target_session_ts_1 = require("../target-session.js");
10
+ const bounded_json_ts_1 = require("../bounded-json.js");
11
+ const MAX_RESPONSE_BYTES = 1024 * 1024;
12
+ exports.UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+ exports.TERMINAL_STATES = new Set(['succeeded', 'failed', 'cancelled', 'expired']);
14
+ const RUN_INTENT_STATES = new Set([
15
+ 'queued',
16
+ 'provisioning',
17
+ 'running',
18
+ 'cancelling',
19
+ ...exports.TERMINAL_STATES,
20
+ ]);
21
+ function wait(milliseconds) {
22
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
23
+ }
24
+ class HostedRunHttpError extends Error {
25
+ status;
26
+ code;
27
+ constructor(status, message, code = null) {
28
+ super(message);
29
+ this.name = 'HostedRunHttpError';
30
+ this.status = status;
31
+ this.code = code;
32
+ }
33
+ }
34
+ exports.HostedRunHttpError = HostedRunHttpError;
35
+ function validateIntent(value) {
36
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
37
+ throw new Error('Zero Cloud returned an invalid run intent');
38
+ }
39
+ const record = value;
40
+ if (typeof record['intent_id'] !== 'string' ||
41
+ !exports.UUID.test(record['intent_id']) ||
42
+ typeof record['state'] !== 'string' ||
43
+ !RUN_INTENT_STATES.has(record['state'])) {
44
+ throw new Error('Zero Cloud returned an invalid run intent');
45
+ }
46
+ const result = record['result'];
47
+ if (result !== null && (typeof result !== 'object' || Array.isArray(result))) {
48
+ throw new Error('Zero Cloud returned an invalid run intent result');
49
+ }
50
+ return record;
51
+ }
52
+ class RunIntentClient {
53
+ baseUrl;
54
+ organization;
55
+ tokenProvider;
56
+ fetch;
57
+ constructor(baseUrl, organization, tokenProvider, fetchImplementation) {
58
+ this.baseUrl = baseUrl.replace(/\/$/, '');
59
+ this.organization = organization;
60
+ this.tokenProvider = tokenProvider;
61
+ this.fetch = fetchImplementation;
62
+ }
63
+ async submit(body, submissionKey) {
64
+ let failure;
65
+ for (let attempt = 0; attempt < 2; attempt += 1) {
66
+ try {
67
+ return await this.request('', {
68
+ method: 'POST',
69
+ body: JSON.stringify(body),
70
+ headers: { 'Idempotency-Key': submissionKey },
71
+ });
72
+ }
73
+ catch (error) {
74
+ failure = error;
75
+ if (error instanceof HostedRunHttpError && error.status < 500)
76
+ throw error;
77
+ if (attempt === 0)
78
+ await wait(250);
79
+ }
80
+ }
81
+ throw failure;
82
+ }
83
+ get(intentId) {
84
+ return this.request(`/${encodeURIComponent(intentId)}`, { method: 'GET' });
85
+ }
86
+ cancel(intentId) {
87
+ return this.request(`/${encodeURIComponent(intentId)}`, { method: 'DELETE' });
88
+ }
89
+ async request(suffix, options) {
90
+ const token = await this.tokenProvider.getAccessToken();
91
+ const url = `${this.baseUrl}/orgs/${encodeURIComponent(this.organization)}/run-intents${suffix}`;
92
+ const init = {
93
+ method: options.method,
94
+ redirect: 'error',
95
+ headers: {
96
+ Accept: 'application/json',
97
+ Authorization: `Bearer ${token}`,
98
+ 'Content-Type': 'application/json',
99
+ ...options.headers,
100
+ },
101
+ };
102
+ if (options.body !== undefined)
103
+ init.body = options.body;
104
+ let response;
105
+ try {
106
+ response = await this.fetch(url, init);
107
+ }
108
+ catch (error) {
109
+ throw new Error(`Zero Cloud request failed: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ const body = await (0, bounded_json_ts_1.readBoundedJson)(response, MAX_RESPONSE_BYTES, {
112
+ tooLarge: () => new Error('Zero Cloud response exceeded 1 MiB'),
113
+ invalid: () => new Error('Zero Cloud returned invalid JSON'),
114
+ });
115
+ if (!response.ok) {
116
+ const problem = body;
117
+ const code = typeof problem['code'] === 'string' ? problem['code'] : null;
118
+ const message = typeof problem['message'] === 'string' ? problem['message'] : 'request failed';
119
+ throw new HostedRunHttpError(response.status, `Zero Cloud ${response.status}: ${message}`, code);
120
+ }
121
+ return validateIntent(body);
122
+ }
123
+ }
124
+ exports.RunIntentClient = RunIntentClient;
125
+ async function context(targetName, deps) {
126
+ const environment = deps.environment ?? process.env;
127
+ const target = (0, target_registry_ts_1.getTarget)(targetName, deps.settings);
128
+ if (!target)
129
+ throw new Error(`Target "${targetName}" not found.`);
130
+ const http = {
131
+ fetch: (url, init) => (deps.fetch ?? globalThis.fetch)(url, init),
132
+ };
133
+ const discovery = await (0, discovery_ts_1.discoverTargetSessionEndpoints)(target.url, http);
134
+ const ephemeralToken = environment['ZEROSHOT_TARGET_ACCESS_TOKEN']?.trim();
135
+ const ephemeralOrganization = environment['ZEROSHOT_TARGET_ORGANIZATION']?.trim();
136
+ if ((ephemeralToken && !ephemeralOrganization) || (!ephemeralToken && ephemeralOrganization)) {
137
+ throw new Error('ZEROSHOT_TARGET_ACCESS_TOKEN and ZEROSHOT_TARGET_ORGANIZATION must be provided together');
138
+ }
139
+ let organization;
140
+ let tokenProvider;
141
+ if (ephemeralToken && ephemeralOrganization) {
142
+ organization = ephemeralOrganization;
143
+ tokenProvider = { getAccessToken: async () => ephemeralToken };
144
+ }
145
+ else {
146
+ if (!target.organization) {
147
+ throw new Error(`Login required. Run: zeroshot target login ${targetName}`);
148
+ }
149
+ organization = target.organization.id;
150
+ const credentials = await credential_store_ts_1.KeyringCredentialStore.create();
151
+ tokenProvider = (0, target_session_ts_1.getAccessTokenProvider)(targetName, target, credentials, () => (0, credential_lock_ts_1.acquireTargetLock)(target.id), { http, discoveryEndpoints: discovery });
152
+ }
153
+ return {
154
+ targetName,
155
+ organization,
156
+ client: new RunIntentClient(discovery.capsuleApiBaseUrl, organization, tokenProvider, deps.fetch ?? globalThis.fetch),
157
+ };
158
+ }
@@ -0,0 +1,4 @@
1
+ import type { HostedOptions, HostedRunDependencies, HostedRunIntent } from './contracts.ts';
2
+ export declare function runHosted(input: string, options: HostedOptions, deps: HostedRunDependencies): Promise<HostedRunIntent | Record<string, unknown> | null>;
3
+ export declare function statusHostedRun(targetName: string, intentId: string, shouldFollow: boolean, deps: HostedRunDependencies): Promise<HostedRunIntent | Record<string, unknown> | null>;
4
+ export declare function cancelHostedRun(targetName: string, intentId: string, deps: HostedRunDependencies): Promise<HostedRunIntent>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runHosted = runHosted;
7
+ exports.statusHostedRun = statusHostedRun;
8
+ exports.cancelHostedRun = cancelHostedRun;
9
+ const node_crypto_1 = __importDefault(require("node:crypto"));
10
+ const client_ts_1 = require("./client.js");
11
+ const input_ts_1 = require("./input.js");
12
+ const DEFAULT_MODEL = 'openai/gpt-5.4';
13
+ const RUN_INTENT_VERSION = 'zeroshot.run-intent/v1';
14
+ const MAX_RUN_INTENT_BYTES = 1024 * 1024 + 64 * 1024;
15
+ const RUN_INTENT_POLL_MS = 500;
16
+ function output(deps, value) {
17
+ (deps.stdout ?? process.stdout).write(`${value}\n`);
18
+ }
19
+ function wait(milliseconds) {
20
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
21
+ }
22
+ function displayState(intent) {
23
+ return intent.waiting_reason ? `${intent.state} (${intent.waiting_reason})` : intent.state;
24
+ }
25
+ function resumeCommand(value, intentId) {
26
+ return `zeroshot target status ${value.targetName} ${intentId} --follow`;
27
+ }
28
+ async function follow(value, initial, deps) {
29
+ let intent = initial;
30
+ let displayed = null;
31
+ for (;;) {
32
+ const state = displayState(intent);
33
+ if (state !== displayed) {
34
+ output(deps, `Run ${intent.intent_id}: ${state}`);
35
+ displayed = state;
36
+ }
37
+ if (client_ts_1.TERMINAL_STATES.has(intent.state))
38
+ break;
39
+ await (deps.delay ?? wait)(RUN_INTENT_POLL_MS);
40
+ intent = await value.client.get(intent.intent_id);
41
+ }
42
+ if (intent.state === 'succeeded') {
43
+ const summary = intent.result?.['summary'];
44
+ if (typeof summary === 'string' && summary)
45
+ output(deps, summary);
46
+ return intent.result;
47
+ }
48
+ const detail = intent.error_code ? ` (${intent.error_code})` : '';
49
+ throw new Error(`hosted run ${intent.state}${detail}`);
50
+ }
51
+ async function runHosted(input, options, deps) {
52
+ (0, input_ts_1.validateHostedOptions)(options);
53
+ const environment = deps.environment ?? process.env;
54
+ const resolved = await (0, input_ts_1.resolveHostedInput)(input, options, environment);
55
+ const value = await (0, client_ts_1.context)(options.target, deps);
56
+ const body = {
57
+ label: 'zeroshot-cli',
58
+ size: options.size ?? 'standard',
59
+ intent: {
60
+ version: RUN_INTENT_VERSION,
61
+ credentials: {
62
+ githubToken: (0, input_ts_1.githubToken)(environment),
63
+ openrouterApiKey: (0, input_ts_1.providerKey)(environment),
64
+ repository: resolved.repository,
65
+ model: options.model ?? DEFAULT_MODEL,
66
+ },
67
+ request: resolved.request,
68
+ },
69
+ };
70
+ if (Buffer.byteLength(JSON.stringify(body)) > MAX_RUN_INTENT_BYTES) {
71
+ throw new Error('hosted run intent exceeds the 1088 KiB upload limit');
72
+ }
73
+ const submissionKey = options.submissionKey ?? node_crypto_1.default.randomUUID();
74
+ let created;
75
+ try {
76
+ created = await value.client.submit(body, submissionKey);
77
+ }
78
+ catch (error) {
79
+ if (error instanceof client_ts_1.HostedRunHttpError)
80
+ throw error;
81
+ throw new Error(`${error instanceof Error ? error.message : String(error)}. Recover this submission by rerunning ` +
82
+ `the same command with --submission-key ${submissionKey}`, { cause: error });
83
+ }
84
+ output(deps, `Run ${created.intent_id} queued`);
85
+ output(deps, `Resume: ${resumeCommand(value, created.intent_id)}`);
86
+ if (options.detach)
87
+ return created;
88
+ output(deps, 'Ctrl+C disconnects without cancelling.');
89
+ return follow(value, created, deps);
90
+ }
91
+ async function statusHostedRun(targetName, intentId, shouldFollow, deps) {
92
+ if (!client_ts_1.UUID.test(intentId))
93
+ throw new Error('run intent id must be a UUID');
94
+ const value = await (0, client_ts_1.context)(targetName, deps);
95
+ const intent = await value.client.get(intentId);
96
+ if (!shouldFollow) {
97
+ output(deps, JSON.stringify(intent, null, 2));
98
+ return intent;
99
+ }
100
+ if (!client_ts_1.TERMINAL_STATES.has(intent.state)) {
101
+ output(deps, `Following ${intentId}; Ctrl+C disconnects without cancelling.`);
102
+ output(deps, `Resume: ${resumeCommand(value, intentId)}`);
103
+ }
104
+ return follow(value, intent, deps);
105
+ }
106
+ async function cancelHostedRun(targetName, intentId, deps) {
107
+ if (!client_ts_1.UUID.test(intentId))
108
+ throw new Error('run intent id must be a UUID');
109
+ const value = await (0, client_ts_1.context)(targetName, deps);
110
+ const intent = await value.client.cancel(intentId);
111
+ output(deps, `Run ${intent.intent_id}: ${displayState(intent)}`);
112
+ return intent;
113
+ }
@@ -0,0 +1,49 @@
1
+ import type { SettingsPort } from '../target-registry.ts';
2
+ export interface HostedOptions {
3
+ readonly target?: string;
4
+ readonly repository?: string;
5
+ readonly model?: string;
6
+ readonly size?: string;
7
+ readonly submissionKey?: string;
8
+ readonly detach?: boolean;
9
+ readonly pr?: boolean;
10
+ readonly provider?: string;
11
+ readonly config?: string;
12
+ readonly docker?: boolean;
13
+ readonly worktree?: boolean;
14
+ readonly dockerImage?: string;
15
+ readonly strictSchema?: boolean;
16
+ readonly ship?: boolean;
17
+ readonly prBase?: string;
18
+ readonly mergeQueue?: boolean;
19
+ readonly closeIssue?: string;
20
+ readonly workers?: number;
21
+ readonly gitlab?: boolean;
22
+ readonly jira?: boolean;
23
+ readonly devops?: boolean;
24
+ readonly linear?: boolean;
25
+ readonly mount?: readonly string[];
26
+ readonly noMounts?: boolean;
27
+ readonly containerHome?: string;
28
+ }
29
+ export interface HostedRunIntent {
30
+ readonly intent_id: string;
31
+ readonly state: string;
32
+ readonly waiting_reason: string | null;
33
+ readonly result: Record<string, unknown> | null;
34
+ readonly error_code: string | null;
35
+ readonly [key: string]: unknown;
36
+ }
37
+ export interface HostedRunDependencies {
38
+ readonly settings: SettingsPort;
39
+ readonly environment?: NodeJS.ProcessEnv;
40
+ readonly fetch?: typeof globalThis.fetch;
41
+ readonly delay?: (milliseconds: number) => Promise<void>;
42
+ readonly stdout?: {
43
+ write(value: string): void;
44
+ };
45
+ }
46
+ export interface ResolvedInput {
47
+ readonly repository: string;
48
+ readonly request: Record<string, unknown>;
49
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Shared hosted-run command contracts.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ import type { HostedOptions, ResolvedInput } from './contracts.ts';
2
+ export declare function resolveHostedInput(input: string, options: HostedOptions, environment?: NodeJS.ProcessEnv): Promise<ResolvedInput>;
3
+ export declare function githubToken(environment: NodeJS.ProcessEnv): string;
4
+ export declare function providerKey(environment: NodeJS.ProcessEnv): string;
5
+ export declare function validateHostedOptions(options: HostedOptions): void;