@indemn/cli 1.5.0 → 1.7.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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +9 -4
- package/.github/workflows/ci.yml +10 -0
- package/.github/workflows/publish.yml +2 -2
- package/.mcp.json +1 -4
- package/CONTRIBUTING.md +3 -1
- package/README.md +69 -176
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +226 -18
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/analytics.d.ts +3 -0
- package/dist/cli/analytics.d.ts.map +1 -0
- package/dist/cli/analytics.js +133 -0
- package/dist/cli/analytics.js.map +1 -0
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +62 -17
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/billing.d.ts +3 -0
- package/dist/cli/billing.d.ts.map +1 -0
- package/dist/cli/billing.js +23 -0
- package/dist/cli/billing.js.map +1 -0
- package/dist/cli/campaigns.d.ts +3 -0
- package/dist/cli/campaigns.d.ts.map +1 -0
- package/dist/cli/campaigns.js +34 -0
- package/dist/cli/campaigns.js.map +1 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +69 -102
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +71 -27
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/conversations.d.ts +3 -0
- package/dist/cli/conversations.d.ts.map +1 -0
- package/dist/cli/conversations.js +129 -0
- package/dist/cli/conversations.js.map +1 -0
- package/dist/cli/distribution.d.ts +3 -0
- package/dist/cli/distribution.d.ts.map +1 -0
- package/dist/cli/distribution.js +122 -0
- package/dist/cli/distribution.js.map +1 -0
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +315 -71
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/functions.d.ts.map +1 -1
- package/dist/cli/functions.js +125 -35
- package/dist/cli/functions.js.map +1 -1
- package/dist/cli/index.js +63 -54
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +27 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +152 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/kb.d.ts.map +1 -1
- package/dist/cli/kb.js +141 -15
- package/dist/cli/kb.js.map +1 -1
- package/dist/cli/link.d.ts +21 -0
- package/dist/cli/link.d.ts.map +1 -0
- package/dist/cli/link.js +112 -0
- package/dist/cli/link.js.map +1 -0
- package/dist/cli/org.d.ts.map +1 -1
- package/dist/cli/org.js +79 -67
- package/dist/cli/org.js.map +1 -1
- package/dist/cli/platform.d.ts +3 -0
- package/dist/cli/platform.d.ts.map +1 -0
- package/dist/cli/platform.js +133 -0
- package/dist/cli/platform.js.map +1 -0
- package/dist/cli/tickets.d.ts +3 -0
- package/dist/cli/tickets.d.ts.map +1 -0
- package/dist/cli/tickets.js +125 -0
- package/dist/cli/tickets.js.map +1 -0
- package/dist/cli/utils/output.d.ts +15 -9
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +53 -46
- package/dist/cli/utils/output.js.map +1 -1
- package/dist/cli/voice.d.ts +3 -0
- package/dist/cli/voice.d.ts.map +1 -0
- package/dist/cli/voice.js +206 -0
- package/dist/cli/voice.js.map +1 -0
- package/dist/core/diff.d.ts +16 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +57 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/org-resolver.d.ts +37 -0
- package/dist/core/org-resolver.d.ts.map +1 -0
- package/dist/core/org-resolver.js +73 -0
- package/dist/core/org-resolver.js.map +1 -0
- package/dist/core/runtime.d.ts +36 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +62 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/scope.d.ts +33 -0
- package/dist/core/scope.d.ts.map +1 -0
- package/dist/core/scope.js +37 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/mcp/registry.d.ts +67 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +195 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +87 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-meta.d.ts +21 -0
- package/dist/mcp/tool-meta.d.ts.map +1 -0
- package/dist/mcp/tool-meta.js +203 -0
- package/dist/mcp/tool-meta.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1462 -215
- package/dist/mcp/tools.js.map +1 -1
- package/dist/sdk/agent-bundle.d.ts +99 -0
- package/dist/sdk/agent-bundle.d.ts.map +1 -0
- package/dist/sdk/agent-bundle.js +211 -0
- package/dist/sdk/agent-bundle.js.map +1 -0
- package/dist/sdk/agents.d.ts +54 -1
- package/dist/sdk/agents.d.ts.map +1 -1
- package/dist/sdk/agents.js +59 -5
- package/dist/sdk/agents.js.map +1 -1
- package/dist/sdk/analytics.d.ts +158 -0
- package/dist/sdk/analytics.d.ts.map +1 -0
- package/dist/sdk/analytics.js +185 -0
- package/dist/sdk/analytics.js.map +1 -0
- package/dist/sdk/auth.d.ts +34 -12
- package/dist/sdk/auth.d.ts.map +1 -1
- package/dist/sdk/auth.js +60 -77
- package/dist/sdk/auth.js.map +1 -1
- package/dist/sdk/billing.d.ts +25 -0
- package/dist/sdk/billing.d.ts.map +1 -0
- package/dist/sdk/billing.js +61 -0
- package/dist/sdk/billing.js.map +1 -0
- package/dist/sdk/campaigns.d.ts +35 -0
- package/dist/sdk/campaigns.d.ts.map +1 -0
- package/dist/sdk/campaigns.js +59 -0
- package/dist/sdk/campaigns.js.map +1 -0
- package/dist/sdk/chat.d.ts +25 -2
- package/dist/sdk/chat.d.ts.map +1 -1
- package/dist/sdk/chat.js +37 -10
- package/dist/sdk/chat.js.map +1 -1
- package/dist/sdk/client.d.ts +93 -5
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +362 -100
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/config-change.d.ts +74 -0
- package/dist/sdk/config-change.d.ts.map +1 -0
- package/dist/sdk/config-change.js +117 -0
- package/dist/sdk/config-change.js.map +1 -0
- package/dist/sdk/config.d.ts.map +1 -1
- package/dist/sdk/config.js +2 -1
- package/dist/sdk/config.js.map +1 -1
- package/dist/sdk/conversations.d.ts +101 -0
- package/dist/sdk/conversations.d.ts.map +1 -0
- package/dist/sdk/conversations.js +155 -0
- package/dist/sdk/conversations.js.map +1 -0
- package/dist/sdk/credentials.d.ts +25 -0
- package/dist/sdk/credentials.d.ts.map +1 -0
- package/dist/sdk/credentials.js +44 -0
- package/dist/sdk/credentials.js.map +1 -0
- package/dist/sdk/distribution.d.ts +94 -0
- package/dist/sdk/distribution.d.ts.map +1 -0
- package/dist/sdk/distribution.js +100 -0
- package/dist/sdk/distribution.js.map +1 -0
- package/dist/sdk/eval.d.ts +81 -0
- package/dist/sdk/eval.d.ts.map +1 -1
- package/dist/sdk/eval.js +117 -0
- package/dist/sdk/eval.js.map +1 -1
- package/dist/sdk/functions.d.ts +14 -1
- package/dist/sdk/functions.d.ts.map +1 -1
- package/dist/sdk/functions.js +48 -5
- package/dist/sdk/functions.js.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/jarvis-chat.d.ts +28 -0
- package/dist/sdk/jarvis-chat.d.ts.map +1 -0
- package/dist/sdk/jarvis-chat.js +117 -0
- package/dist/sdk/jarvis-chat.js.map +1 -0
- package/dist/sdk/kb.d.ts +69 -1
- package/dist/sdk/kb.d.ts.map +1 -1
- package/dist/sdk/kb.js +111 -24
- package/dist/sdk/kb.js.map +1 -1
- package/dist/sdk/orgs.d.ts +13 -0
- package/dist/sdk/orgs.d.ts.map +1 -0
- package/dist/sdk/orgs.js +59 -0
- package/dist/sdk/orgs.js.map +1 -0
- package/dist/sdk/platform.d.ts +62 -0
- package/dist/sdk/platform.d.ts.map +1 -0
- package/dist/sdk/platform.js +95 -0
- package/dist/sdk/platform.js.map +1 -0
- package/dist/sdk/session.d.ts +25 -0
- package/dist/sdk/session.d.ts.map +1 -0
- package/dist/sdk/session.js +69 -0
- package/dist/sdk/session.js.map +1 -0
- package/dist/sdk/skills.d.ts +46 -0
- package/dist/sdk/skills.d.ts.map +1 -0
- package/dist/sdk/skills.js +203 -0
- package/dist/sdk/skills.js.map +1 -0
- package/dist/sdk/test-chat.d.ts +52 -0
- package/dist/sdk/test-chat.d.ts.map +1 -0
- package/dist/sdk/test-chat.js +134 -0
- package/dist/sdk/test-chat.js.map +1 -0
- package/dist/sdk/tickets.d.ts +98 -0
- package/dist/sdk/tickets.d.ts.map +1 -0
- package/dist/sdk/tickets.js +91 -0
- package/dist/sdk/tickets.js.map +1 -0
- package/dist/sdk/types.d.ts +91 -6
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/voice.d.ts +147 -0
- package/dist/sdk/voice.d.ts.map +1 -0
- package/dist/sdk/voice.js +236 -0
- package/dist/sdk/voice.js.map +1 -0
- package/package.json +22 -3
- package/scripts/check-skills.mjs +25 -0
- package/skills/agent-builder/SKILL.md +89 -252
- package/skills/evaluations/SKILL.md +23 -234
- package/skills/evaluations/references/analysis.md +95 -0
- package/skills/evaluations/references/cli-reference.md +28 -0
- package/skills/evaluations/references/json-formats.md +100 -0
- package/src/cli/agents.ts +202 -22
- package/src/cli/analytics.ts +138 -0
- package/src/cli/auth.ts +56 -18
- package/src/cli/billing.ts +25 -0
- package/src/cli/campaigns.ts +33 -0
- package/src/cli/chat.ts +66 -115
- package/src/cli/config.ts +65 -31
- package/src/cli/conversations.ts +133 -0
- package/src/cli/distribution.ts +116 -0
- package/src/cli/eval.ts +254 -70
- package/src/cli/functions.ts +122 -36
- package/src/cli/index.ts +58 -55
- package/src/cli/init.ts +154 -0
- package/src/cli/kb.ts +120 -17
- package/src/cli/link.ts +117 -0
- package/src/cli/org.ts +81 -75
- package/src/cli/platform.ts +128 -0
- package/src/cli/tickets.ts +130 -0
- package/src/cli/utils/output.ts +51 -42
- package/src/cli/voice.ts +204 -0
- package/src/core/diff.ts +52 -0
- package/src/core/org-resolver.ts +113 -0
- package/src/core/runtime.ts +73 -0
- package/src/core/scope.ts +64 -0
- package/src/mcp/registry.ts +236 -0
- package/src/mcp/server.ts +84 -2
- package/src/mcp/tool-meta.ts +241 -0
- package/src/mcp/tools.ts +1529 -265
- package/src/sdk/agent-bundle.ts +245 -0
- package/src/sdk/agents.ts +74 -5
- package/src/sdk/analytics.ts +220 -0
- package/src/sdk/auth.ts +87 -107
- package/src/sdk/billing.ts +59 -0
- package/src/sdk/campaigns.ts +60 -0
- package/src/sdk/chat.ts +44 -10
- package/src/sdk/client.ts +373 -102
- package/src/sdk/config-change.ts +148 -0
- package/src/sdk/config.ts +2 -1
- package/src/sdk/conversations.ts +199 -0
- package/src/sdk/credentials.ts +53 -0
- package/src/sdk/distribution.ts +132 -0
- package/src/sdk/eval.ts +140 -0
- package/src/sdk/functions.ts +52 -6
- package/src/sdk/index.ts +2 -0
- package/src/sdk/jarvis-chat.ts +112 -0
- package/src/sdk/kb.ts +116 -29
- package/src/sdk/orgs.ts +62 -0
- package/src/sdk/platform.ts +115 -0
- package/src/sdk/session.ts +90 -0
- package/src/sdk/skills.ts +188 -0
- package/src/sdk/test-chat.ts +151 -0
- package/src/sdk/tickets.ts +120 -0
- package/src/sdk/types.ts +88 -6
- package/src/sdk/voice.ts +262 -0
- package/test/cli/entry.test.ts +34 -0
- package/test/cli/init.test.ts +66 -0
- package/test/cli/link.test.ts +57 -0
- package/test/core/org-resolver.test.ts +92 -0
- package/test/core/runtime.test.ts +30 -0
- package/test/core/scope.test.ts +93 -0
- package/test/harness.test.ts +24 -0
- package/test/helpers/config-home.ts +22 -0
- package/test/helpers/fake-middleware.ts +56 -0
- package/test/helpers/fetch-recorder.ts +75 -0
- package/test/helpers/jwt.ts +9 -0
- package/test/helpers/mcp-pair.ts +38 -0
- package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +145 -0
- package/test/mcp/askers.test.ts +60 -0
- package/test/mcp/elicit-timeout.test.ts +29 -0
- package/test/mcp/registry.test.ts +169 -0
- package/test/mcp/shaping.test.ts +41 -0
- package/test/mcp/tools-snapshot.test.ts +42 -0
- package/test/sdk/agents.test.ts +232 -0
- package/test/sdk/analytics.test.ts +89 -0
- package/test/sdk/auth.test.ts +70 -0
- package/test/sdk/billing.test.ts +47 -0
- package/test/sdk/campaigns.test.ts +43 -0
- package/test/sdk/client.test.ts +80 -0
- package/test/sdk/config-change.test.ts +142 -0
- package/test/sdk/conversations.test.ts +80 -0
- package/test/sdk/credentials.test.ts +33 -0
- package/test/sdk/distribution.test.ts +55 -0
- package/test/sdk/eval.test.ts +155 -0
- package/test/sdk/kb-upload.test.ts +50 -0
- package/test/sdk/kb.test.ts +95 -0
- package/test/sdk/orgs.test.ts +48 -0
- package/test/sdk/platform.test.ts +89 -0
- package/test/sdk/renewal.test.ts +115 -0
- package/test/sdk/session.test.ts +60 -0
- package/test/sdk/skills.test.ts +235 -0
- package/test/sdk/test-chat.test.ts +112 -0
- package/test/sdk/tickets.test.ts +42 -0
- package/test/sdk/voice.test.ts +147 -0
- package/vitest.config.ts +11 -0
package/dist/mcp/tools.js
CHANGED
|
@@ -1,66 +1,327 @@
|
|
|
1
1
|
import { z } from 'zod/v3';
|
|
2
|
-
import { AgentsSDK } from '../sdk/agents.js';
|
|
2
|
+
import { AgentInsightsSDK, AgentsSDK } from '../sdk/agents.js';
|
|
3
|
+
import { createAgentFromSkeleton, exportAgent, importAgent, missingSkeletonFields, parseModel, } from '../sdk/agent-bundle.js';
|
|
4
|
+
import { getAskers } from '../core/scope.js';
|
|
5
|
+
import { proposedSkills, skillConfiguration } from '../sdk/skills.js';
|
|
6
|
+
import { VoiceSDK } from '../sdk/voice.js';
|
|
7
|
+
import { DistributionSDK } from '../sdk/distribution.js';
|
|
8
|
+
import { AnalyticsSDK, AuditSDK, OrgSettingsSDK } from '../sdk/analytics.js';
|
|
9
|
+
import { TicketsSDK } from '../sdk/tickets.js';
|
|
10
|
+
import { BillingSDK } from '../sdk/billing.js';
|
|
11
|
+
import { CampaignsSDK } from '../sdk/campaigns.js';
|
|
12
|
+
import { PlatformSDK } from '../sdk/platform.js';
|
|
13
|
+
import { jarvisChat } from '../sdk/jarvis-chat.js';
|
|
14
|
+
import { applyConfigChange, describePlan, liveText, mayBeLive, modelArg, planConfigChange } from '../sdk/config-change.js';
|
|
3
15
|
import { ConfigSDK } from '../sdk/config.js';
|
|
4
16
|
import { FunctionsSDK } from '../sdk/functions.js';
|
|
5
17
|
import { KBSDK } from '../sdk/kb.js';
|
|
6
18
|
import { RubricsSDK } from '../sdk/rubrics.js';
|
|
7
19
|
import { TestSetsSDK } from '../sdk/test-sets.js';
|
|
8
|
-
import { EvalSDK } from '../sdk/eval.js';
|
|
9
|
-
import {
|
|
20
|
+
import { EvalSDK, gateVerdict } from '../sdk/eval.js';
|
|
21
|
+
import { exchange, openTestChat } from '../sdk/test-chat.js';
|
|
22
|
+
import { ConversationsSDK, conversationToTestSet, conversationUrl, isCliConversation, isTestConversation, projectOfAgent } from '../sdk/conversations.js';
|
|
10
23
|
import { exportEvalMarkdown, exportEvalReport, exportAgentCard } from '../report/export.js';
|
|
24
|
+
import { createRegistry } from './registry.js';
|
|
25
|
+
import { OrgsSDK } from '../sdk/orgs.js';
|
|
26
|
+
import { whoami } from '../sdk/auth.js';
|
|
27
|
+
import { NeedsInputError, SessionExpiredError } from '../core/runtime.js';
|
|
28
|
+
// A REST skill's authentication as flat fields: for a nested object Gemini writes calls Google rejects.
|
|
29
|
+
const skillAuthFields = {
|
|
30
|
+
auth_type: z.enum(['no-auth', 'api-key', 'basic-auth', 'bearer-token']).optional().describe('rest_api: how the API is called (default no-auth)'),
|
|
31
|
+
auth_token: z.string().optional().describe('bearer-token: the token'),
|
|
32
|
+
auth_key: z.string().optional().describe('api-key: the header name, e.g. x-api-key'),
|
|
33
|
+
auth_value: z.string().optional().describe('api-key: the key'),
|
|
34
|
+
auth_username: z.string().optional().describe('basic-auth: user name'),
|
|
35
|
+
auth_password: z.string().optional().describe('basic-auth: password'),
|
|
36
|
+
};
|
|
37
|
+
// Helper: the flat auth fields as the dashboard's skill form saves them (configurationSchema.authorization)
|
|
38
|
+
function withAuthorization(args) {
|
|
39
|
+
if (!args.auth_type)
|
|
40
|
+
return args;
|
|
41
|
+
const fields = { 'api-key': ['key', 'value'], 'basic-auth': ['username', 'password'], 'bearer-token': ['token'], 'no-auth': [] };
|
|
42
|
+
const authorization = { type: args.auth_type };
|
|
43
|
+
for (const k of fields[args.auth_type] ?? []) {
|
|
44
|
+
const v = args[`auth_${k}`];
|
|
45
|
+
if (typeof v === 'string' && v)
|
|
46
|
+
authorization[k] = v;
|
|
47
|
+
}
|
|
48
|
+
return { ...args, configuration: { ...(args.configuration ?? {}), authorization } };
|
|
49
|
+
}
|
|
11
50
|
// Helper: format a successful MCP tool result
|
|
12
51
|
function success(data) {
|
|
13
52
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
14
53
|
}
|
|
15
54
|
// Helper: format an error MCP tool result
|
|
16
55
|
function error(err) {
|
|
56
|
+
if (err instanceof NeedsInputError) {
|
|
57
|
+
return { content: [{ type: 'text', text: JSON.stringify({ needs_input: true, message: err.message, choices: err.choices, to_proceed: 'Ask the user which organization to use, then call again with org_id set to its id.' }, null, 2) }] };
|
|
58
|
+
}
|
|
17
59
|
const message = err instanceof Error ? err.message : String(err);
|
|
18
60
|
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
|
|
19
61
|
}
|
|
62
|
+
// Helper: the org's pages in the dashboard, so the person gets a link instead of an id
|
|
63
|
+
async function orgPages(client) {
|
|
64
|
+
const base = client.getHosts().copilot.replace(/\/api\/?$/, '');
|
|
65
|
+
return `${base}/#/organization/${await client.currentOrgId()}`;
|
|
66
|
+
}
|
|
67
|
+
// Test items and rubric rules as the evaluations service defines them (indemn_evals models).
|
|
68
|
+
// Typed fields, not open records: models such as Gemini cannot fill an object with no
|
|
69
|
+
// declared fields. Other fields the service accepts still pass through.
|
|
70
|
+
// Test items and rubric rules with flat fields (a nested object makes Gemini write calls Google rejects);
|
|
71
|
+
// mapped to the evaluations service's shape (inputs / expected, evaluation_criteria) before saving.
|
|
72
|
+
const testItemSchema = z.object({
|
|
73
|
+
type: z.enum(['single_turn', 'scenario', 'voice_simulation', 'transcript']).describe('single_turn: one message; scenario: a simulated chat; voice_simulation: a simulated phone call'),
|
|
74
|
+
name: z.string().describe('Short name of what is tested'),
|
|
75
|
+
message: z.string().optional().describe('single_turn: the message sent to the agent'),
|
|
76
|
+
persona: z.string().optional().describe('scenario / voice_simulation: who the simulated customer is'),
|
|
77
|
+
initial_message: z.string().optional().describe('scenario / voice_simulation: their first message'),
|
|
78
|
+
goal: z.string().optional().describe('voice_simulation: what the caller wants'),
|
|
79
|
+
max_turns: z.number().int().optional().describe('scenario / voice_simulation: turns before stopping (default 5)'),
|
|
80
|
+
success_criteria: z.array(z.string()).optional().describe('Plain-language checks that must all pass (needed)'),
|
|
81
|
+
expected_outcome: z.string().optional(),
|
|
82
|
+
tool_present: z.array(z.string()).optional().describe('Skills that must be used'),
|
|
83
|
+
tool_absent: z.array(z.string()).optional().describe('Skills that must not be used'),
|
|
84
|
+
tags: z.array(z.string()).optional(),
|
|
85
|
+
priority: z.string().optional(),
|
|
86
|
+
}).passthrough();
|
|
87
|
+
const rubricRuleSchema = z.object({
|
|
88
|
+
id: z.string().describe('Short rule id, e.g. STAYS_ON_TOPIC'),
|
|
89
|
+
name: z.string(),
|
|
90
|
+
severity: z.string().describe('high, medium or low'),
|
|
91
|
+
category: z.string().describe('e.g. response_quality, accuracy, safety'),
|
|
92
|
+
description: z.string(),
|
|
93
|
+
pass_conditions: z.array(z.string()).optional().describe('What passing looks like'),
|
|
94
|
+
fail_conditions: z.array(z.string()).optional().describe('What failing looks like'),
|
|
95
|
+
}).passthrough();
|
|
96
|
+
const defined = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
|
|
97
|
+
// Helper: a flat test item in the service's shape; inputs / expected given as objects (other clients) are kept.
|
|
98
|
+
function toTestItem(item) {
|
|
99
|
+
const { message, persona, initial_message, goal, max_turns, success_criteria, expected_outcome, tool_present, tool_absent, inputs, expected, ...rest } = item;
|
|
100
|
+
return {
|
|
101
|
+
...rest,
|
|
102
|
+
inputs: { ...(inputs ?? {}), ...defined({ message, persona, initial_message, goal, max_turns }) },
|
|
103
|
+
expected: { ...(expected ?? {}), ...defined({ success_criteria, expected_outcome, tool_present, tool_absent }) },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Helper: a flat rubric rule in the service's shape (evaluation_criteria).
|
|
107
|
+
function toRubricRule(rule) {
|
|
108
|
+
const { pass_conditions, fail_conditions, evaluation_criteria, ...rest } = rule;
|
|
109
|
+
const criteria = { ...(evaluation_criteria ?? {}), ...defined({ pass_conditions, fail_conditions }) };
|
|
110
|
+
return Object.keys(criteria).length ? { ...rest, evaluation_criteria: criteria } : rest;
|
|
111
|
+
}
|
|
112
|
+
// Objects whose fields a model cannot see (Gemini) arrive as JSON text: accept both.
|
|
113
|
+
const parseJsonText = (v) => {
|
|
114
|
+
if (typeof v !== 'string')
|
|
115
|
+
return v;
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(v);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return v;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const jsonObject = () => z.preprocess(parseJsonText, z.record(z.any()));
|
|
124
|
+
// A skill for bulk_create_skills with flat fields (the configuration is built as create_function builds it);
|
|
125
|
+
// a proposal from generate_skills_from_doc keeps its own configurationSchema.
|
|
126
|
+
const skillDefinition = z.preprocess(parseJsonText, z.object({
|
|
127
|
+
name: z.string(),
|
|
128
|
+
description: z.string().optional(),
|
|
129
|
+
type: z.string().describe('e.g. rest_api, transfer_call'),
|
|
130
|
+
url: z.string().optional().describe('rest_api: endpoint URL; {{parameter}} placeholders allowed'),
|
|
131
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).optional().describe('rest_api'),
|
|
132
|
+
payload: z.string().optional().describe('rest_api: request body as JSON text with {{parameter}} placeholders'),
|
|
133
|
+
...skillAuthFields,
|
|
134
|
+
response_field: z.string().optional().describe('rest_api: dot-path of the answer to use'),
|
|
135
|
+
output_instructions: z.string().optional().describe('What the agent says with the answer'),
|
|
136
|
+
transfer_to: z.string().optional().describe('transfer_call: phone number (E.164) or SIP address'),
|
|
137
|
+
parameters: z.array(z.object({
|
|
138
|
+
name: z.string(), type: z.string().describe('string, number, boolean or list'),
|
|
139
|
+
description: z.string().optional(), required: z.boolean().optional(),
|
|
140
|
+
}).passthrough()).optional(),
|
|
141
|
+
}).passthrough());
|
|
20
142
|
export function registerTools(server, client) {
|
|
143
|
+
const reg = createRegistry(server, {
|
|
144
|
+
context: () => client.getResultContext(),
|
|
145
|
+
// Each call decides its own org; an answer given once is not reused for the next call.
|
|
146
|
+
resolveOrg: (explicit) => client.resolveOrg(explicit, { cache: false }),
|
|
147
|
+
// The person approves a change by names: look up the ids the call names.
|
|
148
|
+
// An id that names nothing gets no card: the registry answers "not found" instead.
|
|
149
|
+
displayNames: async (args, orgId) => {
|
|
150
|
+
const names = {};
|
|
151
|
+
const missing = [];
|
|
152
|
+
const notFound = /not found|is not on agent|404/i;
|
|
153
|
+
const add = (id, what, lookup) => typeof id === 'string' && id
|
|
154
|
+
? lookup(id).then((x) => {
|
|
155
|
+
if (typeof x?.name === 'string' && x.name)
|
|
156
|
+
names[id] = x.name;
|
|
157
|
+
else if (what && !x)
|
|
158
|
+
missing.push(`${what} ${id}`);
|
|
159
|
+
}, (err) => { if (what && notFound.test(err instanceof Error ? err.message : String(err)))
|
|
160
|
+
missing.push(`${what} ${id}`); })
|
|
161
|
+
: undefined;
|
|
162
|
+
await Promise.all([
|
|
163
|
+
add(args.agent_id ?? args.bot_id, 'agent', (id) => agents.getAgent(id)),
|
|
164
|
+
typeof args.agent_id === 'string' ? add(args.function_id, 'skill', (id) => skillOf(String(args.agent_id), id)) : undefined,
|
|
165
|
+
add(args.kb_id, 'knowledge base', (id) => kb.getKB(id)),
|
|
166
|
+
add(args.test_set_id, 'test set', (id) => testSets.getTestSet(id)),
|
|
167
|
+
add(args.rubric_id, 'rubric', (id) => rubrics.getRubric(id)),
|
|
168
|
+
add(orgId, null, (id) => client.copilotGet(`/organization/${id}`).then((r) => r.organization)),
|
|
169
|
+
]);
|
|
170
|
+
return { names, missing };
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
const orgs = new OrgsSDK(client);
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Login and organization tools
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
reg.tool('whoami', 'Show the platform (dev or production), the logged-in user, the session expiry, the orgs you belong to, and whether this folder is linked to one org. Call this first when unsure which org to use.', {}, async () => {
|
|
178
|
+
try {
|
|
179
|
+
const info = whoami();
|
|
180
|
+
if (!info)
|
|
181
|
+
throw new SessionExpiredError('Not logged in. Run `indemn login` in a terminal.');
|
|
182
|
+
const link = client.getLink();
|
|
183
|
+
return success({ ...info, linked_folder: link ? { org_id: link.org_id, org_name: link.org_name, agents: link.agents, path: link.path } : null });
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
return error(err);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
reg.tool('list_organizations', 'List the organizations you belong to on this platform. Pass one of these ids as org_id to other tools.', {}, async () => {
|
|
190
|
+
try {
|
|
191
|
+
return success(await orgs.listOrganizations());
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return error(err);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
reg.tool('list_org_members', 'List the owners, admins and agents of an organization, with their roles.', {}, async () => {
|
|
198
|
+
try {
|
|
199
|
+
return success(await orgs.listMembers());
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
return error(err);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
reg.tool('list_channels', 'List the channels an agent can be published on, such as WEB or VOICE.', {}, async () => {
|
|
206
|
+
try {
|
|
207
|
+
return success(await orgs.listChannels());
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
return error(err);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
21
213
|
const agents = new AgentsSDK(client);
|
|
22
214
|
const config = new ConfigSDK(client);
|
|
23
215
|
const functions = new FunctionsSDK(client);
|
|
216
|
+
// A skill of an agent by its id.
|
|
217
|
+
const skillOf = async (agentId, id) => {
|
|
218
|
+
const skill = (await functions.listFunctions(agentId)).find((f) => f._id === id);
|
|
219
|
+
if (!skill)
|
|
220
|
+
throw new Error(`Skill ${id} is not on agent ${agentId}.`);
|
|
221
|
+
return skill;
|
|
222
|
+
};
|
|
24
223
|
const kb = new KBSDK(client);
|
|
25
224
|
const rubrics = new RubricsSDK(client);
|
|
26
225
|
const testSets = new TestSetsSDK(client);
|
|
27
226
|
const evals = new EvalSDK(client);
|
|
227
|
+
// The evaluations service checks nothing about orgs: touch a rubric or test set only when its agent is in this org.
|
|
228
|
+
const rubricInOrg = async (id) => {
|
|
229
|
+
const r = await rubrics.getRubric(id);
|
|
230
|
+
if (r.agent_id)
|
|
231
|
+
await evals.assertAgentInOrg(String(r.agent_id));
|
|
232
|
+
return r;
|
|
233
|
+
};
|
|
234
|
+
const testSetInOrg = async (id) => {
|
|
235
|
+
const t = await testSets.getTestSet(id);
|
|
236
|
+
if (t.agent_id)
|
|
237
|
+
await evals.assertAgentInOrg(String(t.agent_id));
|
|
238
|
+
return t;
|
|
239
|
+
};
|
|
28
240
|
// ---------------------------------------------------------------------------
|
|
29
241
|
// Agent tools
|
|
30
242
|
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
243
|
+
reg.tool('list_agents', 'List the agents (bots) of the organization: id, name, channels. Start here to find an agent id.', { channel: z.string().optional().describe('Only agents on this channel, e.g. web, voice, email') }, async (args) => {
|
|
32
244
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
245
|
+
const pages = await orgPages(client);
|
|
246
|
+
const wanted = args.channel?.trim().toUpperCase();
|
|
247
|
+
// What a person needs, not the stored record (a page of full records ran to 73k characters).
|
|
248
|
+
const rows = (await agents.listAgents()).map((a) => ({
|
|
249
|
+
_id: a._id,
|
|
250
|
+
name: a.name,
|
|
251
|
+
...(a.description ? { description: a.description } : {}),
|
|
252
|
+
// The list returns channel records, not the names the type declares.
|
|
253
|
+
channels: (a.channels ?? []).map((c) => (typeof c === 'string' ? c : c?.name ?? '')).filter(Boolean),
|
|
254
|
+
...(a.connected_phone ? { phone: a.connected_phone } : {}),
|
|
255
|
+
updated_at: a.updatedAt,
|
|
256
|
+
url: `${pages}/ai-studio/bot/${a._id}/configurations`,
|
|
257
|
+
}));
|
|
258
|
+
return success(wanted ? rows.filter((a) => a.channels.some((c) => c.toUpperCase() === wanted)) : rows);
|
|
37
259
|
}
|
|
38
260
|
catch (err) {
|
|
39
261
|
return error(err);
|
|
40
262
|
}
|
|
41
263
|
});
|
|
42
|
-
|
|
264
|
+
reg.tool('get_agent', 'Get one agent: name, description, channels and project. For what it does, use agent_summary; for its prompt and model, get_config.', { agent_id: z.string().describe('The agent ID') }, async (args) => {
|
|
43
265
|
try {
|
|
44
266
|
const result = await agents.getAgent(args.agent_id);
|
|
45
|
-
return success(result);
|
|
267
|
+
return success({ ...result, url: `${await orgPages(client)}/ai-studio/bot/${args.agent_id}/configurations` });
|
|
46
268
|
}
|
|
47
269
|
catch (err) {
|
|
48
270
|
return error(err);
|
|
49
271
|
}
|
|
50
272
|
});
|
|
51
|
-
|
|
52
|
-
name: z.string().describe('Agent name'),
|
|
53
|
-
channels: z.array(z.string()).optional().describe('Channels
|
|
273
|
+
reg.tool('create_agent', 'Create an agent with its basics: name, channels, what it is for, system prompt and model. Missing basics are asked from the user when the host can ask; otherwise the result lists them (pass minimal: true to create with only a name). Refuses a name that already exists in the org.', {
|
|
274
|
+
name: z.string().optional().describe('Agent name'),
|
|
275
|
+
channels: z.array(z.string()).optional().describe('Channels, e.g. ["WEB"] (see list_channels)'),
|
|
276
|
+
description: z.string().optional().describe('What the agent is for'),
|
|
277
|
+
system_prompt: z.string().optional().describe('System prompt'),
|
|
278
|
+
model: z.string().optional().describe('provider/model, e.g. openai/gpt-4.1 (see list_llm_models)'),
|
|
279
|
+
allow_duplicate: z.boolean().optional().describe('Create even if an agent with this name exists'),
|
|
280
|
+
minimal: z.boolean().optional().describe('Create with only the fields given, without asking for the rest'),
|
|
54
281
|
}, async (args) => {
|
|
55
282
|
try {
|
|
56
|
-
const
|
|
57
|
-
|
|
283
|
+
const skeleton = {
|
|
284
|
+
name: args.name ?? '',
|
|
285
|
+
channels: args.channels,
|
|
286
|
+
description: args.description,
|
|
287
|
+
system_prompt: args.system_prompt,
|
|
288
|
+
model: args.model ? parseModel(args.model) : undefined,
|
|
289
|
+
};
|
|
290
|
+
let missing = missingSkeletonFields(skeleton);
|
|
291
|
+
if (missing.length && !args.minimal) {
|
|
292
|
+
const titles = { name: 'Agent name', description: 'What is this agent for?', system_prompt: 'System prompt' };
|
|
293
|
+
const answers = await getAskers().form?.('Fill in the basics for the new agent.', missing.map((key) => ({ key, title: titles[key] ?? key, required: key === 'name' })));
|
|
294
|
+
if (answers) {
|
|
295
|
+
if (answers.name)
|
|
296
|
+
skeleton.name = answers.name;
|
|
297
|
+
if (answers.description)
|
|
298
|
+
skeleton.description = answers.description;
|
|
299
|
+
if (answers.system_prompt)
|
|
300
|
+
skeleton.system_prompt = answers.system_prompt;
|
|
301
|
+
}
|
|
302
|
+
missing = missingSkeletonFields(skeleton);
|
|
303
|
+
if (missing.includes('name') || (missing.length && !answers)) {
|
|
304
|
+
throw new NeedsInputError(`Before creating the agent, provide: ${missing.join(', ')}. Or pass minimal: true to create it with only a name.`, missing.map((m) => ({ id: m, label: titles[m] ?? m })));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const result = await createAgentFromSkeleton(client, skeleton, { allowDuplicate: args.allow_duplicate });
|
|
308
|
+
return success({ agent: result.agent, set: result.set, not_set: missingSkeletonFields(skeleton) });
|
|
58
309
|
}
|
|
59
310
|
catch (err) {
|
|
60
311
|
return error(err);
|
|
61
312
|
}
|
|
62
313
|
});
|
|
63
|
-
|
|
314
|
+
// Lifecycle lives on the V2 platform; "unknown" when it cannot be read (treated as published: mayBeLive).
|
|
315
|
+
const lifecycleOf = (id) => new AgentInsightsSDK(client).detail(id).then((d) => d.lifecycle ?? 'prototype', () => 'unknown');
|
|
316
|
+
// A change to an agent that may be live asks first; the approval names the fields, never their values (skill auth).
|
|
317
|
+
const liveChange = (what, skip) => ({
|
|
318
|
+
guardWhen: async (a) => mayBeLive(await lifecycleOf(String(a.agent_id))),
|
|
319
|
+
preview: async (a) => {
|
|
320
|
+
const fields = Object.keys(a).filter((k) => a[k] !== undefined && !['agent_id', 'confirm', 'org_id', ...skip].includes(k));
|
|
321
|
+
return `This will change ${what} of agent ${a.agent_id}: ${fields.join(', ') || 'nothing'}. The agent ${liveText(await lifecycleOf(String(a.agent_id)))}.`;
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
reg.tool('update_agent', 'Change an agent\'s name, description or webhook. For the prompt, greeting or model use set_config.', {
|
|
64
325
|
agent_id: z.string().describe('The agent ID'),
|
|
65
326
|
name: z.string().optional().describe('New agent name'),
|
|
66
327
|
description: z.string().optional().describe('New agent description'),
|
|
@@ -81,20 +342,98 @@ export function registerTools(server, client) {
|
|
|
81
342
|
catch (err) {
|
|
82
343
|
return error(err);
|
|
83
344
|
}
|
|
84
|
-
});
|
|
85
|
-
|
|
345
|
+
}, liveChange('the name, description or webhook', []));
|
|
346
|
+
reg.tool('delete_agent', 'Delete an agent. It is removed from the dashboard and its knowledge base links are dropped.', {
|
|
347
|
+
agent_id: z.string().describe('The agent ID to delete'),
|
|
348
|
+
confirm_published: z.boolean().optional().describe('Required with confirm when the agent is published and the user explicitly agreed'),
|
|
349
|
+
}, async (args, meta) => {
|
|
86
350
|
try {
|
|
351
|
+
const agent = await agents.getAgent(args.agent_id);
|
|
352
|
+
const lifecycle = await lifecycleOf(args.agent_id);
|
|
353
|
+
if (mayBeLive(lifecycle) && !meta.confirmedByPerson && args.confirm_published !== true) {
|
|
354
|
+
throw new Error(`"${agent.name}" ${liveText(lifecycle)}. Show the user; only if they explicitly agree, call again with confirm: true and confirm_published: true.`);
|
|
355
|
+
}
|
|
87
356
|
await agents.deleteAgent(args.agent_id);
|
|
88
|
-
return success({ deleted: true, agent_id: args.agent_id });
|
|
357
|
+
return success({ deleted: true, agent_id: args.agent_id, name: agent.name });
|
|
89
358
|
}
|
|
90
359
|
catch (err) {
|
|
91
360
|
return error(err);
|
|
92
361
|
}
|
|
362
|
+
}, {
|
|
363
|
+
preview: async (a) => {
|
|
364
|
+
const agent = await agents.getAgent(String(a.agent_id));
|
|
365
|
+
const lifecycle = await lifecycleOf(String(a.agent_id));
|
|
366
|
+
return `This will delete agent "${agent.name}"${mayBeLive(lifecycle) ? `, which ${liveText(lifecycle)}` : ''}. It cannot be restored from the CLI.`;
|
|
367
|
+
},
|
|
93
368
|
});
|
|
94
|
-
|
|
369
|
+
const insights = new AgentInsightsSDK(client);
|
|
370
|
+
reg.tool('agent_summary', 'What an agent does: one paragraph (default) or its skills and knowledge sources (full: true). Read this before changing an agent.', { agent_id: z.string().describe('The agent ID'), full: z.boolean().optional().describe('List skills and knowledge sources') }, async (args) => {
|
|
95
371
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
372
|
+
return success(await insights.summary(args.agent_id, { text: !args.full }));
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
return error(err);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
reg.tool('agent_stats', 'Conversation metrics for one agent (test conversations excluded). Dates are YYYY-MM-DD; default is the last 30 days.', {
|
|
379
|
+
agent_id: z.string().describe('The agent ID'),
|
|
380
|
+
date_from: z.string().optional().describe('YYYY-MM-DD'),
|
|
381
|
+
date_to: z.string().optional().describe('YYYY-MM-DD'),
|
|
382
|
+
channel: z.enum(['web', 'voice', 'all']).optional(),
|
|
383
|
+
}, async (args) => {
|
|
384
|
+
try {
|
|
385
|
+
return success(await insights.stats(args.agent_id, { from: args.date_from, to: args.date_to, channel: args.channel }));
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
return error(err);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
reg.tool('set_agent_lifecycle', 'Move an agent between prototype, beta and published. Only the owner or an org admin may; the V2 platform enforces it.', { agent_id: z.string().describe('The agent ID'), lifecycle: z.enum(['prototype', 'beta', 'published']) }, async (args) => {
|
|
392
|
+
try {
|
|
393
|
+
return success(await insights.setLifecycle(args.agent_id, args.lifecycle));
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
return error(err);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
reg.tool('transfer_agent_owner', 'Make another user the owner of an agent. Only the current owner or an org admin may; the V2 platform enforces it.', { agent_id: z.string().describe('The agent ID'), owner_id: z.string().describe('User id of the new owner (see list_org_members)') }, async (args) => {
|
|
400
|
+
try {
|
|
401
|
+
return success(await insights.transferOwner(args.agent_id, args.owner_id));
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
return error(err);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
reg.tool('clone_agent', 'Copy an agent of this org into a new agent in the same org.', {
|
|
408
|
+
agent_id: z.string().describe('The agent ID to copy'),
|
|
409
|
+
name: z.string().optional().describe('Name of the copy'),
|
|
410
|
+
channels: z.array(z.string()).optional().describe('Channels of the copy'),
|
|
411
|
+
}, async (args) => {
|
|
412
|
+
try {
|
|
413
|
+
return success(await agents.cloneAgent(args.agent_id, { name: args.name, channels: args.channels }));
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
return error(err);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
reg.tool('export_agent', 'Export an agent as a bundle: prompt, greeting, model, skills with parameters, linked knowledge base ids. Secrets in skill settings are redacted unless include_secrets is true.', { agent_id: z.string().describe('The agent ID'), include_secrets: z.boolean().optional() }, async (args) => {
|
|
420
|
+
try {
|
|
421
|
+
return success(await exportAgent(client, args.agent_id, { includeSecrets: args.include_secrets }));
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
return error(err);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
reg.tool('import_agent', 'Create a new agent in this org from a bundle made by export_agent (possibly from another org or platform). Lists skills that failed and secrets to fill in.', {
|
|
428
|
+
bundle: z.record(z.unknown()).describe('The bundle returned by export_agent'),
|
|
429
|
+
name: z.string().optional().describe('Name for the new agent (default: the bundle name)'),
|
|
430
|
+
link_knowledge_bases: z.boolean().optional().describe('Link the same knowledge base ids if they exist in this org'),
|
|
431
|
+
allow_duplicate: z.boolean().optional(),
|
|
432
|
+
}, async (args) => {
|
|
433
|
+
try {
|
|
434
|
+
return success(await importAgent(client, args.bundle, {
|
|
435
|
+
name: args.name, linkKnowledgeBases: args.link_knowledge_bases, allowDuplicate: args.allow_duplicate,
|
|
436
|
+
}));
|
|
98
437
|
}
|
|
99
438
|
catch (err) {
|
|
100
439
|
return error(err);
|
|
@@ -103,7 +442,7 @@ export function registerTools(server, client) {
|
|
|
103
442
|
// ---------------------------------------------------------------------------
|
|
104
443
|
// Config tools
|
|
105
444
|
// ---------------------------------------------------------------------------
|
|
106
|
-
|
|
445
|
+
reg.tool('get_config', 'An agent\'s configuration: system prompt, greeting (first message), LLM model and knowledge settings.', { agent_id: z.string().describe('The agent ID') }, async (args) => {
|
|
107
446
|
try {
|
|
108
447
|
const result = await config.getConfig(args.agent_id);
|
|
109
448
|
return success(result);
|
|
@@ -112,25 +451,37 @@ export function registerTools(server, client) {
|
|
|
112
451
|
return error(err);
|
|
113
452
|
}
|
|
114
453
|
});
|
|
115
|
-
|
|
454
|
+
const applyChange = async (agentId, change, confirmedByPerson, confirmPublished) => {
|
|
455
|
+
const plan = await planConfigChange(client, agentId, change);
|
|
456
|
+
if (plan.changes.length === 0)
|
|
457
|
+
return success({ changed: [], message: 'Nothing to change: the agent already has these values.' });
|
|
458
|
+
if (mayBeLive(plan.lifecycle) && !confirmedByPerson && confirmPublished !== true) {
|
|
459
|
+
throw new NeedsInputError(`Agent ${agentId} ${liveText(plan.lifecycle)}. Show the user the change; only if they explicitly agree, call again with confirm: true and confirm_published: true.`);
|
|
460
|
+
}
|
|
461
|
+
const result = await applyConfigChange(client, plan, change);
|
|
462
|
+
return success({ changed: result.applied, not_saved: result.mismatched, lifecycle: plan.lifecycle });
|
|
463
|
+
};
|
|
464
|
+
const previewChange = (argsToChange) => async (a) => describePlan(await planConfigChange(client, String(a.agent_id), argsToChange(a)));
|
|
465
|
+
const configArgs = (a) => ({
|
|
466
|
+
system_prompt: a.system_prompt,
|
|
467
|
+
greeting: a.first_message,
|
|
468
|
+
model: a.model ? modelArg(String(a.model)) : undefined,
|
|
469
|
+
});
|
|
470
|
+
reg.tool('set_config', 'Change an agent\'s prompt, greeting and/or model. The confirmation shows the prompt diff and whether the agent is published; published agents need confirm_published too. The result says what was saved (read back from the platform).', {
|
|
116
471
|
agent_id: z.string().describe('The agent ID'),
|
|
117
|
-
system_prompt: z.string().optional().describe('
|
|
118
|
-
first_message: z.string().optional().describe('
|
|
119
|
-
|
|
472
|
+
system_prompt: z.string().optional().describe('New system prompt (whole text)'),
|
|
473
|
+
first_message: z.string().optional().describe('New greeting shown to users'),
|
|
474
|
+
model: z.string().optional().describe('provider/model, e.g. openai/gpt-4.1 (see list_llm_models)'),
|
|
475
|
+
confirm_published: z.boolean().optional().describe('Required with confirm when the agent is published and the user explicitly agreed'),
|
|
476
|
+
}, async (args, meta) => {
|
|
120
477
|
try {
|
|
121
|
-
|
|
122
|
-
if (args.system_prompt !== undefined)
|
|
123
|
-
input.system_prompt = args.system_prompt;
|
|
124
|
-
if (args.first_message !== undefined)
|
|
125
|
-
input.first_message = { text: args.first_message };
|
|
126
|
-
const result = await config.setConfig(args.agent_id, input);
|
|
127
|
-
return success(result);
|
|
478
|
+
return await applyChange(args.agent_id, configArgs(args), meta.confirmedByPerson, args.confirm_published);
|
|
128
479
|
}
|
|
129
480
|
catch (err) {
|
|
130
481
|
return error(err);
|
|
131
482
|
}
|
|
132
|
-
});
|
|
133
|
-
|
|
483
|
+
}, { preview: previewChange(configArgs) });
|
|
484
|
+
reg.tool('get_prompt', 'An agent\'s system prompt (the instructions it follows).', { agent_id: z.string().describe('The agent ID') }, async (args) => {
|
|
134
485
|
try {
|
|
135
486
|
const result = await config.getPrompt(args.agent_id);
|
|
136
487
|
return success({ agent_id: args.agent_id, system_prompt: result ?? null });
|
|
@@ -139,22 +490,22 @@ export function registerTools(server, client) {
|
|
|
139
490
|
return error(err);
|
|
140
491
|
}
|
|
141
492
|
});
|
|
142
|
-
|
|
493
|
+
reg.tool('set_prompt', 'Replace an agent\'s system prompt. The confirmation shows the diff; published agents need confirm_published too.', {
|
|
143
494
|
agent_id: z.string().describe('The agent ID'),
|
|
144
|
-
prompt: z.string().describe('The system prompt
|
|
145
|
-
|
|
495
|
+
prompt: z.string().describe('The whole new system prompt'),
|
|
496
|
+
confirm_published: z.boolean().optional().describe('Required with confirm when the agent is published and the user explicitly agreed'),
|
|
497
|
+
}, async (args, meta) => {
|
|
146
498
|
try {
|
|
147
|
-
|
|
148
|
-
return success(result);
|
|
499
|
+
return await applyChange(args.agent_id, { system_prompt: args.prompt }, meta.confirmedByPerson, args.confirm_published);
|
|
149
500
|
}
|
|
150
501
|
catch (err) {
|
|
151
502
|
return error(err);
|
|
152
503
|
}
|
|
153
|
-
});
|
|
504
|
+
}, { preview: previewChange((a) => ({ system_prompt: String(a.prompt) })) });
|
|
154
505
|
// ---------------------------------------------------------------------------
|
|
155
506
|
// Function tools
|
|
156
507
|
// ---------------------------------------------------------------------------
|
|
157
|
-
|
|
508
|
+
reg.tool('list_functions', 'List an agent\'s skills (the actions it can take: REST calls, handoff, FAQ retriever, transfer call, ...), with their parameters.', { agent_id: z.string().describe('The agent ID') }, async (args) => {
|
|
158
509
|
try {
|
|
159
510
|
const result = await functions.listFunctions(args.agent_id);
|
|
160
511
|
return success(result);
|
|
@@ -163,66 +514,130 @@ export function registerTools(server, client) {
|
|
|
163
514
|
return error(err);
|
|
164
515
|
}
|
|
165
516
|
});
|
|
166
|
-
|
|
517
|
+
reg.tool('create_function', 'Add a skill to an agent. rest_api skills need a configurationSchema (endpoint, method, auth, payload). Names too close to the built-in FAQ retriever are refused.', {
|
|
167
518
|
agent_id: z.string().describe('The agent ID'),
|
|
168
519
|
type: z.string().describe('Function type (e.g. "rest_api")'),
|
|
169
520
|
description: z.string().optional().describe('What the function does'),
|
|
170
521
|
name: z.string().optional().describe('Function name'),
|
|
171
|
-
url: z.string().optional().describe('
|
|
172
|
-
method: z.string().optional().describe('HTTP method (GET, POST, etc.)'),
|
|
522
|
+
url: z.string().optional().describe('rest_api: endpoint URL'),
|
|
523
|
+
method: z.string().optional().describe('rest_api: HTTP method (GET, POST, etc.)'),
|
|
524
|
+
payload: z.union([z.string(), z.record(z.any())]).optional().describe('rest_api: request body (JSON); {{parameter}} placeholders are filled from the skill parameters'),
|
|
525
|
+
response_field: z.string().optional().describe('rest_api: dot-path of the response to use (e.g. data.result)'),
|
|
526
|
+
output_instructions: z.string().optional().describe('What the agent says with the answer; {{response.field}} reads the API answer'),
|
|
527
|
+
craft_response: z.boolean().optional().describe('true: the agent phrases the answer itself (default for rest_api without output_instructions); false: the answer, or output_instructions, goes to the customer as is'),
|
|
528
|
+
...skillAuthFields,
|
|
529
|
+
configuration: jsonObject().optional().describe('Other configurationSchema fields (an object, or JSON text); the fields above win'),
|
|
530
|
+
transfer_to: z.string().optional().describe('transfer_call: phone number (E.164, e.g. +15551234567) or SIP address (sip:...) calls are transferred to'),
|
|
173
531
|
}, async (args) => {
|
|
174
532
|
try {
|
|
175
|
-
const { agent_id,
|
|
176
|
-
const
|
|
177
|
-
|
|
533
|
+
const { agent_id, type, name, description } = args;
|
|
534
|
+
const configurationSchema = skillConfiguration(type, withAuthorization(args));
|
|
535
|
+
// copilot sends a REST answer straight to the customer unless craftResponse is on; with nothing to say
|
|
536
|
+
// how to present it, let the agent phrase it.
|
|
537
|
+
if (type === 'rest_api' && configurationSchema && configurationSchema.craftResponse === undefined && !configurationSchema.outputInstructions) {
|
|
538
|
+
configurationSchema.craftResponse = true;
|
|
539
|
+
}
|
|
540
|
+
if (type === 'rest_api' && !(configurationSchema?.endpoint && configurationSchema?.method)) {
|
|
541
|
+
throw new Error('A rest_api skill needs url and method.');
|
|
542
|
+
}
|
|
543
|
+
if (type === 'transfer_call' && !(configurationSchema?.phoneNumber || configurationSchema?.sipUri)) {
|
|
544
|
+
throw new Error('A transfer_call skill needs transfer_to: a phone number (E.164, e.g. +15551234567) or a SIP address (sip:...).');
|
|
545
|
+
}
|
|
546
|
+
// A retry must not leave copies: one skill per name on an agent.
|
|
547
|
+
const same = name ? (await functions.listFunctions(agent_id)).find((f) => (f.name ?? '').trim().toLowerCase() === name.trim().toLowerCase()) : undefined;
|
|
548
|
+
if (same)
|
|
549
|
+
throw new Error(`The agent already has a skill named "${name}" (function_id ${same._id}). Change it with update_function instead of creating another.`);
|
|
550
|
+
// copilot turns configurationSchema into the executable form only on update: create, then configure.
|
|
551
|
+
const created = await functions.createFunction(agent_id, { type, name, description });
|
|
552
|
+
if (!configurationSchema)
|
|
553
|
+
return success(created);
|
|
554
|
+
try {
|
|
555
|
+
return success(await functions.updateFunction(agent_id, created._id, { type, description: description ?? '', configurationSchema }));
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
// Leave no half-made skill behind: remove the empty one, then say why nothing was saved.
|
|
559
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
560
|
+
try {
|
|
561
|
+
await functions.deleteFunction(agent_id, created._id);
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
throw new Error(`Skill ${created._id} was created, but its configuration was not saved (${why}). Remove it with delete_function, or fix it with update_function.`);
|
|
565
|
+
}
|
|
566
|
+
throw new Error(`Nothing was saved: the skill's configuration was refused (${why}). Fix it and call create_function again.`);
|
|
567
|
+
}
|
|
178
568
|
}
|
|
179
569
|
catch (err) {
|
|
180
570
|
return error(err);
|
|
181
571
|
}
|
|
182
572
|
});
|
|
183
|
-
|
|
573
|
+
reg.tool('update_function', 'Change a skill of an agent (name, description, configurationSchema).', {
|
|
184
574
|
agent_id: z.string().describe('The agent ID'),
|
|
185
575
|
function_id: z.string().describe('The function ID'),
|
|
186
576
|
description: z.string().optional().describe('Updated description'),
|
|
187
577
|
name: z.string().optional().describe('Updated function name'),
|
|
188
|
-
url: z.string().optional().describe('
|
|
189
|
-
method: z.string().optional().describe('
|
|
578
|
+
url: z.string().optional().describe('rest_api: new endpoint URL'),
|
|
579
|
+
method: z.string().optional().describe('rest_api: new HTTP method'),
|
|
580
|
+
payload: z.union([z.string(), z.record(z.any())]).optional().describe('rest_api: new request body (JSON) with {{parameter}} placeholders'),
|
|
581
|
+
response_field: z.string().optional().describe('rest_api: new response dot-path'),
|
|
582
|
+
output_instructions: z.string().optional().describe('New text the agent says with the answer; {{response.field}} reads the API answer'),
|
|
583
|
+
craft_response: z.boolean().optional().describe('true: the agent phrases the answer; false: it goes to the customer as is'),
|
|
584
|
+
...skillAuthFields,
|
|
585
|
+
configuration: jsonObject().optional().describe('Other configurationSchema fields to change (an object, or JSON text); the rest of the saved configuration is kept'),
|
|
586
|
+
transfer_to: z.string().optional().describe('transfer_call: new phone number (E.164) or SIP address; replaces the saved destination'),
|
|
190
587
|
}, async (args) => {
|
|
191
588
|
try {
|
|
192
|
-
const { agent_id
|
|
193
|
-
|
|
194
|
-
|
|
589
|
+
const { agent_id } = args;
|
|
590
|
+
// copilot needs type and description on every update and replaces the configuration whole.
|
|
591
|
+
const current = await skillOf(agent_id, args.function_id);
|
|
592
|
+
const function_id = current._id;
|
|
593
|
+
const input = {
|
|
594
|
+
type: current.type, description: args.description ?? current.description ?? '',
|
|
595
|
+
};
|
|
596
|
+
if (args.name)
|
|
597
|
+
input.name = args.name;
|
|
598
|
+
const configurationSchema = skillConfiguration(current.type, withAuthorization(args), current.configurationSchema ?? {});
|
|
599
|
+
if (configurationSchema)
|
|
600
|
+
input.configurationSchema = configurationSchema;
|
|
601
|
+
return success(await functions.updateFunction(agent_id, function_id, input));
|
|
195
602
|
}
|
|
196
603
|
catch (err) {
|
|
197
604
|
return error(err);
|
|
198
605
|
}
|
|
199
|
-
});
|
|
200
|
-
|
|
606
|
+
}, liveChange('a skill', ['function_id']));
|
|
607
|
+
reg.tool('delete_function', 'Remove a skill from an agent.', {
|
|
201
608
|
agent_id: z.string().describe('The agent ID'),
|
|
202
609
|
function_id: z.string().describe('The function ID to delete'),
|
|
203
610
|
}, async (args) => {
|
|
204
611
|
try {
|
|
205
|
-
await
|
|
206
|
-
|
|
612
|
+
const skill = await skillOf(args.agent_id, args.function_id);
|
|
613
|
+
await functions.deleteFunction(args.agent_id, skill._id);
|
|
614
|
+
return success({ deleted: true, agent_id: args.agent_id, function_id: skill._id, name: skill.name });
|
|
207
615
|
}
|
|
208
616
|
catch (err) {
|
|
209
617
|
return error(err);
|
|
210
618
|
}
|
|
211
619
|
});
|
|
212
|
-
|
|
620
|
+
reg.tool('test_function', 'Run a skill once with sample parameters and return what its API answered. Calls the real endpoint.', {
|
|
213
621
|
agent_id: z.string().describe('The agent ID'),
|
|
214
622
|
function_id: z.string().describe('The function ID to test'),
|
|
215
|
-
params:
|
|
623
|
+
params: jsonObject().optional().describe('Sample values for the skill\'s {{parameter}} placeholders (an object, or JSON text)'),
|
|
216
624
|
}, async (args) => {
|
|
217
625
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
626
|
+
// copilot's test route runs the configuration it is sent (not the saved one) and sends the body as is:
|
|
627
|
+
// send the saved configuration with the sample values in place of its placeholders.
|
|
628
|
+
const current = await skillOf(args.agent_id, args.function_id);
|
|
629
|
+
const params = (args.params ?? {});
|
|
630
|
+
const fill = (v) => (typeof v === 'string'
|
|
631
|
+
? v.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (m, k) => (k in params ? String(params[k]) : m))
|
|
632
|
+
: v);
|
|
633
|
+
const config = Object.fromEntries(Object.entries(current.configurationSchema ?? {}).map(([k, v]) => [k, fill(v)]));
|
|
634
|
+
return success(await functions.testFunction(args.agent_id, current._id, config));
|
|
220
635
|
}
|
|
221
636
|
catch (err) {
|
|
222
637
|
return error(err);
|
|
223
638
|
}
|
|
224
639
|
});
|
|
225
|
-
|
|
640
|
+
reg.tool('export_functions', 'Export every skill of an agent as a JSON file (to copy them or keep a backup before import_functions).', { agent_id: z.string().describe('The agent ID') }, async (args) => {
|
|
226
641
|
try {
|
|
227
642
|
const result = await functions.exportFunctions(args.agent_id);
|
|
228
643
|
return success(result);
|
|
@@ -231,7 +646,7 @@ export function registerTools(server, client) {
|
|
|
231
646
|
return error(err);
|
|
232
647
|
}
|
|
233
648
|
});
|
|
234
|
-
|
|
649
|
+
reg.tool('import_functions', 'Import functions into an agent from a URL (e.g. OpenAPI spec)', {
|
|
235
650
|
agent_id: z.string().describe('The agent ID'),
|
|
236
651
|
url: z.string().describe('URL to import functions from'),
|
|
237
652
|
}, async (args) => {
|
|
@@ -243,36 +658,73 @@ export function registerTools(server, client) {
|
|
|
243
658
|
return error(err);
|
|
244
659
|
}
|
|
245
660
|
});
|
|
246
|
-
|
|
661
|
+
reg.tool('generate_skills_from_doc', 'Propose skills from an API document (file path on this machine) or pasted text. Creates nothing: review the proposal, then call bulk_create_skills.', {
|
|
662
|
+
agent_id: z.string().describe('The agent ID'),
|
|
663
|
+
file_path: z.string().optional().describe('Path of a document on this machine'),
|
|
664
|
+
text: z.string().optional().describe('Text to read instead of a file'),
|
|
665
|
+
}, async (args) => {
|
|
666
|
+
try {
|
|
667
|
+
if (!args.file_path && !args.text)
|
|
668
|
+
throw new Error('Give file_path or text.');
|
|
669
|
+
const proposal = await functions.generateFromDoc(args.agent_id, { filePath: args.file_path, text: args.text });
|
|
670
|
+
return success({ proposed: proposedSkills(proposal), raw: proposedSkills(proposal) ? undefined : proposal });
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
return error(err);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
reg.tool('bulk_create_skills', 'Create several skills on an agent at once, for example the proposal from generate_skills_from_doc. Names are checked first; a failure part-way can leave some created.', {
|
|
677
|
+
agent_id: z.string().describe('The agent ID'),
|
|
678
|
+
functions: z.array(skillDefinition).min(1).describe('Skill definitions'),
|
|
679
|
+
}, async (args) => {
|
|
680
|
+
try {
|
|
681
|
+
const skills = args.functions.map((f) => {
|
|
682
|
+
const { url, method, payload, response_field, output_instructions, transfer_to, auth_type, auth_token, auth_key, auth_value, auth_username, auth_password, ...rest } = f;
|
|
683
|
+
const built = skillConfiguration(String(f.type), withAuthorization({
|
|
684
|
+
url: url, method: method, payload, response_field: response_field,
|
|
685
|
+
output_instructions: output_instructions, transfer_to: transfer_to,
|
|
686
|
+
auth_type, auth_token, auth_key, auth_value, auth_username, auth_password,
|
|
687
|
+
configuration: rest.configurationSchema,
|
|
688
|
+
}));
|
|
689
|
+
return built ? { ...rest, configurationSchema: built } : rest;
|
|
690
|
+
});
|
|
691
|
+
return success(await functions.bulkCreate(args.agent_id, skills));
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
return error(err);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
reg.tool('list_function_parameters', 'The parameters a skill collects from the customer before it runs.', {
|
|
247
698
|
agent_id: z.string().describe('The agent ID'),
|
|
248
699
|
function_id: z.string().describe('The function ID'),
|
|
249
700
|
}, async (args) => {
|
|
250
701
|
try {
|
|
251
|
-
const result = await functions.listParameters(args.agent_id, args.function_id);
|
|
702
|
+
const result = await functions.listParameters(args.agent_id, (await skillOf(args.agent_id, args.function_id))._id);
|
|
252
703
|
return success(result);
|
|
253
704
|
}
|
|
254
705
|
catch (err) {
|
|
255
706
|
return error(err);
|
|
256
707
|
}
|
|
257
708
|
});
|
|
258
|
-
|
|
709
|
+
reg.tool('add_function_parameter', 'Add a parameter the skill should collect (name, type, description, required).', {
|
|
259
710
|
agent_id: z.string().describe('The agent ID'),
|
|
260
711
|
function_id: z.string().describe('The function ID'),
|
|
261
712
|
name: z.string().describe('Parameter name'),
|
|
262
713
|
type: z.string().describe('Parameter type (string, number, boolean)'),
|
|
263
714
|
description: z.string().optional().describe('Parameter description'),
|
|
264
715
|
required: z.boolean().optional().describe('Whether the parameter is required'),
|
|
716
|
+
allowed_values: z.array(z.string()).optional().describe('Fixed choices the customer picks from (shown as buttons); omit for free text'),
|
|
265
717
|
}, async (args) => {
|
|
266
718
|
try {
|
|
267
|
-
const { agent_id, function_id, ...param } = args;
|
|
268
|
-
const result = await functions.addParameter(agent_id, function_id, param);
|
|
719
|
+
const { agent_id, function_id, allowed_values, ...param } = args;
|
|
720
|
+
const result = await functions.addParameter(agent_id, (await skillOf(agent_id, function_id))._id, { ...param, allowedValues: allowed_values });
|
|
269
721
|
return success(result);
|
|
270
722
|
}
|
|
271
723
|
catch (err) {
|
|
272
724
|
return error(err);
|
|
273
725
|
}
|
|
274
726
|
});
|
|
275
|
-
|
|
727
|
+
reg.tool('update_function_parameter', 'Change a skill parameter.', {
|
|
276
728
|
agent_id: z.string().describe('The agent ID'),
|
|
277
729
|
function_id: z.string().describe('The function ID'),
|
|
278
730
|
parameter_id: z.string().describe('The parameter ID'),
|
|
@@ -280,39 +732,41 @@ export function registerTools(server, client) {
|
|
|
280
732
|
type: z.string().optional().describe('Updated type'),
|
|
281
733
|
description: z.string().optional().describe('Updated description'),
|
|
282
734
|
required: z.boolean().optional().describe('Updated required flag'),
|
|
735
|
+
allowed_values: z.array(z.string()).optional().describe('New fixed choices; [] removes them. Anything not given stays as it is'),
|
|
283
736
|
}, async (args) => {
|
|
284
737
|
try {
|
|
285
|
-
const { agent_id, function_id, parameter_id, ...param } = args;
|
|
286
|
-
const result = await functions.updateParameter(agent_id, function_id, parameter_id, param);
|
|
738
|
+
const { agent_id, function_id, parameter_id, allowed_values, ...param } = args;
|
|
739
|
+
const result = await functions.updateParameter(agent_id, (await skillOf(agent_id, function_id))._id, parameter_id, { ...param, allowedValues: allowed_values });
|
|
287
740
|
return success(result);
|
|
288
741
|
}
|
|
289
742
|
catch (err) {
|
|
290
743
|
return error(err);
|
|
291
744
|
}
|
|
292
745
|
});
|
|
293
|
-
|
|
746
|
+
reg.tool('delete_function_parameter', 'Remove a skill parameter.', {
|
|
294
747
|
agent_id: z.string().describe('The agent ID'),
|
|
295
748
|
function_id: z.string().describe('The function ID'),
|
|
296
749
|
parameter_id: z.string().describe('The parameter ID'),
|
|
297
750
|
}, async (args) => {
|
|
298
751
|
try {
|
|
299
|
-
await functions.deleteParameter(args.agent_id, args.function_id, args.parameter_id);
|
|
752
|
+
await functions.deleteParameter(args.agent_id, (await skillOf(args.agent_id, args.function_id))._id, args.parameter_id);
|
|
300
753
|
return success({ deleted: true });
|
|
301
754
|
}
|
|
302
755
|
catch (err) {
|
|
303
756
|
return error(err);
|
|
304
757
|
}
|
|
305
758
|
});
|
|
306
|
-
|
|
759
|
+
reg.tool('list_master_functions', 'Built-in skill types an agent can use as starting points (pass agent_id to get the ones for its channels, e.g. voice).', { agent_id: z.string().optional().describe('Agent whose channels (WEB, VOICE) decide which templates apply') }, async (args) => {
|
|
307
760
|
try {
|
|
308
|
-
const
|
|
761
|
+
const channels = args.agent_id ? (await agents.getAgent(args.agent_id)).channels : undefined;
|
|
762
|
+
const result = await functions.listMasterFunctions(channels?.map((c) => (typeof c === 'string' ? c : String(c.type ?? ''))).filter(Boolean));
|
|
309
763
|
return success(result);
|
|
310
764
|
}
|
|
311
765
|
catch (err) {
|
|
312
766
|
return error(err);
|
|
313
767
|
}
|
|
314
768
|
});
|
|
315
|
-
|
|
769
|
+
reg.tool('list_llm_models', 'LLM providers and models an agent can run on (use provider/model with set_config).', {}, async () => {
|
|
316
770
|
try {
|
|
317
771
|
const result = await functions.listLLMModels();
|
|
318
772
|
return success(result);
|
|
@@ -324,27 +778,27 @@ export function registerTools(server, client) {
|
|
|
324
778
|
// ---------------------------------------------------------------------------
|
|
325
779
|
// Knowledge base tools
|
|
326
780
|
// ---------------------------------------------------------------------------
|
|
327
|
-
|
|
781
|
+
reg.tool('list_knowledge_bases', 'List the org\'s knowledge bases (what agents can look answers up in), with the agents each is linked to.', {}, async () => {
|
|
328
782
|
try {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return success(
|
|
783
|
+
// A website base lists every crawled page (100k characters for a large site); get_knowledge_base has them.
|
|
784
|
+
const kbs = (await kb.listKBs());
|
|
785
|
+
const pages = await orgPages(client);
|
|
786
|
+
return success(kbs.map(({ fileWebSources, kbSampleFaqs: _faqs, ...rest }) => ({ ...rest, sources: fileWebSources?.length ?? 0, url: `${pages}/ai-studio/knowledge-base/${rest._id}` })));
|
|
333
787
|
}
|
|
334
788
|
catch (err) {
|
|
335
789
|
return error(err);
|
|
336
790
|
}
|
|
337
791
|
});
|
|
338
|
-
|
|
792
|
+
reg.tool('get_knowledge_base', 'One knowledge base with its website and file sources and their crawl status.', { kb_id: z.string().describe('The knowledge base ID') }, async (args) => {
|
|
339
793
|
try {
|
|
340
794
|
const result = await kb.getKB(args.kb_id);
|
|
341
|
-
return success(result);
|
|
795
|
+
return success({ ...result, url: `${await orgPages(client)}/ai-studio/knowledge-base/${args.kb_id}` });
|
|
342
796
|
}
|
|
343
797
|
catch (err) {
|
|
344
798
|
return error(err);
|
|
345
799
|
}
|
|
346
800
|
});
|
|
347
|
-
|
|
801
|
+
reg.tool('create_knowledge_base', 'Create a knowledge base. type QNA (question/answer pairs), URL (websites) or FILE (documents).', {
|
|
348
802
|
name: z.string().describe('Knowledge base name'),
|
|
349
803
|
type: z.string().describe('Knowledge base type'),
|
|
350
804
|
}, async (args) => {
|
|
@@ -356,7 +810,7 @@ export function registerTools(server, client) {
|
|
|
356
810
|
return error(err);
|
|
357
811
|
}
|
|
358
812
|
});
|
|
359
|
-
|
|
813
|
+
reg.tool('update_knowledge_base', 'Rename a knowledge base.', {
|
|
360
814
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
361
815
|
name: z.string().optional().describe('Updated name'),
|
|
362
816
|
description: z.string().optional().describe('Updated description'),
|
|
@@ -374,7 +828,7 @@ export function registerTools(server, client) {
|
|
|
374
828
|
return error(err);
|
|
375
829
|
}
|
|
376
830
|
});
|
|
377
|
-
|
|
831
|
+
reg.tool('delete_knowledge_base', 'Delete a knowledge base with all its sources. It must be unlinked from every agent first.', { kb_id: z.string().describe('The knowledge base ID to delete') }, async (args) => {
|
|
378
832
|
try {
|
|
379
833
|
await kb.deleteKB(args.kb_id);
|
|
380
834
|
return success({ deleted: true, kb_id: args.kb_id });
|
|
@@ -383,7 +837,7 @@ export function registerTools(server, client) {
|
|
|
383
837
|
return error(err);
|
|
384
838
|
}
|
|
385
839
|
});
|
|
386
|
-
|
|
840
|
+
reg.tool('list_kb_data', 'The question/answer pairs typed into a knowledge base (website and file sources are listed by get_knowledge_base).', {
|
|
387
841
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
388
842
|
page: z.number().optional().describe('Page number'),
|
|
389
843
|
limit: z.number().optional().describe('Items per page'),
|
|
@@ -401,7 +855,7 @@ export function registerTools(server, client) {
|
|
|
401
855
|
return error(err);
|
|
402
856
|
}
|
|
403
857
|
});
|
|
404
|
-
|
|
858
|
+
reg.tool('add_kb_data', 'Add a data source entry to a knowledge base. For URL scraping use scrape_url instead. For file uploads use upload_kb_file.', {
|
|
405
859
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
406
860
|
question: z.string().optional().describe('FAQ question'),
|
|
407
861
|
answer: z.string().optional().describe('FAQ answer'),
|
|
@@ -421,7 +875,7 @@ export function registerTools(server, client) {
|
|
|
421
875
|
return error(err);
|
|
422
876
|
}
|
|
423
877
|
});
|
|
424
|
-
|
|
878
|
+
reg.tool('scrape_url', 'Scrape a website URL and add it as a data source to a knowledge base. Initiates async crawling.', {
|
|
425
879
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
426
880
|
url: z.string().describe('URL to scrape'),
|
|
427
881
|
crawl_mode: z.string().optional().describe('Crawl mode: "full" (entire site) or "selected"'),
|
|
@@ -434,7 +888,7 @@ export function registerTools(server, client) {
|
|
|
434
888
|
return error(err);
|
|
435
889
|
}
|
|
436
890
|
});
|
|
437
|
-
|
|
891
|
+
reg.tool('upload_kb_file', 'Upload a document from this machine into a knowledge base; it is read in the background (see kb_scrape_status).', {
|
|
438
892
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
439
893
|
file_path: z.string().describe('Path to the file to upload'),
|
|
440
894
|
}, async (args) => {
|
|
@@ -446,7 +900,7 @@ export function registerTools(server, client) {
|
|
|
446
900
|
return error(err);
|
|
447
901
|
}
|
|
448
902
|
});
|
|
449
|
-
|
|
903
|
+
reg.tool('update_kb_data', 'Change a question/answer pair of a knowledge base.', {
|
|
450
904
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
451
905
|
source_id: z.string().describe('The data source ID'),
|
|
452
906
|
question: z.string().optional().describe('Updated question'),
|
|
@@ -463,7 +917,7 @@ export function registerTools(server, client) {
|
|
|
463
917
|
return error(err);
|
|
464
918
|
}
|
|
465
919
|
});
|
|
466
|
-
|
|
920
|
+
reg.tool('delete_kb_data', 'Delete a source of a knowledge base.', {
|
|
467
921
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
468
922
|
source_id: z.string().describe('The data source ID to delete'),
|
|
469
923
|
}, async (args) => {
|
|
@@ -475,7 +929,57 @@ export function registerTools(server, client) {
|
|
|
475
929
|
return error(err);
|
|
476
930
|
}
|
|
477
931
|
});
|
|
478
|
-
|
|
932
|
+
reg.tool('kb_chunks', 'The text chunks the agent actually retrieves from one knowledge base source. Use it to check what was learned from a page or file.', { kb_id: z.string(), source_id: z.string() }, async (args) => {
|
|
933
|
+
try {
|
|
934
|
+
return success(await kb.chunks(args.kb_id, args.source_id));
|
|
935
|
+
}
|
|
936
|
+
catch (err) {
|
|
937
|
+
return error(err);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
reg.tool('reimport_kb_source', 'Crawl a website source again after the site changed. Runs in the background; follow it with kb_scrape_status.', { kb_id: z.string(), source_id: z.string() }, async (args) => {
|
|
941
|
+
try {
|
|
942
|
+
return success(await kb.reimport(args.kb_id, args.source_id));
|
|
943
|
+
}
|
|
944
|
+
catch (err) {
|
|
945
|
+
return error(err);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
reg.tool('kb_scrape_status', 'Progress of website crawls and file imports of a knowledge base (per source: status, job status, error).', { kb_id: z.string() }, async (args) => {
|
|
949
|
+
try {
|
|
950
|
+
return success(await kb.scrapeStatus(args.kb_id));
|
|
951
|
+
}
|
|
952
|
+
catch (err) {
|
|
953
|
+
return error(err);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
reg.tool('kb_gaps', 'Questions the agents could not answer, to decide what to add to the knowledge base. themes: true groups them by topic.', {
|
|
957
|
+
agent_id: z.string().optional(),
|
|
958
|
+
kb_id: z.string().optional(),
|
|
959
|
+
themes: z.boolean().optional(),
|
|
960
|
+
days: z.number().int().min(1).max(365).optional().describe('With themes: look back this many days'),
|
|
961
|
+
include_test: z.boolean().optional(),
|
|
962
|
+
page: z.number().int().min(0).optional().describe('Starting at 0'),
|
|
963
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
964
|
+
}, async (args) => {
|
|
965
|
+
try {
|
|
966
|
+
if (args.themes)
|
|
967
|
+
return success(await kb.gapClusters({ days: args.days, page: args.page, limit: args.limit }));
|
|
968
|
+
return success(await kb.gaps({ agentId: args.agent_id, kbId: args.kb_id, includeTest: args.include_test, page: args.page, limit: args.limit }));
|
|
969
|
+
}
|
|
970
|
+
catch (err) {
|
|
971
|
+
return error(err);
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
reg.tool('kb_check', 'Could the org\'s knowledge bases answer this question? Returns whether it is answerable, the top score and the matching chunks.', { question: z.string() }, async (args) => {
|
|
975
|
+
try {
|
|
976
|
+
return success(await kb.check(args.question));
|
|
977
|
+
}
|
|
978
|
+
catch (err) {
|
|
979
|
+
return error(err);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
reg.tool('link_kb', 'Let an agent answer from these knowledge bases.', {
|
|
479
983
|
agent_id: z.string().describe('The agent ID'),
|
|
480
984
|
kb_ids: z.array(z.string()).describe('Array of knowledge base IDs to link'),
|
|
481
985
|
}, async (args) => {
|
|
@@ -487,7 +991,7 @@ export function registerTools(server, client) {
|
|
|
487
991
|
return error(err);
|
|
488
992
|
}
|
|
489
993
|
});
|
|
490
|
-
|
|
994
|
+
reg.tool('unlink_kb', 'Stop an agent from using a knowledge base.', {
|
|
491
995
|
agent_id: z.string().describe('The agent ID'),
|
|
492
996
|
kb_id: z.string().describe('The knowledge base ID to unlink'),
|
|
493
997
|
}, async (args) => {
|
|
@@ -499,7 +1003,7 @@ export function registerTools(server, client) {
|
|
|
499
1003
|
return error(err);
|
|
500
1004
|
}
|
|
501
1005
|
});
|
|
502
|
-
|
|
1006
|
+
reg.tool('export_kb', 'Export the question/answer pairs of a knowledge base as a CSV or XLSX download link.', {
|
|
503
1007
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
504
1008
|
format: z.string().optional().describe('Export format (e.g. "json", "csv")'),
|
|
505
1009
|
}, async (args) => {
|
|
@@ -511,7 +1015,7 @@ export function registerTools(server, client) {
|
|
|
511
1015
|
return error(err);
|
|
512
1016
|
}
|
|
513
1017
|
});
|
|
514
|
-
|
|
1018
|
+
reg.tool('import_kb', 'Import websites or files into a knowledge base; processed in the background (see kb_scrape_status).', {
|
|
515
1019
|
kb_id: z.string().describe('The knowledge base ID'),
|
|
516
1020
|
data: z.record(z.unknown()).describe('Data to import'),
|
|
517
1021
|
}, async (args) => {
|
|
@@ -526,125 +1030,855 @@ export function registerTools(server, client) {
|
|
|
526
1030
|
// ---------------------------------------------------------------------------
|
|
527
1031
|
// Chat tool
|
|
528
1032
|
// ---------------------------------------------------------------------------
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
chat.on('stream', (data) => {
|
|
541
|
-
if (data.isFirstChunk)
|
|
542
|
-
receiving = true;
|
|
543
|
-
if (data.streamFinished)
|
|
544
|
-
receiving = false;
|
|
545
|
-
});
|
|
546
|
-
const greetingMessages = [];
|
|
547
|
-
chat.on('message', (text) => {
|
|
548
|
-
greetingMessages.push(text);
|
|
549
|
-
messageCount++;
|
|
550
|
-
});
|
|
551
|
-
const sessionId = await chat.connect();
|
|
552
|
-
// Wait for greeting to finish streaming (up to 8s)
|
|
553
|
-
await new Promise((resolve) => {
|
|
554
|
-
const check = setInterval(() => {
|
|
555
|
-
if (!receiving) {
|
|
556
|
-
clearInterval(check);
|
|
557
|
-
resolve();
|
|
558
|
-
}
|
|
559
|
-
}, 100);
|
|
560
|
-
setTimeout(() => { clearInterval(check); resolve(); }, 8000);
|
|
1033
|
+
const conversations = new ConversationsSDK(client);
|
|
1034
|
+
reg.tool('list_conversations', 'List conversations of one agent in the org, newest first, including test ones, with whether each is a test and whether the CLI opened it.', {
|
|
1035
|
+
agent_id: z.string().describe('Agent whose conversations to list'),
|
|
1036
|
+
limit: z.number().int().min(1).max(100).optional().describe('Rows per page (default 50, at most 100)'),
|
|
1037
|
+
page: z.number().int().min(0).optional().describe('Page, starting at 0'),
|
|
1038
|
+
live_only: z.boolean().optional().describe('Hide test conversations'),
|
|
1039
|
+
cli_only: z.boolean().optional().describe('Only conversations opened by the CLI'),
|
|
1040
|
+
}, async (args) => {
|
|
1041
|
+
try {
|
|
1042
|
+
const res = await conversations.list(args.agent_id, {
|
|
1043
|
+
limit: args.limit, page: args.page, includeTest: !args.live_only, cliOnly: args.cli_only,
|
|
561
1044
|
});
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
1045
|
+
return success(res.conversations.map((c) => ({
|
|
1046
|
+
conversation_id: c.request_id, created_at: c.created_at ?? c.createdAt, test: isTestConversation(c),
|
|
1047
|
+
cli: isCliConversation(c), first_message: c.first_text,
|
|
1048
|
+
})));
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
return error(err);
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
reg.tool('get_conversation', 'Get one conversation and its messages (who said what, oldest first). format "testset" returns it as an evaluation test set with one scenario item to replay.', {
|
|
1055
|
+
agent_id: z.string().describe('Agent the conversation belongs to'),
|
|
1056
|
+
conversation_id: z.string().describe('Conversation (request) id'),
|
|
1057
|
+
format: z.enum(['messages', 'testset']).optional().describe('messages (default) or testset'),
|
|
1058
|
+
}, async (args) => {
|
|
1059
|
+
try {
|
|
1060
|
+
const projectId = await projectOfAgent(client, args.agent_id);
|
|
1061
|
+
const [conversation, messages] = await Promise.all([
|
|
1062
|
+
conversations.get(projectId, args.conversation_id),
|
|
1063
|
+
conversations.messages(projectId, args.conversation_id, args.agent_id),
|
|
1064
|
+
]);
|
|
1065
|
+
if (args.format === 'testset')
|
|
1066
|
+
return success(conversationToTestSet(conversation, messages, args.agent_id));
|
|
1067
|
+
return success({
|
|
1068
|
+
conversation_id: conversation.request_id, test: conversation.isTestMode === true,
|
|
1069
|
+
url: conversationUrl(client, projectId, conversation.request_id),
|
|
1070
|
+
messages: messages.map((m) => ({ role: m.role, text: m.text, at: m.createdAt })),
|
|
575
1071
|
});
|
|
576
|
-
chat.disconnect();
|
|
577
|
-
return success({ session_id: sessionId, greeting: greetingMessages.slice(0, greetingCount).join('\n'), response });
|
|
578
1072
|
}
|
|
579
1073
|
catch (err) {
|
|
580
1074
|
return error(err);
|
|
581
1075
|
}
|
|
582
1076
|
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
server.tool('run_evaluation', 'Trigger an evaluation run for a bot against a test set', {
|
|
587
|
-
bot_id: z.string().describe('The bot ID to evaluate'),
|
|
588
|
-
test_set_id: z.string().describe('The test set ID'),
|
|
589
|
-
rubric_id: z.string().optional().describe('Optional rubric ID'),
|
|
590
|
-
concurrency: z.number().optional().describe('Number of concurrent evaluations'),
|
|
591
|
-
eval_model: z.string().optional().describe('Model to use for evaluation judging'),
|
|
592
|
-
keep: z.boolean().optional().describe('Keep evaluation data after completion'),
|
|
593
|
-
limit: z.number().optional().describe('Limit items to evaluate'),
|
|
594
|
-
component_scope: z.string().optional().describe('Component scope filter'),
|
|
595
|
-
component_ids: z.array(z.string()).optional().describe('Component ID filter'),
|
|
596
|
-
mode: z.string().optional().describe('Evaluation mode'),
|
|
1077
|
+
reg.tool('mark_conversation_test', 'Mark conversations as test, as the dashboard does: removed from analytics and usage billing reversed.', {
|
|
1078
|
+
agent_id: z.string().describe('Agent the conversations belong to'),
|
|
1079
|
+
conversation_ids: z.array(z.string()).min(1).max(100).describe('Conversation (request) ids'),
|
|
597
1080
|
}, async (args) => {
|
|
598
1081
|
try {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
input.concurrency = args.concurrency;
|
|
607
|
-
if (args.eval_model !== undefined)
|
|
608
|
-
input.eval_model = args.eval_model;
|
|
609
|
-
if (args.keep !== undefined)
|
|
610
|
-
input.keep = args.keep;
|
|
611
|
-
if (args.limit !== undefined)
|
|
612
|
-
input.limit = args.limit;
|
|
613
|
-
if (args.component_scope !== undefined)
|
|
614
|
-
input.component_scope = args.component_scope;
|
|
615
|
-
if (args.component_ids !== undefined)
|
|
616
|
-
input.component_ids = args.component_ids;
|
|
617
|
-
if (args.mode !== undefined)
|
|
618
|
-
input.mode = args.mode;
|
|
619
|
-
const result = await evals.triggerEvaluation(input);
|
|
620
|
-
return success(result);
|
|
1082
|
+
const projectId = await projectOfAgent(client, args.agent_id);
|
|
1083
|
+
const done = [];
|
|
1084
|
+
for (const id of args.conversation_ids) {
|
|
1085
|
+
await conversations.markTest(projectId, id);
|
|
1086
|
+
done.push(id);
|
|
1087
|
+
}
|
|
1088
|
+
return success({ marked_test: done });
|
|
621
1089
|
}
|
|
622
1090
|
catch (err) {
|
|
623
1091
|
return error(err);
|
|
624
1092
|
}
|
|
625
1093
|
});
|
|
626
|
-
|
|
1094
|
+
const voice = new VoiceSDK(client);
|
|
1095
|
+
reg.tool('list_voices', 'Text-to-speech voices of a provider (cartesia default, or elevenlabs), filterable by language, gender and name.', {
|
|
1096
|
+
provider: z.enum(['cartesia', 'elevenlabs']).optional(),
|
|
1097
|
+
language: z.string().optional(),
|
|
1098
|
+
gender: z.enum(['masculine', 'feminine', 'gender_neutral']).optional(),
|
|
1099
|
+
search: z.string().optional(),
|
|
1100
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
1101
|
+
}, async (args) => {
|
|
627
1102
|
try {
|
|
628
|
-
|
|
629
|
-
return success(result);
|
|
1103
|
+
return success(await voice.listVoices({ provider: args.provider, language: args.language, gender: args.gender, q: args.search, limit: args.limit }));
|
|
630
1104
|
}
|
|
631
1105
|
catch (err) {
|
|
632
1106
|
return error(err);
|
|
633
1107
|
}
|
|
634
1108
|
});
|
|
635
|
-
|
|
1109
|
+
reg.tool('list_voice_models', 'Speech provider and model catalog: stt (speech to text) or tts (text to speech).', { kind: z.enum(['stt', 'tts']) }, async (args) => {
|
|
636
1110
|
try {
|
|
637
|
-
|
|
638
|
-
return success(result);
|
|
1111
|
+
return success(await voice.voiceModels(args.kind));
|
|
639
1112
|
}
|
|
640
1113
|
catch (err) {
|
|
641
1114
|
return error(err);
|
|
642
1115
|
}
|
|
643
1116
|
});
|
|
644
|
-
|
|
1117
|
+
reg.tool('get_agent_voice', 'Voice settings of an agent (text-to-speech voice, speech-to-text, noise cancellation, interruption). They live on its phone number; an agent without one has none.', { agent_id: z.string() }, async (args) => {
|
|
645
1118
|
try {
|
|
646
|
-
|
|
647
|
-
|
|
1119
|
+
return success(await voice.getAgentVoice(args.agent_id));
|
|
1120
|
+
}
|
|
1121
|
+
catch (err) {
|
|
1122
|
+
return error(err);
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
reg.tool('set_agent_voice', 'Change the text-to-speech voice of an agent. Take voice_id, name, gender, language and country_code from list_voices.', {
|
|
1126
|
+
agent_id: z.string(),
|
|
1127
|
+
provider: z.enum(['cartesia', 'elevenlabs']).optional(),
|
|
1128
|
+
voice_id: z.string(),
|
|
1129
|
+
name: z.string(),
|
|
1130
|
+
gender: z.string(),
|
|
1131
|
+
language: z.string(),
|
|
1132
|
+
country_code: z.string(),
|
|
1133
|
+
model: z.string().optional(),
|
|
1134
|
+
speed: z.number().optional(),
|
|
1135
|
+
}, async (args) => {
|
|
1136
|
+
try {
|
|
1137
|
+
return success(await voice.setAgentVoice(args.agent_id, {
|
|
1138
|
+
provider: args.provider ?? 'cartesia', voice_id: args.voice_id, name: args.name, gender: args.gender, language: args.language,
|
|
1139
|
+
countryCode: args.country_code, ...(args.model ? { model: args.model } : {}), ...(args.speed ? { settings: { speed: args.speed } } : {}),
|
|
1140
|
+
}));
|
|
1141
|
+
}
|
|
1142
|
+
catch (err) {
|
|
1143
|
+
return error(err);
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
reg.tool('set_agent_transcriber', 'Change how a voice agent hears callers (speech-to-text): provider and model from list_voice_models (kind stt), language, and key terms to listen for (names, products).', {
|
|
1147
|
+
agent_id: z.string(),
|
|
1148
|
+
provider: z.string(),
|
|
1149
|
+
model: z.string(),
|
|
1150
|
+
language: z.string(),
|
|
1151
|
+
keyterms: z.array(z.string()).optional().describe('Up to 50 words or phrases, 100 characters each'),
|
|
1152
|
+
}, async (args) => {
|
|
1153
|
+
try {
|
|
1154
|
+
return success(await voice.setAgentTranscriber(args.agent_id, { provider: args.provider, model: args.model, language: args.language, keyterms: args.keyterms }));
|
|
1155
|
+
}
|
|
1156
|
+
catch (err) {
|
|
1157
|
+
return error(err);
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
reg.tool('list_agent_numbers', 'Phone numbers attached to an agent, or (without agent_id) every number the org owns.', { agent_id: z.string().optional() }, async (args) => {
|
|
1161
|
+
try {
|
|
1162
|
+
return success(args.agent_id ? await voice.agentNumbers(args.agent_id) : await voice.orgNumbers());
|
|
1163
|
+
}
|
|
1164
|
+
catch (err) {
|
|
1165
|
+
return error(err);
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
reg.tool('attach_phone_number', 'Attach a phone number the org already owns to an agent. Never buys a number.', { agent_id: z.string(), phone_number: z.string() }, async (args) => {
|
|
1169
|
+
try {
|
|
1170
|
+
return success(await voice.attachNumber(args.agent_id, args.phone_number));
|
|
1171
|
+
}
|
|
1172
|
+
catch (err) {
|
|
1173
|
+
return error(err);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
reg.tool('search_phone_numbers', 'Numbers available to buy for a voice agent (US, CA, GB, AU; local or toll-free). Free: nothing is bought. First ask the person what kind of number they want (local with which area code, or toll-free), then search and show 4-5 numbers with their location, and buy only the one they choose.', {
|
|
1177
|
+
area_code: z.string().optional().describe('Three-digit area code, e.g. 331'),
|
|
1178
|
+
contains: z.string().optional().describe('Digits the number should contain'),
|
|
1179
|
+
country: z.enum(['US', 'CA', 'GB', 'AU']).optional().describe('Default US'),
|
|
1180
|
+
toll_free: z.boolean().optional(),
|
|
1181
|
+
limit: z.number().int().min(1).max(20).optional().describe('How many (default 5)'),
|
|
1182
|
+
}, async (args) => {
|
|
1183
|
+
try {
|
|
1184
|
+
return success(await voice.searchNumbers({ areaCode: args.area_code, contains: args.contains, country: args.country, tollFree: args.toll_free, limit: args.limit }));
|
|
1185
|
+
}
|
|
1186
|
+
catch (err) {
|
|
1187
|
+
return error(err);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
reg.tool('get_phone_number', 'The phone number of a voice agent: number, location, type, and a summary of its voice and transcriber.', { agent_id: z.string() }, async (args) => {
|
|
1191
|
+
try {
|
|
1192
|
+
return success(await voice.numberDetails(args.agent_id));
|
|
1193
|
+
}
|
|
1194
|
+
catch (err) {
|
|
1195
|
+
return error(err);
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
reg.tool('buy_phone_number', 'Buy a number from search_phone_numbers and attach it to a voice agent (costs the org money every month; no undo here). Only after the person chose that exact number. The host asks the person to approve; without an approval form this is refused.', {
|
|
1199
|
+
agent_id: z.string(),
|
|
1200
|
+
phone_number: z.string().describe('The number the person chose, as search_phone_numbers returned it'),
|
|
1201
|
+
country: z.enum(['US', 'CA', 'GB', 'AU']).optional(),
|
|
1202
|
+
toll_free: z.boolean().optional(),
|
|
1203
|
+
}, async (args, meta) => {
|
|
1204
|
+
try {
|
|
1205
|
+
// A purchase needs the person, not the model: confirm: true from a host without approval forms is not enough.
|
|
1206
|
+
if (!meta.confirmedByPerson)
|
|
1207
|
+
throw new Error('Buying a number needs a person to approve it in an approval form, and this host cannot show one. Nothing was bought. Buy it in the dashboard, or with `indemn agents numbers buy` in a terminal.');
|
|
1208
|
+
return success(await voice.buyNumber(args.agent_id, args.phone_number, { country: args.country, tollFree: args.toll_free }));
|
|
1209
|
+
}
|
|
1210
|
+
catch (err) {
|
|
1211
|
+
return error(err);
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
reg.tool('detach_phone_number', 'Detach a phone number from an agent; the org keeps owning it.', { agent_id: z.string(), phone_number: z.string() }, async (args) => {
|
|
1215
|
+
try {
|
|
1216
|
+
return success(await voice.detachNumber(args.agent_id, args.phone_number));
|
|
1217
|
+
}
|
|
1218
|
+
catch (err) {
|
|
1219
|
+
return error(err);
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
reg.tool('voice_preview', 'A browser link for a test voice call with an agent (marked as test, valid 1 hour). Give the join_url to the user; do not share it further.', { agent_id: z.string() }, async (args) => {
|
|
1223
|
+
try {
|
|
1224
|
+
return success(await voice.voicePreview(args.agent_id));
|
|
1225
|
+
}
|
|
1226
|
+
catch (err) {
|
|
1227
|
+
return error(err);
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
const distribution = new DistributionSDK(client);
|
|
1231
|
+
reg.tool('list_distributions', 'Website paths that open agents (web widget) and the org\'s embed code.', {}, async () => {
|
|
1232
|
+
try {
|
|
1233
|
+
return success(await distribution.list());
|
|
1234
|
+
}
|
|
1235
|
+
catch (err) {
|
|
1236
|
+
return error(err);
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
reg.tool('get_embed_code', 'The embed code and the website paths that open one agent.', { agent_id: z.string() }, async (args) => {
|
|
1240
|
+
try {
|
|
1241
|
+
return success(await distribution.embed(args.agent_id));
|
|
1242
|
+
}
|
|
1243
|
+
catch (err) {
|
|
1244
|
+
return error(err);
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
reg.tool('create_distribution', 'Open an agent on a website path. Visitors of that page see it once the embed code is on the site.', { agent_id: z.string(), host: z.string(), path: z.string(), path_expression: z.string().optional(), greeting: z.string().optional(), allow_upload: z.boolean().optional() }, async (args) => {
|
|
1248
|
+
try {
|
|
1249
|
+
return success(await distribution.create({ agentId: args.agent_id, host: args.host, path: args.path, pathExpression: args.path_expression, greeting: args.greeting, allowUpload: args.allow_upload }));
|
|
1250
|
+
}
|
|
1251
|
+
catch (err) {
|
|
1252
|
+
return error(err);
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
reg.tool('update_distribution', 'Change a website distribution of this org: which agent it opens, its greeting or page pattern.', { distribution_id: z.string(), agent_id: z.string().optional(), greeting: z.string().optional(), path_expression: z.string().optional() }, async (args) => {
|
|
1256
|
+
try {
|
|
1257
|
+
const changes = {};
|
|
1258
|
+
if (args.agent_id)
|
|
1259
|
+
changes.id_bot = args.agent_id;
|
|
1260
|
+
if (args.greeting)
|
|
1261
|
+
changes.greetingMessage = args.greeting;
|
|
1262
|
+
if (args.path_expression)
|
|
1263
|
+
changes.pathExpression = args.path_expression;
|
|
1264
|
+
return success(await distribution.update(args.distribution_id, changes));
|
|
1265
|
+
}
|
|
1266
|
+
catch (err) {
|
|
1267
|
+
return error(err);
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
reg.tool('list_templates', 'Agent templates, shared by every org on the platform.', {}, async () => {
|
|
1271
|
+
try {
|
|
1272
|
+
return success(await distribution.listTemplates());
|
|
1273
|
+
}
|
|
1274
|
+
catch (err) {
|
|
1275
|
+
return error(err);
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
reg.tool('create_template', 'Publish an agent of this org as a template that every org can copy.', { agent_id: z.string(), name: z.string(), description: z.string().optional(), category: z.array(z.string()).optional(), tags: z.array(z.string()).optional() }, async (args) => {
|
|
1279
|
+
try {
|
|
1280
|
+
return success(await distribution.createTemplate({ agentId: args.agent_id, name: args.name, description: args.description, category: args.category, tags: args.tags }));
|
|
1281
|
+
}
|
|
1282
|
+
catch (err) {
|
|
1283
|
+
return error(err);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
reg.tool('delete_template', 'Remove a template made from an agent of this org.', { template_id: z.string() }, async (args) => {
|
|
1287
|
+
try {
|
|
1288
|
+
return success(await distribution.deleteTemplate(args.template_id));
|
|
1289
|
+
}
|
|
1290
|
+
catch (err) {
|
|
1291
|
+
return error(err);
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
reg.tool('create_agent_from_template', 'Create a new agent in this org copied from a template (prompt, model, skills, knowledge bases; not phone numbers, voice settings or distributions).', { template_id: z.string(), name: z.string().describe('Name of the new agent'), channels: z.array(z.string()).optional() }, async (args) => {
|
|
1295
|
+
try {
|
|
1296
|
+
if ((await agents.findByName(args.name)).length)
|
|
1297
|
+
throw new Error(`An agent named "${args.name}" already exists in this org.`);
|
|
1298
|
+
return success(await distribution.createFromTemplate(args.template_id, { name: args.name, channels: args.channels }));
|
|
1299
|
+
}
|
|
1300
|
+
catch (err) {
|
|
1301
|
+
return error(err);
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
reg.tool('list_solutions', 'Solutions of the org with their connected systems.', {}, async () => {
|
|
1305
|
+
try {
|
|
1306
|
+
return success(await distribution.listSolutions());
|
|
1307
|
+
}
|
|
1308
|
+
catch (err) {
|
|
1309
|
+
return error(err);
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
const analytics = new AnalyticsSDK(client);
|
|
1313
|
+
const orgSettings = new OrgSettingsSDK(client);
|
|
1314
|
+
const range = {
|
|
1315
|
+
date_from: z.string().optional().describe('YYYY-MM-DD, default 30 days ago'),
|
|
1316
|
+
date_to: z.string().optional().describe('YYYY-MM-DD, default today'),
|
|
1317
|
+
channel: z.enum(['web', 'voice', 'all']).optional(),
|
|
1318
|
+
agent_id: z.string().optional().describe('Only this agent'),
|
|
1319
|
+
};
|
|
1320
|
+
reg.tool('get_analytics', 'Dashboard analytics (test conversations excluded): metrics, model-performance, model-performance/bots, agent-volume, handoff-precursors, critical-feedback.', { kind: z.enum(['metrics', 'model-performance', 'model-performance/bots', 'agent-volume', 'handoff-precursors', 'critical-feedback']), ...range }, async (args) => {
|
|
1321
|
+
try {
|
|
1322
|
+
return success(await analytics.get(args.kind, { from: args.date_from, to: args.date_to, channel: args.channel, agentId: args.agent_id }));
|
|
1323
|
+
}
|
|
1324
|
+
catch (err) {
|
|
1325
|
+
return error(err);
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
reg.tool('list_analytics_conversations', 'The conversations behind an analytics number, filtered by a dimension (outcome, feedback, sentiment, precursor, reason, critical_feedback_theme, attribute:<key>).', { ...range, dimension: z.string().optional(), value: z.string().optional(), search: z.string().optional(), page: z.number().int().min(1).optional(), page_size: z.number().int().min(1).max(200).optional() }, async (args) => {
|
|
1329
|
+
try {
|
|
1330
|
+
return success(await analytics.conversations({ from: args.date_from, to: args.date_to, channel: args.channel, agentId: args.agent_id, dimension: args.dimension, value: args.value, search: args.search, page: args.page, pageSize: args.page_size }));
|
|
1331
|
+
}
|
|
1332
|
+
catch (err) {
|
|
1333
|
+
return error(err);
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
reg.tool('list_custom_charts', 'Custom charts configured for the org.', {}, async () => {
|
|
1337
|
+
try {
|
|
1338
|
+
return success(await analytics.charts());
|
|
1339
|
+
}
|
|
1340
|
+
catch (err) {
|
|
1341
|
+
return error(err);
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
reg.tool('get_custom_chart', 'Data of one custom chart.', { chart_id: z.string(), ...range }, async (args) => {
|
|
1345
|
+
try {
|
|
1346
|
+
return success(await analytics.chart(args.chart_id, { from: args.date_from, to: args.date_to, channel: args.channel, agentId: args.agent_id }));
|
|
1347
|
+
}
|
|
1348
|
+
catch (err) {
|
|
1349
|
+
return error(err);
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
reg.tool('get_org_settings', 'Org settings: timezone, business hours, holidays, handoff settings (read-only) and feature switches.', {}, async () => {
|
|
1353
|
+
try {
|
|
1354
|
+
return success(await orgSettings.get());
|
|
1355
|
+
}
|
|
1356
|
+
catch (err) {
|
|
1357
|
+
return error(err);
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
reg.tool('set_org_timezone', 'Set the org timezone (business hours are read in it).', { timezone: z.string().describe('For example America/New_York') }, async (args) => {
|
|
1361
|
+
try {
|
|
1362
|
+
return success(await orgSettings.setTimezone(args.timezone));
|
|
1363
|
+
}
|
|
1364
|
+
catch (err) {
|
|
1365
|
+
return error(err);
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
reg.tool('set_business_hours', 'Replace the org business hours. days: 0 Sunday to 6; an empty slots list closes that day.', { enabled: z.boolean(), hours: z.array(z.object({ day: z.number().int().min(0).max(6), slots: z.array(z.object({ start: z.string().describe('HH:MM'), end: z.string().describe('HH:MM') })) })).optional(), out_of_hours_message: z.string().optional() }, async (args) => {
|
|
1369
|
+
try {
|
|
1370
|
+
return success(await orgSettings.setBusinessHours({ businessHoursEnabled: args.enabled, businessHours: args.hours, ...(args.out_of_hours_message ? { businessHoursUtterance: args.out_of_hours_message } : {}) }));
|
|
1371
|
+
}
|
|
1372
|
+
catch (err) {
|
|
1373
|
+
return error(err);
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
reg.tool('add_holiday', 'Close the org on a date.', { date: z.string().describe('YYYY-MM-DD'), name: z.string(), message: z.string().optional().describe('What agents say that day') }, async (args) => {
|
|
1377
|
+
try {
|
|
1378
|
+
return success(await orgSettings.addHolidays([{ name: args.name, date: args.date, ...(args.message ? { holidayFallbackUtterance: args.message } : {}) }]));
|
|
1379
|
+
}
|
|
1380
|
+
catch (err) {
|
|
1381
|
+
return error(err);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
reg.tool('set_org_admin_settings', 'Indemn staff only: the org Settings → Admin tab (feature switches, Intake Manager name, web AI disclosure, conversation timers). Give only what should change; get_org_settings shows the current values.', {
|
|
1385
|
+
conversation_summaries: z.boolean().optional().describe('AI Conversation Summaries: an AI summary when a conversation is closed'),
|
|
1386
|
+
canned_responses: z.boolean().optional().describe('Canned Responses: saved replies agents can insert'),
|
|
1387
|
+
indemn_branding: z.boolean().optional().describe('Show Indemn Branding: the Indemn logo in the chat widget'),
|
|
1388
|
+
underwriting_portal: z.boolean().optional().describe('Underwriting Portal: the Intake Manager item in the sidebar'),
|
|
1389
|
+
underwriting_portal_name: z.string().optional().describe('Name of the Intake Manager item in the sidebar (the default is "Intake Manager")'),
|
|
1390
|
+
smart_inbox: z.boolean().optional().describe('Smart Inbox: email triage and intake'),
|
|
1391
|
+
solutions: z.boolean().optional().describe('Solutions: renewal desks, campaigns and book management'),
|
|
1392
|
+
tickets: z.boolean().optional().describe('Tickets: the tickets page and the ticket scan when a conversation is resolved'),
|
|
1393
|
+
agent_testing: z.boolean().optional().describe('Agent Testing Mode: internal agent testing tools and debug details'),
|
|
1394
|
+
composer_dictation: z.boolean().optional().describe('Composer Dictation: a mic button in the agents\' chat box'),
|
|
1395
|
+
web_ai_disclosure: z.boolean().optional().describe('Show Web AI Disclosure below the first AI reply in the web widget (needs its text)'),
|
|
1396
|
+
web_ai_disclosure_text: z.string().optional().describe('The web AI disclosure text'),
|
|
1397
|
+
ongoing_to_ended_minutes: z.number().int().min(0).optional().describe('Ongoing → Ended: minutes a quiet AI conversation waits before it ends (at least 1; 0 = the default, 30 minutes)'),
|
|
1398
|
+
ended_to_resolved_minutes: z.number().int().min(0).optional().describe('Ended → Resolved: minutes before an ended conversation resolves (at least 10; 0 = the default, 24 hours)'),
|
|
1399
|
+
resolved_to_archived_minutes: z.number().int().min(0).optional().describe('Resolved → Archived: minutes before a resolved conversation is archived (at least 1; 0 = the default, 7 days)'),
|
|
1400
|
+
}, async (args) => {
|
|
1401
|
+
try {
|
|
1402
|
+
const { body } = await orgSettings.planAdmin(args);
|
|
1403
|
+
const saved = (await orgSettings.setAdminSettings(body));
|
|
1404
|
+
return success({ saved: Object.fromEntries(Object.keys(body).map((k) => [k, saved?.orgConfigs?.[k] ?? body[k]])) });
|
|
1405
|
+
}
|
|
1406
|
+
catch (err) {
|
|
1407
|
+
return error(err);
|
|
1408
|
+
}
|
|
1409
|
+
}, { preview: async (a) => `This will ${(await orgSettings.planAdmin(a)).changes.join('; ')}.` });
|
|
1410
|
+
reg.tool('list_audit_logs', 'Who changed what in the org, newest first, filtered here (the server has no filters).', { since: z.string().optional(), action: z.string().optional(), resource_type: z.string().optional(), user_id: z.string().optional(), limit: z.number().int().min(1).max(500).optional() }, async (args) => {
|
|
1411
|
+
try {
|
|
1412
|
+
return success(await new AuditSDK(client).list({ since: args.since, action: args.action, resourceType: args.resource_type, user: args.user_id, limit: args.limit }));
|
|
1413
|
+
}
|
|
1414
|
+
catch (err) {
|
|
1415
|
+
return error(err);
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
const tickets = new TicketsSDK(client);
|
|
1419
|
+
const ticketStatus = z.enum(['not_started', 'in_progress', 'completed']);
|
|
1420
|
+
const ticketPriority = z.enum(['urgent', 'high', 'normal', 'low']);
|
|
1421
|
+
reg.tool('list_tickets', 'Support tickets of the org, newest first (the org must have tickets turned on).', { status: z.array(ticketStatus).optional(), assignee_user_id: z.string().optional(), search: z.string().optional(), limit: z.number().int().min(1).max(500).optional() }, async (args) => {
|
|
1422
|
+
try {
|
|
1423
|
+
return success(await tickets.list({ status: args.status, assignee: args.assignee_user_id, search: args.search, limit: args.limit }));
|
|
1424
|
+
}
|
|
1425
|
+
catch (err) {
|
|
1426
|
+
return error(err);
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
reg.tool('get_ticket', 'One ticket with its conversations and related tickets.', { ticket_id: z.string() }, async (args) => {
|
|
1430
|
+
try {
|
|
1431
|
+
return success(await tickets.get(args.ticket_id));
|
|
1432
|
+
}
|
|
1433
|
+
catch (err) {
|
|
1434
|
+
return error(err);
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
reg.tool('create_ticket', 'Create a ticket. Without force, similar open tickets are looked for first and nothing is created when some exist (the result lists them).', {
|
|
1438
|
+
title: z.string(), description: z.string().optional(), priority: ticketPriority.optional(),
|
|
1439
|
+
assignee_user_id: z.string().optional(), conversation_id: z.string().optional().describe('Conversation (request) id to attach'),
|
|
1440
|
+
force: z.boolean().optional(),
|
|
1441
|
+
}, async (args) => {
|
|
1442
|
+
try {
|
|
1443
|
+
return success(await tickets.create({
|
|
1444
|
+
title: args.title, description: args.description, priority: args.priority,
|
|
1445
|
+
...(args.assignee_user_id ? { assignee: { type: 'user', target: args.assignee_user_id } } : {}),
|
|
1446
|
+
...(args.conversation_id ? { conversations: [{ request_id: args.conversation_id }] } : {}),
|
|
1447
|
+
}, { force: args.force }));
|
|
1448
|
+
}
|
|
1449
|
+
catch (err) {
|
|
1450
|
+
return error(err);
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
1453
|
+
reg.tool('update_ticket', 'Change a ticket: fields, assignee (user id, or unassign: true), or close it with resolve: true (and an optional note).', {
|
|
1454
|
+
ticket_id: z.string(), title: z.string().optional(), description: z.string().optional(), notes: z.string().optional(),
|
|
1455
|
+
status: z.enum(['not_started', 'in_progress']).optional(), priority: ticketPriority.optional(),
|
|
1456
|
+
assignee_user_id: z.string().optional(), unassign: z.boolean().optional(), resolve: z.boolean().optional(), note: z.string().optional(),
|
|
1457
|
+
link_ticket_id: z.string().optional().describe('Link to another ticket'),
|
|
1458
|
+
}, async (args) => {
|
|
1459
|
+
try {
|
|
1460
|
+
const out = {};
|
|
1461
|
+
const changes = {};
|
|
1462
|
+
for (const k of ['title', 'description', 'notes', 'status', 'priority'])
|
|
1463
|
+
if (args[k] !== undefined)
|
|
1464
|
+
changes[k] = args[k];
|
|
1465
|
+
if (Object.keys(changes).length)
|
|
1466
|
+
out.updated = await tickets.update(args.ticket_id, changes);
|
|
1467
|
+
if (args.assignee_user_id || args.unassign)
|
|
1468
|
+
out.assigned = await tickets.assign(args.ticket_id, args.unassign ? null : { type: 'user', target: args.assignee_user_id });
|
|
1469
|
+
if (args.link_ticket_id)
|
|
1470
|
+
out.linked = await tickets.link(args.ticket_id, args.link_ticket_id);
|
|
1471
|
+
if (args.resolve)
|
|
1472
|
+
out.resolved = await tickets.resolve(args.ticket_id, args.note);
|
|
1473
|
+
if (!Object.keys(out).length)
|
|
1474
|
+
throw new Error('Nothing to change.');
|
|
1475
|
+
return success(out);
|
|
1476
|
+
}
|
|
1477
|
+
catch (err) {
|
|
1478
|
+
return error(err);
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
reg.tool('archive_ticket', 'Archive a ticket.', { ticket_id: z.string() }, async (args) => {
|
|
1482
|
+
try {
|
|
1483
|
+
return success(await tickets.archive(args.ticket_id));
|
|
1484
|
+
}
|
|
1485
|
+
catch (err) {
|
|
1486
|
+
return error(err);
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1489
|
+
reg.tool('list_ticket_rules', 'Rules that let agents open tickets from conversations.', { agent_id: z.string().optional().describe('Only rules enabled for this agent') }, async (args) => {
|
|
1490
|
+
try {
|
|
1491
|
+
return success(await tickets.listRules({ agentId: args.agent_id }));
|
|
1492
|
+
}
|
|
1493
|
+
catch (err) {
|
|
1494
|
+
return error(err);
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
reg.tool('save_ticket_rule', 'Create a ticket rule (prompt required; checked for overlaps unless force) or change one (rule_id).', {
|
|
1498
|
+
rule_id: z.string().optional(), name: z.string().max(75).optional(), prompt: z.string().optional(),
|
|
1499
|
+
agent_ids: z.array(z.string()).optional().describe('Only these agents; default all'),
|
|
1500
|
+
assignee_user_id: z.string().optional(), enabled: z.boolean().optional(), force: z.boolean().optional(),
|
|
1501
|
+
}, async (args) => {
|
|
1502
|
+
try {
|
|
1503
|
+
return success(await tickets.saveRule({
|
|
1504
|
+
ruleId: args.rule_id, name: args.name, prompt: args.prompt, enabled: args.enabled,
|
|
1505
|
+
...(args.agent_ids ? { scope: { kind: 'specific', agents: args.agent_ids } } : args.rule_id ? {} : { scope: { kind: 'all_agents', agents: [] } }),
|
|
1506
|
+
...(args.assignee_user_id ? { assignment: { type: 'user', target: args.assignee_user_id } } : {}),
|
|
1507
|
+
}, { force: args.force }));
|
|
1508
|
+
}
|
|
1509
|
+
catch (err) {
|
|
1510
|
+
return error(err);
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
reg.tool('delete_ticket_rule', 'Delete a ticket rule.', { rule_id: z.string() }, async (args) => {
|
|
1514
|
+
try {
|
|
1515
|
+
return success(await tickets.deleteRule(args.rule_id));
|
|
1516
|
+
}
|
|
1517
|
+
catch (err) {
|
|
1518
|
+
return error(err);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
reg.tool('get_billing', 'Read-only billing: status (subscription), invoices, unpaid (open invoices), usage_forecast, or plans. Nothing here changes a plan or moves money.', { what: z.enum(['status', 'invoices', 'unpaid', 'usage_forecast', 'plans']) }, async (args) => {
|
|
1522
|
+
try {
|
|
1523
|
+
const billing = new BillingSDK(client);
|
|
1524
|
+
const result = args.what === 'status' ? await billing.status()
|
|
1525
|
+
: args.what === 'invoices' ? await billing.invoices()
|
|
1526
|
+
: args.what === 'unpaid' ? await billing.unpaid()
|
|
1527
|
+
: args.what === 'usage_forecast' ? await billing.usageForecast()
|
|
1528
|
+
: await billing.plans();
|
|
1529
|
+
return success(result);
|
|
1530
|
+
}
|
|
1531
|
+
catch (err) {
|
|
1532
|
+
return error(err);
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
reg.tool('get_campaigns', 'Read-only campaigns: list, one campaign (campaign_id), its counts / forecast / next-touchpoints / versions, templates, one template (template_id, versions: true for subject and body), or template skills.', {
|
|
1536
|
+
what: z.enum(['list', 'campaign', 'counts', 'forecast', 'next-touchpoints', 'versions', 'templates', 'template', 'template-skills']),
|
|
1537
|
+
campaign_id: z.string().optional(), template_id: z.string().optional(), versions: z.boolean().optional(),
|
|
1538
|
+
status: z.string().optional(), solution_id: z.string().optional(), channel: z.string().optional(),
|
|
1539
|
+
days: z.number().int().min(1).max(365).optional(), limit: z.number().int().min(1).max(500).optional(),
|
|
1540
|
+
}, async (args) => {
|
|
1541
|
+
try {
|
|
1542
|
+
const c = new CampaignsSDK(client);
|
|
1543
|
+
const need = (v, name) => { if (!v)
|
|
1544
|
+
throw new Error(`${name} is required for ${args.what}.`); return v; };
|
|
1545
|
+
switch (args.what) {
|
|
1546
|
+
case 'list': return success(await c.list({ status: args.status, solutionId: args.solution_id, limit: args.limit }));
|
|
1547
|
+
case 'campaign': return success(await c.get(need(args.campaign_id, 'campaign_id')));
|
|
1548
|
+
case 'templates': return success(await c.templates({ channel: args.channel, solutionId: args.solution_id, limit: args.limit }));
|
|
1549
|
+
case 'template': return success(await c.template(need(args.template_id, 'template_id'), Boolean(args.versions)));
|
|
1550
|
+
case 'template-skills': return success(await c.templateSkills(args.solution_id));
|
|
1551
|
+
default: return success(await c.detail(need(args.campaign_id, 'campaign_id'), args.what, args.days));
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
catch (err) {
|
|
1555
|
+
return error(err);
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
const platform = new PlatformSDK(client);
|
|
1559
|
+
const platformResource = z.enum(['agents', 'templates', 'components', 'connectors', 'connector-bindings', 'scenarios']);
|
|
1560
|
+
reg.tool('platform_list', 'List V2 platform items: agents, templates, components, connectors (shared catalogs), connector-bindings, scenarios (agent_id filters scenarios).', { resource: platformResource, agent_id: z.string().optional() }, async (args) => {
|
|
1561
|
+
try {
|
|
1562
|
+
return success(await platform.list(args.resource, args.agent_id ? { agent_id: args.agent_id } : undefined));
|
|
1563
|
+
}
|
|
1564
|
+
catch (err) {
|
|
1565
|
+
return error(err);
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
reg.tool('platform_get', 'One V2 platform item by id.', { resource: platformResource, id: z.string() }, async (args) => {
|
|
1569
|
+
try {
|
|
1570
|
+
return success(await platform.get(args.resource, args.id));
|
|
1571
|
+
}
|
|
1572
|
+
catch (err) {
|
|
1573
|
+
return error(err);
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
reg.tool('platform_save', 'Create (no id) or change (id) a V2 agent, template, connector binding or scenario. Components and connectors are read-only shared catalogs.', { resource: z.enum(['agents', 'templates', 'connector-bindings', 'scenarios']), id: z.string().optional(), body: z.record(z.unknown()) }, async (args) => {
|
|
1577
|
+
try {
|
|
1578
|
+
return success(args.id ? await platform.update(args.resource, args.id, args.body) : await platform.create(args.resource, args.body));
|
|
1579
|
+
}
|
|
1580
|
+
catch (err) {
|
|
1581
|
+
return error(err);
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
reg.tool('platform_delete', 'Delete a V2 agent, template, connector binding or scenario.', { resource: z.enum(['agents', 'templates', 'connector-bindings', 'scenarios']), id: z.string() }, async (args) => {
|
|
1585
|
+
try {
|
|
1586
|
+
return success(await platform.remove(args.resource, args.id));
|
|
1587
|
+
}
|
|
1588
|
+
catch (err) {
|
|
1589
|
+
return error(err);
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
reg.tool('validate_platform_template', 'Check a V2 template graph: components exist, edges point at real nodes.', { template_id: z.string() }, async (args) => {
|
|
1593
|
+
try {
|
|
1594
|
+
return success(await platform.validateTemplate(args.template_id));
|
|
1595
|
+
}
|
|
1596
|
+
catch (err) {
|
|
1597
|
+
return error(err);
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
reg.tool('run_simulation', 'Run scenarios against a V2 agent; poll simulation_status with the job id.', { agent_id: z.string(), scenario_ids: z.array(z.string()).optional() }, async (args) => {
|
|
1601
|
+
try {
|
|
1602
|
+
return success(await platform.runSimulation(args.agent_id, args.scenario_ids));
|
|
1603
|
+
}
|
|
1604
|
+
catch (err) {
|
|
1605
|
+
return error(err);
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
reg.tool('simulation_status', 'Simulation jobs (job_id for one) or results (result_id for one, or the latest of an agent).', { job_id: z.string().optional(), result_id: z.string().optional(), agent_id: z.string().optional() }, async (args) => {
|
|
1609
|
+
try {
|
|
1610
|
+
if (args.job_id)
|
|
1611
|
+
return success(await platform.simulationJob(args.job_id));
|
|
1612
|
+
if (args.result_id)
|
|
1613
|
+
return success(await platform.simulationResults({ resultId: args.result_id }));
|
|
1614
|
+
return success({ jobs: await platform.simulationJobs({ agentId: args.agent_id }), results: await platform.simulationResults({ agentId: args.agent_id }) });
|
|
1615
|
+
}
|
|
1616
|
+
catch (err) {
|
|
1617
|
+
return error(err);
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
reg.tool('org_evaluations', 'Which agents of the org have a baseline evaluation (rubric and test set), and which are stale.', {}, async () => {
|
|
1621
|
+
try {
|
|
1622
|
+
return success(await platform.orgEvaluations());
|
|
1623
|
+
}
|
|
1624
|
+
catch (err) {
|
|
1625
|
+
return error(err);
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
reg.tool('jarvis_create_job', 'Ask Jarvis to generate baseline evaluations for 1 to 50 agents of the org. Poll jarvis_jobs with the job id; per-agent failures are in progress.results.', { bot_ids: z.array(z.string()).min(1).max(50) }, async (args) => {
|
|
1629
|
+
try {
|
|
1630
|
+
return success(await platform.createJarvisJob(args.bot_ids));
|
|
1631
|
+
}
|
|
1632
|
+
catch (err) {
|
|
1633
|
+
return error(err);
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1636
|
+
reg.tool('jarvis_jobs', 'Jarvis jobs of the org, or one job (job_id).', { job_id: z.string().optional(), status: z.enum(['pending', 'running', 'completed', 'failed']).optional() }, async (args) => {
|
|
1637
|
+
try {
|
|
1638
|
+
return success(args.job_id ? await platform.jarvisJob(args.job_id) : await platform.jarvisJobs(args.status));
|
|
1639
|
+
}
|
|
1640
|
+
catch (err) {
|
|
1641
|
+
return error(err);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
reg.tool('jarvis_chat', 'One turn with Jarvis, the V2 platform assistant. Returns its reply, the tools it used, and a conversation_id to continue. agent_id tells Jarvis which V1 agent this is about.', { message: z.string(), conversation_id: z.string().optional(), agent_id: z.string().optional() }, async (args) => {
|
|
1645
|
+
try {
|
|
1646
|
+
return success(await jarvisChat(client, args.message, { conversationId: args.conversation_id, context: args.agent_id ? { federation_bot_id: args.agent_id } : undefined }));
|
|
1647
|
+
}
|
|
1648
|
+
catch (err) {
|
|
1649
|
+
return error(err);
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
reg.tool('chat_with_agent', 'Send one message to an agent and return its reply. Always a test conversation: never billed, never in analytics, shown with your name as tester. Pass the returned session_id to continue the same conversation.', {
|
|
1653
|
+
agent_id: z.string().describe('The agent ID to chat with'),
|
|
1654
|
+
message: z.string().describe('The message to send'),
|
|
1655
|
+
session_id: z.string().optional().describe('session_id from an earlier call, to continue that conversation'),
|
|
1656
|
+
end_conversation: z.boolean().optional().describe('End the conversation after this reply (default: keep it open so session_id can continue it)'),
|
|
1657
|
+
}, async (args) => {
|
|
1658
|
+
try {
|
|
1659
|
+
const chat = await openTestChat(client, args.agent_id, { sessionId: args.session_id });
|
|
1660
|
+
try {
|
|
1661
|
+
const { greeting, response } = await exchange(chat, args.message);
|
|
1662
|
+
return success({
|
|
1663
|
+
session_id: chat.sessionId,
|
|
1664
|
+
test_conversation: true,
|
|
1665
|
+
tester: chat.tester,
|
|
1666
|
+
url: chat.url,
|
|
1667
|
+
greeting: chat.resumed ? undefined : greeting,
|
|
1668
|
+
response,
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
finally {
|
|
1672
|
+
await chat.session.disconnect({ end: args.end_conversation === true });
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
catch (err) {
|
|
1676
|
+
return error(err);
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
// ---------------------------------------------------------------------------
|
|
1680
|
+
// Eval tools
|
|
1681
|
+
// ---------------------------------------------------------------------------
|
|
1682
|
+
const testSetsForRun = new TestSetsSDK(client);
|
|
1683
|
+
reg.tool('run_evaluation', 'Start an evaluation of an agent against its test set (paid model calls, cannot be cancelled; asks first). The agent must be in the org and the test set must belong to it. date_from/date_to grade real conversations (transcript mode). Poll get_eval_status; eval_gate_verdict judges a finished run.', {
|
|
1684
|
+
agent_id: z.string().optional().describe('The agent ID to evaluate'),
|
|
1685
|
+
bot_id: z.string().optional().describe('Same as agent_id (older name)'),
|
|
1686
|
+
test_set_id: z.string().describe('The test set ID (must belong to this agent)'),
|
|
1687
|
+
rubric_id: z.string().optional().describe('Optional rubric ID'),
|
|
1688
|
+
concurrency: z.number().int().min(1).max(10).optional().describe('Parallel cases (default 1)'),
|
|
1689
|
+
eval_model: z.string().optional().describe('Judge model'),
|
|
1690
|
+
limit: z.number().int().min(1).optional().describe('Only the first N items'),
|
|
1691
|
+
component_scope: z.string().optional(),
|
|
1692
|
+
component_ids: z.array(z.string()).optional(),
|
|
1693
|
+
conversation_ids: z.array(z.string()).optional().describe('Transcript mode: these conversations'),
|
|
1694
|
+
date_from: z.string().optional().describe('Transcript mode: conversations from this ISO date'),
|
|
1695
|
+
date_to: z.string().optional().describe('Transcript mode: up to this ISO date, exclusive'),
|
|
1696
|
+
mode: z.enum(['transcript']).optional(),
|
|
1697
|
+
allow_other_agent: z.boolean().optional().describe('Run a test set written for another agent'),
|
|
1698
|
+
}, async (args) => {
|
|
1699
|
+
try {
|
|
1700
|
+
const agentId = args.agent_id ?? args.bot_id;
|
|
1701
|
+
if (!agentId)
|
|
1702
|
+
throw new Error('Give agent_id: the agent to evaluate.');
|
|
1703
|
+
await evals.assertAgentInOrg(agentId);
|
|
1704
|
+
const ts = await testSetsForRun.getTestSet(args.test_set_id);
|
|
1705
|
+
if (ts.agent_id && ts.agent_id !== agentId && !args.allow_other_agent) {
|
|
1706
|
+
throw new Error(`Test set ${args.test_set_id} was written for agent ${ts.agent_id}, not ${agentId}. Pass allow_other_agent: true to run it anyway.`);
|
|
1707
|
+
}
|
|
1708
|
+
if (Boolean(args.date_from) !== Boolean(args.date_to))
|
|
1709
|
+
throw new Error('date_from and date_to go together.');
|
|
1710
|
+
const input = { bot_id: agentId, test_set_id: args.test_set_id };
|
|
1711
|
+
if (args.rubric_id)
|
|
1712
|
+
input.rubric_id = args.rubric_id;
|
|
1713
|
+
if (args.concurrency)
|
|
1714
|
+
input.concurrency = args.concurrency;
|
|
1715
|
+
// Voice simulations share call rooms; the dashboard runs them one at a time.
|
|
1716
|
+
if ((ts.items ?? []).some((i) => i.type === 'voice_simulation'))
|
|
1717
|
+
input.concurrency = 1;
|
|
1718
|
+
if (args.eval_model)
|
|
1719
|
+
input.eval_model = args.eval_model;
|
|
1720
|
+
if (args.limit)
|
|
1721
|
+
input.limit = args.limit;
|
|
1722
|
+
if (args.component_scope)
|
|
1723
|
+
input.component_scope = args.component_scope;
|
|
1724
|
+
if (args.component_ids)
|
|
1725
|
+
input.component_ids = args.component_ids;
|
|
1726
|
+
if (args.conversation_ids)
|
|
1727
|
+
input.conversation_ids = args.conversation_ids;
|
|
1728
|
+
if (args.mode)
|
|
1729
|
+
input.mode = args.mode;
|
|
1730
|
+
if (args.date_from) {
|
|
1731
|
+
input.date_from = args.date_from;
|
|
1732
|
+
input.date_to = args.date_to;
|
|
1733
|
+
input.mode = 'transcript';
|
|
1734
|
+
}
|
|
1735
|
+
return success(await evals.triggerEvaluation(input));
|
|
1736
|
+
}
|
|
1737
|
+
catch (err) {
|
|
1738
|
+
return error(err);
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
reg.tool('get_eval_status', 'Status and counts of an evaluation run (passed, failed, infrastructure errors, latency). While the run is still going it waits up to wait_seconds for it to finish, so call it again only if it is still running.', {
|
|
1742
|
+
run_id: z.string().describe('The evaluation run ID'),
|
|
1743
|
+
wait_seconds: z.number().int().min(0).max(60).optional().describe('How long to wait for a running run to finish (default 20; 0 = answer at once)'),
|
|
1744
|
+
}, async (args) => {
|
|
1745
|
+
try {
|
|
1746
|
+
// Waiting here instead of being called every few seconds: each call is a model turn for an agent.
|
|
1747
|
+
const deadline = Date.now() + 1000 * (args.wait_seconds ?? 20);
|
|
1748
|
+
let run = await evals.getRunInOrg(args.run_id);
|
|
1749
|
+
while (run.status !== 'completed' && run.status !== 'failed' && Date.now() < deadline) {
|
|
1750
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(5000, deadline - Date.now())));
|
|
1751
|
+
run = await evals.getRunStatus(args.run_id);
|
|
1752
|
+
}
|
|
1753
|
+
return success(run);
|
|
1754
|
+
}
|
|
1755
|
+
catch (err) {
|
|
1756
|
+
return error(err);
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
reg.tool('eval_gate_verdict', 'Judge a finished run like the CI gate: pass rate = passed/total, optional p95 limit, failed runs fail by default.', {
|
|
1760
|
+
run_id: z.string(),
|
|
1761
|
+
min_pass_rate: z.number().min(0).max(1).optional(),
|
|
1762
|
+
max_p95_ms: z.number().optional(),
|
|
1763
|
+
error_policy: z.enum(['block', 'warn']).optional(),
|
|
1764
|
+
fail_on_error: z.boolean().optional(),
|
|
1765
|
+
}, async (args) => {
|
|
1766
|
+
try {
|
|
1767
|
+
const run = await evals.getRunInOrg(args.run_id);
|
|
1768
|
+
if (run.status !== 'completed' && run.status !== 'failed')
|
|
1769
|
+
return success({ finished: false, status: run.status });
|
|
1770
|
+
return success({ finished: true, ...gateVerdict(run, { minPassRate: args.min_pass_rate, maxP95Ms: args.max_p95_ms, errorPolicy: args.error_policy, failOnError: args.fail_on_error }) });
|
|
1771
|
+
}
|
|
1772
|
+
catch (err) {
|
|
1773
|
+
return error(err);
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
reg.tool('get_eval_results', 'Per-case results of an evaluation run. response_format "concise" (default) keeps pass/fail, scores and failed criteria; "detailed" adds transcripts, tool calls and evidence.', {
|
|
1777
|
+
run_id: z.string().describe('The evaluation run ID'),
|
|
1778
|
+
response_format: z.enum(['concise', 'detailed']).optional(),
|
|
1779
|
+
failed_only: z.boolean().optional().describe('Only cases that did not pass'),
|
|
1780
|
+
}, async (args) => {
|
|
1781
|
+
try {
|
|
1782
|
+
await evals.getRunInOrg(args.run_id);
|
|
1783
|
+
let results = await evals.getRunResults(args.run_id);
|
|
1784
|
+
if (args.failed_only)
|
|
1785
|
+
results = results.filter((r) => r.passed !== true);
|
|
1786
|
+
if (args.response_format !== 'detailed') {
|
|
1787
|
+
results = results.map((r) => ({
|
|
1788
|
+
result_id: r.result_id, item_name: r.item_name, item_type: r.item_type, status: r.status, passed: r.passed,
|
|
1789
|
+
scores: r.scores, failed_criteria: Array.isArray(r.criteria_scores) ? r.criteria_scores.filter((c) => !c.passed) : undefined,
|
|
1790
|
+
failed_rules: Array.isArray(r.rubric_scores) ? r.rubric_scores.filter((c) => !c.passed) : undefined,
|
|
1791
|
+
error: r.error,
|
|
1792
|
+
}));
|
|
1793
|
+
}
|
|
1794
|
+
return success(results);
|
|
1795
|
+
}
|
|
1796
|
+
catch (err) {
|
|
1797
|
+
return error(err);
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
reg.tool('get_bot_context', 'Everything the evaluator knows about an agent: prompt, tools, knowledge bases, LLM config.', { bot_id: z.string().describe('The agent ID') }, async (args) => {
|
|
1801
|
+
try {
|
|
1802
|
+
await evals.assertAgentInOrg(args.bot_id);
|
|
1803
|
+
return success(await evals.getBotContext(args.bot_id));
|
|
1804
|
+
}
|
|
1805
|
+
catch (err) {
|
|
1806
|
+
return error(err);
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
reg.tool('eval_trends', 'Pass rate and latency over an agent\'s past runs, with drift when the pass rate dropped by 0.1 or more.', { agent_id: z.string(), test_set_id: z.string().optional(), limit: z.number().int().min(1).max(200).optional() }, async (args) => {
|
|
1810
|
+
try {
|
|
1811
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
1812
|
+
return success(await evals.trends(args.agent_id, args.test_set_id, args.limit));
|
|
1813
|
+
}
|
|
1814
|
+
catch (err) {
|
|
1815
|
+
return error(err);
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
reg.tool('compare_eval_runs', 'Pass rate and p95 latency difference between two runs.', { run_a: z.string(), run_b: z.string() }, async (args) => {
|
|
1819
|
+
try {
|
|
1820
|
+
await evals.getRunInOrg(args.run_a);
|
|
1821
|
+
await evals.getRunInOrg(args.run_b);
|
|
1822
|
+
return success(await evals.compareRuns(args.run_a, args.run_b));
|
|
1823
|
+
}
|
|
1824
|
+
catch (err) {
|
|
1825
|
+
return error(err);
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1828
|
+
reg.tool('eval_report_link', 'Have the evaluations service render the PDF report of a completed run; returns a link valid for 15 minutes.', { run_id: z.string(), force: z.boolean().optional().describe('Render again instead of reusing the last report') }, async (args) => {
|
|
1829
|
+
try {
|
|
1830
|
+
await evals.getRunInOrg(args.run_id);
|
|
1831
|
+
return success(await evals.exportReport(args.run_id, args.force));
|
|
1832
|
+
}
|
|
1833
|
+
catch (err) {
|
|
1834
|
+
return error(err);
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
reg.tool('eval_quality_metrics', 'Voice quality metrics a test item may require (names for expected.voice.required_metrics).', {}, async () => {
|
|
1838
|
+
try {
|
|
1839
|
+
return success(await evals.qualityMetrics());
|
|
1840
|
+
}
|
|
1841
|
+
catch (err) {
|
|
1842
|
+
return error(err);
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
reg.tool('list_prod_calls', 'Ingested production voice calls of an agent, newest first; search matches the transcript.', { agent_id: z.string(), search: z.string().optional(), limit: z.number().int().min(1).max(200).optional() }, async (args) => {
|
|
1846
|
+
try {
|
|
1847
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
1848
|
+
return success(await evals.prodCalls(args.agent_id, { search: args.search, limit: args.limit }));
|
|
1849
|
+
}
|
|
1850
|
+
catch (err) {
|
|
1851
|
+
return error(err);
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
reg.tool('promote_prod_call', 'Add an ingested production voice call as a test item to the agent\'s "Regressions (review)" test set.', { session_id: z.string() }, async (args) => {
|
|
1855
|
+
try {
|
|
1856
|
+
const call = await evals.prodCall(args.session_id);
|
|
1857
|
+
if (call.bot_id)
|
|
1858
|
+
await evals.assertAgentInOrg(String(call.bot_id));
|
|
1859
|
+
return success(await evals.promoteProdCall(args.session_id));
|
|
1860
|
+
}
|
|
1861
|
+
catch (err) {
|
|
1862
|
+
return error(err);
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
reg.tool('evaluate_transcript', 'Grade a replayed conversation against a baseline with the agent\'s stored evaluator (paid model calls).', {
|
|
1866
|
+
agent_id: z.string(),
|
|
1867
|
+
baseline_transcript: z.array(z.object({ sender: z.enum(['user', 'bot']), text: z.string() })),
|
|
1868
|
+
replay_transcript: z.array(z.object({ sender: z.enum(['user', 'bot']), text: z.string() })),
|
|
1869
|
+
}, async (args) => {
|
|
1870
|
+
try {
|
|
1871
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
1872
|
+
return success(await evals.evaluateTranscript(args));
|
|
1873
|
+
}
|
|
1874
|
+
catch (err) {
|
|
1875
|
+
return error(err);
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
reg.tool('list_evaluator_configs', 'Stored evaluator configs of an agent (which judges and rubric a run uses).', { agent_id: z.string() }, async (args) => {
|
|
1879
|
+
try {
|
|
1880
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
1881
|
+
return success(await evals.listEvaluatorConfigs(args.agent_id));
|
|
648
1882
|
}
|
|
649
1883
|
catch (err) {
|
|
650
1884
|
return error(err);
|
|
@@ -653,8 +1887,9 @@ export function registerTools(server, client) {
|
|
|
653
1887
|
// ---------------------------------------------------------------------------
|
|
654
1888
|
// Rubric tools
|
|
655
1889
|
// ---------------------------------------------------------------------------
|
|
656
|
-
|
|
1890
|
+
reg.tool('list_rubrics', 'Evaluation rubrics (behaviour rules an agent is graded on) of one agent.', { agent_id: z.string().describe('Agent ID (rubrics of other orgs are never listed)') }, async (args) => {
|
|
657
1891
|
try {
|
|
1892
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
658
1893
|
const result = await rubrics.listRubrics(args.agent_id);
|
|
659
1894
|
return success(result);
|
|
660
1895
|
}
|
|
@@ -662,30 +1897,32 @@ export function registerTools(server, client) {
|
|
|
662
1897
|
return error(err);
|
|
663
1898
|
}
|
|
664
1899
|
});
|
|
665
|
-
|
|
1900
|
+
reg.tool('get_rubric', 'One rubric with its rules.', { rubric_id: z.string().describe('The rubric ID') }, async (args) => {
|
|
666
1901
|
try {
|
|
667
|
-
const result = await
|
|
1902
|
+
const result = await rubricInOrg(args.rubric_id);
|
|
668
1903
|
return success(result);
|
|
669
1904
|
}
|
|
670
1905
|
catch (err) {
|
|
671
1906
|
return error(err);
|
|
672
1907
|
}
|
|
673
1908
|
});
|
|
674
|
-
|
|
1909
|
+
reg.tool('create_rubric', 'Create a rubric: 3 to 6 behaviour rules an agent must follow, each with a severity.', {
|
|
675
1910
|
name: z.string().describe('Rubric name'),
|
|
676
|
-
rules: z.array(
|
|
1911
|
+
rules: z.array(rubricRuleSchema).describe('The rubric rules'),
|
|
677
1912
|
agent_id: z.string().optional().describe('Agent ID to associate the rubric with'),
|
|
678
1913
|
description: z.string().optional().describe('Rubric description'),
|
|
679
1914
|
severity_definitions: z.record(z.string()).optional().describe('Severity level definitions'),
|
|
680
1915
|
}, async (args) => {
|
|
681
1916
|
try {
|
|
682
|
-
const input = { name: args.name, rules: args.rules };
|
|
1917
|
+
const input = { name: args.name, rules: args.rules.map(toRubricRule) };
|
|
683
1918
|
if (args.agent_id !== undefined)
|
|
684
1919
|
input.agent_id = args.agent_id;
|
|
685
1920
|
if (args.description !== undefined)
|
|
686
1921
|
input.description = args.description;
|
|
687
1922
|
if (args.severity_definitions !== undefined)
|
|
688
1923
|
input.severity_definitions = args.severity_definitions;
|
|
1924
|
+
if (args.agent_id)
|
|
1925
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
689
1926
|
const result = await rubrics.createRubric(input);
|
|
690
1927
|
return success(result);
|
|
691
1928
|
}
|
|
@@ -693,11 +1930,11 @@ export function registerTools(server, client) {
|
|
|
693
1930
|
return error(err);
|
|
694
1931
|
}
|
|
695
1932
|
});
|
|
696
|
-
|
|
1933
|
+
reg.tool('update_rubric', 'Change a rubric; this saves a new version.', {
|
|
697
1934
|
rubric_id: z.string().describe('The rubric ID'),
|
|
698
1935
|
name: z.string().optional().describe('Updated name'),
|
|
699
1936
|
description: z.string().optional().describe('Updated description'),
|
|
700
|
-
rules: z.array(
|
|
1937
|
+
rules: z.array(rubricRuleSchema).optional().describe('Updated rules'),
|
|
701
1938
|
}, async (args) => {
|
|
702
1939
|
try {
|
|
703
1940
|
const input = {};
|
|
@@ -706,7 +1943,8 @@ export function registerTools(server, client) {
|
|
|
706
1943
|
if (args.description !== undefined)
|
|
707
1944
|
input.description = args.description;
|
|
708
1945
|
if (args.rules !== undefined)
|
|
709
|
-
input.rules = args.rules;
|
|
1946
|
+
input.rules = args.rules.map(toRubricRule);
|
|
1947
|
+
await rubricInOrg(args.rubric_id);
|
|
710
1948
|
const result = await rubrics.updateRubric(args.rubric_id, input);
|
|
711
1949
|
return success(result);
|
|
712
1950
|
}
|
|
@@ -714,8 +1952,9 @@ export function registerTools(server, client) {
|
|
|
714
1952
|
return error(err);
|
|
715
1953
|
}
|
|
716
1954
|
});
|
|
717
|
-
|
|
1955
|
+
reg.tool('delete_rubric', 'Delete a rubric.', { rubric_id: z.string().describe('The rubric ID to delete') }, async (args) => {
|
|
718
1956
|
try {
|
|
1957
|
+
await rubricInOrg(args.rubric_id);
|
|
719
1958
|
await rubrics.deleteRubric(args.rubric_id);
|
|
720
1959
|
return success({ deleted: true, rubric_id: args.rubric_id });
|
|
721
1960
|
}
|
|
@@ -723,8 +1962,9 @@ export function registerTools(server, client) {
|
|
|
723
1962
|
return error(err);
|
|
724
1963
|
}
|
|
725
1964
|
});
|
|
726
|
-
|
|
1965
|
+
reg.tool('list_rubric_versions', 'Saved versions of a rubric.', { rubric_id: z.string().describe('The rubric ID') }, async (args) => {
|
|
727
1966
|
try {
|
|
1967
|
+
await rubricInOrg(args.rubric_id);
|
|
728
1968
|
const result = await rubrics.listVersions(args.rubric_id);
|
|
729
1969
|
return success(result);
|
|
730
1970
|
}
|
|
@@ -735,8 +1975,9 @@ export function registerTools(server, client) {
|
|
|
735
1975
|
// ---------------------------------------------------------------------------
|
|
736
1976
|
// Test set tools
|
|
737
1977
|
// ---------------------------------------------------------------------------
|
|
738
|
-
|
|
1978
|
+
reg.tool('list_test_sets', 'Evaluation test sets (scenarios an agent is tested on) of one agent.', { agent_id: z.string().describe('Agent ID (test sets of other orgs are never listed)') }, async (args) => {
|
|
739
1979
|
try {
|
|
1980
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
740
1981
|
const result = await testSets.listTestSets(args.agent_id);
|
|
741
1982
|
return success(result);
|
|
742
1983
|
}
|
|
@@ -744,27 +1985,29 @@ export function registerTools(server, client) {
|
|
|
744
1985
|
return error(err);
|
|
745
1986
|
}
|
|
746
1987
|
});
|
|
747
|
-
|
|
1988
|
+
reg.tool('get_test_set', 'One test set with its items (single turns, scenarios, voice simulations).', { test_set_id: z.string().describe('The test set ID') }, async (args) => {
|
|
748
1989
|
try {
|
|
749
|
-
const result = await
|
|
1990
|
+
const result = await testSetInOrg(args.test_set_id);
|
|
750
1991
|
return success(result);
|
|
751
1992
|
}
|
|
752
1993
|
catch (err) {
|
|
753
1994
|
return error(err);
|
|
754
1995
|
}
|
|
755
1996
|
});
|
|
756
|
-
|
|
1997
|
+
reg.tool('create_test_set', 'Create a test set for an agent: items with inputs and success criteria.', {
|
|
757
1998
|
name: z.string().describe('Test set name'),
|
|
758
|
-
items: z.array(
|
|
1999
|
+
items: z.array(testItemSchema).describe('The test items'),
|
|
759
2000
|
agent_id: z.string().optional().describe('Agent ID to associate the test set with'),
|
|
760
2001
|
description: z.string().optional().describe('Test set description'),
|
|
761
2002
|
}, async (args) => {
|
|
762
2003
|
try {
|
|
763
|
-
const input = { name: args.name, items: args.items };
|
|
2004
|
+
const input = { name: args.name, items: args.items.map(toTestItem) };
|
|
764
2005
|
if (args.agent_id !== undefined)
|
|
765
2006
|
input.agent_id = args.agent_id;
|
|
766
2007
|
if (args.description !== undefined)
|
|
767
2008
|
input.description = args.description;
|
|
2009
|
+
if (args.agent_id)
|
|
2010
|
+
await evals.assertAgentInOrg(args.agent_id);
|
|
768
2011
|
const result = await testSets.createTestSet(input);
|
|
769
2012
|
return success(result);
|
|
770
2013
|
}
|
|
@@ -772,11 +2015,11 @@ export function registerTools(server, client) {
|
|
|
772
2015
|
return error(err);
|
|
773
2016
|
}
|
|
774
2017
|
});
|
|
775
|
-
|
|
2018
|
+
reg.tool('update_test_set', 'Change a test set; this saves a new version.', {
|
|
776
2019
|
test_set_id: z.string().describe('The test set ID'),
|
|
777
2020
|
name: z.string().optional().describe('Updated name'),
|
|
778
2021
|
description: z.string().optional().describe('Updated description'),
|
|
779
|
-
items: z.array(
|
|
2022
|
+
items: z.array(testItemSchema).optional().describe('Updated test items'),
|
|
780
2023
|
}, async (args) => {
|
|
781
2024
|
try {
|
|
782
2025
|
const input = {};
|
|
@@ -785,7 +2028,8 @@ export function registerTools(server, client) {
|
|
|
785
2028
|
if (args.description !== undefined)
|
|
786
2029
|
input.description = args.description;
|
|
787
2030
|
if (args.items !== undefined)
|
|
788
|
-
input.items = args.items;
|
|
2031
|
+
input.items = args.items.map(toTestItem);
|
|
2032
|
+
await testSetInOrg(args.test_set_id);
|
|
789
2033
|
const result = await testSets.updateTestSet(args.test_set_id, input);
|
|
790
2034
|
return success(result);
|
|
791
2035
|
}
|
|
@@ -793,8 +2037,9 @@ export function registerTools(server, client) {
|
|
|
793
2037
|
return error(err);
|
|
794
2038
|
}
|
|
795
2039
|
});
|
|
796
|
-
|
|
2040
|
+
reg.tool('delete_test_set', 'Delete a test set.', { test_set_id: z.string().describe('The test set ID to delete') }, async (args) => {
|
|
797
2041
|
try {
|
|
2042
|
+
await testSetInOrg(args.test_set_id);
|
|
798
2043
|
await testSets.deleteTestSet(args.test_set_id);
|
|
799
2044
|
return success({ deleted: true, test_set_id: args.test_set_id });
|
|
800
2045
|
}
|
|
@@ -802,8 +2047,9 @@ export function registerTools(server, client) {
|
|
|
802
2047
|
return error(err);
|
|
803
2048
|
}
|
|
804
2049
|
});
|
|
805
|
-
|
|
2050
|
+
reg.tool('list_test_set_versions', 'Saved versions of a test set.', { test_set_id: z.string().describe('The test set ID') }, async (args) => {
|
|
806
2051
|
try {
|
|
2052
|
+
await testSetInOrg(args.test_set_id);
|
|
807
2053
|
const result = await testSets.listVersions(args.test_set_id);
|
|
808
2054
|
return success(result);
|
|
809
2055
|
}
|
|
@@ -814,7 +2060,7 @@ export function registerTools(server, client) {
|
|
|
814
2060
|
// ---------------------------------------------------------------------------
|
|
815
2061
|
// Export tools
|
|
816
2062
|
// ---------------------------------------------------------------------------
|
|
817
|
-
|
|
2063
|
+
reg.tool('export_eval_markdown', 'Export evaluation results as a structured markdown file with conversations, scores, and criteria', {
|
|
818
2064
|
run_id: z.string().describe('The evaluation run ID'),
|
|
819
2065
|
output_path: z.string().optional().describe('Output file path (defaults to current directory)'),
|
|
820
2066
|
}, async (args) => {
|
|
@@ -826,7 +2072,7 @@ export function registerTools(server, client) {
|
|
|
826
2072
|
return error(err);
|
|
827
2073
|
}
|
|
828
2074
|
});
|
|
829
|
-
|
|
2075
|
+
reg.tool('export_eval_report', 'Generate a branded PDF evaluation report with cover page, scores, and per-item details', {
|
|
830
2076
|
run_id: z.string().describe('The evaluation run ID'),
|
|
831
2077
|
output_path: z.string().optional().describe('Output file path (defaults to current directory)'),
|
|
832
2078
|
}, async (args) => {
|
|
@@ -838,7 +2084,7 @@ export function registerTools(server, client) {
|
|
|
838
2084
|
return error(err);
|
|
839
2085
|
}
|
|
840
2086
|
});
|
|
841
|
-
|
|
2087
|
+
reg.tool('export_agent_card', 'Generate a branded PDF agent card with system prompt, tools, KBs, LLM config, and evaluation history', {
|
|
842
2088
|
agent_id: z.string().describe('The agent ID'),
|
|
843
2089
|
output_path: z.string().optional().describe('Output file path (defaults to current directory)'),
|
|
844
2090
|
}, async (args) => {
|
|
@@ -850,5 +2096,6 @@ export function registerTools(server, client) {
|
|
|
850
2096
|
return error(err);
|
|
851
2097
|
}
|
|
852
2098
|
});
|
|
2099
|
+
return reg;
|
|
853
2100
|
}
|
|
854
2101
|
//# sourceMappingURL=tools.js.map
|