8
8
"fmt"
9
9
"io"
10
10
"runtime/debug"
11
+ "strconv"
11
12
"strings"
12
13
13
14
"github.com/google/uuid"
@@ -17,6 +18,7 @@ import (
17
18
18
19
"github.com/coder/coder/v2/buildinfo"
19
20
"github.com/coder/coder/v2/cli/cliui"
21
+ "github.com/coder/coder/v2/coderd/workspaceapps/appurl"
20
22
"github.com/coder/coder/v2/codersdk"
21
23
"github.com/coder/coder/v2/codersdk/workspacesdk"
22
24
)
@@ -47,6 +49,7 @@ const (
47
49
ToolNameWorkspaceWriteFile = "coder_workspace_write_file"
48
50
ToolNameWorkspaceEditFile = "coder_workspace_edit_file"
49
51
ToolNameWorkspaceEditFiles = "coder_workspace_edit_files"
52
+ ToolNameWorkspacePortForward = "coder_workspace_port_forward"
50
53
)
51
54
52
55
func NewDeps (client * codersdk.Client ,opts ... func (* Deps )) (Deps ,error ) {
@@ -219,6 +222,7 @@ var All = []GenericTool{
219
222
WorkspaceWriteFile .Generic (),
220
223
WorkspaceEditFile .Generic (),
221
224
WorkspaceEditFiles .Generic (),
225
+ WorkspacePortForward .Generic (),
222
226
}
223
227
224
228
type ReportTaskArgs struct {
@@ -1389,6 +1393,8 @@ type WorkspaceLSResponse struct {
1389
1393
Contents []WorkspaceLSFile `json:"contents"`
1390
1394
}
1391
1395
1396
+ const workspaceDescription = "The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used."
1397
+
1392
1398
var WorkspaceLS = Tool [WorkspaceLSArgs ,WorkspaceLSResponse ]{
1393
1399
Tool : aisdk.Tool {
1394
1400
Name :ToolNameWorkspaceLS ,
@@ -1397,7 +1403,7 @@ var WorkspaceLS = Tool[WorkspaceLSArgs, WorkspaceLSResponse]{
1397
1403
Properties :map [string ]any {
1398
1404
"workspace" :map [string ]any {
1399
1405
"type" :"string" ,
1400
- "description" :"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used." ,
1406
+ "description" :workspaceDescription ,
1401
1407
},
1402
1408
"path" :map [string ]any {
1403
1409
"type" :"string" ,
@@ -1454,7 +1460,7 @@ var WorkspaceReadFile = Tool[WorkspaceReadFileArgs, WorkspaceReadFileResponse]{
1454
1460
Properties :map [string ]any {
1455
1461
"workspace" :map [string ]any {
1456
1462
"type" :"string" ,
1457
- "description" :"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used." ,
1463
+ "description" :workspaceDescription ,
1458
1464
},
1459
1465
"path" :map [string ]any {
1460
1466
"type" :"string" ,
@@ -1519,7 +1525,7 @@ var WorkspaceWriteFile = Tool[WorkspaceWriteFileArgs, codersdk.Response]{
1519
1525
Properties :map [string ]any {
1520
1526
"workspace" :map [string ]any {
1521
1527
"type" :"string" ,
1522
- "description" :"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used." ,
1528
+ "description" :workspaceDescription ,
1523
1529
},
1524
1530
"path" :map [string ]any {
1525
1531
"type" :"string" ,
@@ -1567,7 +1573,7 @@ var WorkspaceEditFile = Tool[WorkspaceEditFileArgs, codersdk.Response]{
1567
1573
Properties :map [string ]any {
1568
1574
"workspace" :map [string ]any {
1569
1575
"type" :"string" ,
1570
- "description" :"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used." ,
1576
+ "description" :workspaceDescription ,
1571
1577
},
1572
1578
"path" :map [string ]any {
1573
1579
"type" :"string" ,
@@ -1634,7 +1640,7 @@ var WorkspaceEditFiles = Tool[WorkspaceEditFilesArgs, codersdk.Response]{
1634
1640
Properties :map [string ]any {
1635
1641
"workspace" :map [string ]any {
1636
1642
"type" :"string" ,
1637
- "description" :"The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used." ,
1643
+ "description" :workspaceDescription ,
1638
1644
},
1639
1645
"files" :map [string ]any {
1640
1646
"type" :"array" ,
@@ -1691,6 +1697,59 @@ var WorkspaceEditFiles = Tool[WorkspaceEditFilesArgs, codersdk.Response]{
1691
1697
},
1692
1698
}
1693
1699
1700
+ type WorkspacePortForwardArgs struct {
1701
+ Workspace string `json:"workspace"`
1702
+ Port int `json:"port"`
1703
+ }
1704
+
1705
+ type WorkspacePortForwardResponse struct {
1706
+ URL string `json:"url"`
1707
+ }
1708
+
1709
+ var WorkspacePortForward = Tool [WorkspacePortForwardArgs ,WorkspacePortForwardResponse ]{
1710
+ Tool : aisdk.Tool {
1711
+ Name :ToolNameWorkspacePortForward ,
1712
+ Description :`Fetch URLs that forward to the specified port.` ,
1713
+ Schema : aisdk.Schema {
1714
+ Properties :map [string ]any {
1715
+ "workspace" :map [string ]any {
1716
+ "type" :"string" ,
1717
+ "description" :workspaceDescription ,
1718
+ },
1719
+ "port" :map [string ]any {
1720
+ "type" :"number" ,
1721
+ "description" :"The port to forward." ,
1722
+ },
1723
+ },
1724
+ Required : []string {"workspace" ,"port" },
1725
+ },
1726
+ },
1727
+ UserClientOptional :true ,
1728
+ Handler :func (ctx context.Context ,deps Deps ,args WorkspacePortForwardArgs ) (WorkspacePortForwardResponse ,error ) {
1729
+ workspaceName := NormalizeWorkspaceInput (args .Workspace )
1730
+ workspace ,workspaceAgent ,err := findWorkspaceAndAgent (ctx ,deps .coderClient ,workspaceName )
1731
+ if err != nil {
1732
+ return WorkspacePortForwardResponse {},xerrors .Errorf ("failed to find workspace: %w" ,err )
1733
+ }
1734
+ res ,err := deps .coderClient .AppHost (ctx )
1735
+ if err != nil {
1736
+ return WorkspacePortForwardResponse {},xerrors .Errorf ("failed to get app host: %w" ,err )
1737
+ }
1738
+ if res .Host == "" {
1739
+ return WorkspacePortForwardResponse {},xerrors .New ("no app host for forwarding has been configured" )
1740
+ }
1741
+ url := appurl.ApplicationURL {
1742
+ AppSlugOrPort :strconv .Itoa (args .Port ),
1743
+ AgentName :workspaceAgent .Name ,
1744
+ WorkspaceName :workspace .Name ,
1745
+ Username :workspace .OwnerName ,
1746
+ }
1747
+ return WorkspacePortForwardResponse {
1748
+ URL :deps .coderClient .URL .Scheme + "://" + strings .Replace (res .Host ,"*" ,url .String (),1 ),
1749
+ },nil
1750
+ },
1751
+ }
1752
+
1694
1753
// NormalizeWorkspaceInput converts workspace name input to standard format.
1695
1754
// Handles the following input formats:
1696
1755
// - workspace → workspace