@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.
- package/bin/mcp-server.js +719 -100
- package/bin/mcp-server.js.map +20 -13
- package/esm/funcs/agentsListAgents.d.ts +10 -0
- package/esm/funcs/agentsListAgents.d.ts.map +1 -0
- package/esm/funcs/agentsListAgents.js +83 -0
- package/esm/funcs/agentsListAgents.js.map +1 -0
- package/esm/funcs/agentsStreamConversation.d.ts +10 -0
- package/esm/funcs/agentsStreamConversation.d.ts.map +1 -0
- package/esm/funcs/agentsStreamConversation.js +83 -0
- package/esm/funcs/agentsStreamConversation.js.map +1 -0
- package/esm/funcs/agentsStreamMessage.d.ts +10 -0
- package/esm/funcs/agentsStreamMessage.d.ts.map +1 -0
- package/esm/funcs/agentsStreamMessage.js +86 -0
- package/esm/funcs/agentsStreamMessage.js.map +1 -0
- package/esm/funcs/conversationsStreamConversation.d.ts.map +1 -1
- package/esm/funcs/conversationsStreamConversation.js +1 -3
- package/esm/funcs/conversationsStreamConversation.js.map +1 -1
- package/esm/funcs/conversationsStreamMessage.d.ts.map +1 -1
- package/esm/funcs/conversationsStreamMessage.js +0 -7
- package/esm/funcs/conversationsStreamMessage.js.map +1 -1
- package/esm/mcp-server/instructions.d.ts +2 -0
- package/esm/mcp-server/instructions.d.ts.map +1 -0
- package/esm/mcp-server/instructions.js +83 -0
- package/esm/mcp-server/instructions.js.map +1 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.d.ts +8 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.d.ts.map +1 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.js +31 -0
- package/esm/mcp-server/prompts/pipeshubAssistant.js.map +1 -0
- package/esm/mcp-server/server.d.ts.map +1 -1
- package/esm/mcp-server/server.js +7 -60
- package/esm/mcp-server/server.js.map +1 -1
- package/esm/mcp-server/tools/_helpers.d.ts +78 -0
- package/esm/mcp-server/tools/_helpers.d.ts.map +1 -1
- package/esm/mcp-server/tools/_helpers.js +155 -1
- package/esm/mcp-server/tools/_helpers.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubAgents.d.ts +8 -0
- package/esm/mcp-server/tools/pipeshubAgents.d.ts.map +1 -0
- package/esm/mcp-server/tools/pipeshubAgents.js +62 -0
- package/esm/mcp-server/tools/pipeshubAgents.js.map +1 -0
- package/esm/mcp-server/tools/pipeshubChat.d.ts +6 -1
- package/esm/mcp-server/tools/pipeshubChat.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubChat.js +88 -26
- package/esm/mcp-server/tools/pipeshubChat.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubDirectory.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubDirectory.js +0 -1
- package/esm/mcp-server/tools/pipeshubDirectory.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubDownloadRecord.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubDownloadRecord.js +0 -3
- package/esm/mcp-server/tools/pipeshubDownloadRecord.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubSearch.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubSearch.js +0 -3
- package/esm/mcp-server/tools/pipeshubSearch.js.map +1 -1
- package/esm/mcp-server/tools/pipeshubSources.d.ts.map +1 -1
- package/esm/mcp-server/tools/pipeshubSources.js +0 -4
- package/esm/mcp-server/tools/pipeshubSources.js.map +1 -1
- package/esm/models/agentops.d.ts +119 -0
- package/esm/models/agentops.d.ts.map +1 -0
- package/esm/models/agentops.js +97 -0
- package/esm/models/agentops.js.map +1 -0
- package/esm/tool-names.d.ts.map +1 -1
- package/esm/tool-names.js +5 -1
- package/esm/tool-names.js.map +1 -1
- package/package.json +2 -2
- package/src/funcs/agentsListAgents.ts +141 -0
- package/src/funcs/agentsStreamConversation.ts +144 -0
- package/src/funcs/agentsStreamMessage.ts +147 -0
- package/src/funcs/conversationsStreamConversation.ts +1 -3
- package/src/funcs/conversationsStreamMessage.ts +0 -8
- package/src/mcp-server/instructions.ts +83 -0
- package/src/mcp-server/prompts/pipeshubAssistant.ts +37 -0
- package/src/mcp-server/server.ts +8 -62
- package/src/mcp-server/tools/_helpers.ts +216 -1
- package/src/mcp-server/tools/pipeshubAgents.ts +68 -0
- package/src/mcp-server/tools/pipeshubChat.ts +87 -26
- package/src/mcp-server/tools/pipeshubDirectory.ts +0 -1
- package/src/mcp-server/tools/pipeshubDownloadRecord.ts +0 -4
- package/src/mcp-server/tools/pipeshubSearch.ts +0 -3
- package/src/mcp-server/tools/pipeshubSources.ts +0 -5
- package/src/models/agentops.ts +160 -0
- 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
|
-
|
|
72
|
-
"
|
|
73
|
-
+ "
|
|
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
|
-
`**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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\` (
|
|
90
|
-
|
|
91
|
-
- "
|
|
92
|
-
- "What's
|
|
93
|
-
- "What
|
|
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.
|
|
126
|
-
//
|
|
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,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>;
|