From f094a17c58e9293e4504c8553626ed785ea08bee Mon Sep 17 00:00:00 2001 From: Tristan Cartledge Date: Fri, 23 Oct 2026 10:41:56 +1000 Subject: [PATCH 1/3] fix(cli): show request progress while streaming-capable operations wait for a response --- .changesets/1792716065-457f42af.yaml | 11 ++++ .../internal/output/streaming.go.stmpl | 17 ++++++- templates/templates/cli/opcmd.go.stmpl | 16 +++++- .../cli/request_progress_test.go.stmpl | 51 +++++++++++++++++++ zSDKs/sdk-cli/.speakeasy/gen.lock | 12 ++--- zSDKs/sdk-cli/internal/cli/chat.go | 4 +- zSDKs/sdk-cli/internal/cli/getasset.go | 4 +- zSDKs/sdk-cli/internal/cli/renderasset.go | 4 +- .../cli/streammetadata/streammetadata.go | 4 +- zSDKs/sdk-cli/internal/output/streaming.go | 17 ++++++- zSDKs/sdk-cli/tests/request_progress_test.go | 51 +++++++++++++++++++ 11 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 .changesets/1792716065-457f42af.yaml diff --git a/.changesets/1792716065-457f42af.yaml b/.changesets/1792716065-457f42af.yaml new file mode 100644 index 000000000..ddc65cce8 --- /dev/null +++ b/.changesets/1792716065-457f42af.yaml @@ -0,0 +1,11 @@ +id: 1792716065-457f42af +features: + - core + - serverEvents +targets: + - cli +type: fix +bump: patch +description: show the request progress indicator while streaming-capable operations wait for their first response +author: TristanSpeakEasy +date: "2026-10-23" diff --git a/templates/templates/cli/auxiliary/internal/output/streaming.go.stmpl b/templates/templates/cli/auxiliary/internal/output/streaming.go.stmpl index 24b351243..30d43f0a3 100644 --- a/templates/templates/cli/auxiliary/internal/output/streaming.go.stmpl +++ b/templates/templates/cli/auxiliary/internal/output/streaming.go.stmpl @@ -21,7 +21,17 @@ import ( // "Object"). The function uses reflection to call Next()/Value()/Err()/Close() // on the stream field, handling both EventStream (Value() *T) and JsonLStream // (Value() (T, error)) signatures. -func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) error { +// stopProgress, when given, runs before anything is written: on the first +// event, at end of stream, or before falling back to a single JSON result. +func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string, stopProgress ...func()) error { + stop := func() { + for _, fn := range stopProgress { + if fn != nil { + fn() + } + } + } + defer stop() if flagutil.DidDryRunRequest(cmd) { return nil } @@ -43,16 +53,19 @@ func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) e } if v.Kind() != reflect.Struct { // Fallback: not a struct envelope — output as regular result + stop() return Result(cmd, res) } streamVal := v.FieldByName(streamFieldName) if !streamVal.IsValid() { // Fallback: stream field not found — output as regular result + stop() return Result(cmd, res) } // Guard IsNil: only call on nillable kinds. Fall back to Result() for // mixed-content operations where non-stream content may still exist. if isNillable(streamVal) && streamVal.IsNil() { + stop() return Result(cmd, res) } @@ -64,6 +77,7 @@ func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) e rawMethod := streamVal.MethodByName("RawValue") if !nextMethod.IsValid() || !valueMethod.IsValid() { // Fallback: not a streaming type — output as regular result + stop() return Result(cmd, res) } @@ -105,6 +119,7 @@ func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) e // Call Next() bool nextResults := nextMethod.Call(nil) + stop() if !nextResults[0].Bool() { break } diff --git a/templates/templates/cli/opcmd.go.stmpl b/templates/templates/cli/opcmd.go.stmpl index 374fd83d4..0aba7e840 100644 --- a/templates/templates/cli/opcmd.go.stmpl +++ b/templates/templates/cli/opcmd.go.stmpl @@ -342,11 +342,17 @@ func {{$runFuncName}}(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { + {{- if operationHasDeferredBody $op}} + stopProgress := func() {} + {{- else}} + stopProgress := output.StartRequestProgress(cmd) + {{- end}} res, err := s{{.Local.SubSDKAccessor}}.{{sanitizeMethodName $op}}({{templateSDKCallArgs $op $reqObjName}}) if err != nil { + stopProgress() return output.Error(cmd, err) } - return output.StreamResult(cmd, res, "{{getStreamingFieldName $op}}") + return output.StreamResult(cmd, res, "{{getStreamingFieldName $op}}", stopProgress) } {{- end}} {{- if not (operationHasDeferredBody $op)}} @@ -496,11 +502,17 @@ func {{$executeFuncName}}(cmd *cobra.Command, args []string, asyncIntent bool) ( {{- end}} {{- if hasStreamingResponse $op}} if !client.IsDryRun(cmd) && !asyncIntent { + {{- if operationHasDeferredBody $op}} + stopProgress := func() {} + {{- else}} + stopProgress := output.StartRequestProgress(cmd) + {{- end}} res, err := s{{.Local.SubSDKAccessor}}.{{sanitizeMethodName $op}}({{templateSDKCallArgs $op $reqObjName}}) if err != nil { + stopProgress() return nil, output.Error(cmd, err) } - if err := output.StreamResult(cmd, res, "{{getStreamingFieldName $op}}"); err != nil { + if err := output.StreamResult(cmd, res, "{{getStreamingFieldName $op}}", stopProgress); err != nil { return nil, err } return nil, nil diff --git a/templates/templates/cli/request_progress_test.go.stmpl b/templates/templates/cli/request_progress_test.go.stmpl index 43ff3f028..d76723b96 100644 --- a/templates/templates/cli/request_progress_test.go.stmpl +++ b/templates/templates/cli/request_progress_test.go.stmpl @@ -124,6 +124,57 @@ func TestRequestProgressContextCancellationClears(t *testing.T) { stop() } +type progressTestStream struct { + items []string + next int + delay time.Duration +} + +func (s *progressTestStream) Next() bool { + if s.next == 0 { + time.Sleep(s.delay) + } + if s.next >= len(s.items) { + return false + } + s.next++ + return true +} + +func (s *progressTestStream) Value() *string { return &s.items[s.next-1] } + +func (s *progressTestStream) Close() error { return nil } + +func TestRequestProgressStreamClearsBeforeFirstEvent(t *testing.T) { + cmd, master, _ := newProgressTestCommand(t, context.Background()) + stop := output.StartRequestProgress(cmd) + stream := &progressTestStream{items: []string{"first-event"}, delay: 300 * time.Millisecond} + require.NoError(t, output.StreamResult(cmd, struct{ Stream *progressTestStream }{stream}, "Stream", stop)) + + got := readProgressOutput(master, 100*time.Millisecond) + require.Contains(t, got, "fixture request") + event := strings.Index(got, "first-event") + require.GreaterOrEqual(t, event, 0) + require.Contains(t, got[:event], "\r\x1b[2K") + require.NotContains(t, got[event:], "fixture request") +} + +func TestRequestProgressStreamFallbackClearsBeforeResult(t *testing.T) { + cmd, master, _ := newProgressTestCommand(t, context.Background()) + stop := output.StartRequestProgress(cmd) + time.Sleep(300 * time.Millisecond) + require.NoError(t, output.StreamResult(cmd, struct { + Stream *progressTestStream + Value string + }{Value: "single-result"}, "Stream", stop)) + + got := readProgressOutput(master, 100*time.Millisecond) + require.Contains(t, got, "fixture request") + result := strings.Index(got, "single-result") + require.GreaterOrEqual(t, result, 0) + require.Contains(t, got[:result], "\r\x1b[2K") +} + func TestRequestProgressSuppressionFlags(t *testing.T) { for name, set := range map[string]func(*cobra.Command) error{ diff --git a/zSDKs/sdk-cli/.speakeasy/gen.lock b/zSDKs/sdk-cli/.speakeasy/gen.lock index 5b420f96d..8744f4b67 100644 --- a/zSDKs/sdk-cli/.speakeasy/gen.lock +++ b/zSDKs/sdk-cli/.speakeasy/gen.lock @@ -64,7 +64,7 @@ trackedFiles: internal/cli/binaryandstringupload.go: last_write_checksum: sha1:20d8028b7618e73dcf1763372f9145ea325b32b0 internal/cli/chat.go: - last_write_checksum: sha1:9a9077f9bd0eb3ef19d36d461243215c50f3442b + last_write_checksum: sha1:5d43b3ba3a46273cedf95a25e1466cba2d7a1d35 internal/cli/configure.go: last_write_checksum: sha1:91f681e0c0bb3ec3fccfe48939b8bb0b71d89619 internal/cli/createuser.go: @@ -74,7 +74,7 @@ trackedFiles: internal/cli/deleteuser.go: last_write_checksum: sha1:e6876685816745a290da3903a03a3afe8f0812e7 internal/cli/getasset.go: - last_write_checksum: sha1:5a5d0784d2880dd248401b62b0579e6b21ff7c04 + last_write_checksum: sha1:a8f08c2f09245d9b95c0ace79df1048ad570584f internal/cli/getbinarydefaultresponse.go: last_write_checksum: sha1:75df919b8b943ca85ed7299d14cd2c5ee7cb7b56 internal/cli/getduplicateexportcollision.go: @@ -180,13 +180,13 @@ trackedFiles: internal/cli/postfile.go: last_write_checksum: sha1:f67d5ddcaa3ea921513ee6f97fe5eb8db9580aae internal/cli/renderasset.go: - last_write_checksum: sha1:c72bde224ebd3246cce2e14e5f04b1e3ed854ba2 + last_write_checksum: sha1:53d22b867c45e2a0b1e9a671d1f6b3d4dc439a5a internal/cli/root.go: last_write_checksum: sha1:48f538b9ead4f7b896c5064555a05f04987b0e1c internal/cli/streammetadata/root.go: last_write_checksum: sha1:42a01737002d2daef8a33be267f7060659fdac65 internal/cli/streammetadata/streammetadata.go: - last_write_checksum: sha1:4c706900a7fd6d2bfaa971da5d46a118b192e53f + last_write_checksum: sha1:9278f96c6eec411ec6b740105d5f45fc8120fcec internal/cli/tag1/auth.go: last_write_checksum: sha1:f25ba9acd8909779428dd8bc0a7eeab5f5a292a4 internal/cli/tag1/deprecated1.go: @@ -294,7 +294,7 @@ trackedFiles: internal/output/progress.go: last_write_checksum: sha1:f8b839f9394602fe9c30feb3d7155af6235663be internal/output/streaming.go: - last_write_checksum: sha1:bafe2e29958d59888553e30527ce340600b94c94 + last_write_checksum: sha1:3ffdcd4e995640502eaf0b10086fb4d1324ae971 internal/output/streaming_test.go: last_write_checksum: sha1:9bb53612aa2a3e6d3da96f0c8f8e2ce4ba99b509 internal/output/streamproject.go: @@ -1104,7 +1104,7 @@ trackedFiles: tests/readme_examples_test.go: last_write_checksum: sha1:441c213c8049299e7b3efd8cb7033e5885610a20 tests/request_progress_test.go: - last_write_checksum: sha1:06772a2e9900b76d04fc16f62e6c86b52a29407e + last_write_checksum: sha1:fbc7850aea432536d6bb83002a062aed3884346d tests/sdk_test.go: last_write_checksum: sha1:0ac872f31dea293b708409ea99d662eafa9725b5 tests/singlebar_test.go: diff --git a/zSDKs/sdk-cli/internal/cli/chat.go b/zSDKs/sdk-cli/internal/cli/chat.go index d76c5f805..5defb88c4 100644 --- a/zSDKs/sdk-cli/internal/cli/chat.go +++ b/zSDKs/sdk-cli/internal/cli/chat.go @@ -112,11 +112,13 @@ func runChatCmd(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { + stopProgress := func() {} res, err := s.Chat(cmd.Context(), *request, sdkOpts...) if err != nil { + stopProgress() return output.Error(cmd, err) } - return output.StreamResult(cmd, res, "ChatStream") + return output.StreamResult(cmd, res, "ChatStream", stopProgress) } res, err := s.Chat(cmd.Context(), *request, sdkOpts...) if err != nil { diff --git a/zSDKs/sdk-cli/internal/cli/getasset.go b/zSDKs/sdk-cli/internal/cli/getasset.go index f5269bf94..d47ce532d 100644 --- a/zSDKs/sdk-cli/internal/cli/getasset.go +++ b/zSDKs/sdk-cli/internal/cli/getasset.go @@ -71,11 +71,13 @@ func runGetAssetCmd(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { + stopProgress := func() {} res, err := s.GetAsset(cmd.Context(), *req, sdkOpts...) if err != nil { + stopProgress() return output.Error(cmd, err) } - return output.StreamResult(cmd, res, "AssetStatusStream") + return output.StreamResult(cmd, res, "AssetStatusStream", stopProgress) } res, err := s.GetAsset(cmd.Context(), *req, sdkOpts...) if err != nil { diff --git a/zSDKs/sdk-cli/internal/cli/renderasset.go b/zSDKs/sdk-cli/internal/cli/renderasset.go index ff5be470d..eda1669cb 100644 --- a/zSDKs/sdk-cli/internal/cli/renderasset.go +++ b/zSDKs/sdk-cli/internal/cli/renderasset.go @@ -91,11 +91,13 @@ func executeRenderAssetCmd(cmd *cobra.Command, args []string, asyncIntent bool) sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) } if !client.IsDryRun(cmd) && !asyncIntent { + stopProgress := func() {} res, err := s.RenderAsset(cmd.Context(), *request, sdkOpts...) if err != nil { + stopProgress() return nil, output.Error(cmd, err) } - if err := output.StreamResult(cmd, res, "AssetStream"); err != nil { + if err := output.StreamResult(cmd, res, "AssetStream", stopProgress); err != nil { return nil, err } return nil, nil diff --git a/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go b/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go index f5dfb5fa3..fc3f6435b 100644 --- a/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go +++ b/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go @@ -54,11 +54,13 @@ func runStreamMetadataCmd(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { + stopProgress := func() {} res, err := s.StreamMetadata.StreamMetadata(cmd.Context(), sdkOpts...) if err != nil { + stopProgress() return output.Error(cmd, err) } - return output.StreamResult(cmd, res, "Object") + return output.StreamResult(cmd, res, "Object", stopProgress) } res, err := s.StreamMetadata.StreamMetadata(cmd.Context(), sdkOpts...) if err != nil { diff --git a/zSDKs/sdk-cli/internal/output/streaming.go b/zSDKs/sdk-cli/internal/output/streaming.go index a24105b4f..3ace53f12 100644 --- a/zSDKs/sdk-cli/internal/output/streaming.go +++ b/zSDKs/sdk-cli/internal/output/streaming.go @@ -22,7 +22,17 @@ import ( // "Object"). The function uses reflection to call Next()/Value()/Err()/Close() // on the stream field, handling both EventStream (Value() *T) and JsonLStream // (Value() (T, error)) signatures. -func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) error { +// stopProgress, when given, runs before anything is written: on the first +// event, at end of stream, or before falling back to a single JSON result. +func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string, stopProgress ...func()) error { + stop := func() { + for _, fn := range stopProgress { + if fn != nil { + fn() + } + } + } + defer stop() if flagutil.DidDryRunRequest(cmd) { return nil } @@ -44,16 +54,19 @@ func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) e } if v.Kind() != reflect.Struct { // Fallback: not a struct envelope — output as regular result + stop() return Result(cmd, res) } streamVal := v.FieldByName(streamFieldName) if !streamVal.IsValid() { // Fallback: stream field not found — output as regular result + stop() return Result(cmd, res) } // Guard IsNil: only call on nillable kinds. Fall back to Result() for // mixed-content operations where non-stream content may still exist. if isNillable(streamVal) && streamVal.IsNil() { + stop() return Result(cmd, res) } @@ -65,6 +78,7 @@ func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) e rawMethod := streamVal.MethodByName("RawValue") if !nextMethod.IsValid() || !valueMethod.IsValid() { // Fallback: not a streaming type — output as regular result + stop() return Result(cmd, res) } @@ -106,6 +120,7 @@ func StreamResult(cmd *cobra.Command, res interface{}, streamFieldName string) e // Call Next() bool nextResults := nextMethod.Call(nil) + stop() if !nextResults[0].Bool() { break } diff --git a/zSDKs/sdk-cli/tests/request_progress_test.go b/zSDKs/sdk-cli/tests/request_progress_test.go index 835d37ded..0b6424a49 100644 --- a/zSDKs/sdk-cli/tests/request_progress_test.go +++ b/zSDKs/sdk-cli/tests/request_progress_test.go @@ -126,6 +126,57 @@ func TestRequestProgressContextCancellationClears(t *testing.T) { stop() } +type progressTestStream struct { + items []string + next int + delay time.Duration +} + +func (s *progressTestStream) Next() bool { + if s.next == 0 { + time.Sleep(s.delay) + } + if s.next >= len(s.items) { + return false + } + s.next++ + return true +} + +func (s *progressTestStream) Value() *string { return &s.items[s.next-1] } + +func (s *progressTestStream) Close() error { return nil } + +func TestRequestProgressStreamClearsBeforeFirstEvent(t *testing.T) { + cmd, master, _ := newProgressTestCommand(t, context.Background()) + stop := output.StartRequestProgress(cmd) + stream := &progressTestStream{items: []string{"first-event"}, delay: 300 * time.Millisecond} + require.NoError(t, output.StreamResult(cmd, struct{ Stream *progressTestStream }{stream}, "Stream", stop)) + + got := readProgressOutput(master, 100*time.Millisecond) + require.Contains(t, got, "fixture request") + event := strings.Index(got, "first-event") + require.GreaterOrEqual(t, event, 0) + require.Contains(t, got[:event], "\r\x1b[2K") + require.NotContains(t, got[event:], "fixture request") +} + +func TestRequestProgressStreamFallbackClearsBeforeResult(t *testing.T) { + cmd, master, _ := newProgressTestCommand(t, context.Background()) + stop := output.StartRequestProgress(cmd) + time.Sleep(300 * time.Millisecond) + require.NoError(t, output.StreamResult(cmd, struct { + Stream *progressTestStream + Value string + }{Value: "single-result"}, "Stream", stop)) + + got := readProgressOutput(master, 100*time.Millisecond) + require.Contains(t, got, "fixture request") + result := strings.Index(got, "single-result") + require.GreaterOrEqual(t, result, 0) + require.Contains(t, got[:result], "\r\x1b[2K") +} + func TestRequestProgressSuppressionFlags(t *testing.T) { for name, set := range map[string]func(*cobra.Command) error{ From 7a5a5d2955db45446b9b6218b56f2e8f73d3b4e5 Mon Sep 17 00:00:00 2001 From: Tristan Cartledge Date: Fri, 23 Oct 2026 11:16:00 +1000 Subject: [PATCH 2/3] test(cli): only generate stream progress tests when the API has streaming operations --- templates/templates/cli/request_progress_test.go.stmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/templates/cli/request_progress_test.go.stmpl b/templates/templates/cli/request_progress_test.go.stmpl index d76723b96..c206f31ba 100644 --- a/templates/templates/cli/request_progress_test.go.stmpl +++ b/templates/templates/cli/request_progress_test.go.stmpl @@ -123,6 +123,7 @@ func TestRequestProgressContextCancellationClears(t *testing.T) { require.True(t, strings.HasSuffix(got, "\r\x1b[2K"), "cancellation must leave the line cleared") stop() } +{{- if anyOperationHasStreaming}} type progressTestStream struct { items []string @@ -174,6 +175,7 @@ func TestRequestProgressStreamFallbackClearsBeforeResult(t *testing.T) { require.GreaterOrEqual(t, result, 0) require.Contains(t, got[:result], "\r\x1b[2K") } +{{- end}} func TestRequestProgressSuppressionFlags(t *testing.T) { for name, set := range map[string]func(*cobra.Command) error{ From 4da64cb563d727f986ac9890d2f12691b06721c9 Mon Sep 17 00:00:00 2001 From: Tristan Cartledge Date: Fri, 23 Oct 2026 11:45:14 +1000 Subject: [PATCH 3/3] fix(cli): start request progress for streaming operations --- templates/templates/cli/opcmd.go.stmpl | 8 -------- templates/templates/cli/request_progress_test.go.stmpl | 4 ++-- zSDKs/sdk-cli/.speakeasy/gen.lock | 10 +++++----- zSDKs/sdk-cli/internal/cli/chat.go | 2 +- zSDKs/sdk-cli/internal/cli/getasset.go | 2 +- zSDKs/sdk-cli/internal/cli/renderasset.go | 2 +- .../internal/cli/streammetadata/streammetadata.go | 2 +- zSDKs/sdk-cli/tests/request_progress_test.go | 4 ++-- 8 files changed, 13 insertions(+), 21 deletions(-) diff --git a/templates/templates/cli/opcmd.go.stmpl b/templates/templates/cli/opcmd.go.stmpl index 0aba7e840..d8aa95bd5 100644 --- a/templates/templates/cli/opcmd.go.stmpl +++ b/templates/templates/cli/opcmd.go.stmpl @@ -342,11 +342,7 @@ func {{$runFuncName}}(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { - {{- if operationHasDeferredBody $op}} - stopProgress := func() {} - {{- else}} stopProgress := output.StartRequestProgress(cmd) - {{- end}} res, err := s{{.Local.SubSDKAccessor}}.{{sanitizeMethodName $op}}({{templateSDKCallArgs $op $reqObjName}}) if err != nil { stopProgress() @@ -502,11 +498,7 @@ func {{$executeFuncName}}(cmd *cobra.Command, args []string, asyncIntent bool) ( {{- end}} {{- if hasStreamingResponse $op}} if !client.IsDryRun(cmd) && !asyncIntent { - {{- if operationHasDeferredBody $op}} - stopProgress := func() {} - {{- else}} stopProgress := output.StartRequestProgress(cmd) - {{- end}} res, err := s{{.Local.SubSDKAccessor}}.{{sanitizeMethodName $op}}({{templateSDKCallArgs $op $reqObjName}}) if err != nil { stopProgress() diff --git a/templates/templates/cli/request_progress_test.go.stmpl b/templates/templates/cli/request_progress_test.go.stmpl index c206f31ba..8ccc6366a 100644 --- a/templates/templates/cli/request_progress_test.go.stmpl +++ b/templates/templates/cli/request_progress_test.go.stmpl @@ -156,7 +156,7 @@ func TestRequestProgressStreamClearsBeforeFirstEvent(t *testing.T) { require.Contains(t, got, "fixture request") event := strings.Index(got, "first-event") require.GreaterOrEqual(t, event, 0) - require.Contains(t, got[:event], "\r\x1b[2K") + require.True(t, strings.HasSuffix(got[:event], "\r\x1b[2K"), "progress must be cleared immediately before the first event") require.NotContains(t, got[event:], "fixture request") } @@ -173,7 +173,7 @@ func TestRequestProgressStreamFallbackClearsBeforeResult(t *testing.T) { require.Contains(t, got, "fixture request") result := strings.Index(got, "single-result") require.GreaterOrEqual(t, result, 0) - require.Contains(t, got[:result], "\r\x1b[2K") + require.True(t, strings.HasSuffix(got[:result], "\r\x1b[2K"), "progress must be cleared immediately before the result") } {{- end}} diff --git a/zSDKs/sdk-cli/.speakeasy/gen.lock b/zSDKs/sdk-cli/.speakeasy/gen.lock index 8744f4b67..a69247a6f 100644 --- a/zSDKs/sdk-cli/.speakeasy/gen.lock +++ b/zSDKs/sdk-cli/.speakeasy/gen.lock @@ -64,7 +64,7 @@ trackedFiles: internal/cli/binaryandstringupload.go: last_write_checksum: sha1:20d8028b7618e73dcf1763372f9145ea325b32b0 internal/cli/chat.go: - last_write_checksum: sha1:5d43b3ba3a46273cedf95a25e1466cba2d7a1d35 + last_write_checksum: sha1:e195b15f923b77ba9c19861e0145ba17dd896c2c internal/cli/configure.go: last_write_checksum: sha1:91f681e0c0bb3ec3fccfe48939b8bb0b71d89619 internal/cli/createuser.go: @@ -74,7 +74,7 @@ trackedFiles: internal/cli/deleteuser.go: last_write_checksum: sha1:e6876685816745a290da3903a03a3afe8f0812e7 internal/cli/getasset.go: - last_write_checksum: sha1:a8f08c2f09245d9b95c0ace79df1048ad570584f + last_write_checksum: sha1:88824e116bf23f10a35e329a86917ccbc0daf7b2 internal/cli/getbinarydefaultresponse.go: last_write_checksum: sha1:75df919b8b943ca85ed7299d14cd2c5ee7cb7b56 internal/cli/getduplicateexportcollision.go: @@ -180,13 +180,13 @@ trackedFiles: internal/cli/postfile.go: last_write_checksum: sha1:f67d5ddcaa3ea921513ee6f97fe5eb8db9580aae internal/cli/renderasset.go: - last_write_checksum: sha1:53d22b867c45e2a0b1e9a671d1f6b3d4dc439a5a + last_write_checksum: sha1:6ac1b5552a2f82c3ee4a300084f75af0972bda96 internal/cli/root.go: last_write_checksum: sha1:48f538b9ead4f7b896c5064555a05f04987b0e1c internal/cli/streammetadata/root.go: last_write_checksum: sha1:42a01737002d2daef8a33be267f7060659fdac65 internal/cli/streammetadata/streammetadata.go: - last_write_checksum: sha1:9278f96c6eec411ec6b740105d5f45fc8120fcec + last_write_checksum: sha1:43dea3efb32cb60d4b1bfd90454959678111c465 internal/cli/tag1/auth.go: last_write_checksum: sha1:f25ba9acd8909779428dd8bc0a7eeab5f5a292a4 internal/cli/tag1/deprecated1.go: @@ -1104,7 +1104,7 @@ trackedFiles: tests/readme_examples_test.go: last_write_checksum: sha1:441c213c8049299e7b3efd8cb7033e5885610a20 tests/request_progress_test.go: - last_write_checksum: sha1:fbc7850aea432536d6bb83002a062aed3884346d + last_write_checksum: sha1:9b64e6626ee244a6a36d1c1b8bb0b0d20ed48a6e tests/sdk_test.go: last_write_checksum: sha1:0ac872f31dea293b708409ea99d662eafa9725b5 tests/singlebar_test.go: diff --git a/zSDKs/sdk-cli/internal/cli/chat.go b/zSDKs/sdk-cli/internal/cli/chat.go index 5defb88c4..a5b29e497 100644 --- a/zSDKs/sdk-cli/internal/cli/chat.go +++ b/zSDKs/sdk-cli/internal/cli/chat.go @@ -112,7 +112,7 @@ func runChatCmd(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { - stopProgress := func() {} + stopProgress := output.StartRequestProgress(cmd) res, err := s.Chat(cmd.Context(), *request, sdkOpts...) if err != nil { stopProgress() diff --git a/zSDKs/sdk-cli/internal/cli/getasset.go b/zSDKs/sdk-cli/internal/cli/getasset.go index d47ce532d..4765dd4c1 100644 --- a/zSDKs/sdk-cli/internal/cli/getasset.go +++ b/zSDKs/sdk-cli/internal/cli/getasset.go @@ -71,7 +71,7 @@ func runGetAssetCmd(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { - stopProgress := func() {} + stopProgress := output.StartRequestProgress(cmd) res, err := s.GetAsset(cmd.Context(), *req, sdkOpts...) if err != nil { stopProgress() diff --git a/zSDKs/sdk-cli/internal/cli/renderasset.go b/zSDKs/sdk-cli/internal/cli/renderasset.go index eda1669cb..160b9334d 100644 --- a/zSDKs/sdk-cli/internal/cli/renderasset.go +++ b/zSDKs/sdk-cli/internal/cli/renderasset.go @@ -91,7 +91,7 @@ func executeRenderAssetCmd(cmd *cobra.Command, args []string, asyncIntent bool) sdkOpts = append(sdkOpts, operations.WithSkipDeserialization()) } if !client.IsDryRun(cmd) && !asyncIntent { - stopProgress := func() {} + stopProgress := output.StartRequestProgress(cmd) res, err := s.RenderAsset(cmd.Context(), *request, sdkOpts...) if err != nil { stopProgress() diff --git a/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go b/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go index fc3f6435b..165f4ddc4 100644 --- a/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go +++ b/zSDKs/sdk-cli/internal/cli/streammetadata/streammetadata.go @@ -54,7 +54,7 @@ func runStreamMetadataCmd(cmd *cobra.Command, args []string) error { // Streaming response — iterate events and output incrementally. // Skip streaming iteration in dry-run mode (synthetic response has no stream). if !client.IsDryRun(cmd) { - stopProgress := func() {} + stopProgress := output.StartRequestProgress(cmd) res, err := s.StreamMetadata.StreamMetadata(cmd.Context(), sdkOpts...) if err != nil { stopProgress() diff --git a/zSDKs/sdk-cli/tests/request_progress_test.go b/zSDKs/sdk-cli/tests/request_progress_test.go index 0b6424a49..e1f3f05c0 100644 --- a/zSDKs/sdk-cli/tests/request_progress_test.go +++ b/zSDKs/sdk-cli/tests/request_progress_test.go @@ -157,7 +157,7 @@ func TestRequestProgressStreamClearsBeforeFirstEvent(t *testing.T) { require.Contains(t, got, "fixture request") event := strings.Index(got, "first-event") require.GreaterOrEqual(t, event, 0) - require.Contains(t, got[:event], "\r\x1b[2K") + require.True(t, strings.HasSuffix(got[:event], "\r\x1b[2K"), "progress must be cleared immediately before the first event") require.NotContains(t, got[event:], "fixture request") } @@ -174,7 +174,7 @@ func TestRequestProgressStreamFallbackClearsBeforeResult(t *testing.T) { require.Contains(t, got, "fixture request") result := strings.Index(got, "single-result") require.GreaterOrEqual(t, result, 0) - require.Contains(t, got[:result], "\r\x1b[2K") + require.True(t, strings.HasSuffix(got[:result], "\r\x1b[2K"), "progress must be cleared immediately before the result") } func TestRequestProgressSuppressionFlags(t *testing.T) {