@channlize/mcp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +15 -2
- package/dist/agents.d.mts +12 -2
- package/dist/agents.d.ts +12 -2
- package/dist/agents.js +548 -0
- package/dist/agents.js.map +1 -1
- package/dist/agents.mjs +548 -0
- package/dist/agents.mjs.map +1 -1
- package/dist/inbox.js.map +1 -1
- package/dist/inbox.mjs.map +1 -1
- package/dist/index.js +554 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +554 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +566 -13
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/inbox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/inbox.ts","../src/api.ts"],"sourcesContent":["/**\n * The Channlize inbox toolset: names, zod input/output schemas and handlers\n * for working the inbox through the API-key endpoints under `/v1`.\n *\n * This module has no dependency on the MCP server or its transports, so any\n * agent can import it (`@channlize/mcp/inbox`): the stdio/HTTP server\n * registers these tools (`registerInboxTools` in `tools.ts`), and the\n * dashboard copilot or an internal bot can run the same handlers with its own\n * client (anything with a `request(method, path, { query, body })`).\n *\n * Writes act as a named agent: the API key's name. Replies show in Slack as\n * \"Replied by <key name> (agent)\", and changes as \"… by <key name> (agent)\".\n *\n * Scopes (pick them when creating the key): `inbox:read`, `inbox:write`,\n * `routing:write`, `analytics:read`. A `read` key can use the read tools.\n */\nimport * as z from 'zod/v4';\nimport { ToolError, type HttpMethod } from './api';\n\nexport type { HttpMethod };\n\ntype Json = Record<string, unknown>;\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** What a handler needs: `ChannlizeApi` from this package, or your own. */\nexport interface InboxRequester {\n request<T>(\n method: HttpMethod,\n path: string,\n options?: { query?: Query; body?: unknown }\n ): Promise<T>;\n}\n\n/** The API key scope a tool needs. `routing:read` is granted by `routing:write`, `inbox:read` or `read`. */\nexport type InboxScope =\n | 'inbox:read'\n | 'inbox:write'\n | 'routing:read'\n | 'routing:write'\n | 'analytics:read';\n\nexport interface ToolAnnotations {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface InboxTool<\n I extends z.ZodObject = z.ZodObject,\n O extends z.ZodObject = z.ZodObject,\n> {\n name: string;\n title: string;\n description: string;\n scope: InboxScope;\n /** The HTTP endpoint it calls, for docs and logs. */\n endpoint: string;\n inputSchema: I;\n outputSchema: O;\n annotations: ToolAnnotations;\n handler: (api: InboxRequester, args: z.output<I>) => Promise<z.output<O>>;\n}\n\nfunction defineTool<I extends z.ZodObject, O extends z.ZodObject>(\n tool: InboxTool<I, O>\n): InboxTool {\n return tool as unknown as InboxTool;\n}\n\n// ----- shared schemas -----\n\nexport const CONVERSATION_STATUSES = [\n 'open',\n 'pending',\n 'resolved',\n 'closed',\n] as const;\nexport const CONVERSATION_PRIORITIES = [\n 'low',\n 'normal',\n 'high',\n 'urgent',\n] as const;\nexport const CONVERSATION_SORTS = [\n 'recent',\n 'oldest',\n 'newest',\n 'priority',\n] as const;\n\nconst loose = z.looseObject({});\nconst id = (what: string) => z.string().uuid().describe(`${what} ID`);\nconst conversationId = id(\n 'Conversation (from list_conversations or search_conversations)'\n);\n\nconst person = z\n .looseObject({\n id: z.string(),\n name: z.string().nullable().optional(),\n email: z.string().nullable().optional(),\n })\n .nullable();\n\nconst conversationSummary = z.looseObject({\n id: z.string(),\n subject: z.string().nullable().optional(),\n status: z.string(),\n priority: z.string().optional(),\n source: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n customer: person.optional(),\n assignee: person.optional(),\n triage: z.record(z.string(), z.unknown()).nullable().optional(),\n needsReply: z.boolean().optional(),\n lastActivityAt: z.string().nullable().optional(),\n});\n\nconst page = {\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n hasMore: z.boolean(),\n};\n\nconst listOutput = z.object({\n conversations: z.array(conversationSummary),\n ...page,\n});\n\nconst changeOutput = z.object({\n conversation: z.looseObject({ id: z.string(), status: z.string() }),\n changes: z.array(z.string()),\n});\n\nconst slackOutcome = z\n .string()\n .describe(\n 'Slack mirror: queued, posted, no_thread (not in Slack), not_connected or failed'\n );\n\nconst tags = z.array(z.string().trim().min(1).max(50)).max(20);\n\nconst paging = {\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .default(20)\n .describe('How many to return (1-100, default 20)'),\n offset: z.number().int().min(0).default(0).describe('Skip this many'),\n};\n\n/** One value or a list of them (sent as a comma list). */\nfunction oneOrMany<T extends readonly [string, ...string[]]>(values: T) {\n return z.union([z.enum(values), z.array(z.enum(values)).max(10)]).optional();\n}\n\nconst asList = (v: string | string[] | undefined) =>\n v === undefined ? undefined : Array.isArray(v) ? v.join(',') : v;\n\nconst listFilters = {\n status: oneOrMany(CONVERSATION_STATUSES).describe(\n 'Only these statuses: \"open\" or a list like [\"open\", \"pending\"]'\n ),\n priority: oneOrMany(CONVERSATION_PRIORITIES).describe(\n 'Only these priorities: \"urgent\" or a list'\n ),\n assignee: z\n .string()\n .max(255)\n .optional()\n .describe('A teammate (user ID or email), \"unassigned\" or \"assigned\"'),\n projectId: id('Project').optional(),\n updatedSince: z.iso\n .datetime({ offset: true })\n .optional()\n .describe('Updated or with a new message since (ISO 8601)'),\n tag: z.string().max(50).optional().describe('Carrying this tag'),\n source: z\n .string()\n .max(32)\n .optional()\n .describe('Entry point, e.g. live_chat, contact_form, api, email'),\n intent: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage intent (triaged conversations only)'),\n urgency: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage urgency (triaged conversations only)'),\n sort: z\n .enum(CONVERSATION_SORTS)\n .optional()\n .describe(\n 'recent (last activity, default), oldest, newest (created) or priority'\n ),\n};\n\ntype ListArgs = {\n status?: string | string[];\n priority?: string | string[];\n assignee?: string;\n projectId?: string;\n updatedSince?: string;\n tag?: string;\n source?: string;\n intent?: string;\n urgency?: string;\n sort?: string;\n limit: number;\n offset: number;\n q?: string;\n};\n\nasync function list(api: InboxRequester, a: ListArgs) {\n const r = await api.request<Json>('GET', '/v1/conversations', {\n query: {\n status: asList(a.status),\n priority: asList(a.priority),\n assignee: a.assignee,\n projectId: a.projectId,\n updatedSince: a.updatedSince,\n tag: a.tag,\n source: a.source,\n intent: a.intent,\n urgency: a.urgency,\n sort: a.sort,\n q: a.q,\n limit: a.limit,\n offset: a.offset,\n },\n });\n return {\n conversations:\n (r.conversations as z.output<typeof conversationSummary>[]) ?? [],\n total: Number(r.total ?? 0),\n limit: Number(r.limit ?? a.limit),\n offset: Number(r.offset ?? a.offset),\n hasMore: Boolean(r.hasMore),\n };\n}\n\nconst path = (cid: string, rest = '') =>\n `/v1/conversations/${encodeURIComponent(cid)}${rest}`;\n\nasync function patch(api: InboxRequester, cid: string, body: Json) {\n const r = await api.request<{ conversation: Json; changes?: string[] }>(\n 'PATCH',\n path(cid),\n { body }\n );\n return {\n conversation: r.conversation as { id: string; status: string },\n changes: r.changes ?? [],\n };\n}\n\nconst READ: ToolAnnotations = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n};\nconst CHANGE: ToolAnnotations = {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n};\n\n// ----- routing rules -----\n\nexport const ROUTING_FIELDS = [\n 'source',\n 'project',\n 'widget',\n 'connection',\n 'page_url',\n 'page_domain',\n 'customer_email',\n 'customer_email_domain',\n 'message',\n 'subject',\n 'tags',\n 'priority',\n 'language',\n] as const;\nexport const ROUTING_OPERATORS = [\n 'equals',\n 'not_equals',\n 'contains',\n 'not_contains',\n 'starts_with',\n 'ends_with',\n 'matches',\n] as const;\n\nconst routingConditions = z\n .object({\n match: z\n .enum(['all', 'any'])\n .describe('all = every condition holds, any = one is enough'),\n conditions: z\n .array(\n z.object({\n field: z.enum(ROUTING_FIELDS),\n operator: z.enum(ROUTING_OPERATORS),\n value: z\n .union([z.string(), z.array(z.string())])\n .describe(\n 'A value, or a list (positive operators: any matches; negative: none does). Case-insensitive; matches uses * and ? wildcards.'\n ),\n })\n )\n .max(50)\n .describe('Empty = matches every new conversation (catch-all)'),\n })\n .describe(\n 'Which new conversations the rule catches, tested on the first message, e.g. { \"match\": \"any\", \"conditions\": [{ \"field\": \"message\", \"operator\": \"contains\", \"value\": [\"refund\", \"invoice\"] }] }'\n );\n\nconst ruleFields = {\n name: z.string().trim().min(1).max(255).describe('Rule name'),\n targetChannelId: z\n .string()\n .regex(/^[A-Z0-9]{2,30}$/, 'a Slack channel ID like C0123ABCD')\n .describe('Slack channel ID to post matching conversations to'),\n conditions: routingConditions,\n projectId: id('Project (default: the oldest project)').optional(),\n widgetId: id('Widget').nullable().optional(),\n integrationId: id('Integration (default: the Slack workspace)').optional(),\n assignToUserId: id('Teammate to assign matching conversations to')\n .nullable()\n .optional(),\n isActive: z.boolean().optional().describe('Default true'),\n priority: z\n .number()\n .int()\n .min(-1_000_000)\n .max(1_000_000)\n .optional()\n .describe('Higher runs first (default: after every rule)'),\n};\n\nconst rule = z.looseObject({\n id: z.string(),\n name: z.string(),\n priority: z.number(),\n is_active: z.boolean(),\n target_channel_id: z.string(),\n});\n\n// ----- the tools -----\n\nexport const INBOX_TOOLS: readonly InboxTool[] = [\n defineTool({\n name: 'list_conversations',\n title: 'List conversations',\n description:\n 'List support conversations, most recent activity first, filtered by status, priority, assignee, project, tag, source, last update or AI triage (intent/urgency). Each row has the customer, assignee, needsReply (the customer spoke last) and a preview of the latest message. Use get_conversation for the thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations',\n inputSchema: z.object({ ...listFilters, ...paging }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, a) => list(api, a),\n }),\n\n defineTool({\n name: 'search_conversations',\n title: 'Search conversations',\n description:\n 'Find conversations whose subject, customer name or email, or any message contains the text (case-insensitive), with the same filters as list_conversations. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations?q=',\n inputSchema: z.object({\n query: z.string().trim().min(1).max(200).describe('Text to look for'),\n ...listFilters,\n ...paging,\n }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, { query, ...a }) => list(api, { ...a, q: query }),\n }),\n\n defineTool({\n name: 'get_conversation',\n title: 'Get conversation',\n description:\n 'Read one conversation with its full thread: customer, agent, system and bot messages in order, internal notes included (isInternal: true, never seen by the customer), plus status, priority, tags, assignee, customer, triage and its Slack thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: z.object({\n conversation: z.looseObject({\n id: z.string(),\n status: z.string(),\n messages: z.array(loose),\n }),\n }),\n annotations: READ,\n handler: async (api, { conversationId: cid }) => {\n const r = await api.request<{ conversation: Json }>('GET', path(cid));\n return { conversation: r.conversation as never };\n },\n }),\n\n defineTool({\n name: 'reply_to_conversation',\n title: 'Reply to conversation',\n description:\n 'Send a reply to the customer, as this API key (shown as \"<key name> (agent)\"). It reaches the customer like a dashboard reply (chat widget, email, SMS, ...) and is mirrored to the Slack thread; an open conversation becomes pending. Confirm the text with the user before sending. For a note only the team sees, use add_internal_note. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/reply',\n inputSchema: z.object({\n conversationId,\n content: z\n .string()\n .trim()\n .min(1)\n .max(10_000)\n .describe('Reply text, as the customer will read it'),\n }),\n outputSchema: z.object({\n message: z.looseObject({ id: z.string() }),\n conversationStatus: z.string(),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/reply'), {\n body: { content },\n });\n return {\n message: r.message as { id: string },\n conversationStatus: String(r.conversationStatus ?? ''),\n slack: String(r.slack ?? ''),\n };\n },\n }),\n\n defineTool({\n name: 'add_internal_note',\n title: 'Add internal note',\n description:\n 'Add an internal note to a conversation: posted in its Slack thread for the team and kept in the conversation, never sent to the customer. Use it for findings, handoffs and suggested replies. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/notes',\n inputSchema: z.object({\n conversationId,\n content: z.string().trim().min(1).max(10_000).describe('Note text'),\n }),\n outputSchema: z.object({\n note: z.looseObject({ id: z.string() }),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/notes'), {\n body: { content },\n });\n return { note: r.note as { id: string }, slack: String(r.slack ?? '') };\n },\n }),\n\n defineTool({\n name: 'assign_conversation',\n title: 'Assign conversation',\n description:\n 'Assign a conversation to a teammate (by user ID or email; owners, admins and agents, not viewers), or unassign it with null. The teammate is notified, and the change shows in the timeline and Slack. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n assignee: z\n .string()\n .min(1)\n .max(255)\n .nullable()\n .describe('User ID or email of the teammate, or null to unassign'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, assignee }) =>\n patch(\n api,\n cid,\n assignee === null\n ? { assigneeId: null }\n : assignee.includes('@')\n ? { assigneeEmail: assignee }\n : { assigneeId: assignee }\n ),\n }),\n\n defineTool({\n name: 'tag_conversation',\n title: 'Tag conversation',\n description:\n 'Add or remove tags on a conversation, or replace them all with `set`. Tags are lowercased; at most 20. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n add: tags.optional().describe('Tags to add'),\n remove: tags.optional().describe('Tags to remove'),\n set: tags\n .optional()\n .describe('Replace every tag with these ([] clears them)'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, add, remove, set }) => {\n if (!add?.length && !remove?.length && !set) {\n throw new ToolError('Pass tags to add, remove, or set.');\n }\n return patch(api, cid, {\n ...(set ? { tags: set } : {}),\n ...(add?.length ? { addTags: add } : {}),\n ...(remove?.length ? { removeTags: remove } : {}),\n });\n },\n }),\n\n defineTool({\n name: 'close_conversation',\n title: 'Close conversation',\n description:\n 'Close a conversation (or mark it resolved). Nothing is sent to the customer; the change shows in the timeline and the Slack root post. Reply first if the customer should hear back. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n status: z\n .enum(['closed', 'resolved'])\n .default('closed')\n .describe('closed (default) or resolved'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, status }) =>\n patch(api, cid, { status }),\n }),\n\n defineTool({\n name: 'reopen_conversation',\n title: 'Reopen conversation',\n description:\n 'Reopen a closed or resolved conversation (status open). Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid }) =>\n patch(api, cid, { status: 'open' }),\n }),\n\n defineTool({\n name: 'get_analytics_summary',\n title: 'Get analytics summary',\n description:\n 'Support KPIs for a period (default the last 30 days): conversations started and resolved, median first-response and resolution times, messages in and out, each with the previous period; plus the inbox right now (open, pending, unassigned, urgent, awaiting a reply). Needs the analytics:read (or read) scope.',\n scope: 'analytics:read',\n endpoint: 'GET /v1/analytics/summary',\n inputSchema: z.object({\n days: z\n .number()\n .int()\n .min(1)\n .max(366)\n .optional()\n .describe('Period length in days, ending today (default 30)'),\n from: z.iso.date().optional().describe('First day, YYYY-MM-DD'),\n to: z.iso.date().optional().describe('Last day, YYYY-MM-DD'),\n tz: z\n .string()\n .max(64)\n .optional()\n .describe('IANA time zone for day boundaries (default UTC)'),\n projectId: id('Project').optional(),\n }),\n outputSchema: z.looseObject({ range: loose, kpis: loose, inbox: loose }),\n annotations: READ,\n handler: async (api, args) =>\n api.request<{ range: Json; kpis: Json; inbox: Json }>(\n 'GET',\n '/v1/analytics/summary',\n { query: args }\n ),\n }),\n\n defineTool({\n name: 'list_routing_rules',\n title: 'List routing rules',\n description:\n 'List the routing rules in evaluation order (highest priority first): which new conversations each one sends to which Slack channel, and who it assigns. Needs the routing:write, inbox:read or read scope.',\n scope: 'routing:read',\n endpoint: 'GET /v1/routing-rules',\n inputSchema: z.object({}),\n outputSchema: z.object({ rules: z.array(rule) }),\n annotations: READ,\n handler: async api => {\n const r = await api.request<{ routing_rules: Json[] }>(\n 'GET',\n '/v1/routing-rules'\n );\n return { rules: (r.routing_rules ?? []) as never };\n },\n }),\n\n defineTool({\n name: 'create_routing_rule',\n title: 'Create routing rule',\n description:\n 'Create a routing rule: new conversations whose first message matches `conditions` are posted to `targetChannelId` instead of the default channel (and optionally assigned). It runs after the existing rules unless `priority` is given. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'POST /v1/routing-rules',\n inputSchema: z.object(ruleFields),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n handler: async (api, body) => {\n const r = await api.request<{ routing_rule: Json }>(\n 'POST',\n '/v1/routing-rules',\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'update_routing_rule',\n title: 'Update routing rule',\n description:\n 'Change a routing rule (from list_routing_rules); omitted fields keep their value, and `conditions` replaces the whole condition group. Use isActive: false to pause a rule. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'PATCH /v1/routing-rules/:id',\n inputSchema: z.object({\n ruleId: id('Routing rule'),\n name: ruleFields.name.optional(),\n targetChannelId: ruleFields.targetChannelId.optional(),\n conditions: routingConditions.optional(),\n projectId: ruleFields.projectId,\n widgetId: ruleFields.widgetId,\n integrationId: ruleFields.integrationId,\n assignToUserId: ruleFields.assignToUserId,\n isActive: ruleFields.isActive,\n priority: ruleFields.priority,\n }),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId, ...body }) => {\n if (Object.values(body).every(v => v === undefined)) {\n throw new ToolError('Pass at least one field to change.');\n }\n const r = await api.request<{ routing_rule: Json }>(\n 'PATCH',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`,\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'delete_routing_rule',\n title: 'Delete routing rule',\n description:\n 'Delete a routing rule for good. To stop it temporarily, use update_routing_rule with isActive: false instead. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'DELETE /v1/routing-rules/:id',\n inputSchema: z.object({ ruleId: id('Routing rule') }),\n outputSchema: z.object({ deleted: z.boolean(), ruleId: z.string() }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId }) => {\n await api.request(\n 'DELETE',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`\n );\n return { deleted: true, ruleId };\n },\n }),\n];\n\nexport const INBOX_TOOL_NAMES = INBOX_TOOLS.map(t => t.name);\n\nexport function getInboxTool(name: string): InboxTool | undefined {\n return INBOX_TOOLS.find(t => t.name === name);\n}\n\n/**\n * Validate `args` against the tool's input schema and run it. Throws\n * `ToolError` for an unknown tool or invalid arguments, and for API\n * failures (`toToolError` in `api.ts` words them for the model).\n */\nexport async function runInboxTool(\n api: InboxRequester,\n name: string,\n args: unknown\n): Promise<Json> {\n const tool = getInboxTool(name);\n if (!tool) throw new ToolError(`Unknown inbox tool \"${name}\"`);\n const parsed = tool.inputSchema.safeParse(args ?? {});\n if (!parsed.success) {\n throw new ToolError(\n `Invalid arguments for ${name}: ${parsed.error.issues\n .map(i => `${i.path.join('.') || 'input'}: ${i.message}`)\n .join('; ')}`\n );\n }\n return (await tool.handler(api, parsed.data)) as Json;\n}\n\n/**\n * The tools as JSON Schema function definitions, for LLM APIs that take\n * `{ name, description, parameters }` (OpenAI / OpenRouter style).\n */\nexport function inboxToolsAsJsonSchema(\n filter: (tool: InboxTool) => boolean = () => true\n): Array<{ name: string; description: string; parameters: Json }> {\n return INBOX_TOOLS.filter(filter).map(t => ({\n name: t.name,\n description: t.description,\n parameters: z.toJSONSchema(t.inputSchema, { io: 'input' }) as Json,\n }));\n}\n","import {\n ChannlizeClient,\n ChannlizeError,\n parseApiError,\n} from '@channlize/core';\nimport type { ResolvedConfig } from './config';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\n/**\n * A failed Channlize call, worded for the model: what went wrong and how\n * to fix it (wrong key, missing scope, unknown id). Tool handlers throw it\n * and the MCP server returns it as a tool error (`isError: true`).\n */\nexport class ToolError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public code?: string,\n /** ISO time the call may succeed again (agent policies, takeovers). */\n public retryAt?: string\n ) {\n super(message);\n this.name = 'ToolError';\n }\n}\n\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** The scope to add for an \"API key lacks required permission: <perm>\" 403. */\nfunction scopeFor(apiMessage: string): string {\n const perm = /permission:\\s*([\\w:*-]+)/i.exec(apiMessage)?.[1] ?? '';\n switch (perm) {\n case 'inbox:read':\n return 'inbox:read (or read)';\n case 'inbox:write':\n return 'inbox:write';\n case 'routing:read':\n return 'routing:write, inbox:read (or read)';\n case 'routing:write':\n return 'routing:write';\n case 'analytics:read':\n return 'analytics:read (or read)';\n case 'agents:send':\n return 'agents:send';\n case 'agents:read':\n return 'agents:read';\n case 'agents:ask_human':\n return 'agents:ask_human';\n case 'agents:admin':\n return 'agents:admin (an unbound key)';\n default:\n return /write|notify/.test(apiMessage) ? 'write' : 'read';\n }\n}\n\n/** \" Retry after <time>.\" for a refusal that names one. */\nfunction retryText(retryAt: string | undefined): string {\n return retryAt ? ` Retry after ${retryAt}, not before.` : '';\n}\n\n/** Hints for the Channlize for Agents error codes. */\nfunction agentHint(\n code: string | undefined,\n body: Record<string, unknown> = {}\n): string | undefined {\n const retryAt = typeof body.retryAt === 'string' ? body.retryAt : undefined;\n switch (code) {\n case 'AGENT_KEY_REQUIRED':\n return 'Agent tools need an agent key or a multi-agent key: create one under SDK & Integration → API keys and pick the agents.';\n case 'AGENT_NOT_ALLOWED':\n return \"The key can't act as that agent: pass one of the key's agents as agentId (list_agents), or fix CHANNLIZE_AGENT_ID.\";\n case 'NOT_A_CHANNEL_MEMBER':\n return 'Only channel members can post (observers only read): use list_agent_channels, or ask an admin to add the agent to the channel.';\n case 'AGENT_PAUSED':\n case 'AGENT_RETIRED':\n return 'Only active agents can act: ask an admin to reactivate it in the dashboard.';\n case 'AGENT_RATE_LIMITED':\n return 'The agent is sending too fast: wait a minute before sending again.';\n case 'AGENT_THREAD_PAUSED': {\n const why =\n body.reason === 'ping_pong'\n ? ' (two agents were going back and forth)'\n : body.reason === 'duplicate_content'\n ? ' (the same message kept being posted)'\n : '';\n return `The loop guard paused this thread${why}: stop sending in it until a person replies or resumes it. Don't rephrase and retry.`;\n }\n case 'HUMAN_TAKEOVER':\n return `A person took over this thread: don't post in it until the takeover ends or they hand it back.${retryText(retryAt)}`;\n case 'POLICY_RATE_LIMITED':\n case 'POLICY_BUDGET_EXHAUSTED':\n case 'POLICY_OUTSIDE_WORKING_HOURS':\n return `The agent's policy refuses this for now: wait, don't retry in a loop.${retryText(retryAt)}`;\n case 'POLICY_CHANNEL_NOT_ALLOWED':\n case 'POLICY_DM_NOT_ALLOWED':\n case 'POLICY_ASK_HUMAN_NOT_ALLOWED':\n case 'POLICY_URGENCY_NOT_ALLOWED':\n case 'POLICY_MAX_DEPTH':\n return \"The agent's policy forbids this: retrying won't help. Pick another channel, agent or urgency, or ask an admin to change the policy.\";\n case 'TASK_CLOSED':\n return 'The task is closed (completed, failed, canceled or rejected) and cannot change: create a new task instead.';\n case 'ALREADY_ASSIGNED':\n return 'The task is already assigned to that agent.';\n case 'AGENT_NOT_IN_CHANNEL':\n return \"The other agent isn't a member of the task's channel: pick a member (list_agent_channels), or ask an admin to add it.\";\n case 'NOT_TASK_PARTICIPANT':\n return \"Only the task's assignee or creator can change it or hand it off.\";\n case 'QUESTION_CLOSED':\n return 'The question is already closed (answered, expired or canceled): the first answer wins. Read it with get_answer.';\n case 'APPROVAL_ROLE_REQUIRED':\n return 'This approval needs a person with the required role: an agent cannot answer it.';\n case 'AGENT_ADMIN_KEY_REQUIRED':\n return 'This needs an unbound agents:admin key (not a key bound to agents): create one under SDK & Integration → API keys.';\n case 'INVALID_ANSWER':\n return \"Answer with one of the question's option ids (approve / deny for approvals), or text when free text is allowed.\";\n default:\n return undefined;\n }\n}\n\nfunction hintFor(\n status: number,\n apiMessage: string,\n code?: string,\n body?: Record<string, unknown>\n): string {\n const agent = agentHint(code, body);\n if (agent) return `${apiMessage}. ${agent}`;\n if (status === 400 && /agent_id is required/i.test(apiMessage)) {\n return `${apiMessage}. Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server.`;\n }\n switch (status) {\n case 401:\n return `${apiMessage}. The server API key is invalid, revoked or expired: create a new one under SDK & Integration → API keys and update CHANNLIZE_SERVER_API_KEY.`;\n case 403:\n return /permission/i.test(apiMessage)\n ? `${apiMessage}. This needs a server API key with the ${scopeFor(\n apiMessage\n )} scope: create one under SDK & Integration → API keys.`\n : apiMessage;\n case 404:\n return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;\n case 429:\n return `${apiMessage}. Rate limited: wait a little before retrying.`;\n default:\n return apiMessage;\n }\n}\n\n/** Maps a `@channlize/core` error to a `ToolError` with the same hints. */\nexport function toToolError(err: unknown): ToolError {\n if (err instanceof ToolError) return err;\n if (err instanceof ChannlizeError) {\n return new ToolError(\n err.statusCode\n ? hintFor(\n err.statusCode,\n err.message.replace(/\\.$/, ''),\n err.code,\n err.details\n )\n : err.message,\n err.statusCode,\n err.code,\n err.retryAt\n );\n }\n return new ToolError(err instanceof Error ? err.message : String(err));\n}\n\n/**\n * Thin client for the API-key endpoints `@channlize/core` doesn't wrap\n * (projects, the inbox, routing rules, contacts, Events and log reads,\n * agents). Widget-key\n * calls and `notify` go through `@channlize/core`.\n */\nexport class ChannlizeApi {\n private projectIdPromise?: Promise<string>;\n private widgetClient?: ChannlizeClient;\n\n constructor(readonly config: ResolvedConfig) {}\n\n get hasServerKey(): boolean {\n return !!this.config.serverApiKey;\n }\n\n get hasWidgetKey(): boolean {\n return !!this.config.widgetKey;\n }\n\n /** The default agent for the agent tools (`CHANNLIZE_AGENT_ID`). */\n get agentId(): string | undefined {\n return this.config.agentId;\n }\n\n private requireServerKey(): string {\n if (!this.config.serverApiKey) {\n throw new ToolError(\n 'This tool needs a server API key (ch_live_…): set CHANNLIZE_SERVER_API_KEY or pass --server-api-key.'\n );\n }\n return this.config.serverApiKey;\n }\n\n async request<T>(\n method: HttpMethod,\n path: string,\n options: {\n query?: Query;\n body?: unknown;\n headers?: Record<string, string>;\n /** Overrides the configured timeout, in ms (long-polls). */\n timeout?: number;\n } = {}\n ): Promise<T> {\n const key = this.requireServerKey();\n const timeout = options.timeout ?? this.config.timeout;\n const url = new URL(`${this.config.baseUrl}${path}`);\n for (const [k, v] of Object.entries(options.query ?? {})) {\n if (v !== undefined && v !== '') url.searchParams.set(k, String(v));\n }\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${key}`,\n Accept: 'application/json',\n ...(options.body !== undefined\n ? { 'Content-Type': 'application/json' }\n : {}),\n ...options.headers,\n },\n // oxlint-disable-next-line unicorn/no-invalid-fetch-options -- `method` is dynamic; GET calls never pass a body.\n body:\n options.body !== undefined ? JSON.stringify(options.body) : undefined,\n signal: AbortSignal.timeout(timeout),\n });\n } catch (err) {\n const e = err as Error;\n if (e.name === 'TimeoutError' || e.name === 'AbortError') {\n throw new ToolError(\n `Channlize API timed out after ${timeout} ms (${method} ${url.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,\n 408,\n 'TIMEOUT'\n );\n }\n throw new ToolError(\n `Could not reach the Channlize API at ${this.config.baseUrl}: ${e.message}`\n );\n }\n\n const text = await res.text();\n let data: unknown = undefined;\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = undefined;\n }\n }\n\n if (!res.ok) {\n const { message, code, body } = parseApiError(\n res.status,\n data,\n res.statusText\n );\n const retryAt =\n typeof body.retryAt === 'string' ? body.retryAt : undefined;\n throw new ToolError(\n hintFor(res.status, message.replace(/\\.$/, ''), code, body),\n res.status,\n code,\n retryAt\n );\n }\n if (data === undefined) {\n throw new ToolError(\n `Unexpected non-JSON response from ${method} ${url.pathname}`\n );\n }\n return data as T;\n }\n\n /**\n * `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization\n * (`GET /projects/me`), looked up once.\n */\n projectId(): Promise<string> {\n if (this.config.projectId) return Promise.resolve(this.config.projectId);\n this.projectIdPromise ??= this.request<{ project: { id: string } }>(\n 'GET',\n '/projects/me'\n )\n .then(r => {\n if (!r.project?.id) {\n throw new ToolError(\n 'Could not find a project for this key: set CHANNLIZE_PROJECT_ID.'\n );\n }\n return r.project.id;\n })\n .catch(err => {\n this.projectIdPromise = undefined;\n throw err;\n });\n return this.projectIdPromise;\n }\n\n /** `@channlize/core` client bound to the widget key (visitor tools). */\n widget(): ChannlizeClient {\n if (!this.config.widgetKey) {\n throw new ToolError(\n 'This tool needs the public widget key (wk_…): set CHANNLIZE_WIDGET_KEY or pass --widget-key.'\n );\n }\n this.widgetClient ??= new ChannlizeClient({\n apiKey: this.config.widgetKey,\n projectId: this.config.projectId ?? '',\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n return this.widgetClient;\n }\n\n /** `@channlize/core` client for `notify` (server key + project). */\n async notifier(): Promise<ChannlizeClient> {\n const serverApiKey = this.requireServerKey();\n return new ChannlizeClient({\n // core requires a widget key; notify only uses serverApiKey.\n apiKey: this.config.widgetKey ?? 'unused',\n serverApiKey,\n projectId: await this.projectId(),\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/inbox.ts","../src/api.ts"],"sourcesContent":["/**\n * The Channlize inbox toolset: names, zod input/output schemas and handlers\n * for working the inbox through the API-key endpoints under `/v1`.\n *\n * This module has no dependency on the MCP server or its transports, so any\n * agent can import it (`@channlize/mcp/inbox`): the stdio/HTTP server\n * registers these tools (`registerInboxTools` in `tools.ts`), and the\n * dashboard copilot or an internal bot can run the same handlers with its own\n * client (anything with a `request(method, path, { query, body })`).\n *\n * Writes act as a named agent: the API key's name. Replies show in Slack as\n * \"Replied by <key name> (agent)\", and changes as \"… by <key name> (agent)\".\n *\n * Scopes (pick them when creating the key): `inbox:read`, `inbox:write`,\n * `routing:write`, `analytics:read`. A `read` key can use the read tools.\n */\nimport * as z from 'zod/v4';\nimport { ToolError, type HttpMethod } from './api';\n\nexport type { HttpMethod };\n\ntype Json = Record<string, unknown>;\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** What a handler needs: `ChannlizeApi` from this package, or your own. */\nexport interface InboxRequester {\n request<T>(\n method: HttpMethod,\n path: string,\n options?: { query?: Query; body?: unknown }\n ): Promise<T>;\n}\n\n/** The API key scope a tool needs. `routing:read` is granted by `routing:write`, `inbox:read` or `read`. */\nexport type InboxScope =\n | 'inbox:read'\n | 'inbox:write'\n | 'routing:read'\n | 'routing:write'\n | 'analytics:read';\n\nexport interface ToolAnnotations {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface InboxTool<\n I extends z.ZodObject = z.ZodObject,\n O extends z.ZodObject = z.ZodObject,\n> {\n name: string;\n title: string;\n description: string;\n scope: InboxScope;\n /** The HTTP endpoint it calls, for docs and logs. */\n endpoint: string;\n inputSchema: I;\n outputSchema: O;\n annotations: ToolAnnotations;\n handler: (api: InboxRequester, args: z.output<I>) => Promise<z.output<O>>;\n}\n\nfunction defineTool<I extends z.ZodObject, O extends z.ZodObject>(\n tool: InboxTool<I, O>\n): InboxTool {\n return tool as unknown as InboxTool;\n}\n\n// ----- shared schemas -----\n\nexport const CONVERSATION_STATUSES = [\n 'open',\n 'pending',\n 'resolved',\n 'closed',\n] as const;\nexport const CONVERSATION_PRIORITIES = [\n 'low',\n 'normal',\n 'high',\n 'urgent',\n] as const;\nexport const CONVERSATION_SORTS = [\n 'recent',\n 'oldest',\n 'newest',\n 'priority',\n] as const;\n\nconst loose = z.looseObject({});\nconst id = (what: string) => z.string().uuid().describe(`${what} ID`);\nconst conversationId = id(\n 'Conversation (from list_conversations or search_conversations)'\n);\n\nconst person = z\n .looseObject({\n id: z.string(),\n name: z.string().nullable().optional(),\n email: z.string().nullable().optional(),\n })\n .nullable();\n\nconst conversationSummary = z.looseObject({\n id: z.string(),\n subject: z.string().nullable().optional(),\n status: z.string(),\n priority: z.string().optional(),\n source: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n customer: person.optional(),\n assignee: person.optional(),\n triage: z.record(z.string(), z.unknown()).nullable().optional(),\n needsReply: z.boolean().optional(),\n lastActivityAt: z.string().nullable().optional(),\n});\n\nconst page = {\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n hasMore: z.boolean(),\n};\n\nconst listOutput = z.object({\n conversations: z.array(conversationSummary),\n ...page,\n});\n\nconst changeOutput = z.object({\n conversation: z.looseObject({ id: z.string(), status: z.string() }),\n changes: z.array(z.string()),\n});\n\nconst slackOutcome = z\n .string()\n .describe(\n 'Slack mirror: queued, posted, no_thread (not in Slack), not_connected or failed'\n );\n\nconst tags = z.array(z.string().trim().min(1).max(50)).max(20);\n\nconst paging = {\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .default(20)\n .describe('How many to return (1-100, default 20)'),\n offset: z.number().int().min(0).default(0).describe('Skip this many'),\n};\n\n/** One value or a list of them (sent as a comma list). */\nfunction oneOrMany<T extends readonly [string, ...string[]]>(values: T) {\n return z.union([z.enum(values), z.array(z.enum(values)).max(10)]).optional();\n}\n\nconst asList = (v: string | string[] | undefined) =>\n v === undefined ? undefined : Array.isArray(v) ? v.join(',') : v;\n\nconst listFilters = {\n status: oneOrMany(CONVERSATION_STATUSES).describe(\n 'Only these statuses: \"open\" or a list like [\"open\", \"pending\"]'\n ),\n priority: oneOrMany(CONVERSATION_PRIORITIES).describe(\n 'Only these priorities: \"urgent\" or a list'\n ),\n assignee: z\n .string()\n .max(255)\n .optional()\n .describe('A teammate (user ID or email), \"unassigned\" or \"assigned\"'),\n projectId: id('Project').optional(),\n updatedSince: z.iso\n .datetime({ offset: true })\n .optional()\n .describe('Updated or with a new message since (ISO 8601)'),\n tag: z.string().max(50).optional().describe('Carrying this tag'),\n source: z\n .string()\n .max(32)\n .optional()\n .describe('Entry point, e.g. live_chat, contact_form, api, email'),\n intent: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage intent (triaged conversations only)'),\n urgency: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage urgency (triaged conversations only)'),\n sort: z\n .enum(CONVERSATION_SORTS)\n .optional()\n .describe(\n 'recent (last activity, default), oldest, newest (created) or priority'\n ),\n};\n\ntype ListArgs = {\n status?: string | string[];\n priority?: string | string[];\n assignee?: string;\n projectId?: string;\n updatedSince?: string;\n tag?: string;\n source?: string;\n intent?: string;\n urgency?: string;\n sort?: string;\n limit: number;\n offset: number;\n q?: string;\n};\n\nasync function list(api: InboxRequester, a: ListArgs) {\n const r = await api.request<Json>('GET', '/v1/conversations', {\n query: {\n status: asList(a.status),\n priority: asList(a.priority),\n assignee: a.assignee,\n projectId: a.projectId,\n updatedSince: a.updatedSince,\n tag: a.tag,\n source: a.source,\n intent: a.intent,\n urgency: a.urgency,\n sort: a.sort,\n q: a.q,\n limit: a.limit,\n offset: a.offset,\n },\n });\n return {\n conversations:\n (r.conversations as z.output<typeof conversationSummary>[]) ?? [],\n total: Number(r.total ?? 0),\n limit: Number(r.limit ?? a.limit),\n offset: Number(r.offset ?? a.offset),\n hasMore: Boolean(r.hasMore),\n };\n}\n\nconst path = (cid: string, rest = '') =>\n `/v1/conversations/${encodeURIComponent(cid)}${rest}`;\n\nasync function patch(api: InboxRequester, cid: string, body: Json) {\n const r = await api.request<{ conversation: Json; changes?: string[] }>(\n 'PATCH',\n path(cid),\n { body }\n );\n return {\n conversation: r.conversation as { id: string; status: string },\n changes: r.changes ?? [],\n };\n}\n\nconst READ: ToolAnnotations = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n};\nconst CHANGE: ToolAnnotations = {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n};\n\n// ----- routing rules -----\n\nexport const ROUTING_FIELDS = [\n 'source',\n 'project',\n 'widget',\n 'connection',\n 'page_url',\n 'page_domain',\n 'customer_email',\n 'customer_email_domain',\n 'message',\n 'subject',\n 'tags',\n 'priority',\n 'language',\n] as const;\nexport const ROUTING_OPERATORS = [\n 'equals',\n 'not_equals',\n 'contains',\n 'not_contains',\n 'starts_with',\n 'ends_with',\n 'matches',\n] as const;\n\nconst routingConditions = z\n .object({\n match: z\n .enum(['all', 'any'])\n .describe('all = every condition holds, any = one is enough'),\n conditions: z\n .array(\n z.object({\n field: z.enum(ROUTING_FIELDS),\n operator: z.enum(ROUTING_OPERATORS),\n value: z\n .union([z.string(), z.array(z.string())])\n .describe(\n 'A value, or a list (positive operators: any matches; negative: none does). Case-insensitive; matches uses * and ? wildcards.'\n ),\n })\n )\n .max(50)\n .describe('Empty = matches every new conversation (catch-all)'),\n })\n .describe(\n 'Which new conversations the rule catches, tested on the first message, e.g. { \"match\": \"any\", \"conditions\": [{ \"field\": \"message\", \"operator\": \"contains\", \"value\": [\"refund\", \"invoice\"] }] }'\n );\n\nconst ruleFields = {\n name: z.string().trim().min(1).max(255).describe('Rule name'),\n targetChannelId: z\n .string()\n .regex(/^[A-Z0-9]{2,30}$/, 'a Slack channel ID like C0123ABCD')\n .describe('Slack channel ID to post matching conversations to'),\n conditions: routingConditions,\n projectId: id('Project (default: the oldest project)').optional(),\n widgetId: id('Widget').nullable().optional(),\n integrationId: id('Integration (default: the Slack workspace)').optional(),\n assignToUserId: id('Teammate to assign matching conversations to')\n .nullable()\n .optional(),\n isActive: z.boolean().optional().describe('Default true'),\n priority: z\n .number()\n .int()\n .min(-1_000_000)\n .max(1_000_000)\n .optional()\n .describe('Higher runs first (default: after every rule)'),\n};\n\nconst rule = z.looseObject({\n id: z.string(),\n name: z.string(),\n priority: z.number(),\n is_active: z.boolean(),\n target_channel_id: z.string(),\n});\n\n// ----- the tools -----\n\nexport const INBOX_TOOLS: readonly InboxTool[] = [\n defineTool({\n name: 'list_conversations',\n title: 'List conversations',\n description:\n 'List support conversations, most recent activity first, filtered by status, priority, assignee, project, tag, source, last update or AI triage (intent/urgency). Each row has the customer, assignee, needsReply (the customer spoke last) and a preview of the latest message. Use get_conversation for the thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations',\n inputSchema: z.object({ ...listFilters, ...paging }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, a) => list(api, a),\n }),\n\n defineTool({\n name: 'search_conversations',\n title: 'Search conversations',\n description:\n 'Find conversations whose subject, customer name or email, or any message contains the text (case-insensitive), with the same filters as list_conversations. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations?q=',\n inputSchema: z.object({\n query: z.string().trim().min(1).max(200).describe('Text to look for'),\n ...listFilters,\n ...paging,\n }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, { query, ...a }) => list(api, { ...a, q: query }),\n }),\n\n defineTool({\n name: 'get_conversation',\n title: 'Get conversation',\n description:\n 'Read one conversation with its full thread: customer, agent, system and bot messages in order, internal notes included (isInternal: true, never seen by the customer), plus status, priority, tags, assignee, customer, triage and its Slack thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: z.object({\n conversation: z.looseObject({\n id: z.string(),\n status: z.string(),\n messages: z.array(loose),\n }),\n }),\n annotations: READ,\n handler: async (api, { conversationId: cid }) => {\n const r = await api.request<{ conversation: Json }>('GET', path(cid));\n return { conversation: r.conversation as never };\n },\n }),\n\n defineTool({\n name: 'reply_to_conversation',\n title: 'Reply to conversation',\n description:\n 'Send a reply to the customer, as this API key (shown as \"<key name> (agent)\"). It reaches the customer like a dashboard reply (chat widget, email, SMS, ...) and is mirrored to the Slack thread; an open conversation becomes pending. Confirm the text with the user before sending. For a note only the team sees, use add_internal_note. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/reply',\n inputSchema: z.object({\n conversationId,\n content: z\n .string()\n .trim()\n .min(1)\n .max(10_000)\n .describe('Reply text, as the customer will read it'),\n }),\n outputSchema: z.object({\n message: z.looseObject({ id: z.string() }),\n conversationStatus: z.string(),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/reply'), {\n body: { content },\n });\n return {\n message: r.message as { id: string },\n conversationStatus: String(r.conversationStatus ?? ''),\n slack: String(r.slack ?? ''),\n };\n },\n }),\n\n defineTool({\n name: 'add_internal_note',\n title: 'Add internal note',\n description:\n 'Add an internal note to a conversation: posted in its Slack thread for the team and kept in the conversation, never sent to the customer. Use it for findings, handoffs and suggested replies. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/notes',\n inputSchema: z.object({\n conversationId,\n content: z.string().trim().min(1).max(10_000).describe('Note text'),\n }),\n outputSchema: z.object({\n note: z.looseObject({ id: z.string() }),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/notes'), {\n body: { content },\n });\n return { note: r.note as { id: string }, slack: String(r.slack ?? '') };\n },\n }),\n\n defineTool({\n name: 'assign_conversation',\n title: 'Assign conversation',\n description:\n 'Assign a conversation to a teammate (by user ID or email; owners, admins and agents, not viewers), or unassign it with null. The teammate is notified, and the change shows in the timeline and Slack. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n assignee: z\n .string()\n .min(1)\n .max(255)\n .nullable()\n .describe('User ID or email of the teammate, or null to unassign'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, assignee }) =>\n patch(\n api,\n cid,\n assignee === null\n ? { assigneeId: null }\n : assignee.includes('@')\n ? { assigneeEmail: assignee }\n : { assigneeId: assignee }\n ),\n }),\n\n defineTool({\n name: 'tag_conversation',\n title: 'Tag conversation',\n description:\n 'Add or remove tags on a conversation, or replace them all with `set`. Tags are lowercased; at most 20. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n add: tags.optional().describe('Tags to add'),\n remove: tags.optional().describe('Tags to remove'),\n set: tags\n .optional()\n .describe('Replace every tag with these ([] clears them)'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, add, remove, set }) => {\n if (!add?.length && !remove?.length && !set) {\n throw new ToolError('Pass tags to add, remove, or set.');\n }\n return patch(api, cid, {\n ...(set ? { tags: set } : {}),\n ...(add?.length ? { addTags: add } : {}),\n ...(remove?.length ? { removeTags: remove } : {}),\n });\n },\n }),\n\n defineTool({\n name: 'close_conversation',\n title: 'Close conversation',\n description:\n 'Close a conversation (or mark it resolved). Nothing is sent to the customer; the change shows in the timeline and the Slack root post. Reply first if the customer should hear back. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n status: z\n .enum(['closed', 'resolved'])\n .default('closed')\n .describe('closed (default) or resolved'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, status }) =>\n patch(api, cid, { status }),\n }),\n\n defineTool({\n name: 'reopen_conversation',\n title: 'Reopen conversation',\n description:\n 'Reopen a closed or resolved conversation (status open). Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid }) =>\n patch(api, cid, { status: 'open' }),\n }),\n\n defineTool({\n name: 'get_analytics_summary',\n title: 'Get analytics summary',\n description:\n 'Support KPIs for a period (default the last 30 days): conversations started and resolved, median first-response and resolution times, messages in and out, each with the previous period; plus the inbox right now (open, pending, unassigned, urgent, awaiting a reply). Needs the analytics:read (or read) scope.',\n scope: 'analytics:read',\n endpoint: 'GET /v1/analytics/summary',\n inputSchema: z.object({\n days: z\n .number()\n .int()\n .min(1)\n .max(366)\n .optional()\n .describe('Period length in days, ending today (default 30)'),\n from: z.iso.date().optional().describe('First day, YYYY-MM-DD'),\n to: z.iso.date().optional().describe('Last day, YYYY-MM-DD'),\n tz: z\n .string()\n .max(64)\n .optional()\n .describe('IANA time zone for day boundaries (default UTC)'),\n projectId: id('Project').optional(),\n }),\n outputSchema: z.looseObject({ range: loose, kpis: loose, inbox: loose }),\n annotations: READ,\n handler: async (api, args) =>\n api.request<{ range: Json; kpis: Json; inbox: Json }>(\n 'GET',\n '/v1/analytics/summary',\n { query: args }\n ),\n }),\n\n defineTool({\n name: 'list_routing_rules',\n title: 'List routing rules',\n description:\n 'List the routing rules in evaluation order (highest priority first): which new conversations each one sends to which Slack channel, and who it assigns. Needs the routing:write, inbox:read or read scope.',\n scope: 'routing:read',\n endpoint: 'GET /v1/routing-rules',\n inputSchema: z.object({}),\n outputSchema: z.object({ rules: z.array(rule) }),\n annotations: READ,\n handler: async api => {\n const r = await api.request<{ routing_rules: Json[] }>(\n 'GET',\n '/v1/routing-rules'\n );\n return { rules: (r.routing_rules ?? []) as never };\n },\n }),\n\n defineTool({\n name: 'create_routing_rule',\n title: 'Create routing rule',\n description:\n 'Create a routing rule: new conversations whose first message matches `conditions` are posted to `targetChannelId` instead of the default channel (and optionally assigned). It runs after the existing rules unless `priority` is given. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'POST /v1/routing-rules',\n inputSchema: z.object(ruleFields),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n handler: async (api, body) => {\n const r = await api.request<{ routing_rule: Json }>(\n 'POST',\n '/v1/routing-rules',\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'update_routing_rule',\n title: 'Update routing rule',\n description:\n 'Change a routing rule (from list_routing_rules); omitted fields keep their value, and `conditions` replaces the whole condition group. Use isActive: false to pause a rule. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'PATCH /v1/routing-rules/:id',\n inputSchema: z.object({\n ruleId: id('Routing rule'),\n name: ruleFields.name.optional(),\n targetChannelId: ruleFields.targetChannelId.optional(),\n conditions: routingConditions.optional(),\n projectId: ruleFields.projectId,\n widgetId: ruleFields.widgetId,\n integrationId: ruleFields.integrationId,\n assignToUserId: ruleFields.assignToUserId,\n isActive: ruleFields.isActive,\n priority: ruleFields.priority,\n }),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId, ...body }) => {\n if (Object.values(body).every(v => v === undefined)) {\n throw new ToolError('Pass at least one field to change.');\n }\n const r = await api.request<{ routing_rule: Json }>(\n 'PATCH',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`,\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'delete_routing_rule',\n title: 'Delete routing rule',\n description:\n 'Delete a routing rule for good. To stop it temporarily, use update_routing_rule with isActive: false instead. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'DELETE /v1/routing-rules/:id',\n inputSchema: z.object({ ruleId: id('Routing rule') }),\n outputSchema: z.object({ deleted: z.boolean(), ruleId: z.string() }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId }) => {\n await api.request(\n 'DELETE',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`\n );\n return { deleted: true, ruleId };\n },\n }),\n];\n\nexport const INBOX_TOOL_NAMES = INBOX_TOOLS.map(t => t.name);\n\nexport function getInboxTool(name: string): InboxTool | undefined {\n return INBOX_TOOLS.find(t => t.name === name);\n}\n\n/**\n * Validate `args` against the tool's input schema and run it. Throws\n * `ToolError` for an unknown tool or invalid arguments, and for API\n * failures (`toToolError` in `api.ts` words them for the model).\n */\nexport async function runInboxTool(\n api: InboxRequester,\n name: string,\n args: unknown\n): Promise<Json> {\n const tool = getInboxTool(name);\n if (!tool) throw new ToolError(`Unknown inbox tool \"${name}\"`);\n const parsed = tool.inputSchema.safeParse(args ?? {});\n if (!parsed.success) {\n throw new ToolError(\n `Invalid arguments for ${name}: ${parsed.error.issues\n .map(i => `${i.path.join('.') || 'input'}: ${i.message}`)\n .join('; ')}`\n );\n }\n return (await tool.handler(api, parsed.data)) as Json;\n}\n\n/**\n * The tools as JSON Schema function definitions, for LLM APIs that take\n * `{ name, description, parameters }` (OpenAI / OpenRouter style).\n */\nexport function inboxToolsAsJsonSchema(\n filter: (tool: InboxTool) => boolean = () => true\n): Array<{ name: string; description: string; parameters: Json }> {\n return INBOX_TOOLS.filter(filter).map(t => ({\n name: t.name,\n description: t.description,\n parameters: z.toJSONSchema(t.inputSchema, { io: 'input' }) as Json,\n }));\n}\n","import {\n ChannlizeClient,\n ChannlizeError,\n parseApiError,\n} from '@channlize/core';\nimport type { ResolvedConfig } from './config';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\n/**\n * A failed Channlize call, worded for the model: what went wrong and how\n * to fix it (wrong key, missing scope, unknown id). Tool handlers throw it\n * and the MCP server returns it as a tool error (`isError: true`).\n */\nexport class ToolError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public code?: string,\n /** ISO time the call may succeed again (agent policies, takeovers). */\n public retryAt?: string\n ) {\n super(message);\n this.name = 'ToolError';\n }\n}\n\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** The scope to add for an \"API key lacks required permission: <perm>\" 403. */\nfunction scopeFor(apiMessage: string): string {\n const perm = /permission:\\s*([\\w:*-]+)/i.exec(apiMessage)?.[1] ?? '';\n switch (perm) {\n case 'inbox:read':\n return 'inbox:read (or read)';\n case 'inbox:write':\n return 'inbox:write';\n case 'routing:read':\n return 'routing:write, inbox:read (or read)';\n case 'routing:write':\n return 'routing:write';\n case 'analytics:read':\n return 'analytics:read (or read)';\n case 'agents:send':\n return 'agents:send';\n case 'agents:read':\n return 'agents:read';\n case 'agents:ask_human':\n return 'agents:ask_human';\n case 'agents:admin':\n return 'agents:admin (an unbound key)';\n default:\n return /write|notify/.test(apiMessage) ? 'write' : 'read';\n }\n}\n\n/** \" Retry after <time>.\" for a refusal that names one. */\nfunction retryText(retryAt: string | undefined): string {\n return retryAt ? ` Retry after ${retryAt}, not before.` : '';\n}\n\n/** Hints for the Channlize for Agents error codes. */\nfunction agentHint(\n code: string | undefined,\n body: Record<string, unknown> = {}\n): string | undefined {\n const retryAt = typeof body.retryAt === 'string' ? body.retryAt : undefined;\n switch (code) {\n case 'AGENT_KEY_REQUIRED':\n return 'Agent tools need an agent key or a multi-agent key: create one under SDK & Integration → API keys and pick the agents.';\n case 'AGENT_NOT_ALLOWED':\n return \"The key can't act as that agent: pass one of the key's agents as agentId (list_agents), or fix CHANNLIZE_AGENT_ID.\";\n case 'NOT_A_CHANNEL_MEMBER':\n return 'Only channel members can post (observers only read): use list_agent_channels, or ask an admin to add the agent to the channel.';\n case 'AGENT_PAUSED':\n case 'AGENT_RETIRED':\n return 'Only active agents can act: ask an admin to reactivate it in the dashboard.';\n case 'AGENT_RATE_LIMITED':\n return 'The agent is sending too fast: wait a minute before sending again.';\n case 'AGENT_THREAD_PAUSED': {\n const why =\n body.reason === 'ping_pong'\n ? ' (two agents were going back and forth)'\n : body.reason === 'duplicate_content'\n ? ' (the same message kept being posted)'\n : '';\n return `The loop guard paused this thread${why}: stop sending in it until a person replies or resumes it. Don't rephrase and retry.`;\n }\n case 'HUMAN_TAKEOVER':\n return `A person took over this thread: don't post in it until the takeover ends or they hand it back.${retryText(retryAt)}`;\n case 'POLICY_RATE_LIMITED':\n case 'POLICY_BUDGET_EXHAUSTED':\n case 'POLICY_OUTSIDE_WORKING_HOURS':\n return `The agent's policy refuses this for now: wait, don't retry in a loop.${retryText(retryAt)}`;\n case 'POLICY_CHANNEL_NOT_ALLOWED':\n case 'POLICY_DM_NOT_ALLOWED':\n case 'POLICY_ASK_HUMAN_NOT_ALLOWED':\n case 'POLICY_URGENCY_NOT_ALLOWED':\n case 'POLICY_MAX_DEPTH':\n return \"The agent's policy forbids this: retrying won't help. Pick another channel, agent or urgency, or ask an admin to change the policy.\";\n case 'TASK_CLOSED':\n return 'The task is closed (completed, failed, canceled or rejected) and cannot change: create a new task instead.';\n case 'ALREADY_ASSIGNED':\n return 'The task is already assigned to that agent.';\n case 'AGENT_NOT_IN_CHANNEL':\n return \"The other agent isn't a member of the task's channel: pick a member (list_agent_channels), or ask an admin to add it.\";\n case 'NOT_TASK_PARTICIPANT':\n return \"Only the task's assignee or creator can change it or hand it off.\";\n case 'QUESTION_CLOSED':\n return 'The question is already closed (answered, expired or canceled): the first answer wins. Read it with get_answer.';\n case 'APPROVAL_ROLE_REQUIRED':\n return 'This approval needs a person with the required role: an agent cannot answer it.';\n case 'AGENT_ADMIN_KEY_REQUIRED':\n return 'This needs an unbound agents:admin key (not a key bound to agents): create one under SDK & Integration → API keys.';\n case 'CHANNEL_NOT_VISIBLE':\n return \"Your key's agents can't read that channel: leave channelId out, or pick one from list_agent_channels.\";\n case 'INVALID_ANSWER':\n return \"Answer with one of the question's option ids (approve / deny for approvals), or text when free text is allowed.\";\n default:\n return undefined;\n }\n}\n\nfunction hintFor(\n status: number,\n apiMessage: string,\n code?: string,\n body?: Record<string, unknown>\n): string {\n const agent = agentHint(code, body);\n if (agent) return `${apiMessage}. ${agent}`;\n if (status === 400 && /agent_id is required/i.test(apiMessage)) {\n return `${apiMessage}. Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server.`;\n }\n switch (status) {\n case 401:\n return `${apiMessage}. The server API key is invalid, revoked or expired: create a new one under SDK & Integration → API keys and update CHANNLIZE_SERVER_API_KEY.`;\n case 403:\n return /permission/i.test(apiMessage)\n ? `${apiMessage}. This needs a server API key with the ${scopeFor(\n apiMessage\n )} scope: create one under SDK & Integration → API keys.`\n : apiMessage;\n case 404:\n return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;\n case 429:\n return `${apiMessage}. Rate limited: wait a little before retrying.`;\n default:\n return apiMessage;\n }\n}\n\n/** Maps a `@channlize/core` error to a `ToolError` with the same hints. */\nexport function toToolError(err: unknown): ToolError {\n if (err instanceof ToolError) return err;\n if (err instanceof ChannlizeError) {\n return new ToolError(\n err.statusCode\n ? hintFor(\n err.statusCode,\n err.message.replace(/\\.$/, ''),\n err.code,\n err.details\n )\n : err.message,\n err.statusCode,\n err.code,\n err.retryAt\n );\n }\n return new ToolError(err instanceof Error ? err.message : String(err));\n}\n\n/**\n * Thin client for the API-key endpoints `@channlize/core` doesn't wrap\n * (projects, the inbox, routing rules, contacts, Events and log reads,\n * agents). Widget-key\n * calls and `notify` go through `@channlize/core`.\n */\nexport class ChannlizeApi {\n private projectIdPromise?: Promise<string>;\n private widgetClient?: ChannlizeClient;\n\n constructor(readonly config: ResolvedConfig) {}\n\n get hasServerKey(): boolean {\n return !!this.config.serverApiKey;\n }\n\n get hasWidgetKey(): boolean {\n return !!this.config.widgetKey;\n }\n\n /** The default agent for the agent tools (`CHANNLIZE_AGENT_ID`). */\n get agentId(): string | undefined {\n return this.config.agentId;\n }\n\n private requireServerKey(): string {\n if (!this.config.serverApiKey) {\n throw new ToolError(\n 'This tool needs a server API key (ch_live_…): set CHANNLIZE_SERVER_API_KEY or pass --server-api-key.'\n );\n }\n return this.config.serverApiKey;\n }\n\n async request<T>(\n method: HttpMethod,\n path: string,\n options: {\n query?: Query;\n body?: unknown;\n headers?: Record<string, string>;\n /** Overrides the configured timeout, in ms (long-polls). */\n timeout?: number;\n } = {}\n ): Promise<T> {\n const key = this.requireServerKey();\n const timeout = options.timeout ?? this.config.timeout;\n const url = new URL(`${this.config.baseUrl}${path}`);\n for (const [k, v] of Object.entries(options.query ?? {})) {\n if (v !== undefined && v !== '') url.searchParams.set(k, String(v));\n }\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${key}`,\n Accept: 'application/json',\n ...(options.body !== undefined\n ? { 'Content-Type': 'application/json' }\n : {}),\n ...options.headers,\n },\n // oxlint-disable-next-line unicorn/no-invalid-fetch-options -- `method` is dynamic; GET calls never pass a body.\n body:\n options.body !== undefined ? JSON.stringify(options.body) : undefined,\n signal: AbortSignal.timeout(timeout),\n });\n } catch (err) {\n const e = err as Error;\n if (e.name === 'TimeoutError' || e.name === 'AbortError') {\n throw new ToolError(\n `Channlize API timed out after ${timeout} ms (${method} ${url.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,\n 408,\n 'TIMEOUT'\n );\n }\n throw new ToolError(\n `Could not reach the Channlize API at ${this.config.baseUrl}: ${e.message}`\n );\n }\n\n const text = await res.text();\n let data: unknown = undefined;\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = undefined;\n }\n }\n\n if (!res.ok) {\n const { message, code, body } = parseApiError(\n res.status,\n data,\n res.statusText\n );\n const retryAt =\n typeof body.retryAt === 'string' ? body.retryAt : undefined;\n throw new ToolError(\n hintFor(res.status, message.replace(/\\.$/, ''), code, body),\n res.status,\n code,\n retryAt\n );\n }\n if (data === undefined) {\n throw new ToolError(\n `Unexpected non-JSON response from ${method} ${url.pathname}`\n );\n }\n return data as T;\n }\n\n /**\n * `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization\n * (`GET /projects/me`), looked up once.\n */\n projectId(): Promise<string> {\n if (this.config.projectId) return Promise.resolve(this.config.projectId);\n this.projectIdPromise ??= this.request<{ project: { id: string } }>(\n 'GET',\n '/projects/me'\n )\n .then(r => {\n if (!r.project?.id) {\n throw new ToolError(\n 'Could not find a project for this key: set CHANNLIZE_PROJECT_ID.'\n );\n }\n return r.project.id;\n })\n .catch(err => {\n this.projectIdPromise = undefined;\n throw err;\n });\n return this.projectIdPromise;\n }\n\n /** `@channlize/core` client bound to the widget key (visitor tools). */\n widget(): ChannlizeClient {\n if (!this.config.widgetKey) {\n throw new ToolError(\n 'This tool needs the public widget key (wk_…): set CHANNLIZE_WIDGET_KEY or pass --widget-key.'\n );\n }\n this.widgetClient ??= new ChannlizeClient({\n apiKey: this.config.widgetKey,\n projectId: this.config.projectId ?? '',\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n return this.widgetClient;\n }\n\n /** `@channlize/core` client for `notify` (server key + project). */\n async notifier(): Promise<ChannlizeClient> {\n const serverApiKey = this.requireServerKey();\n return new ChannlizeClient({\n // core requires a widget key; notify only uses serverApiKey.\n apiKey: this.config.widgetKey ?? 'unused',\n serverApiKey,\n projectId: await this.projectId(),\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/inbox.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/inbox.ts","../src/api.ts"],"sourcesContent":["/**\n * The Channlize inbox toolset: names, zod input/output schemas and handlers\n * for working the inbox through the API-key endpoints under `/v1`.\n *\n * This module has no dependency on the MCP server or its transports, so any\n * agent can import it (`@channlize/mcp/inbox`): the stdio/HTTP server\n * registers these tools (`registerInboxTools` in `tools.ts`), and the\n * dashboard copilot or an internal bot can run the same handlers with its own\n * client (anything with a `request(method, path, { query, body })`).\n *\n * Writes act as a named agent: the API key's name. Replies show in Slack as\n * \"Replied by <key name> (agent)\", and changes as \"… by <key name> (agent)\".\n *\n * Scopes (pick them when creating the key): `inbox:read`, `inbox:write`,\n * `routing:write`, `analytics:read`. A `read` key can use the read tools.\n */\nimport * as z from 'zod/v4';\nimport { ToolError, type HttpMethod } from './api';\n\nexport type { HttpMethod };\n\ntype Json = Record<string, unknown>;\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** What a handler needs: `ChannlizeApi` from this package, or your own. */\nexport interface InboxRequester {\n request<T>(\n method: HttpMethod,\n path: string,\n options?: { query?: Query; body?: unknown }\n ): Promise<T>;\n}\n\n/** The API key scope a tool needs. `routing:read` is granted by `routing:write`, `inbox:read` or `read`. */\nexport type InboxScope =\n | 'inbox:read'\n | 'inbox:write'\n | 'routing:read'\n | 'routing:write'\n | 'analytics:read';\n\nexport interface ToolAnnotations {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface InboxTool<\n I extends z.ZodObject = z.ZodObject,\n O extends z.ZodObject = z.ZodObject,\n> {\n name: string;\n title: string;\n description: string;\n scope: InboxScope;\n /** The HTTP endpoint it calls, for docs and logs. */\n endpoint: string;\n inputSchema: I;\n outputSchema: O;\n annotations: ToolAnnotations;\n handler: (api: InboxRequester, args: z.output<I>) => Promise<z.output<O>>;\n}\n\nfunction defineTool<I extends z.ZodObject, O extends z.ZodObject>(\n tool: InboxTool<I, O>\n): InboxTool {\n return tool as unknown as InboxTool;\n}\n\n// ----- shared schemas -----\n\nexport const CONVERSATION_STATUSES = [\n 'open',\n 'pending',\n 'resolved',\n 'closed',\n] as const;\nexport const CONVERSATION_PRIORITIES = [\n 'low',\n 'normal',\n 'high',\n 'urgent',\n] as const;\nexport const CONVERSATION_SORTS = [\n 'recent',\n 'oldest',\n 'newest',\n 'priority',\n] as const;\n\nconst loose = z.looseObject({});\nconst id = (what: string) => z.string().uuid().describe(`${what} ID`);\nconst conversationId = id(\n 'Conversation (from list_conversations or search_conversations)'\n);\n\nconst person = z\n .looseObject({\n id: z.string(),\n name: z.string().nullable().optional(),\n email: z.string().nullable().optional(),\n })\n .nullable();\n\nconst conversationSummary = z.looseObject({\n id: z.string(),\n subject: z.string().nullable().optional(),\n status: z.string(),\n priority: z.string().optional(),\n source: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n customer: person.optional(),\n assignee: person.optional(),\n triage: z.record(z.string(), z.unknown()).nullable().optional(),\n needsReply: z.boolean().optional(),\n lastActivityAt: z.string().nullable().optional(),\n});\n\nconst page = {\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n hasMore: z.boolean(),\n};\n\nconst listOutput = z.object({\n conversations: z.array(conversationSummary),\n ...page,\n});\n\nconst changeOutput = z.object({\n conversation: z.looseObject({ id: z.string(), status: z.string() }),\n changes: z.array(z.string()),\n});\n\nconst slackOutcome = z\n .string()\n .describe(\n 'Slack mirror: queued, posted, no_thread (not in Slack), not_connected or failed'\n );\n\nconst tags = z.array(z.string().trim().min(1).max(50)).max(20);\n\nconst paging = {\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .default(20)\n .describe('How many to return (1-100, default 20)'),\n offset: z.number().int().min(0).default(0).describe('Skip this many'),\n};\n\n/** One value or a list of them (sent as a comma list). */\nfunction oneOrMany<T extends readonly [string, ...string[]]>(values: T) {\n return z.union([z.enum(values), z.array(z.enum(values)).max(10)]).optional();\n}\n\nconst asList = (v: string | string[] | undefined) =>\n v === undefined ? undefined : Array.isArray(v) ? v.join(',') : v;\n\nconst listFilters = {\n status: oneOrMany(CONVERSATION_STATUSES).describe(\n 'Only these statuses: \"open\" or a list like [\"open\", \"pending\"]'\n ),\n priority: oneOrMany(CONVERSATION_PRIORITIES).describe(\n 'Only these priorities: \"urgent\" or a list'\n ),\n assignee: z\n .string()\n .max(255)\n .optional()\n .describe('A teammate (user ID or email), \"unassigned\" or \"assigned\"'),\n projectId: id('Project').optional(),\n updatedSince: z.iso\n .datetime({ offset: true })\n .optional()\n .describe('Updated or with a new message since (ISO 8601)'),\n tag: z.string().max(50).optional().describe('Carrying this tag'),\n source: z\n .string()\n .max(32)\n .optional()\n .describe('Entry point, e.g. live_chat, contact_form, api, email'),\n intent: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage intent (triaged conversations only)'),\n urgency: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage urgency (triaged conversations only)'),\n sort: z\n .enum(CONVERSATION_SORTS)\n .optional()\n .describe(\n 'recent (last activity, default), oldest, newest (created) or priority'\n ),\n};\n\ntype ListArgs = {\n status?: string | string[];\n priority?: string | string[];\n assignee?: string;\n projectId?: string;\n updatedSince?: string;\n tag?: string;\n source?: string;\n intent?: string;\n urgency?: string;\n sort?: string;\n limit: number;\n offset: number;\n q?: string;\n};\n\nasync function list(api: InboxRequester, a: ListArgs) {\n const r = await api.request<Json>('GET', '/v1/conversations', {\n query: {\n status: asList(a.status),\n priority: asList(a.priority),\n assignee: a.assignee,\n projectId: a.projectId,\n updatedSince: a.updatedSince,\n tag: a.tag,\n source: a.source,\n intent: a.intent,\n urgency: a.urgency,\n sort: a.sort,\n q: a.q,\n limit: a.limit,\n offset: a.offset,\n },\n });\n return {\n conversations:\n (r.conversations as z.output<typeof conversationSummary>[]) ?? [],\n total: Number(r.total ?? 0),\n limit: Number(r.limit ?? a.limit),\n offset: Number(r.offset ?? a.offset),\n hasMore: Boolean(r.hasMore),\n };\n}\n\nconst path = (cid: string, rest = '') =>\n `/v1/conversations/${encodeURIComponent(cid)}${rest}`;\n\nasync function patch(api: InboxRequester, cid: string, body: Json) {\n const r = await api.request<{ conversation: Json; changes?: string[] }>(\n 'PATCH',\n path(cid),\n { body }\n );\n return {\n conversation: r.conversation as { id: string; status: string },\n changes: r.changes ?? [],\n };\n}\n\nconst READ: ToolAnnotations = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n};\nconst CHANGE: ToolAnnotations = {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n};\n\n// ----- routing rules -----\n\nexport const ROUTING_FIELDS = [\n 'source',\n 'project',\n 'widget',\n 'connection',\n 'page_url',\n 'page_domain',\n 'customer_email',\n 'customer_email_domain',\n 'message',\n 'subject',\n 'tags',\n 'priority',\n 'language',\n] as const;\nexport const ROUTING_OPERATORS = [\n 'equals',\n 'not_equals',\n 'contains',\n 'not_contains',\n 'starts_with',\n 'ends_with',\n 'matches',\n] as const;\n\nconst routingConditions = z\n .object({\n match: z\n .enum(['all', 'any'])\n .describe('all = every condition holds, any = one is enough'),\n conditions: z\n .array(\n z.object({\n field: z.enum(ROUTING_FIELDS),\n operator: z.enum(ROUTING_OPERATORS),\n value: z\n .union([z.string(), z.array(z.string())])\n .describe(\n 'A value, or a list (positive operators: any matches; negative: none does). Case-insensitive; matches uses * and ? wildcards.'\n ),\n })\n )\n .max(50)\n .describe('Empty = matches every new conversation (catch-all)'),\n })\n .describe(\n 'Which new conversations the rule catches, tested on the first message, e.g. { \"match\": \"any\", \"conditions\": [{ \"field\": \"message\", \"operator\": \"contains\", \"value\": [\"refund\", \"invoice\"] }] }'\n );\n\nconst ruleFields = {\n name: z.string().trim().min(1).max(255).describe('Rule name'),\n targetChannelId: z\n .string()\n .regex(/^[A-Z0-9]{2,30}$/, 'a Slack channel ID like C0123ABCD')\n .describe('Slack channel ID to post matching conversations to'),\n conditions: routingConditions,\n projectId: id('Project (default: the oldest project)').optional(),\n widgetId: id('Widget').nullable().optional(),\n integrationId: id('Integration (default: the Slack workspace)').optional(),\n assignToUserId: id('Teammate to assign matching conversations to')\n .nullable()\n .optional(),\n isActive: z.boolean().optional().describe('Default true'),\n priority: z\n .number()\n .int()\n .min(-1_000_000)\n .max(1_000_000)\n .optional()\n .describe('Higher runs first (default: after every rule)'),\n};\n\nconst rule = z.looseObject({\n id: z.string(),\n name: z.string(),\n priority: z.number(),\n is_active: z.boolean(),\n target_channel_id: z.string(),\n});\n\n// ----- the tools -----\n\nexport const INBOX_TOOLS: readonly InboxTool[] = [\n defineTool({\n name: 'list_conversations',\n title: 'List conversations',\n description:\n 'List support conversations, most recent activity first, filtered by status, priority, assignee, project, tag, source, last update or AI triage (intent/urgency). Each row has the customer, assignee, needsReply (the customer spoke last) and a preview of the latest message. Use get_conversation for the thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations',\n inputSchema: z.object({ ...listFilters, ...paging }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, a) => list(api, a),\n }),\n\n defineTool({\n name: 'search_conversations',\n title: 'Search conversations',\n description:\n 'Find conversations whose subject, customer name or email, or any message contains the text (case-insensitive), with the same filters as list_conversations. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations?q=',\n inputSchema: z.object({\n query: z.string().trim().min(1).max(200).describe('Text to look for'),\n ...listFilters,\n ...paging,\n }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, { query, ...a }) => list(api, { ...a, q: query }),\n }),\n\n defineTool({\n name: 'get_conversation',\n title: 'Get conversation',\n description:\n 'Read one conversation with its full thread: customer, agent, system and bot messages in order, internal notes included (isInternal: true, never seen by the customer), plus status, priority, tags, assignee, customer, triage and its Slack thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: z.object({\n conversation: z.looseObject({\n id: z.string(),\n status: z.string(),\n messages: z.array(loose),\n }),\n }),\n annotations: READ,\n handler: async (api, { conversationId: cid }) => {\n const r = await api.request<{ conversation: Json }>('GET', path(cid));\n return { conversation: r.conversation as never };\n },\n }),\n\n defineTool({\n name: 'reply_to_conversation',\n title: 'Reply to conversation',\n description:\n 'Send a reply to the customer, as this API key (shown as \"<key name> (agent)\"). It reaches the customer like a dashboard reply (chat widget, email, SMS, ...) and is mirrored to the Slack thread; an open conversation becomes pending. Confirm the text with the user before sending. For a note only the team sees, use add_internal_note. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/reply',\n inputSchema: z.object({\n conversationId,\n content: z\n .string()\n .trim()\n .min(1)\n .max(10_000)\n .describe('Reply text, as the customer will read it'),\n }),\n outputSchema: z.object({\n message: z.looseObject({ id: z.string() }),\n conversationStatus: z.string(),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/reply'), {\n body: { content },\n });\n return {\n message: r.message as { id: string },\n conversationStatus: String(r.conversationStatus ?? ''),\n slack: String(r.slack ?? ''),\n };\n },\n }),\n\n defineTool({\n name: 'add_internal_note',\n title: 'Add internal note',\n description:\n 'Add an internal note to a conversation: posted in its Slack thread for the team and kept in the conversation, never sent to the customer. Use it for findings, handoffs and suggested replies. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/notes',\n inputSchema: z.object({\n conversationId,\n content: z.string().trim().min(1).max(10_000).describe('Note text'),\n }),\n outputSchema: z.object({\n note: z.looseObject({ id: z.string() }),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/notes'), {\n body: { content },\n });\n return { note: r.note as { id: string }, slack: String(r.slack ?? '') };\n },\n }),\n\n defineTool({\n name: 'assign_conversation',\n title: 'Assign conversation',\n description:\n 'Assign a conversation to a teammate (by user ID or email; owners, admins and agents, not viewers), or unassign it with null. The teammate is notified, and the change shows in the timeline and Slack. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n assignee: z\n .string()\n .min(1)\n .max(255)\n .nullable()\n .describe('User ID or email of the teammate, or null to unassign'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, assignee }) =>\n patch(\n api,\n cid,\n assignee === null\n ? { assigneeId: null }\n : assignee.includes('@')\n ? { assigneeEmail: assignee }\n : { assigneeId: assignee }\n ),\n }),\n\n defineTool({\n name: 'tag_conversation',\n title: 'Tag conversation',\n description:\n 'Add or remove tags on a conversation, or replace them all with `set`. Tags are lowercased; at most 20. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n add: tags.optional().describe('Tags to add'),\n remove: tags.optional().describe('Tags to remove'),\n set: tags\n .optional()\n .describe('Replace every tag with these ([] clears them)'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, add, remove, set }) => {\n if (!add?.length && !remove?.length && !set) {\n throw new ToolError('Pass tags to add, remove, or set.');\n }\n return patch(api, cid, {\n ...(set ? { tags: set } : {}),\n ...(add?.length ? { addTags: add } : {}),\n ...(remove?.length ? { removeTags: remove } : {}),\n });\n },\n }),\n\n defineTool({\n name: 'close_conversation',\n title: 'Close conversation',\n description:\n 'Close a conversation (or mark it resolved). Nothing is sent to the customer; the change shows in the timeline and the Slack root post. Reply first if the customer should hear back. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n status: z\n .enum(['closed', 'resolved'])\n .default('closed')\n .describe('closed (default) or resolved'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, status }) =>\n patch(api, cid, { status }),\n }),\n\n defineTool({\n name: 'reopen_conversation',\n title: 'Reopen conversation',\n description:\n 'Reopen a closed or resolved conversation (status open). Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid }) =>\n patch(api, cid, { status: 'open' }),\n }),\n\n defineTool({\n name: 'get_analytics_summary',\n title: 'Get analytics summary',\n description:\n 'Support KPIs for a period (default the last 30 days): conversations started and resolved, median first-response and resolution times, messages in and out, each with the previous period; plus the inbox right now (open, pending, unassigned, urgent, awaiting a reply). Needs the analytics:read (or read) scope.',\n scope: 'analytics:read',\n endpoint: 'GET /v1/analytics/summary',\n inputSchema: z.object({\n days: z\n .number()\n .int()\n .min(1)\n .max(366)\n .optional()\n .describe('Period length in days, ending today (default 30)'),\n from: z.iso.date().optional().describe('First day, YYYY-MM-DD'),\n to: z.iso.date().optional().describe('Last day, YYYY-MM-DD'),\n tz: z\n .string()\n .max(64)\n .optional()\n .describe('IANA time zone for day boundaries (default UTC)'),\n projectId: id('Project').optional(),\n }),\n outputSchema: z.looseObject({ range: loose, kpis: loose, inbox: loose }),\n annotations: READ,\n handler: async (api, args) =>\n api.request<{ range: Json; kpis: Json; inbox: Json }>(\n 'GET',\n '/v1/analytics/summary',\n { query: args }\n ),\n }),\n\n defineTool({\n name: 'list_routing_rules',\n title: 'List routing rules',\n description:\n 'List the routing rules in evaluation order (highest priority first): which new conversations each one sends to which Slack channel, and who it assigns. Needs the routing:write, inbox:read or read scope.',\n scope: 'routing:read',\n endpoint: 'GET /v1/routing-rules',\n inputSchema: z.object({}),\n outputSchema: z.object({ rules: z.array(rule) }),\n annotations: READ,\n handler: async api => {\n const r = await api.request<{ routing_rules: Json[] }>(\n 'GET',\n '/v1/routing-rules'\n );\n return { rules: (r.routing_rules ?? []) as never };\n },\n }),\n\n defineTool({\n name: 'create_routing_rule',\n title: 'Create routing rule',\n description:\n 'Create a routing rule: new conversations whose first message matches `conditions` are posted to `targetChannelId` instead of the default channel (and optionally assigned). It runs after the existing rules unless `priority` is given. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'POST /v1/routing-rules',\n inputSchema: z.object(ruleFields),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n handler: async (api, body) => {\n const r = await api.request<{ routing_rule: Json }>(\n 'POST',\n '/v1/routing-rules',\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'update_routing_rule',\n title: 'Update routing rule',\n description:\n 'Change a routing rule (from list_routing_rules); omitted fields keep their value, and `conditions` replaces the whole condition group. Use isActive: false to pause a rule. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'PATCH /v1/routing-rules/:id',\n inputSchema: z.object({\n ruleId: id('Routing rule'),\n name: ruleFields.name.optional(),\n targetChannelId: ruleFields.targetChannelId.optional(),\n conditions: routingConditions.optional(),\n projectId: ruleFields.projectId,\n widgetId: ruleFields.widgetId,\n integrationId: ruleFields.integrationId,\n assignToUserId: ruleFields.assignToUserId,\n isActive: ruleFields.isActive,\n priority: ruleFields.priority,\n }),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId, ...body }) => {\n if (Object.values(body).every(v => v === undefined)) {\n throw new ToolError('Pass at least one field to change.');\n }\n const r = await api.request<{ routing_rule: Json }>(\n 'PATCH',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`,\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'delete_routing_rule',\n title: 'Delete routing rule',\n description:\n 'Delete a routing rule for good. To stop it temporarily, use update_routing_rule with isActive: false instead. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'DELETE /v1/routing-rules/:id',\n inputSchema: z.object({ ruleId: id('Routing rule') }),\n outputSchema: z.object({ deleted: z.boolean(), ruleId: z.string() }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId }) => {\n await api.request(\n 'DELETE',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`\n );\n return { deleted: true, ruleId };\n },\n }),\n];\n\nexport const INBOX_TOOL_NAMES = INBOX_TOOLS.map(t => t.name);\n\nexport function getInboxTool(name: string): InboxTool | undefined {\n return INBOX_TOOLS.find(t => t.name === name);\n}\n\n/**\n * Validate `args` against the tool's input schema and run it. Throws\n * `ToolError` for an unknown tool or invalid arguments, and for API\n * failures (`toToolError` in `api.ts` words them for the model).\n */\nexport async function runInboxTool(\n api: InboxRequester,\n name: string,\n args: unknown\n): Promise<Json> {\n const tool = getInboxTool(name);\n if (!tool) throw new ToolError(`Unknown inbox tool \"${name}\"`);\n const parsed = tool.inputSchema.safeParse(args ?? {});\n if (!parsed.success) {\n throw new ToolError(\n `Invalid arguments for ${name}: ${parsed.error.issues\n .map(i => `${i.path.join('.') || 'input'}: ${i.message}`)\n .join('; ')}`\n );\n }\n return (await tool.handler(api, parsed.data)) as Json;\n}\n\n/**\n * The tools as JSON Schema function definitions, for LLM APIs that take\n * `{ name, description, parameters }` (OpenAI / OpenRouter style).\n */\nexport function inboxToolsAsJsonSchema(\n filter: (tool: InboxTool) => boolean = () => true\n): Array<{ name: string; description: string; parameters: Json }> {\n return INBOX_TOOLS.filter(filter).map(t => ({\n name: t.name,\n description: t.description,\n parameters: z.toJSONSchema(t.inputSchema, { io: 'input' }) as Json,\n }));\n}\n","import {\n ChannlizeClient,\n ChannlizeError,\n parseApiError,\n} from '@channlize/core';\nimport type { ResolvedConfig } from './config';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\n/**\n * A failed Channlize call, worded for the model: what went wrong and how\n * to fix it (wrong key, missing scope, unknown id). Tool handlers throw it\n * and the MCP server returns it as a tool error (`isError: true`).\n */\nexport class ToolError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public code?: string,\n /** ISO time the call may succeed again (agent policies, takeovers). */\n public retryAt?: string\n ) {\n super(message);\n this.name = 'ToolError';\n }\n}\n\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** The scope to add for an \"API key lacks required permission: <perm>\" 403. */\nfunction scopeFor(apiMessage: string): string {\n const perm = /permission:\\s*([\\w:*-]+)/i.exec(apiMessage)?.[1] ?? '';\n switch (perm) {\n case 'inbox:read':\n return 'inbox:read (or read)';\n case 'inbox:write':\n return 'inbox:write';\n case 'routing:read':\n return 'routing:write, inbox:read (or read)';\n case 'routing:write':\n return 'routing:write';\n case 'analytics:read':\n return 'analytics:read (or read)';\n case 'agents:send':\n return 'agents:send';\n case 'agents:read':\n return 'agents:read';\n case 'agents:ask_human':\n return 'agents:ask_human';\n case 'agents:admin':\n return 'agents:admin (an unbound key)';\n default:\n return /write|notify/.test(apiMessage) ? 'write' : 'read';\n }\n}\n\n/** \" Retry after <time>.\" for a refusal that names one. */\nfunction retryText(retryAt: string | undefined): string {\n return retryAt ? ` Retry after ${retryAt}, not before.` : '';\n}\n\n/** Hints for the Channlize for Agents error codes. */\nfunction agentHint(\n code: string | undefined,\n body: Record<string, unknown> = {}\n): string | undefined {\n const retryAt = typeof body.retryAt === 'string' ? body.retryAt : undefined;\n switch (code) {\n case 'AGENT_KEY_REQUIRED':\n return 'Agent tools need an agent key or a multi-agent key: create one under SDK & Integration → API keys and pick the agents.';\n case 'AGENT_NOT_ALLOWED':\n return \"The key can't act as that agent: pass one of the key's agents as agentId (list_agents), or fix CHANNLIZE_AGENT_ID.\";\n case 'NOT_A_CHANNEL_MEMBER':\n return 'Only channel members can post (observers only read): use list_agent_channels, or ask an admin to add the agent to the channel.';\n case 'AGENT_PAUSED':\n case 'AGENT_RETIRED':\n return 'Only active agents can act: ask an admin to reactivate it in the dashboard.';\n case 'AGENT_RATE_LIMITED':\n return 'The agent is sending too fast: wait a minute before sending again.';\n case 'AGENT_THREAD_PAUSED': {\n const why =\n body.reason === 'ping_pong'\n ? ' (two agents were going back and forth)'\n : body.reason === 'duplicate_content'\n ? ' (the same message kept being posted)'\n : '';\n return `The loop guard paused this thread${why}: stop sending in it until a person replies or resumes it. Don't rephrase and retry.`;\n }\n case 'HUMAN_TAKEOVER':\n return `A person took over this thread: don't post in it until the takeover ends or they hand it back.${retryText(retryAt)}`;\n case 'POLICY_RATE_LIMITED':\n case 'POLICY_BUDGET_EXHAUSTED':\n case 'POLICY_OUTSIDE_WORKING_HOURS':\n return `The agent's policy refuses this for now: wait, don't retry in a loop.${retryText(retryAt)}`;\n case 'POLICY_CHANNEL_NOT_ALLOWED':\n case 'POLICY_DM_NOT_ALLOWED':\n case 'POLICY_ASK_HUMAN_NOT_ALLOWED':\n case 'POLICY_URGENCY_NOT_ALLOWED':\n case 'POLICY_MAX_DEPTH':\n return \"The agent's policy forbids this: retrying won't help. Pick another channel, agent or urgency, or ask an admin to change the policy.\";\n case 'TASK_CLOSED':\n return 'The task is closed (completed, failed, canceled or rejected) and cannot change: create a new task instead.';\n case 'ALREADY_ASSIGNED':\n return 'The task is already assigned to that agent.';\n case 'AGENT_NOT_IN_CHANNEL':\n return \"The other agent isn't a member of the task's channel: pick a member (list_agent_channels), or ask an admin to add it.\";\n case 'NOT_TASK_PARTICIPANT':\n return \"Only the task's assignee or creator can change it or hand it off.\";\n case 'QUESTION_CLOSED':\n return 'The question is already closed (answered, expired or canceled): the first answer wins. Read it with get_answer.';\n case 'APPROVAL_ROLE_REQUIRED':\n return 'This approval needs a person with the required role: an agent cannot answer it.';\n case 'AGENT_ADMIN_KEY_REQUIRED':\n return 'This needs an unbound agents:admin key (not a key bound to agents): create one under SDK & Integration → API keys.';\n case 'INVALID_ANSWER':\n return \"Answer with one of the question's option ids (approve / deny for approvals), or text when free text is allowed.\";\n default:\n return undefined;\n }\n}\n\nfunction hintFor(\n status: number,\n apiMessage: string,\n code?: string,\n body?: Record<string, unknown>\n): string {\n const agent = agentHint(code, body);\n if (agent) return `${apiMessage}. ${agent}`;\n if (status === 400 && /agent_id is required/i.test(apiMessage)) {\n return `${apiMessage}. Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server.`;\n }\n switch (status) {\n case 401:\n return `${apiMessage}. The server API key is invalid, revoked or expired: create a new one under SDK & Integration → API keys and update CHANNLIZE_SERVER_API_KEY.`;\n case 403:\n return /permission/i.test(apiMessage)\n ? `${apiMessage}. This needs a server API key with the ${scopeFor(\n apiMessage\n )} scope: create one under SDK & Integration → API keys.`\n : apiMessage;\n case 404:\n return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;\n case 429:\n return `${apiMessage}. Rate limited: wait a little before retrying.`;\n default:\n return apiMessage;\n }\n}\n\n/** Maps a `@channlize/core` error to a `ToolError` with the same hints. */\nexport function toToolError(err: unknown): ToolError {\n if (err instanceof ToolError) return err;\n if (err instanceof ChannlizeError) {\n return new ToolError(\n err.statusCode\n ? hintFor(\n err.statusCode,\n err.message.replace(/\\.$/, ''),\n err.code,\n err.details\n )\n : err.message,\n err.statusCode,\n err.code,\n err.retryAt\n );\n }\n return new ToolError(err instanceof Error ? err.message : String(err));\n}\n\n/**\n * Thin client for the API-key endpoints `@channlize/core` doesn't wrap\n * (projects, the inbox, routing rules, contacts, Events and log reads,\n * agents). Widget-key\n * calls and `notify` go through `@channlize/core`.\n */\nexport class ChannlizeApi {\n private projectIdPromise?: Promise<string>;\n private widgetClient?: ChannlizeClient;\n\n constructor(readonly config: ResolvedConfig) {}\n\n get hasServerKey(): boolean {\n return !!this.config.serverApiKey;\n }\n\n get hasWidgetKey(): boolean {\n return !!this.config.widgetKey;\n }\n\n /** The default agent for the agent tools (`CHANNLIZE_AGENT_ID`). */\n get agentId(): string | undefined {\n return this.config.agentId;\n }\n\n private requireServerKey(): string {\n if (!this.config.serverApiKey) {\n throw new ToolError(\n 'This tool needs a server API key (ch_live_…): set CHANNLIZE_SERVER_API_KEY or pass --server-api-key.'\n );\n }\n return this.config.serverApiKey;\n }\n\n async request<T>(\n method: HttpMethod,\n path: string,\n options: {\n query?: Query;\n body?: unknown;\n headers?: Record<string, string>;\n /** Overrides the configured timeout, in ms (long-polls). */\n timeout?: number;\n } = {}\n ): Promise<T> {\n const key = this.requireServerKey();\n const timeout = options.timeout ?? this.config.timeout;\n const url = new URL(`${this.config.baseUrl}${path}`);\n for (const [k, v] of Object.entries(options.query ?? {})) {\n if (v !== undefined && v !== '') url.searchParams.set(k, String(v));\n }\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${key}`,\n Accept: 'application/json',\n ...(options.body !== undefined\n ? { 'Content-Type': 'application/json' }\n : {}),\n ...options.headers,\n },\n // oxlint-disable-next-line unicorn/no-invalid-fetch-options -- `method` is dynamic; GET calls never pass a body.\n body:\n options.body !== undefined ? JSON.stringify(options.body) : undefined,\n signal: AbortSignal.timeout(timeout),\n });\n } catch (err) {\n const e = err as Error;\n if (e.name === 'TimeoutError' || e.name === 'AbortError') {\n throw new ToolError(\n `Channlize API timed out after ${timeout} ms (${method} ${url.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,\n 408,\n 'TIMEOUT'\n );\n }\n throw new ToolError(\n `Could not reach the Channlize API at ${this.config.baseUrl}: ${e.message}`\n );\n }\n\n const text = await res.text();\n let data: unknown = undefined;\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = undefined;\n }\n }\n\n if (!res.ok) {\n const { message, code, body } = parseApiError(\n res.status,\n data,\n res.statusText\n );\n const retryAt =\n typeof body.retryAt === 'string' ? body.retryAt : undefined;\n throw new ToolError(\n hintFor(res.status, message.replace(/\\.$/, ''), code, body),\n res.status,\n code,\n retryAt\n );\n }\n if (data === undefined) {\n throw new ToolError(\n `Unexpected non-JSON response from ${method} ${url.pathname}`\n );\n }\n return data as T;\n }\n\n /**\n * `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization\n * (`GET /projects/me`), looked up once.\n */\n projectId(): Promise<string> {\n if (this.config.projectId) return Promise.resolve(this.config.projectId);\n this.projectIdPromise ??= this.request<{ project: { id: string } }>(\n 'GET',\n '/projects/me'\n )\n .then(r => {\n if (!r.project?.id) {\n throw new ToolError(\n 'Could not find a project for this key: set CHANNLIZE_PROJECT_ID.'\n );\n }\n return r.project.id;\n })\n .catch(err => {\n this.projectIdPromise = undefined;\n throw err;\n });\n return this.projectIdPromise;\n }\n\n /** `@channlize/core` client bound to the widget key (visitor tools). */\n widget(): ChannlizeClient {\n if (!this.config.widgetKey) {\n throw new ToolError(\n 'This tool needs the public widget key (wk_…): set CHANNLIZE_WIDGET_KEY or pass --widget-key.'\n );\n }\n this.widgetClient ??= new ChannlizeClient({\n apiKey: this.config.widgetKey,\n projectId: this.config.projectId ?? '',\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n return this.widgetClient;\n }\n\n /** `@channlize/core` client for `notify` (server key + project). */\n async notifier(): Promise<ChannlizeClient> {\n const serverApiKey = this.requireServerKey();\n return new ChannlizeClient({\n // core requires a widget key; notify only uses serverApiKey.\n apiKey: this.config.widgetKey ?? 'unused',\n serverApiKey,\n projectId: await this.projectId(),\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/inbox.ts","../src/api.ts"],"sourcesContent":["/**\n * The Channlize inbox toolset: names, zod input/output schemas and handlers\n * for working the inbox through the API-key endpoints under `/v1`.\n *\n * This module has no dependency on the MCP server or its transports, so any\n * agent can import it (`@channlize/mcp/inbox`): the stdio/HTTP server\n * registers these tools (`registerInboxTools` in `tools.ts`), and the\n * dashboard copilot or an internal bot can run the same handlers with its own\n * client (anything with a `request(method, path, { query, body })`).\n *\n * Writes act as a named agent: the API key's name. Replies show in Slack as\n * \"Replied by <key name> (agent)\", and changes as \"… by <key name> (agent)\".\n *\n * Scopes (pick them when creating the key): `inbox:read`, `inbox:write`,\n * `routing:write`, `analytics:read`. A `read` key can use the read tools.\n */\nimport * as z from 'zod/v4';\nimport { ToolError, type HttpMethod } from './api';\n\nexport type { HttpMethod };\n\ntype Json = Record<string, unknown>;\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** What a handler needs: `ChannlizeApi` from this package, or your own. */\nexport interface InboxRequester {\n request<T>(\n method: HttpMethod,\n path: string,\n options?: { query?: Query; body?: unknown }\n ): Promise<T>;\n}\n\n/** The API key scope a tool needs. `routing:read` is granted by `routing:write`, `inbox:read` or `read`. */\nexport type InboxScope =\n | 'inbox:read'\n | 'inbox:write'\n | 'routing:read'\n | 'routing:write'\n | 'analytics:read';\n\nexport interface ToolAnnotations {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface InboxTool<\n I extends z.ZodObject = z.ZodObject,\n O extends z.ZodObject = z.ZodObject,\n> {\n name: string;\n title: string;\n description: string;\n scope: InboxScope;\n /** The HTTP endpoint it calls, for docs and logs. */\n endpoint: string;\n inputSchema: I;\n outputSchema: O;\n annotations: ToolAnnotations;\n handler: (api: InboxRequester, args: z.output<I>) => Promise<z.output<O>>;\n}\n\nfunction defineTool<I extends z.ZodObject, O extends z.ZodObject>(\n tool: InboxTool<I, O>\n): InboxTool {\n return tool as unknown as InboxTool;\n}\n\n// ----- shared schemas -----\n\nexport const CONVERSATION_STATUSES = [\n 'open',\n 'pending',\n 'resolved',\n 'closed',\n] as const;\nexport const CONVERSATION_PRIORITIES = [\n 'low',\n 'normal',\n 'high',\n 'urgent',\n] as const;\nexport const CONVERSATION_SORTS = [\n 'recent',\n 'oldest',\n 'newest',\n 'priority',\n] as const;\n\nconst loose = z.looseObject({});\nconst id = (what: string) => z.string().uuid().describe(`${what} ID`);\nconst conversationId = id(\n 'Conversation (from list_conversations or search_conversations)'\n);\n\nconst person = z\n .looseObject({\n id: z.string(),\n name: z.string().nullable().optional(),\n email: z.string().nullable().optional(),\n })\n .nullable();\n\nconst conversationSummary = z.looseObject({\n id: z.string(),\n subject: z.string().nullable().optional(),\n status: z.string(),\n priority: z.string().optional(),\n source: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n customer: person.optional(),\n assignee: person.optional(),\n triage: z.record(z.string(), z.unknown()).nullable().optional(),\n needsReply: z.boolean().optional(),\n lastActivityAt: z.string().nullable().optional(),\n});\n\nconst page = {\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n hasMore: z.boolean(),\n};\n\nconst listOutput = z.object({\n conversations: z.array(conversationSummary),\n ...page,\n});\n\nconst changeOutput = z.object({\n conversation: z.looseObject({ id: z.string(), status: z.string() }),\n changes: z.array(z.string()),\n});\n\nconst slackOutcome = z\n .string()\n .describe(\n 'Slack mirror: queued, posted, no_thread (not in Slack), not_connected or failed'\n );\n\nconst tags = z.array(z.string().trim().min(1).max(50)).max(20);\n\nconst paging = {\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .default(20)\n .describe('How many to return (1-100, default 20)'),\n offset: z.number().int().min(0).default(0).describe('Skip this many'),\n};\n\n/** One value or a list of them (sent as a comma list). */\nfunction oneOrMany<T extends readonly [string, ...string[]]>(values: T) {\n return z.union([z.enum(values), z.array(z.enum(values)).max(10)]).optional();\n}\n\nconst asList = (v: string | string[] | undefined) =>\n v === undefined ? undefined : Array.isArray(v) ? v.join(',') : v;\n\nconst listFilters = {\n status: oneOrMany(CONVERSATION_STATUSES).describe(\n 'Only these statuses: \"open\" or a list like [\"open\", \"pending\"]'\n ),\n priority: oneOrMany(CONVERSATION_PRIORITIES).describe(\n 'Only these priorities: \"urgent\" or a list'\n ),\n assignee: z\n .string()\n .max(255)\n .optional()\n .describe('A teammate (user ID or email), \"unassigned\" or \"assigned\"'),\n projectId: id('Project').optional(),\n updatedSince: z.iso\n .datetime({ offset: true })\n .optional()\n .describe('Updated or with a new message since (ISO 8601)'),\n tag: z.string().max(50).optional().describe('Carrying this tag'),\n source: z\n .string()\n .max(32)\n .optional()\n .describe('Entry point, e.g. live_chat, contact_form, api, email'),\n intent: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage intent (triaged conversations only)'),\n urgency: z\n .string()\n .max(64)\n .optional()\n .describe('AI triage urgency (triaged conversations only)'),\n sort: z\n .enum(CONVERSATION_SORTS)\n .optional()\n .describe(\n 'recent (last activity, default), oldest, newest (created) or priority'\n ),\n};\n\ntype ListArgs = {\n status?: string | string[];\n priority?: string | string[];\n assignee?: string;\n projectId?: string;\n updatedSince?: string;\n tag?: string;\n source?: string;\n intent?: string;\n urgency?: string;\n sort?: string;\n limit: number;\n offset: number;\n q?: string;\n};\n\nasync function list(api: InboxRequester, a: ListArgs) {\n const r = await api.request<Json>('GET', '/v1/conversations', {\n query: {\n status: asList(a.status),\n priority: asList(a.priority),\n assignee: a.assignee,\n projectId: a.projectId,\n updatedSince: a.updatedSince,\n tag: a.tag,\n source: a.source,\n intent: a.intent,\n urgency: a.urgency,\n sort: a.sort,\n q: a.q,\n limit: a.limit,\n offset: a.offset,\n },\n });\n return {\n conversations:\n (r.conversations as z.output<typeof conversationSummary>[]) ?? [],\n total: Number(r.total ?? 0),\n limit: Number(r.limit ?? a.limit),\n offset: Number(r.offset ?? a.offset),\n hasMore: Boolean(r.hasMore),\n };\n}\n\nconst path = (cid: string, rest = '') =>\n `/v1/conversations/${encodeURIComponent(cid)}${rest}`;\n\nasync function patch(api: InboxRequester, cid: string, body: Json) {\n const r = await api.request<{ conversation: Json; changes?: string[] }>(\n 'PATCH',\n path(cid),\n { body }\n );\n return {\n conversation: r.conversation as { id: string; status: string },\n changes: r.changes ?? [],\n };\n}\n\nconst READ: ToolAnnotations = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n};\nconst CHANGE: ToolAnnotations = {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n};\n\n// ----- routing rules -----\n\nexport const ROUTING_FIELDS = [\n 'source',\n 'project',\n 'widget',\n 'connection',\n 'page_url',\n 'page_domain',\n 'customer_email',\n 'customer_email_domain',\n 'message',\n 'subject',\n 'tags',\n 'priority',\n 'language',\n] as const;\nexport const ROUTING_OPERATORS = [\n 'equals',\n 'not_equals',\n 'contains',\n 'not_contains',\n 'starts_with',\n 'ends_with',\n 'matches',\n] as const;\n\nconst routingConditions = z\n .object({\n match: z\n .enum(['all', 'any'])\n .describe('all = every condition holds, any = one is enough'),\n conditions: z\n .array(\n z.object({\n field: z.enum(ROUTING_FIELDS),\n operator: z.enum(ROUTING_OPERATORS),\n value: z\n .union([z.string(), z.array(z.string())])\n .describe(\n 'A value, or a list (positive operators: any matches; negative: none does). Case-insensitive; matches uses * and ? wildcards.'\n ),\n })\n )\n .max(50)\n .describe('Empty = matches every new conversation (catch-all)'),\n })\n .describe(\n 'Which new conversations the rule catches, tested on the first message, e.g. { \"match\": \"any\", \"conditions\": [{ \"field\": \"message\", \"operator\": \"contains\", \"value\": [\"refund\", \"invoice\"] }] }'\n );\n\nconst ruleFields = {\n name: z.string().trim().min(1).max(255).describe('Rule name'),\n targetChannelId: z\n .string()\n .regex(/^[A-Z0-9]{2,30}$/, 'a Slack channel ID like C0123ABCD')\n .describe('Slack channel ID to post matching conversations to'),\n conditions: routingConditions,\n projectId: id('Project (default: the oldest project)').optional(),\n widgetId: id('Widget').nullable().optional(),\n integrationId: id('Integration (default: the Slack workspace)').optional(),\n assignToUserId: id('Teammate to assign matching conversations to')\n .nullable()\n .optional(),\n isActive: z.boolean().optional().describe('Default true'),\n priority: z\n .number()\n .int()\n .min(-1_000_000)\n .max(1_000_000)\n .optional()\n .describe('Higher runs first (default: after every rule)'),\n};\n\nconst rule = z.looseObject({\n id: z.string(),\n name: z.string(),\n priority: z.number(),\n is_active: z.boolean(),\n target_channel_id: z.string(),\n});\n\n// ----- the tools -----\n\nexport const INBOX_TOOLS: readonly InboxTool[] = [\n defineTool({\n name: 'list_conversations',\n title: 'List conversations',\n description:\n 'List support conversations, most recent activity first, filtered by status, priority, assignee, project, tag, source, last update or AI triage (intent/urgency). Each row has the customer, assignee, needsReply (the customer spoke last) and a preview of the latest message. Use get_conversation for the thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations',\n inputSchema: z.object({ ...listFilters, ...paging }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, a) => list(api, a),\n }),\n\n defineTool({\n name: 'search_conversations',\n title: 'Search conversations',\n description:\n 'Find conversations whose subject, customer name or email, or any message contains the text (case-insensitive), with the same filters as list_conversations. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations?q=',\n inputSchema: z.object({\n query: z.string().trim().min(1).max(200).describe('Text to look for'),\n ...listFilters,\n ...paging,\n }),\n outputSchema: listOutput,\n annotations: READ,\n handler: (api, { query, ...a }) => list(api, { ...a, q: query }),\n }),\n\n defineTool({\n name: 'get_conversation',\n title: 'Get conversation',\n description:\n 'Read one conversation with its full thread: customer, agent, system and bot messages in order, internal notes included (isInternal: true, never seen by the customer), plus status, priority, tags, assignee, customer, triage and its Slack thread. Needs the inbox:read (or read) scope.',\n scope: 'inbox:read',\n endpoint: 'GET /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: z.object({\n conversation: z.looseObject({\n id: z.string(),\n status: z.string(),\n messages: z.array(loose),\n }),\n }),\n annotations: READ,\n handler: async (api, { conversationId: cid }) => {\n const r = await api.request<{ conversation: Json }>('GET', path(cid));\n return { conversation: r.conversation as never };\n },\n }),\n\n defineTool({\n name: 'reply_to_conversation',\n title: 'Reply to conversation',\n description:\n 'Send a reply to the customer, as this API key (shown as \"<key name> (agent)\"). It reaches the customer like a dashboard reply (chat widget, email, SMS, ...) and is mirrored to the Slack thread; an open conversation becomes pending. Confirm the text with the user before sending. For a note only the team sees, use add_internal_note. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/reply',\n inputSchema: z.object({\n conversationId,\n content: z\n .string()\n .trim()\n .min(1)\n .max(10_000)\n .describe('Reply text, as the customer will read it'),\n }),\n outputSchema: z.object({\n message: z.looseObject({ id: z.string() }),\n conversationStatus: z.string(),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/reply'), {\n body: { content },\n });\n return {\n message: r.message as { id: string },\n conversationStatus: String(r.conversationStatus ?? ''),\n slack: String(r.slack ?? ''),\n };\n },\n }),\n\n defineTool({\n name: 'add_internal_note',\n title: 'Add internal note',\n description:\n 'Add an internal note to a conversation: posted in its Slack thread for the team and kept in the conversation, never sent to the customer. Use it for findings, handoffs and suggested replies. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'POST /v1/conversations/:id/notes',\n inputSchema: z.object({\n conversationId,\n content: z.string().trim().min(1).max(10_000).describe('Note text'),\n }),\n outputSchema: z.object({\n note: z.looseObject({ id: z.string() }),\n slack: slackOutcome,\n }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n handler: async (api, { conversationId: cid, content }) => {\n const r = await api.request<Json>('POST', path(cid, '/notes'), {\n body: { content },\n });\n return { note: r.note as { id: string }, slack: String(r.slack ?? '') };\n },\n }),\n\n defineTool({\n name: 'assign_conversation',\n title: 'Assign conversation',\n description:\n 'Assign a conversation to a teammate (by user ID or email; owners, admins and agents, not viewers), or unassign it with null. The teammate is notified, and the change shows in the timeline and Slack. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n assignee: z\n .string()\n .min(1)\n .max(255)\n .nullable()\n .describe('User ID or email of the teammate, or null to unassign'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, assignee }) =>\n patch(\n api,\n cid,\n assignee === null\n ? { assigneeId: null }\n : assignee.includes('@')\n ? { assigneeEmail: assignee }\n : { assigneeId: assignee }\n ),\n }),\n\n defineTool({\n name: 'tag_conversation',\n title: 'Tag conversation',\n description:\n 'Add or remove tags on a conversation, or replace them all with `set`. Tags are lowercased; at most 20. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n add: tags.optional().describe('Tags to add'),\n remove: tags.optional().describe('Tags to remove'),\n set: tags\n .optional()\n .describe('Replace every tag with these ([] clears them)'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, add, remove, set }) => {\n if (!add?.length && !remove?.length && !set) {\n throw new ToolError('Pass tags to add, remove, or set.');\n }\n return patch(api, cid, {\n ...(set ? { tags: set } : {}),\n ...(add?.length ? { addTags: add } : {}),\n ...(remove?.length ? { removeTags: remove } : {}),\n });\n },\n }),\n\n defineTool({\n name: 'close_conversation',\n title: 'Close conversation',\n description:\n 'Close a conversation (or mark it resolved). Nothing is sent to the customer; the change shows in the timeline and the Slack root post. Reply first if the customer should hear back. Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({\n conversationId,\n status: z\n .enum(['closed', 'resolved'])\n .default('closed')\n .describe('closed (default) or resolved'),\n }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid, status }) =>\n patch(api, cid, { status }),\n }),\n\n defineTool({\n name: 'reopen_conversation',\n title: 'Reopen conversation',\n description:\n 'Reopen a closed or resolved conversation (status open). Needs the inbox:write scope.',\n scope: 'inbox:write',\n endpoint: 'PATCH /v1/conversations/:id',\n inputSchema: z.object({ conversationId }),\n outputSchema: changeOutput,\n annotations: CHANGE,\n handler: (api, { conversationId: cid }) =>\n patch(api, cid, { status: 'open' }),\n }),\n\n defineTool({\n name: 'get_analytics_summary',\n title: 'Get analytics summary',\n description:\n 'Support KPIs for a period (default the last 30 days): conversations started and resolved, median first-response and resolution times, messages in and out, each with the previous period; plus the inbox right now (open, pending, unassigned, urgent, awaiting a reply). Needs the analytics:read (or read) scope.',\n scope: 'analytics:read',\n endpoint: 'GET /v1/analytics/summary',\n inputSchema: z.object({\n days: z\n .number()\n .int()\n .min(1)\n .max(366)\n .optional()\n .describe('Period length in days, ending today (default 30)'),\n from: z.iso.date().optional().describe('First day, YYYY-MM-DD'),\n to: z.iso.date().optional().describe('Last day, YYYY-MM-DD'),\n tz: z\n .string()\n .max(64)\n .optional()\n .describe('IANA time zone for day boundaries (default UTC)'),\n projectId: id('Project').optional(),\n }),\n outputSchema: z.looseObject({ range: loose, kpis: loose, inbox: loose }),\n annotations: READ,\n handler: async (api, args) =>\n api.request<{ range: Json; kpis: Json; inbox: Json }>(\n 'GET',\n '/v1/analytics/summary',\n { query: args }\n ),\n }),\n\n defineTool({\n name: 'list_routing_rules',\n title: 'List routing rules',\n description:\n 'List the routing rules in evaluation order (highest priority first): which new conversations each one sends to which Slack channel, and who it assigns. Needs the routing:write, inbox:read or read scope.',\n scope: 'routing:read',\n endpoint: 'GET /v1/routing-rules',\n inputSchema: z.object({}),\n outputSchema: z.object({ rules: z.array(rule) }),\n annotations: READ,\n handler: async api => {\n const r = await api.request<{ routing_rules: Json[] }>(\n 'GET',\n '/v1/routing-rules'\n );\n return { rules: (r.routing_rules ?? []) as never };\n },\n }),\n\n defineTool({\n name: 'create_routing_rule',\n title: 'Create routing rule',\n description:\n 'Create a routing rule: new conversations whose first message matches `conditions` are posted to `targetChannelId` instead of the default channel (and optionally assigned). It runs after the existing rules unless `priority` is given. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'POST /v1/routing-rules',\n inputSchema: z.object(ruleFields),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n handler: async (api, body) => {\n const r = await api.request<{ routing_rule: Json }>(\n 'POST',\n '/v1/routing-rules',\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'update_routing_rule',\n title: 'Update routing rule',\n description:\n 'Change a routing rule (from list_routing_rules); omitted fields keep their value, and `conditions` replaces the whole condition group. Use isActive: false to pause a rule. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'PATCH /v1/routing-rules/:id',\n inputSchema: z.object({\n ruleId: id('Routing rule'),\n name: ruleFields.name.optional(),\n targetChannelId: ruleFields.targetChannelId.optional(),\n conditions: routingConditions.optional(),\n projectId: ruleFields.projectId,\n widgetId: ruleFields.widgetId,\n integrationId: ruleFields.integrationId,\n assignToUserId: ruleFields.assignToUserId,\n isActive: ruleFields.isActive,\n priority: ruleFields.priority,\n }),\n outputSchema: z.object({ rule }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId, ...body }) => {\n if (Object.values(body).every(v => v === undefined)) {\n throw new ToolError('Pass at least one field to change.');\n }\n const r = await api.request<{ routing_rule: Json }>(\n 'PATCH',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`,\n { body }\n );\n return { rule: r.routing_rule as never };\n },\n }),\n\n defineTool({\n name: 'delete_routing_rule',\n title: 'Delete routing rule',\n description:\n 'Delete a routing rule for good. To stop it temporarily, use update_routing_rule with isActive: false instead. Confirm with the user first. Needs the routing:write scope.',\n scope: 'routing:write',\n endpoint: 'DELETE /v1/routing-rules/:id',\n inputSchema: z.object({ ruleId: id('Routing rule') }),\n outputSchema: z.object({ deleted: z.boolean(), ruleId: z.string() }),\n annotations: {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async (api, { ruleId }) => {\n await api.request(\n 'DELETE',\n `/v1/routing-rules/${encodeURIComponent(ruleId)}`\n );\n return { deleted: true, ruleId };\n },\n }),\n];\n\nexport const INBOX_TOOL_NAMES = INBOX_TOOLS.map(t => t.name);\n\nexport function getInboxTool(name: string): InboxTool | undefined {\n return INBOX_TOOLS.find(t => t.name === name);\n}\n\n/**\n * Validate `args` against the tool's input schema and run it. Throws\n * `ToolError` for an unknown tool or invalid arguments, and for API\n * failures (`toToolError` in `api.ts` words them for the model).\n */\nexport async function runInboxTool(\n api: InboxRequester,\n name: string,\n args: unknown\n): Promise<Json> {\n const tool = getInboxTool(name);\n if (!tool) throw new ToolError(`Unknown inbox tool \"${name}\"`);\n const parsed = tool.inputSchema.safeParse(args ?? {});\n if (!parsed.success) {\n throw new ToolError(\n `Invalid arguments for ${name}: ${parsed.error.issues\n .map(i => `${i.path.join('.') || 'input'}: ${i.message}`)\n .join('; ')}`\n );\n }\n return (await tool.handler(api, parsed.data)) as Json;\n}\n\n/**\n * The tools as JSON Schema function definitions, for LLM APIs that take\n * `{ name, description, parameters }` (OpenAI / OpenRouter style).\n */\nexport function inboxToolsAsJsonSchema(\n filter: (tool: InboxTool) => boolean = () => true\n): Array<{ name: string; description: string; parameters: Json }> {\n return INBOX_TOOLS.filter(filter).map(t => ({\n name: t.name,\n description: t.description,\n parameters: z.toJSONSchema(t.inputSchema, { io: 'input' }) as Json,\n }));\n}\n","import {\n ChannlizeClient,\n ChannlizeError,\n parseApiError,\n} from '@channlize/core';\nimport type { ResolvedConfig } from './config';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\n/**\n * A failed Channlize call, worded for the model: what went wrong and how\n * to fix it (wrong key, missing scope, unknown id). Tool handlers throw it\n * and the MCP server returns it as a tool error (`isError: true`).\n */\nexport class ToolError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public code?: string,\n /** ISO time the call may succeed again (agent policies, takeovers). */\n public retryAt?: string\n ) {\n super(message);\n this.name = 'ToolError';\n }\n}\n\ntype Query = Record<string, string | number | boolean | undefined>;\n\n/** The scope to add for an \"API key lacks required permission: <perm>\" 403. */\nfunction scopeFor(apiMessage: string): string {\n const perm = /permission:\\s*([\\w:*-]+)/i.exec(apiMessage)?.[1] ?? '';\n switch (perm) {\n case 'inbox:read':\n return 'inbox:read (or read)';\n case 'inbox:write':\n return 'inbox:write';\n case 'routing:read':\n return 'routing:write, inbox:read (or read)';\n case 'routing:write':\n return 'routing:write';\n case 'analytics:read':\n return 'analytics:read (or read)';\n case 'agents:send':\n return 'agents:send';\n case 'agents:read':\n return 'agents:read';\n case 'agents:ask_human':\n return 'agents:ask_human';\n case 'agents:admin':\n return 'agents:admin (an unbound key)';\n default:\n return /write|notify/.test(apiMessage) ? 'write' : 'read';\n }\n}\n\n/** \" Retry after <time>.\" for a refusal that names one. */\nfunction retryText(retryAt: string | undefined): string {\n return retryAt ? ` Retry after ${retryAt}, not before.` : '';\n}\n\n/** Hints for the Channlize for Agents error codes. */\nfunction agentHint(\n code: string | undefined,\n body: Record<string, unknown> = {}\n): string | undefined {\n const retryAt = typeof body.retryAt === 'string' ? body.retryAt : undefined;\n switch (code) {\n case 'AGENT_KEY_REQUIRED':\n return 'Agent tools need an agent key or a multi-agent key: create one under SDK & Integration → API keys and pick the agents.';\n case 'AGENT_NOT_ALLOWED':\n return \"The key can't act as that agent: pass one of the key's agents as agentId (list_agents), or fix CHANNLIZE_AGENT_ID.\";\n case 'NOT_A_CHANNEL_MEMBER':\n return 'Only channel members can post (observers only read): use list_agent_channels, or ask an admin to add the agent to the channel.';\n case 'AGENT_PAUSED':\n case 'AGENT_RETIRED':\n return 'Only active agents can act: ask an admin to reactivate it in the dashboard.';\n case 'AGENT_RATE_LIMITED':\n return 'The agent is sending too fast: wait a minute before sending again.';\n case 'AGENT_THREAD_PAUSED': {\n const why =\n body.reason === 'ping_pong'\n ? ' (two agents were going back and forth)'\n : body.reason === 'duplicate_content'\n ? ' (the same message kept being posted)'\n : '';\n return `The loop guard paused this thread${why}: stop sending in it until a person replies or resumes it. Don't rephrase and retry.`;\n }\n case 'HUMAN_TAKEOVER':\n return `A person took over this thread: don't post in it until the takeover ends or they hand it back.${retryText(retryAt)}`;\n case 'POLICY_RATE_LIMITED':\n case 'POLICY_BUDGET_EXHAUSTED':\n case 'POLICY_OUTSIDE_WORKING_HOURS':\n return `The agent's policy refuses this for now: wait, don't retry in a loop.${retryText(retryAt)}`;\n case 'POLICY_CHANNEL_NOT_ALLOWED':\n case 'POLICY_DM_NOT_ALLOWED':\n case 'POLICY_ASK_HUMAN_NOT_ALLOWED':\n case 'POLICY_URGENCY_NOT_ALLOWED':\n case 'POLICY_MAX_DEPTH':\n return \"The agent's policy forbids this: retrying won't help. Pick another channel, agent or urgency, or ask an admin to change the policy.\";\n case 'TASK_CLOSED':\n return 'The task is closed (completed, failed, canceled or rejected) and cannot change: create a new task instead.';\n case 'ALREADY_ASSIGNED':\n return 'The task is already assigned to that agent.';\n case 'AGENT_NOT_IN_CHANNEL':\n return \"The other agent isn't a member of the task's channel: pick a member (list_agent_channels), or ask an admin to add it.\";\n case 'NOT_TASK_PARTICIPANT':\n return \"Only the task's assignee or creator can change it or hand it off.\";\n case 'QUESTION_CLOSED':\n return 'The question is already closed (answered, expired or canceled): the first answer wins. Read it with get_answer.';\n case 'APPROVAL_ROLE_REQUIRED':\n return 'This approval needs a person with the required role: an agent cannot answer it.';\n case 'AGENT_ADMIN_KEY_REQUIRED':\n return 'This needs an unbound agents:admin key (not a key bound to agents): create one under SDK & Integration → API keys.';\n case 'CHANNEL_NOT_VISIBLE':\n return \"Your key's agents can't read that channel: leave channelId out, or pick one from list_agent_channels.\";\n case 'INVALID_ANSWER':\n return \"Answer with one of the question's option ids (approve / deny for approvals), or text when free text is allowed.\";\n default:\n return undefined;\n }\n}\n\nfunction hintFor(\n status: number,\n apiMessage: string,\n code?: string,\n body?: Record<string, unknown>\n): string {\n const agent = agentHint(code, body);\n if (agent) return `${apiMessage}. ${agent}`;\n if (status === 400 && /agent_id is required/i.test(apiMessage)) {\n return `${apiMessage}. Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server.`;\n }\n switch (status) {\n case 401:\n return `${apiMessage}. The server API key is invalid, revoked or expired: create a new one under SDK & Integration → API keys and update CHANNLIZE_SERVER_API_KEY.`;\n case 403:\n return /permission/i.test(apiMessage)\n ? `${apiMessage}. This needs a server API key with the ${scopeFor(\n apiMessage\n )} scope: create one under SDK & Integration → API keys.`\n : apiMessage;\n case 404:\n return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;\n case 429:\n return `${apiMessage}. Rate limited: wait a little before retrying.`;\n default:\n return apiMessage;\n }\n}\n\n/** Maps a `@channlize/core` error to a `ToolError` with the same hints. */\nexport function toToolError(err: unknown): ToolError {\n if (err instanceof ToolError) return err;\n if (err instanceof ChannlizeError) {\n return new ToolError(\n err.statusCode\n ? hintFor(\n err.statusCode,\n err.message.replace(/\\.$/, ''),\n err.code,\n err.details\n )\n : err.message,\n err.statusCode,\n err.code,\n err.retryAt\n );\n }\n return new ToolError(err instanceof Error ? err.message : String(err));\n}\n\n/**\n * Thin client for the API-key endpoints `@channlize/core` doesn't wrap\n * (projects, the inbox, routing rules, contacts, Events and log reads,\n * agents). Widget-key\n * calls and `notify` go through `@channlize/core`.\n */\nexport class ChannlizeApi {\n private projectIdPromise?: Promise<string>;\n private widgetClient?: ChannlizeClient;\n\n constructor(readonly config: ResolvedConfig) {}\n\n get hasServerKey(): boolean {\n return !!this.config.serverApiKey;\n }\n\n get hasWidgetKey(): boolean {\n return !!this.config.widgetKey;\n }\n\n /** The default agent for the agent tools (`CHANNLIZE_AGENT_ID`). */\n get agentId(): string | undefined {\n return this.config.agentId;\n }\n\n private requireServerKey(): string {\n if (!this.config.serverApiKey) {\n throw new ToolError(\n 'This tool needs a server API key (ch_live_…): set CHANNLIZE_SERVER_API_KEY or pass --server-api-key.'\n );\n }\n return this.config.serverApiKey;\n }\n\n async request<T>(\n method: HttpMethod,\n path: string,\n options: {\n query?: Query;\n body?: unknown;\n headers?: Record<string, string>;\n /** Overrides the configured timeout, in ms (long-polls). */\n timeout?: number;\n } = {}\n ): Promise<T> {\n const key = this.requireServerKey();\n const timeout = options.timeout ?? this.config.timeout;\n const url = new URL(`${this.config.baseUrl}${path}`);\n for (const [k, v] of Object.entries(options.query ?? {})) {\n if (v !== undefined && v !== '') url.searchParams.set(k, String(v));\n }\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${key}`,\n Accept: 'application/json',\n ...(options.body !== undefined\n ? { 'Content-Type': 'application/json' }\n : {}),\n ...options.headers,\n },\n // oxlint-disable-next-line unicorn/no-invalid-fetch-options -- `method` is dynamic; GET calls never pass a body.\n body:\n options.body !== undefined ? JSON.stringify(options.body) : undefined,\n signal: AbortSignal.timeout(timeout),\n });\n } catch (err) {\n const e = err as Error;\n if (e.name === 'TimeoutError' || e.name === 'AbortError') {\n throw new ToolError(\n `Channlize API timed out after ${timeout} ms (${method} ${url.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,\n 408,\n 'TIMEOUT'\n );\n }\n throw new ToolError(\n `Could not reach the Channlize API at ${this.config.baseUrl}: ${e.message}`\n );\n }\n\n const text = await res.text();\n let data: unknown = undefined;\n if (text) {\n try {\n data = JSON.parse(text);\n } catch {\n data = undefined;\n }\n }\n\n if (!res.ok) {\n const { message, code, body } = parseApiError(\n res.status,\n data,\n res.statusText\n );\n const retryAt =\n typeof body.retryAt === 'string' ? body.retryAt : undefined;\n throw new ToolError(\n hintFor(res.status, message.replace(/\\.$/, ''), code, body),\n res.status,\n code,\n retryAt\n );\n }\n if (data === undefined) {\n throw new ToolError(\n `Unexpected non-JSON response from ${method} ${url.pathname}`\n );\n }\n return data as T;\n }\n\n /**\n * `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization\n * (`GET /projects/me`), looked up once.\n */\n projectId(): Promise<string> {\n if (this.config.projectId) return Promise.resolve(this.config.projectId);\n this.projectIdPromise ??= this.request<{ project: { id: string } }>(\n 'GET',\n '/projects/me'\n )\n .then(r => {\n if (!r.project?.id) {\n throw new ToolError(\n 'Could not find a project for this key: set CHANNLIZE_PROJECT_ID.'\n );\n }\n return r.project.id;\n })\n .catch(err => {\n this.projectIdPromise = undefined;\n throw err;\n });\n return this.projectIdPromise;\n }\n\n /** `@channlize/core` client bound to the widget key (visitor tools). */\n widget(): ChannlizeClient {\n if (!this.config.widgetKey) {\n throw new ToolError(\n 'This tool needs the public widget key (wk_…): set CHANNLIZE_WIDGET_KEY or pass --widget-key.'\n );\n }\n this.widgetClient ??= new ChannlizeClient({\n apiKey: this.config.widgetKey,\n projectId: this.config.projectId ?? '',\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n return this.widgetClient;\n }\n\n /** `@channlize/core` client for `notify` (server key + project). */\n async notifier(): Promise<ChannlizeClient> {\n const serverApiKey = this.requireServerKey();\n return new ChannlizeClient({\n // core requires a widget key; notify only uses serverApiKey.\n apiKey: this.config.widgetKey ?? 'unused',\n serverApiKey,\n projectId: await this.projectId(),\n baseUrl: this.config.baseUrl,\n timeout: this.config.timeout,\n });\n }\n}\n"],"mappings":"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","names":[]}
|