@valon-technologies/gestalt 0.0.1-alpha.1 → 0.0.1-alpha.11

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/src/invoker.ts ADDED
@@ -0,0 +1,160 @@
1
+ import { connect } from "node:net";
2
+
3
+ import type { JsonObject, JsonValue } from "@bufbuild/protobuf";
4
+ import { createClient, type Client } from "@connectrpc/connect";
5
+ import { createGrpcTransport } from "@connectrpc/connect-node";
6
+
7
+ import { PluginInvoker as PluginInvokerService } from "../gen/v1/plugin_pb.ts";
8
+ import type { OperationResult, Request } from "./api.ts";
9
+
10
+ export const ENV_PLUGIN_INVOKER_SOCKET = "GESTALT_PLUGIN_INVOKER_SOCKET";
11
+
12
+ export interface PluginInvokeOptions {
13
+ connection?: string;
14
+ instance?: string;
15
+ }
16
+
17
+ export interface PluginInvocationGrant {
18
+ plugin: string;
19
+ operations?: string[];
20
+ surfaces?: string[];
21
+ allOperations?: boolean;
22
+ }
23
+
24
+ export interface PluginGraphQLInvokeOptions extends PluginInvokeOptions {
25
+ variables?: Record<string, unknown>;
26
+ }
27
+
28
+ export class PluginInvoker {
29
+ private readonly client: Client<typeof PluginInvokerService>;
30
+ private readonly invocationToken: string;
31
+
32
+ constructor(request: Request);
33
+ constructor(invocationToken: string);
34
+ constructor(requestOrToken: Request | string) {
35
+ this.invocationToken = normalizeInvocationToken(requestOrToken);
36
+
37
+ const socketPath = process.env[ENV_PLUGIN_INVOKER_SOCKET];
38
+ if (!socketPath) {
39
+ throw new Error(`plugin invoker: ${ENV_PLUGIN_INVOKER_SOCKET} is not set`);
40
+ }
41
+
42
+ const transport = createGrpcTransport({
43
+ baseUrl: "http://localhost",
44
+ nodeOptions: {
45
+ createConnection: () => connect(socketPath),
46
+ },
47
+ });
48
+ this.client = createClient(PluginInvokerService, transport);
49
+ }
50
+
51
+ async invoke(
52
+ plugin: string,
53
+ operation: string,
54
+ params: Record<string, unknown> = {},
55
+ options?: PluginInvokeOptions,
56
+ ): Promise<OperationResult> {
57
+ const response = await this.client.invoke({
58
+ invocationToken: this.invocationToken,
59
+ plugin,
60
+ operation,
61
+ params: toJsonObject(params),
62
+ connection: options?.connection ?? "",
63
+ instance: options?.instance ?? "",
64
+ });
65
+ return {
66
+ status: response.status,
67
+ body: response.body,
68
+ };
69
+ }
70
+
71
+ async invokeGraphQL(
72
+ plugin: string,
73
+ document: string,
74
+ options?: PluginGraphQLInvokeOptions,
75
+ ): Promise<OperationResult> {
76
+ const trimmedDocument = document.trim();
77
+ if (!trimmedDocument) {
78
+ throw new Error("plugin invoker: graphql document is required");
79
+ }
80
+
81
+ const response = await this.client.invokeGraphQL({
82
+ invocationToken: this.invocationToken,
83
+ plugin,
84
+ document: trimmedDocument,
85
+ ...(options?.variables
86
+ ? { variables: toJsonObject(options.variables) }
87
+ : {}),
88
+ connection: options?.connection ?? "",
89
+ instance: options?.instance ?? "",
90
+ });
91
+ return {
92
+ status: response.status,
93
+ body: response.body,
94
+ };
95
+ }
96
+
97
+ async exchangeInvocationToken(options?: {
98
+ grants?: PluginInvocationGrant[];
99
+ ttlSeconds?: number;
100
+ }): Promise<string> {
101
+ const response = await this.client.exchangeInvocationToken({
102
+ parentInvocationToken: this.invocationToken,
103
+ grants: (options?.grants ?? [])
104
+ .map((grant) => ({
105
+ plugin: grant.plugin.trim(),
106
+ operations: (grant.operations ?? [])
107
+ .map((operation) => operation.trim())
108
+ .filter(Boolean),
109
+ surfaces: (grant.surfaces ?? [])
110
+ .map((surface) => surface.trim().toLowerCase())
111
+ .filter(Boolean),
112
+ allOperations: grant.allOperations ?? false,
113
+ }))
114
+ .filter((grant) => grant.plugin.length > 0),
115
+ ttlSeconds: BigInt(Math.max(0, options?.ttlSeconds ?? 0)),
116
+ });
117
+ return response.invocationToken;
118
+ }
119
+ }
120
+
121
+ function normalizeInvocationToken(requestOrToken: Request | string): string {
122
+ const invocationToken =
123
+ typeof requestOrToken === "string"
124
+ ? requestOrToken
125
+ : requestOrToken.invocationToken;
126
+ const trimmed = invocationToken.trim();
127
+ if (!trimmed) {
128
+ throw new Error("plugin invoker: invocation token is not available");
129
+ }
130
+ return trimmed;
131
+ }
132
+
133
+ function toJsonObject(params: Record<string, unknown>): JsonObject {
134
+ const output: JsonObject = {};
135
+ for (const [key, value] of Object.entries(params ?? {})) {
136
+ if (value === undefined) {
137
+ continue;
138
+ }
139
+ output[key] = toJsonValue(value);
140
+ }
141
+ return output;
142
+ }
143
+
144
+ function toJsonValue(value: unknown): JsonValue {
145
+ if (
146
+ value === null ||
147
+ typeof value === "string" ||
148
+ typeof value === "number" ||
149
+ typeof value === "boolean"
150
+ ) {
151
+ return value as JsonValue;
152
+ }
153
+ if (Array.isArray(value)) {
154
+ return value.map((entry) => toJsonValue(entry));
155
+ }
156
+ if (typeof value === "object") {
157
+ return toJsonObject(value as Record<string, unknown>);
158
+ }
159
+ throw new Error("plugin invoker: params must be JSON-serializable");
160
+ }
@@ -0,0 +1,101 @@
1
+ import { writeFileSync } from "node:fs";
2
+
3
+ import YAML from "yaml";
4
+
5
+ export type HTTPSecuritySchemeType =
6
+ | "slack_signature"
7
+ | "apiKey"
8
+ | "http"
9
+ | "none";
10
+
11
+ export type HTTPIn = "header" | "query";
12
+
13
+ export type HTTPAuthScheme = "basic" | "bearer";
14
+
15
+ export interface HTTPSecretRef {
16
+ env?: string;
17
+ secret?: string;
18
+ }
19
+
20
+ export interface HTTPSecurityScheme {
21
+ type?: HTTPSecuritySchemeType;
22
+ description?: string;
23
+ name?: string;
24
+ in?: HTTPIn;
25
+ scheme?: HTTPAuthScheme;
26
+ secret?: HTTPSecretRef;
27
+ }
28
+
29
+ export interface HTTPMediaType {}
30
+
31
+ export interface HTTPRequestBody {
32
+ required?: boolean;
33
+ content?: Record<string, HTTPMediaType>;
34
+ }
35
+
36
+ export interface HTTPAck {
37
+ status?: number;
38
+ headers?: Record<string, string>;
39
+ body?: any;
40
+ }
41
+
42
+ export interface HTTPBinding {
43
+ path: string;
44
+ method: string;
45
+ requestBody?: HTTPRequestBody;
46
+ security: string;
47
+ target: string;
48
+ ack?: HTTPAck;
49
+ }
50
+
51
+ export interface PluginManifestMetadata {
52
+ securitySchemes?: Record<string, HTTPSecurityScheme>;
53
+ http?: Record<string, HTTPBinding>;
54
+ }
55
+
56
+ export function hasPluginManifestMetadata(
57
+ metadata: PluginManifestMetadata | null | undefined,
58
+ ): boolean {
59
+ return !!(
60
+ metadata &&
61
+ ((metadata.securitySchemes &&
62
+ Object.keys(metadata.securitySchemes).length > 0) ||
63
+ (metadata.http && Object.keys(metadata.http).length > 0))
64
+ );
65
+ }
66
+
67
+ export function manifestMetadataToYaml(
68
+ metadata: PluginManifestMetadata | Record<string, unknown>,
69
+ ): string {
70
+ return YAML.stringify(toManifestMetadataJsonObject(metadata));
71
+ }
72
+
73
+ export function writeManifestMetadataYaml(
74
+ path: string,
75
+ metadata: PluginManifestMetadata | Record<string, unknown>,
76
+ ): void {
77
+ writeFileSync(path, manifestMetadataToYaml(metadata), "utf8");
78
+ }
79
+
80
+ function toManifestMetadataJsonObject(
81
+ metadata: PluginManifestMetadata | Record<string, unknown>,
82
+ ): Record<string, unknown> {
83
+ if (!("securitySchemes" in metadata) && !("http" in metadata)) {
84
+ return {
85
+ ...metadata,
86
+ };
87
+ }
88
+
89
+ const typedMetadata = metadata as PluginManifestMetadata;
90
+ const output: Record<string, unknown> = {};
91
+ if (
92
+ typedMetadata.securitySchemes &&
93
+ Object.keys(typedMetadata.securitySchemes).length > 0
94
+ ) {
95
+ output.securitySchemes = typedMetadata.securitySchemes;
96
+ }
97
+ if (typedMetadata.http && Object.keys(typedMetadata.http).length > 0) {
98
+ output.http = typedMetadata.http;
99
+ }
100
+ return output;
101
+ }
package/src/plugin.ts CHANGED
@@ -7,6 +7,15 @@ import {
7
7
  schemaToParameters,
8
8
  writeCatalogYaml,
9
9
  } from "./catalog.ts";
10
+ import {
11
+ type HTTPAck,
12
+ type HTTPBinding,
13
+ type HTTPRequestBody,
14
+ type HTTPSecurityScheme,
15
+ type PluginManifestMetadata,
16
+ hasPluginManifestMetadata,
17
+ writeManifestMetadataYaml,
18
+ } from "./manifest-metadata.ts";
10
19
  import {
11
20
  errorMessage,
12
21
  type MaybePromise,
@@ -18,13 +27,18 @@ import {
18
27
  import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
19
28
  import type { Schema } from "./schema.ts";
20
29
 
30
+ /**
31
+ * How a plugin provider expects to authenticate or connect.
32
+ */
21
33
  export type ConnectionMode =
22
34
  | "unspecified"
23
35
  | "none"
24
36
  | "user"
25
- | "identity"
26
- | "either";
37
+ | "identity";
27
38
 
39
+ /**
40
+ * Metadata for a single connection parameter exposed by a provider.
41
+ */
28
42
  export interface ConnectionParamDefinition {
29
43
  required?: boolean;
30
44
  description?: string;
@@ -33,6 +47,9 @@ export interface ConnectionParamDefinition {
33
47
  field?: string;
34
48
  }
35
49
 
50
+ /**
51
+ * Operation definition accepted by {@link operation} and {@link definePlugin}.
52
+ */
36
53
  export interface OperationOptions<In, Out> {
37
54
  id: string;
38
55
  method?: string;
@@ -47,27 +64,43 @@ export interface OperationOptions<In, Out> {
47
64
  handler: (input: In, request: Request) => MaybePromise<Out | Response<Out>>;
48
65
  }
49
66
 
67
+ /**
68
+ * Normalized plugin operation definition.
69
+ */
50
70
  export interface OperationDefinition<In, Out> extends OperationOptions<
51
71
  In,
52
72
  Out
53
73
  > {}
54
74
 
75
+ /**
76
+ * Session-specific catalog payload returned by a provider at runtime.
77
+ */
55
78
  export type SessionCatalog = Catalog | Record<string, unknown>;
79
+
80
+ /**
81
+ * Callback used to resolve a catalog for an authenticated request context.
82
+ */
56
83
  export type SessionCatalogHandler = (
57
84
  request: Request,
58
85
  ) => MaybePromise<SessionCatalog | null | undefined>;
59
86
 
87
+ /**
88
+ * Runtime hooks required to implement a plugin provider.
89
+ */
60
90
  export interface PluginDefinitionOptions extends RuntimeProviderOptions {
61
91
  connectionMode?: ConnectionMode;
62
92
  authTypes?: string[];
63
93
  connectionParams?: Record<string, ConnectionParamDefinition>;
94
+ securitySchemes?: Record<string, HTTPSecurityScheme>;
95
+ http?: Record<string, HTTPBinding>;
64
96
  iconSvg?: string;
65
97
  operations: Array<OperationDefinition<any, any>>;
66
98
  sessionCatalog?: SessionCatalogHandler;
67
99
  }
68
100
 
69
- export type IntegrationProviderOptions = PluginDefinitionOptions;
70
-
101
+ /**
102
+ * Normalizes a plugin operation definition.
103
+ */
71
104
  export function operation<In, Out>(
72
105
  options: OperationOptions<In, Out>,
73
106
  ): OperationDefinition<In, Out> {
@@ -82,18 +115,41 @@ export function operation<In, Out>(
82
115
  };
83
116
  }
84
117
 
85
- export class IntegrationProvider extends RuntimeProvider {
118
+ /**
119
+ * Plugin provider implementation consumed by the Gestalt runtime.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * import { definePlugin, ok, operation, s } from "@valon-technologies/gestalt";
124
+ *
125
+ * export const plugin = definePlugin({
126
+ * displayName: "Example Provider",
127
+ * operations: [
128
+ * operation({
129
+ * id: "ping",
130
+ * method: "GET",
131
+ * readOnly: true,
132
+ * input: s.object({ name: s.string({ default: "World" }) }),
133
+ * output: s.object({ message: s.string() }),
134
+ * async handler(input) {
135
+ * return ok({ message: `Hello, ${input.name}` });
136
+ * },
137
+ * }),
138
+ * ],
139
+ * });
140
+ * ```
141
+ */
142
+ export class PluginProvider extends RuntimeProvider {
86
143
  readonly kind = "integration" as const;
87
144
  readonly iconSvg: string;
88
145
  readonly connectionMode: ConnectionMode;
89
146
  readonly authTypes: string[];
90
147
  readonly connectionParams: Record<string, ConnectionParamDefinition>;
148
+ readonly securitySchemes: Record<string, HTTPSecurityScheme>;
149
+ readonly http: Record<string, HTTPBinding>;
91
150
 
92
151
  private readonly sessionCatalogHandler: SessionCatalogHandler | undefined;
93
- private readonly operations = new Map<
94
- string,
95
- OperationDefinition<any, any>
96
- >();
152
+ private readonly operations = new Map<string, OperationDefinition<any, any>>();
97
153
 
98
154
  constructor(options: PluginDefinitionOptions) {
99
155
  super(options);
@@ -101,38 +157,41 @@ export class IntegrationProvider extends RuntimeProvider {
101
157
  this.connectionMode = options.connectionMode ?? "unspecified";
102
158
  this.authTypes = [...(options.authTypes ?? [])];
103
159
  this.connectionParams = normalizeConnectionParams(options.connectionParams);
160
+ this.securitySchemes = normalizeHTTPSecuritySchemes(options.securitySchemes);
161
+ this.http = normalizeHTTPBindings(options.http);
104
162
  this.sessionCatalogHandler = options.sessionCatalog;
105
163
 
106
- for (const entry of options.operations) {
107
- const id = entry.id.trim();
108
- if (!id) {
164
+ for (const rawEntry of options.operations) {
165
+ const entry = operation(rawEntry);
166
+ if (!entry.id) {
109
167
  throw new Error("operation id is required");
110
168
  }
111
- if (this.operations.has(id)) {
112
- throw new Error(`duplicate operation id ${JSON.stringify(id)}`);
169
+ if (this.operations.has(entry.id)) {
170
+ throw new Error(`duplicate operation id ${JSON.stringify(entry.id)}`);
113
171
  }
114
- this.operations.set(id, {
115
- ...entry,
116
- id,
117
- method: normalizeMethod(entry.method),
118
- title: entry.title?.trim() ?? "",
119
- description: entry.description?.trim() ?? "",
120
- allowedRoles: normalizeAllowedRoles(entry.allowedRoles),
121
- tags: [...(entry.tags ?? [])],
122
- });
172
+ this.operations.set(entry.id, entry);
123
173
  }
124
174
  }
125
175
 
176
+ /**
177
+ * Reports whether the provider exposes a session-specific catalog.
178
+ */
126
179
  supportsSessionCatalog(): boolean {
127
180
  return this.sessionCatalogHandler !== undefined;
128
181
  }
129
182
 
183
+ /**
184
+ * Resolves a catalog for the current request context, if configured.
185
+ */
130
186
  async catalogForRequest(
131
187
  request: Request,
132
188
  ): Promise<SessionCatalog | null | undefined> {
133
189
  return await this.sessionCatalogHandler?.(request);
134
190
  }
135
191
 
192
+ /**
193
+ * Returns the static catalog emitted during provider startup.
194
+ */
136
195
  staticCatalog(): Catalog {
137
196
  const catalog: Catalog = {
138
197
  operations: [...this.operations.values()].map<CatalogOperation>(
@@ -197,14 +256,55 @@ export class IntegrationProvider extends RuntimeProvider {
197
256
  return catalog;
198
257
  }
199
258
 
259
+ /**
260
+ * Writes the provider's static catalog to disk as YAML.
261
+ */
200
262
  writeCatalog(path: string): void {
201
263
  writeCatalogYaml(path, this.staticCatalog());
202
264
  }
203
265
 
266
+ /**
267
+ * Returns the static catalog serialized as JSON.
268
+ */
204
269
  catalogJson(): string {
205
270
  return catalogToJson(this.staticCatalog());
206
271
  }
207
272
 
273
+ /**
274
+ * Returns generated manifest-backed HTTP/security metadata for the provider.
275
+ */
276
+ staticManifestMetadata(): PluginManifestMetadata {
277
+ const metadata: PluginManifestMetadata = {};
278
+ if (Object.keys(this.securitySchemes).length > 0) {
279
+ metadata.securitySchemes = cloneHTTPSecuritySchemes(this.securitySchemes);
280
+ }
281
+ if (Object.keys(this.http).length > 0) {
282
+ metadata.http = cloneHTTPBindings(this.http);
283
+ }
284
+ return metadata;
285
+ }
286
+
287
+ /**
288
+ * Reports whether the provider emits manifest metadata in addition to catalog metadata.
289
+ */
290
+ supportsManifestMetadata(): boolean {
291
+ return hasPluginManifestMetadata(this.staticManifestMetadata());
292
+ }
293
+
294
+ /**
295
+ * Writes generated manifest metadata to disk as YAML.
296
+ */
297
+ writeManifestMetadata(path: string): void {
298
+ const metadata = this.staticManifestMetadata();
299
+ if (!hasPluginManifestMetadata(metadata)) {
300
+ return;
301
+ }
302
+ writeManifestMetadataYaml(path, metadata);
303
+ }
304
+
305
+ /**
306
+ * Executes an operation against validated input and request metadata.
307
+ */
208
308
  async execute(
209
309
  operationId: string,
210
310
  params: Record<string, unknown>,
@@ -244,25 +344,23 @@ export class IntegrationProvider extends RuntimeProvider {
244
344
  }
245
345
  }
246
346
 
247
- export const Plugin = IntegrationProvider;
248
-
249
- export function defineIntegrationProvider(
250
- options: PluginDefinitionOptions,
251
- ): IntegrationProvider {
252
- return new IntegrationProvider(options);
253
- }
254
-
347
+ /**
348
+ * Creates a plugin provider.
349
+ */
255
350
  export function definePlugin(
256
351
  options: PluginDefinitionOptions,
257
- ): IntegrationProvider {
258
- return new IntegrationProvider(options);
352
+ ): PluginProvider {
353
+ return new PluginProvider(options);
259
354
  }
260
355
 
261
- export function isIntegrationProvider(
356
+ /**
357
+ * Runtime type guard for plugin providers loaded from user modules.
358
+ */
359
+ export function isPluginProvider(
262
360
  value: unknown,
263
- ): value is IntegrationProvider {
361
+ ): value is PluginProvider {
264
362
  return (
265
- value instanceof IntegrationProvider ||
363
+ value instanceof PluginProvider ||
266
364
  (typeof value === "object" &&
267
365
  value !== null &&
268
366
  "kind" in value &&
@@ -298,6 +396,121 @@ function normalizeConnectionParams(
298
396
  return output;
299
397
  }
300
398
 
399
+ function normalizeHTTPSecuritySchemes(
400
+ input: Record<string, HTTPSecurityScheme> | undefined,
401
+ ): Record<string, HTTPSecurityScheme> {
402
+ const output: Record<string, HTTPSecurityScheme> = {};
403
+ for (const [key, value] of Object.entries(input ?? {})) {
404
+ output[key] = cloneHTTPSecurityScheme(value);
405
+ }
406
+ return output;
407
+ }
408
+
409
+ function cloneHTTPSecuritySchemes(
410
+ input: Record<string, HTTPSecurityScheme>,
411
+ ): Record<string, HTTPSecurityScheme> {
412
+ const output: Record<string, HTTPSecurityScheme> = {};
413
+ for (const [key, value] of Object.entries(input)) {
414
+ output[key] = cloneHTTPSecurityScheme(value);
415
+ }
416
+ return output;
417
+ }
418
+
419
+ function cloneHTTPSecurityScheme(value: HTTPSecurityScheme): HTTPSecurityScheme {
420
+ const output: HTTPSecurityScheme = {};
421
+ if (value.type !== undefined) {
422
+ output.type = value.type;
423
+ }
424
+ if (value.description !== undefined) {
425
+ output.description = value.description;
426
+ }
427
+ if (value.name !== undefined) {
428
+ output.name = value.name;
429
+ }
430
+ if (value.in !== undefined) {
431
+ output.in = value.in;
432
+ }
433
+ if (value.scheme !== undefined) {
434
+ output.scheme = value.scheme;
435
+ }
436
+ if (value.secret) {
437
+ output.secret = {
438
+ ...value.secret,
439
+ };
440
+ }
441
+ return output;
442
+ }
443
+
444
+ function normalizeHTTPBindings(
445
+ input: Record<string, HTTPBinding> | undefined,
446
+ ): Record<string, HTTPBinding> {
447
+ const output: Record<string, HTTPBinding> = {};
448
+ for (const [key, value] of Object.entries(input ?? {})) {
449
+ output[key] = cloneHTTPBinding(value);
450
+ }
451
+ return output;
452
+ }
453
+
454
+ function cloneHTTPBindings(
455
+ input: Record<string, HTTPBinding>,
456
+ ): Record<string, HTTPBinding> {
457
+ const output: Record<string, HTTPBinding> = {};
458
+ for (const [key, value] of Object.entries(input)) {
459
+ output[key] = cloneHTTPBinding(value);
460
+ }
461
+ return output;
462
+ }
463
+
464
+ function cloneHTTPBinding(value: HTTPBinding): HTTPBinding {
465
+ const output: HTTPBinding = {
466
+ path: value.path,
467
+ method: value.method,
468
+ security: value.security,
469
+ target: value.target,
470
+ };
471
+ if (value.requestBody) {
472
+ output.requestBody = cloneHTTPRequestBody(value.requestBody);
473
+ }
474
+ if (value.ack) {
475
+ output.ack = cloneHTTPAck(value.ack);
476
+ }
477
+ return output;
478
+ }
479
+
480
+ function cloneHTTPRequestBody(value: HTTPRequestBody): HTTPRequestBody {
481
+ const output: HTTPRequestBody = {};
482
+ if (value.required !== undefined) {
483
+ output.required = value.required;
484
+ }
485
+ if (value.content) {
486
+ output.content = {};
487
+ for (const key of Object.keys(value.content)) {
488
+ output.content[key] = {};
489
+ }
490
+ }
491
+ return output;
492
+ }
493
+
494
+ function cloneHTTPAck(value: HTTPAck): HTTPAck {
495
+ const output: HTTPAck = {};
496
+ if (value.status !== undefined) {
497
+ output.status = value.status;
498
+ }
499
+ if (value.headers) {
500
+ output.headers = {
501
+ ...value.headers,
502
+ };
503
+ }
504
+ if (value.body !== undefined) {
505
+ output.body = cloneHTTPBodyValue(value.body);
506
+ }
507
+ return output;
508
+ }
509
+
510
+ function cloneHTTPBodyValue<T>(value: T): T {
511
+ return structuredClone(value);
512
+ }
513
+
301
514
  function isResponse(value: unknown): value is Response<unknown> {
302
515
  if (typeof value !== "object" || value === null) {
303
516
  return false;
@@ -353,6 +566,9 @@ function errorResult(status: number, message: string): OperationResult {
353
566
  };
354
567
  }
355
568
 
569
+ /**
570
+ * Converts a connection mode into the shared protocol enum value.
571
+ */
356
572
  export function connectionModeToProtoValue(mode: ConnectionMode): number {
357
573
  switch (mode) {
358
574
  case "none":
@@ -361,14 +577,15 @@ export function connectionModeToProtoValue(mode: ConnectionMode): number {
361
577
  return 2;
362
578
  case "identity":
363
579
  return 3;
364
- case "either":
365
- return 4;
366
580
  case "unspecified":
367
581
  default:
368
582
  return 0;
369
583
  }
370
584
  }
371
585
 
586
+ /**
587
+ * Converts a connection parameter definition into protocol wire metadata.
588
+ */
372
589
  export function connectionParamToProto(value: ConnectionParamDefinition): {
373
590
  required?: boolean;
374
591
  description?: string;