@zuora/agent-toolkit 0.0.1-alpha.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.
package/README.md ADDED
@@ -0,0 +1,298 @@
1
+ # @zuora/agent-toolkit
2
+
3
+ > **Alpha release** — APIs may change before the 1.0 stable release.
4
+
5
+ Zuora billing operations as callable tools for AI agent frameworks.
6
+
7
+ Built on the [Zuora MCP server](https://developer.zuora.com). Tool inventory is discovered at runtime from your tenant's MCP endpoint — it is not hardcoded in this package.
8
+
9
+ ## Adapter status
10
+
11
+ | Subpath | Factory | Status |
12
+ |---|---|---|
13
+ | `@zuora/agent-toolkit/openai` | `createZuoraOpenAIAgentToolkit` | ✅ Implemented |
14
+ | `@zuora/agent-toolkit/openai-agents` | `createZuoraOpenAIAgentsToolkit` | ✅ Implemented |
15
+ | `@zuora/agent-toolkit/langchain` | — | 🚧 Coming soon |
16
+ | `@zuora/agent-toolkit/vercel-ai` | — | 🚧 Coming soon |
17
+ | `@zuora/agent-toolkit/mcp` | — | 🚧 Coming soon |
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @zuora/agent-toolkit
23
+ ```
24
+
25
+ Framework packages are peer dependencies — install only what you use:
26
+
27
+ ```bash
28
+ # OpenAI Chat Completions
29
+ npm install openai
30
+
31
+ # OpenAI Agents SDK
32
+ npm install @openai/agents
33
+ ```
34
+
35
+ ## Credentials
36
+
37
+ The toolkit authenticates against Zuora using OAuth 2.0 client credentials. You need:
38
+
39
+ - `ZUORA_BASE_URL` — your Zuora tenant REST API base URL (e.g. `https://rest.apisandbox.zuora.com`)
40
+ - `ZUORA_CLIENT_ID` — OAuth client ID
41
+ - `ZUORA_CLIENT_SECRET` — OAuth client secret
42
+
43
+ **Use a client scoped to the minimum permissions your agent needs.** Never use admin credentials.
44
+
45
+ ## Usage
46
+
47
+ ### OpenAI Agents SDK
48
+
49
+ ```ts
50
+ import { Agent, run } from "@openai/agents";
51
+ import { createZuoraOpenAIAgentsToolkit } from "@zuora/agent-toolkit/openai-agents";
52
+
53
+ const toolkit = await createZuoraOpenAIAgentsToolkit({
54
+ env: true,
55
+ configuration: { mode: "readOnly", bundles: { query: true } },
56
+ });
57
+
58
+ const tools = await toolkit.getTools({ bundles: ["query"] });
59
+ const agent = new Agent({ name: "Billing Agent", tools });
60
+ const result = await run(agent, "How many active billing accounts do we have?");
61
+
62
+ await toolkit.close();
63
+ ```
64
+
65
+ If you already have a Chat Completions toolkit, you can bridge it with `buildAgentsTools`:
66
+
67
+ ```ts
68
+ import { buildAgentsTools } from "@zuora/agent-toolkit/openai-agents";
69
+ import { createZuoraOpenAIAgentToolkit } from "@zuora/agent-toolkit/openai";
70
+ import { tool } from "@openai/agents";
71
+
72
+ const chatToolkit = await createZuoraOpenAIAgentToolkit({ env: true });
73
+ const tools = await buildAgentsTools(tool, chatToolkit, { bundles: ["query"] });
74
+ ```
75
+
76
+ ### OpenAI Chat Completions
77
+
78
+ ```ts
79
+ import OpenAI from "openai";
80
+ import {
81
+ createZuoraOpenAIAgentToolkit,
82
+ handleToolCalls,
83
+ } from "@zuora/agent-toolkit/openai";
84
+
85
+ const toolkit = await createZuoraOpenAIAgentToolkit({
86
+ env: true,
87
+ configuration: { mode: "readOnly" },
88
+ });
89
+
90
+ const client = new OpenAI();
91
+ const tools = await toolkit.getTools({ bundles: ["accounts", "query"] });
92
+ const messages: OpenAI.ChatCompletionMessageParam[] = [
93
+ { role: "user", content: "List 3 active accounts" },
94
+ ];
95
+
96
+ for (let step = 0; step < 20; step++) {
97
+ const response = await client.chat.completions.create({
98
+ model: "gpt-4o",
99
+ messages,
100
+ tools,
101
+ });
102
+
103
+ const choice = response.choices[0];
104
+ if (!choice) break;
105
+ messages.push(choice.message);
106
+
107
+ if (choice.finish_reason === "tool_calls") {
108
+ const { messages: toolMessages } = await handleToolCalls({ toolkit, response });
109
+ messages.push(...toolMessages);
110
+ continue;
111
+ }
112
+
113
+ console.log(choice.message.content);
114
+ break;
115
+ }
116
+
117
+ await toolkit.close();
118
+ ```
119
+
120
+ ## Configuration
121
+
122
+ ### Safety modes
123
+
124
+ ```ts
125
+ configuration: {
126
+ mode: "readOnly", // only read operations (default)
127
+ // or
128
+ mode: "writeEnabled", // read + write operations
129
+ }
130
+ ```
131
+
132
+ ### Tool bundles
133
+
134
+ Select groups of tools by Zuora domain:
135
+
136
+ ```ts
137
+ configuration: {
138
+ bundles: {
139
+ accounts: true,
140
+ subscriptions: true,
141
+ query: true,
142
+ },
143
+ }
144
+ ```
145
+
146
+ Available bundles: `accounts`, `catalog`, `subscriptions`, `orders`, `billing`, `payments`, `usage`, `revenue`, `finance`, `workflows`, `query`, `admin`, `files`.
147
+
148
+ You can also filter at call time via `getTools({ bundles, include, exclude, mode })`.
149
+
150
+ ### Exclude / include specific tools
151
+
152
+ ```ts
153
+ configuration: {
154
+ bundles: {
155
+ billing: {
156
+ exclude: ["create_invoice", "delete_invoice"],
157
+ // or include: ["list_invoices"]
158
+ },
159
+ },
160
+ }
161
+ ```
162
+
163
+ ### Split operations
164
+
165
+ Multi-operation MCP tools are split into per-operation tools by default (`splitOperations: true`), producing names like `manage_data_queries__submit_query`. Set `splitOperations: false` to keep merged tools.
166
+
167
+ ### Approval policy
168
+
169
+ Gate write / high-risk operations before they reach Zuora. `approvalPolicy` is a **top-level** config field (not inside `configuration`):
170
+
171
+ ```ts
172
+ import { ApprovalPolicyPreset } from "@zuora/agent-toolkit";
173
+
174
+ const toolkit = await createZuoraOpenAIAgentToolkit({
175
+ env: true,
176
+ configuration: { mode: "writeEnabled" },
177
+ approvalPolicy: ApprovalPolicyPreset.HighRisk,
178
+ // or a custom handler:
179
+ // approvalPolicy: async (request) => {
180
+ // if (request.tool.meta.riskLevel === "high") return "deny";
181
+ // return "not_required";
182
+ // },
183
+ });
184
+ ```
185
+
186
+ Presets: `Off`, `AllWrites`, `HighRisk`, `ProductionWrites`, `ProductionHighRisk`.
187
+
188
+ ### Context defaults
189
+
190
+ Pass optional string context keys (for example tenant or entity identifiers) via `configuration.context`. Connection-level `entityIds` / `orgIds` arrays live on `connection` instead:
191
+
192
+ ```ts
193
+ configuration: {
194
+ context: {
195
+ tenantId: "…",
196
+ },
197
+ },
198
+ connection: {
199
+ entityIds: ["entity-1"],
200
+ orgIds: ["org-1"],
201
+ },
202
+ ```
203
+
204
+ ### Observability
205
+
206
+ ```ts
207
+ observability: {
208
+ onEvent: (event) => { /* tool.called, tool.completed, approval.*, … */ },
209
+ logger: { info: console.info, warn: console.warn, error: console.error },
210
+ includePayloads: false, // default; set true to include sanitized I/O
211
+ }
212
+ ```
213
+
214
+ ## Environment variable config
215
+
216
+ Pass `env: true` to read credentials from the environment automatically:
217
+
218
+ ```ts
219
+ const toolkit = await createZuoraOpenAIAgentToolkit({
220
+ env: true,
221
+ configuration: { mode: "readOnly" },
222
+ });
223
+ ```
224
+
225
+ | Variable | Purpose |
226
+ |---|---|
227
+ | `ZUORA_BASE_URL` or `ZUORA_MCP_URL` | Endpoint (required) |
228
+ | `ZUORA_CLIENT_ID` / `ZUORA_CLIENT_SECRET` | OAuth credentials |
229
+ | `ZUORA_TOOLKIT_MODE` | `readOnly` (default) or `writeEnabled` |
230
+
231
+ You can still pass `baseUrl` + `auth` explicitly instead of `env: true`.
232
+
233
+ ## Lifecycle
234
+
235
+ Always call `close()` when done:
236
+
237
+ ```ts
238
+ const toolkit = await createZuoraOpenAIAgentToolkit({ env: true });
239
+ try {
240
+ // use toolkit
241
+ } finally {
242
+ await toolkit.close();
243
+ }
244
+ ```
245
+
246
+ ## Discovery and execution
247
+
248
+ ```ts
249
+ const tools = await toolkit.listTools(); // ZuoraToolDefinition[]
250
+ const def = await toolkit.describeTool("get_account");
251
+ const bundles = await toolkit.listBundles();
252
+ const bundle = await toolkit.describeBundle("subscriptions");
253
+
254
+ const result = await toolkit.callTool("get_account", { accountKey: "…" });
255
+ // For async tools: await toolkit.waitForJob(toolName, asyncResult, options?)
256
+ ```
257
+
258
+ ## Native query / file tools
259
+
260
+ When the `query` bundle is enabled, the SDK may expose local helpers such as `download_query_result` and `read_downloaded_file` so agents can fetch and preview Data Query artifacts. Artifacts default to `~/.zuora-agent-artifacts`.
261
+
262
+ ## Errors
263
+
264
+ All SDK errors are `ZuoraAgentToolkitError` with a typed `code`:
265
+
266
+ | Code | Meaning |
267
+ |---|---|
268
+ | `OAUTH_TOKEN_ERROR` | Authentication or token refresh failed |
269
+ | `TOOL_EXECUTION_ERROR` | MCP tool call failed |
270
+ | `TOOL_NOT_FOUND` | Unknown tool name |
271
+ | `BUNDLE_NOT_FOUND` | Unknown bundle name |
272
+ | `APPROVAL_REJECTED` | Approval policy rejected the operation |
273
+ | `MALFORMED_METADATA` | Server returned incomplete or invalid tool metadata |
274
+ | `ASYNC_JOB_TIMEOUT` | Async job polling timed out |
275
+
276
+ ```ts
277
+ import { ZuoraAgentToolkitError } from "@zuora/agent-toolkit";
278
+
279
+ try {
280
+ await handleToolCalls({ toolkit, response });
281
+ } catch (err) {
282
+ if (err instanceof ZuoraAgentToolkitError && err.code === "APPROVAL_REJECTED") {
283
+ // handle rejection
284
+ }
285
+ }
286
+ ```
287
+
288
+ ## Examples
289
+
290
+ Working examples are available in the source repository:
291
+
292
+ | Example | Description |
293
+ |---|---|
294
+ | `examples/openai/index.ts` | Minimal Chat Completions agent loop |
295
+ | `examples/openai/advanced.ts` | Observability, hooks, async jobs, discovery |
296
+ | `examples/openai/hitl.ts` | Human-in-the-loop via `shouldExecute` |
297
+ | `examples/openai-agents/index.ts` | OpenAI Agents SDK agent |
298
+ | `examples/openai-agents-cli/` | Interactive CLI agent + planner |