@aexhq/sdk 0.40.4 → 0.40.6
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 +159 -159
- package/dist/_contracts/api-key.js +0 -1
- package/dist/_contracts/bundle-manifest.js +0 -1
- package/dist/_contracts/connection-ticket.js +0 -1
- package/dist/_contracts/error-codes.js +0 -1
- package/dist/_contracts/error-factory.js +0 -1
- package/dist/_contracts/event-envelope.js +0 -1
- package/dist/_contracts/event-stream-client.js +0 -1
- package/dist/_contracts/event-view.js +0 -1
- package/dist/_contracts/http.js +0 -1
- package/dist/_contracts/index.js +0 -1
- package/dist/_contracts/internal.d.ts +36 -0
- package/dist/_contracts/internal.js +155 -6
- package/dist/_contracts/models.js +0 -1
- package/dist/_contracts/operations.js +1 -2
- package/dist/_contracts/post-hook.js +0 -1
- package/dist/_contracts/provider-support.js +0 -1
- package/dist/_contracts/run-artifacts.js +0 -1
- package/dist/_contracts/run-config.js +0 -1
- package/dist/_contracts/run-cost.js +0 -1
- package/dist/_contracts/run-custody.js +0 -1
- package/dist/_contracts/run-record.js +0 -1
- package/dist/_contracts/run-retention.js +0 -1
- package/dist/_contracts/run-trace.js +0 -1
- package/dist/_contracts/run-unit.js +0 -1
- package/dist/_contracts/runner-event.js +0 -1
- package/dist/_contracts/runtime-manifest.js +0 -1
- package/dist/_contracts/runtime-security-profile.js +0 -1
- package/dist/_contracts/runtime-sizes.js +0 -1
- package/dist/_contracts/runtime-types.js +0 -1
- package/dist/_contracts/sdk-errors.js +0 -1
- package/dist/_contracts/sdk-secrets.js +0 -1
- package/dist/_contracts/side-effect-audit.js +0 -1
- package/dist/_contracts/sse.js +0 -0
- package/dist/_contracts/stable.js +0 -1
- package/dist/_contracts/status.js +0 -1
- package/dist/_contracts/submission.js +0 -1
- package/dist/_contracts/suggest.js +0 -1
- package/dist/_contracts/webhook-verify.js +0 -1
- package/dist/asset-upload.d.ts +3 -43
- package/dist/asset-upload.js +1 -109
- package/dist/asset-upload.js.map +1 -1
- package/dist/cli.mjs +354 -781
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/fetch-archive.js +76 -16
- package/dist/fetch-archive.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/authentication.md +125 -125
- package/docs/billing.md +118 -118
- package/docs/concepts/agent-tools.md +66 -66
- package/docs/concepts/composition.md +37 -37
- package/docs/concepts/providers-and-runtimes.md +54 -54
- package/docs/concepts/runs.md +49 -49
- package/docs/concepts/subagents.md +88 -88
- package/docs/credentials.md +125 -125
- package/docs/defaults.md +64 -64
- package/docs/errors.md +196 -196
- package/docs/events.md +243 -243
- package/docs/limits-and-quotas.md +144 -144
- package/docs/limits.md +50 -50
- package/docs/mcp.md +44 -44
- package/docs/networking.md +163 -163
- package/docs/outputs.md +267 -267
- package/docs/public-surface.json +77 -77
- package/docs/quickstart.md +119 -119
- package/docs/release.md +38 -38
- package/docs/retries.md +129 -129
- package/docs/run-config.md +58 -58
- package/docs/run-record.md +55 -55
- package/docs/secrets.md +125 -130
- package/docs/testing.md +35 -35
- package/docs/vision-skills.md +91 -91
- package/docs/webhooks.md +127 -131
- package/examples/feature-tour.ts +282 -282
- package/examples/spike-settle-latency.ts +125 -125
- package/package.json +1 -1
package/docs/secrets.md
CHANGED
|
@@ -1,134 +1,129 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Secrets
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Secrets
|
|
6
|
-
|
|
7
|
-
aex supports BYOK provider keys, per-run credentials, and reusable workspace
|
|
8
|
-
secrets. Secret values are excluded from the idempotency fingerprint and do not
|
|
9
|
-
belong in run config.
|
|
10
|
-
|
|
11
|
-
Runnable examples need both `AEX_API_KEY` for aex and the matching BYOK
|
|
12
|
-
provider key, such as `ANTHROPIC_API_KEY` for Claude.
|
|
13
|
-
|
|
14
|
-
## Use A Provider Key For One Run
|
|
15
|
-
|
|
16
|
-
### TypeScript
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
import { Aex, Models } from "@aexhq/sdk";
|
|
20
|
-
|
|
21
|
-
const aex = new Aex({ apiKey: process.env.AEX_API_KEY! });
|
|
22
|
-
|
|
23
|
-
await aex.run({
|
|
24
|
-
model: Models.CLAUDE_HAIKU_4_5,
|
|
25
|
-
message: "Write a short report and save it as a file.",
|
|
26
|
-
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
27
|
-
});
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### CLI
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
aex run \
|
|
34
|
-
--api-key "$AEX_API_KEY" \
|
|
35
|
-
--anthropic-api-key "$ANTHROPIC_API_KEY" \
|
|
36
|
-
--model claude-haiku-4-5 \
|
|
37
|
-
--prompt "Write a short report and save it as a file."
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Upload An Env Secret
|
|
41
|
-
|
|
42
|
-
Use `Secret.value(...).upload(...)` when you start with an ephemeral value and
|
|
43
|
-
want to persist it as a named workspace secret for later runs.
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
import { Aex, Models, Providers, Secret } from "@aexhq/sdk";
|
|
47
|
-
|
|
48
|
-
const aex = new Aex({ apiKey: process.env.AEX_API_KEY! });
|
|
49
|
-
|
|
50
|
-
const githubToken = await Secret.value(process.env.GITHUB_TOKEN!).upload(aex, {
|
|
51
|
-
name: "github-token"
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await aex.run({
|
|
55
|
-
provider: Providers.ANTHROPIC,
|
|
56
|
-
model: Models.CLAUDE_HAIKU_4_5,
|
|
57
|
-
message: "Inspect the repository issues.",
|
|
58
|
-
environment: { secrets: { GITHUB_TOKEN: githubToken } },
|
|
59
|
-
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Set Or Rotate A Workspace Secret
|
|
64
|
-
|
|
65
|
-
Use `client.secrets.set(...)` to create a named secret directly. Use
|
|
66
|
-
`client.secrets.rotate(...)` to replace its value while keeping the same name.
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
await aex.secrets.set({
|
|
70
|
-
name: "serper-api-key",
|
|
71
|
-
value: process.env.SERPER_API_KEY!
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
await aex.secrets.rotate({
|
|
75
|
-
name: "serper-api-key",
|
|
76
|
-
value: process.env.SERPER_API_KEY_NEXT!
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Retrieve Secret Metadata
|
|
81
|
-
|
|
82
|
-
`list` and `get` return metadata only. They never return the secret value.
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
const secrets = await aex.secrets.list();
|
|
86
|
-
const metadata = await aex.secrets.get("serper-api-key");
|
|
87
|
-
```
|
|
88
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Secrets
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Secrets
|
|
6
|
+
|
|
7
|
+
aex supports BYOK provider keys, per-run credentials, and reusable workspace
|
|
8
|
+
secrets. Secret values are excluded from the idempotency fingerprint and do not
|
|
9
|
+
belong in run config.
|
|
10
|
+
|
|
11
|
+
Runnable examples need both `AEX_API_KEY` for aex and the matching BYOK
|
|
12
|
+
provider key, such as `ANTHROPIC_API_KEY` for Claude.
|
|
13
|
+
|
|
14
|
+
## Use A Provider Key For One Run
|
|
15
|
+
|
|
16
|
+
### TypeScript
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { Aex, Models } from "@aexhq/sdk";
|
|
20
|
+
|
|
21
|
+
const aex = new Aex({ apiKey: process.env.AEX_API_KEY! });
|
|
22
|
+
|
|
23
|
+
await aex.run({
|
|
24
|
+
model: Models.CLAUDE_HAIKU_4_5,
|
|
25
|
+
message: "Write a short report and save it as a file.",
|
|
26
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### CLI
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
aex run \
|
|
34
|
+
--api-key "$AEX_API_KEY" \
|
|
35
|
+
--anthropic-api-key "$ANTHROPIC_API_KEY" \
|
|
36
|
+
--model claude-haiku-4-5 \
|
|
37
|
+
--prompt "Write a short report and save it as a file."
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Upload An Env Secret
|
|
41
|
+
|
|
42
|
+
Use `Secret.value(...).upload(...)` when you start with an ephemeral value and
|
|
43
|
+
want to persist it as a named workspace secret for later runs.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { Aex, Models, Providers, Secret } from "@aexhq/sdk";
|
|
47
|
+
|
|
48
|
+
const aex = new Aex({ apiKey: process.env.AEX_API_KEY! });
|
|
49
|
+
|
|
50
|
+
const githubToken = await Secret.value(process.env.GITHUB_TOKEN!).upload(aex, {
|
|
51
|
+
name: "github-token"
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await aex.run({
|
|
55
|
+
provider: Providers.ANTHROPIC,
|
|
56
|
+
model: Models.CLAUDE_HAIKU_4_5,
|
|
57
|
+
message: "Inspect the repository issues.",
|
|
58
|
+
environment: { secrets: { GITHUB_TOKEN: githubToken } },
|
|
59
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Set Or Rotate A Workspace Secret
|
|
64
|
+
|
|
65
|
+
Use `client.secrets.set(...)` to create a named secret directly. Use
|
|
66
|
+
`client.secrets.rotate(...)` to replace its value while keeping the same name.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
await aex.secrets.set({
|
|
70
|
+
name: "serper-api-key",
|
|
71
|
+
value: process.env.SERPER_API_KEY!
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await aex.secrets.rotate({
|
|
75
|
+
name: "serper-api-key",
|
|
76
|
+
value: process.env.SERPER_API_KEY_NEXT!
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Retrieve Secret Metadata
|
|
81
|
+
|
|
82
|
+
`list` and `get` return metadata only. They never return the secret value.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const secrets = await aex.secrets.list();
|
|
86
|
+
const metadata = await aex.secrets.get("serper-api-key");
|
|
87
|
+
```
|
|
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
|
-
|
|
96
91
|
Reference workspace secrets with `Secret.ref(name)`. The value resolves
|
|
97
92
|
server-side and is injected as the named environment variable.
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
import { Models, Providers, Secret } from "@aexhq/sdk";
|
|
101
|
-
|
|
102
|
-
await aex.run({
|
|
103
|
-
provider: Providers.ANTHROPIC,
|
|
104
|
-
model: Models.CLAUDE_HAIKU_4_5,
|
|
105
|
-
message: "Use SERPER_API_KEY for web search.",
|
|
106
|
-
environment: {
|
|
107
|
-
secrets: { SERPER_API_KEY: Secret.ref("serper-api-key") }
|
|
108
|
-
},
|
|
109
|
-
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
110
|
-
});
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Delete A Workspace Secret
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
await aex.secrets.delete("serper-api-key");
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
The CLI supports per-run provider and MCP credentials. Workspace secret
|
|
120
|
-
administration is exposed through the SDK.
|
|
121
|
-
|
|
122
|
-
## Redaction Scope And Output Files
|
|
123
|
-
|
|
124
|
-
Registered secret values are redacted from the run's **event stream** (both tool
|
|
125
|
-
output and model-authored surfaces) — a value you inject via `environment.secrets`
|
|
126
|
-
is masked regardless of its shape. Two surfaces are intentionally *not* scrubbed:
|
|
127
|
-
|
|
128
|
-
- **Captured output files** (`outputs().download()` / `read()` / the `aex download`
|
|
129
|
-
zip) are returned **verbatim**. They are your run's own artifacts, so the platform
|
|
130
|
-
does not rewrite their bytes — if the agent writes a secret into a deliverable file,
|
|
131
|
-
that file contains it. Treat downloaded outputs as unredacted.
|
|
132
|
-
- An **unregistered** secret (a credential the run produces itself and never declared
|
|
133
|
-
via `environment.secrets`) can only be masked heuristically by shape; register the
|
|
134
|
-
values you care about so they are masked by value.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { Models, Providers, Secret } from "@aexhq/sdk";
|
|
96
|
+
|
|
97
|
+
await aex.run({
|
|
98
|
+
provider: Providers.ANTHROPIC,
|
|
99
|
+
model: Models.CLAUDE_HAIKU_4_5,
|
|
100
|
+
message: "Use SERPER_API_KEY for web search.",
|
|
101
|
+
environment: {
|
|
102
|
+
secrets: { SERPER_API_KEY: Secret.ref("serper-api-key") }
|
|
103
|
+
},
|
|
104
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Delete A Workspace Secret
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
await aex.secrets.delete("serper-api-key");
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The CLI supports per-run provider and MCP credentials. Workspace secret
|
|
115
|
+
administration is exposed through the SDK.
|
|
116
|
+
|
|
117
|
+
## Redaction Scope And Output Files
|
|
118
|
+
|
|
119
|
+
Registered secret values are redacted from the run's **event stream** (both tool
|
|
120
|
+
output and model-authored surfaces) — a value you inject via `environment.secrets`
|
|
121
|
+
is masked regardless of its shape. Two surfaces are intentionally *not* scrubbed:
|
|
122
|
+
|
|
123
|
+
- **Captured output files** (`outputs().download()` / `read()` / the `aex download`
|
|
124
|
+
zip) are returned **verbatim**. They are your run's own artifacts, so the platform
|
|
125
|
+
does not rewrite their bytes — if the agent writes a secret into a deliverable file,
|
|
126
|
+
that file contains it. Treat downloaded outputs as unredacted.
|
|
127
|
+
- An **unregistered** secret (a credential the run produces itself and never declared
|
|
128
|
+
via `environment.secrets`) can only be masked heuristically by shape; register the
|
|
129
|
+
values you care about so they are masked by value.
|
package/docs/testing.md
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Testing
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Testing
|
|
6
|
-
|
|
7
|
-
aex uses test-first development.
|
|
8
|
-
For repository changes, default the first meaningful test to blackbox behavior
|
|
9
|
-
from requirements, public contracts, docs, logs, or user-visible evidence before
|
|
10
|
-
reading or changing implementation.
|
|
11
|
-
|
|
12
|
-
Workspace-wide commands:
|
|
13
|
-
|
|
14
|
-
```text
|
|
15
|
-
bun run lint # typecheck/build prerequisites + lint
|
|
16
|
-
bun run test # unit, all packages, deterministic
|
|
17
|
-
bun run test:user:offline # clean Bun install of packed/published SDK, no live API
|
|
18
|
-
bun run test:user # live hosted API user tests
|
|
19
|
-
bun run test:user:heavy # explicit heavy live canary
|
|
20
|
-
bun run docs:build # generated docs + Next build
|
|
21
|
-
bun run pack:sdk # SDK pack dry-run + public boundary check
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Unit tests are deterministic and may use fakes. Offline user tests install the
|
|
25
|
-
packed or published SDK into clean Bun temp projects and do not need provider
|
|
26
|
-
credentials.
|
|
27
|
-
|
|
28
|
-
When neither `AEX_USER_TEST_TARBALL` nor `AEX_USER_TEST_VERSION` is set, the
|
|
29
|
-
user-test fixture packs the current workspace SDK into a tempdir and installs
|
|
30
|
-
that artifact. CI or release validation can pin an explicit artifact with
|
|
31
|
-
exactly one of `AEX_USER_TEST_TARBALL` or `AEX_USER_TEST_VERSION`.
|
|
32
|
-
|
|
33
|
-
Live user tests run against a hosted aex API and fail loudly when required env
|
|
34
|
-
is missing: `AEX_API_URL`, `AEX_API_KEY`, `ANTHROPIC_API_KEY`, and
|
|
35
|
-
`DEEPSEEK_API_KEY`.
|
|
1
|
+
---
|
|
2
|
+
title: Testing
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Testing
|
|
6
|
+
|
|
7
|
+
aex uses test-first development.
|
|
8
|
+
For repository changes, default the first meaningful test to blackbox behavior
|
|
9
|
+
from requirements, public contracts, docs, logs, or user-visible evidence before
|
|
10
|
+
reading or changing implementation.
|
|
11
|
+
|
|
12
|
+
Workspace-wide commands:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
bun run lint # typecheck/build prerequisites + lint
|
|
16
|
+
bun run test # unit, all packages, deterministic
|
|
17
|
+
bun run test:user:offline # clean Bun install of packed/published SDK, no live API
|
|
18
|
+
bun run test:user # live hosted API user tests
|
|
19
|
+
bun run test:user:heavy # explicit heavy live canary
|
|
20
|
+
bun run docs:build # generated docs + Next build
|
|
21
|
+
bun run pack:sdk # SDK pack dry-run + public boundary check
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Unit tests are deterministic and may use fakes. Offline user tests install the
|
|
25
|
+
packed or published SDK into clean Bun temp projects and do not need provider
|
|
26
|
+
credentials.
|
|
27
|
+
|
|
28
|
+
When neither `AEX_USER_TEST_TARBALL` nor `AEX_USER_TEST_VERSION` is set, the
|
|
29
|
+
user-test fixture packs the current workspace SDK into a tempdir and installs
|
|
30
|
+
that artifact. CI or release validation can pin an explicit artifact with
|
|
31
|
+
exactly one of `AEX_USER_TEST_TARBALL` or `AEX_USER_TEST_VERSION`.
|
|
32
|
+
|
|
33
|
+
Live user tests run against a hosted aex API and fail loudly when required env
|
|
34
|
+
is missing: `AEX_API_URL`, `AEX_API_KEY`, `ANTHROPIC_API_KEY`, and
|
|
35
|
+
`DEEPSEEK_API_KEY`.
|
package/docs/vision-skills.md
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Call a vision API from a skill
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Call a vision API from a skill
|
|
6
|
-
|
|
7
|
-
aex has no built-in vision tool. The agent's `provider` / `model` selects the
|
|
8
|
-
reasoning model for the run; if a skill needs image understanding mid-run, ship a
|
|
9
|
-
skill that calls the vision provider with normal HTTP and pass that provider key
|
|
10
|
-
as a runtime secret.
|
|
11
|
-
|
|
12
|
-
The runnable example lives at [`examples/vision-skill/`](../../../examples/vision-skill).
|
|
13
|
-
It captions a frame with ByteDance Doubao Seed Vision (Ark) and returns a
|
|
14
|
-
per-noun "does the frame depict X?" verdict.
|
|
15
|
-
|
|
16
|
-
## Submit the run
|
|
17
|
-
|
|
18
|
-
```ts
|
|
1
|
+
---
|
|
2
|
+
title: Call a vision API from a skill
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Call a vision API from a skill
|
|
6
|
+
|
|
7
|
+
aex has no built-in vision tool. The agent's `provider` / `model` selects the
|
|
8
|
+
reasoning model for the run; if a skill needs image understanding mid-run, ship a
|
|
9
|
+
skill that calls the vision provider with normal HTTP and pass that provider key
|
|
10
|
+
as a runtime secret.
|
|
11
|
+
|
|
12
|
+
The runnable example lives at [`examples/vision-skill/`](../../../examples/vision-skill).
|
|
13
|
+
It captions a frame with ByteDance Doubao Seed Vision (Ark) and returns a
|
|
14
|
+
per-noun "does the frame depict X?" verdict.
|
|
15
|
+
|
|
16
|
+
## Submit the run
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
19
|
import { Aex, Models, Secret, Skill } from "@aexhq/sdk";
|
|
20
|
-
|
|
21
|
-
const aex = new Aex({ apiKey: process.env.AEX_API_KEY! });
|
|
22
|
-
|
|
23
|
-
const result = await aex.run({
|
|
24
|
-
model: Models.CLAUDE_HAIKU_4_5,
|
|
25
|
-
message: "Read skills/frame-vision-gate/SKILL.md, then caption and verify the frame.",
|
|
20
|
+
|
|
21
|
+
const aex = new Aex({ apiKey: process.env.AEX_API_KEY! });
|
|
22
|
+
|
|
23
|
+
const result = await aex.run({
|
|
24
|
+
model: Models.CLAUDE_HAIKU_4_5,
|
|
25
|
+
message: "Read skills/frame-vision-gate/SKILL.md, then caption and verify the frame.",
|
|
26
26
|
skills: [await Skill.fromDir("./vision-skill", { name: "frame-vision-gate" })],
|
|
27
|
-
environment: {
|
|
28
|
-
secrets: {
|
|
29
|
-
DOUBAO_API_KEY: Secret.value(process.env.DOUBAO_API_KEY!)
|
|
30
|
-
},
|
|
31
|
-
networking: {
|
|
32
|
-
mode: "limited",
|
|
33
|
-
allowedHosts: ["ark.ap-southeast.bytepluses.com"]
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
console.log(result.runId, result.text);
|
|
40
|
-
```
|
|
41
|
-
|
|
27
|
+
environment: {
|
|
28
|
+
secrets: {
|
|
29
|
+
DOUBAO_API_KEY: Secret.value(process.env.DOUBAO_API_KEY!)
|
|
30
|
+
},
|
|
31
|
+
networking: {
|
|
32
|
+
mode: "limited",
|
|
33
|
+
allowedHosts: ["ark.ap-southeast.bytepluses.com"]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
console.log(result.runId, result.text);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
42
|
`Skill.fromDir("./vision-skill", ...)` is resolved relative to the process
|
|
43
|
-
CWD. Run the script from the directory that contains `vision-skill/` (in this
|
|
44
|
-
repo, `examples/`).
|
|
45
|
-
|
|
46
|
-
## Call the provider from the skill
|
|
47
|
-
|
|
48
|
-
Inside the run, the skill reads `DOUBAO_API_KEY` and makes an
|
|
49
|
-
OpenAI-compatible chat-completions request with Python's standard HTTP client.
|
|
50
|
-
The image is base64-inlined as a data URL in the request body:
|
|
51
|
-
|
|
52
|
-
```python
|
|
53
|
-
import base64, json, os, urllib.request
|
|
54
|
-
|
|
55
|
-
b64 = base64.b64encode(open("/workspace/files/frame.jpg", "rb").read()).decode()
|
|
56
|
-
request_body = {
|
|
57
|
-
"model": "doubao-seed-1-6-vision-250815",
|
|
58
|
-
"temperature": 0,
|
|
59
|
-
"response_format": {"type": "json_object"},
|
|
60
|
-
"messages": [
|
|
61
|
-
{"role": "system", "content": "Describe only what the pixels show."},
|
|
62
|
-
{"role": "user", "content": [
|
|
63
|
-
{"type": "text", "text": "Does this frame depict an owlbear? Answer as JSON."},
|
|
64
|
-
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}}
|
|
65
|
-
]}
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
req = urllib.request.Request(
|
|
70
|
-
"https://ark.ap-southeast.bytepluses.com/api/v3/chat/completions",
|
|
71
|
-
data=json.dumps(request_body).encode("utf-8"),
|
|
72
|
-
headers={
|
|
73
|
-
"Authorization": f"Bearer {os.environ['DOUBAO_API_KEY']}",
|
|
74
|
-
"Content-Type": "application/json"
|
|
75
|
-
},
|
|
76
|
-
method="POST",
|
|
77
|
-
)
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
The same pattern works for OpenAI, Gemini's OpenAI-compatible endpoint, or any
|
|
81
|
-
other HTTPS model API. Put the key in `environment.secrets`, allow-list the host
|
|
82
|
-
when using limited networking, and use the provider's normal SDK or HTTP API.
|
|
83
|
-
|
|
84
|
-
## Payload size
|
|
85
|
-
|
|
86
|
-
Base64 images are larger than their source files. Scale frames before captioning
|
|
87
|
-
when possible, for example:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
ffmpeg -i source.mp4 -vf fps=1,scale=960:-1 frame_%03d.jpg
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
This keeps upload size and model cost bounded without losing the signal most
|
|
94
|
-
vision models need.
|
|
43
|
+
CWD. Run the script from the directory that contains `vision-skill/` (in this
|
|
44
|
+
repo, `examples/`).
|
|
45
|
+
|
|
46
|
+
## Call the provider from the skill
|
|
47
|
+
|
|
48
|
+
Inside the run, the skill reads `DOUBAO_API_KEY` and makes an
|
|
49
|
+
OpenAI-compatible chat-completions request with Python's standard HTTP client.
|
|
50
|
+
The image is base64-inlined as a data URL in the request body:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import base64, json, os, urllib.request
|
|
54
|
+
|
|
55
|
+
b64 = base64.b64encode(open("/workspace/files/frame.jpg", "rb").read()).decode()
|
|
56
|
+
request_body = {
|
|
57
|
+
"model": "doubao-seed-1-6-vision-250815",
|
|
58
|
+
"temperature": 0,
|
|
59
|
+
"response_format": {"type": "json_object"},
|
|
60
|
+
"messages": [
|
|
61
|
+
{"role": "system", "content": "Describe only what the pixels show."},
|
|
62
|
+
{"role": "user", "content": [
|
|
63
|
+
{"type": "text", "text": "Does this frame depict an owlbear? Answer as JSON."},
|
|
64
|
+
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}}
|
|
65
|
+
]}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
req = urllib.request.Request(
|
|
70
|
+
"https://ark.ap-southeast.bytepluses.com/api/v3/chat/completions",
|
|
71
|
+
data=json.dumps(request_body).encode("utf-8"),
|
|
72
|
+
headers={
|
|
73
|
+
"Authorization": f"Bearer {os.environ['DOUBAO_API_KEY']}",
|
|
74
|
+
"Content-Type": "application/json"
|
|
75
|
+
},
|
|
76
|
+
method="POST",
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The same pattern works for OpenAI, Gemini's OpenAI-compatible endpoint, or any
|
|
81
|
+
other HTTPS model API. Put the key in `environment.secrets`, allow-list the host
|
|
82
|
+
when using limited networking, and use the provider's normal SDK or HTTP API.
|
|
83
|
+
|
|
84
|
+
## Payload size
|
|
85
|
+
|
|
86
|
+
Base64 images are larger than their source files. Scale frames before captioning
|
|
87
|
+
when possible, for example:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ffmpeg -i source.mp4 -vf fps=1,scale=960:-1 frame_%03d.jpg
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This keeps upload size and model cost bounded without losing the signal most
|
|
94
|
+
vision models need.
|