@valon-technologies/gestalt 0.0.1-alpha.12 → 0.0.1-alpha.14

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/plugin.ts CHANGED
@@ -7,15 +7,6 @@ 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";
19
10
  import {
20
11
  errorMessage,
21
12
  type MaybePromise,
@@ -32,7 +23,11 @@ import {
32
23
  type HTTPSubjectResolutionContext,
33
24
  type HTTPSubjectResolver,
34
25
  } from "./http-subject.ts";
35
- import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
26
+ import {
27
+ isRuntimeProvider,
28
+ RuntimeProvider,
29
+ type RuntimeProviderOptions,
30
+ } from "./provider.ts";
36
31
  import type { Schema } from "./schema.ts";
37
32
 
38
33
  /**
@@ -42,7 +37,7 @@ export type ConnectionMode =
42
37
  | "unspecified"
43
38
  | "none"
44
39
  | "user"
45
- | "identity";
40
+ | "platform";
46
41
 
47
42
  /**
48
43
  * Metadata for a single connection parameter exposed by a provider.
@@ -92,6 +87,34 @@ export type SessionCatalogHandler = (
92
87
  request: Request,
93
88
  ) => MaybePromise<SessionCatalog | null | undefined>;
94
89
 
90
+ /**
91
+ * Host-managed connection payload passed into a provider post-connect hook.
92
+ */
93
+ export interface ConnectedToken {
94
+ id: string;
95
+ subjectId: string;
96
+ integration: string;
97
+ connection: string;
98
+ instance: string;
99
+ accessToken: string;
100
+ refreshToken: string;
101
+ scopes: string;
102
+ expiresAt?: Date | undefined;
103
+ lastRefreshedAt?: Date | undefined;
104
+ refreshErrorCount: number;
105
+ metadataJson: string;
106
+ metadata: Record<string, string>;
107
+ createdAt?: Date | undefined;
108
+ updatedAt?: Date | undefined;
109
+ }
110
+
111
+ /**
112
+ * Callback used to add derived metadata after a connection is established.
113
+ */
114
+ export type PostConnectHandler = (
115
+ token: ConnectedToken,
116
+ ) => MaybePromise<Record<string, string> | null | undefined>;
117
+
95
118
  /**
96
119
  * Runtime hooks required to implement a plugin provider.
97
120
  */
@@ -99,9 +122,8 @@ export interface PluginDefinitionOptions extends RuntimeProviderOptions {
99
122
  connectionMode?: ConnectionMode;
100
123
  authTypes?: string[];
101
124
  connectionParams?: Record<string, ConnectionParamDefinition>;
102
- securitySchemes?: Record<string, HTTPSecurityScheme>;
103
- http?: Record<string, HTTPBinding>;
104
125
  resolveHTTPSubject?: HTTPSubjectResolver;
126
+ postConnect?: PostConnectHandler;
105
127
  iconSvg?: string;
106
128
  operations: Array<OperationDefinition<any, any>>;
107
129
  sessionCatalog?: SessionCatalogHandler;
@@ -154,11 +176,10 @@ export class PluginProvider extends RuntimeProvider {
154
176
  readonly connectionMode: ConnectionMode;
155
177
  readonly authTypes: string[];
156
178
  readonly connectionParams: Record<string, ConnectionParamDefinition>;
157
- readonly securitySchemes: Record<string, HTTPSecurityScheme>;
158
- readonly http: Record<string, HTTPBinding>;
159
179
 
160
180
  private readonly sessionCatalogHandler: SessionCatalogHandler | undefined;
161
181
  private readonly httpSubjectResolver: HTTPSubjectResolver | undefined;
182
+ private readonly postConnectHandler: PostConnectHandler | undefined;
162
183
  private readonly operations = new Map<string, OperationDefinition<any, any>>();
163
184
 
164
185
  constructor(options: PluginDefinitionOptions) {
@@ -167,9 +188,8 @@ export class PluginProvider extends RuntimeProvider {
167
188
  this.connectionMode = options.connectionMode ?? "unspecified";
168
189
  this.authTypes = [...(options.authTypes ?? [])];
169
190
  this.connectionParams = normalizeConnectionParams(options.connectionParams);
170
- this.securitySchemes = normalizeHTTPSecuritySchemes(options.securitySchemes);
171
- this.http = normalizeHTTPBindings(options.http);
172
191
  this.httpSubjectResolver = options.resolveHTTPSubject;
192
+ this.postConnectHandler = options.postConnect;
173
193
  this.sessionCatalogHandler = options.sessionCatalog;
174
194
 
175
195
  for (const rawEntry of options.operations) {
@@ -200,6 +220,22 @@ export class PluginProvider extends RuntimeProvider {
200
220
  return await this.sessionCatalogHandler?.(request);
201
221
  }
202
222
 
223
+ /**
224
+ * Reports whether the provider exposes a connect-time metadata hook.
225
+ */
226
+ supportsPostConnect(): boolean {
227
+ return this.postConnectHandler !== undefined;
228
+ }
229
+
230
+ /**
231
+ * Computes additional connection metadata after a successful connect flow.
232
+ */
233
+ async postConnectMetadata(
234
+ token: ConnectedToken,
235
+ ): Promise<Record<string, string> | null | undefined> {
236
+ return await this.postConnectHandler?.(cloneConnectedToken(token));
237
+ }
238
+
203
239
  /**
204
240
  * Resolves the concrete Gestalt subject for a verified hosted HTTP request,
205
241
  * if the plugin opts into subject resolution.
@@ -295,38 +331,6 @@ export class PluginProvider extends RuntimeProvider {
295
331
  return catalogToJson(this.staticCatalog());
296
332
  }
297
333
 
298
- /**
299
- * Returns generated manifest-backed HTTP/security metadata for the provider.
300
- */
301
- staticManifestMetadata(): PluginManifestMetadata {
302
- const metadata: PluginManifestMetadata = {};
303
- if (Object.keys(this.securitySchemes).length > 0) {
304
- metadata.securitySchemes = cloneHTTPSecuritySchemes(this.securitySchemes);
305
- }
306
- if (Object.keys(this.http).length > 0) {
307
- metadata.http = cloneHTTPBindings(this.http);
308
- }
309
- return metadata;
310
- }
311
-
312
- /**
313
- * Reports whether the provider emits manifest metadata in addition to catalog metadata.
314
- */
315
- supportsManifestMetadata(): boolean {
316
- return hasPluginManifestMetadata(this.staticManifestMetadata());
317
- }
318
-
319
- /**
320
- * Writes generated manifest metadata to disk as YAML.
321
- */
322
- writeManifestMetadata(path: string): void {
323
- const metadata = this.staticManifestMetadata();
324
- if (!hasPluginManifestMetadata(metadata)) {
325
- return;
326
- }
327
- writeManifestMetadataYaml(path, metadata);
328
- }
329
-
330
334
  /**
331
335
  * Executes an operation against validated input and request metadata.
332
336
  */
@@ -386,12 +390,23 @@ export function isPluginProvider(
386
390
  ): value is PluginProvider {
387
391
  return (
388
392
  value instanceof PluginProvider ||
389
- (typeof value === "object" &&
390
- value !== null &&
393
+ (isRuntimeProvider(value) &&
391
394
  "kind" in value &&
392
395
  (value as { kind?: unknown }).kind === "integration" &&
393
396
  "staticCatalog" in value &&
394
- "execute" in value)
397
+ typeof (value as { staticCatalog?: unknown }).staticCatalog === "function" &&
398
+ "execute" in value &&
399
+ typeof (value as { execute?: unknown }).execute === "function" &&
400
+ "supportsSessionCatalog" in value &&
401
+ typeof (value as { supportsSessionCatalog?: unknown }).supportsSessionCatalog === "function" &&
402
+ "catalogForRequest" in value &&
403
+ typeof (value as { catalogForRequest?: unknown }).catalogForRequest === "function" &&
404
+ "supportsPostConnect" in value &&
405
+ typeof (value as { supportsPostConnect?: unknown }).supportsPostConnect === "function" &&
406
+ "postConnectMetadata" in value &&
407
+ typeof (value as { postConnectMetadata?: unknown }).postConnectMetadata === "function" &&
408
+ "resolveHTTPSubject" in value &&
409
+ typeof (value as { resolveHTTPSubject?: unknown }).resolveHTTPSubject === "function")
395
410
  );
396
411
  }
397
412
 
@@ -421,134 +436,19 @@ function normalizeConnectionParams(
421
436
  return output;
422
437
  }
423
438
 
424
- function normalizeHTTPSecuritySchemes(
425
- input: Record<string, HTTPSecurityScheme> | undefined,
426
- ): Record<string, HTTPSecurityScheme> {
427
- const output: Record<string, HTTPSecurityScheme> = {};
428
- for (const [key, value] of Object.entries(input ?? {})) {
429
- output[key] = cloneHTTPSecurityScheme(value);
430
- }
431
- return output;
432
- }
433
-
434
- function cloneHTTPSecuritySchemes(
435
- input: Record<string, HTTPSecurityScheme>,
436
- ): Record<string, HTTPSecurityScheme> {
437
- const output: Record<string, HTTPSecurityScheme> = {};
438
- for (const [key, value] of Object.entries(input)) {
439
- output[key] = cloneHTTPSecurityScheme(value);
440
- }
441
- return output;
442
- }
443
-
444
- function cloneHTTPSecurityScheme(value: HTTPSecurityScheme): HTTPSecurityScheme {
445
- const output: HTTPSecurityScheme = {};
446
- if (value.type !== undefined) {
447
- output.type = value.type;
448
- }
449
- if (value.description !== undefined) {
450
- output.description = value.description;
451
- }
452
- if (value.signatureHeader !== undefined) {
453
- output.signatureHeader = value.signatureHeader;
454
- }
455
- if (value.signaturePrefix !== undefined) {
456
- output.signaturePrefix = value.signaturePrefix;
457
- }
458
- if (value.payloadTemplate !== undefined) {
459
- output.payloadTemplate = value.payloadTemplate;
460
- }
461
- if (value.timestampHeader !== undefined) {
462
- output.timestampHeader = value.timestampHeader;
463
- }
464
- if (value.maxAgeSeconds !== undefined) {
465
- output.maxAgeSeconds = value.maxAgeSeconds;
466
- }
467
- if (value.name !== undefined) {
468
- output.name = value.name;
469
- }
470
- if (value.in !== undefined) {
471
- output.in = value.in;
472
- }
473
- if (value.scheme !== undefined) {
474
- output.scheme = value.scheme;
475
- }
476
- if (value.secret) {
477
- output.secret = {
478
- ...value.secret,
479
- };
480
- }
481
- return output;
482
- }
483
-
484
- function normalizeHTTPBindings(
485
- input: Record<string, HTTPBinding> | undefined,
486
- ): Record<string, HTTPBinding> {
487
- const output: Record<string, HTTPBinding> = {};
488
- for (const [key, value] of Object.entries(input ?? {})) {
489
- output[key] = cloneHTTPBinding(value);
490
- }
491
- return output;
492
- }
493
-
494
- function cloneHTTPBindings(
495
- input: Record<string, HTTPBinding>,
496
- ): Record<string, HTTPBinding> {
497
- const output: Record<string, HTTPBinding> = {};
498
- for (const [key, value] of Object.entries(input)) {
499
- output[key] = cloneHTTPBinding(value);
500
- }
501
- return output;
502
- }
503
-
504
- function cloneHTTPBinding(value: HTTPBinding): HTTPBinding {
505
- const output: HTTPBinding = {
506
- path: value.path,
507
- method: value.method,
508
- security: value.security,
509
- target: value.target,
439
+ function cloneConnectedToken(token: ConnectedToken): ConnectedToken {
440
+ return {
441
+ ...token,
442
+ metadata: {
443
+ ...(token.metadata ?? {}),
444
+ },
445
+ expiresAt: token.expiresAt ? new Date(token.expiresAt) : undefined,
446
+ lastRefreshedAt: token.lastRefreshedAt
447
+ ? new Date(token.lastRefreshedAt)
448
+ : undefined,
449
+ createdAt: token.createdAt ? new Date(token.createdAt) : undefined,
450
+ updatedAt: token.updatedAt ? new Date(token.updatedAt) : undefined,
510
451
  };
511
- if (value.requestBody) {
512
- output.requestBody = cloneHTTPRequestBody(value.requestBody);
513
- }
514
- if (value.ack) {
515
- output.ack = cloneHTTPAck(value.ack);
516
- }
517
- return output;
518
- }
519
-
520
- function cloneHTTPRequestBody(value: HTTPRequestBody): HTTPRequestBody {
521
- const output: HTTPRequestBody = {};
522
- if (value.required !== undefined) {
523
- output.required = value.required;
524
- }
525
- if (value.content) {
526
- output.content = {};
527
- for (const key of Object.keys(value.content)) {
528
- output.content[key] = {};
529
- }
530
- }
531
- return output;
532
- }
533
-
534
- function cloneHTTPAck(value: HTTPAck): HTTPAck {
535
- const output: HTTPAck = {};
536
- if (value.status !== undefined) {
537
- output.status = value.status;
538
- }
539
- if (value.headers) {
540
- output.headers = {
541
- ...value.headers,
542
- };
543
- }
544
- if (value.body !== undefined) {
545
- output.body = cloneHTTPBodyValue(value.body);
546
- }
547
- return output;
548
- }
549
-
550
- function cloneHTTPBodyValue<T>(value: T): T {
551
- return structuredClone(value);
552
452
  }
553
453
 
554
454
  function isResponse(value: unknown): value is Response<unknown> {
@@ -615,8 +515,8 @@ export function connectionModeToProtoValue(mode: ConnectionMode): number {
615
515
  return 1;
616
516
  case "user":
617
517
  return 2;
618
- case "identity":
619
- return 3;
518
+ case "platform":
519
+ return 5;
620
520
  case "unspecified":
621
521
  default:
622
522
  return 0;
@@ -0,0 +1,220 @@
1
+ import { create, type MessageInitShape } from "@bufbuild/protobuf";
2
+ import { EmptySchema } from "@bufbuild/protobuf/wkt";
3
+ import {
4
+ Code,
5
+ ConnectError,
6
+ type ServiceImpl,
7
+ } from "@connectrpc/connect";
8
+
9
+ import {
10
+ HostedPluginSchema,
11
+ ListPluginRuntimeSessionsResponseSchema,
12
+ PluginRuntimeEgressMode,
13
+ PluginRuntimeHostServiceAccess,
14
+ PluginRuntimeHostServiceBindingSchema,
15
+ PluginRuntimeProvider as PluginRuntimeProviderService,
16
+ PluginRuntimeSessionSchema,
17
+ PluginRuntimeSupportSchema,
18
+ type BindPluginRuntimeHostServiceRequest,
19
+ type GetPluginRuntimeSessionRequest,
20
+ type HostedPlugin,
21
+ type ListPluginRuntimeSessionsRequest,
22
+ type PluginRuntimeHostServiceBinding,
23
+ type PluginRuntimeSession,
24
+ type PluginRuntimeSupport,
25
+ type StartHostedPluginRequest,
26
+ type StartPluginRuntimeSessionRequest,
27
+ type StopPluginRuntimeSessionRequest,
28
+ } from "../gen/v1/pluginruntime_pb.ts";
29
+ import { errorMessage, type MaybePromise } from "./api.ts";
30
+ import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
31
+
32
+ export type {
33
+ BindPluginRuntimeHostServiceRequest,
34
+ GetPluginRuntimeSessionRequest,
35
+ HostedPlugin,
36
+ ListPluginRuntimeSessionsRequest,
37
+ PluginRuntimeHostServiceBinding,
38
+ PluginRuntimeSession,
39
+ PluginRuntimeSupport,
40
+ StartHostedPluginRequest,
41
+ StartPluginRuntimeSessionRequest,
42
+ StopPluginRuntimeSessionRequest,
43
+ };
44
+ export { PluginRuntimeEgressMode, PluginRuntimeHostServiceAccess };
45
+
46
+ export interface PluginRuntimeProviderOptions extends RuntimeProviderOptions {
47
+ getSupport: () => MaybePromise<MessageInitShape<typeof PluginRuntimeSupportSchema>>;
48
+ startSession: (
49
+ request: StartPluginRuntimeSessionRequest,
50
+ ) => MaybePromise<MessageInitShape<typeof PluginRuntimeSessionSchema>>;
51
+ getSession: (
52
+ request: GetPluginRuntimeSessionRequest,
53
+ ) => MaybePromise<MessageInitShape<typeof PluginRuntimeSessionSchema>>;
54
+ listSessions: (
55
+ request: ListPluginRuntimeSessionsRequest,
56
+ ) => MaybePromise<MessageInitShape<typeof PluginRuntimeSessionSchema>[]>;
57
+ stopSession: (request: StopPluginRuntimeSessionRequest) => MaybePromise<void>;
58
+ bindHostService: (
59
+ request: BindPluginRuntimeHostServiceRequest,
60
+ ) => MaybePromise<MessageInitShape<typeof PluginRuntimeHostServiceBindingSchema>>;
61
+ startPlugin: (
62
+ request: StartHostedPluginRequest,
63
+ ) => MaybePromise<MessageInitShape<typeof HostedPluginSchema>>;
64
+ }
65
+
66
+ export class PluginRuntimeProvider extends RuntimeProvider {
67
+ readonly kind = "runtime" as const;
68
+
69
+ private readonly getSupportHandler: PluginRuntimeProviderOptions["getSupport"];
70
+ private readonly startSessionHandler: PluginRuntimeProviderOptions["startSession"];
71
+ private readonly getSessionHandler: PluginRuntimeProviderOptions["getSession"];
72
+ private readonly listSessionsHandler: PluginRuntimeProviderOptions["listSessions"];
73
+ private readonly stopSessionHandler: PluginRuntimeProviderOptions["stopSession"];
74
+ private readonly bindHostServiceHandler: PluginRuntimeProviderOptions["bindHostService"];
75
+ private readonly startPluginHandler: PluginRuntimeProviderOptions["startPlugin"];
76
+
77
+ constructor(options: PluginRuntimeProviderOptions) {
78
+ super(options);
79
+ this.getSupportHandler = options.getSupport;
80
+ this.startSessionHandler = options.startSession;
81
+ this.getSessionHandler = options.getSession;
82
+ this.listSessionsHandler = options.listSessions;
83
+ this.stopSessionHandler = options.stopSession;
84
+ this.bindHostServiceHandler = options.bindHostService;
85
+ this.startPluginHandler = options.startPlugin;
86
+ }
87
+
88
+ async getSupport(): Promise<MessageInitShape<typeof PluginRuntimeSupportSchema>> {
89
+ return await this.getSupportHandler();
90
+ }
91
+
92
+ async startSession(
93
+ request: StartPluginRuntimeSessionRequest,
94
+ ): Promise<MessageInitShape<typeof PluginRuntimeSessionSchema>> {
95
+ return await this.startSessionHandler(request);
96
+ }
97
+
98
+ async getSession(
99
+ request: GetPluginRuntimeSessionRequest,
100
+ ): Promise<MessageInitShape<typeof PluginRuntimeSessionSchema>> {
101
+ return await this.getSessionHandler(request);
102
+ }
103
+
104
+ async listSessions(
105
+ request: ListPluginRuntimeSessionsRequest,
106
+ ): Promise<MessageInitShape<typeof PluginRuntimeSessionSchema>[]> {
107
+ return await this.listSessionsHandler(request);
108
+ }
109
+
110
+ async stopSession(request: StopPluginRuntimeSessionRequest): Promise<void> {
111
+ await this.stopSessionHandler(request);
112
+ }
113
+
114
+ async bindHostService(
115
+ request: BindPluginRuntimeHostServiceRequest,
116
+ ): Promise<MessageInitShape<typeof PluginRuntimeHostServiceBindingSchema>> {
117
+ return await this.bindHostServiceHandler(request);
118
+ }
119
+
120
+ async startPlugin(
121
+ request: StartHostedPluginRequest,
122
+ ): Promise<MessageInitShape<typeof HostedPluginSchema>> {
123
+ return await this.startPluginHandler(request);
124
+ }
125
+ }
126
+
127
+ export function definePluginRuntimeProvider(
128
+ options: PluginRuntimeProviderOptions,
129
+ ): PluginRuntimeProvider {
130
+ return new PluginRuntimeProvider(options);
131
+ }
132
+
133
+ export function isPluginRuntimeProvider(
134
+ value: unknown,
135
+ ): value is PluginRuntimeProvider {
136
+ return (
137
+ value instanceof PluginRuntimeProvider ||
138
+ (typeof value === "object" &&
139
+ value !== null &&
140
+ "kind" in value &&
141
+ (value as { kind?: unknown }).kind === "runtime" &&
142
+ "getSupport" in value &&
143
+ "startSession" in value &&
144
+ "startPlugin" in value)
145
+ );
146
+ }
147
+
148
+ export function createPluginRuntimeProviderService(
149
+ provider: PluginRuntimeProvider,
150
+ ): Partial<ServiceImpl<typeof PluginRuntimeProviderService>> {
151
+ return {
152
+ async getSupport() {
153
+ return create(
154
+ PluginRuntimeSupportSchema,
155
+ await invokePluginRuntimeProvider("get support", () =>
156
+ provider.getSupport(),
157
+ ),
158
+ );
159
+ },
160
+ async startSession(request) {
161
+ return create(
162
+ PluginRuntimeSessionSchema,
163
+ await invokePluginRuntimeProvider("start session", () =>
164
+ provider.startSession(request),
165
+ ),
166
+ );
167
+ },
168
+ async getSession(request) {
169
+ return create(
170
+ PluginRuntimeSessionSchema,
171
+ await invokePluginRuntimeProvider("get session", () =>
172
+ provider.getSession(request),
173
+ ),
174
+ );
175
+ },
176
+ async listSessions(request) {
177
+ return create(ListPluginRuntimeSessionsResponseSchema, {
178
+ sessions: await invokePluginRuntimeProvider("list sessions", () =>
179
+ provider.listSessions(request),
180
+ ),
181
+ });
182
+ },
183
+ async stopSession(request) {
184
+ await invokePluginRuntimeProvider("stop session", () =>
185
+ provider.stopSession(request),
186
+ );
187
+ return create(EmptySchema);
188
+ },
189
+ async bindHostService(request) {
190
+ return create(
191
+ PluginRuntimeHostServiceBindingSchema,
192
+ await invokePluginRuntimeProvider("bind host service", () =>
193
+ provider.bindHostService(request),
194
+ ),
195
+ );
196
+ },
197
+ async startPlugin(request) {
198
+ return create(
199
+ HostedPluginSchema,
200
+ await invokePluginRuntimeProvider("start plugin", () =>
201
+ provider.startPlugin(request),
202
+ ),
203
+ );
204
+ },
205
+ };
206
+ }
207
+
208
+ async function invokePluginRuntimeProvider<T>(
209
+ label: string,
210
+ fn: () => MaybePromise<T>,
211
+ ): Promise<T> {
212
+ try {
213
+ return await fn();
214
+ } catch (error) {
215
+ throw new ConnectError(
216
+ `plugin runtime provider ${label}: ${errorMessage(error)}`,
217
+ Code.Unknown,
218
+ );
219
+ }
220
+ }
@@ -38,12 +38,24 @@ const PROVIDER_KIND_DEFINITIONS = {
38
38
  defaultExportNames: ["s3", "provider"],
39
39
  label: "s3 provider",
40
40
  },
41
+ runtime: {
42
+ tokens: ["runtime"],
43
+ formatToken: "runtime",
44
+ defaultExportNames: ["runtime", "provider"],
45
+ label: "runtime provider",
46
+ },
41
47
  workflow: {
42
48
  tokens: ["workflow"],
43
49
  formatToken: "workflow",
44
50
  defaultExportNames: ["workflow", "provider"],
45
51
  label: "workflow provider",
46
52
  },
53
+ agent: {
54
+ tokens: ["agent"],
55
+ formatToken: "agent",
56
+ defaultExportNames: ["agent", "provider"],
57
+ label: "agent provider",
58
+ },
47
59
  telemetry: {
48
60
  tokens: ["telemetry"],
49
61
  formatToken: "telemetry",
package/src/provider.ts CHANGED
@@ -9,7 +9,9 @@ export type ProviderKind =
9
9
  | "cache"
10
10
  | "secrets"
11
11
  | "s3"
12
+ | "runtime"
12
13
  | "workflow"
14
+ | "agent"
13
15
  | "telemetry";
14
16
 
15
17
  /**
@@ -41,6 +43,12 @@ export type HealthCheckHandler = () => MaybePromise<void>;
41
43
  */
42
44
  export type WarningsHandler = () => MaybePromise<string[]>;
43
45
 
46
+ /**
47
+ * Optional hook invoked after configuration when the host is ready for
48
+ * provider-owned background work to begin.
49
+ */
50
+ export type StartHandler = () => MaybePromise<void>;
51
+
44
52
  /**
45
53
  * Optional shutdown hook invoked when the provider process exits.
46
54
  */
@@ -57,6 +65,7 @@ export interface RuntimeProviderOptions {
57
65
  configure?: ConfigureHandler;
58
66
  healthCheck?: HealthCheckHandler;
59
67
  warnings?: string[] | WarningsHandler;
68
+ start?: StartHandler;
60
69
  close?: CloseHandler;
61
70
  }
62
71
 
@@ -74,6 +83,7 @@ export abstract class RuntimeProvider {
74
83
  private readonly configureHandler: ConfigureHandler | undefined;
75
84
  private readonly healthCheckHandler: HealthCheckHandler | undefined;
76
85
  private readonly warningsSource: string[] | WarningsHandler | undefined;
86
+ private readonly startHandler: StartHandler | undefined;
77
87
  private readonly closeHandler: CloseHandler | undefined;
78
88
 
79
89
  protected constructor(options: RuntimeProviderOptions) {
@@ -86,6 +96,7 @@ export abstract class RuntimeProvider {
86
96
  this.warningsSource = Array.isArray(options.warnings)
87
97
  ? [...options.warnings]
88
98
  : options.warnings;
99
+ this.startHandler = options.start;
89
100
  this.closeHandler = options.close;
90
101
  }
91
102
 
@@ -126,6 +137,10 @@ export abstract class RuntimeProvider {
126
137
  await this.healthCheckHandler?.();
127
138
  }
128
139
 
140
+ async startProvider(): Promise<void> {
141
+ await this.startHandler?.();
142
+ }
143
+
129
144
  async warnings(): Promise<string[]> {
130
145
  if (!this.warningsSource) {
131
146
  return [];
@@ -151,7 +166,19 @@ export function isRuntimeProvider(value: unknown): value is RuntimeProvider {
151
166
  value !== null &&
152
167
  "kind" in value &&
153
168
  "resolveName" in value &&
154
- "configureProvider" in value)
169
+ typeof (value as { resolveName?: unknown }).resolveName === "function" &&
170
+ "configureProvider" in value &&
171
+ typeof (value as { configureProvider?: unknown }).configureProvider === "function" &&
172
+ "supportsHealthCheck" in value &&
173
+ typeof (value as { supportsHealthCheck?: unknown }).supportsHealthCheck === "function" &&
174
+ "healthCheck" in value &&
175
+ typeof (value as { healthCheck?: unknown }).healthCheck === "function" &&
176
+ "startProvider" in value &&
177
+ typeof (value as { startProvider?: unknown }).startProvider === "function" &&
178
+ "warnings" in value &&
179
+ typeof (value as { warnings?: unknown }).warnings === "function" &&
180
+ "closeProvider" in value &&
181
+ typeof (value as { closeProvider?: unknown }).closeProvider === "function")
155
182
  );
156
183
  }
157
184