@the-open-engine/zeroshot 6.27.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 (59) hide show
  1. package/README.md +3 -3
  2. package/cli/index.js +28 -21
  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/credential-lock.d.ts +1 -0
  17. package/lib/target/credential-lock.js +38 -0
  18. package/lib/target/credential-store.d.ts +27 -0
  19. package/lib/target/credential-store.js +113 -0
  20. package/lib/target/device-flow.d.ts +42 -0
  21. package/lib/target/device-flow.js +109 -0
  22. package/lib/target/discovery.d.ts +12 -0
  23. package/lib/target/discovery.js +97 -0
  24. package/lib/target/hosted-run/client.d.ts +26 -0
  25. package/lib/target/hosted-run/client.js +158 -0
  26. package/lib/target/hosted-run/commands.d.ts +4 -0
  27. package/lib/target/hosted-run/commands.js +113 -0
  28. package/lib/target/hosted-run/contracts.d.ts +49 -0
  29. package/lib/target/hosted-run/contracts.js +3 -0
  30. package/lib/target/hosted-run/input.d.ts +5 -0
  31. package/lib/target/hosted-run/input.js +200 -0
  32. package/lib/target/hosted-run.d.ts +4 -0
  33. package/lib/target/hosted-run.js +12 -0
  34. package/lib/target/index.d.ts +6 -0
  35. package/lib/target/index.js +38 -0
  36. package/lib/target/target-registry.d.ts +45 -0
  37. package/lib/target/target-registry.js +132 -0
  38. package/lib/target/target-session.d.ts +40 -0
  39. package/lib/target/target-session.js +163 -0
  40. package/package.json +25 -7
  41. package/scripts/audit-production-dependencies.js +150 -0
  42. package/scripts/opcore-agent-gate.js +159 -0
  43. package/scripts/opcore-agent-tool-overlays.js +154 -0
  44. package/scripts/opcore-introduced-check.js +290 -0
  45. package/src/agent-cli-provider/adapters/codex.ts +1 -0
  46. package/src/isolation-manager.js +16 -1
  47. package/src/target/bounded-json.ts +48 -0
  48. package/src/target/credential-lock.ts +35 -0
  49. package/src/target/credential-store.ts +107 -0
  50. package/src/target/device-flow.ts +168 -0
  51. package/src/target/discovery.ts +131 -0
  52. package/src/target/hosted-run/client.ts +198 -0
  53. package/src/target/hosted-run/commands.ts +140 -0
  54. package/src/target/hosted-run/contracts.ts +53 -0
  55. package/src/target/hosted-run/input.ts +199 -0
  56. package/src/target/hosted-run.ts +8 -0
  57. package/src/target/index.ts +55 -0
  58. package/src/target/target-registry.ts +174 -0
  59. package/src/target/target-session.ts +253 -0
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FakeCredentialStore = exports.KeyringCredentialStore = exports.TARGET_ACCOUNT = exports.CredentialStoreUnavailableError = void 0;
37
+ exports.targetServiceKey = targetServiceKey;
38
+ class CredentialStoreUnavailableError extends Error {
39
+ constructor(message) {
40
+ super(message ??
41
+ 'OS secure store unavailable. Install libsecret (Linux), or run on macOS/Windows. No plaintext fallback.');
42
+ this.name = 'CredentialStoreUnavailableError';
43
+ }
44
+ }
45
+ exports.CredentialStoreUnavailableError = CredentialStoreUnavailableError;
46
+ function targetServiceKey(targetId) {
47
+ return `zeroshot-target-${targetId}`;
48
+ }
49
+ exports.TARGET_ACCOUNT = 'refresh-token';
50
+ class KeyringCredentialStore {
51
+ Entry;
52
+ constructor(Entry) {
53
+ this.Entry = Entry;
54
+ }
55
+ static async create() {
56
+ let keyringModule;
57
+ try {
58
+ keyringModule = await Promise.resolve().then(() => __importStar(require('@napi-rs/keyring')));
59
+ }
60
+ catch {
61
+ throw new CredentialStoreUnavailableError();
62
+ }
63
+ if (!keyringModule.Entry) {
64
+ throw new CredentialStoreUnavailableError();
65
+ }
66
+ return new KeyringCredentialStore(keyringModule.Entry);
67
+ }
68
+ async get(service, account) {
69
+ try {
70
+ const entry = new this.Entry(service, account);
71
+ return entry.getPassword();
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ async set(service, account, token) {
78
+ const entry = new this.Entry(service, account);
79
+ entry.setPassword(token);
80
+ }
81
+ async delete(service, account) {
82
+ try {
83
+ const entry = new this.Entry(service, account);
84
+ entry.deletePassword();
85
+ }
86
+ catch {
87
+ // Already deleted or not present
88
+ }
89
+ }
90
+ }
91
+ exports.KeyringCredentialStore = KeyringCredentialStore;
92
+ class FakeCredentialStore {
93
+ store = new Map();
94
+ key(service, account) {
95
+ return `${service}::${account}`;
96
+ }
97
+ async get(service, account) {
98
+ return this.store.get(this.key(service, account)) ?? null;
99
+ }
100
+ async set(service, account, token) {
101
+ this.store.set(this.key(service, account), token);
102
+ }
103
+ async delete(service, account) {
104
+ this.store.delete(this.key(service, account));
105
+ }
106
+ has(service, account) {
107
+ return this.store.has(this.key(service, account));
108
+ }
109
+ clear() {
110
+ this.store.clear();
111
+ }
112
+ }
113
+ exports.FakeCredentialStore = FakeCredentialStore;
@@ -0,0 +1,42 @@
1
+ export interface DeviceCodeResponse {
2
+ readonly device_code: string;
3
+ readonly user_code: string;
4
+ readonly verification_uri: string;
5
+ readonly verification_uri_complete?: string;
6
+ readonly expires_in: number;
7
+ readonly interval: number;
8
+ }
9
+ export interface TokenResponse {
10
+ readonly access_token: string;
11
+ readonly refresh_token: string;
12
+ readonly token_type: string;
13
+ readonly expires_in: number;
14
+ readonly organization?: {
15
+ readonly id: string;
16
+ readonly name: string;
17
+ };
18
+ }
19
+ export interface HttpTransport {
20
+ fetch(url: string, init: RequestInit & {
21
+ redirect: 'error';
22
+ }): Promise<Response>;
23
+ }
24
+ export interface Clock {
25
+ now(): number;
26
+ }
27
+ export interface DeviceIdentity {
28
+ readonly token: string;
29
+ readonly label: string;
30
+ }
31
+ export declare class DeviceFlowDeniedError extends Error {
32
+ constructor();
33
+ }
34
+ export declare class DeviceFlowExpiredError extends Error {
35
+ constructor();
36
+ }
37
+ export declare class UnboundSessionError extends Error {
38
+ readonly verificationUri: string;
39
+ constructor(verificationUri: string);
40
+ }
41
+ export declare function requestDeviceCode(deviceAuthorizationEndpoint: string, clientId: string, http: HttpTransport, signal?: AbortSignal): Promise<DeviceCodeResponse>;
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>;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnboundSessionError = exports.DeviceFlowExpiredError = exports.DeviceFlowDeniedError = void 0;
4
+ exports.requestDeviceCode = requestDeviceCode;
5
+ exports.pollForToken = pollForToken;
6
+ class DeviceFlowDeniedError extends Error {
7
+ constructor() {
8
+ super('Device authorization denied by user');
9
+ this.name = 'DeviceFlowDeniedError';
10
+ }
11
+ }
12
+ exports.DeviceFlowDeniedError = DeviceFlowDeniedError;
13
+ class DeviceFlowExpiredError extends Error {
14
+ constructor() {
15
+ super('Device authorization code expired');
16
+ this.name = 'DeviceFlowExpiredError';
17
+ }
18
+ }
19
+ exports.DeviceFlowExpiredError = DeviceFlowExpiredError;
20
+ class UnboundSessionError extends Error {
21
+ verificationUri;
22
+ constructor(verificationUri) {
23
+ super(`Session not bound to an organization. Re-approve at ${verificationUri} and select an organization.`);
24
+ this.name = 'UnboundSessionError';
25
+ this.verificationUri = verificationUri;
26
+ }
27
+ }
28
+ exports.UnboundSessionError = UnboundSessionError;
29
+ const DEFAULT_CLOCK = { now: () => Date.now() };
30
+ function sleep(ms, signal) {
31
+ return new Promise((resolve, reject) => {
32
+ if (signal?.aborted) {
33
+ reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
34
+ return;
35
+ }
36
+ const timer = setTimeout(resolve, ms);
37
+ signal?.addEventListener('abort', () => {
38
+ clearTimeout(timer);
39
+ reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
40
+ }, { once: true });
41
+ });
42
+ }
43
+ async function requestDeviceCode(deviceAuthorizationEndpoint, clientId, http, signal) {
44
+ const body = new URLSearchParams({
45
+ client_id: clientId,
46
+ scope: 'openid',
47
+ });
48
+ const init = {
49
+ method: 'POST',
50
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
51
+ body: body.toString(),
52
+ redirect: 'error',
53
+ };
54
+ if (signal)
55
+ init.signal = signal;
56
+ const response = await http.fetch(deviceAuthorizationEndpoint, init);
57
+ if (!response.ok) {
58
+ const text = await response.text();
59
+ throw new Error(`Device code request failed (${response.status}): ${text}`);
60
+ }
61
+ return (await response.json());
62
+ }
63
+ async function pollForToken(tokenEndpoint, clientId, deviceCode, interval, expiresIn, http, clock = DEFAULT_CLOCK, signal, deviceIdentity) {
64
+ const deadline = clock.now() + expiresIn * 1000;
65
+ let currentInterval = interval;
66
+ while (clock.now() < deadline) {
67
+ if (signal?.aborted) {
68
+ throw signal.reason ?? new DOMException('Aborted', 'AbortError');
69
+ }
70
+ await sleep(currentInterval * 1000, signal);
71
+ const body = new URLSearchParams({
72
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
73
+ device_code: deviceCode,
74
+ client_id: clientId,
75
+ });
76
+ if (deviceIdentity) {
77
+ body.set('audience', 'admin');
78
+ body.set('device_token', deviceIdentity.token);
79
+ body.set('device_label', deviceIdentity.label);
80
+ }
81
+ const init = {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
84
+ body: body.toString(),
85
+ redirect: 'error',
86
+ };
87
+ if (signal)
88
+ init.signal = signal;
89
+ const response = await http.fetch(tokenEndpoint, init);
90
+ if (response.ok) {
91
+ return (await response.json());
92
+ }
93
+ const errorBody = (await response.json());
94
+ switch (errorBody.error) {
95
+ case 'authorization_pending':
96
+ continue;
97
+ case 'slow_down':
98
+ currentInterval += 5;
99
+ continue;
100
+ case 'access_denied':
101
+ throw new DeviceFlowDeniedError();
102
+ case 'expired_token':
103
+ throw new DeviceFlowExpiredError();
104
+ default:
105
+ throw new Error(`Token endpoint error: ${errorBody.error}`);
106
+ }
107
+ }
108
+ throw new DeviceFlowExpiredError();
109
+ }
@@ -0,0 +1,12 @@
1
+ import type { HttpTransport } from './device-flow.ts';
2
+ export interface TargetSessionEndpoints {
3
+ readonly deviceAuthorizationEndpoint: string;
4
+ readonly tokenEndpoint: string;
5
+ readonly revocationEndpoint?: string;
6
+ readonly clientId: string;
7
+ readonly capsuleApiBaseUrl: string;
8
+ }
9
+ export declare class TargetDiscoveryError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export declare function discoverTargetSessionEndpoints(targetUrl: string, http: HttpTransport): Promise<TargetSessionEndpoints>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TargetDiscoveryError = void 0;
4
+ exports.discoverTargetSessionEndpoints = discoverTargetSessionEndpoints;
5
+ const bounded_json_ts_1 = require("./bounded-json.js");
6
+ const DISCOVERY_PATH = '/.well-known/openengine-hosted-target';
7
+ const MAX_DISCOVERY_BYTES = 64 * 1024;
8
+ class TargetDiscoveryError extends Error {
9
+ constructor(message) {
10
+ super(`Target discovery failed: ${message}`);
11
+ this.name = 'TargetDiscoveryError';
12
+ }
13
+ }
14
+ exports.TargetDiscoveryError = TargetDiscoveryError;
15
+ function record(value, field) {
16
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
17
+ throw new TargetDiscoveryError(`${field} must be an object`);
18
+ }
19
+ return value;
20
+ }
21
+ function stringField(source, field) {
22
+ const value = source[field];
23
+ if (typeof value !== 'string' || value.length === 0) {
24
+ throw new TargetDiscoveryError(`${field} must be a non-empty string`);
25
+ }
26
+ return value;
27
+ }
28
+ function safeEndpoint(value, field, serviceOrigin) {
29
+ if (typeof value !== 'string') {
30
+ throw new TargetDiscoveryError(`${field} must be an absolute URL`);
31
+ }
32
+ let endpoint;
33
+ try {
34
+ endpoint = new URL(value);
35
+ }
36
+ catch {
37
+ throw new TargetDiscoveryError(`${field} must be an absolute URL`);
38
+ }
39
+ if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
40
+ throw new TargetDiscoveryError(`${field} contains forbidden URL components`);
41
+ }
42
+ if (endpoint.origin !== serviceOrigin) {
43
+ throw new TargetDiscoveryError(`${field} must remain on the target origin`);
44
+ }
45
+ return endpoint.href;
46
+ }
47
+ async function fetchDocument(http, url) {
48
+ const response = await http.fetch(url, {
49
+ method: 'GET',
50
+ headers: { Accept: 'application/json' },
51
+ redirect: 'error',
52
+ });
53
+ if (!response.ok) {
54
+ throw new TargetDiscoveryError(`request failed with status ${response.status}`);
55
+ }
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');
61
+ }
62
+ async function discoverTargetSessionEndpoints(targetUrl, http) {
63
+ const target = new URL(targetUrl);
64
+ const discovery = await fetchDocument(http, new URL(DISCOVERY_PATH, target).href);
65
+ if (discovery.kind !== 'openengine.hosted-target/v1') {
66
+ throw new TargetDiscoveryError('unsupported hosted-target version');
67
+ }
68
+ if (discovery.organization_binding !== 'device_approval') {
69
+ throw new TargetDiscoveryError('unsupported organization binding');
70
+ }
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(/\/$/, '');
77
+ const metadataUrl = safeEndpoint(oauth.metadata_url, 'oauth.metadata_url', target.origin);
78
+ const deviceEndpoint = safeEndpoint(oauth.device_authorization_endpoint, 'oauth.device_authorization_endpoint', target.origin);
79
+ const tokenEndpoint = safeEndpoint(oauth.token_endpoint, 'oauth.token_endpoint', target.origin);
80
+ const clientId = stringField(oauth, 'client_id');
81
+ const metadata = await fetchDocument(http, metadataUrl);
82
+ const metadataDeviceEndpoint = safeEndpoint(metadata.device_authorization_endpoint, 'device_authorization_endpoint', target.origin);
83
+ const metadataTokenEndpoint = safeEndpoint(metadata.token_endpoint, 'token_endpoint', target.origin);
84
+ if (metadataDeviceEndpoint !== deviceEndpoint || metadataTokenEndpoint !== tokenEndpoint) {
85
+ throw new TargetDiscoveryError('OAuth metadata does not match hosted-target discovery');
86
+ }
87
+ const revocationEndpoint = metadata.revocation_endpoint === undefined
88
+ ? undefined
89
+ : safeEndpoint(metadata.revocation_endpoint, 'revocation_endpoint', target.origin);
90
+ return {
91
+ deviceAuthorizationEndpoint: deviceEndpoint,
92
+ tokenEndpoint,
93
+ ...(revocationEndpoint === undefined ? {} : { revocationEndpoint }),
94
+ clientId,
95
+ capsuleApiBaseUrl,
96
+ };
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
+ }