@usestatemachines/sdk 0.7.1 → 0.8.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.
Files changed (78) hide show
  1. package/README.md +75 -30
  2. package/agents/SKILL.md +24 -9
  3. package/agents/index.d.ts +2 -0
  4. package/agents/index.js +8 -6
  5. package/agents/llms.txt +25 -5
  6. package/agents/prompt.md +11 -5
  7. package/esm/extensions/client.d.ts +11 -7
  8. package/esm/extensions/client.js +32 -49
  9. package/esm/extensions/credentials.d.ts +25 -8
  10. package/esm/extensions/credentials.js +75 -40
  11. package/esm/extensions/environment.d.ts +1 -1
  12. package/esm/extensions/errors.js +1 -1
  13. package/esm/extensions/index.d.ts +2 -2
  14. package/esm/extensions/yaml.js +14 -12
  15. package/esm/funcs/environments-creators.d.ts +11 -0
  16. package/esm/funcs/environments-creators.js +90 -0
  17. package/esm/funcs/environments-list.js +2 -0
  18. package/esm/funcs/requests-list.js +5 -1
  19. package/esm/funcs/requests-stats.js +5 -1
  20. package/esm/lib/config.d.ts +2 -2
  21. package/esm/lib/config.js +2 -2
  22. package/esm/models/app-entry.d.ts +8 -0
  23. package/esm/models/app-entry.js +11 -0
  24. package/esm/models/create-environment-app.d.ts +25 -0
  25. package/esm/models/create-environment-app.js +14 -0
  26. package/esm/models/create-environment.d.ts +3 -26
  27. package/esm/models/create-environment.js +2 -19
  28. package/esm/models/credential.d.ts +50 -0
  29. package/esm/models/credential.js +60 -0
  30. package/esm/models/credentials.d.ts +26 -5
  31. package/esm/models/credentials.js +20 -3
  32. package/esm/models/environment-failure.d.ts +3 -2
  33. package/esm/models/environment-failure.js +3 -2
  34. package/esm/models/environment.d.ts +4 -1
  35. package/esm/models/environment.js +2 -1
  36. package/esm/models/error.d.ts +1 -0
  37. package/esm/models/error.js +1 -0
  38. package/esm/models/index.d.ts +6 -1
  39. package/esm/models/index.js +6 -1
  40. package/esm/models/mcp-server.d.ts +11 -0
  41. package/esm/models/mcp-server.js +16 -0
  42. package/esm/models/mcp-tool.d.ts +12 -0
  43. package/esm/models/mcp-tool.js +16 -0
  44. package/esm/models/me.d.ts +2 -0
  45. package/esm/models/me.js +2 -0
  46. package/esm/models/operations/create-credentials.d.ts +2 -0
  47. package/esm/models/operations/create-credentials.js +1 -0
  48. package/esm/models/operations/get-request-stats.d.ts +10 -2
  49. package/esm/models/operations/get-request-stats.js +5 -1
  50. package/esm/models/operations/index.d.ts +1 -0
  51. package/esm/models/operations/index.js +1 -0
  52. package/esm/models/operations/list-environment-creators.d.ts +23 -0
  53. package/esm/models/operations/list-environment-creators.js +20 -0
  54. package/esm/models/operations/list-environments.d.ts +5 -1
  55. package/esm/models/operations/list-environments.js +2 -0
  56. package/esm/models/operations/list-requests.d.ts +10 -2
  57. package/esm/models/operations/list-requests.js +5 -1
  58. package/esm/models/request-detail.d.ts +8 -1
  59. package/esm/models/request-detail.js +5 -1
  60. package/esm/models/request-stats.d.ts +8 -0
  61. package/esm/models/request-stats.js +9 -0
  62. package/esm/models/request.d.ts +8 -1
  63. package/esm/models/request.js +5 -1
  64. package/esm/models/service.d.ts +25 -0
  65. package/esm/models/service.js +28 -0
  66. package/esm/models/snapshot.d.ts +7 -0
  67. package/esm/models/snapshot.js +3 -0
  68. package/esm/models/version.d.ts +2 -2
  69. package/esm/models/version.js +2 -2
  70. package/esm/sdk/environments.d.ts +1 -0
  71. package/esm/sdk/environments.js +4 -0
  72. package/esm/types/discriminated-union.d.ts +24 -0
  73. package/esm/types/discriminated-union.js +60 -0
  74. package/esm/types/smart-union.js +2 -1
  75. package/openapi.json +596 -116
  76. package/package.json +1 -1
  77. package/esm/models/api.d.ts +0 -17
  78. package/esm/models/api.js +0 -21
package/agents/llms.txt CHANGED
@@ -4,11 +4,29 @@
4
4
 
5
5
  Use the project's package manager to install or reuse `@usestatemachines/sdk`. It is ESM only and runs on Node.js and Cloudflare Workers, not browsers. `engines` in its `package.json` gives the Node.js versions. TypeScript needs `lib: ["DOM"]` or `@types/node`. `new StateMachines()` reads the API key from `STATEMACHINES_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory. Read the bundled `agents/SKILL.md` for the task workflow. Reuse the project's secret loader and check the key in the process that runs the test, not just the current shell. Installing the skill does not require SDK installation, example files or remote resources. Ask what the user wants to run and wait. Install the SDK and verify access when their chosen task needs it. Run the connection check below only if the user asks to verify the connection. Do not choose a business workflow from the repository.
6
6
 
7
+ ## Get a credential
8
+
9
+ If the project's existing command loads `STATEMACHINES_API_KEY`, reuse that command and secret loader. Keep the same credential loader, CLI profile and API origin across setup commands. Otherwise, run `npx @usestatemachines/cli@0.1.1 whoami --json`. If it succeeds, confirm the account and organization match the task, then use the workspace commands below. If the CLI credential is missing, expired or revoked, start `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show its `verificationUrl` and `userCode`, and wait for approval. A matching code does not identify the requesting computer. After that approval, run `npx @usestatemachines/cli@0.1.1 login --resume --json` with the same profile to finish that attempt. Resume an existing pending login after approval instead of creating another. Report access denial or an API origin mismatch without changing accounts or permissions.
10
+
11
+ If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.
12
+
13
+ For `cli.not_linked`, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask which workspace to use when there is more than one, then run `npx @usestatemachines/cli@0.1.1 link --workspace <workspace-id> --json`. Run the user's existing code with `npx @usestatemachines/cli@0.1.1 run -- <command>`. Never print or commit a credential.
14
+
15
+ ## CLI commands
16
+
17
+ Prefix these commands with `npx @usestatemachines/cli@0.1.1`. Use `help --json` for flags and result schemas:
18
+
19
+ - `login`, `logout`, `whoami`, `profiles list`, and `profiles use`
20
+ - `workspaces list`, `workspaces use`, `link`, `unlink`, and `init`
21
+ - `apps list`, `environments create`, `environments list`, `environments get`, `environments wait`, and `environments delete`
22
+ - `connect`, `snapshots create`, `snapshots list`, `snapshots get`, `snapshots delete`, `requests list`, and `requests get`
23
+ - `run -- <command>`, `env pull`, `api`, `doctor`, `version`, `help`, and `completion`
24
+
7
25
  ## Concepts
8
26
 
9
27
  - Environment: one disposable run of one or more apps. `const env = await sm.environments.create({ apps: ['salesforce'] })` creates it and waits until it is `running`. Use `{ wait: false }` to retain the ID before startup. Wait for `running` inside `try`, then delete the owned environment in `finally`, including after a wait failure.
10
- - Apps: a string names the app, and the app's name in the environment is the same string. Use `{ app: 'salesforce', name: 'crm', settings }` for your own name, for two copies of one app, or for settings. Later calls take the name.
11
- - Credentials: `const crm = await env.connect('salesforce')` returns `url`, `headers`, `token` and `fetch`. `crm.fetch(path)` joins `path` to `url`, adds `headers`, and puts `token` in the header of the API whose path matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0. For Salesforce SOAP, put `crm.token` in the envelope's `SessionHeader/sessionId` yourself. `fetch` never sends your API key, never follows redirects and never retries. `token` and `headers` are secrets.
28
+ - Apps: a string names the app by its ID, and the app's name in the environment is the same string. Use `{ appId: 'salesforce', name: 'crm', setup }` for your own name, for two copies of one app, or for the app's setup. Later calls take the name.
29
+ - Credentials: `const crm = await env.connect('salesforce')` returns `token`, `services` and `fetch`. Each service has an `id`, a `url` and the `headers` its requests need. The credentials always list each `public` service of the app's version. Pass `{ services: [id] }` to `connect()` to also list a service by its ID. A `preview` service is listed only when named. `crm.service('api')` returns the `api` service, which is the app URL. `crm.fetch(path)` joins `path` to the `api` service URL and adds its headers, which carry `Authorization: Bearer` with `token`. For Salesforce SOAP, also put `crm.token` in the envelope's `SessionHeader/sessionId`. `fetch` never sends your API key, never follows redirects and never retries. `token` and each service's `headers` are secrets.
12
30
  - Actor: one of the app's users; requests run with that user's permissions. `connect()` acts as the app's default actor (for Salesforce, the administrator) unless you pass `actorId`. Salesforce actor IDs are its user IDs, such as `005…`. Find the app by name in `env.apps`, then page through `sm.apps.versions(appId)` to find its exact `versionId`. Read actor IDs from that version's `actors`.
13
31
  - Snapshot: the saved data of every app of a running environment, taken at one moment. `await env.snapshots.create({ name: 'seeded' })` waits until it is `ready`. `sm.environments.create({ snapshotId })` creates a new environment from it, in place of `apps`; each app keeps its name and version. A delete during the snapshot fails it with `interrupted`.
14
32
  - Recorded requests: `sm.requests.list({ environmentId })` pages the calls made to the environment's apps. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id, 'request')` or `'response'` returns the full body as bytes. Recording is best effort.
@@ -19,7 +37,7 @@ Use the project's package manager to install or reuse `@usestatemachines/sdk`. I
19
37
 
20
38
  Enums are open: handle values you don't recognise.
21
39
 
22
- - Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. A `failed` environment has `failure`: `{ app, code, message }` with `start_failed`, `restart_failed`, `version_unavailable` or `invalid_settings`.
40
+ - Environment `status`: `starting`, `running`, `failed`, `deleted`. A `deleted` environment has a `reason` when State Machines deleted it: `expired`, `failed` (it never reached `running`) or `cancelled` (staff ended it). The `reason` is `null` when a caller deleted it. When the environment failed, `failure` is `{ appName, code, message }`, and `code` is `start_failed`, `pause_removed`, `version_unavailable`, `invalid_setup` or `invalid_services`. For `invalid_setup` and `invalid_services`, the app refused a value in `setup` or in `services`, and `message` starts with its path.
23
41
  - Snapshot `status`: `saving`, `ready`, `failed`, `deleted`. A failed snapshot has `failure.code` `interrupted` or `internal_error`.
24
42
  - `create` throws `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` when it becomes `deleted` for another reason, and `WaitTimeoutError` when `timeoutMs` passes.
25
43
  - `snapshots.create` throws `SnapshotFailedError` when the snapshot fails and `UnexpectedStatusError` when it was deleted.
@@ -60,11 +78,13 @@ A failed API call throws `StateMachinesError` with `status`, `code`, `message`,
60
78
  | `environment_starting` | 503 | For reads, wait `Retry-After` and retry. Check stored state before retrying an uncertain write. |
61
79
  | `environment_unavailable` | 503 | The app is restarting. For reads, wait `Retry-After`. Check stored state before retrying an uncertain write. |
62
80
  | `response_too_large` | 502 | The response is over the size limit. Ask for less, for example with a smaller page. |
63
- | `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |
81
+ | `invalid_setup` | 503 | The app refused its `setup` after the environment started, such as in a newer version. `error.message` starts with the path of the value. Create another environment with a `setup` that the app accepts. |
82
+ | `invalid_services` | 503 | The app refused a value in `services` after the environment started. `error.message` starts with the path of the value. Create another environment with `services` that the app accepts. |
83
+ | `unsupported_protocol` | 501 | The app does not serve this path, method or content type, or the request body is compressed. |
64
84
 
65
85
  ## Quickstart
66
86
 
67
- Use the project's TypeScript runner and secret loader. For an empty Node.js project, save as `state-quickstart.mts` and run `npx tsx state-quickstart.mts` with the key available. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task; otherwise ask what to run next and wait with no setup environment left running.
87
+ Use the project's TypeScript runner with its secret loader, or pass that runner through `npx @usestatemachines/cli@0.1.1 run -- <command>`. For an empty Node.js project, save as `state-quickstart.mts` and run `npx @usestatemachines/cli@0.1.1 run -- npx tsx state-quickstart.mts`. The check prints the response status and deletes the environment. Only after it prints `200`, report verified setup and the rerun command. Continue only with an explicitly supplied task. Otherwise, ask what to run next and wait with no setup environment left running.
68
88
 
69
89
  ```ts
70
90
  import { StateMachines } from '@usestatemachines/sdk';
package/agents/prompt.md CHANGED
@@ -1,6 +1,12 @@
1
- Install the State Machines skill in this existing repository so I can use disposable app environments with my coding agent.
1
+ Set up State Machines in this existing repository so I can use disposable app environments with my coding agent.
2
2
 
3
- 1. Read the repository instructions. Download the skill from https://docs.usestatemachines.com/skill.md and install it as `state-machines/SKILL.md` in this agent's supported project-local skills directory. Reuse an identical installed copy. Preserve customizations and ask before replacing a different copy. If persistent skills are unsupported, read it for this session and explain the limitation. If the download fails, report the blocker.
4
- 2. Read the skill. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Keep the repository's existing conventions.
5
- 3. The API key belongs in the project's existing secret configuration as `STATEMACHINES_API_KEY`. Never ask me to paste it into chat, print it or commit it. If I need help configuring it, identify the existing secret loader and explain where to save it without reading or displaying secret values.
6
- 4. Report where the skill was installed and whether the agent needs to reload it. Skill installation alone does not verify an API connection. Ask: "What would you like to run or test?" Wait for my answer before making further changes.
3
+ 1. Read the repository instructions and inspect the existing State Machines workspace link and agent skill. Keep the repository's structure and conventions. Check that Node.js meets the CLI's runtime requirement. Use the pinned npx command below. A global CLI installation is not required.
4
+ 2. Run `npx @usestatemachines/cli@0.1.1 whoami --json`. If the project already has a `STATEMACHINES_API_KEY` loader, run the check through that loader without reading or displaying the secret. Use the same credential loader, CLI profile and API origin for every setup command below, including workspace discovery and init. Reuse valid access and confirm the returned account and organization match my task. If the existing API key or CLI profile works, skip sign-in and reuse it for setup. Do not create a replacement API key. Report an access denial or origin mismatch instead of changing accounts or permissions.
5
+ 3. Only when no project API key is configured and the access check reports a missing, expired or revoked CLI credential, run `npx @usestatemachines/cli@0.1.1 login --no-wait --json`. For an expired or revoked saved session, add `--yes` to replace that invalid session in the same profile. Do not replace a valid session for a different account. Show me the returned verification URL and code. Wait for my approval before resuming the same login with `npx @usestatemachines/cli@0.1.1 login --resume --json`, using the same profile. If a login is already pending, resume it after approval instead of starting another. If the request expires, offer a new login. Never ask me to paste a credential into chat, print it or commit it.
6
+ If a pending login's approval URL or code is unavailable, ask before recovering it. Explain that recovery may finish an already-approved login. After confirmation, run `npx @usestatemachines/cli@0.1.1 login --resume --json --stream` with the same profile and API origin. Keep that process running and show its URL and code. Request approval only for a recognized login. Wait for `signed-in` before continuing setup. Do not start another login or resume process. If streaming output is unavailable, report the blocker without reading credential files.
7
+
8
+ 4. Run `npx @usestatemachines/cli@0.1.1 init --agent <this-agent> --yes`, choosing `claude` for Claude Code or `codex` for Codex. Reuse the repository's existing workspace link. If the CLI needs a workspace, run `npx @usestatemachines/cli@0.1.1 workspaces list --json`. Ask me which to use when more than one is available, then pass `--workspace <workspace-id>` to init. Do not replace a different workspace link or a customized skill without my approval. Read the installed skill and use its pinned npx commands for later work.
9
+ 5. Do not create a new project, install the SDK, write example code, change an integration or create remote resources during skill installation. Do not add `--sdk`, `--quickstart` or `--env` to init unless I ask for those changes.
10
+ 6. Report where the skill was installed, which workspace is linked, and whether the agent needs to reload it. Setup alone does not verify an integration. Ask: "What would you like to run or test?" Wait for my answer before making further changes.
11
+
12
+ If this agent cannot run commands or is not Claude Code or Codex, ask me to download https://app.usestatemachines.com/skill.md from my signed-in browser and provide the file. Do not request session cookies or credentials. Install that file in the agent's supported project-local skills directory, preserving any customized skill. If persistent skills are unsupported, read the file for this session and explain the limitation. Report setup as incomplete if the file is unavailable.
@@ -2,6 +2,7 @@ import type { App } from '../models/app.js';
2
2
  import type { AuditEvent } from '../models/audit-event.js';
3
3
  import type { CreateEnvironment } from '../models/create-environment.js';
4
4
  import type { Me } from '../models/me.js';
5
+ import type { CreateCredentialsRequestBody } from '../models/operations/create-credentials.js';
5
6
  import type { GetRequestStatsRequest } from '../models/operations/get-request-stats.js';
6
7
  import type { ListAuditEventsRequest } from '../models/operations/list-audit-events.js';
7
8
  import type { ListEnvironmentsRequest } from '../models/operations/list-environments.js';
@@ -21,7 +22,7 @@ import { type KnownEnvironmentStatus, type KnownSnapshotStatus, type WaitOptions
21
22
  export interface StateMachinesOptions {
22
23
  /** An API key or a member's access token. Defaults to `STATEMACHINES_API_KEY` where a `process` exists. */
23
24
  apiKey?: string;
24
- /** Defaults to `https://api.usestatemachines.com`. */
25
+ /** Defaults to `STATEMACHINES_BASE_URL`, then the production API. Requires HTTPS, except HTTP on localhost or 127.0.0.1. */
25
26
  baseUrl?: string;
26
27
  /** The workspace for calls that name none. Member tokens need it; an API key has its own. */
27
28
  workspaceId?: string;
@@ -41,10 +42,13 @@ export interface CreateEnvironmentOptions extends SignalOptions {
41
42
  idempotencyKey?: string;
42
43
  }
43
44
  export type CreateSnapshotOptions = CreateEnvironmentOptions;
44
- export interface ConnectOptions {
45
- /** Defaults to the app's default actor (for Salesforce, the administrator). */
46
- actorId?: string;
47
- }
45
+ /**
46
+ * The body of the credentials call. `actorId` defaults to the app's default
47
+ * actor (for Salesforce, the administrator). `services` names more services
48
+ * to list, by ID. The credentials always list every `public` service, and a
49
+ * `preview` service only when `services` names it.
50
+ */
51
+ export type ConnectOptions = Pick<CreateCredentialsRequestBody, 'actorId' | 'services'>;
48
52
  export interface Page<T> {
49
53
  data: T[];
50
54
  nextCursor: string | null;
@@ -62,7 +66,7 @@ export type RequestStatsQuery = GetRequestStatsRequest;
62
66
  export declare class StateMachines {
63
67
  #private;
64
68
  constructor(options?: StateMachinesOptions);
65
- /** The caller: its principal, organization, workspace, permissions and limits. */
69
+ /** The caller: its credential, principal, organization, workspace, permissions and limits. */
66
70
  me(): Promise<Me>;
67
71
  readonly apps: {
68
72
  list: (query?: PageQuery) => Promise<Page<App>>;
@@ -89,7 +93,7 @@ export declare class StateMachines {
89
93
  */
90
94
  wait: (environmentId: string, options: WaitOptions<KnownEnvironmentStatus>) => Promise<Environment>;
91
95
  /** Credentials for one app of the environment. */
92
- connect: (environmentId: string, app: string, options?: ConnectOptions) => Promise<Credentials>;
96
+ connect: (environmentId: string, appName: string, options?: ConnectOptions) => Promise<Credentials>;
93
97
  };
94
98
  readonly snapshots: {
95
99
  /** Creates a snapshot of a running environment and, unless `wait` is false, waits until it is `ready`. */
@@ -2,7 +2,7 @@ import { SDK_METADATA } from '../lib/config.js';
2
2
  import { HTTPClient } from '../lib/http.js';
3
3
  import { StateMachines as Generated } from '../sdk/sdk.js';
4
4
  import { bindCredentials } from './credentials.js';
5
- import { CallerAbortedError, StateMachinesError, toStateMachinesError, } from './errors.js';
5
+ import { CallerAbortedError, toStateMachinesError } from './errors.js';
6
6
  import { linkSignal } from './signal.js';
7
7
  import { environmentAt, poll, snapshotAt, } from './wait.js';
8
8
  export class StateMachines {
@@ -10,10 +10,9 @@ export class StateMachines {
10
10
  #fetch;
11
11
  #workspaceId;
12
12
  #callSignals = new Map();
13
- #apis = new Map();
14
13
  #lastCall = 0;
15
14
  constructor(options = {}) {
16
- const apiKey = options.apiKey ?? environmentApiKey();
15
+ const apiKey = options.apiKey ?? environmentVariable('STATEMACHINES_API_KEY');
17
16
  if (!apiKey) {
18
17
  throw new TypeError('Pass apiKey or set STATEMACHINES_API_KEY.');
19
18
  }
@@ -25,6 +24,7 @@ export class StateMachines {
25
24
  if (!(attemptTimeoutMs > 0 && Number.isFinite(attemptTimeoutMs))) {
26
25
  throw new RangeError('attemptTimeoutMs must be a positive, finite number.');
27
26
  }
27
+ const baseUrl = baseUrlOrigin(options.baseUrl ?? environmentVariable('STATEMACHINES_BASE_URL'));
28
28
  const fetcher = options.fetch;
29
29
  // The arrow keeps `this` unbound; runtimes such as workerd reject fetch called on another object.
30
30
  const httpClient = new HTTPClient(fetcher ? { fetcher: (input) => fetcher(input) } : {});
@@ -35,10 +35,10 @@ export class StateMachines {
35
35
  apiKey,
36
36
  userAgent: `statemachines-sdk-ts/${SDK_METADATA.sdkVersion}`,
37
37
  httpClient,
38
- ...(options.baseUrl ? { serverURL: options.baseUrl } : {}),
38
+ ...(baseUrl === undefined ? {} : { serverURL: baseUrl }),
39
39
  });
40
40
  }
41
- /** The caller: its principal, organization, workspace, permissions and limits. */
41
+ /** The caller: its credential, principal, organization, workspace, permissions and limits. */
42
42
  me() {
43
43
  return this.#call(undefined, (call) => this.#sdk.me(call));
44
44
  }
@@ -80,7 +80,7 @@ export class StateMachines {
80
80
  */
81
81
  wait: async (environmentId, options) => this.#bind(await poll(environmentId, (signal) => this.#read(signal, (call) => this.#sdk.environments.get({ environmentId }, call)), environmentAt(options.status), options)),
82
82
  /** Credentials for one app of the environment. */
83
- connect: (environmentId, app, options = {}) => this.#connect(environmentId, null, app, options),
83
+ connect: (environmentId, appName, options = {}) => this.#connect(environmentId, appName, options),
84
84
  };
85
85
  snapshots = {
86
86
  /** Creates a snapshot of a running environment and, unless `wait` is false, waits until it is `ready`. */
@@ -129,51 +129,18 @@ export class StateMachines {
129
129
  ? { ...input, workspaceId: this.#workspaceId }
130
130
  : input;
131
131
  }
132
- async #connect(environmentId, known, app, options) {
133
- const body = options.actorId === undefined ? {} : { actorId: options.actorId };
134
- const [credentials, apis] = await Promise.all([
135
- this.#call(undefined, (call) => this.#sdk.environments.connect({ environmentId, appName: app, body }, call)),
136
- (async () => {
137
- const environment = known ?? (await this.environments.get(environmentId));
138
- const entry = environment.apps.find(({ name }) => name === app);
139
- return entry ? this.#versionApis(entry.appId, entry.versionId) : [];
140
- })(),
141
- ]);
142
- return bindCredentials(credentials, apis, this.#fetch);
143
- }
144
- /** A version's APIs never change, so each is read once per client. */
145
- #versionApis(appId, versionId) {
146
- let apis = this.#apis.get(versionId);
147
- if (!apis) {
148
- apis = this.#findVersion(appId, versionId);
149
- apis.catch(() => this.#apis.delete(versionId));
150
- this.#apis.set(versionId, apis);
151
- }
152
- return apis;
153
- }
154
- async #findVersion(appId, versionId) {
155
- let cursor;
156
- do {
157
- const page = await this.apps.versions(appId, cursor ? { cursor } : {});
158
- const version = page.data.find(({ id }) => id === versionId);
159
- if (version) {
160
- return version.apis;
161
- }
162
- cursor = page.nextCursor ?? undefined;
163
- } while (cursor);
164
- throw new StateMachinesError({
165
- message: `Version ${versionId} of ${appId} was not found.`,
166
- status: null,
167
- code: 'not_found',
168
- requestId: null,
169
- fields: null,
170
- retryAfterMs: null,
171
- });
132
+ async #connect(environmentId, appName, options) {
133
+ const body = {
134
+ ...(options.actorId === undefined ? {} : { actorId: options.actorId }),
135
+ ...(options.services === undefined ? {} : { services: options.services }),
136
+ };
137
+ const credentials = await this.#call(undefined, (call) => this.#sdk.environments.connect({ environmentId, appName, body }, call));
138
+ return bindCredentials(credentials, this.#fetch);
172
139
  }
173
140
  #bind(data) {
174
141
  const { id } = data;
175
142
  const helpers = {
176
- connect: (app, options = {}) => this.#connect(id, data, app, options),
143
+ connect: (appName, options = {}) => this.#connect(id, appName, options),
177
144
  delete: (options) => this.environments.delete(id, options),
178
145
  refresh: () => this.environments.get(id),
179
146
  snapshots: {
@@ -257,7 +224,23 @@ function waitOptions(options) {
257
224
  ...(options.signal ? { signal: options.signal } : {}),
258
225
  };
259
226
  }
260
- function environmentApiKey() {
227
+ function baseUrlOrigin(value) {
228
+ if (value === undefined) {
229
+ return undefined;
230
+ }
231
+ const origin = value.endsWith('/') ? value.slice(0, -1) : value;
232
+ const url = URL.canParse(origin) ? new URL(origin) : undefined;
233
+ if (!url ||
234
+ /[{}]/.test(origin) ||
235
+ origin !== url.origin ||
236
+ !(url.protocol === 'https:' ||
237
+ (url.protocol === 'http:' &&
238
+ (url.hostname === 'localhost' || url.hostname === '127.0.0.1')))) {
239
+ throw new TypeError('baseUrl or STATEMACHINES_BASE_URL must be an HTTPS origin, or HTTP with localhost or 127.0.0.1.');
240
+ }
241
+ return origin;
242
+ }
243
+ function environmentVariable(name) {
261
244
  const runtime = globalThis;
262
- return runtime.process?.env?.STATEMACHINES_API_KEY;
245
+ return runtime.process?.env?.[name];
263
246
  }
@@ -1,13 +1,30 @@
1
- import type { Api } from '../models/api.js';
2
- import type { Credentials as CredentialsData } from '../models/credentials.js';
3
- /** The API's `Credentials`, plus `fetch` to call the app with them. */
1
+ import type { Credentials as CredentialsData, CredentialsService as CredentialsServiceData } from '../models/credentials.js';
2
+ /** What a client needs to reach one MCP server of a service: send `headers` with every request to `url`. */
3
+ export interface McpServerCredentials {
4
+ url: string;
5
+ headers: Record<string, string>;
6
+ }
7
+ /** One service from the credentials, plus `server` to hand one of its MCP servers to a client. */
8
+ export type ServiceCredentials = CredentialsServiceData & {
9
+ /**
10
+ * The URL of one MCP server the service serves, with the service's headers.
11
+ * Throws a `TypeError` naming the served MCP servers when it serves no MCP server
12
+ * named `name`.
13
+ */
14
+ server(name: string): McpServerCredentials;
15
+ };
16
+ /** The API's `Credentials`, plus `fetch` to call the app and `service` to find one service. */
4
17
  export type Credentials = CredentialsData & {
5
18
  /**
6
- * Sends one request to the app: `path` is joined to `url`, `headers` are added, and
7
- * `token` goes in the header of the API whose path matches (`Authorization: Bearer`
8
- * for Salesforce REST and Bulk 2.0). Never retries, never follows redirects, never
9
- * sends the State Machines API key. The SOAP session header is the caller's job.
19
+ * Sends one request to the `api` service: `path` is joined to its `url`.
20
+ * Its `headers` are defaults, so a header the caller sets, such as another
21
+ * session's `Authorization`, is sent instead. The environment token header
22
+ * is always the service's. Never retries, never follows redirects, never
23
+ * sends the State Machines API key. Throws a `TypeError` when the
24
+ * credentials list no `api` service.
10
25
  */
11
26
  fetch(path: string, init?: RequestInit): Promise<Response>;
27
+ /** The service with this ID. Throws a `TypeError` naming the listed services when there is none. */
28
+ service(id: string): ServiceCredentials;
12
29
  };
13
- export declare function bindCredentials(data: CredentialsData, apis: readonly Api[], fetcher: typeof fetch | undefined): Credentials;
30
+ export declare function bindCredentials(data: CredentialsData, fetcher: typeof fetch | undefined): Credentials;
@@ -1,56 +1,91 @@
1
- const HTTP_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
1
+ /** The gateway admits a request by this header, so a caller cannot replace it. */
2
+ const ENVIRONMENT_TOKEN_HEADER = 'StateMachines-Environment-Token';
2
3
  // Checked on the path part only. Covers protocol-relative starts, backslashes, control
3
4
  // characters and encoded dots, slashes and backslashes, including double encoding.
4
5
  const UNSAFE_PATH = /^\/\/|\\|%(?:25)*(?:2e|2f|5c)|\p{Cc}/iu;
5
- const OUTSIDE = 'Credentials.fetch only reaches paths under the app url.';
6
- export function bindCredentials(data, apis, fetcher) {
7
- const base = URL.canParse(data.url) ? new URL(data.url) : null;
8
- if (!base || !/^https?:$/.test(base.protocol) || base.search || base.hash) {
9
- throw new TypeError('The credentials url is not a plain http(s) URL.');
10
- }
11
- if (!base.pathname.endsWith('/')) {
12
- base.pathname += '/';
6
+ const OUTSIDE = 'Credentials.fetch only reaches paths under the api service url.';
7
+ export function bindCredentials(data, fetcher) {
8
+ return new BoundCredentials(data, fetcher);
9
+ }
10
+ // Methods live on the prototype, so neither JSON nor a spread copies them.
11
+ class BoundCredentials {
12
+ actorId;
13
+ appName;
14
+ environmentId;
15
+ expiresAt;
16
+ login;
17
+ services;
18
+ token;
19
+ #api;
20
+ #fetcher;
21
+ constructor(data, fetcher) {
22
+ this.actorId = data.actorId;
23
+ this.appName = data.appName;
24
+ this.environmentId = data.environmentId;
25
+ this.expiresAt = data.expiresAt;
26
+ this.login = data.login;
27
+ this.services = data.services;
28
+ this.token = data.token;
29
+ this.#fetcher = fetcher;
30
+ const api = data.services.find((candidate) => candidate.id === 'api');
31
+ this.#api = api
32
+ ? { base: baseUrl(api.url), headers: { ...api.headers } }
33
+ : null;
13
34
  }
14
- const send = async (path, init = {}) => {
35
+ async fetch(path, init = {}) {
36
+ const { base, headers: apiHeaders } = this.#api ?? this.#unlisted('api');
15
37
  const url = resolve(base, path);
16
38
  const headers = new Headers(init.headers);
17
- const actor = actorHeader(apis, `/${url.pathname.slice(base.pathname.length)}`, data.token);
18
- // Ours go last so a caller header of the same name cannot replace them.
19
- for (const [name, value] of Object.entries(data.headers)) {
20
- headers.set(name, value);
21
- }
22
- if (actor) {
23
- headers.set(actor.name, actor.value);
39
+ for (const [name, value] of Object.entries(apiHeaders)) {
40
+ if (!headers.has(name) ||
41
+ name.toLowerCase() === ENVIRONMENT_TOKEN_HEADER.toLowerCase()) {
42
+ headers.set(name, value);
43
+ }
24
44
  }
25
45
  const request = new Request(url, { ...init, headers, redirect: 'manual' });
26
46
  // Called on `globalThis` at use time: a stored unbound `fetch` throws "Illegal invocation" in Workers.
27
- return fetcher ? fetcher(request) : globalThis.fetch(request);
28
- };
29
- return Object.defineProperty({ ...data }, 'fetch', {
30
- value: send,
31
- enumerable: false,
32
- });
47
+ return this.#fetcher ? this.#fetcher(request) : globalThis.fetch(request);
48
+ }
49
+ service(id) {
50
+ const entry = this.services.find((candidate) => candidate.id === id);
51
+ return entry ? new BoundService(entry) : this.#unlisted(id);
52
+ }
53
+ #unlisted(id) {
54
+ const listed = this.services.map((each) => each.id).join(', ') || 'none';
55
+ throw new TypeError(`These credentials list no service named ${JSON.stringify(id)}. They list ${listed}.`);
56
+ }
33
57
  }
34
- /** The longest `pathTemplate` that prefixes the path names the API; `{...}` matches one segment. */
35
- function actorHeader(apis, path, token) {
36
- let match = null;
37
- for (const api of apis) {
38
- const pattern = api.pathTemplate
39
- .replace(/\/+$/, '')
40
- .split(/\{[^}]*\}/)
41
- .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
42
- .join('[^/]+');
43
- if (new RegExp(`^${pattern}(?:/|$)`).test(path) &&
44
- api.pathTemplate.length > (match?.pathTemplate.length ?? -1)) {
45
- match = api;
58
+ class BoundService {
59
+ headers;
60
+ id;
61
+ url;
62
+ constructor(entry) {
63
+ this.headers = entry.headers;
64
+ this.id = entry.id;
65
+ this.url = entry.url;
66
+ // Set only when present, so a spread or JSON of a service without servers has no `servers` key.
67
+ if (entry.servers) {
68
+ this.servers = entry.servers;
69
+ }
70
+ }
71
+ server(name) {
72
+ const found = this.servers?.find((candidate) => candidate.name === name);
73
+ if (!found) {
74
+ const served = this.servers?.map((each) => each.name).join(', ') || 'none';
75
+ throw new TypeError(`The ${this.id} service serves no MCP server named ${JSON.stringify(name)}. It serves ${served}.`);
46
76
  }
77
+ return { url: found.url, headers: { ...this.headers } };
78
+ }
79
+ }
80
+ function baseUrl(url) {
81
+ const base = URL.canParse(url) ? new URL(url) : null;
82
+ if (!base || !/^https?:$/.test(base.protocol) || base.search || base.hash) {
83
+ throw new TypeError('The api service url is not a plain http(s) URL.');
47
84
  }
48
- if (!match || !HTTP_TOKEN.test(match.actorHeader)) {
49
- return null;
85
+ if (!base.pathname.endsWith('/')) {
86
+ base.pathname += '/';
50
87
  }
51
- return match.actorHeader.toLowerCase() === 'authorization'
52
- ? { name: 'Authorization', value: `Bearer ${token}` }
53
- : { name: match.actorHeader, value: token };
88
+ return base;
54
89
  }
55
90
  function resolve(base, path) {
56
91
  const pathPart = path.split(/[?#]/, 1)[0] ?? '';
@@ -9,7 +9,7 @@ import type { Credentials } from './credentials.js';
9
9
  export type Environment = EnvironmentData & EnvironmentHelpers;
10
10
  export interface EnvironmentHelpers {
11
11
  /** Credentials for one app; `actorId` defaults to the app's default actor. */
12
- connect(app: string, options?: ConnectOptions): Promise<Credentials>;
12
+ connect(appName: string, options?: ConnectOptions): Promise<Credentials>;
13
13
  delete(options?: SignalOptions): Promise<Environment>;
14
14
  /** Reads the environment again. */
15
15
  refresh(): Promise<Environment>;
@@ -48,7 +48,7 @@ export class EnvironmentFailedError extends StateMachinesError {
48
48
  super({
49
49
  ...noResponse,
50
50
  message: failure
51
- ? `Environment ${environment.id} failed: ${failure.message} (${failure.code}, app ${failure.app}).`
51
+ ? `Environment ${environment.id} failed: ${failure.message} (${failure.code}, app ${failure.appName}).`
52
52
  : `Environment ${environment.id} failed.`,
53
53
  });
54
54
  this.environment = environment;
@@ -1,7 +1,7 @@
1
- export type { Actor, Api, App, AuditEvent, BodyPreview, CreateEnvironment, CreateEnvironmentApp, EnvironmentApp, EnvironmentFailure, EnvironmentFailureCode, EnvironmentStatus, ErrorCode, Me, Permission, Principal, RequestDetail, RequestStats, RequestT as Request, Snapshot, SnapshotStatus, Version, Workspace, } from '../models/index.js';
1
+ export type { Actor, App, AppEntry, AuditEvent, BodyPreview, CreateEnvironment, CreateEnvironmentApp, EnvironmentApp, EnvironmentFailure, EnvironmentFailureCode, EnvironmentStatus, ErrorCode, Me, Permission, Principal, RequestDetail, RequestStats, RequestT as Request, Snapshot, SnapshotStatus, Version, Workspace, } from '../models/index.js';
2
2
  export type { ConnectOptions, CreateEnvironmentOptions, CreateSnapshotOptions, ListAuditEventsQuery, ListEnvironmentsQuery, ListRequestsQuery, ListSnapshotsQuery, ListWorkspacesQuery, Page, PageQuery, RequestStatsQuery, SignalOptions, StateMachinesOptions, } from './client.js';
3
3
  export { StateMachines } from './client.js';
4
- export type { Credentials } from './credentials.js';
4
+ export type { Credentials, McpServerCredentials, ServiceCredentials, } from './credentials.js';
5
5
  export type { Environment, EnvironmentHelpers } from './environment.js';
6
6
  export { EnvironmentFailedError, SnapshotFailedError, StateMachinesError, UnexpectedStatusError, WaitTimeoutError, } from './errors.js';
7
7
  export type { WaitOptions } from './wait.js';
@@ -1,6 +1,6 @@
1
1
  import { isAlias, isScalar, parseDocument, visit } from 'yaml';
2
2
  import { invalidInput } from './errors.js';
3
- const APP_FIELDS = new Set(['app', 'name', 'settings']);
3
+ const APP_FIELDS = new Set(['appId', 'name', 'setup', 'services']);
4
4
  /**
5
5
  * Parses a YAML document with the fields of `CreateEnvironment`. The SDK checks the shape;
6
6
  * the API checks the values. Anchors, aliases, tags and duplicate keys are refused.
@@ -97,8 +97,8 @@ export function parseEnvironmentYaml(text) {
97
97
  add(key, 'Unknown field.');
98
98
  }
99
99
  }
100
- if ('apps' in value === 'snapshotId' in value) {
101
- add('$', 'Pass exactly one of apps and snapshotId.');
100
+ if (!('apps' in value) && !('snapshotId' in value)) {
101
+ add('$', 'Send apps, snapshotId or both.');
102
102
  }
103
103
  if (Object.keys(fields).length > 0) {
104
104
  throw bad(fields);
@@ -110,22 +110,24 @@ function checkApp(app, path, add) {
110
110
  return;
111
111
  }
112
112
  if (!isRecord(app)) {
113
- add(path, 'Expected an app ID or a mapping with app.');
113
+ add(path, 'Expected an app ID or a mapping with appId.');
114
114
  return;
115
115
  }
116
- const { app: id, name, settings } = app;
117
- if (typeof id !== 'string') {
118
- add(`${path}.app`, 'Expected a string.');
116
+ const { appId, name } = app;
117
+ if (typeof appId !== 'string') {
118
+ add(`${path}.appId`, 'Expected a string.');
119
119
  }
120
120
  if ('name' in app && typeof name !== 'string') {
121
121
  add(`${path}.name`, 'Expected a string.');
122
122
  }
123
- if ('settings' in app && !isRecord(settings)) {
124
- add(`${path}.settings`, 'Expected a mapping.');
123
+ for (const field of ['setup', 'services']) {
124
+ if (field in app && !isRecord(app[field])) {
125
+ add(`${path}.${field}`, 'Expected a mapping.');
126
+ }
125
127
  }
126
- for (const key of Object.keys(app)) {
127
- if (!APP_FIELDS.has(key)) {
128
- add(`${path}.${key}`, 'Unknown field.');
128
+ for (const field of Object.keys(app)) {
129
+ if (!APP_FIELDS.has(field)) {
130
+ add(`${path}.${field}`, 'Unknown field.');
129
131
  }
130
132
  }
131
133
  }
@@ -0,0 +1,11 @@
1
+ import { StateMachinesCore } from "../core.js";
2
+ import { RequestOptions } from "../lib/sdks.js";
3
+ import { ConnectionError, InvalidRequestError, RequestAbortedError, RequestTimeoutError, UnexpectedClientError } from "../models/errors/http-client-errors.js";
4
+ import * as errors from "../models/errors/index.js";
5
+ import { ResponseValidationError } from "../models/errors/response-validation-error.js";
6
+ import { SDKValidationError } from "../models/errors/sdk-validation-error.js";
7
+ import { StateMachinesHTTPError } from "../models/errors/state-machines-http-error.js";
8
+ import * as operations from "../models/operations/index.js";
9
+ import { APIPromise } from "../types/async.js";
10
+ import { Result } from "../types/fp.js";
11
+ export declare function environmentsCreators(client: StateMachinesCore, request?: operations.ListEnvironmentCreatorsRequest | undefined, options?: RequestOptions): APIPromise<Result<operations.ListEnvironmentCreatorsResponse, errors.ErrorT | StateMachinesHTTPError | ResponseValidationError | ConnectionError | RequestAbortedError | RequestTimeoutError | InvalidRequestError | UnexpectedClientError | SDKValidationError>>;