webctx 0.2.6 → 0.2.8
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/internal/app/app.go +29 -1
- package/internal/app/data_audit_test.go +574 -0
- package/internal/app/data_client.go +193 -5
- package/internal/app/data_contract_constraints.go +65 -0
- package/internal/app/data_examples.go +79 -6
- package/internal/app/data_execute.go +224 -64
- package/internal/app/data_inspect.go +20 -37
- package/internal/app/data_pagination.go +126 -0
- package/internal/app/data_replay.go +118 -0
- package/internal/app/data_research.go +189 -16
- package/internal/app/data_tools_test.go +2 -2
- package/internal/app/data_types.go +35 -12
- package/internal/app/research.go +4 -1
- package/internal/app/research_answers.go +30 -0
- package/internal/app/site_map_page.go +275 -0
- package/internal/app/tools.go +16 -46
- package/package.json +1 -1
- package/pkg/origo/data_test.go +27 -0
- package/pkg/origo/server.go +17 -10
- package/pkg/retrieval/data.go +2 -0
|
@@ -3,6 +3,7 @@ package app
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
5
|
"crypto/rand"
|
|
6
|
+
"crypto/sha256"
|
|
6
7
|
"encoding/hex"
|
|
7
8
|
"encoding/json"
|
|
8
9
|
"errors"
|
|
@@ -14,7 +15,10 @@ import (
|
|
|
14
15
|
"regexp"
|
|
15
16
|
"strconv"
|
|
16
17
|
"strings"
|
|
18
|
+
"sync"
|
|
17
19
|
"time"
|
|
20
|
+
|
|
21
|
+
"golang.org/x/sync/singleflight"
|
|
18
22
|
)
|
|
19
23
|
|
|
20
24
|
const dataAPIBase = "https://api.firecrawl.dev/v2"
|
|
@@ -25,10 +29,13 @@ const defaultDataCreditBudget = 200
|
|
|
25
29
|
const maxDataResponseBytes int64 = 12 << 20
|
|
26
30
|
|
|
27
31
|
type DataError struct {
|
|
28
|
-
Code
|
|
29
|
-
Message
|
|
30
|
-
RequiresAction
|
|
31
|
-
Details
|
|
32
|
+
Code string `json:"code"`
|
|
33
|
+
Message string `json:"message"`
|
|
34
|
+
RequiresAction map[string]any `json:"requires_action,omitempty"`
|
|
35
|
+
Details any `json:"details,omitempty"`
|
|
36
|
+
RetryAfterSeconds int `json:"retry_after_seconds,omitempty"`
|
|
37
|
+
RetryAt string `json:"retry_at,omitempty"`
|
|
38
|
+
Retryable bool `json:"retryable,omitempty"`
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
func (e *DataError) Error() string { return e.Code + ": " + e.Message }
|
|
@@ -54,6 +61,101 @@ func dataCreditCap() int {
|
|
|
54
61
|
|
|
55
62
|
var safeRequestID = regexp.MustCompile("^[A-Za-z0-9_-]{8,128}$")
|
|
56
63
|
|
|
64
|
+
var freeDataCooldown struct {
|
|
65
|
+
sync.Mutex
|
|
66
|
+
until time.Time
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type dataReadRetryKey struct{}
|
|
70
|
+
|
|
71
|
+
var upstreamDataGate = make(chan struct{}, 3)
|
|
72
|
+
|
|
73
|
+
type catalogueCacheEntry struct {
|
|
74
|
+
payload map[string]any
|
|
75
|
+
response map[string]any
|
|
76
|
+
expires time.Time
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var catalogueCache = struct {
|
|
80
|
+
sync.Mutex
|
|
81
|
+
entries map[string]catalogueCacheEntry
|
|
82
|
+
}{entries: map[string]catalogueCacheEntry{}}
|
|
83
|
+
var catalogueFlight singleflight.Group
|
|
84
|
+
|
|
85
|
+
func cacheableCatalogueKey(options map[string]any) string {
|
|
86
|
+
key, err := dataAPIKey()
|
|
87
|
+
if err != nil || strings.HasPrefix(key, "test") {
|
|
88
|
+
return ""
|
|
89
|
+
}
|
|
90
|
+
snapshot, err := json.Marshal(options)
|
|
91
|
+
if err != nil {
|
|
92
|
+
return ""
|
|
93
|
+
}
|
|
94
|
+
hash := sha256.Sum256(append(append([]byte(key), 0), snapshot...))
|
|
95
|
+
return hex.EncodeToString(hash[:])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Coordinate upstream read traffic per warm worker. Vercel has multiple
|
|
99
|
+
// workers, so this is a local best-effort throttle, not a global quota.
|
|
100
|
+
func dataReadCooldown(ctx context.Context) error {
|
|
101
|
+
freeDataCooldown.Lock()
|
|
102
|
+
until := freeDataCooldown.until
|
|
103
|
+
freeDataCooldown.Unlock()
|
|
104
|
+
if remaining := time.Until(until); remaining > 0 {
|
|
105
|
+
if remaining < 4*time.Second {
|
|
106
|
+
timer := time.NewTimer(remaining)
|
|
107
|
+
defer timer.Stop()
|
|
108
|
+
select {
|
|
109
|
+
case <-timer.C:
|
|
110
|
+
return nil
|
|
111
|
+
case <-ctx.Done():
|
|
112
|
+
return dataError("request_cancelled", "The provider read was cancelled while throttled.")
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return rateLimitError(int(remaining.Seconds())+1, "The provider's request allowance is temporarily exhausted.", "local_cooldown")
|
|
116
|
+
}
|
|
117
|
+
return nil
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func rateLimitError(seconds int, message, source string) *DataError {
|
|
121
|
+
if seconds < 1 {
|
|
122
|
+
seconds = 60
|
|
123
|
+
}
|
|
124
|
+
if seconds > 3600 {
|
|
125
|
+
seconds = 3600
|
|
126
|
+
}
|
|
127
|
+
return &DataError{
|
|
128
|
+
Code: "rate_limited", Message: message, Retryable: true, RetryAfterSeconds: seconds,
|
|
129
|
+
RetryAt: time.Now().Add(time.Duration(seconds) * time.Second).UTC().Format(time.RFC3339),
|
|
130
|
+
Details: map[string]any{"retry_after_source": source, "guidance": "Retry this read after the indicated delay. For paid calls, preserve the original request_id and exact inputs."},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func readRetryDelay(headers http.Header) (int, string) {
|
|
135
|
+
if raw := strings.TrimSpace(headers.Get("Retry-After")); raw != "" {
|
|
136
|
+
if n, e := strconv.Atoi(raw); e == nil && n > 0 {
|
|
137
|
+
return n, "retry-after-header"
|
|
138
|
+
}
|
|
139
|
+
if t, e := http.ParseTime(raw); e == nil {
|
|
140
|
+
seconds := int(time.Until(t).Seconds()) + 1
|
|
141
|
+
if seconds > 0 {
|
|
142
|
+
return seconds, "retry-after-header"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if raw := strings.TrimSpace(headers.Get("RateLimit-Reset")); raw != "" {
|
|
147
|
+
if n, e := strconv.ParseInt(raw, 10, 64); e == nil {
|
|
148
|
+
if n > 1e9 {
|
|
149
|
+
n = int64(time.Until(time.Unix(n, 0)).Seconds()) + 1
|
|
150
|
+
}
|
|
151
|
+
if n > 0 && n < 3601 {
|
|
152
|
+
return int(n), "ratelimit-reset-header"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return 60, "estimated_minute_window"
|
|
157
|
+
}
|
|
158
|
+
|
|
57
159
|
func newRequestID() (string, error) {
|
|
58
160
|
var bytes [16]byte
|
|
59
161
|
if _, err := rand.Read(bytes[:]); err != nil {
|
|
@@ -80,7 +182,18 @@ func doDataAPI(ctx context.Context, endpoint string, payload any, requestID stri
|
|
|
80
182
|
if requestID != "" {
|
|
81
183
|
req.Header.Set("x-request-id", requestID)
|
|
82
184
|
}
|
|
185
|
+
if requestID == "" {
|
|
186
|
+
if err := dataReadCooldown(ctx); err != nil {
|
|
187
|
+
return nil, err
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
select {
|
|
191
|
+
case upstreamDataGate <- struct{}{}:
|
|
192
|
+
case <-ctx.Done():
|
|
193
|
+
return nil, dataError("request_cancelled", "Timed out waiting for an upstream request slot.")
|
|
194
|
+
}
|
|
83
195
|
res, err := http.DefaultClient.Do(req)
|
|
196
|
+
<-upstreamDataGate
|
|
84
197
|
if err != nil {
|
|
85
198
|
return nil, &DataError{Code: "network_error", Message: "The provider request did not complete. For a paid call, retry only with the same request_id to prevent duplicate charges."}
|
|
86
199
|
}
|
|
@@ -94,6 +207,10 @@ func doDataAPI(ctx context.Context, endpoint string, payload any, requestID stri
|
|
|
94
207
|
}
|
|
95
208
|
var decoded map[string]any
|
|
96
209
|
if json.Unmarshal(raw, &decoded) != nil {
|
|
210
|
+
if res.StatusCode == http.StatusTooManyRequests {
|
|
211
|
+
seconds, source := readRetryDelay(res.Header)
|
|
212
|
+
return nil, rateLimitError(seconds, "The upstream data provider is rate limiting requests.", source)
|
|
213
|
+
}
|
|
97
214
|
return nil, dataError("invalid_upstream_response", fmt.Sprintf("Provider returned non-JSON HTTP %d.", res.StatusCode))
|
|
98
215
|
}
|
|
99
216
|
if res.StatusCode < 200 || res.StatusCode >= 300 || decoded["success"] == false {
|
|
@@ -111,7 +228,36 @@ func doDataAPI(ctx context.Context, endpoint string, payload any, requestID stri
|
|
|
111
228
|
if len(message) > 800 {
|
|
112
229
|
message = message[:800]
|
|
113
230
|
}
|
|
231
|
+
if res.StatusCode == http.StatusTooManyRequests || strings.EqualFold(code, "rate_limit_exceeded") || strings.EqualFold(code, "rate_limited") {
|
|
232
|
+
seconds, source := readRetryDelay(res.Header)
|
|
233
|
+
if requestID == "" {
|
|
234
|
+
freeDataCooldown.Lock()
|
|
235
|
+
next := time.Now().Add(time.Duration(seconds) * time.Second)
|
|
236
|
+
if next.After(freeDataCooldown.until) {
|
|
237
|
+
freeDataCooldown.until = next
|
|
238
|
+
}
|
|
239
|
+
freeDataCooldown.Unlock()
|
|
240
|
+
}
|
|
241
|
+
// A short retry is safe for free discovery. Never automatically
|
|
242
|
+
// repeat a paid execution: its actual charging state may be unknown.
|
|
243
|
+
attempt, _ := ctx.Value(dataReadRetryKey{}).(int)
|
|
244
|
+
if requestID == "" && seconds <= 2 && attempt == 0 {
|
|
245
|
+
timer := time.NewTimer(time.Duration(seconds) * time.Second)
|
|
246
|
+
defer timer.Stop()
|
|
247
|
+
select {
|
|
248
|
+
case <-timer.C:
|
|
249
|
+
return doDataAPI(context.WithValue(ctx, dataReadRetryKey{}, 1), endpoint, payload, requestID)
|
|
250
|
+
case <-ctx.Done():
|
|
251
|
+
return nil, rateLimitError(seconds, "The provider's allowance has not recovered yet.", source)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return nil, rateLimitError(seconds, "The upstream data provider is rate limiting requests.", source)
|
|
255
|
+
}
|
|
114
256
|
out := &DataError{Code: code, Message: message}
|
|
257
|
+
if strings.EqualFold(code, "duplicate_request") {
|
|
258
|
+
out.Message = "This request_id was already used with different inputs. Reuse the exact original request body to replay it, or omit request_id to start a new query."
|
|
259
|
+
out.Details = map[string]any{"guidance": "Do not retry the conflicting payload with the same request_id.", "recovery": "Restore the exact original calls for replay, or generate a fresh request_id for intentionally new calls."}
|
|
260
|
+
}
|
|
115
261
|
if action, ok := decoded["requiresAction"].(map[string]any); ok {
|
|
116
262
|
if target := stringValue(action["url"]); target != "" {
|
|
117
263
|
if u, e := url.Parse(target); e == nil && (u.Scheme == "https" || u.Scheme == "http") && u.Host != "" {
|
|
@@ -148,6 +294,43 @@ func extractNativeData(response map[string]any) (map[string]any, error) {
|
|
|
148
294
|
}
|
|
149
295
|
|
|
150
296
|
func freeDataBrowse(ctx context.Context, options map[string]any) (map[string]any, map[string]any, error) {
|
|
297
|
+
cacheKey := cacheableCatalogueKey(options)
|
|
298
|
+
if cacheKey != "" {
|
|
299
|
+
catalogueCache.Lock()
|
|
300
|
+
cached, ok := catalogueCache.entries[cacheKey]
|
|
301
|
+
catalogueCache.Unlock()
|
|
302
|
+
if ok && time.Now().Before(cached.expires) {
|
|
303
|
+
return cached.payload, cached.response, nil
|
|
304
|
+
}
|
|
305
|
+
type cacheResult struct {
|
|
306
|
+
payload map[string]any
|
|
307
|
+
response map[string]any
|
|
308
|
+
}
|
|
309
|
+
result, err, _ := catalogueFlight.Do(cacheKey, func() (any, error) {
|
|
310
|
+
data, raw, e := uncachedFreeDataBrowse(ctx, options)
|
|
311
|
+
if e != nil {
|
|
312
|
+
return nil, e
|
|
313
|
+
}
|
|
314
|
+
catalogueCache.Lock()
|
|
315
|
+
if len(catalogueCache.entries) > 256 {
|
|
316
|
+
catalogueCache.entries = map[string]catalogueCacheEntry{}
|
|
317
|
+
}
|
|
318
|
+
catalogueCache.entries[cacheKey] = catalogueCacheEntry{
|
|
319
|
+
payload: data, response: raw, expires: time.Now().Add(2 * time.Minute),
|
|
320
|
+
}
|
|
321
|
+
catalogueCache.Unlock()
|
|
322
|
+
return cacheResult{payload: data, response: raw}, nil
|
|
323
|
+
})
|
|
324
|
+
if err != nil {
|
|
325
|
+
return nil, nil, err
|
|
326
|
+
}
|
|
327
|
+
got := result.(cacheResult)
|
|
328
|
+
return got.payload, got.response, nil
|
|
329
|
+
}
|
|
330
|
+
return uncachedFreeDataBrowse(ctx, options)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
func uncachedFreeDataBrowse(ctx context.Context, options map[string]any) (map[string]any, map[string]any, error) {
|
|
151
334
|
response, err := nativeDataCall(ctx, "firecrawl", "find-tools", options, "")
|
|
152
335
|
if err != nil {
|
|
153
336
|
return nil, nil, err
|
|
@@ -158,7 +341,12 @@ func freeDataBrowse(ctx context.Context, options map[string]any) (map[string]any
|
|
|
158
341
|
}
|
|
159
342
|
details, ok := item["data"].(map[string]any)
|
|
160
343
|
if !ok {
|
|
161
|
-
|
|
344
|
+
if item["data"] == nil && item["error"] == nil {
|
|
345
|
+
// Some Alexandria catalogues signal an unknown ID by returning
|
|
346
|
+
// a successful find-tools call with data=null.
|
|
347
|
+
return map[string]any{"items": []any{}, "total": 0}, response, nil
|
|
348
|
+
}
|
|
349
|
+
return nil, nil, dataError("invalid_upstream_response", "The catalogue returned an unreadable response. Retry discovery before selecting an operation.")
|
|
162
350
|
}
|
|
163
351
|
return details, response, nil
|
|
164
352
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package app
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strings"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
var integerDataInputs = map[string]bool{
|
|
8
|
+
"page": true, "page_number": true, "pageNumber": true,
|
|
9
|
+
"page_size": true, "per_page": true, "offset": true,
|
|
10
|
+
"limit": true, "k": true, "top_k": true, "topK": true,
|
|
11
|
+
"count": true, "size": true, "max_results": true,
|
|
12
|
+
"num_results": true, "max_records": true, "results_per_page": true,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Return normalized, machine-readable hints alongside the untouched native
|
|
16
|
+
// field contract. Do not overwrite the upstream schema's declared types.
|
|
17
|
+
func normalizedDataInputFields(contract map[string]any) []map[string]any {
|
|
18
|
+
fields := make([]map[string]any, 0)
|
|
19
|
+
for _, v := range listField(contract["options"]) {
|
|
20
|
+
item := mapField(v)
|
|
21
|
+
if item == nil {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
name := stringField(item["name"])
|
|
25
|
+
if name == "" {
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
normalized := map[string]any{"name": name, "type": item["type"]}
|
|
29
|
+
typ := strings.ToLower(stringField(item["type"]))
|
|
30
|
+
description := strings.ToLower(stringField(item["about"]))
|
|
31
|
+
if typ == "number" && (integerDataInputs[name] || strings.Contains(description, "integer")) {
|
|
32
|
+
normalized["type"] = "integer"
|
|
33
|
+
normalized["original_type"] = item["type"]
|
|
34
|
+
}
|
|
35
|
+
for _, key := range []string{"pattern", "required", "min", "max", "default", "oneOf", "nullable", "requires", "dependsOn", "mutuallyExclusiveWith"} {
|
|
36
|
+
if value, ok := item[key]; ok {
|
|
37
|
+
normalized[key] = value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
fields = append(fields, normalized)
|
|
41
|
+
}
|
|
42
|
+
return fields
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func normalizedDataOutputFields(contract map[string]any) []map[string]any {
|
|
46
|
+
response := mapField(contract["response"])
|
|
47
|
+
fields := make([]map[string]any, 0)
|
|
48
|
+
for _, v := range listField(response["fields"]) {
|
|
49
|
+
item := mapField(v)
|
|
50
|
+
if item == nil {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
normalized := map[string]any{"name": item["name"], "type": item["type"]}
|
|
54
|
+
about := strings.ToLower(stringField(item["about"]))
|
|
55
|
+
nullable, _ := item["nullable"].(bool)
|
|
56
|
+
if strings.Contains(about, "null") || strings.Contains(about, "missing value") {
|
|
57
|
+
nullable = true
|
|
58
|
+
}
|
|
59
|
+
if nullable {
|
|
60
|
+
normalized["nullable"] = true
|
|
61
|
+
}
|
|
62
|
+
fields = append(fields, normalized)
|
|
63
|
+
}
|
|
64
|
+
return fields
|
|
65
|
+
}
|
|
@@ -32,6 +32,32 @@ func contractExamples(contract map[string]any) any {
|
|
|
32
32
|
input[name] = value
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
var chooseOne []any
|
|
36
|
+
for _, v := range listField(contract["requiresOneOf"]) {
|
|
37
|
+
alternatives := listField(v)
|
|
38
|
+
satisfied := false
|
|
39
|
+
for _, candidate := range alternatives {
|
|
40
|
+
name := stringField(candidate)
|
|
41
|
+
if input[name] != nil && input[name] != "" {
|
|
42
|
+
satisfied = true
|
|
43
|
+
break
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if satisfied {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
for _, candidate := range alternatives {
|
|
50
|
+
name := stringField(candidate)
|
|
51
|
+
if value, ok := sampleFieldValue(name, fieldInfo[name]); ok {
|
|
52
|
+
input[name] = value
|
|
53
|
+
satisfied = true
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if !satisfied {
|
|
58
|
+
chooseOne = append(chooseOne, alternatives)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
35
61
|
// An illustrative example should not accidentally fetch huge datasets
|
|
36
62
|
// when copied verbatim. Users may adjust the inputs freely.
|
|
37
63
|
for _, name := range []string{"limit", "per_page", "page_size", "max_results", "count"} {
|
|
@@ -43,15 +69,42 @@ func contractExamples(contract map[string]any) any {
|
|
|
43
69
|
return nil
|
|
44
70
|
}
|
|
45
71
|
validation := validateDataInputs(contract, input)
|
|
72
|
+
practical := validation == nil
|
|
73
|
+
if len(input) == 0 && len(listField(contract["options"])) > 0 {
|
|
74
|
+
practical = false
|
|
75
|
+
}
|
|
76
|
+
// A purely structural URL or a placeholder never proves a real record
|
|
77
|
+
// exists. High-credit person-profile lookups must use a real user URL.
|
|
78
|
+
for name, value := range input {
|
|
79
|
+
lower := strings.ToLower(name)
|
|
80
|
+
s := stringField(value)
|
|
81
|
+
if strings.Contains(lower, "linkedin") && strings.Contains(s, "example") {
|
|
82
|
+
practical = false
|
|
83
|
+
}
|
|
84
|
+
if strings.Contains(strings.ToLower(s), "placeholder") ||
|
|
85
|
+
strings.Contains(strings.ToLower(s), "identifier") {
|
|
86
|
+
practical = false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
id := strings.ToLower(stringField(contract["id"]))
|
|
90
|
+
if (strings.HasSuffix(id, "/get") || strings.HasSuffix(id, "/lookup") ||
|
|
91
|
+
strings.HasSuffix(id, "/detail")) && len(input) == 0 {
|
|
92
|
+
practical = false
|
|
93
|
+
}
|
|
46
94
|
example := map[string]any{
|
|
47
|
-
"kind":
|
|
48
|
-
"note":
|
|
49
|
-
"inputs":
|
|
50
|
-
"
|
|
95
|
+
"kind": "generated_from_inspected_schema",
|
|
96
|
+
"note": "Illustrative Origo-generated inputs; not a published provider example or an executed query.",
|
|
97
|
+
"inputs": input,
|
|
98
|
+
"structurally_valid": validation == nil,
|
|
99
|
+
"valid_input_shape": validation == nil && practical,
|
|
100
|
+
"ready_to_execute": practical,
|
|
51
101
|
}
|
|
52
|
-
if validation != nil {
|
|
102
|
+
if validation != nil || !practical {
|
|
53
103
|
example["note"] = "Illustrative input template; fill required fields and check source-specific constraints before execution."
|
|
54
104
|
example["needs_input"] = needed
|
|
105
|
+
if len(chooseOne) > 0 {
|
|
106
|
+
example["needs_one_of"] = chooseOne
|
|
107
|
+
}
|
|
55
108
|
} else {
|
|
56
109
|
example["next"] = map[string]any{"tool": "execute", "arguments": map[string]any{
|
|
57
110
|
"calls": []any{map[string]any{"id": contract["id"], "inputs": input}},
|
|
@@ -72,7 +125,13 @@ func sampleFieldValue(name string, field map[string]any) (any, bool) {
|
|
|
72
125
|
}
|
|
73
126
|
}
|
|
74
127
|
if sample, ok := field["example"]; ok && sample != nil {
|
|
75
|
-
|
|
128
|
+
if s := stringField(sample); s != "" &&
|
|
129
|
+
(strings.Contains(strings.ToLower(s), "placeholder") ||
|
|
130
|
+
strings.Contains(strings.ToLower(s), "identifier")) {
|
|
131
|
+
// Example-like placeholders are not executable source IDs.
|
|
132
|
+
} else {
|
|
133
|
+
return sample, true
|
|
134
|
+
}
|
|
76
135
|
}
|
|
77
136
|
if samples := listField(field["examples"]); len(samples) > 0 && samples[0] != nil {
|
|
78
137
|
return samples[0], true
|
|
@@ -83,6 +142,20 @@ func sampleFieldValue(name string, field map[string]any) (any, bool) {
|
|
|
83
142
|
info := stringField(field["about"]) + " " + stringField(field["description"])
|
|
84
143
|
lower := strings.ToLower(name + " " + info)
|
|
85
144
|
switch {
|
|
145
|
+
case name == "companyKey":
|
|
146
|
+
return "NASDAQ_AAPL", true
|
|
147
|
+
case name == "repo" || name == "repository":
|
|
148
|
+
return "openai/openai-python", true
|
|
149
|
+
case name == "symbol" || name == "ticker":
|
|
150
|
+
return "AAPL", true
|
|
151
|
+
case name == "from" || name == "base_currency":
|
|
152
|
+
return "USD", true
|
|
153
|
+
case name == "to" || name == "quote_currency":
|
|
154
|
+
return "EUR", true
|
|
155
|
+
case name == "domain":
|
|
156
|
+
return "github.com", true
|
|
157
|
+
case strings.Contains(lower, "linkedin") || strings.Contains(lower, "professional_network_url"):
|
|
158
|
+
return nil, false
|
|
86
159
|
case strings.Contains(name, "series_id") && strings.Contains(info, "CPIAUCSL"):
|
|
87
160
|
return "CPIAUCSL", true
|
|
88
161
|
case strings.Contains(name, "series_id") && strings.Contains(info, "UNRATE"):
|