@aexhq/sdk 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/_contracts/event-envelope.d.ts +22 -1
  3. package/dist/_contracts/event-envelope.js +26 -2
  4. package/dist/_contracts/event-stream-client.js +7 -1
  5. package/dist/_contracts/operations.d.ts +30 -1
  6. package/dist/_contracts/operations.js +54 -1
  7. package/dist/_contracts/run-config.d.ts +1 -1
  8. package/dist/_contracts/run-unit.d.ts +12 -0
  9. package/dist/_contracts/run-unit.js +55 -0
  10. package/dist/_contracts/runtime-sizes.d.ts +2 -2
  11. package/dist/_contracts/runtime-sizes.js +5 -5
  12. package/dist/_contracts/runtime-types.d.ts +98 -0
  13. package/dist/_contracts/submission.d.ts +4 -4
  14. package/dist/cli.mjs +554 -69
  15. package/dist/cli.mjs.sha256 +1 -1
  16. package/dist/client.d.ts +40 -1
  17. package/dist/client.js +90 -5
  18. package/dist/client.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/version.d.ts +1 -1
  22. package/dist/version.js +1 -1
  23. package/docs/authentication.md +92 -0
  24. package/docs/billing.md +112 -0
  25. package/docs/concepts/agent-tools.md +4 -4
  26. package/docs/concepts/providers-and-runtimes.md +4 -1
  27. package/docs/concepts/runs.md +2 -1
  28. package/docs/concepts/subagents.md +85 -0
  29. package/docs/credentials.md +27 -3
  30. package/docs/defaults.md +9 -7
  31. package/docs/errors.md +132 -0
  32. package/docs/events.md +36 -23
  33. package/docs/limits-and-quotas.md +29 -13
  34. package/docs/limits.md +2 -2
  35. package/docs/mcp.md +1 -1
  36. package/docs/networking.md +68 -42
  37. package/docs/outputs.md +4 -3
  38. package/docs/public-surface.json +1 -1
  39. package/docs/quickstart.md +9 -6
  40. package/docs/run-config.md +1 -1
  41. package/docs/secrets.md +5 -0
  42. package/docs/webhooks.md +132 -0
  43. package/package.json +1 -1
package/docs/mcp.md CHANGED
@@ -20,7 +20,7 @@ Use allowlists for sensitive servers whenever possible.
20
20
  ## Large-payload responses
21
21
 
22
22
  aex is a session dispatcher, not an MCP runtime. We intentionally do
23
- **not** interpose on the transport between Claude and an upstream MCP
23
+ **not** interpose on the transport between the model and an upstream MCP
24
24
  server, so we cannot elide MCP responses or write them to the session
25
25
  filesystem on the user's behalf. Anything an MCP tool returns lands
26
26
  directly in the model's context.
@@ -4,44 +4,64 @@ title: Networking
4
4
 
5
5
  # Networking
6
6
 
7
- A run executes your agent's code in a sandbox that has **no unmediated route to
8
- the internet**. Every outbound connection whether it comes from the model, a
9
- built-in tool, or a `curl` your code runs in the shell — passes through the
10
- platform's egress boundary, which enforces the run's networking policy and a
11
- fixed SSRF deny-list (loopback, link-local, cloud-metadata, and other private
12
- ranges are always blocked, including hostnames that resolve to those ranges).
13
-
14
- **Networking is open by default.** A run that does not set
15
- `environment.networking` may reach any public host still subject to the SSRF
16
- deny-list above with no allowlist required. You use the `environment.networking`
17
- field to *narrow* that surface when you want a tighter, auditable egress posture.
18
- Code cannot widen the policy from inside the container: the boundary is the
19
- platform's, not the agent's.
7
+ A run executes your agent's code in a sandbox with **no direct route to the
8
+ internet**. Outbound traffic is governed by **two layers**:
9
+
10
+ 1. **The per-run policy (`environment.networking`)** enforced by the agent
11
+ runtime *inside the run*. It applies to the standard proxy path every normal
12
+ HTTP client uses (see below) and can only *narrow* what the run may reach.
13
+ 2. **The platform ceiling** — a fixed, platform-managed egress boundary every
14
+ connection ultimately traverses. It allows the hosts aex itself manages
15
+ (model providers, built-in tool endpoints, package registries, and related
16
+ well-known development hosts such as `github.com`) and enforces a fixed SSRF
17
+ deny-list: loopback, link-local, cloud-metadata, and other private ranges are
18
+ always blocked, including hostnames that resolve to those ranges.
19
+
20
+ Honest boundary statement: the per-run `allowedHosts` policy is enforced by the
21
+ run's own runtime on the standard proxy path — it is **not** yet enforced at
22
+ the platform proxy layer. A subprocess that deliberately bypasses the standard
23
+ proxy environment (a raw socket / raw CONNECT) is bounded by the **platform
24
+ ceiling** rather than by the per-run list. Per-run enforcement at the platform
25
+ proxy layer is planned; until it ships, treat `allowedHosts` as a strong
26
+ default-path control and an auditable statement of intent, not a hard isolation
27
+ boundary against adversarial code inside the run.
28
+
29
+ **Default posture.** A run that does not set `environment.networking` runs in
30
+ `open` mode: its own code may reach anything within the platform ceiling with
31
+ no allowlist required. Use `environment.networking` to *narrow* that surface
32
+ when you want a tighter, auditable egress posture. Code cannot widen the
33
+ ceiling from inside the container.
20
34
 
21
35
  ## Paths that always work
22
36
 
23
- These reach the network over managed paths and are **not** subject to
37
+ These reach the network over managed platform paths and are **not** subject to
24
38
  `environment.networking`, so you never list their hosts:
25
39
 
26
40
  - The model / provider call for the run (and its subagents).
27
- - The built-in `web_search` and `web_fetch` tools (still SSRF-guarded).
28
- - Any remote MCP servers you declare in `mcpServers` see [MCP](mcp.md).
41
+ - The built-in `web_search` and `web_fetch` tools. They run over a managed,
42
+ SSRF-guarded server-side path, which is why they can reach arbitrary public
43
+ URLs even though your own code is bounded by the ceiling.
44
+ - Remote MCP servers you declare in `mcpServers` — MCP traffic rides a managed
45
+ path; see [MCP](mcp.md).
29
46
  - The package registries for any `environment.packages` you declare (pip → PyPI,
30
47
  apt → the distribution mirrors). Declaring a package implicitly allows the
31
48
  registry it installs from.
32
49
 
33
- `environment.networking` governs the **other** case: arbitrary outbound that
34
- your own code makes to a host the platform doesn't already manage — a `curl` in
35
- the `bash` tool, a `requests`/`urllib` call in Python, a `fetch` in
36
- `code_execution`, or a third-party SDK.
50
+ `environment.networking` governs the **other** case: outbound that your own
51
+ code makes a `curl` in the `bash` tool, a `requests`/`urllib` call in Python,
52
+ a `fetch` in `code_execution`, or a third-party SDK.
37
53
 
38
54
  ## Restrict a run to an allowlist
39
55
 
40
56
  Set `mode: "limited"` and list exactly the hosts your code is allowed to reach.
41
- Anything not on the list (and not one of the always-allowed paths above) is
42
- blocked, and the call fails. The platform automatically appends the
43
- infrastructure hosts aex itself needs, so the model and tool paths keep working
44
- without you listing them.
57
+ The run's runtime enforces the list on the standard proxy path: a connection to
58
+ a host that is neither on the list nor one of the always-allowed paths above is
59
+ refused before it leaves the run. Package-registry hosts implied by
60
+ `environment.packages` are appended automatically so installs keep working.
61
+
62
+ Note that `allowedHosts` narrows *within* the platform ceiling — listing a host
63
+ does not by itself make it reachable if the platform ceiling does not carry it.
64
+ If your run needs a host the ceiling blocks, contact support.
45
65
 
46
66
  ### TypeScript
47
67
 
@@ -75,11 +95,11 @@ visible at the same call site as the code that needs it.
75
95
  ## Open mode
76
96
 
77
97
  `open` is the default: a run that omits `environment.networking` already runs in
78
- open mode. Set `mode: "open"` explicitly when you want to be unambiguous, or when
79
- a run needs to reach hosts you can't enumerate ahead of time. The run may then
80
- reach any public host, still subject to the SSRF deny-list. Prefer `limited`
81
- whenever you can name the hosts — it gives the run a stable, auditable, least-
82
- privilege egress surface (it is the tighter posture, not the default).
98
+ open mode. Set `mode: "open"` explicitly when you want to be unambiguous. Open
99
+ mode applies no per-run allowlist the run's own code may reach anything the
100
+ platform ceiling allows, still subject to the SSRF deny-list. Prefer `limited`
101
+ whenever you can name the hosts — it gives the run a stable, auditable,
102
+ least-privilege egress surface (it is the tighter posture, not the default).
83
103
 
84
104
  ```ts
85
105
  await aex.run({
@@ -90,6 +110,9 @@ await aex.run({
90
110
  });
91
111
  ```
92
112
 
113
+ (Web research like the example above flows through the managed `web_search` /
114
+ `web_fetch` path, which is not ceiling-bounded.)
115
+
93
116
  ## Transparent for normal HTTP clients
94
117
 
95
118
  You write ordinary code — there is no per-request proxy configuration and no
@@ -101,7 +124,7 @@ that honors it — `curl`, Python `requests` / `urllib`, `pip`, `npm`, Node
101
124
  ```bash
102
125
  # In the agent's shell, against a limited run that allows api.example.com:
103
126
  curl -sS https://api.example.com/v1/status # works
104
- curl -sS https://other-host.example # blocked (not in allowlist)
127
+ curl -sS https://other-host.example # refused (not in allowlist)
105
128
  ```
106
129
 
107
130
  You also do **not** need to install any certificate. The platform manages the
@@ -110,20 +133,23 @@ your client succeeds without extra setup.
110
133
 
111
134
  ## Limitations and gotchas
112
135
 
113
- - **Enforcement is at the platform boundary, not in your code.** A tool can't
114
- bypass the policy by ignoring proxy settings or opening a raw socket — the
115
- sandbox has no other route out, so a disallowed host simply fails. This is the
116
- intended fail-closed behavior.
136
+ - **The per-run policy is enforced by the run's runtime, not at the platform
137
+ proxy.** Clients that honor the standard proxy environment (almost all HTTP
138
+ tooling) are held to the `allowedHosts` list. A subprocess that deliberately
139
+ ignores the proxy environment and opens a raw connection is **not** held to
140
+ the per-run list — it is bounded by the platform ceiling (the aex-managed
141
+ provider/tool/registry host set) and the SSRF deny-list instead. Per-run
142
+ enforcement at the platform proxy layer is planned.
117
143
  - **A client that hard-bypasses the standard environment may fail to connect.**
118
- This is rare almost all HTTP tooling honors `HTTP_PROXY` / `HTTPS_PROXY` and
119
- the system trust store. But a client that is explicitly told to ignore the
120
- proxy environment, pins or replaces its certificate trust store, or speaks a
121
- non-HTTP protocol over a raw socket can hit a wall even for an allowed host.
122
- The fix is to let the client use the standard proxy and certificate
123
- environment the runtime provides (most libraries do by default), rather than
124
- overriding it.
144
+ A client that ignores the proxy environment, pins or replaces its certificate
145
+ trust store, or speaks a non-HTTP protocol over a raw socket can hit a wall
146
+ even for a host you allowed. The fix is to let the client use the standard
147
+ proxy and certificate environment the runtime provides (most libraries do by
148
+ default), rather than overriding it.
125
149
  - **`allowedHosts` only applies in `limited` mode.** It is ignored in `open`
126
- mode, where the SSRF deny-list is the only gate.
150
+ mode, where the platform ceiling and the SSRF deny-list are the gates.
151
+ - **`allowedHosts` cannot exceed the platform ceiling.** It narrows; it never
152
+ widens. A listed host outside the ceiling still fails.
127
153
 
128
154
  For credentialed HTTP calls, pass the credential as an `environment.secrets`
129
155
  entry and let your code use its normal HTTP client. For remote tool servers, see
package/docs/outputs.md CHANGED
@@ -163,9 +163,10 @@ const stream = response.body;
163
163
 
164
164
  | Run state | Behaviour |
165
165
  | --- | --- |
166
- | `pending` / `queued` / `provisioning` | `metadata/run.json` reflects the early state; `events/` and `outputs/` are typically empty. |
167
- | `provider_running`, mid-session / `cleaning_up` | Whatever events + outputs have been captured so far. Call again after terminal for the complete set. |
168
- | `succeeded` / `failed` / `cancelled` / `terminated` | The complete typed event archive + all captured outputs. |
166
+ | `queued` / `claiming` / `provisioning` | `metadata/run.json` reflects the early state; `events/` and `outputs/` are typically empty. |
167
+ | `provider_running`, mid-session / `capturing_outputs` / `cleaning_up` | Whatever events + outputs have been captured so far. Call again after the session parks for the complete set. |
168
+ | `idle` / `suspended` (parked between turns) | The complete archive for every turn sent so far; a later turn appends to it. |
169
+ | `succeeded` / `failed` / `timed_out` / `cancelled` | The complete typed event archive + all captured outputs. |
169
170
 
170
171
  ## `outputs.allowedDirs` — override capture roots
171
172
 
@@ -7,7 +7,7 @@
7
7
  "label": "Alpha testing",
8
8
  "description": "Access is limited to invited testers while we harden the hosted runtime, dashboard, and SDK workflows."
9
9
  },
10
- "installCommand": "bun add @aexhq/sdk",
10
+ "installCommand": "npm i @aexhq/sdk",
11
11
  "examples": {
12
12
  "typescriptLines": [
13
13
  "import { Aex, Models, Sizes } from \"@aexhq/sdk\";",
@@ -7,16 +7,18 @@ title: Quickstart
7
7
  ## 1. Install
8
8
 
9
9
  ```bash
10
- bun add @aexhq/sdk
10
+ npm i @aexhq/sdk
11
11
  ```
12
12
 
13
13
  This installs the TypeScript SDK exports and the bundled `aex` CLI.
14
14
 
15
15
  ## 2. Set credentials
16
16
 
17
- In the dashboard, create a quickstart SDK token with `runs:read`, `runs:write`,
18
- and `outputs:read`. The examples also need your BYOK provider key for the model
19
- you choose. For the Claude examples below:
17
+ aex is currently in **invite-only beta**: workspaces and API tokens are issued
18
+ by the aex team contact <support@aex.dev> for beta access. Once you have
19
+ access, create a quickstart SDK token with `runs:read`, `runs:write`, and
20
+ `outputs:read` in the dashboard at <https://aex.dev>. The examples also need
21
+ your BYOK provider key for the model you choose. For the Claude examples below:
20
22
 
21
23
  ```bash
22
24
  export AEX_API_TOKEN="<your-aex-token>"
@@ -28,7 +30,7 @@ export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
28
30
  ```ts
29
31
  import { Aex, Models, Sizes } from "@aexhq/sdk";
30
32
 
31
- const aex = new Aex({ apiToken: process.env.AEX_API_TOKEN! });
33
+ const aex = new Aex(process.env.AEX_API_TOKEN!);
32
34
 
33
35
  const session = await aex.openSession({
34
36
  model: Models.CLAUDE_HAIKU_4_5,
@@ -108,6 +110,7 @@ aex run \
108
110
  ## Add capabilities
109
111
 
110
112
  - Add files, skills, AGENTS.md, MCP servers, packages, and networking controls with [Composition](concepts/composition.md).
111
- - Use parent/child run delegation from the [Features](https://aex.dev/docs/features/#subagents) page.
113
+ - Delegate bounded sub-tasks to child runs with [Subagents](concepts/subagents.md).
114
+ - Get notified when a run finishes with [Webhooks](webhooks.md).
112
115
  - Narrow output capture or download individual files with [Outputs](outputs.md).
113
116
  - Check supported providers and models in the [provider/runtime capability matrix](provider-runtime-capabilities.md).
@@ -16,7 +16,7 @@ Allowed fields:
16
16
  - `metadata` - non-secret structured metadata.
17
17
  - `overrides` - `{ idleTtl?, timeout?, maxSpendUsd? }`. `timeout` is an optional session deadline (e.g. `"30m"`, `"2h"`); `maxSpendUsd` stops the session once its spend would exceed the cap (see [Limits & quotas](limits-and-quotas.md)).
18
18
 
19
- `message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `includeBuiltinTools`, and `outputMode` are `openSession` / `run` options, not reusable run-config fields. They carry the turn input, bytes, capture behavior, or agent tool/output controls that belong on a concrete call. Skill bundles are `tools` entries built with `Tools.fromSkillDir(...)` / `Tools.fromSkillUrl(...)`, so they too are SDK-code options rather than config fields. Subagents run in-process; there is no `limits` / `parentRunId` option.
19
+ `message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `includeBuiltinTools`, and `outputMode` are `openSession` / `run` options, not reusable run-config fields. They carry the turn input, bytes, capture behavior, or agent tool/output controls that belong on a concrete call. Skill bundles are `tools` entries built with `Tools.fromSkillDir(...)` / `Tools.fromSkillUrl(...)`, so they too are SDK-code options rather than config fields. Subagents are session-internal (the in-run `subagent` tool — see [Subagents](concepts/subagents.md)); there is no `parentRunId` option. The wire contract carries a per-run `limits` object (the exported `RunLimits` type: `maxConcurrentChildRuns`, `maxSubagentDepth`, `maxSpendUsd`), but the session surface exposes only its spend dial — set it with `overrides.maxSpendUsd`; the subagent depth/breadth dials are not settable per-session today and take the platform defaults.
20
20
 
21
21
  Secrets never live in run config. Pass provider keys through the top-level
22
22
  `apiKeys` map and runtime secrets through `environment.secrets` in the SDK, or
package/docs/secrets.md CHANGED
@@ -88,6 +88,11 @@ const metadata = await aex.secrets.get("serper-api-key");
88
88
 
89
89
  ## Inject A Workspace Secret Into A Run
90
90
 
91
+ > **Availability note:** workspace-secret `Secret.ref(...)` injection requires
92
+ > the next platform deploy — on the current hosted plane the referenced
93
+ > variable can resolve empty inside the run. Per-run `Secret.value(...)`
94
+ > secrets are unaffected. This note will be removed once the deploy lands.
95
+
91
96
  Reference workspace secrets with `Secret.ref(name)`. The value resolves
92
97
  server-side and is injected as the named environment variable.
93
98
 
@@ -0,0 +1,132 @@
1
+ ---
2
+ title: Webhooks
3
+ ---
4
+
5
+ # Webhooks
6
+
7
+ aex can notify your endpoint when a run finishes. Webhooks are **per-run**: you
8
+ pass a callback URL with the submission, and the platform delivers exactly one
9
+ `run.finished` event to it when the run reaches its terminal state.
10
+
11
+ > **Availability note:** terminal webhook delivery for session-based runs
12
+ > requires the next platform deploy — on the current hosted plane a session
13
+ > run's webhook may not fire. This note will be removed once the deploy lands.
14
+
15
+ ## Register a callback
16
+
17
+ ```ts
18
+ import { Aex, Models } from "@aexhq/sdk";
19
+
20
+ const aex = new Aex(process.env.AEX_API_TOKEN!);
21
+
22
+ const session = await aex.openSession({
23
+ model: Models.CLAUDE_HAIKU_4_5,
24
+ webhook: { url: "https://hooks.example.com/aex" },
25
+ apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
26
+ });
27
+ ```
28
+
29
+ ```bash
30
+ aex run \
31
+ --api-token "$AEX_API_TOKEN" \
32
+ --anthropic-api-key "$ANTHROPIC_API_KEY" \
33
+ --model claude-haiku-4-5 \
34
+ --prompt "Write the report." \
35
+ --webhook https://hooks.example.com/aex
36
+ ```
37
+
38
+ The URL must be `https`. The callback URL is an operational concern, not part
39
+ of the submission fingerprint: retrying the same submission with the same
40
+ idempotency key but a different callback URL never conflicts.
41
+
42
+ ## What gets delivered
43
+
44
+ One POST carrying the terminal `run.finished` event, sent at the
45
+ settle-consistent barrier — when you receive it, the run record is already
46
+ terminal and its outputs are complete and readable. The body is built once at
47
+ settle, frozen, and re-sent byte-identical on every retry or manual redelivery:
48
+
49
+ ```json
50
+ {
51
+ "specversion": "1.0",
52
+ "id": "<delivery id — matches the webhook-id header>",
53
+ "source": "aex",
54
+ "type": "run.finished",
55
+ "subject": "<the run id>",
56
+ "time": "2026-07-02T12:34:56.000Z",
57
+ "data": {
58
+ "runId": "<the run id>",
59
+ "status": "succeeded",
60
+ "terminalAt": "2026-07-02T12:34:56.000Z",
61
+ "reason": null,
62
+ "failureClass": null,
63
+ "costTelemetry": { "billedCostUsd": 0.42 }
64
+ }
65
+ }
66
+ ```
67
+
68
+ `reason` / `failureClass` carry the failure detail on non-success terminals;
69
+ `costTelemetry` is present when the billed cost is known.
70
+
71
+ ## Verify deliveries
72
+
73
+ Deliveries are signed [Standard Webhooks](https://www.standardwebhooks.com/)
74
+ style: HMAC-SHA256 over `` `${webhook-id}.${webhook-timestamp}.${rawBody}` ``,
75
+ sent in three headers — `webhook-id` (stable across retries; your dedupe key),
76
+ `webhook-timestamp` (unix seconds), and `webhook-signature` (a space-delimited
77
+ list of `v1,<base64>` entries).
78
+
79
+ The signing key is a per-workspace secret. Reveal it (it is created on first
80
+ use) with either surface:
81
+
82
+ ```bash
83
+ aex webhooks secret --api-token "$AEX_API_TOKEN" # prints whsec_...
84
+ ```
85
+
86
+ ```ts
87
+ const { whsec } = await aex.webhookSigningSecret();
88
+ ```
89
+
90
+ Verify inbound requests with the exported `verifyAexWebhook` — pure Web Crypto,
91
+ identical under Bun and Node, and interoperable with the `standardwebhooks`
92
+ reference library:
93
+
94
+ ```ts
95
+ import { verifyAexWebhook } from "@aexhq/sdk";
96
+
97
+ // In your HTTP handler — use the EXACT raw body bytes, never re-serialize.
98
+ const ok = await verifyAexWebhook({
99
+ rawBody,
100
+ headers: request.headers,
101
+ secret: process.env.AEX_WEBHOOK_SECRET! // the whsec_... value
102
+ });
103
+ if (!ok) return new Response("bad signature", { status: 401 });
104
+ ```
105
+
106
+ `verifyAexWebhook` rejects stale timestamps (default tolerance 300 seconds) and
107
+ compares signatures constant-time. It accepts every `v1` entry in the signature
108
+ list, so it is already rotation-ready — but **server-side rotation of the
109
+ signing secret is not available yet**: `aex webhooks secret` reveals or creates
110
+ the one workspace secret and there is no rotate endpoint today. If your secret
111
+ is compromised, contact <support@aex.dev>.
112
+
113
+ ## Delivery ledger and redelivery
114
+
115
+ Each run keeps a delivery ledger — attempts, last status code, and last error:
116
+
117
+ ```ts
118
+ const deliveries = await session.webhooks().list();
119
+ await session.webhooks().redeliver(deliveries[0]!.id);
120
+ ```
121
+
122
+ ```bash
123
+ aex deliveries <session-id> --api-token "$AEX_API_TOKEN"
124
+ ```
125
+
126
+ Redelivery re-sends the frozen payload with the **same** `webhook-id`, so a
127
+ consumer that dedupes on `webhook-id` handles retries, redeliveries, and
128
+ at-least-once delivery uniformly. An empty ledger means the run carried no
129
+ `webhook` or has not reached a terminal state yet.
130
+
131
+ For the terminal-event mechanics behind delivery timing, see
132
+ [Events](events.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexhq/sdk",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "TypeScript SDK for running autonomous agent sessions across providers (Anthropic, OpenAI, DeepSeek, Gemini, Mistral) behind one interface.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {