@miosa/sdk 1.2.3 → 1.2.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miosa/sdk",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "TypeScript SDK for the MIOSA API — cloud VM desktop infrastructure for AI agents",
5
5
  "license": "MIT",
6
6
  "author": "MIOSA <hello@miosa.ai>",
@@ -72,6 +72,12 @@ describe("Deployments", () => {
72
72
  metadata: {
73
73
  deployment_product: "docker_deploy",
74
74
  runtime: { ip: "172.16.74.246", port: 23906 },
75
+ docker_deploy: {
76
+ app_id: "miosa-clinic-intake",
77
+ container_id: "container_123",
78
+ status: "running",
79
+ url: "http://127.0.0.1:23906",
80
+ },
75
81
  },
76
82
  },
77
83
  };
@@ -114,8 +120,75 @@ describe("Deployments", () => {
114
120
  "deployment_product",
115
121
  "docker_deploy_host_id",
116
122
  "docker_deploy_host_health",
123
+ "docker_deploy_app",
117
124
  "runtime_route",
118
125
  "public_url_probe",
119
126
  ]);
120
127
  });
128
+
129
+ it("doctorDockerDeploy fails when route metadata does not point at the Docker container port", async () => {
130
+ mockGet.mockImplementation(async (path: string) => {
131
+ if (path === "/deployments/dep_123") {
132
+ return {
133
+ data: {
134
+ id: "dep_123",
135
+ tenant_id: "ten_123",
136
+ name: "Clinic Intake",
137
+ slug: "clinic-intake",
138
+ state: "running",
139
+ deployment_product: "docker_deploy",
140
+ docker_deploy_host_id: "ddh_123",
141
+ public_url: "https://clinic-intake.osa.miosa.app",
142
+ metadata: {
143
+ deployment_product: "docker_deploy",
144
+ runtime: { ip: "172.16.74.246", port: 8080 },
145
+ docker_deploy: {
146
+ app_id: "miosa-clinic-intake",
147
+ container_id: "container_123",
148
+ status: "running",
149
+ url: "http://127.0.0.1:23906",
150
+ },
151
+ },
152
+ },
153
+ };
154
+ }
155
+
156
+ if (path === "/docker-deploy/hosts/ddh_123") {
157
+ return {
158
+ data: {
159
+ id: "ddh_123",
160
+ tenant_id: "ten_123",
161
+ workspace_id: "ws_123",
162
+ status: "active",
163
+ size: "medium",
164
+ region: "us",
165
+ appliance_status: "healthy",
166
+ },
167
+ };
168
+ }
169
+
170
+ throw new Error(`unexpected path ${path}`);
171
+ });
172
+ vi.stubGlobal(
173
+ "fetch",
174
+ vi.fn().mockResolvedValue({ ok: true, status: 200 }),
175
+ );
176
+
177
+ const result = await new Deployments(makeHttp()).doctorDockerDeploy(
178
+ "dep_123",
179
+ { probePath: "/health" },
180
+ );
181
+
182
+ expect(result.ok).toBe(false);
183
+ expect(result.checks).toEqual(
184
+ expect.arrayContaining([
185
+ expect.objectContaining({
186
+ name: "runtime_route",
187
+ ok: false,
188
+ message:
189
+ "Deployment route port 8080 does not match Docker container host port 23906.",
190
+ }),
191
+ ]),
192
+ );
193
+ });
121
194
  });
@@ -481,6 +481,25 @@ function dockerDeployHostId(deployment: DeploymentData): string | null {
481
481
  );
482
482
  }
483
483
 
484
+ function dockerDeployApp(deployment: DeploymentData): Record<string, unknown> | null {
485
+ const app = deployment.metadata?.["docker_deploy"];
486
+ if (!app || typeof app !== "object" || Array.isArray(app)) return null;
487
+ return app as Record<string, unknown>;
488
+ }
489
+
490
+ function dockerDeployAppPort(app: Record<string, unknown> | null): number | null {
491
+ const url = app?.["url"];
492
+ if (typeof url !== "string") return null;
493
+
494
+ try {
495
+ const parsed = new URL(url);
496
+ const port = Number.parseInt(parsed.port, 10);
497
+ return Number.isInteger(port) ? port : null;
498
+ } catch {
499
+ return null;
500
+ }
501
+ }
502
+
484
503
  function addDoctorCheck(
485
504
  checks: DockerDeployDoctorCheck[],
486
505
  name: string,
@@ -787,21 +806,55 @@ export class Deployments {
787
806
  }
788
807
  }
789
808
 
809
+ const app = dockerDeployApp(deployment);
810
+ const appPort = dockerDeployAppPort(app);
811
+ const appRunning = app?.["status"] === "running" && appPort !== null;
812
+ addDoctorCheck(
813
+ checks,
814
+ "docker_deploy_app",
815
+ appRunning,
816
+ appRunning
817
+ ? "Docker Deploy app metadata points at a running container."
818
+ : "Deployment is missing running Docker Deploy app metadata.",
819
+ app
820
+ ? {
821
+ app_id: app["app_id"],
822
+ container_id: app["container_id"],
823
+ status: app["status"],
824
+ url: app["url"],
825
+ expected_port: appPort,
826
+ }
827
+ : undefined,
828
+ );
829
+
790
830
  const runtime = metadata["runtime"];
791
831
  const hasRuntimeRoute =
792
832
  typeof runtime === "object" &&
793
833
  runtime !== null &&
794
834
  typeof (runtime as Record<string, unknown>)["ip"] === "string" &&
795
835
  typeof (runtime as Record<string, unknown>)["port"] === "number";
836
+ const runtimeRecord =
837
+ hasRuntimeRoute && typeof runtime === "object"
838
+ ? (runtime as Record<string, unknown>)
839
+ : undefined;
840
+ const routeMatchesContainerPort =
841
+ hasRuntimeRoute &&
842
+ (appPort === null || runtimeRecord?.["port"] === appPort);
796
843
  addDoctorCheck(
797
844
  checks,
798
845
  "runtime_route",
799
- hasRuntimeRoute,
800
- hasRuntimeRoute
801
- ? "Deployment has appliance runtime route metadata."
846
+ hasRuntimeRoute && routeMatchesContainerPort,
847
+ hasRuntimeRoute && routeMatchesContainerPort
848
+ ? "Deployment route points at the Docker container host port."
849
+ : hasRuntimeRoute && appPort !== null
850
+ ? `Deployment route port ${String(runtimeRecord?.["port"])} does not match Docker container host port ${appPort}.`
802
851
  : "Deployment is missing appliance runtime route metadata.",
803
- typeof runtime === "object" && runtime !== null
804
- ? (runtime as Record<string, unknown>)
852
+ runtimeRecord
853
+ ? {
854
+ ...runtimeRecord,
855
+ expected_port: appPort,
856
+ docker_deploy_url: app?.["url"],
857
+ }
805
858
  : undefined,
806
859
  );
807
860