@simular-ai/sai-sdk 0.0.0-placeholder → 0.1.0-alpha.1

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,137 @@
1
+ /**
2
+ * Direct control: drive a cloud computer with Simulang. No agent.
3
+ *
4
+ * Maps to `/v1/machines/{id}/control`, a lease on the machine: at most one per
5
+ * machine, so the machine id is the handle. Research preview, open to every
6
+ * account; model calls the code makes (`callLLM`, `ground`, `stateSatisfies`)
7
+ * are billed as usual.
8
+ */
9
+ import { BlockError, GoneError } from './errors.js';
10
+ import { idOf } from './machines.js';
11
+ import { parseExecResult } from './types.js';
12
+ function controlPath(machineId) {
13
+ return `/v1/machines/${encodeURIComponent(machineId)}/control`;
14
+ }
15
+ export class Control {
16
+ http;
17
+ constructor(http) {
18
+ this.http = http;
19
+ }
20
+ /**
21
+ * Take a cloud computer. Resolves once the lease holds it, up to ~3
22
+ * minutes when a Sai task holds it or it has to start; at once if the lease
23
+ * is already open. Close it when done (`await using`, or `try/finally`):
24
+ * Sai's tasks on that computer wait until then.
25
+ */
26
+ async open(opts) {
27
+ const machineId = idOf(opts.machine);
28
+ const d = await this.http.json('POST', controlPath(machineId), {
29
+ timeoutMs: 240_000,
30
+ });
31
+ return new ControlLease(this.http, machineId, Number(d.expiresAfterIdleSec ?? 0));
32
+ }
33
+ }
34
+ export class ControlLease {
35
+ http;
36
+ machineId;
37
+ idleTimeoutSec;
38
+ closed = false;
39
+ path;
40
+ constructor(http, machineId, idleTimeoutSec) {
41
+ this.http = http;
42
+ this.machineId = machineId;
43
+ this.idleTimeoutSec = idleTimeoutSec;
44
+ this.path = controlPath(machineId);
45
+ }
46
+ async [Symbol.asyncDispose]() {
47
+ await this.close();
48
+ }
49
+ /**
50
+ * Run one Simulang (JavaScript) block. Use `var` for anything the next block
51
+ * needs. A block that throws, times out or is interrupted still resolves:
52
+ * check `result.error`. Throws `GoneError` once the lease has ended (closed,
53
+ * idle, taken by a Sai task, or the computer stopped answering): open again.
54
+ */
55
+ async exec(code, opts = {}) {
56
+ const body = { code };
57
+ if (opts.timeoutMs !== undefined)
58
+ body.timeoutMs = opts.timeoutMs;
59
+ const d = await this.http.json('POST', `${this.path}/exec`, {
60
+ json: body,
61
+ timeoutMs: (opts.timeoutMs ?? 120_000) + 45_000,
62
+ });
63
+ return parseExecResult(d);
64
+ }
65
+ /**
66
+ * Run code from `sai.learn.create()`: its `main` with `inputs` over the parameters' defaults
67
+ * (an argument to `main` replaces its whole default object, so missing inputs keep their
68
+ * defaults). `main`'s return value is `result.result`. A string runs as code with `inputs` as is.
69
+ */
70
+ async run(learned, inputs = {}, opts = {}) {
71
+ const code = typeof learned === 'string' ? learned : learned.code;
72
+ const defaults = typeof learned === 'string'
73
+ ? {}
74
+ : Object.fromEntries(learned.parameters.map((p) => [p.name, p.defaultValue]));
75
+ return this.exec(`${code}\nawait main(${JSON.stringify({ ...defaults, ...inputs })})`, opts);
76
+ }
77
+ /** Run a shell command: `{ stdout, stderr, exitCode, timedOut }`. */
78
+ async shell(command, opts = {}) {
79
+ const args = { command };
80
+ if (opts.cwd)
81
+ args.cwd = opts.cwd;
82
+ const r = await this.exec(`await machine.exec(${JSON.stringify(args)})`, {
83
+ timeoutMs: opts.timeoutMs,
84
+ });
85
+ raiseOnBlockError(r);
86
+ return r.result;
87
+ }
88
+ async readFile(path) {
89
+ const r = await this.exec(`await machine.readFile(${JSON.stringify({ path })})`);
90
+ raiseOnBlockError(r);
91
+ return r.result;
92
+ }
93
+ async writeFile(path, text) {
94
+ const r = await this.exec(`await machine.writeToFile(${JSON.stringify({ path, text, overwrite: true })})`);
95
+ raiseOnBlockError(r);
96
+ }
97
+ /**
98
+ * The whole screen as image bytes (usually JPEG; use `exec` for the MIME type). Needs the
99
+ * computer's screen-capture service; throws `BlockError` where it is off (a page or app
100
+ * snapshot still carries an image).
101
+ */
102
+ async screenshot() {
103
+ const r = await this.exec('var __sai_page = await machine.pageContent(); null');
104
+ raiseOnBlockError(r);
105
+ if (!r.screenshot) {
106
+ throw new BlockError('exception', r.screenshotOmitted ? 'screenshot too large to return' : 'no screenshot');
107
+ }
108
+ return r.screenshot;
109
+ }
110
+ /** Stop the running block at its next step. False if nothing was running. */
111
+ async interrupt() {
112
+ const d = await this.http.json('POST', `${this.path}/interrupt`);
113
+ return Boolean(d?.interrupted);
114
+ }
115
+ /** `{ status: open|closed, closedReason?, lastExecAt, ... }`. */
116
+ get() {
117
+ return this.http.json('GET', this.path);
118
+ }
119
+ /** Clear the REPL and release the computer. Safe to call twice. */
120
+ async close() {
121
+ if (this.closed)
122
+ return;
123
+ try {
124
+ await this.http.request('DELETE', this.path);
125
+ }
126
+ catch (err) {
127
+ if (!(err instanceof GoneError))
128
+ throw err;
129
+ }
130
+ this.closed = true;
131
+ }
132
+ }
133
+ function raiseOnBlockError(r) {
134
+ if (r.error)
135
+ throw new BlockError(r.error.kind, r.error.message);
136
+ }
137
+ //# sourceMappingURL=control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.js","sourceRoot":"","sources":["../src/control.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * A non-2xx response from the Sai API. `code` is the machine-readable `error`
3
+ * string when the server sent one (`machine_busy`, `link_only`, ...); `message`
4
+ * is the human text; `body` is the parsed JSON body, unchanged.
5
+ */
6
+ export declare class SaiError extends Error {
7
+ readonly status: number;
8
+ readonly code: string | undefined;
9
+ readonly body: Record<string, unknown>;
10
+ readonly retryAfter: number | undefined;
11
+ constructor(message: string, opts?: {
12
+ status?: number;
13
+ code?: string;
14
+ body?: Record<string, unknown>;
15
+ retryAfter?: number;
16
+ cause?: unknown;
17
+ });
18
+ }
19
+ /** The API could not be reached (DNS, TLS, timeout). */
20
+ export declare class ConnectionError extends SaiError {
21
+ }
22
+ /** 400: the request was invalid, e.g. several computers and no `machine`. */
23
+ export declare class BadRequestError extends SaiError {
24
+ }
25
+ /** 401: no key, or the key is unknown or revoked. */
26
+ export declare class AuthenticationError extends SaiError {
27
+ }
28
+ /** 403: the key is valid but the account may not do this (plan, ownership). */
29
+ export declare class PermissionDeniedError extends SaiError {
30
+ }
31
+ /** 404. */
32
+ export declare class NotFoundError extends SaiError {
33
+ }
34
+ /** 409: e.g. `machine_busy`, `link_only`, approval no longer pending. */
35
+ export declare class ConflictError extends SaiError {
36
+ }
37
+ /**
38
+ * 410 `control_closed`: the control lease has ended (closed, sat idle, lost
39
+ * the computer to a Sai task, or the computer stopped answering). `body.reason`
40
+ * says which. Open a new one.
41
+ */
42
+ export declare class GoneError extends SaiError {
43
+ }
44
+ /** 429: a rate limit, or a free-plan budget that resets daily. */
45
+ export declare class RateLimitError extends SaiError {
46
+ }
47
+ /** 503: the computer did not come online, or no capacity. Honor `retryAfter`. */
48
+ export declare class ServiceUnavailableError extends SaiError {
49
+ }
50
+ /**
51
+ * A control helper's Simulang block failed on the computer. `kind` is
52
+ * `exception`, `timeout` or `interrupted`.
53
+ */
54
+ export declare class BlockError extends SaiError {
55
+ readonly kind: string;
56
+ constructor(kind: string, message: string);
57
+ }
58
+ /** The approval must be answered by a human in a browser: open `approvalUrl`. */
59
+ export declare class LinkOnlyApprovalError extends ConflictError {
60
+ get approvalUrl(): string | undefined;
61
+ }
62
+ /**
63
+ * 403 `spend_cap_reached`: the key reached its monthly spend limit. Not
64
+ * retryable: the limit resets at `resetsAt` (ISO 8601, 00:00 UTC on the 1st),
65
+ * or raise it for the key in the portal.
66
+ */
67
+ export declare class SpendCapReachedError extends PermissionDeniedError {
68
+ get resetsAt(): string | undefined;
69
+ }
70
+ export declare function errorFor(status: number, body: Record<string, unknown>, retryAfter: number | undefined): SaiError;
package/dist/errors.js ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * A non-2xx response from the Sai API. `code` is the machine-readable `error`
3
+ * string when the server sent one (`machine_busy`, `link_only`, ...); `message`
4
+ * is the human text; `body` is the parsed JSON body, unchanged.
5
+ */
6
+ export class SaiError extends Error {
7
+ status;
8
+ code;
9
+ body;
10
+ retryAfter;
11
+ constructor(message, opts = {}) {
12
+ super(message, opts.cause === undefined ? undefined : { cause: opts.cause });
13
+ this.name = new.target.name;
14
+ this.status = opts.status ?? 0;
15
+ this.code = opts.code;
16
+ this.body = opts.body ?? {};
17
+ this.retryAfter = opts.retryAfter;
18
+ }
19
+ }
20
+ /** The API could not be reached (DNS, TLS, timeout). */
21
+ export class ConnectionError extends SaiError {
22
+ }
23
+ /** 400: the request was invalid, e.g. several computers and no `machine`. */
24
+ export class BadRequestError extends SaiError {
25
+ }
26
+ /** 401: no key, or the key is unknown or revoked. */
27
+ export class AuthenticationError extends SaiError {
28
+ }
29
+ /** 403: the key is valid but the account may not do this (plan, ownership). */
30
+ export class PermissionDeniedError extends SaiError {
31
+ }
32
+ /** 404. */
33
+ export class NotFoundError extends SaiError {
34
+ }
35
+ /** 409: e.g. `machine_busy`, `link_only`, approval no longer pending. */
36
+ export class ConflictError extends SaiError {
37
+ }
38
+ /**
39
+ * 410 `control_closed`: the control lease has ended (closed, sat idle, lost
40
+ * the computer to a Sai task, or the computer stopped answering). `body.reason`
41
+ * says which. Open a new one.
42
+ */
43
+ export class GoneError extends SaiError {
44
+ }
45
+ /** 429: a rate limit, or a free-plan budget that resets daily. */
46
+ export class RateLimitError extends SaiError {
47
+ }
48
+ /** 503: the computer did not come online, or no capacity. Honor `retryAfter`. */
49
+ export class ServiceUnavailableError extends SaiError {
50
+ }
51
+ /**
52
+ * A control helper's Simulang block failed on the computer. `kind` is
53
+ * `exception`, `timeout` or `interrupted`.
54
+ */
55
+ export class BlockError extends SaiError {
56
+ kind;
57
+ constructor(kind, message) {
58
+ super(`${kind}: ${message}`, { code: kind });
59
+ this.kind = kind;
60
+ }
61
+ }
62
+ /** The approval must be answered by a human in a browser: open `approvalUrl`. */
63
+ export class LinkOnlyApprovalError extends ConflictError {
64
+ get approvalUrl() {
65
+ const url = this.body.approvalUrl;
66
+ return typeof url === 'string' ? url : undefined;
67
+ }
68
+ }
69
+ /**
70
+ * 403 `spend_cap_reached`: the key reached its monthly spend limit. Not
71
+ * retryable: the limit resets at `resetsAt` (ISO 8601, 00:00 UTC on the 1st),
72
+ * or raise it for the key in the portal.
73
+ */
74
+ export class SpendCapReachedError extends PermissionDeniedError {
75
+ get resetsAt() {
76
+ const at = this.body.resetsAt;
77
+ return typeof at === 'string' ? at : undefined;
78
+ }
79
+ }
80
+ const BY_CODE = {
81
+ link_only: LinkOnlyApprovalError,
82
+ spend_cap_reached: SpendCapReachedError,
83
+ };
84
+ const BY_STATUS = {
85
+ 400: BadRequestError,
86
+ 401: AuthenticationError,
87
+ 403: PermissionDeniedError,
88
+ 404: NotFoundError,
89
+ 409: ConflictError,
90
+ 410: GoneError,
91
+ 429: RateLimitError,
92
+ 503: ServiceUnavailableError,
93
+ };
94
+ export function errorFor(status, body, retryAfter) {
95
+ const err = body.error;
96
+ const detail = body.message;
97
+ // Two body shapes: `{ error: "<human text>" }` (/v1/agents) and
98
+ // `{ error: "<code>", message: "<human text>" }` (/v1/machines/{id}/control, newer errors).
99
+ const code = typeof err === 'string' && (detail !== undefined || looksLikeCode(err)) ? err : undefined;
100
+ let message = typeof detail === 'string' ? detail : typeof err === 'string' ? err : `HTTP ${status}`;
101
+ if (status === 401)
102
+ message += ' (SAI_API_KEY was rejected: it may be revoked.)';
103
+ const Cls = (code !== undefined && Object.hasOwn(BY_CODE, code) ? BY_CODE[code] : undefined) ??
104
+ BY_STATUS[status] ??
105
+ SaiError;
106
+ // The `Retry-After` header wins; `retryAfterSeconds` is the same value in the body (`rate_limited`).
107
+ const wait = retryAfter ?? (typeof body.retryAfterSeconds === 'number' ? body.retryAfterSeconds : undefined);
108
+ return new Cls(message, { status, code, body, retryAfter: wait });
109
+ }
110
+ function looksLikeCode(s) {
111
+ return s.length > 0 && !s.includes(' ') && s === s.toLowerCase();
112
+ }
113
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,MAAM,CAAQ;IACd,IAAI,CAAoB;IACxB,IAAI,CAAyB;IAC7B,UAAU,CAAoB;IAEvC,YACE,OAAe,EACf,OAMI,EAAE;QAEN,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QAC5E,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAA;QAC3B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;IACnC,CAAC;CACF;AAED,wDAAwD;AACxD,MAAM,OAAO,eAAgB,SAAQ,QAAQ;CAAG;AAChD,6EAA6E;AAC7E,MAAM,OAAO,eAAgB,SAAQ,QAAQ;CAAG;AAChD,qDAAqD;AACrD,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;CAAG;AACpD,+EAA+E;AAC/E,MAAM,OAAO,qBAAsB,SAAQ,QAAQ;CAAG;AACtD,WAAW;AACX,MAAM,OAAO,aAAc,SAAQ,QAAQ;CAAG;AAC9C,yEAAyE;AACzE,MAAM,OAAO,aAAc,SAAQ,QAAQ;CAAG;AAC9C;;;;GAIG;AACH,MAAM,OAAO,SAAU,SAAQ,QAAQ;CAAG;AAC1C,kEAAkE;AAClE,MAAM,OAAO,cAAe,SAAQ,QAAQ;CAAG;AAC/C,iFAAiF;AACjF,MAAM,OAAO,uBAAwB,SAAQ,QAAQ;CAAG;AAExD;;;GAGG;AACH,MAAM,OAAO,UAAW,SAAQ,QAAQ;IAC7B,IAAI,CAAQ;IACrB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,OAAO,qBAAsB,SAAQ,aAAa;IACtD,IAAI,WAAW;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAA;QACjC,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;IAClD,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,oBAAqB,SAAQ,qBAAqB;IAC7D,IAAI,QAAQ;QACV,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAA;QAC7B,OAAO,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAChD,CAAC;CACF;AAED,MAAM,OAAO,GAA6C;IACxD,SAAS,EAAE,qBAAqB;IAChC,iBAAiB,EAAE,oBAAoB;CACxC,CAAA;AAED,MAAM,SAAS,GAAoC;IACjD,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,qBAAqB;IAC1B,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,uBAAuB;CAC7B,CAAA;AAED,MAAM,UAAU,QAAQ,CACtB,MAAc,EACd,IAA6B,EAC7B,UAA8B;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAA;IACtB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAA;IAC3B,gEAAgE;IAChE,4FAA4F;IAC5F,MAAM,IAAI,GACR,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;IAC3F,IAAI,OAAO,GACT,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAA;IACxF,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,iDAAiD,CAAA;IAChF,MAAM,GAAG,GACP,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChF,SAAS,CAAC,MAAM,CAAC;QACjB,QAAQ,CAAA;IACV,qGAAqG;IACrG,MAAM,IAAI,GACR,UAAU,IAAI,CAAC,OAAO,IAAI,CAAC,iBAAiB,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACjG,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;AACnE,CAAC;AAED,SAAS,aAAa,CAAC,CAAS;IAC9B,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAA;AAClE,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export declare const DEFAULT_BASE_URL = "https://api.simular.ai";
2
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
3
+ /** The bearer token, or a function that returns it before each request. */
4
+ export type Credential = string | (() => Promise<string>);
5
+ export interface RequestOptions {
6
+ json?: unknown;
7
+ params?: Record<string, string | number | undefined>;
8
+ body?: BodyInit;
9
+ headers?: Record<string, string>;
10
+ timeoutMs?: number;
11
+ }
12
+ /** The one place requests are made: auth header, error mapping, timeouts. */
13
+ export declare class Http {
14
+ private readonly credential;
15
+ private readonly timeoutMs;
16
+ private readonly fetchImpl;
17
+ private readonly baseUrl;
18
+ private readonly headers;
19
+ constructor(credential: Credential, baseUrl: string, timeoutMs: number, versionTag: string | undefined, fetchImpl: FetchLike);
20
+ request(method: string, path: string, opts?: RequestOptions): Promise<Response>;
21
+ json<T>(method: string, path: string, opts?: RequestOptions): Promise<T>;
22
+ }
package/dist/http.js ADDED
@@ -0,0 +1,83 @@
1
+ import { ConnectionError, errorFor } from './errors.js';
2
+ import { VERSION } from './version.js';
3
+ export const DEFAULT_BASE_URL = 'https://api.simular.ai';
4
+ /** The one place requests are made: auth header, error mapping, timeouts. */
5
+ export class Http {
6
+ credential;
7
+ timeoutMs;
8
+ fetchImpl;
9
+ baseUrl;
10
+ headers;
11
+ constructor(credential, baseUrl, timeoutMs, versionTag, fetchImpl) {
12
+ this.credential = credential;
13
+ this.timeoutMs = timeoutMs;
14
+ this.fetchImpl = fetchImpl;
15
+ this.baseUrl = baseUrl.replace(/\/+$/, '');
16
+ this.headers = { 'User-Agent': `sai-typescript/${VERSION}` };
17
+ if (typeof credential === 'string')
18
+ this.headers.Authorization = `Bearer ${credential}`;
19
+ // routes the request to a tagged API revision
20
+ if (versionTag)
21
+ this.headers['x-sai-version'] = versionTag;
22
+ }
23
+ async request(method, path, opts = {}) {
24
+ const url = new URL(this.baseUrl + path);
25
+ for (const [k, v] of Object.entries(opts.params ?? {})) {
26
+ if (v !== undefined)
27
+ url.searchParams.set(k, String(v));
28
+ }
29
+ const headers = { ...this.headers, ...opts.headers };
30
+ if (typeof this.credential === 'function') {
31
+ headers.Authorization = `Bearer ${await this.credential()}`;
32
+ }
33
+ let body = opts.body;
34
+ if (opts.json !== undefined) {
35
+ headers['Content-Type'] = 'application/json';
36
+ body = JSON.stringify(opts.json);
37
+ }
38
+ let res;
39
+ try {
40
+ res = await this.fetchImpl(url.toString(), {
41
+ method,
42
+ headers,
43
+ body,
44
+ signal: AbortSignal.timeout(opts.timeoutMs ?? this.timeoutMs),
45
+ });
46
+ }
47
+ catch (err) {
48
+ const name = err instanceof Error ? err.name : '';
49
+ if (name === 'TimeoutError' || name === 'AbortError') {
50
+ throw new ConnectionError(`Request to ${url.host} timed out.`, { cause: err });
51
+ }
52
+ const cause = err instanceof Error ? err.cause : undefined;
53
+ const detail = cause?.message ?? (err instanceof Error ? err.message : String(err));
54
+ throw new ConnectionError(`Could not reach the Sai API at ${url.host}: ${detail}. Check the network and SAI_API_URL.`, { cause: err });
55
+ }
56
+ if (res.status >= 400)
57
+ throw await toError(res);
58
+ return res;
59
+ }
60
+ async json(method, path, opts = {}) {
61
+ const res = await this.request(method, path, opts);
62
+ const text = await res.text();
63
+ return (text ? JSON.parse(text) : null);
64
+ }
65
+ }
66
+ async function toError(res) {
67
+ const text = await res.text().catch(() => '');
68
+ let body;
69
+ try {
70
+ const parsed = JSON.parse(text);
71
+ body =
72
+ parsed && typeof parsed === 'object' && !Array.isArray(parsed)
73
+ ? parsed
74
+ : { error: String(parsed) };
75
+ }
76
+ catch {
77
+ body = { error: text.slice(0, 500) || res.statusText };
78
+ }
79
+ const raw = res.headers.get('retry-after');
80
+ const retryAfter = raw !== null && raw !== '' && !Number.isNaN(Number(raw)) ? Number(raw) : undefined;
81
+ return errorFor(res.status, body, retryAfter);
82
+ }
83
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * TypeScript SDK for the Sai API.
3
+ *
4
+ * - `sai.sessions`: give Sai a task on a computer and get the answer back.
5
+ * - `sai.learn`, `sai.judge`: turn recorded turns into Simulang code, repair it, judge turns.
6
+ * - `sai.control`: drive a cloud computer yourself with Simulang.
7
+ * - `sai.machines`, `sai.models`, `sai.files`, `sai.account`.
8
+ * - `sai.tenants`, `sai.tenantMachines`: your customers under an Organisation (an Organisation Key).
9
+ */
10
+ export { Sai, type SaiOptions } from './client.js';
11
+ export { AuthenticationError, BadRequestError, BlockError, ConflictError, ConnectionError, GoneError, LinkOnlyApprovalError, NotFoundError, PermissionDeniedError, RateLimitError, SaiError, ServiceUnavailableError, SpendCapReachedError, } from './errors.js';
12
+ export { ControlLease } from './control.js';
13
+ export type { MachineRef } from './machines.js';
14
+ export type { TenantMachineRef, TenantRef } from './tenants.js';
15
+ export type { TurnRef } from './turns.js';
16
+ export type { JudgeOptions, LearnOptions } from './learn.js';
17
+ export { Session, Turn, type ApprovalAnswer, type ApprovalHandler, type SendOptions, type WaitOptions, } from './sessions.js';
18
+ export { EVENT_KINDS, TURN_RECORD_STATUSES, TURN_STATUSES, type Account, type Approval, type Attachment, type Check, type CriterionResult, type Enrollment, type Evaluation, type Event, type Evidence, type EventKind, type ExecError, type ExecResult, type Judgement, type LearnParameter, type LearnResult, type LiveView, type Machine, type Model, type Tenant, type TenantMachine, type TenantPage, type TenantToken, type TrajectoryStep, type TurnRecord, type TurnRecordStatus, type TurnResult, type TurnStatus, type Usage, } from './types.js';
19
+ export { VERSION } from './version.js';
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * TypeScript SDK for the Sai API.
3
+ *
4
+ * - `sai.sessions`: give Sai a task on a computer and get the answer back.
5
+ * - `sai.learn`, `sai.judge`: turn recorded turns into Simulang code, repair it, judge turns.
6
+ * - `sai.control`: drive a cloud computer yourself with Simulang.
7
+ * - `sai.machines`, `sai.models`, `sai.files`, `sai.account`.
8
+ * - `sai.tenants`, `sai.tenantMachines`: your customers under an Organisation (an Organisation Key).
9
+ */
10
+ export { Sai } from './client.js';
11
+ export { AuthenticationError, BadRequestError, BlockError, ConflictError, ConnectionError, GoneError, LinkOnlyApprovalError, NotFoundError, PermissionDeniedError, RateLimitError, SaiError, ServiceUnavailableError, SpendCapReachedError, } from './errors.js';
12
+ export { ControlLease } from './control.js';
13
+ export { Session, Turn, } from './sessions.js';
14
+ export { EVENT_KINDS, TURN_RECORD_STATUSES, TURN_STATUSES, } from './types.js';
15
+ export { VERSION } from './version.js';
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,GAAG,EAAmB,MAAM,aAAa,CAAA;AAClD,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,UAAU,EACV,aAAa,EACb,eAAe,EACf,SAAS,EACT,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAK3C,OAAO,EACL,OAAO,EACP,IAAI,GAKL,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,aAAa,GA6Bd,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Learn and judge (research preview): turn recorded turns into Simulang code you own, repair that
3
+ * code, and judge turns against criteria you choose now.
4
+ *
5
+ * Maps to `/v1/agents/learn` and `/v1/agents/judge`.
6
+ */
7
+ import type { Http } from './http.js';
8
+ import type { TurnRef } from './turns.js';
9
+ import { type Check, type Evaluation, type Judgement, type LearnResult } from './types.js';
10
+ export interface LearnOptions {
11
+ /** `'Submit claim for {{patient}} to {{payer}}'`; default: the first turn's template, else inferred. */
12
+ template?: string;
13
+ /** Code to repair (heal). Without it, the turns are converted. */
14
+ initialCode?: string;
15
+ /** How `initialCode` did on runs. */
16
+ evaluations?: Evaluation[];
17
+ /** Your note for the repair, e.g. "the Save dialog changed". */
18
+ feedback?: string;
19
+ /** A model id from `sai.models.list()`; default `anthropic/claude-opus-5-5`. */
20
+ model?: string;
21
+ /** The same key again returns the earlier result and runs nothing. */
22
+ idempotencyKey?: string;
23
+ timeoutMs?: number;
24
+ }
25
+ export declare class Learn {
26
+ private readonly http;
27
+ constructor(http: Http);
28
+ /**
29
+ * Make Simulang code from `turns` (ordered, one session, each sent with `recordTrajectory`), or,
30
+ * with `initialCode`, repair it from `evaluations`, `feedback` or new `turns`. Waits for the
31
+ * result (about two minutes). A result with `validation.errors` comes back `failed`.
32
+ */
33
+ create(turns: TurnRef[], opts?: LearnOptions): Promise<LearnResult>;
34
+ /** A learn call's result, e.g. after the request timed out on your side. */
35
+ get(learnId: string): Promise<LearnResult>;
36
+ }
37
+ export interface JudgeOptions {
38
+ /** Default: the first turn's message. */
39
+ task?: string;
40
+ idempotencyKey?: string;
41
+ timeoutMs?: number;
42
+ }
43
+ export declare class Judge {
44
+ private readonly http;
45
+ constructor(http: Http);
46
+ /**
47
+ * Judge recorded `turns` (one session, as one task) against `criteria`: a sentence for a model
48
+ * to judge, or a list of `Check`s. Nothing runs again, and the turns do not change.
49
+ */
50
+ create(turns: TurnRef[], criteria: string | Check[], opts?: JudgeOptions): Promise<Judgement>;
51
+ get(judgementId: string): Promise<Judgement>;
52
+ }
package/dist/learn.js ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Learn and judge (research preview): turn recorded turns into Simulang code you own, repair that
3
+ * code, and judge turns against criteria you choose now.
4
+ *
5
+ * Maps to `/v1/agents/learn` and `/v1/agents/judge`.
6
+ */
7
+ import { parseJudgement, parseLearnResult, } from './types.js';
8
+ /** A conversion is two model calls of up to a minute or two each, plus one retry. */
9
+ const LEARN_TIMEOUT_MS = 600_000;
10
+ const JUDGE_TIMEOUT_MS = 180_000;
11
+ /** The last log lines of a run sent with an evaluation; the API takes 20 KB per result. */
12
+ const EVAL_LOG_LINES = 30;
13
+ const turnId = (t) => {
14
+ const id = typeof t === 'string' ? t : t.id;
15
+ if (!id)
16
+ throw new Error('This turn has no id: the message was answered without starting one.');
17
+ return id;
18
+ };
19
+ function evaluationJson(e) {
20
+ const r = e.result;
21
+ return {
22
+ inputs: e.inputs,
23
+ ...(r && {
24
+ result: {
25
+ result: r.result,
26
+ logs: r.logs.slice(-EVAL_LOG_LINES),
27
+ ...(r.error && { error: { kind: r.error.kind, message: r.error.message } }),
28
+ },
29
+ }),
30
+ ...(e.judgement !== undefined && {
31
+ judgement: typeof e.judgement === 'string' ? e.judgement : e.judgement.id,
32
+ }),
33
+ ...(e.passed !== undefined && { passed: e.passed }),
34
+ ...(e.note !== undefined && { note: e.note }),
35
+ };
36
+ }
37
+ export class Learn {
38
+ http;
39
+ constructor(http) {
40
+ this.http = http;
41
+ }
42
+ /**
43
+ * Make Simulang code from `turns` (ordered, one session, each sent with `recordTrajectory`), or,
44
+ * with `initialCode`, repair it from `evaluations`, `feedback` or new `turns`. Waits for the
45
+ * result (about two minutes). A result with `validation.errors` comes back `failed`.
46
+ */
47
+ async create(turns, opts = {}) {
48
+ const body = { turns: turns.map(turnId) };
49
+ if (opts.template !== undefined)
50
+ body.template = opts.template;
51
+ if (opts.initialCode !== undefined)
52
+ body.initialCode = opts.initialCode;
53
+ if (opts.evaluations)
54
+ body.evaluations = opts.evaluations.map(evaluationJson);
55
+ if (opts.feedback !== undefined)
56
+ body.feedback = opts.feedback;
57
+ if (opts.model !== undefined)
58
+ body.model = opts.model;
59
+ if (opts.idempotencyKey !== undefined)
60
+ body.idempotency_key = opts.idempotencyKey;
61
+ return parseLearnResult(await this.http.json('POST', '/v1/agents/learn', {
62
+ json: body,
63
+ timeoutMs: opts.timeoutMs ?? LEARN_TIMEOUT_MS,
64
+ }));
65
+ }
66
+ /** A learn call's result, e.g. after the request timed out on your side. */
67
+ async get(learnId) {
68
+ return parseLearnResult(await this.http.json('GET', `/v1/agents/learn/${encodeURIComponent(learnId)}`));
69
+ }
70
+ }
71
+ export class Judge {
72
+ http;
73
+ constructor(http) {
74
+ this.http = http;
75
+ }
76
+ /**
77
+ * Judge recorded `turns` (one session, as one task) against `criteria`: a sentence for a model
78
+ * to judge, or a list of `Check`s. Nothing runs again, and the turns do not change.
79
+ */
80
+ async create(turns, criteria, opts = {}) {
81
+ const body = { turns: turns.map(turnId), criteria };
82
+ if (opts.task !== undefined)
83
+ body.task = opts.task;
84
+ if (opts.idempotencyKey !== undefined)
85
+ body.idempotency_key = opts.idempotencyKey;
86
+ return parseJudgement(await this.http.json('POST', '/v1/agents/judge', {
87
+ json: body,
88
+ timeoutMs: opts.timeoutMs ?? JUDGE_TIMEOUT_MS,
89
+ }));
90
+ }
91
+ async get(judgementId) {
92
+ return parseJudgement(await this.http.json('GET', `/v1/agents/judge/${encodeURIComponent(judgementId)}`));
93
+ }
94
+ }
95
+ //# sourceMappingURL=learn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learn.js","sourceRoot":"","sources":["../src/learn.ts"],"names":[],"mappings":"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"}