@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.
- package/README.md +75 -30
- package/agents/SKILL.md +24 -9
- package/agents/index.d.ts +2 -0
- package/agents/index.js +8 -6
- package/agents/llms.txt +25 -5
- package/agents/prompt.md +11 -5
- package/esm/extensions/client.d.ts +11 -7
- package/esm/extensions/client.js +32 -49
- package/esm/extensions/credentials.d.ts +25 -8
- package/esm/extensions/credentials.js +75 -40
- package/esm/extensions/environment.d.ts +1 -1
- package/esm/extensions/errors.js +1 -1
- package/esm/extensions/index.d.ts +2 -2
- package/esm/extensions/yaml.js +14 -12
- package/esm/funcs/environments-creators.d.ts +11 -0
- package/esm/funcs/environments-creators.js +90 -0
- package/esm/funcs/environments-list.js +2 -0
- package/esm/funcs/requests-list.js +5 -1
- package/esm/funcs/requests-stats.js +5 -1
- package/esm/lib/config.d.ts +2 -2
- package/esm/lib/config.js +2 -2
- package/esm/models/app-entry.d.ts +8 -0
- package/esm/models/app-entry.js +11 -0
- package/esm/models/create-environment-app.d.ts +25 -0
- package/esm/models/create-environment-app.js +14 -0
- package/esm/models/create-environment.d.ts +3 -26
- package/esm/models/create-environment.js +2 -19
- package/esm/models/credential.d.ts +50 -0
- package/esm/models/credential.js +60 -0
- package/esm/models/credentials.d.ts +26 -5
- package/esm/models/credentials.js +20 -3
- package/esm/models/environment-failure.d.ts +3 -2
- package/esm/models/environment-failure.js +3 -2
- package/esm/models/environment.d.ts +4 -1
- package/esm/models/environment.js +2 -1
- package/esm/models/error.d.ts +1 -0
- package/esm/models/error.js +1 -0
- package/esm/models/index.d.ts +6 -1
- package/esm/models/index.js +6 -1
- package/esm/models/mcp-server.d.ts +11 -0
- package/esm/models/mcp-server.js +16 -0
- package/esm/models/mcp-tool.d.ts +12 -0
- package/esm/models/mcp-tool.js +16 -0
- package/esm/models/me.d.ts +2 -0
- package/esm/models/me.js +2 -0
- package/esm/models/operations/create-credentials.d.ts +2 -0
- package/esm/models/operations/create-credentials.js +1 -0
- package/esm/models/operations/get-request-stats.d.ts +10 -2
- package/esm/models/operations/get-request-stats.js +5 -1
- package/esm/models/operations/index.d.ts +1 -0
- package/esm/models/operations/index.js +1 -0
- package/esm/models/operations/list-environment-creators.d.ts +23 -0
- package/esm/models/operations/list-environment-creators.js +20 -0
- package/esm/models/operations/list-environments.d.ts +5 -1
- package/esm/models/operations/list-environments.js +2 -0
- package/esm/models/operations/list-requests.d.ts +10 -2
- package/esm/models/operations/list-requests.js +5 -1
- package/esm/models/request-detail.d.ts +8 -1
- package/esm/models/request-detail.js +5 -1
- package/esm/models/request-stats.d.ts +8 -0
- package/esm/models/request-stats.js +9 -0
- package/esm/models/request.d.ts +8 -1
- package/esm/models/request.js +5 -1
- package/esm/models/service.d.ts +25 -0
- package/esm/models/service.js +28 -0
- package/esm/models/snapshot.d.ts +7 -0
- package/esm/models/snapshot.js +3 -0
- package/esm/models/version.d.ts +2 -2
- package/esm/models/version.js +2 -2
- package/esm/sdk/environments.d.ts +1 -0
- package/esm/sdk/environments.js +4 -0
- package/esm/types/discriminated-union.d.ts +24 -0
- package/esm/types/discriminated-union.js +60 -0
- package/esm/types/smart-union.js +2 -1
- package/openapi.json +596 -116
- package/package.json +1 -1
- package/esm/models/api.d.ts +0 -17
- 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 `{
|
|
11
|
-
- Credentials: `const crm = await env.connect('salesforce')` returns `
|
|
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.
|
|
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
|
-
| `
|
|
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
|
|
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
|
-
|
|
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
|
|
4
|
-
2.
|
|
5
|
-
3.
|
|
6
|
-
|
|
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 `
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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,
|
|
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`. */
|
package/esm/extensions/client.js
CHANGED
|
@@ -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,
|
|
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 ??
|
|
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
|
-
...(
|
|
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,
|
|
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,
|
|
133
|
-
const body =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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: (
|
|
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
|
|
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?.
|
|
245
|
+
return runtime.process?.env?.[name];
|
|
263
246
|
}
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
30
|
+
export declare function bindCredentials(data: CredentialsData, fetcher: typeof fetch | undefined): Credentials;
|
|
@@ -1,56 +1,91 @@
|
|
|
1
|
-
|
|
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
|
|
6
|
-
export function bindCredentials(data,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
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 (!
|
|
49
|
-
|
|
85
|
+
if (!base.pathname.endsWith('/')) {
|
|
86
|
+
base.pathname += '/';
|
|
50
87
|
}
|
|
51
|
-
return
|
|
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(
|
|
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>;
|
package/esm/extensions/errors.js
CHANGED
|
@@ -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.
|
|
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,
|
|
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';
|
package/esm/extensions/yaml.js
CHANGED
|
@@ -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(['
|
|
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
|
|
101
|
-
add('$', '
|
|
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
|
|
113
|
+
add(path, 'Expected an app ID or a mapping with appId.');
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
|
-
const {
|
|
117
|
-
if (typeof
|
|
118
|
-
add(`${path}.
|
|
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
|
-
|
|
124
|
-
|
|
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
|
|
127
|
-
if (!APP_FIELDS.has(
|
|
128
|
-
add(`${path}.${
|
|
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>>;
|