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.
- package/dist/lib/agents/agency-agent/subagents/code.agency +4 -0
- package/dist/lib/agents/agency-agent/subagents/code.js +5 -1
- package/dist/lib/runtime/agencyLlm.d.ts +7 -0
- package/dist/lib/runtime/agencyLlm.js +2 -0
- package/dist/lib/stdlib/threads.js +22 -2
- package/dist/lib/stdlib/version.d.ts +1 -1
- package/dist/lib/stdlib/version.js +1 -1
- package/package.json +1 -1
- package/stdlib/capabilities.agency +1 -1
- package/stdlib/capabilities.js +1 -1
- package/stdlib/data/wikidata.agency +269 -0
- package/stdlib/data/wikidata.js +2975 -0
- package/stdlib/thread.agency +12 -2
- package/stdlib/thread.js +32 -9
|
@@ -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":
|
|
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
|
-
|
|
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.
|
|
1
|
+
export declare const VERSION = "0.7.4";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.7.
|
|
1
|
+
export const VERSION = "0.7.4";
|
package/package.json
CHANGED
|
@@ -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. */
|
package/stdlib/capabilities.js
CHANGED
|
@@ -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
|
+
}
|