@lucern/sdk 0.2.0-alpha.5 → 0.2.0-alpha.7

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 (166) hide show
  1. package/.turbo/turbo-build.log +7 -0
  2. package/.turbo/turbo-typecheck.log +4 -0
  3. package/CHANGELOG.md +13 -0
  4. package/examples/README.md +69 -0
  5. package/examples/contradiction.ts +94 -0
  6. package/examples/investigation-context.ts +118 -0
  7. package/examples/questions-and-tasks.ts +55 -0
  8. package/examples/quickstart.ts +97 -0
  9. package/examples/shared.ts +318 -0
  10. package/examples/strict-public-types.ts +143 -0
  11. package/examples/worktree-lifecycle.ts +79 -0
  12. package/package.json +16 -59
  13. package/src/README.md +6 -0
  14. package/src/adminClient.ts +789 -0
  15. package/{dist/lib/platform/sdk/answersClient.d.ts → src/answersClient.ts} +17 -4
  16. package/src/audiencesClient.ts +209 -0
  17. package/src/auditClient.ts +50 -0
  18. package/src/beliefsClient.ts +319 -0
  19. package/src/client.ts +2647 -0
  20. package/src/contextClient.ts +130 -0
  21. package/src/contextFacade.ts +15 -0
  22. package/src/contextPackCompiler.ts +828 -0
  23. package/src/contextPackSchema.ts +251 -0
  24. package/src/contextTypes.ts +153 -0
  25. package/src/contracts/api-enums.contract.ts +202 -0
  26. package/src/contracts/auth-session.contract.ts +109 -0
  27. package/src/contracts/context-pack.contract.ts +700 -0
  28. package/src/contracts/contextPack.ts +1 -0
  29. package/src/contracts/index.ts +10 -0
  30. package/src/contracts/lens-filter.contract.ts +183 -0
  31. package/src/contracts/lens-workflow.contract.ts +162 -0
  32. package/src/contracts/lensFilter.ts +1 -0
  33. package/src/contracts/lensWorkflow.ts +1 -0
  34. package/src/contracts/mcp-tools.contract.ts +3636 -0
  35. package/src/contracts/mcpTools.ts +1 -0
  36. package/src/contracts/prompt.contract.ts +50 -0
  37. package/src/contracts/prompt.ts +1 -0
  38. package/src/contracts/sdk-tools.contract.ts +1457 -0
  39. package/src/contracts/sdkTools.ts +1 -0
  40. package/src/contracts/workflow-runtime.contract.ts +440 -0
  41. package/src/contracts/workflowRuntime.ts +1 -0
  42. package/src/controlObjectOwnership.ts +286 -0
  43. package/src/coreClient.ts +570 -0
  44. package/src/customTools.ts +398 -0
  45. package/src/decisionsClient.ts +286 -0
  46. package/src/domainContext.ts +15 -0
  47. package/src/events.ts +531 -0
  48. package/src/eventsCore.ts +168 -0
  49. package/src/facade/beliefs.ts +83 -0
  50. package/src/facade/context.ts +110 -0
  51. package/src/facade/contradictions.ts +29 -0
  52. package/src/facade/edges.ts +30 -0
  53. package/src/facade/events.ts +23 -0
  54. package/src/facade/evidence.ts +41 -0
  55. package/src/facade/graph.ts +38 -0
  56. package/src/facade/identity.ts +16 -0
  57. package/src/facade/ontologies.ts +34 -0
  58. package/src/facade/questions.ts +59 -0
  59. package/src/facade/search.ts +16 -0
  60. package/src/facade/tasks.ts +37 -0
  61. package/src/facade/topics.ts +42 -0
  62. package/src/facade/webhooks.ts +58 -0
  63. package/src/facade/worktrees.ts +51 -0
  64. package/src/gatewayFacades.ts +1666 -0
  65. package/src/graphClient.ts +529 -0
  66. package/src/harnessClient.ts +585 -0
  67. package/src/identityClient.ts +278 -0
  68. package/{dist/lib/platform/sdk/index.d.ts → src/index.ts} +3 -0
  69. package/src/learningClient.ts +95 -0
  70. package/src/mcpParityClient.ts +240 -0
  71. package/src/mcpParitySurface.ts +70 -0
  72. package/src/ontologyClient.ts +275 -0
  73. package/src/packRuntime.ts +3 -0
  74. package/src/packsClient.ts +260 -0
  75. package/src/policyClient.ts +572 -0
  76. package/src/promptCatalog.ts +1 -0
  77. package/src/realtime/index.ts +51 -0
  78. package/src/realtime/refs.ts +17 -0
  79. package/src/reportsClient.ts +99 -0
  80. package/src/schemaClient.ts +129 -0
  81. package/src/sdkSurface.ts +190 -0
  82. package/src/topicsClient.ts +243 -0
  83. package/src/types.ts +807 -0
  84. package/src/workflowClient.ts +826 -0
  85. package/tsconfig.json +9 -0
  86. package/dist/.generated +0 -2
  87. package/dist/index.d.ts +0 -3
  88. package/dist/index.js +0 -3
  89. package/dist/lib/platform/auth/credentials.d.ts +0 -5
  90. package/dist/lib/platform/auth/credentials.js +0 -40
  91. package/dist/lib/platform/sdk/adminClient.d.ts +0 -404
  92. package/dist/lib/platform/sdk/adminClient.js +0 -384
  93. package/dist/lib/platform/sdk/answersClient.js +0 -21
  94. package/dist/lib/platform/sdk/audiencesClient.d.ts +0 -93
  95. package/dist/lib/platform/sdk/audiencesClient.js +0 -111
  96. package/dist/lib/platform/sdk/auditClient.d.ts +0 -24
  97. package/dist/lib/platform/sdk/auditClient.js +0 -21
  98. package/dist/lib/platform/sdk/beliefsClient.d.ts +0 -157
  99. package/dist/lib/platform/sdk/beliefsClient.js +0 -124
  100. package/dist/lib/platform/sdk/client.d.ts +0 -2369
  101. package/dist/lib/platform/sdk/client.js +0 -1831
  102. package/dist/lib/platform/sdk/contextClient.d.ts +0 -21
  103. package/dist/lib/platform/sdk/contextClient.js +0 -86
  104. package/dist/lib/platform/sdk/contextPackCompiler.d.ts +0 -100
  105. package/dist/lib/platform/sdk/contextPackCompiler.js +0 -534
  106. package/dist/lib/platform/sdk/contextTypes.d.ts +0 -133
  107. package/dist/lib/platform/sdk/contextTypes.js +0 -1
  108. package/dist/lib/platform/sdk/controlObjectOwnership.d.ts +0 -308
  109. package/dist/lib/platform/sdk/controlObjectOwnership.js +0 -220
  110. package/dist/lib/platform/sdk/coreClient.d.ts +0 -139
  111. package/dist/lib/platform/sdk/coreClient.js +0 -366
  112. package/dist/lib/platform/sdk/customTools.d.ts +0 -83
  113. package/dist/lib/platform/sdk/customTools.js +0 -247
  114. package/dist/lib/platform/sdk/decisionsClient.d.ts +0 -106
  115. package/dist/lib/platform/sdk/decisionsClient.js +0 -129
  116. package/dist/lib/platform/sdk/domainContext.d.ts +0 -1
  117. package/dist/lib/platform/sdk/domainContext.js +0 -1
  118. package/dist/lib/platform/sdk/events.d.ts +0 -176
  119. package/dist/lib/platform/sdk/events.js +0 -261
  120. package/dist/lib/platform/sdk/gatewayFacades.d.ts +0 -586
  121. package/dist/lib/platform/sdk/gatewayFacades.js +0 -845
  122. package/dist/lib/platform/sdk/graphClient.d.ts +0 -266
  123. package/dist/lib/platform/sdk/graphClient.js +0 -235
  124. package/dist/lib/platform/sdk/harnessClient.d.ts +0 -309
  125. package/dist/lib/platform/sdk/harnessClient.js +0 -219
  126. package/dist/lib/platform/sdk/identityClient.d.ts +0 -134
  127. package/dist/lib/platform/sdk/identityClient.js +0 -131
  128. package/dist/lib/platform/sdk/index.js +0 -46
  129. package/dist/lib/platform/sdk/learningClient.d.ts +0 -40
  130. package/dist/lib/platform/sdk/learningClient.js +0 -53
  131. package/dist/lib/platform/sdk/mcpParityClient.d.ts +0 -69
  132. package/dist/lib/platform/sdk/mcpParityClient.js +0 -196
  133. package/dist/lib/platform/sdk/mcpParitySurface.d.ts +0 -10
  134. package/dist/lib/platform/sdk/mcpParitySurface.js +0 -57
  135. package/dist/lib/platform/sdk/ontologyClient.d.ts +0 -131
  136. package/dist/lib/platform/sdk/ontologyClient.js +0 -161
  137. package/dist/lib/platform/sdk/packRuntime.d.ts +0 -1
  138. package/dist/lib/platform/sdk/packRuntime.js +0 -1
  139. package/dist/lib/platform/sdk/packsClient.d.ts +0 -126
  140. package/dist/lib/platform/sdk/packsClient.js +0 -157
  141. package/dist/lib/platform/sdk/policyClient.d.ts +0 -293
  142. package/dist/lib/platform/sdk/policyClient.js +0 -277
  143. package/dist/lib/platform/sdk/promptCatalog.d.ts +0 -1
  144. package/dist/lib/platform/sdk/promptCatalog.js +0 -1
  145. package/dist/lib/platform/sdk/reportsClient.d.ts +0 -34
  146. package/dist/lib/platform/sdk/reportsClient.js +0 -64
  147. package/dist/lib/platform/sdk/schemaClient.d.ts +0 -59
  148. package/dist/lib/platform/sdk/schemaClient.js +0 -71
  149. package/dist/lib/platform/sdk/sdkSurface.d.ts +0 -56
  150. package/dist/lib/platform/sdk/sdkSurface.js +0 -140
  151. package/dist/lib/platform/sdk/topicsClient.d.ts +0 -78
  152. package/dist/lib/platform/sdk/topicsClient.js +0 -118
  153. package/dist/lib/platform/sdk/types.d.ts +0 -692
  154. package/dist/lib/platform/sdk/types.js +0 -1
  155. package/dist/lib/platform/sdk/version.d.ts +0 -2
  156. package/dist/lib/platform/sdk/workflowClient.d.ts +0 -313
  157. package/dist/lib/platform/sdk/workflowClient.js +0 -366
  158. package/dist/lucern/contracts/src/api-enums.contract.d.ts +0 -58
  159. package/dist/lucern/contracts/src/api-enums.contract.js +0 -147
  160. package/dist/lucern/contracts/src/lens-filter.contract.d.ts +0 -70
  161. package/dist/lucern/contracts/src/lens-filter.contract.js +0 -95
  162. package/dist/lucern/contracts/src/lens-workflow.contract.d.ts +0 -84
  163. package/dist/lucern/contracts/src/lens-workflow.contract.js +0 -54
  164. package/dist/lucern/contracts/src/mcp-tools.contract.d.ts +0 -151
  165. package/dist/lucern/contracts/src/mcp-tools.contract.js +0 -3281
  166. /package/{dist/lib/platform/sdk/version.js → src/version.ts} +0 -0
@@ -1,366 +0,0 @@
1
- /**
2
- * Structured error thrown when a platform API request fails.
3
- *
4
- * Includes HTTP status, machine-readable error code, optional invariant
5
- * reference, and actionable suggestions for common failure modes.
6
- */
7
- export class LucernApiError extends Error {
8
- code;
9
- status;
10
- invariant;
11
- suggestion;
12
- details;
13
- requestId;
14
- correlationId;
15
- policyTraceId;
16
- constructor(args) {
17
- super(args.message);
18
- this.name = "LucernApiError";
19
- this.code = args.code;
20
- this.status = args.status;
21
- this.invariant = args.invariant;
22
- this.suggestion = args.suggestion;
23
- this.details = args.details;
24
- this.requestId = args.requestId;
25
- this.correlationId = args.correlationId;
26
- this.policyTraceId = args.policyTraceId;
27
- }
28
- }
29
- /**
30
- * Serialize a gateway query object into a URL query string.
31
- */
32
- export function toQueryString(scope) {
33
- const params = new URLSearchParams();
34
- if (scope.tenantId) {
35
- params.set("tenantId", scope.tenantId);
36
- }
37
- if (scope.workspaceId) {
38
- params.set("workspaceId", scope.workspaceId);
39
- }
40
- for (const [key, value] of Object.entries(scope)) {
41
- if (key === "tenantId" || key === "workspaceId") {
42
- continue;
43
- }
44
- if (value === undefined) {
45
- continue;
46
- }
47
- params.set(key, String(value));
48
- }
49
- const serialized = params.toString();
50
- return serialized.length > 0 ? `?${serialized}` : "";
51
- }
52
- function fillRandomBytes(length) {
53
- const bytes = new Uint8Array(length);
54
- if (typeof globalThis.crypto?.getRandomValues === "function") {
55
- globalThis.crypto.getRandomValues(bytes);
56
- return bytes;
57
- }
58
- for (let index = 0; index < length; index += 1) {
59
- bytes[index] = Math.floor(Math.random() * 256);
60
- }
61
- return bytes;
62
- }
63
- function generatePortableRequestId() {
64
- if (typeof globalThis.crypto?.randomUUID === "function") {
65
- return globalThis.crypto.randomUUID();
66
- }
67
- const bytes = fillRandomBytes(16);
68
- bytes[6] = (bytes[6] & 0x0f) | 0x40;
69
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
70
- const hex = Array.from(bytes, (value) => value.toString(16).padStart(2, "0"));
71
- return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
72
- }
73
- /**
74
- * Generate a random idempotency key for retry-safe writes.
75
- */
76
- export function randomIdempotencyKey() {
77
- return generatePortableRequestId();
78
- }
79
- function isRetryableStatus(status) {
80
- return status >= 500 || status === 408 || status === 429;
81
- }
82
- function fallbackErrorCode(status) {
83
- if (status === 401) {
84
- return "AUTHENTICATION_REQUIRED";
85
- }
86
- if (status === 403) {
87
- return "FORBIDDEN";
88
- }
89
- if (status === 404) {
90
- return "NOT_FOUND";
91
- }
92
- if (status === 408) {
93
- return "UPSTREAM_ERROR";
94
- }
95
- if (status === 409) {
96
- return "CONFLICT";
97
- }
98
- if (status === 429) {
99
- return "RATE_LIMIT_EXCEEDED";
100
- }
101
- if (status >= 500) {
102
- return "UPSTREAM_ERROR";
103
- }
104
- return "INTERNAL_ERROR";
105
- }
106
- function delay(ms) {
107
- return new Promise((resolve) => setTimeout(resolve, ms));
108
- }
109
- function parseRetryAfterMs(value) {
110
- if (!value) {
111
- return null;
112
- }
113
- const trimmed = value.trim();
114
- if (!trimmed) {
115
- return null;
116
- }
117
- const numeric = Number(trimmed);
118
- if (Number.isFinite(numeric)) {
119
- return Math.max(0, Math.round(numeric * 1000));
120
- }
121
- const parsedDate = Date.parse(trimmed);
122
- if (Number.isFinite(parsedDate)) {
123
- return Math.max(0, parsedDate - Date.now());
124
- }
125
- return null;
126
- }
127
- function computeRetryDelayMs(args) {
128
- const baseDelay = args.status === 429
129
- ? Math.max(args.retryAfterMs ?? 0, Math.min(1000 * 2 ** args.attempt, 10_000))
130
- : Math.min(1000 * 2 ** args.attempt, 4000);
131
- if (args.status !== 429) {
132
- return baseDelay;
133
- }
134
- const jitterWindow = Math.max(250, Math.round(baseDelay * 0.25));
135
- return baseDelay + Math.round(Math.random() * jitterWindow);
136
- }
137
- function timeoutError(timeoutMs) {
138
- const error = new Error(`Request timed out after ${timeoutMs}ms`);
139
- error.name = "AbortError";
140
- return error;
141
- }
142
- function readPolicySummaryFromDetails(details) {
143
- if (!details || typeof details !== "object" || Array.isArray(details)) {
144
- return null;
145
- }
146
- const directSummary = details.summary;
147
- if (typeof directSummary === "string" && directSummary.trim().length > 0) {
148
- return directSummary.trim();
149
- }
150
- const policy = details.policy;
151
- if (!policy || typeof policy !== "object" || Array.isArray(policy)) {
152
- return null;
153
- }
154
- const explanation = policy.explanation;
155
- if (!explanation || typeof explanation !== "object" || Array.isArray(explanation)) {
156
- return null;
157
- }
158
- const nestedSummary = explanation.summary;
159
- if (typeof nestedSummary === "string" && nestedSummary.trim().length > 0) {
160
- return nestedSummary.trim();
161
- }
162
- return null;
163
- }
164
- /**
165
- * Create the transport client used by all SDK modules.
166
- */
167
- export function createGatewayRequestClient(config = {}) {
168
- const fetchImpl = config.fetchImpl ?? fetch;
169
- const baseUrl = config.baseUrl?.replace(/\/+$/, "") ?? "";
170
- const maxRetries = config.maxRetries ?? 2;
171
- const requestIdFactory = config.requestIdFactory ?? (() => generatePortableRequestId());
172
- async function resolveAuthHeaders() {
173
- if (!config.getAuthHeaders) {
174
- return {};
175
- }
176
- return await config.getAuthHeaders();
177
- }
178
- async function fetchWithTimeout(url, init, timeoutMs) {
179
- const controller = new AbortController();
180
- const timer = setTimeout(() => controller.abort(), timeoutMs);
181
- try {
182
- return await fetchImpl(url, { ...init, signal: controller.signal });
183
- }
184
- catch (error) {
185
- if (controller.signal.aborted) {
186
- throw timeoutError(timeoutMs);
187
- }
188
- throw error;
189
- }
190
- finally {
191
- clearTimeout(timer);
192
- }
193
- }
194
- async function parsePayload(response) {
195
- const text = await response.text();
196
- if (!text) {
197
- return null;
198
- }
199
- try {
200
- return JSON.parse(text);
201
- }
202
- catch {
203
- return null;
204
- }
205
- }
206
- function resolveTimeoutMs(method, requestTimeoutMs) {
207
- if (typeof requestTimeoutMs === "number") {
208
- return requestTimeoutMs;
209
- }
210
- const methodTimeoutMs = config.timeoutMsByMethod?.[method];
211
- if (typeof methodTimeoutMs === "number") {
212
- return methodTimeoutMs;
213
- }
214
- return config.timeoutMs ?? 15_000;
215
- }
216
- function buildApiError(args) {
217
- const failure = args.failure;
218
- const legacyError = failure && typeof failure.error === "object" && failure.error !== null
219
- ? failure.error
220
- : failure?.legacyError;
221
- const correlationId = failure?.correlationId ??
222
- args.response.headers.get("x-lucern-correlation-id")?.trim() ??
223
- args.requestId;
224
- const policyTraceId = failure?.policyTraceId ??
225
- args.response.headers.get("x-lucern-policy-trace-id")?.trim() ??
226
- null;
227
- const details = failure?.details ?? legacyError?.details;
228
- const policySummary = readPolicySummaryFromDetails(details);
229
- return new LucernApiError({
230
- code: failure?.code ?? legacyError?.code ?? fallbackErrorCode(args.response.status),
231
- message: policySummary ??
232
- (typeof failure?.error === "string"
233
- ? failure.error
234
- : legacyError?.message ??
235
- (args.response.ok
236
- ? "Platform API returned an invalid success payload."
237
- : "Platform API request failed.")),
238
- status: args.response.status,
239
- invariant: failure?.invariant,
240
- suggestion: failure?.suggestion,
241
- details,
242
- requestId: args.requestId,
243
- correlationId,
244
- policyTraceId,
245
- });
246
- }
247
- async function request(args) {
248
- const authHeaders = await resolveAuthHeaders();
249
- const method = args.method ?? "GET";
250
- const timeoutMs = resolveTimeoutMs(method, args.timeoutMs);
251
- const headers = new Headers({
252
- "content-type": "application/json",
253
- ...authHeaders,
254
- });
255
- if (args.idempotencyKey) {
256
- headers.set("idempotency-key", args.idempotencyKey);
257
- }
258
- const requestId = headers.get("x-correlation-id")?.trim() ||
259
- headers.get("x-request-id")?.trim() ||
260
- args.requestId ||
261
- requestIdFactory();
262
- if (!headers.has("x-correlation-id") && !headers.has("x-request-id")) {
263
- headers.set("x-correlation-id", requestId);
264
- }
265
- const url = `${baseUrl}${args.path}`;
266
- const serializedBody = args.body ? JSON.stringify(args.body) : undefined;
267
- const init = {
268
- method,
269
- headers,
270
- body: serializedBody,
271
- };
272
- let lastError;
273
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
274
- const hookRequestContext = {
275
- requestId,
276
- attempt,
277
- maxRetries,
278
- method,
279
- path: args.path,
280
- url,
281
- headers: new Headers(headers),
282
- body: serializedBody,
283
- timeoutMs,
284
- };
285
- await config.onRequest?.(hookRequestContext);
286
- const startedAt = Date.now();
287
- try {
288
- const response = await fetchWithTimeout(url, init, timeoutMs);
289
- const responseClone = response.clone();
290
- const payload = await parsePayload(response);
291
- const retryAfterMs = parseRetryAfterMs(response.headers.get("Retry-After"));
292
- if (!response.ok || !payload?.success) {
293
- const failure = payload && !payload.success ? payload : null;
294
- const apiError = buildApiError({
295
- requestId,
296
- response,
297
- failure,
298
- });
299
- const willRetry = attempt < maxRetries && isRetryableStatus(response.status);
300
- await config.onResponse?.({
301
- ...hookRequestContext,
302
- durationMs: Date.now() - startedAt,
303
- status: response.status,
304
- response: responseClone,
305
- error: apiError,
306
- correlationId: apiError.correlationId ?? requestId,
307
- policyTraceId: apiError.policyTraceId ?? null,
308
- retryAfterMs,
309
- willRetry,
310
- });
311
- if (willRetry) {
312
- lastError = apiError;
313
- await delay(computeRetryDelayMs({
314
- attempt,
315
- status: response.status,
316
- retryAfterMs,
317
- }));
318
- continue;
319
- }
320
- throw apiError;
321
- }
322
- const successPayload = payload;
323
- await config.onResponse?.({
324
- ...hookRequestContext,
325
- durationMs: Date.now() - startedAt,
326
- status: response.status,
327
- response: responseClone,
328
- correlationId: successPayload.correlationId ??
329
- response.headers.get("x-lucern-correlation-id")?.trim() ??
330
- requestId,
331
- policyTraceId: successPayload.policyTraceId ??
332
- response.headers.get("x-lucern-policy-trace-id")?.trim() ??
333
- null,
334
- idempotentReplay: successPayload.idempotentReplay,
335
- retryAfterMs,
336
- willRetry: false,
337
- });
338
- return successPayload;
339
- }
340
- catch (fetchError) {
341
- if (fetchError instanceof LucernApiError) {
342
- throw fetchError;
343
- }
344
- const willRetry = attempt < maxRetries;
345
- await config.onResponse?.({
346
- ...hookRequestContext,
347
- durationMs: Date.now() - startedAt,
348
- error: fetchError,
349
- correlationId: requestId,
350
- policyTraceId: null,
351
- willRetry,
352
- });
353
- lastError = fetchError;
354
- if (willRetry) {
355
- await delay(computeRetryDelayMs({ attempt }));
356
- }
357
- }
358
- }
359
- throw lastError instanceof Error
360
- ? lastError
361
- : new Error("Platform API request failed after retries.");
362
- }
363
- return {
364
- request,
365
- };
366
- }
@@ -1,83 +0,0 @@
1
- import { z } from "zod";
2
- import type { McpToolContract } from "./contracts/mcpTools";
3
- import type { JsonObject } from "./types";
4
- type AnyZodObject = z.ZodObject<z.ZodRawShape>;
5
- /** Origin of a custom tool invocation (SDK call or MCP request). */
6
- export type CustomToolInvocationSource = "sdk" | "mcp";
7
- /** Runtime context passed to a custom tool handler during invocation. */
8
- export type CustomToolInvocationContext = {
9
- source: CustomToolInvocationSource;
10
- };
11
- /** Optional metadata attached to a custom tool registration. */
12
- export type CustomToolMetadata = {
13
- category?: string;
14
- tags?: string[];
15
- requiredScopes?: string[];
16
- };
17
- /** Input descriptor for registering a custom tool with its schemas and handler. */
18
- export type CustomToolRegistration<TInput extends AnyZodObject = AnyZodObject, TOutput extends z.ZodTypeAny = z.ZodTypeAny> = {
19
- namespace?: string;
20
- name: string;
21
- description: string;
22
- inputSchema: TInput;
23
- outputSchema: TOutput;
24
- handler: (input: z.infer<TInput>, context: CustomToolInvocationContext) => Promise<z.infer<TOutput>> | z.infer<TOutput>;
25
- metadata?: CustomToolMetadata;
26
- ownerModule?: string;
27
- ontologyPrimitive?: McpToolContract["ontologyPrimitive"];
28
- tier?: McpToolContract["tier"];
29
- };
30
- /** A fully resolved custom tool entry stored in the in-memory registry. */
31
- export type RegisteredCustomTool = {
32
- fullName: string;
33
- namespace: string;
34
- shortName: string;
35
- metadata: CustomToolMetadata;
36
- contract: McpToolContract;
37
- inputSchema: AnyZodObject;
38
- outputSchema: z.ZodTypeAny;
39
- invoke: (input: JsonObject, context: CustomToolInvocationContext) => Promise<unknown>;
40
- };
41
- /** Error thrown when custom tool registration or invocation fails validation. */
42
- export declare class CustomToolRegistryError extends Error {
43
- constructor(message: string);
44
- }
45
- /**
46
- * Register a custom SDK tool in the in-memory registry.
47
- * @param registration - The tool definition including name, schemas, and handler.
48
- * @param options - Optional flags; set `replace` to overwrite an existing registration.
49
- * @returns The fully resolved registered tool entry.
50
- */
51
- export declare function registerCustomTool<TInput extends AnyZodObject, TOutput extends z.ZodTypeAny>(registration: CustomToolRegistration<TInput, TOutput>, options?: {
52
- replace?: boolean;
53
- }): RegisteredCustomTool;
54
- /**
55
- * Remove a custom SDK tool from the registry.
56
- * @param fullName - Fully qualified tool name (`namespace.name`).
57
- * @returns `true` if the tool was found and removed, `false` otherwise.
58
- */
59
- export declare function unregisterCustomTool(fullName: string): boolean;
60
- /**
61
- * Get a custom SDK tool by fully qualified name.
62
- * @param fullName - Fully qualified tool name (`namespace.name`).
63
- * @returns The registered tool entry, or `undefined` if not found.
64
- */
65
- export declare function getRegisteredCustomTool(fullName: string): RegisteredCustomTool | undefined;
66
- /**
67
- * List all registered custom SDK tools, sorted alphabetically by full name.
68
- * @returns An array of all registered tool entries.
69
- */
70
- export declare function listRegisteredCustomTools(): RegisteredCustomTool[];
71
- /**
72
- * Clear the registered custom tool registry.
73
- */
74
- export declare function clearRegisteredCustomTools(): void;
75
- /**
76
- * Invoke a registered custom SDK tool by fully qualified name.
77
- * @param fullName - Fully qualified tool name (`namespace.name`).
78
- * @param params - JSON-serializable input parameters for the tool handler.
79
- * @param context - Invocation context indicating the call source.
80
- * @returns The validated output from the tool handler.
81
- */
82
- export declare function invokeRegisteredCustomTool(fullName: string, params: JsonObject, context: CustomToolInvocationContext): Promise<unknown>;
83
- export {};
@@ -1,247 +0,0 @@
1
- import { z } from "zod";
2
- /** Default namespace assigned to custom tools when none is specified. */
3
- const DEFAULT_CUSTOM_NAMESPACE = "custom";
4
- /** Namespace strings reserved for platform-internal tools. */
5
- const RESERVED_NAMESPACES = new Set(["lucern"]);
6
- /** Error thrown when custom tool registration or invocation fails validation. */
7
- export class CustomToolRegistryError extends Error {
8
- constructor(message) {
9
- super(message);
10
- this.name = "CustomToolRegistryError";
11
- }
12
- }
13
- const customToolRegistry = new Map();
14
- function normalizeSegment(value, field) {
15
- const normalized = value.trim();
16
- if (!normalized) {
17
- throw new CustomToolRegistryError(`Custom tool ${field} is required.`);
18
- }
19
- if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(normalized)) {
20
- throw new CustomToolRegistryError(`Invalid custom tool ${field}: "${value}". Use letters, numbers, "_" or "-".`);
21
- }
22
- return normalized;
23
- }
24
- function normalizeNamespace(namespace) {
25
- const resolved = normalizeSegment(namespace ?? DEFAULT_CUSTOM_NAMESPACE, "namespace");
26
- if (RESERVED_NAMESPACES.has(resolved.toLowerCase())) {
27
- throw new CustomToolRegistryError(`Namespace "${resolved}" is reserved for platform tools.`);
28
- }
29
- return resolved;
30
- }
31
- function normalizeName(name) {
32
- if (name.includes(".")) {
33
- throw new CustomToolRegistryError(`Custom tool name "${name}" must not include "."; use namespace + name.`);
34
- }
35
- return normalizeSegment(name, "name");
36
- }
37
- function buildFullName(namespace, shortName) {
38
- return `${namespace}.${shortName}`;
39
- }
40
- function unwrapSchema(schema) {
41
- if (schema instanceof z.ZodOptional) {
42
- return { schema: schema.unwrap(), required: false };
43
- }
44
- if (schema instanceof z.ZodDefault) {
45
- const removeDefault = schema.removeDefault;
46
- if (typeof removeDefault === "function") {
47
- return { schema: removeDefault.call(schema), required: false };
48
- }
49
- const inner = schema
50
- ._def?.innerType;
51
- return { schema: inner ?? schema, required: false };
52
- }
53
- return { schema, required: true };
54
- }
55
- function resolveParameterType(schema) {
56
- if (schema instanceof z.ZodString) {
57
- return { type: "string" };
58
- }
59
- if (schema instanceof z.ZodEnum) {
60
- return { type: "string", enum: schema.options };
61
- }
62
- if (schema instanceof z.ZodNativeEnum) {
63
- const values = Object.values(schema.enum).filter((value) => typeof value === "string");
64
- return values.length > 0
65
- ? { type: "string", enum: values }
66
- : { type: "string" };
67
- }
68
- if (schema instanceof z.ZodLiteral) {
69
- const literalValue = schema.value;
70
- if (typeof literalValue === "string") {
71
- return { type: "string", enum: [literalValue] };
72
- }
73
- if (typeof literalValue === "number") {
74
- return { type: "number" };
75
- }
76
- if (typeof literalValue === "boolean") {
77
- return { type: "boolean" };
78
- }
79
- return { type: "object" };
80
- }
81
- if (schema instanceof z.ZodNumber) {
82
- return { type: "number" };
83
- }
84
- if (schema instanceof z.ZodBoolean) {
85
- return { type: "boolean" };
86
- }
87
- if (schema instanceof z.ZodArray) {
88
- return { type: "array" };
89
- }
90
- return { type: "object" };
91
- }
92
- function schemaTypeLabel(schema) {
93
- if (schema instanceof z.ZodString || schema instanceof z.ZodEnum) {
94
- return "string";
95
- }
96
- if (schema instanceof z.ZodNumber) {
97
- return "number";
98
- }
99
- if (schema instanceof z.ZodBoolean) {
100
- return "boolean";
101
- }
102
- if (schema instanceof z.ZodArray) {
103
- return "array";
104
- }
105
- if (schema instanceof z.ZodObject) {
106
- return "object";
107
- }
108
- return "unknown";
109
- }
110
- function parseObjectSchema(value, fieldName) {
111
- if (!(value instanceof z.ZodObject)) {
112
- throw new CustomToolRegistryError(`${fieldName} must be a Zod object schema.`);
113
- }
114
- return value;
115
- }
116
- function deriveParameters(schema) {
117
- const shape = schema.shape;
118
- const parameters = {};
119
- const required = [];
120
- for (const [key, raw] of Object.entries(shape)) {
121
- const { schema: unwrapped, required: isRequired } = unwrapSchema(raw);
122
- const parameterType = resolveParameterType(unwrapped);
123
- parameters[key] = {
124
- type: parameterType.type,
125
- description: raw.description ?? `${key} parameter`,
126
- ...(parameterType.enum ? { enum: parameterType.enum } : {}),
127
- };
128
- if (isRequired) {
129
- required.push(key);
130
- }
131
- }
132
- return { parameters, required };
133
- }
134
- function deriveResponse(schema) {
135
- if (schema instanceof z.ZodObject) {
136
- const fields = Object.fromEntries(Object.entries(schema.shape).map(([key, value]) => {
137
- const zodValue = value;
138
- const label = schemaTypeLabel(unwrapSchema(zodValue).schema);
139
- const description = zodValue.description ?? "custom output field";
140
- return [key, `${label} — ${description}`];
141
- }));
142
- return {
143
- description: "Custom tool result payload",
144
- fields,
145
- };
146
- }
147
- return {
148
- description: "Custom tool result payload",
149
- fields: {
150
- result: `${schemaTypeLabel(schema)} — custom output payload`,
151
- },
152
- };
153
- }
154
- function buildContract(fullName, namespace, registration, inputSchema) {
155
- const { parameters, required } = deriveParameters(inputSchema);
156
- return {
157
- name: fullName,
158
- description: registration.description,
159
- parameters,
160
- required,
161
- response: deriveResponse(registration.outputSchema),
162
- ownerModule: registration.ownerModule ?? `${namespace}-extension`,
163
- ontologyPrimitive: registration.ontologyPrimitive ?? "task",
164
- tier: registration.tier ?? "workhorse",
165
- };
166
- }
167
- /**
168
- * Register a custom SDK tool in the in-memory registry.
169
- * @param registration - The tool definition including name, schemas, and handler.
170
- * @param options - Optional flags; set `replace` to overwrite an existing registration.
171
- * @returns The fully resolved registered tool entry.
172
- */
173
- export function registerCustomTool(registration, options = {}) {
174
- const namespace = normalizeNamespace(registration.namespace);
175
- const shortName = normalizeName(registration.name);
176
- const fullName = buildFullName(namespace, shortName);
177
- if (customToolRegistry.has(fullName) && !options.replace) {
178
- throw new CustomToolRegistryError(`Custom tool "${fullName}" is already registered.`);
179
- }
180
- const inputSchema = parseObjectSchema(registration.inputSchema, "inputSchema");
181
- const outputSchema = registration.outputSchema;
182
- const contract = buildContract(fullName, namespace, registration, inputSchema);
183
- const metadata = {
184
- category: registration.metadata?.category,
185
- tags: registration.metadata?.tags ?? [],
186
- requiredScopes: registration.metadata?.requiredScopes ?? ["custom:execute"],
187
- };
188
- const registered = {
189
- fullName,
190
- namespace,
191
- shortName,
192
- metadata,
193
- contract,
194
- inputSchema,
195
- outputSchema,
196
- invoke: async (input, context) => {
197
- const parsedInput = inputSchema.parse(input);
198
- const rawOutput = await registration.handler(parsedInput, context);
199
- return outputSchema.parse(rawOutput);
200
- },
201
- };
202
- customToolRegistry.set(fullName, registered);
203
- return registered;
204
- }
205
- /**
206
- * Remove a custom SDK tool from the registry.
207
- * @param fullName - Fully qualified tool name (`namespace.name`).
208
- * @returns `true` if the tool was found and removed, `false` otherwise.
209
- */
210
- export function unregisterCustomTool(fullName) {
211
- return customToolRegistry.delete(fullName.trim());
212
- }
213
- /**
214
- * Get a custom SDK tool by fully qualified name.
215
- * @param fullName - Fully qualified tool name (`namespace.name`).
216
- * @returns The registered tool entry, or `undefined` if not found.
217
- */
218
- export function getRegisteredCustomTool(fullName) {
219
- return customToolRegistry.get(fullName.trim());
220
- }
221
- /**
222
- * List all registered custom SDK tools, sorted alphabetically by full name.
223
- * @returns An array of all registered tool entries.
224
- */
225
- export function listRegisteredCustomTools() {
226
- return [...customToolRegistry.values()].sort((left, right) => left.fullName.localeCompare(right.fullName));
227
- }
228
- /**
229
- * Clear the registered custom tool registry.
230
- */
231
- export function clearRegisteredCustomTools() {
232
- customToolRegistry.clear();
233
- }
234
- /**
235
- * Invoke a registered custom SDK tool by fully qualified name.
236
- * @param fullName - Fully qualified tool name (`namespace.name`).
237
- * @param params - JSON-serializable input parameters for the tool handler.
238
- * @param context - Invocation context indicating the call source.
239
- * @returns The validated output from the tool handler.
240
- */
241
- export async function invokeRegisteredCustomTool(fullName, params, context) {
242
- const registered = getRegisteredCustomTool(fullName);
243
- if (!registered) {
244
- throw new CustomToolRegistryError(`Custom tool "${fullName}" is not registered.`);
245
- }
246
- return registered.invoke(params, context);
247
- }