Skip to content

Commit 6ede09c

Browse files
committed
feat: add file revision API for workspaces
Signed-off-by: Donnie Adams <[email protected]>
1 parent 79a6682 commit 6ede09c

File tree

2 files changed

+300
-5
lines changed

2 files changed

+300
-5
lines changed

workspace.go

+111-5
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ func (g *GPTScript) ReadFileInWorkspace(ctx context.Context, filePath string, op
209209
return base64.StdEncoding.DecodeString(out)
210210
}
211211

212+
type FileInfo struct {
213+
WorkspaceID string
214+
Name string
215+
Size int64
216+
ModTime time.Time
217+
MimeType string
218+
}
219+
212220
type StatFileInWorkspaceOptions struct {
213221
WorkspaceID string
214222
}
@@ -247,10 +255,108 @@ func (g *GPTScript) StatFileInWorkspace(ctx context.Context, filePath string, op
247255
return info, nil
248256
}
249257

250-
type FileInfo struct {
258+
type RevisionInfo struct {
259+
FileInfo
260+
RevisionID string
261+
}
262+
263+
type ListRevisionsForFileInWorkspaceOptions struct {
251264
WorkspaceID string
252-
Name string
253-
Size int64
254-
ModTime time.Time
255-
MimeType string
265+
}
266+
267+
func (g *GPTScript) ListRevisionsForFileInWorkspace(ctx context.Context, filePath string, opts ...ListRevisionsForFileInWorkspaceOptions) ([]RevisionInfo, error) {
268+
var opt ListRevisionsForFileInWorkspaceOptions
269+
for _, o := range opts {
270+
if o.WorkspaceID != "" {
271+
opt.WorkspaceID = o.WorkspaceID
272+
}
273+
}
274+
275+
if opt.WorkspaceID == "" {
276+
opt.WorkspaceID = os.Getenv("GPTSCRIPT_WORKSPACE_ID")
277+
}
278+
279+
out, err := g.runBasicCommand(ctx, "workspaces/list-revisions", map[string]any{
280+
"id": opt.WorkspaceID,
281+
"filePath": filePath,
282+
"workspaceTool": g.globalOpts.WorkspaceTool,
283+
"env": g.globalOpts.Env,
284+
})
285+
if err != nil {
286+
if strings.HasSuffix(err.Error(), fmt.Sprintf("not found: %s/%s", opt.WorkspaceID, filePath)) {
287+
return nil, newNotFoundInWorkspaceError(opt.WorkspaceID, filePath)
288+
}
289+
return nil, err
290+
}
291+
292+
var info []RevisionInfo
293+
err = json.Unmarshal([]byte(out), &info)
294+
if err != nil {
295+
return nil, err
296+
}
297+
298+
return info, nil
299+
}
300+
301+
type GetRevisionForFileInWorkspaceOptions struct {
302+
WorkspaceID string
303+
}
304+
305+
func (g *GPTScript) GetRevisionForFileInWorkspace(ctx context.Context, filePath, revisionID string, opts ...GetRevisionForFileInWorkspaceOptions) ([]byte, error) {
306+
var opt GetRevisionForFileInWorkspaceOptions
307+
for _, o := range opts {
308+
if o.WorkspaceID != "" {
309+
opt.WorkspaceID = o.WorkspaceID
310+
}
311+
}
312+
313+
if opt.WorkspaceID == "" {
314+
opt.WorkspaceID = os.Getenv("GPTSCRIPT_WORKSPACE_ID")
315+
}
316+
317+
out, err := g.runBasicCommand(ctx, "workspaces/get-revision", map[string]any{
318+
"id": opt.WorkspaceID,
319+
"filePath": filePath,
320+
"revisionID": revisionID,
321+
"workspaceTool": g.globalOpts.WorkspaceTool,
322+
"env": g.globalOpts.Env,
323+
})
324+
if err != nil {
325+
if strings.HasSuffix(err.Error(), fmt.Sprintf("not found: %s/%s", opt.WorkspaceID, filePath)) {
326+
return nil, newNotFoundInWorkspaceError(opt.WorkspaceID, filePath)
327+
}
328+
return nil, err
329+
}
330+
331+
return base64.StdEncoding.DecodeString(out)
332+
}
333+
334+
type DeleteRevisionForFileInWorkspaceOptions struct {
335+
WorkspaceID string
336+
}
337+
338+
func (g *GPTScript) DeleteRevisionForFileInWorkspace(ctx context.Context, filePath, revisionID string, opts ...DeleteRevisionForFileInWorkspaceOptions) error {
339+
var opt DeleteRevisionForFileInWorkspaceOptions
340+
for _, o := range opts {
341+
if o.WorkspaceID != "" {
342+
opt.WorkspaceID = o.WorkspaceID
343+
}
344+
}
345+
346+
if opt.WorkspaceID == "" {
347+
opt.WorkspaceID = os.Getenv("GPTSCRIPT_WORKSPACE_ID")
348+
}
349+
350+
_, err := g.runBasicCommand(ctx, "workspaces/delete-revision", map[string]any{
351+
"id": opt.WorkspaceID,
352+
"filePath": filePath,
353+
"revisionID": revisionID,
354+
"workspaceTool": g.globalOpts.WorkspaceTool,
355+
"env": g.globalOpts.Env,
356+
})
357+
if err != nil && strings.HasSuffix(err.Error(), fmt.Sprintf("not found: %s/%s", opt.WorkspaceID, filePath)) {
358+
return newNotFoundInWorkspaceError(opt.WorkspaceID, fmt.Sprintf("revision %s for %s", revisionID, filePath))
359+
}
360+
361+
return err
256362
}

workspace_test.go

+189
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"errors"
7+
"fmt"
78
"os"
89
"testing"
910
)
@@ -91,6 +92,98 @@ func TestWriteReadAndDeleteFileFromWorkspace(t *testing.T) {
9192
}
9293
}
9394

95+
func TestRevisionsForFileInWorkspace(t *testing.T) {
96+
id, err := g.CreateWorkspace(context.Background(), "directory")
97+
if err != nil {
98+
t.Fatalf("Error creating workspace: %v", err)
99+
}
100+
101+
t.Cleanup(func() {
102+
err := g.DeleteWorkspace(context.Background(), id)
103+
if err != nil {
104+
t.Errorf("Error deleting workspace: %v", err)
105+
}
106+
})
107+
108+
err = g.WriteFileInWorkspace(context.Background(), "test.txt", []byte("test0"), WriteFileInWorkspaceOptions{WorkspaceID: id})
109+
if err != nil {
110+
t.Fatalf("Error creating file: %v", err)
111+
}
112+
113+
err = g.WriteFileInWorkspace(context.Background(), "test.txt", []byte("test1"), WriteFileInWorkspaceOptions{WorkspaceID: id})
114+
if err != nil {
115+
t.Fatalf("Error creating file: %v", err)
116+
}
117+
118+
err = g.WriteFileInWorkspace(context.Background(), "test.txt", []byte("test2"), WriteFileInWorkspaceOptions{WorkspaceID: id})
119+
if err != nil {
120+
t.Fatalf("Error creating file: %v", err)
121+
}
122+
123+
revisions, err := g.ListRevisionsForFileInWorkspace(context.Background(), "test.txt", ListRevisionsForFileInWorkspaceOptions{WorkspaceID: id})
124+
if err != nil {
125+
t.Errorf("Error reading file: %v", err)
126+
}
127+
128+
if len(revisions) != 2 {
129+
t.Errorf("Unexpected number of revisions: %d", len(revisions))
130+
}
131+
132+
for i, rev := range revisions {
133+
if rev.WorkspaceID != id {
134+
t.Errorf("Unexpected file workspace ID: %v", rev.WorkspaceID)
135+
}
136+
137+
if rev.Name != "test.txt" {
138+
t.Errorf("Unexpected file name: %s", rev.Name)
139+
}
140+
141+
if rev.Size != 5 {
142+
t.Errorf("Unexpected file size: %d", rev.Size)
143+
}
144+
145+
if rev.ModTime.IsZero() {
146+
t.Errorf("Unexpected file mod time: %v", rev.ModTime)
147+
}
148+
149+
if rev.MimeType != "text/plain" {
150+
t.Errorf("Unexpected file mime type: %s", rev.MimeType)
151+
}
152+
153+
if rev.RevisionID != fmt.Sprintf("%d", i+1) {
154+
t.Errorf("Unexpected revision ID: %s", rev.RevisionID)
155+
}
156+
}
157+
158+
err = g.DeleteRevisionForFileInWorkspace(context.Background(), "test.txt", "1", DeleteRevisionForFileInWorkspaceOptions{WorkspaceID: id})
159+
if err != nil {
160+
t.Errorf("Error deleting revision for file: %v", err)
161+
}
162+
163+
revisions, err = g.ListRevisionsForFileInWorkspace(context.Background(), "test.txt", ListRevisionsForFileInWorkspaceOptions{WorkspaceID: id})
164+
if err != nil {
165+
t.Errorf("Error reading file: %v", err)
166+
}
167+
168+
if len(revisions) != 1 {
169+
t.Errorf("Unexpected number of revisions: %d", len(revisions))
170+
}
171+
172+
err = g.DeleteFileInWorkspace(context.Background(), "test.txt", DeleteFileInWorkspaceOptions{WorkspaceID: id})
173+
if err != nil {
174+
t.Errorf("Error deleting file: %v", err)
175+
}
176+
177+
revisions, err = g.ListRevisionsForFileInWorkspace(context.Background(), "test.txt", ListRevisionsForFileInWorkspaceOptions{WorkspaceID: id})
178+
if err != nil {
179+
t.Errorf("Error reading file: %v", err)
180+
}
181+
182+
if len(revisions) != 0 {
183+
t.Errorf("Unexpected number of revisions: %d", len(revisions))
184+
}
185+
}
186+
94187
func TestLsComplexWorkspace(t *testing.T) {
95188
id, err := g.CreateWorkspace(context.Background(), "directory")
96189
if err != nil {
@@ -246,6 +339,102 @@ func TestWriteReadAndDeleteFileFromWorkspaceS3(t *testing.T) {
246339
}
247340
}
248341

342+
func TestRevisionsForFileInWorkspaceS3(t *testing.T) {
343+
if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" || os.Getenv("WORKSPACE_PROVIDER_S3_BUCKET") == "" {
344+
t.Skip("Skipping test because AWS credentials are not set")
345+
}
346+
347+
id, err := g.CreateWorkspace(context.Background(), "s3")
348+
if err != nil {
349+
t.Fatalf("Error creating workspace: %v", err)
350+
}
351+
352+
t.Cleanup(func() {
353+
err := g.DeleteWorkspace(context.Background(), id)
354+
if err != nil {
355+
t.Errorf("Error deleting workspace: %v", err)
356+
}
357+
})
358+
359+
err = g.WriteFileInWorkspace(context.Background(), "test.txt", []byte("test0"), WriteFileInWorkspaceOptions{WorkspaceID: id})
360+
if err != nil {
361+
t.Fatalf("Error creating file: %v", err)
362+
}
363+
364+
err = g.WriteFileInWorkspace(context.Background(), "test.txt", []byte("test1"), WriteFileInWorkspaceOptions{WorkspaceID: id})
365+
if err != nil {
366+
t.Fatalf("Error creating file: %v", err)
367+
}
368+
369+
err = g.WriteFileInWorkspace(context.Background(), "test.txt", []byte("test2"), WriteFileInWorkspaceOptions{WorkspaceID: id})
370+
if err != nil {
371+
t.Fatalf("Error creating file: %v", err)
372+
}
373+
374+
revisions, err := g.ListRevisionsForFileInWorkspace(context.Background(), "test.txt", ListRevisionsForFileInWorkspaceOptions{WorkspaceID: id})
375+
if err != nil {
376+
t.Errorf("Error reading file: %v", err)
377+
}
378+
379+
if len(revisions) != 2 {
380+
t.Errorf("Unexpected number of revisions: %d", len(revisions))
381+
}
382+
383+
for i, rev := range revisions {
384+
if rev.WorkspaceID != id {
385+
t.Errorf("Unexpected file workspace ID: %v", rev.WorkspaceID)
386+
}
387+
388+
if rev.Name != "test.txt" {
389+
t.Errorf("Unexpected file name: %s", rev.Name)
390+
}
391+
392+
if rev.Size != 5 {
393+
t.Errorf("Unexpected file size: %d", rev.Size)
394+
}
395+
396+
if rev.ModTime.IsZero() {
397+
t.Errorf("Unexpected file mod time: %v", rev.ModTime)
398+
}
399+
400+
if rev.MimeType != "text/plain" {
401+
t.Errorf("Unexpected file mime type: %s", rev.MimeType)
402+
}
403+
404+
if rev.RevisionID != fmt.Sprintf("%d", i+1) {
405+
t.Errorf("Unexpected revision ID: %s", rev.RevisionID)
406+
}
407+
}
408+
409+
err = g.DeleteRevisionForFileInWorkspace(context.Background(), "test.txt", "1", DeleteRevisionForFileInWorkspaceOptions{WorkspaceID: id})
410+
if err != nil {
411+
t.Errorf("Error deleting revision for file: %v", err)
412+
}
413+
414+
revisions, err = g.ListRevisionsForFileInWorkspace(context.Background(), "test.txt", ListRevisionsForFileInWorkspaceOptions{WorkspaceID: id})
415+
if err != nil {
416+
t.Errorf("Error reading file: %v", err)
417+
}
418+
419+
if len(revisions) != 1 {
420+
t.Errorf("Unexpected number of revisions: %d", len(revisions))
421+
}
422+
423+
err = g.DeleteFileInWorkspace(context.Background(), "test.txt", DeleteFileInWorkspaceOptions{WorkspaceID: id})
424+
if err != nil {
425+
t.Errorf("Error deleting file: %v", err)
426+
}
427+
428+
revisions, err = g.ListRevisionsForFileInWorkspace(context.Background(), "test.txt", ListRevisionsForFileInWorkspaceOptions{WorkspaceID: id})
429+
if err != nil {
430+
t.Errorf("Error reading file: %v", err)
431+
}
432+
433+
if len(revisions) != 0 {
434+
t.Errorf("Unexpected number of revisions: %d", len(revisions))
435+
}
436+
}
437+
249438
func TestLsComplexWorkspaceS3(t *testing.T) {
250439
if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" || os.Getenv("WORKSPACE_PROVIDER_S3_BUCKET") == "" {
251440
t.Skip("Skipping test because AWS credentials are not set")

0 commit comments

Comments
 (0)