@zuora/agent-toolkit 0.0.1-alpha.0 → 0.1.0-alpha.1

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 CHANGED
@@ -6,18 +6,51 @@ Zuora billing operations as callable tools for AI agent frameworks.
6
6
 
7
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
8
 
9
+ ## Quick start
10
+
11
+ ```ts
12
+ import { Agent, run } from "@openai/agents";
13
+ import { createZuoraOpenAIAgentsToolkit } from "@zuora/agent-toolkit/openai-agents";
14
+
15
+ const toolkit = await createZuoraOpenAIAgentsToolkit({ env: true });
16
+ try {
17
+ const agent = new Agent({ name: "Billing Agent", tools: await toolkit.getTools() });
18
+ const result = await run(agent, "How many active accounts do we have?");
19
+ console.log(result.finalOutput);
20
+ } finally {
21
+ await toolkit.close();
22
+ }
23
+ ```
24
+
25
+ Set `ZUORA_BASE_URL`, `ZUORA_CLIENT_ID`, and `ZUORA_CLIENT_SECRET` in your environment. See [Credentials](#credentials) for other auth options.
26
+
9
27
  ## Adapter status
10
28
 
11
29
  | Subpath | Factory | Status |
12
30
  |---|---|---|
13
31
  | `@zuora/agent-toolkit/openai` | `createZuoraOpenAIAgentToolkit` | ✅ Implemented |
14
32
  | `@zuora/agent-toolkit/openai-agents` | `createZuoraOpenAIAgentsToolkit` | ✅ Implemented |
15
- | `@zuora/agent-toolkit/langchain` | — | 🚧 Coming soon |
16
- | `@zuora/agent-toolkit/vercel-ai` | — | 🚧 Coming soon |
33
+ | `@zuora/agent-toolkit/langchain` | `createZuoraLangChainToolkit` | ✅ Implemented |
34
+ | `@zuora/agent-toolkit/vercel-ai` | `createZuoraVercelAIToolkit` | ✅ Implemented |
17
35
  | `@zuora/agent-toolkit/mcp` | — | 🚧 Coming soon |
18
36
 
37
+ ## Changelog
38
+
39
+ ### [0.1.0-alpha.1]
40
+
41
+ - Fixed HITL approval policy handling with correct `request.environment` usage
42
+ - Improved tool name resolution
43
+ - Removed deprecated `CLI_AGENT_SPLIT_OPERATIONS` references
44
+ - Test and documentation updates
45
+
46
+ ### [0.1.0-alpha.0]
47
+
48
+ Initial alpha release.
49
+
19
50
  ## Installation
20
51
 
52
+ Requires **Node.js 18** or later.
53
+
21
54
  ```bash
22
55
  npm install @zuora/agent-toolkit
23
56
  ```
@@ -25,52 +58,165 @@ npm install @zuora/agent-toolkit
25
58
  Framework packages are peer dependencies — install only what you use:
26
59
 
27
60
  ```bash
28
- # OpenAI Chat Completions
61
+ # OpenAI Chat Completions (requires openai >=4)
29
62
  npm install openai
30
63
 
31
- # OpenAI Agents SDK
64
+ # OpenAI Agents SDK (requires @openai/agents >=0.13)
32
65
  npm install @openai/agents
66
+
67
+ # LangChain (requires @langchain/core >=1.2.1)
68
+ npm install @langchain/core @langchain/langgraph @langchain/openai
69
+
70
+ # Vercel AI SDK (requires ai >=7 — v7 is a breaking rewrite from v6)
71
+ npm install ai @ai-sdk/openai
33
72
  ```
34
73
 
74
+ > **Version notes**
75
+ > - **Vercel AI SDK** — `ai >=7` is required. v7 introduced `ToolSet`, `isStepCount`, and `jsonSchema()`, which this adapter depends on. v6 is **not** compatible.
76
+ > - **LangChain** — `@langchain/core >=1.2.1` is required for `DynamicStructuredTool` and JSON Schema parameter support.
77
+ > - `@langchain/langgraph` and `@langchain/openai` are not direct peer dependencies but are needed to run the agent (see examples).
78
+
35
79
  ## Credentials
36
80
 
37
- The toolkit authenticates against Zuora using OAuth 2.0 client credentials. You need:
81
+ Pass `env: true` to read credentials from the environment automatically:
82
+
83
+ | Variable | Purpose |
84
+ |---|---|
85
+ | `ZUORA_BASE_URL` | Tenant REST API endpoint (required) |
86
+ | `ZUORA_CLIENT_ID` / `ZUORA_CLIENT_SECRET` | OAuth credentials (required) |
87
+ | `ZUORA_TOOLKIT_MODE` | `readOnly` (default) or `writeEnabled` |
38
88
 
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
89
+ `ZUORA_TOOLKIT_MODE` is only read when `env: true` is set, and is overridden by an explicit `configuration.mode`.
90
+
91
+ You can also pass credentials explicitly:
92
+
93
+ ```ts
94
+ import { SafetyMode } from "@zuora/agent-toolkit";
95
+
96
+ const toolkit = await createZuoraOpenAIAgentToolkit({
97
+ baseUrl: process.env.ZUORA_BASE_URL!,
98
+ auth: {
99
+ clientId: process.env.ZUORA_CLIENT_ID!,
100
+ clientSecret: process.env.ZUORA_CLIENT_SECRET!,
101
+ },
102
+ configuration: { mode: SafetyMode.ReadOnly },
103
+ });
104
+ ```
105
+
106
+ `env: true` reads only credentials and base URL from the environment — all other config fields (`configuration`, `approvalPolicy`, `observability`, etc.) can still be set explicitly alongside it.
42
107
 
43
108
  **Use a client scoped to the minimum permissions your agent needs.** Never use admin credentials.
44
109
 
45
110
  ## Usage
46
111
 
112
+ > **Which OpenAI adapter should I use?**
113
+ > - Use `@zuora/agent-toolkit/openai-agents` + `createZuoraOpenAIAgentsToolkit` if you are building with the **`@openai/agents` library** (the higher-level agent runtime).
114
+ > - Use `@zuora/agent-toolkit/openai` + `createZuoraOpenAIAgentToolkit` if you are using the **raw `openai` Chat Completions API** and managing the tool-call loop yourself.
115
+
47
116
  ### OpenAI Agents SDK
48
117
 
49
118
  ```ts
50
119
  import { Agent, run } from "@openai/agents";
120
+ import { SafetyMode } from "@zuora/agent-toolkit";
51
121
  import { createZuoraOpenAIAgentsToolkit } from "@zuora/agent-toolkit/openai-agents";
52
122
 
53
123
  const toolkit = await createZuoraOpenAIAgentsToolkit({
54
124
  env: true,
55
- configuration: { mode: "readOnly", bundles: { query: true } },
125
+ configuration: { mode: SafetyMode.ReadOnly },
56
126
  });
57
127
 
58
- const tools = await toolkit.getTools({ bundles: ["query"] });
128
+ // Scope to specific bundles and cap tool count — improves model accuracy
129
+ const tools = (await toolkit.getTools({ bundles: ["accounts", "query"] })).slice(0, 50);
59
130
  const agent = new Agent({ name: "Billing Agent", tools });
60
131
  const result = await run(agent, "How many active billing accounts do we have?");
61
132
 
62
133
  await toolkit.close();
63
134
  ```
64
135
 
65
- If you already have a Chat Completions toolkit, you can bridge it with `buildAgentsTools`:
136
+ If you already have a `ZuoraOpenAIAgentToolkit` instance (for example, shared across multiple agents or reused from an existing integration), you can convert its tools to Agents SDK format without opening a second MCP connection:
66
137
 
67
138
  ```ts
68
139
  import { buildAgentsTools } from "@zuora/agent-toolkit/openai-agents";
69
- import { createZuoraOpenAIAgentToolkit } from "@zuora/agent-toolkit/openai";
70
140
  import { tool } from "@openai/agents";
71
141
 
72
- const chatToolkit = await createZuoraOpenAIAgentToolkit({ env: true });
142
+ // `tool` is passed in rather than imported directly so @openai/agents stays
143
+ // an optional peer dependency and the module loads without it installed.
73
144
  const tools = await buildAgentsTools(tool, chatToolkit, { bundles: ["query"] });
145
+ const agent = new Agent({ name: "Billing Agent", tools });
146
+ await run(agent, "...");
147
+ // chatToolkit must stay open while tools are in use — close it when done
148
+ ```
149
+
150
+ > If you are starting fresh (no existing toolkit), prefer `createZuoraOpenAIAgentsToolkit` directly — it handles the connection internally.
151
+
152
+ ### LangChain
153
+
154
+ ```ts
155
+ import { ChatOpenAI } from "@langchain/openai";
156
+ import { createReactAgent } from "@langchain/langgraph/prebuilt";
157
+ import { SafetyMode } from "@zuora/agent-toolkit";
158
+ import { createZuoraLangChainToolkit } from "@zuora/agent-toolkit/langchain";
159
+
160
+ const toolkit = await createZuoraLangChainToolkit({
161
+ env: true,
162
+ configuration: { mode: SafetyMode.ReadOnly },
163
+ });
164
+
165
+ // Scope to specific bundles and cap tool count — improves model accuracy
166
+ const tools = (await toolkit.getTools({ bundles: ["accounts", "query"] })).slice(0, 50);
167
+
168
+ const agent = createReactAgent({
169
+ llm: new ChatOpenAI({ model: "gpt-4o" }),
170
+ tools,
171
+ });
172
+
173
+ // LangChain owns the tool-call loop — no manual dispatch needed
174
+ const result = await agent.invoke({
175
+ messages: [{ role: "user", content: "List 3 active accounts" }],
176
+ });
177
+
178
+ const answer = result.messages.at(-1)?.content;
179
+ console.log(typeof answer === "string" ? answer : JSON.stringify(answer));
180
+
181
+ await toolkit.close();
182
+ ```
183
+
184
+ ### Vercel AI SDK
185
+
186
+ > `getToolSet()` returns a `ToolSet` (`Record<string, Tool>`) — the format `streamText` and `generateText` require. All other adapters use `getTools()` which returns an array.
187
+
188
+ ```ts
189
+ import { openai } from "@ai-sdk/openai";
190
+ import { streamText, isStepCount } from "ai";
191
+ import { SafetyMode } from "@zuora/agent-toolkit";
192
+ import { createZuoraVercelAIToolkit } from "@zuora/agent-toolkit/vercel-ai";
193
+
194
+ const toolkit = await createZuoraVercelAIToolkit({
195
+ env: true,
196
+ configuration: { mode: SafetyMode.ReadOnly },
197
+ });
198
+
199
+ // `getToolSet()` returns a ToolSet (`Record<string, Tool>`) rather than an array,
200
+ // so `.slice()` is not available — the second argument is the only way to cap tool count.
201
+ // Omit it to return all matching tools.
202
+ const tools = await toolkit.getToolSet(
203
+ { bundles: ["accounts", "query"] },
204
+ 50,
205
+ );
206
+
207
+ const { textStream } = streamText({
208
+ model: openai("gpt-4o"),
209
+ tools,
210
+ stopWhen: isStepCount(20), // SDK drives the tool-call loop — no manual dispatch needed
211
+ messages: [{ role: "user", content: "List 3 active accounts" }],
212
+ });
213
+
214
+ for await (const chunk of textStream) {
215
+ process.stdout.write(chunk);
216
+ }
217
+ console.log();
218
+
219
+ await toolkit.close();
74
220
  ```
75
221
 
76
222
  ### OpenAI Chat Completions
@@ -81,10 +227,11 @@ import {
81
227
  createZuoraOpenAIAgentToolkit,
82
228
  handleToolCalls,
83
229
  } from "@zuora/agent-toolkit/openai";
230
+ import { SafetyMode } from "@zuora/agent-toolkit";
84
231
 
85
232
  const toolkit = await createZuoraOpenAIAgentToolkit({
86
233
  env: true,
87
- configuration: { mode: "readOnly" },
234
+ configuration: { mode: SafetyMode.ReadOnly },
88
235
  });
89
236
 
90
237
  const client = new OpenAI();
@@ -105,6 +252,8 @@ for (let step = 0; step < 20; step++) {
105
252
  messages.push(choice.message);
106
253
 
107
254
  if (choice.finish_reason === "tool_calls") {
255
+ // handleToolCalls returns { messages, abortTurn, requireToolOnNextStep, followUpToolNames }.
256
+ // For basic loops, only `messages` is needed. See examples/openai/hitl.ts for advanced usage.
108
257
  const { messages: toolMessages } = await handleToolCalls({ toolkit, response });
109
258
  messages.push(...toolMessages);
110
259
  continue;
@@ -119,13 +268,33 @@ await toolkit.close();
119
268
 
120
269
  ## Configuration
121
270
 
271
+ Top-level config shape — all fields except credentials are optional:
272
+
273
+ ```ts
274
+ {
275
+ env: true, // reads ZUORA_BASE_URL, ZUORA_CLIENT_ID, ZUORA_CLIENT_SECRET
276
+ configuration: { // tool filtering, safety, and routing
277
+ mode: SafetyMode.ReadOnly,
278
+ bundles: { accounts: true },
279
+ context: { // scopes operations to the specified entity / org
280
+ entityIds: "entity-1", // → Zuora-Entity-Ids header
281
+ orgIds: "org-1", // → Zuora-Org-Ids header
282
+ },
283
+ },
284
+ // approvalPolicy: … // see "Approval policy" section below
285
+ observability: { // optional: events and logging
286
+ onEvent: (event) => { … },
287
+ },
288
+ }
289
+ ```
290
+
122
291
  ### Safety modes
123
292
 
124
293
  ```ts
125
294
  configuration: {
126
- mode: "readOnly", // only read operations (default)
295
+ mode: SafetyMode.ReadOnly, // only read operations (default)
127
296
  // or
128
- mode: "writeEnabled", // read + write operations
297
+ mode: SafetyMode.WriteEnabled, // read + write operations
129
298
  }
130
299
  ```
131
300
 
@@ -143,96 +312,125 @@ configuration: {
143
312
  }
144
313
  ```
145
314
 
146
- Available bundles: `accounts`, `catalog`, `subscriptions`, `orders`, `billing`, `payments`, `usage`, `revenue`, `finance`, `workflows`, `query`, `admin`, `files`.
315
+ Available bundles: `accounts`, `catalog`, `subscriptions`, `orders`, `billing`, `payments`, `usage`, `revenue`, `finance`, `workflows`, `query`, `admin`, `files`. Which bundles are available depends on your Zuora tenant's MCP endpoint.
316
+
317
+ You can also filter at call time via `getTools()`. Note that `configuration.bundles` uses an object (`{ accounts: true }`) and is fixed at toolkit creation time; `getTools({ bundles: [...] })` uses an array and filters the already-loaded catalog at call time.
318
+
319
+ ```ts
320
+ // Filter by bundle
321
+ toolkit.getTools({ bundles: ["accounts", "query"] })
322
+
323
+ // Include or exclude specific tools by exact name
324
+ toolkit.getTools({ include: ["get_account", "list_accounts"] })
325
+ toolkit.getTools({ exclude: ["delete_account"] })
147
326
 
148
- You can also filter at call time via `getTools({ bundles, include, exclude, mode })`.
327
+ // Narrow to write-enabled tools for this call only (per-call mode overrides global config)
328
+ toolkit.getTools({ mode: SafetyMode.WriteEnabled })
329
+ ```
149
330
 
150
331
  ### Exclude / include specific tools
151
332
 
333
+ `configuration.bundles.billing.exclude` is evaluated once at toolkit creation and applies permanently to that bundle. `getTools({ exclude })` filters the already-loaded catalog on each call and works across all bundles.
334
+
335
+ `include` and `exclude` can be combined: `include` acts as an allowlist first, then `exclude` removes from that set. A tool passes if it matches `include` (when set) **and** is not in `exclude`.
336
+
152
337
  ```ts
153
338
  configuration: {
154
339
  bundles: {
155
340
  billing: {
156
341
  exclude: ["create_invoice", "delete_invoice"],
157
- // or include: ["list_invoices"]
342
+ // or use include to allowlist specific tools: include: ["list_invoices"]
343
+ // or combine both: include: ["run_reports", "list_invoices"], exclude: ["delete_billing_run"]
158
344
  },
159
345
  },
160
346
  }
161
347
  ```
162
348
 
163
- ### Split operations
349
+ ### Enabling or disabling individual tools
164
350
 
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.
351
+ To enable or disable specific tools without affecting their entire bundle:
352
+
353
+ ```ts
354
+ configuration: {
355
+ tools: {
356
+ "delete_invoice": false, // disable one tool
357
+ "run_reports": true, // explicitly enable (useful when a bundle is partially restricted)
358
+ },
359
+ }
360
+ ```
166
361
 
167
362
  ### Approval policy
168
363
 
169
364
  Gate write / high-risk operations before they reach Zuora. `approvalPolicy` is a **top-level** config field (not inside `configuration`):
170
365
 
171
366
  ```ts
172
- import { ApprovalPolicyPreset } from "@zuora/agent-toolkit";
367
+ import { ApprovalPolicyPreset, SafetyMode } from "@zuora/agent-toolkit";
173
368
 
174
369
  const toolkit = await createZuoraOpenAIAgentToolkit({
175
370
  env: true,
176
- configuration: { mode: "writeEnabled" },
371
+ configuration: { mode: SafetyMode.WriteEnabled },
177
372
  approvalPolicy: ApprovalPolicyPreset.HighRisk,
178
373
  // or a custom handler:
179
374
  // approvalPolicy: async (request) => {
180
375
  // if (request.tool.meta.riskLevel === "high") return "deny";
181
- // return "not_required";
376
+ // return "allow";
182
377
  // },
183
378
  });
184
379
  ```
185
380
 
186
381
  Presets: `Off`, `AllWrites`, `HighRisk`, `ProductionWrites`, `ProductionHighRisk`.
187
382
 
188
- ### Context defaults
383
+ Custom handler return values: `"allow"` (proceed) · `"deny"` (block) · `{ decision: "allow" | "deny", reason?: string }` (with an explanation added to the rejection error).
189
384
 
190
- Pass optional string context keys (for example tenant or entity identifiers) via `configuration.context`. Connection-level `entityIds` / `orgIds` arrays live on `connection` instead:
385
+ ### Context
386
+
387
+ Pass routing and observability identifiers via `configuration.context`. All values are strings — use comma-separation for multiple IDs:
388
+
389
+ | Key | Effect |
390
+ |---|---|
391
+ | `entityIds` | Forwarded as `Zuora-Entity-Ids` to scope operations to the specified entity |
392
+ | `orgIds` | Forwarded as `Zuora-Org-Ids` to scope operations to the specified org |
191
393
 
192
394
  ```ts
193
- configuration: {
194
- context: {
195
- tenantId: "…",
395
+ const toolkit = await createZuoraOpenAIAgentToolkit({
396
+ env: true,
397
+ configuration: {
398
+ mode: SafetyMode.WriteEnabled,
399
+ context: {
400
+ entityIds: "entity-1,entity-2", // comma-separated for multiple
401
+ orgIds: "org-1",
402
+ },
196
403
  },
197
- },
198
- connection: {
199
- entityIds: ["entity-1"],
200
- orgIds: ["org-1"],
201
- },
404
+ });
202
405
  ```
203
406
 
204
407
  ### Observability
205
408
 
206
409
  ```ts
207
410
  observability: {
208
- onEvent: (event) => { /* tool.called, tool.completed, approval.*, … */ },
411
+ onEvent: (event) => { /* synchronous observer */ },
412
+ emit: async (event) => { /* async sink — await is respected */ },
209
413
  logger: { info: console.info, warn: console.warn, error: console.error },
210
- includePayloads: false, // default; set true to include sanitized I/O
414
+ includePayloads: false, // default; set true to include tool input keys and
415
+ // truncated values in events — secrets are redacted
211
416
  }
212
417
  ```
213
418
 
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
- ```
419
+ Emitted event names:
224
420
 
225
- | Variable | Purpose |
421
+ | Event | When |
226
422
  |---|---|
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`.
423
+ | `tool.called` | Tool invocation starts |
424
+ | `tool.completed` | Tool returns successfully |
425
+ | `tool.failed` | Tool throws or returns an error |
426
+ | `approval.requested` | Approval policy triggers before execution |
427
+ | `approval.decided` | Approval decision recorded (approved / denied) |
428
+ | `metadata.loaded` | Tool catalog fetched from MCP on `initialize()` |
429
+ | `async.poll.checked` | A polling iteration completed for an async job |
232
430
 
233
431
  ## Lifecycle
234
432
 
235
- Always call `close()` when done:
433
+ Always call `close()` when done — it closes the underlying MCP connection and allows the process to exit cleanly:
236
434
 
237
435
  ```ts
238
436
  const toolkit = await createZuoraOpenAIAgentToolkit({ env: true });
@@ -245,19 +443,19 @@ try {
245
443
 
246
444
  ## Discovery and execution
247
445
 
446
+ > `listTools()` returns `ZuoraToolDefinition[]` — framework-agnostic metadata for inspection and logging.
447
+ > `getTools()` returns framework-native tool objects (e.g. `ChatCompletionTool[]`) ready to pass directly to the LLM.
448
+
248
449
  ```ts
249
450
  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");
451
+ const def = await toolkit.describeTool("get_account"); // ZuoraToolDefinition
452
+ const bundles = await toolkit.listBundles(); // ZuoraBundleName[]
453
+ const bundle = await toolkit.describeBundle("subscriptions"); // { name, description, toolCount, tools: ZuoraToolDefinition[] }
253
454
 
254
455
  const result = await toolkit.callTool("get_account", { accountKey: "…" });
255
- // For async tools: await toolkit.waitForJob(toolName, asyncResult, options?)
256
456
  ```
257
457
 
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`.
458
+ For tools that start async jobs (e.g. Data Query), the tool result automatically includes a follow-up instruction telling the model which tool to call next — the model drives polling on subsequent steps without any extra code.
261
459
 
262
460
  ## Errors
263
461
 
@@ -277,7 +475,7 @@ All SDK errors are `ZuoraAgentToolkitError` with a typed `code`:
277
475
  import { ZuoraAgentToolkitError } from "@zuora/agent-toolkit";
278
476
 
279
477
  try {
280
- await handleToolCalls({ toolkit, response });
478
+ await toolkit.callTool("create_invoice", { accountId: "A-1" });
281
479
  } catch (err) {
282
480
  if (err instanceof ZuoraAgentToolkitError && err.code === "APPROVAL_REJECTED") {
283
481
  // handle rejection
@@ -285,6 +483,21 @@ try {
285
483
  }
286
484
  ```
287
485
 
486
+ When using framework adapters, `ZuoraAgentToolkitError` surfaces from `handleToolCalls()` (OpenAI Chat Completions), `run()` (OpenAI Agents SDK), `agent.invoke()` (LangChain), or during stream consumption with `generateText()` / `streamText()` (Vercel AI SDK).
487
+
488
+ ## Key exported types
489
+
490
+ ```ts
491
+ import type {
492
+ ZuoraToolDefinition, // full tool descriptor: name, description, inputSchema, meta
493
+ ZuoraBundleName, // union of all valid bundle name strings
494
+ ToolSelection, // filter shape passed to getTools() / listTools() — { bundles?, include?, exclude?, mode? }
495
+ ZuoraToolkitEvent, // observability event payload
496
+ ApprovalPolicy, // type for the approvalPolicy config field
497
+ } from "@zuora/agent-toolkit";
498
+ import { ZuoraAgentToolkitError, ApprovalPolicyPreset, SafetyMode } from "@zuora/agent-toolkit";
499
+ ```
500
+
288
501
  ## Examples
289
502
 
290
503
  Working examples are available in the source repository:
@@ -293,6 +506,9 @@ Working examples are available in the source repository:
293
506
  |---|---|
294
507
  | `examples/openai/index.ts` | Minimal Chat Completions agent loop |
295
508
  | `examples/openai/advanced.ts` | Observability, hooks, async jobs, discovery |
296
- | `examples/openai/hitl.ts` | Human-in-the-loop via `shouldExecute` |
509
+ | `examples/openai/hitl.ts` | Human-in-the-loop (HITL) approval |
297
510
  | `examples/openai-agents/index.ts` | OpenAI Agents SDK agent |
298
- | `examples/openai-agents-cli/` | Interactive CLI agent + planner |
511
+ | `examples/openai-agents-cli/` | Interactive CLI agent + planner + dual-layer approval (HITL + toolkit) |
512
+ | `examples/langchain-agents-cli/` | Interactive LangChain CLI agent + planner + dual-layer approval (HITL + toolkit) |
513
+ | `examples/langchain/index.ts` | LangChain ReactAgent with Zuora tools |
514
+ | `examples/vercel-ai/index.ts` | Vercel AI SDK `streamText` agent |
@@ -30,7 +30,11 @@ type EventSanitizerLimits = {
30
30
  };
31
31
 
32
32
  type ZuoraEnvironment = "sandbox" | "production";
33
- type ZuoraSafetyMode = "readOnly" | "writeEnabled";
33
+ declare const SafetyMode: {
34
+ readonly ReadOnly: "readOnly";
35
+ readonly WriteEnabled: "writeEnabled";
36
+ };
37
+ type ZuoraSafetyMode = (typeof SafetyMode)[keyof typeof SafetyMode];
34
38
  type ZuoraToolOperation = "read" | "write";
35
39
  type ZuoraRiskLevel = "low" | "medium" | "high";
36
40
  /**
@@ -164,13 +168,8 @@ type JsonSchema = {
164
168
  [key: string]: unknown;
165
169
  };
166
170
  type ZuoraAuthConfig = {
167
- type: "oauthClientCredentials";
168
171
  clientId: string;
169
172
  clientSecret: string;
170
- scope?: string;
171
- } | {
172
- type: "accessToken";
173
- accessToken: string;
174
173
  };
175
174
  type ZuoraTenantAuthConfig = {
176
175
  enabled: true;
@@ -226,8 +225,6 @@ type ZuoraMcpClient = {
226
225
  close?: () => Promise<void>;
227
226
  };
228
227
  type ZuoraConnectionConfigBase = {
229
- tokenEndpoint?: string;
230
- tokenAuthMethod?: "client_secret_post" | "client_secret_basic";
231
228
  headers?: Record<string, string | undefined>;
232
229
  entityIds?: string | string[];
233
230
  orgIds?: string | string[];
@@ -237,10 +234,6 @@ type ZuoraConnectionConfigBase = {
237
234
  type ZuoraTenantAuthConnectionConfig = ZuoraConnectionConfigBase & {
238
235
  tenantAuth: EnabledZuoraTenantAuthConfig;
239
236
  };
240
- type ZuoraStandardConnectionConfig = ZuoraConnectionConfigBase & {
241
- tenantAuth?: Exclude<ZuoraTenantAuthConfig, EnabledZuoraTenantAuthConfig>;
242
- };
243
- type ZuoraConnectionConfig = ZuoraStandardConnectionConfig | ZuoraTenantAuthConnectionConfig;
244
237
  declare const ApprovalPolicyPreset: {
245
238
  readonly Off: "off";
246
239
  readonly AllWrites: "all_writes";
@@ -249,8 +242,8 @@ declare const ApprovalPolicyPreset: {
249
242
  readonly ProductionHighRisk: "production_high_risk";
250
243
  };
251
244
  type ApprovalPolicyPresetValue = (typeof ApprovalPolicyPreset)[keyof typeof ApprovalPolicyPreset];
252
- type ApprovalPolicyResult = "not_required" | "deny" | {
253
- decision: "not_required" | "deny";
245
+ type ApprovalPolicyResult = "allow" | "deny" | {
246
+ decision: "allow" | "deny";
254
247
  reason?: string;
255
248
  };
256
249
  type ApprovalPolicyHandler = (request: ApprovalRequest) => ApprovalPolicyResult | Promise<ApprovalPolicyResult>;
@@ -362,7 +355,6 @@ type ZuoraAgentToolkitEnvConfig = Omit<ZuoraAgentToolkitConfigBase, "baseUrl"> &
362
355
  };
363
356
  type ZuoraAgentToolkitConfig = (ZuoraAgentToolkitConfigBase & {
364
357
  auth: ZuoraAuthConfig;
365
- connection?: ZuoraConnectionConfig;
366
358
  }) | (ZuoraAgentToolkitConfigBase & {
367
359
  auth?: ZuoraAuthConfig;
368
360
  connection: ZuoraTenantAuthConnectionConfig;
@@ -529,4 +521,4 @@ declare function resolveAsyncJobPollingToolName(result: ZuoraAsyncJobResult | Zu
529
521
  */
530
522
  declare function resolveAsyncJobStatusDetails(result: ZuoraToolExecutionResult, statusPath: string, terminalStatuses: ZuoraAsyncJobTerminalStatuses): ZuoraAsyncJobStatusResolution;
531
523
 
532
- export { type ApprovalPolicy as A, resolveAsyncJobId as B, resolveAsyncJobPollingToolName as C, DEFAULT_ASYNC_JOB_TERMINAL_STATUSES as D, resolveAsyncJobStatus as E, resolveAsyncJobStatusDetails as F, resolveAsyncJobTerminalState as G, type NativeFileApprovalMetadata as N, type ToolSelection as T, type ZuoraToolDefinition as Z, type ZuoraAgentToolkitConfigInput as a, type ZuoraToolExecutionResult as b, type ZuoraAsyncJobResult as c, type ZuoraToolkitEvent as d, type ZuoraAgentToolkitConfig as e, type ZuoraMcpClient as f, type ZuoraBundleDefinition as g, type ZuoraBundleName as h, type ZuoraWaitForJobOptions as i, ApprovalPolicyPreset as j, type ApprovalPolicyPresetValue as k, type ApprovalRequest as l, type NativeFileToolsConfig as m, type NativeToolApprovalMetadata as n, type NativeToolMetadataConfig as o, type ToolkitBundleName as p, type ZuoraAsyncJobStatusResolution as q, type ZuoraAsyncJobTerminalState as r, type ZuoraAsyncJobTerminalStatuses as s, type ZuoraSafetyMode as t, type ZuoraToolkitConfiguration as u, type ZuoraToolkitLogger as v, buildAsyncJobResult as w, isAsyncJobResult as x, isAsyncJobTerminal as y, matchesAsyncJobTerminalStatus as z };
524
+ export { type ApprovalPolicy as A, resolveAsyncJobId as B, resolveAsyncJobPollingToolName as C, DEFAULT_ASYNC_JOB_TERMINAL_STATUSES as D, resolveAsyncJobStatus as E, resolveAsyncJobStatusDetails as F, resolveAsyncJobTerminalState as G, type NativeFileApprovalMetadata as N, SafetyMode as S, type ToolSelection as T, type ZuoraToolDefinition as Z, type ZuoraAgentToolkitConfigInput as a, type ZuoraToolExecutionResult as b, type ZuoraAsyncJobResult as c, type ZuoraToolkitEvent as d, type ZuoraAgentToolkitConfig as e, type ZuoraMcpClient as f, type ZuoraBundleDefinition as g, type ZuoraBundleName as h, type ZuoraWaitForJobOptions as i, ApprovalPolicyPreset as j, type ApprovalPolicyPresetValue as k, type ApprovalRequest as l, type NativeFileToolsConfig as m, type NativeToolApprovalMetadata as n, type NativeToolMetadataConfig as o, type ToolkitBundleName as p, type ZuoraAsyncJobStatusResolution as q, type ZuoraAsyncJobTerminalState as r, type ZuoraAsyncJobTerminalStatuses as s, type ZuoraSafetyMode as t, type ZuoraToolkitConfiguration as u, type ZuoraToolkitLogger as v, buildAsyncJobResult as w, isAsyncJobResult as x, isAsyncJobTerminal as y, matchesAsyncJobTerminalStatus as z };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as ZuoraToolExecutionResult, h as ZuoraBundleName } from './async-jobs-Dd8Jafv0.js';
2
- export { A as ApprovalPolicy, j as ApprovalPolicyPreset, k as ApprovalPolicyPresetValue, l as ApprovalRequest, D as DEFAULT_ASYNC_JOB_TERMINAL_STATUSES, N as NativeFileApprovalMetadata, m as NativeFileToolsConfig, n as NativeToolApprovalMetadata, o as NativeToolMetadataConfig, T as ToolSelection, p as ToolkitBundleName, e as ZuoraAgentToolkitConfig, a as ZuoraAgentToolkitConfigInput, c as ZuoraAsyncJobResult, q as ZuoraAsyncJobStatusResolution, r as ZuoraAsyncJobTerminalState, s as ZuoraAsyncJobTerminalStatuses, g as ZuoraBundleDefinition, t as ZuoraSafetyMode, Z as ZuoraToolDefinition, u as ZuoraToolkitConfiguration, d as ZuoraToolkitEvent, v as ZuoraToolkitLogger, i as ZuoraWaitForJobOptions, w as buildAsyncJobResult, x as isAsyncJobResult, y as isAsyncJobTerminal, z as matchesAsyncJobTerminalStatus, B as resolveAsyncJobId, C as resolveAsyncJobPollingToolName, E as resolveAsyncJobStatus, F as resolveAsyncJobStatusDetails, G as resolveAsyncJobTerminalState } from './async-jobs-Dd8Jafv0.js';
1
+ import { b as ZuoraToolExecutionResult, h as ZuoraBundleName } from './async-jobs-DJ_GygII.js';
2
+ export { A as ApprovalPolicy, j as ApprovalPolicyPreset, k as ApprovalPolicyPresetValue, l as ApprovalRequest, D as DEFAULT_ASYNC_JOB_TERMINAL_STATUSES, N as NativeFileApprovalMetadata, m as NativeFileToolsConfig, n as NativeToolApprovalMetadata, o as NativeToolMetadataConfig, S as SafetyMode, T as ToolSelection, p as ToolkitBundleName, e as ZuoraAgentToolkitConfig, a as ZuoraAgentToolkitConfigInput, c as ZuoraAsyncJobResult, q as ZuoraAsyncJobStatusResolution, r as ZuoraAsyncJobTerminalState, s as ZuoraAsyncJobTerminalStatuses, g as ZuoraBundleDefinition, t as ZuoraSafetyMode, Z as ZuoraToolDefinition, u as ZuoraToolkitConfiguration, d as ZuoraToolkitEvent, v as ZuoraToolkitLogger, i as ZuoraWaitForJobOptions, w as buildAsyncJobResult, x as isAsyncJobResult, y as isAsyncJobTerminal, z as matchesAsyncJobTerminalStatus, B as resolveAsyncJobId, C as resolveAsyncJobPollingToolName, E as resolveAsyncJobStatus, F as resolveAsyncJobStatusDetails, G as resolveAsyncJobTerminalState } from './async-jobs-DJ_GygII.js';
3
3
 
4
4
  type ZuoraAgentToolkitP0ErrorCode = "TOOL_EXECUTION_ERROR" | "TOOL_NOT_FOUND" | "BUNDLE_NOT_FOUND" | "APPROVAL_REJECTED" | "MALFORMED_METADATA" | "OAUTH_TOKEN_ERROR" | "ASYNC_JOB_TIMEOUT";
5
5
  type ZuoraAgentToolkitErrorCode = ZuoraAgentToolkitP0ErrorCode | (string & {});
package/dist/index.js CHANGED
@@ -236,6 +236,10 @@ var readNonEmptyString = (value) => {
236
236
  };
237
237
 
238
238
  // src/shared/types.ts
239
+ var SafetyMode = {
240
+ ReadOnly: "readOnly",
241
+ WriteEnabled: "writeEnabled"
242
+ };
239
243
  var ApprovalPolicyPreset = {
240
244
  Off: "off",
241
245
  AllWrites: "all_writes",
@@ -479,6 +483,7 @@ var ZUORA_BUNDLE_NAME_SET = new Set(ZUORA_BUNDLE_NAMES);
479
483
  export {
480
484
  ApprovalPolicyPreset,
481
485
  DEFAULT_ASYNC_JOB_TERMINAL_STATUSES,
486
+ SafetyMode,
482
487
  ZUORA_BUNDLE_NAME_SET,
483
488
  ZuoraAgentToolkitError,
484
489
  buildAsyncJobResult,