Skip to content

Commit 467f13a

Browse files
author
stlc-bot
committed
feat(api): unify timeout configuration and return partial results (#1030)
Stainless-Generated-From: 18213ddac7685de467dcc665aab9a54e9799721e
1 parent 5d45957 commit 467f13a

12 files changed

Lines changed: 712 additions & 161 deletions

File tree

‎pkg/cmd/ai.go‎

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/urfave/cli/v3"
1515
)
1616

17-
var aiExtractProduct = cli.Command{
17+
var aiExtractProduct = requestflag.WithInnerFlags(cli.Command{
1818
Name: "extract-product",
1919
Usage: "Given a single URL, determines if it is a product page and extracts the product\ninformation.",
2020
Suggest: true,
@@ -36,17 +36,30 @@ var aiExtractProduct = cli.Command{
3636
Usage: "Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.",
3737
BodyPath: "tags",
3838
},
39-
&requestflag.Flag[int64]{
40-
Name: "timeout-ms",
41-
Usage: "Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).",
42-
BodyPath: "timeoutMS",
39+
&requestflag.Flag[map[string]any]{
40+
Name: "timeout-opts",
41+
Usage: "Optional request deadline and behavior on timeout. For GET requests, use timeoutOpts[milliseconds]=30000&timeoutOpts[behavior]=fail or a JSON-encoded timeoutOpts object.",
42+
BodyPath: "timeoutOpts",
4343
},
4444
},
4545
Action: handleAIExtractProduct,
4646
HideHelpCommand: true,
47-
}
47+
}, map[string][]requestflag.HasOuterFlag{
48+
"timeout-opts": {
49+
&requestflag.InnerFlag[int64]{
50+
Name: "timeout-opts.milliseconds",
51+
Usage: "Request deadline in milliseconds. Maximum: 300000 (5 minutes).",
52+
InnerField: "milliseconds",
53+
},
54+
&requestflag.InnerFlag[string]{
55+
Name: "timeout-opts.behavior",
56+
Usage: `What to do at the deadline. "fail" returns 408 REQUEST_TIMEOUT without charging credits. "return-partial" returns usable results collected so far; if none are available, the request still fails without charging credits. Partial results are not cached as complete results.`,
57+
InnerField: "behavior",
58+
},
59+
},
60+
})
4861

49-
var aiExtractProducts = cli.Command{
62+
var aiExtractProducts = requestflag.WithInnerFlags(cli.Command{
5063
Name: "extract-products",
5164
Usage: "Extract product information from a brand's website. We will analyze the website\nand return a list of products with details such as name, description, image,\npricing, features, and more.",
5265
Suggest: true,
@@ -72,10 +85,10 @@ var aiExtractProducts = cli.Command{
7285
Usage: "Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.",
7386
BodyPath: "tags",
7487
},
75-
&requestflag.Flag[int64]{
76-
Name: "timeout-ms",
77-
Usage: "Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).",
78-
BodyPath: "timeoutMS",
88+
&requestflag.Flag[map[string]any]{
89+
Name: "timeout-opts",
90+
Usage: "Optional request deadline and behavior on timeout. For GET requests, use timeoutOpts[milliseconds]=30000&timeoutOpts[behavior]=fail or a JSON-encoded timeoutOpts object.",
91+
BodyPath: "timeoutOpts",
7992
},
8093
&requestflag.Flag[string]{
8194
Name: "direct-url",
@@ -85,7 +98,20 @@ var aiExtractProducts = cli.Command{
8598
},
8699
Action: handleAIExtractProducts,
87100
HideHelpCommand: true,
88-
}
101+
}, map[string][]requestflag.HasOuterFlag{
102+
"timeout-opts": {
103+
&requestflag.InnerFlag[int64]{
104+
Name: "timeout-opts.milliseconds",
105+
Usage: "Request deadline in milliseconds. Maximum: 300000 (5 minutes).",
106+
InnerField: "milliseconds",
107+
},
108+
&requestflag.InnerFlag[string]{
109+
Name: "timeout-opts.behavior",
110+
Usage: `What to do at the deadline. "fail" returns 408 REQUEST_TIMEOUT without charging credits. "return-partial" returns usable results collected so far; if none are available, the request still fails without charging credits. Partial results are not cached as complete results.`,
111+
InnerField: "behavior",
112+
},
113+
},
114+
})
89115

90116
func handleAIExtractProduct(ctx context.Context, cmd *cli.Command) error {
91117
client := contextdev.NewClient(getDefaultRequestOptions(cmd)...)

‎pkg/cmd/ai_test.go‎

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
"github.com/context-dot-dev/context-dev-cli/internal/mocktest"
9+
"github.com/context-dot-dev/context-dev-cli/internal/requestflag"
910
)
1011

1112
func TestAIExtractProduct(t *testing.T) {
@@ -19,7 +20,25 @@ func TestAIExtractProduct(t *testing.T) {
1920
"--max-age-ms", "0",
2021
"--tag", "production",
2122
"--tag", "team-alpha",
22-
"--timeout-ms", "1000",
23+
"--timeout-opts", "{milliseconds: 1000, behavior: fail}",
24+
)
25+
})
26+
27+
t.Run("inner flags", func(t *testing.T) {
28+
// Check that inner flags have been set up correctly
29+
requestflag.CheckInnerFlags(aiExtractProduct)
30+
31+
// Alternative argument passing style using inner flags
32+
mocktest.TestRunMockTestWithFlags(
33+
t,
34+
"--api-key", "string",
35+
"ai", "extract-product",
36+
"--url", "https://example.com",
37+
"--max-age-ms", "0",
38+
"--tag", "production",
39+
"--tag", "team-alpha",
40+
"--timeout-opts.milliseconds", "1000",
41+
"--timeout-opts.behavior", "fail",
2342
)
2443
})
2544

@@ -31,7 +50,9 @@ func TestAIExtractProduct(t *testing.T) {
3150
"tags:\n" +
3251
" - production\n" +
3352
" - team-alpha\n" +
34-
"timeoutMS: 1000\n")
53+
"timeoutOpts:\n" +
54+
" milliseconds: 1000\n" +
55+
" behavior: fail\n")
3556
mocktest.TestRunMockTestWithPipeAndFlags(
3657
t, pipeData,
3758
"--api-key", "string",
@@ -52,7 +73,26 @@ func TestAIExtractProducts(t *testing.T) {
5273
"--max-products", "1",
5374
"--tag", "production",
5475
"--tag", "team-alpha",
55-
"--timeout-ms", "1000",
76+
"--timeout-opts", "{milliseconds: 1000, behavior: fail}",
77+
)
78+
})
79+
80+
t.Run("inner flags", func(t *testing.T) {
81+
// Check that inner flags have been set up correctly
82+
requestflag.CheckInnerFlags(aiExtractProducts)
83+
84+
// Alternative argument passing style using inner flags
85+
mocktest.TestRunMockTestWithFlags(
86+
t,
87+
"--api-key", "string",
88+
"ai", "extract-products",
89+
"--domain", "domain",
90+
"--max-age-ms", "0",
91+
"--max-products", "1",
92+
"--tag", "production",
93+
"--tag", "team-alpha",
94+
"--timeout-opts.milliseconds", "1000",
95+
"--timeout-opts.behavior", "fail",
5696
)
5797
})
5898

@@ -65,7 +105,9 @@ func TestAIExtractProducts(t *testing.T) {
65105
"tags:\n" +
66106
" - production\n" +
67107
" - team-alpha\n" +
68-
"timeoutMS: 1000\n")
108+
"timeoutOpts:\n" +
109+
" milliseconds: 1000\n" +
110+
" behavior: fail\n")
69111
mocktest.TestRunMockTestWithPipeAndFlags(
70112
t, pipeData,
71113
"--api-key", "string",

‎pkg/cmd/brand.go‎

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/urfave/cli/v3"
1515
)
1616

17-
var brandRetrieve = cli.Command{
17+
var brandRetrieve = requestflag.WithInnerFlags(cli.Command{
1818
Name: "retrieve",
1919
Usage: "Retrieve logos, backdrops, colors, industry, description, and more. Provide\nexactly one lookup identifier in the request body: a domain, company name, email\naddress, stock ticker, transaction descriptor, or direct URL. Note:\n`by_direct_url` fetches brand data only from the provided URL — not from the\nentire internet.",
2020
Suggest: true,
@@ -50,10 +50,10 @@ var brandRetrieve = cli.Command{
5050
Usage: "Optional tags for tracking usage. Up to 20 tags, each 1 to 50 characters.",
5151
BodyPath: "tags",
5252
},
53-
&requestflag.Flag[int64]{
54-
Name: "timeout-ms",
55-
Usage: "Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).",
56-
BodyPath: "timeoutMS",
53+
&requestflag.Flag[map[string]any]{
54+
Name: "timeout-opts",
55+
Usage: "Optional request deadline and behavior on timeout. For GET requests, use timeoutOpts[milliseconds]=30000&timeoutOpts[behavior]=fail or a JSON-encoded timeoutOpts object.",
56+
BodyPath: "timeoutOpts",
5757
},
5858
&requestflag.Flag[string]{
5959
Name: "name",
@@ -113,9 +113,22 @@ var brandRetrieve = cli.Command{
113113
},
114114
Action: handleBrandRetrieve,
115115
HideHelpCommand: true,
116-
}
116+
}, map[string][]requestflag.HasOuterFlag{
117+
"timeout-opts": {
118+
&requestflag.InnerFlag[int64]{
119+
Name: "timeout-opts.milliseconds",
120+
Usage: "Request deadline in milliseconds. Maximum: 300000 (5 minutes).",
121+
InnerField: "milliseconds",
122+
},
123+
&requestflag.InnerFlag[string]{
124+
Name: "timeout-opts.behavior",
125+
Usage: `What to do at the deadline. "fail" returns 408 REQUEST_TIMEOUT without charging credits. "return-partial" returns usable results collected so far; if none are available, the request still fails without charging credits. Partial results are not cached as complete results.`,
126+
InnerField: "behavior",
127+
},
128+
},
129+
})
117130

118-
var brandRetrieveSimplified = cli.Command{
131+
var brandRetrieveSimplified = requestflag.WithInnerFlags(cli.Command{
119132
Name: "retrieve-simplified",
120133
Usage: "Returns a simplified version of brand data containing only essential\ninformation: domain, title, colors, logos, and backdrops. Optimized for faster\nresponses and reduced data transfer.",
121134
Suggest: true,
@@ -142,15 +155,28 @@ var brandRetrieveSimplified = cli.Command{
142155
Usage: "Optional theme preference used when selecting brand assets.",
143156
QueryPath: "theme",
144157
},
145-
&requestflag.Flag[int64]{
146-
Name: "timeout-ms",
147-
Usage: "Optional timeout in milliseconds for the request. If the request takes longer than this value, it will be aborted with a 408 status code. Maximum allowed value is 300000ms (5 minutes).",
148-
QueryPath: "timeoutMS",
158+
&requestflag.Flag[map[string]any]{
159+
Name: "timeout-opts",
160+
Usage: "Optional request deadline and behavior on timeout. For GET requests, use timeoutOpts[milliseconds]=30000&timeoutOpts[behavior]=fail or a JSON-encoded timeoutOpts object.",
161+
QueryPath: "timeoutOpts",
149162
},
150163
},
151164
Action: handleBrandRetrieveSimplified,
152165
HideHelpCommand: true,
153-
}
166+
}, map[string][]requestflag.HasOuterFlag{
167+
"timeout-opts": {
168+
&requestflag.InnerFlag[int64]{
169+
Name: "timeout-opts.milliseconds",
170+
Usage: "Request deadline in milliseconds. Maximum: 300000 (5 minutes).",
171+
InnerField: "milliseconds",
172+
},
173+
&requestflag.InnerFlag[string]{
174+
Name: "timeout-opts.behavior",
175+
Usage: `What to do at the deadline. "fail" returns 408 REQUEST_TIMEOUT without charging credits. "return-partial" returns usable results collected so far; if none are available, the request still fails without charging credits. Partial results are not cached as complete results.`,
176+
InnerField: "behavior",
177+
},
178+
},
179+
})
154180

155181
var brandSearch = cli.Command{
156182
Name: "search",

‎pkg/cmd/brand_test.go‎

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
"github.com/context-dot-dev/context-dev-cli/internal/mocktest"
9+
"github.com/context-dot-dev/context-dev-cli/internal/requestflag"
910
)
1011

1112
func TestBrandRetrieve(t *testing.T) {
@@ -22,7 +23,28 @@ func TestBrandRetrieve(t *testing.T) {
2223
"--max-speed=true",
2324
"--tag", "production",
2425
"--tag", "team-alpha",
25-
"--timeout-ms", "1000",
26+
"--timeout-opts", "{milliseconds: 1000, behavior: fail}",
27+
)
28+
})
29+
30+
t.Run("inner flags", func(t *testing.T) {
31+
// Check that inner flags have been set up correctly
32+
requestflag.CheckInnerFlags(brandRetrieve)
33+
34+
// Alternative argument passing style using inner flags
35+
mocktest.TestRunMockTestWithFlags(
36+
t,
37+
"--api-key", "string",
38+
"brand", "retrieve",
39+
"--domain", "xxx",
40+
"--type", "by_domain",
41+
"--force-language", "afrikaans",
42+
"--max-age-ms", "0",
43+
"--max-speed=true",
44+
"--tag", "production",
45+
"--tag", "team-alpha",
46+
"--timeout-opts.milliseconds", "1000",
47+
"--timeout-opts.behavior", "fail",
2648
)
2749
})
2850

@@ -37,7 +59,9 @@ func TestBrandRetrieve(t *testing.T) {
3759
"tags:\n" +
3860
" - production\n" +
3961
" - team-alpha\n" +
40-
"timeoutMS: 1000\n")
62+
"timeoutOpts:\n" +
63+
" milliseconds: 1000\n" +
64+
" behavior: fail\n")
4165
mocktest.TestRunMockTestWithPipeAndFlags(
4266
t, pipeData,
4367
"--api-key", "string",
@@ -58,7 +82,26 @@ func TestBrandRetrieveSimplified(t *testing.T) {
5882
"--tag", "production",
5983
"--tag", "team-alpha",
6084
"--theme", "light",
61-
"--timeout-ms", "1000",
85+
"--timeout-opts", "{milliseconds: 1000, behavior: fail}",
86+
)
87+
})
88+
89+
t.Run("inner flags", func(t *testing.T) {
90+
// Check that inner flags have been set up correctly
91+
requestflag.CheckInnerFlags(brandRetrieveSimplified)
92+
93+
// Alternative argument passing style using inner flags
94+
mocktest.TestRunMockTestWithFlags(
95+
t,
96+
"--api-key", "string",
97+
"brand", "retrieve-simplified",
98+
"--domain", "xxx",
99+
"--max-age-ms", "0",
100+
"--tag", "production",
101+
"--tag", "team-alpha",
102+
"--theme", "light",
103+
"--timeout-opts.milliseconds", "1000",
104+
"--timeout-opts.behavior", "fail",
62105
)
63106
})
64107
}

0 commit comments

Comments
 (0)