@ethisyscore/plugin-ui 1.93.0 → 1.95.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 +2 -1
- package/dist/components/connectors/index.cjs +656 -2
- package/dist/components/connectors/index.cjs.map +1 -1
- package/dist/components/connectors/index.d.cts +135 -373
- package/dist/components/connectors/index.d.ts +135 -373
- package/dist/components/connectors/index.js +652 -5
- package/dist/components/connectors/index.js.map +1 -1
- package/dist/components/ui/index.cjs +4 -0
- package/dist/components/ui/index.d.cts +31 -1
- package/dist/components/ui/index.d.ts +31 -1
- package/dist/components/ui/index.js +1 -1
- package/dist/connector-mappings/index.cjs +1061 -0
- package/dist/connector-mappings/index.cjs.map +1 -0
- package/dist/connector-mappings/index.d.cts +1274 -0
- package/dist/connector-mappings/index.d.ts +1274 -0
- package/dist/connector-mappings/index.js +1020 -0
- package/dist/connector-mappings/index.js.map +1 -0
- package/dist/mappingQueryKeys-BcbA0TGy.d.cts +473 -0
- package/dist/mappingQueryKeys-BcbA0TGy.d.ts +473 -0
- package/dist/mcpService-4h3sKDpW.d.cts +35 -0
- package/dist/mcpService-4h3sKDpW.d.ts +35 -0
- package/dist/platform-react/index.cjs +52 -4
- package/dist/platform-react/index.cjs.map +1 -1
- package/dist/platform-react/index.d.cts +30 -35
- package/dist/platform-react/index.d.ts +30 -35
- package/dist/platform-react/index.js +53 -6
- package/dist/platform-react/index.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,1274 @@
|
|
|
1
|
+
import { T as ToolInvoker } from '../mcpService-4h3sKDpW.cjs';
|
|
2
|
+
import { C as ConnectorCanonicalField, h as MappingCompletenessResult, c as ConnectorMappingSuggestionResult, m as TestToolMappingResult, i as MappingReadiness, g as MappingCompletenessItem, d as ConnectorMappingTranslationEditorProps, f as MappingCompletenessBannerProps } from '../mappingQueryKeys-BcbA0TGy.cjs';
|
|
3
|
+
export { a as CONNECTOR_CONTROL_LABEL, b as ConnectorCanonicalFieldKind, P as PLUGIN_PROVIDER_CONTROL_LABEL, j as PROVIDER_CONTROL_LABEL, k as PROVIDER_OPTION_LABEL, T as TestToolMappingExtractedField, l as TestToolMappingRequest, n as connectorMappingCompletenessQueryKey, o as connectorMappingsQueryKey, p as connectorMappingsRootQueryKey, q as pluginTargetMappingsQueryKey, s as selectIncompleteOperations, r as selectMappingReadiness, t as selectOptionalIncompleteOperations, u as selectRequiredIncompleteOperations } from '../mappingQueryKeys-BcbA0TGy.cjs';
|
|
4
|
+
import { UseMutationResult } from '@tanstack/react-query';
|
|
5
|
+
import 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Wire shapes for the connector tool-mapping surface — the DTOs the host's `connectors:mappings:*`
|
|
9
|
+
* tools return, plus the argument shapes the hooks in this module accept.
|
|
10
|
+
*
|
|
11
|
+
* Declared here rather than in a plugin's generated contracts for the same reason the completeness
|
|
12
|
+
* shapes moved to `components/connectors/types`: this package sits BELOW the plugins, and three of
|
|
13
|
+
* them held their own copy of every type below. The copies are what let the surfaces drift.
|
|
14
|
+
*
|
|
15
|
+
* The rendering-side shapes (`MappingCompletenessItem`, `ConnectorCanonicalField`,
|
|
16
|
+
* `TestToolMappingRequest`, …) are NOT redeclared here. They already live in
|
|
17
|
+
* `components/connectors/types` next to the components that consume them, and are re-exported by
|
|
18
|
+
* this module's barrel so a consumer needs one import.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** How a mapping came to exist. Mirrors the backend `ToolMappingSource` enum (snake_case wire). */
|
|
22
|
+
declare const MAPPING_SOURCE: {
|
|
23
|
+
readonly keyword: "keyword";
|
|
24
|
+
readonly ai: "ai";
|
|
25
|
+
readonly manual: "manual";
|
|
26
|
+
};
|
|
27
|
+
type ConnectorMappingSource = (typeof MAPPING_SOURCE)[keyof typeof MAPPING_SOURCE];
|
|
28
|
+
/** Why the AI matcher was unavailable for an auto-match run. `null` means it was available. */
|
|
29
|
+
declare const AI_UNAVAILABLE_REASON: {
|
|
30
|
+
readonly Timeout: "timeout";
|
|
31
|
+
readonly ProviderError: "provider_error";
|
|
32
|
+
readonly ParseError: "parse_error";
|
|
33
|
+
readonly ContextTooLarge: "context_too_large";
|
|
34
|
+
readonly NoConnectorConfigured: "no_connector_configured";
|
|
35
|
+
};
|
|
36
|
+
type ConnectorAiUnavailableReason = (typeof AI_UNAVAILABLE_REASON)[keyof typeof AI_UNAVAILABLE_REASON] | null;
|
|
37
|
+
/** Why a concurrent auto-match request was refused. `null` means no conflict. */
|
|
38
|
+
declare const AUTO_MATCH_CONFLICT_REASON: {
|
|
39
|
+
readonly AnotherAutoMatchInProgress: "another_auto_match_in_progress";
|
|
40
|
+
};
|
|
41
|
+
type ConnectorAutoMatchConflictReason = (typeof AUTO_MATCH_CONFLICT_REASON)[keyof typeof AUTO_MATCH_CONFLICT_REASON] | null;
|
|
42
|
+
/**
|
|
43
|
+
* What a mapping resolves to, as this surface spells it: a connector to a third-party system, or
|
|
44
|
+
* another installed application.
|
|
45
|
+
*
|
|
46
|
+
* Lower-case, and NOT the string the host stores. The host persists its `ToolMappingTargetKind` by
|
|
47
|
+
* NAME and case (`"Connector"` / `"Plugin"`), so the two vocabularies are declared separately and
|
|
48
|
+
* converted by `toWireTargetKind` / `fromWireTargetKind` in `targetSelection`. Sharing one literal
|
|
49
|
+
* would make a render-side comparison and a wire value the same string by coincidence, and the
|
|
50
|
+
* first time either side changed case the other would fail silently - the stored value matching
|
|
51
|
+
* neither branch of the host's resolver nor its CHECK constraint.
|
|
52
|
+
*/
|
|
53
|
+
declare const MAPPING_TARGET_KIND: {
|
|
54
|
+
readonly connector: "connector";
|
|
55
|
+
readonly plugin: "plugin";
|
|
56
|
+
};
|
|
57
|
+
type MappingTargetKind = (typeof MAPPING_TARGET_KIND)[keyof typeof MAPPING_TARGET_KIND];
|
|
58
|
+
/**
|
|
59
|
+
* Which kind of thing serves a module. The wire values are the host's
|
|
60
|
+
* `ToolMappingTargetKind` member NAMES, so they are a contract rather than a display label.
|
|
61
|
+
*/
|
|
62
|
+
declare const MODULE_PROVIDER_KIND: {
|
|
63
|
+
readonly Connector: "Connector";
|
|
64
|
+
readonly Plugin: "Plugin";
|
|
65
|
+
};
|
|
66
|
+
type ModuleProviderKind = (typeof MODULE_PROVIDER_KIND)[keyof typeof MODULE_PROVIDER_KIND];
|
|
67
|
+
/**
|
|
68
|
+
* The same vocabulary as the host stores and matches it, keyed the way the UI spells its kinds so a
|
|
69
|
+
* render-side value converts without a cast. See {@link MAPPING_TARGET_KIND}.
|
|
70
|
+
*
|
|
71
|
+
* The STRINGS come from {@link MODULE_PROVIDER_KIND} rather than being retyped, because this file
|
|
72
|
+
* arrived at the same contract twice - once for the per-operation target and once for the
|
|
73
|
+
* per-module provider - and two literal copies of a value the host matches by name is the drift
|
|
74
|
+
* this vocabulary split exists to prevent, not an instance of it.
|
|
75
|
+
*/
|
|
76
|
+
declare const MAPPING_TARGET_KIND_WIRE: {
|
|
77
|
+
readonly connector: "Connector";
|
|
78
|
+
readonly plugin: "Plugin";
|
|
79
|
+
};
|
|
80
|
+
type MappingTargetKindWire = (typeof MAPPING_TARGET_KIND_WIRE)[keyof typeof MAPPING_TARGET_KIND_WIRE];
|
|
81
|
+
/**
|
|
82
|
+
* The operator-editable translation attached to one mapping row.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* All three members are `null`-able and **all three must be sent on every write**. The host's
|
|
86
|
+
* `connectors:mappings:set` treats an omitted or null member as "no operator override" and writes
|
|
87
|
+
* NULL, so a partial write silently destroys whichever members it left out. This is not a style
|
|
88
|
+
* point: picking a tool used to clear an operator's field translations, confirmed against the
|
|
89
|
+
* production database. {@link ConnectorMappingWriteArgs} and `useConnectorMappingMutations` are what
|
|
90
|
+
* make a write complete; see the fill-in in `useConnectorMappingMutations`.
|
|
91
|
+
*/
|
|
92
|
+
interface ConnectorMappingTranslationState {
|
|
93
|
+
/** Canonical request field → tool input field name. `null` ⇒ identity translation. */
|
|
94
|
+
requestFieldMap: Record<string, string> | null;
|
|
95
|
+
/** Canonical response field → JSON path against the tool's response. `null` ⇒ identity. */
|
|
96
|
+
responseFieldPaths: Record<string, string> | null;
|
|
97
|
+
/**
|
|
98
|
+
* Constant tool-request parameters injected into every call regardless of the canonical input
|
|
99
|
+
* (tool field name → literal value, e.g. `method: "get_reviews"`). `null` ⇒ none configured.
|
|
100
|
+
*/
|
|
101
|
+
staticRequestFields: Record<string, string> | null;
|
|
102
|
+
}
|
|
103
|
+
/** One row of the operation → tool mapping table, as `connectors:mappings:list` returns it. */
|
|
104
|
+
interface ConnectorMappingItem {
|
|
105
|
+
/** Canonical operation key, e.g. `list-commits`. */
|
|
106
|
+
operationKey: string;
|
|
107
|
+
/** Human label, from the module's catalogue. */
|
|
108
|
+
label: string;
|
|
109
|
+
/** Whether the module declares this operation as required. */
|
|
110
|
+
isRequired: boolean;
|
|
111
|
+
/** Tool the operation currently resolves to, or `null` when unmapped. */
|
|
112
|
+
mappedToolName: string | null;
|
|
113
|
+
source: ConnectorMappingSource | null;
|
|
114
|
+
/** AI match confidence in `0..1`, or `null` for keyword/manual matches. */
|
|
115
|
+
confidence: number | null;
|
|
116
|
+
/**
|
|
117
|
+
* Opaque concurrency token. The host declares it as `byte[]`, which crosses the wire as base64:
|
|
118
|
+
* read one here and hand the same string back on write. `null` means no stored row exists yet,
|
|
119
|
+
* which callers send as `""` so the host upserts.
|
|
120
|
+
*/
|
|
121
|
+
rowVersion: string | null;
|
|
122
|
+
/** True when the row is an AI match below the auto-accept threshold and wants an operator's eye. */
|
|
123
|
+
requiresReview?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Canonical request schema declared by the operation's owning module. `null` when the operation
|
|
126
|
+
* declares none, in which case there is nothing operator-meaningful to translate and the
|
|
127
|
+
* translation affordance stays hidden.
|
|
128
|
+
*/
|
|
129
|
+
canonicalRequestFields?: ConnectorCanonicalField[] | null;
|
|
130
|
+
/** Canonical response schema. Same null semantics as {@link canonicalRequestFields}. */
|
|
131
|
+
canonicalResponseFields?: ConnectorCanonicalField[] | null;
|
|
132
|
+
requestFieldMap?: Record<string, string> | null;
|
|
133
|
+
responseFieldPaths?: Record<string, string> | null;
|
|
134
|
+
staticRequestFields?: Record<string, string> | null;
|
|
135
|
+
}
|
|
136
|
+
/** One operation's outcome from an auto-match run. Mirrors `ConnectorAutoMatchItem`. */
|
|
137
|
+
interface ConnectorAutoMatchItem {
|
|
138
|
+
operationKey: string;
|
|
139
|
+
/**
|
|
140
|
+
* `null` when this operation matched nothing. An item is returned for EVERY operation regardless,
|
|
141
|
+
* so `items.length` is the operation count and not the match count — reading it as the latter
|
|
142
|
+
* reports success on a run that mapped nothing.
|
|
143
|
+
*/
|
|
144
|
+
mappedToolName: string | null;
|
|
145
|
+
source: ConnectorMappingSource | null;
|
|
146
|
+
confidence: number | null;
|
|
147
|
+
requiresReview?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* A tool that keyword-matched but was rejected, and the required parameters it could not be
|
|
150
|
+
* satisfied with. The host has always returned both; the types this was promoted from omitted
|
|
151
|
+
* them in two of three plugins, which discarded the only machine-readable explanation of why an
|
|
152
|
+
* auto-match came back empty.
|
|
153
|
+
*/
|
|
154
|
+
discardedToolName?: string | null;
|
|
155
|
+
unsatisfiedToolParameters?: string[] | null;
|
|
156
|
+
}
|
|
157
|
+
/** Raw auto-match result, before {@link AutoMatchSummary} reduces it for the banner. */
|
|
158
|
+
interface AutoMatchResponse {
|
|
159
|
+
items: ConnectorAutoMatchItem[];
|
|
160
|
+
aiAvailable: boolean;
|
|
161
|
+
aiUnavailableReason: ConnectorAiUnavailableReason;
|
|
162
|
+
unmatchedRequiredCount: number;
|
|
163
|
+
conflictReason: ConnectorAutoMatchConflictReason;
|
|
164
|
+
}
|
|
165
|
+
/** Per-source counts reduced from an {@link AutoMatchResponse}, for the outcome banner. */
|
|
166
|
+
interface AutoMatchSummary {
|
|
167
|
+
aiAvailable: boolean;
|
|
168
|
+
aiUnavailableReason: ConnectorAiUnavailableReason;
|
|
169
|
+
unmatchedRequiredCount: number;
|
|
170
|
+
matchedByKeyword: number;
|
|
171
|
+
matchedByAi: number;
|
|
172
|
+
conflictReason: ConnectorAutoMatchConflictReason;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Arguments for one mapping write.
|
|
176
|
+
*
|
|
177
|
+
* The three translation members are OPTIONAL here and required on the wire, and the gap between
|
|
178
|
+
* those two facts is deliberate: a caller that is only changing the tool omits them, and
|
|
179
|
+
* `useConnectorMappingMutations` fills each one from the row it already holds before the write
|
|
180
|
+
* leaves. Omitting a member therefore means "leave it as it is", never "clear it". A caller that
|
|
181
|
+
* genuinely wants to clear one passes an explicit `null`.
|
|
182
|
+
*/
|
|
183
|
+
interface ConnectorMappingWriteArgs {
|
|
184
|
+
operationKey: string;
|
|
185
|
+
/** `""` is the unmap request; the hooks route it to `connectors:mappings:delete`. */
|
|
186
|
+
toolName: string;
|
|
187
|
+
/** Row version read from {@link ConnectorMappingItem.rowVersion}, or `""` for a fresh row. */
|
|
188
|
+
rowVersion: string;
|
|
189
|
+
requestFieldMap?: Record<string, string> | null;
|
|
190
|
+
responseFieldPaths?: Record<string, string> | null;
|
|
191
|
+
staticRequestFields?: Record<string, string> | null;
|
|
192
|
+
}
|
|
193
|
+
/** Body for the per-operation AI translation suggestion. */
|
|
194
|
+
interface SuggestMappingTranslationBody {
|
|
195
|
+
operationKey: string;
|
|
196
|
+
toolName: string;
|
|
197
|
+
/** Sample values the operator typed. The host declares this non-nullable; absent ⇒ `{}`. */
|
|
198
|
+
sampleArgs?: Record<string, unknown>;
|
|
199
|
+
}
|
|
200
|
+
/** Body for the translation dry-run. `connectorId` / `moduleKey` are passed alongside, not here. */
|
|
201
|
+
interface TestMappingBody {
|
|
202
|
+
operationKey: string;
|
|
203
|
+
toolName: string;
|
|
204
|
+
requestFieldMap: Record<string, string> | null;
|
|
205
|
+
responseFieldPaths: Record<string, string> | null;
|
|
206
|
+
/** The DRAFT static params, so the dry-run exercises the discriminator the operator is testing. */
|
|
207
|
+
staticRequestFields: Record<string, string> | null;
|
|
208
|
+
sampleArgs: Record<string, unknown>;
|
|
209
|
+
}
|
|
210
|
+
/** A connector as `settings:list-connectors` projects it. */
|
|
211
|
+
interface ConnectorSummary {
|
|
212
|
+
id: string;
|
|
213
|
+
name: string;
|
|
214
|
+
slug?: string | null;
|
|
215
|
+
isEnabled: boolean;
|
|
216
|
+
}
|
|
217
|
+
/** One tool a connector exposes, as `settings:get-connector` projects it. */
|
|
218
|
+
interface ConnectorToolSummary {
|
|
219
|
+
toolName: string;
|
|
220
|
+
description?: string | null;
|
|
221
|
+
isEnabled: boolean;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Which provider an organisation has chosen to serve a module.
|
|
225
|
+
*
|
|
226
|
+
* @remarks
|
|
227
|
+
* `targetKind` is `null` when the organisation has made NO choice, and the host deliberately does
|
|
228
|
+
* not default it to `"Connector"`. "Nobody has chosen yet" and "chose a connector that has no
|
|
229
|
+
* mapping yet" call for different remedies, and a surface that cannot tell them apart shows a
|
|
230
|
+
* choice nobody made.
|
|
231
|
+
*/
|
|
232
|
+
interface ModuleProvider {
|
|
233
|
+
targetKind: ModuleProviderKind | null;
|
|
234
|
+
connectorId: string | null;
|
|
235
|
+
targetExtensionId: string | null;
|
|
236
|
+
/**
|
|
237
|
+
* Concurrency token of the stored selection, `null` when there is none yet.
|
|
238
|
+
*
|
|
239
|
+
* Opaque, exactly like a mapping's: read it here and hand the same string back to
|
|
240
|
+
* `setModuleProvider`. Nothing on this side parses or constructs one.
|
|
241
|
+
*/
|
|
242
|
+
rowVersion: string | null;
|
|
243
|
+
}
|
|
244
|
+
/** One tool an installed application exposes, as the provider picker renders it. */
|
|
245
|
+
interface PluginProviderTool {
|
|
246
|
+
/** Fully namespaced, e.g. `hr:get-employees-by-ids`. A plugin tool routes under this name verbatim. */
|
|
247
|
+
toolName: string;
|
|
248
|
+
description?: string | null;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* One installed application an operator may choose to serve a module, with the tools it can answer
|
|
252
|
+
* the module's operations with.
|
|
253
|
+
*
|
|
254
|
+
* @remarks
|
|
255
|
+
* **The candidate set is narrower than "every tool that routes", and has to be.** The host's write
|
|
256
|
+
* path refuses a plugin-target mapping naming a tool the CONSUMING application did not declare in
|
|
257
|
+
* its own manifest `mcpToolDependencies`, because such a row is authority to invoke another
|
|
258
|
+
* application's tool with no per-tool permission check. A picker offering a wider list offers
|
|
259
|
+
* choices the save will reject, so whatever supplies this must apply the same bound - and must also
|
|
260
|
+
* filter by the caller's own permissions, which the host's routable-tool listing explicitly does
|
|
261
|
+
* not.
|
|
262
|
+
*
|
|
263
|
+
* There is no endpoint that returns this, in any transport, which is why it is an INPUT to
|
|
264
|
+
* `useConnectorToolMappings` rather than something this package fetches.
|
|
265
|
+
*/
|
|
266
|
+
interface PluginProviderCandidate {
|
|
267
|
+
extensionId: string;
|
|
268
|
+
/** Display name of the installed application. */
|
|
269
|
+
name: string;
|
|
270
|
+
tools: PluginProviderTool[];
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* A mapping row whose target is another installed application, as the plugin-mode table renders it.
|
|
274
|
+
*
|
|
275
|
+
* Same shape as {@link ConnectorMappingItem} plus the providing extension, which a connector-scoped
|
|
276
|
+
* row has no need of because the connector is the scope of the read.
|
|
277
|
+
*/
|
|
278
|
+
interface PluginTargetMappingItem extends ConnectorMappingItem {
|
|
279
|
+
/** The providing extension, or `null` when the operation is unmapped. */
|
|
280
|
+
targetExtensionId: string | null;
|
|
281
|
+
}
|
|
282
|
+
/** Arguments for one plugin-target mapping write. */
|
|
283
|
+
interface SetPluginTargetMappingArgs {
|
|
284
|
+
operationKey: string;
|
|
285
|
+
/** The PROVIDING extension. Required by the host even on an unmap, which is how it finds the row. */
|
|
286
|
+
targetExtensionId: string;
|
|
287
|
+
/** `""` is the unmap request, matching {@link ConnectorMappingWriteArgs.toolName}. */
|
|
288
|
+
toolName: string;
|
|
289
|
+
/** Row version, or `""` for a fresh row. Not enforced by the host on the unmap branch. */
|
|
290
|
+
rowVersion: string;
|
|
291
|
+
}
|
|
292
|
+
/** The stored plugin-target mapping after a write. */
|
|
293
|
+
interface SetPluginTargetMappingResult {
|
|
294
|
+
/** `null` when the operation is now unmapped. */
|
|
295
|
+
toolName: string | null;
|
|
296
|
+
targetExtensionId: string | null;
|
|
297
|
+
/** `""` after an unmap, where there is no row left to hold a token. */
|
|
298
|
+
rowVersion: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The mapping transport the hooks in this module require.
|
|
303
|
+
*
|
|
304
|
+
* Narrow on purpose: exactly the seven host tools a Tool Mappings page reads and writes, and nothing
|
|
305
|
+
* about connectors themselves. Connector enumeration differs genuinely between plugins — one reads
|
|
306
|
+
* `settings:list-connectors`, another pages a full connector CRUD service — so it is a SEPARATE
|
|
307
|
+
* interface ({@link IConnectorEnumerationService}) that a consumer takes only if it wants the
|
|
308
|
+
* default implementation.
|
|
309
|
+
*
|
|
310
|
+
* Declared as an interface rather than assumed to be {@link ConnectorMappingService} so a consumer
|
|
311
|
+
* can supply a mock, or its own transport, without subclassing.
|
|
312
|
+
*/
|
|
313
|
+
interface IConnectorMappingService {
|
|
314
|
+
/** `connectors:mappings:list` */
|
|
315
|
+
getMappings(connectorId: string, moduleKey: string): Promise<ConnectorMappingItem[]>;
|
|
316
|
+
/**
|
|
317
|
+
* `connectors:mappings:set`
|
|
318
|
+
*
|
|
319
|
+
* Every translation member is sent, including nulls, because the host writes NULL for a null
|
|
320
|
+
* member. Callers must pass the COMPLETE translation; `useConnectorMappingMutations` is where
|
|
321
|
+
* that is guaranteed.
|
|
322
|
+
*
|
|
323
|
+
* @returns the replacement row version, or `null` when the host returned none.
|
|
324
|
+
*/
|
|
325
|
+
updateMapping(connectorId: string, moduleKey: string, operationKey: string, toolName: string, rowVersion: string, requestFieldMap: Record<string, string> | null, responseFieldPaths: Record<string, string> | null, staticRequestFields: Record<string, string> | null): Promise<string | null>;
|
|
326
|
+
/**
|
|
327
|
+
* `connectors:mappings:delete`
|
|
328
|
+
*
|
|
329
|
+
* Distinct from an empty-tool-name write: the host's set validator rejects an empty tool name, so
|
|
330
|
+
* a clear has to be a delete.
|
|
331
|
+
*/
|
|
332
|
+
deleteMapping(connectorId: string, moduleKey: string, operationKey: string, rowVersion: string): Promise<void>;
|
|
333
|
+
/**
|
|
334
|
+
* `connectors:mappings:auto-match`
|
|
335
|
+
*
|
|
336
|
+
* The MCP namespace is derived server-side from `connectorId`. A slug parameter used to sit here
|
|
337
|
+
* and was a live bug: the implementation forwarded it as `moduleKey`, so the tool received no
|
|
338
|
+
* slug and rejected every call, which is what made the Auto-match button appear to do nothing.
|
|
339
|
+
*/
|
|
340
|
+
autoMatch(connectorId: string, moduleKey: string): Promise<AutoMatchResponse>;
|
|
341
|
+
/**
|
|
342
|
+
* `connectors:mappings:completeness`
|
|
343
|
+
*
|
|
344
|
+
* Per-operation completeness of the module's canonical operations against the connector's LIVE
|
|
345
|
+
* tools. An operation with a non-empty `unmappedRequiredCanonicalFields` is incomplete whether or
|
|
346
|
+
* not it has a tool mapped.
|
|
347
|
+
*/
|
|
348
|
+
getMappingCompleteness(connectorId: string, moduleKey: string): Promise<MappingCompletenessResult>;
|
|
349
|
+
/**
|
|
350
|
+
* `connectors:mappings:suggest-translation`
|
|
351
|
+
*
|
|
352
|
+
* Opt-in AI suggestion for one operation's field translation. Row-independent — the caller
|
|
353
|
+
* supplies the tool name — so it works before anything is saved. `success: false` carries a
|
|
354
|
+
* human-readable `error`; it is not an exception.
|
|
355
|
+
*/
|
|
356
|
+
suggestMappingTranslation(connectorId: string, moduleKey: string, body: SuggestMappingTranslationBody): Promise<ConnectorMappingSuggestionResult>;
|
|
357
|
+
/**
|
|
358
|
+
* `connectors:mappings:test`
|
|
359
|
+
*
|
|
360
|
+
* Dry-run of a tool + translation through the production path. Also row-independent: the caller
|
|
361
|
+
* supplies the tool name AND the translation being tried, which is what makes testing an UNSAVED
|
|
362
|
+
* edit possible. A tool-invocation or extraction failure comes back as `success: false`, not as a
|
|
363
|
+
* thrown error.
|
|
364
|
+
*/
|
|
365
|
+
testMapping(connectorId: string, moduleKey: string, body: TestMappingBody): Promise<TestToolMappingResult>;
|
|
366
|
+
/**
|
|
367
|
+
* `connectors:mappings:get-provider`
|
|
368
|
+
*
|
|
369
|
+
* Which provider the organisation has chosen for a module. This is the ORGANISATION's answer,
|
|
370
|
+
* which is the point: before it existed, a plugin had to keep the choice in browser storage, so
|
|
371
|
+
* one operator's selection was invisible to a colleague and the two never reconciled.
|
|
372
|
+
*/
|
|
373
|
+
getModuleProvider(moduleKey: string): Promise<ModuleProvider>;
|
|
374
|
+
/**
|
|
375
|
+
* `connectors:mappings:set-provider`
|
|
376
|
+
*
|
|
377
|
+
* Choose the provider. Mappings stay per-connector, so switching provider and switching back
|
|
378
|
+
* preserves the translation work already done against each.
|
|
379
|
+
*
|
|
380
|
+
* `rowVersion` is the token from {@link getModuleProvider}, or `null` for a module that has never
|
|
381
|
+
* had a selection; the host rejects a mismatch rather than overwriting a concurrent change. The
|
|
382
|
+
* response carries the refreshed token, so two writes in a row need no re-read between them.
|
|
383
|
+
*/
|
|
384
|
+
setModuleProvider(moduleKey: string, targetKind: ModuleProviderKind, connectorId: string | null, targetExtensionId: string | null, rowVersion: string | null): Promise<ModuleProvider>;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Connector enumeration for the mapping picker — deliberately separate from
|
|
388
|
+
* {@link IConnectorMappingService}.
|
|
389
|
+
*
|
|
390
|
+
* Only two of these calls exist because only two are needed: list the connectors, and read one
|
|
391
|
+
* connector's tools. A plugin that already has a connector CRUD service should keep using it and
|
|
392
|
+
* ignore this interface entirely; a plugin that only needs to populate a picker can take the
|
|
393
|
+
* default implementation on {@link ConnectorMappingService} instead of porting a CRUD subsystem
|
|
394
|
+
* that would have no other caller.
|
|
395
|
+
*/
|
|
396
|
+
interface IConnectorEnumerationService {
|
|
397
|
+
/** `settings:list-connectors` — enumeration only, never edits a connector. */
|
|
398
|
+
listConnectors(): Promise<ConnectorSummary[]>;
|
|
399
|
+
/**
|
|
400
|
+
* `settings:get-connector` — one connector's tools.
|
|
401
|
+
*
|
|
402
|
+
* The DETAIL call, not the list one: the list projection carries only tool COUNTS and no names,
|
|
403
|
+
* so a picker built on it renders an empty dropdown beside a connector advertising twelve tools.
|
|
404
|
+
*/
|
|
405
|
+
listConnectorTools(connectorId: string): Promise<ConnectorToolSummary[]>;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* The connector tool-mapping surface, served entirely by the host's `connectors:mappings:*` tools.
|
|
410
|
+
*
|
|
411
|
+
* ## Why the host store and not a plugin table
|
|
412
|
+
*
|
|
413
|
+
* A plugin resolves its operations at runtime through the platform's `resolve-mapping`, so the rows
|
|
414
|
+
* have to live in the platform store. Three plugins each owned their mappings in their own schema
|
|
415
|
+
* first, and every platform-side tool was blind to them: Super Admin could not inspect or repair
|
|
416
|
+
* them, and platform mapping completeness did not cover them.
|
|
417
|
+
*
|
|
418
|
+
* ## Transport is injected
|
|
419
|
+
*
|
|
420
|
+
* The constructor takes a {@link ToolInvoker} — the same shape `BaseMcpService` takes — rather than
|
|
421
|
+
* importing one. That keeps this class free of `@ethisyscore/extension-runtime`, so it is unit
|
|
422
|
+
* testable with a plain function, and it is why this does not extend `BaseMcpService`: the base
|
|
423
|
+
* class exists to give a lifted REST service a `this.tool(...)`, and there is no REST ancestry here
|
|
424
|
+
* to accommodate. A plugin constructs it exactly as before:
|
|
425
|
+
*
|
|
426
|
+
* ```ts
|
|
427
|
+
* const invoker = useToolInvokerMap(CONNECTOR_MAPPING_TOOLS);
|
|
428
|
+
* const service = useMemo(() => new ConnectorMappingService(invoker), [invoker]);
|
|
429
|
+
* ```
|
|
430
|
+
*
|
|
431
|
+
* ## Row versions are opaque
|
|
432
|
+
*
|
|
433
|
+
* The host declares them as `byte[]`, which crosses the wire as base64. Read one from
|
|
434
|
+
* {@link getMappings} and hand the same string back on write; nothing on this side parses or
|
|
435
|
+
* constructs one.
|
|
436
|
+
*
|
|
437
|
+
* ## Translation is NOT a patch on this surface
|
|
438
|
+
*
|
|
439
|
+
* The host writes NULL for a null member, so every save must carry the complete translation.
|
|
440
|
+
* Sending only the field being changed silently clears the other two — that defect reached
|
|
441
|
+
* production and had to be fixed three times, once per plugin, which is the reason this class is
|
|
442
|
+
* here. `useConnectorMappingMutations` is where completeness is enforced; this class sends what it
|
|
443
|
+
* is given, and does not paper over a partial write.
|
|
444
|
+
*
|
|
445
|
+
* ## Every read is null-tolerant
|
|
446
|
+
*
|
|
447
|
+
* Promoted from the three copies, taking the most hardened behaviour of each. A `Result` with no
|
|
448
|
+
* value is representable on the wire, so `response.items` on an absent payload throws inside a
|
|
449
|
+
* react-query `queryFn` or, worse, inside an `onSuccess` where the rejection is unhandled. A page
|
|
450
|
+
* that reports nothing beats a page that crashed.
|
|
451
|
+
*/
|
|
452
|
+
declare class ConnectorMappingService implements IConnectorMappingService, IConnectorEnumerationService {
|
|
453
|
+
private readonly invoker;
|
|
454
|
+
constructor(invoker: ToolInvoker);
|
|
455
|
+
/** Single seam every method goes through, so a subclass can wrap logging or retries in one place. */
|
|
456
|
+
protected tool<TRes>(name: string, args?: unknown): Promise<TRes>;
|
|
457
|
+
getMappings(connectorId: string, moduleKey: string): Promise<ConnectorMappingItem[]>;
|
|
458
|
+
updateMapping(connectorId: string, moduleKey: string, operationKey: string, toolName: string, rowVersion: string, requestFieldMap: Record<string, string> | null, responseFieldPaths: Record<string, string> | null, staticRequestFields: Record<string, string> | null): Promise<string | null>;
|
|
459
|
+
deleteMapping(connectorId: string, moduleKey: string, operationKey: string, rowVersion: string): Promise<void>;
|
|
460
|
+
autoMatch(connectorId: string, moduleKey: string): Promise<AutoMatchResponse>;
|
|
461
|
+
getMappingCompleteness(connectorId: string, moduleKey: string): Promise<MappingCompletenessResult>;
|
|
462
|
+
suggestMappingTranslation(connectorId: string, moduleKey: string, body: SuggestMappingTranslationBody): Promise<ConnectorMappingSuggestionResult>;
|
|
463
|
+
testMapping(connectorId: string, moduleKey: string, body: TestMappingBody): Promise<TestToolMappingResult>;
|
|
464
|
+
listConnectors(): Promise<ConnectorSummary[]>;
|
|
465
|
+
getModuleProvider(moduleKey: string): Promise<ModuleProvider>;
|
|
466
|
+
setModuleProvider(moduleKey: string, targetKind: ModuleProviderKind, connectorId: string | null, targetExtensionId: string | null, rowVersion: string | null): Promise<ModuleProvider>;
|
|
467
|
+
listConnectorTools(connectorId: string): Promise<ConnectorToolSummary[]>;
|
|
468
|
+
/**
|
|
469
|
+
* Whether this transport can serve the plugin-target methods below. `false` here, and the three
|
|
470
|
+
* methods throw, for the reasons in the block comment above.
|
|
471
|
+
*
|
|
472
|
+
* A property and not a guess: "the method exists" and "the method works" are different facts, and
|
|
473
|
+
* a caller that probed by calling and catching would turn a known gap into a request, an error
|
|
474
|
+
* toast and a support ticket.
|
|
475
|
+
*/
|
|
476
|
+
readonly isPluginTargetSurfaceAvailable = false;
|
|
477
|
+
listPluginProviders(moduleKey: string): Promise<PluginProviderCandidate[]>;
|
|
478
|
+
getPluginTargetMappings(moduleKey: string): Promise<PluginTargetMappingItem[]>;
|
|
479
|
+
setPluginTargetMapping(moduleKey: string, args: SetPluginTargetMappingArgs): Promise<SetPluginTargetMappingResult>;
|
|
480
|
+
}
|
|
481
|
+
/** Every host tool this service dispatches, for `useToolInvokerMap`. Frozen and constant-length. */
|
|
482
|
+
declare const CONNECTOR_MAPPING_TOOLS: readonly ["connectors:mappings:list", "connectors:mappings:set", "connectors:mappings:delete", "connectors:mappings:auto-match", "connectors:mappings:completeness", "connectors:mappings:suggest-translation", "connectors:mappings:test", "connectors:mappings:get-provider", "connectors:mappings:set-provider"];
|
|
483
|
+
/**
|
|
484
|
+
* The two enumeration tools, listed separately so a plugin that has its own connector service does
|
|
485
|
+
* not have to declare invokers for tools it never calls. Spread both when using the default
|
|
486
|
+
* enumeration implementation.
|
|
487
|
+
*/
|
|
488
|
+
declare const CONNECTOR_ENUMERATION_TOOLS: readonly ["settings:list-connectors", "settings:get-connector"];
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* What every hook in this module needs to address one connector + module pair.
|
|
492
|
+
*
|
|
493
|
+
* `moduleKey` is a plain input, never a constant baked into the hook, and that is what makes
|
|
494
|
+
* multi-module usage first-class rather than an afterthought: a consumer with twelve catalogues
|
|
495
|
+
* passes whichever the operator selected and the query keys, the invalidation and the auto-match
|
|
496
|
+
* trigger all re-key with it. A consumer with one catalogue passes a module-scope constant. Two of
|
|
497
|
+
* the three plugins this was promoted from hardcoded their single module INSIDE the hook, which is
|
|
498
|
+
* why the twelve-module one could not reuse them.
|
|
499
|
+
*/
|
|
500
|
+
interface ConnectorMappingScope {
|
|
501
|
+
/** Mapping transport. Injected so a consumer can supply a mock or its own. */
|
|
502
|
+
service: IConnectorMappingService;
|
|
503
|
+
/**
|
|
504
|
+
* Selected connector, or `undefined` before the operator has chosen one. Reads stay disabled
|
|
505
|
+
* while it is absent rather than firing against a placeholder id.
|
|
506
|
+
*/
|
|
507
|
+
connectorId: string | undefined;
|
|
508
|
+
/** Wire key the platform stores mappings under, e.g. `repo-monitoring`. */
|
|
509
|
+
moduleKey: string;
|
|
510
|
+
}
|
|
511
|
+
/** Return shape of {@link useConnectorMappings}. */
|
|
512
|
+
interface UseConnectorMappingsResult {
|
|
513
|
+
mappings: ConnectorMappingItem[];
|
|
514
|
+
isLoading: boolean;
|
|
515
|
+
isFetching: boolean;
|
|
516
|
+
/**
|
|
517
|
+
* When the current data was fetched. A real timestamp, not a placeholder: the auto-match trigger
|
|
518
|
+
* gates on this being newer than the last connector switch, so a constant would either fire on
|
|
519
|
+
* stale data or never fire at all.
|
|
520
|
+
*/
|
|
521
|
+
dataUpdatedAt: number;
|
|
522
|
+
error: Error | undefined;
|
|
523
|
+
refetch: () => void;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* The mapping rows for one connector + module. Pure read state; pair it with
|
|
527
|
+
* `useConnectorMappingMutations` for the write side, which reads this hook's cache entry to keep a
|
|
528
|
+
* partial write from destroying a translation.
|
|
529
|
+
*
|
|
530
|
+
* @remarks
|
|
531
|
+
* No `placeholderData`. The auto-match trigger inspects `mappings.length` immediately after a
|
|
532
|
+
* connector switch, and keeping the previous connector's rows visible would make an unmapped
|
|
533
|
+
* connector look mapped and skip the trigger.
|
|
534
|
+
*/
|
|
535
|
+
declare function useConnectorMappings({ service, connectorId, moduleKey, }: ConnectorMappingScope): UseConnectorMappingsResult;
|
|
536
|
+
/** Return shape of {@link useConnectorMappingCompleteness}. */
|
|
537
|
+
interface UseConnectorMappingCompletenessResult {
|
|
538
|
+
operations: MappingCompletenessResult["operations"];
|
|
539
|
+
/**
|
|
540
|
+
* Readiness over REQUIRED operations only, so a correctly configured connector can reach 100%.
|
|
541
|
+
* Counting optional operations would leave it permanently short, for operations the operator was
|
|
542
|
+
* never expected to map. See `selectMappingReadiness`.
|
|
543
|
+
*/
|
|
544
|
+
readiness: MappingReadiness;
|
|
545
|
+
isLoading: boolean;
|
|
546
|
+
isFetching: boolean;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Per-operation mapping completeness — the data behind `MappingCompletenessBanner`.
|
|
550
|
+
*
|
|
551
|
+
* @remarks
|
|
552
|
+
* Keyed with {@link connectorMappingCompletenessQueryKey}, which is a CHILD of the mappings key.
|
|
553
|
+
* That nesting is load-bearing: completeness is the mappings joined against the live tool schemas,
|
|
554
|
+
* so every write can change it, and as sibling keys a mutation had to remember to invalidate two.
|
|
555
|
+
* One plugin invalidated only the mappings key and its banner went stale after every save, clear
|
|
556
|
+
* and auto-match until the operator navigated away.
|
|
557
|
+
*/
|
|
558
|
+
declare function useConnectorMappingCompleteness({ service, connectorId, moduleKey, }: ConnectorMappingScope): UseConnectorMappingCompletenessResult;
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Per-call outcome callbacks for one mapping write.
|
|
562
|
+
*
|
|
563
|
+
* Additive and optional, for a caller that SEQUENCES this write after another and has to know which
|
|
564
|
+
* of the two failed — "the scope saved but the pin did not" is a different situation for the
|
|
565
|
+
* operator than either write failing alone. The shared error toast still fires either way; these
|
|
566
|
+
* only let the caller record which half it was.
|
|
567
|
+
*/
|
|
568
|
+
interface MappingWriteCallbacks {
|
|
569
|
+
onSuccess?: () => void;
|
|
570
|
+
onError?: () => void;
|
|
571
|
+
}
|
|
572
|
+
/** Options for {@link useConnectorMappingMutations}. */
|
|
573
|
+
interface UseConnectorMappingMutationsOptions extends ConnectorMappingScope {
|
|
574
|
+
/**
|
|
575
|
+
* Called after any mutation that SAVES a mapping, with the connector and module it was saved
|
|
576
|
+
* against.
|
|
577
|
+
*
|
|
578
|
+
* @remarks
|
|
579
|
+
* Exists so a consumer can mirror the operator's connector choice into its own state. One plugin
|
|
580
|
+
* must: its transcription and feed-poll tasks are background workers with no caller identity, so
|
|
581
|
+
* they cannot enumerate connectors to discover which one holds the mapping, and without this
|
|
582
|
+
* write-through they report themselves unconfigured no matter what the Tool Mappings page shows.
|
|
583
|
+
* Taken as a callback so this hook keeps no domain knowledge of what a consumer stores.
|
|
584
|
+
*
|
|
585
|
+
* **It hangs off the mutations rather than the page's click handlers deliberately.** There are
|
|
586
|
+
* three distinct ways to save a mapping — picking a tool, auto-matching, and saving field
|
|
587
|
+
* translations — and the third was originally missed, which left an operator who had just fixed a
|
|
588
|
+
* field mapping with a lane that still reported itself unconfigured and no way to tell why. Every
|
|
589
|
+
* save funnels through here, so a fourth route cannot reintroduce that.
|
|
590
|
+
*
|
|
591
|
+
* Deliberately NOT called by a delete: clearing a mapping is not choosing a connector. Leaving
|
|
592
|
+
* the recorded id in place makes the lane report "a connector is chosen but has no tool mapped",
|
|
593
|
+
* which is both accurate and the actionable half.
|
|
594
|
+
*/
|
|
595
|
+
onMappingSaved?: (connectorId: string, moduleKey: string) => void;
|
|
596
|
+
/**
|
|
597
|
+
* Success-toast sink. Defaults to the SDK's `showSuccessToast`, which dispatches the host's
|
|
598
|
+
* `ethisys:toast` event and no-ops outside a host that wired the listener. Override only to
|
|
599
|
+
* silence it or to route it somewhere else — the three plugins this was promoted from all
|
|
600
|
+
* ultimately reached the same host event.
|
|
601
|
+
*/
|
|
602
|
+
showSuccessToast?: (title: string, description?: string) => void;
|
|
603
|
+
}
|
|
604
|
+
/** Return shape of {@link useConnectorMappingMutations}. */
|
|
605
|
+
interface UseConnectorMappingMutationsResult {
|
|
606
|
+
/** Runs auto-match for the whole module. No-ops without a connector. */
|
|
607
|
+
triggerAutoMatch: () => void;
|
|
608
|
+
isAutoMatching: boolean;
|
|
609
|
+
/**
|
|
610
|
+
* Saves one mapping, or clears it when `toolName` is `""`. See the fill-in in the implementation
|
|
611
|
+
* for why omitting a translation member means "leave it alone" and not "clear it".
|
|
612
|
+
*/
|
|
613
|
+
updateMapping: (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => void;
|
|
614
|
+
isUpdating: boolean;
|
|
615
|
+
/** Outcome of the most recent auto-match run, or `null` before one has completed. */
|
|
616
|
+
autoMatchSummary: AutoMatchSummary | null;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* The write side of the connector tool-mapping surface: auto-match, per-operation save, and clear.
|
|
620
|
+
*
|
|
621
|
+
* **This hook is the reason this module exists.** It was duplicated byte-for-byte across three
|
|
622
|
+
* plugins and both production defects on this surface lived inside it:
|
|
623
|
+
*
|
|
624
|
+
* 1. **A stale completeness banner.** Two plugins declared their own query keys, one as siblings,
|
|
625
|
+
* and its mutations invalidated only the mappings key. Fixed by nesting the completeness key
|
|
626
|
+
* beneath the mappings key so one prefix invalidation covers both — see
|
|
627
|
+
* {@link connectorMappingsQueryKey} — and there is nothing left for a mutation to forget.
|
|
628
|
+
*
|
|
629
|
+
* 2. **Silently destroyed field translations.** The tool-level save omitted the three translation
|
|
630
|
+
* members, and because `connectors:mappings:set` treats an omitted member as "no operator
|
|
631
|
+
* override" and writes NULL, picking a tool wiped the operator's field mapping and any pinned
|
|
632
|
+
* static parameter with it. Confirmed against the production database. Fixed by the fill-in
|
|
633
|
+
* below. It had to be fixed three times, in three repos, because this function was identical in
|
|
634
|
+
* each — and one of the three never received the fix at all.
|
|
635
|
+
*
|
|
636
|
+
* 3. **The translation editor could not CLEAR a side**, which is (2)'s fix taken one step too far.
|
|
637
|
+
* See {@link resolveTranslationMember}: it was present in all three repos and was found by this
|
|
638
|
+
* module's tests rather than in production.
|
|
639
|
+
*/
|
|
640
|
+
declare function useConnectorMappingMutations({ service, connectorId, moduleKey, onMappingSaved, showSuccessToast, }: UseConnectorMappingMutationsOptions): UseConnectorMappingMutationsResult;
|
|
641
|
+
|
|
642
|
+
/** Variables for one suggest-translation call. */
|
|
643
|
+
interface SuggestMappingTranslationVars {
|
|
644
|
+
connectorId: string;
|
|
645
|
+
moduleKey: string;
|
|
646
|
+
body: SuggestMappingTranslationBody;
|
|
647
|
+
}
|
|
648
|
+
/** Variables for one dry-run call. */
|
|
649
|
+
interface TestMappingVars {
|
|
650
|
+
connectorId: string;
|
|
651
|
+
moduleKey: string;
|
|
652
|
+
body: TestMappingBody;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* The opt-in "Suggest with AI" action.
|
|
656
|
+
*
|
|
657
|
+
* Read-only inference: it invalidates NOTHING, because nothing was written. The caller decides what
|
|
658
|
+
* to do with the proposal — every consumer so far opens the translation editor seeded with it so
|
|
659
|
+
* the operator reviews before saving.
|
|
660
|
+
*
|
|
661
|
+
* @remarks
|
|
662
|
+
* A soft failure — AI not configured, or no confident suggestion — comes back as
|
|
663
|
+
* `{ success: false, error }` rather than an HTTP error. This hook THROWS on it, so the consumer's
|
|
664
|
+
* shared `meta.errorTitle` handler surfaces the reason. Resolving successfully instead is what made
|
|
665
|
+
* "Suggest with AI" silently do nothing in the copy this was promoted from: the mutation settled
|
|
666
|
+
* happily, `result.success` was false, and the caller's `onSuccess` returned early without telling
|
|
667
|
+
* anyone why.
|
|
668
|
+
*
|
|
669
|
+
* The service itself never throws for this case (see `ConnectorMappingService`), so a consumer that
|
|
670
|
+
* wants to render the soft failure inline rather than as a toast can call the service directly.
|
|
671
|
+
*/
|
|
672
|
+
declare function useSuggestMappingTranslation(service: IConnectorMappingService): UseMutationResult<ConnectorMappingSuggestionResult, Error, SuggestMappingTranslationVars>;
|
|
673
|
+
/**
|
|
674
|
+
* The "Test mapping" dry-run behind the translation editor's Test panel.
|
|
675
|
+
*
|
|
676
|
+
* Never throws for a tool-invocation or extraction failure: those come back as
|
|
677
|
+
* `{ success: false, error }` and the editor renders them in its own panel, next to the raw
|
|
678
|
+
* response the operator needs in order to fix the JSON path. Converting that into a toast would
|
|
679
|
+
* discard the raw response, which is the only actionable half.
|
|
680
|
+
*
|
|
681
|
+
* Invalidates nothing — a dry run writes nothing.
|
|
682
|
+
*/
|
|
683
|
+
declare function useTestMapping(service: IConnectorMappingService): UseMutationResult<TestToolMappingResult, Error, TestMappingVars>;
|
|
684
|
+
|
|
685
|
+
/** Options for {@link useMappingTranslationEditor}. */
|
|
686
|
+
interface UseMappingTranslationEditorOptions {
|
|
687
|
+
service: IConnectorMappingService;
|
|
688
|
+
/** `undefined` before a connector is selected; every action no-ops until then. */
|
|
689
|
+
connectorId: string | undefined;
|
|
690
|
+
moduleKey: string;
|
|
691
|
+
/** The rows the editor looks a row up in — pass `useConnectorMappings`' `mappings` straight in. */
|
|
692
|
+
mappings: ConnectorMappingItem[];
|
|
693
|
+
/**
|
|
694
|
+
* The write. Pass `useConnectorMappingMutations`' `updateMapping`, which is what routes the save
|
|
695
|
+
* through the translation-preserving fill and reports it to `onMappingSaved`.
|
|
696
|
+
*/
|
|
697
|
+
updateMapping: (args: ConnectorMappingWriteArgs, callbacks?: MappingWriteCallbacks) => void;
|
|
698
|
+
}
|
|
699
|
+
/** Return shape of {@link useMappingTranslationEditor}. */
|
|
700
|
+
interface UseMappingTranslationEditorResult {
|
|
701
|
+
/** The row being edited, or `undefined` when the editor is closed or the row vanished. */
|
|
702
|
+
editorRow: ConnectorMappingItem | undefined;
|
|
703
|
+
/** True when a row is being edited. Gate the dialog's mount on this, not on `editorRow`. */
|
|
704
|
+
isOpen: boolean;
|
|
705
|
+
/** Wire to `ConnectorMappingViewProps.onEditTranslation` — the per-row settings icon. */
|
|
706
|
+
onEditTranslation: (operationKey: string, current: ConnectorMappingTranslationState) => void;
|
|
707
|
+
/** Wire to `MappingCompletenessBannerProps.onOpenMappingEditor`. */
|
|
708
|
+
onOpenMappingEditor: (operation: MappingCompletenessItem, seed?: ConnectorMappingTranslationState) => void;
|
|
709
|
+
/** Wire to `MappingCompletenessBannerProps.onSuggestWithAi`. */
|
|
710
|
+
onSuggestWithAi: (operation: MappingCompletenessItem) => void;
|
|
711
|
+
/** Wire to `MappingCompletenessBannerProps.suggestingOperationKey` — drives the per-row spinner. */
|
|
712
|
+
suggestingOperationKey: string | null;
|
|
713
|
+
/**
|
|
714
|
+
* Ready-to-spread props for `ConnectorMappingTranslationEditorView`, valid only while
|
|
715
|
+
* {@link isOpen}. Mount the dialog conditionally: the component initialises its inputs lazily on
|
|
716
|
+
* each mount, which is what keeps an operator's in-flight edits sovereign against a concurrent
|
|
717
|
+
* refresh of the row.
|
|
718
|
+
*/
|
|
719
|
+
editorProps: ConnectorMappingTranslationEditorProps;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* The translation editor's state machine: which row is open, what it was seeded with, and the three
|
|
723
|
+
* ways it can be opened.
|
|
724
|
+
*
|
|
725
|
+
* This was identical in all three plugins that had a Tool Mappings page, down to the comments, and
|
|
726
|
+
* it needs nothing plugin-specific — no routing, no identity, no persistence. It is therefore the
|
|
727
|
+
* part of the page hook that could move wholesale, and the part where a fourth copy would be pure
|
|
728
|
+
* loss.
|
|
729
|
+
*
|
|
730
|
+
* The three entry points and why they differ:
|
|
731
|
+
*
|
|
732
|
+
* - **`onEditTranslation`** — the per-row icon. The view already holds the row's persisted
|
|
733
|
+
* translation and passes it, so this seeds from the argument.
|
|
734
|
+
* - **`onOpenMappingEditor`** — the completeness banner's "Open mapping editor". The banner holds a
|
|
735
|
+
* completeness item, not a mapping row, so this looks the row up and seeds from it.
|
|
736
|
+
* - **`onSuggestWithAi`** — the banner's AI assist. Seeds from the model's PROPOSAL for the two
|
|
737
|
+
* field maps, while keeping the row's persisted `staticRequestFields`: the advisor reasons about
|
|
738
|
+
* canonical field names and has no opinion on a pinned discriminator, so overwriting it with
|
|
739
|
+
* nothing would silently drop a static parameter the operator set by hand.
|
|
740
|
+
*
|
|
741
|
+
* @remarks
|
|
742
|
+
* **Save re-issues the write with the row's EXISTING tool name and row version**, which the host
|
|
743
|
+
* treats as a normal set that leaves the tool selection alone. That route is also why
|
|
744
|
+
* `onMappingSaved` belongs on the mutation and not on a page's click handlers: saving field
|
|
745
|
+
* translations is a save, and it was the one of the three save routes originally missed.
|
|
746
|
+
*/
|
|
747
|
+
declare function useMappingTranslationEditor({ service, connectorId, moduleKey, mappings, updateMapping, }: UseMappingTranslationEditorOptions): UseMappingTranslationEditorResult;
|
|
748
|
+
|
|
749
|
+
/** Options for {@link useAutoMatchOnConnectorSwitch}. */
|
|
750
|
+
interface UseAutoMatchOnConnectorSwitchOptions {
|
|
751
|
+
connectorId: string | undefined;
|
|
752
|
+
/**
|
|
753
|
+
* The module the rows belong to. Part of the latch key, not decoration: one connector commonly
|
|
754
|
+
* serves several modules, and without this the hook cannot tell "already auto-matched this
|
|
755
|
+
* scope" from "auto-matched a different module on the same connector".
|
|
756
|
+
*/
|
|
757
|
+
moduleKey: string;
|
|
758
|
+
/** The rows as last fetched. Their emptiness is what decides whether to trigger. */
|
|
759
|
+
mappings: ConnectorMappingItem[];
|
|
760
|
+
isLoading: boolean;
|
|
761
|
+
isAutoMatching: boolean;
|
|
762
|
+
/** `dataUpdatedAt` from `useConnectorMappings` — the fetch-freshness gate. */
|
|
763
|
+
dataUpdatedAt: number;
|
|
764
|
+
triggerAutoMatch: () => void;
|
|
765
|
+
/**
|
|
766
|
+
* `true` when the consumer arrived with a connector ALREADY selected — a `?connectorId=` deep
|
|
767
|
+
* link, or a restored/preselected choice. Seeds the switch timestamp to mount time.
|
|
768
|
+
*
|
|
769
|
+
* @remarks
|
|
770
|
+
* The three plugins this was promoted from all carry a comment saying this seed exists "so the
|
|
771
|
+
* auto-match trigger effect still fires on the first fetch". That is backwards, and the tests
|
|
772
|
+
* make it plain: seeding raises the bar the fetch timestamp has to clear, so it can only ever
|
|
773
|
+
* SUPPRESS a trigger, never cause one. A fresh fetch resolves after mount and clears it either
|
|
774
|
+
* way.
|
|
775
|
+
*
|
|
776
|
+
* What it actually does is refuse data that predates the mount. React-query can serve a cached
|
|
777
|
+
* entry immediately — the operator was on this page a moment ago, or another surface primed the
|
|
778
|
+
* same key — and with the ref at zero that stale snapshot is enough to fire an auto-match against
|
|
779
|
+
* a connector nobody has looked at since. Seeding says "wait for a fetch that happened after I
|
|
780
|
+
* arrived", which is the same statement `markConnectorChanged` makes about a switch. Set it
|
|
781
|
+
* whenever the connector was chosen before this hook mounted.
|
|
782
|
+
*/
|
|
783
|
+
initiallySelected?: boolean;
|
|
784
|
+
}
|
|
785
|
+
/** Return shape of {@link useAutoMatchOnConnectorSwitch}. */
|
|
786
|
+
interface UseAutoMatchOnConnectorSwitchResult {
|
|
787
|
+
/**
|
|
788
|
+
* Call this whenever the consumer changes the selected connector, from inside its own setter.
|
|
789
|
+
* The hook cannot observe the change itself in time: it needs the timestamp recorded BEFORE the
|
|
790
|
+
* new connector's fetch resolves.
|
|
791
|
+
*/
|
|
792
|
+
markConnectorChanged: () => void;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Runs auto-match once, automatically, when a freshly selected connector turns out to have nothing
|
|
796
|
+
* mapped.
|
|
797
|
+
*
|
|
798
|
+
* Identical in all three plugins this was promoted from, comments included, and it is entirely
|
|
799
|
+
* mechanical — no identity, no routing, no persistence — so it moves whole.
|
|
800
|
+
*
|
|
801
|
+
* The four gates, each of which was added in response to something:
|
|
802
|
+
*
|
|
803
|
+
* 1. **`dataUpdatedAt > connectorChangedAt`.** Only mappings fetched AFTER the latest switch are
|
|
804
|
+
* inspected. Without it the previous connector's data briefly reads as empty mid-transition and
|
|
805
|
+
* auto-match fires against the wrong connector's emptiness.
|
|
806
|
+
* 2. **`autoMatchedRef !== connectorId`.** Once per connector. Auto-match that legitimately matches
|
|
807
|
+
* nothing would otherwise re-fire on every refetch, forever.
|
|
808
|
+
* 3. **Something already mapped.** Never overwrite an operator's existing choices. A connector with
|
|
809
|
+
* any mapped tool is one a human has already been through.
|
|
810
|
+
* 4. **Not loading, not already matching.** Obvious, and the reason the button used to appear to do
|
|
811
|
+
* nothing when pressed during a fetch.
|
|
812
|
+
*/
|
|
813
|
+
declare function useAutoMatchOnConnectorSwitch({ connectorId, moduleKey, mappings, isLoading, isAutoMatching, dataUpdatedAt, triggerAutoMatch, initiallySelected, }: UseAutoMatchOnConnectorSwitchOptions): UseAutoMatchOnConnectorSwitchResult;
|
|
814
|
+
|
|
815
|
+
/** Return shape of {@link useMappingBannerDismissal}. */
|
|
816
|
+
interface UseMappingBannerDismissalResult {
|
|
817
|
+
dismissed: boolean;
|
|
818
|
+
dismiss: () => void;
|
|
819
|
+
/** Un-dismisses. Call it when the selected connector changes — see the remarks. */
|
|
820
|
+
reset: () => void;
|
|
821
|
+
}
|
|
822
|
+
declare function useMappingBannerDismissal(organisationId: string | undefined, moduleKey: string): UseMappingBannerDismissalResult;
|
|
823
|
+
|
|
824
|
+
/** Query key for one module's provider selection. */
|
|
825
|
+
declare function moduleProviderQueryKey(moduleKey: string): readonly ["connector-mappings", "module-provider", string];
|
|
826
|
+
interface UseModuleProviderOptions {
|
|
827
|
+
service: IConnectorMappingService;
|
|
828
|
+
moduleKey: string;
|
|
829
|
+
/**
|
|
830
|
+
* Called after a successful write, with the provider as stored. Lets a page invalidate its own
|
|
831
|
+
* reads — the mappings list is keyed on the connector, so a provider change is a different
|
|
832
|
+
* connector's rows.
|
|
833
|
+
*/
|
|
834
|
+
onProviderChanged?: (provider: ModuleProvider) => void;
|
|
835
|
+
}
|
|
836
|
+
interface UseModuleProviderResult {
|
|
837
|
+
/** The organisation's stored choice, or the unconfigured state while loading or unset. */
|
|
838
|
+
provider: ModuleProvider;
|
|
839
|
+
/**
|
|
840
|
+
* The chosen connector id, or `""` when the module is unconfigured or served by a plugin.
|
|
841
|
+
*
|
|
842
|
+
* Shaped to drop straight into `useConnectorToolMappings`'s `connectorId`, which is the whole
|
|
843
|
+
* point of this hook: that surface already takes the selection as an input, so adopting the
|
|
844
|
+
* organisation-wide answer is a change of SOURCE and not a change to the surface.
|
|
845
|
+
*/
|
|
846
|
+
connectorId: string;
|
|
847
|
+
isLoading: boolean;
|
|
848
|
+
/** Point the module at a connector. Pass `""` to clear the selection back to unconfigured. */
|
|
849
|
+
selectConnector: (connectorId: string) => void;
|
|
850
|
+
/** Point the module at another installed extension. */
|
|
851
|
+
selectExtension: (extensionId: string) => void;
|
|
852
|
+
isSaving: boolean;
|
|
853
|
+
error?: unknown;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Reads and writes WHICH PROVIDER serves a module, as the organisation's own stored answer.
|
|
857
|
+
*
|
|
858
|
+
* @remarks
|
|
859
|
+
* **This replaces a browser-local selection, and that is the entire point.** Before the host had
|
|
860
|
+
* somewhere to put it, each plugin kept the operator's connector choice in `localStorage` — so the
|
|
861
|
+
* answer was per-device rather than per-organisation. One operator selected a provider, a colleague
|
|
862
|
+
* still saw the old one, and the two never reconciled. Worse, the two mechanisms could disagree
|
|
863
|
+
* about a module that a plugin now serves, which is the situation the whole provider-selection
|
|
864
|
+
* concept exists to end.
|
|
865
|
+
*
|
|
866
|
+
* **A clear is a first-class outcome.** `selectConnector("")` writes the unconfigured state rather
|
|
867
|
+
* than silently doing nothing, because "no provider chosen" and "chose a connector with no mappings
|
|
868
|
+
* yet" are different situations with different remedies — the host deliberately does not default
|
|
869
|
+
* one to the other, and neither does this.
|
|
870
|
+
*
|
|
871
|
+
* **The row version is handled here so callers never see it.** It is read from the query, sent on
|
|
872
|
+
* the write, and refreshed from the write's response. A caller that has to thread a concurrency
|
|
873
|
+
* token through its own state is a caller that will eventually send a stale one.
|
|
874
|
+
*/
|
|
875
|
+
declare function useModuleProvider({ service, moduleKey, onProviderChanged, }: UseModuleProviderOptions): UseModuleProviderResult;
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* What a canonical operation resolves to, and what happens to its field translation when that
|
|
879
|
+
* changes.
|
|
880
|
+
*
|
|
881
|
+
* The Tool Mappings surface used to have one answer: a connector's tool. The platform now also
|
|
882
|
+
* lets a mapping target another installed application's MCP tool, so "Projects reads employees
|
|
883
|
+
* from the HR app" and "Projects reads them from Workday" are the same configuration with a
|
|
884
|
+
* different provider. That makes the target a two-part thing - WHO provides it, and WHICH of their
|
|
885
|
+
* tools - and this module owns the rules that follow from it.
|
|
886
|
+
*
|
|
887
|
+
* Pure by design. No react, no react-query, no transport: every rule here is a function of its
|
|
888
|
+
* arguments, so the rules that were previously comments inside a mutation hook are now things a
|
|
889
|
+
* test can fail.
|
|
890
|
+
*/
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* The UI-side kind as the host spells it.
|
|
894
|
+
*
|
|
895
|
+
* The host persists this string VERBATIM and matches it by name and case against its own
|
|
896
|
+
* `ToolMappingTargetKind`, so a differently-cased value is stored as something that matches neither
|
|
897
|
+
* branch of the resolver nor the database CHECK constraint. Converting in one named function is
|
|
898
|
+
* what keeps that from being retyped as a literal at a call site.
|
|
899
|
+
*/
|
|
900
|
+
declare function toWireTargetKind(kind: MappingTargetKind): MappingTargetKindWire;
|
|
901
|
+
/**
|
|
902
|
+
* The host's kind as this surface spells it, or `null`.
|
|
903
|
+
*
|
|
904
|
+
* `null` covers two cases on purpose, because the UI does the same thing in both: the module has no
|
|
905
|
+
* selection at all (the host returns a null `targetKind` for an unconfigured module, and
|
|
906
|
+
* deliberately does NOT default it to `"Connector"`), or the stored string matched neither member.
|
|
907
|
+
* An unrecognised value is NOT guessed into a kind - rendering a choice nobody made is the exact
|
|
908
|
+
* failure the host's own null is there to avoid, and a guess here would reintroduce it one layer up.
|
|
909
|
+
*/
|
|
910
|
+
declare function fromWireTargetKind(value: string | null | undefined): MappingTargetKind | null;
|
|
911
|
+
/**
|
|
912
|
+
* Who answers a module's operations: a connector to a third-party system, or another installed
|
|
913
|
+
* application.
|
|
914
|
+
*
|
|
915
|
+
* The plugin arm carries the providing extension rather than inferring it from the tool name's
|
|
916
|
+
* prefix. That is the host's requirement and its reasoning is worth keeping here: the router
|
|
917
|
+
* resolves a plugin tool by SCANNING the enabled extensions for a name match, breaking a duplicate
|
|
918
|
+
* module-key collision by lowest extension id. A scan result is not a choice, and this type is
|
|
919
|
+
* about recording a choice.
|
|
920
|
+
*/
|
|
921
|
+
type MappingProvider = {
|
|
922
|
+
kind: "connector";
|
|
923
|
+
connectorId: string;
|
|
924
|
+
} | {
|
|
925
|
+
kind: "plugin";
|
|
926
|
+
targetExtensionId: string;
|
|
927
|
+
};
|
|
928
|
+
/**
|
|
929
|
+
* A provider plus the specific tool of theirs that answers one operation.
|
|
930
|
+
*
|
|
931
|
+
* `toolName` means different things per arm, and both are the host's shape rather than this
|
|
932
|
+
* module's invention: a connector target stores the BARE vendor verb, because the
|
|
933
|
+
* `integrations:{slug}:` prefix is a function of the connector and not of the row; a plugin target
|
|
934
|
+
* stores the FULLY NAMESPACED name (`hr:get-employees-by-ids`), because a plugin tool has no slug
|
|
935
|
+
* to prefix and already routes verbatim.
|
|
936
|
+
*
|
|
937
|
+
* `""` is the unmap request, exactly as on the connector-only surface.
|
|
938
|
+
*/
|
|
939
|
+
type MappingTarget = MappingProvider & {
|
|
940
|
+
toolName: string;
|
|
941
|
+
};
|
|
942
|
+
/**
|
|
943
|
+
* The stored mapping a write is replacing, reduced to the parts that decide what happens to the
|
|
944
|
+
* translation.
|
|
945
|
+
*
|
|
946
|
+
* Both provider ids are optional because a caller assembles this from a row plus whatever it knows
|
|
947
|
+
* about where the row came from - the connector-scoped list read, for instance, returns rows that
|
|
948
|
+
* do not repeat the connector id the caller passed to fetch them. Absent is treated as "not the
|
|
949
|
+
* same provider"; see {@link isSameTarget} for why that direction is the safe one.
|
|
950
|
+
*/
|
|
951
|
+
interface ExistingMappingTarget extends Partial<ConnectorMappingTranslationState> {
|
|
952
|
+
operationKey: string;
|
|
953
|
+
targetKind: MappingTargetKind;
|
|
954
|
+
/** The tool currently mapped, or `null` when the operation is unmapped. */
|
|
955
|
+
mappedToolName: string | null;
|
|
956
|
+
connectorId?: string | null;
|
|
957
|
+
targetExtensionId?: string | null;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* One mapping write, with every translation member decided.
|
|
961
|
+
*
|
|
962
|
+
* The three translation members are REQUIRED and nullable rather than optional, matching
|
|
963
|
+
* `IConnectorMappingService.updateMapping` and for the same reason: on this surface an omitted
|
|
964
|
+
* member and a null member both write NULL host-side, so a type that lets a member be omitted lets
|
|
965
|
+
* a caller destroy a translation without saying so.
|
|
966
|
+
*
|
|
967
|
+
* The two provider ids ARE optional, and exactly one of them is ever present. Absent rather than
|
|
968
|
+
* null: "there is no connector here" and "clear the connector" are different statements, and only
|
|
969
|
+
* one of them is true of a plugin target.
|
|
970
|
+
*/
|
|
971
|
+
interface MappingUpdate extends ConnectorMappingTranslationState {
|
|
972
|
+
targetKind: MappingTargetKind;
|
|
973
|
+
toolName: string;
|
|
974
|
+
connectorId?: string;
|
|
975
|
+
targetExtensionId?: string;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Does the write land on the SAME tool the stored translation was written against?
|
|
979
|
+
*
|
|
980
|
+
* Identity is the whole target - kind, provider, and tool name - not the tool name alone, and that
|
|
981
|
+
* is deliberately stricter than a literal reading of the rule it serves. A bare vendor verb is not
|
|
982
|
+
* unique: `get_file_contents` names a different tool with a different parameter shape in every
|
|
983
|
+
* connector that has one, so comparing names alone would carry a translation from one vendor's tool
|
|
984
|
+
* onto another's and call it unchanged.
|
|
985
|
+
*
|
|
986
|
+
* Unknown provenance counts as a different target. When a caller supplies no provider id on
|
|
987
|
+
* {@link ExistingMappingTarget}, this returns `false` and the translation is discarded. That is the
|
|
988
|
+
* safe direction of an unavoidable choice: a discarded translation is visibly absent and the
|
|
989
|
+
* operator re-enters it, a carried-over one is silently wrong and nothing on the page says so.
|
|
990
|
+
*/
|
|
991
|
+
declare function isSameTarget(existing: ExistingMappingTarget | null | undefined, next: MappingTarget): boolean;
|
|
992
|
+
/** Arguments for {@link buildMappingUpdate}. */
|
|
993
|
+
interface BuildMappingUpdateArgs {
|
|
994
|
+
/** The stored mapping, or `null` when the operation has never been mapped. */
|
|
995
|
+
existing: ExistingMappingTarget | null;
|
|
996
|
+
/** Where the operation is being pointed now. */
|
|
997
|
+
next: MappingTarget;
|
|
998
|
+
/**
|
|
999
|
+
* The translation the caller is EXPLICITLY setting, member by member.
|
|
1000
|
+
*
|
|
1001
|
+
* Read with `!== undefined`, never `??`, and the difference is the whole point:
|
|
1002
|
+
*
|
|
1003
|
+
* - a member left `undefined` is the caller not talking about it, and it is decided by the
|
|
1004
|
+
* target-change rule below;
|
|
1005
|
+
* - a member set to `null` is the caller SAYING clear it, and it survives untouched.
|
|
1006
|
+
*
|
|
1007
|
+
* `??` collapses those two into one, which is the defect this surface has shipped three times -
|
|
1008
|
+
* once as "picking a tool wiped the operator's field mapping", once as "the editor could not
|
|
1009
|
+
* clear a side at all", and once in each of three plugin repos.
|
|
1010
|
+
*/
|
|
1011
|
+
translation?: Partial<ConnectorMappingTranslationState>;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Builds the write for pointing one operation at a target, deciding what happens to its translation.
|
|
1015
|
+
*
|
|
1016
|
+
* Two rules, and they are separate because they answer different questions:
|
|
1017
|
+
*
|
|
1018
|
+
* 1. **A translation belongs to ONE tool.** When the target changes, `requestFieldMap`,
|
|
1019
|
+
* `responseFieldPaths` and `staticRequestFields` are DISCARDED rather than carried across. They
|
|
1020
|
+
* are a map onto one tool's parameter names, and against a different tool they do not degrade -
|
|
1021
|
+
* they silently mean something else. Switching provider necessarily changes the tool, so this
|
|
1022
|
+
* fires on every provider switch and not only on a tool edit within a provider.
|
|
1023
|
+
*
|
|
1024
|
+
* 2. **Omitted is not cleared.** Whatever the caller states in `translation` wins, including an
|
|
1025
|
+
* explicit `null`. An operator supplying a translation for the new tool in the same action is
|
|
1026
|
+
* not fighting rule 1; rule 1 exists because nobody said anything about the translation, and
|
|
1027
|
+
* here somebody did.
|
|
1028
|
+
*
|
|
1029
|
+
* The order matters and is asserted by the tests: an explicit member beats the discard, and the
|
|
1030
|
+
* discard beats the carry-forward.
|
|
1031
|
+
*/
|
|
1032
|
+
declare function buildMappingUpdate({ existing, next, translation, }: BuildMappingUpdateArgs): MappingUpdate;
|
|
1033
|
+
/**
|
|
1034
|
+
* The provider half of a target, for the per-module selection. Drops the tool, because the module
|
|
1035
|
+
* selection is a choice about WHO serves the module and every operation under it keeps its own tool.
|
|
1036
|
+
*/
|
|
1037
|
+
declare function providerOf(target: MappingTarget): MappingProvider;
|
|
1038
|
+
/**
|
|
1039
|
+
* The provider's id, whichever half it is. For a caller that only needs to know whether the choice
|
|
1040
|
+
* changed, rather than which kind it is.
|
|
1041
|
+
*/
|
|
1042
|
+
declare function providerIdOf(provider: MappingProvider): string;
|
|
1043
|
+
|
|
1044
|
+
/** A connector as the mapping picker renders it. */
|
|
1045
|
+
interface ConnectorOption {
|
|
1046
|
+
id: string;
|
|
1047
|
+
name: string;
|
|
1048
|
+
}
|
|
1049
|
+
/** A tool as the per-row mapping autocomplete renders it. */
|
|
1050
|
+
interface ConnectorToolOption {
|
|
1051
|
+
name: string;
|
|
1052
|
+
description: string;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Project the organisation's connectors down to the picker's option shape.
|
|
1056
|
+
*
|
|
1057
|
+
* @remarks
|
|
1058
|
+
* **Deliberately unfiltered.** One plugin filtered this list to MCP connectors, because its
|
|
1059
|
+
* connector service returned every type; another reads `settings:list-connectors`, whose projection
|
|
1060
|
+
* does not carry a connector type at all, so the same filter compared against `undefined` and hid
|
|
1061
|
+
* every connector — a picker that is empty for a reason no operator could work out. The host
|
|
1062
|
+
* already scopes the list to the current organisation, and a mapping against a connector that
|
|
1063
|
+
* cannot serve it fails loudly at Test. A consumer whose own service returns types it must exclude
|
|
1064
|
+
* should filter BEFORE calling this, where it can name what it is excluding and why.
|
|
1065
|
+
*/
|
|
1066
|
+
declare function mapConnectorsToOptions(connectors: readonly ConnectorSummary[]): ConnectorOption[];
|
|
1067
|
+
/**
|
|
1068
|
+
* Project a connector's tools down to the mapping autocomplete's option shape.
|
|
1069
|
+
*
|
|
1070
|
+
* Disabled tools are dropped: an operator can only map an operation to a tool the connector is
|
|
1071
|
+
* currently exposing, and offering a switched-off one produces a mapping that resolves to nothing
|
|
1072
|
+
* at runtime. The service returns them unfiltered on purpose — see
|
|
1073
|
+
* `ConnectorMappingService.listConnectorTools` — so the distinction stays visible one layer down.
|
|
1074
|
+
*/
|
|
1075
|
+
declare function mapConnectorToolsToOptions(tools: readonly ConnectorToolSummary[]): ConnectorToolOption[];
|
|
1076
|
+
|
|
1077
|
+
/** Options for {@link useConnectorToolMappings}. */
|
|
1078
|
+
interface UseConnectorToolMappingsOptions {
|
|
1079
|
+
/** Mapping transport. See `ConnectorMappingService` for the default implementation. */
|
|
1080
|
+
service: IConnectorMappingService;
|
|
1081
|
+
/** `""` or `undefined` before the operator has chosen one. */
|
|
1082
|
+
connectorId: string | undefined;
|
|
1083
|
+
/**
|
|
1084
|
+
* Selected module. A plain input, so a consumer with ONE catalogue passes a module-scope constant
|
|
1085
|
+
* and a consumer with twelve passes whichever the operator selected — every query key, the
|
|
1086
|
+
* invalidation, the auto-match trigger and the banner dismissal re-key with it.
|
|
1087
|
+
*/
|
|
1088
|
+
moduleKey: string;
|
|
1089
|
+
/** Human label for the module, for the view's own header and empty state. */
|
|
1090
|
+
moduleLabel: string;
|
|
1091
|
+
/**
|
|
1092
|
+
* The connectors to offer. Injected because connector enumeration differs genuinely between
|
|
1093
|
+
* consumers — one reads `settings:list-connectors`, one pages a full connector CRUD service, one
|
|
1094
|
+
* has no source yet — and none of those belongs in a mapping hook. `mapConnectorsToOptions`
|
|
1095
|
+
* projects the default service's shape.
|
|
1096
|
+
*/
|
|
1097
|
+
connectors: ConnectorOption[];
|
|
1098
|
+
/**
|
|
1099
|
+
* Whether the CONNECTORS query is still in flight. Folded into the view's `isLoading` because the
|
|
1100
|
+
* mappings query is disabled until a connector is chosen and therefore reports `isLoading: false`
|
|
1101
|
+
* on first load — which made the view skip its skeleton and render the picker as an EMPTY
|
|
1102
|
+
* dropdown while the connector list was still loading. It read as "this organisation has no
|
|
1103
|
+
* connectors", and then they appeared.
|
|
1104
|
+
*/
|
|
1105
|
+
isConnectorsLoading?: boolean;
|
|
1106
|
+
/** The selected connector's mappable tools. `mapConnectorToolsToOptions` projects them. */
|
|
1107
|
+
availableTools: ConnectorToolOption[];
|
|
1108
|
+
/**
|
|
1109
|
+
* The consumer's connector setter — bare `setState`, a persisting setter, a URL write, whatever
|
|
1110
|
+
* it owns. Wrapped here so the auto-match trigger learns of the switch; the consumer wires the
|
|
1111
|
+
* returned `view.onConnectorChange` to its picker and never calls its own setter directly.
|
|
1112
|
+
*/
|
|
1113
|
+
onConnectorChange: (connectorId: string) => void;
|
|
1114
|
+
/** `true` when a connector was already selected on mount (deep link / restored / preselected). */
|
|
1115
|
+
initiallySelected?: boolean;
|
|
1116
|
+
/**
|
|
1117
|
+
* Opts this hook into the per-module provider choice: it reads and writes the organisation's
|
|
1118
|
+
* stored provider through {@link useModuleProvider} on the `service` already passed above, and
|
|
1119
|
+
* hands the view the picker's props.
|
|
1120
|
+
*
|
|
1121
|
+
* Defaults to `false`, and the default is the load-bearing part. The transport exists -
|
|
1122
|
+
* `connectors:mappings:get-provider` and `connectors:mappings:set-provider` are real tools - but
|
|
1123
|
+
* only on a host that has them DEPLOYED, and this package is pinned independently by every
|
|
1124
|
+
* plugin. Turning the read on for every existing consumer would fire a tool call an older host
|
|
1125
|
+
* answers with an error toast, on a surface that worked. A consumer opts in when its host is
|
|
1126
|
+
* ready, and adds the two tool names to its `useToolInvokerMap`.
|
|
1127
|
+
*/
|
|
1128
|
+
providerSelectionEnabled?: boolean;
|
|
1129
|
+
/**
|
|
1130
|
+
* The installed applications on offer, with the tools each can answer this module's operations
|
|
1131
|
+
* with.
|
|
1132
|
+
*
|
|
1133
|
+
* An INPUT rather than something this hook fetches, matching {@link connectors} and for the same
|
|
1134
|
+
* reason: the candidate set is a server-side authorisation decision (see
|
|
1135
|
+
* {@link PluginProviderCandidate}), and no consumer should be able to widen it by filtering
|
|
1136
|
+
* client-side. Supply it from whatever surface the host eventually exposes - there is no
|
|
1137
|
+
* candidate-list endpoint in any transport today, which is why this cannot be fetched here.
|
|
1138
|
+
*
|
|
1139
|
+
* Left empty, the app arm of the picker has nothing to offer, so the view renders the connector
|
|
1140
|
+
* arm only.
|
|
1141
|
+
*/
|
|
1142
|
+
pluginProviders?: PluginProviderCandidate[];
|
|
1143
|
+
/** Whether the candidate list is still in flight. Folded into the view's `isLoading`. */
|
|
1144
|
+
isPluginProvidersLoading?: boolean;
|
|
1145
|
+
/**
|
|
1146
|
+
* Current organisation, for the per-tenant banner dismissal. Injected because identity differs:
|
|
1147
|
+
* one plugin reads `useHostIdentity`, two read a local `useAuth`. Absent is handled — dismissal
|
|
1148
|
+
* simply does not persist until identity resolves.
|
|
1149
|
+
*/
|
|
1150
|
+
organisationId?: string;
|
|
1151
|
+
/** Href of the host's AI-providers settings page. Routing is never owned here. */
|
|
1152
|
+
aiProvidersHref: string;
|
|
1153
|
+
/** Href of the selected connector's settings page, already scoped to `connectorId`. */
|
|
1154
|
+
connectorSettingsHref: string;
|
|
1155
|
+
/** Passed through to the view. See `ConnectorMappingViewProps.withHeader`. */
|
|
1156
|
+
withHeader?: boolean;
|
|
1157
|
+
/**
|
|
1158
|
+
* Called after any save. See `useConnectorMappingMutations` — this is the hook a consumer needs
|
|
1159
|
+
* in order to mirror the connector into its own settings, without which a background worker
|
|
1160
|
+
* cannot find a connector at all.
|
|
1161
|
+
*/
|
|
1162
|
+
onMappingSaved?: (connectorId: string, moduleKey: string) => void;
|
|
1163
|
+
/** Success-toast sink. Defaults to the SDK's host-event toast. */
|
|
1164
|
+
showSuccessToast?: (title: string, description?: string) => void;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Props for `ConnectorMappingView`, minus nothing — spread this straight onto it.
|
|
1168
|
+
*
|
|
1169
|
+
* Declared structurally rather than imported from the component so this module stays free of
|
|
1170
|
+
* `@mui/material`: a consumer can take the hooks without installing MUI, and the view's own props
|
|
1171
|
+
* type is assignable to this.
|
|
1172
|
+
*/
|
|
1173
|
+
interface ConnectorMappingViewBundle {
|
|
1174
|
+
isLoading: boolean;
|
|
1175
|
+
moduleKey: string;
|
|
1176
|
+
moduleLabel: string;
|
|
1177
|
+
withHeader?: boolean;
|
|
1178
|
+
connectors: ConnectorOption[];
|
|
1179
|
+
selectedConnectorId: string;
|
|
1180
|
+
onConnectorChange: (connectorId: string) => void;
|
|
1181
|
+
providerKind?: MappingTargetKind;
|
|
1182
|
+
onProviderKindChange?: (kind: MappingTargetKind) => void;
|
|
1183
|
+
pluginProviders?: {
|
|
1184
|
+
extensionId: string;
|
|
1185
|
+
name: string;
|
|
1186
|
+
}[];
|
|
1187
|
+
selectedPluginExtensionId?: string;
|
|
1188
|
+
onPluginProviderChange?: (extensionId: string) => void;
|
|
1189
|
+
mappings: ConnectorMappingItem[];
|
|
1190
|
+
availableTools: ConnectorToolOption[];
|
|
1191
|
+
onUpdateMapping: (operationKey: string, toolName: string, rowVersion: string) => void;
|
|
1192
|
+
onAutoMatch: () => void;
|
|
1193
|
+
isUpdating: boolean;
|
|
1194
|
+
isAutoMatching: boolean;
|
|
1195
|
+
bannerState: ReturnType<typeof useConnectorMappingMutations>["autoMatchSummary"];
|
|
1196
|
+
onDismissBanner: () => void;
|
|
1197
|
+
aiProvidersHref: string;
|
|
1198
|
+
connectorSettingsHref: string;
|
|
1199
|
+
onEditTranslation: (operationKey: string, current: ConnectorMappingTranslationState) => void;
|
|
1200
|
+
}
|
|
1201
|
+
/** Return shape of {@link useConnectorToolMappings}. */
|
|
1202
|
+
interface UseConnectorToolMappingsResult {
|
|
1203
|
+
/** Spread onto `ConnectorMappingView`. */
|
|
1204
|
+
view: ConnectorMappingViewBundle;
|
|
1205
|
+
/**
|
|
1206
|
+
* Spread onto `MappingCompletenessBanner`.
|
|
1207
|
+
*
|
|
1208
|
+
* Render it whenever `banner.isProviderUnconfigured` is true, and otherwise only when a provider
|
|
1209
|
+
* is selected. Gating the mount on a selected CONNECTOR - which the pre-provider guidance here
|
|
1210
|
+
* said, because a connector was the only provider there was - would hide the one message that
|
|
1211
|
+
* tells an operator how to select one.
|
|
1212
|
+
*/
|
|
1213
|
+
banner: MappingCompletenessBannerProps;
|
|
1214
|
+
/** Spread onto `ConnectorMappingTranslationEditorView`. Mount it only when `isEditorOpen`. */
|
|
1215
|
+
editor: ConnectorMappingTranslationEditorProps;
|
|
1216
|
+
/** Gate the editor dialog's MOUNT on this, so it reseeds from the row each time it opens. */
|
|
1217
|
+
isEditorOpen: boolean;
|
|
1218
|
+
/** The row the editor is open for, or `undefined`. Exposed for a consumer's own render guards. */
|
|
1219
|
+
editorRow: ConnectorMappingItem | undefined;
|
|
1220
|
+
/** The rows, for a consumer that renders something of its own alongside the table. */
|
|
1221
|
+
mappings: ConnectorMappingItem[];
|
|
1222
|
+
/** Per-operation completeness, for a consumer that wants it outside the banner. */
|
|
1223
|
+
completenessOperations: MappingCompletenessItem[];
|
|
1224
|
+
/** Readiness over REQUIRED operations only — for a progress indicator or a "ready" badge. */
|
|
1225
|
+
readiness: MappingReadiness;
|
|
1226
|
+
/** True while either query is loading, connectors included. */
|
|
1227
|
+
isLoading: boolean;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* The whole Tool Mappings surface for one connector + module, assembled from the parts in this
|
|
1231
|
+
* module.
|
|
1232
|
+
*
|
|
1233
|
+
* ## What this is, and what it deliberately is not
|
|
1234
|
+
*
|
|
1235
|
+
* Three plugins each had a ~300-line page hook, and roughly 85% of each was identical. This is that
|
|
1236
|
+
* 85%: the reads, the writes, the translation editor, the auto-match trigger, the banner dismissal,
|
|
1237
|
+
* and the three prop bundles a page spreads. What is NOT here is the 15% that genuinely differs —
|
|
1238
|
+
* **which** connector and module are selected, where the selection is remembered, where identity
|
|
1239
|
+
* comes from, how hrefs are built, and how connectors are enumerated. Those arrive as inputs.
|
|
1240
|
+
*
|
|
1241
|
+
* That split is not a compromise, it is the finding: every one of those five is a real difference
|
|
1242
|
+
* between the three consumers, and every attempt to absorb one would have imported a plugin's
|
|
1243
|
+
* router, auth module or connector service into this package. A consumer's remaining page hook is
|
|
1244
|
+
* the selection logic and nothing else.
|
|
1245
|
+
*
|
|
1246
|
+
* ## Multi-module is not a special case
|
|
1247
|
+
*
|
|
1248
|
+
* `moduleKey` and `moduleLabel` are ordinary inputs, so a page with a module selector re-renders
|
|
1249
|
+
* this hook with the new key and everything downstream re-keys — including the auto-match trigger,
|
|
1250
|
+
* which will then evaluate the new module's emptiness rather than the old one's. A page with one
|
|
1251
|
+
* module passes a constant and pays nothing.
|
|
1252
|
+
*
|
|
1253
|
+
* @example Single-module consumer
|
|
1254
|
+
* ```ts
|
|
1255
|
+
* const surface = useConnectorToolMappings({
|
|
1256
|
+
* service,
|
|
1257
|
+
* connectorId,
|
|
1258
|
+
* moduleKey: MODULE_KEY,
|
|
1259
|
+
* moduleLabel: "Repo Monitoring",
|
|
1260
|
+
* connectors: mapConnectorsToOptions(connectors),
|
|
1261
|
+
* isConnectorsLoading,
|
|
1262
|
+
* availableTools: mapConnectorToolsToOptions(connectorDetail?.tools ?? []),
|
|
1263
|
+
* onConnectorChange: setConnectorId,
|
|
1264
|
+
* organisationId: user?.organisationId,
|
|
1265
|
+
* aiProvidersHref: routes.settings.aiProviders.root(),
|
|
1266
|
+
* connectorSettingsHref: connectorId
|
|
1267
|
+
* ? routes.settings.connectors.details(connectorId)
|
|
1268
|
+
* : routes.settings.connectors.root(),
|
|
1269
|
+
* });
|
|
1270
|
+
* ```
|
|
1271
|
+
*/
|
|
1272
|
+
declare function useConnectorToolMappings({ service, connectorId, moduleKey, moduleLabel, connectors, isConnectorsLoading, availableTools, onConnectorChange, initiallySelected, organisationId, aiProvidersHref, connectorSettingsHref, withHeader, onMappingSaved, showSuccessToast, providerSelectionEnabled, pluginProviders, isPluginProvidersLoading, }: UseConnectorToolMappingsOptions): UseConnectorToolMappingsResult;
|
|
1273
|
+
|
|
1274
|
+
export { AI_UNAVAILABLE_REASON, AUTO_MATCH_CONFLICT_REASON, type AutoMatchResponse, type AutoMatchSummary, type BuildMappingUpdateArgs, CONNECTOR_ENUMERATION_TOOLS, CONNECTOR_MAPPING_TOOLS, type ConnectorAiUnavailableReason, type ConnectorAutoMatchConflictReason, type ConnectorAutoMatchItem, ConnectorCanonicalField, type ConnectorMappingItem, type ConnectorMappingScope, ConnectorMappingService, type ConnectorMappingSource, ConnectorMappingSuggestionResult, type ConnectorMappingTranslationState, type ConnectorMappingViewBundle, type ConnectorMappingWriteArgs, type ConnectorOption, type ConnectorSummary, type ConnectorToolOption, type ConnectorToolSummary, type ExistingMappingTarget, type IConnectorEnumerationService, type IConnectorMappingService, MAPPING_SOURCE, MAPPING_TARGET_KIND, MAPPING_TARGET_KIND_WIRE, MODULE_PROVIDER_KIND, MappingCompletenessItem, MappingCompletenessResult, type MappingProvider, MappingReadiness, type MappingTarget, type MappingTargetKind, type MappingTargetKindWire, type MappingUpdate, type MappingWriteCallbacks, type ModuleProvider, type ModuleProviderKind, type PluginProviderCandidate, type PluginProviderTool, type PluginTargetMappingItem, type SetPluginTargetMappingArgs, type SetPluginTargetMappingResult, type SuggestMappingTranslationBody, type SuggestMappingTranslationVars, type TestMappingBody, type TestMappingVars, TestToolMappingResult, type UseAutoMatchOnConnectorSwitchOptions, type UseAutoMatchOnConnectorSwitchResult, type UseConnectorMappingCompletenessResult, type UseConnectorMappingMutationsOptions, type UseConnectorMappingMutationsResult, type UseConnectorMappingsResult, type UseConnectorToolMappingsOptions, type UseConnectorToolMappingsResult, type UseMappingBannerDismissalResult, type UseMappingTranslationEditorOptions, type UseMappingTranslationEditorResult, type UseModuleProviderOptions, type UseModuleProviderResult, buildMappingUpdate, fromWireTargetKind, isSameTarget, mapConnectorToolsToOptions, mapConnectorsToOptions, moduleProviderQueryKey, providerIdOf, providerOf, toWireTargetKind, useAutoMatchOnConnectorSwitch, useConnectorMappingCompleteness, useConnectorMappingMutations, useConnectorMappings, useConnectorToolMappings, useMappingBannerDismissal, useMappingTranslationEditor, useModuleProvider, useSuggestMappingTranslation, useTestMapping };
|