@revealui/ai 0.2.9 → 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/ingestion/admin-indexer.js +1 -1
- package/dist/ingestion/pipeline.d.ts +1 -1
- package/dist/ingestion/pipeline.d.ts.map +1 -1
- package/dist/llm/client.d.ts +33 -10
- package/dist/llm/client.d.ts.map +1 -1
- package/dist/llm/client.js +70 -57
- 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/crdt/lww-register.js +1 -1
- package/dist/memory/preferences/user-preferences-manager.d.ts.map +1 -1
- package/dist/memory/preferences/user-preferences-manager.js +1 -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/stores/working-memory.d.ts.map +1 -1
- package/dist/memory/stores/working-memory.js +1 -1
- package/dist/memory/utils/index.d.ts +1 -2
- package/dist/memory/utils/index.d.ts.map +1 -1
- package/dist/memory/utils/index.js +1 -2
- package/dist/memory/utils/sql-helpers.d.ts +21 -66
- package/dist/memory/utils/sql-helpers.d.ts.map +1 -1
- package/dist/memory/utils/sql-helpers.js +34 -169
- 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/skills/catalog/vercel-catalog.d.ts.map +1 -1
- package/dist/skills/catalog/vercel-catalog.js +13 -7
- 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 +38 -12
- 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,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": "
|
|
29
|
-
"@revealui/core": "0.
|
|
30
|
-
"@revealui/db": "0.
|
|
28
|
+
"@revealui/contracts": "0.4.0",
|
|
29
|
+
"@revealui/core": "0.6.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": {
|
|
@@ -47,6 +49,10 @@
|
|
|
47
49
|
"types": "./dist/index.d.ts",
|
|
48
50
|
"import": "./dist/index.js"
|
|
49
51
|
},
|
|
52
|
+
"./a2a": {
|
|
53
|
+
"types": "./dist/a2a/index.d.ts",
|
|
54
|
+
"import": "./dist/a2a/index.js"
|
|
55
|
+
},
|
|
50
56
|
"./memory": {
|
|
51
57
|
"types": "./dist/memory/index.d.ts",
|
|
52
58
|
"import": "./dist/memory/index.js"
|
|
@@ -83,6 +89,14 @@
|
|
|
83
89
|
"types": "./dist/skills/index.d.ts",
|
|
84
90
|
"import": "./dist/skills/index.js"
|
|
85
91
|
},
|
|
92
|
+
"./skills/registry": {
|
|
93
|
+
"types": "./dist/skills/registry/index.d.ts",
|
|
94
|
+
"import": "./dist/skills/registry/index.js"
|
|
95
|
+
},
|
|
96
|
+
"./llm/cache-utils": {
|
|
97
|
+
"types": "./dist/llm/cache-utils.d.ts",
|
|
98
|
+
"import": "./dist/llm/cache-utils.js"
|
|
99
|
+
},
|
|
86
100
|
"./llm/client": {
|
|
87
101
|
"types": "./dist/llm/client.d.ts",
|
|
88
102
|
"import": "./dist/llm/client.js"
|
|
@@ -99,6 +113,14 @@
|
|
|
99
113
|
"types": "./dist/llm/providers/base.d.ts",
|
|
100
114
|
"import": "./dist/llm/providers/base.js"
|
|
101
115
|
},
|
|
116
|
+
"./llm/response-cache": {
|
|
117
|
+
"types": "./dist/llm/response-cache.d.ts",
|
|
118
|
+
"import": "./dist/llm/response-cache.js"
|
|
119
|
+
},
|
|
120
|
+
"./llm/semantic-cache": {
|
|
121
|
+
"types": "./dist/llm/semantic-cache.d.ts",
|
|
122
|
+
"import": "./dist/llm/semantic-cache.js"
|
|
123
|
+
},
|
|
102
124
|
"./tools/admin": {
|
|
103
125
|
"types": "./dist/tools/admin/index.d.ts",
|
|
104
126
|
"import": "./dist/tools/admin/index.js"
|
|
@@ -111,6 +133,10 @@
|
|
|
111
133
|
"types": "./dist/ingestion/index.d.ts",
|
|
112
134
|
"import": "./dist/ingestion/index.js"
|
|
113
135
|
},
|
|
136
|
+
"./orchestration/runtime": {
|
|
137
|
+
"types": "./dist/orchestration/runtime.d.ts",
|
|
138
|
+
"import": "./dist/orchestration/runtime.js"
|
|
139
|
+
},
|
|
114
140
|
"./orchestration/streaming-runtime": {
|
|
115
141
|
"types": "./dist/orchestration/streaming-runtime.d.ts",
|
|
116
142
|
"import": "./dist/orchestration/streaming-runtime.js"
|
|
@@ -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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vultr.d.ts","sourceRoot":"","sources":["../../../src/llm/providers/vultr.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACd,QAAQ,EACR,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,OAAO,EAER,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAgBD,qBAAa,aAAc,YAAW,WAAW;IAC/C,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,mBAAmB;IAKjC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IA2DzE,KAAK,CACT,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,SAAS,GAAG,SAAS,EAAE,CAAC;IAwC5B,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,aAAa,CAAC,QAAQ,CAAC;CAkExF"}
|