@pippit-dev/cli 1.0.42 → 1.0.43
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/README.md +42 -11
- package/checksums.txt +6 -6
- package/cmd/auth/auth.go +19 -5
- package/cmd/auth/auth_test.go +23 -0
- package/cmd/canvas/canvas.go +7 -7
- package/cmd/download_result.go +3 -2
- package/cmd/marketing.go +19 -3
- package/cmd/marketing_test.go +11 -5
- package/cmd/model.go +5 -4
- package/cmd/root.go +13 -5
- package/cmd/short_drama_test.go +2 -0
- package/cmd/source_test.go +27 -4
- package/cmd/space.go +199 -0
- package/cmd/space_test.go +469 -0
- package/cmd/upload_file_test.go +3 -1
- package/dist/checksums.txt +6 -6
- package/internal/adaptercli/commands_test.go +9 -5
- package/internal/auth/space.go +26 -0
- package/internal/auth/space_test.go +42 -0
- package/internal/canvas/get.go +9 -2
- package/internal/commandnames/names.go +9 -4
- package/internal/common/client.go +23 -0
- package/internal/common/host_source.go +5 -0
- package/internal/common/host_source_test.go +9 -1
- package/internal/common/runner.go +5 -3
- package/internal/common/space.go +185 -0
- package/internal/common/space_test.go +132 -0
- package/internal/common/submit_run.go +2 -0
- package/internal/common/upload_file.go +2 -0
- package/internal/config/config.go +15 -12
- package/internal/generate_image/generate_image.go +3 -9
- package/internal/short_drama/submit_run.go +3 -9
- package/internal/space/session.go +153 -0
- package/internal/space/store.go +211 -0
- package/internal/space/store_test.go +246 -0
- package/package.json +1 -1
- package/scripts/canvas-command.js +37 -4
- package/scripts/install-wizard.js +1 -1
- package/scripts/run.js +2 -2
- package/scripts/telemetry.js +3 -1
- package/skills/short-drama/SKILL.md +12 -1
- package/skills/xyq-marketing-skill/SKILL.md +22 -3
- package/skills/xyq-marketing-skill/references/api.md +7 -1
- package/skills/xyq-marketing-skill/scripts/marketing.js +42 -18
- package/skills/xyq-nest-skill/SKILL.md +15 -4
- package/skills/xyq-nest-skill/commands/canvas-assets.md +1 -1
- package/skills/xyq-nest-skill/commands/canvas.md +5 -3
- package/skills/xyq-nest-skill/commands/get-credit-balance.md +2 -2
- package/skills/xyq-nest-skill/commands/model.md +1 -1
- package/skills/xyq-nest-skill/scripts/ensure-cli.js +8 -1
- package/skills/xyq-nest-skill/scripts/install.md +1 -1
- package/skills/xyq-nest-skill/workflows/canvas-edit.md +2 -2
package/cmd/space.go
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
|
|
8
|
+
"github.com/Pippit-dev/pippit-cli/internal/commandnames"
|
|
9
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
10
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
11
|
+
"github.com/Pippit-dev/pippit-cli/internal/space"
|
|
12
|
+
"github.com/spf13/cobra"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func prepareCommandSpace(cmd *cobra.Command, runner *common.Runner, stderr io.Writer) error {
|
|
16
|
+
if runner == nil {
|
|
17
|
+
return nil
|
|
18
|
+
}
|
|
19
|
+
teamFlag := cmd.Flags().Lookup("team-id")
|
|
20
|
+
var explicit *string
|
|
21
|
+
if teamFlag != nil && teamFlag.Changed {
|
|
22
|
+
id, err := space.ParseTeamID(teamFlag.Value.String())
|
|
23
|
+
if err != nil {
|
|
24
|
+
return err
|
|
25
|
+
}
|
|
26
|
+
explicit = &id
|
|
27
|
+
}
|
|
28
|
+
top := cmd
|
|
29
|
+
for top.Parent() != nil && top.Parent().Parent() != nil {
|
|
30
|
+
top = top.Parent()
|
|
31
|
+
}
|
|
32
|
+
switch top.Name() {
|
|
33
|
+
case commandnames.Space, commandnames.Login, commandnames.Logout, commandnames.Status, commandnames.Update, commandnames.Completion, commandnames.Help, commandnames.DownloadResult:
|
|
34
|
+
return nil
|
|
35
|
+
}
|
|
36
|
+
raw, _ := cmd.Flags().GetString("space-context")
|
|
37
|
+
store, err := common.SpaceStoreForRunner(cmd.Context(), runner)
|
|
38
|
+
if err != nil {
|
|
39
|
+
if explicit == nil || raw != "" {
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
if stderr != nil {
|
|
43
|
+
fmt.Fprintln(stderr, "本地空间记录不可用,将使用显式 --team-id;本次不保存空间记录。")
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if store == nil && explicit == nil && raw == "" {
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
session := &space.Session{Store: store, Explicit: explicit, Stderr: stderr, Identity: func(ctx context.Context) (string, error) { return common.SpaceAccount(ctx, runner) }}
|
|
50
|
+
if raw != "" {
|
|
51
|
+
session.Expected, err = space.DecodeBinding(raw)
|
|
52
|
+
if err != nil {
|
|
53
|
+
return err
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
ctx := common.WithSpace(cmd.Context(), session)
|
|
57
|
+
cmd.SetContext(ctx)
|
|
58
|
+
// Business requests resolve their space from the final request body.
|
|
59
|
+
return nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type listedSpace struct {
|
|
63
|
+
space.Selection
|
|
64
|
+
CurrentRole *int32 `json:"current_role,omitempty"`
|
|
65
|
+
Current bool `json:"current"`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func loadSpaces(ctx context.Context, runner *common.Runner, store *space.Store) ([]listedSpace, error) {
|
|
69
|
+
if store != nil {
|
|
70
|
+
ctx = common.WithSpace(ctx, &space.Session{Store: store, Identity: func(ctx context.Context) (string, error) { return common.SpaceAccount(ctx, runner) }})
|
|
71
|
+
}
|
|
72
|
+
result := []listedSpace{{Selection: space.Personal()}}
|
|
73
|
+
seenTeams := map[string]bool{"0": true}
|
|
74
|
+
seenCursors := map[string]bool{}
|
|
75
|
+
cursor := "0"
|
|
76
|
+
for {
|
|
77
|
+
if seenCursors[cursor] {
|
|
78
|
+
return nil, fmt.Errorf("团队列表分页游标重复,未保存空间选择")
|
|
79
|
+
}
|
|
80
|
+
seenCursors[cursor] = true
|
|
81
|
+
var response struct {
|
|
82
|
+
Ret string `json:"ret"`
|
|
83
|
+
Errmsg string `json:"errmsg"`
|
|
84
|
+
LogID string `json:"log_id"`
|
|
85
|
+
Data *struct {
|
|
86
|
+
Teams []struct {
|
|
87
|
+
TeamID string `json:"team_id"`
|
|
88
|
+
Name string `json:"team_name"`
|
|
89
|
+
Role *int32 `json:"current_role"`
|
|
90
|
+
} `json:"team_info_list"`
|
|
91
|
+
NextCursor string `json:"next_cursor"`
|
|
92
|
+
HasMore bool `json:"has_more"`
|
|
93
|
+
} `json:"data"`
|
|
94
|
+
}
|
|
95
|
+
if err := runner.Client.SendRequest(ctx, config.GetLiteTeamListPath, map[string]any{"cursor": cursor, "count": 100}, &response); err != nil {
|
|
96
|
+
return nil, fmt.Errorf("获取团队列表失败,默认空间未改变: %w", err)
|
|
97
|
+
}
|
|
98
|
+
if response.Ret != "0" {
|
|
99
|
+
return nil, common.NewLogIDError("获取团队列表失败: "+response.Errmsg, response.LogID)
|
|
100
|
+
}
|
|
101
|
+
if response.Data == nil {
|
|
102
|
+
return nil, fmt.Errorf("团队列表响应缺少 data")
|
|
103
|
+
}
|
|
104
|
+
for _, team := range response.Data.Teams {
|
|
105
|
+
id, err := space.ParseTeamID(team.TeamID)
|
|
106
|
+
if err != nil || id == "0" {
|
|
107
|
+
return nil, fmt.Errorf("团队列表返回无效 team_id")
|
|
108
|
+
}
|
|
109
|
+
if !seenTeams[id] {
|
|
110
|
+
result = append(result, listedSpace{Selection: space.Selection{TeamID: id, Name: team.Name}, CurrentRole: team.Role})
|
|
111
|
+
seenTeams[id] = true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if !response.Data.HasMore {
|
|
115
|
+
return result, nil
|
|
116
|
+
}
|
|
117
|
+
if response.Data.NextCursor == "" {
|
|
118
|
+
return nil, fmt.Errorf("团队列表未返回下一页游标,未保存空间选择")
|
|
119
|
+
}
|
|
120
|
+
cursor = response.Data.NextCursor
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func newSpaceCommand(stdout io.Writer, runner *common.Runner) *cobra.Command {
|
|
125
|
+
root := &cobra.Command{
|
|
126
|
+
Use: commandnames.Space,
|
|
127
|
+
Short: "查看和切换个人或团队默认空间",
|
|
128
|
+
Args: cobra.NoArgs,
|
|
129
|
+
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
130
|
+
return cmd.Help()
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
list := &cobra.Command{Use: "list", Args: cobra.NoArgs, Short: "列出个人和团队空间,标记当前选择", RunE: func(cmd *cobra.Command, _ []string) error {
|
|
134
|
+
store, err := common.SpaceStoreForRunner(cmd.Context(), runner)
|
|
135
|
+
if err != nil {
|
|
136
|
+
return err
|
|
137
|
+
}
|
|
138
|
+
var selected *space.Selection
|
|
139
|
+
if store != nil {
|
|
140
|
+
current, err := store.Current()
|
|
141
|
+
if err != nil {
|
|
142
|
+
return err
|
|
143
|
+
}
|
|
144
|
+
selected = ¤t
|
|
145
|
+
}
|
|
146
|
+
spaces, err := loadSpaces(cmd.Context(), runner, store)
|
|
147
|
+
if err != nil {
|
|
148
|
+
return err
|
|
149
|
+
}
|
|
150
|
+
for i := range spaces {
|
|
151
|
+
spaces[i].Current = selected != nil && spaces[i].TeamID == selected.TeamID
|
|
152
|
+
}
|
|
153
|
+
return common.WriteJSON(stdout, map[string]any{"spaces": spaces, "current": selected})
|
|
154
|
+
}}
|
|
155
|
+
use := &cobra.Command{Use: "use <TEAM_ID>", Args: cobra.ExactArgs(1), Short: "保存默认空间;0 为个人,正整数为团队", RunE: func(cmd *cobra.Command, args []string) error {
|
|
156
|
+
id, err := space.ParseTeamID(args[0])
|
|
157
|
+
if err != nil {
|
|
158
|
+
return err
|
|
159
|
+
}
|
|
160
|
+
store, err := common.SpaceStoreForRunner(cmd.Context(), runner)
|
|
161
|
+
if err != nil {
|
|
162
|
+
return err
|
|
163
|
+
}
|
|
164
|
+
if store == nil {
|
|
165
|
+
return fmt.Errorf("无法识别当前账号,不能保存默认空间")
|
|
166
|
+
}
|
|
167
|
+
selected := space.Personal()
|
|
168
|
+
if id != "0" {
|
|
169
|
+
spaces, err := loadSpaces(cmd.Context(), runner, store)
|
|
170
|
+
if err != nil {
|
|
171
|
+
return err
|
|
172
|
+
}
|
|
173
|
+
found := false
|
|
174
|
+
for _, item := range spaces {
|
|
175
|
+
if item.TeamID == id {
|
|
176
|
+
selected = item.Selection
|
|
177
|
+
found = true
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if !found {
|
|
182
|
+
return fmt.Errorf("当前账号的团队列表中没有 %s,默认空间未改变", id)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
account, err := common.SpaceAccount(cmd.Context(), runner)
|
|
186
|
+
if err != nil {
|
|
187
|
+
return err
|
|
188
|
+
}
|
|
189
|
+
if account != store.Account {
|
|
190
|
+
return fmt.Errorf("账号已改变,默认空间未保存;请重新选择")
|
|
191
|
+
}
|
|
192
|
+
if err := store.SaveDefault(selected); err != nil {
|
|
193
|
+
return err
|
|
194
|
+
}
|
|
195
|
+
return common.WriteJSON(stdout, selected)
|
|
196
|
+
}}
|
|
197
|
+
root.AddCommand(list, use)
|
|
198
|
+
return root
|
|
199
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"errors"
|
|
8
|
+
"io"
|
|
9
|
+
"net/http"
|
|
10
|
+
"net/http/httptest"
|
|
11
|
+
"os"
|
|
12
|
+
"path/filepath"
|
|
13
|
+
"strings"
|
|
14
|
+
"testing"
|
|
15
|
+
"time"
|
|
16
|
+
|
|
17
|
+
"github.com/Pippit-dev/pippit-cli/internal/adapter"
|
|
18
|
+
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
19
|
+
canvascore "github.com/Pippit-dev/pippit-cli/internal/canvas"
|
|
20
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
21
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
22
|
+
"github.com/Pippit-dev/pippit-cli/internal/space"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
func TestSpaceCommandsAndBusinessTaskScope(t *testing.T) {
|
|
26
|
+
t.Setenv("HOME", t.TempDir())
|
|
27
|
+
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
|
28
|
+
t.Setenv("LocalAppData", t.TempDir())
|
|
29
|
+
var requests []string
|
|
30
|
+
var requestRef space.References
|
|
31
|
+
listFailure := false
|
|
32
|
+
listTeamID := ""
|
|
33
|
+
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
34
|
+
requests = append(requests, r.URL.Path+":"+r.Header.Get("X-Team-Id"))
|
|
35
|
+
w.Header().Set("Content-Type", "application/json")
|
|
36
|
+
if r.URL.Path == config.GetLiteTeamListPath {
|
|
37
|
+
if r.Header.Get("X-Team-Id") != listTeamID {
|
|
38
|
+
t.Errorf("team list header = %q, want %q", r.Header.Get("X-Team-Id"), listTeamID)
|
|
39
|
+
}
|
|
40
|
+
var body struct {
|
|
41
|
+
Cursor string `json:"cursor"`
|
|
42
|
+
Count int `json:"count"`
|
|
43
|
+
}
|
|
44
|
+
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
45
|
+
t.Error(err)
|
|
46
|
+
}
|
|
47
|
+
if body.Count != 100 {
|
|
48
|
+
t.Error("unexpected page size")
|
|
49
|
+
}
|
|
50
|
+
if listFailure {
|
|
51
|
+
_, _ = w.Write([]byte(`{"ret":"101","errmsg":"denied"}`))
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if body.Cursor == "0" {
|
|
55
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"team_info_list":[{"team_id":"12","team_name":"A","current_role":1}],"has_more":true,"next_cursor":"next"}}`))
|
|
56
|
+
} else {
|
|
57
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"team_info_list":[{"team_id":"34","team_name":"B"}],"has_more":false}}`))
|
|
58
|
+
}
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
if r.URL.Path == config.UploadFilePath {
|
|
62
|
+
_, _ = io.Copy(io.Discard, r.Body)
|
|
63
|
+
} else {
|
|
64
|
+
requestRef = space.References{}
|
|
65
|
+
if err := json.NewDecoder(r.Body).Decode(&requestRef); err != nil {
|
|
66
|
+
t.Error(err)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
switch r.URL.Path {
|
|
70
|
+
case canvascore.QueryPath:
|
|
71
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"Assets":[{"PippitAssetID":"canvas"}]}}`))
|
|
72
|
+
case config.SubmitRunPath:
|
|
73
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"run":{"thread_id":"thread-a","run_id":"run-a"}}}`))
|
|
74
|
+
case config.GetThreadPath:
|
|
75
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"readable_text":"task result"}}`))
|
|
76
|
+
case config.ListThreadFilePath:
|
|
77
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"files":[],"total":0}}`))
|
|
78
|
+
case config.UploadFilePath:
|
|
79
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"pippit_asset_id":"asset-a"}}`))
|
|
80
|
+
case marketingPaths["generate"]:
|
|
81
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"run":{"thread_id":"marketing-thread","run_id":"marketing-run"}}}`))
|
|
82
|
+
case marketingPaths["query"]:
|
|
83
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"thread_id":"marketing-thread","run_id":"marketing-run","run_state":2}}`))
|
|
84
|
+
case config.GetCreditBalancePath:
|
|
85
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"total_remain_amount":"100"}}`))
|
|
86
|
+
case config.GetAvailableModelListPath:
|
|
87
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"scene":"web_turbo_video_generator","config_key":"key","config":{"models":[{"key":"MiniMax-H3","name":"MiniMax","kind":"video","supported_ratio_list":[0,2,3],"default_ratio":3,"audio_total_limit":0}]}}}`))
|
|
88
|
+
default:
|
|
89
|
+
t.Errorf("unexpected request %s", r.URL.Path)
|
|
90
|
+
}
|
|
91
|
+
}))
|
|
92
|
+
defer api.Close()
|
|
93
|
+
cfg := config.Load()
|
|
94
|
+
cfg.BaseURL, cfg.AccessKey = api.URL, ""
|
|
95
|
+
runner := newRootRunner(cfg)
|
|
96
|
+
credentials := &marketingCredentialStore{credential: &auth.Credential{UID: "space-user", AccessKey: "space-test-key", ExpiredAt: time.Now().Add(time.Hour).Unix()}}
|
|
97
|
+
runner.Auth = auth.NewManager(cfg, auth.WithCredentialStore(credentials))
|
|
98
|
+
runner.SpaceStore = &space.Store{Dir: t.TempDir()}
|
|
99
|
+
store, err := common.SpaceStoreForRunner(context.Background(), runner)
|
|
100
|
+
if err != nil {
|
|
101
|
+
t.Fatal(err)
|
|
102
|
+
}
|
|
103
|
+
execute := func(args ...string) (string, error) {
|
|
104
|
+
var out, warnings bytes.Buffer
|
|
105
|
+
root := newRootCommand(&out, &warnings, runner)
|
|
106
|
+
root.SetIn(strings.NewReader(""))
|
|
107
|
+
root.SetArgs(args)
|
|
108
|
+
err := root.Execute()
|
|
109
|
+
return out.String(), err
|
|
110
|
+
}
|
|
111
|
+
must := func(args ...string) string {
|
|
112
|
+
t.Helper()
|
|
113
|
+
out, err := execute(args...)
|
|
114
|
+
if err != nil {
|
|
115
|
+
t.Fatalf("%v: %v", args, err)
|
|
116
|
+
}
|
|
117
|
+
return out
|
|
118
|
+
}
|
|
119
|
+
checkList := func(teamID string) {
|
|
120
|
+
t.Helper()
|
|
121
|
+
var result struct {
|
|
122
|
+
Spaces []listedSpace `json:"spaces"`
|
|
123
|
+
Current *space.Selection `json:"current"`
|
|
124
|
+
}
|
|
125
|
+
out := must("space", "list")
|
|
126
|
+
if err := json.Unmarshal([]byte(out), &result); err != nil {
|
|
127
|
+
t.Fatal(err)
|
|
128
|
+
}
|
|
129
|
+
if result.Current == nil || result.Current.TeamID != teamID || len(result.Spaces) != 3 {
|
|
130
|
+
t.Fatalf("unexpected space list: %s", out)
|
|
131
|
+
}
|
|
132
|
+
for _, item := range result.Spaces {
|
|
133
|
+
if item.Current != (item.TeamID == teamID) {
|
|
134
|
+
t.Fatalf("wrong current marker: %+v", item)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
checkList("0")
|
|
139
|
+
must("space", "use", "12")
|
|
140
|
+
listTeamID = "12"
|
|
141
|
+
checkList("12")
|
|
142
|
+
file := filepath.Join(t.TempDir(), "image.png")
|
|
143
|
+
if err := os.WriteFile(file, []byte("media"), 0600); err != nil {
|
|
144
|
+
t.Fatal(err)
|
|
145
|
+
}
|
|
146
|
+
if out := must("upload-file", "--path", file); !strings.Contains(out, `"team_id":"12"`) {
|
|
147
|
+
t.Fatal(out)
|
|
148
|
+
}
|
|
149
|
+
if out := must("short-drama", "+submit-run", "--message", "create", "--asset-ids", "asset-a"); !strings.Contains(out, `"team_id":"12"`) {
|
|
150
|
+
t.Fatal(out)
|
|
151
|
+
}
|
|
152
|
+
must("space", "use", "34")
|
|
153
|
+
listTeamID = "34"
|
|
154
|
+
checkList("34")
|
|
155
|
+
must("model", "list", "--refresh")
|
|
156
|
+
if requests[len(requests)-1] != config.GetAvailableModelListPath+":34" {
|
|
157
|
+
t.Fatal("model query did not forward the selected team", requests)
|
|
158
|
+
}
|
|
159
|
+
must("model", "list", "--refresh", "--team-id", "0")
|
|
160
|
+
if requests[len(requests)-1] != config.GetAvailableModelListPath+":" {
|
|
161
|
+
t.Fatal("personal model query carried a team header", requests)
|
|
162
|
+
}
|
|
163
|
+
must("get-thread", "--thread-id", "thread-a", "--run-id", "run-a")
|
|
164
|
+
if requests[len(requests)-1] != config.GetThreadPath+":12" {
|
|
165
|
+
t.Fatal("old task used new default", requests)
|
|
166
|
+
}
|
|
167
|
+
for _, tc := range []struct {
|
|
168
|
+
args []string
|
|
169
|
+
path string
|
|
170
|
+
}{
|
|
171
|
+
{[]string{"get-thread", "--thread-id", " thread-a ", "--run-id", " run-a "}, config.GetThreadPath},
|
|
172
|
+
{[]string{"list-thread-file", "--thread-id", " thread-a "}, config.ListThreadFilePath},
|
|
173
|
+
{[]string{"short-drama", "+submit-run", "--thread-id", " thread-a ", "--message", "continue"}, config.SubmitRunPath},
|
|
174
|
+
} {
|
|
175
|
+
must(tc.args...)
|
|
176
|
+
if requests[len(requests)-1] != tc.path+":12" || requestRef.ThreadID != "thread-a" {
|
|
177
|
+
t.Fatalf("normalized task used wrong space: %v, %+v", requests, requestRef)
|
|
178
|
+
}
|
|
179
|
+
if tc.path == config.GetThreadPath && requestRef.RunID != "run-a" {
|
|
180
|
+
t.Fatalf("run ID was not normalized: %+v", requestRef)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
must("get-credit-balance")
|
|
184
|
+
if requests[len(requests)-1] != config.GetCreditBalancePath+":34" {
|
|
185
|
+
t.Fatal(requests)
|
|
186
|
+
}
|
|
187
|
+
must("get-credit-balance", "--team-id", "0")
|
|
188
|
+
if requests[len(requests)-1] != config.GetCreditBalancePath+":" {
|
|
189
|
+
t.Fatal(requests)
|
|
190
|
+
}
|
|
191
|
+
// Canvas uses the same default, override and cross-process binding as other commands.
|
|
192
|
+
for _, tc := range []struct {
|
|
193
|
+
flags []string
|
|
194
|
+
teamID string
|
|
195
|
+
}{
|
|
196
|
+
{nil, "34"},
|
|
197
|
+
{[]string{"--team-id", "12"}, "12"},
|
|
198
|
+
{[]string{"--team-id", "0"}, "0"},
|
|
199
|
+
} {
|
|
200
|
+
var result canvascore.GetResult
|
|
201
|
+
out := must(append([]string{"canvas", "get", "--asset-id", "canvas"}, tc.flags...)...)
|
|
202
|
+
if err := json.Unmarshal([]byte(out), &result); err != nil {
|
|
203
|
+
t.Fatal(err)
|
|
204
|
+
}
|
|
205
|
+
header := tc.teamID
|
|
206
|
+
if header == "0" {
|
|
207
|
+
header = ""
|
|
208
|
+
}
|
|
209
|
+
if result.TeamID != tc.teamID || requests[len(requests)-1] != canvascore.QueryPath+":"+header {
|
|
210
|
+
t.Fatalf("canvas space = %s, requests = %v", out, requests)
|
|
211
|
+
}
|
|
212
|
+
binding, err := space.DecodeBinding(result.SpaceContext)
|
|
213
|
+
if err != nil || binding.TeamID != tc.teamID || binding.Account != store.Account || binding.Origin != store.Origin {
|
|
214
|
+
t.Fatalf("canvas binding = %+v, %v", binding, err)
|
|
215
|
+
}
|
|
216
|
+
must("canvas", "get", "--asset-id", "canvas", "--space-context", result.SpaceContext)
|
|
217
|
+
if requests[len(requests)-1] != canvascore.QueryPath+":"+header {
|
|
218
|
+
t.Fatal("canvas binding did not preserve the original space", requests)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
before := len(requests)
|
|
222
|
+
for _, args := range [][]string{
|
|
223
|
+
{"get-thread", "--thread-id", "thread-a", "--team-id", "34"},
|
|
224
|
+
{"short-drama", "+submit-run", "--message", "wrong asset", "--asset-ids", "asset-a"},
|
|
225
|
+
{"get-credit-balance", "--team-id", "-1"},
|
|
226
|
+
{"space", "use"},
|
|
227
|
+
{"space", "use", "12", "34"},
|
|
228
|
+
{"space", "current"},
|
|
229
|
+
} {
|
|
230
|
+
if _, err := execute(args...); err == nil {
|
|
231
|
+
t.Errorf("accepted %v", args)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if len(requests) != before {
|
|
235
|
+
t.Fatal("invalid operations reached API")
|
|
236
|
+
}
|
|
237
|
+
if _, err := execute("space", "use", "99"); err == nil {
|
|
238
|
+
t.Fatal("nonmember selection saved")
|
|
239
|
+
}
|
|
240
|
+
listFailure = true
|
|
241
|
+
if _, err := execute("space", "use", "12"); err == nil {
|
|
242
|
+
t.Fatal("list failure ignored")
|
|
243
|
+
}
|
|
244
|
+
if selected, err := store.Current(); err != nil || selected.TeamID != "34" {
|
|
245
|
+
t.Fatalf("failed selection changed default: %+v, %v", selected, err)
|
|
246
|
+
}
|
|
247
|
+
before = len(requests)
|
|
248
|
+
must("space", "use", "0")
|
|
249
|
+
listTeamID = ""
|
|
250
|
+
if len(requests) != before {
|
|
251
|
+
t.Fatal("personal switch depends on team list")
|
|
252
|
+
}
|
|
253
|
+
if selected, err := store.Current(); err != nil || selected.TeamID != "0" {
|
|
254
|
+
t.Fatalf("personal selection not saved: %+v, %v", selected, err)
|
|
255
|
+
}
|
|
256
|
+
listFailure = false
|
|
257
|
+
checkList("0")
|
|
258
|
+
before = len(requests)
|
|
259
|
+
// Marketing preview resolves an existing thread before reading the new default.
|
|
260
|
+
if err := store.SaveDefault(space.Selection{TeamID: "34", Name: "B"}); err != nil {
|
|
261
|
+
t.Fatal(err)
|
|
262
|
+
}
|
|
263
|
+
requestFile := filepath.Join(t.TempDir(), "request.json")
|
|
264
|
+
if err := os.WriteFile(requestFile, []byte(`{"thread_id":"thread-a","message":"continue","general_agent_settings":{"video_model":"model"}}`), 0600); err != nil {
|
|
265
|
+
t.Fatal(err)
|
|
266
|
+
}
|
|
267
|
+
loadsBefore := credentials.loads
|
|
268
|
+
if out := must("marketing", "generate", "--request", requestFile); !strings.Contains(out, `"team_id":"12"`) {
|
|
269
|
+
t.Fatal(out)
|
|
270
|
+
}
|
|
271
|
+
if credentials.loads-loadsBefore != 2 {
|
|
272
|
+
t.Fatal("preview must read identity at initialization and check it once")
|
|
273
|
+
}
|
|
274
|
+
if len(requests) != before {
|
|
275
|
+
t.Fatal("preview contacted API")
|
|
276
|
+
}
|
|
277
|
+
if err := os.WriteFile(requestFile, []byte(`{"message":"new marketing task","general_agent_settings":{"video_model":"model"}}`), 0600); err != nil {
|
|
278
|
+
t.Fatal(err)
|
|
279
|
+
}
|
|
280
|
+
loadsBefore = credentials.loads
|
|
281
|
+
if out := must("marketing", "generate", "--request", requestFile, "--execute"); !strings.Contains(out, `"team_id":"34"`) || !strings.Contains(out, `"space_context"`) {
|
|
282
|
+
t.Fatal(out)
|
|
283
|
+
}
|
|
284
|
+
if credentials.loads-loadsBefore != 2 {
|
|
285
|
+
t.Fatal("submission must read identity at initialization and check it only at the HTTP boundary")
|
|
286
|
+
}
|
|
287
|
+
if requests[len(requests)-1] != marketingPaths["generate"]+":34" {
|
|
288
|
+
t.Fatal("independent marketing client lost scope", requests)
|
|
289
|
+
}
|
|
290
|
+
if err := store.SaveDefault(space.Selection{TeamID: "12", Name: "A"}); err != nil {
|
|
291
|
+
t.Fatal(err)
|
|
292
|
+
}
|
|
293
|
+
must("marketing", "query", "--thread-id", "marketing-thread", "--run-id", "marketing-run")
|
|
294
|
+
if requests[len(requests)-1] != marketingPaths["query"]+":34" {
|
|
295
|
+
t.Fatal("marketing task lost scope", requests)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
type spaceExternalAuthAdapter struct{ adapter.Default }
|
|
300
|
+
|
|
301
|
+
func (spaceExternalAuthAdapter) CLIOptions() adapter.CLIOptions {
|
|
302
|
+
options := adapter.Default{}.CLIOptions()
|
|
303
|
+
options.LocalCredentialsEnabled = false
|
|
304
|
+
return options
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
func (spaceExternalAuthAdapter) PrepareAPIRequest(_ context.Context, req *http.Request) (adapter.AuthDecision, error) {
|
|
308
|
+
req.Header.Set("Authorization", "Bearer adapter-key")
|
|
309
|
+
return adapter.AuthProvided, nil
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
type spaceUnavailableAccount struct{ common.AuthManager }
|
|
313
|
+
|
|
314
|
+
func (spaceUnavailableAccount) SpaceAccount(context.Context) (string, error) {
|
|
315
|
+
return "", errors.New("credential metadata unavailable")
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func TestSpaceUsesAvailableIdentityInsteadOfLocalLoginSwitch(t *testing.T) {
|
|
319
|
+
var requests []string
|
|
320
|
+
denied := false
|
|
321
|
+
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
322
|
+
if r.Header.Get("Authorization") != "Bearer adapter-key" {
|
|
323
|
+
t.Error("adapter authentication was not used")
|
|
324
|
+
}
|
|
325
|
+
requests = append(requests, r.URL.Path+":"+r.Header.Get("X-Team-Id"))
|
|
326
|
+
switch r.URL.Path {
|
|
327
|
+
case config.GetLiteTeamListPath:
|
|
328
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"team_info_list":[{"team_id":"12","team_name":"A"}],"has_more":false}}`))
|
|
329
|
+
case config.GetCreditBalancePath:
|
|
330
|
+
if denied {
|
|
331
|
+
_, _ = w.Write([]byte(`{"ret":"101","errmsg":"denied"}`))
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"total_remain_amount":"100"}}`))
|
|
335
|
+
case config.UploadFilePath:
|
|
336
|
+
_, _ = io.Copy(io.Discard, r.Body)
|
|
337
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"pippit_asset_id":"asset-a"}}`))
|
|
338
|
+
case config.SubmitRunPath:
|
|
339
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"run":{"thread_id":"thread-a","run_id":"run-a"}}}`))
|
|
340
|
+
case config.GetThreadPath:
|
|
341
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"readable_text":"task result"}}`))
|
|
342
|
+
case config.QueryGenerateVideoResultPath:
|
|
343
|
+
_, _ = w.Write([]byte(`{"ret":"0","data":{"thread_id":"thread-a","run_id":"run-a","run_state":2}}`))
|
|
344
|
+
default:
|
|
345
|
+
t.Errorf("unexpected request: %s", r.URL.Path)
|
|
346
|
+
}
|
|
347
|
+
}))
|
|
348
|
+
defer api.Close()
|
|
349
|
+
cfg := config.Load()
|
|
350
|
+
// This AK is not used by the adapter and must not supply a false identity.
|
|
351
|
+
cfg.BaseURL, cfg.AccessKey = api.URL, "unrelated-environment-key"
|
|
352
|
+
policy := spaceExternalAuthAdapter{}
|
|
353
|
+
runner := newRootRunnerWithAdapter(cfg, policy)
|
|
354
|
+
dir := filepath.Join(t.TempDir(), "space-records")
|
|
355
|
+
runner.SpaceStore = &space.Store{Dir: dir}
|
|
356
|
+
execute := func(args ...string) (string, error) {
|
|
357
|
+
var out bytes.Buffer
|
|
358
|
+
root := newRootCommandWithAdapter(&out, io.Discard, runner, policy)
|
|
359
|
+
root.SetArgs(args)
|
|
360
|
+
err := root.Execute()
|
|
361
|
+
return out.String(), err
|
|
362
|
+
}
|
|
363
|
+
out, err := execute("space", "list")
|
|
364
|
+
if err != nil || !strings.Contains(out, `"team_id":"12"`) || !strings.Contains(out, `"current":null`) {
|
|
365
|
+
t.Fatalf("space list without local identity = %s, %v", out, err)
|
|
366
|
+
}
|
|
367
|
+
if requests[len(requests)-1] != config.GetLiteTeamListPath+":" {
|
|
368
|
+
t.Fatal("team list used a selected team", requests)
|
|
369
|
+
}
|
|
370
|
+
before := len(requests)
|
|
371
|
+
for _, args := range [][]string{
|
|
372
|
+
{"space", "use", "0"}, {"space", "use", "12"},
|
|
373
|
+
} {
|
|
374
|
+
if _, err := execute(args...); err == nil || !strings.Contains(err.Error(), "无法识别当前账号") {
|
|
375
|
+
t.Fatalf("%v: %v", args, err)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if len(requests) != before {
|
|
379
|
+
t.Fatal("unknown identity tried to persist a default space")
|
|
380
|
+
}
|
|
381
|
+
for _, teamID := range []string{"0", "12", "34"} {
|
|
382
|
+
if _, err := execute("get-credit-balance", "--team-id", teamID); err != nil {
|
|
383
|
+
t.Fatal(err)
|
|
384
|
+
}
|
|
385
|
+
wantHeader := teamID
|
|
386
|
+
if teamID == "0" {
|
|
387
|
+
wantHeader = ""
|
|
388
|
+
}
|
|
389
|
+
if requests[len(requests)-1] != config.GetCreditBalancePath+":"+wantHeader {
|
|
390
|
+
t.Fatal("explicit space was not forwarded", requests)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
file := filepath.Join(t.TempDir(), "image.png")
|
|
394
|
+
if err := os.WriteFile(file, []byte("image"), 0600); err != nil {
|
|
395
|
+
t.Fatal(err)
|
|
396
|
+
}
|
|
397
|
+
for _, tc := range []struct {
|
|
398
|
+
args []string
|
|
399
|
+
path string
|
|
400
|
+
}{
|
|
401
|
+
{[]string{"upload-file", "--path", file}, config.UploadFilePath},
|
|
402
|
+
{[]string{"short-drama", "+submit-run", "--message", "test", "--asset-ids", "asset-a"}, config.SubmitRunPath},
|
|
403
|
+
{[]string{"get-thread", "--thread-id", "thread-a"}, config.GetThreadPath},
|
|
404
|
+
{[]string{"marketing", "query", "--thread-id", "thread-a", "--run-id", "run-a"}, config.QueryGenerateVideoResultPath},
|
|
405
|
+
} {
|
|
406
|
+
if _, err := execute(append(tc.args, "--team-id", "12")...); err != nil {
|
|
407
|
+
t.Fatalf("%v: %v", tc.args, err)
|
|
408
|
+
}
|
|
409
|
+
if requests[len(requests)-1] != tc.path+":12" {
|
|
410
|
+
t.Fatal("explicit task space was not forwarded", requests)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
before = len(requests)
|
|
414
|
+
denied = true
|
|
415
|
+
if _, err := execute("get-credit-balance", "--team-id", "12"); err == nil || !strings.Contains(err.Error(), "denied") {
|
|
416
|
+
t.Fatalf("server denial was not returned: %v", err)
|
|
417
|
+
}
|
|
418
|
+
denied = false
|
|
419
|
+
if len(requests) != before+1 || requests[len(requests)-1] != config.GetCreditBalancePath+":12" {
|
|
420
|
+
t.Fatal("server denial retried or switched spaces", requests)
|
|
421
|
+
}
|
|
422
|
+
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
|
423
|
+
t.Fatalf("unknown identity created local space records: %v", err)
|
|
424
|
+
}
|
|
425
|
+
if _, err := execute("get-credit-balance"); err != nil {
|
|
426
|
+
t.Fatal("ordinary adapter requests must remain available", err)
|
|
427
|
+
}
|
|
428
|
+
// Metadata lookup failure must not block an explicitly scoped API request.
|
|
429
|
+
cfg.AccessKey = ""
|
|
430
|
+
runner.Auth = spaceUnavailableAccount{}
|
|
431
|
+
if _, err := execute("get-credit-balance", "--team-id", "12"); err != nil {
|
|
432
|
+
t.Fatal("local metadata failure blocked adapter authentication", err)
|
|
433
|
+
}
|
|
434
|
+
if requests[len(requests)-1] != config.GetCreditBalancePath+":12" {
|
|
435
|
+
t.Fatal("metadata failure dropped explicit space", requests)
|
|
436
|
+
}
|
|
437
|
+
if _, err := execute("get-credit-balance"); err == nil {
|
|
438
|
+
t.Fatal("implicit space silently ignored metadata failure")
|
|
439
|
+
}
|
|
440
|
+
// With a known matching account, the login-command switch must not suppress
|
|
441
|
+
// saved defaults or their use by business requests.
|
|
442
|
+
cfg.AccessKey = "adapter-key"
|
|
443
|
+
runner.Auth = auth.NewManager(cfg, auth.WithCredentialStore(&marketingCredentialStore{credential: &auth.Credential{UID: "adapter-user", AccessKey: "adapter-key"}}))
|
|
444
|
+
if _, err := execute("space", "use", "12"); err != nil {
|
|
445
|
+
t.Fatal(err)
|
|
446
|
+
}
|
|
447
|
+
out, err = execute("space", "list")
|
|
448
|
+
if err != nil {
|
|
449
|
+
t.Fatal(err)
|
|
450
|
+
}
|
|
451
|
+
var result struct {
|
|
452
|
+
Current *space.Selection `json:"current"`
|
|
453
|
+
}
|
|
454
|
+
if err := json.Unmarshal([]byte(out), &result); err != nil || result.Current == nil || result.Current.TeamID != "12" {
|
|
455
|
+
t.Fatalf("space list = %s, %v", out, err)
|
|
456
|
+
}
|
|
457
|
+
if _, err := execute("get-credit-balance"); err != nil {
|
|
458
|
+
t.Fatal(err)
|
|
459
|
+
}
|
|
460
|
+
if requests[len(requests)-1] != config.GetCreditBalancePath+":12" {
|
|
461
|
+
t.Fatal("saved default was ignored", requests)
|
|
462
|
+
}
|
|
463
|
+
if _, err := execute("get-credit-balance", "--team-id", "0"); err != nil {
|
|
464
|
+
t.Fatal(err)
|
|
465
|
+
}
|
|
466
|
+
if requests[len(requests)-1] != config.GetCreditBalancePath+":" {
|
|
467
|
+
t.Fatal("personal override was ignored", requests)
|
|
468
|
+
}
|
|
469
|
+
}
|
package/cmd/upload_file_test.go
CHANGED
|
@@ -2,6 +2,7 @@ package cmd
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
|
5
|
+
"encoding/json"
|
|
5
6
|
"io"
|
|
6
7
|
"net/http"
|
|
7
8
|
"net/http/httptest"
|
|
@@ -77,7 +78,8 @@ func TestUploadFileMediaContract(t *testing.T) {
|
|
|
77
78
|
if err := root.Execute(); err != nil {
|
|
78
79
|
t.Fatal(err)
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
var output map[string]string
|
|
82
|
+
if json.Unmarshal(stdout.Bytes(), &output) != nil || output["asset_id"] != "asset_original" || output["team_id"] != "0" || calls != 1 {
|
|
81
83
|
t.Fatalf("stdout = %q, requests = %d", stdout.String(), calls)
|
|
82
84
|
}
|
|
83
85
|
})
|