@pippit-dev/cli 1.0.48 → 1.0.49
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 +1 -1
- package/checksums.txt +6 -6
- package/cmd/generate_video/query_result.go +1 -14
- package/cmd/generate_video_test.go +73 -2
- package/dist/checksums.txt +6 -6
- package/internal/generate_video/query_result.go +80 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -455,7 +455,7 @@ pippit-tool-cli query-result \
|
|
|
455
455
|
--download-dir "./output"
|
|
456
456
|
```
|
|
457
457
|
|
|
458
|
-
`query-result` 会查询指定 Run 并输出 JSON。Run 成功完成后下载视频和图片产物,`completed=true`,`videos` 和 `images` 中各包含 `download_url` 和 `output_path`;图片扩展名取自产物 `metadata.format`,缺省时兜底 `.png
|
|
458
|
+
`query-result` 会查询指定 Run 并输出 JSON。Run 成功完成后下载视频和图片产物,`completed=true`、`status="succeeded"`,`videos` 和 `images` 中各包含 `download_url` 和 `output_path`;图片扩展名取自产物 `metadata.format`,缺省时兜底 `.png`。`status` 是机读状态,共 5 种取值:`running`(运行中,对应服务端已提交、处理中、生成中、程序中断等继续轮询的状态)、`succeeded`(成功)、`failed`(失败并填充 `error_message`)、`canceled`(已取消)、`unknown`(未指定、等待补充输入、人工交互中断等无法自动判定的状态)。`completed` 只表示是否到终态(成功、失败、已取消时为 true),判断成功失败请使用 `status`;查询或下载本身失败时 `status="unknown"`、`completed=false`,具体原因见 `error_message`。
|
|
459
459
|
|
|
460
460
|
## Adapter 接入边界
|
|
461
461
|
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
5b8a5628d568cef45b662d2e1d9a67da57781cbb4b6fc0d3caa7decafc775a39 pippit-tool-cli-1.0.49-darwin-amd64.tar.gz
|
|
2
|
+
c2c24abe1d53354645e9e4b65c8bdcf22dad8e563e04fbb1fa9ec4599aced2f9 pippit-tool-cli-1.0.49-darwin-arm64.tar.gz
|
|
3
|
+
9e8342eb4142dc874ca0a812e7e453b09db7be24043381149a7b1df2df764496 pippit-tool-cli-1.0.49-linux-amd64.tar.gz
|
|
4
|
+
cccd6a1d4d24e6a5a891dfd0eeb7aea02e5d0fc5a6cb01e936aa1de947a1b5d4 pippit-tool-cli-1.0.49-linux-arm64.tar.gz
|
|
5
|
+
b827be83509691b5509264553fc7aee52931b21524210957c86dad5bf90c0fb0 pippit-tool-cli-1.0.49-windows-amd64.zip
|
|
6
|
+
1b8b36a8ba738cfbbc42ed6418b54c8d0ee4c57b3de0d35afddcf79d54d8dd20 pippit-tool-cli-1.0.49-windows-arm64.zip
|
|
@@ -26,7 +26,7 @@ func NewQueryResultCommand(stdout, stderr io.Writer, runner *common.Runner) *cob
|
|
|
26
26
|
"run_id": strings.TrimSpace(opts.RunID),
|
|
27
27
|
"download_dir": strings.TrimSpace(opts.DownloadDir),
|
|
28
28
|
})
|
|
29
|
-
result =
|
|
29
|
+
result = internalgen.NewQueryResultError(err, opts)
|
|
30
30
|
}
|
|
31
31
|
encoder := json.NewEncoder(stdout)
|
|
32
32
|
encoder.SetIndent("", " ")
|
|
@@ -40,16 +40,3 @@ func NewQueryResultCommand(stdout, stderr io.Writer, runner *common.Runner) *cob
|
|
|
40
40
|
cmd.Flags().StringVar(&opts.DownloadDir, "download-dir", "", "directory to download completed videos or images into")
|
|
41
41
|
return cmd
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
func queryResultFromError(err error, opts *internalgen.QueryResultOptions) *internalgen.QueryResultResult {
|
|
45
|
-
result := &internalgen.QueryResultResult{
|
|
46
|
-
ErrorMessage: err.Error(),
|
|
47
|
-
Videos: []internalgen.QueryResultVideo{},
|
|
48
|
-
Images: []internalgen.QueryResultImage{},
|
|
49
|
-
}
|
|
50
|
-
if opts != nil {
|
|
51
|
-
result.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
52
|
-
result.RunID = strings.TrimSpace(opts.RunID)
|
|
53
|
-
}
|
|
54
|
-
return result
|
|
55
|
-
}
|
|
@@ -2,6 +2,7 @@ package cmd
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
|
5
|
+
"fmt"
|
|
5
6
|
"io"
|
|
6
7
|
"net/http"
|
|
7
8
|
"net/http/httptest"
|
|
@@ -496,6 +497,9 @@ func TestQueryResultDownloadsCompletedVideo(t *testing.T) {
|
|
|
496
497
|
if got["completed"] != true {
|
|
497
498
|
t.Fatalf("completed = %v, want true", got["completed"])
|
|
498
499
|
}
|
|
500
|
+
if got["status"] != "succeeded" {
|
|
501
|
+
t.Fatalf("status = %v, want succeeded", got["status"])
|
|
502
|
+
}
|
|
499
503
|
if got["thread_id"] != "thread_123" || got["run_id"] != "run_456" {
|
|
500
504
|
t.Fatalf("ids = (%v, %v), want thread/run ids", got["thread_id"], got["run_id"])
|
|
501
505
|
}
|
|
@@ -600,6 +604,9 @@ func TestQueryResultIgnoresVideoDataWithoutVideoSubType(t *testing.T) {
|
|
|
600
604
|
if got["completed"] != false {
|
|
601
605
|
t.Fatalf("completed = %v, want false", got["completed"])
|
|
602
606
|
}
|
|
607
|
+
if got["status"] != "unknown" {
|
|
608
|
+
t.Fatalf("status = %v, want unknown", got["status"])
|
|
609
|
+
}
|
|
603
610
|
if got["thread_id"] != "thread_123" || got["run_id"] != "run_456" {
|
|
604
611
|
t.Fatalf("ids = (%v, %v), want thread/run ids", got["thread_id"], got["run_id"])
|
|
605
612
|
}
|
|
@@ -628,6 +635,9 @@ func TestQueryResultValidationErrorReturnsJSON(t *testing.T) {
|
|
|
628
635
|
if got["completed"] != false {
|
|
629
636
|
t.Fatalf("completed = %v, want false", got["completed"])
|
|
630
637
|
}
|
|
638
|
+
if got["status"] != "unknown" {
|
|
639
|
+
t.Fatalf("status = %v, want unknown", got["status"])
|
|
640
|
+
}
|
|
631
641
|
if got["thread_id"] != "" || got["run_id"] != "run_456" {
|
|
632
642
|
t.Fatalf("ids = (%v, %v), want empty thread_id and run_id", got["thread_id"], got["run_id"])
|
|
633
643
|
}
|
|
@@ -664,12 +674,15 @@ func TestQueryResultGetThreadBusinessErrorReturnsErrorMessage(t *testing.T) {
|
|
|
664
674
|
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
665
675
|
}
|
|
666
676
|
got := decodeJSON(t, stdout.Bytes())
|
|
667
|
-
if got["completed"] !=
|
|
668
|
-
t.Fatalf("completed = %v, want
|
|
677
|
+
if got["completed"] != false {
|
|
678
|
+
t.Fatalf("completed = %v, want false", got["completed"])
|
|
669
679
|
}
|
|
670
680
|
if got["thread_id"] != "thread_123" || got["run_id"] != "run_456" {
|
|
671
681
|
t.Fatalf("ids = (%v, %v), want thread/run ids", got["thread_id"], got["run_id"])
|
|
672
682
|
}
|
|
683
|
+
if got["status"] != "unknown" {
|
|
684
|
+
t.Fatalf("status = %v, want unknown", got["status"])
|
|
685
|
+
}
|
|
673
686
|
if got["error_message"] != "创作失败:暂时无法生成 log_id=log_456" {
|
|
674
687
|
t.Fatalf("error_message = %v, want get_thread business error", got["error_message"])
|
|
675
688
|
}
|
|
@@ -706,6 +719,9 @@ func TestQueryResultFailedReturnsErrorMessage(t *testing.T) {
|
|
|
706
719
|
if got["completed"] != true {
|
|
707
720
|
t.Fatalf("completed = %v, want true", got["completed"])
|
|
708
721
|
}
|
|
722
|
+
if got["status"] != "failed" {
|
|
723
|
+
t.Fatalf("status = %v, want failed", got["status"])
|
|
724
|
+
}
|
|
709
725
|
if _, ok := got["state"]; ok {
|
|
710
726
|
t.Fatalf("state should not be returned: %#v", got)
|
|
711
727
|
}
|
|
@@ -745,6 +761,9 @@ func TestQueryResultPendingDoesNotDownload(t *testing.T) {
|
|
|
745
761
|
if got["completed"] != false {
|
|
746
762
|
t.Fatalf("completed = %v, want false", got["completed"])
|
|
747
763
|
}
|
|
764
|
+
if got["status"] != "running" {
|
|
765
|
+
t.Fatalf("status = %v, want running", got["status"])
|
|
766
|
+
}
|
|
748
767
|
if _, ok := got["state"]; ok {
|
|
749
768
|
t.Fatalf("state should not be returned: %#v", got)
|
|
750
769
|
}
|
|
@@ -757,6 +776,58 @@ func TestQueryResultPendingDoesNotDownload(t *testing.T) {
|
|
|
757
776
|
}
|
|
758
777
|
}
|
|
759
778
|
|
|
779
|
+
func TestQueryResultRunStateMapping(t *testing.T) {
|
|
780
|
+
// state=3 needs artifacts and is covered by the dedicated success tests;
|
|
781
|
+
// every other state must map deterministically, including unknown values.
|
|
782
|
+
tests := []struct {
|
|
783
|
+
state int
|
|
784
|
+
wantStatus string
|
|
785
|
+
wantCompleted bool
|
|
786
|
+
}{
|
|
787
|
+
{0, "unknown", false},
|
|
788
|
+
{1, "running", false},
|
|
789
|
+
{2, "running", false},
|
|
790
|
+
{4, "failed", true},
|
|
791
|
+
{5, "canceled", true},
|
|
792
|
+
{6, "unknown", false},
|
|
793
|
+
{7, "running", false},
|
|
794
|
+
{8, "running", false},
|
|
795
|
+
{9, "unknown", false},
|
|
796
|
+
{99, "unknown", false},
|
|
797
|
+
}
|
|
798
|
+
for _, tt := range tests {
|
|
799
|
+
t.Run(fmt.Sprintf("state=%d", tt.state), func(t *testing.T) {
|
|
800
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
801
|
+
if r.URL.Path != "/api/biz/v1/skill/get_thread" {
|
|
802
|
+
t.Fatalf("unexpected path %s", r.URL.Path)
|
|
803
|
+
}
|
|
804
|
+
payload := fmt.Sprintf(`{"ret":"0","data":{"thread":{"thread_id":"thread_123","run_list":[{"run_id":"run_456","state":%d}]}}}`, tt.state)
|
|
805
|
+
_, _ = w.Write([]byte(payload))
|
|
806
|
+
}))
|
|
807
|
+
defer server.Close()
|
|
808
|
+
|
|
809
|
+
var stdout, stderr bytes.Buffer
|
|
810
|
+
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
811
|
+
root.SetArgs([]string{
|
|
812
|
+
"query-result",
|
|
813
|
+
"--thread-id", "thread_123",
|
|
814
|
+
"--run-id", "run_456",
|
|
815
|
+
"--download-dir", t.TempDir(),
|
|
816
|
+
})
|
|
817
|
+
if err := root.Execute(); err != nil {
|
|
818
|
+
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
819
|
+
}
|
|
820
|
+
got := decodeJSON(t, stdout.Bytes())
|
|
821
|
+
if got["status"] != tt.wantStatus {
|
|
822
|
+
t.Fatalf("status = %v, want %s", got["status"], tt.wantStatus)
|
|
823
|
+
}
|
|
824
|
+
if got["completed"] != tt.wantCompleted {
|
|
825
|
+
t.Fatalf("completed = %v, want %v", got["completed"], tt.wantCompleted)
|
|
826
|
+
}
|
|
827
|
+
})
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
760
831
|
func serverURL(r *http.Request) string {
|
|
761
832
|
return "http://" + r.Host
|
|
762
833
|
}
|
package/dist/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
5b8a5628d568cef45b662d2e1d9a67da57781cbb4b6fc0d3caa7decafc775a39 pippit-tool-cli-1.0.49-darwin-amd64.tar.gz
|
|
2
|
+
c2c24abe1d53354645e9e4b65c8bdcf22dad8e563e04fbb1fa9ec4599aced2f9 pippit-tool-cli-1.0.49-darwin-arm64.tar.gz
|
|
3
|
+
9e8342eb4142dc874ca0a812e7e453b09db7be24043381149a7b1df2df764496 pippit-tool-cli-1.0.49-linux-amd64.tar.gz
|
|
4
|
+
cccd6a1d4d24e6a5a891dfd0eeb7aea02e5d0fc5a6cb01e936aa1de947a1b5d4 pippit-tool-cli-1.0.49-linux-arm64.tar.gz
|
|
5
|
+
b827be83509691b5509264553fc7aee52931b21524210957c86dad5bf90c0fb0 pippit-tool-cli-1.0.49-windows-amd64.zip
|
|
6
|
+
1b8b36a8ba738cfbbc42ed6418b54c8d0ee4c57b3de0d35afddcf79d54d8dd20 pippit-tool-cli-1.0.49-windows-arm64.zip
|
|
@@ -14,10 +14,54 @@ import (
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
const (
|
|
17
|
-
successRunState
|
|
18
|
-
failedRunState
|
|
17
|
+
successRunState = 3
|
|
18
|
+
failedRunState = 4
|
|
19
|
+
canceledRunState = 5
|
|
19
20
|
)
|
|
20
21
|
|
|
22
|
+
// Query-result status values. "completed" stays a terminal flag; "status"
|
|
23
|
+
// carries the machine-readable result so integrators need not parse text.
|
|
24
|
+
const (
|
|
25
|
+
statusRunning = "running"
|
|
26
|
+
statusSucceeded = "succeeded"
|
|
27
|
+
statusFailed = "failed"
|
|
28
|
+
statusCanceled = "canceled"
|
|
29
|
+
statusUnknown = "unknown"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// runStateStatus maps the server-side run state (0-9) to the user-facing
|
|
33
|
+
// status. Any unmapped value falls back to unknown instead of being guessed.
|
|
34
|
+
var runStateStatus = map[int]string{
|
|
35
|
+
0: statusUnknown, // Unspecified
|
|
36
|
+
1: statusRunning, // Submitted
|
|
37
|
+
2: statusRunning, // Working
|
|
38
|
+
3: statusSucceeded,
|
|
39
|
+
4: statusFailed,
|
|
40
|
+
5: statusCanceled,
|
|
41
|
+
6: statusUnknown, // InputRequired
|
|
42
|
+
7: statusRunning, // Generating
|
|
43
|
+
8: statusRunning, // Interrupt
|
|
44
|
+
9: statusUnknown, // HITL_Interrupt
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var terminalRunStates = map[int]struct{}{
|
|
48
|
+
successRunState: {},
|
|
49
|
+
failedRunState: {},
|
|
50
|
+
canceledRunState: {},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func statusForRunState(state int) string {
|
|
54
|
+
if s, ok := runStateStatus[state]; ok {
|
|
55
|
+
return s
|
|
56
|
+
}
|
|
57
|
+
return statusUnknown
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func isTerminalRunState(state int) bool {
|
|
61
|
+
_, ok := terminalRunStates[state]
|
|
62
|
+
return ok
|
|
63
|
+
}
|
|
64
|
+
|
|
21
65
|
// QueryResultOptions is the command-facing request shape for query-result.
|
|
22
66
|
type QueryResultOptions struct {
|
|
23
67
|
ThreadID string
|
|
@@ -27,6 +71,7 @@ type QueryResultOptions struct {
|
|
|
27
71
|
|
|
28
72
|
// QueryResultResult describes the user-facing query-result outcome.
|
|
29
73
|
type QueryResultResult struct {
|
|
74
|
+
Status string `json:"status,omitempty"`
|
|
30
75
|
Completed bool `json:"completed"`
|
|
31
76
|
ThreadID string `json:"thread_id"`
|
|
32
77
|
RunID string `json:"run_id"`
|
|
@@ -112,10 +157,7 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
112
157
|
RunID: opts.RunID,
|
|
113
158
|
}, runner)
|
|
114
159
|
if err != nil {
|
|
115
|
-
|
|
116
|
-
return result, nil
|
|
117
|
-
}
|
|
118
|
-
return nil, fmt.Errorf("查询失败:%w", err)
|
|
160
|
+
return nil, queryResultQueryError(err)
|
|
119
161
|
}
|
|
120
162
|
|
|
121
163
|
thread, err := parseQueryThread(threadResult)
|
|
@@ -129,7 +171,8 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
129
171
|
}
|
|
130
172
|
if run.State != successRunState {
|
|
131
173
|
result := &QueryResultResult{
|
|
132
|
-
|
|
174
|
+
Status: statusForRunState(run.State),
|
|
175
|
+
Completed: isTerminalRunState(run.State),
|
|
133
176
|
ThreadID: firstNonEmpty(thread.ThreadID, opts.ThreadID),
|
|
134
177
|
RunID: opts.RunID,
|
|
135
178
|
Videos: []QueryResultVideo{},
|
|
@@ -209,6 +252,7 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
209
252
|
}
|
|
210
253
|
|
|
211
254
|
return &QueryResultResult{
|
|
255
|
+
Status: statusSucceeded,
|
|
212
256
|
Completed: true,
|
|
213
257
|
ThreadID: firstNonEmpty(thread.ThreadID, opts.ThreadID),
|
|
214
258
|
RunID: opts.RunID,
|
|
@@ -217,26 +261,39 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
217
261
|
}, nil
|
|
218
262
|
}
|
|
219
263
|
|
|
220
|
-
|
|
264
|
+
// queryResultQueryError converts a get_thread failure into a Go error while
|
|
265
|
+
// keeping a clean, customer-facing message. The command layer maps it to
|
|
266
|
+
// status=unknown; the technical envelope stays in the local error log.
|
|
267
|
+
func queryResultQueryError(err error) error {
|
|
221
268
|
var logErr *common.LogIDError
|
|
222
|
-
if
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
269
|
+
if errors.As(err, &logErr) {
|
|
270
|
+
message := getThreadBusinessErrorMessage(logErr.Message)
|
|
271
|
+
if message == "" {
|
|
272
|
+
message = "查询失败"
|
|
273
|
+
}
|
|
274
|
+
if logID := logErr.LogID(); logID != "" {
|
|
275
|
+
return fmt.Errorf("%s log_id=%s", message, logID)
|
|
276
|
+
}
|
|
277
|
+
return fmt.Errorf("%s", message)
|
|
231
278
|
}
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
279
|
+
return fmt.Errorf("查询失败:%w", err)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// NewQueryResultError builds the user-facing result for a failed query. A
|
|
283
|
+
// failed call never produces a run status, so it reports unknown rather than
|
|
284
|
+
// guessing; the concrete cause stays in error_message.
|
|
285
|
+
func NewQueryResultError(err error, opts *QueryResultOptions) *QueryResultResult {
|
|
286
|
+
result := &QueryResultResult{
|
|
287
|
+
Status: statusUnknown,
|
|
288
|
+
ErrorMessage: err.Error(),
|
|
237
289
|
Videos: []QueryResultVideo{},
|
|
238
290
|
Images: []QueryResultImage{},
|
|
239
|
-
}
|
|
291
|
+
}
|
|
292
|
+
if opts != nil {
|
|
293
|
+
result.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
294
|
+
result.RunID = strings.TrimSpace(opts.RunID)
|
|
295
|
+
}
|
|
296
|
+
return result
|
|
240
297
|
}
|
|
241
298
|
|
|
242
299
|
func getThreadBusinessErrorMessage(message string) string {
|