feat: build sdk cookbook
This commit is contained in:
parent
e7f791c2ba
commit
5795df8048
34
.github/workflows/check.yml
vendored
Normal file
34
.github/workflows/check.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.23.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Check
|
||||
run: pnpm check
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
coverage/
|
||||
dist/
|
||||
.turbo/
|
||||
.DS_Store
|
||||
*.log
|
||||
|
||||
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
25
CONTRIBUTING.md
Normal file
25
CONTRIBUTING.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Contributing
|
||||
|
||||
Cookbook recipes should be small, runnable, and copyable.
|
||||
|
||||
## Recipe Rules
|
||||
|
||||
- Put each recipe in `recipes/<id>/`.
|
||||
- Include `README.md` and `index.ts`.
|
||||
- Add the recipe to `recipes/manifest.json`.
|
||||
- Import the SDK as `@openclaw/sdk`, not from OpenClaw monorepo internals.
|
||||
- Keep recipe code focused on one SDK concept.
|
||||
- Prefer environment variables for Gateway configuration.
|
||||
- Add or update tests in `test/recipes.test.ts`.
|
||||
|
||||
## Checks
|
||||
|
||||
Run the full gate before opening a PR:
|
||||
|
||||
```bash
|
||||
pnpm check
|
||||
```
|
||||
|
||||
Until `@openclaw/sdk` is published, tests use `test/shims/openclaw-sdk.ts`.
|
||||
That shim is only a local validation aid; recipe source should still reflect the
|
||||
real public SDK API.
|
||||
74
README.md
74
README.md
@ -1,2 +1,72 @@
|
||||
# cookbook
|
||||
Example apps for the OpenClaw SDK
|
||||
# OpenClaw Cookbook
|
||||
|
||||
Runnable examples for building on the OpenClaw SDK.
|
||||
|
||||
This repository is the public, copyable companion to the SDK. The recipes are
|
||||
small enough to paste into an app, while the examples show how to compose them
|
||||
into complete developer workflows.
|
||||
|
||||
## Status
|
||||
|
||||
The SDK package is landing in `openclaw/openclaw` first. Until `@openclaw/sdk`
|
||||
is published, this repo keeps a tiny test shim so CI can validate recipe shape
|
||||
without depending on a live Gateway or unpublished package.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm check
|
||||
```
|
||||
|
||||
To run a recipe against a real Gateway, install the SDK once it is published and
|
||||
set the Gateway connection details:
|
||||
|
||||
```bash
|
||||
pnpm add @openclaw/sdk
|
||||
export OPENCLAW_GATEWAY=auto
|
||||
export OPENCLAW_AGENT_ID=main
|
||||
pnpm recipe:run-agent -- "Summarize this repository"
|
||||
```
|
||||
|
||||
Useful environment variables:
|
||||
|
||||
| Name | Purpose |
|
||||
| ---------------------- | ------------------------------------------------------------------- |
|
||||
| `OPENCLAW_GATEWAY` | Gateway URL, or `auto` for local discovery. |
|
||||
| `OPENCLAW_TOKEN` | Bearer token for protected Gateways. |
|
||||
| `OPENCLAW_PASSWORD` | Password for protected Gateways. |
|
||||
| `OPENCLAW_AGENT_ID` | Agent id used by recipes. Defaults to `main`. |
|
||||
| `OPENCLAW_SESSION_KEY` | Session key for recipes that reuse a conversation. |
|
||||
| `OPENCLAW_MODEL` | Optional model override, such as `openrouter/deepseek/deepseek-r1`. |
|
||||
|
||||
## Recipes
|
||||
|
||||
| Recipe | What it shows |
|
||||
| ---------------------------------------------- | --------------------------------------------------- |
|
||||
| [`run-an-agent`](recipes/run-an-agent) | Start a run and wait for a stable result envelope. |
|
||||
| [`stream-events`](recipes/stream-events) | Subscribe to normalized SDK events for a run. |
|
||||
| [`cancel-a-run`](recipes/cancel-a-run) | Cancel active work by run id. |
|
||||
| [`reuse-session`](recipes/reuse-session) | Create or reuse a session across multiple messages. |
|
||||
| [`model-status`](recipes/model-status) | Check configured model providers and auth status. |
|
||||
| [`custom-transport`](recipes/custom-transport) | Test SDK code with an in-memory transport. |
|
||||
|
||||
## Examples
|
||||
|
||||
| Example | What it is |
|
||||
| ------------------------------- | ----------------------------------------------------- |
|
||||
| [`node-cli`](examples/node-cli) | A small command-line app that wraps the core recipes. |
|
||||
|
||||
## Repository Scripts
|
||||
|
||||
```bash
|
||||
pnpm format:check
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm docs:check
|
||||
pnpm check
|
||||
```
|
||||
|
||||
The test suite aliases `@openclaw/sdk` to `test/shims/openclaw-sdk.ts`. That
|
||||
shim exists only for cookbook validation. Recipe source imports `@openclaw/sdk`
|
||||
directly so copied code matches real SDK usage.
|
||||
|
||||
13
examples/node-cli/README.md
Normal file
13
examples/node-cli/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Node CLI Example
|
||||
|
||||
A small command-line wrapper around the cookbook recipes.
|
||||
|
||||
```bash
|
||||
pnpm example:node-cli run "Say hello"
|
||||
pnpm example:node-cli stream "Explain this branch"
|
||||
pnpm example:node-cli models
|
||||
pnpm example:node-cli session
|
||||
```
|
||||
|
||||
In a real app, keep the recipe functions as library code and make the CLI only
|
||||
responsible for parsing arguments, output formatting, and exit codes.
|
||||
45
examples/node-cli/src/index.ts
Normal file
45
examples/node-cli/src/index.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { cancelRunRecipe } from "../../../recipes/cancel-a-run/index.js";
|
||||
import { modelStatusRecipe } from "../../../recipes/model-status/index.js";
|
||||
import { reuseSessionRecipe } from "../../../recipes/reuse-session/index.js";
|
||||
import { runAgentRecipe } from "../../../recipes/run-an-agent/index.js";
|
||||
import { streamEventsRecipe } from "../../../recipes/stream-events/index.js";
|
||||
|
||||
function usage(): string {
|
||||
return [
|
||||
"Usage: pnpm example:node-cli <command> [prompt]",
|
||||
"",
|
||||
"Commands:",
|
||||
" run Start a run and wait for the result",
|
||||
" stream Start a run and print normalized event summaries",
|
||||
" cancel Start a run and cancel it",
|
||||
" session Reuse a session for two messages",
|
||||
" models Print model provider/auth status",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function main(argv: string[]): Promise<unknown> {
|
||||
const [command, ...rest] = argv;
|
||||
const input = rest.join(" ") || undefined;
|
||||
switch (command) {
|
||||
case "run":
|
||||
return await runAgentRecipe({ input });
|
||||
case "stream":
|
||||
return await streamEventsRecipe({ input });
|
||||
case "cancel":
|
||||
return await cancelRunRecipe({ input });
|
||||
case "session":
|
||||
return await reuseSessionRecipe({ firstInput: input });
|
||||
case "models":
|
||||
return await modelStatusRecipe();
|
||||
default:
|
||||
return usage();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await main(process.argv.slice(2));
|
||||
console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
37
package.json
Normal file
37
package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@openclaw/cookbook",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.23.0",
|
||||
"scripts": {
|
||||
"check": "pnpm format:check && pnpm typecheck && pnpm test && pnpm docs:check",
|
||||
"docs:check": "node scripts/check-docs.mjs",
|
||||
"example:node-cli": "tsx examples/node-cli/src/index.ts",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"recipe:cancel-a-run": "tsx recipes/cancel-a-run/index.ts",
|
||||
"recipe:custom-transport": "tsx recipes/custom-transport/index.ts",
|
||||
"recipe:model-status": "tsx recipes/model-status/index.ts",
|
||||
"recipe:reuse-session": "tsx recipes/reuse-session/index.ts",
|
||||
"recipe:run-agent": "tsx recipes/run-an-agent/index.ts",
|
||||
"recipe:stream-events": "tsx recipes/stream-events/index.ts",
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@openclaw/sdk": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@openclaw/sdk": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1",
|
||||
"prettier": "^3.7.4",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
1412
pnpm-lock.yaml
generated
Normal file
1412
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- "."
|
||||
14
recipes/README.md
Normal file
14
recipes/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Recipes
|
||||
|
||||
Recipes are intentionally small. Each one demonstrates one SDK workflow and is
|
||||
safe to copy into a real app.
|
||||
|
||||
Current recipes are listed in [`manifest.json`](manifest.json).
|
||||
|
||||
## Adding a Recipe
|
||||
|
||||
1. Create `recipes/<id>/README.md`.
|
||||
2. Create `recipes/<id>/index.ts`.
|
||||
3. Add the recipe to `recipes/manifest.json`.
|
||||
4. Add test coverage in `test/recipes.test.ts`.
|
||||
5. Run `pnpm check`.
|
||||
52
recipes/_shared/config.ts
Normal file
52
recipes/_shared/config.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { OpenClaw, type OpenClawOptions } from "@openclaw/sdk";
|
||||
|
||||
export type CookbookConnectionOptions = {
|
||||
gateway?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export type RunRecipeOptions = CookbookConnectionOptions & {
|
||||
agentId?: string;
|
||||
input?: string;
|
||||
model?: string;
|
||||
sessionKey?: string;
|
||||
timeoutMs?: number;
|
||||
waitTimeoutMs?: number;
|
||||
};
|
||||
|
||||
export function readConnectionOptions(options: CookbookConnectionOptions = {}): OpenClawOptions {
|
||||
return {
|
||||
gateway: options.gateway ?? process.env.OPENCLAW_GATEWAY ?? "auto",
|
||||
token: options.token ?? process.env.OPENCLAW_TOKEN,
|
||||
password: options.password ?? process.env.OPENCLAW_PASSWORD,
|
||||
};
|
||||
}
|
||||
|
||||
export function createClient(options: CookbookConnectionOptions = {}): OpenClaw {
|
||||
return new OpenClaw(readConnectionOptions(options));
|
||||
}
|
||||
|
||||
export function readAgentId(agentId?: string): string {
|
||||
return agentId ?? process.env.OPENCLAW_AGENT_ID ?? "main";
|
||||
}
|
||||
|
||||
export function readInput(input?: string): string {
|
||||
return input ?? (process.argv.slice(2).join(" ") || "Say hello from the OpenClaw SDK cookbook.");
|
||||
}
|
||||
|
||||
export function readSessionKey(sessionKey?: string): string {
|
||||
return sessionKey ?? process.env.OPENCLAW_SESSION_KEY ?? "cookbook";
|
||||
}
|
||||
|
||||
export function readTimeoutMs(value: number | undefined, fallback: number): number {
|
||||
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
||||
return Math.floor(value);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function optionalModel(model?: string): { model?: string } {
|
||||
const resolved = model ?? process.env.OPENCLAW_MODEL;
|
||||
return resolved ? { model: resolved } : {};
|
||||
}
|
||||
19
recipes/_shared/run-main.ts
Normal file
19
recipes/_shared/run-main.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
export function isDirectRun(metaUrl: string): boolean {
|
||||
const entry = process.argv[1];
|
||||
return entry ? metaUrl === pathToFileURL(entry).href : false;
|
||||
}
|
||||
|
||||
export async function runMain(action: () => Promise<unknown>): Promise<void> {
|
||||
try {
|
||||
const result = await action();
|
||||
if (result !== undefined) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(message);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
11
recipes/cancel-a-run/README.md
Normal file
11
recipes/cancel-a-run/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Cancel a Run
|
||||
|
||||
Start a run, cancel it by `runId`, then wait for the Gateway result. This is the
|
||||
shape to use for UI stop buttons and automation time budgets.
|
||||
|
||||
```bash
|
||||
OPENCLAW_CANCEL_AFTER_MS=1500 pnpm recipe:cancel-a-run -- "Keep working until cancelled"
|
||||
```
|
||||
|
||||
The SDK cancellation path does not require callers to know the session key when
|
||||
the Gateway can resolve the active run by id.
|
||||
46
recipes/cancel-a-run/index.ts
Normal file
46
recipes/cancel-a-run/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { RunResult } from "@openclaw/sdk";
|
||||
import {
|
||||
createClient,
|
||||
optionalModel,
|
||||
readAgentId,
|
||||
readInput,
|
||||
readSessionKey,
|
||||
readTimeoutMs,
|
||||
type RunRecipeOptions,
|
||||
} from "../_shared/config.js";
|
||||
import { isDirectRun, runMain } from "../_shared/run-main.js";
|
||||
|
||||
export type CancelRunRecipeResult = {
|
||||
runId: string;
|
||||
cancelResponse: unknown;
|
||||
result: RunResult;
|
||||
};
|
||||
|
||||
export async function cancelRunRecipe(
|
||||
options: RunRecipeOptions & { cancelAfterMs?: number } = {},
|
||||
): Promise<CancelRunRecipeResult> {
|
||||
const oc = createClient(options);
|
||||
try {
|
||||
const run = await oc.runs.create({
|
||||
input: readInput(options.input),
|
||||
agentId: readAgentId(options.agentId),
|
||||
sessionKey: readSessionKey(options.sessionKey),
|
||||
timeoutMs: readTimeoutMs(options.timeoutMs, 300_000),
|
||||
...optionalModel(options.model),
|
||||
});
|
||||
const cancelAfterMs = readTimeoutMs(
|
||||
options.cancelAfterMs ?? Number(process.env.OPENCLAW_CANCEL_AFTER_MS),
|
||||
1_000,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, cancelAfterMs));
|
||||
const cancelResponse = await run.cancel();
|
||||
const result = await run.wait({ timeoutMs: readTimeoutMs(options.waitTimeoutMs, 30_000) });
|
||||
return { runId: run.id, cancelResponse, result };
|
||||
} finally {
|
||||
await oc.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runMain(() => cancelRunRecipe());
|
||||
}
|
||||
11
recipes/custom-transport/README.md
Normal file
11
recipes/custom-transport/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Custom Transport
|
||||
|
||||
Use an in-memory SDK transport to test app code without a real Gateway.
|
||||
|
||||
```bash
|
||||
pnpm recipe:custom-transport -- "test prompt"
|
||||
```
|
||||
|
||||
This pattern is useful for app tests: implement the SDK transport interface,
|
||||
return canned RPC responses, and assert your app behavior around the SDK
|
||||
boundary.
|
||||
55
recipes/custom-transport/index.ts
Normal file
55
recipes/custom-transport/index.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import type { GatewayEvent, GatewayRequestOptions, OpenClawTransport } from "@openclaw/sdk";
|
||||
import { OpenClaw } from "@openclaw/sdk";
|
||||
import { readInput } from "../_shared/config.js";
|
||||
import { isDirectRun, runMain } from "../_shared/run-main.js";
|
||||
|
||||
type RequestCall = {
|
||||
method: string;
|
||||
params?: unknown;
|
||||
options?: GatewayRequestOptions;
|
||||
};
|
||||
|
||||
class CookbookTransport implements OpenClawTransport {
|
||||
readonly calls: RequestCall[] = [];
|
||||
|
||||
async request<T = unknown>(
|
||||
method: string,
|
||||
params?: unknown,
|
||||
options?: GatewayRequestOptions,
|
||||
): Promise<T> {
|
||||
this.calls.push({ method, params, options });
|
||||
if (method === "agent") {
|
||||
return { status: "accepted", runId: "cookbook-run" } as T;
|
||||
}
|
||||
if (method === "agent.wait") {
|
||||
return { status: "ok", runId: "cookbook-run", endedAt: Date.now() } as T;
|
||||
}
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
}
|
||||
|
||||
async *events(): AsyncIterable<GatewayEvent> {
|
||||
yield {
|
||||
event: "agent",
|
||||
payload: {
|
||||
runId: "cookbook-run",
|
||||
stream: "lifecycle",
|
||||
data: { phase: "end" },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function customTransportRecipe(input = readInput()): Promise<{
|
||||
runId: string;
|
||||
calls: RequestCall[];
|
||||
}> {
|
||||
const transport = new CookbookTransport();
|
||||
const oc = new OpenClaw({ transport });
|
||||
const run = await oc.runs.create({ input, idempotencyKey: "cookbook-custom-transport" });
|
||||
await run.wait({ timeoutMs: 1_000 });
|
||||
return { runId: run.id, calls: transport.calls };
|
||||
}
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runMain(() => customTransportRecipe());
|
||||
}
|
||||
38
recipes/manifest.json
Normal file
38
recipes/manifest.json
Normal file
@ -0,0 +1,38 @@
|
||||
[
|
||||
{
|
||||
"id": "run-an-agent",
|
||||
"title": "Run an agent",
|
||||
"entry": "recipes/run-an-agent/index.ts",
|
||||
"readme": "recipes/run-an-agent/README.md"
|
||||
},
|
||||
{
|
||||
"id": "stream-events",
|
||||
"title": "Stream events",
|
||||
"entry": "recipes/stream-events/index.ts",
|
||||
"readme": "recipes/stream-events/README.md"
|
||||
},
|
||||
{
|
||||
"id": "cancel-a-run",
|
||||
"title": "Cancel a run",
|
||||
"entry": "recipes/cancel-a-run/index.ts",
|
||||
"readme": "recipes/cancel-a-run/README.md"
|
||||
},
|
||||
{
|
||||
"id": "reuse-session",
|
||||
"title": "Reuse a session",
|
||||
"entry": "recipes/reuse-session/index.ts",
|
||||
"readme": "recipes/reuse-session/README.md"
|
||||
},
|
||||
{
|
||||
"id": "model-status",
|
||||
"title": "Model status",
|
||||
"entry": "recipes/model-status/index.ts",
|
||||
"readme": "recipes/model-status/README.md"
|
||||
},
|
||||
{
|
||||
"id": "custom-transport",
|
||||
"title": "Custom transport",
|
||||
"entry": "recipes/custom-transport/index.ts",
|
||||
"readme": "recipes/custom-transport/README.md"
|
||||
}
|
||||
]
|
||||
10
recipes/model-status/README.md
Normal file
10
recipes/model-status/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Model Status
|
||||
|
||||
Ask the Gateway for model provider/auth status.
|
||||
|
||||
```bash
|
||||
pnpm recipe:model-status
|
||||
```
|
||||
|
||||
Pass `OPENCLAW_MODEL_STATUS_PROBE=1` to let the Gateway run provider probes when
|
||||
that is appropriate for your environment.
|
||||
18
recipes/model-status/index.ts
Normal file
18
recipes/model-status/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { createClient, type CookbookConnectionOptions } from "../_shared/config.js";
|
||||
import { isDirectRun, runMain } from "../_shared/run-main.js";
|
||||
|
||||
export async function modelStatusRecipe(
|
||||
options: CookbookConnectionOptions & { probe?: boolean } = {},
|
||||
): Promise<unknown> {
|
||||
const oc = createClient(options);
|
||||
try {
|
||||
const probe = options.probe ?? process.env.OPENCLAW_MODEL_STATUS_PROBE === "1";
|
||||
return await oc.models.status({ probe });
|
||||
} finally {
|
||||
await oc.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runMain(() => modelStatusRecipe());
|
||||
}
|
||||
10
recipes/reuse-session/README.md
Normal file
10
recipes/reuse-session/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Reuse a Session
|
||||
|
||||
Create or reuse a session key, send two messages, and wait for both run results.
|
||||
|
||||
```bash
|
||||
OPENCLAW_SESSION_KEY=cookbook-demo pnpm recipe:reuse-session
|
||||
```
|
||||
|
||||
Use this pattern for chat UIs, background workflows, and any app that wants a
|
||||
stable thread rather than one-off runs.
|
||||
48
recipes/reuse-session/index.ts
Normal file
48
recipes/reuse-session/index.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { RunResult } from "@openclaw/sdk";
|
||||
import {
|
||||
createClient,
|
||||
optionalModel,
|
||||
readAgentId,
|
||||
readSessionKey,
|
||||
readTimeoutMs,
|
||||
type RunRecipeOptions,
|
||||
} from "../_shared/config.js";
|
||||
import { isDirectRun, runMain } from "../_shared/run-main.js";
|
||||
|
||||
export type ReuseSessionRecipeResult = {
|
||||
sessionKey: string;
|
||||
results: RunResult[];
|
||||
};
|
||||
|
||||
export async function reuseSessionRecipe(
|
||||
options: RunRecipeOptions & { firstInput?: string; secondInput?: string } = {},
|
||||
): Promise<ReuseSessionRecipeResult> {
|
||||
const oc = createClient(options);
|
||||
try {
|
||||
const sessionKey = readSessionKey(options.sessionKey);
|
||||
const session = await oc.sessions.create({
|
||||
key: sessionKey,
|
||||
agentId: readAgentId(options.agentId),
|
||||
...optionalModel(options.model),
|
||||
});
|
||||
const first = await session.send({
|
||||
message: options.firstInput ?? "Remember that the cookbook session is working.",
|
||||
timeoutMs: readTimeoutMs(options.timeoutMs, 60_000),
|
||||
});
|
||||
const second = await session.send({
|
||||
message: options.secondInput ?? "What did I ask you to remember?",
|
||||
timeoutMs: readTimeoutMs(options.timeoutMs, 60_000),
|
||||
});
|
||||
const waitOptions = { timeoutMs: readTimeoutMs(options.waitTimeoutMs, 120_000) };
|
||||
return {
|
||||
sessionKey,
|
||||
results: [await first.wait(waitOptions), await second.wait(waitOptions)],
|
||||
};
|
||||
} finally {
|
||||
await oc.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runMain(() => reuseSessionRecipe());
|
||||
}
|
||||
11
recipes/run-an-agent/README.md
Normal file
11
recipes/run-an-agent/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Run an Agent
|
||||
|
||||
Start a run, wait for the Gateway to return a terminal result, and print the
|
||||
stable SDK result envelope.
|
||||
|
||||
```bash
|
||||
OPENCLAW_AGENT_ID=main pnpm recipe:run-agent -- "Summarize this repository"
|
||||
```
|
||||
|
||||
Use this when you want the simplest request/response shape. For live progress,
|
||||
use [`stream-events`](../stream-events).
|
||||
31
recipes/run-an-agent/index.ts
Normal file
31
recipes/run-an-agent/index.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { RunResult } from "@openclaw/sdk";
|
||||
import {
|
||||
createClient,
|
||||
optionalModel,
|
||||
readAgentId,
|
||||
readInput,
|
||||
readSessionKey,
|
||||
readTimeoutMs,
|
||||
type RunRecipeOptions,
|
||||
} from "../_shared/config.js";
|
||||
import { isDirectRun, runMain } from "../_shared/run-main.js";
|
||||
|
||||
export async function runAgentRecipe(options: RunRecipeOptions = {}): Promise<RunResult> {
|
||||
const oc = createClient(options);
|
||||
try {
|
||||
const agent = await oc.agents.get(readAgentId(options.agentId));
|
||||
const run = await agent.run({
|
||||
input: readInput(options.input),
|
||||
sessionKey: readSessionKey(options.sessionKey),
|
||||
timeoutMs: readTimeoutMs(options.timeoutMs, 60_000),
|
||||
...optionalModel(options.model),
|
||||
});
|
||||
return await run.wait({ timeoutMs: readTimeoutMs(options.waitTimeoutMs, 120_000) });
|
||||
} finally {
|
||||
await oc.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runMain(() => runAgentRecipe());
|
||||
}
|
||||
12
recipes/stream-events/README.md
Normal file
12
recipes/stream-events/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Stream Events
|
||||
|
||||
Start a run and iterate normalized SDK events until the run reaches a terminal
|
||||
state.
|
||||
|
||||
```bash
|
||||
pnpm recipe:stream-events -- "Refactor the current branch and explain the diff"
|
||||
```
|
||||
|
||||
The SDK keeps provider-native payloads in `event.raw`, while `event.type` gives
|
||||
apps a stable UI contract such as `run.started`, `assistant.delta`, or
|
||||
`run.completed`.
|
||||
48
recipes/stream-events/index.ts
Normal file
48
recipes/stream-events/index.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { OpenClawEvent, OpenClawEventType } from "@openclaw/sdk";
|
||||
import {
|
||||
createClient,
|
||||
optionalModel,
|
||||
readAgentId,
|
||||
readInput,
|
||||
readSessionKey,
|
||||
readTimeoutMs,
|
||||
type RunRecipeOptions,
|
||||
} from "../_shared/config.js";
|
||||
import { isDirectRun, runMain } from "../_shared/run-main.js";
|
||||
|
||||
const terminalEvents = new Set<OpenClawEventType>([
|
||||
"run.completed",
|
||||
"run.failed",
|
||||
"run.cancelled",
|
||||
"run.timed_out",
|
||||
]);
|
||||
|
||||
export async function streamEventsRecipe(
|
||||
options: RunRecipeOptions = {},
|
||||
): Promise<Array<Pick<OpenClawEvent, "type" | "runId">>> {
|
||||
const oc = createClient(options);
|
||||
try {
|
||||
const run = await oc.runs.create({
|
||||
input: readInput(options.input),
|
||||
agentId: readAgentId(options.agentId),
|
||||
sessionKey: readSessionKey(options.sessionKey),
|
||||
timeoutMs: readTimeoutMs(options.timeoutMs, 60_000),
|
||||
...optionalModel(options.model),
|
||||
});
|
||||
|
||||
const seen: Array<Pick<OpenClawEvent, "type" | "runId">> = [];
|
||||
for await (const event of run.events()) {
|
||||
seen.push({ type: event.type, runId: event.runId });
|
||||
if (terminalEvents.has(event.type)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return seen;
|
||||
} finally {
|
||||
await oc.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runMain(() => streamEventsRecipe());
|
||||
}
|
||||
39
scripts/check-docs.mjs
Normal file
39
scripts/check-docs.mjs
Normal file
@ -0,0 +1,39 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const root = process.cwd();
|
||||
const manifestPath = path.join(root, "recipes", "manifest.json");
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||||
const failures = [];
|
||||
|
||||
for (const recipe of manifest) {
|
||||
for (const key of ["entry", "readme"]) {
|
||||
const value = recipe[key];
|
||||
if (typeof value !== "string" || !fs.existsSync(path.join(root, value))) {
|
||||
failures.push(`${recipe.id}: missing ${key} ${value}`);
|
||||
}
|
||||
}
|
||||
const readme = fs.readFileSync(path.join(root, recipe.readme), "utf8");
|
||||
if (!readme.includes("```bash")) {
|
||||
failures.push(`${recipe.id}: README should include a bash command`);
|
||||
}
|
||||
}
|
||||
|
||||
const readme = fs.readFileSync(path.join(root, "README.md"), "utf8");
|
||||
for (const recipe of manifest) {
|
||||
const link = `recipes/${recipe.id}`;
|
||||
if (!readme.includes(link)) {
|
||||
failures.push(`README missing link to ${link}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.join(root, "examples", "node-cli", "src", "index.ts"))) {
|
||||
failures.push("missing node-cli example entry");
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(failures.join("\n"));
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log(`docs ok: ${manifest.length} recipes`);
|
||||
}
|
||||
53
test/recipes.test.ts
Normal file
53
test/recipes.test.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { cancelRunRecipe } from "../recipes/cancel-a-run/index.js";
|
||||
import { customTransportRecipe } from "../recipes/custom-transport/index.js";
|
||||
import { modelStatusRecipe } from "../recipes/model-status/index.js";
|
||||
import { reuseSessionRecipe } from "../recipes/reuse-session/index.js";
|
||||
import { runAgentRecipe } from "../recipes/run-an-agent/index.js";
|
||||
import { streamEventsRecipe } from "../recipes/stream-events/index.js";
|
||||
|
||||
describe("cookbook recipes", () => {
|
||||
it("runs an agent and returns a completed result", async () => {
|
||||
await expect(runAgentRecipe({ input: "hello" })).resolves.toMatchObject({
|
||||
runId: "cookbook-run",
|
||||
status: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
it("streams normalized events through a terminal event", async () => {
|
||||
await expect(streamEventsRecipe({ input: "stream" })).resolves.toEqual([
|
||||
{ type: "run.started", runId: "cookbook-run" },
|
||||
{ type: "assistant.delta", runId: "cookbook-run" },
|
||||
{ type: "run.completed", runId: "cookbook-run" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("cancels a run", async () => {
|
||||
await expect(cancelRunRecipe({ input: "cancel", cancelAfterMs: 0 })).resolves.toMatchObject({
|
||||
runId: "cookbook-run",
|
||||
cancelResponse: { ok: true, status: "aborted" },
|
||||
result: { status: "completed" },
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses a session for multiple messages", async () => {
|
||||
const result = await reuseSessionRecipe({ sessionKey: "recipe-test" });
|
||||
|
||||
expect(result.sessionKey).toBe("recipe-test");
|
||||
expect(result.results).toHaveLength(2);
|
||||
expect(result.results.every((entry) => entry.status === "completed")).toBe(true);
|
||||
});
|
||||
|
||||
it("reads model status", async () => {
|
||||
await expect(modelStatusRecipe()).resolves.toMatchObject({
|
||||
providers: [{ id: "openai", authenticated: true }],
|
||||
});
|
||||
});
|
||||
|
||||
it("runs against a custom transport", async () => {
|
||||
const result = await customTransportRecipe("transport");
|
||||
|
||||
expect(result.runId).toBe("cookbook-run");
|
||||
expect(result.calls.map((call) => call.method)).toEqual(["agent", "agent.wait"]);
|
||||
});
|
||||
});
|
||||
244
test/shims/openclaw-sdk.ts
Normal file
244
test/shims/openclaw-sdk.ts
Normal file
@ -0,0 +1,244 @@
|
||||
export type GatewayRequestOptions = {
|
||||
expectFinal?: boolean;
|
||||
timeoutMs?: number | null;
|
||||
};
|
||||
|
||||
export type GatewayEvent = {
|
||||
event: string;
|
||||
payload?: unknown;
|
||||
seq?: number;
|
||||
stateVersion?: unknown;
|
||||
};
|
||||
|
||||
export type OpenClawTransport = {
|
||||
request<T = unknown>(
|
||||
method: string,
|
||||
params?: unknown,
|
||||
options?: GatewayRequestOptions,
|
||||
): Promise<T>;
|
||||
events(filter?: (event: GatewayEvent) => boolean): AsyncIterable<GatewayEvent>;
|
||||
close?(): Promise<void> | void;
|
||||
};
|
||||
|
||||
export type OpenClawOptions = {
|
||||
gateway?: "auto" | (string & {});
|
||||
url?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
requestTimeoutMs?: number;
|
||||
transport?: OpenClawTransport;
|
||||
};
|
||||
|
||||
export type RunStatus = "accepted" | "completed" | "failed" | "cancelled" | "timed_out";
|
||||
export type RunResult = {
|
||||
runId: string;
|
||||
status: RunStatus;
|
||||
sessionKey?: string;
|
||||
startedAt?: string | number;
|
||||
endedAt?: string | number;
|
||||
raw?: unknown;
|
||||
};
|
||||
|
||||
export type OpenClawEventType =
|
||||
| "run.started"
|
||||
| "run.completed"
|
||||
| "run.failed"
|
||||
| "run.cancelled"
|
||||
| "run.timed_out"
|
||||
| "assistant.delta"
|
||||
| "raw";
|
||||
|
||||
export type OpenClawEvent<TData = unknown> = {
|
||||
version: 1;
|
||||
id: string;
|
||||
ts: number;
|
||||
type: OpenClawEventType;
|
||||
runId?: string;
|
||||
data: TData;
|
||||
raw?: GatewayEvent;
|
||||
};
|
||||
|
||||
export type AgentRunParams = {
|
||||
input: string;
|
||||
agentId?: string;
|
||||
model?: string;
|
||||
sessionKey?: string;
|
||||
timeoutMs?: number;
|
||||
idempotencyKey?: string;
|
||||
};
|
||||
|
||||
export type SessionCreateParams = {
|
||||
key?: string;
|
||||
agentId?: string;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
export type SessionSendParams = {
|
||||
key?: string;
|
||||
message: string;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
function normalizeEvent(event: GatewayEvent): OpenClawEvent {
|
||||
const payload =
|
||||
typeof event.payload === "object" && event.payload !== null
|
||||
? (event.payload as Record<string, unknown>)
|
||||
: {};
|
||||
const data =
|
||||
typeof payload.data === "object" && payload.data !== null
|
||||
? (payload.data as Record<string, unknown>)
|
||||
: {};
|
||||
const phase = typeof data.phase === "string" ? data.phase : undefined;
|
||||
const stream = typeof payload.stream === "string" ? payload.stream : undefined;
|
||||
const runId = typeof payload.runId === "string" ? payload.runId : undefined;
|
||||
const type =
|
||||
stream === "assistant"
|
||||
? "assistant.delta"
|
||||
: phase === "start"
|
||||
? "run.started"
|
||||
: phase === "end"
|
||||
? "run.completed"
|
||||
: "raw";
|
||||
return {
|
||||
version: 1,
|
||||
id: `${event.seq ?? "test"}:${event.event}`,
|
||||
ts: Date.now(),
|
||||
type,
|
||||
runId,
|
||||
data,
|
||||
raw: event,
|
||||
};
|
||||
}
|
||||
|
||||
class ShimRun {
|
||||
constructor(
|
||||
private readonly client: OpenClaw,
|
||||
readonly id: string,
|
||||
private readonly sessionKey?: string,
|
||||
) {}
|
||||
|
||||
async *events(): AsyncIterable<OpenClawEvent> {
|
||||
if (this.client.transport) {
|
||||
for await (const event of this.client.transport.events()) {
|
||||
yield normalizeEvent(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
yield {
|
||||
version: 1,
|
||||
id: "start",
|
||||
ts: Date.now(),
|
||||
type: "run.started",
|
||||
runId: this.id,
|
||||
data: {},
|
||||
};
|
||||
yield {
|
||||
version: 1,
|
||||
id: "message",
|
||||
ts: Date.now(),
|
||||
type: "assistant.delta",
|
||||
runId: this.id,
|
||||
data: { delta: "hello" },
|
||||
};
|
||||
yield {
|
||||
version: 1,
|
||||
id: "end",
|
||||
ts: Date.now(),
|
||||
type: "run.completed",
|
||||
runId: this.id,
|
||||
data: {},
|
||||
};
|
||||
}
|
||||
|
||||
async wait(): Promise<RunResult> {
|
||||
if (this.client.transport) {
|
||||
const raw = await this.client.transport.request<Record<string, unknown>>(
|
||||
"agent.wait",
|
||||
{ runId: this.id },
|
||||
{ timeoutMs: null },
|
||||
);
|
||||
return {
|
||||
runId: this.id,
|
||||
status: raw.status === "ok" ? "completed" : "failed",
|
||||
endedAt: typeof raw.endedAt === "number" ? raw.endedAt : undefined,
|
||||
raw,
|
||||
};
|
||||
}
|
||||
return { runId: this.id, status: "completed", sessionKey: this.sessionKey, endedAt: 456 };
|
||||
}
|
||||
|
||||
async cancel(): Promise<unknown> {
|
||||
return { ok: true, status: "aborted", abortedRunId: this.id };
|
||||
}
|
||||
}
|
||||
|
||||
class ShimAgent {
|
||||
constructor(
|
||||
private readonly client: OpenClaw,
|
||||
readonly id: string,
|
||||
) {}
|
||||
|
||||
async run(input: string | Omit<AgentRunParams, "agentId">): Promise<ShimRun> {
|
||||
const params =
|
||||
typeof input === "string" ? { input, agentId: this.id } : { ...input, agentId: this.id };
|
||||
return await this.client.runs.create(params);
|
||||
}
|
||||
}
|
||||
|
||||
class ShimSession {
|
||||
constructor(
|
||||
private readonly client: OpenClaw,
|
||||
readonly key: string,
|
||||
) {}
|
||||
|
||||
async send(input: string | Omit<SessionSendParams, "key">): Promise<ShimRun> {
|
||||
const message = typeof input === "string" ? input : input.message;
|
||||
return await this.client.runs.create({ input: message, sessionKey: this.key });
|
||||
}
|
||||
|
||||
async abort(runId?: string): Promise<unknown> {
|
||||
return { ok: true, status: runId ? "aborted" : "no-active-run" };
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenClaw {
|
||||
readonly transport?: OpenClawTransport;
|
||||
|
||||
constructor(options: OpenClawOptions = {}) {
|
||||
this.transport = options.transport;
|
||||
}
|
||||
|
||||
readonly agents = {
|
||||
get: async (id: string) => new ShimAgent(this, id),
|
||||
};
|
||||
|
||||
readonly runs = {
|
||||
create: async (params: AgentRunParams) => {
|
||||
if (this.transport) {
|
||||
const raw = await this.transport.request<Record<string, unknown>>("agent", params, {
|
||||
expectFinal: false,
|
||||
});
|
||||
const runId = typeof raw.runId === "string" ? raw.runId : "transport-run";
|
||||
return new ShimRun(this, runId, params.sessionKey);
|
||||
}
|
||||
return new ShimRun(this, "cookbook-run", params.sessionKey);
|
||||
},
|
||||
wait: async (runId: string) => new ShimRun(this, runId).wait(),
|
||||
cancel: async (runId: string) => ({ ok: true, abortedRunId: runId, status: "aborted" }),
|
||||
};
|
||||
|
||||
readonly sessions = {
|
||||
create: async (params: SessionCreateParams = {}) =>
|
||||
new ShimSession(this, params.key ?? "cookbook"),
|
||||
};
|
||||
|
||||
readonly models = {
|
||||
status: async () => ({ providers: [{ id: "openai", authenticated: true }] }),
|
||||
};
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this.transport?.close?.();
|
||||
}
|
||||
}
|
||||
|
||||
export { ShimAgent as Agent, ShimRun as Run, ShimSession as Session };
|
||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": false,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noEmit": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"types": ["node", "vitest/globals"]
|
||||
},
|
||||
"include": [
|
||||
"examples/**/*.ts",
|
||||
"recipes/**/*.ts",
|
||||
"scripts/**/*.mjs",
|
||||
"test/**/*.ts",
|
||||
"types/**/*.d.ts",
|
||||
"vitest.config.ts"
|
||||
]
|
||||
}
|
||||
159
types/openclaw-sdk.d.ts
vendored
Normal file
159
types/openclaw-sdk.d.ts
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
declare module "@openclaw/sdk" {
|
||||
export type GatewayRequestOptions = {
|
||||
expectFinal?: boolean;
|
||||
timeoutMs?: number | null;
|
||||
};
|
||||
|
||||
export type GatewayEvent = {
|
||||
event: string;
|
||||
payload?: unknown;
|
||||
seq?: number;
|
||||
stateVersion?: unknown;
|
||||
};
|
||||
|
||||
export type OpenClawTransport = {
|
||||
request<T = unknown>(
|
||||
method: string,
|
||||
params?: unknown,
|
||||
options?: GatewayRequestOptions,
|
||||
): Promise<T>;
|
||||
events(filter?: (event: GatewayEvent) => boolean): AsyncIterable<GatewayEvent>;
|
||||
close?(): Promise<void> | void;
|
||||
};
|
||||
|
||||
export type OpenClawOptions = {
|
||||
gateway?: "auto" | (string & {});
|
||||
url?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
requestTimeoutMs?: number;
|
||||
transport?: OpenClawTransport;
|
||||
};
|
||||
|
||||
export type RunStatus = "accepted" | "completed" | "failed" | "cancelled" | "timed_out";
|
||||
export type RunTimestamp = string | number;
|
||||
|
||||
export type RunResult = {
|
||||
runId: string;
|
||||
status: RunStatus;
|
||||
sessionId?: string;
|
||||
sessionKey?: string;
|
||||
taskId?: string;
|
||||
startedAt?: RunTimestamp;
|
||||
endedAt?: RunTimestamp;
|
||||
error?: { code?: string; message: string; details?: unknown };
|
||||
raw?: unknown;
|
||||
};
|
||||
|
||||
export type OpenClawEventType =
|
||||
| "run.created"
|
||||
| "run.queued"
|
||||
| "run.started"
|
||||
| "run.completed"
|
||||
| "run.failed"
|
||||
| "run.cancelled"
|
||||
| "run.timed_out"
|
||||
| "assistant.delta"
|
||||
| "assistant.message"
|
||||
| "thinking.delta"
|
||||
| "tool.call.started"
|
||||
| "tool.call.delta"
|
||||
| "tool.call.completed"
|
||||
| "tool.call.failed"
|
||||
| "approval.requested"
|
||||
| "approval.resolved"
|
||||
| "question.requested"
|
||||
| "question.answered"
|
||||
| "artifact.created"
|
||||
| "artifact.updated"
|
||||
| "session.created"
|
||||
| "session.updated"
|
||||
| "session.compacted"
|
||||
| "task.updated"
|
||||
| "git.branch"
|
||||
| "git.diff"
|
||||
| "git.pr"
|
||||
| "raw";
|
||||
|
||||
export type OpenClawEvent<TData = unknown> = {
|
||||
version: 1;
|
||||
id: string;
|
||||
ts: number;
|
||||
type: OpenClawEventType;
|
||||
runId?: string;
|
||||
sessionId?: string;
|
||||
sessionKey?: string;
|
||||
taskId?: string;
|
||||
agentId?: string;
|
||||
data: TData;
|
||||
raw?: GatewayEvent;
|
||||
};
|
||||
|
||||
export type AgentRunParams = {
|
||||
input: string;
|
||||
agentId?: string;
|
||||
model?: string;
|
||||
sessionId?: string;
|
||||
sessionKey?: string;
|
||||
deliver?: boolean;
|
||||
timeoutMs?: number;
|
||||
label?: string;
|
||||
idempotencyKey?: string;
|
||||
};
|
||||
|
||||
export type SessionCreateParams = {
|
||||
key?: string;
|
||||
agentId?: string;
|
||||
label?: string;
|
||||
model?: string;
|
||||
parentSessionKey?: string;
|
||||
task?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export type SessionSendParams = {
|
||||
key?: string;
|
||||
message: string;
|
||||
thinking?: string;
|
||||
attachments?: unknown[];
|
||||
timeoutMs?: number;
|
||||
idempotencyKey?: string;
|
||||
};
|
||||
|
||||
export class Run {
|
||||
readonly id: string;
|
||||
events(filter?: (event: OpenClawEvent) => boolean): AsyncIterable<OpenClawEvent>;
|
||||
wait(options?: { timeoutMs?: number }): Promise<RunResult>;
|
||||
cancel(): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class Agent {
|
||||
readonly id: string;
|
||||
run(input: string | Omit<AgentRunParams, "agentId">): Promise<Run>;
|
||||
}
|
||||
|
||||
export class Session {
|
||||
readonly key: string;
|
||||
send(input: string | Omit<SessionSendParams, "key">): Promise<Run>;
|
||||
abort(runId?: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export class OpenClaw {
|
||||
readonly agents: {
|
||||
get(id: string): Promise<Agent>;
|
||||
};
|
||||
readonly runs: {
|
||||
create(params: AgentRunParams): Promise<Run>;
|
||||
wait(runId: string, options?: { timeoutMs?: number }): Promise<RunResult>;
|
||||
cancel(runId: string, sessionKey?: string): Promise<unknown>;
|
||||
};
|
||||
readonly sessions: {
|
||||
create(params?: SessionCreateParams): Promise<Session>;
|
||||
};
|
||||
readonly models: {
|
||||
status(params?: unknown): Promise<unknown>;
|
||||
};
|
||||
constructor(options?: OpenClawOptions);
|
||||
close(): Promise<void>;
|
||||
}
|
||||
}
|
||||
13
vitest.config.ts
Normal file
13
vitest.config.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@openclaw/sdk": fileURLToPath(new URL("./test/shims/openclaw-sdk.ts", import.meta.url)),
|
||||
},
|
||||
},
|
||||
test: {
|
||||
include: ["test/**/*.test.ts"],
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user