@revealui/ai 0.3.0 → 0.4.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/README.md +200 -26
- package/dist/a2a/handler.d.ts +3 -1
- package/dist/a2a/handler.d.ts.map +1 -1
- package/dist/a2a/handler.js +24 -7
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +5 -1
- package/dist/client/hooks/useAgentStream.d.ts +84 -1
- package/dist/client/hooks/useAgentStream.d.ts.map +1 -1
- package/dist/client/hooks/useAgentStream.js +106 -10
- package/dist/index.d.ts +4 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -15
- package/dist/llm/client.d.ts +33 -4
- package/dist/llm/client.d.ts.map +1 -1
- package/dist/llm/client.js +70 -21
- package/dist/llm/key-validator.d.ts.map +1 -1
- package/dist/llm/key-validator.js +0 -27
- package/dist/llm/providers/inference-snaps.d.ts +7 -5
- package/dist/llm/providers/inference-snaps.d.ts.map +1 -1
- package/dist/llm/providers/inference-snaps.js +7 -5
- package/dist/llm/providers/openai-compat.d.ts +1 -1
- package/dist/llm/providers/openai-compat.js +1 -1
- package/dist/llm/response-cache.d.ts +1 -2
- package/dist/llm/response-cache.d.ts.map +1 -1
- package/dist/llm/response-cache.js +1 -2
- package/dist/llm/server.d.ts +1 -1
- package/dist/llm/server.d.ts.map +1 -1
- package/dist/llm/server.js +3 -1
- package/dist/memory/services/index.d.ts +2 -0
- package/dist/memory/services/index.d.ts.map +1 -1
- package/dist/memory/services/index.js +1 -0
- package/dist/memory/services/reconciliation-service.d.ts +64 -0
- package/dist/memory/services/reconciliation-service.d.ts.map +1 -0
- package/dist/memory/services/reconciliation-service.js +127 -0
- package/dist/memory/utils/sql-helpers.d.ts +19 -25
- package/dist/memory/utils/sql-helpers.d.ts.map +1 -1
- package/dist/memory/utils/sql-helpers.js +34 -81
- package/dist/memory/vector/vector-memory-service.d.ts.map +1 -1
- package/dist/memory/vector/vector-memory-service.js +6 -1
- package/dist/orchestration/runtime.d.ts +30 -1
- package/dist/orchestration/runtime.d.ts.map +1 -1
- package/dist/orchestration/runtime.js +20 -3
- package/dist/orchestration/streaming-runtime.d.ts +48 -1
- package/dist/orchestration/streaming-runtime.d.ts.map +1 -1
- package/dist/orchestration/ticket-agent.d.ts +10 -1
- package/dist/orchestration/ticket-agent.d.ts.map +1 -1
- package/dist/orchestration/ticket-agent.js +11 -2
- package/dist/tools/mcp-adapter.d.ts +192 -1
- package/dist/tools/mcp-adapter.d.ts.map +1 -1
- package/dist/tools/mcp-adapter.js +434 -1
- package/dist/tools/mcp-elicitation.d.ts +128 -0
- package/dist/tools/mcp-elicitation.d.ts.map +1 -0
- package/dist/tools/mcp-elicitation.js +121 -0
- package/dist/tools/mcp-events.d.ts +270 -0
- package/dist/tools/mcp-events.d.ts.map +1 -0
- package/dist/tools/mcp-events.js +165 -0
- package/dist/tools/mcp-sampling.d.ts +155 -0
- package/dist/tools/mcp-sampling.d.ts.map +1 -0
- package/dist/tools/mcp-sampling.js +171 -0
- package/dist/tools/ticket-tools.d.ts +35 -2
- package/dist/tools/ticket-tools.d.ts.map +1 -1
- package/dist/tools/ticket-tools.js +32 -2
- package/package.json +13 -11
- package/dist/llm/providers/vultr.d.ts +0 -18
- package/dist/llm/providers/vultr.d.ts.map +0 -1
- package/dist/llm/providers/vultr.js +0 -169
- package/dist/memory/utils/deep-clone.d.ts +0 -10
- package/dist/memory/utils/deep-clone.d.ts.map +0 -1
- package/dist/memory/utils/deep-clone.js +0 -9
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP protocol log channel — structured events per server-to-client call
|
|
3
|
+
* (Stage 6.1 of the MCP v1 plan).
|
|
4
|
+
*
|
|
5
|
+
* Stage 5 shipped the full agent-side MCP protocol surface
|
|
6
|
+
* (tools + resources + prompts + sampling + elicitation + progress +
|
|
7
|
+
* cancellation). Stage 6.1 adds observability: every adapter in
|
|
8
|
+
* `@revealui/ai/tools/mcp-*` grows an `onEvent?: McpEventSink` hook that
|
|
9
|
+
* fires once per protocol call with a structured summary. Consumers wire
|
|
10
|
+
* the sink to whatever aggregator they run — typically
|
|
11
|
+
* `@revealui/core/observability/logger` via `createCoreLoggerSink()`.
|
|
12
|
+
*
|
|
13
|
+
* Instrumentation lives at the agent-adapter layer by design. The
|
|
14
|
+
* hypervisor is a separate lane (Stage 6.2 lands `usage_meters` rows
|
|
15
|
+
* there, where `accountId` is in scope). Agent adapters are intentionally
|
|
16
|
+
* tenant-agnostic, so events carry no `accountId` field — the consumer
|
|
17
|
+
* adds tenant context in their sink wrapper if they need it for routing.
|
|
18
|
+
*
|
|
19
|
+
* PII note
|
|
20
|
+
* --------
|
|
21
|
+
* Errors surfaced by the server can legitimately contain user-identifying
|
|
22
|
+
* strings (e.g. "user 'alice@example.com' not found"). `error` is passed
|
|
23
|
+
* through to the sink verbatim. If your deployment's observability layer
|
|
24
|
+
* is subject to PII constraints, wrap the sink with a redactor
|
|
25
|
+
* (`redactLogContext` from `@revealui/security`) before passing it in.
|
|
26
|
+
*
|
|
27
|
+
* Params / args / content bodies are **not** included in events —
|
|
28
|
+
* observability is summary-grained by default to avoid accidentally
|
|
29
|
+
* logging credentials, prompts, or document contents.
|
|
30
|
+
*/
|
|
31
|
+
import { logger } from '@revealui/core/observability/logger';
|
|
32
|
+
/**
|
|
33
|
+
* Build an `McpEventSink` that fans events into the central log
|
|
34
|
+
* aggregator via `@revealui/core/observability/logger`. This is the
|
|
35
|
+
* recommended default for in-process consumers (admin app, API, agent
|
|
36
|
+
* orchestration). Out-of-process consumers (CLI, remote inspector)
|
|
37
|
+
* typically construct a custom sink that forwards to their own transport.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import {
|
|
42
|
+
* createToolsFromMcpClient,
|
|
43
|
+
* createCoreLoggerSink,
|
|
44
|
+
* } from '@revealui/ai';
|
|
45
|
+
*
|
|
46
|
+
* const onEvent = createCoreLoggerSink();
|
|
47
|
+
*
|
|
48
|
+
* const tools = await createToolsFromMcpClient(client, {
|
|
49
|
+
* namespace: 'my-server',
|
|
50
|
+
* onEvent,
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createCoreLoggerSink(options = {}) {
|
|
55
|
+
const successLevel = options.successLevel ?? 'info';
|
|
56
|
+
return (event) => {
|
|
57
|
+
const { kind, ...rest } = event;
|
|
58
|
+
const payload = { event: kind, ...rest };
|
|
59
|
+
if (!event.success) {
|
|
60
|
+
logger.warn(`[mcp] ${kind}`, payload);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (successLevel === 'debug') {
|
|
64
|
+
logger.debug(`[mcp] ${kind}`, payload);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
logger.info(`[mcp] ${kind}`, payload);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Internal helper — shared across mcp-adapter / mcp-sampling / mcp-elicitation
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
/**
|
|
75
|
+
* Invoke a sink, swallowing any thrown error. A misbehaving sink must
|
|
76
|
+
* never break the underlying MCP call — observability is best-effort.
|
|
77
|
+
* Logs the sink failure at warn so the regression is still visible.
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export function emitMcpEvent(sink, event) {
|
|
82
|
+
if (!sink)
|
|
83
|
+
return;
|
|
84
|
+
try {
|
|
85
|
+
sink(event);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
logger.warn('[mcp] event sink threw; continuing', {
|
|
89
|
+
event: event.kind,
|
|
90
|
+
error: error instanceof Error ? error.message : String(error),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Map an `McpLogEvent.kind` to a `usage_meters.meterName` string.
|
|
96
|
+
* One-to-one: the dot-notation kind IS the meter name.
|
|
97
|
+
*
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
const METER_NAMES = {
|
|
101
|
+
'mcp.tool.call': 'mcp.tool.call',
|
|
102
|
+
'mcp.resource.list': 'mcp.resource.list',
|
|
103
|
+
'mcp.resource.read': 'mcp.resource.read',
|
|
104
|
+
'mcp.prompt.list': 'mcp.prompt.list',
|
|
105
|
+
'mcp.prompt.get': 'mcp.prompt.get',
|
|
106
|
+
'mcp.sampling.create': 'mcp.sampling.create',
|
|
107
|
+
'mcp.elicitation.create': 'mcp.elicitation.create',
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Build an `McpEventSink` that translates Stage 6.1 protocol log
|
|
111
|
+
* events into `usage_meters` insert rows (Stage 6.2).
|
|
112
|
+
*
|
|
113
|
+
* This is the agent-adapter-lane counterpart to the hypervisor's
|
|
114
|
+
* `setUsageMeterSink()` in `@revealui/mcp`. Both paths land rows in
|
|
115
|
+
* the same `usage_meters` table. The agent-adapter path covers the
|
|
116
|
+
* four boundaries the hypervisor doesn't own
|
|
117
|
+
* (`resource.list/read`, `prompt.list/get`, `sampling.create`,
|
|
118
|
+
* `elicitation.create`) plus the agent-side `tool.call` for
|
|
119
|
+
* `McpClient`-using agents.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* import { usageMeters } from '@revealui/db';
|
|
124
|
+
* import {
|
|
125
|
+
* createToolsFromMcpClient,
|
|
126
|
+
* createUsageMeterSink,
|
|
127
|
+
* } from '@revealui/ai';
|
|
128
|
+
*
|
|
129
|
+
* const tools = await createToolsFromMcpClient(client, {
|
|
130
|
+
* namespace: 'content',
|
|
131
|
+
* onEvent: createUsageMeterSink({
|
|
132
|
+
* accountId: session.accountId,
|
|
133
|
+
* write: (row) => db.insert(usageMeters).values(row),
|
|
134
|
+
* }),
|
|
135
|
+
* });
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export function createUsageMeterSink(options) {
|
|
139
|
+
const source = options.source ?? 'agent';
|
|
140
|
+
const genId = options.id ?? (() => crypto.randomUUID());
|
|
141
|
+
const genKey = options.idempotencyKey ?? (() => crypto.randomUUID());
|
|
142
|
+
return (event) => {
|
|
143
|
+
const row = {
|
|
144
|
+
id: genId(event),
|
|
145
|
+
accountId: options.accountId,
|
|
146
|
+
meterName: METER_NAMES[event.kind],
|
|
147
|
+
quantity: 1,
|
|
148
|
+
periodStart: new Date(),
|
|
149
|
+
periodEnd: null,
|
|
150
|
+
source,
|
|
151
|
+
idempotencyKey: genKey(event),
|
|
152
|
+
durationMs: event.duration_ms,
|
|
153
|
+
errored: !event.success,
|
|
154
|
+
};
|
|
155
|
+
const result = options.write(row);
|
|
156
|
+
if (result && typeof result.catch === 'function') {
|
|
157
|
+
result.catch((error) => {
|
|
158
|
+
logger.warn('[mcp] usage meter write rejected; continuing', {
|
|
159
|
+
event: event.kind,
|
|
160
|
+
error: error instanceof Error ? error.message : String(error),
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Sampling — route server `sampling/createMessage` requests through
|
|
3
|
+
* the agent's configured LLM provider (Stage 5.2 of the MCP v1 plan).
|
|
4
|
+
*
|
|
5
|
+
* The MCP spec defines `sampling/createMessage` as a server-to-client
|
|
6
|
+
* request: an MCP server asks the client to invoke an LLM on its behalf.
|
|
7
|
+
* The client decides which model runs, keeps full control over costs +
|
|
8
|
+
* context, and the server gets LLM capabilities without bundling a
|
|
9
|
+
* provider. On RevealUI's Ubuntu reference stack, the target is a local
|
|
10
|
+
* Canonical Inference Snap — so sampling traffic stays on-device.
|
|
11
|
+
*
|
|
12
|
+
* This module exposes `createSamplingHandler()` — a factory that wraps
|
|
13
|
+
* the agent's `LLMProvider` (or `LLMClient`) into a handler of the
|
|
14
|
+
* structural shape `McpSamplingHandler`. Consumers pass the resulting
|
|
15
|
+
* handler to `McpClient` at construction time:
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { McpClient } from '@revealui/mcp/client';
|
|
20
|
+
* import { InferenceSnapsProvider, createSamplingHandler } from '@revealui/ai';
|
|
21
|
+
*
|
|
22
|
+
* const llm = new InferenceSnapsProvider({
|
|
23
|
+
* baseURL: 'http://localhost:9090/v1',
|
|
24
|
+
* model: 'gemma3',
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* const client = new McpClient({
|
|
28
|
+
* clientInfo: { name: 'my-agent', version: '1.0.0' },
|
|
29
|
+
* transport: { kind: 'streamable-http', url: 'https://example.com/mcp' },
|
|
30
|
+
* samplingHandler: createSamplingHandler({
|
|
31
|
+
* llm,
|
|
32
|
+
* defaultModel: 'gemma3',
|
|
33
|
+
* allowedModels: ['gemma3', 'deepseek-r1'],
|
|
34
|
+
* }),
|
|
35
|
+
* });
|
|
36
|
+
* await client.connect();
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* As with the rest of the MCP-adapter surface, this module uses
|
|
40
|
+
* structural typing to stay decoupled from `@revealui/mcp` at the
|
|
41
|
+
* package level — no runtime import of the SDK. The real
|
|
42
|
+
* `SamplingHandler` from `@revealui/mcp/client` structurally satisfies
|
|
43
|
+
* the `McpSamplingHandler` shape exported here.
|
|
44
|
+
*/
|
|
45
|
+
import type { LLMChatOptions, LLMResponse, Message } from '../llm/providers/base.js';
|
|
46
|
+
import { type McpEventSink } from './mcp-events.js';
|
|
47
|
+
/**
|
|
48
|
+
* Minimal shape needed from an LLM to serve sampling requests. Both
|
|
49
|
+
* `@revealui/ai`'s `LLMProvider` and `LLMClient` structurally satisfy this
|
|
50
|
+
* — consumers pass whichever they have. Decoupled via structural typing
|
|
51
|
+
* so test code can pass an even smaller stub.
|
|
52
|
+
*/
|
|
53
|
+
export interface SamplingLLM {
|
|
54
|
+
chat(messages: Message[], options?: LLMChatOptions): Promise<LLMResponse>;
|
|
55
|
+
}
|
|
56
|
+
/** One message in a `sampling/createMessage` request. */
|
|
57
|
+
export interface McpSamplingMessage {
|
|
58
|
+
role: 'user' | 'assistant';
|
|
59
|
+
content: {
|
|
60
|
+
type: string;
|
|
61
|
+
text?: string;
|
|
62
|
+
data?: string;
|
|
63
|
+
mimeType?: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Model preference hint (spec: `ModelHint`). */
|
|
67
|
+
export interface McpModelHint {
|
|
68
|
+
name?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Parameters of a `sampling/createMessage` request (spec-shaped subset). */
|
|
71
|
+
export interface McpSamplingRequestParams {
|
|
72
|
+
messages: ReadonlyArray<McpSamplingMessage>;
|
|
73
|
+
modelPreferences?: {
|
|
74
|
+
hints?: ReadonlyArray<McpModelHint>;
|
|
75
|
+
costPriority?: number;
|
|
76
|
+
speedPriority?: number;
|
|
77
|
+
intelligencePriority?: number;
|
|
78
|
+
};
|
|
79
|
+
systemPrompt?: string;
|
|
80
|
+
includeContext?: 'none' | 'thisServer' | 'allServers';
|
|
81
|
+
temperature?: number;
|
|
82
|
+
maxTokens: number;
|
|
83
|
+
stopSequences?: ReadonlyArray<string>;
|
|
84
|
+
metadata?: Record<string, unknown>;
|
|
85
|
+
}
|
|
86
|
+
/** Result of a `sampling/createMessage` request (spec-shaped subset). */
|
|
87
|
+
export interface McpSamplingResult {
|
|
88
|
+
model: string;
|
|
89
|
+
stopReason?: 'maxTokens' | 'endTurn' | 'stopSequence' | string;
|
|
90
|
+
role: 'user' | 'assistant';
|
|
91
|
+
content: {
|
|
92
|
+
type: 'text';
|
|
93
|
+
text: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Structural shape of the handler — matches `SamplingHandler` from `@revealui/mcp/client`. */
|
|
97
|
+
export type McpSamplingHandler = (params: McpSamplingRequestParams) => Promise<McpSamplingResult>;
|
|
98
|
+
export interface CreateSamplingHandlerOptions {
|
|
99
|
+
/** LLM to route sampling requests through. */
|
|
100
|
+
llm: SamplingLLM;
|
|
101
|
+
/**
|
|
102
|
+
* Allowlist of models that servers may request via `modelPreferences.hints`.
|
|
103
|
+
* Hint names outside the allowlist are filtered out of selection. When
|
|
104
|
+
* unset, every hint is accepted.
|
|
105
|
+
*
|
|
106
|
+
* **Advisory, not enforced.** `@revealui/ai`'s current `LLMProvider`
|
|
107
|
+
* shape fixes the model at provider construction — this handler does
|
|
108
|
+
* NOT re-route per-request to different providers. The resolved model
|
|
109
|
+
* is reported back to the server in `result.model` (so the server
|
|
110
|
+
* knows what actually ran) but the call always goes to `options.llm`.
|
|
111
|
+
* For per-model routing, the consumer wires their own multiplexer
|
|
112
|
+
* (e.g. via `selectModel` + an `LLMClient` that internally dispatches).
|
|
113
|
+
*/
|
|
114
|
+
allowedModels?: ReadonlyArray<string>;
|
|
115
|
+
/**
|
|
116
|
+
* Label used for `result.model` when no hint matches (or no hints).
|
|
117
|
+
* Purely for reporting. When omitted, `'unknown'` is reported.
|
|
118
|
+
*/
|
|
119
|
+
defaultModel?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Custom model selector. Overrides the default hint-matching logic.
|
|
122
|
+
* Return `undefined` to fall back to `defaultModel`.
|
|
123
|
+
*/
|
|
124
|
+
selectModel?: (hints: ReadonlyArray<McpModelHint>, options: {
|
|
125
|
+
allowedModels?: ReadonlyArray<string>;
|
|
126
|
+
defaultModel?: string;
|
|
127
|
+
}) => string | undefined;
|
|
128
|
+
/**
|
|
129
|
+
* Observability hook invoked before each sampling call. Useful for
|
|
130
|
+
* metering / audit trails / cost tracking.
|
|
131
|
+
*/
|
|
132
|
+
onSamplingRequest?: (info: {
|
|
133
|
+
model: string;
|
|
134
|
+
messageCount: number;
|
|
135
|
+
maxTokens: number;
|
|
136
|
+
systemPrompt?: string;
|
|
137
|
+
}) => void;
|
|
138
|
+
/**
|
|
139
|
+
* Protocol-level observability sink (Stage 6.1). Fires once per
|
|
140
|
+
* `sampling/createMessage` call (after the LLM responds, or on
|
|
141
|
+
* failure) with `{ kind: 'mcp.sampling.create', model, messageCount,
|
|
142
|
+
* maxTokens, duration_ms, success, error? }`. Set `namespace` when
|
|
143
|
+
* the handler is attached to a single-server `McpClient` so events
|
|
144
|
+
* can be grouped in the aggregator.
|
|
145
|
+
*/
|
|
146
|
+
onEvent?: McpEventSink;
|
|
147
|
+
/**
|
|
148
|
+
* Optional server identifier included in emitted events. Leave unset
|
|
149
|
+
* when the handler is shared across multiple servers — consumer's
|
|
150
|
+
* sink wrapper can fill in namespace from call-site context.
|
|
151
|
+
*/
|
|
152
|
+
namespace?: string;
|
|
153
|
+
}
|
|
154
|
+
export declare function createSamplingHandler(options: CreateSamplingHandlerOptions): McpSamplingHandler;
|
|
155
|
+
//# sourceMappingURL=mcp-sampling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-sampling.d.ts","sourceRoot":"","sources":["../../src/tools/mcp-sampling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Sampling — route server `sampling/createMessage` requests through
|
|
3
|
+
* the agent's configured LLM provider (Stage 5.2 of the MCP v1 plan).
|
|
4
|
+
*
|
|
5
|
+
* The MCP spec defines `sampling/createMessage` as a server-to-client
|
|
6
|
+
* request: an MCP server asks the client to invoke an LLM on its behalf.
|
|
7
|
+
* The client decides which model runs, keeps full control over costs +
|
|
8
|
+
* context, and the server gets LLM capabilities without bundling a
|
|
9
|
+
* provider. On RevealUI's Ubuntu reference stack, the target is a local
|
|
10
|
+
* Canonical Inference Snap — so sampling traffic stays on-device.
|
|
11
|
+
*
|
|
12
|
+
* This module exposes `createSamplingHandler()` — a factory that wraps
|
|
13
|
+
* the agent's `LLMProvider` (or `LLMClient`) into a handler of the
|
|
14
|
+
* structural shape `McpSamplingHandler`. Consumers pass the resulting
|
|
15
|
+
* handler to `McpClient` at construction time:
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { McpClient } from '@revealui/mcp/client';
|
|
20
|
+
* import { InferenceSnapsProvider, createSamplingHandler } from '@revealui/ai';
|
|
21
|
+
*
|
|
22
|
+
* const llm = new InferenceSnapsProvider({
|
|
23
|
+
* baseURL: 'http://localhost:9090/v1',
|
|
24
|
+
* model: 'gemma3',
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* const client = new McpClient({
|
|
28
|
+
* clientInfo: { name: 'my-agent', version: '1.0.0' },
|
|
29
|
+
* transport: { kind: 'streamable-http', url: 'https://example.com/mcp' },
|
|
30
|
+
* samplingHandler: createSamplingHandler({
|
|
31
|
+
* llm,
|
|
32
|
+
* defaultModel: 'gemma3',
|
|
33
|
+
* allowedModels: ['gemma3', 'deepseek-r1'],
|
|
34
|
+
* }),
|
|
35
|
+
* });
|
|
36
|
+
* await client.connect();
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* As with the rest of the MCP-adapter surface, this module uses
|
|
40
|
+
* structural typing to stay decoupled from `@revealui/mcp` at the
|
|
41
|
+
* package level — no runtime import of the SDK. The real
|
|
42
|
+
* `SamplingHandler` from `@revealui/mcp/client` structurally satisfies
|
|
43
|
+
* the `McpSamplingHandler` shape exported here.
|
|
44
|
+
*/
|
|
45
|
+
import { emitMcpEvent } from './mcp-events.js';
|
|
46
|
+
export function createSamplingHandler(options) {
|
|
47
|
+
const { llm, allowedModels, defaultModel, selectModel, onSamplingRequest, onEvent, namespace } = options;
|
|
48
|
+
return async (params) => {
|
|
49
|
+
const model = resolveModel(params.modelPreferences?.hints, {
|
|
50
|
+
allowedModels,
|
|
51
|
+
defaultModel,
|
|
52
|
+
selectModel,
|
|
53
|
+
});
|
|
54
|
+
const messages = convertToLLMMessages(params);
|
|
55
|
+
const reportedModel = model ?? 'unknown';
|
|
56
|
+
onSamplingRequest?.({
|
|
57
|
+
model: reportedModel,
|
|
58
|
+
messageCount: params.messages.length,
|
|
59
|
+
maxTokens: params.maxTokens,
|
|
60
|
+
...(params.systemPrompt !== undefined ? { systemPrompt: params.systemPrompt } : {}),
|
|
61
|
+
});
|
|
62
|
+
// `stopSequences` is NOT passed — the current `LLMChatOptions` doesn't
|
|
63
|
+
// expose it (per-provider inconsistency). The MCP spec treats
|
|
64
|
+
// stopSequences as advisory, so omitting is compliant. A future
|
|
65
|
+
// provider-interface extension can wire this through.
|
|
66
|
+
const chatOptions = {
|
|
67
|
+
maxTokens: params.maxTokens,
|
|
68
|
+
...(params.temperature !== undefined ? { temperature: params.temperature } : {}),
|
|
69
|
+
};
|
|
70
|
+
const started = Date.now();
|
|
71
|
+
try {
|
|
72
|
+
const response = await llm.chat(messages, chatOptions);
|
|
73
|
+
emitMcpEvent(onEvent, {
|
|
74
|
+
kind: 'mcp.sampling.create',
|
|
75
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
76
|
+
model: reportedModel,
|
|
77
|
+
messageCount: params.messages.length,
|
|
78
|
+
maxTokens: params.maxTokens,
|
|
79
|
+
duration_ms: Date.now() - started,
|
|
80
|
+
success: true,
|
|
81
|
+
});
|
|
82
|
+
const stopReason = mapFinishReason(response.finishReason);
|
|
83
|
+
const result = {
|
|
84
|
+
model: reportedModel,
|
|
85
|
+
role: 'assistant',
|
|
86
|
+
content: {
|
|
87
|
+
type: 'text',
|
|
88
|
+
text: response.content,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
if (stopReason !== undefined) {
|
|
92
|
+
result.stopReason = stopReason;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
emitMcpEvent(onEvent, {
|
|
98
|
+
kind: 'mcp.sampling.create',
|
|
99
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
100
|
+
model: reportedModel,
|
|
101
|
+
messageCount: params.messages.length,
|
|
102
|
+
maxTokens: params.maxTokens,
|
|
103
|
+
duration_ms: Date.now() - started,
|
|
104
|
+
success: false,
|
|
105
|
+
error: error instanceof Error ? error.message : String(error),
|
|
106
|
+
});
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Helpers
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
function resolveModel(hints, options) {
|
|
115
|
+
// Custom selector, when present, is the final say — no hint-matching
|
|
116
|
+
// fallback. Returning undefined means "I don't have a pick; use the
|
|
117
|
+
// default model label."
|
|
118
|
+
if (options.selectModel) {
|
|
119
|
+
const chosen = options.selectModel(hints ?? [], {
|
|
120
|
+
...(options.allowedModels !== undefined ? { allowedModels: options.allowedModels } : {}),
|
|
121
|
+
...(options.defaultModel !== undefined ? { defaultModel: options.defaultModel } : {}),
|
|
122
|
+
});
|
|
123
|
+
return chosen ?? options.defaultModel;
|
|
124
|
+
}
|
|
125
|
+
// Default logic: first hint whose name is in the allowlist (or any hint if
|
|
126
|
+
// no allowlist). Unknown / non-matching hints are ignored. The MCP spec
|
|
127
|
+
// treats hints as advisory only, so rejecting all of them and falling
|
|
128
|
+
// through to `defaultModel` is compliant.
|
|
129
|
+
if (hints && hints.length > 0) {
|
|
130
|
+
for (const hint of hints) {
|
|
131
|
+
if (!hint.name)
|
|
132
|
+
continue;
|
|
133
|
+
if (options.allowedModels && !options.allowedModels.includes(hint.name))
|
|
134
|
+
continue;
|
|
135
|
+
return hint.name;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return options.defaultModel;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Convert the MCP `sampling/createMessage` message array (and optional
|
|
142
|
+
* `systemPrompt`) into the agent's `Message[]` shape. Text content is
|
|
143
|
+
* passed through verbatim; non-text parts throw so the server gets a
|
|
144
|
+
* clear protocol error rather than a silently-dropped payload.
|
|
145
|
+
*/
|
|
146
|
+
function convertToLLMMessages(params) {
|
|
147
|
+
const out = [];
|
|
148
|
+
if (typeof params.systemPrompt === 'string' && params.systemPrompt.length > 0) {
|
|
149
|
+
out.push({ role: 'system', content: params.systemPrompt });
|
|
150
|
+
}
|
|
151
|
+
for (const msg of params.messages) {
|
|
152
|
+
if (msg.content.type !== 'text' || typeof msg.content.text !== 'string') {
|
|
153
|
+
throw new Error(`sampling/createMessage: non-text message content is not yet supported (got type=${msg.content.type}). Stage 5.2 ships text-only sampling.`);
|
|
154
|
+
}
|
|
155
|
+
out.push({ role: msg.role, content: msg.content.text });
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
function mapFinishReason(reason) {
|
|
160
|
+
switch (reason) {
|
|
161
|
+
case 'stop':
|
|
162
|
+
return 'endTurn';
|
|
163
|
+
case 'length':
|
|
164
|
+
return 'maxTokens';
|
|
165
|
+
case 'tool_calls':
|
|
166
|
+
case 'content_filter':
|
|
167
|
+
return reason;
|
|
168
|
+
default:
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* These are injected by createTicketTools() and are always paired with
|
|
9
9
|
* admin tools so agents can act on content AND report back through the ticket.
|
|
10
|
+
*
|
|
11
|
+
* Retry-safe ids (CR8-P2-01 phase C prerequisite): when a `dispatchId`
|
|
12
|
+
* is provided, comment ids are derived deterministically from
|
|
13
|
+
* (dispatchId, call-ordinal). A crash-and-resume of the same dispatch
|
|
14
|
+
* re-issues tool calls in the same order and generates the same ids,
|
|
15
|
+
* letting the persistence layer's primary-key constraint dedupe
|
|
16
|
+
* naturally. When `dispatchId` is omitted, ids remain random (the
|
|
17
|
+
* caller's `TicketMutationClient` picks its own id).
|
|
10
18
|
*/
|
|
11
19
|
import type { Tool } from './base.js';
|
|
12
20
|
/**
|
|
@@ -22,10 +30,35 @@ export interface TicketMutationClient {
|
|
|
22
30
|
id: string;
|
|
23
31
|
status: string;
|
|
24
32
|
} | null>;
|
|
25
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Create a comment on a ticket. When `options.id` is provided the
|
|
35
|
+
* implementation MUST use that id (enables deterministic replay under
|
|
36
|
+
* retry); when omitted the implementation picks its own id (typically
|
|
37
|
+
* a random UUID for backward compatibility).
|
|
38
|
+
*/
|
|
39
|
+
createComment(ticketId: string, body: Record<string, unknown>, options?: {
|
|
40
|
+
id?: string;
|
|
41
|
+
}): Promise<{
|
|
26
42
|
id: string;
|
|
27
43
|
ticketId: string;
|
|
28
44
|
} | null | undefined>;
|
|
29
45
|
}
|
|
30
|
-
export
|
|
46
|
+
export interface CreateTicketToolsOptions {
|
|
47
|
+
/**
|
|
48
|
+
* When set, tool calls that write rows (e.g. add_ticket_comment) use
|
|
49
|
+
* this as the seed for deterministic id derivation. Pair with the
|
|
50
|
+
* durable work queue's jobId so crash-resume produces the same ids
|
|
51
|
+
* and the DB's PK constraint dedupes duplicate attempts.
|
|
52
|
+
*
|
|
53
|
+
* When omitted, ids remain random (existing behavior).
|
|
54
|
+
*/
|
|
55
|
+
dispatchId?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Derive a deterministic, URL-safe comment id from (dispatchId,
|
|
59
|
+
* callOrdinal). Exported so the handler side can assert that the same
|
|
60
|
+
* inputs produce the same id (e.g. during crash-sim tests).
|
|
61
|
+
*/
|
|
62
|
+
export declare function deriveCommentId(dispatchId: string, callOrdinal: number): string;
|
|
63
|
+
export declare function createTicketTools(ticketId: string, client: TicketMutationClient, options?: CreateTicketToolsOptions): Tool[];
|
|
31
64
|
//# sourceMappingURL=ticket-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticket-tools.d.ts","sourceRoot":"","sources":["../../src/tools/ticket-tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ticket-tools.d.ts","sourceRoot":"","sources":["../../src/tools/ticket-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,WAAW,CAAC;AAElD;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,CACV,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GACA,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAElD;;;;;OAKG;IACH,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GACxB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/E;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE,wBAA6B,GACrC,IAAI,EAAE,CAgFR"}
|
|
@@ -7,9 +7,35 @@
|
|
|
7
7
|
*
|
|
8
8
|
* These are injected by createTicketTools() and are always paired with
|
|
9
9
|
* admin tools so agents can act on content AND report back through the ticket.
|
|
10
|
+
*
|
|
11
|
+
* Retry-safe ids (CR8-P2-01 phase C prerequisite): when a `dispatchId`
|
|
12
|
+
* is provided, comment ids are derived deterministically from
|
|
13
|
+
* (dispatchId, call-ordinal). A crash-and-resume of the same dispatch
|
|
14
|
+
* re-issues tool calls in the same order and generates the same ids,
|
|
15
|
+
* letting the persistence layer's primary-key constraint dedupe
|
|
16
|
+
* naturally. When `dispatchId` is omitted, ids remain random (the
|
|
17
|
+
* caller's `TicketMutationClient` picks its own id).
|
|
10
18
|
*/
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
11
20
|
import { z } from 'zod/v4';
|
|
12
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Derive a deterministic, URL-safe comment id from (dispatchId,
|
|
23
|
+
* callOrdinal). Exported so the handler side can assert that the same
|
|
24
|
+
* inputs produce the same id (e.g. during crash-sim tests).
|
|
25
|
+
*/
|
|
26
|
+
export function deriveCommentId(dispatchId, callOrdinal) {
|
|
27
|
+
const hash = createHash('sha256').update(`${dispatchId}:comment:${callOrdinal}`).digest('hex');
|
|
28
|
+
// 32 hex chars (~128 bits) is plenty for a PK and keeps the column
|
|
29
|
+
// narrower than a full hex digest.
|
|
30
|
+
return `cmt_${hash.slice(0, 32)}`;
|
|
31
|
+
}
|
|
32
|
+
export function createTicketTools(ticketId, client, options = {}) {
|
|
33
|
+
// Per-instance call counter. Because createTicketTools() is invoked
|
|
34
|
+
// fresh inside each dispatcher.dispatch() call, the ordinal is
|
|
35
|
+
// stable across the agentic loop of a single dispatch but reset for
|
|
36
|
+
// the next dispatch — which is exactly what retry-safe id derivation
|
|
37
|
+
// needs.
|
|
38
|
+
let commentOrdinal = 0;
|
|
13
39
|
const updateStatusTool = {
|
|
14
40
|
name: 'update_ticket_status',
|
|
15
41
|
description: 'Update the status of the current ticket. Call this when work is complete (status="done") or if you cannot proceed (status="blocked"). Valid statuses: open, in_progress, done, blocked, cancelled.',
|
|
@@ -47,12 +73,16 @@ export function createTicketTools(ticketId, client) {
|
|
|
47
73
|
}),
|
|
48
74
|
async execute(params) {
|
|
49
75
|
const { text } = params;
|
|
76
|
+
const ordinal = commentOrdinal++;
|
|
77
|
+
const derivedId = options.dispatchId
|
|
78
|
+
? deriveCommentId(options.dispatchId, ordinal)
|
|
79
|
+
: undefined;
|
|
50
80
|
try {
|
|
51
81
|
const body = {
|
|
52
82
|
type: 'doc',
|
|
53
83
|
content: [{ type: 'paragraph', content: [{ type: 'text', text }] }],
|
|
54
84
|
};
|
|
55
|
-
const comment = await client.createComment(ticketId, body);
|
|
85
|
+
const comment = await client.createComment(ticketId, body, derivedId ? { id: derivedId } : undefined);
|
|
56
86
|
if (!comment) {
|
|
57
87
|
return { success: false, error: 'Failed to create comment' };
|
|
58
88
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revealui/ai",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "AI runtime for agent-driven products — agents, memory, LLM providers (Inference Snaps, Ollama, OpenAI-compatible), tools, and orchestration. Anthropic-SDK-free.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
7
7
|
"ai",
|
|
@@ -23,20 +23,22 @@
|
|
|
23
23
|
},
|
|
24
24
|
"license": "FSL-1.1-MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"lru-cache": "^11.3.
|
|
26
|
+
"lru-cache": "^11.3.6",
|
|
27
27
|
"zod": "^4.3.6",
|
|
28
|
-
"@revealui/contracts": "
|
|
28
|
+
"@revealui/contracts": "0.4.0",
|
|
29
29
|
"@revealui/core": "0.6.0",
|
|
30
|
-
"@revealui/db": "0.4.0"
|
|
30
|
+
"@revealui/db": "0.4.0",
|
|
31
|
+
"@revealui/resilience": "0.2.4"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
34
|
+
"@electric-sql/pglite": "^0.4.4",
|
|
33
35
|
"@testing-library/react": "^16.3.2",
|
|
34
|
-
"@vitest/coverage-v8": "^4.1.
|
|
35
|
-
"jsdom": "29.
|
|
36
|
-
"react": "^19.2.
|
|
37
|
-
"react-dom": "^19.2.
|
|
38
|
-
"typescript": "^6.0.
|
|
39
|
-
"vitest": "^4.1.
|
|
36
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
37
|
+
"jsdom": "29.1.1",
|
|
38
|
+
"react": "^19.2.6",
|
|
39
|
+
"react-dom": "^19.2.6",
|
|
40
|
+
"typescript": "^6.0.3",
|
|
41
|
+
"vitest": "^4.1.5",
|
|
40
42
|
"@revealui/dev": "0.1.0"
|
|
41
43
|
},
|
|
42
44
|
"engines": {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vultr Inference Provider
|
|
3
|
-
*
|
|
4
|
-
* Implements the LLMProvider interface for Vultr Serverless Inference API
|
|
5
|
-
*/
|
|
6
|
-
import type { Embedding, LLMChatOptions, LLMChunk, LLMEmbedOptions, LLMProvider, LLMProviderConfig, LLMResponse, LLMStreamOptions, Message } from './base.js';
|
|
7
|
-
export interface VultrProviderConfig extends LLMProviderConfig {
|
|
8
|
-
subscriptionId?: string;
|
|
9
|
-
}
|
|
10
|
-
export declare class VultrProvider implements LLMProvider {
|
|
11
|
-
private config;
|
|
12
|
-
private baseURL;
|
|
13
|
-
constructor(config: VultrProviderConfig);
|
|
14
|
-
chat(messages: Message[], options?: LLMChatOptions): Promise<LLMResponse>;
|
|
15
|
-
embed(text: string | string[], options?: LLMEmbedOptions): Promise<Embedding | Embedding[]>;
|
|
16
|
-
stream(messages: Message[], options?: LLMStreamOptions): AsyncIterable<LLMChunk>;
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=vultr.d.ts.map
|