@codemation/host 0.5.1 → 0.7.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/CHANGELOG.md +465 -0
- package/LICENSE +1 -37
- package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
- package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
- package/dist/{AppConfigFactory-CvpFScwB.js → AppConfigFactory-Cx4qQvRk.js} +114 -53
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
- package/dist/{AppConfigFactory-LK76niPc.d.ts → AppConfigFactory-DnLoQ9Li.d.ts} +8527 -5549
- package/dist/{AppContainerFactory-BlLrm6_h.js → AppContainerFactory-DqKYCRNP.js} +7656 -2090
- package/dist/AppContainerFactory-DqKYCRNP.js.map +1 -0
- package/dist/{CodemationAppContext-CvWi5gey.d.ts → CodemationAppContext-CKVv9W9q.d.ts} +8 -4
- package/dist/{CodemationAuthoring.types-BuKNTDC1.d.ts → CodemationAuthoring.types-DA3G3s6d.d.ts} +25 -5
- package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-NGkBcmmT.js} +18 -6
- package/dist/CodemationAuthoring.types-NGkBcmmT.js.map +1 -0
- package/dist/{CodemationConfigNormalizer-CYdR0PR5.d.ts → CodemationConfigNormalizer-BAKjetJ6.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-GYpBBvqE.js} +79 -10
- package/dist/CodemationConsumerConfigLoader-GYpBBvqE.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-C3nAj9Bj.d.ts → CodemationConsumerConfigLoader-nxOqvv46.d.ts} +17 -2
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
- package/dist/{CodemationPluginListMerger-D-gwVwtw.d.ts → CodemationPluginListMerger-DKLAHT2b.d.ts} +123 -16
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
- package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
- package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
- package/dist/{CredentialContractsRegistry-D7mcPed2.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
- package/dist/{CredentialServices-DdCEP2xt.d.ts → CredentialServices-Be2I60Th.d.ts} +65 -20
- package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
- package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
- package/dist/InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts +17 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
- package/dist/{ItemsInputNormalizer-D1WppVMU.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +108 -25
- package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
- package/dist/PublicFrontendBootstrapFactory-CY2FS-5g.d.ts +82 -0
- package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
- package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
- package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
- package/dist/{TelemetryContracts-BsOD_Y17.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
- package/dist/{WorkflowPolicyUiPresentationFactory-DNE5oAI6.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
- package/dist/{WorkflowViewContracts-0ZgsHQdp.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +15 -1
- package/dist/authoring.d.ts +5 -5
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -2
- package/dist/consumer.d.ts +6 -6
- package/dist/consumer.js +2 -2
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/devServerSidecar.js +1 -94
- package/dist/devServerSidecar.js.map +1 -1
- package/dist/dto.d.ts +6 -6
- package/dist/{index-BlGs9e9Q.d.ts → index-DilAYwnH.d.ts} +49 -3
- package/dist/index.d.ts +110 -21
- package/dist/index.js +15 -13
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +43 -88
- package/dist/nextServer.js +9 -7
- package/dist/pairing.d.ts +93 -0
- package/dist/pairing.js +5 -0
- package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
- package/dist/{persistenceServer-CpNFYa_q.js → persistenceServer-C-hH4z6l.js} +2 -2
- package/dist/{persistenceServer-CpNFYa_q.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
- package/dist/persistenceServer-CeTHtC6E.d.ts +30 -0
- package/dist/persistenceServer.d.ts +8 -8
- package/dist/persistenceServer.js +3 -3
- package/dist/{server-CQWdkT7t.d.ts → server-C4bS62rg.d.ts} +21 -6
- package/dist/{server-BK43OKxW.js → server-Y7kxwtCK.js} +7 -6
- package/dist/{server-BK43OKxW.js.map → server-Y7kxwtCK.js.map} +1 -1
- package/dist/server.d.ts +14 -14
- package/dist/server.js +13 -11
- package/package.json +29 -42
- package/prisma/migrations/20260507120000_execution_instance_child_run_id/migration.sql +5 -0
- package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
- package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
- package/prisma/migrations.sqlite/20260507120000_execution_instance_child_run_id/migration.sql +5 -0
- package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
- package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
- package/prisma/schema.postgresql.prisma +56 -17
- package/prisma/schema.sqlite.prisma +56 -17
- package/prisma-generated/prisma-postgresql-client/edge.js +35 -6
- package/prisma-generated/prisma-postgresql-client/index-browser.js +31 -2
- package/prisma-generated/prisma-postgresql-client/index.d.ts +8971 -5718
- package/prisma-generated/prisma-postgresql-client/index.js +35 -6
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +39 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +35 -6
- package/prisma-generated/prisma-sqlite-client/index-browser.js +31 -2
- package/prisma-generated/prisma-sqlite-client/index.d.ts +8963 -5715
- package/prisma-generated/prisma-sqlite-client/index.js +35 -6
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +39 -0
- package/scripts/check-collections.mjs +18 -0
- package/scripts/generate-prisma-clients.mjs +20 -11
- package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
- package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
- package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
- package/src/application/contracts/WorkflowViewContracts.ts +11 -0
- package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
- package/src/application/mapping/WorkflowDefinitionMapper.ts +44 -1
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
- package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
- package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
- package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
- package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
- package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
- package/src/applicationTokens.ts +20 -1
- package/src/audit/IAuditEmitter.ts +32 -0
- package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
- package/src/audit/WorkflowAuditLogWriter.ts +125 -0
- package/src/auth/managed/ManagedAuthConfig.ts +29 -0
- package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
- package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
- package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
- package/src/auth/managed/index.ts +5 -0
- package/src/bootstrap/AppContainerFactory.ts +277 -29
- package/src/bootstrap/AppContainerLifecycle.ts +31 -0
- package/src/bootstrap/perf/BootTimer.ts +168 -0
- package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
- package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
- package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
- package/src/credentials/BrokerClient.ts +49 -0
- package/src/credentials/BrokerRefreshError.ts +12 -0
- package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
- package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
- package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
- package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
- package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
- package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/catalogTypes.ts +4 -0
- package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
- package/src/domain/credentials/CredentialBindingService.ts +54 -2
- package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
- package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
- package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
- package/src/domain/telemetry/TelemetryContracts.ts +7 -1
- package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
- package/src/index.ts +6 -0
- package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
- package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
- package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
- package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
- package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
- package/src/infrastructure/persistence/InMemoryWorkflowRunRepository.ts +1 -0
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
- package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +33 -3
- package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
- package/src/mcp/McpClientFactory.ts +29 -0
- package/src/mcp/McpConnectionPool.ts +184 -0
- package/src/mcp/McpConnectionPool.types.ts +12 -0
- package/src/mcp/McpServerCatalog.ts +104 -0
- package/src/mcp/index.ts +5 -0
- package/src/pairing/HmacRequestSigner.ts +32 -0
- package/src/pairing/IncomingHmacVerifier.ts +82 -0
- package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
- package/src/pairing/InternalPingRegistrar.ts +25 -0
- package/src/pairing/PairedFetch.ts +33 -0
- package/src/pairing/PairingConfigFactory.ts +35 -0
- package/src/pairing/PairingConfigToken.ts +6 -0
- package/src/pairing/index.ts +14 -0
- package/src/pairing/pairing.types.ts +18 -0
- package/src/pairing.ts +17 -0
- package/src/persistenceServer.ts +1 -0
- package/src/presentation/config/AppConfig.ts +7 -1
- package/src/presentation/config/CodemationAuthConfig.ts +1 -1
- package/src/presentation/config/CodemationAuthoring.types.ts +54 -5
- package/src/presentation/config/CodemationConfig.ts +3 -0
- package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
- package/src/presentation/config/CodemationPlugin.ts +2 -1
- package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
- package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
- package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
- package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
- package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
- package/src/presentation/http/ApiPaths.ts +4 -4
- package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
- package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
- package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
- package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
- package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +54 -7
- package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
- package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
- package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
- package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
- package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
- package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
- package/src/process/ExecaProcessRunner.ts +41 -0
- package/src/process/ProcessRunner.types.ts +39 -0
- package/src/server.ts +2 -0
- package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
- package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
- package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
- package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
- package/tsconfig.json +2 -0
- package/vitest.shared.ts +5 -0
- package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
- package/dist/AppConfigFactory-CvpFScwB.js.map +0 -1
- package/dist/AppContainerFactory-BlLrm6_h.js.map +0 -1
- package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
- package/dist/CredentialServices-CgxwguAv.js.map +0 -1
- package/dist/PublicFrontendBootstrapFactory-BMWqNM9a.d.ts +0 -45
- package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
- package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
- package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
- package/dist/persistenceServer-CIVt3UOX.d.ts +0 -9
- package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
|
@@ -22,6 +22,8 @@ import { inject, injectable } from "@codemation/core";
|
|
|
22
22
|
import type { WorkflowRunRepository } from "../../domain/runs/WorkflowRunRepository";
|
|
23
23
|
import type { Prisma } from "../../../prisma-generated/prisma-postgresql-client/client.js";
|
|
24
24
|
import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
|
|
25
|
+
import type { WorkflowSnapshotRepository } from "./PrismaWorkflowSnapshotRepository";
|
|
26
|
+
import { PrismaWorkflowSnapshotRepository } from "./PrismaWorkflowSnapshotRepository";
|
|
25
27
|
|
|
26
28
|
type ExecutionInstanceRow = {
|
|
27
29
|
instanceId: string;
|
|
@@ -52,6 +54,7 @@ type ExecutionInstanceRow = {
|
|
|
52
54
|
iterationId: string | null;
|
|
53
55
|
itemIndex: number | null;
|
|
54
56
|
parentInvocationId: string | null;
|
|
57
|
+
childRunId: string | null;
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
type RunWorkItemRecord = {
|
|
@@ -79,7 +82,10 @@ type RunSlotProjectionRow = {
|
|
|
79
82
|
|
|
80
83
|
@injectable()
|
|
81
84
|
export class PrismaWorkflowRunRepository implements WorkflowRunRepository, WorkflowExecutionRepository {
|
|
82
|
-
constructor(
|
|
85
|
+
constructor(
|
|
86
|
+
@inject(PrismaDatabaseClientToken) private readonly prisma: PrismaDatabaseClient,
|
|
87
|
+
@inject(PrismaWorkflowSnapshotRepository) private readonly snapshotRepo: WorkflowSnapshotRepository,
|
|
88
|
+
) {}
|
|
83
89
|
|
|
84
90
|
async createRun(args: {
|
|
85
91
|
runId: RunId;
|
|
@@ -95,6 +101,14 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
95
101
|
}): Promise<void> {
|
|
96
102
|
const now = new Date().toISOString();
|
|
97
103
|
const testContext = args.executionOptions?.testContext;
|
|
104
|
+
const snapshotJson = args.workflowSnapshot ? JSON.stringify(args.workflowSnapshot) : null;
|
|
105
|
+
const workflowSnapshotId =
|
|
106
|
+
snapshotJson !== null
|
|
107
|
+
? await this.snapshotRepo.findOrCreate({
|
|
108
|
+
workflowId: args.workflowId,
|
|
109
|
+
snapshotJson,
|
|
110
|
+
})
|
|
111
|
+
: null;
|
|
98
112
|
await this.prisma.run.create({
|
|
99
113
|
data: {
|
|
100
114
|
runId: args.runId,
|
|
@@ -107,7 +121,8 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
107
121
|
revision: 0,
|
|
108
122
|
outputsByNodeJson: JSON.stringify({}),
|
|
109
123
|
controlJson: args.control ? JSON.stringify(args.control) : null,
|
|
110
|
-
workflowSnapshotJson:
|
|
124
|
+
workflowSnapshotJson: snapshotJson,
|
|
125
|
+
workflowSnapshotId,
|
|
111
126
|
policySnapshotJson: args.policySnapshot ? JSON.stringify(args.policySnapshot) : null,
|
|
112
127
|
engineCountersJson: args.engineCounters ? JSON.stringify(args.engineCounters) : null,
|
|
113
128
|
mutableStateJson: args.mutableState ? JSON.stringify(args.mutableState) : null,
|
|
@@ -289,6 +304,14 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
289
304
|
const workItems = this.buildWorkItems(state, now);
|
|
290
305
|
const instances = this.buildExecutionInstances(state);
|
|
291
306
|
const projectionJson = this.buildProjectionSlotStatesJson(state);
|
|
307
|
+
const snapshotJson = state.workflowSnapshot ? JSON.stringify(state.workflowSnapshot) : null;
|
|
308
|
+
const workflowSnapshotId =
|
|
309
|
+
snapshotJson !== null
|
|
310
|
+
? await this.snapshotRepo.findOrCreate({
|
|
311
|
+
workflowId: state.workflowId,
|
|
312
|
+
snapshotJson,
|
|
313
|
+
})
|
|
314
|
+
: null;
|
|
292
315
|
|
|
293
316
|
await this.prisma.$transaction(async (tx) => {
|
|
294
317
|
await tx.runWorkItem.deleteMany({ where: { runId: state.runId } });
|
|
@@ -345,6 +368,7 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
345
368
|
inputTruncated: instance.inputTruncated,
|
|
346
369
|
outputTruncated: instance.outputTruncated,
|
|
347
370
|
usedPinnedOutput: instance.usedPinnedOutput,
|
|
371
|
+
childRunId: instance.childRunId,
|
|
348
372
|
},
|
|
349
373
|
});
|
|
350
374
|
continue;
|
|
@@ -387,7 +411,8 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
387
411
|
parentJson: state.parent ? JSON.stringify(state.parent) : null,
|
|
388
412
|
executionOptionsJson: state.executionOptions ? JSON.stringify(state.executionOptions) : null,
|
|
389
413
|
controlJson: state.control ? JSON.stringify(state.control) : null,
|
|
390
|
-
workflowSnapshotJson:
|
|
414
|
+
workflowSnapshotJson: snapshotJson,
|
|
415
|
+
workflowSnapshotId,
|
|
391
416
|
policySnapshotJson: state.policySnapshot ? JSON.stringify(state.policySnapshot) : null,
|
|
392
417
|
engineCountersJson: state.engineCounters ? JSON.stringify(state.engineCounters) : null,
|
|
393
418
|
mutableStateJson: state.mutableState ? JSON.stringify(state.mutableState) : null,
|
|
@@ -510,6 +535,9 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
510
535
|
inputsByPort,
|
|
511
536
|
outputs,
|
|
512
537
|
error,
|
|
538
|
+
...(row.childRunId !== null && row.childRunId !== undefined
|
|
539
|
+
? { childRunId: row.childRunId as NodeExecutionSnapshot["childRunId"] }
|
|
540
|
+
: {}),
|
|
513
541
|
};
|
|
514
542
|
}
|
|
515
543
|
|
|
@@ -564,6 +592,7 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
564
592
|
iterationId: row.iterationId ?? undefined,
|
|
565
593
|
itemIndex: row.itemIndex ?? undefined,
|
|
566
594
|
parentInvocationId: row.parentInvocationId ?? undefined,
|
|
595
|
+
...(row.childRunId !== null && row.childRunId !== undefined ? { childRunId: row.childRunId } : {}),
|
|
567
596
|
};
|
|
568
597
|
}
|
|
569
598
|
|
|
@@ -636,6 +665,7 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
636
665
|
inputStorageKind: "inline",
|
|
637
666
|
outputStorageKind: "inline",
|
|
638
667
|
usedPinnedOutput: snap.usedPinnedOutput ?? null,
|
|
668
|
+
childRunId: snap.childRunId ?? null,
|
|
639
669
|
});
|
|
640
670
|
}
|
|
641
671
|
let cIdx = 0;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { inject, injectable } from "@codemation/core";
|
|
3
|
+
import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
|
|
4
|
+
|
|
5
|
+
export interface WorkflowSnapshotRepository {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the id of an existing snapshot matching (workflowId, snapshotHash), or creates
|
|
8
|
+
* a new one from the provided snapshotJson. Deduplication is by content hash.
|
|
9
|
+
*/
|
|
10
|
+
findOrCreate(args: Readonly<{ workflowId: string; snapshotJson: string }>): Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@injectable()
|
|
14
|
+
export class PrismaWorkflowSnapshotRepository implements WorkflowSnapshotRepository {
|
|
15
|
+
constructor(
|
|
16
|
+
@inject(PrismaDatabaseClientToken)
|
|
17
|
+
private readonly prisma: PrismaDatabaseClient,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
async findOrCreate(args: Readonly<{ workflowId: string; snapshotJson: string }>): Promise<string> {
|
|
21
|
+
const snapshotHash = createHash("sha256").update(args.snapshotJson, "utf8").digest("hex");
|
|
22
|
+
const existing = await this.prisma.workflowSnapshot.findUnique({
|
|
23
|
+
where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
|
|
24
|
+
select: { id: true },
|
|
25
|
+
});
|
|
26
|
+
if (existing) {
|
|
27
|
+
return existing.id;
|
|
28
|
+
}
|
|
29
|
+
const id = crypto.randomUUID();
|
|
30
|
+
await this.prisma.workflowSnapshot.upsert({
|
|
31
|
+
where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
|
|
32
|
+
create: {
|
|
33
|
+
id,
|
|
34
|
+
workflowId: args.workflowId,
|
|
35
|
+
snapshotHash,
|
|
36
|
+
snapshotJson: args.snapshotJson,
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
},
|
|
39
|
+
update: {},
|
|
40
|
+
});
|
|
41
|
+
// Re-fetch so the returned id is the winner under concurrent inserts
|
|
42
|
+
const row = await this.prisma.workflowSnapshot.findUniqueOrThrow({
|
|
43
|
+
where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
|
|
44
|
+
select: { id: true },
|
|
45
|
+
});
|
|
46
|
+
return row.id;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import type { ToolSet } from "ai";
|
|
2
|
+
import {
|
|
3
|
+
AgentBindError,
|
|
4
|
+
CodemationTelemetryAttributeNames,
|
|
5
|
+
ConnectionInvocationIdFactory,
|
|
6
|
+
ConnectionNodeIdFactory,
|
|
7
|
+
inject,
|
|
8
|
+
injectable,
|
|
9
|
+
type AgentMcpIntegration,
|
|
10
|
+
type AgentMcpToolMap,
|
|
11
|
+
type ConnectionInvocationAppendArgs,
|
|
12
|
+
type JsonValue,
|
|
13
|
+
type McpServerDeclaration,
|
|
14
|
+
type NeedsReconsentEvent,
|
|
15
|
+
type NodeActivationId,
|
|
16
|
+
type NodeIterationId,
|
|
17
|
+
type ConnectionInvocationId,
|
|
18
|
+
type TelemetrySpanEventRecord,
|
|
19
|
+
} from "@codemation/core";
|
|
20
|
+
import { ApplicationTokens } from "../applicationTokens";
|
|
21
|
+
import type { LoggerFactory } from "../application/logging/Logger";
|
|
22
|
+
import { McpServerCatalog } from "./McpServerCatalog";
|
|
23
|
+
import { McpConnectionPool } from "./McpConnectionPool";
|
|
24
|
+
import type { CredentialStore } from "../domain/credentials/CredentialServices";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Host-side implementation of AgentMcpIntegration.
|
|
28
|
+
*
|
|
29
|
+
* Resolves the credential binding for each declared MCP server via the standard
|
|
30
|
+
* credential-binding table — the binding lives on the MCP connection node itself
|
|
31
|
+
* (slot key `"credential"`), matching ChatModel/Tool connection nodes. Opens pool
|
|
32
|
+
* connections and returns a ToolSet map with execute callbacks wrapped for
|
|
33
|
+
* telemetry + 403 detection.
|
|
34
|
+
*/
|
|
35
|
+
@injectable()
|
|
36
|
+
export class AgentMcpIntegrationImpl implements AgentMcpIntegration {
|
|
37
|
+
constructor(
|
|
38
|
+
@inject(McpServerCatalog) private readonly catalog: McpServerCatalog,
|
|
39
|
+
@inject(McpConnectionPool) private readonly pool: McpConnectionPool,
|
|
40
|
+
@inject(ApplicationTokens.CredentialStore) private readonly credentialStore: CredentialStore,
|
|
41
|
+
@inject(ApplicationTokens.LoggerFactory) private readonly loggers: LoggerFactory,
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
async prepareMcpTools(args: Parameters<AgentMcpIntegration["prepareMcpTools"]>[0]): Promise<AgentMcpToolMap> {
|
|
45
|
+
const {
|
|
46
|
+
workflowId,
|
|
47
|
+
agentNodeId,
|
|
48
|
+
serverIds,
|
|
49
|
+
pinnedMcpTools: _pinnedMcpTools,
|
|
50
|
+
emitSpanEvent,
|
|
51
|
+
startChildSpan,
|
|
52
|
+
appendMcpInvocation,
|
|
53
|
+
parentAgentActivationId,
|
|
54
|
+
iterationId,
|
|
55
|
+
itemIndex,
|
|
56
|
+
parentInvocationId,
|
|
57
|
+
} = args;
|
|
58
|
+
|
|
59
|
+
const result = new Map<string, Readonly<Record<string, unknown>>>();
|
|
60
|
+
const logger = this.loggers.create("AgentMcpIntegrationImpl");
|
|
61
|
+
|
|
62
|
+
for (const serverId of serverIds) {
|
|
63
|
+
const decl = this.catalog.get(serverId);
|
|
64
|
+
if (!decl) {
|
|
65
|
+
throw new AgentBindError(`MCP server "${serverId}" not found in catalog`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const credentialInstanceId = await this.resolveCredentialInstanceId(workflowId, agentNodeId, serverId);
|
|
69
|
+
|
|
70
|
+
// Validate scopes before opening the connection.
|
|
71
|
+
await this.validateScopes(decl, credentialInstanceId);
|
|
72
|
+
|
|
73
|
+
// Lazy-open via pool (single-flight, cached after first open).
|
|
74
|
+
await this.pool.getClient(credentialInstanceId, serverId);
|
|
75
|
+
|
|
76
|
+
// Fetch tool list from pool (cached after first fetch).
|
|
77
|
+
const rawTools = await this.pool.getTools(credentialInstanceId, serverId);
|
|
78
|
+
|
|
79
|
+
// Wrap each tool's execute for telemetry and 403 detection.
|
|
80
|
+
const wrappedTools = this.wrapToolExecutes({
|
|
81
|
+
tools: rawTools as ToolSet,
|
|
82
|
+
serverId,
|
|
83
|
+
credentialInstanceId,
|
|
84
|
+
agentNodeId,
|
|
85
|
+
emitSpanEvent,
|
|
86
|
+
startChildSpan,
|
|
87
|
+
logger,
|
|
88
|
+
appendMcpInvocation,
|
|
89
|
+
parentAgentActivationId,
|
|
90
|
+
iterationId,
|
|
91
|
+
itemIndex,
|
|
92
|
+
parentInvocationId,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
result.set(serverId, wrappedTools as unknown as Readonly<Record<string, unknown>>);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Looks up the credential binding for the MCP connection node and verifies the
|
|
103
|
+
* referenced credential instance still exists.
|
|
104
|
+
*/
|
|
105
|
+
private async resolveCredentialInstanceId(workflowId: string, agentNodeId: string, serverId: string): Promise<string> {
|
|
106
|
+
const mcpNodeId = ConnectionNodeIdFactory.mcpConnectionNodeId(agentNodeId, serverId);
|
|
107
|
+
const binding = await this.credentialStore.getBinding({ workflowId, nodeId: mcpNodeId, slotKey: "credential" });
|
|
108
|
+
if (!binding) {
|
|
109
|
+
throw new AgentBindError(
|
|
110
|
+
`MCP server "${serverId}" has no credential bound on connection node "${mcpNodeId}". ` +
|
|
111
|
+
`Bind a credential instance via the canvas credential dropdown before activation.`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const instance = await this.credentialStore.getInstance(binding.instanceId);
|
|
115
|
+
if (!instance) {
|
|
116
|
+
throw new AgentBindError(
|
|
117
|
+
`Credential instance "${binding.instanceId}" not found for mcpServer "${serverId}" (connection node "${mcpNodeId}")`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return instance.instanceId;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validates that the credential instance's granted scopes cover the server's requiredScopes.
|
|
125
|
+
* Scopes are read from the OAuth2 material record (populated by the broker push endpoint).
|
|
126
|
+
*/
|
|
127
|
+
private async validateScopes(decl: McpServerDeclaration, credentialInstanceId: string): Promise<void> {
|
|
128
|
+
if (!decl.requiredScopes?.length) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const material = await this.credentialStore.getOAuth2Material(credentialInstanceId);
|
|
133
|
+
const grantedScopes = new Set(material?.scopes ?? []);
|
|
134
|
+
const missing = decl.requiredScopes.filter((s) => !grantedScopes.has(s));
|
|
135
|
+
|
|
136
|
+
if (missing.length > 0) {
|
|
137
|
+
throw new AgentBindError(
|
|
138
|
+
`Credential instance "${credentialInstanceId}" lacks required scopes for server "${decl.id}": ${missing.join(", ")}. ` +
|
|
139
|
+
`Reconnect the credential to grant the missing scopes.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Returns a new ToolSet where each tool's execute callback is replaced with a wrapped version
|
|
146
|
+
* that:
|
|
147
|
+
* - Opens a child telemetry span tagged with mcp.server_id and mcp.tool_name.
|
|
148
|
+
* - Calls the original tool's execute (from @ai-sdk/mcp), which internally calls the MCP server.
|
|
149
|
+
* - On 403 / permission errors: emits a NeedsReconsentEvent span event, closes the span with
|
|
150
|
+
* error status, and re-throws a descriptive error. The agent turn continues for other tools.
|
|
151
|
+
*/
|
|
152
|
+
private wrapToolExecutes(args: {
|
|
153
|
+
tools: ToolSet;
|
|
154
|
+
serverId: string;
|
|
155
|
+
credentialInstanceId: string;
|
|
156
|
+
agentNodeId: string;
|
|
157
|
+
emitSpanEvent: (event: TelemetrySpanEventRecord) => void;
|
|
158
|
+
startChildSpan: (args: { name: string; attributes?: Record<string, string> }) => {
|
|
159
|
+
end: (args?: { status?: "ok" | "error"; statusMessage?: string }) => void;
|
|
160
|
+
};
|
|
161
|
+
logger: ReturnType<LoggerFactory["create"]>;
|
|
162
|
+
appendMcpInvocation?: (args: ConnectionInvocationAppendArgs) => Promise<void>;
|
|
163
|
+
parentAgentActivationId?: NodeActivationId;
|
|
164
|
+
iterationId?: NodeIterationId;
|
|
165
|
+
itemIndex?: number;
|
|
166
|
+
parentInvocationId?: ConnectionInvocationId;
|
|
167
|
+
}): ToolSet {
|
|
168
|
+
const {
|
|
169
|
+
tools,
|
|
170
|
+
serverId,
|
|
171
|
+
credentialInstanceId,
|
|
172
|
+
agentNodeId,
|
|
173
|
+
emitSpanEvent,
|
|
174
|
+
startChildSpan,
|
|
175
|
+
logger,
|
|
176
|
+
appendMcpInvocation,
|
|
177
|
+
parentAgentActivationId,
|
|
178
|
+
iterationId,
|
|
179
|
+
itemIndex,
|
|
180
|
+
parentInvocationId,
|
|
181
|
+
} = args;
|
|
182
|
+
const wrapped: Record<string, ToolSet[string]> = {};
|
|
183
|
+
const checkPermissionError = (err: unknown): boolean => this.isPermissionError(err);
|
|
184
|
+
const connectionNodeId = ConnectionNodeIdFactory.mcpConnectionNodeId(agentNodeId, serverId);
|
|
185
|
+
|
|
186
|
+
for (const [toolName, toolDef] of Object.entries(tools)) {
|
|
187
|
+
const originalExecute = (toolDef as { execute?: (input: unknown) => Promise<unknown> }).execute;
|
|
188
|
+
const wrappedDef = {
|
|
189
|
+
...toolDef,
|
|
190
|
+
execute: async (input: unknown): Promise<unknown> => {
|
|
191
|
+
const span = startChildSpan({
|
|
192
|
+
name: "mcp.tool_call",
|
|
193
|
+
attributes: {
|
|
194
|
+
[CodemationTelemetryAttributeNames.mcpServerId]: serverId,
|
|
195
|
+
[CodemationTelemetryAttributeNames.mcpToolName]: toolName,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
const invocationId = ConnectionInvocationIdFactory.create();
|
|
199
|
+
const startedAtIso = new Date().toISOString();
|
|
200
|
+
const baseRecord = {
|
|
201
|
+
invocationId,
|
|
202
|
+
connectionNodeId,
|
|
203
|
+
parentAgentNodeId: agentNodeId,
|
|
204
|
+
parentAgentActivationId: parentAgentActivationId ?? agentNodeId,
|
|
205
|
+
iterationId,
|
|
206
|
+
itemIndex,
|
|
207
|
+
parentInvocationId,
|
|
208
|
+
subjectName: toolName,
|
|
209
|
+
};
|
|
210
|
+
const summarizedInput = this.summarizeForInvocation(input);
|
|
211
|
+
if (appendMcpInvocation) {
|
|
212
|
+
await appendMcpInvocation({
|
|
213
|
+
...baseRecord,
|
|
214
|
+
status: "running",
|
|
215
|
+
managedInput: summarizedInput,
|
|
216
|
+
queuedAt: startedAtIso,
|
|
217
|
+
startedAt: startedAtIso,
|
|
218
|
+
statusLabel: `calling ${toolName}`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
if (!originalExecute) {
|
|
223
|
+
throw new Error(`MCP tool "${toolName}" on server "${serverId}" has no execute callback`);
|
|
224
|
+
}
|
|
225
|
+
const result = await originalExecute(input);
|
|
226
|
+
span.end({ status: "ok" });
|
|
227
|
+
if (appendMcpInvocation) {
|
|
228
|
+
const finishedAtIso = new Date().toISOString();
|
|
229
|
+
await appendMcpInvocation({
|
|
230
|
+
...baseRecord,
|
|
231
|
+
status: "completed",
|
|
232
|
+
managedInput: summarizedInput,
|
|
233
|
+
managedOutput: this.summarizeForInvocation(result),
|
|
234
|
+
queuedAt: startedAtIso,
|
|
235
|
+
startedAt: startedAtIso,
|
|
236
|
+
finishedAt: finishedAtIso,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (checkPermissionError(error)) {
|
|
242
|
+
const event: NeedsReconsentEvent = {
|
|
243
|
+
serverId,
|
|
244
|
+
credentialInstanceId,
|
|
245
|
+
};
|
|
246
|
+
const spanEvent: TelemetrySpanEventRecord = {
|
|
247
|
+
name: "mcp.needs_reconsent",
|
|
248
|
+
attributes: {
|
|
249
|
+
"mcp.server_id": serverId,
|
|
250
|
+
"mcp.credential_instance_id": credentialInstanceId,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
emitSpanEvent(spanEvent);
|
|
254
|
+
span.end({ status: "error", statusMessage: "MCP tool permission error" });
|
|
255
|
+
logger.warn(
|
|
256
|
+
`AgentMcpIntegrationImpl: permission error from MCP tool "${toolName}" on server "${serverId}". ` +
|
|
257
|
+
`NeedsReconsentEvent emitted for credential instance "${credentialInstanceId}".`,
|
|
258
|
+
error instanceof Error ? error : undefined,
|
|
259
|
+
);
|
|
260
|
+
const wrapped = new Error(
|
|
261
|
+
`MCP tool "${toolName}" on server "${serverId}" returned a permission error. ` +
|
|
262
|
+
`Reconnect the credential "${credentialInstanceId}" via the Connect flow. ` +
|
|
263
|
+
`needsReconsent: ${JSON.stringify(event satisfies NeedsReconsentEvent)}`,
|
|
264
|
+
{ cause: error },
|
|
265
|
+
);
|
|
266
|
+
if (appendMcpInvocation) {
|
|
267
|
+
await appendMcpInvocation({
|
|
268
|
+
...baseRecord,
|
|
269
|
+
status: "failed",
|
|
270
|
+
managedInput: summarizedInput,
|
|
271
|
+
error: { message: wrapped.message, name: wrapped.name },
|
|
272
|
+
queuedAt: startedAtIso,
|
|
273
|
+
startedAt: startedAtIso,
|
|
274
|
+
finishedAt: new Date().toISOString(),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
// The event carries the structured data; the agent turn continues for other tools.
|
|
278
|
+
throw wrapped;
|
|
279
|
+
}
|
|
280
|
+
const effectiveMessage = error instanceof Error ? error.message : String(error);
|
|
281
|
+
span.end({
|
|
282
|
+
status: "error",
|
|
283
|
+
statusMessage: effectiveMessage,
|
|
284
|
+
});
|
|
285
|
+
if (appendMcpInvocation) {
|
|
286
|
+
await appendMcpInvocation({
|
|
287
|
+
...baseRecord,
|
|
288
|
+
status: "failed",
|
|
289
|
+
managedInput: summarizedInput,
|
|
290
|
+
error: { message: effectiveMessage, name: error instanceof Error ? error.name : undefined },
|
|
291
|
+
queuedAt: startedAtIso,
|
|
292
|
+
startedAt: startedAtIso,
|
|
293
|
+
finishedAt: new Date().toISOString(),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
wrapped[toolName] = wrappedDef as unknown as ToolSet[string];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return wrapped as ToolSet;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private summarizeForInvocation(value: unknown): JsonValue | undefined {
|
|
307
|
+
if (value === undefined) return undefined;
|
|
308
|
+
try {
|
|
309
|
+
const serialized = JSON.stringify(value);
|
|
310
|
+
if (serialized.length > 1024) {
|
|
311
|
+
return { truncated: true, preview: serialized.slice(0, 1024) };
|
|
312
|
+
}
|
|
313
|
+
return JSON.parse(serialized) as JsonValue;
|
|
314
|
+
} catch {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Detects 403 / MCP-level permission / scope-insufficiency errors from callTool.
|
|
321
|
+
* The exact shape depends on how @ai-sdk/mcp surfaces them — we check for HTTP status
|
|
322
|
+
* 403, MCP error codes (insufficient_scope / UNAUTHORIZED), and common message patterns.
|
|
323
|
+
*/
|
|
324
|
+
private isPermissionError(error: unknown): boolean {
|
|
325
|
+
if (!(error instanceof Error)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
const msg = error.message.toLowerCase();
|
|
329
|
+
// HTTP 403 from the MCP transport
|
|
330
|
+
if (msg.includes("403") || msg.includes("forbidden")) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
// MCP-level error codes
|
|
334
|
+
if (msg.includes("insufficient_scope") || msg.includes("unauthorized") || msg.includes("unauthenticated")) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
// Check error name or code
|
|
338
|
+
const candidate = error as Error & { statusCode?: number; code?: string };
|
|
339
|
+
if (candidate.statusCode === 403 || candidate.code === "EUNAUTHORIZED") {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { injectable } from "@codemation/core";
|
|
2
|
+
import { experimental_createMCPClient } from "@ai-sdk/mcp";
|
|
3
|
+
import type { MCPClient } from "@ai-sdk/mcp";
|
|
4
|
+
|
|
5
|
+
export type McpClientOpenArgs = Readonly<{
|
|
6
|
+
url: string;
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export interface McpClientFactory {
|
|
11
|
+
open(args: McpClientOpenArgs): Promise<MCPClient>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default implementation — delegates to @ai-sdk/mcp's experimental_createMCPClient
|
|
16
|
+
* using the streamable HTTP transport.
|
|
17
|
+
*/
|
|
18
|
+
@injectable()
|
|
19
|
+
export class DefaultMcpClientFactory implements McpClientFactory {
|
|
20
|
+
async open(args: McpClientOpenArgs): Promise<MCPClient> {
|
|
21
|
+
return experimental_createMCPClient({
|
|
22
|
+
transport: {
|
|
23
|
+
type: "http",
|
|
24
|
+
url: args.url,
|
|
25
|
+
headers: args.headers,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|