@tiangong-ai/cli 0.0.51 → 0.0.55

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.
Files changed (216) hide show
  1. package/AGENTS.md +22 -2
  2. package/README.md +171 -7
  3. package/dist/cli.js +10 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/data/builtins.d.ts +1 -0
  6. package/dist/data/builtins.js +40 -0
  7. package/dist/data/builtins.js.map +1 -0
  8. package/dist/data/catalog.d.ts +25 -0
  9. package/dist/data/catalog.js +305 -0
  10. package/dist/data/catalog.js.map +1 -0
  11. package/dist/data/commands.d.ts +9 -0
  12. package/dist/data/commands.js +307 -0
  13. package/dist/data/commands.js.map +1 -0
  14. package/dist/data/connectors/airnow-hourly-observations.d.ts +2 -0
  15. package/dist/data/connectors/airnow-hourly-observations.js +522 -0
  16. package/dist/data/connectors/airnow-hourly-observations.js.map +1 -0
  17. package/dist/data/connectors/airnow-hourly-observations.schemas.d.ts +299 -0
  18. package/dist/data/connectors/airnow-hourly-observations.schemas.js +188 -0
  19. package/dist/data/connectors/airnow-hourly-observations.schemas.js.map +1 -0
  20. package/dist/data/connectors/bluesky-public-posts.d.ts +2 -0
  21. package/dist/data/connectors/bluesky-public-posts.js +705 -0
  22. package/dist/data/connectors/bluesky-public-posts.js.map +1 -0
  23. package/dist/data/connectors/bluesky-public-posts.schemas.d.ts +733 -0
  24. package/dist/data/connectors/bluesky-public-posts.schemas.js +422 -0
  25. package/dist/data/connectors/bluesky-public-posts.schemas.js.map +1 -0
  26. package/dist/data/connectors/epa-eis-records.d.ts +2 -0
  27. package/dist/data/connectors/epa-eis-records.js +566 -0
  28. package/dist/data/connectors/epa-eis-records.js.map +1 -0
  29. package/dist/data/connectors/epa-eis-records.schemas.d.ts +199 -0
  30. package/dist/data/connectors/epa-eis-records.schemas.js +146 -0
  31. package/dist/data/connectors/epa-eis-records.schemas.js.map +1 -0
  32. package/dist/data/connectors/federal-register-documents.d.ts +2 -0
  33. package/dist/data/connectors/federal-register-documents.js +517 -0
  34. package/dist/data/connectors/federal-register-documents.js.map +1 -0
  35. package/dist/data/connectors/federal-register-documents.schemas.d.ts +357 -0
  36. package/dist/data/connectors/federal-register-documents.schemas.js +243 -0
  37. package/dist/data/connectors/federal-register-documents.schemas.js.map +1 -0
  38. package/dist/data/connectors/gdelt-doc-search.d.ts +2 -0
  39. package/dist/data/connectors/gdelt-doc-search.js +658 -0
  40. package/dist/data/connectors/gdelt-doc-search.js.map +1 -0
  41. package/dist/data/connectors/gdelt-doc-search.schemas.d.ts +446 -0
  42. package/dist/data/connectors/gdelt-doc-search.schemas.js +330 -0
  43. package/dist/data/connectors/gdelt-doc-search.schemas.js.map +1 -0
  44. package/dist/data/connectors/gdelt-file-feeds.d.ts +4 -0
  45. package/dist/data/connectors/gdelt-file-feeds.js +591 -0
  46. package/dist/data/connectors/gdelt-file-feeds.js.map +1 -0
  47. package/dist/data/connectors/gdelt-file-feeds.schemas.d.ts +40 -0
  48. package/dist/data/connectors/gdelt-file-feeds.schemas.js +256 -0
  49. package/dist/data/connectors/gdelt-file-feeds.schemas.js.map +1 -0
  50. package/dist/data/connectors/nasa-firms-fire.d.ts +2 -0
  51. package/dist/data/connectors/nasa-firms-fire.js +578 -0
  52. package/dist/data/connectors/nasa-firms-fire.js.map +1 -0
  53. package/dist/data/connectors/nasa-firms-fire.schemas.d.ts +369 -0
  54. package/dist/data/connectors/nasa-firms-fire.schemas.js +238 -0
  55. package/dist/data/connectors/nasa-firms-fire.schemas.js.map +1 -0
  56. package/dist/data/connectors/open-meteo-air-quality.d.ts +2 -0
  57. package/dist/data/connectors/open-meteo-air-quality.js +401 -0
  58. package/dist/data/connectors/open-meteo-air-quality.js.map +1 -0
  59. package/dist/data/connectors/open-meteo-air-quality.schemas.d.ts +335 -0
  60. package/dist/data/connectors/open-meteo-air-quality.schemas.js +225 -0
  61. package/dist/data/connectors/open-meteo-air-quality.schemas.js.map +1 -0
  62. package/dist/data/connectors/open-meteo-flood.d.ts +2 -0
  63. package/dist/data/connectors/open-meteo-flood.js +446 -0
  64. package/dist/data/connectors/open-meteo-flood.js.map +1 -0
  65. package/dist/data/connectors/open-meteo-flood.schemas.d.ts +388 -0
  66. package/dist/data/connectors/open-meteo-flood.schemas.js +227 -0
  67. package/dist/data/connectors/open-meteo-flood.schemas.js.map +1 -0
  68. package/dist/data/connectors/open-meteo-historical-weather.d.ts +2 -0
  69. package/dist/data/connectors/open-meteo-historical-weather.js +468 -0
  70. package/dist/data/connectors/open-meteo-historical-weather.js.map +1 -0
  71. package/dist/data/connectors/open-meteo-historical-weather.schemas.d.ts +420 -0
  72. package/dist/data/connectors/open-meteo-historical-weather.schemas.js +282 -0
  73. package/dist/data/connectors/open-meteo-historical-weather.schemas.js.map +1 -0
  74. package/dist/data/connectors/openaq-air-quality.d.ts +2 -0
  75. package/dist/data/connectors/openaq-air-quality.js +734 -0
  76. package/dist/data/connectors/openaq-air-quality.js.map +1 -0
  77. package/dist/data/connectors/openaq-air-quality.schemas.d.ts +1067 -0
  78. package/dist/data/connectors/openaq-air-quality.schemas.js +512 -0
  79. package/dist/data/connectors/openaq-air-quality.schemas.js.map +1 -0
  80. package/dist/data/connectors/regulations-gov-attachments.d.ts +2 -0
  81. package/dist/data/connectors/regulations-gov-attachments.js +486 -0
  82. package/dist/data/connectors/regulations-gov-attachments.js.map +1 -0
  83. package/dist/data/connectors/regulations-gov-attachments.schemas.d.ts +270 -0
  84. package/dist/data/connectors/regulations-gov-attachments.schemas.js +181 -0
  85. package/dist/data/connectors/regulations-gov-attachments.schemas.js.map +1 -0
  86. package/dist/data/connectors/regulations-gov-comments.d.ts +2 -0
  87. package/dist/data/connectors/regulations-gov-comments.js +718 -0
  88. package/dist/data/connectors/regulations-gov-comments.js.map +1 -0
  89. package/dist/data/connectors/regulations-gov-comments.schemas.d.ts +717 -0
  90. package/dist/data/connectors/regulations-gov-comments.schemas.js +389 -0
  91. package/dist/data/connectors/regulations-gov-comments.schemas.js.map +1 -0
  92. package/dist/data/connectors/usbr-project-records.d.ts +2 -0
  93. package/dist/data/connectors/usbr-project-records.js +467 -0
  94. package/dist/data/connectors/usbr-project-records.js.map +1 -0
  95. package/dist/data/connectors/usbr-project-records.schemas.d.ts +194 -0
  96. package/dist/data/connectors/usbr-project-records.schemas.js +137 -0
  97. package/dist/data/connectors/usbr-project-records.schemas.js.map +1 -0
  98. package/dist/data/connectors/usbr-rise.d.ts +2 -0
  99. package/dist/data/connectors/usbr-rise.js +625 -0
  100. package/dist/data/connectors/usbr-rise.js.map +1 -0
  101. package/dist/data/connectors/usbr-rise.schemas.d.ts +623 -0
  102. package/dist/data/connectors/usbr-rise.schemas.js +407 -0
  103. package/dist/data/connectors/usbr-rise.schemas.js.map +1 -0
  104. package/dist/data/connectors/usgs-water-instantaneous-values.d.ts +2 -0
  105. package/dist/data/connectors/usgs-water-instantaneous-values.js +521 -0
  106. package/dist/data/connectors/usgs-water-instantaneous-values.js.map +1 -0
  107. package/dist/data/connectors/usgs-water-instantaneous-values.schemas.d.ts +496 -0
  108. package/dist/data/connectors/usgs-water-instantaneous-values.schemas.js +309 -0
  109. package/dist/data/connectors/usgs-water-instantaneous-values.schemas.js.map +1 -0
  110. package/dist/data/connectors/youtube-public-content.d.ts +2 -0
  111. package/dist/data/connectors/youtube-public-content.js +974 -0
  112. package/dist/data/connectors/youtube-public-content.js.map +1 -0
  113. package/dist/data/connectors/youtube-public-content.schemas.d.ts +773 -0
  114. package/dist/data/connectors/youtube-public-content.schemas.js +567 -0
  115. package/dist/data/connectors/youtube-public-content.schemas.js.map +1 -0
  116. package/dist/data/contracts.d.ts +374 -0
  117. package/dist/data/contracts.js +10 -0
  118. package/dist/data/contracts.js.map +1 -0
  119. package/dist/data/runtime/artifacts.d.ts +6 -0
  120. package/dist/data/runtime/artifacts.js +103 -0
  121. package/dist/data/runtime/artifacts.js.map +1 -0
  122. package/dist/data/runtime/bounded-http.d.ts +10 -0
  123. package/dist/data/runtime/bounded-http.js +403 -0
  124. package/dist/data/runtime/bounded-http.js.map +1 -0
  125. package/dist/data/runtime/canonical-json.d.ts +3 -0
  126. package/dist/data/runtime/canonical-json.js +59 -0
  127. package/dist/data/runtime/canonical-json.js.map +1 -0
  128. package/dist/data/runtime/credentials.d.ts +14 -0
  129. package/dist/data/runtime/credentials.js +67 -0
  130. package/dist/data/runtime/credentials.js.map +1 -0
  131. package/dist/data/runtime/csv.d.ts +4 -0
  132. package/dist/data/runtime/csv.js +51 -0
  133. package/dist/data/runtime/csv.js.map +1 -0
  134. package/dist/data/runtime/errors.d.ts +22 -0
  135. package/dist/data/runtime/errors.js +101 -0
  136. package/dist/data/runtime/errors.js.map +1 -0
  137. package/dist/data/runtime/execute.d.ts +11 -0
  138. package/dist/data/runtime/execute.js +358 -0
  139. package/dist/data/runtime/execute.js.map +1 -0
  140. package/dist/data/runtime/receipts.d.ts +13 -0
  141. package/dist/data/runtime/receipts.js +38 -0
  142. package/dist/data/runtime/receipts.js.map +1 -0
  143. package/dist/data/schemas/catalog.v1.json +73 -0
  144. package/dist/data/schemas/core-receipt.v1.json +117 -0
  145. package/dist/data/schemas/describe.v1.json +24 -0
  146. package/dist/data/schemas/discovery.v1.json +130 -0
  147. package/dist/data/schemas/doctor.v1.json +40 -0
  148. package/dist/data/schemas/error.v1.json +48 -0
  149. package/dist/data/schemas/manifest.v1.json +174 -0
  150. package/dist/data/schemas/run-request.v1.json +37 -0
  151. package/dist/data/schemas/run-result.v1.json +77 -0
  152. package/dist/data/schemas.d.ts +27 -0
  153. package/dist/data/schemas.js +69 -0
  154. package/dist/data/schemas.js.map +1 -0
  155. package/dist/env.js.map +1 -1
  156. package/dist/errors.js.map +1 -1
  157. package/dist/http.js.map +1 -1
  158. package/dist/kb/config.js.map +1 -1
  159. package/dist/kb/pipeline-health.js.map +1 -1
  160. package/dist/research/orchestration.js +54 -7
  161. package/dist/research/orchestration.js.map +1 -1
  162. package/dist/research/workspace/acquisition.js.map +1 -1
  163. package/dist/research/workspace/audit-bundle.js +7 -2
  164. package/dist/research/workspace/audit-bundle.js.map +1 -1
  165. package/dist/research/workspace/broker.js.map +1 -1
  166. package/dist/research/workspace/constants.js.map +1 -1
  167. package/dist/research/workspace/credentials.d.ts +3 -0
  168. package/dist/research/workspace/credentials.js +20 -3
  169. package/dist/research/workspace/credentials.js.map +1 -1
  170. package/dist/research/workspace/data-evidence-adapter.d.ts +60 -0
  171. package/dist/research/workspace/data-evidence-adapter.js +314 -0
  172. package/dist/research/workspace/data-evidence-adapter.js.map +1 -0
  173. package/dist/research/workspace/discovery-status.js +10 -7
  174. package/dist/research/workspace/discovery-status.js.map +1 -1
  175. package/dist/research/workspace/discovery.js +12 -11
  176. package/dist/research/workspace/discovery.js.map +1 -1
  177. package/dist/research/workspace/evidence-ledger.d.ts +8 -1
  178. package/dist/research/workspace/evidence-ledger.js +46 -2
  179. package/dist/research/workspace/evidence-ledger.js.map +1 -1
  180. package/dist/research/workspace/evidence.d.ts +31 -0
  181. package/dist/research/workspace/evidence.js +77 -0
  182. package/dist/research/workspace/evidence.js.map +1 -1
  183. package/dist/research/workspace/executor.js.map +1 -1
  184. package/dist/research/workspace/external-skills.d.ts +49 -49
  185. package/dist/research/workspace/external-skills.js.map +1 -1
  186. package/dist/research/workspace/platform-capabilities.js.map +1 -1
  187. package/dist/research/workspace/preflight.d.ts +4 -4
  188. package/dist/research/workspace/preflight.js +33 -8
  189. package/dist/research/workspace/preflight.js.map +1 -1
  190. package/dist/research/workspace/projects.js.map +1 -1
  191. package/dist/research/workspace/publication.js.map +1 -1
  192. package/dist/research/workspace/research-policy-wizard.js.map +1 -1
  193. package/dist/research/workspace/research-policy.js.map +1 -1
  194. package/dist/research/workspace/runtime.d.ts +7 -0
  195. package/dist/research/workspace/runtime.js +77 -20
  196. package/dist/research/workspace/runtime.js.map +1 -1
  197. package/dist/research/workspace/sanitization.js.map +1 -1
  198. package/dist/research/workspace/schemas.js +1 -1
  199. package/dist/research/workspace/schemas.js.map +1 -1
  200. package/dist/research/workspace/scientific-design.js.map +1 -1
  201. package/dist/research/workspace/scientific-review.js.map +1 -1
  202. package/dist/research/workspace/setup-catalog.d.ts +9 -9
  203. package/dist/research/workspace/setup-catalog.js +2 -2
  204. package/dist/research/workspace/setup-catalog.js.map +1 -1
  205. package/dist/research/workspace/setup-declarative.js.map +1 -1
  206. package/dist/research/workspace/setup-instructions.d.ts +42 -0
  207. package/dist/research/workspace/setup-instructions.js +413 -0
  208. package/dist/research/workspace/setup-instructions.js.map +1 -0
  209. package/dist/research/workspace/setup-invocation.js.map +1 -1
  210. package/dist/research/workspace/setup-wizard.js +7 -0
  211. package/dist/research/workspace/setup-wizard.js.map +1 -1
  212. package/dist/research/workspace/setup.d.ts +18 -15
  213. package/dist/research/workspace/setup.js +68 -3
  214. package/dist/research/workspace/setup.js.map +1 -1
  215. package/dist/research/workspace/workspace.js.map +1 -1
  216. package/package.json +3 -3
@@ -0,0 +1,705 @@
1
+ import { DATA_MANIFEST_SCHEMA_VERSION } from "../contracts.js";
2
+ import { DataRuntimeError } from "../runtime/errors.js";
3
+ import { BLUESKY_CASCADE_INPUT_SCHEMA, BLUESKY_CASCADE_OUTPUT_SCHEMA, } from "./bluesky-public-posts.schemas.js";
4
+ const XRPC_PREFIX = "/xrpc/";
5
+ const SEARCH_PATH = `${XRPC_PREFIX}app.bsky.feed.searchPosts`;
6
+ const AUTHOR_FEED_PATH = `${XRPC_PREFIX}app.bsky.feed.getAuthorFeed`;
7
+ const FEED_PATH = `${XRPC_PREFIX}app.bsky.feed.getFeed`;
8
+ const LIST_FEED_PATH = `${XRPC_PREFIX}app.bsky.feed.getListFeed`;
9
+ const THREAD_PATH = `${XRPC_PREFIX}app.bsky.feed.getPostThread`;
10
+ export const blueskyPublicPostsConnector = {
11
+ schemaVersion: DATA_MANIFEST_SCHEMA_VERSION,
12
+ capabilityId: "bluesky.public-posts",
13
+ capabilityVersion: "1.0.0",
14
+ minimumCliVersion: "0.0.55",
15
+ provider: { providerId: "bluesky", name: "Bluesky AppView" },
16
+ sourceCategory: "public-social-media-posts",
17
+ endpoints: [
18
+ {
19
+ endpointId: "bluesky-public-appview",
20
+ baseUrl: "https://public.api.bsky.app",
21
+ pathPrefixes: [XRPC_PREFIX],
22
+ allowedMethods: ["GET"],
23
+ allowedContentTypes: ["application/json"],
24
+ },
25
+ {
26
+ endpointId: "bluesky-public-appview-fallback",
27
+ baseUrl: "https://api.bsky.app",
28
+ pathPrefixes: [XRPC_PREFIX],
29
+ allowedMethods: ["GET"],
30
+ allowedContentTypes: ["application/json"],
31
+ },
32
+ ],
33
+ license: {
34
+ name: "Bluesky public-service terms and AT Protocol public content",
35
+ url: "https://bsky.social/about/support/tos",
36
+ restrictions: [
37
+ "Public posts remain user-generated content and may be deleted, moderated, blocked, mislabeled, or unsafe.",
38
+ "Respect Bluesky terms, community rules, privacy expectations, applicable law, and downstream research ethics.",
39
+ "Do not use account or post data as identity verification, factual ground truth, or a representative-opinion sample.",
40
+ ],
41
+ },
42
+ credentials: [],
43
+ limits: {
44
+ timeoutMs: 45_000,
45
+ maxRequestBytes: 2_048,
46
+ maxResponseBytes: 20_000_000,
47
+ maxPages: 100,
48
+ maxRecords: 5_000,
49
+ maxRetries: 4,
50
+ maxRetryDelayMs: 120_000,
51
+ maxRedirects: 1,
52
+ },
53
+ diagnostics: { static: true, live: false },
54
+ freshness: {
55
+ kind: "provider-current",
56
+ description: "Results are AppView snapshots at request time; indexing, federation, moderation, deletion, and counter updates can lag or change.",
57
+ },
58
+ limitations: [
59
+ "Search syntax, ranking, hit counts, feed algorithms, and cursor reach are provider dependent and do not guarantee exhaustive retrieval.",
60
+ "Author, custom, and list feeds are views rather than repository-complete archives and may contain reposts or algorithmically selected items.",
61
+ "Thread responses can contain blocked or missing nodes and are not paginated; depth and record caps can truncate a cascade.",
62
+ "Post timestamps are client-declared createdAt values when available, with indexedAt only as a fallback; neither proves when an event occurred.",
63
+ "The connector reads public text and metadata only; it does not fetch media bytes, private views, authenticated viewer state, profiles, likes, or follower graphs.",
64
+ ],
65
+ discovery: {
66
+ source: {
67
+ maintainedBy: "Bluesky Social PBC and the AT Protocol open-source project",
68
+ summary: "Public Bluesky AppView projections of posts, author/custom/list feeds, and reply-thread structures.",
69
+ description: "Bluesky is a decentralized social application built on AT Protocol. The public AppView hydrates indexed public records into searchable post and feed views with mutable counters and moderation-dependent visibility.",
70
+ coverage: {
71
+ geographic: "Global public Bluesky/AT Protocol content visible through the selected AppView.",
72
+ temporal: "Current indexed AppView holdings within caller filters and bounded cursor reach; no archive-completeness guarantee.",
73
+ granularity: "One normalized public post seed plus optional flattened reply-thread nodes for each selected seed.",
74
+ },
75
+ },
76
+ summary: "Fetch bounded public Bluesky post seeds and optionally expand their reply cascades.",
77
+ description: "This read-only capability selects public post seeds from search, an author feed, a custom feed, or a list feed; applies an optional UTC timestamp window; and optionally flattens getPostThread results under shared request and record budgets.",
78
+ provides: [
79
+ "Public post text, author DID and handle, created/indexed timestamps, language tags, reply linkage, and mutable engagement counters when present.",
80
+ "Four explicit public seed-source modes with cursor-bounded retrieval, optional search-side time filters, and authoritative client-side UTC filtering.",
81
+ "Flattened reply cascades with parent URI, depth, blocked/not-found states, per-seed failures, and explicit truncation.",
82
+ ],
83
+ doesNotProvide: [
84
+ "Private or authenticated feeds, viewer-specific state, direct messages, account credentials, or write actions.",
85
+ "Media bytes, link targets, profile histories, follower graphs, complete firehose/repository archives, or deleted content recovery.",
86
+ "Representative public opinion, verified identity, factual verification, causal diffusion claims, or sentiment labels.",
87
+ ],
88
+ selectionHints: [
89
+ "Use search for topic discovery, author-feed for one public actor view, feed for a known generator AT-URI, and list-feed for a known public list AT-URI.",
90
+ "Enable thread expansion only when reply topology matters; seed-only retrieval is cheaper and leaves more request budget for pagination.",
91
+ "Treat counters, ranking, missing nodes, and moderation visibility as snapshot properties and retain the execution receipt.",
92
+ "For a historical search diagnostic, disable the server time filter while retaining the same client-side UTC window to determine whether the provider only returns out-of-window posts.",
93
+ "Use a repository/firehose-specific workflow when exhaustive AT Protocol records are required.",
94
+ ],
95
+ typicalUseCases: [
96
+ "Collect a bounded set of public posts matching a research query and inspect their visible reply structures.",
97
+ "Compare visible discussion cascades seeded from a known author, custom feed, or public list.",
98
+ ],
99
+ sourceDocumentation: [
100
+ {
101
+ title: "Bluesky API hosts and public AppView guidance",
102
+ url: "https://docs.bsky.app/docs/advanced-guides/api-directory",
103
+ },
104
+ {
105
+ title: "app.bsky.feed searchPosts Lexicon",
106
+ url: "https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/searchPosts.json",
107
+ },
108
+ {
109
+ title: "app.bsky.feed getPostThread Lexicon",
110
+ url: "https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getPostThread.json",
111
+ },
112
+ {
113
+ title: "Bluesky read-after-write and AppView indexing guidance",
114
+ url: "https://docs.bsky.app/docs/advanced-guides/read-after-write",
115
+ },
116
+ { title: "Bluesky Terms of Service", url: "https://bsky.social/about/support/tos" },
117
+ {
118
+ title: "Bluesky Community Guidelines",
119
+ url: "https://bsky.social/about/support/community-guidelines",
120
+ },
121
+ ],
122
+ },
123
+ operations: [
124
+ {
125
+ operationId: "fetch-cascades",
126
+ operationVersion: "1.0.0",
127
+ summary: "Fetch public Bluesky seed posts and optional reply cascades.",
128
+ description: "Reads exactly one public seed-source mode, applies optional inclusive/exclusive UTC bounds, expands a bounded number of getPostThread snapshots, and reports missing, blocked, truncated, or failed portions without writing artifacts.",
129
+ inputSchema: BLUESKY_CASCADE_INPUT_SCHEMA,
130
+ outputSchema: BLUESKY_CASCADE_OUTPUT_SCHEMA,
131
+ execute: executeBlueskyCascades,
132
+ },
133
+ ],
134
+ };
135
+ async function executeBlueskyCascades(context) {
136
+ const query = normalizeQuery(context.input);
137
+ const seedPosts = [];
138
+ const seenSeeds = new Set();
139
+ const pages = [];
140
+ const observations = [];
141
+ const failures = [];
142
+ const failureValues = [];
143
+ let cursor;
144
+ let hitsTotal = null;
145
+ let fallbackUsed = false;
146
+ let stopReason = "completed";
147
+ let seedFailure;
148
+ let seedPageNumber = 0;
149
+ let requestCount = 0;
150
+ while (requestCount < context.limits.maxPages) {
151
+ seedPageNumber += 1;
152
+ requestCount += 1;
153
+ try {
154
+ const requested = await requestPublicAppView(context, {
155
+ path: sourcePath(query.source),
156
+ query: sourceParameters(query, cursor),
157
+ });
158
+ const response = requested.response;
159
+ fallbackUsed ||= requested.fallbackUsed;
160
+ const parsed = parseSeedPage(response.json(), query.source.mode);
161
+ if (parsed.hitsTotal !== null)
162
+ hitsTotal = parsed.hitsTotal;
163
+ observations.push({
164
+ ...response.observation,
165
+ sourceId: `seed:${query.source.mode}:page:${seedPageNumber}`,
166
+ });
167
+ let selectedRecords = 0;
168
+ let invalidRecords = parsed.invalidRecords;
169
+ for (const value of parsed.posts) {
170
+ if (seedPosts.length >= context.limits.maxRecords)
171
+ break;
172
+ let record;
173
+ try {
174
+ record = normalizePost(value);
175
+ }
176
+ catch {
177
+ invalidRecords += 1;
178
+ continue;
179
+ }
180
+ if (!insideWindow(record, query))
181
+ continue;
182
+ if (seenSeeds.has(record.uri))
183
+ continue;
184
+ seenSeeds.add(record.uri);
185
+ seedPosts.push(record);
186
+ selectedRecords += 1;
187
+ }
188
+ pages.push({
189
+ pageNumber: seedPageNumber,
190
+ inputRecords: parsed.posts.length,
191
+ selectedRecords,
192
+ invalidRecords,
193
+ });
194
+ if (seedPosts.length >= context.limits.maxRecords) {
195
+ stopReason =
196
+ parsed.cursor || parsed.posts.length > selectedRecords ? "max-records" : "completed";
197
+ break;
198
+ }
199
+ if (!parsed.cursor) {
200
+ stopReason = seedPageNumber === 1 && seedPosts.length === 0 ? "no-results" : "completed";
201
+ break;
202
+ }
203
+ if (parsed.cursor === cursor) {
204
+ throw providerInvalid("Bluesky returned a repeated pagination cursor.");
205
+ }
206
+ cursor = parsed.cursor;
207
+ if (requestCount >= context.limits.maxPages) {
208
+ stopReason = "max-pages";
209
+ break;
210
+ }
211
+ }
212
+ catch (error) {
213
+ if (pages.length === 0)
214
+ throw normalizeProviderFailure(error);
215
+ seedFailure = error;
216
+ stopReason = "partial";
217
+ break;
218
+ }
219
+ }
220
+ const cascades = [];
221
+ let emittedRecords = seedPosts.length;
222
+ let expansionTruncated = false;
223
+ if (query.expandThreads && stopReason !== "partial") {
224
+ for (const seed of seedPosts.slice(0, query.maxThreads)) {
225
+ if (requestCount >= context.limits.maxPages || emittedRecords >= context.limits.maxRecords) {
226
+ expansionTruncated = true;
227
+ break;
228
+ }
229
+ try {
230
+ requestCount += 1;
231
+ const requested = await requestPublicAppView(context, {
232
+ path: THREAD_PATH,
233
+ query: {
234
+ uri: seed.uri,
235
+ depth: query.threadDepth,
236
+ parentHeight: query.threadParentHeight,
237
+ },
238
+ });
239
+ const response = requested.response;
240
+ fallbackUsed ||= requested.fallbackUsed;
241
+ observations.push({ ...response.observation, sourceId: `thread:${seed.uri}` });
242
+ const available = context.limits.maxRecords - emittedRecords;
243
+ const parsed = parseThread(response.json(), seed.uri, available);
244
+ emittedRecords += parsed.nodes.length;
245
+ cascades.push({ seedUri: seed.uri, ...parsed });
246
+ expansionTruncated ||= parsed.truncated;
247
+ }
248
+ catch (error) {
249
+ const normalized = normalizeProviderFailure(error);
250
+ failures.push({ seedUri: seed.uri, code: normalized.code });
251
+ failureValues.push(normalized);
252
+ }
253
+ }
254
+ if (seedPosts.length > query.maxThreads)
255
+ expansionTruncated = true;
256
+ }
257
+ if (expansionTruncated && stopReason !== "partial") {
258
+ stopReason = emittedRecords >= context.limits.maxRecords ? "max-records" : "max-pages";
259
+ }
260
+ const partial = seedFailure !== undefined || failures.length > 0;
261
+ if (partial)
262
+ stopReason = "partial";
263
+ const missingIdentifiers = failures.map((item) => item.seedUri);
264
+ const errors = partial
265
+ ? [
266
+ {
267
+ code: "partial-result",
268
+ message: seedFailure === undefined
269
+ ? "Some Bluesky reply threads could not be retrieved or validated."
270
+ : "A later Bluesky seed page could not be retrieved or validated.",
271
+ retryable: [seedFailure, ...failureValues].some((value) => value instanceof DataRuntimeError && (value.options.retryable ?? false)),
272
+ userActionRequired: false,
273
+ details: {
274
+ ...(missingIdentifiers.length > 0 ? { missingSeedUris: missingIdentifiers } : {}),
275
+ ...(seedFailure === undefined ? {} : { failedSeedPage: seedPageNumber }),
276
+ },
277
+ },
278
+ ]
279
+ : [];
280
+ const truncated = stopReason === "max-pages" || stopReason === "max-records";
281
+ const missing = missingIdentifiers.length > 0
282
+ ? [{ kind: "range", identifiers: missingIdentifiers }]
283
+ : seedFailure === undefined
284
+ ? undefined
285
+ : [{ kind: "page", identifiers: [String(seedPageNumber)] }];
286
+ return {
287
+ status: partial ? "partial" : "success",
288
+ data: {
289
+ source: {
290
+ providerId: "bluesky",
291
+ baseUrl: "https://public.api.bsky.app",
292
+ fallbackBaseUrl: "https://api.bsky.app",
293
+ fallbackUsed,
294
+ publicContent: true,
295
+ userGeneratedContent: true,
296
+ },
297
+ query,
298
+ pages,
299
+ hitsTotal,
300
+ seedPosts,
301
+ cascades,
302
+ failures,
303
+ stopReason,
304
+ },
305
+ summary: {
306
+ recordCount: emittedRecords,
307
+ pageCount: observations.length,
308
+ chunkCount: cascades.length,
309
+ truncated,
310
+ completeness: partial ? "partial" : "complete",
311
+ ...(missing ? { missing } : {}),
312
+ },
313
+ warnings: [
314
+ "Bluesky posts are mutable user-generated content and can contain unsafe or personal information.",
315
+ "Search ranking, feeds, moderation visibility, timestamps, and engagement counters are provider snapshots rather than representative or verified evidence.",
316
+ ...(truncated ? ["The result stopped at an explicit request or record limit."] : []),
317
+ ],
318
+ errors,
319
+ observations,
320
+ };
321
+ }
322
+ function normalizeQuery(input) {
323
+ const startDateTime = input.startDateTime ? exactDateTime(input.startDateTime) : null;
324
+ const endDateTime = input.endDateTime ? exactDateTime(input.endDateTime) : null;
325
+ if (startDateTime && endDateTime && startDateTime >= endDateTime) {
326
+ throw new DataRuntimeError("invalid-request", "Bluesky startDateTime must precede endDateTime.");
327
+ }
328
+ return {
329
+ source: normalizeSource(input.source),
330
+ startDateTime,
331
+ endDateTime,
332
+ pageSize: input.pageSize ?? 50,
333
+ expandThreads: input.expandThreads ?? true,
334
+ maxThreads: input.maxThreads ?? 20,
335
+ threadDepth: input.threadDepth ?? 8,
336
+ threadParentHeight: input.threadParentHeight ?? 5,
337
+ };
338
+ }
339
+ function normalizeSource(source) {
340
+ switch (source.mode) {
341
+ case "search": {
342
+ const query = nonBlankInput(source.query, "Bluesky search query");
343
+ const tags = source.tags?.map((value) => nonBlankInput(value, "Bluesky search tag"));
344
+ if (tags && new Set(tags).size !== tags.length) {
345
+ throw new DataRuntimeError("invalid-request", "Bluesky search tags must remain unique after whitespace normalization.");
346
+ }
347
+ return {
348
+ mode: "search",
349
+ query,
350
+ sort: source.sort ?? "latest",
351
+ ...optionalText("author", source.author),
352
+ ...optionalText("mentions", source.mentions),
353
+ ...optionalText("language", source.language),
354
+ ...optionalText("domain", source.domain),
355
+ ...optionalText("url", source.url),
356
+ ...(tags ? { tags: tags.sort(codePointOrder) } : {}),
357
+ applyServerTimeFilter: source.applyServerTimeFilter ?? true,
358
+ };
359
+ }
360
+ case "author-feed":
361
+ return {
362
+ mode: "author-feed",
363
+ actor: nonBlankInput(source.actor, "Bluesky author-feed actor"),
364
+ filter: source.filter ?? "posts_with_replies",
365
+ includePins: source.includePins ?? false,
366
+ };
367
+ case "feed":
368
+ return { mode: "feed", feedUri: source.feedUri.trim() };
369
+ case "list-feed":
370
+ return { mode: "list-feed", listUri: source.listUri.trim() };
371
+ }
372
+ }
373
+ function optionalText(key, value) {
374
+ const normalized = value?.trim();
375
+ return normalized ? { [key]: normalized } : {};
376
+ }
377
+ function nonBlankInput(value, label) {
378
+ const normalized = value.trim();
379
+ if (!normalized)
380
+ throw new DataRuntimeError("invalid-request", `${label} must not be blank.`);
381
+ return normalized;
382
+ }
383
+ function sourcePath(source) {
384
+ switch (source.mode) {
385
+ case "search":
386
+ return SEARCH_PATH;
387
+ case "author-feed":
388
+ return AUTHOR_FEED_PATH;
389
+ case "feed":
390
+ return FEED_PATH;
391
+ case "list-feed":
392
+ return LIST_FEED_PATH;
393
+ }
394
+ }
395
+ function sourceParameters(query, cursor) {
396
+ const common = { limit: query.pageSize, ...(cursor ? { cursor } : {}) };
397
+ switch (query.source.mode) {
398
+ case "search":
399
+ return {
400
+ ...common,
401
+ q: query.source.query,
402
+ sort: query.source.sort ?? "latest",
403
+ ...(query.source.applyServerTimeFilter !== false && query.startDateTime
404
+ ? { since: query.startDateTime }
405
+ : {}),
406
+ ...(query.source.applyServerTimeFilter !== false && query.endDateTime
407
+ ? { until: query.endDateTime }
408
+ : {}),
409
+ ...(query.source.author ? { author: query.source.author } : {}),
410
+ ...(query.source.mentions ? { mentions: query.source.mentions } : {}),
411
+ ...(query.source.language ? { lang: query.source.language } : {}),
412
+ ...(query.source.domain ? { domain: query.source.domain } : {}),
413
+ ...(query.source.url ? { url: query.source.url } : {}),
414
+ ...(query.source.tags ? { tag: query.source.tags } : {}),
415
+ };
416
+ case "author-feed":
417
+ return {
418
+ ...common,
419
+ actor: query.source.actor,
420
+ filter: query.source.filter ?? "posts_with_replies",
421
+ includePins: query.source.includePins ?? false,
422
+ };
423
+ case "feed":
424
+ return { ...common, feed: query.source.feedUri };
425
+ case "list-feed":
426
+ return { ...common, list: query.source.listUri };
427
+ }
428
+ }
429
+ function parseSeedPage(value, mode) {
430
+ const root = object(value, "Bluesky seed response");
431
+ const rawItems = mode === "search" ? root.posts : root.feed;
432
+ if (!Array.isArray(rawItems)) {
433
+ throw providerInvalid("Bluesky seed response does not contain the expected item array.");
434
+ }
435
+ const posts = [];
436
+ let invalidRecords = 0;
437
+ for (const item of rawItems) {
438
+ if (mode === "search") {
439
+ if (item && typeof item === "object" && !Array.isArray(item))
440
+ posts.push(item);
441
+ else
442
+ invalidRecords += 1;
443
+ continue;
444
+ }
445
+ const feedItem = objectOrNull(item);
446
+ const post = feedItem ? objectOrNull(feedItem.post) : null;
447
+ if (post)
448
+ posts.push(post);
449
+ else
450
+ invalidRecords += 1;
451
+ }
452
+ const cursor = root.cursor;
453
+ if (cursor !== undefined && (typeof cursor !== "string" || !cursor)) {
454
+ throw providerInvalid("Bluesky pagination cursor is invalid.");
455
+ }
456
+ return {
457
+ posts,
458
+ ...(typeof cursor === "string" ? { cursor } : {}),
459
+ hitsTotal: nonNegativeInteger(root.hitsTotal),
460
+ invalidRecords,
461
+ };
462
+ }
463
+ function normalizePost(value) {
464
+ const root = object(value, "Bluesky post view");
465
+ const uri = requiredText(root.uri, "Bluesky post URI");
466
+ const author = objectOrNull(root.author);
467
+ const record = objectOrNull(root.record);
468
+ const createdAt = optionalDateTime(record?.createdAt);
469
+ const indexedAt = optionalDateTime(root.indexedAt);
470
+ const timestampUtc = createdAt ?? indexedAt;
471
+ const reply = objectOrNull(record?.reply);
472
+ const replyRoot = reply ? objectOrNull(reply.root) : null;
473
+ const replyParent = reply ? objectOrNull(reply.parent) : null;
474
+ return {
475
+ uri,
476
+ cid: optionalString(root.cid),
477
+ author: {
478
+ did: optionalString(author?.did),
479
+ handle: optionalString(author?.handle),
480
+ displayName: optionalString(author?.displayName),
481
+ },
482
+ createdAt,
483
+ indexedAt,
484
+ timestampUtc,
485
+ timestampSource: createdAt ? "record.createdAt" : indexedAt ? "indexedAt" : null,
486
+ text: typeof record?.text === "string" ? record.text : "",
487
+ languages: stringArray(record?.langs),
488
+ reply: reply
489
+ ? {
490
+ rootUri: optionalString(replyRoot?.uri),
491
+ parentUri: optionalString(replyParent?.uri),
492
+ }
493
+ : null,
494
+ counters: {
495
+ replyCount: nonNegativeInteger(root.replyCount),
496
+ repostCount: nonNegativeInteger(root.repostCount),
497
+ likeCount: nonNegativeInteger(root.likeCount),
498
+ quoteCount: nonNegativeInteger(root.quoteCount),
499
+ },
500
+ };
501
+ }
502
+ function insideWindow(post, query) {
503
+ if (!query.startDateTime && !query.endDateTime)
504
+ return true;
505
+ if (!post.timestampUtc)
506
+ return false;
507
+ if (query.startDateTime && post.timestampUtc < query.startDateTime)
508
+ return false;
509
+ return !(query.endDateTime && post.timestampUtc >= query.endDateTime);
510
+ }
511
+ function parseThread(value, requestedUri, maxRecords) {
512
+ const root = object(value, "Bluesky thread response");
513
+ if (!("thread" in root))
514
+ throw providerInvalid("Bluesky thread response is missing thread.");
515
+ const nodes = [];
516
+ const issues = [];
517
+ const visited = new Set();
518
+ const stack = [
519
+ { value: root.thread, parentUri: null, depth: 0 },
520
+ ];
521
+ let truncated = false;
522
+ while (stack.length > 0) {
523
+ if (nodes.length >= maxRecords) {
524
+ truncated = true;
525
+ break;
526
+ }
527
+ const current = stack.pop();
528
+ const item = objectOrNull(current.value);
529
+ if (!item) {
530
+ addThreadIssue(issues, `Ignored a non-object thread node at depth ${current.depth}.`);
531
+ continue;
532
+ }
533
+ const type = typeof item.$type === "string" ? item.$type : "";
534
+ if (type.endsWith("#threadViewPost") || (!type && item.post)) {
535
+ let post = null;
536
+ try {
537
+ post = normalizePost(item.post);
538
+ }
539
+ catch {
540
+ addThreadIssue(issues, `Ignored a thread post without a valid URI at depth ${current.depth}.`);
541
+ }
542
+ if (post && visited.has(post.uri)) {
543
+ addThreadIssue(issues, `Ignored duplicate thread URI ${post.uri}.`);
544
+ continue;
545
+ }
546
+ if (post) {
547
+ visited.add(post.uri);
548
+ nodes.push({
549
+ state: "post",
550
+ uri: post.uri,
551
+ parentUri: current.parentUri,
552
+ depth: current.depth,
553
+ post,
554
+ });
555
+ }
556
+ const replies = item.replies ?? [];
557
+ if (!Array.isArray(replies)) {
558
+ addThreadIssue(issues, `Ignored invalid replies for ${post?.uri ?? requestedUri}.`);
559
+ continue;
560
+ }
561
+ for (let index = replies.length - 1; index >= 0; index -= 1) {
562
+ if (!objectOrNull(replies[index])) {
563
+ addThreadIssue(issues, `Ignored a non-object reply under ${post?.uri ?? requestedUri}.`);
564
+ continue;
565
+ }
566
+ stack.push({
567
+ value: replies[index],
568
+ parentUri: post?.uri ?? current.parentUri,
569
+ depth: current.depth + 1,
570
+ });
571
+ }
572
+ continue;
573
+ }
574
+ if (type.endsWith("#blockedPost") || type.endsWith("#notFoundPost")) {
575
+ const uri = optionalString(item.uri);
576
+ if (uri && visited.has(uri)) {
577
+ addThreadIssue(issues, `Ignored duplicate missing-node URI ${uri}.`);
578
+ continue;
579
+ }
580
+ if (uri)
581
+ visited.add(uri);
582
+ nodes.push({
583
+ state: type.endsWith("#blockedPost") ? "blocked" : "not-found",
584
+ uri,
585
+ parentUri: current.parentUri,
586
+ depth: current.depth,
587
+ post: null,
588
+ });
589
+ continue;
590
+ }
591
+ addThreadIssue(issues, `Ignored an unsupported thread node type ${type || "<missing>"} for ${requestedUri}.`);
592
+ }
593
+ const uriSet = new Set(nodes.flatMap((node) => (node.uri ? [node.uri] : [])));
594
+ const branchCounts = new Map();
595
+ let maxDepth = 0;
596
+ let orphanCount = 0;
597
+ let missingNodeCount = 0;
598
+ for (const node of nodes) {
599
+ maxDepth = Math.max(maxDepth, node.depth);
600
+ if (node.state !== "post")
601
+ missingNodeCount += 1;
602
+ if (node.parentUri) {
603
+ branchCounts.set(node.parentUri, (branchCounts.get(node.parentUri) ?? 0) + 1);
604
+ if (!uriSet.has(node.parentUri)) {
605
+ orphanCount += 1;
606
+ addThreadIssue(issues, `Thread node ${node.uri ?? "<missing>"} has absent parent ${node.parentUri}.`);
607
+ }
608
+ }
609
+ }
610
+ if (!uriSet.has(requestedUri)) {
611
+ addThreadIssue(issues, `The requested seed URI ${requestedUri} is absent from the thread.`);
612
+ }
613
+ return {
614
+ rootUri: nodes[0]?.uri ?? null,
615
+ nodes,
616
+ truncated,
617
+ validation: {
618
+ maxDepth,
619
+ maxBranchingFactor: Math.max(0, ...branchCounts.values()),
620
+ orphanCount,
621
+ missingNodeCount,
622
+ issues,
623
+ },
624
+ };
625
+ }
626
+ async function requestPublicAppView(context, request) {
627
+ try {
628
+ return {
629
+ response: await context.http.request({
630
+ endpointId: "bluesky-public-appview",
631
+ method: "GET",
632
+ ...request,
633
+ }),
634
+ fallbackUsed: false,
635
+ };
636
+ }
637
+ catch (error) {
638
+ if (!(error instanceof DataRuntimeError) || error.options.details?.status !== 403)
639
+ throw error;
640
+ return {
641
+ response: await context.http.request({
642
+ endpointId: "bluesky-public-appview-fallback",
643
+ method: "GET",
644
+ ...request,
645
+ }),
646
+ fallbackUsed: true,
647
+ };
648
+ }
649
+ }
650
+ function exactDateTime(value) {
651
+ const parsed = new Date(value);
652
+ if (!Number.isFinite(parsed.getTime())) {
653
+ throw new DataRuntimeError("invalid-request", "Bluesky datetime values must be valid UTC timestamps.");
654
+ }
655
+ return parsed.toISOString();
656
+ }
657
+ function optionalDateTime(value) {
658
+ if (typeof value !== "string" || !value)
659
+ return null;
660
+ const parsed = new Date(value);
661
+ return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : null;
662
+ }
663
+ function object(value, label) {
664
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
665
+ throw providerInvalid(`${label} must be an object.`);
666
+ }
667
+ return value;
668
+ }
669
+ function objectOrNull(value) {
670
+ return value && typeof value === "object" && !Array.isArray(value)
671
+ ? value
672
+ : null;
673
+ }
674
+ function requiredText(value, label) {
675
+ if (typeof value !== "string" || !value.trim())
676
+ throw providerInvalid(`${label} is missing.`);
677
+ return value.trim();
678
+ }
679
+ function optionalString(value) {
680
+ return typeof value === "string" && value ? value : null;
681
+ }
682
+ function stringArray(value) {
683
+ if (!Array.isArray(value))
684
+ return [];
685
+ return value.filter((item) => typeof item === "string" && item.length > 0);
686
+ }
687
+ function addThreadIssue(issues, issue) {
688
+ if (issues.length < 100)
689
+ issues.push(issue);
690
+ }
691
+ function nonNegativeInteger(value) {
692
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
693
+ }
694
+ function normalizeProviderFailure(error) {
695
+ return error instanceof DataRuntimeError
696
+ ? error
697
+ : providerInvalid("Bluesky response could not be normalized.");
698
+ }
699
+ function providerInvalid(message) {
700
+ return new DataRuntimeError("provider-response-invalid", message);
701
+ }
702
+ function codePointOrder(left, right) {
703
+ return left < right ? -1 : left > right ? 1 : 0;
704
+ }
705
+ //# sourceMappingURL=bluesky-public-posts.js.map