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

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/index.ts CHANGED
@@ -32,6 +32,7 @@ export {
32
32
  Authorization,
33
33
  AuthorizationClient,
34
34
  ENV_AUTHORIZATION_SOCKET,
35
+ ENV_AUTHORIZATION_SOCKET_TOKEN,
35
36
  type AuthorizationActionSearchMessage,
36
37
  type AuthorizationDecisionMessage,
37
38
  type AuthorizationEvaluateInput,
@@ -105,8 +106,25 @@ export {
105
106
  type PluginInvocationGrant,
106
107
  type PluginInvokeOptions,
107
108
  } from "./invoker.ts";
109
+ export {
110
+ ENV_AGENT_MANAGER_SOCKET,
111
+ ENV_AGENT_MANAGER_SOCKET_TOKEN,
112
+ AgentManager,
113
+ type AgentManagerCancelTurnInput,
114
+ type AgentManagerCreateSessionInput,
115
+ type AgentManagerCreateTurnInput,
116
+ type AgentManagerGetSessionInput,
117
+ type AgentManagerGetTurnInput,
118
+ type AgentManagerListInteractionsInput,
119
+ type AgentManagerListSessionsInput,
120
+ type AgentManagerListTurnEventsInput,
121
+ type AgentManagerListTurnsInput,
122
+ type AgentManagerResolveInteractionInput,
123
+ type AgentManagerUpdateSessionInput,
124
+ } from "./agent-manager.ts";
108
125
  export {
109
126
  ENV_WORKFLOW_MANAGER_SOCKET,
127
+ ENV_WORKFLOW_MANAGER_SOCKET_TOKEN,
110
128
  WorkflowManager,
111
129
  type ManagedWorkflowEventTriggerMessage,
112
130
  type ManagedWorkflowScheduleMessage,
@@ -155,6 +173,7 @@ export {
155
173
  type SecretsProviderOptions,
156
174
  } from "./secrets.ts";
157
175
  export {
176
+ type ConnectedToken,
158
177
  PluginProvider,
159
178
  connectionModeToProtoValue,
160
179
  connectionParamToProto,
@@ -165,6 +184,7 @@ export {
165
184
  type ConnectionParamDefinition,
166
185
  type OperationDefinition,
167
186
  type OperationOptions,
187
+ type PostConnectHandler,
168
188
  type PluginDefinitionOptions,
169
189
  type SessionCatalog,
170
190
  type SessionCatalogHandler,
@@ -255,7 +275,10 @@ export {
255
275
  createS3Service,
256
276
  defineS3Provider,
257
277
  isS3Provider,
278
+ ENV_S3_SOCKET,
279
+ ENV_S3_SOCKET_TOKEN,
258
280
  s3SocketEnv,
281
+ s3SocketTokenEnv,
259
282
  type ByteRange,
260
283
  type CopyOptions,
261
284
  type ListOptions,
@@ -271,6 +294,50 @@ export {
271
294
  type S3ProviderOptions,
272
295
  type WriteOptions,
273
296
  } from "./s3.ts";
297
+ export {
298
+ ENV_AGENT_HOST_SOCKET,
299
+ AgentHost,
300
+ AgentExecutionStatus,
301
+ AgentInteractionState,
302
+ AgentInteractionType,
303
+ AgentMessagePartType,
304
+ AgentProvider,
305
+ AgentSessionState,
306
+ AgentToolSourceMode,
307
+ createAgentProviderService,
308
+ defineAgentProvider,
309
+ isAgentProvider,
310
+ type AgentActor,
311
+ type AgentInteraction,
312
+ type AgentMessage,
313
+ type AgentMessagePart,
314
+ type AgentMessagePartImageRef,
315
+ type AgentMessagePartToolCall,
316
+ type AgentMessagePartToolResult,
317
+ type AgentProviderCapabilities,
318
+ type AgentProviderOptions,
319
+ type AgentSession,
320
+ type AgentToolRef,
321
+ type AgentTurn,
322
+ type AgentTurnEvent,
323
+ type BoundAgentToolTarget,
324
+ type CancelAgentProviderTurnRequest,
325
+ type CreateAgentProviderSessionRequest,
326
+ type CreateAgentProviderTurnRequest,
327
+ type ExecuteAgentToolRequest,
328
+ type ExecuteAgentToolResponse,
329
+ type GetAgentProviderCapabilitiesRequest,
330
+ type GetAgentProviderInteractionRequest,
331
+ type GetAgentProviderSessionRequest,
332
+ type GetAgentProviderTurnRequest,
333
+ type ListAgentProviderInteractionsRequest,
334
+ type ListAgentProviderSessionsRequest,
335
+ type ListAgentProviderTurnEventsRequest,
336
+ type ListAgentProviderTurnsRequest,
337
+ type ResolvedAgentTool,
338
+ type ResolveAgentProviderInteractionRequest,
339
+ type UpdateAgentProviderSessionRequest,
340
+ } from "./agent.ts";
274
341
  export {
275
342
  ENV_WORKFLOW_HOST_SOCKET,
276
343
  WorkflowHost,
@@ -47,6 +47,7 @@ export interface HTTPAck {
47
47
  export interface HTTPBinding {
48
48
  path: string;
49
49
  method: string;
50
+ credentialMode?: "none" | "user";
50
51
  requestBody?: HTTPRequestBody;
51
52
  security: string;
52
53
  target: string;
package/src/plugin.ts CHANGED
@@ -32,7 +32,11 @@ import {
32
32
  type HTTPSubjectResolutionContext,
33
33
  type HTTPSubjectResolver,
34
34
  } from "./http-subject.ts";
35
- import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
35
+ import {
36
+ isRuntimeProvider,
37
+ RuntimeProvider,
38
+ type RuntimeProviderOptions,
39
+ } from "./provider.ts";
36
40
  import type { Schema } from "./schema.ts";
37
41
 
38
42
  /**
@@ -92,6 +96,34 @@ export type SessionCatalogHandler = (
92
96
  request: Request,
93
97
  ) => MaybePromise<SessionCatalog | null | undefined>;
94
98
 
99
+ /**
100
+ * Host-managed connection payload passed into a provider post-connect hook.
101
+ */
102
+ export interface ConnectedToken {
103
+ id: string;
104
+ subjectId: string;
105
+ integration: string;
106
+ connection: string;
107
+ instance: string;
108
+ accessToken: string;
109
+ refreshToken: string;
110
+ scopes: string;
111
+ expiresAt?: Date | undefined;
112
+ lastRefreshedAt?: Date | undefined;
113
+ refreshErrorCount: number;
114
+ metadataJson: string;
115
+ metadata: Record<string, string>;
116
+ createdAt?: Date | undefined;
117
+ updatedAt?: Date | undefined;
118
+ }
119
+
120
+ /**
121
+ * Callback used to add derived metadata after a connection is established.
122
+ */
123
+ export type PostConnectHandler = (
124
+ token: ConnectedToken,
125
+ ) => MaybePromise<Record<string, string> | null | undefined>;
126
+
95
127
  /**
96
128
  * Runtime hooks required to implement a plugin provider.
97
129
  */
@@ -102,6 +134,7 @@ export interface PluginDefinitionOptions extends RuntimeProviderOptions {
102
134
  securitySchemes?: Record<string, HTTPSecurityScheme>;
103
135
  http?: Record<string, HTTPBinding>;
104
136
  resolveHTTPSubject?: HTTPSubjectResolver;
137
+ postConnect?: PostConnectHandler;
105
138
  iconSvg?: string;
106
139
  operations: Array<OperationDefinition<any, any>>;
107
140
  sessionCatalog?: SessionCatalogHandler;
@@ -159,6 +192,7 @@ export class PluginProvider extends RuntimeProvider {
159
192
 
160
193
  private readonly sessionCatalogHandler: SessionCatalogHandler | undefined;
161
194
  private readonly httpSubjectResolver: HTTPSubjectResolver | undefined;
195
+ private readonly postConnectHandler: PostConnectHandler | undefined;
162
196
  private readonly operations = new Map<string, OperationDefinition<any, any>>();
163
197
 
164
198
  constructor(options: PluginDefinitionOptions) {
@@ -170,6 +204,7 @@ export class PluginProvider extends RuntimeProvider {
170
204
  this.securitySchemes = normalizeHTTPSecuritySchemes(options.securitySchemes);
171
205
  this.http = normalizeHTTPBindings(options.http);
172
206
  this.httpSubjectResolver = options.resolveHTTPSubject;
207
+ this.postConnectHandler = options.postConnect;
173
208
  this.sessionCatalogHandler = options.sessionCatalog;
174
209
 
175
210
  for (const rawEntry of options.operations) {
@@ -200,6 +235,22 @@ export class PluginProvider extends RuntimeProvider {
200
235
  return await this.sessionCatalogHandler?.(request);
201
236
  }
202
237
 
238
+ /**
239
+ * Reports whether the provider exposes a connect-time metadata hook.
240
+ */
241
+ supportsPostConnect(): boolean {
242
+ return this.postConnectHandler !== undefined;
243
+ }
244
+
245
+ /**
246
+ * Computes additional connection metadata after a successful connect flow.
247
+ */
248
+ async postConnectMetadata(
249
+ token: ConnectedToken,
250
+ ): Promise<Record<string, string> | null | undefined> {
251
+ return await this.postConnectHandler?.(cloneConnectedToken(token));
252
+ }
253
+
203
254
  /**
204
255
  * Resolves the concrete Gestalt subject for a verified hosted HTTP request,
205
256
  * if the plugin opts into subject resolution.
@@ -386,12 +437,27 @@ export function isPluginProvider(
386
437
  ): value is PluginProvider {
387
438
  return (
388
439
  value instanceof PluginProvider ||
389
- (typeof value === "object" &&
390
- value !== null &&
440
+ (isRuntimeProvider(value) &&
391
441
  "kind" in value &&
392
442
  (value as { kind?: unknown }).kind === "integration" &&
393
443
  "staticCatalog" in value &&
394
- "execute" in value)
444
+ typeof (value as { staticCatalog?: unknown }).staticCatalog === "function" &&
445
+ "execute" in value &&
446
+ typeof (value as { execute?: unknown }).execute === "function" &&
447
+ "supportsSessionCatalog" in value &&
448
+ typeof (value as { supportsSessionCatalog?: unknown }).supportsSessionCatalog === "function" &&
449
+ "catalogForRequest" in value &&
450
+ typeof (value as { catalogForRequest?: unknown }).catalogForRequest === "function" &&
451
+ "supportsManifestMetadata" in value &&
452
+ typeof (value as { supportsManifestMetadata?: unknown }).supportsManifestMetadata === "function" &&
453
+ "writeManifestMetadata" in value &&
454
+ typeof (value as { writeManifestMetadata?: unknown }).writeManifestMetadata === "function" &&
455
+ "supportsPostConnect" in value &&
456
+ typeof (value as { supportsPostConnect?: unknown }).supportsPostConnect === "function" &&
457
+ "postConnectMetadata" in value &&
458
+ typeof (value as { postConnectMetadata?: unknown }).postConnectMetadata === "function" &&
459
+ "resolveHTTPSubject" in value &&
460
+ typeof (value as { resolveHTTPSubject?: unknown }).resolveHTTPSubject === "function")
395
461
  );
396
462
  }
397
463
 
@@ -421,6 +487,21 @@ function normalizeConnectionParams(
421
487
  return output;
422
488
  }
423
489
 
490
+ function cloneConnectedToken(token: ConnectedToken): ConnectedToken {
491
+ return {
492
+ ...token,
493
+ metadata: {
494
+ ...(token.metadata ?? {}),
495
+ },
496
+ expiresAt: token.expiresAt ? new Date(token.expiresAt) : undefined,
497
+ lastRefreshedAt: token.lastRefreshedAt
498
+ ? new Date(token.lastRefreshedAt)
499
+ : undefined,
500
+ createdAt: token.createdAt ? new Date(token.createdAt) : undefined,
501
+ updatedAt: token.updatedAt ? new Date(token.updatedAt) : undefined,
502
+ };
503
+ }
504
+
424
505
  function normalizeHTTPSecuritySchemes(
425
506
  input: Record<string, HTTPSecurityScheme> | undefined,
426
507
  ): Record<string, HTTPSecurityScheme> {
@@ -44,6 +44,12 @@ const PROVIDER_KIND_DEFINITIONS = {
44
44
  defaultExportNames: ["workflow", "provider"],
45
45
  label: "workflow provider",
46
46
  },
47
+ agent: {
48
+ tokens: ["agent"],
49
+ formatToken: "agent",
50
+ defaultExportNames: ["agent", "provider"],
51
+ label: "agent provider",
52
+ },
47
53
  telemetry: {
48
54
  tokens: ["telemetry"],
49
55
  formatToken: "telemetry",
package/src/provider.ts CHANGED
@@ -10,6 +10,7 @@ export type ProviderKind =
10
10
  | "secrets"
11
11
  | "s3"
12
12
  | "workflow"
13
+ | "agent"
13
14
  | "telemetry";
14
15
 
15
16
  /**
@@ -151,7 +152,17 @@ export function isRuntimeProvider(value: unknown): value is RuntimeProvider {
151
152
  value !== null &&
152
153
  "kind" in value &&
153
154
  "resolveName" in value &&
154
- "configureProvider" in value)
155
+ typeof (value as { resolveName?: unknown }).resolveName === "function" &&
156
+ "configureProvider" in value &&
157
+ typeof (value as { configureProvider?: unknown }).configureProvider === "function" &&
158
+ "supportsHealthCheck" in value &&
159
+ typeof (value as { supportsHealthCheck?: unknown }).supportsHealthCheck === "function" &&
160
+ "healthCheck" in value &&
161
+ typeof (value as { healthCheck?: unknown }).healthCheck === "function" &&
162
+ "warnings" in value &&
163
+ typeof (value as { warnings?: unknown }).warnings === "function" &&
164
+ "closeProvider" in value &&
165
+ typeof (value as { closeProvider?: unknown }).closeProvider === "function")
155
166
  );
156
167
  }
157
168
 
package/src/runtime.ts CHANGED
@@ -12,6 +12,9 @@ import {
12
12
  } from "@connectrpc/connect";
13
13
  import { connectNodeAdapter } from "@connectrpc/connect-node";
14
14
 
15
+ import {
16
+ AgentProvider as AgentProviderService,
17
+ } from "../gen/v1/agent_pb.ts";
15
18
  import {
16
19
  AuthenticationProvider as AuthenticationProviderService,
17
20
  AuthSessionSettingsSchema,
@@ -40,10 +43,12 @@ import {
40
43
  CatalogSchema as ProtoCatalogSchema,
41
44
  ConnectionMode as ProviderConnectionMode,
42
45
  GetSessionCatalogResponseSchema,
46
+ PostConnectResponseSchema,
43
47
  ResolveHTTPSubjectResponseSchema,
44
48
  OperationResultSchema,
45
49
  ProviderMetadataSchema,
46
50
  type HTTPSubjectRequest as ProtoHTTPSubjectRequest,
51
+ type IntegrationToken as ProtoIntegrationToken,
47
52
  type RequestContext as ProtoRequestContext,
48
53
  type ResolveHTTPSubjectRequest as ProtoResolveHTTPSubjectRequest,
49
54
  IntegrationProvider as IntegrationProviderService,
@@ -63,6 +68,11 @@ import {
63
68
  import { S3 as S3Service } from "../gen/v1/s3_pb.ts";
64
69
  import { WorkflowProvider as WorkflowProviderService } from "../gen/v1/workflow_pb.ts";
65
70
  import { errorMessage, type Request } from "./api.ts";
71
+ import {
72
+ AgentProvider,
73
+ createAgentProviderService,
74
+ isAgentProvider,
75
+ } from "./agent.ts";
66
76
  import {
67
77
  AuthenticationProvider,
68
78
  isAuthenticationProvider,
@@ -77,6 +87,7 @@ import {
77
87
  type HTTPSubjectResolutionContext,
78
88
  } from "./http-subject.ts";
79
89
  import {
90
+ type ConnectedToken,
80
91
  PluginProvider,
81
92
  connectionModeToProtoValue,
82
93
  connectionParamToProto,
@@ -127,6 +138,7 @@ export const CURRENT_PROTOCOL_VERSION = 3;
127
138
  * Command-line usage for the runtime entrypoint.
128
139
  */
129
140
  export const USAGE = "usage: bun run runtime.ts ROOT PROVIDER_TARGET";
141
+ export { createAgentProviderService } from "./agent.ts";
130
142
  export { createWorkflowProviderService } from "./workflow.ts";
131
143
 
132
144
  /**
@@ -146,6 +158,7 @@ export type LoadedProvider =
146
158
  | CacheProvider
147
159
  | SecretsProvider
148
160
  | S3Provider
161
+ | AgentProvider
149
162
  | WorkflowProvider;
150
163
 
151
164
  type ProviderRuntimeEntry = {
@@ -205,6 +218,16 @@ const PROVIDER_RUNTIME_ENTRIES: Partial<
205
218
  router.service(S3Service, createS3Service(provider as S3Provider));
206
219
  },
207
220
  },
221
+ agent: {
222
+ isProvider: isAgentProvider as (value: unknown) => value is LoadedProvider,
223
+ protoKind: ProtoProviderKind.AGENT,
224
+ registerService(router, provider) {
225
+ router.service(
226
+ AgentProviderService,
227
+ createAgentProviderService(provider as AgentProvider),
228
+ );
229
+ },
230
+ },
208
231
  workflow: {
209
232
  isProvider: isWorkflowProvider as (value: unknown) => value is LoadedProvider,
210
233
  protoKind: ProtoProviderKind.WORKFLOW,
@@ -412,16 +435,20 @@ export function createRuntimeService(
412
435
  ): Partial<ServiceImpl<typeof ProviderLifecycle>> {
413
436
  return {
414
437
  async getProviderIdentity() {
415
- return create(ProviderIdentitySchema, {
416
- kind: providerRuntimeEntry(provider.kind).protoKind,
417
- name: provider.name,
418
- displayName: provider.displayName,
419
- description: provider.description,
420
- version: provider.version,
421
- warnings: await provider.warnings(),
422
- minProtocolVersion: CURRENT_PROTOCOL_VERSION,
423
- maxProtocolVersion: CURRENT_PROTOCOL_VERSION,
424
- });
438
+ try {
439
+ return create(ProviderIdentitySchema, {
440
+ kind: providerRuntimeEntry(provider.kind).protoKind,
441
+ name: provider.name,
442
+ displayName: provider.displayName,
443
+ description: provider.description,
444
+ version: provider.version,
445
+ warnings: await provider.warnings(),
446
+ minProtocolVersion: CURRENT_PROTOCOL_VERSION,
447
+ maxProtocolVersion: CURRENT_PROTOCOL_VERSION,
448
+ });
449
+ } catch (error) {
450
+ throw providerRuntimeError("provider identity", error);
451
+ }
425
452
  },
426
453
  async configureProvider(request: ConfigureProviderRequest) {
427
454
  assertProtocolVersion(request.protocolVersion);
@@ -431,10 +458,7 @@ export function createRuntimeService(
431
458
  objectFromUnknown(request.config),
432
459
  );
433
460
  } catch (error) {
434
- throw new ConnectError(
435
- `configure provider: ${errorMessage(error)}`,
436
- Code.Unknown,
437
- );
461
+ throw providerRuntimeError("configure provider", error);
438
462
  }
439
463
  return create(ConfigureProviderResponseSchema, {
440
464
  protocolVersion: CURRENT_PROTOCOL_VERSION,
@@ -473,27 +497,31 @@ export function createProviderService(
473
497
  throw new Error("provider is not a plugin provider");
474
498
  }
475
499
  return {
476
- getMetadata() {
477
- return create(ProviderMetadataSchema, {
478
- name: provider.name,
479
- displayName: provider.displayName,
480
- description: provider.description,
481
- connectionMode: connectionModeToProtoValue(
482
- provider.connectionMode,
483
- ) as ProviderConnectionMode,
484
- authTypes: [...provider.authTypes],
485
- connectionParams: Object.fromEntries(
486
- Object.entries(provider.connectionParams).map(([key, value]) => [
487
- key,
488
- connectionParamToProto(value),
489
- ]),
490
- ),
491
- staticCatalog: catalogToProto(provider.staticCatalog()),
492
- supportsSessionCatalog: provider.supportsSessionCatalog(),
493
- supportsPostConnect: false,
494
- minProtocolVersion: CURRENT_PROTOCOL_VERSION,
495
- maxProtocolVersion: CURRENT_PROTOCOL_VERSION,
496
- });
500
+ async getMetadata() {
501
+ try {
502
+ return create(ProviderMetadataSchema, {
503
+ name: provider.name,
504
+ displayName: provider.displayName,
505
+ description: provider.description,
506
+ connectionMode: connectionModeToProtoValue(
507
+ provider.connectionMode,
508
+ ) as ProviderConnectionMode,
509
+ authTypes: [...provider.authTypes],
510
+ connectionParams: Object.fromEntries(
511
+ Object.entries(provider.connectionParams).map(([key, value]) => [
512
+ key,
513
+ connectionParamToProto(value),
514
+ ]),
515
+ ),
516
+ staticCatalog: catalogToProto(provider.staticCatalog()),
517
+ supportsSessionCatalog: provider.supportsSessionCatalog(),
518
+ supportsPostConnect: provider.supportsPostConnect(),
519
+ minProtocolVersion: CURRENT_PROTOCOL_VERSION,
520
+ maxProtocolVersion: CURRENT_PROTOCOL_VERSION,
521
+ });
522
+ } catch (error) {
523
+ throw providerRuntimeError("provider metadata", error);
524
+ }
497
525
  },
498
526
  async startProvider(request: StartProviderRequest) {
499
527
  assertProtocolVersion(request.protocolVersion);
@@ -503,10 +531,7 @@ export function createProviderService(
503
531
  objectFromUnknown(request.config),
504
532
  );
505
533
  } catch (error) {
506
- throw new ConnectError(
507
- `configure provider: ${errorMessage(error)}`,
508
- Code.Unknown,
509
- );
534
+ throw providerRuntimeError("configure provider", error);
510
535
  }
511
536
  return create(StartProviderResponseSchema, {
512
537
  protocolVersion: CURRENT_PROTOCOL_VERSION,
@@ -583,11 +608,29 @@ export function createProviderService(
583
608
  catalog: catalogToProto(catalog),
584
609
  });
585
610
  },
586
- async postConnect() {
587
- throw new ConnectError(
588
- "provider does not support post connect",
589
- Code.Unimplemented,
590
- );
611
+ async postConnect(request) {
612
+ if (!provider.supportsPostConnect()) {
613
+ throw new ConnectError(
614
+ "provider does not support post connect",
615
+ Code.Unimplemented,
616
+ );
617
+ }
618
+ let metadata: Record<string, string> | null | undefined;
619
+ try {
620
+ metadata = await provider.postConnectMetadata(
621
+ providerConnectedToken(request.token),
622
+ );
623
+ } catch (error) {
624
+ throw new ConnectError(
625
+ `post connect: ${errorMessage(error)}`,
626
+ Code.Unknown,
627
+ );
628
+ }
629
+ return create(PostConnectResponseSchema, {
630
+ metadata: {
631
+ ...(metadata ?? {}),
632
+ },
633
+ });
591
634
  },
592
635
  };
593
636
  }
@@ -822,6 +865,29 @@ function providerHTTPSubjectResolutionContext(
822
865
  };
823
866
  }
824
867
 
868
+ function providerConnectedToken(
869
+ token?: ProtoIntegrationToken,
870
+ ): ConnectedToken {
871
+ const metadataJson = token?.metadataJson ?? "";
872
+ return {
873
+ id: token?.id ?? "",
874
+ subjectId: token?.userId ?? "",
875
+ integration: token?.integration ?? "",
876
+ connection: token?.connection ?? "",
877
+ instance: token?.instance ?? "",
878
+ accessToken: token?.accessToken ?? "",
879
+ refreshToken: token?.refreshToken ?? "",
880
+ scopes: token?.scopes ?? "",
881
+ expiresAt: timestampToDate(token?.expiresAt),
882
+ lastRefreshedAt: timestampToDate(token?.lastRefreshedAt),
883
+ refreshErrorCount: token?.refreshErrorCount ?? 0,
884
+ metadataJson,
885
+ metadata: stringRecordFromJSON(metadataJson),
886
+ createdAt: timestampToDate(token?.createdAt),
887
+ updatedAt: timestampToDate(token?.updatedAt),
888
+ };
889
+ }
890
+
825
891
  function providerStringLists(
826
892
  input: Record<string, { values?: string[] }> | undefined,
827
893
  ): Record<string, string[]> {
@@ -844,6 +910,10 @@ function providerRuntimeEntry(
844
910
  return entry;
845
911
  }
846
912
 
913
+ function providerRuntimeError(label: string, error: unknown): ConnectError {
914
+ return new ConnectError(`${label}: ${errorMessage(error)}`, Code.Unknown);
915
+ }
916
+
847
917
  function resolveLoadedProvider(
848
918
  candidate: unknown,
849
919
  kind: ProviderKind,
@@ -930,6 +1000,45 @@ function normalizeBigInt(value: number | bigint): bigint {
930
1000
  return BigInt(Math.max(0, Math.trunc(value)));
931
1001
  }
932
1002
 
1003
+ function timestampToDate(
1004
+ value: { seconds: bigint; nanos: number } | undefined,
1005
+ ): Date | undefined {
1006
+ if (!value) {
1007
+ return undefined;
1008
+ }
1009
+ const seconds = Number(value.seconds ?? 0n);
1010
+ const nanos = Number(value.nanos ?? 0);
1011
+ if (!Number.isFinite(seconds) || !Number.isFinite(nanos)) {
1012
+ return undefined;
1013
+ }
1014
+ const millis = (seconds * 1000) + Math.trunc(nanos / 1_000_000);
1015
+ if (!Number.isFinite(millis)) {
1016
+ return undefined;
1017
+ }
1018
+ return new Date(millis);
1019
+ }
1020
+
1021
+ function stringRecordFromJSON(value: string): Record<string, string> {
1022
+ if (!value.trim()) {
1023
+ return {};
1024
+ }
1025
+ try {
1026
+ const parsed = JSON.parse(value) as unknown;
1027
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1028
+ return {};
1029
+ }
1030
+ const output: Record<string, string> = {};
1031
+ for (const [key, entry] of Object.entries(parsed)) {
1032
+ if (typeof entry === "string") {
1033
+ output[key] = entry;
1034
+ }
1035
+ }
1036
+ return output;
1037
+ } catch {
1038
+ return {};
1039
+ }
1040
+ }
1041
+
933
1042
  function cloneUint8Array(value: Uint8Array | undefined): Uint8Array {
934
1043
  if (!value) {
935
1044
  return new Uint8Array();