@pipeshub-ai/mcp 2.0.1 → 2.2.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 (117) hide show
  1. package/README.md +90 -2
  2. package/bin/mcp-server.js +1156 -213
  3. package/bin/mcp-server.js.map +30 -16
  4. package/esm/funcs/agentsListAgents.d.ts +10 -0
  5. package/esm/funcs/agentsListAgents.d.ts.map +1 -0
  6. package/esm/funcs/agentsListAgents.js +83 -0
  7. package/esm/funcs/agentsListAgents.js.map +1 -0
  8. package/esm/funcs/agentsStreamConversation.d.ts +10 -0
  9. package/esm/funcs/agentsStreamConversation.d.ts.map +1 -0
  10. package/esm/funcs/agentsStreamConversation.js +83 -0
  11. package/esm/funcs/agentsStreamConversation.js.map +1 -0
  12. package/esm/funcs/agentsStreamMessage.d.ts +10 -0
  13. package/esm/funcs/agentsStreamMessage.d.ts.map +1 -0
  14. package/esm/funcs/agentsStreamMessage.js +86 -0
  15. package/esm/funcs/agentsStreamMessage.js.map +1 -0
  16. package/esm/funcs/connectorGetRecordContent.d.ts +43 -0
  17. package/esm/funcs/connectorGetRecordContent.d.ts.map +1 -0
  18. package/esm/funcs/connectorGetRecordContent.js +114 -0
  19. package/esm/funcs/connectorGetRecordContent.js.map +1 -0
  20. package/esm/funcs/conversationsStreamConversation.d.ts.map +1 -1
  21. package/esm/funcs/conversationsStreamConversation.js +2 -4
  22. package/esm/funcs/conversationsStreamConversation.js.map +1 -1
  23. package/esm/funcs/conversationsStreamMessage.d.ts.map +1 -1
  24. package/esm/funcs/conversationsStreamMessage.js +0 -7
  25. package/esm/funcs/conversationsStreamMessage.js.map +1 -1
  26. package/esm/funcs/userGroupsGetAllUserGroups.js +1 -1
  27. package/esm/funcs/userGroupsGetAllUserGroups.js.map +1 -1
  28. package/esm/hooks/registration.d.ts.map +1 -1
  29. package/esm/hooks/registration.js +2 -1
  30. package/esm/hooks/registration.js.map +1 -1
  31. package/esm/hooks/request-context.d.ts +10 -0
  32. package/esm/hooks/request-context.d.ts.map +1 -0
  33. package/esm/hooks/request-context.js +38 -0
  34. package/esm/hooks/request-context.js.map +1 -0
  35. package/esm/hooks/requestid.d.ts +5 -0
  36. package/esm/hooks/requestid.d.ts.map +1 -0
  37. package/esm/hooks/requestid.js +58 -0
  38. package/esm/hooks/requestid.js.map +1 -0
  39. package/esm/mcp-server/instructions.d.ts +2 -0
  40. package/esm/mcp-server/instructions.d.ts.map +1 -0
  41. package/esm/mcp-server/instructions.js +106 -0
  42. package/esm/mcp-server/instructions.js.map +1 -0
  43. package/esm/mcp-server/prompts/pipeshubAssistant.d.ts +8 -0
  44. package/esm/mcp-server/prompts/pipeshubAssistant.d.ts.map +1 -0
  45. package/esm/mcp-server/prompts/pipeshubAssistant.js +31 -0
  46. package/esm/mcp-server/prompts/pipeshubAssistant.js.map +1 -0
  47. package/esm/mcp-server/server.d.ts.map +1 -1
  48. package/esm/mcp-server/server.js +16 -62
  49. package/esm/mcp-server/server.js.map +1 -1
  50. package/esm/mcp-server/tools/_helpers.d.ts +78 -0
  51. package/esm/mcp-server/tools/_helpers.d.ts.map +1 -1
  52. package/esm/mcp-server/tools/_helpers.js +155 -1
  53. package/esm/mcp-server/tools/_helpers.js.map +1 -1
  54. package/esm/mcp-server/tools/pipeshubAgents.d.ts +8 -0
  55. package/esm/mcp-server/tools/pipeshubAgents.d.ts.map +1 -0
  56. package/esm/mcp-server/tools/pipeshubAgents.js +62 -0
  57. package/esm/mcp-server/tools/pipeshubAgents.js.map +1 -0
  58. package/esm/mcp-server/tools/pipeshubChat.d.ts +6 -1
  59. package/esm/mcp-server/tools/pipeshubChat.d.ts.map +1 -1
  60. package/esm/mcp-server/tools/pipeshubChat.js +79 -23
  61. package/esm/mcp-server/tools/pipeshubChat.js.map +1 -1
  62. package/esm/mcp-server/tools/pipeshubDirectory.d.ts.map +1 -1
  63. package/esm/mcp-server/tools/pipeshubDirectory.js +0 -1
  64. package/esm/mcp-server/tools/pipeshubDirectory.js.map +1 -1
  65. package/esm/mcp-server/tools/pipeshubDownloadRecord.d.ts.map +1 -1
  66. package/esm/mcp-server/tools/pipeshubDownloadRecord.js +0 -3
  67. package/esm/mcp-server/tools/pipeshubDownloadRecord.js.map +1 -1
  68. package/esm/mcp-server/tools/pipeshubGetRecordContent.d.ts +8 -0
  69. package/esm/mcp-server/tools/pipeshubGetRecordContent.d.ts.map +1 -0
  70. package/esm/mcp-server/tools/pipeshubGetRecordContent.js +63 -0
  71. package/esm/mcp-server/tools/pipeshubGetRecordContent.js.map +1 -0
  72. package/esm/mcp-server/tools/pipeshubSearch.d.ts.map +1 -1
  73. package/esm/mcp-server/tools/pipeshubSearch.js +8 -7
  74. package/esm/mcp-server/tools/pipeshubSearch.js.map +1 -1
  75. package/esm/mcp-server/tools/pipeshubSources.d.ts.map +1 -1
  76. package/esm/mcp-server/tools/pipeshubSources.js +0 -4
  77. package/esm/mcp-server/tools/pipeshubSources.js.map +1 -1
  78. package/esm/mcp-server/tools.d.ts.map +1 -1
  79. package/esm/mcp-server/tools.js +13 -1
  80. package/esm/mcp-server/tools.js.map +1 -1
  81. package/esm/models/agentops.d.ts +119 -0
  82. package/esm/models/agentops.d.ts.map +1 -0
  83. package/esm/models/agentops.js +97 -0
  84. package/esm/models/agentops.js.map +1 -0
  85. package/esm/models/getrecordcontentop.d.ts +6 -0
  86. package/esm/models/getrecordcontentop.d.ts.map +1 -0
  87. package/esm/models/getrecordcontentop.js +5 -0
  88. package/esm/models/getrecordcontentop.js.map +1 -0
  89. package/esm/tool-names.d.ts.map +1 -1
  90. package/esm/tool-names.js +10 -2
  91. package/esm/tool-names.js.map +1 -1
  92. package/package.json +3 -2
  93. package/src/funcs/agentsListAgents.ts +141 -0
  94. package/src/funcs/agentsStreamConversation.ts +144 -0
  95. package/src/funcs/agentsStreamMessage.ts +147 -0
  96. package/src/funcs/connectorGetRecordContent.ts +179 -0
  97. package/src/funcs/conversationsStreamConversation.ts +2 -4
  98. package/src/funcs/conversationsStreamMessage.ts +0 -8
  99. package/src/funcs/userGroupsGetAllUserGroups.ts +1 -1
  100. package/src/hooks/registration.ts +2 -1
  101. package/src/hooks/request-context.ts +47 -0
  102. package/src/hooks/requestid.ts +62 -0
  103. package/src/mcp-server/instructions.ts +106 -0
  104. package/src/mcp-server/prompts/pipeshubAssistant.ts +37 -0
  105. package/src/mcp-server/server.ts +18 -64
  106. package/src/mcp-server/tools/_helpers.ts +216 -1
  107. package/src/mcp-server/tools/pipeshubAgents.ts +68 -0
  108. package/src/mcp-server/tools/pipeshubChat.ts +78 -23
  109. package/src/mcp-server/tools/pipeshubDirectory.ts +0 -1
  110. package/src/mcp-server/tools/pipeshubDownloadRecord.ts +0 -4
  111. package/src/mcp-server/tools/pipeshubGetRecordContent.ts +67 -0
  112. package/src/mcp-server/tools/pipeshubSearch.ts +8 -7
  113. package/src/mcp-server/tools/pipeshubSources.ts +0 -5
  114. package/src/mcp-server/tools.ts +14 -1
  115. package/src/models/agentops.ts +160 -0
  116. package/src/models/getrecordcontentop.ts +11 -0
  117. package/src/tool-names.ts +10 -2
@@ -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
 
@@ -91,6 +100,9 @@ the user asks about their documents, files, knowledge base, company policies,
91
100
  or anything that could plausibly be answered by content in their PipesHub-indexed
92
101
  sources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).
93
102
  Grounds the answer in the user's actual data and returns citations.
103
+ Answers come from a few retrieved passages, not whole documents — for
104
+ any task needing a document's full content, use
105
+ \`pipeshub_get_record_content\` instead.
94
106
 
95
107
  **Web search** (\`chatMode: "web_search"\`): Use when the user asks about
96
108
  current events, public information, or anything unlikely to be in the org's
@@ -98,8 +110,11 @@ internal knowledge base. Pass \`chatMode: "web_search"\` and this tool will
98
110
  search the public web instead.
99
111
 
100
112
  **When to pick this over other tools:**
101
- - "What does <document> say about X?" \`pipeshub_chat\` (internal_search)
102
- - "Summarize <topic / doc>." \`pipeshub_chat\` (internal_search)
113
+ - "Summarize <doc>" / "key points of <doc>" / "what does <document> say
114
+ about X?" NOT this tool. Use \`pipeshub_search\`
115
+ \`pipeshub_get_record_content\`: answering for a specific document
116
+ requires its full content, and chat only sees a few retrieved
117
+ passages, never the whole document.
103
118
  - "What's our policy on Y?" → \`pipeshub_chat\` (internal_search)
104
119
  - "What's in the news about Z?" → \`pipeshub_chat\` (web_search)
105
120
  - "What is the latest version of <library>?" → \`pipeshub_chat\` (web_search)
@@ -134,8 +149,42 @@ cited document, take \`citations[*].recordId\` and call
134
149
  const fetchOptions = { signal: ctx.signal };
135
150
  let response: Response;
136
151
 
137
- if (args.conversationId) {
138
- // Continue an existing conversation.
152
+ if (args.agentId) {
153
+ // Agent conversation. Agent chatMode vocabulary defaults to `auto`.
154
+ const agentChatMode = args.chatMode ?? "auto";
155
+ if (args.conversationId) {
156
+ // Continue an existing agent conversation.
157
+ const [result] = await agentsStreamMessage(client, {
158
+ agentKey: args.agentId,
159
+ conversationId: args.conversationId,
160
+ body: {
161
+ query: args.query,
162
+ modelKey: args.modelKey,
163
+ modelName: args.modelName,
164
+ modelFriendlyName: args.modelFriendlyName,
165
+ chatMode: agentChatMode,
166
+ },
167
+ }, { fetchOptions }).$inspect();
168
+ if (!result.ok) return errorResult(result.error.message);
169
+ response = result.value;
170
+ } else {
171
+ // Start a new agent conversation.
172
+ const [result] = await agentsStreamConversation(client, {
173
+ agentKey: args.agentId,
174
+ body: {
175
+ query: args.query,
176
+ filters: args.filters,
177
+ modelKey: args.modelKey,
178
+ modelName: args.modelName,
179
+ modelFriendlyName: args.modelFriendlyName,
180
+ chatMode: agentChatMode,
181
+ },
182
+ }, { fetchOptions }).$inspect();
183
+ if (!result.ok) return errorResult(result.error.message);
184
+ response = result.value;
185
+ }
186
+ } else if (args.conversationId) {
187
+ // Continue an existing (non-agent) conversation.
139
188
  const [result] = await conversationsStreamMessage(client, {
140
189
  conversationId: args.conversationId,
141
190
  body: {
@@ -143,25 +192,31 @@ cited document, take \`citations[*].recordId\` and call
143
192
  modelKey: args.modelKey,
144
193
  modelName: args.modelName,
145
194
  modelFriendlyName: args.modelFriendlyName,
146
- chatMode: args.chatMode,
195
+ chatMode: args.chatMode ?? "internal_search",
147
196
  },
148
197
  }, { fetchOptions }).$inspect();
149
198
  if (!result.ok) return errorResult(result.error.message);
150
199
  response = result.value;
151
200
  } else {
152
- // Start a new conversation.
201
+ // Start a new (non-agent) conversation.
153
202
  const [result] = await conversationsStreamConversation(client, {
154
203
  query: args.query,
155
204
  filters: args.filters,
156
205
  modelKey: args.modelKey,
157
206
  modelName: args.modelName,
158
207
  modelFriendlyName: args.modelFriendlyName,
159
- chatMode: args.chatMode,
208
+ chatMode: args.chatMode ?? "internal_search",
160
209
  }, { fetchOptions }).$inspect();
161
210
  if (!result.ok) return errorResult(result.error.message);
162
211
  response = result.value;
163
212
  }
164
213
 
214
+ // The streaming funcs accept any HTTP status as a "successful request",
215
+ // so explicitly reject non-2xx (auth, not-found, server errors) with a
216
+ // clear message before we try to read the body as SSE.
217
+ const httpErr = await httpErrorResult(response, "PipesHub chat request");
218
+ if (httpErr) return httpErr;
219
+
165
220
  // Drain the SSE stream. We only need the terminal `complete` (or
166
221
  // `error`) frame; everything else is observability and ignored.
167
222
  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";
@@ -0,0 +1,67 @@
1
+ import * as z from "zod";
2
+ import { connectorGetRecordContent } from "../../funcs/connectorGetRecordContent.js";
3
+ import { ToolDefinition } from "../tools.js";
4
+ import { errorResult, httpErrorResult, readJson } from "./_helpers.js";
5
+
6
+ const args = {
7
+ recordId: z.string().min(1).describe(
8
+ "Record identifier — usually a UUID for connector-sourced records or "
9
+ + "a 24-character ObjectId for uploaded records. Get it from a chat "
10
+ + "citation (`citations[*].recordId`) or from a `pipeshub_search` hit.",
11
+ ),
12
+ };
13
+
14
+ export const tool$pipeshubGetRecordContent: ToolDefinition<typeof args> = {
15
+ name: "pipeshub_get_record_content",
16
+ description:
17
+ `Read a record's full parsed content by \`recordId\` — the only way to
18
+ see a document's COMPLETE text.
19
+
20
+ Use it whenever the answer depends on a document's full content — any
21
+ task where missing a part could make the answer wrong: summarize / key
22
+ points / action items; extract or list ALL of something; check whether
23
+ or where the doc mentions X; translate, rewrite, outline, or review the
24
+ doc; compare named docs (fetch each); any question scoped to one named
25
+ document. \`pipeshub_chat\` cannot do these — it only sees a few
26
+ retrieved passages, never the whole document. Get the \`recordId\` from
27
+ a \`pipeshub_search\` top hit or a chat citation.
28
+
29
+ Judge by the user's INTENT, not their keywords: they need not say
30
+ "summarize", "key points", or "extract". Reason about what a good
31
+ answer requires — if it would need the whole document (e.g. "what's
32
+ this doc about?", "walk me through the report", "anything in here
33
+ about Y?"), that is a full-content task, so call this tool.
34
+
35
+ Returns a single \`content\` string: a short metadata header (title,
36
+ source, key fields, and a pre-generated summary) followed by the
37
+ record's full parsed text — paragraphs, tables, and lists in reading
38
+ order. For a record with no extractable content, \`content\` is the
39
+ literal \`No record found\`. Use \`pipeshub_download_record\` only when
40
+ you need the original file bytes.`,
41
+ scopes: ["read"],
42
+ annotations: {
43
+ title: "Get a record's full parsed content",
44
+ destructiveHint: false,
45
+ idempotentHint: true,
46
+ openWorldHint: false,
47
+ readOnlyHint: true,
48
+ },
49
+ args,
50
+ tool: async (client, args, ctx) => {
51
+ const [result] = await connectorGetRecordContent(client, {
52
+ recordId: args.recordId,
53
+ }, { fetchOptions: { signal: ctx.signal } }).$inspect();
54
+ if (!result.ok) return errorResult(result.error.message);
55
+
56
+ // The SDK func uses errorCodes:[], so any non-2xx comes back as an
57
+ // ok=false Response — surface it as an error rather than parsing it.
58
+ const httpErr = await httpErrorResult(result.value, "Get record content");
59
+ if (httpErr) return httpErr;
60
+
61
+ // Success: the endpoint returns { content: <string> }. Hand the LLM the
62
+ // plain text (real newlines), not the JSON wrapper.
63
+ const parsed = await readJson<{ content?: string }>(result.value);
64
+ if (!parsed.ok) return parsed.result;
65
+ return { content: [{ type: "text", text: parsed.value.content ?? "" }] };
66
+ },
67
+ };
@@ -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";
@@ -26,12 +23,16 @@ export const tool$pipeshubSearch: ToolDefinition<typeof args> = {
26
23
  description:
27
24
  `Vector / semantic search across the org's indexed documents.
28
25
 
29
- **Use this only when the user wants to LOCATE a document** — by name,
30
- topic, or a phrase to grep for. For "what does the document say about
31
- X?" or any open-ended question, use \`pipeshub_chat\` instead, which
32
- does the retrieval internally and grounds the answer in citations.
26
+ **Use this when the user wants to LOCATE a document** — by name, topic,
27
+ or a phrase to grep for and to resolve it to a \`recordId\`. For
28
+ open-ended questions across many documents, use \`pipeshub_chat\`
29
+ instead, which does the retrieval internally and grounds the answer in
30
+ citations.
33
31
 
34
32
  Typical uses:
33
+ - Resolve a doc name / topic into a \`recordId\` for
34
+ \`pipeshub_get_record_content\` — step 1 of any full-document task
35
+ (summarize, extract, review, "what does the doc say?").
35
36
  - Resolve a filename / phrase into a \`recordId\` for
36
37
  \`pipeshub_download_record\`.
37
38
  - Show the user a ranked list of matching files when they ask "find /
@@ -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";
@@ -75,9 +75,22 @@ export async function formatResult(
75
75
  content = data == null
76
76
  ? []
77
77
  : [{ type: "audio", data, mimeType: contentType }];
78
- } else {
78
+ } else if (
79
+ contentType.startsWith("text/")
80
+ || contentType.includes("json")
81
+ || contentType.includes("xml")
82
+ || contentType.includes("yaml")
83
+ ) {
79
84
  const text = await response.text();
80
85
  content = [{ type: "text", text }];
86
+ } else {
87
+ const blob = await valueToBase64(await response.arrayBuffer());
88
+ content = blob == null
89
+ ? []
90
+ : [{
91
+ type: "resource",
92
+ resource: { uri: response.url, mimeType: contentType, blob },
93
+ }];
81
94
  }
82
95
 
83
96
  return response.ok ? { content } : { content, isError: true };
@@ -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>;
@@ -0,0 +1,11 @@
1
+ import * as z from "zod";
2
+
3
+ export type GetRecordContentRequest = { recordId: string };
4
+
5
+ export const GetRecordContentRequest$zodSchema: z.ZodType<
6
+ GetRecordContentRequest
7
+ > = z.object({
8
+ recordId: z.string().describe(
9
+ "Record ID to fetch. Obtain it from a `pipeshub_search` result (`hits[*].recordId`) or a chat citation (`citations[*].recordId`).",
10
+ ),
11
+ });
package/src/tool-names.ts CHANGED
@@ -6,18 +6,26 @@ export const toolNames: Array<{ name: string; description: string }>= [
6
6
  },
7
7
  {
8
8
  "name": "pipeshub_chat",
9
- "description": "**Primary chat tool — handles both internal knowledge queries and web search.**\n\n**Internal search** (default, `chatMode: \"internal_search\"`): Use whenever\nthe user asks about their documents, files, knowledge base, company policies,\nor anything that could plausibly be answered by content in their PipesHub-indexed\nsources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).\nGrounds the answer in the user's actual data and returns citations.\n\n**Web search** (`chatMode: \"web_search\"`): Use when the user asks about\ncurrent events, public information, or anything unlikely to be in the org's\ninternal knowledge base. Pass `chatMode: \"web_search\"` and this tool will\nsearch the public web instead.\n\n**When to pick this over other tools:**\n- \"What does <document> say about X?\" → `pipeshub_chat` (internal_search)\n- \"Summarize <topic / doc>.\" `pipeshub_chat` (internal_search)\n- \"What's our policy on Y?\" → `pipeshub_chat` (internal_search)\n- \"What's in the news about Z?\" → `pipeshub_chat` (web_search)\n- \"What is the latest version of <library>?\" → `pipeshub_chat` (web_search)\n- \"Find / locate the file named X\" → `pipeshub_search` (then\n `pipeshub_download_record` if the user wants the bytes).\n\n**Conversation lifecycle** — one tool, both start and continue:\n\n- **First turn**: omit `conversationId`. The server creates a new\n conversation; capture `conversationId` from the response.\n- **Follow-up turn**: pass the `conversationId` from the previous\n response. Server-side context is preserved — do NOT replay earlier\n messages, and `filters` is ignored on follow-ups (set once at\n creation).\n\nOnly re-omit `conversationId` (start a fresh conversation) when the\nuser explicitly asks to start over / clear context.\n\nThe response contains the AI's `answer` plus `citations`. To download a\ncited document, take `citations[*].recordId` and call\n`pipeshub_download_record`."
9
+ "description": "**Primary chat tool — handles both internal knowledge queries and web search.**\n\n**Internal search** (default, `chatMode: \"internal_search\"`): Use whenever\nthe user asks about their documents, files, knowledge base, company policies,\nor anything that could plausibly be answered by content in their PipesHub-indexed\nsources (Drive, Box, Confluence, Slack, Gmail, Jira, the org's KB, ...).\nGrounds the answer in the user's actual data and returns citations.\nAnswers come from a few retrieved passages, not whole documents — for\nany task needing a document's full content, use\n`pipeshub_get_record_content` instead.\n\n**Web search** (`chatMode: \"web_search\"`): Use when the user asks about\ncurrent events, public information, or anything unlikely to be in the org's\ninternal knowledge base. Pass `chatMode: \"web_search\"` and this tool will\nsearch the public web instead.\n\n**When to pick this over other tools:**\n- \"Summarize <doc>\" / \"key points of <doc>\" / \"what does <document> say\n about X?\" → NOT this tool. Use `pipeshub_search` →\n `pipeshub_get_record_content`: answering for a specific document\n requires its full content, and chat only sees a few retrieved\n passages, never the whole document.\n- \"What's our policy on Y?\" → `pipeshub_chat` (internal_search)\n- \"What's in the news about Z?\" → `pipeshub_chat` (web_search)\n- \"What is the latest version of <library>?\" → `pipeshub_chat` (web_search)\n- \"Find / locate the file named X\" → `pipeshub_search` (then\n `pipeshub_download_record` if the user wants the bytes).\n\n**Conversation lifecycle** — one tool, both start and continue:\n\n- **First turn**: omit `conversationId`. The server creates a new\n conversation; capture `conversationId` from the response.\n- **Follow-up turn**: pass the `conversationId` from the previous\n response. Server-side context is preserved — do NOT replay earlier\n messages, and `filters` is ignored on follow-ups (set once at\n creation).\n\nOnly re-omit `conversationId` (start a fresh conversation) when the\nuser explicitly asks to start over / clear context.\n\nThe response contains the AI's `answer` plus `citations`. To download a\ncited document, take `citations[*].recordId` and call\n`pipeshub_download_record`."
10
10
  },
11
11
  {
12
12
  "name": "pipeshub_search",
13
- "description": "Vector / semantic search across the org's indexed documents.\n\n**Use this only when the user wants to LOCATE a document** — by name,\ntopic, or a phrase to grep for. For \"what does the document say about\nX?\" or any open-ended question, use `pipeshub_chat` instead, which\ndoes the retrieval internally and grounds the answer in citations.\n\nTypical uses:\n- Resolve a filename / phrase into a `recordId` for\n `pipeshub_download_record`.\n- Show the user a ranked list of matching files when they ask \"find /\n search for X\".\n\nThe response is trimmed to one row per hit:\n`{ recordId, recordName, score, snippet, mimeType, webUrl, ... }`.\nHighest `score` first; multiple hits may share the same `recordId`\n(different chunks of the same record)."
13
+ "description": "Vector / semantic search across the org's indexed documents.\n\n**Use this when the user wants to LOCATE a document** — by name, topic,\nor a phrase to grep for and to resolve it to a `recordId`. For\nopen-ended questions across many documents, use `pipeshub_chat`\ninstead, which does the retrieval internally and grounds the answer in\ncitations.\n\nTypical uses:\n- Resolve a doc name / topic into a `recordId` for\n `pipeshub_get_record_content` — step 1 of any full-document task\n (summarize, extract, review, \"what does the doc say?\").\n- Resolve a filename / phrase into a `recordId` for\n `pipeshub_download_record`.\n- Show the user a ranked list of matching files when they ask \"find /\n search for X\".\n\nThe response is trimmed to one row per hit:\n`{ recordId, recordName, score, snippet, mimeType, webUrl, ... }`.\nHighest `score` first; multiple hits may share the same `recordId`\n(different chunks of the same record)."
14
14
  },
15
15
  {
16
16
  "name": "pipeshub_download_record",
17
17
  "description": "Stream the binary content of a single record from PipesHub.\n\nTypical sources for the `recordId`:\n- A chat citation:\n `pipeshub_chat` response → `citations[*].recordId`.\n- A search result:\n `pipeshub_search` response → `hits[*].recordId` /\n `uniqueRecords[*].recordId`.\n\nResponse `Content-Type` is forwarded from the upstream service —\n`application/pdf`, `application/octet-stream`, etc. Binary content is\nreturned base64-encoded; text content is returned inline."
18
18
  },
19
+ {
20
+ "name": "pipeshub_get_record_content",
21
+ "description": "Read a record's full parsed content by `recordId` — the only way to\nsee a document's COMPLETE text.\n\nUse it whenever the answer depends on a document's full content — any\ntask where missing a part could make the answer wrong: summarize / key\npoints / action items; extract or list ALL of something; check whether\nor where the doc mentions X; translate, rewrite, outline, or review the\ndoc; compare named docs (fetch each); any question scoped to one named\ndocument. `pipeshub_chat` cannot do these — it only sees a few\nretrieved passages, never the whole document. Get the `recordId` from\na `pipeshub_search` top hit or a chat citation.\n\nJudge by the user's INTENT, not their keywords: they need not say\n\"summarize\", \"key points\", or \"extract\". Reason about what a good\nanswer requires — if it would need the whole document (e.g. \"what's\nthis doc about?\", \"walk me through the report\", \"anything in here\nabout Y?\"), that is a full-content task, so call this tool.\n\nReturns a single `content` string: a short metadata header (title,\nsource, key fields, and a pre-generated summary) followed by the\nrecord's full parsed text — paragraphs, tables, and lists in reading\norder. For a record with no extractable content, `content` is the\nliteral `No record found`. Use `pipeshub_download_record` only when\nyou need the original file bytes."
22
+ },
19
23
  {
20
24
  "name": "pipeshub_directory",
21
25
  "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."
26
+ },
27
+ {
28
+ "name": "pipeshub_agents",
29
+ "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
30
  }
23
31
  ];