Skip to content

Commit fa85f19

Browse files
feat(api): new process, fs, and log endpoints
New endpoints for executing processes on browser instances, uploading / downloading whole directories as zip files, and streaming any log file on a browser instance
1 parent 7619f69 commit fa85f19

8 files changed

Lines changed: 918 additions & 4 deletions

File tree

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 31
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-b55c3e0424fa7733487139488b9fff00ad8949ff02ee3160ee36b9334e84b134.yml
3-
openapi_spec_hash: 17f36677e3dc0a3aeb419654c8d5cae3
4-
config_hash: f67e4b33b2fb30c1405ee2fff8096320
1+
configured_endpoints: 41
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-a7c1df5070fe59642d7a1f168aa902a468227752bfc930cbf38930f7c205dbb6.yml
3+
openapi_spec_hash: eab65e39aef4f0a0952b82adeecf6b5b
4+
config_hash: 5de78bc29ac060562575cb54bb26826c

api.md

Lines changed: 29 additions & 0 deletions
Large diffs are not rendered by default.

browser.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type BrowserService struct {
2929
Options []option.RequestOption
3030
Replays BrowserReplayService
3131
Fs BrowserFService
32+
Process BrowserProcessService
33+
Logs BrowserLogService
3234
}
3335

3436
// NewBrowserService generates a new service that applies the given options to each
@@ -39,6 +41,8 @@ func NewBrowserService(opts ...option.RequestOption) (r BrowserService) {
3941
r.Options = opts
4042
r.Replays = NewBrowserReplayService(opts...)
4143
r.Fs = NewBrowserFService(opts...)
44+
r.Process = NewBrowserProcessService(opts...)
45+
r.Logs = NewBrowserLogService(opts...)
4246
return
4347
}
4448

browserf.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ func (r *BrowserFService) DeleteFile(ctx context.Context, id string, body Browse
8282
return
8383
}
8484

85+
// Returns a ZIP file containing the contents of the specified directory.
86+
func (r *BrowserFService) DownloadDirZip(ctx context.Context, id string, query BrowserFDownloadDirZipParams, opts ...option.RequestOption) (res *http.Response, err error) {
87+
opts = append(r.Options[:], opts...)
88+
opts = append([]option.RequestOption{option.WithHeader("Accept", "application/zip")}, opts...)
89+
if id == "" {
90+
err = errors.New("missing required id parameter")
91+
return
92+
}
93+
path := fmt.Sprintf("browsers/%s/fs/download_dir_zip", id)
94+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
95+
return
96+
}
97+
8598
// Get information about a file or directory
8699
func (r *BrowserFService) FileInfo(ctx context.Context, id string, query BrowserFFileInfoParams, opts ...option.RequestOption) (res *BrowserFFileInfoResponse, err error) {
87100
opts = append(r.Options[:], opts...)
@@ -145,6 +158,32 @@ func (r *BrowserFService) SetFilePermissions(ctx context.Context, id string, bod
145158
return
146159
}
147160

161+
// Allows uploading single or multiple files to the remote filesystem.
162+
func (r *BrowserFService) Upload(ctx context.Context, id string, body BrowserFUploadParams, opts ...option.RequestOption) (err error) {
163+
opts = append(r.Options[:], opts...)
164+
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
165+
if id == "" {
166+
err = errors.New("missing required id parameter")
167+
return
168+
}
169+
path := fmt.Sprintf("browsers/%s/fs/upload", id)
170+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
171+
return
172+
}
173+
174+
// Upload a zip file and extract its contents to the specified destination path.
175+
func (r *BrowserFService) UploadZip(ctx context.Context, id string, body BrowserFUploadZipParams, opts ...option.RequestOption) (err error) {
176+
opts = append(r.Options[:], opts...)
177+
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
178+
if id == "" {
179+
err = errors.New("missing required id parameter")
180+
return
181+
}
182+
path := fmt.Sprintf("browsers/%s/fs/upload_zip", id)
183+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
184+
return
185+
}
186+
148187
// Write or create a file
149188
func (r *BrowserFService) WriteFile(ctx context.Context, id string, contents io.Reader, body BrowserFWriteFileParams, opts ...option.RequestOption) (err error) {
150189
opts = append(r.Options[:], opts...)
@@ -266,6 +305,21 @@ func (r *BrowserFDeleteFileParams) UnmarshalJSON(data []byte) error {
266305
return apijson.UnmarshalRoot(data, r)
267306
}
268307

308+
type BrowserFDownloadDirZipParams struct {
309+
// Absolute directory path to archive and download.
310+
Path string `query:"path,required" json:"-"`
311+
paramObj
312+
}
313+
314+
// URLQuery serializes [BrowserFDownloadDirZipParams]'s query parameters as
315+
// `url.Values`.
316+
func (r BrowserFDownloadDirZipParams) URLQuery() (v url.Values, err error) {
317+
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
318+
ArrayFormat: apiquery.ArrayQueryFormatComma,
319+
NestedFormat: apiquery.NestedQueryFormatBrackets,
320+
})
321+
}
322+
269323
type BrowserFFileInfoParams struct {
270324
// Absolute path of the file or directory.
271325
Path string `query:"path,required" json:"-"`
@@ -345,6 +399,70 @@ func (r *BrowserFSetFilePermissionsParams) UnmarshalJSON(data []byte) error {
345399
return apijson.UnmarshalRoot(data, r)
346400
}
347401

402+
type BrowserFUploadParams struct {
403+
Files []BrowserFUploadParamsFile `json:"files,omitzero,required"`
404+
paramObj
405+
}
406+
407+
func (r BrowserFUploadParams) MarshalMultipart() (data []byte, contentType string, err error) {
408+
buf := bytes.NewBuffer(nil)
409+
writer := multipart.NewWriter(buf)
410+
err = apiform.MarshalRoot(r, writer)
411+
if err == nil {
412+
err = apiform.WriteExtras(writer, r.ExtraFields())
413+
}
414+
if err != nil {
415+
writer.Close()
416+
return nil, "", err
417+
}
418+
err = writer.Close()
419+
if err != nil {
420+
return nil, "", err
421+
}
422+
return buf.Bytes(), writer.FormDataContentType(), nil
423+
}
424+
425+
// The properties DestPath, File are required.
426+
type BrowserFUploadParamsFile struct {
427+
// Absolute destination path to write the file.
428+
DestPath string `json:"dest_path,required"`
429+
File io.Reader `json:"file,omitzero,required" format:"binary"`
430+
paramObj
431+
}
432+
433+
func (r BrowserFUploadParamsFile) MarshalJSON() (data []byte, err error) {
434+
type shadow BrowserFUploadParamsFile
435+
return param.MarshalObject(r, (*shadow)(&r))
436+
}
437+
func (r *BrowserFUploadParamsFile) UnmarshalJSON(data []byte) error {
438+
return apijson.UnmarshalRoot(data, r)
439+
}
440+
441+
type BrowserFUploadZipParams struct {
442+
// Absolute destination directory to extract the archive to.
443+
DestPath string `json:"dest_path,required"`
444+
ZipFile io.Reader `json:"zip_file,omitzero,required" format:"binary"`
445+
paramObj
446+
}
447+
448+
func (r BrowserFUploadZipParams) MarshalMultipart() (data []byte, contentType string, err error) {
449+
buf := bytes.NewBuffer(nil)
450+
writer := multipart.NewWriter(buf)
451+
err = apiform.MarshalRoot(r, writer)
452+
if err == nil {
453+
err = apiform.WriteExtras(writer, r.ExtraFields())
454+
}
455+
if err != nil {
456+
writer.Close()
457+
return nil, "", err
458+
}
459+
err = writer.Close()
460+
if err != nil {
461+
return nil, "", err
462+
}
463+
return buf.Bytes(), writer.FormDataContentType(), nil
464+
}
465+
348466
type BrowserFWriteFileParams struct {
349467
// Destination absolute file path.
350468
Path string `query:"path,required" json:"-"`

browserf_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,46 @@ func TestBrowserFDeleteFile(t *testing.T) {
105105
}
106106
}
107107

108+
func TestBrowserFDownloadDirZip(t *testing.T) {
109+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
110+
w.WriteHeader(200)
111+
w.Write([]byte("abc"))
112+
}))
113+
defer server.Close()
114+
baseURL := server.URL
115+
client := kernel.NewClient(
116+
option.WithBaseURL(baseURL),
117+
option.WithAPIKey("My API Key"),
118+
)
119+
resp, err := client.Browsers.Fs.DownloadDirZip(
120+
context.TODO(),
121+
"id",
122+
kernel.BrowserFDownloadDirZipParams{
123+
Path: "/J!",
124+
},
125+
)
126+
if err != nil {
127+
var apierr *kernel.Error
128+
if errors.As(err, &apierr) {
129+
t.Log(string(apierr.DumpRequest(true)))
130+
}
131+
t.Fatalf("err should be nil: %s", err.Error())
132+
}
133+
defer resp.Body.Close()
134+
135+
b, err := io.ReadAll(resp.Body)
136+
if err != nil {
137+
var apierr *kernel.Error
138+
if errors.As(err, &apierr) {
139+
t.Log(string(apierr.DumpRequest(true)))
140+
}
141+
t.Fatalf("err should be nil: %s", err.Error())
142+
}
143+
if !bytes.Equal(b, []byte("abc")) {
144+
t.Fatalf("return value not %s: %s", "abc", b)
145+
}
146+
}
147+
108148
func TestBrowserFFileInfo(t *testing.T) {
109149
t.Skip("Prism tests are disabled")
110150
baseURL := "http://localhost:4010"
@@ -265,6 +305,68 @@ func TestBrowserFSetFilePermissionsWithOptionalParams(t *testing.T) {
265305
}
266306
}
267307

308+
func TestBrowserFUpload(t *testing.T) {
309+
t.Skip("Prism tests are disabled")
310+
baseURL := "http://localhost:4010"
311+
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
312+
baseURL = envURL
313+
}
314+
if !testutil.CheckTestServer(t, baseURL) {
315+
return
316+
}
317+
client := kernel.NewClient(
318+
option.WithBaseURL(baseURL),
319+
option.WithAPIKey("My API Key"),
320+
)
321+
err := client.Browsers.Fs.Upload(
322+
context.TODO(),
323+
"id",
324+
kernel.BrowserFUploadParams{
325+
Files: []kernel.BrowserFUploadParamsFile{{
326+
DestPath: "/J!",
327+
File: io.Reader(bytes.NewBuffer([]byte("some file contents"))),
328+
}},
329+
},
330+
)
331+
if err != nil {
332+
var apierr *kernel.Error
333+
if errors.As(err, &apierr) {
334+
t.Log(string(apierr.DumpRequest(true)))
335+
}
336+
t.Fatalf("err should be nil: %s", err.Error())
337+
}
338+
}
339+
340+
func TestBrowserFUploadZip(t *testing.T) {
341+
t.Skip("Prism tests are disabled")
342+
baseURL := "http://localhost:4010"
343+
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
344+
baseURL = envURL
345+
}
346+
if !testutil.CheckTestServer(t, baseURL) {
347+
return
348+
}
349+
client := kernel.NewClient(
350+
option.WithBaseURL(baseURL),
351+
option.WithAPIKey("My API Key"),
352+
)
353+
err := client.Browsers.Fs.UploadZip(
354+
context.TODO(),
355+
"id",
356+
kernel.BrowserFUploadZipParams{
357+
DestPath: "/J!",
358+
ZipFile: io.Reader(bytes.NewBuffer([]byte("some file contents"))),
359+
},
360+
)
361+
if err != nil {
362+
var apierr *kernel.Error
363+
if errors.As(err, &apierr) {
364+
t.Log(string(apierr.DumpRequest(true)))
365+
}
366+
t.Fatalf("err should be nil: %s", err.Error())
367+
}
368+
}
369+
268370
func TestBrowserFWriteFileWithOptionalParams(t *testing.T) {
269371
t.Skip("Prism tests are disabled")
270372
baseURL := "http://localhost:4010"

browserlog.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
package kernel
4+
5+
import (
6+
"context"
7+
"errors"
8+
"fmt"
9+
"net/http"
10+
"net/url"
11+
12+
"github.com/onkernel/kernel-go-sdk/internal/apiquery"
13+
"github.com/onkernel/kernel-go-sdk/internal/requestconfig"
14+
"github.com/onkernel/kernel-go-sdk/option"
15+
"github.com/onkernel/kernel-go-sdk/packages/param"
16+
"github.com/onkernel/kernel-go-sdk/packages/ssestream"
17+
"github.com/onkernel/kernel-go-sdk/shared"
18+
)
19+
20+
// BrowserLogService contains methods and other services that help with interacting
21+
// with the kernel API.
22+
//
23+
// Note, unlike clients, this service does not read variables from the environment
24+
// automatically. You should not instantiate this service directly, and instead use
25+
// the [NewBrowserLogService] method instead.
26+
type BrowserLogService struct {
27+
Options []option.RequestOption
28+
}
29+
30+
// NewBrowserLogService generates a new service that applies the given options to
31+
// each request. These options are applied after the parent client's options (if
32+
// there is one), and before any request-specific options.
33+
func NewBrowserLogService(opts ...option.RequestOption) (r BrowserLogService) {
34+
r = BrowserLogService{}
35+
r.Options = opts
36+
return
37+
}
38+
39+
// Stream log files on the browser instance via SSE
40+
func (r *BrowserLogService) StreamStreaming(ctx context.Context, id string, query BrowserLogStreamParams, opts ...option.RequestOption) (stream *ssestream.Stream[shared.LogEvent]) {
41+
var (
42+
raw *http.Response
43+
err error
44+
)
45+
opts = append(r.Options[:], opts...)
46+
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
47+
if id == "" {
48+
err = errors.New("missing required id parameter")
49+
return
50+
}
51+
path := fmt.Sprintf("browsers/%s/logs/stream", id)
52+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...)
53+
return ssestream.NewStream[shared.LogEvent](ssestream.NewDecoder(raw), err)
54+
}
55+
56+
type BrowserLogStreamParams struct {
57+
// Any of "path", "supervisor".
58+
Source BrowserLogStreamParamsSource `query:"source,omitzero,required" json:"-"`
59+
Follow param.Opt[bool] `query:"follow,omitzero" json:"-"`
60+
// only required if source is path
61+
Path param.Opt[string] `query:"path,omitzero" json:"-"`
62+
// only required if source is supervisor
63+
SupervisorProcess param.Opt[string] `query:"supervisor_process,omitzero" json:"-"`
64+
paramObj
65+
}
66+
67+
// URLQuery serializes [BrowserLogStreamParams]'s query parameters as `url.Values`.
68+
func (r BrowserLogStreamParams) URLQuery() (v url.Values, err error) {
69+
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
70+
ArrayFormat: apiquery.ArrayQueryFormatComma,
71+
NestedFormat: apiquery.NestedQueryFormatBrackets,
72+
})
73+
}
74+
75+
type BrowserLogStreamParamsSource string
76+
77+
const (
78+
BrowserLogStreamParamsSourcePath BrowserLogStreamParamsSource = "path"
79+
BrowserLogStreamParamsSourceSupervisor BrowserLogStreamParamsSource = "supervisor"
80+
)

0 commit comments

Comments
 (0)