@ganju/utils 0.0.10 → 0.0.12
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/dist/constants.d.ts +12 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +75 -0
- package/dist/index.d.ts +153 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/schema.d.ts +67 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +30 -4
- package/dist/syncableResource.d.ts +5 -0
- package/dist/syncableResource.d.ts.map +1 -1
- package/dist/syncableResource.js +13 -1
- package/dist/toolCatalog.d.ts +69 -0
- package/dist/toolCatalog.d.ts.map +1 -1
- package/dist/toolCatalog.js +150 -62
- package/dist/toolConfirmation.d.ts +58 -0
- package/dist/toolConfirmation.d.ts.map +1 -0
- package/dist/toolConfirmation.js +239 -0
- package/dist/toolLint.d.ts +35 -0
- package/dist/toolLint.d.ts.map +1 -0
- package/dist/toolLint.js +211 -0
- package/package.json +6 -1
- package/src/constants.ts +81 -0
- package/src/index.ts +58 -1
- package/src/schema.ts +41 -6
- package/src/syncableResource.ts +20 -0
- package/src/toolCatalog.ts +169 -62
- package/src/toolConfirmation.ts +291 -0
- package/src/toolLint.ts +277 -0
package/src/index.ts
CHANGED
|
@@ -117,7 +117,11 @@ import type { OAuthProviderConfig } from './oauthProviders';
|
|
|
117
117
|
import { resolveAttachment } from './attachment';
|
|
118
118
|
import { isExposedResource } from './exposedResource';
|
|
119
119
|
import type { ExposableResource } from './exposedResource';
|
|
120
|
-
import {
|
|
120
|
+
import {
|
|
121
|
+
isSyncableResource,
|
|
122
|
+
isResourceSyncInProgress,
|
|
123
|
+
resourceSyncProvider
|
|
124
|
+
} from './syncableResource';
|
|
121
125
|
import type {
|
|
122
126
|
SyncableResource,
|
|
123
127
|
ResourceSyncProvider
|
|
@@ -169,6 +173,7 @@ import type {
|
|
|
169
173
|
CatalogGroup,
|
|
170
174
|
CatalogTool,
|
|
171
175
|
CatalogToolDescriptor,
|
|
176
|
+
ToolAnnotations,
|
|
172
177
|
ToolGroupKey,
|
|
173
178
|
ToolKey
|
|
174
179
|
} from './toolCatalog';
|
|
@@ -220,6 +225,35 @@ import type {
|
|
|
220
225
|
ChannelBufferEnvelope,
|
|
221
226
|
ChannelBufferFlush
|
|
222
227
|
} from './channelDebounce';
|
|
228
|
+
import {
|
|
229
|
+
isConfirmationReply,
|
|
230
|
+
confirmationButtonLabels,
|
|
231
|
+
confirmationButtonId,
|
|
232
|
+
parseConfirmationButton,
|
|
233
|
+
languageFromPhoneNumber,
|
|
234
|
+
formatConfirmationSummary,
|
|
235
|
+
annotationsForEffect,
|
|
236
|
+
TOOL_EFFECTS,
|
|
237
|
+
isSensitiveTool,
|
|
238
|
+
stableJson
|
|
239
|
+
} from './toolConfirmation';
|
|
240
|
+
import type {
|
|
241
|
+
PendingToolConfirmation,
|
|
242
|
+
PendingToolConfirmations,
|
|
243
|
+
ToolEffect
|
|
244
|
+
} from './toolConfirmation';
|
|
245
|
+
import {
|
|
246
|
+
lintTools,
|
|
247
|
+
TOOL_LINT_RULES,
|
|
248
|
+
TOOL_LINT_MAX_TOOLS,
|
|
249
|
+
TOOL_LINT_MIN_DESCRIPTION_LENGTH
|
|
250
|
+
} from './toolLint';
|
|
251
|
+
import type {
|
|
252
|
+
LintableTool,
|
|
253
|
+
ToolLintFinding,
|
|
254
|
+
ToolLintOptions,
|
|
255
|
+
ToolLintRule
|
|
256
|
+
} from './toolLint';
|
|
223
257
|
import {
|
|
224
258
|
isResourceSourceEnabled,
|
|
225
259
|
isDownloadableSource,
|
|
@@ -301,6 +335,7 @@ export const utils = {
|
|
|
301
335
|
resolveAttachment,
|
|
302
336
|
isExposedResource,
|
|
303
337
|
isSyncableResource,
|
|
338
|
+
isResourceSyncInProgress,
|
|
304
339
|
resourceSyncProvider,
|
|
305
340
|
describeVendorError,
|
|
306
341
|
withDeadline,
|
|
@@ -358,6 +393,20 @@ export const utils = {
|
|
|
358
393
|
channelBufferKey,
|
|
359
394
|
batchCommand,
|
|
360
395
|
joinBufferedMessages,
|
|
396
|
+
isConfirmationReply,
|
|
397
|
+
confirmationButtonLabels,
|
|
398
|
+
confirmationButtonId,
|
|
399
|
+
parseConfirmationButton,
|
|
400
|
+
languageFromPhoneNumber,
|
|
401
|
+
formatConfirmationSummary,
|
|
402
|
+
annotationsForEffect,
|
|
403
|
+
TOOL_EFFECTS,
|
|
404
|
+
isSensitiveTool,
|
|
405
|
+
stableJson,
|
|
406
|
+
lintTools,
|
|
407
|
+
TOOL_LINT_RULES,
|
|
408
|
+
TOOL_LINT_MAX_TOOLS,
|
|
409
|
+
TOOL_LINT_MIN_DESCRIPTION_LENGTH,
|
|
361
410
|
isResourceSourceEnabled,
|
|
362
411
|
isDownloadableSource,
|
|
363
412
|
safeHostname,
|
|
@@ -386,6 +435,14 @@ export type {
|
|
|
386
435
|
CatalogGroup,
|
|
387
436
|
CatalogTool,
|
|
388
437
|
CatalogToolDescriptor,
|
|
438
|
+
ToolAnnotations,
|
|
439
|
+
PendingToolConfirmation,
|
|
440
|
+
PendingToolConfirmations,
|
|
441
|
+
ToolEffect,
|
|
442
|
+
LintableTool,
|
|
443
|
+
ToolLintFinding,
|
|
444
|
+
ToolLintOptions,
|
|
445
|
+
ToolLintRule,
|
|
389
446
|
ToolGroupKey,
|
|
390
447
|
ToolKey,
|
|
391
448
|
CalendarConfigField,
|
package/src/schema.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { isAllowedLlmBaseUrl, requiresLlmBaseUrl } from './llmBaseUrl';
|
|
|
5
5
|
import { isReservedSlug, isValidSlugFormat } from './slug';
|
|
6
6
|
import { isReservedToolName } from './reservedToolName';
|
|
7
7
|
import { slugifyTitle } from './slugifyTitle';
|
|
8
|
+
import { TOOL_EFFECTS } from './toolConfirmation';
|
|
8
9
|
|
|
9
10
|
// A prompt title becomes a slash command; it must not collide with a command
|
|
10
11
|
// the channel runner handles itself (e.g. `/link`).
|
|
@@ -43,11 +44,20 @@ const ORGANIZATION_CREATE_VIEW = z.object({
|
|
|
43
44
|
projectDescription: z.string().max(500)
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
// Each setting is saved on its own — the name from its form, the switches the
|
|
48
|
+
// moment they're flipped — so every field is optional, but not all at once.
|
|
49
|
+
const ORGANIZATION_UPDATE = z
|
|
50
|
+
.object({
|
|
51
|
+
id: z.uuid(),
|
|
52
|
+
userId: z.uuid(),
|
|
53
|
+
name: z.string().min(3).max(100).optional(),
|
|
54
|
+
requireToolConfirmation: z.boolean().optional()
|
|
55
|
+
})
|
|
56
|
+
.refine(
|
|
57
|
+
value =>
|
|
58
|
+
value.name !== undefined || value.requireToolConfirmation !== undefined,
|
|
59
|
+
{ message: 'Nothing to update' }
|
|
60
|
+
);
|
|
51
61
|
|
|
52
62
|
const ORGANIZATION_GET = z.object({
|
|
53
63
|
id: z.uuid(),
|
|
@@ -851,6 +861,12 @@ const HTTP_ENDPOINT_CONFIG = z
|
|
|
851
861
|
contentType: constants.HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_AUTO,
|
|
852
862
|
maxBytes: constants.HTTP_ENDPOINT_DEFAULT_MAX_BYTES
|
|
853
863
|
}),
|
|
864
|
+
// What the endpoint does, as its owner says (`read`, `write` or
|
|
865
|
+
// `sensitive`). Absent means the method decides: GET and HEAD read,
|
|
866
|
+
// anything else is sensitive — right for most, but a POST that only
|
|
867
|
+
// searches needs its owner to say so, or a confirming channel asks
|
|
868
|
+
// before every search.
|
|
869
|
+
effect: z.enum(TOOL_EFFECTS).optional(),
|
|
854
870
|
// Auth — credentials referenced by id, never inlined.
|
|
855
871
|
auth: HTTP_ENDPOINT_AUTH.default({
|
|
856
872
|
kind: constants.HTTP_ENDPOINT_AUTH_KIND_NONE
|
|
@@ -1056,6 +1072,19 @@ const CUSTOM_CODE_CONFIG_WRITE = CUSTOM_CODE_CONFIG.superRefine((cfg, ctx) => {
|
|
|
1056
1072
|
// that version registering, and the owner would see tools quietly disappear with
|
|
1057
1073
|
// only a log line to explain it. Reservation belongs to the manifest below,
|
|
1058
1074
|
// which is the write path.
|
|
1075
|
+
// MCP tool annotations as a tool's author declares them — every hint optional,
|
|
1076
|
+
// with the spec's own defaults for what is left out. The channel runner reads
|
|
1077
|
+
// them to decide what to confirm, so a tool that doesn't say it is read-only
|
|
1078
|
+
// is treated as one that may change things.
|
|
1079
|
+
const TOOL_ANNOTATIONS = z
|
|
1080
|
+
.object({
|
|
1081
|
+
readOnlyHint: z.boolean().optional(),
|
|
1082
|
+
destructiveHint: z.boolean().optional(),
|
|
1083
|
+
idempotentHint: z.boolean().optional(),
|
|
1084
|
+
openWorldHint: z.boolean().optional()
|
|
1085
|
+
})
|
|
1086
|
+
.strict();
|
|
1087
|
+
|
|
1059
1088
|
const CUSTOM_CODE_TOOL = z.object({
|
|
1060
1089
|
name: z
|
|
1061
1090
|
.string()
|
|
@@ -1068,7 +1097,8 @@ const CUSTOM_CODE_TOOL = z.object({
|
|
|
1068
1097
|
title: z.string().min(1).max(200).optional(),
|
|
1069
1098
|
description: z.string().max(2000).optional(),
|
|
1070
1099
|
inputSchema: SCHEMA_DEFINITION.default({ type: 'object', properties: {} }),
|
|
1071
|
-
outputSchema: SCHEMA_DEFINITION.optional()
|
|
1100
|
+
outputSchema: SCHEMA_DEFINITION.optional(),
|
|
1101
|
+
annotations: TOOL_ANNOTATIONS.optional()
|
|
1072
1102
|
});
|
|
1073
1103
|
|
|
1074
1104
|
// The manifest a `ganju deploy` (or the dashboard) uploads alongside a bundle.
|
|
@@ -1648,6 +1678,7 @@ export const Schema = {
|
|
|
1648
1678
|
CUSTOM_CODE_CONFIG,
|
|
1649
1679
|
CUSTOM_CODE_CONFIG_WRITE,
|
|
1650
1680
|
CUSTOM_CODE_TOOL,
|
|
1681
|
+
TOOL_ANNOTATIONS,
|
|
1651
1682
|
CUSTOM_CODE_MANIFEST,
|
|
1652
1683
|
ARTIFACT_CUSTOM_CODE_CREATE_VERSION,
|
|
1653
1684
|
ARTIFACT_CUSTOM_CODE_UPLOAD_BUNDLE,
|
|
@@ -1707,6 +1738,10 @@ export interface McpProxyDiscoveredTool {
|
|
|
1707
1738
|
title?: string;
|
|
1708
1739
|
description?: string;
|
|
1709
1740
|
inputSchema: unknown;
|
|
1741
|
+
// The remote's own annotations, passed through at registration. Absent on
|
|
1742
|
+
// discoveries made before they were recorded — the tool then counts as one
|
|
1743
|
+
// that may change things until the server is discovered again.
|
|
1744
|
+
annotations?: z.infer<typeof TOOL_ANNOTATIONS>;
|
|
1710
1745
|
}
|
|
1711
1746
|
|
|
1712
1747
|
// One remote resource discovered from a proxied MCP server. Identified by uri;
|
package/src/syncableResource.ts
CHANGED
|
@@ -46,3 +46,23 @@ export const resourceSyncProvider = (
|
|
|
46
46
|
|
|
47
47
|
export const isSyncableResource = (resource: SyncableResource): boolean =>
|
|
48
48
|
resourceSyncProvider(resource) !== null;
|
|
49
|
+
|
|
50
|
+
// Whether a sync root is still genuinely syncing: PENDING, and started within
|
|
51
|
+
// RESOURCE_SYNC_STALE_MS. Past that, a PENDING root is stuck — a job was lost
|
|
52
|
+
// or gave up — and may be synced again, which re-queues whatever it left
|
|
53
|
+
// unfinished. A root that never synced counts from its creation, so a first
|
|
54
|
+
// import that stalled recovers too.
|
|
55
|
+
export const isResourceSyncInProgress = (
|
|
56
|
+
resource: {
|
|
57
|
+
status: string;
|
|
58
|
+
syncStartedAt?: Date | string | null;
|
|
59
|
+
createdAt: Date | string;
|
|
60
|
+
},
|
|
61
|
+
now: number = Date.now()
|
|
62
|
+
): boolean => {
|
|
63
|
+
if (resource.status !== constants.STATUS_PENDING) return false;
|
|
64
|
+
const startedAt = new Date(
|
|
65
|
+
resource.syncStartedAt ?? resource.createdAt
|
|
66
|
+
).getTime();
|
|
67
|
+
return now - startedAt < constants.RESOURCE_SYNC_STALE_MS;
|
|
68
|
+
};
|