webctx 0.2.2 → 0.2.3

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.
@@ -0,0 +1,526 @@
1
+ package app
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "net/url"
7
+ "regexp"
8
+ "sort"
9
+ "strings"
10
+ "sync"
11
+ "time"
12
+ )
13
+
14
+ // Research deliberately works with source text, not generated answers. The
15
+ // coding agent gets exact excerpts and URLs it can independently inspect.
16
+ // One follow-up level and fixed budgets prevent uncontrolled crawls.
17
+ const (
18
+ // Coding agents value complete code and request contracts more than tiny
19
+ // snippets. Keep ordinary documentation pages whole when reasonably sized.
20
+ focusedPrimaryBudget = 22000
21
+ focusedRelatedBudget = 6500
22
+ searchSourceBudget = 4000
23
+ maxRelatedSources = 2
24
+ maxSearchSources = 3
25
+ )
26
+
27
+ var (
28
+ mdLinkPattern = regexp.MustCompile(`\[([^\]\n]{2,120})\]\(([^\s)]+)(?:\s+"[^"]*")?\)`)
29
+ wordPattern = regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9_-]{2,}`)
30
+ sourceWordPattern = regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9]{1,}`)
31
+ headingPattern = regexp.MustCompile(`^#{1,6}\s+(.+)$`)
32
+ )
33
+
34
+ type researchSection struct {
35
+ index int
36
+ heading string
37
+ body string
38
+ score int
39
+ }
40
+
41
+ type sourceCandidate struct {
42
+ url string
43
+ label string
44
+ score int
45
+ order int
46
+ }
47
+
48
+ type sourceRead struct {
49
+ url string
50
+ content string
51
+ err error
52
+ }
53
+
54
+ // ReadLinkFocused is backwards-compatible: URL-only calls still return the
55
+ // complete original source. A question makes the engine select excerpts and
56
+ // automatically inspect related documentation on the same host.
57
+ func ReadLinkFocused(rawURL, question string) (string, error) {
58
+ return readLinkFocused(rawURL, question, ReadLink)
59
+ }
60
+
61
+ func readLinkFocused(rawURL, question string, reader func(string) (string, error)) (string, error) {
62
+ if strings.TrimSpace(question) == "" {
63
+ return reader(rawURL)
64
+ }
65
+ if err := validateSourceURL(rawURL); err != nil {
66
+ return "", err
67
+ }
68
+ question = strings.TrimSpace(question)
69
+ // REST contract discovery is independent of the page scrape. Start it
70
+ // immediately so a slow Firecrawl read and a large OpenAPI download overlap
71
+ // instead of approaching the hosted MCP function timeout sequentially.
72
+ var openAPIDone chan openAPIContext
73
+ if asksForAPIContracts(question) {
74
+ openAPIDone = make(chan openAPIContext, 1)
75
+ go func() { openAPIDone <- discoverOpenAPIContract(rawURL, question) }()
76
+ }
77
+ primary, err := reader(rawURL)
78
+ if err != nil {
79
+ return "", err
80
+ }
81
+
82
+ links := relatedSourceLinks(primary, rawURL, question, maxRelatedSources)
83
+ if len(links) < maxRelatedSources {
84
+ // Many doc systems publish llms.txt as their navigation index. It is a
85
+ // cheap way to find exact API reference pages not linked from this page.
86
+ for _, candidate := range linksFromDocsIndex(rawURL, question, maxRelatedSources) {
87
+ if len(links) == maxRelatedSources {
88
+ break
89
+ }
90
+ duplicate := candidate.url == rawURL
91
+ for _, chosen := range links {
92
+ duplicate = duplicate || chosen.url == candidate.url
93
+ }
94
+ if !duplicate {
95
+ links = append(links, candidate)
96
+ }
97
+ }
98
+ }
99
+ structuredDone := make(chan string, 1)
100
+ go func() { structuredDone <- embeddedStructuredContext(rawURL, question) }()
101
+ more := readSources(links, reader)
102
+ parts := []string{
103
+ "# Source-grounded context",
104
+ "",
105
+ "**Question:** " + question,
106
+ "",
107
+ "## Primary source",
108
+ "**Source:** " + rawURL,
109
+ "",
110
+ focusExcerpt(primary, question, focusedPrimaryBudget),
111
+ }
112
+ successful := 1
113
+ for _, source := range more {
114
+ if source.err != nil {
115
+ continue
116
+ }
117
+ successful++
118
+ parts = append(parts, "", "## Related source", "**Source:** "+source.url, "", focusExcerpt(source.content, question, focusedRelatedBudget))
119
+ }
120
+ if openAPIDone != nil {
121
+ if contract := <-openAPIDone; contract.Content != "" {
122
+ successful++
123
+ parts = append(parts, "", "## REST API contract", "**Source:** "+contract.URL, "", contract.Content)
124
+ }
125
+ }
126
+ if structured := <-structuredDone; structured != "" {
127
+ parts = append(parts, "", structured)
128
+ }
129
+ parts = append(parts, "", fmt.Sprintf("**Coverage:** %d source document(s) inspected. Extracts are source-grounded, not an independently verified synthesis. REST commands assembled from an OpenAPI operation and its published example are labeled as such.", successful))
130
+ if failed := len(more) - (successful - 1); failed > 0 {
131
+ parts = append(parts, fmt.Sprintf("%d related page(s) could not be retrieved; the primary source is preserved.", failed))
132
+ }
133
+ return strings.Join(parts, "\n"), nil
134
+ }
135
+
136
+ // enrichSearch gives agents real documentation content in their first search
137
+ // response, with the original ranked source list retained for navigation.
138
+ func enrichSearch(query string, ranked []SearchDoc) string {
139
+ return enrichSearchWithReader(query, ranked, ReadLink)
140
+ }
141
+
142
+ func enrichSearchWithReader(query string, ranked []SearchDoc, reader func(string) (string, error)) string {
143
+ chosen := chooseSearchSources(query, ranked, maxSearchSources)
144
+ if len(chosen) == 0 {
145
+ return ""
146
+ }
147
+ more := readSources(chosen, reader)
148
+ parts := []string{}
149
+ seen := make(map[string]bool)
150
+ for _, source := range more {
151
+ if source.err != nil {
152
+ continue
153
+ }
154
+ seen[source.url] = true
155
+ parts = append(parts, "### "+source.url, "", focusExcerpt(source.content, query, searchSourceBudget), "")
156
+ }
157
+ if len(parts) == 0 {
158
+ return ""
159
+ }
160
+ // Expand high-relevance documentation references from retrieved results,
161
+ // not arbitrary websites or unlimited recursive link trees.
162
+ var follow []sourceCandidate
163
+ for _, source := range more {
164
+ if source.err != nil || len(follow) >= 2 {
165
+ continue
166
+ }
167
+ for _, candidate := range relatedSourceLinks(source.content, source.url, query, 2) {
168
+ if seen[candidate.url] {
169
+ continue
170
+ }
171
+ seen[candidate.url] = true
172
+ follow = append(follow, candidate)
173
+ if len(follow) >= 2 {
174
+ break
175
+ }
176
+ }
177
+ }
178
+ for _, source := range readSources(follow, reader) {
179
+ if source.err == nil {
180
+ parts = append(parts, "### Supporting reference: "+source.url, "", focusExcerpt(source.content, query, focusedRelatedBudget), "")
181
+ }
182
+ }
183
+ return "## Retrieved source context\n\nThe following are excerpts from source pages, not an AI-generated answer. Compare versions and follow the URLs for full documents.\n\n" + strings.Join(parts, "\n")
184
+ }
185
+
186
+ func linksFromDocsIndex(rawURL, question string, limit int) []sourceCandidate {
187
+ u, err := url.Parse(rawURL)
188
+ if err != nil || u.Hostname() == "github.com" || directSourceLanguage(rawURL) != "" {
189
+ return nil
190
+ }
191
+ if !strings.HasPrefix(u.Hostname(), "docs.") && !strings.Contains(u.Path, "/docs/") && !strings.Contains(u.Path, "/reference/") {
192
+ return nil
193
+ }
194
+ indexURL := u.Scheme + "://" + u.Host + "/llms.txt"
195
+ if rawURL == indexURL {
196
+ return nil
197
+ }
198
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
199
+ defer cancel()
200
+ index, err := fetchPublicText(ctx, indexURL, 300000)
201
+ if err != nil || !strings.Contains(index, "](") {
202
+ return nil
203
+ }
204
+ return relatedSourceLinks(index, indexURL, question, limit)
205
+ }
206
+
207
+ func readSources(candidates []sourceCandidate, reader func(string) (string, error)) []sourceRead {
208
+ result := make([]sourceRead, len(candidates))
209
+ var wg sync.WaitGroup
210
+ for i, candidate := range candidates {
211
+ wg.Add(1)
212
+ go func(i int, candidate sourceCandidate) {
213
+ defer wg.Done()
214
+ content, err := reader(candidate.url)
215
+ result[i] = sourceRead{url: candidate.url, content: content, err: err}
216
+ }(i, candidate)
217
+ }
218
+ wg.Wait()
219
+ return result
220
+ }
221
+
222
+ func chooseSearchSources(query string, ranked []SearchDoc, limit int) []sourceCandidate {
223
+ items := make([]sourceCandidate, 0, limit)
224
+ terms := queryTerms(query)
225
+ for i, source := range ranked {
226
+ if i >= 18 {
227
+ break
228
+ }
229
+ if err := validateSourceURL(source.URL); err != nil || disallowedSourcePath(source.URL) {
230
+ continue
231
+ }
232
+ u, _ := url.Parse(source.URL)
233
+ score := 18 - i + termScore(source.Title, terms)*2 + termScore(u.Hostname()+" "+u.Path, terms)
234
+ if strings.HasPrefix(u.Hostname(), "docs.") || strings.Contains(u.Path, "/docs/") || strings.Contains(u.Path, "/reference/") {
235
+ score += 7
236
+ }
237
+ if strings.Contains(u.Path, "/specification/") || strings.Contains(u.Path, "/api-reference/") || strings.Contains(u.Path, "/sdk/") {
238
+ score += 7
239
+ }
240
+ if u.Hostname() == "github.com" || strings.Contains(u.Path, "openapi") {
241
+ score += 5
242
+ }
243
+ if strings.Contains(u.Path, "/community/") || strings.Contains(u.Path, "/interest-groups/") {
244
+ score -= 16 // governance pages rarely contain usable API contracts
245
+ }
246
+ if strings.Contains(u.Path, "/blog/") || strings.HasPrefix(u.Hostname(), "blog.") {
247
+ score -= 5
248
+ }
249
+ if strings.Contains(u.Path, "/internal/") && u.Hostname() == "pkg.go.dev" {
250
+ score -= 12 // internal Go packages aren't public SDK contracts
251
+ }
252
+ if strings.Contains(strings.ToLower(query), "go sdk") && strings.Contains(strings.ToLower(source.Title+" "+u.Path), "go-sdk") {
253
+ score += 7
254
+ }
255
+ items = append(items, sourceCandidate{url: source.URL, label: source.Title, score: score, order: i})
256
+ }
257
+ sort.SliceStable(items, func(i, j int) bool { return items[i].score > items[j].score })
258
+ selected := make([]sourceCandidate, 0, limit)
259
+ seenHosts := make(map[string]int)
260
+ for _, item := range items {
261
+ if len(selected) == limit {
262
+ break
263
+ }
264
+ u, _ := url.Parse(item.url)
265
+ if seenHosts[u.Hostname()] >= 2 { // diversify while allowing two relevant docs pages
266
+ continue
267
+ }
268
+ seenHosts[u.Hostname()]++
269
+ selected = append(selected, item)
270
+ }
271
+ return selected
272
+ }
273
+
274
+ func relatedSourceLinks(markdown, baseURL, question string, limit int) []sourceCandidate {
275
+ base, err := url.Parse(baseURL)
276
+ if err != nil {
277
+ return nil
278
+ }
279
+ terms := queryTerms(question)
280
+ seen := map[string]bool{canonicalSourceURL(base): true}
281
+ var links []sourceCandidate
282
+ for i, match := range mdLinkPattern.FindAllStringSubmatch(markdown, 300) {
283
+ destination := strings.TrimSpace(match[2])
284
+ if strings.Contains(destination, "\\") { // malformed Markdown-escaped URLs
285
+ continue
286
+ }
287
+ ref, err := url.Parse(destination)
288
+ if err != nil {
289
+ continue
290
+ }
291
+ u := base.ResolveReference(ref)
292
+ if !strings.EqualFold(base.Hostname(), u.Hostname()) || base.Port() != u.Port() || u.User != nil {
293
+ continue
294
+ }
295
+ if err := validateSourceURL(u.String()); err != nil || disallowedSourcePath(u.String()) || sensitiveURL(u) {
296
+ continue
297
+ }
298
+ key := canonicalSourceURL(u)
299
+ if seen[key] {
300
+ continue
301
+ }
302
+ seen[key] = true
303
+ label := strings.TrimSpace(match[1])
304
+ score := termScore(label, terms)*4 + termScore(u.Path, terms)*3
305
+ if score == 0 { // no relevant relationship to the user's question
306
+ continue
307
+ }
308
+ if len(terms) >= 4 && len(matchedLinkTerms(label+" "+u.Path, terms)) == 1 {
309
+ term := matchedLinkTerms(label+" "+u.Path, terms)[0]
310
+ if len(term) <= 5 { // don't expand generic links matching only 'MCP' or 'tools'
311
+ continue
312
+ }
313
+ }
314
+ links = append(links, sourceCandidate{url: key, label: label, score: score, order: i})
315
+ }
316
+ sort.SliceStable(links, func(i, j int) bool {
317
+ if links[i].score == links[j].score {
318
+ return links[i].order < links[j].order
319
+ }
320
+ return links[i].score > links[j].score
321
+ })
322
+ if len(links) > limit {
323
+ links = links[:limit]
324
+ }
325
+ return links
326
+ }
327
+
328
+ func matchedLinkTerms(text string, terms []string) []string {
329
+ var matched []string
330
+ for _, term := range terms {
331
+ if termScore(text, []string{term}) > 0 {
332
+ matched = append(matched, term)
333
+ }
334
+ }
335
+ return matched
336
+ }
337
+
338
+ func canonicalSourceURL(u *url.URL) string {
339
+ copy := *u
340
+ copy.Fragment = ""
341
+ copy.RawFragment = ""
342
+ return copy.String()
343
+ }
344
+
345
+ func sensitiveURL(u *url.URL) bool {
346
+ for name := range u.Query() {
347
+ name = strings.ToLower(name)
348
+ if strings.Contains(name, "token") || strings.Contains(name, "secret") || strings.Contains(name, "key") || strings.Contains(name, "auth") || strings.Contains(name, "signature") {
349
+ return true
350
+ }
351
+ }
352
+ return false
353
+ }
354
+
355
+ func disallowedSourcePath(raw string) bool {
356
+ u, err := url.Parse(raw)
357
+ if err != nil {
358
+ return true
359
+ }
360
+ path := strings.ToLower(u.Path)
361
+ for _, suffix := range []string{".png", ".jpg", ".jpeg", ".svg", ".gif", ".webp", ".zip", ".mp4", ".mov", ".mp3", ".woff", ".woff2", ".pdf", ".dmg", ".exe"} {
362
+ if strings.HasSuffix(path, suffix) {
363
+ return true
364
+ }
365
+ }
366
+ return false
367
+ }
368
+
369
+ func queryTerms(query string) []string {
370
+ stop := map[string]bool{"the": true, "and": true, "for": true, "from": true, "with": true, "that": true, "this": true, "what": true, "how": true, "when": true, "where": true, "does": true, "which": true, "using": true, "about": true, "into": true, "can": true, "are": true, "get": true, "use": true, "you": true, "your": true, "its": true, "have": true}
371
+ seen := make(map[string]bool)
372
+ var terms []string
373
+ for _, term := range sourceWordPattern.FindAllString(strings.ToLower(query), 40) {
374
+ if len(term) < 3 {
375
+ continue
376
+ }
377
+ if stop[term] || seen[term] {
378
+ continue
379
+ }
380
+ seen[term] = true
381
+ terms = append(terms, term)
382
+ }
383
+ return terms
384
+ }
385
+
386
+ func termScore(text string, terms []string) int {
387
+ // Compare words instead of substrings. Without this, an "api" search
388
+ // incorrectly matches "scraping" and chooses unrelated pages.
389
+ words := sourceWordPattern.FindAllString(strings.ToLower(text), -1)
390
+ count := 0
391
+ for _, term := range terms {
392
+ // Approximate inflection matching: pagination / paginate, authenticated /
393
+ // authentication, etc. Exact matches are weighted slightly higher.
394
+ root := term
395
+ if len(root) > 5 {
396
+ root = root[:5]
397
+ }
398
+ for _, word := range words {
399
+ if word == term {
400
+ count += 2
401
+ break
402
+ }
403
+ if len(root) >= 4 && strings.HasPrefix(word, root) {
404
+ count++
405
+ break
406
+ }
407
+ }
408
+ }
409
+ return count
410
+ }
411
+
412
+ func splitResearchSections(markdown string, terms []string) []researchSection {
413
+ lines := strings.Split(markdown, "\n")
414
+ sections := make([]researchSection, 0, 20)
415
+ current := researchSection{heading: "Introduction"}
416
+ var body strings.Builder
417
+ inFence := false
418
+ flush := func() {
419
+ current.body = strings.TrimSpace(body.String())
420
+ if current.body != "" {
421
+ current.index = len(sections)
422
+ current.score = 5*termScore(current.heading, terms) + termScore(current.body, terms)
423
+ if strings.Contains(current.body, "```") && current.score > 0 {
424
+ current.score += 2
425
+ }
426
+ sections = append(sections, current)
427
+ }
428
+ body.Reset()
429
+ }
430
+ for _, line := range lines {
431
+ trimmed := strings.TrimSpace(line)
432
+ if !inFence {
433
+ if heading := headingPattern.FindStringSubmatch(trimmed); len(heading) == 2 {
434
+ flush()
435
+ current = researchSection{heading: heading[1]}
436
+ }
437
+ }
438
+ body.WriteString(line)
439
+ body.WriteByte('\n')
440
+ if strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~") {
441
+ inFence = !inFence
442
+ }
443
+ }
444
+ flush()
445
+ return sections
446
+ }
447
+
448
+ func focusExcerpt(markdown, question string, budget int) string {
449
+ markdown = strings.TrimSpace(markdown)
450
+ if len(markdown) <= budget {
451
+ return markdown
452
+ }
453
+ if focused := focusedRawJSON(markdown, question, budget); focused != "" {
454
+ return focused
455
+ }
456
+ sections := splitResearchSections(markdown, queryTerms(question))
457
+ if len(sections) == 0 {
458
+ return boundedMarkdown(markdown, budget)
459
+ }
460
+ sorted := append([]researchSection(nil), sections...)
461
+ sort.SliceStable(sorted, func(i, j int) bool {
462
+ if sorted[i].score == sorted[j].score {
463
+ return sorted[i].index < sorted[j].index
464
+ }
465
+ return sorted[i].score > sorted[j].score
466
+ })
467
+ selected := make([]researchSection, 0, 5)
468
+ for _, section := range sorted {
469
+ if len(selected) >= 5 || (section.score == 0 && len(selected) > 0) {
470
+ break
471
+ }
472
+ selected = append(selected, section)
473
+ }
474
+ sort.Slice(selected, func(i, j int) bool { return selected[i].index < selected[j].index })
475
+ var parts []string
476
+ remaining := budget
477
+ for i, section := range selected {
478
+ if remaining < 160 {
479
+ break
480
+ }
481
+ // Allocate budget across selected sections so one huge section doesn't
482
+ // crowd out exact schemas or examples from the other sections.
483
+ allocation := remaining / (len(selected) - i)
484
+ if allocation < 500 && remaining >= 500 {
485
+ allocation = 500
486
+ }
487
+ text := atomicFocusedSection(section.body, question, allocation)
488
+ parts = append(parts, text)
489
+ remaining -= len(text)
490
+ }
491
+ result := strings.Join(parts, "\n\n")
492
+ return result + "\n\n*[Selected excerpts; open the source URL to inspect omitted sections.]*"
493
+ }
494
+
495
+ func boundedMarkdown(markdown string, budget int) string {
496
+ if len(markdown) <= budget {
497
+ return markdown
498
+ }
499
+ if budget <= 80 {
500
+ return "*[Section too large; consult source.]*"
501
+ }
502
+ lines := strings.Split(markdown, "\n")
503
+ var out strings.Builder
504
+ inFence := false
505
+ fence := "```"
506
+ for _, line := range lines {
507
+ if out.Len()+len(line)+len("\n…\n```") > budget {
508
+ break
509
+ }
510
+ trimmed := strings.TrimSpace(line)
511
+ if strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~") {
512
+ if !inFence {
513
+ fence = trimmed[:3]
514
+ }
515
+ inFence = !inFence
516
+ }
517
+ out.WriteString(line)
518
+ out.WriteByte('\n')
519
+ }
520
+ if inFence {
521
+ out.WriteString(fence)
522
+ out.WriteByte('\n')
523
+ }
524
+ out.WriteString("*[Excerpt shortened; see source for full content.]*")
525
+ return out.String()
526
+ }
@@ -0,0 +1,149 @@
1
+ package app
2
+
3
+ import (
4
+ "sort"
5
+ "strings"
6
+ )
7
+
8
+ const backtickFence = "\x60\x60\x60"
9
+
10
+ type evidenceBlock struct {
11
+ text string
12
+ code bool
13
+ score int
14
+ order int
15
+ }
16
+
17
+ // A code fence is indivisible evidence: do not silently truncate a command,
18
+ // request body or SDK example and then invent a closing fence.
19
+ func splitEvidenceBlocks(markdown, question string) []evidenceBlock {
20
+ var blocks []evidenceBlock
21
+ var content strings.Builder
22
+ terms := queryTerms(question)
23
+ inCode := false
24
+ fence := ""
25
+ flush := func(code bool) {
26
+ text := strings.TrimSpace(content.String())
27
+ content.Reset()
28
+ if text == "" {
29
+ return
30
+ }
31
+ score := termScore(text, terms)
32
+ if code {
33
+ score *= 3
34
+ if strings.Contains(strings.ToLower(question), "rest") &&
35
+ (strings.Contains(text, "curl") || strings.Contains(text, "POST ") || strings.Contains(text, "GET ")) {
36
+ score += 4
37
+ }
38
+ }
39
+ blocks = append(blocks, evidenceBlock{text: text, code: code, score: score, order: len(blocks)})
40
+ }
41
+ for _, line := range strings.Split(markdown, "\n") {
42
+ trimmed := strings.TrimSpace(line)
43
+ opening := !inCode && (strings.HasPrefix(trimmed, backtickFence) || strings.HasPrefix(trimmed, "~~~"))
44
+ if opening {
45
+ flush(false)
46
+ inCode = true
47
+ fence = trimmed[:3]
48
+ }
49
+ content.WriteString(line)
50
+ content.WriteByte('\n')
51
+ if inCode && !opening && strings.TrimSpace(strings.TrimPrefix(trimmed, fence)) == "" &&
52
+ strings.HasPrefix(trimmed, fence) {
53
+ flush(true)
54
+ inCode = false
55
+ fence = ""
56
+ }
57
+ }
58
+ // Unclosed code blocks are not valid examples; treat them as source prose.
59
+ flush(false)
60
+ return blocks
61
+ }
62
+
63
+ // Select complete, high-relevance examples and explanatory prose from long
64
+ // sections, preserving original order and the hard context budget.
65
+ func atomicFocusedSection(markdown, question string, budget int) string {
66
+ markdown = strings.TrimSpace(markdown)
67
+ if len(markdown) <= budget {
68
+ return markdown
69
+ }
70
+ if budget < 180 {
71
+ return "*[Section exceeds excerpt budget; follow the source URL.]*"
72
+ }
73
+ blocks := splitEvidenceBlocks(markdown, question)
74
+ const omitted = "\n\n*[Other source text omitted. Follow the original URL for full details.]*"
75
+ remaining := budget - len(omitted) - 8
76
+ if remaining < 80 {
77
+ return "*[Section exceeds excerpt budget; follow the source URL.]*"
78
+ }
79
+ selected := make(map[int]string)
80
+ var codes []evidenceBlock
81
+ var prose []evidenceBlock
82
+ for _, block := range blocks {
83
+ if block.code {
84
+ codes = append(codes, block)
85
+ } else {
86
+ prose = append(prose, block)
87
+ }
88
+ }
89
+ sort.SliceStable(codes, func(i, j int) bool {
90
+ if codes[i].score == codes[j].score {
91
+ return codes[i].order < codes[j].order
92
+ }
93
+ return codes[i].score > codes[j].score
94
+ })
95
+ // Reserve a little space for the section explanation.
96
+ codeAllowance := remaining * 85 / 100
97
+ for _, block := range codes {
98
+ size := len(block.text) + 2
99
+ if size <= codeAllowance {
100
+ selected[block.order] = block.text
101
+ codeAllowance -= size
102
+ remaining -= size
103
+ }
104
+ }
105
+ sort.SliceStable(prose, func(i, j int) bool {
106
+ if prose[i].score == prose[j].score {
107
+ return prose[i].order < prose[j].order
108
+ }
109
+ return prose[i].score > prose[j].score
110
+ })
111
+ for _, block := range prose {
112
+ if remaining < 100 {
113
+ break
114
+ }
115
+ if len(block.text)+2 <= remaining {
116
+ selected[block.order] = block.text
117
+ remaining -= len(block.text) + 2
118
+ } else if part := completeProseLines(block.text, remaining-4); part != "" {
119
+ selected[block.order] = part
120
+ remaining -= len(part) + 2
121
+ }
122
+ }
123
+ var output []string
124
+ for _, block := range blocks {
125
+ if text := selected[block.order]; text != "" {
126
+ output = append(output, text)
127
+ }
128
+ }
129
+ if len(output) == 0 {
130
+ return "*[Code examples exceed the excerpt budget; inspect source URL for the full examples.]*"
131
+ }
132
+ return strings.Join(output, "\n\n") + omitted
133
+ }
134
+
135
+ func completeProseLines(text string, maxBytes int) string {
136
+ if maxBytes <= 0 {
137
+ return ""
138
+ }
139
+ var lines []string
140
+ size := 0
141
+ for _, line := range strings.Split(text, "\n") {
142
+ if size+len(line)+1 > maxBytes {
143
+ break
144
+ }
145
+ lines = append(lines, line)
146
+ size += len(line) + 1
147
+ }
148
+ return strings.TrimSpace(strings.Join(lines, "\n"))
149
+ }