@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 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`。Run 失败也视为终态,`completed=true` 且填充 `error_message`;Run 未到终态时 `completed=false`。
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
- 718067da81039c982cdcb9579f50a06a83bbc7c7dd599ab447b50e8b8e2b13ac pippit-tool-cli-1.0.48-darwin-amd64.tar.gz
2
- d0aa4e2272c8369fb615b82bbb390a3e575e3b3df39dffbb5d38c6fc8e7d67c3 pippit-tool-cli-1.0.48-darwin-arm64.tar.gz
3
- 838f221cef34481f042a2fec32da0f015d5c61151f43eeb3460ceb94c05601fd pippit-tool-cli-1.0.48-linux-amd64.tar.gz
4
- 1ff3d84ffe6a04bc6c5ed92d9e784308d8dc16fffc88037faa21665d9a74106c pippit-tool-cli-1.0.48-linux-arm64.tar.gz
5
- 04400b90e115ba0d9d8e0d6917e8b69cfe980fa5601bd5c9bdb2625135036d5e pippit-tool-cli-1.0.48-windows-amd64.zip
6
- e6a2f024b93abcc546007fe1c5e2fcaebed11ab64123943c30dda9f47f8c4b63 pippit-tool-cli-1.0.48-windows-arm64.zip
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 = queryResultFromError(err, opts)
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"] != true {
668
- t.Fatalf("completed = %v, want true", got["completed"])
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
  }
@@ -1,6 +1,6 @@
1
- 718067da81039c982cdcb9579f50a06a83bbc7c7dd599ab447b50e8b8e2b13ac pippit-tool-cli-1.0.48-darwin-amd64.tar.gz
2
- d0aa4e2272c8369fb615b82bbb390a3e575e3b3df39dffbb5d38c6fc8e7d67c3 pippit-tool-cli-1.0.48-darwin-arm64.tar.gz
3
- 838f221cef34481f042a2fec32da0f015d5c61151f43eeb3460ceb94c05601fd pippit-tool-cli-1.0.48-linux-amd64.tar.gz
4
- 1ff3d84ffe6a04bc6c5ed92d9e784308d8dc16fffc88037faa21665d9a74106c pippit-tool-cli-1.0.48-linux-arm64.tar.gz
5
- 04400b90e115ba0d9d8e0d6917e8b69cfe980fa5601bd5c9bdb2625135036d5e pippit-tool-cli-1.0.48-windows-amd64.zip
6
- e6a2f024b93abcc546007fe1c5e2fcaebed11ab64123943c30dda9f47f8c4b63 pippit-tool-cli-1.0.48-windows-arm64.zip
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 = 3
18
- failedRunState = 4
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
- if result, ok := queryResultFromGetThreadBusinessError(err, opts); ok {
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
- Completed: run.State == failedRunState,
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
- func queryResultFromGetThreadBusinessError(err error, opts *QueryResultOptions) (*QueryResultResult, bool) {
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 !errors.As(err, &logErr) {
223
- return nil, false
224
- }
225
- message := getThreadBusinessErrorMessage(logErr.Message)
226
- if message == "" {
227
- message = "查询失败"
228
- }
229
- if logID := logErr.LogID(); logID != "" {
230
- message = fmt.Sprintf("%s log_id=%s", message, logID)
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 &QueryResultResult{
233
- Completed: true,
234
- ThreadID: opts.ThreadID,
235
- RunID: opts.RunID,
236
- ErrorMessage: message,
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
- }, true
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pippit-dev/cli",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "Pippit CLI",
5
5
  "bin": {
6
6
  "pippit-tool-cli": "scripts/run.js"