@pipeshub-ai/mcp 2.0.1 → 2.1.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.
Files changed (80) hide show
  1. package/bin/mcp-server.js +685 -76
  2. package/bin/mcp-server.js.map +20 -13
  3. package/esm/funcs/agentsListAgents.d.ts +10 -0
  4. package/esm/funcs/agentsListAgents.d.ts.map +1 -0
  5. package/esm/funcs/agentsListAgents.js +83 -0
  6. package/esm/funcs/agentsListAgents.js.map +1 -0
  7. package/esm/funcs/agentsStreamConversation.d.ts +10 -0
  8. package/esm/funcs/agentsStreamConversation.d.ts.map +1 -0
  9. package/esm/funcs/agentsStreamConversation.js +83 -0
  10. package/esm/funcs/agentsStreamConversation.js.map +1 -0
  11. package/esm/funcs/agentsStreamMessage.d.ts +10 -0
  12. package/esm/funcs/agentsStreamMessage.d.ts.map +1 -0
  13. package/esm/funcs/agentsStreamMessage.js +86 -0
  14. package/esm/funcs/agentsStreamMessage.js.map +1 -0
  15. package/esm/funcs/conversationsStreamConversation.d.ts.map +1 -1
  16. package/esm/funcs/conversationsStreamConversation.js +1 -3
  17. package/esm/funcs/conversationsStreamConversation.js.map +1 -1
  18. package/esm/funcs/conversationsStreamMessage.d.ts.map +1 -1
  19. package/esm/funcs/conversationsStreamMessage.js +0 -7
  20. package/esm/funcs/conversationsStreamMessage.js.map +1 -1
  21. package/esm/mcp-server/instructions.d.ts +2 -0
  22. package/esm/mcp-server/instructions.d.ts.map +1 -0
  23. package/esm/mcp-server/instructions.js +83 -0
  24. package/esm/mcp-server/instructions.js.map +1 -0
  25. package/esm/mcp-server/prompts/pipeshubAssistant.d.ts +8 -0
  26. package/esm/mcp-server/prompts/pipeshubAssistant.d.ts.map +1 -0
  27. package/esm/mcp-server/prompts/pipeshubAssistant.js +31 -0
  28. package/esm/mcp-server/prompts/pipeshubAssistant.js.map +1 -0
  29. package/esm/mcp-server/server.d.ts.map +1 -1
  30. package/esm/mcp-server/server.js +7 -60
  31. package/esm/mcp-server/server.js.map +1 -1
  32. package/esm/mcp-server/tools/_helpers.d.ts +78 -0
  33. package/esm/mcp-server/tools/_helpers.d.ts.map +1 -1
  34. package/esm/mcp-server/tools/_helpers.js +155 -1
  35. package/esm/mcp-server/tools/_helpers.js.map +1 -1
  36. package/esm/mcp-server/tools/pipeshubAgents.d.ts +8 -0
  37. package/esm/mcp-server/tools/pipeshubAgents.d.ts.map +1 -0
  38. package/esm/mcp-server/tools/pipeshubAgents.js +62 -0
  39. package/esm/mcp-server/tools/pipeshubAgents.js.map +1 -0
  40. package/esm/mcp-server/tools/pipeshubChat.d.ts +6 -1
  41. package/esm/mcp-server/tools/pipeshubChat.d.ts.map +1 -1
  42. package/esm/mcp-server/tools/pipeshubChat.js +71 -21
  43. package/esm/mcp-server/tools/pipeshubChat.js.map +1 -1
  44. package/esm/mcp-server/tools/pipeshubDirectory.d.ts.map +1 -1
  45. package/esm/mcp-server/tools/pipeshubDirectory.js +0 -1
  46. package/esm/mcp-server/tools/pipeshubDirectory.js.map +1 -1
  47. package/esm/mcp-server/tools/pipeshubDownloadRecord.d.ts.map +1 -1
  48. package/esm/mcp-server/tools/pipeshubDownloadRecord.js +0 -3
  49. package/esm/mcp-server/tools/pipeshubDownloadRecord.js.map +1 -1
  50. package/esm/mcp-server/tools/pipeshubSearch.d.ts.map +1 -1
  51. package/esm/mcp-server/tools/pipeshubSearch.js +0 -3
  52. package/esm/mcp-server/tools/pipeshubSearch.js.map +1 -1
  53. package/esm/mcp-server/tools/pipeshubSources.d.ts.map +1 -1
  54. package/esm/mcp-server/tools/pipeshubSources.js +0 -4
  55. package/esm/mcp-server/tools/pipeshubSources.js.map +1 -1
  56. package/esm/models/agentops.d.ts +119 -0
  57. package/esm/models/agentops.d.ts.map +1 -0
  58. package/esm/models/agentops.js +97 -0
  59. package/esm/models/agentops.js.map +1 -0
  60. package/esm/tool-names.d.ts.map +1 -1
  61. package/esm/tool-names.js +4 -0
  62. package/esm/tool-names.js.map +1 -1
  63. package/package.json +2 -2
  64. package/src/funcs/agentsListAgents.ts +141 -0
  65. package/src/funcs/agentsStreamConversation.ts +144 -0
  66. package/src/funcs/agentsStreamMessage.ts +147 -0
  67. package/src/funcs/conversationsStreamConversation.ts +1 -3
  68. package/src/funcs/conversationsStreamMessage.ts +0 -8
  69. package/src/mcp-server/instructions.ts +83 -0
  70. package/src/mcp-server/prompts/pipeshubAssistant.ts +37 -0
  71. package/src/mcp-server/server.ts +8 -62
  72. package/src/mcp-server/tools/_helpers.ts +216 -1
  73. package/src/mcp-server/tools/pipeshubAgents.ts +68 -0
  74. package/src/mcp-server/tools/pipeshubChat.ts +70 -21
  75. package/src/mcp-server/tools/pipeshubDirectory.ts +0 -1
  76. package/src/mcp-server/tools/pipeshubDownloadRecord.ts +0 -4
  77. package/src/mcp-server/tools/pipeshubSearch.ts +0 -3
  78. package/src/mcp-server/tools/pipeshubSources.ts +0 -5
  79. package/src/models/agentops.ts +160 -0
  80. package/src/tool-names.ts +4 -0
@@ -1,12 +1,17 @@
1
- // Curated-tool helpers. Hand-written, NOT regenerated by Speakeasy.
2
1
  //
3
2
  // Each curated tool fetches a Response from one of the SDK funcs and then
4
3
  // trims / restructures the body before handing it to the LLM. The helpers
5
4
  // here keep that work out of the individual tool files.
6
5
 
6
+ import * as z from "zod";
7
7
  import { Security } from "../../models/security.js";
8
8
  import { PipeshubCore } from "../../core.js";
9
9
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
10
+ import { agentsListAgents } from "../../funcs/agentsListAgents.js";
11
+ import {
12
+ AgentListEnvelope$zodSchema,
13
+ AgentSummary,
14
+ } from "../../models/agentops.js";
10
15
 
11
16
  /**
12
17
  * Parse a fetch Response as JSON, returning a CallToolResult error when the
@@ -58,6 +63,94 @@ export function errorResult(message: string): CallToolResult {
58
63
  };
59
64
  }
60
65
 
66
+ /**
67
+ * Guard a (possibly streaming) `Response` on its HTTP status. The streaming
68
+ * SDK funcs configure `errorCodes: []`, so the request layer treats ANY HTTP
69
+ * response — including 401 / 403 / 404 / 5xx — as a success and hands back the
70
+ * raw `Response`. Without this check those failures slip into the SSE drain
71
+ * and surface as the useless "stream ended without usable frames".
72
+ *
73
+ * Returns a `CallToolResult` error (status + best-effort message from the body)
74
+ * when the response is not ok, or `null` when it is ok (body left untouched so
75
+ * the caller can still stream it).
76
+ */
77
+ export async function httpErrorResult(
78
+ response: Response,
79
+ context: string,
80
+ ): Promise<CallToolResult | null> {
81
+ if (response.ok) return null;
82
+
83
+ let body = "";
84
+ try {
85
+ body = await response.text();
86
+ } catch {
87
+ // body unreadable (e.g. already-consumed or transport error)
88
+ }
89
+
90
+ let message: string = body;
91
+ if (body) {
92
+ try {
93
+ const parsed = JSON.parse(body);
94
+ // Only accept string fields — error envelopes often nest an object
95
+ // under `error`, which must not stringify to "[object Object]".
96
+ const candidate =
97
+ (typeof parsed?.message === "string" && parsed.message)
98
+ || (typeof parsed?.error === "string" && parsed.error)
99
+ || (typeof parsed?.error?.message === "string" && parsed.error.message)
100
+ || (typeof parsed?.detail === "string" && parsed.detail)
101
+ || "";
102
+ message = candidate || body; // fall back to the raw JSON text
103
+ } catch {
104
+ // not JSON — keep the raw text
105
+ }
106
+ }
107
+
108
+ const detail = message ? ` ${message.slice(0, 400)}` : "";
109
+ const auth = (response.status === 401 || response.status === 403)
110
+ ? " Check that the bearer token / credentials are valid and not expired."
111
+ : "";
112
+ return errorResult(
113
+ `${context} failed (HTTP ${response.status} ${response.statusText}).${detail}${auth}`,
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Read a `Response` body as JSON and validate it against a zod `schema`,
119
+ * returning the typed value or a `CallToolResult` error (empty body, invalid
120
+ * JSON, or a shape that doesn't match the schema).
121
+ */
122
+ export async function readValidated<T>(
123
+ response: Response,
124
+ schema: z.ZodType<T>,
125
+ ): Promise<{ ok: true; value: T } | { ok: false; result: CallToolResult }> {
126
+ const text = await response.text();
127
+ if (!text) {
128
+ return { ok: false, result: errorResult("Empty response from server") };
129
+ }
130
+ let json;
131
+ try {
132
+ json = JSON.parse(text);
133
+ } catch (e) {
134
+ return {
135
+ ok: false,
136
+ result: errorResult(
137
+ `Failed to parse response as JSON: ${(e as Error).message}`
138
+ + `\n\nRaw body:\n${text.slice(0, 500)}`,
139
+ ),
140
+ };
141
+ }
142
+ const parsed = schema.safeParse(json);
143
+ if (!parsed.success) {
144
+ return {
145
+ ok: false,
146
+ result: errorResult(
147
+ `Unexpected response shape:\n${z.prettifyError(parsed.error)}`,
148
+ ),
149
+ };
150
+ }
151
+ return { ok: true, value: parsed.data };
152
+ }
153
+
61
154
  /** Resolve `client._options.security` (which may be a function) to a value. */
62
155
  export async function resolveSecurity(
63
156
  client: PipeshubCore,
@@ -257,6 +350,128 @@ export async function* iterateSSE(
257
350
  }
258
351
  }
259
352
 
353
+ // ── Trimmed agent shape handed to the LLM ────────────────────────────────────
354
+
355
+ interface AgentToolsetView {
356
+ /** Integration / connector key, e.g. `jira`, `gmail`. */
357
+ name: string | null | undefined;
358
+ /** Fully-qualified tool ids the agent can run, e.g. `jira.create_issue`. */
359
+ tools: string[];
360
+ }
361
+
362
+ interface AgentKnowledgeView {
363
+ name: string | null | undefined;
364
+ type: string | null | undefined;
365
+ }
366
+
367
+ export interface EnrichedAgent {
368
+ agentId: string;
369
+ name: string;
370
+ description: string | null;
371
+ systemPrompt: string | null;
372
+ startMessage: string | null;
373
+ tags: string[];
374
+ webSearch: boolean;
375
+ isActive: boolean | null | undefined;
376
+ toolsets: AgentToolsetView[];
377
+ knowledge: AgentKnowledgeView[];
378
+ }
379
+
380
+ /**
381
+ * Trim an enriched `AgentSummary` (from `GET /agents`) to just what the LLM
382
+ * needs to pick and converse with an agent. Strips ids/revs/timestamps/perms
383
+ * and the heavy nested tool/knowledge metadata, keeping the routing-critical
384
+ * signal: each toolset's connector `name` + its tool ids (e.g.
385
+ * `jira.create_issue`), and each knowledge source's `name`/`type`.
386
+ */
387
+ export function trimAgent(a: AgentSummary): EnrichedAgent {
388
+ return {
389
+ agentId: a._key,
390
+ name: a.name,
391
+ description: a.description ?? null,
392
+ systemPrompt: a.systemPrompt ?? null,
393
+ startMessage: a.startMessage ?? null,
394
+ tags: a.tags,
395
+ webSearch: !!a.webSearch,
396
+ isActive: a.isActive,
397
+ toolsets: a.toolsets.map((ts) => ({
398
+ name: ts.name,
399
+ tools: ts.tools
400
+ .map((t) => t.fullName ?? t.name)
401
+ .filter((id): id is string => typeof id === "string"),
402
+ })),
403
+ knowledge: a.knowledge.map((k) => ({
404
+ name: k.name ?? k.displayName,
405
+ type: k.type,
406
+ })),
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Fetch the COMPLETE agent list, auto-paginating `GET /agents` so callers
412
+ * (discovery + routing) never have to manage page numbers. Pages are fetched
413
+ * at the max page size until the backend reports no more (`hasNext` /
414
+ * `hasNextPage`, with a short-page fallback), capped by `maxPages` as a
415
+ * runaway guard. Returns the raw `AgentListItem`s plus a normalized `total`
416
+ * and a `truncated` flag (true if the cap was hit before exhausting pages).
417
+ */
418
+ export async function listAllAgents(
419
+ client: PipeshubCore,
420
+ opts: {
421
+ search?: string | undefined;
422
+ signal?: AbortSignal | undefined;
423
+ maxPages?: number | undefined;
424
+ } = {},
425
+ ): Promise<
426
+ | { ok: true; agents: AgentSummary[]; total: number; truncated: boolean }
427
+ | { ok: false; result: CallToolResult }
428
+ > {
429
+ const PAGE_SIZE = 200;
430
+ const maxPages = opts.maxPages ?? 25;
431
+ // Only set `signal` when present (exactOptionalPropertyTypes).
432
+ const reqOptions = opts.signal
433
+ ? { fetchOptions: { signal: opts.signal } }
434
+ : {};
435
+
436
+ const all: AgentSummary[] = [];
437
+ let total = 0;
438
+
439
+ for (let page = 1; page <= maxPages; page++) {
440
+ const [r] = await agentsListAgents(client, {
441
+ page,
442
+ limit: PAGE_SIZE,
443
+ search: opts.search,
444
+ }, reqOptions).$inspect();
445
+ if (!r.ok) return { ok: false, result: errorResult(r.error.message) };
446
+
447
+ const httpErr = await httpErrorResult(r.value, "Agent list request");
448
+ if (httpErr) return { ok: false, result: httpErr };
449
+
450
+ const parsed = await readValidated(r.value, AgentListEnvelope$zodSchema);
451
+ if (!parsed.ok) return { ok: false, result: parsed.result };
452
+ if (parsed.value.success === false) {
453
+ return {
454
+ ok: false,
455
+ result: errorResult("Agent list request returned success: false"),
456
+ };
457
+ }
458
+
459
+ const agents = parsed.value.agents;
460
+ all.push(...agents);
461
+
462
+ const pg = parsed.value.pagination;
463
+ total = pg?.totalItems ?? pg?.total ?? all.length;
464
+
465
+ const hasNext = pg?.hasNext ?? pg?.hasNextPage ?? (agents.length >= PAGE_SIZE);
466
+ if (!hasNext || agents.length === 0) {
467
+ return { ok: true, agents: all, total, truncated: false };
468
+ }
469
+ }
470
+
471
+ // Hit the page cap with more pages still available.
472
+ return { ok: true, agents: all, total: total || all.length, truncated: true };
473
+ }
474
+
260
475
  /** Trim a single search hit to what the LLM actually needs. */
261
476
  export function trimSearchHit(hit: any) {
262
477
  const md = hit?.metadata ?? {};
@@ -0,0 +1,68 @@
1
+ // (`GET /agents`) — each already carrying its `toolsets` and `knowledge` — so
2
+ // the LLM can route on real capability rather than the often-generic
3
+ // description. Pick an agent's `agentId` and pass it to `pipeshub_chat`'s
4
+ // `agentId` to converse with it. Single API call, no per-agent fan-out.
5
+
6
+ import * as z from "zod";
7
+ import { ToolDefinition } from "../tools.js";
8
+ import { jsonResult, listAllAgents, trimAgent } from "./_helpers.js";
9
+
10
+ const args = {
11
+ search: z.string().optional().describe(
12
+ "Optional case-insensitive substring match across agent name, "
13
+ + "description, and tags. Omit to return every agent.",
14
+ ),
15
+ };
16
+
17
+ export const tool$pipeshubAgents: ToolDefinition<typeof args> = {
18
+ name: "pipeshub_agents",
19
+ description:
20
+ `List the PipesHub **agents** configured for this org, each with its
21
+ capabilities.
22
+
23
+ Agents are specialized assistants (custom system prompt, tools, knowledge
24
+ scope). To converse with one, take its \`agentId\` and pass it to
25
+ \`pipeshub_chat\`'s \`agentId\` argument.
26
+
27
+ Each agent is returned as:
28
+ \`{ agentId, name, description, systemPrompt, startMessage, tags, webSearch,
29
+ isActive, toolsets, knowledge }\`.
30
+ - \`toolsets\` — what the agent can DO: each \`{ name, tools }\` where \`name\`
31
+ is the connector (e.g. \`jira\`, \`gmail\`) and \`tools\` are the runnable
32
+ tool ids (e.g. \`jira.create_issue\`, \`gmail.send_email\`).
33
+ - \`knowledge\` — what the agent can READ: each \`{ name, type }\` (e.g.
34
+ \`Confluence-2\` / \`Confluence\`).
35
+
36
+ **Route on \`toolsets\`/\`knowledge\`, not the name** — names and descriptions
37
+ are often generic or misleading. Match the request to the agent whose tools can
38
+ actually perform it (e.g. "create a Jira ticket" → the agent whose toolset is
39
+ \`jira\` and whose tools include \`jira.create_issue\`). If NO agent has a tool
40
+ for the requested action, say so — don't force an unrelated agent.
41
+
42
+ The list **may be empty** (no agents configured). For plain Q&A when no
43
+ specific agent is needed, use \`pipeshub_chat\` WITHOUT \`agentId\` and pick a
44
+ \`chatMode\`: \`internal_search\` (org's indexed knowledge) or \`web_search\`
45
+ (live web). Use \`agentId\` everywhere an agent is referenced.`,
46
+ scopes: ["read"],
47
+ annotations: {
48
+ title: "List PipesHub agents (with toolsets & knowledge)",
49
+ destructiveHint: false,
50
+ idempotentHint: true,
51
+ openWorldHint: false,
52
+ readOnlyHint: true,
53
+ },
54
+ args,
55
+ tool: async (client, args, ctx) => {
56
+ const all = await listAllAgents(client, {
57
+ search: args.search,
58
+ signal: ctx.signal,
59
+ });
60
+ if (!all.ok) return all.result;
61
+
62
+ return jsonResult({
63
+ agents: all.agents.map(trimAgent),
64
+ total: all.total,
65
+ ...(all.truncated ? { truncated: true } : {}),
66
+ });
67
+ },
68
+ };
@@ -1,9 +1,3 @@
1
- // Curated PipesHub chat tool. Hand-written; replaces the two generated
2
- // tools `pipeshub-chat` (create) and `pipeshub-chat-continue` (follow-up)
3
- // with a single tool that branches on whether `conversationId` was given.
4
- //
5
- // Wire transport: SSE. The non-streaming `/conversations/create` endpoint
6
- // is broken in production today, so we drive
7
1
  // `POST /conversations/stream` (and
8
2
  // `POST /conversations/{conversationId}/messages/stream` for follow-ups),
9
3
  // accumulate the frames server-side, and hand the LLM a single trimmed
@@ -29,9 +23,12 @@
29
23
  import * as z from "zod";
30
24
  import { conversationsStreamConversation } from "../../funcs/conversationsStreamConversation.js";
31
25
  import { conversationsStreamMessage } from "../../funcs/conversationsStreamMessage.js";
26
+ import { agentsStreamConversation } from "../../funcs/agentsStreamConversation.js";
27
+ import { agentsStreamMessage } from "../../funcs/agentsStreamMessage.js";
32
28
  import { ToolDefinition } from "../tools.js";
33
29
  import {
34
30
  errorResult,
31
+ httpErrorResult,
35
32
  iterateSSE,
36
33
  jsonResult,
37
34
  trimConversation,
@@ -68,16 +65,28 @@ const args = {
68
65
  ),
69
66
  modelName: z.string().optional(),
70
67
  modelFriendlyName: z.string().optional(),
71
- chatMode: z.enum(["web_search", "internal_search"]).optional().describe(
72
- "Controls retrieval source. "
73
- + "`internal_search` (default) searches the org's indexed knowledge "
74
- + "bases (Drive, Confluence, Slack, Gmail, Jira, Box, etc.) and returns "
75
- + "citations. Use this for questions about internal docs, policies, or "
76
- + "company data. "
77
- + "`web_search` searches the public web instead of internal sources. "
78
- + "Use only when the user explicitly asks about current events, public "
79
- + "information, or anything not expected to be in the org's KB. "
80
- + "Omit this field (or pass `internal_search`) for all normal queries.",
68
+ agentId: z.string().optional().describe(
69
+ "Optional PipesHub agent to converse with — the `agentId` from "
70
+ + "`pipeshub_agents`. When set, this turn runs against that agent's "
71
+ + "configuration (prompt, tools, knowledge). On follow-up turns pass the "
72
+ + "SAME `agentId` together with the `conversationId` returned by the "
73
+ + "previous call. Omit for a plain (non-agent) conversation. If unsure "
74
+ + "which agent to use, call `pipeshub_agents` first to see the options.",
75
+ ),
76
+ chatMode: z.enum([
77
+ "internal_search",
78
+ "web_search",
79
+ "auto",
80
+ "quick",
81
+ "verification",
82
+ "deep",
83
+ ]).optional().describe(
84
+ "Response strategy. The valid values depend on whether `agentId` is set:\n"
85
+ + "- WITHOUT `agentId` (plain chat): `internal_search` — answer from the "
86
+ + "org's indexed knowledge (default) — or `web_search` — answer from the "
87
+ + "live web.\n"
88
+ + "- WITH `agentId` (agent chat): `auto` (let the agent decide; default), "
89
+ + "`quick`, `verification`, or `deep`.",
81
90
  ),
82
91
  };
83
92
 
@@ -134,8 +143,42 @@ cited document, take \`citations[*].recordId\` and call
134
143
  const fetchOptions = { signal: ctx.signal };
135
144
  let response: Response;
136
145
 
137
- if (args.conversationId) {
138
- // Continue an existing conversation.
146
+ if (args.agentId) {
147
+ // Agent conversation. Agent chatMode vocabulary defaults to `auto`.
148
+ const agentChatMode = args.chatMode ?? "auto";
149
+ if (args.conversationId) {
150
+ // Continue an existing agent conversation.
151
+ const [result] = await agentsStreamMessage(client, {
152
+ agentKey: args.agentId,
153
+ conversationId: args.conversationId,
154
+ body: {
155
+ query: args.query,
156
+ modelKey: args.modelKey,
157
+ modelName: args.modelName,
158
+ modelFriendlyName: args.modelFriendlyName,
159
+ chatMode: agentChatMode,
160
+ },
161
+ }, { fetchOptions }).$inspect();
162
+ if (!result.ok) return errorResult(result.error.message);
163
+ response = result.value;
164
+ } else {
165
+ // Start a new agent conversation.
166
+ const [result] = await agentsStreamConversation(client, {
167
+ agentKey: args.agentId,
168
+ body: {
169
+ query: args.query,
170
+ filters: args.filters,
171
+ modelKey: args.modelKey,
172
+ modelName: args.modelName,
173
+ modelFriendlyName: args.modelFriendlyName,
174
+ chatMode: agentChatMode,
175
+ },
176
+ }, { fetchOptions }).$inspect();
177
+ if (!result.ok) return errorResult(result.error.message);
178
+ response = result.value;
179
+ }
180
+ } else if (args.conversationId) {
181
+ // Continue an existing (non-agent) conversation.
139
182
  const [result] = await conversationsStreamMessage(client, {
140
183
  conversationId: args.conversationId,
141
184
  body: {
@@ -143,25 +186,31 @@ cited document, take \`citations[*].recordId\` and call
143
186
  modelKey: args.modelKey,
144
187
  modelName: args.modelName,
145
188
  modelFriendlyName: args.modelFriendlyName,
146
- chatMode: args.chatMode,
189
+ chatMode: args.chatMode ?? "internal_search",
147
190
  },
148
191
  }, { fetchOptions }).$inspect();
149
192
  if (!result.ok) return errorResult(result.error.message);
150
193
  response = result.value;
151
194
  } else {
152
- // Start a new conversation.
195
+ // Start a new (non-agent) conversation.
153
196
  const [result] = await conversationsStreamConversation(client, {
154
197
  query: args.query,
155
198
  filters: args.filters,
156
199
  modelKey: args.modelKey,
157
200
  modelName: args.modelName,
158
201
  modelFriendlyName: args.modelFriendlyName,
159
- chatMode: args.chatMode,
202
+ chatMode: args.chatMode ?? "internal_search",
160
203
  }, { fetchOptions }).$inspect();
161
204
  if (!result.ok) return errorResult(result.error.message);
162
205
  response = result.value;
163
206
  }
164
207
 
208
+ // The streaming funcs accept any HTTP status as a "successful request",
209
+ // so explicitly reject non-2xx (auth, not-found, server errors) with a
210
+ // clear message before we try to read the body as SSE.
211
+ const httpErr = await httpErrorResult(response, "PipesHub chat request");
212
+ if (httpErr) return httpErr;
213
+
165
214
  // Drain the SSE stream. We only need the terminal `complete` (or
166
215
  // `error`) frame; everything else is observability and ignored.
167
216
  let finalConversation: any = null;
@@ -1,4 +1,3 @@
1
- // Curated directory tool. Hand-written; merges four generated tools
2
1
  // (`pipeshub-list-users`, `pipeshub-get-user`, `pipeshub-list-user-groups`,
3
2
  // `pipeshub-list-my-teams`) plus a `whoami` action that decodes the bearer
4
3
  // JWT into a single tool.
@@ -1,7 +1,3 @@
1
- // Curated download tool. Hand-written; replaces the generated
2
- // `pipeshub-download-record`. Pipes raw bytes back through formatResult
3
- // so binary downloads (PDFs, images) are returned correctly.
4
-
5
1
  import * as z from "zod";
6
2
  import { recordsStreamRecordBuffer } from "../../funcs/recordsStreamRecordBuffer.js";
7
3
  import { formatResult, ToolDefinition } from "../tools.js";
@@ -1,6 +1,3 @@
1
- // Curated semantic-search tool. Hand-written; replaces the generated
2
- // `pipeshub-search` tool. Trims the response to the fields the LLM
3
- // actually needs (recordId, recordName, score, snippet, webUrl).
4
1
 
5
2
  import * as z from "zod";
6
3
  import { semanticSearchSearch } from "../../funcs/semanticSearchSearch.js";
@@ -1,8 +1,3 @@
1
- // Curated discovery tool. Hand-written; merges two generated tools
2
- // (`pipeshub-list-sources` for connectors+KB and `pipeshub-list-models`
3
- // for available LLMs / embedding models) into one tool, since the LLM
4
- // almost always wants both before the first chat turn.
5
-
6
1
  import * as z from "zod";
7
2
  import { knowledgeHubGetKnowledgeHubRootNodes } from "../../funcs/knowledgeHubGetKnowledgeHubRootNodes.js";
8
3
  import { aiModelsProvidersGetAvailableModelsByType } from "../../funcs/aiModelsProvidersGetAvailableModelsByType.js";
@@ -0,0 +1,160 @@
1
+ // Request models for the agent
2
+ // endpoints that the vendored SDK does not yet cover:
3
+ // - GET /agents (listAgents)
4
+ // - POST /agents/{agentKey}/conversations/stream (streamAgentConversation)
5
+ // - POST /agents/{agentKey}/conversations/{conversationId}/messages/stream
6
+ // (streamAgentConversationMessage)
7
+ // Mirrors the shapes in the canonical OpenAPI spec; reuses the existing
8
+ // `Filters` model. Unknown fields are stripped server-side, so the shared
9
+ // body schema is safe to reuse for both create and follow-up turns.
10
+
11
+ import * as z from "zod";
12
+ import { Filters, Filters$zodSchema } from "./filters.js";
13
+
14
+ /** Query params for `GET /agents`. */
15
+ export type ListAgentsRequest = {
16
+ page?: number | undefined;
17
+ limit?: number | undefined;
18
+ search?: string | undefined;
19
+ sortBy?: string | undefined;
20
+ sortOrder?: "asc" | "desc" | undefined;
21
+ };
22
+
23
+ export const ListAgentsRequest$zodSchema: z.ZodType<ListAgentsRequest> = z
24
+ .object({
25
+ page: z.int().optional(),
26
+ limit: z.int().optional(),
27
+ search: z.string().optional(),
28
+ sortBy: z.string().optional(),
29
+ sortOrder: z.enum(["asc", "desc"]).optional(),
30
+ });
31
+
32
+ /**
33
+ * Shared body for the agent conversation stream endpoints. `query` is the
34
+ * only required field; everything else is an optional override / routing hint.
35
+ * `chatMode` for agents is one of `auto | quick | verification | deep`.
36
+ */
37
+ export type AgentStreamConversationBody = {
38
+ query: string;
39
+ recordIds?: Array<string> | undefined;
40
+ filters?: Filters | undefined;
41
+ chatMode?: string | undefined;
42
+ modelKey?: string | undefined;
43
+ modelName?: string | undefined;
44
+ modelFriendlyName?: string | undefined;
45
+ tools?: Array<string> | undefined;
46
+ timezone?: string | undefined;
47
+ currentTime?: string | undefined;
48
+ };
49
+
50
+ export const AgentStreamConversationBody$zodSchema: z.ZodType<
51
+ AgentStreamConversationBody
52
+ > = z.object({
53
+ query: z.string().min(1),
54
+ recordIds: z.array(z.string()).optional(),
55
+ filters: Filters$zodSchema.optional(),
56
+ chatMode: z.string().optional(),
57
+ modelKey: z.string().optional(),
58
+ modelName: z.string().optional(),
59
+ modelFriendlyName: z.string().optional(),
60
+ tools: z.array(z.string()).optional(),
61
+ timezone: z.string().optional(),
62
+ currentTime: z.string().optional(),
63
+ });
64
+
65
+ /** `POST /agents/{agentKey}/conversations/stream` — path param + body. */
66
+ export type AgentStreamCreateConversationRequest = {
67
+ agentKey: string;
68
+ body: AgentStreamConversationBody;
69
+ };
70
+
71
+ export const AgentStreamCreateConversationRequest$zodSchema: z.ZodType<
72
+ AgentStreamCreateConversationRequest
73
+ > = z.object({
74
+ agentKey: z.string().min(1),
75
+ body: AgentStreamConversationBody$zodSchema,
76
+ });
77
+
78
+ /**
79
+ * `POST /agents/{agentKey}/conversations/{conversationId}/messages/stream` —
80
+ * two path params + body.
81
+ */
82
+ export type AgentAddMessageStreamRequest = {
83
+ agentKey: string;
84
+ conversationId: string;
85
+ body: AgentStreamConversationBody;
86
+ };
87
+
88
+ export const AgentAddMessageStreamRequest$zodSchema: z.ZodType<
89
+ AgentAddMessageStreamRequest
90
+ > = z.object({
91
+ agentKey: z.string().min(1),
92
+ conversationId: z.string(),
93
+ body: AgentStreamConversationBody$zodSchema,
94
+ });
95
+
96
+ // ── Response models ──────────────────────────────────────────────────────────
97
+ // Validated subsets of the agent responses (only the fields the curated tools
98
+ // surface; zod strips the rest). Types are inferred from the schemas.
99
+ //
100
+ // As of the enriched `GET /agents`, the list item carries `toolsets` and
101
+ // `knowledge` directly, so the curated tool no longer needs a per-agent detail
102
+ // fetch.
103
+
104
+ const AgentWebSearch$zodSchema = z.object({
105
+ provider: z.string().optional(),
106
+ providerKey: z.string().optional(),
107
+ providerLabel: z.string().optional(),
108
+ });
109
+
110
+ const AgentToolsetTool$zodSchema = z.object({
111
+ name: z.string().nullish(),
112
+ fullName: z.string().nullish(),
113
+ });
114
+
115
+ const AgentToolset$zodSchema = z.object({
116
+ name: z.string().nullish(),
117
+ displayName: z.string().nullish(),
118
+ instanceName: z.string().nullish(),
119
+ tools: z.array(AgentToolsetTool$zodSchema).default([]),
120
+ });
121
+
122
+ const AgentKnowledge$zodSchema = z.object({
123
+ name: z.string().nullish(),
124
+ displayName: z.string().nullish(),
125
+ type: z.string().nullish(),
126
+ });
127
+
128
+ /**
129
+ * One item of the enriched `GET /agents` list — includes `toolsets` and
130
+ * `knowledge` (the routing-critical capability signal). `default([])` keeps it
131
+ * working against an older list endpoint that doesn't return them.
132
+ */
133
+ export const AgentSummary$zodSchema = z.object({
134
+ _key: z.string(),
135
+ name: z.string(),
136
+ description: z.string().nullish(),
137
+ systemPrompt: z.string().nullish(),
138
+ startMessage: z.string().nullish(),
139
+ tags: z.array(z.string()).default([]),
140
+ webSearch: AgentWebSearch$zodSchema.nullish(),
141
+ isActive: z.boolean().nullish(),
142
+ toolsets: z.array(AgentToolset$zodSchema).default([]),
143
+ knowledge: z.array(AgentKnowledge$zodSchema).default([]),
144
+ });
145
+ export type AgentSummary = z.infer<typeof AgentSummary$zodSchema>;
146
+
147
+ const AgentListPagination$zodSchema = z.object({
148
+ totalItems: z.number().nullish(),
149
+ total: z.number().nullish(),
150
+ hasNext: z.boolean().nullish(),
151
+ hasNextPage: z.boolean().nullish(),
152
+ });
153
+
154
+ /** Envelope of `GET /agents`. */
155
+ export const AgentListEnvelope$zodSchema = z.object({
156
+ success: z.boolean().nullish(),
157
+ agents: z.array(AgentSummary$zodSchema).default([]),
158
+ pagination: AgentListPagination$zodSchema.nullish(),
159
+ });
160
+ export type AgentListEnvelope = z.infer<typeof AgentListEnvelope$zodSchema>;
package/src/tool-names.ts CHANGED
@@ -19,5 +19,9 @@ export const toolNames: Array<{ name: string; description: string }>= [
19
19
  {
20
20
  "name": "pipeshub_directory",
21
21
  "description": "Look up people, groups, and teams in PipesHub. One tool with five\nactions — pick the right `action`:\n\n- `whoami` — who is the caller? Use this whenever you need the\n authenticated user's own id, email, or full name (e.g. before\n `get_user` on themselves).\n- `list_users` — search / page through org users.\n- `get_user` — full `User` document for one user (requires `userId`).\n- `list_groups` — list user groups with `userCount`.\n- `list_my_teams` — teams the caller belongs to, with capability flags\n (`canEdit` / `canDelete` / `canManageMembers`).\n\nOutput shape varies by action; see each action's docs above."
22
+ },
23
+ {
24
+ "name": "pipeshub_agents",
25
+ "description": "List the PipesHub **agents** configured for this org, each with its\ncapabilities.\n\nAgents are specialized assistants (custom system prompt, tools, knowledge\nscope). To converse with one, take its `agentId` and pass it to\n`pipeshub_chat`'s `agentId` argument.\n\nEach agent is returned as:\n`{ agentId, name, description, systemPrompt, startMessage, tags, webSearch,\nisActive, toolsets, knowledge }`.\n- `toolsets` — what the agent can DO: each `{ name, tools }` where `name`\n is the connector (e.g. `jira`, `gmail`) and `tools` are the runnable\n tool ids (e.g. `jira.create_issue`, `gmail.send_email`).\n- `knowledge` — what the agent can READ: each `{ name, type }` (e.g.\n `Confluence-2` / `Confluence`).\n\n**Route on `toolsets`/`knowledge`, not the name** — names and descriptions\nare often generic or misleading. Match the request to the agent whose tools can\nactually perform it (e.g. \"create a Jira ticket\" → the agent whose toolset is\n`jira` and whose tools include `jira.create_issue`). If NO agent has a tool\nfor the requested action, say so — don't force an unrelated agent.\n\nThe list **may be empty** (no agents configured). For plain Q&A when no\nspecific agent is needed, use `pipeshub_chat` WITHOUT `agentId` and pick a\n`chatMode`: `internal_search` (org's indexed knowledge) or `web_search`\n(live web). Use `agentId` everywhere an agent is referenced."
22
26
  }
23
27
  ];