agency-lang 0.7.3 → 0.7.5
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/agent.agency +15 -0
- package/dist/lib/agents/agency-agent/agent.js +31 -14
- 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
|
@@ -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
|
+
}
|