agency-lang 0.7.3 → 0.7.4

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.
@@ -366,6 +366,10 @@ questions and there is NO next turn. Therefore:
366
366
  If something is ambiguous, state the most reasonable assumption and
367
367
  proceed.
368
368
  - NEVER defer work to "the next turn" or hand back a plan — do it now.
369
+ - NEVER end with an empty response or zero tool calls. If the task is hard,
370
+ underspecified, or outside your usual area, still take a concrete first
371
+ action toward it and write your best attempt to the required file — an
372
+ imperfect artifact on disk always beats producing nothing.
369
373
  - Leave the task COMPLETE on disk: write the required file(s), then verify
370
374
  (compile, run, run any tests) and fix what fails before you stop.
371
375
  - If you are running low on tool budget, spend the remaining calls
@@ -553,6 +553,10 @@ questions and there is NO next turn. Therefore:
553
553
  If something is ambiguous, state the most reasonable assumption and
554
554
  proceed.
555
555
  - NEVER defer work to "the next turn" or hand back a plan \u2014 do it now.
556
+ - NEVER end with an empty response or zero tool calls. If the task is hard,
557
+ underspecified, or outside your usual area, still take a concrete first
558
+ action toward it and write your best attempt to the required file \u2014 an
559
+ imperfect artifact on disk always beats producing nothing.
556
560
  - Leave the task COMPLETE on disk: write the required file(s), then verify
557
561
  (compile, run, run any tests) and fix what fails before you stop.
558
562
  - If you are running low on tool budget, spend the remaining calls
@@ -1232,7 +1236,7 @@ const codeAgent = __AgencyFunction.create({
1232
1236
  exported: true
1233
1237
  }, __toolRegistry);
1234
1238
  var stdin_default = graph;
1235
- const __sourceMap = { "dist/lib/agents/agency-agent/subagents/code.agency:agencyCli": { "1": { "line": 66, "col": 2 } }, "dist/lib/agents/agency-agent/subagents/code.agency:setCodeOneShot": { "1": { "line": 378, "col": 2 } }, "dist/lib/agents/agency-agent/subagents/code.agency:codeAgent": { "1": { "line": 394, "col": 2 }, "2": { "line": 395, "col": 2 }, "3": { "line": 396, "col": 2 }, "4": { "line": 397, "col": 2 }, "5": { "line": 401, "col": 2 }, "6": { "line": 402, "col": 2 }, "7": { "line": 405, "col": 2 }, "8": { "line": 412, "col": 2 }, "9": { "line": 413, "col": 2 }, "11": { "line": 465, "col": 2 }, "4.0": { "line": 398, "col": 4 }, "9.0": { "line": 414, "col": 4 }, "9.1.0": { "line": 416, "col": 6 }, "9.1.2.1": { "line": 421, "col": 8 }, "9.1.2.2.0": { "line": 423, "col": 10 }, "9.1.2.2": { "line": 422, "col": 8 }, "9.1.2.4.0": { "line": 426, "col": 10 }, "9.1.2.4": { "line": 425, "col": 8 }, "9.1.2.5": { "line": 428, "col": 8 }, "9.1.2.6": { "line": 429, "col": 8 }, "9.1.2": { "line": 418, "col": 6 }, "9.1.4": { "line": 431, "col": 6 }, "9.1.5": { "line": 439, "col": 6 }, "9.1.6.0": { "line": 441, "col": 8 }, "9.1.6.1": { "line": 442, "col": 8 }, "9.1.6.2": { "line": 443, "col": 8 }, "9.1.6.3": { "line": 445, "col": 8 }, "9.1.6": { "line": 440, "col": 6 }, "9.1": { "line": 415, "col": 4 }, "9.3": { "line": 449, "col": 4 }, "9.4": { "line": 463, "col": 4 } }, "dist/lib/agents/agency-agent/subagents/code.agency:__block_0": {} };
1239
+ const __sourceMap = { "dist/lib/agents/agency-agent/subagents/code.agency:agencyCli": { "1": { "line": 66, "col": 2 } }, "dist/lib/agents/agency-agent/subagents/code.agency:setCodeOneShot": { "1": { "line": 382, "col": 2 } }, "dist/lib/agents/agency-agent/subagents/code.agency:codeAgent": { "1": { "line": 398, "col": 2 }, "2": { "line": 399, "col": 2 }, "3": { "line": 400, "col": 2 }, "4": { "line": 401, "col": 2 }, "5": { "line": 405, "col": 2 }, "6": { "line": 406, "col": 2 }, "7": { "line": 409, "col": 2 }, "8": { "line": 416, "col": 2 }, "9": { "line": 417, "col": 2 }, "11": { "line": 469, "col": 2 }, "4.0": { "line": 402, "col": 4 }, "9.0": { "line": 418, "col": 4 }, "9.1.0": { "line": 420, "col": 6 }, "9.1.2.1": { "line": 425, "col": 8 }, "9.1.2.2.0": { "line": 427, "col": 10 }, "9.1.2.2": { "line": 426, "col": 8 }, "9.1.2.4.0": { "line": 430, "col": 10 }, "9.1.2.4": { "line": 429, "col": 8 }, "9.1.2.5": { "line": 432, "col": 8 }, "9.1.2.6": { "line": 433, "col": 8 }, "9.1.2": { "line": 422, "col": 6 }, "9.1.4": { "line": 435, "col": 6 }, "9.1.5": { "line": 443, "col": 6 }, "9.1.6.0": { "line": 445, "col": 8 }, "9.1.6.1": { "line": 446, "col": 8 }, "9.1.6.2": { "line": 447, "col": 8 }, "9.1.6.3": { "line": 449, "col": 8 }, "9.1.6": { "line": 444, "col": 6 }, "9.1": { "line": 419, "col": 4 }, "9.3": { "line": 453, "col": 4 }, "9.4": { "line": 467, "col": 4 } }, "dist/lib/agents/agency-agent/subagents/code.agency:__block_0": {} };
1236
1240
  export {
1237
1241
  __getCheckpoints,
1238
1242
  __invokeFunction,
@@ -47,6 +47,13 @@ export type LlmOpts<S extends z.ZodSchema = z.ZodSchema> = RetryConfig & {
47
47
  * prompt. Subsequent `agency.llm` calls without `opts.model` use
48
48
  * the client's default. */
49
49
  model?: string;
50
+ /** Cap on generated output tokens for this call. Maps to the
51
+ * provider's `maxTokens`. Set it whenever the expected output is
52
+ * small and bounded (labels, summaries): without a cap, a model
53
+ * that fails to emit its stop token — small local models under a
54
+ * structured-output grammar are the common case — generates until
55
+ * its context window fills. */
56
+ maxTokens?: number;
50
57
  /** Structured-output schema. Maps to `runPrompt`'s `responseFormat`.
51
58
  * When set, the response is parsed and the call returns
52
59
  * `z.infer<S>` instead of the raw string content. */
@@ -10,6 +10,8 @@ export async function llm(prompt, opts = {}) {
10
10
  const clientConfig = {};
11
11
  if (opts.model !== undefined)
12
12
  clientConfig.model = opts.model;
13
+ if (opts.maxTokens !== undefined)
14
+ clientConfig.maxTokens = opts.maxTokens;
13
15
  // Resilience options ride a dedicated `retryConfig` parameter (cleanly
14
16
  // separated from provider-shaped `clientConfig`). Pass even when all three
15
17
  // are undefined — `resolveRetryPolicy` uses `firstDefined` and just falls
@@ -129,11 +129,20 @@ const _summarySchema = z.object({ summary: z.string() });
129
129
  /** Build the transcript string used by both the lazy and eager
130
130
  * summarize prompts. Keeps the shape symmetric with the Agency-side
131
131
  * `summarize()` def. Non-string content gets coerced the same way
132
- * `_getThread()` does to keep the prompt deterministic. */
132
+ * `_getThread()` does to keep the prompt deterministic.
133
+ *
134
+ * System/developer messages are excluded: they are instructions, not
135
+ * conversation, and an agent's system prompt can run thousands of
136
+ * tokens — pasting it into the "summarize this conversation" prompt
137
+ * leaks its content into summaries and, on small local models, makes
138
+ * the summarizer itself the most expensive call of the turn. */
133
139
  function _buildSummaryTranscript(messages) {
134
140
  let transcript = "";
135
141
  for (const m of messages) {
136
- transcript += `[${String(m.role)}] ${_contentToString(m.content)}\n`;
142
+ const role = String(m.role);
143
+ if (role === "system" || role === "developer")
144
+ continue;
145
+ transcript += `[${role}] ${_contentToString(m.content)}\n`;
137
146
  }
138
147
  return transcript;
139
148
  }
@@ -177,6 +186,10 @@ export async function _eagerSummarizeIfNeeded(evt) {
177
186
  return;
178
187
  try {
179
188
  const transcript = _buildSummaryTranscript(evt.messages);
189
+ // All-system threads produce an empty transcript — nothing to
190
+ // summarize, so skip the LLM call rather than prompt over "".
191
+ if (transcript === "")
192
+ return;
180
193
  const result = await agency.llm(`Summarize this conversation in 1-2 sentences:\n${transcript}`, {
181
194
  schema: _summarySchema,
182
195
  // Standalone MessageThread — not in the active ThreadStore,
@@ -184,6 +197,13 @@ export async function _eagerSummarizeIfNeeded(evt) {
184
197
  // isolation guarantee as `thread(hidden: true) { ... }` on
185
198
  // the Agency side.
186
199
  thread: new MessageThread(),
200
+ // A 1-2 sentence summary is small and bounded. Uncapped, a
201
+ // small local model under the structured-output grammar can
202
+ // miss its stop token and generate until the context window
203
+ // fills — and Runner.thread AWAITS this hook at thread close,
204
+ // so that runaway blocks the whole turn (the agent's reply is
205
+ // computed but never rendered).
206
+ maxTokens: 256,
187
207
  });
188
208
  _setThreadSummary(evt.threadId, result.summary);
189
209
  }
@@ -1 +1 @@
1
- export declare const VERSION = "0.7.3";
1
+ export declare const VERSION = "0.7.4";
@@ -1 +1 @@
1
- export const VERSION = "0.7.3";
1
+ export const VERSION = "0.7.4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -42,7 +42,7 @@ export effectSet FileSystem = <FileRead, FileWrite>
42
42
  export effectSet Shell = <std::bash, std::exec, std::run>
43
43
 
44
44
  /** Anything that talks to the outside world over the network. */
45
- export effectSet Network = <std::http::fetch, std::http::fetchJSON, std::http::fetchMarkdown, std::search, std::tavilySearch, std::weather, std::browserUse, std::wikipedia::article, std::wikipedia::search, std::wikipedia::summary, std::gdelt, std::fred, std::dbnomics, std::edgar, std::littlesis, std::yc, std::hackernews>
45
+ export effectSet Network = <std::http::fetch, std::http::fetchJSON, std::http::fetchMarkdown, std::search, std::tavilySearch, std::weather, std::browserUse, std::wikipedia::article, std::wikipedia::search, std::wikipedia::summary, std::gdelt, std::fred, std::dbnomics, std::edgar, std::littlesis, std::yc, std::hackernews, std::wikidata>
46
46
 
47
47
  /** The std::data/finance connectors (news + macro + filings). Each also raises
48
48
  std::http::fetchJSON, which is covered by the broader Network set. */
@@ -155,7 +155,7 @@ const FileRead = z.union([z.literal("std::read"), z.literal("std::readBinary"),
155
155
  const FileWrite = z.union([z.literal("std::write"), z.literal("std::writeBinary"), z.literal("std::edit"), z.literal("std::applyPatch"), z.literal("std::mkdir"), z.literal("std::move"), z.literal("std::copy"), z.literal("std::remove")]);
156
156
  const FileSystem = z.union([FileRead, FileWrite]);
157
157
  const Shell = z.union([z.literal("std::bash"), z.literal("std::exec"), z.literal("std::run")]);
158
- const Network = z.union([z.literal("std::http::fetch"), z.literal("std::http::fetchJSON"), z.literal("std::http::fetchMarkdown"), z.literal("std::search"), z.literal("std::tavilySearch"), z.literal("std::weather"), z.literal("std::browserUse"), z.literal("std::wikipedia::article"), z.literal("std::wikipedia::search"), z.literal("std::wikipedia::summary"), z.literal("std::gdelt"), z.literal("std::fred"), z.literal("std::dbnomics"), z.literal("std::edgar"), z.literal("std::littlesis"), z.literal("std::yc"), z.literal("std::hackernews")]);
158
+ const Network = z.union([z.literal("std::http::fetch"), z.literal("std::http::fetchJSON"), z.literal("std::http::fetchMarkdown"), z.literal("std::search"), z.literal("std::tavilySearch"), z.literal("std::weather"), z.literal("std::browserUse"), z.literal("std::wikipedia::article"), z.literal("std::wikipedia::search"), z.literal("std::wikipedia::summary"), z.literal("std::gdelt"), z.literal("std::fred"), z.literal("std::dbnomics"), z.literal("std::edgar"), z.literal("std::littlesis"), z.literal("std::yc"), z.literal("std::hackernews"), z.literal("std::wikidata")]);
159
159
  const DataFinance = z.union([z.literal("std::gdelt"), z.literal("std::fred"), z.literal("std::edgar"), z.literal("std::dbnomics")]);
160
160
  const Messaging = z.union([z.literal("std::sendEmail"), z.literal("std::sendSms"), z.literal("std::sendIMessage")]);
161
161
  const Secrets = z.union([z.literal("std::getSecret"), z.literal("std::setSecret"), z.literal("std::deleteSecret")]);
@@ -0,0 +1,269 @@
1
+ import { fetchJSON } from "std::http"
2
+ import { env } from "std::system"
3
+ import { mapValues, values } from "std::object"
4
+
5
+ /** @module
6
+ ## Wikidata — the open knowledge graph
7
+
8
+ Query [Wikidata](https://www.wikidata.org), the free structured database behind Wikipedia:
9
+ resolve a name to an entity, read an entity's facts, or run a raw SPARQL query over the graph.
10
+ Use it for broad entity research — people, organizations, places, works, and their typed
11
+ relationships — complementing the power-network focus of `std::data/people/littlesis`.
12
+
13
+ Recipe: `wikidataSearch` a name to get a QID, then `wikidataEntity(qid)` for its facts, or
14
+ `wikidataQuery` for graph traversal. Entity claims and SPARQL results reference other entities by
15
+ QID (e.g. "Q5"); resolve those with another search/entity call, or ask SPARQL for labels with
16
+ `SERVICE wikibase:label`. No API key required (WDQS is rate-limited and wants a descriptive
17
+ User-Agent — set WIKIDATA_USER_AGENT to override the default).
18
+
19
+ ### Usage
20
+
21
+ ```ts
22
+ import { wikidataSearch, wikidataEntity } from "std::data/wikidata"
23
+
24
+ node main() {
25
+ const hits = wikidataSearch("Ada Lovelace") catch []
26
+ if (hits.length > 0) {
27
+ const e = wikidataEntity(hits[0].id) catch { id: "", label: "", description: "", aliases: [], claims: {} }
28
+ print("${e.label}: ${e.description}")
29
+ }
30
+ }
31
+ ```
32
+ */
33
+
34
+ effect std::wikidata { op: string, query: string }
35
+
36
+ static const WD_BASE = "https://www.wikidata.org"
37
+ static const WD_DOMAINS = ["www.wikidata.org"]
38
+ static const WDQS_BASE = "https://query.wikidata.org"
39
+ static const WDQS_DOMAINS = ["query.wikidata.org"]
40
+
41
+ // WDQS requires a descriptive User-Agent. Read once per run (env), like edgar's SEC_USER_AGENT.
42
+ const wikidataUserAgent = env("WIKIDATA_USER_AGENT") ?? "agency-lang (https://agency-lang.com)"
43
+ const WD_HEADERS = { "User-Agent": wikidataUserAgent }
44
+
45
+ /** A Wikidata entity from a name search. `id` is the QID (e.g. "Q42"). */
46
+ export type Entity = {
47
+ id: string;
48
+ label: string;
49
+ description: string;
50
+ url: string
51
+ }
52
+
53
+ /** One entity's facts. `claims` maps a property id (e.g. "P31") to its values; a value is a QID for
54
+ entity-valued claims, or the literal's string form otherwise. Resolve QIDs with another
55
+ search/entity call. */
56
+ export type EntityDetail = {
57
+ id: string;
58
+ label: string;
59
+ description: string;
60
+ aliases: string[];
61
+ claims: Record<string, string[]>
62
+ }
63
+
64
+ /** Build the entity-search path (wbsearchentities). Pure. */
65
+ safe def buildSearchPath(name: string, limit: number): string {
66
+ const q = encodeURIComponent(name)
67
+ return "/w/api.php?action=wbsearchentities&search=${q}&language=en&format=json&limit=${limit}"
68
+ }
69
+
70
+ /** Build the entity-data path. Pure. */
71
+ safe def buildEntityPath(qid: string): string {
72
+ return "/wiki/Special:EntityData/${encodeURIComponent(qid)}.json"
73
+ }
74
+
75
+ /** Build the SPARQL path. Pure. */
76
+ safe def buildQueryPath(sparql: string): string {
77
+ const q = encodeURIComponent(sparql)
78
+ return "/sparql?query=${q}&format=json"
79
+ }
80
+
81
+ /** Reshape a wbsearchentities body into Entity[]. Pure/total. */
82
+ safe def parseSearch(raw: any): Entity[] {
83
+ const r: any = raw ?? {}
84
+ const results = r.search ?? []
85
+ return map(results) as s {
86
+ const e: any = s ?? {}
87
+ return {
88
+ id: e.id ?? "",
89
+ label: e.label ?? "",
90
+ description: e.description ?? "",
91
+ url: e.concepturi ?? ""
92
+ }
93
+ }
94
+ }
95
+
96
+ /** Reduce a claim's mainsnak to a string by datavalue type. A novalue/somevalue snak → "".
97
+ Pure/total. */
98
+ safe def parseClaimValue(mainsnak: any): string {
99
+ const snak: any = mainsnak ?? {}
100
+ if ((snak.snaktype ?? "") != "value") {
101
+ return ""
102
+ }
103
+ const dv: any = snak.datavalue ?? {}
104
+ const t = dv.type ?? ""
105
+ const v: any = dv.value ?? null
106
+ if (t == "wikibase-entityid") {
107
+ return v.id ?? ""
108
+ }
109
+ if (t == "string") {
110
+ return v ?? ""
111
+ }
112
+ if (t == "monolingualtext") {
113
+ return v.text ?? ""
114
+ }
115
+ if (t == "time") {
116
+ return v.time ?? ""
117
+ }
118
+ if (t == "quantity") {
119
+ return v.amount ?? ""
120
+ }
121
+ if (t == "globecoordinate") {
122
+ return "${v.latitude ?? 0},${v.longitude ?? 0}"
123
+ }
124
+ return ""
125
+ }
126
+
127
+ /** Map a property's statements to a list of stringified claim values. Pure/total. */
128
+ safe def parseClaimList(statements: any): string[] {
129
+ const arr = statements ?? []
130
+ return map(arr) as s {
131
+ const st: any = s ?? {}
132
+ return parseClaimValue(st.mainsnak ?? {})
133
+ }
134
+ }
135
+
136
+ /** Reshape the claims object (PID → statements) into PID → string[]. Pure/total. */
137
+ safe def parseClaims(claims: any): Record<string, string[]> {
138
+ const c: any = claims ?? {}
139
+ return mapValues(c) as (statements, pid) {
140
+ return parseClaimList(statements)
141
+ }
142
+ }
143
+
144
+ /** Reshape an EntityData body into EntityDetail. `qid` is the fallback id. Pure/total. */
145
+ safe def parseEntity(raw: any, qid: string): EntityDetail {
146
+ const r: any = raw ?? {}
147
+ const ents: any[] = values(r.entities ?? {})
148
+ const ent: any = ents[0] ?? {}
149
+ const labels: any = ent.labels ?? {}
150
+ const descriptions: any = ent.descriptions ?? {}
151
+ const aliasesObj: any = ent.aliases ?? {}
152
+ const enLabel: any = labels.en ?? {}
153
+ const enDesc: any = descriptions.en ?? {}
154
+ const enAliases: any[] = aliasesObj.en ?? []
155
+ return {
156
+ id: ent.id ?? qid,
157
+ label: enLabel.value ?? "",
158
+ description: enDesc.value ?? "",
159
+ aliases: map(enAliases) as a {
160
+ const al: any = a ?? {}
161
+ return al.value ?? ""
162
+ },
163
+ claims: parseClaims(ent.claims ?? {})
164
+ }
165
+ }
166
+
167
+ /** Reshape a SPARQL body into rows (variable → value string). Pure/total. */
168
+ safe def parseBindings(raw: any): Record<string, string>[] {
169
+ const r: any = raw ?? {}
170
+ const res: any = r.results ?? {}
171
+ const bindings = res.bindings ?? []
172
+ return map(bindings) as b {
173
+ const binding: any = b ?? {}
174
+ return mapValues(binding) as (cell, varName) {
175
+ const c: any = cell ?? {}
176
+ return c.value ?? ""
177
+ }
178
+ }
179
+ }
180
+
181
+ /** Shared failure message for a failed Wikidata fetch (either host). Pure. */
182
+ safe def wikidataError(err: any): string {
183
+ return "Wikidata request failed (the API is rate-limited and expects a descriptive User-Agent): ${err.message ?? err}"
184
+ }
185
+
186
+ /** Turn a fetch Result into an Entity[] Result. Pure. */
187
+ safe def searchFinalize(fetchResult: any): Result<Entity[]> {
188
+ return match (fetchResult) {
189
+ success(body) => success(parseSearch(body))
190
+ failure(err) => failure(wikidataError(err))
191
+ }
192
+ }
193
+
194
+ /** Turn a fetch Result into an EntityDetail Result; empty entities (unknown QID) → failure. Pure. */
195
+ safe def entityFinalize(fetchResult: any, qid: string): Result<EntityDetail> {
196
+ return match (fetchResult) {
197
+ success(body) => {
198
+ const b: any = body ?? {}
199
+ const ents: any[] = values(b.entities ?? {})
200
+ if (ents.length == 0) {
201
+ return failure("Wikidata returned no entity for ${qid}")
202
+ }
203
+ return success(parseEntity(b, qid))
204
+ }
205
+ failure(err) => failure(wikidataError(err))
206
+ }
207
+ }
208
+
209
+ /** Turn a SPARQL fetch Result into a rows Result. Pure. */
210
+ safe def queryFinalize(fetchResult: any): Result<Record<string, string>[]> {
211
+ return match (fetchResult) {
212
+ success(body) => success(parseBindings(body))
213
+ failure(err) => failure(wikidataError(err))
214
+ }
215
+ }
216
+
217
+ /** Fetch a Wikidata path (either host) with the User-Agent header, returning the parsed-JSON Result.
218
+ Raises std::http::fetchJSON but approves it internally, so a plain caller sees only the connector's
219
+ own std::wikidata prompt while any OUTER fetch handler still receives (and can reject or propagate)
220
+ the fetch. allowedDomains is enforced inside fetchJSON regardless. Private. */
221
+ safe def wikidataFetch(base: string, domains: string[], path: string): Result raises <std::http::fetchJSON> {
222
+ handle {
223
+ return fetchJSON(baseUrl: base, path: path, headers: WD_HEADERS, allowedDomains: domains)
224
+ } with (data) {
225
+ if (data.effect == "std::http::fetchJSON") {
226
+ return approve()
227
+ }
228
+ }
229
+ }
230
+
231
+ export safe def wikidataSearch(name: string, limit: number = 5): Result<Entity[]> raises <std::wikidata, std::http::fetchJSON> {
232
+ """
233
+ Search Wikidata for entities by name. Returns matches with QID, label, description, and URL. The
234
+ QID identifies the entity for a follow-up entity fetch or SPARQL query. Empty result set returns an
235
+ empty list.
236
+
237
+ @param name - The person, organization, place, or thing to search for
238
+ @param limit - Maximum number of matches to return (default 5)
239
+ """
240
+ return interrupt std::wikidata("Search Wikidata for this name?", { op: "search", query: name })
241
+ const result = wikidataFetch(WD_BASE, WD_DOMAINS, buildSearchPath(name, limit))
242
+ return searchFinalize(result)
243
+ }
244
+
245
+ export safe def wikidataEntity(qid: string): Result<EntityDetail> raises <std::wikidata, std::http::fetchJSON> {
246
+ """
247
+ Fetch one Wikidata entity by its QID. Returns the English label, description, aliases, and claims
248
+ (property id to values; entity-valued claims are QIDs to resolve separately). Unknown QID returns
249
+ a failure.
250
+
251
+ @param qid - The Wikidata entity id, e.g. "Q42"
252
+ """
253
+ return interrupt std::wikidata("Fetch this Wikidata entity?", { op: "entity", query: qid })
254
+ const result = wikidataFetch(WD_BASE, WD_DOMAINS, buildEntityPath(qid))
255
+ return entityFinalize(result, qid)
256
+ }
257
+
258
+ export safe def wikidataQuery(sparql: string): Result<Record<string, string>[]> raises <std::wikidata, std::http::fetchJSON> {
259
+ """
260
+ Run a SPARQL query against the Wikidata Query Service and return rows (each a map of the SELECTed
261
+ variable names to string values). Prefixes wd:/wdt:/rdfs: and SERVICE wikibase:label are available.
262
+ Example: SELECT ?item ?itemLabel WHERE { ?item wdt:P31 wd:Q5 . SERVICE wikibase:label { bd:serviceParam wikibase:language "en" } } LIMIT 10
263
+
264
+ @param sparql - The SPARQL query text
265
+ """
266
+ return interrupt std::wikidata("Run this SPARQL query against Wikidata?", { op: "query", query: sparql })
267
+ const result = wikidataFetch(WDQS_BASE, WDQS_DOMAINS, buildQueryPath(sparql))
268
+ return queryFinalize(result)
269
+ }