@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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/errors.ts","../src/shared/tools/native/query-result-download/helpers/index.ts","../src/shared/tools/native/zuora-file-download/helpers/index.ts","../src/shared/agent-tool-follow-ups.ts","../src/shared/types.ts","../src/shared/utils/adapter-name.ts","../src/shared/async-jobs.ts","../src/shared/bundles.ts","../src/shared/utils/values.ts"],"sourcesContent":["export type ZuoraAgentToolkitP0ErrorCode =\n | \"TOOL_EXECUTION_ERROR\"\n | \"TOOL_NOT_FOUND\"\n | \"BUNDLE_NOT_FOUND\"\n | \"APPROVAL_REJECTED\"\n | \"MALFORMED_METADATA\"\n | \"OAUTH_TOKEN_ERROR\"\n | \"ASYNC_JOB_TIMEOUT\";\n\nexport type ZuoraAgentToolkitErrorCode =\n | ZuoraAgentToolkitP0ErrorCode\n | (string & {});\n\nexport type ZuoraAgentToolkitP0ErrorCategory =\n | \"tool_execution\"\n | \"approval\"\n | \"metadata\"\n | \"auth\";\n\nexport type ZuoraAgentToolkitErrorCategory =\n | ZuoraAgentToolkitP0ErrorCategory\n | \"permission\"\n | \"validation\"\n | \"zuora\"\n | \"mcp\"\n | \"configuration\"\n | \"native_file_tools\";\n\nexport class ZuoraAgentToolkitError extends Error {\n readonly code: ZuoraAgentToolkitErrorCode;\n\n readonly category: ZuoraAgentToolkitErrorCategory;\n\n readonly retryable: boolean;\n\n readonly recoverable: boolean;\n\n readonly requestId?: string | undefined;\n\n readonly details?: unknown;\n\n constructor(params: {\n code: ZuoraAgentToolkitErrorCode;\n message: string;\n category: ZuoraAgentToolkitErrorCategory;\n retryable?: boolean;\n recoverable?: boolean;\n requestId?: string;\n details?: unknown;\n }) {\n super(params.message);\n this.name = \"ZuoraAgentToolkitError\";\n this.code = params.code;\n this.category = params.category;\n this.recoverable = params.recoverable ?? params.retryable ?? false;\n this.retryable = params.retryable ?? false;\n this.requestId = params.requestId;\n this.details = params.details;\n }\n}\n","import { ZuoraAgentToolkitError } from \"../../../../errors.js\";\n\nexport const readJobId = (value: unknown): string => {\n if (typeof value === \"string\" && value.trim()) {\n return value.trim();\n }\n\n throw new ZuoraAgentToolkitError({\n code: \"QUERY_RESULT_JOB_ID_INVALID\",\n message: \"download_query_result requires a non-empty jobId.\",\n category: \"validation\",\n recoverable: true,\n });\n};\n\nexport const findDownloadUrl = (value: unknown): string | undefined =>\n findStringByKey(value, (key, candidate) => {\n if (!DOWNLOAD_URL_KEY_PATTERN.test(key)) {\n return false;\n }\n\n return isHttpUrl(candidate);\n });\n\nexport const findQueryStatus = (value: unknown): string | undefined =>\n findStringByKey(value, (key) => /^(queryStatus|status)$/i.test(key));\n\nconst findStringByKey = (\n value: unknown,\n predicate: (key: string, value: string) => boolean,\n depth = 0,\n): string | undefined => {\n if (depth > 8 || typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n const found = findStringByKey(item, predicate, depth + 1);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n }\n\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (typeof item === \"string\" && predicate(key, item)) {\n return item;\n }\n\n const found = findStringByKey(item, predicate, depth + 1);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\nconst isHttpUrl = (value: string): boolean => {\n try {\n const url = new URL(value);\n\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n};\n\nconst DOWNLOAD_URL_KEY_PATTERN =\n /^(dataFile|resultFile|downloadFile|fileUrl|downloadUrl|resultUrl|href)$/i;\n","import { ZuoraAgentToolkitError } from \"../../../../errors.js\";\n\nconst ZUORA_FILE_RESOURCE_URI_PATTERN =\n /^zuora:\\/\\/files\\/([A-Za-z0-9]+)$/i;\n\nexport const readZuoraFileId = (\n input: Record<string, unknown>,\n): string => {\n const fileId = typeof input.fileId === \"string\" ? input.fileId.trim() : \"\";\n const resourceUri =\n typeof input.resourceUri === \"string\" ? input.resourceUri.trim() : \"\";\n\n if (fileId) {\n return fileId;\n }\n\n const parsedFileId = parseZuoraFileResourceUri(resourceUri);\n\n if (parsedFileId) {\n return parsedFileId;\n }\n\n throw new ZuoraAgentToolkitError({\n code: \"INVALID_INPUT\",\n message:\n \"download_zuora_file requires fileId or resourceUri zuora://files/{fileId}.\",\n category: \"validation\",\n recoverable: true,\n });\n};\n\nexport const parseZuoraFileResourceUri = (\n resourceUri: string,\n): string | undefined => {\n const match = resourceUri.match(ZUORA_FILE_RESOURCE_URI_PATTERN);\n return match?.[1];\n};\n\nexport const resolveZuoraFileResource = (\n value: unknown,\n keyPath: string[] = [],\n depth = 0,\n): { fileId: string; resourceUri: string } | undefined => {\n if (depth > 6) {\n return undefined;\n }\n\n if (typeof value === \"string\") {\n const fileId = parseZuoraFileResourceUri(value);\n\n if (fileId && keyPath.at(-1) === \"resourceUri\") {\n return { fileId, resourceUri: value };\n }\n\n return undefined;\n }\n\n if (Array.isArray(value)) {\n for (const [index, item] of value.entries()) {\n const resolved = resolveZuoraFileResource(item, [\n ...keyPath,\n String(index),\n ], depth + 1);\n\n if (resolved) {\n return resolved;\n }\n }\n\n return undefined;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n const resourceUri = record.resourceUri;\n\n if (typeof resourceUri === \"string\") {\n const fileId = parseZuoraFileResourceUri(resourceUri);\n\n if (fileId) {\n return { fileId, resourceUri };\n }\n }\n\n const explicitFileId =\n typeof record.fileId === \"string\" ? record.fileId.trim() : \"\";\n\n if (explicitFileId && typeof resourceUri === \"string\") {\n const parsed = parseZuoraFileResourceUri(resourceUri);\n\n if (parsed === explicitFileId) {\n return { fileId: explicitFileId, resourceUri };\n }\n }\n\n for (const [key, item] of Object.entries(record)) {\n const resolved = resolveZuoraFileResource(item, [...keyPath, key], depth + 1);\n\n if (resolved) {\n return resolved;\n }\n }\n\n return undefined;\n};\n\nexport const defaultZuoraFileName = (\n fileId: string,\n contentType?: string,\n): string => {\n if (contentType?.includes(\"pdf\")) {\n return `${fileId}.pdf`;\n }\n\n if (contentType?.includes(\"json\")) {\n return `${fileId}.json`;\n }\n\n return `${fileId}.bin`;\n};\n","import { findDownloadUrl } from \"./tools/native/query-result-download/helpers/index.js\";\nimport { resolveZuoraFileResource } from \"./tools/native/zuora-file-download/helpers/index.js\";\nimport type { ZuoraToolExecutionResult } from \"./types.js\";\n\nexport const QUERY_RESULT_DOWNLOAD_TOOL = \"download_query_result\";\nexport const ZUORA_FILE_DOWNLOAD_TOOL = \"download_zuora_file\";\nexport const READ_DOWNLOADED_FILE_TOOL = \"read_downloaded_file\";\n\nexport type AgentToolFollowUpContext = {\n /** Job id from a pending async job, when the caller already resolved it. */\n asyncJobId?: string;\n};\n\nexport type AgentToolFollowUpResolution = {\n followUpToolNames: string[];\n requireToolOnNextStep: boolean;\n recommendedNextTool?: string;\n recommendedNextArgs?: Record<string, unknown>;\n hasDownloadableOutput: boolean;\n};\n\nexport const resolveAgentToolFollowUps = (\n result: ZuoraToolExecutionResult,\n context: AgentToolFollowUpContext = {},\n): AgentToolFollowUpResolution => {\n const followUpToolNames: string[] = [];\n const data = result.data;\n const jobId = context.asyncJobId ?? findQueryJobId(data);\n const zuoraFile = resolveZuoraFileResource(data);\n const hasDownloadUrl = findDownloadUrl(data) !== undefined;\n const artifactId = findDownloadArtifactId(data);\n\n let recommendedNextTool: string | undefined;\n let recommendedNextArgs: Record<string, unknown> | undefined;\n\n if (zuoraFile) {\n followUpToolNames.push(ZUORA_FILE_DOWNLOAD_TOOL);\n recommendedNextTool = ZUORA_FILE_DOWNLOAD_TOOL;\n recommendedNextArgs = { fileId: zuoraFile.fileId };\n } else if (hasDownloadUrl && jobId) {\n // A download URL is present in the result, and we have a jobId to pass to\n // the download tool. Note: the download URL itself is not included in\n // recommendedNextArgs — the download_query_result tool resolves the URL\n // internally from the jobId via a status-check call. If a download URL is\n // present but no jobId can be resolved (no jobId/queryJobId field in the\n // payload), this branch is skipped and no follow-up is emitted; the caller\n // is expected to supply asyncJobId via context in that case.\n followUpToolNames.push(QUERY_RESULT_DOWNLOAD_TOOL, READ_DOWNLOADED_FILE_TOOL);\n recommendedNextTool = QUERY_RESULT_DOWNLOAD_TOOL;\n recommendedNextArgs = { jobId };\n } else if (\n artifactId &&\n (result.toolName === QUERY_RESULT_DOWNLOAD_TOOL ||\n result.toolName === \"download_file\" ||\n result.toolName === ZUORA_FILE_DOWNLOAD_TOOL)\n ) {\n followUpToolNames.push(READ_DOWNLOADED_FILE_TOOL);\n recommendedNextTool = READ_DOWNLOADED_FILE_TOOL;\n recommendedNextArgs = { artifactId };\n }\n\n const uniqueFollowUps = [...new Set(followUpToolNames)];\n\n return {\n followUpToolNames: uniqueFollowUps,\n requireToolOnNextStep: uniqueFollowUps.length > 0,\n ...(recommendedNextTool ? { recommendedNextTool } : {}),\n ...(recommendedNextArgs ? { recommendedNextArgs } : {}),\n hasDownloadableOutput: hasDownloadUrl || zuoraFile !== undefined,\n };\n};\n\nexport const buildToolMessagePayload = (\n result: ZuoraToolExecutionResult,\n followUp: AgentToolFollowUpResolution,\n): Record<string, unknown> => {\n const baseData =\n typeof result.data === \"object\" && result.data !== null\n ? (result.data as Record<string, unknown>)\n : { value: result.data };\n\n if (!followUp.recommendedNextTool) {\n return baseData;\n }\n\n return {\n ...baseData,\n hasDownloadableOutput: followUp.hasDownloadableOutput,\n recommendedNextTool: followUp.recommendedNextTool,\n ...(followUp.recommendedNextArgs\n ? { recommendedNextArgs: followUp.recommendedNextArgs }\n : {}),\n };\n};\n\nconst findQueryJobId = (value: unknown): string | undefined => {\n const direct = readNonEmptyString(value);\n\n if (direct) {\n return direct;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n const found = findQueryJobId(item);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n\n // Check specific job-ID field names. \"id\" is intentionally checked last and\n // only at the top level of the direct object (not recursed into nested\n // objects), because it is too generic and would produce false positives on\n // large MCP responses that contain unrelated \"id\" fields deeper in the tree.\n for (const key of [\"jobId\", \"queryJobId\"]) {\n const candidate = readNonEmptyString(record[key]);\n\n if (candidate) {\n return candidate;\n }\n }\n\n // Recurse into nested objects to find jobId / queryJobId, but do NOT carry\n // the generic \"id\" key through recursion to avoid matching unrelated fields.\n for (const item of Object.values(record)) {\n const found = findQueryJobId(item);\n\n if (found) {\n return found;\n }\n }\n\n // Check \"id\" only at this level (after recursion), as a last resort for\n // responses that use a bare \"id\" field at the top of the data structure.\n const idCandidate = readNonEmptyString(record[\"id\"]);\n\n if (idCandidate) {\n return idCandidate;\n }\n\n return undefined;\n};\n\nconst findDownloadArtifactId = (value: unknown): string | undefined => {\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n const direct = readNonEmptyString(record[\"artifactId\"]);\n\n if (direct) {\n return direct;\n }\n\n const download = record[\"download\"];\n\n if (typeof download === \"object\" && download !== null) {\n const nested = readNonEmptyString(\n (download as Record<string, unknown>)[\"artifactId\"],\n );\n\n if (nested) {\n return nested;\n }\n }\n\n for (const item of Object.values(record)) {\n if (item === record[\"download\"]) {\n continue;\n }\n\n const found = findDownloadArtifactId(item);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\nconst readNonEmptyString = (value: unknown): string | undefined => {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n","import type { McpReadResourceResult, McpTool } from \"./transport/mcp-types.js\";\nimport type { EventSanitizerLimits } from \"./utils/event-sanitizer.js\";\n\nexport type { McpReadResourceResult, McpTool } from \"./transport/mcp-types.js\";\nexport type { EventSanitizerLimits } from \"./utils/event-sanitizer.js\";\n\nexport type ZuoraEnvironment = \"sandbox\" | \"production\";\n\nexport type ZuoraSafetyMode = \"readOnly\" | \"writeEnabled\";\n\nexport type ZuoraToolOperation = \"read\" | \"write\";\n\nexport type ZuoraRiskLevel = \"low\" | \"medium\" | \"high\";\n\n/**\n * Well-known Zuora bundle names. Provided for autocompletion in\n * `ToolSelection.bundles` and `ZuoraToolkitConfiguration.bundles`.\n *\n * The `string & {}` tail widens the type to accept any string (so new\n * server-side bundles are never rejected) while preserving IDE autocomplete\n * for the known literal members.\n *\n * `ZuoraToolDefinition.meta.bundle` is plain `string` — the server value\n * is passed through as-is with no normalisation or fallback.\n */\nexport type ZuoraBundleName =\n | \"accounts\"\n | \"catalog\"\n | \"subscriptions\"\n | \"orders\"\n | \"billing\"\n | \"payments\"\n | \"usage\"\n | \"revenue\"\n | \"finance\"\n | \"workflows\"\n | \"query\"\n | \"admin\"\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n | (string & {});\n\n// ── Async operation metadata — directly from _meta.asyncOperations[op] ──\nexport type ZuoraAsyncOperationMeta = {\n /** Operation on the polling MCP tool. Omitted when pollingTool is the standalone poll target. */\n pollingOperation?: string;\n /** Source MCP tool for cross-tool polling, or standalone poll tool when pollingOperation is omitted. */\n pollingTool?: string;\n jobIdPath: string;\n statusPath: string;\n pollingArgs?: Record<string, string>;\n terminalStatuses: {\n success: string[];\n failure: string[];\n canceled?: string[];\n };\n recommendedPollIntervalMs?: number;\n defaultTimeoutMs?: number;\n maxPollAttempts?: number;\n};\n\n// ── Service permission — directly from _meta.requiredServicePermissions[] ──\nexport type ZuoraServicePermission = {\n scope: string;\n name: string;\n};\n\n// ── The only public tool type ──\nexport type ZuoraToolDefinition = {\n name: string; // operation name, e.g. \"run_reports\"\n description: string;\n inputSchema: JsonSchema;\n outputSchema?: JsonSchema;\n meta: {\n bundle: string; // from _meta.bundle or SDK-native tool config; passed through as-is\n access: ZuoraToolOperation; // from _meta.operationPermissions[op]\n riskLevel: ZuoraRiskLevel; // from _meta.riskLevels[op]\n asyncOperation?: ZuoraAsyncOperationMeta; // from _meta.asyncOperations[op] — set on split/single-op tools\n /** Present on multi-op merged tools (splitOperations: false). Keyed by operation name. Used at\n * call time to look up the async meta for the operation the LLM actually invoked. */\n asyncOperations?: Record<string, ZuoraAsyncOperationMeta>;\n /** Per-operation access levels for merged tools. Used at call time to enforce the correct access for the invoked operation. */\n operationAccess?: Record<string, ZuoraToolOperation>;\n /** Per-operation risk levels for merged tools. Used at call time to enforce the correct risk for the invoked operation. */\n operationRiskLevel?: Record<string, ZuoraRiskLevel>;\n requiredServicePermissions: ZuoraServicePermission[]; // from _meta.requiredServicePermissions\n limitations: string[];\n sourceName: string; // original MCP tool name for execution routing\n /** Present on split multi-operation tools — MCP input field auto-injected at call time. */\n operationSelector?: string;\n /** Present on split multi-operation tools — the underlying MCP operation name. */\n operationName?: string;\n };\n};\n\n// ── Native file tools — config ──\n\nexport type NativeToolMetadataConfig = {\n enabled?: boolean;\n operationAccess?: ZuoraToolOperation;\n riskLevel?: ZuoraRiskLevel;\n description?: string;\n};\n\nexport type NativeFileToolsConfig = {\n enabled?: boolean;\n bundleName?: string;\n tools?: {\n downloadFile?: NativeToolMetadataConfig;\n readDownloadedFile?: NativeToolMetadataConfig;\n downloadQueryResult?: NativeToolMetadataConfig;\n downloadZuoraFile?: NativeToolMetadataConfig;\n extractDownloadedArchive?: NativeToolMetadataConfig;\n readResource?: NativeToolMetadataConfig;\n };\n rootDir?: string;\n userHomeDir?: string;\n requireRootUnderUserHome?: boolean;\n allowOverwrite?: boolean;\n maxDownloadBytes?: number;\n maxReadBytes?: number;\n maxReadLines?: number;\n maxExtractedFiles?: number;\n maxExtractedBytes?: number;\n downloadTimeoutMs?: number;\n allowedProtocols?: string[];\n allowedHosts?: string[];\n allowedUrlPrefixes?: string[];\n zuoraDownloadHosts?: string[];\n allowZuoraAuthenticatedDownload?: boolean;\n allowS3PresignedDownload?: boolean;\n allowArchiveExtraction?: boolean;\n};\n\n// ── Native tool approval metadata ──\n\nexport type NativeFileApprovalMetadata = {\n source: \"native\";\n nativeToolType: \"fileArtifacts\";\n filesystemRoot: string;\n sideEffects: Array<\"networkRead\" | \"localFileWrite\" | \"localFileRead\">;\n urlHost?: string;\n urlProtocol?: string;\n urlPath?: string;\n urlKind?: string;\n outputRelativePath?: string;\n artifactId?: string;\n relativePath?: string;\n};\n\nexport type NativeToolApprovalMetadata = NativeFileApprovalMetadata;\n\n// ── Bundle types — used by PR-3 ToolkitCore ──\nexport type ToolkitBundleName = string;\n\nexport type ZuoraBundleDefinition = {\n name: ToolkitBundleName;\n description: string;\n examples: string[];\n commonObjects: string[];\n keywords: string[];\n toolCount: number;\n readToolCount: number;\n writeToolCount: number;\n tools: ZuoraToolDefinition[];\n};\n\nexport type JsonSchema = {\n type?: string;\n properties?: Record<string, JsonSchema>;\n items?: JsonSchema;\n required?: string[];\n enum?: string[];\n description?: string;\n additionalProperties?: boolean | JsonSchema;\n [key: string]: unknown;\n};\n\nexport type ZuoraAuthConfig =\n | {\n type: \"oauthClientCredentials\";\n clientId: string;\n clientSecret: string;\n scope?: string;\n }\n | {\n type: \"accessToken\";\n accessToken: string;\n };\n\ntype ZuoraTenantAuthConfig =\n | {\n enabled: true;\n tenantId: string;\n tokenId: string;\n userId: string;\n }\n | {\n enabled?: false;\n };\n\ntype EnabledZuoraTenantAuthConfig = Extract<\n ZuoraTenantAuthConfig,\n { enabled: true }\n>;\n\nexport type BundleConfiguration = {\n include?: string[];\n exclude?: string[];\n};\n\nexport type ZuoraToolkitConfiguration = {\n mode?: ZuoraSafetyMode;\n bundles?: Partial<Record<ZuoraBundleName, boolean | BundleConfiguration>>;\n tools?: Record<string, boolean>;\n context?: Record<string, string | undefined>;\n // Controls whether multi-operation MCP tools are split into one tool per operation.\n // Default: true. When false, multi-op tools are presented as a single tool.\n splitOperations?: boolean;\n};\n\nexport type ToolSelection = {\n bundles?: ZuoraBundleName[];\n include?: string[];\n exclude?: string[];\n mode?: ZuoraSafetyMode;\n};\n\nexport type ZuoraExecutionRequestMetadata = {\n requestId?: string;\n zuoraRequestId?: string;\n environment?: ZuoraEnvironment;\n tenantId?: string;\n entityIds?: string[];\n orgIds?: string[];\n};\n\nexport type ZuoraToolExecutionResult<TData = unknown> = {\n toolName: string;\n data: TData;\n riskLevel?: ZuoraRiskLevel;\n requestId?: string;\n request?: ZuoraExecutionRequestMetadata;\n warnings?: string[];\n};\n\nexport type ZuoraMcpClient = {\n initialize?: () => Promise<void>;\n listTools: () => Promise<McpTool[]>;\n callTool: (\n name: string,\n input: Record<string, unknown>,\n ) => Promise<ZuoraToolExecutionResult>;\n readResource?: (uri: string) => Promise<McpReadResourceResult>;\n getTenantId?: () => string | undefined;\n getSensitiveValues?: () => readonly string[];\n close?: () => Promise<void>;\n};\n\ntype ZuoraConnectionConfigBase = {\n tokenEndpoint?: string;\n tokenAuthMethod?: \"client_secret_post\" | \"client_secret_basic\";\n headers?: Record<string, string | undefined>;\n entityIds?: string | string[];\n orgIds?: string | string[];\n zuoraVersion?: string;\n protocolVersion?: string;\n};\n\ntype ZuoraTenantAuthConnectionConfig = ZuoraConnectionConfigBase & {\n tenantAuth: EnabledZuoraTenantAuthConfig;\n};\n\ntype ZuoraStandardConnectionConfig = ZuoraConnectionConfigBase & {\n tenantAuth?: Exclude<ZuoraTenantAuthConfig, EnabledZuoraTenantAuthConfig>;\n};\n\nexport type ZuoraConnectionConfig =\n | ZuoraStandardConnectionConfig\n | ZuoraTenantAuthConnectionConfig;\n\ntype ZuoraHttpMcpClientConfigBase = ZuoraConnectionConfigBase & {\n url: string;\n clientName?: string;\n clientVersion?: string;\n};\n\nexport type ZuoraHttpMcpClientConfig =\n | (ZuoraHttpMcpClientConfigBase &\n ZuoraStandardConnectionConfig & {\n auth: ZuoraAuthConfig;\n })\n | (ZuoraHttpMcpClientConfigBase &\n ZuoraTenantAuthConnectionConfig & {\n auth?: ZuoraAuthConfig;\n });\n\nexport const ApprovalPolicyPreset = {\n Off: \"off\",\n AllWrites: \"all_writes\",\n HighRisk: \"high_risk\",\n ProductionWrites: \"production_writes\",\n ProductionHighRisk: \"production_high_risk\",\n} as const;\n\nexport type ApprovalPolicyPresetValue =\n (typeof ApprovalPolicyPreset)[keyof typeof ApprovalPolicyPreset];\n\nexport type ApprovalPolicyResult =\n | \"not_required\"\n | \"deny\"\n | { decision: \"not_required\" | \"deny\"; reason?: string };\n\nexport type ApprovalPolicyHandler = (\n request: ApprovalRequest,\n) => ApprovalPolicyResult | Promise<ApprovalPolicyResult>;\n\nexport type ApprovalPolicy = ApprovalPolicyPresetValue | ApprovalPolicyHandler;\n\nexport type ApprovalRequest = {\n tool: ZuoraToolDefinition;\n input: Record<string, unknown>;\n environment: ZuoraEnvironment;\n /** Present only for SDK-native tools (e.g. download_file). Undefined for MCP-sourced tools. */\n native?: NativeToolApprovalMetadata;\n};\n\n// ── Observability event types ──\n\nexport type ZuoraToolkitEventName =\n | \"tool.called\"\n | \"tool.completed\"\n | \"tool.failed\"\n | \"approval.requested\"\n /**\n * Internal name used when emitting the approval outcome from\n * `enforceApproval()`. It is normalised to the `\"approval.decided\"` type\n * by `normalizeToolkitEvent()` before being delivered to observers.\n * @see ZuoraToolkitEventType\n */\n | \"approval.resolved\"\n | \"approval.decided\"\n | \"metadata.loaded\"\n | \"async.poll.checked\"\n | \"observability.sink_failed\"\n /**\n * Emitted by framework adapters (e.g. the Agents SDK adapter) after\n * `execute()` finishes post-processing the raw MCP result. The `metadata`\n * field carries the actual response the LLM receives, which may differ from\n * the raw `callTool` output (e.g. async-job directives, download hints).\n * Consumers can subscribe to this event to audit what the LLM was told.\n */\n | \"agent.tool.response\";\n\n/**\n * The canonical event type delivered to `onEvent` / `emit` sinks.\n *\n * Note the asymmetry with `ZuoraToolkitEventName`: the internal name\n * `\"approval.resolved\"` is normalised to `\"approval.decided\"` before\n * delivery. Consumers should match on `type`, not `name`, when they need\n * a stable identifier for approval-outcome events.\n */\nexport type ZuoraToolkitEventType =\n | \"tool.called\"\n | \"tool.completed\"\n | \"tool.failed\"\n | \"approval.requested\"\n | \"approval.decided\"\n | \"metadata.loaded\"\n | \"async.poll.checked\"\n | \"observability.sink_failed\"\n | \"agent.tool.response\";\n\nexport type ZuoraToolkitApprovalDecision =\n | \"allow\"\n | \"deny\";\n\nexport type ZuoraToolkitApprovalStatus =\n | \"requested\"\n | \"rejected\"\n | \"not_required\";\n\nexport type ZuoraToolkitEventError = {\n code: string;\n category: string;\n recoverable: boolean;\n retryable?: boolean;\n requestId?: string;\n message?: string;\n};\n\nexport type ZuoraToolExecutionPhase = \"primary\" | \"async_poll\";\n\nexport type ZuoraToolkitEvent = {\n type: ZuoraToolkitEventType;\n name: ZuoraToolkitEventName;\n timestamp: string;\n toolName?: string;\n access?: ZuoraToolOperation;\n bundle?: string;\n executionPhase?: ZuoraToolExecutionPhase;\n input?: Record<string, unknown>;\n output?: Record<string, unknown>;\n requestId?: string;\n zuoraRequestId?: string;\n riskLevel?: ZuoraRiskLevel;\n latencyMs?: number;\n environment?: ZuoraEnvironment;\n tenantId?: string;\n entityIds?: string[];\n orgIds?: string[];\n approvalDecision?: ZuoraToolkitApprovalDecision;\n approvalStatus?: ZuoraToolkitApprovalStatus;\n error?: ZuoraToolkitEventError;\n metadata?: Record<string, unknown>;\n};\n\nexport type ZuoraToolkitLogger = {\n debug?: (message: string, event?: ZuoraToolkitEvent) => void;\n info?: (message: string, event?: ZuoraToolkitEvent) => void;\n warn?: (message: string, event?: ZuoraToolkitEvent) => void;\n error?: (message: string, event?: ZuoraToolkitEvent) => void;\n};\n\ntype ZuoraAgentToolkitConfigBase = {\n baseUrl: string;\n configuration?: ZuoraToolkitConfiguration;\n approvalPolicy?: ApprovalPolicy;\n /**\n * SDK-internal overrides for native file download/read infrastructure.\n * Auto-enabled when the query bundle is configured — agents never set this.\n */\n nativeFileTools?: NativeFileToolsConfig;\n observability?: {\n logger?: ZuoraToolkitLogger;\n emit?: (event: ZuoraToolkitEvent) => void | Promise<void>;\n onEvent?: (event: ZuoraToolkitEvent) => void;\n /**\n * When true, tool.called/tool.completed/tool.failed events include\n * sanitized input and output payloads. Defaults to false.\n */\n includePayloads?: boolean;\n /**\n * Limits applied when sanitizing event metadata fields.\n * Use `resolveEventSanitizerLimits(process.env)` to populate from\n * environment variables, or supply explicit values.\n * Defaults to built-in conservative limits when omitted.\n */\n sanitizerLimits?: EventSanitizerLimits;\n };\n};\n\nexport type ZuoraAgentToolkitEnvSource =\n | true\n | {\n env?: Record<string, string | undefined>;\n };\n\nexport type ZuoraAgentToolkitEnvConfig = Omit<\n ZuoraAgentToolkitConfigBase,\n \"baseUrl\"\n> & {\n env: ZuoraAgentToolkitEnvSource;\n};\n\nexport type ZuoraAgentToolkitConfig =\n | (ZuoraAgentToolkitConfigBase & {\n auth: ZuoraAuthConfig;\n connection?: ZuoraConnectionConfig;\n })\n | (ZuoraAgentToolkitConfigBase & {\n auth?: ZuoraAuthConfig;\n connection: ZuoraTenantAuthConnectionConfig;\n });\n\nexport type ZuoraAgentToolkitConfigInput =\n | ZuoraAgentToolkitConfig\n | ZuoraAgentToolkitEnvConfig;\n\n// ── Async job polling types ──\n\n/**\n * Terminal state of a completed async job.\n */\nexport type ZuoraAsyncJobTerminalState = \"success\" | \"failure\" | \"canceled\";\n\n/**\n * Configuration for which status strings are considered terminal for each outcome.\n */\nexport type ZuoraAsyncJobTerminalStatuses = {\n success: string[];\n failure: string[];\n canceled?: string[];\n};\n\n/**\n * Rich resolution of the current async job status returned by resolveAsyncJobStatusDetails.\n */\nexport type ZuoraAsyncJobStatusResolution = {\n /** Raw status string extracted from the tool result, if any. */\n status: string | undefined;\n /** True if the job has reached a terminal state. */\n isTerminal: boolean;\n /** The resolved terminal state, or undefined if not yet terminal. */\n terminalState: ZuoraAsyncJobTerminalState | undefined;\n};\n\n/**\n * Options for waitForJob() — all fields override the async meta defaults.\n */\nexport type ZuoraWaitForJobOptions = {\n /**\n * Maximum number of polling attempts before giving up.\n * Overrides asyncMeta.maxPollAttempts.\n */\n maxPollAttempts?: number;\n /**\n * Interval in milliseconds between polling attempts.\n * Overrides asyncMeta.recommendedPollIntervalMs.\n */\n pollIntervalMs?: number;\n /**\n * Absolute timeout in milliseconds from when waitForJob() was called.\n * Overrides asyncMeta.defaultTimeoutMs.\n *\n * **Note:** the timeout is checked at the start of each loop iteration,\n * before the poll is issued. A value of `0` will therefore cause\n * `waitForJob` to throw `ASYNC_JOB_TIMEOUT` immediately without ever\n * making a poll attempt.\n */\n timeoutMs?: number;\n /**\n * Custom terminal status sets. When provided, overrides the terminal statuses\n * defined in the async operation meta.\n */\n terminalStatuses?: ZuoraAsyncJobTerminalStatuses;\n};\n","const MAX_ADAPTER_TOOL_NAME_LENGTH = 64;\nconst SAFE_ADAPTER_TOOL_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;\n\nexport const createOperationAdapterToolName = (\n sourceToolName: string,\n operationName: string,\n): string => {\n const rawName = `${sourceToolName}__${operationName}`;\n\n if (\n rawName.length <= MAX_ADAPTER_TOOL_NAME_LENGTH &&\n SAFE_ADAPTER_TOOL_NAME_PATTERN.test(rawName)\n ) {\n return rawName;\n }\n\n return sanitizeAdapterToolName(rawName);\n};\n\nexport const stableNameHash = (value: string): string => {\n let hash = 0x811c9dc5;\n\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193);\n }\n\n return (hash >>> 0).toString(36).padStart(7, \"0\").slice(-7);\n};\n\nexport const sanitizeAdapterToolName = (name: string): string => {\n if (\n name.length <= MAX_ADAPTER_TOOL_NAME_LENGTH &&\n SAFE_ADAPTER_TOOL_NAME_PATTERN.test(name)\n ) {\n return name;\n }\n\n const sanitized = name\n .replace(/[^A-Za-z0-9_-]+/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n\n const candidate = sanitized || \"tool\";\n\n if (\n candidate.length <= MAX_ADAPTER_TOOL_NAME_LENGTH &&\n SAFE_ADAPTER_TOOL_NAME_PATTERN.test(candidate)\n ) {\n return candidate;\n }\n\n const hash = stableNameHash(name);\n const suffix = `_${hash}`;\n const maxPrefixLength = MAX_ADAPTER_TOOL_NAME_LENGTH - suffix.length;\n const prefix = candidate.slice(0, maxPrefixLength) || \"tool\";\n\n return `${prefix}${suffix}`;\n};\n","import { ZuoraAgentToolkitError } from \"./errors.js\";\nimport { createOperationAdapterToolName } from \"./utils/adapter-name.js\";\nimport type {\n ZuoraAsyncJobStatusResolution,\n ZuoraAsyncJobTerminalState,\n ZuoraAsyncJobTerminalStatuses,\n ZuoraAsyncOperationMeta,\n ZuoraToolExecutionResult,\n} from \"./types.js\";\n\nexport type BuildAsyncJobResultContext = {\n sourceToolName: string;\n operationSelector?: string;\n /** Split operation names registered for sourceToolName. */\n sourceOperationNames?: string[];\n};\n\nexport type ZuoraAsyncJobResult = {\n toolName: string;\n status: \"pending\";\n jobId: string;\n /** Resolved toolkit tool name to call when polling or auto-waiting. */\n pollingToolName: string;\n /** Original MCP pollingOperation metadata, when present. */\n pollingOperation?: string;\n pollingArgs?: Record<string, string>;\n /** Dot-path to the status field in the poll result (e.g. \"data.status\"). Populated from asyncMeta.statusPath at job-start time. */\n statusPath?: string;\n /** Terminal status sets for this job. Populated from asyncMeta.terminalStatuses at job-start time. */\n terminalStatuses?: ZuoraAsyncJobTerminalStatuses;\n recommendedPollIntervalMs?: number;\n maxPollAttempts?: number;\n defaultTimeoutMs?: number;\n};\n\n/**\n * Default terminal statuses applied when the async operation meta does not\n * specify its own terminal status sets and no override is provided to waitForJob.\n */\nexport const DEFAULT_ASYNC_JOB_TERMINAL_STATUSES: ZuoraAsyncJobTerminalStatuses = {\n success: [\"Completed\", \"Success\", \"Succeeded\", \"Done\"],\n failure: [\"Failed\", \"Error\", \"Errored\", \"Aborted\"],\n canceled: [\"Canceled\", \"Cancelled\"],\n};\n\n/**\n * Returns `true` when `data` is a `ZuoraAsyncJobResult` — a pending async job\n * result produced by `ToolkitCore.callTool()` via `buildAsyncJobResult()`.\n *\n * The check requires `status === \"pending\"`, a non-empty `jobId` string, and\n * at least one of `pollingToolName` or `pollingOperation`. Those routing fields\n * are only present on data shaped by `buildAsyncJobResult` — plain API responses\n * that happen to contain a job-ID-like field will not pass this guard.\n */\nexport const isAsyncJobResult = (data: unknown): data is ZuoraAsyncJobResult =>\n data != null &&\n typeof data === \"object\" &&\n (data as ZuoraAsyncJobResult).status === \"pending\" &&\n typeof (data as ZuoraAsyncJobResult).jobId === \"string\" &&\n (data as ZuoraAsyncJobResult).jobId.trim().length > 0 &&\n (typeof (data as ZuoraAsyncJobResult).pollingToolName === \"string\" ||\n typeof (data as ZuoraAsyncJobResult).pollingOperation === \"string\");\n\n/**\n * Normalizes MCP JSONPath-style expressions (e.g. \"$.data.jobId\") to the\n * dot-path form used by resolveByPath (e.g. \"data.jobId\").\n */\nexport const normalizeMetadataPath = (path: string): string => {\n const trimmed = path.trim();\n if (trimmed.startsWith(\"$.\")) {\n return trimmed.slice(2);\n }\n if (trimmed.startsWith(\"$\")) {\n return trimmed.slice(1);\n }\n return trimmed;\n};\n\n/**\n * Resolves a dot-path expression (e.g. \"data.jobId\") against an object.\n * Returns undefined if any segment is missing.\n */\nfunction resolveByPath(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic dot-path traversal requires any\n obj: any,\n path: string,\n): unknown {\n return path.split(\".\").reduce(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- same reason as above\n (current: any, segment: string) =>\n current != null && typeof current === \"object\" ? current[segment] : undefined,\n obj,\n );\n}\n\n/**\n * Resolves the toolkit tool name to invoke when polling an async job.\n * Mirrors legacy `resolveAsyncJobPollingToolName()`.\n */\nexport function resolveAsyncJobPollingToolNameFromMeta(\n asyncMeta: ZuoraAsyncOperationMeta,\n context: BuildAsyncJobResultContext,\n): string {\n const pollingOperation = asyncMeta.pollingOperation;\n const pollingTool = asyncMeta.pollingTool;\n\n if (!pollingOperation) {\n return pollingTool ?? context.sourceToolName;\n }\n\n if (pollingTool) {\n return createOperationAdapterToolName(pollingTool, pollingOperation);\n }\n\n if (\n context.operationSelector &&\n context.sourceOperationNames?.includes(pollingOperation)\n ) {\n return createOperationAdapterToolName(\n context.sourceToolName,\n pollingOperation,\n );\n }\n\n // Same-source, unsplit tool: pollingOperation lives on the source tool itself\n // (splitOperations: false). Return the source tool name so callTool can find\n // it in the registry; the operation is injected into pollingArgs by\n // buildAsyncJobResult so the MCP server routes to the correct operation.\n return context.sourceToolName;\n}\n\n/**\n * Builds a structured async-pending result to return to the agent when a tool\n * call produces a job that requires polling.\n *\n * @throws ZuoraAgentToolkitError with TOOL_EXECUTION_ERROR if jobIdPath\n * resolves to undefined or null on the raw result.\n */\nexport function buildAsyncJobResult(\n result: ZuoraToolExecutionResult,\n asyncMeta: ZuoraAsyncOperationMeta,\n context: BuildAsyncJobResultContext,\n): ZuoraAsyncJobResult {\n const payload = result.data;\n // asyncMeta.jobIdPath is already normalized at parse time by parseAsyncOperationMeta.\n const jobId = resolveByPath(payload, asyncMeta.jobIdPath);\n\n if (jobId == null) {\n throw new ZuoraAgentToolkitError({\n code: \"TOOL_EXECUTION_ERROR\",\n message: `Async job ID not found at path \"${asyncMeta.jobIdPath}\" in tool result for \"${result.toolName}\".`,\n category: \"tool_execution\",\n });\n }\n\n const pollingToolName = resolveAsyncJobPollingToolNameFromMeta(\n asyncMeta,\n context,\n );\n\n const pending: ZuoraAsyncJobResult = {\n toolName: result.toolName,\n status: \"pending\",\n jobId: String(jobId),\n pollingToolName,\n ...(asyncMeta.pollingOperation\n ? { pollingOperation: asyncMeta.pollingOperation }\n : {}),\n statusPath: asyncMeta.statusPath,\n terminalStatuses: asyncMeta.terminalStatuses,\n };\n\n // For same-source unsplit tools (pollingTool absent, no operationSelector),\n // the polling call targets the merged source tool, so the operation name must\n // be included in pollingArgs for the MCP server to route it correctly.\n const needsOperationInjection =\n !!asyncMeta.pollingOperation &&\n !asyncMeta.pollingTool &&\n !context.operationSelector;\n\n if (needsOperationInjection) {\n pending.pollingArgs = {\n ...asyncMeta.pollingArgs,\n operation: asyncMeta.pollingOperation!,\n };\n } else if (asyncMeta.pollingArgs !== undefined) {\n pending.pollingArgs = asyncMeta.pollingArgs;\n }\n\n if (asyncMeta.recommendedPollIntervalMs !== undefined) {\n pending.recommendedPollIntervalMs = asyncMeta.recommendedPollIntervalMs;\n }\n\n if (asyncMeta.maxPollAttempts !== undefined) {\n pending.maxPollAttempts = asyncMeta.maxPollAttempts;\n }\n\n if (asyncMeta.defaultTimeoutMs !== undefined) {\n pending.defaultTimeoutMs = asyncMeta.defaultTimeoutMs;\n }\n\n return pending;\n}\n\n// ── Async job status utilities ────────────────────────────────────────────────\n\n/**\n * Returns true when the given status string matches any status in the success,\n * failure, or canceled sets of the provided terminal statuses config.\n *\n * The comparison is case-insensitive.\n */\nexport function matchesAsyncJobTerminalStatus(\n status: string,\n statuses: ZuoraAsyncJobTerminalStatuses,\n): boolean {\n const lower = status.toLowerCase();\n if (statuses.success.some((s) => s.toLowerCase() === lower)) return true;\n if (statuses.failure.some((s) => s.toLowerCase() === lower)) return true;\n if (statuses.canceled?.some((s) => s.toLowerCase() === lower)) return true;\n return false;\n}\n\n/**\n * Resolves the terminal state (\"success\" | \"failure\" | \"canceled\") for the\n * given status string against the provided terminal statuses config.\n *\n * Returns undefined when the status has not yet reached a terminal state.\n */\nexport function resolveAsyncJobTerminalState(\n status: string,\n statuses: ZuoraAsyncJobTerminalStatuses,\n): ZuoraAsyncJobTerminalState | undefined {\n const lower = status.toLowerCase();\n if (statuses.success.some((s) => s.toLowerCase() === lower)) return \"success\";\n if (statuses.failure.some((s) => s.toLowerCase() === lower)) return \"failure\";\n if (statuses.canceled?.some((s) => s.toLowerCase() === lower)) return \"canceled\";\n return undefined;\n}\n\n/**\n * Returns true when the given async job result has reached a terminal state\n * according to the provided terminal statuses config.\n *\n * The job is considered terminal only when a non-null, non-empty status string\n * is present in the result data and that status matches one of the terminal sets.\n */\nexport function isAsyncJobTerminal(\n result: ZuoraToolExecutionResult,\n statusPath: string,\n statuses: ZuoraAsyncJobTerminalStatuses,\n): boolean {\n const status = resolveAsyncJobStatus(result, statusPath);\n if (status == null || status === \"\") return false;\n return matchesAsyncJobTerminalStatus(status, statuses);\n}\n\n/**\n * Extracts the current job status string from a polling tool result.\n *\n * @param result - The result returned by the polling tool.\n * @param statusPath - Dot-path to the status field within result.data,\n * e.g. \"status\" or \"job.state\". Paths are normalized at parse time by\n * parseAsyncOperationMeta, so callers within the SDK should always pass\n * already-normalized paths here.\n * @returns The extracted status string, or undefined if not present.\n */\nexport function resolveAsyncJobStatus(\n result: ZuoraToolExecutionResult,\n statusPath: string,\n): string | undefined {\n const raw = resolveByPath(result.data, statusPath);\n if (raw == null || typeof raw !== \"string\") return undefined;\n return raw;\n}\n\n/**\n * Extracts the job ID from a ZuoraAsyncJobResult embedded inside a tool\n * execution result, or returns the jobId directly from a ZuoraAsyncJobResult.\n *\n * Supports two calling patterns:\n * - resolveAsyncJobId(asyncJobResult)\n * - resolveAsyncJobId(toolResult, jobIdPath)\n */\nexport function resolveAsyncJobId(\n result: ZuoraAsyncJobResult | ZuoraToolExecutionResult,\n jobIdPath?: string,\n): string | undefined {\n // If we have a ZuoraAsyncJobResult (has status: \"pending\")\n if (\"status\" in result && result.status === \"pending\" && \"jobId\" in result) {\n return (result as ZuoraAsyncJobResult).jobId;\n }\n\n // Otherwise treat as ZuoraToolExecutionResult and extract via path.\n // Paths sourced from asyncMeta are already normalized at parse time by\n // parseAsyncOperationMeta, so no further normalization is needed here.\n if (jobIdPath && \"data\" in result) {\n const raw = resolveByPath(result.data, jobIdPath);\n if (raw != null && typeof raw === \"string\") return raw;\n if (raw != null) return String(raw);\n }\n\n return undefined;\n}\n\n/**\n * Extracts the name of the next polling tool from a ZuoraAsyncJobResult.\n * Returns undefined if result is not a ZuoraAsyncJobResult.\n *\n * pollingToolName is always set on ZuoraAsyncJobResult (resolved at job-start\n * time by resolveAsyncJobPollingToolNameFromMeta), so it is returned directly.\n */\nexport function resolveAsyncJobPollingToolName(\n result: ZuoraAsyncJobResult | ZuoraToolExecutionResult,\n): string | undefined {\n if (\"status\" in result && result.status === \"pending\") {\n return (result as ZuoraAsyncJobResult).pollingToolName;\n }\n return undefined;\n}\n\n/**\n * Returns a rich ZuoraAsyncJobStatusResolution describing the current status\n * of an async job, including whether it has reached a terminal state and what\n * the terminal state is.\n *\n * @param result - The result returned by the polling tool.\n * @param statusPath - Dot-path to the status field (e.g. \"data.status\").\n * @param terminalStatuses - Terminal status configuration to match against.\n */\nexport function resolveAsyncJobStatusDetails(\n result: ZuoraToolExecutionResult,\n statusPath: string,\n terminalStatuses: ZuoraAsyncJobTerminalStatuses,\n): ZuoraAsyncJobStatusResolution {\n const status = resolveAsyncJobStatus(result, statusPath);\n\n if (status == null || status === \"\") {\n return { status: undefined, isTerminal: false, terminalState: undefined };\n }\n\n const terminalState = resolveAsyncJobTerminalState(status, terminalStatuses);\n const isTerminal = terminalState !== undefined;\n\n return { status, isTerminal, terminalState };\n}\n","import bundleCatalog from \"./resources/bundle-catalog.json\" with { type: \"json\" };\nimport type {\n ZuoraBundleDefinition,\n ZuoraBundleName,\n ZuoraToolDefinition,\n} from \"./types.js\";\nimport { isRecord } from \"./utils/values.js\";\n\nexport const ZUORA_BUNDLE_NAMES = [\n \"accounts\",\n \"catalog\",\n \"subscriptions\",\n \"orders\",\n \"billing\",\n \"payments\",\n \"usage\",\n \"revenue\",\n \"finance\",\n \"workflows\",\n \"query\",\n \"admin\",\n] as const satisfies readonly ZuoraBundleName[];\n\nexport type BundleCatalogEntry = {\n description: string;\n examples: string[];\n commonObjects: string[];\n keywords: string[];\n};\n\nexport type BundleCatalog = {\n version: number;\n bundles: Record<ZuoraBundleName, BundleCatalogEntry>;\n};\n\nconst readStringArrayField = (\n record: Record<string, unknown>,\n fieldName: string,\n bundleName: ZuoraBundleName,\n): string[] => {\n const value = record[fieldName];\n\n if (\n !Array.isArray(value) ||\n value.length === 0 ||\n value.some((item) => typeof item !== \"string\" || item.trim() === \"\")\n ) {\n throw new Error(\n `Invalid bundle catalog entry for ${bundleName}: ${fieldName} must be a non-empty string array.`,\n );\n }\n\n return value.map((item) => item.trim());\n};\n\nconst readBundleCatalogEntry = (\n value: unknown,\n bundleName: ZuoraBundleName,\n): BundleCatalogEntry => {\n if (!isRecord(value)) {\n throw new Error(`Missing bundle catalog entry for ${bundleName}.`);\n }\n\n const description = value.description;\n\n if (typeof description !== \"string\" || description.trim() === \"\") {\n throw new Error(\n `Invalid bundle catalog entry for ${bundleName}: description is required.`,\n );\n }\n\n return {\n description: description.trim(),\n examples: readStringArrayField(value, \"examples\", bundleName),\n commonObjects: readStringArrayField(value, \"commonObjects\", bundleName),\n keywords: readStringArrayField(value, \"keywords\", bundleName),\n };\n};\n\nconst loadBundleCatalog = (): BundleCatalog => {\n const raw = bundleCatalog as unknown;\n\n if (!isRecord(raw)) {\n throw new Error(\"Invalid bundle catalog: expected an object.\");\n }\n\n const rawBundles = raw.bundles;\n\n if (!isRecord(rawBundles)) {\n throw new Error(\"Invalid bundle catalog: expected a bundles object.\");\n }\n\n const version = raw.version;\n\n if (typeof version !== \"number\" || !Number.isInteger(version)) {\n throw new Error(\"Invalid bundle catalog: version must be an integer.\");\n }\n\n const bundles = Object.fromEntries(\n ZUORA_BUNDLE_NAMES.map((name) => [\n name,\n readBundleCatalogEntry(rawBundles[name], name),\n ]),\n ) as Record<ZuoraBundleName, BundleCatalogEntry>;\n\n return {\n version,\n bundles,\n };\n};\n\nexport const BUNDLE_CATALOG: BundleCatalog = loadBundleCatalog();\n\nexport const ZUORA_BUNDLE_NAME_SET = new Set<ZuoraBundleName>(ZUORA_BUNDLE_NAMES);\n\nexport const hasBundleDefinition = (\n name: string,\n): name is ZuoraBundleName => ZUORA_BUNDLE_NAME_SET.has(name as ZuoraBundleName);\n\nexport const getBundleDefinition = (\n name: ZuoraBundleName,\n tools: ZuoraToolDefinition[] = [],\n): ZuoraBundleDefinition => {\n const metadata = BUNDLE_CATALOG.bundles[name];\n const bundleTools = tools.filter((tool) => tool.meta.bundle === name);\n const description = metadata?.description ?? name;\n const examples = metadata ? [...metadata.examples] : [];\n const commonObjects = metadata ? [...metadata.commonObjects] : [];\n const keywords = metadata ? [...metadata.keywords] : [];\n\n return {\n name,\n description,\n examples,\n commonObjects,\n keywords,\n toolCount: bundleTools.length,\n readToolCount: bundleTools.filter((tool) => tool.meta.access === \"read\")\n .length,\n writeToolCount: bundleTools.filter((tool) => tool.meta.access === \"write\")\n .length,\n tools: bundleTools,\n };\n};\n","export const nonEmptyString = (value: unknown): string | undefined =>\n typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nexport const normalizeIdList = (value: unknown): string[] | undefined => {\n const values = Array.isArray(value)\n ? value\n : typeof value === \"string\"\n ? value.split(\",\")\n : [];\n const normalized = values.flatMap((entry) => {\n const normalizedEntry = nonEmptyString(entry);\n return normalizedEntry ? [normalizedEntry] : [];\n });\n\n return normalized.length > 0 ? normalized : undefined;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/shared/errors.ts","../src/shared/tools/native/query-result-download/helpers/index.ts","../src/shared/tools/native/zuora-file-download/helpers/index.ts","../src/shared/agent-tool-follow-ups.ts","../src/shared/types.ts","../src/shared/utils/adapter-name.ts","../src/shared/async-jobs.ts","../src/shared/bundles.ts","../src/shared/utils/values.ts"],"sourcesContent":["export type ZuoraAgentToolkitP0ErrorCode =\n | \"TOOL_EXECUTION_ERROR\"\n | \"TOOL_NOT_FOUND\"\n | \"BUNDLE_NOT_FOUND\"\n | \"APPROVAL_REJECTED\"\n | \"MALFORMED_METADATA\"\n | \"OAUTH_TOKEN_ERROR\"\n | \"ASYNC_JOB_TIMEOUT\";\n\nexport type ZuoraAgentToolkitErrorCode =\n | ZuoraAgentToolkitP0ErrorCode\n | (string & {});\n\nexport type ZuoraAgentToolkitP0ErrorCategory =\n | \"tool_execution\"\n | \"approval\"\n | \"metadata\"\n | \"auth\";\n\nexport type ZuoraAgentToolkitErrorCategory =\n | ZuoraAgentToolkitP0ErrorCategory\n | \"permission\"\n | \"validation\"\n | \"zuora\"\n | \"mcp\"\n | \"configuration\"\n | \"native_file_tools\";\n\nexport class ZuoraAgentToolkitError extends Error {\n readonly code: ZuoraAgentToolkitErrorCode;\n\n readonly category: ZuoraAgentToolkitErrorCategory;\n\n readonly retryable: boolean;\n\n readonly recoverable: boolean;\n\n readonly requestId?: string | undefined;\n\n readonly details?: unknown;\n\n constructor(params: {\n code: ZuoraAgentToolkitErrorCode;\n message: string;\n category: ZuoraAgentToolkitErrorCategory;\n retryable?: boolean;\n recoverable?: boolean;\n requestId?: string;\n details?: unknown;\n }) {\n super(params.message);\n this.name = \"ZuoraAgentToolkitError\";\n this.code = params.code;\n this.category = params.category;\n this.recoverable = params.recoverable ?? params.retryable ?? false;\n this.retryable = params.retryable ?? false;\n this.requestId = params.requestId;\n this.details = params.details;\n }\n}\n","import { ZuoraAgentToolkitError } from \"../../../../errors.js\";\n\nexport const readJobId = (value: unknown): string => {\n if (typeof value === \"string\" && value.trim()) {\n return value.trim();\n }\n\n throw new ZuoraAgentToolkitError({\n code: \"QUERY_RESULT_JOB_ID_INVALID\",\n message: \"download_query_result requires a non-empty jobId.\",\n category: \"validation\",\n recoverable: true,\n });\n};\n\nexport const findDownloadUrl = (value: unknown): string | undefined =>\n findStringByKey(value, (key, candidate) => {\n if (!DOWNLOAD_URL_KEY_PATTERN.test(key)) {\n return false;\n }\n\n return isHttpUrl(candidate);\n });\n\nexport const findQueryStatus = (value: unknown): string | undefined =>\n findStringByKey(value, (key) => /^(queryStatus|status)$/i.test(key));\n\nconst findStringByKey = (\n value: unknown,\n predicate: (key: string, value: string) => boolean,\n depth = 0,\n): string | undefined => {\n if (depth > 8 || typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n const found = findStringByKey(item, predicate, depth + 1);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n }\n\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (typeof item === \"string\" && predicate(key, item)) {\n return item;\n }\n\n const found = findStringByKey(item, predicate, depth + 1);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\nconst isHttpUrl = (value: string): boolean => {\n try {\n const url = new URL(value);\n\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n};\n\nconst DOWNLOAD_URL_KEY_PATTERN =\n /^(dataFile|resultFile|downloadFile|fileUrl|downloadUrl|resultUrl|href)$/i;\n","import { ZuoraAgentToolkitError } from \"../../../../errors.js\";\n\nconst ZUORA_FILE_RESOURCE_URI_PATTERN =\n /^zuora:\\/\\/files\\/([A-Za-z0-9]+)$/i;\n\nexport const readZuoraFileId = (\n input: Record<string, unknown>,\n): string => {\n const fileId = typeof input.fileId === \"string\" ? input.fileId.trim() : \"\";\n const resourceUri =\n typeof input.resourceUri === \"string\" ? input.resourceUri.trim() : \"\";\n\n if (fileId) {\n return fileId;\n }\n\n const parsedFileId = parseZuoraFileResourceUri(resourceUri);\n\n if (parsedFileId) {\n return parsedFileId;\n }\n\n throw new ZuoraAgentToolkitError({\n code: \"INVALID_INPUT\",\n message:\n \"download_zuora_file requires fileId or resourceUri zuora://files/{fileId}.\",\n category: \"validation\",\n recoverable: true,\n });\n};\n\nexport const parseZuoraFileResourceUri = (\n resourceUri: string,\n): string | undefined => {\n const match = resourceUri.match(ZUORA_FILE_RESOURCE_URI_PATTERN);\n return match?.[1];\n};\n\nexport const resolveZuoraFileResource = (\n value: unknown,\n keyPath: string[] = [],\n depth = 0,\n): { fileId: string; resourceUri: string } | undefined => {\n if (depth > 6) {\n return undefined;\n }\n\n if (typeof value === \"string\") {\n const fileId = parseZuoraFileResourceUri(value);\n\n if (fileId && keyPath.at(-1) === \"resourceUri\") {\n return { fileId, resourceUri: value };\n }\n\n return undefined;\n }\n\n if (Array.isArray(value)) {\n for (const [index, item] of value.entries()) {\n const resolved = resolveZuoraFileResource(item, [\n ...keyPath,\n String(index),\n ], depth + 1);\n\n if (resolved) {\n return resolved;\n }\n }\n\n return undefined;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n const resourceUri = record.resourceUri;\n\n if (typeof resourceUri === \"string\") {\n const fileId = parseZuoraFileResourceUri(resourceUri);\n\n if (fileId) {\n return { fileId, resourceUri };\n }\n }\n\n const explicitFileId =\n typeof record.fileId === \"string\" ? record.fileId.trim() : \"\";\n\n if (explicitFileId && typeof resourceUri === \"string\") {\n const parsed = parseZuoraFileResourceUri(resourceUri);\n\n if (parsed === explicitFileId) {\n return { fileId: explicitFileId, resourceUri };\n }\n }\n\n for (const [key, item] of Object.entries(record)) {\n const resolved = resolveZuoraFileResource(item, [...keyPath, key], depth + 1);\n\n if (resolved) {\n return resolved;\n }\n }\n\n return undefined;\n};\n\nexport const defaultZuoraFileName = (\n fileId: string,\n contentType?: string,\n): string => {\n if (contentType?.includes(\"pdf\")) {\n return `${fileId}.pdf`;\n }\n\n if (contentType?.includes(\"json\")) {\n return `${fileId}.json`;\n }\n\n return `${fileId}.bin`;\n};\n","import { findDownloadUrl } from \"./tools/native/query-result-download/helpers/index.js\";\nimport { resolveZuoraFileResource } from \"./tools/native/zuora-file-download/helpers/index.js\";\nimport type { ZuoraToolExecutionResult } from \"./types.js\";\n\nexport const QUERY_RESULT_DOWNLOAD_TOOL = \"download_query_result\";\nexport const ZUORA_FILE_DOWNLOAD_TOOL = \"download_zuora_file\";\nexport const READ_DOWNLOADED_FILE_TOOL = \"read_downloaded_file\";\n\nexport type AgentToolFollowUpContext = {\n /** Job id from a pending async job, when the caller already resolved it. */\n asyncJobId?: string;\n};\n\nexport type AgentToolFollowUpResolution = {\n followUpToolNames: string[];\n requireToolOnNextStep: boolean;\n recommendedNextTool?: string;\n recommendedNextArgs?: Record<string, unknown>;\n hasDownloadableOutput: boolean;\n};\n\nexport const resolveAgentToolFollowUps = (\n result: ZuoraToolExecutionResult,\n context: AgentToolFollowUpContext = {},\n): AgentToolFollowUpResolution => {\n const followUpToolNames: string[] = [];\n const data = result.data;\n const jobId = context.asyncJobId ?? findQueryJobId(data);\n const zuoraFile = resolveZuoraFileResource(data);\n const hasDownloadUrl = findDownloadUrl(data) !== undefined;\n const artifactId = findDownloadArtifactId(data);\n\n let recommendedNextTool: string | undefined;\n let recommendedNextArgs: Record<string, unknown> | undefined;\n\n if (zuoraFile) {\n followUpToolNames.push(ZUORA_FILE_DOWNLOAD_TOOL);\n recommendedNextTool = ZUORA_FILE_DOWNLOAD_TOOL;\n recommendedNextArgs = { fileId: zuoraFile.fileId };\n } else if (hasDownloadUrl && jobId) {\n // A download URL is present in the result, and we have a jobId to pass to\n // the download tool. Note: the download URL itself is not included in\n // recommendedNextArgs — the download_query_result tool resolves the URL\n // internally from the jobId via a status-check call. If a download URL is\n // present but no jobId can be resolved (no jobId/queryJobId field in the\n // payload), this branch is skipped and no follow-up is emitted; the caller\n // is expected to supply asyncJobId via context in that case.\n followUpToolNames.push(QUERY_RESULT_DOWNLOAD_TOOL, READ_DOWNLOADED_FILE_TOOL);\n recommendedNextTool = QUERY_RESULT_DOWNLOAD_TOOL;\n recommendedNextArgs = { jobId };\n } else if (\n artifactId &&\n (result.toolName === QUERY_RESULT_DOWNLOAD_TOOL ||\n result.toolName === \"download_file\" ||\n result.toolName === ZUORA_FILE_DOWNLOAD_TOOL)\n ) {\n followUpToolNames.push(READ_DOWNLOADED_FILE_TOOL);\n recommendedNextTool = READ_DOWNLOADED_FILE_TOOL;\n recommendedNextArgs = { artifactId };\n }\n\n const uniqueFollowUps = [...new Set(followUpToolNames)];\n\n return {\n followUpToolNames: uniqueFollowUps,\n requireToolOnNextStep: uniqueFollowUps.length > 0,\n ...(recommendedNextTool ? { recommendedNextTool } : {}),\n ...(recommendedNextArgs ? { recommendedNextArgs } : {}),\n hasDownloadableOutput: hasDownloadUrl || zuoraFile !== undefined,\n };\n};\n\nexport const buildToolMessagePayload = (\n result: ZuoraToolExecutionResult,\n followUp: AgentToolFollowUpResolution,\n): Record<string, unknown> => {\n const baseData =\n typeof result.data === \"object\" && result.data !== null\n ? (result.data as Record<string, unknown>)\n : { value: result.data };\n\n if (!followUp.recommendedNextTool) {\n return baseData;\n }\n\n return {\n ...baseData,\n hasDownloadableOutput: followUp.hasDownloadableOutput,\n recommendedNextTool: followUp.recommendedNextTool,\n ...(followUp.recommendedNextArgs\n ? { recommendedNextArgs: followUp.recommendedNextArgs }\n : {}),\n };\n};\n\nconst findQueryJobId = (value: unknown): string | undefined => {\n const direct = readNonEmptyString(value);\n\n if (direct) {\n return direct;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n const found = findQueryJobId(item);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n\n // Check specific job-ID field names. \"id\" is intentionally checked last and\n // only at the top level of the direct object (not recursed into nested\n // objects), because it is too generic and would produce false positives on\n // large MCP responses that contain unrelated \"id\" fields deeper in the tree.\n for (const key of [\"jobId\", \"queryJobId\"]) {\n const candidate = readNonEmptyString(record[key]);\n\n if (candidate) {\n return candidate;\n }\n }\n\n // Recurse into nested objects to find jobId / queryJobId, but do NOT carry\n // the generic \"id\" key through recursion to avoid matching unrelated fields.\n for (const item of Object.values(record)) {\n const found = findQueryJobId(item);\n\n if (found) {\n return found;\n }\n }\n\n // Check \"id\" only at this level (after recursion), as a last resort for\n // responses that use a bare \"id\" field at the top of the data structure.\n const idCandidate = readNonEmptyString(record[\"id\"]);\n\n if (idCandidate) {\n return idCandidate;\n }\n\n return undefined;\n};\n\nconst findDownloadArtifactId = (value: unknown): string | undefined => {\n if (typeof value !== \"object\" || value === null) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n const direct = readNonEmptyString(record[\"artifactId\"]);\n\n if (direct) {\n return direct;\n }\n\n const download = record[\"download\"];\n\n if (typeof download === \"object\" && download !== null) {\n const nested = readNonEmptyString(\n (download as Record<string, unknown>)[\"artifactId\"],\n );\n\n if (nested) {\n return nested;\n }\n }\n\n for (const item of Object.values(record)) {\n if (item === record[\"download\"]) {\n continue;\n }\n\n const found = findDownloadArtifactId(item);\n\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\nconst readNonEmptyString = (value: unknown): string | undefined => {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n","import type { McpReadResourceResult, McpTool } from \"./transport/mcp-types.js\";\nimport type { EventSanitizerLimits } from \"./utils/event-sanitizer.js\";\n\nexport type { McpReadResourceResult, McpTool } from \"./transport/mcp-types.js\";\nexport type { EventSanitizerLimits } from \"./utils/event-sanitizer.js\";\n\nexport type ZuoraEnvironment = \"sandbox\" | \"production\";\n\nexport const SafetyMode = {\n ReadOnly: \"readOnly\",\n WriteEnabled: \"writeEnabled\",\n} as const;\n\nexport type ZuoraSafetyMode = (typeof SafetyMode)[keyof typeof SafetyMode];\n\nexport type ZuoraToolOperation = \"read\" | \"write\";\n\nexport type ZuoraRiskLevel = \"low\" | \"medium\" | \"high\";\n\n/**\n * Well-known Zuora bundle names. Provided for autocompletion in\n * `ToolSelection.bundles` and `ZuoraToolkitConfiguration.bundles`.\n *\n * The `string & {}` tail widens the type to accept any string (so new\n * server-side bundles are never rejected) while preserving IDE autocomplete\n * for the known literal members.\n *\n * `ZuoraToolDefinition.meta.bundle` is plain `string` — the server value\n * is passed through as-is with no normalisation or fallback.\n */\nexport type ZuoraBundleName =\n | \"accounts\"\n | \"catalog\"\n | \"subscriptions\"\n | \"orders\"\n | \"billing\"\n | \"payments\"\n | \"usage\"\n | \"revenue\"\n | \"finance\"\n | \"workflows\"\n | \"query\"\n | \"admin\"\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n | (string & {});\n\n// ── Async operation metadata — directly from _meta.asyncOperations[op] ──\nexport type ZuoraAsyncOperationMeta = {\n /** Operation on the polling MCP tool. Omitted when pollingTool is the standalone poll target. */\n pollingOperation?: string;\n /** Source MCP tool for cross-tool polling, or standalone poll tool when pollingOperation is omitted. */\n pollingTool?: string;\n jobIdPath: string;\n statusPath: string;\n pollingArgs?: Record<string, string>;\n terminalStatuses: {\n success: string[];\n failure: string[];\n canceled?: string[];\n };\n recommendedPollIntervalMs?: number;\n defaultTimeoutMs?: number;\n maxPollAttempts?: number;\n};\n\n// ── Service permission — directly from _meta.requiredServicePermissions[] ──\nexport type ZuoraServicePermission = {\n scope: string;\n name: string;\n};\n\n// ── The only public tool type ──\nexport type ZuoraToolDefinition = {\n name: string; // operation name, e.g. \"run_reports\"\n description: string;\n inputSchema: JsonSchema;\n outputSchema?: JsonSchema;\n meta: {\n bundle: string; // from _meta.bundle or SDK-native tool config; passed through as-is\n access: ZuoraToolOperation; // from _meta.operationPermissions[op]\n riskLevel: ZuoraRiskLevel; // from _meta.riskLevels[op]\n asyncOperation?: ZuoraAsyncOperationMeta; // from _meta.asyncOperations[op] — set on split/single-op tools\n /** Present on multi-op merged tools (splitOperations: false). Keyed by operation name. Used at\n * call time to look up the async meta for the operation the LLM actually invoked. */\n asyncOperations?: Record<string, ZuoraAsyncOperationMeta>;\n /** Per-operation access levels for merged tools. Used at call time to enforce the correct access for the invoked operation. */\n operationAccess?: Record<string, ZuoraToolOperation>;\n /** Per-operation risk levels for merged tools. Used at call time to enforce the correct risk for the invoked operation. */\n operationRiskLevel?: Record<string, ZuoraRiskLevel>;\n requiredServicePermissions: ZuoraServicePermission[]; // from _meta.requiredServicePermissions\n limitations: string[];\n sourceName: string; // original MCP tool name for execution routing\n /** Present on split multi-operation tools — MCP input field auto-injected at call time. */\n operationSelector?: string;\n /** Present on split multi-operation tools — the underlying MCP operation name. */\n operationName?: string;\n };\n};\n\n// ── Native file tools — config ──\n\nexport type NativeToolMetadataConfig = {\n enabled?: boolean;\n operationAccess?: ZuoraToolOperation;\n riskLevel?: ZuoraRiskLevel;\n description?: string;\n};\n\nexport type NativeFileToolsConfig = {\n enabled?: boolean;\n bundleName?: string;\n tools?: {\n downloadFile?: NativeToolMetadataConfig;\n readDownloadedFile?: NativeToolMetadataConfig;\n downloadQueryResult?: NativeToolMetadataConfig;\n downloadZuoraFile?: NativeToolMetadataConfig;\n extractDownloadedArchive?: NativeToolMetadataConfig;\n readResource?: NativeToolMetadataConfig;\n };\n rootDir?: string;\n userHomeDir?: string;\n requireRootUnderUserHome?: boolean;\n allowOverwrite?: boolean;\n maxDownloadBytes?: number;\n maxReadBytes?: number;\n maxReadLines?: number;\n maxExtractedFiles?: number;\n maxExtractedBytes?: number;\n downloadTimeoutMs?: number;\n allowedProtocols?: string[];\n allowedHosts?: string[];\n allowedUrlPrefixes?: string[];\n zuoraDownloadHosts?: string[];\n allowZuoraAuthenticatedDownload?: boolean;\n allowS3PresignedDownload?: boolean;\n allowArchiveExtraction?: boolean;\n};\n\n// ── Native tool approval metadata ──\n\nexport type NativeFileApprovalMetadata = {\n source: \"native\";\n nativeToolType: \"fileArtifacts\";\n filesystemRoot: string;\n sideEffects: Array<\"networkRead\" | \"localFileWrite\" | \"localFileRead\">;\n urlHost?: string;\n urlProtocol?: string;\n urlPath?: string;\n urlKind?: string;\n outputRelativePath?: string;\n artifactId?: string;\n relativePath?: string;\n};\n\nexport type NativeToolApprovalMetadata = NativeFileApprovalMetadata;\n\n// ── Bundle types — used by PR-3 ToolkitCore ──\nexport type ToolkitBundleName = string;\n\nexport type ZuoraBundleDefinition = {\n name: ToolkitBundleName;\n description: string;\n examples: string[];\n commonObjects: string[];\n keywords: string[];\n toolCount: number;\n readToolCount: number;\n writeToolCount: number;\n tools: ZuoraToolDefinition[];\n};\n\nexport type JsonSchema = {\n type?: string;\n properties?: Record<string, JsonSchema>;\n items?: JsonSchema;\n required?: string[];\n enum?: string[];\n description?: string;\n additionalProperties?: boolean | JsonSchema;\n [key: string]: unknown;\n};\n\nexport type ZuoraAuthConfig = {\n clientId: string;\n clientSecret: string;\n};\n\ntype ZuoraTenantAuthConfig =\n | {\n enabled: true;\n tenantId: string;\n tokenId: string;\n userId: string;\n }\n | {\n enabled?: false;\n };\n\ntype EnabledZuoraTenantAuthConfig = Extract<\n ZuoraTenantAuthConfig,\n { enabled: true }\n>;\n\nexport type BundleConfiguration = {\n include?: string[];\n exclude?: string[];\n};\n\nexport type ZuoraToolkitConfiguration = {\n mode?: ZuoraSafetyMode;\n bundles?: Partial<Record<ZuoraBundleName, boolean | BundleConfiguration>>;\n tools?: Record<string, boolean>;\n context?: Record<string, string | undefined>;\n // Controls whether multi-operation MCP tools are split into one tool per operation.\n // Default: true. When false, multi-op tools are presented as a single tool.\n splitOperations?: boolean;\n};\n\nexport type ToolSelection = {\n bundles?: ZuoraBundleName[];\n include?: string[];\n exclude?: string[];\n mode?: ZuoraSafetyMode;\n};\n\nexport type ZuoraExecutionRequestMetadata = {\n requestId?: string;\n zuoraRequestId?: string;\n environment?: ZuoraEnvironment;\n tenantId?: string;\n entityIds?: string[];\n orgIds?: string[];\n};\n\nexport type ZuoraToolExecutionResult<TData = unknown> = {\n toolName: string;\n data: TData;\n riskLevel?: ZuoraRiskLevel;\n requestId?: string;\n request?: ZuoraExecutionRequestMetadata;\n warnings?: string[];\n};\n\nexport type ZuoraMcpClient = {\n initialize?: () => Promise<void>;\n listTools: () => Promise<McpTool[]>;\n callTool: (\n name: string,\n input: Record<string, unknown>,\n ) => Promise<ZuoraToolExecutionResult>;\n readResource?: (uri: string) => Promise<McpReadResourceResult>;\n getTenantId?: () => string | undefined;\n getSensitiveValues?: () => readonly string[];\n close?: () => Promise<void>;\n};\n\ntype ZuoraConnectionConfigBase = {\n headers?: Record<string, string | undefined>;\n entityIds?: string | string[];\n orgIds?: string | string[];\n zuoraVersion?: string;\n protocolVersion?: string;\n};\n\ntype ZuoraTenantAuthConnectionConfig = ZuoraConnectionConfigBase & {\n tenantAuth: EnabledZuoraTenantAuthConfig;\n};\n\ntype ZuoraStandardConnectionConfig = ZuoraConnectionConfigBase & {\n tenantAuth?: Exclude<ZuoraTenantAuthConfig, EnabledZuoraTenantAuthConfig>;\n};\n\nexport type ZuoraConnectionConfig =\n | ZuoraStandardConnectionConfig\n | ZuoraTenantAuthConnectionConfig;\n\ntype ZuoraHttpMcpClientConfigBase = ZuoraConnectionConfigBase & {\n url: string;\n clientName?: string;\n clientVersion?: string;\n};\n\nexport type ZuoraHttpMcpClientConfig =\n | (ZuoraHttpMcpClientConfigBase &\n ZuoraStandardConnectionConfig & {\n auth: ZuoraAuthConfig;\n })\n | (ZuoraHttpMcpClientConfigBase &\n ZuoraTenantAuthConnectionConfig & {\n auth?: ZuoraAuthConfig;\n });\n\nexport const ApprovalPolicyPreset = {\n Off: \"off\",\n AllWrites: \"all_writes\",\n HighRisk: \"high_risk\",\n ProductionWrites: \"production_writes\",\n ProductionHighRisk: \"production_high_risk\",\n} as const;\n\nexport type ApprovalPolicyPresetValue =\n (typeof ApprovalPolicyPreset)[keyof typeof ApprovalPolicyPreset];\n\nexport type ApprovalPolicyResult =\n | \"allow\"\n | \"deny\"\n | { decision: \"allow\" | \"deny\"; reason?: string };\n\nexport type ApprovalPolicyHandler = (\n request: ApprovalRequest,\n) => ApprovalPolicyResult | Promise<ApprovalPolicyResult>;\n\nexport type ApprovalPolicy = ApprovalPolicyPresetValue | ApprovalPolicyHandler;\n\nexport type ApprovalRequest = {\n tool: ZuoraToolDefinition;\n input: Record<string, unknown>;\n environment: ZuoraEnvironment;\n /** Present only for SDK-native tools (e.g. download_file). Undefined for MCP-sourced tools. */\n native?: NativeToolApprovalMetadata;\n};\n\n// ── Observability event types ──\n\nexport type ZuoraToolkitEventName =\n | \"tool.called\"\n | \"tool.completed\"\n | \"tool.failed\"\n | \"approval.requested\"\n /**\n * Internal name used when emitting the approval outcome from\n * `enforceApproval()`. It is normalised to the `\"approval.decided\"` type\n * by `normalizeToolkitEvent()` before being delivered to observers.\n * @see ZuoraToolkitEventType\n */\n | \"approval.resolved\"\n | \"approval.decided\"\n | \"metadata.loaded\"\n | \"async.poll.checked\"\n | \"observability.sink_failed\"\n /**\n * Emitted by framework adapters (e.g. the Agents SDK adapter) after\n * `execute()` finishes post-processing the raw MCP result. The `metadata`\n * field carries the actual response the LLM receives, which may differ from\n * the raw `callTool` output (e.g. async-job directives, download hints).\n * Consumers can subscribe to this event to audit what the LLM was told.\n */\n | \"agent.tool.response\";\n\n/**\n * The canonical event type delivered to `onEvent` / `emit` sinks.\n *\n * Note the asymmetry with `ZuoraToolkitEventName`: the internal name\n * `\"approval.resolved\"` is normalised to `\"approval.decided\"` before\n * delivery. Consumers should match on `type`, not `name`, when they need\n * a stable identifier for approval-outcome events.\n */\nexport type ZuoraToolkitEventType =\n | \"tool.called\"\n | \"tool.completed\"\n | \"tool.failed\"\n | \"approval.requested\"\n | \"approval.decided\"\n | \"metadata.loaded\"\n | \"async.poll.checked\"\n | \"observability.sink_failed\"\n | \"agent.tool.response\";\n\nexport type ZuoraToolkitApprovalDecision =\n | \"allow\"\n | \"deny\";\n\nexport type ZuoraToolkitApprovalStatus =\n | \"requested\"\n | \"rejected\"\n | \"not_required\";\n\nexport type ZuoraToolkitEventError = {\n code: string;\n category: string;\n recoverable: boolean;\n retryable?: boolean;\n requestId?: string;\n message?: string;\n};\n\nexport type ZuoraToolExecutionPhase = \"primary\" | \"async_poll\";\n\nexport type ZuoraToolkitEvent = {\n type: ZuoraToolkitEventType;\n name: ZuoraToolkitEventName;\n timestamp: string;\n toolName?: string;\n access?: ZuoraToolOperation;\n bundle?: string;\n executionPhase?: ZuoraToolExecutionPhase;\n input?: Record<string, unknown>;\n output?: Record<string, unknown>;\n requestId?: string;\n zuoraRequestId?: string;\n riskLevel?: ZuoraRiskLevel;\n latencyMs?: number;\n environment?: ZuoraEnvironment;\n tenantId?: string;\n entityIds?: string[];\n orgIds?: string[];\n approvalDecision?: ZuoraToolkitApprovalDecision;\n approvalStatus?: ZuoraToolkitApprovalStatus;\n error?: ZuoraToolkitEventError;\n metadata?: Record<string, unknown>;\n};\n\nexport type ZuoraToolkitLogger = {\n debug?: (message: string, event?: ZuoraToolkitEvent) => void;\n info?: (message: string, event?: ZuoraToolkitEvent) => void;\n warn?: (message: string, event?: ZuoraToolkitEvent) => void;\n error?: (message: string, event?: ZuoraToolkitEvent) => void;\n};\n\ntype ZuoraAgentToolkitConfigBase = {\n baseUrl: string;\n configuration?: ZuoraToolkitConfiguration;\n approvalPolicy?: ApprovalPolicy;\n /**\n * SDK-internal overrides for native file download/read infrastructure.\n * Auto-enabled when the query bundle is configured — agents never set this.\n */\n nativeFileTools?: NativeFileToolsConfig;\n observability?: {\n logger?: ZuoraToolkitLogger;\n emit?: (event: ZuoraToolkitEvent) => void | Promise<void>;\n onEvent?: (event: ZuoraToolkitEvent) => void;\n /**\n * When true, tool.called/tool.completed/tool.failed events include\n * sanitized input and output payloads. Defaults to false.\n */\n includePayloads?: boolean;\n /**\n * Limits applied when sanitizing event metadata fields.\n * Use `resolveEventSanitizerLimits(process.env)` to populate from\n * environment variables, or supply explicit values.\n * Defaults to built-in conservative limits when omitted.\n */\n sanitizerLimits?: EventSanitizerLimits;\n };\n};\n\nexport type ZuoraAgentToolkitEnvSource =\n | true\n | {\n env?: Record<string, string | undefined>;\n };\n\nexport type ZuoraAgentToolkitEnvConfig = Omit<\n ZuoraAgentToolkitConfigBase,\n \"baseUrl\"\n> & {\n env: ZuoraAgentToolkitEnvSource;\n};\n\nexport type ZuoraAgentToolkitConfig =\n | (ZuoraAgentToolkitConfigBase & {\n auth: ZuoraAuthConfig;\n })\n | (ZuoraAgentToolkitConfigBase & {\n auth?: ZuoraAuthConfig;\n connection: ZuoraTenantAuthConnectionConfig;\n });\n\nexport type ZuoraAgentToolkitConfigInput =\n | ZuoraAgentToolkitConfig\n | ZuoraAgentToolkitEnvConfig;\n\n// ── Async job polling types ──\n\n/**\n * Terminal state of a completed async job.\n */\nexport type ZuoraAsyncJobTerminalState = \"success\" | \"failure\" | \"canceled\";\n\n/**\n * Configuration for which status strings are considered terminal for each outcome.\n */\nexport type ZuoraAsyncJobTerminalStatuses = {\n success: string[];\n failure: string[];\n canceled?: string[];\n};\n\n/**\n * Rich resolution of the current async job status returned by resolveAsyncJobStatusDetails.\n */\nexport type ZuoraAsyncJobStatusResolution = {\n /** Raw status string extracted from the tool result, if any. */\n status: string | undefined;\n /** True if the job has reached a terminal state. */\n isTerminal: boolean;\n /** The resolved terminal state, or undefined if not yet terminal. */\n terminalState: ZuoraAsyncJobTerminalState | undefined;\n};\n\n/**\n * Options for waitForJob() — all fields override the async meta defaults.\n */\nexport type ZuoraWaitForJobOptions = {\n /**\n * Maximum number of polling attempts before giving up.\n * Overrides asyncMeta.maxPollAttempts.\n */\n maxPollAttempts?: number;\n /**\n * Interval in milliseconds between polling attempts.\n * Overrides asyncMeta.recommendedPollIntervalMs.\n */\n pollIntervalMs?: number;\n /**\n * Absolute timeout in milliseconds from when waitForJob() was called.\n * Overrides asyncMeta.defaultTimeoutMs.\n *\n * **Note:** the timeout is checked at the start of each loop iteration,\n * before the poll is issued. A value of `0` will therefore cause\n * `waitForJob` to throw `ASYNC_JOB_TIMEOUT` immediately without ever\n * making a poll attempt.\n */\n timeoutMs?: number;\n /**\n * Custom terminal status sets. When provided, overrides the terminal statuses\n * defined in the async operation meta.\n */\n terminalStatuses?: ZuoraAsyncJobTerminalStatuses;\n};\n","const MAX_ADAPTER_TOOL_NAME_LENGTH = 64;\nconst SAFE_ADAPTER_TOOL_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;\n\nexport const createOperationAdapterToolName = (\n sourceToolName: string,\n operationName: string,\n): string => {\n const rawName = `${sourceToolName}__${operationName}`;\n\n if (\n rawName.length <= MAX_ADAPTER_TOOL_NAME_LENGTH &&\n SAFE_ADAPTER_TOOL_NAME_PATTERN.test(rawName)\n ) {\n return rawName;\n }\n\n return sanitizeAdapterToolName(rawName);\n};\n\nexport const stableNameHash = (value: string): string => {\n let hash = 0x811c9dc5;\n\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193);\n }\n\n return (hash >>> 0).toString(36).padStart(7, \"0\").slice(-7);\n};\n\nexport const sanitizeAdapterToolName = (name: string): string => {\n if (\n name.length <= MAX_ADAPTER_TOOL_NAME_LENGTH &&\n SAFE_ADAPTER_TOOL_NAME_PATTERN.test(name)\n ) {\n return name;\n }\n\n const sanitized = name\n .replace(/[^A-Za-z0-9_-]+/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n\n const candidate = sanitized || \"tool\";\n\n if (\n candidate.length <= MAX_ADAPTER_TOOL_NAME_LENGTH &&\n SAFE_ADAPTER_TOOL_NAME_PATTERN.test(candidate)\n ) {\n return candidate;\n }\n\n const hash = stableNameHash(name);\n const suffix = `_${hash}`;\n const maxPrefixLength = MAX_ADAPTER_TOOL_NAME_LENGTH - suffix.length;\n const prefix = candidate.slice(0, maxPrefixLength) || \"tool\";\n\n return `${prefix}${suffix}`;\n};\n","import { ZuoraAgentToolkitError } from \"./errors.js\";\nimport { createOperationAdapterToolName } from \"./utils/adapter-name.js\";\nimport type {\n ZuoraAsyncJobStatusResolution,\n ZuoraAsyncJobTerminalState,\n ZuoraAsyncJobTerminalStatuses,\n ZuoraAsyncOperationMeta,\n ZuoraToolExecutionResult,\n} from \"./types.js\";\n\nexport type BuildAsyncJobResultContext = {\n sourceToolName: string;\n operationSelector?: string;\n /** Split operation names registered for sourceToolName. */\n sourceOperationNames?: string[];\n};\n\nexport type ZuoraAsyncJobResult = {\n toolName: string;\n status: \"pending\";\n jobId: string;\n /** Resolved toolkit tool name to call when polling or auto-waiting. */\n pollingToolName: string;\n /** Original MCP pollingOperation metadata, when present. */\n pollingOperation?: string;\n pollingArgs?: Record<string, string>;\n /** Dot-path to the status field in the poll result (e.g. \"data.status\"). Populated from asyncMeta.statusPath at job-start time. */\n statusPath?: string;\n /** Terminal status sets for this job. Populated from asyncMeta.terminalStatuses at job-start time. */\n terminalStatuses?: ZuoraAsyncJobTerminalStatuses;\n recommendedPollIntervalMs?: number;\n maxPollAttempts?: number;\n defaultTimeoutMs?: number;\n};\n\n/**\n * Default terminal statuses applied when the async operation meta does not\n * specify its own terminal status sets and no override is provided to waitForJob.\n */\nexport const DEFAULT_ASYNC_JOB_TERMINAL_STATUSES: ZuoraAsyncJobTerminalStatuses = {\n success: [\"Completed\", \"Success\", \"Succeeded\", \"Done\"],\n failure: [\"Failed\", \"Error\", \"Errored\", \"Aborted\"],\n canceled: [\"Canceled\", \"Cancelled\"],\n};\n\n/**\n * Returns `true` when `data` is a `ZuoraAsyncJobResult` — a pending async job\n * result produced by `ToolkitCore.callTool()` via `buildAsyncJobResult()`.\n *\n * The check requires `status === \"pending\"`, a non-empty `jobId` string, and\n * at least one of `pollingToolName` or `pollingOperation`. Those routing fields\n * are only present on data shaped by `buildAsyncJobResult` — plain API responses\n * that happen to contain a job-ID-like field will not pass this guard.\n */\nexport const isAsyncJobResult = (data: unknown): data is ZuoraAsyncJobResult =>\n data != null &&\n typeof data === \"object\" &&\n (data as ZuoraAsyncJobResult).status === \"pending\" &&\n typeof (data as ZuoraAsyncJobResult).jobId === \"string\" &&\n (data as ZuoraAsyncJobResult).jobId.trim().length > 0 &&\n (typeof (data as ZuoraAsyncJobResult).pollingToolName === \"string\" ||\n typeof (data as ZuoraAsyncJobResult).pollingOperation === \"string\");\n\n/**\n * Normalizes MCP JSONPath-style expressions (e.g. \"$.data.jobId\") to the\n * dot-path form used by resolveByPath (e.g. \"data.jobId\").\n */\nexport const normalizeMetadataPath = (path: string): string => {\n const trimmed = path.trim();\n if (trimmed.startsWith(\"$.\")) {\n return trimmed.slice(2);\n }\n if (trimmed.startsWith(\"$\")) {\n return trimmed.slice(1);\n }\n return trimmed;\n};\n\n/**\n * Resolves a dot-path expression (e.g. \"data.jobId\") against an object.\n * Returns undefined if any segment is missing.\n */\nfunction resolveByPath(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic dot-path traversal requires any\n obj: any,\n path: string,\n): unknown {\n return path.split(\".\").reduce(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- same reason as above\n (current: any, segment: string) =>\n current != null && typeof current === \"object\" ? current[segment] : undefined,\n obj,\n );\n}\n\n/**\n * Resolves the toolkit tool name to invoke when polling an async job.\n * Mirrors legacy `resolveAsyncJobPollingToolName()`.\n */\nexport function resolveAsyncJobPollingToolNameFromMeta(\n asyncMeta: ZuoraAsyncOperationMeta,\n context: BuildAsyncJobResultContext,\n): string {\n const pollingOperation = asyncMeta.pollingOperation;\n const pollingTool = asyncMeta.pollingTool;\n\n if (!pollingOperation) {\n return pollingTool ?? context.sourceToolName;\n }\n\n if (pollingTool) {\n return createOperationAdapterToolName(pollingTool, pollingOperation);\n }\n\n if (\n context.operationSelector &&\n context.sourceOperationNames?.includes(pollingOperation)\n ) {\n return createOperationAdapterToolName(\n context.sourceToolName,\n pollingOperation,\n );\n }\n\n // Same-source, unsplit tool: pollingOperation lives on the source tool itself\n // (splitOperations: false). Return the source tool name so callTool can find\n // it in the registry; the operation is injected into pollingArgs by\n // buildAsyncJobResult so the MCP server routes to the correct operation.\n return context.sourceToolName;\n}\n\n/**\n * Builds a structured async-pending result to return to the agent when a tool\n * call produces a job that requires polling.\n *\n * @throws ZuoraAgentToolkitError with TOOL_EXECUTION_ERROR if jobIdPath\n * resolves to undefined or null on the raw result.\n */\nexport function buildAsyncJobResult(\n result: ZuoraToolExecutionResult,\n asyncMeta: ZuoraAsyncOperationMeta,\n context: BuildAsyncJobResultContext,\n): ZuoraAsyncJobResult {\n const payload = result.data;\n // asyncMeta.jobIdPath is already normalized at parse time by parseAsyncOperationMeta.\n const jobId = resolveByPath(payload, asyncMeta.jobIdPath);\n\n if (jobId == null) {\n throw new ZuoraAgentToolkitError({\n code: \"TOOL_EXECUTION_ERROR\",\n message: `Async job ID not found at path \"${asyncMeta.jobIdPath}\" in tool result for \"${result.toolName}\".`,\n category: \"tool_execution\",\n });\n }\n\n const pollingToolName = resolveAsyncJobPollingToolNameFromMeta(\n asyncMeta,\n context,\n );\n\n const pending: ZuoraAsyncJobResult = {\n toolName: result.toolName,\n status: \"pending\",\n jobId: String(jobId),\n pollingToolName,\n ...(asyncMeta.pollingOperation\n ? { pollingOperation: asyncMeta.pollingOperation }\n : {}),\n statusPath: asyncMeta.statusPath,\n terminalStatuses: asyncMeta.terminalStatuses,\n };\n\n // For same-source unsplit tools (pollingTool absent, no operationSelector),\n // the polling call targets the merged source tool, so the operation name must\n // be included in pollingArgs for the MCP server to route it correctly.\n const needsOperationInjection =\n !!asyncMeta.pollingOperation &&\n !asyncMeta.pollingTool &&\n !context.operationSelector;\n\n if (needsOperationInjection) {\n pending.pollingArgs = {\n ...asyncMeta.pollingArgs,\n operation: asyncMeta.pollingOperation!,\n };\n } else if (asyncMeta.pollingArgs !== undefined) {\n pending.pollingArgs = asyncMeta.pollingArgs;\n }\n\n if (asyncMeta.recommendedPollIntervalMs !== undefined) {\n pending.recommendedPollIntervalMs = asyncMeta.recommendedPollIntervalMs;\n }\n\n if (asyncMeta.maxPollAttempts !== undefined) {\n pending.maxPollAttempts = asyncMeta.maxPollAttempts;\n }\n\n if (asyncMeta.defaultTimeoutMs !== undefined) {\n pending.defaultTimeoutMs = asyncMeta.defaultTimeoutMs;\n }\n\n return pending;\n}\n\n// ── Async job status utilities ────────────────────────────────────────────────\n\n/**\n * Returns true when the given status string matches any status in the success,\n * failure, or canceled sets of the provided terminal statuses config.\n *\n * The comparison is case-insensitive.\n */\nexport function matchesAsyncJobTerminalStatus(\n status: string,\n statuses: ZuoraAsyncJobTerminalStatuses,\n): boolean {\n const lower = status.toLowerCase();\n if (statuses.success.some((s) => s.toLowerCase() === lower)) return true;\n if (statuses.failure.some((s) => s.toLowerCase() === lower)) return true;\n if (statuses.canceled?.some((s) => s.toLowerCase() === lower)) return true;\n return false;\n}\n\n/**\n * Resolves the terminal state (\"success\" | \"failure\" | \"canceled\") for the\n * given status string against the provided terminal statuses config.\n *\n * Returns undefined when the status has not yet reached a terminal state.\n */\nexport function resolveAsyncJobTerminalState(\n status: string,\n statuses: ZuoraAsyncJobTerminalStatuses,\n): ZuoraAsyncJobTerminalState | undefined {\n const lower = status.toLowerCase();\n if (statuses.success.some((s) => s.toLowerCase() === lower)) return \"success\";\n if (statuses.failure.some((s) => s.toLowerCase() === lower)) return \"failure\";\n if (statuses.canceled?.some((s) => s.toLowerCase() === lower)) return \"canceled\";\n return undefined;\n}\n\n/**\n * Returns true when the given async job result has reached a terminal state\n * according to the provided terminal statuses config.\n *\n * The job is considered terminal only when a non-null, non-empty status string\n * is present in the result data and that status matches one of the terminal sets.\n */\nexport function isAsyncJobTerminal(\n result: ZuoraToolExecutionResult,\n statusPath: string,\n statuses: ZuoraAsyncJobTerminalStatuses,\n): boolean {\n const status = resolveAsyncJobStatus(result, statusPath);\n if (status == null || status === \"\") return false;\n return matchesAsyncJobTerminalStatus(status, statuses);\n}\n\n/**\n * Extracts the current job status string from a polling tool result.\n *\n * @param result - The result returned by the polling tool.\n * @param statusPath - Dot-path to the status field within result.data,\n * e.g. \"status\" or \"job.state\". Paths are normalized at parse time by\n * parseAsyncOperationMeta, so callers within the SDK should always pass\n * already-normalized paths here.\n * @returns The extracted status string, or undefined if not present.\n */\nexport function resolveAsyncJobStatus(\n result: ZuoraToolExecutionResult,\n statusPath: string,\n): string | undefined {\n const raw = resolveByPath(result.data, statusPath);\n if (raw == null || typeof raw !== \"string\") return undefined;\n return raw;\n}\n\n/**\n * Extracts the job ID from a ZuoraAsyncJobResult embedded inside a tool\n * execution result, or returns the jobId directly from a ZuoraAsyncJobResult.\n *\n * Supports two calling patterns:\n * - resolveAsyncJobId(asyncJobResult)\n * - resolveAsyncJobId(toolResult, jobIdPath)\n */\nexport function resolveAsyncJobId(\n result: ZuoraAsyncJobResult | ZuoraToolExecutionResult,\n jobIdPath?: string,\n): string | undefined {\n // If we have a ZuoraAsyncJobResult (has status: \"pending\")\n if (\"status\" in result && result.status === \"pending\" && \"jobId\" in result) {\n return (result as ZuoraAsyncJobResult).jobId;\n }\n\n // Otherwise treat as ZuoraToolExecutionResult and extract via path.\n // Paths sourced from asyncMeta are already normalized at parse time by\n // parseAsyncOperationMeta, so no further normalization is needed here.\n if (jobIdPath && \"data\" in result) {\n const raw = resolveByPath(result.data, jobIdPath);\n if (raw != null && typeof raw === \"string\") return raw;\n if (raw != null) return String(raw);\n }\n\n return undefined;\n}\n\n/**\n * Extracts the name of the next polling tool from a ZuoraAsyncJobResult.\n * Returns undefined if result is not a ZuoraAsyncJobResult.\n *\n * pollingToolName is always set on ZuoraAsyncJobResult (resolved at job-start\n * time by resolveAsyncJobPollingToolNameFromMeta), so it is returned directly.\n */\nexport function resolveAsyncJobPollingToolName(\n result: ZuoraAsyncJobResult | ZuoraToolExecutionResult,\n): string | undefined {\n if (\"status\" in result && result.status === \"pending\") {\n return (result as ZuoraAsyncJobResult).pollingToolName;\n }\n return undefined;\n}\n\n/**\n * Returns a rich ZuoraAsyncJobStatusResolution describing the current status\n * of an async job, including whether it has reached a terminal state and what\n * the terminal state is.\n *\n * @param result - The result returned by the polling tool.\n * @param statusPath - Dot-path to the status field (e.g. \"data.status\").\n * @param terminalStatuses - Terminal status configuration to match against.\n */\nexport function resolveAsyncJobStatusDetails(\n result: ZuoraToolExecutionResult,\n statusPath: string,\n terminalStatuses: ZuoraAsyncJobTerminalStatuses,\n): ZuoraAsyncJobStatusResolution {\n const status = resolveAsyncJobStatus(result, statusPath);\n\n if (status == null || status === \"\") {\n return { status: undefined, isTerminal: false, terminalState: undefined };\n }\n\n const terminalState = resolveAsyncJobTerminalState(status, terminalStatuses);\n const isTerminal = terminalState !== undefined;\n\n return { status, isTerminal, terminalState };\n}\n","import bundleCatalog from \"./resources/bundle-catalog.json\" with { type: \"json\" };\nimport type {\n ZuoraBundleDefinition,\n ZuoraBundleName,\n ZuoraToolDefinition,\n} from \"./types.js\";\nimport { isRecord } from \"./utils/values.js\";\n\nexport const ZUORA_BUNDLE_NAMES = [\n \"accounts\",\n \"catalog\",\n \"subscriptions\",\n \"orders\",\n \"billing\",\n \"payments\",\n \"usage\",\n \"revenue\",\n \"finance\",\n \"workflows\",\n \"query\",\n \"admin\",\n] as const satisfies readonly ZuoraBundleName[];\n\nexport type BundleCatalogEntry = {\n description: string;\n examples: string[];\n commonObjects: string[];\n keywords: string[];\n};\n\nexport type BundleCatalog = {\n version: number;\n bundles: Record<ZuoraBundleName, BundleCatalogEntry>;\n};\n\nconst readStringArrayField = (\n record: Record<string, unknown>,\n fieldName: string,\n bundleName: ZuoraBundleName,\n): string[] => {\n const value = record[fieldName];\n\n if (\n !Array.isArray(value) ||\n value.length === 0 ||\n value.some((item) => typeof item !== \"string\" || item.trim() === \"\")\n ) {\n throw new Error(\n `Invalid bundle catalog entry for ${bundleName}: ${fieldName} must be a non-empty string array.`,\n );\n }\n\n return value.map((item) => item.trim());\n};\n\nconst readBundleCatalogEntry = (\n value: unknown,\n bundleName: ZuoraBundleName,\n): BundleCatalogEntry => {\n if (!isRecord(value)) {\n throw new Error(`Missing bundle catalog entry for ${bundleName}.`);\n }\n\n const description = value.description;\n\n if (typeof description !== \"string\" || description.trim() === \"\") {\n throw new Error(\n `Invalid bundle catalog entry for ${bundleName}: description is required.`,\n );\n }\n\n return {\n description: description.trim(),\n examples: readStringArrayField(value, \"examples\", bundleName),\n commonObjects: readStringArrayField(value, \"commonObjects\", bundleName),\n keywords: readStringArrayField(value, \"keywords\", bundleName),\n };\n};\n\nconst loadBundleCatalog = (): BundleCatalog => {\n const raw = bundleCatalog as unknown;\n\n if (!isRecord(raw)) {\n throw new Error(\"Invalid bundle catalog: expected an object.\");\n }\n\n const rawBundles = raw.bundles;\n\n if (!isRecord(rawBundles)) {\n throw new Error(\"Invalid bundle catalog: expected a bundles object.\");\n }\n\n const version = raw.version;\n\n if (typeof version !== \"number\" || !Number.isInteger(version)) {\n throw new Error(\"Invalid bundle catalog: version must be an integer.\");\n }\n\n const bundles = Object.fromEntries(\n ZUORA_BUNDLE_NAMES.map((name) => [\n name,\n readBundleCatalogEntry(rawBundles[name], name),\n ]),\n ) as Record<ZuoraBundleName, BundleCatalogEntry>;\n\n return {\n version,\n bundles,\n };\n};\n\nexport const BUNDLE_CATALOG: BundleCatalog = loadBundleCatalog();\n\nexport const ZUORA_BUNDLE_NAME_SET = new Set<ZuoraBundleName>(ZUORA_BUNDLE_NAMES);\n\nexport const hasBundleDefinition = (\n name: string,\n): name is ZuoraBundleName => ZUORA_BUNDLE_NAME_SET.has(name as ZuoraBundleName);\n\nexport const getBundleDefinition = (\n name: ZuoraBundleName,\n tools: ZuoraToolDefinition[] = [],\n): ZuoraBundleDefinition => {\n const metadata = BUNDLE_CATALOG.bundles[name];\n const bundleTools = tools.filter((tool) => tool.meta.bundle === name);\n const description = metadata?.description ?? name;\n const examples = metadata ? [...metadata.examples] : [];\n const commonObjects = metadata ? [...metadata.commonObjects] : [];\n const keywords = metadata ? [...metadata.keywords] : [];\n\n return {\n name,\n description,\n examples,\n commonObjects,\n keywords,\n toolCount: bundleTools.length,\n readToolCount: bundleTools.filter((tool) => tool.meta.access === \"read\")\n .length,\n writeToolCount: bundleTools.filter((tool) => tool.meta.access === \"write\")\n .length,\n tools: bundleTools,\n };\n};\n","export const nonEmptyString = (value: unknown): string | undefined =>\n typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nexport const normalizeIdList = (value: unknown): string[] | undefined => {\n const values = Array.isArray(value)\n ? value\n : typeof value === \"string\"\n ? value.split(\",\")\n : [];\n const normalized = values.flatMap((entry) => {\n const normalizedEntry = nonEmptyString(entry);\n return normalizedEntry ? [normalizedEntry] : [];\n });\n\n return normalized.length > 0 ? normalized : undefined;\n};\n"],"mappings":"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","names":[]}
@@ -1,2 +1,53 @@
1
+ import { DynamicStructuredTool } from '@langchain/core/tools';
2
+ import { ToolkitCore } from '../shared/toolkit-core.js';
3
+ import { Z as ZuoraToolDefinition, a as ZuoraAgentToolkitConfigInput } from '../async-jobs-DJ_GygII.js';
1
4
 
2
- export { }
5
+ /**
6
+ * LangChain adapter for the Zuora Agent Toolkit.
7
+ *
8
+ * Converts Zuora MCP tools into `DynamicStructuredTool` instances that can be
9
+ * passed directly to LangChain's `createToolCallingAgent`, `AgentExecutor`,
10
+ * or any LCEL chain that accepts LangChain tools.
11
+ *
12
+ * Each tool's `func` callback:
13
+ * 1. Calls `callTool()` on the shared core (MCP execution + approval policy).
14
+ * 2. Resolves async-job / download-chain follow-up hints.
15
+ * 3. Returns a JSON string — the content of the `ToolMessage` fed back to
16
+ * the model.
17
+ *
18
+ * No `handleToolCall()` helper is exported because LangChain's `AgentExecutor`
19
+ * owns the tool-call loop and invokes each tool automatically.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { createZuoraLangChainToolkit } from "@zuora/agent-toolkit/langchain";
24
+ *
25
+ * const toolkit = await createZuoraLangChainToolkit({ env: true });
26
+ * const tools = await toolkit.getTools({ bundles: ["query"], mode: SafetyMode.ReadOnly });
27
+ * const agent = createToolCallingAgent({ llm, tools, prompt });
28
+ * const result = await new AgentExecutor({ agent, tools }).invoke({ input: "..." });
29
+ * ```
30
+ */
31
+ declare class ZuoraLangChainToolkit extends ToolkitCore<DynamicStructuredTool> {
32
+ protected convertTools(tools: ZuoraToolDefinition[]): DynamicStructuredTool[];
33
+ close(): Promise<void>;
34
+ }
35
+ /**
36
+ * Creates and initialises a {@link ZuoraLangChainToolkit}.
37
+ *
38
+ * Always use this factory — the toolkit requires async initialisation to fetch
39
+ * the tool catalogue from the Zuora MCP server.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const toolkit = await createZuoraLangChainToolkit({
44
+ * env: true,
45
+ * configuration: { mode: SafetyMode.ReadOnly },
46
+ * });
47
+ * const tools = await toolkit.getTools({ bundles: ["query"] });
48
+ * // pass tools to createToolCallingAgent or AgentExecutor
49
+ * ```
50
+ */
51
+ declare const createZuoraLangChainToolkit: (config: ZuoraAgentToolkitConfigInput) => Promise<ZuoraLangChainToolkit>;
52
+
53
+ export { ZuoraLangChainToolkit, createZuoraLangChainToolkit };