@pipeshub-ai/mcp 2.0.0 → 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 +719 -100
  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 +88 -26
  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 +5 -1
  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 +87 -26
  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 +5 -1
@@ -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,29 +65,53 @@ const args = {
68
65
  ),
69
66
  modelName: z.string().optional(),
70
67
  modelFriendlyName: z.string().optional(),
71
- chatMode: z.enum(["quick", "balanced"]).optional().describe(
72
- "`quick` for low-retrieval / fast answers; `balanced` for full RAG. "
73
- + "Default `quick`.",
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`.",
74
90
  ),
75
91
  };
76
92
 
77
93
  export const tool$pipeshubChat: ToolDefinition<typeof args> = {
78
94
  name: "pipeshub_chat",
79
95
  description:
80
- `**Default tool for anything PipesHub-related.** Use this whenever the
81
- user asks about their documents, files, knowledge base, company
82
- policies, or anything that could plausibly be answered by content in
83
- their PipesHub-indexed sources (Drive, Box, Confluence, Slack, Gmail,
84
- Jira, the org's KB, ...). Do NOT answer those from your own knowledge —
85
- \`pipeshub_chat\` grounds the answer in the user's actual data and
86
- returns citations.
96
+ `**Primary chat tool handles both internal knowledge queries and web search.**
97
+
98
+ **Internal search** (default, \`chatMode: "internal_search"\`): Use whenever
99
+ the user asks about their documents, files, knowledge base, company policies,
100
+ or anything that could plausibly be answered by content in their PipesHub-indexed
101
+ sources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).
102
+ Grounds the answer in the user's actual data and returns citations.
103
+
104
+ **Web search** (\`chatMode: "web_search"\`): Use when the user asks about
105
+ current events, public information, or anything unlikely to be in the org's
106
+ internal knowledge base. Pass \`chatMode: "web_search"\` and this tool will
107
+ search the public web instead.
87
108
 
88
109
  **When to pick this over other tools:**
89
- - "What does <document> say about X?" → \`pipeshub_chat\` (NOT search;
90
- search is only for locating files by name).
91
- - "Summarize <topic / doc>." → \`pipeshub_chat\`.
92
- - "What's our policy on Y?" → \`pipeshub_chat\`.
93
- - "What do we know about Z?" → \`pipeshub_chat\`.
110
+ - "What does <document> say about X?" → \`pipeshub_chat\` (internal_search)
111
+ - "Summarize <topic / doc>." \`pipeshub_chat\` (internal_search)
112
+ - "What's our policy on Y?" → \`pipeshub_chat\` (internal_search)
113
+ - "What's in the news about Z?" → \`pipeshub_chat\` (web_search)
114
+ - "What is the latest version of <library>?" → \`pipeshub_chat\` (web_search)
94
115
  - "Find / locate the file named X" → \`pipeshub_search\` (then
95
116
  \`pipeshub_download_record\` if the user wants the bytes).
96
117
 
@@ -122,8 +143,42 @@ cited document, take \`citations[*].recordId\` and call
122
143
  const fetchOptions = { signal: ctx.signal };
123
144
  let response: Response;
124
145
 
125
- if (args.conversationId) {
126
- // 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.
127
182
  const [result] = await conversationsStreamMessage(client, {
128
183
  conversationId: args.conversationId,
129
184
  body: {
@@ -131,25 +186,31 @@ cited document, take \`citations[*].recordId\` and call
131
186
  modelKey: args.modelKey,
132
187
  modelName: args.modelName,
133
188
  modelFriendlyName: args.modelFriendlyName,
134
- chatMode: args.chatMode,
189
+ chatMode: args.chatMode ?? "internal_search",
135
190
  },
136
191
  }, { fetchOptions }).$inspect();
137
192
  if (!result.ok) return errorResult(result.error.message);
138
193
  response = result.value;
139
194
  } else {
140
- // Start a new conversation.
195
+ // Start a new (non-agent) conversation.
141
196
  const [result] = await conversationsStreamConversation(client, {
142
197
  query: args.query,
143
198
  filters: args.filters,
144
199
  modelKey: args.modelKey,
145
200
  modelName: args.modelName,
146
201
  modelFriendlyName: args.modelFriendlyName,
147
- chatMode: args.chatMode,
202
+ chatMode: args.chatMode ?? "internal_search",
148
203
  }, { fetchOptions }).$inspect();
149
204
  if (!result.ok) return errorResult(result.error.message);
150
205
  response = result.value;
151
206
  }
152
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
+
153
214
  // Drain the SSE stream. We only need the terminal `complete` (or
154
215
  // `error`) frame; everything else is observability and ignored.
155
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>;