Skip to content
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ npm i @digitalocean/dots

https://digitaloceandots.readthedocs.io/en/latest/

#### **Action Gateway**

Use `@digitalocean/dots/action_gateway` for session-bound tools with Chat
Completions, Messages, and Responses. Toolbelt CRUD is generated from the
public DigitalOcean OpenAPI specification, with a `createToolbelt` convenience
method on `ActionGatewayClient`.

See the [Action Gateway guide](docs/action-gateway.md) and
Comment thread
SSharma-10 marked this conversation as resolved.
Outdated
[TypeScript examples](examples/action-gateway/).

## **Basic Usage**
> A quick guide to getting started with client
#### Authenticating
Expand Down
43 changes: 43 additions & 0 deletions docs/action-gateway.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Action Gateway

The TypeScript SDK uses a session-first Action Gateway flow. Create a session
on the DigitalOcean public API, then discover or invoke tools through
`actions.do-ai.run` with the session and actor headers managed by the SDK.

```ts
import { ActionGatewayClient } from "@digitalocean/dots/action_gateway";

const gateway = new ActionGatewayClient({
apiKey: process.env.DIGITALOCEAN_TOKEN!,
});
const session = await gateway.session.create({ actorId: "end-user-123" });
```

Session creation sends `actor_id`, `name`, and `policy_json` to
`POST /v2/action-gateway/sessions`. Gateway REST requests use the bare session
UUID in `X-Session-Id` and the actor in `X-Actor-Id`.

## Toolbelts

Toolbelts are public DigitalOcean API resources, so CRUD operations are
generated from the public OpenAPI specification under `gateway.toolbelts`.
`createToolbelt` is the Action Gateway convenience wrapper:

```ts
const toolbelt = await gateway.createToolbelt({
name: "search-toolbelt",
tools: ["exa_web_search", "exa_web_fetch"],
});

const session = await gateway.session.create({
actorId: "end-user-123",
permissions: {
defaultAction: "ask",
rules: [{ tool: `toolbelt:${toolbelt.ref}`, action: "allow" }],
},
});
```

See `examples/action-gateway/` for Chat Completions, Messages, Responses,
direct tool and code execution, asynchronous usage, toolbelt creation, and
toolbelt policy examples.
13 changes: 13 additions & 0 deletions examples/action-gateway/async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ActionGatewayClient } from "../../src/action-gateway/index.js";

const gateway = new ActionGatewayClient({
apiKey: process.env.DIGITALOCEAN_TOKEN!,
});
const session = await gateway.session.create({ actorId: "end-user-123" });

const [tools, catalog] = await Promise.all([
session.tools(),
session.toolsOperations.list({ includeAll: true }),
]);

console.log(`Loaded ${tools.length} model tools and ${catalog.length} catalog tools.`);
27 changes: 27 additions & 0 deletions examples/action-gateway/chat-completions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Client } from "../../src/inference-gen/inference.js";
import { ActionGatewayClient } from "../../src/action-gateway/index.js";

const apiKey = process.env.DIGITALOCEAN_TOKEN!;
const inference = new Client({ apiKey });
const gateway = new ActionGatewayClient({ apiKey });
const session = await gateway.session.create({ actorId: "end-user-123" });

const messages: Record<string, unknown>[] = [{
role: "user",
content: "Find the latest DigitalOcean news and summarize it.",
}];

while (true) {
const response = await inference.chat.completions.create({
model: "llama3.3-70b-instruct",
messages,
tools: await session.tools(),
});
const message = response.choices[0].message;
messages.push(message);
if (!message.tool_calls?.length) {
console.log(message.content);
break;
}
messages.push(...await session.handleToolCalls(response));
}
24 changes: 24 additions & 0 deletions examples/action-gateway/create-toolbelt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ActionGatewayClient } from "../../src/action-gateway/index.js";

const gateway = new ActionGatewayClient({
apiKey: process.env.DIGITALOCEAN_TOKEN!,
});

const toolbelt = await gateway.createToolbelt({
name: "search-toolbelt",
tools: ["exa_web_search", "exa_web_fetch"],
});

console.log(toolbelt.ref); // search-toolbelt@1

// The base CRUD surface is generated from the public OpenAPI specification.
await gateway.toolbelts.get({ queryParameters: { status: "active" } });
await gateway.toolbelts.byName("search-toolbelt").get({
queryParameters: { version: "1" },
});
await gateway.toolbelts.byName("search-toolbelt").tools.add.post({
tools: ["jira_create_issue"],
});
await gateway.toolbelts.byName("search-toolbelt").tools.remove.post({
tools: ["exa_web_fetch"],
});
15 changes: 15 additions & 0 deletions examples/action-gateway/direct-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ActionGatewayClient } from "../../src/action-gateway/index.js";

const gateway = new ActionGatewayClient({
apiKey: process.env.DIGITALOCEAN_TOKEN!,
});
const session = await gateway.session.create({ actorId: "end-user-123" });

const search = await session.toolsOperations.search("search the web for DigitalOcean news");
const result = await session.toolsOperations.invokeOne("exa_web_search", {
query: "DigitalOcean news",
max_results: 5,
});
const code = await session.code.execute("print(sum(range(10)))");

console.dir({ search, result, code }, { depth: null });
22 changes: 22 additions & 0 deletions examples/action-gateway/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Client } from "../../src/inference-gen/inference.js";
import {
ActionGatewayClient,
MessagesProvider,
} from "../../src/action-gateway/index.js";

const apiKey = process.env.DIGITALOCEAN_TOKEN!;
const inference = new Client({ apiKey });
const gateway = new ActionGatewayClient({
apiKey,
provider: new MessagesProvider(),
});
const session = await gateway.session.create({ actorId: "end-user-123" });

const response = await inference.messages.create({
model: "anthropic-claude-sonnet-4",
max_tokens: 1024,
messages: [{ role: "user", content: "Find the latest DigitalOcean news." }],
tools: await session.tools(),
});

console.dir(await session.handleToolCalls(response), { depth: null });
21 changes: 21 additions & 0 deletions examples/action-gateway/responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Client } from "../../src/inference-gen/inference.js";
import {
ActionGatewayClient,
ResponsesProvider,
} from "../../src/action-gateway/index.js";

const apiKey = process.env.DIGITALOCEAN_TOKEN!;
const inference = new Client({ apiKey });
const gateway = new ActionGatewayClient({
apiKey,
provider: new ResponsesProvider(),
});
const session = await gateway.session.create({ actorId: "end-user-123" });

const response = await inference.responses.create({
model: "openai-gpt-4o",
input: "What DigitalOcean Droplet sizes are available in NYC3?",
tools: await session.tools(),
});

console.dir(await session.handleToolCalls(response), { depth: null });
19 changes: 19 additions & 0 deletions examples/action-gateway/toolbelt-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ActionGatewayClient } from "../../src/action-gateway/index.js";

const gateway = new ActionGatewayClient({
apiKey: process.env.DIGITALOCEAN_TOKEN!,
});
const toolbelt = await gateway.createToolbelt({
name: "search-toolbelt",
tools: ["exa_web_search", "exa_web_fetch"],
});

const session = await gateway.session.create({
actorId: "end-user-123",
permissions: {
defaultAction: "ask",
rules: [{ tool: `toolbelt:${toolbelt.ref}`, action: "allow" }],
},
});

console.log(session.url);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"main": "index.js",
"exports": {
".": "./index.js",
"./action_gateway": "./src/action-gateway/index.js",
"./inference": "./src/inference-gen/inference.js",
"./package.json": "./package.json",
"./*": "./*"
Expand Down
Loading
Loading