@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.
Files changed (82) hide show
  1. package/README.md +200 -26
  2. package/dist/a2a/handler.d.ts +3 -1
  3. package/dist/a2a/handler.d.ts.map +1 -1
  4. package/dist/a2a/handler.js +24 -7
  5. package/dist/a2a/task-store.d.ts.map +1 -1
  6. package/dist/a2a/task-store.js +5 -1
  7. package/dist/client/hooks/useAgentStream.d.ts +84 -1
  8. package/dist/client/hooks/useAgentStream.d.ts.map +1 -1
  9. package/dist/client/hooks/useAgentStream.js +106 -10
  10. package/dist/index.d.ts +4 -6
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -15
  13. package/dist/ingestion/admin-indexer.js +1 -1
  14. package/dist/ingestion/pipeline.d.ts +1 -1
  15. package/dist/ingestion/pipeline.d.ts.map +1 -1
  16. package/dist/llm/client.d.ts +33 -10
  17. package/dist/llm/client.d.ts.map +1 -1
  18. package/dist/llm/client.js +70 -57
  19. package/dist/llm/key-validator.d.ts.map +1 -1
  20. package/dist/llm/key-validator.js +0 -27
  21. package/dist/llm/providers/inference-snaps.d.ts +7 -5
  22. package/dist/llm/providers/inference-snaps.d.ts.map +1 -1
  23. package/dist/llm/providers/inference-snaps.js +7 -5
  24. package/dist/llm/providers/openai-compat.d.ts +1 -1
  25. package/dist/llm/providers/openai-compat.js +1 -1
  26. package/dist/llm/response-cache.d.ts +1 -2
  27. package/dist/llm/response-cache.d.ts.map +1 -1
  28. package/dist/llm/response-cache.js +1 -2
  29. package/dist/llm/server.d.ts +1 -1
  30. package/dist/llm/server.d.ts.map +1 -1
  31. package/dist/llm/server.js +3 -1
  32. package/dist/memory/crdt/lww-register.js +1 -1
  33. package/dist/memory/preferences/user-preferences-manager.d.ts.map +1 -1
  34. package/dist/memory/preferences/user-preferences-manager.js +1 -1
  35. package/dist/memory/services/index.d.ts +2 -0
  36. package/dist/memory/services/index.d.ts.map +1 -1
  37. package/dist/memory/services/index.js +1 -0
  38. package/dist/memory/services/reconciliation-service.d.ts +64 -0
  39. package/dist/memory/services/reconciliation-service.d.ts.map +1 -0
  40. package/dist/memory/services/reconciliation-service.js +127 -0
  41. package/dist/memory/stores/working-memory.d.ts.map +1 -1
  42. package/dist/memory/stores/working-memory.js +1 -1
  43. package/dist/memory/utils/index.d.ts +1 -2
  44. package/dist/memory/utils/index.d.ts.map +1 -1
  45. package/dist/memory/utils/index.js +1 -2
  46. package/dist/memory/utils/sql-helpers.d.ts +21 -66
  47. package/dist/memory/utils/sql-helpers.d.ts.map +1 -1
  48. package/dist/memory/utils/sql-helpers.js +34 -169
  49. package/dist/memory/vector/vector-memory-service.d.ts.map +1 -1
  50. package/dist/memory/vector/vector-memory-service.js +6 -1
  51. package/dist/orchestration/runtime.d.ts +30 -1
  52. package/dist/orchestration/runtime.d.ts.map +1 -1
  53. package/dist/orchestration/runtime.js +20 -3
  54. package/dist/orchestration/streaming-runtime.d.ts +48 -1
  55. package/dist/orchestration/streaming-runtime.d.ts.map +1 -1
  56. package/dist/orchestration/ticket-agent.d.ts +10 -1
  57. package/dist/orchestration/ticket-agent.d.ts.map +1 -1
  58. package/dist/orchestration/ticket-agent.js +11 -2
  59. package/dist/skills/catalog/vercel-catalog.d.ts.map +1 -1
  60. package/dist/skills/catalog/vercel-catalog.js +13 -7
  61. package/dist/tools/mcp-adapter.d.ts +192 -1
  62. package/dist/tools/mcp-adapter.d.ts.map +1 -1
  63. package/dist/tools/mcp-adapter.js +434 -1
  64. package/dist/tools/mcp-elicitation.d.ts +128 -0
  65. package/dist/tools/mcp-elicitation.d.ts.map +1 -0
  66. package/dist/tools/mcp-elicitation.js +121 -0
  67. package/dist/tools/mcp-events.d.ts +270 -0
  68. package/dist/tools/mcp-events.d.ts.map +1 -0
  69. package/dist/tools/mcp-events.js +165 -0
  70. package/dist/tools/mcp-sampling.d.ts +155 -0
  71. package/dist/tools/mcp-sampling.d.ts.map +1 -0
  72. package/dist/tools/mcp-sampling.js +171 -0
  73. package/dist/tools/ticket-tools.d.ts +35 -2
  74. package/dist/tools/ticket-tools.d.ts.map +1 -1
  75. package/dist/tools/ticket-tools.js +32 -2
  76. package/package.json +38 -12
  77. package/dist/llm/providers/vultr.d.ts +0 -18
  78. package/dist/llm/providers/vultr.d.ts.map +0 -1
  79. package/dist/llm/providers/vultr.js +0 -169
  80. package/dist/memory/utils/deep-clone.d.ts +0 -10
  81. package/dist/memory/utils/deep-clone.d.ts.map +0 -1
  82. 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
- createComment(ticketId: string, body: Record<string, unknown>): Promise<{
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 declare function createTicketTools(ticketId: string, client: TicketMutationClient): Tool[];
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;;;;;;;;;GASG;AAGH,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,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACjE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,IAAI,EAAE,CAgExF"}
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
- export function createTicketTools(ticketId, client) {
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.2.9",
4
- "description": "[Pro] AI system for RevealUI - memory, LLM, orchestration, and tools",
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.3",
26
+ "lru-cache": "^11.3.6",
27
27
  "zod": "^4.3.6",
28
- "@revealui/contracts": "1.3.7",
29
- "@revealui/core": "0.5.6",
30
- "@revealui/db": "0.3.7"
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.3",
35
- "jsdom": "29.0.1",
36
- "react": "^19.2.5",
37
- "react-dom": "^19.2.5",
38
- "typescript": "^6.0.2",
39
- "vitest": "^4.1.3",
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"}